CB-4548 Install new node-firefox-* dependencies in node_modules
diff --git a/node_modules/es6-promise/.release.json b/node_modules/es6-promise/.release.json
new file mode 100644
index 0000000..dee8cbc
--- /dev/null
+++ b/node_modules/es6-promise/.release.json
@@ -0,0 +1,17 @@
+{
+  "non-interactive": true,
+  "dry-run": false,
+  "verbose": false,
+  "force": false,
+  "pkgFiles": ["package.json", "bower.json"],
+  "increment": "patch",
+  "commitMessage": "Release %s",
+  "tagName": "%s",
+  "tagAnnotation": "Release %s",
+  "buildCommand": "npm run-script build-all",
+  "distRepo": "git@github.com:components/rsvp.js.git",
+  "distStageDir": "tmp/stage",
+  "distBase": "dist",
+  "distFiles": ["**/*", "../package.json", "../bower.json"],
+  "publish": false
+}
diff --git a/node_modules/es6-promise/Brocfile.js b/node_modules/es6-promise/Brocfile.js
new file mode 100644
index 0000000..d34f458
--- /dev/null
+++ b/node_modules/es6-promise/Brocfile.js
@@ -0,0 +1,75 @@
+/* jshint node:true, undef:true, unused:true */
+var AMDFormatter     = require('es6-module-transpiler-amd-formatter');
+var closureCompiler  = require('broccoli-closure-compiler');
+var compileModules   = require('broccoli-compile-modules');
+var mergeTrees       = require('broccoli-merge-trees');
+var moveFile         = require('broccoli-file-mover');
+var es3Recast        = require('broccoli-es3-safe-recast');
+var concat           = require('broccoli-concat');
+var replace          = require('broccoli-string-replace');
+var calculateVersion = require('./lib/calculateVersion');
+var path             = require('path');
+var trees            = [];
+var env              = process.env.EMBER_ENV || 'development';
+
+var bundle = compileModules('lib', {
+  inputFiles: ['es6-promise.umd.js'],
+  output: '/es6-promise.js',
+  formatter: 'bundle',
+});
+
+trees.push(bundle);
+trees.push(compileModules('lib', {
+  inputFiles: ['**/*.js'],
+  output: '/amd/',
+  formatter: new AMDFormatter()
+}));
+
+if (env === 'production') {
+  trees.push(closureCompiler(moveFile(bundle, {
+    srcFile: 'es6-promise.js',
+    destFile: 'es6-promise.min.js'
+  }), {
+    compilation_level: 'ADVANCED_OPTIMIZATIONS',
+    externs: ['node'],
+  }));
+}
+
+var distTree = mergeTrees(trees.concat('config'));
+var distTrees = [];
+
+distTrees.push(concat(distTree, {
+  inputFiles: [
+    'versionTemplate.txt',
+    'es6-promise.js'
+  ],
+  outputFile: '/es6-promise.js'
+}));
+
+if (env === 'production') {
+  distTrees.push(concat(distTree, {
+    inputFiles: [
+      'versionTemplate.txt',
+      'es6-promise.min.js'
+    ],
+    outputFile: '/es6-promise.min.js'
+  }));
+}
+
+if (env !== 'development') {
+  distTrees = distTrees.map(es3Recast);
+}
+
+distTree = mergeTrees(distTrees);
+var distTree = replace(distTree, {
+  files: [
+    'es6-promise.js',
+    'es6-promise.min.js'
+  ],
+  pattern: {
+    match: /VERSION_PLACEHOLDER_STRING/g,
+    replacement: calculateVersion()
+  }
+});
+
+module.exports = distTree;
diff --git a/node_modules/es6-promise/CHANGELOG.md b/node_modules/es6-promise/CHANGELOG.md
new file mode 100644
index 0000000..e06b496
--- /dev/null
+++ b/node_modules/es6-promise/CHANGELOG.md
@@ -0,0 +1,9 @@
+# Master
+
+# 2.0.0
+
+* re-sync with RSVP. Many large performance improvements and bugfixes.
+
+# 1.0.0
+
+* first subset of RSVP
diff --git a/node_modules/es6-promise/LICENSE b/node_modules/es6-promise/LICENSE
new file mode 100644
index 0000000..954ec59
--- /dev/null
+++ b/node_modules/es6-promise/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/node_modules/es6-promise/README.md b/node_modules/es6-promise/README.md
new file mode 100644
index 0000000..b974ce4
--- /dev/null
+++ b/node_modules/es6-promise/README.md
@@ -0,0 +1,58 @@
+# ES6-Promise (subset of [rsvp.js](https://github.com/tildeio/rsvp.js))
+
+This is a polyfill of the [ES6 Promise](http://people.mozilla.org/~jorendorff/es6-draft.html#sec-promise-constructor). The implementation is a subset of [rsvp.js](https://github.com/tildeio/rsvp.js), if you're wanting extra features and more debugging options, check out the [full library](https://github.com/tildeio/rsvp.js).
+
+For API details and how to use promises, see the <a href="http://www.html5rocks.com/en/tutorials/es6/promises/">JavaScript Promises HTML5Rocks article</a>.
+
+## Downloads
+
+* [es6-promise](https://es6-promises.s3.amazonaws.com/es6-promise-2.0.1.js)
+* [es6-promise-min](https://es6-promises.s3.amazonaws.com/es6-promise-2.0.1.min.js) (~2.2k gzipped)
+
+## Node.js
+
+To install:
+
+```sh
+npm install es6-promise
+```
+
+To use:
+
+```js
+var Promise = require('es6-promise').Promise;
+```
+
+## Usage in IE<9
+
+`catch` is a reserved word in IE<9, meaning `promise.catch(func)` throws a syntax error. To work around this, you can use a string to access the property as shown in the following example.
+
+However, please remember that such technique is already provided by most common minifiers, making the resulting code safe for old browsers and production:
+
+```js
+promise['catch'](function(err) {
+  // ...
+});
+```
+
+Or use `.then` instead:
+
+```js
+promise.then(undefined, function(err) {
+  // ...
+});
+```
+
+## Auto-polyfill
+
+To polyfill the global environment (either in Node or in the browser via CommonJS) use the following code snippet:
+
+```js
+require('es6-promise').polyfill();
+```
+
+Notice that we don't assign the result of `polyfill()` to any variable. The `polyfill()` method will patch the global environment (in this case to the `Promise` name) when called.
+
+## Building & Testing
+
+* `npm run build-all && npm test` - Run Mocha tests through Node and PhantomJS.
diff --git a/node_modules/es6-promise/bin/publish_to_s3.js b/node_modules/es6-promise/bin/publish_to_s3.js
new file mode 100755
index 0000000..7daf49a
--- /dev/null
+++ b/node_modules/es6-promise/bin/publish_to_s3.js
@@ -0,0 +1,28 @@
+#!/usr/bin/env node
+
+// To invoke this from the commandline you need the following to env vars to exist:
+//
+// S3_BUCKET_NAME
+// TRAVIS_BRANCH
+// TRAVIS_TAG
+// TRAVIS_COMMIT
+// S3_SECRET_ACCESS_KEY
+// S3_ACCESS_KEY_ID
+//
+// Once you have those you execute with the following:
+//
+// ```sh
+// ./bin/publish_to_s3.js
+// ```
+var S3Publisher = require('ember-publisher');
+var configPath = require('path').join(__dirname, '../config/s3ProjectConfig.js');
+publisher = new S3Publisher({ projectConfigPath: configPath });
+
+// Always use wildcard section of project config.
+// This is useful when the including library does not
+// require channels (like in ember.js / ember-data).
+publisher.currentBranch = function() {
+  return (process.env.TRAVIS_BRANCH === 'master') ? 'wildcard' : 'no-op';
+};
+publisher.publish();
+
diff --git a/node_modules/es6-promise/bower.json b/node_modules/es6-promise/bower.json
new file mode 100644
index 0000000..84ec5ad
--- /dev/null
+++ b/node_modules/es6-promise/bower.json
@@ -0,0 +1,29 @@
+{
+  "name": "es6-promise",
+  "namespace": "Promise",
+  "version": "2.0.1",
+  "description": "A polyfill for ES6-style Promises, tracking rsvp",
+  "authors": [
+    "Stefan Penner <stefan.penner@gmail.com>"
+  ],
+  "main": "dist/es6-promise.js",
+  "keywords": [
+    "promise"
+  ],
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/jakearchibald/ES6-Promises.git"
+  },
+  "bugs": {
+    "url": "https://github.com/jakearchibald/ES6-Promises/issues"
+  },
+  "license": "MIT",
+  "ignore": [
+    "node_modules",
+    "bower_components",
+    "test",
+    "tests",
+    "vendor",
+    "tasks"
+  ]
+}
diff --git a/node_modules/es6-promise/config/s3ProjectConfig.js b/node_modules/es6-promise/config/s3ProjectConfig.js
new file mode 100644
index 0000000..5f3349a
--- /dev/null
+++ b/node_modules/es6-promise/config/s3ProjectConfig.js
@@ -0,0 +1,26 @@
+/*
+ * Using wildcard because es6-promise does not currently have a
+ * channel system in place.
+ */
+module.exports = function(revision,tag,date){
+  return {
+    'es6-promise.js':
+      { contentType: 'text/javascript',
+        destinations: {
+          wildcard: [
+            'es6-promise-latest.js',
+            'es6-promise-' + revision + '.js'
+          ]
+        }
+      },
+    'es6-promise.min.js':
+      { contentType: 'text/javascript',
+        destinations: {
+          wildcard: [
+            'es6-promise-latest.min.js',
+            'es6-promise-' + revision + '.min.js'
+          ]
+        }
+      }
+  }
+}
diff --git a/node_modules/es6-promise/config/versionTemplate.txt b/node_modules/es6-promise/config/versionTemplate.txt
new file mode 100644
index 0000000..999a5fc
--- /dev/null
+++ b/node_modules/es6-promise/config/versionTemplate.txt
@@ -0,0 +1,7 @@
+/*!
+ * @overview es6-promise - a tiny implementation of Promises/A+.
+ * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald)
+ * @license   Licensed under MIT license
+ *            See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE
+ * @version   VERSION_PLACEHOLDER_STRING
+ */
diff --git a/node_modules/es6-promise/dist/es6-promise.js b/node_modules/es6-promise/dist/es6-promise.js
new file mode 100644
index 0000000..30a6d81
--- /dev/null
+++ b/node_modules/es6-promise/dist/es6-promise.js
@@ -0,0 +1,960 @@
+/*!
+ * @overview es6-promise - a tiny implementation of Promises/A+.
+ * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald)
+ * @license   Licensed under MIT license
+ *            See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE
+ * @version   2.0.1
+ */
+
+(function() {
+    "use strict";
+
+    function $$utils$$objectOrFunction(x) {
+      return typeof x === 'function' || (typeof x === 'object' && x !== null);
+    }
+
+    function $$utils$$isFunction(x) {
+      return typeof x === 'function';
+    }
+
+    function $$utils$$isMaybeThenable(x) {
+      return typeof x === 'object' && x !== null;
+    }
+
+    var $$utils$$_isArray;
+
+    if (!Array.isArray) {
+      $$utils$$_isArray = function (x) {
+        return Object.prototype.toString.call(x) === '[object Array]';
+      };
+    } else {
+      $$utils$$_isArray = Array.isArray;
+    }
+
+    var $$utils$$isArray = $$utils$$_isArray;
+    var $$utils$$now = Date.now || function() { return new Date().getTime(); };
+    function $$utils$$F() { }
+
+    var $$utils$$o_create = (Object.create || function (o) {
+      if (arguments.length > 1) {
+        throw new Error('Second argument not supported');
+      }
+      if (typeof o !== 'object') {
+        throw new TypeError('Argument must be an object');
+      }
+      $$utils$$F.prototype = o;
+      return new $$utils$$F();
+    });
+
+    var $$asap$$len = 0;
+
+    var $$asap$$default = function asap(callback, arg) {
+      $$asap$$queue[$$asap$$len] = callback;
+      $$asap$$queue[$$asap$$len + 1] = arg;
+      $$asap$$len += 2;
+      if ($$asap$$len === 2) {
+        // If len is 1, that means that we need to schedule an async flush.
+        // If additional callbacks are queued before the queue is flushed, they
+        // will be processed by this flush that we are scheduling.
+        $$asap$$scheduleFlush();
+      }
+    };
+
+    var $$asap$$browserGlobal = (typeof window !== 'undefined') ? window : {};
+    var $$asap$$BrowserMutationObserver = $$asap$$browserGlobal.MutationObserver || $$asap$$browserGlobal.WebKitMutationObserver;
+
+    // test for web worker but not in IE10
+    var $$asap$$isWorker = typeof Uint8ClampedArray !== 'undefined' &&
+      typeof importScripts !== 'undefined' &&
+      typeof MessageChannel !== 'undefined';
+
+    // node
+    function $$asap$$useNextTick() {
+      return function() {
+        process.nextTick($$asap$$flush);
+      };
+    }
+
+    function $$asap$$useMutationObserver() {
+      var iterations = 0;
+      var observer = new $$asap$$BrowserMutationObserver($$asap$$flush);
+      var node = document.createTextNode('');
+      observer.observe(node, { characterData: true });
+
+      return function() {
+        node.data = (iterations = ++iterations % 2);
+      };
+    }
+
+    // web worker
+    function $$asap$$useMessageChannel() {
+      var channel = new MessageChannel();
+      channel.port1.onmessage = $$asap$$flush;
+      return function () {
+        channel.port2.postMessage(0);
+      };
+    }
+
+    function $$asap$$useSetTimeout() {
+      return function() {
+        setTimeout($$asap$$flush, 1);
+      };
+    }
+
+    var $$asap$$queue = new Array(1000);
+
+    function $$asap$$flush() {
+      for (var i = 0; i < $$asap$$len; i+=2) {
+        var callback = $$asap$$queue[i];
+        var arg = $$asap$$queue[i+1];
+
+        callback(arg);
+
+        $$asap$$queue[i] = undefined;
+        $$asap$$queue[i+1] = undefined;
+      }
+
+      $$asap$$len = 0;
+    }
+
+    var $$asap$$scheduleFlush;
+
+    // Decide what async method to use to triggering processing of queued callbacks:
+    if (typeof process !== 'undefined' && {}.toString.call(process) === '[object process]') {
+      $$asap$$scheduleFlush = $$asap$$useNextTick();
+    } else if ($$asap$$BrowserMutationObserver) {
+      $$asap$$scheduleFlush = $$asap$$useMutationObserver();
+    } else if ($$asap$$isWorker) {
+      $$asap$$scheduleFlush = $$asap$$useMessageChannel();
+    } else {
+      $$asap$$scheduleFlush = $$asap$$useSetTimeout();
+    }
+
+    function $$$internal$$noop() {}
+    var $$$internal$$PENDING   = void 0;
+    var $$$internal$$FULFILLED = 1;
+    var $$$internal$$REJECTED  = 2;
+    var $$$internal$$GET_THEN_ERROR = new $$$internal$$ErrorObject();
+
+    function $$$internal$$selfFullfillment() {
+      return new TypeError("You cannot resolve a promise with itself");
+    }
+
+    function $$$internal$$cannotReturnOwn() {
+      return new TypeError('A promises callback cannot return that same promise.')
+    }
+
+    function $$$internal$$getThen(promise) {
+      try {
+        return promise.then;
+      } catch(error) {
+        $$$internal$$GET_THEN_ERROR.error = error;
+        return $$$internal$$GET_THEN_ERROR;
+      }
+    }
+
+    function $$$internal$$tryThen(then, value, fulfillmentHandler, rejectionHandler) {
+      try {
+        then.call(value, fulfillmentHandler, rejectionHandler);
+      } catch(e) {
+        return e;
+      }
+    }
+
+    function $$$internal$$handleForeignThenable(promise, thenable, then) {
+       $$asap$$default(function(promise) {
+        var sealed = false;
+        var error = $$$internal$$tryThen(then, thenable, function(value) {
+          if (sealed) { return; }
+          sealed = true;
+          if (thenable !== value) {
+            $$$internal$$resolve(promise, value);
+          } else {
+            $$$internal$$fulfill(promise, value);
+          }
+        }, function(reason) {
+          if (sealed) { return; }
+          sealed = true;
+
+          $$$internal$$reject(promise, reason);
+        }, 'Settle: ' + (promise._label || ' unknown promise'));
+
+        if (!sealed && error) {
+          sealed = true;
+          $$$internal$$reject(promise, error);
+        }
+      }, promise);
+    }
+
+    function $$$internal$$handleOwnThenable(promise, thenable) {
+      if (thenable._state === $$$internal$$FULFILLED) {
+        $$$internal$$fulfill(promise, thenable._result);
+      } else if (promise._state === $$$internal$$REJECTED) {
+        $$$internal$$reject(promise, thenable._result);
+      } else {
+        $$$internal$$subscribe(thenable, undefined, function(value) {
+          $$$internal$$resolve(promise, value);
+        }, function(reason) {
+          $$$internal$$reject(promise, reason);
+        });
+      }
+    }
+
+    function $$$internal$$handleMaybeThenable(promise, maybeThenable) {
+      if (maybeThenable.constructor === promise.constructor) {
+        $$$internal$$handleOwnThenable(promise, maybeThenable);
+      } else {
+        var then = $$$internal$$getThen(maybeThenable);
+
+        if (then === $$$internal$$GET_THEN_ERROR) {
+          $$$internal$$reject(promise, $$$internal$$GET_THEN_ERROR.error);
+        } else if (then === undefined) {
+          $$$internal$$fulfill(promise, maybeThenable);
+        } else if ($$utils$$isFunction(then)) {
+          $$$internal$$handleForeignThenable(promise, maybeThenable, then);
+        } else {
+          $$$internal$$fulfill(promise, maybeThenable);
+        }
+      }
+    }
+
+    function $$$internal$$resolve(promise, value) {
+      if (promise === value) {
+        $$$internal$$reject(promise, $$$internal$$selfFullfillment());
+      } else if ($$utils$$objectOrFunction(value)) {
+        $$$internal$$handleMaybeThenable(promise, value);
+      } else {
+        $$$internal$$fulfill(promise, value);
+      }
+    }
+
+    function $$$internal$$publishRejection(promise) {
+      if (promise._onerror) {
+        promise._onerror(promise._result);
+      }
+
+      $$$internal$$publish(promise);
+    }
+
+    function $$$internal$$fulfill(promise, value) {
+      if (promise._state !== $$$internal$$PENDING) { return; }
+
+      promise._result = value;
+      promise._state = $$$internal$$FULFILLED;
+
+      if (promise._subscribers.length === 0) {
+      } else {
+        $$asap$$default($$$internal$$publish, promise);
+      }
+    }
+
+    function $$$internal$$reject(promise, reason) {
+      if (promise._state !== $$$internal$$PENDING) { return; }
+      promise._state = $$$internal$$REJECTED;
+      promise._result = reason;
+
+      $$asap$$default($$$internal$$publishRejection, promise);
+    }
+
+    function $$$internal$$subscribe(parent, child, onFulfillment, onRejection) {
+      var subscribers = parent._subscribers;
+      var length = subscribers.length;
+
+      parent._onerror = null;
+
+      subscribers[length] = child;
+      subscribers[length + $$$internal$$FULFILLED] = onFulfillment;
+      subscribers[length + $$$internal$$REJECTED]  = onRejection;
+
+      if (length === 0 && parent._state) {
+        $$asap$$default($$$internal$$publish, parent);
+      }
+    }
+
+    function $$$internal$$publish(promise) {
+      var subscribers = promise._subscribers;
+      var settled = promise._state;
+
+      if (subscribers.length === 0) { return; }
+
+      var child, callback, detail = promise._result;
+
+      for (var i = 0; i < subscribers.length; i += 3) {
+        child = subscribers[i];
+        callback = subscribers[i + settled];
+
+        if (child) {
+          $$$internal$$invokeCallback(settled, child, callback, detail);
+        } else {
+          callback(detail);
+        }
+      }
+
+      promise._subscribers.length = 0;
+    }
+
+    function $$$internal$$ErrorObject() {
+      this.error = null;
+    }
+
+    var $$$internal$$TRY_CATCH_ERROR = new $$$internal$$ErrorObject();
+
+    function $$$internal$$tryCatch(callback, detail) {
+      try {
+        return callback(detail);
+      } catch(e) {
+        $$$internal$$TRY_CATCH_ERROR.error = e;
+        return $$$internal$$TRY_CATCH_ERROR;
+      }
+    }
+
+    function $$$internal$$invokeCallback(settled, promise, callback, detail) {
+      var hasCallback = $$utils$$isFunction(callback),
+          value, error, succeeded, failed;
+
+      if (hasCallback) {
+        value = $$$internal$$tryCatch(callback, detail);
+
+        if (value === $$$internal$$TRY_CATCH_ERROR) {
+          failed = true;
+          error = value.error;
+          value = null;
+        } else {
+          succeeded = true;
+        }
+
+        if (promise === value) {
+          $$$internal$$reject(promise, $$$internal$$cannotReturnOwn());
+          return;
+        }
+
+      } else {
+        value = detail;
+        succeeded = true;
+      }
+
+      if (promise._state !== $$$internal$$PENDING) {
+        // noop
+      } else if (hasCallback && succeeded) {
+        $$$internal$$resolve(promise, value);
+      } else if (failed) {
+        $$$internal$$reject(promise, error);
+      } else if (settled === $$$internal$$FULFILLED) {
+        $$$internal$$fulfill(promise, value);
+      } else if (settled === $$$internal$$REJECTED) {
+        $$$internal$$reject(promise, value);
+      }
+    }
+
+    function $$$internal$$initializePromise(promise, resolver) {
+      try {
+        resolver(function resolvePromise(value){
+          $$$internal$$resolve(promise, value);
+        }, function rejectPromise(reason) {
+          $$$internal$$reject(promise, reason);
+        });
+      } catch(e) {
+        $$$internal$$reject(promise, e);
+      }
+    }
+
+    function $$$enumerator$$makeSettledResult(state, position, value) {
+      if (state === $$$internal$$FULFILLED) {
+        return {
+          state: 'fulfilled',
+          value: value
+        };
+      } else {
+        return {
+          state: 'rejected',
+          reason: value
+        };
+      }
+    }
+
+    function $$$enumerator$$Enumerator(Constructor, input, abortOnReject, label) {
+      this._instanceConstructor = Constructor;
+      this.promise = new Constructor($$$internal$$noop, label);
+      this._abortOnReject = abortOnReject;
+
+      if (this._validateInput(input)) {
+        this._input     = input;
+        this.length     = input.length;
+        this._remaining = input.length;
+
+        this._init();
+
+        if (this.length === 0) {
+          $$$internal$$fulfill(this.promise, this._result);
+        } else {
+          this.length = this.length || 0;
+          this._enumerate();
+          if (this._remaining === 0) {
+            $$$internal$$fulfill(this.promise, this._result);
+          }
+        }
+      } else {
+        $$$internal$$reject(this.promise, this._validationError());
+      }
+    }
+
+    $$$enumerator$$Enumerator.prototype._validateInput = function(input) {
+      return $$utils$$isArray(input);
+    };
+
+    $$$enumerator$$Enumerator.prototype._validationError = function() {
+      return new Error('Array Methods must be provided an Array');
+    };
+
+    $$$enumerator$$Enumerator.prototype._init = function() {
+      this._result = new Array(this.length);
+    };
+
+    var $$$enumerator$$default = $$$enumerator$$Enumerator;
+
+    $$$enumerator$$Enumerator.prototype._enumerate = function() {
+      var length  = this.length;
+      var promise = this.promise;
+      var input   = this._input;
+
+      for (var i = 0; promise._state === $$$internal$$PENDING && i < length; i++) {
+        this._eachEntry(input[i], i);
+      }
+    };
+
+    $$$enumerator$$Enumerator.prototype._eachEntry = function(entry, i) {
+      var c = this._instanceConstructor;
+      if ($$utils$$isMaybeThenable(entry)) {
+        if (entry.constructor === c && entry._state !== $$$internal$$PENDING) {
+          entry._onerror = null;
+          this._settledAt(entry._state, i, entry._result);
+        } else {
+          this._willSettleAt(c.resolve(entry), i);
+        }
+      } else {
+        this._remaining--;
+        this._result[i] = this._makeResult($$$internal$$FULFILLED, i, entry);
+      }
+    };
+
+    $$$enumerator$$Enumerator.prototype._settledAt = function(state, i, value) {
+      var promise = this.promise;
+
+      if (promise._state === $$$internal$$PENDING) {
+        this._remaining--;
+
+        if (this._abortOnReject && state === $$$internal$$REJECTED) {
+          $$$internal$$reject(promise, value);
+        } else {
+          this._result[i] = this._makeResult(state, i, value);
+        }
+      }
+
+      if (this._remaining === 0) {
+        $$$internal$$fulfill(promise, this._result);
+      }
+    };
+
+    $$$enumerator$$Enumerator.prototype._makeResult = function(state, i, value) {
+      return value;
+    };
+
+    $$$enumerator$$Enumerator.prototype._willSettleAt = function(promise, i) {
+      var enumerator = this;
+
+      $$$internal$$subscribe(promise, undefined, function(value) {
+        enumerator._settledAt($$$internal$$FULFILLED, i, value);
+      }, function(reason) {
+        enumerator._settledAt($$$internal$$REJECTED, i, reason);
+      });
+    };
+
+    var $$promise$all$$default = function all(entries, label) {
+      return new $$$enumerator$$default(this, entries, true /* abort on reject */, label).promise;
+    };
+
+    var $$promise$race$$default = function race(entries, label) {
+      /*jshint validthis:true */
+      var Constructor = this;
+
+      var promise = new Constructor($$$internal$$noop, label);
+
+      if (!$$utils$$isArray(entries)) {
+        $$$internal$$reject(promise, new TypeError('You must pass an array to race.'));
+        return promise;
+      }
+
+      var length = entries.length;
+
+      function onFulfillment(value) {
+        $$$internal$$resolve(promise, value);
+      }
+
+      function onRejection(reason) {
+        $$$internal$$reject(promise, reason);
+      }
+
+      for (var i = 0; promise._state === $$$internal$$PENDING && i < length; i++) {
+        $$$internal$$subscribe(Constructor.resolve(entries[i]), undefined, onFulfillment, onRejection);
+      }
+
+      return promise;
+    };
+
+    var $$promise$resolve$$default = function resolve(object, label) {
+      /*jshint validthis:true */
+      var Constructor = this;
+
+      if (object && typeof object === 'object' && object.constructor === Constructor) {
+        return object;
+      }
+
+      var promise = new Constructor($$$internal$$noop, label);
+      $$$internal$$resolve(promise, object);
+      return promise;
+    };
+
+    var $$promise$reject$$default = function reject(reason, label) {
+      /*jshint validthis:true */
+      var Constructor = this;
+      var promise = new Constructor($$$internal$$noop, label);
+      $$$internal$$reject(promise, reason);
+      return promise;
+    };
+
+    var $$es6$promise$promise$$counter = 0;
+
+    function $$es6$promise$promise$$needsResolver() {
+      throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
+    }
+
+    function $$es6$promise$promise$$needsNew() {
+      throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");
+    }
+
+    var $$es6$promise$promise$$default = $$es6$promise$promise$$Promise;
+
+    /**
+      Promise objects represent the eventual result of an asynchronous operation. The
+      primary way of interacting with a promise is through its `then` method, which
+      registers callbacks to receive either a promise’s eventual value or the reason
+      why the promise cannot be fulfilled.
+
+      Terminology
+      -----------
+
+      - `promise` is an object or function with a `then` method whose behavior conforms to this specification.
+      - `thenable` is an object or function that defines a `then` method.
+      - `value` is any legal JavaScript value (including undefined, a thenable, or a promise).
+      - `exception` is a value that is thrown using the throw statement.
+      - `reason` is a value that indicates why a promise was rejected.
+      - `settled` the final resting state of a promise, fulfilled or rejected.
+
+      A promise can be in one of three states: pending, fulfilled, or rejected.
+
+      Promises that are fulfilled have a fulfillment value and are in the fulfilled
+      state.  Promises that are rejected have a rejection reason and are in the
+      rejected state.  A fulfillment value is never a thenable.
+
+      Promises can also be said to *resolve* a value.  If this value is also a
+      promise, then the original promise's settled state will match the value's
+      settled state.  So a promise that *resolves* a promise that rejects will
+      itself reject, and a promise that *resolves* a promise that fulfills will
+      itself fulfill.
+
+
+      Basic Usage:
+      ------------
+
+      ```js
+      var promise = new Promise(function(resolve, reject) {
+        // on success
+        resolve(value);
+
+        // on failure
+        reject(reason);
+      });
+
+      promise.then(function(value) {
+        // on fulfillment
+      }, function(reason) {
+        // on rejection
+      });
+      ```
+
+      Advanced Usage:
+      ---------------
+
+      Promises shine when abstracting away asynchronous interactions such as
+      `XMLHttpRequest`s.
+
+      ```js
+      function getJSON(url) {
+        return new Promise(function(resolve, reject){
+          var xhr = new XMLHttpRequest();
+
+          xhr.open('GET', url);
+          xhr.onreadystatechange = handler;
+          xhr.responseType = 'json';
+          xhr.setRequestHeader('Accept', 'application/json');
+          xhr.send();
+
+          function handler() {
+            if (this.readyState === this.DONE) {
+              if (this.status === 200) {
+                resolve(this.response);
+              } else {
+                reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']'));
+              }
+            }
+          };
+        });
+      }
+
+      getJSON('/posts.json').then(function(json) {
+        // on fulfillment
+      }, function(reason) {
+        // on rejection
+      });
+      ```
+
+      Unlike callbacks, promises are great composable primitives.
+
+      ```js
+      Promise.all([
+        getJSON('/posts'),
+        getJSON('/comments')
+      ]).then(function(values){
+        values[0] // => postsJSON
+        values[1] // => commentsJSON
+
+        return values;
+      });
+      ```
+
+      @class Promise
+      @param {function} resolver
+      Useful for tooling.
+      @constructor
+    */
+    function $$es6$promise$promise$$Promise(resolver) {
+      this._id = $$es6$promise$promise$$counter++;
+      this._state = undefined;
+      this._result = undefined;
+      this._subscribers = [];
+
+      if ($$$internal$$noop !== resolver) {
+        if (!$$utils$$isFunction(resolver)) {
+          $$es6$promise$promise$$needsResolver();
+        }
+
+        if (!(this instanceof $$es6$promise$promise$$Promise)) {
+          $$es6$promise$promise$$needsNew();
+        }
+
+        $$$internal$$initializePromise(this, resolver);
+      }
+    }
+
+    $$es6$promise$promise$$Promise.all = $$promise$all$$default;
+    $$es6$promise$promise$$Promise.race = $$promise$race$$default;
+    $$es6$promise$promise$$Promise.resolve = $$promise$resolve$$default;
+    $$es6$promise$promise$$Promise.reject = $$promise$reject$$default;
+
+    $$es6$promise$promise$$Promise.prototype = {
+      constructor: $$es6$promise$promise$$Promise,
+
+    /**
+      The primary way of interacting with a promise is through its `then` method,
+      which registers callbacks to receive either a promise's eventual value or the
+      reason why the promise cannot be fulfilled.
+
+      ```js
+      findUser().then(function(user){
+        // user is available
+      }, function(reason){
+        // user is unavailable, and you are given the reason why
+      });
+      ```
+
+      Chaining
+      --------
+
+      The return value of `then` is itself a promise.  This second, 'downstream'
+      promise is resolved with the return value of the first promise's fulfillment
+      or rejection handler, or rejected if the handler throws an exception.
+
+      ```js
+      findUser().then(function (user) {
+        return user.name;
+      }, function (reason) {
+        return 'default name';
+      }).then(function (userName) {
+        // If `findUser` fulfilled, `userName` will be the user's name, otherwise it
+        // will be `'default name'`
+      });
+
+      findUser().then(function (user) {
+        throw new Error('Found user, but still unhappy');
+      }, function (reason) {
+        throw new Error('`findUser` rejected and we're unhappy');
+      }).then(function (value) {
+        // never reached
+      }, function (reason) {
+        // if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'.
+        // If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'.
+      });
+      ```
+      If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream.
+
+      ```js
+      findUser().then(function (user) {
+        throw new PedagogicalException('Upstream error');
+      }).then(function (value) {
+        // never reached
+      }).then(function (value) {
+        // never reached
+      }, function (reason) {
+        // The `PedgagocialException` is propagated all the way down to here
+      });
+      ```
+
+      Assimilation
+      ------------
+
+      Sometimes the value you want to propagate to a downstream promise can only be
+      retrieved asynchronously. This can be achieved by returning a promise in the
+      fulfillment or rejection handler. The downstream promise will then be pending
+      until the returned promise is settled. This is called *assimilation*.
+
+      ```js
+      findUser().then(function (user) {
+        return findCommentsByAuthor(user);
+      }).then(function (comments) {
+        // The user's comments are now available
+      });
+      ```
+
+      If the assimliated promise rejects, then the downstream promise will also reject.
+
+      ```js
+      findUser().then(function (user) {
+        return findCommentsByAuthor(user);
+      }).then(function (comments) {
+        // If `findCommentsByAuthor` fulfills, we'll have the value here
+      }, function (reason) {
+        // If `findCommentsByAuthor` rejects, we'll have the reason here
+      });
+      ```
+
+      Simple Example
+      --------------
+
+      Synchronous Example
+
+      ```javascript
+      var result;
+
+      try {
+        result = findResult();
+        // success
+      } catch(reason) {
+        // failure
+      }
+      ```
+
+      Errback Example
+
+      ```js
+      findResult(function(result, err){
+        if (err) {
+          // failure
+        } else {
+          // success
+        }
+      });
+      ```
+
+      Promise Example;
+
+      ```javascript
+      findResult().then(function(result){
+        // success
+      }, function(reason){
+        // failure
+      });
+      ```
+
+      Advanced Example
+      --------------
+
+      Synchronous Example
+
+      ```javascript
+      var author, books;
+
+      try {
+        author = findAuthor();
+        books  = findBooksByAuthor(author);
+        // success
+      } catch(reason) {
+        // failure
+      }
+      ```
+
+      Errback Example
+
+      ```js
+
+      function foundBooks(books) {
+
+      }
+
+      function failure(reason) {
+
+      }
+
+      findAuthor(function(author, err){
+        if (err) {
+          failure(err);
+          // failure
+        } else {
+          try {
+            findBoooksByAuthor(author, function(books, err) {
+              if (err) {
+                failure(err);
+              } else {
+                try {
+                  foundBooks(books);
+                } catch(reason) {
+                  failure(reason);
+                }
+              }
+            });
+          } catch(error) {
+            failure(err);
+          }
+          // success
+        }
+      });
+      ```
+
+      Promise Example;
+
+      ```javascript
+      findAuthor().
+        then(findBooksByAuthor).
+        then(function(books){
+          // found books
+      }).catch(function(reason){
+        // something went wrong
+      });
+      ```
+
+      @method then
+      @param {Function} onFulfilled
+      @param {Function} onRejected
+      Useful for tooling.
+      @return {Promise}
+    */
+      then: function(onFulfillment, onRejection) {
+        var parent = this;
+        var state = parent._state;
+
+        if (state === $$$internal$$FULFILLED && !onFulfillment || state === $$$internal$$REJECTED && !onRejection) {
+          return this;
+        }
+
+        var child = new this.constructor($$$internal$$noop);
+        var result = parent._result;
+
+        if (state) {
+          var callback = arguments[state - 1];
+          $$asap$$default(function(){
+            $$$internal$$invokeCallback(state, child, callback, result);
+          });
+        } else {
+          $$$internal$$subscribe(parent, child, onFulfillment, onRejection);
+        }
+
+        return child;
+      },
+
+    /**
+      `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same
+      as the catch block of a try/catch statement.
+
+      ```js
+      function findAuthor(){
+        throw new Error('couldn't find that author');
+      }
+
+      // synchronous
+      try {
+        findAuthor();
+      } catch(reason) {
+        // something went wrong
+      }
+
+      // async with promises
+      findAuthor().catch(function(reason){
+        // something went wrong
+      });
+      ```
+
+      @method catch
+      @param {Function} onRejection
+      Useful for tooling.
+      @return {Promise}
+    */
+      'catch': function(onRejection) {
+        return this.then(null, onRejection);
+      }
+    };
+
+    var $$es6$promise$polyfill$$default = function polyfill() {
+      var local;
+
+      if (typeof global !== 'undefined') {
+        local = global;
+      } else if (typeof window !== 'undefined' && window.document) {
+        local = window;
+      } else {
+        local = self;
+      }
+
+      var es6PromiseSupport =
+        "Promise" in local &&
+        // Some of these methods are missing from
+        // Firefox/Chrome experimental implementations
+        "resolve" in local.Promise &&
+        "reject" in local.Promise &&
+        "all" in local.Promise &&
+        "race" in local.Promise &&
+        // Older version of the spec had a resolver object
+        // as the arg rather than a function
+        (function() {
+          var resolve;
+          new local.Promise(function(r) { resolve = r; });
+          return $$utils$$isFunction(resolve);
+        }());
+
+      if (!es6PromiseSupport) {
+        local.Promise = $$es6$promise$promise$$default;
+      }
+    };
+
+    var es6$promise$umd$$ES6Promise = {
+      'Promise': $$es6$promise$promise$$default,
+      'polyfill': $$es6$promise$polyfill$$default
+    };
+
+    /* global define:true module:true window: true */
+    if (typeof define === 'function' && define['amd']) {
+      define(function() { return es6$promise$umd$$ES6Promise; });
+    } else if (typeof module !== 'undefined' && module['exports']) {
+      module['exports'] = es6$promise$umd$$ES6Promise;
+    } else if (typeof this !== 'undefined') {
+      this['ES6Promise'] = es6$promise$umd$$ES6Promise;
+    }
+}).call(this);
\ No newline at end of file
diff --git a/node_modules/es6-promise/dist/es6-promise.min.js b/node_modules/es6-promise/dist/es6-promise.min.js
new file mode 100644
index 0000000..6e0c99f
--- /dev/null
+++ b/node_modules/es6-promise/dist/es6-promise.min.js
@@ -0,0 +1,18 @@
+/*!
+ * @overview es6-promise - a tiny implementation of Promises/A+.
+ * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald)
+ * @license   Licensed under MIT license
+ *            See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE
+ * @version   2.0.1
+ */
+
+(function(){function r(a,b){n[l]=a;n[l+1]=b;l+=2;2===l&&A()}function s(a){return"function"===typeof a}function F(){return function(){process.nextTick(t)}}function G(){var a=0,b=new B(t),c=document.createTextNode("");b.observe(c,{characterData:!0});return function(){c.data=a=++a%2}}function H(){var a=new MessageChannel;a.port1.onmessage=t;return function(){a.port2.postMessage(0)}}function I(){return function(){setTimeout(t,1)}}function t(){for(var a=0;a<l;a+=2)(0,n[a])(n[a+1]),n[a]=void 0,n[a+1]=void 0;
+l=0}function p(){}function J(a,b,c,d){try{a.call(b,c,d)}catch(e){return e}}function K(a,b,c){r(function(a){var e=!1,f=J(c,b,function(c){e||(e=!0,b!==c?q(a,c):m(a,c))},function(b){e||(e=!0,g(a,b))});!e&&f&&(e=!0,g(a,f))},a)}function L(a,b){1===b.a?m(a,b.b):2===a.a?g(a,b.b):u(b,void 0,function(b){q(a,b)},function(b){g(a,b)})}function q(a,b){if(a===b)g(a,new TypeError("You cannot resolve a promise with itself"));else if("function"===typeof b||"object"===typeof b&&null!==b)if(b.constructor===a.constructor)L(a,
+b);else{var c;try{c=b.then}catch(d){v.error=d,c=v}c===v?g(a,v.error):void 0===c?m(a,b):s(c)?K(a,b,c):m(a,b)}else m(a,b)}function M(a){a.f&&a.f(a.b);x(a)}function m(a,b){void 0===a.a&&(a.b=b,a.a=1,0!==a.e.length&&r(x,a))}function g(a,b){void 0===a.a&&(a.a=2,a.b=b,r(M,a))}function u(a,b,c,d){var e=a.e,f=e.length;a.f=null;e[f]=b;e[f+1]=c;e[f+2]=d;0===f&&a.a&&r(x,a)}function x(a){var b=a.e,c=a.a;if(0!==b.length){for(var d,e,f=a.b,g=0;g<b.length;g+=3)d=b[g],e=b[g+c],d?C(c,d,e,f):e(f);a.e.length=0}}function D(){this.error=
+null}function C(a,b,c,d){var e=s(c),f,k,h,l;if(e){try{f=c(d)}catch(n){y.error=n,f=y}f===y?(l=!0,k=f.error,f=null):h=!0;if(b===f){g(b,new TypeError("A promises callback cannot return that same promise."));return}}else f=d,h=!0;void 0===b.a&&(e&&h?q(b,f):l?g(b,k):1===a?m(b,f):2===a&&g(b,f))}function N(a,b){try{b(function(b){q(a,b)},function(b){g(a,b)})}catch(c){g(a,c)}}function k(a,b,c,d){this.n=a;this.c=new a(p,d);this.i=c;this.o(b)?(this.m=b,this.d=this.length=b.length,this.l(),0===this.length?m(this.c,
+this.b):(this.length=this.length||0,this.k(),0===this.d&&m(this.c,this.b))):g(this.c,this.p())}function h(a){O++;this.b=this.a=void 0;this.e=[];if(p!==a){if(!s(a))throw new TypeError("You must pass a resolver function as the first argument to the promise constructor");if(!(this instanceof h))throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");N(this,a)}}var E=Array.isArray?Array.isArray:function(a){return"[object Array]"===
+Object.prototype.toString.call(a)},l=0,w="undefined"!==typeof window?window:{},B=w.MutationObserver||w.WebKitMutationObserver,w="undefined"!==typeof Uint8ClampedArray&&"undefined"!==typeof importScripts&&"undefined"!==typeof MessageChannel,n=Array(1E3),A;A="undefined"!==typeof process&&"[object process]"==={}.toString.call(process)?F():B?G():w?H():I();var v=new D,y=new D;k.prototype.o=function(a){return E(a)};k.prototype.p=function(){return Error("Array Methods must be provided an Array")};k.prototype.l=
+function(){this.b=Array(this.length)};k.prototype.k=function(){for(var a=this.length,b=this.c,c=this.m,d=0;void 0===b.a&&d<a;d++)this.j(c[d],d)};k.prototype.j=function(a,b){var c=this.n;"object"===typeof a&&null!==a?a.constructor===c&&void 0!==a.a?(a.f=null,this.g(a.a,b,a.b)):this.q(c.resolve(a),b):(this.d--,this.b[b]=this.h(a))};k.prototype.g=function(a,b,c){var d=this.c;void 0===d.a&&(this.d--,this.i&&2===a?g(d,c):this.b[b]=this.h(c));0===this.d&&m(d,this.b)};k.prototype.h=function(a){return a};
+k.prototype.q=function(a,b){var c=this;u(a,void 0,function(a){c.g(1,b,a)},function(a){c.g(2,b,a)})};var O=0;h.all=function(a,b){return(new k(this,a,!0,b)).c};h.race=function(a,b){function c(a){q(e,a)}function d(a){g(e,a)}var e=new this(p,b);if(!E(a))return (g(e,new TypeError("You must pass an array to race.")), e);for(var f=a.length,h=0;void 0===e.a&&h<f;h++)u(this.resolve(a[h]),void 0,c,d);return e};h.resolve=function(a,b){if(a&&"object"===typeof a&&a.constructor===this)return a;var c=new this(p,b);
+q(c,a);return c};h.reject=function(a,b){var c=new this(p,b);g(c,a);return c};h.prototype={constructor:h,then:function(a,b){var c=this.a;if(1===c&&!a||2===c&&!b)return this;var d=new this.constructor(p),e=this.b;if(c){var f=arguments[c-1];r(function(){C(c,d,f,e)})}else u(this,d,a,b);return d},"catch":function(a){return this.then(null,a)}};var z={Promise:h,polyfill:function(){var a;a="undefined"!==typeof global?global:"undefined"!==typeof window&&window.document?window:self;"Promise"in a&&"resolve"in
+a.Promise&&"reject"in a.Promise&&"all"in a.Promise&&"race"in a.Promise&&function(){var b;new a.Promise(function(a){b=a});return s(b)}()||(a.Promise=h)}};"function"===typeof define&&define.amd?define(function(){return z}):"undefined"!==typeof module&&module.exports?module.exports=z:"undefined"!==typeof this&&(this.ES6Promise=z)}).call(this);
diff --git a/node_modules/es6-promise/lib/calculateVersion.js b/node_modules/es6-promise/lib/calculateVersion.js
new file mode 100644
index 0000000..018e364
--- /dev/null
+++ b/node_modules/es6-promise/lib/calculateVersion.js
@@ -0,0 +1,36 @@
+'use strict';
+
+var fs   = require('fs');
+var path = require('path');
+
+module.exports = function () {
+  var packageVersion = require('../package.json').version;
+  var output         = [packageVersion];
+  var gitPath        = path.join(__dirname,'..','.git');
+  var headFilePath   = path.join(gitPath, 'HEAD');
+
+  if (packageVersion.indexOf('+') > -1) {
+    try {
+      if (fs.existsSync(headFilePath)) {
+        var headFile = fs.readFileSync(headFilePath, {encoding: 'utf8'});
+        var branchName = headFile.split('/').slice(-1)[0].trim();
+        var refPath = headFile.split(' ')[1];
+        var branchSHA;
+
+        if (refPath) {
+          var branchPath = path.join(gitPath, refPath.trim());
+          branchSHA  = fs.readFileSync(branchPath);
+        } else {
+          branchSHA = branchName;
+        }
+
+        output.push(branchSHA.slice(0,10));
+      }
+    } catch (err) {
+      console.error(err.stack);
+    }
+    return output.join('.');
+  } else {
+    return packageVersion;
+  }
+};
diff --git a/node_modules/es6-promise/lib/es6-promise.umd.js b/node_modules/es6-promise/lib/es6-promise.umd.js
new file mode 100644
index 0000000..cd01553
--- /dev/null
+++ b/node_modules/es6-promise/lib/es6-promise.umd.js
@@ -0,0 +1,16 @@
+import Promise from './es6-promise/promise';
+import polyfill from './es6-promise/polyfill';
+
+var ES6Promise = {
+  'Promise': Promise,
+  'polyfill': polyfill
+};
+
+/* global define:true module:true window: true */
+if (typeof define === 'function' && define['amd']) {
+  define(function() { return ES6Promise; });
+} else if (typeof module !== 'undefined' && module['exports']) {
+  module['exports'] = ES6Promise;
+} else if (typeof this !== 'undefined') {
+  this['ES6Promise'] = ES6Promise;
+}
diff --git a/node_modules/es6-promise/lib/es6-promise/-internal.js b/node_modules/es6-promise/lib/es6-promise/-internal.js
new file mode 100644
index 0000000..afef22e
--- /dev/null
+++ b/node_modules/es6-promise/lib/es6-promise/-internal.js
@@ -0,0 +1,251 @@
+import {
+  objectOrFunction,
+  isFunction
+} from './utils';
+
+import asap from './asap';
+
+function noop() {}
+
+var PENDING   = void 0;
+var FULFILLED = 1;
+var REJECTED  = 2;
+
+var GET_THEN_ERROR = new ErrorObject();
+
+function selfFullfillment() {
+  return new TypeError("You cannot resolve a promise with itself");
+}
+
+function cannotReturnOwn() {
+  return new TypeError('A promises callback cannot return that same promise.')
+}
+
+function getThen(promise) {
+  try {
+    return promise.then;
+  } catch(error) {
+    GET_THEN_ERROR.error = error;
+    return GET_THEN_ERROR;
+  }
+}
+
+function tryThen(then, value, fulfillmentHandler, rejectionHandler) {
+  try {
+    then.call(value, fulfillmentHandler, rejectionHandler);
+  } catch(e) {
+    return e;
+  }
+}
+
+function handleForeignThenable(promise, thenable, then) {
+   asap(function(promise) {
+    var sealed = false;
+    var error = tryThen(then, thenable, function(value) {
+      if (sealed) { return; }
+      sealed = true;
+      if (thenable !== value) {
+        resolve(promise, value);
+      } else {
+        fulfill(promise, value);
+      }
+    }, function(reason) {
+      if (sealed) { return; }
+      sealed = true;
+
+      reject(promise, reason);
+    }, 'Settle: ' + (promise._label || ' unknown promise'));
+
+    if (!sealed && error) {
+      sealed = true;
+      reject(promise, error);
+    }
+  }, promise);
+}
+
+function handleOwnThenable(promise, thenable) {
+  if (thenable._state === FULFILLED) {
+    fulfill(promise, thenable._result);
+  } else if (promise._state === REJECTED) {
+    reject(promise, thenable._result);
+  } else {
+    subscribe(thenable, undefined, function(value) {
+      resolve(promise, value);
+    }, function(reason) {
+      reject(promise, reason);
+    });
+  }
+}
+
+function handleMaybeThenable(promise, maybeThenable) {
+  if (maybeThenable.constructor === promise.constructor) {
+    handleOwnThenable(promise, maybeThenable);
+  } else {
+    var then = getThen(maybeThenable);
+
+    if (then === GET_THEN_ERROR) {
+      reject(promise, GET_THEN_ERROR.error);
+    } else if (then === undefined) {
+      fulfill(promise, maybeThenable);
+    } else if (isFunction(then)) {
+      handleForeignThenable(promise, maybeThenable, then);
+    } else {
+      fulfill(promise, maybeThenable);
+    }
+  }
+}
+
+function resolve(promise, value) {
+  if (promise === value) {
+    reject(promise, selfFullfillment());
+  } else if (objectOrFunction(value)) {
+    handleMaybeThenable(promise, value);
+  } else {
+    fulfill(promise, value);
+  }
+}
+
+function publishRejection(promise) {
+  if (promise._onerror) {
+    promise._onerror(promise._result);
+  }
+
+  publish(promise);
+}
+
+function fulfill(promise, value) {
+  if (promise._state !== PENDING) { return; }
+
+  promise._result = value;
+  promise._state = FULFILLED;
+
+  if (promise._subscribers.length === 0) {
+  } else {
+    asap(publish, promise);
+  }
+}
+
+function reject(promise, reason) {
+  if (promise._state !== PENDING) { return; }
+  promise._state = REJECTED;
+  promise._result = reason;
+
+  asap(publishRejection, promise);
+}
+
+function subscribe(parent, child, onFulfillment, onRejection) {
+  var subscribers = parent._subscribers;
+  var length = subscribers.length;
+
+  parent._onerror = null;
+
+  subscribers[length] = child;
+  subscribers[length + FULFILLED] = onFulfillment;
+  subscribers[length + REJECTED]  = onRejection;
+
+  if (length === 0 && parent._state) {
+    asap(publish, parent);
+  }
+}
+
+function publish(promise) {
+  var subscribers = promise._subscribers;
+  var settled = promise._state;
+
+  if (subscribers.length === 0) { return; }
+
+  var child, callback, detail = promise._result;
+
+  for (var i = 0; i < subscribers.length; i += 3) {
+    child = subscribers[i];
+    callback = subscribers[i + settled];
+
+    if (child) {
+      invokeCallback(settled, child, callback, detail);
+    } else {
+      callback(detail);
+    }
+  }
+
+  promise._subscribers.length = 0;
+}
+
+function ErrorObject() {
+  this.error = null;
+}
+
+var TRY_CATCH_ERROR = new ErrorObject();
+
+function tryCatch(callback, detail) {
+  try {
+    return callback(detail);
+  } catch(e) {
+    TRY_CATCH_ERROR.error = e;
+    return TRY_CATCH_ERROR;
+  }
+}
+
+function invokeCallback(settled, promise, callback, detail) {
+  var hasCallback = isFunction(callback),
+      value, error, succeeded, failed;
+
+  if (hasCallback) {
+    value = tryCatch(callback, detail);
+
+    if (value === TRY_CATCH_ERROR) {
+      failed = true;
+      error = value.error;
+      value = null;
+    } else {
+      succeeded = true;
+    }
+
+    if (promise === value) {
+      reject(promise, cannotReturnOwn());
+      return;
+    }
+
+  } else {
+    value = detail;
+    succeeded = true;
+  }
+
+  if (promise._state !== PENDING) {
+    // noop
+  } else if (hasCallback && succeeded) {
+    resolve(promise, value);
+  } else if (failed) {
+    reject(promise, error);
+  } else if (settled === FULFILLED) {
+    fulfill(promise, value);
+  } else if (settled === REJECTED) {
+    reject(promise, value);
+  }
+}
+
+function initializePromise(promise, resolver) {
+  try {
+    resolver(function resolvePromise(value){
+      resolve(promise, value);
+    }, function rejectPromise(reason) {
+      reject(promise, reason);
+    });
+  } catch(e) {
+    reject(promise, e);
+  }
+}
+
+export {
+  noop,
+  resolve,
+  reject,
+  fulfill,
+  subscribe,
+  publish,
+  publishRejection,
+  initializePromise,
+  invokeCallback,
+  FULFILLED,
+  REJECTED,
+  PENDING
+};
diff --git a/node_modules/es6-promise/lib/es6-promise/asap.js b/node_modules/es6-promise/lib/es6-promise/asap.js
new file mode 100644
index 0000000..4872589
--- /dev/null
+++ b/node_modules/es6-promise/lib/es6-promise/asap.js
@@ -0,0 +1,82 @@
+var len = 0;
+
+export default function asap(callback, arg) {
+  queue[len] = callback;
+  queue[len + 1] = arg;
+  len += 2;
+  if (len === 2) {
+    // If len is 1, that means that we need to schedule an async flush.
+    // If additional callbacks are queued before the queue is flushed, they
+    // will be processed by this flush that we are scheduling.
+    scheduleFlush();
+  }
+}
+
+var browserGlobal = (typeof window !== 'undefined') ? window : {};
+var BrowserMutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver;
+
+// test for web worker but not in IE10
+var isWorker = typeof Uint8ClampedArray !== 'undefined' &&
+  typeof importScripts !== 'undefined' &&
+  typeof MessageChannel !== 'undefined';
+
+// node
+function useNextTick() {
+  return function() {
+    process.nextTick(flush);
+  };
+}
+
+function useMutationObserver() {
+  var iterations = 0;
+  var observer = new BrowserMutationObserver(flush);
+  var node = document.createTextNode('');
+  observer.observe(node, { characterData: true });
+
+  return function() {
+    node.data = (iterations = ++iterations % 2);
+  };
+}
+
+// web worker
+function useMessageChannel() {
+  var channel = new MessageChannel();
+  channel.port1.onmessage = flush;
+  return function () {
+    channel.port2.postMessage(0);
+  };
+}
+
+function useSetTimeout() {
+  return function() {
+    setTimeout(flush, 1);
+  };
+}
+
+var queue = new Array(1000);
+function flush() {
+  for (var i = 0; i < len; i+=2) {
+    var callback = queue[i];
+    var arg = queue[i+1];
+
+    callback(arg);
+
+    queue[i] = undefined;
+    queue[i+1] = undefined;
+  }
+
+  len = 0;
+}
+
+var scheduleFlush;
+
+// Decide what async method to use to triggering processing of queued callbacks:
+if (typeof process !== 'undefined' && {}.toString.call(process) === '[object process]') {
+  scheduleFlush = useNextTick();
+} else if (BrowserMutationObserver) {
+  scheduleFlush = useMutationObserver();
+} else if (isWorker) {
+  scheduleFlush = useMessageChannel();
+} else {
+  scheduleFlush = useSetTimeout();
+}
diff --git a/node_modules/es6-promise/lib/es6-promise/enumerator.js b/node_modules/es6-promise/lib/es6-promise/enumerator.js
new file mode 100644
index 0000000..7d3f803
--- /dev/null
+++ b/node_modules/es6-promise/lib/es6-promise/enumerator.js
@@ -0,0 +1,125 @@
+import {
+  isArray,
+  isMaybeThenable
+} from './utils';
+
+import {
+  noop,
+  reject,
+  fulfill,
+  subscribe,
+  FULFILLED,
+  REJECTED,
+  PENDING
+} from './-internal';
+
+export function makeSettledResult(state, position, value) {
+  if (state === FULFILLED) {
+    return {
+      state: 'fulfilled',
+      value: value
+    };
+  } else {
+    return {
+      state: 'rejected',
+      reason: value
+    };
+  }
+}
+
+function Enumerator(Constructor, input, abortOnReject, label) {
+  this._instanceConstructor = Constructor;
+  this.promise = new Constructor(noop, label);
+  this._abortOnReject = abortOnReject;
+
+  if (this._validateInput(input)) {
+    this._input     = input;
+    this.length     = input.length;
+    this._remaining = input.length;
+
+    this._init();
+
+    if (this.length === 0) {
+      fulfill(this.promise, this._result);
+    } else {
+      this.length = this.length || 0;
+      this._enumerate();
+      if (this._remaining === 0) {
+        fulfill(this.promise, this._result);
+      }
+    }
+  } else {
+    reject(this.promise, this._validationError());
+  }
+}
+
+Enumerator.prototype._validateInput = function(input) {
+  return isArray(input);
+};
+
+Enumerator.prototype._validationError = function() {
+  return new Error('Array Methods must be provided an Array');
+};
+
+Enumerator.prototype._init = function() {
+  this._result = new Array(this.length);
+};
+
+export default Enumerator;
+
+Enumerator.prototype._enumerate = function() {
+  var length  = this.length;
+  var promise = this.promise;
+  var input   = this._input;
+
+  for (var i = 0; promise._state === PENDING && i < length; i++) {
+    this._eachEntry(input[i], i);
+  }
+};
+
+Enumerator.prototype._eachEntry = function(entry, i) {
+  var c = this._instanceConstructor;
+  if (isMaybeThenable(entry)) {
+    if (entry.constructor === c && entry._state !== PENDING) {
+      entry._onerror = null;
+      this._settledAt(entry._state, i, entry._result);
+    } else {
+      this._willSettleAt(c.resolve(entry), i);
+    }
+  } else {
+    this._remaining--;
+    this._result[i] = this._makeResult(FULFILLED, i, entry);
+  }
+};
+
+Enumerator.prototype._settledAt = function(state, i, value) {
+  var promise = this.promise;
+
+  if (promise._state === PENDING) {
+    this._remaining--;
+
+    if (this._abortOnReject && state === REJECTED) {
+      reject(promise, value);
+    } else {
+      this._result[i] = this._makeResult(state, i, value);
+    }
+  }
+
+  if (this._remaining === 0) {
+    fulfill(promise, this._result);
+  }
+};
+
+Enumerator.prototype._makeResult = function(state, i, value) {
+  return value;
+};
+
+Enumerator.prototype._willSettleAt = function(promise, i) {
+  var enumerator = this;
+
+  subscribe(promise, undefined, function(value) {
+    enumerator._settledAt(FULFILLED, i, value);
+  }, function(reason) {
+    enumerator._settledAt(REJECTED, i, reason);
+  });
+};
diff --git a/node_modules/es6-promise/lib/es6-promise/polyfill.js b/node_modules/es6-promise/lib/es6-promise/polyfill.js
new file mode 100644
index 0000000..e5b0165
--- /dev/null
+++ b/node_modules/es6-promise/lib/es6-promise/polyfill.js
@@ -0,0 +1,35 @@
+/*global self*/
+import { default as RSVPPromise } from "./promise";
+import { isFunction } from "./utils";
+
+export default function polyfill() {
+  var local;
+
+  if (typeof global !== 'undefined') {
+    local = global;
+  } else if (typeof window !== 'undefined' && window.document) {
+    local = window;
+  } else {
+    local = self;
+  }
+
+  var es6PromiseSupport =
+    "Promise" in local &&
+    // Some of these methods are missing from
+    // Firefox/Chrome experimental implementations
+    "resolve" in local.Promise &&
+    "reject" in local.Promise &&
+    "all" in local.Promise &&
+    "race" in local.Promise &&
+    // Older version of the spec had a resolver object
+    // as the arg rather than a function
+    (function() {
+      var resolve;
+      new local.Promise(function(r) { resolve = r; });
+      return isFunction(resolve);
+    }());
+
+  if (!es6PromiseSupport) {
+    local.Promise = RSVPPromise;
+  }
+}
diff --git a/node_modules/es6-promise/lib/es6-promise/promise.js b/node_modules/es6-promise/lib/es6-promise/promise.js
new file mode 100644
index 0000000..5e865a7
--- /dev/null
+++ b/node_modules/es6-promise/lib/es6-promise/promise.js
@@ -0,0 +1,409 @@
+import {
+  isFunction,
+  now
+} from './utils';
+
+import {
+  noop,
+  subscribe,
+  initializePromise,
+  invokeCallback,
+  FULFILLED,
+  REJECTED
+} from './-internal';
+
+import asap from './asap';
+
+import all from './promise/all';
+import race from './promise/race';
+import Resolve from './promise/resolve';
+import Reject from './promise/reject';
+
+var counter = 0;
+
+function needsResolver() {
+  throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
+}
+
+function needsNew() {
+  throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");
+}
+
+export default Promise;
+/**
+  Promise objects represent the eventual result of an asynchronous operation. The
+  primary way of interacting with a promise is through its `then` method, which
+  registers callbacks to receive either a promise’s eventual value or the reason
+  why the promise cannot be fulfilled.
+
+  Terminology
+  -----------
+
+  - `promise` is an object or function with a `then` method whose behavior conforms to this specification.
+  - `thenable` is an object or function that defines a `then` method.
+  - `value` is any legal JavaScript value (including undefined, a thenable, or a promise).
+  - `exception` is a value that is thrown using the throw statement.
+  - `reason` is a value that indicates why a promise was rejected.
+  - `settled` the final resting state of a promise, fulfilled or rejected.
+
+  A promise can be in one of three states: pending, fulfilled, or rejected.
+
+  Promises that are fulfilled have a fulfillment value and are in the fulfilled
+  state.  Promises that are rejected have a rejection reason and are in the
+  rejected state.  A fulfillment value is never a thenable.
+
+  Promises can also be said to *resolve* a value.  If this value is also a
+  promise, then the original promise's settled state will match the value's
+  settled state.  So a promise that *resolves* a promise that rejects will
+  itself reject, and a promise that *resolves* a promise that fulfills will
+  itself fulfill.
+
+
+  Basic Usage:
+  ------------
+
+  ```js
+  var promise = new Promise(function(resolve, reject) {
+    // on success
+    resolve(value);
+
+    // on failure
+    reject(reason);
+  });
+
+  promise.then(function(value) {
+    // on fulfillment
+  }, function(reason) {
+    // on rejection
+  });
+  ```
+
+  Advanced Usage:
+  ---------------
+
+  Promises shine when abstracting away asynchronous interactions such as
+  `XMLHttpRequest`s.
+
+  ```js
+  function getJSON(url) {
+    return new Promise(function(resolve, reject){
+      var xhr = new XMLHttpRequest();
+
+      xhr.open('GET', url);
+      xhr.onreadystatechange = handler;
+      xhr.responseType = 'json';
+      xhr.setRequestHeader('Accept', 'application/json');
+      xhr.send();
+
+      function handler() {
+        if (this.readyState === this.DONE) {
+          if (this.status === 200) {
+            resolve(this.response);
+          } else {
+            reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']'));
+          }
+        }
+      };
+    });
+  }
+
+  getJSON('/posts.json').then(function(json) {
+    // on fulfillment
+  }, function(reason) {
+    // on rejection
+  });
+  ```
+
+  Unlike callbacks, promises are great composable primitives.
+
+  ```js
+  Promise.all([
+    getJSON('/posts'),
+    getJSON('/comments')
+  ]).then(function(values){
+    values[0] // => postsJSON
+    values[1] // => commentsJSON
+
+    return values;
+  });
+  ```
+
+  @class Promise
+  @param {function} resolver
+  Useful for tooling.
+  @constructor
+*/
+function Promise(resolver) {
+  this._id = counter++;
+  this._state = undefined;
+  this._result = undefined;
+  this._subscribers = [];
+
+  if (noop !== resolver) {
+    if (!isFunction(resolver)) {
+      needsResolver();
+    }
+
+    if (!(this instanceof Promise)) {
+      needsNew();
+    }
+
+    initializePromise(this, resolver);
+  }
+}
+
+Promise.all = all;
+Promise.race = race;
+Promise.resolve = Resolve;
+Promise.reject = Reject;
+
+Promise.prototype = {
+  constructor: Promise,
+
+/**
+  The primary way of interacting with a promise is through its `then` method,
+  which registers callbacks to receive either a promise's eventual value or the
+  reason why the promise cannot be fulfilled.
+
+  ```js
+  findUser().then(function(user){
+    // user is available
+  }, function(reason){
+    // user is unavailable, and you are given the reason why
+  });
+  ```
+
+  Chaining
+  --------
+
+  The return value of `then` is itself a promise.  This second, 'downstream'
+  promise is resolved with the return value of the first promise's fulfillment
+  or rejection handler, or rejected if the handler throws an exception.
+
+  ```js
+  findUser().then(function (user) {
+    return user.name;
+  }, function (reason) {
+    return 'default name';
+  }).then(function (userName) {
+    // If `findUser` fulfilled, `userName` will be the user's name, otherwise it
+    // will be `'default name'`
+  });
+
+  findUser().then(function (user) {
+    throw new Error('Found user, but still unhappy');
+  }, function (reason) {
+    throw new Error('`findUser` rejected and we're unhappy');
+  }).then(function (value) {
+    // never reached
+  }, function (reason) {
+    // if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'.
+    // If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'.
+  });
+  ```
+  If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream.
+
+  ```js
+  findUser().then(function (user) {
+    throw new PedagogicalException('Upstream error');
+  }).then(function (value) {
+    // never reached
+  }).then(function (value) {
+    // never reached
+  }, function (reason) {
+    // The `PedgagocialException` is propagated all the way down to here
+  });
+  ```
+
+  Assimilation
+  ------------
+
+  Sometimes the value you want to propagate to a downstream promise can only be
+  retrieved asynchronously. This can be achieved by returning a promise in the
+  fulfillment or rejection handler. The downstream promise will then be pending
+  until the returned promise is settled. This is called *assimilation*.
+
+  ```js
+  findUser().then(function (user) {
+    return findCommentsByAuthor(user);
+  }).then(function (comments) {
+    // The user's comments are now available
+  });
+  ```
+
+  If the assimliated promise rejects, then the downstream promise will also reject.
+
+  ```js
+  findUser().then(function (user) {
+    return findCommentsByAuthor(user);
+  }).then(function (comments) {
+    // If `findCommentsByAuthor` fulfills, we'll have the value here
+  }, function (reason) {
+    // If `findCommentsByAuthor` rejects, we'll have the reason here
+  });
+  ```
+
+  Simple Example
+  --------------
+
+  Synchronous Example
+
+  ```javascript
+  var result;
+
+  try {
+    result = findResult();
+    // success
+  } catch(reason) {
+    // failure
+  }
+  ```
+
+  Errback Example
+
+  ```js
+  findResult(function(result, err){
+    if (err) {
+      // failure
+    } else {
+      // success
+    }
+  });
+  ```
+
+  Promise Example;
+
+  ```javascript
+  findResult().then(function(result){
+    // success
+  }, function(reason){
+    // failure
+  });
+  ```
+
+  Advanced Example
+  --------------
+
+  Synchronous Example
+
+  ```javascript
+  var author, books;
+
+  try {
+    author = findAuthor();
+    books  = findBooksByAuthor(author);
+    // success
+  } catch(reason) {
+    // failure
+  }
+  ```
+
+  Errback Example
+
+  ```js
+
+  function foundBooks(books) {
+
+  }
+
+  function failure(reason) {
+
+  }
+
+  findAuthor(function(author, err){
+    if (err) {
+      failure(err);
+      // failure
+    } else {
+      try {
+        findBoooksByAuthor(author, function(books, err) {
+          if (err) {
+            failure(err);
+          } else {
+            try {
+              foundBooks(books);
+            } catch(reason) {
+              failure(reason);
+            }
+          }
+        });
+      } catch(error) {
+        failure(err);
+      }
+      // success
+    }
+  });
+  ```
+
+  Promise Example;
+
+  ```javascript
+  findAuthor().
+    then(findBooksByAuthor).
+    then(function(books){
+      // found books
+  }).catch(function(reason){
+    // something went wrong
+  });
+  ```
+
+  @method then
+  @param {Function} onFulfilled
+  @param {Function} onRejected
+  Useful for tooling.
+  @return {Promise}
+*/
+  then: function(onFulfillment, onRejection) {
+    var parent = this;
+    var state = parent._state;
+
+    if (state === FULFILLED && !onFulfillment || state === REJECTED && !onRejection) {
+      return this;
+    }
+
+    var child = new this.constructor(noop);
+    var result = parent._result;
+
+    if (state) {
+      var callback = arguments[state - 1];
+      asap(function(){
+        invokeCallback(state, child, callback, result);
+      });
+    } else {
+      subscribe(parent, child, onFulfillment, onRejection);
+    }
+
+    return child;
+  },
+
+/**
+  `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same
+  as the catch block of a try/catch statement.
+
+  ```js
+  function findAuthor(){
+    throw new Error('couldn't find that author');
+  }
+
+  // synchronous
+  try {
+    findAuthor();
+  } catch(reason) {
+    // something went wrong
+  }
+
+  // async with promises
+  findAuthor().catch(function(reason){
+    // something went wrong
+  });
+  ```
+
+  @method catch
+  @param {Function} onRejection
+  Useful for tooling.
+  @return {Promise}
+*/
+  'catch': function(onRejection) {
+    return this.then(null, onRejection);
+  }
+};
diff --git a/node_modules/es6-promise/lib/es6-promise/promise/all.js b/node_modules/es6-promise/lib/es6-promise/promise/all.js
new file mode 100644
index 0000000..8db7e71
--- /dev/null
+++ b/node_modules/es6-promise/lib/es6-promise/promise/all.js
@@ -0,0 +1,52 @@
+import Enumerator from '../enumerator';
+
+/**
+  `Promise.all` accepts an array of promises, and returns a new promise which
+  is fulfilled with an array of fulfillment values for the passed promises, or
+  rejected with the reason of the first passed promise to be rejected. It casts all
+  elements of the passed iterable to promises as it runs this algorithm.
+
+  Example:
+
+  ```javascript
+  var promise1 = resolve(1);
+  var promise2 = resolve(2);
+  var promise3 = resolve(3);
+  var promises = [ promise1, promise2, promise3 ];
+
+  Promise.all(promises).then(function(array){
+    // The array here would be [ 1, 2, 3 ];
+  });
+  ```
+
+  If any of the `promises` given to `all` are rejected, the first promise
+  that is rejected will be given as an argument to the returned promises's
+  rejection handler. For example:
+
+  Example:
+
+  ```javascript
+  var promise1 = resolve(1);
+  var promise2 = reject(new Error("2"));
+  var promise3 = reject(new Error("3"));
+  var promises = [ promise1, promise2, promise3 ];
+
+  Promise.all(promises).then(function(array){
+    // Code here never runs because there are rejected promises!
+  }, function(error) {
+    // error.message === "2"
+  });
+  ```
+
+  @method all
+  @static
+  @param {Array} entries array of promises
+  @param {String} label optional string for labeling the promise.
+  Useful for tooling.
+  @return {Promise} promise that is fulfilled when all `promises` have been
+  fulfilled, or rejected if any of them become rejected.
+  @static
+*/
+export default function all(entries, label) {
+  return new Enumerator(this, entries, true /* abort on reject */, label).promise;
+}
diff --git a/node_modules/es6-promise/lib/es6-promise/promise/race.js b/node_modules/es6-promise/lib/es6-promise/promise/race.js
new file mode 100644
index 0000000..7daa28a
--- /dev/null
+++ b/node_modules/es6-promise/lib/es6-promise/promise/race.js
@@ -0,0 +1,105 @@
+import {
+  isArray
+} from "../utils";
+
+import {
+  noop,
+  resolve,
+  reject,
+  subscribe,
+  PENDING
+} from '../-internal';
+
+/**
+  `Promise.race` returns a new promise which is settled in the same way as the
+  first passed promise to settle.
+
+  Example:
+
+  ```javascript
+  var promise1 = new Promise(function(resolve, reject){
+    setTimeout(function(){
+      resolve('promise 1');
+    }, 200);
+  });
+
+  var promise2 = new Promise(function(resolve, reject){
+    setTimeout(function(){
+      resolve('promise 2');
+    }, 100);
+  });
+
+  Promise.race([promise1, promise2]).then(function(result){
+    // result === 'promise 2' because it was resolved before promise1
+    // was resolved.
+  });
+  ```
+
+  `Promise.race` is deterministic in that only the state of the first
+  settled promise matters. For example, even if other promises given to the
+  `promises` array argument are resolved, but the first settled promise has
+  become rejected before the other promises became fulfilled, the returned
+  promise will become rejected:
+
+  ```javascript
+  var promise1 = new Promise(function(resolve, reject){
+    setTimeout(function(){
+      resolve('promise 1');
+    }, 200);
+  });
+
+  var promise2 = new Promise(function(resolve, reject){
+    setTimeout(function(){
+      reject(new Error('promise 2'));
+    }, 100);
+  });
+
+  Promise.race([promise1, promise2]).then(function(result){
+    // Code here never runs
+  }, function(reason){
+    // reason.message === 'promise 2' because promise 2 became rejected before
+    // promise 1 became fulfilled
+  });
+  ```
+
+  An example real-world use case is implementing timeouts:
+
+  ```javascript
+  Promise.race([ajax('foo.json'), timeout(5000)])
+  ```
+
+  @method race
+  @static
+  @param {Array} promises array of promises to observe
+  @param {String} label optional string for describing the promise returned.
+  Useful for tooling.
+  @return {Promise} a promise which settles in the same way as the first passed
+  promise to settle.
+*/
+export default function race(entries, label) {
+  /*jshint validthis:true */
+  var Constructor = this;
+
+  var promise = new Constructor(noop, label);
+
+  if (!isArray(entries)) {
+    reject(promise, new TypeError('You must pass an array to race.'));
+    return promise;
+  }
+
+  var length = entries.length;
+
+  function onFulfillment(value) {
+    resolve(promise, value);
+  }
+
+  function onRejection(reason) {
+    reject(promise, reason);
+  }
+
+  for (var i = 0; promise._state === PENDING && i < length; i++) {
+    subscribe(Constructor.resolve(entries[i]), undefined, onFulfillment, onRejection);
+  }
+
+  return promise;
+}
diff --git a/node_modules/es6-promise/lib/es6-promise/promise/reject.js b/node_modules/es6-promise/lib/es6-promise/promise/reject.js
new file mode 100644
index 0000000..518eff7
--- /dev/null
+++ b/node_modules/es6-promise/lib/es6-promise/promise/reject.js
@@ -0,0 +1,47 @@
+import {
+  noop,
+  reject as _reject
+} from '../-internal';
+
+/**
+  `Promise.reject` returns a promise rejected with the passed `reason`.
+  It is shorthand for the following:
+
+  ```javascript
+  var promise = new Promise(function(resolve, reject){
+    reject(new Error('WHOOPS'));
+  });
+
+  promise.then(function(value){
+    // Code here doesn't run because the promise is rejected!
+  }, function(reason){
+    // reason.message === 'WHOOPS'
+  });
+  ```
+
+  Instead of writing the above, your code now simply becomes the following:
+
+  ```javascript
+  var promise = Promise.reject(new Error('WHOOPS'));
+
+  promise.then(function(value){
+    // Code here doesn't run because the promise is rejected!
+  }, function(reason){
+    // reason.message === 'WHOOPS'
+  });
+  ```
+
+  @method reject
+  @static
+  @param {Any} reason value that the returned promise will be rejected with.
+  @param {String} label optional string for identifying the returned promise.
+  Useful for tooling.
+  @return {Promise} a promise rejected with the given `reason`.
+*/
+export default function reject(reason, label) {
+  /*jshint validthis:true */
+  var Constructor = this;
+  var promise = new Constructor(noop, label);
+  _reject(promise, reason);
+  return promise;
+}
diff --git a/node_modules/es6-promise/lib/es6-promise/promise/resolve.js b/node_modules/es6-promise/lib/es6-promise/promise/resolve.js
new file mode 100644
index 0000000..1b3c533
--- /dev/null
+++ b/node_modules/es6-promise/lib/es6-promise/promise/resolve.js
@@ -0,0 +1,49 @@
+import {
+  noop,
+  resolve as _resolve
+} from '../-internal';
+
+/**
+  `Promise.resolve` returns a promise that will become resolved with the
+  passed `value`. It is shorthand for the following:
+
+  ```javascript
+  var promise = new Promise(function(resolve, reject){
+    resolve(1);
+  });
+
+  promise.then(function(value){
+    // value === 1
+  });
+  ```
+
+  Instead of writing the above, your code now simply becomes the following:
+
+  ```javascript
+  var promise = Promise.resolve(1);
+
+  promise.then(function(value){
+    // value === 1
+  });
+  ```
+
+  @method resolve
+  @static
+  @param {Any} value value that the returned promise will be resolved with
+  @param {String} label optional string for identifying the returned promise.
+  Useful for tooling.
+  @return {Promise} a promise that will become fulfilled with the given
+  `value`
+*/
+export default function resolve(object, label) {
+  /*jshint validthis:true */
+  var Constructor = this;
+
+  if (object && typeof object === 'object' && object.constructor === Constructor) {
+    return object;
+  }
+
+  var promise = new Constructor(noop, label);
+  _resolve(promise, object);
+  return promise;
+}
diff --git a/node_modules/es6-promise/lib/es6-promise/utils.js b/node_modules/es6-promise/lib/es6-promise/utils.js
new file mode 100644
index 0000000..6b49bbf
--- /dev/null
+++ b/node_modules/es6-promise/lib/es6-promise/utils.js
@@ -0,0 +1,39 @@
+export function objectOrFunction(x) {
+  return typeof x === 'function' || (typeof x === 'object' && x !== null);
+}
+
+export function isFunction(x) {
+  return typeof x === 'function';
+}
+
+export function isMaybeThenable(x) {
+  return typeof x === 'object' && x !== null;
+}
+
+var _isArray;
+if (!Array.isArray) {
+  _isArray = function (x) {
+    return Object.prototype.toString.call(x) === '[object Array]';
+  };
+} else {
+  _isArray = Array.isArray;
+}
+
+export var isArray = _isArray;
+
+// Date.now is not available in browsers < IE9
+// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now#Compatibility
+export var now = Date.now || function() { return new Date().getTime(); };
+
+function F() { }
+
+export var o_create = (Object.create || function (o) {
+  if (arguments.length > 1) {
+    throw new Error('Second argument not supported');
+  }
+  if (typeof o !== 'object') {
+    throw new TypeError('Argument must be an object');
+  }
+  F.prototype = o;
+  return new F();
+});
diff --git a/node_modules/es6-promise/package.json b/node_modules/es6-promise/package.json
new file mode 100644
index 0000000..fabdc88
--- /dev/null
+++ b/node_modules/es6-promise/package.json
@@ -0,0 +1,81 @@
+{
+  "name": "es6-promise",
+  "namespace": "es6-promise",
+  "version": "2.0.1",
+  "description": "A lightweight library that provides tools for organizing asynchronous code",
+  "main": "dist/es6-promise.js",
+  "directories": {
+    "lib": "lib"
+  },
+  "devDependencies": {
+    "bower": "^1.3.9",
+    "brfs": "0.0.8",
+    "broccoli-closure-compiler": "^0.2.0",
+    "broccoli-compile-modules": "git+https://github.com/eventualbuddha/broccoli-compile-modules",
+    "broccoli-concat": "0.0.7",
+    "broccoli-es3-safe-recast": "0.0.8",
+    "broccoli-file-mover": "^0.4.0",
+    "broccoli-jshint": "^0.5.1",
+    "broccoli-merge-trees": "^0.1.4",
+    "broccoli-static-compiler": "^0.1.4",
+    "broccoli-string-replace": "0.0.1",
+    "browserify": "^4.2.0",
+    "ember-cli": "0.0.40",
+    "ember-publisher": "0.0.7",
+    "es6-module-transpiler-amd-formatter": "0.0.1",
+    "express": "^4.5.0",
+    "jshint": "~0.9.1",
+    "mkdirp": "^0.5.0",
+    "mocha": "^1.20.1",
+    "promises-aplus-tests": "git://github.com/stefanpenner/promises-tests.git",
+    "release-it": "0.0.10",
+    "testem": "^0.6.17",
+    "json3": "^3.3.2"
+  },
+  "scripts": {
+    "test": "testem ci -R dot",
+    "test-server": "testem",
+    "lint": "jshint lib",
+    "prepublish": "ember build --environment production",
+    "aplus": "browserify test/main.js",
+    "build-all": "ember build --environment production && browserify ./test/main.js -o tmp/test-bundle.js",
+    "dry-run-release": "ember build --environment production && release-it --dry-run --non-interactive"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/jakearchibald/ES6-Promises.git"
+  },
+  "bugs": {
+    "url": "https://github.com/jakearchibald/ES6-Promises/issues"
+  },
+  "keywords": [
+    "promises",
+    "futures"
+  ],
+  "author": {
+    "name": "Yehuda Katz, Tom Dale, Stefan Penner and contributors",
+    "url": "Conversion to ES6 API by Jake Archibald"
+  },
+  "license": "MIT",
+  "homepage": "https://github.com/jakearchibald/ES6-Promises",
+  "_id": "es6-promise@2.0.1",
+  "dist": {
+    "shasum": "ccc4963e679f0ca9fb187c777b9e583d3c7573c2",
+    "tarball": "http://registry.npmjs.org/es6-promise/-/es6-promise-2.0.1.tgz"
+  },
+  "_from": "es6-promise@",
+  "_npmVersion": "1.3.24",
+  "_npmUser": {
+    "name": "jaffathecake",
+    "email": "jaffathecake@gmail.com"
+  },
+  "maintainers": [
+    {
+      "name": "jaffathecake",
+      "email": "jaffathecake@gmail.com"
+    }
+  ],
+  "_shasum": "ccc4963e679f0ca9fb187c777b9e583d3c7573c2",
+  "_resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-2.0.1.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/es6-promise/server/.jshintrc b/node_modules/es6-promise/server/.jshintrc
new file mode 100644
index 0000000..c1f2978
--- /dev/null
+++ b/node_modules/es6-promise/server/.jshintrc
@@ -0,0 +1,3 @@
+{
+  "node": true
+}
diff --git a/node_modules/es6-promise/server/index.js b/node_modules/es6-promise/server/index.js
new file mode 100644
index 0000000..8a54d36
--- /dev/null
+++ b/node_modules/es6-promise/server/index.js
@@ -0,0 +1,6 @@
+module.exports = function(app) {
+  app.use(require('express').static(__dirname + '/../'));
+  app.get('/', function(req, res) {
+    res.redirect('/test/');
+  })
+};
diff --git a/node_modules/es6-promise/testem.json b/node_modules/es6-promise/testem.json
new file mode 100644
index 0000000..7494912
--- /dev/null
+++ b/node_modules/es6-promise/testem.json
@@ -0,0 +1,11 @@
+{
+  "test_page": "test/index.html",
+  "parallel": 5,
+  "before_tests": "npm run build-all",
+  "launchers": {
+    "Mocha": {
+      "command": "./node_modules/.bin/mocha test/main.js"
+    }
+  },
+  "launch_in_ci":  ["PhantomJS", "Mocha"]
+}
diff --git a/node_modules/node-firefox-connect/.npmignore b/node_modules/node-firefox-connect/.npmignore
new file mode 100644
index 0000000..b0e3907
--- /dev/null
+++ b/node_modules/node-firefox-connect/.npmignore
@@ -0,0 +1,3 @@
+node_modules
+npm-debug.log
+.DS_Store
diff --git a/node_modules/node-firefox-connect/LICENSE b/node_modules/node-firefox-connect/LICENSE
new file mode 100644
index 0000000..23564e2
--- /dev/null
+++ b/node_modules/node-firefox-connect/LICENSE
@@ -0,0 +1,10 @@
+Copyright 2014-2015 Mozilla
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/node_modules/node-firefox-connect/README.md b/node_modules/node-firefox-connect/README.md
new file mode 100644
index 0000000..39d445d
--- /dev/null
+++ b/node_modules/node-firefox-connect/README.md
@@ -0,0 +1,62 @@
+# node-firefox-connect [![Build Status](https://secure.travis-ci.org/mozilla/node-firefox-connect.png?branch=master)](http://travis-ci.org/mozilla/node-firefox-connect)
+
+> Connects to a Firefox debuggable runtime.
+
+[![Install with NPM](https://nodei.co/npm/node-firefox-connect.png?downloads=true&stars=true)](https://nodei.co/npm/node-firefox-connect/)
+
+This is part of the [node-firefox](https://github.com/mozilla/node-firefox) project.
+
+## Installation
+
+### From git
+
+```sh
+git clone https://github.com/mozilla/node-firefox-connect.git
+cd node-firefox-connect
+npm install
+```
+
+If you want to update later on:
+
+```sh
+cd node-firefox-connect
+git pull origin master
+npm install
+```
+
+### npm
+
+```bash
+npm install node-firefox-connect
+```
+
+## Usage
+
+Connects to a Firefox runtime, given a port number, and returns a [client](https://github.com/harthur/firefox-client) that can be used to interact with said client.
+
+```javascript
+// `connect` returns a Promise
+connect(portNumber).then(function(client) {
+
+});
+```
+
+## Example
+
+```javascript
+var connect = require('node-firefox-connect');
+
+connect(1234)
+  .then(function(client) {
+    // Let's show for example all the running apps
+    client.getWebapps(function(err, webapps) {
+      webapps.listRunningApps(function(err, apps) {
+        console.log("Running apps:", apps);
+      });
+    });
+  });
+```
+
+## History
+
+This is based on initial work on fxos-connect by Nicola Greco.
diff --git a/node_modules/node-firefox-connect/examples/connect.js b/node_modules/node-firefox-connect/examples/connect.js
new file mode 100644
index 0000000..f6a3a12
--- /dev/null
+++ b/node_modules/node-firefox-connect/examples/connect.js
@@ -0,0 +1,15 @@
+'use strict';
+
+var connect = require('../');
+
+// Connect to a runtime listening to port 8000
+connect(8000).then(function(client) {
+
+  // Let's show for example all the running apps
+  client.getWebapps(function(err, webapps) {
+    webapps.listRunningApps(function(err, apps) {
+      console.log('Running apps:', apps);
+    });
+  });
+  
+});
diff --git a/node_modules/node-firefox-connect/examples/startAndConnect.js b/node_modules/node-firefox-connect/examples/startAndConnect.js
new file mode 100644
index 0000000..9ff5508
--- /dev/null
+++ b/node_modules/node-firefox-connect/examples/startAndConnect.js
@@ -0,0 +1,21 @@
+'use strict';
+
+var connect = require('../');
+var startSimulator = require('node-firefox-start-simulator');
+
+startSimulator({}).then(function(simulator) {
+
+  // Connect to the simulator we just launched
+  connect(simulator.port).then(function(client) {
+
+    // Let's show for example all the running apps
+    client.getWebapps(function(err, webapps) {
+      webapps.listRunningApps(function(err, apps) {
+        console.log('Running apps:', apps);
+      });
+    });
+    
+  });
+  
+});
+
diff --git a/node_modules/node-firefox-connect/gulpfile.js b/node_modules/node-firefox-connect/gulpfile.js
new file mode 100644
index 0000000..f5e5ab9
--- /dev/null
+++ b/node_modules/node-firefox-connect/gulpfile.js
@@ -0,0 +1,3 @@
+var gulp = require('gulp');
+
+require('node-firefox-build-tools').loadGulpTasks(gulp);
diff --git a/node_modules/node-firefox-connect/index.js b/node_modules/node-firefox-connect/index.js
new file mode 100644
index 0000000..35c5dbd
--- /dev/null
+++ b/node_modules/node-firefox-connect/index.js
@@ -0,0 +1,23 @@
+'use strict';
+
+// See https://github.com/jshint/jshint/issues/1747 for context
+/* global -Promise */
+var Promise = require('es6-promise').Promise;
+var FirefoxClient = require('firefox-client');
+
+module.exports = connect;
+
+function connect(port) {
+  return new Promise(function(resolve, reject) {
+
+    var client = new FirefoxClient();
+    client.connect(port, function(err) {
+      if (err) {
+        reject(err);
+      }
+      resolve(client);
+    });
+
+  });
+}
+ 
diff --git a/node_modules/node-firefox-connect/node_modules/es6-promise/.release.json b/node_modules/node-firefox-connect/node_modules/es6-promise/.release.json
new file mode 100644
index 0000000..dee8cbc
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/es6-promise/.release.json
@@ -0,0 +1,17 @@
+{
+  "non-interactive": true,
+  "dry-run": false,
+  "verbose": false,
+  "force": false,
+  "pkgFiles": ["package.json", "bower.json"],
+  "increment": "patch",
+  "commitMessage": "Release %s",
+  "tagName": "%s",
+  "tagAnnotation": "Release %s",
+  "buildCommand": "npm run-script build-all",
+  "distRepo": "git@github.com:components/rsvp.js.git",
+  "distStageDir": "tmp/stage",
+  "distBase": "dist",
+  "distFiles": ["**/*", "../package.json", "../bower.json"],
+  "publish": false
+}
diff --git a/node_modules/node-firefox-connect/node_modules/es6-promise/Brocfile.js b/node_modules/node-firefox-connect/node_modules/es6-promise/Brocfile.js
new file mode 100644
index 0000000..d34f458
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/es6-promise/Brocfile.js
@@ -0,0 +1,75 @@
+/* jshint node:true, undef:true, unused:true */
+var AMDFormatter     = require('es6-module-transpiler-amd-formatter');
+var closureCompiler  = require('broccoli-closure-compiler');
+var compileModules   = require('broccoli-compile-modules');
+var mergeTrees       = require('broccoli-merge-trees');
+var moveFile         = require('broccoli-file-mover');
+var es3Recast        = require('broccoli-es3-safe-recast');
+var concat           = require('broccoli-concat');
+var replace          = require('broccoli-string-replace');
+var calculateVersion = require('./lib/calculateVersion');
+var path             = require('path');
+var trees            = [];
+var env              = process.env.EMBER_ENV || 'development';
+
+var bundle = compileModules('lib', {
+  inputFiles: ['es6-promise.umd.js'],
+  output: '/es6-promise.js',
+  formatter: 'bundle',
+});
+
+trees.push(bundle);
+trees.push(compileModules('lib', {
+  inputFiles: ['**/*.js'],
+  output: '/amd/',
+  formatter: new AMDFormatter()
+}));
+
+if (env === 'production') {
+  trees.push(closureCompiler(moveFile(bundle, {
+    srcFile: 'es6-promise.js',
+    destFile: 'es6-promise.min.js'
+  }), {
+    compilation_level: 'ADVANCED_OPTIMIZATIONS',
+    externs: ['node'],
+  }));
+}
+
+var distTree = mergeTrees(trees.concat('config'));
+var distTrees = [];
+
+distTrees.push(concat(distTree, {
+  inputFiles: [
+    'versionTemplate.txt',
+    'es6-promise.js'
+  ],
+  outputFile: '/es6-promise.js'
+}));
+
+if (env === 'production') {
+  distTrees.push(concat(distTree, {
+    inputFiles: [
+      'versionTemplate.txt',
+      'es6-promise.min.js'
+    ],
+    outputFile: '/es6-promise.min.js'
+  }));
+}
+
+if (env !== 'development') {
+  distTrees = distTrees.map(es3Recast);
+}
+
+distTree = mergeTrees(distTrees);
+var distTree = replace(distTree, {
+  files: [
+    'es6-promise.js',
+    'es6-promise.min.js'
+  ],
+  pattern: {
+    match: /VERSION_PLACEHOLDER_STRING/g,
+    replacement: calculateVersion()
+  }
+});
+
+module.exports = distTree;
diff --git a/node_modules/node-firefox-connect/node_modules/es6-promise/CHANGELOG.md b/node_modules/node-firefox-connect/node_modules/es6-promise/CHANGELOG.md
new file mode 100644
index 0000000..e06b496
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/es6-promise/CHANGELOG.md
@@ -0,0 +1,9 @@
+# Master
+
+# 2.0.0
+
+* re-sync with RSVP. Many large performance improvements and bugfixes.
+
+# 1.0.0
+
+* first subset of RSVP
diff --git a/node_modules/node-firefox-connect/node_modules/es6-promise/LICENSE b/node_modules/node-firefox-connect/node_modules/es6-promise/LICENSE
new file mode 100644
index 0000000..954ec59
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/es6-promise/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/node_modules/node-firefox-connect/node_modules/es6-promise/README.md b/node_modules/node-firefox-connect/node_modules/es6-promise/README.md
new file mode 100644
index 0000000..b974ce4
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/es6-promise/README.md
@@ -0,0 +1,58 @@
+# ES6-Promise (subset of [rsvp.js](https://github.com/tildeio/rsvp.js))
+
+This is a polyfill of the [ES6 Promise](http://people.mozilla.org/~jorendorff/es6-draft.html#sec-promise-constructor). The implementation is a subset of [rsvp.js](https://github.com/tildeio/rsvp.js), if you're wanting extra features and more debugging options, check out the [full library](https://github.com/tildeio/rsvp.js).
+
+For API details and how to use promises, see the <a href="http://www.html5rocks.com/en/tutorials/es6/promises/">JavaScript Promises HTML5Rocks article</a>.
+
+## Downloads
+
+* [es6-promise](https://es6-promises.s3.amazonaws.com/es6-promise-2.0.1.js)
+* [es6-promise-min](https://es6-promises.s3.amazonaws.com/es6-promise-2.0.1.min.js) (~2.2k gzipped)
+
+## Node.js
+
+To install:
+
+```sh
+npm install es6-promise
+```
+
+To use:
+
+```js
+var Promise = require('es6-promise').Promise;
+```
+
+## Usage in IE<9
+
+`catch` is a reserved word in IE<9, meaning `promise.catch(func)` throws a syntax error. To work around this, you can use a string to access the property as shown in the following example.
+
+However, please remember that such technique is already provided by most common minifiers, making the resulting code safe for old browsers and production:
+
+```js
+promise['catch'](function(err) {
+  // ...
+});
+```
+
+Or use `.then` instead:
+
+```js
+promise.then(undefined, function(err) {
+  // ...
+});
+```
+
+## Auto-polyfill
+
+To polyfill the global environment (either in Node or in the browser via CommonJS) use the following code snippet:
+
+```js
+require('es6-promise').polyfill();
+```
+
+Notice that we don't assign the result of `polyfill()` to any variable. The `polyfill()` method will patch the global environment (in this case to the `Promise` name) when called.
+
+## Building & Testing
+
+* `npm run build-all && npm test` - Run Mocha tests through Node and PhantomJS.
diff --git a/node_modules/node-firefox-connect/node_modules/es6-promise/bin/publish_to_s3.js b/node_modules/node-firefox-connect/node_modules/es6-promise/bin/publish_to_s3.js
new file mode 100755
index 0000000..7daf49a
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/es6-promise/bin/publish_to_s3.js
@@ -0,0 +1,28 @@
+#!/usr/bin/env node
+
+// To invoke this from the commandline you need the following to env vars to exist:
+//
+// S3_BUCKET_NAME
+// TRAVIS_BRANCH
+// TRAVIS_TAG
+// TRAVIS_COMMIT
+// S3_SECRET_ACCESS_KEY
+// S3_ACCESS_KEY_ID
+//
+// Once you have those you execute with the following:
+//
+// ```sh
+// ./bin/publish_to_s3.js
+// ```
+var S3Publisher = require('ember-publisher');
+var configPath = require('path').join(__dirname, '../config/s3ProjectConfig.js');
+publisher = new S3Publisher({ projectConfigPath: configPath });
+
+// Always use wildcard section of project config.
+// This is useful when the including library does not
+// require channels (like in ember.js / ember-data).
+publisher.currentBranch = function() {
+  return (process.env.TRAVIS_BRANCH === 'master') ? 'wildcard' : 'no-op';
+};
+publisher.publish();
+
diff --git a/node_modules/node-firefox-connect/node_modules/es6-promise/bower.json b/node_modules/node-firefox-connect/node_modules/es6-promise/bower.json
new file mode 100644
index 0000000..84ec5ad
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/es6-promise/bower.json
@@ -0,0 +1,29 @@
+{
+  "name": "es6-promise",
+  "namespace": "Promise",
+  "version": "2.0.1",
+  "description": "A polyfill for ES6-style Promises, tracking rsvp",
+  "authors": [
+    "Stefan Penner <stefan.penner@gmail.com>"
+  ],
+  "main": "dist/es6-promise.js",
+  "keywords": [
+    "promise"
+  ],
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/jakearchibald/ES6-Promises.git"
+  },
+  "bugs": {
+    "url": "https://github.com/jakearchibald/ES6-Promises/issues"
+  },
+  "license": "MIT",
+  "ignore": [
+    "node_modules",
+    "bower_components",
+    "test",
+    "tests",
+    "vendor",
+    "tasks"
+  ]
+}
diff --git a/node_modules/node-firefox-connect/node_modules/es6-promise/config/s3ProjectConfig.js b/node_modules/node-firefox-connect/node_modules/es6-promise/config/s3ProjectConfig.js
new file mode 100644
index 0000000..5f3349a
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/es6-promise/config/s3ProjectConfig.js
@@ -0,0 +1,26 @@
+/*
+ * Using wildcard because es6-promise does not currently have a
+ * channel system in place.
+ */
+module.exports = function(revision,tag,date){
+  return {
+    'es6-promise.js':
+      { contentType: 'text/javascript',
+        destinations: {
+          wildcard: [
+            'es6-promise-latest.js',
+            'es6-promise-' + revision + '.js'
+          ]
+        }
+      },
+    'es6-promise.min.js':
+      { contentType: 'text/javascript',
+        destinations: {
+          wildcard: [
+            'es6-promise-latest.min.js',
+            'es6-promise-' + revision + '.min.js'
+          ]
+        }
+      }
+  }
+}
diff --git a/node_modules/node-firefox-connect/node_modules/es6-promise/config/versionTemplate.txt b/node_modules/node-firefox-connect/node_modules/es6-promise/config/versionTemplate.txt
new file mode 100644
index 0000000..999a5fc
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/es6-promise/config/versionTemplate.txt
@@ -0,0 +1,7 @@
+/*!
+ * @overview es6-promise - a tiny implementation of Promises/A+.
+ * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald)
+ * @license   Licensed under MIT license
+ *            See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE
+ * @version   VERSION_PLACEHOLDER_STRING
+ */
diff --git a/node_modules/node-firefox-connect/node_modules/es6-promise/dist/es6-promise.js b/node_modules/node-firefox-connect/node_modules/es6-promise/dist/es6-promise.js
new file mode 100644
index 0000000..30a6d81
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/es6-promise/dist/es6-promise.js
@@ -0,0 +1,960 @@
+/*!
+ * @overview es6-promise - a tiny implementation of Promises/A+.
+ * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald)
+ * @license   Licensed under MIT license
+ *            See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE
+ * @version   2.0.1
+ */
+
+(function() {
+    "use strict";
+
+    function $$utils$$objectOrFunction(x) {
+      return typeof x === 'function' || (typeof x === 'object' && x !== null);
+    }
+
+    function $$utils$$isFunction(x) {
+      return typeof x === 'function';
+    }
+
+    function $$utils$$isMaybeThenable(x) {
+      return typeof x === 'object' && x !== null;
+    }
+
+    var $$utils$$_isArray;
+
+    if (!Array.isArray) {
+      $$utils$$_isArray = function (x) {
+        return Object.prototype.toString.call(x) === '[object Array]';
+      };
+    } else {
+      $$utils$$_isArray = Array.isArray;
+    }
+
+    var $$utils$$isArray = $$utils$$_isArray;
+    var $$utils$$now = Date.now || function() { return new Date().getTime(); };
+    function $$utils$$F() { }
+
+    var $$utils$$o_create = (Object.create || function (o) {
+      if (arguments.length > 1) {
+        throw new Error('Second argument not supported');
+      }
+      if (typeof o !== 'object') {
+        throw new TypeError('Argument must be an object');
+      }
+      $$utils$$F.prototype = o;
+      return new $$utils$$F();
+    });
+
+    var $$asap$$len = 0;
+
+    var $$asap$$default = function asap(callback, arg) {
+      $$asap$$queue[$$asap$$len] = callback;
+      $$asap$$queue[$$asap$$len + 1] = arg;
+      $$asap$$len += 2;
+      if ($$asap$$len === 2) {
+        // If len is 1, that means that we need to schedule an async flush.
+        // If additional callbacks are queued before the queue is flushed, they
+        // will be processed by this flush that we are scheduling.
+        $$asap$$scheduleFlush();
+      }
+    };
+
+    var $$asap$$browserGlobal = (typeof window !== 'undefined') ? window : {};
+    var $$asap$$BrowserMutationObserver = $$asap$$browserGlobal.MutationObserver || $$asap$$browserGlobal.WebKitMutationObserver;
+
+    // test for web worker but not in IE10
+    var $$asap$$isWorker = typeof Uint8ClampedArray !== 'undefined' &&
+      typeof importScripts !== 'undefined' &&
+      typeof MessageChannel !== 'undefined';
+
+    // node
+    function $$asap$$useNextTick() {
+      return function() {
+        process.nextTick($$asap$$flush);
+      };
+    }
+
+    function $$asap$$useMutationObserver() {
+      var iterations = 0;
+      var observer = new $$asap$$BrowserMutationObserver($$asap$$flush);
+      var node = document.createTextNode('');
+      observer.observe(node, { characterData: true });
+
+      return function() {
+        node.data = (iterations = ++iterations % 2);
+      };
+    }
+
+    // web worker
+    function $$asap$$useMessageChannel() {
+      var channel = new MessageChannel();
+      channel.port1.onmessage = $$asap$$flush;
+      return function () {
+        channel.port2.postMessage(0);
+      };
+    }
+
+    function $$asap$$useSetTimeout() {
+      return function() {
+        setTimeout($$asap$$flush, 1);
+      };
+    }
+
+    var $$asap$$queue = new Array(1000);
+
+    function $$asap$$flush() {
+      for (var i = 0; i < $$asap$$len; i+=2) {
+        var callback = $$asap$$queue[i];
+        var arg = $$asap$$queue[i+1];
+
+        callback(arg);
+
+        $$asap$$queue[i] = undefined;
+        $$asap$$queue[i+1] = undefined;
+      }
+
+      $$asap$$len = 0;
+    }
+
+    var $$asap$$scheduleFlush;
+
+    // Decide what async method to use to triggering processing of queued callbacks:
+    if (typeof process !== 'undefined' && {}.toString.call(process) === '[object process]') {
+      $$asap$$scheduleFlush = $$asap$$useNextTick();
+    } else if ($$asap$$BrowserMutationObserver) {
+      $$asap$$scheduleFlush = $$asap$$useMutationObserver();
+    } else if ($$asap$$isWorker) {
+      $$asap$$scheduleFlush = $$asap$$useMessageChannel();
+    } else {
+      $$asap$$scheduleFlush = $$asap$$useSetTimeout();
+    }
+
+    function $$$internal$$noop() {}
+    var $$$internal$$PENDING   = void 0;
+    var $$$internal$$FULFILLED = 1;
+    var $$$internal$$REJECTED  = 2;
+    var $$$internal$$GET_THEN_ERROR = new $$$internal$$ErrorObject();
+
+    function $$$internal$$selfFullfillment() {
+      return new TypeError("You cannot resolve a promise with itself");
+    }
+
+    function $$$internal$$cannotReturnOwn() {
+      return new TypeError('A promises callback cannot return that same promise.')
+    }
+
+    function $$$internal$$getThen(promise) {
+      try {
+        return promise.then;
+      } catch(error) {
+        $$$internal$$GET_THEN_ERROR.error = error;
+        return $$$internal$$GET_THEN_ERROR;
+      }
+    }
+
+    function $$$internal$$tryThen(then, value, fulfillmentHandler, rejectionHandler) {
+      try {
+        then.call(value, fulfillmentHandler, rejectionHandler);
+      } catch(e) {
+        return e;
+      }
+    }
+
+    function $$$internal$$handleForeignThenable(promise, thenable, then) {
+       $$asap$$default(function(promise) {
+        var sealed = false;
+        var error = $$$internal$$tryThen(then, thenable, function(value) {
+          if (sealed) { return; }
+          sealed = true;
+          if (thenable !== value) {
+            $$$internal$$resolve(promise, value);
+          } else {
+            $$$internal$$fulfill(promise, value);
+          }
+        }, function(reason) {
+          if (sealed) { return; }
+          sealed = true;
+
+          $$$internal$$reject(promise, reason);
+        }, 'Settle: ' + (promise._label || ' unknown promise'));
+
+        if (!sealed && error) {
+          sealed = true;
+          $$$internal$$reject(promise, error);
+        }
+      }, promise);
+    }
+
+    function $$$internal$$handleOwnThenable(promise, thenable) {
+      if (thenable._state === $$$internal$$FULFILLED) {
+        $$$internal$$fulfill(promise, thenable._result);
+      } else if (promise._state === $$$internal$$REJECTED) {
+        $$$internal$$reject(promise, thenable._result);
+      } else {
+        $$$internal$$subscribe(thenable, undefined, function(value) {
+          $$$internal$$resolve(promise, value);
+        }, function(reason) {
+          $$$internal$$reject(promise, reason);
+        });
+      }
+    }
+
+    function $$$internal$$handleMaybeThenable(promise, maybeThenable) {
+      if (maybeThenable.constructor === promise.constructor) {
+        $$$internal$$handleOwnThenable(promise, maybeThenable);
+      } else {
+        var then = $$$internal$$getThen(maybeThenable);
+
+        if (then === $$$internal$$GET_THEN_ERROR) {
+          $$$internal$$reject(promise, $$$internal$$GET_THEN_ERROR.error);
+        } else if (then === undefined) {
+          $$$internal$$fulfill(promise, maybeThenable);
+        } else if ($$utils$$isFunction(then)) {
+          $$$internal$$handleForeignThenable(promise, maybeThenable, then);
+        } else {
+          $$$internal$$fulfill(promise, maybeThenable);
+        }
+      }
+    }
+
+    function $$$internal$$resolve(promise, value) {
+      if (promise === value) {
+        $$$internal$$reject(promise, $$$internal$$selfFullfillment());
+      } else if ($$utils$$objectOrFunction(value)) {
+        $$$internal$$handleMaybeThenable(promise, value);
+      } else {
+        $$$internal$$fulfill(promise, value);
+      }
+    }
+
+    function $$$internal$$publishRejection(promise) {
+      if (promise._onerror) {
+        promise._onerror(promise._result);
+      }
+
+      $$$internal$$publish(promise);
+    }
+
+    function $$$internal$$fulfill(promise, value) {
+      if (promise._state !== $$$internal$$PENDING) { return; }
+
+      promise._result = value;
+      promise._state = $$$internal$$FULFILLED;
+
+      if (promise._subscribers.length === 0) {
+      } else {
+        $$asap$$default($$$internal$$publish, promise);
+      }
+    }
+
+    function $$$internal$$reject(promise, reason) {
+      if (promise._state !== $$$internal$$PENDING) { return; }
+      promise._state = $$$internal$$REJECTED;
+      promise._result = reason;
+
+      $$asap$$default($$$internal$$publishRejection, promise);
+    }
+
+    function $$$internal$$subscribe(parent, child, onFulfillment, onRejection) {
+      var subscribers = parent._subscribers;
+      var length = subscribers.length;
+
+      parent._onerror = null;
+
+      subscribers[length] = child;
+      subscribers[length + $$$internal$$FULFILLED] = onFulfillment;
+      subscribers[length + $$$internal$$REJECTED]  = onRejection;
+
+      if (length === 0 && parent._state) {
+        $$asap$$default($$$internal$$publish, parent);
+      }
+    }
+
+    function $$$internal$$publish(promise) {
+      var subscribers = promise._subscribers;
+      var settled = promise._state;
+
+      if (subscribers.length === 0) { return; }
+
+      var child, callback, detail = promise._result;
+
+      for (var i = 0; i < subscribers.length; i += 3) {
+        child = subscribers[i];
+        callback = subscribers[i + settled];
+
+        if (child) {
+          $$$internal$$invokeCallback(settled, child, callback, detail);
+        } else {
+          callback(detail);
+        }
+      }
+
+      promise._subscribers.length = 0;
+    }
+
+    function $$$internal$$ErrorObject() {
+      this.error = null;
+    }
+
+    var $$$internal$$TRY_CATCH_ERROR = new $$$internal$$ErrorObject();
+
+    function $$$internal$$tryCatch(callback, detail) {
+      try {
+        return callback(detail);
+      } catch(e) {
+        $$$internal$$TRY_CATCH_ERROR.error = e;
+        return $$$internal$$TRY_CATCH_ERROR;
+      }
+    }
+
+    function $$$internal$$invokeCallback(settled, promise, callback, detail) {
+      var hasCallback = $$utils$$isFunction(callback),
+          value, error, succeeded, failed;
+
+      if (hasCallback) {
+        value = $$$internal$$tryCatch(callback, detail);
+
+        if (value === $$$internal$$TRY_CATCH_ERROR) {
+          failed = true;
+          error = value.error;
+          value = null;
+        } else {
+          succeeded = true;
+        }
+
+        if (promise === value) {
+          $$$internal$$reject(promise, $$$internal$$cannotReturnOwn());
+          return;
+        }
+
+      } else {
+        value = detail;
+        succeeded = true;
+      }
+
+      if (promise._state !== $$$internal$$PENDING) {
+        // noop
+      } else if (hasCallback && succeeded) {
+        $$$internal$$resolve(promise, value);
+      } else if (failed) {
+        $$$internal$$reject(promise, error);
+      } else if (settled === $$$internal$$FULFILLED) {
+        $$$internal$$fulfill(promise, value);
+      } else if (settled === $$$internal$$REJECTED) {
+        $$$internal$$reject(promise, value);
+      }
+    }
+
+    function $$$internal$$initializePromise(promise, resolver) {
+      try {
+        resolver(function resolvePromise(value){
+          $$$internal$$resolve(promise, value);
+        }, function rejectPromise(reason) {
+          $$$internal$$reject(promise, reason);
+        });
+      } catch(e) {
+        $$$internal$$reject(promise, e);
+      }
+    }
+
+    function $$$enumerator$$makeSettledResult(state, position, value) {
+      if (state === $$$internal$$FULFILLED) {
+        return {
+          state: 'fulfilled',
+          value: value
+        };
+      } else {
+        return {
+          state: 'rejected',
+          reason: value
+        };
+      }
+    }
+
+    function $$$enumerator$$Enumerator(Constructor, input, abortOnReject, label) {
+      this._instanceConstructor = Constructor;
+      this.promise = new Constructor($$$internal$$noop, label);
+      this._abortOnReject = abortOnReject;
+
+      if (this._validateInput(input)) {
+        this._input     = input;
+        this.length     = input.length;
+        this._remaining = input.length;
+
+        this._init();
+
+        if (this.length === 0) {
+          $$$internal$$fulfill(this.promise, this._result);
+        } else {
+          this.length = this.length || 0;
+          this._enumerate();
+          if (this._remaining === 0) {
+            $$$internal$$fulfill(this.promise, this._result);
+          }
+        }
+      } else {
+        $$$internal$$reject(this.promise, this._validationError());
+      }
+    }
+
+    $$$enumerator$$Enumerator.prototype._validateInput = function(input) {
+      return $$utils$$isArray(input);
+    };
+
+    $$$enumerator$$Enumerator.prototype._validationError = function() {
+      return new Error('Array Methods must be provided an Array');
+    };
+
+    $$$enumerator$$Enumerator.prototype._init = function() {
+      this._result = new Array(this.length);
+    };
+
+    var $$$enumerator$$default = $$$enumerator$$Enumerator;
+
+    $$$enumerator$$Enumerator.prototype._enumerate = function() {
+      var length  = this.length;
+      var promise = this.promise;
+      var input   = this._input;
+
+      for (var i = 0; promise._state === $$$internal$$PENDING && i < length; i++) {
+        this._eachEntry(input[i], i);
+      }
+    };
+
+    $$$enumerator$$Enumerator.prototype._eachEntry = function(entry, i) {
+      var c = this._instanceConstructor;
+      if ($$utils$$isMaybeThenable(entry)) {
+        if (entry.constructor === c && entry._state !== $$$internal$$PENDING) {
+          entry._onerror = null;
+          this._settledAt(entry._state, i, entry._result);
+        } else {
+          this._willSettleAt(c.resolve(entry), i);
+        }
+      } else {
+        this._remaining--;
+        this._result[i] = this._makeResult($$$internal$$FULFILLED, i, entry);
+      }
+    };
+
+    $$$enumerator$$Enumerator.prototype._settledAt = function(state, i, value) {
+      var promise = this.promise;
+
+      if (promise._state === $$$internal$$PENDING) {
+        this._remaining--;
+
+        if (this._abortOnReject && state === $$$internal$$REJECTED) {
+          $$$internal$$reject(promise, value);
+        } else {
+          this._result[i] = this._makeResult(state, i, value);
+        }
+      }
+
+      if (this._remaining === 0) {
+        $$$internal$$fulfill(promise, this._result);
+      }
+    };
+
+    $$$enumerator$$Enumerator.prototype._makeResult = function(state, i, value) {
+      return value;
+    };
+
+    $$$enumerator$$Enumerator.prototype._willSettleAt = function(promise, i) {
+      var enumerator = this;
+
+      $$$internal$$subscribe(promise, undefined, function(value) {
+        enumerator._settledAt($$$internal$$FULFILLED, i, value);
+      }, function(reason) {
+        enumerator._settledAt($$$internal$$REJECTED, i, reason);
+      });
+    };
+
+    var $$promise$all$$default = function all(entries, label) {
+      return new $$$enumerator$$default(this, entries, true /* abort on reject */, label).promise;
+    };
+
+    var $$promise$race$$default = function race(entries, label) {
+      /*jshint validthis:true */
+      var Constructor = this;
+
+      var promise = new Constructor($$$internal$$noop, label);
+
+      if (!$$utils$$isArray(entries)) {
+        $$$internal$$reject(promise, new TypeError('You must pass an array to race.'));
+        return promise;
+      }
+
+      var length = entries.length;
+
+      function onFulfillment(value) {
+        $$$internal$$resolve(promise, value);
+      }
+
+      function onRejection(reason) {
+        $$$internal$$reject(promise, reason);
+      }
+
+      for (var i = 0; promise._state === $$$internal$$PENDING && i < length; i++) {
+        $$$internal$$subscribe(Constructor.resolve(entries[i]), undefined, onFulfillment, onRejection);
+      }
+
+      return promise;
+    };
+
+    var $$promise$resolve$$default = function resolve(object, label) {
+      /*jshint validthis:true */
+      var Constructor = this;
+
+      if (object && typeof object === 'object' && object.constructor === Constructor) {
+        return object;
+      }
+
+      var promise = new Constructor($$$internal$$noop, label);
+      $$$internal$$resolve(promise, object);
+      return promise;
+    };
+
+    var $$promise$reject$$default = function reject(reason, label) {
+      /*jshint validthis:true */
+      var Constructor = this;
+      var promise = new Constructor($$$internal$$noop, label);
+      $$$internal$$reject(promise, reason);
+      return promise;
+    };
+
+    var $$es6$promise$promise$$counter = 0;
+
+    function $$es6$promise$promise$$needsResolver() {
+      throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
+    }
+
+    function $$es6$promise$promise$$needsNew() {
+      throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");
+    }
+
+    var $$es6$promise$promise$$default = $$es6$promise$promise$$Promise;
+
+    /**
+      Promise objects represent the eventual result of an asynchronous operation. The
+      primary way of interacting with a promise is through its `then` method, which
+      registers callbacks to receive either a promise’s eventual value or the reason
+      why the promise cannot be fulfilled.
+
+      Terminology
+      -----------
+
+      - `promise` is an object or function with a `then` method whose behavior conforms to this specification.
+      - `thenable` is an object or function that defines a `then` method.
+      - `value` is any legal JavaScript value (including undefined, a thenable, or a promise).
+      - `exception` is a value that is thrown using the throw statement.
+      - `reason` is a value that indicates why a promise was rejected.
+      - `settled` the final resting state of a promise, fulfilled or rejected.
+
+      A promise can be in one of three states: pending, fulfilled, or rejected.
+
+      Promises that are fulfilled have a fulfillment value and are in the fulfilled
+      state.  Promises that are rejected have a rejection reason and are in the
+      rejected state.  A fulfillment value is never a thenable.
+
+      Promises can also be said to *resolve* a value.  If this value is also a
+      promise, then the original promise's settled state will match the value's
+      settled state.  So a promise that *resolves* a promise that rejects will
+      itself reject, and a promise that *resolves* a promise that fulfills will
+      itself fulfill.
+
+
+      Basic Usage:
+      ------------
+
+      ```js
+      var promise = new Promise(function(resolve, reject) {
+        // on success
+        resolve(value);
+
+        // on failure
+        reject(reason);
+      });
+
+      promise.then(function(value) {
+        // on fulfillment
+      }, function(reason) {
+        // on rejection
+      });
+      ```
+
+      Advanced Usage:
+      ---------------
+
+      Promises shine when abstracting away asynchronous interactions such as
+      `XMLHttpRequest`s.
+
+      ```js
+      function getJSON(url) {
+        return new Promise(function(resolve, reject){
+          var xhr = new XMLHttpRequest();
+
+          xhr.open('GET', url);
+          xhr.onreadystatechange = handler;
+          xhr.responseType = 'json';
+          xhr.setRequestHeader('Accept', 'application/json');
+          xhr.send();
+
+          function handler() {
+            if (this.readyState === this.DONE) {
+              if (this.status === 200) {
+                resolve(this.response);
+              } else {
+                reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']'));
+              }
+            }
+          };
+        });
+      }
+
+      getJSON('/posts.json').then(function(json) {
+        // on fulfillment
+      }, function(reason) {
+        // on rejection
+      });
+      ```
+
+      Unlike callbacks, promises are great composable primitives.
+
+      ```js
+      Promise.all([
+        getJSON('/posts'),
+        getJSON('/comments')
+      ]).then(function(values){
+        values[0] // => postsJSON
+        values[1] // => commentsJSON
+
+        return values;
+      });
+      ```
+
+      @class Promise
+      @param {function} resolver
+      Useful for tooling.
+      @constructor
+    */
+    function $$es6$promise$promise$$Promise(resolver) {
+      this._id = $$es6$promise$promise$$counter++;
+      this._state = undefined;
+      this._result = undefined;
+      this._subscribers = [];
+
+      if ($$$internal$$noop !== resolver) {
+        if (!$$utils$$isFunction(resolver)) {
+          $$es6$promise$promise$$needsResolver();
+        }
+
+        if (!(this instanceof $$es6$promise$promise$$Promise)) {
+          $$es6$promise$promise$$needsNew();
+        }
+
+        $$$internal$$initializePromise(this, resolver);
+      }
+    }
+
+    $$es6$promise$promise$$Promise.all = $$promise$all$$default;
+    $$es6$promise$promise$$Promise.race = $$promise$race$$default;
+    $$es6$promise$promise$$Promise.resolve = $$promise$resolve$$default;
+    $$es6$promise$promise$$Promise.reject = $$promise$reject$$default;
+
+    $$es6$promise$promise$$Promise.prototype = {
+      constructor: $$es6$promise$promise$$Promise,
+
+    /**
+      The primary way of interacting with a promise is through its `then` method,
+      which registers callbacks to receive either a promise's eventual value or the
+      reason why the promise cannot be fulfilled.
+
+      ```js
+      findUser().then(function(user){
+        // user is available
+      }, function(reason){
+        // user is unavailable, and you are given the reason why
+      });
+      ```
+
+      Chaining
+      --------
+
+      The return value of `then` is itself a promise.  This second, 'downstream'
+      promise is resolved with the return value of the first promise's fulfillment
+      or rejection handler, or rejected if the handler throws an exception.
+
+      ```js
+      findUser().then(function (user) {
+        return user.name;
+      }, function (reason) {
+        return 'default name';
+      }).then(function (userName) {
+        // If `findUser` fulfilled, `userName` will be the user's name, otherwise it
+        // will be `'default name'`
+      });
+
+      findUser().then(function (user) {
+        throw new Error('Found user, but still unhappy');
+      }, function (reason) {
+        throw new Error('`findUser` rejected and we're unhappy');
+      }).then(function (value) {
+        // never reached
+      }, function (reason) {
+        // if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'.
+        // If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'.
+      });
+      ```
+      If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream.
+
+      ```js
+      findUser().then(function (user) {
+        throw new PedagogicalException('Upstream error');
+      }).then(function (value) {
+        // never reached
+      }).then(function (value) {
+        // never reached
+      }, function (reason) {
+        // The `PedgagocialException` is propagated all the way down to here
+      });
+      ```
+
+      Assimilation
+      ------------
+
+      Sometimes the value you want to propagate to a downstream promise can only be
+      retrieved asynchronously. This can be achieved by returning a promise in the
+      fulfillment or rejection handler. The downstream promise will then be pending
+      until the returned promise is settled. This is called *assimilation*.
+
+      ```js
+      findUser().then(function (user) {
+        return findCommentsByAuthor(user);
+      }).then(function (comments) {
+        // The user's comments are now available
+      });
+      ```
+
+      If the assimliated promise rejects, then the downstream promise will also reject.
+
+      ```js
+      findUser().then(function (user) {
+        return findCommentsByAuthor(user);
+      }).then(function (comments) {
+        // If `findCommentsByAuthor` fulfills, we'll have the value here
+      }, function (reason) {
+        // If `findCommentsByAuthor` rejects, we'll have the reason here
+      });
+      ```
+
+      Simple Example
+      --------------
+
+      Synchronous Example
+
+      ```javascript
+      var result;
+
+      try {
+        result = findResult();
+        // success
+      } catch(reason) {
+        // failure
+      }
+      ```
+
+      Errback Example
+
+      ```js
+      findResult(function(result, err){
+        if (err) {
+          // failure
+        } else {
+          // success
+        }
+      });
+      ```
+
+      Promise Example;
+
+      ```javascript
+      findResult().then(function(result){
+        // success
+      }, function(reason){
+        // failure
+      });
+      ```
+
+      Advanced Example
+      --------------
+
+      Synchronous Example
+
+      ```javascript
+      var author, books;
+
+      try {
+        author = findAuthor();
+        books  = findBooksByAuthor(author);
+        // success
+      } catch(reason) {
+        // failure
+      }
+      ```
+
+      Errback Example
+
+      ```js
+
+      function foundBooks(books) {
+
+      }
+
+      function failure(reason) {
+
+      }
+
+      findAuthor(function(author, err){
+        if (err) {
+          failure(err);
+          // failure
+        } else {
+          try {
+            findBoooksByAuthor(author, function(books, err) {
+              if (err) {
+                failure(err);
+              } else {
+                try {
+                  foundBooks(books);
+                } catch(reason) {
+                  failure(reason);
+                }
+              }
+            });
+          } catch(error) {
+            failure(err);
+          }
+          // success
+        }
+      });
+      ```
+
+      Promise Example;
+
+      ```javascript
+      findAuthor().
+        then(findBooksByAuthor).
+        then(function(books){
+          // found books
+      }).catch(function(reason){
+        // something went wrong
+      });
+      ```
+
+      @method then
+      @param {Function} onFulfilled
+      @param {Function} onRejected
+      Useful for tooling.
+      @return {Promise}
+    */
+      then: function(onFulfillment, onRejection) {
+        var parent = this;
+        var state = parent._state;
+
+        if (state === $$$internal$$FULFILLED && !onFulfillment || state === $$$internal$$REJECTED && !onRejection) {
+          return this;
+        }
+
+        var child = new this.constructor($$$internal$$noop);
+        var result = parent._result;
+
+        if (state) {
+          var callback = arguments[state - 1];
+          $$asap$$default(function(){
+            $$$internal$$invokeCallback(state, child, callback, result);
+          });
+        } else {
+          $$$internal$$subscribe(parent, child, onFulfillment, onRejection);
+        }
+
+        return child;
+      },
+
+    /**
+      `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same
+      as the catch block of a try/catch statement.
+
+      ```js
+      function findAuthor(){
+        throw new Error('couldn't find that author');
+      }
+
+      // synchronous
+      try {
+        findAuthor();
+      } catch(reason) {
+        // something went wrong
+      }
+
+      // async with promises
+      findAuthor().catch(function(reason){
+        // something went wrong
+      });
+      ```
+
+      @method catch
+      @param {Function} onRejection
+      Useful for tooling.
+      @return {Promise}
+    */
+      'catch': function(onRejection) {
+        return this.then(null, onRejection);
+      }
+    };
+
+    var $$es6$promise$polyfill$$default = function polyfill() {
+      var local;
+
+      if (typeof global !== 'undefined') {
+        local = global;
+      } else if (typeof window !== 'undefined' && window.document) {
+        local = window;
+      } else {
+        local = self;
+      }
+
+      var es6PromiseSupport =
+        "Promise" in local &&
+        // Some of these methods are missing from
+        // Firefox/Chrome experimental implementations
+        "resolve" in local.Promise &&
+        "reject" in local.Promise &&
+        "all" in local.Promise &&
+        "race" in local.Promise &&
+        // Older version of the spec had a resolver object
+        // as the arg rather than a function
+        (function() {
+          var resolve;
+          new local.Promise(function(r) { resolve = r; });
+          return $$utils$$isFunction(resolve);
+        }());
+
+      if (!es6PromiseSupport) {
+        local.Promise = $$es6$promise$promise$$default;
+      }
+    };
+
+    var es6$promise$umd$$ES6Promise = {
+      'Promise': $$es6$promise$promise$$default,
+      'polyfill': $$es6$promise$polyfill$$default
+    };
+
+    /* global define:true module:true window: true */
+    if (typeof define === 'function' && define['amd']) {
+      define(function() { return es6$promise$umd$$ES6Promise; });
+    } else if (typeof module !== 'undefined' && module['exports']) {
+      module['exports'] = es6$promise$umd$$ES6Promise;
+    } else if (typeof this !== 'undefined') {
+      this['ES6Promise'] = es6$promise$umd$$ES6Promise;
+    }
+}).call(this);
\ No newline at end of file
diff --git a/node_modules/node-firefox-connect/node_modules/es6-promise/dist/es6-promise.min.js b/node_modules/node-firefox-connect/node_modules/es6-promise/dist/es6-promise.min.js
new file mode 100644
index 0000000..6e0c99f
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/es6-promise/dist/es6-promise.min.js
@@ -0,0 +1,18 @@
+/*!
+ * @overview es6-promise - a tiny implementation of Promises/A+.
+ * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald)
+ * @license   Licensed under MIT license
+ *            See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE
+ * @version   2.0.1
+ */
+
+(function(){function r(a,b){n[l]=a;n[l+1]=b;l+=2;2===l&&A()}function s(a){return"function"===typeof a}function F(){return function(){process.nextTick(t)}}function G(){var a=0,b=new B(t),c=document.createTextNode("");b.observe(c,{characterData:!0});return function(){c.data=a=++a%2}}function H(){var a=new MessageChannel;a.port1.onmessage=t;return function(){a.port2.postMessage(0)}}function I(){return function(){setTimeout(t,1)}}function t(){for(var a=0;a<l;a+=2)(0,n[a])(n[a+1]),n[a]=void 0,n[a+1]=void 0;
+l=0}function p(){}function J(a,b,c,d){try{a.call(b,c,d)}catch(e){return e}}function K(a,b,c){r(function(a){var e=!1,f=J(c,b,function(c){e||(e=!0,b!==c?q(a,c):m(a,c))},function(b){e||(e=!0,g(a,b))});!e&&f&&(e=!0,g(a,f))},a)}function L(a,b){1===b.a?m(a,b.b):2===a.a?g(a,b.b):u(b,void 0,function(b){q(a,b)},function(b){g(a,b)})}function q(a,b){if(a===b)g(a,new TypeError("You cannot resolve a promise with itself"));else if("function"===typeof b||"object"===typeof b&&null!==b)if(b.constructor===a.constructor)L(a,
+b);else{var c;try{c=b.then}catch(d){v.error=d,c=v}c===v?g(a,v.error):void 0===c?m(a,b):s(c)?K(a,b,c):m(a,b)}else m(a,b)}function M(a){a.f&&a.f(a.b);x(a)}function m(a,b){void 0===a.a&&(a.b=b,a.a=1,0!==a.e.length&&r(x,a))}function g(a,b){void 0===a.a&&(a.a=2,a.b=b,r(M,a))}function u(a,b,c,d){var e=a.e,f=e.length;a.f=null;e[f]=b;e[f+1]=c;e[f+2]=d;0===f&&a.a&&r(x,a)}function x(a){var b=a.e,c=a.a;if(0!==b.length){for(var d,e,f=a.b,g=0;g<b.length;g+=3)d=b[g],e=b[g+c],d?C(c,d,e,f):e(f);a.e.length=0}}function D(){this.error=
+null}function C(a,b,c,d){var e=s(c),f,k,h,l;if(e){try{f=c(d)}catch(n){y.error=n,f=y}f===y?(l=!0,k=f.error,f=null):h=!0;if(b===f){g(b,new TypeError("A promises callback cannot return that same promise."));return}}else f=d,h=!0;void 0===b.a&&(e&&h?q(b,f):l?g(b,k):1===a?m(b,f):2===a&&g(b,f))}function N(a,b){try{b(function(b){q(a,b)},function(b){g(a,b)})}catch(c){g(a,c)}}function k(a,b,c,d){this.n=a;this.c=new a(p,d);this.i=c;this.o(b)?(this.m=b,this.d=this.length=b.length,this.l(),0===this.length?m(this.c,
+this.b):(this.length=this.length||0,this.k(),0===this.d&&m(this.c,this.b))):g(this.c,this.p())}function h(a){O++;this.b=this.a=void 0;this.e=[];if(p!==a){if(!s(a))throw new TypeError("You must pass a resolver function as the first argument to the promise constructor");if(!(this instanceof h))throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");N(this,a)}}var E=Array.isArray?Array.isArray:function(a){return"[object Array]"===
+Object.prototype.toString.call(a)},l=0,w="undefined"!==typeof window?window:{},B=w.MutationObserver||w.WebKitMutationObserver,w="undefined"!==typeof Uint8ClampedArray&&"undefined"!==typeof importScripts&&"undefined"!==typeof MessageChannel,n=Array(1E3),A;A="undefined"!==typeof process&&"[object process]"==={}.toString.call(process)?F():B?G():w?H():I();var v=new D,y=new D;k.prototype.o=function(a){return E(a)};k.prototype.p=function(){return Error("Array Methods must be provided an Array")};k.prototype.l=
+function(){this.b=Array(this.length)};k.prototype.k=function(){for(var a=this.length,b=this.c,c=this.m,d=0;void 0===b.a&&d<a;d++)this.j(c[d],d)};k.prototype.j=function(a,b){var c=this.n;"object"===typeof a&&null!==a?a.constructor===c&&void 0!==a.a?(a.f=null,this.g(a.a,b,a.b)):this.q(c.resolve(a),b):(this.d--,this.b[b]=this.h(a))};k.prototype.g=function(a,b,c){var d=this.c;void 0===d.a&&(this.d--,this.i&&2===a?g(d,c):this.b[b]=this.h(c));0===this.d&&m(d,this.b)};k.prototype.h=function(a){return a};
+k.prototype.q=function(a,b){var c=this;u(a,void 0,function(a){c.g(1,b,a)},function(a){c.g(2,b,a)})};var O=0;h.all=function(a,b){return(new k(this,a,!0,b)).c};h.race=function(a,b){function c(a){q(e,a)}function d(a){g(e,a)}var e=new this(p,b);if(!E(a))return (g(e,new TypeError("You must pass an array to race.")), e);for(var f=a.length,h=0;void 0===e.a&&h<f;h++)u(this.resolve(a[h]),void 0,c,d);return e};h.resolve=function(a,b){if(a&&"object"===typeof a&&a.constructor===this)return a;var c=new this(p,b);
+q(c,a);return c};h.reject=function(a,b){var c=new this(p,b);g(c,a);return c};h.prototype={constructor:h,then:function(a,b){var c=this.a;if(1===c&&!a||2===c&&!b)return this;var d=new this.constructor(p),e=this.b;if(c){var f=arguments[c-1];r(function(){C(c,d,f,e)})}else u(this,d,a,b);return d},"catch":function(a){return this.then(null,a)}};var z={Promise:h,polyfill:function(){var a;a="undefined"!==typeof global?global:"undefined"!==typeof window&&window.document?window:self;"Promise"in a&&"resolve"in
+a.Promise&&"reject"in a.Promise&&"all"in a.Promise&&"race"in a.Promise&&function(){var b;new a.Promise(function(a){b=a});return s(b)}()||(a.Promise=h)}};"function"===typeof define&&define.amd?define(function(){return z}):"undefined"!==typeof module&&module.exports?module.exports=z:"undefined"!==typeof this&&(this.ES6Promise=z)}).call(this);
diff --git a/node_modules/node-firefox-connect/node_modules/es6-promise/lib/calculateVersion.js b/node_modules/node-firefox-connect/node_modules/es6-promise/lib/calculateVersion.js
new file mode 100644
index 0000000..018e364
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/es6-promise/lib/calculateVersion.js
@@ -0,0 +1,36 @@
+'use strict';
+
+var fs   = require('fs');
+var path = require('path');
+
+module.exports = function () {
+  var packageVersion = require('../package.json').version;
+  var output         = [packageVersion];
+  var gitPath        = path.join(__dirname,'..','.git');
+  var headFilePath   = path.join(gitPath, 'HEAD');
+
+  if (packageVersion.indexOf('+') > -1) {
+    try {
+      if (fs.existsSync(headFilePath)) {
+        var headFile = fs.readFileSync(headFilePath, {encoding: 'utf8'});
+        var branchName = headFile.split('/').slice(-1)[0].trim();
+        var refPath = headFile.split(' ')[1];
+        var branchSHA;
+
+        if (refPath) {
+          var branchPath = path.join(gitPath, refPath.trim());
+          branchSHA  = fs.readFileSync(branchPath);
+        } else {
+          branchSHA = branchName;
+        }
+
+        output.push(branchSHA.slice(0,10));
+      }
+    } catch (err) {
+      console.error(err.stack);
+    }
+    return output.join('.');
+  } else {
+    return packageVersion;
+  }
+};
diff --git a/node_modules/node-firefox-connect/node_modules/es6-promise/lib/es6-promise.umd.js b/node_modules/node-firefox-connect/node_modules/es6-promise/lib/es6-promise.umd.js
new file mode 100644
index 0000000..cd01553
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/es6-promise/lib/es6-promise.umd.js
@@ -0,0 +1,16 @@
+import Promise from './es6-promise/promise';
+import polyfill from './es6-promise/polyfill';
+
+var ES6Promise = {
+  'Promise': Promise,
+  'polyfill': polyfill
+};
+
+/* global define:true module:true window: true */
+if (typeof define === 'function' && define['amd']) {
+  define(function() { return ES6Promise; });
+} else if (typeof module !== 'undefined' && module['exports']) {
+  module['exports'] = ES6Promise;
+} else if (typeof this !== 'undefined') {
+  this['ES6Promise'] = ES6Promise;
+}
diff --git a/node_modules/node-firefox-connect/node_modules/es6-promise/lib/es6-promise/-internal.js b/node_modules/node-firefox-connect/node_modules/es6-promise/lib/es6-promise/-internal.js
new file mode 100644
index 0000000..afef22e
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/es6-promise/lib/es6-promise/-internal.js
@@ -0,0 +1,251 @@
+import {
+  objectOrFunction,
+  isFunction
+} from './utils';
+
+import asap from './asap';
+
+function noop() {}
+
+var PENDING   = void 0;
+var FULFILLED = 1;
+var REJECTED  = 2;
+
+var GET_THEN_ERROR = new ErrorObject();
+
+function selfFullfillment() {
+  return new TypeError("You cannot resolve a promise with itself");
+}
+
+function cannotReturnOwn() {
+  return new TypeError('A promises callback cannot return that same promise.')
+}
+
+function getThen(promise) {
+  try {
+    return promise.then;
+  } catch(error) {
+    GET_THEN_ERROR.error = error;
+    return GET_THEN_ERROR;
+  }
+}
+
+function tryThen(then, value, fulfillmentHandler, rejectionHandler) {
+  try {
+    then.call(value, fulfillmentHandler, rejectionHandler);
+  } catch(e) {
+    return e;
+  }
+}
+
+function handleForeignThenable(promise, thenable, then) {
+   asap(function(promise) {
+    var sealed = false;
+    var error = tryThen(then, thenable, function(value) {
+      if (sealed) { return; }
+      sealed = true;
+      if (thenable !== value) {
+        resolve(promise, value);
+      } else {
+        fulfill(promise, value);
+      }
+    }, function(reason) {
+      if (sealed) { return; }
+      sealed = true;
+
+      reject(promise, reason);
+    }, 'Settle: ' + (promise._label || ' unknown promise'));
+
+    if (!sealed && error) {
+      sealed = true;
+      reject(promise, error);
+    }
+  }, promise);
+}
+
+function handleOwnThenable(promise, thenable) {
+  if (thenable._state === FULFILLED) {
+    fulfill(promise, thenable._result);
+  } else if (promise._state === REJECTED) {
+    reject(promise, thenable._result);
+  } else {
+    subscribe(thenable, undefined, function(value) {
+      resolve(promise, value);
+    }, function(reason) {
+      reject(promise, reason);
+    });
+  }
+}
+
+function handleMaybeThenable(promise, maybeThenable) {
+  if (maybeThenable.constructor === promise.constructor) {
+    handleOwnThenable(promise, maybeThenable);
+  } else {
+    var then = getThen(maybeThenable);
+
+    if (then === GET_THEN_ERROR) {
+      reject(promise, GET_THEN_ERROR.error);
+    } else if (then === undefined) {
+      fulfill(promise, maybeThenable);
+    } else if (isFunction(then)) {
+      handleForeignThenable(promise, maybeThenable, then);
+    } else {
+      fulfill(promise, maybeThenable);
+    }
+  }
+}
+
+function resolve(promise, value) {
+  if (promise === value) {
+    reject(promise, selfFullfillment());
+  } else if (objectOrFunction(value)) {
+    handleMaybeThenable(promise, value);
+  } else {
+    fulfill(promise, value);
+  }
+}
+
+function publishRejection(promise) {
+  if (promise._onerror) {
+    promise._onerror(promise._result);
+  }
+
+  publish(promise);
+}
+
+function fulfill(promise, value) {
+  if (promise._state !== PENDING) { return; }
+
+  promise._result = value;
+  promise._state = FULFILLED;
+
+  if (promise._subscribers.length === 0) {
+  } else {
+    asap(publish, promise);
+  }
+}
+
+function reject(promise, reason) {
+  if (promise._state !== PENDING) { return; }
+  promise._state = REJECTED;
+  promise._result = reason;
+
+  asap(publishRejection, promise);
+}
+
+function subscribe(parent, child, onFulfillment, onRejection) {
+  var subscribers = parent._subscribers;
+  var length = subscribers.length;
+
+  parent._onerror = null;
+
+  subscribers[length] = child;
+  subscribers[length + FULFILLED] = onFulfillment;
+  subscribers[length + REJECTED]  = onRejection;
+
+  if (length === 0 && parent._state) {
+    asap(publish, parent);
+  }
+}
+
+function publish(promise) {
+  var subscribers = promise._subscribers;
+  var settled = promise._state;
+
+  if (subscribers.length === 0) { return; }
+
+  var child, callback, detail = promise._result;
+
+  for (var i = 0; i < subscribers.length; i += 3) {
+    child = subscribers[i];
+    callback = subscribers[i + settled];
+
+    if (child) {
+      invokeCallback(settled, child, callback, detail);
+    } else {
+      callback(detail);
+    }
+  }
+
+  promise._subscribers.length = 0;
+}
+
+function ErrorObject() {
+  this.error = null;
+}
+
+var TRY_CATCH_ERROR = new ErrorObject();
+
+function tryCatch(callback, detail) {
+  try {
+    return callback(detail);
+  } catch(e) {
+    TRY_CATCH_ERROR.error = e;
+    return TRY_CATCH_ERROR;
+  }
+}
+
+function invokeCallback(settled, promise, callback, detail) {
+  var hasCallback = isFunction(callback),
+      value, error, succeeded, failed;
+
+  if (hasCallback) {
+    value = tryCatch(callback, detail);
+
+    if (value === TRY_CATCH_ERROR) {
+      failed = true;
+      error = value.error;
+      value = null;
+    } else {
+      succeeded = true;
+    }
+
+    if (promise === value) {
+      reject(promise, cannotReturnOwn());
+      return;
+    }
+
+  } else {
+    value = detail;
+    succeeded = true;
+  }
+
+  if (promise._state !== PENDING) {
+    // noop
+  } else if (hasCallback && succeeded) {
+    resolve(promise, value);
+  } else if (failed) {
+    reject(promise, error);
+  } else if (settled === FULFILLED) {
+    fulfill(promise, value);
+  } else if (settled === REJECTED) {
+    reject(promise, value);
+  }
+}
+
+function initializePromise(promise, resolver) {
+  try {
+    resolver(function resolvePromise(value){
+      resolve(promise, value);
+    }, function rejectPromise(reason) {
+      reject(promise, reason);
+    });
+  } catch(e) {
+    reject(promise, e);
+  }
+}
+
+export {
+  noop,
+  resolve,
+  reject,
+  fulfill,
+  subscribe,
+  publish,
+  publishRejection,
+  initializePromise,
+  invokeCallback,
+  FULFILLED,
+  REJECTED,
+  PENDING
+};
diff --git a/node_modules/node-firefox-connect/node_modules/es6-promise/lib/es6-promise/asap.js b/node_modules/node-firefox-connect/node_modules/es6-promise/lib/es6-promise/asap.js
new file mode 100644
index 0000000..4872589
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/es6-promise/lib/es6-promise/asap.js
@@ -0,0 +1,82 @@
+var len = 0;
+
+export default function asap(callback, arg) {
+  queue[len] = callback;
+  queue[len + 1] = arg;
+  len += 2;
+  if (len === 2) {
+    // If len is 1, that means that we need to schedule an async flush.
+    // If additional callbacks are queued before the queue is flushed, they
+    // will be processed by this flush that we are scheduling.
+    scheduleFlush();
+  }
+}
+
+var browserGlobal = (typeof window !== 'undefined') ? window : {};
+var BrowserMutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver;
+
+// test for web worker but not in IE10
+var isWorker = typeof Uint8ClampedArray !== 'undefined' &&
+  typeof importScripts !== 'undefined' &&
+  typeof MessageChannel !== 'undefined';
+
+// node
+function useNextTick() {
+  return function() {
+    process.nextTick(flush);
+  };
+}
+
+function useMutationObserver() {
+  var iterations = 0;
+  var observer = new BrowserMutationObserver(flush);
+  var node = document.createTextNode('');
+  observer.observe(node, { characterData: true });
+
+  return function() {
+    node.data = (iterations = ++iterations % 2);
+  };
+}
+
+// web worker
+function useMessageChannel() {
+  var channel = new MessageChannel();
+  channel.port1.onmessage = flush;
+  return function () {
+    channel.port2.postMessage(0);
+  };
+}
+
+function useSetTimeout() {
+  return function() {
+    setTimeout(flush, 1);
+  };
+}
+
+var queue = new Array(1000);
+function flush() {
+  for (var i = 0; i < len; i+=2) {
+    var callback = queue[i];
+    var arg = queue[i+1];
+
+    callback(arg);
+
+    queue[i] = undefined;
+    queue[i+1] = undefined;
+  }
+
+  len = 0;
+}
+
+var scheduleFlush;
+
+// Decide what async method to use to triggering processing of queued callbacks:
+if (typeof process !== 'undefined' && {}.toString.call(process) === '[object process]') {
+  scheduleFlush = useNextTick();
+} else if (BrowserMutationObserver) {
+  scheduleFlush = useMutationObserver();
+} else if (isWorker) {
+  scheduleFlush = useMessageChannel();
+} else {
+  scheduleFlush = useSetTimeout();
+}
diff --git a/node_modules/node-firefox-connect/node_modules/es6-promise/lib/es6-promise/enumerator.js b/node_modules/node-firefox-connect/node_modules/es6-promise/lib/es6-promise/enumerator.js
new file mode 100644
index 0000000..7d3f803
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/es6-promise/lib/es6-promise/enumerator.js
@@ -0,0 +1,125 @@
+import {
+  isArray,
+  isMaybeThenable
+} from './utils';
+
+import {
+  noop,
+  reject,
+  fulfill,
+  subscribe,
+  FULFILLED,
+  REJECTED,
+  PENDING
+} from './-internal';
+
+export function makeSettledResult(state, position, value) {
+  if (state === FULFILLED) {
+    return {
+      state: 'fulfilled',
+      value: value
+    };
+  } else {
+    return {
+      state: 'rejected',
+      reason: value
+    };
+  }
+}
+
+function Enumerator(Constructor, input, abortOnReject, label) {
+  this._instanceConstructor = Constructor;
+  this.promise = new Constructor(noop, label);
+  this._abortOnReject = abortOnReject;
+
+  if (this._validateInput(input)) {
+    this._input     = input;
+    this.length     = input.length;
+    this._remaining = input.length;
+
+    this._init();
+
+    if (this.length === 0) {
+      fulfill(this.promise, this._result);
+    } else {
+      this.length = this.length || 0;
+      this._enumerate();
+      if (this._remaining === 0) {
+        fulfill(this.promise, this._result);
+      }
+    }
+  } else {
+    reject(this.promise, this._validationError());
+  }
+}
+
+Enumerator.prototype._validateInput = function(input) {
+  return isArray(input);
+};
+
+Enumerator.prototype._validationError = function() {
+  return new Error('Array Methods must be provided an Array');
+};
+
+Enumerator.prototype._init = function() {
+  this._result = new Array(this.length);
+};
+
+export default Enumerator;
+
+Enumerator.prototype._enumerate = function() {
+  var length  = this.length;
+  var promise = this.promise;
+  var input   = this._input;
+
+  for (var i = 0; promise._state === PENDING && i < length; i++) {
+    this._eachEntry(input[i], i);
+  }
+};
+
+Enumerator.prototype._eachEntry = function(entry, i) {
+  var c = this._instanceConstructor;
+  if (isMaybeThenable(entry)) {
+    if (entry.constructor === c && entry._state !== PENDING) {
+      entry._onerror = null;
+      this._settledAt(entry._state, i, entry._result);
+    } else {
+      this._willSettleAt(c.resolve(entry), i);
+    }
+  } else {
+    this._remaining--;
+    this._result[i] = this._makeResult(FULFILLED, i, entry);
+  }
+};
+
+Enumerator.prototype._settledAt = function(state, i, value) {
+  var promise = this.promise;
+
+  if (promise._state === PENDING) {
+    this._remaining--;
+
+    if (this._abortOnReject && state === REJECTED) {
+      reject(promise, value);
+    } else {
+      this._result[i] = this._makeResult(state, i, value);
+    }
+  }
+
+  if (this._remaining === 0) {
+    fulfill(promise, this._result);
+  }
+};
+
+Enumerator.prototype._makeResult = function(state, i, value) {
+  return value;
+};
+
+Enumerator.prototype._willSettleAt = function(promise, i) {
+  var enumerator = this;
+
+  subscribe(promise, undefined, function(value) {
+    enumerator._settledAt(FULFILLED, i, value);
+  }, function(reason) {
+    enumerator._settledAt(REJECTED, i, reason);
+  });
+};
diff --git a/node_modules/node-firefox-connect/node_modules/es6-promise/lib/es6-promise/polyfill.js b/node_modules/node-firefox-connect/node_modules/es6-promise/lib/es6-promise/polyfill.js
new file mode 100644
index 0000000..e5b0165
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/es6-promise/lib/es6-promise/polyfill.js
@@ -0,0 +1,35 @@
+/*global self*/
+import { default as RSVPPromise } from "./promise";
+import { isFunction } from "./utils";
+
+export default function polyfill() {
+  var local;
+
+  if (typeof global !== 'undefined') {
+    local = global;
+  } else if (typeof window !== 'undefined' && window.document) {
+    local = window;
+  } else {
+    local = self;
+  }
+
+  var es6PromiseSupport =
+    "Promise" in local &&
+    // Some of these methods are missing from
+    // Firefox/Chrome experimental implementations
+    "resolve" in local.Promise &&
+    "reject" in local.Promise &&
+    "all" in local.Promise &&
+    "race" in local.Promise &&
+    // Older version of the spec had a resolver object
+    // as the arg rather than a function
+    (function() {
+      var resolve;
+      new local.Promise(function(r) { resolve = r; });
+      return isFunction(resolve);
+    }());
+
+  if (!es6PromiseSupport) {
+    local.Promise = RSVPPromise;
+  }
+}
diff --git a/node_modules/node-firefox-connect/node_modules/es6-promise/lib/es6-promise/promise.js b/node_modules/node-firefox-connect/node_modules/es6-promise/lib/es6-promise/promise.js
new file mode 100644
index 0000000..5e865a7
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/es6-promise/lib/es6-promise/promise.js
@@ -0,0 +1,409 @@
+import {
+  isFunction,
+  now
+} from './utils';
+
+import {
+  noop,
+  subscribe,
+  initializePromise,
+  invokeCallback,
+  FULFILLED,
+  REJECTED
+} from './-internal';
+
+import asap from './asap';
+
+import all from './promise/all';
+import race from './promise/race';
+import Resolve from './promise/resolve';
+import Reject from './promise/reject';
+
+var counter = 0;
+
+function needsResolver() {
+  throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
+}
+
+function needsNew() {
+  throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");
+}
+
+export default Promise;
+/**
+  Promise objects represent the eventual result of an asynchronous operation. The
+  primary way of interacting with a promise is through its `then` method, which
+  registers callbacks to receive either a promise’s eventual value or the reason
+  why the promise cannot be fulfilled.
+
+  Terminology
+  -----------
+
+  - `promise` is an object or function with a `then` method whose behavior conforms to this specification.
+  - `thenable` is an object or function that defines a `then` method.
+  - `value` is any legal JavaScript value (including undefined, a thenable, or a promise).
+  - `exception` is a value that is thrown using the throw statement.
+  - `reason` is a value that indicates why a promise was rejected.
+  - `settled` the final resting state of a promise, fulfilled or rejected.
+
+  A promise can be in one of three states: pending, fulfilled, or rejected.
+
+  Promises that are fulfilled have a fulfillment value and are in the fulfilled
+  state.  Promises that are rejected have a rejection reason and are in the
+  rejected state.  A fulfillment value is never a thenable.
+
+  Promises can also be said to *resolve* a value.  If this value is also a
+  promise, then the original promise's settled state will match the value's
+  settled state.  So a promise that *resolves* a promise that rejects will
+  itself reject, and a promise that *resolves* a promise that fulfills will
+  itself fulfill.
+
+
+  Basic Usage:
+  ------------
+
+  ```js
+  var promise = new Promise(function(resolve, reject) {
+    // on success
+    resolve(value);
+
+    // on failure
+    reject(reason);
+  });
+
+  promise.then(function(value) {
+    // on fulfillment
+  }, function(reason) {
+    // on rejection
+  });
+  ```
+
+  Advanced Usage:
+  ---------------
+
+  Promises shine when abstracting away asynchronous interactions such as
+  `XMLHttpRequest`s.
+
+  ```js
+  function getJSON(url) {
+    return new Promise(function(resolve, reject){
+      var xhr = new XMLHttpRequest();
+
+      xhr.open('GET', url);
+      xhr.onreadystatechange = handler;
+      xhr.responseType = 'json';
+      xhr.setRequestHeader('Accept', 'application/json');
+      xhr.send();
+
+      function handler() {
+        if (this.readyState === this.DONE) {
+          if (this.status === 200) {
+            resolve(this.response);
+          } else {
+            reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']'));
+          }
+        }
+      };
+    });
+  }
+
+  getJSON('/posts.json').then(function(json) {
+    // on fulfillment
+  }, function(reason) {
+    // on rejection
+  });
+  ```
+
+  Unlike callbacks, promises are great composable primitives.
+
+  ```js
+  Promise.all([
+    getJSON('/posts'),
+    getJSON('/comments')
+  ]).then(function(values){
+    values[0] // => postsJSON
+    values[1] // => commentsJSON
+
+    return values;
+  });
+  ```
+
+  @class Promise
+  @param {function} resolver
+  Useful for tooling.
+  @constructor
+*/
+function Promise(resolver) {
+  this._id = counter++;
+  this._state = undefined;
+  this._result = undefined;
+  this._subscribers = [];
+
+  if (noop !== resolver) {
+    if (!isFunction(resolver)) {
+      needsResolver();
+    }
+
+    if (!(this instanceof Promise)) {
+      needsNew();
+    }
+
+    initializePromise(this, resolver);
+  }
+}
+
+Promise.all = all;
+Promise.race = race;
+Promise.resolve = Resolve;
+Promise.reject = Reject;
+
+Promise.prototype = {
+  constructor: Promise,
+
+/**
+  The primary way of interacting with a promise is through its `then` method,
+  which registers callbacks to receive either a promise's eventual value or the
+  reason why the promise cannot be fulfilled.
+
+  ```js
+  findUser().then(function(user){
+    // user is available
+  }, function(reason){
+    // user is unavailable, and you are given the reason why
+  });
+  ```
+
+  Chaining
+  --------
+
+  The return value of `then` is itself a promise.  This second, 'downstream'
+  promise is resolved with the return value of the first promise's fulfillment
+  or rejection handler, or rejected if the handler throws an exception.
+
+  ```js
+  findUser().then(function (user) {
+    return user.name;
+  }, function (reason) {
+    return 'default name';
+  }).then(function (userName) {
+    // If `findUser` fulfilled, `userName` will be the user's name, otherwise it
+    // will be `'default name'`
+  });
+
+  findUser().then(function (user) {
+    throw new Error('Found user, but still unhappy');
+  }, function (reason) {
+    throw new Error('`findUser` rejected and we're unhappy');
+  }).then(function (value) {
+    // never reached
+  }, function (reason) {
+    // if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'.
+    // If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'.
+  });
+  ```
+  If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream.
+
+  ```js
+  findUser().then(function (user) {
+    throw new PedagogicalException('Upstream error');
+  }).then(function (value) {
+    // never reached
+  }).then(function (value) {
+    // never reached
+  }, function (reason) {
+    // The `PedgagocialException` is propagated all the way down to here
+  });
+  ```
+
+  Assimilation
+  ------------
+
+  Sometimes the value you want to propagate to a downstream promise can only be
+  retrieved asynchronously. This can be achieved by returning a promise in the
+  fulfillment or rejection handler. The downstream promise will then be pending
+  until the returned promise is settled. This is called *assimilation*.
+
+  ```js
+  findUser().then(function (user) {
+    return findCommentsByAuthor(user);
+  }).then(function (comments) {
+    // The user's comments are now available
+  });
+  ```
+
+  If the assimliated promise rejects, then the downstream promise will also reject.
+
+  ```js
+  findUser().then(function (user) {
+    return findCommentsByAuthor(user);
+  }).then(function (comments) {
+    // If `findCommentsByAuthor` fulfills, we'll have the value here
+  }, function (reason) {
+    // If `findCommentsByAuthor` rejects, we'll have the reason here
+  });
+  ```
+
+  Simple Example
+  --------------
+
+  Synchronous Example
+
+  ```javascript
+  var result;
+
+  try {
+    result = findResult();
+    // success
+  } catch(reason) {
+    // failure
+  }
+  ```
+
+  Errback Example
+
+  ```js
+  findResult(function(result, err){
+    if (err) {
+      // failure
+    } else {
+      // success
+    }
+  });
+  ```
+
+  Promise Example;
+
+  ```javascript
+  findResult().then(function(result){
+    // success
+  }, function(reason){
+    // failure
+  });
+  ```
+
+  Advanced Example
+  --------------
+
+  Synchronous Example
+
+  ```javascript
+  var author, books;
+
+  try {
+    author = findAuthor();
+    books  = findBooksByAuthor(author);
+    // success
+  } catch(reason) {
+    // failure
+  }
+  ```
+
+  Errback Example
+
+  ```js
+
+  function foundBooks(books) {
+
+  }
+
+  function failure(reason) {
+
+  }
+
+  findAuthor(function(author, err){
+    if (err) {
+      failure(err);
+      // failure
+    } else {
+      try {
+        findBoooksByAuthor(author, function(books, err) {
+          if (err) {
+            failure(err);
+          } else {
+            try {
+              foundBooks(books);
+            } catch(reason) {
+              failure(reason);
+            }
+          }
+        });
+      } catch(error) {
+        failure(err);
+      }
+      // success
+    }
+  });
+  ```
+
+  Promise Example;
+
+  ```javascript
+  findAuthor().
+    then(findBooksByAuthor).
+    then(function(books){
+      // found books
+  }).catch(function(reason){
+    // something went wrong
+  });
+  ```
+
+  @method then
+  @param {Function} onFulfilled
+  @param {Function} onRejected
+  Useful for tooling.
+  @return {Promise}
+*/
+  then: function(onFulfillment, onRejection) {
+    var parent = this;
+    var state = parent._state;
+
+    if (state === FULFILLED && !onFulfillment || state === REJECTED && !onRejection) {
+      return this;
+    }
+
+    var child = new this.constructor(noop);
+    var result = parent._result;
+
+    if (state) {
+      var callback = arguments[state - 1];
+      asap(function(){
+        invokeCallback(state, child, callback, result);
+      });
+    } else {
+      subscribe(parent, child, onFulfillment, onRejection);
+    }
+
+    return child;
+  },
+
+/**
+  `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same
+  as the catch block of a try/catch statement.
+
+  ```js
+  function findAuthor(){
+    throw new Error('couldn't find that author');
+  }
+
+  // synchronous
+  try {
+    findAuthor();
+  } catch(reason) {
+    // something went wrong
+  }
+
+  // async with promises
+  findAuthor().catch(function(reason){
+    // something went wrong
+  });
+  ```
+
+  @method catch
+  @param {Function} onRejection
+  Useful for tooling.
+  @return {Promise}
+*/
+  'catch': function(onRejection) {
+    return this.then(null, onRejection);
+  }
+};
diff --git a/node_modules/node-firefox-connect/node_modules/es6-promise/lib/es6-promise/promise/all.js b/node_modules/node-firefox-connect/node_modules/es6-promise/lib/es6-promise/promise/all.js
new file mode 100644
index 0000000..8db7e71
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/es6-promise/lib/es6-promise/promise/all.js
@@ -0,0 +1,52 @@
+import Enumerator from '../enumerator';
+
+/**
+  `Promise.all` accepts an array of promises, and returns a new promise which
+  is fulfilled with an array of fulfillment values for the passed promises, or
+  rejected with the reason of the first passed promise to be rejected. It casts all
+  elements of the passed iterable to promises as it runs this algorithm.
+
+  Example:
+
+  ```javascript
+  var promise1 = resolve(1);
+  var promise2 = resolve(2);
+  var promise3 = resolve(3);
+  var promises = [ promise1, promise2, promise3 ];
+
+  Promise.all(promises).then(function(array){
+    // The array here would be [ 1, 2, 3 ];
+  });
+  ```
+
+  If any of the `promises` given to `all` are rejected, the first promise
+  that is rejected will be given as an argument to the returned promises's
+  rejection handler. For example:
+
+  Example:
+
+  ```javascript
+  var promise1 = resolve(1);
+  var promise2 = reject(new Error("2"));
+  var promise3 = reject(new Error("3"));
+  var promises = [ promise1, promise2, promise3 ];
+
+  Promise.all(promises).then(function(array){
+    // Code here never runs because there are rejected promises!
+  }, function(error) {
+    // error.message === "2"
+  });
+  ```
+
+  @method all
+  @static
+  @param {Array} entries array of promises
+  @param {String} label optional string for labeling the promise.
+  Useful for tooling.
+  @return {Promise} promise that is fulfilled when all `promises` have been
+  fulfilled, or rejected if any of them become rejected.
+  @static
+*/
+export default function all(entries, label) {
+  return new Enumerator(this, entries, true /* abort on reject */, label).promise;
+}
diff --git a/node_modules/node-firefox-connect/node_modules/es6-promise/lib/es6-promise/promise/race.js b/node_modules/node-firefox-connect/node_modules/es6-promise/lib/es6-promise/promise/race.js
new file mode 100644
index 0000000..7daa28a
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/es6-promise/lib/es6-promise/promise/race.js
@@ -0,0 +1,105 @@
+import {
+  isArray
+} from "../utils";
+
+import {
+  noop,
+  resolve,
+  reject,
+  subscribe,
+  PENDING
+} from '../-internal';
+
+/**
+  `Promise.race` returns a new promise which is settled in the same way as the
+  first passed promise to settle.
+
+  Example:
+
+  ```javascript
+  var promise1 = new Promise(function(resolve, reject){
+    setTimeout(function(){
+      resolve('promise 1');
+    }, 200);
+  });
+
+  var promise2 = new Promise(function(resolve, reject){
+    setTimeout(function(){
+      resolve('promise 2');
+    }, 100);
+  });
+
+  Promise.race([promise1, promise2]).then(function(result){
+    // result === 'promise 2' because it was resolved before promise1
+    // was resolved.
+  });
+  ```
+
+  `Promise.race` is deterministic in that only the state of the first
+  settled promise matters. For example, even if other promises given to the
+  `promises` array argument are resolved, but the first settled promise has
+  become rejected before the other promises became fulfilled, the returned
+  promise will become rejected:
+
+  ```javascript
+  var promise1 = new Promise(function(resolve, reject){
+    setTimeout(function(){
+      resolve('promise 1');
+    }, 200);
+  });
+
+  var promise2 = new Promise(function(resolve, reject){
+    setTimeout(function(){
+      reject(new Error('promise 2'));
+    }, 100);
+  });
+
+  Promise.race([promise1, promise2]).then(function(result){
+    // Code here never runs
+  }, function(reason){
+    // reason.message === 'promise 2' because promise 2 became rejected before
+    // promise 1 became fulfilled
+  });
+  ```
+
+  An example real-world use case is implementing timeouts:
+
+  ```javascript
+  Promise.race([ajax('foo.json'), timeout(5000)])
+  ```
+
+  @method race
+  @static
+  @param {Array} promises array of promises to observe
+  @param {String} label optional string for describing the promise returned.
+  Useful for tooling.
+  @return {Promise} a promise which settles in the same way as the first passed
+  promise to settle.
+*/
+export default function race(entries, label) {
+  /*jshint validthis:true */
+  var Constructor = this;
+
+  var promise = new Constructor(noop, label);
+
+  if (!isArray(entries)) {
+    reject(promise, new TypeError('You must pass an array to race.'));
+    return promise;
+  }
+
+  var length = entries.length;
+
+  function onFulfillment(value) {
+    resolve(promise, value);
+  }
+
+  function onRejection(reason) {
+    reject(promise, reason);
+  }
+
+  for (var i = 0; promise._state === PENDING && i < length; i++) {
+    subscribe(Constructor.resolve(entries[i]), undefined, onFulfillment, onRejection);
+  }
+
+  return promise;
+}
diff --git a/node_modules/node-firefox-connect/node_modules/es6-promise/lib/es6-promise/promise/reject.js b/node_modules/node-firefox-connect/node_modules/es6-promise/lib/es6-promise/promise/reject.js
new file mode 100644
index 0000000..518eff7
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/es6-promise/lib/es6-promise/promise/reject.js
@@ -0,0 +1,47 @@
+import {
+  noop,
+  reject as _reject
+} from '../-internal';
+
+/**
+  `Promise.reject` returns a promise rejected with the passed `reason`.
+  It is shorthand for the following:
+
+  ```javascript
+  var promise = new Promise(function(resolve, reject){
+    reject(new Error('WHOOPS'));
+  });
+
+  promise.then(function(value){
+    // Code here doesn't run because the promise is rejected!
+  }, function(reason){
+    // reason.message === 'WHOOPS'
+  });
+  ```
+
+  Instead of writing the above, your code now simply becomes the following:
+
+  ```javascript
+  var promise = Promise.reject(new Error('WHOOPS'));
+
+  promise.then(function(value){
+    // Code here doesn't run because the promise is rejected!
+  }, function(reason){
+    // reason.message === 'WHOOPS'
+  });
+  ```
+
+  @method reject
+  @static
+  @param {Any} reason value that the returned promise will be rejected with.
+  @param {String} label optional string for identifying the returned promise.
+  Useful for tooling.
+  @return {Promise} a promise rejected with the given `reason`.
+*/
+export default function reject(reason, label) {
+  /*jshint validthis:true */
+  var Constructor = this;
+  var promise = new Constructor(noop, label);
+  _reject(promise, reason);
+  return promise;
+}
diff --git a/node_modules/node-firefox-connect/node_modules/es6-promise/lib/es6-promise/promise/resolve.js b/node_modules/node-firefox-connect/node_modules/es6-promise/lib/es6-promise/promise/resolve.js
new file mode 100644
index 0000000..1b3c533
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/es6-promise/lib/es6-promise/promise/resolve.js
@@ -0,0 +1,49 @@
+import {
+  noop,
+  resolve as _resolve
+} from '../-internal';
+
+/**
+  `Promise.resolve` returns a promise that will become resolved with the
+  passed `value`. It is shorthand for the following:
+
+  ```javascript
+  var promise = new Promise(function(resolve, reject){
+    resolve(1);
+  });
+
+  promise.then(function(value){
+    // value === 1
+  });
+  ```
+
+  Instead of writing the above, your code now simply becomes the following:
+
+  ```javascript
+  var promise = Promise.resolve(1);
+
+  promise.then(function(value){
+    // value === 1
+  });
+  ```
+
+  @method resolve
+  @static
+  @param {Any} value value that the returned promise will be resolved with
+  @param {String} label optional string for identifying the returned promise.
+  Useful for tooling.
+  @return {Promise} a promise that will become fulfilled with the given
+  `value`
+*/
+export default function resolve(object, label) {
+  /*jshint validthis:true */
+  var Constructor = this;
+
+  if (object && typeof object === 'object' && object.constructor === Constructor) {
+    return object;
+  }
+
+  var promise = new Constructor(noop, label);
+  _resolve(promise, object);
+  return promise;
+}
diff --git a/node_modules/node-firefox-connect/node_modules/es6-promise/lib/es6-promise/utils.js b/node_modules/node-firefox-connect/node_modules/es6-promise/lib/es6-promise/utils.js
new file mode 100644
index 0000000..6b49bbf
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/es6-promise/lib/es6-promise/utils.js
@@ -0,0 +1,39 @@
+export function objectOrFunction(x) {
+  return typeof x === 'function' || (typeof x === 'object' && x !== null);
+}
+
+export function isFunction(x) {
+  return typeof x === 'function';
+}
+
+export function isMaybeThenable(x) {
+  return typeof x === 'object' && x !== null;
+}
+
+var _isArray;
+if (!Array.isArray) {
+  _isArray = function (x) {
+    return Object.prototype.toString.call(x) === '[object Array]';
+  };
+} else {
+  _isArray = Array.isArray;
+}
+
+export var isArray = _isArray;
+
+// Date.now is not available in browsers < IE9
+// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now#Compatibility
+export var now = Date.now || function() { return new Date().getTime(); };
+
+function F() { }
+
+export var o_create = (Object.create || function (o) {
+  if (arguments.length > 1) {
+    throw new Error('Second argument not supported');
+  }
+  if (typeof o !== 'object') {
+    throw new TypeError('Argument must be an object');
+  }
+  F.prototype = o;
+  return new F();
+});
diff --git a/node_modules/node-firefox-connect/node_modules/es6-promise/package.json b/node_modules/node-firefox-connect/node_modules/es6-promise/package.json
new file mode 100644
index 0000000..28494ce
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/es6-promise/package.json
@@ -0,0 +1,81 @@
+{
+  "name": "es6-promise",
+  "namespace": "es6-promise",
+  "version": "2.0.1",
+  "description": "A lightweight library that provides tools for organizing asynchronous code",
+  "main": "dist/es6-promise.js",
+  "directories": {
+    "lib": "lib"
+  },
+  "devDependencies": {
+    "bower": "^1.3.9",
+    "brfs": "0.0.8",
+    "broccoli-closure-compiler": "^0.2.0",
+    "broccoli-compile-modules": "git+https://github.com/eventualbuddha/broccoli-compile-modules",
+    "broccoli-concat": "0.0.7",
+    "broccoli-es3-safe-recast": "0.0.8",
+    "broccoli-file-mover": "^0.4.0",
+    "broccoli-jshint": "^0.5.1",
+    "broccoli-merge-trees": "^0.1.4",
+    "broccoli-static-compiler": "^0.1.4",
+    "broccoli-string-replace": "0.0.1",
+    "browserify": "^4.2.0",
+    "ember-cli": "0.0.40",
+    "ember-publisher": "0.0.7",
+    "es6-module-transpiler-amd-formatter": "0.0.1",
+    "express": "^4.5.0",
+    "jshint": "~0.9.1",
+    "mkdirp": "^0.5.0",
+    "mocha": "^1.20.1",
+    "promises-aplus-tests": "git://github.com/stefanpenner/promises-tests.git",
+    "release-it": "0.0.10",
+    "testem": "^0.6.17",
+    "json3": "^3.3.2"
+  },
+  "scripts": {
+    "test": "testem ci -R dot",
+    "test-server": "testem",
+    "lint": "jshint lib",
+    "prepublish": "ember build --environment production",
+    "aplus": "browserify test/main.js",
+    "build-all": "ember build --environment production && browserify ./test/main.js -o tmp/test-bundle.js",
+    "dry-run-release": "ember build --environment production && release-it --dry-run --non-interactive"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/jakearchibald/ES6-Promises.git"
+  },
+  "bugs": {
+    "url": "https://github.com/jakearchibald/ES6-Promises/issues"
+  },
+  "keywords": [
+    "promises",
+    "futures"
+  ],
+  "author": {
+    "name": "Yehuda Katz, Tom Dale, Stefan Penner and contributors",
+    "url": "Conversion to ES6 API by Jake Archibald"
+  },
+  "license": "MIT",
+  "homepage": "https://github.com/jakearchibald/ES6-Promises",
+  "_id": "es6-promise@2.0.1",
+  "dist": {
+    "shasum": "ccc4963e679f0ca9fb187c777b9e583d3c7573c2",
+    "tarball": "http://registry.npmjs.org/es6-promise/-/es6-promise-2.0.1.tgz"
+  },
+  "_from": "es6-promise@^2.0.1",
+  "_npmVersion": "1.3.24",
+  "_npmUser": {
+    "name": "jaffathecake",
+    "email": "jaffathecake@gmail.com"
+  },
+  "maintainers": [
+    {
+      "name": "jaffathecake",
+      "email": "jaffathecake@gmail.com"
+    }
+  ],
+  "_shasum": "ccc4963e679f0ca9fb187c777b9e583d3c7573c2",
+  "_resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-2.0.1.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-connect/node_modules/es6-promise/server/.jshintrc b/node_modules/node-firefox-connect/node_modules/es6-promise/server/.jshintrc
new file mode 100644
index 0000000..c1f2978
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/es6-promise/server/.jshintrc
@@ -0,0 +1,3 @@
+{
+  "node": true
+}
diff --git a/node_modules/node-firefox-connect/node_modules/es6-promise/server/index.js b/node_modules/node-firefox-connect/node_modules/es6-promise/server/index.js
new file mode 100644
index 0000000..8a54d36
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/es6-promise/server/index.js
@@ -0,0 +1,6 @@
+module.exports = function(app) {
+  app.use(require('express').static(__dirname + '/../'));
+  app.get('/', function(req, res) {
+    res.redirect('/test/');
+  })
+};
diff --git a/node_modules/node-firefox-connect/node_modules/es6-promise/testem.json b/node_modules/node-firefox-connect/node_modules/es6-promise/testem.json
new file mode 100644
index 0000000..7494912
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/es6-promise/testem.json
@@ -0,0 +1,11 @@
+{
+  "test_page": "test/index.html",
+  "parallel": 5,
+  "before_tests": "npm run build-all",
+  "launchers": {
+    "Mocha": {
+      "command": "./node_modules/.bin/mocha test/main.js"
+    }
+  },
+  "launch_in_ci":  ["PhantomJS", "Mocha"]
+}
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/README.md b/node_modules/node-firefox-connect/node_modules/firefox-client/README.md
new file mode 100644
index 0000000..c6aefea
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/README.md
@@ -0,0 +1,157 @@
+# firefox-client
+`firefox-client` is a [node](nodejs.org) library for remote debugging Firefox. You can use it to make things like [fxconsole](https://github.com/harthur/fxconsole), a remote JavaScript REPL.
+
+```javascript
+var FirefoxClient = require("firefox-client");
+
+var client = new FirefoxClient();
+
+client.connect(6000, function() {
+  client.listTabs(function(err, tabs) {
+    console.log("first tab:", tabs[0].url);
+  });
+});
+```
+
+## Install
+With [node.js](http://nodejs.org/) npm package manager:
+
+```bash
+npm install firefox-client
+```
+
+## Connecting
+
+### Desktop Firefox
+1. Enable remote debugging (You'll only have to do this once)
+ 1. Open the DevTools. **Web Developer** > **Toggle Tools**
+ 2. Visit the settings panel (gear icon)
+ 3. Check "Enable remote debugging" under Advanced Settings
+
+2. Listen for a connection
+ 1. Open the Firefox command line with **Tools** > **Web Developer** > **Developer Toolbar**.
+ 2. Start a server by entering this command: `listen 6000` (where `6000` is the port number)
+
+### Firefox for Android
+Follow the instructions in [this Hacks video](https://www.youtube.com/watch?v=Znj_8IFeTVs)
+
+### Firefox OS 1.1 Simulator
+A limited set of the API (`Console`, `StyleSheets`) is compatible with the [Simulator 4.0](https://addons.mozilla.org/en-US/firefox/addon/firefox-os-simulator/). See the [wiki instructions](https://github.com/harthur/firefox-client/wiki/Firefox-OS-Simulator-Instructions) for connecting.
+
+`client.listTabs()` will list the currently open apps in the Simulator.
+
+### Firefox OS 1.2+ Simulator and devices
+
+`client.getWebapps()` will expose the webapps in the Simulator, where each app implements the `Tab` API.
+
+```
+client.getWebapps(function(err, webapps) {
+  webapps.getApp("app://homescreen.gaiamobile.org/manifest.webapp", function (err, app) {
+    console.log("homescreen:", actor.url);
+    app.Console.evaluateJS("alert('foo')", function(err, resp) {
+      console.log("alert dismissed");
+    });
+  });
+});
+```
+
+## Compatibility
+
+This latest version of the library will stay compatible with [Firefox Nightly](http://nightly.mozilla.org/). Almost all of it will be compatible with [Firefox Aurora](http://www.mozilla.org/en-US/firefox/aurora/) as well.
+
+## API
+
+A `FirefoxClient` is the entry point to the API. After connecting, get a `Tab` object with `listTabs()` or `selectedTab()`. Once you have a `Tab`, you can call methods and listen to events from the tab's modules, `Console` or `Network`. There are also experimental `DOM` and `StyleSheets` tab modules, and an upcoming `Debugger` module.
+
+#### Methods
+Almost all API calls take a callback that will get called with an error as the first argument (or `null` if there is no error), and a return value as the second:
+
+```javascript
+tab.Console.evaluateJS("6 + 7", function(err, resp) {
+  if (err) throw err;
+
+  console.log(resp.result);
+});
+```
+
+#### Events
+
+The modules are `EventEmitter`s, listen for events with `on` or `once`, and stop listening with `off`:
+
+```javascript
+tab.Console.on("page-error", function(event) {
+  console.log("new error from tab:", event.errorMessage);
+});
+```
+
+Summary of the offerings of the modules and objects:
+
+#### [FirefoxClient](http://github.com/harthur/firefox-client/wiki/FirefoxClient)
+Methods: `connect()`, `disconnect()`, `listTabs()`, `selectedTab()`, `getWebapps()`, `getRoot()`
+
+Events: `"error"`, `"timeout"`, `"end"`
+
+#### [Tab](https://github.com/harthur/firefox-client/wiki/Tab)
+Properties: `url`, `title`
+
+Methods: `reload()`, `navigateTo()`, `attach()`, `detach()`
+
+Events: `"navigate"`, `"before-navigate"`
+
+#### [Tab.Console](https://github.com/harthur/firefox-client/wiki/Console)
+Methods: `evaluateJS()`, `startListening()`, `stopListening()`, `getCachedLogs()`
+
+Events: `"page-error"`, `"console-api-call"`
+
+#### [JSObject](https://github.com/harthur/firefox-client/wiki/JSObject)
+Properties: `class`, `name`, `displayName`
+
+Methods: `ownPropertyNames()`, `ownPropertyDescriptor()`, `ownProperties()`, `prototype()`
+
+#### [Tab.Network](https://github.com/harthur/firefox-client/wiki/Network)
+Methods: `startLogging()`, `stopLogging()`, `sendHTTPRequest()`
+
+Events: `"network-event"`
+
+#### [NetworkEvent](https://github.com/harthur/firefox-client/wiki/NetworkEvent)
+Properties: `url`, `method`, `isXHR`
+
+Methods: `getRequestHeaders()`, `getRequestCookies()`, `getRequestPostData()`, `getResponseHeaders()`, `getResponseCookies()`, `getResponseContent()`, `getEventTimings()`
+
+Events: `"request-headers"`, `"request-cookies"`, `"request-postdata"`, `"response-start"`, `"response-headers"`, `"response-cookies"`, `"event-timings"`
+
+#### [Tab.DOM](https://github.com/harthur/firefox-client/wiki/DOM)
+Methods: `document()`, `documentElement()`, `querySelector()`, `querySelectorAll()`
+
+#### [DOMNode](https://github.com/harthur/firefox-client/wiki/DOMNode)
+Properties: `nodeValue`, `nodeName`, `namespaceURI`
+
+Methods: `parentNode()`, `parents()`, `siblings()`, `nextSibling()`, `previousSibling()`, `querySelector()`, `querySelectorAll()`, `innerHTML()`, `outerHTML()`, `setAttribute()`, `remove()`, `release()`
+
+#### [Tab.StyleSheets](https://github.com/harthur/firefox-client/wiki/StyleSheets)
+Methods: `getStyleSheets()`, `addStyleSheet()`
+
+#### [StyleSheet](https://github.com/harthur/firefox-client/wiki/StyleSheet)
+Properties: `href`, `disabled`, `ruleCount`
+
+Methods: `getText()`, `update()`, `toggleDisabled()`, `getOriginalSources()`
+
+Events: `"disabled-changed"`, `"ruleCount-changed"`
+
+#### Tab.Memory
+Methods: `measure()`
+
+#### Webapps
+Methods: `listRunningApps()`, `getInstalledApps()`, `watchApps()`, `unwatchApps()`, `launch()`, `close()`, `getApp()`, `installHosted()`, `installPackaged()`, `installPackagedWithADB()`, `uninstall()`
+
+Events: `"appOpen"`, `"appClose"`, `"appInstall"`, `"appUninstall"`
+
+## Examples
+
+[fxconsole](https://github.com/harthur/fxconsole) - a remote JavaScript console for Firefox
+
+[webapps test script](https://pastebin.mozilla.org/5094843) - a sample usage of all webapps features
+
+## Feedback
+
+What do you need from the API? [File an issue](https://github.com/harthur/firefox-client/issues/new).
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/index.js b/node_modules/node-firefox-connect/node_modules/firefox-client/index.js
new file mode 100644
index 0000000..cbf7a39
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/index.js
@@ -0,0 +1 @@
+module.exports = require("./lib/browser");
\ No newline at end of file
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/lib/browser.js b/node_modules/node-firefox-connect/node_modules/firefox-client/lib/browser.js
new file mode 100644
index 0000000..2e52220
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/lib/browser.js
@@ -0,0 +1,123 @@
+var extend = require("./extend"),
+    ClientMethods = require("./client-methods"),
+    Client = require("./client"),
+    Tab = require("./tab"),
+    Webapps = require("./webapps"),
+    Device = require("./device"),
+    SimulatorApps = require("./simulator");
+
+const DEFAULT_PORT = 6000;
+const DEFAULT_HOST = "localhost";
+
+module.exports = FirefoxClient;
+
+function FirefoxClient(options) {
+  var client = new Client(options);
+  var actor = 'root';
+
+  client.on("error", this.onError.bind(this));
+  client.on("end", this.onEnd.bind(this));
+  client.on("timeout", this.onTimeout.bind(this));
+
+  this.initialize(client, actor);
+}
+
+FirefoxClient.prototype = extend(ClientMethods, {
+  connect: function(port, host, cb) {
+    if (typeof port == "function") {
+      // (cb)
+      cb = port;
+      port = DEFAULT_PORT;
+      host = DEFAULT_HOST;
+
+    }
+    if (typeof host == "function") {
+      // (port, cb)
+      cb = host;
+      host = DEFAULT_HOST;
+    }
+    // (port, host, cb)
+
+    this.client.connect(port, host, cb);
+
+    this.client.expectReply(this.actor, function(packet) {
+      // root message
+    });
+  },
+
+  disconnect: function() {
+    this.client.disconnect();
+  },
+
+  onError: function(error) {
+    this.emit("error", error);
+  },
+
+  onEnd: function() {
+    this.emit("end");
+  },
+
+  onTimeout: function() {
+    this.emit("timeout");
+  },
+
+  selectedTab: function(cb) {
+    this.request("listTabs", function(resp) {
+      var tab = resp.tabs[resp.selected];
+      return new Tab(this.client, tab);
+    }.bind(this), cb);
+  },
+
+  listTabs: function(cb) {
+    this.request("listTabs", function(err, resp) {
+      if (err) {
+        return cb(err);
+      }
+
+      if (resp.simulatorWebappsActor) {
+        // the server is the Firefox OS Simulator, return apps as "tabs"
+        var apps = new SimulatorApps(this.client, resp.simulatorWebappsActor);
+        apps.listApps(cb);
+      }
+      else {
+        var tabs = resp.tabs.map(function(tab) {
+          return new Tab(this.client, tab);
+        }.bind(this));
+        cb(null, tabs);
+      }
+    }.bind(this));
+  },
+
+  getWebapps: function(cb) {
+    this.request("listTabs", (function(err, resp) {
+      if (err) {
+        return cb(err);
+      }
+      var webapps = new Webapps(this.client, resp);
+      cb(null, webapps);
+    }).bind(this));
+  },
+
+  getDevice: function(cb) {
+    this.request("listTabs", (function(err, resp) {
+      if (err) {
+        return cb(err);
+      }
+      var device = new Device(this.client, resp);
+      cb(null, device);
+    }).bind(this));
+  },
+
+  getRoot: function(cb) {
+    this.request("listTabs", (function(err, resp) {
+      if (err) {
+        return cb(err);
+      }
+      if (!resp.consoleActor) {
+        return cb("No root actor being available.");
+      }
+      var root = new Tab(this.client, resp);
+      cb(null, root);
+    }).bind(this));
+  }
+})
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/lib/client-methods.js b/node_modules/node-firefox-connect/node_modules/firefox-client/lib/client-methods.js
new file mode 100644
index 0000000..c92c44c
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/lib/client-methods.js
@@ -0,0 +1,115 @@
+var events = require("events"),
+    extend = require("./extend");
+
+// to be instantiated later - to avoid circular dep resolution
+var JSObject;
+
+var ClientMethods = extend(events.EventEmitter.prototype, {
+  /**
+   * Intialize this client object.
+   *
+   * @param  {object} client
+   *         Client to send requests on.
+   * @param  {string} actor
+   *         Actor id to set as 'from' field on requests
+   */
+  initialize: function(client, actor) {
+    this.client = client;
+    this.actor = actor;
+
+    this.client.on('message', function(message) {
+      if (message.from == this.actor) {
+        this.emit(message.type, message);
+      }
+    }.bind(this));
+  },
+
+  /**
+   * Make request to our actor on the server.
+   *
+   * @param  {string}   type
+   *         Method name of the request
+   * @param  {object}   message
+   *         Optional extra properties (arguments to method)
+   * @param  {Function}   transform
+   *         Optional tranform for response object. Takes response object
+   *         and returns object to send on.
+   * @param  {Function} callback
+   *         Callback to call with (maybe transformed) response
+   */
+  request: function(type, message, transform, callback) {
+    if (typeof message == "function") {
+      if (typeof transform == "function") {
+        // (type, trans, cb)
+        callback = transform;
+        transform = message;
+      }
+      else {
+        // (type, cb)
+        callback = message;
+      }
+      message = {};
+    }
+    else if (!callback) {
+      if (!message) {
+        // (type)
+        message = {};
+      }
+      // (type, message, cb)
+      callback = transform;
+      transform = null;
+    }
+
+    message.to = this.actor;
+    message.type = type;
+
+    this.client.makeRequest(message, function(resp) {
+      delete resp.from;
+
+      if (resp.error) {
+        var err = new Error(resp.message);
+        err.name = resp.error;
+
+        callback(err);
+        return;
+      }
+
+      if (transform) {
+        resp = transform(resp);
+      }
+
+      if (callback) {
+        callback(null, resp);
+      }
+    });
+  },
+
+  /*
+   * Transform obj response into a JSObject
+   */
+  createJSObject: function(obj) {
+    if (obj == null) {
+      return;
+    }
+    if (!JSObject) {
+      // circular dependencies
+      JSObject = require("./jsobject");
+    }
+    if (obj.type == "object") {
+      return new JSObject(this.client, obj);
+    }
+    return obj;
+  },
+
+  /**
+   * Create function that plucks out only one value from an object.
+   * Used as the transform function for some responses.
+   */
+  pluck: function(prop) {
+    return function(obj) {
+      return obj[prop];
+    }
+  }
+})
+
+module.exports = ClientMethods;
\ No newline at end of file
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/lib/client.js b/node_modules/node-firefox-connect/node_modules/firefox-client/lib/client.js
new file mode 100644
index 0000000..22b3179
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/lib/client.js
@@ -0,0 +1,246 @@
+var net = require("net"),
+    events = require("events"),
+    extend = require("./extend");
+
+var colors = require("colors");
+
+module.exports = Client;
+
+// this is very unfortunate! and temporary. we can't
+// rely on 'type' property to signify an event, and we
+// need to write clients for each actor to handle differences
+// in actor protocols
+var unsolicitedEvents = {
+  "tabNavigated": "tabNavigated",
+  "styleApplied": "styleApplied",
+  "propertyChange": "propertyChange",
+  "networkEventUpdate": "networkEventUpdate",
+  "networkEvent": "networkEvent",
+  "propertyChange": "propertyChange",
+  "newMutations": "newMutations",
+  "appOpen": "appOpen",
+  "appClose": "appClose",
+  "appInstall": "appInstall",
+  "appUninstall": "appUninstall",
+  "frameUpdate": "frameUpdate"
+};
+
+/**
+ * a Client object handles connecting with a Firefox remote debugging
+ * server instance (e.g. a Firefox instance), plus sending and receiving
+ * packets on that conection using the Firefox remote debugging protocol.
+ *
+ * Important methods:
+ * connect - Create the connection to the server.
+ * makeRequest - Make a request to the server with a JSON message,
+ *   and a callback to call with the response.
+ *
+ * Important events:
+ * 'message' - An unsolicited (e.g. not a response to a prior request)
+ *    packet has been received. These packets usually describe events.
+ */
+function Client(options) {
+  this.options = options || {};
+
+  this.incoming = new Buffer("");
+
+  this._pendingRequests = [];
+  this._activeRequests = {};
+}
+
+Client.prototype = extend(events.EventEmitter.prototype, {
+  connect: function(port, host, cb) {
+    this.client = net.createConnection({
+      port: port,
+      host: host
+    });
+
+    this.client.on("connect", cb);
+    this.client.on("data", this.onData.bind(this));
+    this.client.on("error", this.onError.bind(this));
+    this.client.on("end", this.onEnd.bind(this));
+    this.client.on("timeout", this.onTimeout.bind(this));
+  },
+
+  disconnect: function() {
+    if (this.client) {
+      this.client.end();
+    }
+  },
+
+  /**
+   * Set a request to be sent to an actor on the server. If the actor
+   * is already handling a request, queue this request until the actor
+   * has responded to the previous request.
+   *
+   * @param {object} request
+   *        Message to be JSON-ified and sent to server.
+   * @param {function} callback
+   *        Function that's called with the response from the server.
+   */
+  makeRequest: function(request, callback) {
+    this.log("request: " + JSON.stringify(request).green);
+
+    if (!request.to) {
+      var type = request.type || "";
+      throw new Error(type + " request packet has no destination.");
+    }
+    this._pendingRequests.push({ to: request.to,
+                                 message: request,
+                                 callback: callback });
+    this._flushRequests();
+  },
+
+  /**
+   * Activate (send) any pending requests to actors that don't have an
+   * active request.
+   */
+  _flushRequests: function() {
+    this._pendingRequests = this._pendingRequests.filter(function(request) {
+      // only one active request per actor at a time
+      if (this._activeRequests[request.to]) {
+        return true;
+      }
+
+      // no active requests for this actor, so activate this one
+      this.sendMessage(request.message);
+      this.expectReply(request.to, request.callback);
+
+      // remove from pending requests
+      return false;
+    }.bind(this));
+  },
+
+  /**
+   * Send a JSON message over the connection to the server.
+   */
+  sendMessage: function(message) {
+    if (!message.to) {
+      throw new Error("No actor specified in request");
+    }
+    if (!this.client) {
+      throw new Error("Not connected, connect() before sending requests");
+    }
+    var str = JSON.stringify(message);
+
+    // message is preceded by byteLength(message):
+    str = (new Buffer(str).length) + ":" + str;
+
+    this.client.write(str);
+  },
+
+  /**
+   * Arrange to hand the next reply from |actor| to |handler|.
+   */
+  expectReply: function(actor, handler) {
+    if (this._activeRequests[actor]) {
+      throw Error("clashing handlers for next reply from " + uneval(actor));
+    }
+    this._activeRequests[actor] = handler;
+  },
+
+  /**
+   * Handler for a new message coming in. It's either an unsolicited event
+   * from the server, or a response to a previous request from the client.
+   */
+  handleMessage: function(message) {
+    if (!message.from) {
+      if (message.error) {
+        throw new Error(message.message);
+      }
+      throw new Error("Server didn't specify an actor: " + JSON.stringify(message));
+    }
+
+    if (!(message.type in unsolicitedEvents)
+        && this._activeRequests[message.from]) {
+      this.log("response: " + JSON.stringify(message).yellow);
+
+      var callback = this._activeRequests[message.from];
+      delete this._activeRequests[message.from];
+
+      callback(message);
+
+      this._flushRequests();
+    }
+    else if (message.type) {
+      // this is an unsolicited event from the server
+      this.log("unsolicited event: ".grey + JSON.stringify(message).grey);
+
+      this.emit('message', message);
+      return;
+    }
+    else {
+      throw new Error("Unexpected packet from actor " +  message.from
+      +  JSON.stringify(message));
+    }
+  },
+
+  /**
+   * Called when a new data chunk is received on the connection.
+   * Parse data into message(s) and call message handler for any full
+   * messages that are read in.
+   */
+  onData: function(data) {
+    this.incoming = Buffer.concat([this.incoming, data]);
+
+    while(this.readMessage()) {};
+  },
+
+  /**
+   * Parse out and process the next message from the data read from
+   * the connection. Returns true if a full meassage was parsed, false
+   * otherwise.
+   */
+  readMessage: function() {
+    var sep = this.incoming.toString().indexOf(':');
+    if (sep < 0) {
+      return false;
+    }
+
+    // beginning of a message is preceded by byteLength(message) + ":"
+    var count = parseInt(this.incoming.slice(0, sep));
+
+    if (this.incoming.length - (sep + 1) < count) {
+      this.log("no complete response yet".grey);
+      return false;
+    }
+    this.incoming = this.incoming.slice(sep + 1);
+
+    var packet = this.incoming.slice(0, count);
+
+    this.incoming = this.incoming.slice(count);
+
+    var message;
+    try {
+      message = JSON.parse(packet.toString());
+    } catch(e) {
+      throw new Error("Couldn't parse packet from server as JSON " + e
+        + ", message:\n" + packet);
+    }
+    this.handleMessage(message);
+
+    return true;
+  },
+
+  onError: function(error) {
+    var code = error.code ? error.code : error;
+    this.log("connection error: ".red + code.red);
+    this.emit("error", error);
+  },
+
+  onEnd: function() {
+    this.log("connection closed by server".red);
+    this.emit("end");
+  },
+
+  onTimeout: function() {
+    this.log("connection timeout".red);
+    this.emit("timeout");
+  },
+
+  log: function(str) {
+    if (this.options.log) {
+      console.log(str);
+    }
+  }
+})
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/lib/console.js b/node_modules/node-firefox-connect/node_modules/firefox-client/lib/console.js
new file mode 100644
index 0000000..3f3dcff
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/lib/console.js
@@ -0,0 +1,107 @@
+var select = require("js-select"),
+    extend = require("./extend"),
+    ClientMethods = require("./client-methods"),
+    JSObject = require("./jsobject");
+
+module.exports = Console;
+
+function Console(client, actor) {
+  this.initialize(client, actor);
+
+  this.on("consoleAPICall", this.onConsoleAPI.bind(this));
+  this.on("pageError", this.onPageError.bind(this));
+}
+
+Console.prototype = extend(ClientMethods, {
+  types: ["PageError", "ConsoleAPI"],
+
+  /**
+   * Response object:
+   *   -empty-
+   */
+  startListening: function(cb) {
+    this.request('startListeners', { listeners: this.types }, cb);
+  },
+
+  /**
+   * Response object:
+   *   -empty-
+   */
+  stopListening: function(cb) {
+    this.request('stopListeners', { listeners: this.types }, cb);
+  },
+
+  /**
+   * Event object:
+   *   level - "log", etc.
+   *   filename - file with call
+   *   lineNumber - line number of call
+   *   functionName - function log called from
+   *   timeStamp - ms timestamp of call
+   *   arguments - array of the arguments to log call
+   *   private -
+   */
+  onConsoleAPI: function(event) {
+    var message = this.transformConsoleCall(event.message);
+
+    this.emit("console-api-call", message);
+  },
+
+  /**
+   * Event object:
+   *   errorMessage - string error message
+   *   sourceName - file error
+   *   lineText
+   *   lineNumber - line number of error
+   *   columnNumber - column number of error
+   *   category - usually "content javascript",
+   *   timeStamp - time in ms of error occurance
+   *   warning - whether it's a warning
+   *   error - whether it's an error
+   *   exception - whether it's an exception
+   *   strict -
+   *   private -
+   */
+  onPageError: function(event) {
+    this.emit("page-error", event.pageError);
+  },
+
+  /**
+   * Response object: array of page error or console call objects.
+   */
+  getCachedLogs: function(cb) {
+    var message = {
+      messageTypes: this.types
+    };
+    this.request('getCachedMessages', message, function(resp) {
+      select(resp, ".messages > *").update(this.transformConsoleCall.bind(this));
+      return resp.messages;
+    }.bind(this), cb);
+  },
+
+  /**
+   * Response object:
+   *   -empty-
+   */
+  clearCachedLogs: function(cb) {
+    this.request('clearMessagesCache', cb);
+  },
+
+  /**
+   * Response object:
+   *   input - original input
+   *   result - result of the evaluation, a value or JSObject
+   *   timestamp - timestamp in ms of the evaluation
+   *   exception - any exception as a result of the evaluation
+   */
+  evaluateJS: function(text, cb) {
+    this.request('evaluateJS', { text: text }, function(resp) {
+      return select(resp, ".result, .exception")
+             .update(this.createJSObject.bind(this));
+    }.bind(this), cb)
+  },
+
+  transformConsoleCall: function(message) {
+    return select(message, ".arguments > *").update(this.createJSObject.bind(this));
+  }
+})
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/lib/device.js b/node_modules/node-firefox-connect/node_modules/firefox-client/lib/device.js
new file mode 100644
index 0000000..6c5da84
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/lib/device.js
@@ -0,0 +1,29 @@
+var extend = require("./extend"),
+    ClientMethods = require("./client-methods");
+
+module.exports = Device;
+
+function Device(client, tab) {
+  this.initialize(client, tab.deviceActor);
+}
+
+Device.prototype = extend(ClientMethods, {
+  getDescription: function(cb) {
+    this.request("getDescription", function(err, resp) {
+      if (err) {
+        return cb(err);
+      }
+
+      cb(null, resp.value);
+    });
+  },
+  getRawPermissionsTable: function(cb) {
+    this.request("getRawPermissionsTable", function(err, resp) {
+      if (err) {
+        return cb(err);
+      }
+
+      cb(null, resp.value.rawPermissionsTable);
+    });
+  }
+})
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/lib/dom.js b/node_modules/node-firefox-connect/node_modules/firefox-client/lib/dom.js
new file mode 100644
index 0000000..a00018c
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/lib/dom.js
@@ -0,0 +1,68 @@
+var extend = require("./extend"),
+    ClientMethods = require("./client-methods"),
+    Node = require("./domnode");
+
+module.exports = DOM;
+
+function DOM(client, actor) {
+  this.initialize(client, actor);
+  this.walker = null;
+}
+
+DOM.prototype = extend(ClientMethods, {
+  document: function(cb) {
+    this.walkerRequest("document", function(err, resp) {
+      if (err) return cb(err);
+
+      var node = new Node(this.client, this.walker, resp.node);
+      cb(null, node);
+    }.bind(this))
+  },
+
+  documentElement: function(cb) {
+    this.walkerRequest("documentElement", function(err, resp) {
+      var node = new Node(this.client, this.walker, resp.node);
+      cb(err, node);
+    }.bind(this))
+  },
+
+  querySelector: function(selector, cb) {
+    this.document(function(err, node) {
+      if (err) return cb(err);
+
+      node.querySelector(selector, cb);
+    })
+  },
+
+  querySelectorAll: function(selector, cb) {
+    this.document(function(err, node) {
+      if (err) return cb(err);
+
+      node.querySelectorAll(selector, cb);
+    })
+  },
+
+  walkerRequest: function(type, message, cb) {
+    this.getWalker(function(err, walker) {
+      walker.request(type, message, cb);
+    });
+  },
+
+  getWalker: function(cb) {
+    if (this.walker) {
+      return cb(null, this.walker);
+    }
+    this.request('getWalker', function(err, resp) {
+      this.walker = new Walker(this.client, resp.walker);
+      cb(err, this.walker);
+    }.bind(this))
+  }
+})
+
+function Walker(client, walker) {
+  this.initialize(client, walker.actor);
+
+  this.root = new Node(client, this, walker.root);
+}
+
+Walker.prototype = extend(ClientMethods, {});
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/lib/domnode.js b/node_modules/node-firefox-connect/node_modules/firefox-client/lib/domnode.js
new file mode 100644
index 0000000..e0a55b8
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/lib/domnode.js
@@ -0,0 +1,169 @@
+var extend = require("./extend"),
+    ClientMethods = require("./client-methods");
+
+module.exports = Node;
+
+function Node(client, walker, node) {
+  this.initialize(client, node.actor);
+  this.walker = walker;
+
+  this.getNode = this.getNode.bind(this);
+  this.getNodeArray = this.getNodeArray.bind(this);
+  this.getNodeList = this.getNodeList.bind(this);
+
+  walker.on('newMutations', function(event) {
+    //console.log("on new mutations! ", JSON.stringify(event));
+  });
+
+  ['nodeType', 'nodeName', 'namespaceURI', 'attrs']
+  .forEach(function(attr) {
+    this[attr] = node[attr];
+  }.bind(this));
+}
+
+Node.prototype = extend(ClientMethods, {
+  getAttribute: function(name) {
+    for (var i in this.attrs) {
+      var attr = this.attrs[i];
+      if (attr.name == name) {
+        return attr.value;
+      }
+    }
+  },
+
+  setAttribute: function(name, value, cb) {
+    var mods = [{
+      attributeName: name,
+      newValue: value
+    }];
+    this.request('modifyAttributes', { modifications: mods }, cb);
+  },
+
+  parentNode: function(cb) {
+    this.parents(function(err, nodes) {
+      if (err) {
+        return cb(err);
+      }
+      var node = null;
+      if (nodes.length) {
+        node = nodes[0];
+      }
+      cb(null, node);
+    })
+  },
+
+  parents: function(cb) {
+    this.nodeRequest('parents', this.getNodeArray, cb);
+  },
+
+  children: function(cb) {
+    this.nodeRequest('children', this.getNodeArray, cb);
+  },
+
+  siblings: function(cb) {
+    this.nodeRequest('siblings', this.getNodeArray, cb);
+  },
+
+  nextSibling: function(cb) {
+    this.nodeRequest('nextSibling', this.getNode, cb);
+  },
+
+  previousSibling: function(cb) {
+    this.nodeRequest('previousSibling', this.getNode, cb);
+  },
+
+  querySelector: function(selector, cb) {
+    this.nodeRequest('querySelector', { selector: selector },
+                     this.getNode, cb);
+  },
+
+  querySelectorAll: function(selector, cb) {
+    this.nodeRequest('querySelectorAll', { selector: selector },
+                     this.getNodeList, cb);
+  },
+
+  innerHTML: function(cb) {
+    this.nodeRequest('innerHTML', function(resp) {
+      return resp.value;
+    }, cb)
+  },
+
+  outerHTML: function(cb) {
+    this.nodeRequest('outerHTML', function(resp) {
+      return resp.value;
+    }, cb)
+  },
+
+  remove: function(cb) {
+    this.nodeRequest('removeNode', function(resp) {
+      return new Node(this.client, this.walker, resp.nextSibling);
+    }.bind(this), cb);
+  },
+
+  highlight: function(cb) {
+    this.nodeRequest('highlight', cb);
+  },
+
+  release: function(cb) {
+    this.nodeRequest('releaseNode', cb);
+  },
+
+  getNode: function(resp) {
+    if (resp.node) {
+      return new Node(this.client, this.walker, resp.node);
+    }
+    return null;
+  },
+
+  getNodeArray: function(resp) {
+    return resp.nodes.map(function(form) {
+      return new Node(this.client, this.walker, form);
+    }.bind(this));
+  },
+
+  getNodeList: function(resp) {
+    return new NodeList(this.client, this.walker, resp.list);
+  },
+
+  nodeRequest: function(type, message, transform, cb) {
+    if (!cb) {
+      cb = transform;
+      transform = message;
+      message = {};
+    }
+    message.node = this.actor;
+
+    this.walker.request(type, message, transform, cb);
+  }
+});
+
+function NodeList(client, walker, list) {
+  this.client = client;
+  this.walker = walker;
+  this.actor = list.actor;
+
+  this.length = list.length;
+}
+
+NodeList.prototype = extend(ClientMethods, {
+  items: function(start, end, cb) {
+    if (typeof start == "function") {
+      cb = start;
+      start = 0;
+      end = this.length;
+    }
+    else if (typeof end == "function") {
+      cb = end;
+      end = this.length;
+    }
+    this.request('items', { start: start, end: end },
+      this.getNodeArray.bind(this), cb);
+  },
+
+  // TODO: add this function to ClientMethods
+  getNodeArray: function(resp) {
+    return resp.nodes.map(function(form) {
+      return new Node(this.client, this.walker, form);
+    }.bind(this));
+  }
+});
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/lib/extend.js b/node_modules/node-firefox-connect/node_modules/firefox-client/lib/extend.js
new file mode 100644
index 0000000..8624103
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/lib/extend.js
@@ -0,0 +1,12 @@
+module.exports = function extend(prototype, properties) {
+  return Object.create(prototype, getOwnPropertyDescriptors(properties));
+}
+
+function getOwnPropertyDescriptors(object) {
+  var names = Object.getOwnPropertyNames(object);
+
+  return names.reduce(function(descriptor, name) {
+    descriptor[name] = Object.getOwnPropertyDescriptor(object, name);
+    return descriptor;
+  }, {});
+}
\ No newline at end of file
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/lib/jsobject.js b/node_modules/node-firefox-connect/node_modules/firefox-client/lib/jsobject.js
new file mode 100644
index 0000000..907999f
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/lib/jsobject.js
@@ -0,0 +1,91 @@
+var select = require("js-select"),
+    extend = require("./extend"),
+    ClientMethods = require("./client-methods");
+
+module.exports = JSObject;
+
+function JSObject(client, obj) {
+  this.initialize(client, obj.actor);
+  this.obj = obj;
+}
+
+JSObject.prototype = extend(ClientMethods, {
+  type: "object",
+
+  get class() {
+    return this.obj.class;
+  },
+
+  get name() {
+    return this.obj.name;
+  },
+
+  get displayName() {
+    return this.obj.displayName;
+  },
+
+  ownPropertyNames: function(cb) {
+    this.request('ownPropertyNames', function(resp) {
+      return resp.ownPropertyNames;
+    }, cb);
+  },
+
+  ownPropertyDescriptor: function(name, cb) {
+    this.request('property', { name: name }, function(resp) {
+      return this.transformDescriptor(resp.descriptor);
+    }.bind(this), cb);
+  },
+
+  ownProperties: function(cb) {
+    this.request('prototypeAndProperties', function(resp) {
+      return this.transformProperties(resp.ownProperties);
+    }.bind(this), cb);
+  },
+
+  prototype: function(cb) {
+    this.request('prototype', function(resp) {
+      return this.createJSObject(resp.prototype);
+    }.bind(this), cb);
+  },
+
+  ownPropertiesAndPrototype: function(cb) {
+    this.request('prototypeAndProperties', function(resp) {
+      resp.ownProperties = this.transformProperties(resp.ownProperties);
+      resp.safeGetterValues = this.transformGetters(resp.safeGetterValues);
+      resp.prototype = this.createJSObject(resp.prototype);
+
+      return resp;
+    }.bind(this), cb);
+  },
+
+  /* helpers */
+  transformProperties: function(props) {
+    var transformed = {};
+    for (var prop in props) {
+      transformed[prop] = this.transformDescriptor(props[prop]);
+    }
+    return transformed;
+  },
+
+  transformGetters: function(getters) {
+    var transformed = {};
+    for (var prop in getters) {
+      transformed[prop] = this.transformGetter(getters[prop]);
+    }
+    return transformed;
+  },
+
+  transformDescriptor: function(descriptor) {
+    descriptor.value = this.createJSObject(descriptor.value);
+    return descriptor;
+  },
+
+  transformGetter: function(getter) {
+    return {
+      value: this.createJSObject(getter.getterValue),
+      prototypeLevel: getter.getterPrototypeLevel,
+      enumerable: getter.enumerable,
+      writable: getter.writable
+    }
+  }
+})
\ No newline at end of file
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/lib/memory.js b/node_modules/node-firefox-connect/node_modules/firefox-client/lib/memory.js
new file mode 100644
index 0000000..8468539
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/lib/memory.js
@@ -0,0 +1,16 @@
+var extend = require("./extend"),
+    ClientMethods = require("./client-methods");
+
+module.exports = Memory;
+
+function Memory(client, actor) {
+  this.initialize(client, actor);
+}
+
+Memory.prototype = extend(ClientMethods, {
+  measure: function(cb) {
+    this.request('measure', function (err, resp) {
+      cb(err, resp);
+    });
+  }
+})
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/lib/network.js b/node_modules/node-firefox-connect/node_modules/firefox-client/lib/network.js
new file mode 100644
index 0000000..5c1afdb
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/lib/network.js
@@ -0,0 +1,105 @@
+var extend = require("./extend");
+var ClientMethods = require("./client-methods");
+
+module.exports = Network;
+
+function Network(client, actor) {
+  this.initialize(client, actor);
+
+  this.on("networkEvent", this.onNetworkEvent.bind(this));
+}
+
+Network.prototype = extend(ClientMethods, {
+  types: ["NetworkActivity"],
+
+  startLogging: function(cb) {
+    this.request('startListeners', { listeners: this.types }, cb);
+  },
+
+  stopLogging: function(cb) {
+    this.request('stopListeners', { listeners: this.types }, cb);
+  },
+
+  onNetworkEvent: function(event) {
+    var networkEvent = new NetworkEvent(this.client, event.eventActor);
+
+    this.emit("network-event", networkEvent);
+  },
+
+  sendHTTPRequest: function(request, cb) {
+    this.request('sendHTTPRequest', { request: request }, function(resp) {
+      return new NetworkEvent(this.client, resp.eventActor);
+    }.bind(this), cb);
+  }
+})
+
+function NetworkEvent(client, event) {
+  this.initialize(client, event.actor);
+  this.event = event;
+
+  this.on("networkEventUpdate", this.onUpdate.bind(this));
+}
+
+NetworkEvent.prototype = extend(ClientMethods, {
+  get url() {
+   return this.event.url;
+  },
+
+  get method() {
+    return this.event.method;
+  },
+
+  get isXHR() {
+    return this.event.isXHR;
+  },
+
+  getRequestHeaders: function(cb) {
+    this.request('getRequestHeaders', cb);
+  },
+
+  getRequestCookies: function(cb) {
+    this.request('getRequestCookies', this.pluck('cookies'), cb);
+  },
+
+  getRequestPostData: function(cb) {
+    this.request('getRequestPostData', cb);
+  },
+
+  getResponseHeaders: function(cb) {
+    this.request('getResponseHeaders', cb);
+  },
+
+  getResponseCookies: function(cb) {
+    this.request('getResponseCookies', this.pluck('cookies'), cb);
+  },
+
+  getResponseContent: function(cb) {
+    this.request('getResponseContent', cb);
+  },
+
+  getEventTimings: function(cb) {
+    this.request('getEventTimings', cb);
+  },
+
+  onUpdate: function(event) {
+    var types = {
+      "requestHeaders": "request-headers",
+      "requestCookies": "request-cookies",
+      "requestPostData": "request-postdata",
+      "responseStart": "response-start",
+      "responseHeaders": "response-headers",
+      "responseCookies": "response-cookies",
+      "responseContent": "response-content",
+      "eventTimings": "event-timings"
+    }
+
+    var type = types[event.updateType];
+    delete event.updateType;
+
+    this.emit(type, event);
+  }
+})
+
+
+
+
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/lib/simulator.js b/node_modules/node-firefox-connect/node_modules/firefox-client/lib/simulator.js
new file mode 100644
index 0000000..4df6a2b
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/lib/simulator.js
@@ -0,0 +1,22 @@
+var extend = require("./extend"),
+    ClientMethods = require("./client-methods"),
+    Tab = require("./tab");
+
+module.exports = SimulatorApps;
+
+function SimulatorApps(client, actor) {
+  this.initialize(client, actor);
+}
+
+SimulatorApps.prototype = extend(ClientMethods, {
+  listApps: function(cb) {
+    this.request('listApps', function(resp) {
+      var apps = [];
+      for (var url in resp.apps) {
+        var app = resp.apps[url];
+        apps.push(new Tab(this.client, app));
+      }
+      return apps;
+    }.bind(this), cb);
+  }
+})
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/lib/stylesheets.js b/node_modules/node-firefox-connect/node_modules/firefox-client/lib/stylesheets.js
new file mode 100644
index 0000000..0677c02
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/lib/stylesheets.js
@@ -0,0 +1,96 @@
+var extend = require("./extend");
+var ClientMethods = require("./client-methods");
+
+module.exports = StyleSheets;
+
+function StyleSheets(client, actor) {
+  this.initialize(client, actor);
+}
+
+StyleSheets.prototype = extend(ClientMethods, {
+  getStyleSheets: function(cb) {
+    this.request('getStyleSheets', function(resp) {
+      return resp.styleSheets.map(function(sheet) {
+        return new StyleSheet(this.client, sheet);
+      }.bind(this));
+    }.bind(this), cb);
+  },
+
+  addStyleSheet: function(text, cb) {
+    this.request('addStyleSheet', { text: text }, function(resp) {
+      return new StyleSheet(this.client, resp.styleSheet);
+    }.bind(this), cb);
+  }
+})
+
+function StyleSheet(client, sheet) {
+  this.initialize(client, sheet.actor);
+  this.sheet = sheet;
+
+  this.on("propertyChange", this.onPropertyChange.bind(this));
+}
+
+StyleSheet.prototype = extend(ClientMethods, {
+  get href() {
+    return this.sheet.href;
+  },
+
+  get disabled() {
+    return this.sheet.disabled;
+  },
+
+  get ruleCount() {
+    return this.sheet.ruleCount;
+  },
+
+  onPropertyChange: function(event) {
+    this.sheet[event.property] = event.value;
+    this.emit(event.property + "-changed", event.value);
+  },
+
+  toggleDisabled: function(cb) {
+    this.request('toggleDisabled', function(err, resp) {
+      if (err) return cb(err);
+
+      this.sheet.disabled = resp.disabled;
+      cb(null, resp.disabled);
+    }.bind(this));
+  },
+
+  getOriginalSources: function(cb) {
+    this.request('getOriginalSources', function(resp) {
+      if (resp.originalSources === null) {
+        return [];
+      }
+      return resp.originalSources.map(function(form) {
+        return new OriginalSource(this.client, form);
+      }.bind(this));
+    }.bind(this), cb);
+  },
+
+  update: function(text, cb) {
+    this.request('update', { text: text, transition: true }, cb);
+  },
+
+  getText: function(cb) {
+    this.request('getText', this.pluck('text'), cb);
+  }
+});
+
+
+function OriginalSource(client, source) {
+  console.log("source", source);
+  this.initialize(client, source.actor);
+
+  this.source = source;
+}
+
+OriginalSource.prototype = extend(ClientMethods, {
+  get url()  {
+    return this.source.url
+  },
+
+  getText: function(cb) {
+    this.request('getText', this.pluck('text'), cb);
+  }
+});
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/lib/tab.js b/node_modules/node-firefox-connect/node_modules/firefox-client/lib/tab.js
new file mode 100644
index 0000000..5e19e1c
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/lib/tab.js
@@ -0,0 +1,87 @@
+var extend = require("./extend"),
+    ClientMethods = require("./client-methods"),
+    Console = require("./console"),
+    Memory = require("./memory"),
+    DOM = require("./dom"),
+    Network = require("./network"),
+    StyleSheets = require("./stylesheets");
+
+module.exports = Tab;
+
+function Tab(client, tab) {
+  this.initialize(client, tab.actor);
+
+  this.tab = tab;
+  this.updateInfo(tab);
+
+  this.on("tabNavigated", this.onTabNavigated.bind(this));
+}
+
+Tab.prototype = extend(ClientMethods, {
+  updateInfo: function(form) {
+    this.url = form.url;
+    this.title = form.title;
+  },
+
+  get StyleSheets() {
+    if (!this._StyleSheets) {
+      this._StyleSheets = new StyleSheets(this.client, this.tab.styleSheetsActor);
+    }
+    return this._StyleSheets;
+  },
+
+  get DOM() {
+    if (!this._DOM) {
+      this._DOM = new DOM(this.client, this.tab.inspectorActor);
+    }
+    return this._DOM;
+  },
+
+  get Network() {
+    if (!this._Network) {
+      this._Network = new Network(this.client, this.tab.consoleActor);
+    }
+    return this._Network;
+  },
+
+  get Console() {
+    if (!this._Console) {
+      this._Console = new Console(this.client, this.tab.consoleActor);
+    }
+    return this._Console;
+  },
+
+  get Memory() {
+    if (!this._Memory) {
+      this._Memory = new Memory(this.client, this.tab.memoryActor);
+    }
+    return this._Memory;
+  },
+
+  onTabNavigated: function(event) {
+    if (event.state == "start") {
+      this.emit("before-navigate", { url: event.url });
+    }
+    else if (event.state == "stop") {
+      this.updateInfo(event);
+
+      this.emit("navigate", { url: event.url, title: event.title });
+    }
+  },
+
+  attach: function(cb) {
+    this.request("attach", cb);
+  },
+
+  detach: function(cb) {
+    this.request("detach", cb);
+  },
+
+  reload: function(cb) {
+    this.request("reload", cb);
+  },
+
+  navigateTo: function(url, cb) {
+    this.request("navigateTo", { url: url }, cb);
+  }
+})
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/lib/webapps.js b/node_modules/node-firefox-connect/node_modules/firefox-client/lib/webapps.js
new file mode 100644
index 0000000..5ede808
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/lib/webapps.js
@@ -0,0 +1,170 @@
+var extend = require("./extend"),
+    ClientMethods = require("./client-methods"),
+    Tab = require("./tab"),
+    fs = require("fs"),
+    spawn = require("child_process").spawn;
+
+module.exports = Webapps;
+
+var CHUNK_SIZE = 20480;
+
+// Also dispatch appOpen/appClose, appInstall/appUninstall events
+function Webapps(client, tab) {
+  this.initialize(client, tab.webappsActor);
+}
+
+Webapps.prototype = extend(ClientMethods, {
+  watchApps: function(cb) {
+    this.request("watchApps", cb);
+  },
+  unwatchApps: function(cb) {
+    this.request("unwatchApps", cb);
+  },
+  launch: function(manifestURL, cb) {
+    this.request("launch", {manifestURL: manifestURL}, cb);
+  },
+  close: function(manifestURL, cb) {
+    this.request("close", {manifestURL: manifestURL}, cb);
+  },
+  getInstalledApps: function(cb) {
+    this.request("getAll", function (err, resp) {
+      if (err) {
+        cb(err);
+        return;
+      }
+      cb(null, resp.apps);
+    });
+  },
+  listRunningApps: function(cb) {
+    this.request("listRunningApps", function (err, resp) {
+      if (err) {
+        cb(err);
+        return;
+      }
+      cb(null, resp.apps);
+    });
+  },
+  getApp: function(manifestURL, cb) {
+    this.request("getAppActor", {manifestURL: manifestURL}, (function (err, resp) {
+      if (err) {
+        cb(err);
+        return;
+      }
+      var actor = new Tab(this.client, resp.actor);
+      cb(null, actor);
+    }).bind(this));
+  },
+  installHosted: function(options, cb) {
+    this.request(
+      "install",
+      { appId: options.appId,
+        metadata: options.metadata,
+        manifest: options.manifest },
+      function (err, resp) {
+        if (err || resp.error) {
+          cb(err || resp.error);
+          return;
+        }
+        cb(null, resp.appId);
+      });
+  },
+  _upload: function (path, cb) {
+    // First create an upload actor
+    this.request("uploadPackage", function (err, resp) {
+      var actor = resp.actor;
+      fs.readFile(path, function(err, data) {
+        chunk(actor, data);
+      });
+    });
+    // Send push the file chunk by chunk
+    var self = this;
+    var step = 0;
+    function chunk(actor, data) {
+      var i = step++ * CHUNK_SIZE;
+      var m = Math.min(i + CHUNK_SIZE, data.length);
+      var c = "";
+      for(; i < m; i++)
+        c += String.fromCharCode(data[i]);
+      var message = {
+        to: actor,
+        type: "chunk",
+        chunk: c
+      };
+      self.client.makeRequest(message, function(resp) {
+        if (resp.error) {
+          cb(resp);
+          return;
+        }
+        if (i < data.length) {
+          setTimeout(chunk, 0, actor, data);
+        } else {
+          done(actor);
+        }
+      });
+    }
+    // Finally close the upload
+    function done(actor) {
+      var message = {
+        to: actor,
+        type: "done"
+      };
+      self.client.makeRequest(message, function(resp) {
+        if (resp.error) {
+          cb(resp);
+        } else {
+          cb(null, actor, cleanup.bind(null, actor));
+        }
+      });
+    }
+
+    // Remove the temporary uploaded file from the server:
+    function cleanup(actor) {
+      var message = {
+        to: actor,
+        type: "remove"
+      };
+      self.client.makeRequest(message, function () {});
+    }
+  },
+  installPackaged: function(path, appId, cb) {
+    this._upload(path, (function (err, actor, cleanup) {
+      this.request("install", {appId: appId, upload: actor},
+        function (err, resp) {
+          if (err) {
+            cb(err);
+            return;
+          }
+          cb(null, resp.appId);
+          cleanup();
+        });
+    }).bind(this));
+  },
+  installPackagedWithADB: function(path, appId, cb) {
+    var self = this;
+    // First ensure the temporary folder exists
+    function createTemporaryFolder() {
+      var c = spawn("adb", ["shell", "mkdir -p /data/local/tmp/b2g/" + appId], {stdio:"inherit"});
+      c.on("close", uploadPackage);
+    }
+    // then upload the package to the temporary directory
+    function uploadPackage() {
+      var child = spawn("adb", ["push", path, "/data/local/tmp/b2g/" + appId + "/application.zip"], {stdio:"inherit"});
+      child.on("close", installApp);
+    }
+    // finally order the webapps actor to install the app
+    function installApp() {
+      self.request("install", {appId: appId},
+        function (err, resp) {
+          if (err) {
+            cb(err);
+            return;
+          }
+          cb(null, resp.appId);
+        });
+    }
+    createTemporaryFolder();
+  },
+  uninstall: function(manifestURL, cb) {
+    this.request("uninstall", {manifestURL: manifestURL}, cb);
+  }
+})
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/colors/MIT-LICENSE.txt b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/colors/MIT-LICENSE.txt
new file mode 100644
index 0000000..7df0d5e
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/colors/MIT-LICENSE.txt
@@ -0,0 +1,19 @@
+Copyright (c) 2010 Alexis Sellier (cloudhead) , Marak Squires
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
\ No newline at end of file
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/colors/ReadMe.md b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/colors/ReadMe.md
new file mode 100644
index 0000000..74acead
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/colors/ReadMe.md
@@ -0,0 +1,30 @@
+<h1>colors.js - get color and style in your node.js console like what</h1>
+
+<img src="http://i.imgur.com/goJdO.png" border = "0"/>
+
+       var sys = require('sys');
+       var colors = require('./colors');
+
+       sys.puts('hello'.green); // outputs green text
+       sys.puts('i like cake and pies'.underline.red) // outputs red underlined text
+       sys.puts('inverse the color'.inverse); // inverses the color
+       sys.puts('OMG Rainbows!'.rainbow); // rainbow (ignores spaces)
+       
+<h2>colors and styles!</h2>
+- bold
+- italic
+- underline
+- inverse
+- yellow
+- cyan
+- white
+- magenta
+- green
+- red
+- grey
+- blue
+
+
+### Authors 
+
+#### Alexis Sellier (cloudhead) , Marak Squires , Justin Campbell, Dustin Diaz (@ded)
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/colors/colors.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/colors/colors.js
new file mode 100644
index 0000000..8c1d706
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/colors/colors.js
@@ -0,0 +1,230 @@
+/*
+colors.js
+
+Copyright (c) 2010 Alexis Sellier (cloudhead) , Marak Squires
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+*/
+
+exports.mode = "console";
+
+// prototypes the string object to have additional method calls that add terminal colors
+
+var addProperty = function (color, func) {
+  exports[color] = function(str) {
+    return func.apply(str);
+  };
+  String.prototype.__defineGetter__(color, func);
+}
+
+var isHeadless = (typeof module !== 'undefined');
+['bold', 'underline', 'italic', 'inverse', 'grey', 'black', 'yellow', 'red', 'green', 'blue', 'white', 'cyan', 'magenta'].forEach(function (style) {
+
+  // __defineGetter__ at the least works in more browsers
+  // http://robertnyman.com/javascript/javascript-getters-setters.html
+  // Object.defineProperty only works in Chrome
+  addProperty(style, function () {
+    return isHeadless ?
+             stylize(this, style) : // for those running in node (headless environments)
+             this.replace(/( )/, '$1'); // and for those running in browsers:
+             // re: ^ you'd think 'return this' works (but doesn't) so replace coerces the string to be a real string
+  });
+});
+
+// prototypes string with method "rainbow"
+// rainbow will apply a the color spectrum to a string, changing colors every letter
+addProperty('rainbow', function () {
+  if (!isHeadless) {
+    return this.replace(/( )/, '$1');
+  }
+  var rainbowcolors = ['red','yellow','green','blue','magenta']; //RoY G BiV
+  var exploded = this.split("");
+  var i=0;
+  exploded = exploded.map(function(letter) {
+    if (letter==" ") {
+      return letter;
+    }
+    else {
+      return stylize(letter,rainbowcolors[i++ % rainbowcolors.length]);
+    }
+  });
+  return exploded.join("");
+});
+
+function stylize(str, style) {
+  if (exports.mode == 'console') {
+    var styles = {
+      //styles
+      'bold'      : ['\033[1m',  '\033[22m'],
+      'italic'    : ['\033[3m',  '\033[23m'],
+      'underline' : ['\033[4m',  '\033[24m'],
+      'inverse'   : ['\033[7m',  '\033[27m'],
+      //grayscale
+      'white'     : ['\033[37m', '\033[39m'],
+      'grey'      : ['\033[90m', '\033[39m'],
+      'black'     : ['\033[30m', '\033[39m'],
+      //colors
+      'blue'      : ['\033[34m', '\033[39m'],
+      'cyan'      : ['\033[36m', '\033[39m'],
+      'green'     : ['\033[32m', '\033[39m'],
+      'magenta'   : ['\033[35m', '\033[39m'],
+      'red'       : ['\033[31m', '\033[39m'],
+      'yellow'    : ['\033[33m', '\033[39m']
+    };
+  } else if (exports.mode == 'browser') {
+    var styles = {
+      //styles
+      'bold'      : ['<b>',  '</b>'],
+      'italic'    : ['<i>',  '</i>'],
+      'underline' : ['<u>',  '</u>'],
+      'inverse'   : ['<span style="background-color:black;color:white;">',  '</span>'],
+      //grayscale
+      'white'     : ['<span style="color:white;">',   '</span>'],
+      'grey'      : ['<span style="color:grey;">',    '</span>'],
+      'black'     : ['<span style="color:black;">',   '</span>'],
+      //colors
+      'blue'      : ['<span style="color:blue;">',    '</span>'],
+      'cyan'      : ['<span style="color:cyan;">',    '</span>'],
+      'green'     : ['<span style="color:green;">',   '</span>'],
+      'magenta'   : ['<span style="color:magenta;">', '</span>'],
+      'red'       : ['<span style="color:red;">',     '</span>'],
+      'yellow'    : ['<span style="color:yellow;">',  '</span>']
+    };
+  } else if (exports.mode == 'none') {
+      return str;
+  } else {
+    console.log('unsupported mode, try "browser", "console" or "none"');
+  }
+
+  return styles[style][0] + str + styles[style][1];
+};
+
+// don't summon zalgo
+addProperty('zalgo', function () {
+  return zalgo(this);
+});
+
+// please no
+function zalgo(text, options) {
+  var soul = {
+    "up" : [
+      '̍','̎','̄','̅',
+      '̿','̑','̆','̐',
+      '͒','͗','͑','̇',
+      '̈','̊','͂','̓',
+      '̈','͊','͋','͌',
+      '̃','̂','̌','͐',
+      '̀','́','̋','̏',
+      '̒','̓','̔','̽',
+      '̉','ͣ','ͤ','ͥ',
+      'ͦ','ͧ','ͨ','ͩ',
+      'ͪ','ͫ','ͬ','ͭ',
+      'ͮ','ͯ','̾','͛',
+      '͆','̚'
+      ],
+    "down" : [
+      '̖','̗','̘','̙',
+      '̜','̝','̞','̟',
+      '̠','̤','̥','̦',
+      '̩','̪','̫','̬',
+      '̭','̮','̯','̰',
+      '̱','̲','̳','̹',
+      '̺','̻','̼','ͅ',
+      '͇','͈','͉','͍',
+      '͎','͓','͔','͕',
+      '͖','͙','͚','̣'
+      ],
+    "mid" : [
+      '̕','̛','̀','́',
+      '͘','̡','̢','̧',
+      '̨','̴','̵','̶',
+      '͜','͝','͞',
+      '͟','͠','͢','̸',
+      '̷','͡',' ҉'
+      ]
+  },
+  all = [].concat(soul.up, soul.down, soul.mid),
+  zalgo = {};
+
+  function randomNumber(range) {
+    r = Math.floor(Math.random()*range);
+    return r;
+  };
+
+  function is_char(character) {
+    var bool = false;
+    all.filter(function(i){
+     bool = (i == character);
+    });
+    return bool;
+  }
+
+  function heComes(text, options){
+      result = '';
+      options = options || {};
+      options["up"] = options["up"] || true;
+      options["mid"] = options["mid"] || true;
+      options["down"] = options["down"] || true;
+      options["size"] = options["size"] || "maxi";
+      var counts;
+      text = text.split('');
+       for(var l in text){
+         if(is_char(l)) { continue; }
+         result = result + text[l];
+
+        counts = {"up" : 0, "down" : 0, "mid" : 0};
+
+        switch(options.size) {
+          case 'mini':
+            counts.up = randomNumber(8);
+            counts.min= randomNumber(2);
+            counts.down = randomNumber(8);
+          break;
+          case 'maxi':
+            counts.up = randomNumber(16) + 3;
+            counts.min = randomNumber(4) + 1;
+            counts.down = randomNumber(64) + 3;
+          break;
+          default:
+            counts.up = randomNumber(8) + 1;
+            counts.mid = randomNumber(6) / 2;
+            counts.down= randomNumber(8) + 1;
+          break;
+        }
+
+        var arr = ["up", "mid", "down"];
+        for(var d in arr){
+          var index = arr[d];
+          for (var i = 0 ; i <= counts[index]; i++)
+          {
+            if(options[index]) {
+                result = result + soul[index][randomNumber(soul[index].length)];
+              }
+            }
+          }
+        }
+      return result;
+  };
+  return heComes(text);
+}
+
+addProperty('stripColors', function() {
+  return ("" + this).replace(/\u001b\[\d+m/g,'');
+});
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/colors/example.html b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/colors/example.html
new file mode 100644
index 0000000..c9cd68c
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/colors/example.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html lang="en-us">
+  <head>
+    <meta http-equiv="Content-type" content="text/html; charset=utf-8">
+    <title>Colors Example</title>
+    <script src="colors.js"></script>
+    <script type="text/javascript">
+      console.log('Rainbows are fun!'.rainbow);
+      console.log('So '.italic + 'are'.underline + ' styles! '.bold + 'inverse'.inverse);
+      console.log('Chains are also cool.'.bold.italic.underline.red);
+    </script>
+  </head>
+  <body>
+    <script>
+      document.write('Rainbows are fun!'.rainbow + '<br>');
+      document.write('So '.italic + 'are'.underline + ' styles! '.bold + 'inverse'.inverse + '<br>');
+      document.write('Chains are also cool.'.bold.italic.underline.red);
+    </script>
+  </body>
+</html>
\ No newline at end of file
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/colors/example.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/colors/example.js
new file mode 100644
index 0000000..12d8b1a
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/colors/example.js
@@ -0,0 +1,20 @@
+var util = require('util');
+var colors = require('./colors');
+
+//colors.mode = "browser";
+
+var test = colors.red("hopefully colorless output");
+util.puts('Rainbows are fun!'.rainbow);
+util.puts('So '.italic + 'are'.underline + ' styles! '.bold + 'inverse'.inverse); // styles not widely supported
+util.puts('Chains are also cool.'.bold.italic.underline.red); // styles not widely supported
+//util.puts('zalgo time!'.zalgo);
+util.puts(test.stripColors);
+util.puts("a".grey + " b".black);
+
+util.puts(colors.rainbow('Rainbows are fun!'));
+util.puts(colors.italic('So ') + colors.underline('are') + colors.bold(' styles! ') + colors.inverse('inverse')); // styles not widely supported
+util.puts(colors.bold(colors.italic(colors.underline(colors.red('Chains are also cool.'))))); // styles not widely supported
+//util.puts(colors.zalgo('zalgo time!'));
+util.puts(colors.stripColors(test));
+util.puts(colors.grey("a") + colors.black(" b"));
+
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/colors/package.json b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/colors/package.json
new file mode 100644
index 0000000..b0b4f79
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/colors/package.json
@@ -0,0 +1,49 @@
+{
+  "name": "colors",
+  "description": "get colors in your node.js console like what",
+  "version": "0.5.1",
+  "author": {
+    "name": "Marak Squires"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/Marak/colors.js.git"
+  },
+  "engines": {
+    "node": ">=0.1.90"
+  },
+  "main": "colors",
+  "_npmJsonOpts": {
+    "file": "/Users/maraksquires/.npm/colors/0.5.1/package/package.json",
+    "wscript": false,
+    "contributors": false,
+    "serverjs": false
+  },
+  "_id": "colors@0.5.1",
+  "dependencies": {},
+  "devDependencies": {},
+  "_engineSupported": true,
+  "_npmVersion": "1.0.30",
+  "_nodeVersion": "v0.4.10",
+  "_defaultsLoaded": true,
+  "dist": {
+    "shasum": "7d0023eaeb154e8ee9fce75dcb923d0ed1667774",
+    "tarball": "http://registry.npmjs.org/colors/-/colors-0.5.1.tgz"
+  },
+  "maintainers": [
+    {
+      "name": "marak",
+      "email": "marak.squires@gmail.com"
+    }
+  ],
+  "directories": {},
+  "_shasum": "7d0023eaeb154e8ee9fce75dcb923d0ed1667774",
+  "_from": "colors@0.5.x",
+  "_resolved": "https://registry.npmjs.org/colors/-/colors-0.5.1.tgz",
+  "bugs": {
+    "url": "https://github.com/Marak/colors.js/issues"
+  },
+  "readme": "ERROR: No README data found!",
+  "homepage": "https://github.com/Marak/colors.js",
+  "scripts": {}
+}
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/LICENSE b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/LICENSE
new file mode 100644
index 0000000..2983774
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/LICENSE
@@ -0,0 +1,21 @@
+Copyright (c) 2011 Heather Arthur
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/README.md b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/README.md
new file mode 100644
index 0000000..6c218a4
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/README.md
@@ -0,0 +1,118 @@
+# js-select
+
+js-select uses [js-traverse](https://github.com/substack/js-traverse) to traverse and modify JavaScript object nodes that match [JSONSelect](http://jsonselect.org/) selectors.
+
+```javascript
+var people = {
+   george: {
+      age : 35,
+      movie: "Repo Man"
+   },
+   mary: {
+      age: 15,
+      movie: "Twilight"
+   }
+};
+```
+
+### .forEach(fn)
+
+Iterates over all matching nodes in the object. The callback gets a special `this` context. See [js-traverse](https://github.com/substack/js-traverse) for all the things you can do to modify and inspect the node with this context. In addition, js-select adds a `this.matches()` which will test if the node matches a selector:
+
+```javascript
+select(people).forEach(function(node) {
+   if (this.matches(".mary > .movie")) {
+      this.remove();
+   }
+});
+```
+
+### .nodes()
+
+Returns all matching nodes from the object.
+
+```javascript
+select(people, ".age").nodes(); // [35, 15]
+```
+
+### .remove()
+
+Removes matching elements from the original object.
+
+```javascript
+select(people, ".age").remove();
+```
+
+### .update(fn)
+
+Updates all matching nodes using the given callback.
+
+```javascript
+select(people, ".age").update(function(age) {
+   return age - 5;
+});
+```
+
+### .condense()
+
+Reduces the original object down to only the matching elements (the hierarchy is maintained).
+
+```javascript
+select(people, ".age").condense();
+```
+
+```javascript
+{
+    george: { age: 35 },
+    mary: { age: 15 }
+}
+```
+
+## Selectors
+
+js-select supports the following [JSONSelect](http://jsonselect.org/) selectors:
+
+```
+*
+type
+.key
+ancestor selector
+parent > selector
+sibling ~ selector
+selector1, selector2
+:root
+:nth-child(n)
+:nth-child(even)
+:nth-child(odd)
+:nth-last-child(n)
+:first-child
+:last-child
+:only-child
+:has(selector)
+:val("string")
+:contains("substring")
+```
+
+See [details](http://jsonselect.org/#docs/overview) on each selector, and [try them](http://jsonselect.org/#tryit) out on the JSONSelect website.
+
+## Install
+
+For [node](http://nodejs.org), install with [npm](http://npmjs.org):
+
+```bash
+npm install js-select
+```
+
+For the browser, download the select.js file or fetch the latest version from [npm](http://npmjs.org) and build a browser file using [browserify](https://github.com/substack/node-browserify):
+
+```bash
+npm install browserify -g
+npm install js-select
+
+browserify --require js-select --outfile select.js
+```
+this will build a browser file with `require('js-select')` available.
+
+## Propers
+
+Huge thanks to [@substack](http://github.com/substack) for the ingenious [js-traverse](https://github.com/substack/js-traverse) and [@lloyd](https://github.com/lloyd) for the ingenious [JSONSelect spec](http://http://jsonselect.org/) and [selector parser](http://search.npmjs.org/#/JSONSelect).
\ No newline at end of file
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/index.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/index.js
new file mode 100644
index 0000000..1489b8e
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/index.js
@@ -0,0 +1,197 @@
+var traverse = require("traverse"),
+    JSONSelect = require("JSONSelect");
+
+module.exports = function(obj, string) {
+   var sels = parseSelectors(string);
+
+   return {
+      nodes: function() {
+         var nodes = [];
+         this.forEach(function(node) {
+            nodes.push(node);
+         });
+         return nodes;
+      },
+
+      update: function(cb) {
+         this.forEach(function(node) {
+            this.update(typeof cb == "function" ? cb(node) : cb);
+         });
+         return obj;
+      },
+
+      remove: function() {
+         this.forEach(function(node) {
+            this.remove();
+         })
+         return obj;
+      },
+
+      condense: function() {
+         traverse(obj).forEach(function(node) {
+            if (!this.parent) return;
+
+            if (this.parent.keep) {
+               this.keep = true;
+            } else {
+               var match = matchesAny(sels, this);
+               this.keep = match;
+               if (!match) {
+                  if (this.isLeaf) {
+                     this.remove();
+                  } else {
+                     this.after(function() {
+                        if (this.keep_child) {
+                           this.parent.keep_child = true;
+                        }
+                        if (!this.keep && !this.keep_child) {
+                           this.remove();
+                        }
+                     });
+                  }
+               } else {
+                  this.parent.keep_child = true;
+               }
+            }
+         });
+         return obj;
+      },
+
+      forEach: function(cb) {
+         traverse(obj).forEach(function(node) {
+            if (matchesAny(sels, this)) {
+               this.matches = function(string) {
+                  return matchesAny(parseSelectors(string), this);
+               };
+               // inherit context from js-traverse
+               cb.call(this, node);
+            }
+         });
+         return obj;
+      }
+   };
+}
+
+function parseSelectors(string) {
+   var parsed = JSONSelect._parse(string || "*")[1];
+   return getSelectors(parsed);
+}
+
+function getSelectors(parsed) {
+   if (parsed[0] == ",") {  // "selector1, selector2"
+      return parsed.slice(1);
+   }
+   return [parsed];
+}
+
+function matchesAny(sels, context) {
+   for (var i = 0; i < sels.length; i++) {
+      if (matches(sels[i], context)) {
+         return true;
+      }
+   }
+   return false;
+}
+
+function matches(sel, context) {
+   var path = context.parents.concat([context]),
+       i = path.length - 1,
+       j = sel.length - 1;
+
+   // walk up the ancestors
+   var must = true;
+   while(j >= 0 && i >= 0) {
+      var part = sel[j],
+          context = path[i];
+
+      if (part == ">") {
+         j--;
+         must = true;
+         continue;
+      }
+
+      if (matchesKey(part, context)) {
+         j--;
+      }
+      else if (must) {
+         return false;
+      }
+
+      i--;
+      must = false;
+   }
+   return j == -1;
+}
+
+function matchesKey(part, context) {
+   var key = context.key,
+       node = context.node,
+       parent = context.parent;
+
+   if (part.id && key != part.id) {
+      return false;
+   }
+   if (part.type) {
+      var type = part.type;
+
+      if (type == "null" && node !== null) {
+         return false;
+      }
+      else if (type == "array" && !isArray(node)) {
+         return false;
+      }
+      else if (type == "object" && (typeof node != "object"
+                 || node === null || isArray(node))) {
+         return false;
+      }
+      else if ((type == "boolean" || type == "string" || type == "number")
+               && type != typeof node) {
+         return false;
+      }
+   }
+   if (part.pf == ":nth-child") {
+      var index = parseInt(key) + 1;
+      if ((part.a == 0 && index !== part.b)         // :nth-child(i)
+        || (part.a == 1 && !(index >= -part.b))     // :nth-child(n)
+        || (part.a == -1 && !(index <= part.b))     // :nth-child(-n + 1)
+        || (part.a == 2 && index % 2 != part.b)) {  // :nth-child(even)
+         return false;
+      }
+   }
+   if (part.pf == ":nth-last-child"
+      && (!parent || key != parent.node.length - part.b)) {
+         return false;
+   }
+   if (part.pc == ":only-child"
+      && (!parent || parent.node.length != 1)) {
+         return false;
+   }
+   if (part.pc == ":root" && key !== undefined) {
+      return false;
+   }
+   if (part.has) {
+      var sels = getSelectors(part.has[0]),
+          match = false;
+      traverse(node).forEach(function(child) {
+         if (matchesAny(sels, this)) {
+            match = true;
+         }
+      });
+      if (!match) {
+         return false;
+      }
+   }
+   if (part.expr) {
+      var expr = part.expr, lhs = expr[0], op = expr[1], rhs = expr[2];
+      if (typeof node != "string"
+          || (!lhs && op == "=" && node != rhs)   // :val("str")
+          || (!lhs && op == "*=" && node.indexOf(rhs) == -1)) { // :contains("substr")
+         return false;
+      }
+   }
+   return true;
+}
+
+var isArray = Array.isArray || function(obj) {
+    return toString.call(obj) === '[object Array]';
+}
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/.gitmodules b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/.gitmodules
new file mode 100644
index 0000000..15339d4
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "src/test/tests"]
+	path = src/test/tests
+	url = git://github.com/lloyd/JSONSelectTests
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/.npmignore b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/.npmignore
new file mode 100644
index 0000000..0e2021f
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/.npmignore
@@ -0,0 +1,5 @@
+*~
+\#*\#
+.DS_Store
+/src/dist
+
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/JSONSelect.md b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/JSONSelect.md
new file mode 100644
index 0000000..bdd8580
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/JSONSelect.md
@@ -0,0 +1,214 @@
+**WARNING**: This document is a work in progress, just like JSONSelect itself.
+View or contribute to the latest version [on github](http://github.com/lloyd/JSONSelect/blob/master/JSONSelect.md)
+
+# JSONSelect
+
+  1. [introduction](#introduction)
+  1. [levels](#levels)
+  1. [language overview](#overview)
+  1. [grouping](#grouping)
+  1. [selectors](#selectors)
+  1. [pseudo classes](#pseudo)
+  1. [expressions](#expressions)
+  1. [combinators](#combinators)
+  1. [grammar](#grammar)
+  1. [conformance tests](#tests)
+  1. [references](#references)
+
+## Introduction<a name="introduction"></a>
+
+JSONSelect defines a language very similar in syntax and structure to
+[CSS3 Selectors](http://www.w3.org/TR/css3-selectors/).  JSONSelect
+expressions are patterns which can be matched against JSON documents.
+
+Potential applications of JSONSelect include:
+
+  * Simplified programmatic matching of nodes within JSON documents.
+  * Stream filtering, allowing efficient and incremental matching of documents.
+  * As a query language for a document database.
+
+## Levels<a name="levels"></a>
+
+The specification of JSONSelect is broken into three levels.  Higher
+levels include more powerful constructs, and are likewise more
+complicated to implement and use.
+
+**JSONSelect Level 1** is a small subset of CSS3.  Every feature is
+derived from a CSS construct that directly maps to JSON.  A level 1
+implementation is not particularly complicated while providing basic
+querying features.
+
+**JSONSelect Level 2** builds upon Level 1 adapting more complex CSS
+constructs which allow expressions to include constraints such as
+patterns that match against values, and those which consider a node's
+siblings.  Level 2 is still a direct adaptation of CSS, but includes
+constructs whose semantic meaning is significantly changed.
+
+**JSONSelect Level 3** adds constructs which do not necessarily have a
+direct analog in CSS, and are added to increase the power and convenience
+of the selector language.  These include aliases, wholly new pseudo
+class functions, and more blue sky dreaming.
+
+## Language Overview<a name="overview"></a>
+
+<table>
+<tr><th>pattern</th><th>meaning</th><th>level</th></tr>
+<tr><td>*</td><td>Any node</td><td>1</td></tr>
+<tr><td>T</td><td>A node of type T, where T is one string, number, object, array, boolean, or null</td><td>1</td></tr>
+<tr><td>T.key</td><td>A node of type T which is the child of an object and is the value its parents key property</td><td>1</td></tr>
+<tr><td>T."complex key"</td><td>Same as previous, but with property name specified as a JSON string</td><td>1</td></tr>
+<tr><td>T:root</td><td>A node of type T which is the root of the JSON document</td><td>1</td></tr>
+<tr><td>T:nth-child(n)</td><td>A node of type T which is the nth child of an array parent</td><td>1</td></tr>
+<tr><td>T:nth-last-child(n)</td><td>A node of type T which is the nth child of an array parent counting from the end</td><td>2</td></tr>
+<tr><td>T:first-child</td><td>A node of type T which is the first child of an array parent (equivalent to T:nth-child(1)</td><td>1</td></tr>
+<tr><td>T:last-child</td><td>A node of type T which is the last child of an array parent (equivalent to T:nth-last-child(1)</td><td>2</td></tr>
+<tr><td>T:only-child</td><td>A node of type T which is the only child of an array parent</td><td>2</td></tr>
+<tr><td>T:empty</td><td>A node of type T which is an array or object with no child</td><td>2</td></tr>
+<tr><td>T U</td><td>A node of type U with an ancestor of type T</td><td>1</td></tr>
+<tr><td>T > U</td><td>A node of type U with a parent of type T</td><td>1</td></tr>
+<tr><td>T ~ U</td><td>A node of type U with a sibling of type T</td><td>2</td></tr>
+<tr><td>S1, S2</td><td>Any node which matches either selector S1 or S2</td><td>1</td></tr>
+<tr><td>T:has(S)</td><td>A node of type T which has a child node satisfying the selector S</td><td>3</td></tr>
+<tr><td>T:expr(E)</td><td>A node of type T with a value that satisfies the expression E</td><td>3</td></tr>
+<tr><td>T:val(V)</td><td>A node of type T with a value that is equal to V</td><td>3</td></tr>
+<tr><td>T:contains(S)</td><td>A node of type T with a string value contains the substring S</td><td>3</td></tr>
+</table>
+
+## Grouping<a name="grouping"></a>
+
+## Selectors<a name="selectors"></a>
+
+## Pseudo Classes<a name="pseudo"></a>
+
+## Expressions<a name="expressions"></a>
+
+## Combinators<a name="combinators"></a>
+
+## Grammar<a name="grammar"></a>
+
+(Adapted from [CSS3](http://www.w3.org/TR/css3-selectors/#descendant-combinators) and
+ [json.org](http://json.org/))
+
+    selectors_group
+      : selector [ `,` selector ]*
+      ;
+
+    selector
+      : simple_selector_sequence [ combinator simple_selector_sequence ]*
+      ;
+
+    combinator
+      : `>` | \s+
+      ;
+
+    simple_selector_sequence
+      /* why allow multiple HASH entities in the grammar? */
+      : [ type_selector | universal ]
+        [ class | pseudo ]*
+      | [ class | pseudo ]+
+      ;
+
+    type_selector
+      : `object` | `array` | `number` | `string` | `boolean` | `null`
+      ;
+
+    universal
+      : '*'
+      ;
+
+    class
+      : `.` name
+      | `.` json_string
+      ;
+
+    pseudo
+      /* Note that pseudo-elements are restricted to one per selector and */
+      /* occur only in the last simple_selector_sequence. */
+      : `:` pseudo_class_name
+      | `:` nth_function_name `(` nth_expression `)`
+      | `:has` `(`  selectors_group `)`
+      | `:expr` `(`  expr `)`
+      | `:contains` `(`  json_string `)`
+      | `:val` `(` val `)`
+      ;
+
+    pseudo_class_name
+      : `root` | `first-child` | `last-child` | `only-child`
+
+    nth_function_name
+      : `nth-child` | `nth-last-child`
+
+    nth_expression
+      /* expression is and of the form "an+b" */
+      : TODO
+      ;
+
+    expr
+      : expr binop expr
+      | '(' expr ')'
+      | val
+      ;
+
+    binop
+      : '*' | '/' | '%' | '+' | '-' | '<=' | '>=' | '$='
+      | '^=' | '*=' | '>' | '<' | '=' | '!=' | '&&' | '||'
+      ;
+
+    val
+      : json_number | json_string | 'true' | 'false' | 'null' | 'x'
+      ;
+
+    json_string
+      : `"` json_chars* `"`
+      ;
+
+    json_chars
+      : any-Unicode-character-except-"-or-\-or-control-character
+      |  `\"`
+      |  `\\`
+      |  `\/`
+      |  `\b`
+      |  `\f`
+      |  `\n`
+      |  `\r`
+      |  `\t`
+      |   \u four-hex-digits
+      ;
+
+    name
+      : nmstart nmchar*
+      ;
+
+    nmstart
+      : escape | [_a-zA-Z] | nonascii
+      ;
+
+    nmchar
+      : [_a-zA-Z0-9-]
+      | escape
+      | nonascii
+      ;
+
+    escape
+      : \\[^\r\n\f0-9a-fA-F]
+      ;
+
+    nonascii
+      : [^\0-0177]
+      ;
+
+## Conformance Tests<a name="tests"></a>
+
+See [https://github.com/lloyd/JSONSelectTests](https://github.com/lloyd/JSONSelectTests)
+
+## References<a name="references"></a>
+
+In no particular order.
+
+  * [http://json.org/](http://json.org/)
+  * [http://www.w3.org/TR/css3-selectors/](http://www.w3.org/TR/css3-selectors/)
+  * [http://ejohn.org/blog/selectors-that-people-actually-use/](http://ejohn.org/blog/selectors-that-people-actually-use/)
+  * [http://shauninman.com/archive/2008/05/05/css\_qualified\_selectors](  * http://shauninman.com/archive/2008/05/05/css_qualified_selectors)
+  * [http://snook.ca/archives/html\_and\_css/css-parent-selectors](http://snook.ca/archives/html_and_css/css-parent-selectors)
+  * [http://remysharp.com/2010/10/11/css-parent-selector/](http://remysharp.com/2010/10/11/css-parent-selector/)
+  * [https://github.com/jquery/sizzle/wiki/Sizzle-Home](https://github.com/jquery/sizzle/wiki/Sizzle-Home)
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/README.md b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/README.md
new file mode 100644
index 0000000..00a573e
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/README.md
@@ -0,0 +1,33 @@
+JSONSelect is *EXPERIMENTAL*, *ALPHA*, etc.
+
+JSONSelect defines a selector language similar to CSS intended for
+JSON documents.  For an introduction to the project see
+[jsonselect.org](http://jsonselect.org) or the [documentation](https://github.com/lloyd/JSONSelect/blob/master/JSONSelect.md).
+
+## Project Overview
+
+JSONSelect is an attempt to create a selector language similar to
+CSS for JSON objects.  A couple key goals of the project's include:
+
+  * **intuitive** - JSONSelect is meant to *feel like* CSS, meaning a developers with an understanding of CSS can probably guess most of the syntax.
+  * **expressive** - As JSONSelect evolves, it will include more of the most popular constructs from the CSS spec and popular implementations (like [sizzle](http://sizzlejs.com/)).  A successful result will be a good balance of simplicity and power.
+  * **language independence** - The project will avoid features which are unnecessarily tied to a particular implementation language.
+  * **incremental adoption** - JSONSelect features are broken in to conformance levels, to make it easier to build basic support and to allow incremental stabilization of the language.
+  * **efficient** - As many constructs of the language as possible will be able to be evaluated in a single document traversal.  This allows for efficient stream filtering.
+
+JSONSelect should make common operations easy, complex operations possible,
+but haughtily ignore weird shit.
+
+## What's Here
+
+This repository is the home to many things related to JSONSelect:
+
+  * [Documentation](https://github.com/lloyd/JSONSelect/blob/master/JSONSelect.md) which describes the language
+  * The [jsonselect.org](http://jsonselect.org) [site source](https://github.com/lloyd/JSONSelect/blob/master/site/)
+  * A [reference implementation](https://github.com/lloyd/JSONSelect/blob/master/src/jsonselect.js) in JavaScript
+
+## Related projects
+
+Conformance tests are broken out into a [separate
+repository](https://github.com/lloyd/JSONSelectTests) and may be used
+by other implementations.
\ No newline at end of file
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/package.json b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/package.json
new file mode 100644
index 0000000..fae9d34
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/package.json
@@ -0,0 +1,49 @@
+{
+  "author": {
+    "name": "Lloyd Hilaiel",
+    "email": "lloyd@hilaiel.com",
+    "url": "http://trickyco.de"
+  },
+  "name": "JSONSelect",
+  "description": "CSS-like selectors for JSON",
+  "version": "0.2.1",
+  "homepage": "http://jsonselect.org",
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/lloyd/JSONSelect.git"
+  },
+  "main": "src/jsonselect",
+  "engines": {
+    "node": ">=0.4.7"
+  },
+  "dependencies": {},
+  "devDependencies": {},
+  "files": [
+    "src/jsonselect.js",
+    "src/test/run.js",
+    "src/test/tests",
+    "tests",
+    "README.md",
+    "JSONSelect.md",
+    "package.json",
+    "LICENSE"
+  ],
+  "_id": "JSONSelect@0.2.1",
+  "_engineSupported": true,
+  "_npmVersion": "1.0.6",
+  "_nodeVersion": "v0.4.7",
+  "_defaultsLoaded": true,
+  "dist": {
+    "shasum": "415418a526d33fe31d74b4defa3c836d485ec203",
+    "tarball": "http://registry.npmjs.org/JSONSelect/-/JSONSelect-0.2.1.tgz"
+  },
+  "scripts": {},
+  "directories": {},
+  "_shasum": "415418a526d33fe31d74b4defa3c836d485ec203",
+  "_from": "JSONSelect@0.2.1",
+  "_resolved": "https://registry.npmjs.org/JSONSelect/-/JSONSelect-0.2.1.tgz",
+  "bugs": {
+    "url": "https://github.com/lloyd/JSONSelect/issues"
+  },
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/css/droid_sans.css b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/css/droid_sans.css
new file mode 100644
index 0000000..0826445
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/css/droid_sans.css
@@ -0,0 +1,6 @@
+@font-face {
+  font-family: 'Droid Sans';
+  font-style: normal;
+  font-weight: normal;
+  src: local('Droid Sans'), local('DroidSans'), url('droid_sans.tt') format('truetype');
+}
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/css/droid_sans.tt b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/css/droid_sans.tt
new file mode 100644
index 0000000..efd1f8b
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/css/droid_sans.tt
Binary files differ
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/css/style.css b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/css/style.css
new file mode 100644
index 0000000..b4fbaa6
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/css/style.css
@@ -0,0 +1,209 @@
+body {
+    background-color: #191919;
+    color: #a1a1a1;
+    font-family: 'Droid Sans', arial, serif;
+    font-size: 14px;
+}
+
+#header {
+    position: fixed;
+    background-color: #191919;
+    opacity: .7;
+    color: #ccc;
+    width: 100%;
+    top: 0px;
+    left: 0px;
+    margin: 0px;
+    padding: 4px 10px 4px 10px;
+}
+
+#main {
+    margin-top: 100px;
+}
+
+#header b {
+    font-weight: bold;
+    color: #fff;
+}
+
+a {
+    color: #999;
+    text-decoration: underline;
+}
+
+a:hover {
+    color: #fff;
+}
+
+#header .title {
+    font-size: 400%;
+}
+
+#header .subtitle {
+    font-size: 150%;
+    margin-left: 1em;
+    font-style: italic;
+}
+
+#header .nav {
+    float: right;
+    margin-right: 100px;
+}
+
+div.content {
+    max-width: 850px;
+    min-width: 675px;
+    margin: auto;
+}
+
+div#tryit {
+    min-width: 825px;
+}
+
+#splash {
+    max-width: 700px;
+}
+
+.json {
+    background-color: #333;
+    padding: .5em 1em .5em 1em;
+    border: 3px solid #444;
+    border-radius: 1em;
+    -moz-border-radius: 1em;
+    -webkit-border-radius: 1em;
+}
+
+#splash .sample {
+    width: 250px;
+    float: right;
+    margin-left: 1em;
+    margin-top: 3em;
+    font-size: 90%;
+}
+
+#doc {
+    margin-top: 140px;
+}
+
+#doc table {
+    width: 100%;
+}
+
+#doc table th {
+    background-color: #aaa;
+    color: #333;
+    padding: 10px;
+}
+
+#doc table tr td:first-child {
+    font-family: "andale mono",monospace;
+}
+
+#doc table td {
+    padding-top: 3px;
+}
+
+#splash .sample tt {
+    font-size: 90%;
+    padding: 0 .2em 0 .2em;
+}
+
+#splash .sample pre {
+    border-top: 1px dashed #aaa;
+    padding-top: 1em;
+}
+
+#splash .pitch {
+    padding-top: 2em;
+    font-size: 170%;
+}
+
+.selected {
+    opacity: .7;
+    padding: 8px;
+    margin: -10px;
+    background-color: #fff475;
+    color: #000;
+    border: 2px solid white;
+    border-radius: 4px;
+    -moz-border-radius: 4px;
+    -webkit-border-radius: 4px;
+}
+
+div.current input, div.selector, pre, code, tt {
+    font-family: "andale mono",monospace;
+    font-size: .9em;
+}
+
+div.current {
+    width: 100%;
+    padding: 1em;
+}
+
+div.current input {
+    color: #fff;
+    font-size: 110%;
+    background-color: #333;
+    border: 1px solid #444;
+    padding: 8px;
+    width: 400px;
+    margin-left: 1em;
+    margin-right: 1em;
+    border-radius: 4px;
+    -moz-border-radius: 4px;
+    -webkit-border-radius: 4px;
+}
+
+pre.doc {
+    float: right;
+    padding: 1em 3em 1em 3em;
+    padding-right: 160px;
+    font-size: 90%;
+}
+
+.selectors {
+    margin: 1em;
+    background-color: #444;
+    width: 300px;
+    padding: 8px;
+    border-radius: 4px;
+    -moz-border-radius: 4px;
+    -webkit-border-radius: 4px;
+
+}
+.selectors .selector {
+    background-color: #333;
+    padding: .4em;
+    margin: 1px;
+    cursor: pointer;
+}
+
+.selectors .selector.inuse {
+    border: 1px solid #9f9;
+    margin: -1px;
+    margin-left: 0px;
+}
+
+.results.error {
+    color: #f99;
+}
+
+.results {
+    color: #9f9;
+    font-size: 90%;
+    width: 300px;
+}
+
+#cred, #code {
+    padding-top: 2em;
+}
+
+p.psst {
+    margin-top: 4em;
+    font-size: .9em;
+}
+
+p.item {
+    margin-top: 2em;
+    margin-left: 1em;
+}
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/index.html b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/index.html
new file mode 100644
index 0000000..984e184
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/index.html
@@ -0,0 +1,139 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title> JSONSelect </title>
+    <link href='css/droid_sans.css' rel='stylesheet' type='text/css'>
+    <link href='css/style.css' type='text/css' rel='stylesheet'>
+  </head>
+  <body>
+    <div id="header">
+      <div class="content">
+        <div class="title">json<b>:</b>select<b>()</b></div>
+        <div class="nav"> <a href="#overview">Overview</a> | <a href="#tryit">Try It!</a> | <a href="#docs">Docs</a> | <a href="#code">Code</a> | <a href="mailto:jsonselect@librelist.com">Contact</a> | <a href="#cred">Credit</a> </div>
+        <div class="subtitle">CSS-like selectors for JSON.</div>
+      </div>
+    </div>
+    <div id="main">
+      <div style="display: none" id="splash" class="content">
+        <div class="sample json">
+          <tt>".author .drinkPref&nbsp;:first-child"</tt>
+          <pre>{
+  "author": {
+    "name": {
+      "first": "Lloyd",
+      "last": "Hilaiel"
+    },
+    "drinkPref": [
+      <span class="selected">"whiskey"</span>,
+      "beer",
+      "wine"
+    ],
+  },
+  "thing": "JSONSelect site",
+  "license": "<a href="http://creativecommons.org/licenses/by-sa/3.0/us/">(cc) BY-SA</a>"
+}</pre></div>
+        <div class="pitch">
+        <p>JSONSelect is an <i>experimental</i> selector language for JSON.</p>
+        <p>It makes it easy to access data  in complex JSON documents.</p>
+        <p>It <i>feels like</i> CSS.</p>
+        <p>Why not <a href="#tryit">give it a try</a>?
+        </div>
+      </div>
+      <div id="tryit" style="display: none" class="content">
+        <div class="current"> Current Selector (click to edit): <input type="text"></input><span class="results"></span></div>
+        <pre class="doc json">
+{
+    "name": {
+        "first": "Lloyd",
+        "last": "Hilaiel"
+    },
+    "favoriteColor": "yellow",
+    "languagesSpoken": [
+        {
+            "lang": "Bulgarian",
+            "level": "advanced"
+        },
+        {
+            "lang": "English",
+            "level": "native",
+            "preferred": true
+        },
+        {
+            "lang": "Spanish",
+            "level": "beginner"
+        }
+    ],
+    "seatingPreference": [
+        "window",
+        "aisle"
+    ],
+    "drinkPreference": [
+        "whiskey",
+        "beer",
+        "wine"
+    ],
+    "weight": 172
+}
+        </pre>
+        <div class="selectors">
+          <div class="title"> Choose A Selector... </div>
+          <div class="selector">.languagesSpoken .lang</div>
+          <div class="selector">.drinkPreference :first-child</div>
+          <div class="selector">."weight"</div>
+          <div class="selector">.lang</div>
+          <div class="selector">.favoriteColor</div>
+          <div class="selector">string.favoriteColor</div>
+          <div class="selector">string:last-child</div>
+          <div class="selector">string:nth-child(-n+2)</div>
+          <div class="selector">string:nth-child(odd)</div>
+          <div class="selector">string:nth-last-child(1)</div>
+          <div class="selector">:root</div>
+          <div class="selector">number</div>
+          <div class="selector">:has(:root > .preferred)</div>
+          <div class="selector">.preferred ~ .lang</div>
+          <div class="selector">:has(.lang:val("Spanish")) > .level</div>
+          <div class="selector">.lang:val("Bulgarian") ~ .level</div>
+          <div class="selector">.seatingPreference :nth-child(1)</div>
+          <div class="selector">.weight:expr(x<180) ~ .name .first</div>
+        </div>
+      </div>
+      <div style="display: none" id="cred" class="content">
+        <p>JSONSelect is dedicated to sad code everywhere that looks like this:</p>
+        <pre class="json">if (foo && foo.bar && foo.bar.baz && foo.bar.baz.length > 2)
+    return foo.bar.baz[2];
+return undefined;</pre>
+        <p><a href="http://trickyco.de/">Lloyd Hilaiel</a> started it, and is surrounded by many <a href="https://github.com/lloyd/JSONSelect/contributors">awesome contributors</a>.</p>
+        <p><a href="http://blog.mozilla.com/dherman/">Dave Herman</a> provided the name, and lots of encouragement.</p>
+        <p><a href="http://open-mike.org/">Mike Hanson</a> gave deep feedback and ideas.</p>
+        <p><a href="http://ejohn.org/">John Resig</a> unwittingly contributed his <a href="http://ejohn.org/blog/selectors-that-people-actually-use/">design thoughts</a>.</p>
+        <p>The jsonselect.org site design was inspired by <a href="http://www.stephenwildish.co.uk/">Stephen Wildish</a>.</p>
+        <p><a href="http://json.org/">JSON</a> and <a href="http://www.w3.org/TR/css3-selectors/">CSS3 Selectors</a> are the prerequisites to JSONSelect's existence, so thanks to you guys too.</p>
+        <p></p>
+      </div>
+      <div style="display: none" id="doc" class="content">
+        Loading documentation...
+      </div>
+      <div style="display: none" id="code" class="content">
+        <p>Several different implementations of JSONSelect are available:</p>
+        <p class="item"><b>JavaScript:</b> Get it <a href="js/jsonselect.js">documented</a>, or <a href="js/jsonselect.min.js">minified</a> (<i>2.9k</i> minified and gzipped).
+           The code is <a href="https://github.com/lloyd/JSONSelect">on github</a>.</p>
+        <p class="item"><b>node.js:</b> <tt>npm install JSONSelect</tt></p>
+        <p class="item"><b>ruby:</b> A gem by <a href="https://github.com/fd">Simon Menke</a>: <a href="https://github.com/fd/json_select">https://github.com/fd/json_select</a></p>
+        <p class="psst">
+          Something missing from this list?  Please, <a href="https://github.com/lloyd/JSONSelect/issues">tell me about it</a>.
+        </p>
+      </div>
+
+    </div>
+
+
+<a href="https://github.com/lloyd/JSONSelect"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://d3nwyuy0nl342s.cloudfront.net/img/4c7dc970b89fd04b81c8e221ba88ff99a06c6b61/687474703a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f77686974655f6666666666662e706e67" alt="Fork me on GitHub"></a>
+  </body>
+  <script src="js/json2.js"></script>
+  <script src="js/showdown.js"></script>
+  <script src="js/jquery-1.6.1.min.js"></script>
+  <script src="js/jquery.ba-hashchange.min.js"></script>
+  <script src="js/jsonselect.js"></script>
+  <script src="js/nav.js"></script>
+  <script src="js/demo.js"></script>
+</html>
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/demo.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/demo.js
new file mode 100644
index 0000000..7462cf9
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/demo.js
@@ -0,0 +1,64 @@
+window.jsel = JSONSelect;
+
+$(document).ready(function() {
+    var theDoc = JSON.parse($("pre.doc").text());
+
+    function highlightMatches(ar) {
+        // first calculate match offsets
+        var wrk = [];
+        var html = $.trim(JSON.stringify(theDoc, undefined, 4));
+        var ss = "<span class=\"selected\">";
+        var es = "</span>";
+        for (var i = 0; i < ar.length; i++) {
+            var found = $.trim(JSON.stringify(ar[i], undefined, 4));
+            // turn the string into a regex to handle indentation
+            found = found.replace(/[-[\]{}()*+?.,\\^$|#]/g, "\\$&").replace(/\s+/gm, "\\s*");
+            var re = new RegExp(found, "m");
+            var m = re.exec(html);
+            if (!m) continue;
+            wrk.push({ off: m.index, typ: "s" });
+            wrk.push({ off: m[0].length+m.index, typ: "e" });
+        }
+        // sort by offset
+        wrk = wrk.sort(function(a,b) { return a.off - b.off; });
+
+        // now start injecting spans into the text
+        var cur = 0;
+        var cons = 0;
+        for (var i = 0; i < wrk.length; i++) {
+            var diff = wrk[i].off - cons;
+            cons = wrk[i].off;
+            var tag = (wrk[i].typ == 's' ? ss : es);
+            cur += diff;
+            html = html.substr(0, cur) + tag + html.substr(cur);
+            cur += tag.length;
+        }
+        return html;
+    }
+
+    // when a selector is chosen, update the text box
+    $(".selectors .selector").click(function() {
+        $(".current input").val($(this).text()).keyup();
+    });
+
+    var lastSel;
+    $(".current input").keyup(function () {
+        try {
+            var sel = $(".current input").val()
+            if (lastSel === $.trim(sel)) return;
+            lastSel = $.trim(sel);
+            var ar = jsel.match(sel, theDoc);
+            $(".current .results").text(ar.length + " match" + (ar.length == 1 ? "" : "es"))
+                .removeClass("error");
+            $("pre.doc").html(highlightMatches(ar));
+            $("pre.doc .selected").hide().fadeIn(700);
+        } catch(e) {
+            $(".current .results").text(e.toString()).addClass("error");
+            $("pre.doc").text($.trim(JSON.stringify(theDoc, undefined, 4)));
+        }
+        $(".selectors .selector").removeClass("inuse");
+        $(".selectors div.selector").each(function() {
+            if ($(this).text() === sel) $(this).addClass("inuse");
+        });
+    });
+});
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/jquery-1.6.1.min.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/jquery-1.6.1.min.js
new file mode 100644
index 0000000..b2ac174
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/jquery-1.6.1.min.js
@@ -0,0 +1,18 @@
+/*!
+ * jQuery JavaScript Library v1.6.1
+ * http://jquery.com/
+ *
+ * Copyright 2011, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ * Copyright 2011, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ *
+ * Date: Thu May 12 15:04:36 2011 -0400
+ */
+(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cv(a){if(!cj[a]){var b=f("<"+a+">").appendTo("body"),d=b.css("display");b.remove();if(d==="none"||d===""){ck||(ck=c.createElement("iframe"),ck.frameBorder=ck.width=ck.height=0),c.body.appendChild(ck);if(!cl||!ck.createElement)cl=(ck.contentWindow||ck.contentDocument).document,cl.write("<!doctype><html><body></body></html>");b=cl.createElement(a),cl.body.appendChild(b),d=f.css(b,"display"),c.body.removeChild(ck)}cj[a]=d}return cj[a]}function cu(a,b){var c={};f.each(cp.concat.apply([],cp.slice(0,b)),function(){c[this]=a});return c}function ct(){cq=b}function cs(){setTimeout(ct,0);return cq=f.now()}function ci(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ch(){try{return new a.XMLHttpRequest}catch(b){}}function cb(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g<i;g++){if(g===1)for(h in a.converters)typeof h=="string"&&(e[h.toLowerCase()]=a.converters[h]);l=k,k=d[g];if(k==="*")k=l;else if(l!=="*"&&l!==k){m=l+" "+k,n=e[m]||e["* "+k];if(!n){p=b;for(o in e){j=o.split(" ");if(j[0]===l||j[0]==="*"){p=e[j[1]+" "+k];if(p){o=e[o],o===!0?n=p:p===!0&&(n=o);break}}}}!n&&!p&&f.error("No conversion from "+m.replace(" "," to ")),n!==!0&&(c=n?n(c):p(o(c)))}}return c}function ca(a,c,d){var e=a.contents,f=a.dataTypes,g=a.responseFields,h,i,j,k;for(i in g)i in d&&(c[g[i]]=d[i]);while(f[0]==="*")f.shift(),h===b&&(h=a.mimeType||c.getResponseHeader("content-type"));if(h)for(i in e)if(e[i]&&e[i].test(h)){f.unshift(i);break}if(f[0]in d)j=f[0];else{for(i in d){if(!f[0]||a.converters[i+" "+f[0]]){j=i;break}k||(k=i)}j=j||k}if(j){j!==f[0]&&f.unshift(j);return d[j]}}function b_(a,b,c,d){if(f.isArray(b))f.each(b,function(b,e){c||bF.test(a)?d(a,e):b_(a+"["+(typeof e=="object"||f.isArray(e)?b:"")+"]",e,c,d)});else if(!c&&b!=null&&typeof b=="object")for(var e in b)b_(a+"["+e+"]",b[e],c,d);else d(a,b)}function b$(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h=a[f],i=0,j=h?h.length:0,k=a===bU,l;for(;i<j&&(k||!l);i++)l=h[i](c,d,e),typeof l=="string"&&(!k||g[l]?l=b:(c.dataTypes.unshift(l),l=b$(a,c,d,e,l,g)));(k||!l)&&!g["*"]&&(l=b$(a,c,d,e,"*",g));return l}function bZ(a){return function(b,c){typeof b!="string"&&(c=b,b="*");if(f.isFunction(c)){var d=b.toLowerCase().split(bQ),e=0,g=d.length,h,i,j;for(;e<g;e++)h=d[e],j=/^\+/.test(h),j&&(h=h.substr(1)||"*"),i=a[h]=a[h]||[],i[j?"unshift":"push"](c)}}}function bD(a,b,c){var d=b==="width"?bx:by,e=b==="width"?a.offsetWidth:a.offsetHeight;if(c==="border")return e;f.each(d,function(){c||(e-=parseFloat(f.css(a,"padding"+this))||0),c==="margin"?e+=parseFloat(f.css(a,"margin"+this))||0:e-=parseFloat(f.css(a,"border"+this+"Width"))||0});return e}function bn(a,b){b.src?f.ajax({url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(bf,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)}function bm(a){f.nodeName(a,"input")?bl(a):a.getElementsByTagName&&f.grep(a.getElementsByTagName("input"),bl)}function bl(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bk(a){return"getElementsByTagName"in a?a.getElementsByTagName("*"):"querySelectorAll"in a?a.querySelectorAll("*"):[]}function bj(a,b){var c;if(b.nodeType===1){b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase();if(c==="object")b.outerHTML=a.outerHTML;else if(c!=="input"||a.type!=="checkbox"&&a.type!=="radio"){if(c==="option")b.selected=a.defaultSelected;else if(c==="input"||c==="textarea")b.defaultValue=a.defaultValue}else a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value);b.removeAttribute(f.expando)}}function bi(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c=f.expando,d=f.data(a),e=f.data(b,d);if(d=d[c]){var g=d.events;e=e[c]=f.extend({},d);if(g){delete e.handle,e.events={};for(var h in g)for(var i=0,j=g[h].length;i<j;i++)f.event.add(b,h+(g[h][i].namespace?".":"")+g[h][i].namespace,g[h][i],g[h][i].data)}}}}function bh(a,b){return f.nodeName(a,"table")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function X(a,b,c){b=b||0;if(f.isFunction(b))return f.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return f.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=f.grep(a,function(a){return a.nodeType===1});if(S.test(b))return f.filter(b,d,!c);b=f.filter(b,d)}return f.grep(a,function(a,d){return f.inArray(a,b)>=0===c})}function W(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function O(a,b){return(a&&a!=="*"?a+".":"")+b.replace(A,"`").replace(B,"&")}function N(a){var b,c,d,e,g,h,i,j,k,l,m,n,o,p=[],q=[],r=f._data(this,"events");if(!(a.liveFired===this||!r||!r.live||a.target.disabled||a.button&&a.type==="click")){a.namespace&&(n=new RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)")),a.liveFired=this;var s=r.live.slice(0);for(i=0;i<s.length;i++)g=s[i],g.origType.replace(y,"")===a.type?q.push(g.selector):s.splice(i--,1);e=f(a.target).closest(q,a.currentTarget);for(j=0,k=e.length;j<k;j++){m=e[j];for(i=0;i<s.length;i++){g=s[i];if(m.selector===g.selector&&(!n||n.test(g.namespace))&&!m.elem.disabled){h=m.elem,d=null;if(g.preType==="mouseenter"||g.preType==="mouseleave")a.type=g.preType,d=f(a.relatedTarget).closest(g.selector)[0],d&&f.contains(h,d)&&(d=h);(!d||d!==h)&&p.push({elem:h,handleObj:g,level:m.level})}}}for(j=0,k=p.length;j<k;j++){e=p[j];if(c&&e.level>c)break;a.currentTarget=e.elem,a.data=e.handleObj.data,a.handleObj=e.handleObj,o=e.handleObj.origHandler.apply(e.elem,arguments);if(o===!1||a.isPropagationStopped()){c=e.level,o===!1&&(b=!1);if(a.isImmediatePropagationStopped())break}}return b}}function L(a,c,d){var e=f.extend({},d[0]);e.type=a,e.originalEvent={},e.liveFired=b,f.event.handle.call(c,e),e.isDefaultPrevented()&&d[0].preventDefault()}function F(){return!0}function E(){return!1}function m(a,c,d){var e=c+"defer",g=c+"queue",h=c+"mark",i=f.data(a,e,b,!0);i&&(d==="queue"||!f.data(a,g,b,!0))&&(d==="mark"||!f.data(a,h,b,!0))&&setTimeout(function(){!f.data(a,g,b,!0)&&!f.data(a,h,b,!0)&&(f.removeData(a,e,!0),i.resolve())},0)}function l(a){for(var b in a)if(b!=="toJSON")return!1;return!0}function k(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(j,"$1-$2").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNaN(d)?i.test(d)?f.parseJSON(d):d:parseFloat(d)}catch(g){}f.data(a,c,d)}else d=b}return d}var c=a.document,d=a.navigator,e=a.location,f=function(){function H(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(H,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/\d/,n=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,o=/^[\],:{}\s]*$/,p=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,q=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,r=/(?:^|:|,)(?:\s*\[)+/g,s=/(webkit)[ \/]([\w.]+)/,t=/(opera)(?:.*version)?[ \/]([\w.]+)/,u=/(msie) ([\w.]+)/,v=/(mozilla)(?:.*? rv:([\w.]+))?/,w=d.userAgent,x,y,z,A=Object.prototype.toString,B=Object.prototype.hasOwnProperty,C=Array.prototype.push,D=Array.prototype.slice,E=String.prototype.trim,F=Array.prototype.indexOf,G={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=n.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.6.1",length:0,size:function(){return this.length},toArray:function(){return D.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?C.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),y.done(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(D.apply(this,arguments),"slice",D.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:C,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j<k;j++)if((a=arguments[j])!=null)for(c in a){d=i[c],f=a[c];if(i===f)continue;l&&f&&(e.isPlainObject(f)||(g=e.isArray(f)))?(g?(g=!1,h=d&&e.isArray(d)?d:[]):h=d&&e.isPlainObject(d)?d:{},i[c]=e.extend(l,h,f)):f!==b&&(i[c]=f)}return i},e.extend({noConflict:function(b){a.$===e&&(a.$=g),b&&a.jQuery===e&&(a.jQuery=f);return e},isReady:!1,readyWait:1,holdReady:function(a){a?e.readyWait++:e.ready(!0)},ready:function(a){if(a===!0&&!--e.readyWait||a!==!0&&!e.isReady){if(!c.body)return setTimeout(e.ready,1);e.isReady=!0;if(a!==!0&&--e.readyWait>0)return;y.resolveWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").unbind("ready")}},bindReady:function(){if(!y){y=e._Deferred();if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",z,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",z),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&H()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNaN:function(a){return a==null||!m.test(a)||isNaN(a)},type:function(a){return a==null?String(a):G[A.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;if(a.constructor&&!B.call(a,"constructor")&&!B.call(a.constructor.prototype,"isPrototypeOf"))return!1;var c;for(c in a);return c===b||B.call(a,c)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw a},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(o.test(b.replace(p,"@").replace(q,"]").replace(r,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(b,c,d){a.DOMParser?(d=new DOMParser,c=d.parseFromString(b,"text/xml")):(c=new ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(b)),d=c.documentElement,(!d||!d.nodeName||d.nodeName==="parsererror")&&e.error("Invalid XML: "+b);return c},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g<h;)if(c.apply(a[g++],d)===!1)break}else if(i){for(f in a)if(c.call(a[f],f,a[f])===!1)break}else for(;g<h;)if(c.call(a[g],g,a[g++])===!1)break;return a},trim:E?function(a){return a==null?"":E.call(a)}:function(a){return a==null?"":(a+"").replace(k,"").replace(l,"")},makeArray:function(a,b){var c=b||[];if(a!=null){var d=e.type(a);a.length==null||d==="string"||d==="function"||d==="regexp"||e.isWindow(a)?C.call(c,a):e.merge(c,a)}return c},inArray:function(a,b){if(F)return F.call(b,a);for(var c=0,d=b.length;c<d;c++)if(b[c]===a)return c;return-1},merge:function(a,c){var d=a.length,e=0;if(typeof c.length=="number")for(var f=c.length;e<f;e++)a[d++]=c[e];else while(c[e]!==b)a[d++]=c[e++];a.length=d;return a},grep:function(a,b,c){var d=[],e;c=!!c;for(var f=0,g=a.length;f<g;f++)e=!!b(a[f],f),c!==e&&d.push(a[f]);return d},map:function(a,c,d){var f,g,h=[],i=0,j=a.length,k=a instanceof e||j!==b&&typeof j=="number"&&(j>0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i<j;i++)f=c(a[i],i,d),f!=null&&(h[h.length]=f);else for(g in a)f=c(a[g],g,d),f!=null&&(h[h.length]=f);return h.concat.apply([],h)},guid:1,proxy:function(a,c){if(typeof c=="string"){var d=a[c];c=a,a=d}if(!e.isFunction(a))return b;var f=D.call(arguments,2),g=function(){return a.apply(c,f.concat(D.call(arguments)))};g.guid=a.guid=a.guid||g.guid||e.guid++;return g},access:function(a,c,d,f,g,h){var i=a.length;if(typeof c=="object"){for(var j in c)e.access(a,j,c[j],f,g,d);return a}if(d!==b){f=!h&&f&&e.isFunction(d);for(var k=0;k<i;k++)g(a[k],c,f?d.call(a[k],k,g(a[k],c)):d,h);return a}return i?g(a[0],c):b},now:function(){return(new Date).getTime()},uaMatch:function(a){a=a.toLowerCase();var b=s.exec(a)||t.exec(a)||u.exec(a)||a.indexOf("compatible")<0&&v.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},sub:function(){function a(b,c){return new a.fn.init(b,c)}e.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function(d,f){f&&f instanceof e&&!(f instanceof a)&&(f=a(f));return e.fn.init.call(this,d,f,b)},a.fn.init.prototype=a.fn;var b=a(c);return a},browser:{}}),e.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){G["[object "+b+"]"]=b.toLowerCase()}),x=e.uaMatch(w),x.browser&&(e.browser[x.browser]=!0,e.browser.version=x.version),e.browser.webkit&&(e.browser.safari=!0),j.test(" ")&&(k=/^[\s\xA0]+/,l=/[\s\xA0]+$/),h=e(c),c.addEventListener?z=function(){c.removeEventListener("DOMContentLoaded",z,!1),e.ready()}:c.attachEvent&&(z=function(){c.readyState==="complete"&&(c.detachEvent("onreadystatechange",z),e.ready())});return e}(),g="done fail isResolved isRejected promise then always pipe".split(" "),h=[].slice;f.extend({_Deferred:function(){var a=[],b,c,d,e={done:function(){if(!d){var c=arguments,g,h,i,j,k;b&&(k=b,b=0);for(g=0,h=c.length;g<h;g++)i=c[g],j=f.type(i),j==="array"?e.done.apply(e,i):j==="function"&&a.push(i);k&&e.resolveWith(k[0],k[1])}return this},resolveWith:function(e,f){if(!d&&!b&&!c){f=f||[],c=1;try{while(a[0])a.shift().apply(e,f)}finally{b=[e,f],c=0}}return this},resolve:function(){e.resolveWith(this,arguments);return this},isResolved:function(){return!!c||!!b},cancel:function(){d=1,a=[];return this}};return e},Deferred:function(a){var b=f._Deferred(),c=f._Deferred(),d;f.extend(b,{then:function(a,c){b.done(a).fail(c);return this},always:function(){return b.done.apply(b,arguments).fail.apply(this,arguments)},fail:c.done,rejectWith:c.resolveWith,reject:c.resolve,isRejected:c.isResolved,pipe:function(a,c){return f.Deferred(function(d){f.each({done:[a,"resolve"],fail:[c,"reject"]},function(a,c){var e=c[0],g=c[1],h;f.isFunction(e)?b[a](function(){h=e.apply(this,arguments),h&&f.isFunction(h.promise)?h.promise().then(d.resolve,d.reject):d[g](h)}):b[a](d[g])})}).promise()},promise:function(a){if(a==null){if(d)return d;d=a={}}var c=g.length;while(c--)a[g[c]]=b[g[c]];return a}}),b.done(c.cancel).fail(b.cancel),delete b.cancel,a&&a.call(b,b);return b},when:function(a){function i(a){return function(c){b[a]=arguments.length>1?h.call(arguments,0):c,--e||g.resolveWith(g,h.call(b,0))}}var b=arguments,c=0,d=b.length,e=d,g=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred();if(d>1){for(;c<d;c++)b[c]&&f.isFunction(b[c].promise)?b[c].promise().then(i(c),g.reject):--e;e||g.resolveWith(g,b)}else g!==a&&g.resolveWith(g,d?[a]:[]);return g.promise()}}),f.support=function(){var a=c.createElement("div"),b=c.documentElement,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r;a.setAttribute("className","t"),a.innerHTML="   <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>",d=a.getElementsByTagName("*"),e=a.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};f=c.createElement("select"),g=f.appendChild(c.createElement("option")),h=a.getElementsByTagName("input")[0],j={leadingWhitespace:a.firstChild.nodeType===3,tbody:!a.getElementsByTagName("tbody").length,htmlSerialize:!!a.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55$/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:h.value==="on",optSelected:g.selected,getSetAttribute:a.className!=="t",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},h.checked=!0,j.noCloneChecked=h.cloneNode(!0).checked,f.disabled=!0,j.optDisabled=!g.disabled;try{delete a.test}catch(s){j.deleteExpando=!1}!a.addEventListener&&a.attachEvent&&a.fireEvent&&(a.attachEvent("onclick",function b(){j.noCloneEvent=!1,a.detachEvent("onclick",b)}),a.cloneNode(!0).fireEvent("onclick")),h=c.createElement("input"),h.value="t",h.setAttribute("type","radio"),j.radioValue=h.value==="t",h.setAttribute("checked","checked"),a.appendChild(h),k=c.createDocumentFragment(),k.appendChild(a.firstChild),j.checkClone=k.cloneNode(!0).cloneNode(!0).lastChild.checked,a.innerHTML="",a.style.width=a.style.paddingLeft="1px",l=c.createElement("body"),m={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"};for(q in m)l.style[q]=m[q];l.appendChild(a),b.insertBefore(l,b.firstChild),j.appendChecked=h.checked,j.boxModel=a.offsetWidth===2,"zoom"in a.style&&(a.style.display="inline",a.style.zoom=1,j.inlineBlockNeedsLayout=a.offsetWidth===2,a.style.display="",a.innerHTML="<div style='width:4px;'></div>",j.shrinkWrapBlocks=a.offsetWidth!==2),a.innerHTML="<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>",n=a.getElementsByTagName("td"),r=n[0].offsetHeight===0,n[0].style.display="",n[1].style.display="none",j.reliableHiddenOffsets=r&&n[0].offsetHeight===0,a.innerHTML="",c.defaultView&&c.defaultView.getComputedStyle&&(i=c.createElement("div"),i.style.width="0",i.style.marginRight="0",a.appendChild(i),j.reliableMarginRight=(parseInt((c.defaultView.getComputedStyle(i,null)||{marginRight:0}).marginRight,10)||0)===0),l.innerHTML="",b.removeChild(l);if(a.attachEvent)for(q in{submit:1,change:1,focusin:1})p="on"+q,r=p in a,r||(a.setAttribute(p,"return;"),r=typeof a[p]=="function"),j[q+"Bubbles"]=r;return j}(),f.boxModel=f.support.boxModel;var i=/^(?:\{.*\}|\[.*\])$/,j=/([a-z])([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!l(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g=f.expando,h=typeof c=="string",i,j=a.nodeType,k=j?f.cache:a,l=j?a[f.expando]:a[f.expando]&&f.expando;if((!l||e&&l&&!k[l][g])&&h&&d===b)return;l||(j?a[f.expando]=l=++f.uuid:l=f.expando),k[l]||(k[l]={},j||(k[l].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?k[l][g]=f.extend(k[l][g],c):k[l]=f.extend(k[l],c);i=k[l],e&&(i[g]||(i[g]={}),i=i[g]),d!==b&&(i[f.camelCase(c)]=d);if(c==="events"&&!i[c])return i[g]&&i[g].events;return h?i[f.camelCase(c)]:i}},removeData:function(b,c,d){if(!!f.acceptData(b)){var e=f.expando,g=b.nodeType,h=g?f.cache:b,i=g?b[f.expando]:f.expando;if(!h[i])return;if(c){var j=d?h[i][e]:h[i];if(j){delete j[c];if(!l(j))return}}if(d){delete h[i][e];if(!l(h[i]))return}var k=h[i][e];f.support.deleteExpando||h!=a?delete h[i]:h[i]=null,k?(h[i]={},g||(h[i].toJSON=f.noop),h[i][e]=k):g&&(f.support.deleteExpando?delete b[f.expando]:b.removeAttribute?b.removeAttribute(f.expando):b[f.expando]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d=null;if(typeof a=="undefined"){if(this.length){d=f.data(this[0]);if(this[0].nodeType===1){var e=this[0].attributes,g;for(var h=0,i=e.length;h<i;h++)g=e[h].name,g.indexOf("data-")===0&&(g=f.camelCase(g.substring(5)),k(this[0],g,d[g]))}}return d}if(typeof a=="object")return this.each(function(){f.data(this,a)});var j=a.split(".");j[1]=j[1]?"."+j[1]:"";if(c===b){d=this.triggerHandler("getData"+j[1]+"!",[j[0]]),d===b&&this.length&&(d=f.data(this[0],a),d=k(this[0],a,d));return d===b&&j[1]?this.data(j[0]):d}return this.each(function(){var b=f(this),d=[j[0],c];b.triggerHandler("setData"+j[1]+"!",d),f.data(this,a,c),b.triggerHandler("changeData"+j[1]+"!",d)})},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,c){a&&(c=(c||"fx")+"mark",f.data(a,c,(f.data(a,c,b,!0)||0)+1,!0))},_unmark:function(a,c,d){a!==!0&&(d=c,c=a,a=!1);if(c){d=d||"fx";var e=d+"mark",g=a?0:(f.data(c,e,b,!0)||1)-1;g?f.data(c,e,g,!0):(f.removeData(c,e,!0),m(c,d,"mark"))}},queue:function(a,c,d){if(a){c=(c||"fx")+"queue";var e=f.data(a,c,b,!0);d&&(!e||f.isArray(d)?e=f.data(a,c,f.makeArray(d),!0):e.push(d));return e||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e;d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),d.call(a,function(){f.dequeue(a,b)})),c.length||(f.removeData(a,b+"queue",!0),m(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){typeof a!="string"&&(c=a,a="fx");if(c===b)return f.queue(this[0],a);return this.each(function(){var b=f.queue(this,a,c);a==="fx"&&b[0]!=="inprogress"&&f.dequeue(this,a)})},dequeue:function(a){return this.each(function(){f.dequeue(this,a)})},delay:function(a,b){a=f.fx?f.fx.speeds[a]||a:a,b=b||"fx";return this.queue(b,function(){var c=this;setTimeout(function(){f.dequeue(c,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){function m(){--h||d.resolveWith(e,[e])}typeof a!="string"&&(c=a,a=b),a=a||"fx";var d=f.Deferred(),e=this,g=e.length,h=1,i=a+"defer",j=a+"queue",k=a+"mark",l;while(g--)if(l=f.data(e[g],i,b,!0)||(f.data(e[g],j,b,!0)||f.data(e[g],k,b,!0))&&f.data(e[g],i,f._Deferred(),!0))h++,l.done(m);m();return d.promise()}});var n=/[\n\t\r]/g,o=/\s+/,p=/\r/g,q=/^(?:button|input)$/i,r=/^(?:button|input|object|select|textarea)$/i,s=/^a(?:rea)?$/i,t=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,u=/\:/,v,w;f.fn.extend({attr:function(a,b){return f.access(this,a,b,!0,f.attr)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,a,b,!0,f.prop)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.addClass(a.call(this,b,c.attr("class")||""))});if(a&&typeof a=="string"){var b=(a||"").split(o);for(var c=0,d=this.length;c<d;c++){var e=this[c];if(e.nodeType===1)if(!e.className)e.className=a;else{var g=" "+e.className+" ",h=e.className;for(var i=0,j=b.length;i<j;i++)g.indexOf(" "+b[i]+" ")<0&&(h+=" "+b[i]);e.className=f.trim(h)}}}return this},removeClass:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.removeClass(a.call(this,b,c.attr("class")))});if(a&&typeof a=="string"||a===b){var c=(a||"").split(o);for(var d=0,e=this.length;d<e;d++){var g=this[d];if(g.nodeType===1&&g.className)if(a){var h=(" "+g.className+" ").replace(n," ");for(var i=0,j=c.length;i<j;i++)h=h.replace(" "+c[i]+" "," ");g.className=f.trim(h)}else g.className=""}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";if(f.isFunction(a))return this.each(function(c){var d=f(this);d.toggleClass(a.call(this,c,d.attr("class"),b),b)});return this.each(function(){if(c==="string"){var e,g=0,h=f(this),i=b,j=a.split(o);while(e=j[g++])i=d?i:!h.hasClass(e),h[i?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&f._data(this,"__className__",this.className),this.className=this.className||a===!1?"":f._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ";for(var c=0,d=this.length;c<d;c++)if((" "+this[c].className+" ").replace(n," ").indexOf(b)>-1)return!0;return!1},val:function(a){var c,d,e=this[0];if(!arguments.length){if(e){c=f.valHooks[e.nodeName.toLowerCase()]||f.valHooks[e.type];if(c&&"get"in c&&(d=c.get(e,"value"))!==b)return d;return(e.value||"").replace(p,"")}return b}var g=f.isFunction(a);return this.each(function(d){var e=f(this),h;if(this.nodeType===1){g?h=a.call(this,d,e.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c=a.selectedIndex,d=[],e=a.options,g=a.type==="select-one";if(c<0)return null;for(var h=g?c:0,i=g?c+1:e.length;h<i;h++){var j=e[h];if(j.selected&&(f.support.optDisabled?!j.disabled:j.getAttribute("disabled")===null)&&(!j.parentNode.disabled||!f.nodeName(j.parentNode,"optgroup"))){b=f(j).val();if(g)return b;d.push(b)}}if(g&&!d.length&&e.length)return f(e[c]).val();return d},set:function(a,b){var c=f.makeArray(b);f(a).find("option").each(function(){this.selected=f.inArray(f(this).val(),c)>=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attrFix:{tabindex:"tabIndex"},attr:function(a,c,d,e){var g=a.nodeType;if(!a||g===3||g===8||g===2)return b;if(e&&c in f.attrFn)return f(a)[c](d);if(!("getAttribute"in a))return f.prop(a,c,d);var h,i,j=g!==1||!f.isXMLDoc(a);c=j&&f.attrFix[c]||c,i=f.attrHooks[c],i||(!t.test(c)||typeof d!="boolean"&&d!==b&&d.toLowerCase()!==c.toLowerCase()?v&&(f.nodeName(a,"form")||u.test(c))&&(i=v):i=w);if(d!==b){if(d===null){f.removeAttr(a,c);return b}if(i&&"set"in i&&j&&(h=i.set(a,d,c))!==b)return h;a.setAttribute(c,""+d);return d}if(i&&"get"in i&&j)return i.get(a,c);h=a.getAttribute(c);return h===null?b:h},removeAttr:function(a,b){var c;a.nodeType===1&&(b=f.attrFix[b]||b,f.support.getSetAttribute?a.removeAttribute(b):(f.attr(a,b,""),a.removeAttributeNode(a.getAttributeNode(b))),t.test(b)&&(c=f.propFix[b]||b)in a&&(a[c]=!1))},attrHooks:{type:{set:function(a,b){if(q.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.value;a.setAttribute("type",b),c&&(a.value=c);return b}}},tabIndex:{get:function(a){var c=a.getAttributeNode("tabIndex");return c&&c.specified?parseInt(c.value,10):r.test(a.nodeName)||s.test(a.nodeName)&&a.href?0:b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e=a.nodeType;if(!a||e===3||e===8||e===2)return b;var g,h,i=e!==1||!f.isXMLDoc(a);c=i&&f.propFix[c]||c,h=f.propHooks[c];return d!==b?h&&"set"in h&&(g=h.set(a,d,c))!==b?g:a[c]=d:h&&"get"in h&&(g=h.get(a,c))!==b?g:a[c]},propHooks:{}}),w={get:function(a,c){return a[f.propFix[c]||c]?c.toLowerCase():b},set:function(a,b,c){var d;b===!1?f.removeAttr(a,c):(d=f.propFix[c]||c,d in a&&(a[d]=b),a.setAttribute(c,c.toLowerCase()));return c}},f.attrHooks.value={get:function(a,b){if(v&&f.nodeName(a,"button"))return v.get(a,b);return a.value},set:function(a,b,c){if(v&&f.nodeName(a,"button"))return v.set(a,b,c);a.value=b}},f.support.getSetAttribute||(f.attrFix=f.propFix,v=f.attrHooks.name=f.valHooks.button={get:function(a,c){var d;d=a.getAttributeNode(c);return d&&d.nodeValue!==""?d.nodeValue:b},set:function(a,b,c){var d=a.getAttributeNode(c);if(d){d.nodeValue=b;return b}}},f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})})),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}})),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var x=Object.prototype.hasOwnProperty,y=/\.(.*)$/,z=/^(?:textarea|input|select)$/i,A=/\./g,B=/ /g,C=/[^\w\s.|`]/g,D=function(a){return a.replace(C,"\\$&")};f.event={add:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){if(d===!1)d=E;else if(!d)return;var g,h;d.handler&&(g=d,d=g.handler),d.guid||(d.guid=f.guid++);var i=f._data(a);if(!i)return;var j=i.events,k=i.handle;j||(i.events=j={}),k||(i.handle=k=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.handle.apply(k.elem,arguments):b}),k.elem=a,c=c.split(" ");var l,m=0,n;while(l=c[m++]){h=g?f.extend({},g):{handler:d,data:e},l.indexOf(".")>-1?(n=l.split("."),l=n.shift(),h.namespace=n.slice(0).sort().join(".")):(n=[],h.namespace=""),h.type=l,h.guid||(h.guid=d.guid);var o=j[l],p=f.event.special[l]||{};if(!o){o=j[l]=[];if(!p.setup||p.setup.call(a,e,n,k)===!1)a.addEventListener?a.addEventListener(l,k,!1):a.attachEvent&&a.attachEvent("on"+l,k)}p.add&&(p.add.call(a,h),h.handler.guid||(h.handler.guid=d.guid)),o.push(h),f.event.global[l]=!0}a=null}},global:{},remove:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){d===!1&&(d=E);var g,h,i,j,k=0,l,m,n,o,p,q,r,s=f.hasData(a)&&f._data(a),t=s&&s.events;if(!s||!t)return;c&&c.type&&(d=c.handler,c=c.type);if(!c||typeof c=="string"&&c.charAt(0)==="."){c=c||"";for(h in t)f.event.remove(a,h+c);return}c=c.split(" ");while(h=c[k++]){r=h,q=null,l=h.indexOf(".")<0,m=[],l||(m=h.split("."),h=m.shift(),n=new RegExp("(^|\\.)"+f.map(m.slice(0).sort(),D).join("\\.(?:.*\\.)?")+"(\\.|$)")),p=t[h];if(!p)continue;if(!d){for(j=0;j<p.length;j++){q=p[j];if(l||n.test(q.namespace))f.event.remove(a,r,q.handler,j),p.splice(j--,1)}continue}o=f.event.special[h]||{};for(j=e||0;j<p.length;j++){q=p[j];if(d.guid===q.guid){if(l||n.test(q.namespace))e==null&&p.splice(j--,1),o.remove&&o.remove.call(a,q);if(e!=null)break}}if(p.length===0||e!=null&&p.length===1)(!o.teardown||o.teardown.call(a,m)===!1)&&f.removeEvent(a,h,s.handle),g=null,delete t[h]}if(f.isEmptyObject(t)){var u=s.handle;u&&(u.elem=null),delete s.events,delete s.handle,f.isEmptyObject(s)&&f.removeData(a,b,!0)}}},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,e,g){var h=c.type||c,i=[],j;h.indexOf("!")>=0&&(h=h.slice(0,-1),j=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if(!!e&&!f.event.customEvent[h]||!!f.event.global[h]){c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.exclusive=j,c.namespace=i.join("."),c.namespace_re=new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)");if(g||!e)c.preventDefault(),c.stopPropagation();if(!e){f.each(f.cache,function(){var a=f.expando,b=this[a];b&&b.events&&b.events[h]&&f.event.trigger(c,d,b.handle.elem
+)});return}if(e.nodeType===3||e.nodeType===8)return;c.result=b,c.target=e,d=d?f.makeArray(d):[],d.unshift(c);var k=e,l=h.indexOf(":")<0?"on"+h:"";do{var m=f._data(k,"handle");c.currentTarget=k,m&&m.apply(k,d),l&&f.acceptData(k)&&k[l]&&k[l].apply(k,d)===!1&&(c.result=!1,c.preventDefault()),k=k.parentNode||k.ownerDocument||k===c.target.ownerDocument&&a}while(k&&!c.isPropagationStopped());if(!c.isDefaultPrevented()){var n,o=f.event.special[h]||{};if((!o._default||o._default.call(e.ownerDocument,c)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)){try{l&&e[h]&&(n=e[l],n&&(e[l]=null),f.event.triggered=h,e[h]())}catch(p){}n&&(e[l]=n),f.event.triggered=b}}return c.result}},handle:function(c){c=f.event.fix(c||a.event);var d=((f._data(this,"events")||{})[c.type]||[]).slice(0),e=!c.exclusive&&!c.namespace,g=Array.prototype.slice.call(arguments,0);g[0]=c,c.currentTarget=this;for(var h=0,i=d.length;h<i;h++){var j=d[h];if(e||c.namespace_re.test(j.namespace)){c.handler=j.handler,c.data=j.data,c.handleObj=j;var k=j.handler.apply(this,g);k!==b&&(c.result=k,k===!1&&(c.preventDefault(),c.stopPropagation()));if(c.isImmediatePropagationStopped())break}}return c.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(a){if(a[f.expando])return a;var d=a;a=f.Event(d);for(var e=this.props.length,g;e;)g=this.props[--e],a[g]=d[g];a.target||(a.target=a.srcElement||c),a.target.nodeType===3&&(a.target=a.target.parentNode),!a.relatedTarget&&a.fromElement&&(a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement);if(a.pageX==null&&a.clientX!=null){var h=a.target.ownerDocument||c,i=h.documentElement,j=h.body;a.pageX=a.clientX+(i&&i.scrollLeft||j&&j.scrollLeft||0)-(i&&i.clientLeft||j&&j.clientLeft||0),a.pageY=a.clientY+(i&&i.scrollTop||j&&j.scrollTop||0)-(i&&i.clientTop||j&&j.clientTop||0)}a.which==null&&(a.charCode!=null||a.keyCode!=null)&&(a.which=a.charCode!=null?a.charCode:a.keyCode),!a.metaKey&&a.ctrlKey&&(a.metaKey=a.ctrlKey),!a.which&&a.button!==b&&(a.which=a.button&1?1:a.button&2?3:a.button&4?2:0);return a},guid:1e8,proxy:f.proxy,special:{ready:{setup:f.bindReady,teardown:f.noop},live:{add:function(a){f.event.add(this,O(a.origType,a.selector),f.extend({},a,{handler:N,guid:a.handler.guid}))},remove:function(a){f.event.remove(this,O(a.origType,a.selector),a)}},beforeunload:{setup:function(a,b,c){f.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}}},f.removeEvent=c.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){a.detachEvent&&a.detachEvent("on"+b,c)},f.Event=function(a,b){if(!this.preventDefault)return new f.Event(a,b);a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?F:E):this.type=a,b&&f.extend(this,b),this.timeStamp=f.now(),this[f.expando]=!0},f.Event.prototype={preventDefault:function(){this.isDefaultPrevented=F;var a=this.originalEvent;!a||(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){this.isPropagationStopped=F;var a=this.originalEvent;!a||(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=F,this.stopPropagation()},isDefaultPrevented:E,isPropagationStopped:E,isImmediatePropagationStopped:E};var G=function(a){var b=a.relatedTarget;a.type=a.data;try{if(b&&b!==c&&!b.parentNode)return;while(b&&b!==this)b=b.parentNode;b!==this&&f.event.handle.apply(this,arguments)}catch(d){}},H=function(a){a.type=a.data,f.event.handle.apply(this,arguments)};f.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){f.event.special[a]={setup:function(c){f.event.add(this,b,c&&c.selector?H:G,a)},teardown:function(a){f.event.remove(this,b,a&&a.selector?H:G)}}}),f.support.submitBubbles||(f.event.special.submit={setup:function(a,b){if(!f.nodeName(this,"form"))f.event.add(this,"click.specialSubmit",function(a){var b=a.target,c=b.type;(c==="submit"||c==="image")&&f(b).closest("form").length&&L("submit",this,arguments)}),f.event.add(this,"keypress.specialSubmit",function(a){var b=a.target,c=b.type;(c==="text"||c==="password")&&f(b).closest("form").length&&a.keyCode===13&&L("submit",this,arguments)});else return!1},teardown:function(a){f.event.remove(this,".specialSubmit")}});if(!f.support.changeBubbles){var I,J=function(a){var b=a.type,c=a.value;b==="radio"||b==="checkbox"?c=a.checked:b==="select-multiple"?c=a.selectedIndex>-1?f.map(a.options,function(a){return a.selected}).join("-"):"":f.nodeName(a,"select")&&(c=a.selectedIndex);return c},K=function(c){var d=c.target,e,g;if(!!z.test(d.nodeName)&&!d.readOnly){e=f._data(d,"_change_data"),g=J(d),(c.type!=="focusout"||d.type!=="radio")&&f._data(d,"_change_data",g);if(e===b||g===e)return;if(e!=null||g)c.type="change",c.liveFired=b,f.event.trigger(c,arguments[1],d)}};f.event.special.change={filters:{focusout:K,beforedeactivate:K,click:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(c==="radio"||c==="checkbox"||f.nodeName(b,"select"))&&K.call(this,a)},keydown:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(a.keyCode===13&&!f.nodeName(b,"textarea")||a.keyCode===32&&(c==="checkbox"||c==="radio")||c==="select-multiple")&&K.call(this,a)},beforeactivate:function(a){var b=a.target;f._data(b,"_change_data",J(b))}},setup:function(a,b){if(this.type==="file")return!1;for(var c in I)f.event.add(this,c+".specialChange",I[c]);return z.test(this.nodeName)},teardown:function(a){f.event.remove(this,".specialChange");return z.test(this.nodeName)}},I=f.event.special.change.filters,I.focus=I.beforeactivate}f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){function e(a){var c=f.event.fix(a);c.type=b,c.originalEvent={},f.event.trigger(c,null,c.target),c.isDefaultPrevented()&&a.preventDefault()}var d=0;f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.each(["bind","one"],function(a,c){f.fn[c]=function(a,d,e){var g;if(typeof a=="object"){for(var h in a)this[c](h,d,a[h],e);return this}if(arguments.length===2||d===!1)e=d,d=b;c==="one"?(g=function(a){f(this).unbind(a,g);return e.apply(this,arguments)},g.guid=e.guid||f.guid++):g=e;if(a==="unload"&&c!=="one")this.one(a,d,e);else for(var i=0,j=this.length;i<j;i++)f.event.add(this[i],a,g,d);return this}}),f.fn.extend({unbind:function(a,b){if(typeof a=="object"&&!a.preventDefault)for(var c in a)this.unbind(c,a[c]);else for(var d=0,e=this.length;d<e;d++)f.event.remove(this[d],a,b);return this},delegate:function(a,b,c,d){return this.live(b,c,d,a)},undelegate:function(a,b,c){return arguments.length===0?this.unbind("live"):this.die(b,null,c,a)},trigger:function(a,b){return this.each(function(){f.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return f.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=arguments,c=a.guid||f.guid++,d=0,e=function(c){var e=(f.data(this,"lastToggle"+a.guid)||0)%d;f.data(this,"lastToggle"+a.guid,e+1),c.preventDefault();return b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});var M={focus:"focusin",blur:"focusout",mouseenter:"mouseover",mouseleave:"mouseout"};f.each(["live","die"],function(a,c){f.fn[c]=function(a,d,e,g){var h,i=0,j,k,l,m=g||this.selector,n=g?this:f(this.context);if(typeof a=="object"&&!a.preventDefault){for(var o in a)n[c](o,d,a[o],m);return this}if(c==="die"&&!a&&g&&g.charAt(0)==="."){n.unbind(g);return this}if(d===!1||f.isFunction(d))e=d||E,d=b;a=(a||"").split(" ");while((h=a[i++])!=null){j=y.exec(h),k="",j&&(k=j[0],h=h.replace(y,""));if(h==="hover"){a.push("mouseenter"+k,"mouseleave"+k);continue}l=h,M[h]?(a.push(M[h]+k),h=h+k):h=(M[h]||h)+k;if(c==="live")for(var p=0,q=n.length;p<q;p++)f.event.add(n[p],"live."+O(h,m),{data:d,selector:m,handler:e,origType:h,origHandler:e,preType:l});else n.unbind("live."+O(h,m),e)}return this}}),f.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error".split(" "),function(a,b){f.fn[b]=function(a,c){c==null&&(c=a,a=null);return arguments.length>0?this.bind(b,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0)}),function(){function u(a,b,c,d,e,f){for(var g=0,h=d.length;g<h;g++){var i=d[g];if(i){var j=!1;i=i[a];while(i){if(i.sizcache===c){j=d[i.sizset];break}if(i.nodeType===1){f||(i.sizcache=c,i.sizset=g);if(typeof b!="string"){if(i===b){j=!0;break}}else if(k.filter(b,[i]).length>0){j=i;break}}i=i[a]}d[g]=j}}}function t(a,b,c,d,e,f){for(var g=0,h=d.length;g<h;g++){var i=d[g];if(i){var j=!1;i=i[a];while(i){if(i.sizcache===c){j=d[i.sizset];break}i.nodeType===1&&!f&&(i.sizcache=c,i.sizset=g);if(i.nodeName.toLowerCase()===b){j=i;break}i=i[a]}d[g]=j}}}var a=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d=0,e=Object.prototype.toString,g=!1,h=!0,i=/\\/g,j=/\W/;[0,0].sort(function(){h=!1;return 0});var k=function(b,d,f,g){f=f||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return f;var i,j,n,o,q,r,s,t,u=!0,w=k.isXML(d),x=[],y=b;do{a.exec(""),i=a.exec(y);if(i){y=i[3],x.push(i[1]);if(i[2]){o=i[3];break}}}while(i);if(x.length>1&&m.exec(b))if(x.length===2&&l.relative[x[0]])j=v(x[0]+x[1],d);else{j=l.relative[x[0]]?[d]:k(x.shift(),d);while(x.length)b=x.shift(),l.relative[b]&&(b+=x.shift()),j=v(b,j)}else{!g&&x.length>1&&d.nodeType===9&&!w&&l.match.ID.test(x[0])&&!l.match.ID.test(x[x.length-1])&&(q=k.find(x.shift(),d,w),d=q.expr?k.filter(q.expr,q.set)[0]:q.set[0]);if(d){q=g?{expr:x.pop(),set:p(g)}:k.find(x.pop(),x.length===1&&(x[0]==="~"||x[0]==="+")&&d.parentNode?d.parentNode:d,w),j=q.expr?k.filter(q.expr,q.set):q.set,x.length>0?n=p(j):u=!1;while(x.length)r=x.pop(),s=r,l.relative[r]?s=x.pop():r="",s==null&&(s=d),l.relative[r](n,s,w)}else n=x=[]}n||(n=j),n||k.error(r||b);if(e.call(n)==="[object Array]")if(!u)f.push.apply(f,n);else if(d&&d.nodeType===1)for(t=0;n[t]!=null;t++)n[t]&&(n[t]===!0||n[t].nodeType===1&&k.contains(d,n[t]))&&f.push(j[t]);else for(t=0;n[t]!=null;t++)n[t]&&n[t].nodeType===1&&f.push(j[t]);else p(n,f);o&&(k(o,h,f,g),k.uniqueSort(f));return f};k.uniqueSort=function(a){if(r){g=h,a.sort(r);if(g)for(var b=1;b<a.length;b++)a[b]===a[b-1]&&a.splice(b--,1)}return a},k.matches=function(a,b){return k(a,null,null,b)},k.matchesSelector=function(a,b){return k(b,null,null,[a]).length>0},k.find=function(a,b,c){var d;if(!a)return[];for(var e=0,f=l.order.length;e<f;e++){var g,h=l.order[e];if(g=l.leftMatch[h].exec(a)){var j=g[1];g.splice(1,1);if(j.substr(j.length-1)!=="\\"){g[1]=(g[1]||"").replace(i,""),d=l.find[h](g,b,c);if(d!=null){a=a.replace(l.match[h],"");break}}}}d||(d=typeof b.getElementsByTagName!="undefined"?b.getElementsByTagName("*"):[]);return{set:d,expr:a}},k.filter=function(a,c,d,e){var f,g,h=a,i=[],j=c,m=c&&c[0]&&k.isXML(c[0]);while(a&&c.length){for(var n in l.filter)if((f=l.leftMatch[n].exec(a))!=null&&f[2]){var o,p,q=l.filter[n],r=f[1];g=!1,f.splice(1,1);if(r.substr(r.length-1)==="\\")continue;j===i&&(i=[]);if(l.preFilter[n]){f=l.preFilter[n](f,j,d,i,e,m);if(!f)g=o=!0;else if(f===!0)continue}if(f)for(var s=0;(p=j[s])!=null;s++)if(p){o=q(p,f,s,j);var t=e^!!o;d&&o!=null?t?g=!0:j[s]=!1:t&&(i.push(p),g=!0)}if(o!==b){d||(j=i),a=a.replace(l.match[n],"");if(!g)return[];break}}if(a===h)if(g==null)k.error(a);else break;h=a}return j},k.error=function(a){throw"Syntax error, unrecognized expression: "+a};var l=k.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(a){return a.getAttribute("href")},type:function(a){return a.getAttribute("type")}},relative:{"+":function(a,b){var c=typeof b=="string",d=c&&!j.test(b),e=c&&!d;d&&(b=b.toLowerCase());for(var f=0,g=a.length,h;f<g;f++)if(h=a[f]){while((h=h.previousSibling)&&h.nodeType!==1);a[f]=e||h&&h.nodeName.toLowerCase()===b?h||!1:h===b}e&&k.filter(b,a,!0)},">":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!j.test(b)){b=b.toLowerCase();for(;e<f;e++){c=a[e];if(c){var g=c.parentNode;a[e]=g.nodeName.toLowerCase()===b?g:!1}}}else{for(;e<f;e++)c=a[e],c&&(a[e]=d?c.parentNode:c.parentNode===b);d&&k.filter(b,a,!0)}},"":function(a,b,c){var e,f=d++,g=u;typeof b=="string"&&!j.test(b)&&(b=b.toLowerCase(),e=b,g=t),g("parentNode",b,f,a,e,c)},"~":function(a,b,c){var e,f=d++,g=u;typeof b=="string"&&!j.test(b)&&(b=b.toLowerCase(),e=b,g=t),g("previousSibling",b,f,a,e,c)}},find:{ID:function(a,b,c){if(typeof b.getElementById!="undefined"&&!c){var d=b.getElementById(a[1]);return d&&d.parentNode?[d]:[]}},NAME:function(a,b){if(typeof b.getElementsByName!="undefined"){var c=[],d=b.getElementsByName(a[1]);for(var e=0,f=d.length;e<f;e++)d[e].getAttribute("name")===a[1]&&c.push(d[e]);return c.length===0?null:c}},TAG:function(a,b){if(typeof b.getElementsByTagName!="undefined")return b.getElementsByTagName(a[1])}},preFilter:{CLASS:function(a,b,c,d,e,f){a=" "+a[1].replace(i,"")+" ";if(f)return a;for(var g=0,h;(h=b[g])!=null;g++)h&&(e^(h.className&&(" "+h.className+" ").replace(/[\t\n\r]/g," ").indexOf(a)>=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(i,"")},TAG:function(a,b){return a[1].replace(i,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||k.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&k.error(a[0]);a[0]=d++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(i,"");!f&&l.attrMap[g]&&(a[1]=l.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(i,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=k(b[3],null,null,c);else{var g=k.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(l.match.POS.test(b[0])||l.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!k(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return b<c[3]-0},gt:function(a,b,c){return b>c[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=l.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||k.getText([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h<i;h++)if(g[h]===a)return!1;return!0}k.error(e)},CHILD:function(a,b){var c=b[1],d=a;switch(c){case"only":case"first":while(d=d.previousSibling)if(d.nodeType===1)return!1;if(c==="first")return!0;d=a;case"last":while(d=d.nextSibling)if(d.nodeType===1)return!1;return!0;case"nth":var e=b[2],f=b[3];if(e===1&&f===0)return!0;var g=b[0],h=a.parentNode;if(h&&(h.sizcache!==g||!a.nodeIndex)){var i=0;for(d=h.firstChild;d;d=d.nextSibling)d.nodeType===1&&(d.nodeIndex=++i);h.sizcache=g}var j=a.nodeIndex-f;return e===0?j===0:j%e===0&&j/e>=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=l.attrHandle[c]?l.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=l.setFilters[e];if(f)return f(a,c,b,d)}}},m=l.match.POS,n=function(a,b){return"\\"+(b-0+1)};for(var o in l.match)l.match[o]=new RegExp(l.match[o].source+/(?![^\[]*\])(?![^\(]*\))/.source),l.leftMatch[o]=new RegExp(/(^(?:.|\r|\n)*?)/.source+l.match[o].source.replace(/\\(\d+)/g,n));var p=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(q){p=function(a,b){var c=0,d=b||[];if(e.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var f=a.length;c<f;c++)d.push(a[c]);else for(;a[c];c++)d.push(a[c]);return d}}var r,s;c.documentElement.compareDocumentPosition?r=function(a,b){if(a===b){g=!0;return 0}if(!a.compareDocumentPosition||!b.compareDocumentPosition)return a.compareDocumentPosition?-1:1;return a.compareDocumentPosition(b)&4?-1:1}:(r=function(a,b){if(a===b){g=!0;return 0}if(a.sourceIndex&&b.sourceIndex)return a.sourceIndex-b.sourceIndex;var c,d,e=[],f=[],h=a.parentNode,i=b.parentNode,j=h;if(h===i)return s(a,b);if(!h)return-1;if(!i)return 1;while(j)e.unshift(j),j=j.parentNode;j=i;while(j)f.unshift(j),j=j.parentNode;c=e.length,d=f.length;for(var k=0;k<c&&k<d;k++)if(e[k]!==f[k])return s(e[k],f[k]);return k===c?s(a,f[k],-1):s(e[k],b,1)},s=function(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}),k.getText=function(a){var b="",c;for(var d=0;a[d];d++)c=a[d],c.nodeType===3||c.nodeType===4?b+=c.nodeValue:c.nodeType!==8&&(b+=k.getText(c.childNodes));return b},function(){var a=c.createElement("div"),d="script"+(new Date).getTime(),e=c.documentElement;a.innerHTML="<a name='"+d+"'/>",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(l.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},l.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(l.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(l.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=k,b=c.createElement("div"),d="__sizzle__";b.innerHTML="<p class='TEST'></p>";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){k=function(b,e,f,g){e=e||c;if(!g&&!k.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return p(e.getElementsByTagName(b),f);if(h[2]&&l.find.CLASS&&e.getElementsByClassName)return p(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return p([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return p([],f);if(i.id===h[3])return p([i],f)}try{return p(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var m=e,n=e.getAttribute("id"),o=n||d,q=e.parentNode,r=/^\s*[+~]/.test(b);n?o=o.replace(/'/g,"\\$&"):e.setAttribute("id",o),r&&q&&(e=e.parentNode);try{if(!r||q)return p(e.querySelectorAll("[id='"+o+"'] "+b),f)}catch(s){}finally{n||m.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)k[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}k.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!k.isXML(a))try{if(e||!l.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return k(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="<div class='test e'></div><div class='test'></div>";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;l.order.splice(1,0,"CLASS"),l.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?k.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?k.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:k.contains=function(){return!1},k.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var v=function(a,b){var c,d=[],e="",f=b.nodeType?[b]:b;while(c=l.match.PSEUDO.exec(a))e+=c[0],a=a.replace(l.match.PSEUDO,"");a=l.relative[a]?a+"*":a;for(var g=0,h=f.length;g<h;g++)k(a,f[g],d);return k.filter(e,d)};f.find=k,f.expr=k.selectors,f.expr[":"]=f.expr.filters,f.unique=k.uniqueSort,f.text=k.getText,f.isXMLDoc=k.isXML,f.contains=k.contains}();var P=/Until$/,Q=/^(?:parents|prevUntil|prevAll)/,R=/,/,S=/^.[^:#\[\.,]*$/,T=Array.prototype.slice,U=f.expr.match.POS,V={children:!0,contents:!0,next:!0,prev:!0};f.fn.extend({find:function(a){var b=this,c,d;if(typeof a!="string")return f(a).filter(function(){for(c=0,d=b.length;c<d;c++)if(f.contains(b[c],this))return!0});var e=this.pushStack("","find",a),g,h,i;for(c=0,d=this.length;c<d;c++){g=e.length,f.find(a,this[c],e);if(c>0)for(h=g;h<e.length;h++)for(i=0;i<g;i++)if(e[i]===e[h]){e.splice(h--,1);break}}return e},has:function(a){var b=f(a);return this.filter(function(){for(var a=0,c=b.length;a<c;a++)if(f.contains(this,b[a]))return!0})},not:function(a){return this.pushStack(X(this,a,!1),"not",a)},filter:function(a){return this.pushStack(X(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h,i,j={},k=1;if(g&&a.length){for(d=0,e=a.length;d<e;d++)i=a[d],j[i]||(j[i]=U.test(i)?f(i,b||this.context):i);while(g&&g.ownerDocument&&g!==b){for(i in j)h=j[i],(h.jquery?h.index(g)>-1:f(g).is(h))&&c.push({selector:i,elem:g,level:k});g=g.parentNode,k++}}return c}var l=U.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d<e;d++){g=this[d];while(g){if(l?l.index(g)>-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a||typeof a=="string")return f.inArray(this[0],a?f(a):this.parent().children());return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(W(c[0])||W(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c),g=T.call(arguments);P.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!V[a]?f.unique(e):e,(this.length>1||R.test(d))&&Q.test(a)&&(e=e.reverse());return this.pushStack(e,a,g.join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var Y=/ jQuery\d+="(?:\d+|null)"/g,Z=/^\s+/,$=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,_=/<([\w:]+)/,ba=/<tbody/i,bb=/<|&#?\w+;/,bc=/<(?:script|object|embed|option|style)/i,bd=/checked\s*(?:[^=]|=\s*.checked.)/i,be=/\/(java|ecma)script/i,bf=/^\s*<!(?:\[CDATA\[|\-\-)/,bg={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]};bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div<div>","</div>"]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){f(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f(arguments[0]).toArray());return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Y,""):null;if(typeof a=="string"&&!bc.test(a)&&(f.support.leadingWhitespace||!Z.test(a))&&!bg[(_.exec(a)||["",""])[1].toLowerCase()]){a=a.replace($,"<$1></$2>");try{for(var c=0,d=this.length;c<d;c++)this[c].nodeType===1&&(f.cleanData(this[c].getElementsByTagName("*")),this[c].innerHTML=a)}catch(e){this.empty().append(a)}}else f.isFunction(a)?this.each(function(b){var c=f(this);c.html(a.call(this,b,c.html()))}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&this[0].parentNode){if(f.isFunction(a))return this.each(function(b){var c=f(this),d=c.html();c.replaceWith(a.call(this,b,d))});typeof a!="string"&&(a=f(a).detach());return this.each(function(){var b=this.nextSibling,c=this.parentNode;f(this).remove(),b?f(b).before(a):f(c).append(a)})}return this.length?this.pushStack(f(f.isFunction(a)?a():a),"replaceWith",a):this},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){var e,g,h,i,j=a[0],k=[];if(!f.support.checkClone&&arguments.length===3&&typeof j=="string"&&bd.test(j))return this.each(function(){f(this).domManip(a,c,d,!0)});if(f.isFunction(j))return this.each(function(e){var g=f(this);a[0]=j.call(this,e,c?g.html():b),g.domManip(a,c,d)});if(this[0]){i=j&&j.parentNode,f.support.parentNode&&i&&i.nodeType===11&&i.childNodes.length===this.length?e={fragment:i}:e=f.buildFragment(a,this,k),h=e.fragment,h.childNodes.length===1?g=h=h.firstChild:g=h.firstChild;if(g){c=c&&f.nodeName(g,"tr");for(var l=0,m=this.length,n=m-1;l<m;l++)d.call(c?bh(this[l],g):this[l],e.cacheable||m>1&&l<n?f.clone(h,!0,!0):h)}k.length&&f.each(k,bn)}return this}}),f.buildFragment=function(a,b,d){var e,g,h,i=b&&b[0]?b[0].ownerDocument||b[0]:c;a.length===1&&typeof a[0]=="string"&&a[0].length<512&&i===c&&a[0].charAt(0)==="<"&&!bc.test(a[0])&&(f.support.checkClone||!bd.test(a[0]))&&(g=!0,h=f.fragments[a[0]],h&&h!==1&&(e=h)),e||(e=i.createDocumentFragment(),f.clean(a,i,e,d)),g&&(f.fragments[a[0]]=h?e:1);return{fragment:e,cacheable:g}},f.fragments={},f.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){f.fn[a]=function(c){var d=[],e=f(c),g=this.length===1&&this[0].parentNode;if(g&&g.nodeType===11&&g.childNodes.length===1&&e.length===1){e[b](this[0]);return this}for(var h=0,i=e.length;h<i;h++){var j=(h>0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d=a.cloneNode(!0),e,g,h;if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bj(a,d),e=bk(a),g=bk(d);for(h=0;e[h];++h)bj(e[h],g[h])}if(b){bi(a,d);if(c){e=bk(a),g=bk(d);for(h=0;e[h];++h)bi(e[h],g[h])}}return d},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||
+b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!bb.test(k))k=b.createTextNode(k);else{k=k.replace($,"<$1></$2>");var l=(_.exec(k)||["",""])[1].toLowerCase(),m=bg[l]||bg._default,n=m[0],o=b.createElement("div");o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=ba.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]==="<table>"&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&Z.test(k)&&o.insertBefore(b.createTextNode(Z.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i<r;i++)bm(k[i]);else bm(k);k.nodeType?h.push(k):h=f.merge(h,k)}if(d){g=function(a){return!a.type||be.test(a.type)};for(j=0;h[j];j++)if(e&&f.nodeName(h[j],"script")&&(!h[j].type||h[j].type.toLowerCase()==="text/javascript"))e.push(h[j].parentNode?h[j].parentNode.removeChild(h[j]):h[j]);else{if(h[j].nodeType===1){var s=f.grep(h[j].getElementsByTagName("script"),g);h.splice.apply(h,[j+1,0].concat(s))}d.appendChild(h[j])}}return h},cleanData:function(a){var b,c,d=f.cache,e=f.expando,g=f.event.special,h=f.support.deleteExpando;for(var i=0,j;(j=a[i])!=null;i++){if(j.nodeName&&f.noData[j.nodeName.toLowerCase()])continue;c=j[f.expando];if(c){b=d[c]&&d[c][e];if(b&&b.events){for(var k in b.events)g[k]?f.event.remove(j,k):f.removeEvent(j,k,b.handle);b.handle&&(b.handle.elem=null)}h?delete j[f.expando]:j.removeAttribute&&j.removeAttribute(f.expando),delete d[c]}}}});var bo=/alpha\([^)]*\)/i,bp=/opacity=([^)]*)/,bq=/-([a-z])/ig,br=/([A-Z]|^ms)/g,bs=/^-?\d+(?:px)?$/i,bt=/^-?\d/,bu=/^[+\-]=/,bv=/[^+\-\.\de]+/g,bw={position:"absolute",visibility:"hidden",display:"block"},bx=["Left","Right"],by=["Top","Bottom"],bz,bA,bB,bC=function(a,b){return b.toUpperCase()};f.fn.css=function(a,c){if(arguments.length===2&&c===b)return this;return f.access(this,a,c,!0,function(a,c,d){return d!==b?f.style(a,c,d):f.css(a,c)})},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bz(a,"opacity","opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{zIndex:!0,fontWeight:!0,opacity:!0,zoom:!0,lineHeight:!0,widows:!0,orphans:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d;if(h==="number"&&isNaN(d)||d==null)return;h==="string"&&bu.test(d)&&(d=+d.replace(bv,"")+parseFloat(f.css(a,c))),h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(bz)return bz(a,c)},swap:function(a,b,c){var d={};for(var e in b)d[e]=a.style[e],a.style[e]=b[e];c.call(a);for(e in b)a.style[e]=d[e]},camelCase:function(a){return a.replace(bq,bC)}}),f.curCSS=f.css,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){var e;if(c){a.offsetWidth!==0?e=bD(a,b,d):f.swap(a,bw,function(){e=bD(a,b,d)});if(e<=0){e=bz(a,b,b),e==="0px"&&bB&&(e=bB(a,b,b));if(e!=null)return e===""||e==="auto"?"0px":e}if(e<0||e==null){e=a.style[b];return e===""||e==="auto"?"0px":e}return typeof e=="string"?e:e+"px"}},set:function(a,b){if(!bs.test(b))return b;b=parseFloat(b);if(b>=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bp.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle;c.zoom=1;var e=f.isNaN(b)?"":"alpha(opacity="+b*100+")",g=d&&d.filter||c.filter||"";c.filter=bo.test(g)?g.replace(bo,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bz(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(bA=function(a,c){var d,e,g;c=c.replace(br,"-$1").toLowerCase();if(!(e=a.ownerDocument.defaultView))return b;if(g=e.getComputedStyle(a,null))d=g.getPropertyValue(c),d===""&&!f.contains(a.ownerDocument.documentElement,a)&&(d=f.style(a,c));return d}),c.documentElement.currentStyle&&(bB=function(a,b){var c,d=a.currentStyle&&a.currentStyle[b],e=a.runtimeStyle&&a.runtimeStyle[b],f=a.style;!bs.test(d)&&bt.test(d)&&(c=f.left,e&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":d||0,d=f.pixelLeft+"px",f.left=c,e&&(a.runtimeStyle.left=e));return d===""?"auto":d}),bz=bA||bB,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bE=/%20/g,bF=/\[\]$/,bG=/\r?\n/g,bH=/#.*$/,bI=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bJ=/^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bK=/^(?:about|app|app\-storage|.+\-extension|file|widget):$/,bL=/^(?:GET|HEAD)$/,bM=/^\/\//,bN=/\?/,bO=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,bP=/^(?:select|textarea)/i,bQ=/\s+/,bR=/([?&])_=[^&]*/,bS=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bT=f.fn.load,bU={},bV={},bW,bX;try{bW=e.href}catch(bY){bW=c.createElement("a"),bW.href="",bW=bW.href}bX=bS.exec(bW.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bT)return bT.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("<div>").append(c.replace(bO,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bP.test(this.nodeName)||bJ.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bG,"\r\n")}}):{name:b.name,value:c.replace(bG,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.bind(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?f.extend(!0,a,f.ajaxSettings,b):(b=a,a=f.extend(!0,f.ajaxSettings,b));for(var c in{context:1,url:1})c in b?a[c]=b[c]:c in f.ajaxSettings&&(a[c]=f.ajaxSettings[c]);return a},ajaxSettings:{url:bW,isLocal:bK.test(bX[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":"*/*"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML}},ajaxPrefilter:bZ(bU),ajaxTransport:bZ(bV),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a?4:0;var o,r,u,w=l?ca(d,v,l):b,x,y;if(a>=200&&a<300||a===304){if(d.ifModified){if(x=v.getResponseHeader("Last-Modified"))f.lastModified[k]=x;if(y=v.getResponseHeader("Etag"))f.etag[k]=y}if(a===304)c="notmodified",o=!0;else try{r=cb(d,w),c="success",o=!0}catch(z){c="parsererror",u=z}}else{u=c;if(!c||a)c="error",a<0&&(a=0)}v.status=a,v.statusText=c,o?h.resolveWith(e,[r,c,v]):h.rejectWith(e,[v,c,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.resolveWith(e,[v,c]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f._Deferred(),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bI.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.done,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bH,"").replace(bM,bX[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bQ),d.crossDomain==null&&(r=bS.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bX[1]&&r[2]==bX[2]&&(r[3]||(r[1]==="http:"?80:443))==(bX[3]||(bX[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),b$(bU,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bL.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bN.test(d.url)?"&":"?")+d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bR,"$1_="+x);d.url=y+(y===d.url?(bN.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", */*; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=b$(bV,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){status<2?w(-1,z):f.error(z)}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)b_(g,a[g],c,e);return d.join("&").replace(bE,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cc=f.now(),cd=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cc++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(cd.test(b.url)||e&&cd.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(cd,l),b.url===j&&(e&&(k=k.replace(cd,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var ce=a.ActiveXObject?function(){for(var a in cg)cg[a](0,1)}:!1,cf=0,cg;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ch()||ci()}:ch,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,ce&&delete cg[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cf,ce&&(cg||(cg={},f(a).unload(ce)),cg[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cj={},ck,cl,cm=/^(?:toggle|show|hide)$/,cn=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,co,cp=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cq,cr=a.webkitRequestAnimationFrame||a.mozRequestAnimationFrame||a.oRequestAnimationFrame;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cu("show",3),a,b,c);for(var g=0,h=this.length;g<h;g++)d=this[g],d.style&&(e=d.style.display,!f._data(d,"olddisplay")&&e==="none"&&(e=d.style.display=""),e===""&&f.css(d,"display")==="none"&&f._data(d,"olddisplay",cv(d.nodeName)));for(g=0;g<h;g++){d=this[g];if(d.style){e=d.style.display;if(e===""||e==="none")d.style.display=f._data(d,"olddisplay")||""}}return this},hide:function(a,b,c){if(a||a===0)return this.animate(cu("hide",3),a,b,c);for(var d=0,e=this.length;d<e;d++)if(this[d].style){var g=f.css(this[d],"display");g!=="none"&&!f._data(this[d],"olddisplay")&&f._data(this[d],"olddisplay",g)}for(d=0;d<e;d++)this[d].style&&(this[d].style.display="none");return this},_toggle:f.fn.toggle,toggle:function(a,b,c){var d=typeof a=="boolean";f.isFunction(a)&&f.isFunction(b)?this._toggle.apply(this,arguments):a==null||d?this.each(function(){var b=d?a:f(this).is(":hidden");f(this)[b?"show":"hide"]()}):this.animate(cu("toggle",3),a,b,c);return this},fadeTo:function(a,b,c,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=f.speed(b,c,d);if(f.isEmptyObject(a))return this.each(e.complete,[!1]);a=f.extend({},a);return this[e.queue===!1?"each":"queue"](function(){e.queue===!1&&f._mark(this);var b=f.extend({},e),c=this.nodeType===1,d=c&&f(this).is(":hidden"),g,h,i,j,k,l,m,n,o;b.animatedProperties={};for(i in a){g=f.camelCase(i),i!==g&&(a[g]=a[i],delete a[i]),h=a[g],f.isArray(h)?(b.animatedProperties[g]=h[1],h=a[g]=h[0]):b.animatedProperties[g]=b.specialEasing&&b.specialEasing[g]||b.easing||"swing";if(h==="hide"&&d||h==="show"&&!d)return b.complete.call(this);c&&(g==="height"||g==="width")&&(b.overflow=[this.style.overflow,this.style.overflowX,this.style.overflowY],f.css(this,"display")==="inline"&&f.css(this,"float")==="none"&&(f.support.inlineBlockNeedsLayout?(j=cv(this.nodeName),j==="inline"?this.style.display="inline-block":(this.style.display="inline",this.style.zoom=1)):this.style.display="inline-block"))}b.overflow!=null&&(this.style.overflow="hidden");for(i in a)k=new f.fx(this,b,i),h=a[i],cm.test(h)?k[h==="toggle"?d?"show":"hide":h]():(l=cn.exec(h),m=k.cur(),l?(n=parseFloat(l[2]),o=l[3]||(f.cssNumber[i]?"":"px"),o!=="px"&&(f.style(this,i,(n||1)+o),m=(n||1)/k.cur()*m,f.style(this,i,m+o)),l[1]&&(n=(l[1]==="-="?-1:1)*n+m),k.custom(m,n,o)):k.custom(m,h,""));return!0})},stop:function(a,b){a&&this.queue([]),this.each(function(){var a=f.timers,c=a.length;b||f._unmark(!0,this);while(c--)a[c].elem===this&&(b&&a[c](!0),a.splice(c,1))}),b||this.dequeue();return this}}),f.each({slideDown:cu("show",1),slideUp:cu("hide",1),slideToggle:cu("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){f.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),f.extend({speed:function(a,b,c){var d=a&&typeof a=="object"?f.extend({},a):{complete:c||!c&&b||f.isFunction(a)&&a,duration:a,easing:c&&b||b&&!f.isFunction(b)&&b};d.duration=f.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in f.fx.speeds?f.fx.speeds[d.duration]:f.fx.speeds._default,d.old=d.complete,d.complete=function(a){d.queue!==!1?f.dequeue(this):a!==!1&&f._unmark(this),f.isFunction(d.old)&&d.old.call(this)};return d},easing:{linear:function(a,b,c,d){return c+d*a},swing:function(a,b,c,d){return(-Math.cos(a*Math.PI)/2+.5)*d+c}},timers:[],fx:function(a,b,c){this.options=b,this.elem=a,this.prop=c,b.orig=b.orig||{}}}),f.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this),(f.fx.step[this.prop]||f.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a,b=f.css(this.elem,this.prop);return isNaN(a=parseFloat(b))?!b||b==="auto"?0:b:a},custom:function(a,b,c){function h(a){return d.step(a)}var d=this,e=f.fx,g;this.startTime=cq||cs(),this.start=a,this.end=b,this.unit=c||this.unit||(f.cssNumber[this.prop]?"":"px"),this.now=this.start,this.pos=this.state=0,h.elem=this.elem,h()&&f.timers.push(h)&&!co&&(cr?(co=1,g=function(){co&&(cr(g),e.tick())},cr(g)):co=setInterval(e.tick,e.interval))},show:function(){this.options.orig[this.prop]=f.style(this.elem,this.prop),this.options.show=!0,this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur()),f(this.elem).show()},hide:function(){this.options.orig[this.prop]=f.style(this.elem,this.prop),this.options.hide=!0,this.custom(this.cur(),0)},step:function(a){var b=cq||cs(),c=!0,d=this.elem,e=this.options,g,h;if(a||b>=e.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),e.animatedProperties[this.prop]=!0;for(g in e.animatedProperties)e.animatedProperties[g]!==!0&&(c=!1);if(c){e.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){d.style["overflow"+b]=e.overflow[a]}),e.hide&&f(d).hide();if(e.hide||e.show)for(var i in e.animatedProperties)f.style(d,i,e.orig[i]);e.complete.call(d)}return!1}e.duration==Infinity?this.now=b:(h=b-this.startTime,this.state=h/e.duration,this.pos=f.easing[e.animatedProperties[this.prop]](this.state,h,0,1,e.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){for(var a=f.timers,b=0;b<a.length;++b)a[b]()||a.splice(b--,1);a.length||f.fx.stop()},interval:13,stop:function(){clearInterval(co),co=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){f.style(a.elem,"opacity",a.now)},_default:function(a){a.elem.style&&a.elem.style[a.prop]!=null?a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit:a.elem[a.prop]=a.now}}}),f.expr&&f.expr.filters&&(f.expr.filters.animated=function(a){return f.grep(f.timers,function(b){return a===b.elem}).length});var cw=/^t(?:able|d|h)$/i,cx=/^(?:body|html)$/i;"getBoundingClientRect"in c.documentElement?f.fn.offset=function(a){var b=this[0],c;if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);try{c=b.getBoundingClientRect()}catch(d){}var e=b.ownerDocument,g=e.documentElement;if(!c||!f.contains(g,b))return c?{top:c.top,left:c.left}:{top:0,left:0};var h=e.body,i=cy(e),j=g.clientTop||h.clientTop||0,k=g.clientLeft||h.clientLeft||0,l=i.pageYOffset||f.support.boxModel&&g.scrollTop||h.scrollTop,m=i.pageXOffset||f.support.boxModel&&g.scrollLeft||h.scrollLeft,n=c.top+l-j,o=c.left+m-k;return{top:n,left:o}}:f.fn.offset=function(a){var b=this[0];if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);f.offset.initialize();var c,d=b.offsetParent,e=b,g=b.ownerDocument,h=g.documentElement,i=g.body,j=g.defaultView,k=j?j.getComputedStyle(b,null):b.currentStyle,l=b.offsetTop,m=b.offsetLeft;while((b=b.parentNode)&&b!==i&&b!==h){if(f.offset.supportsFixedPosition&&k.position==="fixed")break;c=j?j.getComputedStyle(b,null):b.currentStyle,l-=b.scrollTop,m-=b.scrollLeft,b===d&&(l+=b.offsetTop,m+=b.offsetLeft,f.offset.doesNotAddBorder&&(!f.offset.doesAddBorderForTableAndCells||!cw.test(b.nodeName))&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),e=d,d=b.offsetParent),f.offset.subtractsBorderForOverflowNotVisible&&c.overflow!=="visible"&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),k=c}if(k.position==="relative"||k.position==="static")l+=i.offsetTop,m+=i.offsetLeft;f.offset.supportsFixedPosition&&k.position==="fixed"&&(l+=Math.max(h.scrollTop,i.scrollTop),m+=Math.max(h.scrollLeft,i.scrollLeft));return{top:l,left:m}},f.offset={initialize:function(){var a=c.body,b=c.createElement("div"),d,e,g,h,i=parseFloat(f.css(a,"marginTop"))||0,j="<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";f.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"}),b.innerHTML=j,a.insertBefore(b,a.firstChild),d=b.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,this.doesNotAddBorder=e.offsetTop!==5,this.doesAddBorderForTableAndCells=h.offsetTop===5,e.style.position="fixed",e.style.top="20px",this.supportsFixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",this.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==i,a.removeChild(b),f.offset.initialize=f.noop},bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.offset.initialize(),f.offset.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cy(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cy(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){return this[0]?parseFloat(f.css(this[0],d,"padding")):null},f.fn["outer"+c]=function(a){return this[0]?parseFloat(f.css(this[0],d,a?"margin":"border")):null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c];return e.document.compatMode==="CSS1Compat"&&g||e.document.body["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var h=f.css(e,d),i=parseFloat(h);return f.isNaN(i)?h:i}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f})(window);
\ No newline at end of file
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/jquery.ba-hashchange.min.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/jquery.ba-hashchange.min.js
new file mode 100644
index 0000000..3c607ba
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/jquery.ba-hashchange.min.js
@@ -0,0 +1,9 @@
+/*
+ * jQuery hashchange event - v1.3 - 7/21/2010
+ * http://benalman.com/projects/jquery-hashchange-plugin/
+ * 
+ * Copyright (c) 2010 "Cowboy" Ben Alman
+ * Dual licensed under the MIT and GPL licenses.
+ * http://benalman.com/about/license/
+ */
+(function($,e,b){var c="hashchange",h=document,f,g=$.event.special,i=h.documentMode,d="on"+c in e&&(i===b||i>7);function a(j){j=j||location.href;return"#"+j.replace(/^[^#]*#?(.*)$/,"$1")}$.fn[c]=function(j){return j?this.bind(c,j):this.trigger(c)};$.fn[c].delay=50;g[c]=$.extend(g[c],{setup:function(){if(d){return false}$(f.start)},teardown:function(){if(d){return false}$(f.stop)}});f=(function(){var j={},p,m=a(),k=function(q){return q},l=k,o=k;j.start=function(){p||n()};j.stop=function(){p&&clearTimeout(p);p=b};function n(){var r=a(),q=o(m);if(r!==m){l(m=r,q);$(e).trigger(c)}else{if(q!==m){location.href=location.href.replace(/#.*/,"")+q}}p=setTimeout(n,$.fn[c].delay)}$.browser.msie&&!d&&(function(){var q,r;j.start=function(){if(!q){r=$.fn[c].src;r=r&&r+a();q=$('<iframe tabindex="-1" title="empty"/>').hide().one("load",function(){r||l(a());n()}).attr("src",r||"javascript:0").insertAfter("body")[0].contentWindow;h.onpropertychange=function(){try{if(event.propertyName==="title"){q.document.title=h.title}}catch(s){}}}};j.stop=k;o=function(){return a(q.location.href)};l=function(v,s){var u=q.document,t=$.fn[c].domain;if(v!==s){u.title=h.title;u.open();t&&u.write('<script>document.domain="'+t+'"<\/script>');u.close();q.location.hash=v}}})();return j})()})(jQuery,this);
\ No newline at end of file
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/json2.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/json2.js
new file mode 100644
index 0000000..11e1f0a
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/json2.js
@@ -0,0 +1,480 @@
+/*

+    http://www.JSON.org/json2.js

+    2011-02-23

+

+    Public Domain.

+

+    NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.

+

+    See http://www.JSON.org/js.html

+

+

+    This code should be minified before deployment.

+    See http://javascript.crockford.com/jsmin.html

+

+    USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO

+    NOT CONTROL.

+

+

+    This file creates a global JSON object containing two methods: stringify

+    and parse.

+

+        JSON.stringify(value, replacer, space)

+            value       any JavaScript value, usually an object or array.

+

+            replacer    an optional parameter that determines how object

+                        values are stringified for objects. It can be a

+                        function or an array of strings.

+

+            space       an optional parameter that specifies the indentation

+                        of nested structures. If it is omitted, the text will

+                        be packed without extra whitespace. If it is a number,

+                        it will specify the number of spaces to indent at each

+                        level. If it is a string (such as '\t' or '&nbsp;'),

+                        it contains the characters used to indent at each level.

+

+            This method produces a JSON text from a JavaScript value.

+

+            When an object value is found, if the object contains a toJSON

+            method, its toJSON method will be called and the result will be

+            stringified. A toJSON method does not serialize: it returns the

+            value represented by the name/value pair that should be serialized,

+            or undefined if nothing should be serialized. The toJSON method

+            will be passed the key associated with the value, and this will be

+            bound to the value

+

+            For example, this would serialize Dates as ISO strings.

+

+                Date.prototype.toJSON = function (key) {

+                    function f(n) {

+                        // Format integers to have at least two digits.

+                        return n < 10 ? '0' + n : n;

+                    }

+

+                    return this.getUTCFullYear()   + '-' +

+                         f(this.getUTCMonth() + 1) + '-' +

+                         f(this.getUTCDate())      + 'T' +

+                         f(this.getUTCHours())     + ':' +

+                         f(this.getUTCMinutes())   + ':' +

+                         f(this.getUTCSeconds())   + 'Z';

+                };

+

+            You can provide an optional replacer method. It will be passed the

+            key and value of each member, with this bound to the containing

+            object. The value that is returned from your method will be

+            serialized. If your method returns undefined, then the member will

+            be excluded from the serialization.

+

+            If the replacer parameter is an array of strings, then it will be

+            used to select the members to be serialized. It filters the results

+            such that only members with keys listed in the replacer array are

+            stringified.

+

+            Values that do not have JSON representations, such as undefined or

+            functions, will not be serialized. Such values in objects will be

+            dropped; in arrays they will be replaced with null. You can use

+            a replacer function to replace those with JSON values.

+            JSON.stringify(undefined) returns undefined.

+

+            The optional space parameter produces a stringification of the

+            value that is filled with line breaks and indentation to make it

+            easier to read.

+

+            If the space parameter is a non-empty string, then that string will

+            be used for indentation. If the space parameter is a number, then

+            the indentation will be that many spaces.

+

+            Example:

+

+            text = JSON.stringify(['e', {pluribus: 'unum'}]);

+            // text is '["e",{"pluribus":"unum"}]'

+

+

+            text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');

+            // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'

+

+            text = JSON.stringify([new Date()], function (key, value) {

+                return this[key] instanceof Date ?

+                    'Date(' + this[key] + ')' : value;

+            });

+            // text is '["Date(---current time---)"]'

+

+

+        JSON.parse(text, reviver)

+            This method parses a JSON text to produce an object or array.

+            It can throw a SyntaxError exception.

+

+            The optional reviver parameter is a function that can filter and

+            transform the results. It receives each of the keys and values,

+            and its return value is used instead of the original value.

+            If it returns what it received, then the structure is not modified.

+            If it returns undefined then the member is deleted.

+

+            Example:

+

+            // Parse the text. Values that look like ISO date strings will

+            // be converted to Date objects.

+

+            myData = JSON.parse(text, function (key, value) {

+                var a;

+                if (typeof value === 'string') {

+                    a =

+/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);

+                    if (a) {

+                        return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],

+                            +a[5], +a[6]));

+                    }

+                }

+                return value;

+            });

+

+            myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {

+                var d;

+                if (typeof value === 'string' &&

+                        value.slice(0, 5) === 'Date(' &&

+                        value.slice(-1) === ')') {

+                    d = new Date(value.slice(5, -1));

+                    if (d) {

+                        return d;

+                    }

+                }

+                return value;

+            });

+

+

+    This is a reference implementation. You are free to copy, modify, or

+    redistribute.

+*/

+

+/*jslint evil: true, strict: false, regexp: false */

+

+/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,

+    call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,

+    getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,

+    lastIndex, length, parse, prototype, push, replace, slice, stringify,

+    test, toJSON, toString, valueOf

+*/

+

+

+// Create a JSON object only if one does not already exist. We create the

+// methods in a closure to avoid creating global variables.

+

+var JSON;

+if (!JSON) {

+    JSON = {};

+}

+

+(function () {

+    "use strict";

+

+    function f(n) {

+        // Format integers to have at least two digits.

+        return n < 10 ? '0' + n : n;

+    }

+

+    if (typeof Date.prototype.toJSON !== 'function') {

+

+        Date.prototype.toJSON = function (key) {

+

+            return isFinite(this.valueOf()) ?

+                this.getUTCFullYear()     + '-' +

+                f(this.getUTCMonth() + 1) + '-' +

+                f(this.getUTCDate())      + 'T' +

+                f(this.getUTCHours())     + ':' +

+                f(this.getUTCMinutes())   + ':' +

+                f(this.getUTCSeconds())   + 'Z' : null;

+        };

+

+        String.prototype.toJSON      =

+            Number.prototype.toJSON  =

+            Boolean.prototype.toJSON = function (key) {

+                return this.valueOf();

+            };

+    }

+

+    var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,

+        escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,

+        gap,

+        indent,

+        meta = {    // table of character substitutions

+            '\b': '\\b',

+            '\t': '\\t',

+            '\n': '\\n',

+            '\f': '\\f',

+            '\r': '\\r',

+            '"' : '\\"',

+            '\\': '\\\\'

+        },

+        rep;

+

+

+    function quote(string) {

+

+// If the string contains no control characters, no quote characters, and no

+// backslash characters, then we can safely slap some quotes around it.

+// Otherwise we must also replace the offending characters with safe escape

+// sequences.

+

+        escapable.lastIndex = 0;

+        return escapable.test(string) ? '"' + string.replace(escapable, function (a) {

+            var c = meta[a];

+            return typeof c === 'string' ? c :

+                '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);

+        }) + '"' : '"' + string + '"';

+    }

+

+

+    function str(key, holder) {

+

+// Produce a string from holder[key].

+

+        var i,          // The loop counter.

+            k,          // The member key.

+            v,          // The member value.

+            length,

+            mind = gap,

+            partial,

+            value = holder[key];

+

+// If the value has a toJSON method, call it to obtain a replacement value.

+

+        if (value && typeof value === 'object' &&

+                typeof value.toJSON === 'function') {

+            value = value.toJSON(key);

+        }

+

+// If we were called with a replacer function, then call the replacer to

+// obtain a replacement value.

+

+        if (typeof rep === 'function') {

+            value = rep.call(holder, key, value);

+        }

+

+// What happens next depends on the value's type.

+

+        switch (typeof value) {

+        case 'string':

+            return quote(value);

+

+        case 'number':

+

+// JSON numbers must be finite. Encode non-finite numbers as null.

+

+            return isFinite(value) ? String(value) : 'null';

+

+        case 'boolean':

+        case 'null':

+

+// If the value is a boolean or null, convert it to a string. Note:

+// typeof null does not produce 'null'. The case is included here in

+// the remote chance that this gets fixed someday.

+

+            return String(value);

+

+// If the type is 'object', we might be dealing with an object or an array or

+// null.

+

+        case 'object':

+

+// Due to a specification blunder in ECMAScript, typeof null is 'object',

+// so watch out for that case.

+

+            if (!value) {

+                return 'null';

+            }

+

+// Make an array to hold the partial results of stringifying this object value.

+

+            gap += indent;

+            partial = [];

+

+// Is the value an array?

+

+            if (Object.prototype.toString.apply(value) === '[object Array]') {

+

+// The value is an array. Stringify every element. Use null as a placeholder

+// for non-JSON values.

+

+                length = value.length;

+                for (i = 0; i < length; i += 1) {

+                    partial[i] = str(i, value) || 'null';

+                }

+

+// Join all of the elements together, separated with commas, and wrap them in

+// brackets.

+

+                v = partial.length === 0 ? '[]' : gap ?

+                    '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' :

+                    '[' + partial.join(',') + ']';

+                gap = mind;

+                return v;

+            }

+

+// If the replacer is an array, use it to select the members to be stringified.

+

+            if (rep && typeof rep === 'object') {

+                length = rep.length;

+                for (i = 0; i < length; i += 1) {

+                    if (typeof rep[i] === 'string') {

+                        k = rep[i];

+                        v = str(k, value);

+                        if (v) {

+                            partial.push(quote(k) + (gap ? ': ' : ':') + v);

+                        }

+                    }

+                }

+            } else {

+

+// Otherwise, iterate through all of the keys in the object.

+

+                for (k in value) {

+                    if (Object.prototype.hasOwnProperty.call(value, k)) {

+                        v = str(k, value);

+                        if (v) {

+                            partial.push(quote(k) + (gap ? ': ' : ':') + v);

+                        }

+                    }

+                }

+            }

+

+// Join all of the member texts together, separated with commas,

+// and wrap them in braces.

+

+            v = partial.length === 0 ? '{}' : gap ?

+                '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' :

+                '{' + partial.join(',') + '}';

+            gap = mind;

+            return v;

+        }

+    }

+

+// If the JSON object does not yet have a stringify method, give it one.

+

+    if (typeof JSON.stringify !== 'function') {

+        JSON.stringify = function (value, replacer, space) {

+

+// The stringify method takes a value and an optional replacer, and an optional

+// space parameter, and returns a JSON text. The replacer can be a function

+// that can replace values, or an array of strings that will select the keys.

+// A default replacer method can be provided. Use of the space parameter can

+// produce text that is more easily readable.

+

+            var i;

+            gap = '';

+            indent = '';

+

+// If the space parameter is a number, make an indent string containing that

+// many spaces.

+

+            if (typeof space === 'number') {

+                for (i = 0; i < space; i += 1) {

+                    indent += ' ';

+                }

+

+// If the space parameter is a string, it will be used as the indent string.

+

+            } else if (typeof space === 'string') {

+                indent = space;

+            }

+

+// If there is a replacer, it must be a function or an array.

+// Otherwise, throw an error.

+

+            rep = replacer;

+            if (replacer && typeof replacer !== 'function' &&

+                    (typeof replacer !== 'object' ||

+                    typeof replacer.length !== 'number')) {

+                throw new Error('JSON.stringify');

+            }

+

+// Make a fake root object containing our value under the key of ''.

+// Return the result of stringifying the value.

+

+            return str('', {'': value});

+        };

+    }

+

+

+// If the JSON object does not yet have a parse method, give it one.

+

+    if (typeof JSON.parse !== 'function') {

+        JSON.parse = function (text, reviver) {

+

+// The parse method takes a text and an optional reviver function, and returns

+// a JavaScript value if the text is a valid JSON text.

+

+            var j;

+

+            function walk(holder, key) {

+

+// The walk method is used to recursively walk the resulting structure so

+// that modifications can be made.

+

+                var k, v, value = holder[key];

+                if (value && typeof value === 'object') {

+                    for (k in value) {

+                        if (Object.prototype.hasOwnProperty.call(value, k)) {

+                            v = walk(value, k);

+                            if (v !== undefined) {

+                                value[k] = v;

+                            } else {

+                                delete value[k];

+                            }

+                        }

+                    }

+                }

+                return reviver.call(holder, key, value);

+            }

+

+

+// Parsing happens in four stages. In the first stage, we replace certain

+// Unicode characters with escape sequences. JavaScript handles many characters

+// incorrectly, either silently deleting them, or treating them as line endings.

+

+            text = String(text);

+            cx.lastIndex = 0;

+            if (cx.test(text)) {

+                text = text.replace(cx, function (a) {

+                    return '\\u' +

+                        ('0000' + a.charCodeAt(0).toString(16)).slice(-4);

+                });

+            }

+

+// In the second stage, we run the text against regular expressions that look

+// for non-JSON patterns. We are especially concerned with '()' and 'new'

+// because they can cause invocation, and '=' because it can cause mutation.

+// But just to be safe, we want to reject all unexpected forms.

+

+// We split the second stage into 4 regexp operations in order to work around

+// crippling inefficiencies in IE's and Safari's regexp engines. First we

+// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we

+// replace all simple value tokens with ']' characters. Third, we delete all

+// open brackets that follow a colon or comma or that begin the text. Finally,

+// we look to see that the remaining characters are only whitespace or ']' or

+// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.

+

+            if (/^[\],:{}\s]*$/

+                    .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')

+                        .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')

+                        .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {

+

+// In the third stage we use the eval function to compile the text into a

+// JavaScript structure. The '{' operator is subject to a syntactic ambiguity

+// in JavaScript: it can begin a block or an object literal. We wrap the text

+// in parens to eliminate the ambiguity.

+

+                j = eval('(' + text + ')');

+

+// In the optional fourth stage, we recursively walk the new structure, passing

+// each name/value pair to a reviver function for possible transformation.

+

+                return typeof reviver === 'function' ?

+                    walk({'': j}, '') : j;

+            }

+

+// If the text is not JSON parseable, then a SyntaxError is thrown.

+

+            throw new SyntaxError('JSON.parse');

+        };

+    }

+}());

diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/main.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/main.js
new file mode 100644
index 0000000..4a9025a
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/main.js
@@ -0,0 +1,3 @@
+$(document).ready(function() {
+
+});
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/nav.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/nav.js
new file mode 100644
index 0000000..7736b4a
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/nav.js
@@ -0,0 +1,64 @@
+$(document).ready(function() {
+    var docsLoaded = false;
+
+    $(window).hashchange(function(e){
+        e.preventDefault();
+        e.stopPropagation();
+
+        if (location.hash === "#tryit") {
+            $("#main > .content").hide();
+            $("#tryit input").val("").keyup();
+            $("#tryit").fadeIn(400, function() {
+                $("#tryit input").val(".languagesSpoken .lang").keyup();
+            });
+        } else if (location.hash === "#cred") {
+            $("#main > .content").hide();
+            $("#cred").fadeIn(400);
+        } else if (location.hash === '#overview' || location.hash === '') {
+            $("#main > .content").hide();
+            $("#splash").fadeIn(400);
+        } else if (location.hash === '#code' || location.hash === '') {
+            $("#main > .content").hide();
+            $("#code").fadeIn(400);
+        } else if (location.hash.substr(0,5) === "#docs") {
+            function showIt() {
+                var where = window.location.hash.substr(6);
+                if (!where) {
+                    $("#doc").fadeIn(400);
+                } else {
+                    $("#doc").show();
+                    var dst = $("a[name='" + where + "']");
+                    if (dst.length) {
+                        $('html, body').animate({scrollTop:dst.offset().top - 100}, 500);
+                    }
+                }
+            }
+            $("#main > .content").hide();
+            if (!docsLoaded) {
+                $.get("JSONSelect.md").success(function(data) {
+                    var converter = new Showdown.converter();
+                    $("#doc").html(converter.makeHtml(data));
+                    $("#doc a").each(function() {
+                        var n = $(this).attr('href');
+                        if (typeof n === 'string' && n.substr(0,1) === '#') {
+                            $(this).attr('href', "#docs/" + n.substr(1));
+                        }
+                    });
+                    docsLoaded = true;
+                    showIt();
+                }).error(function() {
+                    $("#doc").text("Darnit, error fetching docs...").fadeIn(400);
+                });
+            } else {
+                showIt();
+            }
+        } else {
+        }
+        return false;
+    });
+
+    // Trigger the event (useful on page load).
+    if (window.location.hash === "")
+        window.location.hash = "#overview";
+    $(window).hashchange();
+});
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/showdown.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/showdown.js
new file mode 100644
index 0000000..43920d9
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/showdown.js
@@ -0,0 +1,1302 @@
+//

+// showdown.js -- A javascript port of Markdown.

+//

+// Copyright (c) 2007 John Fraser.

+//

+// Original Markdown Copyright (c) 2004-2005 John Gruber

+//   <http://daringfireball.net/projects/markdown/>

+//

+// Redistributable under a BSD-style open source license.

+// See license.txt for more information.

+//

+// The full source distribution is at:

+//

+//				A A L

+//				T C A

+//				T K B

+//

+//   <http://www.attacklab.net/>

+//

+

+//

+// Wherever possible, Showdown is a straight, line-by-line port

+// of the Perl version of Markdown.

+//

+// This is not a normal parser design; it's basically just a

+// series of string substitutions.  It's hard to read and

+// maintain this way,  but keeping Showdown close to the original

+// design makes it easier to port new features.

+//

+// More importantly, Showdown behaves like markdown.pl in most

+// edge cases.  So web applications can do client-side preview

+// in Javascript, and then build identical HTML on the server.

+//

+// This port needs the new RegExp functionality of ECMA 262,

+// 3rd Edition (i.e. Javascript 1.5).  Most modern web browsers

+// should do fine.  Even with the new regular expression features,

+// We do a lot of work to emulate Perl's regex functionality.

+// The tricky changes in this file mostly have the "attacklab:"

+// label.  Major or self-explanatory changes don't.

+//

+// Smart diff tools like Araxis Merge will be able to match up

+// this file with markdown.pl in a useful way.  A little tweaking

+// helps: in a copy of markdown.pl, replace "#" with "//" and

+// replace "$text" with "text".  Be sure to ignore whitespace

+// and line endings.

+//

+

+

+//

+// Showdown usage:

+//

+//   var text = "Markdown *rocks*.";

+//

+//   var converter = new Showdown.converter();

+//   var html = converter.makeHtml(text);

+//

+//   alert(html);

+//

+// Note: move the sample code to the bottom of this

+// file before uncommenting it.

+//

+

+

+//

+// Showdown namespace

+//

+var Showdown = {};

+

+//

+// converter

+//

+// Wraps all "globals" so that the only thing

+// exposed is makeHtml().

+//

+Showdown.converter = function() {

+

+//

+// Globals:

+//

+

+// Global hashes, used by various utility routines

+var g_urls;

+var g_titles;

+var g_html_blocks;

+

+// Used to track when we're inside an ordered or unordered list

+// (see _ProcessListItems() for details):

+var g_list_level = 0;

+

+

+this.makeHtml = function(text) {

+//

+// Main function. The order in which other subs are called here is

+// essential. Link and image substitutions need to happen before

+// _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the <a>

+// and <img> tags get encoded.

+//

+

+	// Clear the global hashes. If we don't clear these, you get conflicts

+	// from other articles when generating a page which contains more than

+	// one article (e.g. an index page that shows the N most recent

+	// articles):

+	g_urls = new Array();

+	g_titles = new Array();

+	g_html_blocks = new Array();

+

+	// attacklab: Replace ~ with ~T

+	// This lets us use tilde as an escape char to avoid md5 hashes

+	// The choice of character is arbitray; anything that isn't

+    // magic in Markdown will work.

+	text = text.replace(/~/g,"~T");

+

+	// attacklab: Replace $ with ~D

+	// RegExp interprets $ as a special character

+	// when it's in a replacement string

+	text = text.replace(/\$/g,"~D");

+

+	// Standardize line endings

+	text = text.replace(/\r\n/g,"\n"); // DOS to Unix

+	text = text.replace(/\r/g,"\n"); // Mac to Unix

+

+	// Make sure text begins and ends with a couple of newlines:

+	text = "\n\n" + text + "\n\n";

+

+	// Convert all tabs to spaces.

+	text = _Detab(text);

+

+	// Strip any lines consisting only of spaces and tabs.

+	// This makes subsequent regexen easier to write, because we can

+	// match consecutive blank lines with /\n+/ instead of something

+	// contorted like /[ \t]*\n+/ .

+	text = text.replace(/^[ \t]+$/mg,"");

+

+	// Turn block-level HTML blocks into hash entries

+	text = _HashHTMLBlocks(text);

+

+	// Strip link definitions, store in hashes.

+	text = _StripLinkDefinitions(text);

+

+	text = _RunBlockGamut(text);

+

+	text = _UnescapeSpecialChars(text);

+

+	// attacklab: Restore dollar signs

+	text = text.replace(/~D/g,"$$");

+

+	// attacklab: Restore tildes

+	text = text.replace(/~T/g,"~");

+

+	return text;

+}

+

+

+var _StripLinkDefinitions = function(text) {

+//

+// Strips link definitions from text, stores the URLs and titles in

+// hash references.

+//

+

+	// Link defs are in the form: ^[id]: url "optional title"

+

+	/*

+		var text = text.replace(/

+				^[ ]{0,3}\[(.+)\]:  // id = $1  attacklab: g_tab_width - 1

+				  [ \t]*

+				  \n?				// maybe *one* newline

+				  [ \t]*

+				<?(\S+?)>?			// url = $2

+				  [ \t]*

+				  \n?				// maybe one newline

+				  [ \t]*

+				(?:

+				  (\n*)				// any lines skipped = $3 attacklab: lookbehind removed

+				  ["(]

+				  (.+?)				// title = $4

+				  [")]

+				  [ \t]*

+				)?					// title is optional

+				(?:\n+|$)

+			  /gm,

+			  function(){...});

+	*/

+	var text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|\Z)/gm,

+		function (wholeMatch,m1,m2,m3,m4) {

+			m1 = m1.toLowerCase();

+			g_urls[m1] = _EncodeAmpsAndAngles(m2);  // Link IDs are case-insensitive

+			if (m3) {

+				// Oops, found blank lines, so it's not a title.

+				// Put back the parenthetical statement we stole.

+				return m3+m4;

+			} else if (m4) {

+				g_titles[m1] = m4.replace(/"/g,"&quot;");

+			}

+			

+			// Completely remove the definition from the text

+			return "";

+		}

+	);

+

+	return text;

+}

+

+

+var _HashHTMLBlocks = function(text) {

+	// attacklab: Double up blank lines to reduce lookaround

+	text = text.replace(/\n/g,"\n\n");

+

+	// Hashify HTML blocks:

+	// We only want to do this for block-level HTML tags, such as headers,

+	// lists, and tables. That's because we still want to wrap <p>s around

+	// "paragraphs" that are wrapped in non-block-level tags, such as anchors,

+	// phrase emphasis, and spans. The list of tags we're looking for is

+	// hard-coded:

+	var block_tags_a = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del"

+	var block_tags_b = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math"

+

+	// First, look for nested blocks, e.g.:

+	//   <div>

+	//     <div>

+	//     tags for inner block must be indented.

+	//     </div>

+	//   </div>

+	//

+	// The outermost tags must start at the left margin for this to match, and

+	// the inner nested divs must be indented.

+	// We need to do this before the next, more liberal match, because the next

+	// match will start at the first `<div>` and stop at the first `</div>`.

+

+	// attacklab: This regex can be expensive when it fails.

+	/*

+		var text = text.replace(/

+		(						// save in $1

+			^					// start of line  (with /m)

+			<($block_tags_a)	// start tag = $2

+			\b					// word break

+								// attacklab: hack around khtml/pcre bug...

+			[^\r]*?\n			// any number of lines, minimally matching

+			</\2>				// the matching end tag

+			[ \t]*				// trailing spaces/tabs

+			(?=\n+)				// followed by a newline

+		)						// attacklab: there are sentinel newlines at end of document

+		/gm,function(){...}};

+	*/

+	text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)\b[^\r]*?\n<\/\2>[ \t]*(?=\n+))/gm,hashElement);

+

+	//

+	// Now match more liberally, simply from `\n<tag>` to `</tag>\n`

+	//

+

+	/*

+		var text = text.replace(/

+		(						// save in $1

+			^					// start of line  (with /m)

+			<($block_tags_b)	// start tag = $2

+			\b					// word break

+								// attacklab: hack around khtml/pcre bug...

+			[^\r]*?				// any number of lines, minimally matching

+			.*</\2>				// the matching end tag

+			[ \t]*				// trailing spaces/tabs

+			(?=\n+)				// followed by a newline

+		)						// attacklab: there are sentinel newlines at end of document

+		/gm,function(){...}};

+	*/

+	text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math)\b[^\r]*?.*<\/\2>[ \t]*(?=\n+)\n)/gm,hashElement);

+

+	// Special case just for <hr />. It was easier to make a special case than

+	// to make the other regex more complicated.  

+

+	/*

+		text = text.replace(/

+		(						// save in $1

+			\n\n				// Starting after a blank line

+			[ ]{0,3}

+			(<(hr)				// start tag = $2

+			\b					// word break

+			([^<>])*?			// 

+			\/?>)				// the matching end tag

+			[ \t]*

+			(?=\n{2,})			// followed by a blank line

+		)

+		/g,hashElement);

+	*/

+	text = text.replace(/(\n[ ]{0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,hashElement);

+

+	// Special case for standalone HTML comments:

+

+	/*

+		text = text.replace(/

+		(						// save in $1

+			\n\n				// Starting after a blank line

+			[ ]{0,3}			// attacklab: g_tab_width - 1

+			<!

+			(--[^\r]*?--\s*)+

+			>

+			[ \t]*

+			(?=\n{2,})			// followed by a blank line

+		)

+		/g,hashElement);

+	*/

+	text = text.replace(/(\n\n[ ]{0,3}<!(--[^\r]*?--\s*)+>[ \t]*(?=\n{2,}))/g,hashElement);

+

+	// PHP and ASP-style processor instructions (<?...?> and <%...%>)

+

+	/*

+		text = text.replace(/

+		(?:

+			\n\n				// Starting after a blank line

+		)

+		(						// save in $1

+			[ ]{0,3}			// attacklab: g_tab_width - 1

+			(?:

+				<([?%])			// $2

+				[^\r]*?

+				\2>

+			)

+			[ \t]*

+			(?=\n{2,})			// followed by a blank line

+		)

+		/g,hashElement);

+	*/

+	text = text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,hashElement);

+

+	// attacklab: Undo double lines (see comment at top of this function)

+	text = text.replace(/\n\n/g,"\n");

+	return text;

+}

+

+var hashElement = function(wholeMatch,m1) {

+	var blockText = m1;

+

+	// Undo double lines

+	blockText = blockText.replace(/\n\n/g,"\n");

+	blockText = blockText.replace(/^\n/,"");

+	

+	// strip trailing blank lines

+	blockText = blockText.replace(/\n+$/g,"");

+	

+	// Replace the element text with a marker ("~KxK" where x is its key)

+	blockText = "\n\n~K" + (g_html_blocks.push(blockText)-1) + "K\n\n";

+	

+	return blockText;

+};

+

+var _RunBlockGamut = function(text) {

+//

+// These are all the transformations that form block-level

+// tags like paragraphs, headers, and list items.

+//

+	text = _DoHeaders(text);

+

+	// Do Horizontal Rules:

+	var key = hashBlock("<hr />");

+	text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm,key);

+	text = text.replace(/^[ ]{0,2}([ ]?\-[ ]?){3,}[ \t]*$/gm,key);

+	text = text.replace(/^[ ]{0,2}([ ]?\_[ ]?){3,}[ \t]*$/gm,key);

+

+	text = _DoLists(text);

+	text = _DoCodeBlocks(text);

+	text = _DoBlockQuotes(text);

+

+	// We already ran _HashHTMLBlocks() before, in Markdown(), but that

+	// was to escape raw HTML in the original Markdown source. This time,

+	// we're escaping the markup we've just created, so that we don't wrap

+	// <p> tags around block-level tags.

+	text = _HashHTMLBlocks(text);

+	text = _FormParagraphs(text);

+

+	return text;

+}

+

+

+var _RunSpanGamut = function(text) {

+//

+// These are all the transformations that occur *within* block-level

+// tags like paragraphs, headers, and list items.

+//

+

+	text = _DoCodeSpans(text);

+	text = _EscapeSpecialCharsWithinTagAttributes(text);

+	text = _EncodeBackslashEscapes(text);

+

+	// Process anchor and image tags. Images must come first,

+	// because ![foo][f] looks like an anchor.

+	text = _DoImages(text);

+	text = _DoAnchors(text);

+

+	// Make links out of things like `<http://example.com/>`

+	// Must come after _DoAnchors(), because you can use < and >

+	// delimiters in inline links like [this](<url>).

+	text = _DoAutoLinks(text);

+	text = _EncodeAmpsAndAngles(text);

+	text = _DoItalicsAndBold(text);

+

+	// Do hard breaks:

+	text = text.replace(/  +\n/g," <br />\n");

+

+	return text;

+}

+

+var _EscapeSpecialCharsWithinTagAttributes = function(text) {

+//

+// Within tags -- meaning between < and > -- encode [\ ` * _] so they

+// don't conflict with their use in Markdown for code, italics and strong.

+//

+

+	// Build a regex to find HTML tags and comments.  See Friedl's 

+	// "Mastering Regular Expressions", 2nd Ed., pp. 200-201.

+	var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|<!(--.*?--\s*)+>)/gi;

+

+	text = text.replace(regex, function(wholeMatch) {

+		var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g,"$1`");

+		tag = escapeCharacters(tag,"\\`*_");

+		return tag;

+	});

+

+	return text;

+}

+

+var _DoAnchors = function(text) {

+//

+// Turn Markdown link shortcuts into XHTML <a> tags.

+//

+	//

+	// First, handle reference-style links: [link text] [id]

+	//

+

+	/*

+		text = text.replace(/

+		(							// wrap whole match in $1

+			\[

+			(

+				(?:

+					\[[^\]]*\]		// allow brackets nested one level

+					|

+					[^\[]			// or anything else

+				)*

+			)

+			\]

+

+			[ ]?					// one optional space

+			(?:\n[ ]*)?				// one optional newline followed by spaces

+

+			\[

+			(.*?)					// id = $3

+			\]

+		)()()()()					// pad remaining backreferences

+		/g,_DoAnchors_callback);

+	*/

+	text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeAnchorTag);

+

+	//

+	// Next, inline-style links: [link text](url "optional title")

+	//

+

+	/*

+		text = text.replace(/

+			(						// wrap whole match in $1

+				\[

+				(

+					(?:

+						\[[^\]]*\]	// allow brackets nested one level

+					|

+					[^\[\]]			// or anything else

+				)

+			)

+			\]

+			\(						// literal paren

+			[ \t]*

+			()						// no id, so leave $3 empty

+			<?(.*?)>?				// href = $4

+			[ \t]*

+			(						// $5

+				(['"])				// quote char = $6

+				(.*?)				// Title = $7

+				\6					// matching quote

+				[ \t]*				// ignore any spaces/tabs between closing quote and )

+			)?						// title is optional

+			\)

+		)

+		/g,writeAnchorTag);

+	*/

+	text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()<?(.*?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeAnchorTag);

+

+	//

+	// Last, handle reference-style shortcuts: [link text]

+	// These must come last in case you've also got [link test][1]

+	// or [link test](/foo)

+	//

+

+	/*

+		text = text.replace(/

+		(		 					// wrap whole match in $1

+			\[

+			([^\[\]]+)				// link text = $2; can't contain '[' or ']'

+			\]

+		)()()()()()					// pad rest of backreferences

+		/g, writeAnchorTag);

+	*/

+	text = text.replace(/(\[([^\[\]]+)\])()()()()()/g, writeAnchorTag);

+

+	return text;

+}

+

+var writeAnchorTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) {

+	if (m7 == undefined) m7 = "";

+	var whole_match = m1;

+	var link_text   = m2;

+	var link_id	 = m3.toLowerCase();

+	var url		= m4;

+	var title	= m7;

+	

+	if (url == "") {

+		if (link_id == "") {

+			// lower-case and turn embedded newlines into spaces

+			link_id = link_text.toLowerCase().replace(/ ?\n/g," ");

+		}

+		url = "#"+link_id;

+		

+		if (g_urls[link_id] != undefined) {

+			url = g_urls[link_id];

+			if (g_titles[link_id] != undefined) {

+				title = g_titles[link_id];

+			}

+		}

+		else {

+			if (whole_match.search(/\(\s*\)$/m)>-1) {

+				// Special case for explicit empty url

+				url = "";

+			} else {

+				return whole_match;

+			}

+		}

+	}	

+	

+	url = escapeCharacters(url,"*_");

+	var result = "<a href=\"" + url + "\"";

+	

+	if (title != "") {

+		title = title.replace(/"/g,"&quot;");

+		title = escapeCharacters(title,"*_");

+		result +=  " title=\"" + title + "\"";

+	}

+	

+	result += ">" + link_text + "</a>";

+	

+	return result;

+}

+

+

+var _DoImages = function(text) {

+//

+// Turn Markdown image shortcuts into <img> tags.

+//

+

+	//

+	// First, handle reference-style labeled images: ![alt text][id]

+	//

+

+	/*

+		text = text.replace(/

+		(						// wrap whole match in $1

+			!\[

+			(.*?)				// alt text = $2

+			\]

+

+			[ ]?				// one optional space

+			(?:\n[ ]*)?			// one optional newline followed by spaces

+

+			\[

+			(.*?)				// id = $3

+			\]

+		)()()()()				// pad rest of backreferences

+		/g,writeImageTag);

+	*/

+	text = text.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeImageTag);

+

+	//

+	// Next, handle inline images:  ![alt text](url "optional title")

+	// Don't forget: encode * and _

+

+	/*

+		text = text.replace(/

+		(						// wrap whole match in $1

+			!\[

+			(.*?)				// alt text = $2

+			\]

+			\s?					// One optional whitespace character

+			\(					// literal paren

+			[ \t]*

+			()					// no id, so leave $3 empty

+			<?(\S+?)>?			// src url = $4

+			[ \t]*

+			(					// $5

+				(['"])			// quote char = $6

+				(.*?)			// title = $7

+				\6				// matching quote

+				[ \t]*

+			)?					// title is optional

+		\)

+		)

+		/g,writeImageTag);

+	*/

+	text = text.replace(/(!\[(.*?)\]\s?\([ \t]*()<?(\S+?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeImageTag);

+

+	return text;

+}

+

+var writeImageTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) {

+	var whole_match = m1;

+	var alt_text   = m2;

+	var link_id	 = m3.toLowerCase();

+	var url		= m4;

+	var title	= m7;

+

+	if (!title) title = "";

+	

+	if (url == "") {

+		if (link_id == "") {

+			// lower-case and turn embedded newlines into spaces

+			link_id = alt_text.toLowerCase().replace(/ ?\n/g," ");

+		}

+		url = "#"+link_id;

+		

+		if (g_urls[link_id] != undefined) {

+			url = g_urls[link_id];

+			if (g_titles[link_id] != undefined) {

+				title = g_titles[link_id];

+			}

+		}

+		else {

+			return whole_match;

+		}

+	}	

+	

+	alt_text = alt_text.replace(/"/g,"&quot;");

+	url = escapeCharacters(url,"*_");

+	var result = "<img src=\"" + url + "\" alt=\"" + alt_text + "\"";

+

+	// attacklab: Markdown.pl adds empty title attributes to images.

+	// Replicate this bug.

+

+	//if (title != "") {

+		title = title.replace(/"/g,"&quot;");

+		title = escapeCharacters(title,"*_");

+		result +=  " title=\"" + title + "\"";

+	//}

+	

+	result += " />";

+	

+	return result;

+}

+

+

+var _DoHeaders = function(text) {

+

+	// Setext-style headers:

+	//	Header 1

+	//	========

+	//  

+	//	Header 2

+	//	--------

+	//

+	text = text.replace(/^(.+)[ \t]*\n=+[ \t]*\n+/gm,

+		function(wholeMatch,m1){return hashBlock('<h1 id="' + headerId(m1) + '">' + _RunSpanGamut(m1) + "</h1>");});

+

+	text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm,

+		function(matchFound,m1){return hashBlock('<h2 id="' + headerId(m1) + '">' + _RunSpanGamut(m1) + "</h2>");});

+

+	// atx-style headers:

+	//  # Header 1

+	//  ## Header 2

+	//  ## Header 2 with closing hashes ##

+	//  ...

+	//  ###### Header 6

+	//

+

+	/*

+		text = text.replace(/

+			^(\#{1,6})				// $1 = string of #'s

+			[ \t]*

+			(.+?)					// $2 = Header text

+			[ \t]*

+			\#*						// optional closing #'s (not counted)

+			\n+

+		/gm, function() {...});

+	*/

+

+	text = text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm,

+		function(wholeMatch,m1,m2) {

+			var h_level = m1.length;

+			return hashBlock("<h" + h_level + ' id="' + headerId(m2) + '">' + _RunSpanGamut(m2) + "</h" + h_level + ">");

+		});

+

+	function headerId(m) {

+		return m.replace(/[^\w]/g, '').toLowerCase();

+	}

+	return text;

+}

+

+// This declaration keeps Dojo compressor from outputting garbage:

+var _ProcessListItems;

+

+var _DoLists = function(text) {

+//

+// Form HTML ordered (numbered) and unordered (bulleted) lists.

+//

+

+	// attacklab: add sentinel to hack around khtml/safari bug:

+	// http://bugs.webkit.org/show_bug.cgi?id=11231

+	text += "~0";

+

+	// Re-usable pattern to match any entirel ul or ol list:

+

+	/*

+		var whole_list = /

+		(									// $1 = whole list

+			(								// $2

+				[ ]{0,3}					// attacklab: g_tab_width - 1

+				([*+-]|\d+[.])				// $3 = first list item marker

+				[ \t]+

+			)

+			[^\r]+?

+			(								// $4

+				~0							// sentinel for workaround; should be $

+			|

+				\n{2,}

+				(?=\S)

+				(?!							// Negative lookahead for another list item marker

+					[ \t]*

+					(?:[*+-]|\d+[.])[ \t]+

+				)

+			)

+		)/g

+	*/

+	var whole_list = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;

+

+	if (g_list_level) {

+		text = text.replace(whole_list,function(wholeMatch,m1,m2) {

+			var list = m1;

+			var list_type = (m2.search(/[*+-]/g)>-1) ? "ul" : "ol";

+

+			// Turn double returns into triple returns, so that we can make a

+			// paragraph for the last item in a list, if necessary:

+			list = list.replace(/\n{2,}/g,"\n\n\n");;

+			var result = _ProcessListItems(list);

+	

+			// Trim any trailing whitespace, to put the closing `</$list_type>`

+			// up on the preceding line, to get it past the current stupid

+			// HTML block parser. This is a hack to work around the terrible

+			// hack that is the HTML block parser.

+			result = result.replace(/\s+$/,"");

+			result = "<"+list_type+">" + result + "</"+list_type+">\n";

+			return result;

+		});

+	} else {

+		whole_list = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g;

+		text = text.replace(whole_list,function(wholeMatch,m1,m2,m3) {

+			var runup = m1;

+			var list = m2;

+

+			var list_type = (m3.search(/[*+-]/g)>-1) ? "ul" : "ol";

+			// Turn double returns into triple returns, so that we can make a

+			// paragraph for the last item in a list, if necessary:

+			var list = list.replace(/\n{2,}/g,"\n\n\n");;

+			var result = _ProcessListItems(list);

+			result = runup + "<"+list_type+">\n" + result + "</"+list_type+">\n";	

+			return result;

+		});

+	}

+

+	// attacklab: strip sentinel

+	text = text.replace(/~0/,"");

+

+	return text;

+}

+

+_ProcessListItems = function(list_str) {

+//

+//  Process the contents of a single ordered or unordered list, splitting it

+//  into individual list items.

+//

+	// The $g_list_level global keeps track of when we're inside a list.

+	// Each time we enter a list, we increment it; when we leave a list,

+	// we decrement. If it's zero, we're not in a list anymore.

+	//

+	// We do this because when we're not inside a list, we want to treat

+	// something like this:

+	//

+	//    I recommend upgrading to version

+	//    8. Oops, now this line is treated

+	//    as a sub-list.

+	//

+	// As a single paragraph, despite the fact that the second line starts

+	// with a digit-period-space sequence.

+	//

+	// Whereas when we're inside a list (or sub-list), that line will be

+	// treated as the start of a sub-list. What a kludge, huh? This is

+	// an aspect of Markdown's syntax that's hard to parse perfectly

+	// without resorting to mind-reading. Perhaps the solution is to

+	// change the syntax rules such that sub-lists must start with a

+	// starting cardinal number; e.g. "1." or "a.".

+

+	g_list_level++;

+

+	// trim trailing blank lines:

+	list_str = list_str.replace(/\n{2,}$/,"\n");

+

+	// attacklab: add sentinel to emulate \z

+	list_str += "~0";

+

+	/*

+		list_str = list_str.replace(/

+			(\n)?							// leading line = $1

+			(^[ \t]*)						// leading whitespace = $2

+			([*+-]|\d+[.]) [ \t]+			// list marker = $3

+			([^\r]+?						// list item text   = $4

+			(\n{1,2}))

+			(?= \n* (~0 | \2 ([*+-]|\d+[.]) [ \t]+))

+		/gm, function(){...});

+	*/

+	list_str = list_str.replace(/(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+([^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm,

+		function(wholeMatch,m1,m2,m3,m4){

+			var item = m4;

+			var leading_line = m1;

+			var leading_space = m2;

+

+			if (leading_line || (item.search(/\n{2,}/)>-1)) {

+				item = _RunBlockGamut(_Outdent(item));

+			}

+			else {

+				// Recursion for sub-lists:

+				item = _DoLists(_Outdent(item));

+				item = item.replace(/\n$/,""); // chomp(item)

+				item = _RunSpanGamut(item);

+			}

+

+			return  "<li>" + item + "</li>\n";

+		}

+	);

+

+	// attacklab: strip sentinel

+	list_str = list_str.replace(/~0/g,"");

+

+	g_list_level--;

+	return list_str;

+}

+

+

+var _DoCodeBlocks = function(text) {

+//

+//  Process Markdown `<pre><code>` blocks.

+//  

+

+	/*

+		text = text.replace(text,

+			/(?:\n\n|^)

+			(								// $1 = the code block -- one or more lines, starting with a space/tab

+				(?:

+					(?:[ ]{4}|\t)			// Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width

+					.*\n+

+				)+

+			)

+			(\n*[ ]{0,3}[^ \t\n]|(?=~0))	// attacklab: g_tab_width

+		/g,function(){...});

+	*/

+

+	// attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug

+	text += "~0";

+	

+	text = text.replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,

+		function(wholeMatch,m1,m2) {

+			var codeblock = m1;

+			var nextChar = m2;

+		

+			codeblock = _EncodeCode( _Outdent(codeblock));

+			codeblock = _Detab(codeblock);

+			codeblock = codeblock.replace(/^\n+/g,""); // trim leading newlines

+			codeblock = codeblock.replace(/\n+$/g,""); // trim trailing whitespace

+

+			codeblock = "<pre><code>" + codeblock + "\n</code></pre>";

+

+			return hashBlock(codeblock) + nextChar;

+		}

+	);

+

+	// attacklab: strip sentinel

+	text = text.replace(/~0/,"");

+

+	return text;

+}

+

+var hashBlock = function(text) {

+	text = text.replace(/(^\n+|\n+$)/g,"");

+	return "\n\n~K" + (g_html_blocks.push(text)-1) + "K\n\n";

+}

+

+

+var _DoCodeSpans = function(text) {

+//

+//   *  Backtick quotes are used for <code></code> spans.

+// 

+//   *  You can use multiple backticks as the delimiters if you want to

+//	 include literal backticks in the code span. So, this input:

+//	 

+//		 Just type ``foo `bar` baz`` at the prompt.

+//	 

+//	   Will translate to:

+//	 

+//		 <p>Just type <code>foo `bar` baz</code> at the prompt.</p>

+//	 

+//	There's no arbitrary limit to the number of backticks you

+//	can use as delimters. If you need three consecutive backticks

+//	in your code, use four for delimiters, etc.

+//

+//  *  You can use spaces to get literal backticks at the edges:

+//	 

+//		 ... type `` `bar` `` ...

+//	 

+//	   Turns to:

+//	 

+//		 ... type <code>`bar`</code> ...

+//

+

+	/*

+		text = text.replace(/

+			(^|[^\\])					// Character before opening ` can't be a backslash

+			(`+)						// $2 = Opening run of `

+			(							// $3 = The code block

+				[^\r]*?

+				[^`]					// attacklab: work around lack of lookbehind

+			)

+			\2							// Matching closer

+			(?!`)

+		/gm, function(){...});

+	*/

+

+	text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,

+		function(wholeMatch,m1,m2,m3,m4) {

+			var c = m3;

+			c = c.replace(/^([ \t]*)/g,"");	// leading whitespace

+			c = c.replace(/[ \t]*$/g,"");	// trailing whitespace

+			c = _EncodeCode(c);

+			return m1+"<code>"+c+"</code>";

+		});

+

+	return text;

+}

+

+

+var _EncodeCode = function(text) {

+//

+// Encode/escape certain characters inside Markdown code runs.

+// The point is that in code, these characters are literals,

+// and lose their special Markdown meanings.

+//

+	// Encode all ampersands; HTML entities are not

+	// entities within a Markdown code span.

+	text = text.replace(/&/g,"&amp;");

+

+	// Do the angle bracket song and dance:

+	text = text.replace(/</g,"&lt;");

+	text = text.replace(/>/g,"&gt;");

+

+	// Now, escape characters that are magic in Markdown:

+	text = escapeCharacters(text,"\*_{}[]\\",false);

+

+// jj the line above breaks this:

+//---

+

+//* Item

+

+//   1. Subitem

+

+//            special char: *

+//---

+

+	return text;

+}

+

+

+var _DoItalicsAndBold = function(text) {

+

+	// <strong> must go first:

+	text = text.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g,

+		"<strong>$2</strong>");

+

+	text = text.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g,

+		"<em>$2</em>");

+

+	return text;

+}

+

+

+var _DoBlockQuotes = function(text) {

+

+	/*

+		text = text.replace(/

+		(								// Wrap whole match in $1

+			(

+				^[ \t]*>[ \t]?			// '>' at the start of a line

+				.+\n					// rest of the first line

+				(.+\n)*					// subsequent consecutive lines

+				\n*						// blanks

+			)+

+		)

+		/gm, function(){...});

+	*/

+

+	text = text.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm,

+		function(wholeMatch,m1) {

+			var bq = m1;

+

+			// attacklab: hack around Konqueror 3.5.4 bug:

+			// "----------bug".replace(/^-/g,"") == "bug"

+

+			bq = bq.replace(/^[ \t]*>[ \t]?/gm,"~0");	// trim one level of quoting

+

+			// attacklab: clean up hack

+			bq = bq.replace(/~0/g,"");

+

+			bq = bq.replace(/^[ \t]+$/gm,"");		// trim whitespace-only lines

+			bq = _RunBlockGamut(bq);				// recurse

+			

+			bq = bq.replace(/(^|\n)/g,"$1  ");

+			// These leading spaces screw with <pre> content, so we need to fix that:

+			bq = bq.replace(

+					/(\s*<pre>[^\r]+?<\/pre>)/gm,

+				function(wholeMatch,m1) {

+					var pre = m1;

+					// attacklab: hack around Konqueror 3.5.4 bug:

+					pre = pre.replace(/^  /mg,"~0");

+					pre = pre.replace(/~0/g,"");

+					return pre;

+				});

+			

+			return hashBlock("<blockquote>\n" + bq + "\n</blockquote>");

+		});

+	return text;

+}

+

+

+var _FormParagraphs = function(text) {

+//

+//  Params:

+//    $text - string to process with html <p> tags

+//

+

+	// Strip leading and trailing lines:

+	text = text.replace(/^\n+/g,"");

+	text = text.replace(/\n+$/g,"");

+

+	var grafs = text.split(/\n{2,}/g);

+	var grafsOut = new Array();

+

+	//

+	// Wrap <p> tags.

+	//

+	var end = grafs.length;

+	for (var i=0; i<end; i++) {

+		var str = grafs[i];

+

+		// if this is an HTML marker, copy it

+		if (str.search(/~K(\d+)K/g) >= 0) {

+			grafsOut.push(str);

+		}

+		else if (str.search(/\S/) >= 0) {

+			str = _RunSpanGamut(str);

+			str = str.replace(/^([ \t]*)/g,"<p>");

+			str += "</p>"

+			grafsOut.push(str);

+		}

+

+	}

+

+	//

+	// Unhashify HTML blocks

+	//

+	end = grafsOut.length;

+	for (var i=0; i<end; i++) {

+		// if this is a marker for an html block...

+		while (grafsOut[i].search(/~K(\d+)K/) >= 0) {

+			var blockText = g_html_blocks[RegExp.$1];

+			blockText = blockText.replace(/\$/g,"$$$$"); // Escape any dollar signs

+			grafsOut[i] = grafsOut[i].replace(/~K\d+K/,blockText);

+		}

+	}

+

+	return grafsOut.join("\n\n");

+}

+

+

+var _EncodeAmpsAndAngles = function(text) {

+// Smart processing for ampersands and angle brackets that need to be encoded.

+	

+	// Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin:

+	//   http://bumppo.net/projects/amputator/

+	text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g,"&amp;");

+	

+	// Encode naked <'s

+	text = text.replace(/<(?![a-z\/?\$!])/gi,"&lt;");

+	

+	return text;

+}

+

+

+var _EncodeBackslashEscapes = function(text) {

+//

+//   Parameter:  String.

+//   Returns:	The string, with after processing the following backslash

+//			   escape sequences.

+//

+

+	// attacklab: The polite way to do this is with the new

+	// escapeCharacters() function:

+	//

+	// 	text = escapeCharacters(text,"\\",true);

+	// 	text = escapeCharacters(text,"`*_{}[]()>#+-.!",true);

+	//

+	// ...but we're sidestepping its use of the (slow) RegExp constructor

+	// as an optimization for Firefox.  This function gets called a LOT.

+

+	text = text.replace(/\\(\\)/g,escapeCharacters_callback);

+	text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g,escapeCharacters_callback);

+	return text;

+}

+

+

+var _DoAutoLinks = function(text) {

+

+	text = text.replace(/<((https?|ftp|dict):[^'">\s]+)>/gi,"<a href=\"$1\">$1</a>");

+

+	// Email addresses: <address@domain.foo>

+

+	/*

+		text = text.replace(/

+			<

+			(?:mailto:)?

+			(

+				[-.\w]+

+				\@

+				[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+

+			)

+			>

+		/gi, _DoAutoLinks_callback());

+	*/

+	text = text.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi,

+		function(wholeMatch,m1) {

+			return _EncodeEmailAddress( _UnescapeSpecialChars(m1) );

+		}

+	);

+

+	return text;

+}

+

+

+var _EncodeEmailAddress = function(addr) {

+//

+//  Input: an email address, e.g. "foo@example.com"

+//

+//  Output: the email address as a mailto link, with each character

+//	of the address encoded as either a decimal or hex entity, in

+//	the hopes of foiling most address harvesting spam bots. E.g.:

+//

+//	<a href="&#x6D;&#97;&#105;&#108;&#x74;&#111;:&#102;&#111;&#111;&#64;&#101;

+//	   x&#x61;&#109;&#x70;&#108;&#x65;&#x2E;&#99;&#111;&#109;">&#102;&#111;&#111;

+//	   &#64;&#101;x&#x61;&#109;&#x70;&#108;&#x65;&#x2E;&#99;&#111;&#109;</a>

+//

+//  Based on a filter by Matthew Wickline, posted to the BBEdit-Talk

+//  mailing list: <http://tinyurl.com/yu7ue>

+//

+

+	// attacklab: why can't javascript speak hex?

+	function char2hex(ch) {

+		var hexDigits = '0123456789ABCDEF';

+		var dec = ch.charCodeAt(0);

+		return(hexDigits.charAt(dec>>4) + hexDigits.charAt(dec&15));

+	}

+

+	var encode = [

+		function(ch){return "&#"+ch.charCodeAt(0)+";";},

+		function(ch){return "&#x"+char2hex(ch)+";";},

+		function(ch){return ch;}

+	];

+

+	addr = "mailto:" + addr;

+

+	addr = addr.replace(/./g, function(ch) {

+		if (ch == "@") {

+		   	// this *must* be encoded. I insist.

+			ch = encode[Math.floor(Math.random()*2)](ch);

+		} else if (ch !=":") {

+			// leave ':' alone (to spot mailto: later)

+			var r = Math.random();

+			// roughly 10% raw, 45% hex, 45% dec

+			ch =  (

+					r > .9  ?	encode[2](ch)   :

+					r > .45 ?	encode[1](ch)   :

+								encode[0](ch)

+				);

+		}

+		return ch;

+	});

+

+	addr = "<a href=\"" + addr + "\">" + addr + "</a>";

+	addr = addr.replace(/">.+:/g,"\">"); // strip the mailto: from the visible part

+

+	return addr;

+}

+

+

+var _UnescapeSpecialChars = function(text) {

+//

+// Swap back in all the special characters we've hidden.

+//

+	text = text.replace(/~E(\d+)E/g,

+		function(wholeMatch,m1) {

+			var charCodeToReplace = parseInt(m1);

+			return String.fromCharCode(charCodeToReplace);

+		}

+	);

+	return text;

+}

+

+

+var _Outdent = function(text) {

+//

+// Remove one level of line-leading tabs or spaces

+//

+

+	// attacklab: hack around Konqueror 3.5.4 bug:

+	// "----------bug".replace(/^-/g,"") == "bug"

+

+	text = text.replace(/^(\t|[ ]{1,4})/gm,"~0"); // attacklab: g_tab_width

+

+	// attacklab: clean up hack

+	text = text.replace(/~0/g,"")

+

+	return text;

+}

+

+var _Detab = function(text) {

+// attacklab: Detab's completely rewritten for speed.

+// In perl we could fix it by anchoring the regexp with \G.

+// In javascript we're less fortunate.

+

+	// expand first n-1 tabs

+	text = text.replace(/\t(?=\t)/g,"    "); // attacklab: g_tab_width

+

+	// replace the nth with two sentinels

+	text = text.replace(/\t/g,"~A~B");

+

+	// use the sentinel to anchor our regex so it doesn't explode

+	text = text.replace(/~B(.+?)~A/g,

+		function(wholeMatch,m1,m2) {

+			var leadingText = m1;

+			var numSpaces = 4 - leadingText.length % 4;  // attacklab: g_tab_width

+

+			// there *must* be a better way to do this:

+			for (var i=0; i<numSpaces; i++) leadingText+=" ";

+

+			return leadingText;

+		}

+	);

+

+	// clean up sentinels

+	text = text.replace(/~A/g,"    ");  // attacklab: g_tab_width

+	text = text.replace(/~B/g,"");

+

+	return text;

+}

+

+

+//

+//  attacklab: Utility functions

+//

+

+

+var escapeCharacters = function(text, charsToEscape, afterBackslash) {

+	// First we have to escape the escape characters so that

+	// we can build a character class out of them

+	var regexString = "([" + charsToEscape.replace(/([\[\]\\])/g,"\\$1") + "])";

+

+	if (afterBackslash) {

+		regexString = "\\\\" + regexString;

+	}

+

+	var regex = new RegExp(regexString,"g");

+	text = text.replace(regex,escapeCharacters_callback);

+

+	return text;

+}

+

+

+var escapeCharacters_callback = function(wholeMatch,m1) {

+	var charCodeToEscape = m1.charCodeAt(0);

+	return "~E"+charCodeToEscape+"E";

+}

+

+} // end of Showdown.converter

+

+// export

+if (typeof exports != 'undefined') exports.Showdown = Showdown;
\ No newline at end of file
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/Makefile b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/Makefile
new file mode 100644
index 0000000..ed07610
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/Makefile
@@ -0,0 +1,73 @@
+BUILD_DIR = build
+
+PREFIX = .
+SRC_DIR = ${PREFIX}
+DIST_DIR = ${PREFIX}/dist
+
+
+JS_ENGINE ?= `which node nodejs 2> /dev/null`
+COMPILER = ${JS_ENGINE} ${BUILD_DIR}/uglify.js --unsafe
+POST_COMPILER = ${JS_ENGINE} ${BUILD_DIR}/post-compile.js
+
+SRC = ${SRC_DIR}/jsonselect.js
+DIST = ${DIST_DIR}/jsonselect.js
+DIST_MIN = ${DIST_DIR}/jsonselect.min.js
+
+all: hint project min tests
+	@@echo "Project build complete."
+
+${DIST_DIR}:
+	@@mkdir -p ${DIST_DIR}
+
+project: ${DIST}
+
+${DIST}: ${SRC} | ${DIST_DIR}
+	@@echo "Building" ${DIST}
+	@@echo ${SRC}
+	@@cat ${SRC} > ${DIST};
+
+
+min: project ${DIST_MIN}
+
+${DIST_MIN}: ${DIST}
+	@@if test ! -z ${JS_ENGINE}; then \
+		echo "Minifying Project" ${DIST_MIN}; \
+		${COMPILER} ${DIST} > ${DIST_MIN}.tmp; \
+		${POST_COMPILER} ${DIST_MIN}.tmp > ${DIST_MIN}; \
+		rm -f ${DIST_MIN}.tmp; \
+	else \
+		echo "You must have NodeJS installed in order to minify Project."; \
+	fi
+
+
+hint:
+	@@if test ! -z ${JS_ENGINE}; then \
+		echo "Hinting Project"; \
+		${JS_ENGINE} build/jshint-check.js; \
+	else \
+		echo "Nodejs is missing"; \
+	fi
+
+
+test/tests/README.md: 
+	@@cd .. && git submodule init 
+	@@cd .. && git submodule update 
+
+
+tests: test/tests/README.md
+	@@if test ! -z ${JS_ENGINE}; then \
+		echo "Testing Project"; \
+		${JS_ENGINE} test/run.js; \
+	else \
+		echo "nodejs is missing"; \
+	fi
+
+
+clean:
+	@@echo "Removing Distribution directory:" ${DIST_DIR}
+	@@rm -rf ${DIST_DIR}
+
+
+
+
+.PHONY: all project hint min tests
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/jshint-check.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/jshint-check.js
new file mode 100644
index 0000000..312c643
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/jshint-check.js
@@ -0,0 +1,41 @@
+var JSHINT = require("./lib/jshint").JSHINT,
+	print = require("sys").print,
+	src = require("fs").readFileSync("jsonselect.js", "utf8");
+
+JSHINT(src, { evil: true, forin: true, maxerr: 100 });
+
+var ok = {
+
+	// w.reason
+	"Expected an identifier and instead saw 'undefined' (a reserved word).": true,
+	"Use '===' to compare with 'null'.": true,
+	"Use '!==' to compare with 'null'.": true,
+	"Expected an assignment or function call and instead saw an expression.": true,
+	"Expected a 'break' statement before 'case'.": true,
+	"'e' is already defined.": true,
+	// w.raw
+	"Expected an identifier and instead saw \'{a}\' (a reserved word).": true
+};
+
+var e = JSHINT.errors, 
+		found = 0, 
+		w;
+
+for ( var i = 0; i < e.length; i++ ) {
+	w = e[i];
+
+	//console.log( w );
+	if ( !ok[ w.reason ] && !ok[ w.raw ] ) {
+		found++;
+
+		print( "\n " + found + ". Problem at line " + w.line + " character " + w.character + ": " + w.reason );
+		print( "\n" + w.evidence );
+	}
+}
+
+if ( found > 0 ) {
+	print( "\n\n" + found + " Error(s) found.\n" );
+
+} else {
+	print( "JSHint check passed.\n" );
+}
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/lib/jshint.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/lib/jshint.js
new file mode 100644
index 0000000..6c34fbe
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/lib/jshint.js
@@ -0,0 +1,3854 @@
+/*

+ * JSHint, by JSHint Community.

+ *

+ * Licensed under the same slightly modified MIT license that JSLint is.

+ * It stops evil-doers everywhere.

+ *

+ * JSHint is a derivative work of JSLint:

+ *

+ *   Copyright (c) 2002 Douglas Crockford  (www.JSLint.com)

+ *

+ *   Permission is hereby granted, free of charge, to any person obtaining

+ *   a copy of this software and associated documentation files (the "Software"),

+ *   to deal in the Software without restriction, including without limitation

+ *   the rights to use, copy, modify, merge, publish, distribute, sublicense,

+ *   and/or sell copies of the Software, and to permit persons to whom

+ *   the Software is furnished to do so, subject to the following conditions:

+ *

+ *   The above copyright notice and this permission notice shall be included

+ *   in all copies or substantial portions of the Software.

+ *

+ *   The Software shall be used for Good, not Evil.

+ *

+ *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR

+ *   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,

+ *   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE

+ *   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER

+ *   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING

+ *   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER

+ *   DEALINGS IN THE SOFTWARE.

+ *

+ * JSHint was forked from 2010-12-16 edition of JSLint.

+ *

+ */

+

+/*

+ JSHINT is a global function. It takes two parameters.

+

+     var myResult = JSHINT(source, option);

+

+ The first parameter is either a string or an array of strings. If it is a

+ string, it will be split on '\n' or '\r'. If it is an array of strings, it

+ is assumed that each string represents one line. The source can be a

+ JavaScript text or a JSON text.

+

+ The second parameter is an optional object of options which control the

+ operation of JSHINT. Most of the options are booleans: They are all

+ optional and have a default value of false. One of the options, predef,

+ can be an array of names, which will be used to declare global variables,

+ or an object whose keys are used as global names, with a boolean value

+ that determines if they are assignable.

+

+ If it checks out, JSHINT returns true. Otherwise, it returns false.

+

+ If false, you can inspect JSHINT.errors to find out the problems.

+ JSHINT.errors is an array of objects containing these members:

+

+ {

+     line      : The line (relative to 0) at which the lint was found

+     character : The character (relative to 0) at which the lint was found

+     reason    : The problem

+     evidence  : The text line in which the problem occurred

+     raw       : The raw message before the details were inserted

+     a         : The first detail

+     b         : The second detail

+     c         : The third detail

+     d         : The fourth detail

+ }

+

+ If a fatal error was found, a null will be the last element of the

+ JSHINT.errors array.

+

+ You can request a Function Report, which shows all of the functions

+ and the parameters and vars that they use. This can be used to find

+ implied global variables and other problems. The report is in HTML and

+ can be inserted in an HTML <body>.

+

+     var myReport = JSHINT.report(limited);

+

+ If limited is true, then the report will be limited to only errors.

+

+ You can request a data structure which contains JSHint's results.

+

+     var myData = JSHINT.data();

+

+ It returns a structure with this form:

+

+ {

+     errors: [

+         {

+             line: NUMBER,

+             character: NUMBER,

+             reason: STRING,

+             evidence: STRING

+         }

+     ],

+     functions: [

+         name: STRING,

+         line: NUMBER,

+         last: NUMBER,

+         param: [

+             STRING

+         ],

+         closure: [

+             STRING

+         ],

+         var: [

+             STRING

+         ],

+         exception: [

+             STRING

+         ],

+         outer: [

+             STRING

+         ],

+         unused: [

+             STRING

+         ],

+         global: [

+             STRING

+         ],

+         label: [

+             STRING

+         ]

+     ],

+     globals: [

+         STRING

+     ],

+     member: {

+         STRING: NUMBER

+     },

+     unuseds: [

+         {

+             name: STRING,

+             line: NUMBER

+         }

+     ],

+     implieds: [

+         {

+             name: STRING,

+             line: NUMBER

+         }

+     ],

+     urls: [

+         STRING

+     ],

+     json: BOOLEAN

+ }

+

+ Empty arrays will not be included.

+

+*/

+

+/*jshint

+ evil: true, nomen: false, onevar: false, regexp: false, strict: true, boss: true

+*/

+

+/*members "\b", "\t", "\n", "\f", "\r", "!=", "!==", "\"", "%",

+ "(begin)", "(breakage)", "(context)", "(error)", "(global)",

+ "(identifier)", "(last)", "(line)", "(loopage)", "(name)", "(onevar)",

+ "(params)", "(scope)", "(statement)", "(verb)", "*", "+", "++", "-",

+ "--", "\/", "<", "<=", "==", "===", ">", ">=", $, $$, $A, $F, $H, $R, $break,

+ $continue, $w, Abstract, Ajax, __filename, __dirname, ActiveXObject, Array,

+ ArrayBuffer, ArrayBufferView, Autocompleter, Assets, Boolean, Builder,

+ Buffer, Browser, COM, CScript, Canvas, CustomAnimation, Class, Control,

+ Chain, Color, Cookie, Core, DataView, Date, Debug, Draggable, Draggables,

+ Droppables, Document, DomReady, DOMReady, Drag, E, Enumerator, Enumerable,

+ Element, Elements, Error, Effect, EvalError, Event, Events, FadeAnimation,

+ Field, Flash, Float32Array, Float64Array, Form, FormField, Frame, Function,

+ Fx, Group, Hash, HotKey, HTMLElement, HtmlTable, Iframe, IframeShim, Image,

+ Int16Array, Int32Array, Int8Array, Insertion, InputValidator, JSON, Keyboard,

+ Locale, LN10, LN2, LOG10E, LOG2E, MAX_VALUE, MIN_VALUE, Mask, Math, MenuItem,

+ MoveAnimation, MooTools, Native, NEGATIVE_INFINITY, Number, Object,

+ ObjectRange, Option, Options, OverText, PI, POSITIVE_INFINITY,

+ PeriodicalExecuter, Point, Position, Prototype, RangeError, Rectangle,

+ ReferenceError, RegExp, ResizeAnimation, Request, RotateAnimation, SQRT1_2,

+ SQRT2, ScrollBar, Scriptaculous, Scroller, Slick, Slider, Selector, String,

+ Style, SyntaxError, Sortable, Sortables, SortableObserver, Sound, Spinner,

+ System, Swiff, Text, TextArea, Template, Timer, Tips, Type, TypeError,

+ Toggle, Try, URI, URIError, URL, VBArray, WScript, Web, Window, XMLDOM,

+ XMLHttpRequest, XPathEvaluator, XPathException, XPathExpression,

+ XPathNamespace, XPathNSResolver, XPathResult, "\\", a, addEventListener,

+ address, alert,  apply, applicationCache, arguments, arity, asi, b, bitwise,

+ block, blur, boolOptions, boss, browser, c, call, callee, caller, cases,

+ charAt, charCodeAt, character, clearInterval, clearTimeout, close, closed,

+ closure, comment, condition, confirm, console, constructor, content, couch,

+ create, css, curly, d, data, datalist, dd, debug, decodeURI,

+ decodeURIComponent, defaultStatus, defineClass, deserialize, devel,

+ document, edition, else, emit, encodeURI, encodeURIComponent, entityify,

+ eqeqeq, eqnull, errors, es5, escape, eval, event, evidence, evil, ex,

+ exception, exec, exps, expr, exports, FileReader, first, floor, focus,

+ forin, fragment, frames, from, fromCharCode, fud, funct, function, functions,

+ g, gc, getComputedStyle, getRow, GLOBAL, global, globals, globalstrict,

+ hasOwnProperty, help, history, i, id, identifier, immed, implieds,

+ include, indent, indexOf, init, ins, instanceOf, isAlpha,

+ isApplicationRunning, isArray, isDigit, isFinite, isNaN, join, jshint,

+ JSHINT, json, jquery, jQuery, keys, label, labelled, last, laxbreak,

+ latedef, lbp, led, left, length, line, load, loadClass, localStorage,

+ location, log, loopfunc, m, match, maxerr, maxlen, member,message, meta,

+ module, moveBy, moveTo, mootools, name, navigator, new, newcap, noarg,

+ node, noempty, nomen, nonew, nud, onbeforeunload, onblur, onerror, onevar,

+ onfocus, onload, onresize, onunload, open, openDatabase, openURL, opener,

+ opera, outer, param, parent, parseFloat, parseInt, passfail, plusplus,

+ predef, print, process, prompt, prototype, prototypejs, push, quit, range,

+ raw, reach, reason, regexp, readFile, readUrl, removeEventListener, replace,

+ report, require, reserved, resizeBy, resizeTo, resolvePath, resumeUpdates,

+ respond, rhino, right, runCommand, scroll, screen, scrollBy, scrollTo,

+ scrollbar, search, seal, send, serialize, setInterval, setTimeout, shift,

+ slice, sort,spawn, split, stack, status, start, strict, sub, substr, supernew,

+ shadow, supplant, sum, sync, test, toLowerCase, toString, toUpperCase, toint32,

+ token, top, type, typeOf, Uint16Array, Uint32Array, Uint8Array, undef,

+ unused, urls, value, valueOf, var, version, WebSocket, white, window, Worker

+*/

+

+/*global exports: false */

+

+// We build the application inside a function so that we produce only a single

+// global variable. That function will be invoked immediately, and its return

+// value is the JSHINT function itself.

+

+var JSHINT = (function () {

+    "use strict";

+

+    var anonname,       // The guessed name for anonymous functions.

+

+// These are operators that should not be used with the ! operator.

+

+        bang = {

+            '<'  : true,

+            '<=' : true,

+            '==' : true,

+            '===': true,

+            '!==': true,

+            '!=' : true,

+            '>'  : true,

+            '>=' : true,

+            '+'  : true,

+            '-'  : true,

+            '*'  : true,

+            '/'  : true,

+            '%'  : true

+        },

+

+// These are the JSHint boolean options.

+

+        boolOptions = {

+            asi         : true, // if automatic semicolon insertion should be tolerated

+            bitwise     : true, // if bitwise operators should not be allowed

+            boss        : true, // if advanced usage of assignments should be allowed

+            browser     : true, // if the standard browser globals should be predefined

+            couch       : true, // if CouchDB globals should be predefined

+            curly       : true, // if curly braces around blocks should be required (even in if/for/while)

+            debug       : true, // if debugger statements should be allowed

+            devel       : true, // if logging globals should be predefined (console, alert, etc.)

+            eqeqeq      : true, // if === should be required

+            eqnull      : true, // if == null comparisons should be tolerated

+            es5         : true, // if ES5 syntax should be allowed

+            evil        : true, // if eval should be allowed

+            expr        : true, // if ExpressionStatement should be allowed as Programs

+            forin       : true, // if for in statements must filter

+            globalstrict: true, // if global "use strict"; should be allowed (also enables 'strict')

+            immed       : true, // if immediate invocations must be wrapped in parens

+            jquery      : true, // if jQuery globals should be predefined

+            latedef     : true, // if the use before definition should not be tolerated

+            laxbreak    : true, // if line breaks should not be checked

+            loopfunc    : true, // if functions should be allowed to be defined within loops

+            mootools    : true, // if MooTools globals should be predefined

+            newcap      : true, // if constructor names must be capitalized

+            noarg       : true, // if arguments.caller and arguments.callee should be disallowed

+            node        : true, // if the Node.js environment globals should be predefined

+            noempty     : true, // if empty blocks should be disallowed

+            nonew       : true, // if using `new` for side-effects should be disallowed

+            nomen       : true, // if names should be checked

+            onevar      : true, // if only one var statement per function should be allowed

+            passfail    : true, // if the scan should stop on first error

+            plusplus    : true, // if increment/decrement should not be allowed

+            prototypejs : true, // if Prototype and Scriptaculous globals shoudl be predefined

+            regexp      : true, // if the . should not be allowed in regexp literals

+            rhino       : true, // if the Rhino environment globals should be predefined

+            undef       : true, // if variables should be declared before used

+            shadow      : true, // if variable shadowing should be tolerated

+            strict      : true, // require the "use strict"; pragma

+            sub         : true, // if all forms of subscript notation are tolerated

+            supernew    : true, // if `new function () { ... };` and `new Object;` should be tolerated

+            white       : true  // if strict whitespace rules apply

+        },

+

+// browser contains a set of global names which are commonly provided by a

+// web browser environment.

+

+        browser = {

+            ArrayBuffer     : false,

+            ArrayBufferView : false,

+            addEventListener: false,

+            applicationCache: false,

+            blur            : false,

+            clearInterval   : false,

+            clearTimeout    : false,

+            close           : false,

+            closed          : false,

+            DataView        : false,

+            defaultStatus   : false,

+            document        : false,

+            event           : false,

+            FileReader      : false,

+            Float32Array    : false,

+            Float64Array    : false,

+            focus           : false,

+            frames          : false,

+            getComputedStyle: false,

+            HTMLElement     : false,

+            history         : false,

+            Int16Array      : false,

+            Int32Array      : false,

+            Int8Array       : false,

+            Image           : false,

+            length          : false,

+            localStorage    : false,

+            location        : false,

+            moveBy          : false,

+            moveTo          : false,

+            name            : false,

+            navigator       : false,

+            onbeforeunload  : true,

+            onblur          : true,

+            onerror         : true,

+            onfocus         : true,

+            onload          : true,

+            onresize        : true,

+            onunload        : true,

+            open            : false,

+            openDatabase    : false,

+            opener          : false,

+            Option          : false,

+            parent          : false,

+            print           : false,

+            removeEventListener: false,

+            resizeBy        : false,

+            resizeTo        : false,

+            screen          : false,

+            scroll          : false,

+            scrollBy        : false,

+            scrollTo        : false,

+            setInterval     : false,

+            setTimeout      : false,

+            status          : false,

+            top             : false,

+            Uint16Array     : false,

+            Uint32Array     : false,

+            Uint8Array      : false,

+            WebSocket       : false,

+            window          : false,

+            Worker          : false,

+            XMLHttpRequest  : false,

+            XPathEvaluator  : false,

+            XPathException  : false,

+            XPathExpression : false,

+            XPathNamespace  : false,

+            XPathNSResolver : false,

+            XPathResult     : false

+        },

+

+        couch = {

+            "require" : false,

+            respond   : false,

+            getRow    : false,

+            emit      : false,

+            send      : false,

+            start     : false,

+            sum       : false,

+            log       : false,

+            exports   : false,

+            module    : false

+        },

+

+        devel = {

+            alert           : false,

+            confirm         : false,

+            console         : false,

+            Debug           : false,

+            opera           : false,

+            prompt          : false

+        },

+

+        escapes = {

+            '\b': '\\b',

+            '\t': '\\t',

+            '\n': '\\n',

+            '\f': '\\f',

+            '\r': '\\r',

+            '"' : '\\"',

+            '/' : '\\/',

+            '\\': '\\\\'

+        },

+

+        funct,          // The current function

+

+        functionicity = [

+            'closure', 'exception', 'global', 'label',

+            'outer', 'unused', 'var'

+        ],

+

+        functions,      // All of the functions

+

+        global,         // The global scope

+        implied,        // Implied globals

+        inblock,

+        indent,

+        jsonmode,

+

+        jquery = {

+            '$'    : false,

+            jQuery : false

+        },

+

+        lines,

+        lookahead,

+        member,

+        membersOnly,

+

+        mootools = {

+            '$'             : false,

+            '$$'            : false,

+            Assets          : false,

+            Browser         : false,

+            Chain           : false,

+            Class           : false,

+            Color           : false,

+            Cookie          : false,

+            Core            : false,

+            Document        : false,

+            DomReady        : false,

+            DOMReady        : false,

+            Drag            : false,

+            Element         : false,

+            Elements        : false,

+            Event           : false,

+            Events          : false,

+            Fx              : false,

+            Group           : false,

+            Hash            : false,

+            HtmlTable       : false,

+            Iframe          : false,

+            IframeShim      : false,

+            InputValidator  : false,

+            instanceOf      : false,

+            Keyboard        : false,

+            Locale          : false,

+            Mask            : false,

+            MooTools        : false,

+            Native          : false,

+            Options         : false,

+            OverText        : false,

+            Request         : false,

+            Scroller        : false,

+            Slick           : false,

+            Slider          : false,

+            Sortables       : false,

+            Spinner         : false,

+            Swiff           : false,

+            Tips            : false,

+            Type            : false,

+            typeOf          : false,

+            URI             : false,

+            Window          : false

+        },

+

+        nexttoken,

+

+        node = {

+            __filename  : false,

+            __dirname   : false,

+            exports     : false,

+            Buffer      : false,

+            GLOBAL      : false,

+            global      : false,

+            module      : false,

+            process     : false,

+            require     : false

+        },

+

+        noreach,

+        option,

+        predefined,     // Global variables defined by option

+        prereg,

+        prevtoken,

+

+        prototypejs = {

+            '$'               : false,

+            '$$'              : false,

+            '$A'              : false,

+            '$F'              : false,

+            '$H'              : false,

+            '$R'              : false,

+            '$break'          : false,

+            '$continue'       : false,

+            '$w'              : false,

+            Abstract          : false,

+            Ajax              : false,

+            Class             : false,

+            Enumerable        : false,

+            Element           : false,

+            Event             : false,

+            Field             : false,

+            Form              : false,

+            Hash              : false,

+            Insertion         : false,

+            ObjectRange       : false,

+            PeriodicalExecuter: false,

+            Position          : false,

+            Prototype         : false,

+            Selector          : false,

+            Template          : false,

+            Toggle            : false,

+            Try               : false,

+            Autocompleter     : false,

+            Builder           : false,

+            Control           : false,

+            Draggable         : false,

+            Draggables        : false,

+            Droppables        : false,

+            Effect            : false,

+            Sortable          : false,

+            SortableObserver  : false,

+            Sound             : false,

+            Scriptaculous     : false

+        },

+

+        rhino = {

+            defineClass : false,

+            deserialize : false,

+            gc          : false,

+            help        : false,

+            load        : false,

+            loadClass   : false,

+            print       : false,

+            quit        : false,

+            readFile    : false,

+            readUrl     : false,

+            runCommand  : false,

+            seal        : false,

+            serialize   : false,

+            spawn       : false,

+            sync        : false,

+            toint32     : false,

+            version     : false

+        },

+

+        scope,      // The current scope

+        src,

+        stack,

+

+// standard contains the global names that are provided by the

+// ECMAScript standard.

+

+        standard = {

+            Array               : false,

+            Boolean             : false,

+            Date                : false,

+            decodeURI           : false,

+            decodeURIComponent  : false,

+            encodeURI           : false,

+            encodeURIComponent  : false,

+            Error               : false,

+            'eval'              : false,

+            EvalError           : false,

+            Function            : false,

+            hasOwnProperty      : false,

+            isFinite            : false,

+            isNaN               : false,

+            JSON                : false,

+            Math                : false,

+            Number              : false,

+            Object              : false,

+            parseInt            : false,

+            parseFloat          : false,

+            RangeError          : false,

+            ReferenceError      : false,

+            RegExp              : false,

+            String              : false,

+            SyntaxError         : false,

+            TypeError           : false,

+            URIError            : false

+        },

+

+        standard_member = {

+            E                   : true,

+            LN2                 : true,

+            LN10                : true,

+            LOG2E               : true,

+            LOG10E              : true,

+            MAX_VALUE           : true,

+            MIN_VALUE           : true,

+            NEGATIVE_INFINITY   : true,

+            PI                  : true,

+            POSITIVE_INFINITY   : true,

+            SQRT1_2             : true,

+            SQRT2               : true

+        },

+

+        strict_mode,

+        syntax = {},

+        tab,

+        token,

+        urls,

+        warnings,

+

+// Regular expressions. Some of these are stupidly long.

+

+// unsafe comment or string

+        ax = /@cc|<\/?|script|\]\s*\]|<\s*!|&lt/i,

+// unsafe characters that are silently deleted by one or more browsers

+        cx = /[\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/,

+// token

+        tx = /^\s*([(){}\[.,:;'"~\?\]#@]|==?=?|\/(\*(jshint|jslint|members?|global)?|=|\/)?|\*[\/=]?|\+(?:=|\++)?|-(?:=|-+)?|%=?|&[&=]?|\|[|=]?|>>?>?=?|<([\/=!]|\!(\[|--)?|<=?)?|\^=?|\!=?=?|[a-zA-Z_$][a-zA-Z0-9_$]*|[0-9]+([xX][0-9a-fA-F]+|\.[0-9]*)?([eE][+\-]?[0-9]+)?)/,

+// characters in strings that need escapement

+        nx = /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/,

+        nxg = /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,

+// star slash

+        lx = /\*\/|\/\*/,

+// identifier

+        ix = /^([a-zA-Z_$][a-zA-Z0-9_$]*)$/,

+// javascript url

+        jx = /^(?:javascript|jscript|ecmascript|vbscript|mocha|livescript)\s*:/i,

+// catches /* falls through */ comments

+        ft = /^\s*\/\*\s*falls\sthrough\s*\*\/\s*$/;

+

+    function F() {}     // Used by Object.create

+

+    function is_own(object, name) {

+

+// The object.hasOwnProperty method fails when the property under consideration

+// is named 'hasOwnProperty'. So we have to use this more convoluted form.

+

+        return Object.prototype.hasOwnProperty.call(object, name);

+    }

+

+// Provide critical ES5 functions to ES3.

+

+    if (typeof Array.isArray !== 'function') {

+        Array.isArray = function (o) {

+            return Object.prototype.toString.apply(o) === '[object Array]';

+        };

+    }

+

+    if (typeof Object.create !== 'function') {

+        Object.create = function (o) {

+            F.prototype = o;

+            return new F();

+        };

+    }

+

+    if (typeof Object.keys !== 'function') {

+        Object.keys = function (o) {

+            var a = [], k;

+            for (k in o) {

+                if (is_own(o, k)) {

+                    a.push(k);

+                }

+            }

+            return a;

+        };

+    }

+

+// Non standard methods

+

+    if (typeof String.prototype.entityify !== 'function') {

+        String.prototype.entityify = function () {

+            return this

+                .replace(/&/g, '&amp;')

+                .replace(/</g, '&lt;')

+                .replace(/>/g, '&gt;');

+        };

+    }

+

+    if (typeof String.prototype.isAlpha !== 'function') {

+        String.prototype.isAlpha = function () {

+            return (this >= 'a' && this <= 'z\uffff') ||

+                (this >= 'A' && this <= 'Z\uffff');

+        };

+    }

+

+    if (typeof String.prototype.isDigit !== 'function') {

+        String.prototype.isDigit = function () {

+            return (this >= '0' && this <= '9');

+        };

+    }

+

+    if (typeof String.prototype.supplant !== 'function') {

+        String.prototype.supplant = function (o) {

+            return this.replace(/\{([^{}]*)\}/g, function (a, b) {

+                var r = o[b];

+                return typeof r === 'string' || typeof r === 'number' ? r : a;

+            });

+        };

+    }

+

+    if (typeof String.prototype.name !== 'function') {

+        String.prototype.name = function () {

+

+// If the string looks like an identifier, then we can return it as is.

+// If the string contains no control characters, no quote characters, and no

+// backslash characters, then we can simply slap some quotes around it.

+// Otherwise we must also replace the offending characters with safe

+// sequences.

+

+            if (ix.test(this)) {

+                return this;

+            }

+            if (nx.test(this)) {

+                return '"' + this.replace(nxg, function (a) {

+                    var c = escapes[a];

+                    if (c) {

+                        return c;

+                    }

+                    return '\\u' + ('0000' + a.charCodeAt().toString(16)).slice(-4);

+                }) + '"';

+            }

+            return '"' + this + '"';

+        };

+    }

+

+

+    function combine(t, o) {

+        var n;

+        for (n in o) {

+            if (is_own(o, n)) {

+                t[n] = o[n];

+            }

+        }

+    }

+

+    function assume() {

+        if (option.couch)

+            combine(predefined, couch);

+

+        if (option.rhino)

+            combine(predefined, rhino);

+

+        if (option.prototypejs)

+            combine(predefined, prototypejs);

+

+        if (option.node)

+            combine(predefined, node);

+

+        if (option.devel)

+            combine(predefined, devel);

+

+        if (option.browser)

+            combine(predefined, browser);

+

+        if (option.jquery)

+            combine(predefined, jquery);

+

+        if (option.mootools)

+            combine(predefined, mootools);

+

+        if (option.globalstrict)

+            option.strict = true;

+    }

+

+

+// Produce an error warning.

+

+    function quit(m, l, ch) {

+        throw {

+            name: 'JSHintError',

+            line: l,

+            character: ch,

+            message: m + " (" + Math.floor((l / lines.length) * 100) +

+                    "% scanned)."

+        };

+    }

+

+    function warning(m, t, a, b, c, d) {

+        var ch, l, w;

+        t = t || nexttoken;

+        if (t.id === '(end)') {  // `~

+            t = token;

+        }

+        l = t.line || 0;

+        ch = t.from || 0;

+        w = {

+            id: '(error)',

+            raw: m,

+            evidence: lines[l - 1] || '',

+            line: l,

+            character: ch,

+            a: a,

+            b: b,

+            c: c,

+            d: d

+        };

+        w.reason = m.supplant(w);

+        JSHINT.errors.push(w);

+        if (option.passfail) {

+            quit('Stopping. ', l, ch);

+        }

+        warnings += 1;

+        if (warnings >= option.maxerr) {

+            quit("Too many errors.", l, ch);

+        }

+        return w;

+    }

+

+    function warningAt(m, l, ch, a, b, c, d) {

+        return warning(m, {

+            line: l,

+            from: ch

+        }, a, b, c, d);

+    }

+

+    function error(m, t, a, b, c, d) {

+        var w = warning(m, t, a, b, c, d);

+        quit("Stopping, unable to continue.", w.line, w.character);

+    }

+

+    function errorAt(m, l, ch, a, b, c, d) {

+        return error(m, {

+            line: l,

+            from: ch

+        }, a, b, c, d);

+    }

+

+

+

+// lexical analysis and token construction

+

+    var lex = (function lex() {

+        var character, from, line, s;

+

+// Private lex methods

+

+        function nextLine() {

+            var at,

+                tw; // trailing whitespace check

+

+            if (line >= lines.length)

+                return false;

+

+            character = 1;

+            s = lines[line];

+            line += 1;

+            at = s.search(/ \t/);

+

+            if (at >= 0)

+                warningAt("Mixed spaces and tabs.", line, at + 1);

+

+            s = s.replace(/\t/g, tab);

+            at = s.search(cx);

+

+            if (at >= 0)

+                warningAt("Unsafe character.", line, at);

+

+            if (option.maxlen && option.maxlen < s.length)

+                warningAt("Line too long.", line, s.length);

+

+            // Check for trailing whitespaces

+            tw = s.search(/\s+$/);

+            if (option.white && ~tw)

+                warningAt("Trailing whitespace.", line, tw);

+

+            return true;

+        }

+

+// Produce a token object.  The token inherits from a syntax symbol.

+

+        function it(type, value) {

+            var i, t;

+            if (type === '(color)' || type === '(range)') {

+                t = {type: type};

+            } else if (type === '(punctuator)' ||

+                    (type === '(identifier)' && is_own(syntax, value))) {

+                t = syntax[value] || syntax['(error)'];

+            } else {

+                t = syntax[type];

+            }

+            t = Object.create(t);

+            if (type === '(string)' || type === '(range)') {

+                if (jx.test(value)) {

+                    warningAt("Script URL.", line, from);

+                }

+            }

+            if (type === '(identifier)') {

+                t.identifier = true;

+                if (value === '__iterator__' || value === '__proto__') {

+                    errorAt("Reserved name '{a}'.",

+                        line, from, value);

+                } else if (option.nomen &&

+                        (value.charAt(0) === '_' ||

+                         value.charAt(value.length - 1) === '_')) {

+                    warningAt("Unexpected {a} in '{b}'.", line, from,

+                        "dangling '_'", value);

+                }

+            }

+            t.value = value;

+            t.line = line;

+            t.character = character;

+            t.from = from;

+            i = t.id;

+            if (i !== '(endline)') {

+                prereg = i &&

+                    (('(,=:[!&|?{};'.indexOf(i.charAt(i.length - 1)) >= 0) ||

+                    i === 'return');

+            }

+            return t;

+        }

+

+// Public lex methods

+

+        return {

+            init: function (source) {

+                if (typeof source === 'string') {

+                    lines = source

+                        .replace(/\r\n/g, '\n')

+                        .replace(/\r/g, '\n')

+                        .split('\n');

+                } else {

+                    lines = source;

+                }

+

+                // If the first line is a shebang (#!), make it a blank and move on.

+                // Shebangs are used by Node scripts.

+                if (lines[0] && lines[0].substr(0, 2) == '#!')

+                    lines[0] = '';

+

+                line = 0;

+                nextLine();

+                from = 1;

+            },

+

+            range: function (begin, end) {

+                var c, value = '';

+                from = character;

+                if (s.charAt(0) !== begin) {

+                    errorAt("Expected '{a}' and instead saw '{b}'.",

+                            line, character, begin, s.charAt(0));

+                }

+                for (;;) {

+                    s = s.slice(1);

+                    character += 1;

+                    c = s.charAt(0);

+                    switch (c) {

+                    case '':

+                        errorAt("Missing '{a}'.", line, character, c);

+                        break;

+                    case end:

+                        s = s.slice(1);

+                        character += 1;

+                        return it('(range)', value);

+                    case '\\':

+                        warningAt("Unexpected '{a}'.", line, character, c);

+                    }

+                    value += c;

+                }

+

+            },

+

+// token -- this is called by advance to get the next token.

+

+            token: function () {

+                var b, c, captures, d, depth, high, i, l, low, q, t;

+

+                function match(x) {

+                    var r = x.exec(s), r1;

+                    if (r) {

+                        l = r[0].length;

+                        r1 = r[1];

+                        c = r1.charAt(0);

+                        s = s.substr(l);

+                        from = character + l - r1.length;

+                        character += l;

+                        return r1;

+                    }

+                }

+

+                function string(x) {

+                    var c, j, r = '';

+

+                    if (jsonmode && x !== '"') {

+                        warningAt("Strings must use doublequote.",

+                                line, character);

+                    }

+

+                    function esc(n) {

+                        var i = parseInt(s.substr(j + 1, n), 16);

+                        j += n;

+                        if (i >= 32 && i <= 126 &&

+                                i !== 34 && i !== 92 && i !== 39) {

+                            warningAt("Unnecessary escapement.", line, character);

+                        }

+                        character += n;

+                        c = String.fromCharCode(i);

+                    }

+                    j = 0;

+                    for (;;) {

+                        while (j >= s.length) {

+                            j = 0;

+                            if (!nextLine()) {

+                                errorAt("Unclosed string.", line, from);

+                            }

+                        }

+                        c = s.charAt(j);

+                        if (c === x) {

+                            character += 1;

+                            s = s.substr(j + 1);

+                            return it('(string)', r, x);

+                        }

+                        if (c < ' ') {

+                            if (c === '\n' || c === '\r') {

+                                break;

+                            }

+                            warningAt("Control character in string: {a}.",

+                                    line, character + j, s.slice(0, j));

+                        } else if (c === '\\') {

+                            j += 1;

+                            character += 1;

+                            c = s.charAt(j);

+                            switch (c) {

+                            case '\\':

+                            case '"':

+                            case '/':

+                                break;

+                            case '\'':

+                                if (jsonmode) {

+                                    warningAt("Avoid \\'.", line, character);

+                                }

+                                break;

+                            case 'b':

+                                c = '\b';

+                                break;

+                            case 'f':

+                                c = '\f';

+                                break;

+                            case 'n':

+                                c = '\n';

+                                break;

+                            case 'r':

+                                c = '\r';

+                                break;

+                            case 't':

+                                c = '\t';

+                                break;

+                            case 'u':

+                                esc(4);

+                                break;

+                            case 'v':

+                                if (jsonmode) {

+                                    warningAt("Avoid \\v.", line, character);

+                                }

+                                c = '\v';

+                                break;

+                            case 'x':

+                                if (jsonmode) {

+                                    warningAt("Avoid \\x-.", line, character);

+                                }

+                                esc(2);

+                                break;

+                            default:

+                                warningAt("Bad escapement.", line, character);

+                            }

+                        }

+                        r += c;

+                        character += 1;

+                        j += 1;

+                    }

+                }

+

+                for (;;) {

+                    if (!s) {

+                        return it(nextLine() ? '(endline)' : '(end)', '');

+                    }

+                    t = match(tx);

+                    if (!t) {

+                        t = '';

+                        c = '';

+                        while (s && s < '!') {

+                            s = s.substr(1);

+                        }

+                        if (s) {

+                            errorAt("Unexpected '{a}'.", line, character, s.substr(0, 1));

+                        }

+                    } else {

+

+    //      identifier

+

+                        if (c.isAlpha() || c === '_' || c === '$') {

+                            return it('(identifier)', t);

+                        }

+

+    //      number

+

+                        if (c.isDigit()) {

+                            if (!isFinite(Number(t))) {

+                                warningAt("Bad number '{a}'.",

+                                    line, character, t);

+                            }

+                            if (s.substr(0, 1).isAlpha()) {

+                                warningAt("Missing space after '{a}'.",

+                                        line, character, t);

+                            }

+                            if (c === '0') {

+                                d = t.substr(1, 1);

+                                if (d.isDigit()) {

+                                    if (token.id !== '.') {

+                                        warningAt("Don't use extra leading zeros '{a}'.",

+                                            line, character, t);

+                                    }

+                                } else if (jsonmode && (d === 'x' || d === 'X')) {

+                                    warningAt("Avoid 0x-. '{a}'.",

+                                            line, character, t);

+                                }

+                            }

+                            if (t.substr(t.length - 1) === '.') {

+                                warningAt(

+"A trailing decimal point can be confused with a dot '{a}'.", line, character, t);

+                            }

+                            return it('(number)', t);

+                        }

+                        switch (t) {

+

+    //      string

+

+                        case '"':

+                        case "'":

+                            return string(t);

+

+    //      // comment

+

+                        case '//':

+                            if (src) {

+                                warningAt("Unexpected comment.", line, character);

+                            }

+                            s = '';

+                            token.comment = true;

+                            break;

+

+    //      /* comment

+

+                        case '/*':

+                            if (src) {

+                                warningAt("Unexpected comment.", line, character);

+                            }

+                            for (;;) {

+                                i = s.search(lx);

+                                if (i >= 0) {

+                                    break;

+                                }

+                                if (!nextLine()) {

+                                    errorAt("Unclosed comment.", line, character);

+                                }

+                            }

+                            character += i + 2;

+                            if (s.substr(i, 1) === '/') {

+                                errorAt("Nested comment.", line, character);

+                            }

+                            s = s.substr(i + 2);

+                            token.comment = true;

+                            break;

+

+    //      /*members /*jshint /*global

+

+                        case '/*members':

+                        case '/*member':

+                        case '/*jshint':

+                        case '/*jslint':

+                        case '/*global':

+                        case '*/':

+                            return {

+                                value: t,

+                                type: 'special',

+                                line: line,

+                                character: character,

+                                from: from

+                            };

+

+                        case '':

+                            break;

+    //      /

+                        case '/':

+                            if (token.id === '/=') {

+                                errorAt(

+"A regular expression literal can be confused with '/='.", line, from);

+                            }

+                            if (prereg) {

+                                depth = 0;

+                                captures = 0;

+                                l = 0;

+                                for (;;) {

+                                    b = true;

+                                    c = s.charAt(l);

+                                    l += 1;

+                                    switch (c) {

+                                    case '':

+                                        errorAt("Unclosed regular expression.",

+                                                line, from);

+                                        return;

+                                    case '/':

+                                        if (depth > 0) {

+                                            warningAt("Unescaped '{a}'.",

+                                                    line, from + l, '/');

+                                        }

+                                        c = s.substr(0, l - 1);

+                                        q = {

+                                            g: true,

+                                            i: true,

+                                            m: true

+                                        };

+                                        while (q[s.charAt(l)] === true) {

+                                            q[s.charAt(l)] = false;

+                                            l += 1;

+                                        }

+                                        character += l;

+                                        s = s.substr(l);

+                                        q = s.charAt(0);

+                                        if (q === '/' || q === '*') {

+                                            errorAt("Confusing regular expression.",

+                                                    line, from);

+                                        }

+                                        return it('(regexp)', c);

+                                    case '\\':

+                                        c = s.charAt(l);

+                                        if (c < ' ') {

+                                            warningAt(

+"Unexpected control character in regular expression.", line, from + l);

+                                        } else if (c === '<') {

+                                            warningAt(

+"Unexpected escaped character '{a}' in regular expression.", line, from + l, c);

+                                        }

+                                        l += 1;

+                                        break;

+                                    case '(':

+                                        depth += 1;

+                                        b = false;

+                                        if (s.charAt(l) === '?') {

+                                            l += 1;

+                                            switch (s.charAt(l)) {

+                                            case ':':

+                                            case '=':

+                                            case '!':

+                                                l += 1;

+                                                break;

+                                            default:

+                                                warningAt(

+"Expected '{a}' and instead saw '{b}'.", line, from + l, ':', s.charAt(l));

+                                            }

+                                        } else {

+                                            captures += 1;

+                                        }

+                                        break;

+                                    case '|':

+                                        b = false;

+                                        break;

+                                    case ')':

+                                        if (depth === 0) {

+                                            warningAt("Unescaped '{a}'.",

+                                                    line, from + l, ')');

+                                        } else {

+                                            depth -= 1;

+                                        }

+                                        break;

+                                    case ' ':

+                                        q = 1;

+                                        while (s.charAt(l) === ' ') {

+                                            l += 1;

+                                            q += 1;

+                                        }

+                                        if (q > 1) {

+                                            warningAt(

+"Spaces are hard to count. Use {{a}}.", line, from + l, q);

+                                        }

+                                        break;

+                                    case '[':

+                                        c = s.charAt(l);

+                                        if (c === '^') {

+                                            l += 1;

+                                            if (option.regexp) {

+                                                warningAt("Insecure '{a}'.",

+                                                        line, from + l, c);

+                                            } else if (s.charAt(l) === ']') {

+                                                errorAt("Unescaped '{a}'.",

+                                                    line, from + l, '^');

+                                            }

+                                        }

+                                        q = false;

+                                        if (c === ']') {

+                                            warningAt("Empty class.", line,

+                                                    from + l - 1);

+                                            q = true;

+                                        }

+klass:                                  do {

+                                            c = s.charAt(l);

+                                            l += 1;

+                                            switch (c) {

+                                            case '[':

+                                            case '^':

+                                                warningAt("Unescaped '{a}'.",

+                                                        line, from + l, c);

+                                                q = true;

+                                                break;

+                                            case '-':

+                                                if (q) {

+                                                    q = false;

+                                                } else {

+                                                    warningAt("Unescaped '{a}'.",

+                                                            line, from + l, '-');

+                                                    q = true;

+                                                }

+                                                break;

+                                            case ']':

+                                                if (!q) {

+                                                    warningAt("Unescaped '{a}'.",

+                                                            line, from + l - 1, '-');

+                                                }

+                                                break klass;

+                                            case '\\':

+                                                c = s.charAt(l);

+                                                if (c < ' ') {

+                                                    warningAt(

+"Unexpected control character in regular expression.", line, from + l);

+                                                } else if (c === '<') {

+                                                    warningAt(

+"Unexpected escaped character '{a}' in regular expression.", line, from + l, c);

+                                                }

+                                                l += 1;

+                                                q = true;

+                                                break;

+                                            case '/':

+                                                warningAt("Unescaped '{a}'.",

+                                                        line, from + l - 1, '/');

+                                                q = true;

+                                                break;

+                                            case '<':

+                                                q = true;

+                                                break;

+                                            default:

+                                                q = true;

+                                            }

+                                        } while (c);

+                                        break;

+                                    case '.':

+                                        if (option.regexp) {

+                                            warningAt("Insecure '{a}'.", line,

+                                                    from + l, c);

+                                        }

+                                        break;

+                                    case ']':

+                                    case '?':

+                                    case '{':

+                                    case '}':

+                                    case '+':

+                                    case '*':

+                                        warningAt("Unescaped '{a}'.", line,

+                                                from + l, c);

+                                    }

+                                    if (b) {

+                                        switch (s.charAt(l)) {

+                                        case '?':

+                                        case '+':

+                                        case '*':

+                                            l += 1;

+                                            if (s.charAt(l) === '?') {

+                                                l += 1;

+                                            }

+                                            break;

+                                        case '{':

+                                            l += 1;

+                                            c = s.charAt(l);

+                                            if (c < '0' || c > '9') {

+                                                warningAt(

+"Expected a number and instead saw '{a}'.", line, from + l, c);

+                                            }

+                                            l += 1;

+                                            low = +c;

+                                            for (;;) {

+                                                c = s.charAt(l);

+                                                if (c < '0' || c > '9') {

+                                                    break;

+                                                }

+                                                l += 1;

+                                                low = +c + (low * 10);

+                                            }

+                                            high = low;

+                                            if (c === ',') {

+                                                l += 1;

+                                                high = Infinity;

+                                                c = s.charAt(l);

+                                                if (c >= '0' && c <= '9') {

+                                                    l += 1;

+                                                    high = +c;

+                                                    for (;;) {

+                                                        c = s.charAt(l);

+                                                        if (c < '0' || c > '9') {

+                                                            break;

+                                                        }

+                                                        l += 1;

+                                                        high = +c + (high * 10);

+                                                    }

+                                                }

+                                            }

+                                            if (s.charAt(l) !== '}') {

+                                                warningAt(

+"Expected '{a}' and instead saw '{b}'.", line, from + l, '}', c);

+                                            } else {

+                                                l += 1;

+                                            }

+                                            if (s.charAt(l) === '?') {

+                                                l += 1;

+                                            }

+                                            if (low > high) {

+                                                warningAt(

+"'{a}' should not be greater than '{b}'.", line, from + l, low, high);

+                                            }

+                                        }

+                                    }

+                                }

+                                c = s.substr(0, l - 1);

+                                character += l;

+                                s = s.substr(l);

+                                return it('(regexp)', c);

+                            }

+                            return it('(punctuator)', t);

+

+    //      punctuator

+

+                        case '#':

+                            return it('(punctuator)', t);

+                        default:

+                            return it('(punctuator)', t);

+                        }

+                    }

+                }

+            }

+        };

+    }());

+

+

+    function addlabel(t, type) {

+

+        if (t === 'hasOwnProperty') {

+            warning("'hasOwnProperty' is a really bad name.");

+        }

+

+// Define t in the current function in the current scope.

+

+        if (is_own(funct, t) && !funct['(global)']) {

+            if (funct[t] === true) {

+                if (option.latedef)

+                    warning("'{a}' was used before it was defined.", nexttoken, t);

+            } else {

+                if (!option.shadow)

+                    warning("'{a}' is already defined.", nexttoken, t);

+            }

+        }

+

+        funct[t] = type;

+        if (funct['(global)']) {

+            global[t] = funct;

+            if (is_own(implied, t)) {

+                if (option.latedef)

+                    warning("'{a}' was used before it was defined.", nexttoken, t);

+                delete implied[t];

+            }

+        } else {

+            scope[t] = funct;

+        }

+    }

+

+

+    function doOption() {

+        var b, obj, filter, o = nexttoken.value, t, v;

+        switch (o) {

+        case '*/':

+            error("Unbegun comment.");

+            break;

+        case '/*members':

+        case '/*member':

+            o = '/*members';

+            if (!membersOnly) {

+                membersOnly = {};

+            }

+            obj = membersOnly;

+            break;

+        case '/*jshint':

+        case '/*jslint':

+            obj = option;

+            filter = boolOptions;

+            break;

+        case '/*global':

+            obj = predefined;

+            break;

+        default:

+            error("What?");

+        }

+        t = lex.token();

+loop:   for (;;) {

+            for (;;) {

+                if (t.type === 'special' && t.value === '*/') {

+                    break loop;

+                }

+                if (t.id !== '(endline)' && t.id !== ',') {

+                    break;

+                }

+                t = lex.token();

+            }

+            if (t.type !== '(string)' && t.type !== '(identifier)' &&

+                    o !== '/*members') {

+                error("Bad option.", t);

+            }

+            v = lex.token();

+            if (v.id === ':') {

+                v = lex.token();

+                if (obj === membersOnly) {

+                    error("Expected '{a}' and instead saw '{b}'.",

+                            t, '*/', ':');

+                }

+                if (t.value === 'indent' && (o === '/*jshint' || o === '/*jslint')) {

+                    b = +v.value;

+                    if (typeof b !== 'number' || !isFinite(b) || b <= 0 ||

+                            Math.floor(b) !== b) {

+                        error("Expected a small integer and instead saw '{a}'.",

+                                v, v.value);

+                    }

+                    obj.white = true;

+                    obj.indent = b;

+                } else if (t.value === 'maxerr' && (o === '/*jshint' || o === '/*jslint')) {

+                    b = +v.value;

+                    if (typeof b !== 'number' || !isFinite(b) || b <= 0 ||

+                            Math.floor(b) !== b) {

+                        error("Expected a small integer and instead saw '{a}'.",

+                                v, v.value);

+                    }

+                    obj.maxerr = b;

+                } else if (t.value === 'maxlen' && (o === '/*jshint' || o === '/*jslint')) {

+                    b = +v.value;

+                    if (typeof b !== 'number' || !isFinite(b) || b <= 0 ||

+                            Math.floor(b) !== b) {

+                        error("Expected a small integer and instead saw '{a}'.",

+                                v, v.value);

+                    }

+                    obj.maxlen = b;

+                } else if (v.value === 'true') {

+                    obj[t.value] = true;

+                } else if (v.value === 'false') {

+                    obj[t.value] = false;

+                } else {

+                    error("Bad option value.", v);

+                }

+                t = lex.token();

+            } else {

+                if (o === '/*jshint' || o === '/*jslint') {

+                    error("Missing option value.", t);

+                }

+                obj[t.value] = false;

+                t = v;

+            }

+        }

+        if (filter) {

+            assume();

+        }

+    }

+

+

+// We need a peek function. If it has an argument, it peeks that much farther

+// ahead. It is used to distinguish

+//     for ( var i in ...

+// from

+//     for ( var i = ...

+

+    function peek(p) {

+        var i = p || 0, j = 0, t;

+

+        while (j <= i) {

+            t = lookahead[j];

+            if (!t) {

+                t = lookahead[j] = lex.token();

+            }

+            j += 1;

+        }

+        return t;

+    }

+

+

+

+// Produce the next token. It looks for programming errors.

+

+    function advance(id, t) {

+        switch (token.id) {

+        case '(number)':

+            if (nexttoken.id === '.') {

+                warning("A dot following a number can be confused with a decimal point.", token);

+            }

+            break;

+        case '-':

+            if (nexttoken.id === '-' || nexttoken.id === '--') {

+                warning("Confusing minusses.");

+            }

+            break;

+        case '+':

+            if (nexttoken.id === '+' || nexttoken.id === '++') {

+                warning("Confusing plusses.");

+            }

+            break;

+        }

+        if (token.type === '(string)' || token.identifier) {

+            anonname = token.value;

+        }

+

+        if (id && nexttoken.id !== id) {

+            if (t) {

+                if (nexttoken.id === '(end)') {

+                    warning("Unmatched '{a}'.", t, t.id);

+                } else {

+                    warning("Expected '{a}' to match '{b}' from line {c} and instead saw '{d}'.",

+                            nexttoken, id, t.id, t.line, nexttoken.value);

+                }

+            } else if (nexttoken.type !== '(identifier)' ||

+                            nexttoken.value !== id) {

+                warning("Expected '{a}' and instead saw '{b}'.",

+                        nexttoken, id, nexttoken.value);

+            }

+        }

+        prevtoken = token;

+        token = nexttoken;

+        for (;;) {

+            nexttoken = lookahead.shift() || lex.token();

+            if (nexttoken.id === '(end)' || nexttoken.id === '(error)') {

+                return;

+            }

+            if (nexttoken.type === 'special') {

+                doOption();

+            } else {

+                if (nexttoken.id !== '(endline)') {

+                    break;

+                }

+            }

+        }

+    }

+

+

+// This is the heart of JSHINT, the Pratt parser. In addition to parsing, it

+// is looking for ad hoc lint patterns. We add .fud to Pratt's model, which is

+// like .nud except that it is only used on the first token of a statement.

+// Having .fud makes it much easier to define statement-oriented languages like

+// JavaScript. I retained Pratt's nomenclature.

+

+// .nud     Null denotation

+// .fud     First null denotation

+// .led     Left denotation

+//  lbp     Left binding power

+//  rbp     Right binding power

+

+// They are elements of the parsing method called Top Down Operator Precedence.

+

+    function expression(rbp, initial) {

+        var left, isArray = false;

+

+        if (nexttoken.id === '(end)')

+            error("Unexpected early end of program.", token);

+

+        advance();

+        if (initial) {

+            anonname = 'anonymous';

+            funct['(verb)'] = token.value;

+        }

+        if (initial === true && token.fud) {

+            left = token.fud();

+        } else {

+            if (token.nud) {

+                left = token.nud();

+            } else {

+                if (nexttoken.type === '(number)' && token.id === '.') {

+                    warning("A leading decimal point can be confused with a dot: '.{a}'.",

+                            token, nexttoken.value);

+                    advance();

+                    return token;

+                } else {

+                    error("Expected an identifier and instead saw '{a}'.",

+                            token, token.id);

+                }

+            }

+            while (rbp < nexttoken.lbp) {

+                isArray = token.value == 'Array';

+                advance();

+                if (isArray && token.id == '(' && nexttoken.id == ')')

+                    warning("Use the array literal notation [].", token);

+                if (token.led) {

+                    left = token.led(left);

+                } else {

+                    error("Expected an operator and instead saw '{a}'.",

+                        token, token.id);

+                }

+            }

+        }

+        return left;

+    }

+

+

+// Functions for conformance of style.

+

+    function adjacent(left, right) {

+        left = left || token;

+        right = right || nexttoken;

+        if (option.white) {

+            if (left.character !== right.from && left.line === right.line) {

+                warning("Unexpected space after '{a}'.", right, left.value);

+            }

+        }

+    }

+

+    function nobreak(left, right) {

+        left = left || token;

+        right = right || nexttoken;

+        if (option.white && (left.character !== right.from || left.line !== right.line)) {

+            warning("Unexpected space before '{a}'.", right, right.value);

+        }

+    }

+

+    function nospace(left, right) {

+        left = left || token;

+        right = right || nexttoken;

+        if (option.white && !left.comment) {

+            if (left.line === right.line) {

+                adjacent(left, right);

+            }

+        }

+    }

+

+    function nonadjacent(left, right) {

+        if (option.white) {

+            left = left || token;

+            right = right || nexttoken;

+            if (left.line === right.line && left.character === right.from) {

+                warning("Missing space after '{a}'.",

+                        nexttoken, left.value);

+            }

+        }

+    }

+

+    function nobreaknonadjacent(left, right) {

+        left = left || token;

+        right = right || nexttoken;

+        if (!option.laxbreak && left.line !== right.line) {

+            warning("Bad line breaking before '{a}'.", right, right.id);

+        } else if (option.white) {

+            left = left || token;

+            right = right || nexttoken;

+            if (left.character === right.from) {

+                warning("Missing space after '{a}'.",

+                        nexttoken, left.value);

+            }

+        }

+    }

+

+    function indentation(bias) {

+        var i;

+        if (option.white && nexttoken.id !== '(end)') {

+            i = indent + (bias || 0);

+            if (nexttoken.from !== i) {

+                warning(

+"Expected '{a}' to have an indentation at {b} instead at {c}.",

+                        nexttoken, nexttoken.value, i, nexttoken.from);

+            }

+        }

+    }

+

+    function nolinebreak(t) {

+        t = t || token;

+        if (t.line !== nexttoken.line) {

+            warning("Line breaking error '{a}'.", t, t.value);

+        }

+    }

+

+

+    function comma() {

+        if (token.line !== nexttoken.line) {

+            if (!option.laxbreak) {

+                warning("Bad line breaking before '{a}'.", token, nexttoken.id);

+            }

+        } else if (token.character !== nexttoken.from && option.white) {

+            warning("Unexpected space after '{a}'.", nexttoken, token.value);

+        }

+        advance(',');

+        nonadjacent(token, nexttoken);

+    }

+

+

+// Functional constructors for making the symbols that will be inherited by

+// tokens.

+

+    function symbol(s, p) {

+        var x = syntax[s];

+        if (!x || typeof x !== 'object') {

+            syntax[s] = x = {

+                id: s,

+                lbp: p,

+                value: s

+            };

+        }

+        return x;

+    }

+

+

+    function delim(s) {

+        return symbol(s, 0);

+    }

+

+

+    function stmt(s, f) {

+        var x = delim(s);

+        x.identifier = x.reserved = true;

+        x.fud = f;

+        return x;

+    }

+

+

+    function blockstmt(s, f) {

+        var x = stmt(s, f);

+        x.block = true;

+        return x;

+    }

+

+

+    function reserveName(x) {

+        var c = x.id.charAt(0);

+        if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {

+            x.identifier = x.reserved = true;

+        }

+        return x;

+    }

+

+

+    function prefix(s, f) {

+        var x = symbol(s, 150);

+        reserveName(x);

+        x.nud = (typeof f === 'function') ? f : function () {

+            this.right = expression(150);

+            this.arity = 'unary';

+            if (this.id === '++' || this.id === '--') {

+                if (option.plusplus) {

+                    warning("Unexpected use of '{a}'.", this, this.id);

+                } else if ((!this.right.identifier || this.right.reserved) &&

+                        this.right.id !== '.' && this.right.id !== '[') {

+                    warning("Bad operand.", this);

+                }

+            }

+            return this;

+        };

+        return x;

+    }

+

+

+    function type(s, f) {

+        var x = delim(s);

+        x.type = s;

+        x.nud = f;

+        return x;

+    }

+

+

+    function reserve(s, f) {

+        var x = type(s, f);

+        x.identifier = x.reserved = true;

+        return x;

+    }

+

+

+    function reservevar(s, v) {

+        return reserve(s, function () {

+            if (typeof v === 'function') {

+                v(this);

+            }

+            return this;

+        });

+    }

+

+

+    function infix(s, f, p, w) {

+        var x = symbol(s, p);

+        reserveName(x);

+        x.led = function (left) {

+            if (!w) {

+                nobreaknonadjacent(prevtoken, token);

+                nonadjacent(token, nexttoken);

+            }

+            if (typeof f === 'function') {

+                return f(left, this);

+            } else {

+                this.left = left;

+                this.right = expression(p);

+                return this;

+            }

+        };

+        return x;

+    }

+

+

+    function relation(s, f) {

+        var x = symbol(s, 100);

+        x.led = function (left) {

+            nobreaknonadjacent(prevtoken, token);

+            nonadjacent(token, nexttoken);

+            var right = expression(100);

+            if ((left && left.id === 'NaN') || (right && right.id === 'NaN')) {

+                warning("Use the isNaN function to compare with NaN.", this);

+            } else if (f) {

+                f.apply(this, [left, right]);

+            }

+            if (left.id === '!') {

+                warning("Confusing use of '{a}'.", left, '!');

+            }

+            if (right.id === '!') {

+                warning("Confusing use of '{a}'.", left, '!');

+            }

+            this.left = left;

+            this.right = right;

+            return this;

+        };

+        return x;

+    }

+

+

+    function isPoorRelation(node) {

+        return node &&

+              ((node.type === '(number)' && +node.value === 0) ||

+               (node.type === '(string)' && node.value === '') ||

+               (node.type === 'null' && !option.eqnull) ||

+                node.type === 'true' ||

+                node.type === 'false' ||

+                node.type === 'undefined');

+    }

+

+

+    function assignop(s, f) {

+        symbol(s, 20).exps = true;

+        return infix(s, function (left, that) {

+            var l;

+            that.left = left;

+            if (predefined[left.value] === false &&

+                    scope[left.value]['(global)'] === true) {

+                warning("Read only.", left);

+            } else if (left['function']) {

+                warning("'{a}' is a function.", left, left.value);

+            }

+            if (left) {

+                if (left.id === '.' || left.id === '[') {

+                    if (!left.left || left.left.value === 'arguments') {

+                        warning('Bad assignment.', that);

+                    }

+                    that.right = expression(19);

+                    return that;

+                } else if (left.identifier && !left.reserved) {

+                    if (funct[left.value] === 'exception') {

+                        warning("Do not assign to the exception parameter.", left);

+                    }

+                    that.right = expression(19);

+                    return that;

+                }

+                if (left === syntax['function']) {

+                    warning(

+"Expected an identifier in an assignment and instead saw a function invocation.",

+                                token);

+                }

+            }

+            error("Bad assignment.", that);

+        }, 20);

+    }

+

+

+    function bitwise(s, f, p) {

+        var x = symbol(s, p);

+        reserveName(x);

+        x.led = (typeof f === 'function') ? f : function (left) {

+            if (option.bitwise) {

+                warning("Unexpected use of '{a}'.", this, this.id);

+            }

+            this.left = left;

+            this.right = expression(p);

+            return this;

+        };

+        return x;

+    }

+

+

+    function bitwiseassignop(s) {

+        symbol(s, 20).exps = true;

+        return infix(s, function (left, that) {

+            if (option.bitwise) {

+                warning("Unexpected use of '{a}'.", that, that.id);

+            }

+            nonadjacent(prevtoken, token);

+            nonadjacent(token, nexttoken);

+            if (left) {

+                if (left.id === '.' || left.id === '[' ||

+                        (left.identifier && !left.reserved)) {

+                    expression(19);

+                    return that;

+                }

+                if (left === syntax['function']) {

+                    warning(

+"Expected an identifier in an assignment, and instead saw a function invocation.",

+                                token);

+                }

+                return that;

+            }

+            error("Bad assignment.", that);

+        }, 20);

+    }

+

+

+    function suffix(s, f) {

+        var x = symbol(s, 150);

+        x.led = function (left) {

+            if (option.plusplus) {

+                warning("Unexpected use of '{a}'.", this, this.id);

+            } else if ((!left.identifier || left.reserved) &&

+                    left.id !== '.' && left.id !== '[') {

+                warning("Bad operand.", this);

+            }

+            this.left = left;

+            return this;

+        };

+        return x;

+    }

+

+

+    // fnparam means that this identifier is being defined as a function

+    // argument (see identifier())

+    function optionalidentifier(fnparam) {

+        if (nexttoken.identifier) {

+            advance();

+            if (token.reserved && !option.es5) {

+                // `undefined` as a function param is a common pattern to protect

+                // against the case when somebody does `undefined = true` and

+                // help with minification. More info: https://gist.github.com/315916

+                if (!fnparam || token.value != 'undefined') {

+                    warning("Expected an identifier and instead saw '{a}' (a reserved word).",

+                            token, token.id);

+                }

+            }

+            return token.value;

+        }

+    }

+

+    // fnparam means that this identifier is being defined as a function

+    // argument

+    function identifier(fnparam) {

+        var i = optionalidentifier(fnparam);

+        if (i) {

+            return i;

+        }

+        if (token.id === 'function' && nexttoken.id === '(') {

+            warning("Missing name in function declaration.");

+        } else {

+            error("Expected an identifier and instead saw '{a}'.",

+                    nexttoken, nexttoken.value);

+        }

+    }

+

+

+    function reachable(s) {

+        var i = 0, t;

+        if (nexttoken.id !== ';' || noreach) {

+            return;

+        }

+        for (;;) {

+            t = peek(i);

+            if (t.reach) {

+                return;

+            }

+            if (t.id !== '(endline)') {

+                if (t.id === 'function') {

+                    warning(

+"Inner functions should be listed at the top of the outer function.", t);

+                    break;

+                }

+                warning("Unreachable '{a}' after '{b}'.", t, t.value, s);

+                break;

+            }

+            i += 1;

+        }

+    }

+

+

+    function statement(noindent) {

+        var i = indent, r, s = scope, t = nexttoken;

+

+// We don't like the empty statement.

+

+        if (t.id === ';') {

+            warning("Unnecessary semicolon.", t);

+            advance(';');

+            return;

+        }

+

+// Is this a labelled statement?

+

+        if (t.identifier && !t.reserved && peek().id === ':') {

+            advance();

+            advance(':');

+            scope = Object.create(s);

+            addlabel(t.value, 'label');

+            if (!nexttoken.labelled) {

+                warning("Label '{a}' on {b} statement.",

+                        nexttoken, t.value, nexttoken.value);

+            }

+            if (jx.test(t.value + ':')) {

+                warning("Label '{a}' looks like a javascript url.",

+                        t, t.value);

+            }

+            nexttoken.label = t.value;

+            t = nexttoken;

+        }

+

+// Parse the statement.

+

+        if (!noindent) {

+            indentation();

+        }

+        r = expression(0, true);

+

+// Look for the final semicolon.

+

+        if (!t.block) {

+            if (!option.expr && (!r || !r.exps)) {

+                warning("Expected an assignment or function call and instead saw an expression.", token);

+            } else if (option.nonew && r.id === '(' && r.left.id === 'new') {

+                warning("Do not use 'new' for side effects.");

+            }

+            if (nexttoken.id !== ';') {

+                if (!option.asi) {

+                    warningAt("Missing semicolon.", token.line, token.from + token.value.length);

+                }

+            } else {

+                adjacent(token, nexttoken);

+                advance(';');

+                nonadjacent(token, nexttoken);

+            }

+        }

+

+// Restore the indentation.

+

+        indent = i;

+        scope = s;

+        return r;

+    }

+

+

+    function use_strict() {

+        if (nexttoken.value === 'use strict') {

+            if (strict_mode) {

+                warning("Unnecessary \"use strict\".");

+            }

+            advance();

+            advance(';');

+            strict_mode = true;

+            option.newcap = true;

+            option.undef = true;

+            return true;

+        } else {

+            return false;

+        }

+    }

+

+

+    function statements(begin) {

+        var a = [], f, p;

+

+        while (!nexttoken.reach && nexttoken.id !== '(end)') {

+            if (nexttoken.id === ';') {

+                warning("Unnecessary semicolon.");

+                advance(';');

+            } else {

+                a.push(statement());

+            }

+        }

+        return a;

+    }

+

+

+    /*

+     * Parses a single block. A block is a sequence of statements wrapped in

+     * braces.

+     *

+     * ordinary - true for everything but function bodies and try blocks.

+     * stmt     - true if block can be a single statement (e.g. in if/for/while).

+     */

+    function block(ordinary, stmt) {

+        var a,

+            b = inblock,

+            old_indent = indent,

+            m = strict_mode,

+            s = scope,

+            t;

+

+        inblock = ordinary;

+        scope = Object.create(scope);

+        nonadjacent(token, nexttoken);

+        t = nexttoken;

+

+        if (nexttoken.id === '{') {

+            advance('{');

+            if (nexttoken.id !== '}' || token.line !== nexttoken.line) {

+                indent += option.indent;

+                while (!ordinary && nexttoken.from > indent) {

+                    indent += option.indent;

+                }

+                if (!ordinary && !use_strict() && !m && option.strict &&

+                        funct['(context)']['(global)']) {

+                    warning("Missing \"use strict\" statement.");

+                }

+                a = statements();

+                strict_mode = m;

+                indent -= option.indent;

+                indentation();

+            }

+            advance('}', t);

+            indent = old_indent;

+        } else if (!ordinary) {

+            error("Expected '{a}' and instead saw '{b}'.",

+                  nexttoken, '{', nexttoken.value);

+        } else {

+            if (!stmt || option.curly)

+                warning("Expected '{a}' and instead saw '{b}'.",

+                        nexttoken, '{', nexttoken.value);

+

+            noreach = true;

+            a = [statement()];

+            noreach = false;

+        }

+        funct['(verb)'] = null;

+        scope = s;

+        inblock = b;

+        if (ordinary && option.noempty && (!a || a.length === 0)) {

+            warning("Empty block.");

+        }

+        return a;

+    }

+

+

+    function countMember(m) {

+        if (membersOnly && typeof membersOnly[m] !== 'boolean') {

+            warning("Unexpected /*member '{a}'.", token, m);

+        }

+        if (typeof member[m] === 'number') {

+            member[m] += 1;

+        } else {

+            member[m] = 1;

+        }

+    }

+

+

+    function note_implied(token) {

+        var name = token.value, line = token.line, a = implied[name];

+        if (typeof a === 'function') {

+            a = false;

+        }

+        if (!a) {

+            a = [line];

+            implied[name] = a;

+        } else if (a[a.length - 1] !== line) {

+            a.push(line);

+        }

+    }

+

+// Build the syntax table by declaring the syntactic elements of the language.

+

+    type('(number)', function () {

+        return this;

+    });

+    type('(string)', function () {

+        return this;

+    });

+

+    syntax['(identifier)'] = {

+        type: '(identifier)',

+        lbp: 0,

+        identifier: true,

+        nud: function () {

+            var v = this.value,

+                s = scope[v],

+                f;

+            if (typeof s === 'function') {

+

+// Protection against accidental inheritance.

+

+                s = undefined;

+            } else if (typeof s === 'boolean') {

+                f = funct;

+                funct = functions[0];

+                addlabel(v, 'var');

+                s = funct;

+                funct = f;

+            }

+

+// The name is in scope and defined in the current function.

+

+            if (funct === s) {

+

+//      Change 'unused' to 'var', and reject labels.

+

+                switch (funct[v]) {

+                case 'unused':

+                    funct[v] = 'var';

+                    break;

+                case 'unction':

+                    funct[v] = 'function';

+                    this['function'] = true;

+                    break;

+                case 'function':

+                    this['function'] = true;

+                    break;

+                case 'label':

+                    warning("'{a}' is a statement label.", token, v);

+                    break;

+                }

+

+// The name is not defined in the function.  If we are in the global scope,

+// then we have an undefined variable.

+//

+// Operators typeof and delete do not raise runtime errors even if the base

+// object of a reference is null so no need to display warning if we're

+// inside of typeof or delete.

+

+            } else if (funct['(global)']) {

+                if (anonname != 'typeof' && anonname != 'delete' &&

+                    option.undef && typeof predefined[v] !== 'boolean') {

+                    warning("'{a}' is not defined.", token, v);

+                }

+                note_implied(token);

+

+// If the name is already defined in the current

+// function, but not as outer, then there is a scope error.

+

+            } else {

+                switch (funct[v]) {

+                case 'closure':

+                case 'function':

+                case 'var':

+                case 'unused':

+                    warning("'{a}' used out of scope.", token, v);

+                    break;

+                case 'label':

+                    warning("'{a}' is a statement label.", token, v);

+                    break;

+                case 'outer':

+                case 'global':

+                    break;

+                default:

+

+// If the name is defined in an outer function, make an outer entry, and if

+// it was unused, make it var.

+

+                    if (s === true) {

+                        funct[v] = true;

+                    } else if (s === null) {

+                        warning("'{a}' is not allowed.", token, v);

+                        note_implied(token);

+                    } else if (typeof s !== 'object') {

+

+// Operators typeof and delete do not raise runtime errors even if the base object of

+// a reference is null so no need to display warning if we're inside of typeof or delete.

+

+                        if (anonname != 'typeof' && anonname != 'delete' && option.undef) {

+                            warning("'{a}' is not defined.", token, v);

+                        } else {

+                            funct[v] = true;

+                        }

+                        note_implied(token);

+                    } else {

+                        switch (s[v]) {

+                        case 'function':

+                        case 'unction':

+                            this['function'] = true;

+                            s[v] = 'closure';

+                            funct[v] = s['(global)'] ? 'global' : 'outer';

+                            break;

+                        case 'var':

+                        case 'unused':

+                            s[v] = 'closure';

+                            funct[v] = s['(global)'] ? 'global' : 'outer';

+                            break;

+                        case 'closure':

+                        case 'parameter':

+                            funct[v] = s['(global)'] ? 'global' : 'outer';

+                            break;

+                        case 'label':

+                            warning("'{a}' is a statement label.", token, v);

+                        }

+                    }

+                }

+            }

+            return this;

+        },

+        led: function () {

+            error("Expected an operator and instead saw '{a}'.",

+                nexttoken, nexttoken.value);

+        }

+    };

+

+    type('(regexp)', function () {

+        return this;

+    });

+

+

+// ECMAScript parser

+

+    delim('(endline)');

+    delim('(begin)');

+    delim('(end)').reach = true;

+    delim('</').reach = true;

+    delim('<!');

+    delim('<!--');

+    delim('-->');

+    delim('(error)').reach = true;

+    delim('}').reach = true;

+    delim(')');

+    delim(']');

+    delim('"').reach = true;

+    delim("'").reach = true;

+    delim(';');

+    delim(':').reach = true;

+    delim(',');

+    delim('#');

+    delim('@');

+    reserve('else');

+    reserve('case').reach = true;

+    reserve('catch');

+    reserve('default').reach = true;

+    reserve('finally');

+    reservevar('arguments', function (x) {

+        if (strict_mode && funct['(global)']) {

+            warning("Strict violation.", x);

+        }

+    });

+    reservevar('eval');

+    reservevar('false');

+    reservevar('Infinity');

+    reservevar('NaN');

+    reservevar('null');

+    reservevar('this', function (x) {

+        if (strict_mode && ((funct['(statement)'] &&

+                funct['(name)'].charAt(0) > 'Z') || funct['(global)'])) {

+            warning("Strict violation.", x);

+        }

+    });

+    reservevar('true');

+    reservevar('undefined');

+    assignop('=', 'assign', 20);

+    assignop('+=', 'assignadd', 20);

+    assignop('-=', 'assignsub', 20);

+    assignop('*=', 'assignmult', 20);

+    assignop('/=', 'assigndiv', 20).nud = function () {

+        error("A regular expression literal can be confused with '/='.");

+    };

+    assignop('%=', 'assignmod', 20);

+    bitwiseassignop('&=', 'assignbitand', 20);

+    bitwiseassignop('|=', 'assignbitor', 20);

+    bitwiseassignop('^=', 'assignbitxor', 20);

+    bitwiseassignop('<<=', 'assignshiftleft', 20);

+    bitwiseassignop('>>=', 'assignshiftright', 20);

+    bitwiseassignop('>>>=', 'assignshiftrightunsigned', 20);

+    infix('?', function (left, that) {

+        that.left = left;

+        that.right = expression(10);

+        advance(':');

+        that['else'] = expression(10);

+        return that;

+    }, 30);

+

+    infix('||', 'or', 40);

+    infix('&&', 'and', 50);

+    bitwise('|', 'bitor', 70);

+    bitwise('^', 'bitxor', 80);

+    bitwise('&', 'bitand', 90);

+    relation('==', function (left, right) {

+        var eqnull = option.eqnull &&

+                (left.value == 'null' || right.value == 'null');

+

+        if (!eqnull && option.eqeqeq) {

+            warning("Expected '{a}' and instead saw '{b}'.",

+                    this, '===', '==');

+        } else if (isPoorRelation(left)) {

+            warning("Use '{a}' to compare with '{b}'.",

+                this, '===', left.value);

+        } else if (isPoorRelation(right)) {

+            warning("Use '{a}' to compare with '{b}'.",

+                this, '===', right.value);

+        }

+        return this;

+    });

+    relation('===');

+    relation('!=', function (left, right) {

+        if (option.eqeqeq) {

+            warning("Expected '{a}' and instead saw '{b}'.",

+                    this, '!==', '!=');

+        } else if (isPoorRelation(left)) {

+            warning("Use '{a}' to compare with '{b}'.",

+                    this, '!==', left.value);

+        } else if (isPoorRelation(right)) {

+            warning("Use '{a}' to compare with '{b}'.",

+                    this, '!==', right.value);

+        }

+        return this;

+    });

+    relation('!==');

+    relation('<');

+    relation('>');

+    relation('<=');

+    relation('>=');

+    bitwise('<<', 'shiftleft', 120);

+    bitwise('>>', 'shiftright', 120);

+    bitwise('>>>', 'shiftrightunsigned', 120);

+    infix('in', 'in', 120);

+    infix('instanceof', 'instanceof', 120);

+    infix('+', function (left, that) {

+        var right = expression(130);

+        if (left && right && left.id === '(string)' && right.id === '(string)') {

+            left.value += right.value;

+            left.character = right.character;

+            if (jx.test(left.value)) {

+                warning("JavaScript URL.", left);

+            }

+            return left;

+        }

+        that.left = left;

+        that.right = right;

+        return that;

+    }, 130);

+    prefix('+', 'num');

+    prefix('+++', function () {

+        warning("Confusing pluses.");

+        this.right = expression(150);

+        this.arity = 'unary';

+        return this;

+    });

+    infix('+++', function (left) {

+        warning("Confusing pluses.");

+        this.left = left;

+        this.right = expression(130);

+        return this;

+    }, 130);

+    infix('-', 'sub', 130);

+    prefix('-', 'neg');

+    prefix('---', function () {

+        warning("Confusing minuses.");

+        this.right = expression(150);

+        this.arity = 'unary';

+        return this;

+    });

+    infix('---', function (left) {

+        warning("Confusing minuses.");

+        this.left = left;

+        this.right = expression(130);

+        return this;

+    }, 130);

+    infix('*', 'mult', 140);

+    infix('/', 'div', 140);

+    infix('%', 'mod', 140);

+

+    suffix('++', 'postinc');

+    prefix('++', 'preinc');

+    syntax['++'].exps = true;

+

+    suffix('--', 'postdec');

+    prefix('--', 'predec');

+    syntax['--'].exps = true;

+    prefix('delete', function () {

+        var p = expression(0);

+        if (!p || (p.id !== '.' && p.id !== '[')) {

+            warning("Variables should not be deleted.");

+        }

+        this.first = p;

+        return this;

+    }).exps = true;

+

+    prefix('~', function () {

+        if (option.bitwise) {

+            warning("Unexpected '{a}'.", this, '~');

+        }

+        expression(150);

+        return this;

+    });

+

+    prefix('!', function () {

+        this.right = expression(150);

+        this.arity = 'unary';

+        if (bang[this.right.id] === true) {

+            warning("Confusing use of '{a}'.", this, '!');

+        }

+        return this;

+    });

+    prefix('typeof', 'typeof');

+    prefix('new', function () {

+        var c = expression(155), i;

+        if (c && c.id !== 'function') {

+            if (c.identifier) {

+                c['new'] = true;

+                switch (c.value) {

+                case 'Object':

+                    warning("Use the object literal notation {}.", token);

+                    break;

+                case 'Number':

+                case 'String':

+                case 'Boolean':

+                case 'Math':

+                case 'JSON':

+                    warning("Do not use {a} as a constructor.", token, c.value);

+                    break;

+                case 'Function':

+                    if (!option.evil) {

+                        warning("The Function constructor is eval.");

+                    }

+                    break;

+                case 'Date':

+                case 'RegExp':

+                    break;

+                default:

+                    if (c.id !== 'function') {

+                        i = c.value.substr(0, 1);

+                        if (option.newcap && (i < 'A' || i > 'Z')) {

+                            warning("A constructor name should start with an uppercase letter.", token);

+                        }

+                    }

+                }

+            } else {

+                if (c.id !== '.' && c.id !== '[' && c.id !== '(') {

+                    warning("Bad constructor.", token);

+                }

+            }

+        } else {

+            if (!option.supernew)

+                warning("Weird construction. Delete 'new'.", this);

+        }

+        adjacent(token, nexttoken);

+        if (nexttoken.id !== '(' && !option.supernew) {

+            warning("Missing '()' invoking a constructor.");

+        }

+        this.first = c;

+        return this;

+    });

+    syntax['new'].exps = true;

+

+    prefix('void').exps = true;

+

+    infix('.', function (left, that) {

+        adjacent(prevtoken, token);

+        nobreak();

+        var m = identifier();

+        if (typeof m === 'string') {

+            countMember(m);

+        }

+        that.left = left;

+        that.right = m;

+        if (option.noarg && left && left.value === 'arguments' &&

+                (m === 'callee' || m === 'caller')) {

+            warning("Avoid arguments.{a}.", left, m);

+        } else if (!option.evil && left && left.value === 'document' &&

+                (m === 'write' || m === 'writeln')) {

+            warning("document.write can be a form of eval.", left);

+        }

+        if (!option.evil && (m === 'eval' || m === 'execScript')) {

+            warning('eval is evil.');

+        }

+        return that;

+    }, 160, true);

+

+    infix('(', function (left, that) {

+        if (prevtoken.id !== '}' && prevtoken.id !== ')') {

+            nobreak(prevtoken, token);

+        }

+        nospace();

+        if (option.immed && !left.immed && left.id === 'function') {

+            warning("Wrap an immediate function invocation in parentheses " +

+                "to assist the reader in understanding that the expression " +

+                "is the result of a function, and not the function itself.");

+        }

+        var n = 0,

+            p = [];

+        if (left) {

+            if (left.type === '(identifier)') {

+                if (left.value.match(/^[A-Z]([A-Z0-9_$]*[a-z][A-Za-z0-9_$]*)?$/)) {

+                    if (left.value !== 'Number' && left.value !== 'String' &&

+                            left.value !== 'Boolean' &&

+                            left.value !== 'Date') {

+                        if (left.value === 'Math') {

+                            warning("Math is not a function.", left);

+                        } else if (option.newcap) {

+                            warning(

+"Missing 'new' prefix when invoking a constructor.", left);

+                        }

+                    }

+                }

+            }

+        }

+        if (nexttoken.id !== ')') {

+            for (;;) {

+                p[p.length] = expression(10);

+                n += 1;

+                if (nexttoken.id !== ',') {

+                    break;

+                }

+                comma();

+            }

+        }

+        advance(')');

+        nospace(prevtoken, token);

+        if (typeof left === 'object') {

+            if (left.value === 'parseInt' && n === 1) {

+                warning("Missing radix parameter.", left);

+            }

+            if (!option.evil) {

+                if (left.value === 'eval' || left.value === 'Function' ||

+                        left.value === 'execScript') {

+                    warning("eval is evil.", left);

+                } else if (p[0] && p[0].id === '(string)' &&

+                       (left.value === 'setTimeout' ||

+                        left.value === 'setInterval')) {

+                    warning(

+    "Implied eval is evil. Pass a function instead of a string.", left);

+                }

+            }

+            if (!left.identifier && left.id !== '.' && left.id !== '[' &&

+                    left.id !== '(' && left.id !== '&&' && left.id !== '||' &&

+                    left.id !== '?') {

+                warning("Bad invocation.", left);

+            }

+        }

+        that.left = left;

+        return that;

+    }, 155, true).exps = true;

+

+    prefix('(', function () {

+        nospace();

+        if (nexttoken.id === 'function') {

+            nexttoken.immed = true;

+        }

+        var v = expression(0);

+        advance(')', this);

+        nospace(prevtoken, token);

+        if (option.immed && v.id === 'function') {

+            if (nexttoken.id === '(') {

+                warning(

+"Move the invocation into the parens that contain the function.", nexttoken);

+            } else {

+                warning(

+"Do not wrap function literals in parens unless they are to be immediately invoked.",

+                        this);

+            }

+        }

+        return v;

+    });

+

+    infix('[', function (left, that) {

+        nobreak(prevtoken, token);

+        nospace();

+        var e = expression(0), s;

+        if (e && e.type === '(string)') {

+            if (!option.evil && (e.value === 'eval' || e.value === 'execScript')) {

+                warning("eval is evil.", that);

+            }

+            countMember(e.value);

+            if (!option.sub && ix.test(e.value)) {

+                s = syntax[e.value];

+                if (!s || !s.reserved) {

+                    warning("['{a}'] is better written in dot notation.",

+                            e, e.value);

+                }

+            }

+        }

+        advance(']', that);

+        nospace(prevtoken, token);

+        that.left = left;

+        that.right = e;

+        return that;

+    }, 160, true);

+

+    prefix('[', function () {

+        var b = token.line !== nexttoken.line;

+        this.first = [];

+        if (b) {

+            indent += option.indent;

+            if (nexttoken.from === indent + option.indent) {

+                indent += option.indent;

+            }

+        }

+        while (nexttoken.id !== '(end)') {

+            while (nexttoken.id === ',') {

+                warning("Extra comma.");

+                advance(',');

+            }

+            if (nexttoken.id === ']') {

+                break;

+            }

+            if (b && token.line !== nexttoken.line) {

+                indentation();

+            }

+            this.first.push(expression(10));

+            if (nexttoken.id === ',') {

+                comma();

+                if (nexttoken.id === ']' && !option.es5) {

+                    warning("Extra comma.", token);

+                    break;

+                }

+            } else {

+                break;

+            }

+        }

+        if (b) {

+            indent -= option.indent;

+            indentation();

+        }

+        advance(']', this);

+        return this;

+    }, 160);

+

+

+    function property_name() {

+        var id = optionalidentifier(true);

+        if (!id) {

+            if (nexttoken.id === '(string)') {

+                id = nexttoken.value;

+                advance();

+            } else if (nexttoken.id === '(number)') {

+                id = nexttoken.value.toString();

+                advance();

+            }

+        }

+        return id;

+    }

+

+

+    function functionparams() {

+        var i, t = nexttoken, p = [];

+        advance('(');

+        nospace();

+        if (nexttoken.id === ')') {

+            advance(')');

+            nospace(prevtoken, token);

+            return;

+        }

+        for (;;) {

+            i = identifier(true);

+            p.push(i);

+            addlabel(i, 'parameter');

+            if (nexttoken.id === ',') {

+                comma();

+            } else {

+                advance(')', t);

+                nospace(prevtoken, token);

+                return p;

+            }

+        }

+    }

+

+

+    function doFunction(i, statement) {

+        var f,

+            oldOption = option,

+            oldScope  = scope;

+

+        option = Object.create(option);

+        scope = Object.create(scope);

+

+        funct = {

+            '(name)'     : i || '"' + anonname + '"',

+            '(line)'     : nexttoken.line,

+            '(context)'  : funct,

+            '(breakage)' : 0,

+            '(loopage)'  : 0,

+            '(scope)'    : scope,

+            '(statement)': statement

+        };

+        f = funct;

+        token.funct = funct;

+        functions.push(funct);

+        if (i) {

+            addlabel(i, 'function');

+        }

+        funct['(params)'] = functionparams();

+

+        block(false);

+        scope = oldScope;

+        option = oldOption;

+        funct['(last)'] = token.line;

+        funct = funct['(context)'];

+        return f;

+    }

+

+

+    (function (x) {

+        x.nud = function () {

+            var b, f, i, j, p, seen = {}, t;

+

+            b = token.line !== nexttoken.line;

+            if (b) {

+                indent += option.indent;

+                if (nexttoken.from === indent + option.indent) {

+                    indent += option.indent;

+                }

+            }

+            for (;;) {

+                if (nexttoken.id === '}') {

+                    break;

+                }

+                if (b) {

+                    indentation();

+                }

+                if (nexttoken.value === 'get' && peek().id !== ':') {

+                    advance('get');

+                    if (!option.es5) {

+                        error("get/set are ES5 features.");

+                    }

+                    i = property_name();

+                    if (!i) {

+                        error("Missing property name.");

+                    }

+                    t = nexttoken;

+                    adjacent(token, nexttoken);

+                    f = doFunction();

+                    if (!option.loopfunc && funct['(loopage)']) {

+                        warning("Don't make functions within a loop.", t);

+                    }

+                    p = f['(params)'];

+                    if (p) {

+                        warning("Unexpected parameter '{a}' in get {b} function.", t, p[0], i);

+                    }

+                    adjacent(token, nexttoken);

+                    advance(',');

+                    indentation();

+                    advance('set');

+                    j = property_name();

+                    if (i !== j) {

+                        error("Expected {a} and instead saw {b}.", token, i, j);

+                    }

+                    t = nexttoken;

+                    adjacent(token, nexttoken);

+                    f = doFunction();

+                    p = f['(params)'];

+                    if (!p || p.length !== 1 || p[0] !== 'value') {

+                        warning("Expected (value) in set {a} function.", t, i);

+                    }

+                } else {

+                    i = property_name();

+                    if (typeof i !== 'string') {

+                        break;

+                    }

+                    advance(':');

+                    nonadjacent(token, nexttoken);

+                    expression(10);

+                }

+                if (seen[i] === true) {

+                    warning("Duplicate member '{a}'.", nexttoken, i);

+                }

+                seen[i] = true;

+                countMember(i);

+                if (nexttoken.id === ',') {

+                    comma();

+                    if (nexttoken.id === ',') {

+                        warning("Extra comma.", token);

+                    } else if (nexttoken.id === '}' && !option.es5) {

+                        warning("Extra comma.", token);

+                    }

+                } else {

+                    break;

+                }

+            }

+            if (b) {

+                indent -= option.indent;

+                indentation();

+            }

+            advance('}', this);

+            return this;

+        };

+        x.fud = function () {

+            error("Expected to see a statement and instead saw a block.", token);

+        };

+    }(delim('{')));

+

+    var varstatement = stmt('var', function (prefix) {

+        // JavaScript does not have block scope. It only has function scope. So,

+        // declaring a variable in a block can have unexpected consequences.

+        var id, name, value;

+

+        if (funct['(onevar)'] && option.onevar) {

+            warning("Too many var statements.");

+        } else if (!funct['(global)']) {

+            funct['(onevar)'] = true;

+        }

+        this.first = [];

+        for (;;) {

+            nonadjacent(token, nexttoken);

+            id = identifier();

+            if (funct['(global)'] && predefined[id] === false) {

+                warning("Redefinition of '{a}'.", token, id);

+            }

+            addlabel(id, 'unused');

+            if (prefix) {

+                break;

+            }

+            name = token;

+            this.first.push(token);

+            if (nexttoken.id === '=') {

+                nonadjacent(token, nexttoken);

+                advance('=');

+                nonadjacent(token, nexttoken);

+                if (nexttoken.id === 'undefined') {

+                    warning("It is not necessary to initialize '{a}' to 'undefined'.", token, id);

+                }

+                if (peek(0).id === '=' && nexttoken.identifier) {

+                    error("Variable {a} was not declared correctly.",

+                            nexttoken, nexttoken.value);

+                }

+                value = expression(0);

+                name.first = value;

+            }

+            if (nexttoken.id !== ',') {

+                break;

+            }

+            comma();

+        }

+        return this;

+    });

+    varstatement.exps = true;

+

+    blockstmt('function', function () {

+        if (inblock) {

+            warning(

+"Function declarations should not be placed in blocks. Use a function expression or move the statement to the top of the outer function.", token);

+

+        }

+        var i = identifier();

+        adjacent(token, nexttoken);

+        addlabel(i, 'unction');

+        doFunction(i, true);

+        if (nexttoken.id === '(' && nexttoken.line === token.line) {

+            error(

+"Function declarations are not invocable. Wrap the whole function invocation in parens.");

+        }

+        return this;

+    });

+

+    prefix('function', function () {

+        var i = optionalidentifier();

+        if (i) {

+            adjacent(token, nexttoken);

+        } else {

+            nonadjacent(token, nexttoken);

+        }

+        doFunction(i);

+        if (!option.loopfunc && funct['(loopage)']) {

+            warning("Don't make functions within a loop.");

+        }

+        return this;

+    });

+

+    blockstmt('if', function () {

+        var t = nexttoken;

+        advance('(');

+        nonadjacent(this, t);

+        nospace();

+        expression(20);

+        if (nexttoken.id === '=') {

+            if (!option.boss)

+                warning("Expected a conditional expression and instead saw an assignment.");

+            advance('=');

+            expression(20);

+        }

+        advance(')', t);

+        nospace(prevtoken, token);

+        block(true, true);

+        if (nexttoken.id === 'else') {

+            nonadjacent(token, nexttoken);

+            advance('else');

+            if (nexttoken.id === 'if' || nexttoken.id === 'switch') {

+                statement(true);

+            } else {

+                block(true, true);

+            }

+        }

+        return this;

+    });

+

+    blockstmt('try', function () {

+        var b, e, s;

+

+        block(false);

+        if (nexttoken.id === 'catch') {

+            advance('catch');

+            nonadjacent(token, nexttoken);

+            advance('(');

+            s = scope;

+            scope = Object.create(s);

+            e = nexttoken.value;

+            if (nexttoken.type !== '(identifier)') {

+                warning("Expected an identifier and instead saw '{a}'.",

+                    nexttoken, e);

+            } else {

+                addlabel(e, 'exception');

+            }

+            advance();

+            advance(')');

+            block(false);

+            b = true;

+            scope = s;

+        }

+        if (nexttoken.id === 'finally') {

+            advance('finally');

+            block(false);

+            return;

+        } else if (!b) {

+            error("Expected '{a}' and instead saw '{b}'.",

+                    nexttoken, 'catch', nexttoken.value);

+        }

+        return this;

+    });

+

+    blockstmt('while', function () {

+        var t = nexttoken;

+        funct['(breakage)'] += 1;

+        funct['(loopage)'] += 1;

+        advance('(');

+        nonadjacent(this, t);

+        nospace();

+        expression(20);

+        if (nexttoken.id === '=') {

+            if (!option.boss)

+                warning("Expected a conditional expression and instead saw an assignment.");

+            advance('=');

+            expression(20);

+        }

+        advance(')', t);

+        nospace(prevtoken, token);

+        block(true, true);

+        funct['(breakage)'] -= 1;

+        funct['(loopage)'] -= 1;

+        return this;

+    }).labelled = true;

+

+    reserve('with');

+

+    blockstmt('switch', function () {

+        var t = nexttoken,

+            g = false;

+        funct['(breakage)'] += 1;

+        advance('(');

+        nonadjacent(this, t);

+        nospace();

+        this.condition = expression(20);

+        advance(')', t);

+        nospace(prevtoken, token);

+        nonadjacent(token, nexttoken);

+        t = nexttoken;

+        advance('{');

+        nonadjacent(token, nexttoken);

+        indent += option.indent;

+        this.cases = [];

+        for (;;) {

+            switch (nexttoken.id) {

+            case 'case':

+                switch (funct['(verb)']) {

+                case 'break':

+                case 'case':

+                case 'continue':

+                case 'return':

+                case 'switch':

+                case 'throw':

+                    break;

+                default:

+                    // You can tell JSHint that you don't use break intentionally by

+                    // adding a comment /* falls through */ on a line just before

+                    // the next `case`.

+                    if (!ft.test(lines[nexttoken.line - 2])) {

+                        warning(

+                            "Expected a 'break' statement before 'case'.",

+                            token);

+                    }

+                }

+                indentation(-option.indent);

+                advance('case');

+                this.cases.push(expression(20));

+                g = true;

+                advance(':');

+                funct['(verb)'] = 'case';

+                break;

+            case 'default':

+                switch (funct['(verb)']) {

+                case 'break':

+                case 'continue':

+                case 'return':

+                case 'throw':

+                    break;

+                default:

+                    if (!ft.test(lines[nexttoken.line - 2])) {

+                        warning(

+                            "Expected a 'break' statement before 'default'.",

+                            token);

+                    }

+                }

+                indentation(-option.indent);

+                advance('default');

+                g = true;

+                advance(':');

+                break;

+            case '}':

+                indent -= option.indent;

+                indentation();

+                advance('}', t);

+                if (this.cases.length === 1 || this.condition.id === 'true' ||

+                        this.condition.id === 'false') {

+                    warning("This 'switch' should be an 'if'.", this);

+                }

+                funct['(breakage)'] -= 1;

+                funct['(verb)'] = undefined;

+                return;

+            case '(end)':

+                error("Missing '{a}'.", nexttoken, '}');

+                return;

+            default:

+                if (g) {

+                    switch (token.id) {

+                    case ',':

+                        error("Each value should have its own case label.");

+                        return;

+                    case ':':

+                        statements();

+                        break;

+                    default:

+                        error("Missing ':' on a case clause.", token);

+                    }

+                } else {

+                    error("Expected '{a}' and instead saw '{b}'.",

+                        nexttoken, 'case', nexttoken.value);

+                }

+            }

+        }

+    }).labelled = true;

+

+    stmt('debugger', function () {

+        if (!option.debug) {

+            warning("All 'debugger' statements should be removed.");

+        }

+        return this;

+    }).exps = true;

+

+    (function () {

+        var x = stmt('do', function () {

+            funct['(breakage)'] += 1;

+            funct['(loopage)'] += 1;

+            this.first = block(true);

+            advance('while');

+            var t = nexttoken;

+            nonadjacent(token, t);

+            advance('(');

+            nospace();

+            expression(20);

+            if (nexttoken.id === '=') {

+                if (!option.boss)

+                    warning("Expected a conditional expression and instead saw an assignment.");

+                advance('=');

+                expression(20);

+            }

+            advance(')', t);

+            nospace(prevtoken, token);

+            funct['(breakage)'] -= 1;

+            funct['(loopage)'] -= 1;

+            return this;

+        });

+        x.labelled = true;

+        x.exps = true;

+    }());

+

+    blockstmt('for', function () {

+        var s, t = nexttoken;

+        funct['(breakage)'] += 1;

+        funct['(loopage)'] += 1;

+        advance('(');

+        nonadjacent(this, t);

+        nospace();

+        if (peek(nexttoken.id === 'var' ? 1 : 0).id === 'in') {

+            if (nexttoken.id === 'var') {

+                advance('var');

+                varstatement.fud.call(varstatement, true);

+            } else {

+                switch (funct[nexttoken.value]) {

+                case 'unused':

+                    funct[nexttoken.value] = 'var';

+                    break;

+                case 'var':

+                    break;

+                default:

+                    warning("Bad for in variable '{a}'.",

+                            nexttoken, nexttoken.value);

+                }

+                advance();

+            }

+            advance('in');

+            expression(20);

+            advance(')', t);

+            s = block(true, true);

+            if (option.forin && (s.length > 1 || typeof s[0] !== 'object' ||

+                    s[0].value !== 'if')) {

+                warning("The body of a for in should be wrapped in an if statement to filter unwanted properties from the prototype.", this);

+            }

+            funct['(breakage)'] -= 1;

+            funct['(loopage)'] -= 1;

+            return this;

+        } else {

+            if (nexttoken.id !== ';') {

+                if (nexttoken.id === 'var') {

+                    advance('var');

+                    varstatement.fud.call(varstatement);

+                } else {

+                    for (;;) {

+                        expression(0, 'for');

+                        if (nexttoken.id !== ',') {

+                            break;

+                        }

+                        comma();

+                    }

+                }

+            }

+            nolinebreak(token);

+            advance(';');

+            if (nexttoken.id !== ';') {

+                expression(20);

+                if (nexttoken.id === '=') {

+                    if (!option.boss)

+                        warning("Expected a conditional expression and instead saw an assignment.");

+                    advance('=');

+                    expression(20);

+                }

+            }

+            nolinebreak(token);

+            advance(';');

+            if (nexttoken.id === ';') {

+                error("Expected '{a}' and instead saw '{b}'.",

+                        nexttoken, ')', ';');

+            }

+            if (nexttoken.id !== ')') {

+                for (;;) {

+                    expression(0, 'for');

+                    if (nexttoken.id !== ',') {

+                        break;

+                    }

+                    comma();

+                }

+            }

+            advance(')', t);

+            nospace(prevtoken, token);

+            block(true, true);

+            funct['(breakage)'] -= 1;

+            funct['(loopage)'] -= 1;

+            return this;

+        }

+    }).labelled = true;

+

+

+    stmt('break', function () {

+        var v = nexttoken.value;

+        if (funct['(breakage)'] === 0) {

+            warning("Unexpected '{a}'.", nexttoken, this.value);

+        }

+        nolinebreak(this);

+        if (nexttoken.id !== ';') {

+            if (token.line === nexttoken.line) {

+                if (funct[v] !== 'label') {

+                    warning("'{a}' is not a statement label.", nexttoken, v);

+                } else if (scope[v] !== funct) {

+                    warning("'{a}' is out of scope.", nexttoken, v);

+                }

+                this.first = nexttoken;

+                advance();

+            }

+        }

+        reachable('break');

+        return this;

+    }).exps = true;

+

+

+    stmt('continue', function () {

+        var v = nexttoken.value;

+        if (funct['(breakage)'] === 0) {

+            warning("Unexpected '{a}'.", nexttoken, this.value);

+        }

+        nolinebreak(this);

+        if (nexttoken.id !== ';') {

+            if (token.line === nexttoken.line) {

+                if (funct[v] !== 'label') {

+                    warning("'{a}' is not a statement label.", nexttoken, v);

+                } else if (scope[v] !== funct) {

+                    warning("'{a}' is out of scope.", nexttoken, v);

+                }

+                this.first = nexttoken;

+                advance();

+            }

+        } else if (!funct['(loopage)']) {

+            warning("Unexpected '{a}'.", nexttoken, this.value);

+        }

+        reachable('continue');

+        return this;

+    }).exps = true;

+

+

+    stmt('return', function () {

+        nolinebreak(this);

+        if (nexttoken.id === '(regexp)') {

+            warning("Wrap the /regexp/ literal in parens to disambiguate the slash operator.");

+        }

+        if (nexttoken.id !== ';' && !nexttoken.reach) {

+            nonadjacent(token, nexttoken);

+            this.first = expression(20);

+        }

+        reachable('return');

+        return this;

+    }).exps = true;

+

+

+    stmt('throw', function () {

+        nolinebreak(this);

+        nonadjacent(token, nexttoken);

+        this.first = expression(20);

+        reachable('throw');

+        return this;

+    }).exps = true;

+

+//  Superfluous reserved words

+

+    reserve('class');

+    reserve('const');

+    reserve('enum');

+    reserve('export');

+    reserve('extends');

+    reserve('import');

+    reserve('super');

+

+    reserve('let');

+    reserve('yield');

+    reserve('implements');

+    reserve('interface');

+    reserve('package');

+    reserve('private');

+    reserve('protected');

+    reserve('public');

+    reserve('static');

+

+

+// Parse JSON

+

+    function jsonValue() {

+

+        function jsonObject() {

+            var o = {}, t = nexttoken;

+            advance('{');

+            if (nexttoken.id !== '}') {

+                for (;;) {

+                    if (nexttoken.id === '(end)') {

+                        error("Missing '}' to match '{' from line {a}.",

+                                nexttoken, t.line);

+                    } else if (nexttoken.id === '}') {

+                        warning("Unexpected comma.", token);

+                        break;

+                    } else if (nexttoken.id === ',') {

+                        error("Unexpected comma.", nexttoken);

+                    } else if (nexttoken.id !== '(string)') {

+                        warning("Expected a string and instead saw {a}.",

+                                nexttoken, nexttoken.value);

+                    }

+                    if (o[nexttoken.value] === true) {

+                        warning("Duplicate key '{a}'.",

+                                nexttoken, nexttoken.value);

+                    } else if (nexttoken.value === '__proto__') {

+                        warning("Stupid key '{a}'.",

+                                nexttoken, nexttoken.value);

+                    } else {

+                        o[nexttoken.value] = true;

+                    }

+                    advance();

+                    advance(':');

+                    jsonValue();

+                    if (nexttoken.id !== ',') {

+                        break;

+                    }

+                    advance(',');

+                }

+            }

+            advance('}');

+        }

+

+        function jsonArray() {

+            var t = nexttoken;

+            advance('[');

+            if (nexttoken.id !== ']') {

+                for (;;) {

+                    if (nexttoken.id === '(end)') {

+                        error("Missing ']' to match '[' from line {a}.",

+                                nexttoken, t.line);

+                    } else if (nexttoken.id === ']') {

+                        warning("Unexpected comma.", token);

+                        break;

+                    } else if (nexttoken.id === ',') {

+                        error("Unexpected comma.", nexttoken);

+                    }

+                    jsonValue();

+                    if (nexttoken.id !== ',') {

+                        break;

+                    }

+                    advance(',');

+                }

+            }

+            advance(']');

+        }

+

+        switch (nexttoken.id) {

+        case '{':

+            jsonObject();

+            break;

+        case '[':

+            jsonArray();

+            break;

+        case 'true':

+        case 'false':

+        case 'null':

+        case '(number)':

+        case '(string)':

+            advance();

+            break;

+        case '-':

+            advance('-');

+            if (token.character !== nexttoken.from) {

+                warning("Unexpected space after '-'.", token);

+            }

+            adjacent(token, nexttoken);

+            advance('(number)');

+            break;

+        default:

+            error("Expected a JSON value.", nexttoken);

+        }

+    }

+

+

+// The actual JSHINT function itself.

+

+    var itself = function (s, o, g) {

+        var a, i, k;

+        JSHINT.errors = [];

+        predefined = Object.create(standard);

+        combine(predefined, g || {});

+        if (o) {

+            a = o.predef;

+            if (a) {

+                if (Array.isArray(a)) {

+                    for (i = 0; i < a.length; i += 1) {

+                        predefined[a[i]] = true;

+                    }

+                } else if (typeof a === 'object') {

+                    k = Object.keys(a);

+                    for (i = 0; i < k.length; i += 1) {

+                        predefined[k[i]] = !!a[k];

+                    }

+                }

+            }

+            option = o;

+        } else {

+            option = {};

+        }

+        option.indent = option.indent || 4;

+        option.maxerr = option.maxerr || 50;

+

+        tab = '';

+        for (i = 0; i < option.indent; i += 1) {

+            tab += ' ';

+        }

+        indent = 1;

+        global = Object.create(predefined);

+        scope = global;

+        funct = {

+            '(global)': true,

+            '(name)': '(global)',

+            '(scope)': scope,

+            '(breakage)': 0,

+            '(loopage)': 0

+        };

+        functions = [funct];

+        urls = [];

+        src = false;

+        stack = null;

+        member = {};

+        membersOnly = null;

+        implied = {};

+        inblock = false;

+        lookahead = [];

+        jsonmode = false;

+        warnings = 0;

+        lex.init(s);

+        prereg = true;

+        strict_mode = false;

+

+        prevtoken = token = nexttoken = syntax['(begin)'];

+        assume();

+

+        try {

+            advance();

+            switch (nexttoken.id) {

+            case '{':

+            case '[':

+                option.laxbreak = true;

+                jsonmode = true;

+                jsonValue();

+                break;

+            default:

+                if (nexttoken.value === 'use strict') {

+                    if (!option.globalstrict)

+                        warning("Use the function form of \"use strict\".");

+                    use_strict();

+                }

+                statements('lib');

+            }

+            advance('(end)');

+        } catch (e) {

+            if (e) {

+                JSHINT.errors.push({

+                    reason    : e.message,

+                    line      : e.line || nexttoken.line,

+                    character : e.character || nexttoken.from

+                }, null);

+            }

+        }

+        return JSHINT.errors.length === 0;

+    };

+

+

+// Data summary.

+

+    itself.data = function () {

+

+        var data = {functions: []}, fu, globals, implieds = [], f, i, j,

+            members = [], n, unused = [], v;

+        if (itself.errors.length) {

+            data.errors = itself.errors;

+        }

+

+        if (jsonmode) {

+            data.json = true;

+        }

+

+        for (n in implied) {

+            if (is_own(implied, n)) {

+                implieds.push({

+                    name: n,

+                    line: implied[n]

+                });

+            }

+        }

+        if (implieds.length > 0) {

+            data.implieds = implieds;

+        }

+

+        if (urls.length > 0) {

+            data.urls = urls;

+        }

+

+        globals = Object.keys(scope);

+        if (globals.length > 0) {

+            data.globals = globals;

+        }

+

+        for (i = 1; i < functions.length; i += 1) {

+            f = functions[i];

+            fu = {};

+            for (j = 0; j < functionicity.length; j += 1) {

+                fu[functionicity[j]] = [];

+            }

+            for (n in f) {

+                if (is_own(f, n) && n.charAt(0) !== '(') {

+                    v = f[n];

+                    if (v === 'unction') {

+                        v = 'unused';

+                    }

+                    if (Array.isArray(fu[v])) {

+                        fu[v].push(n);

+                        if (v === 'unused') {

+                            unused.push({

+                                name: n,

+                                line: f['(line)'],

+                                'function': f['(name)']

+                            });

+                        }

+                    }

+                }

+            }

+            for (j = 0; j < functionicity.length; j += 1) {

+                if (fu[functionicity[j]].length === 0) {

+                    delete fu[functionicity[j]];

+                }

+            }

+            fu.name = f['(name)'];

+            fu.param = f['(params)'];

+            fu.line = f['(line)'];

+            fu.last = f['(last)'];

+            data.functions.push(fu);

+        }

+

+        if (unused.length > 0) {

+            data.unused = unused;

+        }

+

+        members = [];

+        for (n in member) {

+            if (typeof member[n] === 'number') {

+                data.member = member;

+                break;

+            }

+        }

+

+        return data;

+    };

+

+    itself.report = function (option) {

+        var data = itself.data();

+

+        var a = [], c, e, err, f, i, k, l, m = '', n, o = [], s;

+

+        function detail(h, array) {

+            var b, i, singularity;

+            if (array) {

+                o.push('<div><i>' + h + '</i> ');

+                array = array.sort();

+                for (i = 0; i < array.length; i += 1) {

+                    if (array[i] !== singularity) {

+                        singularity = array[i];

+                        o.push((b ? ', ' : '') + singularity);

+                        b = true;

+                    }

+                }

+                o.push('</div>');

+            }

+        }

+

+

+        if (data.errors || data.implieds || data.unused) {

+            err = true;

+            o.push('<div id=errors><i>Error:</i>');

+            if (data.errors) {

+                for (i = 0; i < data.errors.length; i += 1) {

+                    c = data.errors[i];

+                    if (c) {

+                        e = c.evidence || '';

+                        o.push('<p>Problem' + (isFinite(c.line) ? ' at line ' +

+                                c.line + ' character ' + c.character : '') +

+                                ': ' + c.reason.entityify() +

+                                '</p><p class=evidence>' +

+                                (e && (e.length > 80 ? e.slice(0, 77) + '...' :

+                                e).entityify()) + '</p>');

+                    }

+                }

+            }

+

+            if (data.implieds) {

+                s = [];

+                for (i = 0; i < data.implieds.length; i += 1) {

+                    s[i] = '<code>' + data.implieds[i].name + '</code>&nbsp;<i>' +

+                        data.implieds[i].line + '</i>';

+                }

+                o.push('<p><i>Implied global:</i> ' + s.join(', ') + '</p>');

+            }

+

+            if (data.unused) {

+                s = [];

+                for (i = 0; i < data.unused.length; i += 1) {

+                    s[i] = '<code><u>' + data.unused[i].name + '</u></code>&nbsp;<i>' +

+                        data.unused[i].line + '</i> <code>' +

+                        data.unused[i]['function'] + '</code>';

+                }

+                o.push('<p><i>Unused variable:</i> ' + s.join(', ') + '</p>');

+            }

+            if (data.json) {

+                o.push('<p>JSON: bad.</p>');

+            }

+            o.push('</div>');

+        }

+

+        if (!option) {

+

+            o.push('<br><div id=functions>');

+

+            if (data.urls) {

+                detail("URLs<br>", data.urls, '<br>');

+            }

+

+            if (data.json && !err) {

+                o.push('<p>JSON: good.</p>');

+            } else if (data.globals) {

+                o.push('<div><i>Global</i> ' +

+                        data.globals.sort().join(', ') + '</div>');

+            } else {

+                o.push('<div><i>No new global variables introduced.</i></div>');

+            }

+

+            for (i = 0; i < data.functions.length; i += 1) {

+                f = data.functions[i];

+

+                o.push('<br><div class=function><i>' + f.line + '-' +

+                        f.last + '</i> ' + (f.name || '') + '(' +

+                        (f.param ? f.param.join(', ') : '') + ')</div>');

+                detail('<big><b>Unused</b></big>', f.unused);

+                detail('Closure', f.closure);

+                detail('Variable', f['var']);

+                detail('Exception', f.exception);

+                detail('Outer', f.outer);

+                detail('Global', f.global);

+                detail('Label', f.label);

+            }

+

+            if (data.member) {

+                a = Object.keys(data.member);

+                if (a.length) {

+                    a = a.sort();

+                    m = '<br><pre id=members>/*members ';

+                    l = 10;

+                    for (i = 0; i < a.length; i += 1) {

+                        k = a[i];

+                        n = k.name();

+                        if (l + n.length > 72) {

+                            o.push(m + '<br>');

+                            m = '    ';

+                            l = 1;

+                        }

+                        l += n.length + 2;

+                        if (data.member[k] === 1) {

+                            n = '<i>' + n + '</i>';

+                        }

+                        if (i < a.length - 1) {

+                            n += ', ';

+                        }

+                        m += n;

+                    }

+                    o.push(m + '<br>*/</pre>');

+                }

+                o.push('</div>');

+            }

+        }

+        return o.join('');

+    };

+    itself.jshint = itself;

+

+    itself.edition = '2011-04-16';

+

+    return itself;

+

+}());

+

+// Make JSHINT a Node module, if possible.

+if (typeof exports == 'object' && exports)

+    exports.JSHINT = JSHINT;

diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/lib/jslint.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/lib/jslint.js
new file mode 100644
index 0000000..f563292
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/lib/jslint.js
@@ -0,0 +1,5504 @@
+// jslint.js
+// 2010-02-20
+
+/*
+Copyright (c) 2002 Douglas Crockford  (www.JSLint.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+/*
+    JSLINT is a global function. It takes two parameters.
+
+        var myResult = JSLINT(source, option);
+
+    The first parameter is either a string or an array of strings. If it is a
+    string, it will be split on '\n' or '\r'. If it is an array of strings, it
+    is assumed that each string represents one line. The source can be a
+    JavaScript text, or HTML text, or a Konfabulator text.
+
+    The second parameter is an optional object of options which control the
+    operation of JSLINT. Most of the options are booleans: They are all are
+    optional and have a default value of false.
+
+    If it checks out, JSLINT returns true. Otherwise, it returns false.
+
+    If false, you can inspect JSLINT.errors to find out the problems.
+    JSLINT.errors is an array of objects containing these members:
+
+    {
+        line      : The line (relative to 0) at which the lint was found
+        character : The character (relative to 0) at which the lint was found
+        reason    : The problem
+        evidence  : The text line in which the problem occurred
+        raw       : The raw message before the details were inserted
+        a         : The first detail
+        b         : The second detail
+        c         : The third detail
+        d         : The fourth detail
+    }
+
+    If a fatal error was found, a null will be the last element of the
+    JSLINT.errors array.
+
+    You can request a Function Report, which shows all of the functions
+    and the parameters and vars that they use. This can be used to find
+    implied global variables and other problems. The report is in HTML and
+    can be inserted in an HTML <body>.
+
+        var myReport = JSLINT.report(limited);
+
+    If limited is true, then the report will be limited to only errors.
+
+    You can request a data structure which contains JSLint's results.
+
+        var myData = JSLINT.data();
+
+    It returns a structure with this form:
+
+    {
+        errors: [
+            {
+                line: NUMBER,
+                character: NUMBER,
+                reason: STRING,
+                evidence: STRING
+            }
+        ],
+        functions: [
+            name: STRING,
+            line: NUMBER,
+            last: NUMBER,
+            param: [
+                STRING
+            ],
+            closure: [
+                STRING
+            ],
+            var: [
+                STRING
+            ],
+            exception: [
+                STRING
+            ],
+            outer: [
+                STRING
+            ],
+            unused: [
+                STRING
+            ],
+            global: [
+                STRING
+            ],
+            label: [
+                STRING
+            ]
+        ],
+        globals: [
+            STRING
+        ],
+        member: {
+            STRING: NUMBER
+        },
+        unuseds: [
+            {
+                name: STRING,
+                line: NUMBER
+            }
+        ],
+        implieds: [
+            {
+                name: STRING,
+                line: NUMBER
+            }
+        ],
+        urls: [
+            STRING
+        ],
+        json: BOOLEAN
+    }
+
+    Empty arrays will not be included.
+
+*/
+
+/*jslint
+    evil: true, nomen: false, onevar: false, regexp: false, strict: true
+*/
+
+/*members "\b", "\t", "\n", "\f", "\r", "!=", "!==", "\"", "%",
+    "(begin)", "(breakage)", "(context)", "(error)", "(global)",
+    "(identifier)", "(last)", "(line)", "(loopage)", "(name)", "(onevar)",
+    "(params)", "(scope)", "(verb)", "*", "+", "++", "-", "--", "\/",
+    "<", "<=", "==", "===", ">", ">=", ADSAFE, Array, Boolean,
+    COM, Canvas, CustomAnimation, Date, Debug, E, Error, EvalError,
+    FadeAnimation, Flash, FormField, Frame, Function, HotKey, Image, JSON,
+    LN10, LN2, LOG10E, LOG2E, MAX_VALUE, MIN_VALUE, Math, MenuItem,
+    MoveAnimation, NEGATIVE_INFINITY, Number, Object, Option, PI,
+    POSITIVE_INFINITY, Point, RangeError, Rectangle, ReferenceError, RegExp,
+    ResizeAnimation, RotateAnimation, SQRT1_2, SQRT2, ScrollBar, String,
+    Style, SyntaxError, System, Text, TextArea, Timer, TypeError, URIError,
+    URL, Web, Window, XMLDOM, XMLHttpRequest, "\\", a, abbr, acronym,
+    addEventListener, address, adsafe, alert, aliceblue, animator,
+    antiquewhite, appleScript, applet, apply, approved, aqua, aquamarine,
+    area, arguments, arity, autocomplete, azure, b, background,
+    "background-attachment", "background-color", "background-image",
+    "background-position", "background-repeat", base, bdo, beep, beige, big,
+    bisque, bitwise, black, blanchedalmond, block, blockquote, blue,
+    blueviolet, blur, body, border, "border-bottom", "border-bottom-color",
+    "border-bottom-style", "border-bottom-width", "border-collapse",
+    "border-color", "border-left", "border-left-color", "border-left-style",
+    "border-left-width", "border-right", "border-right-color",
+    "border-right-style", "border-right-width", "border-spacing",
+    "border-style", "border-top", "border-top-color", "border-top-style",
+    "border-top-width", "border-width", bottom, br, brown, browser,
+    burlywood, button, bytesToUIString, c, cadetblue, call, callee, caller,
+    canvas, cap, caption, "caption-side", cases, center, charAt, charCodeAt,
+    character, chartreuse, chocolate, chooseColor, chooseFile, chooseFolder,
+    cite, clear, clearInterval, clearTimeout, clip, close, closeWidget,
+    closed, closure, cm, code, col, colgroup, color, comment, condition,
+    confirm, console, constructor, content, convertPathToHFS,
+    convertPathToPlatform, coral, cornflowerblue, cornsilk,
+    "counter-increment", "counter-reset", create, crimson, css, cursor,
+    cyan, d, darkblue, darkcyan, darkgoldenrod, darkgray, darkgreen,
+    darkkhaki, darkmagenta, darkolivegreen, darkorange, darkorchid, darkred,
+    darksalmon, darkseagreen, darkslateblue, darkslategray, darkturquoise,
+    darkviolet, data, dd, debug, decodeURI, decodeURIComponent, deeppink,
+    deepskyblue, defaultStatus, defineClass, del, deserialize, devel, dfn,
+    dimension, dimgray, dir, direction, display, div, dl, document,
+    dodgerblue, dt, edition, else, em, embed, empty, "empty-cells",
+    encodeURI, encodeURIComponent, entityify, eqeqeq, errors, escape, eval,
+    event, evidence, evil, ex, exception, exec, exps, fieldset, filesystem,
+    firebrick, first, float, floor, floralwhite, focus, focusWidget, font,
+    "font-face", "font-family", "font-size", "font-size-adjust",
+    "font-stretch", "font-style", "font-variant", "font-weight",
+    forestgreen, forin, form, fragment, frame, frames, frameset, from,
+    fromCharCode, fuchsia, fud, funct, function, functions, g, gainsboro,
+    gc, getComputedStyle, ghostwhite, global, globals, gold, goldenrod,
+    gray, green, greenyellow, h1, h2, h3, h4, h5, h6, hasOwnProperty, head,
+    height, help, history, honeydew, hotpink, hr, html, i, iTunes, id,
+    identifier, iframe, img, immed, implieds, in, include, indent, indexOf,
+    indianred, indigo, init, input, ins, isAlpha, isApplicationRunning,
+    isDigit, isFinite, isNaN, ivory, join, jslint, json, kbd, khaki,
+    konfabulatorVersion, label, labelled, lang, last, lavender,
+    lavenderblush, lawngreen, laxbreak, lbp, led, left, legend,
+    lemonchiffon, length, "letter-spacing", li, lib, lightblue, lightcoral,
+    lightcyan, lightgoldenrodyellow, lightgreen, lightpink, lightsalmon,
+    lightseagreen, lightskyblue, lightslategray, lightsteelblue,
+    lightyellow, lime, limegreen, line, "line-height", linen, link,
+    "list-style", "list-style-image", "list-style-position",
+    "list-style-type", load, loadClass, location, log, m, magenta, map,
+    margin, "margin-bottom", "margin-left", "margin-right", "margin-top",
+    "marker-offset", maroon, match, "max-height", "max-width", maxerr, maxlen,
+    md5, media, mediumaquamarine, mediumblue, mediumorchid, mediumpurple,
+    mediumseagreen, mediumslateblue, mediumspringgreen, mediumturquoise,
+    mediumvioletred, member, menu, message, meta, midnightblue,
+    "min-height", "min-width", mintcream, mistyrose, mm, moccasin, moveBy,
+    moveTo, name, navajowhite, navigator, navy, new, newcap, noframes,
+    nomen, noscript, nud, object, ol, oldlace, olive, olivedrab, on,
+    onbeforeunload, onblur, onerror, onevar, onfocus, onload, onresize,
+    onunload, opacity, open, openURL, opener, opera, optgroup, option,
+    orange, orangered, orchid, outer, outline, "outline-color",
+    "outline-style", "outline-width", overflow, "overflow-x", "overflow-y",
+    p, padding, "padding-bottom", "padding-left", "padding-right",
+    "padding-top", page, "page-break-after", "page-break-before",
+    palegoldenrod, palegreen, paleturquoise, palevioletred, papayawhip,
+    param, parent, parseFloat, parseInt, passfail, pc, peachpuff, peru,
+    pink, play, plum, plusplus, pop, popupMenu, position, powderblue, pre,
+    predef, preferenceGroups, preferences, print, prompt, prototype, pt,
+    purple, push, px, q, quit, quotes, random, range, raw, reach, readFile,
+    readUrl, reason, red, regexp, reloadWidget, removeEventListener,
+    replace, report, reserved, resizeBy, resizeTo, resolvePath,
+    resumeUpdates, rhino, right, rosybrown, royalblue, runCommand,
+    runCommandInBg, saddlebrown, safe, salmon, samp, sandybrown, saveAs,
+    savePreferences, screen, script, scroll, scrollBy, scrollTo, seagreen,
+    seal, search, seashell, select, serialize, setInterval, setTimeout,
+    shift, showWidgetPreferences, sidebar, sienna, silver, skyblue,
+    slateblue, slategray, sleep, slice, small, snow, sort, span, spawn,
+    speak, split, springgreen, src, status, steelblue, strict, strong,
+    style, styleproperty, sub, substr, sup, supplant, suppressUpdates, sync,
+    system, table, "table-layout", tan, tbody, td, teal, tellWidget, test,
+    "text-align", "text-decoration", "text-indent", "text-shadow",
+    "text-transform", textarea, tfoot, th, thead, thistle, title,
+    toLowerCase, toString, toUpperCase, toint32, token, tomato, top, tr, tt,
+    turquoise, type, u, ul, undef, unescape, "unicode-bidi", unused,
+    unwatch, updateNow, urls, value, valueOf, var, version,
+    "vertical-align", violet, visibility, watch, wheat, white,
+    "white-space", whitesmoke, widget, width, "word-spacing", "word-wrap",
+    yahooCheckLogin, yahooLogin, yahooLogout, yellow, yellowgreen,
+    "z-index"
+*/
+
+
+// We build the application inside a function so that we produce only a single
+// global variable. The function will be invoked, its return value is the JSLINT
+// application itself.
+
+"use strict";
+
+var JSLINT = (function () {
+    var adsafe_id,      // The widget's ADsafe id.
+        adsafe_may,     // The widget may load approved scripts.
+        adsafe_went,    // ADSAFE.go has been called.
+        anonname,       // The guessed name for anonymous functions.
+        approved,       // ADsafe approved urls.
+
+        atrule = {
+            media      : true,
+            'font-face': true,
+            page       : true
+        },
+
+// These are operators that should not be used with the ! operator.
+
+        bang = {
+            '<': true,
+            '<=': true,
+            '==': true,
+            '===': true,
+            '!==': true,
+            '!=': true,
+            '>': true,
+            '>=': true,
+            '+': true,
+            '-': true,
+            '*': true,
+            '/': true,
+            '%': true
+        },
+
+// These are members that should not be permitted in the safe subset.
+
+        banned = {              // the member names that ADsafe prohibits.
+            'arguments'     : true,
+            callee          : true,
+            caller          : true,
+            constructor     : true,
+            'eval'          : true,
+            prototype       : true,
+            unwatch         : true,
+            valueOf         : true,
+            watch           : true
+        },
+
+
+// These are the JSLint boolean options.
+
+        boolOptions = {
+            adsafe     : true, // if ADsafe should be enforced
+            bitwise    : true, // if bitwise operators should not be allowed
+            browser    : true, // if the standard browser globals should be predefined
+            cap        : true, // if upper case HTML should be allowed
+            css        : true, // if CSS workarounds should be tolerated
+            debug      : true, // if debugger statements should be allowed
+            devel      : true, // if logging should be allowed (console, alert, etc.)
+            eqeqeq     : true, // if === should be required
+            evil       : true, // if eval should be allowed
+            forin      : true, // if for in statements must filter
+            fragment   : true, // if HTML fragments should be allowed
+            immed      : true, // if immediate invocations must be wrapped in parens
+            laxbreak   : true, // if line breaks should not be checked
+            newcap     : true, // if constructor names must be capitalized
+            nomen      : true, // if names should be checked
+            on         : true, // if HTML event handlers should be allowed
+            onevar     : true, // if only one var statement per function should be allowed
+            passfail   : true, // if the scan should stop on first error
+            plusplus   : true, // if increment/decrement should not be allowed
+            regexp     : true, // if the . should not be allowed in regexp literals
+            rhino      : true, // if the Rhino environment globals should be predefined
+            undef      : true, // if variables should be declared before used
+            safe       : true, // if use of some browser features should be restricted
+            sidebar    : true, // if the System object should be predefined
+            strict     : true, // require the "use strict"; pragma
+            sub        : true, // if all forms of subscript notation are tolerated
+            white      : true, // if strict whitespace rules apply
+            widget     : true  // if the Yahoo Widgets globals should be predefined
+        },
+
+// browser contains a set of global names which are commonly provided by a
+// web browser environment.
+
+        browser = {
+            addEventListener: false,
+            blur            : false,
+            clearInterval   : false,
+            clearTimeout    : false,
+            close           : false,
+            closed          : false,
+            defaultStatus   : false,
+            document        : false,
+            event           : false,
+            focus           : false,
+            frames          : false,
+            getComputedStyle: false,
+            history         : false,
+            Image           : false,
+            length          : false,
+            location        : false,
+            moveBy          : false,
+            moveTo          : false,
+            name            : false,
+            navigator       : false,
+            onbeforeunload  : true,
+            onblur          : true,
+            onerror         : true,
+            onfocus         : true,
+            onload          : true,
+            onresize        : true,
+            onunload        : true,
+            open            : false,
+            opener          : false,
+            Option          : false,
+            parent          : false,
+            print           : false,
+            removeEventListener: false,
+            resizeBy        : false,
+            resizeTo        : false,
+            screen          : false,
+            scroll          : false,
+            scrollBy        : false,
+            scrollTo        : false,
+            setInterval     : false,
+            setTimeout      : false,
+            status          : false,
+            top             : false,
+            XMLHttpRequest  : false
+        },
+
+        cssAttributeData,
+        cssAny,
+
+        cssColorData = {
+            "aliceblue"             : true,
+            "antiquewhite"          : true,
+            "aqua"                  : true,
+            "aquamarine"            : true,
+            "azure"                 : true,
+            "beige"                 : true,
+            "bisque"                : true,
+            "black"                 : true,
+            "blanchedalmond"        : true,
+            "blue"                  : true,
+            "blueviolet"            : true,
+            "brown"                 : true,
+            "burlywood"             : true,
+            "cadetblue"             : true,
+            "chartreuse"            : true,
+            "chocolate"             : true,
+            "coral"                 : true,
+            "cornflowerblue"        : true,
+            "cornsilk"              : true,
+            "crimson"               : true,
+            "cyan"                  : true,
+            "darkblue"              : true,
+            "darkcyan"              : true,
+            "darkgoldenrod"         : true,
+            "darkgray"              : true,
+            "darkgreen"             : true,
+            "darkkhaki"             : true,
+            "darkmagenta"           : true,
+            "darkolivegreen"        : true,
+            "darkorange"            : true,
+            "darkorchid"            : true,
+            "darkred"               : true,
+            "darksalmon"            : true,
+            "darkseagreen"          : true,
+            "darkslateblue"         : true,
+            "darkslategray"         : true,
+            "darkturquoise"         : true,
+            "darkviolet"            : true,
+            "deeppink"              : true,
+            "deepskyblue"           : true,
+            "dimgray"               : true,
+            "dodgerblue"            : true,
+            "firebrick"             : true,
+            "floralwhite"           : true,
+            "forestgreen"           : true,
+            "fuchsia"               : true,
+            "gainsboro"             : true,
+            "ghostwhite"            : true,
+            "gold"                  : true,
+            "goldenrod"             : true,
+            "gray"                  : true,
+            "green"                 : true,
+            "greenyellow"           : true,
+            "honeydew"              : true,
+            "hotpink"               : true,
+            "indianred"             : true,
+            "indigo"                : true,
+            "ivory"                 : true,
+            "khaki"                 : true,
+            "lavender"              : true,
+            "lavenderblush"         : true,
+            "lawngreen"             : true,
+            "lemonchiffon"          : true,
+            "lightblue"             : true,
+            "lightcoral"            : true,
+            "lightcyan"             : true,
+            "lightgoldenrodyellow"  : true,
+            "lightgreen"            : true,
+            "lightpink"             : true,
+            "lightsalmon"           : true,
+            "lightseagreen"         : true,
+            "lightskyblue"          : true,
+            "lightslategray"        : true,
+            "lightsteelblue"        : true,
+            "lightyellow"           : true,
+            "lime"                  : true,
+            "limegreen"             : true,
+            "linen"                 : true,
+            "magenta"               : true,
+            "maroon"                : true,
+            "mediumaquamarine"      : true,
+            "mediumblue"            : true,
+            "mediumorchid"          : true,
+            "mediumpurple"          : true,
+            "mediumseagreen"        : true,
+            "mediumslateblue"       : true,
+            "mediumspringgreen"     : true,
+            "mediumturquoise"       : true,
+            "mediumvioletred"       : true,
+            "midnightblue"          : true,
+            "mintcream"             : true,
+            "mistyrose"             : true,
+            "moccasin"              : true,
+            "navajowhite"           : true,
+            "navy"                  : true,
+            "oldlace"               : true,
+            "olive"                 : true,
+            "olivedrab"             : true,
+            "orange"                : true,
+            "orangered"             : true,
+            "orchid"                : true,
+            "palegoldenrod"         : true,
+            "palegreen"             : true,
+            "paleturquoise"         : true,
+            "palevioletred"         : true,
+            "papayawhip"            : true,
+            "peachpuff"             : true,
+            "peru"                  : true,
+            "pink"                  : true,
+            "plum"                  : true,
+            "powderblue"            : true,
+            "purple"                : true,
+            "red"                   : true,
+            "rosybrown"             : true,
+            "royalblue"             : true,
+            "saddlebrown"           : true,
+            "salmon"                : true,
+            "sandybrown"            : true,
+            "seagreen"              : true,
+            "seashell"              : true,
+            "sienna"                : true,
+            "silver"                : true,
+            "skyblue"               : true,
+            "slateblue"             : true,
+            "slategray"             : true,
+            "snow"                  : true,
+            "springgreen"           : true,
+            "steelblue"             : true,
+            "tan"                   : true,
+            "teal"                  : true,
+            "thistle"               : true,
+            "tomato"                : true,
+            "turquoise"             : true,
+            "violet"                : true,
+            "wheat"                 : true,
+            "white"                 : true,
+            "whitesmoke"            : true,
+            "yellow"                : true,
+            "yellowgreen"           : true
+        },
+
+        cssBorderStyle,
+        cssBreak,
+
+        cssLengthData = {
+            '%': true,
+            'cm': true,
+            'em': true,
+            'ex': true,
+            'in': true,
+            'mm': true,
+            'pc': true,
+            'pt': true,
+            'px': true
+        },
+
+        cssOverflow,
+
+        devel = {
+            alert           : false,
+            confirm         : false,
+            console         : false,
+            Debug           : false,
+            opera           : false,
+            prompt          : false
+        },
+
+        escapes = {
+            '\b': '\\b',
+            '\t': '\\t',
+            '\n': '\\n',
+            '\f': '\\f',
+            '\r': '\\r',
+            '"' : '\\"',
+            '/' : '\\/',
+            '\\': '\\\\'
+        },
+
+        funct,          // The current function
+
+        functionicity = [
+            'closure', 'exception', 'global', 'label',
+            'outer', 'unused', 'var'
+        ],
+
+        functions,      // All of the functions
+
+        global,         // The global scope
+        htmltag = {
+            a:        {},
+            abbr:     {},
+            acronym:  {},
+            address:  {},
+            applet:   {},
+            area:     {empty: true, parent: ' map '},
+            b:        {},
+            base:     {empty: true, parent: ' head '},
+            bdo:      {},
+            big:      {},
+            blockquote: {},
+            body:     {parent: ' html noframes '},
+            br:       {empty: true},
+            button:   {},
+            canvas:   {parent: ' body p div th td '},
+            caption:  {parent: ' table '},
+            center:   {},
+            cite:     {},
+            code:     {},
+            col:      {empty: true, parent: ' table colgroup '},
+            colgroup: {parent: ' table '},
+            dd:       {parent: ' dl '},
+            del:      {},
+            dfn:      {},
+            dir:      {},
+            div:      {},
+            dl:       {},
+            dt:       {parent: ' dl '},
+            em:       {},
+            embed:    {},
+            fieldset: {},
+            font:     {},
+            form:     {},
+            frame:    {empty: true, parent: ' frameset '},
+            frameset: {parent: ' html frameset '},
+            h1:       {},
+            h2:       {},
+            h3:       {},
+            h4:       {},
+            h5:       {},
+            h6:       {},
+            head:     {parent: ' html '},
+            html:     {parent: '*'},
+            hr:       {empty: true},
+            i:        {},
+            iframe:   {},
+            img:      {empty: true},
+            input:    {empty: true},
+            ins:      {},
+            kbd:      {},
+            label:    {},
+            legend:   {parent: ' fieldset '},
+            li:       {parent: ' dir menu ol ul '},
+            link:     {empty: true, parent: ' head '},
+            map:      {},
+            menu:     {},
+            meta:     {empty: true, parent: ' head noframes noscript '},
+            noframes: {parent: ' html body '},
+            noscript: {parent: ' body head noframes '},
+            object:   {},
+            ol:       {},
+            optgroup: {parent: ' select '},
+            option:   {parent: ' optgroup select '},
+            p:        {},
+            param:    {empty: true, parent: ' applet object '},
+            pre:      {},
+            q:        {},
+            samp:     {},
+            script:   {empty: true, parent: ' body div frame head iframe p pre span '},
+            select:   {},
+            small:    {},
+            span:     {},
+            strong:   {},
+            style:    {parent: ' head ', empty: true},
+            sub:      {},
+            sup:      {},
+            table:    {},
+            tbody:    {parent: ' table '},
+            td:       {parent: ' tr '},
+            textarea: {},
+            tfoot:    {parent: ' table '},
+            th:       {parent: ' tr '},
+            thead:    {parent: ' table '},
+            title:    {parent: ' head '},
+            tr:       {parent: ' table tbody thead tfoot '},
+            tt:       {},
+            u:        {},
+            ul:       {},
+            'var':    {}
+        },
+
+        ids,            // HTML ids
+        implied,        // Implied globals
+        inblock,
+        indent,
+        jsonmode,
+        lines,
+        lookahead,
+        member,
+        membersOnly,
+        nexttoken,
+        noreach,
+        option,
+        predefined,     // Global variables defined by option
+        prereg,
+        prevtoken,
+
+        rhino = {
+            defineClass : false,
+            deserialize : false,
+            gc          : false,
+            help        : false,
+            load        : false,
+            loadClass   : false,
+            print       : false,
+            quit        : false,
+            readFile    : false,
+            readUrl     : false,
+            runCommand  : false,
+            seal        : false,
+            serialize   : false,
+            spawn       : false,
+            sync        : false,
+            toint32     : false,
+            version     : false
+        },
+
+        scope,      // The current scope
+
+        sidebar = {
+            System      : false
+        },
+
+        src,
+        stack,
+
+// standard contains the global names that are provided by the
+// ECMAScript standard.
+
+        standard = {
+            Array               : false,
+            Boolean             : false,
+            Date                : false,
+            decodeURI           : false,
+            decodeURIComponent  : false,
+            encodeURI           : false,
+            encodeURIComponent  : false,
+            Error               : false,
+            'eval'              : false,
+            EvalError           : false,
+            Function            : false,
+            hasOwnProperty      : false,
+            isFinite            : false,
+            isNaN               : false,
+            JSON                : false,
+            Math                : false,
+            Number              : false,
+            Object              : false,
+            parseInt            : false,
+            parseFloat          : false,
+            RangeError          : false,
+            ReferenceError      : false,
+            RegExp              : false,
+            String              : false,
+            SyntaxError         : false,
+            TypeError           : false,
+            URIError            : false
+        },
+
+        standard_member = {
+            E                   : true,
+            LN2                 : true,
+            LN10                : true,
+            LOG2E               : true,
+            LOG10E              : true,
+            PI                  : true,
+            SQRT1_2             : true,
+            SQRT2               : true,
+            MAX_VALUE           : true,
+            MIN_VALUE           : true,
+            NEGATIVE_INFINITY   : true,
+            POSITIVE_INFINITY   : true
+        },
+
+        strict_mode,
+        syntax = {},
+        tab,
+        token,
+        urls,
+        warnings,
+
+// widget contains the global names which are provided to a Yahoo
+// (fna Konfabulator) widget.
+
+        widget = {
+            alert                   : true,
+            animator                : true,
+            appleScript             : true,
+            beep                    : true,
+            bytesToUIString         : true,
+            Canvas                  : true,
+            chooseColor             : true,
+            chooseFile              : true,
+            chooseFolder            : true,
+            closeWidget             : true,
+            COM                     : true,
+            convertPathToHFS        : true,
+            convertPathToPlatform   : true,
+            CustomAnimation         : true,
+            escape                  : true,
+            FadeAnimation           : true,
+            filesystem              : true,
+            Flash                   : true,
+            focusWidget             : true,
+            form                    : true,
+            FormField               : true,
+            Frame                   : true,
+            HotKey                  : true,
+            Image                   : true,
+            include                 : true,
+            isApplicationRunning    : true,
+            iTunes                  : true,
+            konfabulatorVersion     : true,
+            log                     : true,
+            md5                     : true,
+            MenuItem                : true,
+            MoveAnimation           : true,
+            openURL                 : true,
+            play                    : true,
+            Point                   : true,
+            popupMenu               : true,
+            preferenceGroups        : true,
+            preferences             : true,
+            print                   : true,
+            prompt                  : true,
+            random                  : true,
+            Rectangle               : true,
+            reloadWidget            : true,
+            ResizeAnimation         : true,
+            resolvePath             : true,
+            resumeUpdates           : true,
+            RotateAnimation         : true,
+            runCommand              : true,
+            runCommandInBg          : true,
+            saveAs                  : true,
+            savePreferences         : true,
+            screen                  : true,
+            ScrollBar               : true,
+            showWidgetPreferences   : true,
+            sleep                   : true,
+            speak                   : true,
+            Style                   : true,
+            suppressUpdates         : true,
+            system                  : true,
+            tellWidget              : true,
+            Text                    : true,
+            TextArea                : true,
+            Timer                   : true,
+            unescape                : true,
+            updateNow               : true,
+            URL                     : true,
+            Web                     : true,
+            widget                  : true,
+            Window                  : true,
+            XMLDOM                  : true,
+            XMLHttpRequest          : true,
+            yahooCheckLogin         : true,
+            yahooLogin              : true,
+            yahooLogout             : true
+        },
+
+//  xmode is used to adapt to the exceptions in html parsing.
+//  It can have these states:
+//      false   .js script file
+//      html
+//      outer
+//      script
+//      style
+//      scriptstring
+//      styleproperty
+
+        xmode,
+        xquote,
+
+// unsafe comment or string
+        ax = /@cc|<\/?|script|\]*s\]|<\s*!|&lt/i,
+// unsafe characters that are silently deleted by one or more browsers
+        cx = /[\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/,
+// token
+        tx = /^\s*([(){}\[.,:;'"~\?\]#@]|==?=?|\/(\*(jslint|members?|global)?|=|\/)?|\*[\/=]?|\+[+=]?|-[\-=]?|%=?|&[&=]?|\|[|=]?|>>?>?=?|<([\/=!]|\!(\[|--)?|<=?)?|\^=?|\!=?=?|[a-zA-Z_$][a-zA-Z0-9_$]*|[0-9]+([xX][0-9a-fA-F]+|\.[0-9]*)?([eE][+\-]?[0-9]+)?)/,
+// html token
+////////        hx = /^\s*(['"=>\/&#]|<(?:\/|\!(?:--)?)?|[a-zA-Z][a-zA-Z0-9_\-]*|[0-9]+|--|.)/,
+        hx = /^\s*(['"=>\/&#]|<(?:\/|\!(?:--)?)?|[a-zA-Z][a-zA-Z0-9_\-]*|[0-9]+|--)/,
+// characters in strings that need escapement
+        nx = /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/,
+        nxg = /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+// outer html token
+        ox = /[>&]|<[\/!]?|--/,
+// star slash
+        lx = /\*\/|\/\*/,
+// identifier
+        ix = /^([a-zA-Z_$][a-zA-Z0-9_$]*)$/,
+// javascript url
+        jx = /^(?:javascript|jscript|ecmascript|vbscript|mocha|livescript)\s*:/i,
+// url badness
+        ux = /&|\+|\u00AD|\.\.|\/\*|%[^;]|base64|url|expression|data|mailto/i,
+// style
+        sx = /^\s*([{:#%.=,>+\[\]@()"';]|\*=?|\$=|\|=|\^=|~=|[a-zA-Z_][a-zA-Z0-9_\-]*|[0-9]+|<\/|\/\*)/,
+        ssx = /^\s*([@#!"'};:\-%.=,+\[\]()*_]|[a-zA-Z][a-zA-Z0-9._\-]*|\/\*?|\d+(?:\.\d+)?|<\/)/,
+// attributes characters
+        qx = /[^a-zA-Z0-9-_\/ ]/,
+// query characters for ids
+        dx = /[\[\]\/\\"'*<>.&:(){}+=#]/,
+
+        rx = {
+            outer: hx,
+            html: hx,
+            style: sx,
+            styleproperty: ssx
+        };
+
+    function F() {}
+
+    if (typeof Object.create !== 'function') {
+        Object.create = function (o) {
+            F.prototype = o;
+            return new F();
+        };
+    }
+
+
+    function is_own(object, name) {
+        return Object.prototype.hasOwnProperty.call(object, name);
+    }
+
+
+    function combine(t, o) {
+        var n;
+        for (n in o) {
+            if (is_own(o, n)) {
+                t[n] = o[n];
+            }
+        }
+    }
+
+    String.prototype.entityify = function () {
+        return this.
+            replace(/&/g, '&amp;').
+            replace(/</g, '&lt;').
+            replace(/>/g, '&gt;');
+    };
+
+    String.prototype.isAlpha = function () {
+        return (this >= 'a' && this <= 'z\uffff') ||
+            (this >= 'A' && this <= 'Z\uffff');
+    };
+
+
+    String.prototype.isDigit = function () {
+        return (this >= '0' && this <= '9');
+    };
+
+
+    String.prototype.supplant = function (o) {
+        return this.replace(/\{([^{}]*)\}/g, function (a, b) {
+            var r = o[b];
+            return typeof r === 'string' || typeof r === 'number' ? r : a;
+        });
+    };
+
+    String.prototype.name = function () {
+
+// If the string looks like an identifier, then we can return it as is.
+// If the string contains no control characters, no quote characters, and no
+// backslash characters, then we can simply slap some quotes around it.
+// Otherwise we must also replace the offending characters with safe
+// sequences.
+
+        if (ix.test(this)) {
+            return this;
+        }
+        if (nx.test(this)) {
+            return '"' + this.replace(nxg, function (a) {
+                var c = escapes[a];
+                if (c) {
+                    return c;
+                }
+                return '\\u' + ('0000' + a.charCodeAt().toString(16)).slice(-4);
+            }) + '"';
+        }
+        return '"' + this + '"';
+    };
+
+
+    function assume() {
+        if (!option.safe) {
+            if (option.rhino) {
+                combine(predefined, rhino);
+            }
+            if (option.devel) {
+                combine(predefined, devel);
+            }
+            if (option.browser || option.sidebar) {
+                combine(predefined, browser);
+            }
+            if (option.sidebar) {
+                combine(predefined, sidebar);
+            }
+            if (option.widget) {
+                combine(predefined, widget);
+            }
+        }
+    }
+
+
+// Produce an error warning.
+
+    function quit(m, l, ch) {
+        throw {
+            name: 'JSLintError',
+            line: l,
+            character: ch,
+            message: m + " (" + Math.floor((l / lines.length) * 100) +
+                    "% scanned)."
+        };
+    }
+
+    function warning(m, t, a, b, c, d) {
+        var ch, l, w;
+        t = t || nexttoken;
+        if (t.id === '(end)') {  // `~
+            t = token;
+        }
+        l = t.line || 0;
+        ch = t.from || 0;
+        w = {
+            id: '(error)',
+            raw: m,
+            evidence: lines[l - 1] || '',
+            line: l,
+            character: ch,
+            a: a,
+            b: b,
+            c: c,
+            d: d
+        };
+        w.reason = m.supplant(w);
+        JSLINT.errors.push(w);
+        if (option.passfail) {
+            quit('Stopping. ', l, ch);
+        }
+        warnings += 1;
+        if (warnings >= option.maxerr) {
+            quit("Too many errors.", l, ch);
+        }
+        return w;
+    }
+
+    function warningAt(m, l, ch, a, b, c, d) {
+        return warning(m, {
+            line: l,
+            from: ch
+        }, a, b, c, d);
+    }
+
+    function error(m, t, a, b, c, d) {
+        var w = warning(m, t, a, b, c, d);
+        quit("Stopping, unable to continue.", w.line, w.character);
+    }
+
+    function errorAt(m, l, ch, a, b, c, d) {
+        return error(m, {
+            line: l,
+            from: ch
+        }, a, b, c, d);
+    }
+
+
+
+// lexical analysis
+
+    var lex = (function lex() {
+        var character, from, line, s;
+
+// Private lex methods
+
+        function nextLine() {
+            var at;
+            if (line >= lines.length) {
+                return false;
+            }
+            character = 1;
+            s = lines[line];
+            line += 1;
+            at = s.search(/ \t/);
+            if (at >= 0) {
+                warningAt("Mixed spaces and tabs.", line, at + 1);
+            }
+            s = s.replace(/\t/g, tab);
+            at = s.search(cx);
+            if (at >= 0) {
+                warningAt("Unsafe character.", line, at);
+            }
+            if (option.maxlen && option.maxlen < s.length) {
+                warningAt("Line too long.", line, s.length);
+            }
+            return true;
+        }
+
+// Produce a token object.  The token inherits from a syntax symbol.
+
+        function it(type, value) {
+            var i, t;
+            if (type === '(color)') {
+                t = {type: type};
+            } else if (type === '(punctuator)' ||
+                    (type === '(identifier)' && is_own(syntax, value))) {
+                t = syntax[value] || syntax['(error)'];
+            } else {
+                t = syntax[type];
+            }
+            t = Object.create(t);
+            if (type === '(string)' || type === '(range)') {
+                if (jx.test(value)) {
+                    warningAt("Script URL.", line, from);
+                }
+            }
+            if (type === '(identifier)') {
+                t.identifier = true;
+                if (value === '__iterator__' || value === '__proto__') {
+                    errorAt("Reserved name '{a}'.",
+                        line, from, value);
+                } else if (option.nomen &&
+                        (value.charAt(0) === '_' ||
+                         value.charAt(value.length - 1) === '_')) {
+                    warningAt("Unexpected {a} in '{b}'.", line, from,
+                        "dangling '_'", value);
+                }
+            }
+            t.value = value;
+            t.line = line;
+            t.character = character;
+            t.from = from;
+            i = t.id;
+            if (i !== '(endline)') {
+                prereg = i &&
+                    (('(,=:[!&|?{};'.indexOf(i.charAt(i.length - 1)) >= 0) ||
+                    i === 'return');
+            }
+            return t;
+        }
+
+// Public lex methods
+
+        return {
+            init: function (source) {
+                if (typeof source === 'string') {
+                    lines = source.
+                        replace(/\r\n/g, '\n').
+                        replace(/\r/g, '\n').
+                        split('\n');
+                } else {
+                    lines = source;
+                }
+                line = 0;
+                nextLine();
+                from = 1;
+            },
+
+            range: function (begin, end) {
+                var c, value = '';
+                from = character;
+                if (s.charAt(0) !== begin) {
+                    errorAt("Expected '{a}' and instead saw '{b}'.",
+                            line, character, begin, s.charAt(0));
+                }
+                for (;;) {
+                    s = s.slice(1);
+                    character += 1;
+                    c = s.charAt(0);
+                    switch (c) {
+                    case '':
+                        errorAt("Missing '{a}'.", line, character, c);
+                        break;
+                    case end:
+                        s = s.slice(1);
+                        character += 1;
+                        return it('(range)', value);
+                    case xquote:
+                    case '\\':
+                        warningAt("Unexpected '{a}'.", line, character, c);
+                    }
+                    value += c;
+                }
+
+            },
+
+// token -- this is called by advance to get the next token.
+
+            token: function () {
+                var b, c, captures, d, depth, high, i, l, low, q, t;
+
+                function match(x) {
+                    var r = x.exec(s), r1;
+                    if (r) {
+                        l = r[0].length;
+                        r1 = r[1];
+                        c = r1.charAt(0);
+                        s = s.substr(l);
+                        from = character + l - r1.length;
+                        character += l;
+                        return r1;
+                    }
+                }
+
+                function string(x) {
+                    var c, j, r = '';
+
+                    if (jsonmode && x !== '"') {
+                        warningAt("Strings must use doublequote.",
+                                line, character);
+                    }
+
+                    if (xquote === x || (xmode === 'scriptstring' && !xquote)) {
+                        return it('(punctuator)', x);
+                    }
+
+                    function esc(n) {
+                        var i = parseInt(s.substr(j + 1, n), 16);
+                        j += n;
+                        if (i >= 32 && i <= 126 &&
+                                i !== 34 && i !== 92 && i !== 39) {
+                            warningAt("Unnecessary escapement.", line, character);
+                        }
+                        character += n;
+                        c = String.fromCharCode(i);
+                    }
+                    j = 0;
+                    for (;;) {
+                        while (j >= s.length) {
+                            j = 0;
+                            if (xmode !== 'html' || !nextLine()) {
+                                errorAt("Unclosed string.", line, from);
+                            }
+                        }
+                        c = s.charAt(j);
+                        if (c === x) {
+                            character += 1;
+                            s = s.substr(j + 1);
+                            return it('(string)', r, x);
+                        }
+                        if (c < ' ') {
+                            if (c === '\n' || c === '\r') {
+                                break;
+                            }
+                            warningAt("Control character in string: {a}.",
+                                    line, character + j, s.slice(0, j));
+                        } else if (c === xquote) {
+                            warningAt("Bad HTML string", line, character + j);
+                        } else if (c === '<') {
+                            if (option.safe && xmode === 'html') {
+                                warningAt("ADsafe string violation.",
+                                        line, character + j);
+                            } else if (s.charAt(j + 1) === '/' && (xmode || option.safe)) {
+                                warningAt("Expected '<\\/' and instead saw '</'.", line, character);
+                            } else if (s.charAt(j + 1) === '!' && (xmode || option.safe)) {
+                                warningAt("Unexpected '<!' in a string.", line, character);
+                            }
+                        } else if (c === '\\') {
+                            if (xmode === 'html') {
+                                if (option.safe) {
+                                    warningAt("ADsafe string violation.",
+                                            line, character + j);
+                                }
+                            } else if (xmode === 'styleproperty') {
+                                j += 1;
+                                character += 1;
+                                c = s.charAt(j);
+                                if (c !== x) {
+                                    warningAt("Escapement in style string.",
+                                            line, character + j);
+                                }
+                            } else {
+                                j += 1;
+                                character += 1;
+                                c = s.charAt(j);
+                                switch (c) {
+                                case xquote:
+                                    warningAt("Bad HTML string", line,
+                                        character + j);
+                                    break;
+                                case '\\':
+                                case '\'':
+                                case '"':
+                                case '/':
+                                    break;
+                                case 'b':
+                                    c = '\b';
+                                    break;
+                                case 'f':
+                                    c = '\f';
+                                    break;
+                                case 'n':
+                                    c = '\n';
+                                    break;
+                                case 'r':
+                                    c = '\r';
+                                    break;
+                                case 't':
+                                    c = '\t';
+                                    break;
+                                case 'u':
+                                    esc(4);
+                                    break;
+                                case 'v':
+                                    c = '\v';
+                                    break;
+                                case 'x':
+                                    if (jsonmode) {
+                                        warningAt("Avoid \\x-.", line, character);
+                                    }
+                                    esc(2);
+                                    break;
+                                default:
+                                    warningAt("Bad escapement.", line, character);
+                                }
+                            }
+                        }
+                        r += c;
+                        character += 1;
+                        j += 1;
+                    }
+                }
+
+                for (;;) {
+                    if (!s) {
+                        return it(nextLine() ? '(endline)' : '(end)', '');
+                    }
+                    while (xmode === 'outer') {
+                        i = s.search(ox);
+                        if (i === 0) {
+                            break;
+                        } else if (i > 0) {
+                            character += 1;
+                            s = s.slice(i);
+                            break;
+                        } else {
+                            if (!nextLine()) {
+                                return it('(end)', '');
+                            }
+                        }
+                    }
+//                     t = match(rx[xmode] || tx);
+//                     if (!t) {
+//                         if (xmode === 'html') {
+//                             return it('(error)', s.charAt(0));
+//                         } else {
+//                             t = '';
+//                             c = '';
+//                             while (s && s < '!') {
+//                                 s = s.substr(1);
+//                             }
+//                             if (s) {
+//                                 errorAt("Unexpected '{a}'.",
+//                                         line, character, s.substr(0, 1));
+//                             }
+//                         }
+                    t = match(rx[xmode] || tx);
+                    if (!t) {
+                        t = '';
+                        c = '';
+                        while (s && s < '!') {
+                            s = s.substr(1);
+                        }
+                        if (s) {
+                            if (xmode === 'html') {
+                                return it('(error)', s.charAt(0));
+                            } else {
+                                errorAt("Unexpected '{a}'.",
+                                        line, character, s.substr(0, 1));
+                            }
+                        }
+                    } else {
+
+    //      identifier
+
+                        if (c.isAlpha() || c === '_' || c === '$') {
+                            return it('(identifier)', t);
+                        }
+
+    //      number
+
+                        if (c.isDigit()) {
+                            if (xmode !== 'style' && !isFinite(Number(t))) {
+                                warningAt("Bad number '{a}'.",
+                                    line, character, t);
+                            }
+                            if (xmode !== 'style' &&
+                                     xmode !== 'styleproperty' &&
+                                     s.substr(0, 1).isAlpha()) {
+                                warningAt("Missing space after '{a}'.",
+                                        line, character, t);
+                            }
+                            if (c === '0') {
+                                d = t.substr(1, 1);
+                                if (d.isDigit()) {
+                                    if (token.id !== '.' && xmode !== 'styleproperty') {
+                                        warningAt("Don't use extra leading zeros '{a}'.",
+                                            line, character, t);
+                                    }
+                                } else if (jsonmode && (d === 'x' || d === 'X')) {
+                                    warningAt("Avoid 0x-. '{a}'.",
+                                            line, character, t);
+                                }
+                            }
+                            if (t.substr(t.length - 1) === '.') {
+                                warningAt(
+        "A trailing decimal point can be confused with a dot '{a}'.",
+                                        line, character, t);
+                            }
+                            return it('(number)', t);
+                        }
+                        switch (t) {
+
+    //      string
+
+                        case '"':
+                        case "'":
+                            return string(t);
+
+    //      // comment
+
+                        case '//':
+                            if (src || (xmode && xmode !== 'script')) {
+                                warningAt("Unexpected comment.", line, character);
+                            } else if (xmode === 'script' && /<\s*\//i.test(s)) {
+                                warningAt("Unexpected <\/ in comment.", line, character);
+                            } else if ((option.safe || xmode === 'script') && ax.test(s)) {
+                                warningAt("Dangerous comment.", line, character);
+                            }
+                            s = '';
+                            token.comment = true;
+                            break;
+
+    //      /* comment
+
+                        case '/*':
+                            if (src || (xmode && xmode !== 'script' && xmode !== 'style' && xmode !== 'styleproperty')) {
+                                warningAt("Unexpected comment.", line, character);
+                            }
+                            if (option.safe && ax.test(s)) {
+                                warningAt("ADsafe comment violation.", line, character);
+                            }
+                            for (;;) {
+                                i = s.search(lx);
+                                if (i >= 0) {
+                                    break;
+                                }
+                                if (!nextLine()) {
+                                    errorAt("Unclosed comment.", line, character);
+                                } else {
+                                    if (option.safe && ax.test(s)) {
+                                        warningAt("ADsafe comment violation.", line, character);
+                                    }
+                                }
+                            }
+                            character += i + 2;
+                            if (s.substr(i, 1) === '/') {
+                                errorAt("Nested comment.", line, character);
+                            }
+                            s = s.substr(i + 2);
+                            token.comment = true;
+                            break;
+
+    //      /*members /*jslint /*global
+
+                        case '/*members':
+                        case '/*member':
+                        case '/*jslint':
+                        case '/*global':
+                        case '*/':
+                            return {
+                                value: t,
+                                type: 'special',
+                                line: line,
+                                character: character,
+                                from: from
+                            };
+
+                        case '':
+                            break;
+    //      /
+                        case '/':
+                            if (token.id === '/=') {
+                                errorAt(
+"A regular expression literal can be confused with '/='.", line, from);
+                            }
+                            if (prereg) {
+                                depth = 0;
+                                captures = 0;
+                                l = 0;
+                                for (;;) {
+                                    b = true;
+                                    c = s.charAt(l);
+                                    l += 1;
+                                    switch (c) {
+                                    case '':
+                                        errorAt("Unclosed regular expression.", line, from);
+                                        return;
+                                    case '/':
+                                        if (depth > 0) {
+                                            warningAt("Unescaped '{a}'.", line, from + l, '/');
+                                        }
+                                        c = s.substr(0, l - 1);
+                                        q = {
+                                            g: true,
+                                            i: true,
+                                            m: true
+                                        };
+                                        while (q[s.charAt(l)] === true) {
+                                            q[s.charAt(l)] = false;
+                                            l += 1;
+                                        }
+                                        character += l;
+                                        s = s.substr(l);
+                                        q = s.charAt(0);
+                                        if (q === '/' || q === '*') {
+                                            errorAt("Confusing regular expression.", line, from);
+                                        }
+                                        return it('(regexp)', c);
+                                    case '\\':
+                                        c = s.charAt(l);
+                                        if (c < ' ') {
+                                            warningAt("Unexpected control character in regular expression.", line, from + l);
+                                        } else if (c === '<') {
+                                            warningAt("Unexpected escaped character '{a}' in regular expression.", line, from + l, c);
+                                        }
+                                        l += 1;
+                                        break;
+                                    case '(':
+                                        depth += 1;
+                                        b = false;
+                                        if (s.charAt(l) === '?') {
+                                            l += 1;
+                                            switch (s.charAt(l)) {
+                                            case ':':
+                                            case '=':
+                                            case '!':
+                                                l += 1;
+                                                break;
+                                            default:
+                                                warningAt("Expected '{a}' and instead saw '{b}'.", line, from + l, ':', s.charAt(l));
+                                            }
+                                        } else {
+                                            captures += 1;
+                                        }
+                                        break;
+                                    case '|':
+                                        b = false;
+                                        break;
+                                    case ')':
+                                        if (depth === 0) {
+                                            warningAt("Unescaped '{a}'.", line, from + l, ')');
+                                        } else {
+                                            depth -= 1;
+                                        }
+                                        break;
+                                    case ' ':
+                                        q = 1;
+                                        while (s.charAt(l) === ' ') {
+                                            l += 1;
+                                            q += 1;
+                                        }
+                                        if (q > 1) {
+                                            warningAt("Spaces are hard to count. Use {{a}}.", line, from + l, q);
+                                        }
+                                        break;
+                                    case '[':
+                                        c = s.charAt(l);
+                                        if (c === '^') {
+                                            l += 1;
+                                            if (option.regexp) {
+                                                warningAt("Insecure '{a}'.", line, from + l, c);
+                                            }
+                                        }
+                                        q = false;
+                                        if (c === ']') {
+                                            warningAt("Empty class.", line, from + l - 1);
+                                            q = true;
+                                        }
+    klass:                              do {
+                                            c = s.charAt(l);
+                                            l += 1;
+                                            switch (c) {
+                                            case '[':
+                                            case '^':
+                                                warningAt("Unescaped '{a}'.", line, from + l, c);
+                                                q = true;
+                                                break;
+                                            case '-':
+                                                if (q) {
+                                                    q = false;
+                                                } else {
+                                                    warningAt("Unescaped '{a}'.", line, from + l, '-');
+                                                    q = true;
+                                                }
+                                                break;
+                                            case ']':
+                                                if (!q) {
+                                                    warningAt("Unescaped '{a}'.", line, from + l - 1, '-');
+                                                }
+                                                break klass;
+                                            case '\\':
+                                                c = s.charAt(l);
+                                                if (c < ' ') {
+                                                    warningAt("Unexpected control character in regular expression.", line, from + l);
+                                                } else if (c === '<') {
+                                                    warningAt("Unexpected escaped character '{a}' in regular expression.", line, from + l, c);
+                                                }
+                                                l += 1;
+                                                q = true;
+                                                break;
+                                            case '/':
+                                                warningAt("Unescaped '{a}'.", line, from + l - 1, '/');
+                                                q = true;
+                                                break;
+                                            case '<':
+                                                if (xmode === 'script') {
+                                                    c = s.charAt(l);
+                                                    if (c === '!' || c === '/') {
+                                                        warningAt("HTML confusion in regular expression '<{a}'.", line, from + l, c);
+                                                    }
+                                                }
+                                                q = true;
+                                                break;
+                                            default:
+                                                q = true;
+                                            }
+                                        } while (c);
+                                        break;
+                                    case '.':
+                                        if (option.regexp) {
+                                            warningAt("Insecure '{a}'.", line, from + l, c);
+                                        }
+                                        break;
+                                    case ']':
+                                    case '?':
+                                    case '{':
+                                    case '}':
+                                    case '+':
+                                    case '*':
+                                        warningAt("Unescaped '{a}'.", line, from + l, c);
+                                        break;
+                                    case '<':
+                                        if (xmode === 'script') {
+                                            c = s.charAt(l);
+                                            if (c === '!' || c === '/') {
+                                                warningAt("HTML confusion in regular expression '<{a}'.", line, from + l, c);
+                                            }
+                                        }
+                                    }
+                                    if (b) {
+                                        switch (s.charAt(l)) {
+                                        case '?':
+                                        case '+':
+                                        case '*':
+                                            l += 1;
+                                            if (s.charAt(l) === '?') {
+                                                l += 1;
+                                            }
+                                            break;
+                                        case '{':
+                                            l += 1;
+                                            c = s.charAt(l);
+                                            if (c < '0' || c > '9') {
+                                                warningAt("Expected a number and instead saw '{a}'.", line, from + l, c);
+                                            }
+                                            l += 1;
+                                            low = +c;
+                                            for (;;) {
+                                                c = s.charAt(l);
+                                                if (c < '0' || c > '9') {
+                                                    break;
+                                                }
+                                                l += 1;
+                                                low = +c + (low * 10);
+                                            }
+                                            high = low;
+                                            if (c === ',') {
+                                                l += 1;
+                                                high = Infinity;
+                                                c = s.charAt(l);
+                                                if (c >= '0' && c <= '9') {
+                                                    l += 1;
+                                                    high = +c;
+                                                    for (;;) {
+                                                        c = s.charAt(l);
+                                                        if (c < '0' || c > '9') {
+                                                            break;
+                                                        }
+                                                        l += 1;
+                                                        high = +c + (high * 10);
+                                                    }
+                                                }
+                                            }
+                                            if (s.charAt(l) !== '}') {
+                                                warningAt("Expected '{a}' and instead saw '{b}'.", line, from + l, '}', c);
+                                            } else {
+                                                l += 1;
+                                            }
+                                            if (s.charAt(l) === '?') {
+                                                l += 1;
+                                            }
+                                            if (low > high) {
+                                                warningAt("'{a}' should not be greater than '{b}'.", line, from + l, low, high);
+                                            }
+                                        }
+                                    }
+                                }
+                                c = s.substr(0, l - 1);
+                                character += l;
+                                s = s.substr(l);
+                                return it('(regexp)', c);
+                            }
+                            return it('(punctuator)', t);
+
+    //      punctuator
+
+                        case '<!--':
+                            l = line;
+                            c = character;
+                            for (;;) {
+                                i = s.indexOf('--');
+                                if (i >= 0) {
+                                    break;
+                                }
+                                i = s.indexOf('<!');
+                                if (i >= 0) {
+                                    errorAt("Nested HTML comment.",
+                                        line, character + i);
+                                }
+                                if (!nextLine()) {
+                                    errorAt("Unclosed HTML comment.", l, c);
+                                }
+                            }
+                            l = s.indexOf('<!');
+                            if (l >= 0 && l < i) {
+                                errorAt("Nested HTML comment.",
+                                    line, character + l);
+                            }
+                            character += i;
+                            if (s[i + 2] !== '>') {
+                                errorAt("Expected -->.", line, character);
+                            }
+                            character += 3;
+                            s = s.slice(i + 3);
+                            break;
+                        case '#':
+                            if (xmode === 'html' || xmode === 'styleproperty') {
+                                for (;;) {
+                                    c = s.charAt(0);
+                                    if ((c < '0' || c > '9') &&
+                                            (c < 'a' || c > 'f') &&
+                                            (c < 'A' || c > 'F')) {
+                                        break;
+                                    }
+                                    character += 1;
+                                    s = s.substr(1);
+                                    t += c;
+                                }
+                                if (t.length !== 4 && t.length !== 7) {
+                                    warningAt("Bad hex color '{a}'.", line,
+                                        from + l, t);
+                                }
+                                return it('(color)', t);
+                            }
+                            return it('(punctuator)', t);
+                        default:
+                            if (xmode === 'outer' && c === '&') {
+                                character += 1;
+                                s = s.substr(1);
+                                for (;;) {
+                                    c = s.charAt(0);
+                                    character += 1;
+                                    s = s.substr(1);
+                                    if (c === ';') {
+                                        break;
+                                    }
+                                    if (!((c >= '0' && c <= '9') ||
+                                            (c >= 'a' && c <= 'z') ||
+                                            c === '#')) {
+                                        errorAt("Bad entity", line, from + l,
+                                        character);
+                                    }
+                                }
+                                break;
+                            }
+                            return it('(punctuator)', t);
+                        }
+                    }
+                }
+            }
+        };
+    }());
+
+
+    function addlabel(t, type) {
+
+        if (option.safe && funct['(global)'] && typeof predefined[t] !== 'boolean') {
+            warning('ADsafe global: ' + t + '.', token);
+        } else if (t === 'hasOwnProperty') {
+            warning("'hasOwnProperty' is a really bad name.");
+        }
+
+// Define t in the current function in the current scope.
+
+        if (is_own(funct, t) && !funct['(global)']) {
+            warning(funct[t] === true ?
+                "'{a}' was used before it was defined." :
+                "'{a}' is already defined.",
+                nexttoken, t);
+        }
+        funct[t] = type;
+        if (funct['(global)']) {
+            global[t] = funct;
+            if (is_own(implied, t)) {
+                warning("'{a}' was used before it was defined.", nexttoken, t);
+                delete implied[t];
+            }
+        } else {
+            scope[t] = funct;
+        }
+    }
+
+
+    function doOption() {
+        var b, obj, filter, o = nexttoken.value, t, v;
+        switch (o) {
+        case '*/':
+            error("Unbegun comment.");
+            break;
+        case '/*members':
+        case '/*member':
+            o = '/*members';
+            if (!membersOnly) {
+                membersOnly = {};
+            }
+            obj = membersOnly;
+            break;
+        case '/*jslint':
+            if (option.safe) {
+                warning("ADsafe restriction.");
+            }
+            obj = option;
+            filter = boolOptions;
+            break;
+        case '/*global':
+            if (option.safe) {
+                warning("ADsafe restriction.");
+            }
+            obj = predefined;
+            break;
+        default:
+        }
+        t = lex.token();
+loop:   for (;;) {
+            for (;;) {
+                if (t.type === 'special' && t.value === '*/') {
+                    break loop;
+                }
+                if (t.id !== '(endline)' && t.id !== ',') {
+                    break;
+                }
+                t = lex.token();
+            }
+            if (t.type !== '(string)' && t.type !== '(identifier)' &&
+                    o !== '/*members') {
+                error("Bad option.", t);
+            }
+            v = lex.token();
+            if (v.id === ':') {
+                v = lex.token();
+                if (obj === membersOnly) {
+                    error("Expected '{a}' and instead saw '{b}'.",
+                            t, '*/', ':');
+                }
+                if (t.value === 'indent' && o === '/*jslint') {
+                    b = +v.value;
+                    if (typeof b !== 'number' || !isFinite(b) || b <= 0 ||
+                            Math.floor(b) !== b) {
+                        error("Expected a small integer and instead saw '{a}'.",
+                                v, v.value);
+                    }
+                    obj.white = true;
+                    obj.indent = b;
+                } else if (t.value === 'maxerr' && o === '/*jslint') {
+                    b = +v.value;
+                    if (typeof b !== 'number' || !isFinite(b) || b <= 0 ||
+                            Math.floor(b) !== b) {
+                        error("Expected a small integer and instead saw '{a}'.",
+                                v, v.value);
+                    }
+                    obj.maxerr = b;
+                } else if (t.value === 'maxlen' && o === '/*jslint') {
+                    b = +v.value;
+                    if (typeof b !== 'number' || !isFinite(b) || b <= 0 ||
+                            Math.floor(b) !== b) {
+                        error("Expected a small integer and instead saw '{a}'.",
+                                v, v.value);
+                    }
+                    obj.maxlen = b;
+                } else if (v.value === 'true') {
+                    obj[t.value] = true;
+                } else if (v.value === 'false') {
+                    obj[t.value] = false;
+                } else {
+                    error("Bad option value.", v);
+                }
+                t = lex.token();
+            } else {
+                if (o === '/*jslint') {
+                    error("Missing option value.", t);
+                }
+                obj[t.value] = false;
+                t = v;
+            }
+        }
+        if (filter) {
+            assume();
+        }
+    }
+
+
+// We need a peek function. If it has an argument, it peeks that much farther
+// ahead. It is used to distinguish
+//     for ( var i in ...
+// from
+//     for ( var i = ...
+
+    function peek(p) {
+        var i = p || 0, j = 0, t;
+
+        while (j <= i) {
+            t = lookahead[j];
+            if (!t) {
+                t = lookahead[j] = lex.token();
+            }
+            j += 1;
+        }
+        return t;
+    }
+
+
+
+// Produce the next token. It looks for programming errors.
+
+    function advance(id, t) {
+        switch (token.id) {
+        case '(number)':
+            if (nexttoken.id === '.') {
+                warning(
+"A dot following a number can be confused with a decimal point.", token);
+            }
+            break;
+        case '-':
+            if (nexttoken.id === '-' || nexttoken.id === '--') {
+                warning("Confusing minusses.");
+            }
+            break;
+        case '+':
+            if (nexttoken.id === '+' || nexttoken.id === '++') {
+                warning("Confusing plusses.");
+            }
+            break;
+        }
+        if (token.type === '(string)' || token.identifier) {
+            anonname = token.value;
+        }
+
+        if (id && nexttoken.id !== id) {
+            if (t) {
+                if (nexttoken.id === '(end)') {
+                    warning("Unmatched '{a}'.", t, t.id);
+                } else {
+                    warning("Expected '{a}' to match '{b}' from line {c} and instead saw '{d}'.",
+                            nexttoken, id, t.id, t.line, nexttoken.value);
+                }
+            } else if (nexttoken.type !== '(identifier)' ||
+                            nexttoken.value !== id) {
+                warning("Expected '{a}' and instead saw '{b}'.",
+                        nexttoken, id, nexttoken.value);
+            }
+        }
+        prevtoken = token;
+        token = nexttoken;
+        for (;;) {
+            nexttoken = lookahead.shift() || lex.token();
+            if (nexttoken.id === '(end)' || nexttoken.id === '(error)') {
+                return;
+            }
+            if (nexttoken.type === 'special') {
+                doOption();
+            } else {
+                if (nexttoken.id !== '(endline)') {
+                    break;
+                }
+            }
+        }
+    }
+
+
+// This is the heart of JSLINT, the Pratt parser. In addition to parsing, it
+// is looking for ad hoc lint patterns. We add to Pratt's model .fud, which is
+// like nud except that it is only used on the first token of a statement.
+// Having .fud makes it much easier to define JavaScript. I retained Pratt's
+// nomenclature.
+
+// .nud     Null denotation
+// .fud     First null denotation
+// .led     Left denotation
+//  lbp     Left binding power
+//  rbp     Right binding power
+
+// They are key to the parsing method called Top Down Operator Precedence.
+
+    function parse(rbp, initial) {
+        var left;
+        if (nexttoken.id === '(end)') {
+            error("Unexpected early end of program.", token);
+        }
+        advance();
+        if (option.safe && typeof predefined[token.value] === 'boolean' &&
+                (nexttoken.id !== '(' && nexttoken.id !== '.')) {
+            warning('ADsafe violation.', token);
+        }
+        if (initial) {
+            anonname = 'anonymous';
+            funct['(verb)'] = token.value;
+        }
+        if (initial === true && token.fud) {
+            left = token.fud();
+        } else {
+            if (token.nud) {
+                left = token.nud();
+            } else {
+                if (nexttoken.type === '(number)' && token.id === '.') {
+                    warning(
+"A leading decimal point can be confused with a dot: '.{a}'.",
+                            token, nexttoken.value);
+                    advance();
+                    return token;
+                } else {
+                    error("Expected an identifier and instead saw '{a}'.",
+                            token, token.id);
+                }
+            }
+            while (rbp < nexttoken.lbp) {
+                advance();
+                if (token.led) {
+                    left = token.led(left);
+                } else {
+                    error("Expected an operator and instead saw '{a}'.",
+                        token, token.id);
+                }
+            }
+        }
+        return left;
+    }
+
+
+// Functions for conformance of style.
+
+    function adjacent(left, right) {
+        left = left || token;
+        right = right || nexttoken;
+        if (option.white || xmode === 'styleproperty' || xmode === 'style') {
+            if (left.character !== right.from && left.line === right.line) {
+                warning("Unexpected space after '{a}'.", right, left.value);
+            }
+        }
+    }
+
+    function nospace(left, right) {
+        left = left || token;
+        right = right || nexttoken;
+        if (option.white && !left.comment) {
+            if (left.line === right.line) {
+                adjacent(left, right);
+            }
+        }
+    }
+
+
+    function nonadjacent(left, right) {
+        if (option.white) {
+            left = left || token;
+            right = right || nexttoken;
+            if (left.line === right.line && left.character === right.from) {
+                warning("Missing space after '{a}'.",
+                        nexttoken, left.value);
+            }
+        }
+    }
+
+    function nobreaknonadjacent(left, right) {
+        left = left || token;
+        right = right || nexttoken;
+        if (!option.laxbreak && left.line !== right.line) {
+            warning("Bad line breaking before '{a}'.", right, right.id);
+        } else if (option.white) {
+            left = left || token;
+            right = right || nexttoken;
+            if (left.character === right.from) {
+                warning("Missing space after '{a}'.",
+                        nexttoken, left.value);
+            }
+        }
+    }
+
+    function indentation(bias) {
+        var i;
+        if (option.white && nexttoken.id !== '(end)') {
+            i = indent + (bias || 0);
+            if (nexttoken.from !== i) {
+                warning("Expected '{a}' to have an indentation at {b} instead at {c}.",
+                        nexttoken, nexttoken.value, i, nexttoken.from);
+            }
+        }
+    }
+
+    function nolinebreak(t) {
+        t = t || token;
+        if (t.line !== nexttoken.line) {
+            warning("Line breaking error '{a}'.", t, t.value);
+        }
+    }
+
+
+    function comma() {
+        if (token.line !== nexttoken.line) {
+            if (!option.laxbreak) {
+                warning("Bad line breaking before '{a}'.", token, nexttoken.id);
+            }
+        } else if (token.character !== nexttoken.from && option.white) {
+            warning("Unexpected space after '{a}'.", nexttoken, token.value);
+        }
+        advance(',');
+        nonadjacent(token, nexttoken);
+    }
+
+
+// Functional constructors for making the symbols that will be inherited by
+// tokens.
+
+    function symbol(s, p) {
+        var x = syntax[s];
+        if (!x || typeof x !== 'object') {
+            syntax[s] = x = {
+                id: s,
+                lbp: p,
+                value: s
+            };
+        }
+        return x;
+    }
+
+
+    function delim(s) {
+        return symbol(s, 0);
+    }
+
+
+    function stmt(s, f) {
+        var x = delim(s);
+        x.identifier = x.reserved = true;
+        x.fud = f;
+        return x;
+    }
+
+
+    function blockstmt(s, f) {
+        var x = stmt(s, f);
+        x.block = true;
+        return x;
+    }
+
+
+    function reserveName(x) {
+        var c = x.id.charAt(0);
+        if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
+            x.identifier = x.reserved = true;
+        }
+        return x;
+    }
+
+
+    function prefix(s, f) {
+        var x = symbol(s, 150);
+        reserveName(x);
+        x.nud = (typeof f === 'function') ? f : function () {
+            this.right = parse(150);
+            this.arity = 'unary';
+            if (this.id === '++' || this.id === '--') {
+                if (option.plusplus) {
+                    warning("Unexpected use of '{a}'.", this, this.id);
+                } else if ((!this.right.identifier || this.right.reserved) &&
+                        this.right.id !== '.' && this.right.id !== '[') {
+                    warning("Bad operand.", this);
+                }
+            }
+            return this;
+        };
+        return x;
+    }
+
+
+    function type(s, f) {
+        var x = delim(s);
+        x.type = s;
+        x.nud = f;
+        return x;
+    }
+
+
+    function reserve(s, f) {
+        var x = type(s, f);
+        x.identifier = x.reserved = true;
+        return x;
+    }
+
+
+    function reservevar(s, v) {
+        return reserve(s, function () {
+            if (this.id === 'this' || this.id === 'arguments') {
+                if (strict_mode && funct['(global)']) {
+                    warning("Strict violation.", this);
+                } else if (option.safe) {
+                    warning("ADsafe violation.", this);
+                }
+            }
+            return this;
+        });
+    }
+
+
+    function infix(s, f, p, w) {
+        var x = symbol(s, p);
+        reserveName(x);
+        x.led = function (left) {
+            if (!w) {
+                nobreaknonadjacent(prevtoken, token);
+                nonadjacent(token, nexttoken);
+            }
+            if (typeof f === 'function') {
+                return f(left, this);
+            } else {
+                this.left = left;
+                this.right = parse(p);
+                return this;
+            }
+        };
+        return x;
+    }
+
+
+    function relation(s, f) {
+        var x = symbol(s, 100);
+        x.led = function (left) {
+            nobreaknonadjacent(prevtoken, token);
+            nonadjacent(token, nexttoken);
+            var right = parse(100);
+            if ((left && left.id === 'NaN') || (right && right.id === 'NaN')) {
+                warning("Use the isNaN function to compare with NaN.", this);
+            } else if (f) {
+                f.apply(this, [left, right]);
+            }
+            if (left.id === '!') {
+                warning("Confusing use of '{a}'.", left, '!');
+            }
+            if (right.id === '!') {
+                warning("Confusing use of '{a}'.", left, '!');
+            }
+            this.left = left;
+            this.right = right;
+            return this;
+        };
+        return x;
+    }
+
+
+    function isPoorRelation(node) {
+        return node &&
+              ((node.type === '(number)' && +node.value === 0) ||
+               (node.type === '(string)' && node.value === ' ') ||
+                node.type === 'true' ||
+                node.type === 'false' ||
+                node.type === 'undefined' ||
+                node.type === 'null');
+    }
+
+
+    function assignop(s, f) {
+        symbol(s, 20).exps = true;
+        return infix(s, function (left, that) {
+            var l;
+            that.left = left;
+            if (predefined[left.value] === false &&
+                    scope[left.value]['(global)'] === true) {
+                warning('Read only.', left);
+            }
+            if (option.safe) {
+                l = left;
+                do {
+                    if (typeof predefined[l.value] === 'boolean') {
+                        warning('ADsafe violation.', l);
+                    }
+                    l = l.left;
+                } while (l);
+            }
+            if (left) {
+                if (left.id === '.' || left.id === '[') {
+                    if (!left.left || left.left.value === 'arguments') {
+                        warning('Bad assignment.', that);
+                    }
+                    that.right = parse(19);
+                    return that;
+                } else if (left.identifier && !left.reserved) {
+                    if (funct[left.value] === 'exception') {
+                        warning("Do not assign to the exception parameter.", left);
+                    }
+                    that.right = parse(19);
+                    return that;
+                }
+                if (left === syntax['function']) {
+                    warning(
+"Expected an identifier in an assignment and instead saw a function invocation.",
+                                token);
+                }
+            }
+            error("Bad assignment.", that);
+        }, 20);
+    }
+
+    function bitwise(s, f, p) {
+        var x = symbol(s, p);
+        reserveName(x);
+        x.led = (typeof f === 'function') ? f : function (left) {
+            if (option.bitwise) {
+                warning("Unexpected use of '{a}'.", this, this.id);
+            }
+            this.left = left;
+            this.right = parse(p);
+            return this;
+        };
+        return x;
+    }
+
+    function bitwiseassignop(s) {
+        symbol(s, 20).exps = true;
+        return infix(s, function (left, that) {
+            if (option.bitwise) {
+                warning("Unexpected use of '{a}'.", that, that.id);
+            }
+            nonadjacent(prevtoken, token);
+            nonadjacent(token, nexttoken);
+            if (left) {
+                if (left.id === '.' || left.id === '[' ||
+                        (left.identifier && !left.reserved)) {
+                    parse(19);
+                    return that;
+                }
+                if (left === syntax['function']) {
+                    warning(
+"Expected an identifier in an assignment, and instead saw a function invocation.",
+                                token);
+                }
+                return that;
+            }
+            error("Bad assignment.", that);
+        }, 20);
+    }
+
+
+    function suffix(s, f) {
+        var x = symbol(s, 150);
+        x.led = function (left) {
+            if (option.plusplus) {
+                warning("Unexpected use of '{a}'.", this, this.id);
+            } else if ((!left.identifier || left.reserved) && left.id !== '.' && left.id !== '[') {
+                warning("Bad operand.", this);
+            }
+            this.left = left;
+            return this;
+        };
+        return x;
+    }
+
+
+    function optionalidentifier() {
+        if (nexttoken.reserved) {
+            warning("Expected an identifier and instead saw '{a}' (a reserved word).",
+                    nexttoken, nexttoken.id);
+        }
+        if (nexttoken.identifier) {
+            advance();
+            return token.value;
+        }
+    }
+
+
+    function identifier() {
+        var i = optionalidentifier();
+        if (i) {
+            return i;
+        }
+        if (token.id === 'function' && nexttoken.id === '(') {
+            warning("Missing name in function statement.");
+        } else {
+            error("Expected an identifier and instead saw '{a}'.",
+                    nexttoken, nexttoken.value);
+        }
+    }
+
+    function reachable(s) {
+        var i = 0, t;
+        if (nexttoken.id !== ';' || noreach) {
+            return;
+        }
+        for (;;) {
+            t = peek(i);
+            if (t.reach) {
+                return;
+            }
+            if (t.id !== '(endline)') {
+                if (t.id === 'function') {
+                    warning(
+"Inner functions should be listed at the top of the outer function.", t);
+                    break;
+                }
+                warning("Unreachable '{a}' after '{b}'.", t, t.value, s);
+                break;
+            }
+            i += 1;
+        }
+    }
+
+
+    function statement(noindent) {
+        var i = indent, r, s = scope, t = nexttoken;
+
+// We don't like the empty statement.
+
+        if (t.id === ';') {
+            warning("Unnecessary semicolon.", t);
+            advance(';');
+            return;
+        }
+
+// Is this a labelled statement?
+
+        if (t.identifier && !t.reserved && peek().id === ':') {
+            advance();
+            advance(':');
+            scope = Object.create(s);
+            addlabel(t.value, 'label');
+            if (!nexttoken.labelled) {
+                warning("Label '{a}' on {b} statement.",
+                        nexttoken, t.value, nexttoken.value);
+            }
+            if (jx.test(t.value + ':')) {
+                warning("Label '{a}' looks like a javascript url.",
+                        t, t.value);
+            }
+            nexttoken.label = t.value;
+            t = nexttoken;
+        }
+
+// Parse the statement.
+
+        if (!noindent) {
+            indentation();
+        }
+        r = parse(0, true);
+
+// Look for the final semicolon.
+
+        if (!t.block) {
+            if (!r || !r.exps) {
+                warning(
+"Expected an assignment or function call and instead saw an expression.",
+                        token);
+            } else if (r.id === '(' && r.left.id === 'new') {
+                warning("Do not use 'new' for side effects.");
+            }
+            if (nexttoken.id !== ';') {
+                warningAt("Missing semicolon.", token.line,
+                        token.from + token.value.length);
+            } else {
+                adjacent(token, nexttoken);
+                advance(';');
+                nonadjacent(token, nexttoken);
+            }
+        }
+
+// Restore the indentation.
+
+        indent = i;
+        scope = s;
+        return r;
+    }
+
+
+    function use_strict() {
+        if (nexttoken.value === 'use strict') {
+            advance();
+            advance(';');
+            strict_mode = true;
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+
+    function statements(begin) {
+        var a = [], f, p;
+        if (begin && !use_strict() && option.strict) {
+            warning('Missing "use strict" statement.', nexttoken);
+        }
+        if (option.adsafe) {
+            switch (begin) {
+            case 'script':
+                if (!adsafe_may) {
+                    if (nexttoken.value !== 'ADSAFE' ||
+                            peek(0).id !== '.' ||
+                            (peek(1).value !== 'id' &&
+                            peek(1).value !== 'go')) {
+                        error('ADsafe violation: Missing ADSAFE.id or ADSAFE.go.',
+                            nexttoken);
+                    }
+                }
+                if (nexttoken.value === 'ADSAFE' &&
+                        peek(0).id === '.' &&
+                        peek(1).value === 'id') {
+                    if (adsafe_may) {
+                        error('ADsafe violation.', nexttoken);
+                    }
+                    advance('ADSAFE');
+                    advance('.');
+                    advance('id');
+                    advance('(');
+                    if (nexttoken.value !== adsafe_id) {
+                        error('ADsafe violation: id does not match.', nexttoken);
+                    }
+                    advance('(string)');
+                    advance(')');
+                    advance(';');
+                    adsafe_may = true;
+                }
+                break;
+            case 'lib':
+                if (nexttoken.value === 'ADSAFE') {
+                    advance('ADSAFE');
+                    advance('.');
+                    advance('lib');
+                    advance('(');
+                    advance('(string)');
+                    comma();
+                    f = parse(0);
+                    if (f.id !== 'function') {
+                        error('The second argument to lib must be a function.', f);
+                    }
+                    p = f.funct['(params)'];
+                    p = p && p.join(', ');
+                    if (p && p !== 'lib') {
+                        error("Expected '{a}' and instead saw '{b}'.",
+                            f, '(lib)', '(' + p + ')');
+                    }
+                    advance(')');
+                    advance(';');
+                    return a;
+                } else {
+                    error("ADsafe lib violation.");
+                }
+            }
+        }
+        while (!nexttoken.reach && nexttoken.id !== '(end)') {
+            if (nexttoken.id === ';') {
+                warning("Unnecessary semicolon.");
+                advance(';');
+            } else {
+                a.push(statement());
+            }
+        }
+        return a;
+    }
+
+
+    function block(f) {
+        var a, b = inblock, old_indent = indent, s = scope, t;
+        inblock = f;
+        scope = Object.create(scope);
+        nonadjacent(token, nexttoken);
+        t = nexttoken;
+        if (nexttoken.id === '{') {
+            advance('{');
+            if (nexttoken.id !== '}' || token.line !== nexttoken.line) {
+                indent += option.indent;
+                while (!f && nexttoken.from > indent) {
+                    indent += option.indent;
+                }
+                if (!f) {
+                    use_strict();
+                }
+                a = statements();
+                indent -= option.indent;
+                indentation();
+            }
+            advance('}', t);
+            indent = old_indent;
+        } else {
+            warning("Expected '{a}' and instead saw '{b}'.",
+                    nexttoken, '{', nexttoken.value);
+            noreach = true;
+            a = [statement()];
+            noreach = false;
+        }
+        funct['(verb)'] = null;
+        scope = s;
+        inblock = b;
+        return a;
+    }
+
+
+// An identity function, used by string and number tokens.
+
+    function idValue() {
+        return this;
+    }
+
+
+    function countMember(m) {
+        if (membersOnly && typeof membersOnly[m] !== 'boolean') {
+            warning("Unexpected /*member '{a}'.", token, m);
+        }
+        if (typeof member[m] === 'number') {
+            member[m] += 1;
+        } else {
+            member[m] = 1;
+        }
+    }
+
+
+    function note_implied(token) {
+        var name = token.value, line = token.line, a = implied[name];
+        if (typeof a === 'function') {
+            a = false;
+        }
+        if (!a) {
+            a = [line];
+            implied[name] = a;
+        } else if (a[a.length - 1] !== line) {
+            a.push(line);
+        }
+    }
+
+// CSS parsing.
+
+
+    function cssName() {
+        if (nexttoken.identifier) {
+            advance();
+            return true;
+        }
+    }
+
+    function cssNumber() {
+        if (nexttoken.id === '-') {
+            advance('-');
+            adjacent();
+            nolinebreak();
+        }
+        if (nexttoken.type === '(number)') {
+            advance('(number)');
+            return true;
+        }
+    }
+
+    function cssString() {
+        if (nexttoken.type === '(string)') {
+            advance();
+            return true;
+        }
+    }
+
+    function cssColor() {
+        var i, number, value;
+        if (nexttoken.identifier) {
+            value = nexttoken.value;
+            if (value === 'rgb' || value === 'rgba') {
+                advance();
+                advance('(');
+                for (i = 0; i < 3; i += 1) {
+                    if (i) {
+                        advance(',');
+                    }
+                    number = nexttoken.value;
+                    if (nexttoken.type !== '(number)' || number < 0) {
+                        warning("Expected a positive number and instead saw '{a}'",
+                            nexttoken, number);
+                        advance();
+                    } else {
+                        advance();
+                        if (nexttoken.id === '%') {
+                            advance('%');
+                            if (number > 100) {
+                                warning("Expected a percentage and instead saw '{a}'",
+                                    token, number);
+                            }
+                        } else {
+                            if (number > 255) {
+                                warning("Expected a small number and instead saw '{a}'",
+                                    token, number);
+                            }
+                        }
+                    }
+                }
+                if (value === 'rgba') {
+                    advance(',');
+                    number = +nexttoken.value;
+                    if (nexttoken.type !== '(number)' || number < 0 || number > 1) {
+                        warning("Expected a number between 0 and 1 and instead saw '{a}'",
+                            nexttoken, number);
+                    }
+                    advance();
+                    if (nexttoken.id === '%') {
+                        warning("Unexpected '%'.");
+                        advance('%');
+                    }
+                }
+                advance(')');
+                return true;
+            } else if (cssColorData[nexttoken.value] === true) {
+                advance();
+                return true;
+            }
+        } else if (nexttoken.type === '(color)') {
+            advance();
+            return true;
+        }
+        return false;
+    }
+
+    function cssLength() {
+        if (nexttoken.id === '-') {
+            advance('-');
+            adjacent();
+            nolinebreak();
+        }
+        if (nexttoken.type === '(number)') {
+            advance();
+            if (nexttoken.type !== '(string)' &&
+                    cssLengthData[nexttoken.value] === true) {
+                adjacent();
+                advance();
+            } else if (+token.value !== 0) {
+                warning("Expected a linear unit and instead saw '{a}'.",
+                    nexttoken, nexttoken.value);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    function cssLineHeight() {
+        if (nexttoken.id === '-') {
+            advance('-');
+            adjacent();
+        }
+        if (nexttoken.type === '(number)') {
+            advance();
+            if (nexttoken.type !== '(string)' &&
+                    cssLengthData[nexttoken.value] === true) {
+                adjacent();
+                advance();
+            }
+            return true;
+        }
+        return false;
+    }
+
+    function cssWidth() {
+        if (nexttoken.identifier) {
+            switch (nexttoken.value) {
+            case 'thin':
+            case 'medium':
+            case 'thick':
+                advance();
+                return true;
+            }
+        } else {
+            return cssLength();
+        }
+    }
+
+    function cssMargin() {
+        if (nexttoken.identifier) {
+            if (nexttoken.value === 'auto') {
+                advance();
+                return true;
+            }
+        } else {
+            return cssLength();
+        }
+    }
+
+    function cssAttr() {
+        if (nexttoken.identifier && nexttoken.value === 'attr') {
+            advance();
+            advance('(');
+            if (!nexttoken.identifier) {
+                warning("Expected a name and instead saw '{a}'.",
+                        nexttoken, nexttoken.value);
+            }
+            advance();
+            advance(')');
+            return true;
+        }
+        return false;
+    }
+
+    function cssCommaList() {
+        while (nexttoken.id !== ';') {
+            if (!cssName() && !cssString()) {
+                warning("Expected a name and instead saw '{a}'.",
+                        nexttoken, nexttoken.value);
+            }
+            if (nexttoken.id !== ',') {
+                return true;
+            }
+            comma();
+        }
+    }
+
+    function cssCounter() {
+        if (nexttoken.identifier && nexttoken.value === 'counter') {
+            advance();
+            advance('(');
+            if (!nexttoken.identifier) {
+            }
+            advance();
+            if (nexttoken.id === ',') {
+                comma();
+                if (nexttoken.type !== '(string)') {
+                    warning("Expected a string and instead saw '{a}'.",
+                        nexttoken, nexttoken.value);
+                }
+                advance();
+            }
+            advance(')');
+            return true;
+        }
+        if (nexttoken.identifier && nexttoken.value === 'counters') {
+            advance();
+            advance('(');
+            if (!nexttoken.identifier) {
+                warning("Expected a name and instead saw '{a}'.",
+                        nexttoken, nexttoken.value);
+            }
+            advance();
+            if (nexttoken.id === ',') {
+                comma();
+                if (nexttoken.type !== '(string)') {
+                    warning("Expected a string and instead saw '{a}'.",
+                        nexttoken, nexttoken.value);
+                }
+                advance();
+            }
+            if (nexttoken.id === ',') {
+                comma();
+                if (nexttoken.type !== '(string)') {
+                    warning("Expected a string and instead saw '{a}'.",
+                        nexttoken, nexttoken.value);
+                }
+                advance();
+            }
+            advance(')');
+            return true;
+        }
+        return false;
+    }
+
+
+    function cssShape() {
+        var i;
+        if (nexttoken.identifier && nexttoken.value === 'rect') {
+            advance();
+            advance('(');
+            for (i = 0; i < 4; i += 1) {
+                if (!cssLength()) {
+                    warning("Expected a number and instead saw '{a}'.",
+                        nexttoken, nexttoken.value);
+                    break;
+                }
+            }
+            advance(')');
+            return true;
+        }
+        return false;
+    }
+
+    function cssUrl() {
+        var c, url;
+        if (nexttoken.identifier && nexttoken.value === 'url') {
+            nexttoken = lex.range('(', ')');
+            url = nexttoken.value;
+            c = url.charAt(0);
+            if (c === '"' || c === '\'') {
+                if (url.slice(-1) !== c) {
+                    warning("Bad url string.");
+                } else {
+                    url = url.slice(1, -1);
+                    if (url.indexOf(c) >= 0) {
+                        warning("Bad url string.");
+                    }
+                }
+            }
+            if (!url) {
+                warning("Missing url.");
+            }
+            advance();
+            if (option.safe && ux.test(url)) {
+                error("ADsafe URL violation.");
+            }
+            urls.push(url);
+            return true;
+        }
+        return false;
+    }
+
+    cssAny = [cssUrl, function () {
+        for (;;) {
+            if (nexttoken.identifier) {
+                switch (nexttoken.value.toLowerCase()) {
+                case 'url':
+                    cssUrl();
+                    break;
+                case 'expression':
+                    warning("Unexpected expression '{a}'.",
+                        nexttoken, nexttoken.value);
+                    advance();
+                    break;
+                default:
+                    advance();
+                }
+            } else {
+                if (nexttoken.id === ';' || nexttoken.id === '!'  ||
+                        nexttoken.id === '(end)' || nexttoken.id === '}') {
+                    return true;
+                }
+                advance();
+            }
+        }
+    }];
+
+    cssBorderStyle = [
+        'none', 'hidden', 'dotted', 'dashed', 'solid', 'double', 'ridge',
+        'inset', 'outset'
+    ];
+
+    cssBreak = [
+        'auto', 'always', 'avoid', 'left', 'right'
+    ];
+
+    cssOverflow = [
+        'auto', 'hidden', 'scroll', 'visible'
+    ];
+
+    cssAttributeData = {
+        background: [
+            true, 'background-attachment', 'background-color',
+            'background-image', 'background-position', 'background-repeat'
+        ],
+        'background-attachment': ['scroll', 'fixed'],
+        'background-color': ['transparent', cssColor],
+        'background-image': ['none', cssUrl],
+        'background-position': [
+            2, [cssLength, 'top', 'bottom', 'left', 'right', 'center']
+        ],
+        'background-repeat': [
+            'repeat', 'repeat-x', 'repeat-y', 'no-repeat'
+        ],
+        'border': [true, 'border-color', 'border-style', 'border-width'],
+        'border-bottom': [
+            true, 'border-bottom-color', 'border-bottom-style',
+            'border-bottom-width'
+        ],
+        'border-bottom-color': cssColor,
+        'border-bottom-style': cssBorderStyle,
+        'border-bottom-width': cssWidth,
+        'border-collapse': ['collapse', 'separate'],
+        'border-color': ['transparent', 4, cssColor],
+        'border-left': [
+            true, 'border-left-color', 'border-left-style', 'border-left-width'
+        ],
+        'border-left-color': cssColor,
+        'border-left-style': cssBorderStyle,
+        'border-left-width': cssWidth,
+        'border-right': [
+            true, 'border-right-color', 'border-right-style',
+            'border-right-width'
+        ],
+        'border-right-color': cssColor,
+        'border-right-style': cssBorderStyle,
+        'border-right-width': cssWidth,
+        'border-spacing': [2, cssLength],
+        'border-style': [4, cssBorderStyle],
+        'border-top': [
+            true, 'border-top-color', 'border-top-style', 'border-top-width'
+        ],
+        'border-top-color': cssColor,
+        'border-top-style': cssBorderStyle,
+        'border-top-width': cssWidth,
+        'border-width': [4, cssWidth],
+        bottom: [cssLength, 'auto'],
+        'caption-side' : ['bottom', 'left', 'right', 'top'],
+        clear: ['both', 'left', 'none', 'right'],
+        clip: [cssShape, 'auto'],
+        color: cssColor,
+        content: [
+            'open-quote', 'close-quote', 'no-open-quote', 'no-close-quote',
+            cssString, cssUrl, cssCounter, cssAttr
+        ],
+        'counter-increment': [
+            cssName, 'none'
+        ],
+        'counter-reset': [
+            cssName, 'none'
+        ],
+        cursor: [
+            cssUrl, 'auto', 'crosshair', 'default', 'e-resize', 'help', 'move',
+            'n-resize', 'ne-resize', 'nw-resize', 'pointer', 's-resize',
+            'se-resize', 'sw-resize', 'w-resize', 'text', 'wait'
+        ],
+        direction: ['ltr', 'rtl'],
+        display: [
+            'block', 'compact', 'inline', 'inline-block', 'inline-table',
+            'list-item', 'marker', 'none', 'run-in', 'table', 'table-caption',
+            'table-cell', 'table-column', 'table-column-group',
+            'table-footer-group', 'table-header-group', 'table-row',
+            'table-row-group'
+        ],
+        'empty-cells': ['show', 'hide'],
+        'float': ['left', 'none', 'right'],
+        font: [
+            'caption', 'icon', 'menu', 'message-box', 'small-caption',
+            'status-bar', true, 'font-size', 'font-style', 'font-weight',
+            'font-family'
+        ],
+        'font-family': cssCommaList,
+        'font-size': [
+            'xx-small', 'x-small', 'small', 'medium', 'large', 'x-large',
+            'xx-large', 'larger', 'smaller', cssLength
+        ],
+        'font-size-adjust': ['none', cssNumber],
+        'font-stretch': [
+            'normal', 'wider', 'narrower', 'ultra-condensed',
+            'extra-condensed', 'condensed', 'semi-condensed',
+            'semi-expanded', 'expanded', 'extra-expanded'
+        ],
+        'font-style': [
+            'normal', 'italic', 'oblique'
+        ],
+        'font-variant': [
+            'normal', 'small-caps'
+        ],
+        'font-weight': [
+            'normal', 'bold', 'bolder', 'lighter', cssNumber
+        ],
+        height: [cssLength, 'auto'],
+        left: [cssLength, 'auto'],
+        'letter-spacing': ['normal', cssLength],
+        'line-height': ['normal', cssLineHeight],
+        'list-style': [
+            true, 'list-style-image', 'list-style-position', 'list-style-type'
+        ],
+        'list-style-image': ['none', cssUrl],
+        'list-style-position': ['inside', 'outside'],
+        'list-style-type': [
+            'circle', 'disc', 'square', 'decimal', 'decimal-leading-zero',
+            'lower-roman', 'upper-roman', 'lower-greek', 'lower-alpha',
+            'lower-latin', 'upper-alpha', 'upper-latin', 'hebrew', 'katakana',
+            'hiragana-iroha', 'katakana-oroha', 'none'
+        ],
+        margin: [4, cssMargin],
+        'margin-bottom': cssMargin,
+        'margin-left': cssMargin,
+        'margin-right': cssMargin,
+        'margin-top': cssMargin,
+        'marker-offset': [cssLength, 'auto'],
+        'max-height': [cssLength, 'none'],
+        'max-width': [cssLength, 'none'],
+        'min-height': cssLength,
+        'min-width': cssLength,
+        opacity: cssNumber,
+        outline: [true, 'outline-color', 'outline-style', 'outline-width'],
+        'outline-color': ['invert', cssColor],
+        'outline-style': [
+            'dashed', 'dotted', 'double', 'groove', 'inset', 'none',
+            'outset', 'ridge', 'solid'
+        ],
+        'outline-width': cssWidth,
+        overflow: cssOverflow,
+        'overflow-x': cssOverflow,
+        'overflow-y': cssOverflow,
+        padding: [4, cssLength],
+        'padding-bottom': cssLength,
+        'padding-left': cssLength,
+        'padding-right': cssLength,
+        'padding-top': cssLength,
+        'page-break-after': cssBreak,
+        'page-break-before': cssBreak,
+        position: ['absolute', 'fixed', 'relative', 'static'],
+        quotes: [8, cssString],
+        right: [cssLength, 'auto'],
+        'table-layout': ['auto', 'fixed'],
+        'text-align': ['center', 'justify', 'left', 'right'],
+        'text-decoration': [
+            'none', 'underline', 'overline', 'line-through', 'blink'
+        ],
+        'text-indent': cssLength,
+        'text-shadow': ['none', 4, [cssColor, cssLength]],
+        'text-transform': ['capitalize', 'uppercase', 'lowercase', 'none'],
+        top: [cssLength, 'auto'],
+        'unicode-bidi': ['normal', 'embed', 'bidi-override'],
+        'vertical-align': [
+            'baseline', 'bottom', 'sub', 'super', 'top', 'text-top', 'middle',
+            'text-bottom', cssLength
+        ],
+        visibility: ['visible', 'hidden', 'collapse'],
+        'white-space': [
+            'normal', 'nowrap', 'pre', 'pre-line', 'pre-wrap', 'inherit'
+        ],
+        width: [cssLength, 'auto'],
+        'word-spacing': ['normal', cssLength],
+        'word-wrap': ['break-word', 'normal'],
+        'z-index': ['auto', cssNumber]
+    };
+
+    function styleAttribute() {
+        var v;
+        while (nexttoken.id === '*' || nexttoken.id === '#' ||
+                nexttoken.value === '_') {
+            if (!option.css) {
+                warning("Unexpected '{a}'.", nexttoken, nexttoken.value);
+            }
+            advance();
+        }
+        if (nexttoken.id === '-') {
+            if (!option.css) {
+                warning("Unexpected '{a}'.", nexttoken, nexttoken.value);
+            }
+            advance('-');
+            if (!nexttoken.identifier) {
+                warning(
+"Expected a non-standard style attribute and instead saw '{a}'.",
+                    nexttoken, nexttoken.value);
+            }
+            advance();
+            return cssAny;
+        } else {
+            if (!nexttoken.identifier) {
+                warning("Excepted a style attribute, and instead saw '{a}'.",
+                    nexttoken, nexttoken.value);
+            } else {
+                if (is_own(cssAttributeData, nexttoken.value)) {
+                    v = cssAttributeData[nexttoken.value];
+                } else {
+                    v = cssAny;
+                    if (!option.css) {
+                        warning("Unrecognized style attribute '{a}'.",
+                                nexttoken, nexttoken.value);
+                    }
+                }
+            }
+            advance();
+            return v;
+        }
+    }
+
+    function styleValue(v) {
+        var i = 0,
+            n,
+            once,
+            match,
+            round,
+            start = 0,
+            vi;
+        switch (typeof v) {
+        case 'function':
+            return v();
+        case 'string':
+            if (nexttoken.identifier && nexttoken.value === v) {
+                advance();
+                return true;
+            }
+            return false;
+        }
+        for (;;) {
+            if (i >= v.length) {
+                return false;
+            }
+            vi = v[i];
+            i += 1;
+            if (vi === true) {
+                break;
+            } else if (typeof vi === 'number') {
+                n = vi;
+                vi = v[i];
+                i += 1;
+            } else {
+                n = 1;
+            }
+            match = false;
+            while (n > 0) {
+                if (styleValue(vi)) {
+                    match = true;
+                    n -= 1;
+                } else {
+                    break;
+                }
+            }
+            if (match) {
+                return true;
+            }
+        }
+        start = i;
+        once = [];
+        for (;;) {
+            round = false;
+            for (i = start; i < v.length; i += 1) {
+                if (!once[i]) {
+                    if (styleValue(cssAttributeData[v[i]])) {
+                        match = true;
+                        round = true;
+                        once[i] = true;
+                        break;
+                    }
+                }
+            }
+            if (!round) {
+                return match;
+            }
+        }
+    }
+
+    function styleChild() {
+        if (nexttoken.id === '(number)') {
+            advance();
+            if (nexttoken.value === 'n' && nexttoken.identifier) {
+                adjacent();
+                advance();
+                if (nexttoken.id === '+') {
+                    adjacent();
+                    advance('+');
+                    adjacent();
+                    advance('(number)');
+                }
+            }
+            return;
+        } else {
+            switch (nexttoken.value) {
+            case 'odd':
+            case 'even':
+                if (nexttoken.identifier) {
+                    advance();
+                    return;
+                }
+            }
+        }
+        warning("Unexpected token '{a}'.", nexttoken, nexttoken.value);
+    }
+
+    function substyle() {
+        var v;
+        for (;;) {
+            if (nexttoken.id === '}' || nexttoken.id === '(end)' ||
+                    xquote && nexttoken.id === xquote) {
+                return;
+            }
+            while (nexttoken.id === ';') {
+                warning("Misplaced ';'.");
+                advance(';');
+            }
+            v = styleAttribute();
+            advance(':');
+            if (nexttoken.identifier && nexttoken.value === 'inherit') {
+                advance();
+            } else {
+                if (!styleValue(v)) {
+                    warning("Unexpected token '{a}'.", nexttoken,
+                        nexttoken.value);
+                    advance();
+                }
+            }
+            if (nexttoken.id === '!') {
+                advance('!');
+                adjacent();
+                if (nexttoken.identifier && nexttoken.value === 'important') {
+                    advance();
+                } else {
+                    warning("Expected '{a}' and instead saw '{b}'.",
+                        nexttoken, 'important', nexttoken.value);
+                }
+            }
+            if (nexttoken.id === '}' || nexttoken.id === xquote) {
+                warning("Missing '{a}'.", nexttoken, ';');
+            } else {
+                advance(';');
+            }
+        }
+    }
+
+    function styleSelector() {
+        if (nexttoken.identifier) {
+            if (!is_own(htmltag, nexttoken.value)) {
+                warning("Expected a tagName, and instead saw {a}.",
+                    nexttoken, nexttoken.value);
+            }
+            advance();
+        } else {
+            switch (nexttoken.id) {
+            case '>':
+            case '+':
+                advance();
+                styleSelector();
+                break;
+            case ':':
+                advance(':');
+                switch (nexttoken.value) {
+                case 'active':
+                case 'after':
+                case 'before':
+                case 'checked':
+                case 'disabled':
+                case 'empty':
+                case 'enabled':
+                case 'first-child':
+                case 'first-letter':
+                case 'first-line':
+                case 'first-of-type':
+                case 'focus':
+                case 'hover':
+                case 'last-of-type':
+                case 'link':
+                case 'only-of-type':
+                case 'root':
+                case 'target':
+                case 'visited':
+                    advance();
+                    break;
+                case 'lang':
+                    advance();
+                    advance('(');
+                    if (!nexttoken.identifier) {
+                        warning("Expected a lang code, and instead saw :{a}.",
+                            nexttoken, nexttoken.value);
+                    }
+                    advance(')');
+                    break;
+                case 'nth-child':
+                case 'nth-last-child':
+                case 'nth-last-of-type':
+                case 'nth-of-type':
+                    advance();
+                    advance('(');
+                    styleChild();
+                    advance(')');
+                    break;
+                case 'not':
+                    advance();
+                    advance('(');
+                    if (nexttoken.id === ':' && peek(0).value === 'not') {
+                        warning("Nested not.");
+                    }
+                    styleSelector();
+                    advance(')');
+                    break;
+                default:
+                    warning("Expected a pseudo, and instead saw :{a}.",
+                        nexttoken, nexttoken.value);
+                }
+                break;
+            case '#':
+                advance('#');
+                if (!nexttoken.identifier) {
+                    warning("Expected an id, and instead saw #{a}.",
+                        nexttoken, nexttoken.value);
+                }
+                advance();
+                break;
+            case '*':
+                advance('*');
+                break;
+            case '.':
+                advance('.');
+                if (!nexttoken.identifier) {
+                    warning("Expected a class, and instead saw #.{a}.",
+                        nexttoken, nexttoken.value);
+                }
+                advance();
+                break;
+            case '[':
+                advance('[');
+                if (!nexttoken.identifier) {
+                    warning("Expected an attribute, and instead saw [{a}].",
+                        nexttoken, nexttoken.value);
+                }
+                advance();
+                if (nexttoken.id === '=' || nexttoken.value === '~=' ||
+                        nexttoken.value === '$=' ||
+                        nexttoken.value === '|=' ||
+                        nexttoken.id === '*=' ||
+                        nexttoken.id === '^=') {
+                    advance();
+                    if (nexttoken.type !== '(string)') {
+                        warning("Expected a string, and instead saw {a}.",
+                            nexttoken, nexttoken.value);
+                    }
+                    advance();
+                }
+                advance(']');
+                break;
+            default:
+                error("Expected a CSS selector, and instead saw {a}.",
+                    nexttoken, nexttoken.value);
+            }
+        }
+    }
+
+    function stylePattern() {
+        var name;
+        if (nexttoken.id === '{') {
+            warning("Expected a style pattern, and instead saw '{a}'.", nexttoken,
+                nexttoken.id);
+        } else if (nexttoken.id === '@') {
+            advance('@');
+            name = nexttoken.value;
+            if (nexttoken.identifier && atrule[name] === true) {
+                advance();
+                return name;
+            }
+            warning("Expected an at-rule, and instead saw @{a}.", nexttoken, name);
+        }
+        for (;;) {
+            styleSelector();
+            if (nexttoken.id === '</' || nexttoken.id === '{' ||
+                    nexttoken.id === '(end)') {
+                return '';
+            }
+            if (nexttoken.id === ',') {
+                comma();
+            }
+        }
+    }
+
+    function styles() {
+        var i;
+        while (nexttoken.id === '@') {
+            i = peek();
+            if (i.identifier && i.value === 'import') {
+                advance('@');
+                advance();
+                if (!cssUrl()) {
+                    warning("Expected '{a}' and instead saw '{b}'.", nexttoken,
+                        'url', nexttoken.value);
+                    advance();
+                }
+                advance(';');
+            } else {
+                break;
+            }
+        }
+        while (nexttoken.id !== '</' && nexttoken.id !== '(end)') {
+            stylePattern();
+            xmode = 'styleproperty';
+            if (nexttoken.id === ';') {
+                advance(';');
+            } else {
+                advance('{');
+                substyle();
+                xmode = 'style';
+                advance('}');
+            }
+        }
+    }
+
+
+// HTML parsing.
+
+    function doBegin(n) {
+        if (n !== 'html' && !option.fragment) {
+            if (n === 'div' && option.adsafe) {
+                error("ADSAFE: Use the fragment option.");
+            } else {
+                error("Expected '{a}' and instead saw '{b}'.",
+                    token, 'html', n);
+            }
+        }
+        if (option.adsafe) {
+            if (n === 'html') {
+                error(
+"Currently, ADsafe does not operate on whole HTML documents. It operates on <div> fragments and .js files.", token);
+            }
+            if (option.fragment) {
+                if (n !== 'div') {
+                    error("ADsafe violation: Wrap the widget in a div.", token);
+                }
+            } else {
+                error("Use the fragment option.", token);
+            }
+        }
+        option.browser = true;
+        assume();
+    }
+
+    function doAttribute(n, a, v) {
+        var u, x;
+        if (a === 'id') {
+            u = typeof v === 'string' ? v.toUpperCase() : '';
+            if (ids[u] === true) {
+                warning("Duplicate id='{a}'.", nexttoken, v);
+            }
+            if (!/^[A-Za-z][A-Za-z0-9._:\-]*$/.test(v)) {
+                warning("Bad id: '{a}'.", nexttoken, v);
+            } else if (option.adsafe) {
+                if (adsafe_id) {
+                    if (v.slice(0, adsafe_id.length) !== adsafe_id) {
+                        warning("ADsafe violation: An id must have a '{a}' prefix",
+                                nexttoken, adsafe_id);
+                    } else if (!/^[A-Z]+_[A-Z]+$/.test(v)) {
+                        warning("ADSAFE violation: bad id.");
+                    }
+                } else {
+                    adsafe_id = v;
+                    if (!/^[A-Z]+_$/.test(v)) {
+                        warning("ADSAFE violation: bad id.");
+                    }
+                }
+            }  
+            x = v.search(dx);
+            if (x >= 0) {
+                warning("Unexpected character '{a}' in {b}.", token, v.charAt(x), a);
+            }
+            ids[u] = true;
+        } else if (a === 'class' || a === 'type' || a === 'name') {
+            x = v.search(qx);
+            if (x >= 0) {
+                warning("Unexpected character '{a}' in {b}.", token, v.charAt(x), a);
+            }
+            ids[u] = true;
+        } else if (a === 'href' || a === 'background' ||
+                a === 'content' || a === 'data' ||
+                a.indexOf('src') >= 0 || a.indexOf('url') >= 0) {
+            if (option.safe && ux.test(v)) {
+                error("ADsafe URL violation.");
+            }
+            urls.push(v);
+        } else if (a === 'for') {
+            if (option.adsafe) {
+                if (adsafe_id) {
+                    if (v.slice(0, adsafe_id.length) !== adsafe_id) {
+                        warning("ADsafe violation: An id must have a '{a}' prefix",
+                                nexttoken, adsafe_id);
+                    } else if (!/^[A-Z]+_[A-Z]+$/.test(v)) {
+                        warning("ADSAFE violation: bad id.");
+                    }
+                } else {
+                    warning("ADSAFE violation: bad id.");
+                }
+            }
+        } else if (a === 'name') {
+            if (option.adsafe && v.indexOf('_') >= 0) {
+                warning("ADsafe name violation.");
+            }
+        }
+    }
+
+    function doTag(n, a) {
+        var i, t = htmltag[n], x;
+        src = false;
+        if (!t) {
+            error("Unrecognized tag '<{a}>'.",
+                    nexttoken,
+                    n === n.toLowerCase() ? n :
+                        n + ' (capitalization error)');
+        }
+        if (stack.length > 0) {
+            if (n === 'html') {
+                error("Too many <html> tags.", token);
+            }
+            x = t.parent;
+            if (x) {
+                if (x.indexOf(' ' + stack[stack.length - 1].name + ' ') < 0) {
+                    error("A '<{a}>' must be within '<{b}>'.",
+                            token, n, x);
+                }
+            } else if (!option.adsafe && !option.fragment) {
+                i = stack.length;
+                do {
+                    if (i <= 0) {
+                        error("A '<{a}>' must be within '<{b}>'.",
+                                token, n, 'body');
+                    }
+                    i -= 1;
+                } while (stack[i].name !== 'body');
+            }
+        }
+        switch (n) {
+        case 'div':
+            if (option.adsafe && stack.length === 1 && !adsafe_id) {
+                warning("ADSAFE violation: missing ID_.");
+            }
+            break;
+        case 'script':
+            xmode = 'script';
+            advance('>');
+            indent = nexttoken.from;
+            if (a.lang) {
+                warning("lang is deprecated.", token);
+            }
+            if (option.adsafe && stack.length !== 1) {
+                warning("ADsafe script placement violation.", token);
+            }
+            if (a.src) {
+                if (option.adsafe && (!adsafe_may || !approved[a.src])) {
+                    warning("ADsafe unapproved script source.", token);
+                }
+                if (a.type) {
+                    warning("type is unnecessary.", token);
+                }
+            } else {
+                if (adsafe_went) {
+                    error("ADsafe script violation.", token);
+                }
+                statements('script');
+            }
+            xmode = 'html';
+            advance('</');
+            if (!nexttoken.identifier && nexttoken.value !== 'script') {
+                warning("Expected '{a}' and instead saw '{b}'.",
+                        nexttoken, 'script', nexttoken.value);
+            }
+            advance();
+            xmode = 'outer';
+            break;
+        case 'style':
+            xmode = 'style';
+            advance('>');
+            styles();
+            xmode = 'html';
+            advance('</');
+            if (!nexttoken.identifier && nexttoken.value !== 'style') {
+                warning("Expected '{a}' and instead saw '{b}'.",
+                        nexttoken, 'style', nexttoken.value);
+            }
+            advance();
+            xmode = 'outer';
+            break;
+        case 'input':
+            switch (a.type) {
+            case 'radio':
+            case 'checkbox':
+            case 'button':
+            case 'reset':
+            case 'submit':
+                break;
+            case 'text':
+            case 'file':
+            case 'password':
+            case 'file':
+            case 'hidden':
+            case 'image':
+                if (option.adsafe && a.autocomplete !== 'off') {
+                    warning("ADsafe autocomplete violation.");
+                }
+                break;
+            default:
+                warning("Bad input type.");
+            }
+            break;
+        case 'applet':
+        case 'body':
+        case 'embed':
+        case 'frame':
+        case 'frameset':
+        case 'head':
+        case 'iframe':
+        case 'noembed':
+        case 'noframes':
+        case 'object':
+        case 'param':
+            if (option.adsafe) {
+                warning("ADsafe violation: Disallowed tag: " + n);
+            }
+            break;
+        }
+    }
+
+
+    function closetag(n) {
+        return '</' + n + '>';
+    }
+
+    function html() {
+        var a, attributes, e, n, q, t, v, w = option.white, wmode;
+        xmode = 'html';
+        xquote = '';
+        stack = null;
+        for (;;) {
+            switch (nexttoken.value) {
+            case '<':
+                xmode = 'html';
+                advance('<');
+                attributes = {};
+                t = nexttoken;
+                if (!t.identifier) {
+                    warning("Bad identifier {a}.", t, t.value);
+                }
+                n = t.value;
+                if (option.cap) {
+                    n = n.toLowerCase();
+                }
+                t.name = n;
+                advance();
+                if (!stack) {
+                    stack = [];
+                    doBegin(n);
+                }
+                v = htmltag[n];
+                if (typeof v !== 'object') {
+                    error("Unrecognized tag '<{a}>'.", t, n);
+                }
+                e = v.empty;
+                t.type = n;
+                for (;;) {
+                    if (nexttoken.id === '/') {
+                        advance('/');
+                        if (nexttoken.id !== '>') {
+                            warning("Expected '{a}' and instead saw '{b}'.",
+                                    nexttoken, '>', nexttoken.value);
+                        }
+                        break;
+                    }
+                    if (nexttoken.id && nexttoken.id.substr(0, 1) === '>') {
+                        break;
+                    }
+                    if (!nexttoken.identifier) {
+                        if (nexttoken.id === '(end)' || nexttoken.id === '(error)') {
+                            error("Missing '>'.", nexttoken);
+                        }
+                        warning("Bad identifier.");
+                    }
+                    option.white = true;
+                    nonadjacent(token, nexttoken);
+                    a = nexttoken.value;
+                    option.white = w;
+                    advance();
+                    if (!option.cap && a !== a.toLowerCase()) {
+                        warning("Attribute '{a}' not all lower case.", nexttoken, a);
+                    }
+                    a = a.toLowerCase();
+                    xquote = '';
+                    if (is_own(attributes, a)) {
+                        warning("Attribute '{a}' repeated.", nexttoken, a);
+                    }
+                    if (a.slice(0, 2) === 'on') {
+                        if (!option.on) {
+                            warning("Avoid HTML event handlers.");
+                        }
+                        xmode = 'scriptstring';
+                        advance('=');
+                        q = nexttoken.id;
+                        if (q !== '"' && q !== "'") {
+                            error("Missing quote.");
+                        }
+                        xquote = q;
+                        wmode = option.white;
+                        option.white = false;
+                        advance(q);
+                        statements('on');
+                        option.white = wmode;
+                        if (nexttoken.id !== q) {
+                            error("Missing close quote on script attribute.");
+                        }
+                        xmode = 'html';
+                        xquote = '';
+                        advance(q);
+                        v = false;
+                    } else if (a === 'style') {
+                        xmode = 'scriptstring';
+                        advance('=');
+                        q = nexttoken.id;
+                        if (q !== '"' && q !== "'") {
+                            error("Missing quote.");
+                        }
+                        xmode = 'styleproperty';
+                        xquote = q;
+                        advance(q);
+                        substyle();
+                        xmode = 'html';
+                        xquote = '';
+                        advance(q);
+                        v = false;
+                    } else {
+                        if (nexttoken.id === '=') {
+                            advance('=');
+                            v = nexttoken.value;
+                            if (!nexttoken.identifier &&
+                                    nexttoken.id !== '"' &&
+                                    nexttoken.id !== '\'' &&
+                                    nexttoken.type !== '(string)' &&
+                                    nexttoken.type !== '(number)' &&
+                                    nexttoken.type !== '(color)') {
+                                warning("Expected an attribute value and instead saw '{a}'.", token, a);
+                            }
+                            advance();
+                        } else {
+                            v = true;
+                        }
+                    }
+                    attributes[a] = v;
+                    doAttribute(n, a, v);
+                }
+                doTag(n, attributes);
+                if (!e) {
+                    stack.push(t);
+                }
+                xmode = 'outer';
+                advance('>');
+                break;
+            case '</':
+                xmode = 'html';
+                advance('</');
+                if (!nexttoken.identifier) {
+                    warning("Bad identifier.");
+                }
+                n = nexttoken.value;
+                if (option.cap) {
+                    n = n.toLowerCase();
+                }
+                advance();
+                if (!stack) {
+                    error("Unexpected '{a}'.", nexttoken, closetag(n));
+                }
+                t = stack.pop();
+                if (!t) {
+                    error("Unexpected '{a}'.", nexttoken, closetag(n));
+                }
+                if (t.name !== n) {
+                    error("Expected '{a}' and instead saw '{b}'.",
+                            nexttoken, closetag(t.name), closetag(n));
+                }
+                if (nexttoken.id !== '>') {
+                    error("Missing '{a}'.", nexttoken, '>');
+                }
+                xmode = 'outer';
+                advance('>');
+                break;
+            case '<!':
+                if (option.safe) {
+                    warning("ADsafe HTML violation.");
+                }
+                xmode = 'html';
+                for (;;) {
+                    advance();
+                    if (nexttoken.id === '>' || nexttoken.id === '(end)') {
+                        break;
+                    }
+                    if (nexttoken.value.indexOf('--') >= 0) {
+                        warning("Unexpected --.");
+                    }
+                    if (nexttoken.value.indexOf('<') >= 0) {
+                        warning("Unexpected <.");
+                    }
+                    if (nexttoken.value.indexOf('>') >= 0) {
+                        warning("Unexpected >.");
+                    }
+                }
+                xmode = 'outer';
+                advance('>');
+                break;
+            case '(end)':
+                return;
+            default:
+                if (nexttoken.id === '(end)') {
+                    error("Missing '{a}'.", nexttoken,
+                            '</' + stack[stack.length - 1].value + '>');
+                } else {
+                    advance();
+                }
+            }
+            if (stack && stack.length === 0 && (option.adsafe ||
+                    !option.fragment || nexttoken.id === '(end)')) {
+                break;
+            }
+        }
+        if (nexttoken.id !== '(end)') {
+            error("Unexpected material after the end.");
+        }
+    }
+
+
+// Build the syntax table by declaring the syntactic elements of the language.
+
+    type('(number)', idValue);
+    type('(string)', idValue);
+
+    syntax['(identifier)'] = {
+        type: '(identifier)',
+        lbp: 0,
+        identifier: true,
+        nud: function () {
+            var v = this.value,
+                s = scope[v],
+                f;
+            if (typeof s === 'function') {
+                s = undefined;
+            } else if (typeof s === 'boolean') {
+                f = funct;
+                funct = functions[0];
+                addlabel(v, 'var');
+                s = funct;
+                funct = f;
+            }
+
+// The name is in scope and defined in the current function.
+
+            if (funct === s) {
+
+//      Change 'unused' to 'var', and reject labels.
+
+                switch (funct[v]) {
+                case 'unused':
+                    funct[v] = 'var';
+                    break;
+                case 'label':
+                    warning("'{a}' is a statement label.", token, v);
+                    break;
+                }
+
+// The name is not defined in the function.  If we are in the global scope,
+// then we have an undefined variable.
+
+            } else if (funct['(global)']) {
+                if (option.undef && predefined[v] !== 'boolean') {
+                    warning("'{a}' is not defined.", token, v);
+                }
+                note_implied(token);
+
+// If the name is already defined in the current
+// function, but not as outer, then there is a scope error.
+
+            } else {
+                switch (funct[v]) {
+                case 'closure':
+                case 'function':
+                case 'var':
+                case 'unused':
+                    warning("'{a}' used out of scope.", token, v);
+                    break;
+                case 'label':
+                    warning("'{a}' is a statement label.", token, v);
+                    break;
+                case 'outer':
+                case 'global':
+                    break;
+                default:
+
+// If the name is defined in an outer function, make an outer entry, and if
+// it was unused, make it var.
+
+                    if (s === true) {
+                        funct[v] = true;
+                    } else if (s === null) {
+                        warning("'{a}' is not allowed.", token, v);
+                        note_implied(token);
+                    } else if (typeof s !== 'object') {
+                        if (option.undef) {
+                            warning("'{a}' is not defined.", token, v);
+                        } else {
+                            funct[v] = true;
+                        }
+                        note_implied(token);
+                    } else {
+                        switch (s[v]) {
+                        case 'function':
+                        case 'var':
+                        case 'unused':
+                            s[v] = 'closure';
+                            funct[v] = s['(global)'] ? 'global' : 'outer';
+                            break;
+                        case 'closure':
+                        case 'parameter':
+                            funct[v] = s['(global)'] ? 'global' : 'outer';
+                            break;
+                        case 'label':
+                            warning("'{a}' is a statement label.", token, v);
+                        }
+                    }
+                }
+            }
+            return this;
+        },
+        led: function () {
+            error("Expected an operator and instead saw '{a}'.",
+                    nexttoken, nexttoken.value);
+        }
+    };
+
+    type('(regexp)', function () {
+        return this;
+    });
+
+    delim('(endline)');
+    delim('(begin)');
+    delim('(end)').reach = true;
+    delim('</').reach = true;
+    delim('<!');
+    delim('<!--');
+    delim('-->');
+    delim('(error)').reach = true;
+    delim('}').reach = true;
+    delim(')');
+    delim(']');
+    delim('"').reach = true;
+    delim("'").reach = true;
+    delim(';');
+    delim(':').reach = true;
+    delim(',');
+    delim('#');
+    delim('@');
+    reserve('else');
+    reserve('case').reach = true;
+    reserve('catch');
+    reserve('default').reach = true;
+    reserve('finally');
+    reservevar('arguments');
+    reservevar('eval');
+    reservevar('false');
+    reservevar('Infinity');
+    reservevar('NaN');
+    reservevar('null');
+    reservevar('this');
+    reservevar('true');
+    reservevar('undefined');
+    assignop('=', 'assign', 20);
+    assignop('+=', 'assignadd', 20);
+    assignop('-=', 'assignsub', 20);
+    assignop('*=', 'assignmult', 20);
+    assignop('/=', 'assigndiv', 20).nud = function () {
+        error("A regular expression literal can be confused with '/='.");
+    };
+    assignop('%=', 'assignmod', 20);
+    bitwiseassignop('&=', 'assignbitand', 20);
+    bitwiseassignop('|=', 'assignbitor', 20);
+    bitwiseassignop('^=', 'assignbitxor', 20);
+    bitwiseassignop('<<=', 'assignshiftleft', 20);
+    bitwiseassignop('>>=', 'assignshiftright', 20);
+    bitwiseassignop('>>>=', 'assignshiftrightunsigned', 20);
+    infix('?', function (left, that) {
+        that.left = left;
+        that.right = parse(10);
+        advance(':');
+        that['else'] = parse(10);
+        return that;
+    }, 30);
+
+    infix('||', 'or', 40);
+    infix('&&', 'and', 50);
+    bitwise('|', 'bitor', 70);
+    bitwise('^', 'bitxor', 80);
+    bitwise('&', 'bitand', 90);
+    relation('==', function (left, right) {
+        if (option.eqeqeq) {
+            warning("Expected '{a}' and instead saw '{b}'.",
+                    this, '===', '==');
+        } else if (isPoorRelation(left)) {
+            warning("Use '{a}' to compare with '{b}'.",
+                this, '===', left.value);
+        } else if (isPoorRelation(right)) {
+            warning("Use '{a}' to compare with '{b}'.",
+                this, '===', right.value);
+        }
+        return this;
+    });
+    relation('===');
+    relation('!=', function (left, right) {
+        if (option.eqeqeq) {
+            warning("Expected '{a}' and instead saw '{b}'.",
+                    this, '!==', '!=');
+        } else if (isPoorRelation(left)) {
+            warning("Use '{a}' to compare with '{b}'.",
+                    this, '!==', left.value);
+        } else if (isPoorRelation(right)) {
+            warning("Use '{a}' to compare with '{b}'.",
+                    this, '!==', right.value);
+        }
+        return this;
+    });
+    relation('!==');
+    relation('<');
+    relation('>');
+    relation('<=');
+    relation('>=');
+    bitwise('<<', 'shiftleft', 120);
+    bitwise('>>', 'shiftright', 120);
+    bitwise('>>>', 'shiftrightunsigned', 120);
+    infix('in', 'in', 120);
+    infix('instanceof', 'instanceof', 120);
+    infix('+', function (left, that) {
+        var right = parse(130);
+        if (left && right && left.id === '(string)' && right.id === '(string)') {
+            left.value += right.value;
+            left.character = right.character;
+            if (jx.test(left.value)) {
+                warning("JavaScript URL.", left);
+            }
+            return left;
+        }
+        that.left = left;
+        that.right = right;
+        return that;
+    }, 130);
+    prefix('+', 'num');
+    infix('-', 'sub', 130);
+    prefix('-', 'neg');
+    infix('*', 'mult', 140);
+    infix('/', 'div', 140);
+    infix('%', 'mod', 140);
+
+    suffix('++', 'postinc');
+    prefix('++', 'preinc');
+    syntax['++'].exps = true;
+
+    suffix('--', 'postdec');
+    prefix('--', 'predec');
+    syntax['--'].exps = true;
+    prefix('delete', function () {
+        var p = parse(0);
+        if (!p || (p.id !== '.' && p.id !== '[')) {
+            warning("Expected '{a}' and instead saw '{b}'.",
+                    nexttoken, '.', nexttoken.value);
+        }
+        this.first = p;
+        return this;
+    }).exps = true;
+
+
+    prefix('~', function () {
+        if (option.bitwise) {
+            warning("Unexpected '{a}'.", this, '~');
+        }
+        parse(150);
+        return this;
+    });
+    prefix('!', function () {
+        this.right = parse(150);
+        this.arity = 'unary';
+        if (bang[this.right.id] === true) {
+            warning("Confusing use of '{a}'.", this, '!');
+        }
+        return this;
+    });
+    prefix('typeof', 'typeof');
+    prefix('new', function () {
+        var c = parse(155), i;
+        if (c && c.id !== 'function') {
+            if (c.identifier) {
+                c['new'] = true;
+                switch (c.value) {
+                case 'Object':
+                    warning("Use the object literal notation {}.", token);
+                    break;
+                case 'Array':
+                    if (nexttoken.id !== '(') {
+                        warning("Use the array literal notation [].", token);
+                    } else {
+                        advance('(');
+                        if (nexttoken.id === ')') {
+                            warning("Use the array literal notation [].", token);
+                        } else {
+                            i = parse(0);
+                            c.dimension = i;
+                            if ((i.id === '(number)' && /[.+\-Ee]/.test(i.value)) ||
+                                    (i.id === '-' && !i.right) ||
+                                    i.id === '(string)' || i.id === '[' ||
+                                    i.id === '{' || i.id === 'true' ||
+                                    i.id === 'false' ||
+                                    i.id === 'null' || i.id === 'undefined' ||
+                                    i.id === 'Infinity') {
+                                warning("Use the array literal notation [].", token);
+                            }
+                            if (nexttoken.id !== ')') {
+                                error("Use the array literal notation [].", token);
+                            }
+                        }
+                        advance(')');
+                    }
+                    this.first = c;
+                    return this;
+                case 'Number':
+                case 'String':
+                case 'Boolean':
+                case 'Math':
+                case 'JSON':
+                    warning("Do not use {a} as a constructor.", token, c.value);
+                    break;
+                case 'Function':
+                    if (!option.evil) {
+                        warning("The Function constructor is eval.");
+                    }
+                    break;
+                case 'Date':
+                case 'RegExp':
+                    break;
+                default:
+                    if (c.id !== 'function') {
+                        i = c.value.substr(0, 1);
+                        if (option.newcap && (i < 'A' || i > 'Z')) {
+                            warning(
+                    "A constructor name should start with an uppercase letter.",
+                                token);
+                        }
+                    }
+                }
+            } else {
+                if (c.id !== '.' && c.id !== '[' && c.id !== '(') {
+                    warning("Bad constructor.", token);
+                }
+            }
+        } else {
+            warning("Weird construction. Delete 'new'.", this);
+        }
+        adjacent(token, nexttoken);
+        if (nexttoken.id !== '(') {
+            warning("Missing '()' invoking a constructor.");
+        }
+        this.first = c;
+        return this;
+    });
+    syntax['new'].exps = true;
+
+    infix('.', function (left, that) {
+        adjacent(prevtoken, token);
+        var m = identifier();
+        if (typeof m === 'string') {
+            countMember(m);
+        }
+        that.left = left;
+        that.right = m;
+        if (!option.evil && left && left.value === 'document' &&
+                (m === 'write' || m === 'writeln')) {
+            warning("document.write can be a form of eval.", left);
+        } else if (option.adsafe) {
+            if (left && left.value === 'ADSAFE') {
+                if (m === 'id' || m === 'lib') {
+                    warning("ADsafe violation.", that);
+                } else if (m === 'go') {
+                    if (xmode !== 'script') {
+                        warning("ADsafe violation.", that);
+                    } else if (adsafe_went || nexttoken.id !== '(' ||
+                            peek(0).id !== '(string)' ||
+                            peek(0).value !== adsafe_id ||
+                            peek(1).id !== ',') {
+                        error("ADsafe violation: go.", that);
+                    }
+                    adsafe_went = true;
+                    adsafe_may = false;
+                }
+            }
+        }
+        if (!option.evil && (m === 'eval' || m === 'execScript')) {
+            warning('eval is evil.');
+        } else if (option.safe) {
+            for (;;) {
+                if (banned[m] === true) {
+                    warning("ADsafe restricted word '{a}'.", token, m);
+                }
+                if (typeof predefined[left.value] !== 'boolean' ||
+                        nexttoken.id === '(') {
+                    break;
+                }
+                if (standard_member[m] === true) {
+                    if (nexttoken.id === '.') {
+                        warning("ADsafe violation.", that);
+                    }
+                    break;
+                }
+                if (nexttoken.id !== '.') {
+                    warning("ADsafe violation.", that);
+                    break;
+                }
+                advance('.');
+                token.left = that;
+                token.right = m;
+                that = token;
+                m = identifier();
+                if (typeof m === 'string') {
+                    countMember(m);
+                }
+            }
+        }
+        return that;
+    }, 160, true);
+
+    infix('(', function (left, that) {
+        adjacent(prevtoken, token);
+        nospace();
+        var n = 0,
+            p = [];
+        if (left) {
+            if (left.type === '(identifier)') {
+                if (left.value.match(/^[A-Z]([A-Z0-9_$]*[a-z][A-Za-z0-9_$]*)?$/)) {
+                    if (left.value !== 'Number' && left.value !== 'String' &&
+                            left.value !== 'Boolean' &&
+                            left.value !== 'Date') {
+                        if (left.value === 'Math') {
+                            warning("Math is not a function.", left);
+                        } else if (option.newcap) {
+                            warning(
+"Missing 'new' prefix when invoking a constructor.", left);
+                        }
+                    }
+                }
+            } else if (left.id === '.') {
+                if (option.safe && left.left.value === 'Math' &&
+                        left.right === 'random') {
+                    warning("ADsafe violation.", left);
+                }
+            }
+        }
+        if (nexttoken.id !== ')') {
+            for (;;) {
+                p[p.length] = parse(10);
+                n += 1;
+                if (nexttoken.id !== ',') {
+                    break;
+                }
+                comma();
+            }
+        }
+        advance(')');
+        if (option.immed && left.id === 'function' && nexttoken.id !== ')') {
+            warning("Wrap the entire immediate function invocation in parens.",
+                that);
+        }
+        nospace(prevtoken, token);
+        if (typeof left === 'object') {
+            if (left.value === 'parseInt' && n === 1) {
+                warning("Missing radix parameter.", left);
+            }
+            if (!option.evil) {
+                if (left.value === 'eval' || left.value === 'Function' ||
+                        left.value === 'execScript') {
+                    warning("eval is evil.", left);
+                } else if (p[0] && p[0].id === '(string)' &&
+                       (left.value === 'setTimeout' ||
+                        left.value === 'setInterval')) {
+                    warning(
+    "Implied eval is evil. Pass a function instead of a string.", left);
+                }
+            }
+            if (!left.identifier && left.id !== '.' && left.id !== '[' &&
+                    left.id !== '(' && left.id !== '&&' && left.id !== '||' &&
+                    left.id !== '?') {
+                warning("Bad invocation.", left);
+            }
+        }
+        that.left = left;
+        return that;
+    }, 155, true).exps = true;
+
+    prefix('(', function () {
+        nospace();
+        var v = parse(0);
+        advance(')', this);
+        nospace(prevtoken, token);
+        if (option.immed && v.id === 'function') {
+            if (nexttoken.id === '(') {
+                warning(
+"Move the invocation into the parens that contain the function.", nexttoken);
+            } else {
+                warning(
+"Do not wrap function literals in parens unless they are to be immediately invoked.",
+                        this);
+            }
+        }
+        return v;
+    });
+
+    infix('[', function (left, that) {
+        nospace();
+        var e = parse(0), s;
+        if (e && e.type === '(string)') {
+            if (option.safe && banned[e.value] === true) {
+                warning("ADsafe restricted word '{a}'.", that, e.value);
+            } else if (!option.evil &&
+                    (e.value === 'eval' || e.value === 'execScript')) {
+                warning("eval is evil.", that);
+            } else if (option.safe &&
+                    (e.value.charAt(0) === '_' || e.value.charAt(0) === '-')) {
+                warning("ADsafe restricted subscript '{a}'.", that, e.value);
+            }
+            countMember(e.value);
+            if (!option.sub && ix.test(e.value)) {
+                s = syntax[e.value];
+                if (!s || !s.reserved) {
+                    warning("['{a}'] is better written in dot notation.",
+                            e, e.value);
+                }
+            }
+        } else if (!e || e.type !== '(number)' || e.value < 0) {
+            if (option.safe) {
+                warning('ADsafe subscripting.');
+            }
+        }
+        advance(']', that);
+        nospace(prevtoken, token);
+        that.left = left;
+        that.right = e;
+        return that;
+    }, 160, true);
+
+    prefix('[', function () {
+        var b = token.line !== nexttoken.line;
+        this.first = [];
+        if (b) {
+            indent += option.indent;
+            if (nexttoken.from === indent + option.indent) {
+                indent += option.indent;
+            }
+        }
+        while (nexttoken.id !== '(end)') {
+            while (nexttoken.id === ',') {
+                warning("Extra comma.");
+                advance(',');
+            }
+            if (nexttoken.id === ']') {
+                break;
+            }
+            if (b && token.line !== nexttoken.line) {
+                indentation();
+            }
+            this.first.push(parse(10));
+            if (nexttoken.id === ',') {
+                comma();
+                if (nexttoken.id === ']') {
+                    warning("Extra comma.", token);
+                    break;
+                }
+            } else {
+                break;
+            }
+        }
+        if (b) {
+            indent -= option.indent;
+            indentation();
+        }
+        advance(']', this);
+        return this;
+    }, 160);
+
+    (function (x) {
+        x.nud = function () {
+            var b, i, s, seen = {};
+            b = token.line !== nexttoken.line;
+            if (b) {
+                indent += option.indent;
+                if (nexttoken.from === indent + option.indent) {
+                    indent += option.indent;
+                }
+            }
+            for (;;) {
+                if (nexttoken.id === '}') {
+                    break;
+                }
+                if (b) {
+                    indentation();
+                }
+                i = optionalidentifier(true);
+                if (!i) {
+                    if (nexttoken.id === '(string)') {
+                        i = nexttoken.value;
+                        if (ix.test(i)) {
+                            s = syntax[i];
+                        }
+                        advance();
+                    } else if (nexttoken.id === '(number)') {
+                        i = nexttoken.value.toString();
+                        advance();
+                    } else {
+                        error("Expected '{a}' and instead saw '{b}'.",
+                                nexttoken, '}', nexttoken.value);
+                    }
+                }
+                if (seen[i] === true) {
+                    warning("Duplicate member '{a}'.", nexttoken, i);
+                }
+                seen[i] = true;
+                countMember(i);
+                advance(':');
+                nonadjacent(token, nexttoken);
+                parse(10);
+                if (nexttoken.id === ',') {
+                    comma();
+                    if (nexttoken.id === ',' || nexttoken.id === '}') {
+                        warning("Extra comma.", token);
+                    }
+                } else {
+                    break;
+                }
+            }
+            if (b) {
+                indent -= option.indent;
+                indentation();
+            }
+            advance('}', this);
+            return this;
+        };
+        x.fud = function () {
+            error("Expected to see a statement and instead saw a block.", token);
+        };
+    }(delim('{')));
+
+
+    function varstatement(prefix) {
+
+// JavaScript does not have block scope. It only has function scope. So,
+// declaring a variable in a block can have unexpected consequences.
+
+        var id, name, value;
+
+        if (funct['(onevar)'] && option.onevar) {
+            warning("Too many var statements.");
+        } else if (!funct['(global)']) {
+            funct['(onevar)'] = true;
+        }
+        this.first = [];
+        for (;;) {
+            nonadjacent(token, nexttoken);
+            id = identifier();
+            if (funct['(global)'] && predefined[id] === false) {
+                warning("Redefinition of '{a}'.", token, id);
+            }
+            addlabel(id, 'unused');
+            if (prefix) {
+                break;
+            }
+            name = token;
+            this.first.push(token);
+            if (nexttoken.id === '=') {
+                nonadjacent(token, nexttoken);
+                advance('=');
+                nonadjacent(token, nexttoken);
+                if (nexttoken.id === 'undefined') {
+                    warning("It is not necessary to initialize '{a}' to 'undefined'.", token, id);
+                }
+                if (peek(0).id === '=' && nexttoken.identifier) {
+                    error("Variable {a} was not declared correctly.",
+                            nexttoken, nexttoken.value);
+                }
+                value = parse(0);
+                name.first = value;
+            }
+            if (nexttoken.id !== ',') {
+                break;
+            }
+            comma();
+        }
+        return this;
+    }
+
+
+    stmt('var', varstatement).exps = true;
+
+
+    function functionparams() {
+        var i, t = nexttoken, p = [];
+        advance('(');
+        nospace();
+        if (nexttoken.id === ')') {
+            advance(')');
+            nospace(prevtoken, token);
+            return;
+        }
+        for (;;) {
+            i = identifier();
+            p.push(i);
+            addlabel(i, 'parameter');
+            if (nexttoken.id === ',') {
+                comma();
+            } else {
+                advance(')', t);
+                nospace(prevtoken, token);
+                return p;
+            }
+        }
+    }
+
+    function doFunction(i) {
+        var s = scope;
+        scope = Object.create(s);
+        funct = {
+            '(name)'    : i || '"' + anonname + '"',
+            '(line)'    : nexttoken.line,
+            '(context)' : funct,
+            '(breakage)': 0,
+            '(loopage)' : 0,
+            '(scope)'   : scope
+        };
+        token.funct = funct;
+        functions.push(funct);
+        if (i) {
+            addlabel(i, 'function');
+        }
+        funct['(params)'] = functionparams();
+
+        block(false);
+        scope = s;
+        funct['(last)'] = token.line;
+        funct = funct['(context)'];
+    }
+
+
+    blockstmt('function', function () {
+        if (inblock) {
+            warning(
+"Function statements cannot be placed in blocks. Use a function expression or move the statement to the top of the outer function.", token);
+
+        }
+        var i = identifier();
+        adjacent(token, nexttoken);
+        addlabel(i, 'unused');
+        doFunction(i);
+        if (nexttoken.id === '(' && nexttoken.line === token.line) {
+            error(
+"Function statements are not invocable. Wrap the whole function invocation in parens.");
+        }
+        return this;
+    });
+
+    prefix('function', function () {
+        var i = optionalidentifier();
+        if (i) {
+            adjacent(token, nexttoken);
+        } else {
+            nonadjacent(token, nexttoken);
+        }
+        doFunction(i);
+        if (funct['(loopage)']) {
+            warning("Don't make functions within a loop.");
+        }
+        return this;
+    });
+
+    blockstmt('if', function () {
+        var t = nexttoken;
+        advance('(');
+        nonadjacent(this, t);
+        nospace();
+        parse(20);
+        if (nexttoken.id === '=') {
+            warning("Expected a conditional expression and instead saw an assignment.");
+            advance('=');
+            parse(20);
+        }
+        advance(')', t);
+        nospace(prevtoken, token);
+        block(true);
+        if (nexttoken.id === 'else') {
+            nonadjacent(token, nexttoken);
+            advance('else');
+            if (nexttoken.id === 'if' || nexttoken.id === 'switch') {
+                statement(true);
+            } else {
+                block(true);
+            }
+        }
+        return this;
+    });
+
+    blockstmt('try', function () {
+        var b, e, s;
+        if (option.adsafe) {
+            warning("ADsafe try violation.", this);
+        }
+        block(false);
+        if (nexttoken.id === 'catch') {
+            advance('catch');
+            nonadjacent(token, nexttoken);
+            advance('(');
+            s = scope;
+            scope = Object.create(s);
+            e = nexttoken.value;
+            if (nexttoken.type !== '(identifier)') {
+                warning("Expected an identifier and instead saw '{a}'.",
+                    nexttoken, e);
+            } else {
+                addlabel(e, 'exception');
+            }
+            advance();
+            advance(')');
+            block(false);
+            b = true;
+            scope = s;
+        }
+        if (nexttoken.id === 'finally') {
+            advance('finally');
+            block(false);
+            return;
+        } else if (!b) {
+            error("Expected '{a}' and instead saw '{b}'.",
+                    nexttoken, 'catch', nexttoken.value);
+        }
+        return this;
+    });
+
+    blockstmt('while', function () {
+        var t = nexttoken;
+        funct['(breakage)'] += 1;
+        funct['(loopage)'] += 1;
+        advance('(');
+        nonadjacent(this, t);
+        nospace();
+        parse(20);
+        if (nexttoken.id === '=') {
+            warning("Expected a conditional expression and instead saw an assignment.");
+            advance('=');
+            parse(20);
+        }
+        advance(')', t);
+        nospace(prevtoken, token);
+        block(true);
+        funct['(breakage)'] -= 1;
+        funct['(loopage)'] -= 1;
+        return this;
+    }).labelled = true;
+
+    reserve('with');
+
+    blockstmt('switch', function () {
+        var t = nexttoken,
+            g = false;
+        funct['(breakage)'] += 1;
+        advance('(');
+        nonadjacent(this, t);
+        nospace();
+        this.condition = parse(20);
+        advance(')', t);
+        nospace(prevtoken, token);
+        nonadjacent(token, nexttoken);
+        t = nexttoken;
+        advance('{');
+        nonadjacent(token, nexttoken);
+        indent += option.indent;
+        this.cases = [];
+        for (;;) {
+            switch (nexttoken.id) {
+            case 'case':
+                switch (funct['(verb)']) {
+                case 'break':
+                case 'case':
+                case 'continue':
+                case 'return':
+                case 'switch':
+                case 'throw':
+                    break;
+                default:
+                    warning(
+                        "Expected a 'break' statement before 'case'.",
+                        token);
+                }
+                indentation(-option.indent);
+                advance('case');
+                this.cases.push(parse(20));
+                g = true;
+                advance(':');
+                funct['(verb)'] = 'case';
+                break;
+            case 'default':
+                switch (funct['(verb)']) {
+                case 'break':
+                case 'continue':
+                case 'return':
+                case 'throw':
+                    break;
+                default:
+                    warning(
+                        "Expected a 'break' statement before 'default'.",
+                        token);
+                }
+                indentation(-option.indent);
+                advance('default');
+                g = true;
+                advance(':');
+                break;
+            case '}':
+                indent -= option.indent;
+                indentation();
+                advance('}', t);
+                if (this.cases.length === 1 || this.condition.id === 'true' ||
+                        this.condition.id === 'false') {
+                    warning("This 'switch' should be an 'if'.", this);
+                }
+                funct['(breakage)'] -= 1;
+                funct['(verb)'] = undefined;
+                return;
+            case '(end)':
+                error("Missing '{a}'.", nexttoken, '}');
+                return;
+            default:
+                if (g) {
+                    switch (token.id) {
+                    case ',':
+                        error("Each value should have its own case label.");
+                        return;
+                    case ':':
+                        statements();
+                        break;
+                    default:
+                        error("Missing ':' on a case clause.", token);
+                    }
+                } else {
+                    error("Expected '{a}' and instead saw '{b}'.",
+                        nexttoken, 'case', nexttoken.value);
+                }
+            }
+        }
+    }).labelled = true;
+
+    stmt('debugger', function () {
+        if (!option.debug) {
+            warning("All 'debugger' statements should be removed.");
+        }
+        return this;
+    }).exps = true;
+
+    (function () {
+        var x = stmt('do', function () {
+            funct['(breakage)'] += 1;
+            funct['(loopage)'] += 1;
+            this.first = block(true);
+            advance('while');
+            var t = nexttoken;
+            nonadjacent(token, t);
+            advance('(');
+            nospace();
+            parse(20);
+            if (nexttoken.id === '=') {
+                warning("Expected a conditional expression and instead saw an assignment.");
+                advance('=');
+                parse(20);
+            }
+            advance(')', t);
+            nospace(prevtoken, token);
+            funct['(breakage)'] -= 1;
+            funct['(loopage)'] -= 1;
+            return this;
+        });
+        x.labelled = true;
+        x.exps = true;
+    }());
+
+    blockstmt('for', function () {
+        var f = option.forin, s, t = nexttoken;
+        funct['(breakage)'] += 1;
+        funct['(loopage)'] += 1;
+        advance('(');
+        nonadjacent(this, t);
+        nospace();
+        if (peek(nexttoken.id === 'var' ? 1 : 0).id === 'in') {
+            if (nexttoken.id === 'var') {
+                advance('var');
+                varstatement(true);
+            } else {
+                switch (funct[nexttoken.value]) {
+                case 'unused':
+                    funct[nexttoken.value] = 'var';
+                    break;
+                case 'var':
+                    break;
+                default:
+                    warning("Bad for in variable '{a}'.",
+                            nexttoken, nexttoken.value);
+                }
+                advance();
+            }
+            advance('in');
+            parse(20);
+            advance(')', t);
+            s = block(true);
+            if (!f && (s.length > 1 || typeof s[0] !== 'object' ||
+                    s[0].value !== 'if')) {
+                warning("The body of a for in should be wrapped in an if statement to filter unwanted properties from the prototype.", this);
+            }
+            funct['(breakage)'] -= 1;
+            funct['(loopage)'] -= 1;
+            return this;
+        } else {
+            if (nexttoken.id !== ';') {
+                if (nexttoken.id === 'var') {
+                    advance('var');
+                    varstatement();
+                } else {
+                    for (;;) {
+                        parse(0, 'for');
+                        if (nexttoken.id !== ',') {
+                            break;
+                        }
+                        comma();
+                    }
+                }
+            }
+            nolinebreak(token);
+            advance(';');
+            if (nexttoken.id !== ';') {
+                parse(20);
+                if (nexttoken.id === '=') {
+                    warning("Expected a conditional expression and instead saw an assignment.");
+                    advance('=');
+                    parse(20);
+                }
+            }
+            nolinebreak(token);
+            advance(';');
+            if (nexttoken.id === ';') {
+                error("Expected '{a}' and instead saw '{b}'.",
+                        nexttoken, ')', ';');
+            }
+            if (nexttoken.id !== ')') {
+                for (;;) {
+                    parse(0, 'for');
+                    if (nexttoken.id !== ',') {
+                        break;
+                    }
+                    comma();
+                }
+            }
+            advance(')', t);
+            nospace(prevtoken, token);
+            block(true);
+            funct['(breakage)'] -= 1;
+            funct['(loopage)'] -= 1;
+            return this;
+        }
+    }).labelled = true;
+
+
+    stmt('break', function () {
+        var v = nexttoken.value;
+        if (funct['(breakage)'] === 0) {
+            warning("Unexpected '{a}'.", nexttoken, this.value);
+        }
+        nolinebreak(this);
+        if (nexttoken.id !== ';') {
+            if (token.line === nexttoken.line) {
+                if (funct[v] !== 'label') {
+                    warning("'{a}' is not a statement label.", nexttoken, v);
+                } else if (scope[v] !== funct) {
+                    warning("'{a}' is out of scope.", nexttoken, v);
+                }
+                this.first = nexttoken;
+                advance();
+            }
+        }
+        reachable('break');
+        return this;
+    }).exps = true;
+
+
+    stmt('continue', function () {
+        var v = nexttoken.value;
+        if (funct['(breakage)'] === 0) {
+            warning("Unexpected '{a}'.", nexttoken, this.value);
+        }
+        nolinebreak(this);
+        if (nexttoken.id !== ';') {
+            if (token.line === nexttoken.line) {
+                if (funct[v] !== 'label') {
+                    warning("'{a}' is not a statement label.", nexttoken, v);
+                } else if (scope[v] !== funct) {
+                    warning("'{a}' is out of scope.", nexttoken, v);
+                }
+                this.first = nexttoken;
+                advance();
+            }
+        } else if (!funct['(loopage)']) {
+            warning("Unexpected '{a}'.", nexttoken, this.value);
+        }
+        reachable('continue');
+        return this;
+    }).exps = true;
+
+
+    stmt('return', function () {
+        nolinebreak(this);
+        if (nexttoken.id === '(regexp)') {
+            warning("Wrap the /regexp/ literal in parens to disambiguate the slash operator.");
+        }
+        if (nexttoken.id !== ';' && !nexttoken.reach) {
+            nonadjacent(token, nexttoken);
+            this.first = parse(20);
+        }
+        reachable('return');
+        return this;
+    }).exps = true;
+
+
+    stmt('throw', function () {
+        nolinebreak(this);
+        nonadjacent(token, nexttoken);
+        this.first = parse(20);
+        reachable('throw');
+        return this;
+    }).exps = true;
+
+    reserve('void');
+
+//  Superfluous reserved words
+
+    reserve('class');
+    reserve('const');
+    reserve('enum');
+    reserve('export');
+    reserve('extends');
+    reserve('import');
+    reserve('super');
+
+    reserve('let');
+    reserve('yield');
+    reserve('implements');
+    reserve('interface');
+    reserve('package');
+    reserve('private');
+    reserve('protected');
+    reserve('public');
+    reserve('static');
+
+    function jsonValue() {
+
+        function jsonObject() {
+            var o = {}, t = nexttoken;
+            advance('{');
+            if (nexttoken.id !== '}') {
+                for (;;) {
+                    if (nexttoken.id === '(end)') {
+                        error("Missing '}' to match '{' from line {a}.",
+                                nexttoken, t.line);
+                    } else if (nexttoken.id === '}') {
+                        warning("Unexpected comma.", token);
+                        break;
+                    } else if (nexttoken.id === ',') {
+                        error("Unexpected comma.", nexttoken);
+                    } else if (nexttoken.id !== '(string)') {
+                        warning("Expected a string and instead saw {a}.",
+                                nexttoken, nexttoken.value);
+                    }
+                    if (o[nexttoken.value] === true) {
+                        warning("Duplicate key '{a}'.",
+                                nexttoken, nexttoken.value);
+                    } else if (nexttoken.value === '__proto__') {
+                        warning("Stupid key '{a}'.",
+                                nexttoken, nexttoken.value);
+                    } else {
+                        o[nexttoken.value] = true;
+                    }
+                    advance();
+                    advance(':');
+                    jsonValue();
+                    if (nexttoken.id !== ',') {
+                        break;
+                    }
+                    advance(',');
+                }
+            }
+            advance('}');
+        }
+
+        function jsonArray() {
+            var t = nexttoken;
+            advance('[');
+            if (nexttoken.id !== ']') {
+                for (;;) {
+                    if (nexttoken.id === '(end)') {
+                        error("Missing ']' to match '[' from line {a}.",
+                                nexttoken, t.line);
+                    } else if (nexttoken.id === ']') {
+                        warning("Unexpected comma.", token);
+                        break;
+                    } else if (nexttoken.id === ',') {
+                        error("Unexpected comma.", nexttoken);
+                    }
+                    jsonValue();
+                    if (nexttoken.id !== ',') {
+                        break;
+                    }
+                    advance(',');
+                }
+            }
+            advance(']');
+        }
+
+        switch (nexttoken.id) {
+        case '{':
+            jsonObject();
+            break;
+        case '[':
+            jsonArray();
+            break;
+        case 'true':
+        case 'false':
+        case 'null':
+        case '(number)':
+        case '(string)':
+            advance();
+            break;
+        case '-':
+            advance('-');
+            if (token.character !== nexttoken.from) {
+                warning("Unexpected space after '-'.", token);
+            }
+            adjacent(token, nexttoken);
+            advance('(number)');
+            break;
+        default:
+            error("Expected a JSON value.", nexttoken);
+        }
+    }
+
+
+// The actual JSLINT function itself.
+
+    var itself = function (s, o) {
+        var a, i;
+        JSLINT.errors = [];
+        predefined = Object.create(standard);
+        if (o) {
+            a = o.predef;
+            if (a instanceof Array) {
+                for (i = 0; i < a.length; i += 1) {
+                    predefined[a[i]] = true;
+                }
+            }
+            if (o.adsafe) {
+                o.safe = true;
+            }
+            if (o.safe) {
+                o.browser = false;
+                o.css     = false;
+                o.debug   = false;
+                o.devel   = false;
+                o.eqeqeq  = true;
+                o.evil    = false;
+                o.forin   = false;
+                o.nomen   = true;
+                o.on      = false;
+                o.rhino   = false;
+                o.safe    = true;
+                o.sidebar = false;
+                o.strict  = true;
+                o.sub     = false;
+                o.undef   = true;
+                o.widget  = false;
+                predefined.Date = null;
+                predefined['eval'] = null;
+                predefined.Function = null;
+                predefined.Object = null;
+                predefined.ADSAFE = false;
+                predefined.lib = false;
+            }
+            option = o;
+        } else {
+            option = {};
+        }
+        option.indent = option.indent || 4;
+        option.maxerr = option.maxerr || 50;
+        adsafe_id = '';
+        adsafe_may = false;
+        adsafe_went = false;
+        approved = {};
+        if (option.approved) {
+            for (i = 0; i < option.approved.length; i += 1) {
+                approved[option.approved[i]] = option.approved[i];
+            }
+        } else {
+            approved.test = 'test';
+        }
+        tab = '';
+        for (i = 0; i < option.indent; i += 1) {
+            tab += ' ';
+        }
+        indent = 1;
+        global = Object.create(predefined);
+        scope = global;
+        funct = {
+            '(global)': true,
+            '(name)': '(global)',
+            '(scope)': scope,
+            '(breakage)': 0,
+            '(loopage)': 0
+        };
+        functions = [funct];
+        ids = {};
+        urls = [];
+        src = false;
+        xmode = false;
+        stack = null;
+        member = {};
+        membersOnly = null;
+        implied = {};
+        inblock = false;
+        lookahead = [];
+        jsonmode = false;
+        warnings = 0;
+        lex.init(s);
+        prereg = true;
+        strict_mode = false;
+
+        prevtoken = token = nexttoken = syntax['(begin)'];
+        assume();
+
+        try {
+            advance();
+            if (nexttoken.value.charAt(0) === '<') {
+                html();
+                if (option.adsafe && !adsafe_went) {
+                    warning("ADsafe violation: Missing ADSAFE.go.", this);
+                }
+            } else {
+                switch (nexttoken.id) {
+                case '{':
+                case '[':
+                    option.laxbreak = true;
+                    jsonmode = true;
+                    jsonValue();
+                    break;
+                case '@':
+                case '*':
+                case '#':
+                case '.':
+                case ':':
+                    xmode = 'style';
+                    advance();
+                    if (token.id !== '@' || !nexttoken.identifier ||
+                            nexttoken.value !== 'charset' || token.line !== 1 ||
+                            token.from !== 1) {
+                        error('A css file should begin with @charset "UTF-8";');
+                    }
+                    advance();
+                    if (nexttoken.type !== '(string)' &&
+                            nexttoken.value !== 'UTF-8') {
+                        error('A css file should begin with @charset "UTF-8";');
+                    }
+                    advance();
+                    advance(';');
+                    styles();
+                    break;
+
+                default:
+                    if (option.adsafe && option.fragment) {
+                        error("Expected '{a}' and instead saw '{b}'.",
+                            nexttoken, '<div>', nexttoken.value);
+                    }
+                    statements('lib');
+                }
+            }
+            advance('(end)');
+        } catch (e) {
+            if (e) {
+                JSLINT.errors.push({
+                    reason    : e.message,
+                    line      : e.line || nexttoken.line,
+                    character : e.character || nexttoken.from
+                }, null);
+            }
+        }
+        return JSLINT.errors.length === 0;
+    };
+
+    function is_array(o) {
+        return Object.prototype.toString.apply(o) === '[object Array]';
+    }
+
+    function to_array(o) {
+        var a = [], k;
+        for (k in o) {
+            if (is_own(o, k)) {
+                a.push(k);
+            }
+        }
+        return a;
+    }
+
+// Data summary.
+
+    itself.data = function () {
+
+        var data = {functions: []}, fu, globals, implieds = [], f, i, j,
+            members = [], n, unused = [], v;
+        if (itself.errors.length) {
+            data.errors = itself.errors;
+        }
+
+        if (jsonmode) {
+            data.json = true;
+        }
+
+        for (n in implied) {
+            if (is_own(implied, n)) {
+                implieds.push({
+                    name: n,
+                    line: implied[n]
+                });
+            }
+        }
+        if (implieds.length > 0) {
+            data.implieds = implieds;
+        }
+
+        if (urls.length > 0) {
+            data.urls = urls;
+        }
+
+        globals = to_array(scope);
+        if (globals.length > 0) {
+            data.globals = globals;
+        }
+
+        for (i = 1; i < functions.length; i += 1) {
+            f = functions[i];
+            fu = {};
+            for (j = 0; j < functionicity.length; j += 1) {
+                fu[functionicity[j]] = [];
+            }
+            for (n in f) {
+                if (is_own(f, n) && n.charAt(0) !== '(') {
+                    v = f[n];
+                    if (is_array(fu[v])) {
+                        fu[v].push(n);
+                        if (v === 'unused') {
+                            unused.push({
+                                name: n,
+                                line: f['(line)'],
+                                'function': f['(name)']
+                            });
+                        }
+                    }
+                }
+            }
+            for (j = 0; j < functionicity.length; j += 1) {
+                if (fu[functionicity[j]].length === 0) {
+                    delete fu[functionicity[j]];
+                }
+            }
+            fu.name = f['(name)'];
+            fu.param = f['(params)'];
+            fu.line = f['(line)'];
+            fu.last = f['(last)'];
+            data.functions.push(fu);
+        }
+
+        if (unused.length > 0) {
+            data.unused = unused;
+        }
+
+        members = [];
+        for (n in member) {
+            if (typeof member[n] === 'number') {
+                data.member = member;
+                break;
+            }
+        }
+
+        return data;
+    };
+
+    itself.report = function (option) {
+        var data = itself.data();
+
+        var a = [], c, e, err, f, i, k, l, m = '', n, o = [], s;
+
+        function detail(h, array) {
+            var b, i, singularity;
+            if (array) {
+                o.push('<div><i>' + h + '</i> ');
+                array = array.sort();
+                for (i = 0; i < array.length; i += 1) {
+                    if (array[i] !== singularity) {
+                        singularity = array[i];
+                        o.push((b ? ', ' : '') + singularity);
+                        b = true;
+                    }
+                }
+                o.push('</div>');
+            }
+        }
+
+
+        if (data.errors || data.implieds || data.unused) {
+            err = true;
+            o.push('<div id=errors><i>Error:</i>');
+            if (data.errors) {
+                for (i = 0; i < data.errors.length; i += 1) {
+                    c = data.errors[i];
+                    if (c) {
+                        e = c.evidence || '';
+                        o.push('<p>Problem' + (isFinite(c.line) ? ' at line ' +
+                                c.line + ' character ' + c.character : '') +
+                                ': ' + c.reason.entityify() +
+                                '</p><p class=evidence>' +
+                                (e && (e.length > 80 ? e.slice(0, 77) + '...' :
+                                e).entityify()) + '</p>');
+                    }
+                }
+            }
+
+            if (data.implieds) {
+                s = [];
+                for (i = 0; i < data.implieds.length; i += 1) {
+                    s[i] = '<code>' + data.implieds[i].name + '</code>&nbsp;<i>' +
+                        data.implieds[i].line + '</i>';
+                }
+                o.push('<p><i>Implied global:</i> ' + s.join(', ') + '</p>');
+            }
+
+            if (data.unused) {
+                s = [];
+                for (i = 0; i < data.unused.length; i += 1) {
+                    s[i] = '<code><u>' + data.unused[i].name + '</u></code>&nbsp;<i>' +
+                        data.unused[i].line + '</i> <code>' +
+                        data.unused[i]['function'] + '</code>';
+                }
+                o.push('<p><i>Unused variable:</i> ' + s.join(', ') + '</p>');
+            }
+            if (data.json) {
+                o.push('<p>JSON: bad.</p>');
+            }
+            o.push('</div>');
+        }
+
+        if (!option) {
+
+            o.push('<br><div id=functions>');
+
+            if (data.urls) {
+                detail("URLs<br>", data.urls, '<br>');
+            }
+
+            if (xmode === 'style') {
+                o.push('<p>CSS.</p>');
+            } else if (data.json && !err) {
+                o.push('<p>JSON: good.</p>');
+            } else if (data.globals) {
+                o.push('<div><i>Global</i> ' +
+                        data.globals.sort().join(', ') + '</div>');
+            } else {
+                o.push('<div><i>No new global variables introduced.</i></div>');
+            }
+
+            for (i = 0; i < data.functions.length; i += 1) {
+                f = data.functions[i];
+
+                o.push('<br><div class=function><i>' + f.line + '-' +
+                        f.last + '</i> ' + (f.name || '') + '(' +
+                        (f.param ? f.param.join(', ') : '') + ')</div>');
+                detail('<big><b>Unused</b></big>', f.unused);
+                detail('Closure', f.closure);
+                detail('Variable', f['var']);
+                detail('Exception', f.exception);
+                detail('Outer', f.outer);
+                detail('Global', f.global);
+                detail('Label', f.label);
+            }
+
+            if (data.member) {
+                a = to_array(data.member);
+                if (a.length) {
+                    a = a.sort();
+                    m = '<br><pre id=members>/*members ';
+                    l = 10;
+                    for (i = 0; i < a.length; i += 1) {
+                        k = a[i];
+                        n = k.name();
+                        if (l + n.length > 72) {
+                            o.push(m + '<br>');
+                            m = '    ';
+                            l = 1;
+                        }
+                        l += n.length + 2;
+                        if (data.member[k] === 1) {
+                            n = '<i>' + n + '</i>';
+                        }
+                        if (i < a.length - 1) {
+                            n += ', ';
+                        }
+                        m += n;
+                    }
+                    o.push(m + '<br>*/</pre>');
+                }
+                o.push('</div>');
+            }
+        }
+        return o.join('');
+    };
+    itself.jslint = itself;
+
+    itself.edition = '2010-02-20';
+
+    if (typeof exports !== "undefined") {
+        exports.JSLINT = itself;
+    }
+
+    return itself;
+
+}());
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/lib/parse-js.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/lib/parse-js.js
new file mode 100644
index 0000000..9f90dfe
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/lib/parse-js.js
@@ -0,0 +1,1319 @@
+/***********************************************************************
+
+  A JavaScript tokenizer / parser / beautifier / compressor.
+
+  This version is suitable for Node.js.  With minimal changes (the
+  exports stuff) it should work on any JS platform.
+
+  This file contains the tokenizer/parser.  It is a port to JavaScript
+  of parse-js [1], a JavaScript parser library written in Common Lisp
+  by Marijn Haverbeke.  Thank you Marijn!
+
+  [1] http://marijn.haverbeke.nl/parse-js/
+
+  Exported functions:
+
+    - tokenizer(code) -- returns a function.  Call the returned
+      function to fetch the next token.
+
+    - parse(code) -- returns an AST of the given JavaScript code.
+
+  -------------------------------- (C) ---------------------------------
+
+                           Author: Mihai Bazon
+                         <mihai.bazon@gmail.com>
+                       http://mihai.bazon.net/blog
+
+  Distributed under the BSD license:
+
+    Copyright 2010 (c) Mihai Bazon <mihai.bazon@gmail.com>
+    Based on parse-js (http://marijn.haverbeke.nl/parse-js/).
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions
+    are met:
+
+        * Redistributions of source code must retain the above
+          copyright notice, this list of conditions and the following
+          disclaimer.
+
+        * Redistributions in binary form must reproduce the above
+          copyright notice, this list of conditions and the following
+          disclaimer in the documentation and/or other materials
+          provided with the distribution.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
+    EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+    PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
+    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+    OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+    TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+    THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+    SUCH DAMAGE.
+
+ ***********************************************************************/
+
+/* -----[ Tokenizer (constants) ]----- */
+
+var KEYWORDS = array_to_hash([
+        "break",
+        "case",
+        "catch",
+        "const",
+        "continue",
+        "default",
+        "delete",
+        "do",
+        "else",
+        "finally",
+        "for",
+        "function",
+        "if",
+        "in",
+        "instanceof",
+        "new",
+        "return",
+        "switch",
+        "throw",
+        "try",
+        "typeof",
+        "var",
+        "void",
+        "while",
+        "with"
+]);
+
+var RESERVED_WORDS = array_to_hash([
+        "abstract",
+        "boolean",
+        "byte",
+        "char",
+        "class",
+        "debugger",
+        "double",
+        "enum",
+        "export",
+        "extends",
+        "final",
+        "float",
+        "goto",
+        "implements",
+        "import",
+        "int",
+        "interface",
+        "long",
+        "native",
+        "package",
+        "private",
+        "protected",
+        "public",
+        "short",
+        "static",
+        "super",
+        "synchronized",
+        "throws",
+        "transient",
+        "volatile"
+]);
+
+var KEYWORDS_BEFORE_EXPRESSION = array_to_hash([
+        "return",
+        "new",
+        "delete",
+        "throw",
+        "else",
+        "case"
+]);
+
+var KEYWORDS_ATOM = array_to_hash([
+        "false",
+        "null",
+        "true",
+        "undefined"
+]);
+
+var OPERATOR_CHARS = array_to_hash(characters("+-*&%=<>!?|~^"));
+
+var RE_HEX_NUMBER = /^0x[0-9a-f]+$/i;
+var RE_OCT_NUMBER = /^0[0-7]+$/;
+var RE_DEC_NUMBER = /^\d*\.?\d*(?:e[+-]?\d*(?:\d\.?|\.?\d)\d*)?$/i;
+
+var OPERATORS = array_to_hash([
+        "in",
+        "instanceof",
+        "typeof",
+        "new",
+        "void",
+        "delete",
+        "++",
+        "--",
+        "+",
+        "-",
+        "!",
+        "~",
+        "&",
+        "|",
+        "^",
+        "*",
+        "/",
+        "%",
+        ">>",
+        "<<",
+        ">>>",
+        "<",
+        ">",
+        "<=",
+        ">=",
+        "==",
+        "===",
+        "!=",
+        "!==",
+        "?",
+        "=",
+        "+=",
+        "-=",
+        "/=",
+        "*=",
+        "%=",
+        ">>=",
+        "<<=",
+        ">>>=",
+        "|=",
+        "^=",
+        "&=",
+        "&&",
+        "||"
+]);
+
+var WHITESPACE_CHARS = array_to_hash(characters(" \n\r\t\u200b"));
+
+var PUNC_BEFORE_EXPRESSION = array_to_hash(characters("[{}(,.;:"));
+
+var PUNC_CHARS = array_to_hash(characters("[]{}(),;:"));
+
+var REGEXP_MODIFIERS = array_to_hash(characters("gmsiy"));
+
+/* -----[ Tokenizer ]----- */
+
+// regexps adapted from http://xregexp.com/plugins/#unicode
+var UNICODE = {
+        letter: new RegExp("[\\u0041-\\u005A\\u0061-\\u007A\\u00AA\\u00B5\\u00BA\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02C1\\u02C6-\\u02D1\\u02E0-\\u02E4\\u02EC\\u02EE\\u0370-\\u0374\\u0376\\u0377\\u037A-\\u037D\\u0386\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03F5\\u03F7-\\u0481\\u048A-\\u0523\\u0531-\\u0556\\u0559\\u0561-\\u0587\\u05D0-\\u05EA\\u05F0-\\u05F2\\u0621-\\u064A\\u066E\\u066F\\u0671-\\u06D3\\u06D5\\u06E5\\u06E6\\u06EE\\u06EF\\u06FA-\\u06FC\\u06FF\\u0710\\u0712-\\u072F\\u074D-\\u07A5\\u07B1\\u07CA-\\u07EA\\u07F4\\u07F5\\u07FA\\u0904-\\u0939\\u093D\\u0950\\u0958-\\u0961\\u0971\\u0972\\u097B-\\u097F\\u0985-\\u098C\\u098F\\u0990\\u0993-\\u09A8\\u09AA-\\u09B0\\u09B2\\u09B6-\\u09B9\\u09BD\\u09CE\\u09DC\\u09DD\\u09DF-\\u09E1\\u09F0\\u09F1\\u0A05-\\u0A0A\\u0A0F\\u0A10\\u0A13-\\u0A28\\u0A2A-\\u0A30\\u0A32\\u0A33\\u0A35\\u0A36\\u0A38\\u0A39\\u0A59-\\u0A5C\\u0A5E\\u0A72-\\u0A74\\u0A85-\\u0A8D\\u0A8F-\\u0A91\\u0A93-\\u0AA8\\u0AAA-\\u0AB0\\u0AB2\\u0AB3\\u0AB5-\\u0AB9\\u0ABD\\u0AD0\\u0AE0\\u0AE1\\u0B05-\\u0B0C\\u0B0F\\u0B10\\u0B13-\\u0B28\\u0B2A-\\u0B30\\u0B32\\u0B33\\u0B35-\\u0B39\\u0B3D\\u0B5C\\u0B5D\\u0B5F-\\u0B61\\u0B71\\u0B83\\u0B85-\\u0B8A\\u0B8E-\\u0B90\\u0B92-\\u0B95\\u0B99\\u0B9A\\u0B9C\\u0B9E\\u0B9F\\u0BA3\\u0BA4\\u0BA8-\\u0BAA\\u0BAE-\\u0BB9\\u0BD0\\u0C05-\\u0C0C\\u0C0E-\\u0C10\\u0C12-\\u0C28\\u0C2A-\\u0C33\\u0C35-\\u0C39\\u0C3D\\u0C58\\u0C59\\u0C60\\u0C61\\u0C85-\\u0C8C\\u0C8E-\\u0C90\\u0C92-\\u0CA8\\u0CAA-\\u0CB3\\u0CB5-\\u0CB9\\u0CBD\\u0CDE\\u0CE0\\u0CE1\\u0D05-\\u0D0C\\u0D0E-\\u0D10\\u0D12-\\u0D28\\u0D2A-\\u0D39\\u0D3D\\u0D60\\u0D61\\u0D7A-\\u0D7F\\u0D85-\\u0D96\\u0D9A-\\u0DB1\\u0DB3-\\u0DBB\\u0DBD\\u0DC0-\\u0DC6\\u0E01-\\u0E30\\u0E32\\u0E33\\u0E40-\\u0E46\\u0E81\\u0E82\\u0E84\\u0E87\\u0E88\\u0E8A\\u0E8D\\u0E94-\\u0E97\\u0E99-\\u0E9F\\u0EA1-\\u0EA3\\u0EA5\\u0EA7\\u0EAA\\u0EAB\\u0EAD-\\u0EB0\\u0EB2\\u0EB3\\u0EBD\\u0EC0-\\u0EC4\\u0EC6\\u0EDC\\u0EDD\\u0F00\\u0F40-\\u0F47\\u0F49-\\u0F6C\\u0F88-\\u0F8B\\u1000-\\u102A\\u103F\\u1050-\\u1055\\u105A-\\u105D\\u1061\\u1065\\u1066\\u106E-\\u1070\\u1075-\\u1081\\u108E\\u10A0-\\u10C5\\u10D0-\\u10FA\\u10FC\\u1100-\\u1159\\u115F-\\u11A2\\u11A8-\\u11F9\\u1200-\\u1248\\u124A-\\u124D\\u1250-\\u1256\\u1258\\u125A-\\u125D\\u1260-\\u1288\\u128A-\\u128D\\u1290-\\u12B0\\u12B2-\\u12B5\\u12B8-\\u12BE\\u12C0\\u12C2-\\u12C5\\u12C8-\\u12D6\\u12D8-\\u1310\\u1312-\\u1315\\u1318-\\u135A\\u1380-\\u138F\\u13A0-\\u13F4\\u1401-\\u166C\\u166F-\\u1676\\u1681-\\u169A\\u16A0-\\u16EA\\u1700-\\u170C\\u170E-\\u1711\\u1720-\\u1731\\u1740-\\u1751\\u1760-\\u176C\\u176E-\\u1770\\u1780-\\u17B3\\u17D7\\u17DC\\u1820-\\u1877\\u1880-\\u18A8\\u18AA\\u1900-\\u191C\\u1950-\\u196D\\u1970-\\u1974\\u1980-\\u19A9\\u19C1-\\u19C7\\u1A00-\\u1A16\\u1B05-\\u1B33\\u1B45-\\u1B4B\\u1B83-\\u1BA0\\u1BAE\\u1BAF\\u1C00-\\u1C23\\u1C4D-\\u1C4F\\u1C5A-\\u1C7D\\u1D00-\\u1DBF\\u1E00-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45\\u1F48-\\u1F4D\\u1F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F7D\\u1F80-\\u1FB4\\u1FB6-\\u1FBC\\u1FBE\\u1FC2-\\u1FC4\\u1FC6-\\u1FCC\\u1FD0-\\u1FD3\\u1FD6-\\u1FDB\\u1FE0-\\u1FEC\\u1FF2-\\u1FF4\\u1FF6-\\u1FFC\\u2071\\u207F\\u2090-\\u2094\\u2102\\u2107\\u210A-\\u2113\\u2115\\u2119-\\u211D\\u2124\\u2126\\u2128\\u212A-\\u212D\\u212F-\\u2139\\u213C-\\u213F\\u2145-\\u2149\\u214E\\u2183\\u2184\\u2C00-\\u2C2E\\u2C30-\\u2C5E\\u2C60-\\u2C6F\\u2C71-\\u2C7D\\u2C80-\\u2CE4\\u2D00-\\u2D25\\u2D30-\\u2D65\\u2D6F\\u2D80-\\u2D96\\u2DA0-\\u2DA6\\u2DA8-\\u2DAE\\u2DB0-\\u2DB6\\u2DB8-\\u2DBE\\u2DC0-\\u2DC6\\u2DC8-\\u2DCE\\u2DD0-\\u2DD6\\u2DD8-\\u2DDE\\u2E2F\\u3005\\u3006\\u3031-\\u3035\\u303B\\u303C\\u3041-\\u3096\\u309D-\\u309F\\u30A1-\\u30FA\\u30FC-\\u30FF\\u3105-\\u312D\\u3131-\\u318E\\u31A0-\\u31B7\\u31F0-\\u31FF\\u3400\\u4DB5\\u4E00\\u9FC3\\uA000-\\uA48C\\uA500-\\uA60C\\uA610-\\uA61F\\uA62A\\uA62B\\uA640-\\uA65F\\uA662-\\uA66E\\uA67F-\\uA697\\uA717-\\uA71F\\uA722-\\uA788\\uA78B\\uA78C\\uA7FB-\\uA801\\uA803-\\uA805\\uA807-\\uA80A\\uA80C-\\uA822\\uA840-\\uA873\\uA882-\\uA8B3\\uA90A-\\uA925\\uA930-\\uA946\\uAA00-\\uAA28\\uAA40-\\uAA42\\uAA44-\\uAA4B\\uAC00\\uD7A3\\uF900-\\uFA2D\\uFA30-\\uFA6A\\uFA70-\\uFAD9\\uFB00-\\uFB06\\uFB13-\\uFB17\\uFB1D\\uFB1F-\\uFB28\\uFB2A-\\uFB36\\uFB38-\\uFB3C\\uFB3E\\uFB40\\uFB41\\uFB43\\uFB44\\uFB46-\\uFBB1\\uFBD3-\\uFD3D\\uFD50-\\uFD8F\\uFD92-\\uFDC7\\uFDF0-\\uFDFB\\uFE70-\\uFE74\\uFE76-\\uFEFC\\uFF21-\\uFF3A\\uFF41-\\uFF5A\\uFF66-\\uFFBE\\uFFC2-\\uFFC7\\uFFCA-\\uFFCF\\uFFD2-\\uFFD7\\uFFDA-\\uFFDC]"),
+        non_spacing_mark: new RegExp("[\\u0300-\\u036F\\u0483-\\u0487\\u0591-\\u05BD\\u05BF\\u05C1\\u05C2\\u05C4\\u05C5\\u05C7\\u0610-\\u061A\\u064B-\\u065E\\u0670\\u06D6-\\u06DC\\u06DF-\\u06E4\\u06E7\\u06E8\\u06EA-\\u06ED\\u0711\\u0730-\\u074A\\u07A6-\\u07B0\\u07EB-\\u07F3\\u0816-\\u0819\\u081B-\\u0823\\u0825-\\u0827\\u0829-\\u082D\\u0900-\\u0902\\u093C\\u0941-\\u0948\\u094D\\u0951-\\u0955\\u0962\\u0963\\u0981\\u09BC\\u09C1-\\u09C4\\u09CD\\u09E2\\u09E3\\u0A01\\u0A02\\u0A3C\\u0A41\\u0A42\\u0A47\\u0A48\\u0A4B-\\u0A4D\\u0A51\\u0A70\\u0A71\\u0A75\\u0A81\\u0A82\\u0ABC\\u0AC1-\\u0AC5\\u0AC7\\u0AC8\\u0ACD\\u0AE2\\u0AE3\\u0B01\\u0B3C\\u0B3F\\u0B41-\\u0B44\\u0B4D\\u0B56\\u0B62\\u0B63\\u0B82\\u0BC0\\u0BCD\\u0C3E-\\u0C40\\u0C46-\\u0C48\\u0C4A-\\u0C4D\\u0C55\\u0C56\\u0C62\\u0C63\\u0CBC\\u0CBF\\u0CC6\\u0CCC\\u0CCD\\u0CE2\\u0CE3\\u0D41-\\u0D44\\u0D4D\\u0D62\\u0D63\\u0DCA\\u0DD2-\\u0DD4\\u0DD6\\u0E31\\u0E34-\\u0E3A\\u0E47-\\u0E4E\\u0EB1\\u0EB4-\\u0EB9\\u0EBB\\u0EBC\\u0EC8-\\u0ECD\\u0F18\\u0F19\\u0F35\\u0F37\\u0F39\\u0F71-\\u0F7E\\u0F80-\\u0F84\\u0F86\\u0F87\\u0F90-\\u0F97\\u0F99-\\u0FBC\\u0FC6\\u102D-\\u1030\\u1032-\\u1037\\u1039\\u103A\\u103D\\u103E\\u1058\\u1059\\u105E-\\u1060\\u1071-\\u1074\\u1082\\u1085\\u1086\\u108D\\u109D\\u135F\\u1712-\\u1714\\u1732-\\u1734\\u1752\\u1753\\u1772\\u1773\\u17B7-\\u17BD\\u17C6\\u17C9-\\u17D3\\u17DD\\u180B-\\u180D\\u18A9\\u1920-\\u1922\\u1927\\u1928\\u1932\\u1939-\\u193B\\u1A17\\u1A18\\u1A56\\u1A58-\\u1A5E\\u1A60\\u1A62\\u1A65-\\u1A6C\\u1A73-\\u1A7C\\u1A7F\\u1B00-\\u1B03\\u1B34\\u1B36-\\u1B3A\\u1B3C\\u1B42\\u1B6B-\\u1B73\\u1B80\\u1B81\\u1BA2-\\u1BA5\\u1BA8\\u1BA9\\u1C2C-\\u1C33\\u1C36\\u1C37\\u1CD0-\\u1CD2\\u1CD4-\\u1CE0\\u1CE2-\\u1CE8\\u1CED\\u1DC0-\\u1DE6\\u1DFD-\\u1DFF\\u20D0-\\u20DC\\u20E1\\u20E5-\\u20F0\\u2CEF-\\u2CF1\\u2DE0-\\u2DFF\\u302A-\\u302F\\u3099\\u309A\\uA66F\\uA67C\\uA67D\\uA6F0\\uA6F1\\uA802\\uA806\\uA80B\\uA825\\uA826\\uA8C4\\uA8E0-\\uA8F1\\uA926-\\uA92D\\uA947-\\uA951\\uA980-\\uA982\\uA9B3\\uA9B6-\\uA9B9\\uA9BC\\uAA29-\\uAA2E\\uAA31\\uAA32\\uAA35\\uAA36\\uAA43\\uAA4C\\uAAB0\\uAAB2-\\uAAB4\\uAAB7\\uAAB8\\uAABE\\uAABF\\uAAC1\\uABE5\\uABE8\\uABED\\uFB1E\\uFE00-\\uFE0F\\uFE20-\\uFE26]"),
+        space_combining_mark: new RegExp("[\\u0903\\u093E-\\u0940\\u0949-\\u094C\\u094E\\u0982\\u0983\\u09BE-\\u09C0\\u09C7\\u09C8\\u09CB\\u09CC\\u09D7\\u0A03\\u0A3E-\\u0A40\\u0A83\\u0ABE-\\u0AC0\\u0AC9\\u0ACB\\u0ACC\\u0B02\\u0B03\\u0B3E\\u0B40\\u0B47\\u0B48\\u0B4B\\u0B4C\\u0B57\\u0BBE\\u0BBF\\u0BC1\\u0BC2\\u0BC6-\\u0BC8\\u0BCA-\\u0BCC\\u0BD7\\u0C01-\\u0C03\\u0C41-\\u0C44\\u0C82\\u0C83\\u0CBE\\u0CC0-\\u0CC4\\u0CC7\\u0CC8\\u0CCA\\u0CCB\\u0CD5\\u0CD6\\u0D02\\u0D03\\u0D3E-\\u0D40\\u0D46-\\u0D48\\u0D4A-\\u0D4C\\u0D57\\u0D82\\u0D83\\u0DCF-\\u0DD1\\u0DD8-\\u0DDF\\u0DF2\\u0DF3\\u0F3E\\u0F3F\\u0F7F\\u102B\\u102C\\u1031\\u1038\\u103B\\u103C\\u1056\\u1057\\u1062-\\u1064\\u1067-\\u106D\\u1083\\u1084\\u1087-\\u108C\\u108F\\u109A-\\u109C\\u17B6\\u17BE-\\u17C5\\u17C7\\u17C8\\u1923-\\u1926\\u1929-\\u192B\\u1930\\u1931\\u1933-\\u1938\\u19B0-\\u19C0\\u19C8\\u19C9\\u1A19-\\u1A1B\\u1A55\\u1A57\\u1A61\\u1A63\\u1A64\\u1A6D-\\u1A72\\u1B04\\u1B35\\u1B3B\\u1B3D-\\u1B41\\u1B43\\u1B44\\u1B82\\u1BA1\\u1BA6\\u1BA7\\u1BAA\\u1C24-\\u1C2B\\u1C34\\u1C35\\u1CE1\\u1CF2\\uA823\\uA824\\uA827\\uA880\\uA881\\uA8B4-\\uA8C3\\uA952\\uA953\\uA983\\uA9B4\\uA9B5\\uA9BA\\uA9BB\\uA9BD-\\uA9C0\\uAA2F\\uAA30\\uAA33\\uAA34\\uAA4D\\uAA7B\\uABE3\\uABE4\\uABE6\\uABE7\\uABE9\\uABEA\\uABEC]"),
+        connector_punctuation: new RegExp("[\\u005F\\u203F\\u2040\\u2054\\uFE33\\uFE34\\uFE4D-\\uFE4F\\uFF3F]")
+};
+
+function is_letter(ch) {
+        return UNICODE.letter.test(ch);
+};
+
+function is_digit(ch) {
+        ch = ch.charCodeAt(0);
+        return ch >= 48 && ch <= 57; //XXX: find out if "UnicodeDigit" means something else than 0..9
+};
+
+function is_alphanumeric_char(ch) {
+        return is_digit(ch) || is_letter(ch);
+};
+
+function is_unicode_combining_mark(ch) {
+        return UNICODE.non_spacing_mark.test(ch) || UNICODE.space_combining_mark.test(ch);
+};
+
+function is_unicode_connector_punctuation(ch) {
+        return UNICODE.connector_punctuation.test(ch);
+};
+
+function is_identifier_start(ch) {
+        return ch == "$" || ch == "_" || is_letter(ch);
+};
+
+function is_identifier_char(ch) {
+        return is_identifier_start(ch)
+                || is_unicode_combining_mark(ch)
+                || is_digit(ch)
+                || is_unicode_connector_punctuation(ch)
+                || ch == "\u200c" // zero-width non-joiner <ZWNJ>
+                || ch == "\u200d" // zero-width joiner <ZWJ> (in my ECMA-262 PDF, this is also 200c)
+        ;
+};
+
+function parse_js_number(num) {
+        if (RE_HEX_NUMBER.test(num)) {
+                return parseInt(num.substr(2), 16);
+        } else if (RE_OCT_NUMBER.test(num)) {
+                return parseInt(num.substr(1), 8);
+        } else if (RE_DEC_NUMBER.test(num)) {
+                return parseFloat(num);
+        }
+};
+
+function JS_Parse_Error(message, line, col, pos) {
+        this.message = message;
+        this.line = line;
+        this.col = col;
+        this.pos = pos;
+        try {
+                ({})();
+        } catch(ex) {
+                this.stack = ex.stack;
+        };
+};
+
+JS_Parse_Error.prototype.toString = function() {
+        return this.message + " (line: " + this.line + ", col: " + this.col + ", pos: " + this.pos + ")" + "\n\n" + this.stack;
+};
+
+function js_error(message, line, col, pos) {
+        throw new JS_Parse_Error(message, line, col, pos);
+};
+
+function is_token(token, type, val) {
+        return token.type == type && (val == null || token.value == val);
+};
+
+var EX_EOF = {};
+
+function tokenizer($TEXT) {
+
+        var S = {
+                text            : $TEXT.replace(/\r\n?|[\n\u2028\u2029]/g, "\n").replace(/^\uFEFF/, ''),
+                pos             : 0,
+                tokpos          : 0,
+                line            : 0,
+                tokline         : 0,
+                col             : 0,
+                tokcol          : 0,
+                newline_before  : false,
+                regex_allowed   : false,
+                comments_before : []
+        };
+
+        function peek() { return S.text.charAt(S.pos); };
+
+        function next(signal_eof) {
+                var ch = S.text.charAt(S.pos++);
+                if (signal_eof && !ch)
+                        throw EX_EOF;
+                if (ch == "\n") {
+                        S.newline_before = true;
+                        ++S.line;
+                        S.col = 0;
+                } else {
+                        ++S.col;
+                }
+                return ch;
+        };
+
+        function eof() {
+                return !S.peek();
+        };
+
+        function find(what, signal_eof) {
+                var pos = S.text.indexOf(what, S.pos);
+                if (signal_eof && pos == -1) throw EX_EOF;
+                return pos;
+        };
+
+        function start_token() {
+                S.tokline = S.line;
+                S.tokcol = S.col;
+                S.tokpos = S.pos;
+        };
+
+        function token(type, value, is_comment) {
+                S.regex_allowed = ((type == "operator" && !HOP(UNARY_POSTFIX, value)) ||
+                                   (type == "keyword" && HOP(KEYWORDS_BEFORE_EXPRESSION, value)) ||
+                                   (type == "punc" && HOP(PUNC_BEFORE_EXPRESSION, value)));
+                var ret = {
+                        type  : type,
+                        value : value,
+                        line  : S.tokline,
+                        col   : S.tokcol,
+                        pos   : S.tokpos,
+                        nlb   : S.newline_before
+                };
+                if (!is_comment) {
+                        ret.comments_before = S.comments_before;
+                        S.comments_before = [];
+                }
+                S.newline_before = false;
+                return ret;
+        };
+
+        function skip_whitespace() {
+                while (HOP(WHITESPACE_CHARS, peek()))
+                        next();
+        };
+
+        function read_while(pred) {
+                var ret = "", ch = peek(), i = 0;
+                while (ch && pred(ch, i++)) {
+                        ret += next();
+                        ch = peek();
+                }
+                return ret;
+        };
+
+        function parse_error(err) {
+                js_error(err, S.tokline, S.tokcol, S.tokpos);
+        };
+
+        function read_num(prefix) {
+                var has_e = false, after_e = false, has_x = false, has_dot = prefix == ".";
+                var num = read_while(function(ch, i){
+                        if (ch == "x" || ch == "X") {
+                                if (has_x) return false;
+                                return has_x = true;
+                        }
+                        if (!has_x && (ch == "E" || ch == "e")) {
+                                if (has_e) return false;
+                                return has_e = after_e = true;
+                        }
+                        if (ch == "-") {
+                                if (after_e || (i == 0 && !prefix)) return true;
+                                return false;
+                        }
+                        if (ch == "+") return after_e;
+                        after_e = false;
+                        if (ch == ".") {
+                                if (!has_dot && !has_x)
+                                        return has_dot = true;
+                                return false;
+                        }
+                        return is_alphanumeric_char(ch);
+                });
+                if (prefix)
+                        num = prefix + num;
+                var valid = parse_js_number(num);
+                if (!isNaN(valid)) {
+                        return token("num", valid);
+                } else {
+                        parse_error("Invalid syntax: " + num);
+                }
+        };
+
+        function read_escaped_char() {
+                var ch = next(true);
+                switch (ch) {
+                    case "n" : return "\n";
+                    case "r" : return "\r";
+                    case "t" : return "\t";
+                    case "b" : return "\b";
+                    case "v" : return "\v";
+                    case "f" : return "\f";
+                    case "0" : return "\0";
+                    case "x" : return String.fromCharCode(hex_bytes(2));
+                    case "u" : return String.fromCharCode(hex_bytes(4));
+                    default  : return ch;
+                }
+        };
+
+        function hex_bytes(n) {
+                var num = 0;
+                for (; n > 0; --n) {
+                        var digit = parseInt(next(true), 16);
+                        if (isNaN(digit))
+                                parse_error("Invalid hex-character pattern in string");
+                        num = (num << 4) | digit;
+                }
+                return num;
+        };
+
+        function read_string() {
+                return with_eof_error("Unterminated string constant", function(){
+                        var quote = next(), ret = "";
+                        for (;;) {
+                                var ch = next(true);
+                                if (ch == "\\") ch = read_escaped_char();
+                                else if (ch == quote) break;
+                                ret += ch;
+                        }
+                        return token("string", ret);
+                });
+        };
+
+        function read_line_comment() {
+                next();
+                var i = find("\n"), ret;
+                if (i == -1) {
+                        ret = S.text.substr(S.pos);
+                        S.pos = S.text.length;
+                } else {
+                        ret = S.text.substring(S.pos, i);
+                        S.pos = i;
+                }
+                return token("comment1", ret, true);
+        };
+
+        function read_multiline_comment() {
+                next();
+                return with_eof_error("Unterminated multiline comment", function(){
+                        var i = find("*/", true),
+                            text = S.text.substring(S.pos, i),
+                            tok = token("comment2", text, true);
+                        S.pos = i + 2;
+                        S.line += text.split("\n").length - 1;
+                        S.newline_before = text.indexOf("\n") >= 0;
+
+                        // https://github.com/mishoo/UglifyJS/issues/#issue/100
+                        if (/^@cc_on/i.test(text)) {
+                                warn("WARNING: at line " + S.line);
+                                warn("*** Found \"conditional comment\": " + text);
+                                warn("*** UglifyJS DISCARDS ALL COMMENTS.  This means your code might no longer work properly in Internet Explorer.");
+                        }
+
+                        return tok;
+                });
+        };
+
+        function read_name() {
+                var backslash = false, name = "", ch;
+                while ((ch = peek()) != null) {
+                        if (!backslash) {
+                                if (ch == "\\") backslash = true, next();
+                                else if (is_identifier_char(ch)) name += next();
+                                else break;
+                        }
+                        else {
+                                if (ch != "u") parse_error("Expecting UnicodeEscapeSequence -- uXXXX");
+                                ch = read_escaped_char();
+                                if (!is_identifier_char(ch)) parse_error("Unicode char: " + ch.charCodeAt(0) + " is not valid in identifier");
+                                name += ch;
+                                backslash = false;
+                        }
+                }
+                return name;
+        };
+
+        function read_regexp() {
+                return with_eof_error("Unterminated regular expression", function(){
+                        var prev_backslash = false, regexp = "", ch, in_class = false;
+                        while ((ch = next(true))) if (prev_backslash) {
+                                regexp += "\\" + ch;
+                                prev_backslash = false;
+                        } else if (ch == "[") {
+                                in_class = true;
+                                regexp += ch;
+                        } else if (ch == "]" && in_class) {
+                                in_class = false;
+                                regexp += ch;
+                        } else if (ch == "/" && !in_class) {
+                                break;
+                        } else if (ch == "\\") {
+                                prev_backslash = true;
+                        } else {
+                                regexp += ch;
+                        }
+                        var mods = read_name();
+                        return token("regexp", [ regexp, mods ]);
+                });
+        };
+
+        function read_operator(prefix) {
+                function grow(op) {
+                        if (!peek()) return op;
+                        var bigger = op + peek();
+                        if (HOP(OPERATORS, bigger)) {
+                                next();
+                                return grow(bigger);
+                        } else {
+                                return op;
+                        }
+                };
+                return token("operator", grow(prefix || next()));
+        };
+
+        function handle_slash() {
+                next();
+                var regex_allowed = S.regex_allowed;
+                switch (peek()) {
+                    case "/":
+                        S.comments_before.push(read_line_comment());
+                        S.regex_allowed = regex_allowed;
+                        return next_token();
+                    case "*":
+                        S.comments_before.push(read_multiline_comment());
+                        S.regex_allowed = regex_allowed;
+                        return next_token();
+                }
+                return S.regex_allowed ? read_regexp() : read_operator("/");
+        };
+
+        function handle_dot() {
+                next();
+                return is_digit(peek())
+                        ? read_num(".")
+                        : token("punc", ".");
+        };
+
+        function read_word() {
+                var word = read_name();
+                return !HOP(KEYWORDS, word)
+                        ? token("name", word)
+                        : HOP(OPERATORS, word)
+                        ? token("operator", word)
+                        : HOP(KEYWORDS_ATOM, word)
+                        ? token("atom", word)
+                        : token("keyword", word);
+        };
+
+        function with_eof_error(eof_error, cont) {
+                try {
+                        return cont();
+                } catch(ex) {
+                        if (ex === EX_EOF) parse_error(eof_error);
+                        else throw ex;
+                }
+        };
+
+        function next_token(force_regexp) {
+                if (force_regexp)
+                        return read_regexp();
+                skip_whitespace();
+                start_token();
+                var ch = peek();
+                if (!ch) return token("eof");
+                if (is_digit(ch)) return read_num();
+                if (ch == '"' || ch == "'") return read_string();
+                if (HOP(PUNC_CHARS, ch)) return token("punc", next());
+                if (ch == ".") return handle_dot();
+                if (ch == "/") return handle_slash();
+                if (HOP(OPERATOR_CHARS, ch)) return read_operator();
+                if (ch == "\\" || is_identifier_start(ch)) return read_word();
+                parse_error("Unexpected character '" + ch + "'");
+        };
+
+        next_token.context = function(nc) {
+                if (nc) S = nc;
+                return S;
+        };
+
+        return next_token;
+
+};
+
+/* -----[ Parser (constants) ]----- */
+
+var UNARY_PREFIX = array_to_hash([
+        "typeof",
+        "void",
+        "delete",
+        "--",
+        "++",
+        "!",
+        "~",
+        "-",
+        "+"
+]);
+
+var UNARY_POSTFIX = array_to_hash([ "--", "++" ]);
+
+var ASSIGNMENT = (function(a, ret, i){
+        while (i < a.length) {
+                ret[a[i]] = a[i].substr(0, a[i].length - 1);
+                i++;
+        }
+        return ret;
+})(
+        ["+=", "-=", "/=", "*=", "%=", ">>=", "<<=", ">>>=", "|=", "^=", "&="],
+        { "=": true },
+        0
+);
+
+var PRECEDENCE = (function(a, ret){
+        for (var i = 0, n = 1; i < a.length; ++i, ++n) {
+                var b = a[i];
+                for (var j = 0; j < b.length; ++j) {
+                        ret[b[j]] = n;
+                }
+        }
+        return ret;
+})(
+        [
+                ["||"],
+                ["&&"],
+                ["|"],
+                ["^"],
+                ["&"],
+                ["==", "===", "!=", "!=="],
+                ["<", ">", "<=", ">=", "in", "instanceof"],
+                [">>", "<<", ">>>"],
+                ["+", "-"],
+                ["*", "/", "%"]
+        ],
+        {}
+);
+
+var STATEMENTS_WITH_LABELS = array_to_hash([ "for", "do", "while", "switch" ]);
+
+var ATOMIC_START_TOKEN = array_to_hash([ "atom", "num", "string", "regexp", "name" ]);
+
+/* -----[ Parser ]----- */
+
+function NodeWithToken(str, start, end) {
+        this.name = str;
+        this.start = start;
+        this.end = end;
+};
+
+NodeWithToken.prototype.toString = function() { return this.name; };
+
+function parse($TEXT, exigent_mode, embed_tokens) {
+
+        var S = {
+                input       : typeof $TEXT == "string" ? tokenizer($TEXT, true) : $TEXT,
+                token       : null,
+                prev        : null,
+                peeked      : null,
+                in_function : 0,
+                in_loop     : 0,
+                labels      : []
+        };
+
+        S.token = next();
+
+        function is(type, value) {
+                return is_token(S.token, type, value);
+        };
+
+        function peek() { return S.peeked || (S.peeked = S.input()); };
+
+        function next() {
+                S.prev = S.token;
+                if (S.peeked) {
+                        S.token = S.peeked;
+                        S.peeked = null;
+                } else {
+                        S.token = S.input();
+                }
+                return S.token;
+        };
+
+        function prev() {
+                return S.prev;
+        };
+
+        function croak(msg, line, col, pos) {
+                var ctx = S.input.context();
+                js_error(msg,
+                         line != null ? line : ctx.tokline,
+                         col != null ? col : ctx.tokcol,
+                         pos != null ? pos : ctx.tokpos);
+        };
+
+        function token_error(token, msg) {
+                croak(msg, token.line, token.col);
+        };
+
+        function unexpected(token) {
+                if (token == null)
+                        token = S.token;
+                token_error(token, "Unexpected token: " + token.type + " (" + token.value + ")");
+        };
+
+        function expect_token(type, val) {
+                if (is(type, val)) {
+                        return next();
+                }
+                token_error(S.token, "Unexpected token " + S.token.type + ", expected " + type);
+        };
+
+        function expect(punc) { return expect_token("punc", punc); };
+
+        function can_insert_semicolon() {
+                return !exigent_mode && (
+                        S.token.nlb || is("eof") || is("punc", "}")
+                );
+        };
+
+        function semicolon() {
+                if (is("punc", ";")) next();
+                else if (!can_insert_semicolon()) unexpected();
+        };
+
+        function as() {
+                return slice(arguments);
+        };
+
+        function parenthesised() {
+                expect("(");
+                var ex = expression();
+                expect(")");
+                return ex;
+        };
+
+        function add_tokens(str, start, end) {
+                return str instanceof NodeWithToken ? str : new NodeWithToken(str, start, end);
+        };
+
+        var statement = embed_tokens ? function() {
+                var start = S.token;
+                var ast = $statement.apply(this, arguments);
+                ast[0] = add_tokens(ast[0], start, prev());
+                return ast;
+        } : $statement;
+
+        function $statement() {
+                if (is("operator", "/")) {
+                        S.peeked = null;
+                        S.token = S.input(true); // force regexp
+                }
+                switch (S.token.type) {
+                    case "num":
+                    case "string":
+                    case "regexp":
+                    case "operator":
+                    case "atom":
+                        return simple_statement();
+
+                    case "name":
+                        return is_token(peek(), "punc", ":")
+                                ? labeled_statement(prog1(S.token.value, next, next))
+                                : simple_statement();
+
+                    case "punc":
+                        switch (S.token.value) {
+                            case "{":
+                                return as("block", block_());
+                            case "[":
+                            case "(":
+                                return simple_statement();
+                            case ";":
+                                next();
+                                return as("block");
+                            default:
+                                unexpected();
+                        }
+
+                    case "keyword":
+                        switch (prog1(S.token.value, next)) {
+                            case "break":
+                                return break_cont("break");
+
+                            case "continue":
+                                return break_cont("continue");
+
+                            case "debugger":
+                                semicolon();
+                                return as("debugger");
+
+                            case "do":
+                                return (function(body){
+                                        expect_token("keyword", "while");
+                                        return as("do", prog1(parenthesised, semicolon), body);
+                                })(in_loop(statement));
+
+                            case "for":
+                                return for_();
+
+                            case "function":
+                                return function_(true);
+
+                            case "if":
+                                return if_();
+
+                            case "return":
+                                if (S.in_function == 0)
+                                        croak("'return' outside of function");
+                                return as("return",
+                                          is("punc", ";")
+                                          ? (next(), null)
+                                          : can_insert_semicolon()
+                                          ? null
+                                          : prog1(expression, semicolon));
+
+                            case "switch":
+                                return as("switch", parenthesised(), switch_block_());
+
+                            case "throw":
+                                return as("throw", prog1(expression, semicolon));
+
+                            case "try":
+                                return try_();
+
+                            case "var":
+                                return prog1(var_, semicolon);
+
+                            case "const":
+                                return prog1(const_, semicolon);
+
+                            case "while":
+                                return as("while", parenthesised(), in_loop(statement));
+
+                            case "with":
+                                return as("with", parenthesised(), statement());
+
+                            default:
+                                unexpected();
+                        }
+                }
+        };
+
+        function labeled_statement(label) {
+                S.labels.push(label);
+                var start = S.token, stat = statement();
+                if (exigent_mode && !HOP(STATEMENTS_WITH_LABELS, stat[0]))
+                        unexpected(start);
+                S.labels.pop();
+                return as("label", label, stat);
+        };
+
+        function simple_statement() {
+                return as("stat", prog1(expression, semicolon));
+        };
+
+        function break_cont(type) {
+                var name = is("name") ? S.token.value : null;
+                if (name != null) {
+                        next();
+                        if (!member(name, S.labels))
+                                croak("Label " + name + " without matching loop or statement");
+                }
+                else if (S.in_loop == 0)
+                        croak(type + " not inside a loop or switch");
+                semicolon();
+                return as(type, name);
+        };
+
+        function for_() {
+                expect("(");
+                var init = null;
+                if (!is("punc", ";")) {
+                        init = is("keyword", "var")
+                                ? (next(), var_(true))
+                                : expression(true, true);
+                        if (is("operator", "in"))
+                                return for_in(init);
+                }
+                return regular_for(init);
+        };
+
+        function regular_for(init) {
+                expect(";");
+                var test = is("punc", ";") ? null : expression();
+                expect(";");
+                var step = is("punc", ")") ? null : expression();
+                expect(")");
+                return as("for", init, test, step, in_loop(statement));
+        };
+
+        function for_in(init) {
+                var lhs = init[0] == "var" ? as("name", init[1][0]) : init;
+                next();
+                var obj = expression();
+                expect(")");
+                return as("for-in", init, lhs, obj, in_loop(statement));
+        };
+
+        var function_ = embed_tokens ? function() {
+                var start = prev();
+                var ast = $function_.apply(this, arguments);
+                ast[0] = add_tokens(ast[0], start, prev());
+                return ast;
+        } : $function_;
+
+        function $function_(in_statement) {
+                var name = is("name") ? prog1(S.token.value, next) : null;
+                if (in_statement && !name)
+                        unexpected();
+                expect("(");
+                return as(in_statement ? "defun" : "function",
+                          name,
+                          // arguments
+                          (function(first, a){
+                                  while (!is("punc", ")")) {
+                                          if (first) first = false; else expect(",");
+                                          if (!is("name")) unexpected();
+                                          a.push(S.token.value);
+                                          next();
+                                  }
+                                  next();
+                                  return a;
+                          })(true, []),
+                          // body
+                          (function(){
+                                  ++S.in_function;
+                                  var loop = S.in_loop;
+                                  S.in_loop = 0;
+                                  var a = block_();
+                                  --S.in_function;
+                                  S.in_loop = loop;
+                                  return a;
+                          })());
+        };
+
+        function if_() {
+                var cond = parenthesised(), body = statement(), belse;
+                if (is("keyword", "else")) {
+                        next();
+                        belse = statement();
+                }
+                return as("if", cond, body, belse);
+        };
+
+        function block_() {
+                expect("{");
+                var a = [];
+                while (!is("punc", "}")) {
+                        if (is("eof")) unexpected();
+                        a.push(statement());
+                }
+                next();
+                return a;
+        };
+
+        var switch_block_ = curry(in_loop, function(){
+                expect("{");
+                var a = [], cur = null;
+                while (!is("punc", "}")) {
+                        if (is("eof")) unexpected();
+                        if (is("keyword", "case")) {
+                                next();
+                                cur = [];
+                                a.push([ expression(), cur ]);
+                                expect(":");
+                        }
+                        else if (is("keyword", "default")) {
+                                next();
+                                expect(":");
+                                cur = [];
+                                a.push([ null, cur ]);
+                        }
+                        else {
+                                if (!cur) unexpected();
+                                cur.push(statement());
+                        }
+                }
+                next();
+                return a;
+        });
+
+        function try_() {
+                var body = block_(), bcatch, bfinally;
+                if (is("keyword", "catch")) {
+                        next();
+                        expect("(");
+                        if (!is("name"))
+                                croak("Name expected");
+                        var name = S.token.value;
+                        next();
+                        expect(")");
+                        bcatch = [ name, block_() ];
+                }
+                if (is("keyword", "finally")) {
+                        next();
+                        bfinally = block_();
+                }
+                if (!bcatch && !bfinally)
+                        croak("Missing catch/finally blocks");
+                return as("try", body, bcatch, bfinally);
+        };
+
+        function vardefs(no_in) {
+                var a = [];
+                for (;;) {
+                        if (!is("name"))
+                                unexpected();
+                        var name = S.token.value;
+                        next();
+                        if (is("operator", "=")) {
+                                next();
+                                a.push([ name, expression(false, no_in) ]);
+                        } else {
+                                a.push([ name ]);
+                        }
+                        if (!is("punc", ","))
+                                break;
+                        next();
+                }
+                return a;
+        };
+
+        function var_(no_in) {
+                return as("var", vardefs(no_in));
+        };
+
+        function const_() {
+                return as("const", vardefs());
+        };
+
+        function new_() {
+                var newexp = expr_atom(false), args;
+                if (is("punc", "(")) {
+                        next();
+                        args = expr_list(")");
+                } else {
+                        args = [];
+                }
+                return subscripts(as("new", newexp, args), true);
+        };
+
+        function expr_atom(allow_calls) {
+                if (is("operator", "new")) {
+                        next();
+                        return new_();
+                }
+                if (is("operator") && HOP(UNARY_PREFIX, S.token.value)) {
+                        return make_unary("unary-prefix",
+                                          prog1(S.token.value, next),
+                                          expr_atom(allow_calls));
+                }
+                if (is("punc")) {
+                        switch (S.token.value) {
+                            case "(":
+                                next();
+                                return subscripts(prog1(expression, curry(expect, ")")), allow_calls);
+                            case "[":
+                                next();
+                                return subscripts(array_(), allow_calls);
+                            case "{":
+                                next();
+                                return subscripts(object_(), allow_calls);
+                        }
+                        unexpected();
+                }
+                if (is("keyword", "function")) {
+                        next();
+                        return subscripts(function_(false), allow_calls);
+                }
+                if (HOP(ATOMIC_START_TOKEN, S.token.type)) {
+                        var atom = S.token.type == "regexp"
+                                ? as("regexp", S.token.value[0], S.token.value[1])
+                                : as(S.token.type, S.token.value);
+                        return subscripts(prog1(atom, next), allow_calls);
+                }
+                unexpected();
+        };
+
+        function expr_list(closing, allow_trailing_comma, allow_empty) {
+                var first = true, a = [];
+                while (!is("punc", closing)) {
+                        if (first) first = false; else expect(",");
+                        if (allow_trailing_comma && is("punc", closing)) break;
+                        if (is("punc", ",") && allow_empty) {
+                                a.push([ "atom", "undefined" ]);
+                        } else {
+                                a.push(expression(false));
+                        }
+                }
+                next();
+                return a;
+        };
+
+        function array_() {
+                return as("array", expr_list("]", !exigent_mode, true));
+        };
+
+        function object_() {
+                var first = true, a = [];
+                while (!is("punc", "}")) {
+                        if (first) first = false; else expect(",");
+                        if (!exigent_mode && is("punc", "}"))
+                                // allow trailing comma
+                                break;
+                        var type = S.token.type;
+                        var name = as_property_name();
+                        if (type == "name" && (name == "get" || name == "set") && !is("punc", ":")) {
+                                a.push([ as_name(), function_(false), name ]);
+                        } else {
+                                expect(":");
+                                a.push([ name, expression(false) ]);
+                        }
+                }
+                next();
+                return as("object", a);
+        };
+
+        function as_property_name() {
+                switch (S.token.type) {
+                    case "num":
+                    case "string":
+                        return prog1(S.token.value, next);
+                }
+                return as_name();
+        };
+
+        function as_name() {
+                switch (S.token.type) {
+                    case "name":
+                    case "operator":
+                    case "keyword":
+                    case "atom":
+                        return prog1(S.token.value, next);
+                    default:
+                        unexpected();
+                }
+        };
+
+        function subscripts(expr, allow_calls) {
+                if (is("punc", ".")) {
+                        next();
+                        return subscripts(as("dot", expr, as_name()), allow_calls);
+                }
+                if (is("punc", "[")) {
+                        next();
+                        return subscripts(as("sub", expr, prog1(expression, curry(expect, "]"))), allow_calls);
+                }
+                if (allow_calls && is("punc", "(")) {
+                        next();
+                        return subscripts(as("call", expr, expr_list(")")), true);
+                }
+                if (allow_calls && is("operator") && HOP(UNARY_POSTFIX, S.token.value)) {
+                        return prog1(curry(make_unary, "unary-postfix", S.token.value, expr),
+                                     next);
+                }
+                return expr;
+        };
+
+        function make_unary(tag, op, expr) {
+                if ((op == "++" || op == "--") && !is_assignable(expr))
+                        croak("Invalid use of " + op + " operator");
+                return as(tag, op, expr);
+        };
+
+        function expr_op(left, min_prec, no_in) {
+                var op = is("operator") ? S.token.value : null;
+                if (op && op == "in" && no_in) op = null;
+                var prec = op != null ? PRECEDENCE[op] : null;
+                if (prec != null && prec > min_prec) {
+                        next();
+                        var right = expr_op(expr_atom(true), prec, no_in);
+                        return expr_op(as("binary", op, left, right), min_prec, no_in);
+                }
+                return left;
+        };
+
+        function expr_ops(no_in) {
+                return expr_op(expr_atom(true), 0, no_in);
+        };
+
+        function maybe_conditional(no_in) {
+                var expr = expr_ops(no_in);
+                if (is("operator", "?")) {
+                        next();
+                        var yes = expression(false);
+                        expect(":");
+                        return as("conditional", expr, yes, expression(false, no_in));
+                }
+                return expr;
+        };
+
+        function is_assignable(expr) {
+                if (!exigent_mode) return true;
+                switch (expr[0]) {
+                    case "dot":
+                    case "sub":
+                    case "new":
+                    case "call":
+                        return true;
+                    case "name":
+                        return expr[1] != "this";
+                }
+        };
+
+        function maybe_assign(no_in) {
+                var left = maybe_conditional(no_in), val = S.token.value;
+                if (is("operator") && HOP(ASSIGNMENT, val)) {
+                        if (is_assignable(left)) {
+                                next();
+                                return as("assign", ASSIGNMENT[val], left, maybe_assign(no_in));
+                        }
+                        croak("Invalid assignment");
+                }
+                return left;
+        };
+
+        function expression(commas, no_in) {
+                if (arguments.length == 0)
+                        commas = true;
+                var expr = maybe_assign(no_in);
+                if (commas && is("punc", ",")) {
+                        next();
+                        return as("seq", expr, expression(true, no_in));
+                }
+                return expr;
+        };
+
+        function in_loop(cont) {
+                try {
+                        ++S.in_loop;
+                        return cont();
+                } finally {
+                        --S.in_loop;
+                }
+        };
+
+        return as("toplevel", (function(a){
+                while (!is("eof"))
+                        a.push(statement());
+                return a;
+        })([]));
+
+};
+
+/* -----[ Utilities ]----- */
+
+function curry(f) {
+        var args = slice(arguments, 1);
+        return function() { return f.apply(this, args.concat(slice(arguments))); };
+};
+
+function prog1(ret) {
+        if (ret instanceof Function)
+                ret = ret();
+        for (var i = 1, n = arguments.length; --n > 0; ++i)
+                arguments[i]();
+        return ret;
+};
+
+function array_to_hash(a) {
+        var ret = {};
+        for (var i = 0; i < a.length; ++i)
+                ret[a[i]] = true;
+        return ret;
+};
+
+function slice(a, start) {
+        return Array.prototype.slice.call(a, start == null ? 0 : start);
+};
+
+function characters(str) {
+        return str.split("");
+};
+
+function member(name, array) {
+        for (var i = array.length; --i >= 0;)
+                if (array[i] === name)
+                        return true;
+        return false;
+};
+
+function HOP(obj, prop) {
+        return Object.prototype.hasOwnProperty.call(obj, prop);
+};
+
+var warn = function() {};
+
+/* -----[ Exports ]----- */
+
+exports.tokenizer = tokenizer;
+exports.parse = parse;
+exports.slice = slice;
+exports.curry = curry;
+exports.member = member;
+exports.array_to_hash = array_to_hash;
+exports.PRECEDENCE = PRECEDENCE;
+exports.KEYWORDS_ATOM = KEYWORDS_ATOM;
+exports.RESERVED_WORDS = RESERVED_WORDS;
+exports.KEYWORDS = KEYWORDS;
+exports.ATOMIC_START_TOKEN = ATOMIC_START_TOKEN;
+exports.OPERATORS = OPERATORS;
+exports.is_alphanumeric_char = is_alphanumeric_char;
+exports.set_logger = function(logger) {
+        warn = logger;
+};
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/lib/process.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/lib/process.js
new file mode 100644
index 0000000..09cbc2a
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/lib/process.js
@@ -0,0 +1,1616 @@
+/***********************************************************************
+
+  A JavaScript tokenizer / parser / beautifier / compressor.
+
+  This version is suitable for Node.js.  With minimal changes (the
+  exports stuff) it should work on any JS platform.
+
+  This file implements some AST processors.  They work on data built
+  by parse-js.
+
+  Exported functions:
+
+    - ast_mangle(ast, options) -- mangles the variable/function names
+      in the AST.  Returns an AST.
+
+    - ast_squeeze(ast) -- employs various optimizations to make the
+      final generated code even smaller.  Returns an AST.
+
+    - gen_code(ast, options) -- generates JS code from the AST.  Pass
+      true (or an object, see the code for some options) as second
+      argument to get "pretty" (indented) code.
+
+  -------------------------------- (C) ---------------------------------
+
+                           Author: Mihai Bazon
+                         <mihai.bazon@gmail.com>
+                       http://mihai.bazon.net/blog
+
+  Distributed under the BSD license:
+
+    Copyright 2010 (c) Mihai Bazon <mihai.bazon@gmail.com>
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions
+    are met:
+
+        * Redistributions of source code must retain the above
+          copyright notice, this list of conditions and the following
+          disclaimer.
+
+        * Redistributions in binary form must reproduce the above
+          copyright notice, this list of conditions and the following
+          disclaimer in the documentation and/or other materials
+          provided with the distribution.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
+    EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+    PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
+    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+    OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+    TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+    THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+    SUCH DAMAGE.
+
+ ***********************************************************************/
+
+var jsp = require("./parse-js"),
+    slice = jsp.slice,
+    member = jsp.member,
+    PRECEDENCE = jsp.PRECEDENCE,
+    OPERATORS = jsp.OPERATORS;
+
+/* -----[ helper for AST traversal ]----- */
+
+function ast_walker(ast) {
+        function _vardefs(defs) {
+                return [ this[0], MAP(defs, function(def){
+                        var a = [ def[0] ];
+                        if (def.length > 1)
+                                a[1] = walk(def[1]);
+                        return a;
+                }) ];
+        };
+        var walkers = {
+                "string": function(str) {
+                        return [ this[0], str ];
+                },
+                "num": function(num) {
+                        return [ this[0], num ];
+                },
+                "name": function(name) {
+                        return [ this[0], name ];
+                },
+                "toplevel": function(statements) {
+                        return [ this[0], MAP(statements, walk) ];
+                },
+                "block": function(statements) {
+                        var out = [ this[0] ];
+                        if (statements != null)
+                                out.push(MAP(statements, walk));
+                        return out;
+                },
+                "var": _vardefs,
+                "const": _vardefs,
+                "try": function(t, c, f) {
+                        return [
+                                this[0],
+                                MAP(t, walk),
+                                c != null ? [ c[0], MAP(c[1], walk) ] : null,
+                                f != null ? MAP(f, walk) : null
+                        ];
+                },
+                "throw": function(expr) {
+                        return [ this[0], walk(expr) ];
+                },
+                "new": function(ctor, args) {
+                        return [ this[0], walk(ctor), MAP(args, walk) ];
+                },
+                "switch": function(expr, body) {
+                        return [ this[0], walk(expr), MAP(body, function(branch){
+                                return [ branch[0] ? walk(branch[0]) : null,
+                                         MAP(branch[1], walk) ];
+                        }) ];
+                },
+                "break": function(label) {
+                        return [ this[0], label ];
+                },
+                "continue": function(label) {
+                        return [ this[0], label ];
+                },
+                "conditional": function(cond, t, e) {
+                        return [ this[0], walk(cond), walk(t), walk(e) ];
+                },
+                "assign": function(op, lvalue, rvalue) {
+                        return [ this[0], op, walk(lvalue), walk(rvalue) ];
+                },
+                "dot": function(expr) {
+                        return [ this[0], walk(expr) ].concat(slice(arguments, 1));
+                },
+                "call": function(expr, args) {
+                        return [ this[0], walk(expr), MAP(args, walk) ];
+                },
+                "function": function(name, args, body) {
+                        return [ this[0], name, args.slice(), MAP(body, walk) ];
+                },
+                "defun": function(name, args, body) {
+                        return [ this[0], name, args.slice(), MAP(body, walk) ];
+                },
+                "if": function(conditional, t, e) {
+                        return [ this[0], walk(conditional), walk(t), walk(e) ];
+                },
+                "for": function(init, cond, step, block) {
+                        return [ this[0], walk(init), walk(cond), walk(step), walk(block) ];
+                },
+                "for-in": function(vvar, key, hash, block) {
+                        return [ this[0], walk(vvar), walk(key), walk(hash), walk(block) ];
+                },
+                "while": function(cond, block) {
+                        return [ this[0], walk(cond), walk(block) ];
+                },
+                "do": function(cond, block) {
+                        return [ this[0], walk(cond), walk(block) ];
+                },
+                "return": function(expr) {
+                        return [ this[0], walk(expr) ];
+                },
+                "binary": function(op, left, right) {
+                        return [ this[0], op, walk(left), walk(right) ];
+                },
+                "unary-prefix": function(op, expr) {
+                        return [ this[0], op, walk(expr) ];
+                },
+                "unary-postfix": function(op, expr) {
+                        return [ this[0], op, walk(expr) ];
+                },
+                "sub": function(expr, subscript) {
+                        return [ this[0], walk(expr), walk(subscript) ];
+                },
+                "object": function(props) {
+                        return [ this[0], MAP(props, function(p){
+                                return p.length == 2
+                                        ? [ p[0], walk(p[1]) ]
+                                        : [ p[0], walk(p[1]), p[2] ]; // get/set-ter
+                        }) ];
+                },
+                "regexp": function(rx, mods) {
+                        return [ this[0], rx, mods ];
+                },
+                "array": function(elements) {
+                        return [ this[0], MAP(elements, walk) ];
+                },
+                "stat": function(stat) {
+                        return [ this[0], walk(stat) ];
+                },
+                "seq": function() {
+                        return [ this[0] ].concat(MAP(slice(arguments), walk));
+                },
+                "label": function(name, block) {
+                        return [ this[0], name, walk(block) ];
+                },
+                "with": function(expr, block) {
+                        return [ this[0], walk(expr), walk(block) ];
+                },
+                "atom": function(name) {
+                        return [ this[0], name ];
+                }
+        };
+
+        var user = {};
+        var stack = [];
+        function walk(ast) {
+                if (ast == null)
+                        return null;
+                try {
+                        stack.push(ast);
+                        var type = ast[0];
+                        var gen = user[type];
+                        if (gen) {
+                                var ret = gen.apply(ast, ast.slice(1));
+                                if (ret != null)
+                                        return ret;
+                        }
+                        gen = walkers[type];
+                        return gen.apply(ast, ast.slice(1));
+                } finally {
+                        stack.pop();
+                }
+        };
+
+        function with_walkers(walkers, cont){
+                var save = {}, i;
+                for (i in walkers) if (HOP(walkers, i)) {
+                        save[i] = user[i];
+                        user[i] = walkers[i];
+                }
+                var ret = cont();
+                for (i in save) if (HOP(save, i)) {
+                        if (!save[i]) delete user[i];
+                        else user[i] = save[i];
+                }
+                return ret;
+        };
+
+        return {
+                walk: walk,
+                with_walkers: with_walkers,
+                parent: function() {
+                        return stack[stack.length - 2]; // last one is current node
+                },
+                stack: function() {
+                        return stack;
+                }
+        };
+};
+
+/* -----[ Scope and mangling ]----- */
+
+function Scope(parent) {
+        this.names = {};        // names defined in this scope
+        this.mangled = {};      // mangled names (orig.name => mangled)
+        this.rev_mangled = {};  // reverse lookup (mangled => orig.name)
+        this.cname = -1;        // current mangled name
+        this.refs = {};         // names referenced from this scope
+        this.uses_with = false; // will become TRUE if with() is detected in this or any subscopes
+        this.uses_eval = false; // will become TRUE if eval() is detected in this or any subscopes
+        this.parent = parent;   // parent scope
+        this.children = [];     // sub-scopes
+        if (parent) {
+                this.level = parent.level + 1;
+                parent.children.push(this);
+        } else {
+                this.level = 0;
+        }
+};
+
+var base54 = (function(){
+        var DIGITS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_";
+        return function(num) {
+                var ret = "";
+                do {
+                        ret = DIGITS.charAt(num % 54) + ret;
+                        num = Math.floor(num / 54);
+                } while (num > 0);
+                return ret;
+        };
+})();
+
+Scope.prototype = {
+        has: function(name) {
+                for (var s = this; s; s = s.parent)
+                        if (HOP(s.names, name))
+                                return s;
+        },
+        has_mangled: function(mname) {
+                for (var s = this; s; s = s.parent)
+                        if (HOP(s.rev_mangled, mname))
+                                return s;
+        },
+        toJSON: function() {
+                return {
+                        names: this.names,
+                        uses_eval: this.uses_eval,
+                        uses_with: this.uses_with
+                };
+        },
+
+        next_mangled: function() {
+                // we must be careful that the new mangled name:
+                //
+                // 1. doesn't shadow a mangled name from a parent
+                //    scope, unless we don't reference the original
+                //    name from this scope OR from any sub-scopes!
+                //    This will get slow.
+                //
+                // 2. doesn't shadow an original name from a parent
+                //    scope, in the event that the name is not mangled
+                //    in the parent scope and we reference that name
+                //    here OR IN ANY SUBSCOPES!
+                //
+                // 3. doesn't shadow a name that is referenced but not
+                //    defined (possibly global defined elsewhere).
+                for (;;) {
+                        var m = base54(++this.cname), prior;
+
+                        // case 1.
+                        prior = this.has_mangled(m);
+                        if (prior && this.refs[prior.rev_mangled[m]] === prior)
+                                continue;
+
+                        // case 2.
+                        prior = this.has(m);
+                        if (prior && prior !== this && this.refs[m] === prior && !prior.has_mangled(m))
+                                continue;
+
+                        // case 3.
+                        if (HOP(this.refs, m) && this.refs[m] == null)
+                                continue;
+
+                        // I got "do" once. :-/
+                        if (!is_identifier(m))
+                                continue;
+
+                        return m;
+                }
+        },
+        get_mangled: function(name, newMangle) {
+                if (this.uses_eval || this.uses_with) return name; // no mangle if eval or with is in use
+                var s = this.has(name);
+                if (!s) return name; // not in visible scope, no mangle
+                if (HOP(s.mangled, name)) return s.mangled[name]; // already mangled in this scope
+                if (!newMangle) return name;                      // not found and no mangling requested
+
+                var m = s.next_mangled();
+                s.rev_mangled[m] = name;
+                return s.mangled[name] = m;
+        },
+        define: function(name) {
+                if (name != null)
+                        return this.names[name] = name;
+        }
+};
+
+function ast_add_scope(ast) {
+
+        var current_scope = null;
+        var w = ast_walker(), walk = w.walk;
+        var having_eval = [];
+
+        function with_new_scope(cont) {
+                current_scope = new Scope(current_scope);
+                var ret = current_scope.body = cont();
+                ret.scope = current_scope;
+                current_scope = current_scope.parent;
+                return ret;
+        };
+
+        function define(name) {
+                return current_scope.define(name);
+        };
+
+        function reference(name) {
+                current_scope.refs[name] = true;
+        };
+
+        function _lambda(name, args, body) {
+                return [ this[0], this[0] == "defun" ? define(name) : name, args, with_new_scope(function(){
+                        MAP(args, define);
+                        return MAP(body, walk);
+                })];
+        };
+
+        return with_new_scope(function(){
+                // process AST
+                var ret = w.with_walkers({
+                        "function": _lambda,
+                        "defun": _lambda,
+                        "with": function(expr, block) {
+                                for (var s = current_scope; s; s = s.parent)
+                                        s.uses_with = true;
+                        },
+                        "var": function(defs) {
+                                MAP(defs, function(d){ define(d[0]) });
+                        },
+                        "const": function(defs) {
+                                MAP(defs, function(d){ define(d[0]) });
+                        },
+                        "try": function(t, c, f) {
+                                if (c != null) return [
+                                        this[0],
+                                        MAP(t, walk),
+                                        [ define(c[0]), MAP(c[1], walk) ],
+                                        f != null ? MAP(f, walk) : null
+                                ];
+                        },
+                        "name": function(name) {
+                                if (name == "eval")
+                                        having_eval.push(current_scope);
+                                reference(name);
+                        }
+                }, function(){
+                        return walk(ast);
+                });
+
+                // the reason why we need an additional pass here is
+                // that names can be used prior to their definition.
+
+                // scopes where eval was detected and their parents
+                // are marked with uses_eval, unless they define the
+                // "eval" name.
+                MAP(having_eval, function(scope){
+                        if (!scope.has("eval")) while (scope) {
+                                scope.uses_eval = true;
+                                scope = scope.parent;
+                        }
+                });
+
+                // for referenced names it might be useful to know
+                // their origin scope.  current_scope here is the
+                // toplevel one.
+                function fixrefs(scope, i) {
+                        // do children first; order shouldn't matter
+                        for (i = scope.children.length; --i >= 0;)
+                                fixrefs(scope.children[i]);
+                        for (i in scope.refs) if (HOP(scope.refs, i)) {
+                                // find origin scope and propagate the reference to origin
+                                for (var origin = scope.has(i), s = scope; s; s = s.parent) {
+                                        s.refs[i] = origin;
+                                        if (s === origin) break;
+                                }
+                        }
+                };
+                fixrefs(current_scope);
+
+                return ret;
+        });
+
+};
+
+/* -----[ mangle names ]----- */
+
+function ast_mangle(ast, options) {
+        var w = ast_walker(), walk = w.walk, scope;
+        options = options || {};
+
+        function get_mangled(name, newMangle) {
+                if (!options.toplevel && !scope.parent) return name; // don't mangle toplevel
+                if (options.except && member(name, options.except))
+                        return name;
+                return scope.get_mangled(name, newMangle);
+        };
+
+        function _lambda(name, args, body) {
+                if (name) name = get_mangled(name);
+                body = with_scope(body.scope, function(){
+                        args = MAP(args, function(name){ return get_mangled(name) });
+                        return MAP(body, walk);
+                });
+                return [ this[0], name, args, body ];
+        };
+
+        function with_scope(s, cont) {
+                var _scope = scope;
+                scope = s;
+                for (var i in s.names) if (HOP(s.names, i)) {
+                        get_mangled(i, true);
+                }
+                var ret = cont();
+                ret.scope = s;
+                scope = _scope;
+                return ret;
+        };
+
+        function _vardefs(defs) {
+                return [ this[0], MAP(defs, function(d){
+                        return [ get_mangled(d[0]), walk(d[1]) ];
+                }) ];
+        };
+
+        return w.with_walkers({
+                "function": _lambda,
+                "defun": function() {
+                        // move function declarations to the top when
+                        // they are not in some block.
+                        var ast = _lambda.apply(this, arguments);
+                        switch (w.parent()[0]) {
+                            case "toplevel":
+                            case "function":
+                            case "defun":
+                                return MAP.at_top(ast);
+                        }
+                        return ast;
+                },
+                "var": _vardefs,
+                "const": _vardefs,
+                "name": function(name) {
+                        return [ this[0], get_mangled(name) ];
+                },
+                "try": function(t, c, f) {
+                        return [ this[0],
+                                 MAP(t, walk),
+                                 c != null ? [ get_mangled(c[0]), MAP(c[1], walk) ] : null,
+                                 f != null ? MAP(f, walk) : null ];
+                },
+                "toplevel": function(body) {
+                        var self = this;
+                        return with_scope(self.scope, function(){
+                                return [ self[0], MAP(body, walk) ];
+                        });
+                }
+        }, function() {
+                return walk(ast_add_scope(ast));
+        });
+};
+
+/* -----[
+   - compress foo["bar"] into foo.bar,
+   - remove block brackets {} where possible
+   - join consecutive var declarations
+   - various optimizations for IFs:
+     - if (cond) foo(); else bar();  ==>  cond?foo():bar();
+     - if (cond) foo();  ==>  cond&&foo();
+     - if (foo) return bar(); else return baz();  ==> return foo?bar():baz(); // also for throw
+     - if (foo) return bar(); else something();  ==> {if(foo)return bar();something()}
+   ]----- */
+
+var warn = function(){};
+
+function best_of(ast1, ast2) {
+        return gen_code(ast1).length > gen_code(ast2[0] == "stat" ? ast2[1] : ast2).length ? ast2 : ast1;
+};
+
+function last_stat(b) {
+        if (b[0] == "block" && b[1] && b[1].length > 0)
+                return b[1][b[1].length - 1];
+        return b;
+}
+
+function aborts(t) {
+        if (t) {
+                t = last_stat(t);
+                if (t[0] == "return" || t[0] == "break" || t[0] == "continue" || t[0] == "throw")
+                        return true;
+        }
+};
+
+function boolean_expr(expr) {
+        return ( (expr[0] == "unary-prefix"
+                  && member(expr[1], [ "!", "delete" ])) ||
+
+                 (expr[0] == "binary"
+                  && member(expr[1], [ "in", "instanceof", "==", "!=", "===", "!==", "<", "<=", ">=", ">" ])) ||
+
+                 (expr[0] == "binary"
+                  && member(expr[1], [ "&&", "||" ])
+                  && boolean_expr(expr[2])
+                  && boolean_expr(expr[3])) ||
+
+                 (expr[0] == "conditional"
+                  && boolean_expr(expr[2])
+                  && boolean_expr(expr[3])) ||
+
+                 (expr[0] == "assign"
+                  && expr[1] === true
+                  && boolean_expr(expr[3])) ||
+
+                 (expr[0] == "seq"
+                  && boolean_expr(expr[expr.length - 1]))
+               );
+};
+
+function make_conditional(c, t, e) {
+        if (c[0] == "unary-prefix" && c[1] == "!") {
+                return e ? [ "conditional", c[2], e, t ] : [ "binary", "||", c[2], t ];
+        } else {
+                return e ? [ "conditional", c, t, e ] : [ "binary", "&&", c, t ];
+        }
+};
+
+function empty(b) {
+        return !b || (b[0] == "block" && (!b[1] || b[1].length == 0));
+};
+
+function is_string(node) {
+        return (node[0] == "string" ||
+                node[0] == "unary-prefix" && node[1] == "typeof" ||
+                node[0] == "binary" && node[1] == "+" &&
+                (is_string(node[2]) || is_string(node[3])));
+};
+
+var when_constant = (function(){
+
+        var $NOT_CONSTANT = {};
+
+        // this can only evaluate constant expressions.  If it finds anything
+        // not constant, it throws $NOT_CONSTANT.
+        function evaluate(expr) {
+                switch (expr[0]) {
+                    case "string":
+                    case "num":
+                        return expr[1];
+                    case "name":
+                    case "atom":
+                        switch (expr[1]) {
+                            case "true": return true;
+                            case "false": return false;
+                        }
+                        break;
+                    case "unary-prefix":
+                        switch (expr[1]) {
+                            case "!": return !evaluate(expr[2]);
+                            case "typeof": return typeof evaluate(expr[2]);
+                            case "~": return ~evaluate(expr[2]);
+                            case "-": return -evaluate(expr[2]);
+                            case "+": return +evaluate(expr[2]);
+                        }
+                        break;
+                    case "binary":
+                        var left = expr[2], right = expr[3];
+                        switch (expr[1]) {
+                            case "&&"         : return evaluate(left) &&         evaluate(right);
+                            case "||"         : return evaluate(left) ||         evaluate(right);
+                            case "|"          : return evaluate(left) |          evaluate(right);
+                            case "&"          : return evaluate(left) &          evaluate(right);
+                            case "^"          : return evaluate(left) ^          evaluate(right);
+                            case "+"          : return evaluate(left) +          evaluate(right);
+                            case "*"          : return evaluate(left) *          evaluate(right);
+                            case "/"          : return evaluate(left) /          evaluate(right);
+                            case "-"          : return evaluate(left) -          evaluate(right);
+                            case "<<"         : return evaluate(left) <<         evaluate(right);
+                            case ">>"         : return evaluate(left) >>         evaluate(right);
+                            case ">>>"        : return evaluate(left) >>>        evaluate(right);
+                            case "=="         : return evaluate(left) ==         evaluate(right);
+                            case "==="        : return evaluate(left) ===        evaluate(right);
+                            case "!="         : return evaluate(left) !=         evaluate(right);
+                            case "!=="        : return evaluate(left) !==        evaluate(right);
+                            case "<"          : return evaluate(left) <          evaluate(right);
+                            case "<="         : return evaluate(left) <=         evaluate(right);
+                            case ">"          : return evaluate(left) >          evaluate(right);
+                            case ">="         : return evaluate(left) >=         evaluate(right);
+                            case "in"         : return evaluate(left) in         evaluate(right);
+                            case "instanceof" : return evaluate(left) instanceof evaluate(right);
+                        }
+                }
+                throw $NOT_CONSTANT;
+        };
+
+        return function(expr, yes, no) {
+                try {
+                        var val = evaluate(expr), ast;
+                        switch (typeof val) {
+                            case "string": ast =  [ "string", val ]; break;
+                            case "number": ast =  [ "num", val ]; break;
+                            case "boolean": ast =  [ "name", String(val) ]; break;
+                            default: throw new Error("Can't handle constant of type: " + (typeof val));
+                        }
+                        return yes.call(expr, ast, val);
+                } catch(ex) {
+                        if (ex === $NOT_CONSTANT) {
+                                if (expr[0] == "binary"
+                                    && (expr[1] == "===" || expr[1] == "!==")
+                                    && ((is_string(expr[2]) && is_string(expr[3]))
+                                        || (boolean_expr(expr[2]) && boolean_expr(expr[3])))) {
+                                        expr[1] = expr[1].substr(0, 2);
+                                }
+                                return no ? no.call(expr, expr) : null;
+                        }
+                        else throw ex;
+                }
+        };
+
+})();
+
+function warn_unreachable(ast) {
+        if (!empty(ast))
+                warn("Dropping unreachable code: " + gen_code(ast, true));
+};
+
+function ast_squeeze(ast, options) {
+        options = defaults(options, {
+                make_seqs   : true,
+                dead_code   : true,
+                keep_comps  : true,
+                no_warnings : false
+        });
+
+        var w = ast_walker(), walk = w.walk, scope;
+
+        function negate(c) {
+                var not_c = [ "unary-prefix", "!", c ];
+                switch (c[0]) {
+                    case "unary-prefix":
+                        return c[1] == "!" && boolean_expr(c[2]) ? c[2] : not_c;
+                    case "seq":
+                        c = slice(c);
+                        c[c.length - 1] = negate(c[c.length - 1]);
+                        return c;
+                    case "conditional":
+                        return best_of(not_c, [ "conditional", c[1], negate(c[2]), negate(c[3]) ]);
+                    case "binary":
+                        var op = c[1], left = c[2], right = c[3];
+                        if (!options.keep_comps) switch (op) {
+                            case "<="  : return [ "binary", ">", left, right ];
+                            case "<"   : return [ "binary", ">=", left, right ];
+                            case ">="  : return [ "binary", "<", left, right ];
+                            case ">"   : return [ "binary", "<=", left, right ];
+                        }
+                        switch (op) {
+                            case "=="  : return [ "binary", "!=", left, right ];
+                            case "!="  : return [ "binary", "==", left, right ];
+                            case "===" : return [ "binary", "!==", left, right ];
+                            case "!==" : return [ "binary", "===", left, right ];
+                            case "&&"  : return best_of(not_c, [ "binary", "||", negate(left), negate(right) ]);
+                            case "||"  : return best_of(not_c, [ "binary", "&&", negate(left), negate(right) ]);
+                        }
+                        break;
+                }
+                return not_c;
+        };
+
+        function with_scope(s, cont) {
+                var _scope = scope;
+                scope = s;
+                var ret = cont();
+                ret.scope = s;
+                scope = _scope;
+                return ret;
+        };
+
+        function rmblock(block) {
+                if (block != null && block[0] == "block" && block[1]) {
+                        if (block[1].length == 1)
+                                block = block[1][0];
+                        else if (block[1].length == 0)
+                                block = [ "block" ];
+                }
+                return block;
+        };
+
+        function _lambda(name, args, body) {
+                return [ this[0], name, args, with_scope(body.scope, function(){
+                        return tighten(MAP(body, walk), "lambda");
+                }) ];
+        };
+
+        // we get here for blocks that have been already transformed.
+        // this function does a few things:
+        // 1. discard useless blocks
+        // 2. join consecutive var declarations
+        // 3. remove obviously dead code
+        // 4. transform consecutive statements using the comma operator
+        // 5. if block_type == "lambda" and it detects constructs like if(foo) return ... - rewrite like if (!foo) { ... }
+        function tighten(statements, block_type) {
+                statements = statements.reduce(function(a, stat){
+                        if (stat[0] == "block") {
+                                if (stat[1]) {
+                                        a.push.apply(a, stat[1]);
+                                }
+                        } else {
+                                a.push(stat);
+                        }
+                        return a;
+                }, []);
+
+                statements = (function(a, prev){
+                        statements.forEach(function(cur){
+                                if (prev && ((cur[0] == "var" && prev[0] == "var") ||
+                                             (cur[0] == "const" && prev[0] == "const"))) {
+                                        prev[1] = prev[1].concat(cur[1]);
+                                } else {
+                                        a.push(cur);
+                                        prev = cur;
+                                }
+                        });
+                        return a;
+                })([]);
+
+                if (options.dead_code) statements = (function(a, has_quit){
+                        statements.forEach(function(st){
+                                if (has_quit) {
+                                        if (member(st[0], [ "function", "defun" , "var", "const" ])) {
+                                                a.push(st);
+                                        }
+                                        else if (!options.no_warnings)
+                                                warn_unreachable(st);
+                                }
+                                else {
+                                        a.push(st);
+                                        if (member(st[0], [ "return", "throw", "break", "continue" ]))
+                                                has_quit = true;
+                                }
+                        });
+                        return a;
+                })([]);
+
+                if (options.make_seqs) statements = (function(a, prev) {
+                        statements.forEach(function(cur){
+                                if (prev && prev[0] == "stat" && cur[0] == "stat") {
+                                        prev[1] = [ "seq", prev[1], cur[1] ];
+                                } else {
+                                        a.push(cur);
+                                        prev = cur;
+                                }
+                        });
+                        return a;
+                })([]);
+
+                if (block_type == "lambda") statements = (function(i, a, stat){
+                        while (i < statements.length) {
+                                stat = statements[i++];
+                                if (stat[0] == "if" && !stat[3]) {
+                                        if (stat[2][0] == "return" && stat[2][1] == null) {
+                                                a.push(make_if(negate(stat[1]), [ "block", statements.slice(i) ]));
+                                                break;
+                                        }
+                                        var last = last_stat(stat[2]);
+                                        if (last[0] == "return" && last[1] == null) {
+                                                a.push(make_if(stat[1], [ "block", stat[2][1].slice(0, -1) ], [ "block", statements.slice(i) ]));
+                                                break;
+                                        }
+                                }
+                                a.push(stat);
+                        }
+                        return a;
+                })(0, []);
+
+                return statements;
+        };
+
+        function make_if(c, t, e) {
+                return when_constant(c, function(ast, val){
+                        if (val) {
+                                warn_unreachable(e);
+                                return t;
+                        } else {
+                                warn_unreachable(t);
+                                return e;
+                        }
+                }, function() {
+                        return make_real_if(c, t, e);
+                });
+        };
+
+        function make_real_if(c, t, e) {
+                c = walk(c);
+                t = walk(t);
+                e = walk(e);
+
+                if (empty(t)) {
+                        c = negate(c);
+                        t = e;
+                        e = null;
+                } else if (empty(e)) {
+                        e = null;
+                } else {
+                        // if we have both else and then, maybe it makes sense to switch them?
+                        (function(){
+                                var a = gen_code(c);
+                                var n = negate(c);
+                                var b = gen_code(n);
+                                if (b.length < a.length) {
+                                        var tmp = t;
+                                        t = e;
+                                        e = tmp;
+                                        c = n;
+                                }
+                        })();
+                }
+                if (empty(e) && empty(t))
+                        return [ "stat", c ];
+                var ret = [ "if", c, t, e ];
+                if (t[0] == "if" && empty(t[3]) && empty(e)) {
+                        ret = best_of(ret, walk([ "if", [ "binary", "&&", c, t[1] ], t[2] ]));
+                }
+                else if (t[0] == "stat") {
+                        if (e) {
+                                if (e[0] == "stat") {
+                                        ret = best_of(ret, [ "stat", make_conditional(c, t[1], e[1]) ]);
+                                }
+                        }
+                        else {
+                                ret = best_of(ret, [ "stat", make_conditional(c, t[1]) ]);
+                        }
+                }
+                else if (e && t[0] == e[0] && (t[0] == "return" || t[0] == "throw") && t[1] && e[1]) {
+                        ret = best_of(ret, [ t[0], make_conditional(c, t[1], e[1] ) ]);
+                }
+                else if (e && aborts(t)) {
+                        ret = [ [ "if", c, t ] ];
+                        if (e[0] == "block") {
+                                if (e[1]) ret = ret.concat(e[1]);
+                        }
+                        else {
+                                ret.push(e);
+                        }
+                        ret = walk([ "block", ret ]);
+                }
+                else if (t && aborts(e)) {
+                        ret = [ [ "if", negate(c), e ] ];
+                        if (t[0] == "block") {
+                                if (t[1]) ret = ret.concat(t[1]);
+                        } else {
+                                ret.push(t);
+                        }
+                        ret = walk([ "block", ret ]);
+                }
+                return ret;
+        };
+
+        function _do_while(cond, body) {
+                return when_constant(cond, function(cond, val){
+                        if (!val) {
+                                warn_unreachable(body);
+                                return [ "block" ];
+                        } else {
+                                return [ "for", null, null, null, walk(body) ];
+                        }
+                });
+        };
+
+        return w.with_walkers({
+                "sub": function(expr, subscript) {
+                        if (subscript[0] == "string") {
+                                var name = subscript[1];
+                                if (is_identifier(name))
+                                        return [ "dot", walk(expr), name ];
+                                else if (/^[1-9][0-9]*$/.test(name) || name === "0")
+                                        return [ "sub", walk(expr), [ "num", parseInt(name, 10) ] ];
+                        }
+                },
+                "if": make_if,
+                "toplevel": function(body) {
+                        return [ "toplevel", with_scope(this.scope, function(){
+                                return tighten(MAP(body, walk));
+                        }) ];
+                },
+                "switch": function(expr, body) {
+                        var last = body.length - 1;
+                        return [ "switch", walk(expr), MAP(body, function(branch, i){
+                                var block = tighten(MAP(branch[1], walk));
+                                if (i == last && block.length > 0) {
+                                        var node = block[block.length - 1];
+                                        if (node[0] == "break" && !node[1])
+                                                block.pop();
+                                }
+                                return [ branch[0] ? walk(branch[0]) : null, block ];
+                        }) ];
+                },
+                "function": function() {
+                        var ret = _lambda.apply(this, arguments);
+                        if (ret[1] && !HOP(scope.refs, ret[1])) {
+                                ret[1] = null;
+                        }
+                        return ret;
+                },
+                "defun": _lambda,
+                "block": function(body) {
+                        if (body) return rmblock([ "block", tighten(MAP(body, walk)) ]);
+                },
+                "binary": function(op, left, right) {
+                        return when_constant([ "binary", op, walk(left), walk(right) ], function yes(c){
+                                return best_of(walk(c), this);
+                        }, function no() {
+                                return this;
+                        });
+                },
+                "conditional": function(c, t, e) {
+                        return make_conditional(walk(c), walk(t), walk(e));
+                },
+                "try": function(t, c, f) {
+                        return [
+                                "try",
+                                tighten(MAP(t, walk)),
+                                c != null ? [ c[0], tighten(MAP(c[1], walk)) ] : null,
+                                f != null ? tighten(MAP(f, walk)) : null
+                        ];
+                },
+                "unary-prefix": function(op, expr) {
+                        expr = walk(expr);
+                        var ret = [ "unary-prefix", op, expr ];
+                        if (op == "!")
+                                ret = best_of(ret, negate(expr));
+                        return when_constant(ret, function(ast, val){
+                                return walk(ast); // it's either true or false, so minifies to !0 or !1
+                        }, function() { return ret });
+                },
+                "name": function(name) {
+                        switch (name) {
+                            case "true": return [ "unary-prefix", "!", [ "num", 0 ]];
+                            case "false": return [ "unary-prefix", "!", [ "num", 1 ]];
+                        }
+                },
+                "new": function(ctor, args) {
+                        if (ctor[0] == "name" && ctor[1] == "Array" && !scope.has("Array")) {
+                                if (args.length != 1) {
+                                        return [ "array", args ];
+                                } else {
+                                        return [ "call", [ "name", "Array" ], args ];
+                                }
+                        }
+                },
+                "call": function(expr, args) {
+                        if (expr[0] == "name" && expr[1] == "Array" && args.length != 1 && !scope.has("Array")) {
+                                return [ "array", args ];
+                        }
+                },
+                "while": _do_while,
+                "do": _do_while
+        }, function() {
+                return walk(ast_add_scope(ast));
+        });
+};
+
+/* -----[ re-generate code from the AST ]----- */
+
+var DOT_CALL_NO_PARENS = jsp.array_to_hash([
+        "name",
+        "array",
+        "object",
+        "string",
+        "dot",
+        "sub",
+        "call",
+        "regexp"
+]);
+
+function make_string(str, ascii_only) {
+        var dq = 0, sq = 0;
+        str = str.replace(/[\\\b\f\n\r\t\x22\x27\u2028\u2029]/g, function(s){
+                switch (s) {
+                    case "\\": return "\\\\";
+                    case "\b": return "\\b";
+                    case "\f": return "\\f";
+                    case "\n": return "\\n";
+                    case "\r": return "\\r";
+                    case "\t": return "\\t";
+                    case "\u2028": return "\\u2028";
+                    case "\u2029": return "\\u2029";
+                    case '"': ++dq; return '"';
+                    case "'": ++sq; return "'";
+                }
+                return s;
+        });
+        if (ascii_only) str = to_ascii(str);
+        if (dq > sq) return "'" + str.replace(/\x27/g, "\\'") + "'";
+        else return '"' + str.replace(/\x22/g, '\\"') + '"';
+};
+
+function to_ascii(str) {
+        return str.replace(/[\u0080-\uffff]/g, function(ch) {
+                var code = ch.charCodeAt(0).toString(16);
+                while (code.length < 4) code = "0" + code;
+                return "\\u" + code;
+        });
+};
+
+function gen_code(ast, options) {
+        options = defaults(options, {
+                indent_start : 0,
+                indent_level : 4,
+                quote_keys   : false,
+                space_colon  : false,
+                beautify     : false,
+                ascii_only   : false
+        });
+        var beautify = !!options.beautify;
+        var indentation = 0,
+            newline = beautify ? "\n" : "",
+            space = beautify ? " " : "";
+
+        function encode_string(str) {
+                return make_string(str, options.ascii_only);
+        };
+
+        function make_name(name) {
+                name = name.toString();
+                if (options.ascii_only)
+                        name = to_ascii(name);
+                return name;
+        };
+
+        function indent(line) {
+                if (line == null)
+                        line = "";
+                if (beautify)
+                        line = repeat_string(" ", options.indent_start + indentation * options.indent_level) + line;
+                return line;
+        };
+
+        function with_indent(cont, incr) {
+                if (incr == null) incr = 1;
+                indentation += incr;
+                try { return cont.apply(null, slice(arguments, 1)); }
+                finally { indentation -= incr; }
+        };
+
+        function add_spaces(a) {
+                if (beautify)
+                        return a.join(" ");
+                var b = [];
+                for (var i = 0; i < a.length; ++i) {
+                        var next = a[i + 1];
+                        b.push(a[i]);
+                        if (next &&
+                            ((/[a-z0-9_\x24]$/i.test(a[i].toString()) && /^[a-z0-9_\x24]/i.test(next.toString())) ||
+                             (/[\+\-]$/.test(a[i].toString()) && /^[\+\-]/.test(next.toString())))) {
+                                b.push(" ");
+                        }
+                }
+                return b.join("");
+        };
+
+        function add_commas(a) {
+                return a.join("," + space);
+        };
+
+        function parenthesize(expr) {
+                var gen = make(expr);
+                for (var i = 1; i < arguments.length; ++i) {
+                        var el = arguments[i];
+                        if ((el instanceof Function && el(expr)) || expr[0] == el)
+                                return "(" + gen + ")";
+                }
+                return gen;
+        };
+
+        function best_of(a) {
+                if (a.length == 1) {
+                        return a[0];
+                }
+                if (a.length == 2) {
+                        var b = a[1];
+                        a = a[0];
+                        return a.length <= b.length ? a : b;
+                }
+                return best_of([ a[0], best_of(a.slice(1)) ]);
+        };
+
+        function needs_parens(expr) {
+                if (expr[0] == "function" || expr[0] == "object") {
+                        // dot/call on a literal function requires the
+                        // function literal itself to be parenthesized
+                        // only if it's the first "thing" in a
+                        // statement.  This means that the parent is
+                        // "stat", but it could also be a "seq" and
+                        // we're the first in this "seq" and the
+                        // parent is "stat", and so on.  Messy stuff,
+                        // but it worths the trouble.
+                        var a = slice($stack), self = a.pop(), p = a.pop();
+                        while (p) {
+                                if (p[0] == "stat") return true;
+                                if (((p[0] == "seq" || p[0] == "call" || p[0] == "dot" || p[0] == "sub" || p[0] == "conditional") && p[1] === self) ||
+                                    ((p[0] == "binary" || p[0] == "assign" || p[0] == "unary-postfix") && p[2] === self)) {
+                                        self = p;
+                                        p = a.pop();
+                                } else {
+                                        return false;
+                                }
+                        }
+                }
+                return !HOP(DOT_CALL_NO_PARENS, expr[0]);
+        };
+
+        function make_num(num) {
+                var str = num.toString(10), a = [ str.replace(/^0\./, ".") ], m;
+                if (Math.floor(num) === num) {
+                        a.push("0x" + num.toString(16).toLowerCase(), // probably pointless
+                               "0" + num.toString(8)); // same.
+                        if ((m = /^(.*?)(0+)$/.exec(num))) {
+                                a.push(m[1] + "e" + m[2].length);
+                        }
+                } else if ((m = /^0?\.(0+)(.*)$/.exec(num))) {
+                        a.push(m[2] + "e-" + (m[1].length + m[2].length),
+                               str.substr(str.indexOf(".")));
+                }
+                return best_of(a);
+        };
+
+        var generators = {
+                "string": encode_string,
+                "num": make_num,
+                "name": make_name,
+                "toplevel": function(statements) {
+                        return make_block_statements(statements)
+                                .join(newline + newline);
+                },
+                "block": make_block,
+                "var": function(defs) {
+                        return "var " + add_commas(MAP(defs, make_1vardef)) + ";";
+                },
+                "const": function(defs) {
+                        return "const " + add_commas(MAP(defs, make_1vardef)) + ";";
+                },
+                "try": function(tr, ca, fi) {
+                        var out = [ "try", make_block(tr) ];
+                        if (ca) out.push("catch", "(" + ca[0] + ")", make_block(ca[1]));
+                        if (fi) out.push("finally", make_block(fi));
+                        return add_spaces(out);
+                },
+                "throw": function(expr) {
+                        return add_spaces([ "throw", make(expr) ]) + ";";
+                },
+                "new": function(ctor, args) {
+                        args = args.length > 0 ? "(" + add_commas(MAP(args, make)) + ")" : "";
+                        return add_spaces([ "new", parenthesize(ctor, "seq", "binary", "conditional", "assign", function(expr){
+                                var w = ast_walker(), has_call = {};
+                                try {
+                                        w.with_walkers({
+                                                "call": function() { throw has_call },
+                                                "function": function() { return this }
+                                        }, function(){
+                                                w.walk(expr);
+                                        });
+                                } catch(ex) {
+                                        if (ex === has_call)
+                                                return true;
+                                        throw ex;
+                                }
+                        }) + args ]);
+                },
+                "switch": function(expr, body) {
+                        return add_spaces([ "switch", "(" + make(expr) + ")", make_switch_block(body) ]);
+                },
+                "break": function(label) {
+                        var out = "break";
+                        if (label != null)
+                                out += " " + make_name(label);
+                        return out + ";";
+                },
+                "continue": function(label) {
+                        var out = "continue";
+                        if (label != null)
+                                out += " " + make_name(label);
+                        return out + ";";
+                },
+                "conditional": function(co, th, el) {
+                        return add_spaces([ parenthesize(co, "assign", "seq", "conditional"), "?",
+                                            parenthesize(th, "seq"), ":",
+                                            parenthesize(el, "seq") ]);
+                },
+                "assign": function(op, lvalue, rvalue) {
+                        if (op && op !== true) op += "=";
+                        else op = "=";
+                        return add_spaces([ make(lvalue), op, parenthesize(rvalue, "seq") ]);
+                },
+                "dot": function(expr) {
+                        var out = make(expr), i = 1;
+                        if (expr[0] == "num") {
+                                if (!/\./.test(expr[1]))
+                                        out += ".";
+                        } else if (needs_parens(expr))
+                                out = "(" + out + ")";
+                        while (i < arguments.length)
+                                out += "." + make_name(arguments[i++]);
+                        return out;
+                },
+                "call": function(func, args) {
+                        var f = make(func);
+                        if (needs_parens(func))
+                                f = "(" + f + ")";
+                        return f + "(" + add_commas(MAP(args, function(expr){
+                                return parenthesize(expr, "seq");
+                        })) + ")";
+                },
+                "function": make_function,
+                "defun": make_function,
+                "if": function(co, th, el) {
+                        var out = [ "if", "(" + make(co) + ")", el ? make_then(th) : make(th) ];
+                        if (el) {
+                                out.push("else", make(el));
+                        }
+                        return add_spaces(out);
+                },
+                "for": function(init, cond, step, block) {
+                        var out = [ "for" ];
+                        init = (init != null ? make(init) : "").replace(/;*\s*$/, ";" + space);
+                        cond = (cond != null ? make(cond) : "").replace(/;*\s*$/, ";" + space);
+                        step = (step != null ? make(step) : "").replace(/;*\s*$/, "");
+                        var args = init + cond + step;
+                        if (args == "; ; ") args = ";;";
+                        out.push("(" + args + ")", make(block));
+                        return add_spaces(out);
+                },
+                "for-in": function(vvar, key, hash, block) {
+                        return add_spaces([ "for", "(" +
+                                            (vvar ? make(vvar).replace(/;+$/, "") : make(key)),
+                                            "in",
+                                            make(hash) + ")", make(block) ]);
+                },
+                "while": function(condition, block) {
+                        return add_spaces([ "while", "(" + make(condition) + ")", make(block) ]);
+                },
+                "do": function(condition, block) {
+                        return add_spaces([ "do", make(block), "while", "(" + make(condition) + ")" ]) + ";";
+                },
+                "return": function(expr) {
+                        var out = [ "return" ];
+                        if (expr != null) out.push(make(expr));
+                        return add_spaces(out) + ";";
+                },
+                "binary": function(operator, lvalue, rvalue) {
+                        var left = make(lvalue), right = make(rvalue);
+                        // XXX: I'm pretty sure other cases will bite here.
+                        //      we need to be smarter.
+                        //      adding parens all the time is the safest bet.
+                        if (member(lvalue[0], [ "assign", "conditional", "seq" ]) ||
+                            lvalue[0] == "binary" && PRECEDENCE[operator] > PRECEDENCE[lvalue[1]]) {
+                                left = "(" + left + ")";
+                        }
+                        if (member(rvalue[0], [ "assign", "conditional", "seq" ]) ||
+                            rvalue[0] == "binary" && PRECEDENCE[operator] >= PRECEDENCE[rvalue[1]] &&
+                            !(rvalue[1] == operator && member(operator, [ "&&", "||", "*" ]))) {
+                                right = "(" + right + ")";
+                        }
+                        return add_spaces([ left, operator, right ]);
+                },
+                "unary-prefix": function(operator, expr) {
+                        var val = make(expr);
+                        if (!(expr[0] == "num" || (expr[0] == "unary-prefix" && !HOP(OPERATORS, operator + expr[1])) || !needs_parens(expr)))
+                                val = "(" + val + ")";
+                        return operator + (jsp.is_alphanumeric_char(operator.charAt(0)) ? " " : "") + val;
+                },
+                "unary-postfix": function(operator, expr) {
+                        var val = make(expr);
+                        if (!(expr[0] == "num" || (expr[0] == "unary-postfix" && !HOP(OPERATORS, operator + expr[1])) || !needs_parens(expr)))
+                                val = "(" + val + ")";
+                        return val + operator;
+                },
+                "sub": function(expr, subscript) {
+                        var hash = make(expr);
+                        if (needs_parens(expr))
+                                hash = "(" + hash + ")";
+                        return hash + "[" + make(subscript) + "]";
+                },
+                "object": function(props) {
+                        if (props.length == 0)
+                                return "{}";
+                        return "{" + newline + with_indent(function(){
+                                return MAP(props, function(p){
+                                        if (p.length == 3) {
+                                                // getter/setter.  The name is in p[0], the arg.list in p[1][2], the
+                                                // body in p[1][3] and type ("get" / "set") in p[2].
+                                                return indent(make_function(p[0], p[1][2], p[1][3], p[2]));
+                                        }
+                                        var key = p[0], val = make(p[1]);
+                                        if (options.quote_keys) {
+                                                key = encode_string(key);
+                                        } else if ((typeof key == "number" || !beautify && +key + "" == key)
+                                                   && parseFloat(key) >= 0) {
+                                                key = make_num(+key);
+                                        } else if (!is_identifier(key)) {
+                                                key = encode_string(key);
+                                        }
+                                        return indent(add_spaces(beautify && options.space_colon
+                                                                 ? [ key, ":", val ]
+                                                                 : [ key + ":", val ]));
+                                }).join("," + newline);
+                        }) + newline + indent("}");
+                },
+                "regexp": function(rx, mods) {
+                        return "/" + rx + "/" + mods;
+                },
+                "array": function(elements) {
+                        if (elements.length == 0) return "[]";
+                        return add_spaces([ "[", add_commas(MAP(elements, function(el){
+                                if (!beautify && el[0] == "atom" && el[1] == "undefined") return "";
+                                return parenthesize(el, "seq");
+                        })), "]" ]);
+                },
+                "stat": function(stmt) {
+                        return make(stmt).replace(/;*\s*$/, ";");
+                },
+                "seq": function() {
+                        return add_commas(MAP(slice(arguments), make));
+                },
+                "label": function(name, block) {
+                        return add_spaces([ make_name(name), ":", make(block) ]);
+                },
+                "with": function(expr, block) {
+                        return add_spaces([ "with", "(" + make(expr) + ")", make(block) ]);
+                },
+                "atom": function(name) {
+                        return make_name(name);
+                }
+        };
+
+        // The squeezer replaces "block"-s that contain only a single
+        // statement with the statement itself; technically, the AST
+        // is correct, but this can create problems when we output an
+        // IF having an ELSE clause where the THEN clause ends in an
+        // IF *without* an ELSE block (then the outer ELSE would refer
+        // to the inner IF).  This function checks for this case and
+        // adds the block brackets if needed.
+        function make_then(th) {
+                if (th[0] == "do") {
+                        // https://github.com/mishoo/UglifyJS/issues/#issue/57
+                        // IE croaks with "syntax error" on code like this:
+                        //     if (foo) do ... while(cond); else ...
+                        // we need block brackets around do/while
+                        return make([ "block", [ th ]]);
+                }
+                var b = th;
+                while (true) {
+                        var type = b[0];
+                        if (type == "if") {
+                                if (!b[3])
+                                        // no else, we must add the block
+                                        return make([ "block", [ th ]]);
+                                b = b[3];
+                        }
+                        else if (type == "while" || type == "do") b = b[2];
+                        else if (type == "for" || type == "for-in") b = b[4];
+                        else break;
+                }
+                return make(th);
+        };
+
+        function make_function(name, args, body, keyword) {
+                var out = keyword || "function";
+                if (name) {
+                        out += " " + make_name(name);
+                }
+                out += "(" + add_commas(MAP(args, make_name)) + ")";
+                return add_spaces([ out, make_block(body) ]);
+        };
+
+        function make_block_statements(statements) {
+                for (var a = [], last = statements.length - 1, i = 0; i <= last; ++i) {
+                        var stat = statements[i];
+                        var code = make(stat);
+                        if (code != ";") {
+                                if (!beautify && i == last) {
+                                        if ((stat[0] == "while" && empty(stat[2])) ||
+                                            (member(stat[0], [ "for", "for-in"] ) && empty(stat[4])) ||
+                                            (stat[0] == "if" && empty(stat[2]) && !stat[3]) ||
+                                            (stat[0] == "if" && stat[3] && empty(stat[3]))) {
+                                                code = code.replace(/;*\s*$/, ";");
+                                        } else {
+                                                code = code.replace(/;+\s*$/, "");
+                                        }
+                                }
+                                a.push(code);
+                        }
+                }
+                return MAP(a, indent);
+        };
+
+        function make_switch_block(body) {
+                var n = body.length;
+                if (n == 0) return "{}";
+                return "{" + newline + MAP(body, function(branch, i){
+                        var has_body = branch[1].length > 0, code = with_indent(function(){
+                                return indent(branch[0]
+                                              ? add_spaces([ "case", make(branch[0]) + ":" ])
+                                              : "default:");
+                        }, 0.5) + (has_body ? newline + with_indent(function(){
+                                return make_block_statements(branch[1]).join(newline);
+                        }) : "");
+                        if (!beautify && has_body && i < n - 1)
+                                code += ";";
+                        return code;
+                }).join(newline) + newline + indent("}");
+        };
+
+        function make_block(statements) {
+                if (!statements) return ";";
+                if (statements.length == 0) return "{}";
+                return "{" + newline + with_indent(function(){
+                        return make_block_statements(statements).join(newline);
+                }) + newline + indent("}");
+        };
+
+        function make_1vardef(def) {
+                var name = def[0], val = def[1];
+                if (val != null)
+                        name = add_spaces([ make_name(name), "=", parenthesize(val, "seq") ]);
+                return name;
+        };
+
+        var $stack = [];
+
+        function make(node) {
+                var type = node[0];
+                var gen = generators[type];
+                if (!gen)
+                        throw new Error("Can't find generator for \"" + type + "\"");
+                $stack.push(node);
+                var ret = gen.apply(type, node.slice(1));
+                $stack.pop();
+                return ret;
+        };
+
+        return make(ast);
+};
+
+function split_lines(code, max_line_length) {
+        var splits = [ 0 ];
+        jsp.parse(function(){
+                var next_token = jsp.tokenizer(code);
+                var last_split = 0;
+                var prev_token;
+                function current_length(tok) {
+                        return tok.pos - last_split;
+                };
+                function split_here(tok) {
+                        last_split = tok.pos;
+                        splits.push(last_split);
+                };
+                function custom(){
+                        var tok = next_token.apply(this, arguments);
+                        out: {
+                                if (prev_token) {
+                                        if (prev_token.type == "keyword") break out;
+                                }
+                                if (current_length(tok) > max_line_length) {
+                                        switch (tok.type) {
+                                            case "keyword":
+                                            case "atom":
+                                            case "name":
+                                            case "punc":
+                                                split_here(tok);
+                                                break out;
+                                        }
+                                }
+                        }
+                        prev_token = tok;
+                        return tok;
+                };
+                custom.context = function() {
+                        return next_token.context.apply(this, arguments);
+                };
+                return custom;
+        }());
+        return splits.map(function(pos, i){
+                return code.substring(pos, splits[i + 1] || code.length);
+        }).join("\n");
+};
+
+/* -----[ Utilities ]----- */
+
+function repeat_string(str, i) {
+        if (i <= 0) return "";
+        if (i == 1) return str;
+        var d = repeat_string(str, i >> 1);
+        d += d;
+        if (i & 1) d += str;
+        return d;
+};
+
+function defaults(args, defs) {
+        var ret = {};
+        if (args === true)
+                args = {};
+        for (var i in defs) if (HOP(defs, i)) {
+                ret[i] = (args && HOP(args, i)) ? args[i] : defs[i];
+        }
+        return ret;
+};
+
+function is_identifier(name) {
+        return /^[a-z_$][a-z0-9_$]*$/i.test(name)
+                && name != "this"
+                && !HOP(jsp.KEYWORDS_ATOM, name)
+                && !HOP(jsp.RESERVED_WORDS, name)
+                && !HOP(jsp.KEYWORDS, name);
+};
+
+function HOP(obj, prop) {
+        return Object.prototype.hasOwnProperty.call(obj, prop);
+};
+
+// some utilities
+
+var MAP;
+
+(function(){
+        MAP = function(a, f, o) {
+                var ret = [];
+                for (var i = 0; i < a.length; ++i) {
+                        var val = f.call(o, a[i], i);
+                        if (val instanceof AtTop) ret.unshift(val.v);
+                        else ret.push(val);
+                }
+                return ret;
+        };
+        MAP.at_top = function(val) { return new AtTop(val) };
+        function AtTop(val) { this.v = val };
+})();
+
+/* -----[ Exports ]----- */
+
+exports.ast_walker = ast_walker;
+exports.ast_mangle = ast_mangle;
+exports.ast_squeeze = ast_squeeze;
+exports.gen_code = gen_code;
+exports.ast_add_scope = ast_add_scope;
+exports.set_logger = function(logger) { warn = logger };
+exports.make_string = make_string;
+exports.split_lines = split_lines;
+exports.MAP = MAP;
+
+// keep this last!
+exports.ast_squeeze_more = require("./squeeze-more").ast_squeeze_more;
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/lib/squeeze-more.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/lib/squeeze-more.js
new file mode 100644
index 0000000..12380af
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/lib/squeeze-more.js
@@ -0,0 +1,22 @@
+var jsp = require("./parse-js"),
+    pro = require("./process"),
+    slice = jsp.slice,
+    member = jsp.member,
+    PRECEDENCE = jsp.PRECEDENCE,
+    OPERATORS = jsp.OPERATORS;
+
+function ast_squeeze_more(ast) {
+        var w = pro.ast_walker(), walk = w.walk;
+        return w.with_walkers({
+                "call": function(expr, args) {
+                        if (expr[0] == "dot" && expr[2] == "toString" && args.length == 0) {
+                                // foo.toString()  ==>  foo+""
+                                return [ "binary", "+", expr[1], [ "string", "" ]];
+                        }
+                }
+        }, function() {
+                return walk(ast);
+        });
+};
+
+exports.ast_squeeze_more = ast_squeeze_more;
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/post-compile.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/post-compile.js
new file mode 100644
index 0000000..4bcafe8
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/post-compile.js
@@ -0,0 +1,7 @@
+#!/usr/bin/env node
+
+var print = require("sys").print,
+	src = require("fs").readFileSync(process.argv[2], "utf8");
+
+// Previously done in sed but reimplemented here due to portability issues
+print(src.replace(/^(\s*\*\/)(.+)/m, "$1\n$2;"));
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/uglify.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/uglify.js
new file mode 100644
index 0000000..943ddd8
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/uglify.js
@@ -0,0 +1,199 @@
+#! /usr/bin/env node
+// -*- js2 -*-
+
+global.sys = require(/^v0\.[012]/.test(process.version) ? "sys" : "util");
+var fs = require("fs"),
+    jsp = require("./lib/parse-js"),
+    pro = require("./lib/process");
+
+pro.set_logger(function(msg){
+        sys.debug(msg);
+});
+
+var options = {
+        ast: false,
+        mangle: true,
+        mangle_toplevel: false,
+        squeeze: true,
+        make_seqs: true,
+        dead_code: true,
+        beautify: false,
+        verbose: false,
+        show_copyright: true,
+        out_same_file: false,
+        extra: false,
+        unsafe: false,            // XXX: extra & unsafe?  but maybe we don't want both, so....
+        beautify_options: {
+                indent_level: 4,
+                indent_start: 0,
+                quote_keys: false,
+                space_colon: false
+        },
+        output: true            // stdout
+};
+
+var args = jsp.slice(process.argv, 2);
+var filename;
+
+out: while (args.length > 0) {
+        var v = args.shift();
+        switch (v) {
+            case "-b":
+            case "--beautify":
+                options.beautify = true;
+                break;
+            case "-i":
+            case "--indent":
+                options.beautify_options.indent_level = args.shift();
+                break;
+            case "-q":
+            case "--quote-keys":
+                options.beautify_options.quote_keys = true;
+                break;
+            case "-mt":
+            case "--mangle-toplevel":
+                options.mangle_toplevel = true;
+                break;
+            case "--no-mangle":
+            case "-nm":
+                options.mangle = false;
+                break;
+            case "--no-squeeze":
+            case "-ns":
+                options.squeeze = false;
+                break;
+            case "--no-seqs":
+                options.make_seqs = false;
+                break;
+            case "--no-dead-code":
+                options.dead_code = false;
+                break;
+            case "--no-copyright":
+            case "-nc":
+                options.show_copyright = false;
+                break;
+            case "-o":
+            case "--output":
+                options.output = args.shift();
+                break;
+            case "--overwrite":
+                options.out_same_file = true;
+                break;
+            case "-v":
+            case "--verbose":
+                options.verbose = true;
+                break;
+            case "--ast":
+                options.ast = true;
+                break;
+            case "--extra":
+                options.extra = true;
+                break;
+            case "--unsafe":
+                options.unsafe = true;
+                break;
+            default:
+                filename = v;
+                break out;
+        }
+}
+
+if (filename) {
+        fs.readFile(filename, "utf8", function(err, text){
+                if (err) {
+                        throw err;
+                }
+                output(squeeze_it(text));
+        });
+} else {
+        var stdin = process.openStdin();
+        stdin.setEncoding("utf8");
+        var text = "";
+        stdin.on("data", function(chunk){
+                text += chunk;
+        });
+        stdin.on("end", function() {
+                output(squeeze_it(text));
+        });
+}
+
+function output(text) {
+        var out;
+        if (options.out_same_file && filename)
+                options.output = filename;
+        if (options.output === true) {
+                out = process.stdout;
+        } else {
+                out = fs.createWriteStream(options.output, {
+                        flags: "w",
+                        encoding: "utf8",
+                        mode: 0644
+                });
+        }
+        out.write(text);
+        out.end();
+};
+
+// --------- main ends here.
+
+function show_copyright(comments) {
+        var ret = "";
+        for (var i = 0; i < comments.length; ++i) {
+                var c = comments[i];
+                if (c.type == "comment1") {
+                        ret += "//" + c.value + "\n";
+                } else {
+                        ret += "/*" + c.value + "*/";
+                }
+        }
+        return ret;
+};
+
+function squeeze_it(code) {
+        var result = "";
+        if (options.show_copyright) {
+                var initial_comments = [];
+                // keep first comment
+                var tok = jsp.tokenizer(code, false), c;
+                c = tok();
+                var prev = null;
+                while (/^comment/.test(c.type) && (!prev || prev == c.type)) {
+                        initial_comments.push(c);
+                        prev = c.type;
+                        c = tok();
+                }
+                result += show_copyright(initial_comments);
+        }
+        try {
+                var ast = time_it("parse", function(){ return jsp.parse(code); });
+                if (options.mangle)
+                        ast = time_it("mangle", function(){ return pro.ast_mangle(ast, options.mangle_toplevel); });
+                if (options.squeeze)
+                        ast = time_it("squeeze", function(){
+                                ast = pro.ast_squeeze(ast, {
+                                        make_seqs : options.make_seqs,
+                                        dead_code : options.dead_code,
+                                        extra     : options.extra
+                                });
+                                if (options.unsafe)
+                                        ast = pro.ast_squeeze_more(ast);
+                                return ast;
+                        });
+                if (options.ast)
+                        return sys.inspect(ast, null, null);
+                result += time_it("generate", function(){ return pro.gen_code(ast, options.beautify && options.beautify_options) });
+                return result;
+        } catch(ex) {
+                sys.debug(ex.stack);
+                sys.debug(sys.inspect(ex));
+                sys.debug(JSON.stringify(ex));
+        }
+};
+
+function time_it(name, cont) {
+        if (!options.verbose)
+                return cont();
+        var t1 = new Date().getTime();
+        try { return cont(); }
+        finally { sys.debug("// " + name + ": " + ((new Date().getTime() - t1) / 1000).toFixed(3) + " sec."); }
+};
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/dist/jsonselect.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/dist/jsonselect.js
new file mode 100644
index 0000000..50059b7
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/dist/jsonselect.js
@@ -0,0 +1,522 @@
+/*! Copyright (c) 2011, Lloyd Hilaiel, ISC License */
+/*
+ * This is the JSONSelect reference implementation, in javascript.
+ */
+(function(exports) {
+
+    var // localize references
+    toString = Object.prototype.toString;
+
+    function jsonParse(str) {
+      try {
+          if(JSON && JSON.parse){
+              return JSON.parse(str);
+          }
+          return (new Function("return " + str))();
+      } catch(e) {
+        te("ijs");
+      }
+    }
+
+    // emitted error codes.
+    var errorCodes = {
+        "bop":  "binary operator expected",
+        "ee":   "expression expected",
+        "epex": "closing paren expected ')'",
+        "ijs":  "invalid json string",
+        "mcp":  "missing closing paren",
+        "mepf": "malformed expression in pseudo-function",
+        "mexp": "multiple expressions not allowed",
+        "mpc":  "multiple pseudo classes (:xxx) not allowed",
+        "nmi":  "multiple ids not allowed",
+        "pex":  "opening paren expected '('",
+        "se":   "selector expected",
+        "sex":  "string expected",
+        "sra":  "string required after '.'",
+        "uc":   "unrecognized char",
+        "ucp":  "unexpected closing paren",
+        "ujs":  "unclosed json string",
+        "upc":  "unrecognized pseudo class"
+    };
+
+    // throw an error message
+    function te(ec) {
+      throw new Error(errorCodes[ec]);
+    }
+
+    // THE LEXER
+    var toks = {
+        psc: 1, // pseudo class
+        psf: 2, // pseudo class function
+        typ: 3, // type
+        str: 4, // string
+        ide: 5  // identifiers (or "classes", stuff after a dot)
+    };
+
+    // The primary lexing regular expression in jsonselect
+    var pat = new RegExp(
+        "^(?:" +
+        // (1) whitespace
+        "([\\r\\n\\t\\ ]+)|" +
+        // (2) one-char ops
+        "([~*,>\\)\\(])|" +
+        // (3) types names
+        "(string|boolean|null|array|object|number)|" +
+        // (4) pseudo classes
+        "(:(?:root|first-child|last-child|only-child))|" +
+        // (5) pseudo functions
+        "(:(?:nth-child|nth-last-child|has|expr|val|contains))|" +
+        // (6) bogusly named pseudo something or others
+        "(:\\w+)|" +
+        // (7 & 8) identifiers and JSON strings
+        "(?:(\\.)?(\\\"(?:[^\\\\]|\\\\[^\\\"])*\\\"))|" +
+        // (8) bogus JSON strings missing a trailing quote
+        "(\\\")|" +
+        // (9) identifiers (unquoted)
+        "\\.((?:[_a-zA-Z]|[^\\0-\\0177]|\\\\[^\\r\\n\\f0-9a-fA-F])(?:[_a-zA-Z0-9\\-]|[^\\u0000-\\u0177]|(?:\\\\[^\\r\\n\\f0-9a-fA-F]))*)" +
+        ")"
+    );
+
+    // A regular expression for matching "nth expressions" (see grammar, what :nth-child() eats)
+    var nthPat = /^\s*\(\s*(?:([+\-]?)([0-9]*)n\s*(?:([+\-])\s*([0-9]))?|(odd|even)|([+\-]?[0-9]+))\s*\)/;
+    function lex(str, off) {
+        if (!off) off = 0;
+        var m = pat.exec(str.substr(off));
+        if (!m) return undefined;
+        off+=m[0].length;
+        var a;
+        if (m[1]) a = [off, " "];
+        else if (m[2]) a = [off, m[0]];
+        else if (m[3]) a = [off, toks.typ, m[0]];
+        else if (m[4]) a = [off, toks.psc, m[0]];
+        else if (m[5]) a = [off, toks.psf, m[0]];
+        else if (m[6]) te("upc");
+        else if (m[8]) a = [off, m[7] ? toks.ide : toks.str, jsonParse(m[8])];
+        else if (m[9]) te("ujs");
+        else if (m[10]) a = [off, toks.ide, m[10].replace(/\\([^\r\n\f0-9a-fA-F])/g,"$1")];
+        return a;
+    }
+
+    // THE EXPRESSION SUBSYSTEM
+
+    var exprPat = new RegExp(
+            // skip and don't capture leading whitespace
+            "^\\s*(?:" +
+            // (1) simple vals
+            "(true|false|null)|" + 
+            // (2) numbers
+            "(-?\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?)|" +
+            // (3) strings
+            "(\"(?:[^\\]|\\[^\"])*\")|" +
+            // (4) the 'x' value placeholder
+            "(x)|" +
+            // (5) binops
+            "(&&|\\|\\||[\\$\\^<>!\\*]=|[=+\\-*/%<>])|" +
+            // (6) parens
+            "([\\(\\)])" +
+            ")"
+    );
+
+    function is(o, t) { return typeof o === t; }
+    var operators = {
+        '*':  [ 9, function(lhs, rhs) { return lhs * rhs; } ],
+        '/':  [ 9, function(lhs, rhs) { return lhs / rhs; } ],
+        '%':  [ 9, function(lhs, rhs) { return lhs % rhs; } ],
+        '+':  [ 7, function(lhs, rhs) { return lhs + rhs; } ],
+        '-':  [ 7, function(lhs, rhs) { return lhs - rhs; } ],
+        '<=': [ 5, function(lhs, rhs) { return is(lhs, 'number') && is(rhs, 'number') && lhs <= rhs; } ],
+        '>=': [ 5, function(lhs, rhs) { return is(lhs, 'number') && is(rhs, 'number') && lhs >= rhs; } ],
+        '$=': [ 5, function(lhs, rhs) { return is(lhs, 'string') && is(rhs, 'string') && lhs.lastIndexOf(rhs) === lhs.length - rhs.length; } ],
+        '^=': [ 5, function(lhs, rhs) { return is(lhs, 'string') && is(rhs, 'string') && lhs.indexOf(rhs) === 0; } ],
+        '*=': [ 5, function(lhs, rhs) { return is(lhs, 'string') && is(rhs, 'string') && lhs.indexOf(rhs) !== -1; } ],
+        '>':  [ 5, function(lhs, rhs) { return is(lhs, 'number') && is(rhs, 'number') && lhs > rhs; } ],
+        '<':  [ 5, function(lhs, rhs) { return is(lhs, 'number') && is(rhs, 'number') && lhs < rhs; } ],
+        '=':  [ 3, function(lhs, rhs) { return lhs === rhs; } ],
+        '!=': [ 3, function(lhs, rhs) { return lhs !== rhs; } ],
+        '&&': [ 2, function(lhs, rhs) { return lhs && rhs; } ],
+        '||': [ 1, function(lhs, rhs) { return lhs || rhs; } ]
+    };
+
+    function exprLex(str, off) {
+        var v, m = exprPat.exec(str.substr(off));
+        if (m) {
+            off += m[0].length;
+            v = m[1] || m[2] || m[3] || m[5] || m[6];
+            if (m[1] || m[2] || m[3]) return [off, 0, jsonParse(v)];
+            else if (m[4]) return [off, 0, undefined];
+            return [off, v];
+        }
+    }
+
+    function exprParse2(str, off) {
+        if (!off) off = 0;
+        // first we expect a value or a '('
+        var l = exprLex(str, off),
+            lhs;
+        if (l && l[1] === '(') {
+            lhs = exprParse2(str, l[0]);
+            var p = exprLex(str, lhs[0]);
+            if (!p || p[1] !== ')') te('epex');
+            off = p[0];
+            lhs = [ '(', lhs[1] ];
+        } else if (!l || (l[1] && l[1] != 'x')) {
+            te("ee");
+        } else {
+            lhs = ((l[1] === 'x') ? undefined : l[2]);
+            off = l[0];
+        }
+
+        // now we expect a binary operator or a ')'
+        var op = exprLex(str, off);
+        if (!op || op[1] == ')') return [off, lhs];
+        else if (op[1] == 'x' || !op[1]) {
+            te('bop');
+        }
+
+        // tail recursion to fetch the rhs expression
+        var rhs = exprParse2(str, op[0]);
+        off = rhs[0];
+        rhs = rhs[1];
+
+        // and now precedence!  how shall we put everything together?
+        var v;
+        if (typeof rhs !== 'object' || rhs[0] === '(' || operators[op[1]][0] < operators[rhs[1]][0] ) {
+            v = [lhs, op[1], rhs];
+        }
+        else {
+            v = rhs;
+            while (typeof rhs[0] === 'object' && rhs[0][0] != '(' && operators[op[1]][0] >= operators[rhs[0][1]][0]) {
+                rhs = rhs[0];
+            }
+            rhs[0] = [lhs, op[1], rhs[0]];
+        }
+        return [off, v];
+    }
+
+    function exprParse(str, off) {
+        function deparen(v) {
+            if (typeof v !== 'object' || v === null) return v;
+            else if (v[0] === '(') return deparen(v[1]);
+            else return [deparen(v[0]), v[1], deparen(v[2])];
+        }
+        var e = exprParse2(str, off ? off : 0);
+        return [e[0], deparen(e[1])];
+    }
+
+    function exprEval(expr, x) {
+        if (expr === undefined) return x;
+        else if (expr === null || typeof expr !== 'object') {
+            return expr;
+        }
+        var lhs = exprEval(expr[0], x),
+            rhs = exprEval(expr[2], x);
+        return operators[expr[1]][1](lhs, rhs);
+    }
+
+    // THE PARSER
+
+    function parse(str, off, nested, hints) {
+        if (!nested) hints = {};
+
+        var a = [], am, readParen;
+        if (!off) off = 0; 
+
+        while (true) {
+            var s = parse_selector(str, off, hints);
+            a.push(s[1]);
+            s = lex(str, off = s[0]);
+            if (s && s[1] === " ") s = lex(str, off = s[0]);
+            if (!s) break;
+            // now we've parsed a selector, and have something else...
+            if (s[1] === ">" || s[1] === "~") {
+                if (s[1] === "~") hints.usesSiblingOp = true;
+                a.push(s[1]);
+                off = s[0];
+            } else if (s[1] === ",") {
+                if (am === undefined) am = [ ",", a ];
+                else am.push(a);
+                a = [];
+                off = s[0];
+            } else if (s[1] === ")") {
+                if (!nested) te("ucp");
+                readParen = 1;
+                off = s[0];
+                break;
+            }
+        }
+        if (nested && !readParen) te("mcp");
+        if (am) am.push(a);
+        var rv;
+        if (!nested && hints.usesSiblingOp) {
+            rv = normalize(am ? am : a);
+        } else {
+            rv = am ? am : a;
+        }
+        return [off, rv];
+    }
+
+    function normalizeOne(sel) {
+        var sels = [], s;
+        for (var i = 0; i < sel.length; i++) {
+            if (sel[i] === '~') {
+                // `A ~ B` maps to `:has(:root > A) > B`
+                // `Z A ~ B` maps to `Z :has(:root > A) > B, Z:has(:root > A) > B`
+                // This first clause, takes care of the first case, and the first half of the latter case.
+                if (i < 2 || sel[i-2] != '>') {
+                    s = sel.slice(0,i-1);
+                    s = s.concat([{has:[[{pc: ":root"}, ">", sel[i-1]]]}, ">"]);
+                    s = s.concat(sel.slice(i+1));
+                    sels.push(s);
+                }
+                // here we take care of the second half of above:
+                // (`Z A ~ B` maps to `Z :has(:root > A) > B, Z :has(:root > A) > B`)
+                // and a new case:
+                // Z > A ~ B maps to Z:has(:root > A) > B
+                if (i > 1) {
+                    var at = sel[i-2] === '>' ? i-3 : i-2;
+                    s = sel.slice(0,at);
+                    var z = {};
+                    for (var k in sel[at]) if (sel[at].hasOwnProperty(k)) z[k] = sel[at][k];
+                    if (!z.has) z.has = [];
+                    z.has.push([{pc: ":root"}, ">", sel[i-1]]);
+                    s = s.concat(z, '>', sel.slice(i+1));
+                    sels.push(s);
+                }
+                break;
+            }
+        }
+        if (i == sel.length) return sel;
+        return sels.length > 1 ? [','].concat(sels) : sels[0];
+    }
+
+    function normalize(sels) {
+        if (sels[0] === ',') {
+            var r = [","];
+            for (var i = i; i < sels.length; i++) {
+                var s = normalizeOne(s[i]);
+                r = r.concat(s[0] === "," ? s.slice(1) : s);
+            }
+            return r;
+        } else {
+            return normalizeOne(sels);
+        }
+    }
+
+    function parse_selector(str, off, hints) {
+        var soff = off;
+        var s = { };
+        var l = lex(str, off);
+        // skip space
+        if (l && l[1] === " ") { soff = off = l[0]; l = lex(str, off); }
+        if (l && l[1] === toks.typ) {
+            s.type = l[2];
+            l = lex(str, (off = l[0]));
+        } else if (l && l[1] === "*") {
+            // don't bother representing the universal sel, '*' in the
+            // parse tree, cause it's the default
+            l = lex(str, (off = l[0]));
+        }
+
+        // now support either an id or a pc
+        while (true) {
+            if (l === undefined) {
+                break;
+            } else if (l[1] === toks.ide) {
+                if (s.id) te("nmi");
+                s.id = l[2];
+            } else if (l[1] === toks.psc) {
+                if (s.pc || s.pf) te("mpc");
+                // collapse first-child and last-child into nth-child expressions
+                if (l[2] === ":first-child") {
+                    s.pf = ":nth-child";
+                    s.a = 0;
+                    s.b = 1;
+                } else if (l[2] === ":last-child") {
+                    s.pf = ":nth-last-child";
+                    s.a = 0;
+                    s.b = 1;
+                } else {
+                    s.pc = l[2];
+                }
+            } else if (l[1] === toks.psf) {
+                if (l[2] === ":val" || l[2] === ":contains") {
+                    s.expr = [ undefined, l[2] === ":val" ? "=" : "*=", undefined];
+                    // any amount of whitespace, followed by paren, string, paren
+                    l = lex(str, (off = l[0]));
+                    if (l && l[1] === " ") l = lex(str, off = l[0]);
+                    if (!l || l[1] !== "(") te("pex");
+                    l = lex(str, (off = l[0]));
+                    if (l && l[1] === " ") l = lex(str, off = l[0]);
+                    if (!l || l[1] !== toks.str) te("sex");
+                    s.expr[2] = l[2];
+                    l = lex(str, (off = l[0]));
+                    if (l && l[1] === " ") l = lex(str, off = l[0]);
+                    if (!l || l[1] !== ")") te("epex");
+                } else if (l[2] === ":has") {
+                    // any amount of whitespace, followed by paren
+                    l = lex(str, (off = l[0]));
+                    if (l && l[1] === " ") l = lex(str, off = l[0]);
+                    if (!l || l[1] !== "(") te("pex");
+                    var h = parse(str, l[0], true);
+                    l[0] = h[0];
+                    if (!s.has) s.has = [];
+                    s.has.push(h[1]);
+                } else if (l[2] === ":expr") {
+                    if (s.expr) te("mexp");
+                    var e = exprParse(str, l[0]);
+                    l[0] = e[0];
+                    s.expr = e[1];
+                } else {
+                    if (s.pc || s.pf ) te("mpc");
+                    s.pf = l[2];
+                    var m = nthPat.exec(str.substr(l[0]));
+                    if (!m) te("mepf");
+                    if (m[5]) {
+                        s.a = 2;
+                        s.b = (m[5] === "odd") ? 1 : 0;
+                    } else if (m[6]) {
+                        s.a = 0;
+                        s.b = parseInt(m[6], 10);
+                    } else {
+                        s.a = parseInt((m[1] ? m[1] : "+") + (m[2] ? m[2] : "1"),10);
+                        s.b = m[3] ? parseInt(m[3] + m[4],10) : 0;
+                    }
+                    l[0] += m[0].length;
+                }
+            } else {
+                break;
+            }
+            l = lex(str, (off = l[0]));
+        }
+
+        // now if we didn't actually parse anything it's an error
+        if (soff === off) te("se");
+
+        return [off, s];
+    }
+
+    // THE EVALUATOR
+
+    function isArray(o) {
+        return Array.isArray ? Array.isArray(o) : 
+          toString.call(o) === "[object Array]";
+    }
+
+    function mytypeof(o) {
+        if (o === null) return "null";
+        var to = typeof o;
+        if (to === "object" && isArray(o)) to = "array";
+        return to;
+    }
+
+    function mn(node, sel, id, num, tot) {
+        var sels = [];
+        var cs = (sel[0] === ">") ? sel[1] : sel[0];
+        var m = true, mod;
+        if (cs.type) m = m && (cs.type === mytypeof(node));
+        if (cs.id)   m = m && (cs.id === id);
+        if (m && cs.pf) {
+            if (cs.pf === ":nth-last-child") num = tot - num;
+            else num++;
+            if (cs.a === 0) {
+                m = cs.b === num;
+            } else {
+                mod = ((num - cs.b) % cs.a);
+
+                m = (!mod && ((num*cs.a + cs.b) >= 0));
+            }
+        }
+        if (m && cs.has) {
+            // perhaps we should augment forEach to handle a return value
+            // that indicates "client cancels traversal"?
+            var bail = function() { throw 42; };
+            for (var i = 0; i < cs.has.length; i++) {
+                try {
+                    forEach(cs.has[i], node, bail);
+                } catch (e) {
+                    if (e === 42) continue;
+                }
+                m = false;
+                break;
+            }
+        }
+        if (m && cs.expr) {
+            m = exprEval(cs.expr, node);
+        }
+        // should we repeat this selector for descendants?
+        if (sel[0] !== ">" && sel[0].pc !== ":root") sels.push(sel);
+
+        if (m) {
+            // is there a fragment that we should pass down?
+            if (sel[0] === ">") { if (sel.length > 2) { m = false; sels.push(sel.slice(2)); } }
+            else if (sel.length > 1) { m = false; sels.push(sel.slice(1)); }
+        }
+
+        return [m, sels];
+    }
+
+    function forEach(sel, obj, fun, id, num, tot) {
+        var a = (sel[0] === ",") ? sel.slice(1) : [sel],
+        a0 = [],
+        call = false,
+        i = 0, j = 0, k, x;
+        for (i = 0; i < a.length; i++) {
+            x = mn(obj, a[i], id, num, tot);
+            if (x[0]) {
+                call = true;
+            }
+            for (j = 0; j < x[1].length; j++) {
+                a0.push(x[1][j]);
+            }
+        }
+        if (a0.length && typeof obj === "object") {
+            if (a0.length >= 1) {
+                a0.unshift(",");
+            }
+            if (isArray(obj)) {
+                for (i = 0; i < obj.length; i++) {
+                    forEach(a0, obj[i], fun, undefined, i, obj.length);
+                }
+            } else {
+                for (k in obj) {
+                    if (obj.hasOwnProperty(k)) {
+                        forEach(a0, obj[k], fun, k);
+                    }
+                }
+            }
+        }
+        if (call && fun) {
+            fun(obj);
+        }
+    }
+
+    function match(sel, obj) {
+        var a = [];
+        forEach(sel, obj, function(x) {
+            a.push(x);
+        });
+        return a;
+    }
+
+    function compile(sel) {
+        return {
+            sel: parse(sel)[1],
+            match: function(obj){
+                return match(this.sel, obj);
+            },
+            forEach: function(obj, fun) {
+                return forEach(this.sel, obj, fun);
+            }
+        };
+    }
+
+    exports._lex = lex;
+    exports._parse = parse;
+    exports.match = function (sel, obj) {
+        return compile(sel).match(obj);
+    };
+    exports.forEach = function(sel, obj, fun) {
+        return compile(sel).forEach(obj, fun);
+    };
+    exports.compile = compile;
+})(typeof exports === "undefined" ? (window.JSONSelect = {}) : exports);
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/dist/jsonselect.min.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/dist/jsonselect.min.js
new file mode 100644
index 0000000..ec5015f
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/dist/jsonselect.min.js
@@ -0,0 +1 @@
+(function(a){function z(a){return{sel:q(a)[1],match:function(a){return y(this.sel,a)},forEach:function(a,b){return x(this.sel,a,b)}}}function y(a,b){var c=[];x(a,b,function(a){c.push(a)});return c}function x(a,b,c,d,e,f){var g=a[0]===","?a.slice(1):[a],h=[],i=!1,j=0,k=0,l,m;for(j=0;j<g.length;j++){m=w(b,g[j],d,e,f),m[0]&&(i=!0);for(k=0;k<m[1].length;k++)h.push(m[1][k])}if(h.length&&typeof b=="object"){h.length>=1&&h.unshift(",");if(u(b))for(j=0;j<b.length;j++)x(h,b[j],c,undefined,j,b.length);else for(l in b)b.hasOwnProperty(l)&&x(h,b[l],c,l)}i&&c&&c(b)}function w(a,b,c,d,e){var f=[],g=b[0]===">"?b[1]:b[0],h=!0,i;g.type&&(h=h&&g.type===v(a)),g.id&&(h=h&&g.id===c),h&&g.pf&&(g.pf===":nth-last-child"?d=e-d:d++,g.a===0?h=g.b===d:(i=(d-g.b)%g.a,h=!i&&d*g.a+g.b>=0));if(h&&g.has){var j=function(){throw 42};for(var k=0;k<g.has.length;k++){try{x(g.has[k],a,j)}catch(l){if(l===42)continue}h=!1;break}}h&&g.expr&&(h=p(g.expr,a)),b[0]!==">"&&b[0].pc!==":root"&&f.push(b),h&&(b[0]===">"?b.length>2&&(h=!1,f.push(b.slice(2))):b.length>1&&(h=!1,f.push(b.slice(1))));return[h,f]}function v(a){if(a===null)return"null";var b=typeof a;b==="object"&&u(a)&&(b="array");return b}function u(a){return Array.isArray?Array.isArray(a):b.call(a)==="[object Array]"}function t(a,b,c){var d=b,g={},j=i(a,b);j&&j[1]===" "&&(d=b=j[0],j=i(a,b)),j&&j[1]===f.typ?(g.type=j[2],j=i(a,b=j[0])):j&&j[1]==="*"&&(j=i(a,b=j[0]));for(;;){if(j===undefined)break;if(j[1]===f.ide)g.id&&e("nmi"),g.id=j[2];else if(j[1]===f.psc)(g.pc||g.pf)&&e("mpc"),j[2]===":first-child"?(g.pf=":nth-child",g.a=0,g.b=1):j[2]===":last-child"?(g.pf=":nth-last-child",g.a=0,g.b=1):g.pc=j[2];else{if(j[1]!==f.psf)break;if(j[2]===":val"||j[2]===":contains")g.expr=[undefined,j[2]===":val"?"=":"*=",undefined],j=i(a,b=j[0]),j&&j[1]===" "&&(j=i(a,b=j[0])),(!j||j[1]!=="(")&&e("pex"),j=i(a,b=j[0]),j&&j[1]===" "&&(j=i(a,b=j[0])),(!j||j[1]!==f.str)&&e("sex"),g.expr[2]=j[2],j=i(a,b=j[0]),j&&j[1]===" "&&(j=i(a,b=j[0])),(!j||j[1]!==")")&&e("epex");else if(j[2]===":has"){j=i(a,b=j[0]),j&&j[1]===" "&&(j=i(a,b=j[0])),(!j||j[1]!=="(")&&e("pex");var k=q(a,j[0],!0);j[0]=k[0],g.has||(g.has=[]),g.has.push(k[1])}else if(j[2]===":expr"){g.expr&&e("mexp");var l=o(a,j[0]);j[0]=l[0],g.expr=l[1]}else{(g.pc||g.pf)&&e("mpc"),g.pf=j[2];var m=h.exec(a.substr(j[0]));m||e("mepf"),m[5]?(g.a=2,g.b=m[5]==="odd"?1:0):m[6]?(g.a=0,g.b=parseInt(m[6],10)):(g.a=parseInt((m[1]?m[1]:"+")+(m[2]?m[2]:"1"),10),g.b=m[3]?parseInt(m[3]+m[4],10):0),j[0]+=m[0].length}}j=i(a,b=j[0])}d===b&&e("se");return[b,g]}function s(a){if(a[0]===","){var b=[","];for(var c=c;c<a.length;c++){var d=r(d[c]);b=b.concat(d[0]===","?d.slice(1):d)}return b}return r(a)}function r(a){var b=[],c;for(var d=0;d<a.length;d++)if(a[d]==="~"){if(d<2||a[d-2]!=">")c=a.slice(0,d-1),c=c.concat([{has:[[{pc:":root"},">",a[d-1]]]},">"]),c=c.concat(a.slice(d+1)),b.push(c);if(d>1){var e=a[d-2]===">"?d-3:d-2;c=a.slice(0,e);var f={};for(var g in a[e])a[e].hasOwnProperty(g)&&(f[g]=a[e][g]);f.has||(f.has=[]),f.has.push([{pc:":root"},">",a[d-1]]),c=c.concat(f,">",a.slice(d+1)),b.push(c)}break}if(d==a.length)return a;return b.length>1?[","].concat(b):b[0]}function q(a,b,c,d){c||(d={});var f=[],g,h;b||(b=0);for(;;){var j=t(a,b,d);f.push(j[1]),j=i(a,b=j[0]),j&&j[1]===" "&&(j=i(a,b=j[0]));if(!j)break;if(j[1]===">"||j[1]==="~")j[1]==="~"&&(d.usesSiblingOp=!0),f.push(j[1]),b=j[0];else if(j[1]===",")g===undefined?g=[",",f]:g.push(f),f=[],b=j[0];else if(j[1]===")"){c||e("ucp"),h=1,b=j[0];break}}c&&!h&&e("mcp"),g&&g.push(f);var k;!c&&d.usesSiblingOp?k=s(g?g:f):k=g?g:f;return[b,k]}function p(a,b){if(a===undefined)return b;if(a===null||typeof a!="object")return a;var c=p(a[0],b),d=p(a[2],b);return l[a[1]][1](c,d)}function o(a,b){function c(a){return typeof a!="object"||a===null?a:a[0]==="("?c(a[1]):[c(a[0]),a[1],c(a[2])]}var d=n(a,b?b:0);return[d[0],c(d[1])]}function n(a,b){b||(b=0);var c=m(a,b),d;if(c&&c[1]==="("){d=n(a,c[0]);var f=m(a,d[0]);(!f||f[1]!==")")&&e("epex"),b=f[0],d=["(",d[1]]}else!c||c[1]&&c[1]!="x"?e("ee"):(d=c[1]==="x"?undefined:c[2],b=c[0]);var g=m(a,b);if(!g||g[1]==")")return[b,d];(g[1]=="x"||!g[1])&&e("bop");var h=n(a,g[0]);b=h[0],h=h[1];var i;if(typeof h!="object"||h[0]==="("||l[g[1]][0]<l[h[1]][0])i=[d,g[1],h];else{i=h;while(typeof h[0]=="object"&&h[0][0]!="("&&l[g[1]][0]>=l[h[0][1]][0])h=h[0];h[0]=[d,g[1],h[0]]}return[b,i]}function m(a,b){var d,e=j.exec(a.substr(b));if(e){b+=e[0].length,d=e[1]||e[2]||e[3]||e[5]||e[6];if(e[1]||e[2]||e[3])return[b,0,c(d)];if(e[4])return[b,0,undefined];return[b,d]}}function k(a,b){return typeof a===b}function i(a,b){b||(b=0);var d=g.exec(a.substr(b));if(!d)return undefined;b+=d[0].length;var h;d[1]?h=[b," "]:d[2]?h=[b,d[0]]:d[3]?h=[b,f.typ,d[0]]:d[4]?h=[b,f.psc,d[0]]:d[5]?h=[b,f.psf,d[0]]:d[6]?e("upc"):d[8]?h=[b,d[7]?f.ide:f.str,c(d[8])]:d[9]?e("ujs"):d[10]&&(h=[b,f.ide,d[10].replace(/\\([^\r\n\f0-9a-fA-F])/g,"$1")]);return h}function e(a){throw new Error(d[a])}function c(a){try{if(JSON&&JSON.parse)return JSON.parse(a);return(new Function("return "+a))()}catch(b){e("ijs")}}var b=Object.prototype.toString,d={bop:"binary operator expected",ee:"expression expected",epex:"closing paren expected ')'",ijs:"invalid json string",mcp:"missing closing paren",mepf:"malformed expression in pseudo-function",mexp:"multiple expressions not allowed",mpc:"multiple pseudo classes (:xxx) not allowed",nmi:"multiple ids not allowed",pex:"opening paren expected '('",se:"selector expected",sex:"string expected",sra:"string required after '.'",uc:"unrecognized char",ucp:"unexpected closing paren",ujs:"unclosed json string",upc:"unrecognized pseudo class"},f={psc:1,psf:2,typ:3,str:4,ide:5},g=new RegExp('^(?:([\\r\\n\\t\\ ]+)|([~*,>\\)\\(])|(string|boolean|null|array|object|number)|(:(?:root|first-child|last-child|only-child))|(:(?:nth-child|nth-last-child|has|expr|val|contains))|(:\\w+)|(?:(\\.)?(\\"(?:[^\\\\]|\\\\[^\\"])*\\"))|(\\")|\\.((?:[_a-zA-Z]|[^\\0-\\0177]|\\\\[^\\r\\n\\f0-9a-fA-F])(?:[_a-zA-Z0-9\\-]|[^\\u0000-\\u0177]|(?:\\\\[^\\r\\n\\f0-9a-fA-F]))*))'),h=/^\s*\(\s*(?:([+\-]?)([0-9]*)n\s*(?:([+\-])\s*([0-9]))?|(odd|even)|([+\-]?[0-9]+))\s*\)/,j=new RegExp('^\\s*(?:(true|false|null)|(-?\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?)|("(?:[^\\]|\\[^"])*")|(x)|(&&|\\|\\||[\\$\\^<>!\\*]=|[=+\\-*/%<>])|([\\(\\)]))'),l={"*":[9,function(a,b){return a*b}],"/":[9,function(a,b){return a/b}],"%":[9,function(a,b){return a%b}],"+":[7,function(a,b){return a+b}],"-":[7,function(a,b){return a-b}],"<=":[5,function(a,b){return k(a,"number")&&k(b,"number")&&a<=b}],">=":[5,function(a,b){return k(a,"number")&&k(b,"number")&&a>=b}],"$=":[5,function(a,b){return k(a,"string")&&k(b,"string")&&a.lastIndexOf(b)===a.length-b.length}],"^=":[5,function(a,b){return k(a,"string")&&k(b,"string")&&a.indexOf(b)===0}],"*=":[5,function(a,b){return k(a,"string")&&k(b,"string")&&a.indexOf(b)!==-1}],">":[5,function(a,b){return k(a,"number")&&k(b,"number")&&a>b}],"<":[5,function(a,b){return k(a,"number")&&k(b,"number")&&a<b}],"=":[3,function(a,b){return a===b}],"!=":[3,function(a,b){return a!==b}],"&&":[2,function(a,b){return a&&b}],"||":[1,function(a,b){return a||b}]};a._lex=i,a._parse=q,a.match=function(a,b){return z(a).match(b)},a.forEach=function(a,b,c){return z(a).forEach(b,c)},a.compile=z})(typeof exports=="undefined"?window.JSONSelect={}:exports)
\ No newline at end of file
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/dist/jsonselect.min.js.gz b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/dist/jsonselect.min.js.gz
new file mode 100644
index 0000000..4eb9b37
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/dist/jsonselect.min.js.gz
Binary files differ
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/jsonselect.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/jsonselect.js
new file mode 100644
index 0000000..f0611ea
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/jsonselect.js
@@ -0,0 +1,522 @@
+/*! Copyright (c) 2011, Lloyd Hilaiel, ISC License */
+/*
+ * This is the JSONSelect reference implementation, in javascript.
+ */
+(function(exports) {
+
+    var // localize references
+    toString = Object.prototype.toString;
+
+    function jsonParse(str) {
+      try {
+          if(JSON && JSON.parse){
+              return JSON.parse(str);
+          }
+          return (new Function("return " + str))();
+      } catch(e) {
+        te("ijs", e.message);
+      }
+    }
+
+    // emitted error codes.
+    var errorCodes = {
+        "bop":  "binary operator expected",
+        "ee":   "expression expected",
+        "epex": "closing paren expected ')'",
+        "ijs":  "invalid json string",
+        "mcp":  "missing closing paren",
+        "mepf": "malformed expression in pseudo-function",
+        "mexp": "multiple expressions not allowed",
+        "mpc":  "multiple pseudo classes (:xxx) not allowed",
+        "nmi":  "multiple ids not allowed",
+        "pex":  "opening paren expected '('",
+        "se":   "selector expected",
+        "sex":  "string expected",
+        "sra":  "string required after '.'",
+        "uc":   "unrecognized char",
+        "ucp":  "unexpected closing paren",
+        "ujs":  "unclosed json string",
+        "upc":  "unrecognized pseudo class"
+    };
+
+    // throw an error message
+    function te(ec, context) {
+      throw new Error(errorCodes[ec] + ( context && " in '" + context + "'"));
+    }
+
+    // THE LEXER
+    var toks = {
+        psc: 1, // pseudo class
+        psf: 2, // pseudo class function
+        typ: 3, // type
+        str: 4, // string
+        ide: 5  // identifiers (or "classes", stuff after a dot)
+    };
+
+    // The primary lexing regular expression in jsonselect
+    var pat = new RegExp(
+        "^(?:" +
+        // (1) whitespace
+        "([\\r\\n\\t\\ ]+)|" +
+        // (2) one-char ops
+        "([~*,>\\)\\(])|" +
+        // (3) types names
+        "(string|boolean|null|array|object|number)|" +
+        // (4) pseudo classes
+        "(:(?:root|first-child|last-child|only-child))|" +
+        // (5) pseudo functions
+        "(:(?:nth-child|nth-last-child|has|expr|val|contains))|" +
+        // (6) bogusly named pseudo something or others
+        "(:\\w+)|" +
+        // (7 & 8) identifiers and JSON strings
+        "(?:(\\.)?(\\\"(?:[^\\\\\\\"]|\\\\[^\\\"])*\\\"))|" +
+        // (8) bogus JSON strings missing a trailing quote
+        "(\\\")|" +
+        // (9) identifiers (unquoted)
+        "\\.((?:[_a-zA-Z]|[^\\0-\\0177]|\\\\[^\\r\\n\\f0-9a-fA-F])(?:[_a-zA-Z0-9\\-]|[^\\u0000-\\u0177]|(?:\\\\[^\\r\\n\\f0-9a-fA-F]))*)" +
+        ")"
+    );
+
+    // A regular expression for matching "nth expressions" (see grammar, what :nth-child() eats)
+    var nthPat = /^\s*\(\s*(?:([+\-]?)([0-9]*)n\s*(?:([+\-])\s*([0-9]))?|(odd|even)|([+\-]?[0-9]+))\s*\)/;
+    function lex(str, off) {
+        if (!off) off = 0;
+        var m = pat.exec(str.substr(off));
+        if (!m) return undefined;
+        off+=m[0].length;
+        var a;
+        if (m[1]) a = [off, " "];
+        else if (m[2]) a = [off, m[0]];
+        else if (m[3]) a = [off, toks.typ, m[0]];
+        else if (m[4]) a = [off, toks.psc, m[0]];
+        else if (m[5]) a = [off, toks.psf, m[0]];
+        else if (m[6]) te("upc", str);
+        else if (m[8]) a = [off, m[7] ? toks.ide : toks.str, jsonParse(m[8])];
+        else if (m[9]) te("ujs", str);
+        else if (m[10]) a = [off, toks.ide, m[10].replace(/\\([^\r\n\f0-9a-fA-F])/g,"$1")];
+        return a;
+    }
+
+    // THE EXPRESSION SUBSYSTEM
+
+    var exprPat = new RegExp(
+            // skip and don't capture leading whitespace
+            "^\\s*(?:" +
+            // (1) simple vals
+            "(true|false|null)|" + 
+            // (2) numbers
+            "(-?\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?)|" +
+            // (3) strings
+            "(\"(?:[^\\]|\\[^\"])*\")|" +
+            // (4) the 'x' value placeholder
+            "(x)|" +
+            // (5) binops
+            "(&&|\\|\\||[\\$\\^<>!\\*]=|[=+\\-*/%<>])|" +
+            // (6) parens
+            "([\\(\\)])" +
+            ")"
+    );
+
+    function is(o, t) { return typeof o === t; }
+    var operators = {
+        '*':  [ 9, function(lhs, rhs) { return lhs * rhs; } ],
+        '/':  [ 9, function(lhs, rhs) { return lhs / rhs; } ],
+        '%':  [ 9, function(lhs, rhs) { return lhs % rhs; } ],
+        '+':  [ 7, function(lhs, rhs) { return lhs + rhs; } ],
+        '-':  [ 7, function(lhs, rhs) { return lhs - rhs; } ],
+        '<=': [ 5, function(lhs, rhs) { return is(lhs, 'number') && is(rhs, 'number') && lhs <= rhs; } ],
+        '>=': [ 5, function(lhs, rhs) { return is(lhs, 'number') && is(rhs, 'number') && lhs >= rhs; } ],
+        '$=': [ 5, function(lhs, rhs) { return is(lhs, 'string') && is(rhs, 'string') && lhs.lastIndexOf(rhs) === lhs.length - rhs.length; } ],
+        '^=': [ 5, function(lhs, rhs) { return is(lhs, 'string') && is(rhs, 'string') && lhs.indexOf(rhs) === 0; } ],
+        '*=': [ 5, function(lhs, rhs) { return is(lhs, 'string') && is(rhs, 'string') && lhs.indexOf(rhs) !== -1; } ],
+        '>':  [ 5, function(lhs, rhs) { return is(lhs, 'number') && is(rhs, 'number') && lhs > rhs; } ],
+        '<':  [ 5, function(lhs, rhs) { return is(lhs, 'number') && is(rhs, 'number') && lhs < rhs; } ],
+        '=':  [ 3, function(lhs, rhs) { return lhs === rhs; } ],
+        '!=': [ 3, function(lhs, rhs) { return lhs !== rhs; } ],
+        '&&': [ 2, function(lhs, rhs) { return lhs && rhs; } ],
+        '||': [ 1, function(lhs, rhs) { return lhs || rhs; } ]
+    };
+
+    function exprLex(str, off) {
+        var v, m = exprPat.exec(str.substr(off));
+        if (m) {
+            off += m[0].length;
+            v = m[1] || m[2] || m[3] || m[5] || m[6];
+            if (m[1] || m[2] || m[3]) return [off, 0, jsonParse(v)];
+            else if (m[4]) return [off, 0, undefined];
+            return [off, v];
+        }
+    }
+
+    function exprParse2(str, off) {
+        if (!off) off = 0;
+        // first we expect a value or a '('
+        var l = exprLex(str, off),
+            lhs;
+        if (l && l[1] === '(') {
+            lhs = exprParse2(str, l[0]);
+            var p = exprLex(str, lhs[0]);
+            if (!p || p[1] !== ')') te('epex', str);
+            off = p[0];
+            lhs = [ '(', lhs[1] ];
+        } else if (!l || (l[1] && l[1] != 'x')) {
+            te("ee", str + " - " + ( l[1] && l[1] ));
+        } else {
+            lhs = ((l[1] === 'x') ? undefined : l[2]);
+            off = l[0];
+        }
+
+        // now we expect a binary operator or a ')'
+        var op = exprLex(str, off);
+        if (!op || op[1] == ')') return [off, lhs];
+        else if (op[1] == 'x' || !op[1]) {
+            te('bop', str + " - " + ( op[1] && op[1] ));
+        }
+
+        // tail recursion to fetch the rhs expression
+        var rhs = exprParse2(str, op[0]);
+        off = rhs[0];
+        rhs = rhs[1];
+
+        // and now precedence!  how shall we put everything together?
+        var v;
+        if (typeof rhs !== 'object' || rhs[0] === '(' || operators[op[1]][0] < operators[rhs[1]][0] ) {
+            v = [lhs, op[1], rhs];
+        }
+        else {
+            v = rhs;
+            while (typeof rhs[0] === 'object' && rhs[0][0] != '(' && operators[op[1]][0] >= operators[rhs[0][1]][0]) {
+                rhs = rhs[0];
+            }
+            rhs[0] = [lhs, op[1], rhs[0]];
+        }
+        return [off, v];
+    }
+
+    function exprParse(str, off) {
+        function deparen(v) {
+            if (typeof v !== 'object' || v === null) return v;
+            else if (v[0] === '(') return deparen(v[1]);
+            else return [deparen(v[0]), v[1], deparen(v[2])];
+        }
+        var e = exprParse2(str, off ? off : 0);
+        return [e[0], deparen(e[1])];
+    }
+
+    function exprEval(expr, x) {
+        if (expr === undefined) return x;
+        else if (expr === null || typeof expr !== 'object') {
+            return expr;
+        }
+        var lhs = exprEval(expr[0], x),
+            rhs = exprEval(expr[2], x);
+        return operators[expr[1]][1](lhs, rhs);
+    }
+
+    // THE PARSER
+
+    function parse(str, off, nested, hints) {
+        if (!nested) hints = {};
+
+        var a = [], am, readParen;
+        if (!off) off = 0; 
+
+        while (true) {
+            var s = parse_selector(str, off, hints);
+            a.push(s[1]);
+            s = lex(str, off = s[0]);
+            if (s && s[1] === " ") s = lex(str, off = s[0]);
+            if (!s) break;
+            // now we've parsed a selector, and have something else...
+            if (s[1] === ">" || s[1] === "~") {
+                if (s[1] === "~") hints.usesSiblingOp = true;
+                a.push(s[1]);
+                off = s[0];
+            } else if (s[1] === ",") {
+                if (am === undefined) am = [ ",", a ];
+                else am.push(a);
+                a = [];
+                off = s[0];
+            } else if (s[1] === ")") {
+                if (!nested) te("ucp", s[1]);
+                readParen = 1;
+                off = s[0];
+                break;
+            }
+        }
+        if (nested && !readParen) te("mcp", str);
+        if (am) am.push(a);
+        var rv;
+        if (!nested && hints.usesSiblingOp) {
+            rv = normalize(am ? am : a);
+        } else {
+            rv = am ? am : a;
+        }
+        return [off, rv];
+    }
+
+    function normalizeOne(sel) {
+        var sels = [], s;
+        for (var i = 0; i < sel.length; i++) {
+            if (sel[i] === '~') {
+                // `A ~ B` maps to `:has(:root > A) > B`
+                // `Z A ~ B` maps to `Z :has(:root > A) > B, Z:has(:root > A) > B`
+                // This first clause, takes care of the first case, and the first half of the latter case.
+                if (i < 2 || sel[i-2] != '>') {
+                    s = sel.slice(0,i-1);
+                    s = s.concat([{has:[[{pc: ":root"}, ">", sel[i-1]]]}, ">"]);
+                    s = s.concat(sel.slice(i+1));
+                    sels.push(s);
+                }
+                // here we take care of the second half of above:
+                // (`Z A ~ B` maps to `Z :has(:root > A) > B, Z :has(:root > A) > B`)
+                // and a new case:
+                // Z > A ~ B maps to Z:has(:root > A) > B
+                if (i > 1) {
+                    var at = sel[i-2] === '>' ? i-3 : i-2;
+                    s = sel.slice(0,at);
+                    var z = {};
+                    for (var k in sel[at]) if (sel[at].hasOwnProperty(k)) z[k] = sel[at][k];
+                    if (!z.has) z.has = [];
+                    z.has.push([{pc: ":root"}, ">", sel[i-1]]);
+                    s = s.concat(z, '>', sel.slice(i+1));
+                    sels.push(s);
+                }
+                break;
+            }
+        }
+        if (i == sel.length) return sel;
+        return sels.length > 1 ? [','].concat(sels) : sels[0];
+    }
+
+    function normalize(sels) {
+        if (sels[0] === ',') {
+            var r = [","];
+            for (var i = i; i < sels.length; i++) {
+                var s = normalizeOne(s[i]);
+                r = r.concat(s[0] === "," ? s.slice(1) : s);
+            }
+            return r;
+        } else {
+            return normalizeOne(sels);
+        }
+    }
+
+    function parse_selector(str, off, hints) {
+        var soff = off;
+        var s = { };
+        var l = lex(str, off);
+        // skip space
+        if (l && l[1] === " ") { soff = off = l[0]; l = lex(str, off); }
+        if (l && l[1] === toks.typ) {
+            s.type = l[2];
+            l = lex(str, (off = l[0]));
+        } else if (l && l[1] === "*") {
+            // don't bother representing the universal sel, '*' in the
+            // parse tree, cause it's the default
+            l = lex(str, (off = l[0]));
+        }
+
+        // now support either an id or a pc
+        while (true) {
+            if (l === undefined) {
+                break;
+            } else if (l[1] === toks.ide) {
+                if (s.id) te("nmi", l[1]);
+                s.id = l[2];
+            } else if (l[1] === toks.psc) {
+                if (s.pc || s.pf) te("mpc", l[1]);
+                // collapse first-child and last-child into nth-child expressions
+                if (l[2] === ":first-child") {
+                    s.pf = ":nth-child";
+                    s.a = 0;
+                    s.b = 1;
+                } else if (l[2] === ":last-child") {
+                    s.pf = ":nth-last-child";
+                    s.a = 0;
+                    s.b = 1;
+                } else {
+                    s.pc = l[2];
+                }
+            } else if (l[1] === toks.psf) {
+                if (l[2] === ":val" || l[2] === ":contains") {
+                    s.expr = [ undefined, l[2] === ":val" ? "=" : "*=", undefined];
+                    // any amount of whitespace, followed by paren, string, paren
+                    l = lex(str, (off = l[0]));
+                    if (l && l[1] === " ") l = lex(str, off = l[0]);
+                    if (!l || l[1] !== "(") te("pex", str);
+                    l = lex(str, (off = l[0]));
+                    if (l && l[1] === " ") l = lex(str, off = l[0]);
+                    if (!l || l[1] !== toks.str) te("sex", str);
+                    s.expr[2] = l[2];
+                    l = lex(str, (off = l[0]));
+                    if (l && l[1] === " ") l = lex(str, off = l[0]);
+                    if (!l || l[1] !== ")") te("epex", str);
+                } else if (l[2] === ":has") {
+                    // any amount of whitespace, followed by paren
+                    l = lex(str, (off = l[0]));
+                    if (l && l[1] === " ") l = lex(str, off = l[0]);
+                    if (!l || l[1] !== "(") te("pex", str);
+                    var h = parse(str, l[0], true);
+                    l[0] = h[0];
+                    if (!s.has) s.has = [];
+                    s.has.push(h[1]);
+                } else if (l[2] === ":expr") {
+                    if (s.expr) te("mexp", str);
+                    var e = exprParse(str, l[0]);
+                    l[0] = e[0];
+                    s.expr = e[1];
+                } else {
+                    if (s.pc || s.pf ) te("mpc", str);
+                    s.pf = l[2];
+                    var m = nthPat.exec(str.substr(l[0]));
+                    if (!m) te("mepf", str);
+                    if (m[5]) {
+                        s.a = 2;
+                        s.b = (m[5] === "odd") ? 1 : 0;
+                    } else if (m[6]) {
+                        s.a = 0;
+                        s.b = parseInt(m[6], 10);
+                    } else {
+                        s.a = parseInt((m[1] ? m[1] : "+") + (m[2] ? m[2] : "1"),10);
+                        s.b = m[3] ? parseInt(m[3] + m[4],10) : 0;
+                    }
+                    l[0] += m[0].length;
+                }
+            } else {
+                break;
+            }
+            l = lex(str, (off = l[0]));
+        }
+
+        // now if we didn't actually parse anything it's an error
+        if (soff === off) te("se", str);
+
+        return [off, s];
+    }
+
+    // THE EVALUATOR
+
+    function isArray(o) {
+        return Array.isArray ? Array.isArray(o) : 
+          toString.call(o) === "[object Array]";
+    }
+
+    function mytypeof(o) {
+        if (o === null) return "null";
+        var to = typeof o;
+        if (to === "object" && isArray(o)) to = "array";
+        return to;
+    }
+
+    function mn(node, sel, id, num, tot) {
+        var sels = [];
+        var cs = (sel[0] === ">") ? sel[1] : sel[0];
+        var m = true, mod;
+        if (cs.type) m = m && (cs.type === mytypeof(node));
+        if (cs.id)   m = m && (cs.id === id);
+        if (m && cs.pf) {
+            if (cs.pf === ":nth-last-child") num = tot - num;
+            else num++;
+            if (cs.a === 0) {
+                m = cs.b === num;
+            } else {
+                mod = ((num - cs.b) % cs.a);
+
+                m = (!mod && ((num*cs.a + cs.b) >= 0));
+            }
+        }
+        if (m && cs.has) {
+            // perhaps we should augment forEach to handle a return value
+            // that indicates "client cancels traversal"?
+            var bail = function() { throw 42; };
+            for (var i = 0; i < cs.has.length; i++) {
+                try {
+                    forEach(cs.has[i], node, bail);
+                } catch (e) {
+                    if (e === 42) continue;
+                }
+                m = false;
+                break;
+            }
+        }
+        if (m && cs.expr) {
+            m = exprEval(cs.expr, node);
+        }
+        // should we repeat this selector for descendants?
+        if (sel[0] !== ">" && sel[0].pc !== ":root") sels.push(sel);
+
+        if (m) {
+            // is there a fragment that we should pass down?
+            if (sel[0] === ">") { if (sel.length > 2) { m = false; sels.push(sel.slice(2)); } }
+            else if (sel.length > 1) { m = false; sels.push(sel.slice(1)); }
+        }
+
+        return [m, sels];
+    }
+
+    function forEach(sel, obj, fun, id, num, tot) {
+        var a = (sel[0] === ",") ? sel.slice(1) : [sel],
+        a0 = [],
+        call = false,
+        i = 0, j = 0, k, x;
+        for (i = 0; i < a.length; i++) {
+            x = mn(obj, a[i], id, num, tot);
+            if (x[0]) {
+                call = true;
+            }
+            for (j = 0; j < x[1].length; j++) {
+                a0.push(x[1][j]);
+            }
+        }
+        if (a0.length && typeof obj === "object") {
+            if (a0.length >= 1) {
+                a0.unshift(",");
+            }
+            if (isArray(obj)) {
+                for (i = 0; i < obj.length; i++) {
+                    forEach(a0, obj[i], fun, undefined, i, obj.length);
+                }
+            } else {
+                for (k in obj) {
+                    if (obj.hasOwnProperty(k)) {
+                        forEach(a0, obj[k], fun, k);
+                    }
+                }
+            }
+        }
+        if (call && fun) {
+            fun(obj);
+        }
+    }
+
+    function match(sel, obj) {
+        var a = [];
+        forEach(sel, obj, function(x) {
+            a.push(x);
+        });
+        return a;
+    }
+
+    function compile(sel) {
+        return {
+            sel: parse(sel)[1],
+            match: function(obj){
+                return match(this.sel, obj);
+            },
+            forEach: function(obj, fun) {
+                return forEach(this.sel, obj, fun);
+            }
+        };
+    }
+
+    exports._lex = lex;
+    exports._parse = parse;
+    exports.match = function (sel, obj) {
+        return compile(sel).match(obj);
+    };
+    exports.forEach = function(sel, obj, fun) {
+        return compile(sel).forEach(obj, fun);
+    };
+    exports.compile = compile;
+})(typeof exports === "undefined" ? (window.JSONSelect = {}) : exports);
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/conformance_tests.html b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/conformance_tests.html
new file mode 100644
index 0000000..a53dfd0
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/conformance_tests.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title> JSONSelect conformance tests </title>
+  <link href='css/style.css' type='text/css' rel='stylesheet'>
+</head>
+<body>
+These are the the official JSONQuery conformance tests.  <button id="runit">run tests</button>
+<div id="tests">
+</div>
+</body>
+<script src="../jsonselect.js"></script>
+<script src="js/jquery-1.6.1.min.js"></script>
+<script src="js/conf_tests.js"></script>
+</html>
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/css/style.css b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/css/style.css
new file mode 100644
index 0000000..f62cc9d
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/css/style.css
@@ -0,0 +1,43 @@
+pre {
+  color: #ddd;
+  background-color: #333;
+  padding: 1em;
+  border-radius: 1em;
+  -moz-border-radius: 1em;
+  -webkit-border-radius: 1em;
+}
+
+pre.selector {
+    text-align: center;
+    padding: .5em;
+    cursor: pointer;
+}
+
+pre.document {
+    max-height: 150px;
+    overflow: auto;
+}
+
+pre.success {
+    background-color: #363;
+}
+
+pre.failure {
+    background-color: #633;
+}
+
+table {
+    width: 100%;
+}
+
+table td {
+    width: 50%;
+}
+
+table pre {
+    height: 100%;
+}
+
+h1,h2 {
+    text-align: center;
+}
\ No newline at end of file
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/js/conf_tests.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/js/conf_tests.js
new file mode 100644
index 0000000..42711ea
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/js/conf_tests.js
@@ -0,0 +1,77 @@
+$(document).ready(function() {
+    var tests = {};
+
+    $("#runit").click(function() {
+        for (var k in tests) {
+            var obj = JSON.parse($("." + k + "_document").text());
+            for (var i = 0; i < tests[k].length; i++) {
+                var n = tests[k][i];
+                var cl = k + "_" + n;
+                var b = $("." + cl + "_output.before");
+                var a = $("." + cl + "_output.after");
+                var s = $("." + cl + "_selector.selector");
+                try {
+                    a.text("");
+                    JSONSelect.forEach(s.text(), obj, function(m) {
+                        a.text($.trim(a.text() + "\n" + JSON.stringify(m, null, "    ")));
+                    });
+                } catch(e) {
+                    a.text("Error: " + e);
+                }
+                if (a.text() === b.text()) s.addClass("success").removeClass("failure");
+                else s.addClass("failure").removeClass("success");
+            }
+        }
+    });
+
+    function fetchFile(p, c) {
+        $.get(p, function (data) {
+            $("." + c).text($.trim(data));
+        });
+    }
+
+    function renderTests() {
+        function setClickToggle(cTarget, node) {
+            cTarget.click(function() { node.toggle("medium"); });
+        }
+
+        var c = $("<div/>");
+        for (var k in tests) {
+            c.append($("<h1/>").text("document: " + k));
+            var cl = k + "_document";
+            c.append($("<pre/>").addClass(cl).addClass("document").text("loading document..."));
+            fetchFile("tests/" + k + ".json", cl);
+            for (var i = 0; i < tests[k].length; i++) {
+                var n = tests[k][i];
+                var cl = k + "_" + n + "_selector";
+                var s = $("<pre/>").addClass(cl).addClass("selector").text("loading selector...");
+                c.append(s);
+                fetchFile("tests/" + k + "_" + n + ".selector", cl);
+                cl = k + "_" + n + "_output";
+                var t = $("<table/>").append($("<tr/>").append(
+                    $("<td/>").append($("<pre/>").addClass(cl).addClass("before").text("loading output..."))).append(
+                    $("<td/>").append($("<pre/>").addClass(cl).addClass("after").text("... test output ..."))));
+
+                c.append(t);
+                t.hide();
+                setClickToggle(s, t);
+                fetchFile("tests/" + k + "_" + n + ".output", cl + ".before");
+            }
+        }
+        c.appendTo($("#tests"));
+    }
+
+    $.get("tests/alltests.txt", function (data) {
+        var lines = data.split("\n");
+        for (var i = 0; i < lines.length; i++) {
+            var f = $.trim(lines[i]);
+            if (f.length == 0) continue;
+            var m = /^([A-Za-z]+)_(.+)\.selector$/.exec(f);
+            if (m) {
+                if (!tests.hasOwnProperty(m[1])) tests[m[1]] = [];
+                tests[m[1]].push(m[2]);
+            }
+        }
+        renderTests();
+    });
+});
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/js/doctest.css b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/js/doctest.css
new file mode 100644
index 0000000..7403feb
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/js/doctest.css
@@ -0,0 +1,69 @@
+pre {
+  overflow: auto;
+}
+
+pre.doctest {
+  border-left: 3px solid #99f;
+  padding-left: 1em;
+}
+
+pre.output {
+  border-left: 3px solid #9f9;
+  padding-left: 1em;
+}
+
+.doctest-example-prompt {
+  color: #333;
+}
+
+.doctest-success {
+  color: #060;
+}
+
+.doctest-failure {
+  color: #600;
+}
+
+.doctest-example-detail {
+  color: #060;
+  font-weight: bold;
+}
+
+a.doctest-failure-link {
+  text-decoration: none;
+}
+
+a.doctest-failure-link:hover {
+  text-decoration: underline;
+}
+
+.doctest-example:target {
+  border-left: 3px solid #f00;
+}
+
+div.test:target {
+  border: 3px solid #ff0;
+}
+
+div.test {
+  border: 1px solid #999;
+  margin-bottom: 1em;
+}
+
+div.test .test-id {
+  position: relative;
+  float: right;
+  background-color: #000;
+  color: #bbb;
+  padding: 3px;
+}
+
+div.test .test-id a:link,
+div.test .test-id a:visited {
+  color: #bbb;
+  text-decoration: none;
+}
+
+div.test .test-id a:hover {
+  text-decoration: underline;
+}
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/js/doctest.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/js/doctest.js
new file mode 100644
index 0000000..0cfb7eb
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/js/doctest.js
@@ -0,0 +1,1397 @@
+/*
+
+Javascript doctest runner
+Copyright 2006-2010 Ian Bicking
+
+This program is free software; you can redistribute it and/or modify it under
+the terms of the MIT License.
+
+*/
+
+
+function doctest(verbosity/*default=0*/, elements/*optional*/,
+                 outputId/*optional*/) {
+  var output = document.getElementById(outputId || 'doctestOutput');
+  var reporter = new doctest.Reporter(output, verbosity || 0);
+  if (elements) {
+      if (typeof elements == 'string') {
+        // Treat it as an id
+        elements = [document.getElementById(elementId)];
+      }
+      if (! elements.length) {
+          throw('No elements');
+      }
+      var suite = new doctest.TestSuite(elements, reporter);
+  } else {
+      var els = doctest.getElementsByTagAndClassName('pre', 'doctest');
+      var suite = new doctest.TestSuite(els, reporter);
+  }
+  suite.run();
+}
+
+doctest.runDoctest = function (el, reporter) {
+  logDebug('Testing element', el);
+  reporter.startElement(el);
+  if (el === null) {
+    throw('runDoctest() with a null element');
+  }
+  var parsed = new doctest.Parser(el);
+  var runner = new doctest.JSRunner(reporter);
+  runner.runParsed(parsed);
+};
+
+doctest.TestSuite = function (els, reporter) {
+  if (this === window) {
+    throw('you forgot new!');
+  }
+  this.els = els;
+  this.parsers = [];
+  for (var i=0; i<els.length; i++) {
+    this.parsers.push(new doctest.Parser(els[i]));
+  }
+  this.reporter = reporter;
+};
+
+doctest.TestSuite.prototype.run = function (ctx) {
+  if (! ctx) {
+    ctx = new doctest.Context(this);
+  }
+  if (! ctx.runner ) {
+    ctx.runner = new doctest.JSRunner(this.reporter);
+  }
+  return ctx.run();
+};
+
+// FIXME: should this just be part of TestSuite?
+doctest.Context = function (testSuite) {
+  if (this === window) {
+    throw('You forgot new!');
+  }
+  this.testSuite = testSuite;
+  this.runner = null;
+};
+
+doctest.Context.prototype.run = function (parserIndex) {
+  var self = this;
+  parserIndex = parserIndex || 0;
+  if (parserIndex >= this.testSuite.parsers.length) {
+    logInfo('All examples from all sections tested');
+    this.runner.reporter.finish();
+    return;
+  }
+  logInfo('Testing example ' + (parserIndex+1) + ' of '
+           + this.testSuite.parsers.length);
+  var runNext = function () {
+    self.run(parserIndex+1);
+  };
+  this.runner.runParsed(this.testSuite.parsers[parserIndex], 0, runNext);
+};
+
+doctest.Parser = function (el) {
+  if (this === window) {
+    throw('you forgot new!');
+  }
+  if (! el) {
+    throw('Bad call to doctest.Parser');
+  }
+  if (el.getAttribute('parsed-id')) {
+    var examplesID = el.getAttribute('parsed-id');
+    if (doctest._allExamples[examplesID]) {
+      this.examples = doctest._allExamples[examplesID];
+      return;
+    }
+  }
+  var newHTML = document.createElement('span');
+  newHTML.className = 'doctest-example-set';
+  var examplesID = doctest.genID('example-set');
+  newHTML.setAttribute('id', examplesID);
+  el.setAttribute('parsed-id', examplesID);
+  var text = doctest.getText(el);
+  var lines = text.split(/(?:\r\n|\r|\n)/);
+  this.examples = [];
+  var example_lines = [];
+  var output_lines = [];
+  for (var i=0; i<lines.length; i++) {
+    var line = lines[i];
+    if (/^[$]/.test(line)) {
+      if (example_lines.length) {
+        var ex = new doctest.Example(example_lines, output_lines);
+        this.examples.push(ex);
+        newHTML.appendChild(ex.createSpan());
+      }
+      example_lines = [];
+      output_lines = [];
+      line = line.substr(1).replace(/ *$/, '').replace(/^ /, '');
+      example_lines.push(line);
+    } else if (/^>/.test(line)) {
+      if (! example_lines.length) {
+        throw('Bad example: '+doctest.repr(line)+'\n'
+              +'> line not preceded by $');
+      }
+      line = line.substr(1).replace(/ *$/, '').replace(/^ /, '');
+      example_lines.push(line);
+    } else {
+      output_lines.push(line);
+    }
+  }
+  if (example_lines.length) {
+    var ex = new doctest.Example(example_lines, output_lines);
+    this.examples.push(ex);
+    newHTML.appendChild(ex.createSpan());
+  }
+  el.innerHTML = '';
+  el.appendChild(newHTML);
+  doctest._allExamples[examplesID] = this.examples;
+};
+
+doctest._allExamples = {};
+
+doctest.Example = function (example, output) {
+  if (this === window) {
+    throw('you forgot new!');
+  }
+  this.example = example.join('\n');
+  this.output = output.join('\n');
+  this.htmlID = null;
+  this.detailID = null;
+};
+
+doctest.Example.prototype.createSpan = function () {
+  var id = doctest.genID('example');
+  var span = document.createElement('span');
+  span.className = 'doctest-example';
+  span.setAttribute('id', id);
+  this.htmlID = id;
+  var exampleSpan = document.createElement('span');
+  exampleSpan.className = 'doctest-example-code';
+  var exampleLines = this.example.split(/\n/);
+  for (var i=0; i<exampleLines.length; i++) {
+    var promptSpan = document.createElement('span');
+    promptSpan.className = 'doctest-example-prompt';
+    promptSpan.innerHTML = i == 0 ? '$ ' : '&gt; ';
+    exampleSpan.appendChild(promptSpan);
+    var lineSpan = document.createElement('span');
+    lineSpan.className = 'doctest-example-code-line';
+    lineSpan.appendChild(document.createTextNode(doctest.rstrip(exampleLines[i])));
+    exampleSpan.appendChild(lineSpan);
+    exampleSpan.appendChild(document.createTextNode('\n'));
+  }
+  span.appendChild(exampleSpan);
+  var outputSpan = document.createElement('span');
+  outputSpan.className = 'doctest-example-output';
+  outputSpan.appendChild(document.createTextNode(this.output));
+  span.appendChild(outputSpan);
+  span.appendChild(document.createTextNode('\n'));
+  return span;
+};
+
+doctest.Example.prototype.markExample = function (name, detail) {
+  if (! this.htmlID) {
+    return;
+  }
+  if (this.detailID) {
+    var el = document.getElementById(this.detailID);
+    el.parentNode.removeChild(el);
+    this.detailID = null;
+  }
+  var span = document.getElementById(this.htmlID);
+  span.className = span.className.replace(/ doctest-failure/, '')
+                   .replace(/ doctest-success/, '')
+                   + ' ' + name;
+  if (detail) {
+    this.detailID = doctest.genID('doctest-example-detail');
+    var detailSpan = document.createElement('span');
+    detailSpan.className = 'doctest-example-detail';
+    detailSpan.setAttribute('id', this.detailID);
+    detailSpan.appendChild(document.createTextNode(detail));
+    span.appendChild(detailSpan);
+  }
+};
+
+doctest.Reporter = function (container, verbosity) {
+  if (this === window) {
+    throw('you forgot new!');
+  }
+  if (! container) {
+    throw('No container passed to doctest.Reporter');
+  }
+  this.container = container;
+  this.verbosity = verbosity;
+  this.success = 0;
+  this.failure = 0;
+  this.elements = 0;
+};
+
+doctest.Reporter.prototype.startElement = function (el) {
+  this.elements += 1;
+  logDebug('Adding element', el);
+};
+
+doctest.Reporter.prototype.reportSuccess = function (example, output) {
+  if (this.verbosity > 0) {
+    if (this.verbosity > 1) {
+      this.write('Trying:\n');
+      this.write(this.formatOutput(example.example));
+      this.write('Expecting:\n');
+      this.write(this.formatOutput(example.output));
+      this.write('ok\n');
+    } else {
+      this.writeln(example.example + ' ... passed!');
+    }
+  }
+  this.success += 1;
+  if ((example.output.indexOf('...') >= 0
+       || example.output.indexOf('?') >= 0)
+      && output) {
+    example.markExample('doctest-success', 'Output:\n' + output);
+  } else {
+    example.markExample('doctest-success');
+  }
+};
+
+doctest.Reporter.prototype.reportFailure = function (example, output) {
+  this.write('Failed example:\n');
+  this.write('<span style="color: #00f"><a href="#'
+             + example.htmlID
+             + '" class="doctest-failure-link" title="Go to example">'
+             + this.formatOutput(example.example)
+             +'</a></span>');
+  this.write('Expected:\n');
+  this.write(this.formatOutput(example.output));
+  this.write('Got:\n');
+  this.write(this.formatOutput(output));
+  this.failure += 1;
+  example.markExample('doctest-failure', 'Actual output:\n' + output);
+};
+
+doctest.Reporter.prototype.finish = function () {
+  this.writeln((this.success+this.failure)
+               + ' tests in ' + this.elements + ' items.');
+  if (this.failure) {
+    var color = '#f00';
+  } else {
+    var color = '#0f0';
+  }
+  this.writeln('<span class="passed">' + this.success + '</span> tests of '
+               + '<span class="total">' + (this.success+this.failure) + '</span> passed, '
+               + '<span class="failed" style="color: '+color+'">'
+               + this.failure + '</span> failed.');
+};
+
+doctest.Reporter.prototype.writeln = function (text) {
+  this.write(text + '\n');
+};
+
+doctest.Reporter.prototype.write = function (text) {
+  var leading = /^[ ]*/.exec(text)[0];
+  text = text.substr(leading.length);
+  for (var i=0; i<leading.length; i++) {
+    text = String.fromCharCode(160)+text;
+  }
+  text = text.replace(/\n/g, '<br>');
+  this.container.innerHTML += text;
+};
+
+doctest.Reporter.prototype.formatOutput = function (text) {
+  if (! text) {
+    return '    <span style="color: #999">(nothing)</span>\n';
+  }
+  var lines = text.split(/\n/);
+  var output = '';
+  for (var i=0; i<lines.length; i++) {
+    output += '    '+doctest.escapeSpaces(doctest.escapeHTML(lines[i]))+'\n';
+  }
+  return output;
+};
+
+doctest.JSRunner = function (reporter) {
+  if (this === window) {
+    throw('you forgot new!');
+  }
+  this.reporter = reporter;
+};
+
+doctest.JSRunner.prototype.runParsed = function (parsed, index, finishedCallback) {
+  var self = this;
+  index = index || 0;
+  if (index >= parsed.examples.length) {
+    if (finishedCallback) {
+      finishedCallback();
+    }
+    return;
+  }
+  var example = parsed.examples[index];
+  if (typeof example == 'undefined') {
+    throw('Undefined example (' + (index+1) + ' of ' + parsed.examples.length + ')');
+  }
+  doctest._waitCond = null;
+  this.run(example);
+  var finishThisRun = function () {
+    self.finishRun(example);
+    if (doctest._AbortCalled) {
+      // FIXME: I need to find a way to make this more visible:
+      logWarn('Abort() called');
+      return;
+    }
+    self.runParsed(parsed, index+1, finishedCallback);
+  };
+  if (doctest._waitCond !== null) {
+    if (typeof doctest._waitCond == 'number') {
+      var condition = null;
+      var time = doctest._waitCond;
+      var maxTime = null;
+    } else {
+      var condition = doctest._waitCond;
+      // FIXME: shouldn't be hard-coded
+      var time = 100;
+      var maxTime = doctest._waitTimeout || doctest.defaultTimeout;
+    }
+    var start = (new Date()).getTime();
+    var timeoutFunc = function () {
+      if (condition === null
+          || condition()) {
+        finishThisRun();
+      } else {
+        // Condition not met, try again soon...
+        if ((new Date()).getTime() - start > maxTime) {
+          // Time has run out
+          var msg = 'Error: wait(' + repr(condition) + ') has timed out';
+          writeln(msg);
+          logDebug(msg);
+          logDebug('Timeout after ' + ((new Date()).getTime() - start)
+                   + ' milliseconds');
+          finishThisRun();
+          return;
+        }
+        setTimeout(timeoutFunc, time);
+      }
+    };
+    setTimeout(timeoutFunc, time);
+  } else {
+    finishThisRun();
+  }
+};
+
+doctest.formatTraceback = function (e, skipFrames) {
+  skipFrames = skipFrames || 0;
+  var lines = [];
+  if (typeof e == 'undefined' || !e) {
+    var caughtErr = null;
+    try {
+      (null).foo;
+    } catch (caughtErr) {
+      e = caughtErr;
+    }
+    skipFrames++;
+  }
+  if (e.stack) {
+    var stack = e.stack.split('\n');
+    for (var i=skipFrames; i<stack.length; i++) {
+      if (stack[i] == '@:0' || ! stack[i]) {
+        continue;
+      }
+      if (stack[i].indexOf('@') == -1) {
+        lines.push(stack[i]);
+        continue;
+      }
+      var parts = stack[i].split('@');
+      var context = parts[0];
+      parts = parts[1].split(':');
+      var filename = parts[parts.length-2].split('/');
+      filename = filename[filename.length-1];
+      var lineno = parts[parts.length-1];
+      context = context.replace('\\n', '\n');
+      if (context != '' && filename != 'doctest.js') {
+        lines.push('  ' + context + ' -> ' + filename + ':' + lineno);
+      }
+    }
+  }
+  if (lines.length) {
+    return lines;
+  } else {
+    return null;
+  }
+};
+
+doctest.logTraceback = function (e, skipFrames) {
+  var tracebackLines = doctest.formatTraceback(e, skipFrames);
+  if (! tracebackLines) {
+    return;
+  }
+  for (var i=0; i<tracebackLines.length; i++) {
+    logDebug(tracebackLines[i]);
+  }
+};
+
+doctest.JSRunner.prototype.run = function (example) {
+  this.capturer = new doctest.OutputCapturer();
+  this.capturer.capture();
+  try {
+    var result = doctest.eval(example.example);
+  } catch (e) {
+    var tracebackLines = doctest.formatTraceback(e);
+    writeln('Error: ' + (e.message || e));
+    var result = null;
+    logWarn('Error in expression: ' + example.example);
+    logDebug('Traceback for error', e);
+    if (tracebackLines) {
+      for (var i=0; i<tracebackLines.length; i++) {
+        logDebug(tracebackLines[i]);
+      }
+    }
+    if (e instanceof Abort) {
+      throw e;
+    }
+  }
+  if (typeof result != 'undefined'
+      && result !== null
+      && example.output) {
+    writeln(doctest.repr(result));
+  }
+};
+
+doctest._AbortCalled = false;
+
+doctest.Abort = function (message) {
+  if (this === window) {
+    return new Abort(message);
+  }
+  this.message = message;
+  // We register this so Abort can be raised in an async call:
+  doctest._AbortCalled = true;
+};
+
+doctest.Abort.prototype.toString = function () {
+  return this.message;
+};
+
+if (typeof Abort == 'undefined') {
+  Abort = doctest.Abort;
+}
+
+doctest.JSRunner.prototype.finishRun = function(example) {
+  this.capturer.stopCapture();
+  var success = this.checkResult(this.capturer.output, example.output);
+  if (success) {
+    this.reporter.reportSuccess(example, this.capturer.output);
+  } else {
+    this.reporter.reportFailure(example, this.capturer.output);
+    logDebug('Failure: '+doctest.repr(example.output)
+             +' != '+doctest.repr(this.capturer.output));
+    if (location.href.search(/abort/) != -1) {
+      doctest.Abort('abort on first failure');
+    }
+  }
+};
+
+doctest.JSRunner.prototype.checkResult = function (got, expected) {
+  // Make sure trailing whitespace doesn't matter:
+  got = got.replace(/ +\n/, '\n');
+  expected = expected.replace(/ +\n/, '\n');
+  got = got.replace(/[ \n\r]*$/, '') + '\n';
+  expected = expected.replace(/[ \n\r]*$/, '') + '\n';
+  if (expected == '...\n') {
+    return true;
+  }
+  expected = RegExp.escape(expected);
+  // Note: .* doesn't match newlines, [^] doesn't work on IE
+  expected = '^' + expected.replace(/\\\.\\\.\\\./g, "[\\S\\s\\r\\n]*") + '$';
+  expected = expected.replace(/\\\?/g, "[a-zA-Z0-9_.]+");
+  expected = expected.replace(/[ \t]+/g, " +");
+  expected = expected.replace(/\n/g, '\\n');
+  var re = new RegExp(expected);
+  var result = got.search(re) != -1;
+  if (! result) {
+    if (doctest.strip(got).split('\n').length > 1) {
+      // If it's only one line it's not worth showing this
+      var check = this.showCheckDifference(got, expected);
+      logWarn('Mismatch of output (line-by-line comparison follows)');
+      for (var i=0; i<check.length; i++) {
+        logDebug(check[i]);
+      }
+    }
+  }
+  return result;
+};
+
+doctest.JSRunner.prototype.showCheckDifference = function (got, expectedRegex) {
+  if (expectedRegex.charAt(0) != '^') {
+    throw 'Unexpected regex, no leading ^';
+  }
+  if (expectedRegex.charAt(expectedRegex.length-1) != '$') {
+    throw 'Unexpected regex, no trailing $';
+  }
+  expectedRegex = expectedRegex.substr(1, expectedRegex.length-2);
+  // Technically this might not be right, but this is all a heuristic:
+  var expectedRegex = expectedRegex.replace(/\(\?:\.\|\[\\r\\n\]\)\*/g, '...');
+  var expectedLines = expectedRegex.split('\\n');
+  for (var i=0; i<expectedLines.length; i++) {
+    expectedLines[i] = expectedLines[i].replace(/\.\.\./g, '(?:.|[\r\n])*');
+  }
+  var gotLines = got.split('\n');
+  var result = [];
+  var totalLines = expectedLines.length > gotLines.length ?
+    expectedLines.length : gotLines.length;
+  function displayExpectedLine(line) {
+    return line;
+    line = line.replace(/\[a-zA-Z0-9_.\]\+/g, '?');
+    line = line.replace(/ \+/g, ' ');
+    line = line.replace(/\(\?:\.\|\[\\r\\n\]\)\*/g, '...');
+    // FIXME: also unescape values? e.g., * became \*
+    return line;
+  }
+  for (var i=0; i<totalLines; i++) {
+    if (i >= expectedLines.length) {
+      result.push('got extra line: ' + repr(gotLines[i]));
+      continue;
+    } else if (i >= gotLines.length) {
+      result.push('expected extra line: ' + displayExpectedLine(expectedLines[i]));
+      continue;
+    }
+    var gotLine = gotLines[i];
+    try {
+      var expectRE = new RegExp('^' + expectedLines[i] + '$');
+    } catch (e) {
+      result.push('regex match failed: ' + repr(gotLine) + ' ('
+            + expectedLines[i] + ')');
+      continue;
+    }
+    if (gotLine.search(expectRE) != -1) {
+      result.push('match: ' + repr(gotLine));
+    } else {
+      result.push('no match: ' + repr(gotLine) + ' ('
+            + displayExpectedLine(expectedLines[i]) + ')');
+    }
+  }
+  return result;
+};
+
+// Should I really be setting this on RegExp?
+RegExp.escape = function (text) {
+  if (!arguments.callee.sRE) {
+    var specials = [
+      '/', '.', '*', '+', '?', '|',
+      '(', ')', '[', ']', '{', '}', '\\'
+    ];
+    arguments.callee.sRE = new RegExp(
+      '(\\' + specials.join('|\\') + ')', 'g'
+    );
+  }
+  return text.replace(arguments.callee.sRE, '\\$1');
+};
+
+doctest.OutputCapturer = function () {
+  if (this === window) {
+    throw('you forgot new!');
+  }
+  this.output = '';
+};
+
+doctest._output = null;
+
+doctest.OutputCapturer.prototype.capture = function () {
+  doctest._output = this;
+};
+
+doctest.OutputCapturer.prototype.stopCapture = function () {
+  doctest._output = null;
+};
+
+doctest.OutputCapturer.prototype.write = function (text) {
+  if (typeof text == 'string') {
+    this.output += text;
+  } else {
+    this.output += repr(text);
+  }
+};
+
+// Used to create unique IDs:
+doctest._idGen = 0;
+
+doctest.genID = function (prefix) {
+  prefix = prefix || 'generic-doctest';
+  var id = doctest._idGen++;
+  return prefix + '-' + doctest._idGen;
+};
+
+doctest.writeln = function () {
+  for (var i=0; i<arguments.length; i++) {
+    write(arguments[i]);
+    if (i) {
+      write(' ');
+    }
+  }
+  write('\n');
+};
+
+if (typeof writeln == 'undefined') {
+  writeln = doctest.writeln;
+}
+
+doctest.write = function (text) {
+  if (doctest._output !== null) {
+    doctest._output.write(text);
+  } else {
+    log(text);
+  }
+};
+
+if (typeof write == 'undefined') {
+  write = doctest.write;
+}
+
+doctest._waitCond = null;
+
+function wait(conditionOrTime, hardTimeout) {
+  // FIXME: should support a timeout even with a condition
+  if (typeof conditionOrTime == 'undefined'
+      || conditionOrTime === null) {
+    // same as wait-some-small-amount-of-time
+    conditionOrTime = 0;
+  }
+  doctest._waitCond = conditionOrTime;
+  doctest._waitTimeout = hardTimeout;
+};
+
+doctest.wait = wait;
+
+doctest.assert = function (expr, statement) {
+  if (typeof expr == 'string') {
+    if (! statement) {
+      statement = expr;
+    }
+    expr = doctest.eval(expr);
+  }
+  if (! expr) {
+    throw('AssertionError: '+statement);
+  }
+};
+
+if (typeof assert == 'undefined') {
+  assert = doctest.assert;
+}
+
+doctest.getText = function (el) {
+  if (! el) {
+    throw('You must pass in an element');
+  }
+  var text = '';
+  for (var i=0; i<el.childNodes.length; i++) {
+    var sub = el.childNodes[i];
+    if (sub.nodeType == 3) {
+      // TEXT_NODE
+      text += sub.nodeValue;
+    } else if (sub.childNodes) {
+      text += doctest.getText(sub);
+    }
+  }
+  return text;
+};
+
+doctest.reload = function (button/*optional*/) {
+  if (button) {
+    button.innerHTML = 'reloading...';
+    button.disabled = true;
+  }
+  location.reload();
+};
+
+/* Taken from MochiKit, with an addition to print objects */
+doctest.repr = function (o, indentString, maxLen) {
+    indentString = indentString || '';
+    if (doctest._reprTracker === null) {
+      var iAmTheTop = true;
+      doctest._reprTracker = [];
+    } else {
+      var iAmTheTop = false;
+    }
+    try {
+      if (doctest._reprTrackObj(o)) {
+        return '..recursive..';
+      }
+      if (maxLen === undefined) {
+        maxLen = 120;
+      }
+      if (typeof o == 'undefined') {
+          return 'undefined';
+      } else if (o === null) {
+          return "null";
+      }
+      try {
+          if (typeof(o.__repr__) == 'function') {
+              return o.__repr__(indentString, maxLen);
+          } else if (typeof(o.repr) == 'function' && o.repr != arguments.callee) {
+              return o.repr(indentString, maxLen);
+          }
+          for (var i=0; i<doctest.repr.registry.length; i++) {
+              var item = doctest.repr.registry[i];
+              if (item[0](o)) {
+                  return item[1](o, indentString, maxLen);
+              }
+          }
+      } catch (e) {
+          if (typeof(o.NAME) == 'string' && (
+                  o.toString == Function.prototype.toString ||
+                      o.toString == Object.prototype.toString)) {
+              return o.NAME;
+          }
+      }
+      try {
+          var ostring = (o + "");
+          if (ostring == '[object Object]' || ostring == '[object]') {
+            ostring = doctest.objRepr(o, indentString, maxLen);
+          }
+      } catch (e) {
+          return "[" + typeof(o) + "]";
+      }
+      if (typeof(o) == "function") {
+          var ostring = ostring.replace(/^\s+/, "").replace(/\s+/g, " ");
+          var idx = ostring.indexOf("{");
+          if (idx != -1) {
+              ostring = ostring.substr(o, idx) + "{...}";
+          }
+      }
+      return ostring;
+    } finally {
+      if (iAmTheTop) {
+        doctest._reprTracker = null;
+      }
+    }
+};
+
+doctest._reprTracker = null;
+
+doctest._reprTrackObj = function (obj) {
+  if (typeof obj != 'object') {
+    return false;
+  }
+  for (var i=0; i<doctest._reprTracker.length; i++) {
+    if (doctest._reprTracker[i] === obj) {
+      return true;
+    }
+  }
+  doctest._reprTracker.push(obj);
+  return false;
+};
+
+doctest._reprTrackSave = function () {
+  return doctest._reprTracker.length-1;
+};
+
+doctest._reprTrackRestore = function (point) {
+  doctest._reprTracker.splice(point, doctest._reprTracker.length - point);
+};
+
+doctest._sortedKeys = function (obj) {
+  var keys = [];
+  for (var i in obj) {
+    // FIXME: should I use hasOwnProperty?
+    if (typeof obj.prototype == 'undefined'
+        || obj[i] !== obj.prototype[i]) {
+      keys.push(i);
+    }
+  }
+  keys.sort();
+  return keys;
+};
+
+doctest.objRepr = function (obj, indentString, maxLen) {
+  var restorer = doctest._reprTrackSave();
+  var ostring = '{';
+  var keys = doctest._sortedKeys(obj);
+  for (var i=0; i<keys.length; i++) {
+    if (ostring != '{') {
+      ostring += ', ';
+    }
+    ostring += keys[i] + ': ' + doctest.repr(obj[keys[i]], indentString, maxLen);
+  }
+  ostring += '}';
+  if (ostring.length > (maxLen - indentString.length)) {
+    doctest._reprTrackRestore(restorer);
+    return doctest.multilineObjRepr(obj, indentString, maxLen);
+  }
+  return ostring;
+};
+
+doctest.multilineObjRepr = function (obj, indentString, maxLen) {
+  var keys = doctest._sortedKeys(obj);
+  var ostring = '{\n';
+  for (var i=0; i<keys.length; i++) {
+    ostring += indentString + '  ' + keys[i] + ': ';
+    ostring += doctest.repr(obj[keys[i]], indentString+'  ', maxLen);
+    if (i != keys.length - 1) {
+      ostring += ',';
+    }
+    ostring += '\n';
+  }
+  ostring += indentString + '}';
+  return ostring;
+};
+
+doctest.arrayRepr = function (obj, indentString, maxLen) {
+  var restorer = doctest._reprTrackSave();
+  var s = "[";
+  for (var i=0; i<obj.length; i++) {
+    s += doctest.repr(obj[i], indentString, maxLen);
+    if (i != obj.length-1) {
+      s += ", ";
+    }
+  }
+  s += "]";
+  if (s.length > (maxLen + indentString.length)) {
+    doctest._reprTrackRestore(restorer);
+    return doctest.multilineArrayRepr(obj, indentString, maxLen);
+  }
+  return s;
+};
+
+doctest.multilineArrayRepr = function (obj, indentString, maxLen) {
+  var s = "[\n";
+  for (var i=0; i<obj.length; i++) {
+    s += indentString + '  ' + doctest.repr(obj[i], indentString+'  ', maxLen);
+    if (i != obj.length - 1) {
+      s += ',';
+    }
+    s += '\n';
+  }
+  s += indentString + ']';
+  return s;
+};
+
+doctest.xmlRepr = function (doc, indentString) {
+  var i;
+  if (doc.nodeType == doc.DOCUMENT_NODE) {
+    return doctest.xmlRepr(doc.childNodes[0], indentString);
+  }
+  indentString = indentString || '';
+  var s = indentString + '<' + doc.tagName;
+  var attrs = [];
+  if (doc.attributes && doc.attributes.length) {
+    for (i=0; i<doc.attributes.length; i++) {
+      attrs.push(doc.attributes[i].nodeName);
+    }
+    attrs.sort();
+    for (i=0; i<attrs.length; i++) {
+      s += ' ' + attrs[i] + '="';
+      var value = doc.getAttribute(attrs[i]);
+      value = value.replace('&', '&amp;');
+      value = value.replace('"', '&quot;');
+      s += value;
+      s += '"';
+    }
+  }
+  if (! doc.childNodes.length) {
+    s += ' />';
+    return s;
+  } else {
+    s += '>';
+  }
+  var hasNewline = false;
+  for (i=0; i<doc.childNodes.length; i++) {
+    var el = doc.childNodes[i];
+    if (el.nodeType == doc.TEXT_NODE) {
+      s += doctest.strip(el.textContent);
+    } else {
+      if (! hasNewline) {
+        s += '\n';
+        hasNewline = true;
+      }
+      s += doctest.xmlRepr(el, indentString + '  ');
+      s += '\n';
+    }
+  }
+  if (hasNewline) {
+    s += indentString;
+  }
+  s += '</' + doc.tagName + '>';
+  return s;
+};
+
+doctest.repr.registry = [
+    [function (o) {
+         return typeof o == 'string';},
+     function (o) {
+         o = '"' + o.replace(/([\"\\])/g, '\\$1') + '"';
+         o = o.replace(/[\f]/g, "\\f")
+         .replace(/[\b]/g, "\\b")
+         .replace(/[\n]/g, "\\n")
+         .replace(/[\t]/g, "\\t")
+         .replace(/[\r]/g, "\\r");
+         return o;
+     }],
+    [function (o) {
+         return typeof o == 'number';},
+     function (o) {
+         return o + "";
+     }],
+    [function (o) {
+          return (typeof o == 'object' && o.xmlVersion);
+     },
+     doctest.xmlRepr],
+    [function (o) {
+         var typ = typeof o;
+         if ((typ != 'object' && ! (type == 'function' && typeof o.item == 'function')) ||
+             o === null ||
+             typeof o.length != 'number' ||
+             o.nodeType === 3) {
+             return false;
+         }
+         return true;
+     },
+     doctest.arrayRepr
+     ]];
+
+doctest.objDiff = function (orig, current) {
+  var result = {
+    added: {},
+    removed: {},
+    changed: {},
+    same: {}
+  };
+  for (var i in orig) {
+    if (! (i in current)) {
+      result.removed[i] = orig[i];
+    } else if (orig[i] !== current[i]) {
+      result.changed[i] = [orig[i], current[i]];
+    } else {
+      result.same[i] = orig[i];
+    }
+  }
+  for (i in current) {
+    if (! (i in orig)) {
+      result.added[i] = current[i];
+    }
+  }
+  return result;
+};
+
+doctest.writeDiff = function (orig, current, indentString) {
+  if (typeof orig != 'object' || typeof current != 'object') {
+    writeln(indentString + repr(orig, indentString) + ' -> ' + repr(current, indentString));
+    return;
+  }
+  indentString = indentString || '';
+  var diff = doctest.objDiff(orig, current);
+  var i, keys;
+  var any = false;
+  keys = doctest._sortedKeys(diff.added);
+  for (i=0; i<keys.length; i++) {
+    any = true;
+    writeln(indentString + '+' + keys[i] + ': '
+            + repr(diff.added[keys[i]], indentString));
+  }
+  keys = doctest._sortedKeys(diff.removed);
+  for (i=0; i<keys.length; i++) {
+    any = true;
+    writeln(indentString + '-' + keys[i] + ': '
+            + repr(diff.removed[keys[i]], indentString));
+  }
+  keys = doctest._sortedKeys(diff.changed);
+  for (i=0; i<keys.length; i++) {
+    any = true;
+    writeln(indentString + keys[i] + ': '
+            + repr(diff.changed[keys[i]][0], indentString)
+            + ' -> '
+            + repr(diff.changed[keys[i]][1], indentString));
+  }
+  if (! any) {
+    writeln(indentString + '(no changes)');
+  }
+};
+
+doctest.objectsEqual = function (ob1, ob2) {
+  var i;
+  if (typeof ob1 != 'object' || typeof ob2 != 'object') {
+    return ob1 === ob2;
+  }
+  for (i in ob1) {
+    if (ob1[i] !== ob2[i]) {
+      return false;
+    }
+  }
+  for (i in ob2) {
+    if (! (i in ob1)) {
+      return false;
+    }
+  }
+  return true;
+};
+
+doctest.getElementsByTagAndClassName = function (tagName, className, parent/*optional*/) {
+    parent = parent || document;
+    var els = parent.getElementsByTagName(tagName);
+    var result = [];
+    var regexes = [];
+    if (typeof className == 'string') {
+      className = [className];
+    }
+    for (var i=0; i<className.length; i++) {
+      regexes.push(new RegExp("\\b" + className[i] + "\\b"));
+    }
+    for (i=0; i<els.length; i++) {
+      var el = els[i];
+      if (el.className) {
+        var passed = true;
+        for (var j=0; j<regexes.length; j++) {
+          if (el.className.search(regexes[j]) == -1) {
+            passed = false;
+            break;
+          }
+        }
+        if (passed) {
+          result.push(el);
+        }
+      }
+    }
+    return result;
+};
+
+doctest.strip = function (str) {
+    str = str + "";
+    return str.replace(/\s+$/, "").replace(/^\s+/, "");
+};
+
+doctest.rstrip = function (str) {
+  str = str + "";
+  return str.replace(/\s+$/, "");
+};
+
+doctest.escapeHTML = function (s) {
+    return s.replace(/&/g, '&amp;')
+    .replace(/\"/g, "&quot;")
+    .replace(/</g, '&lt;')
+    .replace(/>/g, '&gt;');
+};
+
+doctest.escapeSpaces = function (s) {
+  return s.replace(/  /g, '&nbsp; ');
+};
+
+doctest.extend = function (obj, extendWith) {
+    for (i in extendWith) {
+        obj[i] = extendWith[i];
+    }
+    return obj;
+};
+
+doctest.extendDefault = function (obj, extendWith) {
+    for (i in extendWith) {
+        if (typeof obj[i] == 'undefined') {
+            obj[i] = extendWith[i];
+        }
+    }
+    return obj;
+};
+
+if (typeof repr == 'undefined') {
+    repr = doctest.repr;
+}
+
+doctest._consoleFunc = function (attr) {
+  if (typeof window.console != 'undefined'
+      && typeof window.console[attr] != 'undefined') {
+    if (typeof console[attr].apply === 'function') {
+      result = function() {
+        console[attr].apply(console, arguments);
+      };
+    } else {
+      result = console[attr];
+    }
+  } else {
+    result = function () {
+      // FIXME: do something
+    };
+  }
+  return result;
+};
+
+if (typeof log == 'undefined') {
+  log = doctest._consoleFunc('log');
+}
+
+if (typeof logDebug == 'undefined') {
+  logDebug = doctest._consoleFunc('log');
+}
+
+if (typeof logInfo == 'undefined') {
+  logInfo = doctest._consoleFunc('info');
+}
+
+if (typeof logWarn == 'undefined') {
+  logWarn = doctest._consoleFunc('warn');
+}
+
+doctest.eval = function () {
+  return window.eval.apply(window, arguments);
+};
+
+doctest.useCoffeeScript = function (options) {
+  options = options || {};
+  options.bare = true;
+  options.globals = true;
+  if (! options.fileName) {
+    options.fileName = 'repl';
+  }
+  if (typeof CoffeeScript == 'undefined') {
+    doctest.logWarn('coffee-script.js is not included');
+    throw 'coffee-script.js is not included';
+  }
+  doctest.eval = function (code) {
+    var src = CoffeeScript.compile(code, options);
+    logDebug('Compiled code to:', src);
+    return window.eval(src);
+  };
+};
+
+doctest.autoSetup = function (parent) {
+  var tags = doctest.getElementsByTagAndClassName('div', 'test', parent);
+  // First we'll make sure everything has an ID
+  var tagsById = {};
+  for (var i=0; i<tags.length; i++) {
+    var tagId = tags[i].getAttribute('id');
+    if (! tagId) {
+      tagId = 'test-' + (++doctest.autoSetup._idCount);
+      tags[i].setAttribute('id', tagId);
+    }
+    // FIXME: test uniqueness here, warn
+    tagsById[tagId] = tags[i];
+  }
+  // Then fill in the labels
+  for (i=0; i<tags.length; i++) {
+    var el = document.createElement('span');
+    el.className = 'test-id';
+    var anchor = document.createElement('a');
+    anchor.setAttribute('href', '#' + tags[i].getAttribute('id'));
+    anchor.appendChild(document.createTextNode(tags[i].getAttribute('id')));
+    var button = document.createElement('button');
+    button.innerHTML = 'test';
+    button.setAttribute('type', 'button');
+    button.setAttribute('test-id', tags[i].getAttribute('id'));
+    button.onclick = function () {
+      location.hash = '#' + this.getAttribute('test-id');
+      location.reload();
+    };
+    el.appendChild(anchor);
+    el.appendChild(button);
+    tags[i].insertBefore(el, tags[i].childNodes[0]);
+  }
+  // Lastly, create output areas in each section
+  for (i=0; i<tags.length; i++) {
+    var outEl = doctest.getElementsByTagAndClassName('pre', 'output', tags[i]);
+    if (! outEl.length) {
+      outEl = document.createElement('pre');
+      outEl.className = 'output';
+      outEl.setAttribute('id', tags[i].getAttribute('id') + '-output');
+    }
+  }
+  if (location.hash.length > 1) {
+    // This makes the :target CSS work, since if the hash points to an
+    // element whose id has just been added, it won't be noticed
+    location.hash = location.hash;
+  }
+  var output = document.getElementById('doctestOutput');
+  if (! tags.length) {
+    tags = document.getElementsByTagName('body');
+  }
+  if (! output) {
+    output = document.createElement('pre');
+    output.setAttribute('id', 'doctestOutput');
+    output.className = 'output';
+    tags[0].parentNode.insertBefore(output, tags[0]);
+  }
+  var reloader = document.getElementById('doctestReload');
+  if (! reloader) {
+    reloader = document.createElement('button');
+    reloader.setAttribute('type', 'button');
+    reloader.setAttribute('id', 'doctest-testall');
+    reloader.innerHTML = 'test all';
+    reloader.onclick = function () {
+      location.hash = '#doctest-testall';
+      location.reload();
+    };
+    output.parentNode.insertBefore(reloader, output);
+  }
+};
+
+doctest.autoSetup._idCount = 0;
+
+doctest.Spy = function (name, options, extraOptions) {
+  var self;
+  if (doctest.spies[name]) {
+     self = doctest.spies[name];
+     if (! options && ! extraOptions) {
+       return self;
+     }
+  } else {
+    self = function () {
+      return self.func.apply(this, arguments);
+    };
+  }
+  name = name || 'spy';
+  options = options || {};
+  if (typeof options == 'function') {
+    options = {applies: options};
+  }
+  if (extraOptions) {
+    doctest.extendDefault(options, extraOptions);
+  }
+  doctest.extendDefault(options, doctest.defaultSpyOptions);
+  self._name = name;
+  self.options = options;
+  self.called = false;
+  self.calledWait = false;
+  self.args = null;
+  self.self = null;
+  self.argList = [];
+  self.selfList = [];
+  self.writes = options.writes || false;
+  self.returns = options.returns || null;
+  self.applies = options.applies || null;
+  self.binds = options.binds || null;
+  self.throwError = options.throwError || null;
+  self.ignoreThis = options.ignoreThis || false;
+  self.wrapArgs = options.wrapArgs || false;
+  self.func = function () {
+    self.called = true;
+    self.calledWait = true;
+    self.args = doctest._argsToArray(arguments);
+    self.self = this;
+    self.argList.push(self.args);
+    self.selfList.push(this);
+    // It might be possible to get the caller?
+    if (self.writes) {
+      writeln(self.formatCall());
+    }
+    if (self.throwError) {
+      throw self.throwError;
+    }
+    if (self.applies) {
+      return self.applies.apply(this, arguments);
+    }
+    return self.returns;
+  };
+  self.func.toString = function () {
+    return "Spy('" + self._name + "').func";
+  };
+
+  // Method definitions:
+  self.formatCall = function () {
+    var s = '';
+    if ((! self.ignoreThis) && self.self !== window && self.self !== self) {
+      s += doctest.repr(self.self) + '.';
+    }
+    s += self._name;
+    if (self.args === null) {
+      return s + ':never called';
+    }
+    s += '(';
+    for (var i=0; i<self.args.length; i++) {
+      if (i) {
+        s += ', ';
+      }
+      if (self.wrapArgs) {
+        var maxLen = 10;
+      } else {
+        var maxLen = undefined;
+      }
+      s += doctest.repr(self.args[i], '', maxLen);
+    }
+    s += ')';
+    return s;
+  };
+
+  self.method = function (name, options, extraOptions) {
+    var desc = self._name + '.' + name;
+    var newSpy = Spy(desc, options, extraOptions);
+    self[name] = self.func[name] = newSpy.func;
+    return newSpy;
+  };
+
+  self.methods = function (props) {
+    for (var i in props) {
+      if (props[i] === props.prototype[i]) {
+        continue;
+      }
+      self.method(i, props[i]);
+    }
+    return self;
+  };
+
+  self.wait = function (timeout) {
+    var func = function () {
+      var value = self.calledWait;
+      if (value) {
+        self.calledWait = false;
+      }
+      return value;
+    };
+    func.repr = function () {
+      return 'called:'+repr(self);
+    };
+    doctest.wait(func, timeout);
+  };
+
+  self.repr = function () {
+    return "Spy('" + self._name + "')";
+  };
+
+  if (options.methods) {
+    self.methods(options.methods);
+  }
+  doctest.spies[name] = self;
+  if (options.wait) {
+    self.wait();
+  }
+  return self;
+};
+
+doctest._argsToArray = function (args) {
+  var array = [];
+  for (var i=0; i<args.length; i++) {
+    array.push(args[i]);
+  }
+  return array;
+};
+
+Spy = doctest.Spy;
+
+doctest.spies = {};
+
+doctest.defaultTimeout = 2000;
+
+doctest.defaultSpyOptions = {writes: true};
+
+var docTestOnLoad = function () {
+  var auto = false;
+  if (/\bautodoctest\b/.exec(document.body.className)) {
+    doctest.autoSetup();
+    auto = true;
+  } else {
+    logDebug('No autodoctest class on <body>');
+  }
+  var loc = window.location.search.substring(1);
+  if (auto || (/doctestRun/).exec(loc)) {
+    var elements = null;
+    // FIXME: we need to put the output near the specific test being tested:
+    if (location.hash) {
+      var el = document.getElementById(location.hash.substr(1));
+      if (el) {
+        if (/\btest\b/.exec(el.className)) {
+          var testEls = doctest.getElementsByTagAndClassName('pre', 'doctest', el);
+          elements = doctest.getElementsByTagAndClassName('pre', ['doctest', 'setup']);
+          for (var i=0; i<testEls.length; i++) {
+            elements.push(testEls[i]);
+          }
+        }
+      }
+    }
+    doctest(0, elements);
+  }
+};
+
+if (window.addEventListener) {
+    window.addEventListener('load', docTestOnLoad, false);
+} else if(window.attachEvent) {
+    window.attachEvent('onload', docTestOnLoad);
+}
+
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/js/jquery-1.6.1.min.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/js/jquery-1.6.1.min.js
new file mode 100644
index 0000000..b2ac174
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/js/jquery-1.6.1.min.js
@@ -0,0 +1,18 @@
+/*!
+ * jQuery JavaScript Library v1.6.1
+ * http://jquery.com/
+ *
+ * Copyright 2011, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ * Copyright 2011, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ *
+ * Date: Thu May 12 15:04:36 2011 -0400
+ */
+(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cv(a){if(!cj[a]){var b=f("<"+a+">").appendTo("body"),d=b.css("display");b.remove();if(d==="none"||d===""){ck||(ck=c.createElement("iframe"),ck.frameBorder=ck.width=ck.height=0),c.body.appendChild(ck);if(!cl||!ck.createElement)cl=(ck.contentWindow||ck.contentDocument).document,cl.write("<!doctype><html><body></body></html>");b=cl.createElement(a),cl.body.appendChild(b),d=f.css(b,"display"),c.body.removeChild(ck)}cj[a]=d}return cj[a]}function cu(a,b){var c={};f.each(cp.concat.apply([],cp.slice(0,b)),function(){c[this]=a});return c}function ct(){cq=b}function cs(){setTimeout(ct,0);return cq=f.now()}function ci(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ch(){try{return new a.XMLHttpRequest}catch(b){}}function cb(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g<i;g++){if(g===1)for(h in a.converters)typeof h=="string"&&(e[h.toLowerCase()]=a.converters[h]);l=k,k=d[g];if(k==="*")k=l;else if(l!=="*"&&l!==k){m=l+" "+k,n=e[m]||e["* "+k];if(!n){p=b;for(o in e){j=o.split(" ");if(j[0]===l||j[0]==="*"){p=e[j[1]+" "+k];if(p){o=e[o],o===!0?n=p:p===!0&&(n=o);break}}}}!n&&!p&&f.error("No conversion from "+m.replace(" "," to ")),n!==!0&&(c=n?n(c):p(o(c)))}}return c}function ca(a,c,d){var e=a.contents,f=a.dataTypes,g=a.responseFields,h,i,j,k;for(i in g)i in d&&(c[g[i]]=d[i]);while(f[0]==="*")f.shift(),h===b&&(h=a.mimeType||c.getResponseHeader("content-type"));if(h)for(i in e)if(e[i]&&e[i].test(h)){f.unshift(i);break}if(f[0]in d)j=f[0];else{for(i in d){if(!f[0]||a.converters[i+" "+f[0]]){j=i;break}k||(k=i)}j=j||k}if(j){j!==f[0]&&f.unshift(j);return d[j]}}function b_(a,b,c,d){if(f.isArray(b))f.each(b,function(b,e){c||bF.test(a)?d(a,e):b_(a+"["+(typeof e=="object"||f.isArray(e)?b:"")+"]",e,c,d)});else if(!c&&b!=null&&typeof b=="object")for(var e in b)b_(a+"["+e+"]",b[e],c,d);else d(a,b)}function b$(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h=a[f],i=0,j=h?h.length:0,k=a===bU,l;for(;i<j&&(k||!l);i++)l=h[i](c,d,e),typeof l=="string"&&(!k||g[l]?l=b:(c.dataTypes.unshift(l),l=b$(a,c,d,e,l,g)));(k||!l)&&!g["*"]&&(l=b$(a,c,d,e,"*",g));return l}function bZ(a){return function(b,c){typeof b!="string"&&(c=b,b="*");if(f.isFunction(c)){var d=b.toLowerCase().split(bQ),e=0,g=d.length,h,i,j;for(;e<g;e++)h=d[e],j=/^\+/.test(h),j&&(h=h.substr(1)||"*"),i=a[h]=a[h]||[],i[j?"unshift":"push"](c)}}}function bD(a,b,c){var d=b==="width"?bx:by,e=b==="width"?a.offsetWidth:a.offsetHeight;if(c==="border")return e;f.each(d,function(){c||(e-=parseFloat(f.css(a,"padding"+this))||0),c==="margin"?e+=parseFloat(f.css(a,"margin"+this))||0:e-=parseFloat(f.css(a,"border"+this+"Width"))||0});return e}function bn(a,b){b.src?f.ajax({url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(bf,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)}function bm(a){f.nodeName(a,"input")?bl(a):a.getElementsByTagName&&f.grep(a.getElementsByTagName("input"),bl)}function bl(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bk(a){return"getElementsByTagName"in a?a.getElementsByTagName("*"):"querySelectorAll"in a?a.querySelectorAll("*"):[]}function bj(a,b){var c;if(b.nodeType===1){b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase();if(c==="object")b.outerHTML=a.outerHTML;else if(c!=="input"||a.type!=="checkbox"&&a.type!=="radio"){if(c==="option")b.selected=a.defaultSelected;else if(c==="input"||c==="textarea")b.defaultValue=a.defaultValue}else a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value);b.removeAttribute(f.expando)}}function bi(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c=f.expando,d=f.data(a),e=f.data(b,d);if(d=d[c]){var g=d.events;e=e[c]=f.extend({},d);if(g){delete e.handle,e.events={};for(var h in g)for(var i=0,j=g[h].length;i<j;i++)f.event.add(b,h+(g[h][i].namespace?".":"")+g[h][i].namespace,g[h][i],g[h][i].data)}}}}function bh(a,b){return f.nodeName(a,"table")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function X(a,b,c){b=b||0;if(f.isFunction(b))return f.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return f.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=f.grep(a,function(a){return a.nodeType===1});if(S.test(b))return f.filter(b,d,!c);b=f.filter(b,d)}return f.grep(a,function(a,d){return f.inArray(a,b)>=0===c})}function W(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function O(a,b){return(a&&a!=="*"?a+".":"")+b.replace(A,"`").replace(B,"&")}function N(a){var b,c,d,e,g,h,i,j,k,l,m,n,o,p=[],q=[],r=f._data(this,"events");if(!(a.liveFired===this||!r||!r.live||a.target.disabled||a.button&&a.type==="click")){a.namespace&&(n=new RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)")),a.liveFired=this;var s=r.live.slice(0);for(i=0;i<s.length;i++)g=s[i],g.origType.replace(y,"")===a.type?q.push(g.selector):s.splice(i--,1);e=f(a.target).closest(q,a.currentTarget);for(j=0,k=e.length;j<k;j++){m=e[j];for(i=0;i<s.length;i++){g=s[i];if(m.selector===g.selector&&(!n||n.test(g.namespace))&&!m.elem.disabled){h=m.elem,d=null;if(g.preType==="mouseenter"||g.preType==="mouseleave")a.type=g.preType,d=f(a.relatedTarget).closest(g.selector)[0],d&&f.contains(h,d)&&(d=h);(!d||d!==h)&&p.push({elem:h,handleObj:g,level:m.level})}}}for(j=0,k=p.length;j<k;j++){e=p[j];if(c&&e.level>c)break;a.currentTarget=e.elem,a.data=e.handleObj.data,a.handleObj=e.handleObj,o=e.handleObj.origHandler.apply(e.elem,arguments);if(o===!1||a.isPropagationStopped()){c=e.level,o===!1&&(b=!1);if(a.isImmediatePropagationStopped())break}}return b}}function L(a,c,d){var e=f.extend({},d[0]);e.type=a,e.originalEvent={},e.liveFired=b,f.event.handle.call(c,e),e.isDefaultPrevented()&&d[0].preventDefault()}function F(){return!0}function E(){return!1}function m(a,c,d){var e=c+"defer",g=c+"queue",h=c+"mark",i=f.data(a,e,b,!0);i&&(d==="queue"||!f.data(a,g,b,!0))&&(d==="mark"||!f.data(a,h,b,!0))&&setTimeout(function(){!f.data(a,g,b,!0)&&!f.data(a,h,b,!0)&&(f.removeData(a,e,!0),i.resolve())},0)}function l(a){for(var b in a)if(b!=="toJSON")return!1;return!0}function k(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(j,"$1-$2").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNaN(d)?i.test(d)?f.parseJSON(d):d:parseFloat(d)}catch(g){}f.data(a,c,d)}else d=b}return d}var c=a.document,d=a.navigator,e=a.location,f=function(){function H(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(H,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/\d/,n=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,o=/^[\],:{}\s]*$/,p=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,q=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,r=/(?:^|:|,)(?:\s*\[)+/g,s=/(webkit)[ \/]([\w.]+)/,t=/(opera)(?:.*version)?[ \/]([\w.]+)/,u=/(msie) ([\w.]+)/,v=/(mozilla)(?:.*? rv:([\w.]+))?/,w=d.userAgent,x,y,z,A=Object.prototype.toString,B=Object.prototype.hasOwnProperty,C=Array.prototype.push,D=Array.prototype.slice,E=String.prototype.trim,F=Array.prototype.indexOf,G={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=n.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.6.1",length:0,size:function(){return this.length},toArray:function(){return D.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?C.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),y.done(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(D.apply(this,arguments),"slice",D.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:C,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j<k;j++)if((a=arguments[j])!=null)for(c in a){d=i[c],f=a[c];if(i===f)continue;l&&f&&(e.isPlainObject(f)||(g=e.isArray(f)))?(g?(g=!1,h=d&&e.isArray(d)?d:[]):h=d&&e.isPlainObject(d)?d:{},i[c]=e.extend(l,h,f)):f!==b&&(i[c]=f)}return i},e.extend({noConflict:function(b){a.$===e&&(a.$=g),b&&a.jQuery===e&&(a.jQuery=f);return e},isReady:!1,readyWait:1,holdReady:function(a){a?e.readyWait++:e.ready(!0)},ready:function(a){if(a===!0&&!--e.readyWait||a!==!0&&!e.isReady){if(!c.body)return setTimeout(e.ready,1);e.isReady=!0;if(a!==!0&&--e.readyWait>0)return;y.resolveWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").unbind("ready")}},bindReady:function(){if(!y){y=e._Deferred();if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",z,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",z),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&H()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNaN:function(a){return a==null||!m.test(a)||isNaN(a)},type:function(a){return a==null?String(a):G[A.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;if(a.constructor&&!B.call(a,"constructor")&&!B.call(a.constructor.prototype,"isPrototypeOf"))return!1;var c;for(c in a);return c===b||B.call(a,c)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw a},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(o.test(b.replace(p,"@").replace(q,"]").replace(r,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(b,c,d){a.DOMParser?(d=new DOMParser,c=d.parseFromString(b,"text/xml")):(c=new ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(b)),d=c.documentElement,(!d||!d.nodeName||d.nodeName==="parsererror")&&e.error("Invalid XML: "+b);return c},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g<h;)if(c.apply(a[g++],d)===!1)break}else if(i){for(f in a)if(c.call(a[f],f,a[f])===!1)break}else for(;g<h;)if(c.call(a[g],g,a[g++])===!1)break;return a},trim:E?function(a){return a==null?"":E.call(a)}:function(a){return a==null?"":(a+"").replace(k,"").replace(l,"")},makeArray:function(a,b){var c=b||[];if(a!=null){var d=e.type(a);a.length==null||d==="string"||d==="function"||d==="regexp"||e.isWindow(a)?C.call(c,a):e.merge(c,a)}return c},inArray:function(a,b){if(F)return F.call(b,a);for(var c=0,d=b.length;c<d;c++)if(b[c]===a)return c;return-1},merge:function(a,c){var d=a.length,e=0;if(typeof c.length=="number")for(var f=c.length;e<f;e++)a[d++]=c[e];else while(c[e]!==b)a[d++]=c[e++];a.length=d;return a},grep:function(a,b,c){var d=[],e;c=!!c;for(var f=0,g=a.length;f<g;f++)e=!!b(a[f],f),c!==e&&d.push(a[f]);return d},map:function(a,c,d){var f,g,h=[],i=0,j=a.length,k=a instanceof e||j!==b&&typeof j=="number"&&(j>0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i<j;i++)f=c(a[i],i,d),f!=null&&(h[h.length]=f);else for(g in a)f=c(a[g],g,d),f!=null&&(h[h.length]=f);return h.concat.apply([],h)},guid:1,proxy:function(a,c){if(typeof c=="string"){var d=a[c];c=a,a=d}if(!e.isFunction(a))return b;var f=D.call(arguments,2),g=function(){return a.apply(c,f.concat(D.call(arguments)))};g.guid=a.guid=a.guid||g.guid||e.guid++;return g},access:function(a,c,d,f,g,h){var i=a.length;if(typeof c=="object"){for(var j in c)e.access(a,j,c[j],f,g,d);return a}if(d!==b){f=!h&&f&&e.isFunction(d);for(var k=0;k<i;k++)g(a[k],c,f?d.call(a[k],k,g(a[k],c)):d,h);return a}return i?g(a[0],c):b},now:function(){return(new Date).getTime()},uaMatch:function(a){a=a.toLowerCase();var b=s.exec(a)||t.exec(a)||u.exec(a)||a.indexOf("compatible")<0&&v.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},sub:function(){function a(b,c){return new a.fn.init(b,c)}e.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function(d,f){f&&f instanceof e&&!(f instanceof a)&&(f=a(f));return e.fn.init.call(this,d,f,b)},a.fn.init.prototype=a.fn;var b=a(c);return a},browser:{}}),e.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){G["[object "+b+"]"]=b.toLowerCase()}),x=e.uaMatch(w),x.browser&&(e.browser[x.browser]=!0,e.browser.version=x.version),e.browser.webkit&&(e.browser.safari=!0),j.test(" ")&&(k=/^[\s\xA0]+/,l=/[\s\xA0]+$/),h=e(c),c.addEventListener?z=function(){c.removeEventListener("DOMContentLoaded",z,!1),e.ready()}:c.attachEvent&&(z=function(){c.readyState==="complete"&&(c.detachEvent("onreadystatechange",z),e.ready())});return e}(),g="done fail isResolved isRejected promise then always pipe".split(" "),h=[].slice;f.extend({_Deferred:function(){var a=[],b,c,d,e={done:function(){if(!d){var c=arguments,g,h,i,j,k;b&&(k=b,b=0);for(g=0,h=c.length;g<h;g++)i=c[g],j=f.type(i),j==="array"?e.done.apply(e,i):j==="function"&&a.push(i);k&&e.resolveWith(k[0],k[1])}return this},resolveWith:function(e,f){if(!d&&!b&&!c){f=f||[],c=1;try{while(a[0])a.shift().apply(e,f)}finally{b=[e,f],c=0}}return this},resolve:function(){e.resolveWith(this,arguments);return this},isResolved:function(){return!!c||!!b},cancel:function(){d=1,a=[];return this}};return e},Deferred:function(a){var b=f._Deferred(),c=f._Deferred(),d;f.extend(b,{then:function(a,c){b.done(a).fail(c);return this},always:function(){return b.done.apply(b,arguments).fail.apply(this,arguments)},fail:c.done,rejectWith:c.resolveWith,reject:c.resolve,isRejected:c.isResolved,pipe:function(a,c){return f.Deferred(function(d){f.each({done:[a,"resolve"],fail:[c,"reject"]},function(a,c){var e=c[0],g=c[1],h;f.isFunction(e)?b[a](function(){h=e.apply(this,arguments),h&&f.isFunction(h.promise)?h.promise().then(d.resolve,d.reject):d[g](h)}):b[a](d[g])})}).promise()},promise:function(a){if(a==null){if(d)return d;d=a={}}var c=g.length;while(c--)a[g[c]]=b[g[c]];return a}}),b.done(c.cancel).fail(b.cancel),delete b.cancel,a&&a.call(b,b);return b},when:function(a){function i(a){return function(c){b[a]=arguments.length>1?h.call(arguments,0):c,--e||g.resolveWith(g,h.call(b,0))}}var b=arguments,c=0,d=b.length,e=d,g=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred();if(d>1){for(;c<d;c++)b[c]&&f.isFunction(b[c].promise)?b[c].promise().then(i(c),g.reject):--e;e||g.resolveWith(g,b)}else g!==a&&g.resolveWith(g,d?[a]:[]);return g.promise()}}),f.support=function(){var a=c.createElement("div"),b=c.documentElement,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r;a.setAttribute("className","t"),a.innerHTML="   <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>",d=a.getElementsByTagName("*"),e=a.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};f=c.createElement("select"),g=f.appendChild(c.createElement("option")),h=a.getElementsByTagName("input")[0],j={leadingWhitespace:a.firstChild.nodeType===3,tbody:!a.getElementsByTagName("tbody").length,htmlSerialize:!!a.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55$/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:h.value==="on",optSelected:g.selected,getSetAttribute:a.className!=="t",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},h.checked=!0,j.noCloneChecked=h.cloneNode(!0).checked,f.disabled=!0,j.optDisabled=!g.disabled;try{delete a.test}catch(s){j.deleteExpando=!1}!a.addEventListener&&a.attachEvent&&a.fireEvent&&(a.attachEvent("onclick",function b(){j.noCloneEvent=!1,a.detachEvent("onclick",b)}),a.cloneNode(!0).fireEvent("onclick")),h=c.createElement("input"),h.value="t",h.setAttribute("type","radio"),j.radioValue=h.value==="t",h.setAttribute("checked","checked"),a.appendChild(h),k=c.createDocumentFragment(),k.appendChild(a.firstChild),j.checkClone=k.cloneNode(!0).cloneNode(!0).lastChild.checked,a.innerHTML="",a.style.width=a.style.paddingLeft="1px",l=c.createElement("body"),m={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"};for(q in m)l.style[q]=m[q];l.appendChild(a),b.insertBefore(l,b.firstChild),j.appendChecked=h.checked,j.boxModel=a.offsetWidth===2,"zoom"in a.style&&(a.style.display="inline",a.style.zoom=1,j.inlineBlockNeedsLayout=a.offsetWidth===2,a.style.display="",a.innerHTML="<div style='width:4px;'></div>",j.shrinkWrapBlocks=a.offsetWidth!==2),a.innerHTML="<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>",n=a.getElementsByTagName("td"),r=n[0].offsetHeight===0,n[0].style.display="",n[1].style.display="none",j.reliableHiddenOffsets=r&&n[0].offsetHeight===0,a.innerHTML="",c.defaultView&&c.defaultView.getComputedStyle&&(i=c.createElement("div"),i.style.width="0",i.style.marginRight="0",a.appendChild(i),j.reliableMarginRight=(parseInt((c.defaultView.getComputedStyle(i,null)||{marginRight:0}).marginRight,10)||0)===0),l.innerHTML="",b.removeChild(l);if(a.attachEvent)for(q in{submit:1,change:1,focusin:1})p="on"+q,r=p in a,r||(a.setAttribute(p,"return;"),r=typeof a[p]=="function"),j[q+"Bubbles"]=r;return j}(),f.boxModel=f.support.boxModel;var i=/^(?:\{.*\}|\[.*\])$/,j=/([a-z])([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!l(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g=f.expando,h=typeof c=="string",i,j=a.nodeType,k=j?f.cache:a,l=j?a[f.expando]:a[f.expando]&&f.expando;if((!l||e&&l&&!k[l][g])&&h&&d===b)return;l||(j?a[f.expando]=l=++f.uuid:l=f.expando),k[l]||(k[l]={},j||(k[l].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?k[l][g]=f.extend(k[l][g],c):k[l]=f.extend(k[l],c);i=k[l],e&&(i[g]||(i[g]={}),i=i[g]),d!==b&&(i[f.camelCase(c)]=d);if(c==="events"&&!i[c])return i[g]&&i[g].events;return h?i[f.camelCase(c)]:i}},removeData:function(b,c,d){if(!!f.acceptData(b)){var e=f.expando,g=b.nodeType,h=g?f.cache:b,i=g?b[f.expando]:f.expando;if(!h[i])return;if(c){var j=d?h[i][e]:h[i];if(j){delete j[c];if(!l(j))return}}if(d){delete h[i][e];if(!l(h[i]))return}var k=h[i][e];f.support.deleteExpando||h!=a?delete h[i]:h[i]=null,k?(h[i]={},g||(h[i].toJSON=f.noop),h[i][e]=k):g&&(f.support.deleteExpando?delete b[f.expando]:b.removeAttribute?b.removeAttribute(f.expando):b[f.expando]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d=null;if(typeof a=="undefined"){if(this.length){d=f.data(this[0]);if(this[0].nodeType===1){var e=this[0].attributes,g;for(var h=0,i=e.length;h<i;h++)g=e[h].name,g.indexOf("data-")===0&&(g=f.camelCase(g.substring(5)),k(this[0],g,d[g]))}}return d}if(typeof a=="object")return this.each(function(){f.data(this,a)});var j=a.split(".");j[1]=j[1]?"."+j[1]:"";if(c===b){d=this.triggerHandler("getData"+j[1]+"!",[j[0]]),d===b&&this.length&&(d=f.data(this[0],a),d=k(this[0],a,d));return d===b&&j[1]?this.data(j[0]):d}return this.each(function(){var b=f(this),d=[j[0],c];b.triggerHandler("setData"+j[1]+"!",d),f.data(this,a,c),b.triggerHandler("changeData"+j[1]+"!",d)})},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,c){a&&(c=(c||"fx")+"mark",f.data(a,c,(f.data(a,c,b,!0)||0)+1,!0))},_unmark:function(a,c,d){a!==!0&&(d=c,c=a,a=!1);if(c){d=d||"fx";var e=d+"mark",g=a?0:(f.data(c,e,b,!0)||1)-1;g?f.data(c,e,g,!0):(f.removeData(c,e,!0),m(c,d,"mark"))}},queue:function(a,c,d){if(a){c=(c||"fx")+"queue";var e=f.data(a,c,b,!0);d&&(!e||f.isArray(d)?e=f.data(a,c,f.makeArray(d),!0):e.push(d));return e||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e;d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),d.call(a,function(){f.dequeue(a,b)})),c.length||(f.removeData(a,b+"queue",!0),m(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){typeof a!="string"&&(c=a,a="fx");if(c===b)return f.queue(this[0],a);return this.each(function(){var b=f.queue(this,a,c);a==="fx"&&b[0]!=="inprogress"&&f.dequeue(this,a)})},dequeue:function(a){return this.each(function(){f.dequeue(this,a)})},delay:function(a,b){a=f.fx?f.fx.speeds[a]||a:a,b=b||"fx";return this.queue(b,function(){var c=this;setTimeout(function(){f.dequeue(c,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){function m(){--h||d.resolveWith(e,[e])}typeof a!="string"&&(c=a,a=b),a=a||"fx";var d=f.Deferred(),e=this,g=e.length,h=1,i=a+"defer",j=a+"queue",k=a+"mark",l;while(g--)if(l=f.data(e[g],i,b,!0)||(f.data(e[g],j,b,!0)||f.data(e[g],k,b,!0))&&f.data(e[g],i,f._Deferred(),!0))h++,l.done(m);m();return d.promise()}});var n=/[\n\t\r]/g,o=/\s+/,p=/\r/g,q=/^(?:button|input)$/i,r=/^(?:button|input|object|select|textarea)$/i,s=/^a(?:rea)?$/i,t=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,u=/\:/,v,w;f.fn.extend({attr:function(a,b){return f.access(this,a,b,!0,f.attr)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,a,b,!0,f.prop)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.addClass(a.call(this,b,c.attr("class")||""))});if(a&&typeof a=="string"){var b=(a||"").split(o);for(var c=0,d=this.length;c<d;c++){var e=this[c];if(e.nodeType===1)if(!e.className)e.className=a;else{var g=" "+e.className+" ",h=e.className;for(var i=0,j=b.length;i<j;i++)g.indexOf(" "+b[i]+" ")<0&&(h+=" "+b[i]);e.className=f.trim(h)}}}return this},removeClass:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.removeClass(a.call(this,b,c.attr("class")))});if(a&&typeof a=="string"||a===b){var c=(a||"").split(o);for(var d=0,e=this.length;d<e;d++){var g=this[d];if(g.nodeType===1&&g.className)if(a){var h=(" "+g.className+" ").replace(n," ");for(var i=0,j=c.length;i<j;i++)h=h.replace(" "+c[i]+" "," ");g.className=f.trim(h)}else g.className=""}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";if(f.isFunction(a))return this.each(function(c){var d=f(this);d.toggleClass(a.call(this,c,d.attr("class"),b),b)});return this.each(function(){if(c==="string"){var e,g=0,h=f(this),i=b,j=a.split(o);while(e=j[g++])i=d?i:!h.hasClass(e),h[i?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&f._data(this,"__className__",this.className),this.className=this.className||a===!1?"":f._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ";for(var c=0,d=this.length;c<d;c++)if((" "+this[c].className+" ").replace(n," ").indexOf(b)>-1)return!0;return!1},val:function(a){var c,d,e=this[0];if(!arguments.length){if(e){c=f.valHooks[e.nodeName.toLowerCase()]||f.valHooks[e.type];if(c&&"get"in c&&(d=c.get(e,"value"))!==b)return d;return(e.value||"").replace(p,"")}return b}var g=f.isFunction(a);return this.each(function(d){var e=f(this),h;if(this.nodeType===1){g?h=a.call(this,d,e.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c=a.selectedIndex,d=[],e=a.options,g=a.type==="select-one";if(c<0)return null;for(var h=g?c:0,i=g?c+1:e.length;h<i;h++){var j=e[h];if(j.selected&&(f.support.optDisabled?!j.disabled:j.getAttribute("disabled")===null)&&(!j.parentNode.disabled||!f.nodeName(j.parentNode,"optgroup"))){b=f(j).val();if(g)return b;d.push(b)}}if(g&&!d.length&&e.length)return f(e[c]).val();return d},set:function(a,b){var c=f.makeArray(b);f(a).find("option").each(function(){this.selected=f.inArray(f(this).val(),c)>=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attrFix:{tabindex:"tabIndex"},attr:function(a,c,d,e){var g=a.nodeType;if(!a||g===3||g===8||g===2)return b;if(e&&c in f.attrFn)return f(a)[c](d);if(!("getAttribute"in a))return f.prop(a,c,d);var h,i,j=g!==1||!f.isXMLDoc(a);c=j&&f.attrFix[c]||c,i=f.attrHooks[c],i||(!t.test(c)||typeof d!="boolean"&&d!==b&&d.toLowerCase()!==c.toLowerCase()?v&&(f.nodeName(a,"form")||u.test(c))&&(i=v):i=w);if(d!==b){if(d===null){f.removeAttr(a,c);return b}if(i&&"set"in i&&j&&(h=i.set(a,d,c))!==b)return h;a.setAttribute(c,""+d);return d}if(i&&"get"in i&&j)return i.get(a,c);h=a.getAttribute(c);return h===null?b:h},removeAttr:function(a,b){var c;a.nodeType===1&&(b=f.attrFix[b]||b,f.support.getSetAttribute?a.removeAttribute(b):(f.attr(a,b,""),a.removeAttributeNode(a.getAttributeNode(b))),t.test(b)&&(c=f.propFix[b]||b)in a&&(a[c]=!1))},attrHooks:{type:{set:function(a,b){if(q.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.value;a.setAttribute("type",b),c&&(a.value=c);return b}}},tabIndex:{get:function(a){var c=a.getAttributeNode("tabIndex");return c&&c.specified?parseInt(c.value,10):r.test(a.nodeName)||s.test(a.nodeName)&&a.href?0:b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e=a.nodeType;if(!a||e===3||e===8||e===2)return b;var g,h,i=e!==1||!f.isXMLDoc(a);c=i&&f.propFix[c]||c,h=f.propHooks[c];return d!==b?h&&"set"in h&&(g=h.set(a,d,c))!==b?g:a[c]=d:h&&"get"in h&&(g=h.get(a,c))!==b?g:a[c]},propHooks:{}}),w={get:function(a,c){return a[f.propFix[c]||c]?c.toLowerCase():b},set:function(a,b,c){var d;b===!1?f.removeAttr(a,c):(d=f.propFix[c]||c,d in a&&(a[d]=b),a.setAttribute(c,c.toLowerCase()));return c}},f.attrHooks.value={get:function(a,b){if(v&&f.nodeName(a,"button"))return v.get(a,b);return a.value},set:function(a,b,c){if(v&&f.nodeName(a,"button"))return v.set(a,b,c);a.value=b}},f.support.getSetAttribute||(f.attrFix=f.propFix,v=f.attrHooks.name=f.valHooks.button={get:function(a,c){var d;d=a.getAttributeNode(c);return d&&d.nodeValue!==""?d.nodeValue:b},set:function(a,b,c){var d=a.getAttributeNode(c);if(d){d.nodeValue=b;return b}}},f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})})),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}})),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var x=Object.prototype.hasOwnProperty,y=/\.(.*)$/,z=/^(?:textarea|input|select)$/i,A=/\./g,B=/ /g,C=/[^\w\s.|`]/g,D=function(a){return a.replace(C,"\\$&")};f.event={add:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){if(d===!1)d=E;else if(!d)return;var g,h;d.handler&&(g=d,d=g.handler),d.guid||(d.guid=f.guid++);var i=f._data(a);if(!i)return;var j=i.events,k=i.handle;j||(i.events=j={}),k||(i.handle=k=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.handle.apply(k.elem,arguments):b}),k.elem=a,c=c.split(" ");var l,m=0,n;while(l=c[m++]){h=g?f.extend({},g):{handler:d,data:e},l.indexOf(".")>-1?(n=l.split("."),l=n.shift(),h.namespace=n.slice(0).sort().join(".")):(n=[],h.namespace=""),h.type=l,h.guid||(h.guid=d.guid);var o=j[l],p=f.event.special[l]||{};if(!o){o=j[l]=[];if(!p.setup||p.setup.call(a,e,n,k)===!1)a.addEventListener?a.addEventListener(l,k,!1):a.attachEvent&&a.attachEvent("on"+l,k)}p.add&&(p.add.call(a,h),h.handler.guid||(h.handler.guid=d.guid)),o.push(h),f.event.global[l]=!0}a=null}},global:{},remove:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){d===!1&&(d=E);var g,h,i,j,k=0,l,m,n,o,p,q,r,s=f.hasData(a)&&f._data(a),t=s&&s.events;if(!s||!t)return;c&&c.type&&(d=c.handler,c=c.type);if(!c||typeof c=="string"&&c.charAt(0)==="."){c=c||"";for(h in t)f.event.remove(a,h+c);return}c=c.split(" ");while(h=c[k++]){r=h,q=null,l=h.indexOf(".")<0,m=[],l||(m=h.split("."),h=m.shift(),n=new RegExp("(^|\\.)"+f.map(m.slice(0).sort(),D).join("\\.(?:.*\\.)?")+"(\\.|$)")),p=t[h];if(!p)continue;if(!d){for(j=0;j<p.length;j++){q=p[j];if(l||n.test(q.namespace))f.event.remove(a,r,q.handler,j),p.splice(j--,1)}continue}o=f.event.special[h]||{};for(j=e||0;j<p.length;j++){q=p[j];if(d.guid===q.guid){if(l||n.test(q.namespace))e==null&&p.splice(j--,1),o.remove&&o.remove.call(a,q);if(e!=null)break}}if(p.length===0||e!=null&&p.length===1)(!o.teardown||o.teardown.call(a,m)===!1)&&f.removeEvent(a,h,s.handle),g=null,delete t[h]}if(f.isEmptyObject(t)){var u=s.handle;u&&(u.elem=null),delete s.events,delete s.handle,f.isEmptyObject(s)&&f.removeData(a,b,!0)}}},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,e,g){var h=c.type||c,i=[],j;h.indexOf("!")>=0&&(h=h.slice(0,-1),j=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if(!!e&&!f.event.customEvent[h]||!!f.event.global[h]){c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.exclusive=j,c.namespace=i.join("."),c.namespace_re=new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)");if(g||!e)c.preventDefault(),c.stopPropagation();if(!e){f.each(f.cache,function(){var a=f.expando,b=this[a];b&&b.events&&b.events[h]&&f.event.trigger(c,d,b.handle.elem
+)});return}if(e.nodeType===3||e.nodeType===8)return;c.result=b,c.target=e,d=d?f.makeArray(d):[],d.unshift(c);var k=e,l=h.indexOf(":")<0?"on"+h:"";do{var m=f._data(k,"handle");c.currentTarget=k,m&&m.apply(k,d),l&&f.acceptData(k)&&k[l]&&k[l].apply(k,d)===!1&&(c.result=!1,c.preventDefault()),k=k.parentNode||k.ownerDocument||k===c.target.ownerDocument&&a}while(k&&!c.isPropagationStopped());if(!c.isDefaultPrevented()){var n,o=f.event.special[h]||{};if((!o._default||o._default.call(e.ownerDocument,c)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)){try{l&&e[h]&&(n=e[l],n&&(e[l]=null),f.event.triggered=h,e[h]())}catch(p){}n&&(e[l]=n),f.event.triggered=b}}return c.result}},handle:function(c){c=f.event.fix(c||a.event);var d=((f._data(this,"events")||{})[c.type]||[]).slice(0),e=!c.exclusive&&!c.namespace,g=Array.prototype.slice.call(arguments,0);g[0]=c,c.currentTarget=this;for(var h=0,i=d.length;h<i;h++){var j=d[h];if(e||c.namespace_re.test(j.namespace)){c.handler=j.handler,c.data=j.data,c.handleObj=j;var k=j.handler.apply(this,g);k!==b&&(c.result=k,k===!1&&(c.preventDefault(),c.stopPropagation()));if(c.isImmediatePropagationStopped())break}}return c.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(a){if(a[f.expando])return a;var d=a;a=f.Event(d);for(var e=this.props.length,g;e;)g=this.props[--e],a[g]=d[g];a.target||(a.target=a.srcElement||c),a.target.nodeType===3&&(a.target=a.target.parentNode),!a.relatedTarget&&a.fromElement&&(a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement);if(a.pageX==null&&a.clientX!=null){var h=a.target.ownerDocument||c,i=h.documentElement,j=h.body;a.pageX=a.clientX+(i&&i.scrollLeft||j&&j.scrollLeft||0)-(i&&i.clientLeft||j&&j.clientLeft||0),a.pageY=a.clientY+(i&&i.scrollTop||j&&j.scrollTop||0)-(i&&i.clientTop||j&&j.clientTop||0)}a.which==null&&(a.charCode!=null||a.keyCode!=null)&&(a.which=a.charCode!=null?a.charCode:a.keyCode),!a.metaKey&&a.ctrlKey&&(a.metaKey=a.ctrlKey),!a.which&&a.button!==b&&(a.which=a.button&1?1:a.button&2?3:a.button&4?2:0);return a},guid:1e8,proxy:f.proxy,special:{ready:{setup:f.bindReady,teardown:f.noop},live:{add:function(a){f.event.add(this,O(a.origType,a.selector),f.extend({},a,{handler:N,guid:a.handler.guid}))},remove:function(a){f.event.remove(this,O(a.origType,a.selector),a)}},beforeunload:{setup:function(a,b,c){f.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}}},f.removeEvent=c.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){a.detachEvent&&a.detachEvent("on"+b,c)},f.Event=function(a,b){if(!this.preventDefault)return new f.Event(a,b);a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?F:E):this.type=a,b&&f.extend(this,b),this.timeStamp=f.now(),this[f.expando]=!0},f.Event.prototype={preventDefault:function(){this.isDefaultPrevented=F;var a=this.originalEvent;!a||(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){this.isPropagationStopped=F;var a=this.originalEvent;!a||(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=F,this.stopPropagation()},isDefaultPrevented:E,isPropagationStopped:E,isImmediatePropagationStopped:E};var G=function(a){var b=a.relatedTarget;a.type=a.data;try{if(b&&b!==c&&!b.parentNode)return;while(b&&b!==this)b=b.parentNode;b!==this&&f.event.handle.apply(this,arguments)}catch(d){}},H=function(a){a.type=a.data,f.event.handle.apply(this,arguments)};f.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){f.event.special[a]={setup:function(c){f.event.add(this,b,c&&c.selector?H:G,a)},teardown:function(a){f.event.remove(this,b,a&&a.selector?H:G)}}}),f.support.submitBubbles||(f.event.special.submit={setup:function(a,b){if(!f.nodeName(this,"form"))f.event.add(this,"click.specialSubmit",function(a){var b=a.target,c=b.type;(c==="submit"||c==="image")&&f(b).closest("form").length&&L("submit",this,arguments)}),f.event.add(this,"keypress.specialSubmit",function(a){var b=a.target,c=b.type;(c==="text"||c==="password")&&f(b).closest("form").length&&a.keyCode===13&&L("submit",this,arguments)});else return!1},teardown:function(a){f.event.remove(this,".specialSubmit")}});if(!f.support.changeBubbles){var I,J=function(a){var b=a.type,c=a.value;b==="radio"||b==="checkbox"?c=a.checked:b==="select-multiple"?c=a.selectedIndex>-1?f.map(a.options,function(a){return a.selected}).join("-"):"":f.nodeName(a,"select")&&(c=a.selectedIndex);return c},K=function(c){var d=c.target,e,g;if(!!z.test(d.nodeName)&&!d.readOnly){e=f._data(d,"_change_data"),g=J(d),(c.type!=="focusout"||d.type!=="radio")&&f._data(d,"_change_data",g);if(e===b||g===e)return;if(e!=null||g)c.type="change",c.liveFired=b,f.event.trigger(c,arguments[1],d)}};f.event.special.change={filters:{focusout:K,beforedeactivate:K,click:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(c==="radio"||c==="checkbox"||f.nodeName(b,"select"))&&K.call(this,a)},keydown:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(a.keyCode===13&&!f.nodeName(b,"textarea")||a.keyCode===32&&(c==="checkbox"||c==="radio")||c==="select-multiple")&&K.call(this,a)},beforeactivate:function(a){var b=a.target;f._data(b,"_change_data",J(b))}},setup:function(a,b){if(this.type==="file")return!1;for(var c in I)f.event.add(this,c+".specialChange",I[c]);return z.test(this.nodeName)},teardown:function(a){f.event.remove(this,".specialChange");return z.test(this.nodeName)}},I=f.event.special.change.filters,I.focus=I.beforeactivate}f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){function e(a){var c=f.event.fix(a);c.type=b,c.originalEvent={},f.event.trigger(c,null,c.target),c.isDefaultPrevented()&&a.preventDefault()}var d=0;f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.each(["bind","one"],function(a,c){f.fn[c]=function(a,d,e){var g;if(typeof a=="object"){for(var h in a)this[c](h,d,a[h],e);return this}if(arguments.length===2||d===!1)e=d,d=b;c==="one"?(g=function(a){f(this).unbind(a,g);return e.apply(this,arguments)},g.guid=e.guid||f.guid++):g=e;if(a==="unload"&&c!=="one")this.one(a,d,e);else for(var i=0,j=this.length;i<j;i++)f.event.add(this[i],a,g,d);return this}}),f.fn.extend({unbind:function(a,b){if(typeof a=="object"&&!a.preventDefault)for(var c in a)this.unbind(c,a[c]);else for(var d=0,e=this.length;d<e;d++)f.event.remove(this[d],a,b);return this},delegate:function(a,b,c,d){return this.live(b,c,d,a)},undelegate:function(a,b,c){return arguments.length===0?this.unbind("live"):this.die(b,null,c,a)},trigger:function(a,b){return this.each(function(){f.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return f.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=arguments,c=a.guid||f.guid++,d=0,e=function(c){var e=(f.data(this,"lastToggle"+a.guid)||0)%d;f.data(this,"lastToggle"+a.guid,e+1),c.preventDefault();return b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});var M={focus:"focusin",blur:"focusout",mouseenter:"mouseover",mouseleave:"mouseout"};f.each(["live","die"],function(a,c){f.fn[c]=function(a,d,e,g){var h,i=0,j,k,l,m=g||this.selector,n=g?this:f(this.context);if(typeof a=="object"&&!a.preventDefault){for(var o in a)n[c](o,d,a[o],m);return this}if(c==="die"&&!a&&g&&g.charAt(0)==="."){n.unbind(g);return this}if(d===!1||f.isFunction(d))e=d||E,d=b;a=(a||"").split(" ");while((h=a[i++])!=null){j=y.exec(h),k="",j&&(k=j[0],h=h.replace(y,""));if(h==="hover"){a.push("mouseenter"+k,"mouseleave"+k);continue}l=h,M[h]?(a.push(M[h]+k),h=h+k):h=(M[h]||h)+k;if(c==="live")for(var p=0,q=n.length;p<q;p++)f.event.add(n[p],"live."+O(h,m),{data:d,selector:m,handler:e,origType:h,origHandler:e,preType:l});else n.unbind("live."+O(h,m),e)}return this}}),f.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error".split(" "),function(a,b){f.fn[b]=function(a,c){c==null&&(c=a,a=null);return arguments.length>0?this.bind(b,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0)}),function(){function u(a,b,c,d,e,f){for(var g=0,h=d.length;g<h;g++){var i=d[g];if(i){var j=!1;i=i[a];while(i){if(i.sizcache===c){j=d[i.sizset];break}if(i.nodeType===1){f||(i.sizcache=c,i.sizset=g);if(typeof b!="string"){if(i===b){j=!0;break}}else if(k.filter(b,[i]).length>0){j=i;break}}i=i[a]}d[g]=j}}}function t(a,b,c,d,e,f){for(var g=0,h=d.length;g<h;g++){var i=d[g];if(i){var j=!1;i=i[a];while(i){if(i.sizcache===c){j=d[i.sizset];break}i.nodeType===1&&!f&&(i.sizcache=c,i.sizset=g);if(i.nodeName.toLowerCase()===b){j=i;break}i=i[a]}d[g]=j}}}var a=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d=0,e=Object.prototype.toString,g=!1,h=!0,i=/\\/g,j=/\W/;[0,0].sort(function(){h=!1;return 0});var k=function(b,d,f,g){f=f||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return f;var i,j,n,o,q,r,s,t,u=!0,w=k.isXML(d),x=[],y=b;do{a.exec(""),i=a.exec(y);if(i){y=i[3],x.push(i[1]);if(i[2]){o=i[3];break}}}while(i);if(x.length>1&&m.exec(b))if(x.length===2&&l.relative[x[0]])j=v(x[0]+x[1],d);else{j=l.relative[x[0]]?[d]:k(x.shift(),d);while(x.length)b=x.shift(),l.relative[b]&&(b+=x.shift()),j=v(b,j)}else{!g&&x.length>1&&d.nodeType===9&&!w&&l.match.ID.test(x[0])&&!l.match.ID.test(x[x.length-1])&&(q=k.find(x.shift(),d,w),d=q.expr?k.filter(q.expr,q.set)[0]:q.set[0]);if(d){q=g?{expr:x.pop(),set:p(g)}:k.find(x.pop(),x.length===1&&(x[0]==="~"||x[0]==="+")&&d.parentNode?d.parentNode:d,w),j=q.expr?k.filter(q.expr,q.set):q.set,x.length>0?n=p(j):u=!1;while(x.length)r=x.pop(),s=r,l.relative[r]?s=x.pop():r="",s==null&&(s=d),l.relative[r](n,s,w)}else n=x=[]}n||(n=j),n||k.error(r||b);if(e.call(n)==="[object Array]")if(!u)f.push.apply(f,n);else if(d&&d.nodeType===1)for(t=0;n[t]!=null;t++)n[t]&&(n[t]===!0||n[t].nodeType===1&&k.contains(d,n[t]))&&f.push(j[t]);else for(t=0;n[t]!=null;t++)n[t]&&n[t].nodeType===1&&f.push(j[t]);else p(n,f);o&&(k(o,h,f,g),k.uniqueSort(f));return f};k.uniqueSort=function(a){if(r){g=h,a.sort(r);if(g)for(var b=1;b<a.length;b++)a[b]===a[b-1]&&a.splice(b--,1)}return a},k.matches=function(a,b){return k(a,null,null,b)},k.matchesSelector=function(a,b){return k(b,null,null,[a]).length>0},k.find=function(a,b,c){var d;if(!a)return[];for(var e=0,f=l.order.length;e<f;e++){var g,h=l.order[e];if(g=l.leftMatch[h].exec(a)){var j=g[1];g.splice(1,1);if(j.substr(j.length-1)!=="\\"){g[1]=(g[1]||"").replace(i,""),d=l.find[h](g,b,c);if(d!=null){a=a.replace(l.match[h],"");break}}}}d||(d=typeof b.getElementsByTagName!="undefined"?b.getElementsByTagName("*"):[]);return{set:d,expr:a}},k.filter=function(a,c,d,e){var f,g,h=a,i=[],j=c,m=c&&c[0]&&k.isXML(c[0]);while(a&&c.length){for(var n in l.filter)if((f=l.leftMatch[n].exec(a))!=null&&f[2]){var o,p,q=l.filter[n],r=f[1];g=!1,f.splice(1,1);if(r.substr(r.length-1)==="\\")continue;j===i&&(i=[]);if(l.preFilter[n]){f=l.preFilter[n](f,j,d,i,e,m);if(!f)g=o=!0;else if(f===!0)continue}if(f)for(var s=0;(p=j[s])!=null;s++)if(p){o=q(p,f,s,j);var t=e^!!o;d&&o!=null?t?g=!0:j[s]=!1:t&&(i.push(p),g=!0)}if(o!==b){d||(j=i),a=a.replace(l.match[n],"");if(!g)return[];break}}if(a===h)if(g==null)k.error(a);else break;h=a}return j},k.error=function(a){throw"Syntax error, unrecognized expression: "+a};var l=k.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(a){return a.getAttribute("href")},type:function(a){return a.getAttribute("type")}},relative:{"+":function(a,b){var c=typeof b=="string",d=c&&!j.test(b),e=c&&!d;d&&(b=b.toLowerCase());for(var f=0,g=a.length,h;f<g;f++)if(h=a[f]){while((h=h.previousSibling)&&h.nodeType!==1);a[f]=e||h&&h.nodeName.toLowerCase()===b?h||!1:h===b}e&&k.filter(b,a,!0)},">":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!j.test(b)){b=b.toLowerCase();for(;e<f;e++){c=a[e];if(c){var g=c.parentNode;a[e]=g.nodeName.toLowerCase()===b?g:!1}}}else{for(;e<f;e++)c=a[e],c&&(a[e]=d?c.parentNode:c.parentNode===b);d&&k.filter(b,a,!0)}},"":function(a,b,c){var e,f=d++,g=u;typeof b=="string"&&!j.test(b)&&(b=b.toLowerCase(),e=b,g=t),g("parentNode",b,f,a,e,c)},"~":function(a,b,c){var e,f=d++,g=u;typeof b=="string"&&!j.test(b)&&(b=b.toLowerCase(),e=b,g=t),g("previousSibling",b,f,a,e,c)}},find:{ID:function(a,b,c){if(typeof b.getElementById!="undefined"&&!c){var d=b.getElementById(a[1]);return d&&d.parentNode?[d]:[]}},NAME:function(a,b){if(typeof b.getElementsByName!="undefined"){var c=[],d=b.getElementsByName(a[1]);for(var e=0,f=d.length;e<f;e++)d[e].getAttribute("name")===a[1]&&c.push(d[e]);return c.length===0?null:c}},TAG:function(a,b){if(typeof b.getElementsByTagName!="undefined")return b.getElementsByTagName(a[1])}},preFilter:{CLASS:function(a,b,c,d,e,f){a=" "+a[1].replace(i,"")+" ";if(f)return a;for(var g=0,h;(h=b[g])!=null;g++)h&&(e^(h.className&&(" "+h.className+" ").replace(/[\t\n\r]/g," ").indexOf(a)>=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(i,"")},TAG:function(a,b){return a[1].replace(i,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||k.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&k.error(a[0]);a[0]=d++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(i,"");!f&&l.attrMap[g]&&(a[1]=l.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(i,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=k(b[3],null,null,c);else{var g=k.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(l.match.POS.test(b[0])||l.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!k(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return b<c[3]-0},gt:function(a,b,c){return b>c[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=l.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||k.getText([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h<i;h++)if(g[h]===a)return!1;return!0}k.error(e)},CHILD:function(a,b){var c=b[1],d=a;switch(c){case"only":case"first":while(d=d.previousSibling)if(d.nodeType===1)return!1;if(c==="first")return!0;d=a;case"last":while(d=d.nextSibling)if(d.nodeType===1)return!1;return!0;case"nth":var e=b[2],f=b[3];if(e===1&&f===0)return!0;var g=b[0],h=a.parentNode;if(h&&(h.sizcache!==g||!a.nodeIndex)){var i=0;for(d=h.firstChild;d;d=d.nextSibling)d.nodeType===1&&(d.nodeIndex=++i);h.sizcache=g}var j=a.nodeIndex-f;return e===0?j===0:j%e===0&&j/e>=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=l.attrHandle[c]?l.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=l.setFilters[e];if(f)return f(a,c,b,d)}}},m=l.match.POS,n=function(a,b){return"\\"+(b-0+1)};for(var o in l.match)l.match[o]=new RegExp(l.match[o].source+/(?![^\[]*\])(?![^\(]*\))/.source),l.leftMatch[o]=new RegExp(/(^(?:.|\r|\n)*?)/.source+l.match[o].source.replace(/\\(\d+)/g,n));var p=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(q){p=function(a,b){var c=0,d=b||[];if(e.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var f=a.length;c<f;c++)d.push(a[c]);else for(;a[c];c++)d.push(a[c]);return d}}var r,s;c.documentElement.compareDocumentPosition?r=function(a,b){if(a===b){g=!0;return 0}if(!a.compareDocumentPosition||!b.compareDocumentPosition)return a.compareDocumentPosition?-1:1;return a.compareDocumentPosition(b)&4?-1:1}:(r=function(a,b){if(a===b){g=!0;return 0}if(a.sourceIndex&&b.sourceIndex)return a.sourceIndex-b.sourceIndex;var c,d,e=[],f=[],h=a.parentNode,i=b.parentNode,j=h;if(h===i)return s(a,b);if(!h)return-1;if(!i)return 1;while(j)e.unshift(j),j=j.parentNode;j=i;while(j)f.unshift(j),j=j.parentNode;c=e.length,d=f.length;for(var k=0;k<c&&k<d;k++)if(e[k]!==f[k])return s(e[k],f[k]);return k===c?s(a,f[k],-1):s(e[k],b,1)},s=function(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}),k.getText=function(a){var b="",c;for(var d=0;a[d];d++)c=a[d],c.nodeType===3||c.nodeType===4?b+=c.nodeValue:c.nodeType!==8&&(b+=k.getText(c.childNodes));return b},function(){var a=c.createElement("div"),d="script"+(new Date).getTime(),e=c.documentElement;a.innerHTML="<a name='"+d+"'/>",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(l.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},l.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(l.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(l.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=k,b=c.createElement("div"),d="__sizzle__";b.innerHTML="<p class='TEST'></p>";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){k=function(b,e,f,g){e=e||c;if(!g&&!k.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return p(e.getElementsByTagName(b),f);if(h[2]&&l.find.CLASS&&e.getElementsByClassName)return p(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return p([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return p([],f);if(i.id===h[3])return p([i],f)}try{return p(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var m=e,n=e.getAttribute("id"),o=n||d,q=e.parentNode,r=/^\s*[+~]/.test(b);n?o=o.replace(/'/g,"\\$&"):e.setAttribute("id",o),r&&q&&(e=e.parentNode);try{if(!r||q)return p(e.querySelectorAll("[id='"+o+"'] "+b),f)}catch(s){}finally{n||m.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)k[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}k.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!k.isXML(a))try{if(e||!l.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return k(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="<div class='test e'></div><div class='test'></div>";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;l.order.splice(1,0,"CLASS"),l.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?k.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?k.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:k.contains=function(){return!1},k.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var v=function(a,b){var c,d=[],e="",f=b.nodeType?[b]:b;while(c=l.match.PSEUDO.exec(a))e+=c[0],a=a.replace(l.match.PSEUDO,"");a=l.relative[a]?a+"*":a;for(var g=0,h=f.length;g<h;g++)k(a,f[g],d);return k.filter(e,d)};f.find=k,f.expr=k.selectors,f.expr[":"]=f.expr.filters,f.unique=k.uniqueSort,f.text=k.getText,f.isXMLDoc=k.isXML,f.contains=k.contains}();var P=/Until$/,Q=/^(?:parents|prevUntil|prevAll)/,R=/,/,S=/^.[^:#\[\.,]*$/,T=Array.prototype.slice,U=f.expr.match.POS,V={children:!0,contents:!0,next:!0,prev:!0};f.fn.extend({find:function(a){var b=this,c,d;if(typeof a!="string")return f(a).filter(function(){for(c=0,d=b.length;c<d;c++)if(f.contains(b[c],this))return!0});var e=this.pushStack("","find",a),g,h,i;for(c=0,d=this.length;c<d;c++){g=e.length,f.find(a,this[c],e);if(c>0)for(h=g;h<e.length;h++)for(i=0;i<g;i++)if(e[i]===e[h]){e.splice(h--,1);break}}return e},has:function(a){var b=f(a);return this.filter(function(){for(var a=0,c=b.length;a<c;a++)if(f.contains(this,b[a]))return!0})},not:function(a){return this.pushStack(X(this,a,!1),"not",a)},filter:function(a){return this.pushStack(X(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h,i,j={},k=1;if(g&&a.length){for(d=0,e=a.length;d<e;d++)i=a[d],j[i]||(j[i]=U.test(i)?f(i,b||this.context):i);while(g&&g.ownerDocument&&g!==b){for(i in j)h=j[i],(h.jquery?h.index(g)>-1:f(g).is(h))&&c.push({selector:i,elem:g,level:k});g=g.parentNode,k++}}return c}var l=U.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d<e;d++){g=this[d];while(g){if(l?l.index(g)>-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a||typeof a=="string")return f.inArray(this[0],a?f(a):this.parent().children());return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(W(c[0])||W(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c),g=T.call(arguments);P.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!V[a]?f.unique(e):e,(this.length>1||R.test(d))&&Q.test(a)&&(e=e.reverse());return this.pushStack(e,a,g.join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var Y=/ jQuery\d+="(?:\d+|null)"/g,Z=/^\s+/,$=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,_=/<([\w:]+)/,ba=/<tbody/i,bb=/<|&#?\w+;/,bc=/<(?:script|object|embed|option|style)/i,bd=/checked\s*(?:[^=]|=\s*.checked.)/i,be=/\/(java|ecma)script/i,bf=/^\s*<!(?:\[CDATA\[|\-\-)/,bg={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]};bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div<div>","</div>"]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){f(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f(arguments[0]).toArray());return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Y,""):null;if(typeof a=="string"&&!bc.test(a)&&(f.support.leadingWhitespace||!Z.test(a))&&!bg[(_.exec(a)||["",""])[1].toLowerCase()]){a=a.replace($,"<$1></$2>");try{for(var c=0,d=this.length;c<d;c++)this[c].nodeType===1&&(f.cleanData(this[c].getElementsByTagName("*")),this[c].innerHTML=a)}catch(e){this.empty().append(a)}}else f.isFunction(a)?this.each(function(b){var c=f(this);c.html(a.call(this,b,c.html()))}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&this[0].parentNode){if(f.isFunction(a))return this.each(function(b){var c=f(this),d=c.html();c.replaceWith(a.call(this,b,d))});typeof a!="string"&&(a=f(a).detach());return this.each(function(){var b=this.nextSibling,c=this.parentNode;f(this).remove(),b?f(b).before(a):f(c).append(a)})}return this.length?this.pushStack(f(f.isFunction(a)?a():a),"replaceWith",a):this},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){var e,g,h,i,j=a[0],k=[];if(!f.support.checkClone&&arguments.length===3&&typeof j=="string"&&bd.test(j))return this.each(function(){f(this).domManip(a,c,d,!0)});if(f.isFunction(j))return this.each(function(e){var g=f(this);a[0]=j.call(this,e,c?g.html():b),g.domManip(a,c,d)});if(this[0]){i=j&&j.parentNode,f.support.parentNode&&i&&i.nodeType===11&&i.childNodes.length===this.length?e={fragment:i}:e=f.buildFragment(a,this,k),h=e.fragment,h.childNodes.length===1?g=h=h.firstChild:g=h.firstChild;if(g){c=c&&f.nodeName(g,"tr");for(var l=0,m=this.length,n=m-1;l<m;l++)d.call(c?bh(this[l],g):this[l],e.cacheable||m>1&&l<n?f.clone(h,!0,!0):h)}k.length&&f.each(k,bn)}return this}}),f.buildFragment=function(a,b,d){var e,g,h,i=b&&b[0]?b[0].ownerDocument||b[0]:c;a.length===1&&typeof a[0]=="string"&&a[0].length<512&&i===c&&a[0].charAt(0)==="<"&&!bc.test(a[0])&&(f.support.checkClone||!bd.test(a[0]))&&(g=!0,h=f.fragments[a[0]],h&&h!==1&&(e=h)),e||(e=i.createDocumentFragment(),f.clean(a,i,e,d)),g&&(f.fragments[a[0]]=h?e:1);return{fragment:e,cacheable:g}},f.fragments={},f.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){f.fn[a]=function(c){var d=[],e=f(c),g=this.length===1&&this[0].parentNode;if(g&&g.nodeType===11&&g.childNodes.length===1&&e.length===1){e[b](this[0]);return this}for(var h=0,i=e.length;h<i;h++){var j=(h>0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d=a.cloneNode(!0),e,g,h;if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bj(a,d),e=bk(a),g=bk(d);for(h=0;e[h];++h)bj(e[h],g[h])}if(b){bi(a,d);if(c){e=bk(a),g=bk(d);for(h=0;e[h];++h)bi(e[h],g[h])}}return d},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||
+b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!bb.test(k))k=b.createTextNode(k);else{k=k.replace($,"<$1></$2>");var l=(_.exec(k)||["",""])[1].toLowerCase(),m=bg[l]||bg._default,n=m[0],o=b.createElement("div");o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=ba.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]==="<table>"&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&Z.test(k)&&o.insertBefore(b.createTextNode(Z.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i<r;i++)bm(k[i]);else bm(k);k.nodeType?h.push(k):h=f.merge(h,k)}if(d){g=function(a){return!a.type||be.test(a.type)};for(j=0;h[j];j++)if(e&&f.nodeName(h[j],"script")&&(!h[j].type||h[j].type.toLowerCase()==="text/javascript"))e.push(h[j].parentNode?h[j].parentNode.removeChild(h[j]):h[j]);else{if(h[j].nodeType===1){var s=f.grep(h[j].getElementsByTagName("script"),g);h.splice.apply(h,[j+1,0].concat(s))}d.appendChild(h[j])}}return h},cleanData:function(a){var b,c,d=f.cache,e=f.expando,g=f.event.special,h=f.support.deleteExpando;for(var i=0,j;(j=a[i])!=null;i++){if(j.nodeName&&f.noData[j.nodeName.toLowerCase()])continue;c=j[f.expando];if(c){b=d[c]&&d[c][e];if(b&&b.events){for(var k in b.events)g[k]?f.event.remove(j,k):f.removeEvent(j,k,b.handle);b.handle&&(b.handle.elem=null)}h?delete j[f.expando]:j.removeAttribute&&j.removeAttribute(f.expando),delete d[c]}}}});var bo=/alpha\([^)]*\)/i,bp=/opacity=([^)]*)/,bq=/-([a-z])/ig,br=/([A-Z]|^ms)/g,bs=/^-?\d+(?:px)?$/i,bt=/^-?\d/,bu=/^[+\-]=/,bv=/[^+\-\.\de]+/g,bw={position:"absolute",visibility:"hidden",display:"block"},bx=["Left","Right"],by=["Top","Bottom"],bz,bA,bB,bC=function(a,b){return b.toUpperCase()};f.fn.css=function(a,c){if(arguments.length===2&&c===b)return this;return f.access(this,a,c,!0,function(a,c,d){return d!==b?f.style(a,c,d):f.css(a,c)})},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bz(a,"opacity","opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{zIndex:!0,fontWeight:!0,opacity:!0,zoom:!0,lineHeight:!0,widows:!0,orphans:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d;if(h==="number"&&isNaN(d)||d==null)return;h==="string"&&bu.test(d)&&(d=+d.replace(bv,"")+parseFloat(f.css(a,c))),h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(bz)return bz(a,c)},swap:function(a,b,c){var d={};for(var e in b)d[e]=a.style[e],a.style[e]=b[e];c.call(a);for(e in b)a.style[e]=d[e]},camelCase:function(a){return a.replace(bq,bC)}}),f.curCSS=f.css,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){var e;if(c){a.offsetWidth!==0?e=bD(a,b,d):f.swap(a,bw,function(){e=bD(a,b,d)});if(e<=0){e=bz(a,b,b),e==="0px"&&bB&&(e=bB(a,b,b));if(e!=null)return e===""||e==="auto"?"0px":e}if(e<0||e==null){e=a.style[b];return e===""||e==="auto"?"0px":e}return typeof e=="string"?e:e+"px"}},set:function(a,b){if(!bs.test(b))return b;b=parseFloat(b);if(b>=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bp.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle;c.zoom=1;var e=f.isNaN(b)?"":"alpha(opacity="+b*100+")",g=d&&d.filter||c.filter||"";c.filter=bo.test(g)?g.replace(bo,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bz(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(bA=function(a,c){var d,e,g;c=c.replace(br,"-$1").toLowerCase();if(!(e=a.ownerDocument.defaultView))return b;if(g=e.getComputedStyle(a,null))d=g.getPropertyValue(c),d===""&&!f.contains(a.ownerDocument.documentElement,a)&&(d=f.style(a,c));return d}),c.documentElement.currentStyle&&(bB=function(a,b){var c,d=a.currentStyle&&a.currentStyle[b],e=a.runtimeStyle&&a.runtimeStyle[b],f=a.style;!bs.test(d)&&bt.test(d)&&(c=f.left,e&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":d||0,d=f.pixelLeft+"px",f.left=c,e&&(a.runtimeStyle.left=e));return d===""?"auto":d}),bz=bA||bB,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bE=/%20/g,bF=/\[\]$/,bG=/\r?\n/g,bH=/#.*$/,bI=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bJ=/^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bK=/^(?:about|app|app\-storage|.+\-extension|file|widget):$/,bL=/^(?:GET|HEAD)$/,bM=/^\/\//,bN=/\?/,bO=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,bP=/^(?:select|textarea)/i,bQ=/\s+/,bR=/([?&])_=[^&]*/,bS=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bT=f.fn.load,bU={},bV={},bW,bX;try{bW=e.href}catch(bY){bW=c.createElement("a"),bW.href="",bW=bW.href}bX=bS.exec(bW.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bT)return bT.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("<div>").append(c.replace(bO,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bP.test(this.nodeName)||bJ.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bG,"\r\n")}}):{name:b.name,value:c.replace(bG,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.bind(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?f.extend(!0,a,f.ajaxSettings,b):(b=a,a=f.extend(!0,f.ajaxSettings,b));for(var c in{context:1,url:1})c in b?a[c]=b[c]:c in f.ajaxSettings&&(a[c]=f.ajaxSettings[c]);return a},ajaxSettings:{url:bW,isLocal:bK.test(bX[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":"*/*"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML}},ajaxPrefilter:bZ(bU),ajaxTransport:bZ(bV),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a?4:0;var o,r,u,w=l?ca(d,v,l):b,x,y;if(a>=200&&a<300||a===304){if(d.ifModified){if(x=v.getResponseHeader("Last-Modified"))f.lastModified[k]=x;if(y=v.getResponseHeader("Etag"))f.etag[k]=y}if(a===304)c="notmodified",o=!0;else try{r=cb(d,w),c="success",o=!0}catch(z){c="parsererror",u=z}}else{u=c;if(!c||a)c="error",a<0&&(a=0)}v.status=a,v.statusText=c,o?h.resolveWith(e,[r,c,v]):h.rejectWith(e,[v,c,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.resolveWith(e,[v,c]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f._Deferred(),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bI.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.done,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bH,"").replace(bM,bX[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bQ),d.crossDomain==null&&(r=bS.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bX[1]&&r[2]==bX[2]&&(r[3]||(r[1]==="http:"?80:443))==(bX[3]||(bX[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),b$(bU,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bL.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bN.test(d.url)?"&":"?")+d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bR,"$1_="+x);d.url=y+(y===d.url?(bN.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", */*; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=b$(bV,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){status<2?w(-1,z):f.error(z)}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)b_(g,a[g],c,e);return d.join("&").replace(bE,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cc=f.now(),cd=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cc++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(cd.test(b.url)||e&&cd.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(cd,l),b.url===j&&(e&&(k=k.replace(cd,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var ce=a.ActiveXObject?function(){for(var a in cg)cg[a](0,1)}:!1,cf=0,cg;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ch()||ci()}:ch,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,ce&&delete cg[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cf,ce&&(cg||(cg={},f(a).unload(ce)),cg[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cj={},ck,cl,cm=/^(?:toggle|show|hide)$/,cn=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,co,cp=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cq,cr=a.webkitRequestAnimationFrame||a.mozRequestAnimationFrame||a.oRequestAnimationFrame;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cu("show",3),a,b,c);for(var g=0,h=this.length;g<h;g++)d=this[g],d.style&&(e=d.style.display,!f._data(d,"olddisplay")&&e==="none"&&(e=d.style.display=""),e===""&&f.css(d,"display")==="none"&&f._data(d,"olddisplay",cv(d.nodeName)));for(g=0;g<h;g++){d=this[g];if(d.style){e=d.style.display;if(e===""||e==="none")d.style.display=f._data(d,"olddisplay")||""}}return this},hide:function(a,b,c){if(a||a===0)return this.animate(cu("hide",3),a,b,c);for(var d=0,e=this.length;d<e;d++)if(this[d].style){var g=f.css(this[d],"display");g!=="none"&&!f._data(this[d],"olddisplay")&&f._data(this[d],"olddisplay",g)}for(d=0;d<e;d++)this[d].style&&(this[d].style.display="none");return this},_toggle:f.fn.toggle,toggle:function(a,b,c){var d=typeof a=="boolean";f.isFunction(a)&&f.isFunction(b)?this._toggle.apply(this,arguments):a==null||d?this.each(function(){var b=d?a:f(this).is(":hidden");f(this)[b?"show":"hide"]()}):this.animate(cu("toggle",3),a,b,c);return this},fadeTo:function(a,b,c,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=f.speed(b,c,d);if(f.isEmptyObject(a))return this.each(e.complete,[!1]);a=f.extend({},a);return this[e.queue===!1?"each":"queue"](function(){e.queue===!1&&f._mark(this);var b=f.extend({},e),c=this.nodeType===1,d=c&&f(this).is(":hidden"),g,h,i,j,k,l,m,n,o;b.animatedProperties={};for(i in a){g=f.camelCase(i),i!==g&&(a[g]=a[i],delete a[i]),h=a[g],f.isArray(h)?(b.animatedProperties[g]=h[1],h=a[g]=h[0]):b.animatedProperties[g]=b.specialEasing&&b.specialEasing[g]||b.easing||"swing";if(h==="hide"&&d||h==="show"&&!d)return b.complete.call(this);c&&(g==="height"||g==="width")&&(b.overflow=[this.style.overflow,this.style.overflowX,this.style.overflowY],f.css(this,"display")==="inline"&&f.css(this,"float")==="none"&&(f.support.inlineBlockNeedsLayout?(j=cv(this.nodeName),j==="inline"?this.style.display="inline-block":(this.style.display="inline",this.style.zoom=1)):this.style.display="inline-block"))}b.overflow!=null&&(this.style.overflow="hidden");for(i in a)k=new f.fx(this,b,i),h=a[i],cm.test(h)?k[h==="toggle"?d?"show":"hide":h]():(l=cn.exec(h),m=k.cur(),l?(n=parseFloat(l[2]),o=l[3]||(f.cssNumber[i]?"":"px"),o!=="px"&&(f.style(this,i,(n||1)+o),m=(n||1)/k.cur()*m,f.style(this,i,m+o)),l[1]&&(n=(l[1]==="-="?-1:1)*n+m),k.custom(m,n,o)):k.custom(m,h,""));return!0})},stop:function(a,b){a&&this.queue([]),this.each(function(){var a=f.timers,c=a.length;b||f._unmark(!0,this);while(c--)a[c].elem===this&&(b&&a[c](!0),a.splice(c,1))}),b||this.dequeue();return this}}),f.each({slideDown:cu("show",1),slideUp:cu("hide",1),slideToggle:cu("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){f.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),f.extend({speed:function(a,b,c){var d=a&&typeof a=="object"?f.extend({},a):{complete:c||!c&&b||f.isFunction(a)&&a,duration:a,easing:c&&b||b&&!f.isFunction(b)&&b};d.duration=f.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in f.fx.speeds?f.fx.speeds[d.duration]:f.fx.speeds._default,d.old=d.complete,d.complete=function(a){d.queue!==!1?f.dequeue(this):a!==!1&&f._unmark(this),f.isFunction(d.old)&&d.old.call(this)};return d},easing:{linear:function(a,b,c,d){return c+d*a},swing:function(a,b,c,d){return(-Math.cos(a*Math.PI)/2+.5)*d+c}},timers:[],fx:function(a,b,c){this.options=b,this.elem=a,this.prop=c,b.orig=b.orig||{}}}),f.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this),(f.fx.step[this.prop]||f.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a,b=f.css(this.elem,this.prop);return isNaN(a=parseFloat(b))?!b||b==="auto"?0:b:a},custom:function(a,b,c){function h(a){return d.step(a)}var d=this,e=f.fx,g;this.startTime=cq||cs(),this.start=a,this.end=b,this.unit=c||this.unit||(f.cssNumber[this.prop]?"":"px"),this.now=this.start,this.pos=this.state=0,h.elem=this.elem,h()&&f.timers.push(h)&&!co&&(cr?(co=1,g=function(){co&&(cr(g),e.tick())},cr(g)):co=setInterval(e.tick,e.interval))},show:function(){this.options.orig[this.prop]=f.style(this.elem,this.prop),this.options.show=!0,this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur()),f(this.elem).show()},hide:function(){this.options.orig[this.prop]=f.style(this.elem,this.prop),this.options.hide=!0,this.custom(this.cur(),0)},step:function(a){var b=cq||cs(),c=!0,d=this.elem,e=this.options,g,h;if(a||b>=e.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),e.animatedProperties[this.prop]=!0;for(g in e.animatedProperties)e.animatedProperties[g]!==!0&&(c=!1);if(c){e.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){d.style["overflow"+b]=e.overflow[a]}),e.hide&&f(d).hide();if(e.hide||e.show)for(var i in e.animatedProperties)f.style(d,i,e.orig[i]);e.complete.call(d)}return!1}e.duration==Infinity?this.now=b:(h=b-this.startTime,this.state=h/e.duration,this.pos=f.easing[e.animatedProperties[this.prop]](this.state,h,0,1,e.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){for(var a=f.timers,b=0;b<a.length;++b)a[b]()||a.splice(b--,1);a.length||f.fx.stop()},interval:13,stop:function(){clearInterval(co),co=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){f.style(a.elem,"opacity",a.now)},_default:function(a){a.elem.style&&a.elem.style[a.prop]!=null?a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit:a.elem[a.prop]=a.now}}}),f.expr&&f.expr.filters&&(f.expr.filters.animated=function(a){return f.grep(f.timers,function(b){return a===b.elem}).length});var cw=/^t(?:able|d|h)$/i,cx=/^(?:body|html)$/i;"getBoundingClientRect"in c.documentElement?f.fn.offset=function(a){var b=this[0],c;if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);try{c=b.getBoundingClientRect()}catch(d){}var e=b.ownerDocument,g=e.documentElement;if(!c||!f.contains(g,b))return c?{top:c.top,left:c.left}:{top:0,left:0};var h=e.body,i=cy(e),j=g.clientTop||h.clientTop||0,k=g.clientLeft||h.clientLeft||0,l=i.pageYOffset||f.support.boxModel&&g.scrollTop||h.scrollTop,m=i.pageXOffset||f.support.boxModel&&g.scrollLeft||h.scrollLeft,n=c.top+l-j,o=c.left+m-k;return{top:n,left:o}}:f.fn.offset=function(a){var b=this[0];if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);f.offset.initialize();var c,d=b.offsetParent,e=b,g=b.ownerDocument,h=g.documentElement,i=g.body,j=g.defaultView,k=j?j.getComputedStyle(b,null):b.currentStyle,l=b.offsetTop,m=b.offsetLeft;while((b=b.parentNode)&&b!==i&&b!==h){if(f.offset.supportsFixedPosition&&k.position==="fixed")break;c=j?j.getComputedStyle(b,null):b.currentStyle,l-=b.scrollTop,m-=b.scrollLeft,b===d&&(l+=b.offsetTop,m+=b.offsetLeft,f.offset.doesNotAddBorder&&(!f.offset.doesAddBorderForTableAndCells||!cw.test(b.nodeName))&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),e=d,d=b.offsetParent),f.offset.subtractsBorderForOverflowNotVisible&&c.overflow!=="visible"&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),k=c}if(k.position==="relative"||k.position==="static")l+=i.offsetTop,m+=i.offsetLeft;f.offset.supportsFixedPosition&&k.position==="fixed"&&(l+=Math.max(h.scrollTop,i.scrollTop),m+=Math.max(h.scrollLeft,i.scrollLeft));return{top:l,left:m}},f.offset={initialize:function(){var a=c.body,b=c.createElement("div"),d,e,g,h,i=parseFloat(f.css(a,"marginTop"))||0,j="<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";f.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"}),b.innerHTML=j,a.insertBefore(b,a.firstChild),d=b.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,this.doesNotAddBorder=e.offsetTop!==5,this.doesAddBorderForTableAndCells=h.offsetTop===5,e.style.position="fixed",e.style.top="20px",this.supportsFixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",this.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==i,a.removeChild(b),f.offset.initialize=f.noop},bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.offset.initialize(),f.offset.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cy(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cy(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){return this[0]?parseFloat(f.css(this[0],d,"padding")):null},f.fn["outer"+c]=function(a){return this[0]?parseFloat(f.css(this[0],d,a?"margin":"border")):null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c];return e.document.compatMode==="CSS1Compat"&&g||e.document.body["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var h=f.css(e,d),i=parseFloat(h);return f.isNaN(i)?h:i}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f})(window);
\ No newline at end of file
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/lex_test.html b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/lex_test.html
new file mode 100644
index 0000000..788bfcd
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/lex_test.html
@@ -0,0 +1,122 @@
+<html>
+  <head>
+    <title>JSONSelect JS lex tests</title>
+    <link rel="stylesheet" type="text/css" href="js/doctest.css" />
+    <script src="js/doctest.js"></script>
+    <script src="../jsonselect.js"></script>
+    <meta http-equiv="Content-type" content="text/html; charset=utf-8">
+  </head>
+  <body>
+
+<div>
+  <button onclick="doctest()" type="button">run tests</button>
+  <pre id="doctestOutput"></pre>
+</div>
+
+    <h2> Tests of the JSONSelect lexer </h2>
+
+<div class="test">
+Simple tokens
+<pre class="doctest">
+$ JSONSelect._lex(">");
+[1, ">"]
+$ JSONSelect._lex("*");
+[1, "*"]
+$ JSONSelect._lex(",");
+[1, ","]
+$ JSONSelect._lex(".");
+[1, "."]
+</pre>
+</div>
+
+<div class="test">
+Offsets
+<pre class="doctest">
+$ JSONSelect._lex("foobar>",6);
+[7, ">"]
+</pre>
+</div>
+
+<div class="test">
+Types
+<pre class="doctest">
+$ JSONSelect._lex("string");
+[6, 3, "string"]
+$ JSONSelect._lex("boolean");
+[7, 3, "boolean"]
+$ JSONSelect._lex("null");
+[4, 3, "null"]
+$ JSONSelect._lex("array");
+[5, 3, "array"]
+$ JSONSelect._lex("object");
+[6, 3, "object"]
+$ JSONSelect._lex("number");
+[6, 3, "number"]
+</pre>
+</div>
+
+<div class="test">
+Whitespace
+<pre class="doctest">
+$ JSONSelect._lex("\r");
+[1, " "]
+$ JSONSelect._lex("\n");
+[1, " "]
+$ JSONSelect._lex("\t");
+[1, " "]
+$ JSONSelect._lex(" ");
+[1, " "]
+$ JSONSelect._lex("     \t   \r\n  !");
+[13, " "]
+</pre>
+
+<div class="test">
+pseudo classes
+<pre class="doctest">
+$ JSONSelect._lex(":root");
+[5, 1, ":root"]
+$ JSONSelect._lex(":first-child");
+[12, 1, ":first-child"]
+$ JSONSelect._lex(":last-child");
+[11, 1, ":last-child"]
+$ JSONSelect._lex(":only-child");
+[11, 1, ":only-child"]
+</pre>
+</div>
+
+<div class="test">
+json strings
+<pre class="doctest">
+$ JSONSelect._lex('"foo bar baz"');
+[13, 4, "foo bar baz"]
+$ JSONSelect._lex('"\\u0020"');
+[8, 4, " "]
+$ JSONSelect._lex('\"not terminated');
+Error: unclosed json string
+$ JSONSelect._lex('"invalid escape: \\y"');
+Error: invalid json string
+</pre>
+</div>
+
+<div class="test">
+identifiers (like after '.')
+<pre class="doctest">
+$ JSONSelect._lex("foo");
+[3, 4, "foo"]
+$ JSONSelect._lex("foo\\ bar");
+[8, 4, "foo bar"]
+$ JSONSelect._lex("_aB129bcde-\\:foo\\@$");
+[18, 4, "_aB129bcde-:foo@"]
+</pre>
+</div>
+
+<div class="test">
+non-ascii
+<pre class="doctest">
+$ JSONSelect._lex("обичам\\ те\\!");
+[12, 4, "обичам те!"]
+</pre>
+</div>
+
+  </body>
+</html>
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/match_test.html b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/match_test.html
new file mode 100644
index 0000000..0fc784f
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/match_test.html
@@ -0,0 +1,71 @@
+<html>
+  <head>
+    <title>JSONSelect JS matching tests</title>
+    <link rel="stylesheet" type="text/css" href="js/doctest.css" />
+    <script src="js/doctest.js"></script>
+    <script src="../jsonselect.js"></script>
+    <meta http-equiv="Content-type" content="text/html; charset=utf-8">
+  </head>
+  <body>
+
+<div>
+  <button onclick="doctest()" type="button">run tests</button>
+  <pre id="doctestOutput"></pre>
+</div>
+
+    <h2> Tests of the JSONSelect matcher </h2>
+
+<div class="test">
+Types
+<pre class="doctest">
+$ JSONSelect.match("null", null);
+[null]
+$ JSONSelect.match("array", { 1: [], 2: [] });
+[[], []]
+$ JSONSelect.match("object", [ {}, {} ]);
+[{}, {}]
+$ JSONSelect.match("string", [ "a", 1, true, null, false, "b", 3.1415, "c" ] );
+["a", "b", "c"]
+$ JSONSelect.match("boolean", [ "a", 1, true, null, false, "b", 3.1415, "c" ] );
+[true, false]
+$ JSONSelect.match("number", [ "a", 1, true, null, false, "b", 3.1415, "c" ] );
+[1, 3.1415]
+</pre>
+</div>
+
+<div class="test">
+IDs
+<pre class="doctest">
+$ JSONSelect.match(".foo", {foo: "aMatch", bar: [ { foo: "anotherMatch" } ] });
+["aMatch", "anotherMatch"]
+</pre>
+</div>
+
+<div class="test">
+Descendants
+<pre class="doctest">
+$ JSONSelect.match(".foo .bar", {foo: { baz: 1, bar: 2 }, bar: 3});
+[2]
+$ JSONSelect.match(".foo > .bar", {foo: { baz: 1, bar: 2 }, bar: 3});
+[2]
+$ JSONSelect.match(".foo > .bar", {foo: { baz: { bar: 4 }, bar: 2 }, bar: 3});
+[2]
+$ JSONSelect.match(".foo .bar", {foo: { baz: { bar: 4 }, bar: 2 }, bar: 3});
+[4, 2]
+</pre>
+</div>
+
+<div class="test">
+Grouping
+<pre class="doctest">
+$ JSONSelect.match("number,boolean", [ "a", 1, true, null, false, "b", 3.1415, "c" ] );
+[1, true, false, 3.1415]
+$ JSONSelect.match("number,boolean,null", [ "a", 1, true, null, false, "b", 3.1415, "c" ] );
+[1, true, null, false, 3.1415]
+</pre>
+</div>
+
+  </body>
+</html>
+
+
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/parse_test.html b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/parse_test.html
new file mode 100644
index 0000000..9744457
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/parse_test.html
@@ -0,0 +1,98 @@
+<html>
+  <head>
+    <title>JSONSelect JS parser tests</title>
+    <link rel="stylesheet" type="text/css" href="js/doctest.css" />
+    <script src="js/doctest.js"></script>
+    <script src="../jsonselect.js"></script>
+    <meta http-equiv="Content-type" content="text/html; charset=utf-8">
+  </head>
+  <body>
+
+<div>
+  <button onclick="doctest()" type="button">run tests</button>
+  <pre id="doctestOutput"></pre>
+</div>
+
+    <h2> Tests of the JSONSelect parser </h2>
+
+<div class="test">
+Selectors
+<pre class="doctest">
+$ JSONSelect._parse(".foo");
+[{id: "foo"}]
+$ JSONSelect._parse('." foo "');
+[{id: " foo "}]
+$ JSONSelect._parse("string.foo:last-child");
+[{a: 0, b: 1, id: "foo", pf: ":nth-last-child", type: "string"}]
+$ JSONSelect._parse("string.foo.bar");
+Error: multiple ids not allowed
+$ JSONSelect._parse("string.foo:first-child.bar");
+Error: multiple ids not allowed
+$ JSONSelect._parse("string:last-child.foo:first-child.bar");
+Error: multiple pseudo classes (:xxx) not allowed
+$ JSONSelect._parse("string.");
+Error: string required after '.'
+$ JSONSelect._parse("string:bogus");
+Error: unrecognized pseudo class
+$ JSONSelect._parse("string.xxx\\@yyy");
+[{id: "xxx@yyy", type: "string"}]
+$ JSONSelect._parse(" ");
+Error: selector expected
+$ JSONSelect._parse("");
+Error: selector expected
+</pre>
+</div>
+
+<div class="test">
+Combinators
+<pre class="doctest">
+$ JSONSelect._parse(".foo .bar");
+[{id: "foo"}, {id: "bar"}]
+$ JSONSelect._parse("string.foo , number.foo");
+[",", [{id: "foo", type: "string"}], [{id: "foo", type: "number"}]]
+$ JSONSelect._parse("string > .foo number.bar");
+[{type: "string"}, ">", {id: "foo"}, {id: "bar", type: "number"}]
+$ JSONSelect._parse("string > .foo number.bar, object");
+[",", [{type: "string"}, ">", {id: "foo"}, {id: "bar", type: "number"}], [{type: "object"}]]
+$ JSONSelect._parse("string > .foo number.bar, object, string, .\"baz bing\", :root");
+[
+  ",",
+  [{type: "string"}, ">", {id: "foo"}, {id: "bar", type: "number"}],
+  [{type: "object"}],
+  [{type: "string"}],
+  [{id: "baz bing"}],
+  [{pc: ":root"}]
+]
+</pre>
+</div>
+
+<div class="test">
+Expressions
+<pre class="doctest">
+$ JSONSelect._parse(":nth-child(1)");
+[{a: 0, b: 1, pf: ":nth-child"}]
+$ JSONSelect._parse(":nth-child(2n+1)");
+[{a: 2, b: 1, pf: ":nth-child"}]
+$ JSONSelect._parse(":nth-child ( 2n + 1 )");
+[{a: 2, b: 1, pf: ":nth-child"}]
+$ JSONSelect._parse(":nth-child(odd)");
+[{a: 2, b: 1, pf: ":nth-child"}]
+$ JSONSelect._parse(":nth-child(even)");
+[{a: 2, b: 0, pf: ":nth-child"}]
+$ JSONSelect._parse(":nth-child(-n+6)");
+[{a: -1, b: 6, pf: ":nth-child"}]
+$ JSONSelect._parse(":nth-child(2n)");
+[{a: 2, b: 0, pf: ":nth-child"}]
+$ JSONSelect._parse(":nth-last-child(-3n - 3)");
+[{a: -3, b: -3, pf: ":nth-last-child"}]
+$ JSONSelect._parse(":first-child");
+[{a: 0, b: 1, pf: ":nth-child"}]
+$ JSONSelect._parse(":last-child");
+[{a: 0, b: 1, pf: ":nth-last-child"}]
+</pre>
+</div>
+
+  </body>
+</html>
+
+
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/run.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/run.js
new file mode 100644
index 0000000..c873c3a
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/run.js
@@ -0,0 +1,85 @@
+/*
+ * a node.js test runner that executes all conformance
+ * tests and outputs results to console.
+ * Process returns zero on success, non-zero on failure.
+ */
+
+const   fs = require('fs'),
+      path = require('path'),
+jsonselect = require('../jsonselect.js'),
+       sys = require('sys');
+
+var pathToTests = path.join(__dirname, "tests");
+
+// a map: document nametest name -> list of se
+var numTests = 0;
+var numPassed = 0;
+var tests = {};
+
+function runOneSync(name, selname, p) {
+    var testDocPath = path.join(p, name + ".json");
+    var selDocPath = path.join(p, name + '_' +
+                               selname + ".selector");
+    var outputDocPath = selDocPath.replace(/selector$/, "output");
+
+    // take `obj`, apply `sel, get `got`, is it what we `want`? 
+    var obj = JSON.parse(fs.readFileSync(testDocPath));
+    var want = String(fs.readFileSync(outputDocPath)).trim();
+    var got = "";
+    var sel = String(fs.readFileSync(selDocPath)).trim();
+
+    try {
+        jsonselect.forEach(sel, obj, function(m) {
+            got += JSON.stringify(m, undefined, 4) + "\n";
+        });
+    } catch(e) {
+        got = e.toString();
+        if (want.trim() != got.trim()) throw e;
+    }
+    if (want.trim() != got.trim()) throw "mismatch";
+}
+
+
+function runTests() {
+    console.log("Running Tests:"); 
+    for (var l in tests) {
+        for (var d in tests[l]) {
+            console.log("  level " + l + " tests against \"" + d + ".json\":");
+            for (var i = 0; i < tests[l][d].length; i++) {
+                sys.print("    " + tests[l][d][i][0] + ": ");
+                try {
+                    runOneSync(d, tests[l][d][i][0], tests[l][d][i][1]);
+                    numPassed++;
+                    console.log("pass");
+                } catch (e) {
+                    console.log("fail (" + e.toString() + ")");
+                }
+            }
+        }
+    }
+    console.log(numPassed + "/" + numTests + " passed");
+    process.exit(numPassed == numTests ? 0 : 1);
+}
+
+// discover all tests
+var pathToTests = path.join(__dirname, "tests");
+
+fs.readdirSync(pathToTests).forEach(function(subdir) {
+    var p = path.join(pathToTests, subdir);
+    if (!fs.statSync(p).isDirectory()) return;
+    var l = /^level_([\d+])$/.exec(subdir);
+    if (!l) return;
+    l = l[1];
+    var files = fs.readdirSync(p);
+    for (var i = 0; i < files.length; i++) {
+        var f = files[i];
+        var m = /^([A-Za-z]+)_(.+)\.selector$/.exec(f);
+        if (m) {
+            if (!tests.hasOwnProperty(l)) tests[l] = [];
+            if (!tests[l].hasOwnProperty(m[1])) tests[l][m[1]] = [];
+            numTests++;
+            tests[l][m[1]].push([m[2], p]);
+        }
+    }
+});
+runTests();
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/.npmignore b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/.npmignore
new file mode 100644
index 0000000..b25c15b
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/.npmignore
@@ -0,0 +1 @@
+*~
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/README.md b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/README.md
new file mode 100644
index 0000000..a9e59ca
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/README.md
@@ -0,0 +1,22 @@
+## JSONSelect Conformance Tests.
+
+This repository contains conformance tests for the
+[JSONSelect](http://jsonselect.org) selector language.  The tests
+are divided among subdirectories which correspond to "level" supported
+by a particular implementation.  Levels are described more fully in the 
+jsonselect [documentation](http://jsonselect.org/#docs).
+
+## Test organization
+
+Test documents have a suffix of `.json`, like `basic.json`.
+
+Selectors to be applied to test documents have the document name,
+followed by an underbar, followed by a description of the test, with
+a `.selector` file name suffix, like `basic_grouping.selector`.
+
+Expected output files have the same name as the `.selector` file, 
+but have a `.output` suffix, like `basic_grouping.output`.
+
+Expected output files contain a stream of JSON objects that are what
+is expected to be produced when a given selector is applied to a given
+document.
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic.json b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic.json
new file mode 100644
index 0000000..83c3769
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic.json
@@ -0,0 +1,31 @@
+{
+    "name": {
+        "first": "Lloyd",
+        "last": "Hilaiel"
+    },
+    "favoriteColor": "yellow",
+    "languagesSpoken": [
+        {
+            "language": "Bulgarian",
+            "level": "advanced"
+        },
+        {
+            "language": "English",
+            "level": "native"
+        },
+        {
+            "language": "Spanish",
+            "level": "beginner"
+        }
+    ],
+    "seatingPreference": [
+        "window",
+        "aisle"
+    ],
+    "drinkPreference": [
+        "beer",
+        "whiskey",
+        "wine"
+    ],
+    "weight": 172
+}
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_first-child.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_first-child.output
new file mode 100644
index 0000000..ba6c81a
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_first-child.output
@@ -0,0 +1,2 @@
+"window"
+"beer"
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_first-child.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_first-child.selector
new file mode 100644
index 0000000..8288dac
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_first-child.selector
@@ -0,0 +1,2 @@
+string:first-child
+
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_grouping.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_grouping.output
new file mode 100644
index 0000000..ebbc034
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_grouping.output
@@ -0,0 +1,4 @@
+"advanced"
+"native"
+"beginner"
+172
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_grouping.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_grouping.selector
new file mode 100644
index 0000000..b8d0d79
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_grouping.selector
@@ -0,0 +1 @@
+string.level,number
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id.output
new file mode 100644
index 0000000..1511f18
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id.output
@@ -0,0 +1 @@
+"yellow"
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id.selector
new file mode 100644
index 0000000..7a3e32b
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id.selector
@@ -0,0 +1 @@
+.favoriteColor
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_multiple.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_multiple.output
new file mode 100644
index 0000000..9021aaa
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_multiple.output
@@ -0,0 +1,3 @@
+"Bulgarian"
+"English"
+"Spanish"
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_multiple.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_multiple.selector
new file mode 100644
index 0000000..24170a9
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_multiple.selector
@@ -0,0 +1 @@
+.language
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_quotes.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_quotes.output
new file mode 100644
index 0000000..1912273
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_quotes.output
@@ -0,0 +1,2 @@
+172
+
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_quotes.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_quotes.selector
new file mode 100644
index 0000000..1adaed0
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_quotes.selector
@@ -0,0 +1 @@
+."weight"
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_with_type.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_with_type.output
new file mode 100644
index 0000000..1511f18
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_with_type.output
@@ -0,0 +1 @@
+"yellow"
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_with_type.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_with_type.selector
new file mode 100644
index 0000000..48c2619
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_with_type.selector
@@ -0,0 +1 @@
+string.favoriteColor
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_last-child.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_last-child.output
new file mode 100644
index 0000000..9a334d0
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_last-child.output
@@ -0,0 +1,2 @@
+"aisle"
+"wine"
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_last-child.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_last-child.selector
new file mode 100644
index 0000000..8591de0
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_last-child.selector
@@ -0,0 +1,2 @@
+string:last-child
+
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-child-2.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-child-2.output
new file mode 100644
index 0000000..b55a882
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-child-2.output
@@ -0,0 +1,4 @@
+"window"
+"aisle"
+"beer"
+"whiskey"
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-child-2.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-child-2.selector
new file mode 100644
index 0000000..6418a97
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-child-2.selector
@@ -0,0 +1 @@
+string:nth-child(-n+2)
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-child.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-child.output
new file mode 100644
index 0000000..196d836
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-child.output
@@ -0,0 +1,3 @@
+"window"
+"beer"
+"wine"
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-child.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-child.selector
new file mode 100644
index 0000000..9bd4c5f
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-child.selector
@@ -0,0 +1 @@
+string:nth-child(odd)
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-last-child.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-last-child.output
new file mode 100644
index 0000000..9a334d0
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-last-child.output
@@ -0,0 +1,2 @@
+"aisle"
+"wine"
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-last-child.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-last-child.selector
new file mode 100644
index 0000000..bc100d5
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-last-child.selector
@@ -0,0 +1 @@
+string:nth-last-child(1)
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_root_pseudo.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_root_pseudo.output
new file mode 100644
index 0000000..83c3769
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_root_pseudo.output
@@ -0,0 +1,31 @@
+{
+    "name": {
+        "first": "Lloyd",
+        "last": "Hilaiel"
+    },
+    "favoriteColor": "yellow",
+    "languagesSpoken": [
+        {
+            "language": "Bulgarian",
+            "level": "advanced"
+        },
+        {
+            "language": "English",
+            "level": "native"
+        },
+        {
+            "language": "Spanish",
+            "level": "beginner"
+        }
+    ],
+    "seatingPreference": [
+        "window",
+        "aisle"
+    ],
+    "drinkPreference": [
+        "beer",
+        "whiskey",
+        "wine"
+    ],
+    "weight": 172
+}
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_root_pseudo.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_root_pseudo.selector
new file mode 100644
index 0000000..4035e80
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_root_pseudo.selector
@@ -0,0 +1 @@
+:root
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type.output
new file mode 100644
index 0000000..332db77
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type.output
@@ -0,0 +1,14 @@
+"Lloyd"
+"Hilaiel"
+"yellow"
+"Bulgarian"
+"advanced"
+"English"
+"native"
+"Spanish"
+"beginner"
+"window"
+"aisle"
+"beer"
+"whiskey"
+"wine"
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type.selector
new file mode 100644
index 0000000..ec186f1
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type.selector
@@ -0,0 +1 @@
+string
\ No newline at end of file
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type2.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type2.output
new file mode 100644
index 0000000..730a054
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type2.output
@@ -0,0 +1 @@
+172
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type2.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type2.selector
new file mode 100644
index 0000000..0dfad6f
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type2.selector
@@ -0,0 +1 @@
+number
\ No newline at end of file
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type3.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type3.output
new file mode 100644
index 0000000..8394ec8
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type3.output
@@ -0,0 +1,47 @@
+{
+    "first": "Lloyd",
+    "last": "Hilaiel"
+}
+{
+    "language": "Bulgarian",
+    "level": "advanced"
+}
+{
+    "language": "English",
+    "level": "native"
+}
+{
+    "language": "Spanish",
+    "level": "beginner"
+}
+{
+    "name": {
+        "first": "Lloyd",
+        "last": "Hilaiel"
+    },
+    "favoriteColor": "yellow",
+    "languagesSpoken": [
+        {
+            "language": "Bulgarian",
+            "level": "advanced"
+        },
+        {
+            "language": "English",
+            "level": "native"
+        },
+        {
+            "language": "Spanish",
+            "level": "beginner"
+        }
+    ],
+    "seatingPreference": [
+        "window",
+        "aisle"
+    ],
+    "drinkPreference": [
+        "beer",
+        "whiskey",
+        "wine"
+    ],
+    "weight": 172
+}
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type3.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type3.selector
new file mode 100644
index 0000000..e2f6b77
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type3.selector
@@ -0,0 +1 @@
+object
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_universal.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_universal.output
new file mode 100644
index 0000000..cfbf20a
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_universal.output
@@ -0,0 +1,85 @@
+"Lloyd"
+"Hilaiel"
+{
+    "first": "Lloyd",
+    "last": "Hilaiel"
+}
+"yellow"
+"Bulgarian"
+"advanced"
+{
+    "language": "Bulgarian",
+    "level": "advanced"
+}
+"English"
+"native"
+{
+    "language": "English",
+    "level": "native"
+}
+"Spanish"
+"beginner"
+{
+    "language": "Spanish",
+    "level": "beginner"
+}
+[
+    {
+        "language": "Bulgarian",
+        "level": "advanced"
+    },
+    {
+        "language": "English",
+        "level": "native"
+    },
+    {
+        "language": "Spanish",
+        "level": "beginner"
+    }
+]
+"window"
+"aisle"
+[
+    "window",
+    "aisle"
+]
+"beer"
+"whiskey"
+"wine"
+[
+    "beer",
+    "whiskey",
+    "wine"
+]
+172
+{
+    "name": {
+        "first": "Lloyd",
+        "last": "Hilaiel"
+    },
+    "favoriteColor": "yellow",
+    "languagesSpoken": [
+        {
+            "language": "Bulgarian",
+            "level": "advanced"
+        },
+        {
+            "language": "English",
+            "level": "native"
+        },
+        {
+            "language": "Spanish",
+            "level": "beginner"
+        }
+    ],
+    "seatingPreference": [
+        "window",
+        "aisle"
+    ],
+    "drinkPreference": [
+        "beer",
+        "whiskey",
+        "wine"
+    ],
+    "weight": 172
+}
\ No newline at end of file
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_universal.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_universal.selector
new file mode 100644
index 0000000..72e8ffc
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_universal.selector
@@ -0,0 +1 @@
+*
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision.json b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision.json
new file mode 100644
index 0000000..d2bc912
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision.json
@@ -0,0 +1,6 @@
+{
+    "object": {
+      "string": "some string",
+      "stringTwo": "some other string"
+    }
+}
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_nested.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_nested.output
new file mode 100644
index 0000000..e39067c
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_nested.output
@@ -0,0 +1 @@
+"some string"
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_nested.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_nested.selector
new file mode 100644
index 0000000..5eaa374
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_nested.selector
@@ -0,0 +1 @@
+.object .string
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_quoted-string.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_quoted-string.output
new file mode 100644
index 0000000..e39067c
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_quoted-string.output
@@ -0,0 +1 @@
+"some string"
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_quoted-string.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_quoted-string.selector
new file mode 100644
index 0000000..4faa4aa
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_quoted-string.selector
@@ -0,0 +1 @@
+."string"
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_string.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_string.output
new file mode 100644
index 0000000..e39067c
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_string.output
@@ -0,0 +1 @@
+"some string"
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_string.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_string.selector
new file mode 100644
index 0000000..8b2be0c
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_string.selector
@@ -0,0 +1 @@
+.string
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling.json b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling.json
new file mode 100644
index 0000000..6e5aecb
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling.json
@@ -0,0 +1,18 @@
+{
+  "a": 1,
+  "b": 2,
+  "c": {
+    "a": 3,
+    "b": 4,
+    "c": {
+      "a": 5,
+      "b": 6
+    }
+  },
+  "d": {
+    "a": 7
+  },
+  "e": {
+    "b": 8
+  }
+}
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_childof.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_childof.output
new file mode 100644
index 0000000..0cfbf08
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_childof.output
@@ -0,0 +1 @@
+2
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_childof.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_childof.selector
new file mode 100644
index 0000000..f13fbdd
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_childof.selector
@@ -0,0 +1,2 @@
+:root > .a ~ .b
+
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_descendantof.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_descendantof.output
new file mode 100644
index 0000000..e2ba1ef
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_descendantof.output
@@ -0,0 +1,3 @@
+2
+4
+6
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_descendantof.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_descendantof.selector
new file mode 100644
index 0000000..7c637b9
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_descendantof.selector
@@ -0,0 +1 @@
+:root .a ~ .b
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_unrooted.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_unrooted.output
new file mode 100644
index 0000000..e2ba1ef
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_unrooted.output
@@ -0,0 +1,3 @@
+2
+4
+6
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_unrooted.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_unrooted.selector
new file mode 100644
index 0000000..9a4ae06
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_unrooted.selector
@@ -0,0 +1 @@
+.a ~ .b
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic.json b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic.json
new file mode 100644
index 0000000..83c3769
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic.json
@@ -0,0 +1,31 @@
+{
+    "name": {
+        "first": "Lloyd",
+        "last": "Hilaiel"
+    },
+    "favoriteColor": "yellow",
+    "languagesSpoken": [
+        {
+            "language": "Bulgarian",
+            "level": "advanced"
+        },
+        {
+            "language": "English",
+            "level": "native"
+        },
+        {
+            "language": "Spanish",
+            "level": "beginner"
+        }
+    ],
+    "seatingPreference": [
+        "window",
+        "aisle"
+    ],
+    "drinkPreference": [
+        "beer",
+        "whiskey",
+        "wine"
+    ],
+    "weight": 172
+}
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-multiple.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-multiple.output
new file mode 100644
index 0000000..1cb38cb
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-multiple.output
@@ -0,0 +1,4 @@
+{
+    "first": "Lloyd",
+    "last": "Hilaiel"
+}
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-multiple.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-multiple.selector
new file mode 100644
index 0000000..956f1bc
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-multiple.selector
@@ -0,0 +1 @@
+:root > object:has(string.first):has(string.last)
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-root-in-expr.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-root-in-expr.output
new file mode 100644
index 0000000..1cb38cb
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-root-in-expr.output
@@ -0,0 +1,4 @@
+{
+    "first": "Lloyd",
+    "last": "Hilaiel"
+}
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-root-in-expr.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-root-in-expr.selector
new file mode 100644
index 0000000..329e224
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-root-in-expr.selector
@@ -0,0 +1 @@
+:has(:root > .first)
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-sans-first-paren.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-sans-first-paren.output
new file mode 100644
index 0000000..32f476b
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-sans-first-paren.output
@@ -0,0 +1 @@
+Error: opening paren expected '(' in 'object:has .language)'
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-sans-first-paren.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-sans-first-paren.selector
new file mode 100644
index 0000000..6165481
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-sans-first-paren.selector
@@ -0,0 +1 @@
+object:has .language)
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-sans-paren.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-sans-paren.output
new file mode 100644
index 0000000..3baa674
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-sans-paren.output
@@ -0,0 +1 @@
+Error: missing closing paren in 'object:has(.language'
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-sans-paren.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-sans-paren.selector
new file mode 100644
index 0000000..8111708
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-sans-paren.selector
@@ -0,0 +1,2 @@
+object:has(.language
+
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-whitespace.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-whitespace.output
new file mode 100644
index 0000000..7a87f46
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-whitespace.output
@@ -0,0 +1,12 @@
+{
+    "language": "Bulgarian",
+    "level": "advanced"
+}
+{
+    "language": "English",
+    "level": "native"
+}
+{
+    "language": "Spanish",
+    "level": "beginner"
+}
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-whitespace.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-whitespace.selector
new file mode 100644
index 0000000..69d2801
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-whitespace.selector
@@ -0,0 +1 @@
+.languagesSpoken object:has       (       .language       ) 
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-with-comma.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-with-comma.output
new file mode 100644
index 0000000..2df8708
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-with-comma.output
@@ -0,0 +1,17 @@
+{
+    "first": "Lloyd",
+    "last": "Hilaiel"
+}
+{
+    "language": "Bulgarian",
+    "level": "advanced"
+}
+{
+    "language": "English",
+    "level": "native"
+}
+{
+    "language": "Spanish",
+    "level": "beginner"
+}
+
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-with-comma.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-with-comma.selector
new file mode 100644
index 0000000..67e78d3
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-with-comma.selector
@@ -0,0 +1 @@
+:has(:root > .language, :root > .last)
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has.output
new file mode 100644
index 0000000..7a87f46
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has.output
@@ -0,0 +1,12 @@
+{
+    "language": "Bulgarian",
+    "level": "advanced"
+}
+{
+    "language": "English",
+    "level": "native"
+}
+{
+    "language": "Spanish",
+    "level": "beginner"
+}
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has.selector
new file mode 100644
index 0000000..aaa0c19
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has.selector
@@ -0,0 +1 @@
+.languagesSpoken object:has(.language)
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_multiple-has-with-strings.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_multiple-has-with-strings.output
new file mode 100644
index 0000000..1ce5c78
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_multiple-has-with-strings.output
@@ -0,0 +1,5 @@
+{
+    "first": "Lloyd",
+    "last": "Hilaiel"
+}
+
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_multiple-has-with-strings.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_multiple-has-with-strings.selector
new file mode 100644
index 0000000..7dd0647
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_multiple-has-with-strings.selector
@@ -0,0 +1 @@
+:has(:val("Lloyd")) object:has(:val("Hilaiel"))
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr.json b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr.json
new file mode 100644
index 0000000..e1c9de8
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr.json
@@ -0,0 +1,9 @@
+{
+    "int": 42,
+    "float": 3.1415,
+    "string": "foo.exe",
+    "string2": "bar.dmg",
+    "null": null,
+    "true": true,
+    "false": false
+}
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_div.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_div.output
new file mode 100644
index 0000000..d81cc07
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_div.output
@@ -0,0 +1 @@
+42
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_div.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_div.selector
new file mode 100644
index 0000000..55a288c
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_div.selector
@@ -0,0 +1 @@
+:expr(7 = x / 6)
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_ends-with.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_ends-with.output
new file mode 100644
index 0000000..5517937
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_ends-with.output
@@ -0,0 +1 @@
+"bar.dmg"
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_ends-with.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_ends-with.selector
new file mode 100644
index 0000000..456c961
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_ends-with.selector
@@ -0,0 +1 @@
+:expr(x $= ".dmg")
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_false-eq.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_false-eq.output
new file mode 100644
index 0000000..a232278
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_false-eq.output
@@ -0,0 +1,3 @@
+false
+
+
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_false-eq.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_false-eq.selector
new file mode 100644
index 0000000..b906ff9
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_false-eq.selector
@@ -0,0 +1,4 @@
+:expr(false=x)
+
+
+
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_greater-than.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_greater-than.output
new file mode 100644
index 0000000..4f86ad6
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_greater-than.output
@@ -0,0 +1,2 @@
+42
+3.1415
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_greater-than.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_greater-than.selector
new file mode 100644
index 0000000..878cf08
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_greater-than.selector
@@ -0,0 +1 @@
+:expr(x>3.1)
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_less-than.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_less-than.output
new file mode 100644
index 0000000..4f34d97
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_less-than.output
@@ -0,0 +1,2 @@
+3.1415
+
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_less-than.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_less-than.selector
new file mode 100644
index 0000000..bd4f1bc
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_less-than.selector
@@ -0,0 +1 @@
+:expr(x<4)
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_mod.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_mod.output
new file mode 100644
index 0000000..d81cc07
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_mod.output
@@ -0,0 +1 @@
+42
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_mod.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_mod.selector
new file mode 100644
index 0000000..fec575b
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_mod.selector
@@ -0,0 +1 @@
+:expr((12 % 10) + 40 = x)
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_mult.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_mult.output
new file mode 100644
index 0000000..d81cc07
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_mult.output
@@ -0,0 +1 @@
+42
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_mult.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_mult.selector
new file mode 100644
index 0000000..869c723
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_mult.selector
@@ -0,0 +1 @@
+:expr(7 * 6 = x)
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_null-eq.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_null-eq.output
new file mode 100644
index 0000000..9fba3cf
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_null-eq.output
@@ -0,0 +1,5 @@
+null
+
+
+
+
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_null-eq.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_null-eq.selector
new file mode 100644
index 0000000..d015ac3
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_null-eq.selector
@@ -0,0 +1,4 @@
+:expr(null=x)
+
+
+
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_number-eq.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_number-eq.output
new file mode 100644
index 0000000..d81cc07
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_number-eq.output
@@ -0,0 +1 @@
+42
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_number-eq.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_number-eq.selector
new file mode 100644
index 0000000..4440ecb
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_number-eq.selector
@@ -0,0 +1 @@
+:expr(42 = x)
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_precedence-1.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_precedence-1.output
new file mode 100644
index 0000000..f32a580
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_precedence-1.output
@@ -0,0 +1 @@
+true
\ No newline at end of file
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_precedence-1.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_precedence-1.selector
new file mode 100644
index 0000000..eb214ef
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_precedence-1.selector
@@ -0,0 +1 @@
+.true:expr( 4 + 5 * 6 / 5 + 3 * 2 = 16 )
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_precedence-2.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_precedence-2.output
new file mode 100644
index 0000000..27ba77d
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_precedence-2.output
@@ -0,0 +1 @@
+true
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_precedence-2.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_precedence-2.selector
new file mode 100644
index 0000000..79c7f2c
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_precedence-2.selector
@@ -0,0 +1 @@
+.true:expr( (4 + 5) * 6 / (2 + 1) * 2 = 36 )
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_simple-false.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_simple-false.output
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_simple-false.output
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_simple-false.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_simple-false.selector
new file mode 100644
index 0000000..d39a80a
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_simple-false.selector
@@ -0,0 +1 @@
+.true:expr(1=2)
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_simple.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_simple.output
new file mode 100644
index 0000000..27ba77d
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_simple.output
@@ -0,0 +1 @@
+true
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_simple.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_simple.selector
new file mode 100644
index 0000000..d912927
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_simple.selector
@@ -0,0 +1 @@
+.true:expr(1=1)
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_starts-with.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_starts-with.output
new file mode 100644
index 0000000..f07ea50
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_starts-with.output
@@ -0,0 +1 @@
+"foo.exe"
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_starts-with.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_starts-with.selector
new file mode 100644
index 0000000..f76cd75
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_starts-with.selector
@@ -0,0 +1 @@
+:expr(x ^= "foo")
\ No newline at end of file
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_string-eq.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_string-eq.output
new file mode 100644
index 0000000..f07ea50
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_string-eq.output
@@ -0,0 +1 @@
+"foo.exe"
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_string-eq.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_string-eq.selector
new file mode 100644
index 0000000..0f19d62
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_string-eq.selector
@@ -0,0 +1 @@
+:expr(x = "foo.exe")
\ No newline at end of file
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_true-eq.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_true-eq.output
new file mode 100644
index 0000000..28b0c28
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_true-eq.output
@@ -0,0 +1,3 @@
+true
+
+
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_true-eq.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_true-eq.selector
new file mode 100644
index 0000000..02e1841
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_true-eq.selector
@@ -0,0 +1,2 @@
+:expr(true = x)
+
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/polykids.json b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/polykids.json
new file mode 100644
index 0000000..2429d0f
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/polykids.json
@@ -0,0 +1,15 @@
+[
+    {
+        "language": "Bulgarian",
+        "level": "advanced"
+    },
+    {
+        "language": "English",
+        "level": "native",
+        "preferred": true
+    },
+    {
+        "language": "Spanish",
+        "level": "beginner"
+    }
+]
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/polykids_has-with_descendant.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/polykids_has-with_descendant.output
new file mode 100644
index 0000000..9699f99
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/polykids_has-with_descendant.output
@@ -0,0 +1 @@
+"English"
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/polykids_has-with_descendant.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/polykids_has-with_descendant.selector
new file mode 100644
index 0000000..84d51be
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/polykids_has-with_descendant.selector
@@ -0,0 +1 @@
+:has(.preferred) > .language
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/polykids_val.output b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/polykids_val.output
new file mode 100644
index 0000000..fd1d03f
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/polykids_val.output
@@ -0,0 +1,2 @@
+"advanced"
+
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/polykids_val.selector b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/polykids_val.selector
new file mode 100644
index 0000000..076f443
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/polykids_val.selector
@@ -0,0 +1 @@
+:has(:root > .language:val("Bulgarian")) > .level
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/.npmignore b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/.npmignore
new file mode 100644
index 0000000..3c3629e
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/.npmignore
@@ -0,0 +1 @@
+node_modules
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/LICENSE b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/LICENSE
new file mode 100644
index 0000000..7b75500
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/LICENSE
@@ -0,0 +1,24 @@
+Copyright 2010 James Halliday (mail@substack.net)
+
+This project is free software released under the MIT/X11 license:
+http://www.opensource.org/licenses/mit-license.php 
+
+Copyright 2010 James Halliday (mail@substack.net)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/README.markdown b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/README.markdown
new file mode 100644
index 0000000..0734006
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/README.markdown
@@ -0,0 +1,273 @@
+traverse
+========
+
+Traverse and transform objects by visiting every node on a recursive walk.
+
+examples
+========
+
+transform negative numbers in-place
+-----------------------------------
+
+negative.js
+
+````javascript
+var traverse = require('traverse');
+var obj = [ 5, 6, -3, [ 7, 8, -2, 1 ], { f : 10, g : -13 } ];
+
+traverse(obj).forEach(function (x) {
+    if (x < 0) this.update(x + 128);
+});
+
+console.dir(obj);
+````
+
+Output:
+
+    [ 5, 6, 125, [ 7, 8, 126, 1 ], { f: 10, g: 115 } ]
+
+collect leaf nodes
+------------------
+
+leaves.js
+
+````javascript
+var traverse = require('traverse');
+
+var obj = {
+    a : [1,2,3],
+    b : 4,
+    c : [5,6],
+    d : { e : [7,8], f : 9 },
+};
+
+var leaves = traverse(obj).reduce(function (acc, x) {
+    if (this.isLeaf) acc.push(x);
+    return acc;
+}, []);
+
+console.dir(leaves);
+````
+
+Output:
+
+    [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
+
+scrub circular references
+-------------------------
+
+scrub.js:
+
+````javascript
+var traverse = require('traverse');
+
+var obj = { a : 1, b : 2, c : [ 3, 4 ] };
+obj.c.push(obj);
+
+var scrubbed = traverse(obj).map(function (x) {
+    if (this.circular) this.remove()
+});
+console.dir(scrubbed);
+````
+
+output:
+
+    { a: 1, b: 2, c: [ 3, 4 ] }
+
+context
+=======
+
+Each method that takes a callback has a context (its `this` object) with these
+attributes:
+
+this.node
+---------
+
+The present node on the recursive walk
+
+this.path
+---------
+
+An array of string keys from the root to the present node
+
+this.parent
+-----------
+
+The context of the node's parent.
+This is `undefined` for the root node.
+
+this.key
+--------
+
+The name of the key of the present node in its parent.
+This is `undefined` for the root node.
+
+this.isRoot, this.notRoot
+-------------------------
+
+Whether the present node is the root node
+
+this.isLeaf, this.notLeaf
+-------------------------
+
+Whether or not the present node is a leaf node (has no children)
+
+this.level
+----------
+
+Depth of the node within the traversal
+
+this.circular
+-------------
+
+If the node equals one of its parents, the `circular` attribute is set to the
+context of that parent and the traversal progresses no deeper.
+
+this.update(value, stopHere=false)
+----------------------------------
+
+Set a new value for the present node.
+
+All the elements in `value` will be recursively traversed unless `stopHere` is
+true.
+
+this.remove()
+-------------
+
+Remove the current element from the output. If the node is in an Array it will
+be spliced off. Otherwise it will be deleted from its parent.
+
+this.delete()
+-------------
+
+Delete the current element from its parent in the output. Calls `delete` even on
+Arrays.
+
+this.before(fn)
+---------------
+
+Call this function before any of the children are traversed.
+
+You can assign into `this.keys` here to traverse in a custom order.
+
+this.after(fn)
+--------------
+
+Call this function after any of the children are traversed.
+
+this.pre(fn)
+------------
+
+Call this function before each of the children are traversed.
+
+this.post(fn)
+-------------
+
+Call this function after each of the children are traversed.
+
+methods
+=======
+
+.map(fn)
+--------
+
+Execute `fn` for each node in the object and return a new object with the
+results of the walk. To update nodes in the result use `this.update(value)`.
+
+.forEach(fn)
+------------
+
+Execute `fn` for each node in the object but unlike `.map()`, when
+`this.update()` is called it updates the object in-place.
+
+.reduce(fn, acc)
+----------------
+
+For each node in the object, perform a
+[left-fold](http://en.wikipedia.org/wiki/Fold_(higher-order_function))
+with the return value of `fn(acc, node)`.
+
+If `acc` isn't specified, `acc` is set to the root object for the first step
+and the root element is skipped.
+
+.deepEqual(obj)
+---------------
+
+Returns a boolean, whether the instance value is equal to the supplied object
+along a deep traversal using some opinionated choices.
+
+Some notes:
+
+* RegExps are equal if their .toString()s match, but not functions since
+functions can close over different variables.
+
+* Date instances are compared using `.getTime()` just like `assert.deepEqual()`.
+
+* Circular references must refer to the same paths within the data structure for
+both objects. For instance, in this snippet:
+
+````javascript
+var a = [1];
+a.push(a); // a = [ 1, *a ]
+
+var b = [1];
+b.push(a); // b = [ 1, [ 1, *a ] ]
+````
+
+`a` is not the same as `b` since even though the expansion is the same, the
+circular references in each refer to different paths into the data structure.
+
+However, in:
+
+````javascript
+var c = [1];
+c.push(c); // c = [ 1, *c ];
+````
+
+`c` is equal to `a` in a `deepEqual()` because they have the same terminal node
+structure.
+
+* Arguments objects are not arrays and neither are they the same as regular
+objects.
+
+* Instances created with `new` of String, Boolean, and Number types are never
+equal to the native versions.
+
+.paths()
+--------
+
+Return an `Array` of every possible non-cyclic path in the object.
+Paths are `Array`s of string keys.
+
+.nodes()
+--------
+
+Return an `Array` of every node in the object.
+
+.clone()
+--------
+
+Create a deep clone of the object.
+
+installation
+============
+
+Using npm:
+    npm install traverse
+
+Or check out the repository and link your development copy:
+    git clone http://github.com/substack/js-traverse.git
+    cd js-traverse
+    npm link .
+
+You can test traverse with "expresso":http://github.com/visionmedia/expresso
+(`npm install expresso`):
+    js-traverse $ expresso
+    
+    100% wahoo, your stuff is not broken!
+
+hash transforms
+===============
+
+This library formerly had a hash transformation component. It has been
+[moved to the hashish package](https://github.com/substack/node-hashish).
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/examples/json.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/examples/json.js
new file mode 100755
index 0000000..50d612e
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/examples/json.js
@@ -0,0 +1,16 @@
+var traverse = require('traverse');
+
+var id = 54;
+var callbacks = {};
+var obj = { moo : function () {}, foo : [2,3,4, function () {}] };
+
+var scrubbed = traverse(obj).map(function (x) {
+    if (typeof x === 'function') {
+        callbacks[id] = { id : id, f : x, path : this.path };
+        this.update('[Function]');
+        id++;
+    }
+});
+
+console.dir(scrubbed);
+console.dir(callbacks);
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/examples/leaves.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/examples/leaves.js
new file mode 100755
index 0000000..c1b310b
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/examples/leaves.js
@@ -0,0 +1,15 @@
+var traverse = require('traverse');
+
+var obj = {
+    a : [1,2,3],
+    b : 4,
+    c : [5,6],
+    d : { e : [7,8], f : 9 },
+};
+
+var leaves = traverse(obj).reduce(function (acc, x) {
+    if (this.isLeaf) acc.push(x);
+    return acc;
+}, []);
+
+console.dir(leaves);
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/examples/negative.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/examples/negative.js
new file mode 100755
index 0000000..78608a0
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/examples/negative.js
@@ -0,0 +1,8 @@
+var traverse = require('traverse');
+var obj = [ 5, 6, -3, [ 7, 8, -2, 1 ], { f : 10, g : -13 } ];
+
+traverse(obj).forEach(function (x) {
+    if (x < 0) this.update(x + 128);
+});
+
+console.dir(obj);
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/examples/scrub.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/examples/scrub.js
new file mode 100755
index 0000000..5d15b91
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/examples/scrub.js
@@ -0,0 +1,10 @@
+// scrub out circular references
+var traverse = require('traverse');
+
+var obj = { a : 1, b : 2, c : [ 3, 4 ] };
+obj.c.push(obj);
+
+var scrubbed = traverse(obj).map(function (x) {
+    if (this.circular) this.remove()
+});
+console.dir(scrubbed);
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/examples/stringify.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/examples/stringify.js
new file mode 100755
index 0000000..167b68b
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/examples/stringify.js
@@ -0,0 +1,38 @@
+#!/usr/bin/env node
+var traverse = require('traverse');
+
+var obj = [ 'five', 6, -3, [ 7, 8, -2, 1 ], { f : 10, g : -13 } ];
+
+var s = '';
+traverse(obj).forEach(function to_s (node) {
+    if (Array.isArray(node)) {
+        this.before(function () { s += '[' });
+        this.post(function (child) {
+            if (!child.isLast) s += ',';
+        });
+        this.after(function () { s += ']' });
+    }
+    else if (typeof node == 'object') {
+        this.before(function () { s += '{' });
+        this.pre(function (x, key) {
+            to_s(key);
+            s += ':';
+        });
+        this.post(function (child) {
+            if (!child.isLast) s += ',';
+        });
+        this.after(function () { s += '}' });
+    }
+    else if (typeof node == 'string') {
+        s += '"' + node.toString().replace(/"/g, '\\"') + '"';
+    }
+    else if (typeof node == 'function') {
+        s += 'null';
+    }
+    else {
+        s += node.toString();
+    }
+});
+
+console.log('JSON.stringify: ' + JSON.stringify(obj));
+console.log('this stringify: ' + s);
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/index.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/index.js
new file mode 100644
index 0000000..7161685
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/index.js
@@ -0,0 +1,332 @@
+module.exports = Traverse;
+function Traverse (obj) {
+    if (!(this instanceof Traverse)) return new Traverse(obj);
+    this.value = obj;
+}
+
+Traverse.prototype.get = function (ps) {
+    var node = this.value;
+    for (var i = 0; i < ps.length; i ++) {
+        var key = ps[i];
+        if (!Object.hasOwnProperty.call(node, key)) {
+            node = undefined;
+            break;
+        }
+        node = node[key];
+    }
+    return node;
+};
+
+Traverse.prototype.set = function (ps, value) {
+    var node = this.value;
+    for (var i = 0; i < ps.length - 1; i ++) {
+        var key = ps[i];
+        if (!Object.hasOwnProperty.call(node, key)) node[key] = {};
+        node = node[key];
+    }
+    node[ps[i]] = value;
+    return value;
+};
+
+Traverse.prototype.map = function (cb) {
+    return walk(this.value, cb, true);
+};
+
+Traverse.prototype.forEach = function (cb) {
+    this.value = walk(this.value, cb, false);
+    return this.value;
+};
+
+Traverse.prototype.reduce = function (cb, init) {
+    var skip = arguments.length === 1;
+    var acc = skip ? this.value : init;
+    this.forEach(function (x) {
+        if (!this.isRoot || !skip) {
+            acc = cb.call(this, acc, x);
+        }
+    });
+    return acc;
+};
+
+Traverse.prototype.deepEqual = function (obj) {
+    if (arguments.length !== 1) {
+        throw new Error(
+            'deepEqual requires exactly one object to compare against'
+        );
+    }
+    
+    var equal = true;
+    var node = obj;
+    
+    this.forEach(function (y) {
+        var notEqual = (function () {
+            equal = false;
+            //this.stop();
+            return undefined;
+        }).bind(this);
+        
+        //if (node === undefined || node === null) return notEqual();
+        
+        if (!this.isRoot) {
+        /*
+            if (!Object.hasOwnProperty.call(node, this.key)) {
+                return notEqual();
+            }
+        */
+            if (typeof node !== 'object') return notEqual();
+            node = node[this.key];
+        }
+        
+        var x = node;
+        
+        this.post(function () {
+            node = x;
+        });
+        
+        var toS = function (o) {
+            return Object.prototype.toString.call(o);
+        };
+        
+        if (this.circular) {
+            if (Traverse(obj).get(this.circular.path) !== x) notEqual();
+        }
+        else if (typeof x !== typeof y) {
+            notEqual();
+        }
+        else if (x === null || y === null || x === undefined || y === undefined) {
+            if (x !== y) notEqual();
+        }
+        else if (x.__proto__ !== y.__proto__) {
+            notEqual();
+        }
+        else if (x === y) {
+            // nop
+        }
+        else if (typeof x === 'function') {
+            if (x instanceof RegExp) {
+                // both regexps on account of the __proto__ check
+                if (x.toString() != y.toString()) notEqual();
+            }
+            else if (x !== y) notEqual();
+        }
+        else if (typeof x === 'object') {
+            if (toS(y) === '[object Arguments]'
+            || toS(x) === '[object Arguments]') {
+                if (toS(x) !== toS(y)) {
+                    notEqual();
+                }
+            }
+            else if (x instanceof Date || y instanceof Date) {
+                if (!(x instanceof Date) || !(y instanceof Date)
+                || x.getTime() !== y.getTime()) {
+                    notEqual();
+                }
+            }
+            else {
+                var kx = Object.keys(x);
+                var ky = Object.keys(y);
+                if (kx.length !== ky.length) return notEqual();
+                for (var i = 0; i < kx.length; i++) {
+                    var k = kx[i];
+                    if (!Object.hasOwnProperty.call(y, k)) {
+                        notEqual();
+                    }
+                }
+            }
+        }
+    });
+    
+    return equal;
+};
+
+Traverse.prototype.paths = function () {
+    var acc = [];
+    this.forEach(function (x) {
+        acc.push(this.path); 
+    });
+    return acc;
+};
+
+Traverse.prototype.nodes = function () {
+    var acc = [];
+    this.forEach(function (x) {
+        acc.push(this.node);
+    });
+    return acc;
+};
+
+Traverse.prototype.clone = function () {
+    var parents = [], nodes = [];
+    
+    return (function clone (src) {
+        for (var i = 0; i < parents.length; i++) {
+            if (parents[i] === src) {
+                return nodes[i];
+            }
+        }
+        
+        if (typeof src === 'object' && src !== null) {
+            var dst = copy(src);
+            
+            parents.push(src);
+            nodes.push(dst);
+            
+            Object.keys(src).forEach(function (key) {
+                dst[key] = clone(src[key]);
+            });
+            
+            parents.pop();
+            nodes.pop();
+            return dst;
+        }
+        else {
+            return src;
+        }
+    })(this.value);
+};
+
+function walk (root, cb, immutable) {
+    var path = [];
+    var parents = [];
+    var alive = true;
+    
+    return (function walker (node_) {
+        var node = immutable ? copy(node_) : node_;
+        var modifiers = {};
+        
+        var keepGoing = true;
+        
+        var state = {
+            node : node,
+            node_ : node_,
+            path : [].concat(path),
+            parent : parents[parents.length - 1],
+            parents : parents,
+            key : path.slice(-1)[0],
+            isRoot : path.length === 0,
+            level : path.length,
+            circular : null,
+            update : function (x, stopHere) {
+                if (!state.isRoot) {
+                    state.parent.node[state.key] = x;
+                }
+                state.node = x;
+                if (stopHere) keepGoing = false;
+            },
+            'delete' : function () {
+                delete state.parent.node[state.key];
+            },
+            remove : function () {
+                if (Array.isArray(state.parent.node)) {
+                    state.parent.node.splice(state.key, 1);
+                }
+                else {
+                    delete state.parent.node[state.key];
+                }
+            },
+            keys : null,
+            before : function (f) { modifiers.before = f },
+            after : function (f) { modifiers.after = f },
+            pre : function (f) { modifiers.pre = f },
+            post : function (f) { modifiers.post = f },
+            stop : function () { alive = false },
+            block : function () { keepGoing = false }
+        };
+        
+        if (!alive) return state;
+        
+        if (typeof node === 'object' && node !== null) {
+            state.keys = Object.keys(node);
+            
+            state.isLeaf = state.keys.length == 0;
+            
+            for (var i = 0; i < parents.length; i++) {
+                if (parents[i].node_ === node_) {
+                    state.circular = parents[i];
+                    break;
+                }
+            }
+        }
+        else {
+            state.isLeaf = true;
+        }
+        
+        state.notLeaf = !state.isLeaf;
+        state.notRoot = !state.isRoot;
+        
+        // use return values to update if defined
+        var ret = cb.call(state, state.node);
+        if (ret !== undefined && state.update) state.update(ret);
+        
+        if (modifiers.before) modifiers.before.call(state, state.node);
+        
+        if (!keepGoing) return state;
+        
+        if (typeof state.node == 'object'
+        && state.node !== null && !state.circular) {
+            parents.push(state);
+            
+            state.keys.forEach(function (key, i) {
+                path.push(key);
+                
+                if (modifiers.pre) modifiers.pre.call(state, state.node[key], key);
+                
+                var child = walker(state.node[key]);
+                if (immutable && Object.hasOwnProperty.call(state.node, key)) {
+                    state.node[key] = child.node;
+                }
+                
+                child.isLast = i == state.keys.length - 1;
+                child.isFirst = i == 0;
+                
+                if (modifiers.post) modifiers.post.call(state, child);
+                
+                path.pop();
+            });
+            parents.pop();
+        }
+        
+        if (modifiers.after) modifiers.after.call(state, state.node);
+        
+        return state;
+    })(root).node;
+}
+
+Object.keys(Traverse.prototype).forEach(function (key) {
+    Traverse[key] = function (obj) {
+        var args = [].slice.call(arguments, 1);
+        var t = Traverse(obj);
+        return t[key].apply(t, args);
+    };
+});
+
+function copy (src) {
+    if (typeof src === 'object' && src !== null) {
+        var dst;
+        
+        if (Array.isArray(src)) {
+            dst = [];
+        }
+        else if (src instanceof Date) {
+            dst = new Date(src);
+        }
+        else if (src instanceof Boolean) {
+            dst = new Boolean(src);
+        }
+        else if (src instanceof Number) {
+            dst = new Number(src);
+        }
+        else if (src instanceof String) {
+            dst = new String(src);
+        }
+        else {
+            dst = Object.create(Object.getPrototypeOf(src));
+        }
+        
+        Object.keys(src).forEach(function (key) {
+            dst[key] = src[key];
+        });
+        return dst;
+    }
+    else return src;
+}
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/package.json b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/package.json
new file mode 100644
index 0000000..e337200
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/package.json
@@ -0,0 +1,42 @@
+{
+  "name": "traverse",
+  "version": "0.4.6",
+  "description": "Traverse and transform objects by visiting every node on a recursive walk",
+  "author": {
+    "name": "James Halliday"
+  },
+  "license": "MIT/X11",
+  "main": "./index",
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/substack/js-traverse.git"
+  },
+  "devDependencies": {
+    "expresso": "0.7.x"
+  },
+  "scripts": {
+    "test": "expresso"
+  },
+  "_id": "traverse@0.4.6",
+  "dependencies": {},
+  "engines": {
+    "node": "*"
+  },
+  "_engineSupported": true,
+  "_npmVersion": "1.0.10",
+  "_nodeVersion": "v0.5.0-pre",
+  "_defaultsLoaded": true,
+  "dist": {
+    "shasum": "d04b2280e4c792a5815429ef7b8b60c64c9ccc34",
+    "tarball": "http://registry.npmjs.org/traverse/-/traverse-0.4.6.tgz"
+  },
+  "directories": {},
+  "_shasum": "d04b2280e4c792a5815429ef7b8b60c64c9ccc34",
+  "_from": "traverse@0.4.x",
+  "_resolved": "https://registry.npmjs.org/traverse/-/traverse-0.4.6.tgz",
+  "bugs": {
+    "url": "https://github.com/substack/js-traverse/issues"
+  },
+  "readme": "ERROR: No README data found!",
+  "homepage": "https://github.com/substack/js-traverse"
+}
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/circular.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/circular.js
new file mode 100644
index 0000000..e1eef3f
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/circular.js
@@ -0,0 +1,114 @@
+var assert = require('assert');
+var Traverse = require('traverse');
+var util = require('util');
+
+exports.circular = function () {
+    var obj = { x : 3 };
+    obj.y = obj;
+    var foundY = false;
+    Traverse(obj).forEach(function (x) {
+        if (this.path.join('') == 'y') {
+            assert.equal(
+                util.inspect(this.circular.node),
+                util.inspect(obj)
+            );
+            foundY = true;
+        }
+    });
+    assert.ok(foundY);
+};
+
+exports.deepCirc = function () {
+    var obj = { x : [ 1, 2, 3 ], y : [ 4, 5 ] };
+    obj.y[2] = obj;
+    
+    var times = 0;
+    Traverse(obj).forEach(function (x) {
+        if (this.circular) {
+            assert.deepEqual(this.circular.path, []);
+            assert.deepEqual(this.path, [ 'y', 2 ]);
+            times ++;
+        }
+    });
+    
+    assert.deepEqual(times, 1);
+};
+
+exports.doubleCirc = function () {
+    var obj = { x : [ 1, 2, 3 ], y : [ 4, 5 ] };
+    obj.y[2] = obj;
+    obj.x.push(obj.y);
+    
+    var circs = [];
+    Traverse(obj).forEach(function (x) {
+        if (this.circular) {
+            circs.push({ circ : this.circular, self : this, node : x });
+        }
+    });
+    
+    assert.deepEqual(circs[0].self.path, [ 'x', 3, 2 ]);
+    assert.deepEqual(circs[0].circ.path, []);
+     
+    assert.deepEqual(circs[1].self.path, [ 'y', 2 ]);
+    assert.deepEqual(circs[1].circ.path, []);
+    
+    assert.deepEqual(circs.length, 2);
+};
+
+exports.circDubForEach = function () {
+    var obj = { x : [ 1, 2, 3 ], y : [ 4, 5 ] };
+    obj.y[2] = obj;
+    obj.x.push(obj.y);
+    
+    Traverse(obj).forEach(function (x) {
+        if (this.circular) this.update('...');
+    });
+    
+    assert.deepEqual(obj, { x : [ 1, 2, 3, [ 4, 5, '...' ] ], y : [ 4, 5, '...' ] });
+};
+
+exports.circDubMap = function () {
+    var obj = { x : [ 1, 2, 3 ], y : [ 4, 5 ] };
+    obj.y[2] = obj;
+    obj.x.push(obj.y);
+    
+    var c = Traverse(obj).map(function (x) {
+        if (this.circular) {
+            this.update('...');
+        }
+    });
+    
+    assert.deepEqual(c, { x : [ 1, 2, 3, [ 4, 5, '...' ] ], y : [ 4, 5, '...' ] });
+};
+
+exports.circClone = function () {
+    var obj = { x : [ 1, 2, 3 ], y : [ 4, 5 ] };
+    obj.y[2] = obj;
+    obj.x.push(obj.y);
+    
+    var clone = Traverse.clone(obj);
+    assert.ok(obj !== clone);
+    
+    assert.ok(clone.y[2] === clone);
+    assert.ok(clone.y[2] !== obj);
+    assert.ok(clone.x[3][2] === clone);
+    assert.ok(clone.x[3][2] !== obj);
+    assert.deepEqual(clone.x.slice(0,3), [1,2,3]);
+    assert.deepEqual(clone.y.slice(0,2), [4,5]);
+};
+
+exports.circMapScrub = function () {
+    var obj = { a : 1, b : 2 };
+    obj.c = obj;
+    
+    var scrubbed = Traverse(obj).map(function (node) {
+        if (this.circular) this.remove();
+    });
+    assert.deepEqual(
+        Object.keys(scrubbed).sort(),
+        [ 'a', 'b' ]
+    );
+    assert.ok(Traverse.deepEqual(scrubbed, { a : 1, b : 2 }));
+    
+    assert.equal(obj.c, obj);
+};
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/date.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/date.js
new file mode 100644
index 0000000..2cb8252
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/date.js
@@ -0,0 +1,35 @@
+var assert = require('assert');
+var Traverse = require('traverse');
+
+exports.dateEach = function () {
+    var obj = { x : new Date, y : 10, z : 5 };
+    
+    var counts = {};
+    
+    Traverse(obj).forEach(function (node) {
+        var t = (node instanceof Date && 'Date') || typeof node;
+        counts[t] = (counts[t] || 0) + 1;
+    });
+    
+    assert.deepEqual(counts, {
+        object : 1,
+        Date : 1,
+        number : 2,
+    });
+};
+
+exports.dateMap = function () {
+    var obj = { x : new Date, y : 10, z : 5 };
+    
+    var res = Traverse(obj).map(function (node) {
+        if (typeof node === 'number') this.update(node + 100);
+    });
+    
+    assert.ok(obj.x !== res.x);
+    assert.deepEqual(res, {
+        x : obj.x,
+        y : 110,
+        z : 105,
+    });
+};
+
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/equal.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/equal.js
new file mode 100644
index 0000000..4d732fa
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/equal.js
@@ -0,0 +1,219 @@
+var assert = require('assert');
+var traverse = require('traverse');
+
+exports.deepDates = function () {
+    assert.ok(
+        traverse.deepEqual(
+            { d : new Date, x : [ 1, 2, 3 ] },
+            { d : new Date, x : [ 1, 2, 3 ] }
+        ),
+        'dates should be equal'
+    );
+    
+    var d0 = new Date;
+    setTimeout(function () {
+        assert.ok(
+            !traverse.deepEqual(
+                { d : d0, x : [ 1, 2, 3 ], },
+                { d : new Date, x : [ 1, 2, 3 ] }
+            ),
+            'microseconds should count in date equality'
+        );
+    }, 5);
+};
+
+exports.deepCircular = function () {
+    var a = [1];
+    a.push(a); // a = [ 1, *a ]
+    
+    var b = [1];
+    b.push(a); // b = [ 1, [ 1, *a ] ]
+    
+    assert.ok(
+        !traverse.deepEqual(a, b),
+        'circular ref mount points count towards equality'
+    );
+    
+    var c = [1];
+    c.push(c); // c = [ 1, *c ]
+    assert.ok(
+        traverse.deepEqual(a, c),
+        'circular refs are structurally the same here'
+    );
+    
+    var d = [1];
+    d.push(a); // c = [ 1, [ 1, *d ] ]
+    assert.ok(
+        traverse.deepEqual(b, d),
+        'non-root circular ref structural comparison'
+    );
+};
+
+exports.deepInstances = function () {
+    assert.ok(
+        !traverse.deepEqual([ new Boolean(false) ], [ false ]),
+        'boolean instances are not real booleans'
+    );
+    
+    assert.ok(
+        !traverse.deepEqual([ new String('x') ], [ 'x' ]),
+        'string instances are not real strings'
+    );
+    
+    assert.ok(
+        !traverse.deepEqual([ new Number(4) ], [ 4 ]),
+        'number instances are not real numbers'
+    );
+    
+    assert.ok(
+        traverse.deepEqual([ new RegExp('x') ], [ /x/ ]),
+        'regexp instances are real regexps'
+    );
+    
+    assert.ok(
+        !traverse.deepEqual([ new RegExp(/./) ], [ /../ ]),
+        'these regexps aren\'t the same'
+    );
+    
+    assert.ok(
+        !traverse.deepEqual(
+            [ function (x) { return x * 2 } ],
+            [ function (x) { return x * 2 } ]
+        ),
+        'functions with the same .toString() aren\'t necessarily the same'
+    );
+    
+    var f = function (x) { return x * 2 };
+    assert.ok(
+        traverse.deepEqual([ f ], [ f ]),
+        'these functions are actually equal'
+    );
+};
+
+exports.deepEqual = function () {
+    assert.ok(
+        !traverse.deepEqual([ 1, 2, 3 ], { 0 : 1, 1 : 2, 2 : 3 }),
+        'arrays are not objects'
+    );
+};
+
+exports.falsy = function () {
+    assert.ok(
+        !traverse.deepEqual([ undefined ], [ null ]),
+        'null is not undefined!'
+    );
+    
+    assert.ok(
+        !traverse.deepEqual([ null ], [ undefined ]),
+        'undefined is not null!'
+    );
+    
+    assert.ok(
+        !traverse.deepEqual(
+            { a : 1, b : 2, c : [ 3, undefined, 5 ] },
+            { a : 1, b : 2, c : [ 3, null, 5 ] }
+        ),
+        'undefined is not null, however deeply!'
+    );
+    
+    assert.ok(
+        !traverse.deepEqual(
+            { a : 1, b : 2, c : [ 3, undefined, 5 ] },
+            { a : 1, b : 2, c : [ 3, null, 5 ] }
+        ),
+        'null is not undefined, however deeply!'
+    );
+    
+    assert.ok(
+        !traverse.deepEqual(
+            { a : 1, b : 2, c : [ 3, undefined, 5 ] },
+            { a : 1, b : 2, c : [ 3, null, 5 ] }
+        ),
+        'null is not undefined, however deeply!'
+    );
+};
+
+exports.deletedArrayEqual = function () {
+    var xs = [ 1, 2, 3, 4 ];
+    delete xs[2];
+    
+    var ys = Object.create(Array.prototype);
+    ys[0] = 1;
+    ys[1] = 2;
+    ys[3] = 4;
+    
+    assert.ok(
+        traverse.deepEqual(xs, ys),
+        'arrays with deleted elements are only equal to'
+        + ' arrays with similarly deleted elements'
+    );
+    
+    assert.ok(
+        !traverse.deepEqual(xs, [ 1, 2, undefined, 4 ]),
+        'deleted array elements cannot be undefined'
+    );
+    
+    assert.ok(
+        !traverse.deepEqual(xs, [ 1, 2, null, 4 ]),
+        'deleted array elements cannot be null'
+    );
+};
+
+exports.deletedObjectEqual = function () {
+    var obj = { a : 1, b : 2, c : 3 };
+    delete obj.c;
+    
+    assert.ok(
+        traverse.deepEqual(obj, { a : 1, b : 2 }),
+        'deleted object elements should not show up'
+    );
+    
+    assert.ok(
+        !traverse.deepEqual(obj, { a : 1, b : 2, c : undefined }),
+        'deleted object elements are not undefined'
+    );
+    
+    assert.ok(
+        !traverse.deepEqual(obj, { a : 1, b : 2, c : null }),
+        'deleted object elements are not null'
+    );
+};
+
+exports.emptyKeyEqual = function () {
+    assert.ok(!traverse.deepEqual(
+        { a : 1 }, { a : 1, '' : 55 }
+    ));
+};
+
+exports.deepArguments = function () {
+    assert.ok(
+        !traverse.deepEqual(
+            [ 4, 5, 6 ],
+            (function () { return arguments })(4, 5, 6)
+        ),
+        'arguments are not arrays'
+    );
+    
+    assert.ok(
+        traverse.deepEqual(
+            (function () { return arguments })(4, 5, 6),
+            (function () { return arguments })(4, 5, 6)
+        ),
+        'arguments should equal'
+    );
+};
+
+exports.deepUn = function () {
+    assert.ok(!traverse.deepEqual({ a : 1, b : 2 }, undefined));
+    assert.ok(!traverse.deepEqual({ a : 1, b : 2 }, {}));
+    assert.ok(!traverse.deepEqual(undefined, { a : 1, b : 2 }));
+    assert.ok(!traverse.deepEqual({}, { a : 1, b : 2 }));
+    assert.ok(traverse.deepEqual(undefined, undefined));
+    assert.ok(traverse.deepEqual(null, null));
+    assert.ok(!traverse.deepEqual(undefined, null));
+};
+
+exports.deepLevels = function () {
+    var xs = [ 1, 2, [ 3, 4, [ 5, 6 ] ] ];
+    assert.ok(!traverse.deepEqual(xs, []));
+};
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/instance.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/instance.js
new file mode 100644
index 0000000..501981f
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/instance.js
@@ -0,0 +1,17 @@
+var assert = require('assert');
+var Traverse = require('traverse');
+var EventEmitter = require('events').EventEmitter;
+
+exports['check instanceof on node elems'] = function () {
+    
+    var counts = { emitter : 0 };
+    
+    Traverse([ new EventEmitter, 3, 4, { ev : new EventEmitter }])
+        .forEach(function (node) {
+            if (node instanceof EventEmitter) counts.emitter ++;
+        })
+    ;
+    
+    assert.equal(counts.emitter, 2);
+};
+
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/interface.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/interface.js
new file mode 100644
index 0000000..df5b037
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/interface.js
@@ -0,0 +1,42 @@
+var assert = require('assert');
+var Traverse = require('traverse');
+
+exports['interface map'] = function () {
+    var obj = { a : [ 5,6,7 ], b : { c : [8] } };
+    
+    assert.deepEqual(
+        Traverse.paths(obj)
+            .sort()
+            .map(function (path) { return path.join('/') })
+            .slice(1)
+            .join(' ')
+         ,
+         'a a/0 a/1 a/2 b b/c b/c/0'
+    );
+    
+    assert.deepEqual(
+        Traverse.nodes(obj),
+        [
+            { a: [ 5, 6, 7 ], b: { c: [ 8 ] } },
+            [ 5, 6, 7 ], 5, 6, 7,
+            { c: [ 8 ] }, [ 8 ], 8
+        ]
+    );
+    
+    assert.deepEqual(
+        Traverse.map(obj, function (node) {
+            if (typeof node == 'number') {
+                return node + 1000;
+            }
+            else if (Array.isArray(node)) {
+                return node.join(' ');
+            }
+        }),
+        { a: '5 6 7', b: { c: '8' } }
+    );
+    
+    var nodes = 0;
+    Traverse.forEach(obj, function (node) { nodes ++ });
+    assert.deepEqual(nodes, 8);
+};
+
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/json.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/json.js
new file mode 100644
index 0000000..bf36620
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/json.js
@@ -0,0 +1,47 @@
+var assert = require('assert');
+var Traverse = require('traverse');
+
+exports['json test'] = function () {
+    var id = 54;
+    var callbacks = {};
+    var obj = { moo : function () {}, foo : [2,3,4, function () {}] };
+    
+    var scrubbed = Traverse(obj).map(function (x) {
+        if (typeof x === 'function') {
+            callbacks[id] = { id : id, f : x, path : this.path };
+            this.update('[Function]');
+            id++;
+        }
+    });
+    
+    assert.equal(
+        scrubbed.moo, '[Function]',
+        'obj.moo replaced with "[Function]"'
+    );
+    
+    assert.equal(
+        scrubbed.foo[3], '[Function]',
+        'obj.foo[3] replaced with "[Function]"'
+    );
+    
+    assert.deepEqual(scrubbed, {
+        moo : '[Function]',
+        foo : [ 2, 3, 4, "[Function]" ]
+    }, 'Full JSON string matches');
+    
+    assert.deepEqual(
+        typeof obj.moo, 'function',
+        'Original obj.moo still a function'
+    );
+    
+    assert.deepEqual(
+        typeof obj.foo[3], 'function',
+        'Original obj.foo[3] still a function'
+    );
+    
+    assert.deepEqual(callbacks, {
+        54: { id: 54, f : obj.moo, path: [ 'moo' ] },
+        55: { id: 55, f : obj.foo[3], path: [ 'foo', '3' ] },
+    }, 'Check the generated callbacks list');
+};
+
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/keys.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/keys.js
new file mode 100644
index 0000000..8bf88ed
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/keys.js
@@ -0,0 +1,29 @@
+var assert = require('assert');
+var Traverse = require('traverse');
+
+exports['sort test'] = function () {
+    var acc = [];
+    Traverse({
+        a: 30,
+        b: 22,
+        id: 9
+    }).forEach(function (node) {
+        if ((! Array.isArray(node)) && typeof node === 'object') {
+            this.before(function(node) {
+                this.keys = Object.keys(node);
+                this.keys.sort(function(a, b) {
+                    a = [a === "id" ? 0 : 1, a];
+                    b = [b === "id" ? 0 : 1, b];
+                    return a < b ? -1 : a > b ? 1 : 0;
+                });
+            });
+        }
+        if (this.isLeaf) acc.push(node);
+    });
+    
+    assert.equal(
+        acc.join(' '),
+        '9 30 22',
+        'Traversal in a custom order'
+    );
+};
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/leaves.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/leaves.js
new file mode 100644
index 0000000..4e8d280
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/leaves.js
@@ -0,0 +1,21 @@
+var assert = require('assert');
+var Traverse = require('traverse');
+
+exports['leaves test'] = function () {
+    var acc = [];
+    Traverse({
+        a : [1,2,3],
+        b : 4,
+        c : [5,6],
+        d : { e : [7,8], f : 9 }
+    }).forEach(function (x) {
+        if (this.isLeaf) acc.push(x);
+    });
+    
+    assert.equal(
+        acc.join(' '),
+        '1 2 3 4 5 6 7 8 9',
+        'Traversal in the right(?) order'
+    );
+};
+
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/mutability.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/mutability.js
new file mode 100644
index 0000000..5a4d6dd
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/mutability.js
@@ -0,0 +1,203 @@
+var assert = require('assert');
+var Traverse = require('traverse');
+
+exports.mutate = function () {
+    var obj = { a : 1, b : 2, c : [ 3, 4 ] };
+    var res = Traverse(obj).forEach(function (x) {
+        if (typeof x === 'number' && x % 2 === 0) {
+            this.update(x * 10);
+        }
+    });
+    assert.deepEqual(obj, res);
+    assert.deepEqual(obj, { a : 1, b : 20, c : [ 3, 40 ] });
+};
+
+exports.mutateT = function () {
+    var obj = { a : 1, b : 2, c : [ 3, 4 ] };
+    var res = Traverse.forEach(obj, function (x) {
+        if (typeof x === 'number' && x % 2 === 0) {
+            this.update(x * 10);
+        }
+    });
+    assert.deepEqual(obj, res);
+    assert.deepEqual(obj, { a : 1, b : 20, c : [ 3, 40 ] });
+};
+
+exports.map = function () {
+    var obj = { a : 1, b : 2, c : [ 3, 4 ] };
+    var res = Traverse(obj).map(function (x) {
+        if (typeof x === 'number' && x % 2 === 0) {
+            this.update(x * 10);
+        }
+    });
+    assert.deepEqual(obj, { a : 1, b : 2, c : [ 3, 4 ] });
+    assert.deepEqual(res, { a : 1, b : 20, c : [ 3, 40 ] });
+};
+
+exports.mapT = function () {
+    var obj = { a : 1, b : 2, c : [ 3, 4 ] };
+    var res = Traverse.map(obj, function (x) {
+        if (typeof x === 'number' && x % 2 === 0) {
+            this.update(x * 10);
+        }
+    });
+    assert.deepEqual(obj, { a : 1, b : 2, c : [ 3, 4 ] });
+    assert.deepEqual(res, { a : 1, b : 20, c : [ 3, 40 ] });
+};
+
+exports.clone = function () {
+    var obj = { a : 1, b : 2, c : [ 3, 4 ] };
+    var res = Traverse(obj).clone();
+    assert.deepEqual(obj, res);
+    assert.ok(obj !== res);
+    obj.a ++;
+    assert.deepEqual(res.a, 1);
+    obj.c.push(5);
+    assert.deepEqual(res.c, [ 3, 4 ]);
+};
+
+exports.cloneT = function () {
+    var obj = { a : 1, b : 2, c : [ 3, 4 ] };
+    var res = Traverse.clone(obj);
+    assert.deepEqual(obj, res);
+    assert.ok(obj !== res);
+    obj.a ++;
+    assert.deepEqual(res.a, 1);
+    obj.c.push(5);
+    assert.deepEqual(res.c, [ 3, 4 ]);
+};
+
+exports.reduce = function () {
+    var obj = { a : 1, b : 2, c : [ 3, 4 ] };
+    var res = Traverse(obj).reduce(function (acc, x) {
+        if (this.isLeaf) acc.push(x);
+        return acc;
+    }, []);
+    assert.deepEqual(obj, { a : 1, b : 2, c : [ 3, 4 ] });
+    assert.deepEqual(res, [ 1, 2, 3, 4 ]);
+};
+
+exports.reduceInit = function () {
+    var obj = { a : 1, b : 2, c : [ 3, 4 ] };
+    var res = Traverse(obj).reduce(function (acc, x) {
+        if (this.isRoot) assert.fail('got root');
+        return acc;
+    });
+    assert.deepEqual(obj, { a : 1, b : 2, c : [ 3, 4 ] });
+    assert.deepEqual(res, obj);
+};
+
+exports.remove = function () {
+    var obj = { a : 1, b : 2, c : [ 3, 4 ] };
+    Traverse(obj).forEach(function (x) {
+        if (this.isLeaf && x % 2 == 0) this.remove();
+    });
+    
+    assert.deepEqual(obj, { a : 1, c : [ 3 ] });
+};
+
+exports.removeMap = function () {
+    var obj = { a : 1, b : 2, c : [ 3, 4 ] };
+    var res = Traverse(obj).map(function (x) {
+        if (this.isLeaf && x % 2 == 0) this.remove();
+    });
+    
+    assert.deepEqual(obj, { a : 1, b : 2, c : [ 3, 4 ] });
+    assert.deepEqual(res, { a : 1, c : [ 3 ] });
+};
+
+exports.delete = function () {
+    var obj = { a : 1, b : 2, c : [ 3, 4 ] };
+    Traverse(obj).forEach(function (x) {
+        if (this.isLeaf && x % 2 == 0) this.delete();
+    });
+    
+    assert.ok(!Traverse.deepEqual(
+        obj, { a : 1, c : [ 3, undefined ] }
+    ));
+    
+    assert.ok(Traverse.deepEqual(
+        obj, { a : 1, c : [ 3 ] }
+    ));
+    
+    assert.ok(!Traverse.deepEqual(
+        obj, { a : 1, c : [ 3, null ] }
+    ));
+};
+
+exports.deleteRedux = function () {
+    var obj = { a : 1, b : 2, c : [ 3, 4, 5 ] };
+    Traverse(obj).forEach(function (x) {
+        if (this.isLeaf && x % 2 == 0) this.delete();
+    });
+    
+    assert.ok(!Traverse.deepEqual(
+        obj, { a : 1, c : [ 3, undefined, 5 ] }
+    ));
+    
+    assert.ok(Traverse.deepEqual(
+        obj, { a : 1, c : [ 3 ,, 5 ] }
+    ));
+    
+    assert.ok(!Traverse.deepEqual(
+        obj, { a : 1, c : [ 3, null, 5 ] }
+    ));
+    
+    assert.ok(!Traverse.deepEqual(
+        obj, { a : 1, c : [ 3, 5 ] }
+    ));
+};
+
+exports.deleteMap = function () {
+    var obj = { a : 1, b : 2, c : [ 3, 4 ] };
+    var res = Traverse(obj).map(function (x) {
+        if (this.isLeaf && x % 2 == 0) this.delete();
+    });
+    
+    assert.ok(Traverse.deepEqual(
+        obj,
+        { a : 1, b : 2, c : [ 3, 4 ] }
+    ));
+    
+    var xs = [ 3, 4 ];
+    delete xs[1];
+    
+    assert.ok(Traverse.deepEqual(
+        res, { a : 1, c : xs }
+    ));
+    
+    assert.ok(Traverse.deepEqual(
+        res, { a : 1, c : [ 3, ] }
+    ));
+    
+    assert.ok(Traverse.deepEqual(
+        res, { a : 1, c : [ 3 ] }
+    ));
+};
+
+exports.deleteMapRedux = function () {
+    var obj = { a : 1, b : 2, c : [ 3, 4, 5 ] };
+    var res = Traverse(obj).map(function (x) {
+        if (this.isLeaf && x % 2 == 0) this.delete();
+    });
+    
+    assert.ok(Traverse.deepEqual(
+        obj,
+        { a : 1, b : 2, c : [ 3, 4, 5 ] }
+    ));
+    
+    var xs = [ 3, 4, 5 ];
+    delete xs[1];
+    
+    assert.ok(Traverse.deepEqual(
+        res, { a : 1, c : xs }
+    ));
+    
+    assert.ok(!Traverse.deepEqual(
+        res, { a : 1, c : [ 3, 5 ] }
+    ));
+    
+    assert.ok(Traverse.deepEqual(
+        res, { a : 1, c : [ 3 ,, 5 ] }
+    ));
+};
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/negative.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/negative.js
new file mode 100644
index 0000000..6cf287d
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/negative.js
@@ -0,0 +1,20 @@
+var Traverse = require('traverse');
+var assert = require('assert');
+
+exports['negative update test'] = function () {
+    var obj = [ 5, 6, -3, [ 7, 8, -2, 1 ], { f : 10, g : -13 } ];
+    var fixed = Traverse.map(obj, function (x) {
+        if (x < 0) this.update(x + 128);
+    });
+    
+    assert.deepEqual(fixed,
+        [ 5, 6, 125, [ 7, 8, 126, 1 ], { f: 10, g: 115 } ],
+        'Negative values += 128'
+    );
+    
+    assert.deepEqual(obj,
+        [ 5, 6, -3, [ 7, 8, -2, 1 ], { f: 10, g: -13 } ],
+        'Original references not modified'
+    );
+}
+
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/obj.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/obj.js
new file mode 100644
index 0000000..9c3b0db
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/obj.js
@@ -0,0 +1,15 @@
+var assert = require('assert');
+var Traverse = require('traverse');
+
+exports['traverse an object with nested functions'] = function () {
+    var to = setTimeout(function () {
+        assert.fail('never ran');
+    }, 1000);
+    
+    function Cons (x) {
+        clearTimeout(to);
+        assert.equal(x, 10);
+    };
+    Traverse(new Cons(10));
+};
+
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/siblings.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/siblings.js
new file mode 100644
index 0000000..1236834
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/siblings.js
@@ -0,0 +1,35 @@
+var assert = require('assert');
+var traverse = require('traverse');
+
+exports.siblings = function () {
+    var obj = { a : 1, b : 2, c : [ 4, 5, 6 ] };
+    
+    var res = traverse(obj).reduce(function (acc, x) {
+        var p = '/' + this.path.join('/');
+        if (this.parent) {
+            acc[p] = {
+                siblings : this.parent.keys,
+                key : this.key,
+                index : this.parent.keys.indexOf(this.key)
+            };
+        }
+        else {
+            acc[p] = {
+                siblings : [],
+                key : this.key,
+                index : -1
+            }
+        }
+        return acc;
+    }, {});
+    
+    assert.deepEqual(res, {
+        '/' : { siblings : [], key : undefined, index : -1 },
+        '/a' : { siblings : [ 'a', 'b', 'c' ], key : 'a', index : 0 },
+        '/b' : { siblings : [ 'a', 'b', 'c' ], key : 'b', index : 1 },
+        '/c' : { siblings : [ 'a', 'b', 'c' ], key : 'c', index : 2 },
+        '/c/0' : { siblings : [ '0', '1', '2' ], key : '0', index : 0 },
+        '/c/1' : { siblings : [ '0', '1', '2' ], key : '1', index : 1 },
+        '/c/2' : { siblings : [ '0', '1', '2' ], key : '2', index : 2 }
+    });
+};
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/stop.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/stop.js
new file mode 100644
index 0000000..ef6b36e
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/stop.js
@@ -0,0 +1,41 @@
+var assert = require('assert');
+var traverse = require('traverse');
+
+exports.stop = function () {
+    var visits = 0;
+    traverse('abcdefghij'.split('')).forEach(function (node) {
+        if (typeof node === 'string') {
+            visits ++;
+            if (node === 'e') this.stop()
+        }
+    });
+    
+    assert.equal(visits, 5);
+};
+
+exports.stopMap = function () {
+    var s = traverse('abcdefghij'.split('')).map(function (node) {
+        if (typeof node === 'string') {
+            if (node === 'e') this.stop()
+            return node.toUpperCase();
+        }
+    }).join('');
+    
+    assert.equal(s, 'ABCDEfghij');
+};
+
+exports.stopReduce = function () {
+    var obj = {
+        a : [ 4, 5 ],
+        b : [ 6, [ 7, 8, 9 ] ]
+    };
+    var xs = traverse(obj).reduce(function (acc, node) {
+        if (this.isLeaf) {
+            if (node === 7) this.stop();
+            else acc.push(node)
+        }
+        return acc;
+    }, []);
+    
+    assert.deepEqual(xs, [ 4, 5, 6 ]);
+};
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/stringify.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/stringify.js
new file mode 100644
index 0000000..bf36f63
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/stringify.js
@@ -0,0 +1,36 @@
+var assert = require('assert');
+var Traverse = require('traverse');
+
+exports.stringify = function () {
+    var obj = [ 5, 6, -3, [ 7, 8, -2, 1 ], { f : 10, g : -13 } ];
+    
+    var s = '';
+    Traverse(obj).forEach(function (node) {
+        if (Array.isArray(node)) {
+            this.before(function () { s += '[' });
+            this.post(function (child) {
+                if (!child.isLast) s += ',';
+            });
+            this.after(function () { s += ']' });
+        }
+        else if (typeof node == 'object') {
+            this.before(function () { s += '{' });
+            this.pre(function (x, key) {
+                s += '"' + key + '"' + ':';
+            });
+            this.post(function (child) {
+                if (!child.isLast) s += ',';
+            });
+            this.after(function () { s += '}' });
+        }
+        else if (typeof node == 'function') {
+            s += 'null';
+        }
+        else {
+            s += node.toString();
+        }
+    });
+    
+    assert.equal(s, JSON.stringify(obj));
+}
+
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/subexpr.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/subexpr.js
new file mode 100644
index 0000000..a4960fb
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/subexpr.js
@@ -0,0 +1,34 @@
+var traverse = require('traverse');
+var assert = require('assert');
+
+exports.subexpr = function () {
+    var obj = [ 'a', 4, 'b', 5, 'c', 6 ];
+    var r = traverse(obj).map(function (x) {
+        if (typeof x === 'number') {
+            this.update([ x - 0.1, x, x + 0.1 ], true);
+        }
+    });
+    
+    assert.deepEqual(obj, [ 'a', 4, 'b', 5, 'c', 6 ]);
+    assert.deepEqual(r, [
+        'a', [ 3.9, 4, 4.1 ],
+        'b', [ 4.9, 5, 5.1 ],
+        'c', [ 5.9, 6, 6.1 ],
+    ]);
+};
+
+exports.block = function () {
+    var obj = [ [ 1 ], [ 2 ], [ 3 ] ];
+    var r = traverse(obj).map(function (x) {
+        if (Array.isArray(x) && !this.isRoot) {
+            if (x[0] === 5) this.block()
+            else this.update([ [ x[0] + 1 ] ])
+        }
+    });
+    
+    assert.deepEqual(r, [
+        [ [ [ [ [ 5 ] ] ] ] ],
+        [ [ [ [ 5 ] ] ] ],
+        [ [ [ 5 ] ] ],
+    ]);
+};
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/super_deep.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/super_deep.js
new file mode 100644
index 0000000..974181e
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/super_deep.js
@@ -0,0 +1,54 @@
+var assert = require('assert');
+var traverse = require('traverse');
+
+exports.super_deep = function () {
+    var util = require('util');
+    var a0 = make();
+    var a1 = make();
+    assert.ok(traverse.deepEqual(a0, a1));
+    
+    a0.c.d.moo = true;
+    assert.ok(!traverse.deepEqual(a0, a1));
+    
+    a1.c.d.moo = true;
+    assert.ok(traverse.deepEqual(a0, a1));
+    
+    // TODO: this one
+    //a0.c.a = a1;
+    //assert.ok(!traverse.deepEqual(a0, a1));
+};
+
+function make () {
+    var a = { self : 'a' };
+    var b = { self : 'b' };
+    var c = { self : 'c' };
+    var d = { self : 'd' };
+    var e = { self : 'e' };
+    
+    a.a = a;
+    a.b = b;
+    a.c = c;
+    
+    b.a = a;
+    b.b = b;
+    b.c = c;
+    
+    c.a = a;
+    c.b = b;
+    c.c = c;
+    c.d = d;
+    
+    d.a = a;
+    d.b = b;
+    d.c = c;
+    d.d = d;
+    d.e = e;
+    
+    e.a = a;
+    e.b = b;
+    e.c = c;
+    e.d = d;
+    e.e = e;
+    
+    return a;
+}
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/package.json b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/package.json
new file mode 100644
index 0000000..074b9db
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/package.json
@@ -0,0 +1,51 @@
+{
+  "name": "js-select",
+  "description": "Traverse and modify objects with JSONSelect selectors",
+  "version": "0.6.0",
+  "author": {
+    "name": "Heather Arthur",
+    "email": "fayearthur@gmail.com"
+  },
+  "repository": {
+    "type": "git",
+    "url": "http://github.com/harthur/js-select.git"
+  },
+  "main": "./index",
+  "dependencies": {
+    "traverse": "0.4.x",
+    "JSONSelect": "0.2.1"
+  },
+  "devDependencies": {
+    "nomnom": "0.6.x",
+    "color": "0.3.x"
+  },
+  "keywords": [
+    "json"
+  ],
+  "readme": "# js-select\n\njs-select uses [js-traverse](https://github.com/substack/js-traverse) to traverse and modify JavaScript object nodes that match [JSONSelect](http://jsonselect.org/) selectors.\n\n```javascript\nvar people = {\n   george: {\n      age : 35,\n      movie: \"Repo Man\"\n   },\n   mary: {\n      age: 15,\n      movie: \"Twilight\"\n   }\n};\n```\n\n### .forEach(fn)\n\nIterates over all matching nodes in the object. The callback gets a special `this` context. See [js-traverse](https://github.com/substack/js-traverse) for all the things you can do to modify and inspect the node with this context. In addition, js-select adds a `this.matches()` which will test if the node matches a selector:\n\n```javascript\nselect(people).forEach(function(node) {\n   if (this.matches(\".mary > .movie\")) {\n      this.remove();\n   }\n});\n```\n\n### .nodes()\n\nReturns all matching nodes from the object.\n\n```javascript\nselect(people, \".age\").nodes(); // [35, 15]\n```\n\n### .remove()\n\nRemoves matching elements from the original object.\n\n```javascript\nselect(people, \".age\").remove();\n```\n\n### .update(fn)\n\nUpdates all matching nodes using the given callback.\n\n```javascript\nselect(people, \".age\").update(function(age) {\n   return age - 5;\n});\n```\n\n### .condense()\n\nReduces the original object down to only the matching elements (the hierarchy is maintained).\n\n```javascript\nselect(people, \".age\").condense();\n```\n\n```javascript\n{\n    george: { age: 35 },\n    mary: { age: 15 }\n}\n```\n\n## Selectors\n\njs-select supports the following [JSONSelect](http://jsonselect.org/) selectors:\n\n```\n*\ntype\n.key\nancestor selector\nparent > selector\nsibling ~ selector\nselector1, selector2\n:root\n:nth-child(n)\n:nth-child(even)\n:nth-child(odd)\n:nth-last-child(n)\n:first-child\n:last-child\n:only-child\n:has(selector)\n:val(\"string\")\n:contains(\"substring\")\n```\n\nSee [details](http://jsonselect.org/#docs/overview) on each selector, and [try them](http://jsonselect.org/#tryit) out on the JSONSelect website.\n\n## Install\n\nFor [node](http://nodejs.org), install with [npm](http://npmjs.org):\n\n```bash\nnpm install js-select\n```\n\nFor the browser, download the select.js file or fetch the latest version from [npm](http://npmjs.org) and build a browser file using [browserify](https://github.com/substack/node-browserify):\n\n```bash\nnpm install browserify -g\nnpm install js-select\n\nbrowserify --require js-select --outfile select.js\n```\nthis will build a browser file with `require('js-select')` available.\n\n## Propers\n\nHuge thanks to [@substack](http://github.com/substack) for the ingenious [js-traverse](https://github.com/substack/js-traverse) and [@lloyd](https://github.com/lloyd) for the ingenious [JSONSelect spec](http://http://jsonselect.org/) and [selector parser](http://search.npmjs.org/#/JSONSelect).",
+  "readmeFilename": "README.md",
+  "_id": "js-select@0.6.0",
+  "dist": {
+    "shasum": "c284e22824d5927aec962dcdf247174aefb0d190",
+    "tarball": "http://registry.npmjs.org/js-select/-/js-select-0.6.0.tgz"
+  },
+  "_from": "js-select@~0.6.0",
+  "_npmVersion": "1.2.14",
+  "_npmUser": {
+    "name": "harth",
+    "email": "fayearthur@gmail.com"
+  },
+  "maintainers": [
+    {
+      "name": "harth",
+      "email": "fayearthur@gmail.com"
+    }
+  ],
+  "directories": {},
+  "_shasum": "c284e22824d5927aec962dcdf247174aefb0d190",
+  "_resolved": "https://registry.npmjs.org/js-select/-/js-select-0.6.0.tgz",
+  "bugs": {
+    "url": "https://github.com/harthur/js-select/issues"
+  },
+  "homepage": "https://github.com/harthur/js-select"
+}
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/select-min.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/select-min.js
new file mode 100644
index 0000000..466ccf7
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/select-min.js
@@ -0,0 +1,37 @@
+var select=function(){var c=function(j,f){var d=c.resolve(j,f||"/"),l=c.modules[d];if(!l)throw Error("Failed to resolve module "+j+", tried "+d);return(d=c.cache[d])?d.exports:l()};c.paths=[];c.modules={};c.cache={};c.extensions=[".js",".coffee"];c._core={assert:!0,events:!0,fs:!0,path:!0,vm:!0};c.resolve=function(j,f){function d(b){b=h.normalize(b);if(c.modules[b])return b;for(var a=0;a<c.extensions.length;a++){var e=c.extensions[a];if(c.modules[b+e])return b+e}}function l(b){var b=b.replace(/\/+$/,
+""),a=h.normalize(b+"/package.json");if(c.modules[a]){var a=c.modules[a](),e=a.browserify;if("object"===typeof e&&e.main){if(a=d(h.resolve(b,e.main)))return a}else if("string"===typeof e){if(a=d(h.resolve(b,e)))return a}else if(a.main&&(a=d(h.resolve(b,a.main))))return a}return d(b+"/index")}f||(f="/");if(c._core[j])return j;var h=c.modules.path(),a=(f=h.resolve("/",f))||"/";if(j.match(/^(?:\.\.?\/|\/)/)){var e=d(h.resolve(a,j))||l(h.resolve(a,j));if(e)return e}a:{for(var e="/"===a?[""]:h.normalize(a).split("/"),
+a=[],g=e.length-1;0<=g;g--)if("node_modules"!==e[g]){var b=e.slice(0,g+1).join("/")+"/node_modules";a.push(b)}for(e=0;e<a.length;e++){g=a[e];if(b=d(g+"/"+j)){a=b;break a}if(g=l(g+"/"+j)){a=g;break a}}a=(b=d(j))?b:void 0}if(a)return a;throw Error("Cannot find module '"+j+"'");};c.alias=function(j,f){var d=c.modules.path(),l=null;try{l=c.resolve(j+"/package.json","/")}catch(h){l=c.resolve(j,"/")}for(var d=d.dirname(l),l=(Object.keys||function(a){var b=[],e;for(e in a)b.push(e);return b})(c.modules),
+a=0;a<l.length;a++){var e=l[a];e.slice(0,d.length+1)===d+"/"?(e=e.slice(d.length),c.modules[f+e]=c.modules[d+e]):e===d&&(c.modules[f]=c.modules[d])}};var u={};c.define=function(j,f){c.modules.__browserify_process&&(u=c.modules.__browserify_process());var d=c._core[j]?"":c.modules.path().dirname(j),l=function(a){var e=c(a,d);if((a=c.cache[c.resolve(a,d)])&&null===a.parent)a.parent=h;return e};l.resolve=function(a){return c.resolve(a,d)};l.modules=c.modules;l.define=c.define;l.cache=c.cache;var h={id:j,
+filename:j,exports:{},loaded:!1,parent:null};c.modules[j]=function(){c.cache[j]=h;f.call(h.exports,l,h,h.exports,d,j,u);h.loaded=!0;return h.exports}};c.define("path",function(c,f,d,l,h,a){function e(a,b){for(var e=[],g=0;g<a.length;g++)b(a[g],g,a)&&e.push(a[g]);return e}function g(a,b){for(var e=0,g=a.length;0<=g;g--){var d=a[g];"."==d?a.splice(g,1):".."===d?(a.splice(g,1),e++):e&&(a.splice(g,1),e--)}if(b)for(;e--;e)a.unshift("..");return a}var b=/^(.+\/(?!$)|\/)?((?:.+?)?(\.[^.]*)?)$/;d.resolve=
+function(){for(var b="",o=!1,d=arguments.length;-1<=d&&!o;d--){var c=0<=d?arguments[d]:a.cwd();"string"===typeof c&&c&&(b=c+"/"+b,o="/"===c.charAt(0))}b=g(e(b.split("/"),function(a){return!!a}),!o).join("/");return(o?"/":"")+b||"."};d.normalize=function(a){var b="/"===a.charAt(0),d="/"===a.slice(-1),a=g(e(a.split("/"),function(a){return!!a}),!b).join("/");!a&&!b&&(a=".");a&&d&&(a+="/");return(b?"/":"")+a};d.join=function(){var a=Array.prototype.slice.call(arguments,0);return d.normalize(e(a,function(a){return a&&
+"string"===typeof a}).join("/"))};d.dirname=function(a){return(a=b.exec(a)[1]||"")?1===a.length?a:a.substring(0,a.length-1):"."};d.basename=function(a,e){var g=b.exec(a)[2]||"";e&&g.substr(-1*e.length)===e&&(g=g.substr(0,g.length-e.length));return g};d.extname=function(a){return b.exec(a)[3]||""}});c.define("__browserify_process",function(c,f,d,l,h,a){var f=a=f.exports={},e=[],g="undefined"!==typeof window&&window.postMessage&&window.addEventListener;g&&window.addEventListener("message",function(a){a.source===
+window&&"browserify-tick"===a.data&&(a.stopPropagation(),0<e.length&&e.shift()())},!0);f.nextTick=function(a){g?(e.push(a),window.postMessage("browserify-tick","*")):setTimeout(a,0)};a.title="browser";a.browser=!0;a.env={};a.argv=[];a.binding=function(a){if("evals"===a)return c("vm");throw Error("No such module. (Possibly not yet loaded)");};var b="/",t;a.cwd=function(){return b};a.chdir=function(a){t||(t=c("path"));b=t.resolve(a,b)}});c.define("vm",function(c,f){f.exports=c("vm-browserify")});c.define("/node_modules/vm-browserify/package.json",
+function(c,f){f.exports={main:"index.js"}});c.define("/node_modules/vm-browserify/index.js",function(c,f,d){var l=function(a){if(Object.keys)return Object.keys(a);var g=[],b;for(b in a)g.push(b);return g},h=function(a,g){if(a.forEach)return a.forEach(g);for(var b=0;b<a.length;b++)g(a[b],b,a)},a=d.Script=function(e){if(!(this instanceof a))return new a(e);this.code=e};a.prototype.runInNewContext=function(a){a||(a={});var g=document.createElement("iframe");g.style||(g.style={});g.style.display="none";
+document.body.appendChild(g);var b=g.contentWindow;h(l(a),function(g){b[g]=a[g]});!b.eval&&b.execScript&&b.execScript("null");var d=b.eval(this.code);h(l(b),function(g){a[g]=b[g]});document.body.removeChild(g);return d};a.prototype.runInThisContext=function(){return eval(this.code)};a.prototype.runInContext=function(a){return this.runInNewContext(a)};h(l(a.prototype),function(e){d[e]=a[e]=function(g){var b=a(g);return b[e].apply(b,[].slice.call(arguments,1))}});d.createScript=function(a){return d.Script(a)};
+d.createContext=a.createContext=function(a){var g={};"object"===typeof a&&h(l(a),function(b){g[b]=a[b]});return g}});c.define("/package.json",function(c,f){f.exports={main:"./index"}});c.define("js-select",function(c,f){function d(a){a=e._parse(a||"*")[1];return","==a[0]?a.slice(1):[a]}function l(a,e){for(var g=0;g<a.length;g++){var d;a:{d=a[g];for(var c=e,l=c.parents.concat([c]),j=l.length-1,f=d.length-1,r=!0;0<=f&&0<=j;){var q=d[f],c=l[j];if(">"==q)f--,r=!0;else{if(h(q,c))f--;else if(r){d=!1;break a}j--;
+r=!1}}d=-1==f}if(d)return!0}return!1}function h(b,e){var d=e.key,c=e.node,h=e.parent;if(b.id&&d!=b.id)return!1;if(b.type){var f=b.type;if("null"==f&&null!==c||"array"==f&&!g(c)||"object"==f&&("object"!=typeof c||null===c||g(c))||("boolean"==f||"string"==f||"number"==f)&&f!=typeof c)return!1}if(":nth-child"==b.pf&&(f=parseInt(d)+1,0==b.a&&f!==b.b||1==b.a&&!(f>=-b.b)||-1==b.a&&!(f<=b.b)||2==b.a&&f%2!=b.b)||":nth-last-child"==b.pf&&(!h||d!=h.node.length-b.b)||":only-child"==b.pc&&(!h||1!=h.node.length)||
+":root"==b.pc&&void 0!==d)return!1;if(b.has){var j=","==b.has[0][0]?b.has[0].slice(1):[b.has[0]],p=!1;a(c).forEach(function(){l(j,this)&&(p=!0)});if(!p)return!1}return b.expr&&(f=b.expr,d=f[0],h=f[1],f=f[2],"string"!=typeof c||!d&&"="==h&&c!=f||!d&&"*="==h&&-1==c.indexOf(f))?!1:!0}var a=c("traverse"),e=c("JSONSelect");f.exports=function(b,e){var g=d(e);return{nodes:function(){var a=[];this.forEach(function(b){a.push(b)});return a},update:function(a){this.forEach(function(b){this.update("function"==
+typeof a?a(b):a)})},remove:function(){this.forEach(function(){this.remove()})},condense:function(){a(b).forEach(function(){if(this.parent)if(this.parent.keep)this.keep=!0;else{var a=l(g,this);(this.keep=a)?this.parent.keep_child=!0:this.isLeaf?this.remove():this.after(function(){this.keep_child&&(this.parent.keep_child=!0);!this.keep&&!this.keep_child&&this.remove()})}})},forEach:function(e){a(b).forEach(function(a){l(g,this)&&(this.matches=function(a){return l(d(a),this)},e.call(this,a))})}}};var g=
+Array.isArray||function(a){return"[object Array]"===toString.call(a)}});c.define("/node_modules/traverse/package.json",function(c,f){f.exports={main:"./index"}});c.define("/node_modules/traverse/index.js",function(c,f){function d(a){if(!(this instanceof d))return new d(a);this.value=a}function l(a,e,g){var b=[],d=[],c=!0;return function x(a){var f=g?h(a):a,l,j,q,v,n=!0,i={node:f,node_:a,path:[].concat(b),parent:d[d.length-1],parents:d,key:b.slice(-1)[0],isRoot:0===b.length,level:b.length,circular:null,
+update:function(a,b){i.isRoot||(i.parent.node[i.key]=a);i.node=a;b&&(n=!1)},"delete":function(){delete i.parent.node[i.key]},remove:function(){Array.isArray(i.parent.node)?i.parent.node.splice(i.key,1):delete i.parent.node[i.key]},keys:null,before:function(a){l=a},after:function(a){j=a},pre:function(a){q=a},post:function(a){v=a},stop:function(){c=!1},block:function(){n=!1}};if(!c)return i;if("object"===typeof f&&null!==f){i.keys=Object.keys(f);i.isLeaf=0==i.keys.length;for(f=0;f<d.length;f++)if(d[f].node_===
+a){i.circular=d[f];break}}else i.isLeaf=!0;i.notLeaf=!i.isLeaf;i.notRoot=!i.isRoot;a=e.call(i,i.node);void 0!==a&&i.update&&i.update(a);l&&l.call(i,i.node);if(!n)return i;"object"==typeof i.node&&(null!==i.node&&!i.circular)&&(d.push(i),i.keys.forEach(function(a,e){b.push(a);q&&q.call(i,i.node[a],a);var d=x(i.node[a]);g&&Object.hasOwnProperty.call(i.node,a)&&(i.node[a]=d.node);d.isLast=e==i.keys.length-1;d.isFirst=0==e;v&&v.call(i,d);b.pop()}),d.pop());j&&j.call(i,i.node);return i}(a).node}function h(a){if("object"===
+typeof a&&null!==a){var e;e=Array.isArray(a)?[]:a instanceof Date?new Date(a):a instanceof Boolean?new Boolean(a):a instanceof Number?new Number(a):a instanceof String?new String(a):Object.create(Object.getPrototypeOf(a));Object.keys(a).forEach(function(d){e[d]=a[d]});return e}return a}f.exports=d;d.prototype.get=function(a){for(var e=this.value,d=0;d<a.length;d++){var b=a[d];if(!Object.hasOwnProperty.call(e,b)){e=void 0;break}e=e[b]}return e};d.prototype.set=function(a,e){for(var d=this.value,b=
+0;b<a.length-1;b++){var c=a[b];Object.hasOwnProperty.call(d,c)||(d[c]={});d=d[c]}return d[a[b]]=e};d.prototype.map=function(a){return l(this.value,a,!0)};d.prototype.forEach=function(a){return this.value=l(this.value,a,!1)};d.prototype.reduce=function(a,d){var c=1===arguments.length,b=c?this.value:d;this.forEach(function(d){if(!this.isRoot||!c)b=a.call(this,b,d)});return b};d.prototype.deepEqual=function(a){if(1!==arguments.length)throw Error("deepEqual requires exactly one object to compare against");
+var e=!0,c=a;this.forEach(function(b){var f=function(){e=!1}.bind(this);if(!this.isRoot){if("object"!==typeof c)return f();c=c[this.key]}var h=c;this.post(function(){c=h});if(this.circular)d(a).get(this.circular.path)!==h&&f();else if(typeof h!==typeof b)f();else if(null===h||null===b||void 0===h||void 0===b)h!==b&&f();else if(h.__proto__!==b.__proto__)f();else if(h!==b)if("function"===typeof h)h instanceof RegExp?h.toString()!=b.toString()&&f():h!==b&&f();else if("object"===typeof h)if("[object Arguments]"===
+Object.prototype.toString.call(b)||"[object Arguments]"===Object.prototype.toString.call(h))Object.prototype.toString.call(h)!==Object.prototype.toString.call(b)&&f();else if(h instanceof Date||b instanceof Date)(!(h instanceof Date)||!(b instanceof Date)||h.getTime()!==b.getTime())&&f();else{var l=Object.keys(h),j=Object.keys(b);if(l.length!==j.length)return f();for(j=0;j<l.length;j++)Object.hasOwnProperty.call(b,l[j])||f()}});return e};d.prototype.paths=function(){var a=[];this.forEach(function(){a.push(this.path)});
+return a};d.prototype.nodes=function(){var a=[];this.forEach(function(){a.push(this.node)});return a};d.prototype.clone=function(){var a=[],d=[];return function b(c){for(var f=0;f<a.length;f++)if(a[f]===c)return d[f];if("object"===typeof c&&null!==c){var j=h(c);a.push(c);d.push(j);Object.keys(c).forEach(function(a){j[a]=b(c[a])});a.pop();d.pop();return j}return c}(this.value)};Object.keys(d.prototype).forEach(function(a){d[a]=function(c){var f=[].slice.call(arguments,1),b=d(c);return b[a].apply(b,
+f)}})});c.define("/node_modules/JSONSelect/package.json",function(c,f){f.exports={main:"src/jsonselect"}});c.define("/node_modules/JSONSelect/src/jsonselect.js",function(c,f,d){var c="undefined"===typeof d?window.JSONSelect={}:d,l=function(a){try{return JSON&&JSON.parse?JSON.parse(a):(new Function("return "+a))()}catch(b){h("ijs",b.message)}},h=function(a,b){throw Error(v[a]+(b&&" in '"+b+"'"));},a=function(a,b){b||(b=0);var d=i.exec(a.substr(b));if(d){var b=b+d[0].length,c;d[1]?c=[b," "]:d[2]?c=
+[b,d[0]]:d[3]?c=[b,n.typ,d[0]]:d[4]?c=[b,n.psc,d[0]]:d[5]?c=[b,n.psf,d[0]]:d[6]?h("upc",a):d[8]?c=[b,d[7]?n.ide:n.str,l(d[8])]:d[9]?h("ujs",a):d[10]&&(c=[b,n.ide,d[10].replace(/\\([^\r\n\f0-9a-fA-F])/g,"$1")]);return c}},e=function(a,b){return typeof a===b},g=function(a,b){var d,c=B.exec(a.substr(b));if(c)return b+=c[0].length,d=c[1]||c[2]||c[3]||c[5]||c[6],c[1]||c[2]||c[3]?[b,0,l(d)]:c[4]?[b,0,void 0]:[b,d]},b=function(a,d){d||(d=0);var c=g(a,d),e;c&&"("===c[1]?(e=b(a,c[0]),c=g(a,e[0]),(!c||")"!==
+c[1])&&h("epex",a),d=c[0],e=["(",e[1]]):!c||c[1]&&"x"!=c[1]?h("ee",a+" - "+(c[1]&&c[1])):(e="x"===c[1]?void 0:c[2],d=c[0]);c=g(a,d);if(!c||")"==c[1])return[d,e];("x"==c[1]||!c[1])&&h("bop",a+" - "+(c[1]&&c[1]));var f=b(a,c[0]),d=f[0],f=f[1],l;if("object"!==typeof f||"("===f[0]||w[c[1]][0]<w[f[1]][0])l=[e,c[1],f];else{for(l=f;"object"===typeof f[0]&&"("!=f[0][0]&&w[c[1]][0]>=w[f[0][1]][0];)f=f[0];f[0]=[e,c[1],f[0]]}return[d,l]},t=function(a,c){function d(a){return"object"!==typeof a||null===a?a:"("===
+a[0]?d(a[1]):[d(a[0]),a[1],d(a[2])]}var e=b(a,c?c:0);return[e[0],d(e[1])]},o=function(a,c){if(void 0===a)return c;if(null===a||"object"!==typeof a)return a;var b=o(a[0],c),d=o(a[2],c);return w[a[1]][1](b,d)},y=function(c,b,d,e){d||(e={});var f=[],g,l;for(b||(b=0);;){var m;m=c;var i=b,b=i,j={},k=a(m,i);k&&" "===k[1]&&(b=i=k[0],k=a(m,i));k&&k[1]===n.typ?(j.type=k[2],k=a(m,i=k[0])):k&&"*"===k[1]&&(k=a(m,i=k[0]));for(;void 0!==k;){if(k[1]===n.ide)j.id&&h("nmi",k[1]),j.id=k[2];else if(k[1]===n.psc)(j.pc||
+j.pf)&&h("mpc",k[1]),":first-child"===k[2]?(j.pf=":nth-child",j.a=0,j.b=1):":last-child"===k[2]?(j.pf=":nth-last-child",j.a=0,j.b=1):j.pc=k[2];else if(k[1]===n.psf)":val"===k[2]||":contains"===k[2]?(j.expr=[void 0,":val"===k[2]?"=":"*=",void 0],(k=a(m,k[0]))&&" "===k[1]&&(k=a(m,k[0])),(!k||"("!==k[1])&&h("pex",m),(k=a(m,k[0]))&&" "===k[1]&&(k=a(m,k[0])),(!k||k[1]!==n.str)&&h("sex",m),j.expr[2]=k[2],(k=a(m,k[0]))&&" "===k[1]&&(k=a(m,k[0])),(!k||")"!==k[1])&&h("epex",m)):":has"===k[2]?((k=a(m,k[0]))&&
+" "===k[1]&&(k=a(m,k[0])),(!k||"("!==k[1])&&h("pex",m),i=y(m,k[0],!0),k[0]=i[0],j.has||(j.has=[]),j.has.push(i[1])):":expr"===k[2]?(j.expr&&h("mexp",m),i=t(m,k[0]),k[0]=i[0],j.expr=i[1]):((j.pc||j.pf)&&h("mpc",m),j.pf=k[2],(i=A.exec(m.substr(k[0])))||h("mepf",m),i[5]?(j.a=2,j.b="odd"===i[5]?1:0):i[6]?(j.a=0,j.b=parseInt(i[6],10)):(j.a=parseInt((i[1]?i[1]:"+")+(i[2]?i[2]:"1"),10),j.b=i[3]?parseInt(i[3]+i[4],10):0),k[0]+=i[0].length);else break;k=a(m,i=k[0])}b===i&&h("se",m);m=[i,j];f.push(m[1]);(m=
+a(c,b=m[0]))&&" "===m[1]&&(m=a(c,b=m[0]));if(!m)break;if(">"===m[1]||"~"===m[1])"~"===m[1]&&(e.usesSiblingOp=!0),f.push(m[1]),b=m[0];else if(","===m[1])void 0===g?g=[",",f]:g.push(f),f=[],b=m[0];else if(")"===m[1]){d||h("ucp",m[1]);l=1;b=m[0];break}}d&&!l&&h("mcp",c);g&&g.push(f);var s;if(!d&&e.usesSiblingOp)if(c=g?g:f,","===c[0]){for(d=[","];s<c.length;s++)var o=x(o[s]),d=d.concat(","===o[0]?o.slice(1):o);s=d}else s=x(c);else s=g?g:f;return[b,s]},x=function(a){for(var c=[],b,d=0;d<a.length;d++)if("~"===
+a[d]){if(2>d||">"!=a[d-2])b=a.slice(0,d-1),b=b.concat([{has:[[{pc:":root"},">",a[d-1]]]},">"]),b=b.concat(a.slice(d+1)),c.push(b);if(1<d){var e=">"===a[d-2]?d-3:d-2;b=a.slice(0,e);var f={},g;for(g in a[e])a[e].hasOwnProperty(g)&&(f[g]=a[e][g]);f.has||(f.has=[]);f.has.push([{pc:":root"},">",a[d-1]]);b=b.concat(f,">",a.slice(d+1));c.push(b)}break}return d==a.length?a:1<c.length?[","].concat(c):c[0]},u=function(a){return Array.isArray?Array.isArray(a):"[object Array]"===q.call(a)},z=function(a,b,c,d,
+e){var f=[],g=">"===b[0]?b[1]:b[0],h=!0;if(g.type&&h){var h=g.type,i;null===a?i="null":(i=typeof a,"object"===i&&u(a)&&(i="array"));h=h===i}g.id&&(h=h&&g.id===c);h&&g.pf&&(":nth-last-child"===g.pf?d=e-d:d++,0===g.a?h=g.b===d:(c=(d-g.b)%g.a,h=!c&&0<=d*g.a+g.b));if(h&&g.has){d=function(){throw 42;};for(c=0;c<g.has.length;c++){try{p(g.has[c],a,d)}catch(j){if(42===j)continue}h=!1;break}}h&&g.expr&&(h=o(g.expr,a));">"!==b[0]&&":root"!==b[0].pc&&f.push(b);h&&(">"===b[0]?2<b.length&&(h=!1,f.push(b.slice(2))):
+1<b.length&&(h=!1,f.push(b.slice(1))));return[h,f]},p=function(a,b,c,d,e,f){for(var a=","===a[0]?a.slice(1):[a],g=[],h=!1,i=0,j=0,k,l,i=0;i<a.length;i++){l=z(b,a[i],d,e,f);l[0]&&(h=!0);for(j=0;j<l[1].length;j++)g.push(l[1][j])}if(g.length&&"object"===typeof b)if(1<=g.length&&g.unshift(","),u(b))for(i=0;i<b.length;i++)p(g,b[i],c,void 0,i,b.length);else for(k in b)b.hasOwnProperty(k)&&p(g,b[k],c,k);h&&c&&c(b)},r=function(a){return{sel:y(a)[1],match:function(a){var b=[];p(this.sel,a,function(a){b.push(a)});
+return b},forEach:function(a,b){return p(this.sel,a,b)}}},q=Object.prototype.toString,v={bop:"binary operator expected",ee:"expression expected",epex:"closing paren expected ')'",ijs:"invalid json string",mcp:"missing closing paren",mepf:"malformed expression in pseudo-function",mexp:"multiple expressions not allowed",mpc:"multiple pseudo classes (:xxx) not allowed",nmi:"multiple ids not allowed",pex:"opening paren expected '('",se:"selector expected",sex:"string expected",sra:"string required after '.'",
+uc:"unrecognized char",ucp:"unexpected closing paren",ujs:"unclosed json string",upc:"unrecognized pseudo class"},n={psc:1,psf:2,typ:3,str:4,ide:5},i=RegExp('^(?:([\\r\\n\\t\\ ]+)|([~*,>\\)\\(])|(string|boolean|null|array|object|number)|(:(?:root|first-child|last-child|only-child))|(:(?:nth-child|nth-last-child|has|expr|val|contains))|(:\\w+)|(?:(\\.)?(\\"(?:[^\\\\\\"]|\\\\[^\\"])*\\"))|(\\")|\\.((?:[_a-zA-Z]|[^\\0-\\0177]|\\\\[^\\r\\n\\f0-9a-fA-F])(?:[_a-zA-Z0-9\\-]|[^\\u0000-\\u0177]|(?:\\\\[^\\r\\n\\f0-9a-fA-F]))*))'),
+A=/^\s*\(\s*(?:([+\-]?)([0-9]*)n\s*(?:([+\-])\s*([0-9]))?|(odd|even)|([+\-]?[0-9]+))\s*\)/,B=RegExp('^\\s*(?:(true|false|null)|(-?\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?)|("(?:[^\\]|\\[^"])*")|(x)|(&&|\\|\\||[\\$\\^<>!\\*]=|[=+\\-*/%<>])|([\\(\\)]))'),w={"*":[9,function(a,b){return a*b}],"/":[9,function(a,b){return a/b}],"%":[9,function(a,b){return a%b}],"+":[7,function(a,b){return a+b}],"-":[7,function(a,b){return a-b}],"<=":[5,function(a,b){return e(a,"number")&&e(b,"number")&&a<=b}],">=":[5,function(a,
+b){return e(a,"number")&&e(b,"number")&&a>=b}],"$=":[5,function(a,b){return e(a,"string")&&e(b,"string")&&a.lastIndexOf(b)===a.length-b.length}],"^=":[5,function(a,b){return e(a,"string")&&e(b,"string")&&0===a.indexOf(b)}],"*=":[5,function(a,b){return e(a,"string")&&e(b,"string")&&-1!==a.indexOf(b)}],">":[5,function(a,b){return e(a,"number")&&e(b,"number")&&a>b}],"<":[5,function(a,b){return e(a,"number")&&e(b,"number")&&a<b}],"=":[3,function(a,b){return a===b}],"!=":[3,function(a,b){return a!==b}],
+"&&":[2,function(a,b){return a&&b}],"||":[1,function(a,b){return a||b}]};c._lex=a;c._parse=y;c.match=function(a,b){return r(a).match(b)};c.forEach=function(a,b,c){return r(a).forEach(b,c)};c.compile=r});return c("js-select")}();
\ No newline at end of file
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/select.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/select.js
new file mode 100644
index 0000000..2a5cb31
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/select.js
@@ -0,0 +1,1533 @@
+var select = (function() {
+	var require = function (file, cwd) {
+	    var resolved = require.resolve(file, cwd || '/');
+	    var mod = require.modules[resolved];
+	    if (!mod) throw new Error(
+	        'Failed to resolve module ' + file + ', tried ' + resolved
+	    );
+	    var cached = require.cache[resolved];
+	    var res = cached? cached.exports : mod();
+	    return res;
+	};
+
+	require.paths = [];
+	require.modules = {};
+	require.cache = {};
+	require.extensions = [".js",".coffee"];
+
+	require._core = {
+	    'assert': true,
+	    'events': true,
+	    'fs': true,
+	    'path': true,
+	    'vm': true
+	};
+
+	require.resolve = (function () {
+	    return function (x, cwd) {
+	        if (!cwd) cwd = '/';
+        
+	        if (require._core[x]) return x;
+	        var path = require.modules.path();
+	        cwd = path.resolve('/', cwd);
+	        var y = cwd || '/';
+        
+	        if (x.match(/^(?:\.\.?\/|\/)/)) {
+	            var m = loadAsFileSync(path.resolve(y, x))
+	                || loadAsDirectorySync(path.resolve(y, x));
+	            if (m) return m;
+	        }
+        
+	        var n = loadNodeModulesSync(x, y);
+	        if (n) return n;
+        
+	        throw new Error("Cannot find module '" + x + "'");
+        
+	        function loadAsFileSync (x) {
+	            x = path.normalize(x);
+	            if (require.modules[x]) {
+	                return x;
+	            }
+            
+	            for (var i = 0; i < require.extensions.length; i++) {
+	                var ext = require.extensions[i];
+	                if (require.modules[x + ext]) return x + ext;
+	            }
+	        }
+        
+	        function loadAsDirectorySync (x) {
+	            x = x.replace(/\/+$/, '');
+	            var pkgfile = path.normalize(x + '/package.json');
+	            if (require.modules[pkgfile]) {
+	                var pkg = require.modules[pkgfile]();
+	                var b = pkg.browserify;
+	                if (typeof b === 'object' && b.main) {
+	                    var m = loadAsFileSync(path.resolve(x, b.main));
+	                    if (m) return m;
+	                }
+	                else if (typeof b === 'string') {
+	                    var m = loadAsFileSync(path.resolve(x, b));
+	                    if (m) return m;
+	                }
+	                else if (pkg.main) {
+	                    var m = loadAsFileSync(path.resolve(x, pkg.main));
+	                    if (m) return m;
+	                }
+	            }
+            
+	            return loadAsFileSync(x + '/index');
+	        }
+        
+	        function loadNodeModulesSync (x, start) {
+	            var dirs = nodeModulesPathsSync(start);
+	            for (var i = 0; i < dirs.length; i++) {
+	                var dir = dirs[i];
+	                var m = loadAsFileSync(dir + '/' + x);
+	                if (m) return m;
+	                var n = loadAsDirectorySync(dir + '/' + x);
+	                if (n) return n;
+	            }
+            
+	            var m = loadAsFileSync(x);
+	            if (m) return m;
+	        }
+        
+	        function nodeModulesPathsSync (start) {
+	            var parts;
+	            if (start === '/') parts = [ '' ];
+	            else parts = path.normalize(start).split('/');
+            
+	            var dirs = [];
+	            for (var i = parts.length - 1; i >= 0; i--) {
+	                if (parts[i] === 'node_modules') continue;
+	                var dir = parts.slice(0, i + 1).join('/') + '/node_modules';
+	                dirs.push(dir);
+	            }
+            
+	            return dirs;
+	        }
+	    };
+	})();
+
+	require.alias = function (from, to) {
+	    var path = require.modules.path();
+	    var res = null;
+	    try {
+	        res = require.resolve(from + '/package.json', '/');
+	    }
+	    catch (err) {
+	        res = require.resolve(from, '/');
+	    }
+	    var basedir = path.dirname(res);
+    
+	    var keys = (Object.keys || function (obj) {
+	        var res = [];
+	        for (var key in obj) res.push(key);
+	        return res;
+	    })(require.modules);
+    
+	    for (var i = 0; i < keys.length; i++) {
+	        var key = keys[i];
+	        if (key.slice(0, basedir.length + 1) === basedir + '/') {
+	            var f = key.slice(basedir.length);
+	            require.modules[to + f] = require.modules[basedir + f];
+	        }
+	        else if (key === basedir) {
+	            require.modules[to] = require.modules[basedir];
+	        }
+	    }
+	};
+
+	(function () {
+	    var process = {};
+    
+	    require.define = function (filename, fn) {
+	        if (require.modules.__browserify_process) {
+	            process = require.modules.__browserify_process();
+	        }
+        
+	        var dirname = require._core[filename]
+	            ? ''
+	            : require.modules.path().dirname(filename)
+	        ;
+        
+	        var require_ = function (file) {
+	            var requiredModule = require(file, dirname);
+	            var cached = require.cache[require.resolve(file, dirname)];
+
+	            if (cached && cached.parent === null) {
+	                cached.parent = module_;
+	            }
+
+	            return requiredModule;
+	        };
+	        require_.resolve = function (name) {
+	            return require.resolve(name, dirname);
+	        };
+	        require_.modules = require.modules;
+	        require_.define = require.define;
+	        require_.cache = require.cache;
+	        var module_ = {
+	            id : filename,
+	            filename: filename,
+	            exports : {},
+	            loaded : false,
+	            parent: null
+	        };
+        
+	        require.modules[filename] = function () {
+	            require.cache[filename] = module_;
+	            fn.call(
+	                module_.exports,
+	                require_,
+	                module_,
+	                module_.exports,
+	                dirname,
+	                filename,
+	                process
+	            );
+	            module_.loaded = true;
+	            return module_.exports;
+	        };
+	    };
+	})();
+
+
+	require.define("path",function(require,module,exports,__dirname,__filename,process){function filter (xs, fn) {
+	    var res = [];
+	    for (var i = 0; i < xs.length; i++) {
+	        if (fn(xs[i], i, xs)) res.push(xs[i]);
+	    }
+	    return res;
+	}
+
+	// resolves . and .. elements in a path array with directory names there
+	// must be no slashes, empty elements, or device names (c:\) in the array
+	// (so also no leading and trailing slashes - it does not distinguish
+	// relative and absolute paths)
+	function normalizeArray(parts, allowAboveRoot) {
+	  // if the path tries to go above the root, `up` ends up > 0
+	  var up = 0;
+	  for (var i = parts.length; i >= 0; i--) {
+	    var last = parts[i];
+	    if (last == '.') {
+	      parts.splice(i, 1);
+	    } else if (last === '..') {
+	      parts.splice(i, 1);
+	      up++;
+	    } else if (up) {
+	      parts.splice(i, 1);
+	      up--;
+	    }
+	  }
+
+	  // if the path is allowed to go above the root, restore leading ..s
+	  if (allowAboveRoot) {
+	    for (; up--; up) {
+	      parts.unshift('..');
+	    }
+	  }
+
+	  return parts;
+	}
+
+	// Regex to split a filename into [*, dir, basename, ext]
+	// posix version
+	var splitPathRe = /^(.+\/(?!$)|\/)?((?:.+?)?(\.[^.]*)?)$/;
+
+	// path.resolve([from ...], to)
+	// posix version
+	exports.resolve = function() {
+	var resolvedPath = '',
+	    resolvedAbsolute = false;
+
+	for (var i = arguments.length; i >= -1 && !resolvedAbsolute; i--) {
+	  var path = (i >= 0)
+	      ? arguments[i]
+	      : process.cwd();
+
+	  // Skip empty and invalid entries
+	  if (typeof path !== 'string' || !path) {
+	    continue;
+	  }
+
+	  resolvedPath = path + '/' + resolvedPath;
+	  resolvedAbsolute = path.charAt(0) === '/';
+	}
+
+	// At this point the path should be resolved to a full absolute path, but
+	// handle relative paths to be safe (might happen when process.cwd() fails)
+
+	// Normalize the path
+	resolvedPath = normalizeArray(filter(resolvedPath.split('/'), function(p) {
+	    return !!p;
+	  }), !resolvedAbsolute).join('/');
+
+	  return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.';
+	};
+
+	// path.normalize(path)
+	// posix version
+	exports.normalize = function(path) {
+	var isAbsolute = path.charAt(0) === '/',
+	    trailingSlash = path.slice(-1) === '/';
+
+	// Normalize the path
+	path = normalizeArray(filter(path.split('/'), function(p) {
+	    return !!p;
+	  }), !isAbsolute).join('/');
+
+	  if (!path && !isAbsolute) {
+	    path = '.';
+	  }
+	  if (path && trailingSlash) {
+	    path += '/';
+	  }
+  
+	  return (isAbsolute ? '/' : '') + path;
+	};
+
+
+	// posix version
+	exports.join = function() {
+	  var paths = Array.prototype.slice.call(arguments, 0);
+	  return exports.normalize(filter(paths, function(p, index) {
+	    return p && typeof p === 'string';
+	  }).join('/'));
+	};
+
+
+	exports.dirname = function(path) {
+	  var dir = splitPathRe.exec(path)[1] || '';
+	  var isWindows = false;
+	  if (!dir) {
+	    // No dirname
+	    return '.';
+	  } else if (dir.length === 1 ||
+	      (isWindows && dir.length <= 3 && dir.charAt(1) === ':')) {
+	    // It is just a slash or a drive letter with a slash
+	    return dir;
+	  } else {
+	    // It is a full dirname, strip trailing slash
+	    return dir.substring(0, dir.length - 1);
+	  }
+	};
+
+
+	exports.basename = function(path, ext) {
+	  var f = splitPathRe.exec(path)[2] || '';
+	  // TODO: make this comparison case-insensitive on windows?
+	  if (ext && f.substr(-1 * ext.length) === ext) {
+	    f = f.substr(0, f.length - ext.length);
+	  }
+	  return f;
+	};
+
+
+	exports.extname = function(path) {
+	  return splitPathRe.exec(path)[3] || '';
+	};
+	});
+
+	require.define("__browserify_process",function(require,module,exports,__dirname,__filename,process){var process = module.exports = {};
+
+	process.nextTick = (function () {
+	    var queue = [];
+	    var canPost = typeof window !== 'undefined'
+	        && window.postMessage && window.addEventListener
+	    ;
+    
+	    if (canPost) {
+	        window.addEventListener('message', function (ev) {
+	            if (ev.source === window && ev.data === 'browserify-tick') {
+	                ev.stopPropagation();
+	                if (queue.length > 0) {
+	                    var fn = queue.shift();
+	                    fn();
+	                }
+	            }
+	        }, true);
+	    }
+    
+	    return function (fn) {
+	        if (canPost) {
+	            queue.push(fn);
+	            window.postMessage('browserify-tick', '*');
+	        }
+	        else setTimeout(fn, 0);
+	    };
+	})();
+
+	process.title = 'browser';
+	process.browser = true;
+	process.env = {};
+	process.argv = [];
+
+	process.binding = function (name) {
+	    if (name === 'evals') return (require)('vm')
+	    else throw new Error('No such module. (Possibly not yet loaded)')
+	};
+
+	(function () {
+	    var cwd = '/';
+	    var path;
+	    process.cwd = function () { return cwd };
+	    process.chdir = function (dir) {
+	        if (!path) path = require('path');
+	        cwd = path.resolve(dir, cwd);
+	    };
+	})();
+	});
+
+	require.define("vm",function(require,module,exports,__dirname,__filename,process){module.exports = require("vm-browserify")});
+
+	require.define("/node_modules/vm-browserify/package.json",function(require,module,exports,__dirname,__filename,process){module.exports = {"main":"index.js"}});
+
+	require.define("/node_modules/vm-browserify/index.js",function(require,module,exports,__dirname,__filename,process){var Object_keys = function (obj) {
+	    if (Object.keys) return Object.keys(obj)
+	    else {
+	        var res = [];
+	        for (var key in obj) res.push(key)
+	        return res;
+	    }
+	};
+
+	var forEach = function (xs, fn) {
+	    if (xs.forEach) return xs.forEach(fn)
+	    else for (var i = 0; i < xs.length; i++) {
+	        fn(xs[i], i, xs);
+	    }
+	};
+
+	var Script = exports.Script = function NodeScript (code) {
+	    if (!(this instanceof Script)) return new Script(code);
+	    this.code = code;
+	};
+
+	Script.prototype.runInNewContext = function (context) {
+	    if (!context) context = {};
+    
+	    var iframe = document.createElement('iframe');
+	    if (!iframe.style) iframe.style = {};
+	    iframe.style.display = 'none';
+    
+	    document.body.appendChild(iframe);
+    
+	    var win = iframe.contentWindow;
+    
+	    forEach(Object_keys(context), function (key) {
+	        win[key] = context[key];
+	    });
+     
+	    if (!win.eval && win.execScript) {
+	        // win.eval() magically appears when this is called in IE:
+	        win.execScript('null');
+	    }
+    
+	    var res = win.eval(this.code);
+    
+	    forEach(Object_keys(win), function (key) {
+	        context[key] = win[key];
+	    });
+    
+	    document.body.removeChild(iframe);
+    
+	    return res;
+	};
+
+	Script.prototype.runInThisContext = function () {
+	    return eval(this.code); // maybe...
+	};
+
+	Script.prototype.runInContext = function (context) {
+	    // seems to be just runInNewContext on magical context objects which are
+	    // otherwise indistinguishable from objects except plain old objects
+	    // for the parameter segfaults node
+	    return this.runInNewContext(context);
+	};
+
+	forEach(Object_keys(Script.prototype), function (name) {
+	    exports[name] = Script[name] = function (code) {
+	        var s = Script(code);
+	        return s[name].apply(s, [].slice.call(arguments, 1));
+	    };
+	});
+
+	exports.createScript = function (code) {
+	    return exports.Script(code);
+	};
+
+	exports.createContext = Script.createContext = function (context) {
+	    // not really sure what this one does
+	    // seems to just make a shallow copy
+	    var copy = {};
+	    if(typeof context === 'object') {
+	        forEach(Object_keys(context), function (key) {
+	            copy[key] = context[key];
+	        });
+	    }
+	    return copy;
+	};
+	});
+
+	require.define("/package.json",function(require,module,exports,__dirname,__filename,process){module.exports = {"main":"./index"}});
+
+	require.define("js-select",function(require,module,exports,__dirname,__filename,process){var traverse = require("traverse"),
+	    JSONSelect = require("JSONSelect");
+
+	module.exports = function(obj, string) {
+	   var sels = parseSelectors(string);
+
+	   return {
+	      nodes: function() {
+	         var nodes = [];
+	         this.forEach(function(node) {
+	            nodes.push(node);
+	         });
+	         return nodes;
+	      },
+      
+	      update: function(cb) {
+	         this.forEach(function(node) {
+	            this.update(typeof cb == "function" ? cb(node) : cb);
+	         });
+	      },
+      
+	      remove: function() {
+	         this.forEach(function(node) {
+	            this.remove();
+	         })
+	      },
+      
+	      condense: function() {
+	         traverse(obj).forEach(function(node) {
+	            if (!this.parent) return;
+            
+	            if (this.parent.keep) {
+	               this.keep = true;
+	            } else {
+	               var match = matchesAny(sels, this);
+	               this.keep = match;
+	               if (!match) {
+	                  if (this.isLeaf) {
+	                     this.remove();
+	                  } else {
+	                     this.after(function() {
+	                        if (this.keep_child) {
+	                           this.parent.keep_child = true;
+	                        }
+	                        if (!this.keep && !this.keep_child) {
+	                           this.remove();
+	                        }
+	                     });
+	                  }
+	               } else {
+	                  this.parent.keep_child = true;
+	               }
+	            }
+	         });
+	      },
+      
+	      forEach: function(cb) {
+	         traverse(obj).forEach(function(node) {
+	            if (matchesAny(sels, this)) {
+	               this.matches = function(string) {
+	                  return matchesAny(parseSelectors(string), this);
+	               };
+	               // inherit context from js-traverse
+	               cb.call(this, node);            
+	            }
+	         });
+	      }
+	   };
+	}
+
+	function parseSelectors(string) {
+	   var parsed = JSONSelect._parse(string || "*")[1];
+	   return getSelectors(parsed);
+	}
+
+	function getSelectors(parsed) {
+	   if (parsed[0] == ",") {  // "selector1, selector2"
+	      return parsed.slice(1);
+	   }
+	   return [parsed];
+	}
+
+	function matchesAny(sels, context) {
+	   for (var i = 0; i < sels.length; i++) {
+	      if (matches(sels[i], context)) {
+	         return true;
+	      }
+	   }
+	   return false;
+	}
+
+	function matches(sel, context) {
+	   var path = context.parents.concat([context]),
+	       i = path.length - 1,
+	       j = sel.length - 1;
+
+	   // walk up the ancestors
+	   var must = true;
+	   while(j >= 0 && i >= 0) {
+	      var part = sel[j],
+	          context = path[i];
+
+	      if (part == ">") {
+	         j--;
+	         must = true;
+	         continue;
+	      }
+
+	      if (matchesKey(part, context)) {
+	         j--;
+	      }
+	      else if (must) {
+	         return false;
+	      }
+
+	      i--;
+	      must = false;
+	   }
+	   return j == -1;
+	}
+
+	function matchesKey(part, context) {
+	   var key = context.key,
+	       node = context.node,
+	       parent = context.parent;
+
+	   if (part.id && key != part.id) {
+	      return false;
+	   }
+	   if (part.type) {
+	      var type = part.type;
+
+	      if (type == "null" && node !== null) {
+	         return false;
+	      }
+	      else if (type == "array" && !isArray(node)) {
+	         return false;
+	      }
+	      else if (type == "object" && (typeof node != "object"
+	                 || node === null || isArray(node))) {
+	         return false;
+	      }
+	      else if ((type == "boolean" || type == "string" || type == "number")
+	               && type != typeof node) {
+	         return false;
+	      }
+	   }
+	   if (part.pf == ":nth-child") {
+	      var index = parseInt(key) + 1;
+	      if ((part.a == 0 && index !== part.b)         // :nth-child(i)
+	        || (part.a == 1 && !(index >= -part.b))     // :nth-child(n)
+	        || (part.a == -1 && !(index <= part.b))     // :nth-child(-n + 1)
+	        || (part.a == 2 && index % 2 != part.b)) {  // :nth-child(even)
+	         return false;
+	      }
+	   }
+	   if (part.pf == ":nth-last-child"
+	      && (!parent || key != parent.node.length - part.b)) {
+	         return false;
+	   }
+	   if (part.pc == ":only-child"
+	      && (!parent || parent.node.length != 1)) {
+	         return false;
+	   }
+	   if (part.pc == ":root" && key !== undefined) {
+	      return false;
+	   }
+	   if (part.has) {
+	      var sels = getSelectors(part.has[0]),
+	          match = false;
+	      traverse(node).forEach(function(child) {
+	         if (matchesAny(sels, this)) {
+	            match = true;
+	         }
+	      });
+	      if (!match) {
+	         return false;
+	      }
+	   }
+	   if (part.expr) {
+	      var expr = part.expr, lhs = expr[0], op = expr[1], rhs = expr[2];
+	      if (typeof node != "string"
+	          || (!lhs && op == "=" && node != rhs)   // :val("str")
+	          || (!lhs && op == "*=" && node.indexOf(rhs) == -1)) { // :contains("substr")
+	         return false;
+	      }
+	   }
+	   return true;
+	}
+
+	var isArray = Array.isArray || function(obj) {
+	    return toString.call(obj) === '[object Array]';
+	}
+	});
+
+	require.define("/node_modules/traverse/package.json",function(require,module,exports,__dirname,__filename,process){module.exports = {"main":"./index"}});
+
+	require.define("/node_modules/traverse/index.js",function(require,module,exports,__dirname,__filename,process){module.exports = Traverse;
+	function Traverse (obj) {
+	    if (!(this instanceof Traverse)) return new Traverse(obj);
+	    this.value = obj;
+	}
+
+	Traverse.prototype.get = function (ps) {
+	    var node = this.value;
+	    for (var i = 0; i < ps.length; i ++) {
+	        var key = ps[i];
+	        if (!Object.hasOwnProperty.call(node, key)) {
+	            node = undefined;
+	            break;
+	        }
+	        node = node[key];
+	    }
+	    return node;
+	};
+
+	Traverse.prototype.set = function (ps, value) {
+	    var node = this.value;
+	    for (var i = 0; i < ps.length - 1; i ++) {
+	        var key = ps[i];
+	        if (!Object.hasOwnProperty.call(node, key)) node[key] = {};
+	        node = node[key];
+	    }
+	    node[ps[i]] = value;
+	    return value;
+	};
+
+	Traverse.prototype.map = function (cb) {
+	    return walk(this.value, cb, true);
+	};
+
+	Traverse.prototype.forEach = function (cb) {
+	    this.value = walk(this.value, cb, false);
+	    return this.value;
+	};
+
+	Traverse.prototype.reduce = function (cb, init) {
+	    var skip = arguments.length === 1;
+	    var acc = skip ? this.value : init;
+	    this.forEach(function (x) {
+	        if (!this.isRoot || !skip) {
+	            acc = cb.call(this, acc, x);
+	        }
+	    });
+	    return acc;
+	};
+
+	Traverse.prototype.deepEqual = function (obj) {
+	    if (arguments.length !== 1) {
+	        throw new Error(
+	            'deepEqual requires exactly one object to compare against'
+	        );
+	    }
+    
+	    var equal = true;
+	    var node = obj;
+    
+	    this.forEach(function (y) {
+	        var notEqual = (function () {
+	            equal = false;
+	            //this.stop();
+	            return undefined;
+	        }).bind(this);
+        
+	        //if (node === undefined || node === null) return notEqual();
+        
+	        if (!this.isRoot) {
+	        /*
+	            if (!Object.hasOwnProperty.call(node, this.key)) {
+	                return notEqual();
+	            }
+	        */
+	            if (typeof node !== 'object') return notEqual();
+	            node = node[this.key];
+	        }
+        
+	        var x = node;
+        
+	        this.post(function () {
+	            node = x;
+	        });
+        
+	        var toS = function (o) {
+	            return Object.prototype.toString.call(o);
+	        };
+        
+	        if (this.circular) {
+	            if (Traverse(obj).get(this.circular.path) !== x) notEqual();
+	        }
+	        else if (typeof x !== typeof y) {
+	            notEqual();
+	        }
+	        else if (x === null || y === null || x === undefined || y === undefined) {
+	            if (x !== y) notEqual();
+	        }
+	        else if (x.__proto__ !== y.__proto__) {
+	            notEqual();
+	        }
+	        else if (x === y) {
+	            // nop
+	        }
+	        else if (typeof x === 'function') {
+	            if (x instanceof RegExp) {
+	                // both regexps on account of the __proto__ check
+	                if (x.toString() != y.toString()) notEqual();
+	            }
+	            else if (x !== y) notEqual();
+	        }
+	        else if (typeof x === 'object') {
+	            if (toS(y) === '[object Arguments]'
+	            || toS(x) === '[object Arguments]') {
+	                if (toS(x) !== toS(y)) {
+	                    notEqual();
+	                }
+	            }
+	            else if (x instanceof Date || y instanceof Date) {
+	                if (!(x instanceof Date) || !(y instanceof Date)
+	                || x.getTime() !== y.getTime()) {
+	                    notEqual();
+	                }
+	            }
+	            else {
+	                var kx = Object.keys(x);
+	                var ky = Object.keys(y);
+	                if (kx.length !== ky.length) return notEqual();
+	                for (var i = 0; i < kx.length; i++) {
+	                    var k = kx[i];
+	                    if (!Object.hasOwnProperty.call(y, k)) {
+	                        notEqual();
+	                    }
+	                }
+	            }
+	        }
+	    });
+    
+	    return equal;
+	};
+
+	Traverse.prototype.paths = function () {
+	    var acc = [];
+	    this.forEach(function (x) {
+	        acc.push(this.path); 
+	    });
+	    return acc;
+	};
+
+	Traverse.prototype.nodes = function () {
+	    var acc = [];
+	    this.forEach(function (x) {
+	        acc.push(this.node);
+	    });
+	    return acc;
+	};
+
+	Traverse.prototype.clone = function () {
+	    var parents = [], nodes = [];
+    
+	    return (function clone (src) {
+	        for (var i = 0; i < parents.length; i++) {
+	            if (parents[i] === src) {
+	                return nodes[i];
+	            }
+	        }
+        
+	        if (typeof src === 'object' && src !== null) {
+	            var dst = copy(src);
+            
+	            parents.push(src);
+	            nodes.push(dst);
+            
+	            Object.keys(src).forEach(function (key) {
+	                dst[key] = clone(src[key]);
+	            });
+            
+	            parents.pop();
+	            nodes.pop();
+	            return dst;
+	        }
+	        else {
+	            return src;
+	        }
+	    })(this.value);
+	};
+
+	function walk (root, cb, immutable) {
+	    var path = [];
+	    var parents = [];
+	    var alive = true;
+    
+	    return (function walker (node_) {
+	        var node = immutable ? copy(node_) : node_;
+	        var modifiers = {};
+        
+	        var keepGoing = true;
+        
+	        var state = {
+	            node : node,
+	            node_ : node_,
+	            path : [].concat(path),
+	            parent : parents[parents.length - 1],
+	            parents : parents,
+	            key : path.slice(-1)[0],
+	            isRoot : path.length === 0,
+	            level : path.length,
+	            circular : null,
+	            update : function (x, stopHere) {
+	                if (!state.isRoot) {
+	                    state.parent.node[state.key] = x;
+	                }
+	                state.node = x;
+	                if (stopHere) keepGoing = false;
+	            },
+	            'delete' : function () {
+	                delete state.parent.node[state.key];
+	            },
+	            remove : function () {
+	                if (Array.isArray(state.parent.node)) {
+	                    state.parent.node.splice(state.key, 1);
+	                }
+	                else {
+	                    delete state.parent.node[state.key];
+	                }
+	            },
+	            keys : null,
+	            before : function (f) { modifiers.before = f },
+	            after : function (f) { modifiers.after = f },
+	            pre : function (f) { modifiers.pre = f },
+	            post : function (f) { modifiers.post = f },
+	            stop : function () { alive = false },
+	            block : function () { keepGoing = false }
+	        };
+        
+	        if (!alive) return state;
+        
+	        if (typeof node === 'object' && node !== null) {
+	            state.keys = Object.keys(node);
+            
+	            state.isLeaf = state.keys.length == 0;
+            
+	            for (var i = 0; i < parents.length; i++) {
+	                if (parents[i].node_ === node_) {
+	                    state.circular = parents[i];
+	                    break;
+	                }
+	            }
+	        }
+	        else {
+	            state.isLeaf = true;
+	        }
+        
+	        state.notLeaf = !state.isLeaf;
+	        state.notRoot = !state.isRoot;
+        
+	        // use return values to update if defined
+	        var ret = cb.call(state, state.node);
+	        if (ret !== undefined && state.update) state.update(ret);
+        
+	        if (modifiers.before) modifiers.before.call(state, state.node);
+        
+	        if (!keepGoing) return state;
+        
+	        if (typeof state.node == 'object'
+	        && state.node !== null && !state.circular) {
+	            parents.push(state);
+            
+	            state.keys.forEach(function (key, i) {
+	                path.push(key);
+                
+	                if (modifiers.pre) modifiers.pre.call(state, state.node[key], key);
+                
+	                var child = walker(state.node[key]);
+	                if (immutable && Object.hasOwnProperty.call(state.node, key)) {
+	                    state.node[key] = child.node;
+	                }
+                
+	                child.isLast = i == state.keys.length - 1;
+	                child.isFirst = i == 0;
+                
+	                if (modifiers.post) modifiers.post.call(state, child);
+                
+	                path.pop();
+	            });
+	            parents.pop();
+	        }
+        
+	        if (modifiers.after) modifiers.after.call(state, state.node);
+        
+	        return state;
+	    })(root).node;
+	}
+
+	Object.keys(Traverse.prototype).forEach(function (key) {
+	    Traverse[key] = function (obj) {
+	        var args = [].slice.call(arguments, 1);
+	        var t = Traverse(obj);
+	        return t[key].apply(t, args);
+	    };
+	});
+
+	function copy (src) {
+	    if (typeof src === 'object' && src !== null) {
+	        var dst;
+        
+	        if (Array.isArray(src)) {
+	            dst = [];
+	        }
+	        else if (src instanceof Date) {
+	            dst = new Date(src);
+	        }
+	        else if (src instanceof Boolean) {
+	            dst = new Boolean(src);
+	        }
+	        else if (src instanceof Number) {
+	            dst = new Number(src);
+	        }
+	        else if (src instanceof String) {
+	            dst = new String(src);
+	        }
+	        else {
+	            dst = Object.create(Object.getPrototypeOf(src));
+	        }
+        
+	        Object.keys(src).forEach(function (key) {
+	            dst[key] = src[key];
+	        });
+	        return dst;
+	    }
+	    else return src;
+	}
+	});
+
+	require.define("/node_modules/JSONSelect/package.json",function(require,module,exports,__dirname,__filename,process){module.exports = {"main":"src/jsonselect"}});
+
+	require.define("/node_modules/JSONSelect/src/jsonselect.js",function(require,module,exports,__dirname,__filename,process){/*! Copyright (c) 2011, Lloyd Hilaiel, ISC License */
+	/*
+	 * This is the JSONSelect reference implementation, in javascript.
+	 */
+	(function(exports) {
+
+	    var // localize references
+	    toString = Object.prototype.toString;
+
+	    function jsonParse(str) {
+	      try {
+	          if(JSON && JSON.parse){
+	              return JSON.parse(str);
+	          }
+	          return (new Function("return " + str))();
+	      } catch(e) {
+	        te("ijs", e.message);
+	      }
+	    }
+
+	    // emitted error codes.
+	    var errorCodes = {
+	        "bop":  "binary operator expected",
+	        "ee":   "expression expected",
+	        "epex": "closing paren expected ')'",
+	        "ijs":  "invalid json string",
+	        "mcp":  "missing closing paren",
+	        "mepf": "malformed expression in pseudo-function",
+	        "mexp": "multiple expressions not allowed",
+	        "mpc":  "multiple pseudo classes (:xxx) not allowed",
+	        "nmi":  "multiple ids not allowed",
+	        "pex":  "opening paren expected '('",
+	        "se":   "selector expected",
+	        "sex":  "string expected",
+	        "sra":  "string required after '.'",
+	        "uc":   "unrecognized char",
+	        "ucp":  "unexpected closing paren",
+	        "ujs":  "unclosed json string",
+	        "upc":  "unrecognized pseudo class"
+	    };
+
+	    // throw an error message
+	    function te(ec, context) {
+	      throw new Error(errorCodes[ec] + ( context && " in '" + context + "'"));
+	    }
+
+	    // THE LEXER
+	    var toks = {
+	        psc: 1, // pseudo class
+	        psf: 2, // pseudo class function
+	        typ: 3, // type
+	        str: 4, // string
+	        ide: 5  // identifiers (or "classes", stuff after a dot)
+	    };
+
+	    // The primary lexing regular expression in jsonselect
+	    var pat = new RegExp(
+	        "^(?:" +
+	        // (1) whitespace
+	        "([\\r\\n\\t\\ ]+)|" +
+	        // (2) one-char ops
+	        "([~*,>\\)\\(])|" +
+	        // (3) types names
+	        "(string|boolean|null|array|object|number)|" +
+	        // (4) pseudo classes
+	        "(:(?:root|first-child|last-child|only-child))|" +
+	        // (5) pseudo functions
+	        "(:(?:nth-child|nth-last-child|has|expr|val|contains))|" +
+	        // (6) bogusly named pseudo something or others
+	        "(:\\w+)|" +
+	        // (7 & 8) identifiers and JSON strings
+	        "(?:(\\.)?(\\\"(?:[^\\\\\\\"]|\\\\[^\\\"])*\\\"))|" +
+	        // (8) bogus JSON strings missing a trailing quote
+	        "(\\\")|" +
+	        // (9) identifiers (unquoted)
+	        "\\.((?:[_a-zA-Z]|[^\\0-\\0177]|\\\\[^\\r\\n\\f0-9a-fA-F])(?:[_a-zA-Z0-9\\-]|[^\\u0000-\\u0177]|(?:\\\\[^\\r\\n\\f0-9a-fA-F]))*)" +
+	        ")"
+	    );
+
+	    // A regular expression for matching "nth expressions" (see grammar, what :nth-child() eats)
+	    var nthPat = /^\s*\(\s*(?:([+\-]?)([0-9]*)n\s*(?:([+\-])\s*([0-9]))?|(odd|even)|([+\-]?[0-9]+))\s*\)/;
+	    function lex(str, off) {
+	        if (!off) off = 0;
+	        var m = pat.exec(str.substr(off));
+	        if (!m) return undefined;
+	        off+=m[0].length;
+	        var a;
+	        if (m[1]) a = [off, " "];
+	        else if (m[2]) a = [off, m[0]];
+	        else if (m[3]) a = [off, toks.typ, m[0]];
+	        else if (m[4]) a = [off, toks.psc, m[0]];
+	        else if (m[5]) a = [off, toks.psf, m[0]];
+	        else if (m[6]) te("upc", str);
+	        else if (m[8]) a = [off, m[7] ? toks.ide : toks.str, jsonParse(m[8])];
+	        else if (m[9]) te("ujs", str);
+	        else if (m[10]) a = [off, toks.ide, m[10].replace(/\\([^\r\n\f0-9a-fA-F])/g,"$1")];
+	        return a;
+	    }
+
+	    // THE EXPRESSION SUBSYSTEM
+
+	    var exprPat = new RegExp(
+	            // skip and don't capture leading whitespace
+	            "^\\s*(?:" +
+	            // (1) simple vals
+	            "(true|false|null)|" + 
+	            // (2) numbers
+	            "(-?\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?)|" +
+	            // (3) strings
+	            "(\"(?:[^\\]|\\[^\"])*\")|" +
+	            // (4) the 'x' value placeholder
+	            "(x)|" +
+	            // (5) binops
+	            "(&&|\\|\\||[\\$\\^<>!\\*]=|[=+\\-*/%<>])|" +
+	            // (6) parens
+	            "([\\(\\)])" +
+	            ")"
+	    );
+
+	    function is(o, t) { return typeof o === t; }
+	    var operators = {
+	        '*':  [ 9, function(lhs, rhs) { return lhs * rhs; } ],
+	        '/':  [ 9, function(lhs, rhs) { return lhs / rhs; } ],
+	        '%':  [ 9, function(lhs, rhs) { return lhs % rhs; } ],
+	        '+':  [ 7, function(lhs, rhs) { return lhs + rhs; } ],
+	        '-':  [ 7, function(lhs, rhs) { return lhs - rhs; } ],
+	        '<=': [ 5, function(lhs, rhs) { return is(lhs, 'number') && is(rhs, 'number') && lhs <= rhs; } ],
+	        '>=': [ 5, function(lhs, rhs) { return is(lhs, 'number') && is(rhs, 'number') && lhs >= rhs; } ],
+	        '$=': [ 5, function(lhs, rhs) { return is(lhs, 'string') && is(rhs, 'string') && lhs.lastIndexOf(rhs) === lhs.length - rhs.length; } ],
+	        '^=': [ 5, function(lhs, rhs) { return is(lhs, 'string') && is(rhs, 'string') && lhs.indexOf(rhs) === 0; } ],
+	        '*=': [ 5, function(lhs, rhs) { return is(lhs, 'string') && is(rhs, 'string') && lhs.indexOf(rhs) !== -1; } ],
+	        '>':  [ 5, function(lhs, rhs) { return is(lhs, 'number') && is(rhs, 'number') && lhs > rhs; } ],
+	        '<':  [ 5, function(lhs, rhs) { return is(lhs, 'number') && is(rhs, 'number') && lhs < rhs; } ],
+	        '=':  [ 3, function(lhs, rhs) { return lhs === rhs; } ],
+	        '!=': [ 3, function(lhs, rhs) { return lhs !== rhs; } ],
+	        '&&': [ 2, function(lhs, rhs) { return lhs && rhs; } ],
+	        '||': [ 1, function(lhs, rhs) { return lhs || rhs; } ]
+	    };
+
+	    function exprLex(str, off) {
+	        var v, m = exprPat.exec(str.substr(off));
+	        if (m) {
+	            off += m[0].length;
+	            v = m[1] || m[2] || m[3] || m[5] || m[6];
+	            if (m[1] || m[2] || m[3]) return [off, 0, jsonParse(v)];
+	            else if (m[4]) return [off, 0, undefined];
+	            return [off, v];
+	        }
+	    }
+
+	    function exprParse2(str, off) {
+	        if (!off) off = 0;
+	        // first we expect a value or a '('
+	        var l = exprLex(str, off),
+	            lhs;
+	        if (l && l[1] === '(') {
+	            lhs = exprParse2(str, l[0]);
+	            var p = exprLex(str, lhs[0]);
+	            if (!p || p[1] !== ')') te('epex', str);
+	            off = p[0];
+	            lhs = [ '(', lhs[1] ];
+	        } else if (!l || (l[1] && l[1] != 'x')) {
+	            te("ee", str + " - " + ( l[1] && l[1] ));
+	        } else {
+	            lhs = ((l[1] === 'x') ? undefined : l[2]);
+	            off = l[0];
+	        }
+
+	        // now we expect a binary operator or a ')'
+	        var op = exprLex(str, off);
+	        if (!op || op[1] == ')') return [off, lhs];
+	        else if (op[1] == 'x' || !op[1]) {
+	            te('bop', str + " - " + ( op[1] && op[1] ));
+	        }
+
+	        // tail recursion to fetch the rhs expression
+	        var rhs = exprParse2(str, op[0]);
+	        off = rhs[0];
+	        rhs = rhs[1];
+
+	        // and now precedence!  how shall we put everything together?
+	        var v;
+	        if (typeof rhs !== 'object' || rhs[0] === '(' || operators[op[1]][0] < operators[rhs[1]][0] ) {
+	            v = [lhs, op[1], rhs];
+	        }
+	        else {
+	            v = rhs;
+	            while (typeof rhs[0] === 'object' && rhs[0][0] != '(' && operators[op[1]][0] >= operators[rhs[0][1]][0]) {
+	                rhs = rhs[0];
+	            }
+	            rhs[0] = [lhs, op[1], rhs[0]];
+	        }
+	        return [off, v];
+	    }
+
+	    function exprParse(str, off) {
+	        function deparen(v) {
+	            if (typeof v !== 'object' || v === null) return v;
+	            else if (v[0] === '(') return deparen(v[1]);
+	            else return [deparen(v[0]), v[1], deparen(v[2])];
+	        }
+	        var e = exprParse2(str, off ? off : 0);
+	        return [e[0], deparen(e[1])];
+	    }
+
+	    function exprEval(expr, x) {
+	        if (expr === undefined) return x;
+	        else if (expr === null || typeof expr !== 'object') {
+	            return expr;
+	        }
+	        var lhs = exprEval(expr[0], x),
+	            rhs = exprEval(expr[2], x);
+	        return operators[expr[1]][1](lhs, rhs);
+	    }
+
+	    // THE PARSER
+
+	    function parse(str, off, nested, hints) {
+	        if (!nested) hints = {};
+
+	        var a = [], am, readParen;
+	        if (!off) off = 0; 
+
+	        while (true) {
+	            var s = parse_selector(str, off, hints);
+	            a.push(s[1]);
+	            s = lex(str, off = s[0]);
+	            if (s && s[1] === " ") s = lex(str, off = s[0]);
+	            if (!s) break;
+	            // now we've parsed a selector, and have something else...
+	            if (s[1] === ">" || s[1] === "~") {
+	                if (s[1] === "~") hints.usesSiblingOp = true;
+	                a.push(s[1]);
+	                off = s[0];
+	            } else if (s[1] === ",") {
+	                if (am === undefined) am = [ ",", a ];
+	                else am.push(a);
+	                a = [];
+	                off = s[0];
+	            } else if (s[1] === ")") {
+	                if (!nested) te("ucp", s[1]);
+	                readParen = 1;
+	                off = s[0];
+	                break;
+	            }
+	        }
+	        if (nested && !readParen) te("mcp", str);
+	        if (am) am.push(a);
+	        var rv;
+	        if (!nested && hints.usesSiblingOp) {
+	            rv = normalize(am ? am : a);
+	        } else {
+	            rv = am ? am : a;
+	        }
+	        return [off, rv];
+	    }
+
+	    function normalizeOne(sel) {
+	        var sels = [], s;
+	        for (var i = 0; i < sel.length; i++) {
+	            if (sel[i] === '~') {
+	                // `A ~ B` maps to `:has(:root > A) > B`
+	                // `Z A ~ B` maps to `Z :has(:root > A) > B, Z:has(:root > A) > B`
+	                // This first clause, takes care of the first case, and the first half of the latter case.
+	                if (i < 2 || sel[i-2] != '>') {
+	                    s = sel.slice(0,i-1);
+	                    s = s.concat([{has:[[{pc: ":root"}, ">", sel[i-1]]]}, ">"]);
+	                    s = s.concat(sel.slice(i+1));
+	                    sels.push(s);
+	                }
+	                // here we take care of the second half of above:
+	                // (`Z A ~ B` maps to `Z :has(:root > A) > B, Z :has(:root > A) > B`)
+	                // and a new case:
+	                // Z > A ~ B maps to Z:has(:root > A) > B
+	                if (i > 1) {
+	                    var at = sel[i-2] === '>' ? i-3 : i-2;
+	                    s = sel.slice(0,at);
+	                    var z = {};
+	                    for (var k in sel[at]) if (sel[at].hasOwnProperty(k)) z[k] = sel[at][k];
+	                    if (!z.has) z.has = [];
+	                    z.has.push([{pc: ":root"}, ">", sel[i-1]]);
+	                    s = s.concat(z, '>', sel.slice(i+1));
+	                    sels.push(s);
+	                }
+	                break;
+	            }
+	        }
+	        if (i == sel.length) return sel;
+	        return sels.length > 1 ? [','].concat(sels) : sels[0];
+	    }
+
+	    function normalize(sels) {
+	        if (sels[0] === ',') {
+	            var r = [","];
+	            for (var i = i; i < sels.length; i++) {
+	                var s = normalizeOne(s[i]);
+	                r = r.concat(s[0] === "," ? s.slice(1) : s);
+	            }
+	            return r;
+	        } else {
+	            return normalizeOne(sels);
+	        }
+	    }
+
+	    function parse_selector(str, off, hints) {
+	        var soff = off;
+	        var s = { };
+	        var l = lex(str, off);
+	        // skip space
+	        if (l && l[1] === " ") { soff = off = l[0]; l = lex(str, off); }
+	        if (l && l[1] === toks.typ) {
+	            s.type = l[2];
+	            l = lex(str, (off = l[0]));
+	        } else if (l && l[1] === "*") {
+	            // don't bother representing the universal sel, '*' in the
+	            // parse tree, cause it's the default
+	            l = lex(str, (off = l[0]));
+	        }
+
+	        // now support either an id or a pc
+	        while (true) {
+	            if (l === undefined) {
+	                break;
+	            } else if (l[1] === toks.ide) {
+	                if (s.id) te("nmi", l[1]);
+	                s.id = l[2];
+	            } else if (l[1] === toks.psc) {
+	                if (s.pc || s.pf) te("mpc", l[1]);
+	                // collapse first-child and last-child into nth-child expressions
+	                if (l[2] === ":first-child") {
+	                    s.pf = ":nth-child";
+	                    s.a = 0;
+	                    s.b = 1;
+	                } else if (l[2] === ":last-child") {
+	                    s.pf = ":nth-last-child";
+	                    s.a = 0;
+	                    s.b = 1;
+	                } else {
+	                    s.pc = l[2];
+	                }
+	            } else if (l[1] === toks.psf) {
+	                if (l[2] === ":val" || l[2] === ":contains") {
+	                    s.expr = [ undefined, l[2] === ":val" ? "=" : "*=", undefined];
+	                    // any amount of whitespace, followed by paren, string, paren
+	                    l = lex(str, (off = l[0]));
+	                    if (l && l[1] === " ") l = lex(str, off = l[0]);
+	                    if (!l || l[1] !== "(") te("pex", str);
+	                    l = lex(str, (off = l[0]));
+	                    if (l && l[1] === " ") l = lex(str, off = l[0]);
+	                    if (!l || l[1] !== toks.str) te("sex", str);
+	                    s.expr[2] = l[2];
+	                    l = lex(str, (off = l[0]));
+	                    if (l && l[1] === " ") l = lex(str, off = l[0]);
+	                    if (!l || l[1] !== ")") te("epex", str);
+	                } else if (l[2] === ":has") {
+	                    // any amount of whitespace, followed by paren
+	                    l = lex(str, (off = l[0]));
+	                    if (l && l[1] === " ") l = lex(str, off = l[0]);
+	                    if (!l || l[1] !== "(") te("pex", str);
+	                    var h = parse(str, l[0], true);
+	                    l[0] = h[0];
+	                    if (!s.has) s.has = [];
+	                    s.has.push(h[1]);
+	                } else if (l[2] === ":expr") {
+	                    if (s.expr) te("mexp", str);
+	                    var e = exprParse(str, l[0]);
+	                    l[0] = e[0];
+	                    s.expr = e[1];
+	                } else {
+	                    if (s.pc || s.pf ) te("mpc", str);
+	                    s.pf = l[2];
+	                    var m = nthPat.exec(str.substr(l[0]));
+	                    if (!m) te("mepf", str);
+	                    if (m[5]) {
+	                        s.a = 2;
+	                        s.b = (m[5] === "odd") ? 1 : 0;
+	                    } else if (m[6]) {
+	                        s.a = 0;
+	                        s.b = parseInt(m[6], 10);
+	                    } else {
+	                        s.a = parseInt((m[1] ? m[1] : "+") + (m[2] ? m[2] : "1"),10);
+	                        s.b = m[3] ? parseInt(m[3] + m[4],10) : 0;
+	                    }
+	                    l[0] += m[0].length;
+	                }
+	            } else {
+	                break;
+	            }
+	            l = lex(str, (off = l[0]));
+	        }
+
+	        // now if we didn't actually parse anything it's an error
+	        if (soff === off) te("se", str);
+
+	        return [off, s];
+	    }
+
+	    // THE EVALUATOR
+
+	    function isArray(o) {
+	        return Array.isArray ? Array.isArray(o) : 
+	          toString.call(o) === "[object Array]";
+	    }
+
+	    function mytypeof(o) {
+	        if (o === null) return "null";
+	        var to = typeof o;
+	        if (to === "object" && isArray(o)) to = "array";
+	        return to;
+	    }
+
+	    function mn(node, sel, id, num, tot) {
+	        var sels = [];
+	        var cs = (sel[0] === ">") ? sel[1] : sel[0];
+	        var m = true, mod;
+	        if (cs.type) m = m && (cs.type === mytypeof(node));
+	        if (cs.id)   m = m && (cs.id === id);
+	        if (m && cs.pf) {
+	            if (cs.pf === ":nth-last-child") num = tot - num;
+	            else num++;
+	            if (cs.a === 0) {
+	                m = cs.b === num;
+	            } else {
+	                mod = ((num - cs.b) % cs.a);
+
+	                m = (!mod && ((num*cs.a + cs.b) >= 0));
+	            }
+	        }
+	        if (m && cs.has) {
+	            // perhaps we should augment forEach to handle a return value
+	            // that indicates "client cancels traversal"?
+	            var bail = function() { throw 42; };
+	            for (var i = 0; i < cs.has.length; i++) {
+	                try {
+	                    forEach(cs.has[i], node, bail);
+	                } catch (e) {
+	                    if (e === 42) continue;
+	                }
+	                m = false;
+	                break;
+	            }
+	        }
+	        if (m && cs.expr) {
+	            m = exprEval(cs.expr, node);
+	        }
+	        // should we repeat this selector for descendants?
+	        if (sel[0] !== ">" && sel[0].pc !== ":root") sels.push(sel);
+
+	        if (m) {
+	            // is there a fragment that we should pass down?
+	            if (sel[0] === ">") { if (sel.length > 2) { m = false; sels.push(sel.slice(2)); } }
+	            else if (sel.length > 1) { m = false; sels.push(sel.slice(1)); }
+	        }
+
+	        return [m, sels];
+	    }
+
+	    function forEach(sel, obj, fun, id, num, tot) {
+	        var a = (sel[0] === ",") ? sel.slice(1) : [sel],
+	        a0 = [],
+	        call = false,
+	        i = 0, j = 0, k, x;
+	        for (i = 0; i < a.length; i++) {
+	            x = mn(obj, a[i], id, num, tot);
+	            if (x[0]) {
+	                call = true;
+	            }
+	            for (j = 0; j < x[1].length; j++) {
+	                a0.push(x[1][j]);
+	            }
+	        }
+	        if (a0.length && typeof obj === "object") {
+	            if (a0.length >= 1) {
+	                a0.unshift(",");
+	            }
+	            if (isArray(obj)) {
+	                for (i = 0; i < obj.length; i++) {
+	                    forEach(a0, obj[i], fun, undefined, i, obj.length);
+	                }
+	            } else {
+	                for (k in obj) {
+	                    if (obj.hasOwnProperty(k)) {
+	                        forEach(a0, obj[k], fun, k);
+	                    }
+	                }
+	            }
+	        }
+	        if (call && fun) {
+	            fun(obj);
+	        }
+	    }
+
+	    function match(sel, obj) {
+	        var a = [];
+	        forEach(sel, obj, function(x) {
+	            a.push(x);
+	        });
+	        return a;
+	    }
+
+	    function compile(sel) {
+	        return {
+	            sel: parse(sel)[1],
+	            match: function(obj){
+	                return match(this.sel, obj);
+	            },
+	            forEach: function(obj, fun) {
+	                return forEach(this.sel, obj, fun);
+	            }
+	        };
+	    }
+
+	    exports._lex = lex;
+	    exports._parse = parse;
+	    exports.match = function (sel, obj) {
+	        return compile(sel).match(obj);
+	    };
+	    exports.forEach = function(sel, obj, fun) {
+	        return compile(sel).forEach(obj, fun);
+	    };
+	    exports.compile = compile;
+	})(typeof exports === "undefined" ? (window.JSONSelect = {}) : exports);
+	});
+	
+	return require('js-select');
+})();
\ No newline at end of file
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/test/runner.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/test/runner.js
new file mode 100644
index 0000000..9d90ac0
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/test/runner.js
@@ -0,0 +1,58 @@
+/* node runner for the JSONSelect conformance tests
+   https://github.com/lloyd/JSONSelectTests */
+
+var assert = require("assert"),
+    fs = require("fs"),
+    path = require("path"),
+    colors = require("colors"),
+    traverse = require("traverse")
+    select = require("../index");
+
+var options = require("nomnom").opts({
+   directory: {
+      abbr: 'd',
+      help: 'directory of tests to run',
+      metavar: 'PATH',
+      default: __dirname + "/level_1"
+   }
+}).parseArgs();
+
+var directory = options.directory,
+    files = fs.readdirSync(directory);
+
+var jsonFiles = files.filter(function(file) {
+   return path.extname(file) == ".json";
+});
+
+jsonFiles.forEach(function(file) {
+   var jsonName = file.replace(/\..*$/, ""),
+       json = JSON.parse(fs.readFileSync(path.join(directory, file), "utf-8"));
+   
+   var selFiles = files.filter(function(file) {
+      return file.indexOf(jsonName) == 0 && path.extname(file) == ".selector"
+   })
+   
+   selFiles.forEach(function(file) {
+      var test = file.replace(/\..*$/, "");
+      var selector = fs.readFileSync(path.join(directory, file), "utf-8");
+      var output = fs.readFileSync(path.join(directory, test + ".output"), "utf-8");
+      
+      var expected = JSON.parse(output).sort(sort);
+      var got = select(json, selector).nodes().sort(sort);
+      try {
+          assert.deepEqual(got, expected);
+      }
+      catch(AssertionError) {
+         console.log("\nfail".red + " " + test + "\ngot: ".blue + JSON.stringify(got)
+            + "\n\expected: ".blue + JSON.stringify(expected) + "\n")
+      };
+      console.log("pass".green + " " + test);
+   });
+});
+
+function sort(a, b) {
+   if (JSON.stringify(a) < JSON.stringify(b)) {
+      return -1;      
+   }
+   return 1;
+}
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/test/test.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/test/test.js
new file mode 100644
index 0000000..2a215da
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/test/test.js
@@ -0,0 +1,161 @@
+var assert = require("assert"),
+    fs = require("fs"),
+    traverse = require("traverse")
+    select = require("../index");
+
+var people = {
+   "george": {
+       age : 35,
+       movies: [{
+          name: "Repo Man",
+          stars: 5
+      }]
+   },
+   "mary": {
+       age: 15,
+       movies: [{
+           name: "Twilight",
+           stars: 3
+       },
+       {
+          name: "Trudy",
+          stars: 2
+       },
+       {
+          name: "The Fighter",
+          stars: 4
+       }]
+   },
+   "chris" : {
+      car: null,
+      male: true
+   }
+};
+
+var people2, obj;
+
+assert.deepEqual(select(people, "*").nodes(), [{"george":{"age":35,"movies":[{"name":"Repo Man","stars":5}]},"mary":{"age":15,"movies":[{"name":"Twilight","stars":3},{"name":"Trudy","stars":2},{"name":"The Fighter","stars":4}]},"chris":{"car":null,"male":true}},{"age":35,"movies":[{"name":"Repo Man","stars":5}]},35,[{"name":"Repo Man","stars":5}],{"name":"Repo Man","stars":5},"Repo Man",5,{"age":15,"movies":[{"name":"Twilight","stars":3},{"name":"Trudy","stars":2},{"name":"The Fighter","stars":4}]},15,[{"name":"Twilight","stars":3},{"name":"Trudy","stars":2},{"name":"The Fighter","stars":4}],{"name":"Twilight","stars":3},"Twilight",3,{"name":"Trudy","stars":2},"Trudy",2,{"name":"The Fighter","stars":4},"The Fighter",4,{"car":null,"male":true},null,true]);
+assert.deepEqual(select(people, ".george").nodes(), [{"age":35,"movies":[{"name":"Repo Man","stars":5}]}]);
+assert.deepEqual(select(people, ".george .age").nodes(), [35]);
+assert.deepEqual(select(people, ".george .name").nodes(), ["Repo Man"]);
+assert.deepEqual(select(people, ".george *").nodes(), [35,[{"name":"Repo Man","stars":5}],{"name":"Repo Man","stars":5},"Repo Man",5])
+
+assert.deepEqual(select(people, ".george > *").nodes(), [35,[{"name":"Repo Man","stars":5}]]);
+assert.deepEqual(select(people, ".george > .name").nodes(), []);
+
+assert.deepEqual(select(people, ":first-child").nodes(), [{"name":"Repo Man","stars":5},{"name":"Twilight","stars":3}]);
+assert.deepEqual(select(people, ":nth-child(1)").nodes(), select(people, ":first-child").nodes());
+assert.deepEqual(select(people, ":nth-child(2)").nodes(), [{"name":"Trudy","stars":2}]);
+assert.deepEqual(select(people, ":nth-child(odd)").nodes(), [{"name":"Repo Man","stars":5},{"name":"Twilight","stars":3},{"name":"The Fighter","stars":4}]);
+assert.deepEqual(select(people, ":nth-child(even)").nodes(), [{"name":"Trudy","stars":2}]);
+
+assert.deepEqual(select(people, ":nth-child(-n+1)").nodes(), select(people, ":first-child").nodes());
+assert.deepEqual(select(people, ":nth-child(-n+2)").nodes(), [{"name":"Repo Man","stars":5},{"name":"Twilight","stars":3},{"name":"Trudy","stars":2}]);
+assert.deepEqual(select(people, ":nth-child(n)").nodes(), [{"name":"Repo Man","stars":5},{"name":"Twilight","stars":3},{"name":"Trudy","stars":2},{"name":"The Fighter","stars":4}]);
+assert.deepEqual(select(people, ":nth-child(n-1)").nodes(), select(people, ":nth-child(n)").nodes());
+assert.deepEqual(select(people, ":nth-child(n-2)").nodes(), [{"name":"Trudy","stars":2},{"name":"The Fighter","stars":4}]);
+
+assert.deepEqual(select(people, ":last-child").nodes(), [{"name":"Repo Man","stars":5},{"name":"The Fighter","stars":4}]);
+assert.deepEqual(select(people, ":nth-last-child(1)").nodes(), select(people, ":last-child").nodes());
+assert.deepEqual(select(people, ":nth-last-child(2)").nodes(), [{"name":"Trudy","stars":2}]);
+assert.deepEqual(select(people, ":only-child").nodes(), [{"name":"Repo Man","stars":5}]);
+assert.deepEqual(select(people, ":root").nodes(),[{"george":{"age":35,"movies":[{"name":"Repo Man","stars":5}]},"mary":{"age":15,"movies":[{"name":"Twilight","stars":3},{"name":"Trudy","stars":2},{"name":"The Fighter","stars":4}]},"chris":{"car":null,"male":true}}])
+
+assert.deepEqual(select(people, "string").nodes(),["Repo Man","Twilight","Trudy","The Fighter"]);
+assert.deepEqual(select(people, "number").nodes(),[35,5,15,3,2,4]);
+assert.deepEqual(select(people, "boolean").nodes(),[true]);
+assert.deepEqual(select(people, "object").nodes(),[{"george":{"age":35,"movies":[{"name":"Repo Man","stars":5}]},"mary":{"age":15,"movies":[{"name":"Twilight","stars":3},{"name":"Trudy","stars":2},{"name":"The Fighter","stars":4}]},"chris":{"car":null,"male":true}},{"age":35,"movies":[{"name":"Repo Man","stars":5}]},{"name":"Repo Man","stars":5},{"age":15,"movies":[{"name":"Twilight","stars":3},{"name":"Trudy","stars":2},{"name":"The Fighter","stars":4}]},{"name":"Twilight","stars":3},{"name":"Trudy","stars":2},{"name":"The Fighter","stars":4},{"car":null,"male":true}]);
+assert.deepEqual(select(people, "array").nodes(),[[{"name":"Repo Man","stars":5}],[{"name":"Twilight","stars":3},{"name":"Trudy","stars":2},{"name":"The Fighter","stars":4}]]);
+assert.deepEqual(select(people, "null").nodes(),[null]);
+
+assert.deepEqual(select(people, "number, string, boolean").nodes(), [35,"Repo Man",5,15,"Twilight",3,"Trudy",2,"The Fighter",4,true])
+
+assert.deepEqual(select(people, ":has(.car) > .male").nodes(), [true]);
+assert.deepEqual(select(people, ".male ~ .car").nodes(), [null])
+
+assert.deepEqual(select(people, ':val("Twilight")').nodes(), ["Twilight"])
+assert.deepEqual(select(people, ':val("Twi")').nodes(), [])
+assert.deepEqual(select(people, ':contains("Twi")').nodes(), ["Twilight"])
+assert.deepEqual(select(people, ':contains("weif")').nodes(), [])
+
+// invalid
+assert.deepEqual(select(people, ".hmmm").nodes(), []);
+assert.throws(function() {
+   select(people, "afcjwiojwe9q28*C@!(# (!#R($R)))").nodes();
+});
+
+// update()
+people2 = traverse.clone(people);
+
+select(people2, ".age").update(function(age) {
+    return age - 5;
+})
+assert.deepEqual(select(people2, ".age").nodes(), [30, 10]);
+
+obj = select(people2, ".age").update(3)
+assert.deepEqual(select(people2, ".age").nodes(), [3, 3]);
+assert.deepEqual(obj, people2);
+
+// remove()
+people2 = traverse.clone(people);
+
+obj = select(people2, ".age").remove();
+assert.deepEqual(select(people2, ".age").nodes(), []);
+assert.deepEqual(obj, people2);
+
+// condense()
+people2 = traverse.clone(people);
+select(people2, ".george").condense();
+assert.deepEqual(people2, {"george": {age: 35, movies: [{name: "Repo Man", stars: 5}]}});
+
+people2 = traverse.clone(people);
+select(people2, ".hmmm").condense();
+assert.deepEqual(people2, {});
+
+people2 = traverse.clone(people);
+obj = select(people2, ".stars").condense();
+assert.deepEqual(people2, {"george": {movies: [{stars: 5}]}, "mary": {movies: [{stars: 3},{stars: 2},{stars: 4}]}});
+assert.deepEqual(obj, people2);
+
+// forEach()
+people2 = traverse.clone(people);
+
+obj = select(people2, ".age").forEach(function(age) {
+    this.update(age - 5);
+})
+assert.deepEqual(select(people2, ".age").nodes(), [30, 10]);
+assert.deepEqual(obj, people2);
+
+
+// this.matches()
+people2 = traverse.clone(people);
+select(people2).forEach(function(node) {
+   if (this.matches(".age")) {
+      this.update(node + 10);
+   }
+});
+assert.deepEqual(select(people2, ".age").nodes(), [45, 25])
+
+
+// bigger stuff
+var timeline = require("./timeline.js");
+
+console.time("select time");
+assert.equal(select(timeline, ".bug .id").nodes().length, 126);
+assert.equal(select(timeline, ".id").nodes().length, 141);
+assert.equal(select(timeline, ".comments .id").nodes().length, 115);
+assert.equal(select(timeline, ":nth-child(n-2)").nodes().length, 335);
+assert.equal(select(timeline, "object").nodes().length, 927);
+assert.equal(select(timeline, "*").nodes().length, 3281);
+console.timeEnd("select time")
+
+var sel = require("JSONSelect");
+
+console.time("JSONSelect time")
+assert.equal(sel.match(".bug .id", timeline).length, 126);
+assert.equal(sel.match(".id", timeline).length, 141);
+assert.equal(sel.match(".comments .id", timeline).length, 115);
+assert.equal(sel.match(":nth-child(n-2)", timeline).length, 335);
+assert.equal(sel.match("object", timeline).length, 927);
+assert.equal(sel.match("*", timeline).length, 3281);
+console.timeEnd("JSONSelect time")
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/test/timeline.js b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/test/timeline.js
new file mode 100644
index 0000000..59799a0
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/node_modules/js-select/test/timeline.js
@@ -0,0 +1 @@
+module.exports = [{"bug":{"history":[{"changes":[{"removed":"","added":"gmealer@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/gmealer@mozilla.com","name":"gmealer@mozilla.com"},"change_time":"2011-07-19T22:35:56Z"},{"changes":[{"removed":"NEW","added":"RESOLVED","field_name":"status"},{"removed":"","added":"WONTFIX","field_name":"resolution"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/fayearthur+bugs@gmail.com","name":"fayearthur+bugs@gmail.com"},"change_time":"2011-07-19T22:24:59Z"},{"changes":[{"removed":"","added":"fayearthur+bugs@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/fayearthur+bugs@gmail.com","name":"fayearthur+bugs@gmail.com"},"change_time":"2011-07-13T19:49:06Z"},{"changes":[{"removed":"","added":"hskupin@gmail.com, stomlinson@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-07-13T19:11:32Z"},{"changes":[{"removed":"","added":"ctalbert@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-07-13T18:43:17Z"}],"summary":"Adding isNumber, isFunction, isString, isArray and isObject to assert.js","last_change_time":"2011-07-19T22:35:56Z","comments":[{"is_private":false,"creator":{"real_name":"Geo Mealer [:geo]","name":"gmealer"},"text":"","id":5600392,"creation_time":"2011-07-19T22:35:56Z"},{"is_private":false,"creator":{"real_name":"Heather Arthur [:harth]","name":"fayearthur+bugs"},"text":"","id":5600359,"creation_time":"2011-07-19T22:24:59Z"},{"is_private":false,"creator":{"real_name":"Heather Arthur [:harth]","name":"fayearthur+bugs"},"text":"","id":5589849,"creation_time":"2011-07-13T20:24:38Z"},{"is_private":false,"creator":{"real_name":"Shane Tomlinson","name":"stomlinson"},"text":"","id":5589791,"creation_time":"2011-07-13T19:59:43Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5589782,"creation_time":"2011-07-13T19:56:44Z"},{"is_private":false,"creator":{"real_name":"Heather Arthur [:harth]","name":"fayearthur+bugs"},"text":"","id":5589758,"creation_time":"2011-07-13T19:49:06Z"},{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":5589591,"creation_time":"2011-07-13T18:42:07Z"}],"id":671367},"events":[{"time":"2011-07-19T22:35:56Z","changeset":{"changes":[{"removed":"","added":"gmealer@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/gmealer@mozilla.com","name":"gmealer@mozilla.com"},"change_time":"2011-07-19T22:35:56Z"}},{"time":"2011-07-19T22:35:56Z","comment":{"is_private":false,"creator":{"real_name":"Geo Mealer [:geo]","name":"gmealer"},"text":"","id":5600392,"creation_time":"2011-07-19T22:35:56Z"}},{"time":"2011-07-19T22:24:59Z","changeset":{"changes":[{"removed":"NEW","added":"RESOLVED","field_name":"status"},{"removed":"","added":"WONTFIX","field_name":"resolution"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/fayearthur+bugs@gmail.com","name":"fayearthur+bugs@gmail.com"},"change_time":"2011-07-19T22:24:59Z"}},{"time":"2011-07-19T22:24:59Z","comment":{"is_private":false,"creator":{"real_name":"Heather Arthur [:harth]","name":"fayearthur+bugs"},"text":"","id":5600359,"creation_time":"2011-07-19T22:24:59Z"}}]},{"bug":{"history":[{"changes":[{"removed":"","added":"ctalbert@mozilla.com, fayearthur+bugs@gmail.com, jhammel@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-07-19T20:22:11Z"}],"summary":"mozmill \"--profile\" option not working with relative path","last_change_time":"2011-07-19T20:46:19Z","comments":[{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":5600020,"creation_time":"2011-07-19T20:46:19Z"},{"is_private":false,"creator":{"real_name":"Marc-Aurèle DARCHE","name":"mozdev"},"text":"","id":5599999,"creation_time":"2011-07-19T20:39:07Z"},{"is_private":false,"creator":{"real_name":"Marc-Aurèle DARCHE","name":"mozdev"},"text":"","id":5599848,"creation_time":"2011-07-19T19:29:35Z"}],"id":672605},"events":[{"time":"2011-07-19T20:46:19Z","comment":{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":5600020,"creation_time":"2011-07-19T20:46:19Z"}},{"time":"2011-07-19T20:39:07Z","comment":{"is_private":false,"creator":{"real_name":"Marc-Aurèle DARCHE","name":"mozdev"},"text":"","id":5599999,"creation_time":"2011-07-19T20:39:07Z"}},{"time":"2011-07-19T20:22:11Z","changeset":{"changes":[{"removed":"","added":"ctalbert@mozilla.com, fayearthur+bugs@gmail.com, jhammel@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-07-19T20:22:11Z"}},{"time":"2011-07-19T19:29:35Z","comment":{"is_private":false,"creator":{"real_name":"Marc-Aurèle DARCHE","name":"mozdev"},"text":"","id":5599848,"creation_time":"2011-07-19T19:29:35Z"}}]},{"bug":{"history":[{"changes":[{"removed":"","added":"ehsan@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ehsan@mozilla.com","name":"ehsan@mozilla.com"},"change_time":"2011-07-19T20:37:46Z"},{"changes":[{"removed":"","added":"fayearthur+bugs@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2011-07-19T17:37:11Z"},{"changes":[{"removed":"","added":"ctalbert@mozilla.com, jhammel@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2011-07-19T17:35:58Z"},{"changes":[{"removed":"","added":"ted.mielczarek@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ted.mielczarek@gmail.com","name":"ted.mielczarek@gmail.com"},"change_time":"2011-07-18T12:00:25Z"},{"changes":[{"removed":"","added":"bmo@edmorley.co.uk","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/bmo@edmorley.co.uk","name":"bmo@edmorley.co.uk"},"change_time":"2011-07-17T11:20:51Z"},{"changes":[{"removed":"","added":"bzbarsky@mit.edu","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/bzbarsky@mit.edu","name":"bzbarsky@mit.edu"},"change_time":"2011-07-14T03:29:52Z"},{"changes":[{"removed":"","added":"dmandelin@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/dmandelin@mozilla.com","name":"dmandelin@mozilla.com"},"change_time":"2011-07-14T00:27:21Z"},{"changes":[{"removed":"","added":"kairo@kairo.at","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/kairo@kairo.at","name":"kairo@kairo.at"},"change_time":"2011-07-14T00:04:16Z"},{"changes":[{"removed":"","added":"gavin.sharp@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/gavin.sharp@gmail.com","name":"gavin.sharp@gmail.com"},"change_time":"2011-07-13T23:17:24Z"},{"changes":[{"removed":"","added":"anygregor@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/anygregor@gmail.com","name":"anygregor@gmail.com"},"change_time":"2011-07-13T19:57:34Z"},{"changes":[{"removed":"","added":"luke@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/luke@mozilla.com","name":"luke@mozilla.com"},"change_time":"2011-07-13T18:53:42Z"},{"changes":[{"removed":"","added":"sphink@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/sphink@gmail.com","name":"sphink@gmail.com"},"change_time":"2011-07-13T18:23:30Z"},{"changes":[{"removed":"","added":"davemgarrett@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/davemgarrett@gmail.com","name":"davemgarrett@gmail.com"},"change_time":"2011-07-13T18:21:27Z"},{"changes":[{"removed":"","added":"khuey@kylehuey.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/khuey@kylehuey.com","name":"khuey@kylehuey.com"},"change_time":"2011-07-13T18:21:08Z"}],"summary":"Split chrome into multiple compartments for better accounting of JS memory used by chrome code (and add-ons)","last_change_time":"2011-07-19T20:37:46Z","comments":[{"is_private":false,"creator":{"real_name":"Ehsan Akhgari [:ehsan]","name":"ehsan"},"text":"","id":5599990,"creation_time":"2011-07-19T20:37:46Z"},{"is_private":false,"creator":{"real_name":"Gregor Wagner","name":"anygregor"},"text":"","id":5589947,"creation_time":"2011-07-13T21:04:28Z"},{"is_private":false,"creator":{"real_name":"Andreas Gal :gal","name":"gal"},"text":"","id":5589879,"creation_time":"2011-07-13T20:39:32Z"},{"is_private":false,"creator":{"real_name":"Gregor Wagner","name":"anygregor"},"text":"","id":5589812,"creation_time":"2011-07-13T20:07:18Z"},{"is_private":false,"creator":{"real_name":"Luke Wagner [:luke]","name":"luke"},"text":"","id":5589801,"creation_time":"2011-07-13T20:03:36Z"},{"is_private":false,"creator":{"real_name":"Gregor Wagner","name":"anygregor"},"text":"","id":5589787,"creation_time":"2011-07-13T19:57:34Z"},{"is_private":false,"creator":{"real_name":"Ehsan Akhgari [:ehsan]","name":"ehsan"},"text":"","id":5589485,"creation_time":"2011-07-13T18:15:17Z"}],"id":671352},"events":[{"time":"2011-07-19T20:37:46Z","changeset":{"changes":[{"removed":"","added":"ehsan@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ehsan@mozilla.com","name":"ehsan@mozilla.com"},"change_time":"2011-07-19T20:37:46Z"}},{"time":"2011-07-19T20:37:46Z","comment":{"is_private":false,"creator":{"real_name":"Ehsan Akhgari [:ehsan]","name":"ehsan"},"text":"","id":5599990,"creation_time":"2011-07-19T20:37:46Z"}},{"time":"2011-07-19T17:37:11Z","changeset":{"changes":[{"removed":"","added":"fayearthur+bugs@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2011-07-19T17:37:11Z"}},{"time":"2011-07-19T17:35:58Z","changeset":{"changes":[{"removed":"","added":"ctalbert@mozilla.com, jhammel@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2011-07-19T17:35:58Z"}},{"time":"2011-07-18T12:00:25Z","changeset":{"changes":[{"removed":"","added":"ted.mielczarek@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ted.mielczarek@gmail.com","name":"ted.mielczarek@gmail.com"},"change_time":"2011-07-18T12:00:25Z"}}]},{"bug":{"history":[{"changes":[{"attachment_id":"546836","removed":"text/x-python","added":"text/plain","field_name":"attachment.content_type"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-07-19T18:28:25Z"},{"changes":[{"removed":"","added":"ctalbert@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2011-07-19T17:58:20Z"}],"summary":"mozprofile should set permissions for profiles","last_change_time":"2011-07-19T18:28:25Z","comments":[{"is_private":false,"creator":{"real_name":"Joel Maher (:jmaher)","name":"jmaher"},"text":"","id":5599602,"creation_time":"2011-07-19T18:15:26Z"},{"is_private":false,"creator":{"real_name":"Joel Maher (:jmaher)","name":"jmaher"},"text":"","id":5599285,"creation_time":"2011-07-19T16:21:34Z"},{"is_private":false,"creator":{"real_name":"Joel Maher (:jmaher)","name":"jmaher"},"text":"","id":5554367,"creation_time":"2011-06-24T17:41:38Z"},{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":5552758,"creation_time":"2011-06-23T23:26:21Z"}],"id":666791},"events":[{"time":"2011-07-19T18:28:25Z","changeset":{"changes":[{"attachment_id":"546836","removed":"text/x-python","added":"text/plain","field_name":"attachment.content_type"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-07-19T18:28:25Z"}},{"time":"2011-07-19T18:15:26Z","comment":{"is_private":false,"creator":{"real_name":"Joel Maher (:jmaher)","name":"jmaher"},"text":"","id":5599602,"creation_time":"2011-07-19T18:15:26Z"}},{"time":"2011-07-19T17:58:20Z","changeset":{"changes":[{"removed":"","added":"ctalbert@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2011-07-19T17:58:20Z"}},{"time":"2011-07-19T16:21:34Z","comment":{"is_private":false,"creator":{"real_name":"Joel Maher (:jmaher)","name":"jmaher"},"text":"","id":5599285,"creation_time":"2011-07-19T16:21:34Z"}}]},{"bug":{"history":[{"changes":[{"removed":"NEW","added":"RESOLVED","field_name":"status"},{"removed":"","added":"FIXED","field_name":"resolution"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-07-19T17:57:44Z"},{"changes":[{"removed":"nobody@mozilla.org","added":"jhammel@mozilla.com","field_name":"assigned_to"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-07-19T15:39:01Z"},{"changes":[{"removed":"[mozmill-2.0?]","added":"[mozmill-2.0+]","field_name":"whiteboard"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-07-19T04:24:09Z"},{"changes":[{"attachment_id":"545960","removed":"review?(ctalbert@mozilla.com)","added":"review+","field_name":"flag"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2011-07-18T22:04:12Z"},{"changes":[{"attachment_id":"545960","removed":"","added":"review?(ctalbert@mozilla.com)","field_name":"flag"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-07-14T18:16:53Z"},{"changes":[{"removed":"","added":"ctalbert@mozilla.com, fayearthur+bugs@gmail.com","field_name":"cc"},{"removed":"","added":"[mozmill-2.0?]","field_name":"whiteboard"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-07-13T21:37:01Z"}],"summary":"mutt processhandler and mozprocess processhandler: to merge?","last_change_time":"2011-07-19T17:57:44Z","comments":[{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":5599538,"creation_time":"2011-07-19T17:57:44Z"},{"is_private":false,"creator":{"real_name":"Clint Talbert ( :ctalbert )","name":"ctalbert"},"text":"","id":5597782,"creation_time":"2011-07-18T22:04:12Z"},{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":5591772,"creation_time":"2011-07-14T18:16:53Z"},{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":5590023,"creation_time":"2011-07-13T21:35:42Z"}],"id":671420},"events":[{"time":"2011-07-19T17:57:44Z","changeset":{"changes":[{"removed":"NEW","added":"RESOLVED","field_name":"status"},{"removed":"","added":"FIXED","field_name":"resolution"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-07-19T17:57:44Z"}},{"time":"2011-07-19T17:57:44Z","comment":{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":5599538,"creation_time":"2011-07-19T17:57:44Z"}},{"time":"2011-07-19T15:39:01Z","changeset":{"changes":[{"removed":"nobody@mozilla.org","added":"jhammel@mozilla.com","field_name":"assigned_to"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-07-19T15:39:01Z"}},{"time":"2011-07-19T04:24:09Z","changeset":{"changes":[{"removed":"[mozmill-2.0?]","added":"[mozmill-2.0+]","field_name":"whiteboard"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-07-19T04:24:09Z"}},{"time":"2011-07-18T22:04:12Z","changeset":{"changes":[{"attachment_id":"545960","removed":"review?(ctalbert@mozilla.com)","added":"review+","field_name":"flag"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2011-07-18T22:04:12Z"}},{"time":"2011-07-18T22:04:12Z","comment":{"is_private":false,"creator":{"real_name":"Clint Talbert ( :ctalbert )","name":"ctalbert"},"text":"","id":5597782,"creation_time":"2011-07-18T22:04:12Z"}}]},{"bug":{"history":[{"changes":[{"attachment_id":"546171","removed":"review?(fayearthur+bugs@gmail.com)","added":"review+","field_name":"flag"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/fayearthur+bugs@gmail.com","name":"fayearthur+bugs@gmail.com"},"change_time":"2011-07-19T17:42:04Z"},{"changes":[{"attachment_id":"546171","removed":"","added":"review?(fayearthur+bugs@gmail.com), feedback?(halbersa@gmail.com)","field_name":"flag"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-07-15T16:21:21Z"},{"changes":[{"attachment_id":"545775","removed":"review?(fayearthur+bugs@gmail.com)","added":"review+","field_name":"flag"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/fayearthur+bugs@gmail.com","name":"fayearthur+bugs@gmail.com"},"change_time":"2011-07-14T19:40:03Z"},{"changes":[{"attachment_id":"545771","removed":"0","added":"1","field_name":"attachment.is_obsolete"},{"attachment_id":"545771","removed":"review?(fayearthur+bugs@gmail.com)","added":"","field_name":"flag"},{"attachment_id":"545775","removed":"","added":"review?(fayearthur+bugs@gmail.com)","field_name":"flag"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-07-13T23:19:12Z"},{"changes":[{"attachment_id":"545771","removed":"","added":"review?(fayearthur+bugs@gmail.com)","field_name":"flag"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-07-13T23:14:09Z"},{"changes":[{"attachment_id":"535258","removed":"0","added":"1","field_name":"attachment.is_obsolete"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-07-13T23:12:37Z"},{"changes":[{"removed":"","added":"661408","field_name":"blocks"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-06-28T23:56:05Z"},{"changes":[{"attachment_id":"535258","removed":"feedback?(hskupin@gmail.com)","added":"feedback-","field_name":"flag"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-05-26T07:48:02Z"},{"changes":[{"attachment_id":"535258","removed":"","added":"feedback?(hskupin@gmail.com)","field_name":"flag"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2011-05-26T02:57:15Z"},{"changes":[{"removed":"","added":"halbersa@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2011-05-26T02:48:33Z"},{"changes":[{"removed":"waitForPageLoad() fails if the document owner is not a tab but an HTMLDocument","added":"waitForPageLoad() fails if the document owner is not a tab but an HTMLDocument (add support for iframes)","field_name":"summary"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-05-25T23:56:38Z"},{"changes":[{"attachment_id":"534720","removed":"review?(ctalbert@mozilla.com)","added":"review+","field_name":"flag"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2011-05-25T23:50:33Z"},{"changes":[{"removed":"","added":"fayearthur+bugs@gmail.com","field_name":"cc"},{"removed":"[mozmill-1.5.4+]","added":"[mozmill-1.5.4+][mozmill-2.0+]","field_name":"whiteboard"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-05-25T12:08:34Z"},{"changes":[{"removed":"","added":"ctalbert@mozilla.com","field_name":"cc"},{"removed":"[mozmill-1.5.4?]","added":"[mozmill-1.5.4+]","field_name":"whiteboard"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2011-05-24T21:25:21Z"},{"changes":[{"attachment_id":"534714","removed":"0","added":"1","field_name":"attachment.is_obsolete"},{"attachment_id":"534714","removed":"review?(ctalbert@mozilla.com)","added":"","field_name":"flag"},{"attachment_id":"534720","removed":"","added":"review?(ctalbert@mozilla.com)","field_name":"flag"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-05-24T11:15:13Z"},{"changes":[{"attachment_id":"534445","removed":"0","added":"1","field_name":"attachment.is_obsolete"},{"attachment_id":"534714","removed":"","added":"review?(ctalbert@mozilla.com)","field_name":"flag"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-05-24T10:59:06Z"},{"changes":[{"removed":"waitForPageLoad() fails for Discovery Pane with 'Specified tab hasn't been found.'","added":"waitForPageLoad() fails if the document owner is not a tab but an HTMLDocument","field_name":"summary"},{"removed":"","added":"in-testsuite?","field_name":"flag"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-05-24T10:53:16Z"},{"changes":[{"removed":"waitForPageLoad() fails for iFrames with 'Specified tab hasn't been found.'","added":"waitForPageLoad() fails for Discovery Pane with 'Specified tab hasn't been found.'","field_name":"summary"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-05-24T00:22:33Z"},{"changes":[{"removed":"","added":"[mozmill-1.5.4?]","field_name":"whiteboard"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-05-23T21:33:23Z"},{"changes":[{"removed":"","added":"regression","field_name":"keywords"},{"removed":"NEW","added":"ASSIGNED","field_name":"status"},{"removed":"x86_64","added":"All","field_name":"platform"},{"removed":"","added":"604878","field_name":"blocks"},{"removed":"nobody@mozilla.org","added":"hskupin@gmail.com","field_name":"assigned_to"},{"removed":"waitForPageLoad() does not handle iframes","added":"waitForPageLoad() fails for iFrames with 'Specified tab hasn't been found.'","field_name":"summary"},{"removed":"Linux","added":"All","field_name":"op_sys"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-05-23T15:59:53Z"},{"changes":[{"removed":"","added":"alex.lakatos@softvision.ro, hskupin@gmail.com, vlad.maniac@softvision.ro","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/vlad.maniac@softvision.ro","name":"vlad.maniac@softvision.ro"},"change_time":"2011-05-23T15:24:49Z"}],"summary":"waitForPageLoad() fails if the document owner is not a tab but an HTMLDocument (add support for iframes)","last_change_time":"2011-07-19T17:42:04Z","comments":[{"is_private":false,"creator":{"real_name":"Heather Arthur [:harth]","name":"fayearthur+bugs"},"text":"","id":5599498,"creation_time":"2011-07-19T17:42:04Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5593624,"creation_time":"2011-07-15T16:21:21Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5592472,"creation_time":"2011-07-14T22:38:58Z"},{"is_private":false,"creator":{"real_name":"Heather Arthur [:harth]","name":"fayearthur+bugs"},"text":"","id":5591961,"creation_time":"2011-07-14T19:40:03Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5590242,"creation_time":"2011-07-13T23:19:12Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5590234,"creation_time":"2011-07-13T23:14:09Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5590231,"creation_time":"2011-07-13T23:12:37Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5494401,"creation_time":"2011-05-26T07:48:02Z"},{"is_private":false,"creator":{"real_name":"Clint Talbert ( :ctalbert )","name":"ctalbert"},"text":"","id":5494130,"creation_time":"2011-05-26T02:58:56Z"},{"is_private":false,"creator":{"real_name":"Clint Talbert ( :ctalbert )","name":"ctalbert"},"text":"","id":5494127,"creation_time":"2011-05-26T02:57:15Z"},{"is_private":false,"creator":{"real_name":"Clint Talbert ( :ctalbert )","name":"ctalbert"},"text":"","id":5493838,"creation_time":"2011-05-25T23:50:33Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5491980,"creation_time":"2011-05-25T12:08:34Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5488829,"creation_time":"2011-05-24T11:18:12Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5488828,"creation_time":"2011-05-24T11:15:13Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5488811,"creation_time":"2011-05-24T10:59:06Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5488807,"creation_time":"2011-05-24T10:53:16Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5488698,"creation_time":"2011-05-24T09:37:36Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5488122,"creation_time":"2011-05-24T00:22:33Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5487989,"creation_time":"2011-05-23T23:17:41Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5487883,"creation_time":"2011-05-23T22:39:31Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5487655,"creation_time":"2011-05-23T21:33:23Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5486617,"creation_time":"2011-05-23T15:59:53Z"},{"is_private":false,"creator":{"real_name":"Maniac Vlad Florin (:vladmaniac)","name":"vlad.maniac"},"text":"","id":5486535,"creation_time":"2011-05-23T15:23:48Z"}],"id":659000},"events":[{"time":"2011-07-19T17:42:04Z","changeset":{"changes":[{"attachment_id":"546171","removed":"review?(fayearthur+bugs@gmail.com)","added":"review+","field_name":"flag"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/fayearthur+bugs@gmail.com","name":"fayearthur+bugs@gmail.com"},"change_time":"2011-07-19T17:42:04Z"}},{"time":"2011-07-19T17:42:04Z","comment":{"is_private":false,"creator":{"real_name":"Heather Arthur [:harth]","name":"fayearthur+bugs"},"text":"","id":5599498,"creation_time":"2011-07-19T17:42:04Z"}}]},{"bug":{"history":[],"summary":"QA Companion marked incompatible for Firefox versions higher than 6.*.","last_change_time":"2011-07-18T23:00:06Z","comments":[{"is_private":false,"creator":{"real_name":"Al Billings [:abillings]","name":"abillings"},"text":"","id":5597976,"creation_time":"2011-07-18T23:00:06Z"}],"id":672393},"events":[{"time":"2011-07-18T23:00:06Z","comment":{"is_private":false,"creator":{"real_name":"Al Billings [:abillings]","name":"abillings"},"text":"","id":5597976,"creation_time":"2011-07-18T23:00:06Z"}}]},{"bug":{"history":[{"changes":[{"removed":"","added":"khuey@kylehuey.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/khuey@kylehuey.com","name":"khuey@kylehuey.com"},"change_time":"2011-07-18T20:35:37Z"},{"changes":[{"removed":"","added":"kairo@kairo.at","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/kairo@kairo.at","name":"kairo@kairo.at"},"change_time":"2010-04-05T16:18:55Z"},{"changes":[{"removed":"","added":"mook.moz+mozbz@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/mook@songbirdnest.com","name":"mook@songbirdnest.com"},"change_time":"2010-03-16T21:34:23Z"},{"changes":[{"removed":"","added":"harthur@cmu.edu","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/fayearthur+bugs@gmail.com","name":"fayearthur+bugs@gmail.com"},"change_time":"2010-03-16T04:31:00Z"},{"changes":[{"removed":"","added":"ted.mielczarek@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ted.mielczarek@gmail.com","name":"ted.mielczarek@gmail.com"},"change_time":"2010-03-15T23:52:39Z"},{"changes":[{"removed":"","added":"552533","field_name":"depends_on"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/dwitte@gmail.com","name":"dwitte@gmail.com"},"change_time":"2010-03-15T22:02:20Z"},{"changes":[{"removed":"","added":"447581","field_name":"blocks"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/dietrich@mozilla.com","name":"dietrich@mozilla.com"},"change_time":"2010-03-02T23:57:25Z"},{"changes":[{"removed":"412531","added":"","field_name":"blocks"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/dietrich@mozilla.com","name":"dietrich@mozilla.com"},"change_time":"2009-12-11T02:49:08Z"},{"changes":[{"removed":"","added":"Pidgeot18@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/Pidgeot18@gmail.com","name":"Pidgeot18@gmail.com"},"change_time":"2009-10-26T14:47:22Z"},{"changes":[{"removed":"js-ctypes","added":"js-ctypes","field_name":"component"},{"removed":"Trunk","added":"unspecified","field_name":"version"},{"removed":"Other Applications","added":"Core","field_name":"product"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/mozillamarcia.knous@gmail.com","name":"mozillamarcia.knous@gmail.com"},"change_time":"2009-10-02T10:17:18Z"},{"changes":[{"removed":"","added":"jwalden+bmo@mit.edu","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jwalden+bmo@mit.edu","name":"jwalden+bmo@mit.edu"},"change_time":"2009-08-17T19:11:28Z"},{"changes":[{"removed":"","added":"ajvincent@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ajvincent@gmail.com","name":"ajvincent@gmail.com"},"change_time":"2009-08-13T00:28:01Z"},{"changes":[{"removed":"","added":"benjamin@smedbergs.us","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/benjamin@smedbergs.us","name":"benjamin@smedbergs.us"},"change_time":"2009-07-23T03:14:04Z"},{"changes":[{"removed":"","added":"highmind63@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/highmind63@gmail.com","name":"highmind63@gmail.com"},"change_time":"2009-07-23T01:02:51Z"},{"changes":[{"removed":"","added":"shaver@mozilla.org","field_name":"cc"},{"removed":"JavaScript Engine","added":"js-ctypes","field_name":"component"},{"removed":"general@js.bugs","added":"nobody@mozilla.org","field_name":"assigned_to"},{"removed":"Core","added":"Other Applications","field_name":"product"},{"removed":"add a pinvoke-like method to js","added":"Support C++ calling from JSctypes","field_name":"summary"},{"removed":"general@spidermonkey.bugs","added":"js-ctypes@otherapps.bugs","field_name":"qa_contact"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/shaver@mozilla.org","name":"shaver@mozilla.org"},"change_time":"2009-07-23T00:55:48Z"},{"changes":[{"removed":"","added":"ryanvm@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ryanvm@gmail.com","name":"ryanvm@gmail.com"},"change_time":"2009-07-23T00:34:18Z"},{"changes":[{"removed":"","added":"412531","field_name":"blocks"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/dietrich@mozilla.com","name":"dietrich@mozilla.com"},"change_time":"2009-07-23T00:06:41Z"},{"changes":[{"removed":"","added":"dvander@alliedmods.net","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/dvander@alliedmods.net","name":"dvander@alliedmods.net"},"change_time":"2009-07-23T00:03:46Z"},{"changes":[{"removed":"","added":"dietrich@mozilla.com, mark.finkle@gmail.com","field_name":"cc"},{"removed":"","added":"[ts][tsnap]","field_name":"whiteboard"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/dietrich@mozilla.com","name":"dietrich@mozilla.com"},"change_time":"2009-07-22T23:58:23Z"}],"summary":"Support C++ calling from JSctypes","last_change_time":"2011-07-18T20:35:37Z","comments":[{"is_private":false,"creator":{"real_name":"Ted Mielczarek [:ted, :luser]","name":"ted.mielczarek"},"text":"","id":4586274,"creation_time":"2010-03-15T23:52:39Z"},{"is_private":false,"creator":{"real_name":"Dan Witte (:dwitte)","name":"dwitte"},"text":"","id":4586028,"creation_time":"2010-03-15T22:21:21Z"},{"is_private":false,"creator":{"real_name":"Dan Witte (:dwitte)","name":"dwitte"},"text":"","id":4586006,"creation_time":"2010-03-15T22:11:11Z"},{"is_private":false,"creator":{"real_name":"Joshua Cranmer [:jcranmer]","name":"Pidgeot18"},"text":"","id":4364260,"creation_time":"2009-10-26T18:33:21Z"},{"is_private":false,"creator":{"real_name":"Dan Witte (:dwitte)","name":"dwitte"},"text":"","id":4364119,"creation_time":"2009-10-26T17:27:05Z"},{"is_private":false,"creator":{"real_name":"Joshua Cranmer [:jcranmer]","name":"Pidgeot18"},"text":"","id":4363794,"creation_time":"2009-10-26T14:47:22Z"},{"is_private":false,"creator":{"real_name":"Benjamin Smedberg [:bsmedberg]","name":"benjamin"},"text":"","id":4211892,"creation_time":"2009-07-23T03:14:04Z"},{"is_private":false,"creator":{"real_name":"Mike Shaver (:shaver)","name":"shaver"},"text":"","id":4211710,"creation_time":"2009-07-23T00:56:10Z"},{"is_private":false,"creator":{"real_name":"Taras Glek (:taras)","name":"tglek"},"text":"","id":4211707,"creation_time":"2009-07-23T00:54:06Z"},{"is_private":false,"creator":{"real_name":"Mike Shaver (:shaver)","name":"shaver"},"text":"","id":4211677,"creation_time":"2009-07-23T00:42:01Z"},{"is_private":false,"creator":{"real_name":"Taras Glek (:taras)","name":"tglek"},"text":"","id":4211672,"creation_time":"2009-07-23T00:40:10Z"},{"is_private":false,"creator":{"real_name":"Mike Shaver (:shaver)","name":"shaver"},"text":"","id":4211668,"creation_time":"2009-07-23T00:38:34Z"},{"is_private":false,"creator":{"real_name":"Taras Glek (:taras)","name":"tglek"},"text":"","id":4211665,"creation_time":"2009-07-23T00:36:58Z"},{"is_private":false,"creator":{"real_name":"Mike Shaver (:shaver)","name":"shaver"},"text":"","id":4211660,"creation_time":"2009-07-23T00:34:27Z"},{"is_private":false,"creator":{"real_name":"Taras Glek (:taras)","name":"tglek"},"text":"","id":4211624,"creation_time":"2009-07-23T00:15:38Z"},{"is_private":false,"creator":{"real_name":"Mike Shaver (:shaver)","name":"shaver"},"text":"","id":4211608,"creation_time":"2009-07-23T00:08:16Z"},{"is_private":false,"creator":{"real_name":"Taras Glek (:taras)","name":"tglek"},"text":"","id":4211586,"creation_time":"2009-07-22T23:57:14Z"}],"id":505907},"events":[{"time":"2011-07-18T20:35:37Z","changeset":{"changes":[{"removed":"","added":"khuey@kylehuey.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/khuey@kylehuey.com","name":"khuey@kylehuey.com"},"change_time":"2011-07-18T20:35:37Z"}}]},{"bug":{"history":[{"changes":[{"removed":"","added":"bmo@edmorley.co.uk","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/bmo@edmorley.co.uk","name":"bmo@edmorley.co.uk"},"change_time":"2011-07-10T14:30:39Z"},{"changes":[{"removed":"","added":"bear@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/bear@mozilla.com","name":"bear@mozilla.com"},"change_time":"2011-07-07T21:21:56Z"},{"changes":[{"removed":"","added":"ctalbert@mozilla.com, fayearthur+bugs@gmail.com, sliu@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2011-07-07T21:19:46Z"},{"changes":[{"removed":"","added":"armenzg@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/armenzg@mozilla.com","name":"armenzg@mozilla.com"},"change_time":"2011-07-07T17:04:41Z"}],"summary":"Publish Build Faster metrics","last_change_time":"2011-07-18T20:25:40Z","comments":[{"is_private":false,"creator":{"real_name":"Chris AtLee [:catlee]","name":"catlee"},"text":"","id":5597430,"creation_time":"2011-07-18T20:25:40Z"},{"is_private":false,"creator":{"real_name":"Sam Liu","name":"sliu"},"text":"","id":5597428,"creation_time":"2011-07-18T20:24:35Z"},{"is_private":false,"creator":{"real_name":"Chris AtLee [:catlee]","name":"catlee"},"text":"","id":5597393,"creation_time":"2011-07-18T20:10:45Z"},{"is_private":false,"creator":{"real_name":"Sam Liu","name":"sliu"},"text":"","id":5589839,"creation_time":"2011-07-13T20:20:49Z"},{"is_private":false,"creator":{"real_name":"Chris AtLee [:catlee]","name":"catlee"},"text":"","id":5582189,"creation_time":"2011-07-09T01:25:21Z"},{"is_private":false,"creator":{"real_name":"Sam Liu","name":"sliu"},"text":"","id":5581446,"creation_time":"2011-07-08T18:25:33Z"},{"is_private":false,"creator":{"real_name":"Armen Zambrano G. [:armenzg] - Release Engineer","name":"armenzg"},"text":"","id":5580728,"creation_time":"2011-07-08T12:36:01Z"},{"is_private":false,"creator":{"real_name":"Chris AtLee [:catlee]","name":"catlee"},"text":"","id":5580206,"creation_time":"2011-07-08T03:21:41Z"},{"is_private":false,"creator":{"real_name":"Sam Liu","name":"sliu"},"text":"","id":5579985,"creation_time":"2011-07-08T00:01:37Z"},{"is_private":false,"creator":{"real_name":"Chris AtLee [:catlee]","name":"catlee"},"text":"","id":5579980,"creation_time":"2011-07-07T23:57:48Z"},{"is_private":false,"creator":{"real_name":"Sam Liu","name":"sliu"},"text":"","id":5579965,"creation_time":"2011-07-07T23:51:02Z"},{"is_private":false,"creator":{"real_name":"Chris AtLee [:catlee]","name":"catlee"},"text":"","id":5579631,"creation_time":"2011-07-07T21:32:17Z"},{"is_private":false,"creator":{"real_name":"Mike Taylor [:bear]","name":"bear"},"text":"","id":5579612,"creation_time":"2011-07-07T21:21:56Z"},{"is_private":false,"creator":{"real_name":"Clint Talbert ( :ctalbert )","name":"ctalbert"},"text":"","id":5579605,"creation_time":"2011-07-07T21:19:46Z"},{"is_private":false,"creator":{"real_name":"Chris AtLee [:catlee]","name":"catlee"},"text":"","id":5578932,"creation_time":"2011-07-07T17:02:45Z"}],"id":669930},"events":[{"time":"2011-07-18T20:25:40Z","comment":{"is_private":false,"creator":{"real_name":"Chris AtLee [:catlee]","name":"catlee"},"text":"","id":5597430,"creation_time":"2011-07-18T20:25:40Z"}},{"time":"2011-07-18T20:24:35Z","comment":{"is_private":false,"creator":{"real_name":"Sam Liu","name":"sliu"},"text":"","id":5597428,"creation_time":"2011-07-18T20:24:35Z"}},{"time":"2011-07-18T20:10:45Z","comment":{"is_private":false,"creator":{"real_name":"Chris AtLee [:catlee]","name":"catlee"},"text":"","id":5597393,"creation_time":"2011-07-18T20:10:45Z"}}]},{"bug":{"history":[{"changes":[{"removed":"","added":"jwatt@jwatt.org","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jwatt@jwatt.org","name":"jwatt@jwatt.org"},"change_time":"2011-07-18T12:06:33Z"},{"changes":[{"removed":"","added":"taku.eof@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/taku.eof@gmail.com","name":"taku.eof@gmail.com"},"change_time":"2011-06-27T04:50:23Z"},{"changes":[{"removed":"","added":"jeff@stikman.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jeff@stikman.com","name":"jeff@stikman.com"},"change_time":"2011-05-30T15:15:06Z"},{"changes":[{"removed":"","added":"spencerselander@yahoo.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/spencerselander@yahoo.com","name":"spencerselander@yahoo.com"},"change_time":"2011-05-18T07:58:37Z"},{"changes":[{"removed":"VanillaMozilla@hotmail.com","added":"","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/VanillaMozilla@hotmail.com","name":"VanillaMozilla@hotmail.com"},"change_time":"2011-05-17T02:57:17Z"},{"changes":[{"removed":"","added":"mcdavis941.bugs@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/mcdavis941.bugs@gmail.com","name":"mcdavis941.bugs@gmail.com"},"change_time":"2011-05-16T21:08:18Z"},{"changes":[{"removed":"beltzner@mozilla.com","added":"","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/mbeltzner@gmail.com","name":"mbeltzner@gmail.com"},"change_time":"2011-04-02T00:17:26Z"},{"changes":[{"removed":"","added":"cbaker@customsoftwareconsult.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/cbaker@customsoftwareconsult.com","name":"cbaker@customsoftwareconsult.com"},"change_time":"2011-03-28T14:08:35Z"},{"changes":[{"removed":"","added":"SGrage@gmx.net","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/SGrage@gmx.net","name":"SGrage@gmx.net"},"change_time":"2011-03-28T10:13:51Z"},{"changes":[{"removed":"","added":"dewmigg@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/dewmigg@gmail.com","name":"dewmigg@gmail.com"},"change_time":"2011-03-13T21:34:21Z"},{"changes":[{"removed":"","added":"ulysse@email.it","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/euryalus.0@gmail.com","name":"euryalus.0@gmail.com"},"change_time":"2011-03-13T12:14:04Z"},{"changes":[{"removed":"","added":"heraldo99@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/heraldo99@gmail.com","name":"heraldo99@gmail.com"},"change_time":"2011-03-13T11:36:08Z"},{"changes":[{"removed":"","added":"pardal@gmx.de","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/pardal@gmx.de","name":"pardal@gmx.de"},"change_time":"2011-03-13T02:48:03Z"},{"changes":[{"removed":"","added":"terrell.kelley@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/terrell.kelley@gmail.com","name":"terrell.kelley@gmail.com"},"change_time":"2011-01-31T11:46:28Z"},{"changes":[{"removed":"","added":"kiuzeppe@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/kiuzeppe@gmail.com","name":"kiuzeppe@gmail.com"},"change_time":"2011-01-20T22:13:01Z"},{"changes":[{"removed":"","added":"saneyuki.snyk@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/saneyuki.s.snyk@gmail.com","name":"saneyuki.s.snyk@gmail.com"},"change_time":"2011-01-11T09:25:01Z"},{"changes":[{"removed":"","added":"stephen.donner@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/stephen.donner@gmail.com","name":"stephen.donner@gmail.com"},"change_time":"2011-01-04T21:07:17Z"},{"changes":[{"removed":"","added":"prog_when_resolved_notification@bluebottle.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/prog_when_resolved_notification@bluebottle.com","name":"prog_when_resolved_notification@bluebottle.com"},"change_time":"2011-01-02T18:11:00Z"},{"changes":[{"removed":"jmjeffery@embarqmail.com","added":"","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jmjeffery@embarqmail.com","name":"jmjeffery@embarqmail.com"},"change_time":"2010-12-14T18:17:14Z"},{"changes":[{"removed":"","added":"colmeiro@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/colmeiro@gmail.com","name":"colmeiro@gmail.com"},"change_time":"2010-12-10T23:46:15Z"},{"changes":[{"removed":"","added":"willyaranda@mozilla-hispano.org","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/willyaranda@mozilla-hispano.org","name":"willyaranda@mozilla-hispano.org"},"change_time":"2010-12-10T22:34:30Z"},{"changes":[{"removed":"","added":"dale.bugzilla@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/dale.bugzilla@gmail.com","name":"dale.bugzilla@gmail.com"},"change_time":"2010-12-10T00:50:10Z"},{"changes":[{"removed":"","added":"sabret00the@yahoo.co.uk","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/sabret00the@yahoo.co.uk","name":"sabret00the@yahoo.co.uk"},"change_time":"2010-12-09T17:01:15Z"},{"changes":[{"removed":"","added":"daveryeo@telus.net","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/daveryeo@telus.net","name":"daveryeo@telus.net"},"change_time":"2010-12-09T04:46:59Z"},{"changes":[{"removed":"","added":"bugspam.Callek@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/bugspam.Callek@gmail.com","name":"bugspam.Callek@gmail.com"},"change_time":"2010-12-09T01:05:37Z"},{"changes":[{"removed":"","added":"mcepl@redhat.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/mcepl@redhat.com","name":"mcepl@redhat.com"},"change_time":"2010-12-08T23:55:49Z"},{"changes":[{"removed":"","added":"tymerkaev@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/tymerkaev@gmail.com","name":"tymerkaev@gmail.com"},"change_time":"2010-11-29T10:19:49Z"},{"changes":[{"removed":"","added":"kwierso@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/kwierso@gmail.com","name":"kwierso@gmail.com"},"change_time":"2010-11-29T05:42:05Z"},{"changes":[{"removed":"","added":"mtanalin@yandex.ru","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/mtanalin@yandex.ru","name":"mtanalin@yandex.ru"},"change_time":"2010-11-15T22:33:18Z"},{"changes":[{"removed":"","added":"briks.si@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/briks.si@gmail.com","name":"briks.si@gmail.com"},"change_time":"2010-11-15T20:45:37Z"},{"changes":[{"removed":"","added":"mozdiav@aeons.lv","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/mozdiav@aeons.lv","name":"mozdiav@aeons.lv"},"change_time":"2010-11-12T20:25:54Z"},{"changes":[{"removed":"","added":"soufian.j@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/soufian.j@gmail.com","name":"soufian.j@gmail.com"},"change_time":"2010-11-12T16:43:16Z"},{"changes":[{"removed":"","added":"jmjeffery@embarqmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jmjeffery@embarqmail.com","name":"jmjeffery@embarqmail.com"},"change_time":"2010-11-12T12:59:58Z"},{"changes":[{"removed":"","added":"markc@qsiuk.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/markc@qsiuk.com","name":"markc@qsiuk.com"},"change_time":"2010-11-12T12:14:42Z"},{"changes":[{"removed":"","added":"thibaut.bethune@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/thibaut.bethune@gmail.com","name":"thibaut.bethune@gmail.com"},"change_time":"2010-11-11T22:54:55Z"},{"changes":[{"removed":"","added":"gmontagu@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/gmontagu@gmail.com","name":"gmontagu@gmail.com"},"change_time":"2010-11-11T22:45:52Z"},{"changes":[{"removed":"","added":"KenSaunders@AccessFirefox.org","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/KenSaunders@AccessFirefox.org","name":"KenSaunders@AccessFirefox.org"},"change_time":"2010-11-11T19:26:57Z"},{"changes":[{"removed":"","added":"lists@eitanadler.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/lists@eitanadler.com","name":"lists@eitanadler.com"},"change_time":"2010-11-11T18:07:05Z"},{"changes":[{"removed":"","added":"geekshadow@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/geekshadow@gmail.com","name":"geekshadow@gmail.com"},"change_time":"2010-10-20T09:22:55Z"},{"changes":[{"removed":"","added":"webmaster@keryx.se","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/webmaster@keryx.se","name":"webmaster@keryx.se"},"change_time":"2010-10-20T09:06:57Z"},{"changes":[{"removed":"","added":"bugzilla@zirro.se","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/bugzilla@zirro.se","name":"bugzilla@zirro.se"},"change_time":"2010-10-20T09:03:47Z"},{"changes":[{"removed":"","added":"supernova00@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/supernova00@gmail.com","name":"supernova00@gmail.com"},"change_time":"2010-10-19T21:35:02Z"},{"changes":[{"removed":"","added":"beltzner@mozilla.com, dtownsend@mozilla.com, jgriffin@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jgriffin@mozilla.com","name":"jgriffin@mozilla.com"},"change_time":"2010-10-08T22:07:52Z"},{"changes":[{"removed":"","added":"gavin.sharp@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/gavin.sharp@gmail.com","name":"gavin.sharp@gmail.com"},"change_time":"2010-09-24T18:44:50Z"},{"changes":[{"removed":"","added":"omarb.public@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/omarb.public@gmail.com","name":"omarb.public@gmail.com"},"change_time":"2010-09-06T18:12:39Z"},{"changes":[{"removed":"","added":"David.Vo2+bmo@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/auscompgeek@sumovolunteers.org","name":"auscompgeek@sumovolunteers.org"},"change_time":"2010-09-03T02:57:43Z"},{"changes":[{"removed":"","added":"mcs@pearlcrescent.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/mcs@pearlcrescent.com","name":"mcs@pearlcrescent.com"},"change_time":"2010-09-02T02:33:22Z"},{"changes":[{"removed":"","added":"archaeopteryx@coole-files.de","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/archaeopteryx@coole-files.de","name":"archaeopteryx@coole-files.de"},"change_time":"2010-09-01T09:17:01Z"},{"changes":[{"removed":"","added":"antoine.mechelynck@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/antoine.mechelynck@gmail.com","name":"antoine.mechelynck@gmail.com"},"change_time":"2010-09-01T03:05:29Z"},{"changes":[{"removed":"","added":"m-wada@japan.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/m-wada@japan.com","name":"m-wada@japan.com"},"change_time":"2010-08-31T09:53:39Z"},{"changes":[{"removed":"","added":"bugzilla@standard8.plus.com, ludovic@mozillamessaging.com, vseerror@lehigh.edu","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ludovic@mozilla.com","name":"ludovic@mozilla.com"},"change_time":"2010-08-31T06:10:50Z"},{"changes":[{"removed":"","added":"grenavitar@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/grenavitar@gmail.com","name":"grenavitar@gmail.com"},"change_time":"2010-08-31T03:59:31Z"},{"changes":[{"removed":"","added":"jorge@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jorge@mozilla.com","name":"jorge@mozilla.com"},"change_time":"2010-08-30T18:18:56Z"},{"changes":[{"removed":"","added":"ehsan@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ehsan@mozilla.com","name":"ehsan@mozilla.com"},"change_time":"2010-08-30T18:09:28Z"},{"changes":[{"removed":"","added":"tyler.downer@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/tyler.downer@gmail.com","name":"tyler.downer@gmail.com"},"change_time":"2010-08-30T17:40:02Z"},{"changes":[{"removed":"","added":"asqueella@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/asqueella@gmail.com","name":"asqueella@gmail.com"},"change_time":"2010-08-26T20:47:46Z"},{"changes":[{"removed":"","added":"590788","field_name":"depends_on"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2010-08-26T15:49:52Z"},{"changes":[{"removed":"","added":"benjamin@smedbergs.us","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2010-08-26T08:24:38Z"},{"changes":[{"removed":"","added":"bugs-bmo@unknownbrackets.org","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/bugs-bmo@unknownbrackets.org","name":"bugs-bmo@unknownbrackets.org"},"change_time":"2010-08-26T07:24:55Z"},{"changes":[{"removed":"","added":"highmind63@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/highmind63@gmail.com","name":"highmind63@gmail.com"},"change_time":"2010-08-26T03:45:34Z"},{"changes":[{"removed":"stream@abv.bg","added":"","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/stream@abv.bg","name":"stream@abv.bg"},"change_time":"2010-08-24T16:50:19Z"},{"changes":[{"removed":"Infrastructure","added":"ProfileManager","field_name":"component"},{"removed":"jhammel@mozilla.com","added":"nobody@mozilla.org","field_name":"assigned_to"},{"removed":"infra@testing.bugs","added":"profilemanager@testing.bugs","field_name":"qa_contact"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2010-08-24T15:27:00Z"},{"changes":[{"removed":"","added":"justin.lebar+bug@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/justin.lebar+bug@gmail.com","name":"justin.lebar+bug@gmail.com"},"change_time":"2010-07-28T21:26:38Z"},{"changes":[{"removed":"mozilla.bugs@alyoung.com","added":"","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/mozilla.bugs@alyoung.com","name":"mozilla.bugs@alyoung.com"},"change_time":"2010-07-22T23:59:31Z"},{"changes":[{"removed":"","added":"deletesoftware@yandex.ru","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/deletesoftware@yandex.ru","name":"deletesoftware@yandex.ru"},"change_time":"2010-06-19T14:19:55Z"},{"changes":[{"removed":"NEW","added":"ASSIGNED","field_name":"status"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2010-06-14T17:26:17Z"},{"changes":[{"removed":"nobody@mozilla.org","added":"jhammel@mozilla.com","field_name":"assigned_to"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2010-05-27T15:42:33Z"},{"changes":[{"removed":"","added":"harthur@cmu.edu","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2010-03-18T20:49:42Z"},{"changes":[{"removed":"","added":"k0scist@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2010-03-09T17:10:48Z"},{"changes":[{"removed":"","added":"stream@abv.bg","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/stream@abv.bg","name":"stream@abv.bg"},"change_time":"2010-02-07T01:32:42Z"},{"changes":[{"removed":"","added":"VanillaMozilla@hotmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/VanillaMozilla@hotmail.com","name":"VanillaMozilla@hotmail.com"},"change_time":"2010-01-22T17:28:59Z"},{"changes":[{"removed":"","added":"reed@reedloden.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/reed@reedloden.com","name":"reed@reedloden.com"},"change_time":"2010-01-17T19:22:31Z"},{"changes":[{"removed":"","added":"blizzard@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/blizzard@mozilla.com","name":"blizzard@mozilla.com"},"change_time":"2010-01-17T01:12:28Z"},{"changes":[{"removed":"","added":"540194","field_name":"blocks"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/david@rossde.com","name":"david@rossde.com"},"change_time":"2010-01-16T19:18:12Z"},{"changes":[{"removed":"","added":"fullmetaljacket.xp+bugmail@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/fullmetaljacket.xp+bugmail@gmail.com","name":"fullmetaljacket.xp+bugmail@gmail.com"},"change_time":"2010-01-15T09:22:52Z"},{"changes":[{"removed":"","added":"wladow@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/wladow@gmail.com","name":"wladow@gmail.com"},"change_time":"2010-01-14T22:31:39Z"},{"changes":[{"removed":"278860","added":"","field_name":"depends_on"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2010-01-14T17:46:11Z"},{"changes":[{"removed":"","added":"chado_moz@yahoo.co.jp","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/chado_moz@yahoo.co.jp","name":"chado_moz@yahoo.co.jp"},"change_time":"2010-01-14T15:30:57Z"},{"changes":[{"removed":"","added":"mnyromyr@tprac.de","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/mnyromyr@tprac.de","name":"mnyromyr@tprac.de"},"change_time":"2010-01-14T10:56:22Z"},{"changes":[{"removed":"","added":"spitfire.kuden@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/spitfire.kuden@gmail.com","name":"spitfire.kuden@gmail.com"},"change_time":"2010-01-14T10:15:48Z"},{"changes":[{"removed":"","added":"mozilla.bugs@alyoung.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/mozilla.bugs@alyoung.com","name":"mozilla.bugs@alyoung.com"},"change_time":"2010-01-14T05:57:27Z"},{"changes":[{"removed":"","added":"nori@noasobi.net","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/nori@noasobi.net","name":"nori@noasobi.net"},"change_time":"2010-01-14T05:41:47Z"},{"changes":[{"removed":"","added":"michaelkohler@live.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/michaelkohler@linux.com","name":"michaelkohler@linux.com"},"change_time":"2010-01-14T01:39:36Z"},{"changes":[{"removed":"","added":"kairo@kairo.at","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/kairo@kairo.at","name":"kairo@kairo.at"},"change_time":"2010-01-14T00:16:58Z"},{"changes":[{"removed":"normal","added":"enhancement","field_name":"severity"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/david@rossde.com","name":"david@rossde.com"},"change_time":"2010-01-14T00:08:41Z"},{"changes":[{"removed":"","added":"ryanvm@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ryanvm@gmail.com","name":"ryanvm@gmail.com"},"change_time":"2010-01-14T00:02:33Z"},{"changes":[{"removed":"","added":"phiw@l-c-n.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/phiw@l-c-n.com","name":"phiw@l-c-n.com"},"change_time":"2010-01-13T23:56:59Z"},{"changes":[{"removed":"","added":"pcvrcek@mozilla.cz","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/pcvrcek@mozilla.cz","name":"pcvrcek@mozilla.cz"},"change_time":"2010-01-13T23:45:00Z"},{"changes":[{"removed":"","added":"axel@pike.org","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/l10n@mozilla.com","name":"l10n@mozilla.com"},"change_time":"2010-01-13T23:17:08Z"},{"changes":[{"removed":"","added":"stefanh@inbox.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/stefanh@inbox.com","name":"stefanh@inbox.com"},"change_time":"2010-01-13T23:12:13Z"},{"changes":[{"removed":"x86","added":"All","field_name":"platform"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2010-01-13T23:06:25Z"},{"changes":[{"removed":"","added":"johnath@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/johnath@mozilla.com","name":"johnath@mozilla.com"},"change_time":"2010-01-13T23:04:10Z"},{"changes":[{"removed":"","added":"mrmazda@earthlink.net","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/mrmazda@earthlink.net","name":"mrmazda@earthlink.net"},"change_time":"2010-01-13T22:46:40Z"},{"changes":[{"removed":"","added":"iann_bugzilla@blueyonder.co.uk","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/iann_bugzilla@blueyonder.co.uk","name":"iann_bugzilla@blueyonder.co.uk"},"change_time":"2010-01-13T21:41:32Z"},{"changes":[{"removed":"","added":"xtc4uall@gmail.com","field_name":"cc"},{"removed":"","added":"214675","field_name":"blocks"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/xtc4uall@gmail.com","name":"xtc4uall@gmail.com"},"change_time":"2010-01-13T21:34:20Z"},{"changes":[{"removed":"","added":"hskupin@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2010-01-13T21:31:14Z"},{"changes":[{"removed":"","added":"hickendorffbas@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hickendorffbas@gmail.com","name":"hickendorffbas@gmail.com"},"change_time":"2010-01-13T20:58:31Z"}],"summary":"New Test/Triage Profile Manager Application","last_change_time":"2011-07-18T12:06:33Z","comments":[{"is_private":false,"creator":{"real_name":"Jonathan Griffin (:jgriffin)","name":"jgriffin"},"text":"","id":5480531,"creation_time":"2011-05-19T17:30:57Z"},{"is_private":false,"creator":{"real_name":"Spencer Selander [greenknight]","name":"spencerselander"},"text":"","id":5476603,"creation_time":"2011-05-18T07:58:37Z"},{"is_private":false,"creator":{"real_name":"Paul [sabret00the]","name":"sabret00the"},"text":"","id":5373656,"creation_time":"2011-03-28T06:59:22Z"},{"is_private":false,"creator":{"real_name":"Jonathan Griffin (:jgriffin)","name":"jgriffin"},"text":"","id":5240204,"creation_time":"2011-01-31T17:58:24Z"},{"is_private":false,"creator":{"real_name":"Terrell Kelley","name":"terrell.kelley"},"text":"","id":5239501,"creation_time":"2011-01-31T11:46:28Z"},{"is_private":false,"creator":{"real_name":"Benjamin Smedberg [:bsmedberg]","name":"benjamin"},"text":"","id":5002085,"creation_time":"2010-10-11T18:04:48Z"},{"is_private":false,"creator":{"real_name":"Clint Talbert ( :ctalbert )","name":"ctalbert"},"text":"","id":5002054,"creation_time":"2010-10-11T17:52:13Z"},{"is_private":false,"creator":{"real_name":"Dave Townsend (:Mossop)","name":"dtownsend"},"text":"","id":4999117,"creation_time":"2010-10-08T22:12:23Z"},{"is_private":false,"creator":{"real_name":"Jonathan Griffin (:jgriffin)","name":"jgriffin"},"text":"","id":4999107,"creation_time":"2010-10-08T22:07:52Z"},{"is_private":false,"creator":{"real_name":"Benjamin Smedberg [:bsmedberg]","name":"benjamin"},"text":"","id":4991281,"creation_time":"2010-10-06T18:42:20Z"},{"is_private":false,"creator":{"real_name":"David E. Ross","name":"david"},"text":"","id":4990011,"creation_time":"2010-10-06T17:50:51Z"},{"is_private":false,"creator":{"real_name":"Benjamin Smedberg [:bsmedberg]","name":"benjamin"},"text":"","id":4986111,"creation_time":"2010-10-05T02:54:24Z"},{"is_private":false,"creator":{"real_name":"Clint Talbert ( :ctalbert )","name":"ctalbert"},"text":"","id":4985884,"creation_time":"2010-10-05T00:30:08Z"},{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":4921920,"creation_time":"2010-09-08T15:20:06Z"},{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":4892626,"creation_time":"2010-08-26T16:05:50Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":4891863,"creation_time":"2010-08-26T08:24:38Z"},{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":4886114,"creation_time":"2010-08-24T15:31:05Z"},{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":4743315,"creation_time":"2010-06-14T17:26:17Z"},{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":4716160,"creation_time":"2010-05-27T16:53:53Z"},{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":4716148,"creation_time":"2010-05-27T16:44:32Z"},{"is_private":false,"creator":{"real_name":"David E. Ross","name":"david"},"text":"","id":4492322,"creation_time":"2010-01-18T15:12:52Z"},{"is_private":false,"creator":{"real_name":"Christopher Blizzard (:blizzard)","name":"blizzard"},"text":"","id":4490922,"creation_time":"2010-01-17T01:13:33Z"},{"is_private":false,"creator":{"real_name":"Clint Talbert ( :ctalbert )","name":"ctalbert"},"text":"","id":4487552,"creation_time":"2010-01-14T17:46:11Z"},{"is_private":false,"creator":{"real_name":"David E. Ross","name":"david"},"text":"","id":4486520,"creation_time":"2010-01-14T00:08:41Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":4486434,"creation_time":"2010-01-13T23:06:25Z"},{"is_private":false,"creator":{"real_name":"XtC4UaLL [:xtc4uall]","name":"xtc4uall"},"text":"","id":4486282,"creation_time":"2010-01-13T21:34:20Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":4486275,"creation_time":"2010-01-13T21:31:14Z"},{"is_private":false,"creator":{"real_name":"Clint Talbert ( :ctalbert )","name":"ctalbert"},"text":"","id":4486171,"creation_time":"2010-01-13T20:37:30Z"}],"id":539524},"events":[{"time":"2011-07-18T12:06:33Z","changeset":{"changes":[{"removed":"","added":"jwatt@jwatt.org","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jwatt@jwatt.org","name":"jwatt@jwatt.org"},"change_time":"2011-07-18T12:06:33Z"}}]},{"bug":{"history":[{"changes":[{"removed":"","added":"568943","field_name":"blocks"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-07-18T09:21:42Z"},{"changes":[{"removed":"[mozmill-next?]","added":"[mozmill-2.0-][mozmill-next?]","field_name":"whiteboard"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-05-25T12:01:37Z"},{"changes":[{"removed":"[mozmill-2.0+]","added":"[mozmill-next?]","field_name":"whiteboard"},{"removed":"critical","added":"normal","field_name":"severity"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2011-05-24T22:41:14Z"},{"changes":[{"removed":"[mozmill-2.0?]","added":"[mozmill-2.0+]","field_name":"whiteboard"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2011-03-29T21:43:13Z"},{"changes":[{"removed":"","added":"dataloss","field_name":"keywords"},{"removed":"enhancement","added":"critical","field_name":"severity"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-03-21T00:24:58Z"},{"changes":[{"removed":"nobody@mozilla.org","added":"jhammel@mozilla.com","field_name":"assigned_to"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-03-18T21:05:30Z"},{"changes":[{"removed":"","added":"ahalberstadt@mozilla.com, ctalbert@mozilla.com, fayearthur+bugs@gmail.com, hskupin@gmail.com","field_name":"cc"},{"removed":"","added":"[mozmill-2.0?]","field_name":"whiteboard"},{"removed":"normal","added":"enhancement","field_name":"severity"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-03-18T16:29:22Z"}],"summary":"should be able to clone from a profile as a basis","last_change_time":"2011-07-18T09:21:42Z","comments":[{"is_private":false,"creator":{"real_name":"Clint Talbert ( :ctalbert )","name":"ctalbert"},"text":"","id":5490872,"creation_time":"2011-05-24T22:41:14Z"},{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":5357384,"creation_time":"2011-03-21T16:41:15Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5356453,"creation_time":"2011-03-21T00:24:58Z"},{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":5353489,"creation_time":"2011-03-18T16:32:57Z"},{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":5353480,"creation_time":"2011-03-18T16:29:22Z"},{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":5353472,"creation_time":"2011-03-18T16:27:14Z"}],"id":642843},"events":[{"time":"2011-07-18T09:21:42Z","changeset":{"changes":[{"removed":"","added":"568943","field_name":"blocks"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-07-18T09:21:42Z"}}]}]
\ No newline at end of file
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/package.json b/node_modules/node-firefox-connect/node_modules/firefox-client/package.json
new file mode 100644
index 0000000..8985573
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/package.json
@@ -0,0 +1,52 @@
+{
+  "name": "firefox-client",
+  "description": "Firefox remote debugging client",
+  "version": "0.3.0",
+  "author": {
+    "name": "Heather Arthur",
+    "email": "fayearthur@gmail.com"
+  },
+  "main": "index.js",
+  "repository": {
+    "type": "git",
+    "url": "http://github.com/harthur/firefox-client.git"
+  },
+  "dependencies": {
+    "colors": "0.5.x",
+    "js-select": "~0.6.0"
+  },
+  "devDependencies": {
+    "connect": "~2.8.2",
+    "mocha": "~1.12.0"
+  },
+  "keywords": [
+    "firefox",
+    "debugger",
+    "remote debugging"
+  ],
+  "bugs": {
+    "url": "https://github.com/harthur/firefox-client/issues"
+  },
+  "homepage": "https://github.com/harthur/firefox-client",
+  "_id": "firefox-client@0.3.0",
+  "dist": {
+    "shasum": "3794460f6eb6afcf41376addcbc7462e24a4cd8b",
+    "tarball": "http://registry.npmjs.org/firefox-client/-/firefox-client-0.3.0.tgz"
+  },
+  "_from": "firefox-client@^0.3.0",
+  "_npmVersion": "1.4.3",
+  "_npmUser": {
+    "name": "harth",
+    "email": "fayearthur@gmail.com"
+  },
+  "maintainers": [
+    {
+      "name": "harth",
+      "email": "fayearthur@gmail.com"
+    }
+  ],
+  "directories": {},
+  "_shasum": "3794460f6eb6afcf41376addcbc7462e24a4cd8b",
+  "_resolved": "https://registry.npmjs.org/firefox-client/-/firefox-client-0.3.0.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/test.js b/node_modules/node-firefox-connect/node_modules/firefox-client/test.js
new file mode 100644
index 0000000..1f2d334
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/test.js
@@ -0,0 +1,54 @@
+var assert = require('assert'),
+    FirefoxClient = require("./index");
+
+
+var url = "file:///Users/harth/repos/sass-wwcode/index.html";
+
+loadUrl(url, function(tab) {
+  tab.StyleSheets.getStyleSheets(function(err, sheets) {
+    var sheet = sheets[1];
+    sheet.getOriginalSources(function(err, sources) {
+      console.log(err);
+      console.log(sources[0].url);
+      sources[0].getText(function(err, resp) {
+        console.log(err);
+        console.log(resp);
+      })
+    });
+    console.log(sheet.href);
+  });
+})
+
+
+/**
+ * Helper functions
+ */
+function loadUrl(url, callback) {
+  getFirstTab(function(tab) {
+    console.log("GOT TAB");
+    tab.navigateTo(url);
+
+    tab.once("navigate", function() {
+      console.log("NAVIGATED");
+      callback(tab);
+    });
+  });
+}
+
+function getFirstTab(callback) {
+  var client = new FirefoxClient({log: true});
+
+  client.connect(function() {
+    client.listTabs(function(err, tabs) {
+      if (err) throw err;
+
+      var tab = tabs[0];
+
+      // attach so we can receive load events
+      tab.attach(function(err) {
+        if (err) throw err;
+        callback(tab);
+      })
+    });
+  });
+}
\ No newline at end of file
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/test/README.md b/node_modules/node-firefox-connect/node_modules/firefox-client/test/README.md
new file mode 100644
index 0000000..75bd86f
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/test/README.md
@@ -0,0 +1,29 @@
+# testing
+
+### dependencies
+To run the tests in this directory, first install the dev dependencies with this command from the top-level directory:
+
+```
+npm install --dev
+```
+
+You'll also have to globally install [mocha](http://visionmedia.github.io/mocha). `npm install mocha -g`.
+
+### running
+First open up a [Firefox Nightly build](http://nightly.mozilla.org/) and serve the test files up:
+
+```
+node server.js &
+```
+
+visit the url the server tells you to visit.
+
+Finally, run the tests with:
+
+```
+mocha test-dom.js --timeout 10000
+````
+
+The increased timeout is to give you enough time to manually verify the incoming connection in Firefox.
+
+Right now you have to run each test individually, until Firefox [bug 891003](https://bugzilla.mozilla.org/show_bug.cgi?id=891003) is fixed.
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/test/pages/dom.html b/node_modules/node-firefox-connect/node_modules/firefox-client/test/pages/dom.html
new file mode 100644
index 0000000..c662637
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/test/pages/dom.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>DOM tests</title>
+  <meta charset="utf-8">
+</head>
+<body>
+  <main>
+    <section id="test-section">
+      <div id="test1" class="item"></div>
+      <div id="test2" class="item">
+          <div id="child1"></div>
+          <div id="child2"></div>
+      </div>
+      <div id="test3" class="item"></div>
+    </section>
+  </main>
+</body>
+</html>
\ No newline at end of file
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/test/pages/index.html b/node_modules/node-firefox-connect/node_modules/firefox-client/test/pages/index.html
new file mode 100644
index 0000000..4a67c5a
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/test/pages/index.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Firefox remote debugging client tests</title>
+  <meta charset="utf-8">
+  <style>
+    header {
+      font-family: Georgia, sans-serif;
+      font-size: 3em;
+      margin: 3em;
+      color: hsl(30, 90%, 50%);
+    }
+  </style>
+</head>
+<body>
+  <header>
+    Firefox Client Tests
+  </header>
+  <main>
+  </main>
+</body>
+</html>
\ No newline at end of file
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/test/pages/logs.html b/node_modules/node-firefox-connect/node_modules/firefox-client/test/pages/logs.html
new file mode 100644
index 0000000..56b7c1d
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/test/pages/logs.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Logs tests</title>
+  <meta charset="utf-8">
+
+  <script>
+    console.log("hi");
+    console.dir({a: 3});
+    foo;
+  </script>
+</head>
+<body>
+  <main>
+  </main>
+</body>
+</html>
\ No newline at end of file
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/test/pages/network.html b/node_modules/node-firefox-connect/node_modules/firefox-client/test/pages/network.html
new file mode 100644
index 0000000..922e27c
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/test/pages/network.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Logs tests</title>
+  <meta charset="utf-8">
+
+  <script>
+    function sendRequest() {
+      var req = new XMLHttpRequest();
+
+      req.open("GET", "test-network.json", true);
+      req.responseType = "json";
+      req.setRequestHeader("test-header", "test-value");
+      req.send();
+    }
+  </script>
+</head>
+<body>
+  <main>
+  </main>
+</body>
+</html>
\ No newline at end of file
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/test/pages/stylesheet1.css b/node_modules/node-firefox-connect/node_modules/firefox-client/test/pages/stylesheet1.css
new file mode 100644
index 0000000..ff5907f
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/test/pages/stylesheet1.css
@@ -0,0 +1,9 @@
+main {
+  font-family: Georgia, sans-serif;
+  color: black;
+}
+
+* {
+  padding: 0;
+  margin: 0;
+}
\ No newline at end of file
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/test/pages/stylesheets.html b/node_modules/node-firefox-connect/node_modules/firefox-client/test/pages/stylesheets.html
new file mode 100644
index 0000000..8cab1e8
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/test/pages/stylesheets.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Stylesheets tests</title>
+  <meta charset="utf-8">
+  <style>
+    main {
+      margin: 20px;
+    }
+  </style>
+  <link rel="stylesheet" href="stylesheet1.css"/>
+</head>
+<body>
+  <main>
+    <main>
+      <div>Stylesheet Tests</div>
+    </main>
+  </main>
+</body>
+</html>
\ No newline at end of file
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/test/pages/test-network.json b/node_modules/node-firefox-connect/node_modules/firefox-client/test/pages/test-network.json
new file mode 100644
index 0000000..5b89410
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/test/pages/test-network.json
@@ -0,0 +1,4 @@
+{
+  "a": 2,
+  "b": "hello"
+}
\ No newline at end of file
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/test/server.js b/node_modules/node-firefox-connect/node_modules/firefox-client/test/server.js
new file mode 100644
index 0000000..aca94f7
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/test/server.js
@@ -0,0 +1,8 @@
+var path = require("path"),
+    connect = require('connect');
+
+var port = 3000;
+
+connect.createServer(connect.static(path.join(__dirname, "pages"))).listen(port);
+
+console.log("visit:\nhttp://127.0.0.1:" + port + "/index.html");
\ No newline at end of file
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/test/test-console.js b/node_modules/node-firefox-connect/node_modules/firefox-client/test/test-console.js
new file mode 100644
index 0000000..9198ad0
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/test/test-console.js
@@ -0,0 +1,64 @@
+var assert = require("assert"),
+    utils = require("./utils");
+
+var Console;
+
+before(function(done) {
+  utils.loadTab('dom.html', function(aTab) {
+    Console = aTab.Console;
+    done();
+  });
+});
+
+// Console - evaluateJS()
+
+describe('evaluateJS()', function() {
+  it('should evaluate expr to number', function(done) {
+    Console.evaluateJS('6 + 7', function(err, resp) {
+      assert.strictEqual(err, null);
+      assert.equal(resp.result, 13);
+      done();
+    })
+  })
+
+  it('should evaluate expr to boolean', function(done) {
+    Console.evaluateJS('!!window', function(err, resp) {
+      assert.strictEqual(err, null);
+      assert.strictEqual(resp.result, true);
+      done();
+    })
+  })
+
+  it('should evaluate expr to string', function(done) {
+    Console.evaluateJS('"hello"', function(err, resp) {
+      assert.strictEqual(err, null);
+      assert.equal(resp.result, "hello");
+      done();
+    })
+  })
+
+  it('should evaluate expr to JSObject', function(done) {
+    Console.evaluateJS('x = {a: 2, b: "hello"}', function(err, resp) {
+      assert.strictEqual(err, null);
+      assert.ok(resp.result.ownPropertyNames, "result has JSObject methods");
+      done();
+    })
+  })
+
+  it('should evaluate to undefined', function(done) {
+    Console.evaluateJS('undefined', function(err, resp) {
+      assert.strictEqual(err, null);
+      assert.ok(resp.result.type, "undefined");
+      done();
+    })
+  })
+
+  it('should have exception in response', function(done) {
+    Console.evaluateJS('blargh', function(err, resp) {
+      assert.strictEqual(err, null);
+      assert.equal(resp.exception.class, "Error"); // TODO: error should be JSObject
+      assert.equal(resp.exceptionMessage, "ReferenceError: blargh is not defined");
+      done();
+    })
+  })
+})
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/test/test-dom.js b/node_modules/node-firefox-connect/node_modules/firefox-client/test/test-dom.js
new file mode 100644
index 0000000..9206561
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/test/test-dom.js
@@ -0,0 +1,329 @@
+var assert = require("assert"),
+    utils = require("./utils");
+
+var doc;
+var DOM;
+var node;
+var firstNode;
+var lastNode;
+
+before(function(done) {
+  utils.loadTab('dom.html', function(aTab) {
+    DOM = aTab.DOM;
+    DOM.document(function(err, aDoc) {
+      doc = aDoc;
+      DOM.querySelectorAll(".item", function(err, list) {
+        list.items(function(err, items) {
+          firstNode = items[0];
+          node = items[1];
+          lastNode = items[2];
+          done();
+        })
+      })
+    })
+  });
+});
+
+// DOM - document(), documentElement()
+
+describe('document()', function() {
+  it('should get document node', function(done) {
+    DOM.document(function(err, doc) {
+      assert.strictEqual(err, null);
+      assert.equal(doc.nodeName, "#document");
+      assert.equal(doc.nodeType, 9);
+      done();
+    })
+  })
+})
+
+
+describe('documentElement()', function() {
+  it('should get documentElement node', function(done) {
+    DOM.documentElement(function(err, elem) {
+      assert.strictEqual(err, null);
+      assert.equal(elem.nodeName, "HTML");
+      assert.equal(elem.nodeType, 1);
+      done();
+    })
+  })
+})
+
+describe('querySelector()', function() {
+  it('should get first item node', function(done) {
+    DOM.querySelector(".item", function(err, child) {
+      assert.strictEqual(err, null);
+      assert.equal(child.getAttribute("id"), "test1");
+      assert.ok(child.querySelector, "node has node methods");
+      done();
+    })
+  })
+})
+
+describe('querySelector()', function() {
+  it('should get all item nodes', function(done) {
+    DOM.querySelectorAll(".item", function(err, list) {
+      assert.strictEqual(err, null);
+      assert.equal(list.length, 3);
+
+      list.items(function(err, children) {
+        assert.strictEqual(err, null);
+        var ids = children.map(function(child) {
+          assert.ok(child.querySelector, "list item has node methods");
+          return child.getAttribute("id");
+        })
+        assert.deepEqual(ids, ["test1","test2","test3"]);
+        done();
+      })
+    })
+  })
+})
+
+// Node - parentNode(), parent(), siblings(), nextSibling(), previousSibling(),
+// querySelector(), querySelectorAll(), innerHTML(), outerHTML(), getAttribute(),
+// setAttribute()
+
+describe('parentNode()', function() {
+  it('should get parent node', function(done) {
+    node.parentNode(function(err, parent) {
+      assert.strictEqual(err, null);
+      assert.equal(parent.nodeName, "SECTION");
+      assert.ok(parent.querySelector, "parent has node methods");
+      done();
+    })
+  })
+
+  it('should be null for document parentNode', function(done) {
+    doc.parentNode(function(err, parent) {
+      assert.strictEqual(err, null);
+      assert.strictEqual(parent, null);
+      done();
+    })
+  })
+})
+
+describe('parents()', function() {
+  it('should get ancestor nodes', function(done) {
+    node.parents(function(err, ancestors) {
+      assert.strictEqual(err, null);
+      var names = ancestors.map(function(ancestor) {
+        assert.ok(ancestor.querySelector, "ancestor has node methods");
+        return ancestor.nodeName;
+      })
+      assert.deepEqual(names, ["SECTION","MAIN","BODY","HTML","#document"]);
+      done();
+    })
+  })
+})
+
+describe('children()', function() {
+  it('should get child nodes', function(done) {
+    node.children(function(err, children) {
+      assert.strictEqual(err, null);
+      var ids = children.map(function(child) {
+        assert.ok(child.querySelector, "child has node methods");
+        return child.getAttribute("id");
+      })
+      assert.deepEqual(ids, ["child1","child2"]);
+      done();
+    })
+  })
+})
+
+describe('siblings()', function() {
+  it('should get sibling nodes', function(done) {
+    node.siblings(function(err, siblings) {
+      assert.strictEqual(err, null);
+      var ids = siblings.map(function(sibling) {
+        assert.ok(sibling.querySelector, "sibling has node methods");
+        return sibling.getAttribute("id");
+      })
+      assert.deepEqual(ids, ["test1","test2","test3"]);
+      done();
+    })
+  })
+})
+
+describe('nextSibling()', function() {
+  it('should get next sibling node', function(done) {
+    node.nextSibling(function(err, sibling) {
+      assert.strictEqual(err, null);
+      assert.equal(sibling.getAttribute("id"), "test3");
+      assert.ok(sibling.querySelector, "next sibling has node methods");
+      done();
+    })
+  })
+
+  it('should be null if no next sibling', function(done) {
+    lastNode.nextSibling(function(err, sibling) {
+      assert.strictEqual(err, null);
+      assert.strictEqual(sibling, null);
+      done();
+    })
+  })
+})
+
+describe('previousSibling()', function() {
+  it('should get next sibling node', function(done) {
+    node.previousSibling(function(err, sibling) {
+      assert.strictEqual(err, null);
+      assert.equal(sibling.getAttribute("id"), "test1");
+      assert.ok(sibling.querySelector, "next sibling has node methods");
+      done();
+    })
+  })
+
+  it('should be null if no prev sibling', function(done) {
+    firstNode.previousSibling(function(err, sibling) {
+      assert.strictEqual(err, null);
+      assert.strictEqual(sibling, null);
+      done();
+    })
+  })
+})
+
+describe('querySelector()', function() {
+  it('should get first child node', function(done) {
+    node.querySelector("*", function(err, child) {
+      assert.strictEqual(err, null);
+      assert.equal(child.getAttribute("id"), "child1");
+      assert.ok(child.querySelector, "node has node methods");
+      done();
+    })
+  })
+
+  it('should be null if no nodes with selector', function(done) {
+    node.querySelector("blarg", function(err, resp) {
+      assert.strictEqual(err, null);
+      assert.strictEqual(resp, null);
+      done();
+    })
+  })
+})
+
+describe('querySelectorAll()', function() {
+  it('should get all child nodes', function(done) {
+    node.querySelectorAll("*", function(err, list) {
+      assert.strictEqual(err, null);
+      assert.equal(list.length, 2);
+
+      list.items(function(err, children) {
+        assert.strictEqual(err, null);
+        var ids = children.map(function(child) {
+          assert.ok(child.querySelector, "list item has node methods");
+          return child.getAttribute("id");
+        })
+        assert.deepEqual(ids, ["child1", "child2"]);
+        done();
+      })
+    })
+  })
+
+  it('should get nodes from "start" to "end"', function(done) {
+    doc.querySelectorAll(".item", function(err, list) {
+      assert.strictEqual(err, null);
+      assert.equal(list.length, 3);
+
+      list.items(1, 2, function(err, items) {
+        assert.strictEqual(err, null);
+        assert.equal(items.length, 1);
+        assert.deepEqual(items[0].getAttribute("id"), "test2")
+        done();
+      })
+    })
+  })
+
+  it('should get nodes from "start"', function(done) {
+    doc.querySelectorAll(".item", function(err, list) {
+      assert.strictEqual(err, null);
+      assert.equal(list.length, 3);
+
+      list.items(1, function(err, items) {
+        assert.strictEqual(err, null);
+        assert.equal(items.length, 2);
+        var ids = items.map(function(item) {
+          assert.ok(item.querySelector, "list item has node methods");
+          return item.getAttribute("id");
+        })
+        assert.deepEqual(ids, ["test2","test3"]);
+        done();
+      })
+    })
+  })
+
+  it('should be empty list if no nodes with selector', function(done) {
+    node.querySelectorAll("blarg", function(err, list) {
+      assert.strictEqual(err, null);
+      assert.equal(list.length, 0);
+
+      list.items(function(err, items) {
+        assert.strictEqual(err, null);
+        assert.deepEqual(items, []);
+        done();
+      })
+    })
+  })
+})
+
+describe('innerHTML()', function() {
+  it('should get innerHTML of node', function(done) {
+    node.innerHTML(function(err, text) {
+      assert.strictEqual(err, null);
+      assert.equal(text, '\n          <div id="child1"></div>\n'
+                   + '          <div id="child2"></div>\n      ');
+      done();
+    })
+  })
+})
+
+describe('outerHTML()', function() {
+  it('should get outerHTML of node', function(done) {
+    node.outerHTML(function(err, text) {
+      assert.strictEqual(err, null);
+      assert.equal(text, '<div id="test2" class="item">\n'
+                   + '          <div id="child1"></div>\n'
+                   + '          <div id="child2"></div>\n      '
+                   + '</div>');
+      done();
+    })
+  })
+})
+
+describe('highlight()', function() {
+  it('should highlight node', function(done) {
+    node.highlight(function(err, resp) {
+      assert.strictEqual(err, null);
+      done();
+    })
+  })
+})
+
+/* MUST BE LAST */
+describe('remove()', function() {
+  it('should remove node', function(done) {
+    node.remove(function(err, nextSibling) {
+      assert.strictEqual(err, null);
+      assert.equal(nextSibling.getAttribute("id"), "test3");
+
+      doc.querySelectorAll(".item", function(err, list) {
+        assert.strictEqual(err, null);
+        assert.equal(list.length, 2);
+        done();
+      })
+    })
+  })
+
+  it("should err if performing further operations after release()", function(done) {
+    node.release(function(err) {
+      assert.strictEqual(err, null);
+
+      node.innerHTML(function(err, text) {
+        assert.equal(err.message, "TypeError: node is null")
+        assert.equal(err.toString(), "unknownError: TypeError: node is null");
+        done();
+      })
+    })
+  })
+})
+
+
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/test/test-jsobject.js b/node_modules/node-firefox-connect/node_modules/firefox-client/test/test-jsobject.js
new file mode 100644
index 0000000..f416674
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/test/test-jsobject.js
@@ -0,0 +1,115 @@
+var assert = require("assert"),
+    utils = require("./utils");
+
+var Console;
+var obj;
+var func;
+
+before(function(done) {
+  utils.loadTab('dom.html', function(aTab) {
+    Console = aTab.Console;
+    Console.evaluateJS('x = {a: 2, b: {c: 3}, get d() {return 4;}}', function(err, resp) {
+      obj = resp.result;
+
+      var input = 'y = function testfunc(a, b) { return a + b; }';
+      Console.evaluateJS(input, function(err, resp) {
+        func = resp.result;
+        done();
+      })
+    });
+  });
+});
+
+// JSObject - ownPropertyNames(), ownPropertyDescriptor(), prototype(), properties()
+
+describe('ownPropertyNames()', function() {
+  it('should fetch property names', function(done) {
+    obj.ownPropertyNames(function(err, names) {
+      assert.strictEqual(err, null);
+      assert.deepEqual(names, ['a', 'b', 'd']);
+      done();
+    })
+  })
+});
+
+describe('ownPropertyDescriptor()', function() {
+  it('should fetch descriptor for property', function(done) {
+    obj.ownPropertyDescriptor('a', function(err, desc) {
+      assert.strictEqual(err, null);
+      testDescriptor(desc);
+      assert.equal(desc.value, 2);
+      done();
+    })
+  })
+
+  /* TODO: doesn't call callback if not defined property - Server side problem
+  it('should be undefined for nonexistent property', function(done) {
+    obj.ownPropertyDescriptor('g', function(desc) {
+      console.log("desc", desc);
+      done();
+    })
+  }) */
+})
+
+describe('ownProperties()', function() {
+  it('should fetch all own properties and descriptors', function(done) {
+    obj.ownProperties(function(err, props) {
+      assert.strictEqual(err, null);
+      testDescriptor(props.a);
+      assert.equal(props.a.value, 2);
+
+      testDescriptor(props.b);
+      assert.ok(props.b.value.ownProperties, "prop value has JSObject methods");
+      done();
+    })
+  })
+})
+
+describe('prototype()', function() {
+  it('should fetch prototype as an object', function(done) {
+    obj.prototype(function(err, proto) {
+      assert.strictEqual(err, null);
+      assert.ok(proto.ownProperties, "prototype has JSObject methods");
+      done();
+    })
+  })
+})
+
+describe('ownPropertiesAndPrototype()', function() {
+  it('should fetch properties, prototype, and getters', function(done) {
+    obj.ownPropertiesAndPrototype(function(err, resp) {
+      assert.strictEqual(err, null);
+
+      // own properties
+      var props = resp.ownProperties;
+      assert.equal(Object.keys(props).length, 3);
+
+      testDescriptor(props.a);
+      assert.equal(props.a.value, 2);
+
+      // prototype
+      assert.ok(resp.prototype.ownProperties,
+                "prototype has JSObject methods");
+
+      // getters
+      var getters = resp.safeGetterValues;
+      assert.equal(Object.keys(getters).length, 0);
+
+      done();
+    })
+  })
+})
+
+describe('Function objects', function() {
+  it('sould have correct properties', function() {
+    assert.equal(func.class, "Function");
+    assert.equal(func.name, "testfunc");
+    assert.ok(func.ownProperties, "function has JSObject methods")
+  })
+})
+
+function testDescriptor(desc) {
+  assert.strictEqual(desc.configurable, true);
+  assert.strictEqual(desc.enumerable, true);
+  assert.strictEqual(desc.writable, true);
+}
\ No newline at end of file
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/test/test-logs.js b/node_modules/node-firefox-connect/node_modules/firefox-client/test/test-logs.js
new file mode 100644
index 0000000..2c076a6
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/test/test-logs.js
@@ -0,0 +1,106 @@
+var assert = require("assert"),
+    utils = require("./utils");
+
+var tab;
+var Console;
+
+before(function(done) {
+  utils.loadTab('logs.html', function(aTab) {
+    tab = aTab;
+    Console = aTab.Console;
+
+    Console.startListening(function() {
+      done();
+    })
+  });
+});
+
+// Console - startLogging(), stopLogging(), getCachedMessages(),
+// clearCachedMessages(), event:page-error, event:console-api-call
+
+describe('getCachedMessages()', function() {
+  it('should get messages from before listening', function(done) {
+    Console.getCachedLogs(function(err, messages) {
+      assert.strictEqual(err, null);
+
+      var hasLog = messages.some(function(message) {
+        return message.level == "log";
+      })
+      assert.ok(hasLog);
+
+      var hasDir = messages.some(function(message) {
+        return message.level == "dir";
+      })
+      assert.ok(hasDir);
+
+      var hasError = messages.some(function(message) {
+        return message.errorMessage == "ReferenceError: foo is not defined";
+      })
+      assert.ok(hasError);
+      done();
+    });
+  })
+})
+
+describe('clearCachedMessages()', function() {
+  it('should clear cached messages', function(done) {
+    Console.clearCachedLogs(function() {
+      Console.getCachedLogs(function(err, messages) {
+        assert.strictEqual(err, null);
+        // The error message should be left
+        assert.equal(messages.length, 1);
+        assert.equal(messages[0].errorMessage, "ReferenceError: foo is not defined")
+        done();
+      })
+    });
+  })
+})
+
+describe('"page-error" event', function() {
+  it('should receive "page-error" event with message', function(done) {
+    Console.once('page-error', function(event) {
+      assert.equal(event.errorMessage, "ReferenceError: foo is not defined");
+      assert.ok(event.sourceName.indexOf("logs.html") > 0);
+      assert.equal(event.lineNumber, 10);
+      assert.equal(event.columnNumber, 0);
+      assert.ok(event.exception);
+
+      done();
+    });
+
+    tab.reload();
+  })
+})
+
+describe('"console-api-call" event', function() {
+  it('should receive "console-api-call" for console.log', function(done) {
+    Console.on('console-api-call', function(event) {
+      if (event.level == "log") {
+        assert.deepEqual(event.arguments, ["hi"]);
+
+        Console.removeAllListeners('console-api-call');
+        done();
+      }
+    });
+
+    tab.reload();
+  })
+
+  it('should receive "console-api-call" for console.dir', function(done) {
+    Console.on('console-api-call', function(event) {
+      if (event.level == "dir") {
+        var obj = event.arguments[0];
+        assert.ok(obj.ownPropertyNames, "dir argument has JSObject methods");
+
+        Console.removeAllListeners('console-api-call');
+        done();
+      }
+    });
+
+    tab.reload();
+  })
+})
+
+after(function() {
+  Console.stopListening();
+})
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/test/test-network.js b/node_modules/node-firefox-connect/node_modules/firefox-client/test/test-network.js
new file mode 100644
index 0000000..969ec63
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/test/test-network.js
@@ -0,0 +1,88 @@
+var assert = require("assert"),
+    path = require("path"),
+    utils = require("./utils");
+
+var tab;
+var Network;
+var Console;
+
+before(function(done) {
+  utils.loadTab('network.html', function(aTab) {
+    tab = aTab;
+    Network = aTab.Network;
+    Console = aTab.Console;
+
+    Network.startLogging(function(err) {
+      assert.strictEqual(err, null);
+      done();
+    })
+  });
+});
+
+// Network - startLogging(), stopLogging(), sendHTTPRequest(), event:network-event
+
+describe('"network-event" event', function() {
+  it('should receive "network-event" event with message', function(done) {
+    Network.once('network-event', function(event) {
+      assert.equal(event.method, "GET");
+      assert.equal(path.basename(event.url), "test-network.json");
+      assert.ok(event.isXHR);
+      assert.ok(event.getResponseHeaders, "event has NetworkEvent methods")
+      done();
+    });
+
+    Console.evaluateJS("sendRequest()")
+  })
+})
+
+describe('sendHTTPRequest()', function() {
+  it('should send a new XHR request from page', function(done) {
+    var request = {
+      url: "test-network.json",
+      method: "GET",
+      headers: [{name: "test-header", value: "test-value"}]
+    };
+
+    Network.sendHTTPRequest(request, function(err, netEvent) {
+      assert.strictEqual(err, null);
+      assert.ok(netEvent.getResponseHeaders, "event has NetworkEvent methods");
+      done();
+    });
+  })
+})
+
+// NetworkEvent - getRequestHeaders(), getRequestCookies(), getRequestPostData(),
+// getResponseHeaders(), getResponseCookies(), getResponseContent(), getEventTimings()
+// event:update
+
+
+describe('getRequestHeaders(', function() {
+  it('should get request headers', function(done) {
+    Network.on('network-event', function(netEvent) {
+      netEvent.on("request-headers", function(event) {
+        assert.ok(event.headers);
+        assert.ok(event.headersSize);
+
+        netEvent.getRequestHeaders(function(err, resp) {
+          assert.strictEqual(err, null);
+
+          var found = resp.headers.some(function(header) {
+            return header.name == "test-header" &&
+                   header.value == "test-value";
+          });
+          assert.ok(found, "contains that header we sent");
+          done();
+        })
+      })
+    });
+    Console.evaluateJS("sendRequest()");
+  })
+})
+
+// TODO: NetworkEvent tests
+
+after(function() {
+  Network.stopLogging(function(err) {
+    assert.strictEqual(err, null);
+  });
+})
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/test/test-raw.js b/node_modules/node-firefox-connect/node_modules/firefox-client/test/test-raw.js
new file mode 100644
index 0000000..433eede
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/test/test-raw.js
@@ -0,0 +1,26 @@
+var assert = require("assert"),
+    FirefoxClient = require("../index");
+
+var client = new FirefoxClient();
+
+before(function(done) {
+  client.connect(function() {
+    done();
+  })
+});
+
+describe('makeRequest()', function() {
+  it('should do listTabs request', function(done) {
+    var message = {
+      to: 'root',
+      type: 'listTabs'
+    };
+
+    client.client.makeRequest(message, function(resp) {
+      assert.equal(resp.from, "root");
+      assert.ok(resp.tabs);
+      assert.ok(resp.profilerActor)
+      done();
+    })
+  })
+})
\ No newline at end of file
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/test/test-stylesheets.js b/node_modules/node-firefox-connect/node_modules/firefox-client/test/test-stylesheets.js
new file mode 100644
index 0000000..86af8d0
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/test/test-stylesheets.js
@@ -0,0 +1,124 @@
+var assert = require("assert"),
+    path = require("path"),
+    utils = require("./utils");
+
+var StyleSheets;
+var styleSheet;
+
+var SS_TEXT = [
+"main {",
+"  font-family: Georgia, sans-serif;",
+"  color: black;",
+"}",
+"",
+"* {",
+"  padding: 0;",
+"  margin: 0;",
+"}"
+].join("\n");
+
+before(function(done) {
+  utils.loadTab('stylesheets.html', function(aTab) {
+    StyleSheets = aTab.StyleSheets;
+    StyleSheets.getStyleSheets(function(err, sheets) {
+      assert.strictEqual(err, null);
+      styleSheet = sheets[1];
+      done();
+    })
+  });
+});
+
+// Stylesheets - getStyleSheets(), addStyleSheet()
+
+describe('getStyleSheets()', function() {
+  it('should list all the stylesheets', function(done) {
+    StyleSheets.getStyleSheets(function(err, sheets) {
+      assert.strictEqual(err, null);
+
+      var hrefs = sheets.map(function(sheet) {
+        assert.ok(sheet.update, "sheet has Stylesheet methods");
+        return path.basename(sheet.href);
+      });
+      assert.deepEqual(hrefs, ["null", "stylesheet1.css"]);
+      done();
+    })
+  })
+})
+
+describe('addStyleSheet()', function() {
+  it('should add a new stylesheet', function(done) {
+    var text = "div { font-weight: bold; }";
+
+    StyleSheets.addStyleSheet(text, function(err, sheet) {
+      assert.strictEqual(err, null);
+      assert.ok(sheet.update, "sheet has Stylesheet methods");
+      assert.equal(sheet.ruleCount, 1);
+      done();
+    })
+  })
+})
+
+// StyleSheet - update(), toggleDisabled()
+
+describe('StyleSheet', function() {
+  it('should have the correct properties', function() {
+    assert.equal(path.basename(styleSheet.href), "stylesheet1.css");
+    assert.strictEqual(styleSheet.disabled, false);
+    assert.equal(styleSheet.ruleCount, 2);
+  })
+})
+
+describe('StyleSheet.getText()', function() {
+  it('should get the text of the style sheet', function(done) {
+    styleSheet.getText(function(err, resp) {
+      assert.strictEqual(err, null);
+      assert.equal(resp, SS_TEXT);
+      done();
+    })
+  })
+});
+
+describe('StyleSheet.update()', function() {
+  it('should update stylesheet', function(done) {
+    var text = "main { color: red; }";
+
+    styleSheet.update(text, function(err, resp) {
+      assert.strictEqual(err, null);
+      // TODO: assert.equal(styleSheet.ruleCount, 1);
+      done();
+    })
+  })
+})
+
+describe('StyleSheet.toggleDisabled()', function() {
+  it('should toggle disabled attribute', function(done) {
+    assert.deepEqual(styleSheet.disabled, false);
+
+    styleSheet.toggleDisabled(function(err, resp) {
+      assert.strictEqual(err, null);
+      assert.deepEqual(styleSheet.disabled, true);
+      done();
+    })
+  })
+
+  it('should fire disabled-changed event', function(done) {
+    styleSheet.toggleDisabled(function(err, resp) {
+      assert.strictEqual(err, null);
+      assert.deepEqual(styleSheet.disabled, false);
+    })
+    styleSheet.on("disabled-changed", function(disabled) {
+      assert.strictEqual(disabled, false);
+      done();
+    })
+  })
+})
+
+describe('StyleSheet.getOriginalSources()', function() {
+  it('should get no original sources', function(done) {
+    styleSheet.getOriginalSources(function(err, resp) {
+      assert.strictEqual(err, null);
+      assert.deepEqual(resp, []);
+      done();
+    })
+  })
+})
diff --git a/node_modules/node-firefox-connect/node_modules/firefox-client/test/utils.js b/node_modules/node-firefox-connect/node_modules/firefox-client/test/utils.js
new file mode 100644
index 0000000..45dc191
--- /dev/null
+++ b/node_modules/node-firefox-connect/node_modules/firefox-client/test/utils.js
@@ -0,0 +1,35 @@
+var assert = require('assert'),
+    FirefoxClient = require("../index");
+
+var tab;
+
+exports.loadTab = function(url, callback) {
+  getFirstTab(function(tab) {
+    tab.navigateTo(url);
+
+    tab.once("navigate", function() {
+      callback(tab);
+    });
+  })
+};
+
+
+function getFirstTab(callback) {
+  if (tab) {
+    return callback(tab);
+  }
+  var client = new FirefoxClient({log: true});
+
+  client.connect(function() {
+    client.listTabs(function(err, tabs) {
+      if (err) throw err;
+
+      tab = tabs[0];
+
+      tab.attach(function(err) {
+        if (err) throw err;
+        callback(tab);
+      })
+    });
+  });
+}
\ No newline at end of file
diff --git a/node_modules/node-firefox-connect/package.json b/node_modules/node-firefox-connect/package.json
new file mode 100644
index 0000000..9a4c9c0
--- /dev/null
+++ b/node_modules/node-firefox-connect/package.json
@@ -0,0 +1,81 @@
+{
+  "name": "node-firefox-connect",
+  "version": "1.0.0",
+  "description": "Connect to a Firefox simulator",
+  "main": "index.js",
+  "dependencies": {
+    "es6-promise": "^2.0.1",
+    "firefox-client": "^0.3.0"
+  },
+  "devDependencies": {
+    "gulp": "^3.8.10",
+    "node-firefox-build-tools": "^0.1.0",
+    "node-firefox-start-simulator": "^1.0.0",
+    "should": "^4.0.4"
+  },
+  "scripts": {
+    "gulp": "gulp",
+    "test": "gulp test"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/mozilla/node-firefox-connect.git"
+  },
+  "keywords": [
+    "firefox",
+    "developer tools",
+    "b2g",
+    "firefox os",
+    "firefoxos",
+    "fxos",
+    "connect"
+  ],
+  "author": {
+    "name": "Mozilla",
+    "url": "https://mozilla.org/"
+  },
+  "contributors": [
+    {
+      "name": "Nicola Greco",
+      "email": "me@nicola.io",
+      "url": "http://nicolagreco.com/"
+    },
+    {
+      "name": "Brittany Storoz",
+      "email": "bstoroz@mozilla.com"
+    },
+    {
+      "name": "Soledad Penadés",
+      "email": "listas@soledadpenades.com",
+      "url": "http://soledadpenades.com/"
+    }
+  ],
+  "license": "Apache 2.0",
+  "bugs": {
+    "url": "https://github.com/mozilla/node-firefox-connect/issues"
+  },
+  "homepage": "https://github.com/mozilla/node-firefox-connect",
+  "gitHead": "5d4e4f060ea7e90444ded97a4c99e34462ac22cc",
+  "_id": "node-firefox-connect@1.0.0",
+  "_shasum": "3624bbde9ec407a3d75eb9783703e3a02a6ea2b0",
+  "_from": "node-firefox-connect@",
+  "_npmVersion": "2.1.6",
+  "_nodeVersion": "0.10.33",
+  "_npmUser": {
+    "name": "sole",
+    "email": "listas@soledadpenades.com"
+  },
+  "maintainers": [
+    {
+      "name": "sole",
+      "email": "listas@soledadpenades.com"
+    }
+  ],
+  "dist": {
+    "shasum": "3624bbde9ec407a3d75eb9783703e3a02a6ea2b0",
+    "tarball": "http://registry.npmjs.org/node-firefox-connect/-/node-firefox-connect-1.0.0.tgz"
+  },
+  "directories": {},
+  "_resolved": "https://registry.npmjs.org/node-firefox-connect/-/node-firefox-connect-1.0.0.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-connect/test/test.js b/node_modules/node-firefox-connect/test/test.js
new file mode 100644
index 0000000..f7bd584
--- /dev/null
+++ b/node_modules/node-firefox-connect/test/test.js
@@ -0,0 +1,157 @@
+var assert = require("assert");
+var should = require("should");
+var Connect = require("../");
+var Start = require("fxos-start");
+var Ports = require("fx-ports");
+var Q = require('q');
+
+
+describe('fxos-connect', function(){
+  this.timeout(10000);
+  afterEach(function() {
+    Ports({b2g:true}, function(err, instances) {
+      instances.forEach(function(i) {
+        process.kill(i.pid);
+      });
+    });
+  });
+
+  describe('when no simulator is open', function(){
+
+    it('should start a simulator', function(done) {
+      Connect()
+        .then(function(sim) {
+          sim.pid.should.be.type('number');
+          sim.port.should.be.type('number');
+          sim.release.should.be.type('string');
+        })
+        .then(done)
+        .fail(done);
+    });
+
+    it('should match given release', function(done) {
+      Connect({release:['2.1']})
+        .then(function(sim) {
+          sim.release.should.equal('2.1');
+        })
+        .then(done)
+        .fail(done);
+    });
+
+    it('should match given port', function(done) {
+      Connect({port:8081})
+        .then(function(sim) {
+          sim.port.should.equal(8081);
+        })
+        .then(done)
+        .fail(done);
+    });
+  });
+
+  describe('when a simulator is open', function(){
+
+    it('should find a simulator', function(done) {
+      var starting = Start({
+        connect:false,
+        force:true
+      });
+      var connecting = starting.then(function(sim) {
+        return Connect();
+      });
+
+      Q.all([connecting, starting])
+        .spread(function(sim1, sim2) {
+          sim1.pid.should.equal(sim2.pid);
+        })
+        .then(done)
+        .fail(done);
+    });
+
+    it('should reuse if release matches', function(done) {
+      var starting = Start({
+        connect:false,
+        force: true,
+        release: ['2.1']
+      });
+      var connecting = starting.then(function(sim) {
+        return Connect();
+      });
+
+      Q.all([connecting, starting])
+        .spread(function(sim1, sim2) {
+          var regex = new RegExp("^(" + sim2.release + ")");
+          assert(regex.exec(sim1.release));
+        })
+        .then(done)
+        .fail(done);
+    });
+
+
+    it('should reuse if port matches', function(done) {
+      var starting = Start({
+        connect:false,
+        force: true,
+        port: 8081
+      });
+      var connecting = starting.then(function(sim) {
+        return Connect();
+      });
+
+      Q.all([connecting, starting])
+        .spread(function(sim1, sim2) {
+          sim1.port.should.equal(sim2.port);
+        })
+        .then(done)
+        .fail(done);
+    });
+
+    it('should start new sim if port not matching', function(done) {
+      var starting = Start({
+        connect:false,
+        force: true,
+        port: 8081
+      }).fail(done);
+      var connecting = starting.then(function(sim) {
+        return Connect({force:true, port:8082});
+      }).fail(done);
+
+      Q.all([connecting, starting])
+        .spread(function(sim1, sim2) {
+          sim1.pid.should.not.equal(sim2.pid);
+          sim1.port.should.equal(8082);
+        })
+        .then(done)
+        .fail(done);
+    });
+  });
+
+  describe('opts.connect', function(){
+    it('should return a simulator obj with client instance', function(done) {
+      Connect({connect: true})
+        .then(function(sim) {
+          sim.client.disconnect();
+          should.exist(sim.client);
+        })
+        .then(done)
+        .fail(done);
+    });
+  });
+
+  describe('callback', function(){
+    it('should return a simulator obj with client instance', function(done) {
+      Connect(function(err, sim) {
+        sim.client.disconnect();
+        should.exist(sim.client);
+        done();
+      });
+    });
+
+    it('should return a simulator obj', function(done) {
+      Connect({port:8081}, function(err, sim) {
+        sim.port.should.equal(8081);
+        done();
+      });
+    });
+  });
+
+});
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-app/.npmignore b/node_modules/node-firefox-find-app/.npmignore
new file mode 100644
index 0000000..fd4f2b0
--- /dev/null
+++ b/node_modules/node-firefox-find-app/.npmignore
@@ -0,0 +1,2 @@
+node_modules
+.DS_Store
diff --git a/node_modules/node-firefox-find-app/LICENSE b/node_modules/node-firefox-find-app/LICENSE
new file mode 100644
index 0000000..a7c6b5e
--- /dev/null
+++ b/node_modules/node-firefox-find-app/LICENSE
@@ -0,0 +1,13 @@
+Copyright 2015 Mozilla
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/node_modules/node-firefox-find-app/README.md b/node_modules/node-firefox-find-app/README.md
new file mode 100644
index 0000000..197b2d4
--- /dev/null
+++ b/node_modules/node-firefox-find-app/README.md
@@ -0,0 +1,125 @@
+# node-firefox-find-app [![Build Status](https://secure.travis-ci.org/mozilla/node-firefox-find-app.png?branch=master)](http://travis-ci.org/mozilla/node-firefox-find-app)
+
+> Find if an app is installed on a runtime.
+
+This is part of the [node-firefox](https://github.com/mozilla/node-firefox) project.
+
+## Current limitations
+
+We do not support Windows yet. But there are placeholders in the code marked with `TODO: Windows` that indicate where the Windows code would need to be added. If you want to contribute, those are the *gaps* that need to be filled in order for this to work on Windows.
+
+**NOTE**
+
+*This is a work in progress. Things will probably be missing and broken while we move from `fxos-findapp` to `node-firefox-find-app`. Please have a look at the [existing issues](https://github.com/mozilla/node-firefox-find-app/issues), and/or [file more](https://github.com/mozilla/node-firefox-find-app/issues/new) if you find any! :-)*
+
+## Installation
+
+### From git
+
+```bash
+git clone https://github.com/mozilla/node-firefox-find-app.git
+cd node-firefox-find-app
+npm install
+```
+
+If you want to update later on:
+
+```bash
+cd node-firefox-find-app
+git pull origin master
+npm install
+```
+
+### npm
+
+<!--
+```bash
+npm install node-firefox-find-app
+```
+-->
+
+This module is not on npm yet.
+
+## Usage
+
+```javascript
+findApp(options) // returns a Promise
+```
+
+where `options` is a plain `Object` which must contain the following:
+
+* `manifest`: the manifest contents, in JSON format
+* `client`: the remote client where we want to find if this app is installed
+
+If no `options` are provided, or if `options` is an empty `Object` (`{}`), then `findApp` will fail (how can you find *you don't know what app exactly* in *you don't know where*?)
+
+
+### Finding apps in simulators, using the manifest JSON contents
+
+```javascript
+var findApp = require('node-firefox-find-app');
+var startSimulator = require('node-firefox-start-simulator');
+var connect = require('node-firefox-connect');
+var manifest = loadJSON('/path/to/manifest.webapp'));
+
+startSimulator().then(function(simulator) {
+
+  connect(simulator.port).then(function(client) {
+
+    findApp({
+      manifest: manifest,
+      client: client
+    }).then(function(result) {
+
+      if (result.length === 0) {
+        console.log('App is not installed');
+      } else {
+        console.log('Found app!', result);
+      }
+
+      client.disconnect();
+      stopSimulator(simulator);
+
+    });
+
+  });
+
+});
+
+
+```
+
+You can have a look at the `examples` folder for a complete example.
+
+## Running the tests
+
+After installing, you can simply run the following from the module folder:
+
+```bash
+npm test
+```
+
+To add a new unit test file, create a new file in the `tests/unit` folder. Any file that matches `test.*.js` will be run as a test by the appropriate test runner, based on the folder location.
+
+We use `gulp` behind the scenes to run the test; if you don't have it installed globally you can use `npm gulp` from inside the project's root folder to run `gulp`.
+
+### Code quality and style
+
+Because we have multiple contributors working on our projects, we value consistent code styles. It makes it easier to read code written by many people! :-)
+
+Our tests include unit tests as well as code quality ("linting") tests that make sure our test pass a style guide and [JSHint](http://jshint.com/). Instead of submitting code with the wrong indentation or a different style, run the tests and you will be told where your code quality/style differs from ours and instructions on how to fix it.
+
+## History
+
+This is based on initial work on [fxos-findapp](https://github.com/nicola/fxos-findapp) by Nicola Greco.
+
+## License
+
+This program is free software; it is distributed under an
+[Apache License](https://github.com/mozilla/node-firefox-find-app/blob/master/LICENSE).
+
+## Copyright
+
+Copyright (c) 2015 [Mozilla](https://mozilla.org)
+([Contributors](https://github.com/mozilla/node-firefox-find-app/graphs/contributors)).
+
diff --git a/node_modules/node-firefox-find-app/examples/data/app.js b/node_modules/node-firefox-find-app/examples/data/app.js
new file mode 100644
index 0000000..dc7e45e
--- /dev/null
+++ b/node_modules/node-firefox-find-app/examples/data/app.js
@@ -0,0 +1,3 @@
+window.addEventListener("load", function() {
+  console.log("Hello World!");
+});
diff --git a/node_modules/node-firefox-find-app/examples/data/icons/icon128x128.png b/node_modules/node-firefox-find-app/examples/data/icons/icon128x128.png
new file mode 100644
index 0000000..7db00d7
--- /dev/null
+++ b/node_modules/node-firefox-find-app/examples/data/icons/icon128x128.png
Binary files differ
diff --git a/node_modules/node-firefox-find-app/examples/data/icons/icon16x16.png b/node_modules/node-firefox-find-app/examples/data/icons/icon16x16.png
new file mode 100644
index 0000000..1e751ce
--- /dev/null
+++ b/node_modules/node-firefox-find-app/examples/data/icons/icon16x16.png
Binary files differ
diff --git a/node_modules/node-firefox-find-app/examples/data/icons/icon48x48.png b/node_modules/node-firefox-find-app/examples/data/icons/icon48x48.png
new file mode 100644
index 0000000..acc33ff
--- /dev/null
+++ b/node_modules/node-firefox-find-app/examples/data/icons/icon48x48.png
Binary files differ
diff --git a/node_modules/node-firefox-find-app/examples/data/icons/icon60x60.png b/node_modules/node-firefox-find-app/examples/data/icons/icon60x60.png
new file mode 100644
index 0000000..bc14314
--- /dev/null
+++ b/node_modules/node-firefox-find-app/examples/data/icons/icon60x60.png
Binary files differ
diff --git a/node_modules/node-firefox-find-app/examples/data/index.html b/node_modules/node-firefox-find-app/examples/data/index.html
new file mode 100644
index 0000000..238e891
--- /dev/null
+++ b/node_modules/node-firefox-find-app/examples/data/index.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1">
+
+    <title>node-firefox-find-app</title>
+
+    <style>
+      body {
+        border: 1px solid black;
+      }
+    </style>
+
+    <!-- Inline scripts are forbidden in Firefox OS apps (CSP restrictions),
+         so we use a script file. -->
+    <script src="app.js" defer></script>
+
+  </head>
+
+  <body>
+    <!-- This code is in the public domain. Enjoy! -->
+    <h1><tt>node-firefox-find-app</tt></h1>
+    <p>This is a simple test app for demonstrating how to use this module.</p>
+  </body>
+
+</html>
diff --git a/node_modules/node-firefox-find-app/examples/data/manifest.webapp b/node_modules/node-firefox-find-app/examples/data/manifest.webapp
new file mode 100644
index 0000000..3edadc2
--- /dev/null
+++ b/node_modules/node-firefox-find-app/examples/data/manifest.webapp
@@ -0,0 +1,14 @@
+{
+  "name": "node-firefox-find-app",
+  "description": "A test app for this module",
+  "launch_path": "/index.html",
+  "icons": {
+    "16": "/icons/icon16x16.png",
+    "48": "/icons/icon48x48.png",
+    "60": "/icons/icon60x60.png",
+    "128": "/icons/icon128x128.png"
+  },
+  "type": "privileged",
+  "permissions": {
+  }
+}
diff --git a/node_modules/node-firefox-find-app/examples/usage.js b/node_modules/node-firefox-find-app/examples/usage.js
new file mode 100644
index 0000000..4184f76
--- /dev/null
+++ b/node_modules/node-firefox-find-app/examples/usage.js
@@ -0,0 +1,45 @@
+'use strict';
+
+var fs = require('fs');
+var path = require('path');
+var findApp = require('../index.js');
+var startSimulator = require('node-firefox-start-simulator');
+var connect = require('node-firefox-connect');
+
+var manifest = loadJSON(path.join(__dirname, 'data/manifest.webapp'));
+
+startSimulator().then(function(simulator) {
+
+  connect(simulator.port).then(function(client) {
+
+    findApp({
+      manifest: manifest,
+      client: client
+    }).then(function(result) {
+
+      if (result.length === 0) {
+        console.log('App is not installed');
+      } else {
+        console.log('Found app!', result);
+      }
+
+      client.disconnect();
+      stopSimulator(simulator);
+
+    });
+
+  });
+
+});
+
+
+function loadJSON(path) {
+  var data = fs.readFileSync(path, 'utf8');
+  return JSON.parse(data);
+}
+
+
+function stopSimulator(simulator) {
+  process.kill(simulator.pid);
+}
+
diff --git a/node_modules/node-firefox-find-app/gulpfile.js b/node_modules/node-firefox-find-app/gulpfile.js
new file mode 100644
index 0000000..75941db
--- /dev/null
+++ b/node_modules/node-firefox-find-app/gulpfile.js
@@ -0,0 +1,4 @@
+var gulp = require('gulp');
+var buildTools = require('node-firefox-build-tools');
+
+buildTools.loadGulpTasks(gulp);
diff --git a/node_modules/node-firefox-find-app/index.js b/node_modules/node-firefox-find-app/index.js
new file mode 100644
index 0000000..766b334
--- /dev/null
+++ b/node_modules/node-firefox-find-app/index.js
@@ -0,0 +1,70 @@
+'use strict';
+
+// See https://github.com/jshint/jshint/issues/1747 for context
+/* global -Promise */
+var Promise = require('es6-Promise').Promise;
+
+module.exports = findApp;
+
+function findApp(options) {
+  
+  options = options || {};
+
+  var client = options.client;
+  var manifest = options.manifest;
+
+  return new Promise(function(resolve, reject) {
+
+    if (client === undefined || manifest === undefined) {
+      return reject(new Error('We need a client and a manifest to find an app'));
+    }
+
+    getWebAppsActor(client)
+      .then(listInstalledApps)
+      .then(function(installedApps) {
+        // And we filter the installed apps down to
+        // just the apps with the same name as in the manifest
+        var name = manifest.name;
+
+        resolve(installedApps.filter(function(app) {
+          return app.name === name;
+        }));
+      });
+
+  });
+
+}
+
+
+function getWebAppsActor(client) {
+  return new Promise(function(resolve, reject) {
+
+    client.getWebapps(function(err, webapps) {
+      if (err) {
+        return reject(err);
+      }
+
+      resolve(webapps);
+
+    });
+
+  });
+}
+
+
+function listInstalledApps(webAppsActor) {
+  return new Promise(function(resolve, reject) {
+
+    webAppsActor.getInstalledApps(function(err, apps) {
+
+      if (err) {
+        return reject(err);
+      }
+
+      resolve(apps);
+
+    });
+
+  });
+}
+
diff --git a/node_modules/node-firefox-find-app/node_modules/es6-promise/.release.json b/node_modules/node-firefox-find-app/node_modules/es6-promise/.release.json
new file mode 100644
index 0000000..dee8cbc
--- /dev/null
+++ b/node_modules/node-firefox-find-app/node_modules/es6-promise/.release.json
@@ -0,0 +1,17 @@
+{
+  "non-interactive": true,
+  "dry-run": false,
+  "verbose": false,
+  "force": false,
+  "pkgFiles": ["package.json", "bower.json"],
+  "increment": "patch",
+  "commitMessage": "Release %s",
+  "tagName": "%s",
+  "tagAnnotation": "Release %s",
+  "buildCommand": "npm run-script build-all",
+  "distRepo": "git@github.com:components/rsvp.js.git",
+  "distStageDir": "tmp/stage",
+  "distBase": "dist",
+  "distFiles": ["**/*", "../package.json", "../bower.json"],
+  "publish": false
+}
diff --git a/node_modules/node-firefox-find-app/node_modules/es6-promise/Brocfile.js b/node_modules/node-firefox-find-app/node_modules/es6-promise/Brocfile.js
new file mode 100644
index 0000000..d34f458
--- /dev/null
+++ b/node_modules/node-firefox-find-app/node_modules/es6-promise/Brocfile.js
@@ -0,0 +1,75 @@
+/* jshint node:true, undef:true, unused:true */
+var AMDFormatter     = require('es6-module-transpiler-amd-formatter');
+var closureCompiler  = require('broccoli-closure-compiler');
+var compileModules   = require('broccoli-compile-modules');
+var mergeTrees       = require('broccoli-merge-trees');
+var moveFile         = require('broccoli-file-mover');
+var es3Recast        = require('broccoli-es3-safe-recast');
+var concat           = require('broccoli-concat');
+var replace          = require('broccoli-string-replace');
+var calculateVersion = require('./lib/calculateVersion');
+var path             = require('path');
+var trees            = [];
+var env              = process.env.EMBER_ENV || 'development';
+
+var bundle = compileModules('lib', {
+  inputFiles: ['es6-promise.umd.js'],
+  output: '/es6-promise.js',
+  formatter: 'bundle',
+});
+
+trees.push(bundle);
+trees.push(compileModules('lib', {
+  inputFiles: ['**/*.js'],
+  output: '/amd/',
+  formatter: new AMDFormatter()
+}));
+
+if (env === 'production') {
+  trees.push(closureCompiler(moveFile(bundle, {
+    srcFile: 'es6-promise.js',
+    destFile: 'es6-promise.min.js'
+  }), {
+    compilation_level: 'ADVANCED_OPTIMIZATIONS',
+    externs: ['node'],
+  }));
+}
+
+var distTree = mergeTrees(trees.concat('config'));
+var distTrees = [];
+
+distTrees.push(concat(distTree, {
+  inputFiles: [
+    'versionTemplate.txt',
+    'es6-promise.js'
+  ],
+  outputFile: '/es6-promise.js'
+}));
+
+if (env === 'production') {
+  distTrees.push(concat(distTree, {
+    inputFiles: [
+      'versionTemplate.txt',
+      'es6-promise.min.js'
+    ],
+    outputFile: '/es6-promise.min.js'
+  }));
+}
+
+if (env !== 'development') {
+  distTrees = distTrees.map(es3Recast);
+}
+
+distTree = mergeTrees(distTrees);
+var distTree = replace(distTree, {
+  files: [
+    'es6-promise.js',
+    'es6-promise.min.js'
+  ],
+  pattern: {
+    match: /VERSION_PLACEHOLDER_STRING/g,
+    replacement: calculateVersion()
+  }
+});
+
+module.exports = distTree;
diff --git a/node_modules/node-firefox-find-app/node_modules/es6-promise/CHANGELOG.md b/node_modules/node-firefox-find-app/node_modules/es6-promise/CHANGELOG.md
new file mode 100644
index 0000000..e06b496
--- /dev/null
+++ b/node_modules/node-firefox-find-app/node_modules/es6-promise/CHANGELOG.md
@@ -0,0 +1,9 @@
+# Master
+
+# 2.0.0
+
+* re-sync with RSVP. Many large performance improvements and bugfixes.
+
+# 1.0.0
+
+* first subset of RSVP
diff --git a/node_modules/node-firefox-find-app/node_modules/es6-promise/LICENSE b/node_modules/node-firefox-find-app/node_modules/es6-promise/LICENSE
new file mode 100644
index 0000000..954ec59
--- /dev/null
+++ b/node_modules/node-firefox-find-app/node_modules/es6-promise/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/node_modules/node-firefox-find-app/node_modules/es6-promise/README.md b/node_modules/node-firefox-find-app/node_modules/es6-promise/README.md
new file mode 100644
index 0000000..b974ce4
--- /dev/null
+++ b/node_modules/node-firefox-find-app/node_modules/es6-promise/README.md
@@ -0,0 +1,58 @@
+# ES6-Promise (subset of [rsvp.js](https://github.com/tildeio/rsvp.js))
+
+This is a polyfill of the [ES6 Promise](http://people.mozilla.org/~jorendorff/es6-draft.html#sec-promise-constructor). The implementation is a subset of [rsvp.js](https://github.com/tildeio/rsvp.js), if you're wanting extra features and more debugging options, check out the [full library](https://github.com/tildeio/rsvp.js).
+
+For API details and how to use promises, see the <a href="http://www.html5rocks.com/en/tutorials/es6/promises/">JavaScript Promises HTML5Rocks article</a>.
+
+## Downloads
+
+* [es6-promise](https://es6-promises.s3.amazonaws.com/es6-promise-2.0.1.js)
+* [es6-promise-min](https://es6-promises.s3.amazonaws.com/es6-promise-2.0.1.min.js) (~2.2k gzipped)
+
+## Node.js
+
+To install:
+
+```sh
+npm install es6-promise
+```
+
+To use:
+
+```js
+var Promise = require('es6-promise').Promise;
+```
+
+## Usage in IE<9
+
+`catch` is a reserved word in IE<9, meaning `promise.catch(func)` throws a syntax error. To work around this, you can use a string to access the property as shown in the following example.
+
+However, please remember that such technique is already provided by most common minifiers, making the resulting code safe for old browsers and production:
+
+```js
+promise['catch'](function(err) {
+  // ...
+});
+```
+
+Or use `.then` instead:
+
+```js
+promise.then(undefined, function(err) {
+  // ...
+});
+```
+
+## Auto-polyfill
+
+To polyfill the global environment (either in Node or in the browser via CommonJS) use the following code snippet:
+
+```js
+require('es6-promise').polyfill();
+```
+
+Notice that we don't assign the result of `polyfill()` to any variable. The `polyfill()` method will patch the global environment (in this case to the `Promise` name) when called.
+
+## Building & Testing
+
+* `npm run build-all && npm test` - Run Mocha tests through Node and PhantomJS.
diff --git a/node_modules/node-firefox-find-app/node_modules/es6-promise/bin/publish_to_s3.js b/node_modules/node-firefox-find-app/node_modules/es6-promise/bin/publish_to_s3.js
new file mode 100755
index 0000000..7daf49a
--- /dev/null
+++ b/node_modules/node-firefox-find-app/node_modules/es6-promise/bin/publish_to_s3.js
@@ -0,0 +1,28 @@
+#!/usr/bin/env node
+
+// To invoke this from the commandline you need the following to env vars to exist:
+//
+// S3_BUCKET_NAME
+// TRAVIS_BRANCH
+// TRAVIS_TAG
+// TRAVIS_COMMIT
+// S3_SECRET_ACCESS_KEY
+// S3_ACCESS_KEY_ID
+//
+// Once you have those you execute with the following:
+//
+// ```sh
+// ./bin/publish_to_s3.js
+// ```
+var S3Publisher = require('ember-publisher');
+var configPath = require('path').join(__dirname, '../config/s3ProjectConfig.js');
+publisher = new S3Publisher({ projectConfigPath: configPath });
+
+// Always use wildcard section of project config.
+// This is useful when the including library does not
+// require channels (like in ember.js / ember-data).
+publisher.currentBranch = function() {
+  return (process.env.TRAVIS_BRANCH === 'master') ? 'wildcard' : 'no-op';
+};
+publisher.publish();
+
diff --git a/node_modules/node-firefox-find-app/node_modules/es6-promise/bower.json b/node_modules/node-firefox-find-app/node_modules/es6-promise/bower.json
new file mode 100644
index 0000000..84ec5ad
--- /dev/null
+++ b/node_modules/node-firefox-find-app/node_modules/es6-promise/bower.json
@@ -0,0 +1,29 @@
+{
+  "name": "es6-promise",
+  "namespace": "Promise",
+  "version": "2.0.1",
+  "description": "A polyfill for ES6-style Promises, tracking rsvp",
+  "authors": [
+    "Stefan Penner <stefan.penner@gmail.com>"
+  ],
+  "main": "dist/es6-promise.js",
+  "keywords": [
+    "promise"
+  ],
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/jakearchibald/ES6-Promises.git"
+  },
+  "bugs": {
+    "url": "https://github.com/jakearchibald/ES6-Promises/issues"
+  },
+  "license": "MIT",
+  "ignore": [
+    "node_modules",
+    "bower_components",
+    "test",
+    "tests",
+    "vendor",
+    "tasks"
+  ]
+}
diff --git a/node_modules/node-firefox-find-app/node_modules/es6-promise/config/s3ProjectConfig.js b/node_modules/node-firefox-find-app/node_modules/es6-promise/config/s3ProjectConfig.js
new file mode 100644
index 0000000..5f3349a
--- /dev/null
+++ b/node_modules/node-firefox-find-app/node_modules/es6-promise/config/s3ProjectConfig.js
@@ -0,0 +1,26 @@
+/*
+ * Using wildcard because es6-promise does not currently have a
+ * channel system in place.
+ */
+module.exports = function(revision,tag,date){
+  return {
+    'es6-promise.js':
+      { contentType: 'text/javascript',
+        destinations: {
+          wildcard: [
+            'es6-promise-latest.js',
+            'es6-promise-' + revision + '.js'
+          ]
+        }
+      },
+    'es6-promise.min.js':
+      { contentType: 'text/javascript',
+        destinations: {
+          wildcard: [
+            'es6-promise-latest.min.js',
+            'es6-promise-' + revision + '.min.js'
+          ]
+        }
+      }
+  }
+}
diff --git a/node_modules/node-firefox-find-app/node_modules/es6-promise/config/versionTemplate.txt b/node_modules/node-firefox-find-app/node_modules/es6-promise/config/versionTemplate.txt
new file mode 100644
index 0000000..999a5fc
--- /dev/null
+++ b/node_modules/node-firefox-find-app/node_modules/es6-promise/config/versionTemplate.txt
@@ -0,0 +1,7 @@
+/*!
+ * @overview es6-promise - a tiny implementation of Promises/A+.
+ * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald)
+ * @license   Licensed under MIT license
+ *            See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE
+ * @version   VERSION_PLACEHOLDER_STRING
+ */
diff --git a/node_modules/node-firefox-find-app/node_modules/es6-promise/dist/es6-promise.js b/node_modules/node-firefox-find-app/node_modules/es6-promise/dist/es6-promise.js
new file mode 100644
index 0000000..30a6d81
--- /dev/null
+++ b/node_modules/node-firefox-find-app/node_modules/es6-promise/dist/es6-promise.js
@@ -0,0 +1,960 @@
+/*!
+ * @overview es6-promise - a tiny implementation of Promises/A+.
+ * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald)
+ * @license   Licensed under MIT license
+ *            See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE
+ * @version   2.0.1
+ */
+
+(function() {
+    "use strict";
+
+    function $$utils$$objectOrFunction(x) {
+      return typeof x === 'function' || (typeof x === 'object' && x !== null);
+    }
+
+    function $$utils$$isFunction(x) {
+      return typeof x === 'function';
+    }
+
+    function $$utils$$isMaybeThenable(x) {
+      return typeof x === 'object' && x !== null;
+    }
+
+    var $$utils$$_isArray;
+
+    if (!Array.isArray) {
+      $$utils$$_isArray = function (x) {
+        return Object.prototype.toString.call(x) === '[object Array]';
+      };
+    } else {
+      $$utils$$_isArray = Array.isArray;
+    }
+
+    var $$utils$$isArray = $$utils$$_isArray;
+    var $$utils$$now = Date.now || function() { return new Date().getTime(); };
+    function $$utils$$F() { }
+
+    var $$utils$$o_create = (Object.create || function (o) {
+      if (arguments.length > 1) {
+        throw new Error('Second argument not supported');
+      }
+      if (typeof o !== 'object') {
+        throw new TypeError('Argument must be an object');
+      }
+      $$utils$$F.prototype = o;
+      return new $$utils$$F();
+    });
+
+    var $$asap$$len = 0;
+
+    var $$asap$$default = function asap(callback, arg) {
+      $$asap$$queue[$$asap$$len] = callback;
+      $$asap$$queue[$$asap$$len + 1] = arg;
+      $$asap$$len += 2;
+      if ($$asap$$len === 2) {
+        // If len is 1, that means that we need to schedule an async flush.
+        // If additional callbacks are queued before the queue is flushed, they
+        // will be processed by this flush that we are scheduling.
+        $$asap$$scheduleFlush();
+      }
+    };
+
+    var $$asap$$browserGlobal = (typeof window !== 'undefined') ? window : {};
+    var $$asap$$BrowserMutationObserver = $$asap$$browserGlobal.MutationObserver || $$asap$$browserGlobal.WebKitMutationObserver;
+
+    // test for web worker but not in IE10
+    var $$asap$$isWorker = typeof Uint8ClampedArray !== 'undefined' &&
+      typeof importScripts !== 'undefined' &&
+      typeof MessageChannel !== 'undefined';
+
+    // node
+    function $$asap$$useNextTick() {
+      return function() {
+        process.nextTick($$asap$$flush);
+      };
+    }
+
+    function $$asap$$useMutationObserver() {
+      var iterations = 0;
+      var observer = new $$asap$$BrowserMutationObserver($$asap$$flush);
+      var node = document.createTextNode('');
+      observer.observe(node, { characterData: true });
+
+      return function() {
+        node.data = (iterations = ++iterations % 2);
+      };
+    }
+
+    // web worker
+    function $$asap$$useMessageChannel() {
+      var channel = new MessageChannel();
+      channel.port1.onmessage = $$asap$$flush;
+      return function () {
+        channel.port2.postMessage(0);
+      };
+    }
+
+    function $$asap$$useSetTimeout() {
+      return function() {
+        setTimeout($$asap$$flush, 1);
+      };
+    }
+
+    var $$asap$$queue = new Array(1000);
+
+    function $$asap$$flush() {
+      for (var i = 0; i < $$asap$$len; i+=2) {
+        var callback = $$asap$$queue[i];
+        var arg = $$asap$$queue[i+1];
+
+        callback(arg);
+
+        $$asap$$queue[i] = undefined;
+        $$asap$$queue[i+1] = undefined;
+      }
+
+      $$asap$$len = 0;
+    }
+
+    var $$asap$$scheduleFlush;
+
+    // Decide what async method to use to triggering processing of queued callbacks:
+    if (typeof process !== 'undefined' && {}.toString.call(process) === '[object process]') {
+      $$asap$$scheduleFlush = $$asap$$useNextTick();
+    } else if ($$asap$$BrowserMutationObserver) {
+      $$asap$$scheduleFlush = $$asap$$useMutationObserver();
+    } else if ($$asap$$isWorker) {
+      $$asap$$scheduleFlush = $$asap$$useMessageChannel();
+    } else {
+      $$asap$$scheduleFlush = $$asap$$useSetTimeout();
+    }
+
+    function $$$internal$$noop() {}
+    var $$$internal$$PENDING   = void 0;
+    var $$$internal$$FULFILLED = 1;
+    var $$$internal$$REJECTED  = 2;
+    var $$$internal$$GET_THEN_ERROR = new $$$internal$$ErrorObject();
+
+    function $$$internal$$selfFullfillment() {
+      return new TypeError("You cannot resolve a promise with itself");
+    }
+
+    function $$$internal$$cannotReturnOwn() {
+      return new TypeError('A promises callback cannot return that same promise.')
+    }
+
+    function $$$internal$$getThen(promise) {
+      try {
+        return promise.then;
+      } catch(error) {
+        $$$internal$$GET_THEN_ERROR.error = error;
+        return $$$internal$$GET_THEN_ERROR;
+      }
+    }
+
+    function $$$internal$$tryThen(then, value, fulfillmentHandler, rejectionHandler) {
+      try {
+        then.call(value, fulfillmentHandler, rejectionHandler);
+      } catch(e) {
+        return e;
+      }
+    }
+
+    function $$$internal$$handleForeignThenable(promise, thenable, then) {
+       $$asap$$default(function(promise) {
+        var sealed = false;
+        var error = $$$internal$$tryThen(then, thenable, function(value) {
+          if (sealed) { return; }
+          sealed = true;
+          if (thenable !== value) {
+            $$$internal$$resolve(promise, value);
+          } else {
+            $$$internal$$fulfill(promise, value);
+          }
+        }, function(reason) {
+          if (sealed) { return; }
+          sealed = true;
+
+          $$$internal$$reject(promise, reason);
+        }, 'Settle: ' + (promise._label || ' unknown promise'));
+
+        if (!sealed && error) {
+          sealed = true;
+          $$$internal$$reject(promise, error);
+        }
+      }, promise);
+    }
+
+    function $$$internal$$handleOwnThenable(promise, thenable) {
+      if (thenable._state === $$$internal$$FULFILLED) {
+        $$$internal$$fulfill(promise, thenable._result);
+      } else if (promise._state === $$$internal$$REJECTED) {
+        $$$internal$$reject(promise, thenable._result);
+      } else {
+        $$$internal$$subscribe(thenable, undefined, function(value) {
+          $$$internal$$resolve(promise, value);
+        }, function(reason) {
+          $$$internal$$reject(promise, reason);
+        });
+      }
+    }
+
+    function $$$internal$$handleMaybeThenable(promise, maybeThenable) {
+      if (maybeThenable.constructor === promise.constructor) {
+        $$$internal$$handleOwnThenable(promise, maybeThenable);
+      } else {
+        var then = $$$internal$$getThen(maybeThenable);
+
+        if (then === $$$internal$$GET_THEN_ERROR) {
+          $$$internal$$reject(promise, $$$internal$$GET_THEN_ERROR.error);
+        } else if (then === undefined) {
+          $$$internal$$fulfill(promise, maybeThenable);
+        } else if ($$utils$$isFunction(then)) {
+          $$$internal$$handleForeignThenable(promise, maybeThenable, then);
+        } else {
+          $$$internal$$fulfill(promise, maybeThenable);
+        }
+      }
+    }
+
+    function $$$internal$$resolve(promise, value) {
+      if (promise === value) {
+        $$$internal$$reject(promise, $$$internal$$selfFullfillment());
+      } else if ($$utils$$objectOrFunction(value)) {
+        $$$internal$$handleMaybeThenable(promise, value);
+      } else {
+        $$$internal$$fulfill(promise, value);
+      }
+    }
+
+    function $$$internal$$publishRejection(promise) {
+      if (promise._onerror) {
+        promise._onerror(promise._result);
+      }
+
+      $$$internal$$publish(promise);
+    }
+
+    function $$$internal$$fulfill(promise, value) {
+      if (promise._state !== $$$internal$$PENDING) { return; }
+
+      promise._result = value;
+      promise._state = $$$internal$$FULFILLED;
+
+      if (promise._subscribers.length === 0) {
+      } else {
+        $$asap$$default($$$internal$$publish, promise);
+      }
+    }
+
+    function $$$internal$$reject(promise, reason) {
+      if (promise._state !== $$$internal$$PENDING) { return; }
+      promise._state = $$$internal$$REJECTED;
+      promise._result = reason;
+
+      $$asap$$default($$$internal$$publishRejection, promise);
+    }
+
+    function $$$internal$$subscribe(parent, child, onFulfillment, onRejection) {
+      var subscribers = parent._subscribers;
+      var length = subscribers.length;
+
+      parent._onerror = null;
+
+      subscribers[length] = child;
+      subscribers[length + $$$internal$$FULFILLED] = onFulfillment;
+      subscribers[length + $$$internal$$REJECTED]  = onRejection;
+
+      if (length === 0 && parent._state) {
+        $$asap$$default($$$internal$$publish, parent);
+      }
+    }
+
+    function $$$internal$$publish(promise) {
+      var subscribers = promise._subscribers;
+      var settled = promise._state;
+
+      if (subscribers.length === 0) { return; }
+
+      var child, callback, detail = promise._result;
+
+      for (var i = 0; i < subscribers.length; i += 3) {
+        child = subscribers[i];
+        callback = subscribers[i + settled];
+
+        if (child) {
+          $$$internal$$invokeCallback(settled, child, callback, detail);
+        } else {
+          callback(detail);
+        }
+      }
+
+      promise._subscribers.length = 0;
+    }
+
+    function $$$internal$$ErrorObject() {
+      this.error = null;
+    }
+
+    var $$$internal$$TRY_CATCH_ERROR = new $$$internal$$ErrorObject();
+
+    function $$$internal$$tryCatch(callback, detail) {
+      try {
+        return callback(detail);
+      } catch(e) {
+        $$$internal$$TRY_CATCH_ERROR.error = e;
+        return $$$internal$$TRY_CATCH_ERROR;
+      }
+    }
+
+    function $$$internal$$invokeCallback(settled, promise, callback, detail) {
+      var hasCallback = $$utils$$isFunction(callback),
+          value, error, succeeded, failed;
+
+      if (hasCallback) {
+        value = $$$internal$$tryCatch(callback, detail);
+
+        if (value === $$$internal$$TRY_CATCH_ERROR) {
+          failed = true;
+          error = value.error;
+          value = null;
+        } else {
+          succeeded = true;
+        }
+
+        if (promise === value) {
+          $$$internal$$reject(promise, $$$internal$$cannotReturnOwn());
+          return;
+        }
+
+      } else {
+        value = detail;
+        succeeded = true;
+      }
+
+      if (promise._state !== $$$internal$$PENDING) {
+        // noop
+      } else if (hasCallback && succeeded) {
+        $$$internal$$resolve(promise, value);
+      } else if (failed) {
+        $$$internal$$reject(promise, error);
+      } else if (settled === $$$internal$$FULFILLED) {
+        $$$internal$$fulfill(promise, value);
+      } else if (settled === $$$internal$$REJECTED) {
+        $$$internal$$reject(promise, value);
+      }
+    }
+
+    function $$$internal$$initializePromise(promise, resolver) {
+      try {
+        resolver(function resolvePromise(value){
+          $$$internal$$resolve(promise, value);
+        }, function rejectPromise(reason) {
+          $$$internal$$reject(promise, reason);
+        });
+      } catch(e) {
+        $$$internal$$reject(promise, e);
+      }
+    }
+
+    function $$$enumerator$$makeSettledResult(state, position, value) {
+      if (state === $$$internal$$FULFILLED) {
+        return {
+          state: 'fulfilled',
+          value: value
+        };
+      } else {
+        return {
+          state: 'rejected',
+          reason: value
+        };
+      }
+    }
+
+    function $$$enumerator$$Enumerator(Constructor, input, abortOnReject, label) {
+      this._instanceConstructor = Constructor;
+      this.promise = new Constructor($$$internal$$noop, label);
+      this._abortOnReject = abortOnReject;
+
+      if (this._validateInput(input)) {
+        this._input     = input;
+        this.length     = input.length;
+        this._remaining = input.length;
+
+        this._init();
+
+        if (this.length === 0) {
+          $$$internal$$fulfill(this.promise, this._result);
+        } else {
+          this.length = this.length || 0;
+          this._enumerate();
+          if (this._remaining === 0) {
+            $$$internal$$fulfill(this.promise, this._result);
+          }
+        }
+      } else {
+        $$$internal$$reject(this.promise, this._validationError());
+      }
+    }
+
+    $$$enumerator$$Enumerator.prototype._validateInput = function(input) {
+      return $$utils$$isArray(input);
+    };
+
+    $$$enumerator$$Enumerator.prototype._validationError = function() {
+      return new Error('Array Methods must be provided an Array');
+    };
+
+    $$$enumerator$$Enumerator.prototype._init = function() {
+      this._result = new Array(this.length);
+    };
+
+    var $$$enumerator$$default = $$$enumerator$$Enumerator;
+
+    $$$enumerator$$Enumerator.prototype._enumerate = function() {
+      var length  = this.length;
+      var promise = this.promise;
+      var input   = this._input;
+
+      for (var i = 0; promise._state === $$$internal$$PENDING && i < length; i++) {
+        this._eachEntry(input[i], i);
+      }
+    };
+
+    $$$enumerator$$Enumerator.prototype._eachEntry = function(entry, i) {
+      var c = this._instanceConstructor;
+      if ($$utils$$isMaybeThenable(entry)) {
+        if (entry.constructor === c && entry._state !== $$$internal$$PENDING) {
+          entry._onerror = null;
+          this._settledAt(entry._state, i, entry._result);
+        } else {
+          this._willSettleAt(c.resolve(entry), i);
+        }
+      } else {
+        this._remaining--;
+        this._result[i] = this._makeResult($$$internal$$FULFILLED, i, entry);
+      }
+    };
+
+    $$$enumerator$$Enumerator.prototype._settledAt = function(state, i, value) {
+      var promise = this.promise;
+
+      if (promise._state === $$$internal$$PENDING) {
+        this._remaining--;
+
+        if (this._abortOnReject && state === $$$internal$$REJECTED) {
+          $$$internal$$reject(promise, value);
+        } else {
+          this._result[i] = this._makeResult(state, i, value);
+        }
+      }
+
+      if (this._remaining === 0) {
+        $$$internal$$fulfill(promise, this._result);
+      }
+    };
+
+    $$$enumerator$$Enumerator.prototype._makeResult = function(state, i, value) {
+      return value;
+    };
+
+    $$$enumerator$$Enumerator.prototype._willSettleAt = function(promise, i) {
+      var enumerator = this;
+
+      $$$internal$$subscribe(promise, undefined, function(value) {
+        enumerator._settledAt($$$internal$$FULFILLED, i, value);
+      }, function(reason) {
+        enumerator._settledAt($$$internal$$REJECTED, i, reason);
+      });
+    };
+
+    var $$promise$all$$default = function all(entries, label) {
+      return new $$$enumerator$$default(this, entries, true /* abort on reject */, label).promise;
+    };
+
+    var $$promise$race$$default = function race(entries, label) {
+      /*jshint validthis:true */
+      var Constructor = this;
+
+      var promise = new Constructor($$$internal$$noop, label);
+
+      if (!$$utils$$isArray(entries)) {
+        $$$internal$$reject(promise, new TypeError('You must pass an array to race.'));
+        return promise;
+      }
+
+      var length = entries.length;
+
+      function onFulfillment(value) {
+        $$$internal$$resolve(promise, value);
+      }
+
+      function onRejection(reason) {
+        $$$internal$$reject(promise, reason);
+      }
+
+      for (var i = 0; promise._state === $$$internal$$PENDING && i < length; i++) {
+        $$$internal$$subscribe(Constructor.resolve(entries[i]), undefined, onFulfillment, onRejection);
+      }
+
+      return promise;
+    };
+
+    var $$promise$resolve$$default = function resolve(object, label) {
+      /*jshint validthis:true */
+      var Constructor = this;
+
+      if (object && typeof object === 'object' && object.constructor === Constructor) {
+        return object;
+      }
+
+      var promise = new Constructor($$$internal$$noop, label);
+      $$$internal$$resolve(promise, object);
+      return promise;
+    };
+
+    var $$promise$reject$$default = function reject(reason, label) {
+      /*jshint validthis:true */
+      var Constructor = this;
+      var promise = new Constructor($$$internal$$noop, label);
+      $$$internal$$reject(promise, reason);
+      return promise;
+    };
+
+    var $$es6$promise$promise$$counter = 0;
+
+    function $$es6$promise$promise$$needsResolver() {
+      throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
+    }
+
+    function $$es6$promise$promise$$needsNew() {
+      throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");
+    }
+
+    var $$es6$promise$promise$$default = $$es6$promise$promise$$Promise;
+
+    /**
+      Promise objects represent the eventual result of an asynchronous operation. The
+      primary way of interacting with a promise is through its `then` method, which
+      registers callbacks to receive either a promise’s eventual value or the reason
+      why the promise cannot be fulfilled.
+
+      Terminology
+      -----------
+
+      - `promise` is an object or function with a `then` method whose behavior conforms to this specification.
+      - `thenable` is an object or function that defines a `then` method.
+      - `value` is any legal JavaScript value (including undefined, a thenable, or a promise).
+      - `exception` is a value that is thrown using the throw statement.
+      - `reason` is a value that indicates why a promise was rejected.
+      - `settled` the final resting state of a promise, fulfilled or rejected.
+
+      A promise can be in one of three states: pending, fulfilled, or rejected.
+
+      Promises that are fulfilled have a fulfillment value and are in the fulfilled
+      state.  Promises that are rejected have a rejection reason and are in the
+      rejected state.  A fulfillment value is never a thenable.
+
+      Promises can also be said to *resolve* a value.  If this value is also a
+      promise, then the original promise's settled state will match the value's
+      settled state.  So a promise that *resolves* a promise that rejects will
+      itself reject, and a promise that *resolves* a promise that fulfills will
+      itself fulfill.
+
+
+      Basic Usage:
+      ------------
+
+      ```js
+      var promise = new Promise(function(resolve, reject) {
+        // on success
+        resolve(value);
+
+        // on failure
+        reject(reason);
+      });
+
+      promise.then(function(value) {
+        // on fulfillment
+      }, function(reason) {
+        // on rejection
+      });
+      ```
+
+      Advanced Usage:
+      ---------------
+
+      Promises shine when abstracting away asynchronous interactions such as
+      `XMLHttpRequest`s.
+
+      ```js
+      function getJSON(url) {
+        return new Promise(function(resolve, reject){
+          var xhr = new XMLHttpRequest();
+
+          xhr.open('GET', url);
+          xhr.onreadystatechange = handler;
+          xhr.responseType = 'json';
+          xhr.setRequestHeader('Accept', 'application/json');
+          xhr.send();
+
+          function handler() {
+            if (this.readyState === this.DONE) {
+              if (this.status === 200) {
+                resolve(this.response);
+              } else {
+                reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']'));
+              }
+            }
+          };
+        });
+      }
+
+      getJSON('/posts.json').then(function(json) {
+        // on fulfillment
+      }, function(reason) {
+        // on rejection
+      });
+      ```
+
+      Unlike callbacks, promises are great composable primitives.
+
+      ```js
+      Promise.all([
+        getJSON('/posts'),
+        getJSON('/comments')
+      ]).then(function(values){
+        values[0] // => postsJSON
+        values[1] // => commentsJSON
+
+        return values;
+      });
+      ```
+
+      @class Promise
+      @param {function} resolver
+      Useful for tooling.
+      @constructor
+    */
+    function $$es6$promise$promise$$Promise(resolver) {
+      this._id = $$es6$promise$promise$$counter++;
+      this._state = undefined;
+      this._result = undefined;
+      this._subscribers = [];
+
+      if ($$$internal$$noop !== resolver) {
+        if (!$$utils$$isFunction(resolver)) {
+          $$es6$promise$promise$$needsResolver();
+        }
+
+        if (!(this instanceof $$es6$promise$promise$$Promise)) {
+          $$es6$promise$promise$$needsNew();
+        }
+
+        $$$internal$$initializePromise(this, resolver);
+      }
+    }
+
+    $$es6$promise$promise$$Promise.all = $$promise$all$$default;
+    $$es6$promise$promise$$Promise.race = $$promise$race$$default;
+    $$es6$promise$promise$$Promise.resolve = $$promise$resolve$$default;
+    $$es6$promise$promise$$Promise.reject = $$promise$reject$$default;
+
+    $$es6$promise$promise$$Promise.prototype = {
+      constructor: $$es6$promise$promise$$Promise,
+
+    /**
+      The primary way of interacting with a promise is through its `then` method,
+      which registers callbacks to receive either a promise's eventual value or the
+      reason why the promise cannot be fulfilled.
+
+      ```js
+      findUser().then(function(user){
+        // user is available
+      }, function(reason){
+        // user is unavailable, and you are given the reason why
+      });
+      ```
+
+      Chaining
+      --------
+
+      The return value of `then` is itself a promise.  This second, 'downstream'
+      promise is resolved with the return value of the first promise's fulfillment
+      or rejection handler, or rejected if the handler throws an exception.
+
+      ```js
+      findUser().then(function (user) {
+        return user.name;
+      }, function (reason) {
+        return 'default name';
+      }).then(function (userName) {
+        // If `findUser` fulfilled, `userName` will be the user's name, otherwise it
+        // will be `'default name'`
+      });
+
+      findUser().then(function (user) {
+        throw new Error('Found user, but still unhappy');
+      }, function (reason) {
+        throw new Error('`findUser` rejected and we're unhappy');
+      }).then(function (value) {
+        // never reached
+      }, function (reason) {
+        // if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'.
+        // If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'.
+      });
+      ```
+      If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream.
+
+      ```js
+      findUser().then(function (user) {
+        throw new PedagogicalException('Upstream error');
+      }).then(function (value) {
+        // never reached
+      }).then(function (value) {
+        // never reached
+      }, function (reason) {
+        // The `PedgagocialException` is propagated all the way down to here
+      });
+      ```
+
+      Assimilation
+      ------------
+
+      Sometimes the value you want to propagate to a downstream promise can only be
+      retrieved asynchronously. This can be achieved by returning a promise in the
+      fulfillment or rejection handler. The downstream promise will then be pending
+      until the returned promise is settled. This is called *assimilation*.
+
+      ```js
+      findUser().then(function (user) {
+        return findCommentsByAuthor(user);
+      }).then(function (comments) {
+        // The user's comments are now available
+      });
+      ```
+
+      If the assimliated promise rejects, then the downstream promise will also reject.
+
+      ```js
+      findUser().then(function (user) {
+        return findCommentsByAuthor(user);
+      }).then(function (comments) {
+        // If `findCommentsByAuthor` fulfills, we'll have the value here
+      }, function (reason) {
+        // If `findCommentsByAuthor` rejects, we'll have the reason here
+      });
+      ```
+
+      Simple Example
+      --------------
+
+      Synchronous Example
+
+      ```javascript
+      var result;
+
+      try {
+        result = findResult();
+        // success
+      } catch(reason) {
+        // failure
+      }
+      ```
+
+      Errback Example
+
+      ```js
+      findResult(function(result, err){
+        if (err) {
+          // failure
+        } else {
+          // success
+        }
+      });
+      ```
+
+      Promise Example;
+
+      ```javascript
+      findResult().then(function(result){
+        // success
+      }, function(reason){
+        // failure
+      });
+      ```
+
+      Advanced Example
+      --------------
+
+      Synchronous Example
+
+      ```javascript
+      var author, books;
+
+      try {
+        author = findAuthor();
+        books  = findBooksByAuthor(author);
+        // success
+      } catch(reason) {
+        // failure
+      }
+      ```
+
+      Errback Example
+
+      ```js
+
+      function foundBooks(books) {
+
+      }
+
+      function failure(reason) {
+
+      }
+
+      findAuthor(function(author, err){
+        if (err) {
+          failure(err);
+          // failure
+        } else {
+          try {
+            findBoooksByAuthor(author, function(books, err) {
+              if (err) {
+                failure(err);
+              } else {
+                try {
+                  foundBooks(books);
+                } catch(reason) {
+                  failure(reason);
+                }
+              }
+            });
+          } catch(error) {
+            failure(err);
+          }
+          // success
+        }
+      });
+      ```
+
+      Promise Example;
+
+      ```javascript
+      findAuthor().
+        then(findBooksByAuthor).
+        then(function(books){
+          // found books
+      }).catch(function(reason){
+        // something went wrong
+      });
+      ```
+
+      @method then
+      @param {Function} onFulfilled
+      @param {Function} onRejected
+      Useful for tooling.
+      @return {Promise}
+    */
+      then: function(onFulfillment, onRejection) {
+        var parent = this;
+        var state = parent._state;
+
+        if (state === $$$internal$$FULFILLED && !onFulfillment || state === $$$internal$$REJECTED && !onRejection) {
+          return this;
+        }
+
+        var child = new this.constructor($$$internal$$noop);
+        var result = parent._result;
+
+        if (state) {
+          var callback = arguments[state - 1];
+          $$asap$$default(function(){
+            $$$internal$$invokeCallback(state, child, callback, result);
+          });
+        } else {
+          $$$internal$$subscribe(parent, child, onFulfillment, onRejection);
+        }
+
+        return child;
+      },
+
+    /**
+      `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same
+      as the catch block of a try/catch statement.
+
+      ```js
+      function findAuthor(){
+        throw new Error('couldn't find that author');
+      }
+
+      // synchronous
+      try {
+        findAuthor();
+      } catch(reason) {
+        // something went wrong
+      }
+
+      // async with promises
+      findAuthor().catch(function(reason){
+        // something went wrong
+      });
+      ```
+
+      @method catch
+      @param {Function} onRejection
+      Useful for tooling.
+      @return {Promise}
+    */
+      'catch': function(onRejection) {
+        return this.then(null, onRejection);
+      }
+    };
+
+    var $$es6$promise$polyfill$$default = function polyfill() {
+      var local;
+
+      if (typeof global !== 'undefined') {
+        local = global;
+      } else if (typeof window !== 'undefined' && window.document) {
+        local = window;
+      } else {
+        local = self;
+      }
+
+      var es6PromiseSupport =
+        "Promise" in local &&
+        // Some of these methods are missing from
+        // Firefox/Chrome experimental implementations
+        "resolve" in local.Promise &&
+        "reject" in local.Promise &&
+        "all" in local.Promise &&
+        "race" in local.Promise &&
+        // Older version of the spec had a resolver object
+        // as the arg rather than a function
+        (function() {
+          var resolve;
+          new local.Promise(function(r) { resolve = r; });
+          return $$utils$$isFunction(resolve);
+        }());
+
+      if (!es6PromiseSupport) {
+        local.Promise = $$es6$promise$promise$$default;
+      }
+    };
+
+    var es6$promise$umd$$ES6Promise = {
+      'Promise': $$es6$promise$promise$$default,
+      'polyfill': $$es6$promise$polyfill$$default
+    };
+
+    /* global define:true module:true window: true */
+    if (typeof define === 'function' && define['amd']) {
+      define(function() { return es6$promise$umd$$ES6Promise; });
+    } else if (typeof module !== 'undefined' && module['exports']) {
+      module['exports'] = es6$promise$umd$$ES6Promise;
+    } else if (typeof this !== 'undefined') {
+      this['ES6Promise'] = es6$promise$umd$$ES6Promise;
+    }
+}).call(this);
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-app/node_modules/es6-promise/dist/es6-promise.min.js b/node_modules/node-firefox-find-app/node_modules/es6-promise/dist/es6-promise.min.js
new file mode 100644
index 0000000..6e0c99f
--- /dev/null
+++ b/node_modules/node-firefox-find-app/node_modules/es6-promise/dist/es6-promise.min.js
@@ -0,0 +1,18 @@
+/*!
+ * @overview es6-promise - a tiny implementation of Promises/A+.
+ * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald)
+ * @license   Licensed under MIT license
+ *            See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE
+ * @version   2.0.1
+ */
+
+(function(){function r(a,b){n[l]=a;n[l+1]=b;l+=2;2===l&&A()}function s(a){return"function"===typeof a}function F(){return function(){process.nextTick(t)}}function G(){var a=0,b=new B(t),c=document.createTextNode("");b.observe(c,{characterData:!0});return function(){c.data=a=++a%2}}function H(){var a=new MessageChannel;a.port1.onmessage=t;return function(){a.port2.postMessage(0)}}function I(){return function(){setTimeout(t,1)}}function t(){for(var a=0;a<l;a+=2)(0,n[a])(n[a+1]),n[a]=void 0,n[a+1]=void 0;
+l=0}function p(){}function J(a,b,c,d){try{a.call(b,c,d)}catch(e){return e}}function K(a,b,c){r(function(a){var e=!1,f=J(c,b,function(c){e||(e=!0,b!==c?q(a,c):m(a,c))},function(b){e||(e=!0,g(a,b))});!e&&f&&(e=!0,g(a,f))},a)}function L(a,b){1===b.a?m(a,b.b):2===a.a?g(a,b.b):u(b,void 0,function(b){q(a,b)},function(b){g(a,b)})}function q(a,b){if(a===b)g(a,new TypeError("You cannot resolve a promise with itself"));else if("function"===typeof b||"object"===typeof b&&null!==b)if(b.constructor===a.constructor)L(a,
+b);else{var c;try{c=b.then}catch(d){v.error=d,c=v}c===v?g(a,v.error):void 0===c?m(a,b):s(c)?K(a,b,c):m(a,b)}else m(a,b)}function M(a){a.f&&a.f(a.b);x(a)}function m(a,b){void 0===a.a&&(a.b=b,a.a=1,0!==a.e.length&&r(x,a))}function g(a,b){void 0===a.a&&(a.a=2,a.b=b,r(M,a))}function u(a,b,c,d){var e=a.e,f=e.length;a.f=null;e[f]=b;e[f+1]=c;e[f+2]=d;0===f&&a.a&&r(x,a)}function x(a){var b=a.e,c=a.a;if(0!==b.length){for(var d,e,f=a.b,g=0;g<b.length;g+=3)d=b[g],e=b[g+c],d?C(c,d,e,f):e(f);a.e.length=0}}function D(){this.error=
+null}function C(a,b,c,d){var e=s(c),f,k,h,l;if(e){try{f=c(d)}catch(n){y.error=n,f=y}f===y?(l=!0,k=f.error,f=null):h=!0;if(b===f){g(b,new TypeError("A promises callback cannot return that same promise."));return}}else f=d,h=!0;void 0===b.a&&(e&&h?q(b,f):l?g(b,k):1===a?m(b,f):2===a&&g(b,f))}function N(a,b){try{b(function(b){q(a,b)},function(b){g(a,b)})}catch(c){g(a,c)}}function k(a,b,c,d){this.n=a;this.c=new a(p,d);this.i=c;this.o(b)?(this.m=b,this.d=this.length=b.length,this.l(),0===this.length?m(this.c,
+this.b):(this.length=this.length||0,this.k(),0===this.d&&m(this.c,this.b))):g(this.c,this.p())}function h(a){O++;this.b=this.a=void 0;this.e=[];if(p!==a){if(!s(a))throw new TypeError("You must pass a resolver function as the first argument to the promise constructor");if(!(this instanceof h))throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");N(this,a)}}var E=Array.isArray?Array.isArray:function(a){return"[object Array]"===
+Object.prototype.toString.call(a)},l=0,w="undefined"!==typeof window?window:{},B=w.MutationObserver||w.WebKitMutationObserver,w="undefined"!==typeof Uint8ClampedArray&&"undefined"!==typeof importScripts&&"undefined"!==typeof MessageChannel,n=Array(1E3),A;A="undefined"!==typeof process&&"[object process]"==={}.toString.call(process)?F():B?G():w?H():I();var v=new D,y=new D;k.prototype.o=function(a){return E(a)};k.prototype.p=function(){return Error("Array Methods must be provided an Array")};k.prototype.l=
+function(){this.b=Array(this.length)};k.prototype.k=function(){for(var a=this.length,b=this.c,c=this.m,d=0;void 0===b.a&&d<a;d++)this.j(c[d],d)};k.prototype.j=function(a,b){var c=this.n;"object"===typeof a&&null!==a?a.constructor===c&&void 0!==a.a?(a.f=null,this.g(a.a,b,a.b)):this.q(c.resolve(a),b):(this.d--,this.b[b]=this.h(a))};k.prototype.g=function(a,b,c){var d=this.c;void 0===d.a&&(this.d--,this.i&&2===a?g(d,c):this.b[b]=this.h(c));0===this.d&&m(d,this.b)};k.prototype.h=function(a){return a};
+k.prototype.q=function(a,b){var c=this;u(a,void 0,function(a){c.g(1,b,a)},function(a){c.g(2,b,a)})};var O=0;h.all=function(a,b){return(new k(this,a,!0,b)).c};h.race=function(a,b){function c(a){q(e,a)}function d(a){g(e,a)}var e=new this(p,b);if(!E(a))return (g(e,new TypeError("You must pass an array to race.")), e);for(var f=a.length,h=0;void 0===e.a&&h<f;h++)u(this.resolve(a[h]),void 0,c,d);return e};h.resolve=function(a,b){if(a&&"object"===typeof a&&a.constructor===this)return a;var c=new this(p,b);
+q(c,a);return c};h.reject=function(a,b){var c=new this(p,b);g(c,a);return c};h.prototype={constructor:h,then:function(a,b){var c=this.a;if(1===c&&!a||2===c&&!b)return this;var d=new this.constructor(p),e=this.b;if(c){var f=arguments[c-1];r(function(){C(c,d,f,e)})}else u(this,d,a,b);return d},"catch":function(a){return this.then(null,a)}};var z={Promise:h,polyfill:function(){var a;a="undefined"!==typeof global?global:"undefined"!==typeof window&&window.document?window:self;"Promise"in a&&"resolve"in
+a.Promise&&"reject"in a.Promise&&"all"in a.Promise&&"race"in a.Promise&&function(){var b;new a.Promise(function(a){b=a});return s(b)}()||(a.Promise=h)}};"function"===typeof define&&define.amd?define(function(){return z}):"undefined"!==typeof module&&module.exports?module.exports=z:"undefined"!==typeof this&&(this.ES6Promise=z)}).call(this);
diff --git a/node_modules/node-firefox-find-app/node_modules/es6-promise/lib/calculateVersion.js b/node_modules/node-firefox-find-app/node_modules/es6-promise/lib/calculateVersion.js
new file mode 100644
index 0000000..018e364
--- /dev/null
+++ b/node_modules/node-firefox-find-app/node_modules/es6-promise/lib/calculateVersion.js
@@ -0,0 +1,36 @@
+'use strict';
+
+var fs   = require('fs');
+var path = require('path');
+
+module.exports = function () {
+  var packageVersion = require('../package.json').version;
+  var output         = [packageVersion];
+  var gitPath        = path.join(__dirname,'..','.git');
+  var headFilePath   = path.join(gitPath, 'HEAD');
+
+  if (packageVersion.indexOf('+') > -1) {
+    try {
+      if (fs.existsSync(headFilePath)) {
+        var headFile = fs.readFileSync(headFilePath, {encoding: 'utf8'});
+        var branchName = headFile.split('/').slice(-1)[0].trim();
+        var refPath = headFile.split(' ')[1];
+        var branchSHA;
+
+        if (refPath) {
+          var branchPath = path.join(gitPath, refPath.trim());
+          branchSHA  = fs.readFileSync(branchPath);
+        } else {
+          branchSHA = branchName;
+        }
+
+        output.push(branchSHA.slice(0,10));
+      }
+    } catch (err) {
+      console.error(err.stack);
+    }
+    return output.join('.');
+  } else {
+    return packageVersion;
+  }
+};
diff --git a/node_modules/node-firefox-find-app/node_modules/es6-promise/lib/es6-promise.umd.js b/node_modules/node-firefox-find-app/node_modules/es6-promise/lib/es6-promise.umd.js
new file mode 100644
index 0000000..cd01553
--- /dev/null
+++ b/node_modules/node-firefox-find-app/node_modules/es6-promise/lib/es6-promise.umd.js
@@ -0,0 +1,16 @@
+import Promise from './es6-promise/promise';
+import polyfill from './es6-promise/polyfill';
+
+var ES6Promise = {
+  'Promise': Promise,
+  'polyfill': polyfill
+};
+
+/* global define:true module:true window: true */
+if (typeof define === 'function' && define['amd']) {
+  define(function() { return ES6Promise; });
+} else if (typeof module !== 'undefined' && module['exports']) {
+  module['exports'] = ES6Promise;
+} else if (typeof this !== 'undefined') {
+  this['ES6Promise'] = ES6Promise;
+}
diff --git a/node_modules/node-firefox-find-app/node_modules/es6-promise/lib/es6-promise/-internal.js b/node_modules/node-firefox-find-app/node_modules/es6-promise/lib/es6-promise/-internal.js
new file mode 100644
index 0000000..afef22e
--- /dev/null
+++ b/node_modules/node-firefox-find-app/node_modules/es6-promise/lib/es6-promise/-internal.js
@@ -0,0 +1,251 @@
+import {
+  objectOrFunction,
+  isFunction
+} from './utils';
+
+import asap from './asap';
+
+function noop() {}
+
+var PENDING   = void 0;
+var FULFILLED = 1;
+var REJECTED  = 2;
+
+var GET_THEN_ERROR = new ErrorObject();
+
+function selfFullfillment() {
+  return new TypeError("You cannot resolve a promise with itself");
+}
+
+function cannotReturnOwn() {
+  return new TypeError('A promises callback cannot return that same promise.')
+}
+
+function getThen(promise) {
+  try {
+    return promise.then;
+  } catch(error) {
+    GET_THEN_ERROR.error = error;
+    return GET_THEN_ERROR;
+  }
+}
+
+function tryThen(then, value, fulfillmentHandler, rejectionHandler) {
+  try {
+    then.call(value, fulfillmentHandler, rejectionHandler);
+  } catch(e) {
+    return e;
+  }
+}
+
+function handleForeignThenable(promise, thenable, then) {
+   asap(function(promise) {
+    var sealed = false;
+    var error = tryThen(then, thenable, function(value) {
+      if (sealed) { return; }
+      sealed = true;
+      if (thenable !== value) {
+        resolve(promise, value);
+      } else {
+        fulfill(promise, value);
+      }
+    }, function(reason) {
+      if (sealed) { return; }
+      sealed = true;
+
+      reject(promise, reason);
+    }, 'Settle: ' + (promise._label || ' unknown promise'));
+
+    if (!sealed && error) {
+      sealed = true;
+      reject(promise, error);
+    }
+  }, promise);
+}
+
+function handleOwnThenable(promise, thenable) {
+  if (thenable._state === FULFILLED) {
+    fulfill(promise, thenable._result);
+  } else if (promise._state === REJECTED) {
+    reject(promise, thenable._result);
+  } else {
+    subscribe(thenable, undefined, function(value) {
+      resolve(promise, value);
+    }, function(reason) {
+      reject(promise, reason);
+    });
+  }
+}
+
+function handleMaybeThenable(promise, maybeThenable) {
+  if (maybeThenable.constructor === promise.constructor) {
+    handleOwnThenable(promise, maybeThenable);
+  } else {
+    var then = getThen(maybeThenable);
+
+    if (then === GET_THEN_ERROR) {
+      reject(promise, GET_THEN_ERROR.error);
+    } else if (then === undefined) {
+      fulfill(promise, maybeThenable);
+    } else if (isFunction(then)) {
+      handleForeignThenable(promise, maybeThenable, then);
+    } else {
+      fulfill(promise, maybeThenable);
+    }
+  }
+}
+
+function resolve(promise, value) {
+  if (promise === value) {
+    reject(promise, selfFullfillment());
+  } else if (objectOrFunction(value)) {
+    handleMaybeThenable(promise, value);
+  } else {
+    fulfill(promise, value);
+  }
+}
+
+function publishRejection(promise) {
+  if (promise._onerror) {
+    promise._onerror(promise._result);
+  }
+
+  publish(promise);
+}
+
+function fulfill(promise, value) {
+  if (promise._state !== PENDING) { return; }
+
+  promise._result = value;
+  promise._state = FULFILLED;
+
+  if (promise._subscribers.length === 0) {
+  } else {
+    asap(publish, promise);
+  }
+}
+
+function reject(promise, reason) {
+  if (promise._state !== PENDING) { return; }
+  promise._state = REJECTED;
+  promise._result = reason;
+
+  asap(publishRejection, promise);
+}
+
+function subscribe(parent, child, onFulfillment, onRejection) {
+  var subscribers = parent._subscribers;
+  var length = subscribers.length;
+
+  parent._onerror = null;
+
+  subscribers[length] = child;
+  subscribers[length + FULFILLED] = onFulfillment;
+  subscribers[length + REJECTED]  = onRejection;
+
+  if (length === 0 && parent._state) {
+    asap(publish, parent);
+  }
+}
+
+function publish(promise) {
+  var subscribers = promise._subscribers;
+  var settled = promise._state;
+
+  if (subscribers.length === 0) { return; }
+
+  var child, callback, detail = promise._result;
+
+  for (var i = 0; i < subscribers.length; i += 3) {
+    child = subscribers[i];
+    callback = subscribers[i + settled];
+
+    if (child) {
+      invokeCallback(settled, child, callback, detail);
+    } else {
+      callback(detail);
+    }
+  }
+
+  promise._subscribers.length = 0;
+}
+
+function ErrorObject() {
+  this.error = null;
+}
+
+var TRY_CATCH_ERROR = new ErrorObject();
+
+function tryCatch(callback, detail) {
+  try {
+    return callback(detail);
+  } catch(e) {
+    TRY_CATCH_ERROR.error = e;
+    return TRY_CATCH_ERROR;
+  }
+}
+
+function invokeCallback(settled, promise, callback, detail) {
+  var hasCallback = isFunction(callback),
+      value, error, succeeded, failed;
+
+  if (hasCallback) {
+    value = tryCatch(callback, detail);
+
+    if (value === TRY_CATCH_ERROR) {
+      failed = true;
+      error = value.error;
+      value = null;
+    } else {
+      succeeded = true;
+    }
+
+    if (promise === value) {
+      reject(promise, cannotReturnOwn());
+      return;
+    }
+
+  } else {
+    value = detail;
+    succeeded = true;
+  }
+
+  if (promise._state !== PENDING) {
+    // noop
+  } else if (hasCallback && succeeded) {
+    resolve(promise, value);
+  } else if (failed) {
+    reject(promise, error);
+  } else if (settled === FULFILLED) {
+    fulfill(promise, value);
+  } else if (settled === REJECTED) {
+    reject(promise, value);
+  }
+}
+
+function initializePromise(promise, resolver) {
+  try {
+    resolver(function resolvePromise(value){
+      resolve(promise, value);
+    }, function rejectPromise(reason) {
+      reject(promise, reason);
+    });
+  } catch(e) {
+    reject(promise, e);
+  }
+}
+
+export {
+  noop,
+  resolve,
+  reject,
+  fulfill,
+  subscribe,
+  publish,
+  publishRejection,
+  initializePromise,
+  invokeCallback,
+  FULFILLED,
+  REJECTED,
+  PENDING
+};
diff --git a/node_modules/node-firefox-find-app/node_modules/es6-promise/lib/es6-promise/asap.js b/node_modules/node-firefox-find-app/node_modules/es6-promise/lib/es6-promise/asap.js
new file mode 100644
index 0000000..4872589
--- /dev/null
+++ b/node_modules/node-firefox-find-app/node_modules/es6-promise/lib/es6-promise/asap.js
@@ -0,0 +1,82 @@
+var len = 0;
+
+export default function asap(callback, arg) {
+  queue[len] = callback;
+  queue[len + 1] = arg;
+  len += 2;
+  if (len === 2) {
+    // If len is 1, that means that we need to schedule an async flush.
+    // If additional callbacks are queued before the queue is flushed, they
+    // will be processed by this flush that we are scheduling.
+    scheduleFlush();
+  }
+}
+
+var browserGlobal = (typeof window !== 'undefined') ? window : {};
+var BrowserMutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver;
+
+// test for web worker but not in IE10
+var isWorker = typeof Uint8ClampedArray !== 'undefined' &&
+  typeof importScripts !== 'undefined' &&
+  typeof MessageChannel !== 'undefined';
+
+// node
+function useNextTick() {
+  return function() {
+    process.nextTick(flush);
+  };
+}
+
+function useMutationObserver() {
+  var iterations = 0;
+  var observer = new BrowserMutationObserver(flush);
+  var node = document.createTextNode('');
+  observer.observe(node, { characterData: true });
+
+  return function() {
+    node.data = (iterations = ++iterations % 2);
+  };
+}
+
+// web worker
+function useMessageChannel() {
+  var channel = new MessageChannel();
+  channel.port1.onmessage = flush;
+  return function () {
+    channel.port2.postMessage(0);
+  };
+}
+
+function useSetTimeout() {
+  return function() {
+    setTimeout(flush, 1);
+  };
+}
+
+var queue = new Array(1000);
+function flush() {
+  for (var i = 0; i < len; i+=2) {
+    var callback = queue[i];
+    var arg = queue[i+1];
+
+    callback(arg);
+
+    queue[i] = undefined;
+    queue[i+1] = undefined;
+  }
+
+  len = 0;
+}
+
+var scheduleFlush;
+
+// Decide what async method to use to triggering processing of queued callbacks:
+if (typeof process !== 'undefined' && {}.toString.call(process) === '[object process]') {
+  scheduleFlush = useNextTick();
+} else if (BrowserMutationObserver) {
+  scheduleFlush = useMutationObserver();
+} else if (isWorker) {
+  scheduleFlush = useMessageChannel();
+} else {
+  scheduleFlush = useSetTimeout();
+}
diff --git a/node_modules/node-firefox-find-app/node_modules/es6-promise/lib/es6-promise/enumerator.js b/node_modules/node-firefox-find-app/node_modules/es6-promise/lib/es6-promise/enumerator.js
new file mode 100644
index 0000000..7d3f803
--- /dev/null
+++ b/node_modules/node-firefox-find-app/node_modules/es6-promise/lib/es6-promise/enumerator.js
@@ -0,0 +1,125 @@
+import {
+  isArray,
+  isMaybeThenable
+} from './utils';
+
+import {
+  noop,
+  reject,
+  fulfill,
+  subscribe,
+  FULFILLED,
+  REJECTED,
+  PENDING
+} from './-internal';
+
+export function makeSettledResult(state, position, value) {
+  if (state === FULFILLED) {
+    return {
+      state: 'fulfilled',
+      value: value
+    };
+  } else {
+    return {
+      state: 'rejected',
+      reason: value
+    };
+  }
+}
+
+function Enumerator(Constructor, input, abortOnReject, label) {
+  this._instanceConstructor = Constructor;
+  this.promise = new Constructor(noop, label);
+  this._abortOnReject = abortOnReject;
+
+  if (this._validateInput(input)) {
+    this._input     = input;
+    this.length     = input.length;
+    this._remaining = input.length;
+
+    this._init();
+
+    if (this.length === 0) {
+      fulfill(this.promise, this._result);
+    } else {
+      this.length = this.length || 0;
+      this._enumerate();
+      if (this._remaining === 0) {
+        fulfill(this.promise, this._result);
+      }
+    }
+  } else {
+    reject(this.promise, this._validationError());
+  }
+}
+
+Enumerator.prototype._validateInput = function(input) {
+  return isArray(input);
+};
+
+Enumerator.prototype._validationError = function() {
+  return new Error('Array Methods must be provided an Array');
+};
+
+Enumerator.prototype._init = function() {
+  this._result = new Array(this.length);
+};
+
+export default Enumerator;
+
+Enumerator.prototype._enumerate = function() {
+  var length  = this.length;
+  var promise = this.promise;
+  var input   = this._input;
+
+  for (var i = 0; promise._state === PENDING && i < length; i++) {
+    this._eachEntry(input[i], i);
+  }
+};
+
+Enumerator.prototype._eachEntry = function(entry, i) {
+  var c = this._instanceConstructor;
+  if (isMaybeThenable(entry)) {
+    if (entry.constructor === c && entry._state !== PENDING) {
+      entry._onerror = null;
+      this._settledAt(entry._state, i, entry._result);
+    } else {
+      this._willSettleAt(c.resolve(entry), i);
+    }
+  } else {
+    this._remaining--;
+    this._result[i] = this._makeResult(FULFILLED, i, entry);
+  }
+};
+
+Enumerator.prototype._settledAt = function(state, i, value) {
+  var promise = this.promise;
+
+  if (promise._state === PENDING) {
+    this._remaining--;
+
+    if (this._abortOnReject && state === REJECTED) {
+      reject(promise, value);
+    } else {
+      this._result[i] = this._makeResult(state, i, value);
+    }
+  }
+
+  if (this._remaining === 0) {
+    fulfill(promise, this._result);
+  }
+};
+
+Enumerator.prototype._makeResult = function(state, i, value) {
+  return value;
+};
+
+Enumerator.prototype._willSettleAt = function(promise, i) {
+  var enumerator = this;
+
+  subscribe(promise, undefined, function(value) {
+    enumerator._settledAt(FULFILLED, i, value);
+  }, function(reason) {
+    enumerator._settledAt(REJECTED, i, reason);
+  });
+};
diff --git a/node_modules/node-firefox-find-app/node_modules/es6-promise/lib/es6-promise/polyfill.js b/node_modules/node-firefox-find-app/node_modules/es6-promise/lib/es6-promise/polyfill.js
new file mode 100644
index 0000000..e5b0165
--- /dev/null
+++ b/node_modules/node-firefox-find-app/node_modules/es6-promise/lib/es6-promise/polyfill.js
@@ -0,0 +1,35 @@
+/*global self*/
+import { default as RSVPPromise } from "./promise";
+import { isFunction } from "./utils";
+
+export default function polyfill() {
+  var local;
+
+  if (typeof global !== 'undefined') {
+    local = global;
+  } else if (typeof window !== 'undefined' && window.document) {
+    local = window;
+  } else {
+    local = self;
+  }
+
+  var es6PromiseSupport =
+    "Promise" in local &&
+    // Some of these methods are missing from
+    // Firefox/Chrome experimental implementations
+    "resolve" in local.Promise &&
+    "reject" in local.Promise &&
+    "all" in local.Promise &&
+    "race" in local.Promise &&
+    // Older version of the spec had a resolver object
+    // as the arg rather than a function
+    (function() {
+      var resolve;
+      new local.Promise(function(r) { resolve = r; });
+      return isFunction(resolve);
+    }());
+
+  if (!es6PromiseSupport) {
+    local.Promise = RSVPPromise;
+  }
+}
diff --git a/node_modules/node-firefox-find-app/node_modules/es6-promise/lib/es6-promise/promise.js b/node_modules/node-firefox-find-app/node_modules/es6-promise/lib/es6-promise/promise.js
new file mode 100644
index 0000000..5e865a7
--- /dev/null
+++ b/node_modules/node-firefox-find-app/node_modules/es6-promise/lib/es6-promise/promise.js
@@ -0,0 +1,409 @@
+import {
+  isFunction,
+  now
+} from './utils';
+
+import {
+  noop,
+  subscribe,
+  initializePromise,
+  invokeCallback,
+  FULFILLED,
+  REJECTED
+} from './-internal';
+
+import asap from './asap';
+
+import all from './promise/all';
+import race from './promise/race';
+import Resolve from './promise/resolve';
+import Reject from './promise/reject';
+
+var counter = 0;
+
+function needsResolver() {
+  throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
+}
+
+function needsNew() {
+  throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");
+}
+
+export default Promise;
+/**
+  Promise objects represent the eventual result of an asynchronous operation. The
+  primary way of interacting with a promise is through its `then` method, which
+  registers callbacks to receive either a promise’s eventual value or the reason
+  why the promise cannot be fulfilled.
+
+  Terminology
+  -----------
+
+  - `promise` is an object or function with a `then` method whose behavior conforms to this specification.
+  - `thenable` is an object or function that defines a `then` method.
+  - `value` is any legal JavaScript value (including undefined, a thenable, or a promise).
+  - `exception` is a value that is thrown using the throw statement.
+  - `reason` is a value that indicates why a promise was rejected.
+  - `settled` the final resting state of a promise, fulfilled or rejected.
+
+  A promise can be in one of three states: pending, fulfilled, or rejected.
+
+  Promises that are fulfilled have a fulfillment value and are in the fulfilled
+  state.  Promises that are rejected have a rejection reason and are in the
+  rejected state.  A fulfillment value is never a thenable.
+
+  Promises can also be said to *resolve* a value.  If this value is also a
+  promise, then the original promise's settled state will match the value's
+  settled state.  So a promise that *resolves* a promise that rejects will
+  itself reject, and a promise that *resolves* a promise that fulfills will
+  itself fulfill.
+
+
+  Basic Usage:
+  ------------
+
+  ```js
+  var promise = new Promise(function(resolve, reject) {
+    // on success
+    resolve(value);
+
+    // on failure
+    reject(reason);
+  });
+
+  promise.then(function(value) {
+    // on fulfillment
+  }, function(reason) {
+    // on rejection
+  });
+  ```
+
+  Advanced Usage:
+  ---------------
+
+  Promises shine when abstracting away asynchronous interactions such as
+  `XMLHttpRequest`s.
+
+  ```js
+  function getJSON(url) {
+    return new Promise(function(resolve, reject){
+      var xhr = new XMLHttpRequest();
+
+      xhr.open('GET', url);
+      xhr.onreadystatechange = handler;
+      xhr.responseType = 'json';
+      xhr.setRequestHeader('Accept', 'application/json');
+      xhr.send();
+
+      function handler() {
+        if (this.readyState === this.DONE) {
+          if (this.status === 200) {
+            resolve(this.response);
+          } else {
+            reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']'));
+          }
+        }
+      };
+    });
+  }
+
+  getJSON('/posts.json').then(function(json) {
+    // on fulfillment
+  }, function(reason) {
+    // on rejection
+  });
+  ```
+
+  Unlike callbacks, promises are great composable primitives.
+
+  ```js
+  Promise.all([
+    getJSON('/posts'),
+    getJSON('/comments')
+  ]).then(function(values){
+    values[0] // => postsJSON
+    values[1] // => commentsJSON
+
+    return values;
+  });
+  ```
+
+  @class Promise
+  @param {function} resolver
+  Useful for tooling.
+  @constructor
+*/
+function Promise(resolver) {
+  this._id = counter++;
+  this._state = undefined;
+  this._result = undefined;
+  this._subscribers = [];
+
+  if (noop !== resolver) {
+    if (!isFunction(resolver)) {
+      needsResolver();
+    }
+
+    if (!(this instanceof Promise)) {
+      needsNew();
+    }
+
+    initializePromise(this, resolver);
+  }
+}
+
+Promise.all = all;
+Promise.race = race;
+Promise.resolve = Resolve;
+Promise.reject = Reject;
+
+Promise.prototype = {
+  constructor: Promise,
+
+/**
+  The primary way of interacting with a promise is through its `then` method,
+  which registers callbacks to receive either a promise's eventual value or the
+  reason why the promise cannot be fulfilled.
+
+  ```js
+  findUser().then(function(user){
+    // user is available
+  }, function(reason){
+    // user is unavailable, and you are given the reason why
+  });
+  ```
+
+  Chaining
+  --------
+
+  The return value of `then` is itself a promise.  This second, 'downstream'
+  promise is resolved with the return value of the first promise's fulfillment
+  or rejection handler, or rejected if the handler throws an exception.
+
+  ```js
+  findUser().then(function (user) {
+    return user.name;
+  }, function (reason) {
+    return 'default name';
+  }).then(function (userName) {
+    // If `findUser` fulfilled, `userName` will be the user's name, otherwise it
+    // will be `'default name'`
+  });
+
+  findUser().then(function (user) {
+    throw new Error('Found user, but still unhappy');
+  }, function (reason) {
+    throw new Error('`findUser` rejected and we're unhappy');
+  }).then(function (value) {
+    // never reached
+  }, function (reason) {
+    // if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'.
+    // If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'.
+  });
+  ```
+  If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream.
+
+  ```js
+  findUser().then(function (user) {
+    throw new PedagogicalException('Upstream error');
+  }).then(function (value) {
+    // never reached
+  }).then(function (value) {
+    // never reached
+  }, function (reason) {
+    // The `PedgagocialException` is propagated all the way down to here
+  });
+  ```
+
+  Assimilation
+  ------------
+
+  Sometimes the value you want to propagate to a downstream promise can only be
+  retrieved asynchronously. This can be achieved by returning a promise in the
+  fulfillment or rejection handler. The downstream promise will then be pending
+  until the returned promise is settled. This is called *assimilation*.
+
+  ```js
+  findUser().then(function (user) {
+    return findCommentsByAuthor(user);
+  }).then(function (comments) {
+    // The user's comments are now available
+  });
+  ```
+
+  If the assimliated promise rejects, then the downstream promise will also reject.
+
+  ```js
+  findUser().then(function (user) {
+    return findCommentsByAuthor(user);
+  }).then(function (comments) {
+    // If `findCommentsByAuthor` fulfills, we'll have the value here
+  }, function (reason) {
+    // If `findCommentsByAuthor` rejects, we'll have the reason here
+  });
+  ```
+
+  Simple Example
+  --------------
+
+  Synchronous Example
+
+  ```javascript
+  var result;
+
+  try {
+    result = findResult();
+    // success
+  } catch(reason) {
+    // failure
+  }
+  ```
+
+  Errback Example
+
+  ```js
+  findResult(function(result, err){
+    if (err) {
+      // failure
+    } else {
+      // success
+    }
+  });
+  ```
+
+  Promise Example;
+
+  ```javascript
+  findResult().then(function(result){
+    // success
+  }, function(reason){
+    // failure
+  });
+  ```
+
+  Advanced Example
+  --------------
+
+  Synchronous Example
+
+  ```javascript
+  var author, books;
+
+  try {
+    author = findAuthor();
+    books  = findBooksByAuthor(author);
+    // success
+  } catch(reason) {
+    // failure
+  }
+  ```
+
+  Errback Example
+
+  ```js
+
+  function foundBooks(books) {
+
+  }
+
+  function failure(reason) {
+
+  }
+
+  findAuthor(function(author, err){
+    if (err) {
+      failure(err);
+      // failure
+    } else {
+      try {
+        findBoooksByAuthor(author, function(books, err) {
+          if (err) {
+            failure(err);
+          } else {
+            try {
+              foundBooks(books);
+            } catch(reason) {
+              failure(reason);
+            }
+          }
+        });
+      } catch(error) {
+        failure(err);
+      }
+      // success
+    }
+  });
+  ```
+
+  Promise Example;
+
+  ```javascript
+  findAuthor().
+    then(findBooksByAuthor).
+    then(function(books){
+      // found books
+  }).catch(function(reason){
+    // something went wrong
+  });
+  ```
+
+  @method then
+  @param {Function} onFulfilled
+  @param {Function} onRejected
+  Useful for tooling.
+  @return {Promise}
+*/
+  then: function(onFulfillment, onRejection) {
+    var parent = this;
+    var state = parent._state;
+
+    if (state === FULFILLED && !onFulfillment || state === REJECTED && !onRejection) {
+      return this;
+    }
+
+    var child = new this.constructor(noop);
+    var result = parent._result;
+
+    if (state) {
+      var callback = arguments[state - 1];
+      asap(function(){
+        invokeCallback(state, child, callback, result);
+      });
+    } else {
+      subscribe(parent, child, onFulfillment, onRejection);
+    }
+
+    return child;
+  },
+
+/**
+  `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same
+  as the catch block of a try/catch statement.
+
+  ```js
+  function findAuthor(){
+    throw new Error('couldn't find that author');
+  }
+
+  // synchronous
+  try {
+    findAuthor();
+  } catch(reason) {
+    // something went wrong
+  }
+
+  // async with promises
+  findAuthor().catch(function(reason){
+    // something went wrong
+  });
+  ```
+
+  @method catch
+  @param {Function} onRejection
+  Useful for tooling.
+  @return {Promise}
+*/
+  'catch': function(onRejection) {
+    return this.then(null, onRejection);
+  }
+};
diff --git a/node_modules/node-firefox-find-app/node_modules/es6-promise/lib/es6-promise/promise/all.js b/node_modules/node-firefox-find-app/node_modules/es6-promise/lib/es6-promise/promise/all.js
new file mode 100644
index 0000000..8db7e71
--- /dev/null
+++ b/node_modules/node-firefox-find-app/node_modules/es6-promise/lib/es6-promise/promise/all.js
@@ -0,0 +1,52 @@
+import Enumerator from '../enumerator';
+
+/**
+  `Promise.all` accepts an array of promises, and returns a new promise which
+  is fulfilled with an array of fulfillment values for the passed promises, or
+  rejected with the reason of the first passed promise to be rejected. It casts all
+  elements of the passed iterable to promises as it runs this algorithm.
+
+  Example:
+
+  ```javascript
+  var promise1 = resolve(1);
+  var promise2 = resolve(2);
+  var promise3 = resolve(3);
+  var promises = [ promise1, promise2, promise3 ];
+
+  Promise.all(promises).then(function(array){
+    // The array here would be [ 1, 2, 3 ];
+  });
+  ```
+
+  If any of the `promises` given to `all` are rejected, the first promise
+  that is rejected will be given as an argument to the returned promises's
+  rejection handler. For example:
+
+  Example:
+
+  ```javascript
+  var promise1 = resolve(1);
+  var promise2 = reject(new Error("2"));
+  var promise3 = reject(new Error("3"));
+  var promises = [ promise1, promise2, promise3 ];
+
+  Promise.all(promises).then(function(array){
+    // Code here never runs because there are rejected promises!
+  }, function(error) {
+    // error.message === "2"
+  });
+  ```
+
+  @method all
+  @static
+  @param {Array} entries array of promises
+  @param {String} label optional string for labeling the promise.
+  Useful for tooling.
+  @return {Promise} promise that is fulfilled when all `promises` have been
+  fulfilled, or rejected if any of them become rejected.
+  @static
+*/
+export default function all(entries, label) {
+  return new Enumerator(this, entries, true /* abort on reject */, label).promise;
+}
diff --git a/node_modules/node-firefox-find-app/node_modules/es6-promise/lib/es6-promise/promise/race.js b/node_modules/node-firefox-find-app/node_modules/es6-promise/lib/es6-promise/promise/race.js
new file mode 100644
index 0000000..7daa28a
--- /dev/null
+++ b/node_modules/node-firefox-find-app/node_modules/es6-promise/lib/es6-promise/promise/race.js
@@ -0,0 +1,105 @@
+import {
+  isArray
+} from "../utils";
+
+import {
+  noop,
+  resolve,
+  reject,
+  subscribe,
+  PENDING
+} from '../-internal';
+
+/**
+  `Promise.race` returns a new promise which is settled in the same way as the
+  first passed promise to settle.
+
+  Example:
+
+  ```javascript
+  var promise1 = new Promise(function(resolve, reject){
+    setTimeout(function(){
+      resolve('promise 1');
+    }, 200);
+  });
+
+  var promise2 = new Promise(function(resolve, reject){
+    setTimeout(function(){
+      resolve('promise 2');
+    }, 100);
+  });
+
+  Promise.race([promise1, promise2]).then(function(result){
+    // result === 'promise 2' because it was resolved before promise1
+    // was resolved.
+  });
+  ```
+
+  `Promise.race` is deterministic in that only the state of the first
+  settled promise matters. For example, even if other promises given to the
+  `promises` array argument are resolved, but the first settled promise has
+  become rejected before the other promises became fulfilled, the returned
+  promise will become rejected:
+
+  ```javascript
+  var promise1 = new Promise(function(resolve, reject){
+    setTimeout(function(){
+      resolve('promise 1');
+    }, 200);
+  });
+
+  var promise2 = new Promise(function(resolve, reject){
+    setTimeout(function(){
+      reject(new Error('promise 2'));
+    }, 100);
+  });
+
+  Promise.race([promise1, promise2]).then(function(result){
+    // Code here never runs
+  }, function(reason){
+    // reason.message === 'promise 2' because promise 2 became rejected before
+    // promise 1 became fulfilled
+  });
+  ```
+
+  An example real-world use case is implementing timeouts:
+
+  ```javascript
+  Promise.race([ajax('foo.json'), timeout(5000)])
+  ```
+
+  @method race
+  @static
+  @param {Array} promises array of promises to observe
+  @param {String} label optional string for describing the promise returned.
+  Useful for tooling.
+  @return {Promise} a promise which settles in the same way as the first passed
+  promise to settle.
+*/
+export default function race(entries, label) {
+  /*jshint validthis:true */
+  var Constructor = this;
+
+  var promise = new Constructor(noop, label);
+
+  if (!isArray(entries)) {
+    reject(promise, new TypeError('You must pass an array to race.'));
+    return promise;
+  }
+
+  var length = entries.length;
+
+  function onFulfillment(value) {
+    resolve(promise, value);
+  }
+
+  function onRejection(reason) {
+    reject(promise, reason);
+  }
+
+  for (var i = 0; promise._state === PENDING && i < length; i++) {
+    subscribe(Constructor.resolve(entries[i]), undefined, onFulfillment, onRejection);
+  }
+
+  return promise;
+}
diff --git a/node_modules/node-firefox-find-app/node_modules/es6-promise/lib/es6-promise/promise/reject.js b/node_modules/node-firefox-find-app/node_modules/es6-promise/lib/es6-promise/promise/reject.js
new file mode 100644
index 0000000..518eff7
--- /dev/null
+++ b/node_modules/node-firefox-find-app/node_modules/es6-promise/lib/es6-promise/promise/reject.js
@@ -0,0 +1,47 @@
+import {
+  noop,
+  reject as _reject
+} from '../-internal';
+
+/**
+  `Promise.reject` returns a promise rejected with the passed `reason`.
+  It is shorthand for the following:
+
+  ```javascript
+  var promise = new Promise(function(resolve, reject){
+    reject(new Error('WHOOPS'));
+  });
+
+  promise.then(function(value){
+    // Code here doesn't run because the promise is rejected!
+  }, function(reason){
+    // reason.message === 'WHOOPS'
+  });
+  ```
+
+  Instead of writing the above, your code now simply becomes the following:
+
+  ```javascript
+  var promise = Promise.reject(new Error('WHOOPS'));
+
+  promise.then(function(value){
+    // Code here doesn't run because the promise is rejected!
+  }, function(reason){
+    // reason.message === 'WHOOPS'
+  });
+  ```
+
+  @method reject
+  @static
+  @param {Any} reason value that the returned promise will be rejected with.
+  @param {String} label optional string for identifying the returned promise.
+  Useful for tooling.
+  @return {Promise} a promise rejected with the given `reason`.
+*/
+export default function reject(reason, label) {
+  /*jshint validthis:true */
+  var Constructor = this;
+  var promise = new Constructor(noop, label);
+  _reject(promise, reason);
+  return promise;
+}
diff --git a/node_modules/node-firefox-find-app/node_modules/es6-promise/lib/es6-promise/promise/resolve.js b/node_modules/node-firefox-find-app/node_modules/es6-promise/lib/es6-promise/promise/resolve.js
new file mode 100644
index 0000000..1b3c533
--- /dev/null
+++ b/node_modules/node-firefox-find-app/node_modules/es6-promise/lib/es6-promise/promise/resolve.js
@@ -0,0 +1,49 @@
+import {
+  noop,
+  resolve as _resolve
+} from '../-internal';
+
+/**
+  `Promise.resolve` returns a promise that will become resolved with the
+  passed `value`. It is shorthand for the following:
+
+  ```javascript
+  var promise = new Promise(function(resolve, reject){
+    resolve(1);
+  });
+
+  promise.then(function(value){
+    // value === 1
+  });
+  ```
+
+  Instead of writing the above, your code now simply becomes the following:
+
+  ```javascript
+  var promise = Promise.resolve(1);
+
+  promise.then(function(value){
+    // value === 1
+  });
+  ```
+
+  @method resolve
+  @static
+  @param {Any} value value that the returned promise will be resolved with
+  @param {String} label optional string for identifying the returned promise.
+  Useful for tooling.
+  @return {Promise} a promise that will become fulfilled with the given
+  `value`
+*/
+export default function resolve(object, label) {
+  /*jshint validthis:true */
+  var Constructor = this;
+
+  if (object && typeof object === 'object' && object.constructor === Constructor) {
+    return object;
+  }
+
+  var promise = new Constructor(noop, label);
+  _resolve(promise, object);
+  return promise;
+}
diff --git a/node_modules/node-firefox-find-app/node_modules/es6-promise/lib/es6-promise/utils.js b/node_modules/node-firefox-find-app/node_modules/es6-promise/lib/es6-promise/utils.js
new file mode 100644
index 0000000..6b49bbf
--- /dev/null
+++ b/node_modules/node-firefox-find-app/node_modules/es6-promise/lib/es6-promise/utils.js
@@ -0,0 +1,39 @@
+export function objectOrFunction(x) {
+  return typeof x === 'function' || (typeof x === 'object' && x !== null);
+}
+
+export function isFunction(x) {
+  return typeof x === 'function';
+}
+
+export function isMaybeThenable(x) {
+  return typeof x === 'object' && x !== null;
+}
+
+var _isArray;
+if (!Array.isArray) {
+  _isArray = function (x) {
+    return Object.prototype.toString.call(x) === '[object Array]';
+  };
+} else {
+  _isArray = Array.isArray;
+}
+
+export var isArray = _isArray;
+
+// Date.now is not available in browsers < IE9
+// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now#Compatibility
+export var now = Date.now || function() { return new Date().getTime(); };
+
+function F() { }
+
+export var o_create = (Object.create || function (o) {
+  if (arguments.length > 1) {
+    throw new Error('Second argument not supported');
+  }
+  if (typeof o !== 'object') {
+    throw new TypeError('Argument must be an object');
+  }
+  F.prototype = o;
+  return new F();
+});
diff --git a/node_modules/node-firefox-find-app/node_modules/es6-promise/package.json b/node_modules/node-firefox-find-app/node_modules/es6-promise/package.json
new file mode 100644
index 0000000..28494ce
--- /dev/null
+++ b/node_modules/node-firefox-find-app/node_modules/es6-promise/package.json
@@ -0,0 +1,81 @@
+{
+  "name": "es6-promise",
+  "namespace": "es6-promise",
+  "version": "2.0.1",
+  "description": "A lightweight library that provides tools for organizing asynchronous code",
+  "main": "dist/es6-promise.js",
+  "directories": {
+    "lib": "lib"
+  },
+  "devDependencies": {
+    "bower": "^1.3.9",
+    "brfs": "0.0.8",
+    "broccoli-closure-compiler": "^0.2.0",
+    "broccoli-compile-modules": "git+https://github.com/eventualbuddha/broccoli-compile-modules",
+    "broccoli-concat": "0.0.7",
+    "broccoli-es3-safe-recast": "0.0.8",
+    "broccoli-file-mover": "^0.4.0",
+    "broccoli-jshint": "^0.5.1",
+    "broccoli-merge-trees": "^0.1.4",
+    "broccoli-static-compiler": "^0.1.4",
+    "broccoli-string-replace": "0.0.1",
+    "browserify": "^4.2.0",
+    "ember-cli": "0.0.40",
+    "ember-publisher": "0.0.7",
+    "es6-module-transpiler-amd-formatter": "0.0.1",
+    "express": "^4.5.0",
+    "jshint": "~0.9.1",
+    "mkdirp": "^0.5.0",
+    "mocha": "^1.20.1",
+    "promises-aplus-tests": "git://github.com/stefanpenner/promises-tests.git",
+    "release-it": "0.0.10",
+    "testem": "^0.6.17",
+    "json3": "^3.3.2"
+  },
+  "scripts": {
+    "test": "testem ci -R dot",
+    "test-server": "testem",
+    "lint": "jshint lib",
+    "prepublish": "ember build --environment production",
+    "aplus": "browserify test/main.js",
+    "build-all": "ember build --environment production && browserify ./test/main.js -o tmp/test-bundle.js",
+    "dry-run-release": "ember build --environment production && release-it --dry-run --non-interactive"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/jakearchibald/ES6-Promises.git"
+  },
+  "bugs": {
+    "url": "https://github.com/jakearchibald/ES6-Promises/issues"
+  },
+  "keywords": [
+    "promises",
+    "futures"
+  ],
+  "author": {
+    "name": "Yehuda Katz, Tom Dale, Stefan Penner and contributors",
+    "url": "Conversion to ES6 API by Jake Archibald"
+  },
+  "license": "MIT",
+  "homepage": "https://github.com/jakearchibald/ES6-Promises",
+  "_id": "es6-promise@2.0.1",
+  "dist": {
+    "shasum": "ccc4963e679f0ca9fb187c777b9e583d3c7573c2",
+    "tarball": "http://registry.npmjs.org/es6-promise/-/es6-promise-2.0.1.tgz"
+  },
+  "_from": "es6-promise@^2.0.1",
+  "_npmVersion": "1.3.24",
+  "_npmUser": {
+    "name": "jaffathecake",
+    "email": "jaffathecake@gmail.com"
+  },
+  "maintainers": [
+    {
+      "name": "jaffathecake",
+      "email": "jaffathecake@gmail.com"
+    }
+  ],
+  "_shasum": "ccc4963e679f0ca9fb187c777b9e583d3c7573c2",
+  "_resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-2.0.1.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-find-app/node_modules/es6-promise/server/.jshintrc b/node_modules/node-firefox-find-app/node_modules/es6-promise/server/.jshintrc
new file mode 100644
index 0000000..c1f2978
--- /dev/null
+++ b/node_modules/node-firefox-find-app/node_modules/es6-promise/server/.jshintrc
@@ -0,0 +1,3 @@
+{
+  "node": true
+}
diff --git a/node_modules/node-firefox-find-app/node_modules/es6-promise/server/index.js b/node_modules/node-firefox-find-app/node_modules/es6-promise/server/index.js
new file mode 100644
index 0000000..8a54d36
--- /dev/null
+++ b/node_modules/node-firefox-find-app/node_modules/es6-promise/server/index.js
@@ -0,0 +1,6 @@
+module.exports = function(app) {
+  app.use(require('express').static(__dirname + '/../'));
+  app.get('/', function(req, res) {
+    res.redirect('/test/');
+  })
+};
diff --git a/node_modules/node-firefox-find-app/node_modules/es6-promise/testem.json b/node_modules/node-firefox-find-app/node_modules/es6-promise/testem.json
new file mode 100644
index 0000000..7494912
--- /dev/null
+++ b/node_modules/node-firefox-find-app/node_modules/es6-promise/testem.json
@@ -0,0 +1,11 @@
+{
+  "test_page": "test/index.html",
+  "parallel": 5,
+  "before_tests": "npm run build-all",
+  "launchers": {
+    "Mocha": {
+      "command": "./node_modules/.bin/mocha test/main.js"
+    }
+  },
+  "launch_in_ci":  ["PhantomJS", "Mocha"]
+}
diff --git a/node_modules/node-firefox-find-app/package.json b/node_modules/node-firefox-find-app/package.json
new file mode 100644
index 0000000..c062705
--- /dev/null
+++ b/node_modules/node-firefox-find-app/package.json
@@ -0,0 +1,64 @@
+{
+  "name": "node-firefox-find-app",
+  "version": "1.0.0",
+  "description": "Find an installed app on a runtime",
+  "main": "index.js",
+  "scripts": {
+    "gulp": "gulp",
+    "test": "gulp test"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/mozilla/node-firefox-find-app.git"
+  },
+  "author": {
+    "name": "Mozilla",
+    "url": "https://mozilla.org/"
+  },
+  "license": "Apache 2",
+  "bugs": {
+    "url": "https://github.com/mozilla/node-firefox-find-app/issues"
+  },
+  "homepage": "https://github.com/mozilla/node-firefox-find-app",
+  "dependencies": {
+    "es6-promise": "^2.0.1"
+  },
+  "keywords": [
+    "apps",
+    "webapps",
+    "b2g",
+    "firefoxos",
+    "fxos",
+    "firefox os"
+  ],
+  "devDependencies": {
+    "gulp": "^3.8.10",
+    "node-firefox-build-tools": "^0.1.0",
+    "node-firefox-connect": "^1.0.0",
+    "node-firefox-start-simulator": "^1.1.0",
+    "should": "^4.0.4"
+  },
+  "gitHead": "9105b904630df908fedb72e354088e17d09f49ea",
+  "_id": "node-firefox-find-app@1.0.0",
+  "_shasum": "c6fa1693e59bd9af77cdf812d8f9bdd943a4b30e",
+  "_from": "node-firefox-find-app@",
+  "_npmVersion": "2.1.6",
+  "_nodeVersion": "0.10.33",
+  "_npmUser": {
+    "name": "sole",
+    "email": "listas@soledadpenades.com"
+  },
+  "maintainers": [
+    {
+      "name": "sole",
+      "email": "listas@soledadpenades.com"
+    }
+  ],
+  "dist": {
+    "shasum": "c6fa1693e59bd9af77cdf812d8f9bdd943a4b30e",
+    "tarball": "http://registry.npmjs.org/node-firefox-find-app/-/node-firefox-find-app-1.0.0.tgz"
+  },
+  "directories": {},
+  "_resolved": "https://registry.npmjs.org/node-firefox-find-app/-/node-firefox-find-app-1.0.0.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-find-app/test/sampleapp/build/app.zip b/node_modules/node-firefox-find-app/test/sampleapp/build/app.zip
new file mode 100644
index 0000000..fdbe68a
--- /dev/null
+++ b/node_modules/node-firefox-find-app/test/sampleapp/build/app.zip
Binary files differ
diff --git a/node_modules/node-firefox-find-app/test/sampleapp/fake.webapp b/node_modules/node-firefox-find-app/test/sampleapp/fake.webapp
new file mode 100644
index 0000000..7627607
--- /dev/null
+++ b/node_modules/node-firefox-find-app/test/sampleapp/fake.webapp
@@ -0,0 +1,22 @@
+{
+  "version": "0.1.0",
+  "name": "fxos-deploy-sample-fake",
+  "description": "Sample app",
+  "launch_path": "/index.html",
+  "icons": {
+    "16": "/icons/icon16x16.png",
+    "48": "/icons/icon48x48.png",
+    "60": "/icons/icon60x60.png",
+    "128": "/icons/icon128x128.png"
+  },
+  "developer": {
+    "name": "Your name",
+    "url": "http://example.com"
+  },
+  "type": "privileged",
+  "permissions": {},
+  "installs_allowed_from": [
+    "*"
+  ],
+  "default_locale": "en"
+}
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-app/test/sampleapp/icons/icon.svg b/node_modules/node-firefox-find-app/test/sampleapp/icons/icon.svg
new file mode 100644
index 0000000..834968c
--- /dev/null
+++ b/node_modules/node-firefox-find-app/test/sampleapp/icons/icon.svg
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:sketch="http://www.bohemiancoding.com/sketch/ns"
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="128px"
+   height="128px"
+   viewBox="0 0 128 128"
+   version="1.1"
+   id="svg2"
+   inkscape:version="0.48.2 r9819"
+   sodipodi:docname="icon.svg">
+  <metadata
+     id="metadata14">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1142"
+     inkscape:window-height="849"
+     id="namedview12"
+     showgrid="false"
+     inkscape:zoom="1.84375"
+     inkscape:cx="-32.542373"
+     inkscape:cy="64"
+     inkscape:window-x="672"
+     inkscape:window-y="146"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="Page-1" />
+  <!-- Generator: Sketch 3.0.2 (7799) - http://www.bohemiancoding.com/sketch -->
+  <title
+     id="title4">empty</title>
+  <description
+     id="description6">Created with Sketch.</description>
+  <defs
+     id="defs8">
+    <linearGradient
+       id="linearGradient3761">
+      <stop
+         style="stop-color:#4e748b;stop-opacity:1;"
+         offset="0"
+         id="stop3763" />
+      <stop
+         style="stop-color:#393e3f;stop-opacity:1;"
+         offset="1"
+         id="stop3765" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3761"
+       id="linearGradient3767"
+       x1="129.66949"
+       y1="65.8983"
+       x2="129.66949"
+       y2="188.59828"
+       gradientUnits="userSpaceOnUse" />
+  </defs>
+  <path
+     sodipodi:type="arc"
+     style="fill:url(#linearGradient3767);fill-opacity:1;fill-rule:evenodd;stroke:none"
+     id="path2991"
+     sodipodi:cx="129.35593"
+     sodipodi:cy="128.27118"
+     sodipodi:rx="63.18644"
+     sodipodi:ry="63.18644"
+     d="m 192.54237,128.27118 a 63.18644,63.18644 0 1 1 -126.372883,0 63.18644,63.18644 0 1 1 126.372883,0 z"
+     transform="translate(-65.355927,-64.271179)" />
+  <g
+     id="Page-1"
+     sketch:type="MSPage"
+     transform="matrix(0.9,0,0,0.9,6.4,6.4)"
+     style="fill:none;stroke:none">
+    <circle
+       id="empty"
+       sketch:type="MSShapeGroup"
+       cx="64"
+       cy="64"
+       r="58"
+       sodipodi:cx="64"
+       sodipodi:cy="64"
+       sodipodi:rx="58"
+       sodipodi:ry="58"
+       style="stroke:#bcc6c5;stroke-width:11;stroke-linecap:round;stroke-dasharray:20, 20;fill:none;fill-opacity:1"
+       d="M 122,64 C 122,96.032515 96.032515,122 64,122 31.967485,122 6,96.032515 6,64 6,31.967485 31.967485,6 64,6 c 32.032515,0 58,25.967485 58,58 z" />
+  </g>
+</svg>
diff --git a/node_modules/node-firefox-find-app/test/sampleapp/icons/icon128x128.png b/node_modules/node-firefox-find-app/test/sampleapp/icons/icon128x128.png
new file mode 100644
index 0000000..7c672c5
--- /dev/null
+++ b/node_modules/node-firefox-find-app/test/sampleapp/icons/icon128x128.png
Binary files differ
diff --git a/node_modules/node-firefox-find-app/test/sampleapp/icons/icon16x16.png b/node_modules/node-firefox-find-app/test/sampleapp/icons/icon16x16.png
new file mode 100644
index 0000000..abf254e
--- /dev/null
+++ b/node_modules/node-firefox-find-app/test/sampleapp/icons/icon16x16.png
Binary files differ
diff --git a/node_modules/node-firefox-find-app/test/sampleapp/icons/icon48x48.png b/node_modules/node-firefox-find-app/test/sampleapp/icons/icon48x48.png
new file mode 100644
index 0000000..451655b
--- /dev/null
+++ b/node_modules/node-firefox-find-app/test/sampleapp/icons/icon48x48.png
Binary files differ
diff --git a/node_modules/node-firefox-find-app/test/sampleapp/icons/icon60x60.png b/node_modules/node-firefox-find-app/test/sampleapp/icons/icon60x60.png
new file mode 100644
index 0000000..c7d4115
--- /dev/null
+++ b/node_modules/node-firefox-find-app/test/sampleapp/icons/icon60x60.png
Binary files differ
diff --git a/node_modules/node-firefox-find-app/test/sampleapp/index.html b/node_modules/node-firefox-find-app/test/sampleapp/index.html
new file mode 100644
index 0000000..2c9d1a3
--- /dev/null
+++ b/node_modules/node-firefox-find-app/test/sampleapp/index.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta charset="utf-8">
+        <title>Sample app</title>
+        <meta name="description" content="A privileged app stub">
+        <meta name="viewport" content="width=device-width">
+    </head>
+    <body>
+        Sample app
+    </body>
+</html>
diff --git a/node_modules/node-firefox-find-app/test/sampleapp/manifest.webapp b/node_modules/node-firefox-find-app/test/sampleapp/manifest.webapp
new file mode 100644
index 0000000..d5ceca3
--- /dev/null
+++ b/node_modules/node-firefox-find-app/test/sampleapp/manifest.webapp
@@ -0,0 +1,22 @@
+{
+  "version": "0.1.0",
+  "name": "fxos-findapp-sample",
+  "description": "Sample app",
+  "launch_path": "/index.html",
+  "icons": {
+    "16": "/icons/icon16x16.png",
+    "48": "/icons/icon48x48.png",
+    "60": "/icons/icon60x60.png",
+    "128": "/icons/icon128x128.png"
+  },
+  "developer": {
+    "name": "Your name",
+    "url": "http://example.com"
+  },
+  "type": "privileged",
+  "permissions": {},
+  "installs_allowed_from": [
+    "*"
+  ],
+  "default_locale": "en"
+}
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-app/test/test.js b/node_modules/node-firefox-find-app/test/test.js
new file mode 100644
index 0000000..378418e
--- /dev/null
+++ b/node_modules/node-firefox-find-app/test/test.js
@@ -0,0 +1,73 @@
+var assert = require("assert");
+var should = require("should");
+var FindApp = require("../");
+var Ports = require("fx-ports");
+var Start = require("fxos-start");
+var Deploy = require("fxos-deploy/command");
+var Q = require('q');
+
+
+describe('fxos-findapp', function(){
+  this.timeout(10000);
+
+  afterEach(function() {
+    Ports({b2g:true}, function(err, instances) {
+      instances.forEach(function(i) {
+        process.kill(i.pid);
+      });
+    });
+  });
+
+  describe('when no open simulator', function(){
+
+    it('should find app manifest', function(done) {
+      Start({connect:true, force: true})
+        .then(function(sim) {
+          return FindApp({
+            manifestURL: './test/sampleapp/manifest.webapp',
+            client: sim.client
+          });
+        })
+        .then(function(app) {
+          app.manifest.should.be.ok;
+          app.manifest.name.should.be.type('string');
+        })
+        .then(done)
+        .fail(done);
+    });
+
+    it('should throw error if not existing', function(done) {
+      Start({connect:true, force: true})
+        .then(function(sim) {
+          return FindApp({
+            manifestURL: './test/sampleapp/fake.webapp',
+            client: sim.client
+          });
+        })
+        .fail(function(err) {
+          err.should.be.ok;
+          done();
+        });
+    });
+
+    it('should throw reuse existing client', function(done) {
+      Start({connect:true, force: true})
+        .then(function(sim) {
+
+          return FindApp({
+              manifestURL: './test/sampleapp/manifest.webapp',
+              client: sim.client
+            })
+            .then(function(app) {
+              app.manifest.should.be.ok;
+              app.manifest.name.should.be.type('string');
+            });
+
+        }).then(done)
+            .fail(done);
+
+    });
+
+  });
+
+});
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-devices/.npmignore b/node_modules/node-firefox-find-devices/.npmignore
new file mode 100644
index 0000000..ae7e1e3
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/.npmignore
@@ -0,0 +1,4 @@
+node_modules
+npm-debug.log
+.DS_Store
+*.sw?
diff --git a/node_modules/node-firefox-find-devices/.travis.yml b/node_modules/node-firefox-find-devices/.travis.yml
new file mode 100644
index 0000000..0394ba7
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/.travis.yml
@@ -0,0 +1,3 @@
+language: node_js
+node_js:
+- 0.10
diff --git a/node_modules/node-firefox-find-devices/LICENSE b/node_modules/node-firefox-find-devices/LICENSE
new file mode 100644
index 0000000..a7c6b5e
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/LICENSE
@@ -0,0 +1,13 @@
+Copyright 2015 Mozilla
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/node_modules/node-firefox-find-devices/README.md b/node_modules/node-firefox-find-devices/README.md
new file mode 100644
index 0000000..ee94d94
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/README.md
@@ -0,0 +1,78 @@
+# node-firefox-find-devices [![Build Status](https://secure.travis-ci.org/mozilla/node-firefox-find-devices.png?branch=master)](http://travis-ci.org/mozilla/node-firefox-find-devices)
+
+> Find attached FirefoxOS devices using ADB.
+
+[![Install with NPM](https://nodei.co/npm/node-firefox-find-devices.png?downloads=true&stars=true)](https://nodei.co/npm/node-firefox-find-devices/)
+
+This is part of the [node-firefox](https://github.com/mozilla/node-firefox) project.
+
+When Firefox OS devices are plugged in via USB, they can be found using the
+Android Debug Bridge. Once found, a test for the presence of Firefox OS on the
+device can separate them from normal Android devices.
+
+## Installation
+
+### From git
+
+```bash
+git clone https://github.com/mozilla/node-firefox-find-devices.git
+cd node-firefox-find-devices
+npm install
+```
+
+If you want to update later on:
+
+```bash
+cd node-firefox-find-devices
+git pull origin master
+npm install
+```
+
+### npm
+
+```bash
+npm install node-firefox-find-devices
+```
+
+## Usage
+
+```javascript
+findDevices() // returns a Promise
+```
+
+### Finding devices
+
+```javascript
+var findDevices = require('node-firefox-find-devices');
+
+// Return all listening runtimes
+findDevices().then(function(results) {
+  console.log(results);
+});
+```
+
+## Running the tests
+
+After installing, you can simply run the following from the module folder:
+
+```bash
+npm test
+```
+
+To add a new unit test file, create a new file in the `tests/unit` folder. Any file that matches `test.*.js` will be run as a test by the appropriate test runner, based on the folder location.
+
+We use `gulp` behind the scenes to run the test; if you don't have it installed globally you can use `npm gulp` from inside the project's root folder to run `gulp`.
+
+### Code quality and style
+
+Because we have multiple contributors working on our projects, we value consistent code styles. It makes it easier to read code written by many people! :-)
+
+Our tests include unit tests as well as code quality ("linting") tests that make sure our test pass a style guide and [JSHint](http://jshint.com/). Instead of submitting code with the wrong indentation or a different style, run the tests and you will be told where your code quality/style differs from ours and instructions on how to fix it.
+
+This program is free software; it is distributed under an
+[Apache License](https://github.com/mozilla/node-firefox-find-devices/blob/master/LICENSE).
+
+## Copyright
+
+Copyright (c) 2015 [Mozilla](https://mozilla.org)
+([Contributors](https://github.com/mozilla/node-firefox-find-devices/graphs/contributors)).
diff --git a/node_modules/node-firefox-find-devices/gulpfile.js b/node_modules/node-firefox-find-devices/gulpfile.js
new file mode 100644
index 0000000..f5e5ab9
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/gulpfile.js
@@ -0,0 +1,3 @@
+var gulp = require('gulp');
+
+require('node-firefox-build-tools').loadGulpTasks(gulp);
diff --git a/node_modules/node-firefox-find-devices/index.js b/node_modules/node-firefox-find-devices/index.js
new file mode 100644
index 0000000..723d44c
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/index.js
@@ -0,0 +1,41 @@
+'use strict';
+
+// See https://github.com/jshint/jshint/issues/1747 for context
+/* global -Promise */
+var Promise = require('es6-promise').Promise;
+var adb = require('adbkit');
+
+module.exports = findDevices;
+
+// Stolen from the ADB Helper add-on
+// https://github.com/mozilla/adbhelper/blob/36559184f122f410a4e571f6217e6ed8ca4fa33e/scanner.js#L114
+var B2G_TEST_COMMAND = 'test -f /system/b2g/b2g; echo $?';
+
+function findDevices() {
+  var client = adb.createClient();
+
+  return client.listDevices().then(function(devices) {
+
+    return Promise.all(devices.map(function(device) {
+
+      // Test for Firefox OS on devices, annotate the device list with result.
+      return client.shell(device.id, B2G_TEST_COMMAND)
+        .then(adb.util.readAll)
+        .then(function(output) {
+          // This is counterintuitive: The command result is the exit code,
+          // which is 1 for failure, which means Firefox OS was *not* detected.
+          device.isFirefoxOS = (output.toString('utf8').charAt(0) === '0');
+          return device;
+        });
+
+    }));
+
+  }).then(function(devices) {
+
+    // Filter out all but the Firefox OS devices
+    return devices.filter(function(device) {
+      return device.isFirefoxOS;
+    });
+
+  });
+}
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/LICENSE b/node_modules/node-firefox-find-devices/node_modules/adbkit/LICENSE
new file mode 100644
index 0000000..2ffff3d
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/LICENSE
@@ -0,0 +1,13 @@
+Copyright © CyberAgent, Inc. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/README.md b/node_modules/node-firefox-find-devices/node_modules/adbkit/README.md
new file mode 100644
index 0000000..c8557ce
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/README.md
@@ -0,0 +1,1089 @@
+# adbkit
+
+**adbkit** is a pure [Node.js][nodejs] client for the [Android Debug Bridge][adb-site] server. It can be used either as a library in your own application, or simply as a convenient utility for playing with your device.
+
+Most of the `adb` command line tool's functionality is supported (including pushing/pulling files, installing APKs and processing logs), with some added functionality such as being able to generate touch/key events and take screenshots. Some shims are provided for older devices, but we have not and will not test anything below Android 2.3.
+
+Internally, we use this library to drive a multitude of Android devices from a variety of manufacturers, so we can say with a fairly high degree of confidence that it will most likely work with your device(s), too.
+
+## Requirements
+
+* [Node.js][nodejs] >= 0.10
+* The `adb` command line tool
+
+Please note that although it may happen at some point, **this project is NOT an implementation of the ADB _server_**. The target host (where the devices are connected) must still have ADB installed and either already running (e.g. via `adb start-server`) or available in `$PATH`. An attempt will be made to start the server locally via the aforementioned command if the initial connection fails. This is the only case where we fall back to the `adb` binary.
+
+When targeting a remote host, starting the server is entirely your responsibility.
+
+Alternatively, you may want to consider using the Chrome [ADB][chrome-adb] extension, as it includes the ADB server and can be started/stopped quite easily.
+
+## Getting started
+
+Install via NPM:
+
+```bash
+npm install --save adbkit
+```
+
+We use [debug][node-debug], and our debug namespace is `adb`. Some of the dependencies may provide debug output of their own. To see the debug output, set the `DEBUG` environment variable. For example, run your program with `DEBUG=adb:* node app.js`.
+
+Note that even though the module is written in [CoffeeScript][coffeescript], only the compiled JavaScript is published to [NPM][npm], which means that it can easily be used with pure JavaScript codebases, too.
+
+### Examples
+
+The examples may be a bit verbose, but that's because we're trying to keep them as close to real-life code as possible, with flow control and error handling taken care of.
+
+#### Checking for NFC support
+
+```js
+var Promise = require('bluebird')
+var adb = require('adbkit')
+var client = adb.createClient()
+
+client.listDevices()
+  .then(function(devices) {
+    return Promise.filter(devices, function(device) {
+      return client.getFeatures(device.id)
+        .then(function(features) {
+          return features['android.hardware.nfc']
+        })
+    })
+  })
+  .then(function(supportedDevices) {
+    console.log('The following devices support NFC:', supportedDevices)
+  })
+  .catch(function(err) {
+    console.error('Something went wrong:', err.stack)
+  })
+```
+
+#### Installing an APK
+
+```js
+var Promise = require('bluebird')
+var adb = require('adbkit')
+var client = adb.createClient()
+var apk = 'vendor/app.apk'
+
+client.listDevices()
+  .then(function(devices) {
+    return Promise.map(devices, function(device) {
+      return client.install(device.id, apk)
+    })
+  })
+  .then(function() {
+    console.log('Installed %s on all connected devices', apk)
+  })
+  .catch(function(err) {
+    console.error('Something went wrong:', err.stack)
+  })
+```
+
+#### Tracking devices
+
+```js
+var adb = require('adbkit')
+var client = adb.createClient()
+
+client.trackDevices()
+  .then(function(tracker) {
+    tracker.on('add', function(device) {
+      console.log('Device %s was plugged in', device.id)
+    })
+    tracker.on('remove', function(device) {
+      console.log('Device %s was unplugged', device.id)
+    })
+    tracker.on('end', function() {
+      console.log('Tracking stopped')
+    })
+  })
+  .catch(function(err) {
+    console.error('Something went wrong:', err.stack)
+  })
+```
+
+#### Pulling a file from all connected devices
+
+```js
+var Promise = require('bluebird')
+var fs = require('fs')
+var adb = require('adbkit')
+var client = adb.createClient()
+
+client.listDevices()
+  .then(function(devices) {
+    return Promise.map(devices, function(device) {
+      return client.pull(device.id, '/system/build.prop')
+        .then(function(transfer) {
+          return new Promise(function(resolve, reject) {
+            var fn = '/tmp/' + device.id + '.build.prop'
+            transfer.on('progress', function(stats) {
+              console.log('[%s] Pulled %d bytes so far',
+                device.id,
+                stats.bytesTransferred)
+            })
+            transfer.on('end', function() {
+              console.log('[%s] Pull complete', device.id)
+              resolve(device.id)
+            })
+            transfer.on('error', reject)
+            transfer.pipe(fs.createWriteStream(fn))
+          })
+        })
+    })
+  })
+  .then(function() {
+    console.log('Done pulling /system/build.prop from all connected devices')
+  })
+  .catch(function(err) {
+    console.error('Something went wrong:', err.stack)
+  })
+```
+
+#### Pushing a file to all connected devices
+
+```js
+var Promise = require('bluebird')
+var adb = require('adbkit')
+var client = adb.createClient()
+
+client.listDevices()
+  .then(function(devices) {
+    return Promise.map(devices, function(device) {
+      return client.push(device.id, 'temp/foo.txt', '/data/local/tmp/foo.txt')
+        .then(function(transfer) {
+          return new Promise(function(resolve, reject) {
+            transfer.on('progress', function(stats) {
+              console.log('[%s] Pushed %d bytes so far',
+                device.id,
+                stats.bytesTransferred)
+            })
+            transfer.on('end', function() {
+              console.log('[%s] Push complete', device.id)
+              resolve()
+            })
+            transfer.on('error', reject)
+          })
+        })
+    })
+  })
+  .then(function() {
+    console.log('Done pushing foo.txt to all connected devices')
+  })
+  .catch(function(err) {
+    console.error('Something went wrong:', err.stack)
+  })
+```
+
+#### List files in a folder
+
+```js
+var Promise = require('bluebird')
+var adb = require('adbkit')
+var client = adb.createClient()
+
+client.listDevices()
+  .then(function(devices) {
+    return Promise.map(devices, function(device) {
+      return client.readdir(device.id, '/sdcard')
+        .then(function(files) {
+          // Synchronous, so we don't have to care about returning at the
+          // right time
+          files.forEach(function(file) {
+            if (file.isFile()) {
+              console.log('[%s] Found file "%s"', device.id, file.name)
+            }
+          })
+        })
+    })
+  })
+  .then(function() {
+    console.log('Done checking /sdcard files on connected devices')
+  })
+  .catch(function(err) {
+    console.error('Something went wrong:', err.stack)
+  })
+```
+
+## API
+
+### ADB
+
+#### adb.createClient([options])
+
+Creates a client instance with the provided options. Note that this will not automatically establish a connection, it will only be done when necessary.
+
+* **options** An object compatible with [Net.connect][net-connect]'s options:
+    - **port** The port where the ADB server is listening. Defaults to `5037`.
+    - **host** The host of the ADB server. Defaults to `'localhost'`.
+    - **bin** As the sole exception, this option provides the path to the `adb` binary, used for starting the server locally if initial connection fails. Defaults to `'adb'`.
+* Returns: The client instance.
+
+#### adb.util.parsePublicKey(androidKey[, callback])
+
+Parses an Android-formatted mincrypt public key (e.g. `~/.android/adbkey.pub`).
+
+* **androidKey** The key String or [`Buffer`][node-buffer] to parse. Not a filename.
+* **callback(err, output)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **key** The key as a [forge.pki](https://github.com/digitalbazaar/forge#rsa) public key. You may need [node-forge](https://github.com/digitalbazaar/forge) for complicated operations. Additionally the following properties are present:
+      * **fingerprint** The key fingerprint, like it would display on a device. Note that this is different than the one you'd get from `forge.ssh.getPublicKeyFingerprint(key)`, because the device fingerprint is based on the original format.
+      * **comment** The key comment, if any.
+* Returns: `Promise`
+* Resolves with: `key` (see callback)
+
+#### adb.util.readAll(stream[, callback])
+
+Takes a [`Stream`][node-stream] and reads everything it outputs until the stream ends. Then it resolves with the collected output. Convenient with `client.shell()`.
+
+* **stream** The [`Stream`][node-stream] to read.
+* **callback(err, output)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **output** All the output as a [`Buffer`][node-buffer]. Use `output.toString('utf-8')` to get a readable string from it.
+* Returns: `Promise`
+* Resolves with: `output` (see callback)
+
+### Client
+
+#### client.clear(serial, pkg[, callback])
+
+Deletes all data associated with a package from the device. This is roughly analogous to `adb shell pm clear <pkg>`.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **pkg** The package name. This is NOT the APK.
+* **callback(err)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+* Returns: `Promise`
+* Resolves with: `true`
+
+#### client.connect(host[, port]&#91;, callback])
+
+Connects to the given device, which must have its ADB daemon running in tcp mode (see `client.tcpip()`) and be accessible on the same network. Same as `adb connect <host>:<port>`.
+
+* **host** The target host. Can also contain the port, in which case the port argument is not used and can be skipped.
+* **port** Optional. The target port. Defaults to `5555`.
+* **callback(err, id)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **id** The connected device ID. Can be used as `serial` in other commands.
+* Returns: `Promise`
+* Resolves with: `id` (see callback)
+
+##### Example - switch to TCP mode and set up a forward for Chrome devtools
+
+Note: be careful with using `client.listDevices()` together with `client.tcpip()` and other similar methods that modify the connection with ADB. You might have the same device twice in your device list (i.e. one device connected via both USB and TCP), which can cause havoc if run simultaneously.
+
+```javascript
+var Promise = require('bluebird')
+var client = require('adbkit').createClient()
+
+client.listDevices()
+  .then(function(devices) {
+    return Promise.map(devices, function(device) {
+      return client.tcpip(device.id)
+        .then(function(port) {
+          // Switching to TCP mode causes ADB to lose the device for a
+          // moment, so let's just wait till we get it back.
+          return client.waitForDevice(device.id).return(port)
+        })
+        .then(function(port) {
+          return client.getDHCPIpAddress(device.id)
+            .then(function(ip) {
+              return client.connect(ip, port)
+            })
+            .then(function(id) {
+              // It can take a moment for the connection to happen.
+              return client.waitForDevice(id)
+            })
+            .then(function(id) {
+              return client.forward(id, 'tcp:9222', 'localabstract:chrome_devtools_remote')
+                .then(function() {
+                  console.log('Setup devtools on "%s"', id)
+                })
+            })
+        })
+    })
+  })
+```
+
+#### client.disconnect(host[, port]&#91;, callback])
+
+Disconnects from the given device, which should have been connected via `client.connect()` or just `adb connect <host>:<port>`.
+
+* **host** The target host. Can also contain the port, in which case the port argument is not used and can be skipped. In other words you can just put the `id` you got from `client.connect()` here and it will be fine.
+* **port** Optional. The target port. Defaults to `5555`.
+* **callback(err, id)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **id** The disconnected device ID. Will no longer be usable as a `serial` in other commands until you've connected again.
+* Returns: `Promise`
+* Resolves with: `id` (see callback)
+
+#### client.forward(serial, local, remote[, callback])
+
+Forwards socket connections from the ADB server host (local) to the device (remote). This is analogous to `adb forward <local> <remote>`. It's important to note that if you are connected to a remote ADB server, the forward will be created on that host.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **local** A string representing the local endpoint on the ADB host. At time of writing, can be one of:
+    - `tcp:<port>`
+    - `localabstract:<unix domain socket name>`
+    - `localreserved:<unix domain socket name>`
+    - `localfilesystem:<unix domain socket name>`
+    - `dev:<character device name>`
+* **remote** A string representing the remote endpoint on the device. At time of writing, can be one of:
+    - Any value accepted by the `local` argument
+    - `jdwp:<process pid>`
+* **callback(err)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+* Returns: `Promise`
+* Resolves with: `true`
+
+#### client.framebuffer(serial[, format]&#91;, callback])
+
+Fetches the current **raw** framebuffer (i.e. what is visible on the screen) from the device, and optionally converts it into something more usable by using [GraphicsMagick][graphicsmagick]'s `gm` command, which must be available in `$PATH` if conversion is desired. Note that we don't bother supporting really old framebuffer formats such as RGB_565. If for some mysterious reason you happen to run into a `>=2.3` device that uses RGB_565, let us know.
+
+Note that high-resolution devices can have quite massive framebuffers. For example, a device with a resolution of 1920x1080 and 32 bit colors would have a roughly 8MB (`1920*1080*4` byte) RGBA framebuffer. Empirical tests point to about 5MB/s bandwidth limit for the ADB USB connection, which means that it can take ~1.6 seconds for the raw data to arrive, or even more if the USB connection is already congested. Using a conversion will further slow down completion.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **format** The desired output format. Any output format supported by [GraphicsMagick][graphicsmagick] (such as `'png'`) is supported. Defaults to `'raw'` for raw framebuffer data.
+* **callback(err, framebuffer)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **framebuffer** The possibly converted framebuffer stream. The stream also has a `meta` property with the following values:
+        * **version** The framebuffer version. Useful for patching possible backwards-compatibility issues.
+        * **bpp** Bits per pixel (i.e. color depth).
+        * **size** The raw byte size of the framebuffer.
+        * **width** The horizontal resolution of the framebuffer. This SHOULD always be the same as screen width. We have not encountered any device with incorrect framebuffer metadata, but according to rumors there might be some.
+        * **height** The vertical resolution of the framebuffer. This SHOULD always be the same as screen height.
+        * **red_offset** The bit offset of the red color in a pixel.
+        * **red_length** The bit length of the red color in a pixel.
+        * **blue_offset** The bit offset of the blue color in a pixel.
+        * **blue_length** The bit length of the blue color in a pixel.
+        * **green_offset** The bit offset of the green color in a pixel.
+        * **green_length** The bit length of the green color in a pixel.
+        * **alpha_offset** The bit offset of alpha in a pixel.
+        * **alpha_length** The bit length of alpha in a pixel. `0` when not available.
+        * **format** The framebuffer format for convenience. This can be one of `'bgr'`,  `'bgra'`, `'rgb'`, `'rgba'`.
+* Returns: `Promise`
+* Resolves with: `framebuffer` (see callback)
+
+#### client.getDevicePath(serial[, callback])
+
+Gets the device path of the device identified by the given serial number.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err, path)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **path** The device path. This corresponds to the device path in `client.listDevicesWithPaths()`.
+* Returns: `Promise`
+* Resolves with: `path` (see callback)
+
+#### client.getDHCPIpAddress(serial[, iface]&#91;, callback])
+
+Attemps to retrieve the IP address of the device. Roughly analogous to `adb shell getprop dhcp.<iface>.ipaddress`.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **iface** Optional. The network interface. Defaults to `'wlan0'`.
+* **callback(err, ip)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **ip** The IP address as a `String`.
+* Returns: `Promise`
+* Resolves with: `ip` (see callback)
+
+#### client.getFeatures(serial[, callback])
+
+Retrieves the features of the device identified by the given serial number. This is analogous to `adb shell pm list features`. Useful for checking whether hardware features such as NFC are available (you'd check for `'android.hardware.nfc'`).
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err, features)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **features** An object of device features. Each key corresponds to a device feature, with the value being either `true` for a boolean feature, or the feature value as a string (e.g. `'0x20000'` for `reqGlEsVersion`).
+* Returns: `Promise`
+* Resolves with: `features` (see callback)
+
+#### client.getPackages(serial[, callback])
+
+Retrieves the list of packages present on the device. This is analogous to `adb shell pm list packages`. If you just want to see if something's installed, consider using `client.isInstalled()` instead.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err, packages)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **packages** An array of package names.
+* Returns: `Promise`
+* Resolves with: `packages` (see callback)
+
+#### client.getProperties(serial[, callback])
+
+Retrieves the properties of the device identified by the given serial number. This is analogous to `adb shell getprop`.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err, properties)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **properties** An object of device properties. Each key corresponds to a device property. Convenient for accessing things like `'ro.product.model'`.
+* Returns: `Promise`
+* Resolves with: `properties` (see callback)
+
+#### client.getSerialNo(serial[, callback])
+
+Gets the serial number of the device identified by the given serial number. With our API this doesn't really make much sense, but it has been implemented for completeness. _FYI: in the raw ADB protocol you can specify a device in other ways, too._
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err, serial)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **serial** The serial number of the device.
+* Returns: `Promise`
+* Resolves with: `serial` (see callback)
+
+#### client.getState(serial[, callback])
+
+Gets the state of the device identified by the given serial number.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err, state)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **state** The device state. This corresponds to the device type in `client.listDevices()`.
+* Returns: `Promise`
+* Resolves with: `state` (see callback)
+
+#### client.install(serial, apk[, callback])
+
+Installs the APK on the device, replacing any previously installed version. This is roughly analogous to `adb install -r <apk>`.
+
+Note that if the call seems to stall, you may have to accept a dialog on the phone first.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **apk** When `String`, interpreted as a path to an APK file. When [`Stream`][node-stream], installs directly from the stream, which must be a valid APK.
+* **callback(err)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise. It may have a `.code` property containing the error code reported by the device.
+* Returns: `Promise`
+* Resolves with: `true`
+
+##### Example - install an APK from a URL
+
+This example requires the [request](https://www.npmjs.org/package/request) module. It also doesn't do any error handling (404 responses, timeouts, invalid URLs etc).
+
+```javascript
+var client = require('adbkit').createClient()
+var request = require('request')
+var Readable = require('stream').Readable
+
+// The request module implements old-style streams, so we have to wrap it.
+client.install('<serial>', new Readable().wrap(request('http://example.org/app.apk')))
+  .then(function() {
+    console.log('Installed')
+  })
+```
+
+#### client.installRemote(serial, apk[, callback])
+
+Installs an APK file which must already be located on the device file system, and replaces any previously installed version. Useful if you've previously pushed the file to the device for some reason (perhaps to have direct access to `client.push()`'s transfer stats). This is roughly analogous to `adb shell pm install -r <apk>` followed by `adb shell rm -f <apk>`.
+
+Note that if the call seems to stall, you may have to accept a dialog on the phone first.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **apk** The path to the APK file on the device. The file will be removed when the command completes.
+* **callback(err)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+* Returns: `Promise`
+* Resolves with: `true`
+
+#### client.isInstalled(serial, pkg[, callback])
+
+Tells you if the specific package is installed or not. This is analogous to `adb shell pm path <pkg>` and some output parsing.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **pkg** The package name. This is NOT the APK.
+* **callback(err, installed)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **installed** `true` if the package is installed, `false` otherwise.
+* Returns: `Promise`
+* Resolves with: `installed` (see callback)
+
+#### client.kill([callback])
+
+This kills the ADB server. Note that the next connection will attempt to start the server again when it's unable to connect.
+
+* **callback(err)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+* Returns: `Promise`
+* Resolves with: `true`
+
+#### client.listDevices([callback])
+
+Gets the list of currently connected devices and emulators.
+
+* **callback(err, devices)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **devices** An array of device objects. The device objects are plain JavaScript objects with two properties: `id` and `type`.
+        * **id** The ID of the device. For real devices, this is usually the USB identifier.
+        * **type** The device type. Values include `'emulator'` for emulators, `'device'` for devices, and `'offline'` for offline devices. `'offline'` can occur for example during boot, in low-battery conditions or when the ADB connection has not yet been approved on the device.
+* Returns: `Promise`
+* Resolves with: `devices` (see callback)
+
+#### client.listDevicesWithPaths([callback])
+
+Like `client.listDevices()`, but includes the "path" of every device.
+
+* **callback(err, devices)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **devices** An array of device objects. The device objects are plain JavaScript objects with the following properties:
+        * **id** See `client.listDevices()`.
+        * **type** See `client.listDevices()`.
+        * **path** The device path. This can be something like `usb:FD120000` for real devices.
+* Returns: `Promise`
+* Resolves with: `devices` (see callback)
+
+#### client.listForwards(serial[, callback])
+
+Lists forwarded connections on the device. This is analogous to `adb forward --list`.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err, forwards)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **forwards** An array of forward objects with the following properties:
+        * **serial** The device serial.
+        * **local** The local endpoint. Same format as `client.forward()`'s `local` argument.
+        * **remote** The remote endpoint on the device. Same format as `client.forward()`'s `remote` argument.
+* Returns: `Promise`
+* Resolves with: `forwards` (see callback)
+
+#### client.openLocal(serial, path[, callback])
+
+Opens a direct connection to a unix domain socket in the given path.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **path** The path to the socket. Prefixed with `'localfilesystem:'` by default, include another prefix (e.g. `'localabstract:'`) in the path to override.
+* **callback(err, conn)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **conn** The connection (i.e. [`net.Socket`][node-net]). Read and write as you please. Call `conn.end()` to end the connection.
+* Returns: `Promise`
+* Resolves with: `conn` (see callback)
+
+#### client.openLog(serial, name[, callback])
+
+Opens a direct connection to a binary log file, providing access to the raw log data. Note that it is usually much more convenient to use the `client.openLogcat()` method, described separately.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **name** The name of the log. Available logs include `'main'`, `'system'`, `'radio'` and `'events'`.
+* **callback(err, log)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **log** The binary log stream. Call `log.end()` when you wish to stop receiving data.
+* Returns: `Promise`
+* Resolves with: `log` (see callback)
+
+#### client.openLogcat(serial[, options]&#91;, callback])
+
+Calls the `logcat` utility on the device and hands off the connection to [adbkit-logcat][adbkit-logcat], a pure Node.js Logcat client. This is analogous to `adb logcat -B`, but the event stream will be parsed for you and a separate event will be emitted for every log entry, allowing for easy processing.
+
+For more information, check out the [adbkit-logcat][adbkit-logcat] documentation.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **options** Optional. The following options are supported:
+    - **clear** When `true`, clears logcat before opening the reader. Not set by default.
+* **callback(err, logcat)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **logcat** The Logcat client. Please see the [adbkit-logcat][adbkit-logcat] documentation for details.
+* Returns: `Promise`
+* Resolves with: `logcat` (see callback)
+
+#### client.openMonkey(serial[, port]&#91;, callback])
+
+Starts the built-in `monkey` utility on the device, connects to it using `client.openTcp()` and hands the connection to [adbkit-monkey][adbkit-monkey], a pure Node.js Monkey client. This allows you to create touch and key events, among other things.
+
+For more information, check out the [adbkit-monkey][adbkit-monkey] documentation.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **port** Optional. The device port where you'd like Monkey to run at. Defaults to `1080`.
+* **callback(err, monkey)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **monkey** The Monkey client. Please see the [adbkit-monkey][adbkit-monkey] documentation for details.
+* Returns: `Promise`
+* Resolves with: `monkey` (see callback)
+
+#### client.openProcStat(serial[, callback])
+
+Tracks `/proc/stat` and emits useful information, such as CPU load. A single sync service instance is used to download the `/proc/stat` file for processing. While doing this does consume some resources, it is very light and should not be a problem.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err, stats)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **stats** The `/proc/stat` tracker, which is an [`EventEmitter`][node-events]. Call `stat.end()` to stop tracking. The following events are available:
+        * **load** **(loads)** Emitted when a CPU load calculation is available.
+            - **loads** CPU loads of **online** CPUs. Each key is a CPU id (e.g. `'cpu0'`, `'cpu1'`) and the value an object with the following properties:
+                * **user** Percentage (0-100) of ticks spent on user programs.
+                * **nice** Percentage (0-100) of ticks spent on `nice`d user programs.
+                * **system** Percentage (0-100) of ticks spent on system programs.
+                * **idle** Percentage (0-100) of ticks spent idling.
+                * **iowait** Percentage (0-100) of ticks spent waiting for IO.
+                * **irq** Percentage (0-100) of ticks spent on hardware interrupts.
+                * **softirq** Percentage (0-100) of ticks spent on software interrupts.
+                * **steal** Percentage (0-100) of ticks stolen by others.
+                * **guest** Percentage (0-100) of ticks spent by a guest.
+                * **guestnice** Percentage (0-100) of ticks spent by a `nice`d guest.
+                * **total** Total. Always 100.
+* Returns: `Promise`
+* Resolves with: `stats` (see callback)
+
+#### client.openTcp(serial, port[, host]&#91;, callback])
+
+Opens a direct TCP connection to a port on the device, without any port forwarding required.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **port** The port number to connect to.
+* **host** Optional. The host to connect to. Allegedly this is supposed to establish a connection to the given host from the device, but we have not been able to get it to work at all. Skip the host and everything works great.
+* **callback(err, conn)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **conn** The TCP connection (i.e. [`net.Socket`][node-net]). Read and write as you please. Call `conn.end()` to end the connection.
+* Returns: `Promise`
+* Resolves with: `conn` (see callback)
+
+#### client.pull(serial, path[, callback])
+
+A convenience shortcut for `sync.pull()`, mainly for one-off use cases. The connection cannot be reused, resulting in poorer performance over multiple calls. However, the Sync client will be closed automatically for you, so that's one less thing to worry about.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **path** See `sync.pull()` for details.
+* **callback(err, transfer)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **transfer** A `PullTransfer` instance (see below)
+* Returns: `Promise`
+* Resolves with: `transfer` (see callback)
+
+#### client.push(serial, contents, path[, mode]&#91;, callback])
+
+A convenience shortcut for `sync.push()`, mainly for one-off use cases. The connection cannot be reused, resulting in poorer performance over multiple calls. However, the Sync client will be closed automatically for you, so that's one less thing to worry about.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **contents** See `sync.push()` for details.
+* **path** See `sync.push()` for details.
+* **mode** See `sync.push()` for details.
+* **callback(err, transfer)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **transfer** A `PushTransfer` instance (see below)
+* Returns: `Promise`
+* Resolves with: `transfer` (see callback)
+
+#### client.readdir(serial, path[, callback])
+
+A convenience shortcut for `sync.readdir()`, mainly for one-off use cases. The connection cannot be reused, resulting in poorer performance over multiple calls. However, the Sync client will be closed automatically for you, so that's one less thing to worry about.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **path** See `sync.readdir()` for details.
+* **callback(err, files)** Optional. Use this or the returned `Promise`. See `sync.readdir()` for details.
+* Returns: `Promise`
+* Resolves with: See `sync.readdir()` for details.
+
+#### client.reboot(serial[, callback])
+
+Reboots the device. Similar to `adb reboot`. Note that the method resolves when ADB reports that the device has been rebooted (i.e. the reboot command was successful), not when the device becomes available again.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+* Returns: `Promise`
+* Resolves with: `true`
+
+#### client.remount(serial[, callback])
+
+Attempts to remount the `/system` partition in read-write mode. This will usually only work on emulators and developer devices.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+* Returns: `Promise`
+* Resolves with: `true`
+
+#### client.screencap(serial[, callback])
+
+Takes a screenshot in PNG format using the built-in `screencap` utility. This is analogous to `adb shell screencap -p`. Sadly, the utility is not available on most Android `<=2.3` devices, but a silent fallback to the `client.framebuffer()` command in PNG mode is attempted, so you should have its dependencies installed just in case.
+
+Generating the PNG on the device naturally requires considerably more processing time on that side. However, as the data transferred over USB easily decreases by ~95%, and no conversion being required on the host, this method is usually several times faster than using the framebuffer. Naturally, this benefit does not apply if we're forced to fall back to the framebuffer.
+
+For convenience purposes, if the screencap command fails (e.g. because it doesn't exist on older Androids), we fall back to `client.framebuffer(serial, 'png')`, which is slower and has additional installation requirements.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err, screencap)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **screencap** The PNG stream.
+* Returns: `Promise`
+* Resolves with: `screencap` (see callback)
+
+#### client.shell(serial, command[, callback])
+
+Runs a shell command on the device. Note that you'll be limited to the permissions of the `shell` user, which ADB uses.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **command** The shell command to execute. When `String`, the command is run as-is. When `Array`, the elements will be rudimentarily escaped (for convenience, not security) and joined to form a command.
+* **callback(err, output)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **output** A Buffer containing all the output. Call `output.toString('utf-8')` to get a readable String from it.
+* Returns: `Promise`
+* Resolves with: `output` (see callback)
+
+##### Example
+
+```js
+var Promise = require('bluebird')
+var adb = require('adbkit')
+var client = adb.createClient()
+
+client.listDevices()
+  .then(function(devices) {
+    return Promise.map(devices, function(device) {
+      return client.shell(device.id, 'echo $RANDOM')
+        // Use the readAll() utility to read all the content without
+        // having to deal with the events. `output` will be a Buffer
+        // containing all the output.
+        .then(adb.util.readAll)
+        .then(function(output) {
+          console.log('[%s] %s', device.id, output.toString().trim())
+        })
+    })
+  })
+  .then(function() {
+    console.log('Done.')
+  })
+  .catch(function(err) {
+    console.error('Something went wrong:', err.stack)
+  })
+```
+
+#### client.startActivity(serial, options[, callback])
+
+Starts the configured activity on the device. Roughly analogous to `adb shell am start <options>`.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **options** The activity configuration. The following options are available:
+    - **debug** Set to `true` to enable debugging.
+    - **wait** Set to `true` to wait for the activity to launch.
+    - **user** The user to run as. Not set by default. If the option is unsupported by the device, an attempt will be made to run the same command again without the user option.
+    - **action** The action.
+    - **data** The data URI, if any.
+    - **mimeType** The mime type, if any.
+    - **category** The category. For multiple categories, pass an `Array`.
+    - **component** The component.
+    - **flags** Numeric flags.
+    - **extras** Any extra data.
+        * When an `Array`, each item must be an `Object` the following properties:
+            - **key** The key name.
+            - **type** The type, which can be one of `'string'`, `'null'`, `'bool'`, `'int'`, `'long'`, `'float'`, `'uri'`, `'component'`.
+            - **value** The value. Optional and unused if type is `'null'`. If an `Array`, type is automatically set to be an array of `<type>`.
+        * When an `Object`, each key is treated as the key name. Simple values like `null`, `String`, `Boolean` and `Number` are type-mapped automatically (`Number` maps to `'int'`) and can be used as-is. For more complex types, like arrays and URIs, set the value to be an `Object` like in the Array syntax (see above), but leave out the `key` property.
+* **callback(err)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+* Returns: `Promise`
+* Resolves with: `true`
+
+#### client.startService(serial, options[, callback])
+
+Starts the configured service on the device. Roughly analogous to `adb shell am startservice <options>`.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **options** The service configuration. The following options are available:
+    - **user** The user to run as. Defaults to `0`. If the option is unsupported by the device, an attempt will be made to run the same command again without the user option.
+    - **action** See `client.startActivity()` for details.
+    - **data** See `client.startActivity()` for details.
+    - **mimeType** See `client.startActivity()` for details.
+    - **category** See `client.startActivity()` for details.
+    - **component** See `client.startActivity()` for details.
+    - **flags** See `client.startActivity()` for details.
+    - **extras** See `client.startActivity()` for details.
+* Returns: `Promise`
+* Resolves with: `true`
+
+#### client.stat(serial, path[, callback])
+
+A convenience shortcut for `sync.stat()`, mainly for one-off use cases. The connection cannot be reused, resulting in poorer performance over multiple calls. However, the Sync client will be closed automatically for you, so that's one less thing to worry about.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **path** See `sync.stat()` for details.
+* **callback(err, stats)** Optional. Use this or the returned `Promise`. See `sync.stat()` for details.
+* Returns: `Promise`
+* Resolves with: See `sync.stat()` for details.
+
+#### client.syncService(serial[, callback])
+
+Establishes a new Sync connection that can be used to push and pull files. This method provides the most freedom and the best performance for repeated use, but can be a bit cumbersome to use. For simple use cases, consider using `client.stat()`, `client.push()` and `client.pull()`.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err, sync)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **sync** The Sync client. See below for details. Call `sync.end()` when done.
+* Returns: `Promise`
+* Resolves with: `sync` (see callback)
+
+#### client.tcpip(serial, port[, callback])
+
+Puts the device's ADB daemon into tcp mode, allowing you to use `adb connect` or `client.connect()` to connect to it. Note that the device will still be visible to ADB as a regular USB-connected device until you unplug it. Same as `adb tcpip <port>`.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **port** Optional. The port the device should listen on. Defaults to `5555`.
+* **callback(err, port)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **port** The port the device started listening on.
+* Returns: `Promise`
+* Resolves with: `port` (see callback)
+
+#### client.trackDevices([callback])
+
+Gets a device tracker. Events will be emitted when devices are added, removed, or their type changes (i.e. to/from `offline`). Note that the same events will be emitted for the initially connected devices also, so that you don't need to use both `client.listDevices()` and `client.trackDevices()`.
+
+Note that as the tracker will keep a connection open, you must call `tracker.end()` if you wish to stop tracking devices.
+
+* **callback(err, tracker)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **tracker** The device tracker, which is an [`EventEmitter`][node-events]. The following events are available:
+        * **add** **(device)** Emitted when a new device is connected, once per device. See `client.listDevices()` for details on the device object.
+        * **remove** **(device)** Emitted when a device is unplugged, once per device. This does not include `offline` devices, those devices are connected but unavailable to ADB. See `client.listDevices()` for details on the device object.
+        * **change** **(device)** Emitted when the `type` property of a device changes, once per device. The current value of `type` is the new value. This event usually occurs the type changes from `'device'` to `'offline'` or the other way around. See `client.listDevices()` for details on the device object and the `'offline'` type.
+        * **changeSet** **(changes)** Emitted once for all changes reported by ADB in a single run. Multiple changes can occur when, for example, a USB hub is connected/unplugged and the device list changes quickly. If you wish to process all changes at once, use this event instead of the once-per-device ones. Keep in mind that the other events will still be emitted, though.
+            - **changes** An object with the following properties always present:
+                * **added** An array of added device objects, each one as in the `add` event. Empty if none.
+                * **removed** An array of removed device objects, each one as in the `remove` event. Empty if none.
+                * **changed** An array of changed device objects, each one as in the `change` event. Empty if none.
+        * **end** Emitted when the underlying connection ends.
+        * **error** **(err)** Emitted if there's an error.
+* Returns: `Promise`
+* Resolves with: `tracker` (see callback)
+
+#### client.trackJdwp(serial[, callback])
+
+Starts a JDWP tracker for the given device.
+
+Note that as the tracker will keep a connection open, you must call `tracker.end()` if you wish to stop tracking JDWP processes.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err, tracker)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **tracker** The JDWP tracker, which is an [`EventEmitter`][node-events]. The following events are available:
+        * **add** **(pid)** Emitted when a new JDWP process becomes available, once per pid.
+        * **remove** **(pid)** Emitted when a JDWP process becomes unavailable, once per pid.
+        * **changeSet** **(changes, pids)** All changes in a single event.
+            - **changes** An object with the following properties always present:
+                * **added** An array of pids that were added. Empty if none.
+                * **removed** An array of pids that were removed. Empty if none.
+            - **pids** All currently active pids (including pids from previous runs).
+        * **end** Emitted when the underlying connection ends.
+        * **error** **(err)** Emitted if there's an error.
+* Returns: `Promise`
+* Resolves with: `tracker` (see callback)
+
+#### client.uninstall(serial, pkg[, callback])
+
+Uninstalls the package from the device. This is roughly analogous to `adb uninstall <pkg>`.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **pkg** The package name. This is NOT the APK.
+* **callback(err)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+* Returns: `Promise`
+* Resolves with: `true`
+
+#### client.usb(serial[, callback])
+
+Puts the device's ADB daemon back into USB mode. Reverses `client.tcpip()`. Same as `adb usb`.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+* Returns: `Promise`
+* Resolves with: `true`
+
+#### client.version([callback])
+
+Queries the ADB server for its version. This is mainly useful for backwards-compatibility purposes.
+
+* **callback(err, version)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **version** The version of the ADB server.
+* Returns: `Promise`
+* Resolves with: `version` (see callback)
+
+#### client.waitBootComplete(serial[, callback])
+
+Waits until the device has finished booting. Note that the device must already be seen by ADB. This is roughly analogous to periodically checking `adb shell getprop sys.boot_completed`.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err)** Optional. Use this or the returned `Promise`.
+    - **err** `null` if the device has completed booting, `Error` otherwise (can occur if the connection dies while checking).
+* Returns: `Promise`
+* Resolves with: `true`
+
+#### client.waitForDevice(serial[, callback])
+
+Waits until ADB can see the device. Note that you must know the serial in advance. Other than that, works like `adb -s serial wait-for-device`. If you're planning on reacting to random devices being plugged in and out, consider using `client.trackDevices()` instead.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err, id)** Optional. Use this or the returned `Promise`.
+    - **err** `null` if the device has completed booting, `Error` otherwise (can occur if the connection dies while checking).
+    - **id** The device ID. Can be useful for chaining.
+* Returns: `Promise`
+* Resolves with: `id` (see callback)
+
+### Sync
+
+#### sync.end()
+
+Closes the Sync connection, allowing Node to quit (assuming nothing else is keeping it alive, of course).
+
+* Returns: The sync instance.
+
+#### sync.pull(path)
+
+Pulls a file from the device as a `PullTransfer` [`Stream`][node-stream].
+
+* **path** The path to pull from.
+* Returns: A `PullTransfer` instance. See below for details.
+
+#### sync.push(contents, path[, mode])
+
+Attempts to identify `contents` and calls the appropriate `push*` method for it.
+
+* **contents** When `String`, treated as a local file path and forwarded to `sync.pushFile()`. Otherwise, treated as a [`Stream`][node-stream] and forwarded to `sync.pushStream()`.
+* **path** The path to push to.
+* **mode** Optional. The mode of the file. Defaults to `0644`.
+* Returns: A `PushTransfer` instance. See below for details.
+
+#### sync.pushFile(file, path[, mode])
+
+Pushes a local file to the given path. Note that the path must be writable by the ADB user (usually `shell`). When in doubt, use `'/data/local/tmp'` with an appropriate filename.
+
+* **file** The local file path.
+* **path** See `sync.push()` for details.
+* **mode** See `sync.push()` for details.
+* Returns: See `sync.push()` for details.
+
+#### sync.pushStream(stream, path[, mode])
+
+Pushes a [`Stream`][node-stream] to the given path. Note that the path must be writable by the ADB user (usually `shell`). When in doubt, use `'/data/local/tmp'` with an appropriate filename.
+
+* **stream** The readable stream.
+* **path** See `sync.push()` for details.
+* **mode** See `sync.push()` for details.
+* Returns: See `sync.push()` for details.
+
+#### sync.readdir(path[, callback])
+
+Retrieves a list of directory entries (e.g. files) in the given path, not including the `.` and `..` entries, just like [`fs.readdir`][node-fs]. If given a non-directory path, no entries are returned.
+
+* **path** The path.
+* **callback(err, files)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **files** An `Array` of [`fs.Stats`][node-fs-stats]-compatible instances. While the `stats.is*` methods are available, only the following properties are supported (in addition to the `name` field which contains the filename):
+        * **name** The filename.
+        * **mode** The raw mode.
+        * **size** The file size.
+        * **mtime** The time of last modification as a `Date`.
+* Returns: `Promise`
+* Resolves with: `files` (see callback)
+
+#### sync.stat(path[, callback])
+
+Retrieves information about the given path.
+
+* **path** The path.
+* **callback(err, stats)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **stats** An [`fs.Stats`][node-fs-stats] instance. While the `stats.is*` methods are available, only the following properties are supported:
+        * **mode** The raw mode.
+        * **size** The file size.
+        * **mtime** The time of last modification as a `Date`.
+* Returns: `Promise`
+* Resolves with: `stats` (see callback)
+
+#### sync.tempFile(path)
+
+A simple helper method for creating appropriate temporary filenames for pushing files. This is essentially the same as taking the basename of the file and appending it to `'/data/local/tmp/'`.
+
+* **path** The path of the file.
+* Returns: An appropriate temporary file path.
+
+### PushTransfer
+
+A simple EventEmitter, mainly for keeping track of the progress.
+
+List of events:
+
+* **progress** **(stats)** Emitted when a chunk has been flushed to the ADB connection.
+    - **stats** An object with the following stats about the transfer:
+        * **bytesTransferred** The number of bytes transferred so far.
+* **error** **(err)** Emitted on error.
+    - **err** An `Error`.
+* **end** Emitted when the transfer has successfully completed.
+
+#### pushTransfer.cancel()
+
+Cancels the transfer by ending both the stream that is being pushed and the sync connection. This will most likely end up creating a broken file on your device. **Use at your own risk.** Also note that you must create a new sync connection if you wish to continue using the sync service.
+
+* Returns: The pushTransfer instance.
+
+### PullTransfer
+
+`PullTransfer` is a [`Stream`][node-stream]. Use [`fs.createWriteStream()`][node-fs] to pipe the stream to a file if necessary.
+
+List of events:
+
+* **progress** **(stats)** Emitted when a new chunk is received.
+    - **stats** An object with the following stats about the transfer:
+        * **bytesTransferred** The number of bytes transferred so far.
+* **error** **(err)** Emitted on error.
+    - **err** An `Error`.
+* **end** Emitted when the transfer has successfully completed.
+
+#### pullTransfer.cancel()
+
+Cancels the transfer by ending the connection. Can be useful for reading endless streams of data, such as `/dev/urandom` or `/dev/zero`, perhaps for benchmarking use. Note that you must create a new sync connection if you wish to continue using the sync service.
+
+* Returns: The pullTransfer instance.
+
+# Incompatible changes in version 2.x
+
+Previously, we made extensive use of callbacks in almost every feature. While this normally works okay, ADB connections can be quite fickle, and it was starting to become difficult to handle every possible error. For example, we'd often fail to properly clean up after ourselves when a connection suddenly died in an unexpected place, causing memory and resource leaks.
+
+In version 2, we've replaced nearly all callbacks with [Promises](http://promisesaplus.com/) (using [Bluebird](https://github.com/petkaantonov/bluebird)), allowing for much more reliable error propagation and resource cleanup (thanks to `.finally()`). Additionally, many commands can now be cancelled on the fly, and although unimplemented at this point, we'll also be able to report progress on long-running commands without any changes to the API.
+
+Unfortunately, some API changes were required for this change. `client.framebuffer()`'s callback, for example, previously accepted more than one argument, which doesn't translate into Promises so well. Thankfully, it made sense to combine the arguments anyway, and we were able to do it quite cleanly.
+
+Furthermore, most API methods were returning the current instance for chaining purposes. While perhaps useful in some contexts, most of the time it probably didn't quite do what users expected, as chained calls were run in parallel rather than in serial fashion. Now every applicable API method returns a Promise, which is an incompatible but welcome change. This will also allow you to hook into `yield` and coroutines in Node 0.12.
+
+**However, all methods still accept (and will accept in the future) callbacks for those who prefer them.**
+
+Test coverage was also massively improved, although we've still got ways to go.
+
+## More information
+
+* [Android Debug Bridge][adb-site]
+    - [SERVICES.TXT][adb-services] (ADB socket protocol)
+* [Android ADB Protocols][adb-protocols] (a blog post explaining the protocol)
+* [adb.js][adb-js] (another Node.js ADB implementation)
+* [ADB Chrome extension][chrome-adb]
+
+## Contributing
+
+See [CONTRIBUTING.md](CONTRIBUTING.md).
+
+## License
+
+See [LICENSE](LICENSE).
+
+Copyright © CyberAgent, Inc. All Rights Reserved.
+
+[nodejs]: <http://nodejs.org/>
+[coffeescript]: <http://coffeescript.org/>
+[npm]: <https://npmjs.org/>
+[adb-js]: <https://github.com/flier/adb.js>
+[adb-site]: <http://developer.android.com/tools/help/adb.html>
+[adb-services]: <https://github.com/android/platform_system_core/blob/master/adb/SERVICES.TXT>
+[adb-protocols]: <http://blogs.kgsoft.co.uk/2013_03_15_prg.htm>
+[file_sync_service.h]: <https://github.com/android/platform_system_core/blob/master/adb/file_sync_service.h>
+[chrome-adb]: <https://chrome.google.com/webstore/detail/adb/dpngiggdglpdnjdoaefidgiigpemgage>
+[node-debug]: <https://npmjs.org/package/debug>
+[net-connect]: <http://nodejs.org/api/net.html#net_net_connect_options_connectionlistener>
+[node-events]: <http://nodejs.org/api/events.html>
+[node-stream]: <http://nodejs.org/api/stream.html>
+[node-buffer]: <http://nodejs.org/api/buffer.html>
+[node-net]: <http://nodejs.org/api/net.html>
+[node-fs]: <http://nodejs.org/api/fs.html>
+[node-fs-stats]: <http://nodejs.org/api/fs.html#fs_class_fs_stats>
+[node-gm]: <https://github.com/aheckmann/gm>
+[graphicsmagick]: <http://www.graphicsmagick.org/>
+[imagemagick]: <http://www.imagemagick.org/>
+[adbkit-logcat]: <https://npmjs.org/package/adbkit-logcat>
+[adbkit-monkey]: <https://npmjs.org/package/adbkit-monkey>
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/index.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/index.js
new file mode 100644
index 0000000..a4c08ac
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/index.js
@@ -0,0 +1,15 @@
+(function() {
+  var Path;
+
+  Path = require('path');
+
+  module.exports = (function() {
+    switch (Path.extname(__filename)) {
+      case '.coffee':
+        return require('./src/adb');
+      default:
+        return require('./lib/adb');
+    }
+  })();
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb.js
new file mode 100644
index 0000000..50e8e1b
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb.js
@@ -0,0 +1,27 @@
+(function() {
+  var Adb, Client, Keycode, util;
+
+  Client = require('./adb/client');
+
+  Keycode = require('./adb/keycode');
+
+  util = require('./adb/util');
+
+  Adb = (function() {
+    function Adb() {}
+
+    Adb.createClient = function(options) {
+      return new Client(options);
+    };
+
+    return Adb;
+
+  })();
+
+  Adb.Keycode = Keycode;
+
+  Adb.util = util;
+
+  module.exports = Adb;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/auth.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/auth.js
new file mode 100644
index 0000000..868ed63
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/auth.js
@@ -0,0 +1,81 @@
+(function() {
+  var Auth, BigInteger, Promise, forge;
+
+  Promise = require('bluebird');
+
+  forge = require('node-forge');
+
+  BigInteger = forge.jsbn.BigInteger;
+
+
+  /*
+  The stucture of an ADB RSAPublicKey is as follows:
+  
+       *define RSANUMBYTES 256           // 2048 bit key length
+       *define RSANUMWORDS (RSANUMBYTES / sizeof(uint32_t))
+  
+      typedef struct RSAPublicKey {
+          int len;                  // Length of n[] in number of uint32_t
+          uint32_t n0inv;           // -1 / n[0] mod 2^32
+          uint32_t n[RSANUMWORDS];  // modulus as little endian array
+          uint32_t rr[RSANUMWORDS]; // R^2 as little endian array
+          int exponent;             // 3 or 65537
+      } RSAPublicKey;
+   */
+
+  Auth = (function() {
+    var RE, readPublicKeyFromStruct;
+
+    function Auth() {}
+
+    RE = /^((?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?) (.*)$/;
+
+    readPublicKeyFromStruct = function(struct, comment) {
+      var e, key, len, md, n, offset;
+      if (!struct.length) {
+        throw new Error("Invalid public key");
+      }
+      offset = 0;
+      len = struct.readUInt32LE(offset) * 4;
+      offset += 4;
+      if (struct.length !== 4 + 4 + len + len + 4) {
+        throw new Error("Invalid public key");
+      }
+      offset += 4;
+      n = new Buffer(len);
+      struct.copy(n, 0, offset, offset + len);
+      [].reverse.call(n);
+      offset += len;
+      offset += len;
+      e = struct.readUInt32LE(offset);
+      if (!(e === 3 || e === 65537)) {
+        throw new Error("Invalid exponent " + e + ", only 3 and 65537 are supported");
+      }
+      key = forge.pki.setRsaPublicKey(new BigInteger(n.toString('hex'), 16), new BigInteger(e.toString(), 10));
+      md = forge.md.md5.create();
+      md.update(struct.toString('binary'));
+      key.fingerprint = md.digest().toHex().match(/../g).join(':');
+      key.comment = comment;
+      return key;
+    };
+
+    Auth.parsePublicKey = function(buffer) {
+      return new Promise(function(resolve, reject) {
+        var comment, match, struct;
+        if (match = RE.exec(buffer)) {
+          struct = new Buffer(match[1], 'base64');
+          comment = match[2];
+          return resolve(readPublicKeyFromStruct(struct, comment));
+        } else {
+          return reject(new Error("Unrecognizable public key format"));
+        }
+      });
+    };
+
+    return Auth;
+
+  })();
+
+  module.exports = Auth;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/client.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/client.js
new file mode 100644
index 0000000..abfc40e
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/client.js
@@ -0,0 +1,552 @@
+(function() {
+  var ClearCommand, Client, Connection, ForwardCommand, FrameBufferCommand, GetDevicePathCommand, GetFeaturesCommand, GetPackagesCommand, GetPropertiesCommand, GetSerialNoCommand, GetStateCommand, HostConnectCommand, HostDevicesCommand, HostDevicesWithPathsCommand, HostDisconnectCommand, HostKillCommand, HostTrackDevicesCommand, HostTransportCommand, HostVersionCommand, InstallCommand, IsInstalledCommand, ListForwardsCommand, LocalCommand, LogCommand, Logcat, LogcatCommand, Monkey, MonkeyCommand, Parser, ProcStat, Promise, RebootCommand, RemountCommand, ScreencapCommand, ShellCommand, StartActivityCommand, StartServiceCommand, Sync, SyncCommand, TcpCommand, TcpIpCommand, TcpUsbServer, TrackJdwpCommand, UninstallCommand, UsbCommand, WaitBootCompleteCommand, WaitForDeviceCommand, debug;
+
+  Monkey = require('adbkit-monkey');
+
+  Logcat = require('adbkit-logcat');
+
+  Promise = require('bluebird');
+
+  debug = require('debug')('adb:client');
+
+  Connection = require('./connection');
+
+  Sync = require('./sync');
+
+  Parser = require('./parser');
+
+  ProcStat = require('./proc/stat');
+
+  HostVersionCommand = require('./command/host/version');
+
+  HostConnectCommand = require('./command/host/connect');
+
+  HostDevicesCommand = require('./command/host/devices');
+
+  HostDevicesWithPathsCommand = require('./command/host/deviceswithpaths');
+
+  HostDisconnectCommand = require('./command/host/disconnect');
+
+  HostTrackDevicesCommand = require('./command/host/trackdevices');
+
+  HostKillCommand = require('./command/host/kill');
+
+  HostTransportCommand = require('./command/host/transport');
+
+  ClearCommand = require('./command/host-transport/clear');
+
+  FrameBufferCommand = require('./command/host-transport/framebuffer');
+
+  GetFeaturesCommand = require('./command/host-transport/getfeatures');
+
+  GetPackagesCommand = require('./command/host-transport/getpackages');
+
+  GetPropertiesCommand = require('./command/host-transport/getproperties');
+
+  InstallCommand = require('./command/host-transport/install');
+
+  IsInstalledCommand = require('./command/host-transport/isinstalled');
+
+  LocalCommand = require('./command/host-transport/local');
+
+  LogcatCommand = require('./command/host-transport/logcat');
+
+  LogCommand = require('./command/host-transport/log');
+
+  MonkeyCommand = require('./command/host-transport/monkey');
+
+  RebootCommand = require('./command/host-transport/reboot');
+
+  RemountCommand = require('./command/host-transport/remount');
+
+  ScreencapCommand = require('./command/host-transport/screencap');
+
+  ShellCommand = require('./command/host-transport/shell');
+
+  StartActivityCommand = require('./command/host-transport/startactivity');
+
+  StartServiceCommand = require('./command/host-transport/startservice');
+
+  SyncCommand = require('./command/host-transport/sync');
+
+  TcpCommand = require('./command/host-transport/tcp');
+
+  TcpIpCommand = require('./command/host-transport/tcpip');
+
+  TrackJdwpCommand = require('./command/host-transport/trackjdwp');
+
+  UninstallCommand = require('./command/host-transport/uninstall');
+
+  UsbCommand = require('./command/host-transport/usb');
+
+  WaitBootCompleteCommand = require('./command/host-transport/waitbootcomplete');
+
+  ForwardCommand = require('./command/host-serial/forward');
+
+  GetDevicePathCommand = require('./command/host-serial/getdevicepath');
+
+  GetSerialNoCommand = require('./command/host-serial/getserialno');
+
+  GetStateCommand = require('./command/host-serial/getstate');
+
+  ListForwardsCommand = require('./command/host-serial/listforwards');
+
+  WaitForDeviceCommand = require('./command/host-serial/waitfordevice');
+
+  TcpUsbServer = require('./tcpusb/server');
+
+  Client = (function() {
+    var NoUserOptionError;
+
+    function Client(options) {
+      var _base, _base1;
+      this.options = options != null ? options : {};
+      (_base = this.options).port || (_base.port = 5037);
+      (_base1 = this.options).bin || (_base1.bin = 'adb');
+    }
+
+    Client.prototype.createTcpUsbBridge = function(serial, options) {
+      return new TcpUsbServer(this, serial, options);
+    };
+
+    Client.prototype.connection = function() {
+      var conn, connectListener, errorListener, resolver;
+      resolver = Promise.defer();
+      conn = new Connection(this.options).on('error', errorListener = function(err) {
+        return resolver.reject(err);
+      }).on('connect', connectListener = function() {
+        return resolver.resolve(conn);
+      }).connect();
+      return resolver.promise["finally"](function() {
+        conn.removeListener('error', errorListener);
+        return conn.removeListener('connect', connectListener);
+      });
+    };
+
+    Client.prototype.version = function(callback) {
+      return this.connection().then(function(conn) {
+        return new HostVersionCommand(conn).execute();
+      }).nodeify(callback);
+    };
+
+    Client.prototype.connect = function(host, port, callback) {
+      var _ref;
+      if (port == null) {
+        port = 5555;
+      }
+      if (typeof port === 'function') {
+        callback = port;
+        port = 5555;
+      }
+      if (host.indexOf(':') !== -1) {
+        _ref = host.split(':', 2), host = _ref[0], port = _ref[1];
+      }
+      return this.connection().then(function(conn) {
+        return new HostConnectCommand(conn).execute(host, port);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.disconnect = function(host, port, callback) {
+      var _ref;
+      if (port == null) {
+        port = 5555;
+      }
+      if (typeof port === 'function') {
+        callback = port;
+        port = 5555;
+      }
+      if (host.indexOf(':') !== -1) {
+        _ref = host.split(':', 2), host = _ref[0], port = _ref[1];
+      }
+      return this.connection().then(function(conn) {
+        return new HostDisconnectCommand(conn).execute(host, port);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.listDevices = function(callback) {
+      return this.connection().then(function(conn) {
+        return new HostDevicesCommand(conn).execute();
+      }).nodeify(callback);
+    };
+
+    Client.prototype.listDevicesWithPaths = function(callback) {
+      return this.connection().then(function(conn) {
+        return new HostDevicesWithPathsCommand(conn).execute();
+      }).nodeify(callback);
+    };
+
+    Client.prototype.trackDevices = function(callback) {
+      return this.connection().then(function(conn) {
+        return new HostTrackDevicesCommand(conn).execute();
+      }).nodeify(callback);
+    };
+
+    Client.prototype.kill = function(callback) {
+      return this.connection().then(function(conn) {
+        return new HostKillCommand(conn).execute();
+      }).nodeify(callback);
+    };
+
+    Client.prototype.getSerialNo = function(serial, callback) {
+      return this.connection().then(function(conn) {
+        return new GetSerialNoCommand(conn).execute(serial);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.getDevicePath = function(serial, callback) {
+      return this.connection().then(function(conn) {
+        return new GetDevicePathCommand(conn).execute(serial);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.getState = function(serial, callback) {
+      return this.connection().then(function(conn) {
+        return new GetStateCommand(conn).execute(serial);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.getProperties = function(serial, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new GetPropertiesCommand(transport).execute();
+      }).nodeify(callback);
+    };
+
+    Client.prototype.getFeatures = function(serial, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new GetFeaturesCommand(transport).execute();
+      }).nodeify(callback);
+    };
+
+    Client.prototype.getPackages = function(serial, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new GetPackagesCommand(transport).execute();
+      }).nodeify(callback);
+    };
+
+    Client.prototype.getDHCPIpAddress = function(serial, iface, callback) {
+      if (iface == null) {
+        iface = 'wlan0';
+      }
+      if (typeof iface === 'function') {
+        callback = iface;
+        iface = 'wlan0';
+      }
+      return this.getProperties(serial).then(function(properties) {
+        var ip;
+        if (ip = properties["dhcp." + iface + ".ipaddress"]) {
+          return ip;
+        }
+        throw new Error("Unable to find ipaddress for '" + iface + "'");
+      });
+    };
+
+    Client.prototype.forward = function(serial, local, remote, callback) {
+      return this.connection().then(function(conn) {
+        return new ForwardCommand(conn).execute(serial, local, remote);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.listForwards = function(serial, callback) {
+      return this.connection().then(function(conn) {
+        return new ListForwardsCommand(conn).execute(serial);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.transport = function(serial, callback) {
+      return this.connection().then(function(conn) {
+        return new HostTransportCommand(conn).execute(serial)["return"](conn);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.shell = function(serial, command, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new ShellCommand(transport).execute(command);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.reboot = function(serial, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new RebootCommand(transport).execute();
+      }).nodeify(callback);
+    };
+
+    Client.prototype.remount = function(serial, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new RemountCommand(transport).execute();
+      }).nodeify(callback);
+    };
+
+    Client.prototype.trackJdwp = function(serial, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new TrackJdwpCommand(transport).execute();
+      }).nodeify(callback);
+    };
+
+    Client.prototype.framebuffer = function(serial, format, callback) {
+      if (format == null) {
+        format = 'raw';
+      }
+      if (typeof format === 'function') {
+        callback = format;
+        format = 'raw';
+      }
+      return this.transport(serial).then(function(transport) {
+        return new FrameBufferCommand(transport).execute(format);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.screencap = function(serial, callback) {
+      return this.transport(serial).then((function(_this) {
+        return function(transport) {
+          return new ScreencapCommand(transport).execute()["catch"](function(err) {
+            debug("Emulating screencap command due to '" + err + "'");
+            return _this.framebuffer(serial, 'png');
+          });
+        };
+      })(this)).nodeify(callback);
+    };
+
+    Client.prototype.openLocal = function(serial, path, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new LocalCommand(transport).execute(path);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.openLog = function(serial, name, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new LogCommand(transport).execute(name);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.openTcp = function(serial, port, host, callback) {
+      if (typeof host === 'function') {
+        callback = host;
+        host = void 0;
+      }
+      return this.transport(serial).then(function(transport) {
+        return new TcpCommand(transport).execute(port, host);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.openMonkey = function(serial, port, callback) {
+      var tryConnect;
+      if (port == null) {
+        port = 1080;
+      }
+      if (typeof port === 'function') {
+        callback = port;
+        port = 1080;
+      }
+      tryConnect = (function(_this) {
+        return function(times) {
+          return _this.openTcp(serial, port).then(function(stream) {
+            return Monkey.connectStream(stream);
+          })["catch"](function(err) {
+            if (times -= 1) {
+              debug("Monkey can't be reached, trying " + times + " more times");
+              return Promise.delay(100).then(function() {
+                return tryConnect(times);
+              });
+            } else {
+              throw err;
+            }
+          });
+        };
+      })(this);
+      return tryConnect(1)["catch"]((function(_this) {
+        return function(err) {
+          return _this.transport(serial).then(function(transport) {
+            return new MonkeyCommand(transport).execute(port);
+          }).then(function(out) {
+            return tryConnect(20).then(function(monkey) {
+              return monkey.once('end', function() {
+                return out.end();
+              });
+            });
+          });
+        };
+      })(this)).nodeify(callback);
+    };
+
+    Client.prototype.openLogcat = function(serial, options, callback) {
+      if (typeof options === 'function') {
+        callback = options;
+        options = {};
+      }
+      return this.transport(serial).then(function(transport) {
+        return new LogcatCommand(transport).execute(options);
+      }).then(function(stream) {
+        return Logcat.readStream(stream, {
+          fixLineFeeds: false
+        });
+      }).nodeify(callback);
+    };
+
+    Client.prototype.openProcStat = function(serial, callback) {
+      return this.syncService(serial).then(function(sync) {
+        return new ProcStat(sync);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.clear = function(serial, pkg, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new ClearCommand(transport).execute(pkg);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.install = function(serial, apk, callback) {
+      var temp;
+      temp = Sync.temp(typeof apk === 'string' ? apk : '_stream.apk');
+      return this.push(serial, apk, temp).then((function(_this) {
+        return function(transfer) {
+          var endListener, errorListener, resolver;
+          resolver = Promise.defer();
+          transfer.on('error', errorListener = function(err) {
+            return resolver.reject(err);
+          });
+          transfer.on('end', endListener = function() {
+            return resolver.resolve(_this.installRemote(serial, temp));
+          });
+          return resolver.promise["finally"](function() {
+            transfer.removeListener('error', errorListener);
+            return transfer.removeListener('end', endListener);
+          });
+        };
+      })(this)).nodeify(callback);
+    };
+
+    Client.prototype.installRemote = function(serial, apk, callback) {
+      return this.transport(serial).then((function(_this) {
+        return function(transport) {
+          return new InstallCommand(transport).execute(apk).then(function() {
+            return _this.shell(serial, ['rm', '-f', apk]);
+          }).then(function(stream) {
+            return new Parser(stream).readAll();
+          }).then(function(out) {
+            return true;
+          });
+        };
+      })(this)).nodeify(callback);
+    };
+
+    Client.prototype.uninstall = function(serial, pkg, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new UninstallCommand(transport).execute(pkg);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.isInstalled = function(serial, pkg, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new IsInstalledCommand(transport).execute(pkg);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.startActivity = function(serial, options, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new StartActivityCommand(transport).execute(options);
+      })["catch"](NoUserOptionError, (function(_this) {
+        return function() {
+          options.user = null;
+          return _this.startActivity(serial, options);
+        };
+      })(this)).nodeify(callback);
+    };
+
+    Client.prototype.startService = function(serial, options, callback) {
+      return this.transport(serial).then(function(transport) {
+        if (!(options.user || options.user === null)) {
+          options.user = 0;
+        }
+        return new StartServiceCommand(transport).execute(options);
+      })["catch"](NoUserOptionError, (function(_this) {
+        return function() {
+          options.user = null;
+          return _this.startService(serial, options);
+        };
+      })(this)).nodeify(callback);
+    };
+
+    Client.prototype.syncService = function(serial, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new SyncCommand(transport).execute();
+      }).nodeify(callback);
+    };
+
+    Client.prototype.stat = function(serial, path, callback) {
+      return this.syncService(serial).then(function(sync) {
+        return sync.stat(path)["finally"](function() {
+          return sync.end();
+        });
+      }).nodeify(callback);
+    };
+
+    Client.prototype.readdir = function(serial, path, callback) {
+      return this.syncService(serial).then(function(sync) {
+        return sync.readdir(path)["finally"](function() {
+          return sync.end();
+        });
+      }).nodeify(callback);
+    };
+
+    Client.prototype.pull = function(serial, path, callback) {
+      return this.syncService(serial).then(function(sync) {
+        return sync.pull(path).on('end', function() {
+          return sync.end();
+        });
+      }).nodeify(callback);
+    };
+
+    Client.prototype.push = function(serial, contents, path, mode, callback) {
+      if (typeof mode === 'function') {
+        callback = mode;
+        mode = void 0;
+      }
+      return this.syncService(serial).then(function(sync) {
+        return sync.push(contents, path, mode).on('end', function() {
+          return sync.end();
+        });
+      }).nodeify(callback);
+    };
+
+    Client.prototype.tcpip = function(serial, port, callback) {
+      if (port == null) {
+        port = 5555;
+      }
+      if (typeof port === 'function') {
+        callback = port;
+        port = 5555;
+      }
+      return this.transport(serial).then(function(transport) {
+        return new TcpIpCommand(transport).execute(port);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.usb = function(serial, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new UsbCommand(transport).execute();
+      }).nodeify(callback);
+    };
+
+    Client.prototype.waitBootComplete = function(serial, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new WaitBootCompleteCommand(transport).execute();
+      }).nodeify(callback);
+    };
+
+    Client.prototype.waitForDevice = function(serial, callback) {
+      return this.connection().then(function(conn) {
+        return new WaitForDeviceCommand(conn).execute(serial);
+      }).nodeify(callback);
+    };
+
+    NoUserOptionError = function(err) {
+      return err.message.indexOf('--user') !== -1;
+    };
+
+    return Client;
+
+  })();
+
+  module.exports = Client;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command.js
new file mode 100644
index 0000000..a01f7d3
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command.js
@@ -0,0 +1,48 @@
+(function() {
+  var Command, Parser, Protocol, debug;
+
+  debug = require('debug')('adb:command');
+
+  Parser = require('./parser');
+
+  Protocol = require('./protocol');
+
+  Command = (function() {
+    var RE_SQUOT;
+
+    RE_SQUOT = /'/g;
+
+    function Command(connection) {
+      this.connection = connection;
+      this.parser = this.connection.parser;
+      this.protocol = Protocol;
+    }
+
+    Command.prototype.execute = function() {
+      throw new Exception('Missing implementation');
+    };
+
+    Command.prototype._send = function(data) {
+      var encoded;
+      encoded = Protocol.encodeData(data);
+      debug("Send '" + encoded + "'");
+      this.connection.write(encoded);
+      return this;
+    };
+
+    Command.prototype._escape = function(arg) {
+      switch (typeof arg) {
+        case 'number':
+          return arg;
+        default:
+          return "'" + arg.toString().replace(RE_SQUOT, "'\"'\"'") + "'";
+      }
+    };
+
+    return Command;
+
+  })();
+
+  module.exports = Command;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-serial/forward.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-serial/forward.js
new file mode 100644
index 0000000..597d37f
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-serial/forward.js
@@ -0,0 +1,48 @@
+(function() {
+  var Command, ForwardCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  ForwardCommand = (function(_super) {
+    __extends(ForwardCommand, _super);
+
+    function ForwardCommand() {
+      return ForwardCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    ForwardCommand.prototype.execute = function(serial, local, remote) {
+      this._send("host-serial:" + serial + ":forward:" + local + ";" + remote);
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readAscii(4).then(function(reply) {
+                switch (reply) {
+                  case Protocol.OKAY:
+                    return true;
+                  case Protocol.FAIL:
+                    return _this.parser.readError();
+                  default:
+                    return _this.parser.unexpected(reply, 'OKAY or FAIL');
+                }
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return ForwardCommand;
+
+  })(Command);
+
+  module.exports = ForwardCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-serial/getdevicepath.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-serial/getdevicepath.js
new file mode 100644
index 0000000..b171cae
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-serial/getdevicepath.js
@@ -0,0 +1,41 @@
+(function() {
+  var Command, GetDevicePathCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  GetDevicePathCommand = (function(_super) {
+    __extends(GetDevicePathCommand, _super);
+
+    function GetDevicePathCommand() {
+      return GetDevicePathCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    GetDevicePathCommand.prototype.execute = function(serial) {
+      this._send("host-serial:" + serial + ":get-devpath");
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readValue().then(function(value) {
+                return value.toString();
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return GetDevicePathCommand;
+
+  })(Command);
+
+  module.exports = GetDevicePathCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-serial/getserialno.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-serial/getserialno.js
new file mode 100644
index 0000000..28f858e
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-serial/getserialno.js
@@ -0,0 +1,41 @@
+(function() {
+  var Command, GetSerialNoCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  GetSerialNoCommand = (function(_super) {
+    __extends(GetSerialNoCommand, _super);
+
+    function GetSerialNoCommand() {
+      return GetSerialNoCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    GetSerialNoCommand.prototype.execute = function(serial) {
+      this._send("host-serial:" + serial + ":get-serialno");
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readValue().then(function(value) {
+                return value.toString();
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return GetSerialNoCommand;
+
+  })(Command);
+
+  module.exports = GetSerialNoCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-serial/getstate.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-serial/getstate.js
new file mode 100644
index 0000000..fa6378f
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-serial/getstate.js
@@ -0,0 +1,41 @@
+(function() {
+  var Command, GetStateCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  GetStateCommand = (function(_super) {
+    __extends(GetStateCommand, _super);
+
+    function GetStateCommand() {
+      return GetStateCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    GetStateCommand.prototype.execute = function(serial) {
+      this._send("host-serial:" + serial + ":get-state");
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readValue().then(function(value) {
+                return value.toString();
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return GetStateCommand;
+
+  })(Command);
+
+  module.exports = GetStateCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-serial/listforwards.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-serial/listforwards.js
new file mode 100644
index 0000000..e426d9d
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-serial/listforwards.js
@@ -0,0 +1,59 @@
+(function() {
+  var Command, ListForwardsCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  ListForwardsCommand = (function(_super) {
+    __extends(ListForwardsCommand, _super);
+
+    function ListForwardsCommand() {
+      return ListForwardsCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    ListForwardsCommand.prototype.execute = function(serial) {
+      this._send("host-serial:" + serial + ":list-forward");
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readValue().then(function(value) {
+                return _this._parseForwards(value);
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    ListForwardsCommand.prototype._parseForwards = function(value) {
+      var forward, forwards, local, remote, serial, _i, _len, _ref, _ref1;
+      forwards = [];
+      _ref = value.toString().split('\n');
+      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+        forward = _ref[_i];
+        if (forward) {
+          _ref1 = forward.split(/\s+/), serial = _ref1[0], local = _ref1[1], remote = _ref1[2];
+          forwards.push({
+            serial: serial,
+            local: local,
+            remote: remote
+          });
+        }
+      }
+      return forwards;
+    };
+
+    return ListForwardsCommand;
+
+  })(Command);
+
+  module.exports = ListForwardsCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-serial/waitfordevice.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-serial/waitfordevice.js
new file mode 100644
index 0000000..efa1a0b
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-serial/waitfordevice.js
@@ -0,0 +1,48 @@
+(function() {
+  var Command, Protocol, WaitForDeviceCommand,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  WaitForDeviceCommand = (function(_super) {
+    __extends(WaitForDeviceCommand, _super);
+
+    function WaitForDeviceCommand() {
+      return WaitForDeviceCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    WaitForDeviceCommand.prototype.execute = function(serial) {
+      this._send("host-serial:" + serial + ":wait-for-any");
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readAscii(4).then(function(reply) {
+                switch (reply) {
+                  case Protocol.OKAY:
+                    return serial;
+                  case Protocol.FAIL:
+                    return _this.parser.readError();
+                  default:
+                    return _this.parser.unexpected(reply, 'OKAY or FAIL');
+                }
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return WaitForDeviceCommand;
+
+  })(Command);
+
+  module.exports = WaitForDeviceCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/clear.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/clear.js
new file mode 100644
index 0000000..7a8acb0
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/clear.js
@@ -0,0 +1,47 @@
+(function() {
+  var ClearCommand, Command, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  ClearCommand = (function(_super) {
+    __extends(ClearCommand, _super);
+
+    function ClearCommand() {
+      return ClearCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    ClearCommand.prototype.execute = function(pkg) {
+      this._send("shell:pm clear " + pkg);
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.searchLine(/^(Success|Failed)$/).then(function(result) {
+                switch (result[0]) {
+                  case 'Success':
+                    return true;
+                  case 'Failed':
+                    _this.connection.end();
+                    throw new Error("Package '" + pkg + "' could not be cleared");
+                }
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return ClearCommand;
+
+  })(Command);
+
+  module.exports = ClearCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/framebuffer.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/framebuffer.js
new file mode 100644
index 0000000..5689f3c
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/framebuffer.js
@@ -0,0 +1,117 @@
+(function() {
+  var Assert, Command, FrameBufferCommand, Protocol, RgbTransform, debug, spawn,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Assert = require('assert');
+
+  spawn = require('child_process').spawn;
+
+  debug = require('debug')('adb:command:framebuffer');
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  RgbTransform = require('../../framebuffer/rgbtransform');
+
+  FrameBufferCommand = (function(_super) {
+    __extends(FrameBufferCommand, _super);
+
+    function FrameBufferCommand() {
+      return FrameBufferCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    FrameBufferCommand.prototype.execute = function(format) {
+      this._send('framebuffer:');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readBytes(52).then(function(header) {
+                var meta, stream;
+                meta = _this._parseHeader(header);
+                switch (format) {
+                  case 'raw':
+                    stream = _this.parser.raw();
+                    stream.meta = meta;
+                    return stream;
+                  default:
+                    stream = _this._convert(meta);
+                    stream.meta = meta;
+                    return stream;
+                }
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    FrameBufferCommand.prototype._convert = function(meta, format, raw) {
+      var proc, transform;
+      debug("Converting raw framebuffer stream into " + (format.toUpperCase()));
+      switch (meta.format) {
+        case 'rgb':
+        case 'rgba':
+          break;
+        default:
+          debug("Silently transforming '" + meta.format + "' into 'rgb' for `gm`");
+          transform = new RgbTransform(meta);
+          meta.format = 'rgb';
+          raw = this.parser.raw().pipe(transform);
+      }
+      proc = spawn('gm', ['convert', '-size', "" + meta.width + "x" + meta.height, "" + meta.format + ":-", "" + format + ":-"]);
+      raw.pipe(proc.stdin);
+      return proc.stdout;
+    };
+
+    FrameBufferCommand.prototype._parseHeader = function(header) {
+      var meta, offset;
+      meta = {};
+      offset = 0;
+      meta.version = header.readUInt32LE(offset);
+      if (meta.version === 16) {
+        throw new Error('Old-style raw images are not supported');
+      }
+      offset += 4;
+      meta.bpp = header.readUInt32LE(offset);
+      offset += 4;
+      meta.size = header.readUInt32LE(offset);
+      offset += 4;
+      meta.width = header.readUInt32LE(offset);
+      offset += 4;
+      meta.height = header.readUInt32LE(offset);
+      offset += 4;
+      meta.red_offset = header.readUInt32LE(offset);
+      offset += 4;
+      meta.red_length = header.readUInt32LE(offset);
+      offset += 4;
+      meta.blue_offset = header.readUInt32LE(offset);
+      offset += 4;
+      meta.blue_length = header.readUInt32LE(offset);
+      offset += 4;
+      meta.green_offset = header.readUInt32LE(offset);
+      offset += 4;
+      meta.green_length = header.readUInt32LE(offset);
+      offset += 4;
+      meta.alpha_offset = header.readUInt32LE(offset);
+      offset += 4;
+      meta.alpha_length = header.readUInt32LE(offset);
+      meta.format = meta.blue_offset === 0 ? 'bgr' : 'rgb';
+      if (meta.bpp === 32 || meta.alpha_length) {
+        meta.format += 'a';
+      }
+      return meta;
+    };
+
+    return FrameBufferCommand;
+
+  })(Command);
+
+  module.exports = FrameBufferCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/getfeatures.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/getfeatures.js
new file mode 100644
index 0000000..be6083d
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/getfeatures.js
@@ -0,0 +1,54 @@
+(function() {
+  var Command, GetFeaturesCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  GetFeaturesCommand = (function(_super) {
+    var RE_FEATURE;
+
+    __extends(GetFeaturesCommand, _super);
+
+    function GetFeaturesCommand() {
+      return GetFeaturesCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    RE_FEATURE = /^feature:(.*?)(?:=(.*?))?\r?$/gm;
+
+    GetFeaturesCommand.prototype.execute = function() {
+      this._send('shell:pm list features 2>/dev/null');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readAll().then(function(data) {
+                return _this._parseFeatures(data.toString());
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    GetFeaturesCommand.prototype._parseFeatures = function(value) {
+      var features, match;
+      features = {};
+      while (match = RE_FEATURE.exec(value)) {
+        features[match[1]] = match[2] || true;
+      }
+      return features;
+    };
+
+    return GetFeaturesCommand;
+
+  })(Command);
+
+  module.exports = GetFeaturesCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/getpackages.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/getpackages.js
new file mode 100644
index 0000000..6e98fdf
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/getpackages.js
@@ -0,0 +1,54 @@
+(function() {
+  var Command, GetPackagesCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  GetPackagesCommand = (function(_super) {
+    var RE_PACKAGE;
+
+    __extends(GetPackagesCommand, _super);
+
+    function GetPackagesCommand() {
+      return GetPackagesCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    RE_PACKAGE = /^package:(.*?)\r?$/gm;
+
+    GetPackagesCommand.prototype.execute = function() {
+      this._send('shell:pm list packages 2>/dev/null');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readAll().then(function(data) {
+                return _this._parsePackages(data.toString());
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    GetPackagesCommand.prototype._parsePackages = function(value) {
+      var features, match;
+      features = [];
+      while (match = RE_PACKAGE.exec(value)) {
+        features.push(match[1]);
+      }
+      return features;
+    };
+
+    return GetPackagesCommand;
+
+  })(Command);
+
+  module.exports = GetPackagesCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/getproperties.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/getproperties.js
new file mode 100644
index 0000000..1c3756b
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/getproperties.js
@@ -0,0 +1,56 @@
+(function() {
+  var Command, GetPropertiesCommand, LineTransform, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  LineTransform = require('../../linetransform');
+
+  GetPropertiesCommand = (function(_super) {
+    var RE_KEYVAL;
+
+    __extends(GetPropertiesCommand, _super);
+
+    function GetPropertiesCommand() {
+      return GetPropertiesCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    RE_KEYVAL = /^\[([\s\S]*?)\]: \[([\s\S]*?)\]\r?$/gm;
+
+    GetPropertiesCommand.prototype.execute = function() {
+      this._send('shell:getprop');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readAll().then(function(data) {
+                return _this._parseProperties(data.toString());
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    GetPropertiesCommand.prototype._parseProperties = function(value) {
+      var match, properties;
+      properties = {};
+      while (match = RE_KEYVAL.exec(value)) {
+        properties[match[1]] = match[2];
+      }
+      return properties;
+    };
+
+    return GetPropertiesCommand;
+
+  })(Command);
+
+  module.exports = GetPropertiesCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/install.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/install.js
new file mode 100644
index 0000000..634dbab
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/install.js
@@ -0,0 +1,51 @@
+(function() {
+  var Command, InstallCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  InstallCommand = (function(_super) {
+    __extends(InstallCommand, _super);
+
+    function InstallCommand() {
+      return InstallCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    InstallCommand.prototype.execute = function(apk) {
+      this._send("shell:pm install -r '" + apk + "'");
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.searchLine(/^(Success|Failure \[(.*?)\])$/).then(function(match) {
+                var code, err;
+                if (match[1] === 'Success') {
+                  return true;
+                } else {
+                  code = match[2];
+                  err = new Error("" + apk + " could not be installed [" + code + "]");
+                  err.code = code;
+                  throw err;
+                }
+              })["finally"](function() {
+                return _this.parser.readAll();
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return InstallCommand;
+
+  })(Command);
+
+  module.exports = InstallCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/isinstalled.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/isinstalled.js
new file mode 100644
index 0000000..463c0f9
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/isinstalled.js
@@ -0,0 +1,50 @@
+(function() {
+  var Command, IsInstalledCommand, Parser, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  Parser = require('../../parser');
+
+  IsInstalledCommand = (function(_super) {
+    __extends(IsInstalledCommand, _super);
+
+    function IsInstalledCommand() {
+      return IsInstalledCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    IsInstalledCommand.prototype.execute = function(pkg) {
+      this._send("shell:pm path " + pkg + " 2>/dev/null");
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readAscii(8).then(function(reply) {
+                switch (reply) {
+                  case 'package:':
+                    return true;
+                  default:
+                    return _this.parser.unexpected(reply, "'package:'");
+                }
+              })["catch"](Parser.PrematureEOFError, function(err) {
+                return false;
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return IsInstalledCommand;
+
+  })(Command);
+
+  module.exports = IsInstalledCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/local.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/local.js
new file mode 100644
index 0000000..fc764a1
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/local.js
@@ -0,0 +1,39 @@
+(function() {
+  var Command, LocalCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  LocalCommand = (function(_super) {
+    __extends(LocalCommand, _super);
+
+    function LocalCommand() {
+      return LocalCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    LocalCommand.prototype.execute = function(path) {
+      this._send(/:/.test(path) ? path : "localfilesystem:" + path);
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.raw();
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return LocalCommand;
+
+  })(Command);
+
+  module.exports = LocalCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/log.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/log.js
new file mode 100644
index 0000000..1e8ddc9
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/log.js
@@ -0,0 +1,39 @@
+(function() {
+  var Command, LogCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  LogCommand = (function(_super) {
+    __extends(LogCommand, _super);
+
+    function LogCommand() {
+      return LogCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    LogCommand.prototype.execute = function(name) {
+      this._send("log:" + name);
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.raw();
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return LogCommand;
+
+  })(Command);
+
+  module.exports = LogCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/logcat.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/logcat.js
new file mode 100644
index 0000000..778ec34
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/logcat.js
@@ -0,0 +1,49 @@
+(function() {
+  var Command, LineTransform, LogcatCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  LineTransform = require('../../linetransform');
+
+  LogcatCommand = (function(_super) {
+    __extends(LogcatCommand, _super);
+
+    function LogcatCommand() {
+      return LogcatCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    LogcatCommand.prototype.execute = function(options) {
+      var cmd;
+      if (options == null) {
+        options = {};
+      }
+      cmd = 'logcat -B *:I 2>/dev/null';
+      if (options.clear) {
+        cmd = "logcat -c 2>/dev/null && " + cmd;
+      }
+      this._send("shell:" + cmd);
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.raw().pipe(new LineTransform);
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return LogcatCommand;
+
+  })(Command);
+
+  module.exports = LogcatCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/monkey.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/monkey.js
new file mode 100644
index 0000000..d913e9e
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/monkey.js
@@ -0,0 +1,45 @@
+(function() {
+  var Command, MonkeyCommand, Promise, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Promise = require('bluebird');
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  MonkeyCommand = (function(_super) {
+    __extends(MonkeyCommand, _super);
+
+    function MonkeyCommand() {
+      return MonkeyCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    MonkeyCommand.prototype.execute = function(port) {
+      this._send("shell:EXTERNAL_STORAGE=/data/local/tmp monkey --port " + port + " -v");
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.searchLine(/^:Monkey:/).timeout(1000).then(function() {
+                return _this.parser.raw();
+              })["catch"](Promise.TimeoutError, function(err) {
+                return _this.parser.raw();
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return MonkeyCommand;
+
+  })(Command);
+
+  module.exports = MonkeyCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/reboot.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/reboot.js
new file mode 100644
index 0000000..57a8b51
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/reboot.js
@@ -0,0 +1,39 @@
+(function() {
+  var Command, Protocol, RebootCommand,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  RebootCommand = (function(_super) {
+    __extends(RebootCommand, _super);
+
+    function RebootCommand() {
+      return RebootCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    RebootCommand.prototype.execute = function() {
+      this._send('reboot:');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readAll()["return"](true);
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return RebootCommand;
+
+  })(Command);
+
+  module.exports = RebootCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/remount.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/remount.js
new file mode 100644
index 0000000..173f45d
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/remount.js
@@ -0,0 +1,39 @@
+(function() {
+  var Command, Protocol, RemountCommand,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  RemountCommand = (function(_super) {
+    __extends(RemountCommand, _super);
+
+    function RemountCommand() {
+      return RemountCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    RemountCommand.prototype.execute = function() {
+      this._send('remount:');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return true;
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return RemountCommand;
+
+  })(Command);
+
+  module.exports = RemountCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/screencap.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/screencap.js
new file mode 100644
index 0000000..30098c2
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/screencap.js
@@ -0,0 +1,57 @@
+(function() {
+  var Command, LineTransform, Parser, Promise, Protocol, ScreencapCommand,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Promise = require('bluebird');
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  Parser = require('../../parser');
+
+  LineTransform = require('../../linetransform');
+
+  ScreencapCommand = (function(_super) {
+    __extends(ScreencapCommand, _super);
+
+    function ScreencapCommand() {
+      return ScreencapCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    ScreencapCommand.prototype.execute = function() {
+      this._send('shell:screencap -p 2>/dev/null');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          var endListener, out, readableListener, resolver;
+          switch (reply) {
+            case Protocol.OKAY:
+              resolver = Promise.defer();
+              out = _this.parser.raw().pipe(new LineTransform);
+              out.on('readable', readableListener = function() {
+                return resolver.resolve(out);
+              });
+              out.on('end', endListener = function() {
+                return resolver.reject(new Error('Unable to run screencap command'));
+              });
+              return resolver.promise["finally"](function() {
+                out.removeListener('end', endListener);
+                return out.removeListener('readable', readableListener);
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return ScreencapCommand;
+
+  })(Command);
+
+  module.exports = ScreencapCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/shell.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/shell.js
new file mode 100644
index 0000000..4ac2074
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/shell.js
@@ -0,0 +1,42 @@
+(function() {
+  var Command, Protocol, ShellCommand,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  ShellCommand = (function(_super) {
+    __extends(ShellCommand, _super);
+
+    function ShellCommand() {
+      return ShellCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    ShellCommand.prototype.execute = function(command) {
+      if (Array.isArray(command)) {
+        command = command.map(this._escape).join(' ');
+      }
+      this._send("shell:" + command);
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.raw();
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return ShellCommand;
+
+  })(Command);
+
+  module.exports = ShellCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/startactivity.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/startactivity.js
new file mode 100644
index 0000000..572abda
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/startactivity.js
@@ -0,0 +1,187 @@
+(function() {
+  var Command, Parser, Protocol, StartActivityCommand,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  Parser = require('../../parser');
+
+  StartActivityCommand = (function(_super) {
+    var EXTRA_TYPES, RE_ERROR;
+
+    __extends(StartActivityCommand, _super);
+
+    function StartActivityCommand() {
+      return StartActivityCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    RE_ERROR = /^Error: (.*)$/;
+
+    EXTRA_TYPES = {
+      string: 's',
+      "null": 'sn',
+      bool: 'z',
+      int: 'i',
+      long: 'l',
+      float: 'l',
+      uri: 'u',
+      component: 'cn'
+    };
+
+    StartActivityCommand.prototype.execute = function(options) {
+      var args;
+      args = this._intentArgs(options);
+      if (options.debug) {
+        args.push('-D');
+      }
+      if (options.wait) {
+        args.push('-W');
+      }
+      if (options.user || options.user === 0) {
+        args.push('--user', this._escape(options.user));
+      }
+      return this._run('start', args);
+    };
+
+    StartActivityCommand.prototype._run = function(command, args) {
+      this._send("shell:am " + command + " " + (args.join(' ')));
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.searchLine(RE_ERROR)["finally"](function() {
+                return _this.connection.end();
+              }).then(function(match) {
+                throw new Error(match[1]);
+              })["catch"](Parser.PrematureEOFError, function(err) {
+                return true;
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    StartActivityCommand.prototype._intentArgs = function(options) {
+      var args;
+      args = [];
+      if (options.extras) {
+        args.push.apply(args, this._formatExtras(options.extras));
+      }
+      if (options.action) {
+        args.push('-a', this._escape(options.action));
+      }
+      if (options.data) {
+        args.push('-d', this._escape(options.data));
+      }
+      if (options.mimeType) {
+        args.push('-t', this._escape(options.mimeType));
+      }
+      if (options.category) {
+        if (Array.isArray(options.category)) {
+          options.category.forEach((function(_this) {
+            return function(category) {
+              return args.push('-c', _this._escape(category));
+            };
+          })(this));
+        } else {
+          args.push('-c', this._escape(options.category));
+        }
+      }
+      if (options.component) {
+        args.push('-n', this._escape(options.component));
+      }
+      if (options.flags) {
+        args.push('-f', this._escape(options.flags));
+      }
+      return args;
+    };
+
+    StartActivityCommand.prototype._formatExtras = function(extras) {
+      if (!extras) {
+        return [];
+      }
+      if (Array.isArray(extras)) {
+        return extras.reduce((function(_this) {
+          return function(all, extra) {
+            return all.concat(_this._formatLongExtra(extra));
+          };
+        })(this), []);
+      } else {
+        return Object.keys(extras).reduce((function(_this) {
+          return function(all, key) {
+            return all.concat(_this._formatShortExtra(key, extras[key]));
+          };
+        })(this), []);
+      }
+    };
+
+    StartActivityCommand.prototype._formatShortExtra = function(key, value) {
+      var sugared;
+      sugared = {
+        key: key
+      };
+      if (value === null) {
+        sugared.type = 'null';
+      } else if (Array.isArray(value)) {
+        throw new Error("Refusing to format array value '" + key + "' using short syntax; empty array would cause unpredictable results due to unknown type. Please use long syntax instead.");
+      } else {
+        switch (typeof value) {
+          case 'string':
+            sugared.type = 'string';
+            sugared.value = value;
+            break;
+          case 'boolean':
+            sugared.type = 'bool';
+            sugared.value = value;
+            break;
+          case 'number':
+            sugared.type = 'int';
+            sugared.value = value;
+            break;
+          case 'object':
+            sugared = value;
+            sugared.key = key;
+        }
+      }
+      return this._formatLongExtra(sugared);
+    };
+
+    StartActivityCommand.prototype._formatLongExtra = function(extra) {
+      var args, type;
+      args = [];
+      if (!extra.type) {
+        extra.type = 'string';
+      }
+      type = EXTRA_TYPES[extra.type];
+      if (!type) {
+        throw new Error("Unsupported type '" + extra.type + "' for extra '" + extra.key + "'");
+      }
+      if (extra.type === 'null') {
+        args.push("--e" + type);
+        args.push(this._escape(extra.key));
+      } else if (Array.isArray(extra.value)) {
+        args.push("--e" + type + "a");
+        args.push(this._escape(extra.key));
+        args.push(this._escape(extra.value.join(',')));
+      } else {
+        args.push("--e" + type);
+        args.push(this._escape(extra.key));
+        args.push(this._escape(extra.value));
+      }
+      return args;
+    };
+
+    return StartActivityCommand;
+
+  })(Command);
+
+  module.exports = StartActivityCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/startservice.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/startservice.js
new file mode 100644
index 0000000..a119a36
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/startservice.js
@@ -0,0 +1,36 @@
+(function() {
+  var Command, Parser, Protocol, StartActivityCommand, StartServiceCommand,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  Parser = require('../../parser');
+
+  StartActivityCommand = require('./startactivity');
+
+  StartServiceCommand = (function(_super) {
+    __extends(StartServiceCommand, _super);
+
+    function StartServiceCommand() {
+      return StartServiceCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    StartServiceCommand.prototype.execute = function(options) {
+      var args;
+      args = this._intentArgs(options);
+      if (options.user || options.user === 0) {
+        args.push('--user', this._escape(options.user));
+      }
+      return this._run('startservice', args);
+    };
+
+    return StartServiceCommand;
+
+  })(StartActivityCommand);
+
+  module.exports = StartServiceCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/sync.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/sync.js
new file mode 100644
index 0000000..638cc6e
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/sync.js
@@ -0,0 +1,41 @@
+(function() {
+  var Command, Protocol, Sync, SyncCommand,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  Sync = require('../../sync');
+
+  SyncCommand = (function(_super) {
+    __extends(SyncCommand, _super);
+
+    function SyncCommand() {
+      return SyncCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    SyncCommand.prototype.execute = function() {
+      this._send('sync:');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return new Sync(_this.connection);
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return SyncCommand;
+
+  })(Command);
+
+  module.exports = SyncCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/tcp.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/tcp.js
new file mode 100644
index 0000000..05c7ec2
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/tcp.js
@@ -0,0 +1,39 @@
+(function() {
+  var Command, Protocol, TcpCommand,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  TcpCommand = (function(_super) {
+    __extends(TcpCommand, _super);
+
+    function TcpCommand() {
+      return TcpCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    TcpCommand.prototype.execute = function(port, host) {
+      this._send(("tcp:" + port) + (host ? ":" + host : ''));
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.raw();
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return TcpCommand;
+
+  })(Command);
+
+  module.exports = TcpCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/tcpip.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/tcpip.js
new file mode 100644
index 0000000..821b5fa
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/tcpip.js
@@ -0,0 +1,51 @@
+(function() {
+  var Command, LineTransform, Protocol, TcpIpCommand,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  LineTransform = require('../../linetransform');
+
+  TcpIpCommand = (function(_super) {
+    var RE_OK;
+
+    __extends(TcpIpCommand, _super);
+
+    function TcpIpCommand() {
+      return TcpIpCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    RE_OK = /restarting in/;
+
+    TcpIpCommand.prototype.execute = function(port) {
+      this._send("tcpip:" + port);
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readAll().then(function(value) {
+                if (RE_OK.test(value)) {
+                  return port;
+                } else {
+                  throw new Error(value.toString().trim());
+                }
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return TcpIpCommand;
+
+  })(Command);
+
+  module.exports = TcpIpCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/trackjdwp.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/trackjdwp.js
new file mode 100644
index 0000000..07ec6a9
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/trackjdwp.js
@@ -0,0 +1,123 @@
+(function() {
+  var Command, EventEmitter, Parser, Promise, Protocol, TrackJdwpCommand,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  EventEmitter = require('events').EventEmitter;
+
+  Promise = require('bluebird');
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  Parser = require('../../parser');
+
+  TrackJdwpCommand = (function(_super) {
+    var Tracker;
+
+    __extends(TrackJdwpCommand, _super);
+
+    function TrackJdwpCommand() {
+      return TrackJdwpCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    TrackJdwpCommand.prototype.execute = function() {
+      this._send('track-jdwp');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return new Tracker(_this);
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    Tracker = (function(_super1) {
+      __extends(Tracker, _super1);
+
+      function Tracker(command) {
+        this.command = command;
+        this.pids = [];
+        this.pidMap = Object.create(null);
+        this.reader = this.read()["catch"](Parser.PrematureEOFError, (function(_this) {
+          return function(err) {
+            return _this.emit('end');
+          };
+        })(this))["catch"](Promise.CancellationError, (function(_this) {
+          return function(err) {
+            _this.command.connection.end();
+            return _this.emit('end');
+          };
+        })(this))["catch"]((function(_this) {
+          return function(err) {
+            _this.command.connection.end();
+            _this.emit('error', err);
+            return _this.emit('end');
+          };
+        })(this));
+      }
+
+      Tracker.prototype.read = function() {
+        return this.command.parser.readValue().cancellable().then((function(_this) {
+          return function(list) {
+            var maybeEmpty, pids;
+            pids = list.toString().split('\n');
+            if (maybeEmpty = pids.pop()) {
+              pids.push(maybeEmpty);
+            }
+            return _this.update(pids);
+          };
+        })(this));
+      };
+
+      Tracker.prototype.update = function(newList) {
+        var changeSet, newMap, pid, _i, _j, _len, _len1, _ref;
+        changeSet = {
+          removed: [],
+          added: []
+        };
+        newMap = Object.create(null);
+        for (_i = 0, _len = newList.length; _i < _len; _i++) {
+          pid = newList[_i];
+          if (!this.pidMap[pid]) {
+            changeSet.added.push(pid);
+            this.emit('add', pid);
+            newMap[pid] = pid;
+          }
+        }
+        _ref = this.pids;
+        for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) {
+          pid = _ref[_j];
+          if (!newMap[pid]) {
+            changeSet.removed.push(pid);
+            this.emit('remove', pid);
+          }
+        }
+        this.pids = newList;
+        this.pidMap = newMap;
+        this.emit('changeSet', changeSet, newList);
+        return this;
+      };
+
+      Tracker.prototype.end = function() {
+        this.reader.cancel();
+        return this;
+      };
+
+      return Tracker;
+
+    })(EventEmitter);
+
+    return TrackJdwpCommand;
+
+  })(Command);
+
+  module.exports = TrackJdwpCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/uninstall.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/uninstall.js
new file mode 100644
index 0000000..0fdb286
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/uninstall.js
@@ -0,0 +1,47 @@
+(function() {
+  var Command, Protocol, UninstallCommand,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  UninstallCommand = (function(_super) {
+    __extends(UninstallCommand, _super);
+
+    function UninstallCommand() {
+      return UninstallCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    UninstallCommand.prototype.execute = function(pkg) {
+      this._send("shell:pm uninstall " + pkg + " 2>/dev/null");
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readAscii(7).then(function(reply) {
+                switch (reply) {
+                  case 'Success':
+                  case 'Failure':
+                    return true;
+                  default:
+                    return _this.parser.unexpected(reply, "'Success' or 'Failure'");
+                }
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, "OKAY or FAIL");
+          }
+        };
+      })(this));
+    };
+
+    return UninstallCommand;
+
+  })(Command);
+
+  module.exports = UninstallCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/usb.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/usb.js
new file mode 100644
index 0000000..8c5e797
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/usb.js
@@ -0,0 +1,51 @@
+(function() {
+  var Command, LineTransform, Protocol, UsbCommand,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  LineTransform = require('../../linetransform');
+
+  UsbCommand = (function(_super) {
+    var RE_OK;
+
+    __extends(UsbCommand, _super);
+
+    function UsbCommand() {
+      return UsbCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    RE_OK = /restarting in/;
+
+    UsbCommand.prototype.execute = function() {
+      this._send('usb:');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readAll().then(function(value) {
+                if (RE_OK.test(value)) {
+                  return true;
+                } else {
+                  throw new Error(value.toString().trim());
+                }
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return UsbCommand;
+
+  })(Command);
+
+  module.exports = UsbCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/waitbootcomplete.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/waitbootcomplete.js
new file mode 100644
index 0000000..739e397
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host-transport/waitbootcomplete.js
@@ -0,0 +1,45 @@
+(function() {
+  var Command, Protocol, WaitBootCompleteCommand, debug,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  debug = require('debug')('adb:command:waitboot');
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  WaitBootCompleteCommand = (function(_super) {
+    __extends(WaitBootCompleteCommand, _super);
+
+    function WaitBootCompleteCommand() {
+      return WaitBootCompleteCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    WaitBootCompleteCommand.prototype.execute = function() {
+      this._send('shell:while getprop sys.boot_completed 2>/dev/null; do sleep 1; done');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.searchLine(/^1$/)["finally"](function() {
+                return _this.connection.end();
+              }).then(function() {
+                return true;
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return WaitBootCompleteCommand;
+
+  })(Command);
+
+  module.exports = WaitBootCompleteCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host/connect.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host/connect.js
new file mode 100644
index 0000000..fd9da1c
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host/connect.js
@@ -0,0 +1,49 @@
+(function() {
+  var Command, ConnectCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  ConnectCommand = (function(_super) {
+    var RE_OK;
+
+    __extends(ConnectCommand, _super);
+
+    function ConnectCommand() {
+      return ConnectCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    RE_OK = /connected to|already connected/;
+
+    ConnectCommand.prototype.execute = function(host, port) {
+      this._send("host:connect:" + host + ":" + port);
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readValue().then(function(value) {
+                if (RE_OK.test(value)) {
+                  return "" + host + ":" + port;
+                } else {
+                  throw new Error(value.toString());
+                }
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return ConnectCommand;
+
+  })(Command);
+
+  module.exports = ConnectCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host/devices.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host/devices.js
new file mode 100644
index 0000000..e9d5a46
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host/devices.js
@@ -0,0 +1,67 @@
+(function() {
+  var Command, HostDevicesCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  HostDevicesCommand = (function(_super) {
+    __extends(HostDevicesCommand, _super);
+
+    function HostDevicesCommand() {
+      return HostDevicesCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    HostDevicesCommand.prototype.execute = function() {
+      this._send('host:devices');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this._readDevices();
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    HostDevicesCommand.prototype._readDevices = function() {
+      return this.parser.readValue().then((function(_this) {
+        return function(value) {
+          return _this._parseDevices(value);
+        };
+      })(this));
+    };
+
+    HostDevicesCommand.prototype._parseDevices = function(value) {
+      var devices, id, line, type, _i, _len, _ref, _ref1;
+      devices = [];
+      if (!value.length) {
+        return devices;
+      }
+      _ref = value.toString('ascii').split('\n');
+      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+        line = _ref[_i];
+        if (line) {
+          _ref1 = line.split('\t'), id = _ref1[0], type = _ref1[1];
+          devices.push({
+            id: id,
+            type: type
+          });
+        }
+      }
+      return devices;
+    };
+
+    return HostDevicesCommand;
+
+  })(Command);
+
+  module.exports = HostDevicesCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host/deviceswithpaths.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host/deviceswithpaths.js
new file mode 100644
index 0000000..a63cdf1
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host/deviceswithpaths.js
@@ -0,0 +1,68 @@
+(function() {
+  var Command, HostDevicesWithPathsCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  HostDevicesWithPathsCommand = (function(_super) {
+    __extends(HostDevicesWithPathsCommand, _super);
+
+    function HostDevicesWithPathsCommand() {
+      return HostDevicesWithPathsCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    HostDevicesWithPathsCommand.prototype.execute = function() {
+      this._send('host:devices-l');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this._readDevices();
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    HostDevicesWithPathsCommand.prototype._readDevices = function() {
+      return this.parser.readValue().then((function(_this) {
+        return function(value) {
+          return _this._parseDevices(value);
+        };
+      })(this));
+    };
+
+    HostDevicesWithPathsCommand.prototype._parseDevices = function(value) {
+      var devices, id, line, path, type, _i, _len, _ref, _ref1;
+      devices = [];
+      if (!value.length) {
+        return devices;
+      }
+      _ref = value.toString('ascii').split('\n');
+      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+        line = _ref[_i];
+        if (line) {
+          _ref1 = line.split(/\s+/), id = _ref1[0], type = _ref1[1], path = _ref1[2];
+          devices.push({
+            id: id,
+            type: type,
+            path: path
+          });
+        }
+      }
+      return devices;
+    };
+
+    return HostDevicesWithPathsCommand;
+
+  })(Command);
+
+  module.exports = HostDevicesWithPathsCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host/disconnect.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host/disconnect.js
new file mode 100644
index 0000000..9cf978e
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host/disconnect.js
@@ -0,0 +1,49 @@
+(function() {
+  var Command, DisconnectCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  DisconnectCommand = (function(_super) {
+    var RE_OK;
+
+    __extends(DisconnectCommand, _super);
+
+    function DisconnectCommand() {
+      return DisconnectCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    RE_OK = /^$/;
+
+    DisconnectCommand.prototype.execute = function(host, port) {
+      this._send("host:disconnect:" + host + ":" + port);
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readValue().then(function(value) {
+                if (RE_OK.test(value)) {
+                  return "" + host + ":" + port;
+                } else {
+                  throw new Error(value.toString());
+                }
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return DisconnectCommand;
+
+  })(Command);
+
+  module.exports = DisconnectCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host/kill.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host/kill.js
new file mode 100644
index 0000000..74d531c
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host/kill.js
@@ -0,0 +1,39 @@
+(function() {
+  var Command, HostKillCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  HostKillCommand = (function(_super) {
+    __extends(HostKillCommand, _super);
+
+    function HostKillCommand() {
+      return HostKillCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    HostKillCommand.prototype.execute = function() {
+      this._send('host:kill');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return true;
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return HostKillCommand;
+
+  })(Command);
+
+  module.exports = HostKillCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host/trackdevices.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host/trackdevices.js
new file mode 100644
index 0000000..103793a
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host/trackdevices.js
@@ -0,0 +1,43 @@
+(function() {
+  var Command, HostDevicesCommand, HostTrackDevicesCommand, Protocol, Tracker,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  Tracker = require('../../tracker');
+
+  HostDevicesCommand = require('./devices');
+
+  HostTrackDevicesCommand = (function(_super) {
+    __extends(HostTrackDevicesCommand, _super);
+
+    function HostTrackDevicesCommand() {
+      return HostTrackDevicesCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    HostTrackDevicesCommand.prototype.execute = function() {
+      this._send('host:track-devices');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return new Tracker(_this);
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return HostTrackDevicesCommand;
+
+  })(HostDevicesCommand);
+
+  module.exports = HostTrackDevicesCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host/transport.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host/transport.js
new file mode 100644
index 0000000..8264c4f
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host/transport.js
@@ -0,0 +1,39 @@
+(function() {
+  var Command, HostTransportCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  HostTransportCommand = (function(_super) {
+    __extends(HostTransportCommand, _super);
+
+    function HostTransportCommand() {
+      return HostTransportCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    HostTransportCommand.prototype.execute = function(serial) {
+      this._send("host:transport:" + serial);
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return true;
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return HostTransportCommand;
+
+  })(Command);
+
+  module.exports = HostTransportCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host/version.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host/version.js
new file mode 100644
index 0000000..48accc1
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/command/host/version.js
@@ -0,0 +1,45 @@
+(function() {
+  var Command, HostVersionCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  HostVersionCommand = (function(_super) {
+    __extends(HostVersionCommand, _super);
+
+    function HostVersionCommand() {
+      return HostVersionCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    HostVersionCommand.prototype.execute = function() {
+      this._send('host:version');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readValue().then(function(value) {
+                return _this._parseVersion(value);
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this._parseVersion(reply);
+          }
+        };
+      })(this));
+    };
+
+    HostVersionCommand.prototype._parseVersion = function(version) {
+      return parseInt(version, 16);
+    };
+
+    return HostVersionCommand;
+
+  })(Command);
+
+  module.exports = HostVersionCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/connection.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/connection.js
new file mode 100644
index 0000000..bccd503
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/connection.js
@@ -0,0 +1,108 @@
+(function() {
+  var Connection, EventEmitter, Net, Parser, debug, execFile,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Net = require('net');
+
+  debug = require('debug')('adb:connection');
+
+  EventEmitter = require('events').EventEmitter;
+
+  execFile = require('child_process').execFile;
+
+  Parser = require('./parser');
+
+  Connection = (function(_super) {
+    __extends(Connection, _super);
+
+    function Connection(options) {
+      this.options = options;
+      this.socket = null;
+      this.parser = null;
+      this.triedStarting = false;
+    }
+
+    Connection.prototype.connect = function() {
+      this.socket = Net.connect(this.options);
+      this.parser = new Parser(this.socket);
+      this.socket.on('connect', (function(_this) {
+        return function() {
+          return _this.emit('connect');
+        };
+      })(this));
+      this.socket.on('end', (function(_this) {
+        return function() {
+          return _this.emit('end');
+        };
+      })(this));
+      this.socket.on('drain', (function(_this) {
+        return function() {
+          return _this.emit('drain');
+        };
+      })(this));
+      this.socket.on('timeout', (function(_this) {
+        return function() {
+          return _this.emit('timeout');
+        };
+      })(this));
+      this.socket.on('error', (function(_this) {
+        return function(err) {
+          return _this._handleError(err);
+        };
+      })(this));
+      this.socket.on('close', (function(_this) {
+        return function(hadError) {
+          return _this.emit('close', hadError);
+        };
+      })(this));
+      return this;
+    };
+
+    Connection.prototype.end = function() {
+      this.socket.end();
+      return this;
+    };
+
+    Connection.prototype.write = function(data, callback) {
+      this.socket.write(data, callback);
+      return this;
+    };
+
+    Connection.prototype.startServer = function(callback) {
+      debug("Starting ADB server via '" + this.options.bin + " start-server'");
+      return this._exec(['start-server'], {}, callback);
+    };
+
+    Connection.prototype._exec = function(args, options, callback) {
+      debug("CLI: " + this.options.bin + " " + (args.join(' ')));
+      execFile(this.options.bin, args, options, callback);
+      return this;
+    };
+
+    Connection.prototype._handleError = function(err) {
+      if (err.code === 'ECONNREFUSED' && !this.triedStarting) {
+        debug("Connection was refused, let's try starting the server once");
+        this.triedStarting = true;
+        this.startServer((function(_this) {
+          return function(err) {
+            if (err) {
+              return _this._handleError(err);
+            }
+            return _this.connect();
+          };
+        })(this));
+      } else {
+        debug("Connection had an error: " + err.message);
+        this.emit('error', err);
+        this.end();
+      }
+    };
+
+    return Connection;
+
+  })(EventEmitter);
+
+  module.exports = Connection;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/framebuffer/rgbtransform.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/framebuffer/rgbtransform.js
new file mode 100644
index 0000000..e759722
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/framebuffer/rgbtransform.js
@@ -0,0 +1,58 @@
+(function() {
+  var Assert, RgbTransform, Stream,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Assert = require('assert');
+
+  Stream = require('stream');
+
+  RgbTransform = (function(_super) {
+    __extends(RgbTransform, _super);
+
+    function RgbTransform(meta, options) {
+      this.meta = meta;
+      this._buffer = new Buffer('');
+      Assert.ok(this.meta.bpp === 24 || this.meta.bpp === 32, 'Only 24-bit and 32-bit raw images with 8-bits per color are supported');
+      this._r_pos = this.meta.red_offset / 8;
+      this._g_pos = this.meta.green_offset / 8;
+      this._b_pos = this.meta.blue_offset / 8;
+      this._a_pos = this.meta.alpha_offset / 8;
+      this._pixel_bytes = this.meta.bpp / 8;
+      RgbTransform.__super__.constructor.call(this, options);
+    }
+
+    RgbTransform.prototype._transform = function(chunk, encoding, done) {
+      var b, g, r, sourceCursor, target, targetCursor;
+      if (this._buffer.length) {
+        this._buffer = Buffer.concat([this._buffer, chunk], this._buffer.length + chunk.length);
+      } else {
+        this._buffer = chunk;
+      }
+      sourceCursor = 0;
+      targetCursor = 0;
+      target = this._pixel_bytes === 3 ? this._buffer : new Buffer(Math.max(4, chunk.length / this._pixel_bytes * 3));
+      while (this._buffer.length - sourceCursor >= this._pixel_bytes) {
+        r = this._buffer[sourceCursor + this._r_pos];
+        g = this._buffer[sourceCursor + this._g_pos];
+        b = this._buffer[sourceCursor + this._b_pos];
+        target[targetCursor + 0] = r;
+        target[targetCursor + 1] = g;
+        target[targetCursor + 2] = b;
+        sourceCursor += this._pixel_bytes;
+        targetCursor += 3;
+      }
+      if (targetCursor) {
+        this.push(target.slice(0, targetCursor));
+        this._buffer = this._buffer.slice(sourceCursor);
+      }
+      done();
+    };
+
+    return RgbTransform;
+
+  })(Stream.Transform);
+
+  module.exports = RgbTransform;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/keycode.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/keycode.js
new file mode 100644
index 0000000..0ca3c08
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/keycode.js
@@ -0,0 +1,228 @@
+(function() {
+  module.exports = {
+    KEYCODE_UNKNOWN: 0,
+    KEYCODE_SOFT_LEFT: 1,
+    KEYCODE_SOFT_RIGHT: 2,
+    KEYCODE_HOME: 3,
+    KEYCODE_BACK: 4,
+    KEYCODE_CALL: 5,
+    KEYCODE_ENDCALL: 6,
+    KEYCODE_0: 7,
+    KEYCODE_1: 8,
+    KEYCODE_2: 9,
+    KEYCODE_3: 10,
+    KEYCODE_4: 11,
+    KEYCODE_5: 12,
+    KEYCODE_6: 13,
+    KEYCODE_7: 14,
+    KEYCODE_8: 15,
+    KEYCODE_9: 16,
+    KEYCODE_STAR: 17,
+    KEYCODE_POUND: 18,
+    KEYCODE_DPAD_UP: 19,
+    KEYCODE_DPAD_DOWN: 20,
+    KEYCODE_DPAD_LEFT: 21,
+    KEYCODE_DPAD_RIGHT: 22,
+    KEYCODE_DPAD_CENTER: 23,
+    KEYCODE_VOLUME_UP: 24,
+    KEYCODE_VOLUME_DOWN: 25,
+    KEYCODE_POWER: 26,
+    KEYCODE_CAMERA: 27,
+    KEYCODE_CLEAR: 28,
+    KEYCODE_A: 29,
+    KEYCODE_B: 30,
+    KEYCODE_C: 31,
+    KEYCODE_D: 32,
+    KEYCODE_E: 33,
+    KEYCODE_F: 34,
+    KEYCODE_G: 35,
+    KEYCODE_H: 36,
+    KEYCODE_I: 37,
+    KEYCODE_J: 38,
+    KEYCODE_K: 39,
+    KEYCODE_L: 40,
+    KEYCODE_M: 41,
+    KEYCODE_N: 42,
+    KEYCODE_O: 43,
+    KEYCODE_P: 44,
+    KEYCODE_Q: 45,
+    KEYCODE_R: 46,
+    KEYCODE_S: 47,
+    KEYCODE_T: 48,
+    KEYCODE_U: 49,
+    KEYCODE_V: 50,
+    KEYCODE_W: 51,
+    KEYCODE_X: 52,
+    KEYCODE_Y: 53,
+    KEYCODE_Z: 54,
+    KEYCODE_COMMA: 55,
+    KEYCODE_PERIOD: 56,
+    KEYCODE_ALT_LEFT: 57,
+    KEYCODE_ALT_RIGHT: 58,
+    KEYCODE_SHIFT_LEFT: 59,
+    KEYCODE_SHIFT_RIGHT: 60,
+    KEYCODE_TAB: 61,
+    KEYCODE_SPACE: 62,
+    KEYCODE_SYM: 63,
+    KEYCODE_EXPLORER: 64,
+    KEYCODE_ENVELOPE: 65,
+    KEYCODE_ENTER: 66,
+    KEYCODE_DEL: 67,
+    KEYCODE_GRAVE: 68,
+    KEYCODE_MINUS: 69,
+    KEYCODE_EQUALS: 70,
+    KEYCODE_LEFT_BRACKET: 71,
+    KEYCODE_RIGHT_BRACKET: 72,
+    KEYCODE_BACKSLASH: 73,
+    KEYCODE_SEMICOLON: 74,
+    KEYCODE_APOSTROPHE: 75,
+    KEYCODE_SLASH: 76,
+    KEYCODE_AT: 77,
+    KEYCODE_NUM: 78,
+    KEYCODE_HEADSETHOOK: 79,
+    KEYCODE_FOCUS: 80,
+    KEYCODE_PLUS: 81,
+    KEYCODE_MENU: 82,
+    KEYCODE_NOTIFICATION: 83,
+    KEYCODE_SEARCH: 84,
+    KEYCODE_MEDIA_PLAY_PAUSE: 85,
+    KEYCODE_MEDIA_STOP: 86,
+    KEYCODE_MEDIA_NEXT: 87,
+    KEYCODE_MEDIA_PREVIOUS: 88,
+    KEYCODE_MEDIA_REWIND: 89,
+    KEYCODE_MEDIA_FAST_FORWARD: 90,
+    KEYCODE_MUTE: 91,
+    KEYCODE_PAGE_UP: 92,
+    KEYCODE_PAGE_DOWN: 93,
+    KEYCODE_PICTSYMBOLS: 94,
+    KEYCODE_SWITCH_CHARSET: 95,
+    KEYCODE_BUTTON_A: 96,
+    KEYCODE_BUTTON_B: 97,
+    KEYCODE_BUTTON_C: 98,
+    KEYCODE_BUTTON_X: 99,
+    KEYCODE_BUTTON_Y: 100,
+    KEYCODE_BUTTON_Z: 101,
+    KEYCODE_BUTTON_L1: 102,
+    KEYCODE_BUTTON_R1: 103,
+    KEYCODE_BUTTON_L2: 104,
+    KEYCODE_BUTTON_R2: 105,
+    KEYCODE_BUTTON_THUMBL: 106,
+    KEYCODE_BUTTON_THUMBR: 107,
+    KEYCODE_BUTTON_START: 108,
+    KEYCODE_BUTTON_SELECT: 109,
+    KEYCODE_BUTTON_MODE: 110,
+    KEYCODE_ESCAPE: 111,
+    KEYCODE_FORWARD_DEL: 112,
+    KEYCODE_CTRL_LEFT: 113,
+    KEYCODE_CTRL_RIGHT: 114,
+    KEYCODE_CAPS_LOCK: 115,
+    KEYCODE_SCROLL_LOCK: 116,
+    KEYCODE_META_LEFT: 117,
+    KEYCODE_META_RIGHT: 118,
+    KEYCODE_FUNCTION: 119,
+    KEYCODE_SYSRQ: 120,
+    KEYCODE_BREAK: 121,
+    KEYCODE_MOVE_HOME: 122,
+    KEYCODE_MOVE_END: 123,
+    KEYCODE_INSERT: 124,
+    KEYCODE_FORWARD: 125,
+    KEYCODE_MEDIA_PLAY: 126,
+    KEYCODE_MEDIA_PAUSE: 127,
+    KEYCODE_MEDIA_CLOSE: 128,
+    KEYCODE_MEDIA_EJECT: 129,
+    KEYCODE_MEDIA_RECORD: 130,
+    KEYCODE_F1: 131,
+    KEYCODE_F2: 132,
+    KEYCODE_F3: 133,
+    KEYCODE_F4: 134,
+    KEYCODE_F5: 135,
+    KEYCODE_F6: 136,
+    KEYCODE_F7: 137,
+    KEYCODE_F8: 138,
+    KEYCODE_F9: 139,
+    KEYCODE_F10: 140,
+    KEYCODE_F11: 141,
+    KEYCODE_F12: 142,
+    KEYCODE_NUM_LOCK: 143,
+    KEYCODE_NUMPAD_0: 144,
+    KEYCODE_NUMPAD_1: 145,
+    KEYCODE_NUMPAD_2: 146,
+    KEYCODE_NUMPAD_3: 147,
+    KEYCODE_NUMPAD_4: 148,
+    KEYCODE_NUMPAD_5: 149,
+    KEYCODE_NUMPAD_6: 150,
+    KEYCODE_NUMPAD_7: 151,
+    KEYCODE_NUMPAD_8: 152,
+    KEYCODE_NUMPAD_9: 153,
+    KEYCODE_NUMPAD_DIVIDE: 154,
+    KEYCODE_NUMPAD_MULTIPLY: 155,
+    KEYCODE_NUMPAD_SUBTRACT: 156,
+    KEYCODE_NUMPAD_ADD: 157,
+    KEYCODE_NUMPAD_DOT: 158,
+    KEYCODE_NUMPAD_COMMA: 159,
+    KEYCODE_NUMPAD_ENTER: 160,
+    KEYCODE_NUMPAD_EQUALS: 161,
+    KEYCODE_NUMPAD_LEFT_PAREN: 162,
+    KEYCODE_NUMPAD_RIGHT_PAREN: 163,
+    KEYCODE_VOLUME_MUTE: 164,
+    KEYCODE_INFO: 165,
+    KEYCODE_CHANNEL_UP: 166,
+    KEYCODE_CHANNEL_DOWN: 167,
+    KEYCODE_ZOOM_IN: 168,
+    KEYCODE_ZOOM_OUT: 169,
+    KEYCODE_TV: 170,
+    KEYCODE_WINDOW: 171,
+    KEYCODE_GUIDE: 172,
+    KEYCODE_DVR: 173,
+    KEYCODE_BOOKMARK: 174,
+    KEYCODE_CAPTIONS: 175,
+    KEYCODE_SETTINGS: 176,
+    KEYCODE_TV_POWER: 177,
+    KEYCODE_TV_INPUT: 178,
+    KEYCODE_STB_POWER: 179,
+    KEYCODE_STB_INPUT: 180,
+    KEYCODE_AVR_POWER: 181,
+    KEYCODE_AVR_INPUT: 182,
+    KEYCODE_PROG_RED: 183,
+    KEYCODE_PROG_GREEN: 184,
+    KEYCODE_PROG_YELLOW: 185,
+    KEYCODE_PROG_BLUE: 186,
+    KEYCODE_APP_SWITCH: 187,
+    KEYCODE_BUTTON_1: 188,
+    KEYCODE_BUTTON_2: 189,
+    KEYCODE_BUTTON_3: 190,
+    KEYCODE_BUTTON_4: 191,
+    KEYCODE_BUTTON_5: 192,
+    KEYCODE_BUTTON_6: 193,
+    KEYCODE_BUTTON_7: 194,
+    KEYCODE_BUTTON_8: 195,
+    KEYCODE_BUTTON_9: 196,
+    KEYCODE_BUTTON_10: 197,
+    KEYCODE_BUTTON_11: 198,
+    KEYCODE_BUTTON_12: 199,
+    KEYCODE_BUTTON_13: 200,
+    KEYCODE_BUTTON_14: 201,
+    KEYCODE_BUTTON_15: 202,
+    KEYCODE_BUTTON_16: 203,
+    KEYCODE_LANGUAGE_SWITCH: 204,
+    KEYCODE_MANNER_MODE: 205,
+    KEYCODE_3D_MODE: 206,
+    KEYCODE_CONTACTS: 207,
+    KEYCODE_CALENDAR: 208,
+    KEYCODE_MUSIC: 209,
+    KEYCODE_CALCULATOR: 210,
+    KEYCODE_ZENKAKU_HANKAKU: 211,
+    KEYCODE_EISU: 212,
+    KEYCODE_MUHENKAN: 213,
+    KEYCODE_HENKAN: 214,
+    KEYCODE_KATAKANA_HIRAGANA: 215,
+    KEYCODE_YEN: 216,
+    KEYCODE_RO: 217,
+    KEYCODE_KANA: 218,
+    KEYCODE_ASSIST: 219,
+    KEYCODE_BRIGHTNESS_DOWN: 220,
+    KEYCODE_BRIGHTNESS_UP: 221,
+    KEYCODE_MEDIA_AUDIO_TRACK: 222
+  };
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/linetransform.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/linetransform.js
new file mode 100644
index 0000000..05efce1
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/linetransform.js
@@ -0,0 +1,58 @@
+(function() {
+  var LineTransform, Stream,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Stream = require('stream');
+
+  LineTransform = (function(_super) {
+    __extends(LineTransform, _super);
+
+    function LineTransform(options) {
+      this.savedR = null;
+      LineTransform.__super__.constructor.call(this, options);
+    }
+
+    LineTransform.prototype._transform = function(chunk, encoding, done) {
+      var hi, last, lo;
+      lo = 0;
+      hi = 0;
+      if (this.savedR) {
+        if (chunk[0] !== 0x0a) {
+          this.push(this.savedR);
+        }
+        this.savedR = null;
+      }
+      last = chunk.length - 1;
+      while (hi <= last) {
+        if (chunk[hi] === 0x0d) {
+          if (hi === last) {
+            this.savedR = chunk.slice(last);
+            break;
+          } else if (chunk[hi + 1] === 0x0a) {
+            this.push(chunk.slice(lo, hi));
+            lo = hi + 1;
+          }
+        }
+        hi += 1;
+      }
+      if (hi !== lo) {
+        this.push(chunk.slice(lo, hi));
+      }
+      done();
+    };
+
+    LineTransform.prototype._flush = function(done) {
+      if (this.savedR) {
+        this.push(this.savedR);
+      }
+      return done();
+    };
+
+    return LineTransform;
+
+  })(Stream.Transform);
+
+  module.exports = LineTransform;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/parser.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/parser.js
new file mode 100644
index 0000000..65c50f6
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/parser.js
@@ -0,0 +1,247 @@
+(function() {
+  var Parser, Promise, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Promise = require('bluebird');
+
+  Protocol = require('./protocol');
+
+  Parser = (function() {
+    Parser.FailError = (function(_super) {
+      __extends(FailError, _super);
+
+      function FailError(message) {
+        Error.call(this);
+        this.name = 'FailError';
+        this.message = "Failure: '" + message + "'";
+        Error.captureStackTrace(this, Parser.FailError);
+      }
+
+      return FailError;
+
+    })(Error);
+
+    Parser.PrematureEOFError = (function(_super) {
+      __extends(PrematureEOFError, _super);
+
+      function PrematureEOFError(howManyMissing) {
+        Error.call(this);
+        this.name = 'PrematureEOFError';
+        this.message = "Premature end of stream, needed " + howManyMissing + " more bytes";
+        this.missingBytes = howManyMissing;
+        Error.captureStackTrace(this, Parser.PrematureEOFError);
+      }
+
+      return PrematureEOFError;
+
+    })(Error);
+
+    Parser.UnexpectedDataError = (function(_super) {
+      __extends(UnexpectedDataError, _super);
+
+      function UnexpectedDataError(unexpected, expected) {
+        Error.call(this);
+        this.name = 'UnexpectedDataError';
+        this.message = "Unexpected '" + unexpected + "', was expecting " + expected;
+        this.unexpected = unexpected;
+        this.expected = expected;
+        Error.captureStackTrace(this, Parser.UnexpectedDataError);
+      }
+
+      return UnexpectedDataError;
+
+    })(Error);
+
+    function Parser(stream) {
+      this.stream = stream;
+    }
+
+    Parser.prototype.raw = function() {
+      return this.stream;
+    };
+
+    Parser.prototype.readAll = function() {
+      var all, endListener, errorListener, resolver, tryRead;
+      all = new Buffer(0);
+      resolver = Promise.defer();
+      tryRead = (function(_this) {
+        return function() {
+          var chunk, _results;
+          _results = [];
+          while (chunk = _this.stream.read()) {
+            _results.push(all = Buffer.concat([all, chunk]));
+          }
+          return _results;
+        };
+      })(this);
+      this.stream.on('readable', tryRead);
+      this.stream.on('error', errorListener = function(err) {
+        return resolver.reject(err);
+      });
+      this.stream.on('end', endListener = function() {
+        return resolver.resolve(all);
+      });
+      tryRead();
+      return resolver.promise.cancellable()["finally"]((function(_this) {
+        return function() {
+          _this.stream.removeListener('readable', tryRead);
+          _this.stream.removeListener('error', errorListener);
+          return _this.stream.removeListener('end', endListener);
+        };
+      })(this));
+    };
+
+    Parser.prototype.readAscii = function(howMany) {
+      return this.readBytes(howMany).then(function(chunk) {
+        return chunk.toString('ascii');
+      });
+    };
+
+    Parser.prototype.readBytes = function(howMany) {
+      var endListener, errorListener, resolver, tryRead;
+      resolver = Promise.defer();
+      tryRead = (function(_this) {
+        return function() {
+          var chunk;
+          if (howMany) {
+            if (chunk = _this.stream.read(howMany)) {
+              howMany -= chunk.length;
+              if (howMany === 0) {
+                return resolver.resolve(chunk);
+              }
+            }
+          } else {
+            return resolver.resolve(new Buffer(0));
+          }
+        };
+      })(this);
+      endListener = function() {
+        return resolver.reject(new Parser.PrematureEOFError(howMany));
+      };
+      errorListener = function(err) {
+        return resolver.reject(err);
+      };
+      this.stream.on('readable', tryRead);
+      this.stream.on('error', errorListener);
+      this.stream.on('end', endListener);
+      tryRead();
+      return resolver.promise.cancellable()["finally"]((function(_this) {
+        return function() {
+          _this.stream.removeListener('readable', tryRead);
+          _this.stream.removeListener('error', errorListener);
+          return _this.stream.removeListener('end', endListener);
+        };
+      })(this));
+    };
+
+    Parser.prototype.readByteFlow = function(howMany) {
+      var endListener, errorListener, resolver, tryRead;
+      resolver = Promise.defer();
+      tryRead = (function(_this) {
+        return function() {
+          var chunk, _results;
+          if (howMany) {
+            _results = [];
+            while (chunk = _this.stream.read(howMany) || _this.stream.read()) {
+              howMany -= chunk.length;
+              if (howMany === 0) {
+                resolver.progress(chunk);
+                resolver.resolve();
+                break;
+              }
+              _results.push(resolver.progress(chunk));
+            }
+            return _results;
+          } else {
+            return resolver.resolve();
+          }
+        };
+      })(this);
+      endListener = function() {
+        return resolver.reject(new Parser.PrematureEOFError(howMany));
+      };
+      errorListener = function(err) {
+        return resolver.reject(err);
+      };
+      this.stream.on('readable', tryRead);
+      this.stream.on('error', errorListener);
+      this.stream.on('end', endListener);
+      tryRead();
+      return resolver.promise.cancellable()["finally"]((function(_this) {
+        return function() {
+          _this.stream.removeListener('readable', tryRead);
+          _this.stream.removeListener('error', errorListener);
+          return _this.stream.removeListener('end', endListener);
+        };
+      })(this));
+    };
+
+    Parser.prototype.readError = function() {
+      return this.readValue().then(function(value) {
+        return Promise.reject(new Parser.FailError(value.toString()));
+      });
+    };
+
+    Parser.prototype.readValue = function() {
+      return this.readAscii(4).then((function(_this) {
+        return function(value) {
+          var length;
+          length = Protocol.decodeLength(value);
+          return _this.readBytes(length);
+        };
+      })(this));
+    };
+
+    Parser.prototype.readUntil = function(code) {
+      var read, skipped;
+      skipped = new Buffer(0);
+      read = (function(_this) {
+        return function() {
+          return _this.readBytes(1).then(function(chunk) {
+            if (chunk[0] === code) {
+              return skipped;
+            } else {
+              skipped = Buffer.concat([skipped, chunk]);
+              return read();
+            }
+          });
+        };
+      })(this);
+      return read();
+    };
+
+    Parser.prototype.searchLine = function(re) {
+      return this.readLine().then((function(_this) {
+        return function(line) {
+          var match;
+          if (match = re.exec(line)) {
+            return match;
+          } else {
+            return _this.searchLine(re);
+          }
+        };
+      })(this));
+    };
+
+    Parser.prototype.readLine = function() {
+      return this.readUntil(0x0a).then(function(line) {
+        if (line[line.length - 1] === 0x0d) {
+          return line.slice(0, -1);
+        } else {
+          return line;
+        }
+      });
+    };
+
+    Parser.prototype.unexpected = function(data, expected) {
+      return Promise.reject(new Parser.UnexpectedDataError(data, expected));
+    };
+
+    return Parser;
+
+  })();
+
+  module.exports = Parser;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/proc/stat.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/proc/stat.js
new file mode 100644
index 0000000..76ac4e9
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/proc/stat.js
@@ -0,0 +1,140 @@
+(function() {
+  var EventEmitter, Parser, ProcStat, split,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  EventEmitter = require('events').EventEmitter;
+
+  split = require('split');
+
+  Parser = require('../parser');
+
+  ProcStat = (function(_super) {
+    var RE_COLSEP, RE_CPULINE;
+
+    __extends(ProcStat, _super);
+
+    RE_CPULINE = /^cpu[0-9]+ .*$/mg;
+
+    RE_COLSEP = /\ +/g;
+
+    function ProcStat(sync) {
+      this.sync = sync;
+      this.interval = 1000;
+      this.stats = this._emptyStats();
+      this._ignore = {};
+      this._timer = setInterval((function(_this) {
+        return function() {
+          return _this.update();
+        };
+      })(this), this.interval);
+      this.update();
+    }
+
+    ProcStat.prototype.end = function() {
+      clearInterval(this._timer);
+      this.sync.end();
+      return this.sync = null;
+    };
+
+    ProcStat.prototype.update = function() {
+      return new Parser(this.sync.pull('/proc/stat')).readAll().then((function(_this) {
+        return function(out) {
+          return _this._parse(out);
+        };
+      })(this))["catch"]((function(_this) {
+        return function(err) {
+          _this._error(err);
+        };
+      })(this));
+    };
+
+    ProcStat.prototype._parse = function(out) {
+      var cols, line, match, stats, total, type, val, _i, _len;
+      stats = this._emptyStats();
+      while (match = RE_CPULINE.exec(out)) {
+        line = match[0];
+        cols = line.split(RE_COLSEP);
+        type = cols.shift();
+        if (this._ignore[type] === line) {
+          continue;
+        }
+        total = 0;
+        for (_i = 0, _len = cols.length; _i < _len; _i++) {
+          val = cols[_i];
+          total += +val;
+        }
+        stats.cpus[type] = {
+          line: line,
+          user: +cols[0] || 0,
+          nice: +cols[1] || 0,
+          system: +cols[2] || 0,
+          idle: +cols[3] || 0,
+          iowait: +cols[4] || 0,
+          irq: +cols[5] || 0,
+          softirq: +cols[6] || 0,
+          steal: +cols[7] || 0,
+          guest: +cols[8] || 0,
+          guestnice: +cols[9] || 0,
+          total: total
+        };
+      }
+      return this._set(stats);
+    };
+
+    ProcStat.prototype._set = function(stats) {
+      var cur, found, id, loads, m, old, ticks, _ref;
+      loads = {};
+      found = false;
+      _ref = stats.cpus;
+      for (id in _ref) {
+        cur = _ref[id];
+        old = this.stats.cpus[id];
+        if (!old) {
+          continue;
+        }
+        ticks = cur.total - old.total;
+        if (ticks > 0) {
+          found = true;
+          m = 100 / ticks;
+          loads[id] = {
+            user: Math.floor(m * (cur.user - old.user)),
+            nice: Math.floor(m * (cur.nice - old.nice)),
+            system: Math.floor(m * (cur.system - old.system)),
+            idle: Math.floor(m * (cur.idle - old.idle)),
+            iowait: Math.floor(m * (cur.iowait - old.iowait)),
+            irq: Math.floor(m * (cur.irq - old.irq)),
+            softirq: Math.floor(m * (cur.softirq - old.softirq)),
+            steal: Math.floor(m * (cur.steal - old.steal)),
+            guest: Math.floor(m * (cur.guest - old.guest)),
+            guestnice: Math.floor(m * (cur.guestnice - old.guestnice)),
+            total: 100
+          };
+        } else {
+          this._ignore[id] = cur.line;
+          delete stats.cpus[id];
+        }
+      }
+      if (found) {
+        this.emit('load', loads);
+      }
+      return this.stats = stats;
+    };
+
+    ProcStat.prototype._error = function(err) {
+      return this.emit('error', err);
+    };
+
+    ProcStat.prototype._emptyStats = function() {
+      return {
+        cpus: {}
+      };
+    };
+
+    return ProcStat;
+
+  })(EventEmitter);
+
+  module.exports = ProcStat;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/protocol.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/protocol.js
new file mode 100644
index 0000000..72ac5f3
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/protocol.js
@@ -0,0 +1,48 @@
+(function() {
+  var Protocol;
+
+  Protocol = (function() {
+    function Protocol() {}
+
+    Protocol.OKAY = 'OKAY';
+
+    Protocol.FAIL = 'FAIL';
+
+    Protocol.STAT = 'STAT';
+
+    Protocol.LIST = 'LIST';
+
+    Protocol.DENT = 'DENT';
+
+    Protocol.RECV = 'RECV';
+
+    Protocol.DATA = 'DATA';
+
+    Protocol.DONE = 'DONE';
+
+    Protocol.SEND = 'SEND';
+
+    Protocol.QUIT = 'QUIT';
+
+    Protocol.decodeLength = function(length) {
+      return parseInt(length, 16);
+    };
+
+    Protocol.encodeLength = function(length) {
+      return ('0000' + length.toString(16)).slice(-4).toUpperCase();
+    };
+
+    Protocol.encodeData = function(data) {
+      if (!Buffer.isBuffer(data)) {
+        data = new Buffer(data);
+      }
+      return Buffer.concat([new Buffer(Protocol.encodeLength(data.length)), data]);
+    };
+
+    return Protocol;
+
+  })();
+
+  module.exports = Protocol;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/sync.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/sync.js
new file mode 100644
index 0000000..8c157a9
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/sync.js
@@ -0,0 +1,337 @@
+(function() {
+  var Entry, EventEmitter, Fs, Path, Promise, Protocol, PullTransfer, PushTransfer, Stats, Sync, debug,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Fs = require('fs');
+
+  Path = require('path');
+
+  Promise = require('bluebird');
+
+  EventEmitter = require('events').EventEmitter;
+
+  debug = require('debug')('adb:sync');
+
+  Protocol = require('./protocol');
+
+  Stats = require('./sync/stats');
+
+  Entry = require('./sync/entry');
+
+  PushTransfer = require('./sync/pushtransfer');
+
+  PullTransfer = require('./sync/pulltransfer');
+
+  Sync = (function(_super) {
+    var DATA_MAX_LENGTH, DEFAULT_CHMOD, TEMP_PATH;
+
+    __extends(Sync, _super);
+
+    TEMP_PATH = '/data/local/tmp';
+
+    DEFAULT_CHMOD = 0x1a4;
+
+    DATA_MAX_LENGTH = 65536;
+
+    Sync.temp = function(path) {
+      return "" + TEMP_PATH + "/" + (Path.basename(path));
+    };
+
+    function Sync(connection) {
+      this.connection = connection;
+      this.parser = this.connection.parser;
+    }
+
+    Sync.prototype.stat = function(path, callback) {
+      this._sendCommandWithArg(Protocol.STAT, path);
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.STAT:
+              return _this.parser.readBytes(12).then(function(stat) {
+                var mode, mtime, size;
+                mode = stat.readUInt32LE(0);
+                size = stat.readUInt32LE(4);
+                mtime = stat.readUInt32LE(8);
+                if (mode === 0) {
+                  return _this._enoent(path);
+                } else {
+                  return new Stats(mode, size, mtime);
+                }
+              });
+            case Protocol.FAIL:
+              return _this._readError();
+            default:
+              return _this.parser.unexpected(reply, 'STAT or FAIL');
+          }
+        };
+      })(this)).nodeify(callback);
+    };
+
+    Sync.prototype.readdir = function(path, callback) {
+      var files, readNext;
+      files = [];
+      readNext = (function(_this) {
+        return function() {
+          return _this.parser.readAscii(4).then(function(reply) {
+            switch (reply) {
+              case Protocol.DENT:
+                return _this.parser.readBytes(16).then(function(stat) {
+                  var mode, mtime, namelen, size;
+                  mode = stat.readUInt32LE(0);
+                  size = stat.readUInt32LE(4);
+                  mtime = stat.readUInt32LE(8);
+                  namelen = stat.readUInt32LE(12);
+                  return _this.parser.readBytes(namelen).then(function(name) {
+                    name = name.toString();
+                    if (!(name === '.' || name === '..')) {
+                      files.push(new Entry(name, mode, size, mtime));
+                    }
+                    return readNext();
+                  });
+                });
+              case Protocol.DONE:
+                return _this.parser.readBytes(16).then(function(zero) {
+                  return files;
+                });
+              case Protocol.FAIL:
+                return _this._readError();
+              default:
+                return _this.parser.unexpected(reply, 'DENT, DONE or FAIL');
+            }
+          });
+        };
+      })(this);
+      this._sendCommandWithArg(Protocol.LIST, path);
+      return readNext().nodeify(callback);
+    };
+
+    Sync.prototype.push = function(contents, path, mode) {
+      if (typeof contents === 'string') {
+        return this.pushFile(contents, path, mode);
+      } else {
+        return this.pushStream(contents, path, mode);
+      }
+    };
+
+    Sync.prototype.pushFile = function(file, path, mode) {
+      if (mode == null) {
+        mode = DEFAULT_CHMOD;
+      }
+      mode || (mode = DEFAULT_CHMOD);
+      return this.pushStream(Fs.createReadStream(file), path, mode);
+    };
+
+    Sync.prototype.pushStream = function(stream, path, mode) {
+      if (mode == null) {
+        mode = DEFAULT_CHMOD;
+      }
+      mode |= Stats.S_IFREG;
+      this._sendCommandWithArg(Protocol.SEND, "" + path + "," + mode);
+      return this._writeData(stream, Math.floor(Date.now() / 1000));
+    };
+
+    Sync.prototype.pull = function(path) {
+      this._sendCommandWithArg(Protocol.RECV, "" + path);
+      return this._readData();
+    };
+
+    Sync.prototype.end = function() {
+      this.connection.end();
+      return this;
+    };
+
+    Sync.prototype.tempFile = function(path) {
+      return Sync.temp(path);
+    };
+
+    Sync.prototype._writeData = function(stream, timeStamp) {
+      var readReply, reader, transfer, writeData, writer;
+      transfer = new PushTransfer;
+      writeData = (function(_this) {
+        return function() {
+          var endListener, errorListener, readableListener, resolver, track, waitForDrain, writeNext, writer;
+          resolver = Promise.defer();
+          writer = Promise.resolve().cancellable();
+          stream.on('end', endListener = function() {
+            return writer.then(function() {
+              _this._sendCommandWithLength(Protocol.DONE, timeStamp);
+              return resolver.resolve();
+            });
+          });
+          waitForDrain = function() {
+            var drainListener;
+            resolver = Promise.defer();
+            _this.connection.on('drain', drainListener = function() {
+              return resolver.resolve();
+            });
+            return resolver.promise["finally"](function() {
+              return _this.connection.removeListener('drain', drainListener);
+            });
+          };
+          track = function() {
+            return transfer.pop();
+          };
+          writeNext = function() {
+            var chunk;
+            if (chunk = stream.read(DATA_MAX_LENGTH) || stream.read()) {
+              _this._sendCommandWithLength(Protocol.DATA, chunk.length);
+              transfer.push(chunk.length);
+              if (_this.connection.write(chunk, track)) {
+                return writeNext();
+              } else {
+                return waitForDrain().then(writeNext);
+              }
+            } else {
+              return Promise.resolve();
+            }
+          };
+          stream.on('readable', readableListener = function() {
+            return writer.then(writeNext);
+          });
+          stream.on('error', errorListener = function(err) {
+            return resolver.reject(err);
+          });
+          return resolver.promise["finally"](function() {
+            stream.removeListener('end', endListener);
+            stream.removeListener('readable', readableListener);
+            stream.removeListener('error', errorListener);
+            return writer.cancel();
+          });
+        };
+      })(this);
+      readReply = (function(_this) {
+        return function() {
+          return _this.parser.readAscii(4).then(function(reply) {
+            switch (reply) {
+              case Protocol.OKAY:
+                return _this.parser.readBytes(4).then(function(zero) {
+                  return true;
+                });
+              case Protocol.FAIL:
+                return _this._readError();
+              default:
+                return _this.parser.unexpected(reply, 'OKAY or FAIL');
+            }
+          });
+        };
+      })(this);
+      writer = writeData().cancellable()["catch"](Promise.CancellationError, (function(_this) {
+        return function(err) {
+          return _this.connection.end();
+        };
+      })(this))["catch"](function(err) {
+        transfer.emit('error', err);
+        return reader.cancel();
+      });
+      reader = readReply().cancellable()["catch"](Promise.CancellationError, function(err) {
+        return true;
+      })["catch"](function(err) {
+        transfer.emit('error', err);
+        return writer.cancel();
+      })["finally"](function() {
+        return transfer.end();
+      });
+      transfer.on('cancel', function() {
+        writer.cancel();
+        return reader.cancel();
+      });
+      return transfer;
+    };
+
+    Sync.prototype._readData = function() {
+      var cancelListener, readNext, reader, transfer;
+      transfer = new PullTransfer;
+      readNext = (function(_this) {
+        return function() {
+          return _this.parser.readAscii(4).cancellable().then(function(reply) {
+            switch (reply) {
+              case Protocol.DATA:
+                return _this.parser.readBytes(4).then(function(lengthData) {
+                  var length;
+                  length = lengthData.readUInt32LE(0);
+                  return _this.parser.readByteFlow(length).progressed(function(chunk) {
+                    return transfer.write(chunk);
+                  }).then(function() {
+                    return readNext();
+                  });
+                });
+              case Protocol.DONE:
+                return _this.parser.readBytes(4).then(function(zero) {
+                  return true;
+                });
+              case Protocol.FAIL:
+                return _this._readError();
+              default:
+                return _this.parser.unexpected(reply, 'DATA, DONE or FAIL');
+            }
+          });
+        };
+      })(this);
+      reader = readNext()["catch"](Promise.CancellationError, (function(_this) {
+        return function(err) {
+          return _this.connection.end();
+        };
+      })(this))["catch"](function(err) {
+        return transfer.emit('error', err);
+      })["finally"](function() {
+        transfer.removeListener('cancel', cancelListener);
+        return transfer.end();
+      });
+      transfer.on('cancel', cancelListener = function() {
+        return reader.cancel();
+      });
+      return transfer;
+    };
+
+    Sync.prototype._readError = function() {
+      return this.parser.readBytes(4).then((function(_this) {
+        return function(zero) {
+          return _this.parser.readAll().then(function(buf) {
+            return Promise.reject(new Parser.FailError(buf.toString()));
+          });
+        };
+      })(this));
+    };
+
+    Sync.prototype._sendCommandWithLength = function(cmd, length) {
+      var payload;
+      if (cmd !== Protocol.DATA) {
+        debug(cmd);
+      }
+      payload = new Buffer(cmd.length + 4);
+      payload.write(cmd, 0, cmd.length);
+      payload.writeUInt32LE(length, cmd.length);
+      return this.connection.write(payload);
+    };
+
+    Sync.prototype._sendCommandWithArg = function(cmd, arg) {
+      var payload, pos;
+      debug("" + cmd + " " + arg);
+      payload = new Buffer(cmd.length + 4 + arg.length);
+      pos = 0;
+      payload.write(cmd, pos, cmd.length);
+      pos += cmd.length;
+      payload.writeUInt32LE(arg.length, pos);
+      pos += 4;
+      payload.write(arg, pos);
+      return this.connection.write(payload);
+    };
+
+    Sync.prototype._enoent = function(path) {
+      var err;
+      err = new Error("ENOENT, no such file or directory '" + path + "'");
+      err.errno = 34;
+      err.code = 'ENOENT';
+      err.path = path;
+      return Promise.reject(err);
+    };
+
+    return Sync;
+
+  })(EventEmitter);
+
+  module.exports = Sync;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/sync/entry.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/sync/entry.js
new file mode 100644
index 0000000..1025e82
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/sync/entry.js
@@ -0,0 +1,26 @@
+(function() {
+  var Entry, Stats,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Stats = require('./stats');
+
+  Entry = (function(_super) {
+    __extends(Entry, _super);
+
+    function Entry(name, mode, size, mtime) {
+      this.name = name;
+      Entry.__super__.constructor.call(this, mode, size, mtime);
+    }
+
+    Entry.prototype.toString = function() {
+      return this.name;
+    };
+
+    return Entry;
+
+  })(Stats);
+
+  module.exports = Entry;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/sync/pulltransfer.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/sync/pulltransfer.js
new file mode 100644
index 0000000..1032956
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/sync/pulltransfer.js
@@ -0,0 +1,34 @@
+(function() {
+  var PullTransfer, Stream,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Stream = require('stream');
+
+  PullTransfer = (function(_super) {
+    __extends(PullTransfer, _super);
+
+    function PullTransfer() {
+      this.stats = {
+        bytesTransferred: 0
+      };
+      PullTransfer.__super__.constructor.call(this);
+    }
+
+    PullTransfer.prototype.cancel = function() {
+      return this.emit('cancel');
+    };
+
+    PullTransfer.prototype.write = function(chunk, encoding, callback) {
+      this.stats.bytesTransferred += chunk.length;
+      this.emit('progress', this.stats);
+      return PullTransfer.__super__.write.call(this, chunk, encoding, callback);
+    };
+
+    return PullTransfer;
+
+  })(Stream.PassThrough);
+
+  module.exports = PullTransfer;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/sync/pushtransfer.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/sync/pushtransfer.js
new file mode 100644
index 0000000..a966c3c
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/sync/pushtransfer.js
@@ -0,0 +1,43 @@
+(function() {
+  var EventEmitter, PushTransfer,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  EventEmitter = require('events').EventEmitter;
+
+  PushTransfer = (function(_super) {
+    __extends(PushTransfer, _super);
+
+    function PushTransfer() {
+      this._stack = [];
+      this.stats = {
+        bytesTransferred: 0
+      };
+    }
+
+    PushTransfer.prototype.cancel = function() {
+      return this.emit('cancel');
+    };
+
+    PushTransfer.prototype.push = function(byteCount) {
+      return this._stack.push(byteCount);
+    };
+
+    PushTransfer.prototype.pop = function() {
+      var byteCount;
+      byteCount = this._stack.pop();
+      this.stats.bytesTransferred += byteCount;
+      return this.emit('progress', this.stats);
+    };
+
+    PushTransfer.prototype.end = function() {
+      return this.emit('end');
+    };
+
+    return PushTransfer;
+
+  })(EventEmitter);
+
+  module.exports = PushTransfer;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/sync/stats.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/sync/stats.js
new file mode 100644
index 0000000..39d2c9b
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/sync/stats.js
@@ -0,0 +1,57 @@
+(function() {
+  var Fs, Stats,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Fs = require('fs');
+
+  Stats = (function(_super) {
+    __extends(Stats, _super);
+
+    Stats.S_IFMT = 0xf000;
+
+    Stats.S_IFSOCK = 0xc000;
+
+    Stats.S_IFLNK = 0xa000;
+
+    Stats.S_IFREG = 0x8000;
+
+    Stats.S_IFBLK = 0x6000;
+
+    Stats.S_IFDIR = 0x4000;
+
+    Stats.S_IFCHR = 0x2000;
+
+    Stats.S_IFIFO = 0x1000;
+
+    Stats.S_ISUID = 0x800;
+
+    Stats.S_ISGID = 0x400;
+
+    Stats.S_ISVTX = 0x200;
+
+    Stats.S_IRWXU = 0x1c0;
+
+    Stats.S_IRUSR = 0x100;
+
+    Stats.S_IWUSR = 0x80;
+
+    Stats.S_IXUSR = 0x40;
+
+    Stats.S_IRWXG = 0x38;
+
+    Stats.S_IRGRP = 0x20;
+
+    function Stats(mode, size, mtime) {
+      this.mode = mode;
+      this.size = size;
+      this.mtime = new Date(mtime * 1000);
+    }
+
+    return Stats;
+
+  })(Fs.Stats);
+
+  module.exports = Stats;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/tcpusb/rollingcounter.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/tcpusb/rollingcounter.js
new file mode 100644
index 0000000..f339afa
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/tcpusb/rollingcounter.js
@@ -0,0 +1,24 @@
+(function() {
+  var RollingCounter;
+
+  RollingCounter = (function() {
+    function RollingCounter(max, min) {
+      this.max = max;
+      this.min = min != null ? min : 1;
+      this.now = this.min;
+    }
+
+    RollingCounter.prototype.next = function() {
+      if (!(this.now < this.max)) {
+        this.now = this.min;
+      }
+      return ++this.now;
+    };
+
+    return RollingCounter;
+
+  })();
+
+  module.exports = RollingCounter;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/tcpusb/server.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/tcpusb/server.js
new file mode 100644
index 0000000..0d13739
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/tcpusb/server.js
@@ -0,0 +1,72 @@
+(function() {
+  var EventEmitter, Net, Server, Socket,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Net = require('net');
+
+  EventEmitter = require('events').EventEmitter;
+
+  Socket = require('./socket');
+
+  Server = (function(_super) {
+    __extends(Server, _super);
+
+    function Server(client, serial, options) {
+      this.client = client;
+      this.serial = serial;
+      this.options = options;
+      this.connections = [];
+      this.server = Net.createServer();
+      this.server.on('error', (function(_this) {
+        return function(err) {
+          return _this.emit('error', err);
+        };
+      })(this));
+      this.server.on('listening', (function(_this) {
+        return function() {
+          return _this.emit('listening');
+        };
+      })(this));
+      this.server.on('close', (function(_this) {
+        return function() {
+          return _this.emit('close');
+        };
+      })(this));
+      this.server.on('connection', (function(_this) {
+        return function(conn) {
+          var socket;
+          socket = new Socket(_this.client, _this.serial, conn, _this.options);
+          _this.connections.push(socket);
+          return _this.emit('connection', socket);
+        };
+      })(this));
+    }
+
+    Server.prototype.listen = function() {
+      this.server.listen.apply(this.server, arguments);
+      return this;
+    };
+
+    Server.prototype.close = function() {
+      this.server.close();
+      return this;
+    };
+
+    Server.prototype.end = function() {
+      var conn, _i, _len, _ref;
+      _ref = this.connections;
+      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+        conn = _ref[_i];
+        conn.end();
+      }
+      return this;
+    };
+
+    return Server;
+
+  })(EventEmitter);
+
+  module.exports = Server;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/tcpusb/servicemap.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/tcpusb/servicemap.js
new file mode 100644
index 0000000..d4b3ccc
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/tcpusb/servicemap.js
@@ -0,0 +1,43 @@
+(function() {
+  var ServiceMap;
+
+  ServiceMap = (function() {
+    function ServiceMap() {
+      this.remotes = Object.create(null);
+    }
+
+    ServiceMap.prototype.end = function() {
+      var remote, remoteId, _ref;
+      _ref = this.remotes;
+      for (remoteId in _ref) {
+        remote = _ref[remoteId];
+        remote.end();
+      }
+      this.remotes = Object.create(null);
+    };
+
+    ServiceMap.prototype.put = function(remoteId, socket) {
+      return this.remotes[remoteId] = socket;
+    };
+
+    ServiceMap.prototype.get = function(remoteId) {
+      return this.remotes[remoteId] || null;
+    };
+
+    ServiceMap.prototype.remove = function(remoteId) {
+      var remote;
+      if (remote = this.remotes[remoteId]) {
+        delete this.remotes[remoteId];
+        return remote;
+      } else {
+        return null;
+      }
+    };
+
+    return ServiceMap;
+
+  })();
+
+  module.exports = ServiceMap;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/tcpusb/socket.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/tcpusb/socket.js
new file mode 100644
index 0000000..67fa54f
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/tcpusb/socket.js
@@ -0,0 +1,391 @@
+(function() {
+  var Auth, EventEmitter, Forge, Parser, Promise, Protocol, RollingCounter, ServiceMap, Socket, crypto, debug,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  crypto = require('crypto');
+
+  EventEmitter = require('events').EventEmitter;
+
+  Promise = require('bluebird');
+
+  Forge = require('node-forge');
+
+  debug = require('debug')('adb:tcpusb:socket');
+
+  Parser = require('../parser');
+
+  Protocol = require('../protocol');
+
+  Auth = require('../auth');
+
+  ServiceMap = require('./servicemap');
+
+  RollingCounter = require('./rollingcounter');
+
+  Socket = (function(_super) {
+    var AUTH_RSAPUBLICKEY, AUTH_SIGNATURE, AUTH_TOKEN, A_AUTH, A_CLSE, A_CNXN, A_OKAY, A_OPEN, A_SYNC, A_WRTE, TOKEN_LENGTH, UINT32_MAX;
+
+    __extends(Socket, _super);
+
+    A_SYNC = 0x434e5953;
+
+    A_CNXN = 0x4e584e43;
+
+    A_OPEN = 0x4e45504f;
+
+    A_OKAY = 0x59414b4f;
+
+    A_CLSE = 0x45534c43;
+
+    A_WRTE = 0x45545257;
+
+    A_AUTH = 0x48545541;
+
+    UINT32_MAX = 0xFFFFFFFF;
+
+    AUTH_TOKEN = 1;
+
+    AUTH_SIGNATURE = 2;
+
+    AUTH_RSAPUBLICKEY = 3;
+
+    TOKEN_LENGTH = 20;
+
+    function Socket(client, serial, socket, options) {
+      var _base;
+      this.client = client;
+      this.serial = serial;
+      this.socket = socket;
+      this.options = options != null ? options : {};
+      (_base = this.options).auth || (_base.auth = Promise.resolve(true));
+      this.ended = false;
+      this.parser = new Parser(this.socket);
+      this.version = 1;
+      this.maxPayload = 4096;
+      this.authorized = false;
+      this.syncToken = new RollingCounter(UINT32_MAX);
+      this.remoteId = new RollingCounter(UINT32_MAX);
+      this.services = new ServiceMap;
+      this.remoteAddress = this.socket.remoteAddress;
+      this.token = null;
+      this.signature = null;
+      this._inputLoop();
+    }
+
+    Socket.prototype.end = function() {
+      if (!this.ended) {
+        this.socket.end();
+        this.services.end();
+        this.emit('end');
+        this.ended = true;
+      }
+      return this;
+    };
+
+    Socket.prototype._inputLoop = function() {
+      return this._readMessage().then((function(_this) {
+        return function(message) {
+          return _this._route(message);
+        };
+      })(this)).then((function(_this) {
+        return function() {
+          return setImmediate(_this._inputLoop.bind(_this));
+        };
+      })(this))["catch"](Parser.PrematureEOFError, (function(_this) {
+        return function() {
+          return _this.end();
+        };
+      })(this))["catch"]((function(_this) {
+        return function(err) {
+          debug("Error: " + err.message);
+          _this.emit('error', err);
+          return _this.end();
+        };
+      })(this));
+    };
+
+    Socket.prototype._readMessage = function() {
+      return this.parser.readBytes(24).then(function(header) {
+        return {
+          command: header.readUInt32LE(0),
+          arg0: header.readUInt32LE(4),
+          arg1: header.readUInt32LE(8),
+          length: header.readUInt32LE(12),
+          check: header.readUInt32LE(16),
+          magic: header.readUInt32LE(20)
+        };
+      }).then((function(_this) {
+        return function(message) {
+          return _this.parser.readBytes(message.length).then(function(data) {
+            message.data = data;
+            return message;
+          });
+        };
+      })(this)).then((function(_this) {
+        return function(message) {
+          return _this._validateMessage(message);
+        };
+      })(this));
+    };
+
+    Socket.prototype._route = function(message) {
+      if (this.ended) {
+        return;
+      }
+      this.emit('userActivity', message);
+      switch (message.command) {
+        case A_SYNC:
+          return this._handleSyncMessage(message);
+        case A_CNXN:
+          return this._handleConnectionMessage(message);
+        case A_OPEN:
+          return this._handleOpenMessage(message);
+        case A_OKAY:
+          return this._handleOkayMessage(message);
+        case A_CLSE:
+          return this._handleCloseMessage(message);
+        case A_WRTE:
+          return this._handleWriteMessage(message);
+        case A_AUTH:
+          return this._handleAuthMessage(message);
+        default:
+          return this.emit('error', new Error("Unknown command " + message.command));
+      }
+    };
+
+    Socket.prototype._handleSyncMessage = function(message) {
+      return this._writeHeader(A_SYNC, 1, this.syncToken.next(), 0);
+    };
+
+    Socket.prototype._handleConnectionMessage = function(message) {
+      debug('A_CNXN', message);
+      return this._createToken().then((function(_this) {
+        return function(token) {
+          _this.token = token;
+          return _this._writeMessage(A_AUTH, AUTH_TOKEN, 0, _this.token);
+        };
+      })(this));
+    };
+
+    Socket.prototype._handleAuthMessage = function(message) {
+      debug('A_AUTH', message);
+      switch (message.arg0) {
+        case AUTH_SIGNATURE:
+          if (!this.signature) {
+            this.signature = message.data;
+          }
+          return this._writeMessage(A_AUTH, AUTH_TOKEN, 0, this.token);
+        case AUTH_RSAPUBLICKEY:
+          return Auth.parsePublicKey(message.data.slice(0, -1)).then((function(_this) {
+            return function(key) {
+              var digest, sig;
+              digest = _this.token.toString('binary');
+              sig = _this.signature.toString('binary');
+              if (key.verify(digest, sig)) {
+                return _this.options.auth(key).then(function() {
+                  return _this._deviceId().then(function(id) {
+                    _this.authorized = true;
+                    return _this._writeMessage(A_CNXN, _this.version, _this.maxPayload, "device::" + id);
+                  });
+                })["catch"](function(err) {
+                  return _this.end();
+                });
+              } else {
+                return _this.end();
+              }
+            };
+          })(this));
+        default:
+          return this.end();
+      }
+    };
+
+    Socket.prototype._handleOpenMessage = function(message) {
+      var command, localId, remoteId, service;
+      if (!this.authorized) {
+        return;
+      }
+      debug('A_OPEN', message);
+      localId = message.arg0;
+      remoteId = this.remoteId.next();
+      service = message.data.slice(0, -1);
+      command = service.toString().split(':', 1)[0];
+      return this.client.transport(this.serial).then((function(_this) {
+        return function(transport) {
+          var parser, pump;
+          if (_this.ended) {
+            return;
+          }
+          debug("Calling " + service);
+          _this.services.put(remoteId, transport);
+          transport.write(Protocol.encodeData(service));
+          parser = transport.parser;
+          pump = function() {
+            return new Promise(function(resolve, reject) {
+              var maybeRead, out;
+              out = parser.raw();
+              maybeRead = function() {
+                var chunk, _results;
+                _results = [];
+                while (chunk = _this._readChunk(out)) {
+                  _results.push(_this._writeMessage(A_WRTE, remoteId, localId, chunk));
+                }
+                return _results;
+              };
+              out.on('readable', maybeRead);
+              return out.on('end', resolve);
+            });
+          };
+          parser.readAscii(4).then(function(reply) {
+            switch (reply) {
+              case Protocol.OKAY:
+                _this._writeHeader(A_OKAY, remoteId, localId);
+                return pump();
+              case Protocol.FAIL:
+                return parser.readError();
+              default:
+                return parser.unexpected(reply, 'OKAY or FAIL');
+            }
+          })["catch"](Parser.PrematureEOFError, function() {
+            return true;
+          })["finally"](function() {
+            return _this._close(remoteId, localId);
+          })["catch"](Parser.FailError, function(err) {
+            debug("Unable to open transport: " + err);
+            return _this.end();
+          });
+        };
+      })(this));
+    };
+
+    Socket.prototype._handleOkayMessage = function(message) {
+      var localId, remoteId;
+      if (!this.authorized) {
+        return;
+      }
+      debug('A_OKAY', message);
+      localId = message.arg0;
+      return remoteId = message.arg1;
+    };
+
+    Socket.prototype._handleCloseMessage = function(message) {
+      var localId, remoteId;
+      if (!this.authorized) {
+        return;
+      }
+      debug('A_CLSE', message);
+      localId = message.arg0;
+      remoteId = message.arg1;
+      return this._close(remoteId, localId);
+    };
+
+    Socket.prototype._handleWriteMessage = function(message) {
+      var localId, remote, remoteId;
+      if (!this.authorized) {
+        return;
+      }
+      debug('A_WRTE', message);
+      localId = message.arg0;
+      remoteId = message.arg1;
+      if (remote = this.services.get(remoteId)) {
+        remote.write(message.data);
+        this._writeHeader(A_OKAY, remoteId, localId);
+      } else {
+        debug("A_WRTE to unknown socket pair " + localId + "/" + remoteId);
+      }
+      return true;
+    };
+
+    Socket.prototype._close = function(remoteId, localId) {
+      var remote;
+      if (remote = this.services.remove(remoteId)) {
+        remote.end();
+        return this._writeHeader(A_CLSE, remoteId, localId);
+      }
+    };
+
+    Socket.prototype._writeHeader = function(command, arg0, arg1, length, checksum) {
+      var header;
+      if (this.ended) {
+        return;
+      }
+      header = new Buffer(24);
+      header.writeUInt32LE(command, 0);
+      header.writeUInt32LE(arg0 || 0, 4);
+      header.writeUInt32LE(arg1 || 0, 8);
+      header.writeUInt32LE(length || 0, 12);
+      header.writeUInt32LE(checksum || 0, 16);
+      header.writeUInt32LE(this._magic(command), 20);
+      return this.socket.write(header);
+    };
+
+    Socket.prototype._writeMessage = function(command, arg0, arg1, data) {
+      if (this.ended) {
+        return;
+      }
+      if (!Buffer.isBuffer(data)) {
+        data = new Buffer(data);
+      }
+      this._writeHeader(command, arg0, arg1, data.length, this._checksum(data));
+      return this.socket.write(data);
+    };
+
+    Socket.prototype._validateMessage = function(message) {
+      if (message.magic !== this._magic(message.command)) {
+        throw new Error("Command failed magic check");
+      }
+      if (message.check !== this._checksum(message.data)) {
+        throw new Error("Message checksum doesn't match received message");
+      }
+      return message;
+    };
+
+    Socket.prototype._readChunk = function(stream) {
+      return stream.read(this.maxPayload) || stream.read();
+    };
+
+    Socket.prototype._checksum = function(data) {
+      var char, sum, _i, _len;
+      if (!Buffer.isBuffer(data)) {
+        throw new Error("Unable to calculate checksum of non-Buffer");
+      }
+      sum = 0;
+      for (_i = 0, _len = data.length; _i < _len; _i++) {
+        char = data[_i];
+        sum += char;
+      }
+      return sum;
+    };
+
+    Socket.prototype._magic = function(command) {
+      return (command ^ 0xffffffff) >>> 0;
+    };
+
+    Socket.prototype._createToken = function() {
+      return Promise.promisify(crypto.randomBytes)(TOKEN_LENGTH);
+    };
+
+    Socket.prototype._deviceId = function() {
+      return this.client.getProperties(this.serial).then(function(properties) {
+        var prop;
+        return ((function() {
+          var _i, _len, _ref, _results;
+          _ref = ['ro.product.name', 'ro.product.model', 'ro.product.device'];
+          _results = [];
+          for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+            prop = _ref[_i];
+            _results.push("" + prop + "=" + properties[prop] + ";");
+          }
+          return _results;
+        })()).join('');
+      });
+    };
+
+    return Socket;
+
+  })(EventEmitter);
+
+  module.exports = Socket;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/tracker.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/tracker.js
new file mode 100644
index 0000000..ca96230
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/tracker.js
@@ -0,0 +1,92 @@
+(function() {
+  var EventEmitter, Parser, Promise, Tracker,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  EventEmitter = require('events').EventEmitter;
+
+  Promise = require('bluebird');
+
+  Parser = require('./parser');
+
+  Tracker = (function(_super) {
+    __extends(Tracker, _super);
+
+    function Tracker(command) {
+      this.command = command;
+      this.deviceList = [];
+      this.deviceMap = {};
+      this.reader = this.read()["catch"](Parser.PrematureEOFError, (function(_this) {
+        return function(err) {
+          return _this.emit('end');
+        };
+      })(this))["catch"](Promise.CancellationError, (function(_this) {
+        return function(err) {
+          _this.command.connection.end();
+          return _this.emit('end');
+        };
+      })(this))["catch"]((function(_this) {
+        return function(err) {
+          _this.emit('error', err);
+          return _this.emit('end');
+        };
+      })(this));
+    }
+
+    Tracker.prototype.read = function() {
+      return this.command._readDevices().cancellable().then((function(_this) {
+        return function(list) {
+          _this.update(list);
+          return _this.read();
+        };
+      })(this));
+    };
+
+    Tracker.prototype.update = function(newList) {
+      var changeSet, device, newMap, oldDevice, _i, _j, _len, _len1, _ref;
+      changeSet = {
+        removed: [],
+        changed: [],
+        added: []
+      };
+      newMap = {};
+      for (_i = 0, _len = newList.length; _i < _len; _i++) {
+        device = newList[_i];
+        oldDevice = this.deviceMap[device.id];
+        if (oldDevice) {
+          if (oldDevice.type !== device.type) {
+            changeSet.changed.push(device);
+            this.emit('change', device, oldDevice);
+          }
+        } else {
+          changeSet.added.push(device);
+          this.emit('add', device);
+        }
+        newMap[device.id] = device;
+      }
+      _ref = this.deviceList;
+      for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) {
+        device = _ref[_j];
+        if (!newMap[device.id]) {
+          changeSet.removed.push(device);
+          this.emit('remove', device);
+        }
+      }
+      this.emit('changeSet', changeSet);
+      this.deviceList = newList;
+      this.deviceMap = newMap;
+      return this;
+    };
+
+    Tracker.prototype.end = function() {
+      this.reader.cancel();
+      return this;
+    };
+
+    return Tracker;
+
+  })(EventEmitter);
+
+  module.exports = Tracker;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/util.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/util.js
new file mode 100644
index 0000000..8207028
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/adb/util.js
@@ -0,0 +1,16 @@
+(function() {
+  var Auth, Parser;
+
+  Parser = require('./parser');
+
+  Auth = require('./auth');
+
+  module.exports.readAll = function(stream, callback) {
+    return new Parser(stream).readAll(stream).nodeify(callback);
+  };
+
+  module.exports.parsePublicKey = function(keyString, callback) {
+    return Auth.parsePublicKey(keyString).nodeify(callback);
+  };
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/cli.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/cli.js
new file mode 100644
index 0000000..11dc9cf
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/lib/cli.js
@@ -0,0 +1,42 @@
+(function() {
+  var Auth, Promise, forge, fs, pkg, program;
+
+  fs = require('fs');
+
+  program = require('commander');
+
+  Promise = require('bluebird');
+
+  forge = require('node-forge');
+
+  pkg = require('../package');
+
+  Auth = require('./adb/auth');
+
+  Promise.longStackTraces();
+
+  program.version(pkg.version);
+
+  program.command('pubkey-convert <file>').option('-f, --format <format>', 'format (pem or openssh)', String, 'pem').description('Converts an ADB-generated public key into PEM format.').action(function(file, options) {
+    return Auth.parsePublicKey(fs.readFileSync(file)).then(function(key) {
+      switch (options.format.toLowerCase()) {
+        case 'pem':
+          return console.log(forge.pki.publicKeyToPem(key).trim());
+        case 'openssh':
+          return console.log(forge.ssh.publicKeyToOpenSSH(key, 'adbkey').trim());
+        default:
+          console.error("Unsupported format '" + options.format + "'");
+          return process.exit(1);
+      }
+    });
+  });
+
+  program.command('pubkey-fingerprint <file>').description('Outputs the fingerprint of an ADB-generated public key.').action(function(file) {
+    return Auth.parsePublicKey(fs.readFileSync(file)).then(function(key) {
+      return console.log('%s %s', key.fingerprint, key.comment);
+    });
+  });
+
+  program.parse(process.argv);
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-logcat/LICENSE b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-logcat/LICENSE
new file mode 100644
index 0000000..2ffff3d
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-logcat/LICENSE
@@ -0,0 +1,13 @@
+Copyright © CyberAgent, Inc. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-logcat/README.md b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-logcat/README.md
new file mode 100644
index 0000000..df54e35
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-logcat/README.md
@@ -0,0 +1,240 @@
+# adbkit-logcat
+
+**adbkit-logcat** provides a [Node.js][nodejs] interface for working with output produced by the Android [`logcat` tool][logcat-site]. It takes a log stream (that you must create separately), parses it, and emits log entries in real-time as they occur. Possible use cases include storing logs in a database, forwarding logs via [MessagePack][msgpack], or just advanced filtering.
+
+## Getting started
+
+Install via NPM:
+
+```bash
+npm install --save adbkit-logcat
+```
+
+Note that while adbkit-logcat is written in CoffeeScript, it is compiled to JavaScript before publishing to NPM, which means that you are not required to use CoffeeScript.
+
+### Examples
+
+#### Output all log messages
+
+##### JavaScript
+
+```javascript
+var logcat = require('adbkit-logcat');
+var spawn = require('child_process').spawn;
+
+// Retrieve a binary log stream
+var proc = spawn('adb', ['logcat', '-B']);
+
+// Connect logcat to the stream
+reader = logcat.readStream(proc.stdout);
+reader.on('entry', function(entry) {
+  console.log(entry.message);
+});
+
+// Make sure we don't leave anything hanging
+process.on('exit', function() {
+  proc.kill();
+});
+```
+
+##### CoffeeScript
+
+```coffeescript
+Logcat = require 'adbkit-logcat'
+{spawn} = require 'child_process'
+
+# Retrieve a binary log stream
+proc = spawn 'adb', ['logcat', '-B']
+
+# Connect logcat to the stream
+reader = Logcat.readStream proc.stdout
+reader.on 'entry', (entry) ->
+  console.log entry.message
+
+# Make sure we don't leave anything hanging
+process.on 'exit', ->
+  proc.kill()
+```
+
+## API
+
+### Logcat
+
+#### logcat.Priority
+
+Exposes `Priority`. See below for details.
+
+#### logcat.Reader
+
+Exposes `Reader`. See below for details.
+
+#### logcat.readStream(stream[, options])
+
+Creates a logcat reader instance from the provided logcat event [`Stream`][node-stream]. Note that you must create the stream separately.
+
+* **stream** The event stream to read.
+* **options** Optional. The following options are supported:
+    - **format** The format of the stream. Currently, the only supported value is `'binary'`, which (for example) `adb logcat -B` produces. Defaults to `'binary'`.
+    - **fixLineFeeds** All programs run via the ADB shell transform any `'\n'` in the output to `'\r\n'`, which breaks binary content. If set, this option reverses the transformation before parsing the stream. Defaults to `true`.
+* Returns: The `Reader` instance.
+
+### Priority
+
+#### Constants
+
+The following static properties are available:
+
+* **Priority.UNKNOWN** i.e. `0`.
+* **Priority.DEFAULT** i.e. `1`. Not available when reading a stream.
+* **Priority.VERBOSE** i.e. `2`.
+* **Priority.DEBUG** i.e. `3`.
+* **Priority.INFO** i.e. `4`.
+* **Priority.WARN** i.e. `5`.
+* **Priority.ERROR** i.e. `6`.
+* **Priority.FATAL** i.e. `7`.
+* **Priority.SILENT** i.e. `8`. Not available when reading a stream.
+
+#### Priority.fromLetter(letter)
+
+Static method to convert the given `letter` into a numeric priority. For example, `Priority.fromName('d')` would return `Priority.DEBUG`.
+
+* **letter** The priority as a `String`. Any single, case-insensitive character matching the first character of any `Priority` constant is accepted.
+* Returns: The priority as a `Number`, or `undefined`.
+
+#### Priority.fromName(name)
+
+Static method to convert the given `name` into a numeric priority. For example, `Priority.fromName('debug')` (or `Priority.fromName('d')`) would return `Priority.DEBUG`.
+
+* **name** The priority as a `String`. Any full, case-insensitive match of the `Priority` constants is accepted. If no match is found, falls back to `Priority.fromLetter()`.
+* Returns: The priority as a `Number`, or `undefined`.
+
+#### Priority.toLetter(priority)
+
+Static method to convert the numeric priority into its letter representation. For example, `Priority.toLetter(Priority.DEBUG)` would return `'D'`.
+
+* **priority** The priority as a `Number`. Any `Priority` constant value is accepted.
+* Returns: The priority as a `String` letter, or `undefined`.
+
+#### Priority.toName(priority)
+
+Static method to convert the numeric priority into its full string representation. For example, `Priority.toLetter(Priority.DEBUG)` would return `'DEBUG'`.
+
+* **priority** The priority as a `Number`. Any `Priority` constant value is accepted.
+* Returns: The priority as a `String`, or `undefined`.
+
+### Reader
+
+A reader instance, which is an [`EventEmitter`][node-events].
+
+#### Events
+
+The following events are available:
+
+* **error** **(err)** Emitted when an error occurs.
+    * **err** An `Error`.
+* **end** Emitted when the stream ends.
+* **finish** Emitted when the stream finishes.
+* **entry** **(entry)** Emitted when the stream finishes.
+    * **entry** A log `Entry`. See below for details.
+
+#### constructor([options])
+
+For advanced users. Manually constructs a `Reader` instance. Useful for testing and/or playing around. Normally you would use `logcat.readStream()` to create the instance.
+
+* **options** See `logcat.readStream()` for details.
+* Returns: N/A
+
+#### reader.connect(stream)
+
+For advanced users. When instantiated manually (not via `logcat.readStream()`), connects the `Reader` instance to the given stream.
+
+* **stream** See `logcat.readStream()` for details.
+* Returns: The `Reader` instance.
+
+#### reader.end()
+
+Convenience method for ending the stream.
+
+* Returns: The `Reader` instance.
+
+#### reader.exclude(tag)
+
+Skip entries with the provided tag. Alias for `reader.include(tag, Priority.SILENT)`. Note that even skipped events have to be parsed so that they can be ignored.
+
+* **tag** The tag string to exclude. If `'*'`, works the same as `reader.excludeAll()`.
+* Returns: The `Reader` instance.
+
+#### reader.excludeAll()
+
+Skip **ALL** entries. Alias for `reader.includeAll(Priority.SILENT)`. Any entries you wish to see must be included via `include()`/`includeAll()`.
+
+* Returns: The `Reader` instance.
+
+#### reader.include(tag[, priority])
+
+Include all entries with the given tag and a priority higher or equal to the given `priority`.
+
+* **tag** The tag string to include. If `'*'`, works the same as `reader.includeAll(priority)`.
+* **priority** Optional. A lower bound for the priority. Any numeric `Priority` constant or any `String` value accepted by `Priority.fromName()` is accepted. Defaults to `Priority.DEBUG`.
+* Returns: The `Reader` instance.
+
+#### reader.includeAll([priority])
+
+Include all entries with a priority higher or equal to the given `priority`.
+
+* **tag** The tag string to exclude.
+* **priority** Optional. See `reader.include()` for details.
+* Returns: The `Reader` instance.
+
+#### reader.resetFilters()
+
+Resets all inclusions/exclusions.
+
+* Returns: The `Reader` instance.
+
+### Entry
+
+A log entry.
+
+#### Properties
+
+The following properties are available:
+
+* **date** Event time as a `Date`.
+* **pid** Process ID as a `Number`.
+* **tid** Thread ID as a `Number`.
+* **priority** Event priority as a `Number`. You can use `logcat.Priority` to convert the value into a `String`.
+* **tag** Event tag as a `String`.
+* **message** Message as a `String`.
+
+#### entry.toBinary()
+
+Converts the entry back to the binary log format.
+
+* Returns: The binary event as a [`Buffer`][node-buffer].
+
+## More information
+
+* Liblog
+    - [logprint.c][logprint-source]
+* Logcat
+    - [logcat.cpp][logcat-source]
+
+## Contributing
+
+See [CONTRIBUTING.md](CONTRIBUTING.md).
+
+## License
+
+See [LICENSE](LICENSE).
+
+Copyright © CyberAgent, Inc. All Rights Reserved.
+
+[nodejs]: <http://nodejs.org/>
+[msgpack]: <http://msgpack.org/>
+[logcat-site]: <http://developer.android.com/tools/help/logcat.html>
+[logprint-source]: <https://github.com/android/platform_system_core/blob/master/liblog/logprint.c>
+[logcat-source]: <https://github.com/android/platform_system_core/blob/master/logcat/logcat.cpp>
+[node-stream]: <http://nodejs.org/api/stream.html>
+[node-events]: <http://nodejs.org/api/events.html>
+[node-buffer]: <http://nodejs.org/api/buffer.html>
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-logcat/index.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-logcat/index.js
new file mode 100644
index 0000000..607433c
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-logcat/index.js
@@ -0,0 +1,15 @@
+(function() {
+  var Path;
+
+  Path = require('path');
+
+  module.exports = (function() {
+    switch (Path.extname(__filename)) {
+      case '.coffee':
+        return require('./src/logcat');
+      default:
+        return require('./lib/logcat');
+    }
+  })();
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat.js
new file mode 100644
index 0000000..d224f07
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat.js
@@ -0,0 +1,25 @@
+(function() {
+  var Logcat, Priority, Reader;
+
+  Reader = require('./logcat/reader');
+
+  Priority = require('./logcat/priority');
+
+  Logcat = (function() {
+    function Logcat() {}
+
+    Logcat.readStream = function(stream, options) {
+      return new Reader(options).connect(stream);
+    };
+
+    return Logcat;
+
+  })();
+
+  Logcat.Reader = Reader;
+
+  Logcat.Priority = Priority;
+
+  module.exports = Logcat;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/entry.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/entry.js
new file mode 100644
index 0000000..1a7f5d2
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/entry.js
@@ -0,0 +1,76 @@
+(function() {
+  var Entry;
+
+  Entry = (function() {
+    function Entry() {
+      this.date = null;
+      this.pid = -1;
+      this.tid = -1;
+      this.priority = null;
+      this.tag = null;
+      this.message = null;
+    }
+
+    Entry.prototype.setDate = function(date) {
+      this.date = date;
+    };
+
+    Entry.prototype.setPid = function(pid) {
+      this.pid = pid;
+    };
+
+    Entry.prototype.setTid = function(tid) {
+      this.tid = tid;
+    };
+
+    Entry.prototype.setPriority = function(priority) {
+      this.priority = priority;
+    };
+
+    Entry.prototype.setTag = function(tag) {
+      this.tag = tag;
+    };
+
+    Entry.prototype.setMessage = function(message) {
+      this.message = message;
+    };
+
+    Entry.prototype.toBinary = function() {
+      var buffer, cursor, length;
+      length = 20;
+      length += 1;
+      length += this.tag.length;
+      length += 1;
+      length += this.message.length;
+      length += 1;
+      buffer = new Buffer(length);
+      cursor = 0;
+      buffer.writeUInt16LE(length - 20, cursor);
+      cursor += 4;
+      buffer.writeInt32LE(this.pid, cursor);
+      cursor += 4;
+      buffer.writeInt32LE(this.tid, cursor);
+      cursor += 4;
+      buffer.writeInt32LE(Math.floor(this.date.getTime() / 1000), cursor);
+      cursor += 4;
+      buffer.writeInt32LE((this.date.getTime() % 1000) * 1000000, cursor);
+      cursor += 4;
+      buffer[cursor] = this.priority;
+      cursor += 1;
+      buffer.write(this.tag, cursor, this.tag.length);
+      cursor += this.tag.length;
+      buffer[cursor] = 0x00;
+      cursor += 1;
+      buffer.write(this.message, cursor, this.message.length);
+      cursor += this.message.length;
+      buffer[cursor] = 0x00;
+      return buffer;
+    };
+
+    return Entry;
+
+  })();
+
+  module.exports = Entry;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/parser.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/parser.js
new file mode 100644
index 0000000..e3c2939
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/parser.js
@@ -0,0 +1,32 @@
+(function() {
+  var EventEmitter, Parser, _ref,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  EventEmitter = require('events').EventEmitter;
+
+  Parser = (function(_super) {
+    __extends(Parser, _super);
+
+    function Parser() {
+      _ref = Parser.__super__.constructor.apply(this, arguments);
+      return _ref;
+    }
+
+    Parser.get = function(type) {
+      var parser;
+      parser = require("./parser/" + type);
+      return new parser();
+    };
+
+    Parser.prototype.parse = function() {
+      throw new Error("parse() is unimplemented");
+    };
+
+    return Parser;
+
+  })(EventEmitter);
+
+  module.exports = Parser;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/parser/binary.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/parser/binary.js
new file mode 100644
index 0000000..b861ace
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/parser/binary.js
@@ -0,0 +1,78 @@
+(function() {
+  var Binary, Entry, Parser, Priority,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Parser = require('../parser');
+
+  Entry = require('../entry');
+
+  Priority = require('../priority');
+
+  Binary = (function(_super) {
+    var HEADER_LENGTH;
+
+    __extends(Binary, _super);
+
+    HEADER_LENGTH = 20;
+
+    function Binary() {
+      this.buffer = new Buffer('');
+    }
+
+    Binary.prototype.parse = function(chunk) {
+      var cursor, data, entry, length, nsec, sec;
+      this.buffer = Buffer.concat([this.buffer, chunk]);
+      while (this.buffer.length > HEADER_LENGTH) {
+        cursor = 0;
+        length = this.buffer.readUInt16LE(cursor);
+        if (this.buffer.length < HEADER_LENGTH + length) {
+          break;
+        }
+        entry = new Entry;
+        cursor += 4;
+        entry.setPid(this.buffer.readInt32LE(cursor));
+        cursor += 4;
+        entry.setTid(this.buffer.readInt32LE(cursor));
+        cursor += 4;
+        sec = this.buffer.readInt32LE(cursor);
+        cursor += 4;
+        nsec = this.buffer.readInt32LE(cursor);
+        entry.setDate(new Date(sec * 1000 + nsec / 1000000));
+        cursor += 4;
+        data = this.buffer.slice(cursor, cursor + length);
+        cursor += length;
+        this.buffer = this.buffer.slice(cursor);
+        this._processEntry(entry, data);
+      }
+      if (this.buffer.length) {
+        this.emit('wait');
+      } else {
+        this.emit('drain');
+      }
+    };
+
+    Binary.prototype._processEntry = function(entry, data) {
+      var cursor, length;
+      entry.setPriority(data[0]);
+      cursor = 1;
+      length = data.length;
+      while (cursor < length) {
+        if (data[cursor] === 0) {
+          entry.setTag(data.slice(1, cursor).toString());
+          entry.setMessage(data.slice(cursor + 1, length - 1).toString());
+          this.emit('entry', entry);
+          return;
+        }
+        cursor += 1;
+      }
+      this.emit('error', new Error("Unprocessable entry data '" + data + "'"));
+    };
+
+    return Binary;
+
+  })(Parser);
+
+  module.exports = Binary;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/priority.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/priority.js
new file mode 100644
index 0000000..532ea62
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/priority.js
@@ -0,0 +1,89 @@
+(function() {
+  var Priority;
+
+  Priority = (function() {
+    var letterNames, letters, names;
+
+    function Priority() {}
+
+    Priority.UNKNOWN = 0;
+
+    Priority.DEFAULT = 1;
+
+    Priority.VERBOSE = 2;
+
+    Priority.DEBUG = 3;
+
+    Priority.INFO = 4;
+
+    Priority.WARN = 5;
+
+    Priority.ERROR = 6;
+
+    Priority.FATAL = 7;
+
+    Priority.SILENT = 8;
+
+    names = {
+      0: 'UNKNOWN',
+      1: 'DEFAULT',
+      2: 'VERBOSE',
+      3: 'DEBUG',
+      4: 'INFO',
+      5: 'WARN',
+      6: 'ERROR',
+      7: 'FATAL',
+      8: 'SILENT'
+    };
+
+    letters = {
+      '?': Priority.UNKNOWN,
+      'V': Priority.VERBOSE,
+      'D': Priority.DEBUG,
+      'I': Priority.INFO,
+      'W': Priority.WARN,
+      'E': Priority.ERROR,
+      'F': Priority.FATAL,
+      'S': Priority.SILENT
+    };
+
+    letterNames = {
+      0: '?',
+      1: '?',
+      2: 'V',
+      3: 'D',
+      4: 'I',
+      5: 'W',
+      6: 'E',
+      7: 'F',
+      8: 'S'
+    };
+
+    Priority.fromName = function(name) {
+      var value;
+      value = Priority[name.toUpperCase()];
+      if (value || value === 0) {
+        return value;
+      }
+      return Priority.fromLetter(name);
+    };
+
+    Priority.toName = function(value) {
+      return names[value];
+    };
+
+    Priority.fromLetter = function(letter) {
+      return letters[letter.toUpperCase()];
+    };
+
+    Priority.toLetter = function(value) {
+      return letterNames[value];
+    };
+
+    return Priority;
+
+  })();
+
+  module.exports = Priority;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/reader.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/reader.js
new file mode 100644
index 0000000..7f07626
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/reader.js
@@ -0,0 +1,137 @@
+(function() {
+  var EventEmitter, Parser, Priority, Reader, Transform,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  EventEmitter = require('events').EventEmitter;
+
+  Parser = require('./parser');
+
+  Transform = require('./transform');
+
+  Priority = require('./priority');
+
+  Reader = (function(_super) {
+    __extends(Reader, _super);
+
+    Reader.ANY = '*';
+
+    function Reader(options) {
+      var _base;
+      this.options = options != null ? options : {};
+      (_base = this.options).format || (_base.format = 'binary');
+      if (this.options.fixLineFeeds == null) {
+        this.options.fixLineFeeds = true;
+      }
+      this.filters = {
+        all: -1,
+        tags: {}
+      };
+      this.parser = Parser.get(this.options.format);
+      this.stream = null;
+    }
+
+    Reader.prototype.exclude = function(tag) {
+      if (tag === Reader.ANY) {
+        return this.excludeAll();
+      }
+      this.filters.tags[tag] = Priority.SILENT;
+      return this;
+    };
+
+    Reader.prototype.excludeAll = function() {
+      this.filters.all = Priority.SILENT;
+      return this;
+    };
+
+    Reader.prototype.include = function(tag, priority) {
+      if (priority == null) {
+        priority = Priority.DEBUG;
+      }
+      if (tag === Reader.ANY) {
+        return this.includeAll(priority);
+      }
+      this.filters.tags[tag] = this._priority(priority);
+      return this;
+    };
+
+    Reader.prototype.includeAll = function(priority) {
+      if (priority == null) {
+        priority = Priority.DEBUG;
+      }
+      this.filters.all = this._priority(priority);
+      return this;
+    };
+
+    Reader.prototype.resetFilters = function() {
+      this.filters.all = -1;
+      this.filters.tags = {};
+      return this;
+    };
+
+    Reader.prototype._hook = function() {
+      var transform,
+        _this = this;
+      if (this.options.fixLineFeeds) {
+        transform = this.stream.pipe(new Transform);
+        transform.on('data', function(data) {
+          return _this.parser.parse(data);
+        });
+      } else {
+        this.stream.on('data', function(data) {
+          return _this.parser.parse(data);
+        });
+      }
+      this.stream.on('error', function(err) {
+        return _this.emit('error', err);
+      });
+      this.stream.on('end', function() {
+        return _this.emit('end');
+      });
+      this.stream.on('finish', function() {
+        return _this.emit('finish');
+      });
+      this.parser.on('entry', function(entry) {
+        if (_this._filter(entry)) {
+          return _this.emit('entry', entry);
+        }
+      });
+      this.parser.on('error', function(err) {
+        return _this.emit('error', err);
+      });
+    };
+
+    Reader.prototype._filter = function(entry) {
+      var priority;
+      priority = this.filters.tags[entry.tag];
+      if (!(priority >= 0)) {
+        priority = this.filters.all;
+      }
+      return entry.priority >= priority;
+    };
+
+    Reader.prototype._priority = function(priority) {
+      if (typeof priority === 'number') {
+        return priority;
+      }
+      return Priority.fromName(priority);
+    };
+
+    Reader.prototype.connect = function(stream) {
+      this.stream = stream;
+      this._hook();
+      return this;
+    };
+
+    Reader.prototype.end = function() {
+      this.stream.end();
+      return this;
+    };
+
+    return Reader;
+
+  })(EventEmitter);
+
+  module.exports = Reader;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/transform.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/transform.js
new file mode 100644
index 0000000..b6fe7a2
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/transform.js
@@ -0,0 +1,51 @@
+(function() {
+  var Stream, Transform,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Stream = require('stream');
+
+  Transform = (function(_super) {
+    __extends(Transform, _super);
+
+    function Transform(options) {
+      this.savedR = null;
+      Transform.__super__.constructor.call(this, options);
+    }
+
+    Transform.prototype._transform = function(chunk, encoding, done) {
+      var hi, last, lo;
+      lo = 0;
+      hi = 0;
+      if (this.savedR) {
+        if (chunk[0] !== 0x0a) {
+          this.push(this.savedR);
+        }
+        this.savedR = null;
+      }
+      last = chunk.length - 1;
+      while (hi <= last) {
+        if (chunk[hi] === 0x0d) {
+          if (hi === last) {
+            this.savedR = chunk.slice(last);
+            break;
+          } else if (chunk[hi + 1] === 0x0a) {
+            this.push(chunk.slice(lo, hi));
+            lo = hi + 1;
+          }
+        }
+        hi += 1;
+      }
+      if (hi !== lo) {
+        this.push(chunk.slice(lo, hi));
+      }
+      done();
+    };
+
+    return Transform;
+
+  })(Stream.Transform);
+
+  module.exports = Transform;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-logcat/package.json b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-logcat/package.json
new file mode 100644
index 0000000..6fe20fd
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-logcat/package.json
@@ -0,0 +1,75 @@
+{
+  "name": "adbkit-logcat",
+  "version": "1.0.3",
+  "description": "A Node.js interface for working with Android's logcat output.",
+  "keywords": [
+    "adb",
+    "adbkit",
+    "logcat"
+  ],
+  "bugs": {
+    "url": "https://github.com/CyberAgent/adbkit-logcat/issues"
+  },
+  "license": "Apache-2.0",
+  "author": {
+    "name": "CyberAgent, Inc.",
+    "email": "npm@cyberagent.co.jp",
+    "url": "http://www.cyberagent.co.jp/"
+  },
+  "main": "./index",
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/CyberAgent/adbkit-logcat.git"
+  },
+  "scripts": {
+    "postpublish": "grunt clean",
+    "prepublish": "grunt coffee",
+    "test": "grunt test"
+  },
+  "dependencies": {},
+  "devDependencies": {
+    "chai": "~1.8.1",
+    "coffee-script": "~1.6.3",
+    "grunt": "~0.4.1",
+    "grunt-cli": "~0.1.11",
+    "grunt-coffeelint": "~0.0.7",
+    "grunt-contrib-clean": "~0.5.0",
+    "grunt-contrib-coffee": "~0.7.0",
+    "grunt-contrib-watch": "~0.5.3",
+    "grunt-exec": "~0.4.2",
+    "grunt-jsonlint": "~1.0.2",
+    "grunt-notify": "~0.2.16",
+    "mocha": "~1.14.0",
+    "sinon": "~1.7.3",
+    "sinon-chai": "~2.4.0"
+  },
+  "engines": {
+    "node": ">= 0.10.4"
+  },
+  "homepage": "https://github.com/CyberAgent/adbkit-logcat",
+  "_id": "adbkit-logcat@1.0.3",
+  "dist": {
+    "shasum": "8b5fef57086c02c9e24004af5706ee508d83db07",
+    "tarball": "http://registry.npmjs.org/adbkit-logcat/-/adbkit-logcat-1.0.3.tgz"
+  },
+  "_from": "adbkit-logcat@~1.0.3",
+  "_npmVersion": "1.4.3",
+  "_npmUser": {
+    "name": "sorccu",
+    "email": "simo@shoqolate.com"
+  },
+  "maintainers": [
+    {
+      "name": "sorccu",
+      "email": "simo@shoqolate.com"
+    },
+    {
+      "name": "cyberagent",
+      "email": "npm@cyberagent.co.jp"
+    }
+  ],
+  "directories": {},
+  "_shasum": "8b5fef57086c02c9e24004af5706ee508d83db07",
+  "_resolved": "https://registry.npmjs.org/adbkit-logcat/-/adbkit-logcat-1.0.3.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/LICENSE b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/LICENSE
new file mode 100644
index 0000000..2ffff3d
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/LICENSE
@@ -0,0 +1,13 @@
+Copyright © CyberAgent, Inc. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/README.md b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/README.md
new file mode 100644
index 0000000..97977bb
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/README.md
@@ -0,0 +1,469 @@
+# adbkit-monkey
+
+**adbkit-monkey** provides a [Node.js][nodejs] interface for working with the Android [`monkey` tool][monkey-site]. Albeit undocumented, they monkey program can be started in TCP mode with the `--port` argument. In this mode, it accepts a [range of commands][monkey-proto] that can be used to interact with the UI in a non-random manner. This mode is also used internally by the [`monkeyrunner` tool][monkeyrunner-site], although the documentation claims no relation to the monkey tool.
+
+## Getting started
+
+Install via NPM:
+
+```bash
+npm install --save adbkit-monkey
+```
+
+Note that while adbkit-monkey is written in CoffeeScript, it is compiled to JavaScript before publishing to NPM, which means that you are not required to use CoffeeScript.
+
+### Examples
+
+The following examples assume that monkey is already running (via `adb shell monkey --port 1080`) and a port forwarding (`adb forward tcp:1080 tcp:1080`) has been set up.
+
+#### Press the home button
+
+```javascript
+var assert = require('assert');
+var monkey = require('adbkit-monkey');
+
+var client = monkey.connect({ port: 1080 });
+
+client.press(3 /* KEYCODE_HOME */, function(err) {
+  assert.ifError(err);
+  console.log('Pressed home button');
+  client.end();
+});
+```
+
+#### Drag out the notification bar
+
+```javascript
+var assert = require('assert');
+var monkey = require('adbkit-monkey');
+
+var client = monkey.connect({ port: 1080 });
+
+client.multi()
+  .touchDown(100, 0)
+  .sleep(5)
+  .touchMove(100, 20)
+  .sleep(5)
+  .touchMove(100, 40)
+  .sleep(5)
+  .touchMove(100, 60)
+  .sleep(5)
+  .touchMove(100, 80)
+  .sleep(5)
+  .touchMove(100, 100)
+  .sleep(5)
+  .touchUp(100, 100)
+  .sleep(5)
+  .execute(function(err) {
+    assert.ifError(err);
+    console.log('Dragged out the notification bar');
+    client.end();
+  });
+```
+
+#### Get display size
+
+```javascript
+var assert = require('assert');
+var monkey = require('adbkit-monkey');
+
+var client = monkey.connect({ port: 1080 });
+
+client.getDisplayWidth(function(err, width) {
+  assert.ifError(err);
+  client.getDisplayHeight(function(err, height) {
+    assert.ifError(err);
+    console.log('Display size is %dx%d', width, height);
+    client.end();
+  });
+});
+```
+
+#### Type text
+
+Note that you should manually focus a text field first.
+
+```javascript
+var assert = require('assert');
+var monkey = require('adbkit-monkey');
+
+var client = monkey.connect({ port: 1080 });
+
+client.type('hello monkey!', function(err) {
+  assert.ifError(err);
+  console.log('Said hello to monkey');
+  client.end();
+});
+```
+
+## API
+
+### Monkey
+
+#### monkey.connect(options)
+
+Uses [Net.connect()][node-net] to open a new TCP connection to monkey. Useful when combined with `adb forward`.
+
+* **options** Any options [`Net.connect()`][node-net] accepts.
+* Returns: A new monkey `Client` instance.
+
+#### monkey.connectStream(stream)
+
+Attaches a monkey client to an existing monkey protocol stream.
+
+* **stream** The monkey protocol [`Stream`][node-stream].
+* Returns: A new monkey `Client` instance.
+
+### Client
+
+Implements `Api`. See below for details.
+
+#### Events
+
+The following events are available:
+
+* **error** **(err)** Emitted when an error occurs.
+    * **err** An `Error`.
+* **end** Emitted when the stream ends.
+* **finish** Emitted when the stream finishes.
+
+#### client.end()
+
+Ends the underlying stream/connection.
+
+* Returns: The `Client` instance.
+
+#### client.multi()
+
+Returns a new API wrapper that buffers commands for simultaneous delivery instead of sending them individually. When used with `api.sleep()`, allows simple gestures to be executed.
+
+* Returns: A new `Multi` instance. See `Multi` below.
+
+#### client.send(command, callback)
+
+Sends a raw protocol command to monkey.
+
+* **command** The command to send. When `String`, a single command is sent. When `Array`, a series of commands is sent at once.
+* **callback(err, value, command)** Called when monkey responds to the command. If multiple commands were sent, the callback will be called once for each command.
+    * **err** `null` when successful, `Error` otherwise.
+    * **value** The response value, if any.
+    * **command** The command the response is for.
+* Returns: The `Client` instance.
+
+### Api
+
+The monkey API implemented by `Client` and `Multi`.
+
+#### api.done(callback)
+
+Closes the current monkey session and allows a new session to connect.
+
+* **callback(err)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+* Returns: The `Api` implementation instance.
+
+#### api.flipClose(callback)
+
+Simulates closing the keyboard.
+
+* **callback(err)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+* Returns: The `Api` implementation instance.
+
+#### api.flipOpen(callback)
+
+Simulates opening the keyboard.
+
+* **callback(err)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+* Returns: The `Api` implementation instance.
+
+#### api.get(name, callback)
+
+Gets the value of a variable. Use `api.list()` to retrieve a list of supported variables.
+
+* **name** The name of the variable.
+* **callback(err, value)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+    * **value** The value of the variable.
+* Returns: The `Api` implementation instance.
+
+#### api.getAmCurrentAction(callback)
+
+Alias for `api.get('am.current.action', callback)`.
+
+#### api.getAmCurrentCategories(callback)
+
+Alias for `api.get('am.current.categories', callback)`.
+
+#### api.getAmCurrentCompClass(callback)
+
+Alias for `api.get('am.current.comp.class', callback)`.
+
+#### api.getAmCurrentCompPackage(callback)
+
+Alias for `api.get('am.current.comp.package', callback)`.
+
+#### api.getCurrentData(callback)
+
+Alias for `api.get('am.current.data', callback)`.
+
+#### api.getAmCurrentPackage(callback)
+
+Alias for `api.get('am.current.package', callback)`.
+
+#### api.getBuildBoard(callback)
+
+Alias for `api.get('build.board', callback)`.
+
+#### api.getBuildBrand(callback)
+
+Alias for `api.get('build.brand', callback)`.
+
+#### api.getBuildCpuAbi(callback)
+
+Alias for `api.get('build.cpu_abi', callback)`.
+
+#### api.getBuildDevice(callback)
+
+Alias for `api.get('build.device', callback)`.
+
+#### api.getBuildDisplay(callback)
+
+Alias for `api.get('build.display', callback)`.
+
+#### api.getBuildFingerprint(callback)
+
+Alias for `api.get('build.fingerprint', callback)`.
+
+#### api.getBuildHost(callback)
+
+Alias for `api.get('build.host', callback)`.
+
+#### api.getBuildId(callback)
+
+Alias for `api.get('build.id', callback)`.
+
+#### api.getBuildManufacturer(callback)
+
+Alias for `api.get('build.manufacturer', callback)`.
+
+#### api.getBuildModel(callback)
+
+Alias for `api.get('build.model', callback)`.
+
+#### api.getBuildProduct(callback)
+
+Alias for `api.get('build.product', callback)`.
+
+#### api.getBuildTags(callback)
+
+Alias for `api.get('build.tags', callback)`.
+
+#### api.getBuildType(callback)
+
+Alias for `api.get('build.type', callback)`.
+
+#### api.getBuildUser(callback)
+
+Alias for `api.get('build.user', callback)`.
+
+#### api.getBuildVersionCodename(callback)
+
+Alias for `api.get('build.version.codename', callback)`.
+
+#### api.getBuildVersionIncremental(callback)
+
+Alias for `api.get('build.version.incremental', callback)`.
+
+#### api.getBuildVersionRelease(callback)
+
+Alias for `api.get('build.version.release', callback)`.
+
+#### api.getBuildVersionSdk(callback)
+
+Alias for `api.get('build.version.sdk', callback)`.
+
+#### api.getClockMillis(callback)
+
+Alias for `api.get('clock.millis', callback)`.
+
+#### api.getClockRealtime(callback)
+
+Alias for `api.get('clock.realtime', callback)`.
+
+#### api.getClockUptime(callback)
+
+Alias for `api.get('clock.uptime', callback)`.
+
+#### api.getDisplayDensity(callback)
+
+Alias for `api.get('display.density', callback)`.
+
+#### api.getDisplayHeight(callback)
+
+Alias for `api.get('display.height', callback)`. Note that the height may exclude any virtual home button row.
+
+#### api.getDisplayWidth(callback)
+
+Alias for `api.get('display.width', callback)`.
+
+#### api.keyDown(keyCode, callback)
+
+Sends a key down event. Should be coupled with `api.keyUp()`. Note that `api.press()` performs the two events automatically.
+
+* **keyCode** The [key code][android-keycodes]. All monkeys support numeric keycodes, and some support automatic conversion from key names to key codes (e.g. `'home'` to `KEYCODE_HOME`). This will not work for number keys however. The most portable method is to simply use numeric key codes.
+* **callback(err)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+* Returns: The `Api` implementation instance.
+
+#### api.keyUp(keyCode, callback)
+
+Sends a key up event. Should be coupled with `api.keyDown()`. Note that `api.press()` performs the two events automatically.
+
+* **keyCode** See `api.keyDown()`.
+* **callback(err)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+* Returns: The `Api` implementation instance.
+
+#### api.list(callback)
+
+Lists supported variables.
+
+* **callback(err, vars)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+    * **vars** An array of supported variable names, to be used with `api.get()`.
+* Returns: The `Api` implementation instance.
+
+#### api.press(keyCode, callback)
+
+Sends a key press event.
+
+* **keyCode** See `api.keyDown()`.
+* **callback(err)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+* Returns: The `Api` implementation instance.
+
+#### api.quit(callback)
+
+Closes the current monkey session and quits monkey.
+
+* **callback(err)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+* Returns: The `Api` implementation instance.
+
+#### api.sleep(ms, callback)
+
+Sleeps for the given duration. Can be useful for simulating gestures.
+
+* **ms** How many milliseconds to sleep for.
+* **callback(err)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+* Returns: The `Api` implementation instance.
+
+#### api.tap(x, y, callback)
+
+Taps the given coordinates.
+
+* **x** The x coordinate.
+* **y** The y coordinate.
+* **callback(err)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+* Returns: The `Api` implementation instance.
+
+#### api.touchDown(x, y, callback)
+
+Sends a touch down event on the given coordinates.
+
+* **x** The x coordinate.
+* **y** The y coordinate.
+* **callback(err)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+* Returns: The `Api` implementation instance.
+
+#### api.touchMove(x, y, callback)
+
+Sends a touch move event on the given coordinates.
+
+* **x** The x coordinate.
+* **y** The y coordinate.
+* **callback(err)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+* Returns: The `Api` implementation instance.
+
+#### api.touchUp(x, y, callback)
+
+Sends a touch up event on the given coordinates.
+
+* **x** The x coordinate.
+* **y** The y coordinate.
+* **callback(err)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+* Returns: The `Api` implementation instance.
+
+#### api.trackball(x, y, callback)
+
+Sends a trackball event on the given coordinates.
+
+* **x** The x coordinate.
+* **y** The y coordinate.
+* **callback(err)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+* Returns: The `Api` implementation instance.
+
+#### api.type(text, callback)
+
+Types the given text.
+
+* **text** A text `String`. Note that only characters for which [key codes][android-keycodes] exist can be entered. Also note that any IME in use may or may not transform the text.
+* **callback(err)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+* Returns: The `Api` implementation instance.
+
+#### api.wake(callback)
+
+Wakes the device from sleep and allows user input.
+
+* **callback(err)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+* Returns: The `Api` implementation instance.
+
+### Multi
+
+Buffers `Api` commands and delivers them simultaneously for greater control over timing.
+
+Implements all `Api` methods, but without the last `callback` parameter.
+
+#### multi.execute(callback)
+
+Sends all buffered commands.
+
+* **callback(err, values)** Called when monkey has responded to all commands (i.e. just once at the end).
+    * **err** `null` when successful, `Error` otherwise.
+    * **values** An array of all response values, identical to individual `Api` responses.
+
+## More information
+
+* [Monkey][monkey-site]
+    - [Source code][monkey-source]
+    - [Protocol][monkey-proto]
+* [Monkeyrunner][monkeyrunner-site]
+
+## Contributing
+
+See [CONTRIBUTING.md](CONTRIBUTING.md).
+
+## License
+
+See [LICENSE](LICENSE).
+
+Copyright © CyberAgent, Inc. All Rights Reserved.
+
+[nodejs]: <http://nodejs.org/>
+[monkey-site]: <http://developer.android.com/tools/help/monkey.html>
+[monkey-source]: <https://github.com/android/platform_development/blob/master/cmds/monkey/>
+[monkey-proto]: <https://github.com/android/platform_development/blob/master/cmds/monkey/README.NETWORK.txt>
+[monkeyrunner-site]: <http://developer.android.com/tools/help/monkeyrunner_concepts.html>
+[node-net]: <http://nodejs.org/api/net.html>
+[node-stream]: <http://nodejs.org/api/stream.html>
+[android-keycodes]: <http://developer.android.com/reference/android/view/KeyEvent.html>
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/index.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/index.js
new file mode 100644
index 0000000..a81226d
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/index.js
@@ -0,0 +1,15 @@
+(function() {
+  var Path;
+
+  Path = require('path');
+
+  module.exports = (function() {
+    switch (Path.extname(__filename)) {
+      case '.coffee':
+        return require('./src/monkey');
+      default:
+        return require('./lib/monkey');
+    }
+  })();
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey.js
new file mode 100644
index 0000000..9e81c56
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey.js
@@ -0,0 +1,29 @@
+(function() {
+  var Client, Connection, Monkey;
+
+  Client = require('./monkey/client');
+
+  Connection = require('./monkey/connection');
+
+  Monkey = (function() {
+    function Monkey() {}
+
+    Monkey.connect = function(options) {
+      return new Connection().connect(options);
+    };
+
+    Monkey.connectStream = function(stream) {
+      return new Client().connect(stream);
+    };
+
+    return Monkey;
+
+  })();
+
+  Monkey.Connection = Connection;
+
+  Monkey.Client = Client;
+
+  module.exports = Monkey;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/api.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/api.js
new file mode 100644
index 0000000..34fce09
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/api.js
@@ -0,0 +1,276 @@
+(function() {
+  var Api, EventEmitter, _ref,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  EventEmitter = require('events').EventEmitter;
+
+  Api = (function(_super) {
+    __extends(Api, _super);
+
+    function Api() {
+      _ref = Api.__super__.constructor.apply(this, arguments);
+      return _ref;
+    }
+
+    Api.prototype.send = function() {
+      throw new Error("send is not implemented");
+    };
+
+    Api.prototype.keyDown = function(keyCode, callback) {
+      this.send("key down " + keyCode, callback);
+      return this;
+    };
+
+    Api.prototype.keyUp = function(keyCode, callback) {
+      this.send("key up " + keyCode, callback);
+      return this;
+    };
+
+    Api.prototype.touchDown = function(x, y, callback) {
+      this.send("touch down " + x + " " + y, callback);
+      return this;
+    };
+
+    Api.prototype.touchUp = function(x, y, callback) {
+      this.send("touch up " + x + " " + y, callback);
+      return this;
+    };
+
+    Api.prototype.touchMove = function(x, y, callback) {
+      this.send("touch move " + x + " " + y, callback);
+      return this;
+    };
+
+    Api.prototype.trackball = function(dx, dy, callback) {
+      this.send("trackball " + dx + " " + dy, callback);
+      return this;
+    };
+
+    Api.prototype.flipOpen = function(callback) {
+      this.send("flip open", callback);
+      return this;
+    };
+
+    Api.prototype.flipClose = function(callback) {
+      this.send("flip close", callback);
+      return this;
+    };
+
+    Api.prototype.wake = function(callback) {
+      this.send("wake", callback);
+      return this;
+    };
+
+    Api.prototype.tap = function(x, y, callback) {
+      this.send("tap " + x + " " + y, callback);
+      return this;
+    };
+
+    Api.prototype.press = function(keyCode, callback) {
+      this.send("press " + keyCode, callback);
+      return this;
+    };
+
+    Api.prototype.type = function(str, callback) {
+      str = str.replace(/"/g, '\\"');
+      if (str.indexOf(' ') === -1) {
+        this.send("type " + str, callback);
+      } else {
+        this.send("type \"" + str + "\"", callback);
+      }
+      return this;
+    };
+
+    Api.prototype.list = function(callback) {
+      var _this = this;
+      this.send("listvar", function(err, vars) {
+        if (err) {
+          return _this(callback(err));
+        }
+        if (err) {
+          return callback(err);
+        } else {
+          return callback(null, vars.split(/\s+/g));
+        }
+      });
+      return this;
+    };
+
+    Api.prototype.get = function(name, callback) {
+      this.send("getvar " + name, callback);
+      return this;
+    };
+
+    Api.prototype.quit = function(callback) {
+      this.send("quit", callback);
+      return this;
+    };
+
+    Api.prototype.done = function(callback) {
+      this.send("done", callback);
+      return this;
+    };
+
+    Api.prototype.sleep = function(ms, callback) {
+      this.send("sleep " + ms, callback);
+      return this;
+    };
+
+    Api.prototype.getAmCurrentAction = function(callback) {
+      this.get('am.current.action', callback);
+      return this;
+    };
+
+    Api.prototype.getAmCurrentCategories = function(callback) {
+      this.get('am.current.categories', callback);
+      return this;
+    };
+
+    Api.prototype.getAmCurrentCompClass = function(callback) {
+      this.get('am.current.comp.class', callback);
+      return this;
+    };
+
+    Api.prototype.getAmCurrentCompPackage = function(callback) {
+      this.get('am.current.comp.package', callback);
+      return this;
+    };
+
+    Api.prototype.getAmCurrentData = function(callback) {
+      this.get('am.current.data', callback);
+      return this;
+    };
+
+    Api.prototype.getAmCurrentPackage = function(callback) {
+      this.get('am.current.package', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildBoard = function(callback) {
+      this.get('build.board', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildBrand = function(callback) {
+      this.get('build.brand', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildCpuAbi = function(callback) {
+      this.get('build.cpu_abi', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildDevice = function(callback) {
+      this.get('build.device', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildDisplay = function(callback) {
+      this.get('build.display', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildFingerprint = function(callback) {
+      this.get('build.fingerprint', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildHost = function(callback) {
+      this.get('build.host', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildId = function(callback) {
+      this.get('build.id', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildManufacturer = function(callback) {
+      this.get('build.manufacturer', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildModel = function(callback) {
+      this.get('build.model', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildProduct = function(callback) {
+      this.get('build.product', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildTags = function(callback) {
+      this.get('build.tags', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildType = function(callback) {
+      this.get('build.type', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildUser = function(callback) {
+      this.get('build.user', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildVersionCodename = function(callback) {
+      this.get('build.version.codename', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildVersionIncremental = function(callback) {
+      this.get('build.version.incremental', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildVersionRelease = function(callback) {
+      this.get('build.version.release', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildVersionSdk = function(callback) {
+      this.get('build.version.sdk', callback);
+      return this;
+    };
+
+    Api.prototype.getClockMillis = function(callback) {
+      this.get('clock.millis', callback);
+      return this;
+    };
+
+    Api.prototype.getClockRealtime = function(callback) {
+      this.get('clock.realtime', callback);
+      return this;
+    };
+
+    Api.prototype.getClockUptime = function(callback) {
+      this.get('clock.uptime', callback);
+      return this;
+    };
+
+    Api.prototype.getDisplayDensity = function(callback) {
+      this.get('display.density', callback);
+      return this;
+    };
+
+    Api.prototype.getDisplayHeight = function(callback) {
+      this.get('display.height', callback);
+      return this;
+    };
+
+    Api.prototype.getDisplayWidth = function(callback) {
+      this.get('display.width', callback);
+      return this;
+    };
+
+    return Api;
+
+  })(EventEmitter);
+
+  module.exports = Api;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/client.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/client.js
new file mode 100644
index 0000000..b6e1278
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/client.js
@@ -0,0 +1,98 @@
+(function() {
+  var Api, Client, Command, Multi, Parser, Queue, Reply,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Api = require('./api');
+
+  Command = require('./command');
+
+  Reply = require('./reply');
+
+  Queue = require('./queue');
+
+  Multi = require('./multi');
+
+  Parser = require('./parser');
+
+  Client = (function(_super) {
+    __extends(Client, _super);
+
+    function Client() {
+      this.commandQueue = new Queue;
+      this.parser = new Parser;
+      this.stream = null;
+    }
+
+    Client.prototype._hook = function() {
+      var _this = this;
+      this.stream.on('data', function(data) {
+        return _this.parser.parse(data);
+      });
+      this.stream.on('error', function(err) {
+        return _this.emit('error', err);
+      });
+      this.stream.on('end', function() {
+        return _this.emit('end');
+      });
+      this.stream.on('finish', function() {
+        return _this.emit('finish');
+      });
+      this.parser.on('reply', function(reply) {
+        return _this._consume(reply);
+      });
+      this.parser.on('error', function(err) {
+        return _this.emit('error', err);
+      });
+    };
+
+    Client.prototype._consume = function(reply) {
+      var command;
+      if (command = this.commandQueue.dequeue()) {
+        if (reply.isError()) {
+          command.callback(reply.toError(), null, command.command);
+        } else {
+          command.callback(null, reply.value, command.command);
+        }
+      } else {
+        throw new Error("Command queue depleted, but replies still coming in");
+      }
+    };
+
+    Client.prototype.connect = function(stream) {
+      this.stream = stream;
+      this._hook();
+      return this;
+    };
+
+    Client.prototype.end = function() {
+      this.stream.end();
+      return this;
+    };
+
+    Client.prototype.send = function(commands, callback) {
+      var command, _i, _len;
+      if (Array.isArray(commands)) {
+        for (_i = 0, _len = commands.length; _i < _len; _i++) {
+          command = commands[_i];
+          this.commandQueue.enqueue(new Command(command, callback));
+        }
+        this.stream.write("" + (commands.join('\n')) + "\n");
+      } else {
+        this.commandQueue.enqueue(new Command(commands, callback));
+        this.stream.write("" + commands + "\n");
+      }
+      return this;
+    };
+
+    Client.prototype.multi = function() {
+      return new Multi(this);
+    };
+
+    return Client;
+
+  })(Api);
+
+  module.exports = Client;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/command.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/command.js
new file mode 100644
index 0000000..41062e0
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/command.js
@@ -0,0 +1,17 @@
+(function() {
+  var Command;
+
+  Command = (function() {
+    function Command(command, callback) {
+      this.command = command;
+      this.callback = callback;
+      this.next = null;
+    }
+
+    return Command;
+
+  })();
+
+  module.exports = Command;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/connection.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/connection.js
new file mode 100644
index 0000000..b52ee14
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/connection.js
@@ -0,0 +1,42 @@
+(function() {
+  var Client, Connection, Net, _ref,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Net = require('net');
+
+  Client = require('./client');
+
+  Connection = (function(_super) {
+    __extends(Connection, _super);
+
+    function Connection() {
+      _ref = Connection.__super__.constructor.apply(this, arguments);
+      return _ref;
+    }
+
+    Connection.prototype.connect = function(options) {
+      var stream;
+      stream = Net.connect(options);
+      stream.setNoDelay(true);
+      return Connection.__super__.connect.call(this, stream);
+    };
+
+    Connection.prototype._hook = function() {
+      var _this = this;
+      this.stream.on('connect', function() {
+        return _this.emit('connect');
+      });
+      this.stream.on('close', function(hadError) {
+        return _this.emit('close', hadError);
+      });
+      return Connection.__super__._hook.call(this);
+    };
+
+    return Connection;
+
+  })(Client);
+
+  module.exports = Connection;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/multi.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/multi.js
new file mode 100644
index 0000000..80e8214
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/multi.js
@@ -0,0 +1,85 @@
+(function() {
+  var Api, Command, Multi,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Api = require('./api');
+
+  Command = require('./command');
+
+  Multi = (function(_super) {
+    __extends(Multi, _super);
+
+    function Multi(monkey) {
+      var _this = this;
+      this.monkey = monkey;
+      this.commands = [];
+      this.replies = [];
+      this.errors = [];
+      this.counter = 0;
+      this.sent = false;
+      this.callback = null;
+      this.collector = function(err, result, cmd) {
+        if (err) {
+          _this.errors.push("" + cmd + ": " + err.message);
+        }
+        _this.replies.push(result);
+        _this.counter -= 1;
+        return _this._maybeFinish();
+      };
+    }
+
+    Multi.prototype._maybeFinish = function() {
+      var _this = this;
+      if (this.counter === 0) {
+        if (this.errors.length) {
+          setImmediate(function() {
+            return _this.callback(new Error(_this.errors.join(', ')));
+          });
+        } else {
+          setImmediate(function() {
+            return _this.callback(null, _this.replies);
+          });
+        }
+      }
+    };
+
+    Multi.prototype._forbidReuse = function() {
+      if (this.sent) {
+        throw new Error("Reuse not supported");
+      }
+    };
+
+    Multi.prototype.send = function(command) {
+      this._forbidReuse();
+      this.commands.push(new Command(command, this.collector));
+    };
+
+    Multi.prototype.execute = function(callback) {
+      var command, parts, _i, _len, _ref;
+      this._forbidReuse();
+      this.counter = this.commands.length;
+      this.sent = true;
+      this.callback = callback;
+      if (this.counter === 0) {
+        return;
+      }
+      parts = [];
+      _ref = this.commands;
+      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+        command = _ref[_i];
+        this.monkey.commandQueue.enqueue(command);
+        parts.push(command.command);
+      }
+      parts.push('');
+      this.commands = [];
+      this.monkey.stream.write(parts.join('\n'));
+    };
+
+    return Multi;
+
+  })(Api);
+
+  module.exports = Multi;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/parser.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/parser.js
new file mode 100644
index 0000000..75dcb9f
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/parser.js
@@ -0,0 +1,66 @@
+(function() {
+  var EventEmitter, Parser, Reply,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  EventEmitter = require('events').EventEmitter;
+
+  Reply = require('./reply');
+
+  Parser = (function(_super) {
+    __extends(Parser, _super);
+
+    function Parser(options) {
+      this.column = 0;
+      this.buffer = new Buffer('');
+    }
+
+    Parser.prototype.parse = function(chunk) {
+      this.buffer = Buffer.concat([this.buffer, chunk]);
+      while (this.column < this.buffer.length) {
+        if (this.buffer[this.column] === 0x0a) {
+          this._parseLine(this.buffer.slice(0, this.column));
+          this.buffer = this.buffer.slice(this.column + 1);
+          this.column = 0;
+        }
+        this.column += 1;
+      }
+      if (this.buffer.length) {
+        this.emit('wait');
+      } else {
+        this.emit('drain');
+      }
+    };
+
+    Parser.prototype._parseLine = function(line) {
+      switch (line[0]) {
+        case 0x4f:
+          if (line.length === 2) {
+            this.emit('reply', new Reply(Reply.OK, null));
+          } else {
+            this.emit('reply', new Reply(Reply.OK, line.toString('ascii', 3)));
+          }
+          break;
+        case 0x45:
+          if (line.length === 5) {
+            this.emit('reply', new Reply(Reply.ERROR, null));
+          } else {
+            this.emit('reply', new Reply(Reply.ERROR, line.toString('ascii', 6)));
+          }
+          break;
+        default:
+          this._complain(line);
+      }
+    };
+
+    Parser.prototype._complain = function(line) {
+      this.emit('error', new SyntaxError("Unparseable line '" + line + "'"));
+    };
+
+    return Parser;
+
+  })(EventEmitter);
+
+  module.exports = Parser;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/queue.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/queue.js
new file mode 100644
index 0000000..19d6615
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/queue.js
@@ -0,0 +1,38 @@
+(function() {
+  var Queue;
+
+  Queue = (function() {
+    function Queue() {
+      this.head = null;
+      this.tail = null;
+    }
+
+    Queue.prototype.enqueue = function(item) {
+      if (this.tail) {
+        this.tail.next = item;
+      } else {
+        this.head = item;
+      }
+      this.tail = item;
+    };
+
+    Queue.prototype.dequeue = function() {
+      var item;
+      item = this.head;
+      if (item) {
+        if (item === this.tail) {
+          this.tail = null;
+        }
+        this.head = item.next;
+        item.next = null;
+      }
+      return item;
+    };
+
+    return Queue;
+
+  })();
+
+  module.exports = Queue;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/reply.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/reply.js
new file mode 100644
index 0000000..349c920
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/reply.js
@@ -0,0 +1,31 @@
+(function() {
+  var Reply;
+
+  Reply = (function() {
+    Reply.ERROR = 'ERROR';
+
+    Reply.OK = 'OK';
+
+    function Reply(type, value) {
+      this.type = type;
+      this.value = value;
+    }
+
+    Reply.prototype.isError = function() {
+      return this.type === Reply.ERROR;
+    };
+
+    Reply.prototype.toError = function() {
+      if (!this.isError()) {
+        throw new Error('toError() cannot be called for non-errors');
+      }
+      return new Error(this.value);
+    };
+
+    return Reply;
+
+  })();
+
+  module.exports = Reply;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/node_modules/async/LICENSE b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/node_modules/async/LICENSE
new file mode 100644
index 0000000..b7f9d50
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/node_modules/async/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2010 Caolan McMahon
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/node_modules/async/README.md b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/node_modules/async/README.md
new file mode 100644
index 0000000..951f76e
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/node_modules/async/README.md
@@ -0,0 +1,1425 @@
+# Async.js
+
+Async is a utility module which provides straight-forward, powerful functions
+for working with asynchronous JavaScript. Although originally designed for
+use with [node.js](http://nodejs.org), it can also be used directly in the
+browser. Also supports [component](https://github.com/component/component).
+
+Async provides around 20 functions that include the usual 'functional'
+suspects (map, reduce, filter, each…) as well as some common patterns
+for asynchronous control flow (parallel, series, waterfall…). All these
+functions assume you follow the node.js convention of providing a single
+callback as the last argument of your async function.
+
+
+## Quick Examples
+
+```javascript
+async.map(['file1','file2','file3'], fs.stat, function(err, results){
+    // results is now an array of stats for each file
+});
+
+async.filter(['file1','file2','file3'], fs.exists, function(results){
+    // results now equals an array of the existing files
+});
+
+async.parallel([
+    function(){ ... },
+    function(){ ... }
+], callback);
+
+async.series([
+    function(){ ... },
+    function(){ ... }
+]);
+```
+
+There are many more functions available so take a look at the docs below for a
+full list. This module aims to be comprehensive, so if you feel anything is
+missing please create a GitHub issue for it.
+
+## Common Pitfalls
+
+### Binding a context to an iterator
+
+This section is really about bind, not about async. If you are wondering how to
+make async execute your iterators in a given context, or are confused as to why
+a method of another library isn't working as an iterator, study this example:
+
+```js
+// Here is a simple object with an (unnecessarily roundabout) squaring method
+var AsyncSquaringLibrary = {
+  squareExponent: 2,
+  square: function(number, callback){ 
+    var result = Math.pow(number, this.squareExponent);
+    setTimeout(function(){
+      callback(null, result);
+    }, 200);
+  }
+};
+
+async.map([1, 2, 3], AsyncSquaringLibrary.square, function(err, result){
+  // result is [NaN, NaN, NaN]
+  // This fails because the `this.squareExponent` expression in the square
+  // function is not evaluated in the context of AsyncSquaringLibrary, and is
+  // therefore undefined.
+});
+
+async.map([1, 2, 3], AsyncSquaringLibrary.square.bind(AsyncSquaringLibrary), function(err, result){
+  // result is [1, 4, 9]
+  // With the help of bind we can attach a context to the iterator before
+  // passing it to async. Now the square function will be executed in its 
+  // 'home' AsyncSquaringLibrary context and the value of `this.squareExponent`
+  // will be as expected.
+});
+```
+
+## Download
+
+The source is available for download from
+[GitHub](http://github.com/caolan/async).
+Alternatively, you can install using Node Package Manager (npm):
+
+    npm install async
+
+__Development:__ [async.js](https://github.com/caolan/async/raw/master/lib/async.js) - 29.6kb Uncompressed
+
+## In the Browser
+
+So far it's been tested in IE6, IE7, IE8, FF3.6 and Chrome 5. Usage:
+
+```html
+<script type="text/javascript" src="async.js"></script>
+<script type="text/javascript">
+
+    async.map(data, asyncProcess, function(err, results){
+        alert(results);
+    });
+
+</script>
+```
+
+## Documentation
+
+### Collections
+
+* [each](#each)
+* [eachSeries](#eachSeries)
+* [eachLimit](#eachLimit)
+* [map](#map)
+* [mapSeries](#mapSeries)
+* [mapLimit](#mapLimit)
+* [filter](#filter)
+* [filterSeries](#filterSeries)
+* [reject](#reject)
+* [rejectSeries](#rejectSeries)
+* [reduce](#reduce)
+* [reduceRight](#reduceRight)
+* [detect](#detect)
+* [detectSeries](#detectSeries)
+* [sortBy](#sortBy)
+* [some](#some)
+* [every](#every)
+* [concat](#concat)
+* [concatSeries](#concatSeries)
+
+### Control Flow
+
+* [series](#series)
+* [parallel](#parallel)
+* [parallelLimit](#parallellimittasks-limit-callback)
+* [whilst](#whilst)
+* [doWhilst](#doWhilst)
+* [until](#until)
+* [doUntil](#doUntil)
+* [forever](#forever)
+* [waterfall](#waterfall)
+* [compose](#compose)
+* [applyEach](#applyEach)
+* [applyEachSeries](#applyEachSeries)
+* [queue](#queue)
+* [cargo](#cargo)
+* [auto](#auto)
+* [iterator](#iterator)
+* [apply](#apply)
+* [nextTick](#nextTick)
+* [times](#times)
+* [timesSeries](#timesSeries)
+
+### Utils
+
+* [memoize](#memoize)
+* [unmemoize](#unmemoize)
+* [log](#log)
+* [dir](#dir)
+* [noConflict](#noConflict)
+
+
+## Collections
+
+<a name="forEach" />
+<a name="each" />
+### each(arr, iterator, callback)
+
+Applies an iterator function to each item in an array, in parallel.
+The iterator is called with an item from the list and a callback for when it
+has finished. If the iterator passes an error to this callback, the main
+callback for the each function is immediately called with the error.
+
+Note, that since this function applies the iterator to each item in parallel
+there is no guarantee that the iterator functions will complete in order.
+
+__Arguments__
+
+* arr - An array to iterate over.
+* iterator(item, callback) - A function to apply to each item in the array.
+  The iterator is passed a callback(err) which must be called once it has 
+  completed. If no error has occured, the callback should be run without 
+  arguments or with an explicit null argument.
+* callback(err) - A callback which is called after all the iterator functions
+  have finished, or an error has occurred.
+
+__Example__
+
+```js
+// assuming openFiles is an array of file names and saveFile is a function
+// to save the modified contents of that file:
+
+async.each(openFiles, saveFile, function(err){
+    // if any of the saves produced an error, err would equal that error
+});
+```
+
+---------------------------------------
+
+<a name="forEachSeries" />
+<a name="eachSeries" />
+### eachSeries(arr, iterator, callback)
+
+The same as each only the iterator is applied to each item in the array in
+series. The next iterator is only called once the current one has completed
+processing. This means the iterator functions will complete in order.
+
+
+---------------------------------------
+
+<a name="forEachLimit" />
+<a name="eachLimit" />
+### eachLimit(arr, limit, iterator, callback)
+
+The same as each only no more than "limit" iterators will be simultaneously 
+running at any time.
+
+Note that the items are not processed in batches, so there is no guarantee that
+ the first "limit" iterator functions will complete before any others are 
+started.
+
+__Arguments__
+
+* arr - An array to iterate over.
+* limit - The maximum number of iterators to run at any time.
+* iterator(item, callback) - A function to apply to each item in the array.
+  The iterator is passed a callback(err) which must be called once it has 
+  completed. If no error has occured, the callback should be run without 
+  arguments or with an explicit null argument.
+* callback(err) - A callback which is called after all the iterator functions
+  have finished, or an error has occurred.
+
+__Example__
+
+```js
+// Assume documents is an array of JSON objects and requestApi is a
+// function that interacts with a rate-limited REST api.
+
+async.eachLimit(documents, 20, requestApi, function(err){
+    // if any of the saves produced an error, err would equal that error
+});
+```
+
+---------------------------------------
+
+<a name="map" />
+### map(arr, iterator, callback)
+
+Produces a new array of values by mapping each value in the given array through
+the iterator function. The iterator is called with an item from the array and a
+callback for when it has finished processing. The callback takes 2 arguments, 
+an error and the transformed item from the array. If the iterator passes an
+error to this callback, the main callback for the map function is immediately
+called with the error.
+
+Note, that since this function applies the iterator to each item in parallel
+there is no guarantee that the iterator functions will complete in order, however
+the results array will be in the same order as the original array.
+
+__Arguments__
+
+* arr - An array to iterate over.
+* iterator(item, callback) - A function to apply to each item in the array.
+  The iterator is passed a callback(err, transformed) which must be called once 
+  it has completed with an error (which can be null) and a transformed item.
+* callback(err, results) - A callback which is called after all the iterator
+  functions have finished, or an error has occurred. Results is an array of the
+  transformed items from the original array.
+
+__Example__
+
+```js
+async.map(['file1','file2','file3'], fs.stat, function(err, results){
+    // results is now an array of stats for each file
+});
+```
+
+---------------------------------------
+
+<a name="mapSeries" />
+### mapSeries(arr, iterator, callback)
+
+The same as map only the iterator is applied to each item in the array in
+series. The next iterator is only called once the current one has completed
+processing. The results array will be in the same order as the original.
+
+
+---------------------------------------
+
+<a name="mapLimit" />
+### mapLimit(arr, limit, iterator, callback)
+
+The same as map only no more than "limit" iterators will be simultaneously 
+running at any time.
+
+Note that the items are not processed in batches, so there is no guarantee that
+ the first "limit" iterator functions will complete before any others are 
+started.
+
+__Arguments__
+
+* arr - An array to iterate over.
+* limit - The maximum number of iterators to run at any time.
+* iterator(item, callback) - A function to apply to each item in the array.
+  The iterator is passed a callback(err, transformed) which must be called once 
+  it has completed with an error (which can be null) and a transformed item.
+* callback(err, results) - A callback which is called after all the iterator
+  functions have finished, or an error has occurred. Results is an array of the
+  transformed items from the original array.
+
+__Example__
+
+```js
+async.mapLimit(['file1','file2','file3'], 1, fs.stat, function(err, results){
+    // results is now an array of stats for each file
+});
+```
+
+---------------------------------------
+
+<a name="filter" />
+### filter(arr, iterator, callback)
+
+__Alias:__ select
+
+Returns a new array of all the values which pass an async truth test.
+_The callback for each iterator call only accepts a single argument of true or
+false, it does not accept an error argument first!_ This is in-line with the
+way node libraries work with truth tests like fs.exists. This operation is
+performed in parallel, but the results array will be in the same order as the
+original.
+
+__Arguments__
+
+* arr - An array to iterate over.
+* iterator(item, callback) - A truth test to apply to each item in the array.
+  The iterator is passed a callback(truthValue) which must be called with a 
+  boolean argument once it has completed.
+* callback(results) - A callback which is called after all the iterator
+  functions have finished.
+
+__Example__
+
+```js
+async.filter(['file1','file2','file3'], fs.exists, function(results){
+    // results now equals an array of the existing files
+});
+```
+
+---------------------------------------
+
+<a name="filterSeries" />
+### filterSeries(arr, iterator, callback)
+
+__alias:__ selectSeries
+
+The same as filter only the iterator is applied to each item in the array in
+series. The next iterator is only called once the current one has completed
+processing. The results array will be in the same order as the original.
+
+---------------------------------------
+
+<a name="reject" />
+### reject(arr, iterator, callback)
+
+The opposite of filter. Removes values that pass an async truth test.
+
+---------------------------------------
+
+<a name="rejectSeries" />
+### rejectSeries(arr, iterator, callback)
+
+The same as reject, only the iterator is applied to each item in the array
+in series.
+
+
+---------------------------------------
+
+<a name="reduce" />
+### reduce(arr, memo, iterator, callback)
+
+__aliases:__ inject, foldl
+
+Reduces a list of values into a single value using an async iterator to return
+each successive step. Memo is the initial state of the reduction. This
+function only operates in series. For performance reasons, it may make sense to
+split a call to this function into a parallel map, then use the normal
+Array.prototype.reduce on the results. This function is for situations where
+each step in the reduction needs to be async, if you can get the data before
+reducing it then it's probably a good idea to do so.
+
+__Arguments__
+
+* arr - An array to iterate over.
+* memo - The initial state of the reduction.
+* iterator(memo, item, callback) - A function applied to each item in the
+  array to produce the next step in the reduction. The iterator is passed a
+  callback(err, reduction) which accepts an optional error as its first 
+  argument, and the state of the reduction as the second. If an error is 
+  passed to the callback, the reduction is stopped and the main callback is 
+  immediately called with the error.
+* callback(err, result) - A callback which is called after all the iterator
+  functions have finished. Result is the reduced value.
+
+__Example__
+
+```js
+async.reduce([1,2,3], 0, function(memo, item, callback){
+    // pointless async:
+    process.nextTick(function(){
+        callback(null, memo + item)
+    });
+}, function(err, result){
+    // result is now equal to the last value of memo, which is 6
+});
+```
+
+---------------------------------------
+
+<a name="reduceRight" />
+### reduceRight(arr, memo, iterator, callback)
+
+__Alias:__ foldr
+
+Same as reduce, only operates on the items in the array in reverse order.
+
+
+---------------------------------------
+
+<a name="detect" />
+### detect(arr, iterator, callback)
+
+Returns the first value in a list that passes an async truth test. The
+iterator is applied in parallel, meaning the first iterator to return true will
+fire the detect callback with that result. That means the result might not be
+the first item in the original array (in terms of order) that passes the test.
+
+If order within the original array is important then look at detectSeries.
+
+__Arguments__
+
+* arr - An array to iterate over.
+* iterator(item, callback) - A truth test to apply to each item in the array.
+  The iterator is passed a callback(truthValue) which must be called with a 
+  boolean argument once it has completed.
+* callback(result) - A callback which is called as soon as any iterator returns
+  true, or after all the iterator functions have finished. Result will be
+  the first item in the array that passes the truth test (iterator) or the
+  value undefined if none passed.
+
+__Example__
+
+```js
+async.detect(['file1','file2','file3'], fs.exists, function(result){
+    // result now equals the first file in the list that exists
+});
+```
+
+---------------------------------------
+
+<a name="detectSeries" />
+### detectSeries(arr, iterator, callback)
+
+The same as detect, only the iterator is applied to each item in the array
+in series. This means the result is always the first in the original array (in
+terms of array order) that passes the truth test.
+
+
+---------------------------------------
+
+<a name="sortBy" />
+### sortBy(arr, iterator, callback)
+
+Sorts a list by the results of running each value through an async iterator.
+
+__Arguments__
+
+* arr - An array to iterate over.
+* iterator(item, callback) - A function to apply to each item in the array.
+  The iterator is passed a callback(err, sortValue) which must be called once it
+  has completed with an error (which can be null) and a value to use as the sort
+  criteria.
+* callback(err, results) - A callback which is called after all the iterator
+  functions have finished, or an error has occurred. Results is the items from
+  the original array sorted by the values returned by the iterator calls.
+
+__Example__
+
+```js
+async.sortBy(['file1','file2','file3'], function(file, callback){
+    fs.stat(file, function(err, stats){
+        callback(err, stats.mtime);
+    });
+}, function(err, results){
+    // results is now the original array of files sorted by
+    // modified date
+});
+```
+
+---------------------------------------
+
+<a name="some" />
+### some(arr, iterator, callback)
+
+__Alias:__ any
+
+Returns true if at least one element in the array satisfies an async test.
+_The callback for each iterator call only accepts a single argument of true or
+false, it does not accept an error argument first!_ This is in-line with the
+way node libraries work with truth tests like fs.exists. Once any iterator
+call returns true, the main callback is immediately called.
+
+__Arguments__
+
+* arr - An array to iterate over.
+* iterator(item, callback) - A truth test to apply to each item in the array.
+  The iterator is passed a callback(truthValue) which must be called with a 
+  boolean argument once it has completed.
+* callback(result) - A callback which is called as soon as any iterator returns
+  true, or after all the iterator functions have finished. Result will be
+  either true or false depending on the values of the async tests.
+
+__Example__
+
+```js
+async.some(['file1','file2','file3'], fs.exists, function(result){
+    // if result is true then at least one of the files exists
+});
+```
+
+---------------------------------------
+
+<a name="every" />
+### every(arr, iterator, callback)
+
+__Alias:__ all
+
+Returns true if every element in the array satisfies an async test.
+_The callback for each iterator call only accepts a single argument of true or
+false, it does not accept an error argument first!_ This is in-line with the
+way node libraries work with truth tests like fs.exists.
+
+__Arguments__
+
+* arr - An array to iterate over.
+* iterator(item, callback) - A truth test to apply to each item in the array.
+  The iterator is passed a callback(truthValue) which must be called with a 
+  boolean argument once it has completed.
+* callback(result) - A callback which is called after all the iterator
+  functions have finished. Result will be either true or false depending on
+  the values of the async tests.
+
+__Example__
+
+```js
+async.every(['file1','file2','file3'], fs.exists, function(result){
+    // if result is true then every file exists
+});
+```
+
+---------------------------------------
+
+<a name="concat" />
+### concat(arr, iterator, callback)
+
+Applies an iterator to each item in a list, concatenating the results. Returns the
+concatenated list. The iterators are called in parallel, and the results are
+concatenated as they return. There is no guarantee that the results array will
+be returned in the original order of the arguments passed to the iterator function.
+
+__Arguments__
+
+* arr - An array to iterate over
+* iterator(item, callback) - A function to apply to each item in the array.
+  The iterator is passed a callback(err, results) which must be called once it 
+  has completed with an error (which can be null) and an array of results.
+* callback(err, results) - A callback which is called after all the iterator
+  functions have finished, or an error has occurred. Results is an array containing
+  the concatenated results of the iterator function.
+
+__Example__
+
+```js
+async.concat(['dir1','dir2','dir3'], fs.readdir, function(err, files){
+    // files is now a list of filenames that exist in the 3 directories
+});
+```
+
+---------------------------------------
+
+<a name="concatSeries" />
+### concatSeries(arr, iterator, callback)
+
+Same as async.concat, but executes in series instead of parallel.
+
+
+## Control Flow
+
+<a name="series" />
+### series(tasks, [callback])
+
+Run an array of functions in series, each one running once the previous
+function has completed. If any functions in the series pass an error to its
+callback, no more functions are run and the callback for the series is
+immediately called with the value of the error. Once the tasks have completed,
+the results are passed to the final callback as an array.
+
+It is also possible to use an object instead of an array. Each property will be
+run as a function and the results will be passed to the final callback as an object
+instead of an array. This can be a more readable way of handling results from
+async.series.
+
+
+__Arguments__
+
+* tasks - An array or object containing functions to run, each function is passed
+  a callback(err, result) it must call on completion with an error (which can
+  be null) and an optional result value.
+* callback(err, results) - An optional callback to run once all the functions
+  have completed. This function gets a results array (or object) containing all 
+  the result arguments passed to the task callbacks.
+
+__Example__
+
+```js
+async.series([
+    function(callback){
+        // do some stuff ...
+        callback(null, 'one');
+    },
+    function(callback){
+        // do some more stuff ...
+        callback(null, 'two');
+    }
+],
+// optional callback
+function(err, results){
+    // results is now equal to ['one', 'two']
+});
+
+
+// an example using an object instead of an array
+async.series({
+    one: function(callback){
+        setTimeout(function(){
+            callback(null, 1);
+        }, 200);
+    },
+    two: function(callback){
+        setTimeout(function(){
+            callback(null, 2);
+        }, 100);
+    }
+},
+function(err, results) {
+    // results is now equal to: {one: 1, two: 2}
+});
+```
+
+---------------------------------------
+
+<a name="parallel" />
+### parallel(tasks, [callback])
+
+Run an array of functions in parallel, without waiting until the previous
+function has completed. If any of the functions pass an error to its
+callback, the main callback is immediately called with the value of the error.
+Once the tasks have completed, the results are passed to the final callback as an
+array.
+
+It is also possible to use an object instead of an array. Each property will be
+run as a function and the results will be passed to the final callback as an object
+instead of an array. This can be a more readable way of handling results from
+async.parallel.
+
+
+__Arguments__
+
+* tasks - An array or object containing functions to run, each function is passed 
+  a callback(err, result) it must call on completion with an error (which can
+  be null) and an optional result value.
+* callback(err, results) - An optional callback to run once all the functions
+  have completed. This function gets a results array (or object) containing all 
+  the result arguments passed to the task callbacks.
+
+__Example__
+
+```js
+async.parallel([
+    function(callback){
+        setTimeout(function(){
+            callback(null, 'one');
+        }, 200);
+    },
+    function(callback){
+        setTimeout(function(){
+            callback(null, 'two');
+        }, 100);
+    }
+],
+// optional callback
+function(err, results){
+    // the results array will equal ['one','two'] even though
+    // the second function had a shorter timeout.
+});
+
+
+// an example using an object instead of an array
+async.parallel({
+    one: function(callback){
+        setTimeout(function(){
+            callback(null, 1);
+        }, 200);
+    },
+    two: function(callback){
+        setTimeout(function(){
+            callback(null, 2);
+        }, 100);
+    }
+},
+function(err, results) {
+    // results is now equals to: {one: 1, two: 2}
+});
+```
+
+---------------------------------------
+
+<a name="parallel" />
+### parallelLimit(tasks, limit, [callback])
+
+The same as parallel only the tasks are executed in parallel with a maximum of "limit" 
+tasks executing at any time.
+
+Note that the tasks are not executed in batches, so there is no guarantee that 
+the first "limit" tasks will complete before any others are started.
+
+__Arguments__
+
+* tasks - An array or object containing functions to run, each function is passed 
+  a callback(err, result) it must call on completion with an error (which can
+  be null) and an optional result value.
+* limit - The maximum number of tasks to run at any time.
+* callback(err, results) - An optional callback to run once all the functions
+  have completed. This function gets a results array (or object) containing all 
+  the result arguments passed to the task callbacks.
+
+---------------------------------------
+
+<a name="whilst" />
+### whilst(test, fn, callback)
+
+Repeatedly call fn, while test returns true. Calls the callback when stopped,
+or an error occurs.
+
+__Arguments__
+
+* test() - synchronous truth test to perform before each execution of fn.
+* fn(callback) - A function to call each time the test passes. The function is
+  passed a callback(err) which must be called once it has completed with an 
+  optional error argument.
+* callback(err) - A callback which is called after the test fails and repeated
+  execution of fn has stopped.
+
+__Example__
+
+```js
+var count = 0;
+
+async.whilst(
+    function () { return count < 5; },
+    function (callback) {
+        count++;
+        setTimeout(callback, 1000);
+    },
+    function (err) {
+        // 5 seconds have passed
+    }
+);
+```
+
+---------------------------------------
+
+<a name="doWhilst" />
+### doWhilst(fn, test, callback)
+
+The post check version of whilst. To reflect the difference in the order of operations `test` and `fn` arguments are switched. `doWhilst` is to `whilst` as `do while` is to `while` in plain JavaScript.
+
+---------------------------------------
+
+<a name="until" />
+### until(test, fn, callback)
+
+Repeatedly call fn, until test returns true. Calls the callback when stopped,
+or an error occurs.
+
+The inverse of async.whilst.
+
+---------------------------------------
+
+<a name="doUntil" />
+### doUntil(fn, test, callback)
+
+Like doWhilst except the test is inverted. Note the argument ordering differs from `until`.
+
+---------------------------------------
+
+<a name="forever" />
+### forever(fn, callback)
+
+Calls the asynchronous function 'fn' repeatedly, in series, indefinitely.
+If an error is passed to fn's callback then 'callback' is called with the
+error, otherwise it will never be called.
+
+---------------------------------------
+
+<a name="waterfall" />
+### waterfall(tasks, [callback])
+
+Runs an array of functions in series, each passing their results to the next in
+the array. However, if any of the functions pass an error to the callback, the
+next function is not executed and the main callback is immediately called with
+the error.
+
+__Arguments__
+
+* tasks - An array of functions to run, each function is passed a 
+  callback(err, result1, result2, ...) it must call on completion. The first
+  argument is an error (which can be null) and any further arguments will be 
+  passed as arguments in order to the next task.
+* callback(err, [results]) - An optional callback to run once all the functions
+  have completed. This will be passed the results of the last task's callback.
+
+
+
+__Example__
+
+```js
+async.waterfall([
+    function(callback){
+        callback(null, 'one', 'two');
+    },
+    function(arg1, arg2, callback){
+        callback(null, 'three');
+    },
+    function(arg1, callback){
+        // arg1 now equals 'three'
+        callback(null, 'done');
+    }
+], function (err, result) {
+   // result now equals 'done'    
+});
+```
+
+---------------------------------------
+<a name="compose" />
+### compose(fn1, fn2...)
+
+Creates a function which is a composition of the passed asynchronous
+functions. Each function consumes the return value of the function that
+follows. Composing functions f(), g() and h() would produce the result of
+f(g(h())), only this version uses callbacks to obtain the return values.
+
+Each function is executed with the `this` binding of the composed function.
+
+__Arguments__
+
+* functions... - the asynchronous functions to compose
+
+
+__Example__
+
+```js
+function add1(n, callback) {
+    setTimeout(function () {
+        callback(null, n + 1);
+    }, 10);
+}
+
+function mul3(n, callback) {
+    setTimeout(function () {
+        callback(null, n * 3);
+    }, 10);
+}
+
+var add1mul3 = async.compose(mul3, add1);
+
+add1mul3(4, function (err, result) {
+   // result now equals 15
+});
+```
+
+---------------------------------------
+<a name="applyEach" />
+### applyEach(fns, args..., callback)
+
+Applies the provided arguments to each function in the array, calling the
+callback after all functions have completed. If you only provide the first
+argument then it will return a function which lets you pass in the
+arguments as if it were a single function call.
+
+__Arguments__
+
+* fns - the asynchronous functions to all call with the same arguments
+* args... - any number of separate arguments to pass to the function
+* callback - the final argument should be the callback, called when all
+  functions have completed processing
+
+
+__Example__
+
+```js
+async.applyEach([enableSearch, updateSchema], 'bucket', callback);
+
+// partial application example:
+async.each(
+    buckets,
+    async.applyEach([enableSearch, updateSchema]),
+    callback
+);
+```
+
+---------------------------------------
+
+<a name="applyEachSeries" />
+### applyEachSeries(arr, iterator, callback)
+
+The same as applyEach only the functions are applied in series.
+
+---------------------------------------
+
+<a name="queue" />
+### queue(worker, concurrency)
+
+Creates a queue object with the specified concurrency. Tasks added to the
+queue will be processed in parallel (up to the concurrency limit). If all
+workers are in progress, the task is queued until one is available. Once
+a worker has completed a task, the task's callback is called.
+
+__Arguments__
+
+* worker(task, callback) - An asynchronous function for processing a queued
+  task, which must call its callback(err) argument when finished, with an 
+  optional error as an argument.
+* concurrency - An integer for determining how many worker functions should be
+  run in parallel.
+
+__Queue objects__
+
+The queue object returned by this function has the following properties and
+methods:
+
+* length() - a function returning the number of items waiting to be processed.
+* concurrency - an integer for determining how many worker functions should be
+  run in parallel. This property can be changed after a queue is created to
+  alter the concurrency on-the-fly.
+* push(task, [callback]) - add a new task to the queue, the callback is called
+  once the worker has finished processing the task.
+  instead of a single task, an array of tasks can be submitted. the respective callback is used for every task in the list.
+* unshift(task, [callback]) - add a new task to the front of the queue.
+* saturated - a callback that is called when the queue length hits the concurrency and further tasks will be queued
+* empty - a callback that is called when the last item from the queue is given to a worker
+* drain - a callback that is called when the last item from the queue has returned from the worker
+
+__Example__
+
+```js
+// create a queue object with concurrency 2
+
+var q = async.queue(function (task, callback) {
+    console.log('hello ' + task.name);
+    callback();
+}, 2);
+
+
+// assign a callback
+q.drain = function() {
+    console.log('all items have been processed');
+}
+
+// add some items to the queue
+
+q.push({name: 'foo'}, function (err) {
+    console.log('finished processing foo');
+});
+q.push({name: 'bar'}, function (err) {
+    console.log('finished processing bar');
+});
+
+// add some items to the queue (batch-wise)
+
+q.push([{name: 'baz'},{name: 'bay'},{name: 'bax'}], function (err) {
+    console.log('finished processing bar');
+});
+
+// add some items to the front of the queue
+
+q.unshift({name: 'bar'}, function (err) {
+    console.log('finished processing bar');
+});
+```
+
+---------------------------------------
+
+<a name="cargo" />
+### cargo(worker, [payload])
+
+Creates a cargo object with the specified payload. Tasks added to the
+cargo will be processed altogether (up to the payload limit). If the
+worker is in progress, the task is queued until it is available. Once
+the worker has completed some tasks, each callback of those tasks is called.
+
+__Arguments__
+
+* worker(tasks, callback) - An asynchronous function for processing an array of
+  queued tasks, which must call its callback(err) argument when finished, with 
+  an optional error as an argument.
+* payload - An optional integer for determining how many tasks should be
+  processed per round; if omitted, the default is unlimited.
+
+__Cargo objects__
+
+The cargo object returned by this function has the following properties and
+methods:
+
+* length() - a function returning the number of items waiting to be processed.
+* payload - an integer for determining how many tasks should be
+  process per round. This property can be changed after a cargo is created to
+  alter the payload on-the-fly.
+* push(task, [callback]) - add a new task to the queue, the callback is called
+  once the worker has finished processing the task.
+  instead of a single task, an array of tasks can be submitted. the respective callback is used for every task in the list.
+* saturated - a callback that is called when the queue length hits the concurrency and further tasks will be queued
+* empty - a callback that is called when the last item from the queue is given to a worker
+* drain - a callback that is called when the last item from the queue has returned from the worker
+
+__Example__
+
+```js
+// create a cargo object with payload 2
+
+var cargo = async.cargo(function (tasks, callback) {
+    for(var i=0; i<tasks.length; i++){
+      console.log('hello ' + tasks[i].name);
+    }
+    callback();
+}, 2);
+
+
+// add some items
+
+cargo.push({name: 'foo'}, function (err) {
+    console.log('finished processing foo');
+});
+cargo.push({name: 'bar'}, function (err) {
+    console.log('finished processing bar');
+});
+cargo.push({name: 'baz'}, function (err) {
+    console.log('finished processing baz');
+});
+```
+
+---------------------------------------
+
+<a name="auto" />
+### auto(tasks, [callback])
+
+Determines the best order for running functions based on their requirements.
+Each function can optionally depend on other functions being completed first,
+and each function is run as soon as its requirements are satisfied. If any of
+the functions pass an error to their callback, that function will not complete
+(so any other functions depending on it will not run) and the main callback
+will be called immediately with the error. Functions also receive an object
+containing the results of functions which have completed so far.
+
+Note, all functions are called with a results object as a second argument, 
+so it is unsafe to pass functions in the tasks object which cannot handle the
+extra argument. For example, this snippet of code:
+
+```js
+async.auto({
+  readData: async.apply(fs.readFile, 'data.txt', 'utf-8')
+}, callback);
+```
+
+will have the effect of calling readFile with the results object as the last
+argument, which will fail:
+
+```js
+fs.readFile('data.txt', 'utf-8', cb, {});
+```
+
+Instead, wrap the call to readFile in a function which does not forward the 
+results object:
+
+```js
+async.auto({
+  readData: function(cb, results){
+    fs.readFile('data.txt', 'utf-8', cb);
+  }
+}, callback);
+```
+
+__Arguments__
+
+* tasks - An object literal containing named functions or an array of
+  requirements, with the function itself the last item in the array. The key
+  used for each function or array is used when specifying requirements. The 
+  function receives two arguments: (1) a callback(err, result) which must be 
+  called when finished, passing an error (which can be null) and the result of 
+  the function's execution, and (2) a results object, containing the results of
+  the previously executed functions.
+* callback(err, results) - An optional callback which is called when all the
+  tasks have been completed. The callback will receive an error as an argument
+  if any tasks pass an error to their callback. Results will always be passed
+	but if an error occurred, no other tasks will be performed, and the results
+	object will only contain partial results.
+  
+
+__Example__
+
+```js
+async.auto({
+    get_data: function(callback){
+        // async code to get some data
+    },
+    make_folder: function(callback){
+        // async code to create a directory to store a file in
+        // this is run at the same time as getting the data
+    },
+    write_file: ['get_data', 'make_folder', function(callback){
+        // once there is some data and the directory exists,
+        // write the data to a file in the directory
+        callback(null, filename);
+    }],
+    email_link: ['write_file', function(callback, results){
+        // once the file is written let's email a link to it...
+        // results.write_file contains the filename returned by write_file.
+    }]
+});
+```
+
+This is a fairly trivial example, but to do this using the basic parallel and
+series functions would look like this:
+
+```js
+async.parallel([
+    function(callback){
+        // async code to get some data
+    },
+    function(callback){
+        // async code to create a directory to store a file in
+        // this is run at the same time as getting the data
+    }
+],
+function(err, results){
+    async.series([
+        function(callback){
+            // once there is some data and the directory exists,
+            // write the data to a file in the directory
+        },
+        function(callback){
+            // once the file is written let's email a link to it...
+        }
+    ]);
+});
+```
+
+For a complicated series of async tasks using the auto function makes adding
+new tasks much easier and makes the code more readable.
+
+
+---------------------------------------
+
+<a name="iterator" />
+### iterator(tasks)
+
+Creates an iterator function which calls the next function in the array,
+returning a continuation to call the next one after that. It's also possible to
+'peek' the next iterator by doing iterator.next().
+
+This function is used internally by the async module but can be useful when
+you want to manually control the flow of functions in series.
+
+__Arguments__
+
+* tasks - An array of functions to run.
+
+__Example__
+
+```js
+var iterator = async.iterator([
+    function(){ sys.p('one'); },
+    function(){ sys.p('two'); },
+    function(){ sys.p('three'); }
+]);
+
+node> var iterator2 = iterator();
+'one'
+node> var iterator3 = iterator2();
+'two'
+node> iterator3();
+'three'
+node> var nextfn = iterator2.next();
+node> nextfn();
+'three'
+```
+
+---------------------------------------
+
+<a name="apply" />
+### apply(function, arguments..)
+
+Creates a continuation function with some arguments already applied, a useful
+shorthand when combined with other control flow functions. Any arguments
+passed to the returned function are added to the arguments originally passed
+to apply.
+
+__Arguments__
+
+* function - The function you want to eventually apply all arguments to.
+* arguments... - Any number of arguments to automatically apply when the
+  continuation is called.
+
+__Example__
+
+```js
+// using apply
+
+async.parallel([
+    async.apply(fs.writeFile, 'testfile1', 'test1'),
+    async.apply(fs.writeFile, 'testfile2', 'test2'),
+]);
+
+
+// the same process without using apply
+
+async.parallel([
+    function(callback){
+        fs.writeFile('testfile1', 'test1', callback);
+    },
+    function(callback){
+        fs.writeFile('testfile2', 'test2', callback);
+    }
+]);
+```
+
+It's possible to pass any number of additional arguments when calling the
+continuation:
+
+```js
+node> var fn = async.apply(sys.puts, 'one');
+node> fn('two', 'three');
+one
+two
+three
+```
+
+---------------------------------------
+
+<a name="nextTick" />
+### nextTick(callback)
+
+Calls the callback on a later loop around the event loop. In node.js this just
+calls process.nextTick, in the browser it falls back to setImmediate(callback)
+if available, otherwise setTimeout(callback, 0), which means other higher priority
+events may precede the execution of the callback.
+
+This is used internally for browser-compatibility purposes.
+
+__Arguments__
+
+* callback - The function to call on a later loop around the event loop.
+
+__Example__
+
+```js
+var call_order = [];
+async.nextTick(function(){
+    call_order.push('two');
+    // call_order now equals ['one','two']
+});
+call_order.push('one')
+```
+
+<a name="times" />
+### times(n, callback)
+
+Calls the callback n times and accumulates results in the same manner
+you would use with async.map.
+
+__Arguments__
+
+* n - The number of times to run the function.
+* callback - The function to call n times.
+
+__Example__
+
+```js
+// Pretend this is some complicated async factory
+var createUser = function(id, callback) {
+  callback(null, {
+    id: 'user' + id
+  })
+}
+// generate 5 users
+async.times(5, function(n, next){
+    createUser(n, function(err, user) {
+      next(err, user)
+    })
+}, function(err, users) {
+  // we should now have 5 users
+});
+```
+
+<a name="timesSeries" />
+### timesSeries(n, callback)
+
+The same as times only the iterator is applied to each item in the array in
+series. The next iterator is only called once the current one has completed
+processing. The results array will be in the same order as the original.
+
+
+## Utils
+
+<a name="memoize" />
+### memoize(fn, [hasher])
+
+Caches the results of an async function. When creating a hash to store function
+results against, the callback is omitted from the hash and an optional hash
+function can be used.
+
+The cache of results is exposed as the `memo` property of the function returned
+by `memoize`.
+
+__Arguments__
+
+* fn - the function you to proxy and cache results from.
+* hasher - an optional function for generating a custom hash for storing
+  results, it has all the arguments applied to it apart from the callback, and
+  must be synchronous.
+
+__Example__
+
+```js
+var slow_fn = function (name, callback) {
+    // do something
+    callback(null, result);
+};
+var fn = async.memoize(slow_fn);
+
+// fn can now be used as if it were slow_fn
+fn('some name', function () {
+    // callback
+});
+```
+
+<a name="unmemoize" />
+### unmemoize(fn)
+
+Undoes a memoized function, reverting it to the original, unmemoized
+form. Comes handy in tests.
+
+__Arguments__
+
+* fn - the memoized function
+
+<a name="log" />
+### log(function, arguments)
+
+Logs the result of an async function to the console. Only works in node.js or
+in browsers that support console.log and console.error (such as FF and Chrome).
+If multiple arguments are returned from the async function, console.log is
+called on each argument in order.
+
+__Arguments__
+
+* function - The function you want to eventually apply all arguments to.
+* arguments... - Any number of arguments to apply to the function.
+
+__Example__
+
+```js
+var hello = function(name, callback){
+    setTimeout(function(){
+        callback(null, 'hello ' + name);
+    }, 1000);
+};
+```
+```js
+node> async.log(hello, 'world');
+'hello world'
+```
+
+---------------------------------------
+
+<a name="dir" />
+### dir(function, arguments)
+
+Logs the result of an async function to the console using console.dir to
+display the properties of the resulting object. Only works in node.js or
+in browsers that support console.dir and console.error (such as FF and Chrome).
+If multiple arguments are returned from the async function, console.dir is
+called on each argument in order.
+
+__Arguments__
+
+* function - The function you want to eventually apply all arguments to.
+* arguments... - Any number of arguments to apply to the function.
+
+__Example__
+
+```js
+var hello = function(name, callback){
+    setTimeout(function(){
+        callback(null, {hello: name});
+    }, 1000);
+};
+```
+```js
+node> async.dir(hello, 'world');
+{hello: 'world'}
+```
+
+---------------------------------------
+
+<a name="noConflict" />
+### noConflict()
+
+Changes the value of async back to its original value, returning a reference to the
+async object.
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/node_modules/async/component.json b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/node_modules/async/component.json
new file mode 100644
index 0000000..bbb0115
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/node_modules/async/component.json
@@ -0,0 +1,11 @@
+{
+  "name": "async",
+  "repo": "caolan/async",
+  "description": "Higher-order functions and common patterns for asynchronous code",
+  "version": "0.1.23",
+  "keywords": [],
+  "dependencies": {},
+  "development": {},
+  "main": "lib/async.js",
+  "scripts": [ "lib/async.js" ]
+}
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/node_modules/async/lib/async.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/node_modules/async/lib/async.js
new file mode 100755
index 0000000..1eebb15
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/node_modules/async/lib/async.js
@@ -0,0 +1,958 @@
+/*global setImmediate: false, setTimeout: false, console: false */
+(function () {
+
+    var async = {};
+
+    // global on the server, window in the browser
+    var root, previous_async;
+
+    root = this;
+    if (root != null) {
+      previous_async = root.async;
+    }
+
+    async.noConflict = function () {
+        root.async = previous_async;
+        return async;
+    };
+
+    function only_once(fn) {
+        var called = false;
+        return function() {
+            if (called) throw new Error("Callback was already called.");
+            called = true;
+            fn.apply(root, arguments);
+        }
+    }
+
+    //// cross-browser compatiblity functions ////
+
+    var _each = function (arr, iterator) {
+        if (arr.forEach) {
+            return arr.forEach(iterator);
+        }
+        for (var i = 0; i < arr.length; i += 1) {
+            iterator(arr[i], i, arr);
+        }
+    };
+
+    var _map = function (arr, iterator) {
+        if (arr.map) {
+            return arr.map(iterator);
+        }
+        var results = [];
+        _each(arr, function (x, i, a) {
+            results.push(iterator(x, i, a));
+        });
+        return results;
+    };
+
+    var _reduce = function (arr, iterator, memo) {
+        if (arr.reduce) {
+            return arr.reduce(iterator, memo);
+        }
+        _each(arr, function (x, i, a) {
+            memo = iterator(memo, x, i, a);
+        });
+        return memo;
+    };
+
+    var _keys = function (obj) {
+        if (Object.keys) {
+            return Object.keys(obj);
+        }
+        var keys = [];
+        for (var k in obj) {
+            if (obj.hasOwnProperty(k)) {
+                keys.push(k);
+            }
+        }
+        return keys;
+    };
+
+    //// exported async module functions ////
+
+    //// nextTick implementation with browser-compatible fallback ////
+    if (typeof process === 'undefined' || !(process.nextTick)) {
+        if (typeof setImmediate === 'function') {
+            async.nextTick = function (fn) {
+                // not a direct alias for IE10 compatibility
+                setImmediate(fn);
+            };
+            async.setImmediate = async.nextTick;
+        }
+        else {
+            async.nextTick = function (fn) {
+                setTimeout(fn, 0);
+            };
+            async.setImmediate = async.nextTick;
+        }
+    }
+    else {
+        async.nextTick = process.nextTick;
+        if (typeof setImmediate !== 'undefined') {
+            async.setImmediate = function (fn) {
+              // not a direct alias for IE10 compatibility
+              setImmediate(fn);
+            };
+        }
+        else {
+            async.setImmediate = async.nextTick;
+        }
+    }
+
+    async.each = function (arr, iterator, callback) {
+        callback = callback || function () {};
+        if (!arr.length) {
+            return callback();
+        }
+        var completed = 0;
+        _each(arr, function (x) {
+            iterator(x, only_once(function (err) {
+                if (err) {
+                    callback(err);
+                    callback = function () {};
+                }
+                else {
+                    completed += 1;
+                    if (completed >= arr.length) {
+                        callback(null);
+                    }
+                }
+            }));
+        });
+    };
+    async.forEach = async.each;
+
+    async.eachSeries = function (arr, iterator, callback) {
+        callback = callback || function () {};
+        if (!arr.length) {
+            return callback();
+        }
+        var completed = 0;
+        var iterate = function () {
+            iterator(arr[completed], function (err) {
+                if (err) {
+                    callback(err);
+                    callback = function () {};
+                }
+                else {
+                    completed += 1;
+                    if (completed >= arr.length) {
+                        callback(null);
+                    }
+                    else {
+                        iterate();
+                    }
+                }
+            });
+        };
+        iterate();
+    };
+    async.forEachSeries = async.eachSeries;
+
+    async.eachLimit = function (arr, limit, iterator, callback) {
+        var fn = _eachLimit(limit);
+        fn.apply(null, [arr, iterator, callback]);
+    };
+    async.forEachLimit = async.eachLimit;
+
+    var _eachLimit = function (limit) {
+
+        return function (arr, iterator, callback) {
+            callback = callback || function () {};
+            if (!arr.length || limit <= 0) {
+                return callback();
+            }
+            var completed = 0;
+            var started = 0;
+            var running = 0;
+
+            (function replenish () {
+                if (completed >= arr.length) {
+                    return callback();
+                }
+
+                while (running < limit && started < arr.length) {
+                    started += 1;
+                    running += 1;
+                    iterator(arr[started - 1], function (err) {
+                        if (err) {
+                            callback(err);
+                            callback = function () {};
+                        }
+                        else {
+                            completed += 1;
+                            running -= 1;
+                            if (completed >= arr.length) {
+                                callback();
+                            }
+                            else {
+                                replenish();
+                            }
+                        }
+                    });
+                }
+            })();
+        };
+    };
+
+
+    var doParallel = function (fn) {
+        return function () {
+            var args = Array.prototype.slice.call(arguments);
+            return fn.apply(null, [async.each].concat(args));
+        };
+    };
+    var doParallelLimit = function(limit, fn) {
+        return function () {
+            var args = Array.prototype.slice.call(arguments);
+            return fn.apply(null, [_eachLimit(limit)].concat(args));
+        };
+    };
+    var doSeries = function (fn) {
+        return function () {
+            var args = Array.prototype.slice.call(arguments);
+            return fn.apply(null, [async.eachSeries].concat(args));
+        };
+    };
+
+
+    var _asyncMap = function (eachfn, arr, iterator, callback) {
+        var results = [];
+        arr = _map(arr, function (x, i) {
+            return {index: i, value: x};
+        });
+        eachfn(arr, function (x, callback) {
+            iterator(x.value, function (err, v) {
+                results[x.index] = v;
+                callback(err);
+            });
+        }, function (err) {
+            callback(err, results);
+        });
+    };
+    async.map = doParallel(_asyncMap);
+    async.mapSeries = doSeries(_asyncMap);
+    async.mapLimit = function (arr, limit, iterator, callback) {
+        return _mapLimit(limit)(arr, iterator, callback);
+    };
+
+    var _mapLimit = function(limit) {
+        return doParallelLimit(limit, _asyncMap);
+    };
+
+    // reduce only has a series version, as doing reduce in parallel won't
+    // work in many situations.
+    async.reduce = function (arr, memo, iterator, callback) {
+        async.eachSeries(arr, function (x, callback) {
+            iterator(memo, x, function (err, v) {
+                memo = v;
+                callback(err);
+            });
+        }, function (err) {
+            callback(err, memo);
+        });
+    };
+    // inject alias
+    async.inject = async.reduce;
+    // foldl alias
+    async.foldl = async.reduce;
+
+    async.reduceRight = function (arr, memo, iterator, callback) {
+        var reversed = _map(arr, function (x) {
+            return x;
+        }).reverse();
+        async.reduce(reversed, memo, iterator, callback);
+    };
+    // foldr alias
+    async.foldr = async.reduceRight;
+
+    var _filter = function (eachfn, arr, iterator, callback) {
+        var results = [];
+        arr = _map(arr, function (x, i) {
+            return {index: i, value: x};
+        });
+        eachfn(arr, function (x, callback) {
+            iterator(x.value, function (v) {
+                if (v) {
+                    results.push(x);
+                }
+                callback();
+            });
+        }, function (err) {
+            callback(_map(results.sort(function (a, b) {
+                return a.index - b.index;
+            }), function (x) {
+                return x.value;
+            }));
+        });
+    };
+    async.filter = doParallel(_filter);
+    async.filterSeries = doSeries(_filter);
+    // select alias
+    async.select = async.filter;
+    async.selectSeries = async.filterSeries;
+
+    var _reject = function (eachfn, arr, iterator, callback) {
+        var results = [];
+        arr = _map(arr, function (x, i) {
+            return {index: i, value: x};
+        });
+        eachfn(arr, function (x, callback) {
+            iterator(x.value, function (v) {
+                if (!v) {
+                    results.push(x);
+                }
+                callback();
+            });
+        }, function (err) {
+            callback(_map(results.sort(function (a, b) {
+                return a.index - b.index;
+            }), function (x) {
+                return x.value;
+            }));
+        });
+    };
+    async.reject = doParallel(_reject);
+    async.rejectSeries = doSeries(_reject);
+
+    var _detect = function (eachfn, arr, iterator, main_callback) {
+        eachfn(arr, function (x, callback) {
+            iterator(x, function (result) {
+                if (result) {
+                    main_callback(x);
+                    main_callback = function () {};
+                }
+                else {
+                    callback();
+                }
+            });
+        }, function (err) {
+            main_callback();
+        });
+    };
+    async.detect = doParallel(_detect);
+    async.detectSeries = doSeries(_detect);
+
+    async.some = function (arr, iterator, main_callback) {
+        async.each(arr, function (x, callback) {
+            iterator(x, function (v) {
+                if (v) {
+                    main_callback(true);
+                    main_callback = function () {};
+                }
+                callback();
+            });
+        }, function (err) {
+            main_callback(false);
+        });
+    };
+    // any alias
+    async.any = async.some;
+
+    async.every = function (arr, iterator, main_callback) {
+        async.each(arr, function (x, callback) {
+            iterator(x, function (v) {
+                if (!v) {
+                    main_callback(false);
+                    main_callback = function () {};
+                }
+                callback();
+            });
+        }, function (err) {
+            main_callback(true);
+        });
+    };
+    // all alias
+    async.all = async.every;
+
+    async.sortBy = function (arr, iterator, callback) {
+        async.map(arr, function (x, callback) {
+            iterator(x, function (err, criteria) {
+                if (err) {
+                    callback(err);
+                }
+                else {
+                    callback(null, {value: x, criteria: criteria});
+                }
+            });
+        }, function (err, results) {
+            if (err) {
+                return callback(err);
+            }
+            else {
+                var fn = function (left, right) {
+                    var a = left.criteria, b = right.criteria;
+                    return a < b ? -1 : a > b ? 1 : 0;
+                };
+                callback(null, _map(results.sort(fn), function (x) {
+                    return x.value;
+                }));
+            }
+        });
+    };
+
+    async.auto = function (tasks, callback) {
+        callback = callback || function () {};
+        var keys = _keys(tasks);
+        if (!keys.length) {
+            return callback(null);
+        }
+
+        var results = {};
+
+        var listeners = [];
+        var addListener = function (fn) {
+            listeners.unshift(fn);
+        };
+        var removeListener = function (fn) {
+            for (var i = 0; i < listeners.length; i += 1) {
+                if (listeners[i] === fn) {
+                    listeners.splice(i, 1);
+                    return;
+                }
+            }
+        };
+        var taskComplete = function () {
+            _each(listeners.slice(0), function (fn) {
+                fn();
+            });
+        };
+
+        addListener(function () {
+            if (_keys(results).length === keys.length) {
+                callback(null, results);
+                callback = function () {};
+            }
+        });
+
+        _each(keys, function (k) {
+            var task = (tasks[k] instanceof Function) ? [tasks[k]]: tasks[k];
+            var taskCallback = function (err) {
+                var args = Array.prototype.slice.call(arguments, 1);
+                if (args.length <= 1) {
+                    args = args[0];
+                }
+                if (err) {
+                    var safeResults = {};
+                    _each(_keys(results), function(rkey) {
+                        safeResults[rkey] = results[rkey];
+                    });
+                    safeResults[k] = args;
+                    callback(err, safeResults);
+                    // stop subsequent errors hitting callback multiple times
+                    callback = function () {};
+                }
+                else {
+                    results[k] = args;
+                    async.setImmediate(taskComplete);
+                }
+            };
+            var requires = task.slice(0, Math.abs(task.length - 1)) || [];
+            var ready = function () {
+                return _reduce(requires, function (a, x) {
+                    return (a && results.hasOwnProperty(x));
+                }, true) && !results.hasOwnProperty(k);
+            };
+            if (ready()) {
+                task[task.length - 1](taskCallback, results);
+            }
+            else {
+                var listener = function () {
+                    if (ready()) {
+                        removeListener(listener);
+                        task[task.length - 1](taskCallback, results);
+                    }
+                };
+                addListener(listener);
+            }
+        });
+    };
+
+    async.waterfall = function (tasks, callback) {
+        callback = callback || function () {};
+        if (tasks.constructor !== Array) {
+          var err = new Error('First argument to waterfall must be an array of functions');
+          return callback(err);
+        }
+        if (!tasks.length) {
+            return callback();
+        }
+        var wrapIterator = function (iterator) {
+            return function (err) {
+                if (err) {
+                    callback.apply(null, arguments);
+                    callback = function () {};
+                }
+                else {
+                    var args = Array.prototype.slice.call(arguments, 1);
+                    var next = iterator.next();
+                    if (next) {
+                        args.push(wrapIterator(next));
+                    }
+                    else {
+                        args.push(callback);
+                    }
+                    async.setImmediate(function () {
+                        iterator.apply(null, args);
+                    });
+                }
+            };
+        };
+        wrapIterator(async.iterator(tasks))();
+    };
+
+    var _parallel = function(eachfn, tasks, callback) {
+        callback = callback || function () {};
+        if (tasks.constructor === Array) {
+            eachfn.map(tasks, function (fn, callback) {
+                if (fn) {
+                    fn(function (err) {
+                        var args = Array.prototype.slice.call(arguments, 1);
+                        if (args.length <= 1) {
+                            args = args[0];
+                        }
+                        callback.call(null, err, args);
+                    });
+                }
+            }, callback);
+        }
+        else {
+            var results = {};
+            eachfn.each(_keys(tasks), function (k, callback) {
+                tasks[k](function (err) {
+                    var args = Array.prototype.slice.call(arguments, 1);
+                    if (args.length <= 1) {
+                        args = args[0];
+                    }
+                    results[k] = args;
+                    callback(err);
+                });
+            }, function (err) {
+                callback(err, results);
+            });
+        }
+    };
+
+    async.parallel = function (tasks, callback) {
+        _parallel({ map: async.map, each: async.each }, tasks, callback);
+    };
+
+    async.parallelLimit = function(tasks, limit, callback) {
+        _parallel({ map: _mapLimit(limit), each: _eachLimit(limit) }, tasks, callback);
+    };
+
+    async.series = function (tasks, callback) {
+        callback = callback || function () {};
+        if (tasks.constructor === Array) {
+            async.mapSeries(tasks, function (fn, callback) {
+                if (fn) {
+                    fn(function (err) {
+                        var args = Array.prototype.slice.call(arguments, 1);
+                        if (args.length <= 1) {
+                            args = args[0];
+                        }
+                        callback.call(null, err, args);
+                    });
+                }
+            }, callback);
+        }
+        else {
+            var results = {};
+            async.eachSeries(_keys(tasks), function (k, callback) {
+                tasks[k](function (err) {
+                    var args = Array.prototype.slice.call(arguments, 1);
+                    if (args.length <= 1) {
+                        args = args[0];
+                    }
+                    results[k] = args;
+                    callback(err);
+                });
+            }, function (err) {
+                callback(err, results);
+            });
+        }
+    };
+
+    async.iterator = function (tasks) {
+        var makeCallback = function (index) {
+            var fn = function () {
+                if (tasks.length) {
+                    tasks[index].apply(null, arguments);
+                }
+                return fn.next();
+            };
+            fn.next = function () {
+                return (index < tasks.length - 1) ? makeCallback(index + 1): null;
+            };
+            return fn;
+        };
+        return makeCallback(0);
+    };
+
+    async.apply = function (fn) {
+        var args = Array.prototype.slice.call(arguments, 1);
+        return function () {
+            return fn.apply(
+                null, args.concat(Array.prototype.slice.call(arguments))
+            );
+        };
+    };
+
+    var _concat = function (eachfn, arr, fn, callback) {
+        var r = [];
+        eachfn(arr, function (x, cb) {
+            fn(x, function (err, y) {
+                r = r.concat(y || []);
+                cb(err);
+            });
+        }, function (err) {
+            callback(err, r);
+        });
+    };
+    async.concat = doParallel(_concat);
+    async.concatSeries = doSeries(_concat);
+
+    async.whilst = function (test, iterator, callback) {
+        if (test()) {
+            iterator(function (err) {
+                if (err) {
+                    return callback(err);
+                }
+                async.whilst(test, iterator, callback);
+            });
+        }
+        else {
+            callback();
+        }
+    };
+
+    async.doWhilst = function (iterator, test, callback) {
+        iterator(function (err) {
+            if (err) {
+                return callback(err);
+            }
+            if (test()) {
+                async.doWhilst(iterator, test, callback);
+            }
+            else {
+                callback();
+            }
+        });
+    };
+
+    async.until = function (test, iterator, callback) {
+        if (!test()) {
+            iterator(function (err) {
+                if (err) {
+                    return callback(err);
+                }
+                async.until(test, iterator, callback);
+            });
+        }
+        else {
+            callback();
+        }
+    };
+
+    async.doUntil = function (iterator, test, callback) {
+        iterator(function (err) {
+            if (err) {
+                return callback(err);
+            }
+            if (!test()) {
+                async.doUntil(iterator, test, callback);
+            }
+            else {
+                callback();
+            }
+        });
+    };
+
+    async.queue = function (worker, concurrency) {
+        if (concurrency === undefined) {
+            concurrency = 1;
+        }
+        function _insert(q, data, pos, callback) {
+          if(data.constructor !== Array) {
+              data = [data];
+          }
+          _each(data, function(task) {
+              var item = {
+                  data: task,
+                  callback: typeof callback === 'function' ? callback : null
+              };
+
+              if (pos) {
+                q.tasks.unshift(item);
+              } else {
+                q.tasks.push(item);
+              }
+
+              if (q.saturated && q.tasks.length === concurrency) {
+                  q.saturated();
+              }
+              async.setImmediate(q.process);
+          });
+        }
+
+        var workers = 0;
+        var q = {
+            tasks: [],
+            concurrency: concurrency,
+            saturated: null,
+            empty: null,
+            drain: null,
+            push: function (data, callback) {
+              _insert(q, data, false, callback);
+            },
+            unshift: function (data, callback) {
+              _insert(q, data, true, callback);
+            },
+            process: function () {
+                if (workers < q.concurrency && q.tasks.length) {
+                    var task = q.tasks.shift();
+                    if (q.empty && q.tasks.length === 0) {
+                        q.empty();
+                    }
+                    workers += 1;
+                    var next = function () {
+                        workers -= 1;
+                        if (task.callback) {
+                            task.callback.apply(task, arguments);
+                        }
+                        if (q.drain && q.tasks.length + workers === 0) {
+                            q.drain();
+                        }
+                        q.process();
+                    };
+                    var cb = only_once(next);
+                    worker(task.data, cb);
+                }
+            },
+            length: function () {
+                return q.tasks.length;
+            },
+            running: function () {
+                return workers;
+            }
+        };
+        return q;
+    };
+
+    async.cargo = function (worker, payload) {
+        var working     = false,
+            tasks       = [];
+
+        var cargo = {
+            tasks: tasks,
+            payload: payload,
+            saturated: null,
+            empty: null,
+            drain: null,
+            push: function (data, callback) {
+                if(data.constructor !== Array) {
+                    data = [data];
+                }
+                _each(data, function(task) {
+                    tasks.push({
+                        data: task,
+                        callback: typeof callback === 'function' ? callback : null
+                    });
+                    if (cargo.saturated && tasks.length === payload) {
+                        cargo.saturated();
+                    }
+                });
+                async.setImmediate(cargo.process);
+            },
+            process: function process() {
+                if (working) return;
+                if (tasks.length === 0) {
+                    if(cargo.drain) cargo.drain();
+                    return;
+                }
+
+                var ts = typeof payload === 'number'
+                            ? tasks.splice(0, payload)
+                            : tasks.splice(0);
+
+                var ds = _map(ts, function (task) {
+                    return task.data;
+                });
+
+                if(cargo.empty) cargo.empty();
+                working = true;
+                worker(ds, function () {
+                    working = false;
+
+                    var args = arguments;
+                    _each(ts, function (data) {
+                        if (data.callback) {
+                            data.callback.apply(null, args);
+                        }
+                    });
+
+                    process();
+                });
+            },
+            length: function () {
+                return tasks.length;
+            },
+            running: function () {
+                return working;
+            }
+        };
+        return cargo;
+    };
+
+    var _console_fn = function (name) {
+        return function (fn) {
+            var args = Array.prototype.slice.call(arguments, 1);
+            fn.apply(null, args.concat([function (err) {
+                var args = Array.prototype.slice.call(arguments, 1);
+                if (typeof console !== 'undefined') {
+                    if (err) {
+                        if (console.error) {
+                            console.error(err);
+                        }
+                    }
+                    else if (console[name]) {
+                        _each(args, function (x) {
+                            console[name](x);
+                        });
+                    }
+                }
+            }]));
+        };
+    };
+    async.log = _console_fn('log');
+    async.dir = _console_fn('dir');
+    /*async.info = _console_fn('info');
+    async.warn = _console_fn('warn');
+    async.error = _console_fn('error');*/
+
+    async.memoize = function (fn, hasher) {
+        var memo = {};
+        var queues = {};
+        hasher = hasher || function (x) {
+            return x;
+        };
+        var memoized = function () {
+            var args = Array.prototype.slice.call(arguments);
+            var callback = args.pop();
+            var key = hasher.apply(null, args);
+            if (key in memo) {
+                callback.apply(null, memo[key]);
+            }
+            else if (key in queues) {
+                queues[key].push(callback);
+            }
+            else {
+                queues[key] = [callback];
+                fn.apply(null, args.concat([function () {
+                    memo[key] = arguments;
+                    var q = queues[key];
+                    delete queues[key];
+                    for (var i = 0, l = q.length; i < l; i++) {
+                      q[i].apply(null, arguments);
+                    }
+                }]));
+            }
+        };
+        memoized.memo = memo;
+        memoized.unmemoized = fn;
+        return memoized;
+    };
+
+    async.unmemoize = function (fn) {
+      return function () {
+        return (fn.unmemoized || fn).apply(null, arguments);
+      };
+    };
+
+    async.times = function (count, iterator, callback) {
+        var counter = [];
+        for (var i = 0; i < count; i++) {
+            counter.push(i);
+        }
+        return async.map(counter, iterator, callback);
+    };
+
+    async.timesSeries = function (count, iterator, callback) {
+        var counter = [];
+        for (var i = 0; i < count; i++) {
+            counter.push(i);
+        }
+        return async.mapSeries(counter, iterator, callback);
+    };
+
+    async.compose = function (/* functions... */) {
+        var fns = Array.prototype.reverse.call(arguments);
+        return function () {
+            var that = this;
+            var args = Array.prototype.slice.call(arguments);
+            var callback = args.pop();
+            async.reduce(fns, args, function (newargs, fn, cb) {
+                fn.apply(that, newargs.concat([function () {
+                    var err = arguments[0];
+                    var nextargs = Array.prototype.slice.call(arguments, 1);
+                    cb(err, nextargs);
+                }]))
+            },
+            function (err, results) {
+                callback.apply(that, [err].concat(results));
+            });
+        };
+    };
+
+    var _applyEach = function (eachfn, fns /*args...*/) {
+        var go = function () {
+            var that = this;
+            var args = Array.prototype.slice.call(arguments);
+            var callback = args.pop();
+            return eachfn(fns, function (fn, cb) {
+                fn.apply(that, args.concat([cb]));
+            },
+            callback);
+        };
+        if (arguments.length > 2) {
+            var args = Array.prototype.slice.call(arguments, 2);
+            return go.apply(this, args);
+        }
+        else {
+            return go;
+        }
+    };
+    async.applyEach = doParallel(_applyEach);
+    async.applyEachSeries = doSeries(_applyEach);
+
+    async.forever = function (fn, callback) {
+        function next(err) {
+            if (err) {
+                if (callback) {
+                    return callback(err);
+                }
+                throw err;
+            }
+            fn(next);
+        }
+        next();
+    };
+
+    // AMD / RequireJS
+    if (typeof define !== 'undefined' && define.amd) {
+        define([], function () {
+            return async;
+        });
+    }
+    // Node.js
+    else if (typeof module !== 'undefined' && module.exports) {
+        module.exports = async;
+    }
+    // included directly via <script> tag
+    else {
+        root.async = async;
+    }
+
+}());
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/node_modules/async/package.json b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/node_modules/async/package.json
new file mode 100644
index 0000000..5648629
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/node_modules/async/package.json
@@ -0,0 +1,45 @@
+{
+  "name": "async",
+  "description": "Higher-order functions and common patterns for asynchronous code",
+  "main": "./lib/async",
+  "author": {
+    "name": "Caolan McMahon"
+  },
+  "version": "0.2.10",
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/caolan/async.git"
+  },
+  "bugs": {
+    "url": "https://github.com/caolan/async/issues"
+  },
+  "licenses": [
+    {
+      "type": "MIT",
+      "url": "https://github.com/caolan/async/raw/master/LICENSE"
+    }
+  ],
+  "devDependencies": {
+    "nodeunit": ">0.0.0",
+    "uglify-js": "1.2.x",
+    "nodelint": ">0.0.0"
+  },
+  "jam": {
+    "main": "lib/async.js",
+    "include": [
+      "lib/async.js",
+      "README.md",
+      "LICENSE"
+    ]
+  },
+  "scripts": {
+    "test": "nodeunit test/test-async.js"
+  },
+  "readme": "# Async.js\n\nAsync is a utility module which provides straight-forward, powerful functions\nfor working with asynchronous JavaScript. Although originally designed for\nuse with [node.js](http://nodejs.org), it can also be used directly in the\nbrowser. Also supports [component](https://github.com/component/component).\n\nAsync provides around 20 functions that include the usual 'functional'\nsuspects (map, reduce, filter, each…) as well as some common patterns\nfor asynchronous control flow (parallel, series, waterfall…). All these\nfunctions assume you follow the node.js convention of providing a single\ncallback as the last argument of your async function.\n\n\n## Quick Examples\n\n```javascript\nasync.map(['file1','file2','file3'], fs.stat, function(err, results){\n    // results is now an array of stats for each file\n});\n\nasync.filter(['file1','file2','file3'], fs.exists, function(results){\n    // results now equals an array of the existing files\n});\n\nasync.parallel([\n    function(){ ... },\n    function(){ ... }\n], callback);\n\nasync.series([\n    function(){ ... },\n    function(){ ... }\n]);\n```\n\nThere are many more functions available so take a look at the docs below for a\nfull list. This module aims to be comprehensive, so if you feel anything is\nmissing please create a GitHub issue for it.\n\n## Common Pitfalls\n\n### Binding a context to an iterator\n\nThis section is really about bind, not about async. If you are wondering how to\nmake async execute your iterators in a given context, or are confused as to why\na method of another library isn't working as an iterator, study this example:\n\n```js\n// Here is a simple object with an (unnecessarily roundabout) squaring method\nvar AsyncSquaringLibrary = {\n  squareExponent: 2,\n  square: function(number, callback){ \n    var result = Math.pow(number, this.squareExponent);\n    setTimeout(function(){\n      callback(null, result);\n    }, 200);\n  }\n};\n\nasync.map([1, 2, 3], AsyncSquaringLibrary.square, function(err, result){\n  // result is [NaN, NaN, NaN]\n  // This fails because the `this.squareExponent` expression in the square\n  // function is not evaluated in the context of AsyncSquaringLibrary, and is\n  // therefore undefined.\n});\n\nasync.map([1, 2, 3], AsyncSquaringLibrary.square.bind(AsyncSquaringLibrary), function(err, result){\n  // result is [1, 4, 9]\n  // With the help of bind we can attach a context to the iterator before\n  // passing it to async. Now the square function will be executed in its \n  // 'home' AsyncSquaringLibrary context and the value of `this.squareExponent`\n  // will be as expected.\n});\n```\n\n## Download\n\nThe source is available for download from\n[GitHub](http://github.com/caolan/async).\nAlternatively, you can install using Node Package Manager (npm):\n\n    npm install async\n\n__Development:__ [async.js](https://github.com/caolan/async/raw/master/lib/async.js) - 29.6kb Uncompressed\n\n## In the Browser\n\nSo far it's been tested in IE6, IE7, IE8, FF3.6 and Chrome 5. Usage:\n\n```html\n<script type=\"text/javascript\" src=\"async.js\"></script>\n<script type=\"text/javascript\">\n\n    async.map(data, asyncProcess, function(err, results){\n        alert(results);\n    });\n\n</script>\n```\n\n## Documentation\n\n### Collections\n\n* [each](#each)\n* [eachSeries](#eachSeries)\n* [eachLimit](#eachLimit)\n* [map](#map)\n* [mapSeries](#mapSeries)\n* [mapLimit](#mapLimit)\n* [filter](#filter)\n* [filterSeries](#filterSeries)\n* [reject](#reject)\n* [rejectSeries](#rejectSeries)\n* [reduce](#reduce)\n* [reduceRight](#reduceRight)\n* [detect](#detect)\n* [detectSeries](#detectSeries)\n* [sortBy](#sortBy)\n* [some](#some)\n* [every](#every)\n* [concat](#concat)\n* [concatSeries](#concatSeries)\n\n### Control Flow\n\n* [series](#series)\n* [parallel](#parallel)\n* [parallelLimit](#parallellimittasks-limit-callback)\n* [whilst](#whilst)\n* [doWhilst](#doWhilst)\n* [until](#until)\n* [doUntil](#doUntil)\n* [forever](#forever)\n* [waterfall](#waterfall)\n* [compose](#compose)\n* [applyEach](#applyEach)\n* [applyEachSeries](#applyEachSeries)\n* [queue](#queue)\n* [cargo](#cargo)\n* [auto](#auto)\n* [iterator](#iterator)\n* [apply](#apply)\n* [nextTick](#nextTick)\n* [times](#times)\n* [timesSeries](#timesSeries)\n\n### Utils\n\n* [memoize](#memoize)\n* [unmemoize](#unmemoize)\n* [log](#log)\n* [dir](#dir)\n* [noConflict](#noConflict)\n\n\n## Collections\n\n<a name=\"forEach\" />\n<a name=\"each\" />\n### each(arr, iterator, callback)\n\nApplies an iterator function to each item in an array, in parallel.\nThe iterator is called with an item from the list and a callback for when it\nhas finished. If the iterator passes an error to this callback, the main\ncallback for the each function is immediately called with the error.\n\nNote, that since this function applies the iterator to each item in parallel\nthere is no guarantee that the iterator functions will complete in order.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* iterator(item, callback) - A function to apply to each item in the array.\n  The iterator is passed a callback(err) which must be called once it has \n  completed. If no error has occured, the callback should be run without \n  arguments or with an explicit null argument.\n* callback(err) - A callback which is called after all the iterator functions\n  have finished, or an error has occurred.\n\n__Example__\n\n```js\n// assuming openFiles is an array of file names and saveFile is a function\n// to save the modified contents of that file:\n\nasync.each(openFiles, saveFile, function(err){\n    // if any of the saves produced an error, err would equal that error\n});\n```\n\n---------------------------------------\n\n<a name=\"forEachSeries\" />\n<a name=\"eachSeries\" />\n### eachSeries(arr, iterator, callback)\n\nThe same as each only the iterator is applied to each item in the array in\nseries. The next iterator is only called once the current one has completed\nprocessing. This means the iterator functions will complete in order.\n\n\n---------------------------------------\n\n<a name=\"forEachLimit\" />\n<a name=\"eachLimit\" />\n### eachLimit(arr, limit, iterator, callback)\n\nThe same as each only no more than \"limit\" iterators will be simultaneously \nrunning at any time.\n\nNote that the items are not processed in batches, so there is no guarantee that\n the first \"limit\" iterator functions will complete before any others are \nstarted.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* limit - The maximum number of iterators to run at any time.\n* iterator(item, callback) - A function to apply to each item in the array.\n  The iterator is passed a callback(err) which must be called once it has \n  completed. If no error has occured, the callback should be run without \n  arguments or with an explicit null argument.\n* callback(err) - A callback which is called after all the iterator functions\n  have finished, or an error has occurred.\n\n__Example__\n\n```js\n// Assume documents is an array of JSON objects and requestApi is a\n// function that interacts with a rate-limited REST api.\n\nasync.eachLimit(documents, 20, requestApi, function(err){\n    // if any of the saves produced an error, err would equal that error\n});\n```\n\n---------------------------------------\n\n<a name=\"map\" />\n### map(arr, iterator, callback)\n\nProduces a new array of values by mapping each value in the given array through\nthe iterator function. The iterator is called with an item from the array and a\ncallback for when it has finished processing. The callback takes 2 arguments, \nan error and the transformed item from the array. If the iterator passes an\nerror to this callback, the main callback for the map function is immediately\ncalled with the error.\n\nNote, that since this function applies the iterator to each item in parallel\nthere is no guarantee that the iterator functions will complete in order, however\nthe results array will be in the same order as the original array.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* iterator(item, callback) - A function to apply to each item in the array.\n  The iterator is passed a callback(err, transformed) which must be called once \n  it has completed with an error (which can be null) and a transformed item.\n* callback(err, results) - A callback which is called after all the iterator\n  functions have finished, or an error has occurred. Results is an array of the\n  transformed items from the original array.\n\n__Example__\n\n```js\nasync.map(['file1','file2','file3'], fs.stat, function(err, results){\n    // results is now an array of stats for each file\n});\n```\n\n---------------------------------------\n\n<a name=\"mapSeries\" />\n### mapSeries(arr, iterator, callback)\n\nThe same as map only the iterator is applied to each item in the array in\nseries. The next iterator is only called once the current one has completed\nprocessing. The results array will be in the same order as the original.\n\n\n---------------------------------------\n\n<a name=\"mapLimit\" />\n### mapLimit(arr, limit, iterator, callback)\n\nThe same as map only no more than \"limit\" iterators will be simultaneously \nrunning at any time.\n\nNote that the items are not processed in batches, so there is no guarantee that\n the first \"limit\" iterator functions will complete before any others are \nstarted.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* limit - The maximum number of iterators to run at any time.\n* iterator(item, callback) - A function to apply to each item in the array.\n  The iterator is passed a callback(err, transformed) which must be called once \n  it has completed with an error (which can be null) and a transformed item.\n* callback(err, results) - A callback which is called after all the iterator\n  functions have finished, or an error has occurred. Results is an array of the\n  transformed items from the original array.\n\n__Example__\n\n```js\nasync.mapLimit(['file1','file2','file3'], 1, fs.stat, function(err, results){\n    // results is now an array of stats for each file\n});\n```\n\n---------------------------------------\n\n<a name=\"filter\" />\n### filter(arr, iterator, callback)\n\n__Alias:__ select\n\nReturns a new array of all the values which pass an async truth test.\n_The callback for each iterator call only accepts a single argument of true or\nfalse, it does not accept an error argument first!_ This is in-line with the\nway node libraries work with truth tests like fs.exists. This operation is\nperformed in parallel, but the results array will be in the same order as the\noriginal.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* iterator(item, callback) - A truth test to apply to each item in the array.\n  The iterator is passed a callback(truthValue) which must be called with a \n  boolean argument once it has completed.\n* callback(results) - A callback which is called after all the iterator\n  functions have finished.\n\n__Example__\n\n```js\nasync.filter(['file1','file2','file3'], fs.exists, function(results){\n    // results now equals an array of the existing files\n});\n```\n\n---------------------------------------\n\n<a name=\"filterSeries\" />\n### filterSeries(arr, iterator, callback)\n\n__alias:__ selectSeries\n\nThe same as filter only the iterator is applied to each item in the array in\nseries. The next iterator is only called once the current one has completed\nprocessing. The results array will be in the same order as the original.\n\n---------------------------------------\n\n<a name=\"reject\" />\n### reject(arr, iterator, callback)\n\nThe opposite of filter. Removes values that pass an async truth test.\n\n---------------------------------------\n\n<a name=\"rejectSeries\" />\n### rejectSeries(arr, iterator, callback)\n\nThe same as reject, only the iterator is applied to each item in the array\nin series.\n\n\n---------------------------------------\n\n<a name=\"reduce\" />\n### reduce(arr, memo, iterator, callback)\n\n__aliases:__ inject, foldl\n\nReduces a list of values into a single value using an async iterator to return\neach successive step. Memo is the initial state of the reduction. This\nfunction only operates in series. For performance reasons, it may make sense to\nsplit a call to this function into a parallel map, then use the normal\nArray.prototype.reduce on the results. This function is for situations where\neach step in the reduction needs to be async, if you can get the data before\nreducing it then it's probably a good idea to do so.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* memo - The initial state of the reduction.\n* iterator(memo, item, callback) - A function applied to each item in the\n  array to produce the next step in the reduction. The iterator is passed a\n  callback(err, reduction) which accepts an optional error as its first \n  argument, and the state of the reduction as the second. If an error is \n  passed to the callback, the reduction is stopped and the main callback is \n  immediately called with the error.\n* callback(err, result) - A callback which is called after all the iterator\n  functions have finished. Result is the reduced value.\n\n__Example__\n\n```js\nasync.reduce([1,2,3], 0, function(memo, item, callback){\n    // pointless async:\n    process.nextTick(function(){\n        callback(null, memo + item)\n    });\n}, function(err, result){\n    // result is now equal to the last value of memo, which is 6\n});\n```\n\n---------------------------------------\n\n<a name=\"reduceRight\" />\n### reduceRight(arr, memo, iterator, callback)\n\n__Alias:__ foldr\n\nSame as reduce, only operates on the items in the array in reverse order.\n\n\n---------------------------------------\n\n<a name=\"detect\" />\n### detect(arr, iterator, callback)\n\nReturns the first value in a list that passes an async truth test. The\niterator is applied in parallel, meaning the first iterator to return true will\nfire the detect callback with that result. That means the result might not be\nthe first item in the original array (in terms of order) that passes the test.\n\nIf order within the original array is important then look at detectSeries.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* iterator(item, callback) - A truth test to apply to each item in the array.\n  The iterator is passed a callback(truthValue) which must be called with a \n  boolean argument once it has completed.\n* callback(result) - A callback which is called as soon as any iterator returns\n  true, or after all the iterator functions have finished. Result will be\n  the first item in the array that passes the truth test (iterator) or the\n  value undefined if none passed.\n\n__Example__\n\n```js\nasync.detect(['file1','file2','file3'], fs.exists, function(result){\n    // result now equals the first file in the list that exists\n});\n```\n\n---------------------------------------\n\n<a name=\"detectSeries\" />\n### detectSeries(arr, iterator, callback)\n\nThe same as detect, only the iterator is applied to each item in the array\nin series. This means the result is always the first in the original array (in\nterms of array order) that passes the truth test.\n\n\n---------------------------------------\n\n<a name=\"sortBy\" />\n### sortBy(arr, iterator, callback)\n\nSorts a list by the results of running each value through an async iterator.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* iterator(item, callback) - A function to apply to each item in the array.\n  The iterator is passed a callback(err, sortValue) which must be called once it\n  has completed with an error (which can be null) and a value to use as the sort\n  criteria.\n* callback(err, results) - A callback which is called after all the iterator\n  functions have finished, or an error has occurred. Results is the items from\n  the original array sorted by the values returned by the iterator calls.\n\n__Example__\n\n```js\nasync.sortBy(['file1','file2','file3'], function(file, callback){\n    fs.stat(file, function(err, stats){\n        callback(err, stats.mtime);\n    });\n}, function(err, results){\n    // results is now the original array of files sorted by\n    // modified date\n});\n```\n\n---------------------------------------\n\n<a name=\"some\" />\n### some(arr, iterator, callback)\n\n__Alias:__ any\n\nReturns true if at least one element in the array satisfies an async test.\n_The callback for each iterator call only accepts a single argument of true or\nfalse, it does not accept an error argument first!_ This is in-line with the\nway node libraries work with truth tests like fs.exists. Once any iterator\ncall returns true, the main callback is immediately called.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* iterator(item, callback) - A truth test to apply to each item in the array.\n  The iterator is passed a callback(truthValue) which must be called with a \n  boolean argument once it has completed.\n* callback(result) - A callback which is called as soon as any iterator returns\n  true, or after all the iterator functions have finished. Result will be\n  either true or false depending on the values of the async tests.\n\n__Example__\n\n```js\nasync.some(['file1','file2','file3'], fs.exists, function(result){\n    // if result is true then at least one of the files exists\n});\n```\n\n---------------------------------------\n\n<a name=\"every\" />\n### every(arr, iterator, callback)\n\n__Alias:__ all\n\nReturns true if every element in the array satisfies an async test.\n_The callback for each iterator call only accepts a single argument of true or\nfalse, it does not accept an error argument first!_ This is in-line with the\nway node libraries work with truth tests like fs.exists.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* iterator(item, callback) - A truth test to apply to each item in the array.\n  The iterator is passed a callback(truthValue) which must be called with a \n  boolean argument once it has completed.\n* callback(result) - A callback which is called after all the iterator\n  functions have finished. Result will be either true or false depending on\n  the values of the async tests.\n\n__Example__\n\n```js\nasync.every(['file1','file2','file3'], fs.exists, function(result){\n    // if result is true then every file exists\n});\n```\n\n---------------------------------------\n\n<a name=\"concat\" />\n### concat(arr, iterator, callback)\n\nApplies an iterator to each item in a list, concatenating the results. Returns the\nconcatenated list. The iterators are called in parallel, and the results are\nconcatenated as they return. There is no guarantee that the results array will\nbe returned in the original order of the arguments passed to the iterator function.\n\n__Arguments__\n\n* arr - An array to iterate over\n* iterator(item, callback) - A function to apply to each item in the array.\n  The iterator is passed a callback(err, results) which must be called once it \n  has completed with an error (which can be null) and an array of results.\n* callback(err, results) - A callback which is called after all the iterator\n  functions have finished, or an error has occurred. Results is an array containing\n  the concatenated results of the iterator function.\n\n__Example__\n\n```js\nasync.concat(['dir1','dir2','dir3'], fs.readdir, function(err, files){\n    // files is now a list of filenames that exist in the 3 directories\n});\n```\n\n---------------------------------------\n\n<a name=\"concatSeries\" />\n### concatSeries(arr, iterator, callback)\n\nSame as async.concat, but executes in series instead of parallel.\n\n\n## Control Flow\n\n<a name=\"series\" />\n### series(tasks, [callback])\n\nRun an array of functions in series, each one running once the previous\nfunction has completed. If any functions in the series pass an error to its\ncallback, no more functions are run and the callback for the series is\nimmediately called with the value of the error. Once the tasks have completed,\nthe results are passed to the final callback as an array.\n\nIt is also possible to use an object instead of an array. Each property will be\nrun as a function and the results will be passed to the final callback as an object\ninstead of an array. This can be a more readable way of handling results from\nasync.series.\n\n\n__Arguments__\n\n* tasks - An array or object containing functions to run, each function is passed\n  a callback(err, result) it must call on completion with an error (which can\n  be null) and an optional result value.\n* callback(err, results) - An optional callback to run once all the functions\n  have completed. This function gets a results array (or object) containing all \n  the result arguments passed to the task callbacks.\n\n__Example__\n\n```js\nasync.series([\n    function(callback){\n        // do some stuff ...\n        callback(null, 'one');\n    },\n    function(callback){\n        // do some more stuff ...\n        callback(null, 'two');\n    }\n],\n// optional callback\nfunction(err, results){\n    // results is now equal to ['one', 'two']\n});\n\n\n// an example using an object instead of an array\nasync.series({\n    one: function(callback){\n        setTimeout(function(){\n            callback(null, 1);\n        }, 200);\n    },\n    two: function(callback){\n        setTimeout(function(){\n            callback(null, 2);\n        }, 100);\n    }\n},\nfunction(err, results) {\n    // results is now equal to: {one: 1, two: 2}\n});\n```\n\n---------------------------------------\n\n<a name=\"parallel\" />\n### parallel(tasks, [callback])\n\nRun an array of functions in parallel, without waiting until the previous\nfunction has completed. If any of the functions pass an error to its\ncallback, the main callback is immediately called with the value of the error.\nOnce the tasks have completed, the results are passed to the final callback as an\narray.\n\nIt is also possible to use an object instead of an array. Each property will be\nrun as a function and the results will be passed to the final callback as an object\ninstead of an array. This can be a more readable way of handling results from\nasync.parallel.\n\n\n__Arguments__\n\n* tasks - An array or object containing functions to run, each function is passed \n  a callback(err, result) it must call on completion with an error (which can\n  be null) and an optional result value.\n* callback(err, results) - An optional callback to run once all the functions\n  have completed. This function gets a results array (or object) containing all \n  the result arguments passed to the task callbacks.\n\n__Example__\n\n```js\nasync.parallel([\n    function(callback){\n        setTimeout(function(){\n            callback(null, 'one');\n        }, 200);\n    },\n    function(callback){\n        setTimeout(function(){\n            callback(null, 'two');\n        }, 100);\n    }\n],\n// optional callback\nfunction(err, results){\n    // the results array will equal ['one','two'] even though\n    // the second function had a shorter timeout.\n});\n\n\n// an example using an object instead of an array\nasync.parallel({\n    one: function(callback){\n        setTimeout(function(){\n            callback(null, 1);\n        }, 200);\n    },\n    two: function(callback){\n        setTimeout(function(){\n            callback(null, 2);\n        }, 100);\n    }\n},\nfunction(err, results) {\n    // results is now equals to: {one: 1, two: 2}\n});\n```\n\n---------------------------------------\n\n<a name=\"parallel\" />\n### parallelLimit(tasks, limit, [callback])\n\nThe same as parallel only the tasks are executed in parallel with a maximum of \"limit\" \ntasks executing at any time.\n\nNote that the tasks are not executed in batches, so there is no guarantee that \nthe first \"limit\" tasks will complete before any others are started.\n\n__Arguments__\n\n* tasks - An array or object containing functions to run, each function is passed \n  a callback(err, result) it must call on completion with an error (which can\n  be null) and an optional result value.\n* limit - The maximum number of tasks to run at any time.\n* callback(err, results) - An optional callback to run once all the functions\n  have completed. This function gets a results array (or object) containing all \n  the result arguments passed to the task callbacks.\n\n---------------------------------------\n\n<a name=\"whilst\" />\n### whilst(test, fn, callback)\n\nRepeatedly call fn, while test returns true. Calls the callback when stopped,\nor an error occurs.\n\n__Arguments__\n\n* test() - synchronous truth test to perform before each execution of fn.\n* fn(callback) - A function to call each time the test passes. The function is\n  passed a callback(err) which must be called once it has completed with an \n  optional error argument.\n* callback(err) - A callback which is called after the test fails and repeated\n  execution of fn has stopped.\n\n__Example__\n\n```js\nvar count = 0;\n\nasync.whilst(\n    function () { return count < 5; },\n    function (callback) {\n        count++;\n        setTimeout(callback, 1000);\n    },\n    function (err) {\n        // 5 seconds have passed\n    }\n);\n```\n\n---------------------------------------\n\n<a name=\"doWhilst\" />\n### doWhilst(fn, test, callback)\n\nThe post check version of whilst. To reflect the difference in the order of operations `test` and `fn` arguments are switched. `doWhilst` is to `whilst` as `do while` is to `while` in plain JavaScript.\n\n---------------------------------------\n\n<a name=\"until\" />\n### until(test, fn, callback)\n\nRepeatedly call fn, until test returns true. Calls the callback when stopped,\nor an error occurs.\n\nThe inverse of async.whilst.\n\n---------------------------------------\n\n<a name=\"doUntil\" />\n### doUntil(fn, test, callback)\n\nLike doWhilst except the test is inverted. Note the argument ordering differs from `until`.\n\n---------------------------------------\n\n<a name=\"forever\" />\n### forever(fn, callback)\n\nCalls the asynchronous function 'fn' repeatedly, in series, indefinitely.\nIf an error is passed to fn's callback then 'callback' is called with the\nerror, otherwise it will never be called.\n\n---------------------------------------\n\n<a name=\"waterfall\" />\n### waterfall(tasks, [callback])\n\nRuns an array of functions in series, each passing their results to the next in\nthe array. However, if any of the functions pass an error to the callback, the\nnext function is not executed and the main callback is immediately called with\nthe error.\n\n__Arguments__\n\n* tasks - An array of functions to run, each function is passed a \n  callback(err, result1, result2, ...) it must call on completion. The first\n  argument is an error (which can be null) and any further arguments will be \n  passed as arguments in order to the next task.\n* callback(err, [results]) - An optional callback to run once all the functions\n  have completed. This will be passed the results of the last task's callback.\n\n\n\n__Example__\n\n```js\nasync.waterfall([\n    function(callback){\n        callback(null, 'one', 'two');\n    },\n    function(arg1, arg2, callback){\n        callback(null, 'three');\n    },\n    function(arg1, callback){\n        // arg1 now equals 'three'\n        callback(null, 'done');\n    }\n], function (err, result) {\n   // result now equals 'done'    \n});\n```\n\n---------------------------------------\n<a name=\"compose\" />\n### compose(fn1, fn2...)\n\nCreates a function which is a composition of the passed asynchronous\nfunctions. Each function consumes the return value of the function that\nfollows. Composing functions f(), g() and h() would produce the result of\nf(g(h())), only this version uses callbacks to obtain the return values.\n\nEach function is executed with the `this` binding of the composed function.\n\n__Arguments__\n\n* functions... - the asynchronous functions to compose\n\n\n__Example__\n\n```js\nfunction add1(n, callback) {\n    setTimeout(function () {\n        callback(null, n + 1);\n    }, 10);\n}\n\nfunction mul3(n, callback) {\n    setTimeout(function () {\n        callback(null, n * 3);\n    }, 10);\n}\n\nvar add1mul3 = async.compose(mul3, add1);\n\nadd1mul3(4, function (err, result) {\n   // result now equals 15\n});\n```\n\n---------------------------------------\n<a name=\"applyEach\" />\n### applyEach(fns, args..., callback)\n\nApplies the provided arguments to each function in the array, calling the\ncallback after all functions have completed. If you only provide the first\nargument then it will return a function which lets you pass in the\narguments as if it were a single function call.\n\n__Arguments__\n\n* fns - the asynchronous functions to all call with the same arguments\n* args... - any number of separate arguments to pass to the function\n* callback - the final argument should be the callback, called when all\n  functions have completed processing\n\n\n__Example__\n\n```js\nasync.applyEach([enableSearch, updateSchema], 'bucket', callback);\n\n// partial application example:\nasync.each(\n    buckets,\n    async.applyEach([enableSearch, updateSchema]),\n    callback\n);\n```\n\n---------------------------------------\n\n<a name=\"applyEachSeries\" />\n### applyEachSeries(arr, iterator, callback)\n\nThe same as applyEach only the functions are applied in series.\n\n---------------------------------------\n\n<a name=\"queue\" />\n### queue(worker, concurrency)\n\nCreates a queue object with the specified concurrency. Tasks added to the\nqueue will be processed in parallel (up to the concurrency limit). If all\nworkers are in progress, the task is queued until one is available. Once\na worker has completed a task, the task's callback is called.\n\n__Arguments__\n\n* worker(task, callback) - An asynchronous function for processing a queued\n  task, which must call its callback(err) argument when finished, with an \n  optional error as an argument.\n* concurrency - An integer for determining how many worker functions should be\n  run in parallel.\n\n__Queue objects__\n\nThe queue object returned by this function has the following properties and\nmethods:\n\n* length() - a function returning the number of items waiting to be processed.\n* concurrency - an integer for determining how many worker functions should be\n  run in parallel. This property can be changed after a queue is created to\n  alter the concurrency on-the-fly.\n* push(task, [callback]) - add a new task to the queue, the callback is called\n  once the worker has finished processing the task.\n  instead of a single task, an array of tasks can be submitted. the respective callback is used for every task in the list.\n* unshift(task, [callback]) - add a new task to the front of the queue.\n* saturated - a callback that is called when the queue length hits the concurrency and further tasks will be queued\n* empty - a callback that is called when the last item from the queue is given to a worker\n* drain - a callback that is called when the last item from the queue has returned from the worker\n\n__Example__\n\n```js\n// create a queue object with concurrency 2\n\nvar q = async.queue(function (task, callback) {\n    console.log('hello ' + task.name);\n    callback();\n}, 2);\n\n\n// assign a callback\nq.drain = function() {\n    console.log('all items have been processed');\n}\n\n// add some items to the queue\n\nq.push({name: 'foo'}, function (err) {\n    console.log('finished processing foo');\n});\nq.push({name: 'bar'}, function (err) {\n    console.log('finished processing bar');\n});\n\n// add some items to the queue (batch-wise)\n\nq.push([{name: 'baz'},{name: 'bay'},{name: 'bax'}], function (err) {\n    console.log('finished processing bar');\n});\n\n// add some items to the front of the queue\n\nq.unshift({name: 'bar'}, function (err) {\n    console.log('finished processing bar');\n});\n```\n\n---------------------------------------\n\n<a name=\"cargo\" />\n### cargo(worker, [payload])\n\nCreates a cargo object with the specified payload. Tasks added to the\ncargo will be processed altogether (up to the payload limit). If the\nworker is in progress, the task is queued until it is available. Once\nthe worker has completed some tasks, each callback of those tasks is called.\n\n__Arguments__\n\n* worker(tasks, callback) - An asynchronous function for processing an array of\n  queued tasks, which must call its callback(err) argument when finished, with \n  an optional error as an argument.\n* payload - An optional integer for determining how many tasks should be\n  processed per round; if omitted, the default is unlimited.\n\n__Cargo objects__\n\nThe cargo object returned by this function has the following properties and\nmethods:\n\n* length() - a function returning the number of items waiting to be processed.\n* payload - an integer for determining how many tasks should be\n  process per round. This property can be changed after a cargo is created to\n  alter the payload on-the-fly.\n* push(task, [callback]) - add a new task to the queue, the callback is called\n  once the worker has finished processing the task.\n  instead of a single task, an array of tasks can be submitted. the respective callback is used for every task in the list.\n* saturated - a callback that is called when the queue length hits the concurrency and further tasks will be queued\n* empty - a callback that is called when the last item from the queue is given to a worker\n* drain - a callback that is called when the last item from the queue has returned from the worker\n\n__Example__\n\n```js\n// create a cargo object with payload 2\n\nvar cargo = async.cargo(function (tasks, callback) {\n    for(var i=0; i<tasks.length; i++){\n      console.log('hello ' + tasks[i].name);\n    }\n    callback();\n}, 2);\n\n\n// add some items\n\ncargo.push({name: 'foo'}, function (err) {\n    console.log('finished processing foo');\n});\ncargo.push({name: 'bar'}, function (err) {\n    console.log('finished processing bar');\n});\ncargo.push({name: 'baz'}, function (err) {\n    console.log('finished processing baz');\n});\n```\n\n---------------------------------------\n\n<a name=\"auto\" />\n### auto(tasks, [callback])\n\nDetermines the best order for running functions based on their requirements.\nEach function can optionally depend on other functions being completed first,\nand each function is run as soon as its requirements are satisfied. If any of\nthe functions pass an error to their callback, that function will not complete\n(so any other functions depending on it will not run) and the main callback\nwill be called immediately with the error. Functions also receive an object\ncontaining the results of functions which have completed so far.\n\nNote, all functions are called with a results object as a second argument, \nso it is unsafe to pass functions in the tasks object which cannot handle the\nextra argument. For example, this snippet of code:\n\n```js\nasync.auto({\n  readData: async.apply(fs.readFile, 'data.txt', 'utf-8')\n}, callback);\n```\n\nwill have the effect of calling readFile with the results object as the last\nargument, which will fail:\n\n```js\nfs.readFile('data.txt', 'utf-8', cb, {});\n```\n\nInstead, wrap the call to readFile in a function which does not forward the \nresults object:\n\n```js\nasync.auto({\n  readData: function(cb, results){\n    fs.readFile('data.txt', 'utf-8', cb);\n  }\n}, callback);\n```\n\n__Arguments__\n\n* tasks - An object literal containing named functions or an array of\n  requirements, with the function itself the last item in the array. The key\n  used for each function or array is used when specifying requirements. The \n  function receives two arguments: (1) a callback(err, result) which must be \n  called when finished, passing an error (which can be null) and the result of \n  the function's execution, and (2) a results object, containing the results of\n  the previously executed functions.\n* callback(err, results) - An optional callback which is called when all the\n  tasks have been completed. The callback will receive an error as an argument\n  if any tasks pass an error to their callback. Results will always be passed\n\tbut if an error occurred, no other tasks will be performed, and the results\n\tobject will only contain partial results.\n  \n\n__Example__\n\n```js\nasync.auto({\n    get_data: function(callback){\n        // async code to get some data\n    },\n    make_folder: function(callback){\n        // async code to create a directory to store a file in\n        // this is run at the same time as getting the data\n    },\n    write_file: ['get_data', 'make_folder', function(callback){\n        // once there is some data and the directory exists,\n        // write the data to a file in the directory\n        callback(null, filename);\n    }],\n    email_link: ['write_file', function(callback, results){\n        // once the file is written let's email a link to it...\n        // results.write_file contains the filename returned by write_file.\n    }]\n});\n```\n\nThis is a fairly trivial example, but to do this using the basic parallel and\nseries functions would look like this:\n\n```js\nasync.parallel([\n    function(callback){\n        // async code to get some data\n    },\n    function(callback){\n        // async code to create a directory to store a file in\n        // this is run at the same time as getting the data\n    }\n],\nfunction(err, results){\n    async.series([\n        function(callback){\n            // once there is some data and the directory exists,\n            // write the data to a file in the directory\n        },\n        function(callback){\n            // once the file is written let's email a link to it...\n        }\n    ]);\n});\n```\n\nFor a complicated series of async tasks using the auto function makes adding\nnew tasks much easier and makes the code more readable.\n\n\n---------------------------------------\n\n<a name=\"iterator\" />\n### iterator(tasks)\n\nCreates an iterator function which calls the next function in the array,\nreturning a continuation to call the next one after that. It's also possible to\n'peek' the next iterator by doing iterator.next().\n\nThis function is used internally by the async module but can be useful when\nyou want to manually control the flow of functions in series.\n\n__Arguments__\n\n* tasks - An array of functions to run.\n\n__Example__\n\n```js\nvar iterator = async.iterator([\n    function(){ sys.p('one'); },\n    function(){ sys.p('two'); },\n    function(){ sys.p('three'); }\n]);\n\nnode> var iterator2 = iterator();\n'one'\nnode> var iterator3 = iterator2();\n'two'\nnode> iterator3();\n'three'\nnode> var nextfn = iterator2.next();\nnode> nextfn();\n'three'\n```\n\n---------------------------------------\n\n<a name=\"apply\" />\n### apply(function, arguments..)\n\nCreates a continuation function with some arguments already applied, a useful\nshorthand when combined with other control flow functions. Any arguments\npassed to the returned function are added to the arguments originally passed\nto apply.\n\n__Arguments__\n\n* function - The function you want to eventually apply all arguments to.\n* arguments... - Any number of arguments to automatically apply when the\n  continuation is called.\n\n__Example__\n\n```js\n// using apply\n\nasync.parallel([\n    async.apply(fs.writeFile, 'testfile1', 'test1'),\n    async.apply(fs.writeFile, 'testfile2', 'test2'),\n]);\n\n\n// the same process without using apply\n\nasync.parallel([\n    function(callback){\n        fs.writeFile('testfile1', 'test1', callback);\n    },\n    function(callback){\n        fs.writeFile('testfile2', 'test2', callback);\n    }\n]);\n```\n\nIt's possible to pass any number of additional arguments when calling the\ncontinuation:\n\n```js\nnode> var fn = async.apply(sys.puts, 'one');\nnode> fn('two', 'three');\none\ntwo\nthree\n```\n\n---------------------------------------\n\n<a name=\"nextTick\" />\n### nextTick(callback)\n\nCalls the callback on a later loop around the event loop. In node.js this just\ncalls process.nextTick, in the browser it falls back to setImmediate(callback)\nif available, otherwise setTimeout(callback, 0), which means other higher priority\nevents may precede the execution of the callback.\n\nThis is used internally for browser-compatibility purposes.\n\n__Arguments__\n\n* callback - The function to call on a later loop around the event loop.\n\n__Example__\n\n```js\nvar call_order = [];\nasync.nextTick(function(){\n    call_order.push('two');\n    // call_order now equals ['one','two']\n});\ncall_order.push('one')\n```\n\n<a name=\"times\" />\n### times(n, callback)\n\nCalls the callback n times and accumulates results in the same manner\nyou would use with async.map.\n\n__Arguments__\n\n* n - The number of times to run the function.\n* callback - The function to call n times.\n\n__Example__\n\n```js\n// Pretend this is some complicated async factory\nvar createUser = function(id, callback) {\n  callback(null, {\n    id: 'user' + id\n  })\n}\n// generate 5 users\nasync.times(5, function(n, next){\n    createUser(n, function(err, user) {\n      next(err, user)\n    })\n}, function(err, users) {\n  // we should now have 5 users\n});\n```\n\n<a name=\"timesSeries\" />\n### timesSeries(n, callback)\n\nThe same as times only the iterator is applied to each item in the array in\nseries. The next iterator is only called once the current one has completed\nprocessing. The results array will be in the same order as the original.\n\n\n## Utils\n\n<a name=\"memoize\" />\n### memoize(fn, [hasher])\n\nCaches the results of an async function. When creating a hash to store function\nresults against, the callback is omitted from the hash and an optional hash\nfunction can be used.\n\nThe cache of results is exposed as the `memo` property of the function returned\nby `memoize`.\n\n__Arguments__\n\n* fn - the function you to proxy and cache results from.\n* hasher - an optional function for generating a custom hash for storing\n  results, it has all the arguments applied to it apart from the callback, and\n  must be synchronous.\n\n__Example__\n\n```js\nvar slow_fn = function (name, callback) {\n    // do something\n    callback(null, result);\n};\nvar fn = async.memoize(slow_fn);\n\n// fn can now be used as if it were slow_fn\nfn('some name', function () {\n    // callback\n});\n```\n\n<a name=\"unmemoize\" />\n### unmemoize(fn)\n\nUndoes a memoized function, reverting it to the original, unmemoized\nform. Comes handy in tests.\n\n__Arguments__\n\n* fn - the memoized function\n\n<a name=\"log\" />\n### log(function, arguments)\n\nLogs the result of an async function to the console. Only works in node.js or\nin browsers that support console.log and console.error (such as FF and Chrome).\nIf multiple arguments are returned from the async function, console.log is\ncalled on each argument in order.\n\n__Arguments__\n\n* function - The function you want to eventually apply all arguments to.\n* arguments... - Any number of arguments to apply to the function.\n\n__Example__\n\n```js\nvar hello = function(name, callback){\n    setTimeout(function(){\n        callback(null, 'hello ' + name);\n    }, 1000);\n};\n```\n```js\nnode> async.log(hello, 'world');\n'hello world'\n```\n\n---------------------------------------\n\n<a name=\"dir\" />\n### dir(function, arguments)\n\nLogs the result of an async function to the console using console.dir to\ndisplay the properties of the resulting object. Only works in node.js or\nin browsers that support console.dir and console.error (such as FF and Chrome).\nIf multiple arguments are returned from the async function, console.dir is\ncalled on each argument in order.\n\n__Arguments__\n\n* function - The function you want to eventually apply all arguments to.\n* arguments... - Any number of arguments to apply to the function.\n\n__Example__\n\n```js\nvar hello = function(name, callback){\n    setTimeout(function(){\n        callback(null, {hello: name});\n    }, 1000);\n};\n```\n```js\nnode> async.dir(hello, 'world');\n{hello: 'world'}\n```\n\n---------------------------------------\n\n<a name=\"noConflict\" />\n### noConflict()\n\nChanges the value of async back to its original value, returning a reference to the\nasync object.\n",
+  "readmeFilename": "README.md",
+  "homepage": "https://github.com/caolan/async",
+  "_id": "async@0.2.10",
+  "_shasum": "b6bbe0b0674b9d719708ca38de8c237cb526c3d1",
+  "_from": "async@~0.2.9",
+  "_resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz"
+}
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/package.json b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/package.json
new file mode 100644
index 0000000..473485c
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/adbkit-monkey/package.json
@@ -0,0 +1,79 @@
+{
+  "name": "adbkit-monkey",
+  "version": "1.0.1",
+  "description": "A Node.js interface to the Android monkey tool.",
+  "keywords": [
+    "adb",
+    "adbkit",
+    "monkey",
+    "monkeyrunner"
+  ],
+  "bugs": {
+    "url": "https://github.com/CyberAgent/adbkit-monkey/issues"
+  },
+  "license": "Apache-2.0",
+  "author": {
+    "name": "CyberAgent, Inc.",
+    "email": "npm@cyberagent.co.jp",
+    "url": "http://www.cyberagent.co.jp/"
+  },
+  "main": "./index",
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/CyberAgent/adbkit-monkey.git"
+  },
+  "scripts": {
+    "postpublish": "grunt clean",
+    "prepublish": "grunt coffee",
+    "test": "grunt test"
+  },
+  "dependencies": {
+    "async": "~0.2.9"
+  },
+  "devDependencies": {
+    "chai": "~1.8.1",
+    "coffee-script": "~1.6.3",
+    "grunt": "~0.4.1",
+    "grunt-cli": "~0.1.11",
+    "grunt-coffeelint": "~0.0.7",
+    "grunt-contrib-clean": "~0.5.0",
+    "grunt-contrib-coffee": "~0.7.0",
+    "grunt-contrib-watch": "~0.5.3",
+    "grunt-exec": "~0.4.2",
+    "grunt-jsonlint": "~1.0.2",
+    "grunt-notify": "~0.2.16",
+    "mocha": "~1.14.0",
+    "sinon": "~1.7.3",
+    "sinon-chai": "~2.4.0"
+  },
+  "engines": {
+    "node": ">= 0.10.4"
+  },
+  "readme": "# adbkit-monkey\n\n**adbkit-monkey** provides a [Node.js][nodejs] interface for working with the Android [`monkey` tool][monkey-site]. Albeit undocumented, they monkey program can be started in TCP mode with the `--port` argument. In this mode, it accepts a [range of commands][monkey-proto] that can be used to interact with the UI in a non-random manner. This mode is also used internally by the [`monkeyrunner` tool][monkeyrunner-site], although the documentation claims no relation to the monkey tool.\n\n## Getting started\n\nInstall via NPM:\n\n```bash\nnpm install --save adbkit-monkey\n```\n\nNote that while adbkit-monkey is written in CoffeeScript, it is compiled to JavaScript before publishing to NPM, which means that you are not required to use CoffeeScript.\n\n### Examples\n\nThe following examples assume that monkey is already running (via `adb shell monkey --port 1080`) and a port forwarding (`adb forward tcp:1080 tcp:1080`) has been set up.\n\n#### Press the home button\n\n```javascript\nvar assert = require('assert');\nvar monkey = require('adbkit-monkey');\n\nvar client = monkey.connect({ port: 1080 });\n\nclient.press(3 /* KEYCODE_HOME */, function(err) {\n  assert.ifError(err);\n  console.log('Pressed home button');\n  client.end();\n});\n```\n\n#### Drag out the notification bar\n\n```javascript\nvar assert = require('assert');\nvar monkey = require('adbkit-monkey');\n\nvar client = monkey.connect({ port: 1080 });\n\nclient.multi()\n  .touchDown(100, 0)\n  .sleep(5)\n  .touchMove(100, 20)\n  .sleep(5)\n  .touchMove(100, 40)\n  .sleep(5)\n  .touchMove(100, 60)\n  .sleep(5)\n  .touchMove(100, 80)\n  .sleep(5)\n  .touchMove(100, 100)\n  .sleep(5)\n  .touchUp(100, 100)\n  .sleep(5)\n  .execute(function(err) {\n    assert.ifError(err);\n    console.log('Dragged out the notification bar');\n    client.end();\n  });\n```\n\n#### Get display size\n\n```javascript\nvar assert = require('assert');\nvar monkey = require('adbkit-monkey');\n\nvar client = monkey.connect({ port: 1080 });\n\nclient.getDisplayWidth(function(err, width) {\n  assert.ifError(err);\n  client.getDisplayHeight(function(err, height) {\n    assert.ifError(err);\n    console.log('Display size is %dx%d', width, height);\n    client.end();\n  });\n});\n```\n\n#### Type text\n\nNote that you should manually focus a text field first.\n\n```javascript\nvar assert = require('assert');\nvar monkey = require('adbkit-monkey');\n\nvar client = monkey.connect({ port: 1080 });\n\nclient.type('hello monkey!', function(err) {\n  assert.ifError(err);\n  console.log('Said hello to monkey');\n  client.end();\n});\n```\n\n## API\n\n### Monkey\n\n#### monkey.connect(options)\n\nUses [Net.connect()][node-net] to open a new TCP connection to monkey. Useful when combined with `adb forward`.\n\n* **options** Any options [`Net.connect()`][node-net] accepts.\n* Returns: A new monkey `Client` instance.\n\n#### monkey.connectStream(stream)\n\nAttaches a monkey client to an existing monkey protocol stream.\n\n* **stream** The monkey protocol [`Stream`][node-stream].\n* Returns: A new monkey `Client` instance.\n\n### Client\n\nImplements `Api`. See below for details.\n\n#### Events\n\nThe following events are available:\n\n* **error** **(err)** Emitted when an error occurs.\n    * **err** An `Error`.\n* **end** Emitted when the stream ends.\n* **finish** Emitted when the stream finishes.\n\n#### client.end()\n\nEnds the underlying stream/connection.\n\n* Returns: The `Client` instance.\n\n#### client.multi()\n\nReturns a new API wrapper that buffers commands for simultaneous delivery instead of sending them individually. When used with `api.sleep()`, allows simple gestures to be executed.\n\n* Returns: A new `Multi` instance. See `Multi` below.\n\n#### client.send(command, callback)\n\nSends a raw protocol command to monkey.\n\n* **command** The command to send. When `String`, a single command is sent. When `Array`, a series of commands is sent at once.\n* **callback(err, value, command)** Called when monkey responds to the command. If multiple commands were sent, the callback will be called once for each command.\n    * **err** `null` when successful, `Error` otherwise.\n    * **value** The response value, if any.\n    * **command** The command the response is for.\n* Returns: The `Client` instance.\n\n### Api\n\nThe monkey API implemented by `Client` and `Multi`.\n\n#### api.done(callback)\n\nCloses the current monkey session and allows a new session to connect.\n\n* **callback(err)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n* Returns: The `Api` implementation instance.\n\n#### api.flipClose(callback)\n\nSimulates closing the keyboard.\n\n* **callback(err)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n* Returns: The `Api` implementation instance.\n\n#### api.flipOpen(callback)\n\nSimulates opening the keyboard.\n\n* **callback(err)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n* Returns: The `Api` implementation instance.\n\n#### api.get(name, callback)\n\nGets the value of a variable. Use `api.list()` to retrieve a list of supported variables.\n\n* **name** The name of the variable.\n* **callback(err, value)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n    * **value** The value of the variable.\n* Returns: The `Api` implementation instance.\n\n#### api.getAmCurrentAction(callback)\n\nAlias for `api.get('am.current.action', callback)`.\n\n#### api.getAmCurrentCategories(callback)\n\nAlias for `api.get('am.current.categories', callback)`.\n\n#### api.getAmCurrentCompClass(callback)\n\nAlias for `api.get('am.current.comp.class', callback)`.\n\n#### api.getAmCurrentCompPackage(callback)\n\nAlias for `api.get('am.current.comp.package', callback)`.\n\n#### api.getCurrentData(callback)\n\nAlias for `api.get('am.current.data', callback)`.\n\n#### api.getAmCurrentPackage(callback)\n\nAlias for `api.get('am.current.package', callback)`.\n\n#### api.getBuildBoard(callback)\n\nAlias for `api.get('build.board', callback)`.\n\n#### api.getBuildBrand(callback)\n\nAlias for `api.get('build.brand', callback)`.\n\n#### api.getBuildCpuAbi(callback)\n\nAlias for `api.get('build.cpu_abi', callback)`.\n\n#### api.getBuildDevice(callback)\n\nAlias for `api.get('build.device', callback)`.\n\n#### api.getBuildDisplay(callback)\n\nAlias for `api.get('build.display', callback)`.\n\n#### api.getBuildFingerprint(callback)\n\nAlias for `api.get('build.fingerprint', callback)`.\n\n#### api.getBuildHost(callback)\n\nAlias for `api.get('build.host', callback)`.\n\n#### api.getBuildId(callback)\n\nAlias for `api.get('build.id', callback)`.\n\n#### api.getBuildManufacturer(callback)\n\nAlias for `api.get('build.manufacturer', callback)`.\n\n#### api.getBuildModel(callback)\n\nAlias for `api.get('build.model', callback)`.\n\n#### api.getBuildProduct(callback)\n\nAlias for `api.get('build.product', callback)`.\n\n#### api.getBuildTags(callback)\n\nAlias for `api.get('build.tags', callback)`.\n\n#### api.getBuildType(callback)\n\nAlias for `api.get('build.type', callback)`.\n\n#### api.getBuildUser(callback)\n\nAlias for `api.get('build.user', callback)`.\n\n#### api.getBuildVersionCodename(callback)\n\nAlias for `api.get('build.version.codename', callback)`.\n\n#### api.getBuildVersionIncremental(callback)\n\nAlias for `api.get('build.version.incremental', callback)`.\n\n#### api.getBuildVersionRelease(callback)\n\nAlias for `api.get('build.version.release', callback)`.\n\n#### api.getBuildVersionSdk(callback)\n\nAlias for `api.get('build.version.sdk', callback)`.\n\n#### api.getClockMillis(callback)\n\nAlias for `api.get('clock.millis', callback)`.\n\n#### api.getClockRealtime(callback)\n\nAlias for `api.get('clock.realtime', callback)`.\n\n#### api.getClockUptime(callback)\n\nAlias for `api.get('clock.uptime', callback)`.\n\n#### api.getDisplayDensity(callback)\n\nAlias for `api.get('display.density', callback)`.\n\n#### api.getDisplayHeight(callback)\n\nAlias for `api.get('display.height', callback)`. Note that the height may exclude any virtual home button row.\n\n#### api.getDisplayWidth(callback)\n\nAlias for `api.get('display.width', callback)`.\n\n#### api.keyDown(keyCode, callback)\n\nSends a key down event. Should be coupled with `api.keyUp()`. Note that `api.press()` performs the two events automatically.\n\n* **keyCode** The [key code][android-keycodes]. All monkeys support numeric keycodes, and some support automatic conversion from key names to key codes (e.g. `'home'` to `KEYCODE_HOME`). This will not work for number keys however. The most portable method is to simply use numeric key codes.\n* **callback(err)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n* Returns: The `Api` implementation instance.\n\n#### api.keyUp(keyCode, callback)\n\nSends a key up event. Should be coupled with `api.keyDown()`. Note that `api.press()` performs the two events automatically.\n\n* **keyCode** See `api.keyDown()`.\n* **callback(err)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n* Returns: The `Api` implementation instance.\n\n#### api.list(callback)\n\nLists supported variables.\n\n* **callback(err, vars)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n    * **vars** An array of supported variable names, to be used with `api.get()`.\n* Returns: The `Api` implementation instance.\n\n#### api.press(keyCode, callback)\n\nSends a key press event.\n\n* **keyCode** See `api.keyDown()`.\n* **callback(err)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n* Returns: The `Api` implementation instance.\n\n#### api.quit(callback)\n\nCloses the current monkey session and quits monkey.\n\n* **callback(err)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n* Returns: The `Api` implementation instance.\n\n#### api.sleep(ms, callback)\n\nSleeps for the given duration. Can be useful for simulating gestures.\n\n* **ms** How many milliseconds to sleep for.\n* **callback(err)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n* Returns: The `Api` implementation instance.\n\n#### api.tap(x, y, callback)\n\nTaps the given coordinates.\n\n* **x** The x coordinate.\n* **y** The y coordinate.\n* **callback(err)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n* Returns: The `Api` implementation instance.\n\n#### api.touchDown(x, y, callback)\n\nSends a touch down event on the given coordinates.\n\n* **x** The x coordinate.\n* **y** The y coordinate.\n* **callback(err)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n* Returns: The `Api` implementation instance.\n\n#### api.touchMove(x, y, callback)\n\nSends a touch move event on the given coordinates.\n\n* **x** The x coordinate.\n* **y** The y coordinate.\n* **callback(err)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n* Returns: The `Api` implementation instance.\n\n#### api.touchUp(x, y, callback)\n\nSends a touch up event on the given coordinates.\n\n* **x** The x coordinate.\n* **y** The y coordinate.\n* **callback(err)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n* Returns: The `Api` implementation instance.\n\n#### api.trackball(x, y, callback)\n\nSends a trackball event on the given coordinates.\n\n* **x** The x coordinate.\n* **y** The y coordinate.\n* **callback(err)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n* Returns: The `Api` implementation instance.\n\n#### api.type(text, callback)\n\nTypes the given text.\n\n* **text** A text `String`. Note that only characters for which [key codes][android-keycodes] exist can be entered. Also note that any IME in use may or may not transform the text.\n* **callback(err)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n* Returns: The `Api` implementation instance.\n\n#### api.wake(callback)\n\nWakes the device from sleep and allows user input.\n\n* **callback(err)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n* Returns: The `Api` implementation instance.\n\n### Multi\n\nBuffers `Api` commands and delivers them simultaneously for greater control over timing.\n\nImplements all `Api` methods, but without the last `callback` parameter.\n\n#### multi.execute(callback)\n\nSends all buffered commands.\n\n* **callback(err, values)** Called when monkey has responded to all commands (i.e. just once at the end).\n    * **err** `null` when successful, `Error` otherwise.\n    * **values** An array of all response values, identical to individual `Api` responses.\n\n## More information\n\n* [Monkey][monkey-site]\n    - [Source code][monkey-source]\n    - [Protocol][monkey-proto]\n* [Monkeyrunner][monkeyrunner-site]\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md).\n\n## License\n\nSee [LICENSE](LICENSE).\n\nCopyright © CyberAgent, Inc. All Rights Reserved.\n\n[nodejs]: <http://nodejs.org/>\n[monkey-site]: <http://developer.android.com/tools/help/monkey.html>\n[monkey-source]: <https://github.com/android/platform_development/blob/master/cmds/monkey/>\n[monkey-proto]: <https://github.com/android/platform_development/blob/master/cmds/monkey/README.NETWORK.txt>\n[monkeyrunner-site]: <http://developer.android.com/tools/help/monkeyrunner_concepts.html>\n[node-net]: <http://nodejs.org/api/net.html>\n[node-stream]: <http://nodejs.org/api/stream.html>\n[android-keycodes]: <http://developer.android.com/reference/android/view/KeyEvent.html>\n",
+  "readmeFilename": "README.md",
+  "homepage": "https://github.com/CyberAgent/adbkit-monkey",
+  "_id": "adbkit-monkey@1.0.1",
+  "dist": {
+    "shasum": "f291be701a2efc567a63fc7aa6afcded31430be1",
+    "tarball": "http://registry.npmjs.org/adbkit-monkey/-/adbkit-monkey-1.0.1.tgz"
+  },
+  "_from": "adbkit-monkey@~1.0.1",
+  "_npmVersion": "1.3.14",
+  "_npmUser": {
+    "name": "sorccu",
+    "email": "simo@shoqolate.com"
+  },
+  "maintainers": [
+    {
+      "name": "sorccu",
+      "email": "simo@shoqolate.com"
+    },
+    {
+      "name": "cyberagent",
+      "email": "npm@cyberagent.co.jp"
+    }
+  ],
+  "directories": {},
+  "_shasum": "f291be701a2efc567a63fc7aa6afcded31430be1",
+  "_resolved": "https://registry.npmjs.org/adbkit-monkey/-/adbkit-monkey-1.0.1.tgz"
+}
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/.npmignore b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/.npmignore
new file mode 100644
index 0000000..36ee3f7
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/.npmignore
@@ -0,0 +1,30 @@
+node_modules/*
+todo.txt
+npm-debug.log
+test/*
+benchmark/*
+browser/*
+src/*
+async
+sync
+mixed
+bench.json
+js/browser
+js/browser/*
+js/debug
+js/debug/*
+reader.js
+read.txt
+bench
+.editorconfig
+.jshintrc
+ast_passes.js
+mocharun.js
+throwaway.js
+throwaway.html
+bluebird.sublime-workspace
+bluebird.sublime-project
+changelog.js
+.travis.yml
+sauce_connect.log
+bump.js
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/API.md b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/API.md
new file mode 100644
index 0000000..7e940c2
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/API.md
@@ -0,0 +1,1741 @@
+#API Reference
+
+- [Core](#core)
+    - [`new Promise(Function<Function resolve, Function reject> resolver)`](#new-promisefunctionfunction-resolve-function-reject-resolver---promise)
+    - [`.then([Function fulfilledHandler] [, Function rejectedHandler ] [, Function progressHandler ])`](#thenfunction-fulfilledhandler--function-rejectedhandler---function-progresshandler----promise)
+    - [`.catch(Function handler)`](#catchfunction-handler---promise)
+    - [`.catch([Function ErrorClass|Function predicate...], Function handler)`](#catchfunction-errorclassfunction-predicate-function-handler---promise)
+    - [`.error( [rejectedHandler] )`](#error-rejectedhandler----promise)
+    - [`.finally(Function handler)`](#finallyfunction-handler---promise)
+    - [`.tap(Function handler)`](#tapfunction-handler---promise)
+    - [`.bind(dynamic thisArg)`](#binddynamic-thisarg---promise)
+    - [`.done([Function fulfilledHandler] [, Function rejectedHandler ] [, Function progressHandler ])`](#donefunction-fulfilledhandler--function-rejectedhandler---function-progresshandler----promise)
+    - [`Promise.try(Function fn [, Array<dynamic>|dynamic arguments] [, dynamic ctx] )`](#promisetryfunction-fn--arraydynamicdynamic-arguments--dynamic-ctx----promise)
+    - [`Promise.method(Function fn)`](#promisemethodfunction-fn---function)
+    - [`Promise.resolve(dynamic value)`](#promiseresolvedynamic-value---promise)
+    - [`Promise.reject(dynamic reason)`](#promiserejectdynamic-reason---promise)
+    - [`Promise.defer()`](#promisedefer---promiseresolver)
+    - [`Promise.cast(dynamic value)`](#promisecastdynamic-value---promise)
+    - [`Promise.bind(dynamic thisArg)`](#promisebinddynamic-thisarg---promise)
+    - [`Promise.is(dynamic value)`](#promiseisdynamic-value---boolean)
+    - [`Promise.longStackTraces()`](#promiselongstacktraces---void)
+- [Progression](#progression)
+    - [`.progressed(Function handler)`](#progressedfunction-handler---promise)
+- [Promise resolution](#promise-resolution)
+    - [`.resolve(dynamic value)`](#resolvedynamic-value---undefined)
+    - [`.reject(dynamic reason)`](#rejectdynamic-reason---undefined)
+    - [`.progress(dynamic value)`](#progressdynamic-value---undefined)
+    - [`.callback`](#callback---function)
+- [Timers](#timers)
+    - [`.delay(int ms)`](#delayint-ms---promise)
+    - [`.timeout(int ms [, String message])`](#timeoutint-ms--string-message---promise)
+    - [`Promise.delay([dynamic value], int ms)`](#promisedelaydynamic-value-int-ms---promise)
+- [Promisification](#promisification)
+    - [`Promise.promisify(Function nodeFunction [, dynamic receiver])`](#promisepromisifyfunction-nodefunction--dynamic-receiver---function)
+    - [`Promise.promisify(Object target)`](#promisepromisifyobject-target---object)
+    - [`Promise.promisifyAll(Object target)`](#promisepromisifyallobject-target---object)
+    - [`.nodeify([Function callback])`](#nodeifyfunction-callback---promise)
+- [Cancellation](#cancellation)
+    - [`.cancellable()`](#cancellable---promise)
+    - [`.cancel()`](#cancel---promise)
+    - [`.fork([Function fulfilledHandler] [, Function rejectedHandler ] [, Function progressHandler ])`](#forkfunction-fulfilledhandler--function-rejectedhandler---function-progresshandler----promise)
+    - [`.uncancellable()`](#uncancellable---promise)
+    - [`.isCancellable()`](#iscancellable---boolean)
+- [Synchronous inspection](#synchronous-inspection)
+    - [`.isFulfilled()`](#isfulfilled---boolean)
+    - [`.isRejected()`](#isrejected---boolean)
+    - [`.isPending()`](#isdefer---boolean)
+    - [`.isResolved()`](#isresolved---boolean)
+    - [`.inspect()`](#inspect---promiseinspection)
+- [Generators](#generators)
+    - [`Promise.coroutine(GeneratorFunction generatorFunction)`](#promisecoroutinegeneratorfunction-generatorfunction---function)
+    - [`Promise.coroutine.addYieldHandler(function handler)`](#promisecoroutineaddyieldhandlerfunction-handler---void)
+- [Utility](#utility)
+    - [`.call(String propertyName [, dynamic arg...])`](#callstring-propertyname--dynamic-arg---promise)
+    - [`.get(String propertyName)`](#getstring-propertyname---promise)
+    - [`.return(dynamic value)`](#returndynamic-value---promise)
+    - [`.throw(dynamic reason)`](#throwdynamic-reason---promise)
+    - [`.toString()`](#tostring---string)
+    - [`.toJSON()`](#tojson---object)
+    - [`Promise.noConflict()`](#promisenoconflict---object)
+    - [`Promise.onPossiblyUnhandledRejection(Function handler)`](#promiseonpossiblyunhandledrejectionfunction-handler---undefined)
+- [Collections](#collections)
+    - [`.all()`](#all---promise)
+    - [`.props()`](#props---promise)
+    - [`.settle()`](#settle---promise)
+    - [`.any()`](#any---promise)
+    - [`.race()`](#race---promise)
+    - [`.some(int count)`](#someint-count---promise)
+    - [`.spread([Function fulfilledHandler] [, Function rejectedHandler ])`](#spreadfunction-fulfilledhandler--function-rejectedhandler----promise)
+    - [`.map(Function mapper)`](#mapfunction-mapper---promise)
+    - [`.reduce(Function reducer [, dynamic initialValue])`](#reducefunction-reducer--dynamic-initialvalue---promise)
+    - [`.filter(Function filterer)`](#filterfunction-filterer---promise)
+    - [`Promise.all(Array<dynamic>|Promise values)`](#promiseallarraydynamicpromise-values---promise)
+    - [`Promise.props(Object|Promise object)`](#promisepropsobjectpromise-object---promise)
+    - [`Promise.settle(Array<dynamic>|Promise values)`](#promisesettlearraydynamicpromise-values---promise)
+    - [`Promise.any(Array<dynamic>|Promise values)`](#promiseanyarraydynamicpromise-values---promise)
+    - [`Promise.race(Array|Promise promises)`](#promiseracearraypromise-promises---promise)
+    - [`Promise.some(Array<dynamic>|Promise values, int count)`](#promisesomearraydynamicpromise-values-int-count---promise)
+    - [`Promise.join([dynamic value...])`](#promisejoindynamic-value---promise)
+    - [`Promise.map(Array<dynamic>|Promise values, Function mapper)`](#promisemaparraydynamicpromise-values-function-mapper---promise)
+    - [`Promise.reduce(Array<dynamic>|Promise values, Function reducer [, dynamic initialValue])`](#promisereducearraydynamicpromise-values-function-reducer--dynamic-initialvalue---promise)
+    - [`Promise.filter(Array<dynamic>|Promise values, Function filterer)`](#promisefilterarraydynamicpromise-values-function-filterer---promise)
+
+##Core
+
+Core methods of `Promise` instances and core static methods of the Promise class.
+
+#####`new Promise(Function<Function resolve, Function reject> resolver)` -> `Promise`
+
+Create a new promise. The passed in function will receive functions `resolve` and `reject` as its arguments which can be called to seal the fate of the created promise.
+
+Example:
+
+```js
+function ajaxGetAsync(url) {
+    return new Promise(function (resolve, reject) {
+        var xhr = new XMLHttpRequest;
+        xhr.addEventListener("error", reject);
+        xhr.addEventListener("load", resolve);
+        xhr.open("GET", url);
+        xhr.send(null);
+    });
+}
+```
+
+If you pass a promise object to the `resolve` function, the created promise will follow the state of that promise.
+
+<hr>
+
+To make sure a function that returns a promise is following the implicit but critically important contract of promises, you can start a function with `new Promise` if you cannot start a chain immediately:
+
+```js
+function getConnection(urlString) {
+    return new Promise(function(resolve) {
+        //Without new Promise, this throwing will throw an actual exception
+        var params = parse(urlString);
+        resolve(getAdapater(params).getConnection());
+    });
+}
+```
+
+The above ensures `getConnection()` fulfills the contract of a promise-returning function of never throwing a synchronous exception. Also see [`Promise.try`](#promisetryfunction-fn--arraydynamicdynamic-arguments--dynamic-ctx----promise) and [`Promise.method`](#promisemethodfunction-fn---function)
+
+<hr>
+
+#####`.then([Function fulfilledHandler] [, Function rejectedHandler ] [, Function progressHandler ])` -> `Promise`
+
+[Promises/A+ `.then()`](http://promises-aplus.github.io/promises-spec/) with progress handler. Returns a new promise chained from this promise. The new promise will be rejected or resolved dedefer on the passed `fulfilledHandler`, `rejectedHandler` and the state of this promise.
+
+Example:
+
+```js
+promptAsync("Which url to visit?").then(function(url){
+    return ajaxGetAsync(url);
+}).then(function(contents){
+    alertAsync("The contents were: " + contents);
+}).catch(function(e){
+    alertAsync("Exception " + e);
+});
+```
+
+<hr>
+
+#####`.catch(Function handler)` -> `Promise`
+
+This is a catch-all exception handler, shortcut for calling `.then(null, handler)` on this promise. Any exception happening in a `.then`-chain will propagate to nearest `.catch` handler.
+
+*For compatibility with earlier ECMAScript version, an alias `.caught()` is provided for `.catch()`.*
+
+<hr>
+
+#####`.catch([Function ErrorClass|Function predicate...], Function handler)` -> `Promise`
+
+This extends `.catch` to work more like catch-clauses in languages like Java or C#. Instead of manually checking `instanceof` or `.name === "SomeError"`, you may specify a number of error constructors which are eligible for this catch handler. The catch handler that is first met that has eligible constructors specified, is the one that will be called.
+
+Example:
+
+```js
+somePromise.then(function(){
+    return a.b.c.d();
+}).catch(TypeError, function(e){
+    //If a is defined, will end up here because
+    //it is a type error to reference property of undefined
+}).catch(ReferenceError, function(e){
+    //Will end up here if a wasn't defined at all
+}).catch(function(e){
+    //Generic catch-the rest, error wasn't TypeError nor
+    //ReferenceError
+});
+ ```
+
+You may also add multiple filters for a catch handler:
+
+```js
+somePromise.then(function(){
+    return a.b.c.d();
+}).catch(TypeError, ReferenceError, function(e){
+    //Will end up here on programmer error
+}).catch(NetworkError, TimeoutError, function(e){
+    //Will end up here on expected everyday network errors
+}).catch(function(e){
+    //Catch any unexpected errors
+});
+```
+
+For a parameter to be considered a type of error that you want to filter, you need the constructor to have its `.prototype` property be `instanceof Error`.
+
+Such a constructor can be minimally created like so:
+
+```js
+function MyCustomError() {}
+MyCustomError.prototype = Object.create(Error.prototype);
+```
+
+Using it:
+
+```js
+Promise.resolve().then(function(){
+    throw new MyCustomError();
+}).catch(MyCustomError, function(e){
+    //will end up here now
+});
+```
+
+However if you  want stack traces and cleaner string output, then you should do:
+
+*in Node.js and other V8 environments, with support for `Error.captureStackTrace`*
+
+```js
+function MyCustomError(message) {
+    this.message = message;
+    this.name = "MyCustomError";
+    Error.captureStackTrace(this, MyCustomError);
+}
+MyCustomError.prototype = Object.create(Error.prototype);
+MyCustomError.prototype.constructor = MyCustomError;
+```
+
+Using CoffeeScript's `class` for the same:
+
+```coffee
+class MyCustomError extends Error
+  constructor: (@message) ->
+    @name = "MyCustomError"
+    Error.captureStackTrace(this, MyCustomError)
+```
+
+This method also supports predicate-based filters. If you pass a
+predicate function instead of an error constructor, the predicate will receive
+the error as an argument. The return result of the predicate will be used
+determine whether the error handler should be called.
+
+Predicates should allow for very fine grained control over caught errors:
+pattern matching, error-type sets with set operations and many other techniques
+can be implemented on top of them.
+
+Example of using a predicate-based filter:
+
+```js
+var Promise = require("bluebird");
+var request = Promise.promisify(require("request"));
+
+function clientError(e) {
+    return e.code >= 400 && e.code < 500;
+}
+
+request("http://www.google.com").then(function(contents){
+    console.log(contents);
+}).catch(clientError, function(e){
+   //A client error like 400 Bad Request happened
+});
+```
+
+*For compatibility with earlier ECMAScript version, an alias `.caught()` is provided for `.catch()`.*
+
+<hr>
+
+#####`.error( [rejectedHandler] )` -> `Promise`
+
+Like `.catch` but instead of catching all types of exceptions, it only catches those that don't originate from thrown errors but rather from explicit rejections.
+
+*Note, "errors" mean errors, as in objects that are `instanceof Error` - not strings, numbers and so on. See [a string is not an error](http://www.devthought.com/2011/12/22/a-string-is-not-an-error/).*
+
+For example, if a promisified function errbacks the node-style callback with an error, that could be caught with `.error()`. However if the node-style callback **throws** an error, only `.catch` would catch that.
+
+In the following example you might want to handle just the `SyntaxError` from JSON.parse and Filesystem errors from `fs` but let programmer errors bubble as unhandled rejections:
+
+```js
+var fs = Promise.promisifyAll(require("fs"));
+
+fs.readFileAsync("myfile.json").then(JSON.parse).then(function (json) {
+    console.log("Successful json")
+}).catch(SyntaxError, function (e) {
+    console.error("file contains invalid json");
+}).error(function (e) {
+    console.error("unable to read file, because: ", e.message);
+});
+```
+
+Now, because there is no catch-all handler, if you typed `console.lag` (causes an error you don't expect), you will see:
+
+```
+Possibly unhandled TypeError: Object #<Console> has no method 'lag'
+    at application.js:8:13
+From previous event:
+    at Object.<anonymous> (application.js:7:4)
+    at Module._compile (module.js:449:26)
+    at Object.Module._extensions..js (module.js:467:10)
+    at Module.load (module.js:349:32)
+    at Function.Module._load (module.js:305:12)
+    at Function.Module.runMain (module.js:490:10)
+    at startup (node.js:121:16)
+    at node.js:761:3
+```
+
+*( If you don't get the above - you need to enable [long stack traces](#promiselongstacktraces---void) )*
+
+And if the file contains invalid JSON:
+
+```
+file contains invalid json
+```
+
+And if the `fs` module causes an error like file not found:
+
+```
+unable to read file, because:  ENOENT, open 'not_there.txt'
+```
+
+<hr>
+
+#####`.finally(Function handler)` -> `Promise`
+
+Pass a handler that will be called regardless of this promise's fate. Returns a new promise chained from this promise. There are special semantics for `.finally()` in that the final value cannot be modified from the handler.
+
+Consider the example:
+
+```js
+function anyway() {
+    $("#ajax-loader-animation").hide();
+}
+
+function ajaxGetAsync(url) {
+    return new Promise(function (resolve, reject) {
+        var xhr = new XMLHttpRequest;
+        xhr.addEventListener("error", reject);
+        xhr.addEventListener("load", resolve);
+        xhr.open("GET", url);
+        xhr.send(null);
+    }).then(anyway, anyway);
+}
+```
+
+This example doesn't work as intended because the `then` handler actually swallows the exception and returns `undefined` for any further chainers.
+
+The situation can be fixed with `.finally`:
+
+```js
+function ajaxGetAsync(url) {
+    return new Promise(function (resolve, reject) {
+        var xhr = new XMLHttpRequest;
+        xhr.addEventListener("error", reject);
+        xhr.addEventListener("load", resolve);
+        xhr.open("GET", url);
+        xhr.send(null);
+    }).finally(function(){
+        $("#ajax-loader-animation").hide();
+    });
+}
+```
+
+Now the animation is hidden but an exception or the actual return value will automatically skip the finally and propagate to further chainers. This is more in line with the synchronous `finally` keyword.
+
+The `.finally` works like [Q's finally method](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback).
+
+*For compatibility with earlier ECMAScript version, an alias `.lastly()` is provided for `.finally()`.*
+
+<hr>
+
+#####`.tap(Function handler)` -> `Promise`
+
+Like `.finally` that is not called for rejections.
+
+```js
+getUser().tap(function(user) {
+    //Like in finally, if you return a promise from the handler
+    //the promise is awaited for before passing the original value through
+    return recordStatsAsync();
+}).then(function(user) {
+    //user is the user from getUser(), not recordStatsAsync()
+});
+```
+
+<hr>
+
+#####`.bind(dynamic thisArg)` -> `Promise`
+
+Create a promise that follows this promise, but is bound to the given `thisArg` value. A bound promise will call its handlers with the bound value set to `this`. Additionally promises derived from a bound promise will also be bound promises with the same `thisArg` binding as the original promise.
+
+<hr>
+
+Without arrow functions that provide lexical `this`, the correspondence between async and sync code breaks down when writing object-oriented code. `.bind()` alleviates this.
+
+Consider:
+
+```js
+MyClass.prototype.method = function() {
+    try {
+        var contents = fs.readFileSync(this.file);
+        var url = urlParse(contents);
+        var result = this.httpGetSync(url);
+        var refined = this.refine(result);
+        return this.writeRefinedSync(refined);
+    }
+    catch (e) {
+        this.error(e.stack);
+    }
+};
+```
+
+The above has a direct translation:
+
+```js
+MyClass.prototype.method = function() {
+    return fs.readFileAsync(this.file).bind(this)
+    .then(function(contents) {
+        var url = urlParse(contents);
+        return this.httpGetAsync(url);
+    }).then(function(result){
+        var refined = this.refine(result);
+        return this.writeRefinedAsync(refined);
+    }).catch(function(e){
+        this.error(e.stack);
+    });
+};
+```
+
+`.bind()` is the most efficient way of utilizing `this` with promises. The handler functions in the above code are not closures and can therefore even be hoisted out if needed. There is literally no overhead when propagating the bound value from one promise to another.
+
+<hr>
+
+`.bind()` also has a useful side purpose - promise handlers don't need to share a function to use shared state:
+
+```js
+somethingAsync().bind({})
+.then(function (aValue, bValue) {
+    this.aValue = aValue;
+    this.bValue = bValue;
+    return somethingElseAsync(aValue, bValue);
+})
+.then(function (cValue) {
+    return this.aValue + this.bValue + cValue;
+});
+```
+
+The above without `.bind()` could be achieved with:
+
+```js
+var scope = {};
+somethingAsync()
+.then(function (aValue, bValue) {
+    scope.aValue = aValue;
+    scope.bValue = bValue;
+    return somethingElseAsync(aValue, bValue);
+})
+.then(function (cValue) {
+    return scope.aValue + scope.bValue + cValue;
+});
+```
+
+However, there are many differences when you look closer:
+
+- Requires a statement so cannot be used in an expression context
+- If not there already, an additional wrapper function is required to avoid leaking or sharing `scope`
+- The handler functions are now closures, thus less efficient and not reusable
+
+<hr>
+
+Note that bind is only propagated with promise transformation. If you create new promise chains inside a handler, those chains are not bound to the "upper" `this`:
+
+```js
+something().bind(var1).then(function(){
+    //`this` is var1 here
+    return Promise.all(getStuff()).then(function(results){
+        //`this` is undefined here
+        //refine results here etc
+    });
+}).then(function(){
+    //`this` is var1 here
+});
+```
+
+However, if you are utilizing the full bluebird API offering, you will *almost never* need to resort to nesting promises in the first place. The above should be written more like:
+
+```js
+something().bind(var1).then(function() {
+    //`this` is var1 here
+    return getStuff();
+}).map(function(result){
+    //`this` is var1 here
+    //refine result here
+}).then(function(){
+    //`this` is var1 here
+});
+```
+
+Also see [this Stackoverflow answer](http://stackoverflow.com/a/19467053/995876) on a good example on how utilizing the collection instance methods like [`.map()`](#mapfunction-mapper---promise) can clean up code.
+
+<hr>
+
+If you don't want to return a bound promise to the consumers of a promise, you can rebind the chain at the end:
+
+```js
+MyClass.prototype.method = function() {
+    return fs.readFileAsync(this.file).bind(this)
+    .then(function(contents) {
+        var url = urlParse(contents);
+        return this.httpGetAsync(url);
+    }).then(function(result){
+        var refined = this.refine(result);
+        return this.writeRefinedAsync(refined);
+    }).catch(function(e){
+        this.error(e.stack);
+    }).bind(); //The `thisArg` is implicitly undefined - I.E. the default promise `this` value
+};
+```
+
+Rebinding can also be abused to do something gratuitous like this:
+
+```js
+Promise.resolve("my-element")
+    .bind(document)
+    .then(document.getElementById)
+    .bind(console)
+    .then(console.log);
+```
+
+The above does `console.log(document.getElementById("my-element"));`. The `.bind()`s are necessary because in browser neither of the methods can be called as a stand-alone function.
+
+<hr>
+
+#####`.done([Function fulfilledHandler] [, Function rejectedHandler ] [, Function progressHandler ])` -> `Promise`
+
+Like `.then()`, but any unhandled rejection that ends up here will be thrown as an error.
+
+<hr>
+
+#####`Promise.try(Function fn [, Array<dynamic>|dynamic arguments] [, dynamic ctx] )` -> `Promise`
+
+Start the chain of promises with `Promise.try`. Any synchronous exceptions will be turned into rejections on the returned promise.
+
+```js
+function getUserById(id) {
+    return Promise.try(function(){
+        if (typeof id !== "number") {
+            throw new Error("id must be a number");
+        }
+        return db.getUserById(id);
+    });
+}
+```
+
+Now if someone uses this function, they will catch all errors in their Promise `.catch` handlers instead of having to handle both synchronous and asynchronous exception flows.
+
+Note about second argument: if it's specifically a true array, its values become respective arguments for the function call. Otherwise it is passed as is as the first argument for the function call.
+
+*For compatibility with earlier ECMAScript version, an alias `Promise.attempt()` is provided for `Promise.try()`.*
+
+<hr>
+
+#####`Promise.method(Function fn)` -> `Function`
+
+Returns a new function that wraps the given function `fn`. The new function will always return a promise that is fulfilled with the original functions return values or rejected with thrown exceptions from the original function.
+
+This method is convenient when a function can sometimes return synchronously or throw synchronously.
+
+Example without using `Promise.method`:
+
+```js
+MyClass.prototype.method = function(input) {
+    if (!this.isValid(input)) {
+        return Promise.reject(new TypeError("input is not valid"));
+    }
+
+    if (this.cache(input)) {
+        return Promise.resolve(this.someCachedValue);
+    }
+
+    return db.queryAsync(input).bind(this).then(function(value) {
+        this.someCachedValue = value;
+        return value;
+    });
+};
+```
+
+Using the same function `Promise.method`, there is no need to manually wrap direct return or throw values into a promise:
+
+```js
+MyClass.prototype.method = Promise.method(function(input) {
+    if (!this.isValid(input)) {
+        throw new TypeError("input is not valid");
+    }
+
+    if (this.cachedFor(input)) {
+        return this.someCachedValue;
+    }
+
+    return db.queryAsync(input).bind(this).then(function(value) {
+        this.someCachedValue = value;
+        return value;
+    });
+});
+```
+
+<hr>
+
+#####`Promise.resolve(dynamic value)` -> `Promise`
+
+Create a promise that is resolved with the given `value`. If `value` is a thenable or promise, the returned promise will assume its state.
+
+<hr>
+
+#####`Promise.reject(dynamic reason)` -> `Promise`
+
+Create a promise that is rejected with the given `reason`.
+
+<hr>
+
+#####`Promise.defer()` -> `PromiseResolver`
+
+Create a promise with undecided fate and return a `PromiseResolver` to control it. See [Promise resolution](#promise-resolution).
+
+The use of `Promise.defer` is discouraged - it is much more awkward and error-prone than using `new Promise`.
+
+<hr>
+
+#####`Promise.cast(dynamic value)` -> `Promise`
+
+Cast the given `value` to a trusted promise. If `value` is already a trusted `Promise`, it is returned as is. If `value` is not a thenable, a fulfilled Promise is returned with `value` as its fulfillment value. If `value` is a thenable (Promise-like object, like those returned by jQuery's `$.ajax`), returns a trusted Promise that assimilates the state of the thenable.
+
+Example: (`$` is jQuery)
+
+```js
+Promise.cast($.get("http://www.google.com")).then(function(){
+    //Returning a thenable from a handler is automatically
+    //cast to a trusted Promise as per Promises/A+ specification
+    return $.post("http://www.yahoo.com");
+}).then(function(){
+
+}).catch(function(e){
+    //jQuery doesn't throw real errors so use catch-all
+    console.log(e.statusText);
+});
+```
+
+<hr>
+
+#####`Promise.bind(dynamic thisArg)` -> `Promise`
+
+Sugar for `Promise.resolve(undefined).bind(thisArg);`. See [`.bind()`](#binddynamic-thisarg---promise).
+
+<hr>
+
+#####`Promise.is(dynamic value)` -> `boolean`
+
+See if `value` is a trusted Promise.
+
+```js
+Promise.is($.get("http://www.google.com")); //false , thenable returned from $.get is not a `Promise` instance
+Promise.is(Promise.cast($.get("http://www.google.com"))) //true, `.cast` cast the thenable into a `Promise`
+```
+
+Trusted Promises are promises originating in the currently used copy of Bluebird. Promises originating in other libraries are called thenables and are _not_ trusted promises. This method is used for checking if a passed value is of the same type as `Promise` itself creates.
+
+<hr>
+
+#####`Promise.longStackTraces()` -> `void`
+
+Call this right after the library is loaded to enabled long stack traces. Long stack traces cannot be disabled after being enabled, and cannot be enabled after promises have alread been created. Long stack traces imply a substantial performance penalty, around 4-5x for throughput and 0.5x for latency.
+
+Long stack traces are enabled by default in the debug build.
+
+To enable them in all instances of bluebird in node.js, use the environment variable `BLUEBIRD_DEBUG`:
+
+```
+BLUEBIRD_DEBUG=1 node server.js
+```
+
+You should enabled long stack traces if you want better debugging experience. For example:
+
+```js
+Promise.longStackTraces();
+Promise.resolve().then(function outer() {
+    return Promise.resolve().then(function inner() {
+        return Promise.resolve().then(function evenMoreInner() {
+            a.b.c.d()
+        }).catch(function catcher(e){
+            console.error(e.stack);
+        });
+    });
+});
+```
+
+Gives
+
+    ReferenceError: a is not defined
+        at evenMoreInner (<anonymous>:6:13)
+    From previous event:
+        at inner (<anonymous>:5:24)
+    From previous event:
+        at outer (<anonymous>:4:20)
+    From previous event:
+        at <anonymous>:3:9
+        at Object.InjectedScript._evaluateOn (<anonymous>:581:39)
+        at Object.InjectedScript._evaluateAndWrap (<anonymous>:540:52)
+        at Object.InjectedScript.evaluate (<anonymous>:459:21)
+
+While with long stack traces disabled, you would get:
+
+    ReferenceError: a is not defined
+        at evenMoreInner (<anonymous>:6:13)
+        at tryCatch1 (<anonymous>:41:19)
+        at Promise$_resolvePromise [as _resolvePromise] (<anonymous>:1739:13)
+        at Promise$_resolveLast [as _resolveLast] (<anonymous>:1520:14)
+        at Async$_consumeFunctionBuffer [as _consumeFunctionBuffer] (<anonymous>:560:33)
+        at Async$consumeFunctionBuffer (<anonymous>:515:14)
+        at MutationObserver.Promise$_Deferred (<anonymous>:433:17)
+
+On client side, long stack traces currently only work in Firefox and Chrome.
+
+<hr>
+
+##Progression
+
+#####`.progressed(Function handler)` -> `Promise`
+
+Shorthand for `.then(null, null, handler);`. Attach a progress handler that will be called if this promise is progressed. Returns a new promise chained from this promise.
+
+<hr>
+
+##Promise resolution
+
+A `PromiseResolver` can be used to control the fate of a promise. It is like "Deferred" known in jQuery. The `PromiseResolver` objects have a `.promise` property which returns a reference to the controlled promise that can be passed to clients. `.promise` of a `PromiseResolver` is not a getter function to match other implementations.
+
+The methods of a `PromiseResolver` have no effect if the fate of the underlying promise is already decided (follow, reject, fulfill).
+
+The use of `Promise.defer` and deferred objects is discouraged - it is much more awkward and error-prone than using `new Promise`.
+
+<hr>
+
+#####`.resolve(dynamic value)` -> `undefined`
+
+Resolve the underlying promise with `value` as the resolution value. If `value` is a thenable or a promise, the underlying promise will assume its state.
+
+<hr>
+
+#####`.reject(dynamic reason)` -> `undefined`
+
+Reject the underlying promise with `reason` as the rejection reason.
+
+<hr>
+
+#####`.progress(dynamic value)` -> `undefined`
+
+Progress the underlying promise with `value` as the progression value.
+
+Example
+
+```js
+function delay(ms) {
+    var resolver = Promise.defer();
+    var now = Date.now();
+    setTimeout(function(){
+        resolver.resolve(Date.now() - now);
+    }, ms);
+    return resolver.promise;
+}
+
+delay(500).then(function(ms){
+    console.log(ms + " ms passed");
+});
+```
+
+<hr>
+
+#####`.callback` -> `Function`
+
+Gives you a callback representation of the `PromiseResolver`. Note that this is not a method but a property. The callback accepts error object in first argument and success values on the 2nd parameter and the rest, I.E. node js conventions.
+
+If the the callback is called with multiple success values, the resolver fullfills its promise with an array of the values.
+
+```js
+var fs = require("fs");
+function readAbc() {
+    var resolver = Promise.defer();
+    fs.readFile("abc.txt", resolver.callback);
+    return resolver.promise;
+}
+
+readAbc()
+.then(function(abcContents) {
+    console.log(abcContents);
+})
+.catch(function(e) {
+    console.error(e);
+});
+```
+
+This example is an alternative to automatic promisification of node functions.
+
+*Performance tips*
+
+The `callback` is actually an accessor property (except on legacy browsers where it's eager data property) - so save the result if you need to call it multiple times.
+
+This is more efficient way of promisification than using `new Promise`.
+
+<hr>
+
+##Timers
+
+Methods to delay and time promises out.
+
+#####`.delay(int ms)` -> `Promise`
+
+Same as calling [`Promise.delay(this, ms)`](#promisedelaydynamic-value-int-ms---promise). With the exception that if this promise is [bound](#binddynamic-thisarg---promise) to a value, the returned promise is bound to that value too.
+
+<hr>
+
+#####`.timeout(int ms [, String message])` -> `Promise`
+
+Returns a promise that will be fulfilled with this promise's fulfillment value or rejection reason. However, if this promise is not fulfilled or rejected within `ms` milliseconds, the returned promise is rejected with a `Promise.TimeoutError` instance.
+
+You may specify a custom error message with the `message` parameter.
+
+The example function `fetchContent` tries to fetch the contents of a web page with a 50ms timeout and sleeping 100ms between each retry. If there is no response after 5 retries, then the returned promise is rejected with a `ServerError` (made up error type). Additionally the whole process can be cancelled from outside at any point.
+
+```js
+function fetchContent(retries) {
+    if (!retries) retries = 0;
+    var jqXHR = $.get("http://www.slowpage.com");
+    //Cast the jQuery promise into a bluebird promise
+    return Promise.cast(jqXHR)
+        .cancellable()
+        .timeout(50)
+        .catch(Promise.TimeoutError, function() {
+            if (retries < 5) {
+                return Promise.delay(100).then(function(){
+                    return fetchContent(retries+1);
+                });
+            }
+            else {
+                throw new ServerError("not responding after 5 retries");
+            }
+        })
+        .catch(Promise.CancellationError, function(er) {
+            jqXHR.abort();
+            throw er; //Don't swallow it
+        });
+}
+```
+
+<hr>
+
+#####`Promise.delay([dynamic value], int ms)` -> `Promise`
+
+Returns a promise that will be fulfilled with `value` (or `undefined`) after given `ms` milliseconds. If `value` is a promise, the delay will start counting down when it is fulfilled and the returned promise will be fulfilled with the fulfillment value of the `value` promise.
+
+```js
+Promise.delay(500).then(function(){
+    console.log("500 ms passed");
+    return "Hello world";
+}).delay(500).then(function(helloWorldString) {
+    console.log(helloWorldString);
+    console.log("another 500 ms passed") ;
+});
+```
+
+<hr>
+
+##Promisification
+
+#####`Promise.promisify(Function nodeFunction [, dynamic receiver])` -> `Function`
+
+Returns a function that will wrap the given `nodeFunction`. Instead of taking a callback, the returned function will return a promise whose fate is decided by the callback behavior of the given node function. The node function should conform to node.js convention of accepting a callback as last argument and calling that callback with error as the first argument and success value on the second argument.
+
+If the `nodeFunction` calls its callback with multiple success values, the fulfillment value will be an array of them.
+
+If you pass a `receiver`, the `nodeFunction` will be called as a method on the `receiver`.
+
+Example of promisifying the asynchronous `readFile` of node.js `fs`-module:
+
+```js
+var readFile = Promise.promisify(require("fs").readFile);
+
+readFile("myfile.js", "utf8").then(function(contents){
+    return eval(contents);
+}).then(function(result){
+    console.log("The result of evaluating myfile.js", result);
+}).catch(SyntaxError, function(e){
+    console.log("File had syntax error", e);
+//Catch any other error
+}).catch(function(e){
+    console.log("Error reading file", e);
+});
+```
+
+Note that if the node function is a method of some object, you need to pass the object as the second argument like so:
+
+```js
+var redisGet = Promise.promisify(redisClient.get, redisClient);
+redisGet.then(function(){
+    //...
+});
+```
+
+**Tip**
+
+Use [`.spread`](#spreadfunction-fulfilledhandler--function-rejectedhandler----promise) with APIs that have multiple success values:
+
+```js
+var Promise = require("bluebird");
+var request = Promise.promisify(require('request'));
+request("http://www.google.com").spread(function(request, body) {
+    console.log(body);
+}).catch(function(err) {
+    console.error(err);
+});
+```
+
+The above uses [request](https://github.com/mikeal/request) library which has a callback signature of multiple success values.
+
+<hr>
+
+#####`Promise.promisify(Object target)` -> `Object`
+
+This overload has been **deprecated**. The overload will continue working for now. The recommended method for promisifying multiple methods at once is [`Promise.promisifyAll(Object target)`](#promisepromisifyallobject-target---object)
+
+<hr>
+
+#####`Promise.promisifyAll(Object target)` -> `Object`
+
+Promisifies the entire object by going through the object's properties and creating an async equivalent of each function on the object and its prototype chain. The promisified method name will be the original method name postfixed with `Async`. Returns the input object.
+
+Note that the original methods on the object are not overwritten but new methods are created with the `Async`-postfix. For example, if you `promisifyAll()` the node.js `fs` object use `fs.statAsync()` to call the promisified `stat` method.
+
+Example:
+
+```js
+Promise.promisifyAll(RedisClient.prototype);
+
+//Later on, all redis client instances have promise returning functions:
+
+redisClient.hexistsAsync("myhash", "field").then(function(v){
+
+}).catch(function(e){
+
+});
+```
+
+If you don't want to write on foreign prototypes, you can sub-class the target and promisify your subclass:
+
+```js
+function MyRedisClient() {
+    RedisClient.apply(this, arguments);
+}
+MyRedisClient.prototype = Object.create(RedisClient.prototype);
+MyRedisClient.prototype.constructor = MyRedisClient;
+Promise.promisify(MyRedisClient.prototype);
+```
+
+The promisified methods will be written on the `MyRedisClient.prototype` instead. This specific example doesn't actually work with `node_redis` because the `createClient` factory is hardcoded to instantiate `RedisClient` from closure.
+
+
+It also works on singletons or specific instances:
+
+```js
+var fs = Promise.promisifyAll(require("fs"));
+
+fs.readFileAsync("myfile.js", "utf8").then(function(contents){
+    console.log(contents);
+}).catch(function(e){
+    console.error(e.stack);
+});
+```
+
+The entire prototype chain of the object is promisified on the object. Only enumerable are considered. If the object already has a promisified version of the method, it will be skipped. The target methods are assumed to conform to node.js callback convention of accepting a callback as last argument and calling that callback with error as the first argument and success value on the second argument. If the node method calls its callback with multiple success values, the fulfillment value will be an array of them.
+
+If a method already has `"Async"` postfix, it will be duplicated. E.g. `getAsync`'s promisified name is `getAsyncAsync`.
+
+<hr>
+
+#####`.nodeify([Function callback])` -> `Promise`
+
+Register a node-style callback on this promise. When this promise is is either fulfilled or rejected, the node callback will be called back with the node.js convention where error reason is the first argument and success value is the second argument. The error argument will be `null` in case of success.
+
+Returns back this promise instead of creating a new one. If the `callback` argument is not a function, this method does not do anything.
+
+This can be used to create APIs that both accept node-style callbacks and return promises:
+
+```js
+function getDataFor(input, callback) {
+    return dataFromDataBase(input).nodeify(callback);
+}
+```
+
+The above function can then make everyone happy.
+
+Promises:
+
+```js
+getDataFor("me").then(function(dataForMe) {
+    console.log(dataForMe);
+});
+```
+
+Normal callbacks:
+
+```js
+getDataFor("me", function(err, dataForMe) {
+    if( err ) {
+        console.error( err );
+    }
+    console.log(dataForMe);
+});
+```
+
+There is no effect on peformance if the user doesn't actually pass a node-style callback function.
+
+<hr>
+
+##Cancellation
+
+By default, a promise is not cancellable. A promise can be marked as cancellable with `.cancellable()`. A cancellable promise can be cancelled if it's not resolved. Cancelling a promise propagates to the farthest cancellable ancestor of the target promise that is still pending, and rejects that promise with `CancellationError`. The rejection will then propagate back to the original promise and to its descendants. This roughly follows the semantics described [here](https://github.com/promises-aplus/cancellation-spec/issues/7).
+
+Promises marked with `.cancellable()` return cancellable promises automatically.
+
+If you are the resolver for a promise, you can react to a cancel in your promise by catching the `CancellationError`:
+
+```js
+function ajaxGetAsync(url) {
+    var xhr = new XMLHttpRequest;
+    return new Promise(function (resolve, reject) {
+        xhr.addEventListener("error", reject);
+        xhr.addEventListener("load", resolve);
+        xhr.open("GET", url);
+        xhr.send(null);
+    }).cancellable().catch(Promise.CancellationError, function(e) {
+        xhr.abort();
+        throw e; //Don't swallow it
+    });
+}
+```
+
+<hr>
+
+#####`.cancellable()` -> `Promise`
+
+Marks this promise as cancellable. Promises by default are not cancellable after v0.11 and must be marked as such for [`.cancel()`](#cancel---promise) to have any effect. Marking a promise as cancellable is infectious and you don't need to remark any descendant promise.
+
+If you have code written prior v0.11 using cancellation, add calls to `.cancellable()` at the starts of promise chains that need to support
+cancellation in themselves or somewhere in their descendants.
+
+<hr>
+
+#####`.cancel()` -> `Promise`
+
+Cancel this promise. The cancellation will propagate
+to farthest cancellable ancestor promise which is still pending.
+
+That ancestor will then be rejected with a `CancellationError` (get a reference from `Promise.CancellationError`)
+object as the rejection reason.
+
+In a promise rejection handler you may check for a cancellation
+by seeing if the reason object has `.name === "Cancel"`.
+
+Promises are by default not cancellable. Use [`.cancellable()`](#cancellable---promise) to mark a promise as cancellable.
+
+<hr>
+
+#####`.fork([Function fulfilledHandler] [, Function rejectedHandler ] [, Function progressHandler ])` -> `Promise`
+
+Like `.then()`, but cancellation of the the returned promise
+or any of its descendant will not propagate cancellation
+to this promise or this promise's ancestors.
+
+<hr>
+
+#####`.uncancellable()` -> `Promise`
+
+Create an uncancellable promise based on this promise.
+
+<hr>
+
+#####`.isCancellable()` -> `boolean`
+
+See if this promise can be cancelled.
+
+<hr>
+
+##Synchronous inspection
+
+Because `.then()` must give asynchronous guarantees, it cannot be used to inspect a given promise's state synchronously. The following code won't work:
+
+```js
+var wasFulfilled = false;
+var wasRejected = false;
+var resolutionValueOrRejectionReason = null;
+somePromise.then(function(v){
+    wasFulfilled = true;
+    resolutionValueOrRejectionReason = v
+}).catch(function(v){
+    wasRejected = true;
+    resolutionValueOrRejectionReason = v
+});
+//Using the variables won't work here because .then must be called asynchronously
+```
+
+Synchronous inspection API allows you to do this like so:
+
+```js
+var inspection = somePromise.inspect();
+
+if(inspection.isFulfilled()){
+    console.log("Was fulfilled with", inspection.value());
+}
+```
+
+<hr>
+
+#####`.isFulfilled()` -> `boolean`
+
+See if this `promise` has been fulfilled.
+
+<hr>
+
+#####`.isRejected()` -> `boolean`
+
+See if this `promise` has been rejected.
+
+<hr>
+
+#####`.isPending()` -> `boolean`
+
+See if this `promise` is still defer.
+
+<hr>
+
+#####`.isResolved()` -> `boolean`
+
+See if this `promise` is resolved -> either fulfilled or rejected.
+
+<hr>
+
+#####`.inspect()` -> `PromiseInspection`
+
+Synchronously inspect the state of this `promise`. The `PromiseInspection` will represent the state of the promise as snapshotted at the time of calling `.inspect()`. It will have the following methods:
+
+`.isFulfilled()` -> `boolean`
+
+See if the underlying promise was fulfilled at the creation time of this inspection object.
+
+`.isRejected()` -> `boolean`
+
+See if the underlying promise was rejected at the creation time of this inspection object.
+
+`.isPending()` -> `boolean`
+
+See if the underlying promise was defer at the creation time of this inspection object.
+
+`.value()` -> `dynamic`, throws `TypeError`
+
+Get the fulfillment value of the underlying promise. Throws if the promise wasn't fulfilled at the creation time of this inspection object.
+
+`.error()` -> `dynamic`, throws `TypeError`
+
+Get the rejection reason for the underlying promise. Throws if the promise wasn't rejected at the creation time of this inspection object.
+
+<hr>
+
+##Generators
+
+Using ECMAScript6 generators feature to implement C# 5.0 `async/await` like syntax.
+
+#####`Promise.coroutine(GeneratorFunction generatorFunction)` -> `Function`
+
+Returns a function that can use `yield` to run asynchronous code synchronously. This feature requires the support of generators which are drafted in the next version of the language. Node version greater than `0.11.2` is required and needs to be executed with the `--harmony-generators` (or `--harmony`) command-line switch.
+
+This is the recommended, simplest and most performant way of using asynchronous generators with bluebird. It is even faster than typical promise code because the creation of new anonymous function identities at runtime can be completely avoided without obfuscating your code.
+
+```js
+var Promise = require("bluebird");
+
+function delay(ms) {
+    return new Promise(function(f){
+        setTimeout(f, ms);
+    });
+}
+
+function PingPong() {
+
+}
+
+PingPong.prototype.ping = Promise.coroutine(function* (val) {
+    console.log("Ping?", val)
+    yield delay(500)
+    this.pong(val+1)
+});
+
+PingPong.prototype.pong = Promise.coroutine(function* (val) {
+    console.log("Pong!", val)
+    yield delay(500);
+    this.ping(val+1)
+});
+
+var a = new PingPong();
+a.ping(0);
+```
+
+Running the example with node version at least 0.11.2:
+
+    $ node --harmony test.js
+    Ping? 0
+    Pong! 1
+    Ping? 2
+    Pong! 3
+    Ping? 4
+    Pong! 5
+    Ping? 6
+    Pong! 7
+    Ping? 8
+    ...
+
+When called, the coroutine function will start an instance of the generator and returns a promise for its final value.
+
+Doing `Promise.coroutine(function*(){})` is almost like using the C# `async` keyword to mark the function, with `yield` working as the `await` keyword. Promises are somewhat like `Task`s.
+
+**Tip**
+
+If you yield an array then its elements are implicitly waited for. You may add your own custom special treatments with [`Promise.coroutine.addYieldHandler`](#promisecoroutineaddyieldhandlerfunction-handler---void)
+
+You can combine it with ES6 destructuring for some neat syntax:
+
+```js
+var getData = Promise.coroutine(function* (urlA, urlB) {
+    [resultA, resultB] = yield [http.getAsync(urlA), http.getAsync(urlB)];
+    //use resultA
+    //use resultB
+});
+```
+
+You might wonder why not just do this?
+
+```js
+var getData = Promise.coroutine(function* (urlA, urlB) {
+    var resultA = yield http.getAsync(urlA);
+    var resultB = yield http.getAsync(urlB);
+});
+```
+
+The problem with the above is that the requests are not done in parallel. It will completely wait for request A to complete before even starting request B. In the array syntax both requests fire off at the same time in parallel.
+
+<hr>
+
+#####`Promise.coroutine.addYieldHandler(function handler)` -> `void`
+
+By default you can only yield Promises, Thenables and Arrays inside coroutines. You can use this function to add yielding support for arbitrary types.
+
+For example, if you wanted `yield 500` to be same as `yield Promise.delay(500)`:
+
+```js
+Promise.coroutine.addYieldHandler(function(value) {
+     if (typeof value === "number") return Promise.delay(value);
+});
+```
+
+Yield handlers are called when you yield something that is not supported by default. The first yield handler to return a promise or a thenable will be used.
+If no yield handler returns a promise or a thenable then an error is raised.
+
+An example of implementing callback support with `addYieldHandler`:
+
+*This is a demonstration of how powerful the feature is and not the recommended usage. For best performance you need to use `promisifyAll` and yield promises directly.*
+
+```js
+var Promise = require("bluebird");
+var fs = require("fs");
+
+var _ = (function() {
+    var promise = null;
+    Promise.coroutine.addYieldHandler(function(v) {
+        if (v === void 0 && promise != null) {
+            return promise;
+        }
+        promise = null;
+    });
+    return function() {
+        var def = Promise.defer();
+        promise = def.promise;
+        return def.callback;
+    };
+})();
+
+
+var readFileJSON = Promise.coroutine(function* (fileName) {
+   var contents = yield fs.readFile(fileName, "utf8", _());
+   return JSON.parse(contents);
+});
+```
+
+An example of implementing thunks support with `addYieldHandler`:
+
+*This is a demonstration of how powerful the feature is and not the recommended usage. For best performance you need to use `promisifyAll` and yield promises directly.*
+
+```js
+var Promise = require("bluebird");
+var fs = require("fs");
+
+Promise.coroutine.addYieldHandler(function(v) {
+    if (typeof v === "function") {
+        var def = Promise.defer();
+        try { v(def.callback); } catch(e) { def.reject(e); }
+        return def.promise;
+    }
+});
+
+var readFileThunk = function(fileName, encoding) {
+    return function(cb) {
+        return fs.readFile(fileName, encoding, cb);
+    };
+};
+
+var readFileJSON = Promise.coroutine(function* (fileName) {
+   var contents = yield readFileThunk(fileName, "utf8");
+   return JSON.parse(contents);
+});
+```
+
+##Utility
+
+Functions that could potentially be handy in some situations.
+
+#####`.call(String propertyName [, dynamic arg...])` -> `Promise`
+
+This is a convenience method for doing:
+
+```js
+promise.then(function(obj){
+    return obj[propertyName].call(obj, arg...);
+});
+```
+
+<hr>
+
+#####`.get(String propertyName)` -> `Promise`
+
+This is a convenience method for doing:
+
+```js
+promise.then(function(obj){
+    return obj[propertyName];
+});
+```
+
+<hr>
+
+#####`.return(dynamic value)` -> `Promise`
+
+Convenience method for:
+
+```js
+.then(function() {
+   return value;
+});
+```
+
+in the case where `value` doesn't change its value.
+
+That means `value` is bound at the time of calling `.return()` so this will not work as expected:
+
+```js
+function getData() {
+    var data;
+
+    return query().then(function(result) {
+        data = result;
+    }).return(data);
+}
+```
+
+because `data` is `undefined` at the time `.return` is called.
+
+*For compatibility with earlier ECMAScript version, an alias `.thenReturn()` is provided for `.return()`.*
+
+<hr>
+
+#####`.throw(dynamic reason)` -> `Promise`
+
+Convenience method for:
+
+```js
+.then(function() {
+   throw reason;
+});
+```
+
+Same limitations apply as with `.return()`.
+
+*For compatibility with earlier ECMAScript version, an alias `.thenThrow()` is provided for `.throw()`.*
+
+<hr>
+
+#####`.toString()` -> `String`
+
+<hr>
+
+#####`.toJSON()` -> `Object`
+
+This is implicitly called by `JSON.stringify` when serializing the object. Returns a serialized representation of the `Promise`.
+
+<hr>
+
+#####`Promise.noConflict()` -> `Object`
+
+This is relevant to browser environments with no module loader.
+
+Release control of the `Promise` namespace to whatever it was before this library was loaded. Returns a reference to the library namespace so you can attach it to something else.
+
+```html
+<!-- the other promise library must be loaded first -->
+<script type="text/javascript" src="/scripts/other_promise.js"></script>
+<script type="text/javascript" src="/scripts/bluebird_debug.js"></script>
+<script type="text/javascript">
+//Release control right after
+var Bluebird = Promise.noConflict();
+
+//Cast a promise from some other Promise library using the Promise namespace to Bluebird:
+var promise = Bluebird.cast(new Promise());
+</script>
+```
+
+<hr>
+
+#####`Promise.onPossiblyUnhandledRejection(Function handler)` -> `undefined`
+
+Add `handler` as the handler to call when there is a possibly unhandled rejection. The default handler logs the error stack to stderr or `console.error` in browsers.
+
+```html
+Promise.onPossiblyUnhandledRejection(function(e, promise){
+    throw e;
+});
+```
+
+Passing no value or a non-function will have the effect of removing any kind of handling for possibly unhandled rejections.
+
+<hr>
+
+##Collections
+
+Methods of `Promise` instances and core static methods of the Promise class to deal with
+collections of promises or mixed promises and values.
+
+#####`.all()` -> `Promise`
+
+Same as calling [Promise.all\(thisPromise\)](#promiseallarraydynamicpromise-values---promise). With the exception that if this promise is [bound](#binddynamic-thisarg---promise) to a value, the returned promise is bound to that value too.
+
+<hr>
+
+#####`.props()` -> `Promise`
+
+Same as calling [Promise.props\(thisPromise\)](#promisepropsobjectpromise-object---promise). With the exception that if this promise is [bound](#binddynamic-thisarg---promise) to a value, the returned promise is bound to that value too.
+
+<hr>
+
+#####`.settle()` -> `Promise`
+
+Same as calling [Promise.settle\(thisPromise\)](#promisesettlearraydynamicpromise-values---promise). With the exception that if this promise is [bound](#binddynamic-thisarg---promise) to a value, the returned promise is bound to that value too.
+
+<hr>
+
+#####`.any()` -> `Promise`
+
+Same as calling [Promise.any\(thisPromise\)](#promiseanyarraydynamicpromise-values---promise). With the exception that if this promise is [bound](#binddynamic-thisarg---promise) to a value, the returned promise is bound to that value too.
+
+<hr>
+
+#####`.race()` -> `Promise`
+
+Same as calling [Promise.race\(thisPromise\)](#promiseracearraypromise-promises---promise). With the exception that if this promise is [bound](#binddynamic-thisarg---promise) to a value, the returned promise is bound to that value too.
+
+<hr>
+
+
+#####`.some(int count)` -> `Promise`
+
+Same as calling [Promise.some\(thisPromise, count\)](#promisesomearraydynamicpromise-values-int-count---promise). With the exception that if this promise is [bound](#binddynamic-thisarg---promise) to a value, the returned promise is bound to that value too.
+
+<hr>
+
+#####`.spread([Function fulfilledHandler] [, Function rejectedHandler ])` -> `Promise`
+
+Like calling `.then`, but the fulfillment value or rejection reason is assumed to be an array, which is flattened to the formal parameters of the handlers.
+
+```js
+Promise.all([task1, task2, task3]).spread(function(result1, result2, result3){
+
+});
+```
+
+Normally when using `.then` the code would be like:
+
+```js
+Promise.all([task1, task2, task3]).then(function(results){
+    var result1 = results[0];
+    var result2 = results[1];
+    var result3 = results[2];
+});
+```
+
+This is useful when the `results` array contains items that are not conceptually items of the same list.
+
+<hr>
+
+#####`.map(Function mapper)` -> `Promise`
+
+Same as calling [Promise.map\(thisPromise, mapper\)](#promisemaparraydynamicpromise-values-function-mapper---promise). With the exception that if this promise is [bound](#binddynamic-thisarg---promise) to a value, the returned promise is bound to that value too.
+
+<hr>
+
+#####`.reduce(Function reducer [, dynamic initialValue])` -> `Promise`
+
+Same as calling [Promise.reduce\(thisPromise, Function reducer, initialValue\)](#promisereducearraydynamicpromise-values-function-reducer--dynamic-initialvalue---promise). With the exception that if this promise is [bound](#binddynamic-thisarg---promise) to a value, the returned promise is bound to that value too.
+
+<hr>
+
+#####`.filter(Function filterer)` -> `Promise`
+
+Same as calling [`Promise.filter(thisPromise, filterer)`](#promisefilterarraydynamicpromise-values-function-filterer---promise). With the exception that if this promise is [bound](#binddynamic-thisarg---promise) to a value, the returned promise is bound to that value too.
+
+In this example, a list of websites are pinged with 100ms timeout. [`.settle()`](#settle---promise) is used to wait until all pings are either fulfilled or rejected. Then the settled
+list of [`PromiseInspections`](#inspect---promiseinspection) is filtered for those that fulfilled (responded in under 100ms) and [`mapped`](#promisemaparraydynamicpromise-values-function-mapper---promise) to the actual fulfillment value.
+
+```js
+pingWebsitesAsync({timeout: 100}).settle()
+.filter(function(inspection){
+    return inspection.isFulfilled();
+})
+.map(function(inspection){
+    return inspection.value();
+})
+.then(function(websites){
+   //List of website names which answered
+});
+```
+
+The above pattern is actually reusable and can be captured in a method:
+
+```js
+Promise.prototype.settledWithFulfill = function() {
+    return this.settle()
+        .filter(function(inspection){
+            return inspection.isFulfilled();
+        })
+        .map(function(inspection){
+            return inspection.value();
+        });
+};
+```
+
+<hr>
+
+#####`Promise.all(Array<dynamic>|Promise values)` -> `Promise`
+
+Given an array, or a promise of an array, which contains promises (or a mix of promises and values) return a promise that is fulfilled when all the items in the array are fulfilled. The promise's fulfillment value is an array with fulfillment values at respective positions to the original array. If any promise in the array rejects, the returned promise is rejected with the rejection reason.
+
+In this example we create a promise that is fulfilled only when the pictures, comments and tweets are all loaded.
+
+```js
+Promise.all([getPictures(), getComments(), getTweets()]).then(function(results){
+    //Everything loaded and good to go
+    var pictures = results[0];
+    var comments = results[1];
+    var tweets = results[2];
+}).catch(function(e){
+    alertAsync("error when getting your stuff");
+});
+```
+
+See [`.spread()`](#spreadfunction-fulfilledhandler--function-rejectedhandler----promise) for a more convenient way to extract the fulfillment values.
+
+*The original array is not modified. The input array sparsity is retained in the resulting array.*
+
+<hr>
+
+#####`Promise.props(Object|Promise object)` -> `Promise`
+
+Like [`Promise.all`](#promiseallarraydynamic-values---promise) but for object properties instead of array items. Returns a promise that is fulfilled when all the properties of the object are fulfilled. The promise's fulfillment value is an object with fulfillment values at respective keys to the original object. If any promise in the object rejects, the returned promise is rejected with the rejection reason.
+
+If `object` is a trusted `Promise`, then it will be treated as a promise for object rather than for its properties. All other objects are treated for their properties as is returned by `Object.keys` - the object's own enumerable properties.
+
+```js
+Promise.props({
+    pictures: getPictures(),
+    comments: getComments(),
+    tweets: getTweets()
+}).then(function(result){
+    console.log(result.tweets, result.pictures, result.comments);
+});
+```
+
+Note that if you have no use for the result object other than retrieving the properties, it is more convenient to use [`Promise.all`](#promiseallarraydynamic-values---promise) and [`.spread()`](#spreadfunction-fulfilledhandler--function-rejectedhandler----promise):
+
+```js
+Promise.all([getPictures(), getComments(), getTweets()])
+.spread(function(pictures, comments, tweets) {
+    console.log(pictures, comments, tweets);
+});
+```
+
+*The original object is not modified.*
+
+<hr>
+
+#####`Promise.settle(Array<dynamic>|Promise values)` -> `Promise`
+
+Given an array, or a promise of an array, which contains promises (or a mix of promises and values) return a promise that is fulfilled when all the items in the array are either fulfilled or rejected. The fulfillment value is an array of [`PromiseInspection`](#inspect---promiseinspection) instances at respective positions in relation to the input array.
+
+*The original array is not modified. The input array sparsity is retained in the resulting array.*
+
+<hr>
+
+#####`Promise.any(Array<dynamic>|Promise values)` -> `Promise`
+
+Like [`Promise.some\(\)`](#someint-count---promise), with 1 as `count`. However, if the promise fulfills, the fulfillment value is not an array of 1 but the value directly.
+
+<hr>
+
+#####`Promise.race(Array|Promise promises)` -> `Promise`
+
+Given an array, or a promise of an array, which contains promises (or a mix of promises and values) return a promise that is fulfilled or rejected as soon as a promise in the array is fulfilled or rejected with the respective rejection reason or fulfillment value.
+
+Example of implementing a timeout in terms of `Promise.race`:
+
+```js
+var Promise = require("bluebird");
+var fs = Promise.promisifyAll(require("fs"));
+
+function delay(ms) {
+    return new Promise(function (v) {
+        setTimeout(v, ms);
+    });
+}
+
+function timeout(promise, time) {
+    var timeout = delay(time).then(function () {
+        throw new Promise.TimeoutError("Operation timed out after " + time + " ms");
+    });
+
+    return Promise.race([promise, timeout]);
+}
+
+timeout(fs.readFileAsync("slowfile.txt"), 300).then(function (contents) {
+    console.log("Here are the contents", contents);
+}).
+catch(Promise.TimeoutError, function (e) {
+    console.error("Sorry retrieving file took too long");
+});
+```
+
+**Note** If you pass empty array or a sparse array with no values, or a promise/thenable for such, it will be forever pending.
+
+<hr>
+
+#####`Promise.some(Array<dynamic>|Promise values, int count)` -> `Promise`
+
+Initiate a competetive race between multiple promises or values (values will become immediately fulfilled promises). When `count` amount of promises have been fulfilled, the returned promise is fulfilled with an array that contains the fulfillment values of the winners in order of resolution.
+
+This example pings 4 nameservers, and logs the fastest 2 on console:
+
+```js
+Promise.some([
+    ping("ns1.example.com"),
+    ping("ns2.example.com"),
+    ping("ns3.example.com"),
+    ping("ns4.example.com")
+], 2).spread(function(first, second) {
+    console.log(first, second);
+});
+```
+
+If too many promises are rejected so that the promise can never become fulfilled, it will be immediately rejected with an array of rejection reasons in the order they were thrown in.
+
+*The original array is not modified.*
+
+<hr>
+
+#####`Promise.join([dynamic value...])` -> `Promise`
+
+Like [`Promise.all\(\)`](#promiseallarraydynamic-values---promise) but instead of having to pass an array, the array is generated from the passed variadic arguments.
+
+So instead of:
+
+```js
+Promise.all([a, b]).spread(function(aResult, bResult) {
+
+});
+```
+
+You can do:
+
+```js
+Promise.join(a, b).spread(function(aResult, bResult) {
+
+});
+```
+
+<hr>
+
+#####`Promise.map(Array<dynamic>|Promise values, Function mapper)` -> `Promise`
+
+Map an array, or a promise of an array, which contains a promises (or a mix of promises and values) with the given `mapper` function with the signature `(item, index, arrayLength)` where `item` is the resolved value of a respective promise in the input array. If any promise in the input array is rejected the returned promise is rejected as well.
+
+If the `mapper` function returns promises or thenables, the returned promise will wait for all the mapped results to be resolved as well.
+
+*(TODO: an example where this is useful)*
+
+*The original array is not modified.*
+
+<hr>
+
+#####`Promise.reduce(Array<dynamic>|Promise values, Function reducer [, dynamic initialValue])` -> `Promise`
+
+Reduce an array, or a promise of an array, which contains a promises (or a mix of promises and values) with the given `reducer` function with the signature `(total, current, index, arrayLength)` where `item` is the resolved value of a respective promise in the input array. If any promise in the input array is rejected the returned promise is rejected as well.
+
+If the reducer function returns a promise or a thenable, the result for the promise is awaited for before continuing with next iteration.
+
+Read given files sequentially while summing their contents as an integer. Each file contains just the text `10`.
+
+```js
+Promise.reduce(["file1.txt", "file2.txt", "file3.txt"], function(total, fileName) {
+    return fs.readFileAsync(fileName, "utf8").then(function(contents) {
+        return total + parseInt(contents, 10);
+    });
+}, 0).then(function(total) {
+    //Total is 30
+});
+```
+
+*The original array is not modified. If `intialValue` is `undefined` (or a promise that resolves to `undefined`) and the array contains only 1 item, the callback will not be called and `undefined` is returned. If the array is empty, the callback will not be called and `initialValue` is returned (which may be `undefined`).*
+
+<hr>
+
+#####`Promise.filter(Array<dynamic>|Promise values, Function filterer)` -> `Promise`
+
+Filter an array, or a promise of an array, which contains a promises (or a mix of promises and values) with the given `filterer` function with the signature `(item, index, arrayLength)` where `item` is the resolved value of a respective promise in the input array. If any promise in the input array is rejected the returned promise is rejected as well.
+
+The return values from the filtered functions are coerced to booleans, with the exception of promises and thenables which are awaited for their eventual result.
+
+[See the instance method `.filter()` for an example.](#filterfunction-filterer---promise)
+
+*The original array is not modified.
+
+<hr>
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/CONTRIBUTING.md b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/CONTRIBUTING.md
new file mode 100644
index 0000000..7042bcb
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/CONTRIBUTING.md
@@ -0,0 +1,68 @@
+# Contributing to bluebird
+
+1. [Directory structure](#directory-structure)
+2. [Style guide](#style-guide)
+3. [Scripts and macros](#scripts-and-macros)
+4. [JSHint](#jshint)
+5. [Testing](#testing)
+
+## Directory structure
+
+- `/benchmark` contains benchmark scripts and stats of benchmarks
+
+- `/browser` contains scripts and output for browser testing environment
+
+- `/js` contains automatically generated build output. **NOTE** never commit any changes to these files to git.
+
+    - `/js/browser` contains a file suitable for use in browsers
+    - `/js/main` contains the main build to be used with node. The npm package points to /js/main/bluebird.js
+    - `/js/debug` contains the debug build to be used with node. Used when running tests
+    - `/js/zalgo` contains the zalgo build not to be used by any mortals.
+
+- `/node_modules` contains development dependencies such as grunt
+
+- `/src` contains the source code
+
+- `/test/mocha` contains tests using the mocha testing framework
+
+## Scripts and macros
+
+Scripts and macros are necessary for the code the code to remain readable and performant. For example, there is no way to turn the `arguments` object into an array without using a build step macro unless you want to compromise readability or performance.
+
+`/ast_passes.js` contains functions called ast passes that will parse input source code into an AST, modify it in some way and spit out new source code with the changes reflected.
+
+`/src/constants.js` contains declarations for constants that will be inlined in the resulting code during in the build step. JavaScript lacks a way to express constants, particularly if you are expecting the performance implications.
+
+`/Gruntfile.js` contains task definitions to be used with the Grunt build framework. It for example sets up source code transformations.
+
+`/bench` a bash script to run benchmarks.
+
+`/mocharun.js` a hack script to make mocha work when running multiple tests in parallel processes
+
+## JSHint
+
+Due to JSHint globals being dynamic, the JSHint rules are declared in `/Gruntfile.js`.
+
+## Style guide
+
+Use the same style as is used in the surrounding code.
+
+###Whitespace
+
+- No more than 80 columns per line
+- 4 space indentation
+- No trailing whitespace
+- LF at end of files
+- Curly braces can be left out of single statement `if/else/else if`s when it is obvious there will never be multiple statements such as null check at the top of a function for an early return.
+- Add an additional new line between logical sections of code.
+
+###Variables
+
+- Use multiple `var` statements instead of a single one with comma separator. Do not declare variables until you need them.
+
+###Equality and type checks
+
+- Always use `===` except when checking for null or undefined. To check for null or undefined, use `x == null`.
+- For checks that can be done with `typeof`: do not make helper functions, save results of `typeof` to a variable or make the type string a non-constant. Always write the check in the form `typeof expression === "constant string"` even if it feels like repeating yourself.
+
+## Testing
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/Gruntfile.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/Gruntfile.js
new file mode 100644
index 0000000..77fc162
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/Gruntfile.js
@@ -0,0 +1,725 @@
+"use strict";
+Error.stackTraceLimit = 100;
+var astPasses = require("./ast_passes.js");
+var node11 = parseInt(process.versions.node.split(".")[1], 10) >= 11;
+var Q = require("q");
+Q.longStackSupport = true;
+
+module.exports = function( grunt ) {
+    var isCI = !!grunt.option("ci");
+
+
+    function getBrowsers() {
+        //Terse format to generate the verbose format required by sauce
+        var browsers = {
+            "internet explorer|WIN8": ["10"],
+            "internet explorer|WIN8.1": ["11"],
+            "firefox|Windows 7": ["3.5", "3.6", "4", "25"],
+            "firefox|WIN8.1": null,
+            "chrome|Windows 7": null,
+            "safari|Windows 7": ["5"],
+            "iphone|OS X 10.8": ["6.0"]
+        };
+
+        var ret = [];
+        for( var browserAndPlatform in browsers) {
+            var split = browserAndPlatform.split("|");
+            var browser = split[0];
+            var platform = split[1];
+            var versions = browsers[browserAndPlatform];
+            if( versions != null ) {
+                for( var i = 0, len = versions.length; i < len; ++i ) {
+                    ret.push({
+                        browserName: browser,
+                        platform: platform,
+                        version: versions[i]
+                    });
+                }
+            }
+            else {
+                ret.push({
+                    browserName: browser,
+                    platform: platform
+                });
+            }
+        }
+        return ret;
+    }
+
+
+    var optionalModuleDependencyMap = {
+        "timers.js": ['Promise', 'INTERNAL'],
+        "any.js": ['Promise', 'Promise$_CreatePromiseArray', 'PromiseArray'],
+        "race.js": ['Promise', 'INTERNAL'],
+        "call_get.js": ['Promise'],
+        "filter.js": ['Promise', 'Promise$_CreatePromiseArray', 'PromiseArray', 'apiRejection'],
+        "generators.js": ['Promise', 'apiRejection', 'INTERNAL'],
+        "map.js": ['Promise', 'Promise$_CreatePromiseArray', 'PromiseArray', 'apiRejection'],
+        "nodeify.js": ['Promise'],
+        "promisify.js": ['Promise', 'INTERNAL'],
+        "props.js": ['Promise', 'PromiseArray'],
+        "reduce.js": ['Promise', 'Promise$_CreatePromiseArray', 'PromiseArray', 'apiRejection', 'INTERNAL'],
+        "settle.js": ['Promise', 'Promise$_CreatePromiseArray', 'PromiseArray'],
+        "some.js": ['Promise', 'Promise$_CreatePromiseArray', 'PromiseArray', 'apiRejection'],
+        "progress.js": ['Promise', 'isPromiseArrayProxy'],
+        "cancel.js": ['Promise', 'INTERNAL'],
+        "synchronous_inspection.js": ['Promise']
+
+    };
+
+    var optionalModuleRequireMap = {
+        "timers.js": true,
+        "race.js": true,
+        "any.js": true,
+        "call_get.js": true,
+        "filter.js": true,
+        "generators.js": true,
+        "map.js": true,
+        "nodeify.js": true,
+        "promisify.js": true,
+        "props.js": true,
+        "reduce.js": true,
+        "settle.js": true,
+        "some.js": true,
+        "progress.js": true,
+        "cancel.js": true,
+        "synchronous_inspection.js": true
+
+    };
+
+    function getOptionalRequireCode( srcs ) {
+        return srcs.reduce(function(ret, cur, i){
+            if( optionalModuleRequireMap[cur] ) {
+                ret += "require('./"+cur+"')("+ optionalModuleDependencyMap[cur] +");\n";
+            }
+            return ret;
+        }, "") + "\nPromise.prototype = Promise.prototype;\nreturn Promise;\n";
+    }
+
+    function getBrowserBuildHeader( sources ) {
+        var header = "/**\n * bluebird build version " + gruntConfig.pkg.version + "\n";
+        var enabledFeatures = ["core"];
+        var disabledFeatures = [];
+        featureLoop: for( var key in optionalModuleRequireMap ) {
+            for( var i = 0, len = sources.length; i < len; ++i ) {
+                var source = sources[i];
+                if( source.fileName === key ) {
+                    enabledFeatures.push( key.replace( ".js", "") );
+                    continue featureLoop;
+                }
+            }
+            disabledFeatures.push( key.replace( ".js", "") );
+        }
+
+        header += ( " * Features enabled: " + enabledFeatures.join(", ") + "\n" );
+
+        if( disabledFeatures.length ) {
+            header += " * Features disabled: " + disabledFeatures.join(", ") + "\n";
+        }
+        header += "*/\n";
+        return header;
+    }
+
+    function applyOptionalRequires( src, optionalRequireCode ) {
+        return src.replace( /};([^}]*)$/, optionalRequireCode + "\n};$1");
+    }
+
+    var CONSTANTS_FILE = './src/constants.js';
+    var BUILD_DEBUG_DEST = "./js/debug/bluebird.js";
+
+    var license;
+    function getLicense() {
+        if( !license ) {
+            var fs = require("fs");
+            var text = fs.readFileSync("LICENSE", "utf8");
+            text = text.split("\n").map(function(line, index){
+                return " * " + line;
+            }).join("\n")
+            license = "/**\n" + text + "\n */\n";
+        }
+        return license
+    }
+
+    var preserved;
+    function getLicensePreserve() {
+        if( !preserved ) {
+            var fs = require("fs");
+            var text = fs.readFileSync("LICENSE", "utf8");
+            text = text.split("\n").map(function(line, index){
+                if( index === 0 ) {
+                    return " * @preserve " + line;
+                }
+                return " * " + line;
+            }).join("\n")
+            preserved = "/**\n" + text + "\n */\n";
+        }
+        return preserved;
+    }
+
+    function writeFile( dest, content ) {
+        grunt.file.write( dest, content );
+        grunt.log.writeln('File "' + dest + '" created.');
+    }
+
+    function writeFileAsync( dest, content ) {
+        var fs = require("fs");
+        return Q.nfcall(fs.writeFile, dest, content).then(function(){
+            grunt.log.writeln('File "' + dest + '" created.');
+        });
+    }
+
+    var gruntConfig = {};
+
+    var getGlobals = function() {
+        var fs = require("fs");
+        var file = "./src/constants.js";
+        var contents = fs.readFileSync(file, "utf8");
+        var rconstantname = /CONSTANT\(\s*([^,]+)/g;
+        var m;
+        var globals = {
+            Error: true,
+            args: true,
+            INLINE_SLICE: false,
+            TypeError: true,
+            RangeError: true,
+            __DEBUG__: false,
+            __BROWSER__: false,
+            process: true,
+            "console": false,
+            "require": false,
+            "module": false,
+            "define": false
+        };
+        while( ( m = rconstantname.exec( contents ) ) ) {
+            globals[m[1]] = false;
+        }
+        return globals;
+    }
+
+    gruntConfig.pkg = grunt.file.readJSON("package.json");
+
+    gruntConfig.jshint = {
+        all: {
+            options: {
+                globals: getGlobals(),
+
+                "bitwise": false,
+                "camelcase": true,
+                "curly": true,
+                "eqeqeq": true,
+                "es3": true,
+                "forin": true,
+                "immed": true,
+                "latedef": false,
+                "newcap": true,
+                "noarg": true,
+                "noempty": true,
+                "nonew": true,
+                "plusplus": false,
+                "quotmark": "double",
+                "undef": true,
+                "unused": true,
+                "strict": false,
+                "trailing": true,
+                "maxparams": 6,
+                "maxlen": 80,
+
+                "asi": false,
+                "boss": true,
+                "eqnull": true,
+                "evil": true,
+                "expr": false,
+                "funcscope": false,
+                "globalstrict": false,
+                "lastsemic": false,
+                "laxcomma": false,
+                "laxbreak": false,
+                "loopfunc": true,
+                "multistr": true,
+                "proto": false,
+                "scripturl": true,
+                "smarttabs": false,
+                "shadow": true,
+                "sub": true,
+                "supernew": false,
+                "validthis": true,
+
+                "browser": true,
+                "jquery": true,
+                "devel": true,
+
+
+                '-W014': true,
+                '-W116': true,
+                '-W106': true,
+                '-W064': true,
+                '-W097': true
+            },
+
+            files: {
+                src: [
+                    "./src/finally.js",
+                    "./src/direct_resolve.js",
+                    "./src/synchronous_inspection.js",
+                    "./src/thenables.js",
+                    "./src/progress.js",
+                    "./src/cancel.js",
+                    "./src/any.js",
+                    "./src/race.js",
+                    "./src/call_get.js",
+                    "./src/filter.js",
+                    "./src/generators.js",
+                    "./src/map.js",
+                    "./src/nodeify.js",
+                    "./src/promisify.js",
+                    "./src/props.js",
+                    "./src/reduce.js",
+                    "./src/settle.js",
+                    "./src/some.js",
+                    "./src/util.js",
+                    "./src/schedule.js",
+                    "./src/queue.js",
+                    "./src/errors.js",
+                    "./src/captured_trace.js",
+                    "./src/async.js",
+                    "./src/catch_filter.js",
+                    "./src/promise.js",
+                    "./src/promise_array.js",
+                    "./src/settled_promise_array.js",
+                    "./src/some_promise_array.js",
+                    "./src/properties_promise_array.js",
+                    "./src/promise_inspection.js",
+                    "./src/promise_resolver.js",
+                    "./src/promise_spawn.js"
+                ]
+            }
+        }
+    };
+
+    if( !isCI ) {
+        gruntConfig.jshint.all.options.reporter = require("jshint-stylish");
+    }
+
+    gruntConfig.connect = {
+        server: {
+            options: {
+                base: "./browser",
+                port: 9999
+            }
+        }
+    };
+
+    gruntConfig.watch = {};
+
+    gruntConfig["saucelabs-mocha"] = {
+        all: {
+            options: {
+                urls: ["http://127.0.0.1:9999/index.html"],
+                tunnelTimeout: 5,
+                build: process.env.TRAVIS_JOB_ID,
+                concurrency: 3,
+                browsers: getBrowsers(),
+                testname: "mocha tests",
+                tags: ["master"]
+            }
+        }
+    };
+
+    gruntConfig.bump = {
+      options: {
+        files: ['package.json'],
+        updateConfigs: [],
+        commit: true,
+        commitMessage: 'Release v%VERSION%',
+        commitFiles: ['-a'],
+        createTag: true,
+        tagName: 'v%VERSION%',
+        tagMessage: 'Version %VERSION%',
+        false: true,
+        pushTo: 'master',
+        gitDescribeOptions: '--tags --always --abbrev=1 --dirty=-d' // options to use with '$ git describe'
+      }
+    };
+
+    grunt.initConfig(gruntConfig);
+    grunt.loadNpmTasks("grunt-contrib-connect");
+    grunt.loadNpmTasks("grunt-saucelabs");
+    grunt.loadNpmTasks('grunt-contrib-jshint');
+    grunt.loadNpmTasks('grunt-contrib-watch');
+    grunt.loadNpmTasks('grunt-contrib-concat');
+
+    function runIndependentTest( file, cb , env) {
+        var fs = require("fs");
+        var path = require("path");
+        var sys = require('sys');
+        var spawn = require('child_process').spawn;
+        var p = path.join(process.cwd(), "test");
+
+        var stdio = [
+            'ignore',
+            grunt.option("verbose")
+                ? process.stdout
+                : 'ignore',
+            process.stderr
+        ];
+        var flags = node11 ? ["--harmony-generators"] : [];
+        flags.push("--allow-natives-syntax");
+        if( file.indexOf( "mocha/") > -1 || file === "aplus.js" ) {
+            var node = spawn(typeof node11 === "string" ? node11 : 'node',
+                flags.concat(["../mocharun.js", file]),
+                {cwd: p, stdio: stdio, env: env});
+        }
+        else {
+            var node = spawn('node', flags.concat(["./"+file]),
+                             {cwd: p, stdio: stdio, env:env});
+        }
+        node.on('exit', exit );
+
+        function exit( code ) {
+            if( code !== 0 ) {
+                cb(new Error("process didn't exit normally. Code: " + code));
+            }
+            else {
+                cb(null);
+            }
+        }
+
+
+    }
+
+    function buildMain( sources, optionalRequireCode ) {
+        var fs = require("fs");
+        var Q = require("q");
+        var root = cleanDirectory("./js/main/");
+
+        return Q.all(sources.map(function( source ) {
+            var src = astPasses.removeAsserts( source.sourceCode, source.fileName );
+            src = astPasses.inlineExpansion( src, source.fileName );
+            src = astPasses.expandConstants( src, source.fileName );
+            src = src.replace( /__DEBUG__/g, "false" );
+            src = src.replace( /__BROWSER__/g, "false" );
+            if( source.fileName === "promise.js" ) {
+                src = applyOptionalRequires( src, optionalRequireCode );
+            }
+            var path = root + source.fileName;
+            return writeFileAsync(path, src);
+        }));
+    }
+
+    function buildDebug( sources, optionalRequireCode ) {
+        var fs = require("fs");
+        var Q = require("q");
+        var root = cleanDirectory("./js/debug/");
+
+        return Q.nfcall(require('mkdirp'), root).then(function(){
+            return Q.all(sources.map(function( source ) {
+                var src = astPasses.expandAsserts( source.sourceCode, source.fileName );
+                src = astPasses.inlineExpansion( src, source.fileName );
+                src = astPasses.expandConstants( src, source.fileName );
+                src = src.replace( /__DEBUG__/g, "true" );
+                src = src.replace( /__BROWSER__/g, "false" );
+                if( source.fileName === "promise.js" ) {
+                    src = applyOptionalRequires( src, optionalRequireCode );
+                }
+                var path = root + source.fileName;
+                return writeFileAsync(path, src);
+            }));
+        });
+    }
+
+    function buildZalgo( sources, optionalRequireCode ) {
+        var fs = require("fs");
+        var Q = require("q");
+        var root = cleanDirectory("./js/zalgo/");
+
+        return Q.all(sources.map(function( source ) {
+            var src = astPasses.removeAsserts( source.sourceCode, source.fileName );
+            src = astPasses.inlineExpansion( src, source.fileName );
+            src = astPasses.expandConstants( src, source.fileName );
+            src = astPasses.asyncConvert( src, "async", "invoke", source.fileName);
+            src = src.replace( /__DEBUG__/g, "false" );
+            src = src.replace( /__BROWSER__/g, "false" );
+            if( source.fileName === "promise.js" ) {
+                src = applyOptionalRequires( src, optionalRequireCode );
+            }
+            var path = root + source.fileName;
+            return writeFileAsync(path, src);
+        }));
+    }
+
+    function buildBrowser( sources ) {
+        var fs = require("fs");
+        var browserify = require("browserify");
+        var b = browserify("./js/main/bluebird.js");
+        var dest = "./js/browser/bluebird.js";
+
+        var header = getBrowserBuildHeader( sources );
+
+        return Q.nbind(b.bundle, b)({
+                detectGlobals: false,
+                standalone: "Promise"
+        }).then(function(src) {
+            return writeFileAsync( dest,
+                getLicensePreserve() + src )
+        }).then(function() {
+            return Q.nfcall(fs.readFile, dest, "utf8" );
+        }).then(function( src ) {
+            src = header + src;
+            return Q.nfcall(fs.writeFile, dest, src );
+        });
+    }
+
+    function cleanDirectory(dir) {
+        if (isCI) return dir;
+        var fs = require("fs");
+        require("rimraf").sync(dir);
+        fs.mkdirSync(dir);
+        return dir;
+    }
+
+    function getOptionalPathsFromOption( opt ) {
+        opt = (opt + "").toLowerCase().split(/\s+/g);
+        return optionalPaths.filter(function(v){
+            v = v.replace("./src/", "").replace( ".js", "" ).toLowerCase();
+            return opt.indexOf(v) > -1;
+        });
+    }
+
+    var optionalPaths = [
+        "./src/timers.js",
+        "./src/synchronous_inspection.js",
+        "./src/any.js",
+        "./src/race.js",
+        "./src/call_get.js",
+        "./src/filter.js",
+        "./src/generators.js",
+        "./src/map.js",
+        "./src/nodeify.js",
+        "./src/promisify.js",
+        "./src/props.js",
+        "./src/reduce.js",
+        "./src/settle.js",
+        "./src/some.js",
+        "./src/progress.js",
+        "./src/cancel.js"
+    ];
+
+    var mandatoryPaths = [
+        "./src/finally.js",
+        "./src/es5.js",
+        "./src/bluebird.js",
+        "./src/thenables.js",
+        "./src/assert.js",
+        "./src/global.js",
+        "./src/util.js",
+        "./src/schedule.js",
+        "./src/queue.js",
+        "./src/errors.js",
+        "./src/errors_api_rejection.js",
+        "./src/captured_trace.js",
+        "./src/async.js",
+        "./src/catch_filter.js",
+        "./src/promise.js",
+        "./src/promise_array.js",
+        "./src/settled_promise_array.js",
+        "./src/some_promise_array.js",
+        "./src/properties_promise_array.js",
+        "./src/promise_inspection.js",
+        "./src/promise_resolver.js",
+        "./src/promise_spawn.js",
+        "./src/direct_resolve.js"
+    ];
+
+
+
+    function build( paths, isCI ) {
+        var fs = require("fs");
+        astPasses.readConstants(fs.readFileSync(CONSTANTS_FILE, "utf8"), CONSTANTS_FILE);
+        if( !paths ) {
+            paths = optionalPaths.concat(mandatoryPaths);
+        }
+        var optionalRequireCode = getOptionalRequireCode(paths.map(function(v) {
+            return v.replace("./src/", "");
+        }));
+
+        var Q = require("q");
+
+        var promises = [];
+        var sources = paths.map(function(v){
+            var promise = Q.nfcall(fs.readFile, v, "utf8");
+            promises.push(promise);
+            var ret = {};
+
+            ret.fileName = v.replace("./src/", "");
+            ret.sourceCode = promise.then(function(v){
+                ret.sourceCode = v;
+            });
+            return ret;
+        });
+
+        //Perform common AST passes on all builds
+        return Q.all(promises.slice()).then(function(){
+            sources.forEach( function( source ) {
+                var src = source.sourceCode
+                src = astPasses.removeComments(src, source.fileName);
+                src = getLicense() + src;
+                source.sourceCode = src;
+            });
+
+            if( isCI ) {
+                return buildDebug( sources, optionalRequireCode );
+            }
+            else {
+                return Q.all([
+                    buildMain( sources, optionalRequireCode ).then( function() {
+                        return buildBrowser( sources );
+                    }),
+                    buildDebug( sources, optionalRequireCode ),
+                    buildZalgo( sources, optionalRequireCode )
+                ]);
+            }
+        });
+    }
+
+    String.prototype.contains = function String$contains( str ) {
+        return this.indexOf( str ) >= 0;
+    };
+
+    function isSlowTest( file ) {
+        return file.contains("2.3.3") ||
+            file.contains("bind") ||
+            file.contains("unhandled_rejections");
+    }
+
+    function testRun( testOption ) {
+        var fs = require("fs");
+        var path = require("path");
+        var done = this.async();
+
+        var totalTests = 0;
+        var testsDone = 0;
+        function testDone() {
+            testsDone++;
+            if( testsDone >= totalTests ) {
+                done();
+            }
+        }
+        var files;
+        if( testOption === "aplus" ) {
+            files = fs.readdirSync("test/mocha").filter(function(f){
+                return /^\d+\.\d+\.\d+/.test(f);
+            }).map(function( f ){
+                return "mocha/" + f;
+            });
+        }
+        else {
+            files = testOption === "all"
+                ? fs.readdirSync('test')
+                    .concat(fs.readdirSync('test/mocha')
+                        .map(function(fileName){
+                            return "mocha/" + fileName
+                        })
+                    )
+                : [testOption + ".js" ];
+
+
+            if( testOption !== "all" &&
+                !fs.existsSync( "./test/" + files[0] ) ) {
+                files[0] = "mocha/" + files[0];
+            }
+        }
+        files = files.filter(function(fileName){
+            if( !node11 && fileName.indexOf("generator") > -1 ) {
+                return false;
+            }
+            return /\.js$/.test(fileName);
+        }).map(function(f){
+            return f.replace( /(\d)(\d)(\d)/, "$1.$2.$3" );
+        });
+
+
+        var slowTests = files.filter(isSlowTest);
+        files = files.filter(function(file){
+            return !isSlowTest(file);
+        });
+
+        function runFile(file) {
+            totalTests++;
+            grunt.log.writeln("Running test " + file );
+            var env = undefined;
+            if (file.indexOf("bluebird-debug-env-flag") >= 0) {
+                env = Object.create(process.env);
+                env["BLUEBIRD_DEBUG"] = true;
+            }
+            runIndependentTest(file, function(err) {
+                if( err ) throw new Error(err + " " + file + " failed");
+                grunt.log.writeln("Test " + file + " succeeded");
+                testDone();
+                if( files.length > 0 ) {
+                    runFile( files.shift() );
+                }
+            }, env);
+        }
+
+        slowTests.forEach(runFile);
+
+        var maxParallelProcesses = 10;
+        var len = Math.min( files.length, maxParallelProcesses );
+        for( var i = 0; i < len; ++i ) {
+            runFile( files.shift() );
+        }
+    }
+
+    grunt.registerTask( "build", function() {
+
+        var done = this.async();
+        var features = grunt.option("features");
+        var paths = null;
+        if( features ) {
+            paths = getOptionalPathsFromOption( features ).concat( mandatoryPaths );
+        }
+
+        build( paths, isCI ).then(function() {
+            done();
+        }).catch(function(e) {
+            if( e.fileName && e.stack ) {
+                console.log(e.scriptSrc);
+                var stack = e.stack.split("\n");
+                stack[0] = stack[0] + " " + e.fileName;
+                console.error(stack.join("\n"));
+                if (!grunt.option("verbose")) {
+                    console.error("use --verbose to see the source code");
+                }
+
+            }
+            else {
+                console.error(e.stack);
+            }
+            done(false);
+        });
+    });
+
+    grunt.registerTask( "testrun", function(){
+        var testOption = grunt.option("run");
+        var node11path = grunt.option("node11");
+
+        if (typeof node11path === "string" && node11path) {
+            node11 = node11path;
+        }
+
+        if( !testOption ) testOption = "all";
+        else {
+            testOption = ("" + testOption);
+            testOption = testOption
+                .replace( /\.js$/, "" )
+                .replace( /[^a-zA-Z0-9_-]/g, "" );
+        }
+        testRun.call( this, testOption );
+    });
+
+    grunt.registerTask( "test", ["jshint", "build", "testrun"] );
+    grunt.registerTask( "test-browser", ["connect", "saucelabs-mocha"]);
+    grunt.registerTask( "default", ["jshint", "build"] );
+    grunt.registerTask( "dev", ["connect", "watch"] );
+
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/LICENSE b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/LICENSE
new file mode 100644
index 0000000..9ed7b98
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2014 Petka Antonov
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:</p>
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/README.md b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/README.md
new file mode 100644
index 0000000..7d4d1ae
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/README.md
@@ -0,0 +1,696 @@
+[![Build Status](https://travis-ci.org/petkaantonov/bluebird.png?branch=master)](https://travis-ci.org/petkaantonov/bluebird)
+
+<a href="http://promisesaplus.com/">
+    <img src="http://promisesaplus.com/assets/logo-small.png" alt="Promises/A+ logo"
+         title="Promises/A+ 1.0 compliant" align="right" />
+</a>
+
+#Introduction
+
+Bluebird is a fully featured [promise](#what-are-promises-and-why-should-i-use-them) library with focus on innovative features and performance
+
+#Topics
+
+- [Features](#features)
+- [Quick start](#quick-start)
+- [API Reference and examples](https://github.com/petkaantonov/bluebird/blob/master/API.md)
+- [What are promises and why should I use them?](#what-are-promises-and-why-should-i-use-them)
+- [Questions and issues](#questions-and-issues)
+- [Error handling](#error-handling)
+- [Development](#development)
+    - [Testing](#testing)
+    - [Benchmarking](#benchmarks)
+    - [Custom builds](#custom-builds)
+    - [For library authors](#for-library-authors)
+- [What is the sync build?](#what-is-the-sync-build)
+- [License](#license)
+- [Snippets for common problems](https://github.com/petkaantonov/bluebird/wiki/Snippets)
+- [Promise anti-patterns](https://github.com/petkaantonov/bluebird/wiki/Promise-anti-patterns)
+- [Changelog](https://github.com/petkaantonov/bluebird/blob/master/changelog.md)
+- [Optimization guide](#optimization-guide)
+
+#Features:
+
+- [Promises A+ 2.0.2](http://promisesaplus.com)
+- [Cancellation](https://github.com/promises-aplus)
+- [Progression](https://github.com/promises-aplus/progress-spec)
+- [Synchronous inspection](https://github.com/promises-aplus/synchronous-inspection-spec)
+- [`.bind`](https://github.com/petkaantonov/bluebird/blob/master/API.md#binddynamic-thisarg---promise)
+- [Complete parallel for C# 5.0 async and await](https://github.com/petkaantonov/bluebird/blob/master/API.md#promisecoroutinegeneratorfunction-generatorfunction---function)
+- [Collection methods](https://github.com/petkaantonov/bluebird/blob/master/API.md#collections) such as All, any, some, settle, map, filter, reduce, spread, join, race...
+- [Practical debugging solutions](#error-handling) such as unhandled rejection reporting, typed catches, catching only what you expect and very long, relevant stack traces without losing perf
+- [Sick performance](https://github.com/petkaantonov/bluebird/tree/master/benchmark/stats)
+
+Passes [AP2](https://github.com/petkaantonov/bluebird/tree/master/test/mocha), [AP3](https://github.com/petkaantonov/bluebird/tree/master/test/mocha), [Cancellation](https://github.com/petkaantonov/bluebird/blob/master/test/mocha/cancel.js), [Progress](https://github.com/petkaantonov/bluebird/blob/master/test/mocha/q_progress.js) tests and more. See [testing](#testing).
+
+<hr>
+
+#Quick start
+
+##Node.js
+
+    npm install bluebird
+
+Then:
+
+```js
+var Promise = require("bluebird");
+```
+
+##Browsers
+
+Download the [bluebird.js](https://github.com/petkaantonov/bluebird/tree/master/js/browser) file. And then use a script tag:
+
+```html
+<script type="text/javascript" src="/scripts/bluebird.js"></script>
+```
+
+The global variable `Promise` becomes available after the above script tag.
+
+####Browser support
+
+Browsers that [implement ECMA-262, edition 3](http://en.wikipedia.org/wiki/Ecmascript#Implementations) and later are supported.
+
+[![Selenium Test Status](https://saucelabs.com/browser-matrix/petka_antonov.svg)](https://saucelabs.com/u/petka_antonov)
+
+*IE7 and IE8 had to be removed from tests due to SauceLabs bug but are supported and pass all tests*
+
+**Note** that in ECMA-262, edition 3 (IE7, IE8 etc) it is not possible to use methods that have keyword names like `.catch` and `.finally`. The [API documentation](https://github.com/petkaantonov/bluebird/blob/master/API.md) always lists a compatible alternative name that you can use if you need to support these browsers. For example `.catch` is replaced with `.caught` and `.finally` with `.lastly`.
+
+Also, [long stack trace](https://github.com/petkaantonov/bluebird/blob/master/API.md#promiselongstacktraces---void) support is only available in Chrome and Firefox.
+
+<sub>Previously bluebird required es5-shim.js and es5-sham.js to support Edition 3 - these are **no longer required** as of **0.10.4**.</sub>
+
+After quick start, see [API Reference and examples](https://github.com/petkaantonov/bluebird/blob/master/API.md)
+
+<hr>
+
+#What are promises and why should I use them?
+
+You should use promises to turn this:
+
+```js
+readFile("file.json", function(err, val) {
+    if( err ) {
+        console.error("unable to read file");
+    }
+    else {
+        try {
+            val = JSON.parse(val);
+            console.log(val.success);
+        }
+        catch( e ) {
+            console.error("invalid json in file");
+        }
+    }
+});
+```
+
+Into this:
+
+```js
+readFile("file.json").then(JSON.parse).then(function(val) {
+    console.log(val.success);
+})
+.catch(SyntaxError, function(e) {
+    console.error("invalid json in file");
+})
+.catch(function(e){
+    console.error("unable to read file")
+});
+```
+
+Actually you might notice the latter has a lot in common with code that would do the same using synchronous I/O:
+
+```js
+try {
+    var val = JSON.parse(readFile("file.json"));
+    console.log(val.success);
+}
+//Syntax actually not supported in JS but drives the point
+catch(SyntaxError e) {
+    console.error("invalid json in file");
+}
+catch(Error e) {
+    console.error("unable to read file")
+}
+```
+
+And that is the point - being able to have something that is a lot like `return` and `throw` in synchronous code.
+
+You can also use promises to improve code that was written with callback helpers:
+
+
+```js
+//Copyright Plato http://stackoverflow.com/a/19385911/995876
+//CC BY-SA 2.5
+mapSeries(URLs, function (URL, done) {
+    var options = {};
+    needle.get(URL, options, function (error, response, body) {
+        if (error) {
+            return done(error)
+        }
+        try {
+            var ret = JSON.parse(body);
+            return done(null, ret);
+        }
+        catch (e) {
+            done(e);
+        }
+    });
+}, function (err, results) {
+    if (err) {
+        console.log(err)
+    } else {
+        console.log('All Needle requests successful');
+        // results is a 1 to 1 mapping in order of URLs > needle.body
+        processAndSaveAllInDB(results, function (err) {
+            if (err) {
+                return done(err)
+            }
+            console.log('All Needle requests saved');
+            done(null);
+        });
+    }
+});
+```
+
+Is more pleasing to the eye when done with promises:
+
+```js
+Promise.promisifyAll(needle);
+var options = {};
+
+var current = Promise.resolve();
+Promise.map(URLs, function(URL) {
+    current = current.then(function () {
+        return needle.getAsync(URL, options);
+    });
+    return current;
+}).map(function(responseAndBody){
+    return JSON.parse(responseAndBody[1]);
+}).then(function (results) {
+    return processAndSaveAllInDB(results);
+}).then(function(){
+    console.log('All Needle requests saved');
+}).catch(function (e) {
+    console.log(e);
+});
+```
+
+Also promises don't just give you correspondences for synchronous features but can also be used as limited event emitters or callback aggregators.
+
+More reading:
+
+ - [Promise nuggets](http://spion.github.io/promise-nuggets/)
+ - [Why I am switching to promises](http://spion.github.io/posts/why-i-am-switching-to-promises.html)
+ - [What is the the point of promises](http://domenic.me/2012/10/14/youre-missing-the-point-of-promises/#toc_1)
+ - [Snippets for common problems](https://github.com/petkaantonov/bluebird/wiki/Snippets)
+ - [Promise anti-patterns](https://github.com/petkaantonov/bluebird/wiki/Promise-anti-patterns)
+
+#Questions and issues
+
+If you find a bug in bluebird or have a feature request, file an issue in the [github issue tracker](https://github.com/petkaantonov/bluebird/issues). Anything else, such as questions for help in using the library, should be posted in [StackOverflow](http://stackoverflow.com/questions/tagged/bluebird) under tags `promise` and `bluebird`.
+
+#Error handling
+
+This is a problem every promise library needs to handle in some way. Unhandled rejections/exceptions don't really have a good agreed-on asynchronous correspondence. The problem is that it is impossible to predict the future and know if a rejected promise will eventually be handled.
+
+There are two common pragmatic attempts at solving the problem that promise libraries do.
+
+The more popular one is to have the user explicitly communicate that they are done and any unhandled rejections should be thrown, like so:
+
+```js
+download().then(...).then(...).done();
+```
+
+For handling this problem, in my opinion, this is completely unacceptable and pointless. The user must remember to explicitly call `.done` and that cannot be justified when the problem is forgetting to create an error handler in the first place.
+
+The second approach, which is what bluebird by default takes, is to call a registered handler if a rejection is unhandled by the start of a second turn. The default handler is to write the stack trace to stderr or `console.error` in browsers. This is close to what happens with synchronous code - your code doens't work as expected and you open console and see a stack trace. Nice.
+
+Of course this is not perfect, if your code for some reason needs to swoop in and attach error handler to some promise after the promise has been hanging around a while then you will see annoying messages. In that case you can use the `.done()` method to signal that any hanging exceptions should be thrown.
+
+If you want to override the default handler for these possibly unhandled rejections, you can pass yours like so:
+
+```js
+Promise.onPossiblyUnhandledRejection(function(error){
+    throw error;
+});
+```
+
+If you want to also enable long stack traces, call:
+
+```js
+Promise.longStackTraces();
+```
+
+right after the library is loaded.
+
+In node.js use the environment flag `BLUEBIRD_DEBUG`:
+
+```
+BLUEBIRD_DEBUG=1 node server.js
+```
+
+to enable long stack traces in all instances of bluebird.
+
+Long stack traces cannot be disabled after being enabled, and cannot be enabled after promises have alread been created. Long stack traces imply a substantial performance penalty, even after using every trick to optimize them.
+
+Long stack traces are enabled by default in the debug build.
+
+####Expected and unexpected errors
+
+A practical problem with Promises/A+ is that it models Javascript `try-catch` too closely for its own good. Therefore by default promises inherit `try-catch` warts such as the inability to specify the error types that the catch block is eligible for. It is an anti-pattern in every other language to use catch-all handlers because they swallow exceptions that you might not know about.
+
+Now, Javascript does have a perfectly fine and working way of creating error type hierarchies. It is still quite awkward to use them with the built-in `try-catch` however:
+
+```js
+try {
+    //code
+}
+catch(e) {
+    if( e instanceof WhatIWantError) {
+        //handle
+    }
+    else {
+        throw e;
+    }
+}
+```
+
+Without such checking, unexpected errors would be silently swallowed. However, with promises, bluebird brings the future (hopefully) here now and extends the `.catch` to [accept potential error type eligibility](https://github.com/petkaantonov/bluebird/blob/master/API.md#catchfunction-errorclass-function-handler---promise).
+
+For instance here it is expected that some evil or incompetent entity will try to crash our server from `SyntaxError` by providing syntactically invalid JSON:
+
+```js
+getJSONFromSomewhere().then(function(jsonString) {
+    return JSON.parse(jsonString);
+}).then(function(object) {
+    console.log("it was valid json: ", object);
+}).catch(SyntaxError, function(e){
+    console.log("don't be evil");
+});
+```
+
+Here any kind of unexpected error will automatically reported on stderr along with a stack trace because we only register a handler for the expected `SyntaxError`.
+
+Ok, so, that's pretty neat. But actually not many libraries define error types and it is in fact a complete ghetto out there with ad hoc strings being attached as some arbitrary property name like `.name`, `.type`, `.code`, not having any property at all or even throwing strings as errors and so on. So how can we still listen for expected errors?
+
+Bluebird defines a special error type `RejectionError` (you can get a reference from `Promise.RejectionError`). This type of error is given as rejection reason by promisified methods when
+their underlying library gives an untyped, but expected error. Primitives such as strings, and error objects that are directly created like `new Error("database didn't respond")` are considered untyped.
+
+Example of such library is the node core library `fs`. So if we promisify it, we can catch just the errors we want pretty easily and have programmer errors be redirected to unhandled rejection handler so that we notice them:
+
+```js
+//Read more about promisification in the API Reference:
+//https://github.com/petkaantonov/bluebird/blob/master/API.md
+var fs = Promise.promisifyAll(require("fs"));
+
+fs.readFileAsync("myfile.json").then(JSON.parse).then(function (json) {
+    console.log("Successful json")
+}).catch(SyntaxError, function (e) {
+    console.error("file contains invalid json");
+}).catch(Promise.RejectionError, function (e) {
+    console.error("unable to read file, because: ", e.message);
+});
+```
+
+The last `catch` handler is only invoked when the `fs` module explicitly used the `err` argument convention of async callbacks to inform of an expected error. The `RejectionError` instance will contain the original error in its `.cause` property but it does have a direct copy of the `.message` and `.stack` too. In this code any unexpected error - be it in our code or the `fs` module - would not be caught by these handlers and therefore not swallowed.
+
+Since a `catch` handler typed to `Promise.RejectionError` is expected to be used very often, it has a neat shorthand:
+
+```js
+.error(function (e) {
+    console.error("unable to read file, because: ", e.message);
+});
+```
+
+See [API documentation for `.error()`](https://github.com/petkaantonov/bluebird/blob/master/API.md#error-rejectedhandler----promise)
+
+Finally, Bluebird also supports predicate-based filters. If you pass a
+predicate function instead of an error type, the predicate will receive
+the error as an argument. The return result will be used determine whether
+the error handler should be called.
+
+Predicates should allow for very fine grained control over caught errors:
+pattern matching, error typesets with set operations and many other techniques
+can be implemented on top of them.
+
+Example of using a predicate-based filter:
+
+```js
+var Promise = require("bluebird");
+var request = Promise.promisify(require("request"));
+
+function clientError(e) {
+    return e.code >= 400 && e.code < 500;
+}
+
+request("http://www.google.com").then(function(contents){
+    console.log(contents);
+}).catch(clientError, function(e){
+   //A client error like 400 Bad Request happened
+});
+```
+
+**Danger:** The JavaScript language allows throwing primitive values like strings. Throwing primitives can lead to worse or no stack traces. Primitives [are not exceptions](http://www.devthought.com/2011/12/22/a-string-is-not-an-error/). You should consider always throwing Error objects when handling exceptions.
+
+<hr>
+
+####How do long stack traces differ from e.g. Q?
+
+Bluebird attempts to have more elaborate traces. Consider:
+
+```js
+Error.stackTraceLimit = 25;
+Q.longStackSupport = true;
+Q().then(function outer() {
+    return Q().then(function inner() {
+        return Q().then(function evenMoreInner() {
+            a.b.c.d();
+        }).catch(function catcher(e){
+            console.error(e.stack);
+        });
+    })
+});
+```
+
+You will see
+
+    ReferenceError: a is not defined
+        at evenMoreInner (<anonymous>:7:13)
+    From previous event:
+        at inner (<anonymous>:6:20)
+
+Compare to:
+
+```js
+Error.stackTraceLimit = 25;
+Promise.longStackTraces();
+Promise.resolve().then(function outer() {
+    return Promise.resolve().then(function inner() {
+        return Promise.resolve().then(function evenMoreInner() {
+            a.b.c.d()
+        }).catch(function catcher(e){
+            console.error(e.stack);
+        });
+    });
+});
+```
+
+    ReferenceError: a is not defined
+        at evenMoreInner (<anonymous>:7:13)
+    From previous event:
+        at inner (<anonymous>:6:36)
+    From previous event:
+        at outer (<anonymous>:5:32)
+    From previous event:
+        at <anonymous>:4:21
+        at Object.InjectedScript._evaluateOn (<anonymous>:572:39)
+        at Object.InjectedScript._evaluateAndWrap (<anonymous>:531:52)
+        at Object.InjectedScript.evaluate (<anonymous>:450:21)
+
+
+A better and more practical example of the differences can be seen in gorgikosev's [debuggability competition](https://github.com/spion/async-compare#debuggability).
+
+<hr>
+
+####Can I use long stack traces in production?
+
+Probably yes. Bluebird uses multiple innovative techniques to optimize long stack traces. Even with long stack traces, it is still way faster than similarly featured implementations that don't have long stack traces enabled and about same speed as minimal implementations. A slowdown of 4-5x is expected, not 50x.
+
+What techniques are used?
+
+#####V8 API second argument
+
+This technique utilizes the [slightly under-documented](https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi#Stack_trace_collection_for_custom_exceptions) second argument of V8 `Error.captureStackTrace`. It turns out that the second argument can actually be used to make V8 skip all library internal stack frames [for free](https://github.com/v8/v8/blob/b5fabb9225e1eb1c20fd527b037e3f877296e52a/src/isolate.cc#L665). It only requires propagation of callers manually in library internals but this is not visible to you as user at all.
+
+Without this technique, every promise (well not every, see second technique) created would have to waste time creating and collecting library internal frames which will just be thrown away anyway. It also allows one to use smaller stack trace limits because skipped frames are not counted towards the limit whereas with collecting everything upfront and filtering afterwards would likely have to use higher limits to get more user stack frames in.
+
+#####Sharing stack traces
+
+Consider:
+
+```js
+function getSomethingAsync(fileName) {
+    return readFileAsync(fileName).then(function(){
+        //...
+    }).then(function() {
+        //...
+    }).then(function() {
+        //...
+    });
+}
+```
+
+Everytime you call this function it creates 4 promises and in a straight-forward long stack traces implementation it would collect 4 almost identical stack traces. Bluebird has a light weight internal data-structure (kcnown as context stack in the source code) to help tracking when traces can be re-used and this example would only collect one trace.
+
+#####Lazy formatting
+
+After a stack trace has been collected on an object, one must be careful not to reference the `.stack` property until necessary. Referencing the property causes
+an expensive format call and the stack property is turned into a string which uses much more memory.
+
+What about [Q #111](https://github.com/kriskowal/q/issues/111)?
+
+Long stack traces is not inherently the problem. For example with latest Q with stack traces disabled:
+
+```js
+var Q = require("q");
+
+
+function test(i){
+    if (i <= 0){
+       return Q.when('done')
+   } else {
+       return Q.when(i-1).then(test)
+   }
+}
+test(1000000000).then(function(output){console.log(output) });
+```
+
+After 2 minutes of running this, it will give:
+
+```js
+FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - process out of memory
+```
+
+So the problem with this is how much absolute memory is used per promise - not whether long traces are enabled or not.
+
+For some purpose, let's say 100000 parallel pending promises in memory at the same time is the maximum. You would then roughly use 100MB for them instead of 10MB with stack traces disabled.For comparison, just creating 100000 functions alone will use 14MB if they're closures. All numbers can be halved for 32-bit node.
+
+<hr>
+
+#Development
+
+For development tasks such as running benchmarks or testing, you need to clone the repository and install dev-dependencies.
+
+Install [node](http://nodejs.org/), [npm](https://npmjs.org/), and [grunt](http://gruntjs.com/).
+
+    git clone git@github.com:petkaantonov/bluebird.git
+    cd bluebird
+    npm install
+
+##Testing
+
+To run all tests, run `grunt test`. Note that 10 processes are created to run the tests in parallel. The stdout of tests is ignored by default and everything will stop at the first failure.
+
+Individual files can be run with `grunt test --run=filename` where `filename` is a test file name in `/test` folder or `/test/mocha` folder. The `.js` prefix is not needed. The dots for AP compliance tests are not needed, so to run `/test/mocha/2.3.3.js` for instance:
+
+    grunt test --run=233
+
+When trying to get a test to pass, run only that individual test file with `--verbose` to see the output from that test:
+
+    grunt test --run=233 --verbose
+
+The reason for the unusual way of testing is because the majority of tests are from different libraries using different testing frameworks and because it takes forever to test sequentially.
+
+
+###Testing in browsers
+
+To test in browsers:
+
+    cd browser
+    setup
+
+Then open the `index.html` in your browser. Requires bash (on windows the mingw32 that comes with git works fine too).
+
+You may also [visit the github hosted page](http://petkaantonov.github.io/bluebird/browser/).
+
+Keep the test tab active because some tests are timing-sensitive and will fail if the browser is throttling timeouts. Chrome will do this for example when the tab is not active.
+
+##Benchmarks
+
+To run a benchmark, run the given command for a benchmark while on the project root. Requires bash (on windows the mingw32 that comes with git works fine too).
+
+Node 0.11.2+ is required to run the generator examples.
+
+###1\. DoxBee sequential
+
+Currently the most relevant benchmark is @gorkikosev's benchmark in the article [Analysis of generators and other async patterns in node](http://spion.github.io/posts/analysis-generators-and-other-async-patterns-node.html). The benchmark emulates a situation where n amount of users are making a request in parallel to execute some mixed async/sync action. The benchmark has been modified to include a warm-up phase to minimize any JITing during timed sections.
+
+Command: `bench doxbee`
+
+###2\. Made-up parallel
+
+This made-up scenario runs 15 shimmed queries in parallel.
+
+Command: `bench parallel`
+
+##Custom builds
+
+Custom builds for browsers are supported through a command-line utility.
+
+
+
+
+<table>
+    <caption>The following features can be disabled</caption>
+    <thead>
+        <tr>
+            <th>Feature(s)</th>
+            <th>Command line identifier</th>
+        </tr>
+    </thead>
+    <tbody>
+
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#any---promise"><code>.any</code></a> and <a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#promiseanyarraydynamicpromise-values---promise"><code>Promise.any</code></a></td><td><code>any</code></td></tr>
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#race---promise"><code>.race</code></a> and <a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#promiseracearraypromise-promises---promise"><code>Promise.race</code></a></td><td><code>race</code></td></tr>
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#callstring-propertyname--dynamic-arg---promise"><code>.call</code></a> and <a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#getstring-propertyname---promise"><code>.get</code></a></td><td><code>call_get</code></td></tr>
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#filterfunction-filterer---promise"><code>.filter</code></a> and <a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#promisefilterarraydynamicpromise-values-function-filterer---promise"><code>Promise.filter</code></a></td><td><code>filter</code></td></tr>
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#mapfunction-mapper---promise"><code>.map</code></a> and <a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#promisemaparraydynamicpromise-values-function-mapper---promise"><code>Promise.map</code></a></td><td><code>map</code></td></tr>
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#reducefunction-reducer--dynamic-initialvalue---promise"><code>.reduce</code></a> and <a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#promisereducearraydynamicpromise-values-function-reducer--dynamic-initialvalue---promise"><code>Promise.reduce</code></a></td><td><code>reduce</code></td></tr>
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#props---promise"><code>.props</code></a> and <a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#promisepropsobjectpromise-object---promise"><code>Promise.props</code></a></td><td><code>props</code></td></tr>
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#settle---promise"><code>.settle</code></a> and <a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#promisesettlearraydynamicpromise-values---promise"><code>Promise.settle</code></a></td><td><code>settle</code></td></tr>
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#someint-count---promise"><code>.some</code></a> and <a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#promisesomearraydynamicpromise-values-int-count---promise"><code>Promise.some</code></a></td><td><code>some</code></td></tr>
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#nodeifyfunction-callback---promise"><code>.nodeify</code></a></td><td><code>nodeify</code></td></tr>
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#promisecoroutinegeneratorfunction-generatorfunction---function"><code>Promise.coroutine</code></a> and <a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#promisespawngeneratorfunction-generatorfunction---promise"><code>Promise.spawn</code></a></td><td><code>generators</code></td></tr>
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#progression">Progression</a></td><td><code>progress</code></td></tr>
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#promisification">Promisification</a></td><td><code>promisify</code></td></tr>
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#cancellation">Cancellation</a></td><td><code>cancel</code></td></tr>
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#synchronous-inspection">Synchronous inspection</a></td><td><code>synchronous_inspection</code></td></tr>
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#timers">Timers</a></td><td><code>timers</code></td></tr>
+
+    </tbody>
+</table>
+
+
+Make sure you have cloned the repo somewhere and did `npm install` successfully.
+
+After that you can run:
+
+    grunt build --features="core"
+
+
+The above builds the most minimal build you can get. You can add more features separated by spaces from the above list:
+
+    grunt build --features="core filter map reduce"
+
+The custom build file will be found from `/js/browser/bluebird.js`. It will have a comment that lists the disabled and enabled features.
+
+Note that the build leaves the `/js/main` etc folders with same features so if you use the folder for node.js at the same time, don't forget to build
+a full version afterwards (after having taken a copy of the bluebird.js somewhere):
+
+    grunt build
+
+<hr>
+
+##For library authors
+
+Building a library that depends on bluebird? You should know about a few features.
+
+If your library needs to do something obtrusive like adding or modifying methods on the `Promise` prototype, uses long stack traces or uses a custom unhandled rejection handler then... that's totally ok as long as you don't use `require("bluebird")`. Instead you should create a file
+that creates an isolated copy. For example, creating a file called `bluebird-extended.js` that contains:
+
+```js
+                //NOTE the function call right after
+module.exports = require("bluebird/js/main/promise")();
+```
+
+Your library can then use `var Promise = require("bluebird-extended");` and do whatever it wants with it. Then if the application or other library uses their own bluebird promises they will all play well together because of Promises/A+ thenable assimilation magic.
+
+You should also know about [`.nodeify()`](https://github.com/petkaantonov/bluebird/blob/master/API.md#nodeifyfunction-callback---promise) which makes it easy to provide a dual callback/promise API.
+
+<hr>
+
+##What is the sync build?
+
+You may now use sync build by:
+
+    var Promise = require("bluebird/zalgo");
+
+The sync build is provided to see how forced asynchronity affects benchmarks. It should not be used in real code due to the implied hazards.
+
+The normal async build gives Promises/A+ guarantees about asynchronous resolution of promises. Some people think this affects performance or just plain love their code having a possibility
+of stack overflow errors and non-deterministic behavior.
+
+The sync build skips the async call trampoline completely, e.g code like:
+
+    async.invoke( this.fn, this, val );
+
+Appears as this in the sync build:
+
+    this.fn(val);
+
+This should pressure the CPU slightly less and thus the sync build should perform better. Indeed it does, but only marginally. The biggest performance boosts are from writing efficient Javascript, not from compromising determinism.
+
+Note that while some benchmarks are waiting for the next event tick, the CPU is actually not in use during that time. So the resulting benchmark result is not completely accurate because on node.js you only care about how much the CPU is taxed. Any time spent on CPU is time the whole process (or server) is paralyzed. And it is not graceful like it would be with threads.
+
+
+```js
+var cache = new Map(); //ES6 Map or DataStructures/Map or whatever...
+function getResult(url) {
+    var resolver = Promise.pending();
+    if (cache.has(url)) {
+        resolver.resolve(cache.get(url));
+    }
+    else {
+        http.get(url, function(err, content) {
+            if (err) resolver.reject(err);
+            else {
+                cache.set(url, content);
+                resolver.resolve(content);
+            }
+        });
+    }
+    return resolver.promise;
+}
+
+
+
+//The result of console.log is truly random without async guarantees
+function guessWhatItPrints( url ) {
+    var i = 3;
+    getResult(url).then(function(){
+        i = 4;
+    });
+    console.log(i);
+}
+```
+
+#Optimization guide
+
+Articles about optimization will be periodically posted in [the wiki section](https://github.com/petkaantonov/bluebird/wiki), polishing edits are welcome.
+
+A single cohesive guide compiled from the articles will probably be done eventually.
+
+#License
+
+Copyright (c) 2014 Petka Antonov
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:</p>
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/benchmark/package.json b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/benchmark/package.json
new file mode 100644
index 0000000..19cefed
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/benchmark/package.json
@@ -0,0 +1,34 @@
+{
+  "name": "async-compare",
+  "version": "0.1.1",
+  "description": "Compare the performance and code of multiple async patterns",
+  "main": "perf.js",
+  "dependencies": {
+    "async": "~0.2.9",
+    "deferred": "~0.6.6",
+    "kew": "~0.3.0",
+    "liar": "~0.6.0",
+    "optimist": "~0.6.0",
+    "q": "~1.0.0",
+    "rsvp": "~3.0.0",
+    "text-table": "~0.2.0",
+    "when": "~3.0.0",
+    "vow": "~0.4.1",
+    "davy": "~0.2.0"
+  },
+  "devDependencies": {},
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "keywords": [
+    "generators",
+    "fibers",
+    "promises",
+    "callbacks",
+    "comparison",
+    "compare",
+    "async"
+  ],
+  "author": "spion",
+  "license": "MIT"
+}
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/bower.json b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/bower.json
new file mode 100644
index 0000000..396073a
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/bower.json
@@ -0,0 +1,35 @@
+{
+  "name": "bluebird",
+  "version": "1.0.0",
+  "homepage": "https://github.com/petkaantonov/bluebird",
+  "authors": [
+    "Petka Antonov <petka_antonov@hotmail.com>"
+  ],
+  "description": "Bluebird is a full featured promise library with unmatched performance.",
+  "main": "js/browser/bluebird.js",
+  "license": "MIT",
+  "ignore": [
+    "**/.*",
+    "benchmark",
+    "bower_components",
+    "./browser",
+    "js/zalgo",
+    "node_modules",
+    "test"
+  ],
+  "keywords": [
+    "promise",
+    "performance",
+    "promises",
+    "promises-a",
+    "promises-aplus",
+    "async",
+    "await",
+    "deferred",
+    "deferreds",
+    "future",
+    "flow control",
+    "dsl",
+    "fluent interface"
+  ]
+}
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/bundle.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/bundle.js
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/bundle.js
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/changelog.md b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/changelog.md
new file mode 100644
index 0000000..9ae6be2
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/changelog.md
@@ -0,0 +1,1135 @@
+## 1.1.1 (2014-03-18)
+
+Bugfixes:
+
+ - [#138](https://github.com/petkaantonov/bluebird/issues/138)
+ - [#144](https://github.com/petkaantonov/bluebird/issues/144)
+ - [#148](https://github.com/petkaantonov/bluebird/issues/148)
+ - [#151](https://github.com/petkaantonov/bluebird/issues/151)
+
+## 1.1.0 (2014-03-08)
+
+Features:
+
+ - Implement [`Promise.prototype.tap()`](https://github.com/petkaantonov/bluebird/blob/master/API.md#tapfunction-handler---promise)
+ - Implement [`Promise.coroutine.addYieldHandler()`](https://github.com/petkaantonov/bluebird/blob/master/API.md#promisecoroutineaddyieldhandlerfunction-handler---void)
+ - Deprecate `Promise.prototype.spawn`
+
+Bugfixes:
+
+ - Fix already rejected promises being reported as unhandled when handled through collection methods
+ - Fix browserisfy crashing from checking `process.version.indexOf`
+
+## 1.0.8 (2014-03-03)
+
+Bugfixes:
+
+ - Fix active domain being lost across asynchronous boundaries in Node.JS 10.xx
+
+## 1.0.7 (2014-02-25)
+
+Bugfixes:
+
+ - Fix handled errors being reported
+
+## 1.0.6 (2014-02-17)
+
+Bugfixes:
+
+ -  Fix bug with unhandled rejections not being reported
+    when using `Promise.try` or `Promise.method` without
+    attaching further handlers
+
+## 1.0.5 (2014-02-15)
+
+Features:
+
+ - Node.js performance: promisified functions try to check amount of passed arguments in most optimal order
+ - Node.js promisified functions will have same `.length` as the original function minus one (for the callback parameter)
+
+## 1.0.4 (2014-02-09)
+
+Features:
+
+ - Possibly unhandled rejection handler will always get a stack trace, even if the rejection or thrown error was not an error
+ - Unhandled rejections are tracked per promise, not per error. So if you create multiple branches from a single ancestor and that ancestor gets rejected, each branch with no error handler with the end will cause a possibly unhandled rejection handler invocation
+
+Bugfixes:
+
+ - Fix unhandled non-writable objects or primitives not reported by possibly unhandled rejection handler
+
+## 1.0.3 (2014-02-05)
+
+Bugfixes:
+
+ - [#93](https://github.com/petkaantonov/bluebird/issues/88)
+
+## 1.0.2 (2014-02-04)
+
+Features:
+
+ - Significantly improve performance of foreign bluebird thenables
+
+Bugfixes:
+
+ - [#88](https://github.com/petkaantonov/bluebird/issues/88)
+
+## 1.0.1 (2014-01-28)
+
+Features:
+
+ - Error objects that have property `.isAsync = true` will now be caught by `.error()`
+
+Bugfixes:
+
+ - Fix TypeError and RangeError shims not working without `new` operator
+
+## 1.0.0 (2014-01-12)
+
+Features:
+
+ - `.filter`, `.map`, and `.reduce` no longer skip sparse array holes. This is a backwards incompatible change.
+ - Like `.map` and `.filter`, `.reduce` now allows returning promises and thenables from the iteration function.
+
+Bugfixes:
+
+ - [#58](https://github.com/petkaantonov/bluebird/issues/58)
+ - [#61](https://github.com/petkaantonov/bluebird/issues/61)
+ - [#64](https://github.com/petkaantonov/bluebird/issues/64)
+ - [#60](https://github.com/petkaantonov/bluebird/issues/60)
+
+## 0.11.6-1 (2013-12-29)
+
+## 0.11.6-0 (2013-12-29)
+
+Features:
+
+ - You may now return promises and thenables from the filterer function used in `Promise.filter` and `Promise.prototype.filter`.
+
+ - `.error()` now catches additional sources of rejections:
+
+    - Rejections originating from `Promise.reject`
+
+    - Rejections originating from thenables using
+    the `reject` callback
+
+    - Rejections originating from promisified callbacks
+    which use the `errback` argument
+
+    - Rejections originating from `new Promise` constructor
+    where the `reject` callback is called explicitly
+
+    - Rejections originating from `PromiseResolver` where
+    `.reject()` method is called explicitly
+
+Bugfixes:
+
+ - Fix `captureStackTrace` being called when it was `null`
+ - Fix `Promise.map` not unwrapping thenables
+
+## 0.11.5-1 (2013-12-15)
+
+## 0.11.5-0 (2013-12-03)
+
+Features:
+
+ - Improve performance of collection methods
+ - Improve performance of promise chains
+
+## 0.11.4-1 (2013-12-02)
+
+## 0.11.4-0 (2013-12-02)
+
+Bugfixes:
+
+ - Fix `Promise.some` behavior with arguments like negative integers, 0...
+ - Fix stack traces of synchronously throwing promisified functions'
+
+## 0.11.3-0 (2013-12-02)
+
+Features:
+
+ - Improve performance of generators
+
+Bugfixes:
+
+ - Fix critical bug with collection methods.
+
+## 0.11.2-0 (2013-12-02)
+
+Features:
+
+ - Improve performance of all collection methods
+
+## 0.11.1-0 (2013-12-02)
+
+Features:
+
+- Improve overall performance.
+- Improve performance of promisified functions.
+- Improve performance of catch filters.
+- Improve performance of .finally.
+
+Bugfixes:
+
+- Fix `.finally()` rejecting if passed non-function. It will now ignore non-functions like `.then`.
+- Fix `.finally()` not converting thenables returned from the handler to promises.
+- `.spread()` now rejects if the ultimate value given to it is not spreadable.
+
+## 0.11.0-0 (2013-12-02)
+
+Features:
+
+ - Improve overall performance when not using `.bind()` or cancellation.
+ - Promises are now not cancellable by default. This is backwards incompatible change - see [`.cancellable()`](https://github.com/petkaantonov/bluebird/blob/master/API.md#cancellable---promise)
+ - [`Promise.delay`](https://github.com/petkaantonov/bluebird/blob/master/API.md#promisedelaydynamic-value-int-ms---promise)
+ - [`.delay()`](https://github.com/petkaantonov/bluebird/blob/master/API.md#delayint-ms---promise)
+ - [`.timeout()`](https://github.com/petkaantonov/bluebird/blob/master/API.md#timeoutint-ms--string-message---promise)
+
+## 0.10.14-0 (2013-12-01)
+
+Bugfixes:
+
+ - Fix race condition when mixing 3rd party asynchrony.
+
+## 0.10.13-1 (2013-11-30)
+
+## 0.10.13-0 (2013-11-30)
+
+Bugfixes:
+
+ - Fix another bug with progression.
+
+## 0.10.12-0 (2013-11-30)
+
+Bugfixes:
+
+ - Fix bug with progression.
+
+## 0.10.11-4 (2013-11-29)
+
+## 0.10.11-2 (2013-11-29)
+
+Bugfixes:
+
+ - Fix `.race()` not propagating bound values.
+
+## 0.10.11-1 (2013-11-29)
+
+Features:
+
+ - Improve performance of `Promise.race`
+
+## 0.10.11-0 (2013-11-29)
+
+Bugfixes:
+
+ - Fixed `Promise.promisifyAll` invoking property accessors. Only data properties with function values are considered.
+
+## 0.10.10-0 (2013-11-28)
+
+Features:
+
+ - Disable long stack traces in browsers by default. Call `Promise.longStackTraces()` to enable them.
+
+## 0.10.9-1 (2013-11-27)
+
+Bugfixes:
+
+ - Fail early when `new Promise` is constructed incorrectly
+
+## 0.10.9-0 (2013-11-27)
+
+Bugfixes:
+
+ - Promise.props now takes a [thenable-for-collection](https://github.com/petkaantonov/bluebird/blob/f41edac61b7c421608ff439bb5a09b7cffeadcf9/test/mocha/props.js#L197-L217)
+ - All promise collection methods now reject when a promise-or-thenable-for-collection turns out not to give a collection
+
+## 0.10.8-0 (2013-11-25)
+
+Features:
+
+ - All static collection methods take thenable-for-collection
+
+## 0.10.7-0 (2013-11-25)
+
+Features:
+
+ - throw TypeError when thenable resolves with itself
+ - Make .race() and Promise.race() forever pending on empty collections
+
+## 0.10.6-0 (2013-11-25)
+
+Bugfixes:
+
+ - Promise.resolve and PromiseResolver.resolve follow thenables too.
+
+## 0.10.5-0 (2013-11-24)
+
+Bugfixes:
+
+ - Fix infinite loop when thenable resolves with itself
+
+## 0.10.4-1 (2013-11-24)
+
+Bugfixes:
+
+ - Fix a file missing from build. (Critical fix)
+
+## 0.10.4-0 (2013-11-24)
+
+Features:
+
+ - Remove dependency of es5-shim and es5-sham when using ES3.
+
+## 0.10.3-0 (2013-11-24)
+
+Features:
+
+ - Improve performance of `Promise.method`
+
+## 0.10.2-1 (2013-11-24)
+
+Features:
+
+ - Rename PromiseResolver#asCallback to PromiseResolver#callback
+
+## 0.10.2-0 (2013-11-24)
+
+Features:
+
+ - Remove memoization of thenables
+
+## 0.10.1-0 (2013-11-21)
+
+Features:
+
+ - Add methods `Promise.resolve()`, `Promise.reject()`, `Promise.defer()` and `.resolve()`.
+
+## 0.10.0-1 (2013-11-17)
+
+## 0.10.0-0 (2013-11-17)
+
+Features:
+
+ - Implement `Promise.method()`
+ - Implement `.return()`
+ - Implement `.throw()`
+
+Bugfixes:
+
+ - Fix promises being able to use themselves as resolution or follower value
+
+## 0.9.11-1 (2013-11-14)
+
+Features:
+
+ - Implicit `Promise.all()` when yielding an array from generators
+
+## 0.9.11-0 (2013-11-13)
+
+Bugfixes:
+
+ - Fix `.spread` not unwrapping thenables
+
+## 0.9.10-2 (2013-11-13)
+
+Features:
+
+ - Improve performance of promisified functions on V8
+
+Bugfixes:
+
+ - Report unhandled rejections even when long stack traces are disabled
+ - Fix `.error()` showing up in stack traces
+
+## 0.9.10-1 (2013-11-05)
+
+Bugfixes:
+
+ - Catch filter method calls showing in stack traces
+
+## 0.9.10-0 (2013-11-05)
+
+Bugfixes:
+
+ - Support primitives in catch filters
+
+## 0.9.9-0 (2013-11-05)
+
+Features:
+
+ - Add `Promise.race()` and `.race()`
+
+## 0.9.8-0 (2013-11-01)
+
+Bugfixes:
+
+ - Fix bug with `Promise.try` not unwrapping returned promises and thenables
+
+## 0.9.7-0 (2013-10-29)
+
+Bugfixes:
+
+ - Fix bug with build files containing duplicated code for promise.js
+
+## 0.9.6-0 (2013-10-28)
+
+Features:
+
+ - Improve output of reporting unhandled non-errors
+ - Implement RejectionError wrapping and `.error()` method
+
+## 0.9.5-0 (2013-10-27)
+
+Features:
+
+ - Allow fresh copies of the library to be made
+
+## 0.9.4-1 (2013-10-27)
+
+## 0.9.4-0 (2013-10-27)
+
+Bugfixes:
+
+ - Rollback non-working multiple fresh copies feature
+
+## 0.9.3-0 (2013-10-27)
+
+Features:
+
+ - Allow fresh copies of the library to be made
+ - Add more components to customized builds
+
+## 0.9.2-1 (2013-10-25)
+
+## 0.9.2-0 (2013-10-25)
+
+Features:
+
+ - Allow custom builds
+
+## 0.9.1-1 (2013-10-22)
+
+Bugfixes:
+
+ - Fix unhandled rethrown exceptions not reported
+
+## 0.9.1-0 (2013-10-22)
+
+Features:
+
+ - Improve performance of `Promise.try`
+ - Extend `Promise.try` to accept arguments and ctx to make it more usable in promisification of synchronous functions.
+
+## 0.9.0-0 (2013-10-18)
+
+Features:
+
+ - Implement `.bind` and `Promise.bind`
+
+Bugfixes:
+
+ - Fix `.some()` when argument is a pending promise that later resolves to an array
+
+## 0.8.5-1 (2013-10-17)
+
+Features:
+
+ - Enable process wide long stack traces through BLUEBIRD_DEBUG environment variable
+
+## 0.8.5-0 (2013-10-16)
+
+Features:
+
+ - Improve performance of all collection methods
+
+Bugfixes:
+
+ - Fix .finally passing the value to handlers
+ - Remove kew from benchmarks due to bugs in the library breaking the benchmark
+ - Fix some bluebird library calls potentially appearing in stack traces
+
+## 0.8.4-1 (2013-10-15)
+
+Bugfixes:
+
+ - Fix .pending() call showing in long stack traces
+
+## 0.8.4-0 (2013-10-15)
+
+Bugfixes:
+
+ - Fix PromiseArray and its sub-classes swallowing possibly unhandled rejections
+
+## 0.8.3-3 (2013-10-14)
+
+Bugfixes:
+
+ - Fix AMD-declaration using named module.
+
+## 0.8.3-2 (2013-10-14)
+
+Features:
+
+ - The mortals that can handle it may now release Zalgo by `require("bluebird/zalgo");`
+
+## 0.8.3-1 (2013-10-14)
+
+Bugfixes:
+
+ - Fix memory leak when using the same promise to attach handlers over and over again
+
+## 0.8.3-0 (2013-10-13)
+
+Features:
+
+ - Add `Promise.props()` and `Promise.prototype.props()`. They work like `.all()` for object properties.
+
+Bugfixes:
+
+ - Fix bug with .some returning garbage when sparse arrays have rejections
+
+## 0.8.2-2 (2013-10-13)
+
+Features:
+
+ - Improve performance of `.reduce()` when `initialValue` can be synchronously cast to a value
+
+## 0.8.2-1 (2013-10-12)
+
+Bugfixes:
+
+ - Fix .npmignore having irrelevant files
+
+## 0.8.2-0 (2013-10-12)
+
+Features:
+
+ - Improve performance of `.some()`
+
+## 0.8.1-0 (2013-10-11)
+
+Bugfixes:
+
+ - Remove uses of dynamic evaluation (`new Function`, `eval` etc) when strictly not necessary. Use feature detection to use static evaluation to avoid errors when dynamic evaluation is prohibited.
+
+## 0.8.0-3 (2013-10-10)
+
+Features:
+
+ - Add `.asCallback` property to `PromiseResolver`s
+
+## 0.8.0-2 (2013-10-10)
+
+## 0.8.0-1 (2013-10-09)
+
+Features:
+
+ - Improve overall performance. Be able to sustain infinite recursion when using promises.
+
+## 0.8.0-0 (2013-10-09)
+
+Bugfixes:
+
+ - Fix stackoverflow error when function calls itself "synchronously" from a promise handler
+
+## 0.7.12-2 (2013-10-09)
+
+Bugfixes:
+
+ - Fix safari 6 not using `MutationObserver` as a scheduler
+ - Fix process exceptions interfering with internal queue flushing
+
+## 0.7.12-1 (2013-10-09)
+
+Bugfixes:
+
+ - Don't try to detect if generators are available to allow shims to be used
+
+## 0.7.12-0 (2013-10-08)
+
+Features:
+
+ - Promisification now consider all functions on the object and its prototype chain
+ - Individual promisifcation uses current `this` if no explicit receiver is given
+ - Give better stack traces when promisified callbacks throw or errback primitives such as strings by wrapping them in an `Error` object.
+
+Bugfixes:
+
+ - Fix runtime APIs throwing synchronous errors
+
+## 0.7.11-0 (2013-10-08)
+
+Features:
+
+ - Deprecate `Promise.promisify(Object target)` in favor of `Promise.promisifyAll(Object target)` to avoid confusion with function objects
+ - Coroutines now throw error when a non-promise is `yielded`
+
+## 0.7.10-1 (2013-10-05)
+
+Features:
+
+ - Make tests pass Internet Explorer 8
+
+## 0.7.10-0 (2013-10-05)
+
+Features:
+
+ - Create browser tests
+
+## 0.7.9-1 (2013-10-03)
+
+Bugfixes:
+
+ - Fix promise cast bug when thenable fulfills using itself as the fulfillment value
+
+## 0.7.9-0 (2013-10-03)
+
+Features:
+
+ - More performance improvements when long stack traces are enabled
+
+## 0.7.8-1 (2013-10-02)
+
+Features:
+
+ - Performance improvements when long stack traces are enabled
+
+## 0.7.8-0 (2013-10-02)
+
+Bugfixes:
+
+ - Fix promisified methods not turning synchronous exceptions into rejections
+
+## 0.7.7-1 (2013-10-02)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.7-0 (2013-10-01)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.6-0 (2013-09-29)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.5-0 (2013-09-28)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.4-1 (2013-09-28)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.4-0 (2013-09-28)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.3-1 (2013-09-28)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.3-0 (2013-09-27)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.2-0 (2013-09-27)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.1-5 (2013-09-26)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.1-4 (2013-09-25)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.1-3 (2013-09-25)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.1-2 (2013-09-24)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.1-1 (2013-09-24)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.1-0 (2013-09-24)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.0-1 (2013-09-23)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.0-0 (2013-09-23)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.6.5-2 (2013-09-20)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.6.5-1 (2013-09-18)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.6.5-0 (2013-09-18)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.6.4-1 (2013-09-18)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.6.4-0 (2013-09-18)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.6.3-4 (2013-09-18)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.6.3-3 (2013-09-18)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.6.3-2 (2013-09-16)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.6.3-1 (2013-09-16)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.6.3-0 (2013-09-15)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.6.2-1 (2013-09-14)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.6.2-0 (2013-09-14)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.6.1-0 (2013-09-14)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.6.0-0 (2013-09-13)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.9-6 (2013-09-12)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.9-5 (2013-09-12)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.9-4 (2013-09-12)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.9-3 (2013-09-11)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.9-2 (2013-09-11)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.9-1 (2013-09-11)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.9-0 (2013-09-11)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.8-1 (2013-09-11)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.8-0 (2013-09-11)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.7-0 (2013-09-11)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.6-1 (2013-09-10)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.6-0 (2013-09-10)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.5-1 (2013-09-10)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.5-0 (2013-09-09)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.4-1 (2013-09-08)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.4-0 (2013-09-08)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.3-0 (2013-09-07)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.2-0 (2013-09-07)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.1-0 (2013-09-07)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.0-0 (2013-09-07)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.4.0-0 (2013-09-06)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.3.0-1 (2013-09-06)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.3.0 (2013-09-06)
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/any.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/any.js
new file mode 100644
index 0000000..8d174cf
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/any.js
@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, Promise$_CreatePromiseArray, PromiseArray) {
+
+    var SomePromiseArray = require("./some_promise_array.js")(PromiseArray);
+    function Promise$_Any(promises, useBound, caller) {
+        var ret = Promise$_CreatePromiseArray(
+            promises,
+            SomePromiseArray,
+            caller,
+            useBound === true && promises._isBound()
+                ? promises._boundTo
+                : void 0
+       );
+        var promise = ret.promise();
+        if (promise.isRejected()) {
+            return promise;
+        }
+        ret.setHowMany(1);
+        ret.setUnwrap();
+        ret.init();
+        return promise;
+    }
+
+    Promise.any = function Promise$Any(promises) {
+        return Promise$_Any(promises, false, Promise.any);
+    };
+
+    Promise.prototype.any = function Promise$any() {
+        return Promise$_Any(this, true, this.any);
+    };
+
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/assert.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/assert.js
new file mode 100644
index 0000000..4adb8c2
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/assert.js
@@ -0,0 +1,83 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = (function(){
+    var AssertionError = (function() {
+        function AssertionError(a) {
+            this.constructor$(a);
+            this.message = a;
+            this.name = "AssertionError";
+        }
+        AssertionError.prototype = new Error();
+        AssertionError.prototype.constructor = AssertionError;
+        AssertionError.prototype.constructor$ = Error;
+        return AssertionError;
+    })();
+
+    function getParams(args) {
+        var params = [];
+        for (var i = 0; i < args.length; ++i) params.push("arg" + i);
+        return params;
+    }
+
+    function nativeAssert(callName, args, expect) {
+        try {
+            var params = getParams(args);
+            var constructorArgs = params;
+            constructorArgs.push("return " +
+                    callName + "("+ params.join(",") + ");");
+            var fn = Function.apply(null, constructorArgs);
+            return fn.apply(null, args);
+        }
+        catch (e) {
+            if (!(e instanceof SyntaxError)) {
+                throw e;
+            }
+            else {
+                return expect;
+            }
+        }
+    }
+
+    return function assert(boolExpr, message) {
+        if (boolExpr === true) return;
+
+        if (typeof boolExpr === "string" &&
+            boolExpr.charAt(0) === "%") {
+            var nativeCallName = boolExpr;
+            var $_len = arguments.length;var args = new Array($_len - 2); for(var $_i = 2; $_i < $_len; ++$_i) {args[$_i - 2] = arguments[$_i];}
+            if (nativeAssert(nativeCallName, args, message) === message) return;
+            message = (nativeCallName + " !== " + message);
+        }
+
+        var ret = new AssertionError(message);
+        if (Error.captureStackTrace) {
+            Error.captureStackTrace(ret, assert);
+        }
+        if (console && console.error) {
+            console.error(ret.stack + "");
+        }
+        throw ret;
+
+    };
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/async.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/async.js
new file mode 100644
index 0000000..6f32b10
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/async.js
@@ -0,0 +1,111 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var schedule = require("./schedule.js");
+var Queue = require("./queue.js");
+var errorObj = require("./util.js").errorObj;
+var tryCatch1 = require("./util.js").tryCatch1;
+var process = require("./global.js").process;
+
+function Async() {
+    this._isTickUsed = false;
+    this._length = 0;
+    this._lateBuffer = new Queue();
+    this._functionBuffer = new Queue(25000 * 3);
+    var self = this;
+    this.consumeFunctionBuffer = function Async$consumeFunctionBuffer() {
+        self._consumeFunctionBuffer();
+    };
+}
+
+Async.prototype.haveItemsQueued = function Async$haveItemsQueued() {
+    return this._length > 0;
+};
+
+Async.prototype.invokeLater = function Async$invokeLater(fn, receiver, arg) {
+    if (process !== void 0 &&
+        process.domain != null &&
+        !fn.domain) {
+        fn = process.domain.bind(fn);
+    }
+    this._lateBuffer.push(fn, receiver, arg);
+    this._queueTick();
+};
+
+Async.prototype.invoke = function Async$invoke(fn, receiver, arg) {
+    if (process !== void 0 &&
+        process.domain != null &&
+        !fn.domain) {
+        fn = process.domain.bind(fn);
+    }
+    var functionBuffer = this._functionBuffer;
+    functionBuffer.push(fn, receiver, arg);
+    this._length = functionBuffer.length();
+    this._queueTick();
+};
+
+Async.prototype._consumeFunctionBuffer =
+function Async$_consumeFunctionBuffer() {
+    var functionBuffer = this._functionBuffer;
+    while(functionBuffer.length() > 0) {
+        var fn = functionBuffer.shift();
+        var receiver = functionBuffer.shift();
+        var arg = functionBuffer.shift();
+        fn.call(receiver, arg);
+    }
+    this._reset();
+    this._consumeLateBuffer();
+};
+
+Async.prototype._consumeLateBuffer = function Async$_consumeLateBuffer() {
+    var buffer = this._lateBuffer;
+    while(buffer.length() > 0) {
+        var fn = buffer.shift();
+        var receiver = buffer.shift();
+        var arg = buffer.shift();
+        var res = tryCatch1(fn, receiver, arg);
+        if (res === errorObj) {
+            this._queueTick();
+            if (fn.domain != null) {
+                fn.domain.emit("error", res.e);
+            }
+            else {
+                throw res.e;
+            }
+        }
+    }
+};
+
+Async.prototype._queueTick = function Async$_queue() {
+    if (!this._isTickUsed) {
+        schedule(this.consumeFunctionBuffer);
+        this._isTickUsed = true;
+    }
+};
+
+Async.prototype._reset = function Async$_reset() {
+    this._isTickUsed = false;
+    this._length = 0;
+};
+
+module.exports = new Async();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/bluebird.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/bluebird.js
new file mode 100644
index 0000000..6fd85f1
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/bluebird.js
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var Promise = require("./promise.js")();
+module.exports = Promise;
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/call_get.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/call_get.js
new file mode 100644
index 0000000..2a3c1f5
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/call_get.js
@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise) {
+    Promise.prototype.call = function Promise$call(propertyName) {
+        var $_len = arguments.length;var args = new Array($_len - 1); for(var $_i = 1; $_i < $_len; ++$_i) {args[$_i - 1] = arguments[$_i];}
+
+        return this._then(function(obj) {
+                return obj[propertyName].apply(obj, args);
+            },
+            void 0,
+            void 0,
+            void 0,
+            void 0,
+            this.call
+       );
+    };
+
+    function Promise$getter(obj) {
+        var prop = typeof this === "string"
+            ? this
+            : ("" + this);
+        return obj[prop];
+    }
+    Promise.prototype.get = function Promise$get(propertyName) {
+        return this._then(
+            Promise$getter,
+            void 0,
+            void 0,
+            propertyName,
+            void 0,
+            this.get
+       );
+    };
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/cancel.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/cancel.js
new file mode 100644
index 0000000..542b488
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/cancel.js
@@ -0,0 +1,77 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, INTERNAL) {
+    var errors = require("./errors.js");
+    var async = require("./async.js");
+    var CancellationError = errors.CancellationError;
+    var SYNC_TOKEN = {};
+
+    Promise.prototype._cancel = function Promise$_cancel() {
+        if (!this.isCancellable()) return this;
+        var parent;
+        if ((parent = this._cancellationParent) !== void 0) {
+            parent.cancel(SYNC_TOKEN);
+            return;
+        }
+        var err = new CancellationError();
+        this._attachExtraTrace(err);
+        this._rejectUnchecked(err);
+    };
+
+    Promise.prototype.cancel = function Promise$cancel(token) {
+        if (!this.isCancellable()) return this;
+        if (token === SYNC_TOKEN) {
+            this._cancel();
+            return this;
+        }
+        async.invokeLater(this._cancel, this, void 0);
+        return this;
+    };
+
+    Promise.prototype.cancellable = function Promise$cancellable() {
+        if (this._cancellable()) return this;
+        this._setCancellable();
+        this._cancellationParent = void 0;
+        return this;
+    };
+
+    Promise.prototype.uncancellable = function Promise$uncancellable() {
+        var ret = new Promise(INTERNAL);
+        ret._setTrace(this.uncancellable, this);
+        ret._follow(this);
+        ret._unsetCancellable();
+        if (this._isBound()) ret._setBoundTo(this._boundTo);
+        return ret;
+    };
+
+    Promise.prototype.fork =
+    function Promise$fork(didFulfill, didReject, didProgress) {
+        var ret = this._then(didFulfill, didReject, didProgress,
+            void 0, void 0, this.fork);
+
+        ret._setCancellable();
+        ret._cancellationParent = void 0;
+        return ret;
+    };
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/captured_trace.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/captured_trace.js
new file mode 100644
index 0000000..af34f11
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/captured_trace.js
@@ -0,0 +1,237 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function() {
+var inherits = require("./util.js").inherits;
+var defineProperty = require("./es5.js").defineProperty;
+
+var rignore = new RegExp(
+    "\\b(?:[\\w.]*Promise(?:Array|Spawn)?\\$_\\w+|" +
+    "tryCatch(?:1|2|Apply)|new \\w*PromiseArray|" +
+    "\\w*PromiseArray\\.\\w*PromiseArray|" +
+    "setTimeout|CatchFilter\\$_\\w+|makeNodePromisified|processImmediate|" +
+    "process._tickCallback|nextTick|Async\\$\\w+)\\b"
+);
+
+var rtraceline = null;
+var formatStack = null;
+var areNamesMangled = false;
+
+function formatNonError(obj) {
+    var str;
+    if (typeof obj === "function") {
+        str = "[function " +
+            (obj.name || "anonymous") +
+            "]";
+    }
+    else {
+        str = obj.toString();
+        var ruselessToString = /\[object [a-zA-Z0-9$_]+\]/;
+        if (ruselessToString.test(str)) {
+            try {
+                var newStr = JSON.stringify(obj);
+                str = newStr;
+            }
+            catch(e) {
+
+            }
+        }
+        if (str.length === 0) {
+            str = "(empty array)";
+        }
+    }
+    return ("(<" + snip(str) + ">, no stack trace)");
+}
+
+function snip(str) {
+    var maxChars = 41;
+    if (str.length < maxChars) {
+        return str;
+    }
+    return str.substr(0, maxChars - 3) + "...";
+}
+
+function CapturedTrace(ignoreUntil, isTopLevel) {
+    if (!areNamesMangled) {
+    }
+    this.captureStackTrace(ignoreUntil, isTopLevel);
+
+}
+inherits(CapturedTrace, Error);
+
+CapturedTrace.prototype.captureStackTrace =
+function CapturedTrace$captureStackTrace(ignoreUntil, isTopLevel) {
+    captureStackTrace(this, ignoreUntil, isTopLevel);
+};
+
+CapturedTrace.possiblyUnhandledRejection =
+function CapturedTrace$PossiblyUnhandledRejection(reason) {
+    if (typeof console === "object") {
+        var message;
+        if (typeof reason === "object" || typeof reason === "function") {
+            var stack = reason.stack;
+            message = "Possibly unhandled " + formatStack(stack, reason);
+        }
+        else {
+            message = "Possibly unhandled " + String(reason);
+        }
+        if (typeof console.error === "function" ||
+            typeof console.error === "object") {
+            console.error(message);
+        }
+        else if (typeof console.log === "function" ||
+            typeof console.error === "object") {
+            console.log(message);
+        }
+    }
+};
+
+areNamesMangled = CapturedTrace.prototype.captureStackTrace.name !==
+    "CapturedTrace$captureStackTrace";
+
+CapturedTrace.combine = function CapturedTrace$Combine(current, prev) {
+    var curLast = current.length - 1;
+    for (var i = prev.length - 1; i >= 0; --i) {
+        var line = prev[i];
+        if (current[curLast] === line) {
+            current.pop();
+            curLast--;
+        }
+        else {
+            break;
+        }
+    }
+
+    current.push("From previous event:");
+    var lines = current.concat(prev);
+
+    var ret = [];
+
+
+    for (var i = 0, len = lines.length; i < len; ++i) {
+
+        if ((rignore.test(lines[i]) ||
+            (i > 0 && !rtraceline.test(lines[i])) &&
+            lines[i] !== "From previous event:")
+       ) {
+            continue;
+        }
+        ret.push(lines[i]);
+    }
+    return ret;
+};
+
+CapturedTrace.isSupported = function CapturedTrace$IsSupported() {
+    return typeof captureStackTrace === "function";
+};
+
+var captureStackTrace = (function stackDetection() {
+    if (typeof Error.stackTraceLimit === "number" &&
+        typeof Error.captureStackTrace === "function") {
+        rtraceline = /^\s*at\s*/;
+        formatStack = function(stack, error) {
+            if (typeof stack === "string") return stack;
+
+            if (error.name !== void 0 &&
+                error.message !== void 0) {
+                return error.name + ". " + error.message;
+            }
+            return formatNonError(error);
+
+
+        };
+        var captureStackTrace = Error.captureStackTrace;
+        return function CapturedTrace$_captureStackTrace(
+            receiver, ignoreUntil) {
+            captureStackTrace(receiver, ignoreUntil);
+        };
+    }
+    var err = new Error();
+
+    if (!areNamesMangled && typeof err.stack === "string" &&
+        typeof "".startsWith === "function" &&
+        (err.stack.startsWith("stackDetection@")) &&
+        stackDetection.name === "stackDetection") {
+
+        defineProperty(Error, "stackTraceLimit", {
+            writable: true,
+            enumerable: false,
+            configurable: false,
+            value: 25
+        });
+        rtraceline = /@/;
+        var rline = /[@\n]/;
+
+        formatStack = function(stack, error) {
+            if (typeof stack === "string") {
+                return (error.name + ". " + error.message + "\n" + stack);
+            }
+
+            if (error.name !== void 0 &&
+                error.message !== void 0) {
+                return error.name + ". " + error.message;
+            }
+            return formatNonError(error);
+        };
+
+        return function captureStackTrace(o, fn) {
+            var name = fn.name;
+            var stack = new Error().stack;
+            var split = stack.split(rline);
+            var i, len = split.length;
+            for (i = 0; i < len; i += 2) {
+                if (split[i] === name) {
+                    break;
+                }
+            }
+            split = split.slice(i + 2);
+            len = split.length - 2;
+            var ret = "";
+            for (i = 0; i < len; i += 2) {
+                ret += split[i];
+                ret += "@";
+                ret += split[i + 1];
+                ret += "\n";
+            }
+            o.stack = ret;
+        };
+    }
+    else {
+        formatStack = function(stack, error) {
+            if (typeof stack === "string") return stack;
+
+            if ((typeof error === "object" ||
+                typeof error === "function") &&
+                error.name !== void 0 &&
+                error.message !== void 0) {
+                return error.name + ". " + error.message;
+            }
+            return formatNonError(error);
+        };
+
+        return null;
+    }
+})();
+
+return CapturedTrace;
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/catch_filter.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/catch_filter.js
new file mode 100644
index 0000000..8b42af1
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/catch_filter.js
@@ -0,0 +1,93 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(NEXT_FILTER) {
+var util = require("./util.js");
+var errors = require("./errors.js");
+var tryCatch1 = util.tryCatch1;
+var errorObj = util.errorObj;
+var keys = require("./es5.js").keys;
+
+function CatchFilter(instances, callback, promise) {
+    this._instances = instances;
+    this._callback = callback;
+    this._promise = promise;
+}
+
+function CatchFilter$_safePredicate(predicate, e) {
+    var safeObject = {};
+    var retfilter = tryCatch1(predicate, safeObject, e);
+
+    if (retfilter === errorObj) return retfilter;
+
+    var safeKeys = keys(safeObject);
+    if (safeKeys.length) {
+        errorObj.e = new TypeError(
+            "Catch filter must inherit from Error "
+          + "or be a simple predicate function");
+        return errorObj;
+    }
+    return retfilter;
+}
+
+CatchFilter.prototype.doFilter = function CatchFilter$_doFilter(e) {
+    var cb = this._callback;
+    var promise = this._promise;
+    var boundTo = promise._isBound() ? promise._boundTo : void 0;
+    for (var i = 0, len = this._instances.length; i < len; ++i) {
+        var item = this._instances[i];
+        var itemIsErrorType = item === Error ||
+            (item != null && item.prototype instanceof Error);
+
+        if (itemIsErrorType && e instanceof item) {
+            var ret = tryCatch1(cb, boundTo, e);
+            if (ret === errorObj) {
+                NEXT_FILTER.e = ret.e;
+                return NEXT_FILTER;
+            }
+            return ret;
+        } else if (typeof item === "function" && !itemIsErrorType) {
+            var shouldHandle = CatchFilter$_safePredicate(item, e);
+            if (shouldHandle === errorObj) {
+                var trace = errors.canAttach(errorObj.e)
+                    ? errorObj.e
+                    : new Error(errorObj.e + "");
+                this._promise._attachExtraTrace(trace);
+                e = errorObj.e;
+                break;
+            } else if (shouldHandle) {
+                var ret = tryCatch1(cb, boundTo, e);
+                if (ret === errorObj) {
+                    NEXT_FILTER.e = ret.e;
+                    return NEXT_FILTER;
+                }
+                return ret;
+            }
+        }
+    }
+    NEXT_FILTER.e = e;
+    return NEXT_FILTER;
+};
+
+return CatchFilter;
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/direct_resolve.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/direct_resolve.js
new file mode 100644
index 0000000..f4d5384
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/direct_resolve.js
@@ -0,0 +1,83 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var util = require("./util.js");
+var isPrimitive = util.isPrimitive;
+var wrapsPrimitiveReceiver = util.wrapsPrimitiveReceiver;
+
+module.exports = function(Promise) {
+var returner = function Promise$_returner() {
+    return this;
+};
+var thrower = function Promise$_thrower() {
+    throw this;
+};
+
+var wrapper = function Promise$_wrapper(value, action) {
+    if (action === 1) {
+        return function Promise$_thrower() {
+            throw value;
+        };
+    }
+    else if (action === 2) {
+        return function Promise$_returner() {
+            return value;
+        };
+    }
+};
+
+
+Promise.prototype["return"] =
+Promise.prototype.thenReturn =
+function Promise$thenReturn(value) {
+    if (wrapsPrimitiveReceiver && isPrimitive(value)) {
+        return this._then(
+            wrapper(value, 2),
+            void 0,
+            void 0,
+            void 0,
+            void 0,
+            this.thenReturn
+       );
+    }
+    return this._then(returner, void 0, void 0,
+                        value, void 0, this.thenReturn);
+};
+
+Promise.prototype["throw"] =
+Promise.prototype.thenThrow =
+function Promise$thenThrow(reason) {
+    if (wrapsPrimitiveReceiver && isPrimitive(reason)) {
+        return this._then(
+            wrapper(reason, 1),
+            void 0,
+            void 0,
+            void 0,
+            void 0,
+            this.thenThrow
+       );
+    }
+    return this._then(thrower, void 0, void 0,
+                        reason, void 0, this.thenThrow);
+};
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/errors.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/errors.js
new file mode 100644
index 0000000..35fb66e
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/errors.js
@@ -0,0 +1,114 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var global = require("./global.js");
+var Objectfreeze = require("./es5.js").freeze;
+var util = require("./util.js");
+var inherits = util.inherits;
+var notEnumerableProp = util.notEnumerableProp;
+var Error = global.Error;
+
+function markAsOriginatingFromRejection(e) {
+    try {
+        notEnumerableProp(e, "isAsync", true);
+    }
+    catch(ignore) {}
+}
+
+function originatesFromRejection(e) {
+    if (e == null) return false;
+    return ((e instanceof RejectionError) ||
+        e["isAsync"] === true);
+}
+
+function isError(obj) {
+    return obj instanceof Error;
+}
+
+function canAttach(obj) {
+    return isError(obj);
+}
+
+function subError(nameProperty, defaultMessage) {
+    function SubError(message) {
+        if (!(this instanceof SubError)) return new SubError(message);
+        this.message = typeof message === "string" ? message : defaultMessage;
+        this.name = nameProperty;
+        if (Error.captureStackTrace) {
+            Error.captureStackTrace(this, this.constructor);
+        }
+    }
+    inherits(SubError, Error);
+    return SubError;
+}
+
+var TypeError = global.TypeError;
+if (typeof TypeError !== "function") {
+    TypeError = subError("TypeError", "type error");
+}
+var RangeError = global.RangeError;
+if (typeof RangeError !== "function") {
+    RangeError = subError("RangeError", "range error");
+}
+var CancellationError = subError("CancellationError", "cancellation error");
+var TimeoutError = subError("TimeoutError", "timeout error");
+
+function RejectionError(message) {
+    this.name = "RejectionError";
+    this.message = message;
+    this.cause = message;
+    this.isAsync = true;
+
+    if (message instanceof Error) {
+        this.message = message.message;
+        this.stack = message.stack;
+    }
+    else if (Error.captureStackTrace) {
+        Error.captureStackTrace(this, this.constructor);
+    }
+
+}
+inherits(RejectionError, Error);
+
+var key = "__BluebirdErrorTypes__";
+var errorTypes = global[key];
+if (!errorTypes) {
+    errorTypes = Objectfreeze({
+        CancellationError: CancellationError,
+        TimeoutError: TimeoutError,
+        RejectionError: RejectionError
+    });
+    notEnumerableProp(global, key, errorTypes);
+}
+
+module.exports = {
+    Error: Error,
+    TypeError: TypeError,
+    RangeError: RangeError,
+    CancellationError: errorTypes.CancellationError,
+    RejectionError: errorTypes.RejectionError,
+    TimeoutError: errorTypes.TimeoutError,
+    originatesFromRejection: originatesFromRejection,
+    markAsOriginatingFromRejection: markAsOriginatingFromRejection,
+    canAttach: canAttach
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/errors_api_rejection.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/errors_api_rejection.js
new file mode 100644
index 0000000..e953e3b
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/errors_api_rejection.js
@@ -0,0 +1,38 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise) {
+var TypeError = require('./errors.js').TypeError;
+
+function apiRejection(msg) {
+    var error = new TypeError(msg);
+    var ret = Promise.rejected(error);
+    var parent = ret._peekContext();
+    if (parent != null) {
+        parent._attachExtraTrace(error);
+    }
+    return ret;
+}
+
+return apiRejection;
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/es5.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/es5.js
new file mode 100644
index 0000000..e22a0a9
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/es5.js
@@ -0,0 +1,89 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+var isES5 = (function(){
+    "use strict";
+    return this === void 0;
+})();
+
+if (isES5) {
+    module.exports = {
+        freeze: Object.freeze,
+        defineProperty: Object.defineProperty,
+        keys: Object.keys,
+        getPrototypeOf: Object.getPrototypeOf,
+        isArray: Array.isArray,
+        isES5: isES5
+    };
+}
+
+else {
+    var has = {}.hasOwnProperty;
+    var str = {}.toString;
+    var proto = {}.constructor.prototype;
+
+    function ObjectKeys(o) {
+        var ret = [];
+        for (var key in o) {
+            if (has.call(o, key)) {
+                ret.push(key);
+            }
+        }
+        return ret;
+    }
+
+    function ObjectDefineProperty(o, key, desc) {
+        o[key] = desc.value;
+        return o;
+    }
+
+    function ObjectFreeze(obj) {
+        return obj;
+    }
+
+    function ObjectGetPrototypeOf(obj) {
+        try {
+            return Object(obj).constructor.prototype;
+        }
+        catch (e) {
+            return proto;
+        }
+    }
+
+    function ArrayIsArray(obj) {
+        try {
+            return str.call(obj) === "[object Array]";
+        }
+        catch(e) {
+            return false;
+        }
+    }
+
+    module.exports = {
+        isArray: ArrayIsArray,
+        keys: ObjectKeys,
+        defineProperty: ObjectDefineProperty,
+        freeze: ObjectFreeze,
+        getPrototypeOf: ObjectGetPrototypeOf,
+        isES5: isES5
+    };
+}
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/filter.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/filter.js
new file mode 100644
index 0000000..a4b8ae7
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/filter.js
@@ -0,0 +1,53 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise) {
+    var isArray = require("./util.js").isArray;
+
+    function Promise$_filter(booleans) {
+        var values = this._settledValue;
+        var len = values.length;
+        var ret = new Array(len);
+        var j = 0;
+
+        for (var i = 0; i < len; ++i) {
+            if (booleans[i]) ret[j++] = values[i];
+
+        }
+        ret.length = j;
+        return ret;
+    }
+
+    var ref = {ref: null};
+    Promise.filter = function Promise$Filter(promises, fn) {
+        return Promise.map(promises, fn, ref)
+            ._then(Promise$_filter, void 0, void 0,
+                    ref.ref, void 0, Promise.filter);
+    };
+
+    Promise.prototype.filter = function Promise$filter(fn) {
+        return this.map(fn, ref)
+            ._then(Promise$_filter, void 0, void 0,
+                    ref.ref, void 0, this.filter);
+    };
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/finally.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/finally.js
new file mode 100644
index 0000000..ef1e0ef
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/finally.js
@@ -0,0 +1,132 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, NEXT_FILTER) {
+    var util = require("./util.js");
+    var wrapsPrimitiveReceiver = util.wrapsPrimitiveReceiver;
+    var isPrimitive = util.isPrimitive;
+    var thrower = util.thrower;
+
+
+    function returnThis() {
+        return this;
+    }
+    function throwThis() {
+        throw this;
+    }
+    function makeReturner(r) {
+        return function Promise$_returner() {
+            return r;
+        };
+    }
+    function makeThrower(r) {
+        return function Promise$_thrower() {
+            throw r;
+        };
+    }
+    function promisedFinally(ret, reasonOrValue, isFulfilled) {
+        var useConstantFunction =
+                        wrapsPrimitiveReceiver && isPrimitive(reasonOrValue);
+
+        if (isFulfilled) {
+            return ret._then(
+                useConstantFunction
+                    ? returnThis
+                    : makeReturner(reasonOrValue),
+                thrower, void 0, reasonOrValue, void 0, promisedFinally);
+        }
+        else {
+            return ret._then(
+                useConstantFunction
+                    ? throwThis
+                    : makeThrower(reasonOrValue),
+                thrower, void 0, reasonOrValue, void 0, promisedFinally);
+        }
+    }
+
+    function finallyHandler(reasonOrValue) {
+        var promise = this.promise;
+        var handler = this.handler;
+
+        var ret = promise._isBound()
+                        ? handler.call(promise._boundTo)
+                        : handler();
+
+        if (ret !== void 0) {
+            var maybePromise = Promise._cast(ret, finallyHandler, void 0);
+            if (maybePromise instanceof Promise) {
+                return promisedFinally(maybePromise, reasonOrValue,
+                                        promise.isFulfilled());
+            }
+        }
+
+        if (promise.isRejected()) {
+            NEXT_FILTER.e = reasonOrValue;
+            return NEXT_FILTER;
+        }
+        else {
+            return reasonOrValue;
+        }
+    }
+
+    function tapHandler(value) {
+        var promise = this.promise;
+        var handler = this.handler;
+
+        var ret = promise._isBound()
+                        ? handler.call(promise._boundTo, value)
+                        : handler(value);
+
+        if (ret !== void 0) {
+            var maybePromise = Promise._cast(ret, tapHandler, void 0);
+            if (maybePromise instanceof Promise) {
+                return promisedFinally(maybePromise, value, true);
+            }
+        }
+        return value;
+    }
+
+    Promise.prototype._passThroughHandler =
+    function Promise$_passThroughHandler(handler, isFinally, caller) {
+        if (typeof handler !== "function") return this.then();
+
+        var promiseAndHandler = {
+            promise: this,
+            handler: handler
+        };
+
+        return this._then(
+                isFinally ? finallyHandler : tapHandler,
+                isFinally ? finallyHandler : void 0, void 0,
+                promiseAndHandler, void 0, caller);
+    };
+
+    Promise.prototype.lastly =
+    Promise.prototype["finally"] = function Promise$finally(handler) {
+        return this._passThroughHandler(handler, true, this.lastly);
+    };
+
+    Promise.prototype.tap = function Promise$tap(handler) {
+        return this._passThroughHandler(handler, false, this.tap);
+    };
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/generators.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/generators.js
new file mode 100644
index 0000000..9632ae7
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/generators.js
@@ -0,0 +1,56 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, apiRejection, INTERNAL) {
+    var PromiseSpawn = require("./promise_spawn.js")(Promise, INTERNAL);
+    var errors = require("./errors.js");
+    var TypeError = errors.TypeError;
+    var deprecated = require("./util.js").deprecated;
+
+    Promise.coroutine = function Promise$Coroutine(generatorFunction) {
+        if (typeof generatorFunction !== "function") {
+            throw new TypeError("generatorFunction must be a function");
+        }
+        var PromiseSpawn$ = PromiseSpawn;
+        return function anonymous() {
+            var generator = generatorFunction.apply(this, arguments);
+            var spawn = new PromiseSpawn$(void 0, void 0, anonymous);
+            spawn._generator = generator;
+            spawn._next(void 0);
+            return spawn.promise();
+        };
+    };
+
+    Promise.coroutine.addYieldHandler = PromiseSpawn.addYieldHandler;
+
+    Promise.spawn = function Promise$Spawn(generatorFunction) {
+        deprecated("Promise.spawn is deprecated. Use Promise.coroutine instead.");
+        if (typeof generatorFunction !== "function") {
+            return apiRejection("generatorFunction must be a function");
+        }
+        var spawn = new PromiseSpawn(generatorFunction, this, Promise.spawn);
+        var ret = spawn.promise();
+        spawn._run(Promise.spawn);
+        return ret;
+    };
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/global.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/global.js
new file mode 100644
index 0000000..1ab1947
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/global.js
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = (function(){
+    if (typeof this !== "undefined") {
+        return this;
+    }
+    if (typeof process !== "undefined" &&
+        typeof global !== "undefined" &&
+        typeof process.execPath === "string") {
+        return global;
+    }
+    if (typeof window !== "undefined" &&
+        typeof document !== "undefined" &&
+        typeof navigator !== "undefined" && navigator !== null &&
+        typeof navigator.appName === "string") {
+            if(window.wrappedJSObject !== undefined){
+                return window.wrappedJSObject;
+            }
+        return window;
+    }
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/map.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/map.js
new file mode 100644
index 0000000..b2a36b0
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/map.js
@@ -0,0 +1,127 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(
+    Promise, Promise$_CreatePromiseArray, PromiseArray, apiRejection) {
+
+    function Promise$_mapper(values) {
+        var fn = this;
+        var receiver = void 0;
+
+        if (typeof fn !== "function")  {
+            receiver = fn.receiver;
+            fn = fn.fn;
+        }
+        var shouldDefer = false;
+
+        var ret = new Array(values.length);
+
+        if (receiver === void 0) {
+            for (var i = 0, len = values.length; i < len; ++i) {
+                var value = fn(values[i], i, len);
+                if (!shouldDefer) {
+                    var maybePromise = Promise._cast(value,
+                            Promise$_mapper, void 0);
+                    if (maybePromise instanceof Promise) {
+                        if (maybePromise.isFulfilled()) {
+                            ret[i] = maybePromise._settledValue;
+                            continue;
+                        }
+                        else {
+                            shouldDefer = true;
+                        }
+                        value = maybePromise;
+                    }
+                }
+                ret[i] = value;
+            }
+        }
+        else {
+            for (var i = 0, len = values.length; i < len; ++i) {
+                var value = fn.call(receiver, values[i], i, len);
+                if (!shouldDefer) {
+                    var maybePromise = Promise._cast(value,
+                            Promise$_mapper, void 0);
+                    if (maybePromise instanceof Promise) {
+                        if (maybePromise.isFulfilled()) {
+                            ret[i] = maybePromise._settledValue;
+                            continue;
+                        }
+                        else {
+                            shouldDefer = true;
+                        }
+                        value = maybePromise;
+                    }
+                }
+                ret[i] = value;
+            }
+        }
+        return shouldDefer
+            ? Promise$_CreatePromiseArray(ret, PromiseArray,
+                Promise$_mapper, void 0).promise()
+            : ret;
+    }
+
+    function Promise$_Map(promises, fn, useBound, caller, ref) {
+        if (typeof fn !== "function") {
+            return apiRejection("fn must be a function");
+        }
+
+        if (useBound === true && promises._isBound()) {
+            fn = {
+                fn: fn,
+                receiver: promises._boundTo
+            };
+        }
+
+        var ret = Promise$_CreatePromiseArray(
+            promises,
+            PromiseArray,
+            caller,
+            useBound === true && promises._isBound()
+                ? promises._boundTo
+                : void 0
+       ).promise();
+
+        if (ref !== void 0) {
+            ref.ref = ret;
+        }
+
+        return ret._then(
+            Promise$_mapper,
+            void 0,
+            void 0,
+            fn,
+            void 0,
+            caller
+       );
+    }
+
+    Promise.prototype.map = function Promise$map(fn, ref) {
+        return Promise$_Map(this, fn, true, this.map, ref);
+    };
+
+    Promise.map = function Promise$Map(promises, fn, ref) {
+        return Promise$_Map(promises, fn, false, Promise.map, ref);
+    };
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/nodeify.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/nodeify.js
new file mode 100644
index 0000000..9fe25f9
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/nodeify.js
@@ -0,0 +1,64 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise) {
+    var util = require("./util.js");
+    var async = require("./async.js");
+    var tryCatch2 = util.tryCatch2;
+    var tryCatch1 = util.tryCatch1;
+    var errorObj = util.errorObj;
+
+    function thrower(r) {
+        throw r;
+    }
+
+    function Promise$_successAdapter(val, receiver) {
+        var nodeback = this;
+        var ret = tryCatch2(nodeback, receiver, null, val);
+        if (ret === errorObj) {
+            async.invokeLater(thrower, void 0, ret.e);
+        }
+    }
+    function Promise$_errorAdapter(reason, receiver) {
+        var nodeback = this;
+        var ret = tryCatch1(nodeback, receiver, reason);
+        if (ret === errorObj) {
+            async.invokeLater(thrower, void 0, ret.e);
+        }
+    }
+
+    Promise.prototype.nodeify = function Promise$nodeify(nodeback) {
+        if (typeof nodeback == "function") {
+            this._then(
+                Promise$_successAdapter,
+                Promise$_errorAdapter,
+                void 0,
+                nodeback,
+                this._isBound() ? this._boundTo : null,
+                this.nodeify
+            );
+            return;
+        }
+        return this;
+    };
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/progress.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/progress.js
new file mode 100644
index 0000000..106bc58
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/progress.js
@@ -0,0 +1,113 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, isPromiseArrayProxy) {
+    var util = require("./util.js");
+    var async = require("./async.js");
+    var errors = require("./errors.js");
+    var tryCatch1 = util.tryCatch1;
+    var errorObj = util.errorObj;
+
+    Promise.prototype.progressed = function Promise$progressed(handler) {
+        return this._then(void 0, void 0, handler,
+                            void 0, void 0, this.progressed);
+    };
+
+    Promise.prototype._progress = function Promise$_progress(progressValue) {
+        if (this._isFollowingOrFulfilledOrRejected()) return;
+        this._progressUnchecked(progressValue);
+
+    };
+
+    Promise.prototype._progressHandlerAt =
+    function Promise$_progressHandlerAt(index) {
+        if (index === 0) return this._progressHandler0;
+        return this[index + 2 - 5];
+    };
+
+    Promise.prototype._doProgressWith =
+    function Promise$_doProgressWith(progression) {
+        var progressValue = progression.value;
+        var handler = progression.handler;
+        var promise = progression.promise;
+        var receiver = progression.receiver;
+
+        this._pushContext();
+        var ret = tryCatch1(handler, receiver, progressValue);
+        this._popContext();
+
+        if (ret === errorObj) {
+            if (ret.e != null &&
+                ret.e.name !== "StopProgressPropagation") {
+                var trace = errors.canAttach(ret.e)
+                    ? ret.e : new Error(ret.e + "");
+                promise._attachExtraTrace(trace);
+                promise._progress(ret.e);
+            }
+        }
+        else if (Promise.is(ret)) {
+            ret._then(promise._progress, null, null, promise, void 0,
+                this._progress);
+        }
+        else {
+            promise._progress(ret);
+        }
+    };
+
+
+    Promise.prototype._progressUnchecked =
+    function Promise$_progressUnchecked(progressValue) {
+        if (!this.isPending()) return;
+        var len = this._length();
+
+        for (var i = 0; i < len; i += 5) {
+            var handler = this._progressHandlerAt(i);
+            var promise = this._promiseAt(i);
+            if (!Promise.is(promise)) {
+                var receiver = this._receiverAt(i);
+                if (typeof handler === "function") {
+                    handler.call(receiver, progressValue, promise);
+                }
+                else if (Promise.is(receiver) && receiver._isProxied()) {
+                    receiver._progressUnchecked(progressValue);
+                }
+                else if (isPromiseArrayProxy(receiver, promise)) {
+                    receiver._promiseProgressed(progressValue, promise);
+                }
+                continue;
+            }
+
+            if (typeof handler === "function") {
+                async.invoke(this._doProgressWith, this, {
+                    handler: handler,
+                    promise: promise,
+                    receiver: this._receiverAt(i),
+                    value: progressValue
+                });
+            }
+            else {
+                async.invoke(promise._progress, promise, progressValue);
+            }
+        }
+    };
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/promise.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/promise.js
new file mode 100644
index 0000000..a5fe1d6
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/promise.js
@@ -0,0 +1,1167 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function() {
+var global = require("./global.js");
+var util = require("./util.js");
+var async = require("./async.js");
+var errors = require("./errors.js");
+
+var INTERNAL = function(){};
+var APPLY = {};
+var NEXT_FILTER = {e: null};
+
+var PromiseArray = require("./promise_array.js")(Promise, INTERNAL);
+var CapturedTrace = require("./captured_trace.js")();
+var CatchFilter = require("./catch_filter.js")(NEXT_FILTER);
+var PromiseResolver = require("./promise_resolver.js");
+
+var isArray = util.isArray;
+
+var errorObj = util.errorObj;
+var tryCatch1 = util.tryCatch1;
+var tryCatch2 = util.tryCatch2;
+var tryCatchApply = util.tryCatchApply;
+var RangeError = errors.RangeError;
+var TypeError = errors.TypeError;
+var CancellationError = errors.CancellationError;
+var TimeoutError = errors.TimeoutError;
+var RejectionError = errors.RejectionError;
+var originatesFromRejection = errors.originatesFromRejection;
+var markAsOriginatingFromRejection = errors.markAsOriginatingFromRejection;
+var canAttach = errors.canAttach;
+var thrower = util.thrower;
+var apiRejection = require("./errors_api_rejection")(Promise);
+
+
+var makeSelfResolutionError = function Promise$_makeSelfResolutionError() {
+    return new TypeError("circular promise resolution chain");
+};
+
+function isPromise(obj) {
+    if (obj === void 0) return false;
+    return obj instanceof Promise;
+}
+
+function isPromiseArrayProxy(receiver, promiseSlotValue) {
+    if (receiver instanceof PromiseArray) {
+        return promiseSlotValue >= 0;
+    }
+    return false;
+}
+
+function Promise(resolver) {
+    if (typeof resolver !== "function") {
+        throw new TypeError("the promise constructor requires a resolver function");
+    }
+    if (this.constructor !== Promise) {
+        throw new TypeError("the promise constructor cannot be invoked directly");
+    }
+    this._bitField = 0;
+    this._fulfillmentHandler0 = void 0;
+    this._rejectionHandler0 = void 0;
+    this._promise0 = void 0;
+    this._receiver0 = void 0;
+    this._settledValue = void 0;
+    this._boundTo = void 0;
+    if (resolver !== INTERNAL) this._resolveFromResolver(resolver);
+}
+
+Promise.prototype.bind = function Promise$bind(thisArg) {
+    var ret = new Promise(INTERNAL);
+    if (debugging) ret._setTrace(this.bind, this);
+    ret._follow(this);
+    ret._setBoundTo(thisArg);
+    if (this._cancellable()) {
+        ret._setCancellable();
+        ret._cancellationParent = this;
+    }
+    return ret;
+};
+
+Promise.prototype.toString = function Promise$toString() {
+    return "[object Promise]";
+};
+
+Promise.prototype.caught = Promise.prototype["catch"] =
+function Promise$catch(fn) {
+    var len = arguments.length;
+    if (len > 1) {
+        var catchInstances = new Array(len - 1),
+            j = 0, i;
+        for (i = 0; i < len - 1; ++i) {
+            var item = arguments[i];
+            if (typeof item === "function") {
+                catchInstances[j++] = item;
+            }
+            else {
+                var catchFilterTypeError =
+                    new TypeError(
+                        "A catch filter must be an error constructor "
+                        + "or a filter function");
+
+                this._attachExtraTrace(catchFilterTypeError);
+                async.invoke(this._reject, this, catchFilterTypeError);
+                return;
+            }
+        }
+        catchInstances.length = j;
+        fn = arguments[i];
+
+        this._resetTrace(this.caught);
+        var catchFilter = new CatchFilter(catchInstances, fn, this);
+        return this._then(void 0, catchFilter.doFilter, void 0,
+            catchFilter, void 0, this.caught);
+    }
+    return this._then(void 0, fn, void 0, void 0, void 0, this.caught);
+};
+
+Promise.prototype.then =
+function Promise$then(didFulfill, didReject, didProgress) {
+    return this._then(didFulfill, didReject, didProgress,
+        void 0, void 0, this.then);
+};
+
+
+Promise.prototype.done =
+function Promise$done(didFulfill, didReject, didProgress) {
+    var promise = this._then(didFulfill, didReject, didProgress,
+        void 0, void 0, this.done);
+    promise._setIsFinal();
+};
+
+Promise.prototype.spread = function Promise$spread(didFulfill, didReject) {
+    return this._then(didFulfill, didReject, void 0,
+        APPLY, void 0, this.spread);
+};
+
+Promise.prototype.isFulfilled = function Promise$isFulfilled() {
+    return (this._bitField & 268435456) > 0;
+};
+
+
+Promise.prototype.isRejected = function Promise$isRejected() {
+    return (this._bitField & 134217728) > 0;
+};
+
+Promise.prototype.isPending = function Promise$isPending() {
+    return !this.isResolved();
+};
+
+
+Promise.prototype.isResolved = function Promise$isResolved() {
+    return (this._bitField & 402653184) > 0;
+};
+
+
+Promise.prototype.isCancellable = function Promise$isCancellable() {
+    return !this.isResolved() &&
+        this._cancellable();
+};
+
+Promise.prototype.toJSON = function Promise$toJSON() {
+    var ret = {
+        isFulfilled: false,
+        isRejected: false,
+        fulfillmentValue: void 0,
+        rejectionReason: void 0
+    };
+    if (this.isFulfilled()) {
+        ret.fulfillmentValue = this._settledValue;
+        ret.isFulfilled = true;
+    }
+    else if (this.isRejected()) {
+        ret.rejectionReason = this._settledValue;
+        ret.isRejected = true;
+    }
+    return ret;
+};
+
+Promise.prototype.all = function Promise$all() {
+    return Promise$_all(this, true, this.all);
+};
+
+
+Promise.is = isPromise;
+
+function Promise$_all(promises, useBound, caller) {
+    return Promise$_CreatePromiseArray(
+        promises,
+        PromiseArray,
+        caller,
+        useBound === true && promises._isBound()
+            ? promises._boundTo
+            : void 0
+   ).promise();
+}
+Promise.all = function Promise$All(promises) {
+    return Promise$_all(promises, false, Promise.all);
+};
+
+Promise.join = function Promise$Join() {
+    var $_len = arguments.length;var args = new Array($_len); for(var $_i = 0; $_i < $_len; ++$_i) {args[$_i] = arguments[$_i];}
+    return Promise$_CreatePromiseArray(
+        args, PromiseArray, Promise.join, void 0).promise();
+};
+
+Promise.resolve = Promise.fulfilled =
+function Promise$Resolve(value, caller) {
+    var ret = new Promise(INTERNAL);
+    if (debugging) ret._setTrace(typeof caller === "function"
+        ? caller
+        : Promise.resolve, void 0);
+    if (ret._tryFollow(value)) {
+        return ret;
+    }
+    ret._cleanValues();
+    ret._setFulfilled();
+    ret._settledValue = value;
+    return ret;
+};
+
+Promise.reject = Promise.rejected = function Promise$Reject(reason) {
+    var ret = new Promise(INTERNAL);
+    if (debugging) ret._setTrace(Promise.reject, void 0);
+    markAsOriginatingFromRejection(reason);
+    ret._cleanValues();
+    ret._setRejected();
+    ret._settledValue = reason;
+    if (!canAttach(reason)) {
+        var trace = new Error(reason + "");
+        ret._setCarriedStackTrace(trace);
+    }
+    ret._ensurePossibleRejectionHandled();
+    return ret;
+};
+
+Promise.prototype.error = function Promise$_error(fn) {
+    return this.caught(originatesFromRejection, fn);
+};
+
+Promise.prototype._resolveFromSyncValue =
+function Promise$_resolveFromSyncValue(value, caller) {
+    if (value === errorObj) {
+        this._cleanValues();
+        this._setRejected();
+        this._settledValue = value.e;
+        this._ensurePossibleRejectionHandled();
+    }
+    else {
+        var maybePromise = Promise._cast(value, caller, void 0);
+        if (maybePromise instanceof Promise) {
+            this._follow(maybePromise);
+        }
+        else {
+            this._cleanValues();
+            this._setFulfilled();
+            this._settledValue = value;
+        }
+    }
+};
+
+Promise.method = function Promise$_Method(fn) {
+    if (typeof fn !== "function") {
+        throw new TypeError("fn must be a function");
+    }
+    return function Promise$_method() {
+        var value;
+        switch(arguments.length) {
+        case 0: value = tryCatch1(fn, this, void 0); break;
+        case 1: value = tryCatch1(fn, this, arguments[0]); break;
+        case 2: value = tryCatch2(fn, this, arguments[0], arguments[1]); break;
+        default:
+            var $_len = arguments.length;var args = new Array($_len); for(var $_i = 0; $_i < $_len; ++$_i) {args[$_i] = arguments[$_i];}
+            value = tryCatchApply(fn, args, this); break;
+        }
+        var ret = new Promise(INTERNAL);
+        if (debugging) ret._setTrace(Promise$_method, void 0);
+        ret._resolveFromSyncValue(value, Promise$_method);
+        return ret;
+    };
+};
+
+Promise.attempt = Promise["try"] = function Promise$_Try(fn, args, ctx) {
+
+    if (typeof fn !== "function") {
+        return apiRejection("fn must be a function");
+    }
+    var value = isArray(args)
+        ? tryCatchApply(fn, args, ctx)
+        : tryCatch1(fn, ctx, args);
+
+    var ret = new Promise(INTERNAL);
+    if (debugging) ret._setTrace(Promise.attempt, void 0);
+    ret._resolveFromSyncValue(value, Promise.attempt);
+    return ret;
+};
+
+Promise.defer = Promise.pending = function Promise$Defer(caller) {
+    var promise = new Promise(INTERNAL);
+    if (debugging) promise._setTrace(typeof caller === "function"
+                              ? caller : Promise.defer, void 0);
+    return new PromiseResolver(promise);
+};
+
+Promise.bind = function Promise$Bind(thisArg) {
+    var ret = new Promise(INTERNAL);
+    if (debugging) ret._setTrace(Promise.bind, void 0);
+    ret._setFulfilled();
+    ret._setBoundTo(thisArg);
+    return ret;
+};
+
+Promise.cast = function Promise$_Cast(obj, caller) {
+    if (typeof caller !== "function") {
+        caller = Promise.cast;
+    }
+    var ret = Promise._cast(obj, caller, void 0);
+    if (!(ret instanceof Promise)) {
+        return Promise.resolve(ret, caller);
+    }
+    return ret;
+};
+
+Promise.onPossiblyUnhandledRejection =
+function Promise$OnPossiblyUnhandledRejection(fn) {
+    if (typeof fn === "function") {
+        CapturedTrace.possiblyUnhandledRejection = fn;
+    }
+    else {
+        CapturedTrace.possiblyUnhandledRejection = void 0;
+    }
+};
+
+var debugging = false || !!(
+    typeof process !== "undefined" &&
+    typeof process.execPath === "string" &&
+    typeof process.env === "object" &&
+    (process.env["BLUEBIRD_DEBUG"] ||
+        process.env["NODE_ENV"] === "development")
+);
+
+
+Promise.longStackTraces = function Promise$LongStackTraces() {
+    if (async.haveItemsQueued() &&
+        debugging === false
+   ) {
+        throw new Error("cannot enable long stack traces after promises have been created");
+    }
+    debugging = CapturedTrace.isSupported();
+};
+
+Promise.hasLongStackTraces = function Promise$HasLongStackTraces() {
+    return debugging && CapturedTrace.isSupported();
+};
+
+Promise.prototype._setProxyHandlers =
+function Promise$_setProxyHandlers(receiver, promiseSlotValue) {
+    var index = this._length();
+
+    if (index >= 1048575 - 5) {
+        index = 0;
+        this._setLength(0);
+    }
+    if (index === 0) {
+        this._promise0 = promiseSlotValue;
+        this._receiver0 = receiver;
+    }
+    else {
+        var i = index - 5;
+        this[i + 3] = promiseSlotValue;
+        this[i + 4] = receiver;
+        this[i + 0] =
+        this[i + 1] =
+        this[i + 2] = void 0;
+    }
+    this._setLength(index + 5);
+};
+
+Promise.prototype._proxyPromiseArray =
+function Promise$_proxyPromiseArray(promiseArray, index) {
+    this._setProxyHandlers(promiseArray, index);
+};
+
+Promise.prototype._proxyPromise = function Promise$_proxyPromise(promise) {
+    promise._setProxied();
+    this._setProxyHandlers(promise, -1);
+};
+
+Promise.prototype._then =
+function Promise$_then(
+    didFulfill,
+    didReject,
+    didProgress,
+    receiver,
+    internalData,
+    caller
+) {
+    var haveInternalData = internalData !== void 0;
+    var ret = haveInternalData ? internalData : new Promise(INTERNAL);
+
+    if (debugging && !haveInternalData) {
+        var haveSameContext = this._peekContext() === this._traceParent;
+        ret._traceParent = haveSameContext ? this._traceParent : this;
+        ret._setTrace(typeof caller === "function"
+                ? caller
+                : this._then, this);
+    }
+
+    if (!haveInternalData && this._isBound()) {
+        ret._setBoundTo(this._boundTo);
+    }
+
+    var callbackIndex =
+        this._addCallbacks(didFulfill, didReject, didProgress, ret, receiver);
+
+    if (!haveInternalData && this._cancellable()) {
+        ret._setCancellable();
+        ret._cancellationParent = this;
+    }
+
+    if (this.isResolved()) {
+        async.invoke(this._queueSettleAt, this, callbackIndex);
+    }
+
+    return ret;
+};
+
+Promise.prototype._length = function Promise$_length() {
+    return this._bitField & 1048575;
+};
+
+Promise.prototype._isFollowingOrFulfilledOrRejected =
+function Promise$_isFollowingOrFulfilledOrRejected() {
+    return (this._bitField & 939524096) > 0;
+};
+
+Promise.prototype._isFollowing = function Promise$_isFollowing() {
+    return (this._bitField & 536870912) === 536870912;
+};
+
+Promise.prototype._setLength = function Promise$_setLength(len) {
+    this._bitField = (this._bitField & -1048576) |
+        (len & 1048575);
+};
+
+Promise.prototype._setFulfilled = function Promise$_setFulfilled() {
+    this._bitField = this._bitField | 268435456;
+};
+
+Promise.prototype._setRejected = function Promise$_setRejected() {
+    this._bitField = this._bitField | 134217728;
+};
+
+Promise.prototype._setFollowing = function Promise$_setFollowing() {
+    this._bitField = this._bitField | 536870912;
+};
+
+Promise.prototype._setIsFinal = function Promise$_setIsFinal() {
+    this._bitField = this._bitField | 33554432;
+};
+
+Promise.prototype._isFinal = function Promise$_isFinal() {
+    return (this._bitField & 33554432) > 0;
+};
+
+Promise.prototype._cancellable = function Promise$_cancellable() {
+    return (this._bitField & 67108864) > 0;
+};
+
+Promise.prototype._setCancellable = function Promise$_setCancellable() {
+    this._bitField = this._bitField | 67108864;
+};
+
+Promise.prototype._unsetCancellable = function Promise$_unsetCancellable() {
+    this._bitField = this._bitField & (~67108864);
+};
+
+Promise.prototype._setRejectionIsUnhandled =
+function Promise$_setRejectionIsUnhandled() {
+    this._bitField = this._bitField | 2097152;
+};
+
+Promise.prototype._unsetRejectionIsUnhandled =
+function Promise$_unsetRejectionIsUnhandled() {
+    this._bitField = this._bitField & (~2097152);
+};
+
+Promise.prototype._isRejectionUnhandled =
+function Promise$_isRejectionUnhandled() {
+    return (this._bitField & 2097152) > 0;
+};
+
+Promise.prototype._setCarriedStackTrace =
+function Promise$_setCarriedStackTrace(capturedTrace) {
+    this._bitField = this._bitField | 1048576;
+    this._fulfillmentHandler0 = capturedTrace;
+};
+
+Promise.prototype._unsetCarriedStackTrace =
+function Promise$_unsetCarriedStackTrace() {
+    this._bitField = this._bitField & (~1048576);
+    this._fulfillmentHandler0 = void 0;
+};
+
+Promise.prototype._isCarryingStackTrace =
+function Promise$_isCarryingStackTrace() {
+    return (this._bitField & 1048576) > 0;
+};
+
+Promise.prototype._getCarriedStackTrace =
+function Promise$_getCarriedStackTrace() {
+    return this._isCarryingStackTrace()
+        ? this._fulfillmentHandler0
+        : void 0;
+};
+
+Promise.prototype._receiverAt = function Promise$_receiverAt(index) {
+    var ret;
+    if (index === 0) {
+        ret = this._receiver0;
+    }
+    else {
+        ret = this[index + 4 - 5];
+    }
+    if (this._isBound() && ret === void 0) {
+        return this._boundTo;
+    }
+    return ret;
+};
+
+Promise.prototype._promiseAt = function Promise$_promiseAt(index) {
+    if (index === 0) return this._promise0;
+    return this[index + 3 - 5];
+};
+
+Promise.prototype._fulfillmentHandlerAt =
+function Promise$_fulfillmentHandlerAt(index) {
+    if (index === 0) return this._fulfillmentHandler0;
+    return this[index + 0 - 5];
+};
+
+Promise.prototype._rejectionHandlerAt =
+function Promise$_rejectionHandlerAt(index) {
+    if (index === 0) return this._rejectionHandler0;
+    return this[index + 1 - 5];
+};
+
+Promise.prototype._unsetAt = function Promise$_unsetAt(index) {
+     if (index === 0) {
+        this._rejectionHandler0 =
+        this._progressHandler0 =
+        this._promise0 =
+        this._receiver0 = void 0;
+        if (!this._isCarryingStackTrace()) {
+            this._fulfillmentHandler0 = void 0;
+        }
+    }
+    else {
+        this[index - 5 + 0] =
+        this[index - 5 + 1] =
+        this[index - 5 + 2] =
+        this[index - 5 + 3] =
+        this[index - 5 + 4] = void 0;
+    }
+};
+
+Promise.prototype._resolveFromResolver =
+function Promise$_resolveFromResolver(resolver) {
+    var promise = this;
+    var localDebugging = debugging;
+    if (localDebugging) {
+        this._setTrace(this._resolveFromResolver, void 0);
+        this._pushContext();
+    }
+    function Promise$_resolver(val) {
+        if (promise._tryFollow(val)) {
+            return;
+        }
+        promise._fulfill(val);
+    }
+    function Promise$_rejecter(val) {
+        var trace = canAttach(val) ? val : new Error(val + "");
+        promise._attachExtraTrace(trace);
+        markAsOriginatingFromRejection(val);
+        promise._reject(val, trace === val ? void 0 : trace);
+    }
+    var r = tryCatch2(resolver, void 0, Promise$_resolver, Promise$_rejecter);
+    if (localDebugging) this._popContext();
+
+    if (r !== void 0 && r === errorObj) {
+        var e = r.e;
+        var trace = canAttach(e) ? e : new Error(e + "");
+        promise._reject(e, trace);
+    }
+};
+
+Promise.prototype._addCallbacks = function Promise$_addCallbacks(
+    fulfill,
+    reject,
+    progress,
+    promise,
+    receiver
+) {
+    var index = this._length();
+
+    if (index >= 1048575 - 5) {
+        index = 0;
+        this._setLength(0);
+    }
+
+    if (index === 0) {
+        this._promise0 = promise;
+        if (receiver !== void 0) this._receiver0 = receiver;
+        if (typeof fulfill === "function" && !this._isCarryingStackTrace())
+            this._fulfillmentHandler0 = fulfill;
+        if (typeof reject === "function") this._rejectionHandler0 = reject;
+        if (typeof progress === "function") this._progressHandler0 = progress;
+    }
+    else {
+        var i = index - 5;
+        this[i + 3] = promise;
+        this[i + 4] = receiver;
+        this[i + 0] = typeof fulfill === "function"
+                                            ? fulfill : void 0;
+        this[i + 1] = typeof reject === "function"
+                                            ? reject : void 0;
+        this[i + 2] = typeof progress === "function"
+                                            ? progress : void 0;
+    }
+    this._setLength(index + 5);
+    return index;
+};
+
+
+
+Promise.prototype._setBoundTo = function Promise$_setBoundTo(obj) {
+    if (obj !== void 0) {
+        this._bitField = this._bitField | 8388608;
+        this._boundTo = obj;
+    }
+    else {
+        this._bitField = this._bitField & (~8388608);
+    }
+};
+
+Promise.prototype._isBound = function Promise$_isBound() {
+    return (this._bitField & 8388608) === 8388608;
+};
+
+Promise.prototype._spreadSlowCase =
+function Promise$_spreadSlowCase(targetFn, promise, values, boundTo) {
+    var promiseForAll =
+            Promise$_CreatePromiseArray
+                (values, PromiseArray, this._spreadSlowCase, boundTo)
+            .promise()
+            ._then(function() {
+                return targetFn.apply(boundTo, arguments);
+            }, void 0, void 0, APPLY, void 0, this._spreadSlowCase);
+
+    promise._follow(promiseForAll);
+};
+
+Promise.prototype._callSpread =
+function Promise$_callSpread(handler, promise, value, localDebugging) {
+    var boundTo = this._isBound() ? this._boundTo : void 0;
+    if (isArray(value)) {
+        var caller = this._settlePromiseFromHandler;
+        for (var i = 0, len = value.length; i < len; ++i) {
+            if (isPromise(Promise._cast(value[i], caller, void 0))) {
+                this._spreadSlowCase(handler, promise, value, boundTo);
+                return;
+            }
+        }
+    }
+    if (localDebugging) promise._pushContext();
+    return tryCatchApply(handler, value, boundTo);
+};
+
+Promise.prototype._callHandler =
+function Promise$_callHandler(
+    handler, receiver, promise, value, localDebugging) {
+    var x;
+    if (receiver === APPLY && !this.isRejected()) {
+        x = this._callSpread(handler, promise, value, localDebugging);
+    }
+    else {
+        if (localDebugging) promise._pushContext();
+        x = tryCatch1(handler, receiver, value);
+    }
+    if (localDebugging) promise._popContext();
+    return x;
+};
+
+Promise.prototype._settlePromiseFromHandler =
+function Promise$_settlePromiseFromHandler(
+    handler, receiver, value, promise
+) {
+    if (!isPromise(promise)) {
+        handler.call(receiver, value, promise);
+        return;
+    }
+
+    var localDebugging = debugging;
+    var x = this._callHandler(handler, receiver,
+                                promise, value, localDebugging);
+
+    if (promise._isFollowing()) return;
+
+    if (x === errorObj || x === promise || x === NEXT_FILTER) {
+        var err = x === promise
+                    ? makeSelfResolutionError()
+                    : x.e;
+        var trace = canAttach(err) ? err : new Error(err + "");
+        if (x !== NEXT_FILTER) promise._attachExtraTrace(trace);
+        promise._rejectUnchecked(err, trace);
+    }
+    else {
+        var castValue = Promise._cast(x,
+                    localDebugging ? this._settlePromiseFromHandler : void 0,
+                    promise);
+
+        if (isPromise(castValue)) {
+            if (castValue.isRejected() &&
+                !castValue._isCarryingStackTrace() &&
+                !canAttach(castValue._settledValue)) {
+                var trace = new Error(castValue._settledValue + "");
+                promise._attachExtraTrace(trace);
+                castValue._setCarriedStackTrace(trace);
+            }
+            promise._follow(castValue);
+            if (castValue._cancellable()) {
+                promise._cancellationParent = castValue;
+                promise._setCancellable();
+            }
+        }
+        else {
+            promise._fulfillUnchecked(x);
+        }
+    }
+};
+
+Promise.prototype._follow =
+function Promise$_follow(promise) {
+    this._setFollowing();
+
+    if (promise.isPending()) {
+        if (promise._cancellable() ) {
+            this._cancellationParent = promise;
+            this._setCancellable();
+        }
+        promise._proxyPromise(this);
+    }
+    else if (promise.isFulfilled()) {
+        this._fulfillUnchecked(promise._settledValue);
+    }
+    else {
+        this._rejectUnchecked(promise._settledValue,
+            promise._getCarriedStackTrace());
+    }
+
+    if (promise._isRejectionUnhandled()) promise._unsetRejectionIsUnhandled();
+
+    if (debugging &&
+        promise._traceParent == null) {
+        promise._traceParent = this;
+    }
+};
+
+Promise.prototype._tryFollow =
+function Promise$_tryFollow(value) {
+    if (this._isFollowingOrFulfilledOrRejected() ||
+        value === this) {
+        return false;
+    }
+    var maybePromise = Promise._cast(value, this._tryFollow, void 0);
+    if (!isPromise(maybePromise)) {
+        return false;
+    }
+    this._follow(maybePromise);
+    return true;
+};
+
+Promise.prototype._resetTrace = function Promise$_resetTrace(caller) {
+    if (debugging) {
+        var context = this._peekContext();
+        var isTopLevel = context === void 0;
+        this._trace = new CapturedTrace(
+            typeof caller === "function"
+            ? caller
+            : this._resetTrace,
+            isTopLevel
+       );
+    }
+};
+
+Promise.prototype._setTrace = function Promise$_setTrace(caller, parent) {
+    if (debugging) {
+        var context = this._peekContext();
+        this._traceParent = context;
+        var isTopLevel = context === void 0;
+        if (parent !== void 0 &&
+            parent._traceParent === context) {
+            this._trace = parent._trace;
+        }
+        else {
+            this._trace = new CapturedTrace(
+                typeof caller === "function"
+                ? caller
+                : this._setTrace,
+                isTopLevel
+           );
+        }
+    }
+    return this;
+};
+
+Promise.prototype._attachExtraTrace =
+function Promise$_attachExtraTrace(error) {
+    if (debugging) {
+        var promise = this;
+        var stack = error.stack;
+        stack = typeof stack === "string"
+            ? stack.split("\n") : [];
+        var headerLineCount = 1;
+
+        while(promise != null &&
+            promise._trace != null) {
+            stack = CapturedTrace.combine(
+                stack,
+                promise._trace.stack.split("\n")
+           );
+            promise = promise._traceParent;
+        }
+
+        var max = Error.stackTraceLimit + headerLineCount;
+        var len = stack.length;
+        if (len  > max) {
+            stack.length = max;
+        }
+        if (stack.length <= headerLineCount) {
+            error.stack = "(No stack trace)";
+        }
+        else {
+            error.stack = stack.join("\n");
+        }
+    }
+};
+
+Promise.prototype._cleanValues = function Promise$_cleanValues() {
+    if (this._cancellable()) {
+        this._cancellationParent = void 0;
+    }
+};
+
+Promise.prototype._fulfill = function Promise$_fulfill(value) {
+    if (this._isFollowingOrFulfilledOrRejected()) return;
+    this._fulfillUnchecked(value);
+};
+
+Promise.prototype._reject =
+function Promise$_reject(reason, carriedStackTrace) {
+    if (this._isFollowingOrFulfilledOrRejected()) return;
+    this._rejectUnchecked(reason, carriedStackTrace);
+};
+
+Promise.prototype._settlePromiseAt = function Promise$_settlePromiseAt(index) {
+    var handler = this.isFulfilled()
+        ? this._fulfillmentHandlerAt(index)
+        : this._rejectionHandlerAt(index);
+
+    var value = this._settledValue;
+    var receiver = this._receiverAt(index);
+    var promise = this._promiseAt(index);
+
+    if (typeof handler === "function") {
+        this._settlePromiseFromHandler(handler, receiver, value, promise);
+    }
+    else {
+        var done = false;
+        var isFulfilled = this.isFulfilled();
+        if (receiver !== void 0) {
+            if (receiver instanceof Promise &&
+                receiver._isProxied()) {
+                receiver._unsetProxied();
+
+                if (isFulfilled) receiver._fulfillUnchecked(value);
+                else receiver._rejectUnchecked(value,
+                    this._getCarriedStackTrace());
+                done = true;
+            }
+            else if (isPromiseArrayProxy(receiver, promise)) {
+
+                if (isFulfilled) receiver._promiseFulfilled(value, promise);
+                else receiver._promiseRejected(value, promise);
+
+                done = true;
+            }
+        }
+
+        if (!done) {
+
+            if (isFulfilled) promise._fulfill(value);
+            else promise._reject(value, this._getCarriedStackTrace());
+
+        }
+    }
+
+    if (index >= 256) {
+        this._queueGC();
+    }
+};
+
+Promise.prototype._isProxied = function Promise$_isProxied() {
+    return (this._bitField & 4194304) === 4194304;
+};
+
+Promise.prototype._setProxied = function Promise$_setProxied() {
+    this._bitField = this._bitField | 4194304;
+};
+
+Promise.prototype._unsetProxied = function Promise$_unsetProxied() {
+    this._bitField = this._bitField & (~4194304);
+};
+
+Promise.prototype._isGcQueued = function Promise$_isGcQueued() {
+    return (this._bitField & -1073741824) === -1073741824;
+};
+
+Promise.prototype._setGcQueued = function Promise$_setGcQueued() {
+    this._bitField = this._bitField | -1073741824;
+};
+
+Promise.prototype._unsetGcQueued = function Promise$_unsetGcQueued() {
+    this._bitField = this._bitField & (~-1073741824);
+};
+
+Promise.prototype._queueGC = function Promise$_queueGC() {
+    if (this._isGcQueued()) return;
+    this._setGcQueued();
+    async.invokeLater(this._gc, this, void 0);
+};
+
+Promise.prototype._gc = function Promise$gc() {
+    var len = this._length();
+    this._unsetAt(0);
+    for (var i = 0; i < len; i++) {
+        delete this[i];
+    }
+    this._setLength(0);
+    this._unsetGcQueued();
+};
+
+Promise.prototype._queueSettleAt = function Promise$_queueSettleAt(index) {
+    if (this._isRejectionUnhandled()) this._unsetRejectionIsUnhandled();
+    async.invoke(this._settlePromiseAt, this, index);
+};
+
+Promise.prototype._fulfillUnchecked =
+function Promise$_fulfillUnchecked(value) {
+    if (!this.isPending()) return;
+    if (value === this) {
+        var err = makeSelfResolutionError();
+        this._attachExtraTrace(err);
+        return this._rejectUnchecked(err, void 0);
+    }
+    this._cleanValues();
+    this._setFulfilled();
+    this._settledValue = value;
+    var len = this._length();
+
+    if (len > 0) {
+        async.invoke(this._fulfillPromises, this, len);
+    }
+};
+
+Promise.prototype._rejectUncheckedCheckError =
+function Promise$_rejectUncheckedCheckError(reason) {
+    var trace = canAttach(reason) ? reason : new Error(reason + "");
+    this._rejectUnchecked(reason, trace === reason ? void 0 : trace);
+};
+
+Promise.prototype._rejectUnchecked =
+function Promise$_rejectUnchecked(reason, trace) {
+    if (!this.isPending()) return;
+    if (reason === this) {
+        var err = makeSelfResolutionError();
+        this._attachExtraTrace(err);
+        return this._rejectUnchecked(err);
+    }
+    this._cleanValues();
+    this._setRejected();
+    this._settledValue = reason;
+
+    if (this._isFinal()) {
+        async.invokeLater(thrower, void 0, trace === void 0 ? reason : trace);
+        return;
+    }
+    var len = this._length();
+
+    if (trace !== void 0) this._setCarriedStackTrace(trace);
+
+    if (len > 0) {
+        async.invoke(this._rejectPromises, this, null);
+    }
+    else {
+        this._ensurePossibleRejectionHandled();
+    }
+};
+
+Promise.prototype._rejectPromises = function Promise$_rejectPromises() {
+    var len = this._length();
+    for (var i = 0; i < len; i+= 5) {
+        this._settlePromiseAt(i);
+    }
+    this._unsetCarriedStackTrace();
+};
+
+Promise.prototype._fulfillPromises = function Promise$_fulfillPromises(len) {
+    len = this._length();
+    for (var i = 0; i < len; i+= 5) {
+        this._settlePromiseAt(i);
+    }
+};
+
+Promise.prototype._ensurePossibleRejectionHandled =
+function Promise$_ensurePossibleRejectionHandled() {
+    this._setRejectionIsUnhandled();
+    if (CapturedTrace.possiblyUnhandledRejection !== void 0) {
+        async.invokeLater(this._notifyUnhandledRejection, this, void 0);
+    }
+};
+
+Promise.prototype._notifyUnhandledRejection =
+function Promise$_notifyUnhandledRejection() {
+    if (this._isRejectionUnhandled()) {
+        var reason = this._settledValue;
+        var trace = this._getCarriedStackTrace();
+
+        this._unsetRejectionIsUnhandled();
+
+        if (trace !== void 0) {
+            this._unsetCarriedStackTrace();
+            reason = trace;
+        }
+        if (typeof CapturedTrace.possiblyUnhandledRejection === "function") {
+            CapturedTrace.possiblyUnhandledRejection(reason, this);
+        }
+    }
+};
+
+var contextStack = [];
+Promise.prototype._peekContext = function Promise$_peekContext() {
+    var lastIndex = contextStack.length - 1;
+    if (lastIndex >= 0) {
+        return contextStack[lastIndex];
+    }
+    return void 0;
+
+};
+
+Promise.prototype._pushContext = function Promise$_pushContext() {
+    if (!debugging) return;
+    contextStack.push(this);
+};
+
+Promise.prototype._popContext = function Promise$_popContext() {
+    if (!debugging) return;
+    contextStack.pop();
+};
+
+function Promise$_CreatePromiseArray(
+    promises, PromiseArrayConstructor, caller, boundTo) {
+
+    var list = null;
+    if (isArray(promises)) {
+        list = promises;
+    }
+    else {
+        list = Promise._cast(promises, caller, void 0);
+        if (list !== promises) {
+            list._setBoundTo(boundTo);
+        }
+        else if (!isPromise(list)) {
+            list = null;
+        }
+    }
+    if (list !== null) {
+        return new PromiseArrayConstructor(
+            list,
+            typeof caller === "function"
+                ? caller
+                : Promise$_CreatePromiseArray,
+            boundTo
+       );
+    }
+    return {
+        promise: function() {return apiRejection("expecting an array, a promise or a thenable");}
+    };
+}
+
+var old = global.Promise;
+
+Promise.noConflict = function() {
+    if (global.Promise === Promise) {
+        global.Promise = old;
+    }
+    return Promise;
+};
+
+if (!CapturedTrace.isSupported()) {
+    Promise.longStackTraces = function(){};
+    debugging = false;
+}
+
+Promise._makeSelfResolutionError = makeSelfResolutionError;
+require("./finally.js")(Promise, NEXT_FILTER);
+require("./direct_resolve.js")(Promise);
+require("./thenables.js")(Promise, INTERNAL);
+Promise.RangeError = RangeError;
+Promise.CancellationError = CancellationError;
+Promise.TimeoutError = TimeoutError;
+Promise.TypeError = TypeError;
+Promise.RejectionError = RejectionError;
+
+util.toFastProperties(Promise);
+util.toFastProperties(Promise.prototype);
+require('./timers.js')(Promise,INTERNAL);
+require('./synchronous_inspection.js')(Promise);
+require('./any.js')(Promise,Promise$_CreatePromiseArray,PromiseArray);
+require('./race.js')(Promise,INTERNAL);
+require('./call_get.js')(Promise);
+require('./filter.js')(Promise,Promise$_CreatePromiseArray,PromiseArray,apiRejection);
+require('./generators.js')(Promise,apiRejection,INTERNAL);
+require('./map.js')(Promise,Promise$_CreatePromiseArray,PromiseArray,apiRejection);
+require('./nodeify.js')(Promise);
+require('./promisify.js')(Promise,INTERNAL);
+require('./props.js')(Promise,PromiseArray);
+require('./reduce.js')(Promise,Promise$_CreatePromiseArray,PromiseArray,apiRejection,INTERNAL);
+require('./settle.js')(Promise,Promise$_CreatePromiseArray,PromiseArray);
+require('./some.js')(Promise,Promise$_CreatePromiseArray,PromiseArray,apiRejection);
+require('./progress.js')(Promise,isPromiseArrayProxy);
+require('./cancel.js')(Promise,INTERNAL);
+
+Promise.prototype = Promise.prototype;
+return Promise;
+
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/promise_array.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/promise_array.js
new file mode 100644
index 0000000..377f0ec
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/promise_array.js
@@ -0,0 +1,233 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, INTERNAL) {
+var canAttach = require("./errors.js").canAttach;
+var util = require("./util.js");
+var async = require("./async.js");
+var hasOwn = {}.hasOwnProperty;
+var isArray = util.isArray;
+
+function toResolutionValue(val) {
+    switch(val) {
+    case -1: return void 0;
+    case -2: return [];
+    case -3: return {};
+    }
+}
+
+function PromiseArray(values, caller, boundTo) {
+    var promise = this._promise = new Promise(INTERNAL);
+    var parent = void 0;
+    if (Promise.is(values)) {
+        parent = values;
+        if (values._cancellable()) {
+            promise._setCancellable();
+            promise._cancellationParent = values;
+        }
+        if (values._isBound()) {
+            promise._setBoundTo(boundTo);
+        }
+    }
+    promise._setTrace(caller, parent);
+    this._values = values;
+    this._length = 0;
+    this._totalResolved = 0;
+    this._init(void 0, -2);
+}
+PromiseArray.PropertiesPromiseArray = function() {};
+
+PromiseArray.prototype.length = function PromiseArray$length() {
+    return this._length;
+};
+
+PromiseArray.prototype.promise = function PromiseArray$promise() {
+    return this._promise;
+};
+
+PromiseArray.prototype._init =
+function PromiseArray$_init(_, resolveValueIfEmpty) {
+    var values = this._values;
+    if (Promise.is(values)) {
+        if (values.isFulfilled()) {
+            values = values._settledValue;
+            if (!isArray(values)) {
+                var err = new Promise.TypeError("expecting an array, a promise or a thenable");
+                this.__hardReject__(err);
+                return;
+            }
+            this._values = values;
+        }
+        else if (values.isPending()) {
+            values._then(
+                this._init,
+                this._reject,
+                void 0,
+                this,
+                resolveValueIfEmpty,
+                this.constructor
+           );
+            return;
+        }
+        else {
+            this._reject(values._settledValue);
+            return;
+        }
+    }
+
+    if (values.length === 0) {
+        this._resolve(toResolutionValue(resolveValueIfEmpty));
+        return;
+    }
+    var len = values.length;
+    var newLen = len;
+    var newValues;
+    if (this instanceof PromiseArray.PropertiesPromiseArray) {
+        newValues = this._values;
+    }
+    else {
+        newValues = new Array(len);
+    }
+    var isDirectScanNeeded = false;
+    for (var i = 0; i < len; ++i) {
+        var promise = values[i];
+        if (promise === void 0 && !hasOwn.call(values, i)) {
+            newLen--;
+            continue;
+        }
+        var maybePromise = Promise._cast(promise, void 0, void 0);
+        if (maybePromise instanceof Promise) {
+            if (maybePromise.isPending()) {
+                maybePromise._proxyPromiseArray(this, i);
+            }
+            else {
+                maybePromise._unsetRejectionIsUnhandled();
+                isDirectScanNeeded = true;
+            }
+        }
+        else {
+            isDirectScanNeeded = true;
+        }
+        newValues[i] = maybePromise;
+    }
+    if (newLen === 0) {
+        if (resolveValueIfEmpty === -2) {
+            this._resolve(newValues);
+        }
+        else {
+            this._resolve(toResolutionValue(resolveValueIfEmpty));
+        }
+        return;
+    }
+    this._values = newValues;
+    this._length = newLen;
+    if (isDirectScanNeeded) {
+        var scanMethod = newLen === len
+            ? this._scanDirectValues
+            : this._scanDirectValuesHoled;
+        async.invoke(scanMethod, this, len);
+    }
+};
+
+PromiseArray.prototype._settlePromiseAt =
+function PromiseArray$_settlePromiseAt(index) {
+    var value = this._values[index];
+    if (!Promise.is(value)) {
+        this._promiseFulfilled(value, index);
+    }
+    else if (value.isFulfilled()) {
+        this._promiseFulfilled(value._settledValue, index);
+    }
+    else if (value.isRejected()) {
+        this._promiseRejected(value._settledValue, index);
+    }
+};
+
+PromiseArray.prototype._scanDirectValuesHoled =
+function PromiseArray$_scanDirectValuesHoled(len) {
+    for (var i = 0; i < len; ++i) {
+        if (this._isResolved()) {
+            break;
+        }
+        if (hasOwn.call(this._values, i)) {
+            this._settlePromiseAt(i);
+        }
+    }
+};
+
+PromiseArray.prototype._scanDirectValues =
+function PromiseArray$_scanDirectValues(len) {
+    for (var i = 0; i < len; ++i) {
+        if (this._isResolved()) {
+            break;
+        }
+        this._settlePromiseAt(i);
+    }
+};
+
+PromiseArray.prototype._isResolved = function PromiseArray$_isResolved() {
+    return this._values === null;
+};
+
+PromiseArray.prototype._resolve = function PromiseArray$_resolve(value) {
+    this._values = null;
+    this._promise._fulfill(value);
+};
+
+PromiseArray.prototype.__hardReject__ =
+PromiseArray.prototype._reject = function PromiseArray$_reject(reason) {
+    this._values = null;
+    var trace = canAttach(reason) ? reason : new Error(reason + "");
+    this._promise._attachExtraTrace(trace);
+    this._promise._reject(reason, trace);
+};
+
+PromiseArray.prototype._promiseProgressed =
+function PromiseArray$_promiseProgressed(progressValue, index) {
+    if (this._isResolved()) return;
+    this._promise._progress({
+        index: index,
+        value: progressValue
+    });
+};
+
+
+PromiseArray.prototype._promiseFulfilled =
+function PromiseArray$_promiseFulfilled(value, index) {
+    if (this._isResolved()) return;
+    this._values[index] = value;
+    var totalResolved = ++this._totalResolved;
+    if (totalResolved >= this._length) {
+        this._resolve(this._values);
+    }
+};
+
+PromiseArray.prototype._promiseRejected =
+function PromiseArray$_promiseRejected(reason, index) {
+    if (this._isResolved()) return;
+    this._totalResolved++;
+    this._reject(reason);
+};
+
+return PromiseArray;
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/promise_inspection.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/promise_inspection.js
new file mode 100644
index 0000000..0aa233b
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/promise_inspection.js
@@ -0,0 +1,66 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var TypeError = require("./errors.js").TypeError;
+
+function PromiseInspection(promise) {
+    if (promise !== void 0) {
+        this._bitField = promise._bitField;
+        this._settledValue = promise.isResolved()
+            ? promise._settledValue
+            : void 0;
+    }
+    else {
+        this._bitField = 0;
+        this._settledValue = void 0;
+    }
+}
+PromiseInspection.prototype.isFulfilled =
+function PromiseInspection$isFulfilled() {
+    return (this._bitField & 268435456) > 0;
+};
+
+PromiseInspection.prototype.isRejected =
+function PromiseInspection$isRejected() {
+    return (this._bitField & 134217728) > 0;
+};
+
+PromiseInspection.prototype.isPending = function PromiseInspection$isPending() {
+    return (this._bitField & 402653184) === 0;
+};
+
+PromiseInspection.prototype.value = function PromiseInspection$value() {
+    if (!this.isFulfilled()) {
+        throw new TypeError("cannot get fulfillment value of a non-fulfilled promise");
+    }
+    return this._settledValue;
+};
+
+PromiseInspection.prototype.error = function PromiseInspection$error() {
+    if (!this.isRejected()) {
+        throw new TypeError("cannot get rejection reason of a non-rejected promise");
+    }
+    return this._settledValue;
+};
+
+module.exports = PromiseInspection;
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/promise_resolver.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/promise_resolver.js
new file mode 100644
index 0000000..53ead00
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/promise_resolver.js
@@ -0,0 +1,152 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var util = require("./util.js");
+var maybeWrapAsError = util.maybeWrapAsError;
+var errors = require("./errors.js");
+var TimeoutError = errors.TimeoutError;
+var RejectionError = errors.RejectionError;
+var async = require("./async.js");
+var haveGetters = util.haveGetters;
+var es5 = require("./es5.js");
+
+function isUntypedError(obj) {
+    return obj instanceof Error &&
+        es5.getPrototypeOf(obj) === Error.prototype;
+}
+
+function wrapAsRejectionError(obj) {
+    var ret;
+    if (isUntypedError(obj)) {
+        ret = new RejectionError(obj);
+    }
+    else {
+        ret = obj;
+    }
+    errors.markAsOriginatingFromRejection(ret);
+    return ret;
+}
+
+function nodebackForPromise(promise) {
+    function PromiseResolver$_callback(err, value) {
+        if (promise === null) return;
+
+        if (err) {
+            var wrapped = wrapAsRejectionError(maybeWrapAsError(err));
+            promise._attachExtraTrace(wrapped);
+            promise._reject(wrapped);
+        }
+        else {
+            if (arguments.length > 2) {
+                var $_len = arguments.length;var args = new Array($_len - 1); for(var $_i = 1; $_i < $_len; ++$_i) {args[$_i - 1] = arguments[$_i];}
+                promise._fulfill(args);
+            }
+            else {
+                promise._fulfill(value);
+            }
+        }
+
+        promise = null;
+    }
+    return PromiseResolver$_callback;
+}
+
+
+var PromiseResolver;
+if (!haveGetters) {
+    PromiseResolver = function PromiseResolver(promise) {
+        this.promise = promise;
+        this.asCallback = nodebackForPromise(promise);
+        this.callback = this.asCallback;
+    };
+}
+else {
+    PromiseResolver = function PromiseResolver(promise) {
+        this.promise = promise;
+    };
+}
+if (haveGetters) {
+    var prop = {
+        get: function() {
+            return nodebackForPromise(this.promise);
+        }
+    };
+    es5.defineProperty(PromiseResolver.prototype, "asCallback", prop);
+    es5.defineProperty(PromiseResolver.prototype, "callback", prop);
+}
+
+PromiseResolver._nodebackForPromise = nodebackForPromise;
+
+PromiseResolver.prototype.toString = function PromiseResolver$toString() {
+    return "[object PromiseResolver]";
+};
+
+PromiseResolver.prototype.resolve =
+PromiseResolver.prototype.fulfill = function PromiseResolver$resolve(value) {
+    var promise = this.promise;
+    if (promise._tryFollow(value)) {
+        return;
+    }
+    async.invoke(promise._fulfill, promise, value);
+};
+
+PromiseResolver.prototype.reject = function PromiseResolver$reject(reason) {
+    var promise = this.promise;
+    errors.markAsOriginatingFromRejection(reason);
+    var trace = errors.canAttach(reason) ? reason : new Error(reason + "");
+    promise._attachExtraTrace(trace);
+    async.invoke(promise._reject, promise, reason);
+    if (trace !== reason) {
+        async.invoke(this._setCarriedStackTrace, this, trace);
+    }
+};
+
+PromiseResolver.prototype.progress =
+function PromiseResolver$progress(value) {
+    async.invoke(this.promise._progress, this.promise, value);
+};
+
+PromiseResolver.prototype.cancel = function PromiseResolver$cancel() {
+    async.invoke(this.promise.cancel, this.promise, void 0);
+};
+
+PromiseResolver.prototype.timeout = function PromiseResolver$timeout() {
+    this.reject(new TimeoutError("timeout"));
+};
+
+PromiseResolver.prototype.isResolved = function PromiseResolver$isResolved() {
+    return this.promise.isResolved();
+};
+
+PromiseResolver.prototype.toJSON = function PromiseResolver$toJSON() {
+    return this.promise.toJSON();
+};
+
+PromiseResolver.prototype._setCarriedStackTrace =
+function PromiseResolver$_setCarriedStackTrace(trace) {
+    if (this.promise.isRejected()) {
+        this.promise._setCarriedStackTrace(trace);
+    }
+};
+
+module.exports = PromiseResolver;
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/promise_spawn.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/promise_spawn.js
new file mode 100644
index 0000000..2d6525f
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/promise_spawn.js
@@ -0,0 +1,131 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, INTERNAL) {
+var errors = require("./errors.js");
+var TypeError = errors.TypeError;
+var util = require("./util.js");
+var isArray = util.isArray;
+var errorObj = util.errorObj;
+var tryCatch1 = util.tryCatch1;
+var yieldHandlers = [];
+
+function promiseFromYieldHandler(value) {
+    var _yieldHandlers = yieldHandlers;
+    var _errorObj = errorObj;
+    var _Promise = Promise;
+    var len = _yieldHandlers.length;
+    for (var i = 0; i < len; ++i) {
+        var result = tryCatch1(_yieldHandlers[i], void 0, value);
+        if (result === _errorObj) {
+            return _Promise.reject(_errorObj.e);
+        }
+        var maybePromise = _Promise._cast(result,
+            promiseFromYieldHandler, void 0);
+        if (maybePromise instanceof _Promise) return maybePromise;
+    }
+    return null;
+}
+
+function PromiseSpawn(generatorFunction, receiver, caller) {
+    var promise = this._promise = new Promise(INTERNAL);
+    promise._setTrace(caller, void 0);
+    this._generatorFunction = generatorFunction;
+    this._receiver = receiver;
+    this._generator = void 0;
+}
+
+PromiseSpawn.prototype.promise = function PromiseSpawn$promise() {
+    return this._promise;
+};
+
+PromiseSpawn.prototype._run = function PromiseSpawn$_run() {
+    this._generator = this._generatorFunction.call(this._receiver);
+    this._receiver =
+        this._generatorFunction = void 0;
+    this._next(void 0);
+};
+
+PromiseSpawn.prototype._continue = function PromiseSpawn$_continue(result) {
+    if (result === errorObj) {
+        this._generator = void 0;
+        var trace = errors.canAttach(result.e)
+            ? result.e : new Error(result.e + "");
+        this._promise._attachExtraTrace(trace);
+        this._promise._reject(result.e, trace);
+        return;
+    }
+
+    var value = result.value;
+    if (result.done === true) {
+        this._generator = void 0;
+        if (!this._promise._tryFollow(value)) {
+            this._promise._fulfill(value);
+        }
+    }
+    else {
+        var maybePromise = Promise._cast(value, PromiseSpawn$_continue, void 0);
+        if (!(maybePromise instanceof Promise)) {
+            if (isArray(maybePromise)) {
+                maybePromise = Promise.all(maybePromise);
+            }
+            else {
+                maybePromise = promiseFromYieldHandler(maybePromise);
+            }
+            if (maybePromise === null) {
+                this._throw(new TypeError("A value was yielded that could not be treated as a promise"));
+                return;
+            }
+        }
+        maybePromise._then(
+            this._next,
+            this._throw,
+            void 0,
+            this,
+            null,
+            void 0
+       );
+    }
+};
+
+PromiseSpawn.prototype._throw = function PromiseSpawn$_throw(reason) {
+    if (errors.canAttach(reason))
+        this._promise._attachExtraTrace(reason);
+    this._continue(
+        tryCatch1(this._generator["throw"], this._generator, reason)
+   );
+};
+
+PromiseSpawn.prototype._next = function PromiseSpawn$_next(value) {
+    this._continue(
+        tryCatch1(this._generator.next, this._generator, value)
+   );
+};
+
+PromiseSpawn.addYieldHandler = function PromiseSpawn$AddYieldHandler(fn) {
+    if (typeof fn !== "function") throw new TypeError("fn must be a function");
+    yieldHandlers.push(fn);
+};
+
+return PromiseSpawn;
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/promisify.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/promisify.js
new file mode 100644
index 0000000..a550fd0
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/promisify.js
@@ -0,0 +1,278 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, INTERNAL) {
+var THIS = {};
+var util = require("./util.js");
+var es5 = require("./es5.js");
+var nodebackForPromise = require("./promise_resolver.js")
+    ._nodebackForPromise;
+var withAppended = util.withAppended;
+var maybeWrapAsError = util.maybeWrapAsError;
+var canEvaluate = util.canEvaluate;
+var notEnumerableProp = util.notEnumerableProp;
+var deprecated = util.deprecated;
+var roriginal = new RegExp("__beforePromisified__" + "$");
+var hasProp = {}.hasOwnProperty;
+function isPromisified(fn) {
+    return fn.__isPromisified__ === true;
+}
+var inheritedMethods = (function() {
+    if (es5.isES5) {
+        var create = Object.create;
+        var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
+        return function(cur) {
+            var original = cur;
+            var ret = [];
+            var visitedKeys = create(null);
+            while (cur !== null) {
+                var keys = es5.keys(cur);
+                for (var i = 0, len = keys.length; i < len; ++i) {
+                    var key = keys[i];
+                    if (visitedKeys[key] ||
+                        roriginal.test(key) ||
+                        hasProp.call(original, key + "__beforePromisified__")
+                   ) {
+                        continue;
+                    }
+                    visitedKeys[key] = true;
+                    var desc = getOwnPropertyDescriptor(cur, key);
+                    if (desc != null &&
+                        typeof desc.value === "function" &&
+                        !isPromisified(desc.value)) {
+                        ret.push(key, desc.value);
+                    }
+                }
+                cur = es5.getPrototypeOf(cur);
+            }
+            return ret;
+        };
+    }
+    else {
+        return function(obj) {
+            var ret = [];
+            /*jshint forin:false */
+            for (var key in obj) {
+                if (roriginal.test(key) ||
+                    hasProp.call(obj, key + "__beforePromisified__")) {
+                    continue;
+                }
+                var fn = obj[key];
+                if (typeof fn === "function" &&
+                    !isPromisified(fn)) {
+                    ret.push(key, fn);
+                }
+            }
+            return ret;
+        };
+    }
+})();
+
+function switchCaseArgumentOrder(likelyArgumentCount) {
+    var ret = [likelyArgumentCount];
+    var min = Math.max(0, likelyArgumentCount - 1 - 5);
+    for(var i = likelyArgumentCount - 1; i >= min; --i) {
+        if (i === likelyArgumentCount) continue;
+        ret.push(i);
+    }
+    for(var i = likelyArgumentCount + 1; i <= 5; ++i) {
+        ret.push(i);
+    }
+    return ret;
+}
+
+function parameterDeclaration(parameterCount) {
+    var ret = new Array(parameterCount);
+    for(var i = 0; i < ret.length; ++i) {
+        ret[i] = "_arg" + i;
+    }
+    return ret.join(", ");
+}
+
+function parameterCount(fn) {
+    if (typeof fn.length === "number") {
+        return Math.max(Math.min(fn.length, 1023 + 1), 0);
+    }
+    return 0;
+}
+
+function propertyAccess(id) {
+    var rident = /^[a-z$_][a-z$_0-9]*$/i;
+
+    if (rident.test(id)) {
+        return "." + id;
+    }
+    else return "['" + id.replace(/(['\\])/g, "\\$1") + "']";
+}
+
+function makeNodePromisifiedEval(callback, receiver, originalName, fn) {
+    var newParameterCount = Math.max(0, parameterCount(fn) - 1);
+    var argumentOrder = switchCaseArgumentOrder(newParameterCount);
+
+    var callbackName = (typeof originalName === "string" ?
+        originalName + "Async" :
+        "promisified");
+
+    function generateCallForArgumentCount(count) {
+        var args = new Array(count);
+        for (var i = 0, len = args.length; i < len; ++i) {
+            args[i] = "arguments[" + i + "]";
+        }
+        var comma = count > 0 ? "," : "";
+
+        if (typeof callback === "string" &&
+            receiver === THIS) {
+            return "this" + propertyAccess(callback) + "("+args.join(",") +
+                comma +" fn);"+
+                "break;";
+        }
+        return (receiver === void 0
+            ? "callback("+args.join(",")+ comma +" fn);"
+            : "callback.call("+(receiver === THIS
+                ? "this"
+                : "receiver")+", "+args.join(",") + comma + " fn);") +
+        "break;";
+    }
+
+    function generateArgumentSwitchCase() {
+        var ret = "";
+        for(var i = 0; i < argumentOrder.length; ++i) {
+            ret += "case " + argumentOrder[i] +":" +
+                generateCallForArgumentCount(argumentOrder[i]);
+        }
+        ret += "default: var args = new Array(len + 1);" +
+            "var i = 0;" +
+            "for (var i = 0; i < len; ++i) { " +
+            "   args[i] = arguments[i];" +
+            "}" +
+            "args[i] = fn;" +
+
+            (typeof callback === "string"
+            ? "this" + propertyAccess(callback) + ".apply("
+            : "callback.apply(") +
+
+            (receiver === THIS ? "this" : "receiver") +
+            ", args); break;";
+        return ret;
+    }
+
+    return new Function("Promise", "callback", "receiver",
+            "withAppended", "maybeWrapAsError", "nodebackForPromise",
+            "INTERNAL",
+        "var ret = function " + callbackName +
+        "(" + parameterDeclaration(newParameterCount) + ") {\"use strict\";" +
+        "var len = arguments.length;" +
+        "var promise = new Promise(INTERNAL);"+
+        "promise._setTrace(" + callbackName + ", void 0);" +
+        "var fn = nodebackForPromise(promise);"+
+        "try {" +
+        "switch(len) {" +
+        generateArgumentSwitchCase() +
+        "}" +
+        "}" +
+        "catch(e){ " +
+        "var wrapped = maybeWrapAsError(e);" +
+        "promise._attachExtraTrace(wrapped);" +
+        "promise._reject(wrapped);" +
+        "}" +
+        "return promise;" +
+        "" +
+        "}; ret.__isPromisified__ = true; return ret;"
+   )(Promise, callback, receiver, withAppended,
+        maybeWrapAsError, nodebackForPromise, INTERNAL);
+}
+
+function makeNodePromisifiedClosure(callback, receiver) {
+    function promisified() {
+        var _receiver = receiver;
+        if (receiver === THIS) _receiver = this;
+        if (typeof callback === "string") {
+            callback = _receiver[callback];
+        }
+        var promise = new Promise(INTERNAL);
+        promise._setTrace(promisified, void 0);
+        var fn = nodebackForPromise(promise);
+        try {
+            callback.apply(_receiver, withAppended(arguments, fn));
+        }
+        catch(e) {
+            var wrapped = maybeWrapAsError(e);
+            promise._attachExtraTrace(wrapped);
+            promise._reject(wrapped);
+        }
+        return promise;
+    }
+    promisified.__isPromisified__ = true;
+    return promisified;
+}
+
+var makeNodePromisified = canEvaluate
+    ? makeNodePromisifiedEval
+    : makeNodePromisifiedClosure;
+
+function _promisify(callback, receiver, isAll) {
+    if (isAll) {
+        var methods = inheritedMethods(callback);
+        for (var i = 0, len = methods.length; i < len; i+= 2) {
+            var key = methods[i];
+            var fn = methods[i+1];
+            var originalKey = key + "__beforePromisified__";
+            var promisifiedKey = key + "Async";
+            notEnumerableProp(callback, originalKey, fn);
+            callback[promisifiedKey] =
+                makeNodePromisified(originalKey, THIS,
+                    key, fn);
+        }
+        util.toFastProperties(callback);
+        return callback;
+    }
+    else {
+        return makeNodePromisified(callback, receiver, void 0, callback);
+    }
+}
+
+Promise.promisify = function Promise$Promisify(fn, receiver) {
+    if (typeof fn === "object" && fn !== null) {
+        deprecated("Promise.promisify for promisifying entire objects is deprecated. Use Promise.promisifyAll instead.");
+        return _promisify(fn, receiver, true);
+    }
+    if (typeof fn !== "function") {
+        throw new TypeError("fn must be a function");
+    }
+    if (isPromisified(fn)) {
+        return fn;
+    }
+    return _promisify(
+        fn,
+        arguments.length < 2 ? THIS : receiver,
+        false);
+};
+
+Promise.promisifyAll = function Promise$PromisifyAll(target) {
+    if (typeof target !== "function" && typeof target !== "object") {
+        throw new TypeError("the target of promisifyAll must be an object or a function");
+    }
+    return _promisify(target, void 0, true);
+};
+};
+
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/properties_promise_array.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/properties_promise_array.js
new file mode 100644
index 0000000..85f5990
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/properties_promise_array.js
@@ -0,0 +1,77 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, PromiseArray) {
+var util = require("./util.js");
+var inherits = util.inherits;
+var es5 = require("./es5.js");
+
+function PropertiesPromiseArray(obj, caller, boundTo) {
+    var keys = es5.keys(obj);
+    var values = new Array(keys.length);
+    for (var i = 0, len = values.length; i < len; ++i) {
+        values[i] = obj[keys[i]];
+    }
+    this.constructor$(values, caller, boundTo);
+    if (!this._isResolved()) {
+        for (var i = 0, len = keys.length; i < len; ++i) {
+            values.push(keys[i]);
+        }
+    }
+}
+inherits(PropertiesPromiseArray, PromiseArray);
+
+PropertiesPromiseArray.prototype._init =
+function PropertiesPromiseArray$_init() {
+    this._init$(void 0, -3) ;
+};
+
+PropertiesPromiseArray.prototype._promiseFulfilled =
+function PropertiesPromiseArray$_promiseFulfilled(value, index) {
+    if (this._isResolved()) return;
+    this._values[index] = value;
+    var totalResolved = ++this._totalResolved;
+    if (totalResolved >= this._length) {
+        var val = {};
+        var keyOffset = this.length();
+        for (var i = 0, len = this.length(); i < len; ++i) {
+            val[this._values[i + keyOffset]] = this._values[i];
+        }
+        this._resolve(val);
+    }
+};
+
+PropertiesPromiseArray.prototype._promiseProgressed =
+function PropertiesPromiseArray$_promiseProgressed(value, index) {
+    if (this._isResolved()) return;
+
+    this._promise._progress({
+        key: this._values[index + this.length()],
+        value: value
+    });
+};
+
+PromiseArray.PropertiesPromiseArray = PropertiesPromiseArray;
+
+return PropertiesPromiseArray;
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/props.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/props.js
new file mode 100644
index 0000000..3cdba43
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/props.js
@@ -0,0 +1,65 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, PromiseArray) {
+    var PropertiesPromiseArray = require("./properties_promise_array.js")(
+        Promise, PromiseArray);
+    var util = require("./util.js");
+    var apiRejection = require("./errors_api_rejection")(Promise);
+    var isObject = util.isObject;
+
+    function Promise$_Props(promises, useBound, caller) {
+        var ret;
+        var castValue = Promise._cast(promises, caller, void 0);
+
+        if (!isObject(castValue)) {
+            return apiRejection("cannot await properties of a non-object");
+        }
+        else if (Promise.is(castValue)) {
+            ret = castValue._then(Promise.props, void 0, void 0,
+                            void 0, void 0, caller);
+        }
+        else {
+            ret = new PropertiesPromiseArray(
+                castValue,
+                caller,
+                useBound === true && castValue._isBound()
+                            ? castValue._boundTo
+                            : void 0
+           ).promise();
+            useBound = false;
+        }
+        if (useBound === true && castValue._isBound()) {
+            ret._setBoundTo(castValue._boundTo);
+        }
+        return ret;
+    }
+
+    Promise.prototype.props = function Promise$props() {
+        return Promise$_Props(this, true, this.props);
+    };
+
+    Promise.props = function Promise$Props(promises) {
+        return Promise$_Props(promises, false, Promise.props);
+    };
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/queue.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/queue.js
new file mode 100644
index 0000000..bbd3f1b
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/queue.js
@@ -0,0 +1,135 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+function arrayCopy(src, srcIndex, dst, dstIndex, len) {
+    for (var j = 0; j < len; ++j) {
+        dst[j + dstIndex] = src[j + srcIndex];
+    }
+}
+
+function pow2AtLeast(n) {
+    n = n >>> 0;
+    n = n - 1;
+    n = n | (n >> 1);
+    n = n | (n >> 2);
+    n = n | (n >> 4);
+    n = n | (n >> 8);
+    n = n | (n >> 16);
+    return n + 1;
+}
+
+function getCapacity(capacity) {
+    if (typeof capacity !== "number") return 16;
+    return pow2AtLeast(
+        Math.min(
+            Math.max(16, capacity), 1073741824)
+   );
+}
+
+function Queue(capacity) {
+    this._capacity = getCapacity(capacity);
+    this._length = 0;
+    this._front = 0;
+    this._makeCapacity();
+}
+
+Queue.prototype._willBeOverCapacity =
+function Queue$_willBeOverCapacity(size) {
+    return this._capacity < size;
+};
+
+Queue.prototype._pushOne = function Queue$_pushOne(arg) {
+    var length = this.length();
+    this._checkCapacity(length + 1);
+    var i = (this._front + length) & (this._capacity - 1);
+    this[i] = arg;
+    this._length = length + 1;
+};
+
+Queue.prototype.push = function Queue$push(fn, receiver, arg) {
+    var length = this.length() + 3;
+    if (this._willBeOverCapacity(length)) {
+        this._pushOne(fn);
+        this._pushOne(receiver);
+        this._pushOne(arg);
+        return;
+    }
+    var j = this._front + length - 3;
+    this._checkCapacity(length);
+    var wrapMask = this._capacity - 1;
+    this[(j + 0) & wrapMask] = fn;
+    this[(j + 1) & wrapMask] = receiver;
+    this[(j + 2) & wrapMask] = arg;
+    this._length = length;
+};
+
+Queue.prototype.shift = function Queue$shift() {
+    var front = this._front,
+        ret = this[front];
+
+    this[front] = void 0;
+    this._front = (front + 1) & (this._capacity - 1);
+    this._length--;
+    return ret;
+};
+
+Queue.prototype.length = function Queue$length() {
+    return this._length;
+};
+
+Queue.prototype._makeCapacity = function Queue$_makeCapacity() {
+    var len = this._capacity;
+    for (var i = 0; i < len; ++i) {
+        this[i] = void 0;
+    }
+};
+
+Queue.prototype._checkCapacity = function Queue$_checkCapacity(size) {
+    if (this._capacity < size) {
+        this._resizeTo(this._capacity << 3);
+    }
+};
+
+Queue.prototype._resizeTo = function Queue$_resizeTo(capacity) {
+    var oldFront = this._front;
+    var oldCapacity = this._capacity;
+    var oldQueue = new Array(oldCapacity);
+    var length = this.length();
+
+    arrayCopy(this, 0, oldQueue, 0, oldCapacity);
+    this._capacity = capacity;
+    this._makeCapacity();
+    this._front = 0;
+    if (oldFront + length <= oldCapacity) {
+        arrayCopy(oldQueue, oldFront, this, 0, length);
+    }
+    else {        var lengthBeforeWrapping =
+            length - ((oldFront + length) & (oldCapacity - 1));
+
+        arrayCopy(oldQueue, oldFront, this, 0, lengthBeforeWrapping);
+        arrayCopy(oldQueue, 0, this, lengthBeforeWrapping,
+                    length - lengthBeforeWrapping);
+    }
+};
+
+module.exports = Queue;
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/race.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/race.js
new file mode 100644
index 0000000..82b8ce1
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/race.js
@@ -0,0 +1,85 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, INTERNAL) {
+    var apiRejection = require("./errors_api_rejection.js")(Promise);
+    var isArray = require("./util.js").isArray;
+
+    var raceLater = function Promise$_raceLater(promise) {
+        return promise.then(function Promise$_lateRacer(array) {
+            return Promise$_Race(array, Promise$_lateRacer, promise);
+        });
+    };
+
+    var hasOwn = {}.hasOwnProperty;
+    function Promise$_Race(promises, caller, parent) {
+        var maybePromise = Promise._cast(promises, caller, void 0);
+
+        if (Promise.is(maybePromise)) {
+            return raceLater(maybePromise);
+        }
+        else if (!isArray(promises)) {
+            return apiRejection("expecting an array, a promise or a thenable");
+        }
+
+        var ret = new Promise(INTERNAL);
+        ret._setTrace(caller, parent);
+        if (parent !== void 0) {
+            if (parent._isBound()) {
+                ret._setBoundTo(parent._boundTo);
+            }
+            if (parent._cancellable()) {
+                ret._setCancellable();
+                ret._cancellationParent = parent;
+            }
+        }
+        var fulfill = ret._fulfill;
+        var reject = ret._reject;
+        for (var i = 0, len = promises.length; i < len; ++i) {
+            var val = promises[i];
+
+            if (val === void 0 && !(hasOwn.call(promises, i))) {
+                continue;
+            }
+
+            Promise.cast(val)._then(
+                fulfill,
+                reject,
+                void 0,
+                ret,
+                null,
+                caller
+           );
+        }
+        return ret;
+    }
+
+    Promise.race = function Promise$Race(promises) {
+        return Promise$_Race(promises, Promise.race, void 0);
+    };
+
+    Promise.prototype.race = function Promise$race() {
+        return Promise$_Race(this, this.race, void 0);
+    };
+
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/reduce.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/reduce.js
new file mode 100644
index 0000000..e9ef95b
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/reduce.js
@@ -0,0 +1,173 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(
+    Promise, Promise$_CreatePromiseArray,
+    PromiseArray, apiRejection, INTERNAL) {
+
+    function Reduction(callback, index, accum, items, receiver) {
+        this.promise = new Promise(INTERNAL);
+        this.index = index;
+        this.length = items.length;
+        this.items = items;
+        this.callback = callback;
+        this.receiver = receiver;
+        this.accum = accum;
+    }
+
+    Reduction.prototype.reject = function Reduction$reject(e) {
+        this.promise._reject(e);
+    };
+
+    Reduction.prototype.fulfill = function Reduction$fulfill(value, index) {
+        this.accum = value;
+        this.index = index + 1;
+        this.iterate();
+    };
+
+    Reduction.prototype.iterate = function Reduction$iterate() {
+        var i = this.index;
+        var len = this.length;
+        var items = this.items;
+        var result = this.accum;
+        var receiver = this.receiver;
+        var callback = this.callback;
+        var iterate = this.iterate;
+
+        for(; i < len; ++i) {
+            result = Promise._cast(
+                callback.call(
+                    receiver,
+                    result,
+                    items[i],
+                    i,
+                    len
+                ),
+                iterate,
+                void 0
+            );
+
+            if (result instanceof Promise) {
+                result._then(
+                    this.fulfill, this.reject, void 0, this, i, iterate);
+                return;
+            }
+        }
+        this.promise._fulfill(result);
+    };
+
+    function Promise$_reducer(fulfilleds, initialValue) {
+        var fn = this;
+        var receiver = void 0;
+        if (typeof fn !== "function")  {
+            receiver = fn.receiver;
+            fn = fn.fn;
+        }
+        var len = fulfilleds.length;
+        var accum = void 0;
+        var startIndex = 0;
+
+        if (initialValue !== void 0) {
+            accum = initialValue;
+            startIndex = 0;
+        }
+        else {
+            startIndex = 1;
+            if (len > 0) accum = fulfilleds[0];
+        }
+        var i = startIndex;
+
+        if (i >= len) {
+            return accum;
+        }
+
+        var reduction = new Reduction(fn, i, accum, fulfilleds, receiver);
+        reduction.iterate();
+        return reduction.promise;
+    }
+
+    function Promise$_unpackReducer(fulfilleds) {
+        var fn = this.fn;
+        var initialValue = this.initialValue;
+        return Promise$_reducer.call(fn, fulfilleds, initialValue);
+    }
+
+    function Promise$_slowReduce(
+        promises, fn, initialValue, useBound, caller) {
+        return initialValue._then(function callee(initialValue) {
+            return Promise$_Reduce(
+                promises, fn, initialValue, useBound, callee);
+        }, void 0, void 0, void 0, void 0, caller);
+    }
+
+    function Promise$_Reduce(promises, fn, initialValue, useBound, caller) {
+        if (typeof fn !== "function") {
+            return apiRejection("fn must be a function");
+        }
+
+        if (useBound === true && promises._isBound()) {
+            fn = {
+                fn: fn,
+                receiver: promises._boundTo
+            };
+        }
+
+        if (initialValue !== void 0) {
+            if (Promise.is(initialValue)) {
+                if (initialValue.isFulfilled()) {
+                    initialValue = initialValue._settledValue;
+                }
+                else {
+                    return Promise$_slowReduce(promises,
+                        fn, initialValue, useBound, caller);
+                }
+            }
+
+            return Promise$_CreatePromiseArray(promises, PromiseArray, caller,
+                useBound === true && promises._isBound()
+                    ? promises._boundTo
+                    : void 0)
+                .promise()
+                ._then(Promise$_unpackReducer, void 0, void 0, {
+                    fn: fn,
+                    initialValue: initialValue
+                }, void 0, Promise.reduce);
+        }
+        return Promise$_CreatePromiseArray(promises, PromiseArray, caller,
+                useBound === true && promises._isBound()
+                    ? promises._boundTo
+                    : void 0).promise()
+            ._then(Promise$_reducer, void 0, void 0, fn, void 0, caller);
+    }
+
+
+    Promise.reduce = function Promise$Reduce(promises, fn, initialValue) {
+        return Promise$_Reduce(promises, fn,
+            initialValue, false, Promise.reduce);
+    };
+
+    Promise.prototype.reduce = function Promise$reduce(fn, initialValue) {
+        return Promise$_Reduce(this, fn, initialValue,
+                                true, this.reduce);
+    };
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/schedule.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/schedule.js
new file mode 100644
index 0000000..ae2271e
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/schedule.js
@@ -0,0 +1,121 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var global = require("./global.js");
+var schedule;
+if (typeof process !== "undefined" && process !== null &&
+    typeof process.cwd === "function" &&
+    typeof process.nextTick === "function" &&
+    typeof process.version === "string") {
+    schedule = function Promise$_Scheduler(fn) {
+        process.nextTick(fn);
+    };
+}
+else if ((typeof global.MutationObserver === "function" ||
+        typeof global.WebkitMutationObserver === "function" ||
+        typeof global.WebKitMutationObserver === "function") &&
+        typeof document !== "undefined" &&
+        typeof document.createElement === "function") {
+
+
+    schedule = (function(){
+        var MutationObserver = global.MutationObserver ||
+            global.WebkitMutationObserver ||
+            global.WebKitMutationObserver;
+        var div = document.createElement("div");
+        var queuedFn = void 0;
+        var observer = new MutationObserver(
+            function Promise$_Scheduler() {
+                var fn = queuedFn;
+                queuedFn = void 0;
+                fn();
+            }
+       );
+        observer.observe(div, {
+            attributes: true
+        });
+        return function Promise$_Scheduler(fn) {
+            queuedFn = fn;
+            div.setAttribute("class", "foo");
+        };
+
+    })();
+}
+else if (typeof global.postMessage === "function" &&
+    typeof global.importScripts !== "function" &&
+    typeof global.addEventListener === "function" &&
+    typeof global.removeEventListener === "function") {
+
+    var MESSAGE_KEY = "bluebird_message_key_" + Math.random();
+    schedule = (function(){
+        var queuedFn = void 0;
+
+        function Promise$_Scheduler(e) {
+            if (e.source === global &&
+                e.data === MESSAGE_KEY) {
+                var fn = queuedFn;
+                queuedFn = void 0;
+                fn();
+            }
+        }
+
+        global.addEventListener("message", Promise$_Scheduler, false);
+
+        return function Promise$_Scheduler(fn) {
+            queuedFn = fn;
+            global.postMessage(
+                MESSAGE_KEY, "*"
+           );
+        };
+
+    })();
+}
+else if (typeof global.MessageChannel === "function") {
+    schedule = (function(){
+        var queuedFn = void 0;
+
+        var channel = new global.MessageChannel();
+        channel.port1.onmessage = function Promise$_Scheduler() {
+                var fn = queuedFn;
+                queuedFn = void 0;
+                fn();
+        };
+
+        return function Promise$_Scheduler(fn) {
+            queuedFn = fn;
+            channel.port2.postMessage(null);
+        };
+    })();
+}
+else if (global.setTimeout) {
+    schedule = function Promise$_Scheduler(fn) {
+        setTimeout(fn, 4);
+    };
+}
+else {
+    schedule = function Promise$_Scheduler(fn) {
+        fn();
+    };
+}
+
+module.exports = schedule;
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/settle.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/settle.js
new file mode 100644
index 0000000..863882f
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/settle.js
@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports =
+    function(Promise, Promise$_CreatePromiseArray, PromiseArray) {
+
+    var SettledPromiseArray = require("./settled_promise_array.js")(
+        Promise, PromiseArray);
+
+    function Promise$_Settle(promises, useBound, caller) {
+        return Promise$_CreatePromiseArray(
+            promises,
+            SettledPromiseArray,
+            caller,
+            useBound === true && promises._isBound()
+                ? promises._boundTo
+                : void 0
+       ).promise();
+    }
+
+    Promise.settle = function Promise$Settle(promises) {
+        return Promise$_Settle(promises, false, Promise.settle);
+    };
+
+    Promise.prototype.settle = function Promise$settle() {
+        return Promise$_Settle(this, true, this.settle);
+    };
+
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/settled_promise_array.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/settled_promise_array.js
new file mode 100644
index 0000000..fd25d4d
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/settled_promise_array.js
@@ -0,0 +1,60 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, PromiseArray) {
+var PromiseInspection = require("./promise_inspection.js");
+var util = require("./util.js");
+var inherits = util.inherits;
+function SettledPromiseArray(values, caller, boundTo) {
+    this.constructor$(values, caller, boundTo);
+}
+inherits(SettledPromiseArray, PromiseArray);
+
+SettledPromiseArray.prototype._promiseResolved =
+function SettledPromiseArray$_promiseResolved(index, inspection) {
+    this._values[index] = inspection;
+    var totalResolved = ++this._totalResolved;
+    if (totalResolved >= this._length) {
+        this._resolve(this._values);
+    }
+};
+
+SettledPromiseArray.prototype._promiseFulfilled =
+function SettledPromiseArray$_promiseFulfilled(value, index) {
+    if (this._isResolved()) return;
+    var ret = new PromiseInspection();
+    ret._bitField = 268435456;
+    ret._settledValue = value;
+    this._promiseResolved(index, ret);
+};
+SettledPromiseArray.prototype._promiseRejected =
+function SettledPromiseArray$_promiseRejected(reason, index) {
+    if (this._isResolved()) return;
+    var ret = new PromiseInspection();
+    ret._bitField = 134217728;
+    ret._settledValue = reason;
+    this._promiseResolved(index, ret);
+};
+
+return SettledPromiseArray;
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/some.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/some.js
new file mode 100644
index 0000000..21c4ecd
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/some.js
@@ -0,0 +1,57 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports =
+function(Promise, Promise$_CreatePromiseArray, PromiseArray, apiRejection) {
+
+    var SomePromiseArray = require("./some_promise_array.js")(PromiseArray);
+    function Promise$_Some(promises, howMany, useBound, caller) {
+        if ((howMany | 0) !== howMany || howMany < 0) {
+            return apiRejection("expecting a positive integer");
+        }
+        var ret = Promise$_CreatePromiseArray(
+            promises,
+            SomePromiseArray,
+            caller,
+            useBound === true && promises._isBound()
+                ? promises._boundTo
+                : void 0
+       );
+        var promise = ret.promise();
+        if (promise.isRejected()) {
+            return promise;
+        }
+        ret.setHowMany(howMany);
+        ret.init();
+        return promise;
+    }
+
+    Promise.some = function Promise$Some(promises, howMany) {
+        return Promise$_Some(promises, howMany, false, Promise.some);
+    };
+
+    Promise.prototype.some = function Promise$some(count) {
+        return Promise$_Some(this, count, true, this.some);
+    };
+
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/some_promise_array.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/some_promise_array.js
new file mode 100644
index 0000000..d3b89d4
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/some_promise_array.js
@@ -0,0 +1,131 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function (PromiseArray) {
+var util = require("./util.js");
+var RangeError = require("./errors.js").RangeError;
+var inherits = util.inherits;
+var isArray = util.isArray;
+
+function SomePromiseArray(values, caller, boundTo) {
+    this.constructor$(values, caller, boundTo);
+    this._howMany = 0;
+    this._unwrap = false;
+    this._initialized = false;
+}
+inherits(SomePromiseArray, PromiseArray);
+
+SomePromiseArray.prototype._init = function SomePromiseArray$_init() {
+    if (!this._initialized) {
+        return;
+    }
+    if (this._howMany === 0) {
+        this._resolve([]);
+        return;
+    }
+    this._init$(void 0, -2);
+    var isArrayResolved = isArray(this._values);
+    this._holes = isArrayResolved ? this._values.length - this.length() : 0;
+
+    if (!this._isResolved() &&
+        isArrayResolved &&
+        this._howMany > this._canPossiblyFulfill()) {
+        var message = "(Promise.some) input array contains less than " +
+                        this._howMany  + " promises";
+        this._reject(new RangeError(message));
+    }
+};
+
+SomePromiseArray.prototype.init = function SomePromiseArray$init() {
+    this._initialized = true;
+    this._init();
+};
+
+SomePromiseArray.prototype.setUnwrap = function SomePromiseArray$setUnwrap() {
+    this._unwrap = true;
+};
+
+SomePromiseArray.prototype.howMany = function SomePromiseArray$howMany() {
+    return this._howMany;
+};
+
+SomePromiseArray.prototype.setHowMany =
+function SomePromiseArray$setHowMany(count) {
+    if (this._isResolved()) return;
+    this._howMany = count;
+};
+
+SomePromiseArray.prototype._promiseFulfilled =
+function SomePromiseArray$_promiseFulfilled(value) {
+    if (this._isResolved()) return;
+    this._addFulfilled(value);
+    if (this._fulfilled() === this.howMany()) {
+        this._values.length = this.howMany();
+        if (this.howMany() === 1 && this._unwrap) {
+            this._resolve(this._values[0]);
+        }
+        else {
+            this._resolve(this._values);
+        }
+    }
+
+};
+SomePromiseArray.prototype._promiseRejected =
+function SomePromiseArray$_promiseRejected(reason) {
+    if (this._isResolved()) return;
+    this._addRejected(reason);
+    if (this.howMany() > this._canPossiblyFulfill()) {
+        if (this._values.length === this.length()) {
+            this._reject([]);
+        }
+        else {
+            this._reject(this._values.slice(this.length() + this._holes));
+        }
+    }
+};
+
+SomePromiseArray.prototype._fulfilled = function SomePromiseArray$_fulfilled() {
+    return this._totalResolved;
+};
+
+SomePromiseArray.prototype._rejected = function SomePromiseArray$_rejected() {
+    return this._values.length - this.length() - this._holes;
+};
+
+SomePromiseArray.prototype._addRejected =
+function SomePromiseArray$_addRejected(reason) {
+    this._values.push(reason);
+};
+
+SomePromiseArray.prototype._addFulfilled =
+function SomePromiseArray$_addFulfilled(value) {
+    this._values[this._totalResolved++] = value;
+};
+
+SomePromiseArray.prototype._canPossiblyFulfill =
+function SomePromiseArray$_canPossiblyFulfill() {
+    return this.length() - this._rejected();
+};
+
+return SomePromiseArray;
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/synchronous_inspection.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/synchronous_inspection.js
new file mode 100644
index 0000000..dcbdc90
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/synchronous_inspection.js
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise) {
+    var PromiseInspection = require("./promise_inspection.js");
+
+    Promise.prototype.inspect = function Promise$inspect() {
+        return new PromiseInspection(this);
+    };
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/thenables.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/thenables.js
new file mode 100644
index 0000000..510da18
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/thenables.js
@@ -0,0 +1,138 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, INTERNAL) {
+    var util = require("./util.js");
+    var canAttach = require("./errors.js").canAttach;
+    var errorObj = util.errorObj;
+    var isObject = util.isObject;
+
+    function getThen(obj) {
+        try {
+            return obj.then;
+        }
+        catch(e) {
+            errorObj.e = e;
+            return errorObj;
+        }
+    }
+
+    function Promise$_Cast(obj, caller, originalPromise) {
+        if (isObject(obj)) {
+            if (obj instanceof Promise) {
+                return obj;
+            }
+            else if (isAnyBluebirdPromise(obj)) {
+                var ret = new Promise(INTERNAL);
+                ret._setTrace(caller, void 0);
+                obj._then(
+                    ret._fulfillUnchecked,
+                    ret._rejectUncheckedCheckError,
+                    ret._progressUnchecked,
+                    ret,
+                    null,
+                    void 0
+                );
+                ret._setFollowing();
+                return ret;
+            }
+            var then = getThen(obj);
+            if (then === errorObj) {
+                caller = typeof caller === "function" ? caller : Promise$_Cast;
+                if (originalPromise !== void 0 && canAttach(then.e)) {
+                    originalPromise._attachExtraTrace(then.e);
+                }
+                return Promise.reject(then.e, caller);
+            }
+            else if (typeof then === "function") {
+                caller = typeof caller === "function" ? caller : Promise$_Cast;
+                return Promise$_doThenable(obj, then, caller, originalPromise);
+            }
+        }
+        return obj;
+    }
+
+    var hasProp = {}.hasOwnProperty;
+    function isAnyBluebirdPromise(obj) {
+        return hasProp.call(obj, "_promise0");
+    }
+
+    function Promise$_doThenable(x, then, caller, originalPromise) {
+        var resolver = Promise.defer(caller);
+        var called = false;
+        try {
+            then.call(
+                x,
+                Promise$_resolveFromThenable,
+                Promise$_rejectFromThenable,
+                Promise$_progressFromThenable
+            );
+        }
+        catch(e) {
+            if (!called) {
+                called = true;
+                var trace = canAttach(e) ? e : new Error(e + "");
+                if (originalPromise !== void 0) {
+                    originalPromise._attachExtraTrace(trace);
+                }
+                resolver.promise._reject(e, trace);
+            }
+        }
+        return resolver.promise;
+
+        function Promise$_resolveFromThenable(y) {
+            if (called) return;
+            called = true;
+
+            if (x === y) {
+                var e = Promise._makeSelfResolutionError();
+                if (originalPromise !== void 0) {
+                    originalPromise._attachExtraTrace(e);
+                }
+                resolver.promise._reject(e, void 0);
+                return;
+            }
+            resolver.resolve(y);
+        }
+
+        function Promise$_rejectFromThenable(r) {
+            if (called) return;
+            called = true;
+            var trace = canAttach(r) ? r : new Error(r + "");
+            if (originalPromise !== void 0) {
+                originalPromise._attachExtraTrace(trace);
+            }
+            resolver.promise._reject(r, trace);
+        }
+
+        function Promise$_progressFromThenable(v) {
+            if (called) return;
+            var promise = resolver.promise;
+            if (typeof promise._progress === "function") {
+                promise._progress(v);
+            }
+        }
+    }
+
+    Promise._cast = Promise$_Cast;
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/timers.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/timers.js
new file mode 100644
index 0000000..8edcac2
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/timers.js
@@ -0,0 +1,114 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+
+var global = require("./global.js");
+var setTimeout = function(fn, time) {
+    var $_len = arguments.length;var args = new Array($_len - 2); for(var $_i = 2; $_i < $_len; ++$_i) {args[$_i - 2] = arguments[$_i];}
+    global.setTimeout(function() {
+        fn.apply(void 0, args);
+    }, time);
+};
+
+var pass = {};
+global.setTimeout( function(_) {
+    if(_ === pass) {
+        setTimeout = global.setTimeout;
+    }
+}, 1, pass);
+
+module.exports = function(Promise, INTERNAL) {
+    var util = require("./util.js");
+    var errors = require("./errors.js");
+    var apiRejection = require("./errors_api_rejection")(Promise);
+    var TimeoutError = Promise.TimeoutError;
+
+    var afterTimeout = function Promise$_afterTimeout(promise, message, ms) {
+        if (!promise.isPending()) return;
+        if (typeof message !== "string") {
+            message = "operation timed out after" + " " + ms + " ms"
+        }
+        var err = new TimeoutError(message);
+        errors.markAsOriginatingFromRejection(err);
+        promise._attachExtraTrace(err);
+        promise._rejectUnchecked(err);
+    };
+
+    var afterDelay = function Promise$_afterDelay(value, promise) {
+        promise._fulfill(value);
+    };
+
+    Promise.delay = function Promise$Delay(value, ms, caller) {
+        if (ms === void 0) {
+            ms = value;
+            value = void 0;
+        }
+        ms = +ms;
+        if (typeof caller !== "function") {
+            caller = Promise.delay;
+        }
+        var maybePromise = Promise._cast(value, caller, void 0);
+        var promise = new Promise(INTERNAL);
+
+        if (Promise.is(maybePromise)) {
+            if (maybePromise._isBound()) {
+                promise._setBoundTo(maybePromise._boundTo);
+            }
+            if (maybePromise._cancellable()) {
+                promise._setCancellable();
+                promise._cancellationParent = maybePromise;
+            }
+            promise._setTrace(caller, maybePromise);
+            promise._follow(maybePromise);
+            return promise.then(function(value) {
+                return Promise.delay(value, ms);
+            });
+        }
+        else {
+            promise._setTrace(caller, void 0);
+            setTimeout(afterDelay, ms, value, promise);
+        }
+        return promise;
+    };
+
+    Promise.prototype.delay = function Promise$delay(ms) {
+        return Promise.delay(this, ms, this.delay);
+    };
+
+    Promise.prototype.timeout = function Promise$timeout(ms, message) {
+        ms = +ms;
+
+        var ret = new Promise(INTERNAL);
+        ret._setTrace(this.timeout, this);
+
+        if (this._isBound()) ret._setBoundTo(this._boundTo);
+        if (this._cancellable()) {
+            ret._setCancellable();
+            ret._cancellationParent = this;
+        }
+        ret._follow(this);
+        setTimeout(afterTimeout, ms, ret, message, ms);
+        return ret;
+    };
+
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/util.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/util.js
new file mode 100644
index 0000000..fbd34e9
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/main/util.js
@@ -0,0 +1,193 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var global = require("./global.js");
+var es5 = require("./es5.js");
+var haveGetters = (function(){
+    try {
+        var o = {};
+        es5.defineProperty(o, "f", {
+            get: function () {
+                return 3;
+            }
+        });
+        return o.f === 3;
+    }
+    catch (e) {
+        return false;
+    }
+
+})();
+
+var canEvaluate = (function() {
+    if (typeof window !== "undefined" && window !== null &&
+        typeof window.document !== "undefined" &&
+        typeof navigator !== "undefined" && navigator !== null &&
+        typeof navigator.appName === "string" &&
+        window === global) {
+        return false;
+    }
+    return true;
+})();
+
+function deprecated(msg) {
+    if (typeof console !== "undefined" && console !== null &&
+        typeof console.warn === "function") {
+        console.warn("Bluebird: " + msg);
+    }
+}
+
+var errorObj = {e: {}};
+function tryCatch1(fn, receiver, arg) {
+    try {
+        return fn.call(receiver, arg);
+    }
+    catch (e) {
+        errorObj.e = e;
+        return errorObj;
+    }
+}
+
+function tryCatch2(fn, receiver, arg, arg2) {
+    try {
+        return fn.call(receiver, arg, arg2);
+    }
+    catch (e) {
+        errorObj.e = e;
+        return errorObj;
+    }
+}
+
+function tryCatchApply(fn, args, receiver) {
+    try {
+        return fn.apply(receiver, args);
+    }
+    catch (e) {
+        errorObj.e = e;
+        return errorObj;
+    }
+}
+
+var inherits = function(Child, Parent) {
+    var hasProp = {}.hasOwnProperty;
+
+    function T() {
+        this.constructor = Child;
+        this.constructor$ = Parent;
+        for (var propertyName in Parent.prototype) {
+            if (hasProp.call(Parent.prototype, propertyName) &&
+                propertyName.charAt(propertyName.length-1) !== "$"
+           ) {
+                this[propertyName + "$"] = Parent.prototype[propertyName];
+            }
+        }
+    }
+    T.prototype = Parent.prototype;
+    Child.prototype = new T();
+    return Child.prototype;
+};
+
+function asString(val) {
+    return typeof val === "string" ? val : ("" + val);
+}
+
+function isPrimitive(val) {
+    return val == null || val === true || val === false ||
+        typeof val === "string" || typeof val === "number";
+
+}
+
+function isObject(value) {
+    return !isPrimitive(value);
+}
+
+function maybeWrapAsError(maybeError) {
+    if (!isPrimitive(maybeError)) return maybeError;
+
+    return new Error(asString(maybeError));
+}
+
+function withAppended(target, appendee) {
+    var len = target.length;
+    var ret = new Array(len + 1);
+    var i;
+    for (i = 0; i < len; ++i) {
+        ret[i] = target[i];
+    }
+    ret[i] = appendee;
+    return ret;
+}
+
+
+function notEnumerableProp(obj, name, value) {
+    if (isPrimitive(obj)) return obj;
+    var descriptor = {
+        value: value,
+        configurable: true,
+        enumerable: false,
+        writable: true
+    };
+    es5.defineProperty(obj, name, descriptor);
+    return obj;
+}
+
+
+var wrapsPrimitiveReceiver = (function() {
+    return this !== "string";
+}).call("string");
+
+function thrower(r) {
+    throw r;
+}
+
+
+function toFastProperties(obj) {
+    /*jshint -W027*/
+    function f() {}
+    f.prototype = obj;
+    return f;
+    eval(obj);
+}
+
+var ret = {
+    thrower: thrower,
+    isArray: es5.isArray,
+    haveGetters: haveGetters,
+    notEnumerableProp: notEnumerableProp,
+    isPrimitive: isPrimitive,
+    isObject: isObject,
+    canEvaluate: canEvaluate,
+    deprecated: deprecated,
+    errorObj: errorObj,
+    tryCatch1: tryCatch1,
+    tryCatch2: tryCatch2,
+    tryCatchApply: tryCatchApply,
+    inherits: inherits,
+    withAppended: withAppended,
+    asString: asString,
+    maybeWrapAsError: maybeWrapAsError,
+    wrapsPrimitiveReceiver: wrapsPrimitiveReceiver,
+    toFastProperties: toFastProperties
+};
+
+module.exports = ret;
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/any.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/any.js
new file mode 100644
index 0000000..8d174cf
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/any.js
@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, Promise$_CreatePromiseArray, PromiseArray) {
+
+    var SomePromiseArray = require("./some_promise_array.js")(PromiseArray);
+    function Promise$_Any(promises, useBound, caller) {
+        var ret = Promise$_CreatePromiseArray(
+            promises,
+            SomePromiseArray,
+            caller,
+            useBound === true && promises._isBound()
+                ? promises._boundTo
+                : void 0
+       );
+        var promise = ret.promise();
+        if (promise.isRejected()) {
+            return promise;
+        }
+        ret.setHowMany(1);
+        ret.setUnwrap();
+        ret.init();
+        return promise;
+    }
+
+    Promise.any = function Promise$Any(promises) {
+        return Promise$_Any(promises, false, Promise.any);
+    };
+
+    Promise.prototype.any = function Promise$any() {
+        return Promise$_Any(this, true, this.any);
+    };
+
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/assert.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/assert.js
new file mode 100644
index 0000000..4adb8c2
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/assert.js
@@ -0,0 +1,83 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = (function(){
+    var AssertionError = (function() {
+        function AssertionError(a) {
+            this.constructor$(a);
+            this.message = a;
+            this.name = "AssertionError";
+        }
+        AssertionError.prototype = new Error();
+        AssertionError.prototype.constructor = AssertionError;
+        AssertionError.prototype.constructor$ = Error;
+        return AssertionError;
+    })();
+
+    function getParams(args) {
+        var params = [];
+        for (var i = 0; i < args.length; ++i) params.push("arg" + i);
+        return params;
+    }
+
+    function nativeAssert(callName, args, expect) {
+        try {
+            var params = getParams(args);
+            var constructorArgs = params;
+            constructorArgs.push("return " +
+                    callName + "("+ params.join(",") + ");");
+            var fn = Function.apply(null, constructorArgs);
+            return fn.apply(null, args);
+        }
+        catch (e) {
+            if (!(e instanceof SyntaxError)) {
+                throw e;
+            }
+            else {
+                return expect;
+            }
+        }
+    }
+
+    return function assert(boolExpr, message) {
+        if (boolExpr === true) return;
+
+        if (typeof boolExpr === "string" &&
+            boolExpr.charAt(0) === "%") {
+            var nativeCallName = boolExpr;
+            var $_len = arguments.length;var args = new Array($_len - 2); for(var $_i = 2; $_i < $_len; ++$_i) {args[$_i - 2] = arguments[$_i];}
+            if (nativeAssert(nativeCallName, args, message) === message) return;
+            message = (nativeCallName + " !== " + message);
+        }
+
+        var ret = new AssertionError(message);
+        if (Error.captureStackTrace) {
+            Error.captureStackTrace(ret, assert);
+        }
+        if (console && console.error) {
+            console.error(ret.stack + "");
+        }
+        throw ret;
+
+    };
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/async.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/async.js
new file mode 100644
index 0000000..6f32b10
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/async.js
@@ -0,0 +1,111 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var schedule = require("./schedule.js");
+var Queue = require("./queue.js");
+var errorObj = require("./util.js").errorObj;
+var tryCatch1 = require("./util.js").tryCatch1;
+var process = require("./global.js").process;
+
+function Async() {
+    this._isTickUsed = false;
+    this._length = 0;
+    this._lateBuffer = new Queue();
+    this._functionBuffer = new Queue(25000 * 3);
+    var self = this;
+    this.consumeFunctionBuffer = function Async$consumeFunctionBuffer() {
+        self._consumeFunctionBuffer();
+    };
+}
+
+Async.prototype.haveItemsQueued = function Async$haveItemsQueued() {
+    return this._length > 0;
+};
+
+Async.prototype.invokeLater = function Async$invokeLater(fn, receiver, arg) {
+    if (process !== void 0 &&
+        process.domain != null &&
+        !fn.domain) {
+        fn = process.domain.bind(fn);
+    }
+    this._lateBuffer.push(fn, receiver, arg);
+    this._queueTick();
+};
+
+Async.prototype.invoke = function Async$invoke(fn, receiver, arg) {
+    if (process !== void 0 &&
+        process.domain != null &&
+        !fn.domain) {
+        fn = process.domain.bind(fn);
+    }
+    var functionBuffer = this._functionBuffer;
+    functionBuffer.push(fn, receiver, arg);
+    this._length = functionBuffer.length();
+    this._queueTick();
+};
+
+Async.prototype._consumeFunctionBuffer =
+function Async$_consumeFunctionBuffer() {
+    var functionBuffer = this._functionBuffer;
+    while(functionBuffer.length() > 0) {
+        var fn = functionBuffer.shift();
+        var receiver = functionBuffer.shift();
+        var arg = functionBuffer.shift();
+        fn.call(receiver, arg);
+    }
+    this._reset();
+    this._consumeLateBuffer();
+};
+
+Async.prototype._consumeLateBuffer = function Async$_consumeLateBuffer() {
+    var buffer = this._lateBuffer;
+    while(buffer.length() > 0) {
+        var fn = buffer.shift();
+        var receiver = buffer.shift();
+        var arg = buffer.shift();
+        var res = tryCatch1(fn, receiver, arg);
+        if (res === errorObj) {
+            this._queueTick();
+            if (fn.domain != null) {
+                fn.domain.emit("error", res.e);
+            }
+            else {
+                throw res.e;
+            }
+        }
+    }
+};
+
+Async.prototype._queueTick = function Async$_queue() {
+    if (!this._isTickUsed) {
+        schedule(this.consumeFunctionBuffer);
+        this._isTickUsed = true;
+    }
+};
+
+Async.prototype._reset = function Async$_reset() {
+    this._isTickUsed = false;
+    this._length = 0;
+};
+
+module.exports = new Async();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/bluebird.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/bluebird.js
new file mode 100644
index 0000000..6fd85f1
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/bluebird.js
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var Promise = require("./promise.js")();
+module.exports = Promise;
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/call_get.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/call_get.js
new file mode 100644
index 0000000..2a3c1f5
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/call_get.js
@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise) {
+    Promise.prototype.call = function Promise$call(propertyName) {
+        var $_len = arguments.length;var args = new Array($_len - 1); for(var $_i = 1; $_i < $_len; ++$_i) {args[$_i - 1] = arguments[$_i];}
+
+        return this._then(function(obj) {
+                return obj[propertyName].apply(obj, args);
+            },
+            void 0,
+            void 0,
+            void 0,
+            void 0,
+            this.call
+       );
+    };
+
+    function Promise$getter(obj) {
+        var prop = typeof this === "string"
+            ? this
+            : ("" + this);
+        return obj[prop];
+    }
+    Promise.prototype.get = function Promise$get(propertyName) {
+        return this._then(
+            Promise$getter,
+            void 0,
+            void 0,
+            propertyName,
+            void 0,
+            this.get
+       );
+    };
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/cancel.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/cancel.js
new file mode 100644
index 0000000..542b488
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/cancel.js
@@ -0,0 +1,77 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, INTERNAL) {
+    var errors = require("./errors.js");
+    var async = require("./async.js");
+    var CancellationError = errors.CancellationError;
+    var SYNC_TOKEN = {};
+
+    Promise.prototype._cancel = function Promise$_cancel() {
+        if (!this.isCancellable()) return this;
+        var parent;
+        if ((parent = this._cancellationParent) !== void 0) {
+            parent.cancel(SYNC_TOKEN);
+            return;
+        }
+        var err = new CancellationError();
+        this._attachExtraTrace(err);
+        this._rejectUnchecked(err);
+    };
+
+    Promise.prototype.cancel = function Promise$cancel(token) {
+        if (!this.isCancellable()) return this;
+        if (token === SYNC_TOKEN) {
+            this._cancel();
+            return this;
+        }
+        async.invokeLater(this._cancel, this, void 0);
+        return this;
+    };
+
+    Promise.prototype.cancellable = function Promise$cancellable() {
+        if (this._cancellable()) return this;
+        this._setCancellable();
+        this._cancellationParent = void 0;
+        return this;
+    };
+
+    Promise.prototype.uncancellable = function Promise$uncancellable() {
+        var ret = new Promise(INTERNAL);
+        ret._setTrace(this.uncancellable, this);
+        ret._follow(this);
+        ret._unsetCancellable();
+        if (this._isBound()) ret._setBoundTo(this._boundTo);
+        return ret;
+    };
+
+    Promise.prototype.fork =
+    function Promise$fork(didFulfill, didReject, didProgress) {
+        var ret = this._then(didFulfill, didReject, didProgress,
+            void 0, void 0, this.fork);
+
+        ret._setCancellable();
+        ret._cancellationParent = void 0;
+        return ret;
+    };
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/captured_trace.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/captured_trace.js
new file mode 100644
index 0000000..af34f11
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/captured_trace.js
@@ -0,0 +1,237 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function() {
+var inherits = require("./util.js").inherits;
+var defineProperty = require("./es5.js").defineProperty;
+
+var rignore = new RegExp(
+    "\\b(?:[\\w.]*Promise(?:Array|Spawn)?\\$_\\w+|" +
+    "tryCatch(?:1|2|Apply)|new \\w*PromiseArray|" +
+    "\\w*PromiseArray\\.\\w*PromiseArray|" +
+    "setTimeout|CatchFilter\\$_\\w+|makeNodePromisified|processImmediate|" +
+    "process._tickCallback|nextTick|Async\\$\\w+)\\b"
+);
+
+var rtraceline = null;
+var formatStack = null;
+var areNamesMangled = false;
+
+function formatNonError(obj) {
+    var str;
+    if (typeof obj === "function") {
+        str = "[function " +
+            (obj.name || "anonymous") +
+            "]";
+    }
+    else {
+        str = obj.toString();
+        var ruselessToString = /\[object [a-zA-Z0-9$_]+\]/;
+        if (ruselessToString.test(str)) {
+            try {
+                var newStr = JSON.stringify(obj);
+                str = newStr;
+            }
+            catch(e) {
+
+            }
+        }
+        if (str.length === 0) {
+            str = "(empty array)";
+        }
+    }
+    return ("(<" + snip(str) + ">, no stack trace)");
+}
+
+function snip(str) {
+    var maxChars = 41;
+    if (str.length < maxChars) {
+        return str;
+    }
+    return str.substr(0, maxChars - 3) + "...";
+}
+
+function CapturedTrace(ignoreUntil, isTopLevel) {
+    if (!areNamesMangled) {
+    }
+    this.captureStackTrace(ignoreUntil, isTopLevel);
+
+}
+inherits(CapturedTrace, Error);
+
+CapturedTrace.prototype.captureStackTrace =
+function CapturedTrace$captureStackTrace(ignoreUntil, isTopLevel) {
+    captureStackTrace(this, ignoreUntil, isTopLevel);
+};
+
+CapturedTrace.possiblyUnhandledRejection =
+function CapturedTrace$PossiblyUnhandledRejection(reason) {
+    if (typeof console === "object") {
+        var message;
+        if (typeof reason === "object" || typeof reason === "function") {
+            var stack = reason.stack;
+            message = "Possibly unhandled " + formatStack(stack, reason);
+        }
+        else {
+            message = "Possibly unhandled " + String(reason);
+        }
+        if (typeof console.error === "function" ||
+            typeof console.error === "object") {
+            console.error(message);
+        }
+        else if (typeof console.log === "function" ||
+            typeof console.error === "object") {
+            console.log(message);
+        }
+    }
+};
+
+areNamesMangled = CapturedTrace.prototype.captureStackTrace.name !==
+    "CapturedTrace$captureStackTrace";
+
+CapturedTrace.combine = function CapturedTrace$Combine(current, prev) {
+    var curLast = current.length - 1;
+    for (var i = prev.length - 1; i >= 0; --i) {
+        var line = prev[i];
+        if (current[curLast] === line) {
+            current.pop();
+            curLast--;
+        }
+        else {
+            break;
+        }
+    }
+
+    current.push("From previous event:");
+    var lines = current.concat(prev);
+
+    var ret = [];
+
+
+    for (var i = 0, len = lines.length; i < len; ++i) {
+
+        if ((rignore.test(lines[i]) ||
+            (i > 0 && !rtraceline.test(lines[i])) &&
+            lines[i] !== "From previous event:")
+       ) {
+            continue;
+        }
+        ret.push(lines[i]);
+    }
+    return ret;
+};
+
+CapturedTrace.isSupported = function CapturedTrace$IsSupported() {
+    return typeof captureStackTrace === "function";
+};
+
+var captureStackTrace = (function stackDetection() {
+    if (typeof Error.stackTraceLimit === "number" &&
+        typeof Error.captureStackTrace === "function") {
+        rtraceline = /^\s*at\s*/;
+        formatStack = function(stack, error) {
+            if (typeof stack === "string") return stack;
+
+            if (error.name !== void 0 &&
+                error.message !== void 0) {
+                return error.name + ". " + error.message;
+            }
+            return formatNonError(error);
+
+
+        };
+        var captureStackTrace = Error.captureStackTrace;
+        return function CapturedTrace$_captureStackTrace(
+            receiver, ignoreUntil) {
+            captureStackTrace(receiver, ignoreUntil);
+        };
+    }
+    var err = new Error();
+
+    if (!areNamesMangled && typeof err.stack === "string" &&
+        typeof "".startsWith === "function" &&
+        (err.stack.startsWith("stackDetection@")) &&
+        stackDetection.name === "stackDetection") {
+
+        defineProperty(Error, "stackTraceLimit", {
+            writable: true,
+            enumerable: false,
+            configurable: false,
+            value: 25
+        });
+        rtraceline = /@/;
+        var rline = /[@\n]/;
+
+        formatStack = function(stack, error) {
+            if (typeof stack === "string") {
+                return (error.name + ". " + error.message + "\n" + stack);
+            }
+
+            if (error.name !== void 0 &&
+                error.message !== void 0) {
+                return error.name + ". " + error.message;
+            }
+            return formatNonError(error);
+        };
+
+        return function captureStackTrace(o, fn) {
+            var name = fn.name;
+            var stack = new Error().stack;
+            var split = stack.split(rline);
+            var i, len = split.length;
+            for (i = 0; i < len; i += 2) {
+                if (split[i] === name) {
+                    break;
+                }
+            }
+            split = split.slice(i + 2);
+            len = split.length - 2;
+            var ret = "";
+            for (i = 0; i < len; i += 2) {
+                ret += split[i];
+                ret += "@";
+                ret += split[i + 1];
+                ret += "\n";
+            }
+            o.stack = ret;
+        };
+    }
+    else {
+        formatStack = function(stack, error) {
+            if (typeof stack === "string") return stack;
+
+            if ((typeof error === "object" ||
+                typeof error === "function") &&
+                error.name !== void 0 &&
+                error.message !== void 0) {
+                return error.name + ". " + error.message;
+            }
+            return formatNonError(error);
+        };
+
+        return null;
+    }
+})();
+
+return CapturedTrace;
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/catch_filter.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/catch_filter.js
new file mode 100644
index 0000000..8b42af1
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/catch_filter.js
@@ -0,0 +1,93 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(NEXT_FILTER) {
+var util = require("./util.js");
+var errors = require("./errors.js");
+var tryCatch1 = util.tryCatch1;
+var errorObj = util.errorObj;
+var keys = require("./es5.js").keys;
+
+function CatchFilter(instances, callback, promise) {
+    this._instances = instances;
+    this._callback = callback;
+    this._promise = promise;
+}
+
+function CatchFilter$_safePredicate(predicate, e) {
+    var safeObject = {};
+    var retfilter = tryCatch1(predicate, safeObject, e);
+
+    if (retfilter === errorObj) return retfilter;
+
+    var safeKeys = keys(safeObject);
+    if (safeKeys.length) {
+        errorObj.e = new TypeError(
+            "Catch filter must inherit from Error "
+          + "or be a simple predicate function");
+        return errorObj;
+    }
+    return retfilter;
+}
+
+CatchFilter.prototype.doFilter = function CatchFilter$_doFilter(e) {
+    var cb = this._callback;
+    var promise = this._promise;
+    var boundTo = promise._isBound() ? promise._boundTo : void 0;
+    for (var i = 0, len = this._instances.length; i < len; ++i) {
+        var item = this._instances[i];
+        var itemIsErrorType = item === Error ||
+            (item != null && item.prototype instanceof Error);
+
+        if (itemIsErrorType && e instanceof item) {
+            var ret = tryCatch1(cb, boundTo, e);
+            if (ret === errorObj) {
+                NEXT_FILTER.e = ret.e;
+                return NEXT_FILTER;
+            }
+            return ret;
+        } else if (typeof item === "function" && !itemIsErrorType) {
+            var shouldHandle = CatchFilter$_safePredicate(item, e);
+            if (shouldHandle === errorObj) {
+                var trace = errors.canAttach(errorObj.e)
+                    ? errorObj.e
+                    : new Error(errorObj.e + "");
+                this._promise._attachExtraTrace(trace);
+                e = errorObj.e;
+                break;
+            } else if (shouldHandle) {
+                var ret = tryCatch1(cb, boundTo, e);
+                if (ret === errorObj) {
+                    NEXT_FILTER.e = ret.e;
+                    return NEXT_FILTER;
+                }
+                return ret;
+            }
+        }
+    }
+    NEXT_FILTER.e = e;
+    return NEXT_FILTER;
+};
+
+return CatchFilter;
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/direct_resolve.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/direct_resolve.js
new file mode 100644
index 0000000..f4d5384
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/direct_resolve.js
@@ -0,0 +1,83 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var util = require("./util.js");
+var isPrimitive = util.isPrimitive;
+var wrapsPrimitiveReceiver = util.wrapsPrimitiveReceiver;
+
+module.exports = function(Promise) {
+var returner = function Promise$_returner() {
+    return this;
+};
+var thrower = function Promise$_thrower() {
+    throw this;
+};
+
+var wrapper = function Promise$_wrapper(value, action) {
+    if (action === 1) {
+        return function Promise$_thrower() {
+            throw value;
+        };
+    }
+    else if (action === 2) {
+        return function Promise$_returner() {
+            return value;
+        };
+    }
+};
+
+
+Promise.prototype["return"] =
+Promise.prototype.thenReturn =
+function Promise$thenReturn(value) {
+    if (wrapsPrimitiveReceiver && isPrimitive(value)) {
+        return this._then(
+            wrapper(value, 2),
+            void 0,
+            void 0,
+            void 0,
+            void 0,
+            this.thenReturn
+       );
+    }
+    return this._then(returner, void 0, void 0,
+                        value, void 0, this.thenReturn);
+};
+
+Promise.prototype["throw"] =
+Promise.prototype.thenThrow =
+function Promise$thenThrow(reason) {
+    if (wrapsPrimitiveReceiver && isPrimitive(reason)) {
+        return this._then(
+            wrapper(reason, 1),
+            void 0,
+            void 0,
+            void 0,
+            void 0,
+            this.thenThrow
+       );
+    }
+    return this._then(thrower, void 0, void 0,
+                        reason, void 0, this.thenThrow);
+};
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/errors.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/errors.js
new file mode 100644
index 0000000..35fb66e
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/errors.js
@@ -0,0 +1,114 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var global = require("./global.js");
+var Objectfreeze = require("./es5.js").freeze;
+var util = require("./util.js");
+var inherits = util.inherits;
+var notEnumerableProp = util.notEnumerableProp;
+var Error = global.Error;
+
+function markAsOriginatingFromRejection(e) {
+    try {
+        notEnumerableProp(e, "isAsync", true);
+    }
+    catch(ignore) {}
+}
+
+function originatesFromRejection(e) {
+    if (e == null) return false;
+    return ((e instanceof RejectionError) ||
+        e["isAsync"] === true);
+}
+
+function isError(obj) {
+    return obj instanceof Error;
+}
+
+function canAttach(obj) {
+    return isError(obj);
+}
+
+function subError(nameProperty, defaultMessage) {
+    function SubError(message) {
+        if (!(this instanceof SubError)) return new SubError(message);
+        this.message = typeof message === "string" ? message : defaultMessage;
+        this.name = nameProperty;
+        if (Error.captureStackTrace) {
+            Error.captureStackTrace(this, this.constructor);
+        }
+    }
+    inherits(SubError, Error);
+    return SubError;
+}
+
+var TypeError = global.TypeError;
+if (typeof TypeError !== "function") {
+    TypeError = subError("TypeError", "type error");
+}
+var RangeError = global.RangeError;
+if (typeof RangeError !== "function") {
+    RangeError = subError("RangeError", "range error");
+}
+var CancellationError = subError("CancellationError", "cancellation error");
+var TimeoutError = subError("TimeoutError", "timeout error");
+
+function RejectionError(message) {
+    this.name = "RejectionError";
+    this.message = message;
+    this.cause = message;
+    this.isAsync = true;
+
+    if (message instanceof Error) {
+        this.message = message.message;
+        this.stack = message.stack;
+    }
+    else if (Error.captureStackTrace) {
+        Error.captureStackTrace(this, this.constructor);
+    }
+
+}
+inherits(RejectionError, Error);
+
+var key = "__BluebirdErrorTypes__";
+var errorTypes = global[key];
+if (!errorTypes) {
+    errorTypes = Objectfreeze({
+        CancellationError: CancellationError,
+        TimeoutError: TimeoutError,
+        RejectionError: RejectionError
+    });
+    notEnumerableProp(global, key, errorTypes);
+}
+
+module.exports = {
+    Error: Error,
+    TypeError: TypeError,
+    RangeError: RangeError,
+    CancellationError: errorTypes.CancellationError,
+    RejectionError: errorTypes.RejectionError,
+    TimeoutError: errorTypes.TimeoutError,
+    originatesFromRejection: originatesFromRejection,
+    markAsOriginatingFromRejection: markAsOriginatingFromRejection,
+    canAttach: canAttach
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/errors_api_rejection.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/errors_api_rejection.js
new file mode 100644
index 0000000..e953e3b
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/errors_api_rejection.js
@@ -0,0 +1,38 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise) {
+var TypeError = require('./errors.js').TypeError;
+
+function apiRejection(msg) {
+    var error = new TypeError(msg);
+    var ret = Promise.rejected(error);
+    var parent = ret._peekContext();
+    if (parent != null) {
+        parent._attachExtraTrace(error);
+    }
+    return ret;
+}
+
+return apiRejection;
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/es5.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/es5.js
new file mode 100644
index 0000000..e22a0a9
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/es5.js
@@ -0,0 +1,89 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+var isES5 = (function(){
+    "use strict";
+    return this === void 0;
+})();
+
+if (isES5) {
+    module.exports = {
+        freeze: Object.freeze,
+        defineProperty: Object.defineProperty,
+        keys: Object.keys,
+        getPrototypeOf: Object.getPrototypeOf,
+        isArray: Array.isArray,
+        isES5: isES5
+    };
+}
+
+else {
+    var has = {}.hasOwnProperty;
+    var str = {}.toString;
+    var proto = {}.constructor.prototype;
+
+    function ObjectKeys(o) {
+        var ret = [];
+        for (var key in o) {
+            if (has.call(o, key)) {
+                ret.push(key);
+            }
+        }
+        return ret;
+    }
+
+    function ObjectDefineProperty(o, key, desc) {
+        o[key] = desc.value;
+        return o;
+    }
+
+    function ObjectFreeze(obj) {
+        return obj;
+    }
+
+    function ObjectGetPrototypeOf(obj) {
+        try {
+            return Object(obj).constructor.prototype;
+        }
+        catch (e) {
+            return proto;
+        }
+    }
+
+    function ArrayIsArray(obj) {
+        try {
+            return str.call(obj) === "[object Array]";
+        }
+        catch(e) {
+            return false;
+        }
+    }
+
+    module.exports = {
+        isArray: ArrayIsArray,
+        keys: ObjectKeys,
+        defineProperty: ObjectDefineProperty,
+        freeze: ObjectFreeze,
+        getPrototypeOf: ObjectGetPrototypeOf,
+        isES5: isES5
+    };
+}
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/filter.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/filter.js
new file mode 100644
index 0000000..a4b8ae7
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/filter.js
@@ -0,0 +1,53 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise) {
+    var isArray = require("./util.js").isArray;
+
+    function Promise$_filter(booleans) {
+        var values = this._settledValue;
+        var len = values.length;
+        var ret = new Array(len);
+        var j = 0;
+
+        for (var i = 0; i < len; ++i) {
+            if (booleans[i]) ret[j++] = values[i];
+
+        }
+        ret.length = j;
+        return ret;
+    }
+
+    var ref = {ref: null};
+    Promise.filter = function Promise$Filter(promises, fn) {
+        return Promise.map(promises, fn, ref)
+            ._then(Promise$_filter, void 0, void 0,
+                    ref.ref, void 0, Promise.filter);
+    };
+
+    Promise.prototype.filter = function Promise$filter(fn) {
+        return this.map(fn, ref)
+            ._then(Promise$_filter, void 0, void 0,
+                    ref.ref, void 0, this.filter);
+    };
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/finally.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/finally.js
new file mode 100644
index 0000000..ef1e0ef
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/finally.js
@@ -0,0 +1,132 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, NEXT_FILTER) {
+    var util = require("./util.js");
+    var wrapsPrimitiveReceiver = util.wrapsPrimitiveReceiver;
+    var isPrimitive = util.isPrimitive;
+    var thrower = util.thrower;
+
+
+    function returnThis() {
+        return this;
+    }
+    function throwThis() {
+        throw this;
+    }
+    function makeReturner(r) {
+        return function Promise$_returner() {
+            return r;
+        };
+    }
+    function makeThrower(r) {
+        return function Promise$_thrower() {
+            throw r;
+        };
+    }
+    function promisedFinally(ret, reasonOrValue, isFulfilled) {
+        var useConstantFunction =
+                        wrapsPrimitiveReceiver && isPrimitive(reasonOrValue);
+
+        if (isFulfilled) {
+            return ret._then(
+                useConstantFunction
+                    ? returnThis
+                    : makeReturner(reasonOrValue),
+                thrower, void 0, reasonOrValue, void 0, promisedFinally);
+        }
+        else {
+            return ret._then(
+                useConstantFunction
+                    ? throwThis
+                    : makeThrower(reasonOrValue),
+                thrower, void 0, reasonOrValue, void 0, promisedFinally);
+        }
+    }
+
+    function finallyHandler(reasonOrValue) {
+        var promise = this.promise;
+        var handler = this.handler;
+
+        var ret = promise._isBound()
+                        ? handler.call(promise._boundTo)
+                        : handler();
+
+        if (ret !== void 0) {
+            var maybePromise = Promise._cast(ret, finallyHandler, void 0);
+            if (maybePromise instanceof Promise) {
+                return promisedFinally(maybePromise, reasonOrValue,
+                                        promise.isFulfilled());
+            }
+        }
+
+        if (promise.isRejected()) {
+            NEXT_FILTER.e = reasonOrValue;
+            return NEXT_FILTER;
+        }
+        else {
+            return reasonOrValue;
+        }
+    }
+
+    function tapHandler(value) {
+        var promise = this.promise;
+        var handler = this.handler;
+
+        var ret = promise._isBound()
+                        ? handler.call(promise._boundTo, value)
+                        : handler(value);
+
+        if (ret !== void 0) {
+            var maybePromise = Promise._cast(ret, tapHandler, void 0);
+            if (maybePromise instanceof Promise) {
+                return promisedFinally(maybePromise, value, true);
+            }
+        }
+        return value;
+    }
+
+    Promise.prototype._passThroughHandler =
+    function Promise$_passThroughHandler(handler, isFinally, caller) {
+        if (typeof handler !== "function") return this.then();
+
+        var promiseAndHandler = {
+            promise: this,
+            handler: handler
+        };
+
+        return this._then(
+                isFinally ? finallyHandler : tapHandler,
+                isFinally ? finallyHandler : void 0, void 0,
+                promiseAndHandler, void 0, caller);
+    };
+
+    Promise.prototype.lastly =
+    Promise.prototype["finally"] = function Promise$finally(handler) {
+        return this._passThroughHandler(handler, true, this.lastly);
+    };
+
+    Promise.prototype.tap = function Promise$tap(handler) {
+        return this._passThroughHandler(handler, false, this.tap);
+    };
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/generators.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/generators.js
new file mode 100644
index 0000000..9632ae7
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/generators.js
@@ -0,0 +1,56 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, apiRejection, INTERNAL) {
+    var PromiseSpawn = require("./promise_spawn.js")(Promise, INTERNAL);
+    var errors = require("./errors.js");
+    var TypeError = errors.TypeError;
+    var deprecated = require("./util.js").deprecated;
+
+    Promise.coroutine = function Promise$Coroutine(generatorFunction) {
+        if (typeof generatorFunction !== "function") {
+            throw new TypeError("generatorFunction must be a function");
+        }
+        var PromiseSpawn$ = PromiseSpawn;
+        return function anonymous() {
+            var generator = generatorFunction.apply(this, arguments);
+            var spawn = new PromiseSpawn$(void 0, void 0, anonymous);
+            spawn._generator = generator;
+            spawn._next(void 0);
+            return spawn.promise();
+        };
+    };
+
+    Promise.coroutine.addYieldHandler = PromiseSpawn.addYieldHandler;
+
+    Promise.spawn = function Promise$Spawn(generatorFunction) {
+        deprecated("Promise.spawn is deprecated. Use Promise.coroutine instead.");
+        if (typeof generatorFunction !== "function") {
+            return apiRejection("generatorFunction must be a function");
+        }
+        var spawn = new PromiseSpawn(generatorFunction, this, Promise.spawn);
+        var ret = spawn.promise();
+        spawn._run(Promise.spawn);
+        return ret;
+    };
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/global.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/global.js
new file mode 100644
index 0000000..1ab1947
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/global.js
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = (function(){
+    if (typeof this !== "undefined") {
+        return this;
+    }
+    if (typeof process !== "undefined" &&
+        typeof global !== "undefined" &&
+        typeof process.execPath === "string") {
+        return global;
+    }
+    if (typeof window !== "undefined" &&
+        typeof document !== "undefined" &&
+        typeof navigator !== "undefined" && navigator !== null &&
+        typeof navigator.appName === "string") {
+            if(window.wrappedJSObject !== undefined){
+                return window.wrappedJSObject;
+            }
+        return window;
+    }
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/map.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/map.js
new file mode 100644
index 0000000..b2a36b0
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/map.js
@@ -0,0 +1,127 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(
+    Promise, Promise$_CreatePromiseArray, PromiseArray, apiRejection) {
+
+    function Promise$_mapper(values) {
+        var fn = this;
+        var receiver = void 0;
+
+        if (typeof fn !== "function")  {
+            receiver = fn.receiver;
+            fn = fn.fn;
+        }
+        var shouldDefer = false;
+
+        var ret = new Array(values.length);
+
+        if (receiver === void 0) {
+            for (var i = 0, len = values.length; i < len; ++i) {
+                var value = fn(values[i], i, len);
+                if (!shouldDefer) {
+                    var maybePromise = Promise._cast(value,
+                            Promise$_mapper, void 0);
+                    if (maybePromise instanceof Promise) {
+                        if (maybePromise.isFulfilled()) {
+                            ret[i] = maybePromise._settledValue;
+                            continue;
+                        }
+                        else {
+                            shouldDefer = true;
+                        }
+                        value = maybePromise;
+                    }
+                }
+                ret[i] = value;
+            }
+        }
+        else {
+            for (var i = 0, len = values.length; i < len; ++i) {
+                var value = fn.call(receiver, values[i], i, len);
+                if (!shouldDefer) {
+                    var maybePromise = Promise._cast(value,
+                            Promise$_mapper, void 0);
+                    if (maybePromise instanceof Promise) {
+                        if (maybePromise.isFulfilled()) {
+                            ret[i] = maybePromise._settledValue;
+                            continue;
+                        }
+                        else {
+                            shouldDefer = true;
+                        }
+                        value = maybePromise;
+                    }
+                }
+                ret[i] = value;
+            }
+        }
+        return shouldDefer
+            ? Promise$_CreatePromiseArray(ret, PromiseArray,
+                Promise$_mapper, void 0).promise()
+            : ret;
+    }
+
+    function Promise$_Map(promises, fn, useBound, caller, ref) {
+        if (typeof fn !== "function") {
+            return apiRejection("fn must be a function");
+        }
+
+        if (useBound === true && promises._isBound()) {
+            fn = {
+                fn: fn,
+                receiver: promises._boundTo
+            };
+        }
+
+        var ret = Promise$_CreatePromiseArray(
+            promises,
+            PromiseArray,
+            caller,
+            useBound === true && promises._isBound()
+                ? promises._boundTo
+                : void 0
+       ).promise();
+
+        if (ref !== void 0) {
+            ref.ref = ret;
+        }
+
+        return ret._then(
+            Promise$_mapper,
+            void 0,
+            void 0,
+            fn,
+            void 0,
+            caller
+       );
+    }
+
+    Promise.prototype.map = function Promise$map(fn, ref) {
+        return Promise$_Map(this, fn, true, this.map, ref);
+    };
+
+    Promise.map = function Promise$Map(promises, fn, ref) {
+        return Promise$_Map(promises, fn, false, Promise.map, ref);
+    };
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/nodeify.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/nodeify.js
new file mode 100644
index 0000000..9fe25f9
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/nodeify.js
@@ -0,0 +1,64 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise) {
+    var util = require("./util.js");
+    var async = require("./async.js");
+    var tryCatch2 = util.tryCatch2;
+    var tryCatch1 = util.tryCatch1;
+    var errorObj = util.errorObj;
+
+    function thrower(r) {
+        throw r;
+    }
+
+    function Promise$_successAdapter(val, receiver) {
+        var nodeback = this;
+        var ret = tryCatch2(nodeback, receiver, null, val);
+        if (ret === errorObj) {
+            async.invokeLater(thrower, void 0, ret.e);
+        }
+    }
+    function Promise$_errorAdapter(reason, receiver) {
+        var nodeback = this;
+        var ret = tryCatch1(nodeback, receiver, reason);
+        if (ret === errorObj) {
+            async.invokeLater(thrower, void 0, ret.e);
+        }
+    }
+
+    Promise.prototype.nodeify = function Promise$nodeify(nodeback) {
+        if (typeof nodeback == "function") {
+            this._then(
+                Promise$_successAdapter,
+                Promise$_errorAdapter,
+                void 0,
+                nodeback,
+                this._isBound() ? this._boundTo : null,
+                this.nodeify
+            );
+            return;
+        }
+        return this;
+    };
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/progress.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/progress.js
new file mode 100644
index 0000000..668f5f7
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/progress.js
@@ -0,0 +1,111 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, isPromiseArrayProxy) {
+    var util = require("./util.js");
+    var async = require("./async.js");
+    var errors = require("./errors.js");
+    var tryCatch1 = util.tryCatch1;
+    var errorObj = util.errorObj;
+
+    Promise.prototype.progressed = function Promise$progressed(handler) {
+        return this._then(void 0, void 0, handler,
+                            void 0, void 0, this.progressed);
+    };
+
+    Promise.prototype._progress = function Promise$_progress(progressValue) {
+        if (this._isFollowingOrFulfilledOrRejected()) return;
+        this._progressUnchecked(progressValue);
+
+    };
+
+    Promise.prototype._progressHandlerAt =
+    function Promise$_progressHandlerAt(index) {
+        if (index === 0) return this._progressHandler0;
+        return this[index + 2 - 5];
+    };
+
+    Promise.prototype._doProgressWith =
+    function Promise$_doProgressWith(progression) {
+        var progressValue = progression.value;
+        var handler = progression.handler;
+        var promise = progression.promise;
+        var receiver = progression.receiver;
+
+        this._pushContext();
+        var ret = tryCatch1(handler, receiver, progressValue);
+        this._popContext();
+
+        if (ret === errorObj) {
+            if (ret.e != null &&
+                ret.e.name !== "StopProgressPropagation") {
+                var trace = errors.canAttach(ret.e)
+                    ? ret.e : new Error(ret.e + "");
+                promise._attachExtraTrace(trace);
+                promise._progress(ret.e);
+            }
+        }
+        else if (Promise.is(ret)) {
+            ret._then(promise._progress, null, null, promise, void 0,
+                this._progress);
+        }
+        else {
+            promise._progress(ret);
+        }
+    };
+
+
+    Promise.prototype._progressUnchecked =
+    function Promise$_progressUnchecked(progressValue) {
+        if (!this.isPending()) return;
+        var len = this._length();
+
+        for (var i = 0; i < len; i += 5) {
+            var handler = this._progressHandlerAt(i);
+            var promise = this._promiseAt(i);
+            if (!Promise.is(promise)) {
+                var receiver = this._receiverAt(i);
+                if (typeof handler === "function") {
+                    handler.call(receiver, progressValue, promise);
+                }
+                else if (Promise.is(receiver) && receiver._isProxied()) {
+                    receiver._progressUnchecked(progressValue);
+                }
+                else if (isPromiseArrayProxy(receiver, promise)) {
+                    receiver._promiseProgressed(progressValue, promise);
+                }
+                continue;
+            }
+
+            if (typeof handler === "function") {
+                this._doProgressWith(({handler: handler,
+promise: promise,
+receiver: this._receiverAt(i),
+value: progressValue}));
+            }
+            else {
+                promise._progress(progressValue);
+            }
+        }
+    };
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/promise.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/promise.js
new file mode 100644
index 0000000..f1c9ddc
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/promise.js
@@ -0,0 +1,1167 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function() {
+var global = require("./global.js");
+var util = require("./util.js");
+var async = require("./async.js");
+var errors = require("./errors.js");
+
+var INTERNAL = function(){};
+var APPLY = {};
+var NEXT_FILTER = {e: null};
+
+var PromiseArray = require("./promise_array.js")(Promise, INTERNAL);
+var CapturedTrace = require("./captured_trace.js")();
+var CatchFilter = require("./catch_filter.js")(NEXT_FILTER);
+var PromiseResolver = require("./promise_resolver.js");
+
+var isArray = util.isArray;
+
+var errorObj = util.errorObj;
+var tryCatch1 = util.tryCatch1;
+var tryCatch2 = util.tryCatch2;
+var tryCatchApply = util.tryCatchApply;
+var RangeError = errors.RangeError;
+var TypeError = errors.TypeError;
+var CancellationError = errors.CancellationError;
+var TimeoutError = errors.TimeoutError;
+var RejectionError = errors.RejectionError;
+var originatesFromRejection = errors.originatesFromRejection;
+var markAsOriginatingFromRejection = errors.markAsOriginatingFromRejection;
+var canAttach = errors.canAttach;
+var thrower = util.thrower;
+var apiRejection = require("./errors_api_rejection")(Promise);
+
+
+var makeSelfResolutionError = function Promise$_makeSelfResolutionError() {
+    return new TypeError("circular promise resolution chain");
+};
+
+function isPromise(obj) {
+    if (obj === void 0) return false;
+    return obj instanceof Promise;
+}
+
+function isPromiseArrayProxy(receiver, promiseSlotValue) {
+    if (receiver instanceof PromiseArray) {
+        return promiseSlotValue >= 0;
+    }
+    return false;
+}
+
+function Promise(resolver) {
+    if (typeof resolver !== "function") {
+        throw new TypeError("the promise constructor requires a resolver function");
+    }
+    if (this.constructor !== Promise) {
+        throw new TypeError("the promise constructor cannot be invoked directly");
+    }
+    this._bitField = 0;
+    this._fulfillmentHandler0 = void 0;
+    this._rejectionHandler0 = void 0;
+    this._promise0 = void 0;
+    this._receiver0 = void 0;
+    this._settledValue = void 0;
+    this._boundTo = void 0;
+    if (resolver !== INTERNAL) this._resolveFromResolver(resolver);
+}
+
+Promise.prototype.bind = function Promise$bind(thisArg) {
+    var ret = new Promise(INTERNAL);
+    if (debugging) ret._setTrace(this.bind, this);
+    ret._follow(this);
+    ret._setBoundTo(thisArg);
+    if (this._cancellable()) {
+        ret._setCancellable();
+        ret._cancellationParent = this;
+    }
+    return ret;
+};
+
+Promise.prototype.toString = function Promise$toString() {
+    return "[object Promise]";
+};
+
+Promise.prototype.caught = Promise.prototype["catch"] =
+function Promise$catch(fn) {
+    var len = arguments.length;
+    if (len > 1) {
+        var catchInstances = new Array(len - 1),
+            j = 0, i;
+        for (i = 0; i < len - 1; ++i) {
+            var item = arguments[i];
+            if (typeof item === "function") {
+                catchInstances[j++] = item;
+            }
+            else {
+                var catchFilterTypeError =
+                    new TypeError(
+                        "A catch filter must be an error constructor "
+                        + "or a filter function");
+
+                this._attachExtraTrace(catchFilterTypeError);
+                this._reject(catchFilterTypeError);
+                return;
+            }
+        }
+        catchInstances.length = j;
+        fn = arguments[i];
+
+        this._resetTrace(this.caught);
+        var catchFilter = new CatchFilter(catchInstances, fn, this);
+        return this._then(void 0, catchFilter.doFilter, void 0,
+            catchFilter, void 0, this.caught);
+    }
+    return this._then(void 0, fn, void 0, void 0, void 0, this.caught);
+};
+
+Promise.prototype.then =
+function Promise$then(didFulfill, didReject, didProgress) {
+    return this._then(didFulfill, didReject, didProgress,
+        void 0, void 0, this.then);
+};
+
+
+Promise.prototype.done =
+function Promise$done(didFulfill, didReject, didProgress) {
+    var promise = this._then(didFulfill, didReject, didProgress,
+        void 0, void 0, this.done);
+    promise._setIsFinal();
+};
+
+Promise.prototype.spread = function Promise$spread(didFulfill, didReject) {
+    return this._then(didFulfill, didReject, void 0,
+        APPLY, void 0, this.spread);
+};
+
+Promise.prototype.isFulfilled = function Promise$isFulfilled() {
+    return (this._bitField & 268435456) > 0;
+};
+
+
+Promise.prototype.isRejected = function Promise$isRejected() {
+    return (this._bitField & 134217728) > 0;
+};
+
+Promise.prototype.isPending = function Promise$isPending() {
+    return !this.isResolved();
+};
+
+
+Promise.prototype.isResolved = function Promise$isResolved() {
+    return (this._bitField & 402653184) > 0;
+};
+
+
+Promise.prototype.isCancellable = function Promise$isCancellable() {
+    return !this.isResolved() &&
+        this._cancellable();
+};
+
+Promise.prototype.toJSON = function Promise$toJSON() {
+    var ret = {
+        isFulfilled: false,
+        isRejected: false,
+        fulfillmentValue: void 0,
+        rejectionReason: void 0
+    };
+    if (this.isFulfilled()) {
+        ret.fulfillmentValue = this._settledValue;
+        ret.isFulfilled = true;
+    }
+    else if (this.isRejected()) {
+        ret.rejectionReason = this._settledValue;
+        ret.isRejected = true;
+    }
+    return ret;
+};
+
+Promise.prototype.all = function Promise$all() {
+    return Promise$_all(this, true, this.all);
+};
+
+
+Promise.is = isPromise;
+
+function Promise$_all(promises, useBound, caller) {
+    return Promise$_CreatePromiseArray(
+        promises,
+        PromiseArray,
+        caller,
+        useBound === true && promises._isBound()
+            ? promises._boundTo
+            : void 0
+   ).promise();
+}
+Promise.all = function Promise$All(promises) {
+    return Promise$_all(promises, false, Promise.all);
+};
+
+Promise.join = function Promise$Join() {
+    var $_len = arguments.length;var args = new Array($_len); for(var $_i = 0; $_i < $_len; ++$_i) {args[$_i] = arguments[$_i];}
+    return Promise$_CreatePromiseArray(
+        args, PromiseArray, Promise.join, void 0).promise();
+};
+
+Promise.resolve = Promise.fulfilled =
+function Promise$Resolve(value, caller) {
+    var ret = new Promise(INTERNAL);
+    if (debugging) ret._setTrace(typeof caller === "function"
+        ? caller
+        : Promise.resolve, void 0);
+    if (ret._tryFollow(value)) {
+        return ret;
+    }
+    ret._cleanValues();
+    ret._setFulfilled();
+    ret._settledValue = value;
+    return ret;
+};
+
+Promise.reject = Promise.rejected = function Promise$Reject(reason) {
+    var ret = new Promise(INTERNAL);
+    if (debugging) ret._setTrace(Promise.reject, void 0);
+    markAsOriginatingFromRejection(reason);
+    ret._cleanValues();
+    ret._setRejected();
+    ret._settledValue = reason;
+    if (!canAttach(reason)) {
+        var trace = new Error(reason + "");
+        ret._setCarriedStackTrace(trace);
+    }
+    ret._ensurePossibleRejectionHandled();
+    return ret;
+};
+
+Promise.prototype.error = function Promise$_error(fn) {
+    return this.caught(originatesFromRejection, fn);
+};
+
+Promise.prototype._resolveFromSyncValue =
+function Promise$_resolveFromSyncValue(value, caller) {
+    if (value === errorObj) {
+        this._cleanValues();
+        this._setRejected();
+        this._settledValue = value.e;
+        this._ensurePossibleRejectionHandled();
+    }
+    else {
+        var maybePromise = Promise._cast(value, caller, void 0);
+        if (maybePromise instanceof Promise) {
+            this._follow(maybePromise);
+        }
+        else {
+            this._cleanValues();
+            this._setFulfilled();
+            this._settledValue = value;
+        }
+    }
+};
+
+Promise.method = function Promise$_Method(fn) {
+    if (typeof fn !== "function") {
+        throw new TypeError("fn must be a function");
+    }
+    return function Promise$_method() {
+        var value;
+        switch(arguments.length) {
+        case 0: value = tryCatch1(fn, this, void 0); break;
+        case 1: value = tryCatch1(fn, this, arguments[0]); break;
+        case 2: value = tryCatch2(fn, this, arguments[0], arguments[1]); break;
+        default:
+            var $_len = arguments.length;var args = new Array($_len); for(var $_i = 0; $_i < $_len; ++$_i) {args[$_i] = arguments[$_i];}
+            value = tryCatchApply(fn, args, this); break;
+        }
+        var ret = new Promise(INTERNAL);
+        if (debugging) ret._setTrace(Promise$_method, void 0);
+        ret._resolveFromSyncValue(value, Promise$_method);
+        return ret;
+    };
+};
+
+Promise.attempt = Promise["try"] = function Promise$_Try(fn, args, ctx) {
+
+    if (typeof fn !== "function") {
+        return apiRejection("fn must be a function");
+    }
+    var value = isArray(args)
+        ? tryCatchApply(fn, args, ctx)
+        : tryCatch1(fn, ctx, args);
+
+    var ret = new Promise(INTERNAL);
+    if (debugging) ret._setTrace(Promise.attempt, void 0);
+    ret._resolveFromSyncValue(value, Promise.attempt);
+    return ret;
+};
+
+Promise.defer = Promise.pending = function Promise$Defer(caller) {
+    var promise = new Promise(INTERNAL);
+    if (debugging) promise._setTrace(typeof caller === "function"
+                              ? caller : Promise.defer, void 0);
+    return new PromiseResolver(promise);
+};
+
+Promise.bind = function Promise$Bind(thisArg) {
+    var ret = new Promise(INTERNAL);
+    if (debugging) ret._setTrace(Promise.bind, void 0);
+    ret._setFulfilled();
+    ret._setBoundTo(thisArg);
+    return ret;
+};
+
+Promise.cast = function Promise$_Cast(obj, caller) {
+    if (typeof caller !== "function") {
+        caller = Promise.cast;
+    }
+    var ret = Promise._cast(obj, caller, void 0);
+    if (!(ret instanceof Promise)) {
+        return Promise.resolve(ret, caller);
+    }
+    return ret;
+};
+
+Promise.onPossiblyUnhandledRejection =
+function Promise$OnPossiblyUnhandledRejection(fn) {
+    if (typeof fn === "function") {
+        CapturedTrace.possiblyUnhandledRejection = fn;
+    }
+    else {
+        CapturedTrace.possiblyUnhandledRejection = void 0;
+    }
+};
+
+var debugging = false || !!(
+    typeof process !== "undefined" &&
+    typeof process.execPath === "string" &&
+    typeof process.env === "object" &&
+    (process.env["BLUEBIRD_DEBUG"] ||
+        process.env["NODE_ENV"] === "development")
+);
+
+
+Promise.longStackTraces = function Promise$LongStackTraces() {
+    if (async.haveItemsQueued() &&
+        debugging === false
+   ) {
+        throw new Error("cannot enable long stack traces after promises have been created");
+    }
+    debugging = CapturedTrace.isSupported();
+};
+
+Promise.hasLongStackTraces = function Promise$HasLongStackTraces() {
+    return debugging && CapturedTrace.isSupported();
+};
+
+Promise.prototype._setProxyHandlers =
+function Promise$_setProxyHandlers(receiver, promiseSlotValue) {
+    var index = this._length();
+
+    if (index >= 1048575 - 5) {
+        index = 0;
+        this._setLength(0);
+    }
+    if (index === 0) {
+        this._promise0 = promiseSlotValue;
+        this._receiver0 = receiver;
+    }
+    else {
+        var i = index - 5;
+        this[i + 3] = promiseSlotValue;
+        this[i + 4] = receiver;
+        this[i + 0] =
+        this[i + 1] =
+        this[i + 2] = void 0;
+    }
+    this._setLength(index + 5);
+};
+
+Promise.prototype._proxyPromiseArray =
+function Promise$_proxyPromiseArray(promiseArray, index) {
+    this._setProxyHandlers(promiseArray, index);
+};
+
+Promise.prototype._proxyPromise = function Promise$_proxyPromise(promise) {
+    promise._setProxied();
+    this._setProxyHandlers(promise, -1);
+};
+
+Promise.prototype._then =
+function Promise$_then(
+    didFulfill,
+    didReject,
+    didProgress,
+    receiver,
+    internalData,
+    caller
+) {
+    var haveInternalData = internalData !== void 0;
+    var ret = haveInternalData ? internalData : new Promise(INTERNAL);
+
+    if (debugging && !haveInternalData) {
+        var haveSameContext = this._peekContext() === this._traceParent;
+        ret._traceParent = haveSameContext ? this._traceParent : this;
+        ret._setTrace(typeof caller === "function"
+                ? caller
+                : this._then, this);
+    }
+
+    if (!haveInternalData && this._isBound()) {
+        ret._setBoundTo(this._boundTo);
+    }
+
+    var callbackIndex =
+        this._addCallbacks(didFulfill, didReject, didProgress, ret, receiver);
+
+    if (!haveInternalData && this._cancellable()) {
+        ret._setCancellable();
+        ret._cancellationParent = this;
+    }
+
+    if (this.isResolved()) {
+        this._queueSettleAt(callbackIndex);
+    }
+
+    return ret;
+};
+
+Promise.prototype._length = function Promise$_length() {
+    return this._bitField & 1048575;
+};
+
+Promise.prototype._isFollowingOrFulfilledOrRejected =
+function Promise$_isFollowingOrFulfilledOrRejected() {
+    return (this._bitField & 939524096) > 0;
+};
+
+Promise.prototype._isFollowing = function Promise$_isFollowing() {
+    return (this._bitField & 536870912) === 536870912;
+};
+
+Promise.prototype._setLength = function Promise$_setLength(len) {
+    this._bitField = (this._bitField & -1048576) |
+        (len & 1048575);
+};
+
+Promise.prototype._setFulfilled = function Promise$_setFulfilled() {
+    this._bitField = this._bitField | 268435456;
+};
+
+Promise.prototype._setRejected = function Promise$_setRejected() {
+    this._bitField = this._bitField | 134217728;
+};
+
+Promise.prototype._setFollowing = function Promise$_setFollowing() {
+    this._bitField = this._bitField | 536870912;
+};
+
+Promise.prototype._setIsFinal = function Promise$_setIsFinal() {
+    this._bitField = this._bitField | 33554432;
+};
+
+Promise.prototype._isFinal = function Promise$_isFinal() {
+    return (this._bitField & 33554432) > 0;
+};
+
+Promise.prototype._cancellable = function Promise$_cancellable() {
+    return (this._bitField & 67108864) > 0;
+};
+
+Promise.prototype._setCancellable = function Promise$_setCancellable() {
+    this._bitField = this._bitField | 67108864;
+};
+
+Promise.prototype._unsetCancellable = function Promise$_unsetCancellable() {
+    this._bitField = this._bitField & (~67108864);
+};
+
+Promise.prototype._setRejectionIsUnhandled =
+function Promise$_setRejectionIsUnhandled() {
+    this._bitField = this._bitField | 2097152;
+};
+
+Promise.prototype._unsetRejectionIsUnhandled =
+function Promise$_unsetRejectionIsUnhandled() {
+    this._bitField = this._bitField & (~2097152);
+};
+
+Promise.prototype._isRejectionUnhandled =
+function Promise$_isRejectionUnhandled() {
+    return (this._bitField & 2097152) > 0;
+};
+
+Promise.prototype._setCarriedStackTrace =
+function Promise$_setCarriedStackTrace(capturedTrace) {
+    this._bitField = this._bitField | 1048576;
+    this._fulfillmentHandler0 = capturedTrace;
+};
+
+Promise.prototype._unsetCarriedStackTrace =
+function Promise$_unsetCarriedStackTrace() {
+    this._bitField = this._bitField & (~1048576);
+    this._fulfillmentHandler0 = void 0;
+};
+
+Promise.prototype._isCarryingStackTrace =
+function Promise$_isCarryingStackTrace() {
+    return (this._bitField & 1048576) > 0;
+};
+
+Promise.prototype._getCarriedStackTrace =
+function Promise$_getCarriedStackTrace() {
+    return this._isCarryingStackTrace()
+        ? this._fulfillmentHandler0
+        : void 0;
+};
+
+Promise.prototype._receiverAt = function Promise$_receiverAt(index) {
+    var ret;
+    if (index === 0) {
+        ret = this._receiver0;
+    }
+    else {
+        ret = this[index + 4 - 5];
+    }
+    if (this._isBound() && ret === void 0) {
+        return this._boundTo;
+    }
+    return ret;
+};
+
+Promise.prototype._promiseAt = function Promise$_promiseAt(index) {
+    if (index === 0) return this._promise0;
+    return this[index + 3 - 5];
+};
+
+Promise.prototype._fulfillmentHandlerAt =
+function Promise$_fulfillmentHandlerAt(index) {
+    if (index === 0) return this._fulfillmentHandler0;
+    return this[index + 0 - 5];
+};
+
+Promise.prototype._rejectionHandlerAt =
+function Promise$_rejectionHandlerAt(index) {
+    if (index === 0) return this._rejectionHandler0;
+    return this[index + 1 - 5];
+};
+
+Promise.prototype._unsetAt = function Promise$_unsetAt(index) {
+     if (index === 0) {
+        this._rejectionHandler0 =
+        this._progressHandler0 =
+        this._promise0 =
+        this._receiver0 = void 0;
+        if (!this._isCarryingStackTrace()) {
+            this._fulfillmentHandler0 = void 0;
+        }
+    }
+    else {
+        this[index - 5 + 0] =
+        this[index - 5 + 1] =
+        this[index - 5 + 2] =
+        this[index - 5 + 3] =
+        this[index - 5 + 4] = void 0;
+    }
+};
+
+Promise.prototype._resolveFromResolver =
+function Promise$_resolveFromResolver(resolver) {
+    var promise = this;
+    var localDebugging = debugging;
+    if (localDebugging) {
+        this._setTrace(this._resolveFromResolver, void 0);
+        this._pushContext();
+    }
+    function Promise$_resolver(val) {
+        if (promise._tryFollow(val)) {
+            return;
+        }
+        promise._fulfill(val);
+    }
+    function Promise$_rejecter(val) {
+        var trace = canAttach(val) ? val : new Error(val + "");
+        promise._attachExtraTrace(trace);
+        markAsOriginatingFromRejection(val);
+        promise._reject(val, trace === val ? void 0 : trace);
+    }
+    var r = tryCatch2(resolver, void 0, Promise$_resolver, Promise$_rejecter);
+    if (localDebugging) this._popContext();
+
+    if (r !== void 0 && r === errorObj) {
+        var e = r.e;
+        var trace = canAttach(e) ? e : new Error(e + "");
+        promise._reject(e, trace);
+    }
+};
+
+Promise.prototype._addCallbacks = function Promise$_addCallbacks(
+    fulfill,
+    reject,
+    progress,
+    promise,
+    receiver
+) {
+    var index = this._length();
+
+    if (index >= 1048575 - 5) {
+        index = 0;
+        this._setLength(0);
+    }
+
+    if (index === 0) {
+        this._promise0 = promise;
+        if (receiver !== void 0) this._receiver0 = receiver;
+        if (typeof fulfill === "function" && !this._isCarryingStackTrace())
+            this._fulfillmentHandler0 = fulfill;
+        if (typeof reject === "function") this._rejectionHandler0 = reject;
+        if (typeof progress === "function") this._progressHandler0 = progress;
+    }
+    else {
+        var i = index - 5;
+        this[i + 3] = promise;
+        this[i + 4] = receiver;
+        this[i + 0] = typeof fulfill === "function"
+                                            ? fulfill : void 0;
+        this[i + 1] = typeof reject === "function"
+                                            ? reject : void 0;
+        this[i + 2] = typeof progress === "function"
+                                            ? progress : void 0;
+    }
+    this._setLength(index + 5);
+    return index;
+};
+
+
+
+Promise.prototype._setBoundTo = function Promise$_setBoundTo(obj) {
+    if (obj !== void 0) {
+        this._bitField = this._bitField | 8388608;
+        this._boundTo = obj;
+    }
+    else {
+        this._bitField = this._bitField & (~8388608);
+    }
+};
+
+Promise.prototype._isBound = function Promise$_isBound() {
+    return (this._bitField & 8388608) === 8388608;
+};
+
+Promise.prototype._spreadSlowCase =
+function Promise$_spreadSlowCase(targetFn, promise, values, boundTo) {
+    var promiseForAll =
+            Promise$_CreatePromiseArray
+                (values, PromiseArray, this._spreadSlowCase, boundTo)
+            .promise()
+            ._then(function() {
+                return targetFn.apply(boundTo, arguments);
+            }, void 0, void 0, APPLY, void 0, this._spreadSlowCase);
+
+    promise._follow(promiseForAll);
+};
+
+Promise.prototype._callSpread =
+function Promise$_callSpread(handler, promise, value, localDebugging) {
+    var boundTo = this._isBound() ? this._boundTo : void 0;
+    if (isArray(value)) {
+        var caller = this._settlePromiseFromHandler;
+        for (var i = 0, len = value.length; i < len; ++i) {
+            if (isPromise(Promise._cast(value[i], caller, void 0))) {
+                this._spreadSlowCase(handler, promise, value, boundTo);
+                return;
+            }
+        }
+    }
+    if (localDebugging) promise._pushContext();
+    return tryCatchApply(handler, value, boundTo);
+};
+
+Promise.prototype._callHandler =
+function Promise$_callHandler(
+    handler, receiver, promise, value, localDebugging) {
+    var x;
+    if (receiver === APPLY && !this.isRejected()) {
+        x = this._callSpread(handler, promise, value, localDebugging);
+    }
+    else {
+        if (localDebugging) promise._pushContext();
+        x = tryCatch1(handler, receiver, value);
+    }
+    if (localDebugging) promise._popContext();
+    return x;
+};
+
+Promise.prototype._settlePromiseFromHandler =
+function Promise$_settlePromiseFromHandler(
+    handler, receiver, value, promise
+) {
+    if (!isPromise(promise)) {
+        handler.call(receiver, value, promise);
+        return;
+    }
+
+    var localDebugging = debugging;
+    var x = this._callHandler(handler, receiver,
+                                promise, value, localDebugging);
+
+    if (promise._isFollowing()) return;
+
+    if (x === errorObj || x === promise || x === NEXT_FILTER) {
+        var err = x === promise
+                    ? makeSelfResolutionError()
+                    : x.e;
+        var trace = canAttach(err) ? err : new Error(err + "");
+        if (x !== NEXT_FILTER) promise._attachExtraTrace(trace);
+        promise._rejectUnchecked(err, trace);
+    }
+    else {
+        var castValue = Promise._cast(x,
+                    localDebugging ? this._settlePromiseFromHandler : void 0,
+                    promise);
+
+        if (isPromise(castValue)) {
+            if (castValue.isRejected() &&
+                !castValue._isCarryingStackTrace() &&
+                !canAttach(castValue._settledValue)) {
+                var trace = new Error(castValue._settledValue + "");
+                promise._attachExtraTrace(trace);
+                castValue._setCarriedStackTrace(trace);
+            }
+            promise._follow(castValue);
+            if (castValue._cancellable()) {
+                promise._cancellationParent = castValue;
+                promise._setCancellable();
+            }
+        }
+        else {
+            promise._fulfillUnchecked(x);
+        }
+    }
+};
+
+Promise.prototype._follow =
+function Promise$_follow(promise) {
+    this._setFollowing();
+
+    if (promise.isPending()) {
+        if (promise._cancellable() ) {
+            this._cancellationParent = promise;
+            this._setCancellable();
+        }
+        promise._proxyPromise(this);
+    }
+    else if (promise.isFulfilled()) {
+        this._fulfillUnchecked(promise._settledValue);
+    }
+    else {
+        this._rejectUnchecked(promise._settledValue,
+            promise._getCarriedStackTrace());
+    }
+
+    if (promise._isRejectionUnhandled()) promise._unsetRejectionIsUnhandled();
+
+    if (debugging &&
+        promise._traceParent == null) {
+        promise._traceParent = this;
+    }
+};
+
+Promise.prototype._tryFollow =
+function Promise$_tryFollow(value) {
+    if (this._isFollowingOrFulfilledOrRejected() ||
+        value === this) {
+        return false;
+    }
+    var maybePromise = Promise._cast(value, this._tryFollow, void 0);
+    if (!isPromise(maybePromise)) {
+        return false;
+    }
+    this._follow(maybePromise);
+    return true;
+};
+
+Promise.prototype._resetTrace = function Promise$_resetTrace(caller) {
+    if (debugging) {
+        var context = this._peekContext();
+        var isTopLevel = context === void 0;
+        this._trace = new CapturedTrace(
+            typeof caller === "function"
+            ? caller
+            : this._resetTrace,
+            isTopLevel
+       );
+    }
+};
+
+Promise.prototype._setTrace = function Promise$_setTrace(caller, parent) {
+    if (debugging) {
+        var context = this._peekContext();
+        this._traceParent = context;
+        var isTopLevel = context === void 0;
+        if (parent !== void 0 &&
+            parent._traceParent === context) {
+            this._trace = parent._trace;
+        }
+        else {
+            this._trace = new CapturedTrace(
+                typeof caller === "function"
+                ? caller
+                : this._setTrace,
+                isTopLevel
+           );
+        }
+    }
+    return this;
+};
+
+Promise.prototype._attachExtraTrace =
+function Promise$_attachExtraTrace(error) {
+    if (debugging) {
+        var promise = this;
+        var stack = error.stack;
+        stack = typeof stack === "string"
+            ? stack.split("\n") : [];
+        var headerLineCount = 1;
+
+        while(promise != null &&
+            promise._trace != null) {
+            stack = CapturedTrace.combine(
+                stack,
+                promise._trace.stack.split("\n")
+           );
+            promise = promise._traceParent;
+        }
+
+        var max = Error.stackTraceLimit + headerLineCount;
+        var len = stack.length;
+        if (len  > max) {
+            stack.length = max;
+        }
+        if (stack.length <= headerLineCount) {
+            error.stack = "(No stack trace)";
+        }
+        else {
+            error.stack = stack.join("\n");
+        }
+    }
+};
+
+Promise.prototype._cleanValues = function Promise$_cleanValues() {
+    if (this._cancellable()) {
+        this._cancellationParent = void 0;
+    }
+};
+
+Promise.prototype._fulfill = function Promise$_fulfill(value) {
+    if (this._isFollowingOrFulfilledOrRejected()) return;
+    this._fulfillUnchecked(value);
+};
+
+Promise.prototype._reject =
+function Promise$_reject(reason, carriedStackTrace) {
+    if (this._isFollowingOrFulfilledOrRejected()) return;
+    this._rejectUnchecked(reason, carriedStackTrace);
+};
+
+Promise.prototype._settlePromiseAt = function Promise$_settlePromiseAt(index) {
+    var handler = this.isFulfilled()
+        ? this._fulfillmentHandlerAt(index)
+        : this._rejectionHandlerAt(index);
+
+    var value = this._settledValue;
+    var receiver = this._receiverAt(index);
+    var promise = this._promiseAt(index);
+
+    if (typeof handler === "function") {
+        this._settlePromiseFromHandler(handler, receiver, value, promise);
+    }
+    else {
+        var done = false;
+        var isFulfilled = this.isFulfilled();
+        if (receiver !== void 0) {
+            if (receiver instanceof Promise &&
+                receiver._isProxied()) {
+                receiver._unsetProxied();
+
+                if (isFulfilled) receiver._fulfillUnchecked(value);
+                else receiver._rejectUnchecked(value,
+                    this._getCarriedStackTrace());
+                done = true;
+            }
+            else if (isPromiseArrayProxy(receiver, promise)) {
+
+                if (isFulfilled) receiver._promiseFulfilled(value, promise);
+                else receiver._promiseRejected(value, promise);
+
+                done = true;
+            }
+        }
+
+        if (!done) {
+
+            if (isFulfilled) promise._fulfill(value);
+            else promise._reject(value, this._getCarriedStackTrace());
+
+        }
+    }
+
+    if (index >= 256) {
+        this._queueGC();
+    }
+};
+
+Promise.prototype._isProxied = function Promise$_isProxied() {
+    return (this._bitField & 4194304) === 4194304;
+};
+
+Promise.prototype._setProxied = function Promise$_setProxied() {
+    this._bitField = this._bitField | 4194304;
+};
+
+Promise.prototype._unsetProxied = function Promise$_unsetProxied() {
+    this._bitField = this._bitField & (~4194304);
+};
+
+Promise.prototype._isGcQueued = function Promise$_isGcQueued() {
+    return (this._bitField & -1073741824) === -1073741824;
+};
+
+Promise.prototype._setGcQueued = function Promise$_setGcQueued() {
+    this._bitField = this._bitField | -1073741824;
+};
+
+Promise.prototype._unsetGcQueued = function Promise$_unsetGcQueued() {
+    this._bitField = this._bitField & (~-1073741824);
+};
+
+Promise.prototype._queueGC = function Promise$_queueGC() {
+    if (this._isGcQueued()) return;
+    this._setGcQueued();
+    async.invokeLater(this._gc, this, void 0);
+};
+
+Promise.prototype._gc = function Promise$gc() {
+    var len = this._length();
+    this._unsetAt(0);
+    for (var i = 0; i < len; i++) {
+        delete this[i];
+    }
+    this._setLength(0);
+    this._unsetGcQueued();
+};
+
+Promise.prototype._queueSettleAt = function Promise$_queueSettleAt(index) {
+    if (this._isRejectionUnhandled()) this._unsetRejectionIsUnhandled();
+    this._settlePromiseAt(index);
+};
+
+Promise.prototype._fulfillUnchecked =
+function Promise$_fulfillUnchecked(value) {
+    if (!this.isPending()) return;
+    if (value === this) {
+        var err = makeSelfResolutionError();
+        this._attachExtraTrace(err);
+        return this._rejectUnchecked(err, void 0);
+    }
+    this._cleanValues();
+    this._setFulfilled();
+    this._settledValue = value;
+    var len = this._length();
+
+    if (len > 0) {
+        this._fulfillPromises(len);
+    }
+};
+
+Promise.prototype._rejectUncheckedCheckError =
+function Promise$_rejectUncheckedCheckError(reason) {
+    var trace = canAttach(reason) ? reason : new Error(reason + "");
+    this._rejectUnchecked(reason, trace === reason ? void 0 : trace);
+};
+
+Promise.prototype._rejectUnchecked =
+function Promise$_rejectUnchecked(reason, trace) {
+    if (!this.isPending()) return;
+    if (reason === this) {
+        var err = makeSelfResolutionError();
+        this._attachExtraTrace(err);
+        return this._rejectUnchecked(err);
+    }
+    this._cleanValues();
+    this._setRejected();
+    this._settledValue = reason;
+
+    if (this._isFinal()) {
+        async.invokeLater(thrower, void 0, trace === void 0 ? reason : trace);
+        return;
+    }
+    var len = this._length();
+
+    if (trace !== void 0) this._setCarriedStackTrace(trace);
+
+    if (len > 0) {
+        this._rejectPromises(null);
+    }
+    else {
+        this._ensurePossibleRejectionHandled();
+    }
+};
+
+Promise.prototype._rejectPromises = function Promise$_rejectPromises() {
+    var len = this._length();
+    for (var i = 0; i < len; i+= 5) {
+        this._settlePromiseAt(i);
+    }
+    this._unsetCarriedStackTrace();
+};
+
+Promise.prototype._fulfillPromises = function Promise$_fulfillPromises(len) {
+    len = this._length();
+    for (var i = 0; i < len; i+= 5) {
+        this._settlePromiseAt(i);
+    }
+};
+
+Promise.prototype._ensurePossibleRejectionHandled =
+function Promise$_ensurePossibleRejectionHandled() {
+    this._setRejectionIsUnhandled();
+    if (CapturedTrace.possiblyUnhandledRejection !== void 0) {
+        async.invokeLater(this._notifyUnhandledRejection, this, void 0);
+    }
+};
+
+Promise.prototype._notifyUnhandledRejection =
+function Promise$_notifyUnhandledRejection() {
+    if (this._isRejectionUnhandled()) {
+        var reason = this._settledValue;
+        var trace = this._getCarriedStackTrace();
+
+        this._unsetRejectionIsUnhandled();
+
+        if (trace !== void 0) {
+            this._unsetCarriedStackTrace();
+            reason = trace;
+        }
+        if (typeof CapturedTrace.possiblyUnhandledRejection === "function") {
+            CapturedTrace.possiblyUnhandledRejection(reason, this);
+        }
+    }
+};
+
+var contextStack = [];
+Promise.prototype._peekContext = function Promise$_peekContext() {
+    var lastIndex = contextStack.length - 1;
+    if (lastIndex >= 0) {
+        return contextStack[lastIndex];
+    }
+    return void 0;
+
+};
+
+Promise.prototype._pushContext = function Promise$_pushContext() {
+    if (!debugging) return;
+    contextStack.push(this);
+};
+
+Promise.prototype._popContext = function Promise$_popContext() {
+    if (!debugging) return;
+    contextStack.pop();
+};
+
+function Promise$_CreatePromiseArray(
+    promises, PromiseArrayConstructor, caller, boundTo) {
+
+    var list = null;
+    if (isArray(promises)) {
+        list = promises;
+    }
+    else {
+        list = Promise._cast(promises, caller, void 0);
+        if (list !== promises) {
+            list._setBoundTo(boundTo);
+        }
+        else if (!isPromise(list)) {
+            list = null;
+        }
+    }
+    if (list !== null) {
+        return new PromiseArrayConstructor(
+            list,
+            typeof caller === "function"
+                ? caller
+                : Promise$_CreatePromiseArray,
+            boundTo
+       );
+    }
+    return {
+        promise: function() {return apiRejection("expecting an array, a promise or a thenable");}
+    };
+}
+
+var old = global.Promise;
+
+Promise.noConflict = function() {
+    if (global.Promise === Promise) {
+        global.Promise = old;
+    }
+    return Promise;
+};
+
+if (!CapturedTrace.isSupported()) {
+    Promise.longStackTraces = function(){};
+    debugging = false;
+}
+
+Promise._makeSelfResolutionError = makeSelfResolutionError;
+require("./finally.js")(Promise, NEXT_FILTER);
+require("./direct_resolve.js")(Promise);
+require("./thenables.js")(Promise, INTERNAL);
+Promise.RangeError = RangeError;
+Promise.CancellationError = CancellationError;
+Promise.TimeoutError = TimeoutError;
+Promise.TypeError = TypeError;
+Promise.RejectionError = RejectionError;
+
+util.toFastProperties(Promise);
+util.toFastProperties(Promise.prototype);
+require('./timers.js')(Promise,INTERNAL);
+require('./synchronous_inspection.js')(Promise);
+require('./any.js')(Promise,Promise$_CreatePromiseArray,PromiseArray);
+require('./race.js')(Promise,INTERNAL);
+require('./call_get.js')(Promise);
+require('./filter.js')(Promise,Promise$_CreatePromiseArray,PromiseArray,apiRejection);
+require('./generators.js')(Promise,apiRejection,INTERNAL);
+require('./map.js')(Promise,Promise$_CreatePromiseArray,PromiseArray,apiRejection);
+require('./nodeify.js')(Promise);
+require('./promisify.js')(Promise,INTERNAL);
+require('./props.js')(Promise,PromiseArray);
+require('./reduce.js')(Promise,Promise$_CreatePromiseArray,PromiseArray,apiRejection,INTERNAL);
+require('./settle.js')(Promise,Promise$_CreatePromiseArray,PromiseArray);
+require('./some.js')(Promise,Promise$_CreatePromiseArray,PromiseArray,apiRejection);
+require('./progress.js')(Promise,isPromiseArrayProxy);
+require('./cancel.js')(Promise,INTERNAL);
+
+Promise.prototype = Promise.prototype;
+return Promise;
+
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/promise_array.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/promise_array.js
new file mode 100644
index 0000000..6d43606
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/promise_array.js
@@ -0,0 +1,233 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, INTERNAL) {
+var canAttach = require("./errors.js").canAttach;
+var util = require("./util.js");
+var async = require("./async.js");
+var hasOwn = {}.hasOwnProperty;
+var isArray = util.isArray;
+
+function toResolutionValue(val) {
+    switch(val) {
+    case -1: return void 0;
+    case -2: return [];
+    case -3: return {};
+    }
+}
+
+function PromiseArray(values, caller, boundTo) {
+    var promise = this._promise = new Promise(INTERNAL);
+    var parent = void 0;
+    if (Promise.is(values)) {
+        parent = values;
+        if (values._cancellable()) {
+            promise._setCancellable();
+            promise._cancellationParent = values;
+        }
+        if (values._isBound()) {
+            promise._setBoundTo(boundTo);
+        }
+    }
+    promise._setTrace(caller, parent);
+    this._values = values;
+    this._length = 0;
+    this._totalResolved = 0;
+    this._init(void 0, -2);
+}
+PromiseArray.PropertiesPromiseArray = function() {};
+
+PromiseArray.prototype.length = function PromiseArray$length() {
+    return this._length;
+};
+
+PromiseArray.prototype.promise = function PromiseArray$promise() {
+    return this._promise;
+};
+
+PromiseArray.prototype._init =
+function PromiseArray$_init(_, resolveValueIfEmpty) {
+    var values = this._values;
+    if (Promise.is(values)) {
+        if (values.isFulfilled()) {
+            values = values._settledValue;
+            if (!isArray(values)) {
+                var err = new Promise.TypeError("expecting an array, a promise or a thenable");
+                this.__hardReject__(err);
+                return;
+            }
+            this._values = values;
+        }
+        else if (values.isPending()) {
+            values._then(
+                this._init,
+                this._reject,
+                void 0,
+                this,
+                resolveValueIfEmpty,
+                this.constructor
+           );
+            return;
+        }
+        else {
+            this._reject(values._settledValue);
+            return;
+        }
+    }
+
+    if (values.length === 0) {
+        this._resolve(toResolutionValue(resolveValueIfEmpty));
+        return;
+    }
+    var len = values.length;
+    var newLen = len;
+    var newValues;
+    if (this instanceof PromiseArray.PropertiesPromiseArray) {
+        newValues = this._values;
+    }
+    else {
+        newValues = new Array(len);
+    }
+    var isDirectScanNeeded = false;
+    for (var i = 0; i < len; ++i) {
+        var promise = values[i];
+        if (promise === void 0 && !hasOwn.call(values, i)) {
+            newLen--;
+            continue;
+        }
+        var maybePromise = Promise._cast(promise, void 0, void 0);
+        if (maybePromise instanceof Promise) {
+            if (maybePromise.isPending()) {
+                maybePromise._proxyPromiseArray(this, i);
+            }
+            else {
+                maybePromise._unsetRejectionIsUnhandled();
+                isDirectScanNeeded = true;
+            }
+        }
+        else {
+            isDirectScanNeeded = true;
+        }
+        newValues[i] = maybePromise;
+    }
+    if (newLen === 0) {
+        if (resolveValueIfEmpty === -2) {
+            this._resolve(newValues);
+        }
+        else {
+            this._resolve(toResolutionValue(resolveValueIfEmpty));
+        }
+        return;
+    }
+    this._values = newValues;
+    this._length = newLen;
+    if (isDirectScanNeeded) {
+        var scanMethod = newLen === len
+            ? this._scanDirectValues
+            : this._scanDirectValuesHoled;
+        scanMethod.call(this, len);
+    }
+};
+
+PromiseArray.prototype._settlePromiseAt =
+function PromiseArray$_settlePromiseAt(index) {
+    var value = this._values[index];
+    if (!Promise.is(value)) {
+        this._promiseFulfilled(value, index);
+    }
+    else if (value.isFulfilled()) {
+        this._promiseFulfilled(value._settledValue, index);
+    }
+    else if (value.isRejected()) {
+        this._promiseRejected(value._settledValue, index);
+    }
+};
+
+PromiseArray.prototype._scanDirectValuesHoled =
+function PromiseArray$_scanDirectValuesHoled(len) {
+    for (var i = 0; i < len; ++i) {
+        if (this._isResolved()) {
+            break;
+        }
+        if (hasOwn.call(this._values, i)) {
+            this._settlePromiseAt(i);
+        }
+    }
+};
+
+PromiseArray.prototype._scanDirectValues =
+function PromiseArray$_scanDirectValues(len) {
+    for (var i = 0; i < len; ++i) {
+        if (this._isResolved()) {
+            break;
+        }
+        this._settlePromiseAt(i);
+    }
+};
+
+PromiseArray.prototype._isResolved = function PromiseArray$_isResolved() {
+    return this._values === null;
+};
+
+PromiseArray.prototype._resolve = function PromiseArray$_resolve(value) {
+    this._values = null;
+    this._promise._fulfill(value);
+};
+
+PromiseArray.prototype.__hardReject__ =
+PromiseArray.prototype._reject = function PromiseArray$_reject(reason) {
+    this._values = null;
+    var trace = canAttach(reason) ? reason : new Error(reason + "");
+    this._promise._attachExtraTrace(trace);
+    this._promise._reject(reason, trace);
+};
+
+PromiseArray.prototype._promiseProgressed =
+function PromiseArray$_promiseProgressed(progressValue, index) {
+    if (this._isResolved()) return;
+    this._promise._progress({
+        index: index,
+        value: progressValue
+    });
+};
+
+
+PromiseArray.prototype._promiseFulfilled =
+function PromiseArray$_promiseFulfilled(value, index) {
+    if (this._isResolved()) return;
+    this._values[index] = value;
+    var totalResolved = ++this._totalResolved;
+    if (totalResolved >= this._length) {
+        this._resolve(this._values);
+    }
+};
+
+PromiseArray.prototype._promiseRejected =
+function PromiseArray$_promiseRejected(reason, index) {
+    if (this._isResolved()) return;
+    this._totalResolved++;
+    this._reject(reason);
+};
+
+return PromiseArray;
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/promise_inspection.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/promise_inspection.js
new file mode 100644
index 0000000..0aa233b
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/promise_inspection.js
@@ -0,0 +1,66 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var TypeError = require("./errors.js").TypeError;
+
+function PromiseInspection(promise) {
+    if (promise !== void 0) {
+        this._bitField = promise._bitField;
+        this._settledValue = promise.isResolved()
+            ? promise._settledValue
+            : void 0;
+    }
+    else {
+        this._bitField = 0;
+        this._settledValue = void 0;
+    }
+}
+PromiseInspection.prototype.isFulfilled =
+function PromiseInspection$isFulfilled() {
+    return (this._bitField & 268435456) > 0;
+};
+
+PromiseInspection.prototype.isRejected =
+function PromiseInspection$isRejected() {
+    return (this._bitField & 134217728) > 0;
+};
+
+PromiseInspection.prototype.isPending = function PromiseInspection$isPending() {
+    return (this._bitField & 402653184) === 0;
+};
+
+PromiseInspection.prototype.value = function PromiseInspection$value() {
+    if (!this.isFulfilled()) {
+        throw new TypeError("cannot get fulfillment value of a non-fulfilled promise");
+    }
+    return this._settledValue;
+};
+
+PromiseInspection.prototype.error = function PromiseInspection$error() {
+    if (!this.isRejected()) {
+        throw new TypeError("cannot get rejection reason of a non-rejected promise");
+    }
+    return this._settledValue;
+};
+
+module.exports = PromiseInspection;
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/promise_resolver.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/promise_resolver.js
new file mode 100644
index 0000000..ea14069
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/promise_resolver.js
@@ -0,0 +1,152 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var util = require("./util.js");
+var maybeWrapAsError = util.maybeWrapAsError;
+var errors = require("./errors.js");
+var TimeoutError = errors.TimeoutError;
+var RejectionError = errors.RejectionError;
+var async = require("./async.js");
+var haveGetters = util.haveGetters;
+var es5 = require("./es5.js");
+
+function isUntypedError(obj) {
+    return obj instanceof Error &&
+        es5.getPrototypeOf(obj) === Error.prototype;
+}
+
+function wrapAsRejectionError(obj) {
+    var ret;
+    if (isUntypedError(obj)) {
+        ret = new RejectionError(obj);
+    }
+    else {
+        ret = obj;
+    }
+    errors.markAsOriginatingFromRejection(ret);
+    return ret;
+}
+
+function nodebackForPromise(promise) {
+    function PromiseResolver$_callback(err, value) {
+        if (promise === null) return;
+
+        if (err) {
+            var wrapped = wrapAsRejectionError(maybeWrapAsError(err));
+            promise._attachExtraTrace(wrapped);
+            promise._reject(wrapped);
+        }
+        else {
+            if (arguments.length > 2) {
+                var $_len = arguments.length;var args = new Array($_len - 1); for(var $_i = 1; $_i < $_len; ++$_i) {args[$_i - 1] = arguments[$_i];}
+                promise._fulfill(args);
+            }
+            else {
+                promise._fulfill(value);
+            }
+        }
+
+        promise = null;
+    }
+    return PromiseResolver$_callback;
+}
+
+
+var PromiseResolver;
+if (!haveGetters) {
+    PromiseResolver = function PromiseResolver(promise) {
+        this.promise = promise;
+        this.asCallback = nodebackForPromise(promise);
+        this.callback = this.asCallback;
+    };
+}
+else {
+    PromiseResolver = function PromiseResolver(promise) {
+        this.promise = promise;
+    };
+}
+if (haveGetters) {
+    var prop = {
+        get: function() {
+            return nodebackForPromise(this.promise);
+        }
+    };
+    es5.defineProperty(PromiseResolver.prototype, "asCallback", prop);
+    es5.defineProperty(PromiseResolver.prototype, "callback", prop);
+}
+
+PromiseResolver._nodebackForPromise = nodebackForPromise;
+
+PromiseResolver.prototype.toString = function PromiseResolver$toString() {
+    return "[object PromiseResolver]";
+};
+
+PromiseResolver.prototype.resolve =
+PromiseResolver.prototype.fulfill = function PromiseResolver$resolve(value) {
+    var promise = this.promise;
+    if (promise._tryFollow(value)) {
+        return;
+    }
+    promise._fulfill(value);
+};
+
+PromiseResolver.prototype.reject = function PromiseResolver$reject(reason) {
+    var promise = this.promise;
+    errors.markAsOriginatingFromRejection(reason);
+    var trace = errors.canAttach(reason) ? reason : new Error(reason + "");
+    promise._attachExtraTrace(trace);
+    promise._reject(reason);
+    if (trace !== reason) {
+        this._setCarriedStackTrace(trace);
+    }
+};
+
+PromiseResolver.prototype.progress =
+function PromiseResolver$progress(value) {
+    this.promise._progress(value);
+};
+
+PromiseResolver.prototype.cancel = function PromiseResolver$cancel() {
+    this.promise.cancel((void 0));
+};
+
+PromiseResolver.prototype.timeout = function PromiseResolver$timeout() {
+    this.reject(new TimeoutError("timeout"));
+};
+
+PromiseResolver.prototype.isResolved = function PromiseResolver$isResolved() {
+    return this.promise.isResolved();
+};
+
+PromiseResolver.prototype.toJSON = function PromiseResolver$toJSON() {
+    return this.promise.toJSON();
+};
+
+PromiseResolver.prototype._setCarriedStackTrace =
+function PromiseResolver$_setCarriedStackTrace(trace) {
+    if (this.promise.isRejected()) {
+        this.promise._setCarriedStackTrace(trace);
+    }
+};
+
+module.exports = PromiseResolver;
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/promise_spawn.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/promise_spawn.js
new file mode 100644
index 0000000..2d6525f
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/promise_spawn.js
@@ -0,0 +1,131 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, INTERNAL) {
+var errors = require("./errors.js");
+var TypeError = errors.TypeError;
+var util = require("./util.js");
+var isArray = util.isArray;
+var errorObj = util.errorObj;
+var tryCatch1 = util.tryCatch1;
+var yieldHandlers = [];
+
+function promiseFromYieldHandler(value) {
+    var _yieldHandlers = yieldHandlers;
+    var _errorObj = errorObj;
+    var _Promise = Promise;
+    var len = _yieldHandlers.length;
+    for (var i = 0; i < len; ++i) {
+        var result = tryCatch1(_yieldHandlers[i], void 0, value);
+        if (result === _errorObj) {
+            return _Promise.reject(_errorObj.e);
+        }
+        var maybePromise = _Promise._cast(result,
+            promiseFromYieldHandler, void 0);
+        if (maybePromise instanceof _Promise) return maybePromise;
+    }
+    return null;
+}
+
+function PromiseSpawn(generatorFunction, receiver, caller) {
+    var promise = this._promise = new Promise(INTERNAL);
+    promise._setTrace(caller, void 0);
+    this._generatorFunction = generatorFunction;
+    this._receiver = receiver;
+    this._generator = void 0;
+}
+
+PromiseSpawn.prototype.promise = function PromiseSpawn$promise() {
+    return this._promise;
+};
+
+PromiseSpawn.prototype._run = function PromiseSpawn$_run() {
+    this._generator = this._generatorFunction.call(this._receiver);
+    this._receiver =
+        this._generatorFunction = void 0;
+    this._next(void 0);
+};
+
+PromiseSpawn.prototype._continue = function PromiseSpawn$_continue(result) {
+    if (result === errorObj) {
+        this._generator = void 0;
+        var trace = errors.canAttach(result.e)
+            ? result.e : new Error(result.e + "");
+        this._promise._attachExtraTrace(trace);
+        this._promise._reject(result.e, trace);
+        return;
+    }
+
+    var value = result.value;
+    if (result.done === true) {
+        this._generator = void 0;
+        if (!this._promise._tryFollow(value)) {
+            this._promise._fulfill(value);
+        }
+    }
+    else {
+        var maybePromise = Promise._cast(value, PromiseSpawn$_continue, void 0);
+        if (!(maybePromise instanceof Promise)) {
+            if (isArray(maybePromise)) {
+                maybePromise = Promise.all(maybePromise);
+            }
+            else {
+                maybePromise = promiseFromYieldHandler(maybePromise);
+            }
+            if (maybePromise === null) {
+                this._throw(new TypeError("A value was yielded that could not be treated as a promise"));
+                return;
+            }
+        }
+        maybePromise._then(
+            this._next,
+            this._throw,
+            void 0,
+            this,
+            null,
+            void 0
+       );
+    }
+};
+
+PromiseSpawn.prototype._throw = function PromiseSpawn$_throw(reason) {
+    if (errors.canAttach(reason))
+        this._promise._attachExtraTrace(reason);
+    this._continue(
+        tryCatch1(this._generator["throw"], this._generator, reason)
+   );
+};
+
+PromiseSpawn.prototype._next = function PromiseSpawn$_next(value) {
+    this._continue(
+        tryCatch1(this._generator.next, this._generator, value)
+   );
+};
+
+PromiseSpawn.addYieldHandler = function PromiseSpawn$AddYieldHandler(fn) {
+    if (typeof fn !== "function") throw new TypeError("fn must be a function");
+    yieldHandlers.push(fn);
+};
+
+return PromiseSpawn;
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/promisify.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/promisify.js
new file mode 100644
index 0000000..a550fd0
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/promisify.js
@@ -0,0 +1,278 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, INTERNAL) {
+var THIS = {};
+var util = require("./util.js");
+var es5 = require("./es5.js");
+var nodebackForPromise = require("./promise_resolver.js")
+    ._nodebackForPromise;
+var withAppended = util.withAppended;
+var maybeWrapAsError = util.maybeWrapAsError;
+var canEvaluate = util.canEvaluate;
+var notEnumerableProp = util.notEnumerableProp;
+var deprecated = util.deprecated;
+var roriginal = new RegExp("__beforePromisified__" + "$");
+var hasProp = {}.hasOwnProperty;
+function isPromisified(fn) {
+    return fn.__isPromisified__ === true;
+}
+var inheritedMethods = (function() {
+    if (es5.isES5) {
+        var create = Object.create;
+        var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
+        return function(cur) {
+            var original = cur;
+            var ret = [];
+            var visitedKeys = create(null);
+            while (cur !== null) {
+                var keys = es5.keys(cur);
+                for (var i = 0, len = keys.length; i < len; ++i) {
+                    var key = keys[i];
+                    if (visitedKeys[key] ||
+                        roriginal.test(key) ||
+                        hasProp.call(original, key + "__beforePromisified__")
+                   ) {
+                        continue;
+                    }
+                    visitedKeys[key] = true;
+                    var desc = getOwnPropertyDescriptor(cur, key);
+                    if (desc != null &&
+                        typeof desc.value === "function" &&
+                        !isPromisified(desc.value)) {
+                        ret.push(key, desc.value);
+                    }
+                }
+                cur = es5.getPrototypeOf(cur);
+            }
+            return ret;
+        };
+    }
+    else {
+        return function(obj) {
+            var ret = [];
+            /*jshint forin:false */
+            for (var key in obj) {
+                if (roriginal.test(key) ||
+                    hasProp.call(obj, key + "__beforePromisified__")) {
+                    continue;
+                }
+                var fn = obj[key];
+                if (typeof fn === "function" &&
+                    !isPromisified(fn)) {
+                    ret.push(key, fn);
+                }
+            }
+            return ret;
+        };
+    }
+})();
+
+function switchCaseArgumentOrder(likelyArgumentCount) {
+    var ret = [likelyArgumentCount];
+    var min = Math.max(0, likelyArgumentCount - 1 - 5);
+    for(var i = likelyArgumentCount - 1; i >= min; --i) {
+        if (i === likelyArgumentCount) continue;
+        ret.push(i);
+    }
+    for(var i = likelyArgumentCount + 1; i <= 5; ++i) {
+        ret.push(i);
+    }
+    return ret;
+}
+
+function parameterDeclaration(parameterCount) {
+    var ret = new Array(parameterCount);
+    for(var i = 0; i < ret.length; ++i) {
+        ret[i] = "_arg" + i;
+    }
+    return ret.join(", ");
+}
+
+function parameterCount(fn) {
+    if (typeof fn.length === "number") {
+        return Math.max(Math.min(fn.length, 1023 + 1), 0);
+    }
+    return 0;
+}
+
+function propertyAccess(id) {
+    var rident = /^[a-z$_][a-z$_0-9]*$/i;
+
+    if (rident.test(id)) {
+        return "." + id;
+    }
+    else return "['" + id.replace(/(['\\])/g, "\\$1") + "']";
+}
+
+function makeNodePromisifiedEval(callback, receiver, originalName, fn) {
+    var newParameterCount = Math.max(0, parameterCount(fn) - 1);
+    var argumentOrder = switchCaseArgumentOrder(newParameterCount);
+
+    var callbackName = (typeof originalName === "string" ?
+        originalName + "Async" :
+        "promisified");
+
+    function generateCallForArgumentCount(count) {
+        var args = new Array(count);
+        for (var i = 0, len = args.length; i < len; ++i) {
+            args[i] = "arguments[" + i + "]";
+        }
+        var comma = count > 0 ? "," : "";
+
+        if (typeof callback === "string" &&
+            receiver === THIS) {
+            return "this" + propertyAccess(callback) + "("+args.join(",") +
+                comma +" fn);"+
+                "break;";
+        }
+        return (receiver === void 0
+            ? "callback("+args.join(",")+ comma +" fn);"
+            : "callback.call("+(receiver === THIS
+                ? "this"
+                : "receiver")+", "+args.join(",") + comma + " fn);") +
+        "break;";
+    }
+
+    function generateArgumentSwitchCase() {
+        var ret = "";
+        for(var i = 0; i < argumentOrder.length; ++i) {
+            ret += "case " + argumentOrder[i] +":" +
+                generateCallForArgumentCount(argumentOrder[i]);
+        }
+        ret += "default: var args = new Array(len + 1);" +
+            "var i = 0;" +
+            "for (var i = 0; i < len; ++i) { " +
+            "   args[i] = arguments[i];" +
+            "}" +
+            "args[i] = fn;" +
+
+            (typeof callback === "string"
+            ? "this" + propertyAccess(callback) + ".apply("
+            : "callback.apply(") +
+
+            (receiver === THIS ? "this" : "receiver") +
+            ", args); break;";
+        return ret;
+    }
+
+    return new Function("Promise", "callback", "receiver",
+            "withAppended", "maybeWrapAsError", "nodebackForPromise",
+            "INTERNAL",
+        "var ret = function " + callbackName +
+        "(" + parameterDeclaration(newParameterCount) + ") {\"use strict\";" +
+        "var len = arguments.length;" +
+        "var promise = new Promise(INTERNAL);"+
+        "promise._setTrace(" + callbackName + ", void 0);" +
+        "var fn = nodebackForPromise(promise);"+
+        "try {" +
+        "switch(len) {" +
+        generateArgumentSwitchCase() +
+        "}" +
+        "}" +
+        "catch(e){ " +
+        "var wrapped = maybeWrapAsError(e);" +
+        "promise._attachExtraTrace(wrapped);" +
+        "promise._reject(wrapped);" +
+        "}" +
+        "return promise;" +
+        "" +
+        "}; ret.__isPromisified__ = true; return ret;"
+   )(Promise, callback, receiver, withAppended,
+        maybeWrapAsError, nodebackForPromise, INTERNAL);
+}
+
+function makeNodePromisifiedClosure(callback, receiver) {
+    function promisified() {
+        var _receiver = receiver;
+        if (receiver === THIS) _receiver = this;
+        if (typeof callback === "string") {
+            callback = _receiver[callback];
+        }
+        var promise = new Promise(INTERNAL);
+        promise._setTrace(promisified, void 0);
+        var fn = nodebackForPromise(promise);
+        try {
+            callback.apply(_receiver, withAppended(arguments, fn));
+        }
+        catch(e) {
+            var wrapped = maybeWrapAsError(e);
+            promise._attachExtraTrace(wrapped);
+            promise._reject(wrapped);
+        }
+        return promise;
+    }
+    promisified.__isPromisified__ = true;
+    return promisified;
+}
+
+var makeNodePromisified = canEvaluate
+    ? makeNodePromisifiedEval
+    : makeNodePromisifiedClosure;
+
+function _promisify(callback, receiver, isAll) {
+    if (isAll) {
+        var methods = inheritedMethods(callback);
+        for (var i = 0, len = methods.length; i < len; i+= 2) {
+            var key = methods[i];
+            var fn = methods[i+1];
+            var originalKey = key + "__beforePromisified__";
+            var promisifiedKey = key + "Async";
+            notEnumerableProp(callback, originalKey, fn);
+            callback[promisifiedKey] =
+                makeNodePromisified(originalKey, THIS,
+                    key, fn);
+        }
+        util.toFastProperties(callback);
+        return callback;
+    }
+    else {
+        return makeNodePromisified(callback, receiver, void 0, callback);
+    }
+}
+
+Promise.promisify = function Promise$Promisify(fn, receiver) {
+    if (typeof fn === "object" && fn !== null) {
+        deprecated("Promise.promisify for promisifying entire objects is deprecated. Use Promise.promisifyAll instead.");
+        return _promisify(fn, receiver, true);
+    }
+    if (typeof fn !== "function") {
+        throw new TypeError("fn must be a function");
+    }
+    if (isPromisified(fn)) {
+        return fn;
+    }
+    return _promisify(
+        fn,
+        arguments.length < 2 ? THIS : receiver,
+        false);
+};
+
+Promise.promisifyAll = function Promise$PromisifyAll(target) {
+    if (typeof target !== "function" && typeof target !== "object") {
+        throw new TypeError("the target of promisifyAll must be an object or a function");
+    }
+    return _promisify(target, void 0, true);
+};
+};
+
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/properties_promise_array.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/properties_promise_array.js
new file mode 100644
index 0000000..85f5990
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/properties_promise_array.js
@@ -0,0 +1,77 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, PromiseArray) {
+var util = require("./util.js");
+var inherits = util.inherits;
+var es5 = require("./es5.js");
+
+function PropertiesPromiseArray(obj, caller, boundTo) {
+    var keys = es5.keys(obj);
+    var values = new Array(keys.length);
+    for (var i = 0, len = values.length; i < len; ++i) {
+        values[i] = obj[keys[i]];
+    }
+    this.constructor$(values, caller, boundTo);
+    if (!this._isResolved()) {
+        for (var i = 0, len = keys.length; i < len; ++i) {
+            values.push(keys[i]);
+        }
+    }
+}
+inherits(PropertiesPromiseArray, PromiseArray);
+
+PropertiesPromiseArray.prototype._init =
+function PropertiesPromiseArray$_init() {
+    this._init$(void 0, -3) ;
+};
+
+PropertiesPromiseArray.prototype._promiseFulfilled =
+function PropertiesPromiseArray$_promiseFulfilled(value, index) {
+    if (this._isResolved()) return;
+    this._values[index] = value;
+    var totalResolved = ++this._totalResolved;
+    if (totalResolved >= this._length) {
+        var val = {};
+        var keyOffset = this.length();
+        for (var i = 0, len = this.length(); i < len; ++i) {
+            val[this._values[i + keyOffset]] = this._values[i];
+        }
+        this._resolve(val);
+    }
+};
+
+PropertiesPromiseArray.prototype._promiseProgressed =
+function PropertiesPromiseArray$_promiseProgressed(value, index) {
+    if (this._isResolved()) return;
+
+    this._promise._progress({
+        key: this._values[index + this.length()],
+        value: value
+    });
+};
+
+PromiseArray.PropertiesPromiseArray = PropertiesPromiseArray;
+
+return PropertiesPromiseArray;
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/props.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/props.js
new file mode 100644
index 0000000..3cdba43
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/props.js
@@ -0,0 +1,65 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, PromiseArray) {
+    var PropertiesPromiseArray = require("./properties_promise_array.js")(
+        Promise, PromiseArray);
+    var util = require("./util.js");
+    var apiRejection = require("./errors_api_rejection")(Promise);
+    var isObject = util.isObject;
+
+    function Promise$_Props(promises, useBound, caller) {
+        var ret;
+        var castValue = Promise._cast(promises, caller, void 0);
+
+        if (!isObject(castValue)) {
+            return apiRejection("cannot await properties of a non-object");
+        }
+        else if (Promise.is(castValue)) {
+            ret = castValue._then(Promise.props, void 0, void 0,
+                            void 0, void 0, caller);
+        }
+        else {
+            ret = new PropertiesPromiseArray(
+                castValue,
+                caller,
+                useBound === true && castValue._isBound()
+                            ? castValue._boundTo
+                            : void 0
+           ).promise();
+            useBound = false;
+        }
+        if (useBound === true && castValue._isBound()) {
+            ret._setBoundTo(castValue._boundTo);
+        }
+        return ret;
+    }
+
+    Promise.prototype.props = function Promise$props() {
+        return Promise$_Props(this, true, this.props);
+    };
+
+    Promise.props = function Promise$Props(promises) {
+        return Promise$_Props(promises, false, Promise.props);
+    };
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/queue.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/queue.js
new file mode 100644
index 0000000..bbd3f1b
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/queue.js
@@ -0,0 +1,135 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+function arrayCopy(src, srcIndex, dst, dstIndex, len) {
+    for (var j = 0; j < len; ++j) {
+        dst[j + dstIndex] = src[j + srcIndex];
+    }
+}
+
+function pow2AtLeast(n) {
+    n = n >>> 0;
+    n = n - 1;
+    n = n | (n >> 1);
+    n = n | (n >> 2);
+    n = n | (n >> 4);
+    n = n | (n >> 8);
+    n = n | (n >> 16);
+    return n + 1;
+}
+
+function getCapacity(capacity) {
+    if (typeof capacity !== "number") return 16;
+    return pow2AtLeast(
+        Math.min(
+            Math.max(16, capacity), 1073741824)
+   );
+}
+
+function Queue(capacity) {
+    this._capacity = getCapacity(capacity);
+    this._length = 0;
+    this._front = 0;
+    this._makeCapacity();
+}
+
+Queue.prototype._willBeOverCapacity =
+function Queue$_willBeOverCapacity(size) {
+    return this._capacity < size;
+};
+
+Queue.prototype._pushOne = function Queue$_pushOne(arg) {
+    var length = this.length();
+    this._checkCapacity(length + 1);
+    var i = (this._front + length) & (this._capacity - 1);
+    this[i] = arg;
+    this._length = length + 1;
+};
+
+Queue.prototype.push = function Queue$push(fn, receiver, arg) {
+    var length = this.length() + 3;
+    if (this._willBeOverCapacity(length)) {
+        this._pushOne(fn);
+        this._pushOne(receiver);
+        this._pushOne(arg);
+        return;
+    }
+    var j = this._front + length - 3;
+    this._checkCapacity(length);
+    var wrapMask = this._capacity - 1;
+    this[(j + 0) & wrapMask] = fn;
+    this[(j + 1) & wrapMask] = receiver;
+    this[(j + 2) & wrapMask] = arg;
+    this._length = length;
+};
+
+Queue.prototype.shift = function Queue$shift() {
+    var front = this._front,
+        ret = this[front];
+
+    this[front] = void 0;
+    this._front = (front + 1) & (this._capacity - 1);
+    this._length--;
+    return ret;
+};
+
+Queue.prototype.length = function Queue$length() {
+    return this._length;
+};
+
+Queue.prototype._makeCapacity = function Queue$_makeCapacity() {
+    var len = this._capacity;
+    for (var i = 0; i < len; ++i) {
+        this[i] = void 0;
+    }
+};
+
+Queue.prototype._checkCapacity = function Queue$_checkCapacity(size) {
+    if (this._capacity < size) {
+        this._resizeTo(this._capacity << 3);
+    }
+};
+
+Queue.prototype._resizeTo = function Queue$_resizeTo(capacity) {
+    var oldFront = this._front;
+    var oldCapacity = this._capacity;
+    var oldQueue = new Array(oldCapacity);
+    var length = this.length();
+
+    arrayCopy(this, 0, oldQueue, 0, oldCapacity);
+    this._capacity = capacity;
+    this._makeCapacity();
+    this._front = 0;
+    if (oldFront + length <= oldCapacity) {
+        arrayCopy(oldQueue, oldFront, this, 0, length);
+    }
+    else {        var lengthBeforeWrapping =
+            length - ((oldFront + length) & (oldCapacity - 1));
+
+        arrayCopy(oldQueue, oldFront, this, 0, lengthBeforeWrapping);
+        arrayCopy(oldQueue, 0, this, lengthBeforeWrapping,
+                    length - lengthBeforeWrapping);
+    }
+};
+
+module.exports = Queue;
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/race.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/race.js
new file mode 100644
index 0000000..82b8ce1
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/race.js
@@ -0,0 +1,85 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, INTERNAL) {
+    var apiRejection = require("./errors_api_rejection.js")(Promise);
+    var isArray = require("./util.js").isArray;
+
+    var raceLater = function Promise$_raceLater(promise) {
+        return promise.then(function Promise$_lateRacer(array) {
+            return Promise$_Race(array, Promise$_lateRacer, promise);
+        });
+    };
+
+    var hasOwn = {}.hasOwnProperty;
+    function Promise$_Race(promises, caller, parent) {
+        var maybePromise = Promise._cast(promises, caller, void 0);
+
+        if (Promise.is(maybePromise)) {
+            return raceLater(maybePromise);
+        }
+        else if (!isArray(promises)) {
+            return apiRejection("expecting an array, a promise or a thenable");
+        }
+
+        var ret = new Promise(INTERNAL);
+        ret._setTrace(caller, parent);
+        if (parent !== void 0) {
+            if (parent._isBound()) {
+                ret._setBoundTo(parent._boundTo);
+            }
+            if (parent._cancellable()) {
+                ret._setCancellable();
+                ret._cancellationParent = parent;
+            }
+        }
+        var fulfill = ret._fulfill;
+        var reject = ret._reject;
+        for (var i = 0, len = promises.length; i < len; ++i) {
+            var val = promises[i];
+
+            if (val === void 0 && !(hasOwn.call(promises, i))) {
+                continue;
+            }
+
+            Promise.cast(val)._then(
+                fulfill,
+                reject,
+                void 0,
+                ret,
+                null,
+                caller
+           );
+        }
+        return ret;
+    }
+
+    Promise.race = function Promise$Race(promises) {
+        return Promise$_Race(promises, Promise.race, void 0);
+    };
+
+    Promise.prototype.race = function Promise$race() {
+        return Promise$_Race(this, this.race, void 0);
+    };
+
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/reduce.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/reduce.js
new file mode 100644
index 0000000..e9ef95b
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/reduce.js
@@ -0,0 +1,173 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(
+    Promise, Promise$_CreatePromiseArray,
+    PromiseArray, apiRejection, INTERNAL) {
+
+    function Reduction(callback, index, accum, items, receiver) {
+        this.promise = new Promise(INTERNAL);
+        this.index = index;
+        this.length = items.length;
+        this.items = items;
+        this.callback = callback;
+        this.receiver = receiver;
+        this.accum = accum;
+    }
+
+    Reduction.prototype.reject = function Reduction$reject(e) {
+        this.promise._reject(e);
+    };
+
+    Reduction.prototype.fulfill = function Reduction$fulfill(value, index) {
+        this.accum = value;
+        this.index = index + 1;
+        this.iterate();
+    };
+
+    Reduction.prototype.iterate = function Reduction$iterate() {
+        var i = this.index;
+        var len = this.length;
+        var items = this.items;
+        var result = this.accum;
+        var receiver = this.receiver;
+        var callback = this.callback;
+        var iterate = this.iterate;
+
+        for(; i < len; ++i) {
+            result = Promise._cast(
+                callback.call(
+                    receiver,
+                    result,
+                    items[i],
+                    i,
+                    len
+                ),
+                iterate,
+                void 0
+            );
+
+            if (result instanceof Promise) {
+                result._then(
+                    this.fulfill, this.reject, void 0, this, i, iterate);
+                return;
+            }
+        }
+        this.promise._fulfill(result);
+    };
+
+    function Promise$_reducer(fulfilleds, initialValue) {
+        var fn = this;
+        var receiver = void 0;
+        if (typeof fn !== "function")  {
+            receiver = fn.receiver;
+            fn = fn.fn;
+        }
+        var len = fulfilleds.length;
+        var accum = void 0;
+        var startIndex = 0;
+
+        if (initialValue !== void 0) {
+            accum = initialValue;
+            startIndex = 0;
+        }
+        else {
+            startIndex = 1;
+            if (len > 0) accum = fulfilleds[0];
+        }
+        var i = startIndex;
+
+        if (i >= len) {
+            return accum;
+        }
+
+        var reduction = new Reduction(fn, i, accum, fulfilleds, receiver);
+        reduction.iterate();
+        return reduction.promise;
+    }
+
+    function Promise$_unpackReducer(fulfilleds) {
+        var fn = this.fn;
+        var initialValue = this.initialValue;
+        return Promise$_reducer.call(fn, fulfilleds, initialValue);
+    }
+
+    function Promise$_slowReduce(
+        promises, fn, initialValue, useBound, caller) {
+        return initialValue._then(function callee(initialValue) {
+            return Promise$_Reduce(
+                promises, fn, initialValue, useBound, callee);
+        }, void 0, void 0, void 0, void 0, caller);
+    }
+
+    function Promise$_Reduce(promises, fn, initialValue, useBound, caller) {
+        if (typeof fn !== "function") {
+            return apiRejection("fn must be a function");
+        }
+
+        if (useBound === true && promises._isBound()) {
+            fn = {
+                fn: fn,
+                receiver: promises._boundTo
+            };
+        }
+
+        if (initialValue !== void 0) {
+            if (Promise.is(initialValue)) {
+                if (initialValue.isFulfilled()) {
+                    initialValue = initialValue._settledValue;
+                }
+                else {
+                    return Promise$_slowReduce(promises,
+                        fn, initialValue, useBound, caller);
+                }
+            }
+
+            return Promise$_CreatePromiseArray(promises, PromiseArray, caller,
+                useBound === true && promises._isBound()
+                    ? promises._boundTo
+                    : void 0)
+                .promise()
+                ._then(Promise$_unpackReducer, void 0, void 0, {
+                    fn: fn,
+                    initialValue: initialValue
+                }, void 0, Promise.reduce);
+        }
+        return Promise$_CreatePromiseArray(promises, PromiseArray, caller,
+                useBound === true && promises._isBound()
+                    ? promises._boundTo
+                    : void 0).promise()
+            ._then(Promise$_reducer, void 0, void 0, fn, void 0, caller);
+    }
+
+
+    Promise.reduce = function Promise$Reduce(promises, fn, initialValue) {
+        return Promise$_Reduce(promises, fn,
+            initialValue, false, Promise.reduce);
+    };
+
+    Promise.prototype.reduce = function Promise$reduce(fn, initialValue) {
+        return Promise$_Reduce(this, fn, initialValue,
+                                true, this.reduce);
+    };
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/schedule.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/schedule.js
new file mode 100644
index 0000000..ae2271e
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/schedule.js
@@ -0,0 +1,121 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var global = require("./global.js");
+var schedule;
+if (typeof process !== "undefined" && process !== null &&
+    typeof process.cwd === "function" &&
+    typeof process.nextTick === "function" &&
+    typeof process.version === "string") {
+    schedule = function Promise$_Scheduler(fn) {
+        process.nextTick(fn);
+    };
+}
+else if ((typeof global.MutationObserver === "function" ||
+        typeof global.WebkitMutationObserver === "function" ||
+        typeof global.WebKitMutationObserver === "function") &&
+        typeof document !== "undefined" &&
+        typeof document.createElement === "function") {
+
+
+    schedule = (function(){
+        var MutationObserver = global.MutationObserver ||
+            global.WebkitMutationObserver ||
+            global.WebKitMutationObserver;
+        var div = document.createElement("div");
+        var queuedFn = void 0;
+        var observer = new MutationObserver(
+            function Promise$_Scheduler() {
+                var fn = queuedFn;
+                queuedFn = void 0;
+                fn();
+            }
+       );
+        observer.observe(div, {
+            attributes: true
+        });
+        return function Promise$_Scheduler(fn) {
+            queuedFn = fn;
+            div.setAttribute("class", "foo");
+        };
+
+    })();
+}
+else if (typeof global.postMessage === "function" &&
+    typeof global.importScripts !== "function" &&
+    typeof global.addEventListener === "function" &&
+    typeof global.removeEventListener === "function") {
+
+    var MESSAGE_KEY = "bluebird_message_key_" + Math.random();
+    schedule = (function(){
+        var queuedFn = void 0;
+
+        function Promise$_Scheduler(e) {
+            if (e.source === global &&
+                e.data === MESSAGE_KEY) {
+                var fn = queuedFn;
+                queuedFn = void 0;
+                fn();
+            }
+        }
+
+        global.addEventListener("message", Promise$_Scheduler, false);
+
+        return function Promise$_Scheduler(fn) {
+            queuedFn = fn;
+            global.postMessage(
+                MESSAGE_KEY, "*"
+           );
+        };
+
+    })();
+}
+else if (typeof global.MessageChannel === "function") {
+    schedule = (function(){
+        var queuedFn = void 0;
+
+        var channel = new global.MessageChannel();
+        channel.port1.onmessage = function Promise$_Scheduler() {
+                var fn = queuedFn;
+                queuedFn = void 0;
+                fn();
+        };
+
+        return function Promise$_Scheduler(fn) {
+            queuedFn = fn;
+            channel.port2.postMessage(null);
+        };
+    })();
+}
+else if (global.setTimeout) {
+    schedule = function Promise$_Scheduler(fn) {
+        setTimeout(fn, 4);
+    };
+}
+else {
+    schedule = function Promise$_Scheduler(fn) {
+        fn();
+    };
+}
+
+module.exports = schedule;
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/settle.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/settle.js
new file mode 100644
index 0000000..863882f
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/settle.js
@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports =
+    function(Promise, Promise$_CreatePromiseArray, PromiseArray) {
+
+    var SettledPromiseArray = require("./settled_promise_array.js")(
+        Promise, PromiseArray);
+
+    function Promise$_Settle(promises, useBound, caller) {
+        return Promise$_CreatePromiseArray(
+            promises,
+            SettledPromiseArray,
+            caller,
+            useBound === true && promises._isBound()
+                ? promises._boundTo
+                : void 0
+       ).promise();
+    }
+
+    Promise.settle = function Promise$Settle(promises) {
+        return Promise$_Settle(promises, false, Promise.settle);
+    };
+
+    Promise.prototype.settle = function Promise$settle() {
+        return Promise$_Settle(this, true, this.settle);
+    };
+
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/settled_promise_array.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/settled_promise_array.js
new file mode 100644
index 0000000..fd25d4d
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/settled_promise_array.js
@@ -0,0 +1,60 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, PromiseArray) {
+var PromiseInspection = require("./promise_inspection.js");
+var util = require("./util.js");
+var inherits = util.inherits;
+function SettledPromiseArray(values, caller, boundTo) {
+    this.constructor$(values, caller, boundTo);
+}
+inherits(SettledPromiseArray, PromiseArray);
+
+SettledPromiseArray.prototype._promiseResolved =
+function SettledPromiseArray$_promiseResolved(index, inspection) {
+    this._values[index] = inspection;
+    var totalResolved = ++this._totalResolved;
+    if (totalResolved >= this._length) {
+        this._resolve(this._values);
+    }
+};
+
+SettledPromiseArray.prototype._promiseFulfilled =
+function SettledPromiseArray$_promiseFulfilled(value, index) {
+    if (this._isResolved()) return;
+    var ret = new PromiseInspection();
+    ret._bitField = 268435456;
+    ret._settledValue = value;
+    this._promiseResolved(index, ret);
+};
+SettledPromiseArray.prototype._promiseRejected =
+function SettledPromiseArray$_promiseRejected(reason, index) {
+    if (this._isResolved()) return;
+    var ret = new PromiseInspection();
+    ret._bitField = 134217728;
+    ret._settledValue = reason;
+    this._promiseResolved(index, ret);
+};
+
+return SettledPromiseArray;
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/some.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/some.js
new file mode 100644
index 0000000..21c4ecd
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/some.js
@@ -0,0 +1,57 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports =
+function(Promise, Promise$_CreatePromiseArray, PromiseArray, apiRejection) {
+
+    var SomePromiseArray = require("./some_promise_array.js")(PromiseArray);
+    function Promise$_Some(promises, howMany, useBound, caller) {
+        if ((howMany | 0) !== howMany || howMany < 0) {
+            return apiRejection("expecting a positive integer");
+        }
+        var ret = Promise$_CreatePromiseArray(
+            promises,
+            SomePromiseArray,
+            caller,
+            useBound === true && promises._isBound()
+                ? promises._boundTo
+                : void 0
+       );
+        var promise = ret.promise();
+        if (promise.isRejected()) {
+            return promise;
+        }
+        ret.setHowMany(howMany);
+        ret.init();
+        return promise;
+    }
+
+    Promise.some = function Promise$Some(promises, howMany) {
+        return Promise$_Some(promises, howMany, false, Promise.some);
+    };
+
+    Promise.prototype.some = function Promise$some(count) {
+        return Promise$_Some(this, count, true, this.some);
+    };
+
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/some_promise_array.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/some_promise_array.js
new file mode 100644
index 0000000..d3b89d4
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/some_promise_array.js
@@ -0,0 +1,131 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function (PromiseArray) {
+var util = require("./util.js");
+var RangeError = require("./errors.js").RangeError;
+var inherits = util.inherits;
+var isArray = util.isArray;
+
+function SomePromiseArray(values, caller, boundTo) {
+    this.constructor$(values, caller, boundTo);
+    this._howMany = 0;
+    this._unwrap = false;
+    this._initialized = false;
+}
+inherits(SomePromiseArray, PromiseArray);
+
+SomePromiseArray.prototype._init = function SomePromiseArray$_init() {
+    if (!this._initialized) {
+        return;
+    }
+    if (this._howMany === 0) {
+        this._resolve([]);
+        return;
+    }
+    this._init$(void 0, -2);
+    var isArrayResolved = isArray(this._values);
+    this._holes = isArrayResolved ? this._values.length - this.length() : 0;
+
+    if (!this._isResolved() &&
+        isArrayResolved &&
+        this._howMany > this._canPossiblyFulfill()) {
+        var message = "(Promise.some) input array contains less than " +
+                        this._howMany  + " promises";
+        this._reject(new RangeError(message));
+    }
+};
+
+SomePromiseArray.prototype.init = function SomePromiseArray$init() {
+    this._initialized = true;
+    this._init();
+};
+
+SomePromiseArray.prototype.setUnwrap = function SomePromiseArray$setUnwrap() {
+    this._unwrap = true;
+};
+
+SomePromiseArray.prototype.howMany = function SomePromiseArray$howMany() {
+    return this._howMany;
+};
+
+SomePromiseArray.prototype.setHowMany =
+function SomePromiseArray$setHowMany(count) {
+    if (this._isResolved()) return;
+    this._howMany = count;
+};
+
+SomePromiseArray.prototype._promiseFulfilled =
+function SomePromiseArray$_promiseFulfilled(value) {
+    if (this._isResolved()) return;
+    this._addFulfilled(value);
+    if (this._fulfilled() === this.howMany()) {
+        this._values.length = this.howMany();
+        if (this.howMany() === 1 && this._unwrap) {
+            this._resolve(this._values[0]);
+        }
+        else {
+            this._resolve(this._values);
+        }
+    }
+
+};
+SomePromiseArray.prototype._promiseRejected =
+function SomePromiseArray$_promiseRejected(reason) {
+    if (this._isResolved()) return;
+    this._addRejected(reason);
+    if (this.howMany() > this._canPossiblyFulfill()) {
+        if (this._values.length === this.length()) {
+            this._reject([]);
+        }
+        else {
+            this._reject(this._values.slice(this.length() + this._holes));
+        }
+    }
+};
+
+SomePromiseArray.prototype._fulfilled = function SomePromiseArray$_fulfilled() {
+    return this._totalResolved;
+};
+
+SomePromiseArray.prototype._rejected = function SomePromiseArray$_rejected() {
+    return this._values.length - this.length() - this._holes;
+};
+
+SomePromiseArray.prototype._addRejected =
+function SomePromiseArray$_addRejected(reason) {
+    this._values.push(reason);
+};
+
+SomePromiseArray.prototype._addFulfilled =
+function SomePromiseArray$_addFulfilled(value) {
+    this._values[this._totalResolved++] = value;
+};
+
+SomePromiseArray.prototype._canPossiblyFulfill =
+function SomePromiseArray$_canPossiblyFulfill() {
+    return this.length() - this._rejected();
+};
+
+return SomePromiseArray;
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/synchronous_inspection.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/synchronous_inspection.js
new file mode 100644
index 0000000..dcbdc90
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/synchronous_inspection.js
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise) {
+    var PromiseInspection = require("./promise_inspection.js");
+
+    Promise.prototype.inspect = function Promise$inspect() {
+        return new PromiseInspection(this);
+    };
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/thenables.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/thenables.js
new file mode 100644
index 0000000..510da18
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/thenables.js
@@ -0,0 +1,138 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, INTERNAL) {
+    var util = require("./util.js");
+    var canAttach = require("./errors.js").canAttach;
+    var errorObj = util.errorObj;
+    var isObject = util.isObject;
+
+    function getThen(obj) {
+        try {
+            return obj.then;
+        }
+        catch(e) {
+            errorObj.e = e;
+            return errorObj;
+        }
+    }
+
+    function Promise$_Cast(obj, caller, originalPromise) {
+        if (isObject(obj)) {
+            if (obj instanceof Promise) {
+                return obj;
+            }
+            else if (isAnyBluebirdPromise(obj)) {
+                var ret = new Promise(INTERNAL);
+                ret._setTrace(caller, void 0);
+                obj._then(
+                    ret._fulfillUnchecked,
+                    ret._rejectUncheckedCheckError,
+                    ret._progressUnchecked,
+                    ret,
+                    null,
+                    void 0
+                );
+                ret._setFollowing();
+                return ret;
+            }
+            var then = getThen(obj);
+            if (then === errorObj) {
+                caller = typeof caller === "function" ? caller : Promise$_Cast;
+                if (originalPromise !== void 0 && canAttach(then.e)) {
+                    originalPromise._attachExtraTrace(then.e);
+                }
+                return Promise.reject(then.e, caller);
+            }
+            else if (typeof then === "function") {
+                caller = typeof caller === "function" ? caller : Promise$_Cast;
+                return Promise$_doThenable(obj, then, caller, originalPromise);
+            }
+        }
+        return obj;
+    }
+
+    var hasProp = {}.hasOwnProperty;
+    function isAnyBluebirdPromise(obj) {
+        return hasProp.call(obj, "_promise0");
+    }
+
+    function Promise$_doThenable(x, then, caller, originalPromise) {
+        var resolver = Promise.defer(caller);
+        var called = false;
+        try {
+            then.call(
+                x,
+                Promise$_resolveFromThenable,
+                Promise$_rejectFromThenable,
+                Promise$_progressFromThenable
+            );
+        }
+        catch(e) {
+            if (!called) {
+                called = true;
+                var trace = canAttach(e) ? e : new Error(e + "");
+                if (originalPromise !== void 0) {
+                    originalPromise._attachExtraTrace(trace);
+                }
+                resolver.promise._reject(e, trace);
+            }
+        }
+        return resolver.promise;
+
+        function Promise$_resolveFromThenable(y) {
+            if (called) return;
+            called = true;
+
+            if (x === y) {
+                var e = Promise._makeSelfResolutionError();
+                if (originalPromise !== void 0) {
+                    originalPromise._attachExtraTrace(e);
+                }
+                resolver.promise._reject(e, void 0);
+                return;
+            }
+            resolver.resolve(y);
+        }
+
+        function Promise$_rejectFromThenable(r) {
+            if (called) return;
+            called = true;
+            var trace = canAttach(r) ? r : new Error(r + "");
+            if (originalPromise !== void 0) {
+                originalPromise._attachExtraTrace(trace);
+            }
+            resolver.promise._reject(r, trace);
+        }
+
+        function Promise$_progressFromThenable(v) {
+            if (called) return;
+            var promise = resolver.promise;
+            if (typeof promise._progress === "function") {
+                promise._progress(v);
+            }
+        }
+    }
+
+    Promise._cast = Promise$_Cast;
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/timers.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/timers.js
new file mode 100644
index 0000000..8edcac2
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/timers.js
@@ -0,0 +1,114 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+
+var global = require("./global.js");
+var setTimeout = function(fn, time) {
+    var $_len = arguments.length;var args = new Array($_len - 2); for(var $_i = 2; $_i < $_len; ++$_i) {args[$_i - 2] = arguments[$_i];}
+    global.setTimeout(function() {
+        fn.apply(void 0, args);
+    }, time);
+};
+
+var pass = {};
+global.setTimeout( function(_) {
+    if(_ === pass) {
+        setTimeout = global.setTimeout;
+    }
+}, 1, pass);
+
+module.exports = function(Promise, INTERNAL) {
+    var util = require("./util.js");
+    var errors = require("./errors.js");
+    var apiRejection = require("./errors_api_rejection")(Promise);
+    var TimeoutError = Promise.TimeoutError;
+
+    var afterTimeout = function Promise$_afterTimeout(promise, message, ms) {
+        if (!promise.isPending()) return;
+        if (typeof message !== "string") {
+            message = "operation timed out after" + " " + ms + " ms"
+        }
+        var err = new TimeoutError(message);
+        errors.markAsOriginatingFromRejection(err);
+        promise._attachExtraTrace(err);
+        promise._rejectUnchecked(err);
+    };
+
+    var afterDelay = function Promise$_afterDelay(value, promise) {
+        promise._fulfill(value);
+    };
+
+    Promise.delay = function Promise$Delay(value, ms, caller) {
+        if (ms === void 0) {
+            ms = value;
+            value = void 0;
+        }
+        ms = +ms;
+        if (typeof caller !== "function") {
+            caller = Promise.delay;
+        }
+        var maybePromise = Promise._cast(value, caller, void 0);
+        var promise = new Promise(INTERNAL);
+
+        if (Promise.is(maybePromise)) {
+            if (maybePromise._isBound()) {
+                promise._setBoundTo(maybePromise._boundTo);
+            }
+            if (maybePromise._cancellable()) {
+                promise._setCancellable();
+                promise._cancellationParent = maybePromise;
+            }
+            promise._setTrace(caller, maybePromise);
+            promise._follow(maybePromise);
+            return promise.then(function(value) {
+                return Promise.delay(value, ms);
+            });
+        }
+        else {
+            promise._setTrace(caller, void 0);
+            setTimeout(afterDelay, ms, value, promise);
+        }
+        return promise;
+    };
+
+    Promise.prototype.delay = function Promise$delay(ms) {
+        return Promise.delay(this, ms, this.delay);
+    };
+
+    Promise.prototype.timeout = function Promise$timeout(ms, message) {
+        ms = +ms;
+
+        var ret = new Promise(INTERNAL);
+        ret._setTrace(this.timeout, this);
+
+        if (this._isBound()) ret._setBoundTo(this._boundTo);
+        if (this._cancellable()) {
+            ret._setCancellable();
+            ret._cancellationParent = this;
+        }
+        ret._follow(this);
+        setTimeout(afterTimeout, ms, ret, message, ms);
+        return ret;
+    };
+
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/util.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/util.js
new file mode 100644
index 0000000..fbd34e9
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/js/zalgo/util.js
@@ -0,0 +1,193 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var global = require("./global.js");
+var es5 = require("./es5.js");
+var haveGetters = (function(){
+    try {
+        var o = {};
+        es5.defineProperty(o, "f", {
+            get: function () {
+                return 3;
+            }
+        });
+        return o.f === 3;
+    }
+    catch (e) {
+        return false;
+    }
+
+})();
+
+var canEvaluate = (function() {
+    if (typeof window !== "undefined" && window !== null &&
+        typeof window.document !== "undefined" &&
+        typeof navigator !== "undefined" && navigator !== null &&
+        typeof navigator.appName === "string" &&
+        window === global) {
+        return false;
+    }
+    return true;
+})();
+
+function deprecated(msg) {
+    if (typeof console !== "undefined" && console !== null &&
+        typeof console.warn === "function") {
+        console.warn("Bluebird: " + msg);
+    }
+}
+
+var errorObj = {e: {}};
+function tryCatch1(fn, receiver, arg) {
+    try {
+        return fn.call(receiver, arg);
+    }
+    catch (e) {
+        errorObj.e = e;
+        return errorObj;
+    }
+}
+
+function tryCatch2(fn, receiver, arg, arg2) {
+    try {
+        return fn.call(receiver, arg, arg2);
+    }
+    catch (e) {
+        errorObj.e = e;
+        return errorObj;
+    }
+}
+
+function tryCatchApply(fn, args, receiver) {
+    try {
+        return fn.apply(receiver, args);
+    }
+    catch (e) {
+        errorObj.e = e;
+        return errorObj;
+    }
+}
+
+var inherits = function(Child, Parent) {
+    var hasProp = {}.hasOwnProperty;
+
+    function T() {
+        this.constructor = Child;
+        this.constructor$ = Parent;
+        for (var propertyName in Parent.prototype) {
+            if (hasProp.call(Parent.prototype, propertyName) &&
+                propertyName.charAt(propertyName.length-1) !== "$"
+           ) {
+                this[propertyName + "$"] = Parent.prototype[propertyName];
+            }
+        }
+    }
+    T.prototype = Parent.prototype;
+    Child.prototype = new T();
+    return Child.prototype;
+};
+
+function asString(val) {
+    return typeof val === "string" ? val : ("" + val);
+}
+
+function isPrimitive(val) {
+    return val == null || val === true || val === false ||
+        typeof val === "string" || typeof val === "number";
+
+}
+
+function isObject(value) {
+    return !isPrimitive(value);
+}
+
+function maybeWrapAsError(maybeError) {
+    if (!isPrimitive(maybeError)) return maybeError;
+
+    return new Error(asString(maybeError));
+}
+
+function withAppended(target, appendee) {
+    var len = target.length;
+    var ret = new Array(len + 1);
+    var i;
+    for (i = 0; i < len; ++i) {
+        ret[i] = target[i];
+    }
+    ret[i] = appendee;
+    return ret;
+}
+
+
+function notEnumerableProp(obj, name, value) {
+    if (isPrimitive(obj)) return obj;
+    var descriptor = {
+        value: value,
+        configurable: true,
+        enumerable: false,
+        writable: true
+    };
+    es5.defineProperty(obj, name, descriptor);
+    return obj;
+}
+
+
+var wrapsPrimitiveReceiver = (function() {
+    return this !== "string";
+}).call("string");
+
+function thrower(r) {
+    throw r;
+}
+
+
+function toFastProperties(obj) {
+    /*jshint -W027*/
+    function f() {}
+    f.prototype = obj;
+    return f;
+    eval(obj);
+}
+
+var ret = {
+    thrower: thrower,
+    isArray: es5.isArray,
+    haveGetters: haveGetters,
+    notEnumerableProp: notEnumerableProp,
+    isPrimitive: isPrimitive,
+    isObject: isObject,
+    canEvaluate: canEvaluate,
+    deprecated: deprecated,
+    errorObj: errorObj,
+    tryCatch1: tryCatch1,
+    tryCatch2: tryCatch2,
+    tryCatchApply: tryCatchApply,
+    inherits: inherits,
+    withAppended: withAppended,
+    asString: asString,
+    maybeWrapAsError: maybeWrapAsError,
+    wrapsPrimitiveReceiver: wrapsPrimitiveReceiver,
+    toFastProperties: toFastProperties
+};
+
+module.exports = ret;
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/package.json b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/package.json
new file mode 100644
index 0000000..5980304
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/package.json
@@ -0,0 +1,85 @@
+{
+  "name": "bluebird",
+  "description": "Full featured Promises/A+ implementation with exceptionally good performance",
+  "version": "1.1.1",
+  "keywords": [
+    "promise",
+    "performance",
+    "promises",
+    "promises-a",
+    "promises-aplus",
+    "async",
+    "await",
+    "deferred",
+    "deferreds",
+    "future",
+    "flow control",
+    "dsl",
+    "fluent interface"
+  ],
+  "scripts": {
+    "test": "grunt test"
+  },
+  "homepage": "https://github.com/petkaantonov/bluebird",
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/petkaantonov/bluebird.git"
+  },
+  "bugs": {
+    "url": "http://github.com/petkaantonov/bluebird/issues"
+  },
+  "license": "MIT",
+  "author": {
+    "name": "Petka Antonov",
+    "email": "petka_antonov@hotmail.com",
+    "url": "http://github.com/petkaantonov/"
+  },
+  "devDependencies": {
+    "grunt": "~0.4.1",
+    "grunt-contrib-jshint": "~0.6.4",
+    "grunt-contrib-watch": "latest",
+    "grunt-contrib-connect": "latest",
+    "grunt-saucelabs": "latest",
+    "acorn": "~0.3.1",
+    "mocha": "~1.12.1",
+    "q": "~0.9.7",
+    "when": "~2.4.0",
+    "deferred": "~0.6.5",
+    "rsvp": "~2.0.4",
+    "avow": "~2.0.1",
+    "jsdom": "~0.8.4",
+    "jquery-browserify": "~1.8.1",
+    "sinon": "~1.7.3",
+    "kew": "~0.2.2",
+    "browserify": "~2.35.0",
+    "concurrent": "~0.3.2",
+    "text-table": "~0.2.0",
+    "grunt-cli": "~0.1.9",
+    "jshint-stylish": "~0.1.3",
+    "semver-utils": "~1.1.0",
+    "rimraf": "~2.2.6",
+    "mkdirp": "~0.3.5"
+  },
+  "main": "./js/main/bluebird.js",
+  "_id": "bluebird@1.1.1",
+  "dist": {
+    "shasum": "744e9980145e2ebc41a9e34826f913096667fb33",
+    "tarball": "http://registry.npmjs.org/bluebird/-/bluebird-1.1.1.tgz"
+  },
+  "_from": "bluebird@~1.1.0",
+  "_npmVersion": "1.4.3",
+  "_npmUser": {
+    "name": "esailija",
+    "email": "petka_antonov@hotmail.com"
+  },
+  "maintainers": [
+    {
+      "name": "esailija",
+      "email": "petka_antonov@hotmail.com"
+    }
+  ],
+  "directories": {},
+  "_shasum": "744e9980145e2ebc41a9e34826f913096667fb33",
+  "_resolved": "https://registry.npmjs.org/bluebird/-/bluebird-1.1.1.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/zalgo.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/zalgo.js
new file mode 100644
index 0000000..1357352
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/bluebird/zalgo.js
@@ -0,0 +1 @@
+module.exports = require('./js/zalgo/bluebird.js');
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/commander/History.md b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/commander/History.md
new file mode 100644
index 0000000..3a69559
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/commander/History.md
@@ -0,0 +1,239 @@
+
+2.7.1 / 2015-03-11
+==================
+
+ * Revert #347 (fix collisions when option and first arg have same name) which causes a bug in #367.
+
+2.7.0 / 2015-03-09
+==================
+
+ * Fix git-style bug when installed globally. Close #335 #349 @zhiyelee
+ * Fix collisions when option and first arg have same name. Close #346 #347 @tonylukasavage
+ * Add support for camelCase on `opts()`. Close #353  @nkzawa
+ * Add node.js 0.12 and io.js to travis.yml
+ * Allow RegEx options. #337 @palanik
+ * Fixes exit code when sub-command failing.  Close #260 #332 @pirelenito
+ * git-style `bin` files in $PATH make sense. Close #196 #327  @zhiyelee
+
+2.6.0 / 2014-12-30
+==================
+
+  * added `Command#allowUnknownOption` method. Close #138 #318 @doozr @zhiyelee
+  * Add application description to the help msg. Close #112 @dalssoft
+
+2.5.1 / 2014-12-15
+==================
+
+  * fixed two bugs incurred by variadic arguments. Close #291 @Quentin01 #302 @zhiyelee
+
+2.5.0 / 2014-10-24
+==================
+
+ * add support for variadic arguments. Closes #277 @whitlockjc
+
+2.4.0 / 2014-10-17
+==================
+
+ * fixed a bug on executing the coercion function of subcommands option. Closes #270
+ * added `Command.prototype.name` to retrieve command name. Closes #264 #266 @tonylukasavage
+ * added `Command.prototype.opts` to retrieve all the options as a simple object of key-value pairs. Closes #262 @tonylukasavage
+ * fixed a bug on subcommand name. Closes #248 @jonathandelgado
+ * fixed function normalize doesn’t honor option terminator. Closes #216 @abbr
+
+2.3.0 / 2014-07-16
+==================
+
+ * add command alias'. Closes PR #210
+ * fix: Typos. Closes #99
+ * fix: Unused fs module. Closes #217
+
+2.2.0 / 2014-03-29
+==================
+
+ * add passing of previous option value
+ * fix: support subcommands on windows. Closes #142
+ * Now the defaultValue passed as the second argument of the coercion function.
+
+2.1.0 / 2013-11-21
+==================
+
+ * add: allow cflag style option params, unit test, fixes #174
+
+2.0.0 / 2013-07-18
+==================
+
+ * remove input methods (.prompt, .confirm, etc)
+
+1.3.2 / 2013-07-18
+==================
+
+ * add support for sub-commands to co-exist with the original command
+
+1.3.1 / 2013-07-18
+==================
+
+ * add quick .runningCommand hack so you can opt-out of other logic when running a sub command
+
+1.3.0 / 2013-07-09
+==================
+
+ * add EACCES error handling
+ * fix sub-command --help
+
+1.2.0 / 2013-06-13
+==================
+
+ * allow "-" hyphen as an option argument
+ * support for RegExp coercion
+
+1.1.1 / 2012-11-20
+==================
+
+  * add more sub-command padding
+  * fix .usage() when args are present. Closes #106
+
+1.1.0 / 2012-11-16
+==================
+
+  * add git-style executable subcommand support. Closes #94
+
+1.0.5 / 2012-10-09
+==================
+
+  * fix `--name` clobbering. Closes #92
+  * fix examples/help. Closes #89
+
+1.0.4 / 2012-09-03
+==================
+
+  * add `outputHelp()` method.
+
+1.0.3 / 2012-08-30
+==================
+
+  * remove invalid .version() defaulting
+
+1.0.2 / 2012-08-24
+==================
+
+  * add `--foo=bar` support [arv]
+  * fix password on node 0.8.8. Make backward compatible with 0.6 [focusaurus]
+
+1.0.1 / 2012-08-03
+==================
+
+  * fix issue #56
+  * fix tty.setRawMode(mode) was moved to tty.ReadStream#setRawMode() (i.e. process.stdin.setRawMode())
+
+1.0.0 / 2012-07-05
+==================
+
+  * add support for optional option descriptions
+  * add defaulting of `.version()` to package.json's version
+
+0.6.1 / 2012-06-01
+==================
+
+  * Added: append (yes or no) on confirmation
+  * Added: allow node.js v0.7.x
+
+0.6.0 / 2012-04-10
+==================
+
+  * Added `.prompt(obj, callback)` support. Closes #49
+  * Added default support to .choose(). Closes #41
+  * Fixed the choice example
+
+0.5.1 / 2011-12-20
+==================
+
+  * Fixed `password()` for recent nodes. Closes #36
+
+0.5.0 / 2011-12-04
+==================
+
+  * Added sub-command option support [itay]
+
+0.4.3 / 2011-12-04
+==================
+
+  * Fixed custom help ordering. Closes #32
+
+0.4.2 / 2011-11-24
+==================
+
+  * Added travis support
+  * Fixed: line-buffered input automatically trimmed. Closes #31
+
+0.4.1 / 2011-11-18
+==================
+
+  * Removed listening for "close" on --help
+
+0.4.0 / 2011-11-15
+==================
+
+  * Added support for `--`. Closes #24
+
+0.3.3 / 2011-11-14
+==================
+
+  * Fixed: wait for close event when writing help info [Jerry Hamlet]
+
+0.3.2 / 2011-11-01
+==================
+
+  * Fixed long flag definitions with values [felixge]
+
+0.3.1 / 2011-10-31
+==================
+
+  * Changed `--version` short flag to `-V` from `-v`
+  * Changed `.version()` so it's configurable [felixge]
+
+0.3.0 / 2011-10-31
+==================
+
+  * Added support for long flags only. Closes #18
+
+0.2.1 / 2011-10-24
+==================
+
+  * "node": ">= 0.4.x < 0.7.0". Closes #20
+
+0.2.0 / 2011-09-26
+==================
+
+  * Allow for defaults that are not just boolean. Default peassignment only occurs for --no-*, optional, and required arguments. [Jim Isaacs]
+
+0.1.0 / 2011-08-24
+==================
+
+  * Added support for custom `--help` output
+
+0.0.5 / 2011-08-18
+==================
+
+  * Changed: when the user enters nothing prompt for password again
+  * Fixed issue with passwords beginning with numbers [NuckChorris]
+
+0.0.4 / 2011-08-15
+==================
+
+  * Fixed `Commander#args`
+
+0.0.3 / 2011-08-15
+==================
+
+  * Added default option value support
+
+0.0.2 / 2011-08-15
+==================
+
+  * Added mask support to `Command#password(str[, mask], fn)`
+  * Added `Command#password(str, fn)`
+
+0.0.1 / 2010-01-03
+==================
+
+  * Initial release
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/commander/LICENSE b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/commander/LICENSE
new file mode 100644
index 0000000..10f997a
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/commander/LICENSE
@@ -0,0 +1,22 @@
+(The MIT License)
+
+Copyright (c) 2011 TJ Holowaychuk <tj@vision-media.ca>
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/commander/Readme.md b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/commander/Readme.md
new file mode 100644
index 0000000..4e091d2
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/commander/Readme.md
@@ -0,0 +1,311 @@
+# Commander.js
+
+
+[![Build Status](https://api.travis-ci.org/tj/commander.js.svg)](http://travis-ci.org/tj/commander.js)
+[![NPM Version](http://img.shields.io/npm/v/commander.svg?style=flat)](https://www.npmjs.org/package/commander)
+[![NPM Downloads](https://img.shields.io/npm/dm/commander.svg?style=flat)](https://www.npmjs.org/package/commander)
+[![Join the chat at https://gitter.im/tj/commander.js](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/tj/commander.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+
+  The complete solution for [node.js](http://nodejs.org) command-line interfaces, inspired by Ruby's [commander](https://github.com/tj/commander).  
+  [API documentation](http://tj.github.com/commander.js/)
+
+
+## Installation
+
+    $ npm install commander
+
+## Option parsing
+
+ Options with commander are defined with the `.option()` method, also serving as documentation for the options. The example below parses args and options from `process.argv`, leaving remaining args as the `program.args` array which were not consumed by options.
+
+```js
+#!/usr/bin/env node
+
+/**
+ * Module dependencies.
+ */
+
+var program = require('commander');
+
+program
+  .version('0.0.1')
+  .option('-p, --peppers', 'Add peppers')
+  .option('-P, --pineapple', 'Add pineapple')
+  .option('-b, --bbq', 'Add bbq sauce')
+  .option('-c, --cheese [type]', 'Add the specified type of cheese [marble]', 'marble')
+  .parse(process.argv);
+
+console.log('you ordered a pizza with:');
+if (program.peppers) console.log('  - peppers');
+if (program.pineapple) console.log('  - pineapple');
+if (program.bbq) console.log('  - bbq');
+console.log('  - %s cheese', program.cheese);
+```
+
+ Short flags may be passed as a single arg, for example `-abc` is equivalent to `-a -b -c`. Multi-word options such as "--template-engine" are camel-cased, becoming `program.templateEngine` etc.
+
+
+## Coercion
+
+```js
+function range(val) {
+  return val.split('..').map(Number);
+}
+
+function list(val) {
+  return val.split(',');
+}
+
+function collect(val, memo) {
+  memo.push(val);
+  return memo;
+}
+
+function increaseVerbosity(v, total) {
+  return total + 1;
+}
+
+program
+  .version('0.0.1')
+  .usage('[options] <file ...>')
+  .option('-i, --integer <n>', 'An integer argument', parseInt)
+  .option('-f, --float <n>', 'A float argument', parseFloat)
+  .option('-r, --range <a>..<b>', 'A range', range)
+  .option('-l, --list <items>', 'A list', list)
+  .option('-o, --optional [value]', 'An optional value')
+  .option('-c, --collect [value]', 'A repeatable value', collect, [])
+  .option('-v, --verbose', 'A value that can be increased', increaseVerbosity, 0)
+  .parse(process.argv);
+
+console.log(' int: %j', program.integer);
+console.log(' float: %j', program.float);
+console.log(' optional: %j', program.optional);
+program.range = program.range || [];
+console.log(' range: %j..%j', program.range[0], program.range[1]);
+console.log(' list: %j', program.list);
+console.log(' collect: %j', program.collect);
+console.log(' verbosity: %j', program.verbose);
+console.log(' args: %j', program.args);
+```
+
+## Regular Expression
+```js
+program
+  .version('0.0.1')
+  .option('-s --size <size>', 'Pizza size', /^(large|medium|small)$/i, 'medium')
+  .option('-d --drink [drink]', 'Drink', /^(coke|pepsi|izze)$/i)
+  .parse(process.argv);
+  
+console.log(' size: %j', program.size);
+console.log(' drink: %j', program.drink);
+```
+
+## Variadic arguments
+
+ The last argument of a command can be variadic, and only the last argument.  To make an argument variadic you have to
+ append `...` to the argument name.  Here is an example:
+
+```js
+#!/usr/bin/env node
+
+/**
+ * Module dependencies.
+ */
+
+var program = require('commander');
+
+program
+  .version('0.0.1')
+  .command('rmdir <dir> [otherDirs...]')
+  .action(function (dir, otherDirs) {
+    console.log('rmdir %s', dir);
+    if (otherDirs) {
+      otherDirs.forEach(function (oDir) {
+        console.log('rmdir %s', oDir);
+      });
+    }
+  });
+
+program.parse(process.argv);
+```
+
+ An `Array` is used for the value of a variadic argument.  This applies to `program.args` as well as the argument passed
+ to your action as demonstrated above.
+
+## Git-style sub-commands
+
+```js
+// file: ./examples/pm
+var program = require('..');
+
+program
+  .version('0.0.1')
+  .command('install [name]', 'install one or more packages')
+  .command('search [query]', 'search with optional query')
+  .command('list', 'list packages installed')
+  .parse(process.argv);
+```
+
+When `.command()` is invoked with a description argument, no `.action(callback)` should be called to handle sub-commands, otherwise there will be an error. This tells commander that you're going to use separate executables for sub-commands, much like `git(1)` and other popular tools.  
+The commander will try to search the executables in the directory of the entry script (like `./examples/pm`) with the name `program-command`, like `pm-install`, `pm-search`.
+
+If the program is designed to installed globally, make sure the executables have proper modes, like `755`.
+
+## Automated --help
+
+ The help information is auto-generated based on the information commander already knows about your program, so the following `--help` info is for free:
+
+```  
+ $ ./examples/pizza --help
+
+   Usage: pizza [options]
+
+   An application for pizzas ordering
+
+   Options:
+
+     -h, --help           output usage information
+     -V, --version        output the version number
+     -p, --peppers        Add peppers
+     -P, --pineapple      Add pineapple
+     -b, --bbq            Add bbq sauce
+     -c, --cheese <type>  Add the specified type of cheese [marble]
+     -C, --no-cheese      You do not want any cheese
+
+```
+
+## Custom help
+
+ You can display arbitrary `-h, --help` information
+ by listening for "--help". Commander will automatically
+ exit once you are done so that the remainder of your program
+ does not execute causing undesired behaviours, for example
+ in the following executable "stuff" will not output when
+ `--help` is used.
+
+```js
+#!/usr/bin/env node
+
+/**
+ * Module dependencies.
+ */
+
+var program = require('commander');
+
+program
+  .version('0.0.1')
+  .option('-f, --foo', 'enable some foo')
+  .option('-b, --bar', 'enable some bar')
+  .option('-B, --baz', 'enable some baz');
+
+// must be before .parse() since
+// node's emit() is immediate
+
+program.on('--help', function(){
+  console.log('  Examples:');
+  console.log('');
+  console.log('    $ custom-help --help');
+  console.log('    $ custom-help -h');
+  console.log('');
+});
+
+program.parse(process.argv);
+
+console.log('stuff');
+```
+
+Yields the following help output when `node script-name.js -h` or `node script-name.js --help` are run:
+
+```
+
+Usage: custom-help [options]
+
+Options:
+
+  -h, --help     output usage information
+  -V, --version  output the version number
+  -f, --foo      enable some foo
+  -b, --bar      enable some bar
+  -B, --baz      enable some baz
+
+Examples:
+
+  $ custom-help --help
+  $ custom-help -h
+
+```
+
+## .outputHelp()
+
+Output help information without exiting.
+
+If you want to display help by default (e.g. if no command was provided), you can use something like:
+
+```js
+var program = require('commander');
+
+program
+  .version('0.0.1')
+  .command('getstream [url]', 'get stream URL')
+  .parse(process.argv);
+
+  if (!process.argv.slice(2).length) {
+    program.outputHelp();
+  }
+```
+
+## .help()
+
+  Output help information and exit immediately.
+
+## Examples
+
+```js
+var program = require('commander');
+
+program
+  .version('0.0.1')
+  .option('-C, --chdir <path>', 'change the working directory')
+  .option('-c, --config <path>', 'set config path. defaults to ./deploy.conf')
+  .option('-T, --no-tests', 'ignore test hook')
+
+program
+  .command('setup [env]')
+  .description('run setup commands for all envs')
+  .option("-s, --setup_mode [mode]", "Which setup mode to use")
+  .action(function(env, options){
+    var mode = options.setup_mode || "normal";
+    env = env || 'all';
+    console.log('setup for %s env(s) with %s mode', env, mode);
+  });
+
+program
+  .command('exec <cmd>')
+  .alias('ex')
+  .description('execute the given remote cmd')
+  .option("-e, --exec_mode <mode>", "Which exec mode to use")
+  .action(function(cmd, options){
+    console.log('exec "%s" using %s mode', cmd, options.exec_mode);
+  }).on('--help', function() {
+    console.log('  Examples:');
+    console.log();
+    console.log('    $ deploy exec sequential');
+    console.log('    $ deploy exec async');
+    console.log();
+  });
+
+program
+  .command('*')
+  .action(function(env){
+    console.log('deploying "%s"', env);
+  });
+
+program.parse(process.argv);
+```
+
+More Demos can be found in the [examples](https://github.com/tj/commander.js/tree/master/examples) directory.
+
+## License
+
+MIT
+
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/commander/index.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/commander/index.js
new file mode 100644
index 0000000..1abf4df
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/commander/index.js
@@ -0,0 +1,1051 @@
+
+/**
+ * Module dependencies.
+ */
+
+var EventEmitter = require('events').EventEmitter;
+var spawn = require('child_process').spawn;
+var readlink = require('graceful-readlink').readlinkSync;
+var path = require('path');
+var dirname = path.dirname;
+var basename = path.basename;
+var fs = require('fs');
+
+/**
+ * Expose the root command.
+ */
+
+exports = module.exports = new Command();
+
+/**
+ * Expose `Command`.
+ */
+
+exports.Command = Command;
+
+/**
+ * Expose `Option`.
+ */
+
+exports.Option = Option;
+
+/**
+ * Initialize a new `Option` with the given `flags` and `description`.
+ *
+ * @param {String} flags
+ * @param {String} description
+ * @api public
+ */
+
+function Option(flags, description) {
+  this.flags = flags;
+  this.required = ~flags.indexOf('<');
+  this.optional = ~flags.indexOf('[');
+  this.bool = !~flags.indexOf('-no-');
+  flags = flags.split(/[ ,|]+/);
+  if (flags.length > 1 && !/^[[<]/.test(flags[1])) this.short = flags.shift();
+  this.long = flags.shift();
+  this.description = description || '';
+}
+
+/**
+ * Return option name.
+ *
+ * @return {String}
+ * @api private
+ */
+
+Option.prototype.name = function() {
+  return this.long
+    .replace('--', '')
+    .replace('no-', '');
+};
+
+/**
+ * Check if `arg` matches the short or long flag.
+ *
+ * @param {String} arg
+ * @return {Boolean}
+ * @api private
+ */
+
+Option.prototype.is = function(arg) {
+  return arg == this.short || arg == this.long;
+};
+
+/**
+ * Initialize a new `Command`.
+ *
+ * @param {String} name
+ * @api public
+ */
+
+function Command(name) {
+  this.commands = [];
+  this.options = [];
+  this._execs = [];
+  this._allowUnknownOption = false;
+  this._args = [];
+  this._name = name;
+}
+
+/**
+ * Inherit from `EventEmitter.prototype`.
+ */
+
+Command.prototype.__proto__ = EventEmitter.prototype;
+
+/**
+ * Add command `name`.
+ *
+ * The `.action()` callback is invoked when the
+ * command `name` is specified via __ARGV__,
+ * and the remaining arguments are applied to the
+ * function for access.
+ *
+ * When the `name` is "*" an un-matched command
+ * will be passed as the first arg, followed by
+ * the rest of __ARGV__ remaining.
+ *
+ * Examples:
+ *
+ *      program
+ *        .version('0.0.1')
+ *        .option('-C, --chdir <path>', 'change the working directory')
+ *        .option('-c, --config <path>', 'set config path. defaults to ./deploy.conf')
+ *        .option('-T, --no-tests', 'ignore test hook')
+ *
+ *      program
+ *        .command('setup')
+ *        .description('run remote setup commands')
+ *        .action(function() {
+ *          console.log('setup');
+ *        });
+ *
+ *      program
+ *        .command('exec <cmd>')
+ *        .description('run the given remote command')
+ *        .action(function(cmd) {
+ *          console.log('exec "%s"', cmd);
+ *        });
+ *
+ *      program
+ *        .command('teardown <dir> [otherDirs...]')
+ *        .description('run teardown commands')
+ *        .action(function(dir, otherDirs) {
+ *          console.log('dir "%s"', dir);
+ *          if (otherDirs) {
+ *            otherDirs.forEach(function (oDir) {
+ *              console.log('dir "%s"', oDir);
+ *            });
+ *          }
+ *        });
+ *
+ *      program
+ *        .command('*')
+ *        .description('deploy the given env')
+ *        .action(function(env) {
+ *          console.log('deploying "%s"', env);
+ *        });
+ *
+ *      program.parse(process.argv);
+  *
+ * @param {String} name
+ * @param {String} [desc] for git-style sub-commands
+ * @return {Command} the new command
+ * @api public
+ */
+
+Command.prototype.command = function(name, desc) {
+  var args = name.split(/ +/);
+  var cmd = new Command(args.shift());
+
+  if (desc) {
+    cmd.description(desc);
+    this.executables = true;
+    this._execs[cmd._name] = true;
+  }
+
+  this.commands.push(cmd);
+  cmd.parseExpectedArgs(args);
+  cmd.parent = this;
+
+  if (desc) return this;
+  return cmd;
+};
+
+/**
+ * Add an implicit `help [cmd]` subcommand
+ * which invokes `--help` for the given command.
+ *
+ * @api private
+ */
+
+Command.prototype.addImplicitHelpCommand = function() {
+  this.command('help [cmd]', 'display help for [cmd]');
+};
+
+/**
+ * Parse expected `args`.
+ *
+ * For example `["[type]"]` becomes `[{ required: false, name: 'type' }]`.
+ *
+ * @param {Array} args
+ * @return {Command} for chaining
+ * @api public
+ */
+
+Command.prototype.parseExpectedArgs = function(args) {
+  if (!args.length) return;
+  var self = this;
+  args.forEach(function(arg) {
+    var argDetails = {
+      required: false,
+      name: '',
+      variadic: false
+    };
+
+    switch (arg[0]) {
+      case '<':
+        argDetails.required = true;
+        argDetails.name = arg.slice(1, -1);
+        break;
+      case '[':
+        argDetails.name = arg.slice(1, -1);
+        break;
+    }
+
+    if (argDetails.name.length > 3 && argDetails.name.slice(-3) === '...') {
+      argDetails.variadic = true;
+      argDetails.name = argDetails.name.slice(0, -3);
+    }
+    if (argDetails.name) {
+      self._args.push(argDetails);
+    }
+  });
+  return this;
+};
+
+/**
+ * Register callback `fn` for the command.
+ *
+ * Examples:
+ *
+ *      program
+ *        .command('help')
+ *        .description('display verbose help')
+ *        .action(function() {
+ *           // output help here
+ *        });
+ *
+ * @param {Function} fn
+ * @return {Command} for chaining
+ * @api public
+ */
+
+Command.prototype.action = function(fn) {
+  var self = this;
+  var listener = function(args, unknown) {
+    // Parse any so-far unknown options
+    args = args || [];
+    unknown = unknown || [];
+
+    var parsed = self.parseOptions(unknown);
+
+    // Output help if necessary
+    outputHelpIfNecessary(self, parsed.unknown);
+
+    // If there are still any unknown options, then we simply
+    // die, unless someone asked for help, in which case we give it
+    // to them, and then we die.
+    if (parsed.unknown.length > 0) {
+      self.unknownOption(parsed.unknown[0]);
+    }
+
+    // Leftover arguments need to be pushed back. Fixes issue #56
+    if (parsed.args.length) args = parsed.args.concat(args);
+
+    self._args.forEach(function(arg, i) {
+      if (arg.required && null == args[i]) {
+        self.missingArgument(arg.name);
+      } else if (arg.variadic) {
+        if (i !== self._args.length - 1) {
+          self.variadicArgNotLast(arg.name);
+        }
+
+        args[i] = args.splice(i);
+      }
+    });
+
+    // Always append ourselves to the end of the arguments,
+    // to make sure we match the number of arguments the user
+    // expects
+    if (self._args.length) {
+      args[self._args.length] = self;
+    } else {
+      args.push(self);
+    }
+
+    fn.apply(self, args);
+  };
+  this.parent.on(this._name, listener);
+  if (this._alias) this.parent.on(this._alias, listener);
+  return this;
+};
+
+/**
+ * Define option with `flags`, `description` and optional
+ * coercion `fn`.
+ *
+ * The `flags` string should contain both the short and long flags,
+ * separated by comma, a pipe or space. The following are all valid
+ * all will output this way when `--help` is used.
+ *
+ *    "-p, --pepper"
+ *    "-p|--pepper"
+ *    "-p --pepper"
+ *
+ * Examples:
+ *
+ *     // simple boolean defaulting to false
+ *     program.option('-p, --pepper', 'add pepper');
+ *
+ *     --pepper
+ *     program.pepper
+ *     // => Boolean
+ *
+ *     // simple boolean defaulting to true
+ *     program.option('-C, --no-cheese', 'remove cheese');
+ *
+ *     program.cheese
+ *     // => true
+ *
+ *     --no-cheese
+ *     program.cheese
+ *     // => false
+ *
+ *     // required argument
+ *     program.option('-C, --chdir <path>', 'change the working directory');
+ *
+ *     --chdir /tmp
+ *     program.chdir
+ *     // => "/tmp"
+ *
+ *     // optional argument
+ *     program.option('-c, --cheese [type]', 'add cheese [marble]');
+ *
+ * @param {String} flags
+ * @param {String} description
+ * @param {Function|Mixed} fn or default
+ * @param {Mixed} defaultValue
+ * @return {Command} for chaining
+ * @api public
+ */
+
+Command.prototype.option = function(flags, description, fn, defaultValue) {
+  var self = this
+    , option = new Option(flags, description)
+    , oname = option.name()
+    , name = camelcase(oname);
+
+  // default as 3rd arg
+  if (typeof fn != 'function') {
+    if (fn instanceof RegExp) {
+      var regex = fn;
+      fn = function(val, def) {
+        var m = regex.exec(val);
+        return m ? m[0] : def;
+      }
+    }
+    else {
+      defaultValue = fn;
+      fn = null;
+    }
+  }
+
+  // preassign default value only for --no-*, [optional], or <required>
+  if (false == option.bool || option.optional || option.required) {
+    // when --no-* we make sure default is true
+    if (false == option.bool) defaultValue = true;
+    // preassign only if we have a default
+    if (undefined !== defaultValue) self[name] = defaultValue;
+  }
+
+  // register the option
+  this.options.push(option);
+
+  // when it's passed assign the value
+  // and conditionally invoke the callback
+  this.on(oname, function(val) {
+    // coercion
+    if (null !== val && fn) val = fn(val, undefined === self[name]
+      ? defaultValue
+      : self[name]);
+
+    // unassigned or bool
+    if ('boolean' == typeof self[name] || 'undefined' == typeof self[name]) {
+      // if no value, bool true, and we have a default, then use it!
+      if (null == val) {
+        self[name] = option.bool
+          ? defaultValue || true
+          : false;
+      } else {
+        self[name] = val;
+      }
+    } else if (null !== val) {
+      // reassign
+      self[name] = val;
+    }
+  });
+
+  return this;
+};
+
+/**
+ * Allow unknown options on the command line.
+ *
+ * @param {Boolean} arg if `true` or omitted, no error will be thrown
+ * for unknown options.
+ * @api public
+ */
+Command.prototype.allowUnknownOption = function(arg) {
+    this._allowUnknownOption = arguments.length === 0 || arg;
+    return this;
+};
+
+/**
+ * Parse `argv`, settings options and invoking commands when defined.
+ *
+ * @param {Array} argv
+ * @return {Command} for chaining
+ * @api public
+ */
+
+Command.prototype.parse = function(argv) {
+  // implicit help
+  if (this.executables) this.addImplicitHelpCommand();
+
+  // store raw args
+  this.rawArgs = argv;
+
+  // guess name
+  this._name = this._name || basename(argv[1], '.js');
+
+  // process argv
+  var parsed = this.parseOptions(this.normalize(argv.slice(2)));
+  var args = this.args = parsed.args;
+
+  var result = this.parseArgs(this.args, parsed.unknown);
+
+  // executable sub-commands
+  var name = result.args[0];
+  if (this._execs[name] && typeof this._execs[name] != "function") {
+    return this.executeSubCommand(argv, args, parsed.unknown);
+  }
+
+  return result;
+};
+
+/**
+ * Execute a sub-command executable.
+ *
+ * @param {Array} argv
+ * @param {Array} args
+ * @param {Array} unknown
+ * @api private
+ */
+
+Command.prototype.executeSubCommand = function(argv, args, unknown) {
+  args = args.concat(unknown);
+
+  if (!args.length) this.help();
+  if ('help' == args[0] && 1 == args.length) this.help();
+
+  // <cmd> --help
+  if ('help' == args[0]) {
+    args[0] = args[1];
+    args[1] = '--help';
+  }
+
+  // executable
+  var f = argv[1];
+  var link = readlink(f);
+  if (link !== f && link.charAt(0) !== '/') {
+    link = path.join(dirname(f), link)
+  }
+  var dir = dirname(link);
+  var bin = basename(f, '.js') + '-' + args[0];
+
+  // prefer local `./<bin>` to bin in the $PATH
+  var local = path.join(dir, bin);
+  try {
+    // for versions before node v0.8 when there weren't `fs.existsSync`
+    if (fs.statSync(local).isFile()) {
+      bin = local;
+    }
+  } catch (e) {}
+
+  // run it
+  args = args.slice(1);
+
+  var proc;
+  if (process.platform !== 'win32') {
+    proc = spawn(bin, args, { stdio: 'inherit', customFds: [0, 1, 2] });
+  } else {
+    args.unshift(local);
+    proc = spawn(process.execPath, args, { stdio: 'inherit'});
+  }
+
+  proc.on('close', process.exit.bind(process));
+  proc.on('error', function(err) {
+    if (err.code == "ENOENT") {
+      console.error('\n  %s(1) does not exist, try --help\n', bin);
+    } else if (err.code == "EACCES") {
+      console.error('\n  %s(1) not executable. try chmod or run with root\n', bin);
+    }
+    process.exit(1);
+  });
+
+  this.runningCommand = proc;
+};
+
+/**
+ * Normalize `args`, splitting joined short flags. For example
+ * the arg "-abc" is equivalent to "-a -b -c".
+ * This also normalizes equal sign and splits "--abc=def" into "--abc def".
+ *
+ * @param {Array} args
+ * @return {Array}
+ * @api private
+ */
+
+Command.prototype.normalize = function(args) {
+  var ret = []
+    , arg
+    , lastOpt
+    , index;
+
+  for (var i = 0, len = args.length; i < len; ++i) {
+    arg = args[i];
+    if (i > 0) {
+      lastOpt = this.optionFor(args[i-1]);
+    }
+
+    if (arg === '--') {
+      // Honor option terminator
+      ret = ret.concat(args.slice(i));
+      break;
+    } else if (lastOpt && lastOpt.required) {
+      ret.push(arg);
+    } else if (arg.length > 1 && '-' == arg[0] && '-' != arg[1]) {
+      arg.slice(1).split('').forEach(function(c) {
+        ret.push('-' + c);
+      });
+    } else if (/^--/.test(arg) && ~(index = arg.indexOf('='))) {
+      ret.push(arg.slice(0, index), arg.slice(index + 1));
+    } else {
+      ret.push(arg);
+    }
+  }
+
+  return ret;
+};
+
+/**
+ * Parse command `args`.
+ *
+ * When listener(s) are available those
+ * callbacks are invoked, otherwise the "*"
+ * event is emitted and those actions are invoked.
+ *
+ * @param {Array} args
+ * @return {Command} for chaining
+ * @api private
+ */
+
+Command.prototype.parseArgs = function(args, unknown) {
+  var name;
+
+  if (args.length) {
+    name = args[0];
+    if (this.listeners(name).length) {
+      this.emit(args.shift(), args, unknown);
+    } else {
+      this.emit('*', args);
+    }
+  } else {
+    outputHelpIfNecessary(this, unknown);
+
+    // If there were no args and we have unknown options,
+    // then they are extraneous and we need to error.
+    if (unknown.length > 0) {
+      this.unknownOption(unknown[0]);
+    }
+  }
+
+  return this;
+};
+
+/**
+ * Return an option matching `arg` if any.
+ *
+ * @param {String} arg
+ * @return {Option}
+ * @api private
+ */
+
+Command.prototype.optionFor = function(arg) {
+  for (var i = 0, len = this.options.length; i < len; ++i) {
+    if (this.options[i].is(arg)) {
+      return this.options[i];
+    }
+  }
+};
+
+/**
+ * Parse options from `argv` returning `argv`
+ * void of these options.
+ *
+ * @param {Array} argv
+ * @return {Array}
+ * @api public
+ */
+
+Command.prototype.parseOptions = function(argv) {
+  var args = []
+    , len = argv.length
+    , literal
+    , option
+    , arg;
+
+  var unknownOptions = [];
+
+  // parse options
+  for (var i = 0; i < len; ++i) {
+    arg = argv[i];
+
+    // literal args after --
+    if ('--' == arg) {
+      literal = true;
+      continue;
+    }
+
+    if (literal) {
+      args.push(arg);
+      continue;
+    }
+
+    // find matching Option
+    option = this.optionFor(arg);
+
+    // option is defined
+    if (option) {
+      // requires arg
+      if (option.required) {
+        arg = argv[++i];
+        if (null == arg) return this.optionMissingArgument(option);
+        this.emit(option.name(), arg);
+      // optional arg
+      } else if (option.optional) {
+        arg = argv[i+1];
+        if (null == arg || ('-' == arg[0] && '-' != arg)) {
+          arg = null;
+        } else {
+          ++i;
+        }
+        this.emit(option.name(), arg);
+      // bool
+      } else {
+        this.emit(option.name());
+      }
+      continue;
+    }
+
+    // looks like an option
+    if (arg.length > 1 && '-' == arg[0]) {
+      unknownOptions.push(arg);
+
+      // If the next argument looks like it might be
+      // an argument for this option, we pass it on.
+      // If it isn't, then it'll simply be ignored
+      if (argv[i+1] && '-' != argv[i+1][0]) {
+        unknownOptions.push(argv[++i]);
+      }
+      continue;
+    }
+
+    // arg
+    args.push(arg);
+  }
+
+  return { args: args, unknown: unknownOptions };
+};
+
+/**
+ * Return an object containing options as key-value pairs
+ *
+ * @return {Object}
+ * @api public
+ */
+Command.prototype.opts = function() {
+  var result = {}
+    , len = this.options.length;
+
+  for (var i = 0 ; i < len; i++) {
+    var key = camelcase(this.options[i].name());
+    result[key] = key === 'version' ? this._version : this[key];
+  }
+  return result;
+};
+
+/**
+ * Argument `name` is missing.
+ *
+ * @param {String} name
+ * @api private
+ */
+
+Command.prototype.missingArgument = function(name) {
+  console.error();
+  console.error("  error: missing required argument `%s'", name);
+  console.error();
+  process.exit(1);
+};
+
+/**
+ * `Option` is missing an argument, but received `flag` or nothing.
+ *
+ * @param {String} option
+ * @param {String} flag
+ * @api private
+ */
+
+Command.prototype.optionMissingArgument = function(option, flag) {
+  console.error();
+  if (flag) {
+    console.error("  error: option `%s' argument missing, got `%s'", option.flags, flag);
+  } else {
+    console.error("  error: option `%s' argument missing", option.flags);
+  }
+  console.error();
+  process.exit(1);
+};
+
+/**
+ * Unknown option `flag`.
+ *
+ * @param {String} flag
+ * @api private
+ */
+
+Command.prototype.unknownOption = function(flag) {
+  if (this._allowUnknownOption) return;
+  console.error();
+  console.error("  error: unknown option `%s'", flag);
+  console.error();
+  process.exit(1);
+};
+
+/**
+ * Variadic argument with `name` is not the last argument as required.
+ *
+ * @param {String} name
+ * @api private
+ */
+
+Command.prototype.variadicArgNotLast = function(name) {
+  console.error();
+  console.error("  error: variadic arguments must be last `%s'", name);
+  console.error();
+  process.exit(1);
+};
+
+/**
+ * Set the program version to `str`.
+ *
+ * This method auto-registers the "-V, --version" flag
+ * which will print the version number when passed.
+ *
+ * @param {String} str
+ * @param {String} flags
+ * @return {Command} for chaining
+ * @api public
+ */
+
+Command.prototype.version = function(str, flags) {
+  if (0 == arguments.length) return this._version;
+  this._version = str;
+  flags = flags || '-V, --version';
+  this.option(flags, 'output the version number');
+  this.on('version', function() {
+    process.stdout.write(str + '\n');
+    process.exit(0);
+  });
+  return this;
+};
+
+/**
+ * Set the description to `str`.
+ *
+ * @param {String} str
+ * @return {String|Command}
+ * @api public
+ */
+
+Command.prototype.description = function(str) {
+  if (0 == arguments.length) return this._description;
+  this._description = str;
+  return this;
+};
+
+/**
+ * Set an alias for the command
+ *
+ * @param {String} alias
+ * @return {String|Command}
+ * @api public
+ */
+
+Command.prototype.alias = function(alias) {
+  if (0 == arguments.length) return this._alias;
+  this._alias = alias;
+  return this;
+};
+
+/**
+ * Set / get the command usage `str`.
+ *
+ * @param {String} str
+ * @return {String|Command}
+ * @api public
+ */
+
+Command.prototype.usage = function(str) {
+  var args = this._args.map(function(arg) {
+    return humanReadableArgName(arg);
+  });
+
+  var usage = '[options]'
+    + (this.commands.length ? ' [command]' : '')
+    + (this._args.length ? ' ' + args.join(' ') : '');
+
+  if (0 == arguments.length) return this._usage || usage;
+  this._usage = str;
+
+  return this;
+};
+
+/**
+ * Get the name of the command
+ *
+ * @param {String} name
+ * @return {String|Command}
+ * @api public
+ */
+
+Command.prototype.name = function() {
+  return this._name;
+};
+
+/**
+ * Return the largest option length.
+ *
+ * @return {Number}
+ * @api private
+ */
+
+Command.prototype.largestOptionLength = function() {
+  return this.options.reduce(function(max, option) {
+    return Math.max(max, option.flags.length);
+  }, 0);
+};
+
+/**
+ * Return help for options.
+ *
+ * @return {String}
+ * @api private
+ */
+
+Command.prototype.optionHelp = function() {
+  var width = this.largestOptionLength();
+
+  // Prepend the help information
+  return [pad('-h, --help', width) + '  ' + 'output usage information']
+    .concat(this.options.map(function(option) {
+      return pad(option.flags, width) + '  ' + option.description;
+      }))
+    .join('\n');
+};
+
+/**
+ * Return command help documentation.
+ *
+ * @return {String}
+ * @api private
+ */
+
+Command.prototype.commandHelp = function() {
+  if (!this.commands.length) return '';
+
+  var commands = this.commands.map(function(cmd) {
+    var args = cmd._args.map(function(arg) {
+      return humanReadableArgName(arg);
+    }).join(' ');
+
+    return [
+      cmd._name
+        + (cmd._alias
+          ? '|' + cmd._alias
+          : '')
+        + (cmd.options.length
+          ? ' [options]'
+          : '')
+        + ' ' + args
+    , cmd.description()
+    ];
+  });
+
+  var width = commands.reduce(function(max, command) {
+    return Math.max(max, command[0].length);
+  }, 0);
+
+  return [
+      ''
+    , '  Commands:'
+    , ''
+    , commands.map(function(cmd) {
+      return pad(cmd[0], width) + '  ' + cmd[1];
+    }).join('\n').replace(/^/gm, '    ')
+    , ''
+  ].join('\n');
+};
+
+/**
+ * Return program help documentation.
+ *
+ * @return {String}
+ * @api private
+ */
+
+Command.prototype.helpInformation = function() {
+  var desc = [];
+  if (this._description) {
+    desc = [
+      '  ' + this._description
+      , ''
+    ];
+  }
+
+  var cmdName = this._name;
+  if (this._alias) {
+    cmdName = cmdName + '|' + this._alias;
+  }
+  var usage = [
+    ''
+    ,'  Usage: ' + cmdName + ' ' + this.usage()
+    , ''
+  ];
+
+  var cmds = [];
+  var commandHelp = this.commandHelp();
+  if (commandHelp) cmds = [commandHelp];
+
+  var options = [
+    '  Options:'
+    , ''
+    , '' + this.optionHelp().replace(/^/gm, '    ')
+    , ''
+    , ''
+  ];
+
+  return usage
+    .concat(cmds)
+    .concat(desc)
+    .concat(options)
+    .join('\n');
+};
+
+/**
+ * Output help information for this command
+ *
+ * @api public
+ */
+
+Command.prototype.outputHelp = function() {
+  process.stdout.write(this.helpInformation());
+  this.emit('--help');
+};
+
+/**
+ * Output help information and exit.
+ *
+ * @api public
+ */
+
+Command.prototype.help = function() {
+  this.outputHelp();
+  process.exit();
+};
+
+/**
+ * Camel-case the given `flag`
+ *
+ * @param {String} flag
+ * @return {String}
+ * @api private
+ */
+
+function camelcase(flag) {
+  return flag.split('-').reduce(function(str, word) {
+    return str + word[0].toUpperCase() + word.slice(1);
+  });
+}
+
+/**
+ * Pad `str` to `width`.
+ *
+ * @param {String} str
+ * @param {Number} width
+ * @return {String}
+ * @api private
+ */
+
+function pad(str, width) {
+  var len = Math.max(0, width - str.length);
+  return str + Array(len + 1).join(' ');
+}
+
+/**
+ * Output help information if necessary
+ *
+ * @param {Command} command to output help for
+ * @param {Array} array of options to search for -h or --help
+ * @api private
+ */
+
+function outputHelpIfNecessary(cmd, options) {
+  options = options || [];
+  for (var i = 0; i < options.length; i++) {
+    if (options[i] == '--help' || options[i] == '-h') {
+      cmd.outputHelp();
+      process.exit(0);
+    }
+  }
+}
+
+/**
+ * Takes an argument an returns its human readable equivalent for help usage.
+ *
+ * @param {Object} arg
+ * @return {String}
+ * @api private
+ */
+
+function humanReadableArgName(arg) {
+  var nameOutput = arg.name + (arg.variadic === true ? '...' : '');
+
+  return arg.required
+    ? '<' + nameOutput + '>'
+    : '[' + nameOutput + ']'
+}
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/.npmignore b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/.npmignore
new file mode 100644
index 0000000..3ac7d16
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/.npmignore
@@ -0,0 +1,3 @@
+.idea/
+.DS_Store
+node_modules/
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/.travis.yml b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/.travis.yml
new file mode 100644
index 0000000..baf9be7
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/.travis.yml
@@ -0,0 +1,5 @@
+language: node_js
+node_js:
+  - "0.10"
+  - "0.12"
+  - "io.js"
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/LICENSE b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/LICENSE
new file mode 100644
index 0000000..d1f842f
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Zhiye Li
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/README.md b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/README.md
new file mode 100644
index 0000000..fc63b50
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/README.md
@@ -0,0 +1,17 @@
+# graceful-readlink
+[![NPM Version](http://img.shields.io/npm/v/graceful-readlink.svg?style=flat)](https://www.npmjs.org/package/graceful-readlink)
+[![NPM Downloads](https://img.shields.io/npm/dm/graceful-readlink.svg?style=flat)](https://www.npmjs.org/package/graceful-readlink)
+
+
+## Usage
+
+```js
+var readlinkSync = require('graceful-readlink').readlinkSync;
+console.log(readlinkSync(f));
+// output
+//  the file pointed to when `f` is a symbolic link
+//  the `f` itself when `f` is not a symbolic link
+```
+## Licence
+
+MIT License
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/index.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/index.js
new file mode 100644
index 0000000..7e9fc70
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/index.js
@@ -0,0 +1,12 @@
+var fs = require('fs')
+  , lstat = fs.lstatSync;
+
+exports.readlinkSync = function (p) {
+  if (lstat(p).isSymbolicLink()) {
+    return fs.readlinkSync(p);
+  } else {
+    return p;
+  }
+};
+
+
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/package.json b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/package.json
new file mode 100644
index 0000000..00bc841
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/package.json
@@ -0,0 +1,48 @@
+{
+  "name": "graceful-readlink",
+  "version": "1.0.1",
+  "description": "graceful fs.readlink",
+  "main": "index.js",
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/zhiyelee/graceful-readlink.git"
+  },
+  "homepage": "https://github.com/zhiyelee/graceful-readlink",
+  "bugs": {
+    "url": "https://github.com/zhiyelee/graceful-readlink/issues"
+  },
+  "keywords": [
+    "fs.readlink",
+    "readlink"
+  ],
+  "author": {
+    "name": "zhiyelee"
+  },
+  "license": "MIT",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "gitHead": "f6655275bebef706fb63fd01b5f062a7052419a5",
+  "_id": "graceful-readlink@1.0.1",
+  "_shasum": "4cafad76bc62f02fa039b2f94e9a3dd3a391a725",
+  "_from": "graceful-readlink@>= 1.0.0",
+  "_npmVersion": "2.1.17",
+  "_nodeVersion": "0.11.14",
+  "_npmUser": {
+    "name": "zhiyelee",
+    "email": "zhiyelee@gmail.com"
+  },
+  "maintainers": [
+    {
+      "name": "zhiyelee",
+      "email": "zhiyelee@gmail.com"
+    }
+  ],
+  "dist": {
+    "shasum": "4cafad76bc62f02fa039b2f94e9a3dd3a391a725",
+    "tarball": "http://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz"
+  },
+  "directories": {},
+  "_resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/commander/package.json b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/commander/package.json
new file mode 100644
index 0000000..d39156f
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/commander/package.json
@@ -0,0 +1,74 @@
+{
+  "name": "commander",
+  "version": "2.7.1",
+  "description": "the complete solution for node.js command-line programs",
+  "keywords": [
+    "command",
+    "option",
+    "parser"
+  ],
+  "author": {
+    "name": "TJ Holowaychuk",
+    "email": "tj@vision-media.ca"
+  },
+  "license": "MIT",
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/tj/commander.js.git"
+  },
+  "devDependencies": {
+    "should": ">= 0.0.1"
+  },
+  "scripts": {
+    "test": "make test"
+  },
+  "main": "index",
+  "engines": {
+    "node": ">= 0.6.x"
+  },
+  "files": [
+    "index.js"
+  ],
+  "dependencies": {
+    "graceful-readlink": ">= 1.0.0"
+  },
+  "gitHead": "103654f8f32c010ad1e62cefc9ab92d7c8d18c8e",
+  "bugs": {
+    "url": "https://github.com/tj/commander.js/issues"
+  },
+  "homepage": "https://github.com/tj/commander.js",
+  "_id": "commander@2.7.1",
+  "_shasum": "5d419a2bbed2c32ee3e4dca9bb45ab83ecc3065a",
+  "_from": "commander@^2.3.0",
+  "_npmVersion": "2.1.17",
+  "_nodeVersion": "0.11.14",
+  "_npmUser": {
+    "name": "zhiyelee",
+    "email": "zhiyelee@gmail.com"
+  },
+  "maintainers": [
+    {
+      "name": "tjholowaychuk",
+      "email": "tj@vision-media.ca"
+    },
+    {
+      "name": "somekittens",
+      "email": "rkoutnik@gmail.com"
+    },
+    {
+      "name": "zhiyelee",
+      "email": "zhiyelee@gmail.com"
+    },
+    {
+      "name": "thethomaseffect",
+      "email": "thethomaseffect@gmail.com"
+    }
+  ],
+  "dist": {
+    "shasum": "5d419a2bbed2c32ee3e4dca9bb45ab83ecc3065a",
+    "tarball": "http://registry.npmjs.org/commander/-/commander-2.7.1.tgz"
+  },
+  "directories": {},
+  "_resolved": "https://registry.npmjs.org/commander/-/commander-2.7.1.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/debug/Readme.md b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/debug/Readme.md
new file mode 100644
index 0000000..c5a34e8
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/debug/Readme.md
@@ -0,0 +1,115 @@
+# debug
+
+  tiny node.js debugging utility modelled after node core's debugging technique.
+
+## Installation
+
+```
+$ npm install debug
+```
+
+## Usage
+
+ With `debug` you simply invoke the exported function to generate your debug function, passing it a name which will determine if a noop function is returned, or a decorated `console.error`, so all of the `console` format string goodies you're used to work fine. A unique color is selected per-function for visibility.
+ 
+Example _app.js_:
+
+```js
+var debug = require('debug')('http')
+  , http = require('http')
+  , name = 'My App';
+
+// fake app
+
+debug('booting %s', name);
+
+http.createServer(function(req, res){
+  debug(req.method + ' ' + req.url);
+  res.end('hello\n');
+}).listen(3000, function(){
+  debug('listening');
+});
+
+// fake worker of some kind
+
+require('./worker');
+```
+
+Example _worker.js_:
+
+```js
+var debug = require('debug')('worker');
+
+setInterval(function(){
+  debug('doing some work');
+}, 1000);
+```
+
+ The __DEBUG__ environment variable is then used to enable these based on space or comma-delimited names. Here are some examples:
+
+  ![debug http and worker](http://f.cl.ly/items/18471z1H402O24072r1J/Screenshot.png)
+
+  ![debug worker](http://f.cl.ly/items/1X413v1a3M0d3C2c1E0i/Screenshot.png)
+
+## Millisecond diff
+
+  When actively developing an application it can be useful to see when the time spent between one `debug()` call and the next. Suppose for example you invoke `debug()` before requesting a resource, and after as well, the "+NNNms" will show you how much time was spent between calls.
+
+  ![](http://f.cl.ly/items/2i3h1d3t121M2Z1A3Q0N/Screenshot.png)
+
+  When stderr is not a TTY, `Date#toUTCString()` is used, making it more useful for logging the debug information as shown below:
+  _(NOTE: Debug now uses stderr instead of stdout, so the correct shell command for this example is actually `DEBUG=* node example/worker 2> out &`)_
+  
+  ![](http://f.cl.ly/items/112H3i0e0o0P0a2Q2r11/Screenshot.png)
+  
+## Conventions
+
+ If you're using this in one or more of your libraries, you _should_ use the name of your library so that developers may toggle debugging as desired without guessing names. If you have more than one debuggers you _should_ prefix them with your library name and use ":" to separate features. For example "bodyParser" from Connect would then be "connect:bodyParser". 
+
+## Wildcards
+
+  The "*" character may be used as a wildcard. Suppose for example your library has debuggers named "connect:bodyParser", "connect:compress", "connect:session", instead of listing all three with `DEBUG=connect:bodyParser,connect.compress,connect:session`, you may simply do `DEBUG=connect:*`, or to run everything using this module simply use `DEBUG=*`.
+
+  You can also exclude specific debuggers by prefixing them with a "-" character.  For example, `DEBUG=* -connect:*` would include all debuggers except those starting with "connect:".
+
+## Browser support
+
+ Debug works in the browser as well, currently persisted by `localStorage`. For example if you have `worker:a` and `worker:b` as shown below, and wish to debug both type `debug.enable('worker:*')` in the console and refresh the page, this will remain until you disable with `debug.disable()`. 
+
+```js
+a = debug('worker:a');
+b = debug('worker:b');
+
+setInterval(function(){
+  a('doing some work');
+}, 1000);
+
+setInterval(function(){
+  a('doing some work');
+}, 1200);
+```
+
+## License 
+
+(The MIT License)
+
+Copyright (c) 2011 TJ Holowaychuk &lt;tj@vision-media.ca&gt;
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/debug/debug.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/debug/debug.js
new file mode 100644
index 0000000..509dc0d
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/debug/debug.js
@@ -0,0 +1,137 @@
+
+/**
+ * Expose `debug()` as the module.
+ */
+
+module.exports = debug;
+
+/**
+ * Create a debugger with the given `name`.
+ *
+ * @param {String} name
+ * @return {Type}
+ * @api public
+ */
+
+function debug(name) {
+  if (!debug.enabled(name)) return function(){};
+
+  return function(fmt){
+    fmt = coerce(fmt);
+
+    var curr = new Date;
+    var ms = curr - (debug[name] || curr);
+    debug[name] = curr;
+
+    fmt = name
+      + ' '
+      + fmt
+      + ' +' + debug.humanize(ms);
+
+    // This hackery is required for IE8
+    // where `console.log` doesn't have 'apply'
+    window.console
+      && console.log
+      && Function.prototype.apply.call(console.log, console, arguments);
+  }
+}
+
+/**
+ * The currently active debug mode names.
+ */
+
+debug.names = [];
+debug.skips = [];
+
+/**
+ * Enables a debug mode by name. This can include modes
+ * separated by a colon and wildcards.
+ *
+ * @param {String} name
+ * @api public
+ */
+
+debug.enable = function(name) {
+  try {
+    localStorage.debug = name;
+  } catch(e){}
+
+  var split = (name || '').split(/[\s,]+/)
+    , len = split.length;
+
+  for (var i = 0; i < len; i++) {
+    name = split[i].replace('*', '.*?');
+    if (name[0] === '-') {
+      debug.skips.push(new RegExp('^' + name.substr(1) + '$'));
+    }
+    else {
+      debug.names.push(new RegExp('^' + name + '$'));
+    }
+  }
+};
+
+/**
+ * Disable debug output.
+ *
+ * @api public
+ */
+
+debug.disable = function(){
+  debug.enable('');
+};
+
+/**
+ * Humanize the given `ms`.
+ *
+ * @param {Number} m
+ * @return {String}
+ * @api private
+ */
+
+debug.humanize = function(ms) {
+  var sec = 1000
+    , min = 60 * 1000
+    , hour = 60 * min;
+
+  if (ms >= hour) return (ms / hour).toFixed(1) + 'h';
+  if (ms >= min) return (ms / min).toFixed(1) + 'm';
+  if (ms >= sec) return (ms / sec | 0) + 's';
+  return ms + 'ms';
+};
+
+/**
+ * Returns true if the given mode name is enabled, false otherwise.
+ *
+ * @param {String} name
+ * @return {Boolean}
+ * @api public
+ */
+
+debug.enabled = function(name) {
+  for (var i = 0, len = debug.skips.length; i < len; i++) {
+    if (debug.skips[i].test(name)) {
+      return false;
+    }
+  }
+  for (var i = 0, len = debug.names.length; i < len; i++) {
+    if (debug.names[i].test(name)) {
+      return true;
+    }
+  }
+  return false;
+};
+
+/**
+ * Coerce `val`.
+ */
+
+function coerce(val) {
+  if (val instanceof Error) return val.stack || val.message;
+  return val;
+}
+
+// persist
+
+try {
+  if (window.localStorage) debug.enable(localStorage.debug);
+} catch(e){}
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/debug/index.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/debug/index.js
new file mode 100644
index 0000000..e02c13b
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/debug/index.js
@@ -0,0 +1,5 @@
+if ('undefined' == typeof window) {
+  module.exports = require('./lib/debug');
+} else {
+  module.exports = require('./debug');
+}
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/debug/lib/debug.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/debug/lib/debug.js
new file mode 100644
index 0000000..3b0a918
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/debug/lib/debug.js
@@ -0,0 +1,147 @@
+/**
+ * Module dependencies.
+ */
+
+var tty = require('tty');
+
+/**
+ * Expose `debug()` as the module.
+ */
+
+module.exports = debug;
+
+/**
+ * Enabled debuggers.
+ */
+
+var names = []
+  , skips = [];
+
+(process.env.DEBUG || '')
+  .split(/[\s,]+/)
+  .forEach(function(name){
+    name = name.replace('*', '.*?');
+    if (name[0] === '-') {
+      skips.push(new RegExp('^' + name.substr(1) + '$'));
+    } else {
+      names.push(new RegExp('^' + name + '$'));
+    }
+  });
+
+/**
+ * Colors.
+ */
+
+var colors = [6, 2, 3, 4, 5, 1];
+
+/**
+ * Previous debug() call.
+ */
+
+var prev = {};
+
+/**
+ * Previously assigned color.
+ */
+
+var prevColor = 0;
+
+/**
+ * Is stdout a TTY? Colored output is disabled when `true`.
+ */
+
+var isatty = tty.isatty(2);
+
+/**
+ * Select a color.
+ *
+ * @return {Number}
+ * @api private
+ */
+
+function color() {
+  return colors[prevColor++ % colors.length];
+}
+
+/**
+ * Humanize the given `ms`.
+ *
+ * @param {Number} m
+ * @return {String}
+ * @api private
+ */
+
+function humanize(ms) {
+  var sec = 1000
+    , min = 60 * 1000
+    , hour = 60 * min;
+
+  if (ms >= hour) return (ms / hour).toFixed(1) + 'h';
+  if (ms >= min) return (ms / min).toFixed(1) + 'm';
+  if (ms >= sec) return (ms / sec | 0) + 's';
+  return ms + 'ms';
+}
+
+/**
+ * Create a debugger with the given `name`.
+ *
+ * @param {String} name
+ * @return {Type}
+ * @api public
+ */
+
+function debug(name) {
+  function disabled(){}
+  disabled.enabled = false;
+
+  var match = skips.some(function(re){
+    return re.test(name);
+  });
+
+  if (match) return disabled;
+
+  match = names.some(function(re){
+    return re.test(name);
+  });
+
+  if (!match) return disabled;
+  var c = color();
+
+  function colored(fmt) {
+    fmt = coerce(fmt);
+
+    var curr = new Date;
+    var ms = curr - (prev[name] || curr);
+    prev[name] = curr;
+
+    fmt = '  \u001b[9' + c + 'm' + name + ' '
+      + '\u001b[3' + c + 'm\u001b[90m'
+      + fmt + '\u001b[3' + c + 'm'
+      + ' +' + humanize(ms) + '\u001b[0m';
+
+    console.error.apply(this, arguments);
+  }
+
+  function plain(fmt) {
+    fmt = coerce(fmt);
+
+    fmt = new Date().toUTCString()
+      + ' ' + name + ' ' + fmt;
+    console.error.apply(this, arguments);
+  }
+
+  colored.enabled = plain.enabled = true;
+
+  return isatty || process.env.DEBUG_COLORS
+    ? colored
+    : plain;
+}
+
+/**
+ * Coerce `val`.
+ */
+
+function coerce(val) {
+  if (val instanceof Error) return val.stack || val.message;
+  return val;
+}
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/debug/package.json b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/debug/package.json
new file mode 100644
index 0000000..a88241d
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/debug/package.json
@@ -0,0 +1,65 @@
+{
+  "name": "debug",
+  "version": "0.7.4",
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/visionmedia/debug.git"
+  },
+  "description": "small debugging utility",
+  "keywords": [
+    "debug",
+    "log",
+    "debugger"
+  ],
+  "author": {
+    "name": "TJ Holowaychuk",
+    "email": "tj@vision-media.ca"
+  },
+  "dependencies": {},
+  "devDependencies": {
+    "mocha": "*"
+  },
+  "main": "lib/debug.js",
+  "browser": "./debug.js",
+  "engines": {
+    "node": "*"
+  },
+  "files": [
+    "lib/debug.js",
+    "debug.js",
+    "index.js"
+  ],
+  "component": {
+    "scripts": {
+      "debug/index.js": "index.js",
+      "debug/debug.js": "debug.js"
+    }
+  },
+  "bugs": {
+    "url": "https://github.com/visionmedia/debug/issues"
+  },
+  "homepage": "https://github.com/visionmedia/debug",
+  "_id": "debug@0.7.4",
+  "dist": {
+    "shasum": "06e1ea8082c2cb14e39806e22e2f6f757f92af39",
+    "tarball": "http://registry.npmjs.org/debug/-/debug-0.7.4.tgz"
+  },
+  "_from": "debug@~0.7.4",
+  "_npmVersion": "1.3.13",
+  "_npmUser": {
+    "name": "tjholowaychuk",
+    "email": "tj@vision-media.ca"
+  },
+  "maintainers": [
+    {
+      "name": "tjholowaychuk",
+      "email": "tj@vision-media.ca"
+    }
+  ],
+  "directories": {},
+  "_shasum": "06e1ea8082c2cb14e39806e22e2f6f757f92af39",
+  "_resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz",
+  "readme": "# debug\n\n  tiny node.js debugging utility modelled after node core's debugging technique.\n\n## Installation\n\n```\n$ npm install debug\n```\n\n## Usage\n\n With `debug` you simply invoke the exported function to generate your debug function, passing it a name which will determine if a noop function is returned, or a decorated `console.error`, so all of the `console` format string goodies you're used to work fine. A unique color is selected per-function for visibility.\n \nExample _app.js_:\n\n```js\nvar debug = require('debug')('http')\n  , http = require('http')\n  , name = 'My App';\n\n// fake app\n\ndebug('booting %s', name);\n\nhttp.createServer(function(req, res){\n  debug(req.method + ' ' + req.url);\n  res.end('hello\\n');\n}).listen(3000, function(){\n  debug('listening');\n});\n\n// fake worker of some kind\n\nrequire('./worker');\n```\n\nExample _worker.js_:\n\n```js\nvar debug = require('debug')('worker');\n\nsetInterval(function(){\n  debug('doing some work');\n}, 1000);\n```\n\n The __DEBUG__ environment variable is then used to enable these based on space or comma-delimited names. Here are some examples:\n\n  ![debug http and worker](http://f.cl.ly/items/18471z1H402O24072r1J/Screenshot.png)\n\n  ![debug worker](http://f.cl.ly/items/1X413v1a3M0d3C2c1E0i/Screenshot.png)\n\n## Millisecond diff\n\n  When actively developing an application it can be useful to see when the time spent between one `debug()` call and the next. Suppose for example you invoke `debug()` before requesting a resource, and after as well, the \"+NNNms\" will show you how much time was spent between calls.\n\n  ![](http://f.cl.ly/items/2i3h1d3t121M2Z1A3Q0N/Screenshot.png)\n\n  When stderr is not a TTY, `Date#toUTCString()` is used, making it more useful for logging the debug information as shown below:\n  _(NOTE: Debug now uses stderr instead of stdout, so the correct shell command for this example is actually `DEBUG=* node example/worker 2> out &`)_\n  \n  ![](http://f.cl.ly/items/112H3i0e0o0P0a2Q2r11/Screenshot.png)\n  \n## Conventions\n\n If you're using this in one or more of your libraries, you _should_ use the name of your library so that developers may toggle debugging as desired without guessing names. If you have more than one debuggers you _should_ prefix them with your library name and use \":\" to separate features. For example \"bodyParser\" from Connect would then be \"connect:bodyParser\". \n\n## Wildcards\n\n  The \"*\" character may be used as a wildcard. Suppose for example your library has debuggers named \"connect:bodyParser\", \"connect:compress\", \"connect:session\", instead of listing all three with `DEBUG=connect:bodyParser,connect.compress,connect:session`, you may simply do `DEBUG=connect:*`, or to run everything using this module simply use `DEBUG=*`.\n\n  You can also exclude specific debuggers by prefixing them with a \"-\" character.  For example, `DEBUG=* -connect:*` would include all debuggers except those starting with \"connect:\".\n\n## Browser support\n\n Debug works in the browser as well, currently persisted by `localStorage`. For example if you have `worker:a` and `worker:b` as shown below, and wish to debug both type `debug.enable('worker:*')` in the console and refresh the page, this will remain until you disable with `debug.disable()`. \n\n```js\na = debug('worker:a');\nb = debug('worker:b');\n\nsetInterval(function(){\n  a('doing some work');\n}, 1000);\n\nsetInterval(function(){\n  a('doing some work');\n}, 1200);\n```\n\n## License \n\n(The MIT License)\n\nCopyright (c) 2011 TJ Holowaychuk &lt;tj@vision-media.ca&gt;\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n'Software'), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n",
+  "readmeFilename": "Readme.md",
+  "scripts": {}
+}
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/.jscsrc b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/.jscsrc
new file mode 100644
index 0000000..06245e6
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/.jscsrc
@@ -0,0 +1,64 @@
+{
+  "excludeFiles": [
+    "./js/forge.bundle.js",
+    "./js/forge.min.js",
+    "./js/jsbn.js",
+    "./nodejs/ui/require.js",
+    "./nodejs/ui/test.min.js"
+  ],
+  "disallowKeywords": ["with"],
+  "disallowKeywordsOnNewLine": ["else", "catch"],
+  // FIXME: enable this?
+  //"disallowImplicitTypeConversion": ["string"],
+  "disallowMixedSpacesAndTabs": true,
+  "disallowMultipleLineBreaks": true,
+  // FIXME: enable this or do we prefer to
+  // use w/angular directive templates?
+  //"disallowMultipleLineStrings": true,
+  "disallowNewlineBeforeBlockStatements": true,
+  "disallowSpaceAfterObjectKeys": true,
+  "disallowSpaceAfterPrefixUnaryOperators": ["++", "--", "+", "-", "~", "!"],
+  "disallowSpaceBeforeBinaryOperators": [","],
+  "disallowSpaceBeforePostfixUnaryOperators": ["++", "--"],
+  "disallowSpacesInAnonymousFunctionExpression": {
+    "beforeOpeningRoundBrace": true
+  },
+  "disallowSpacesInFunctionDeclaration": {
+    "beforeOpeningRoundBrace": true
+  },
+  "disallowSpacesInNamedFunctionExpression": {
+    "beforeOpeningRoundBrace": true
+  },
+  "disallowSpacesInsideParentheses": true,
+  "disallowTrailingComma": true,
+  "disallowTrailingWhitespace": true,
+  "requireCommaBeforeLineBreak": true,
+  "requireCurlyBraces": ["if", "else", "for", "while", "do", "try", "catch"],
+  "requireLineFeedAtFileEnd": true,
+  "requireSpaceAfterBinaryOperators": ["?", ":", "+", "-", "/", "*", "%", "==", "===", "!=", "!==", ">", ">=", "<", "<=", "&&", "||"],
+  "requireSpaceBeforeBinaryOperators": ["?", ":", "+", "-", "/", "*", "%", "==", "===", "!=", "!==", ">", ">=", "<", "<=", "&&", "||"],
+  "requireSpaceAfterKeywords": [
+    "else",
+    "do",
+    "return",
+    "try"
+  ],
+  "requireSpaceBeforeBlockStatements": true,
+  "requireSpacesInConditionalExpression": {
+    "afterTest": true,
+    "beforeConsequent": true,
+    "afterConsequent": true,
+    "beforeAlternate": true
+  },
+  "requireSpacesInFunction": {
+    "beforeOpeningCurlyBrace": true
+  },
+  "safeContextKeyword": "self",
+  "validateLineBreaks": "LF",
+  // FIXME: enable doc checks (update to use newer jscs jsdoc module)
+  //"validateJSDoc": {
+  //  "checkParamNames": true,
+  //  "requireParamTypes": true
+  //},
+  "validateParameterSeparator": ", "
+}
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/.jshintignore b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/.jshintignore
new file mode 100644
index 0000000..c50f474
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/.jshintignore
@@ -0,0 +1,6 @@
+js/forge.bundle.js
+minify.js
+nodejs/build.js
+nodejs/minify.js
+nodejs/ui/require.js
+nodejs/ui/test.min.js
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/.jshintrc b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/.jshintrc
new file mode 100644
index 0000000..26d261c
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/.jshintrc
@@ -0,0 +1,3 @@
+{
+  "sub": true
+}
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/.npmignore b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/.npmignore
new file mode 100644
index 0000000..50980d3
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/.npmignore
@@ -0,0 +1,22 @@
+*.py[co]
+*.sw[nop]
+*~
+.bower.json
+.cdtproject
+.classpath
+.cproject
+.project
+.settings
+Makefile
+TAGS
+aclocal.m4
+autom4te.cache
+build
+config.log
+config.status
+configure
+dist
+js/forge.bundle.js
+js/forge.min.js
+node_modules
+tests/forge
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/.travis.yml b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/.travis.yml
new file mode 100644
index 0000000..c4aa766
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/.travis.yml
@@ -0,0 +1,10 @@
+language: node_js
+node_js:
+  - "0.10"
+install: (cd nodejs && npm install)
+script: 
+  - (cd nodejs && npm test)
+notifications:
+  email:
+    on_success: change
+    on_failure: change
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/HACKING.md b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/HACKING.md
new file mode 100644
index 0000000..762dfe1
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/HACKING.md
@@ -0,0 +1,37 @@
+Hacking on forge
+================
+
+Want to hack on forge? Great! Here are a few notes:
+
+Code
+----
+
+* In general, follow a common [Node.js Style Guide][].
+* Use version X.Y.Z-dev in dev mode.
+* Use version X.Y.Z for releases.
+
+Versioning
+----------
+
+* Follow the [Semantic Versioning][] guidelines.
+
+Release Process
+---------------
+
+* commit changes
+* `$EDITOR package.json`: update to release version and remove `-dev` suffix.
+* `git commit package.json -m "Release {version}."`
+* `git tag {version}`
+* `$EDITOR package.json`: update to next version and add `-dev` suffix.
+* `git commit package.json -m "Start {next-version}."`
+* `git push`
+* `git push --tags`
+
+To ensure a clean upload, use a clean updated checkout, and run the following:
+
+* `git checkout {version}`
+* `npm publish`
+
+[Node.js Style Guide]: http://nodeguide.com/style.html
+[jshint]: http://www.jshint.com/install/
+[Semantic Versioning]: http://semver.org/
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/LICENSE b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/LICENSE
new file mode 100644
index 0000000..1bba5ce
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/LICENSE
@@ -0,0 +1,331 @@
+You may use the Forge project under the terms of either the BSD License or the 
+GNU General Public License (GPL) Version 2.
+
+The BSD License is recommended for most projects. It is simple and easy to 
+understand and it places almost no restrictions on what you can do with the
+Forge project.
+
+If the GPL suits your project better you are also free to use Forge under 
+that license.
+
+You don't have to do anything special to choose one license or the other and
+you don't have to notify anyone which license you are using. You are free to
+use this project in commercial projects as long as the copyright header is
+left intact.
+
+If you are a commercial entity and use this set of libraries in your
+commercial software then reasonable payment to Digital Bazaar, if you can
+afford it, is not required but is expected and would be appreciated. If this
+library saves you time, then it's saving you money. The cost of developing
+the Forge software was on the order of several hundred hours and tens of
+thousands of dollars. We are attempting to strike a balance between helping
+the development community while not being taken advantage of by lucrative
+commercial entities for our efforts.
+
+-------------------------------------------------------------------------------
+New BSD License (3-clause)
+Copyright (c) 2010, Digital Bazaar, Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of Digital Bazaar, Inc. nor the
+      names of its contributors may be used to endorse or promote products
+      derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL DIGITAL BAZAAR BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+-------------------------------------------------------------------------------
+        GNU GENERAL PUBLIC LICENSE
+           Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+          Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+        GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+          NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/Makefile.in b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/Makefile.in
new file mode 100644
index 0000000..eb4495e
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/Makefile.in
@@ -0,0 +1,110 @@
+# Makefile for Forge
+
+# Top-level build and dist dir
+BUILD_DIR=@FORGE_DIR@/build
+TOP_DIST_DIR=@FORGE_DIR@/dist
+DIST_DIR=$(TOP_DIST_DIR)/forge
+
+_FLASH := $(DIST_DIR)/SocketPool.swf
+ifeq (@BUILD_FLASH@,yes)
+FLASH := $(_FLASH)
+else
+ifeq (@USE_PRE_BUILT_FLASH@,yes)
+FLASH := $(_FLASH)
+endif
+endif
+JS_SOURCES := $(wildcard js/*.js)
+JS_DIST := $(JS_SOURCES:js/%.js=$(DIST_DIR)/%.js)
+JS_DIST_MIN := $(JS_DIST:%.js=%.min.js)
+TESTS_FORGE_LINK := @FORGE_DIR@/tests/forge
+
+ifeq (@BUILD_PYTHON_MODULES@,yes)
+SSL_SESSIONS_DIR = \
+	$(TOP_DIST_DIR)/forge_ssl/lib/python@PYTHON_VERSION@/site-packages
+SSL_SESSIONS_FILES = \
+	$(SSL_SESSIONS_DIR)/_forge_ssl.so \
+	$(SSL_SESSIONS_DIR)/forge/ssl.py
+endif
+
+# Whether or not to print commands as they are being executed, helpful for
+# debugging the build system.
+ifdef PRINT_COMMANDS
+PCMD=
+else
+PCMD=@
+endif
+
+.PHONY: all build-all update-all verbose clean verbose-commands
+
+# debug flags for flash build
+ifeq (@MXMLC_DEBUG_MODE@,yes)
+FLASH_FLAGS = \
+	-debug=true \
+	-define=CONFIG::debugging,true \
+	-define=CONFIG::release,false
+else
+FLASH_FLAGS = \
+	-debug=false \
+	-define=CONFIG::debugging,false \
+	-define=CONFIG::release,true
+endif
+
+all: $(BUILD_DIR) $(DIST_DIR) $(FLASH) $(JS_DIST) $(TESTS_FORGE_LINK) $(SSL_SESSIONS_FILES)
+	@echo "forge build complete."
+
+build-all: all
+
+update-all:
+	@git pull && ./build-setup && make all
+
+$(BUILD_DIR):
+	$(PCMD) mkdir -p $@
+$(DIST_DIR):
+	$(PCMD) mkdir -p $@
+
+ifeq (@BUILD_FLASH@,yes)
+$(DIST_DIR)/SocketPool.swf: flash/SocketPool.as flash/PooledSocket.as flash/SocketEvent.as
+	@echo "Building $@..."
+	$(PCMD) @MXMLC@ $(FLASH_FLAGS) \
+		-load-config+=build-flash.xml \
+		-output=$@ $<
+else
+ifeq (@USE_PRE_BUILT_FLASH@,yes)
+$(DIST_DIR)/SocketPool.swf: @FORGE_DIR@/swf/SocketPool.swf
+	@echo "Copying pre-built $(@F)..."
+	$(PCMD) cp $< $@
+endif
+endif
+
+$(DIST_DIR)/%.js: js/%.js
+	@echo "Linking $@..."
+	$(PCMD) ln -sf $(realpath $<) $@
+
+$(TESTS_FORGE_LINK): $(DIST_DIR)
+	@echo "Linking $@..."
+	$(PCMD) ln -sf $(realpath $<) $@
+
+ifeq (@BUILD_PYTHON_MODULES@,yes)
+$(SSL_SESSIONS_DIR)/_forge_ssl.so: \
+	@FORGE_DIR@/tests/forge_ssl/forge/_ssl.c \
+	@FORGE_DIR@/tests/forge_ssl/forge/socketmodule.h \
+	@FORGE_DIR@/tests/forge_ssl/setup.py
+$(SSL_SESSIONS_DIR)/forge/ssl.py: \
+	@FORGE_DIR@/tests/forge_ssl/forge/ssl.py \
+	@FORGE_DIR@/tests/forge_ssl/setup.py
+	(cd @FORGE_DIR@/tests/forge_ssl && \
+	@PYTHON@ setup.py \
+	   build --build-base $(BUILD_DIR) \
+	   install --prefix=$(TOP_DIST_DIR)/forge_ssl)
+	@# fix distutils timestamp issue
+	@# (sub-seconds of source file are truncated on target so rebuild is
+	@# always triggered)
+	@touch $@
+endif
+
+clean:
+	$(PCMD) rm -rf $(BUILD_DIR) $(TOP_DIST_DIR)
+	@echo "Removed all generated files."
+
+verbose-commands:
+	PRINT_COMMANDS=true $(MAKE) all
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/README.md b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/README.md
new file mode 100644
index 0000000..00eb178
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/README.md
@@ -0,0 +1,1782 @@
+# Forge
+
+[![Build Status][travis-ci-png]][travis-ci-site]
+[travis-ci-png]: https://travis-ci.org/digitalbazaar/forge.png?branch=master
+[travis-ci-site]: https://travis-ci.org/digitalbazaar/forge
+
+A native implementation of [TLS][] (and various other cryptographic tools) in
+[JavaScript][].
+
+## Introduction
+
+The Forge software is a fully native implementation of the [TLS][] protocol in
+JavaScript as well as a set of tools for developing Web Apps that utilize many
+network resources.
+
+## Performance
+
+Forge is fast. Benchmarks against other popular JavaScript cryptography
+libraries can be found here:
+
+http://dominictarr.github.io/crypto-bench/
+
+http://cryptojs.altervista.org/test/simulate-threading-speed_test.html
+
+## Getting Started
+------------------
+
+### Node.js ###
+
+If you want to use forge with [node.js][], it is available through `npm`:
+
+https://npmjs.org/package/node-forge
+
+Installation:
+
+    npm install node-forge
+
+You can then use forge as a regular module:
+
+    var forge = require('node-forge');
+
+### Requirements ###
+
+* General
+  * Optional: GNU autotools for the build infrastructure if using Flash.
+* Building a Browser Bundle:
+  * nodejs
+  * npm
+* Testing
+  * nodejs
+  * Optional: Python and OpenSSL development environment to build
+  * a special SSL module with session cache support for testing with flash.
+  * http://www.python.org/dev/
+  * http://www.openssl.org/
+  * Debian users should install python-dev and libssl-dev.
+* Optional: Flash
+  * A pre-built SocketPool.swf is included.
+  * Adobe Flex 3 SDK to build the Flash socket code.
+  * http://opensource.adobe.com/wiki/display/flexsdk/
+
+### Building a browser bundle ###
+
+To create a minimized JavaScript bundle, run the following:
+
+```
+npm install
+npm run minify
+```
+
+This will create a single minimized file that can be included in
+the browser:
+
+```
+js/forge.min.js
+```
+
+Include the file via:
+
+```html
+<script src="js/forge.min.js"></script>
+```
+
+Note that the minify script depends on the requirejs package,
+and that the requirejs binary 'r.js' assumes that the name of
+the node binary is 'node' not 'nodejs', as it is on some
+systems. You may need to change the hashbang line to use
+'nodejs' or run the command manually.
+
+To create a single non-minimized file that can be included in
+the browser:
+
+```
+npm install
+npm run bundle
+```
+
+This will create:
+
+```
+js/forge.bundle.js
+```
+
+Include the file via:
+
+```html
+<script src="js/forge.bundle.js"></script>
+```
+
+The above bundles will synchronously create a global 'forge' object.
+
+Keep in mind that these bundles will not include any WebWorker
+scripts (eg: prime.worker.js) or their dependencies, so these will
+need to be accessible from the browser if any WebWorkers are used.
+
+### Testing with NodeJS & RequireJS ###
+
+A test server for [node.js][] can be found at `./nodejs`. The following are included:
+
+  * Example of how to use `forge` within NodeJS in the form of a [mocha](http://visionmedia.github.io/mocha/) test.
+  * Example of how to serve `forge` to the browser using [RequireJS](http://requirejs.org/).
+
+To run:
+
+    cd nodejs
+    npm install
+    npm test
+    npm start
+
+
+### Old build system that includes flash support ###
+
+To build the whole project, including Flash, run the following:
+
+    $ ./build-setup
+    $ make
+
+This will create the SWF, symlink all the JavaScript files, and build a Python
+SSL module for testing. To see configure options, run `./configure --help`.
+
+### Old test system including flash support ###
+
+A test server is provided which can be run in TLS mode and non-TLS mode. Use
+the --help option to get help for configuring ports. The server will print out
+the local URL you can vist to run tests.
+
+Some of the simplier tests should be run with just the non-TLS server::
+
+    $ ./tests/server.py
+
+More advanced tests need TLS enabled::
+
+    $ ./tests/server.py --tls
+
+## Contributing
+---------------
+
+Any contributions (eg: PRs) that are accepted will be brought under the same
+license used by the rest of the Forge project. This license allows Forge to
+be used under the terms of either the BSD License or the GNU General Public
+License (GPL) Version 2.
+
+See: [LICENSE](https://github.com/digitalbazaar/forge/blob/cbebca3780658703d925b61b2caffb1d263a6c1d/LICENSE)
+
+If a contribution contains 3rd party source code with its own license, it
+may retain it, so long as that license is compatible with the Forge license.
+
+## Documentation
+----------------
+
+### Transports
+
+* [TLS](#tls)
+* [HTTP](#http)
+* [SSH](#ssh)
+* [XHR](#xhr)
+* [Sockets](#socket)
+
+### Ciphers
+
+* [CIPHER](#cipher)
+* [AES](#aes)
+* [DES](#des)
+* [RC2](#rc2)
+
+### PKI
+
+* [RSA](#rsa)
+* [RSA-KEM](#rsakem)
+* [X.509](#x509)
+* [PKCS#5](#pkcs5)
+* [PKCS#7](#pkcs7)
+* [PKCS#8](#pkcs8)
+* [PKCS#10](#pkcs10)
+* [PKCS#12](#pkcs12)
+* [ASN.1](#asn)
+
+### Message Digests
+
+* [SHA1](#sha1)
+* [SHA256](#sha256)
+* [SHA384](#sha384)
+* [SHA512](#sha512)
+* [MD5](#md5)
+* [HMAC](#hmac)
+
+### Utilities
+
+* [Prime](#prime)
+* [PRNG](#prng)
+* [Tasks](#task)
+* [Utilities](#util)
+* [Logging](#log)
+* [Debugging](#debug)
+* [Flash Socket Policy Module](#fsp)
+
+---------------------------------------
+
+If at any time you wish to disable the use of native code, where available,
+for particular forge features like its secure random number generator, you
+may set the ```disableNativeCode``` flag on ```forge``` to ```true```. It
+is not recommended that you set this flag as native code is typically more
+performant and may have stronger security properties. It may be useful to
+set this flag to test certain features that you plan to run in environments
+that are different from your testing environment.
+
+To disable native code when including forge in the browser:
+
+```js
+forge = {disableNativeCode: true};
+// now include forge script file(s)
+// Note: with this approach, script files *must*
+// be included after initializing the global forge var
+
+// alternatively, include script files first and then call
+forge = forge({disableNativeCode: true});
+
+// Note: forge will be permanently reconfigured now;
+// to avoid this but use the same "forge" var name,
+// you can wrap your code in a function to shadow the
+// global var, eg:
+(function(forge) {
+  // ...
+})(forge({disableNativeCode: true}));
+```
+
+To disable native code when using node.js:
+
+```js
+var forge = require('node-forge')({disableNativeCode: true});
+```
+
+---------------------------------------
+## Transports
+
+<a name="tls" />
+### TLS
+
+Provides a native javascript client and server-side [TLS][] implementation.
+
+__Examples__
+
+```js
+// create TLS client
+var client = forge.tls.createConnection({
+  server: false,
+  caStore: /* Array of PEM-formatted certs or a CA store object */,
+  sessionCache: {},
+  // supported cipher suites in order of preference
+  cipherSuites: [
+    forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+    forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+  virtualHost: 'example.com',
+  verify: function(connection, verified, depth, certs) {
+    if(depth === 0) {
+      var cn = certs[0].subject.getField('CN').value;
+      if(cn !== 'example.com') {
+        verified = {
+          alert: forge.tls.Alert.Description.bad_certificate,
+          message: 'Certificate common name does not match hostname.'
+        };
+      }
+    }
+    return verified;
+  },
+  connected: function(connection) {
+    console.log('connected');
+    // send message to server
+    connection.prepare(forge.util.encodeUtf8('Hi server!'));
+    /* NOTE: experimental, start heartbeat retransmission timer
+    myHeartbeatTimer = setInterval(function() {
+      connection.prepareHeartbeatRequest(forge.util.createBuffer('1234'));
+    }, 5*60*1000);*/
+  },
+  /* provide a client-side cert if you want
+  getCertificate: function(connection, hint) {
+    return myClientCertificate;
+  },
+  /* the private key for the client-side cert if provided */
+  getPrivateKey: function(connection, cert) {
+    return myClientPrivateKey;
+  },
+  tlsDataReady: function(connection) {
+    // TLS data (encrypted) is ready to be sent to the server
+    sendToServerSomehow(connection.tlsData.getBytes());
+    // if you were communicating with the server below, you'd do:
+    // server.process(connection.tlsData.getBytes());
+  },
+  dataReady: function(connection) {
+    // clear data from the server is ready
+    console.log('the server sent: ' +
+      forge.util.decodeUtf8(connection.data.getBytes()));
+    // close connection
+    connection.close();
+  },
+  /* NOTE: experimental
+  heartbeatReceived: function(connection, payload) {
+    // restart retransmission timer, look at payload
+    clearInterval(myHeartbeatTimer);
+    myHeartbeatTimer = setInterval(function() {
+      connection.prepareHeartbeatRequest(forge.util.createBuffer('1234'));
+    }, 5*60*1000);
+    payload.getBytes();
+  },*/
+  closed: function(connection) {
+    console.log('disconnected');
+  },
+  error: function(connection, error) {
+    console.log('uh oh', error);
+  }
+});
+
+// start the handshake process
+client.handshake();
+
+// when encrypted TLS data is received from the server, process it
+client.process(encryptedBytesFromServer);
+
+// create TLS server
+var server = forge.tls.createConnection({
+  server: true,
+  caStore: /* Array of PEM-formatted certs or a CA store object */,
+  sessionCache: {},
+  // supported cipher suites in order of preference
+  cipherSuites: [
+    forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+    forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+  // require a client-side certificate if you want
+  verifyClient: true,
+  verify: function(connection, verified, depth, certs) {
+    if(depth === 0) {
+      var cn = certs[0].subject.getField('CN').value;
+      if(cn !== 'the-client') {
+        verified = {
+          alert: forge.tls.Alert.Description.bad_certificate,
+          message: 'Certificate common name does not match expected client.'
+        };
+      }
+    }
+    return verified;
+  },
+  connected: function(connection) {
+    console.log('connected');
+    // send message to client
+    connection.prepare(forge.util.encodeUtf8('Hi client!'));
+    /* NOTE: experimental, start heartbeat retransmission timer
+    myHeartbeatTimer = setInterval(function() {
+      connection.prepareHeartbeatRequest(forge.util.createBuffer('1234'));
+    }, 5*60*1000);*/
+  },
+  getCertificate: function(connection, hint) {
+    return myServerCertificate;
+  },
+  getPrivateKey: function(connection, cert) {
+    return myServerPrivateKey;
+  },
+  tlsDataReady: function(connection) {
+    // TLS data (encrypted) is ready to be sent to the client
+    sendToClientSomehow(connection.tlsData.getBytes());
+    // if you were communicating with the client above you'd do:
+    // client.process(connection.tlsData.getBytes());
+  },
+  dataReady: function(connection) {
+    // clear data from the client is ready
+    console.log('the client sent: ' +
+      forge.util.decodeUtf8(connection.data.getBytes()));
+    // close connection
+    connection.close();
+  },
+  /* NOTE: experimental
+  heartbeatReceived: function(connection, payload) {
+    // restart retransmission timer, look at payload
+    clearInterval(myHeartbeatTimer);
+    myHeartbeatTimer = setInterval(function() {
+      connection.prepareHeartbeatRequest(forge.util.createBuffer('1234'));
+    }, 5*60*1000);
+    payload.getBytes();
+  },*/
+  closed: function(connection) {
+    console.log('disconnected');
+  },
+  error: function(connection, error) {
+    console.log('uh oh', error);
+  }
+});
+
+// when encrypted TLS data is received from the client, process it
+server.process(encryptedBytesFromClient);
+```
+
+Connect to a TLS server using node's net.Socket:
+
+```js
+var socket = new net.Socket();
+
+var client = forge.tls.createConnection({
+  server: false,
+  verify: function(connection, verified, depth, certs) {
+    // skip verification for testing
+    console.log('[tls] server certificate verified');
+    return true;
+  },
+  connected: function(connection) {
+    console.log('[tls] connected');
+    // prepare some data to send (note that the string is interpreted as
+    // 'binary' encoded, which works for HTTP which only uses ASCII, use
+    // forge.util.encodeUtf8(str) otherwise
+    client.prepare('GET / HTTP/1.0\r\n\r\n');
+  },
+  tlsDataReady: function(connection) {
+    // encrypted data is ready to be sent to the server
+    var data = connection.tlsData.getBytes();
+    socket.write(data, 'binary'); // encoding should be 'binary'
+  },
+  dataReady: function(connection) {
+    // clear data from the server is ready
+    var data = connection.data.getBytes();
+    console.log('[tls] data received from the server: ' + data);
+  },
+  closed: function() {
+    console.log('[tls] disconnected');
+  },
+  error: function(connection, error) {
+    console.log('[tls] error', error);
+  }
+});
+
+socket.on('connect', function() {
+  console.log('[socket] connected');
+  client.handshake();
+});
+socket.on('data', function(data) {
+  client.process(data.toString('binary')); // encoding should be 'binary'
+});
+socket.on('end', function() {
+  console.log('[socket] disconnected');
+});
+
+// connect to google.com
+socket.connect(443, 'google.com');
+
+// or connect to gmail's imap server (but don't send the HTTP header above)
+//socket.connect(993, 'imap.gmail.com');
+```
+
+<a name="http" />
+### HTTP
+
+Provides a native [JavaScript][] mini-implementation of an http client that
+uses pooled sockets.
+
+__Examples__
+
+```js
+// create an HTTP GET request
+var request = forge.http.createRequest({method: 'GET', path: url.path});
+
+// send the request somewhere
+sendSomehow(request.toString());
+
+// receive response
+var buffer = forge.util.createBuffer();
+var response = forge.http.createResponse();
+var someAsyncDataHandler = function(bytes) {
+  if(!response.bodyReceived) {
+    buffer.putBytes(bytes);
+    if(!response.headerReceived) {
+      if(response.readHeader(buffer)) {
+        console.log('HTTP response header: ' + response.toString());
+      }
+    }
+    if(response.headerReceived && !response.bodyReceived) {
+      if(response.readBody(buffer)) {
+        console.log('HTTP response body: ' + response.body);
+      }
+    }
+  }
+};
+```
+
+<a name="ssh" />
+### SSH
+
+Provides some SSH utility functions.
+
+__Examples__
+
+```js
+// encodes (and optionally encrypts) a private RSA key as a Putty PPK file
+forge.ssh.privateKeyToPutty(privateKey, passphrase, comment);
+
+// encodes a public RSA key as an OpenSSH file
+forge.ssh.publicKeyToOpenSSH(key, comment);
+
+// encodes a private RSA key as an OpenSSH file
+forge.ssh.privateKeyToOpenSSH(privateKey, passphrase);
+
+// gets the SSH public key fingerprint in a byte buffer
+forge.ssh.getPublicKeyFingerprint(key);
+
+// gets a hex-encoded, colon-delimited SSH public key fingerprint
+forge.ssh.getPublicKeyFingerprint(key, {encoding: 'hex', delimiter: ':'});
+```
+
+<a name="xhr" />
+### XHR
+
+Provides an XmlHttpRequest implementation using forge.http as a backend.
+
+__Examples__
+
+```js
+```
+
+<a name="socket" />
+### Sockets
+
+Provides an interface to create and use raw sockets provided via Flash.
+
+__Examples__
+
+```js
+```
+
+---------------------------------------
+## Ciphers
+
+<a name="cipher" />
+### CIPHER
+
+Provides a basic API for block encryption and decryption. There is built-in
+support for the ciphers: [AES][], [3DES][], and [DES][], and for the modes
+of operation: [ECB][], [CBC][], [CFB][], [OFB][], [CTR][], and [GCM][].
+
+These algorithms are currently supported:
+
+* AES-CBC
+* AES-CFB
+* AES-OFB
+* AES-CTR
+* AES-GCM
+* 3DES-ECB
+* 3DES-CBC
+* DES-ECB
+* DES-CBC
+
+When using an [AES][] algorithm, the key size will determine whether
+AES-128, AES-192, or AES-256 is used (all are supported). When a [DES][]
+algorithm is used, the key size will determine whether [3DES][] or regular
+[DES][] is used. Use a [3DES][] algorithm to enforce Triple-DES.
+
+__Examples__
+
+```js
+// generate a random key and IV
+// Note: a key size of 16 bytes will use AES-128, 24 => AES-192, 32 => AES-256
+var key = forge.random.getBytesSync(16);
+var iv = forge.random.getBytesSync(16);
+
+/* alternatively, generate a password-based 16-byte key
+var salt = forge.random.getBytesSync(128);
+var key = forge.pkcs5.pbkdf2('password', salt, numIterations, 16);
+*/
+
+// encrypt some bytes using CBC mode
+// (other modes include: CFB, OFB, CTR, and GCM)
+var cipher = forge.cipher.createCipher('AES-CBC', key);
+cipher.start({iv: iv});
+cipher.update(forge.util.createBuffer(someBytes));
+cipher.finish();
+var encrypted = cipher.output;
+// outputs encrypted hex
+console.log(encrypted.toHex());
+
+// decrypt some bytes using CBC mode
+// (other modes include: CFB, OFB, CTR, and GCM)
+var decipher = forge.cipher.createDecipher('AES-CBC', key);
+decipher.start({iv: iv});
+decipher.update(encrypted);
+decipher.finish();
+// outputs decrypted hex
+console.log(decipher.output.toHex());
+
+// encrypt some bytes using GCM mode
+var cipher = forge.cipher.createCipher('AES-GCM', key);
+cipher.start({
+  iv: iv, // should be a 12-byte binary-encoded string or byte buffer
+  additionalData: 'binary-encoded string', // optional
+  tagLength: 128 // optional, defaults to 128 bits
+});
+cipher.update(forge.util.createBuffer(someBytes));
+cipher.finish();
+var encrypted = cipher.output;
+var tag = cipher.mode.tag;
+// outputs encrypted hex
+console.log(encrypted.toHex());
+// outputs authentication tag
+console.log(tag.toHex());
+
+// decrypt some bytes using GCM mode
+var decipher = forge.cipher.createDecipher('AES-GCM', key);
+decipher.start({
+  iv: iv,
+  additionalData: 'binary-encoded string', // optional
+  tagLength: 128, // optional, defaults to 128 bits
+  tag: tag // authentication tag from encryption
+});
+decipher.update(encrypted);
+var pass = decipher.finish();
+// pass is false if there was a failure (eg: authentication tag didn't match)
+if(pass) {
+  // outputs decrypted hex
+  console.log(decipher.output.toHex());
+}
+```
+
+Using forge in node.js to match openssl's "enc" command line tool (**Note**: OpenSSL "enc" uses a non-standard file format with a custom key derivation function and a fixed iteration count of 1, which some consider less secure than alternatives such as [OpenPGP](https://tools.ietf.org/html/rfc4880)/[GnuPG](https://www.gnupg.org/)):
+
+```js
+var forge = require('node-forge');
+var fs = require('fs');
+
+// openssl enc -des3 -in input.txt -out input.enc
+function encrypt(password) {
+  var input = fs.readFileSync('input.txt', {encoding: 'binary'});
+
+  // 3DES key and IV sizes
+  var keySize = 24;
+  var ivSize = 8;
+
+  // get derived bytes
+  // Notes:
+  // 1. If using an alternative hash (eg: "-md sha1") pass
+  //   "forge.md.sha1.create()" as the final parameter.
+  // 2. If using "-nosalt", set salt to null.
+  var salt = forge.random.getBytesSync(8);
+  // var md = forge.md.sha1.create(); // "-md sha1"
+  var derivedBytes = forge.pbe.opensslDeriveBytes(
+    password, salt, keySize + ivSize/*, md*/);
+  var buffer = forge.util.createBuffer(derivedBytes);
+  var key = buffer.getBytes(keySize);
+  var iv = buffer.getBytes(ivSize);
+
+  var cipher = forge.cipher.createCipher('3DES-CBC', key);
+  cipher.start({iv: iv});
+  cipher.update(forge.util.createBuffer(input, 'binary'));
+  cipher.finish();
+
+  var output = forge.util.createBuffer();
+
+  // if using a salt, prepend this to the output:
+  if(salt !== null) {
+    output.putBytes('Salted__'); // (add to match openssl tool output)
+    output.putBytes(salt);
+  }
+  output.putBuffer(cipher.output);
+
+  fs.writeFileSync('input.enc', output.getBytes(), {encoding: 'binary'});
+}
+
+// openssl enc -d -des3 -in input.enc -out input.dec.txt
+function decrypt(password) {
+  var input = fs.readFileSync('input.enc', {encoding: 'binary'});
+
+  // parse salt from input
+  input = forge.util.createBuffer(input, 'binary');
+  // skip "Salted__" (if known to be present)
+  input.getBytes('Salted__'.length);
+  // read 8-byte salt
+  var salt = input.getBytes(8);
+
+  // Note: if using "-nosalt", skip above parsing and use
+  // var salt = null;
+
+  // 3DES key and IV sizes
+  var keySize = 24;
+  var ivSize = 8;
+
+  var derivedBytes = forge.pbe.opensslDeriveBytes(
+    password, salt, keySize + ivSize);
+  var buffer = forge.util.createBuffer(derivedBytes);
+  var key = buffer.getBytes(keySize);
+  var iv = buffer.getBytes(ivSize);
+
+  var decipher = forge.cipher.createDecipher('3DES-CBC', key);
+  decipher.start({iv: iv});
+  decipher.update(input);
+  var result = decipher.finish(); // check 'result' for true/false
+
+  fs.writeFileSync(
+    'input.dec.txt', decipher.output.getBytes(), {encoding: 'binary'});
+}
+```
+
+<a name="aes" />
+### AES
+
+Provides [AES][] encryption and decryption in [CBC][], [CFB][], [OFB][],
+[CTR][], and [GCM][] modes. See [CIPHER](#cipher) for examples.
+
+<a name="des" />
+### DES
+
+Provides [3DES][] and [DES][] encryption and decryption in [ECB][] and
+[CBC][] modes. See [CIPHER](#cipher) for examples.
+
+<a name="rc2" />
+### RC2
+
+__Examples__
+
+```js
+// generate a random key and IV
+var key = forge.random.getBytesSync(16);
+var iv = forge.random.getBytesSync(8);
+
+// encrypt some bytes
+var cipher = forge.rc2.createEncryptionCipher(key);
+cipher.start(iv);
+cipher.update(forge.util.createBuffer(someBytes));
+cipher.finish();
+var encrypted = cipher.output;
+// outputs encrypted hex
+console.log(encrypted.toHex());
+
+// decrypt some bytes
+var cipher = forge.rc2.createDecryptionCipher(key);
+cipher.start(iv);
+cipher.update(encrypted);
+cipher.finish();
+// outputs decrypted hex
+console.log(cipher.output.toHex());
+```
+---------------------------------------
+## PKI
+
+Provides [X.509][] certificate and RSA public and private key encoding,
+decoding, encryption/decryption, and signing/verifying.
+
+<a name="rsa" />
+### RSA
+
+__Examples__
+
+```js
+var rsa = forge.pki.rsa;
+
+// generate an RSA key pair synchronously
+var keypair = rsa.generateKeyPair({bits: 2048, e: 0x10001});
+
+// generate an RSA key pair asynchronously (uses web workers if available)
+// use workers: -1 to run a fast core estimator to optimize # of workers
+rsa.generateKeyPair({bits: 2048, workers: 2}, function(err, keypair) {
+  // keypair.privateKey, keypair.publicKey
+});
+
+// generate an RSA key pair in steps that attempt to run for a specified period
+// of time on the main JS thread
+var state = rsa.createKeyPairGenerationState(2048, 0x10001);
+var step = function() {
+  // run for 100 ms
+  if(!rsa.stepKeyPairGenerationState(state, 100)) {
+    setTimeout(step, 1);
+  }
+  else {
+    // done, turn off progress indicator, use state.keys
+  }
+};
+// turn on progress indicator, schedule generation to run
+setTimeout(step);
+
+// sign data with a private key and output DigestInfo DER-encoded bytes
+// (defaults to RSASSA PKCS#1 v1.5)
+var md = forge.md.sha1.create();
+md.update('sign this', 'utf8');
+var signature = privateKey.sign(md);
+
+// verify data with a public key
+// (defaults to RSASSA PKCS#1 v1.5)
+var verified = publicKey.verify(md.digest().bytes(), signature);
+
+// sign data using RSASSA-PSS where PSS uses a SHA-1 hash, a SHA-1 based
+// masking function MGF1, and a 20 byte salt
+var md = forge.md.sha1.create();
+md.update('sign this', 'utf8');
+var pss = forge.pss.create({
+  md: forge.md.sha1.create(),
+  mgf: forge.mgf.mgf1.create(forge.md.sha1.create()),
+  saltLength: 20
+  // optionally pass 'prng' with a custom PRNG implementation
+  // optionalls pass 'salt' with a forge.util.ByteBuffer w/custom salt
+});
+var signature = privateKey.sign(md, pss);
+
+// verify RSASSA-PSS signature
+var pss = forge.pss.create({
+  md: forge.md.sha1.create(),
+  mgf: forge.mgf.mgf1.create(forge.md.sha1.create()),
+  saltLength: 20
+  // optionally pass 'prng' with a custom PRNG implementation
+});
+var md = forge.md.sha1.create();
+md.update('sign this', 'utf8');
+publicKey.verify(md.digest().getBytes(), signature, pss);
+
+// encrypt data with a public key (defaults to RSAES PKCS#1 v1.5)
+var encrypted = publicKey.encrypt(bytes);
+
+// decrypt data with a private key (defaults to RSAES PKCS#1 v1.5)
+var decrypted = privateKey.decrypt(encrypted);
+
+// encrypt data with a public key using RSAES PKCS#1 v1.5
+var encrypted = publicKey.encrypt(bytes, 'RSAES-PKCS1-V1_5');
+
+// decrypt data with a private key using RSAES PKCS#1 v1.5
+var decrypted = privateKey.decrypt(encrypted, 'RSAES-PKCS1-V1_5');
+
+// encrypt data with a public key using RSAES-OAEP
+var encrypted = publicKey.encrypt(bytes, 'RSA-OAEP');
+
+// decrypt data with a private key using RSAES-OAEP
+var decrypted = privateKey.decrypt(encrypted, 'RSA-OAEP');
+
+// encrypt data with a public key using RSAES-OAEP/SHA-256
+var encrypted = publicKey.encrypt(bytes, 'RSA-OAEP', {
+  md: forge.md.sha256.create()
+});
+
+// decrypt data with a private key using RSAES-OAEP/SHA-256
+var decrypted = privateKey.decrypt(encrypted, 'RSA-OAEP', {
+  md: forge.md.sha256.create()
+});
+
+// encrypt data with a public key using RSAES-OAEP/SHA-256/MGF1-SHA-1
+// compatible with Java's RSA/ECB/OAEPWithSHA-256AndMGF1Padding
+var encrypted = publicKey.encrypt(bytes, 'RSA-OAEP', {
+  md: forge.md.sha256.create(),
+  mgf1: {
+    md: forge.md.sha1.create()
+  }
+});
+
+// decrypt data with a private key using RSAES-OAEP/SHA-256/MGF1-SHA-1
+// compatible with Java's RSA/ECB/OAEPWithSHA-256AndMGF1Padding
+var decrypted = privateKey.decrypt(encrypted, 'RSA-OAEP', {
+  md: forge.md.sha256.create(),
+  mgf1: {
+    md: forge.md.sha1.create()
+  }
+});
+
+```
+
+<a name="rsakem" />
+### RSA-KEM
+
+__Examples__
+
+```js
+// generate an RSA key pair asynchronously (uses web workers if available)
+// use workers: -1 to run a fast core estimator to optimize # of workers
+forge.rsa.generateKeyPair({bits: 2048, workers: -1}, function(err, keypair) {
+  // keypair.privateKey, keypair.publicKey
+});
+
+// generate and encapsulate a 16-byte secret key
+var kdf1 = new forge.kem.kdf1(forge.md.sha1.create());
+var kem = forge.kem.rsa.create(kdf1);
+var result = kem.encrypt(keypair.publicKey, 16);
+// result has 'encapsulation' and 'key'
+
+// encrypt some bytes
+var iv = forge.random.getBytesSync(12);
+var someBytes = 'hello world!';
+var cipher = forge.cipher.createCipher('AES-GCM', result.key);
+cipher.start({iv: iv});
+cipher.update(forge.util.createBuffer(someBytes));
+cipher.finish();
+var encrypted = cipher.output.getBytes();
+var tag = cipher.mode.tag.getBytes();
+
+// send 'encrypted', 'iv', 'tag', and result.encapsulation to recipient
+
+// decrypt encapsulated 16-byte secret key
+var kdf1 = new forge.kem.kdf1(forge.md.sha1.create());
+var kem = forge.kem.rsa.create(kdf1);
+var key = kem.decrypt(keypair.privateKey, result.encapsulation, 16);
+
+// decrypt some bytes
+var decipher = forge.cipher.createDecipher('AES-GCM', key);
+decipher.start({iv: iv, tag: tag});
+decipher.update(forge.util.createBuffer(encrypted));
+var pass = decipher.finish();
+// pass is false if there was a failure (eg: authentication tag didn't match)
+if(pass) {
+  // outputs 'hello world!'
+  console.log(decipher.output.getBytes());
+}
+
+```
+
+<a name="x509" />
+### X.509
+
+__Examples__
+
+```js
+var pki = forge.pki;
+
+// convert a PEM-formatted public key to a Forge public key
+var publicKey = pki.publicKeyFromPem(pem);
+
+// convert a Forge public key to PEM-format
+var pem = pki.publicKeyToPem(publicKey);
+
+// convert an ASN.1 SubjectPublicKeyInfo to a Forge public key
+var publicKey = pki.publicKeyFromAsn1(subjectPublicKeyInfo);
+
+// convert a Forge public key to an ASN.1 SubjectPublicKeyInfo
+var subjectPublicKeyInfo = pki.publicKeyToAsn1(publicKey);
+
+// gets a SHA-1 RSAPublicKey fingerprint a byte buffer
+pki.getPublicKeyFingerprint(key);
+
+// gets a SHA-1 SubjectPublicKeyInfo fingerprint a byte buffer
+pki.getPublicKeyFingerprint(key, {type: 'SubjectPublicKeyInfo'});
+
+// gets a hex-encoded, colon-delimited SHA-1 RSAPublicKey public key fingerprint
+pki.getPublicKeyFingerprint(key, {encoding: 'hex', delimiter: ':'});
+
+// gets a hex-encoded, colon-delimited SHA-1 SubjectPublicKeyInfo public key fingerprint
+pki.getPublicKeyFingerprint(key, {
+  type: 'SubjectPublicKeyInfo',
+  encoding: 'hex',
+  delimiter: ':'
+});
+
+// gets a hex-encoded, colon-delimited MD5 RSAPublicKey public key fingerprint
+pki.getPublicKeyFingerprint(key, {
+  md: forge.md.md5.create(),
+  encoding: 'hex',
+  delimiter: ':'
+});
+
+// creates a CA store
+var caStore = pki.createCaStore([/* PEM-encoded cert */, ...]);
+
+// add a certificate to the CA store
+caStore.addCertificate(certObjectOrPemString);
+
+// gets the issuer (its certificate) for the given certificate
+var issuerCert = caStore.getIssuer(subjectCert);
+
+// verifies a certificate chain against a CA store
+pki.verifyCertificateChain(caStore, chain, customVerifyCallback);
+
+// signs a certificate using the given private key
+cert.sign(privateKey);
+
+// signs a certificate using SHA-256 instead of SHA-1
+cert.sign(privateKey, forge.md.sha256.create());
+
+// verifies an issued certificate using the certificates public key
+var verified = issuer.verify(issued);
+
+// generate a keypair and create an X.509v3 certificate
+var keys = pki.rsa.generateKeyPair(2048);
+var cert = pki.createCertificate();
+cert.publicKey = keys.publicKey;
+// alternatively set public key from a csr
+//cert.publicKey = csr.publicKey;
+cert.serialNumber = '01';
+cert.validity.notBefore = new Date();
+cert.validity.notAfter = new Date();
+cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1);
+var attrs = [{
+  name: 'commonName',
+  value: 'example.org'
+}, {
+  name: 'countryName',
+  value: 'US'
+}, {
+  shortName: 'ST',
+  value: 'Virginia'
+}, {
+  name: 'localityName',
+  value: 'Blacksburg'
+}, {
+  name: 'organizationName',
+  value: 'Test'
+}, {
+  shortName: 'OU',
+  value: 'Test'
+}];
+cert.setSubject(attrs);
+// alternatively set subject from a csr
+//cert.setSubject(csr.subject.attributes);
+cert.setIssuer(attrs);
+cert.setExtensions([{
+  name: 'basicConstraints',
+  cA: true
+}, {
+  name: 'keyUsage',
+  keyCertSign: true,
+  digitalSignature: true,
+  nonRepudiation: true,
+  keyEncipherment: true,
+  dataEncipherment: true
+}, {
+  name: 'extKeyUsage',
+  serverAuth: true,
+  clientAuth: true,
+  codeSigning: true,
+  emailProtection: true,
+  timeStamping: true
+}, {
+  name: 'nsCertType',
+  client: true,
+  server: true,
+  email: true,
+  objsign: true,
+  sslCA: true,
+  emailCA: true,
+  objCA: true
+}, {
+  name: 'subjectAltName',
+  altNames: [{
+    type: 6, // URI
+    value: 'http://example.org/webid#me'
+  }, {
+    type: 7, // IP
+    ip: '127.0.0.1'
+  }]
+}, {
+  name: 'subjectKeyIdentifier'
+}]);
+/* alternatively set extensions from a csr
+var extensions = csr.getAttribute({name: 'extensionRequest'}).extensions;
+// optionally add more extensions
+extensions.push.apply(extensions, [{
+  name: 'basicConstraints',
+  cA: true
+}, {
+  name: 'keyUsage',
+  keyCertSign: true,
+  digitalSignature: true,
+  nonRepudiation: true,
+  keyEncipherment: true,
+  dataEncipherment: true
+}]);
+cert.setExtensions(extensions);
+*/
+// self-sign certificate
+cert.sign(keys.privateKey);
+
+// convert a Forge certificate to PEM
+var pem = pki.certificateToPem(cert);
+
+// convert a Forge certificate from PEM
+var cert = pki.certificateFromPem(pem);
+
+// convert an ASN.1 X.509x3 object to a Forge certificate
+var cert = pki.certificateFromAsn1(obj);
+
+// convert a Forge certificate to an ASN.1 X.509v3 object
+var asn1Cert = pki.certificateToAsn1(cert);
+```
+
+<a name="pkcs5" />
+### PKCS#5
+
+Provides the password-based key-derivation function from [PKCS#5][].
+
+__Examples__
+
+```js
+// generate a password-based 16-byte key
+// note an optional message digest can be passed as the final parameter
+var salt = forge.random.getBytesSync(128);
+var derivedKey = forge.pkcs5.pbkdf2('password', salt, numIterations, 16);
+
+// generate key asynchronously
+// note an optional message digest can be passed before the callback
+forge.pkcs5.pbkdf2('password', salt, numIterations, 16, function(err, derivedKey) {
+  // do something w/derivedKey
+});
+```
+
+<a name="pkcs7" />
+### PKCS#7
+
+Provides cryptographically protected messages from [PKCS#7][].
+
+__Examples__
+
+```js
+// convert a message from PEM
+var p7 = forge.pkcs7.messageFromPem(pem);
+// look at p7.recipients
+
+// find a recipient by the issuer of a certificate
+var recipient = p7.findRecipient(cert);
+
+// decrypt
+p7.decrypt(p7.recipients[0], privateKey);
+
+// create a p7 enveloped message
+var p7 = forge.pkcs7.createEnvelopedData();
+
+// add a recipient
+var cert = forge.pki.certificateFromPem(certPem);
+p7.addRecipient(cert);
+
+// set content
+p7.content = forge.util.createBuffer('Hello');
+
+// encrypt
+p7.encrypt();
+
+// convert message to PEM
+var pem = forge.pkcs7.messageToPem(p7);
+
+// create a degenerate PKCS#7 certificate container
+// (CRLs not currently supported, only certificates)
+var p7 = forge.pkcs7.createSignedData();
+p7.addCertificate(certOrCertPem1);
+p7.addCertificate(certOrCertPem2);
+var pem = forge.pkcs7.messageToPem(p7);
+```
+
+<a name="pkcs8" />
+### PKCS#8
+
+__Examples__
+
+```js
+var pki = forge.pki;
+
+// convert a PEM-formatted private key to a Forge private key
+var privateKey = pki.privateKeyFromPem(pem);
+
+// convert a Forge private key to PEM-format
+var pem = pki.privateKeyToPem(privateKey);
+
+// convert an ASN.1 PrivateKeyInfo or RSAPrivateKey to a Forge private key
+var privateKey = pki.privateKeyFromAsn1(rsaPrivateKey);
+
+// convert a Forge private key to an ASN.1 RSAPrivateKey
+var rsaPrivateKey = pki.privateKeyToAsn1(privateKey);
+
+// wrap an RSAPrivateKey ASN.1 object in a PKCS#8 ASN.1 PrivateKeyInfo
+var privateKeyInfo = pki.wrapRsaPrivateKey(rsaPrivateKey);
+
+// convert a PKCS#8 ASN.1 PrivateKeyInfo to PEM
+var pem = pki.privateKeyInfoToPem(privateKeyInfo);
+
+// encrypts a PrivateKeyInfo and outputs an EncryptedPrivateKeyInfo
+var encryptedPrivateKeyInfo = pki.encryptPrivateKeyInfo(
+  privateKeyInfo, 'password', {
+    algorithm: 'aes256', // 'aes128', 'aes192', 'aes256', '3des'
+  });
+
+// decrypts an ASN.1 EncryptedPrivateKeyInfo
+var privateKeyInfo = pki.decryptPrivateKeyInfo(
+  encryptedPrivateKeyInfo, 'password');
+
+// converts an EncryptedPrivateKeyInfo to PEM
+var pem = pki.encryptedPrivateKeyToPem(encryptedPrivateKeyInfo);
+
+// converts a PEM-encoded EncryptedPrivateKeyInfo to ASN.1 format
+var encryptedPrivateKeyInfo = pki.encryptedPrivateKeyFromPem(pem);
+
+// wraps and encrypts a Forge private key and outputs it in PEM format
+var pem = pki.encryptRsaPrivateKey(privateKey, 'password');
+
+// encrypts a Forge private key and outputs it in PEM format using OpenSSL's
+// proprietary legacy format + encapsulated PEM headers (DEK-Info)
+var pem = pki.encryptRsaPrivateKey(privateKey, 'password', {legacy: true});
+
+// decrypts a PEM-formatted, encrypted private key
+var privateKey = pki.decryptRsaPrivateKey(pem, 'password');
+
+// sets an RSA public key from a private key
+var publicKey = pki.setRsaPublicKey(privateKey.n, privateKey.e);
+```
+
+<a name="pkcs10" />
+### PKCS#10
+
+Provides certification requests or certificate signing requests (CSR) from
+[PKCS#10][].
+
+__Examples__
+
+```js
+// generate a key pair
+var keys = forge.pki.rsa.generateKeyPair(1024);
+
+// create a certification request (CSR)
+var csr = forge.pki.createCertificationRequest();
+csr.publicKey = keys.publicKey;
+csr.setSubject([{
+  name: 'commonName',
+  value: 'example.org'
+}, {
+  name: 'countryName',
+  value: 'US'
+}, {
+  shortName: 'ST',
+  value: 'Virginia'
+}, {
+  name: 'localityName',
+  value: 'Blacksburg'
+}, {
+  name: 'organizationName',
+  value: 'Test'
+}, {
+  shortName: 'OU',
+  value: 'Test'
+}]);
+// set (optional) attributes
+csr.setAttributes([{
+  name: 'challengePassword',
+  value: 'password'
+}, {
+  name: 'unstructuredName',
+  value: 'My Company, Inc.'
+}, {
+  name: 'extensionRequest',
+  extensions: [{
+    name: 'subjectAltName',
+    altNames: [{
+      // 2 is DNS type
+      type: 2,
+      value: 'test.domain.com'
+    }, {
+      type: 2,
+      value: 'other.domain.com',
+    }, {
+      type: 2,
+      value: 'www.domain.net'
+    }]
+  }]
+}]);
+
+// sign certification request
+csr.sign(keys.privateKey);
+
+// verify certification request
+var verified = csr.verify();
+
+// convert certification request to PEM-format
+var pem = forge.pki.certificationRequestToPem(csr);
+
+// convert a Forge certification request from PEM-format
+var csr = forge.pki.certificationRequestFromPem(pem);
+
+// get an attribute
+csr.getAttribute({name: 'challengePassword'});
+
+// get extensions array
+csr.getAttribute({name: 'extensionRequest'}).extensions;
+
+```
+
+<a name="pkcs12" />
+### PKCS#12
+
+Provides the cryptographic archive file format from [PKCS#12][].
+
+__Examples__
+
+```js
+// decode p12 from base64
+var p12Der = forge.util.decode64(p12b64);
+// get p12 as ASN.1 object
+var p12Asn1 = forge.asn1.fromDer(p12Der);
+// decrypt p12 using the password 'password'
+var p12 = forge.pkcs12.pkcs12FromAsn1(p12Asn1, 'password');
+// decrypt p12 using non-strict parsing mode (resolves some ASN.1 parse errors)
+var p12 = forge.pkcs12.pkcs12FromAsn1(p12Asn1, false, 'password');
+// decrypt p12 using literally no password (eg: Mac OS X/apple push)
+var p12 = forge.pkcs12.pkcs12FromAsn1(p12Asn1);
+// decrypt p12 using an "empty" password (eg: OpenSSL with no password input)
+var p12 = forge.pkcs12.pkcs12FromAsn1(p12Asn1, '');
+// p12.safeContents is an array of safe contents, each of
+// which contains an array of safeBags
+
+// get bags by friendlyName
+var bags = p12.getBags({friendlyName: 'test'});
+// bags are key'd by attribute type (here "friendlyName")
+// and the key values are an array of matching objects
+var cert = bags.friendlyName[0];
+
+// get bags by localKeyId
+var bags = p12.getBags({localKeyId: buffer});
+// bags are key'd by attribute type (here "localKeyId")
+// and the key values are an array of matching objects
+var cert = bags.localKeyId[0];
+
+// get bags by localKeyId (input in hex)
+var bags = p12.getBags({localKeyIdHex: '7b59377ff142d0be4565e9ac3d396c01401cd879'});
+// bags are key'd by attribute type (here "localKeyId", *not* "localKeyIdHex")
+// and the key values are an array of matching objects
+var cert = bags.localKeyId[0];
+
+// get bags by type
+var bags = p12.getBags({bagType: forge.pki.oids.certBag});
+// bags are key'd by bagType and each bagType key's value
+// is an array of matches (in this case, certificate objects)
+var cert = bags[forge.pki.oids.certBag][0];
+
+// get bags by friendlyName and filter on bag type
+var bags = p12.getBags({
+  friendlyName: 'test',
+  bagType: forge.pki.oids.certBag
+});
+
+// generate a p12 using AES (default)
+var p12Asn1 = forge.pkcs12.toPkcs12Asn1(
+  privateKey, certificateChain, 'password');
+
+// generate a p12 that can be imported by Chrome/Firefox
+// (requires the use of Triple DES instead of AES)
+var p12Asn1 = forge.pkcs12.toPkcs12Asn1(
+  privateKey, certificateChain, 'password',
+  {algorithm: '3des'});
+
+// base64-encode p12
+var p12Der = forge.asn1.toDer(p12Asn1).getBytes();
+var p12b64 = forge.util.encode64(p12Der);
+
+// create download link for p12
+var a = document.createElement('a');
+a.download = 'example.p12';
+a.setAttribute('href', 'data:application/x-pkcs12;base64,' + p12b64);
+a.appendChild(document.createTextNode('Download'));
+```
+
+<a name="asn" />
+### ASN.1
+
+Provides [ASN.1][] DER encoding and decoding.
+
+__Examples__
+
+```js
+var asn1 = forge.asn1;
+
+// create a SubjectPublicKeyInfo
+var subjectPublicKeyInfo =
+  asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+    // AlgorithmIdentifier
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+      // algorithm
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+        asn1.oidToDer(pki.oids['rsaEncryption']).getBytes()),
+      // parameters (null)
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
+    ]),
+    // subjectPublicKey
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.BITSTRING, false, [
+      // RSAPublicKey
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+        // modulus (n)
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+          _bnToBytes(key.n)),
+        // publicExponent (e)
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+          _bnToBytes(key.e))
+      ])
+    ])
+  ]);
+
+// serialize an ASN.1 object to DER format
+var derBuffer = asn1.toDer(subjectPublicKeyInfo);
+
+// deserialize to an ASN.1 object from a byte buffer filled with DER data
+var object = asn1.fromDer(derBuffer);
+
+// convert an OID dot-separated string to a byte buffer
+var derOidBuffer = asn1.oidToDer('1.2.840.113549.1.1.5');
+
+// convert a byte buffer with a DER-encoded OID to a dot-separated string
+console.log(asn1.derToDer(derOidBuffer));
+// output: 1.2.840.113549.1.1.5
+
+// validates that an ASN.1 object matches a particular ASN.1 structure and
+// captures data of interest from that structure for easy access
+var publicKeyValidator = {
+  name: 'SubjectPublicKeyInfo',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  captureAsn1: 'subjectPublicKeyInfo',
+  value: [{
+    name: 'SubjectPublicKeyInfo.AlgorithmIdentifier',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true,
+    value: [{
+      name: 'AlgorithmIdentifier.algorithm',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.OID,
+      constructed: false,
+      capture: 'publicKeyOid'
+    }]
+  }, {
+    // subjectPublicKey
+    name: 'SubjectPublicKeyInfo.subjectPublicKey',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.BITSTRING,
+    constructed: false,
+    value: [{
+      // RSAPublicKey
+      name: 'SubjectPublicKeyInfo.subjectPublicKey.RSAPublicKey',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.SEQUENCE,
+      constructed: true,
+      optional: true,
+      captureAsn1: 'rsaPublicKey'
+    }]
+  }]
+};
+
+var capture = {};
+var errors = [];
+if(!asn1.validate(
+  publicKeyValidator, subjectPublicKeyInfo, validator, capture, errors)) {
+  throw 'ASN.1 object is not a SubjectPublicKeyInfo.';
+}
+// capture.subjectPublicKeyInfo contains the full ASN.1 object
+// capture.rsaPublicKey contains the full ASN.1 object for the RSA public key
+// capture.publicKeyOid only contains the value for the OID
+var oid = asn1.derToOid(capture.publicKeyOid);
+if(oid !== pki.oids['rsaEncryption']) {
+  throw 'Unsupported OID.';
+}
+
+// pretty print an ASN.1 object to a string for debugging purposes
+asn1.prettyPrint(object);
+```
+
+---------------------------------------
+## Message Digests
+
+<a name="sha1" />
+### SHA1
+
+Provides [SHA-1][] message digests.
+
+__Examples__
+
+```js
+var md = forge.md.sha1.create();
+md.update('The quick brown fox jumps over the lazy dog');
+console.log(md.digest().toHex());
+// output: 2fd4e1c67a2d28fced849ee1bb76e7391b93eb12
+```
+
+<a name="sha256" />
+### SHA256
+
+Provides [SHA-256][] message digests.
+
+__Examples__
+
+```js
+var md = forge.md.sha256.create();
+md.update('The quick brown fox jumps over the lazy dog');
+console.log(md.digest().toHex());
+// output: d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592
+```
+
+<a name="sha384" />
+### SHA384
+
+Provides [SHA-384][] message digests.
+
+__Examples__
+
+```js
+var md = forge.md.sha384.create();
+md.update('The quick brown fox jumps over the lazy dog');
+console.log(md.digest().toHex());
+// output: ca737f1014a48f4c0b6dd43cb177b0afd9e5169367544c494011e3317dbf9a509cb1e5dc1e85a941bbee3d7f2afbc9b1
+```
+
+<a name="sha512" />
+### SHA512
+
+Provides [SHA-512][] message digests.
+
+__Examples__
+
+```js
+// SHA-512
+var md = forge.md.sha512.create();
+md.update('The quick brown fox jumps over the lazy dog');
+console.log(md.digest().toHex());
+// output: 07e547d9586f6a73f73fbac0435ed76951218fb7d0c8d788a309d785436bbb642e93a252a954f23912547d1e8a3b5ed6e1bfd7097821233fa0538f3db854fee6
+
+// SHA-512/224
+var md = forge.md.sha512.sha224.create();
+md.update('The quick brown fox jumps over the lazy dog');
+console.log(md.digest().toHex());
+// output: 944cd2847fb54558d4775db0485a50003111c8e5daa63fe722c6aa37
+
+// SHA-512/256
+var md = forge.md.sha512.sha256.create();
+md.update('The quick brown fox jumps over the lazy dog');
+console.log(md.digest().toHex());
+// output: dd9d67b371519c339ed8dbd25af90e976a1eeefd4ad3d889005e532fc5bef04d
+```
+
+<a name="md5" />
+### MD5
+
+Provides [MD5][] message digests.
+
+__Examples__
+
+```js
+var md = forge.md.md5.create();
+md.update('The quick brown fox jumps over the lazy dog');
+console.log(md.digest().toHex());
+// output: 9e107d9d372bb6826bd81d3542a419d6
+```
+
+<a name="hmac" />
+### HMAC
+
+Provides [HMAC][] w/any supported message digest algorithm.
+
+__Examples__
+
+```js
+var hmac = forge.hmac.create();
+hmac.start('sha1', 'Jefe');
+hmac.update('what do ya want for nothing?');
+console.log(hmac.digest().toHex());
+// output: effcdf6ae5eb2fa2d27416d5f184df9c259a7c79
+```
+
+---------------------------------------
+## Utilities
+
+<a name="prime" />
+### Prime
+
+Provides an API for generating large, random, probable primes.
+
+__Examples__
+
+```js
+// generate a random prime on the main JS thread
+var bits = 1024;
+forge.prime.generateProbablePrime(bits, function(err, num) {
+  console.log('random prime', num.toString(16));
+});
+
+// generate a random prime using Web Workers (if available, otherwise
+// falls back to the main thread)
+var bits = 1024;
+var options = {
+  algorithm: {
+    name: 'PRIMEINC',
+    workers: -1 // auto-optimize # of workers
+  }
+};
+forge.prime.generateProbablePrime(bits, options, function(err, num) {
+  console.log('random prime', num.toString(16));
+});
+```
+
+<a name="prng" />
+### PRNG
+
+Provides a [Fortuna][]-based cryptographically-secure pseudo-random number
+generator, to be used with a cryptographic function backend, e.g. [AES][]. An
+implementation using [AES][] as a backend is provided. An API for collecting
+entropy is given, though if window.crypto.getRandomValues is available, it will
+be used automatically.
+
+__Examples__
+
+```js
+// get some random bytes synchronously
+var bytes = forge.random.getBytesSync(32);
+console.log(forge.util.bytesToHex(bytes));
+
+// get some random bytes asynchronously
+forge.random.getBytes(32, function(err, bytes) {
+  console.log(forge.util.bytesToHex(bytes));
+});
+
+// collect some entropy if you'd like
+forge.random.collect(someRandomBytes);
+jQuery().mousemove(function(e) {
+  forge.random.collectInt(e.clientX, 16);
+  forge.random.collectInt(e.clientY, 16);
+});
+
+// specify a seed file for use with the synchronous API if you'd like
+forge.random.seedFileSync = function(needed) {
+  // get 'needed' number of random bytes from somewhere
+  return fetchedRandomBytes;
+};
+
+// specify a seed file for use with the asynchronous API if you'd like
+forge.random.seedFile = function(needed, callback) {
+  // get the 'needed' number of random bytes from somewhere
+  callback(null, fetchedRandomBytes);
+});
+
+// register the main thread to send entropy or a Web Worker to receive
+// entropy on demand from the main thread
+forge.random.registerWorker(self);
+
+// generate a new instance of a PRNG with no collected entropy
+var myPrng = forge.random.createInstance();
+```
+
+<a name="task" />
+### Tasks
+
+Provides queuing and synchronizing tasks in a web application.
+
+__Examples__
+
+```js
+```
+
+<a name="util" />
+### Utilities
+
+Provides utility functions, including byte buffer support, base64,
+bytes to/from hex, zlib inflate/deflate, etc.
+
+__Examples__
+
+```js
+// encode/decode base64
+var encoded = forge.util.encode64(str);
+var str = forge.util.decode64(encoded);
+
+// encode/decode UTF-8
+var encoded = forge.util.encodeUtf8(str);
+var str = forge.util.decodeUtf8(encoded);
+
+// bytes to/from hex
+var bytes = forge.util.hexToBytes(hex);
+var hex = forge.util.bytesToHex(bytes);
+
+// create an empty byte buffer
+var buffer = forge.util.createBuffer();
+// create a byte buffer from raw binary bytes
+var buffer = forge.util.createBuffer(input, 'raw');
+// create a byte buffer from utf8 bytes
+var buffer = forge.util.createBuffer(input, 'utf8');
+
+// get the length of the buffer in bytes
+buffer.length();
+// put bytes into the buffer
+buffer.putBytes(bytes);
+// put a 32-bit integer into the buffer
+buffer.putInt32(10);
+// buffer to hex
+buffer.toHex();
+// get a copy of the bytes in the buffer
+bytes.bytes(/* count */);
+// empty this buffer and get its contents
+bytes.getBytes(/* count */);
+
+// convert a forge buffer into a node.js Buffer
+// make sure you specify the encoding as 'binary'
+var forgeBuffer = forge.util.createBuffer();
+var nodeBuffer = new Buffer(forgeBuffer.getBytes(), 'binary');
+
+// convert a node.js Buffer into a forge buffer
+// make sure you specify the encoding as 'binary'
+var nodeBuffer = new Buffer();
+var forgeBuffer = forge.util.createBuffer(nodeBuffer.toString('binary'));
+
+// parse a URL
+var parsed = forge.util.parseUrl('http://example.com/foo?bar=baz');
+// parsed.scheme, parsed.host, parsed.port, parsed.path, parsed.fullHost
+```
+
+<a name="log" />
+### Logging
+
+Provides logging to a javascript console using various categories and
+levels of verbosity.
+
+__Examples__
+
+```js
+```
+
+<a name="debug" />
+### Debugging
+
+Provides storage of debugging information normally inaccessible in
+closures for viewing/investigation.
+
+__Examples__
+
+```js
+```
+
+<a name="fsp" />
+### Flash Socket Policy Module
+
+Provides an [Apache][] module "mod_fsp" that can serve up a Flash Socket
+Policy. See `mod_fsp/README` for more details. This module makes it easy to
+modify an [Apache][] server to allow cross domain requests to be made to it.
+
+
+Library Details
+---------------
+
+* http://digitalbazaar.com/2010/07/20/javascript-tls-1/
+* http://digitalbazaar.com/2010/07/20/javascript-tls-2/
+
+Contact
+-------
+
+* Code: https://github.com/digitalbazaar/forge
+* Bugs: https://github.com/digitalbazaar/forge/issues
+* Email: support@digitalbazaar.com
+
+Donations welcome:
+
+* Donate: paypal@digitalbazaar.com
+
+[AES]: http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
+[ASN.1]: http://en.wikipedia.org/wiki/ASN.1
+[Apache]: http://httpd.apache.org/
+[CFB]: http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation
+[CBC]: http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation
+[CTR]: http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation
+[3DES]: http://en.wikipedia.org/wiki/Triple_DES
+[DES]: http://en.wikipedia.org/wiki/Data_Encryption_Standard
+[ECB]: http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation
+[Fortuna]: http://en.wikipedia.org/wiki/Fortuna_(PRNG)
+[GCM]: http://en.wikipedia.org/wiki/GCM_mode
+[HMAC]: http://en.wikipedia.org/wiki/HMAC
+[JavaScript]: http://en.wikipedia.org/wiki/JavaScript
+[MD5]: http://en.wikipedia.org/wiki/MD5
+[OFB]: http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation
+[PKCS#5]: http://en.wikipedia.org/wiki/PKCS
+[PKCS#7]: http://en.wikipedia.org/wiki/Cryptographic_Message_Syntax
+[PKCS#10]: http://en.wikipedia.org/wiki/Certificate_signing_request
+[PKCS#12]: http://en.wikipedia.org/wiki/PKCS_%E2%99%AF12
+[RC2]: http://en.wikipedia.org/wiki/RC2
+[SHA-1]: http://en.wikipedia.org/wiki/SHA-1
+[SHA-256]: http://en.wikipedia.org/wiki/SHA-256
+[SHA-384]: http://en.wikipedia.org/wiki/SHA-384
+[SHA-512]: http://en.wikipedia.org/wiki/SHA-512
+[TLS]: http://en.wikipedia.org/wiki/Transport_Layer_Security
+[X.509]: http://en.wikipedia.org/wiki/X.509
+[node.js]: http://nodejs.org/
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/bower.json b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/bower.json
new file mode 100644
index 0000000..c65a95e
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/bower.json
@@ -0,0 +1,15 @@
+{
+  "name": "forge",
+  "version": "0.6.21",
+  "description": "JavaScript implementations of network transports, cryptography, ciphers, PKI, message digests, and various utilities.",
+  "authors": [
+    "Digital Bazaar, Inc."
+  ],
+  "license": "BSD",
+  "main": ["js/forge.js"],
+  "dependencies": {},
+  "ignore": [
+    "node_modules",
+    "bower_components"
+  ]
+}
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/build-flash.xml b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/build-flash.xml
new file mode 100644
index 0000000..8d8829c
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/build-flash.xml
@@ -0,0 +1,7 @@
+<flex-config>
+   <compiler>
+      <source-path>
+         <path-element>flash</path-element>
+      </source-path>
+   </compiler>
+</flex-config>
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/build-setup b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/build-setup
new file mode 100755
index 0000000..5c3866e
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/build-setup
@@ -0,0 +1,46 @@
+#!/bin/sh
+#
+# This shell script sets up the software to be built using 'make'. In 
+# order to perform a build from a fresh source tree, do the following:
+#
+# 1. ./build-setup
+# 2. make
+#
+# If you don't want ./configure to be run automatically, you can do
+# the following: ./build-setup -s
+
+# Process command line options
+SKIP_CONFIGURE=0
+for arg in "$*"
+do
+   case $arg in
+      "-s" | "--setup-only" ) SKIP_CONFIGURE=1 ;;
+   esac
+done
+
+# Check and add potential aclocal dirs
+MAYBE_AC_DIRS="
+   /usr/local/share/aclocal
+   /opt/local/share/aclocal
+   /sw/share/aclocal
+   "
+ACDIRS="-I m4"
+for dir in $MAYBE_AC_DIRS; do
+   if test -d $dir; then
+      ACDIRS="$ACDIRS -I $dir"
+   fi
+done
+
+# Run aclocal on the set of local ac scripts
+cd setup
+aclocal $ACDIRS
+# Generate the configure script
+autoconf && mv configure ..
+cd ..
+
+# Run the configure script if "-s" isn't a command line option
+if [ $SKIP_CONFIGURE -eq 0 ]; then
+   # Run the configure script in default development mode
+   ./configure $*
+fi
+
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/end.frag b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/end.frag
new file mode 100644
index 0000000..cbcf226
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/end.frag
@@ -0,0 +1,4 @@
+
+return require('js/forge');
+
+});
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/flash/PooledSocket.as b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/flash/PooledSocket.as
new file mode 100644
index 0000000..15e3ae4
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/flash/PooledSocket.as
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2009 Digital Bazaar, Inc. All rights reserved.
+ * 
+ * @author Dave Longley
+ */
+package
+{
+   import flash.net.Socket;
+   
+   /**
+    * A helper class that contains the ID for a Socket.
+    */
+   public class PooledSocket extends Socket
+   {
+      // the ID in the related socket pool
+      public var id:String;
+   }
+}
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/flash/SocketEvent.as b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/flash/SocketEvent.as
new file mode 100644
index 0000000..56f5e7f
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/flash/SocketEvent.as
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2009 Digital Bazaar, Inc. All rights reserved.
+ * 
+ * @author Dave Longley
+ */
+package
+{
+   import flash.events.Event;
+   
+   /**
+    * A helper class that contains the ID for a Socket.
+    */
+   public class SocketEvent extends Event
+   {
+      // the associated socket
+      public var socket:PooledSocket;
+      // an associated message
+      public var message:String;
+      
+      /**
+       * Creates a new SocketEvent.
+       * 
+       * @param type the type of event.
+       * @param socket the associated PooledSocket.
+       * @param message an associated message.
+       */
+      public function SocketEvent(
+         type:String, socket:PooledSocket, message:String = null)
+      {
+         super(type, false, false);
+         this.socket = socket;
+         this.message = message;
+      }
+   }
+}
+
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/flash/SocketPool.as b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/flash/SocketPool.as
new file mode 100644
index 0000000..d99b3ec
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/flash/SocketPool.as
@@ -0,0 +1,754 @@
+/*
+ * Copyright (c) 2009-2010 Digital Bazaar, Inc. All rights reserved.
+ * 
+ * @author Dave Longley
+ */
+package
+{
+   import flash.display.Sprite;
+   
+   /**
+    * A SocketPool is a flash object that can be embedded in a web page to
+    * allow javascript access to pools of Sockets.
+    * 
+    * Javascript can create a pool and then as many Sockets as it desires. Each
+    * Socket will be assigned a unique ID that allows continued javascript
+    * access to it. There is no limit on the number of persistent socket
+    * connections.
+    */
+   public class SocketPool extends Sprite
+   {
+      import flash.events.Event;
+      import flash.events.EventDispatcher;
+      import flash.errors.IOError;
+      import flash.events.IOErrorEvent;
+      import flash.events.ProgressEvent;
+      import flash.events.SecurityErrorEvent;
+      import flash.events.TextEvent;
+      import flash.external.ExternalInterface;
+      import flash.net.SharedObject;
+      import flash.system.Security;
+      import flash.utils.ByteArray;
+      import mx.utils.Base64Decoder;
+      import mx.utils.Base64Encoder;
+      
+      // a map of ID to Socket
+      private var mSocketMap:Object;
+      
+      // a counter for Socket IDs (Note: assumes there will be no overflow)
+      private var mNextId:uint;
+      
+      // an event dispatcher for sending events to javascript
+      private var mEventDispatcher:EventDispatcher;
+      
+      /**
+       * Creates a new, unitialized SocketPool.
+       * 
+       * @throws Error - if no external interface is available to provide
+       *                 javascript access.
+       */
+      public function SocketPool()
+      {
+         if(!ExternalInterface.available)
+         {
+            trace("ExternalInterface is not available");
+            throw new Error(
+               "Flash's ExternalInterface is not available. This is a " +
+               "requirement of SocketPool and therefore, it will be " +
+               "unavailable.");
+         }
+         else
+         {
+            try
+            {
+               // set up javascript access:
+               
+               // initializes/cleans up the SocketPool
+               ExternalInterface.addCallback("init", init);
+               ExternalInterface.addCallback("cleanup", cleanup);
+               
+               // creates/destroys a socket
+               ExternalInterface.addCallback("create", create);
+               ExternalInterface.addCallback("destroy", destroy);
+               
+               // connects/closes a socket
+               ExternalInterface.addCallback("connect", connect);
+               ExternalInterface.addCallback("close", close);
+               
+               // checks for a connection
+               ExternalInterface.addCallback("isConnected", isConnected);
+               
+               // sends/receives data over the socket
+               ExternalInterface.addCallback("send", send);
+               ExternalInterface.addCallback("receive", receive);
+               
+               // gets the number of bytes available on a socket
+               ExternalInterface.addCallback(
+                  "getBytesAvailable", getBytesAvailable);
+               
+               // add a callback for subscribing to socket events
+               ExternalInterface.addCallback("subscribe", subscribe);
+               
+               // add callbacks for deflate/inflate
+               ExternalInterface.addCallback("deflate", deflate);
+               ExternalInterface.addCallback("inflate", inflate);
+               
+               // add callbacks for local disk storage
+               ExternalInterface.addCallback("setItem", setItem);
+               ExternalInterface.addCallback("getItem", getItem);
+               ExternalInterface.addCallback("removeItem", removeItem);
+               ExternalInterface.addCallback("clearItems", clearItems);
+               
+               // socket pool is now ready
+               ExternalInterface.call("window.forge.socketPool.ready");
+            }
+            catch(e:Error)
+            {
+               log("error=" + e.errorID + "," + e.name + "," + e.message);
+               throw e;
+            }
+            
+            log("ready");
+         }
+      }
+      
+      /**
+       * A debug logging function.
+       * 
+       * @param obj the string or error to log.
+       */
+      CONFIG::debugging
+      private function log(obj:Object):void
+      {
+         if(obj is String)
+         {
+            var str:String = obj as String;
+            ExternalInterface.call("console.log", "SocketPool", str);
+         }
+         else if(obj is Error)
+         {
+            var e:Error = obj as Error;
+            log("error=" + e.errorID + "," + e.name + "," + e.message);
+         }
+      }
+      
+      CONFIG::release
+      private function log(obj:Object):void
+      {
+         // log nothing in release mode
+      }
+      
+      /**
+       * Called by javascript to initialize this SocketPool.
+       * 
+       * @param options:
+       *        marshallExceptions: true to pass exceptions to and from
+       *           javascript.
+       */
+      private function init(... args):void
+      {
+         log("init()");
+         
+         // get options from first argument
+         var options:Object = args.length > 0 ? args[0] : null;
+         
+         // create socket map, set next ID, and create event dispatcher
+         mSocketMap = new Object();
+         mNextId = 1;
+         mEventDispatcher = new EventDispatcher();
+         
+         // enable marshalling exceptions if appropriate
+         if(options != null &&
+            "marshallExceptions" in options &&
+            options.marshallExceptions === true)
+         {
+            try
+            {
+               // Note: setting marshallExceptions in IE, even inside of a
+               // try-catch block will terminate flash. Don't set this on IE.
+               ExternalInterface.marshallExceptions = true;
+            }
+            catch(e:Error)
+            {
+               log(e);
+            }
+         }
+         
+         log("init() done");
+      }
+      
+      /**
+       * Called by javascript to clean up a SocketPool.
+       */
+      private function cleanup():void
+      {
+         log("cleanup()");
+         
+         mSocketMap = null;
+         mNextId = 1;
+         mEventDispatcher = null;
+         
+         log("cleanup() done");
+      }
+      
+      /**
+       * Handles events.
+       * 
+       * @param e the event to handle.
+       */
+      private function handleEvent(e:Event):void
+      {
+         // dispatch socket event
+         var message:String = (e is TextEvent) ? (e as TextEvent).text : null;
+         mEventDispatcher.dispatchEvent(
+            new SocketEvent(e.type, e.target as PooledSocket, message));
+      }
+      
+      /**
+       * Called by javascript to create a Socket.
+       * 
+       * @return the Socket ID.
+       */
+      private function create():String
+      {
+         log("create()");
+         
+         // create a Socket
+         var id:String = "" + mNextId++;
+         var s:PooledSocket = new PooledSocket();
+         s.id = id;
+         s.addEventListener(Event.CONNECT, handleEvent);
+         s.addEventListener(Event.CLOSE, handleEvent);
+         s.addEventListener(ProgressEvent.SOCKET_DATA, handleEvent);
+         s.addEventListener(IOErrorEvent.IO_ERROR, handleEvent);
+         s.addEventListener(SecurityErrorEvent.SECURITY_ERROR, handleEvent);
+         mSocketMap[id] = s;
+         
+         log("socket " + id + " created");
+         log("create() done");
+         
+         return id;
+      }
+      
+      /**
+       * Called by javascript to clean up a Socket.
+       * 
+       * @param id the ID of the Socket to clean up.
+       */
+      private function destroy(id:String):void
+      {
+         log("destroy(" + id + ")");
+         
+         if(id in mSocketMap)
+         {
+            // remove Socket
+            delete mSocketMap[id];
+            log("socket " + id + " destroyed");
+         }
+         
+         log("destroy(" + id + ") done");
+      }
+      
+      /**
+       * Connects the Socket with the given ID to the given host and port,
+       * using the given socket policy port.
+       *
+       * @param id the ID of the Socket.
+       * @param host the host to connect to.
+       * @param port the port to connect to.
+       * @param spPort the security policy port to use, 0 to use a url.
+       * @param spUrl the http URL to the policy file to use, null for default.
+       */
+      private function connect(
+         id:String, host:String, port:uint, spPort:uint,
+         spUrl:String = null):void
+      {
+         log("connect(" +
+            id + "," + host + "," + port + "," + spPort + "," + spUrl + ")");
+         
+         if(id in mSocketMap)
+         {
+            // get the Socket
+            var s:PooledSocket = mSocketMap[id];
+            
+            // load socket policy file
+            // (permits socket access to backend)
+            if(spPort !== 0)
+            {
+               spUrl = "xmlsocket://" + host + ":" + spPort;
+               log("using cross-domain url: " + spUrl);
+               Security.loadPolicyFile(spUrl);
+            }
+            else if(spUrl !== null && typeof(spUrl) !== undefined)
+            {
+               log("using cross-domain url: " + spUrl);
+               Security.loadPolicyFile(spUrl);
+            }
+            else
+            {
+               log("not loading any cross-domain url");
+            }
+            
+            // connect
+            s.connect(host, port);
+         }
+         else
+         {
+            // no such socket
+            log("socket " + id + " does not exist");
+         }
+         
+         log("connect(" + id + ") done");
+      }
+      
+      /**
+       * Closes the Socket with the given ID.
+       *
+       * @param id the ID of the Socket.
+       */
+      private function close(id:String):void
+      {
+         log("close(" + id + ")");
+         
+         if(id in mSocketMap)
+         {
+            // close the Socket
+            var s:PooledSocket = mSocketMap[id];
+            if(s.connected)
+            {
+               s.close();
+            }
+         }
+         else
+         {
+            // no such socket
+            log("socket " + id + " does not exist");
+         }
+         
+         log("close(" + id + ") done");
+      }
+      
+      /**
+       * Determines if the Socket with the given ID is connected or not.
+       *
+       * @param id the ID of the Socket.
+       *
+       * @return true if the socket is connected, false if not.
+       */
+      private function isConnected(id:String):Boolean
+      {
+         var rval:Boolean = false;
+         log("isConnected(" + id + ")");
+         
+         if(id in mSocketMap)
+         {
+            // check the Socket
+            var s:PooledSocket = mSocketMap[id];
+            rval = s.connected;
+         }
+         else
+         {
+            // no such socket
+            log("socket " + id + " does not exist");
+         }
+         
+         log("isConnected(" + id + ") done");
+         return rval;
+      }
+      
+      /**
+       * Writes bytes to a Socket.
+       *
+       * @param id the ID of the Socket.
+       * @param bytes the string of base64-encoded bytes to write.
+       *
+       * @return true on success, false on failure. 
+       */
+      private function send(id:String, bytes:String):Boolean
+      {
+         var rval:Boolean = false;
+         log("send(" + id + ")");
+         
+         if(id in mSocketMap)
+         {
+         	// write bytes to socket
+            var s:PooledSocket = mSocketMap[id];
+            try
+            {
+               var b64:Base64Decoder = new Base64Decoder();
+               b64.decode(bytes);
+               var b:ByteArray = b64.toByteArray();
+               s.writeBytes(b, 0, b.length);
+               s.flush();
+               rval = true;
+            }
+            catch(e:IOError)
+            {
+               log(e);
+               
+               // dispatch IO error event
+               mEventDispatcher.dispatchEvent(new SocketEvent(
+                  IOErrorEvent.IO_ERROR, s, e.message));
+               if(s.connected)
+               {
+                  s.close();
+               }
+            }
+         }
+         else
+         {
+            // no such socket
+            log("socket " + id + " does not exist");
+         }
+         
+         log("send(" + id + ") done");
+         return rval;
+      }
+      
+      /**
+       * Receives bytes from a Socket.
+       *
+       * @param id the ID of the Socket.
+       * @param count the maximum number of bytes to receive.
+       *
+       * @return an object with 'rval' set to the received bytes,
+       *         base64-encoded, or set to null on error.
+       */
+      private function receive(id:String, count:uint):Object
+      {
+      	 var rval:String = null;
+         log("receive(" + id + "," + count + ")");
+         
+         if(id in mSocketMap)
+         {
+         	// only read what is available
+            var s:PooledSocket = mSocketMap[id];
+            if(count > s.bytesAvailable)
+            {
+               count = s.bytesAvailable;
+            }
+            
+            try
+            {
+               // read bytes from socket
+               var b:ByteArray = new ByteArray();
+               s.readBytes(b, 0, count);
+               b.position = 0;
+               var b64:Base64Encoder = new Base64Encoder();
+               b64.insertNewLines = false;
+               b64.encodeBytes(b, 0, b.length);
+               rval = b64.toString();
+            }
+            catch(e:IOError)
+            {
+               log(e);
+               
+               // dispatch IO error event
+               mEventDispatcher.dispatchEvent(new SocketEvent(
+                  IOErrorEvent.IO_ERROR, s, e.message));
+               if(s.connected)
+               {
+                  s.close();
+               }
+            }
+         }
+         else
+         {
+            // no such socket
+            log("socket " + id + " does not exist");
+         }
+         
+         log("receive(" + id + "," + count + ") done");
+         return {rval: rval};
+      }
+      
+      /**
+       * Gets the number of bytes available from a Socket.
+       *
+       * @param id the ID of the Socket.
+       *
+       * @return the number of available bytes.
+       */
+      private function getBytesAvailable(id:String):uint
+      {
+         var rval:uint = 0;
+         log("getBytesAvailable(" + id + ")");
+         
+         if(id in mSocketMap)
+         {
+            var s:PooledSocket = mSocketMap[id];
+            rval = s.bytesAvailable;
+         }
+         else
+         {
+            // no such socket
+            log("socket " + id + " does not exist");
+         }
+         
+         log("getBytesAvailable(" + id +") done");
+         return rval;
+      }      
+      
+      /**
+       * Registers a javascript function as a callback for an event.
+       * 
+       * @param eventType the type of event (socket event types).
+       * @param callback the name of the callback function.
+       */
+      private function subscribe(eventType:String, callback:String):void
+      {
+         log("subscribe(" + eventType + "," + callback + ")");
+         
+         switch(eventType)
+         {
+            case Event.CONNECT:
+            case Event.CLOSE:
+            case IOErrorEvent.IO_ERROR:
+            case SecurityErrorEvent.SECURITY_ERROR:
+            case ProgressEvent.SOCKET_DATA:
+            {
+               log(eventType + " => " + callback);
+               mEventDispatcher.addEventListener(
+                  eventType, function(event:SocketEvent):void
+               {
+                  log("event dispatched: " + eventType);
+                  
+                  // build event for javascript
+                  var e:Object = new Object();
+                  e.id = event.socket ? event.socket.id : 0;
+                  e.type = eventType;
+                  if(event.socket && event.socket.connected)
+                  {
+                     e.bytesAvailable = event.socket.bytesAvailable;
+                  }
+                  else
+                  {
+                     e.bytesAvailable = 0;
+                  }
+                  if(event.message)
+                  {
+                     e.message = event.message;
+                  }
+                  
+                  // send event to javascript
+                  ExternalInterface.call(callback, e);
+               });
+               break;
+            }
+            default:
+               throw new ArgumentError(
+                  "Could not subscribe to event. " +
+                  "Invalid event type specified: " + eventType);
+         }
+         
+         log("subscribe(" + eventType + "," + callback + ") done");
+      }
+      
+      /**
+       * Deflates the given data.
+       * 
+       * @param data the base64-encoded data to deflate.
+       * 
+       * @return an object with 'rval' set to deflated data, base64-encoded.
+       */
+      private function deflate(data:String):Object
+      {
+         log("deflate");
+         
+         var b64d:Base64Decoder = new Base64Decoder();
+         b64d.decode(data);
+         var b:ByteArray = b64d.toByteArray();
+         b.compress();
+         b.position = 0;
+         var b64e:Base64Encoder = new Base64Encoder();
+         b64e.insertNewLines = false;
+         b64e.encodeBytes(b, 0, b.length);
+         
+         log("deflate done");
+         return {rval: b64e.toString()};
+      }
+      
+      /**
+       * Inflates the given data.
+       * 
+       * @param data the base64-encoded data to inflate.
+       * 
+       * @return an object with 'rval' set to the inflated data,
+       *         base64-encoded, null on error.
+       */
+      private function inflate(data:String):Object
+      {
+         log("inflate");
+         var rval:Object = {rval: null};
+      	 
+      	 try
+      	 {
+            var b64d:Base64Decoder = new Base64Decoder();
+            b64d.decode(data);
+            var b:ByteArray = b64d.toByteArray();
+            b.uncompress();
+            b.position = 0;
+            var b64e:Base64Encoder = new Base64Encoder();
+            b64e.insertNewLines = false;
+            b64e.encodeBytes(b, 0, b.length);
+            rval.rval = b64e.toString();
+         }
+         catch(e:Error)
+         {
+         	log(e);
+            rval.error = {
+                id: e.errorID,
+                name: e.name,
+                message: e.message
+            };
+         }
+         
+         log("inflate done");
+         return rval;
+      }
+      
+      /**
+       * Stores an item with a key and arbitrary base64-encoded data on local
+       * disk.
+       * 
+       * @param key the key for the item.
+       * @param data the base64-encoded item data.
+       * @param storeId the storage ID to use, defaults to "forge.storage".
+       * 
+       * @return an object with rval set to true on success, false on failure
+       *         with error included.
+       */
+      private function setItem(
+         key:String, data:String, storeId:String = "forge.storage"):Object
+      {
+         var rval:Object = {rval: false};
+         try
+         {
+            var store:SharedObject = SharedObject.getLocal(storeId);
+            if(!('keys' in store.data))
+            {
+               store.data.keys = {};
+            }
+            store.data.keys[key] = data;
+            store.flush();
+            rval.rval = true;
+         }
+         catch(e:Error)
+         {
+         	log(e);
+         	rval.error = {
+                id: e.errorID,
+                name: e.name,
+                message: e.message
+            };
+         }
+         return rval;
+      }
+      
+      /**
+       * Gets an item from the local disk.
+       * 
+       * @param key the key for the item.
+       * @param storeId the storage ID to use, defaults to "forge.storage".
+       * 
+       * @return an object with rval set to the item data (which may be null),
+       *         check for error object if null.
+       */
+      private function getItem(
+         key:String, storeId:String = "forge.storage"):Object
+      {
+         var rval:Object = {rval: null};
+         try
+         {
+            var store:SharedObject = SharedObject.getLocal(storeId);
+            if('keys' in store.data && key in store.data.keys)
+            {
+               rval.rval = store.data.keys[key];
+            }
+         }
+         catch(e:Error)
+         {
+         	log(e);
+            rval.error = {
+                id: e.errorID,
+                name: e.name,
+                message: e.message
+            };
+         }
+         return rval;
+      }
+      
+      /**
+       * Removes an item from the local disk.
+       * 
+       * @param key the key for the item.
+       * @param storeId the storage ID to use, defaults to "forge.storage".
+       * 
+       * @return an object with rval set to true if removed, false if not.
+       */
+      private function removeItem(
+         key:String, storeId:String = "forge.storage"):Object
+      {
+         var rval:Object = {rval: false};
+         try
+         {
+            var store:SharedObject = SharedObject.getLocal(storeId);
+            if('keys' in store.data && key in store.data.keys)
+            {
+               delete store.data.keys[key];
+               
+               // clean up storage entirely if empty
+               var empty:Boolean = true;
+               for(var prop:String in store.data.keys)
+               {
+                  empty = false;
+                  break;
+               }
+               if(empty)
+               {
+                  store.clear();
+               }
+               rval.rval = true;
+            }
+         }
+         catch(e:Error)
+         {
+            log(e);
+            rval.error = {
+                id: e.errorID,
+                name: e.name,
+                message: e.message
+            };
+         }
+         return rval;
+      }
+      
+      /**
+       * Clears an entire store of all of its items.
+       * 
+       * @param storeId the storage ID to use, defaults to "forge.storage".
+       * 
+       * @return an object with rval set to true if cleared, false if not.
+       */
+      private function clearItems(storeId:String = "forge.storage"):Object
+      {
+         var rval:Object = {rval: false};
+         try
+         {
+            var store:SharedObject = SharedObject.getLocal(storeId);
+            store.clear();
+            rval.rval = true;
+         }
+         catch(e:Error)
+         {
+            log(e);
+            rval.error = {
+                id: e.errorID,
+                name: e.name,
+                message: e.message
+            };
+         }
+         return rval;
+      }
+   }
+}
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/aes.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/aes.js
new file mode 100644
index 0000000..d4b745b
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/aes.js
@@ -0,0 +1,1146 @@
+/**
+ * Advanced Encryption Standard (AES) implementation.
+ *
+ * This implementation is based on the public domain library 'jscrypto' which
+ * was written by:
+ *
+ * Emily Stark (estark@stanford.edu)
+ * Mike Hamburg (mhamburg@stanford.edu)
+ * Dan Boneh (dabo@cs.stanford.edu)
+ *
+ * Parts of this code are based on the OpenSSL implementation of AES:
+ * http://www.openssl.org
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+/* AES API */
+forge.aes = forge.aes || {};
+
+/**
+ * Deprecated. Instead, use:
+ *
+ * var cipher = forge.cipher.createCipher('AES-<mode>', key);
+ * cipher.start({iv: iv});
+ *
+ * Creates an AES cipher object to encrypt data using the given symmetric key.
+ * The output will be stored in the 'output' member of the returned cipher.
+ *
+ * The key and iv may be given as a string of bytes, an array of bytes,
+ * a byte buffer, or an array of 32-bit words.
+ *
+ * @param key the symmetric key to use.
+ * @param iv the initialization vector to use.
+ * @param output the buffer to write to, null to create one.
+ * @param mode the cipher mode to use (default: 'CBC').
+ *
+ * @return the cipher.
+ */
+forge.aes.startEncrypting = function(key, iv, output, mode) {
+  var cipher = _createCipher({
+    key: key,
+    output: output,
+    decrypt: false,
+    mode: mode
+  });
+  cipher.start(iv);
+  return cipher;
+};
+
+/**
+ * Deprecated. Instead, use:
+ *
+ * var cipher = forge.cipher.createCipher('AES-<mode>', key);
+ *
+ * Creates an AES cipher object to encrypt data using the given symmetric key.
+ *
+ * The key may be given as a string of bytes, an array of bytes, a
+ * byte buffer, or an array of 32-bit words.
+ *
+ * @param key the symmetric key to use.
+ * @param mode the cipher mode to use (default: 'CBC').
+ *
+ * @return the cipher.
+ */
+forge.aes.createEncryptionCipher = function(key, mode) {
+  return _createCipher({
+    key: key,
+    output: null,
+    decrypt: false,
+    mode: mode
+  });
+};
+
+/**
+ * Deprecated. Instead, use:
+ *
+ * var decipher = forge.cipher.createDecipher('AES-<mode>', key);
+ * decipher.start({iv: iv});
+ *
+ * Creates an AES cipher object to decrypt data using the given symmetric key.
+ * The output will be stored in the 'output' member of the returned cipher.
+ *
+ * The key and iv may be given as a string of bytes, an array of bytes,
+ * a byte buffer, or an array of 32-bit words.
+ *
+ * @param key the symmetric key to use.
+ * @param iv the initialization vector to use.
+ * @param output the buffer to write to, null to create one.
+ * @param mode the cipher mode to use (default: 'CBC').
+ *
+ * @return the cipher.
+ */
+forge.aes.startDecrypting = function(key, iv, output, mode) {
+  var cipher = _createCipher({
+    key: key,
+    output: output,
+    decrypt: true,
+    mode: mode
+  });
+  cipher.start(iv);
+  return cipher;
+};
+
+/**
+ * Deprecated. Instead, use:
+ *
+ * var decipher = forge.cipher.createDecipher('AES-<mode>', key);
+ *
+ * Creates an AES cipher object to decrypt data using the given symmetric key.
+ *
+ * The key may be given as a string of bytes, an array of bytes, a
+ * byte buffer, or an array of 32-bit words.
+ *
+ * @param key the symmetric key to use.
+ * @param mode the cipher mode to use (default: 'CBC').
+ *
+ * @return the cipher.
+ */
+forge.aes.createDecryptionCipher = function(key, mode) {
+  return _createCipher({
+    key: key,
+    output: null,
+    decrypt: true,
+    mode: mode
+  });
+};
+
+/**
+ * Creates a new AES cipher algorithm object.
+ *
+ * @param name the name of the algorithm.
+ * @param mode the mode factory function.
+ *
+ * @return the AES algorithm object.
+ */
+forge.aes.Algorithm = function(name, mode) {
+  if(!init) {
+    initialize();
+  }
+  var self = this;
+  self.name = name;
+  self.mode = new mode({
+    blockSize: 16,
+    cipher: {
+      encrypt: function(inBlock, outBlock) {
+        return _updateBlock(self._w, inBlock, outBlock, false);
+      },
+      decrypt: function(inBlock, outBlock) {
+        return _updateBlock(self._w, inBlock, outBlock, true);
+      }
+    }
+  });
+  self._init = false;
+};
+
+/**
+ * Initializes this AES algorithm by expanding its key.
+ *
+ * @param options the options to use.
+ *          key the key to use with this algorithm.
+ *          decrypt true if the algorithm should be initialized for decryption,
+ *            false for encryption.
+ */
+forge.aes.Algorithm.prototype.initialize = function(options) {
+  if(this._init) {
+    return;
+  }
+
+  var key = options.key;
+  var tmp;
+
+  /* Note: The key may be a string of bytes, an array of bytes, a byte
+    buffer, or an array of 32-bit integers. If the key is in bytes, then
+    it must be 16, 24, or 32 bytes in length. If it is in 32-bit
+    integers, it must be 4, 6, or 8 integers long. */
+
+  if(typeof key === 'string' &&
+    (key.length === 16 || key.length === 24 || key.length === 32)) {
+    // convert key string into byte buffer
+    key = forge.util.createBuffer(key);
+  } else if(forge.util.isArray(key) &&
+    (key.length === 16 || key.length === 24 || key.length === 32)) {
+    // convert key integer array into byte buffer
+    tmp = key;
+    key = forge.util.createBuffer();
+    for(var i = 0; i < tmp.length; ++i) {
+      key.putByte(tmp[i]);
+    }
+  }
+
+  // convert key byte buffer into 32-bit integer array
+  if(!forge.util.isArray(key)) {
+    tmp = key;
+    key = [];
+
+    // key lengths of 16, 24, 32 bytes allowed
+    var len = tmp.length();
+    if(len === 16 || len === 24 || len === 32) {
+      len = len >>> 2;
+      for(var i = 0; i < len; ++i) {
+        key.push(tmp.getInt32());
+      }
+    }
+  }
+
+  // key must be an array of 32-bit integers by now
+  if(!forge.util.isArray(key) ||
+    !(key.length === 4 || key.length === 6 || key.length === 8)) {
+    throw new Error('Invalid key parameter.');
+  }
+
+  // encryption operation is always used for these modes
+  var mode = this.mode.name;
+  var encryptOp = (['CFB', 'OFB', 'CTR', 'GCM'].indexOf(mode) !== -1);
+
+  // do key expansion
+  this._w = _expandKey(key, options.decrypt && !encryptOp);
+  this._init = true;
+};
+
+/**
+ * Expands a key. Typically only used for testing.
+ *
+ * @param key the symmetric key to expand, as an array of 32-bit words.
+ * @param decrypt true to expand for decryption, false for encryption.
+ *
+ * @return the expanded key.
+ */
+forge.aes._expandKey = function(key, decrypt) {
+  if(!init) {
+    initialize();
+  }
+  return _expandKey(key, decrypt);
+};
+
+/**
+ * Updates a single block. Typically only used for testing.
+ *
+ * @param w the expanded key to use.
+ * @param input an array of block-size 32-bit words.
+ * @param output an array of block-size 32-bit words.
+ * @param decrypt true to decrypt, false to encrypt.
+ */
+forge.aes._updateBlock = _updateBlock;
+
+
+/** Register AES algorithms **/
+
+registerAlgorithm('AES-CBC', forge.cipher.modes.cbc);
+registerAlgorithm('AES-CFB', forge.cipher.modes.cfb);
+registerAlgorithm('AES-OFB', forge.cipher.modes.ofb);
+registerAlgorithm('AES-CTR', forge.cipher.modes.ctr);
+registerAlgorithm('AES-GCM', forge.cipher.modes.gcm);
+
+function registerAlgorithm(name, mode) {
+  var factory = function() {
+    return new forge.aes.Algorithm(name, mode);
+  };
+  forge.cipher.registerAlgorithm(name, factory);
+}
+
+
+/** AES implementation **/
+
+var init = false; // not yet initialized
+var Nb = 4;       // number of words comprising the state (AES = 4)
+var sbox;         // non-linear substitution table used in key expansion
+var isbox;        // inversion of sbox
+var rcon;         // round constant word array
+var mix;          // mix-columns table
+var imix;         // inverse mix-columns table
+
+/**
+ * Performs initialization, ie: precomputes tables to optimize for speed.
+ *
+ * One way to understand how AES works is to imagine that 'addition' and
+ * 'multiplication' are interfaces that require certain mathematical
+ * properties to hold true (ie: they are associative) but they might have
+ * different implementations and produce different kinds of results ...
+ * provided that their mathematical properties remain true. AES defines
+ * its own methods of addition and multiplication but keeps some important
+ * properties the same, ie: associativity and distributivity. The
+ * explanation below tries to shed some light on how AES defines addition
+ * and multiplication of bytes and 32-bit words in order to perform its
+ * encryption and decryption algorithms.
+ *
+ * The basics:
+ *
+ * The AES algorithm views bytes as binary representations of polynomials
+ * that have either 1 or 0 as the coefficients. It defines the addition
+ * or subtraction of two bytes as the XOR operation. It also defines the
+ * multiplication of two bytes as a finite field referred to as GF(2^8)
+ * (Note: 'GF' means "Galois Field" which is a field that contains a finite
+ * number of elements so GF(2^8) has 256 elements).
+ *
+ * This means that any two bytes can be represented as binary polynomials;
+ * when they multiplied together and modularly reduced by an irreducible
+ * polynomial of the 8th degree, the results are the field GF(2^8). The
+ * specific irreducible polynomial that AES uses in hexadecimal is 0x11b.
+ * This multiplication is associative with 0x01 as the identity:
+ *
+ * (b * 0x01 = GF(b, 0x01) = b).
+ *
+ * The operation GF(b, 0x02) can be performed at the byte level by left
+ * shifting b once and then XOR'ing it (to perform the modular reduction)
+ * with 0x11b if b is >= 128. Repeated application of the multiplication
+ * of 0x02 can be used to implement the multiplication of any two bytes.
+ *
+ * For instance, multiplying 0x57 and 0x13, denoted as GF(0x57, 0x13), can
+ * be performed by factoring 0x13 into 0x01, 0x02, and 0x10. Then these
+ * factors can each be multiplied by 0x57 and then added together. To do
+ * the multiplication, values for 0x57 multiplied by each of these 3 factors
+ * can be precomputed and stored in a table. To add them, the values from
+ * the table are XOR'd together.
+ *
+ * AES also defines addition and multiplication of words, that is 4-byte
+ * numbers represented as polynomials of 3 degrees where the coefficients
+ * are the values of the bytes.
+ *
+ * The word [a0, a1, a2, a3] is a polynomial a3x^3 + a2x^2 + a1x + a0.
+ *
+ * Addition is performed by XOR'ing like powers of x. Multiplication
+ * is performed in two steps, the first is an algebriac expansion as
+ * you would do normally (where addition is XOR). But the result is
+ * a polynomial larger than 3 degrees and thus it cannot fit in a word. So
+ * next the result is modularly reduced by an AES-specific polynomial of
+ * degree 4 which will always produce a polynomial of less than 4 degrees
+ * such that it will fit in a word. In AES, this polynomial is x^4 + 1.
+ *
+ * The modular product of two polynomials 'a' and 'b' is thus:
+ *
+ * d(x) = d3x^3 + d2x^2 + d1x + d0
+ * with
+ * d0 = GF(a0, b0) ^ GF(a3, b1) ^ GF(a2, b2) ^ GF(a1, b3)
+ * d1 = GF(a1, b0) ^ GF(a0, b1) ^ GF(a3, b2) ^ GF(a2, b3)
+ * d2 = GF(a2, b0) ^ GF(a1, b1) ^ GF(a0, b2) ^ GF(a3, b3)
+ * d3 = GF(a3, b0) ^ GF(a2, b1) ^ GF(a1, b2) ^ GF(a0, b3)
+ *
+ * As a matrix:
+ *
+ * [d0] = [a0 a3 a2 a1][b0]
+ * [d1]   [a1 a0 a3 a2][b1]
+ * [d2]   [a2 a1 a0 a3][b2]
+ * [d3]   [a3 a2 a1 a0][b3]
+ *
+ * Special polynomials defined by AES (0x02 == {02}):
+ * a(x)    = {03}x^3 + {01}x^2 + {01}x + {02}
+ * a^-1(x) = {0b}x^3 + {0d}x^2 + {09}x + {0e}.
+ *
+ * These polynomials are used in the MixColumns() and InverseMixColumns()
+ * operations, respectively, to cause each element in the state to affect
+ * the output (referred to as diffusing).
+ *
+ * RotWord() uses: a0 = a1 = a2 = {00} and a3 = {01}, which is the
+ * polynomial x3.
+ *
+ * The ShiftRows() method modifies the last 3 rows in the state (where
+ * the state is 4 words with 4 bytes per word) by shifting bytes cyclically.
+ * The 1st byte in the second row is moved to the end of the row. The 1st
+ * and 2nd bytes in the third row are moved to the end of the row. The 1st,
+ * 2nd, and 3rd bytes are moved in the fourth row.
+ *
+ * More details on how AES arithmetic works:
+ *
+ * In the polynomial representation of binary numbers, XOR performs addition
+ * and subtraction and multiplication in GF(2^8) denoted as GF(a, b)
+ * corresponds with the multiplication of polynomials modulo an irreducible
+ * polynomial of degree 8. In other words, for AES, GF(a, b) will multiply
+ * polynomial 'a' with polynomial 'b' and then do a modular reduction by
+ * an AES-specific irreducible polynomial of degree 8.
+ *
+ * A polynomial is irreducible if its only divisors are one and itself. For
+ * the AES algorithm, this irreducible polynomial is:
+ *
+ * m(x) = x^8 + x^4 + x^3 + x + 1,
+ *
+ * or {01}{1b} in hexadecimal notation, where each coefficient is a bit:
+ * 100011011 = 283 = 0x11b.
+ *
+ * For example, GF(0x57, 0x83) = 0xc1 because
+ *
+ * 0x57 = 87  = 01010111 = x^6 + x^4 + x^2 + x + 1
+ * 0x85 = 131 = 10000101 = x^7 + x + 1
+ *
+ * (x^6 + x^4 + x^2 + x + 1) * (x^7 + x + 1)
+ * =  x^13 + x^11 + x^9 + x^8 + x^7 +
+ *    x^7 + x^5 + x^3 + x^2 + x +
+ *    x^6 + x^4 + x^2 + x + 1
+ * =  x^13 + x^11 + x^9 + x^8 + x^6 + x^5 + x^4 + x^3 + 1 = y
+ *    y modulo (x^8 + x^4 + x^3 + x + 1)
+ * =  x^7 + x^6 + 1.
+ *
+ * The modular reduction by m(x) guarantees the result will be a binary
+ * polynomial of less than degree 8, so that it can fit in a byte.
+ *
+ * The operation to multiply a binary polynomial b with x (the polynomial
+ * x in binary representation is 00000010) is:
+ *
+ * b_7x^8 + b_6x^7 + b_5x^6 + b_4x^5 + b_3x^4 + b_2x^3 + b_1x^2 + b_0x^1
+ *
+ * To get GF(b, x) we must reduce that by m(x). If b_7 is 0 (that is the
+ * most significant bit is 0 in b) then the result is already reduced. If
+ * it is 1, then we can reduce it by subtracting m(x) via an XOR.
+ *
+ * It follows that multiplication by x (00000010 or 0x02) can be implemented
+ * by performing a left shift followed by a conditional bitwise XOR with
+ * 0x1b. This operation on bytes is denoted by xtime(). Multiplication by
+ * higher powers of x can be implemented by repeated application of xtime().
+ *
+ * By adding intermediate results, multiplication by any constant can be
+ * implemented. For instance:
+ *
+ * GF(0x57, 0x13) = 0xfe because:
+ *
+ * xtime(b) = (b & 128) ? (b << 1 ^ 0x11b) : (b << 1)
+ *
+ * Note: We XOR with 0x11b instead of 0x1b because in javascript our
+ * datatype for b can be larger than 1 byte, so a left shift will not
+ * automatically eliminate bits that overflow a byte ... by XOR'ing the
+ * overflow bit with 1 (the extra one from 0x11b) we zero it out.
+ *
+ * GF(0x57, 0x02) = xtime(0x57) = 0xae
+ * GF(0x57, 0x04) = xtime(0xae) = 0x47
+ * GF(0x57, 0x08) = xtime(0x47) = 0x8e
+ * GF(0x57, 0x10) = xtime(0x8e) = 0x07
+ *
+ * GF(0x57, 0x13) = GF(0x57, (0x01 ^ 0x02 ^ 0x10))
+ *
+ * And by the distributive property (since XOR is addition and GF() is
+ * multiplication):
+ *
+ * = GF(0x57, 0x01) ^ GF(0x57, 0x02) ^ GF(0x57, 0x10)
+ * = 0x57 ^ 0xae ^ 0x07
+ * = 0xfe.
+ */
+function initialize() {
+  init = true;
+
+  /* Populate the Rcon table. These are the values given by
+    [x^(i-1),{00},{00},{00}] where x^(i-1) are powers of x (and x = 0x02)
+    in the field of GF(2^8), where i starts at 1.
+
+    rcon[0] = [0x00, 0x00, 0x00, 0x00]
+    rcon[1] = [0x01, 0x00, 0x00, 0x00] 2^(1-1) = 2^0 = 1
+    rcon[2] = [0x02, 0x00, 0x00, 0x00] 2^(2-1) = 2^1 = 2
+    ...
+    rcon[9]  = [0x1B, 0x00, 0x00, 0x00] 2^(9-1)  = 2^8 = 0x1B
+    rcon[10] = [0x36, 0x00, 0x00, 0x00] 2^(10-1) = 2^9 = 0x36
+
+    We only store the first byte because it is the only one used.
+  */
+  rcon = [0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36];
+
+  // compute xtime table which maps i onto GF(i, 0x02)
+  var xtime = new Array(256);
+  for(var i = 0; i < 128; ++i) {
+    xtime[i] = i << 1;
+    xtime[i + 128] = (i + 128) << 1 ^ 0x11B;
+  }
+
+  // compute all other tables
+  sbox = new Array(256);
+  isbox = new Array(256);
+  mix = new Array(4);
+  imix = new Array(4);
+  for(var i = 0; i < 4; ++i) {
+    mix[i] = new Array(256);
+    imix[i] = new Array(256);
+  }
+  var e = 0, ei = 0, e2, e4, e8, sx, sx2, me, ime;
+  for(var i = 0; i < 256; ++i) {
+    /* We need to generate the SubBytes() sbox and isbox tables so that
+      we can perform byte substitutions. This requires us to traverse
+      all of the elements in GF, find their multiplicative inverses,
+      and apply to each the following affine transformation:
+
+      bi' = bi ^ b(i + 4) mod 8 ^ b(i + 5) mod 8 ^ b(i + 6) mod 8 ^
+            b(i + 7) mod 8 ^ ci
+      for 0 <= i < 8, where bi is the ith bit of the byte, and ci is the
+      ith bit of a byte c with the value {63} or {01100011}.
+
+      It is possible to traverse every possible value in a Galois field
+      using what is referred to as a 'generator'. There are many
+      generators (128 out of 256): 3,5,6,9,11,82 to name a few. To fully
+      traverse GF we iterate 255 times, multiplying by our generator
+      each time.
+
+      On each iteration we can determine the multiplicative inverse for
+      the current element.
+
+      Suppose there is an element in GF 'e'. For a given generator 'g',
+      e = g^x. The multiplicative inverse of e is g^(255 - x). It turns
+      out that if use the inverse of a generator as another generator
+      it will produce all of the corresponding multiplicative inverses
+      at the same time. For this reason, we choose 5 as our inverse
+      generator because it only requires 2 multiplies and 1 add and its
+      inverse, 82, requires relatively few operations as well.
+
+      In order to apply the affine transformation, the multiplicative
+      inverse 'ei' of 'e' can be repeatedly XOR'd (4 times) with a
+      bit-cycling of 'ei'. To do this 'ei' is first stored in 's' and
+      'x'. Then 's' is left shifted and the high bit of 's' is made the
+      low bit. The resulting value is stored in 's'. Then 'x' is XOR'd
+      with 's' and stored in 'x'. On each subsequent iteration the same
+      operation is performed. When 4 iterations are complete, 'x' is
+      XOR'd with 'c' (0x63) and the transformed value is stored in 'x'.
+      For example:
+
+      s = 01000001
+      x = 01000001
+
+      iteration 1: s = 10000010, x ^= s
+      iteration 2: s = 00000101, x ^= s
+      iteration 3: s = 00001010, x ^= s
+      iteration 4: s = 00010100, x ^= s
+      x ^= 0x63
+
+      This can be done with a loop where s = (s << 1) | (s >> 7). However,
+      it can also be done by using a single 16-bit (in this case 32-bit)
+      number 'sx'. Since XOR is an associative operation, we can set 'sx'
+      to 'ei' and then XOR it with 'sx' left-shifted 1,2,3, and 4 times.
+      The most significant bits will flow into the high 8 bit positions
+      and be correctly XOR'd with one another. All that remains will be
+      to cycle the high 8 bits by XOR'ing them all with the lower 8 bits
+      afterwards.
+
+      At the same time we're populating sbox and isbox we can precompute
+      the multiplication we'll need to do to do MixColumns() later.
+    */
+
+    // apply affine transformation
+    sx = ei ^ (ei << 1) ^ (ei << 2) ^ (ei << 3) ^ (ei << 4);
+    sx = (sx >> 8) ^ (sx & 255) ^ 0x63;
+
+    // update tables
+    sbox[e] = sx;
+    isbox[sx] = e;
+
+    /* Mixing columns is done using matrix multiplication. The columns
+      that are to be mixed are each a single word in the current state.
+      The state has Nb columns (4 columns). Therefore each column is a
+      4 byte word. So to mix the columns in a single column 'c' where
+      its rows are r0, r1, r2, and r3, we use the following matrix
+      multiplication:
+
+      [2 3 1 1]*[r0,c]=[r'0,c]
+      [1 2 3 1] [r1,c] [r'1,c]
+      [1 1 2 3] [r2,c] [r'2,c]
+      [3 1 1 2] [r3,c] [r'3,c]
+
+      r0, r1, r2, and r3 are each 1 byte of one of the words in the
+      state (a column). To do matrix multiplication for each mixed
+      column c' we multiply the corresponding row from the left matrix
+      with the corresponding column from the right matrix. In total, we
+      get 4 equations:
+
+      r0,c' = 2*r0,c + 3*r1,c + 1*r2,c + 1*r3,c
+      r1,c' = 1*r0,c + 2*r1,c + 3*r2,c + 1*r3,c
+      r2,c' = 1*r0,c + 1*r1,c + 2*r2,c + 3*r3,c
+      r3,c' = 3*r0,c + 1*r1,c + 1*r2,c + 2*r3,c
+
+      As usual, the multiplication is as previously defined and the
+      addition is XOR. In order to optimize mixing columns we can store
+      the multiplication results in tables. If you think of the whole
+      column as a word (it might help to visualize by mentally rotating
+      the equations above by counterclockwise 90 degrees) then you can
+      see that it would be useful to map the multiplications performed on
+      each byte (r0, r1, r2, r3) onto a word as well. For instance, we
+      could map 2*r0,1*r0,1*r0,3*r0 onto a word by storing 2*r0 in the
+      highest 8 bits and 3*r0 in the lowest 8 bits (with the other two
+      respectively in the middle). This means that a table can be
+      constructed that uses r0 as an index to the word. We can do the
+      same with r1, r2, and r3, creating a total of 4 tables.
+
+      To construct a full c', we can just look up each byte of c in
+      their respective tables and XOR the results together.
+
+      Also, to build each table we only have to calculate the word
+      for 2,1,1,3 for every byte ... which we can do on each iteration
+      of this loop since we will iterate over every byte. After we have
+      calculated 2,1,1,3 we can get the results for the other tables
+      by cycling the byte at the end to the beginning. For instance
+      we can take the result of table 2,1,1,3 and produce table 3,2,1,1
+      by moving the right most byte to the left most position just like
+      how you can imagine the 3 moved out of 2,1,1,3 and to the front
+      to produce 3,2,1,1.
+
+      There is another optimization in that the same multiples of
+      the current element we need in order to advance our generator
+      to the next iteration can be reused in performing the 2,1,1,3
+      calculation. We also calculate the inverse mix column tables,
+      with e,9,d,b being the inverse of 2,1,1,3.
+
+      When we're done, and we need to actually mix columns, the first
+      byte of each state word should be put through mix[0] (2,1,1,3),
+      the second through mix[1] (3,2,1,1) and so forth. Then they should
+      be XOR'd together to produce the fully mixed column.
+    */
+
+    // calculate mix and imix table values
+    sx2 = xtime[sx];
+    e2 = xtime[e];
+    e4 = xtime[e2];
+    e8 = xtime[e4];
+    me =
+      (sx2 << 24) ^  // 2
+      (sx << 16) ^   // 1
+      (sx << 8) ^    // 1
+      (sx ^ sx2);    // 3
+    ime =
+      (e2 ^ e4 ^ e8) << 24 ^  // E (14)
+      (e ^ e8) << 16 ^        // 9
+      (e ^ e4 ^ e8) << 8 ^    // D (13)
+      (e ^ e2 ^ e8);          // B (11)
+    // produce each of the mix tables by rotating the 2,1,1,3 value
+    for(var n = 0; n < 4; ++n) {
+      mix[n][e] = me;
+      imix[n][sx] = ime;
+      // cycle the right most byte to the left most position
+      // ie: 2,1,1,3 becomes 3,2,1,1
+      me = me << 24 | me >>> 8;
+      ime = ime << 24 | ime >>> 8;
+    }
+
+    // get next element and inverse
+    if(e === 0) {
+      // 1 is the inverse of 1
+      e = ei = 1;
+    } else {
+      // e = 2e + 2*2*2*(10e)) = multiply e by 82 (chosen generator)
+      // ei = ei + 2*2*ei = multiply ei by 5 (inverse generator)
+      e = e2 ^ xtime[xtime[xtime[e2 ^ e8]]];
+      ei ^= xtime[xtime[ei]];
+    }
+  }
+}
+
+/**
+ * Generates a key schedule using the AES key expansion algorithm.
+ *
+ * The AES algorithm takes the Cipher Key, K, and performs a Key Expansion
+ * routine to generate a key schedule. The Key Expansion generates a total
+ * of Nb*(Nr + 1) words: the algorithm requires an initial set of Nb words,
+ * and each of the Nr rounds requires Nb words of key data. The resulting
+ * key schedule consists of a linear array of 4-byte words, denoted [wi ],
+ * with i in the range 0 ≤ i < Nb(Nr + 1).
+ *
+ * KeyExpansion(byte key[4*Nk], word w[Nb*(Nr+1)], Nk)
+ * AES-128 (Nb=4, Nk=4, Nr=10)
+ * AES-192 (Nb=4, Nk=6, Nr=12)
+ * AES-256 (Nb=4, Nk=8, Nr=14)
+ * Note: Nr=Nk+6.
+ *
+ * Nb is the number of columns (32-bit words) comprising the State (or
+ * number of bytes in a block). For AES, Nb=4.
+ *
+ * @param key the key to schedule (as an array of 32-bit words).
+ * @param decrypt true to modify the key schedule to decrypt, false not to.
+ *
+ * @return the generated key schedule.
+ */
+function _expandKey(key, decrypt) {
+  // copy the key's words to initialize the key schedule
+  var w = key.slice(0);
+
+  /* RotWord() will rotate a word, moving the first byte to the last
+    byte's position (shifting the other bytes left).
+
+    We will be getting the value of Rcon at i / Nk. 'i' will iterate
+    from Nk to (Nb * Nr+1). Nk = 4 (4 byte key), Nb = 4 (4 words in
+    a block), Nr = Nk + 6 (10). Therefore 'i' will iterate from
+    4 to 44 (exclusive). Each time we iterate 4 times, i / Nk will
+    increase by 1. We use a counter iNk to keep track of this.
+   */
+
+  // go through the rounds expanding the key
+  var temp, iNk = 1;
+  var Nk = w.length;
+  var Nr1 = Nk + 6 + 1;
+  var end = Nb * Nr1;
+  for(var i = Nk; i < end; ++i) {
+    temp = w[i - 1];
+    if(i % Nk === 0) {
+      // temp = SubWord(RotWord(temp)) ^ Rcon[i / Nk]
+      temp =
+        sbox[temp >>> 16 & 255] << 24 ^
+        sbox[temp >>> 8 & 255] << 16 ^
+        sbox[temp & 255] << 8 ^
+        sbox[temp >>> 24] ^ (rcon[iNk] << 24);
+      iNk++;
+    } else if(Nk > 6 && (i % Nk === 4)) {
+      // temp = SubWord(temp)
+      temp =
+        sbox[temp >>> 24] << 24 ^
+        sbox[temp >>> 16 & 255] << 16 ^
+        sbox[temp >>> 8 & 255] << 8 ^
+        sbox[temp & 255];
+    }
+    w[i] = w[i - Nk] ^ temp;
+  }
+
+   /* When we are updating a cipher block we always use the code path for
+     encryption whether we are decrypting or not (to shorten code and
+     simplify the generation of look up tables). However, because there
+     are differences in the decryption algorithm, other than just swapping
+     in different look up tables, we must transform our key schedule to
+     account for these changes:
+
+     1. The decryption algorithm gets its key rounds in reverse order.
+     2. The decryption algorithm adds the round key before mixing columns
+       instead of afterwards.
+
+     We don't need to modify our key schedule to handle the first case,
+     we can just traverse the key schedule in reverse order when decrypting.
+
+     The second case requires a little work.
+
+     The tables we built for performing rounds will take an input and then
+     perform SubBytes() and MixColumns() or, for the decrypt version,
+     InvSubBytes() and InvMixColumns(). But the decrypt algorithm requires
+     us to AddRoundKey() before InvMixColumns(). This means we'll need to
+     apply some transformations to the round key to inverse-mix its columns
+     so they'll be correct for moving AddRoundKey() to after the state has
+     had its columns inverse-mixed.
+
+     To inverse-mix the columns of the state when we're decrypting we use a
+     lookup table that will apply InvSubBytes() and InvMixColumns() at the
+     same time. However, the round key's bytes are not inverse-substituted
+     in the decryption algorithm. To get around this problem, we can first
+     substitute the bytes in the round key so that when we apply the
+     transformation via the InvSubBytes()+InvMixColumns() table, it will
+     undo our substitution leaving us with the original value that we
+     want -- and then inverse-mix that value.
+
+     This change will correctly alter our key schedule so that we can XOR
+     each round key with our already transformed decryption state. This
+     allows us to use the same code path as the encryption algorithm.
+
+     We make one more change to the decryption key. Since the decryption
+     algorithm runs in reverse from the encryption algorithm, we reverse
+     the order of the round keys to avoid having to iterate over the key
+     schedule backwards when running the encryption algorithm later in
+     decryption mode. In addition to reversing the order of the round keys,
+     we also swap each round key's 2nd and 4th rows. See the comments
+     section where rounds are performed for more details about why this is
+     done. These changes are done inline with the other substitution
+     described above.
+  */
+  if(decrypt) {
+    var tmp;
+    var m0 = imix[0];
+    var m1 = imix[1];
+    var m2 = imix[2];
+    var m3 = imix[3];
+    var wnew = w.slice(0);
+    end = w.length;
+    for(var i = 0, wi = end - Nb; i < end; i += Nb, wi -= Nb) {
+      // do not sub the first or last round key (round keys are Nb
+      // words) as no column mixing is performed before they are added,
+      // but do change the key order
+      if(i === 0 || i === (end - Nb)) {
+        wnew[i] = w[wi];
+        wnew[i + 1] = w[wi + 3];
+        wnew[i + 2] = w[wi + 2];
+        wnew[i + 3] = w[wi + 1];
+      } else {
+        // substitute each round key byte because the inverse-mix
+        // table will inverse-substitute it (effectively cancel the
+        // substitution because round key bytes aren't sub'd in
+        // decryption mode) and swap indexes 3 and 1
+        for(var n = 0; n < Nb; ++n) {
+          tmp = w[wi + n];
+          wnew[i + (3&-n)] =
+            m0[sbox[tmp >>> 24]] ^
+            m1[sbox[tmp >>> 16 & 255]] ^
+            m2[sbox[tmp >>> 8 & 255]] ^
+            m3[sbox[tmp & 255]];
+        }
+      }
+    }
+    w = wnew;
+  }
+
+  return w;
+}
+
+/**
+ * Updates a single block (16 bytes) using AES. The update will either
+ * encrypt or decrypt the block.
+ *
+ * @param w the key schedule.
+ * @param input the input block (an array of 32-bit words).
+ * @param output the updated output block.
+ * @param decrypt true to decrypt the block, false to encrypt it.
+ */
+function _updateBlock(w, input, output, decrypt) {
+  /*
+  Cipher(byte in[4*Nb], byte out[4*Nb], word w[Nb*(Nr+1)])
+  begin
+    byte state[4,Nb]
+    state = in
+    AddRoundKey(state, w[0, Nb-1])
+    for round = 1 step 1 to Nr–1
+      SubBytes(state)
+      ShiftRows(state)
+      MixColumns(state)
+      AddRoundKey(state, w[round*Nb, (round+1)*Nb-1])
+    end for
+    SubBytes(state)
+    ShiftRows(state)
+    AddRoundKey(state, w[Nr*Nb, (Nr+1)*Nb-1])
+    out = state
+  end
+
+  InvCipher(byte in[4*Nb], byte out[4*Nb], word w[Nb*(Nr+1)])
+  begin
+    byte state[4,Nb]
+    state = in
+    AddRoundKey(state, w[Nr*Nb, (Nr+1)*Nb-1])
+    for round = Nr-1 step -1 downto 1
+      InvShiftRows(state)
+      InvSubBytes(state)
+      AddRoundKey(state, w[round*Nb, (round+1)*Nb-1])
+      InvMixColumns(state)
+    end for
+    InvShiftRows(state)
+    InvSubBytes(state)
+    AddRoundKey(state, w[0, Nb-1])
+    out = state
+  end
+  */
+
+  // Encrypt: AddRoundKey(state, w[0, Nb-1])
+  // Decrypt: AddRoundKey(state, w[Nr*Nb, (Nr+1)*Nb-1])
+  var Nr = w.length / 4 - 1;
+  var m0, m1, m2, m3, sub;
+  if(decrypt) {
+    m0 = imix[0];
+    m1 = imix[1];
+    m2 = imix[2];
+    m3 = imix[3];
+    sub = isbox;
+  } else {
+    m0 = mix[0];
+    m1 = mix[1];
+    m2 = mix[2];
+    m3 = mix[3];
+    sub = sbox;
+  }
+  var a, b, c, d, a2, b2, c2;
+  a = input[0] ^ w[0];
+  b = input[decrypt ? 3 : 1] ^ w[1];
+  c = input[2] ^ w[2];
+  d = input[decrypt ? 1 : 3] ^ w[3];
+  var i = 3;
+
+  /* In order to share code we follow the encryption algorithm when both
+    encrypting and decrypting. To account for the changes required in the
+    decryption algorithm, we use different lookup tables when decrypting
+    and use a modified key schedule to account for the difference in the
+    order of transformations applied when performing rounds. We also get
+    key rounds in reverse order (relative to encryption). */
+  for(var round = 1; round < Nr; ++round) {
+    /* As described above, we'll be using table lookups to perform the
+      column mixing. Each column is stored as a word in the state (the
+      array 'input' has one column as a word at each index). In order to
+      mix a column, we perform these transformations on each row in c,
+      which is 1 byte in each word. The new column for c0 is c'0:
+
+               m0      m1      m2      m3
+      r0,c'0 = 2*r0,c0 + 3*r1,c0 + 1*r2,c0 + 1*r3,c0
+      r1,c'0 = 1*r0,c0 + 2*r1,c0 + 3*r2,c0 + 1*r3,c0
+      r2,c'0 = 1*r0,c0 + 1*r1,c0 + 2*r2,c0 + 3*r3,c0
+      r3,c'0 = 3*r0,c0 + 1*r1,c0 + 1*r2,c0 + 2*r3,c0
+
+      So using mix tables where c0 is a word with r0 being its upper
+      8 bits and r3 being its lower 8 bits:
+
+      m0[c0 >> 24] will yield this word: [2*r0,1*r0,1*r0,3*r0]
+      ...
+      m3[c0 & 255] will yield this word: [1*r3,1*r3,3*r3,2*r3]
+
+      Therefore to mix the columns in each word in the state we
+      do the following (& 255 omitted for brevity):
+      c'0,r0 = m0[c0 >> 24] ^ m1[c1 >> 16] ^ m2[c2 >> 8] ^ m3[c3]
+      c'0,r1 = m0[c0 >> 24] ^ m1[c1 >> 16] ^ m2[c2 >> 8] ^ m3[c3]
+      c'0,r2 = m0[c0 >> 24] ^ m1[c1 >> 16] ^ m2[c2 >> 8] ^ m3[c3]
+      c'0,r3 = m0[c0 >> 24] ^ m1[c1 >> 16] ^ m2[c2 >> 8] ^ m3[c3]
+
+      However, before mixing, the algorithm requires us to perform
+      ShiftRows(). The ShiftRows() transformation cyclically shifts the
+      last 3 rows of the state over different offsets. The first row
+      (r = 0) is not shifted.
+
+      s'_r,c = s_r,(c + shift(r, Nb) mod Nb
+      for 0 < r < 4 and 0 <= c < Nb and
+      shift(1, 4) = 1
+      shift(2, 4) = 2
+      shift(3, 4) = 3.
+
+      This causes the first byte in r = 1 to be moved to the end of
+      the row, the first 2 bytes in r = 2 to be moved to the end of
+      the row, the first 3 bytes in r = 3 to be moved to the end of
+      the row:
+
+      r1: [c0 c1 c2 c3] => [c1 c2 c3 c0]
+      r2: [c0 c1 c2 c3]    [c2 c3 c0 c1]
+      r3: [c0 c1 c2 c3]    [c3 c0 c1 c2]
+
+      We can make these substitutions inline with our column mixing to
+      generate an updated set of equations to produce each word in the
+      state (note the columns have changed positions):
+
+      c0 c1 c2 c3 => c0 c1 c2 c3
+      c0 c1 c2 c3    c1 c2 c3 c0  (cycled 1 byte)
+      c0 c1 c2 c3    c2 c3 c0 c1  (cycled 2 bytes)
+      c0 c1 c2 c3    c3 c0 c1 c2  (cycled 3 bytes)
+
+      Therefore:
+
+      c'0 = 2*r0,c0 + 3*r1,c1 + 1*r2,c2 + 1*r3,c3
+      c'0 = 1*r0,c0 + 2*r1,c1 + 3*r2,c2 + 1*r3,c3
+      c'0 = 1*r0,c0 + 1*r1,c1 + 2*r2,c2 + 3*r3,c3
+      c'0 = 3*r0,c0 + 1*r1,c1 + 1*r2,c2 + 2*r3,c3
+
+      c'1 = 2*r0,c1 + 3*r1,c2 + 1*r2,c3 + 1*r3,c0
+      c'1 = 1*r0,c1 + 2*r1,c2 + 3*r2,c3 + 1*r3,c0
+      c'1 = 1*r0,c1 + 1*r1,c2 + 2*r2,c3 + 3*r3,c0
+      c'1 = 3*r0,c1 + 1*r1,c2 + 1*r2,c3 + 2*r3,c0
+
+      ... and so forth for c'2 and c'3. The important distinction is
+      that the columns are cycling, with c0 being used with the m0
+      map when calculating c0, but c1 being used with the m0 map when
+      calculating c1 ... and so forth.
+
+      When performing the inverse we transform the mirror image and
+      skip the bottom row, instead of the top one, and move upwards:
+
+      c3 c2 c1 c0 => c0 c3 c2 c1  (cycled 3 bytes) *same as encryption
+      c3 c2 c1 c0    c1 c0 c3 c2  (cycled 2 bytes)
+      c3 c2 c1 c0    c2 c1 c0 c3  (cycled 1 byte)  *same as encryption
+      c3 c2 c1 c0    c3 c2 c1 c0
+
+      If you compare the resulting matrices for ShiftRows()+MixColumns()
+      and for InvShiftRows()+InvMixColumns() the 2nd and 4th columns are
+      different (in encrypt mode vs. decrypt mode). So in order to use
+      the same code to handle both encryption and decryption, we will
+      need to do some mapping.
+
+      If in encryption mode we let a=c0, b=c1, c=c2, d=c3, and r<N> be
+      a row number in the state, then the resulting matrix in encryption
+      mode for applying the above transformations would be:
+
+      r1: a b c d
+      r2: b c d a
+      r3: c d a b
+      r4: d a b c
+
+      If we did the same in decryption mode we would get:
+
+      r1: a d c b
+      r2: b a d c
+      r3: c b a d
+      r4: d c b a
+
+      If instead we swap d and b (set b=c3 and d=c1), then we get:
+
+      r1: a b c d
+      r2: d a b c
+      r3: c d a b
+      r4: b c d a
+
+      Now the 1st and 3rd rows are the same as the encryption matrix. All
+      we need to do then to make the mapping exactly the same is to swap
+      the 2nd and 4th rows when in decryption mode. To do this without
+      having to do it on each iteration, we swapped the 2nd and 4th rows
+      in the decryption key schedule. We also have to do the swap above
+      when we first pull in the input and when we set the final output. */
+    a2 =
+      m0[a >>> 24] ^
+      m1[b >>> 16 & 255] ^
+      m2[c >>> 8 & 255] ^
+      m3[d & 255] ^ w[++i];
+    b2 =
+      m0[b >>> 24] ^
+      m1[c >>> 16 & 255] ^
+      m2[d >>> 8 & 255] ^
+      m3[a & 255] ^ w[++i];
+    c2 =
+      m0[c >>> 24] ^
+      m1[d >>> 16 & 255] ^
+      m2[a >>> 8 & 255] ^
+      m3[b & 255] ^ w[++i];
+    d =
+      m0[d >>> 24] ^
+      m1[a >>> 16 & 255] ^
+      m2[b >>> 8 & 255] ^
+      m3[c & 255] ^ w[++i];
+    a = a2;
+    b = b2;
+    c = c2;
+  }
+
+  /*
+    Encrypt:
+    SubBytes(state)
+    ShiftRows(state)
+    AddRoundKey(state, w[Nr*Nb, (Nr+1)*Nb-1])
+
+    Decrypt:
+    InvShiftRows(state)
+    InvSubBytes(state)
+    AddRoundKey(state, w[0, Nb-1])
+   */
+   // Note: rows are shifted inline
+  output[0] =
+    (sub[a >>> 24] << 24) ^
+    (sub[b >>> 16 & 255] << 16) ^
+    (sub[c >>> 8 & 255] << 8) ^
+    (sub[d & 255]) ^ w[++i];
+  output[decrypt ? 3 : 1] =
+    (sub[b >>> 24] << 24) ^
+    (sub[c >>> 16 & 255] << 16) ^
+    (sub[d >>> 8 & 255] << 8) ^
+    (sub[a & 255]) ^ w[++i];
+  output[2] =
+    (sub[c >>> 24] << 24) ^
+    (sub[d >>> 16 & 255] << 16) ^
+    (sub[a >>> 8 & 255] << 8) ^
+    (sub[b & 255]) ^ w[++i];
+  output[decrypt ? 1 : 3] =
+    (sub[d >>> 24] << 24) ^
+    (sub[a >>> 16 & 255] << 16) ^
+    (sub[b >>> 8 & 255] << 8) ^
+    (sub[c & 255]) ^ w[++i];
+}
+
+/**
+ * Deprecated. Instead, use:
+ *
+ * forge.cipher.createCipher('AES-<mode>', key);
+ * forge.cipher.createDecipher('AES-<mode>', key);
+ *
+ * Creates a deprecated AES cipher object. This object's mode will default to
+ * CBC (cipher-block-chaining).
+ *
+ * The key and iv may be given as a string of bytes, an array of bytes, a
+ * byte buffer, or an array of 32-bit words.
+ *
+ * @param options the options to use.
+ *          key the symmetric key to use.
+ *          output the buffer to write to.
+ *          decrypt true for decryption, false for encryption.
+ *          mode the cipher mode to use (default: 'CBC').
+ *
+ * @return the cipher.
+ */
+function _createCipher(options) {
+  options = options || {};
+  var mode = (options.mode || 'CBC').toUpperCase();
+  var algorithm = 'AES-' + mode;
+
+  var cipher;
+  if(options.decrypt) {
+    cipher = forge.cipher.createDecipher(algorithm, options.key);
+  } else {
+    cipher = forge.cipher.createCipher(algorithm, options.key);
+  }
+
+  // backwards compatible start API
+  var start = cipher.start;
+  cipher.start = function(iv, options) {
+    // backwards compatibility: support second arg as output buffer
+    var output = null;
+    if(options instanceof forge.util.ByteBuffer) {
+      output = options;
+      options = {};
+    }
+    options = options || {};
+    options.output = output;
+    options.iv = iv;
+    start.call(cipher, options);
+  };
+
+  return cipher;
+}
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'aes';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(
+  ['require', 'module', './cipher', './cipherModes', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/aesCipherSuites.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/aesCipherSuites.js
new file mode 100644
index 0000000..b0b4b4f
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/aesCipherSuites.js
@@ -0,0 +1,312 @@
+/**
+ * A Javascript implementation of AES Cipher Suites for TLS.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2009-2014 Digital Bazaar, Inc.
+ *
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+var tls = forge.tls;
+
+/**
+ * Supported cipher suites.
+ */
+tls.CipherSuites['TLS_RSA_WITH_AES_128_CBC_SHA'] = {
+  id: [0x00,0x2f],
+  name: 'TLS_RSA_WITH_AES_128_CBC_SHA',
+  initSecurityParameters: function(sp) {
+    sp.bulk_cipher_algorithm = tls.BulkCipherAlgorithm.aes;
+    sp.cipher_type = tls.CipherType.block;
+    sp.enc_key_length = 16;
+    sp.block_length = 16;
+    sp.fixed_iv_length = 16;
+    sp.record_iv_length = 16;
+    sp.mac_algorithm = tls.MACAlgorithm.hmac_sha1;
+    sp.mac_length = 20;
+    sp.mac_key_length = 20;
+  },
+  initConnectionState: initConnectionState
+};
+tls.CipherSuites['TLS_RSA_WITH_AES_256_CBC_SHA'] = {
+  id: [0x00,0x35],
+  name: 'TLS_RSA_WITH_AES_256_CBC_SHA',
+  initSecurityParameters: function(sp) {
+    sp.bulk_cipher_algorithm = tls.BulkCipherAlgorithm.aes;
+    sp.cipher_type = tls.CipherType.block;
+    sp.enc_key_length = 32;
+    sp.block_length = 16;
+    sp.fixed_iv_length = 16;
+    sp.record_iv_length = 16;
+    sp.mac_algorithm = tls.MACAlgorithm.hmac_sha1;
+    sp.mac_length = 20;
+    sp.mac_key_length = 20;
+  },
+  initConnectionState: initConnectionState
+};
+
+function initConnectionState(state, c, sp) {
+  var client = (c.entity === forge.tls.ConnectionEnd.client);
+
+  // cipher setup
+  state.read.cipherState = {
+    init: false,
+    cipher: forge.cipher.createDecipher('AES-CBC', client ?
+      sp.keys.server_write_key : sp.keys.client_write_key),
+    iv: client ? sp.keys.server_write_IV : sp.keys.client_write_IV
+  };
+  state.write.cipherState = {
+    init: false,
+    cipher: forge.cipher.createCipher('AES-CBC', client ?
+      sp.keys.client_write_key : sp.keys.server_write_key),
+    iv: client ? sp.keys.client_write_IV : sp.keys.server_write_IV
+  };
+  state.read.cipherFunction = decrypt_aes_cbc_sha1;
+  state.write.cipherFunction = encrypt_aes_cbc_sha1;
+
+  // MAC setup
+  state.read.macLength = state.write.macLength = sp.mac_length;
+  state.read.macFunction = state.write.macFunction = tls.hmac_sha1;
+}
+
+/**
+ * Encrypts the TLSCompressed record into a TLSCipherText record using AES
+ * in CBC mode.
+ *
+ * @param record the TLSCompressed record to encrypt.
+ * @param s the ConnectionState to use.
+ *
+ * @return true on success, false on failure.
+ */
+function encrypt_aes_cbc_sha1(record, s) {
+  var rval = false;
+
+  // append MAC to fragment, update sequence number
+  var mac = s.macFunction(s.macKey, s.sequenceNumber, record);
+  record.fragment.putBytes(mac);
+  s.updateSequenceNumber();
+
+  // TLS 1.1+ use an explicit IV every time to protect against CBC attacks
+  var iv;
+  if(record.version.minor === tls.Versions.TLS_1_0.minor) {
+    // use the pre-generated IV when initializing for TLS 1.0, otherwise use
+    // the residue from the previous encryption
+    iv = s.cipherState.init ? null : s.cipherState.iv;
+  } else {
+    iv = forge.random.getBytesSync(16);
+  }
+
+  s.cipherState.init = true;
+
+  // start cipher
+  var cipher = s.cipherState.cipher;
+  cipher.start({iv: iv});
+
+  // TLS 1.1+ write IV into output
+  if(record.version.minor >= tls.Versions.TLS_1_1.minor) {
+    cipher.output.putBytes(iv);
+  }
+
+  // do encryption (default padding is appropriate)
+  cipher.update(record.fragment);
+  if(cipher.finish(encrypt_aes_cbc_sha1_padding)) {
+    // set record fragment to encrypted output
+    record.fragment = cipher.output;
+    record.length = record.fragment.length();
+    rval = true;
+  }
+
+  return rval;
+}
+
+/**
+ * Handles padding for aes_cbc_sha1 in encrypt mode.
+ *
+ * @param blockSize the block size.
+ * @param input the input buffer.
+ * @param decrypt true in decrypt mode, false in encrypt mode.
+ *
+ * @return true on success, false on failure.
+ */
+function encrypt_aes_cbc_sha1_padding(blockSize, input, decrypt) {
+  /* The encrypted data length (TLSCiphertext.length) is one more than the sum
+   of SecurityParameters.block_length, TLSCompressed.length,
+   SecurityParameters.mac_length, and padding_length.
+
+   The padding may be any length up to 255 bytes long, as long as it results in
+   the TLSCiphertext.length being an integral multiple of the block length.
+   Lengths longer than necessary might be desirable to frustrate attacks on a
+   protocol based on analysis of the lengths of exchanged messages. Each uint8
+   in the padding data vector must be filled with the padding length value.
+
+   The padding length should be such that the total size of the
+   GenericBlockCipher structure is a multiple of the cipher's block length.
+   Legal values range from zero to 255, inclusive. This length specifies the
+   length of the padding field exclusive of the padding_length field itself.
+
+   This is slightly different from PKCS#7 because the padding value is 1
+   less than the actual number of padding bytes if you include the
+   padding_length uint8 itself as a padding byte. */
+  if(!decrypt) {
+    // get the number of padding bytes required to reach the blockSize and
+    // subtract 1 for the padding value (to make room for the padding_length
+    // uint8)
+    var padding = blockSize - (input.length() % blockSize);
+    input.fillWithByte(padding - 1, padding);
+  }
+  return true;
+}
+
+/**
+ * Handles padding for aes_cbc_sha1 in decrypt mode.
+ *
+ * @param blockSize the block size.
+ * @param output the output buffer.
+ * @param decrypt true in decrypt mode, false in encrypt mode.
+ *
+ * @return true on success, false on failure.
+ */
+function decrypt_aes_cbc_sha1_padding(blockSize, output, decrypt) {
+  var rval = true;
+  if(decrypt) {
+    /* The last byte in the output specifies the number of padding bytes not
+      including itself. Each of the padding bytes has the same value as that
+      last byte (known as the padding_length). Here we check all padding
+      bytes to ensure they have the value of padding_length even if one of
+      them is bad in order to ward-off timing attacks. */
+    var len = output.length();
+    var paddingLength = output.last();
+    for(var i = len - 1 - paddingLength; i < len - 1; ++i) {
+      rval = rval && (output.at(i) == paddingLength);
+    }
+    if(rval) {
+      // trim off padding bytes and last padding length byte
+      output.truncate(paddingLength + 1);
+    }
+  }
+  return rval;
+}
+
+/**
+ * Decrypts a TLSCipherText record into a TLSCompressed record using
+ * AES in CBC mode.
+ *
+ * @param record the TLSCipherText record to decrypt.
+ * @param s the ConnectionState to use.
+ *
+ * @return true on success, false on failure.
+ */
+var count = 0;
+function decrypt_aes_cbc_sha1(record, s) {
+  var rval = false;
+  ++count;
+
+  var iv;
+  if(record.version.minor === tls.Versions.TLS_1_0.minor) {
+    // use pre-generated IV when initializing for TLS 1.0, otherwise use the
+    // residue from the previous decryption
+    iv = s.cipherState.init ? null : s.cipherState.iv;
+  } else {
+    // TLS 1.1+ use an explicit IV every time to protect against CBC attacks
+    // that is appended to the record fragment
+    iv = record.fragment.getBytes(16);
+  }
+
+  s.cipherState.init = true;
+
+  // start cipher
+  var cipher = s.cipherState.cipher;
+  cipher.start({iv: iv});
+
+  // do decryption
+  cipher.update(record.fragment);
+  rval = cipher.finish(decrypt_aes_cbc_sha1_padding);
+
+  // even if decryption fails, keep going to minimize timing attacks
+
+  // decrypted data:
+  // first (len - 20) bytes = application data
+  // last 20 bytes          = MAC
+  var macLen = s.macLength;
+
+  // create a zero'd out mac
+  var mac = '';
+  for(var i = 0; i < macLen; ++i) {
+    mac += String.fromCharCode(0);
+  }
+
+  // get fragment and mac
+  var len = cipher.output.length();
+  if(len >= macLen) {
+    record.fragment = cipher.output.getBytes(len - macLen);
+    mac = cipher.output.getBytes(macLen);
+  } else {
+    // bad data, but get bytes anyway to try to keep timing consistent
+    record.fragment = cipher.output.getBytes();
+  }
+  record.fragment = forge.util.createBuffer(record.fragment);
+  record.length = record.fragment.length();
+
+  // see if data integrity checks out, update sequence number
+  var mac2 = s.macFunction(s.macKey, s.sequenceNumber, record);
+  s.updateSequenceNumber();
+  rval = (mac2 === mac) && rval;
+  return rval;
+}
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'aesCipherSuites';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './aes', './tls'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/asn1.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/asn1.js
new file mode 100644
index 0000000..9ac7df4
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/asn1.js
@@ -0,0 +1,1114 @@
+/**
+ * Javascript implementation of Abstract Syntax Notation Number One.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2014 Digital Bazaar, Inc.
+ *
+ * An API for storing data using the Abstract Syntax Notation Number One
+ * format using DER (Distinguished Encoding Rules) encoding. This encoding is
+ * commonly used to store data for PKI, i.e. X.509 Certificates, and this
+ * implementation exists for that purpose.
+ *
+ * Abstract Syntax Notation Number One (ASN.1) is used to define the abstract
+ * syntax of information without restricting the way the information is encoded
+ * for transmission. It provides a standard that allows for open systems
+ * communication. ASN.1 defines the syntax of information data and a number of
+ * simple data types as well as a notation for describing them and specifying
+ * values for them.
+ *
+ * The RSA algorithm creates public and private keys that are often stored in
+ * X.509 or PKCS#X formats -- which use ASN.1 (encoded in DER format). This
+ * class provides the most basic functionality required to store and load DSA
+ * keys that are encoded according to ASN.1.
+ *
+ * The most common binary encodings for ASN.1 are BER (Basic Encoding Rules)
+ * and DER (Distinguished Encoding Rules). DER is just a subset of BER that
+ * has stricter requirements for how data must be encoded.
+ *
+ * Each ASN.1 structure has a tag (a byte identifying the ASN.1 structure type)
+ * and a byte array for the value of this ASN1 structure which may be data or a
+ * list of ASN.1 structures.
+ *
+ * Each ASN.1 structure using BER is (Tag-Length-Value):
+ *
+ * | byte 0 | bytes X | bytes Y |
+ * |--------|---------|----------
+ * |  tag   | length  |  value  |
+ *
+ * ASN.1 allows for tags to be of "High-tag-number form" which allows a tag to
+ * be two or more octets, but that is not supported by this class. A tag is
+ * only 1 byte. Bits 1-5 give the tag number (ie the data type within a
+ * particular 'class'), 6 indicates whether or not the ASN.1 value is
+ * constructed from other ASN.1 values, and bits 7 and 8 give the 'class'. If
+ * bits 7 and 8 are both zero, the class is UNIVERSAL. If only bit 7 is set,
+ * then the class is APPLICATION. If only bit 8 is set, then the class is
+ * CONTEXT_SPECIFIC. If both bits 7 and 8 are set, then the class is PRIVATE.
+ * The tag numbers for the data types for the class UNIVERSAL are listed below:
+ *
+ * UNIVERSAL 0 Reserved for use by the encoding rules
+ * UNIVERSAL 1 Boolean type
+ * UNIVERSAL 2 Integer type
+ * UNIVERSAL 3 Bitstring type
+ * UNIVERSAL 4 Octetstring type
+ * UNIVERSAL 5 Null type
+ * UNIVERSAL 6 Object identifier type
+ * UNIVERSAL 7 Object descriptor type
+ * UNIVERSAL 8 External type and Instance-of type
+ * UNIVERSAL 9 Real type
+ * UNIVERSAL 10 Enumerated type
+ * UNIVERSAL 11 Embedded-pdv type
+ * UNIVERSAL 12 UTF8String type
+ * UNIVERSAL 13 Relative object identifier type
+ * UNIVERSAL 14-15 Reserved for future editions
+ * UNIVERSAL 16 Sequence and Sequence-of types
+ * UNIVERSAL 17 Set and Set-of types
+ * UNIVERSAL 18-22, 25-30 Character string types
+ * UNIVERSAL 23-24 Time types
+ *
+ * The length of an ASN.1 structure is specified after the tag identifier.
+ * There is a definite form and an indefinite form. The indefinite form may
+ * be used if the encoding is constructed and not all immediately available.
+ * The indefinite form is encoded using a length byte with only the 8th bit
+ * set. The end of the constructed object is marked using end-of-contents
+ * octets (two zero bytes).
+ *
+ * The definite form looks like this:
+ *
+ * The length may take up 1 or more bytes, it depends on the length of the
+ * value of the ASN.1 structure. DER encoding requires that if the ASN.1
+ * structure has a value that has a length greater than 127, more than 1 byte
+ * will be used to store its length, otherwise just one byte will be used.
+ * This is strict.
+ *
+ * In the case that the length of the ASN.1 value is less than 127, 1 octet
+ * (byte) is used to store the "short form" length. The 8th bit has a value of
+ * 0 indicating the length is "short form" and not "long form" and bits 7-1
+ * give the length of the data. (The 8th bit is the left-most, most significant
+ * bit: also known as big endian or network format).
+ *
+ * In the case that the length of the ASN.1 value is greater than 127, 2 to
+ * 127 octets (bytes) are used to store the "long form" length. The first
+ * byte's 8th bit is set to 1 to indicate the length is "long form." Bits 7-1
+ * give the number of additional octets. All following octets are in base 256
+ * with the most significant digit first (typical big-endian binary unsigned
+ * integer storage). So, for instance, if the length of a value was 257, the
+ * first byte would be set to:
+ *
+ * 10000010 = 130 = 0x82.
+ *
+ * This indicates there are 2 octets (base 256) for the length. The second and
+ * third bytes (the octets just mentioned) would store the length in base 256:
+ *
+ * octet 2: 00000001 = 1 * 256^1 = 256
+ * octet 3: 00000001 = 1 * 256^0 = 1
+ * total = 257
+ *
+ * The algorithm for converting a js integer value of 257 to base-256 is:
+ *
+ * var value = 257;
+ * var bytes = [];
+ * bytes[0] = (value >>> 8) & 0xFF; // most significant byte first
+ * bytes[1] = value & 0xFF;        // least significant byte last
+ *
+ * On the ASN.1 UNIVERSAL Object Identifier (OID) type:
+ *
+ * An OID can be written like: "value1.value2.value3...valueN"
+ *
+ * The DER encoding rules:
+ *
+ * The first byte has the value 40 * value1 + value2.
+ * The following bytes, if any, encode the remaining values. Each value is
+ * encoded in base 128, most significant digit first (big endian), with as
+ * few digits as possible, and the most significant bit of each byte set
+ * to 1 except the last in each value's encoding. For example: Given the
+ * OID "1.2.840.113549", its DER encoding is (remember each byte except the
+ * last one in each encoding is OR'd with 0x80):
+ *
+ * byte 1: 40 * 1 + 2 = 42 = 0x2A.
+ * bytes 2-3: 128 * 6 + 72 = 840 = 6 72 = 6 72 = 0x0648 = 0x8648
+ * bytes 4-6: 16384 * 6 + 128 * 119 + 13 = 6 119 13 = 0x06770D = 0x86F70D
+ *
+ * The final value is: 0x2A864886F70D.
+ * The full OID (including ASN.1 tag and length of 6 bytes) is:
+ * 0x06062A864886F70D
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+/* ASN.1 API */
+var asn1 = forge.asn1 = forge.asn1 || {};
+
+/**
+ * ASN.1 classes.
+ */
+asn1.Class = {
+  UNIVERSAL:        0x00,
+  APPLICATION:      0x40,
+  CONTEXT_SPECIFIC: 0x80,
+  PRIVATE:          0xC0
+};
+
+/**
+ * ASN.1 types. Not all types are supported by this implementation, only
+ * those necessary to implement a simple PKI are implemented.
+ */
+asn1.Type = {
+  NONE:             0,
+  BOOLEAN:          1,
+  INTEGER:          2,
+  BITSTRING:        3,
+  OCTETSTRING:      4,
+  NULL:             5,
+  OID:              6,
+  ODESC:            7,
+  EXTERNAL:         8,
+  REAL:             9,
+  ENUMERATED:      10,
+  EMBEDDED:        11,
+  UTF8:            12,
+  ROID:            13,
+  SEQUENCE:        16,
+  SET:             17,
+  PRINTABLESTRING: 19,
+  IA5STRING:       22,
+  UTCTIME:         23,
+  GENERALIZEDTIME: 24,
+  BMPSTRING:       30
+};
+
+/**
+ * Creates a new asn1 object.
+ *
+ * @param tagClass the tag class for the object.
+ * @param type the data type (tag number) for the object.
+ * @param constructed true if the asn1 object is in constructed form.
+ * @param value the value for the object, if it is not constructed.
+ *
+ * @return the asn1 object.
+ */
+asn1.create = function(tagClass, type, constructed, value) {
+  /* An asn1 object has a tagClass, a type, a constructed flag, and a
+    value. The value's type depends on the constructed flag. If
+    constructed, it will contain a list of other asn1 objects. If not,
+    it will contain the ASN.1 value as an array of bytes formatted
+    according to the ASN.1 data type. */
+
+  // remove undefined values
+  if(forge.util.isArray(value)) {
+    var tmp = [];
+    for(var i = 0; i < value.length; ++i) {
+      if(value[i] !== undefined) {
+        tmp.push(value[i]);
+      }
+    }
+    value = tmp;
+  }
+
+  return {
+    tagClass: tagClass,
+    type: type,
+    constructed: constructed,
+    composed: constructed || forge.util.isArray(value),
+    value: value
+  };
+};
+
+/**
+ * Gets the length of an ASN.1 value.
+ *
+ * In case the length is not specified, undefined is returned.
+ *
+ * @param b the ASN.1 byte buffer.
+ *
+ * @return the length of the ASN.1 value.
+ */
+var _getValueLength = function(b) {
+  var b2 = b.getByte();
+  if(b2 === 0x80) {
+    return undefined;
+  }
+
+  // see if the length is "short form" or "long form" (bit 8 set)
+  var length;
+  var longForm = b2 & 0x80;
+  if(!longForm) {
+    // length is just the first byte
+    length = b2;
+  } else {
+    // the number of bytes the length is specified in bits 7 through 1
+    // and each length byte is in big-endian base-256
+    length = b.getInt((b2 & 0x7F) << 3);
+  }
+  return length;
+};
+
+/**
+ * Parses an asn1 object from a byte buffer in DER format.
+ *
+ * @param bytes the byte buffer to parse from.
+ * @param strict true to be strict when checking value lengths, false to
+ *          allow truncated values (default: true).
+ *
+ * @return the parsed asn1 object.
+ */
+asn1.fromDer = function(bytes, strict) {
+  if(strict === undefined) {
+    strict = true;
+  }
+
+  // wrap in buffer if needed
+  if(typeof bytes === 'string') {
+    bytes = forge.util.createBuffer(bytes);
+  }
+
+  // minimum length for ASN.1 DER structure is 2
+  if(bytes.length() < 2) {
+    var error = new Error('Too few bytes to parse DER.');
+    error.bytes = bytes.length();
+    throw error;
+  }
+
+  // get the first byte
+  var b1 = bytes.getByte();
+
+  // get the tag class
+  var tagClass = (b1 & 0xC0);
+
+  // get the type (bits 1-5)
+  var type = b1 & 0x1F;
+
+  // get the value length
+  var length = _getValueLength(bytes);
+
+  // ensure there are enough bytes to get the value
+  if(bytes.length() < length) {
+    if(strict) {
+      var error = new Error('Too few bytes to read ASN.1 value.');
+      error.detail = bytes.length() + ' < ' + length;
+      throw error;
+    }
+    // Note: be lenient with truncated values
+    length = bytes.length();
+  }
+
+  // prepare to get value
+  var value;
+
+  // constructed flag is bit 6 (32 = 0x20) of the first byte
+  var constructed = ((b1 & 0x20) === 0x20);
+
+  // determine if the value is composed of other ASN.1 objects (if its
+  // constructed it will be and if its a BITSTRING it may be)
+  var composed = constructed;
+  if(!composed && tagClass === asn1.Class.UNIVERSAL &&
+    type === asn1.Type.BITSTRING && length > 1) {
+    /* The first octet gives the number of bits by which the length of the
+      bit string is less than the next multiple of eight (this is called
+      the "number of unused bits").
+
+      The second and following octets give the value of the bit string
+      converted to an octet string. */
+    // if there are no unused bits, maybe the bitstring holds ASN.1 objs
+    var read = bytes.read;
+    var unused = bytes.getByte();
+    if(unused === 0) {
+      // if the first byte indicates UNIVERSAL or CONTEXT_SPECIFIC,
+      // and the length is valid, assume we've got an ASN.1 object
+      b1 = bytes.getByte();
+      var tc = (b1 & 0xC0);
+      if(tc === asn1.Class.UNIVERSAL || tc === asn1.Class.CONTEXT_SPECIFIC) {
+        try {
+          var len = _getValueLength(bytes);
+          composed = (len === length - (bytes.read - read));
+          if(composed) {
+            // adjust read/length to account for unused bits byte
+            ++read;
+            --length;
+          }
+        } catch(ex) {}
+      }
+    }
+    // restore read pointer
+    bytes.read = read;
+  }
+
+  if(composed) {
+    // parse child asn1 objects from the value
+    value = [];
+    if(length === undefined) {
+      // asn1 object of indefinite length, read until end tag
+      for(;;) {
+        if(bytes.bytes(2) === String.fromCharCode(0, 0)) {
+          bytes.getBytes(2);
+          break;
+        }
+        value.push(asn1.fromDer(bytes, strict));
+      }
+    } else {
+      // parsing asn1 object of definite length
+      var start = bytes.length();
+      while(length > 0) {
+        value.push(asn1.fromDer(bytes, strict));
+        length -= start - bytes.length();
+        start = bytes.length();
+      }
+    }
+  } else {
+    // asn1 not composed, get raw value
+    // TODO: do DER to OID conversion and vice-versa in .toDer?
+
+    if(length === undefined) {
+      if(strict) {
+        throw new Error('Non-constructed ASN.1 object of indefinite length.');
+      }
+      // be lenient and use remaining bytes
+      length = bytes.length();
+    }
+
+    if(type === asn1.Type.BMPSTRING) {
+      value = '';
+      for(var i = 0; i < length; i += 2) {
+        value += String.fromCharCode(bytes.getInt16());
+      }
+    } else {
+      value = bytes.getBytes(length);
+    }
+  }
+
+  // create and return asn1 object
+  return asn1.create(tagClass, type, constructed, value);
+};
+
+/**
+ * Converts the given asn1 object to a buffer of bytes in DER format.
+ *
+ * @param asn1 the asn1 object to convert to bytes.
+ *
+ * @return the buffer of bytes.
+ */
+asn1.toDer = function(obj) {
+  var bytes = forge.util.createBuffer();
+
+  // build the first byte
+  var b1 = obj.tagClass | obj.type;
+
+  // for storing the ASN.1 value
+  var value = forge.util.createBuffer();
+
+  // if composed, use each child asn1 object's DER bytes as value
+  if(obj.composed) {
+    // turn on 6th bit (0x20 = 32) to indicate asn1 is constructed
+    // from other asn1 objects
+    if(obj.constructed) {
+      b1 |= 0x20;
+    } else {
+      // type is a bit string, add unused bits of 0x00
+      value.putByte(0x00);
+    }
+
+    // add all of the child DER bytes together
+    for(var i = 0; i < obj.value.length; ++i) {
+      if(obj.value[i] !== undefined) {
+        value.putBuffer(asn1.toDer(obj.value[i]));
+      }
+    }
+  } else {
+    // use asn1.value directly
+    if(obj.type === asn1.Type.BMPSTRING) {
+      for(var i = 0; i < obj.value.length; ++i) {
+        value.putInt16(obj.value.charCodeAt(i));
+      }
+    } else {
+      value.putBytes(obj.value);
+    }
+  }
+
+  // add tag byte
+  bytes.putByte(b1);
+
+  // use "short form" encoding
+  if(value.length() <= 127) {
+    // one byte describes the length
+    // bit 8 = 0 and bits 7-1 = length
+    bytes.putByte(value.length() & 0x7F);
+  } else {
+    // use "long form" encoding
+    // 2 to 127 bytes describe the length
+    // first byte: bit 8 = 1 and bits 7-1 = # of additional bytes
+    // other bytes: length in base 256, big-endian
+    var len = value.length();
+    var lenBytes = '';
+    do {
+      lenBytes += String.fromCharCode(len & 0xFF);
+      len = len >>> 8;
+    } while(len > 0);
+
+    // set first byte to # bytes used to store the length and turn on
+    // bit 8 to indicate long-form length is used
+    bytes.putByte(lenBytes.length | 0x80);
+
+    // concatenate length bytes in reverse since they were generated
+    // little endian and we need big endian
+    for(var i = lenBytes.length - 1; i >= 0; --i) {
+      bytes.putByte(lenBytes.charCodeAt(i));
+    }
+  }
+
+  // concatenate value bytes
+  bytes.putBuffer(value);
+  return bytes;
+};
+
+/**
+ * Converts an OID dot-separated string to a byte buffer. The byte buffer
+ * contains only the DER-encoded value, not any tag or length bytes.
+ *
+ * @param oid the OID dot-separated string.
+ *
+ * @return the byte buffer.
+ */
+asn1.oidToDer = function(oid) {
+  // split OID into individual values
+  var values = oid.split('.');
+  var bytes = forge.util.createBuffer();
+
+  // first byte is 40 * value1 + value2
+  bytes.putByte(40 * parseInt(values[0], 10) + parseInt(values[1], 10));
+  // other bytes are each value in base 128 with 8th bit set except for
+  // the last byte for each value
+  var last, valueBytes, value, b;
+  for(var i = 2; i < values.length; ++i) {
+    // produce value bytes in reverse because we don't know how many
+    // bytes it will take to store the value
+    last = true;
+    valueBytes = [];
+    value = parseInt(values[i], 10);
+    do {
+      b = value & 0x7F;
+      value = value >>> 7;
+      // if value is not last, then turn on 8th bit
+      if(!last) {
+        b |= 0x80;
+      }
+      valueBytes.push(b);
+      last = false;
+    } while(value > 0);
+
+    // add value bytes in reverse (needs to be in big endian)
+    for(var n = valueBytes.length - 1; n >= 0; --n) {
+      bytes.putByte(valueBytes[n]);
+    }
+  }
+
+  return bytes;
+};
+
+/**
+ * Converts a DER-encoded byte buffer to an OID dot-separated string. The
+ * byte buffer should contain only the DER-encoded value, not any tag or
+ * length bytes.
+ *
+ * @param bytes the byte buffer.
+ *
+ * @return the OID dot-separated string.
+ */
+asn1.derToOid = function(bytes) {
+  var oid;
+
+  // wrap in buffer if needed
+  if(typeof bytes === 'string') {
+    bytes = forge.util.createBuffer(bytes);
+  }
+
+  // first byte is 40 * value1 + value2
+  var b = bytes.getByte();
+  oid = Math.floor(b / 40) + '.' + (b % 40);
+
+  // other bytes are each value in base 128 with 8th bit set except for
+  // the last byte for each value
+  var value = 0;
+  while(bytes.length() > 0) {
+    b = bytes.getByte();
+    value = value << 7;
+    // not the last byte for the value
+    if(b & 0x80) {
+      value += b & 0x7F;
+    } else {
+      // last byte
+      oid += '.' + (value + b);
+      value = 0;
+    }
+  }
+
+  return oid;
+};
+
+/**
+ * Converts a UTCTime value to a date.
+ *
+ * Note: GeneralizedTime has 4 digits for the year and is used for X.509
+ * dates passed 2049. Parsing that structure hasn't been implemented yet.
+ *
+ * @param utc the UTCTime value to convert.
+ *
+ * @return the date.
+ */
+asn1.utcTimeToDate = function(utc) {
+  /* The following formats can be used:
+
+    YYMMDDhhmmZ
+    YYMMDDhhmm+hh'mm'
+    YYMMDDhhmm-hh'mm'
+    YYMMDDhhmmssZ
+    YYMMDDhhmmss+hh'mm'
+    YYMMDDhhmmss-hh'mm'
+
+    Where:
+
+    YY is the least significant two digits of the year
+    MM is the month (01 to 12)
+    DD is the day (01 to 31)
+    hh is the hour (00 to 23)
+    mm are the minutes (00 to 59)
+    ss are the seconds (00 to 59)
+    Z indicates that local time is GMT, + indicates that local time is
+    later than GMT, and - indicates that local time is earlier than GMT
+    hh' is the absolute value of the offset from GMT in hours
+    mm' is the absolute value of the offset from GMT in minutes */
+  var date = new Date();
+
+  // if YY >= 50 use 19xx, if YY < 50 use 20xx
+  var year = parseInt(utc.substr(0, 2), 10);
+  year = (year >= 50) ? 1900 + year : 2000 + year;
+  var MM = parseInt(utc.substr(2, 2), 10) - 1; // use 0-11 for month
+  var DD = parseInt(utc.substr(4, 2), 10);
+  var hh = parseInt(utc.substr(6, 2), 10);
+  var mm = parseInt(utc.substr(8, 2), 10);
+  var ss = 0;
+
+  // not just YYMMDDhhmmZ
+  if(utc.length > 11) {
+    // get character after minutes
+    var c = utc.charAt(10);
+    var end = 10;
+
+    // see if seconds are present
+    if(c !== '+' && c !== '-') {
+      // get seconds
+      ss = parseInt(utc.substr(10, 2), 10);
+      end += 2;
+    }
+  }
+
+  // update date
+  date.setUTCFullYear(year, MM, DD);
+  date.setUTCHours(hh, mm, ss, 0);
+
+  if(end) {
+    // get +/- after end of time
+    c = utc.charAt(end);
+    if(c === '+' || c === '-') {
+      // get hours+minutes offset
+      var hhoffset = parseInt(utc.substr(end + 1, 2), 10);
+      var mmoffset = parseInt(utc.substr(end + 4, 2), 10);
+
+      // calculate offset in milliseconds
+      var offset = hhoffset * 60 + mmoffset;
+      offset *= 60000;
+
+      // apply offset
+      if(c === '+') {
+        date.setTime(+date - offset);
+      } else {
+        date.setTime(+date + offset);
+      }
+    }
+  }
+
+  return date;
+};
+
+/**
+ * Converts a GeneralizedTime value to a date.
+ *
+ * @param gentime the GeneralizedTime value to convert.
+ *
+ * @return the date.
+ */
+asn1.generalizedTimeToDate = function(gentime) {
+  /* The following formats can be used:
+
+    YYYYMMDDHHMMSS
+    YYYYMMDDHHMMSS.fff
+    YYYYMMDDHHMMSSZ
+    YYYYMMDDHHMMSS.fffZ
+    YYYYMMDDHHMMSS+hh'mm'
+    YYYYMMDDHHMMSS.fff+hh'mm'
+    YYYYMMDDHHMMSS-hh'mm'
+    YYYYMMDDHHMMSS.fff-hh'mm'
+
+    Where:
+
+    YYYY is the year
+    MM is the month (01 to 12)
+    DD is the day (01 to 31)
+    hh is the hour (00 to 23)
+    mm are the minutes (00 to 59)
+    ss are the seconds (00 to 59)
+    .fff is the second fraction, accurate to three decimal places
+    Z indicates that local time is GMT, + indicates that local time is
+    later than GMT, and - indicates that local time is earlier than GMT
+    hh' is the absolute value of the offset from GMT in hours
+    mm' is the absolute value of the offset from GMT in minutes */
+  var date = new Date();
+
+  var YYYY = parseInt(gentime.substr(0, 4), 10);
+  var MM = parseInt(gentime.substr(4, 2), 10) - 1; // use 0-11 for month
+  var DD = parseInt(gentime.substr(6, 2), 10);
+  var hh = parseInt(gentime.substr(8, 2), 10);
+  var mm = parseInt(gentime.substr(10, 2), 10);
+  var ss = parseInt(gentime.substr(12, 2), 10);
+  var fff = 0;
+  var offset = 0;
+  var isUTC = false;
+
+  if(gentime.charAt(gentime.length - 1) === 'Z') {
+    isUTC = true;
+  }
+
+  var end = gentime.length - 5, c = gentime.charAt(end);
+  if(c === '+' || c === '-') {
+    // get hours+minutes offset
+    var hhoffset = parseInt(gentime.substr(end + 1, 2), 10);
+    var mmoffset = parseInt(gentime.substr(end + 4, 2), 10);
+
+    // calculate offset in milliseconds
+    offset = hhoffset * 60 + mmoffset;
+    offset *= 60000;
+
+    // apply offset
+    if(c === '+') {
+      offset *= -1;
+    }
+
+    isUTC = true;
+  }
+
+  // check for second fraction
+  if(gentime.charAt(14) === '.') {
+    fff = parseFloat(gentime.substr(14), 10) * 1000;
+  }
+
+  if(isUTC) {
+    date.setUTCFullYear(YYYY, MM, DD);
+    date.setUTCHours(hh, mm, ss, fff);
+
+    // apply offset
+    date.setTime(+date + offset);
+  } else {
+    date.setFullYear(YYYY, MM, DD);
+    date.setHours(hh, mm, ss, fff);
+  }
+
+  return date;
+};
+
+
+/**
+ * Converts a date to a UTCTime value.
+ *
+ * Note: GeneralizedTime has 4 digits for the year and is used for X.509
+ * dates passed 2049. Converting to a GeneralizedTime hasn't been
+ * implemented yet.
+ *
+ * @param date the date to convert.
+ *
+ * @return the UTCTime value.
+ */
+asn1.dateToUtcTime = function(date) {
+  var rval = '';
+
+  // create format YYMMDDhhmmssZ
+  var format = [];
+  format.push(('' + date.getUTCFullYear()).substr(2));
+  format.push('' + (date.getUTCMonth() + 1));
+  format.push('' + date.getUTCDate());
+  format.push('' + date.getUTCHours());
+  format.push('' + date.getUTCMinutes());
+  format.push('' + date.getUTCSeconds());
+
+  // ensure 2 digits are used for each format entry
+  for(var i = 0; i < format.length; ++i) {
+    if(format[i].length < 2) {
+      rval += '0';
+    }
+    rval += format[i];
+  }
+  rval += 'Z';
+
+  return rval;
+};
+
+/**
+ * Converts a javascript integer to a DER-encoded byte buffer to be used
+ * as the value for an INTEGER type.
+ *
+ * @param x the integer.
+ *
+ * @return the byte buffer.
+ */
+asn1.integerToDer = function(x) {
+  var rval = forge.util.createBuffer();
+  if(x >= -0x80 && x < 0x80) {
+    return rval.putSignedInt(x, 8);
+  }
+  if(x >= -0x8000 && x < 0x8000) {
+    return rval.putSignedInt(x, 16);
+  }
+  if(x >= -0x800000 && x < 0x800000) {
+    return rval.putSignedInt(x, 24);
+  }
+  if(x >= -0x80000000 && x < 0x80000000) {
+    return rval.putSignedInt(x, 32);
+  }
+  var error = new Error('Integer too large; max is 32-bits.');
+  error.integer = x;
+  throw error;
+};
+
+/**
+ * Converts a DER-encoded byte buffer to a javascript integer. This is
+ * typically used to decode the value of an INTEGER type.
+ *
+ * @param bytes the byte buffer.
+ *
+ * @return the integer.
+ */
+asn1.derToInteger = function(bytes) {
+  // wrap in buffer if needed
+  if(typeof bytes === 'string') {
+    bytes = forge.util.createBuffer(bytes);
+  }
+
+  var n = bytes.length() * 8;
+  if(n > 32) {
+    throw new Error('Integer too large; max is 32-bits.');
+  }
+  return bytes.getSignedInt(n);
+};
+
+/**
+ * Validates the that given ASN.1 object is at least a super set of the
+ * given ASN.1 structure. Only tag classes and types are checked. An
+ * optional map may also be provided to capture ASN.1 values while the
+ * structure is checked.
+ *
+ * To capture an ASN.1 value, set an object in the validator's 'capture'
+ * parameter to the key to use in the capture map. To capture the full
+ * ASN.1 object, specify 'captureAsn1'.
+ *
+ * Objects in the validator may set a field 'optional' to true to indicate
+ * that it isn't necessary to pass validation.
+ *
+ * @param obj the ASN.1 object to validate.
+ * @param v the ASN.1 structure validator.
+ * @param capture an optional map to capture values in.
+ * @param errors an optional array for storing validation errors.
+ *
+ * @return true on success, false on failure.
+ */
+asn1.validate = function(obj, v, capture, errors) {
+  var rval = false;
+
+  // ensure tag class and type are the same if specified
+  if((obj.tagClass === v.tagClass || typeof(v.tagClass) === 'undefined') &&
+    (obj.type === v.type || typeof(v.type) === 'undefined')) {
+    // ensure constructed flag is the same if specified
+    if(obj.constructed === v.constructed ||
+      typeof(v.constructed) === 'undefined') {
+      rval = true;
+
+      // handle sub values
+      if(v.value && forge.util.isArray(v.value)) {
+        var j = 0;
+        for(var i = 0; rval && i < v.value.length; ++i) {
+          rval = v.value[i].optional || false;
+          if(obj.value[j]) {
+            rval = asn1.validate(obj.value[j], v.value[i], capture, errors);
+            if(rval) {
+              ++j;
+            } else if(v.value[i].optional) {
+              rval = true;
+            }
+          }
+          if(!rval && errors) {
+            errors.push(
+              '[' + v.name + '] ' +
+              'Tag class "' + v.tagClass + '", type "' +
+              v.type + '" expected value length "' +
+              v.value.length + '", got "' +
+              obj.value.length + '"');
+          }
+        }
+      }
+
+      if(rval && capture) {
+        if(v.capture) {
+          capture[v.capture] = obj.value;
+        }
+        if(v.captureAsn1) {
+          capture[v.captureAsn1] = obj;
+        }
+      }
+    } else if(errors) {
+      errors.push(
+        '[' + v.name + '] ' +
+        'Expected constructed "' + v.constructed + '", got "' +
+        obj.constructed + '"');
+    }
+  } else if(errors) {
+    if(obj.tagClass !== v.tagClass) {
+      errors.push(
+        '[' + v.name + '] ' +
+        'Expected tag class "' + v.tagClass + '", got "' +
+        obj.tagClass + '"');
+    }
+    if(obj.type !== v.type) {
+      errors.push(
+        '[' + v.name + '] ' +
+        'Expected type "' + v.type + '", got "' + obj.type + '"');
+    }
+  }
+  return rval;
+};
+
+// regex for testing for non-latin characters
+var _nonLatinRegex = /[^\\u0000-\\u00ff]/;
+
+/**
+ * Pretty prints an ASN.1 object to a string.
+ *
+ * @param obj the object to write out.
+ * @param level the level in the tree.
+ * @param indentation the indentation to use.
+ *
+ * @return the string.
+ */
+asn1.prettyPrint = function(obj, level, indentation) {
+  var rval = '';
+
+  // set default level and indentation
+  level = level || 0;
+  indentation = indentation || 2;
+
+  // start new line for deep levels
+  if(level > 0) {
+    rval += '\n';
+  }
+
+  // create indent
+  var indent = '';
+  for(var i = 0; i < level * indentation; ++i) {
+    indent += ' ';
+  }
+
+  // print class:type
+  rval += indent + 'Tag: ';
+  switch(obj.tagClass) {
+  case asn1.Class.UNIVERSAL:
+    rval += 'Universal:';
+    break;
+  case asn1.Class.APPLICATION:
+    rval += 'Application:';
+    break;
+  case asn1.Class.CONTEXT_SPECIFIC:
+    rval += 'Context-Specific:';
+    break;
+  case asn1.Class.PRIVATE:
+    rval += 'Private:';
+    break;
+  }
+
+  if(obj.tagClass === asn1.Class.UNIVERSAL) {
+    rval += obj.type;
+
+    // known types
+    switch(obj.type) {
+    case asn1.Type.NONE:
+      rval += ' (None)';
+      break;
+    case asn1.Type.BOOLEAN:
+      rval += ' (Boolean)';
+      break;
+    case asn1.Type.BITSTRING:
+      rval += ' (Bit string)';
+      break;
+    case asn1.Type.INTEGER:
+      rval += ' (Integer)';
+      break;
+    case asn1.Type.OCTETSTRING:
+      rval += ' (Octet string)';
+      break;
+    case asn1.Type.NULL:
+      rval += ' (Null)';
+      break;
+    case asn1.Type.OID:
+      rval += ' (Object Identifier)';
+      break;
+    case asn1.Type.ODESC:
+      rval += ' (Object Descriptor)';
+      break;
+    case asn1.Type.EXTERNAL:
+      rval += ' (External or Instance of)';
+      break;
+    case asn1.Type.REAL:
+      rval += ' (Real)';
+      break;
+    case asn1.Type.ENUMERATED:
+      rval += ' (Enumerated)';
+      break;
+    case asn1.Type.EMBEDDED:
+      rval += ' (Embedded PDV)';
+      break;
+    case asn1.Type.UTF8:
+      rval += ' (UTF8)';
+      break;
+    case asn1.Type.ROID:
+      rval += ' (Relative Object Identifier)';
+      break;
+    case asn1.Type.SEQUENCE:
+      rval += ' (Sequence)';
+      break;
+    case asn1.Type.SET:
+      rval += ' (Set)';
+      break;
+    case asn1.Type.PRINTABLESTRING:
+      rval += ' (Printable String)';
+      break;
+    case asn1.Type.IA5String:
+      rval += ' (IA5String (ASCII))';
+      break;
+    case asn1.Type.UTCTIME:
+      rval += ' (UTC time)';
+      break;
+    case asn1.Type.GENERALIZEDTIME:
+      rval += ' (Generalized time)';
+      break;
+    case asn1.Type.BMPSTRING:
+      rval += ' (BMP String)';
+      break;
+    }
+  } else {
+    rval += obj.type;
+  }
+
+  rval += '\n';
+  rval += indent + 'Constructed: ' + obj.constructed + '\n';
+
+  if(obj.composed) {
+    var subvalues = 0;
+    var sub = '';
+    for(var i = 0; i < obj.value.length; ++i) {
+      if(obj.value[i] !== undefined) {
+        subvalues += 1;
+        sub += asn1.prettyPrint(obj.value[i], level + 1, indentation);
+        if((i + 1) < obj.value.length) {
+          sub += ',';
+        }
+      }
+    }
+    rval += indent + 'Sub values: ' + subvalues + sub;
+  } else {
+    rval += indent + 'Value: ';
+    if(obj.type === asn1.Type.OID) {
+      var oid = asn1.derToOid(obj.value);
+      rval += oid;
+      if(forge.pki && forge.pki.oids) {
+        if(oid in forge.pki.oids) {
+          rval += ' (' + forge.pki.oids[oid] + ') ';
+        }
+      }
+    }
+    if(obj.type === asn1.Type.INTEGER) {
+      try {
+        rval += asn1.derToInteger(obj.value);
+      } catch(ex) {
+        rval += '0x' + forge.util.bytesToHex(obj.value);
+      }
+    } else if(obj.type === asn1.Type.OCTETSTRING) {
+      if(!_nonLatinRegex.test(obj.value)) {
+        rval += '(' + obj.value + ') ';
+      }
+      rval += '0x' + forge.util.bytesToHex(obj.value);
+    } else if(obj.type === asn1.Type.UTF8) {
+      rval += forge.util.decodeUtf8(obj.value);
+    } else if(obj.type === asn1.Type.PRINTABLESTRING ||
+      obj.type === asn1.Type.IA5String) {
+      rval += obj.value;
+    } else if(_nonLatinRegex.test(obj.value)) {
+      rval += '0x' + forge.util.bytesToHex(obj.value);
+    } else if(obj.value.length === 0) {
+      rval += '[null]';
+    } else {
+      rval += obj.value;
+    }
+  }
+
+  return rval;
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'asn1';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './util', './oids'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/cipher.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/cipher.js
new file mode 100644
index 0000000..58db4ce
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/cipher.js
@@ -0,0 +1,286 @@
+/**
+ * Cipher base API.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+forge.cipher = forge.cipher || {};
+
+// registered algorithms
+forge.cipher.algorithms = forge.cipher.algorithms || {};
+
+/**
+ * Creates a cipher object that can be used to encrypt data using the given
+ * algorithm and key. The algorithm may be provided as a string value for a
+ * previously registered algorithm or it may be given as a cipher algorithm
+ * API object.
+ *
+ * @param algorithm the algorithm to use, either a string or an algorithm API
+ *          object.
+ * @param key the key to use, as a binary-encoded string of bytes or a
+ *          byte buffer.
+ *
+ * @return the cipher.
+ */
+forge.cipher.createCipher = function(algorithm, key) {
+  var api = algorithm;
+  if(typeof api === 'string') {
+    api = forge.cipher.getAlgorithm(api);
+    if(api) {
+      api = api();
+    }
+  }
+  if(!api) {
+    throw new Error('Unsupported algorithm: ' + algorithm);
+  }
+
+  // assume block cipher
+  return new forge.cipher.BlockCipher({
+    algorithm: api,
+    key: key,
+    decrypt: false
+  });
+};
+
+/**
+ * Creates a decipher object that can be used to decrypt data using the given
+ * algorithm and key. The algorithm may be provided as a string value for a
+ * previously registered algorithm or it may be given as a cipher algorithm
+ * API object.
+ *
+ * @param algorithm the algorithm to use, either a string or an algorithm API
+ *          object.
+ * @param key the key to use, as a binary-encoded string of bytes or a
+ *          byte buffer.
+ *
+ * @return the cipher.
+ */
+forge.cipher.createDecipher = function(algorithm, key) {
+  var api = algorithm;
+  if(typeof api === 'string') {
+    api = forge.cipher.getAlgorithm(api);
+    if(api) {
+      api = api();
+    }
+  }
+  if(!api) {
+    throw new Error('Unsupported algorithm: ' + algorithm);
+  }
+
+  // assume block cipher
+  return new forge.cipher.BlockCipher({
+    algorithm: api,
+    key: key,
+    decrypt: true
+  });
+};
+
+/**
+ * Registers an algorithm by name. If the name was already registered, the
+ * algorithm API object will be overwritten.
+ *
+ * @param name the name of the algorithm.
+ * @param algorithm the algorithm API object.
+ */
+forge.cipher.registerAlgorithm = function(name, algorithm) {
+  name = name.toUpperCase();
+  forge.cipher.algorithms[name] = algorithm;
+};
+
+/**
+ * Gets a registered algorithm by name.
+ *
+ * @param name the name of the algorithm.
+ *
+ * @return the algorithm, if found, null if not.
+ */
+forge.cipher.getAlgorithm = function(name) {
+  name = name.toUpperCase();
+  if(name in forge.cipher.algorithms) {
+    return forge.cipher.algorithms[name];
+  }
+  return null;
+};
+
+var BlockCipher = forge.cipher.BlockCipher = function(options) {
+  this.algorithm = options.algorithm;
+  this.mode = this.algorithm.mode;
+  this.blockSize = this.mode.blockSize;
+  this._finish = false;
+  this._input = null;
+  this.output = null;
+  this._op = options.decrypt ? this.mode.decrypt : this.mode.encrypt;
+  this._decrypt = options.decrypt;
+  this.algorithm.initialize(options);
+};
+
+/**
+ * Starts or restarts the encryption or decryption process, whichever
+ * was previously configured.
+ *
+ * For non-GCM mode, the IV may be a binary-encoded string of bytes, an array
+ * of bytes, a byte buffer, or an array of 32-bit integers. If the IV is in
+ * bytes, then it must be Nb (16) bytes in length. If the IV is given in as
+ * 32-bit integers, then it must be 4 integers long.
+ *
+ * For GCM-mode, the IV must be given as a binary-encoded string of bytes or
+ * a byte buffer. The number of bytes should be 12 (96 bits) as recommended
+ * by NIST SP-800-38D but another length may be given.
+ *
+ * @param options the options to use:
+ *          iv the initialization vector to use as a binary-encoded string of
+ *            bytes, null to reuse the last ciphered block from a previous
+ *            update() (this "residue" method is for legacy support only).
+ *          additionalData additional authentication data as a binary-encoded
+ *            string of bytes, for 'GCM' mode, (default: none).
+ *          tagLength desired length of authentication tag, in bits, for
+ *            'GCM' mode (0-128, default: 128).
+ *          tag the authentication tag to check if decrypting, as a
+ *             binary-encoded string of bytes.
+ *          output the output the buffer to write to, null to create one.
+ */
+BlockCipher.prototype.start = function(options) {
+  options = options || {};
+  var opts = {};
+  for(var key in options) {
+    opts[key] = options[key];
+  }
+  opts.decrypt = this._decrypt;
+  this._finish = false;
+  this._input = forge.util.createBuffer();
+  this.output = options.output || forge.util.createBuffer();
+  this.mode.start(opts);
+};
+
+/**
+ * Updates the next block according to the cipher mode.
+ *
+ * @param input the buffer to read from.
+ */
+BlockCipher.prototype.update = function(input) {
+  if(!this._finish) {
+    // not finishing, so fill the input buffer with more input
+    this._input.putBuffer(input);
+  }
+
+  // do cipher operation while input contains full blocks or if finishing
+  while(this._input.length() >= this.blockSize ||
+    (this._input.length() > 0 && this._finish)) {
+    this._op.call(this.mode, this._input, this.output);
+  }
+
+  // free consumed memory from input buffer
+  this._input.compact();
+};
+
+/**
+ * Finishes encrypting or decrypting.
+ *
+ * @param pad a padding function to use in CBC mode, null for default,
+ *          signature(blockSize, buffer, decrypt).
+ *
+ * @return true if successful, false on error.
+ */
+BlockCipher.prototype.finish = function(pad) {
+  // backwards-compatibility w/deprecated padding API
+  // Note: will overwrite padding functions even after another start() call
+  if(pad && this.mode.name === 'CBC') {
+    this.mode.pad = function(input) {
+      return pad(this.blockSize, input, false);
+    };
+    this.mode.unpad = function(output) {
+      return pad(this.blockSize, output, true);
+    };
+  }
+
+  // build options for padding and afterFinish functions
+  var options = {};
+  options.decrypt = this._decrypt;
+
+  // get # of bytes that won't fill a block
+  options.overflow = this._input.length() % this.blockSize;
+
+  if(!this._decrypt && this.mode.pad) {
+    if(!this.mode.pad(this._input, options)) {
+      return false;
+    }
+  }
+
+  // do final update
+  this._finish = true;
+  this.update();
+
+  if(this._decrypt && this.mode.unpad) {
+    if(!this.mode.unpad(this.output, options)) {
+      return false;
+    }
+  }
+
+  if(this.mode.afterFinish) {
+    if(!this.mode.afterFinish(this.output, options)) {
+      return false;
+    }
+  }
+
+  return true;
+};
+
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'cipher';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/cipherModes.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/cipherModes.js
new file mode 100644
index 0000000..9ed318f
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/cipherModes.js
@@ -0,0 +1,817 @@
+/**
+ * Supported cipher modes.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+forge.cipher = forge.cipher || {};
+
+// supported cipher modes
+var modes = forge.cipher.modes = forge.cipher.modes || {};
+
+
+/** Electronic codebook (ECB) (Don't use this; it's not secure) **/
+
+modes.ecb = function(options) {
+  options = options || {};
+  this.name = 'ECB';
+  this.cipher = options.cipher;
+  this.blockSize = options.blockSize || 16;
+  this._blocks = this.blockSize / 4;
+  this._inBlock = new Array(this._blocks);
+  this._outBlock = new Array(this._blocks);
+};
+
+modes.ecb.prototype.start = function(options) {};
+
+modes.ecb.prototype.encrypt = function(input, output) {
+  // get next block
+  for(var i = 0; i < this._blocks; ++i) {
+    this._inBlock[i] = input.getInt32();
+  }
+
+  // encrypt block
+  this.cipher.encrypt(this._inBlock, this._outBlock);
+
+  // write output
+  for(var i = 0; i < this._blocks; ++i) {
+    output.putInt32(this._outBlock[i]);
+  }
+};
+
+modes.ecb.prototype.decrypt = function(input, output) {
+  // get next block
+  for(var i = 0; i < this._blocks; ++i) {
+    this._inBlock[i] = input.getInt32();
+  }
+
+  // decrypt block
+  this.cipher.decrypt(this._inBlock, this._outBlock);
+
+  // write output
+  for(var i = 0; i < this._blocks; ++i) {
+    output.putInt32(this._outBlock[i]);
+  }
+};
+
+modes.ecb.prototype.pad = function(input, options) {
+  // add PKCS#7 padding to block (each pad byte is the
+  // value of the number of pad bytes)
+  var padding = (input.length() === this.blockSize ?
+    this.blockSize : (this.blockSize - input.length()));
+  input.fillWithByte(padding, padding);
+  return true;
+};
+
+modes.ecb.prototype.unpad = function(output, options) {
+  // check for error: input data not a multiple of blockSize
+  if(options.overflow > 0) {
+    return false;
+  }
+
+  // ensure padding byte count is valid
+  var len = output.length();
+  var count = output.at(len - 1);
+  if(count > (this.blockSize << 2)) {
+    return false;
+  }
+
+  // trim off padding bytes
+  output.truncate(count);
+  return true;
+};
+
+
+/** Cipher-block Chaining (CBC) **/
+
+modes.cbc = function(options) {
+  options = options || {};
+  this.name = 'CBC';
+  this.cipher = options.cipher;
+  this.blockSize = options.blockSize || 16;
+  this._blocks = this.blockSize / 4;
+  this._inBlock = new Array(this._blocks);
+  this._outBlock = new Array(this._blocks);
+};
+
+modes.cbc.prototype.start = function(options) {
+  // Note: legacy support for using IV residue (has security flaws)
+  // if IV is null, reuse block from previous processing
+  if(options.iv === null) {
+    // must have a previous block
+    if(!this._prev) {
+      throw new Error('Invalid IV parameter.');
+    }
+    this._iv = this._prev.slice(0);
+  } else if(!('iv' in options)) {
+    throw new Error('Invalid IV parameter.');
+  } else {
+    // save IV as "previous" block
+    this._iv = transformIV(options.iv);
+    this._prev = this._iv.slice(0);
+  }
+};
+
+modes.cbc.prototype.encrypt = function(input, output) {
+  // get next block
+  // CBC XOR's IV (or previous block) with plaintext
+  for(var i = 0; i < this._blocks; ++i) {
+    this._inBlock[i] = this._prev[i] ^ input.getInt32();
+  }
+
+  // encrypt block
+  this.cipher.encrypt(this._inBlock, this._outBlock);
+
+  // write output, save previous block
+  for(var i = 0; i < this._blocks; ++i) {
+    output.putInt32(this._outBlock[i]);
+  }
+  this._prev = this._outBlock;
+};
+
+modes.cbc.prototype.decrypt = function(input, output) {
+  // get next block
+  for(var i = 0; i < this._blocks; ++i) {
+    this._inBlock[i] = input.getInt32();
+  }
+
+  // decrypt block
+  this.cipher.decrypt(this._inBlock, this._outBlock);
+
+  // write output, save previous ciphered block
+  // CBC XOR's IV (or previous block) with ciphertext
+  for(var i = 0; i < this._blocks; ++i) {
+    output.putInt32(this._prev[i] ^ this._outBlock[i]);
+  }
+  this._prev = this._inBlock.slice(0);
+};
+
+modes.cbc.prototype.pad = function(input, options) {
+  // add PKCS#7 padding to block (each pad byte is the
+  // value of the number of pad bytes)
+  var padding = (input.length() === this.blockSize ?
+    this.blockSize : (this.blockSize - input.length()));
+  input.fillWithByte(padding, padding);
+  return true;
+};
+
+modes.cbc.prototype.unpad = function(output, options) {
+  // check for error: input data not a multiple of blockSize
+  if(options.overflow > 0) {
+    return false;
+  }
+
+  // ensure padding byte count is valid
+  var len = output.length();
+  var count = output.at(len - 1);
+  if(count > (this.blockSize << 2)) {
+    return false;
+  }
+
+  // trim off padding bytes
+  output.truncate(count);
+  return true;
+};
+
+
+/** Cipher feedback (CFB) **/
+
+modes.cfb = function(options) {
+  options = options || {};
+  this.name = 'CFB';
+  this.cipher = options.cipher;
+  this.blockSize = options.blockSize || 16;
+  this._blocks = this.blockSize / 4;
+  this._inBlock = null;
+  this._outBlock = new Array(this._blocks);
+};
+
+modes.cfb.prototype.start = function(options) {
+  if(!('iv' in options)) {
+    throw new Error('Invalid IV parameter.');
+  }
+  // use IV as first input
+  this._iv = transformIV(options.iv);
+  this._inBlock = this._iv.slice(0);
+};
+
+modes.cfb.prototype.encrypt = function(input, output) {
+  // encrypt block
+  this.cipher.encrypt(this._inBlock, this._outBlock);
+
+  // XOR input with output, write input as output
+  for(var i = 0; i < this._blocks; ++i) {
+    this._inBlock[i] = input.getInt32() ^ this._outBlock[i];
+    output.putInt32(this._inBlock[i]);
+  }
+};
+
+modes.cfb.prototype.decrypt = function(input, output) {
+  // encrypt block (CFB always uses encryption mode)
+  this.cipher.encrypt(this._inBlock, this._outBlock);
+
+  // XOR input with output
+  for(var i = 0; i < this._blocks; ++i) {
+    this._inBlock[i] = input.getInt32();
+    output.putInt32(this._inBlock[i] ^ this._outBlock[i]);
+  }
+};
+
+modes.cfb.prototype.afterFinish = function(output, options) {
+  // handle stream mode truncation
+  if(options.overflow > 0) {
+    output.truncate(this.blockSize - options.overflow);
+  }
+  return true;
+};
+
+/** Output feedback (OFB) **/
+
+modes.ofb = function(options) {
+  options = options || {};
+  this.name = 'OFB';
+  this.cipher = options.cipher;
+  this.blockSize = options.blockSize || 16;
+  this._blocks = this.blockSize / 4;
+  this._inBlock = null;
+  this._outBlock = new Array(this._blocks);
+};
+
+modes.ofb.prototype.start = function(options) {
+  if(!('iv' in options)) {
+    throw new Error('Invalid IV parameter.');
+  }
+  // use IV as first input
+  this._iv = transformIV(options.iv);
+  this._inBlock = this._iv.slice(0);
+};
+
+modes.ofb.prototype.encrypt = function(input, output) {
+  // encrypt block (OFB always uses encryption mode)
+  this.cipher.encrypt(this._inBlock, this._outBlock);
+
+  // XOR input with output and update next input
+  for(var i = 0; i < this._blocks; ++i) {
+    output.putInt32(input.getInt32() ^ this._outBlock[i]);
+    this._inBlock[i] = this._outBlock[i];
+  }
+};
+
+modes.ofb.prototype.decrypt = modes.ofb.prototype.encrypt;
+
+modes.ofb.prototype.afterFinish = function(output, options) {
+  // handle stream mode truncation
+  if(options.overflow > 0) {
+    output.truncate(this.blockSize - options.overflow);
+  }
+  return true;
+};
+
+
+/** Counter (CTR) **/
+
+modes.ctr = function(options) {
+  options = options || {};
+  this.name = 'CTR';
+  this.cipher = options.cipher;
+  this.blockSize = options.blockSize || 16;
+  this._blocks = this.blockSize / 4;
+  this._inBlock = null;
+  this._outBlock = new Array(this._blocks);
+};
+
+modes.ctr.prototype.start = function(options) {
+  if(!('iv' in options)) {
+    throw new Error('Invalid IV parameter.');
+  }
+  // use IV as first input
+  this._iv = transformIV(options.iv);
+  this._inBlock = this._iv.slice(0);
+};
+
+modes.ctr.prototype.encrypt = function(input, output) {
+  // encrypt block (CTR always uses encryption mode)
+  this.cipher.encrypt(this._inBlock, this._outBlock);
+
+  // increment counter (input block)
+  inc32(this._inBlock);
+
+  // XOR input with output
+  for(var i = 0; i < this._blocks; ++i) {
+    output.putInt32(input.getInt32() ^ this._outBlock[i]);
+  }
+};
+
+modes.ctr.prototype.decrypt = modes.ctr.prototype.encrypt;
+
+modes.ctr.prototype.afterFinish = function(output, options) {
+  // handle stream mode truncation
+  if(options.overflow > 0) {
+    output.truncate(this.blockSize - options.overflow);
+  }
+  return true;
+};
+
+
+/** Galois/Counter Mode (GCM) **/
+
+modes.gcm = function(options) {
+  options = options || {};
+  this.name = 'GCM';
+  this.cipher = options.cipher;
+  this.blockSize = options.blockSize || 16;
+  this._blocks = this.blockSize / 4;
+  this._inBlock = new Array(this._blocks);
+  this._outBlock = new Array(this._blocks);
+
+  // R is actually this value concatenated with 120 more zero bits, but
+  // we only XOR against R so the other zeros have no effect -- we just
+  // apply this value to the first integer in a block
+  this._R = 0xE1000000;
+};
+
+modes.gcm.prototype.start = function(options) {
+  if(!('iv' in options)) {
+    throw new Error('Invalid IV parameter.');
+  }
+  // ensure IV is a byte buffer
+  var iv = forge.util.createBuffer(options.iv);
+
+  // no ciphered data processed yet
+  this._cipherLength = 0;
+
+  // default additional data is none
+  var additionalData;
+  if('additionalData' in options) {
+    additionalData = forge.util.createBuffer(options.additionalData);
+  } else {
+    additionalData = forge.util.createBuffer();
+  }
+
+  // default tag length is 128 bits
+  if('tagLength' in options) {
+    this._tagLength = options.tagLength;
+  } else {
+    this._tagLength = 128;
+  }
+
+  // if tag is given, ensure tag matches tag length
+  this._tag = null;
+  if(options.decrypt) {
+    // save tag to check later
+    this._tag = forge.util.createBuffer(options.tag).getBytes();
+    if(this._tag.length !== (this._tagLength / 8)) {
+      throw new Error('Authentication tag does not match tag length.');
+    }
+  }
+
+  // create tmp storage for hash calculation
+  this._hashBlock = new Array(this._blocks);
+
+  // no tag generated yet
+  this.tag = null;
+
+  // generate hash subkey
+  // (apply block cipher to "zero" block)
+  this._hashSubkey = new Array(this._blocks);
+  this.cipher.encrypt([0, 0, 0, 0], this._hashSubkey);
+
+  // generate table M
+  // use 4-bit tables (32 component decomposition of a 16 byte value)
+  // 8-bit tables take more space and are known to have security
+  // vulnerabilities (in native implementations)
+  this.componentBits = 4;
+  this._m = this.generateHashTable(this._hashSubkey, this.componentBits);
+
+  // Note: support IV length different from 96 bits? (only supporting
+  // 96 bits is recommended by NIST SP-800-38D)
+  // generate J_0
+  var ivLength = iv.length();
+  if(ivLength === 12) {
+    // 96-bit IV
+    this._j0 = [iv.getInt32(), iv.getInt32(), iv.getInt32(), 1];
+  } else {
+    // IV is NOT 96-bits
+    this._j0 = [0, 0, 0, 0];
+    while(iv.length() > 0) {
+      this._j0 = this.ghash(
+        this._hashSubkey, this._j0,
+        [iv.getInt32(), iv.getInt32(), iv.getInt32(), iv.getInt32()]);
+    }
+    this._j0 = this.ghash(
+      this._hashSubkey, this._j0, [0, 0].concat(from64To32(ivLength * 8)));
+  }
+
+  // generate ICB (initial counter block)
+  this._inBlock = this._j0.slice(0);
+  inc32(this._inBlock);
+
+  // consume authentication data
+  additionalData = forge.util.createBuffer(additionalData);
+  // save additional data length as a BE 64-bit number
+  this._aDataLength = from64To32(additionalData.length() * 8);
+  // pad additional data to 128 bit (16 byte) block size
+  var overflow = additionalData.length() % this.blockSize;
+  if(overflow) {
+    additionalData.fillWithByte(0, this.blockSize - overflow);
+  }
+  this._s = [0, 0, 0, 0];
+  while(additionalData.length() > 0) {
+    this._s = this.ghash(this._hashSubkey, this._s, [
+      additionalData.getInt32(),
+      additionalData.getInt32(),
+      additionalData.getInt32(),
+      additionalData.getInt32()
+    ]);
+  }
+};
+
+modes.gcm.prototype.encrypt = function(input, output) {
+  // encrypt block
+  this.cipher.encrypt(this._inBlock, this._outBlock);
+
+  // increment counter (input block)
+  inc32(this._inBlock);
+
+  // save input length
+  var inputLength = input.length();
+
+  // XOR input with output
+  for(var i = 0; i < this._blocks; ++i) {
+    this._outBlock[i] ^= input.getInt32();
+  }
+
+  // handle overflow prior to hashing
+  if(inputLength < this.blockSize) {
+    // get block overflow
+    var overflow = inputLength % this.blockSize;
+    this._cipherLength += overflow;
+
+    // truncate for hash function
+    var tmp = forge.util.createBuffer();
+    tmp.putInt32(this._outBlock[0]);
+    tmp.putInt32(this._outBlock[1]);
+    tmp.putInt32(this._outBlock[2]);
+    tmp.putInt32(this._outBlock[3]);
+    tmp.truncate(this.blockSize - overflow);
+    this._outBlock[0] = tmp.getInt32();
+    this._outBlock[1] = tmp.getInt32();
+    this._outBlock[2] = tmp.getInt32();
+    this._outBlock[3] = tmp.getInt32();
+  } else {
+    this._cipherLength += this.blockSize;
+  }
+
+  for(var i = 0; i < this._blocks; ++i) {
+    output.putInt32(this._outBlock[i]);
+  }
+
+  // update hash block S
+  this._s = this.ghash(this._hashSubkey, this._s, this._outBlock);
+};
+
+modes.gcm.prototype.decrypt = function(input, output) {
+  // encrypt block (GCM always uses encryption mode)
+  this.cipher.encrypt(this._inBlock, this._outBlock);
+
+  // increment counter (input block)
+  inc32(this._inBlock);
+
+  // save input length
+  var inputLength = input.length();
+
+  // update hash block S
+  this._hashBlock[0] = input.getInt32();
+  this._hashBlock[1] = input.getInt32();
+  this._hashBlock[2] = input.getInt32();
+  this._hashBlock[3] = input.getInt32();
+  this._s = this.ghash(this._hashSubkey, this._s, this._hashBlock);
+
+  // XOR hash input with output
+  for(var i = 0; i < this._blocks; ++i) {
+    output.putInt32(this._outBlock[i] ^ this._hashBlock[i]);
+  }
+
+  // increment cipher data length
+  if(inputLength < this.blockSize) {
+    this._cipherLength += inputLength % this.blockSize;
+  } else {
+    this._cipherLength += this.blockSize;
+  }
+};
+
+modes.gcm.prototype.afterFinish = function(output, options) {
+  var rval = true;
+
+  // handle overflow
+  if(options.overflow) {
+    output.truncate(this.blockSize - options.overflow);
+  }
+
+  // handle authentication tag
+  this.tag = forge.util.createBuffer();
+
+  // concatenate additional data length with cipher length
+  var lengths = this._aDataLength.concat(from64To32(this._cipherLength * 8));
+
+  // include lengths in hash
+  this._s = this.ghash(this._hashSubkey, this._s, lengths);
+
+  // do GCTR(J_0, S)
+  var tag = [];
+  this.cipher.encrypt(this._j0, tag);
+  for(var i = 0; i < this._blocks; ++i) {
+    this.tag.putInt32(this._s[i] ^ tag[i]);
+  }
+
+  // trim tag to length
+  this.tag.truncate(this.tag.length() % (this._tagLength / 8));
+
+  // check authentication tag
+  if(options.decrypt && this.tag.bytes() !== this._tag) {
+    rval = false;
+  }
+
+  return rval;
+};
+
+/**
+ * See NIST SP-800-38D 6.3 (Algorithm 1). This function performs Galois
+ * field multiplication. The field, GF(2^128), is defined by the polynomial:
+ *
+ * x^128 + x^7 + x^2 + x + 1
+ *
+ * Which is represented in little-endian binary form as: 11100001 (0xe1). When
+ * the value of a coefficient is 1, a bit is set. The value R, is the
+ * concatenation of this value and 120 zero bits, yielding a 128-bit value
+ * which matches the block size.
+ *
+ * This function will multiply two elements (vectors of bytes), X and Y, in
+ * the field GF(2^128). The result is initialized to zero. For each bit of
+ * X (out of 128), x_i, if x_i is set, then the result is multiplied (XOR'd)
+ * by the current value of Y. For each bit, the value of Y will be raised by
+ * a power of x (multiplied by the polynomial x). This can be achieved by
+ * shifting Y once to the right. If the current value of Y, prior to being
+ * multiplied by x, has 0 as its LSB, then it is a 127th degree polynomial.
+ * Otherwise, we must divide by R after shifting to find the remainder.
+ *
+ * @param x the first block to multiply by the second.
+ * @param y the second block to multiply by the first.
+ *
+ * @return the block result of the multiplication.
+ */
+modes.gcm.prototype.multiply = function(x, y) {
+  var z_i = [0, 0, 0, 0];
+  var v_i = y.slice(0);
+
+  // calculate Z_128 (block has 128 bits)
+  for(var i = 0; i < 128; ++i) {
+    // if x_i is 0, Z_{i+1} = Z_i (unchanged)
+    // else Z_{i+1} = Z_i ^ V_i
+    // get x_i by finding 32-bit int position, then left shift 1 by remainder
+    var x_i = x[(i / 32) | 0] & (1 << (31 - i % 32));
+    if(x_i) {
+      z_i[0] ^= v_i[0];
+      z_i[1] ^= v_i[1];
+      z_i[2] ^= v_i[2];
+      z_i[3] ^= v_i[3];
+    }
+
+    // if LSB(V_i) is 1, V_i = V_i >> 1
+    // else V_i = (V_i >> 1) ^ R
+    this.pow(v_i, v_i);
+  }
+
+  return z_i;
+};
+
+modes.gcm.prototype.pow = function(x, out) {
+  // if LSB(x) is 1, x = x >>> 1
+  // else x = (x >>> 1) ^ R
+  var lsb = x[3] & 1;
+
+  // always do x >>> 1:
+  // starting with the rightmost integer, shift each integer to the right
+  // one bit, pulling in the bit from the integer to the left as its top
+  // most bit (do this for the last 3 integers)
+  for(var i = 3; i > 0; --i) {
+    out[i] = (x[i] >>> 1) | ((x[i - 1] & 1) << 31);
+  }
+  // shift the first integer normally
+  out[0] = x[0] >>> 1;
+
+  // if lsb was not set, then polynomial had a degree of 127 and doesn't
+  // need to divided; otherwise, XOR with R to find the remainder; we only
+  // need to XOR the first integer since R technically ends w/120 zero bits
+  if(lsb) {
+    out[0] ^= this._R;
+  }
+};
+
+modes.gcm.prototype.tableMultiply = function(x) {
+  // assumes 4-bit tables are used
+  var z = [0, 0, 0, 0];
+  for(var i = 0; i < 32; ++i) {
+    var idx = (i / 8) | 0;
+    var x_i = (x[idx] >>> ((7 - (i % 8)) * 4)) & 0xF;
+    var ah = this._m[i][x_i];
+    z[0] ^= ah[0];
+    z[1] ^= ah[1];
+    z[2] ^= ah[2];
+    z[3] ^= ah[3];
+  }
+  return z;
+};
+
+/**
+ * A continuing version of the GHASH algorithm that operates on a single
+ * block. The hash block, last hash value (Ym) and the new block to hash
+ * are given.
+ *
+ * @param h the hash block.
+ * @param y the previous value for Ym, use [0, 0, 0, 0] for a new hash.
+ * @param x the block to hash.
+ *
+ * @return the hashed value (Ym).
+ */
+modes.gcm.prototype.ghash = function(h, y, x) {
+  y[0] ^= x[0];
+  y[1] ^= x[1];
+  y[2] ^= x[2];
+  y[3] ^= x[3];
+  return this.tableMultiply(y);
+  //return this.multiply(y, h);
+};
+
+/**
+ * Precomputes a table for multiplying against the hash subkey. This
+ * mechanism provides a substantial speed increase over multiplication
+ * performed without a table. The table-based multiplication this table is
+ * for solves X * H by multiplying each component of X by H and then
+ * composing the results together using XOR.
+ *
+ * This function can be used to generate tables with different bit sizes
+ * for the components, however, this implementation assumes there are
+ * 32 components of X (which is a 16 byte vector), therefore each component
+ * takes 4-bits (so the table is constructed with bits=4).
+ *
+ * @param h the hash subkey.
+ * @param bits the bit size for a component.
+ */
+modes.gcm.prototype.generateHashTable = function(h, bits) {
+  // TODO: There are further optimizations that would use only the
+  // first table M_0 (or some variant) along with a remainder table;
+  // this can be explored in the future
+  var multiplier = 8 / bits;
+  var perInt = 4 * multiplier;
+  var size = 16 * multiplier;
+  var m = new Array(size);
+  for(var i = 0; i < size; ++i) {
+    var tmp = [0, 0, 0, 0];
+    var idx = (i / perInt) | 0;
+    var shft = ((perInt - 1 - (i % perInt)) * bits);
+    tmp[idx] = (1 << (bits - 1)) << shft;
+    m[i] = this.generateSubHashTable(this.multiply(tmp, h), bits);
+  }
+  return m;
+};
+
+/**
+ * Generates a table for multiplying against the hash subkey for one
+ * particular component (out of all possible component values).
+ *
+ * @param mid the pre-multiplied value for the middle key of the table.
+ * @param bits the bit size for a component.
+ */
+modes.gcm.prototype.generateSubHashTable = function(mid, bits) {
+  // compute the table quickly by minimizing the number of
+  // POW operations -- they only need to be performed for powers of 2,
+  // all other entries can be composed from those powers using XOR
+  var size = 1 << bits;
+  var half = size >>> 1;
+  var m = new Array(size);
+  m[half] = mid.slice(0);
+  var i = half >>> 1;
+  while(i > 0) {
+    // raise m0[2 * i] and store in m0[i]
+    this.pow(m[2 * i], m[i] = []);
+    i >>= 1;
+  }
+  i = 2;
+  while(i < half) {
+    for(var j = 1; j < i; ++j) {
+      var m_i = m[i];
+      var m_j = m[j];
+      m[i + j] = [
+        m_i[0] ^ m_j[0],
+        m_i[1] ^ m_j[1],
+        m_i[2] ^ m_j[2],
+        m_i[3] ^ m_j[3]
+      ];
+    }
+    i *= 2;
+  }
+  m[0] = [0, 0, 0, 0];
+  /* Note: We could avoid storing these by doing composition during multiply
+  calculate top half using composition by speed is preferred. */
+  for(i = half + 1; i < size; ++i) {
+    var c = m[i ^ half];
+    m[i] = [mid[0] ^ c[0], mid[1] ^ c[1], mid[2] ^ c[2], mid[3] ^ c[3]];
+  }
+  return m;
+};
+
+
+/** Utility functions */
+
+function transformIV(iv) {
+  if(typeof iv === 'string') {
+    // convert iv string into byte buffer
+    iv = forge.util.createBuffer(iv);
+  }
+
+  if(forge.util.isArray(iv) && iv.length > 4) {
+    // convert iv byte array into byte buffer
+    var tmp = iv;
+    iv = forge.util.createBuffer();
+    for(var i = 0; i < tmp.length; ++i) {
+      iv.putByte(tmp[i]);
+    }
+  }
+  if(!forge.util.isArray(iv)) {
+    // convert iv byte buffer into 32-bit integer array
+    iv = [iv.getInt32(), iv.getInt32(), iv.getInt32(), iv.getInt32()];
+  }
+
+  return iv;
+}
+
+function inc32(block) {
+  // increment last 32 bits of block only
+  block[block.length - 1] = (block[block.length - 1] + 1) & 0xFFFFFFFF;
+}
+
+function from64To32(num) {
+  // convert 64-bit number to two BE Int32s
+  return [(num / 0x100000000) | 0, num & 0xFFFFFFFF];
+}
+
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'cipherModes';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/debug.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/debug.js
new file mode 100644
index 0000000..4f7c13d
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/debug.js
@@ -0,0 +1,134 @@
+/**
+ * Debugging support for web applications.
+ *
+ * @author David I. Lehn <dlehn@digitalbazaar.com>
+ *
+ * Copyright 2008-2013 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+/* DEBUG API */
+forge.debug = forge.debug || {};
+
+// Private storage for debugging.
+// Useful to expose data that is otherwise unviewable behind closures.
+// NOTE: remember that this can hold references to data and cause leaks!
+// format is "forge._debug.<modulename>.<dataname> = data"
+// Example:
+// (function() {
+//   var cat = 'forge.test.Test'; // debugging category
+//   var sState = {...}; // local state
+//   forge.debug.set(cat, 'sState', sState);
+// })();
+forge.debug.storage = {};
+
+/**
+ * Gets debug data. Omit name for all cat data  Omit name and cat for
+ * all data.
+ *
+ * @param cat name of debugging category.
+ * @param name name of data to get (optional).
+ * @return object with requested debug data or undefined.
+ */
+forge.debug.get = function(cat, name) {
+  var rval;
+  if(typeof(cat) === 'undefined') {
+    rval = forge.debug.storage;
+  } else if(cat in forge.debug.storage) {
+    if(typeof(name) === 'undefined') {
+      rval = forge.debug.storage[cat];
+    } else {
+      rval = forge.debug.storage[cat][name];
+    }
+  }
+  return rval;
+};
+
+/**
+ * Sets debug data.
+ *
+ * @param cat name of debugging category.
+ * @param name name of data to set.
+ * @param data data to set.
+ */
+forge.debug.set = function(cat, name, data) {
+  if(!(cat in forge.debug.storage)) {
+    forge.debug.storage[cat] = {};
+  }
+  forge.debug.storage[cat][name] = data;
+};
+
+/**
+ * Clears debug data. Omit name for all cat data. Omit name and cat for
+ * all data.
+ *
+ * @param cat name of debugging category.
+ * @param name name of data to clear or omit to clear entire category.
+ */
+forge.debug.clear = function(cat, name) {
+  if(typeof(cat) === 'undefined') {
+    forge.debug.storage = {};
+  } else if(cat in forge.debug.storage) {
+    if(typeof(name) === 'undefined') {
+      delete forge.debug.storage[cat];
+    } else {
+      delete forge.debug.storage[cat][name];
+    }
+  }
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'debug';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/des.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/des.js
new file mode 100644
index 0000000..bf6d477
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/des.js
@@ -0,0 +1,552 @@
+/**
+ * DES (Data Encryption Standard) implementation.
+ *
+ * This implementation supports DES as well as 3DES-EDE in ECB and CBC mode.
+ * It is based on the BSD-licensed implementation by Paul Tero:
+ *
+ * Paul Tero, July 2001
+ * http://www.tero.co.uk/des/
+ *
+ * Optimised for performance with large blocks by Michael Hayworth, November 2001
+ * http://www.netdealing.com
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @author Stefan Siegl
+ * @author Dave Longley
+ *
+ * Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
+ * Copyright (c) 2012-2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+/* DES API */
+forge.des = forge.des || {};
+
+/**
+ * Deprecated. Instead, use:
+ *
+ * var cipher = forge.cipher.createCipher('DES-<mode>', key);
+ * cipher.start({iv: iv});
+ *
+ * Creates an DES cipher object to encrypt data using the given symmetric key.
+ * The output will be stored in the 'output' member of the returned cipher.
+ *
+ * The key and iv may be given as binary-encoded strings of bytes or
+ * byte buffers.
+ *
+ * @param key the symmetric key to use (64 or 192 bits).
+ * @param iv the initialization vector to use.
+ * @param output the buffer to write to, null to create one.
+ * @param mode the cipher mode to use (default: 'CBC' if IV is
+ *          given, 'ECB' if null).
+ *
+ * @return the cipher.
+ */
+forge.des.startEncrypting = function(key, iv, output, mode) {
+  var cipher = _createCipher({
+    key: key,
+    output: output,
+    decrypt: false,
+    mode: mode || (iv === null ? 'ECB' : 'CBC')
+  });
+  cipher.start(iv);
+  return cipher;
+};
+
+/**
+ * Deprecated. Instead, use:
+ *
+ * var cipher = forge.cipher.createCipher('DES-<mode>', key);
+ *
+ * Creates an DES cipher object to encrypt data using the given symmetric key.
+ *
+ * The key may be given as a binary-encoded string of bytes or a byte buffer.
+ *
+ * @param key the symmetric key to use (64 or 192 bits).
+ * @param mode the cipher mode to use (default: 'CBC').
+ *
+ * @return the cipher.
+ */
+forge.des.createEncryptionCipher = function(key, mode) {
+  return _createCipher({
+    key: key,
+    output: null,
+    decrypt: false,
+    mode: mode
+  });
+};
+
+/**
+ * Deprecated. Instead, use:
+ *
+ * var decipher = forge.cipher.createDecipher('DES-<mode>', key);
+ * decipher.start({iv: iv});
+ *
+ * Creates an DES cipher object to decrypt data using the given symmetric key.
+ * The output will be stored in the 'output' member of the returned cipher.
+ *
+ * The key and iv may be given as binary-encoded strings of bytes or
+ * byte buffers.
+ *
+ * @param key the symmetric key to use (64 or 192 bits).
+ * @param iv the initialization vector to use.
+ * @param output the buffer to write to, null to create one.
+ * @param mode the cipher mode to use (default: 'CBC' if IV is
+ *          given, 'ECB' if null).
+ *
+ * @return the cipher.
+ */
+forge.des.startDecrypting = function(key, iv, output, mode) {
+  var cipher = _createCipher({
+    key: key,
+    output: output,
+    decrypt: true,
+    mode: mode || (iv === null ? 'ECB' : 'CBC')
+  });
+  cipher.start(iv);
+  return cipher;
+};
+
+/**
+ * Deprecated. Instead, use:
+ *
+ * var decipher = forge.cipher.createDecipher('DES-<mode>', key);
+ *
+ * Creates an DES cipher object to decrypt data using the given symmetric key.
+ *
+ * The key may be given as a binary-encoded string of bytes or a byte buffer.
+ *
+ * @param key the symmetric key to use (64 or 192 bits).
+ * @param mode the cipher mode to use (default: 'CBC').
+ *
+ * @return the cipher.
+ */
+forge.des.createDecryptionCipher = function(key, mode) {
+  return _createCipher({
+    key: key,
+    output: null,
+    decrypt: true,
+    mode: mode
+  });
+};
+
+/**
+ * Creates a new DES cipher algorithm object.
+ *
+ * @param name the name of the algorithm.
+ * @param mode the mode factory function.
+ *
+ * @return the DES algorithm object.
+ */
+forge.des.Algorithm = function(name, mode) {
+  var self = this;
+  self.name = name;
+  self.mode = new mode({
+    blockSize: 8,
+    cipher: {
+      encrypt: function(inBlock, outBlock) {
+        return _updateBlock(self._keys, inBlock, outBlock, false);
+      },
+      decrypt: function(inBlock, outBlock) {
+        return _updateBlock(self._keys, inBlock, outBlock, true);
+      }
+    }
+  });
+  self._init = false;
+};
+
+/**
+ * Initializes this DES algorithm by expanding its key.
+ *
+ * @param options the options to use.
+ *          key the key to use with this algorithm.
+ *          decrypt true if the algorithm should be initialized for decryption,
+ *            false for encryption.
+ */
+forge.des.Algorithm.prototype.initialize = function(options) {
+  if(this._init) {
+    return;
+  }
+
+  var key = forge.util.createBuffer(options.key);
+  if(this.name.indexOf('3DES') === 0) {
+    if(key.length() !== 24) {
+      throw new Error('Invalid Triple-DES key size: ' + key.length() * 8);
+    }
+  }
+
+  // do key expansion to 16 or 48 subkeys (single or triple DES)
+  this._keys = _createKeys(key);
+  this._init = true;
+};
+
+
+/** Register DES algorithms **/
+
+registerAlgorithm('DES-ECB', forge.cipher.modes.ecb);
+registerAlgorithm('DES-CBC', forge.cipher.modes.cbc);
+registerAlgorithm('DES-CFB', forge.cipher.modes.cfb);
+registerAlgorithm('DES-OFB', forge.cipher.modes.ofb);
+registerAlgorithm('DES-CTR', forge.cipher.modes.ctr);
+
+registerAlgorithm('3DES-ECB', forge.cipher.modes.ecb);
+registerAlgorithm('3DES-CBC', forge.cipher.modes.cbc);
+registerAlgorithm('3DES-CFB', forge.cipher.modes.cfb);
+registerAlgorithm('3DES-OFB', forge.cipher.modes.ofb);
+registerAlgorithm('3DES-CTR', forge.cipher.modes.ctr);
+
+function registerAlgorithm(name, mode) {
+  var factory = function() {
+    return new forge.des.Algorithm(name, mode);
+  };
+  forge.cipher.registerAlgorithm(name, factory);
+}
+
+
+/** DES implementation **/
+
+var spfunction1 = [0x1010400,0,0x10000,0x1010404,0x1010004,0x10404,0x4,0x10000,0x400,0x1010400,0x1010404,0x400,0x1000404,0x1010004,0x1000000,0x4,0x404,0x1000400,0x1000400,0x10400,0x10400,0x1010000,0x1010000,0x1000404,0x10004,0x1000004,0x1000004,0x10004,0,0x404,0x10404,0x1000000,0x10000,0x1010404,0x4,0x1010000,0x1010400,0x1000000,0x1000000,0x400,0x1010004,0x10000,0x10400,0x1000004,0x400,0x4,0x1000404,0x10404,0x1010404,0x10004,0x1010000,0x1000404,0x1000004,0x404,0x10404,0x1010400,0x404,0x1000400,0x1000400,0,0x10004,0x10400,0,0x1010004];
+var spfunction2 = [-0x7fef7fe0,-0x7fff8000,0x8000,0x108020,0x100000,0x20,-0x7fefffe0,-0x7fff7fe0,-0x7fffffe0,-0x7fef7fe0,-0x7fef8000,-0x80000000,-0x7fff8000,0x100000,0x20,-0x7fefffe0,0x108000,0x100020,-0x7fff7fe0,0,-0x80000000,0x8000,0x108020,-0x7ff00000,0x100020,-0x7fffffe0,0,0x108000,0x8020,-0x7fef8000,-0x7ff00000,0x8020,0,0x108020,-0x7fefffe0,0x100000,-0x7fff7fe0,-0x7ff00000,-0x7fef8000,0x8000,-0x7ff00000,-0x7fff8000,0x20,-0x7fef7fe0,0x108020,0x20,0x8000,-0x80000000,0x8020,-0x7fef8000,0x100000,-0x7fffffe0,0x100020,-0x7fff7fe0,-0x7fffffe0,0x100020,0x108000,0,-0x7fff8000,0x8020,-0x80000000,-0x7fefffe0,-0x7fef7fe0,0x108000];
+var spfunction3 = [0x208,0x8020200,0,0x8020008,0x8000200,0,0x20208,0x8000200,0x20008,0x8000008,0x8000008,0x20000,0x8020208,0x20008,0x8020000,0x208,0x8000000,0x8,0x8020200,0x200,0x20200,0x8020000,0x8020008,0x20208,0x8000208,0x20200,0x20000,0x8000208,0x8,0x8020208,0x200,0x8000000,0x8020200,0x8000000,0x20008,0x208,0x20000,0x8020200,0x8000200,0,0x200,0x20008,0x8020208,0x8000200,0x8000008,0x200,0,0x8020008,0x8000208,0x20000,0x8000000,0x8020208,0x8,0x20208,0x20200,0x8000008,0x8020000,0x8000208,0x208,0x8020000,0x20208,0x8,0x8020008,0x20200];
+var spfunction4 = [0x802001,0x2081,0x2081,0x80,0x802080,0x800081,0x800001,0x2001,0,0x802000,0x802000,0x802081,0x81,0,0x800080,0x800001,0x1,0x2000,0x800000,0x802001,0x80,0x800000,0x2001,0x2080,0x800081,0x1,0x2080,0x800080,0x2000,0x802080,0x802081,0x81,0x800080,0x800001,0x802000,0x802081,0x81,0,0,0x802000,0x2080,0x800080,0x800081,0x1,0x802001,0x2081,0x2081,0x80,0x802081,0x81,0x1,0x2000,0x800001,0x2001,0x802080,0x800081,0x2001,0x2080,0x800000,0x802001,0x80,0x800000,0x2000,0x802080];
+var spfunction5 = [0x100,0x2080100,0x2080000,0x42000100,0x80000,0x100,0x40000000,0x2080000,0x40080100,0x80000,0x2000100,0x40080100,0x42000100,0x42080000,0x80100,0x40000000,0x2000000,0x40080000,0x40080000,0,0x40000100,0x42080100,0x42080100,0x2000100,0x42080000,0x40000100,0,0x42000000,0x2080100,0x2000000,0x42000000,0x80100,0x80000,0x42000100,0x100,0x2000000,0x40000000,0x2080000,0x42000100,0x40080100,0x2000100,0x40000000,0x42080000,0x2080100,0x40080100,0x100,0x2000000,0x42080000,0x42080100,0x80100,0x42000000,0x42080100,0x2080000,0,0x40080000,0x42000000,0x80100,0x2000100,0x40000100,0x80000,0,0x40080000,0x2080100,0x40000100];
+var spfunction6 = [0x20000010,0x20400000,0x4000,0x20404010,0x20400000,0x10,0x20404010,0x400000,0x20004000,0x404010,0x400000,0x20000010,0x400010,0x20004000,0x20000000,0x4010,0,0x400010,0x20004010,0x4000,0x404000,0x20004010,0x10,0x20400010,0x20400010,0,0x404010,0x20404000,0x4010,0x404000,0x20404000,0x20000000,0x20004000,0x10,0x20400010,0x404000,0x20404010,0x400000,0x4010,0x20000010,0x400000,0x20004000,0x20000000,0x4010,0x20000010,0x20404010,0x404000,0x20400000,0x404010,0x20404000,0,0x20400010,0x10,0x4000,0x20400000,0x404010,0x4000,0x400010,0x20004010,0,0x20404000,0x20000000,0x400010,0x20004010];
+var spfunction7 = [0x200000,0x4200002,0x4000802,0,0x800,0x4000802,0x200802,0x4200800,0x4200802,0x200000,0,0x4000002,0x2,0x4000000,0x4200002,0x802,0x4000800,0x200802,0x200002,0x4000800,0x4000002,0x4200000,0x4200800,0x200002,0x4200000,0x800,0x802,0x4200802,0x200800,0x2,0x4000000,0x200800,0x4000000,0x200800,0x200000,0x4000802,0x4000802,0x4200002,0x4200002,0x2,0x200002,0x4000000,0x4000800,0x200000,0x4200800,0x802,0x200802,0x4200800,0x802,0x4000002,0x4200802,0x4200000,0x200800,0,0x2,0x4200802,0,0x200802,0x4200000,0x800,0x4000002,0x4000800,0x800,0x200002];
+var spfunction8 = [0x10001040,0x1000,0x40000,0x10041040,0x10000000,0x10001040,0x40,0x10000000,0x40040,0x10040000,0x10041040,0x41000,0x10041000,0x41040,0x1000,0x40,0x10040000,0x10000040,0x10001000,0x1040,0x41000,0x40040,0x10040040,0x10041000,0x1040,0,0,0x10040040,0x10000040,0x10001000,0x41040,0x40000,0x41040,0x40000,0x10041000,0x1000,0x40,0x10040040,0x1000,0x41040,0x10001000,0x40,0x10000040,0x10040000,0x10040040,0x10000000,0x40000,0x10001040,0,0x10041040,0x40040,0x10000040,0x10040000,0x10001000,0x10001040,0,0x10041040,0x41000,0x41000,0x1040,0x1040,0x40040,0x10000000,0x10041000];
+
+/**
+ * Create necessary sub keys.
+ *
+ * @param key the 64-bit or 192-bit key.
+ *
+ * @return the expanded keys.
+ */
+function _createKeys(key) {
+  var pc2bytes0  = [0,0x4,0x20000000,0x20000004,0x10000,0x10004,0x20010000,0x20010004,0x200,0x204,0x20000200,0x20000204,0x10200,0x10204,0x20010200,0x20010204],
+      pc2bytes1  = [0,0x1,0x100000,0x100001,0x4000000,0x4000001,0x4100000,0x4100001,0x100,0x101,0x100100,0x100101,0x4000100,0x4000101,0x4100100,0x4100101],
+      pc2bytes2  = [0,0x8,0x800,0x808,0x1000000,0x1000008,0x1000800,0x1000808,0,0x8,0x800,0x808,0x1000000,0x1000008,0x1000800,0x1000808],
+      pc2bytes3  = [0,0x200000,0x8000000,0x8200000,0x2000,0x202000,0x8002000,0x8202000,0x20000,0x220000,0x8020000,0x8220000,0x22000,0x222000,0x8022000,0x8222000],
+      pc2bytes4  = [0,0x40000,0x10,0x40010,0,0x40000,0x10,0x40010,0x1000,0x41000,0x1010,0x41010,0x1000,0x41000,0x1010,0x41010],
+      pc2bytes5  = [0,0x400,0x20,0x420,0,0x400,0x20,0x420,0x2000000,0x2000400,0x2000020,0x2000420,0x2000000,0x2000400,0x2000020,0x2000420],
+      pc2bytes6  = [0,0x10000000,0x80000,0x10080000,0x2,0x10000002,0x80002,0x10080002,0,0x10000000,0x80000,0x10080000,0x2,0x10000002,0x80002,0x10080002],
+      pc2bytes7  = [0,0x10000,0x800,0x10800,0x20000000,0x20010000,0x20000800,0x20010800,0x20000,0x30000,0x20800,0x30800,0x20020000,0x20030000,0x20020800,0x20030800],
+      pc2bytes8  = [0,0x40000,0,0x40000,0x2,0x40002,0x2,0x40002,0x2000000,0x2040000,0x2000000,0x2040000,0x2000002,0x2040002,0x2000002,0x2040002],
+      pc2bytes9  = [0,0x10000000,0x8,0x10000008,0,0x10000000,0x8,0x10000008,0x400,0x10000400,0x408,0x10000408,0x400,0x10000400,0x408,0x10000408],
+      pc2bytes10 = [0,0x20,0,0x20,0x100000,0x100020,0x100000,0x100020,0x2000,0x2020,0x2000,0x2020,0x102000,0x102020,0x102000,0x102020],
+      pc2bytes11 = [0,0x1000000,0x200,0x1000200,0x200000,0x1200000,0x200200,0x1200200,0x4000000,0x5000000,0x4000200,0x5000200,0x4200000,0x5200000,0x4200200,0x5200200],
+      pc2bytes12 = [0,0x1000,0x8000000,0x8001000,0x80000,0x81000,0x8080000,0x8081000,0x10,0x1010,0x8000010,0x8001010,0x80010,0x81010,0x8080010,0x8081010],
+      pc2bytes13 = [0,0x4,0x100,0x104,0,0x4,0x100,0x104,0x1,0x5,0x101,0x105,0x1,0x5,0x101,0x105];
+
+  // how many iterations (1 for des, 3 for triple des)
+  // changed by Paul 16/6/2007 to use Triple DES for 9+ byte keys
+  var iterations = key.length() > 8 ? 3 : 1;
+
+  // stores the return keys
+  var keys = [];
+
+  // now define the left shifts which need to be done
+  var shifts = [0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0];
+
+  var n = 0, tmp;
+  for(var j = 0; j < iterations; j ++) {
+    var left = key.getInt32();
+    var right = key.getInt32();
+
+    tmp = ((left >>> 4) ^ right) & 0x0f0f0f0f;
+    right ^= tmp;
+    left ^= (tmp << 4);
+
+    tmp = ((right >>> -16) ^ left) & 0x0000ffff;
+    left ^= tmp;
+    right ^= (tmp << -16);
+
+    tmp = ((left >>> 2) ^ right) & 0x33333333;
+    right ^= tmp;
+    left ^= (tmp << 2);
+
+    tmp = ((right >>> -16) ^ left) & 0x0000ffff;
+    left ^= tmp;
+    right ^= (tmp << -16);
+
+    tmp = ((left >>> 1) ^ right) & 0x55555555;
+    right ^= tmp;
+    left ^= (tmp << 1);
+
+    tmp = ((right >>> 8) ^ left) & 0x00ff00ff;
+    left ^= tmp;
+    right ^= (tmp << 8);
+
+    tmp = ((left >>> 1) ^ right) & 0x55555555;
+    right ^= tmp;
+    left ^= (tmp << 1);
+
+    // right needs to be shifted and OR'd with last four bits of left
+    tmp = (left << 8) | ((right >>> 20) & 0x000000f0);
+
+    // left needs to be put upside down
+    left = ((right << 24) | ((right << 8) & 0xff0000) |
+      ((right >>> 8) & 0xff00) | ((right >>> 24) & 0xf0));
+    right = tmp;
+
+    // now go through and perform these shifts on the left and right keys
+    for(var i = 0; i < shifts.length; ++i) {
+      //shift the keys either one or two bits to the left
+      if(shifts[i]) {
+        left = (left << 2) | (left >>> 26);
+        right = (right << 2) | (right >>> 26);
+      } else {
+        left = (left << 1) | (left >>> 27);
+        right = (right << 1) | (right >>> 27);
+      }
+      left &= -0xf;
+      right &= -0xf;
+
+      // now apply PC-2, in such a way that E is easier when encrypting or
+      // decrypting this conversion will look like PC-2 except only the last 6
+      // bits of each byte are used rather than 48 consecutive bits and the
+      // order of lines will be according to how the S selection functions will
+      // be applied: S2, S4, S6, S8, S1, S3, S5, S7
+      var lefttmp = (
+        pc2bytes0[left >>> 28] | pc2bytes1[(left >>> 24) & 0xf] |
+        pc2bytes2[(left >>> 20) & 0xf] | pc2bytes3[(left >>> 16) & 0xf] |
+        pc2bytes4[(left >>> 12) & 0xf] | pc2bytes5[(left >>> 8) & 0xf] |
+        pc2bytes6[(left >>> 4) & 0xf]);
+      var righttmp = (
+        pc2bytes7[right >>> 28] | pc2bytes8[(right >>> 24) & 0xf] |
+        pc2bytes9[(right >>> 20) & 0xf] | pc2bytes10[(right >>> 16) & 0xf] |
+        pc2bytes11[(right >>> 12) & 0xf] | pc2bytes12[(right >>> 8) & 0xf] |
+        pc2bytes13[(right >>> 4) & 0xf]);
+      tmp = ((righttmp >>> 16) ^ lefttmp) & 0x0000ffff;
+      keys[n++] = lefttmp ^ tmp;
+      keys[n++] = righttmp ^ (tmp << 16);
+    }
+  }
+
+  return keys;
+}
+
+/**
+ * Updates a single block (1 byte) using DES. The update will either
+ * encrypt or decrypt the block.
+ *
+ * @param keys the expanded keys.
+ * @param input the input block (an array of 32-bit words).
+ * @param output the updated output block.
+ * @param decrypt true to decrypt the block, false to encrypt it.
+ */
+function _updateBlock(keys, input, output, decrypt) {
+  // set up loops for single or triple DES
+  var iterations = keys.length === 32 ? 3 : 9;
+  var looping;
+  if(iterations === 3) {
+    looping = decrypt ? [30, -2, -2] : [0, 32, 2];
+  } else {
+    looping = (decrypt ?
+      [94, 62, -2, 32, 64, 2, 30, -2, -2] :
+      [0, 32, 2, 62, 30, -2, 64, 96, 2]);
+  }
+
+  var tmp;
+
+  var left = input[0];
+  var right = input[1];
+
+  // first each 64 bit chunk of the message must be permuted according to IP
+  tmp = ((left >>> 4) ^ right) & 0x0f0f0f0f;
+  right ^= tmp;
+  left ^= (tmp << 4);
+
+  tmp = ((left >>> 16) ^ right) & 0x0000ffff;
+  right ^= tmp;
+  left ^= (tmp << 16);
+
+  tmp = ((right >>> 2) ^ left) & 0x33333333;
+  left ^= tmp;
+  right ^= (tmp << 2);
+
+  tmp = ((right >>> 8) ^ left) & 0x00ff00ff;
+  left ^= tmp;
+  right ^= (tmp << 8);
+
+  tmp = ((left >>> 1) ^ right) & 0x55555555;
+  right ^= tmp;
+  left ^= (tmp << 1);
+
+  // rotate left 1 bit
+  left = ((left << 1) | (left >>> 31));
+  right = ((right << 1) | (right >>> 31));
+
+  for(var j = 0; j < iterations; j += 3) {
+    var endloop = looping[j + 1];
+    var loopinc = looping[j + 2];
+
+    // now go through and perform the encryption or decryption
+    for(var i = looping[j]; i != endloop; i += loopinc) {
+      var right1 = right ^ keys[i];
+      var right2 = ((right >>> 4) | (right << 28)) ^ keys[i + 1];
+
+      // passing these bytes through the S selection functions
+      tmp = left;
+      left = right;
+      right = tmp ^ (
+        spfunction2[(right1 >>> 24) & 0x3f] |
+        spfunction4[(right1 >>> 16) & 0x3f] |
+        spfunction6[(right1 >>>  8) & 0x3f] |
+        spfunction8[right1 & 0x3f] |
+        spfunction1[(right2 >>> 24) & 0x3f] |
+        spfunction3[(right2 >>> 16) & 0x3f] |
+        spfunction5[(right2 >>>  8) & 0x3f] |
+        spfunction7[right2 & 0x3f]);
+    }
+    // unreverse left and right
+    tmp = left;
+    left = right;
+    right = tmp;
+  }
+
+  // rotate right 1 bit
+  left = ((left >>> 1) | (left << 31));
+  right = ((right >>> 1) | (right << 31));
+
+  // now perform IP-1, which is IP in the opposite direction
+  tmp = ((left >>> 1) ^ right) & 0x55555555;
+  right ^= tmp;
+  left ^= (tmp << 1);
+
+  tmp = ((right >>> 8) ^ left) & 0x00ff00ff;
+  left ^= tmp;
+  right ^= (tmp << 8);
+
+  tmp = ((right >>> 2) ^ left) & 0x33333333;
+  left ^= tmp;
+  right ^= (tmp << 2);
+
+  tmp = ((left >>> 16) ^ right) & 0x0000ffff;
+  right ^= tmp;
+  left ^= (tmp << 16);
+
+  tmp = ((left >>> 4) ^ right) & 0x0f0f0f0f;
+  right ^= tmp;
+  left ^= (tmp << 4);
+
+  output[0] = left;
+  output[1] = right;
+}
+
+/**
+ * Deprecated. Instead, use:
+ *
+ * forge.cipher.createCipher('DES-<mode>', key);
+ * forge.cipher.createDecipher('DES-<mode>', key);
+ *
+ * Creates a deprecated DES cipher object. This object's mode will default to
+ * CBC (cipher-block-chaining).
+ *
+ * The key may be given as a binary-encoded string of bytes or a byte buffer.
+ *
+ * @param options the options to use.
+ *          key the symmetric key to use (64 or 192 bits).
+ *          output the buffer to write to.
+ *          decrypt true for decryption, false for encryption.
+ *          mode the cipher mode to use (default: 'CBC').
+ *
+ * @return the cipher.
+ */
+function _createCipher(options) {
+  options = options || {};
+  var mode = (options.mode || 'CBC').toUpperCase();
+  var algorithm = 'DES-' + mode;
+
+  var cipher;
+  if(options.decrypt) {
+    cipher = forge.cipher.createDecipher(algorithm, options.key);
+  } else {
+    cipher = forge.cipher.createCipher(algorithm, options.key);
+  }
+
+  // backwards compatible start API
+  var start = cipher.start;
+  cipher.start = function(iv, options) {
+    // backwards compatibility: support second arg as output buffer
+    var output = null;
+    if(options instanceof forge.util.ByteBuffer) {
+      output = options;
+      options = {};
+    }
+    options = options || {};
+    options.output = output;
+    options.iv = iv;
+    start.call(cipher, options);
+  };
+
+  return cipher;
+}
+
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'des';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(
+  ['require', 'module', './cipher', './cipherModes', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/forge.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/forge.js
new file mode 100644
index 0000000..b314e22
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/forge.js
@@ -0,0 +1,92 @@
+/**
+ * Node.js module for Forge.
+ *
+ * @author Dave Longley
+ *
+ * Copyright 2011-2014 Digital Bazaar, Inc.
+ */
+(function() {
+var name = 'forge';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      // set to true to disable native code if even it's available
+      forge = {disableNativeCode: false};
+    }
+    return;
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    });
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge;
+  };
+  // set to true to disable native code if even it's available
+  module.exports.disableNativeCode = false;
+  module.exports(module.exports);
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define([
+  'require',
+  'module',
+  './aes',
+  './aesCipherSuites',
+  './asn1',
+  './cipher',
+  './cipherModes',
+  './debug',
+  './des',
+  './hmac',
+  './kem',
+  './log',
+  './md',
+  './mgf1',
+  './pbkdf2',
+  './pem',
+  './pkcs7',
+  './pkcs1',
+  './pkcs12',
+  './pki',
+  './prime',
+  './prng',
+  './pss',
+  './random',
+  './rc2',
+  './ssh',
+  './task',
+  './tls',
+  './util'
+], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/form.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/form.js
new file mode 100644
index 0000000..62d4424
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/form.js
@@ -0,0 +1,157 @@
+/**
+ * Functions for manipulating web forms.
+ *
+ * @author David I. Lehn <dlehn@digitalbazaar.com>
+ * @author Dave Longley
+ * @author Mike Johnson
+ *
+ * Copyright (c) 2011-2014 Digital Bazaar, Inc. All rights reserved.
+ */
+(function($) {
+
+/**
+ * The form namespace.
+ */
+var form = {};
+
+/**
+ * Regex for parsing a single name property (handles array brackets).
+ */
+var _regex = /(.*?)\[(.*?)\]/g;
+
+/**
+ * Parses a single name property into an array with the name and any
+ * array indices.
+ *
+ * @param name the name to parse.
+ *
+ * @return the array of the name and its array indices in order.
+ */
+var _parseName = function(name) {
+  var rval = [];
+
+  var matches;
+  while(!!(matches = _regex.exec(name))) {
+    if(matches[1].length > 0) {
+      rval.push(matches[1]);
+    }
+    if(matches.length >= 2) {
+      rval.push(matches[2]);
+    }
+  }
+  if(rval.length === 0) {
+    rval.push(name);
+  }
+
+  return rval;
+};
+
+/**
+ * Adds a field from the given form to the given object.
+ *
+ * @param obj the object.
+ * @param names the field as an array of object property names.
+ * @param value the value of the field.
+ * @param dict a dictionary of names to replace.
+ */
+var _addField = function(obj, names, value, dict) {
+  // combine array names that fall within square brackets
+  var tmp = [];
+  for(var i = 0; i < names.length; ++i) {
+    // check name for starting square bracket but no ending one
+    var name = names[i];
+    if(name.indexOf('[') !== -1 && name.indexOf(']') === -1 &&
+      i < names.length - 1) {
+      do {
+        name += '.' + names[++i];
+      } while(i < names.length - 1 && names[i].indexOf(']') === -1);
+    }
+    tmp.push(name);
+  }
+  names = tmp;
+
+  // split out array indexes
+  var tmp = [];
+  $.each(names, function(n, name) {
+    tmp = tmp.concat(_parseName(name));
+  });
+  names = tmp;
+
+  // iterate over object property names until value is set
+  $.each(names, function(n, name) {
+    // do dictionary name replacement
+    if(dict && name.length !== 0 && name in dict) {
+       name = dict[name];
+    }
+
+    // blank name indicates appending to an array, set name to
+    // new last index of array
+    if(name.length === 0) {
+       name = obj.length;
+    }
+
+    // value already exists, append value
+    if(obj[name]) {
+      // last name in the field
+      if(n == names.length - 1) {
+        // more than one value, so convert into an array
+        if(!$.isArray(obj[name])) {
+          obj[name] = [obj[name]];
+        }
+        obj[name].push(value);
+      } else {
+        // not last name, go deeper into object
+        obj = obj[name];
+      }
+    } else if(n == names.length - 1) {
+      // new value, last name in the field, set value
+      obj[name] = value;
+    } else {
+      // new value, not last name, go deeper
+      // get next name
+      var next = names[n + 1];
+
+      // blank next value indicates array-appending, so create array
+      if(next.length === 0) {
+         obj[name] = [];
+      } else {
+        // if next name is a number create an array, otherwise a map
+        var isNum = ((next - 0) == next && next.length > 0);
+        obj[name] = isNum ? [] : {};
+      }
+      obj = obj[name];
+    }
+  });
+};
+
+/**
+ * Serializes a form to a JSON object. Object properties will be separated
+ * using the given separator (defaults to '.') and by square brackets.
+ *
+ * @param input the jquery form to serialize.
+ * @param sep the object-property separator (defaults to '.').
+ * @param dict a dictionary of names to replace (name=replace).
+ *
+ * @return the JSON-serialized form.
+ */
+form.serialize = function(input, sep, dict) {
+  var rval = {};
+
+  // add all fields in the form to the object
+  sep = sep || '.';
+  $.each(input.serializeArray(), function() {
+    _addField(rval, this.name.split(sep), this.value || '', dict);
+  });
+
+  return rval;
+};
+
+/**
+ * The forge namespace and form API.
+ */
+if(typeof forge === 'undefined') {
+  forge = {};
+}
+forge.form = form;
+
+})(jQuery);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/hmac.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/hmac.js
new file mode 100644
index 0000000..eee58bc
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/hmac.js
@@ -0,0 +1,200 @@
+/**
+ * Hash-based Message Authentication Code implementation. Requires a message
+ * digest object that can be obtained, for example, from forge.md.sha1 or
+ * forge.md.md5.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2012 Digital Bazaar, Inc. All rights reserved.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+/* HMAC API */
+var hmac = forge.hmac = forge.hmac || {};
+
+/**
+ * Creates an HMAC object that uses the given message digest object.
+ *
+ * @return an HMAC object.
+ */
+hmac.create = function() {
+  // the hmac key to use
+  var _key = null;
+
+  // the message digest to use
+  var _md = null;
+
+  // the inner padding
+  var _ipadding = null;
+
+  // the outer padding
+  var _opadding = null;
+
+  // hmac context
+  var ctx = {};
+
+  /**
+   * Starts or restarts the HMAC with the given key and message digest.
+   *
+   * @param md the message digest to use, null to reuse the previous one,
+   *           a string to use builtin 'sha1', 'md5', 'sha256'.
+   * @param key the key to use as a string, array of bytes, byte buffer,
+   *           or null to reuse the previous key.
+   */
+  ctx.start = function(md, key) {
+    if(md !== null) {
+      if(typeof md === 'string') {
+        // create builtin message digest
+        md = md.toLowerCase();
+        if(md in forge.md.algorithms) {
+          _md = forge.md.algorithms[md].create();
+        } else {
+          throw new Error('Unknown hash algorithm "' + md + '"');
+        }
+      } else {
+        // store message digest
+        _md = md;
+      }
+    }
+
+    if(key === null) {
+      // reuse previous key
+      key = _key;
+    } else {
+      if(typeof key === 'string') {
+        // convert string into byte buffer
+        key = forge.util.createBuffer(key);
+      } else if(forge.util.isArray(key)) {
+        // convert byte array into byte buffer
+        var tmp = key;
+        key = forge.util.createBuffer();
+        for(var i = 0; i < tmp.length; ++i) {
+          key.putByte(tmp[i]);
+        }
+      }
+
+      // if key is longer than blocksize, hash it
+      var keylen = key.length();
+      if(keylen > _md.blockLength) {
+        _md.start();
+        _md.update(key.bytes());
+        key = _md.digest();
+      }
+
+      // mix key into inner and outer padding
+      // ipadding = [0x36 * blocksize] ^ key
+      // opadding = [0x5C * blocksize] ^ key
+      _ipadding = forge.util.createBuffer();
+      _opadding = forge.util.createBuffer();
+      keylen = key.length();
+      for(var i = 0; i < keylen; ++i) {
+        var tmp = key.at(i);
+        _ipadding.putByte(0x36 ^ tmp);
+        _opadding.putByte(0x5C ^ tmp);
+      }
+
+      // if key is shorter than blocksize, add additional padding
+      if(keylen < _md.blockLength) {
+        var tmp = _md.blockLength - keylen;
+        for(var i = 0; i < tmp; ++i) {
+          _ipadding.putByte(0x36);
+          _opadding.putByte(0x5C);
+        }
+      }
+      _key = key;
+      _ipadding = _ipadding.bytes();
+      _opadding = _opadding.bytes();
+    }
+
+    // digest is done like so: hash(opadding | hash(ipadding | message))
+
+    // prepare to do inner hash
+    // hash(ipadding | message)
+    _md.start();
+    _md.update(_ipadding);
+  };
+
+  /**
+   * Updates the HMAC with the given message bytes.
+   *
+   * @param bytes the bytes to update with.
+   */
+  ctx.update = function(bytes) {
+    _md.update(bytes);
+  };
+
+  /**
+   * Produces the Message Authentication Code (MAC).
+   *
+   * @return a byte buffer containing the digest value.
+   */
+  ctx.getMac = function() {
+    // digest is done like so: hash(opadding | hash(ipadding | message))
+    // here we do the outer hashing
+    var inner = _md.digest().bytes();
+    _md.start();
+    _md.update(_opadding);
+    _md.update(inner);
+    return _md.digest();
+  };
+  // alias for getMac
+  ctx.digest = ctx.getMac;
+
+  return ctx;
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'hmac';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './md', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/http.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/http.js
new file mode 100644
index 0000000..fa01aed
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/http.js
@@ -0,0 +1,1369 @@
+/**
+ * HTTP client-side implementation that uses forge.net sockets.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2014 Digital Bazaar, Inc. All rights reserved.
+ */
+(function() {
+
+// define http namespace
+var http = {};
+
+// logging category
+var cat = 'forge.http';
+
+// add array of clients to debug storage
+if(forge.debug) {
+  forge.debug.set('forge.http', 'clients', []);
+}
+
+// normalizes an http header field name
+var _normalize = function(name) {
+  return name.toLowerCase().replace(/(^.)|(-.)/g,
+    function(a){return a.toUpperCase();});
+};
+
+/**
+ * Gets the local storage ID for the given client.
+ *
+ * @param client the client to get the local storage ID for.
+ *
+ * @return the local storage ID to use.
+ */
+var _getStorageId = function(client) {
+  // TODO: include browser in ID to avoid sharing cookies between
+  // browsers (if this is undesirable)
+  // navigator.userAgent
+  return 'forge.http.' +
+    client.url.scheme + '.' +
+    client.url.host + '.' +
+    client.url.port;
+};
+
+/**
+ * Loads persistent cookies from disk for the given client.
+ *
+ * @param client the client.
+ */
+var _loadCookies = function(client) {
+  if(client.persistCookies) {
+    try {
+      var cookies = forge.util.getItem(
+        client.socketPool.flashApi,
+        _getStorageId(client), 'cookies');
+      client.cookies = cookies || {};
+    } catch(ex) {
+      // no flash storage available, just silently fail
+      // TODO: i assume we want this logged somewhere or
+      // should it actually generate an error
+      //forge.log.error(cat, ex);
+    }
+  }
+};
+
+/**
+ * Saves persistent cookies on disk for the given client.
+ *
+ * @param client the client.
+ */
+var _saveCookies = function(client) {
+  if(client.persistCookies) {
+    try {
+      forge.util.setItem(
+        client.socketPool.flashApi,
+        _getStorageId(client), 'cookies', client.cookies);
+    } catch(ex) {
+      // no flash storage available, just silently fail
+      // TODO: i assume we want this logged somewhere or
+      // should it actually generate an error
+      //forge.log.error(cat, ex);
+    }
+  }
+
+  // FIXME: remove me
+  _loadCookies(client);
+};
+
+/**
+ * Clears persistent cookies on disk for the given client.
+ *
+ * @param client the client.
+ */
+var _clearCookies = function(client) {
+  if(client.persistCookies) {
+    try {
+      // only thing stored is 'cookies', so clear whole storage
+      forge.util.clearItems(
+        client.socketPool.flashApi,
+        _getStorageId(client));
+    } catch(ex) {
+      // no flash storage available, just silently fail
+      // TODO: i assume we want this logged somewhere or
+      // should it actually generate an error
+      //forge.log.error(cat, ex);
+    }
+  }
+};
+
+/**
+ * Connects and sends a request.
+ *
+ * @param client the http client.
+ * @param socket the socket to use.
+ */
+var _doRequest = function(client, socket) {
+  if(socket.isConnected()) {
+    // already connected
+    socket.options.request.connectTime = +new Date();
+    socket.connected({
+      type: 'connect',
+      id: socket.id
+    });
+  } else {
+    // connect
+    socket.options.request.connectTime = +new Date();
+    socket.connect({
+      host: client.url.host,
+      port: client.url.port,
+      policyPort: client.policyPort,
+      policyUrl: client.policyUrl
+    });
+  }
+};
+
+/**
+ * Handles the next request or marks a socket as idle.
+ *
+ * @param client the http client.
+ * @param socket the socket.
+ */
+var _handleNextRequest = function(client, socket) {
+  // clear buffer
+  socket.buffer.clear();
+
+  // get pending request
+  var pending = null;
+  while(pending === null && client.requests.length > 0) {
+    pending = client.requests.shift();
+    if(pending.request.aborted) {
+      pending = null;
+    }
+  }
+
+  // mark socket idle if no pending requests
+  if(pending === null) {
+    if(socket.options !== null) {
+      socket.options = null;
+    }
+    client.idle.push(socket);
+  } else {
+    // handle pending request, allow 1 retry
+    socket.retries = 1;
+    socket.options = pending;
+    _doRequest(client, socket);
+  }
+};
+
+/**
+ * Sets up a socket for use with an http client.
+ *
+ * @param client the parent http client.
+ * @param socket the socket to set up.
+ * @param tlsOptions if the socket must use TLS, the TLS options.
+ */
+var _initSocket = function(client, socket, tlsOptions) {
+  // no socket options yet
+  socket.options = null;
+
+  // set up handlers
+  socket.connected = function(e) {
+    // socket primed by caching TLS session, handle next request
+    if(socket.options === null) {
+      _handleNextRequest(client, socket);
+    } else {
+      // socket in use
+      var request = socket.options.request;
+      request.connectTime = +new Date() - request.connectTime;
+      e.socket = socket;
+      socket.options.connected(e);
+      if(request.aborted) {
+        socket.close();
+      } else {
+        var out = request.toString();
+        if(request.body) {
+          out += request.body;
+        }
+        request.time = +new Date();
+        socket.send(out);
+        request.time = +new Date() - request.time;
+        socket.options.response.time = +new Date();
+        socket.sending = true;
+      }
+    }
+  };
+  socket.closed = function(e) {
+    if(socket.sending) {
+      socket.sending = false;
+      if(socket.retries > 0) {
+        --socket.retries;
+        _doRequest(client, socket);
+      } else {
+        // error, closed during send
+        socket.error({
+          id: socket.id,
+          type: 'ioError',
+          message: 'Connection closed during send. Broken pipe.',
+          bytesAvailable: 0
+        });
+      }
+    } else {
+      // handle unspecified content-length transfer
+      var response = socket.options.response;
+      if(response.readBodyUntilClose) {
+        response.time = +new Date() - response.time;
+        response.bodyReceived = true;
+        socket.options.bodyReady({
+          request: socket.options.request,
+          response: response,
+          socket: socket
+        });
+      }
+      socket.options.closed(e);
+      _handleNextRequest(client, socket);
+    }
+  };
+  socket.data = function(e) {
+    socket.sending = false;
+    var request = socket.options.request;
+    if(request.aborted) {
+      socket.close();
+    } else {
+      // receive all bytes available
+      var response = socket.options.response;
+      var bytes = socket.receive(e.bytesAvailable);
+      if(bytes !== null) {
+        // receive header and then body
+        socket.buffer.putBytes(bytes);
+        if(!response.headerReceived) {
+          response.readHeader(socket.buffer);
+          if(response.headerReceived) {
+            socket.options.headerReady({
+              request: socket.options.request,
+              response: response,
+              socket: socket
+            });
+          }
+        }
+        if(response.headerReceived && !response.bodyReceived) {
+          response.readBody(socket.buffer);
+        }
+        if(response.bodyReceived) {
+          socket.options.bodyReady({
+            request: socket.options.request,
+            response: response,
+            socket: socket
+          });
+          // close connection if requested or by default on http/1.0
+          var value = response.getField('Connection') || '';
+          if(value.indexOf('close') != -1 ||
+            (response.version === 'HTTP/1.0' &&
+            response.getField('Keep-Alive') === null)) {
+            socket.close();
+          } else {
+            _handleNextRequest(client, socket);
+          }
+        }
+      }
+    }
+  };
+  socket.error = function(e) {
+    // do error callback, include request
+    socket.options.error({
+      type: e.type,
+      message: e.message,
+      request: socket.options.request,
+      response: socket.options.response,
+      socket: socket
+    });
+    socket.close();
+  };
+
+  // wrap socket for TLS
+  if(tlsOptions) {
+    socket = forge.tls.wrapSocket({
+      sessionId: null,
+      sessionCache: {},
+      caStore: tlsOptions.caStore,
+      cipherSuites: tlsOptions.cipherSuites,
+      socket: socket,
+      virtualHost: tlsOptions.virtualHost,
+      verify: tlsOptions.verify,
+      getCertificate: tlsOptions.getCertificate,
+      getPrivateKey: tlsOptions.getPrivateKey,
+      getSignature: tlsOptions.getSignature,
+      deflate: tlsOptions.deflate || null,
+      inflate: tlsOptions.inflate || null
+    });
+
+    socket.options = null;
+    socket.buffer = forge.util.createBuffer();
+    client.sockets.push(socket);
+    if(tlsOptions.prime) {
+      // prime socket by connecting and caching TLS session, will do
+      // next request from there
+      socket.connect({
+        host: client.url.host,
+        port: client.url.port,
+        policyPort: client.policyPort,
+        policyUrl: client.policyUrl
+      });
+    } else {
+      // do not prime socket, just add as idle
+      client.idle.push(socket);
+    }
+  } else {
+    // no need to prime non-TLS sockets
+    socket.buffer = forge.util.createBuffer();
+    client.sockets.push(socket);
+    client.idle.push(socket);
+  }
+};
+
+/**
+ * Checks to see if the given cookie has expired. If the cookie's max-age
+ * plus its created time is less than the time now, it has expired, unless
+ * its max-age is set to -1 which indicates it will never expire.
+ *
+ * @param cookie the cookie to check.
+ *
+ * @return true if it has expired, false if not.
+ */
+var _hasCookieExpired = function(cookie) {
+  var rval = false;
+
+  if(cookie.maxAge !== -1) {
+    var now = _getUtcTime(new Date());
+    var expires = cookie.created + cookie.maxAge;
+    if(expires <= now) {
+      rval = true;
+    }
+  }
+
+  return rval;
+};
+
+/**
+ * Adds cookies in the given client to the given request.
+ *
+ * @param client the client.
+ * @param request the request.
+ */
+var _writeCookies = function(client, request) {
+  var expired = [];
+  var url = client.url;
+  var cookies = client.cookies;
+  for(var name in cookies) {
+    // get cookie paths
+    var paths = cookies[name];
+    for(var p in paths) {
+      var cookie = paths[p];
+      if(_hasCookieExpired(cookie)) {
+        // store for clean up
+        expired.push(cookie);
+      } else if(request.path.indexOf(cookie.path) === 0) {
+        // path or path's ancestor must match cookie.path
+        request.addCookie(cookie);
+      }
+    }
+  }
+
+  // clean up expired cookies
+  for(var i = 0; i < expired.length; ++i) {
+    var cookie = expired[i];
+    client.removeCookie(cookie.name, cookie.path);
+  }
+};
+
+/**
+ * Gets cookies from the given response and adds the to the given client.
+ *
+ * @param client the client.
+ * @param response the response.
+ */
+var _readCookies = function(client, response) {
+  var cookies = response.getCookies();
+  for(var i = 0; i < cookies.length; ++i) {
+    try {
+      client.setCookie(cookies[i]);
+    } catch(ex) {
+      // ignore failure to add other-domain, etc. cookies
+    }
+  }
+};
+
+/**
+ * Creates an http client that uses forge.net sockets as a backend and
+ * forge.tls for security.
+ *
+ * @param options:
+ *   url: the url to connect to (scheme://host:port).
+ *     socketPool: the flash socket pool to use.
+ *   policyPort: the flash policy port to use (if other than the
+ *     socket pool default), use 0 for flash default.
+ *   policyUrl: the flash policy file URL to use (if provided will
+ *     be used instead of a policy port).
+ *   connections: number of connections to use to handle requests.
+ *   caCerts: an array of certificates to trust for TLS, certs may
+ *     be PEM-formatted or cert objects produced via forge.pki.
+ *   cipherSuites: an optional array of cipher suites to use,
+ *     see forge.tls.CipherSuites.
+ *   virtualHost: the virtual server name to use in a TLS SNI
+ *     extension, if not provided the url host will be used.
+ *   verify: a custom TLS certificate verify callback to use.
+ *   getCertificate: an optional callback used to get a client-side
+ *     certificate (see forge.tls for details).
+ *   getPrivateKey: an optional callback used to get a client-side
+ *     private key (see forge.tls for details).
+ *   getSignature: an optional callback used to get a client-side
+ *     signature (see forge.tls for details).
+ *   persistCookies: true to use persistent cookies via flash local
+ *     storage, false to only keep cookies in javascript.
+ *   primeTlsSockets: true to immediately connect TLS sockets on
+ *     their creation so that they will cache TLS sessions for reuse.
+ *
+ * @return the client.
+ */
+http.createClient = function(options) {
+  // create CA store to share with all TLS connections
+  var caStore = null;
+  if(options.caCerts) {
+    caStore = forge.pki.createCaStore(options.caCerts);
+  }
+
+  // get scheme, host, and port from url
+  options.url = (options.url ||
+    window.location.protocol + '//' + window.location.host);
+  var url = http.parseUrl(options.url);
+  if(!url) {
+    var error = new Error('Invalid url.');
+    error.details = {url: options.url};
+    throw error;
+  }
+
+  // default to 1 connection
+  options.connections = options.connections || 1;
+
+  // create client
+  var sp = options.socketPool;
+  var client = {
+    // url
+    url: url,
+    // socket pool
+    socketPool: sp,
+    // the policy port to use
+    policyPort: options.policyPort,
+    // policy url to use
+    policyUrl: options.policyUrl,
+    // queue of requests to service
+    requests: [],
+    // all sockets
+    sockets: [],
+    // idle sockets
+    idle: [],
+    // whether or not the connections are secure
+    secure: (url.scheme === 'https'),
+    // cookie jar (key'd off of name and then path, there is only 1 domain
+    // and one setting for secure per client so name+path is unique)
+    cookies: {},
+    // default to flash storage of cookies
+    persistCookies: (typeof(options.persistCookies) === 'undefined') ?
+      true : options.persistCookies
+  };
+
+  // add client to debug storage
+  if(forge.debug) {
+    forge.debug.get('forge.http', 'clients').push(client);
+  }
+
+  // load cookies from disk
+  _loadCookies(client);
+
+  /**
+   * A default certificate verify function that checks a certificate common
+   * name against the client's URL host.
+   *
+   * @param c the TLS connection.
+   * @param verified true if cert is verified, otherwise alert number.
+   * @param depth the chain depth.
+   * @param certs the cert chain.
+   *
+   * @return true if verified and the common name matches the host, error
+   *         otherwise.
+   */
+  var _defaultCertificateVerify = function(c, verified, depth, certs) {
+    if(depth === 0 && verified === true) {
+      // compare common name to url host
+      var cn = certs[depth].subject.getField('CN');
+      if(cn === null || client.url.host !== cn.value) {
+        verified = {
+          message: 'Certificate common name does not match url host.'
+        };
+      }
+    }
+    return verified;
+  };
+
+  // determine if TLS is used
+  var tlsOptions = null;
+  if(client.secure) {
+    tlsOptions = {
+      caStore: caStore,
+      cipherSuites: options.cipherSuites || null,
+      virtualHost: options.virtualHost || url.host,
+      verify: options.verify || _defaultCertificateVerify,
+      getCertificate: options.getCertificate || null,
+      getPrivateKey: options.getPrivateKey || null,
+      getSignature: options.getSignature || null,
+      prime: options.primeTlsSockets || false
+    };
+
+    // if socket pool uses a flash api, then add deflate support to TLS
+    if(sp.flashApi !== null) {
+      tlsOptions.deflate = function(bytes) {
+        // strip 2 byte zlib header and 4 byte trailer
+        return forge.util.deflate(sp.flashApi, bytes, true);
+      };
+      tlsOptions.inflate = function(bytes) {
+        return forge.util.inflate(sp.flashApi, bytes, true);
+      };
+    }
+  }
+
+  // create and initialize sockets
+  for(var i = 0; i < options.connections; ++i) {
+    _initSocket(client, sp.createSocket(), tlsOptions);
+  }
+
+  /**
+   * Sends a request. A method 'abort' will be set on the request that
+   * can be called to attempt to abort the request.
+   *
+   * @param options:
+   *          request: the request to send.
+   *          connected: a callback for when the connection is open.
+   *          closed: a callback for when the connection is closed.
+   *          headerReady: a callback for when the response header arrives.
+   *          bodyReady: a callback for when the response body arrives.
+   *          error: a callback for if an error occurs.
+   */
+  client.send = function(options) {
+    // add host header if not set
+    if(options.request.getField('Host') === null) {
+      options.request.setField('Host', client.url.fullHost);
+    }
+
+    // set default dummy handlers
+    var opts = {};
+    opts.request = options.request;
+    opts.connected = options.connected || function(){};
+    opts.closed = options.close || function(){};
+    opts.headerReady = function(e) {
+      // read cookies
+      _readCookies(client, e.response);
+      if(options.headerReady) {
+        options.headerReady(e);
+      }
+    };
+    opts.bodyReady = options.bodyReady || function(){};
+    opts.error = options.error || function(){};
+
+    // create response
+    opts.response = http.createResponse();
+    opts.response.time = 0;
+    opts.response.flashApi = client.socketPool.flashApi;
+    opts.request.flashApi = client.socketPool.flashApi;
+
+    // create abort function
+    opts.request.abort = function() {
+      // set aborted, clear handlers
+      opts.request.aborted = true;
+      opts.connected = function(){};
+      opts.closed = function(){};
+      opts.headerReady = function(){};
+      opts.bodyReady = function(){};
+      opts.error = function(){};
+    };
+
+    // add cookies to request
+    _writeCookies(client, opts.request);
+
+    // queue request options if there are no idle sockets
+    if(client.idle.length === 0) {
+      client.requests.push(opts);
+    } else {
+      // use an idle socket, prefer an idle *connected* socket first
+      var socket = null;
+      var len = client.idle.length;
+      for(var i = 0; socket === null && i < len; ++i) {
+        socket = client.idle[i];
+        if(socket.isConnected()) {
+          client.idle.splice(i, 1);
+        } else {
+          socket = null;
+        }
+      }
+      // no connected socket available, get unconnected socket
+      if(socket === null) {
+        socket = client.idle.pop();
+      }
+      socket.options = opts;
+      _doRequest(client, socket);
+    }
+  };
+
+  /**
+   * Destroys this client.
+   */
+  client.destroy = function() {
+    // clear pending requests, close and destroy sockets
+    client.requests = [];
+    for(var i = 0; i < client.sockets.length; ++i) {
+      client.sockets[i].close();
+      client.sockets[i].destroy();
+    }
+    client.socketPool = null;
+    client.sockets = [];
+    client.idle = [];
+  };
+
+  /**
+   * Sets a cookie for use with all connections made by this client. Any
+   * cookie with the same name will be replaced. If the cookie's value
+   * is undefined, null, or the blank string, the cookie will be removed.
+   *
+   * If the cookie's domain doesn't match this client's url host or the
+   * cookie's secure flag doesn't match this client's url scheme, then
+   * setting the cookie will fail with an exception.
+   *
+   * @param cookie the cookie with parameters:
+   *   name: the name of the cookie.
+   *   value: the value of the cookie.
+   *   comment: an optional comment string.
+   *   maxAge: the age of the cookie in seconds relative to created time.
+   *   secure: true if the cookie must be sent over a secure protocol.
+   *   httpOnly: true to restrict access to the cookie from javascript
+   *     (inaffective since the cookies are stored in javascript).
+   *   path: the path for the cookie.
+   *   domain: optional domain the cookie belongs to (must start with dot).
+   *   version: optional version of the cookie.
+   *   created: creation time, in UTC seconds, of the cookie.
+   */
+  client.setCookie = function(cookie) {
+    var rval;
+    if(typeof(cookie.name) !== 'undefined') {
+      if(cookie.value === null || typeof(cookie.value) === 'undefined' ||
+        cookie.value === '') {
+        // remove cookie
+        rval = client.removeCookie(cookie.name, cookie.path);
+      } else {
+        // set cookie defaults
+        cookie.comment = cookie.comment || '';
+        cookie.maxAge = cookie.maxAge || 0;
+        cookie.secure = (typeof(cookie.secure) === 'undefined') ?
+          true : cookie.secure;
+        cookie.httpOnly = cookie.httpOnly || true;
+        cookie.path = cookie.path || '/';
+        cookie.domain = cookie.domain || null;
+        cookie.version = cookie.version || null;
+        cookie.created = _getUtcTime(new Date());
+
+        // do secure check
+        if(cookie.secure !== client.secure) {
+          var error = new Error('Http client url scheme is incompatible ' +
+            'with cookie secure flag.');
+          error.url = client.url;
+          error.cookie = cookie;
+          throw error;
+        }
+        // make sure url host is within cookie.domain
+        if(!http.withinCookieDomain(client.url, cookie)) {
+          var error = new Error('Http client url scheme is incompatible ' +
+            'with cookie secure flag.');
+          error.url = client.url;
+          error.cookie = cookie;
+          throw error;
+        }
+
+        // add new cookie
+        if(!(cookie.name in client.cookies)) {
+          client.cookies[cookie.name] = {};
+        }
+        client.cookies[cookie.name][cookie.path] = cookie;
+        rval = true;
+
+        // save cookies
+        _saveCookies(client);
+      }
+    }
+
+    return rval;
+  };
+
+  /**
+   * Gets a cookie by its name.
+   *
+   * @param name the name of the cookie to retrieve.
+   * @param path an optional path for the cookie (if there are multiple
+   *          cookies with the same name but different paths).
+   *
+   * @return the cookie or null if not found.
+   */
+  client.getCookie = function(name, path) {
+    var rval = null;
+    if(name in client.cookies) {
+      var paths = client.cookies[name];
+
+      // get path-specific cookie
+      if(path) {
+        if(path in paths) {
+          rval = paths[path];
+        }
+      } else {
+        // get first cookie
+        for(var p in paths) {
+          rval = paths[p];
+          break;
+        }
+      }
+    }
+    return rval;
+  };
+
+  /**
+   * Removes a cookie.
+   *
+   * @param name the name of the cookie to remove.
+   * @param path an optional path for the cookie (if there are multiple
+   *          cookies with the same name but different paths).
+   *
+   * @return true if a cookie was removed, false if not.
+   */
+  client.removeCookie = function(name, path) {
+    var rval = false;
+    if(name in client.cookies) {
+      // delete the specific path
+      if(path) {
+        var paths = client.cookies[name];
+        if(path in paths) {
+          rval = true;
+          delete client.cookies[name][path];
+          // clean up entry if empty
+          var empty = true;
+          for(var i in client.cookies[name]) {
+            empty = false;
+            break;
+          }
+          if(empty) {
+            delete client.cookies[name];
+          }
+        }
+      } else {
+        // delete all cookies with the given name
+        rval = true;
+        delete client.cookies[name];
+      }
+    }
+    if(rval) {
+      // save cookies
+      _saveCookies(client);
+    }
+    return rval;
+  };
+
+  /**
+   * Clears all cookies stored in this client.
+   */
+  client.clearCookies = function() {
+    client.cookies = {};
+    _clearCookies(client);
+  };
+
+  if(forge.log) {
+    forge.log.debug('forge.http', 'created client', options);
+  }
+
+  return client;
+};
+
+/**
+ * Trims the whitespace off of the beginning and end of a string.
+ *
+ * @param str the string to trim.
+ *
+ * @return the trimmed string.
+ */
+var _trimString = function(str) {
+  return str.replace(/^\s*/, '').replace(/\s*$/, '');
+};
+
+/**
+ * Creates an http header object.
+ *
+ * @return the http header object.
+ */
+var _createHeader = function() {
+  var header = {
+    fields: {},
+    setField: function(name, value) {
+      // normalize field name, trim value
+      header.fields[_normalize(name)] = [_trimString('' + value)];
+    },
+    appendField: function(name, value) {
+      name = _normalize(name);
+      if(!(name in header.fields)) {
+        header.fields[name] = [];
+      }
+      header.fields[name].push(_trimString('' + value));
+    },
+    getField: function(name, index) {
+      var rval = null;
+      name = _normalize(name);
+      if(name in header.fields) {
+        index = index || 0;
+        rval = header.fields[name][index];
+      }
+      return rval;
+    }
+  };
+  return header;
+};
+
+/**
+ * Gets the time in utc seconds given a date.
+ *
+ * @param d the date to use.
+ *
+ * @return the time in utc seconds.
+ */
+var _getUtcTime = function(d) {
+  var utc = +d + d.getTimezoneOffset() * 60000;
+  return Math.floor(+new Date() / 1000);
+};
+
+/**
+ * Creates an http request.
+ *
+ * @param options:
+ *          version: the version.
+ *          method: the method.
+ *          path: the path.
+ *          body: the body.
+ *          headers: custom header fields to add,
+ *            eg: [{'Content-Length': 0}].
+ *
+ * @return the http request.
+ */
+http.createRequest = function(options) {
+  options = options || {};
+  var request = _createHeader();
+  request.version = options.version || 'HTTP/1.1';
+  request.method = options.method || null;
+  request.path = options.path || null;
+  request.body = options.body || null;
+  request.bodyDeflated = false;
+  request.flashApi = null;
+
+  // add custom headers
+  var headers = options.headers || [];
+  if(!forge.util.isArray(headers)) {
+    headers = [headers];
+  }
+  for(var i = 0; i < headers.length; ++i) {
+    for(var name in headers[i]) {
+      request.appendField(name, headers[i][name]);
+    }
+  }
+
+  /**
+   * Adds a cookie to the request 'Cookie' header.
+   *
+   * @param cookie a cookie to add.
+   */
+  request.addCookie = function(cookie) {
+    var value = '';
+    var field = request.getField('Cookie');
+    if(field !== null) {
+      // separate cookies by semi-colons
+      value = field + '; ';
+    }
+
+    // get current time in utc seconds
+    var now = _getUtcTime(new Date());
+
+    // output cookie name and value
+    value += cookie.name + '=' + cookie.value;
+    request.setField('Cookie', value);
+  };
+
+  /**
+   * Converts an http request into a string that can be sent as an
+   * HTTP request. Does not include any data.
+   *
+   * @return the string representation of the request.
+   */
+  request.toString = function() {
+    /* Sample request header:
+      GET /some/path/?query HTTP/1.1
+      Host: www.someurl.com
+      Connection: close
+      Accept-Encoding: deflate
+      Accept: image/gif, text/html
+      User-Agent: Mozilla 4.0
+     */
+
+    // set default headers
+    if(request.getField('User-Agent') === null) {
+      request.setField('User-Agent', 'forge.http 1.0');
+    }
+    if(request.getField('Accept') === null) {
+      request.setField('Accept', '*/*');
+    }
+    if(request.getField('Connection') === null) {
+      request.setField('Connection', 'keep-alive');
+      request.setField('Keep-Alive', '115');
+    }
+
+    // add Accept-Encoding if not specified
+    if(request.flashApi !== null &&
+      request.getField('Accept-Encoding') === null) {
+      request.setField('Accept-Encoding', 'deflate');
+    }
+
+    // if the body isn't null, deflate it if its larger than 100 bytes
+    if(request.flashApi !== null && request.body !== null &&
+      request.getField('Content-Encoding') === null &&
+      !request.bodyDeflated && request.body.length > 100) {
+      // use flash to compress data
+      request.body = forge.util.deflate(request.flashApi, request.body);
+      request.bodyDeflated = true;
+      request.setField('Content-Encoding', 'deflate');
+      request.setField('Content-Length', request.body.length);
+    } else if(request.body !== null) {
+      // set content length for body
+      request.setField('Content-Length', request.body.length);
+    }
+
+    // build start line
+    var rval =
+      request.method.toUpperCase() + ' ' + request.path + ' ' +
+      request.version + '\r\n';
+
+    // add each header
+    for(var name in request.fields) {
+      var fields = request.fields[name];
+      for(var i = 0; i < fields.length; ++i) {
+        rval += name + ': ' + fields[i] + '\r\n';
+      }
+    }
+    // final terminating CRLF
+    rval += '\r\n';
+
+    return rval;
+  };
+
+  return request;
+};
+
+/**
+ * Creates an empty http response header.
+ *
+ * @return the empty http response header.
+ */
+http.createResponse = function() {
+  // private vars
+  var _first = true;
+  var _chunkSize = 0;
+  var _chunksFinished = false;
+
+  // create response
+  var response = _createHeader();
+  response.version = null;
+  response.code = 0;
+  response.message = null;
+  response.body = null;
+  response.headerReceived = false;
+  response.bodyReceived = false;
+  response.flashApi = null;
+
+  /**
+   * Reads a line that ends in CRLF from a byte buffer.
+   *
+   * @param b the byte buffer.
+   *
+   * @return the line or null if none was found.
+   */
+  var _readCrlf = function(b) {
+    var line = null;
+    var i = b.data.indexOf('\r\n', b.read);
+    if(i != -1) {
+      // read line, skip CRLF
+      line = b.getBytes(i - b.read);
+      b.getBytes(2);
+    }
+    return line;
+  };
+
+  /**
+   * Parses a header field and appends it to the response.
+   *
+   * @param line the header field line.
+   */
+  var _parseHeader = function(line) {
+    var tmp = line.indexOf(':');
+    var name = line.substring(0, tmp++);
+    response.appendField(
+      name, (tmp < line.length) ? line.substring(tmp) : '');
+  };
+
+  /**
+   * Reads an http response header from a buffer of bytes.
+   *
+   * @param b the byte buffer to parse the header from.
+   *
+   * @return true if the whole header was read, false if not.
+   */
+  response.readHeader = function(b) {
+    // read header lines (each ends in CRLF)
+    var line = '';
+    while(!response.headerReceived && line !== null) {
+      line = _readCrlf(b);
+      if(line !== null) {
+        // parse first line
+        if(_first) {
+          _first = false;
+          var tmp = line.split(' ');
+          if(tmp.length >= 3) {
+            response.version = tmp[0];
+            response.code = parseInt(tmp[1], 10);
+            response.message = tmp.slice(2).join(' ');
+          } else {
+            // invalid header
+            var error = new Error('Invalid http response header.');
+            error.details = {'line': line};
+            throw error;
+          }
+        } else if(line.length === 0) {
+          // handle final line, end of header
+          response.headerReceived = true;
+        } else {
+          _parseHeader(line);
+        }
+      }
+    }
+
+    return response.headerReceived;
+  };
+
+  /**
+   * Reads some chunked http response entity-body from the given buffer of
+   * bytes.
+   *
+   * @param b the byte buffer to read from.
+   *
+   * @return true if the whole body was read, false if not.
+   */
+  var _readChunkedBody = function(b) {
+    /* Chunked transfer-encoding sends data in a series of chunks,
+      followed by a set of 0-N http trailers.
+      The format is as follows:
+
+      chunk-size (in hex) CRLF
+      chunk data (with "chunk-size" many bytes) CRLF
+      ... (N many chunks)
+      chunk-size (of 0 indicating the last chunk) CRLF
+      N many http trailers followed by CRLF
+      blank line + CRLF (terminates the trailers)
+
+      If there are no http trailers, then after the chunk-size of 0,
+      there is still a single CRLF (indicating the blank line + CRLF
+      that terminates the trailers). In other words, you always terminate
+      the trailers with blank line + CRLF, regardless of 0-N trailers. */
+
+      /* From RFC-2616, section 3.6.1, here is the pseudo-code for
+      implementing chunked transfer-encoding:
+
+      length := 0
+      read chunk-size, chunk-extension (if any) and CRLF
+      while (chunk-size > 0) {
+        read chunk-data and CRLF
+        append chunk-data to entity-body
+        length := length + chunk-size
+        read chunk-size and CRLF
+      }
+      read entity-header
+      while (entity-header not empty) {
+        append entity-header to existing header fields
+        read entity-header
+      }
+      Content-Length := length
+      Remove "chunked" from Transfer-Encoding
+    */
+
+    var line = '';
+    while(line !== null && b.length() > 0) {
+      // if in the process of reading a chunk
+      if(_chunkSize > 0) {
+        // if there are not enough bytes to read chunk and its
+        // trailing CRLF,  we must wait for more data to be received
+        if(_chunkSize + 2 > b.length()) {
+          break;
+        }
+
+        // read chunk data, skip CRLF
+        response.body += b.getBytes(_chunkSize);
+        b.getBytes(2);
+        _chunkSize = 0;
+      } else if(!_chunksFinished) {
+        // more chunks, read next chunk-size line
+        line = _readCrlf(b);
+        if(line !== null) {
+          // parse chunk-size (ignore any chunk extension)
+          _chunkSize = parseInt(line.split(';', 1)[0], 16);
+          _chunksFinished = (_chunkSize === 0);
+        }
+      } else {
+        // chunks finished, read next trailer
+        line = _readCrlf(b);
+        while(line !== null) {
+          if(line.length > 0) {
+            // parse trailer
+            _parseHeader(line);
+            // read next trailer
+            line = _readCrlf(b);
+          } else {
+            // body received
+            response.bodyReceived = true;
+            line = null;
+          }
+        }
+      }
+    }
+
+    return response.bodyReceived;
+  };
+
+  /**
+   * Reads an http response body from a buffer of bytes.
+   *
+   * @param b the byte buffer to read from.
+   *
+   * @return true if the whole body was read, false if not.
+   */
+  response.readBody = function(b) {
+    var contentLength = response.getField('Content-Length');
+    var transferEncoding = response.getField('Transfer-Encoding');
+    if(contentLength !== null) {
+      contentLength = parseInt(contentLength);
+    }
+
+    // read specified length
+    if(contentLength !== null && contentLength >= 0) {
+      response.body = response.body || '';
+      response.body += b.getBytes(contentLength);
+      response.bodyReceived = (response.body.length === contentLength);
+    } else if(transferEncoding !== null) {
+      // read chunked encoding
+      if(transferEncoding.indexOf('chunked') != -1) {
+        response.body = response.body || '';
+        _readChunkedBody(b);
+      } else {
+        var error = new Error('Unknown Transfer-Encoding.');
+        error.details = {'transferEncoding': transferEncoding};
+        throw error;
+      }
+    } else if((contentLength !== null && contentLength < 0) ||
+      (contentLength === null &&
+      response.getField('Content-Type') !== null)) {
+      // read all data in the buffer
+      response.body = response.body || '';
+      response.body += b.getBytes();
+      response.readBodyUntilClose = true;
+    } else {
+      // no body
+      response.body = null;
+      response.bodyReceived = true;
+    }
+
+    if(response.bodyReceived) {
+      response.time = +new Date() - response.time;
+    }
+
+    if(response.flashApi !== null &&
+      response.bodyReceived && response.body !== null &&
+      response.getField('Content-Encoding') === 'deflate') {
+      // inflate using flash api
+      response.body = forge.util.inflate(
+        response.flashApi, response.body);
+    }
+
+    return response.bodyReceived;
+  };
+
+   /**
+    * Parses an array of cookies from the 'Set-Cookie' field, if present.
+    *
+    * @return the array of cookies.
+    */
+   response.getCookies = function() {
+     var rval = [];
+
+     // get Set-Cookie field
+     if('Set-Cookie' in response.fields) {
+       var field = response.fields['Set-Cookie'];
+
+       // get current local time in seconds
+       var now = +new Date() / 1000;
+
+       // regex for parsing 'name1=value1; name2=value2; name3'
+       var regex = /\s*([^=]*)=?([^;]*)(;|$)/g;
+
+       // examples:
+       // Set-Cookie: cookie1_name=cookie1_value; max-age=0; path=/
+       // Set-Cookie: c2=v2; expires=Thu, 21-Aug-2008 23:47:25 GMT; path=/
+       for(var i = 0; i < field.length; ++i) {
+         var fv = field[i];
+         var m;
+         regex.lastIndex = 0;
+         var first = true;
+         var cookie = {};
+         do {
+           m = regex.exec(fv);
+           if(m !== null) {
+             var name = _trimString(m[1]);
+             var value = _trimString(m[2]);
+
+             // cookie_name=value
+             if(first) {
+               cookie.name = name;
+               cookie.value = value;
+               first = false;
+             } else {
+               // property_name=value
+               name = name.toLowerCase();
+               switch(name) {
+               case 'expires':
+                 // replace hyphens w/spaces so date will parse
+                 value = value.replace(/-/g, ' ');
+                 var secs = Date.parse(value) / 1000;
+                 cookie.maxAge = Math.max(0, secs - now);
+                 break;
+               case 'max-age':
+                 cookie.maxAge = parseInt(value, 10);
+                 break;
+               case 'secure':
+                 cookie.secure = true;
+                 break;
+               case 'httponly':
+                 cookie.httpOnly = true;
+                 break;
+               default:
+                 if(name !== '') {
+                   cookie[name] = value;
+                 }
+               }
+             }
+           }
+         } while(m !== null && m[0] !== '');
+         rval.push(cookie);
+       }
+     }
+
+     return rval;
+  };
+
+  /**
+   * Converts an http response into a string that can be sent as an
+   * HTTP response. Does not include any data.
+   *
+   * @return the string representation of the response.
+   */
+  response.toString = function() {
+    /* Sample response header:
+      HTTP/1.0 200 OK
+      Host: www.someurl.com
+      Connection: close
+     */
+
+    // build start line
+    var rval =
+      response.version + ' ' + response.code + ' ' + response.message + '\r\n';
+
+    // add each header
+    for(var name in response.fields) {
+      var fields = response.fields[name];
+      for(var i = 0; i < fields.length; ++i) {
+        rval += name + ': ' + fields[i] + '\r\n';
+      }
+    }
+    // final terminating CRLF
+    rval += '\r\n';
+
+    return rval;
+  };
+
+  return response;
+};
+
+/**
+ * Parses the scheme, host, and port from an http(s) url.
+ *
+ * @param str the url string.
+ *
+ * @return the parsed url object or null if the url is invalid.
+ */
+http.parseUrl = forge.util.parseUrl;
+
+/**
+ * Returns true if the given url is within the given cookie's domain.
+ *
+ * @param url the url to check.
+ * @param cookie the cookie or cookie domain to check.
+ */
+http.withinCookieDomain = function(url, cookie) {
+  var rval = false;
+
+  // cookie may be null, a cookie object, or a domain string
+  var domain = (cookie === null || typeof cookie === 'string') ?
+    cookie : cookie.domain;
+
+  // any domain will do
+  if(domain === null) {
+    rval = true;
+  } else if(domain.charAt(0) === '.') {
+    // ensure domain starts with a '.'
+    // parse URL as necessary
+    if(typeof url === 'string') {
+      url = http.parseUrl(url);
+    }
+
+    // add '.' to front of URL host to match against domain
+    var host = '.' + url.host;
+
+    // if the host ends with domain then it falls within it
+    var idx = host.lastIndexOf(domain);
+    if(idx !== -1 && (idx + domain.length === host.length)) {
+      rval = true;
+    }
+  }
+
+  return rval;
+};
+
+// public access to http namespace
+if(typeof forge === 'undefined') {
+  forge = {};
+}
+forge.http = http;
+
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/jsbn.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/jsbn.js
new file mode 100644
index 0000000..29a1509
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/jsbn.js
@@ -0,0 +1,1321 @@
+// Copyright (c) 2005  Tom Wu
+// All Rights Reserved.
+// See "LICENSE" for details.
+
+// Basic JavaScript BN library - subset useful for RSA encryption.
+
+/*
+Licensing (LICENSE)
+-------------------
+
+This software is covered under the following copyright:
+*/
+/*
+ * Copyright (c) 2003-2005  Tom Wu
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
+ * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
+ * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF
+ * THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * In addition, the following condition applies:
+ *
+ * All redistributions must retain an intact copy of this copyright notice
+ * and disclaimer.
+ */
+/*
+Address all questions regarding this license to:
+
+  Tom Wu
+  tjw@cs.Stanford.EDU
+*/
+
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+// Bits per digit
+var dbits;
+
+// JavaScript engine analysis
+var canary = 0xdeadbeefcafe;
+var j_lm = ((canary&0xffffff)==0xefcafe);
+
+// (public) Constructor
+function BigInteger(a,b,c) {
+  this.data = [];
+  if(a != null)
+    if("number" == typeof a) this.fromNumber(a,b,c);
+    else if(b == null && "string" != typeof a) this.fromString(a,256);
+    else this.fromString(a,b);
+}
+
+// return new, unset BigInteger
+function nbi() { return new BigInteger(null); }
+
+// am: Compute w_j += (x*this_i), propagate carries,
+// c is initial carry, returns final carry.
+// c < 3*dvalue, x < 2*dvalue, this_i < dvalue
+// We need to select the fastest one that works in this environment.
+
+// am1: use a single mult and divide to get the high bits,
+// max digit bits should be 26 because
+// max internal value = 2*dvalue^2-2*dvalue (< 2^53)
+function am1(i,x,w,j,c,n) {
+  while(--n >= 0) {
+    var v = x*this.data[i++]+w.data[j]+c;
+    c = Math.floor(v/0x4000000);
+    w.data[j++] = v&0x3ffffff;
+  }
+  return c;
+}
+// am2 avoids a big mult-and-extract completely.
+// Max digit bits should be <= 30 because we do bitwise ops
+// on values up to 2*hdvalue^2-hdvalue-1 (< 2^31)
+function am2(i,x,w,j,c,n) {
+  var xl = x&0x7fff, xh = x>>15;
+  while(--n >= 0) {
+    var l = this.data[i]&0x7fff;
+    var h = this.data[i++]>>15;
+    var m = xh*l+h*xl;
+    l = xl*l+((m&0x7fff)<<15)+w.data[j]+(c&0x3fffffff);
+    c = (l>>>30)+(m>>>15)+xh*h+(c>>>30);
+    w.data[j++] = l&0x3fffffff;
+  }
+  return c;
+}
+// Alternately, set max digit bits to 28 since some
+// browsers slow down when dealing with 32-bit numbers.
+function am3(i,x,w,j,c,n) {
+  var xl = x&0x3fff, xh = x>>14;
+  while(--n >= 0) {
+    var l = this.data[i]&0x3fff;
+    var h = this.data[i++]>>14;
+    var m = xh*l+h*xl;
+    l = xl*l+((m&0x3fff)<<14)+w.data[j]+c;
+    c = (l>>28)+(m>>14)+xh*h;
+    w.data[j++] = l&0xfffffff;
+  }
+  return c;
+}
+
+// node.js (no browser)
+if(typeof(navigator) === 'undefined')
+{
+   BigInteger.prototype.am = am3;
+   dbits = 28;
+} else if(j_lm && (navigator.appName == "Microsoft Internet Explorer")) {
+  BigInteger.prototype.am = am2;
+  dbits = 30;
+} else if(j_lm && (navigator.appName != "Netscape")) {
+  BigInteger.prototype.am = am1;
+  dbits = 26;
+} else { // Mozilla/Netscape seems to prefer am3
+  BigInteger.prototype.am = am3;
+  dbits = 28;
+}
+
+BigInteger.prototype.DB = dbits;
+BigInteger.prototype.DM = ((1<<dbits)-1);
+BigInteger.prototype.DV = (1<<dbits);
+
+var BI_FP = 52;
+BigInteger.prototype.FV = Math.pow(2,BI_FP);
+BigInteger.prototype.F1 = BI_FP-dbits;
+BigInteger.prototype.F2 = 2*dbits-BI_FP;
+
+// Digit conversions
+var BI_RM = "0123456789abcdefghijklmnopqrstuvwxyz";
+var BI_RC = new Array();
+var rr,vv;
+rr = "0".charCodeAt(0);
+for(vv = 0; vv <= 9; ++vv) BI_RC[rr++] = vv;
+rr = "a".charCodeAt(0);
+for(vv = 10; vv < 36; ++vv) BI_RC[rr++] = vv;
+rr = "A".charCodeAt(0);
+for(vv = 10; vv < 36; ++vv) BI_RC[rr++] = vv;
+
+function int2char(n) { return BI_RM.charAt(n); }
+function intAt(s,i) {
+  var c = BI_RC[s.charCodeAt(i)];
+  return (c==null)?-1:c;
+}
+
+// (protected) copy this to r
+function bnpCopyTo(r) {
+  for(var i = this.t-1; i >= 0; --i) r.data[i] = this.data[i];
+  r.t = this.t;
+  r.s = this.s;
+}
+
+// (protected) set from integer value x, -DV <= x < DV
+function bnpFromInt(x) {
+  this.t = 1;
+  this.s = (x<0)?-1:0;
+  if(x > 0) this.data[0] = x;
+  else if(x < -1) this.data[0] = x+this.DV;
+  else this.t = 0;
+}
+
+// return bigint initialized to value
+function nbv(i) { var r = nbi(); r.fromInt(i); return r; }
+
+// (protected) set from string and radix
+function bnpFromString(s,b) {
+  var k;
+  if(b == 16) k = 4;
+  else if(b == 8) k = 3;
+  else if(b == 256) k = 8; // byte array
+  else if(b == 2) k = 1;
+  else if(b == 32) k = 5;
+  else if(b == 4) k = 2;
+  else { this.fromRadix(s,b); return; }
+  this.t = 0;
+  this.s = 0;
+  var i = s.length, mi = false, sh = 0;
+  while(--i >= 0) {
+    var x = (k==8)?s[i]&0xff:intAt(s,i);
+    if(x < 0) {
+      if(s.charAt(i) == "-") mi = true;
+      continue;
+    }
+    mi = false;
+    if(sh == 0)
+      this.data[this.t++] = x;
+    else if(sh+k > this.DB) {
+      this.data[this.t-1] |= (x&((1<<(this.DB-sh))-1))<<sh;
+      this.data[this.t++] = (x>>(this.DB-sh));
+    } else
+      this.data[this.t-1] |= x<<sh;
+    sh += k;
+    if(sh >= this.DB) sh -= this.DB;
+  }
+  if(k == 8 && (s[0]&0x80) != 0) {
+    this.s = -1;
+    if(sh > 0) this.data[this.t-1] |= ((1<<(this.DB-sh))-1)<<sh;
+  }
+  this.clamp();
+  if(mi) BigInteger.ZERO.subTo(this,this);
+}
+
+// (protected) clamp off excess high words
+function bnpClamp() {
+  var c = this.s&this.DM;
+  while(this.t > 0 && this.data[this.t-1] == c) --this.t;
+}
+
+// (public) return string representation in given radix
+function bnToString(b) {
+  if(this.s < 0) return "-"+this.negate().toString(b);
+  var k;
+  if(b == 16) k = 4;
+  else if(b == 8) k = 3;
+  else if(b == 2) k = 1;
+  else if(b == 32) k = 5;
+  else if(b == 4) k = 2;
+  else return this.toRadix(b);
+  var km = (1<<k)-1, d, m = false, r = "", i = this.t;
+  var p = this.DB-(i*this.DB)%k;
+  if(i-- > 0) {
+    if(p < this.DB && (d = this.data[i]>>p) > 0) { m = true; r = int2char(d); }
+    while(i >= 0) {
+      if(p < k) {
+        d = (this.data[i]&((1<<p)-1))<<(k-p);
+        d |= this.data[--i]>>(p+=this.DB-k);
+      } else {
+        d = (this.data[i]>>(p-=k))&km;
+        if(p <= 0) { p += this.DB; --i; }
+      }
+      if(d > 0) m = true;
+      if(m) r += int2char(d);
+    }
+  }
+  return m?r:"0";
+}
+
+// (public) -this
+function bnNegate() { var r = nbi(); BigInteger.ZERO.subTo(this,r); return r; }
+
+// (public) |this|
+function bnAbs() { return (this.s<0)?this.negate():this; }
+
+// (public) return + if this > a, - if this < a, 0 if equal
+function bnCompareTo(a) {
+  var r = this.s-a.s;
+  if(r != 0) return r;
+  var i = this.t;
+  r = i-a.t;
+  if(r != 0) return (this.s<0)?-r:r;
+  while(--i >= 0) if((r=this.data[i]-a.data[i]) != 0) return r;
+  return 0;
+}
+
+// returns bit length of the integer x
+function nbits(x) {
+  var r = 1, t;
+  if((t=x>>>16) != 0) { x = t; r += 16; }
+  if((t=x>>8) != 0) { x = t; r += 8; }
+  if((t=x>>4) != 0) { x = t; r += 4; }
+  if((t=x>>2) != 0) { x = t; r += 2; }
+  if((t=x>>1) != 0) { x = t; r += 1; }
+  return r;
+}
+
+// (public) return the number of bits in "this"
+function bnBitLength() {
+  if(this.t <= 0) return 0;
+  return this.DB*(this.t-1)+nbits(this.data[this.t-1]^(this.s&this.DM));
+}
+
+// (protected) r = this << n*DB
+function bnpDLShiftTo(n,r) {
+  var i;
+  for(i = this.t-1; i >= 0; --i) r.data[i+n] = this.data[i];
+  for(i = n-1; i >= 0; --i) r.data[i] = 0;
+  r.t = this.t+n;
+  r.s = this.s;
+}
+
+// (protected) r = this >> n*DB
+function bnpDRShiftTo(n,r) {
+  for(var i = n; i < this.t; ++i) r.data[i-n] = this.data[i];
+  r.t = Math.max(this.t-n,0);
+  r.s = this.s;
+}
+
+// (protected) r = this << n
+function bnpLShiftTo(n,r) {
+  var bs = n%this.DB;
+  var cbs = this.DB-bs;
+  var bm = (1<<cbs)-1;
+  var ds = Math.floor(n/this.DB), c = (this.s<<bs)&this.DM, i;
+  for(i = this.t-1; i >= 0; --i) {
+    r.data[i+ds+1] = (this.data[i]>>cbs)|c;
+    c = (this.data[i]&bm)<<bs;
+  }
+  for(i = ds-1; i >= 0; --i) r.data[i] = 0;
+  r.data[ds] = c;
+  r.t = this.t+ds+1;
+  r.s = this.s;
+  r.clamp();
+}
+
+// (protected) r = this >> n
+function bnpRShiftTo(n,r) {
+  r.s = this.s;
+  var ds = Math.floor(n/this.DB);
+  if(ds >= this.t) { r.t = 0; return; }
+  var bs = n%this.DB;
+  var cbs = this.DB-bs;
+  var bm = (1<<bs)-1;
+  r.data[0] = this.data[ds]>>bs;
+  for(var i = ds+1; i < this.t; ++i) {
+    r.data[i-ds-1] |= (this.data[i]&bm)<<cbs;
+    r.data[i-ds] = this.data[i]>>bs;
+  }
+  if(bs > 0) r.data[this.t-ds-1] |= (this.s&bm)<<cbs;
+  r.t = this.t-ds;
+  r.clamp();
+}
+
+// (protected) r = this - a
+function bnpSubTo(a,r) {
+  var i = 0, c = 0, m = Math.min(a.t,this.t);
+  while(i < m) {
+    c += this.data[i]-a.data[i];
+    r.data[i++] = c&this.DM;
+    c >>= this.DB;
+  }
+  if(a.t < this.t) {
+    c -= a.s;
+    while(i < this.t) {
+      c += this.data[i];
+      r.data[i++] = c&this.DM;
+      c >>= this.DB;
+    }
+    c += this.s;
+  } else {
+    c += this.s;
+    while(i < a.t) {
+      c -= a.data[i];
+      r.data[i++] = c&this.DM;
+      c >>= this.DB;
+    }
+    c -= a.s;
+  }
+  r.s = (c<0)?-1:0;
+  if(c < -1) r.data[i++] = this.DV+c;
+  else if(c > 0) r.data[i++] = c;
+  r.t = i;
+  r.clamp();
+}
+
+// (protected) r = this * a, r != this,a (HAC 14.12)
+// "this" should be the larger one if appropriate.
+function bnpMultiplyTo(a,r) {
+  var x = this.abs(), y = a.abs();
+  var i = x.t;
+  r.t = i+y.t;
+  while(--i >= 0) r.data[i] = 0;
+  for(i = 0; i < y.t; ++i) r.data[i+x.t] = x.am(0,y.data[i],r,i,0,x.t);
+  r.s = 0;
+  r.clamp();
+  if(this.s != a.s) BigInteger.ZERO.subTo(r,r);
+}
+
+// (protected) r = this^2, r != this (HAC 14.16)
+function bnpSquareTo(r) {
+  var x = this.abs();
+  var i = r.t = 2*x.t;
+  while(--i >= 0) r.data[i] = 0;
+  for(i = 0; i < x.t-1; ++i) {
+    var c = x.am(i,x.data[i],r,2*i,0,1);
+    if((r.data[i+x.t]+=x.am(i+1,2*x.data[i],r,2*i+1,c,x.t-i-1)) >= x.DV) {
+      r.data[i+x.t] -= x.DV;
+      r.data[i+x.t+1] = 1;
+    }
+  }
+  if(r.t > 0) r.data[r.t-1] += x.am(i,x.data[i],r,2*i,0,1);
+  r.s = 0;
+  r.clamp();
+}
+
+// (protected) divide this by m, quotient and remainder to q, r (HAC 14.20)
+// r != q, this != m.  q or r may be null.
+function bnpDivRemTo(m,q,r) {
+  var pm = m.abs();
+  if(pm.t <= 0) return;
+  var pt = this.abs();
+  if(pt.t < pm.t) {
+    if(q != null) q.fromInt(0);
+    if(r != null) this.copyTo(r);
+    return;
+  }
+  if(r == null) r = nbi();
+  var y = nbi(), ts = this.s, ms = m.s;
+  var nsh = this.DB-nbits(pm.data[pm.t-1]);	// normalize modulus
+  if(nsh > 0) { pm.lShiftTo(nsh,y); pt.lShiftTo(nsh,r); } else { pm.copyTo(y); pt.copyTo(r); }
+  var ys = y.t;
+  var y0 = y.data[ys-1];
+  if(y0 == 0) return;
+  var yt = y0*(1<<this.F1)+((ys>1)?y.data[ys-2]>>this.F2:0);
+  var d1 = this.FV/yt, d2 = (1<<this.F1)/yt, e = 1<<this.F2;
+  var i = r.t, j = i-ys, t = (q==null)?nbi():q;
+  y.dlShiftTo(j,t);
+  if(r.compareTo(t) >= 0) {
+    r.data[r.t++] = 1;
+    r.subTo(t,r);
+  }
+  BigInteger.ONE.dlShiftTo(ys,t);
+  t.subTo(y,y);	// "negative" y so we can replace sub with am later
+  while(y.t < ys) y.data[y.t++] = 0;
+  while(--j >= 0) {
+    // Estimate quotient digit
+    var qd = (r.data[--i]==y0)?this.DM:Math.floor(r.data[i]*d1+(r.data[i-1]+e)*d2);
+    if((r.data[i]+=y.am(0,qd,r,j,0,ys)) < qd) {	// Try it out
+      y.dlShiftTo(j,t);
+      r.subTo(t,r);
+      while(r.data[i] < --qd) r.subTo(t,r);
+    }
+  }
+  if(q != null) {
+    r.drShiftTo(ys,q);
+    if(ts != ms) BigInteger.ZERO.subTo(q,q);
+  }
+  r.t = ys;
+  r.clamp();
+  if(nsh > 0) r.rShiftTo(nsh,r);	// Denormalize remainder
+  if(ts < 0) BigInteger.ZERO.subTo(r,r);
+}
+
+// (public) this mod a
+function bnMod(a) {
+  var r = nbi();
+  this.abs().divRemTo(a,null,r);
+  if(this.s < 0 && r.compareTo(BigInteger.ZERO) > 0) a.subTo(r,r);
+  return r;
+}
+
+// Modular reduction using "classic" algorithm
+function Classic(m) { this.m = m; }
+function cConvert(x) {
+  if(x.s < 0 || x.compareTo(this.m) >= 0) return x.mod(this.m);
+  else return x;
+}
+function cRevert(x) { return x; }
+function cReduce(x) { x.divRemTo(this.m,null,x); }
+function cMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); }
+function cSqrTo(x,r) { x.squareTo(r); this.reduce(r); }
+
+Classic.prototype.convert = cConvert;
+Classic.prototype.revert = cRevert;
+Classic.prototype.reduce = cReduce;
+Classic.prototype.mulTo = cMulTo;
+Classic.prototype.sqrTo = cSqrTo;
+
+// (protected) return "-1/this % 2^DB"; useful for Mont. reduction
+// justification:
+//         xy == 1 (mod m)
+//         xy =  1+km
+//   xy(2-xy) = (1+km)(1-km)
+// x[y(2-xy)] = 1-k^2m^2
+// x[y(2-xy)] == 1 (mod m^2)
+// if y is 1/x mod m, then y(2-xy) is 1/x mod m^2
+// should reduce x and y(2-xy) by m^2 at each step to keep size bounded.
+// JS multiply "overflows" differently from C/C++, so care is needed here.
+function bnpInvDigit() {
+  if(this.t < 1) return 0;
+  var x = this.data[0];
+  if((x&1) == 0) return 0;
+  var y = x&3;		// y == 1/x mod 2^2
+  y = (y*(2-(x&0xf)*y))&0xf;	// y == 1/x mod 2^4
+  y = (y*(2-(x&0xff)*y))&0xff;	// y == 1/x mod 2^8
+  y = (y*(2-(((x&0xffff)*y)&0xffff)))&0xffff;	// y == 1/x mod 2^16
+  // last step - calculate inverse mod DV directly;
+  // assumes 16 < DB <= 32 and assumes ability to handle 48-bit ints
+  y = (y*(2-x*y%this.DV))%this.DV;		// y == 1/x mod 2^dbits
+  // we really want the negative inverse, and -DV < y < DV
+  return (y>0)?this.DV-y:-y;
+}
+
+// Montgomery reduction
+function Montgomery(m) {
+  this.m = m;
+  this.mp = m.invDigit();
+  this.mpl = this.mp&0x7fff;
+  this.mph = this.mp>>15;
+  this.um = (1<<(m.DB-15))-1;
+  this.mt2 = 2*m.t;
+}
+
+// xR mod m
+function montConvert(x) {
+  var r = nbi();
+  x.abs().dlShiftTo(this.m.t,r);
+  r.divRemTo(this.m,null,r);
+  if(x.s < 0 && r.compareTo(BigInteger.ZERO) > 0) this.m.subTo(r,r);
+  return r;
+}
+
+// x/R mod m
+function montRevert(x) {
+  var r = nbi();
+  x.copyTo(r);
+  this.reduce(r);
+  return r;
+}
+
+// x = x/R mod m (HAC 14.32)
+function montReduce(x) {
+  while(x.t <= this.mt2)	// pad x so am has enough room later
+    x.data[x.t++] = 0;
+  for(var i = 0; i < this.m.t; ++i) {
+    // faster way of calculating u0 = x.data[i]*mp mod DV
+    var j = x.data[i]&0x7fff;
+    var u0 = (j*this.mpl+(((j*this.mph+(x.data[i]>>15)*this.mpl)&this.um)<<15))&x.DM;
+    // use am to combine the multiply-shift-add into one call
+    j = i+this.m.t;
+    x.data[j] += this.m.am(0,u0,x,i,0,this.m.t);
+    // propagate carry
+    while(x.data[j] >= x.DV) { x.data[j] -= x.DV; x.data[++j]++; }
+  }
+  x.clamp();
+  x.drShiftTo(this.m.t,x);
+  if(x.compareTo(this.m) >= 0) x.subTo(this.m,x);
+}
+
+// r = "x^2/R mod m"; x != r
+function montSqrTo(x,r) { x.squareTo(r); this.reduce(r); }
+
+// r = "xy/R mod m"; x,y != r
+function montMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); }
+
+Montgomery.prototype.convert = montConvert;
+Montgomery.prototype.revert = montRevert;
+Montgomery.prototype.reduce = montReduce;
+Montgomery.prototype.mulTo = montMulTo;
+Montgomery.prototype.sqrTo = montSqrTo;
+
+// (protected) true iff this is even
+function bnpIsEven() { return ((this.t>0)?(this.data[0]&1):this.s) == 0; }
+
+// (protected) this^e, e < 2^32, doing sqr and mul with "r" (HAC 14.79)
+function bnpExp(e,z) {
+  if(e > 0xffffffff || e < 1) return BigInteger.ONE;
+  var r = nbi(), r2 = nbi(), g = z.convert(this), i = nbits(e)-1;
+  g.copyTo(r);
+  while(--i >= 0) {
+    z.sqrTo(r,r2);
+    if((e&(1<<i)) > 0) z.mulTo(r2,g,r);
+    else { var t = r; r = r2; r2 = t; }
+  }
+  return z.revert(r);
+}
+
+// (public) this^e % m, 0 <= e < 2^32
+function bnModPowInt(e,m) {
+  var z;
+  if(e < 256 || m.isEven()) z = new Classic(m); else z = new Montgomery(m);
+  return this.exp(e,z);
+}
+
+// protected
+BigInteger.prototype.copyTo = bnpCopyTo;
+BigInteger.prototype.fromInt = bnpFromInt;
+BigInteger.prototype.fromString = bnpFromString;
+BigInteger.prototype.clamp = bnpClamp;
+BigInteger.prototype.dlShiftTo = bnpDLShiftTo;
+BigInteger.prototype.drShiftTo = bnpDRShiftTo;
+BigInteger.prototype.lShiftTo = bnpLShiftTo;
+BigInteger.prototype.rShiftTo = bnpRShiftTo;
+BigInteger.prototype.subTo = bnpSubTo;
+BigInteger.prototype.multiplyTo = bnpMultiplyTo;
+BigInteger.prototype.squareTo = bnpSquareTo;
+BigInteger.prototype.divRemTo = bnpDivRemTo;
+BigInteger.prototype.invDigit = bnpInvDigit;
+BigInteger.prototype.isEven = bnpIsEven;
+BigInteger.prototype.exp = bnpExp;
+
+// public
+BigInteger.prototype.toString = bnToString;
+BigInteger.prototype.negate = bnNegate;
+BigInteger.prototype.abs = bnAbs;
+BigInteger.prototype.compareTo = bnCompareTo;
+BigInteger.prototype.bitLength = bnBitLength;
+BigInteger.prototype.mod = bnMod;
+BigInteger.prototype.modPowInt = bnModPowInt;
+
+// "constants"
+BigInteger.ZERO = nbv(0);
+BigInteger.ONE = nbv(1);
+
+// jsbn2 lib
+
+//Copyright (c) 2005-2009  Tom Wu
+//All Rights Reserved.
+//See "LICENSE" for details (See jsbn.js for LICENSE).
+
+//Extended JavaScript BN functions, required for RSA private ops.
+
+//Version 1.1: new BigInteger("0", 10) returns "proper" zero
+
+//(public)
+function bnClone() { var r = nbi(); this.copyTo(r); return r; }
+
+//(public) return value as integer
+function bnIntValue() {
+if(this.s < 0) {
+ if(this.t == 1) return this.data[0]-this.DV;
+ else if(this.t == 0) return -1;
+} else if(this.t == 1) return this.data[0];
+else if(this.t == 0) return 0;
+// assumes 16 < DB < 32
+return ((this.data[1]&((1<<(32-this.DB))-1))<<this.DB)|this.data[0];
+}
+
+//(public) return value as byte
+function bnByteValue() { return (this.t==0)?this.s:(this.data[0]<<24)>>24; }
+
+//(public) return value as short (assumes DB>=16)
+function bnShortValue() { return (this.t==0)?this.s:(this.data[0]<<16)>>16; }
+
+//(protected) return x s.t. r^x < DV
+function bnpChunkSize(r) { return Math.floor(Math.LN2*this.DB/Math.log(r)); }
+
+//(public) 0 if this == 0, 1 if this > 0
+function bnSigNum() {
+if(this.s < 0) return -1;
+else if(this.t <= 0 || (this.t == 1 && this.data[0] <= 0)) return 0;
+else return 1;
+}
+
+//(protected) convert to radix string
+function bnpToRadix(b) {
+if(b == null) b = 10;
+if(this.signum() == 0 || b < 2 || b > 36) return "0";
+var cs = this.chunkSize(b);
+var a = Math.pow(b,cs);
+var d = nbv(a), y = nbi(), z = nbi(), r = "";
+this.divRemTo(d,y,z);
+while(y.signum() > 0) {
+ r = (a+z.intValue()).toString(b).substr(1) + r;
+ y.divRemTo(d,y,z);
+}
+return z.intValue().toString(b) + r;
+}
+
+//(protected) convert from radix string
+function bnpFromRadix(s,b) {
+this.fromInt(0);
+if(b == null) b = 10;
+var cs = this.chunkSize(b);
+var d = Math.pow(b,cs), mi = false, j = 0, w = 0;
+for(var i = 0; i < s.length; ++i) {
+ var x = intAt(s,i);
+ if(x < 0) {
+   if(s.charAt(i) == "-" && this.signum() == 0) mi = true;
+   continue;
+ }
+ w = b*w+x;
+ if(++j >= cs) {
+   this.dMultiply(d);
+   this.dAddOffset(w,0);
+   j = 0;
+   w = 0;
+ }
+}
+if(j > 0) {
+ this.dMultiply(Math.pow(b,j));
+ this.dAddOffset(w,0);
+}
+if(mi) BigInteger.ZERO.subTo(this,this);
+}
+
+//(protected) alternate constructor
+function bnpFromNumber(a,b,c) {
+if("number" == typeof b) {
+ // new BigInteger(int,int,RNG)
+ if(a < 2) this.fromInt(1);
+ else {
+   this.fromNumber(a,c);
+   if(!this.testBit(a-1))  // force MSB set
+     this.bitwiseTo(BigInteger.ONE.shiftLeft(a-1),op_or,this);
+   if(this.isEven()) this.dAddOffset(1,0); // force odd
+   while(!this.isProbablePrime(b)) {
+     this.dAddOffset(2,0);
+     if(this.bitLength() > a) this.subTo(BigInteger.ONE.shiftLeft(a-1),this);
+   }
+ }
+} else {
+ // new BigInteger(int,RNG)
+ var x = new Array(), t = a&7;
+ x.length = (a>>3)+1;
+ b.nextBytes(x);
+ if(t > 0) x[0] &= ((1<<t)-1); else x[0] = 0;
+ this.fromString(x,256);
+}
+}
+
+//(public) convert to bigendian byte array
+function bnToByteArray() {
+var i = this.t, r = new Array();
+r[0] = this.s;
+var p = this.DB-(i*this.DB)%8, d, k = 0;
+if(i-- > 0) {
+ if(p < this.DB && (d = this.data[i]>>p) != (this.s&this.DM)>>p)
+   r[k++] = d|(this.s<<(this.DB-p));
+ while(i >= 0) {
+   if(p < 8) {
+     d = (this.data[i]&((1<<p)-1))<<(8-p);
+     d |= this.data[--i]>>(p+=this.DB-8);
+   } else {
+     d = (this.data[i]>>(p-=8))&0xff;
+     if(p <= 0) { p += this.DB; --i; }
+   }
+   if((d&0x80) != 0) d |= -256;
+   if(k == 0 && (this.s&0x80) != (d&0x80)) ++k;
+   if(k > 0 || d != this.s) r[k++] = d;
+ }
+}
+return r;
+}
+
+function bnEquals(a) { return(this.compareTo(a)==0); }
+function bnMin(a) { return(this.compareTo(a)<0)?this:a; }
+function bnMax(a) { return(this.compareTo(a)>0)?this:a; }
+
+//(protected) r = this op a (bitwise)
+function bnpBitwiseTo(a,op,r) {
+var i, f, m = Math.min(a.t,this.t);
+for(i = 0; i < m; ++i) r.data[i] = op(this.data[i],a.data[i]);
+if(a.t < this.t) {
+ f = a.s&this.DM;
+ for(i = m; i < this.t; ++i) r.data[i] = op(this.data[i],f);
+ r.t = this.t;
+} else {
+ f = this.s&this.DM;
+ for(i = m; i < a.t; ++i) r.data[i] = op(f,a.data[i]);
+ r.t = a.t;
+}
+r.s = op(this.s,a.s);
+r.clamp();
+}
+
+//(public) this & a
+function op_and(x,y) { return x&y; }
+function bnAnd(a) { var r = nbi(); this.bitwiseTo(a,op_and,r); return r; }
+
+//(public) this | a
+function op_or(x,y) { return x|y; }
+function bnOr(a) { var r = nbi(); this.bitwiseTo(a,op_or,r); return r; }
+
+//(public) this ^ a
+function op_xor(x,y) { return x^y; }
+function bnXor(a) { var r = nbi(); this.bitwiseTo(a,op_xor,r); return r; }
+
+//(public) this & ~a
+function op_andnot(x,y) { return x&~y; }
+function bnAndNot(a) { var r = nbi(); this.bitwiseTo(a,op_andnot,r); return r; }
+
+//(public) ~this
+function bnNot() {
+var r = nbi();
+for(var i = 0; i < this.t; ++i) r.data[i] = this.DM&~this.data[i];
+r.t = this.t;
+r.s = ~this.s;
+return r;
+}
+
+//(public) this << n
+function bnShiftLeft(n) {
+var r = nbi();
+if(n < 0) this.rShiftTo(-n,r); else this.lShiftTo(n,r);
+return r;
+}
+
+//(public) this >> n
+function bnShiftRight(n) {
+var r = nbi();
+if(n < 0) this.lShiftTo(-n,r); else this.rShiftTo(n,r);
+return r;
+}
+
+//return index of lowest 1-bit in x, x < 2^31
+function lbit(x) {
+if(x == 0) return -1;
+var r = 0;
+if((x&0xffff) == 0) { x >>= 16; r += 16; }
+if((x&0xff) == 0) { x >>= 8; r += 8; }
+if((x&0xf) == 0) { x >>= 4; r += 4; }
+if((x&3) == 0) { x >>= 2; r += 2; }
+if((x&1) == 0) ++r;
+return r;
+}
+
+//(public) returns index of lowest 1-bit (or -1 if none)
+function bnGetLowestSetBit() {
+for(var i = 0; i < this.t; ++i)
+ if(this.data[i] != 0) return i*this.DB+lbit(this.data[i]);
+if(this.s < 0) return this.t*this.DB;
+return -1;
+}
+
+//return number of 1 bits in x
+function cbit(x) {
+var r = 0;
+while(x != 0) { x &= x-1; ++r; }
+return r;
+}
+
+//(public) return number of set bits
+function bnBitCount() {
+var r = 0, x = this.s&this.DM;
+for(var i = 0; i < this.t; ++i) r += cbit(this.data[i]^x);
+return r;
+}
+
+//(public) true iff nth bit is set
+function bnTestBit(n) {
+var j = Math.floor(n/this.DB);
+if(j >= this.t) return(this.s!=0);
+return((this.data[j]&(1<<(n%this.DB)))!=0);
+}
+
+//(protected) this op (1<<n)
+function bnpChangeBit(n,op) {
+var r = BigInteger.ONE.shiftLeft(n);
+this.bitwiseTo(r,op,r);
+return r;
+}
+
+//(public) this | (1<<n)
+function bnSetBit(n) { return this.changeBit(n,op_or); }
+
+//(public) this & ~(1<<n)
+function bnClearBit(n) { return this.changeBit(n,op_andnot); }
+
+//(public) this ^ (1<<n)
+function bnFlipBit(n) { return this.changeBit(n,op_xor); }
+
+//(protected) r = this + a
+function bnpAddTo(a,r) {
+var i = 0, c = 0, m = Math.min(a.t,this.t);
+while(i < m) {
+ c += this.data[i]+a.data[i];
+ r.data[i++] = c&this.DM;
+ c >>= this.DB;
+}
+if(a.t < this.t) {
+ c += a.s;
+ while(i < this.t) {
+   c += this.data[i];
+   r.data[i++] = c&this.DM;
+   c >>= this.DB;
+ }
+ c += this.s;
+} else {
+ c += this.s;
+ while(i < a.t) {
+   c += a.data[i];
+   r.data[i++] = c&this.DM;
+   c >>= this.DB;
+ }
+ c += a.s;
+}
+r.s = (c<0)?-1:0;
+if(c > 0) r.data[i++] = c;
+else if(c < -1) r.data[i++] = this.DV+c;
+r.t = i;
+r.clamp();
+}
+
+//(public) this + a
+function bnAdd(a) { var r = nbi(); this.addTo(a,r); return r; }
+
+//(public) this - a
+function bnSubtract(a) { var r = nbi(); this.subTo(a,r); return r; }
+
+//(public) this * a
+function bnMultiply(a) { var r = nbi(); this.multiplyTo(a,r); return r; }
+
+//(public) this / a
+function bnDivide(a) { var r = nbi(); this.divRemTo(a,r,null); return r; }
+
+//(public) this % a
+function bnRemainder(a) { var r = nbi(); this.divRemTo(a,null,r); return r; }
+
+//(public) [this/a,this%a]
+function bnDivideAndRemainder(a) {
+var q = nbi(), r = nbi();
+this.divRemTo(a,q,r);
+return new Array(q,r);
+}
+
+//(protected) this *= n, this >= 0, 1 < n < DV
+function bnpDMultiply(n) {
+this.data[this.t] = this.am(0,n-1,this,0,0,this.t);
+++this.t;
+this.clamp();
+}
+
+//(protected) this += n << w words, this >= 0
+function bnpDAddOffset(n,w) {
+if(n == 0) return;
+while(this.t <= w) this.data[this.t++] = 0;
+this.data[w] += n;
+while(this.data[w] >= this.DV) {
+ this.data[w] -= this.DV;
+ if(++w >= this.t) this.data[this.t++] = 0;
+ ++this.data[w];
+}
+}
+
+//A "null" reducer
+function NullExp() {}
+function nNop(x) { return x; }
+function nMulTo(x,y,r) { x.multiplyTo(y,r); }
+function nSqrTo(x,r) { x.squareTo(r); }
+
+NullExp.prototype.convert = nNop;
+NullExp.prototype.revert = nNop;
+NullExp.prototype.mulTo = nMulTo;
+NullExp.prototype.sqrTo = nSqrTo;
+
+//(public) this^e
+function bnPow(e) { return this.exp(e,new NullExp()); }
+
+//(protected) r = lower n words of "this * a", a.t <= n
+//"this" should be the larger one if appropriate.
+function bnpMultiplyLowerTo(a,n,r) {
+var i = Math.min(this.t+a.t,n);
+r.s = 0; // assumes a,this >= 0
+r.t = i;
+while(i > 0) r.data[--i] = 0;
+var j;
+for(j = r.t-this.t; i < j; ++i) r.data[i+this.t] = this.am(0,a.data[i],r,i,0,this.t);
+for(j = Math.min(a.t,n); i < j; ++i) this.am(0,a.data[i],r,i,0,n-i);
+r.clamp();
+}
+
+//(protected) r = "this * a" without lower n words, n > 0
+//"this" should be the larger one if appropriate.
+function bnpMultiplyUpperTo(a,n,r) {
+--n;
+var i = r.t = this.t+a.t-n;
+r.s = 0; // assumes a,this >= 0
+while(--i >= 0) r.data[i] = 0;
+for(i = Math.max(n-this.t,0); i < a.t; ++i)
+ r.data[this.t+i-n] = this.am(n-i,a.data[i],r,0,0,this.t+i-n);
+r.clamp();
+r.drShiftTo(1,r);
+}
+
+//Barrett modular reduction
+function Barrett(m) {
+// setup Barrett
+this.r2 = nbi();
+this.q3 = nbi();
+BigInteger.ONE.dlShiftTo(2*m.t,this.r2);
+this.mu = this.r2.divide(m);
+this.m = m;
+}
+
+function barrettConvert(x) {
+if(x.s < 0 || x.t > 2*this.m.t) return x.mod(this.m);
+else if(x.compareTo(this.m) < 0) return x;
+else { var r = nbi(); x.copyTo(r); this.reduce(r); return r; }
+}
+
+function barrettRevert(x) { return x; }
+
+//x = x mod m (HAC 14.42)
+function barrettReduce(x) {
+x.drShiftTo(this.m.t-1,this.r2);
+if(x.t > this.m.t+1) { x.t = this.m.t+1; x.clamp(); }
+this.mu.multiplyUpperTo(this.r2,this.m.t+1,this.q3);
+this.m.multiplyLowerTo(this.q3,this.m.t+1,this.r2);
+while(x.compareTo(this.r2) < 0) x.dAddOffset(1,this.m.t+1);
+x.subTo(this.r2,x);
+while(x.compareTo(this.m) >= 0) x.subTo(this.m,x);
+}
+
+//r = x^2 mod m; x != r
+function barrettSqrTo(x,r) { x.squareTo(r); this.reduce(r); }
+
+//r = x*y mod m; x,y != r
+function barrettMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); }
+
+Barrett.prototype.convert = barrettConvert;
+Barrett.prototype.revert = barrettRevert;
+Barrett.prototype.reduce = barrettReduce;
+Barrett.prototype.mulTo = barrettMulTo;
+Barrett.prototype.sqrTo = barrettSqrTo;
+
+//(public) this^e % m (HAC 14.85)
+function bnModPow(e,m) {
+var i = e.bitLength(), k, r = nbv(1), z;
+if(i <= 0) return r;
+else if(i < 18) k = 1;
+else if(i < 48) k = 3;
+else if(i < 144) k = 4;
+else if(i < 768) k = 5;
+else k = 6;
+if(i < 8)
+ z = new Classic(m);
+else if(m.isEven())
+ z = new Barrett(m);
+else
+ z = new Montgomery(m);
+
+// precomputation
+var g = new Array(), n = 3, k1 = k-1, km = (1<<k)-1;
+g[1] = z.convert(this);
+if(k > 1) {
+ var g2 = nbi();
+ z.sqrTo(g[1],g2);
+ while(n <= km) {
+   g[n] = nbi();
+   z.mulTo(g2,g[n-2],g[n]);
+   n += 2;
+ }
+}
+
+var j = e.t-1, w, is1 = true, r2 = nbi(), t;
+i = nbits(e.data[j])-1;
+while(j >= 0) {
+ if(i >= k1) w = (e.data[j]>>(i-k1))&km;
+ else {
+   w = (e.data[j]&((1<<(i+1))-1))<<(k1-i);
+   if(j > 0) w |= e.data[j-1]>>(this.DB+i-k1);
+ }
+
+ n = k;
+ while((w&1) == 0) { w >>= 1; --n; }
+ if((i -= n) < 0) { i += this.DB; --j; }
+ if(is1) {  // ret == 1, don't bother squaring or multiplying it
+   g[w].copyTo(r);
+   is1 = false;
+ } else {
+   while(n > 1) { z.sqrTo(r,r2); z.sqrTo(r2,r); n -= 2; }
+   if(n > 0) z.sqrTo(r,r2); else { t = r; r = r2; r2 = t; }
+   z.mulTo(r2,g[w],r);
+ }
+
+ while(j >= 0 && (e.data[j]&(1<<i)) == 0) {
+   z.sqrTo(r,r2); t = r; r = r2; r2 = t;
+   if(--i < 0) { i = this.DB-1; --j; }
+ }
+}
+return z.revert(r);
+}
+
+//(public) gcd(this,a) (HAC 14.54)
+function bnGCD(a) {
+var x = (this.s<0)?this.negate():this.clone();
+var y = (a.s<0)?a.negate():a.clone();
+if(x.compareTo(y) < 0) { var t = x; x = y; y = t; }
+var i = x.getLowestSetBit(), g = y.getLowestSetBit();
+if(g < 0) return x;
+if(i < g) g = i;
+if(g > 0) {
+ x.rShiftTo(g,x);
+ y.rShiftTo(g,y);
+}
+while(x.signum() > 0) {
+ if((i = x.getLowestSetBit()) > 0) x.rShiftTo(i,x);
+ if((i = y.getLowestSetBit()) > 0) y.rShiftTo(i,y);
+ if(x.compareTo(y) >= 0) {
+   x.subTo(y,x);
+   x.rShiftTo(1,x);
+ } else {
+   y.subTo(x,y);
+   y.rShiftTo(1,y);
+ }
+}
+if(g > 0) y.lShiftTo(g,y);
+return y;
+}
+
+//(protected) this % n, n < 2^26
+function bnpModInt(n) {
+if(n <= 0) return 0;
+var d = this.DV%n, r = (this.s<0)?n-1:0;
+if(this.t > 0)
+ if(d == 0) r = this.data[0]%n;
+ else for(var i = this.t-1; i >= 0; --i) r = (d*r+this.data[i])%n;
+return r;
+}
+
+//(public) 1/this % m (HAC 14.61)
+function bnModInverse(m) {
+var ac = m.isEven();
+if((this.isEven() && ac) || m.signum() == 0) return BigInteger.ZERO;
+var u = m.clone(), v = this.clone();
+var a = nbv(1), b = nbv(0), c = nbv(0), d = nbv(1);
+while(u.signum() != 0) {
+ while(u.isEven()) {
+   u.rShiftTo(1,u);
+   if(ac) {
+     if(!a.isEven() || !b.isEven()) { a.addTo(this,a); b.subTo(m,b); }
+     a.rShiftTo(1,a);
+   } else if(!b.isEven()) b.subTo(m,b);
+   b.rShiftTo(1,b);
+ }
+ while(v.isEven()) {
+   v.rShiftTo(1,v);
+   if(ac) {
+     if(!c.isEven() || !d.isEven()) { c.addTo(this,c); d.subTo(m,d); }
+     c.rShiftTo(1,c);
+   } else if(!d.isEven()) d.subTo(m,d);
+   d.rShiftTo(1,d);
+ }
+ if(u.compareTo(v) >= 0) {
+   u.subTo(v,u);
+   if(ac) a.subTo(c,a);
+   b.subTo(d,b);
+ } else {
+   v.subTo(u,v);
+   if(ac) c.subTo(a,c);
+   d.subTo(b,d);
+ }
+}
+if(v.compareTo(BigInteger.ONE) != 0) return BigInteger.ZERO;
+if(d.compareTo(m) >= 0) return d.subtract(m);
+if(d.signum() < 0) d.addTo(m,d); else return d;
+if(d.signum() < 0) return d.add(m); else return d;
+}
+
+var lowprimes = [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,503,509];
+var lplim = (1<<26)/lowprimes[lowprimes.length-1];
+
+//(public) test primality with certainty >= 1-.5^t
+function bnIsProbablePrime(t) {
+var i, x = this.abs();
+if(x.t == 1 && x.data[0] <= lowprimes[lowprimes.length-1]) {
+ for(i = 0; i < lowprimes.length; ++i)
+   if(x.data[0] == lowprimes[i]) return true;
+ return false;
+}
+if(x.isEven()) return false;
+i = 1;
+while(i < lowprimes.length) {
+ var m = lowprimes[i], j = i+1;
+ while(j < lowprimes.length && m < lplim) m *= lowprimes[j++];
+ m = x.modInt(m);
+ while(i < j) if(m%lowprimes[i++] == 0) return false;
+}
+return x.millerRabin(t);
+}
+
+//(protected) true if probably prime (HAC 4.24, Miller-Rabin)
+function bnpMillerRabin(t) {
+var n1 = this.subtract(BigInteger.ONE);
+var k = n1.getLowestSetBit();
+if(k <= 0) return false;
+var r = n1.shiftRight(k);
+var prng = bnGetPrng();
+var a;
+for(var i = 0; i < t; ++i) {
+ // select witness 'a' at random from between 1 and n1
+ do {
+   a = new BigInteger(this.bitLength(), prng);
+ }
+ while(a.compareTo(BigInteger.ONE) <= 0 || a.compareTo(n1) >= 0);
+ var y = a.modPow(r,this);
+ if(y.compareTo(BigInteger.ONE) != 0 && y.compareTo(n1) != 0) {
+   var j = 1;
+   while(j++ < k && y.compareTo(n1) != 0) {
+     y = y.modPowInt(2,this);
+     if(y.compareTo(BigInteger.ONE) == 0) return false;
+   }
+   if(y.compareTo(n1) != 0) return false;
+ }
+}
+return true;
+}
+
+// get pseudo random number generator
+function bnGetPrng() {
+  // create prng with api that matches BigInteger secure random
+  return {
+    // x is an array to fill with bytes
+    nextBytes: function(x) {
+      for(var i = 0; i < x.length; ++i) {
+        x[i] = Math.floor(Math.random() * 0xFF);
+      }
+    }
+  };
+}
+
+//protected
+BigInteger.prototype.chunkSize = bnpChunkSize;
+BigInteger.prototype.toRadix = bnpToRadix;
+BigInteger.prototype.fromRadix = bnpFromRadix;
+BigInteger.prototype.fromNumber = bnpFromNumber;
+BigInteger.prototype.bitwiseTo = bnpBitwiseTo;
+BigInteger.prototype.changeBit = bnpChangeBit;
+BigInteger.prototype.addTo = bnpAddTo;
+BigInteger.prototype.dMultiply = bnpDMultiply;
+BigInteger.prototype.dAddOffset = bnpDAddOffset;
+BigInteger.prototype.multiplyLowerTo = bnpMultiplyLowerTo;
+BigInteger.prototype.multiplyUpperTo = bnpMultiplyUpperTo;
+BigInteger.prototype.modInt = bnpModInt;
+BigInteger.prototype.millerRabin = bnpMillerRabin;
+
+//public
+BigInteger.prototype.clone = bnClone;
+BigInteger.prototype.intValue = bnIntValue;
+BigInteger.prototype.byteValue = bnByteValue;
+BigInteger.prototype.shortValue = bnShortValue;
+BigInteger.prototype.signum = bnSigNum;
+BigInteger.prototype.toByteArray = bnToByteArray;
+BigInteger.prototype.equals = bnEquals;
+BigInteger.prototype.min = bnMin;
+BigInteger.prototype.max = bnMax;
+BigInteger.prototype.and = bnAnd;
+BigInteger.prototype.or = bnOr;
+BigInteger.prototype.xor = bnXor;
+BigInteger.prototype.andNot = bnAndNot;
+BigInteger.prototype.not = bnNot;
+BigInteger.prototype.shiftLeft = bnShiftLeft;
+BigInteger.prototype.shiftRight = bnShiftRight;
+BigInteger.prototype.getLowestSetBit = bnGetLowestSetBit;
+BigInteger.prototype.bitCount = bnBitCount;
+BigInteger.prototype.testBit = bnTestBit;
+BigInteger.prototype.setBit = bnSetBit;
+BigInteger.prototype.clearBit = bnClearBit;
+BigInteger.prototype.flipBit = bnFlipBit;
+BigInteger.prototype.add = bnAdd;
+BigInteger.prototype.subtract = bnSubtract;
+BigInteger.prototype.multiply = bnMultiply;
+BigInteger.prototype.divide = bnDivide;
+BigInteger.prototype.remainder = bnRemainder;
+BigInteger.prototype.divideAndRemainder = bnDivideAndRemainder;
+BigInteger.prototype.modPow = bnModPow;
+BigInteger.prototype.modInverse = bnModInverse;
+BigInteger.prototype.pow = bnPow;
+BigInteger.prototype.gcd = bnGCD;
+BigInteger.prototype.isProbablePrime = bnIsProbablePrime;
+
+//BigInteger interfaces not implemented in jsbn:
+
+//BigInteger(int signum, byte[] magnitude)
+//double doubleValue()
+//float floatValue()
+//int hashCode()
+//long longValue()
+//static BigInteger valueOf(long val)
+
+forge.jsbn = forge.jsbn || {};
+forge.jsbn.BigInteger = BigInteger;
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'jsbn';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/kem.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/kem.js
new file mode 100644
index 0000000..7ac7851
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/kem.js
@@ -0,0 +1,221 @@
+/**
+ * Javascript implementation of RSA-KEM.
+ *
+ * @author Lautaro Cozzani Rodriguez
+ * @author Dave Longley
+ *
+ * Copyright (c) 2014 Lautaro Cozzani <lautaro.cozzani@scytl.com>
+ * Copyright (c) 2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+forge.kem = forge.kem || {};
+
+var BigInteger = forge.jsbn.BigInteger;
+
+/**
+ * The API for the RSA Key Encapsulation Mechanism (RSA-KEM) from ISO 18033-2.
+ */
+forge.kem.rsa = {};
+
+/**
+ * Creates an RSA KEM API object for generating a secret asymmetric key.
+ *
+ * The symmetric key may be generated via a call to 'encrypt', which will
+ * produce a ciphertext to be transmitted to the recipient and a key to be
+ * kept secret. The ciphertext is a parameter to be passed to 'decrypt' which
+ * will produce the same secret key for the recipient to use to decrypt a
+ * message that was encrypted with the secret key.
+ *
+ * @param kdf the KDF API to use (eg: new forge.kem.kdf1()).
+ * @param options the options to use.
+ *          [prng] a custom crypto-secure pseudo-random number generator to use,
+ *            that must define "getBytesSync".
+ */
+forge.kem.rsa.create = function(kdf, options) {
+  options = options || {};
+  var prng = options.prng || forge.random;
+
+  var kem = {};
+
+  /**
+   * Generates a secret key and its encapsulation.
+   *
+   * @param publicKey the RSA public key to encrypt with.
+   * @param keyLength the length, in bytes, of the secret key to generate.
+   *
+   * @return an object with:
+   *   encapsulation: the ciphertext for generating the secret key, as a
+   *     binary-encoded string of bytes.
+   *   key: the secret key to use for encrypting a message.
+   */
+  kem.encrypt = function(publicKey, keyLength) {
+    // generate a random r where 1 > r > n
+    var byteLength = Math.ceil(publicKey.n.bitLength() / 8);
+    var r;
+    do {
+      r = new BigInteger(
+        forge.util.bytesToHex(prng.getBytesSync(byteLength)),
+        16).mod(publicKey.n);
+    } while(r.equals(BigInteger.ZERO));
+
+    // prepend r with zeros
+    r = forge.util.hexToBytes(r.toString(16));
+    var zeros = byteLength - r.length;
+    if(zeros > 0) {
+      r = forge.util.fillString(String.fromCharCode(0), zeros) + r;
+    }
+
+    // encrypt the random
+    var encapsulation = publicKey.encrypt(r, 'NONE');
+
+    // generate the secret key
+    var key = kdf.generate(r, keyLength);
+
+    return {encapsulation: encapsulation, key: key};
+  };
+
+  /**
+   * Decrypts an encapsulated secret key.
+   *
+   * @param privateKey the RSA private key to decrypt with.
+   * @param encapsulation the ciphertext for generating the secret key, as
+   *          a binary-encoded string of bytes.
+   * @param keyLength the length, in bytes, of the secret key to generate.
+   *
+   * @return the secret key as a binary-encoded string of bytes.
+   */
+  kem.decrypt = function(privateKey, encapsulation, keyLength) {
+    // decrypt the encapsulation and generate the secret key
+    var r = privateKey.decrypt(encapsulation, 'NONE');
+    return kdf.generate(r, keyLength);
+  };
+
+  return kem;
+};
+
+// TODO: add forge.kem.kdf.create('KDF1', {md: ..., ...}) API?
+
+/**
+ * Creates a key derivation API object that implements KDF1 per ISO 18033-2.
+ *
+ * @param md the hash API to use.
+ * @param [digestLength] an optional digest length that must be positive and
+ *          less than or equal to md.digestLength.
+ *
+ * @return a KDF1 API object.
+ */
+forge.kem.kdf1 = function(md, digestLength) {
+  _createKDF(this, md, 0, digestLength || md.digestLength);
+};
+
+/**
+ * Creates a key derivation API object that implements KDF2 per ISO 18033-2.
+ *
+ * @param md the hash API to use.
+ * @param [digestLength] an optional digest length that must be positive and
+ *          less than or equal to md.digestLength.
+ *
+ * @return a KDF2 API object.
+ */
+forge.kem.kdf2 = function(md, digestLength) {
+  _createKDF(this, md, 1, digestLength || md.digestLength);
+};
+
+/**
+ * Creates a KDF1 or KDF2 API object.
+ *
+ * @param md the hash API to use.
+ * @param counterStart the starting index for the counter.
+ * @param digestLength the digest length to use.
+ *
+ * @return the KDF API object.
+ */
+function _createKDF(kdf, md, counterStart, digestLength) {
+  /**
+   * Generate a key of the specified length.
+   *
+   * @param x the binary-encoded byte string to generate a key from.
+   * @param length the number of bytes to generate (the size of the key).
+   *
+   * @return the key as a binary-encoded string.
+   */
+  kdf.generate = function(x, length) {
+    var key = new forge.util.ByteBuffer();
+
+    // run counter from counterStart to ceil(length / Hash.len)
+    var k = Math.ceil(length / digestLength) + counterStart;
+
+    var c = new forge.util.ByteBuffer();
+    for(var i = counterStart; i < k; ++i) {
+      // I2OSP(i, 4): convert counter to an octet string of 4 octets
+      c.putInt32(i);
+
+      // digest 'x' and the counter and add the result to the key
+      md.start();
+      md.update(x + c.getBytes());
+      var hash = md.digest();
+      key.putBytes(hash.getBytes(digestLength));
+    }
+
+    // truncate to the correct key length
+    key.truncate(key.length() - length);
+    return key.getBytes();
+  };
+}
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'kem';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './util','./random','./jsbn'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/log.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/log.js
new file mode 100644
index 0000000..c7931f5
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/log.js
@@ -0,0 +1,372 @@
+/**
+ * Cross-browser support for logging in a web application.
+ *
+ * @author David I. Lehn <dlehn@digitalbazaar.com>
+ *
+ * Copyright (c) 2008-2013 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+/* LOG API */
+forge.log = forge.log || {};
+
+/**
+ * Application logging system.
+ *
+ * Each logger level available as it's own function of the form:
+ *   forge.log.level(category, args...)
+ * The category is an arbitrary string, and the args are the same as
+ * Firebug's console.log API. By default the call will be output as:
+ *   'LEVEL [category] <args[0]>, args[1], ...'
+ * This enables proper % formatting via the first argument.
+ * Each category is enabled by default but can be enabled or disabled with
+ * the setCategoryEnabled() function.
+ */
+// list of known levels
+forge.log.levels = [
+  'none', 'error', 'warning', 'info', 'debug', 'verbose', 'max'];
+// info on the levels indexed by name:
+//   index: level index
+//   name: uppercased display name
+var sLevelInfo = {};
+// list of loggers
+var sLoggers = [];
+/**
+ * Standard console logger. If no console support is enabled this will
+ * remain null. Check before using.
+ */
+var sConsoleLogger = null;
+
+// logger flags
+/**
+ * Lock the level at the current value. Used in cases where user config may
+ * set the level such that only critical messages are seen but more verbose
+ * messages are needed for debugging or other purposes.
+ */
+forge.log.LEVEL_LOCKED = (1 << 1);
+/**
+ * Always call log function. By default, the logging system will check the
+ * message level against logger.level before calling the log function. This
+ * flag allows the function to do its own check.
+ */
+forge.log.NO_LEVEL_CHECK = (1 << 2);
+/**
+ * Perform message interpolation with the passed arguments. "%" style
+ * fields in log messages will be replaced by arguments as needed. Some
+ * loggers, such as Firebug, may do this automatically. The original log
+ * message will be available as 'message' and the interpolated version will
+ * be available as 'fullMessage'.
+ */
+forge.log.INTERPOLATE = (1 << 3);
+
+// setup each log level
+for(var i = 0; i < forge.log.levels.length; ++i) {
+  var level = forge.log.levels[i];
+  sLevelInfo[level] = {
+    index: i,
+    name: level.toUpperCase()
+  };
+}
+
+/**
+ * Message logger. Will dispatch a message to registered loggers as needed.
+ *
+ * @param message message object
+ */
+forge.log.logMessage = function(message) {
+  var messageLevelIndex = sLevelInfo[message.level].index;
+  for(var i = 0; i < sLoggers.length; ++i) {
+    var logger = sLoggers[i];
+    if(logger.flags & forge.log.NO_LEVEL_CHECK) {
+      logger.f(message);
+    } else {
+      // get logger level
+      var loggerLevelIndex = sLevelInfo[logger.level].index;
+      // check level
+      if(messageLevelIndex <= loggerLevelIndex) {
+        // message critical enough, call logger
+        logger.f(logger, message);
+      }
+    }
+  }
+};
+
+/**
+ * Sets the 'standard' key on a message object to:
+ * "LEVEL [category] " + message
+ *
+ * @param message a message log object
+ */
+forge.log.prepareStandard = function(message) {
+  if(!('standard' in message)) {
+    message.standard =
+      sLevelInfo[message.level].name +
+      //' ' + +message.timestamp +
+      ' [' + message.category + '] ' +
+      message.message;
+  }
+};
+
+/**
+ * Sets the 'full' key on a message object to the original message
+ * interpolated via % formatting with the message arguments.
+ *
+ * @param message a message log object.
+ */
+forge.log.prepareFull = function(message) {
+  if(!('full' in message)) {
+    // copy args and insert message at the front
+    var args = [message.message];
+    args = args.concat([] || message['arguments']);
+    // format the message
+    message.full = forge.util.format.apply(this, args);
+  }
+};
+
+/**
+ * Applies both preparseStandard() and prepareFull() to a message object and
+ * store result in 'standardFull'.
+ *
+ * @param message a message log object.
+ */
+forge.log.prepareStandardFull = function(message) {
+  if(!('standardFull' in message)) {
+    // FIXME implement 'standardFull' logging
+    forge.log.prepareStandard(message);
+    message.standardFull = message.standard;
+  }
+};
+
+// create log level functions
+if(true) {
+  // levels for which we want functions
+  var levels = ['error', 'warning', 'info', 'debug', 'verbose'];
+  for(var i = 0; i < levels.length; ++i) {
+    // wrap in a function to ensure proper level var is passed
+    (function(level) {
+      // create function for this level
+      forge.log[level] = function(category, message/*, args...*/) {
+        // convert arguments to real array, remove category and message
+        var args = Array.prototype.slice.call(arguments).slice(2);
+        // create message object
+        // Note: interpolation and standard formatting is done lazily
+        var msg = {
+          timestamp: new Date(),
+          level: level,
+          category: category,
+          message: message,
+          'arguments': args
+          /*standard*/
+          /*full*/
+          /*fullMessage*/
+        };
+        // process this message
+        forge.log.logMessage(msg);
+      };
+    })(levels[i]);
+  }
+}
+
+/**
+ * Creates a new logger with specified custom logging function.
+ *
+ * The logging function has a signature of:
+ *   function(logger, message)
+ * logger: current logger
+ * message: object:
+ *   level: level id
+ *   category: category
+ *   message: string message
+ *   arguments: Array of extra arguments
+ *   fullMessage: interpolated message and arguments if INTERPOLATE flag set
+ *
+ * @param logFunction a logging function which takes a log message object
+ *          as a parameter.
+ *
+ * @return a logger object.
+ */
+forge.log.makeLogger = function(logFunction) {
+  var logger = {
+    flags: 0,
+    f: logFunction
+  };
+  forge.log.setLevel(logger, 'none');
+  return logger;
+};
+
+/**
+ * Sets the current log level on a logger.
+ *
+ * @param logger the target logger.
+ * @param level the new maximum log level as a string.
+ *
+ * @return true if set, false if not.
+ */
+forge.log.setLevel = function(logger, level) {
+  var rval = false;
+  if(logger && !(logger.flags & forge.log.LEVEL_LOCKED)) {
+    for(var i = 0; i < forge.log.levels.length; ++i) {
+      var aValidLevel = forge.log.levels[i];
+      if(level == aValidLevel) {
+        // set level
+        logger.level = level;
+        rval = true;
+        break;
+      }
+    }
+  }
+
+  return rval;
+};
+
+/**
+ * Locks the log level at its current value.
+ *
+ * @param logger the target logger.
+ * @param lock boolean lock value, default to true.
+ */
+forge.log.lock = function(logger, lock) {
+  if(typeof lock === 'undefined' || lock) {
+    logger.flags |= forge.log.LEVEL_LOCKED;
+  } else {
+    logger.flags &= ~forge.log.LEVEL_LOCKED;
+  }
+};
+
+/**
+ * Adds a logger.
+ *
+ * @param logger the logger object.
+ */
+forge.log.addLogger = function(logger) {
+  sLoggers.push(logger);
+};
+
+// setup the console logger if possible, else create fake console.log
+if(typeof(console) !== 'undefined' && 'log' in console) {
+  var logger;
+  if(console.error && console.warn && console.info && console.debug) {
+    // looks like Firebug-style logging is available
+    // level handlers map
+    var levelHandlers = {
+      error: console.error,
+      warning: console.warn,
+      info: console.info,
+      debug: console.debug,
+      verbose: console.debug
+    };
+    var f = function(logger, message) {
+      forge.log.prepareStandard(message);
+      var handler = levelHandlers[message.level];
+      // prepend standard message and concat args
+      var args = [message.standard];
+      args = args.concat(message['arguments'].slice());
+      // apply to low-level console function
+      handler.apply(console, args);
+    };
+    logger = forge.log.makeLogger(f);
+  } else {
+    // only appear to have basic console.log
+    var f = function(logger, message) {
+      forge.log.prepareStandardFull(message);
+      console.log(message.standardFull);
+    };
+    logger = forge.log.makeLogger(f);
+  }
+  forge.log.setLevel(logger, 'debug');
+  forge.log.addLogger(logger);
+  sConsoleLogger = logger;
+} else {
+  // define fake console.log to avoid potential script errors on
+  // browsers that do not have console logging
+  console = {
+    log: function() {}
+  };
+}
+
+/*
+ * Check for logging control query vars.
+ *
+ * console.level=<level-name>
+ * Set's the console log level by name.  Useful to override defaults and
+ * allow more verbose logging before a user config is loaded.
+ *
+ * console.lock=<true|false>
+ * Lock the console log level at whatever level it is set at.  This is run
+ * after console.level is processed.  Useful to force a level of verbosity
+ * that could otherwise be limited by a user config.
+ */
+if(sConsoleLogger !== null) {
+  var query = forge.util.getQueryVariables();
+  if('console.level' in query) {
+    // set with last value
+    forge.log.setLevel(
+      sConsoleLogger, query['console.level'].slice(-1)[0]);
+  }
+  if('console.lock' in query) {
+    // set with last value
+    var lock = query['console.lock'].slice(-1)[0];
+    if(lock == 'true') {
+      forge.log.lock(sConsoleLogger);
+    }
+  }
+}
+
+// provide public access to console logger
+forge.log.consoleLogger = sConsoleLogger;
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'log';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/md.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/md.js
new file mode 100644
index 0000000..e980cfd
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/md.js
@@ -0,0 +1,75 @@
+/**
+ * Node.js module for Forge message digests.
+ *
+ * @author Dave Longley
+ *
+ * Copyright 2011-2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+forge.md = forge.md || {};
+forge.md.algorithms = {
+  md5: forge.md5,
+  sha1: forge.sha1,
+  sha256: forge.sha256
+};
+forge.md.md5 = forge.md5;
+forge.md.sha1 = forge.sha1;
+forge.md.sha256 = forge.sha256;
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'md';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(
+  ['require', 'module', './md5', './sha1', './sha256', './sha512'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/md5.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/md5.js
new file mode 100644
index 0000000..acf7d11
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/md5.js
@@ -0,0 +1,322 @@
+/**
+ * Message Digest Algorithm 5 with 128-bit digest (MD5) implementation.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+var md5 = forge.md5 = forge.md5 || {};
+forge.md = forge.md || {};
+forge.md.algorithms = forge.md.algorithms || {};
+forge.md.md5 = forge.md.algorithms.md5 = md5;
+
+/**
+ * Creates an MD5 message digest object.
+ *
+ * @return a message digest object.
+ */
+md5.create = function() {
+  // do initialization as necessary
+  if(!_initialized) {
+    _init();
+  }
+
+  // MD5 state contains four 32-bit integers
+  var _state = null;
+
+  // input buffer
+  var _input = forge.util.createBuffer();
+
+  // used for word storage
+  var _w = new Array(16);
+
+  // message digest object
+  var md = {
+    algorithm: 'md5',
+    blockLength: 64,
+    digestLength: 16,
+    // 56-bit length of message so far (does not including padding)
+    messageLength: 0,
+    // true 64-bit message length as two 32-bit ints
+    messageLength64: [0, 0]
+  };
+
+  /**
+   * Starts the digest.
+   *
+   * @return this digest object.
+   */
+  md.start = function() {
+    md.messageLength = 0;
+    md.messageLength64 = [0, 0];
+    _input = forge.util.createBuffer();
+    _state = {
+      h0: 0x67452301,
+      h1: 0xEFCDAB89,
+      h2: 0x98BADCFE,
+      h3: 0x10325476
+    };
+    return md;
+  };
+  // start digest automatically for first time
+  md.start();
+
+  /**
+   * Updates the digest with the given message input. The given input can
+   * treated as raw input (no encoding will be applied) or an encoding of
+   * 'utf8' maybe given to encode the input using UTF-8.
+   *
+   * @param msg the message input to update with.
+   * @param encoding the encoding to use (default: 'raw', other: 'utf8').
+   *
+   * @return this digest object.
+   */
+  md.update = function(msg, encoding) {
+    if(encoding === 'utf8') {
+      msg = forge.util.encodeUtf8(msg);
+    }
+
+    // update message length
+    md.messageLength += msg.length;
+    md.messageLength64[0] += (msg.length / 0x100000000) >>> 0;
+    md.messageLength64[1] += msg.length >>> 0;
+
+    // add bytes to input buffer
+    _input.putBytes(msg);
+
+    // process bytes
+    _update(_state, _w, _input);
+
+    // compact input buffer every 2K or if empty
+    if(_input.read > 2048 || _input.length() === 0) {
+      _input.compact();
+    }
+
+    return md;
+  };
+
+  /**
+   * Produces the digest.
+   *
+   * @return a byte buffer containing the digest value.
+   */
+  md.digest = function() {
+    /* Note: Here we copy the remaining bytes in the input buffer and
+    add the appropriate MD5 padding. Then we do the final update
+    on a copy of the state so that if the user wants to get
+    intermediate digests they can do so. */
+
+    /* Determine the number of bytes that must be added to the message
+    to ensure its length is congruent to 448 mod 512. In other words,
+    the data to be digested must be a multiple of 512 bits (or 128 bytes).
+    This data includes the message, some padding, and the length of the
+    message. Since the length of the message will be encoded as 8 bytes (64
+    bits), that means that the last segment of the data must have 56 bytes
+    (448 bits) of message and padding. Therefore, the length of the message
+    plus the padding must be congruent to 448 mod 512 because
+    512 - 128 = 448.
+
+    In order to fill up the message length it must be filled with
+    padding that begins with 1 bit followed by all 0 bits. Padding
+    must *always* be present, so if the message length is already
+    congruent to 448 mod 512, then 512 padding bits must be added. */
+
+    // 512 bits == 64 bytes, 448 bits == 56 bytes, 64 bits = 8 bytes
+    // _padding starts with 1 byte with first bit is set in it which
+    // is byte value 128, then there may be up to 63 other pad bytes
+    var padBytes = forge.util.createBuffer();
+    padBytes.putBytes(_input.bytes());
+    // 64 - (remaining msg + 8 bytes msg length) mod 64
+    padBytes.putBytes(
+      _padding.substr(0, 64 - ((md.messageLength64[1] + 8) & 0x3F)));
+
+    /* Now append length of the message. The length is appended in bits
+    as a 64-bit number in little-endian order. Since we store the length in
+    bytes, we must multiply the 64-bit length by 8 (or left shift by 3). */
+    padBytes.putInt32Le(md.messageLength64[1] << 3);
+    padBytes.putInt32Le(
+      (md.messageLength64[0] << 3) | (md.messageLength64[0] >>> 28));
+    var s2 = {
+      h0: _state.h0,
+      h1: _state.h1,
+      h2: _state.h2,
+      h3: _state.h3
+    };
+    _update(s2, _w, padBytes);
+    var rval = forge.util.createBuffer();
+    rval.putInt32Le(s2.h0);
+    rval.putInt32Le(s2.h1);
+    rval.putInt32Le(s2.h2);
+    rval.putInt32Le(s2.h3);
+    return rval;
+  };
+
+  return md;
+};
+
+// padding, constant tables for calculating md5
+var _padding = null;
+var _g = null;
+var _r = null;
+var _k = null;
+var _initialized = false;
+
+/**
+ * Initializes the constant tables.
+ */
+function _init() {
+  // create padding
+  _padding = String.fromCharCode(128);
+  _padding += forge.util.fillString(String.fromCharCode(0x00), 64);
+
+  // g values
+  _g = [
+    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+    1, 6, 11, 0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12,
+    5, 8, 11, 14, 1, 4, 7, 10, 13, 0, 3, 6, 9, 12, 15, 2,
+    0, 7, 14, 5, 12, 3, 10, 1, 8, 15, 6, 13, 4, 11, 2, 9];
+
+  // rounds table
+  _r = [
+    7, 12, 17, 22,  7, 12, 17, 22,  7, 12, 17, 22,  7, 12, 17, 22,
+    5,  9, 14, 20,  5,  9, 14, 20,  5,  9, 14, 20,  5,  9, 14, 20,
+    4, 11, 16, 23,  4, 11, 16, 23,  4, 11, 16, 23,  4, 11, 16, 23,
+    6, 10, 15, 21,  6, 10, 15, 21,  6, 10, 15, 21,  6, 10, 15, 21];
+
+  // get the result of abs(sin(i + 1)) as a 32-bit integer
+  _k = new Array(64);
+  for(var i = 0; i < 64; ++i) {
+    _k[i] = Math.floor(Math.abs(Math.sin(i + 1)) * 0x100000000);
+  }
+
+  // now initialized
+  _initialized = true;
+}
+
+/**
+ * Updates an MD5 state with the given byte buffer.
+ *
+ * @param s the MD5 state to update.
+ * @param w the array to use to store words.
+ * @param bytes the byte buffer to update with.
+ */
+function _update(s, w, bytes) {
+  // consume 512 bit (64 byte) chunks
+  var t, a, b, c, d, f, r, i;
+  var len = bytes.length();
+  while(len >= 64) {
+    // initialize hash value for this chunk
+    a = s.h0;
+    b = s.h1;
+    c = s.h2;
+    d = s.h3;
+
+    // round 1
+    for(i = 0; i < 16; ++i) {
+      w[i] = bytes.getInt32Le();
+      f = d ^ (b & (c ^ d));
+      t = (a + f + _k[i] + w[i]);
+      r = _r[i];
+      a = d;
+      d = c;
+      c = b;
+      b += (t << r) | (t >>> (32 - r));
+    }
+    // round 2
+    for(; i < 32; ++i) {
+      f = c ^ (d & (b ^ c));
+      t = (a + f + _k[i] + w[_g[i]]);
+      r = _r[i];
+      a = d;
+      d = c;
+      c = b;
+      b += (t << r) | (t >>> (32 - r));
+    }
+    // round 3
+    for(; i < 48; ++i) {
+      f = b ^ c ^ d;
+      t = (a + f + _k[i] + w[_g[i]]);
+      r = _r[i];
+      a = d;
+      d = c;
+      c = b;
+      b += (t << r) | (t >>> (32 - r));
+    }
+    // round 4
+    for(; i < 64; ++i) {
+      f = c ^ (b | ~d);
+      t = (a + f + _k[i] + w[_g[i]]);
+      r = _r[i];
+      a = d;
+      d = c;
+      c = b;
+      b += (t << r) | (t >>> (32 - r));
+    }
+
+    // update hash state
+    s.h0 = (s.h0 + a) | 0;
+    s.h1 = (s.h1 + b) | 0;
+    s.h2 = (s.h2 + c) | 0;
+    s.h3 = (s.h3 + d) | 0;
+
+    len -= 64;
+  }
+}
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'md5';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/mgf.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/mgf.js
new file mode 100644
index 0000000..927082a
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/mgf.js
@@ -0,0 +1,67 @@
+/**
+ * Node.js module for Forge mask generation functions.
+ *
+ * @author Stefan Siegl
+ *
+ * Copyright 2012 Stefan Siegl <stesie@brokenpipe.de>
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+forge.mgf = forge.mgf || {};
+forge.mgf.mgf1 = forge.mgf1;
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'mgf';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './mgf1'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/mgf1.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/mgf1.js
new file mode 100644
index 0000000..82d62cd
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/mgf1.js
@@ -0,0 +1,112 @@
+/**
+ * Javascript implementation of mask generation function MGF1.
+ *
+ * @author Stefan Siegl
+ * @author Dave Longley
+ *
+ * Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
+ * Copyright (c) 2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+forge.mgf = forge.mgf || {};
+var mgf1 = forge.mgf.mgf1 = forge.mgf1 = forge.mgf1 || {};
+
+/**
+ * Creates a MGF1 mask generation function object.
+ *
+ * @param md the message digest API to use (eg: forge.md.sha1.create()).
+ *
+ * @return a mask generation function object.
+ */
+mgf1.create = function(md) {
+  var mgf = {
+    /**
+     * Generate mask of specified length.
+     *
+     * @param {String} seed The seed for mask generation.
+     * @param maskLen Number of bytes to generate.
+     * @return {String} The generated mask.
+     */
+    generate: function(seed, maskLen) {
+      /* 2. Let T be the empty octet string. */
+      var t = new forge.util.ByteBuffer();
+
+      /* 3. For counter from 0 to ceil(maskLen / hLen), do the following: */
+      var len = Math.ceil(maskLen / md.digestLength);
+      for(var i = 0; i < len; i++) {
+        /* a. Convert counter to an octet string C of length 4 octets */
+        var c = new forge.util.ByteBuffer();
+        c.putInt32(i);
+
+        /* b. Concatenate the hash of the seed mgfSeed and C to the octet
+         * string T: */
+        md.start();
+        md.update(seed + c.getBytes());
+        t.putBuffer(md.digest());
+      }
+
+      /* Output the leading maskLen octets of T as the octet string mask. */
+      t.truncate(t.length() - maskLen);
+      return t.getBytes();
+    }
+  };
+
+  return mgf;
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'mgf1';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/oids.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/oids.js
new file mode 100644
index 0000000..ef3e67d
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/oids.js
@@ -0,0 +1,269 @@
+/**
+ * Object IDs for ASN.1.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2013 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+forge.pki = forge.pki || {};
+var oids = forge.pki.oids = forge.oids = forge.oids || {};
+
+// algorithm OIDs
+oids['1.2.840.113549.1.1.1'] = 'rsaEncryption';
+oids['rsaEncryption'] = '1.2.840.113549.1.1.1';
+// Note: md2 & md4 not implemented
+//oids['1.2.840.113549.1.1.2'] = 'md2WithRSAEncryption';
+//oids['md2WithRSAEncryption'] = '1.2.840.113549.1.1.2';
+//oids['1.2.840.113549.1.1.3'] = 'md4WithRSAEncryption';
+//oids['md4WithRSAEncryption'] = '1.2.840.113549.1.1.3';
+oids['1.2.840.113549.1.1.4'] = 'md5WithRSAEncryption';
+oids['md5WithRSAEncryption'] = '1.2.840.113549.1.1.4';
+oids['1.2.840.113549.1.1.5'] = 'sha1WithRSAEncryption';
+oids['sha1WithRSAEncryption'] = '1.2.840.113549.1.1.5';
+oids['1.2.840.113549.1.1.7'] = 'RSAES-OAEP';
+oids['RSAES-OAEP'] = '1.2.840.113549.1.1.7';
+oids['1.2.840.113549.1.1.8'] = 'mgf1';
+oids['mgf1'] = '1.2.840.113549.1.1.8';
+oids['1.2.840.113549.1.1.9'] = 'pSpecified';
+oids['pSpecified'] = '1.2.840.113549.1.1.9';
+oids['1.2.840.113549.1.1.10'] = 'RSASSA-PSS';
+oids['RSASSA-PSS'] = '1.2.840.113549.1.1.10';
+oids['1.2.840.113549.1.1.11'] = 'sha256WithRSAEncryption';
+oids['sha256WithRSAEncryption'] = '1.2.840.113549.1.1.11';
+oids['1.2.840.113549.1.1.12'] = 'sha384WithRSAEncryption';
+oids['sha384WithRSAEncryption'] = '1.2.840.113549.1.1.12';
+oids['1.2.840.113549.1.1.13'] = 'sha512WithRSAEncryption';
+oids['sha512WithRSAEncryption'] = '1.2.840.113549.1.1.13';
+
+oids['1.3.14.3.2.7'] = 'desCBC';
+oids['desCBC'] = '1.3.14.3.2.7';
+
+oids['1.3.14.3.2.26'] = 'sha1';
+oids['sha1'] = '1.3.14.3.2.26';
+oids['2.16.840.1.101.3.4.2.1'] = 'sha256';
+oids['sha256'] = '2.16.840.1.101.3.4.2.1';
+oids['2.16.840.1.101.3.4.2.2'] = 'sha384';
+oids['sha384'] = '2.16.840.1.101.3.4.2.2';
+oids['2.16.840.1.101.3.4.2.3'] = 'sha512';
+oids['sha512'] = '2.16.840.1.101.3.4.2.3';
+oids['1.2.840.113549.2.5'] = 'md5';
+oids['md5'] = '1.2.840.113549.2.5';
+
+// pkcs#7 content types
+oids['1.2.840.113549.1.7.1'] = 'data';
+oids['data'] = '1.2.840.113549.1.7.1';
+oids['1.2.840.113549.1.7.2'] = 'signedData';
+oids['signedData'] = '1.2.840.113549.1.7.2';
+oids['1.2.840.113549.1.7.3'] = 'envelopedData';
+oids['envelopedData'] = '1.2.840.113549.1.7.3';
+oids['1.2.840.113549.1.7.4'] = 'signedAndEnvelopedData';
+oids['signedAndEnvelopedData'] = '1.2.840.113549.1.7.4';
+oids['1.2.840.113549.1.7.5'] = 'digestedData';
+oids['digestedData'] = '1.2.840.113549.1.7.5';
+oids['1.2.840.113549.1.7.6'] = 'encryptedData';
+oids['encryptedData'] = '1.2.840.113549.1.7.6';
+
+// pkcs#9 oids
+oids['1.2.840.113549.1.9.1'] = 'emailAddress';
+oids['emailAddress'] = '1.2.840.113549.1.9.1';
+oids['1.2.840.113549.1.9.2'] = 'unstructuredName';
+oids['unstructuredName'] = '1.2.840.113549.1.9.2';
+oids['1.2.840.113549.1.9.3'] = 'contentType';
+oids['contentType'] = '1.2.840.113549.1.9.3';
+oids['1.2.840.113549.1.9.4'] = 'messageDigest';
+oids['messageDigest'] = '1.2.840.113549.1.9.4';
+oids['1.2.840.113549.1.9.5'] = 'signingTime';
+oids['signingTime'] = '1.2.840.113549.1.9.5';
+oids['1.2.840.113549.1.9.6'] = 'counterSignature';
+oids['counterSignature'] = '1.2.840.113549.1.9.6';
+oids['1.2.840.113549.1.9.7'] = 'challengePassword';
+oids['challengePassword'] = '1.2.840.113549.1.9.7';
+oids['1.2.840.113549.1.9.8'] = 'unstructuredAddress';
+oids['unstructuredAddress'] = '1.2.840.113549.1.9.8';
+oids['1.2.840.113549.1.9.14'] = 'extensionRequest';
+oids['extensionRequest'] = '1.2.840.113549.1.9.14';
+
+oids['1.2.840.113549.1.9.20'] = 'friendlyName';
+oids['friendlyName'] = '1.2.840.113549.1.9.20';
+oids['1.2.840.113549.1.9.21'] = 'localKeyId';
+oids['localKeyId'] = '1.2.840.113549.1.9.21';
+oids['1.2.840.113549.1.9.22.1'] = 'x509Certificate';
+oids['x509Certificate'] = '1.2.840.113549.1.9.22.1';
+
+// pkcs#12 safe bags
+oids['1.2.840.113549.1.12.10.1.1'] = 'keyBag';
+oids['keyBag'] = '1.2.840.113549.1.12.10.1.1';
+oids['1.2.840.113549.1.12.10.1.2'] = 'pkcs8ShroudedKeyBag';
+oids['pkcs8ShroudedKeyBag'] = '1.2.840.113549.1.12.10.1.2';
+oids['1.2.840.113549.1.12.10.1.3'] = 'certBag';
+oids['certBag'] = '1.2.840.113549.1.12.10.1.3';
+oids['1.2.840.113549.1.12.10.1.4'] = 'crlBag';
+oids['crlBag'] = '1.2.840.113549.1.12.10.1.4';
+oids['1.2.840.113549.1.12.10.1.5'] = 'secretBag';
+oids['secretBag'] = '1.2.840.113549.1.12.10.1.5';
+oids['1.2.840.113549.1.12.10.1.6'] = 'safeContentsBag';
+oids['safeContentsBag'] = '1.2.840.113549.1.12.10.1.6';
+
+// password-based-encryption for pkcs#12
+oids['1.2.840.113549.1.5.13'] = 'pkcs5PBES2';
+oids['pkcs5PBES2'] = '1.2.840.113549.1.5.13';
+oids['1.2.840.113549.1.5.12'] = 'pkcs5PBKDF2';
+oids['pkcs5PBKDF2'] = '1.2.840.113549.1.5.12';
+
+oids['1.2.840.113549.1.12.1.1'] = 'pbeWithSHAAnd128BitRC4';
+oids['pbeWithSHAAnd128BitRC4'] = '1.2.840.113549.1.12.1.1';
+oids['1.2.840.113549.1.12.1.2'] = 'pbeWithSHAAnd40BitRC4';
+oids['pbeWithSHAAnd40BitRC4'] = '1.2.840.113549.1.12.1.2';
+oids['1.2.840.113549.1.12.1.3'] = 'pbeWithSHAAnd3-KeyTripleDES-CBC';
+oids['pbeWithSHAAnd3-KeyTripleDES-CBC'] = '1.2.840.113549.1.12.1.3';
+oids['1.2.840.113549.1.12.1.4'] = 'pbeWithSHAAnd2-KeyTripleDES-CBC';
+oids['pbeWithSHAAnd2-KeyTripleDES-CBC'] = '1.2.840.113549.1.12.1.4';
+oids['1.2.840.113549.1.12.1.5'] = 'pbeWithSHAAnd128BitRC2-CBC';
+oids['pbeWithSHAAnd128BitRC2-CBC'] = '1.2.840.113549.1.12.1.5';
+oids['1.2.840.113549.1.12.1.6'] = 'pbewithSHAAnd40BitRC2-CBC';
+oids['pbewithSHAAnd40BitRC2-CBC'] = '1.2.840.113549.1.12.1.6';
+
+// symmetric key algorithm oids
+oids['1.2.840.113549.3.7'] = 'des-EDE3-CBC';
+oids['des-EDE3-CBC'] = '1.2.840.113549.3.7';
+oids['2.16.840.1.101.3.4.1.2'] = 'aes128-CBC';
+oids['aes128-CBC'] = '2.16.840.1.101.3.4.1.2';
+oids['2.16.840.1.101.3.4.1.22'] = 'aes192-CBC';
+oids['aes192-CBC'] = '2.16.840.1.101.3.4.1.22';
+oids['2.16.840.1.101.3.4.1.42'] = 'aes256-CBC';
+oids['aes256-CBC'] = '2.16.840.1.101.3.4.1.42';
+
+// certificate issuer/subject OIDs
+oids['2.5.4.3'] = 'commonName';
+oids['commonName'] = '2.5.4.3';
+oids['2.5.4.5'] = 'serialName';
+oids['serialName'] = '2.5.4.5';
+oids['2.5.4.6'] = 'countryName';
+oids['countryName'] = '2.5.4.6';
+oids['2.5.4.7'] = 'localityName';
+oids['localityName'] = '2.5.4.7';
+oids['2.5.4.8'] = 'stateOrProvinceName';
+oids['stateOrProvinceName'] = '2.5.4.8';
+oids['2.5.4.10'] = 'organizationName';
+oids['organizationName'] = '2.5.4.10';
+oids['2.5.4.11'] = 'organizationalUnitName';
+oids['organizationalUnitName'] = '2.5.4.11';
+
+// X.509 extension OIDs
+oids['2.16.840.1.113730.1.1'] = 'nsCertType';
+oids['nsCertType'] = '2.16.840.1.113730.1.1';
+oids['2.5.29.1'] = 'authorityKeyIdentifier'; // deprecated, use .35
+oids['2.5.29.2'] = 'keyAttributes'; // obsolete use .37 or .15
+oids['2.5.29.3'] = 'certificatePolicies'; // deprecated, use .32
+oids['2.5.29.4'] = 'keyUsageRestriction'; // obsolete use .37 or .15
+oids['2.5.29.5'] = 'policyMapping'; // deprecated use .33
+oids['2.5.29.6'] = 'subtreesConstraint'; // obsolete use .30
+oids['2.5.29.7'] = 'subjectAltName'; // deprecated use .17
+oids['2.5.29.8'] = 'issuerAltName'; // deprecated use .18
+oids['2.5.29.9'] = 'subjectDirectoryAttributes';
+oids['2.5.29.10'] = 'basicConstraints'; // deprecated use .19
+oids['2.5.29.11'] = 'nameConstraints'; // deprecated use .30
+oids['2.5.29.12'] = 'policyConstraints'; // deprecated use .36
+oids['2.5.29.13'] = 'basicConstraints'; // deprecated use .19
+oids['2.5.29.14'] = 'subjectKeyIdentifier';
+oids['subjectKeyIdentifier'] = '2.5.29.14';
+oids['2.5.29.15'] = 'keyUsage';
+oids['keyUsage'] = '2.5.29.15';
+oids['2.5.29.16'] = 'privateKeyUsagePeriod';
+oids['2.5.29.17'] = 'subjectAltName';
+oids['subjectAltName'] = '2.5.29.17';
+oids['2.5.29.18'] = 'issuerAltName';
+oids['issuerAltName'] = '2.5.29.18';
+oids['2.5.29.19'] = 'basicConstraints';
+oids['basicConstraints'] = '2.5.29.19';
+oids['2.5.29.20'] = 'cRLNumber';
+oids['2.5.29.21'] = 'cRLReason';
+oids['2.5.29.22'] = 'expirationDate';
+oids['2.5.29.23'] = 'instructionCode';
+oids['2.5.29.24'] = 'invalidityDate';
+oids['2.5.29.25'] = 'cRLDistributionPoints'; // deprecated use .31
+oids['2.5.29.26'] = 'issuingDistributionPoint'; // deprecated use .28
+oids['2.5.29.27'] = 'deltaCRLIndicator';
+oids['2.5.29.28'] = 'issuingDistributionPoint';
+oids['2.5.29.29'] = 'certificateIssuer';
+oids['2.5.29.30'] = 'nameConstraints';
+oids['2.5.29.31'] = 'cRLDistributionPoints';
+oids['2.5.29.32'] = 'certificatePolicies';
+oids['2.5.29.33'] = 'policyMappings';
+oids['2.5.29.34'] = 'policyConstraints'; // deprecated use .36
+oids['2.5.29.35'] = 'authorityKeyIdentifier';
+oids['2.5.29.36'] = 'policyConstraints';
+oids['2.5.29.37'] = 'extKeyUsage';
+oids['extKeyUsage'] = '2.5.29.37';
+oids['2.5.29.46'] = 'freshestCRL';
+oids['2.5.29.54'] = 'inhibitAnyPolicy';
+
+// extKeyUsage purposes
+oids['1.3.6.1.5.5.7.3.1'] = 'serverAuth';
+oids['serverAuth'] = '1.3.6.1.5.5.7.3.1';
+oids['1.3.6.1.5.5.7.3.2'] = 'clientAuth';
+oids['clientAuth'] = '1.3.6.1.5.5.7.3.2';
+oids['1.3.6.1.5.5.7.3.3'] = 'codeSigning';
+oids['codeSigning'] = '1.3.6.1.5.5.7.3.3';
+oids['1.3.6.1.5.5.7.3.4'] = 'emailProtection';
+oids['emailProtection'] = '1.3.6.1.5.5.7.3.4';
+oids['1.3.6.1.5.5.7.3.8'] = 'timeStamping';
+oids['timeStamping'] = '1.3.6.1.5.5.7.3.8';
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'oids';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/pbe.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/pbe.js
new file mode 100644
index 0000000..0b25758
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/pbe.js
@@ -0,0 +1,975 @@
+/**
+ * Password-based encryption functions.
+ *
+ * @author Dave Longley
+ * @author Stefan Siegl <stesie@brokenpipe.de>
+ *
+ * Copyright (c) 2010-2013 Digital Bazaar, Inc.
+ * Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
+ *
+ * An EncryptedPrivateKeyInfo:
+ *
+ * EncryptedPrivateKeyInfo ::= SEQUENCE {
+ *   encryptionAlgorithm  EncryptionAlgorithmIdentifier,
+ *   encryptedData        EncryptedData }
+ *
+ * EncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
+ *
+ * EncryptedData ::= OCTET STRING
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+if(typeof BigInteger === 'undefined') {
+  var BigInteger = forge.jsbn.BigInteger;
+}
+
+// shortcut for asn.1 API
+var asn1 = forge.asn1;
+
+/* Password-based encryption implementation. */
+var pki = forge.pki = forge.pki || {};
+pki.pbe = forge.pbe = forge.pbe || {};
+var oids = pki.oids;
+
+// validator for an EncryptedPrivateKeyInfo structure
+// Note: Currently only works w/algorithm params
+var encryptedPrivateKeyValidator = {
+  name: 'EncryptedPrivateKeyInfo',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'EncryptedPrivateKeyInfo.encryptionAlgorithm',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true,
+    value: [{
+      name: 'AlgorithmIdentifier.algorithm',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.OID,
+      constructed: false,
+      capture: 'encryptionOid'
+    }, {
+      name: 'AlgorithmIdentifier.parameters',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.SEQUENCE,
+      constructed: true,
+      captureAsn1: 'encryptionParams'
+    }]
+  }, {
+    // encryptedData
+    name: 'EncryptedPrivateKeyInfo.encryptedData',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.OCTETSTRING,
+    constructed: false,
+    capture: 'encryptedData'
+  }]
+};
+
+// validator for a PBES2Algorithms structure
+// Note: Currently only works w/PBKDF2 + AES encryption schemes
+var PBES2AlgorithmsValidator = {
+  name: 'PBES2Algorithms',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'PBES2Algorithms.keyDerivationFunc',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true,
+    value: [{
+      name: 'PBES2Algorithms.keyDerivationFunc.oid',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.OID,
+      constructed: false,
+      capture: 'kdfOid'
+    }, {
+      name: 'PBES2Algorithms.params',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.SEQUENCE,
+      constructed: true,
+      value: [{
+        name: 'PBES2Algorithms.params.salt',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.OCTETSTRING,
+        constructed: false,
+        capture: 'kdfSalt'
+      }, {
+        name: 'PBES2Algorithms.params.iterationCount',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.INTEGER,
+        onstructed: true,
+        capture: 'kdfIterationCount'
+      }]
+    }]
+  }, {
+    name: 'PBES2Algorithms.encryptionScheme',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true,
+    value: [{
+      name: 'PBES2Algorithms.encryptionScheme.oid',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.OID,
+      constructed: false,
+      capture: 'encOid'
+    }, {
+      name: 'PBES2Algorithms.encryptionScheme.iv',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.OCTETSTRING,
+      constructed: false,
+      capture: 'encIv'
+    }]
+  }]
+};
+
+var pkcs12PbeParamsValidator = {
+  name: 'pkcs-12PbeParams',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'pkcs-12PbeParams.salt',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.OCTETSTRING,
+    constructed: false,
+    capture: 'salt'
+  }, {
+    name: 'pkcs-12PbeParams.iterations',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'iterations'
+  }]
+};
+
+/**
+ * Encrypts a ASN.1 PrivateKeyInfo object, producing an EncryptedPrivateKeyInfo.
+ *
+ * PBES2Algorithms ALGORITHM-IDENTIFIER ::=
+ *   { {PBES2-params IDENTIFIED BY id-PBES2}, ...}
+ *
+ * id-PBES2 OBJECT IDENTIFIER ::= {pkcs-5 13}
+ *
+ * PBES2-params ::= SEQUENCE {
+ *   keyDerivationFunc AlgorithmIdentifier {{PBES2-KDFs}},
+ *   encryptionScheme AlgorithmIdentifier {{PBES2-Encs}}
+ * }
+ *
+ * PBES2-KDFs ALGORITHM-IDENTIFIER ::=
+ *   { {PBKDF2-params IDENTIFIED BY id-PBKDF2}, ... }
+ *
+ * PBES2-Encs ALGORITHM-IDENTIFIER ::= { ... }
+ *
+ * PBKDF2-params ::= SEQUENCE {
+ *   salt CHOICE {
+ *     specified OCTET STRING,
+ *     otherSource AlgorithmIdentifier {{PBKDF2-SaltSources}}
+ *   },
+ *   iterationCount INTEGER (1..MAX),
+ *   keyLength INTEGER (1..MAX) OPTIONAL,
+ *   prf AlgorithmIdentifier {{PBKDF2-PRFs}} DEFAULT algid-hmacWithSHA1
+ * }
+ *
+ * @param obj the ASN.1 PrivateKeyInfo object.
+ * @param password the password to encrypt with.
+ * @param options:
+ *          algorithm the encryption algorithm to use
+ *            ('aes128', 'aes192', 'aes256', '3des'), defaults to 'aes128'.
+ *          count the iteration count to use.
+ *          saltSize the salt size to use.
+ *
+ * @return the ASN.1 EncryptedPrivateKeyInfo.
+ */
+pki.encryptPrivateKeyInfo = function(obj, password, options) {
+  // set default options
+  options = options || {};
+  options.saltSize = options.saltSize || 8;
+  options.count = options.count || 2048;
+  options.algorithm = options.algorithm || 'aes128';
+
+  // generate PBE params
+  var salt = forge.random.getBytesSync(options.saltSize);
+  var count = options.count;
+  var countBytes = asn1.integerToDer(count);
+  var dkLen;
+  var encryptionAlgorithm;
+  var encryptedData;
+  if(options.algorithm.indexOf('aes') === 0 || options.algorithm === 'des') {
+    // Do PBES2
+    var ivLen, encOid, cipherFn;
+    switch(options.algorithm) {
+    case 'aes128':
+      dkLen = 16;
+      ivLen = 16;
+      encOid = oids['aes128-CBC'];
+      cipherFn = forge.aes.createEncryptionCipher;
+      break;
+    case 'aes192':
+      dkLen = 24;
+      ivLen = 16;
+      encOid = oids['aes192-CBC'];
+      cipherFn = forge.aes.createEncryptionCipher;
+      break;
+    case 'aes256':
+      dkLen = 32;
+      ivLen = 16;
+      encOid = oids['aes256-CBC'];
+      cipherFn = forge.aes.createEncryptionCipher;
+      break;
+    case 'des':
+      dkLen = 8;
+      ivLen = 8;
+      encOid = oids['desCBC'];
+      cipherFn = forge.des.createEncryptionCipher;
+      break;
+    default:
+      var error = new Error('Cannot encrypt private key. Unknown encryption algorithm.');
+      error.algorithm = options.algorithm;
+      throw error;
+    }
+
+    // encrypt private key using pbe SHA-1 and AES/DES
+    var dk = forge.pkcs5.pbkdf2(password, salt, count, dkLen);
+    var iv = forge.random.getBytesSync(ivLen);
+    var cipher = cipherFn(dk);
+    cipher.start(iv);
+    cipher.update(asn1.toDer(obj));
+    cipher.finish();
+    encryptedData = cipher.output.getBytes();
+
+    encryptionAlgorithm = asn1.create(
+      asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+        asn1.oidToDer(oids['pkcs5PBES2']).getBytes()),
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+        // keyDerivationFunc
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+            asn1.oidToDer(oids['pkcs5PBKDF2']).getBytes()),
+          // PBKDF2-params
+          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+            // salt
+            asn1.create(
+              asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, salt),
+            // iteration count
+            asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+              countBytes.getBytes())
+          ])
+        ]),
+        // encryptionScheme
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+            asn1.oidToDer(encOid).getBytes()),
+          // iv
+          asn1.create(
+            asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, iv)
+        ])
+      ])
+    ]);
+  } else if(options.algorithm === '3des') {
+    // Do PKCS12 PBE
+    dkLen = 24;
+
+    var saltBytes = new forge.util.ByteBuffer(salt);
+    var dk = pki.pbe.generatePkcs12Key(password, saltBytes, 1, count, dkLen);
+    var iv = pki.pbe.generatePkcs12Key(password, saltBytes, 2, count, dkLen);
+    var cipher = forge.des.createEncryptionCipher(dk);
+    cipher.start(iv);
+    cipher.update(asn1.toDer(obj));
+    cipher.finish();
+    encryptedData = cipher.output.getBytes();
+
+    encryptionAlgorithm = asn1.create(
+      asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+        asn1.oidToDer(oids['pbeWithSHAAnd3-KeyTripleDES-CBC']).getBytes()),
+      // pkcs-12PbeParams
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+        // salt
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, salt),
+        // iteration count
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+          countBytes.getBytes())
+      ])
+    ]);
+  } else {
+    var error = new Error('Cannot encrypt private key. Unknown encryption algorithm.');
+    error.algorithm = options.algorithm;
+    throw error;
+  }
+
+  // EncryptedPrivateKeyInfo
+  var rval = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+    // encryptionAlgorithm
+    encryptionAlgorithm,
+    // encryptedData
+    asn1.create(
+      asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, encryptedData)
+  ]);
+  return rval;
+};
+
+/**
+ * Decrypts a ASN.1 PrivateKeyInfo object.
+ *
+ * @param obj the ASN.1 EncryptedPrivateKeyInfo object.
+ * @param password the password to decrypt with.
+ *
+ * @return the ASN.1 PrivateKeyInfo on success, null on failure.
+ */
+pki.decryptPrivateKeyInfo = function(obj, password) {
+  var rval = null;
+
+  // get PBE params
+  var capture = {};
+  var errors = [];
+  if(!asn1.validate(obj, encryptedPrivateKeyValidator, capture, errors)) {
+    var error = new Error('Cannot read encrypted private key. ' +
+      'ASN.1 object is not a supported EncryptedPrivateKeyInfo.');
+    error.errors = errors;
+    throw error;
+  }
+
+  // get cipher
+  var oid = asn1.derToOid(capture.encryptionOid);
+  var cipher = pki.pbe.getCipher(oid, capture.encryptionParams, password);
+
+  // get encrypted data
+  var encrypted = forge.util.createBuffer(capture.encryptedData);
+
+  cipher.update(encrypted);
+  if(cipher.finish()) {
+    rval = asn1.fromDer(cipher.output);
+  }
+
+  return rval;
+};
+
+/**
+ * Converts a EncryptedPrivateKeyInfo to PEM format.
+ *
+ * @param epki the EncryptedPrivateKeyInfo.
+ * @param maxline the maximum characters per line, defaults to 64.
+ *
+ * @return the PEM-formatted encrypted private key.
+ */
+pki.encryptedPrivateKeyToPem = function(epki, maxline) {
+  // convert to DER, then PEM-encode
+  var msg = {
+    type: 'ENCRYPTED PRIVATE KEY',
+    body: asn1.toDer(epki).getBytes()
+  };
+  return forge.pem.encode(msg, {maxline: maxline});
+};
+
+/**
+ * Converts a PEM-encoded EncryptedPrivateKeyInfo to ASN.1 format. Decryption
+ * is not performed.
+ *
+ * @param pem the EncryptedPrivateKeyInfo in PEM-format.
+ *
+ * @return the ASN.1 EncryptedPrivateKeyInfo.
+ */
+pki.encryptedPrivateKeyFromPem = function(pem) {
+  var msg = forge.pem.decode(pem)[0];
+
+  if(msg.type !== 'ENCRYPTED PRIVATE KEY') {
+    var error = new Error('Could not convert encrypted private key from PEM; ' +
+      'PEM header type is "ENCRYPTED PRIVATE KEY".');
+    error.headerType = msg.type;
+    throw error;
+  }
+  if(msg.procType && msg.procType.type === 'ENCRYPTED') {
+    throw new Error('Could not convert encrypted private key from PEM; ' +
+      'PEM is encrypted.');
+  }
+
+  // convert DER to ASN.1 object
+  return asn1.fromDer(msg.body);
+};
+
+/**
+ * Encrypts an RSA private key. By default, the key will be wrapped in
+ * a PrivateKeyInfo and encrypted to produce a PKCS#8 EncryptedPrivateKeyInfo.
+ * This is the standard, preferred way to encrypt a private key.
+ *
+ * To produce a non-standard PEM-encrypted private key that uses encapsulated
+ * headers to indicate the encryption algorithm (old-style non-PKCS#8 OpenSSL
+ * private key encryption), set the 'legacy' option to true. Note: Using this
+ * option will cause the iteration count to be forced to 1.
+ *
+ * Note: The 'des' algorithm is supported, but it is not considered to be
+ * secure because it only uses a single 56-bit key. If possible, it is highly
+ * recommended that a different algorithm be used.
+ *
+ * @param rsaKey the RSA key to encrypt.
+ * @param password the password to use.
+ * @param options:
+ *          algorithm: the encryption algorithm to use
+ *            ('aes128', 'aes192', 'aes256', '3des', 'des').
+ *          count: the iteration count to use.
+ *          saltSize: the salt size to use.
+ *          legacy: output an old non-PKCS#8 PEM-encrypted+encapsulated
+ *            headers (DEK-Info) private key.
+ *
+ * @return the PEM-encoded ASN.1 EncryptedPrivateKeyInfo.
+ */
+pki.encryptRsaPrivateKey = function(rsaKey, password, options) {
+  // standard PKCS#8
+  options = options || {};
+  if(!options.legacy) {
+    // encrypt PrivateKeyInfo
+    var rval = pki.wrapRsaPrivateKey(pki.privateKeyToAsn1(rsaKey));
+    rval = pki.encryptPrivateKeyInfo(rval, password, options);
+    return pki.encryptedPrivateKeyToPem(rval);
+  }
+
+  // legacy non-PKCS#8
+  var algorithm;
+  var iv;
+  var dkLen;
+  var cipherFn;
+  switch(options.algorithm) {
+  case 'aes128':
+    algorithm = 'AES-128-CBC';
+    dkLen = 16;
+    iv = forge.random.getBytesSync(16);
+    cipherFn = forge.aes.createEncryptionCipher;
+    break;
+  case 'aes192':
+    algorithm = 'AES-192-CBC';
+    dkLen = 24;
+    iv = forge.random.getBytesSync(16);
+    cipherFn = forge.aes.createEncryptionCipher;
+    break;
+  case 'aes256':
+    algorithm = 'AES-256-CBC';
+    dkLen = 32;
+    iv = forge.random.getBytesSync(16);
+    cipherFn = forge.aes.createEncryptionCipher;
+    break;
+  case '3des':
+    algorithm = 'DES-EDE3-CBC';
+    dkLen = 24;
+    iv = forge.random.getBytesSync(8);
+    cipherFn = forge.des.createEncryptionCipher;
+    break;
+  case 'des':
+    algorithm = 'DES-CBC';
+    dkLen = 8;
+    iv = forge.random.getBytesSync(8);
+    cipherFn = forge.des.createEncryptionCipher;
+    break;
+  default:
+    var error = new Error('Could not encrypt RSA private key; unsupported ' +
+      'encryption algorithm "' + options.algorithm + '".');
+    error.algorithm = options.algorithm;
+    throw error;
+  }
+
+  // encrypt private key using OpenSSL legacy key derivation
+  var dk = forge.pbe.opensslDeriveBytes(password, iv.substr(0, 8), dkLen);
+  var cipher = cipherFn(dk);
+  cipher.start(iv);
+  cipher.update(asn1.toDer(pki.privateKeyToAsn1(rsaKey)));
+  cipher.finish();
+
+  var msg = {
+    type: 'RSA PRIVATE KEY',
+    procType: {
+      version: '4',
+      type: 'ENCRYPTED'
+    },
+    dekInfo: {
+      algorithm: algorithm,
+      parameters: forge.util.bytesToHex(iv).toUpperCase()
+    },
+    body: cipher.output.getBytes()
+  };
+  return forge.pem.encode(msg);
+};
+
+/**
+ * Decrypts an RSA private key.
+ *
+ * @param pem the PEM-formatted EncryptedPrivateKeyInfo to decrypt.
+ * @param password the password to use.
+ *
+ * @return the RSA key on success, null on failure.
+ */
+pki.decryptRsaPrivateKey = function(pem, password) {
+  var rval = null;
+
+  var msg = forge.pem.decode(pem)[0];
+
+  if(msg.type !== 'ENCRYPTED PRIVATE KEY' &&
+    msg.type !== 'PRIVATE KEY' &&
+    msg.type !== 'RSA PRIVATE KEY') {
+    var error = new Error('Could not convert private key from PEM; PEM header type ' +
+      'is not "ENCRYPTED PRIVATE KEY", "PRIVATE KEY", or "RSA PRIVATE KEY".');
+    error.headerType = error;
+    throw error;
+  }
+
+  if(msg.procType && msg.procType.type === 'ENCRYPTED') {
+    var dkLen;
+    var cipherFn;
+    switch(msg.dekInfo.algorithm) {
+    case 'DES-CBC':
+      dkLen = 8;
+      cipherFn = forge.des.createDecryptionCipher;
+      break;
+    case 'DES-EDE3-CBC':
+      dkLen = 24;
+      cipherFn = forge.des.createDecryptionCipher;
+      break;
+    case 'AES-128-CBC':
+      dkLen = 16;
+      cipherFn = forge.aes.createDecryptionCipher;
+      break;
+    case 'AES-192-CBC':
+      dkLen = 24;
+      cipherFn = forge.aes.createDecryptionCipher;
+      break;
+    case 'AES-256-CBC':
+      dkLen = 32;
+      cipherFn = forge.aes.createDecryptionCipher;
+      break;
+    case 'RC2-40-CBC':
+      dkLen = 5;
+      cipherFn = function(key) {
+        return forge.rc2.createDecryptionCipher(key, 40);
+      };
+      break;
+    case 'RC2-64-CBC':
+      dkLen = 8;
+      cipherFn = function(key) {
+        return forge.rc2.createDecryptionCipher(key, 64);
+      };
+      break;
+    case 'RC2-128-CBC':
+      dkLen = 16;
+      cipherFn = function(key) {
+        return forge.rc2.createDecryptionCipher(key, 128);
+      };
+      break;
+    default:
+      var error = new Error('Could not decrypt private key; unsupported ' +
+        'encryption algorithm "' + msg.dekInfo.algorithm + '".');
+      error.algorithm = msg.dekInfo.algorithm;
+      throw error;
+    }
+
+    // use OpenSSL legacy key derivation
+    var iv = forge.util.hexToBytes(msg.dekInfo.parameters);
+    var dk = forge.pbe.opensslDeriveBytes(password, iv.substr(0, 8), dkLen);
+    var cipher = cipherFn(dk);
+    cipher.start(iv);
+    cipher.update(forge.util.createBuffer(msg.body));
+    if(cipher.finish()) {
+      rval = cipher.output.getBytes();
+    } else {
+      return rval;
+    }
+  } else {
+    rval = msg.body;
+  }
+
+  if(msg.type === 'ENCRYPTED PRIVATE KEY') {
+    rval = pki.decryptPrivateKeyInfo(asn1.fromDer(rval), password);
+  } else {
+    // decryption already performed above
+    rval = asn1.fromDer(rval);
+  }
+
+  if(rval !== null) {
+    rval = pki.privateKeyFromAsn1(rval);
+  }
+
+  return rval;
+};
+
+/**
+ * Derives a PKCS#12 key.
+ *
+ * @param password the password to derive the key material from, null or
+ *          undefined for none.
+ * @param salt the salt, as a ByteBuffer, to use.
+ * @param id the PKCS#12 ID byte (1 = key material, 2 = IV, 3 = MAC).
+ * @param iter the iteration count.
+ * @param n the number of bytes to derive from the password.
+ * @param md the message digest to use, defaults to SHA-1.
+ *
+ * @return a ByteBuffer with the bytes derived from the password.
+ */
+pki.pbe.generatePkcs12Key = function(password, salt, id, iter, n, md) {
+  var j, l;
+
+  if(typeof md === 'undefined' || md === null) {
+    md = forge.md.sha1.create();
+  }
+
+  var u = md.digestLength;
+  var v = md.blockLength;
+  var result = new forge.util.ByteBuffer();
+
+  /* Convert password to Unicode byte buffer + trailing 0-byte. */
+  var passBuf = new forge.util.ByteBuffer();
+  if(password !== null && password !== undefined) {
+    for(l = 0; l < password.length; l++) {
+      passBuf.putInt16(password.charCodeAt(l));
+    }
+    passBuf.putInt16(0);
+  }
+
+  /* Length of salt and password in BYTES. */
+  var p = passBuf.length();
+  var s = salt.length();
+
+  /* 1. Construct a string, D (the "diversifier"), by concatenating
+        v copies of ID. */
+  var D = new forge.util.ByteBuffer();
+  D.fillWithByte(id, v);
+
+  /* 2. Concatenate copies of the salt together to create a string S of length
+        v * ceil(s / v) bytes (the final copy of the salt may be trunacted
+        to create S).
+        Note that if the salt is the empty string, then so is S. */
+  var Slen = v * Math.ceil(s / v);
+  var S = new forge.util.ByteBuffer();
+  for(l = 0; l < Slen; l ++) {
+    S.putByte(salt.at(l % s));
+  }
+
+  /* 3. Concatenate copies of the password together to create a string P of
+        length v * ceil(p / v) bytes (the final copy of the password may be
+        truncated to create P).
+        Note that if the password is the empty string, then so is P. */
+  var Plen = v * Math.ceil(p / v);
+  var P = new forge.util.ByteBuffer();
+  for(l = 0; l < Plen; l ++) {
+    P.putByte(passBuf.at(l % p));
+  }
+
+  /* 4. Set I=S||P to be the concatenation of S and P. */
+  var I = S;
+  I.putBuffer(P);
+
+  /* 5. Set c=ceil(n / u). */
+  var c = Math.ceil(n / u);
+
+  /* 6. For i=1, 2, ..., c, do the following: */
+  for(var i = 1; i <= c; i ++) {
+    /* a) Set Ai=H^r(D||I). (l.e. the rth hash of D||I, H(H(H(...H(D||I)))) */
+    var buf = new forge.util.ByteBuffer();
+    buf.putBytes(D.bytes());
+    buf.putBytes(I.bytes());
+    for(var round = 0; round < iter; round ++) {
+      md.start();
+      md.update(buf.getBytes());
+      buf = md.digest();
+    }
+
+    /* b) Concatenate copies of Ai to create a string B of length v bytes (the
+          final copy of Ai may be truncated to create B). */
+    var B = new forge.util.ByteBuffer();
+    for(l = 0; l < v; l ++) {
+      B.putByte(buf.at(l % u));
+    }
+
+    /* c) Treating I as a concatenation I0, I1, ..., Ik-1 of v-byte blocks,
+          where k=ceil(s / v) + ceil(p / v), modify I by setting
+          Ij=(Ij+B+1) mod 2v for each j.  */
+    var k = Math.ceil(s / v) + Math.ceil(p / v);
+    var Inew = new forge.util.ByteBuffer();
+    for(j = 0; j < k; j ++) {
+      var chunk = new forge.util.ByteBuffer(I.getBytes(v));
+      var x = 0x1ff;
+      for(l = B.length() - 1; l >= 0; l --) {
+        x = x >> 8;
+        x += B.at(l) + chunk.at(l);
+        chunk.setAt(l, x & 0xff);
+      }
+      Inew.putBuffer(chunk);
+    }
+    I = Inew;
+
+    /* Add Ai to A. */
+    result.putBuffer(buf);
+  }
+
+  result.truncate(result.length() - n);
+  return result;
+};
+
+/**
+ * Get new Forge cipher object instance.
+ *
+ * @param oid the OID (in string notation).
+ * @param params the ASN.1 params object.
+ * @param password the password to decrypt with.
+ *
+ * @return new cipher object instance.
+ */
+pki.pbe.getCipher = function(oid, params, password) {
+  switch(oid) {
+  case pki.oids['pkcs5PBES2']:
+    return pki.pbe.getCipherForPBES2(oid, params, password);
+
+  case pki.oids['pbeWithSHAAnd3-KeyTripleDES-CBC']:
+  case pki.oids['pbewithSHAAnd40BitRC2-CBC']:
+    return pki.pbe.getCipherForPKCS12PBE(oid, params, password);
+
+  default:
+    var error = new Error('Cannot read encrypted PBE data block. Unsupported OID.');
+    error.oid = oid;
+    error.supportedOids = [
+      'pkcs5PBES2',
+      'pbeWithSHAAnd3-KeyTripleDES-CBC',
+      'pbewithSHAAnd40BitRC2-CBC'
+    ];
+    throw error;
+  }
+};
+
+/**
+ * Get new Forge cipher object instance according to PBES2 params block.
+ *
+ * The returned cipher instance is already started using the IV
+ * from PBES2 parameter block.
+ *
+ * @param oid the PKCS#5 PBKDF2 OID (in string notation).
+ * @param params the ASN.1 PBES2-params object.
+ * @param password the password to decrypt with.
+ *
+ * @return new cipher object instance.
+ */
+pki.pbe.getCipherForPBES2 = function(oid, params, password) {
+  // get PBE params
+  var capture = {};
+  var errors = [];
+  if(!asn1.validate(params, PBES2AlgorithmsValidator, capture, errors)) {
+    var error = new Error('Cannot read password-based-encryption algorithm ' +
+      'parameters. ASN.1 object is not a supported EncryptedPrivateKeyInfo.');
+    error.errors = errors;
+    throw error;
+  }
+
+  // check oids
+  oid = asn1.derToOid(capture.kdfOid);
+  if(oid !== pki.oids['pkcs5PBKDF2']) {
+    var error = new Error('Cannot read encrypted private key. ' +
+      'Unsupported key derivation function OID.');
+    error.oid = oid;
+    error.supportedOids = ['pkcs5PBKDF2'];
+    throw error;
+  }
+  oid = asn1.derToOid(capture.encOid);
+  if(oid !== pki.oids['aes128-CBC'] &&
+    oid !== pki.oids['aes192-CBC'] &&
+    oid !== pki.oids['aes256-CBC'] &&
+    oid !== pki.oids['des-EDE3-CBC'] &&
+    oid !== pki.oids['desCBC']) {
+    var error = new Error('Cannot read encrypted private key. ' +
+      'Unsupported encryption scheme OID.');
+    error.oid = oid;
+    error.supportedOids = [
+      'aes128-CBC', 'aes192-CBC', 'aes256-CBC', 'des-EDE3-CBC', 'desCBC'];
+    throw error;
+  }
+
+  // set PBE params
+  var salt = capture.kdfSalt;
+  var count = forge.util.createBuffer(capture.kdfIterationCount);
+  count = count.getInt(count.length() << 3);
+  var dkLen;
+  var cipherFn;
+  switch(pki.oids[oid]) {
+  case 'aes128-CBC':
+    dkLen = 16;
+    cipherFn = forge.aes.createDecryptionCipher;
+    break;
+  case 'aes192-CBC':
+    dkLen = 24;
+    cipherFn = forge.aes.createDecryptionCipher;
+    break;
+  case 'aes256-CBC':
+    dkLen = 32;
+    cipherFn = forge.aes.createDecryptionCipher;
+    break;
+  case 'des-EDE3-CBC':
+    dkLen = 24;
+    cipherFn = forge.des.createDecryptionCipher;
+    break;
+  case 'desCBC':
+    dkLen = 8;
+    cipherFn = forge.des.createDecryptionCipher;
+    break;
+  }
+
+  // decrypt private key using pbe SHA-1 and AES/DES
+  var dk = forge.pkcs5.pbkdf2(password, salt, count, dkLen);
+  var iv = capture.encIv;
+  var cipher = cipherFn(dk);
+  cipher.start(iv);
+
+  return cipher;
+};
+
+/**
+ * Get new Forge cipher object instance for PKCS#12 PBE.
+ *
+ * The returned cipher instance is already started using the key & IV
+ * derived from the provided password and PKCS#12 PBE salt.
+ *
+ * @param oid The PKCS#12 PBE OID (in string notation).
+ * @param params The ASN.1 PKCS#12 PBE-params object.
+ * @param password The password to decrypt with.
+ *
+ * @return the new cipher object instance.
+ */
+pki.pbe.getCipherForPKCS12PBE = function(oid, params, password) {
+  // get PBE params
+  var capture = {};
+  var errors = [];
+  if(!asn1.validate(params, pkcs12PbeParamsValidator, capture, errors)) {
+    var error = new Error('Cannot read password-based-encryption algorithm ' +
+      'parameters. ASN.1 object is not a supported EncryptedPrivateKeyInfo.');
+    error.errors = errors;
+    throw error;
+  }
+
+  var salt = forge.util.createBuffer(capture.salt);
+  var count = forge.util.createBuffer(capture.iterations);
+  count = count.getInt(count.length() << 3);
+
+  var dkLen, dIvLen, cipherFn;
+  switch(oid) {
+    case pki.oids['pbeWithSHAAnd3-KeyTripleDES-CBC']:
+      dkLen = 24;
+      dIvLen = 8;
+      cipherFn = forge.des.startDecrypting;
+      break;
+
+    case pki.oids['pbewithSHAAnd40BitRC2-CBC']:
+      dkLen = 5;
+      dIvLen = 8;
+      cipherFn = function(key, iv) {
+        var cipher = forge.rc2.createDecryptionCipher(key, 40);
+        cipher.start(iv, null);
+        return cipher;
+      };
+      break;
+
+    default:
+      var error = new Error('Cannot read PKCS #12 PBE data block. Unsupported OID.');
+      error.oid = oid;
+      throw error;
+  }
+
+  var key = pki.pbe.generatePkcs12Key(password, salt, 1, count, dkLen);
+  var iv = pki.pbe.generatePkcs12Key(password, salt, 2, count, dIvLen);
+
+  return cipherFn(key, iv);
+};
+
+/**
+ * OpenSSL's legacy key derivation function.
+ *
+ * See: http://www.openssl.org/docs/crypto/EVP_BytesToKey.html
+ *
+ * @param password the password to derive the key from.
+ * @param salt the salt to use, null for none.
+ * @param dkLen the number of bytes needed for the derived key.
+ * @param [options] the options to use:
+ *          [md] an optional message digest object to use.
+ */
+pki.pbe.opensslDeriveBytes = function(password, salt, dkLen, md) {
+  if(typeof md === 'undefined' || md === null) {
+    md = forge.md.md5.create();
+  }
+  if(salt === null) {
+    salt = '';
+  }
+  var digests = [hash(md, password + salt)];
+  for(var length = 16, i = 1; length < dkLen; ++i, length += 16) {
+    digests.push(hash(md, digests[i - 1] + password + salt));
+  }
+  return digests.join('').substr(0, dkLen);
+};
+
+function hash(md, bytes) {
+  return md.start().update(bytes).digest().getBytes();
+}
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'pbe';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define([
+  'require',
+  'module',
+  './aes',
+  './asn1',
+  './des',
+  './md',
+  './oids',
+  './pem',
+  './pbkdf2',
+  './random',
+  './rc2',
+  './rsa',
+  './util'
+], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/pbkdf2.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/pbkdf2.js
new file mode 100644
index 0000000..d983610
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/pbkdf2.js
@@ -0,0 +1,214 @@
+/**
+ * Password-Based Key-Derivation Function #2 implementation.
+ *
+ * See RFC 2898 for details.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2013 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+var pkcs5 = forge.pkcs5 = forge.pkcs5 || {};
+
+/**
+ * Derives a key from a password.
+ *
+ * @param p the password as a string of bytes.
+ * @param s the salt as a string of bytes.
+ * @param c the iteration count, a positive integer.
+ * @param dkLen the intended length, in bytes, of the derived key,
+ *          (max: 2^32 - 1) * hash length of the PRF.
+ * @param md the message digest to use in the PRF, defaults to SHA-1.
+ *
+ * @return the derived key, as a string of bytes.
+ */
+forge.pbkdf2 = pkcs5.pbkdf2 = function(p, s, c, dkLen, md, callback) {
+  if(typeof md === 'function') {
+    callback = md;
+    md = null;
+  }
+  // default prf to SHA-1
+  if(typeof md === 'undefined' || md === null) {
+    md = forge.md.sha1.create();
+  }
+
+  var hLen = md.digestLength;
+
+  /* 1. If dkLen > (2^32 - 1) * hLen, output "derived key too long" and
+    stop. */
+  if(dkLen > (0xFFFFFFFF * hLen)) {
+    var err = new Error('Derived key is too long.');
+    if(callback) {
+      return callback(err);
+    }
+    throw err;
+  }
+
+  /* 2. Let len be the number of hLen-octet blocks in the derived key,
+    rounding up, and let r be the number of octets in the last
+    block:
+
+    len = CEIL(dkLen / hLen),
+    r = dkLen - (len - 1) * hLen. */
+  var len = Math.ceil(dkLen / hLen);
+  var r = dkLen - (len - 1) * hLen;
+
+  /* 3. For each block of the derived key apply the function F defined
+    below to the password P, the salt S, the iteration count c, and
+    the block index to compute the block:
+
+    T_1 = F(P, S, c, 1),
+    T_2 = F(P, S, c, 2),
+    ...
+    T_len = F(P, S, c, len),
+
+    where the function F is defined as the exclusive-or sum of the
+    first c iterates of the underlying pseudorandom function PRF
+    applied to the password P and the concatenation of the salt S
+    and the block index i:
+
+    F(P, S, c, i) = u_1 XOR u_2 XOR ... XOR u_c
+
+    where
+
+    u_1 = PRF(P, S || INT(i)),
+    u_2 = PRF(P, u_1),
+    ...
+    u_c = PRF(P, u_{c-1}).
+
+    Here, INT(i) is a four-octet encoding of the integer i, most
+    significant octet first. */
+  var prf = forge.hmac.create();
+  prf.start(md, p);
+  var dk = '';
+  var xor, u_c, u_c1;
+
+  // sync version
+  if(!callback) {
+    for(var i = 1; i <= len; ++i) {
+      // PRF(P, S || INT(i)) (first iteration)
+      prf.start(null, null);
+      prf.update(s);
+      prf.update(forge.util.int32ToBytes(i));
+      xor = u_c1 = prf.digest().getBytes();
+
+      // PRF(P, u_{c-1}) (other iterations)
+      for(var j = 2; j <= c; ++j) {
+        prf.start(null, null);
+        prf.update(u_c1);
+        u_c = prf.digest().getBytes();
+        // F(p, s, c, i)
+        xor = forge.util.xorBytes(xor, u_c, hLen);
+        u_c1 = u_c;
+      }
+
+      /* 4. Concatenate the blocks and extract the first dkLen octets to
+        produce a derived key DK:
+
+        DK = T_1 || T_2 ||  ...  || T_len<0..r-1> */
+      dk += (i < len) ? xor : xor.substr(0, r);
+    }
+    /* 5. Output the derived key DK. */
+    return dk;
+  }
+
+  // async version
+  var i = 1, j;
+  function outer() {
+    if(i > len) {
+      // done
+      return callback(null, dk);
+    }
+
+    // PRF(P, S || INT(i)) (first iteration)
+    prf.start(null, null);
+    prf.update(s);
+    prf.update(forge.util.int32ToBytes(i));
+    xor = u_c1 = prf.digest().getBytes();
+
+    // PRF(P, u_{c-1}) (other iterations)
+    j = 2;
+    inner();
+  }
+
+  function inner() {
+    if(j <= c) {
+      prf.start(null, null);
+      prf.update(u_c1);
+      u_c = prf.digest().getBytes();
+      // F(p, s, c, i)
+      xor = forge.util.xorBytes(xor, u_c, hLen);
+      u_c1 = u_c;
+      ++j;
+      return forge.util.setImmediate(inner);
+    }
+
+    /* 4. Concatenate the blocks and extract the first dkLen octets to
+      produce a derived key DK:
+
+      DK = T_1 || T_2 ||  ...  || T_len<0..r-1> */
+    dk += (i < len) ? xor : xor.substr(0, r);
+
+    ++i;
+    outer();
+  }
+
+  outer();
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'pbkdf2';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './hmac', './md', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/pem.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/pem.js
new file mode 100644
index 0000000..e3085dc
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/pem.js
@@ -0,0 +1,285 @@
+/**
+ * Javascript implementation of basic PEM (Privacy Enhanced Mail) algorithms.
+ *
+ * See: RFC 1421.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2013-2014 Digital Bazaar, Inc.
+ *
+ * A Forge PEM object has the following fields:
+ *
+ * type: identifies the type of message (eg: "RSA PRIVATE KEY").
+ *
+ * procType: identifies the type of processing performed on the message,
+ *   it has two subfields: version and type, eg: 4,ENCRYPTED.
+ *
+ * contentDomain: identifies the type of content in the message, typically
+ *   only uses the value: "RFC822".
+ *
+ * dekInfo: identifies the message encryption algorithm and mode and includes
+ *   any parameters for the algorithm, it has two subfields: algorithm and
+ *   parameters, eg: DES-CBC,F8143EDE5960C597.
+ *
+ * headers: contains all other PEM encapsulated headers -- where order is
+ *   significant (for pairing data like recipient ID + key info).
+ *
+ * body: the binary-encoded body.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+// shortcut for pem API
+var pem = forge.pem = forge.pem || {};
+
+/**
+ * Encodes (serializes) the given PEM object.
+ *
+ * @param msg the PEM message object to encode.
+ * @param options the options to use:
+ *          maxline the maximum characters per line for the body, (default: 64).
+ *
+ * @return the PEM-formatted string.
+ */
+pem.encode = function(msg, options) {
+  options = options || {};
+  var rval = '-----BEGIN ' + msg.type + '-----\r\n';
+
+  // encode special headers
+  var header;
+  if(msg.procType) {
+    header = {
+      name: 'Proc-Type',
+      values: [String(msg.procType.version), msg.procType.type]
+    };
+    rval += foldHeader(header);
+  }
+  if(msg.contentDomain) {
+    header = {name: 'Content-Domain', values: [msg.contentDomain]};
+    rval += foldHeader(header);
+  }
+  if(msg.dekInfo) {
+    header = {name: 'DEK-Info', values: [msg.dekInfo.algorithm]};
+    if(msg.dekInfo.parameters) {
+      header.values.push(msg.dekInfo.parameters);
+    }
+    rval += foldHeader(header);
+  }
+
+  if(msg.headers) {
+    // encode all other headers
+    for(var i = 0; i < msg.headers.length; ++i) {
+      rval += foldHeader(msg.headers[i]);
+    }
+  }
+
+  // terminate header
+  if(msg.procType) {
+    rval += '\r\n';
+  }
+
+  // add body
+  rval += forge.util.encode64(msg.body, options.maxline || 64) + '\r\n';
+
+  rval += '-----END ' + msg.type + '-----\r\n';
+  return rval;
+};
+
+/**
+ * Decodes (deserializes) all PEM messages found in the given string.
+ *
+ * @param str the PEM-formatted string to decode.
+ *
+ * @return the PEM message objects in an array.
+ */
+pem.decode = function(str) {
+  var rval = [];
+
+  // split string into PEM messages (be lenient w/EOF on BEGIN line)
+  var rMessage = /\s*-----BEGIN ([A-Z0-9- ]+)-----\r?\n?([\x21-\x7e\s]+?(?:\r?\n\r?\n))?([:A-Za-z0-9+\/=\s]+?)-----END \1-----/g;
+  var rHeader = /([\x21-\x7e]+):\s*([\x21-\x7e\s^:]+)/;
+  var rCRLF = /\r?\n/;
+  var match;
+  while(true) {
+    match = rMessage.exec(str);
+    if(!match) {
+      break;
+    }
+
+    var msg = {
+      type: match[1],
+      procType: null,
+      contentDomain: null,
+      dekInfo: null,
+      headers: [],
+      body: forge.util.decode64(match[3])
+    };
+    rval.push(msg);
+
+    // no headers
+    if(!match[2]) {
+      continue;
+    }
+
+    // parse headers
+    var lines = match[2].split(rCRLF);
+    var li = 0;
+    while(match && li < lines.length) {
+      // get line, trim any rhs whitespace
+      var line = lines[li].replace(/\s+$/, '');
+
+      // RFC2822 unfold any following folded lines
+      for(var nl = li + 1; nl < lines.length; ++nl) {
+        var next = lines[nl];
+        if(!/\s/.test(next[0])) {
+          break;
+        }
+        line += next;
+        li = nl;
+      }
+
+      // parse header
+      match = line.match(rHeader);
+      if(match) {
+        var header = {name: match[1], values: []};
+        var values = match[2].split(',');
+        for(var vi = 0; vi < values.length; ++vi) {
+          header.values.push(ltrim(values[vi]));
+        }
+
+        // Proc-Type must be the first header
+        if(!msg.procType) {
+          if(header.name !== 'Proc-Type') {
+            throw new Error('Invalid PEM formatted message. The first ' +
+              'encapsulated header must be "Proc-Type".');
+          } else if(header.values.length !== 2) {
+            throw new Error('Invalid PEM formatted message. The "Proc-Type" ' +
+              'header must have two subfields.');
+          }
+          msg.procType = {version: values[0], type: values[1]};
+        } else if(!msg.contentDomain && header.name === 'Content-Domain') {
+          // special-case Content-Domain
+          msg.contentDomain = values[0] || '';
+        } else if(!msg.dekInfo && header.name === 'DEK-Info') {
+          // special-case DEK-Info
+          if(header.values.length === 0) {
+            throw new Error('Invalid PEM formatted message. The "DEK-Info" ' +
+              'header must have at least one subfield.');
+          }
+          msg.dekInfo = {algorithm: values[0], parameters: values[1] || null};
+        } else {
+          msg.headers.push(header);
+        }
+      }
+
+      ++li;
+    }
+
+    if(msg.procType === 'ENCRYPTED' && !msg.dekInfo) {
+      throw new Error('Invalid PEM formatted message. The "DEK-Info" ' +
+        'header must be present if "Proc-Type" is "ENCRYPTED".');
+    }
+  }
+
+  if(rval.length === 0) {
+    throw new Error('Invalid PEM formatted message.');
+  }
+
+  return rval;
+};
+
+function foldHeader(header) {
+  var rval = header.name + ': ';
+
+  // ensure values with CRLF are folded
+  var values = [];
+  var insertSpace = function(match, $1) {
+    return ' ' + $1;
+  };
+  for(var i = 0; i < header.values.length; ++i) {
+    values.push(header.values[i].replace(/^(\S+\r\n)/, insertSpace));
+  }
+  rval += values.join(',') + '\r\n';
+
+  // do folding
+  var length = 0;
+  var candidate = -1;
+  for(var i = 0; i < rval.length; ++i, ++length) {
+    if(length > 65 && candidate !== -1) {
+      var insert = rval[candidate];
+      if(insert === ',') {
+        ++candidate;
+        rval = rval.substr(0, candidate) + '\r\n ' + rval.substr(candidate);
+      } else {
+        rval = rval.substr(0, candidate) +
+          '\r\n' + insert + rval.substr(candidate + 1);
+      }
+      length = (i - candidate - 1);
+      candidate = -1;
+      ++i;
+    } else if(rval[i] === ' ' || rval[i] === '\t' || rval[i] === ',') {
+      candidate = i;
+    }
+  }
+
+  return rval;
+}
+
+function ltrim(str) {
+  return str.replace(/^\s+/, '');
+}
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'pem';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/pkcs1.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/pkcs1.js
new file mode 100644
index 0000000..7bf734c
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/pkcs1.js
@@ -0,0 +1,329 @@
+/**
+ * Partial implementation of PKCS#1 v2.2: RSA-OEAP
+ *
+ * Modified but based on the following MIT and BSD licensed code:
+ *
+ * https://github.com/kjur/jsjws/blob/master/rsa.js:
+ *
+ * The 'jsjws'(JSON Web Signature JavaScript Library) License
+ *
+ * Copyright (c) 2012 Kenji Urushima
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * http://webrsa.cvs.sourceforge.net/viewvc/webrsa/Client/RSAES-OAEP.js?content-type=text%2Fplain:
+ *
+ * RSAES-OAEP.js
+ * $Id: RSAES-OAEP.js,v 1.1.1.1 2003/03/19 15:37:20 ellispritchard Exp $
+ * JavaScript Implementation of PKCS #1 v2.1 RSA CRYPTOGRAPHY STANDARD (RSA Laboratories, June 14, 2002)
+ * Copyright (C) Ellis Pritchard, Guardian Unlimited 2003.
+ * Contact: ellis@nukinetics.com
+ * Distributed under the BSD License.
+ *
+ * Official documentation: http://www.rsa.com/rsalabs/node.asp?id=2125
+ *
+ * @author Evan Jones (http://evanjones.ca/)
+ * @author Dave Longley
+ *
+ * Copyright (c) 2013-2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+// shortcut for PKCS#1 API
+var pkcs1 = forge.pkcs1 = forge.pkcs1 || {};
+
+/**
+ * Encode the given RSAES-OAEP message (M) using key, with optional label (L)
+ * and seed.
+ *
+ * This method does not perform RSA encryption, it only encodes the message
+ * using RSAES-OAEP.
+ *
+ * @param key the RSA key to use.
+ * @param message the message to encode.
+ * @param options the options to use:
+ *          label an optional label to use.
+ *          seed the seed to use.
+ *          md the message digest object to use, undefined for SHA-1.
+ *          mgf1 optional mgf1 parameters:
+ *            md the message digest object to use for MGF1.
+ *
+ * @return the encoded message bytes.
+ */
+pkcs1.encode_rsa_oaep = function(key, message, options) {
+  // parse arguments
+  var label;
+  var seed;
+  var md;
+  var mgf1Md;
+  // legacy args (label, seed, md)
+  if(typeof options === 'string') {
+    label = options;
+    seed = arguments[3] || undefined;
+    md = arguments[4] || undefined;
+  } else if(options) {
+    label = options.label || undefined;
+    seed = options.seed || undefined;
+    md = options.md || undefined;
+    if(options.mgf1 && options.mgf1.md) {
+      mgf1Md = options.mgf1.md;
+    }
+  }
+
+  // default OAEP to SHA-1 message digest
+  if(!md) {
+    md = forge.md.sha1.create();
+  } else {
+    md.start();
+  }
+
+  // default MGF-1 to same as OAEP
+  if(!mgf1Md) {
+    mgf1Md = md;
+  }
+
+  // compute length in bytes and check output
+  var keyLength = Math.ceil(key.n.bitLength() / 8);
+  var maxLength = keyLength - 2 * md.digestLength - 2;
+  if(message.length > maxLength) {
+    var error = new Error('RSAES-OAEP input message length is too long.');
+    error.length = message.length;
+    error.maxLength = maxLength;
+    throw error;
+  }
+
+  if(!label) {
+    label = '';
+  }
+  md.update(label, 'raw');
+  var lHash = md.digest();
+
+  var PS = '';
+  var PS_length = maxLength - message.length;
+  for (var i = 0; i < PS_length; i++) {
+    PS += '\x00';
+  }
+
+  var DB = lHash.getBytes() + PS + '\x01' + message;
+
+  if(!seed) {
+    seed = forge.random.getBytes(md.digestLength);
+  } else if(seed.length !== md.digestLength) {
+    var error = new Error('Invalid RSAES-OAEP seed. The seed length must ' +
+      'match the digest length.')
+    error.seedLength = seed.length;
+    error.digestLength = md.digestLength;
+    throw error;
+  }
+
+  var dbMask = rsa_mgf1(seed, keyLength - md.digestLength - 1, mgf1Md);
+  var maskedDB = forge.util.xorBytes(DB, dbMask, DB.length);
+
+  var seedMask = rsa_mgf1(maskedDB, md.digestLength, mgf1Md);
+  var maskedSeed = forge.util.xorBytes(seed, seedMask, seed.length);
+
+  // return encoded message
+  return '\x00' + maskedSeed + maskedDB;
+};
+
+/**
+ * Decode the given RSAES-OAEP encoded message (EM) using key, with optional
+ * label (L).
+ *
+ * This method does not perform RSA decryption, it only decodes the message
+ * using RSAES-OAEP.
+ *
+ * @param key the RSA key to use.
+ * @param em the encoded message to decode.
+ * @param options the options to use:
+ *          label an optional label to use.
+ *          md the message digest object to use for OAEP, undefined for SHA-1.
+ *          mgf1 optional mgf1 parameters:
+ *            md the message digest object to use for MGF1.
+ *
+ * @return the decoded message bytes.
+ */
+pkcs1.decode_rsa_oaep = function(key, em, options) {
+  // parse args
+  var label;
+  var md;
+  var mgf1Md;
+  // legacy args
+  if(typeof options === 'string') {
+    label = options;
+    md = arguments[3] || undefined;
+  } else if(options) {
+    label = options.label || undefined;
+    md = options.md || undefined;
+    if(options.mgf1 && options.mgf1.md) {
+      mgf1Md = options.mgf1.md;
+    }
+  }
+
+  // compute length in bytes
+  var keyLength = Math.ceil(key.n.bitLength() / 8);
+
+  if(em.length !== keyLength) {
+    var error = new Error('RSAES-OAEP encoded message length is invalid.');
+    error.length = em.length;
+    error.expectedLength = keyLength;
+    throw error;
+  }
+
+  // default OAEP to SHA-1 message digest
+  if(md === undefined) {
+    md = forge.md.sha1.create();
+  } else {
+    md.start();
+  }
+
+  // default MGF-1 to same as OAEP
+  if(!mgf1Md) {
+    mgf1Md = md;
+  }
+
+  if(keyLength < 2 * md.digestLength + 2) {
+    throw new Error('RSAES-OAEP key is too short for the hash function.');
+  }
+
+  if(!label) {
+    label = '';
+  }
+  md.update(label, 'raw');
+  var lHash = md.digest().getBytes();
+
+  // split the message into its parts
+  var y = em.charAt(0);
+  var maskedSeed = em.substring(1, md.digestLength + 1);
+  var maskedDB = em.substring(1 + md.digestLength);
+
+  var seedMask = rsa_mgf1(maskedDB, md.digestLength, mgf1Md);
+  var seed = forge.util.xorBytes(maskedSeed, seedMask, maskedSeed.length);
+
+  var dbMask = rsa_mgf1(seed, keyLength - md.digestLength - 1, mgf1Md);
+  var db = forge.util.xorBytes(maskedDB, dbMask, maskedDB.length);
+
+  var lHashPrime = db.substring(0, md.digestLength);
+
+  // constant time check that all values match what is expected
+  var error = (y !== '\x00');
+
+  // constant time check lHash vs lHashPrime
+  for(var i = 0; i < md.digestLength; ++i) {
+    error |= (lHash.charAt(i) !== lHashPrime.charAt(i));
+  }
+
+  // "constant time" find the 0x1 byte separating the padding (zeros) from the
+  // message
+  // TODO: It must be possible to do this in a better/smarter way?
+  var in_ps = 1;
+  var index = md.digestLength;
+  for(var j = md.digestLength; j < db.length; j++) {
+    var code = db.charCodeAt(j);
+
+    var is_0 = (code & 0x1) ^ 0x1;
+
+    // non-zero if not 0 or 1 in the ps section
+    var error_mask = in_ps ? 0xfffe : 0x0000;
+    error |= (code & error_mask);
+
+    // latch in_ps to zero after we find 0x1
+    in_ps = in_ps & is_0;
+    index += in_ps;
+  }
+
+  if(error || db.charCodeAt(index) !== 0x1) {
+    throw new Error('Invalid RSAES-OAEP padding.');
+  }
+
+  return db.substring(index + 1);
+};
+
+function rsa_mgf1(seed, maskLength, hash) {
+  // default to SHA-1 message digest
+  if(!hash) {
+    hash = forge.md.sha1.create();
+  }
+  var t = '';
+  var count = Math.ceil(maskLength / hash.digestLength);
+  for(var i = 0; i < count; ++i) {
+    var c = String.fromCharCode(
+      (i >> 24) & 0xFF, (i >> 16) & 0xFF, (i >> 8) & 0xFF, i & 0xFF);
+    hash.start();
+    hash.update(seed + c);
+    t += hash.digest().getBytes();
+  }
+  return t.substring(0, maskLength);
+}
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'pkcs1';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './util', './random', './sha1'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/pkcs12.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/pkcs12.js
new file mode 100644
index 0000000..19f1c55
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/pkcs12.js
@@ -0,0 +1,1121 @@
+/**
+ * Javascript implementation of PKCS#12.
+ *
+ * @author Dave Longley
+ * @author Stefan Siegl <stesie@brokenpipe.de>
+ *
+ * Copyright (c) 2010-2014 Digital Bazaar, Inc.
+ * Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
+ *
+ * The ASN.1 representation of PKCS#12 is as follows
+ * (see ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-12/pkcs-12-tc1.pdf for details)
+ *
+ * PFX ::= SEQUENCE {
+ *   version  INTEGER {v3(3)}(v3,...),
+ *   authSafe ContentInfo,
+ *   macData  MacData OPTIONAL
+ * }
+ *
+ * MacData ::= SEQUENCE {
+ *   mac DigestInfo,
+ *   macSalt OCTET STRING,
+ *   iterations INTEGER DEFAULT 1
+ * }
+ * Note: The iterations default is for historical reasons and its use is
+ * deprecated. A higher value, like 1024, is recommended.
+ *
+ * DigestInfo is defined in PKCS#7 as follows:
+ *
+ * DigestInfo ::= SEQUENCE {
+ *   digestAlgorithm DigestAlgorithmIdentifier,
+ *   digest Digest
+ * }
+ *
+ * DigestAlgorithmIdentifier ::= AlgorithmIdentifier
+ *
+ * The AlgorithmIdentifier contains an Object Identifier (OID) and parameters
+ * for the algorithm, if any. In the case of SHA1 there is none.
+ *
+ * AlgorithmIdentifer ::= SEQUENCE {
+ *    algorithm OBJECT IDENTIFIER,
+ *    parameters ANY DEFINED BY algorithm OPTIONAL
+ * }
+ *
+ * Digest ::= OCTET STRING
+ *
+ *
+ * ContentInfo ::= SEQUENCE {
+ *   contentType ContentType,
+ *   content     [0] EXPLICIT ANY DEFINED BY contentType OPTIONAL
+ * }
+ *
+ * ContentType ::= OBJECT IDENTIFIER
+ *
+ * AuthenticatedSafe ::= SEQUENCE OF ContentInfo
+ * -- Data if unencrypted
+ * -- EncryptedData if password-encrypted
+ * -- EnvelopedData if public key-encrypted
+ *
+ *
+ * SafeContents ::= SEQUENCE OF SafeBag
+ *
+ * SafeBag ::= SEQUENCE {
+ *   bagId     BAG-TYPE.&id ({PKCS12BagSet})
+ *   bagValue  [0] EXPLICIT BAG-TYPE.&Type({PKCS12BagSet}{@bagId}),
+ *   bagAttributes SET OF PKCS12Attribute OPTIONAL
+ * }
+ *
+ * PKCS12Attribute ::= SEQUENCE {
+ *   attrId ATTRIBUTE.&id ({PKCS12AttrSet}),
+ *   attrValues SET OF ATTRIBUTE.&Type ({PKCS12AttrSet}{@attrId})
+ * } -- This type is compatible with the X.500 type ’Attribute’
+ *
+ * PKCS12AttrSet ATTRIBUTE ::= {
+ *   friendlyName | -- from PKCS #9
+ *   localKeyId, -- from PKCS #9
+ *   ... -- Other attributes are allowed
+ * }
+ *
+ * CertBag ::= SEQUENCE {
+ *   certId    BAG-TYPE.&id   ({CertTypes}),
+ *   certValue [0] EXPLICIT BAG-TYPE.&Type ({CertTypes}{@certId})
+ * }
+ *
+ * x509Certificate BAG-TYPE ::= {OCTET STRING IDENTIFIED BY {certTypes 1}}
+ *   -- DER-encoded X.509 certificate stored in OCTET STRING
+ *
+ * sdsiCertificate BAG-TYPE ::= {IA5String IDENTIFIED BY {certTypes 2}}
+ * -- Base64-encoded SDSI certificate stored in IA5String
+ *
+ * CertTypes BAG-TYPE ::= {
+ *   x509Certificate |
+ *   sdsiCertificate,
+ *   ... -- For future extensions
+ * }
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+// shortcut for asn.1 & PKI API
+var asn1 = forge.asn1;
+var pki = forge.pki;
+
+// shortcut for PKCS#12 API
+var p12 = forge.pkcs12 = forge.pkcs12 || {};
+
+var contentInfoValidator = {
+  name: 'ContentInfo',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,  // a ContentInfo
+  constructed: true,
+  value: [{
+    name: 'ContentInfo.contentType',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.OID,
+    constructed: false,
+    capture: 'contentType'
+  }, {
+    name: 'ContentInfo.content',
+    tagClass: asn1.Class.CONTEXT_SPECIFIC,
+    constructed: true,
+    captureAsn1: 'content'
+  }]
+};
+
+var pfxValidator = {
+  name: 'PFX',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'PFX.version',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'version'
+  },
+  contentInfoValidator, {
+    name: 'PFX.macData',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true,
+    optional: true,
+    captureAsn1: 'mac',
+    value: [{
+      name: 'PFX.macData.mac',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.SEQUENCE,  // DigestInfo
+      constructed: true,
+      value: [{
+        name: 'PFX.macData.mac.digestAlgorithm',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.SEQUENCE,  // DigestAlgorithmIdentifier
+        constructed: true,
+        value: [{
+          name: 'PFX.macData.mac.digestAlgorithm.algorithm',
+          tagClass: asn1.Class.UNIVERSAL,
+          type: asn1.Type.OID,
+          constructed: false,
+          capture: 'macAlgorithm'
+        }, {
+          name: 'PFX.macData.mac.digestAlgorithm.parameters',
+          tagClass: asn1.Class.UNIVERSAL,
+          captureAsn1: 'macAlgorithmParameters'
+        }]
+      }, {
+        name: 'PFX.macData.mac.digest',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.OCTETSTRING,
+        constructed: false,
+        capture: 'macDigest'
+      }]
+    }, {
+      name: 'PFX.macData.macSalt',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.OCTETSTRING,
+      constructed: false,
+      capture: 'macSalt'
+    }, {
+      name: 'PFX.macData.iterations',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.INTEGER,
+      constructed: false,
+      optional: true,
+      capture: 'macIterations'
+    }]
+  }]
+};
+
+var safeBagValidator = {
+  name: 'SafeBag',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'SafeBag.bagId',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.OID,
+    constructed: false,
+    capture: 'bagId'
+  }, {
+    name: 'SafeBag.bagValue',
+    tagClass: asn1.Class.CONTEXT_SPECIFIC,
+    constructed: true,
+    captureAsn1: 'bagValue'
+  }, {
+    name: 'SafeBag.bagAttributes',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SET,
+    constructed: true,
+    optional: true,
+    capture: 'bagAttributes'
+  }]
+};
+
+var attributeValidator = {
+  name: 'Attribute',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'Attribute.attrId',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.OID,
+    constructed: false,
+    capture: 'oid'
+  }, {
+    name: 'Attribute.attrValues',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SET,
+    constructed: true,
+    capture: 'values'
+  }]
+};
+
+var certBagValidator = {
+  name: 'CertBag',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'CertBag.certId',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.OID,
+    constructed: false,
+    capture: 'certId'
+  }, {
+    name: 'CertBag.certValue',
+    tagClass: asn1.Class.CONTEXT_SPECIFIC,
+    constructed: true,
+    /* So far we only support X.509 certificates (which are wrapped in
+       an OCTET STRING, hence hard code that here). */
+    value: [{
+      name: 'CertBag.certValue[0]',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Class.OCTETSTRING,
+      constructed: false,
+      capture: 'cert'
+    }]
+  }]
+};
+
+/**
+ * Search SafeContents structure for bags with matching attributes.
+ *
+ * The search can optionally be narrowed by a certain bag type.
+ *
+ * @param safeContents the SafeContents structure to search in.
+ * @param attrName the name of the attribute to compare against.
+ * @param attrValue the attribute value to search for.
+ * @param [bagType] bag type to narrow search by.
+ *
+ * @return an array of matching bags.
+ */
+function _getBagsByAttribute(safeContents, attrName, attrValue, bagType) {
+  var result = [];
+
+  for(var i = 0; i < safeContents.length; i ++) {
+    for(var j = 0; j < safeContents[i].safeBags.length; j ++) {
+      var bag = safeContents[i].safeBags[j];
+      if(bagType !== undefined && bag.type !== bagType) {
+        continue;
+      }
+      // only filter by bag type, no attribute specified
+      if(attrName === null) {
+        result.push(bag);
+        continue;
+      }
+      if(bag.attributes[attrName] !== undefined &&
+        bag.attributes[attrName].indexOf(attrValue) >= 0) {
+        result.push(bag);
+      }
+    }
+  }
+
+  return result;
+}
+
+/**
+ * Converts a PKCS#12 PFX in ASN.1 notation into a PFX object.
+ *
+ * @param obj The PKCS#12 PFX in ASN.1 notation.
+ * @param strict true to use strict DER decoding, false not to (default: true).
+ * @param {String} password Password to decrypt with (optional).
+ *
+ * @return PKCS#12 PFX object.
+ */
+p12.pkcs12FromAsn1 = function(obj, strict, password) {
+  // handle args
+  if(typeof strict === 'string') {
+    password = strict;
+    strict = true;
+  } else if(strict === undefined) {
+    strict = true;
+  }
+
+  // validate PFX and capture data
+  var capture = {};
+  var errors = [];
+  if(!asn1.validate(obj, pfxValidator, capture, errors)) {
+    var error = new Error('Cannot read PKCS#12 PFX. ' +
+      'ASN.1 object is not an PKCS#12 PFX.');
+    error.errors = error;
+    throw error;
+  }
+
+  var pfx = {
+    version: capture.version.charCodeAt(0),
+    safeContents: [],
+
+    /**
+     * Gets bags with matching attributes.
+     *
+     * @param filter the attributes to filter by:
+     *          [localKeyId] the localKeyId to search for.
+     *          [localKeyIdHex] the localKeyId in hex to search for.
+     *          [friendlyName] the friendly name to search for.
+     *          [bagType] bag type to narrow each attribute search by.
+     *
+     * @return a map of attribute type to an array of matching bags or, if no
+     *           attribute was given but a bag type, the map key will be the
+     *           bag type.
+     */
+    getBags: function(filter) {
+      var rval = {};
+
+      var localKeyId;
+      if('localKeyId' in filter) {
+        localKeyId = filter.localKeyId;
+      } else if('localKeyIdHex' in filter) {
+        localKeyId = forge.util.hexToBytes(filter.localKeyIdHex);
+      }
+
+      // filter on bagType only
+      if(localKeyId === undefined && !('friendlyName' in filter) &&
+        'bagType' in filter) {
+        rval[filter.bagType] = _getBagsByAttribute(
+          pfx.safeContents, null, null, filter.bagType);
+      }
+
+      if(localKeyId !== undefined) {
+        rval.localKeyId = _getBagsByAttribute(
+          pfx.safeContents, 'localKeyId',
+          localKeyId, filter.bagType);
+      }
+      if('friendlyName' in filter) {
+        rval.friendlyName = _getBagsByAttribute(
+          pfx.safeContents, 'friendlyName',
+          filter.friendlyName, filter.bagType);
+      }
+
+      return rval;
+    },
+
+    /**
+     * DEPRECATED: use getBags() instead.
+     *
+     * Get bags with matching friendlyName attribute.
+     *
+     * @param friendlyName the friendly name to search for.
+     * @param [bagType] bag type to narrow search by.
+     *
+     * @return an array of bags with matching friendlyName attribute.
+     */
+    getBagsByFriendlyName: function(friendlyName, bagType) {
+      return _getBagsByAttribute(
+        pfx.safeContents, 'friendlyName', friendlyName, bagType);
+    },
+
+    /**
+     * DEPRECATED: use getBags() instead.
+     *
+     * Get bags with matching localKeyId attribute.
+     *
+     * @param localKeyId the localKeyId to search for.
+     * @param [bagType] bag type to narrow search by.
+     *
+     * @return an array of bags with matching localKeyId attribute.
+     */
+    getBagsByLocalKeyId: function(localKeyId, bagType) {
+      return _getBagsByAttribute(
+        pfx.safeContents, 'localKeyId', localKeyId, bagType);
+    }
+  };
+
+  if(capture.version.charCodeAt(0) !== 3) {
+    var error = new Error('PKCS#12 PFX of version other than 3 not supported.');
+    error.version = capture.version.charCodeAt(0);
+    throw error;
+  }
+
+  if(asn1.derToOid(capture.contentType) !== pki.oids.data) {
+    var error = new Error('Only PKCS#12 PFX in password integrity mode supported.');
+    error.oid = asn1.derToOid(capture.contentType);
+    throw error;
+  }
+
+  var data = capture.content.value[0];
+  if(data.tagClass !== asn1.Class.UNIVERSAL ||
+     data.type !== asn1.Type.OCTETSTRING) {
+    throw new Error('PKCS#12 authSafe content data is not an OCTET STRING.');
+  }
+  data = _decodePkcs7Data(data);
+
+  // check for MAC
+  if(capture.mac) {
+    var md = null;
+    var macKeyBytes = 0;
+    var macAlgorithm = asn1.derToOid(capture.macAlgorithm);
+    switch(macAlgorithm) {
+    case pki.oids.sha1:
+      md = forge.md.sha1.create();
+      macKeyBytes = 20;
+      break;
+    case pki.oids.sha256:
+      md = forge.md.sha256.create();
+      macKeyBytes = 32;
+      break;
+    case pki.oids.sha384:
+      md = forge.md.sha384.create();
+      macKeyBytes = 48;
+      break;
+    case pki.oids.sha512:
+      md = forge.md.sha512.create();
+      macKeyBytes = 64;
+      break;
+    case pki.oids.md5:
+      md = forge.md.md5.create();
+      macKeyBytes = 16;
+      break;
+    }
+    if(md === null) {
+      throw new Error('PKCS#12 uses unsupported MAC algorithm: ' + macAlgorithm);
+    }
+
+    // verify MAC (iterations default to 1)
+    var macSalt = new forge.util.ByteBuffer(capture.macSalt);
+    var macIterations = (('macIterations' in capture) ?
+      parseInt(forge.util.bytesToHex(capture.macIterations), 16) : 1);
+    var macKey = p12.generateKey(
+      password, macSalt, 3, macIterations, macKeyBytes, md);
+    var mac = forge.hmac.create();
+    mac.start(md, macKey);
+    mac.update(data.value);
+    var macValue = mac.getMac();
+    if(macValue.getBytes() !== capture.macDigest) {
+      throw new Error('PKCS#12 MAC could not be verified. Invalid password?');
+    }
+  }
+
+  _decodeAuthenticatedSafe(pfx, data.value, strict, password);
+  return pfx;
+};
+
+/**
+ * Decodes PKCS#7 Data. PKCS#7 (RFC 2315) defines "Data" as an OCTET STRING,
+ * but it is sometimes an OCTET STRING that is composed/constructed of chunks,
+ * each its own OCTET STRING. This is BER-encoding vs. DER-encoding. This
+ * function transforms this corner-case into the usual simple,
+ * non-composed/constructed OCTET STRING.
+ *
+ * This function may be moved to ASN.1 at some point to better deal with
+ * more BER-encoding issues, should they arise.
+ *
+ * @param data the ASN.1 Data object to transform.
+ */
+function _decodePkcs7Data(data) {
+  // handle special case of "chunked" data content: an octet string composed
+  // of other octet strings
+  if(data.composed || data.constructed) {
+    var value = forge.util.createBuffer();
+    for(var i = 0; i < data.value.length; ++i) {
+      value.putBytes(data.value[i].value);
+    }
+    data.composed = data.constructed = false;
+    data.value = value.getBytes();
+  }
+  return data;
+}
+
+/**
+ * Decode PKCS#12 AuthenticatedSafe (BER encoded) into PFX object.
+ *
+ * The AuthenticatedSafe is a BER-encoded SEQUENCE OF ContentInfo.
+ *
+ * @param pfx The PKCS#12 PFX object to fill.
+ * @param {String} authSafe BER-encoded AuthenticatedSafe.
+ * @param strict true to use strict DER decoding, false not to.
+ * @param {String} password Password to decrypt with (optional).
+ */
+function _decodeAuthenticatedSafe(pfx, authSafe, strict, password) {
+  authSafe = asn1.fromDer(authSafe, strict);  /* actually it's BER encoded */
+
+  if(authSafe.tagClass !== asn1.Class.UNIVERSAL ||
+     authSafe.type !== asn1.Type.SEQUENCE ||
+     authSafe.constructed !== true) {
+    throw new Error('PKCS#12 AuthenticatedSafe expected to be a ' +
+      'SEQUENCE OF ContentInfo');
+  }
+
+  for(var i = 0; i < authSafe.value.length; i ++) {
+    var contentInfo = authSafe.value[i];
+
+    // validate contentInfo and capture data
+    var capture = {};
+    var errors = [];
+    if(!asn1.validate(contentInfo, contentInfoValidator, capture, errors)) {
+      var error = new Error('Cannot read ContentInfo.');
+      error.errors = errors;
+      throw error;
+    }
+
+    var obj = {
+      encrypted: false
+    };
+    var safeContents = null;
+    var data = capture.content.value[0];
+    switch(asn1.derToOid(capture.contentType)) {
+    case pki.oids.data:
+      if(data.tagClass !== asn1.Class.UNIVERSAL ||
+         data.type !== asn1.Type.OCTETSTRING) {
+        throw new Error('PKCS#12 SafeContents Data is not an OCTET STRING.');
+      }
+      safeContents = _decodePkcs7Data(data).value;
+      break;
+    case pki.oids.encryptedData:
+      safeContents = _decryptSafeContents(data, password);
+      obj.encrypted = true;
+      break;
+    default:
+      var error = new Error('Unsupported PKCS#12 contentType.');
+      error.contentType = asn1.derToOid(capture.contentType);
+      throw error;
+    }
+
+    obj.safeBags = _decodeSafeContents(safeContents, strict, password);
+    pfx.safeContents.push(obj);
+  }
+}
+
+/**
+ * Decrypt PKCS#7 EncryptedData structure.
+ *
+ * @param data ASN.1 encoded EncryptedContentInfo object.
+ * @param password The user-provided password.
+ *
+ * @return The decrypted SafeContents (ASN.1 object).
+ */
+function _decryptSafeContents(data, password) {
+  var capture = {};
+  var errors = [];
+  if(!asn1.validate(
+    data, forge.pkcs7.asn1.encryptedDataValidator, capture, errors)) {
+    var error = new Error('Cannot read EncryptedContentInfo.');
+    error.errors = errors;
+    throw error;
+  }
+
+  var oid = asn1.derToOid(capture.contentType);
+  if(oid !== pki.oids.data) {
+    var error = new Error(
+      'PKCS#12 EncryptedContentInfo ContentType is not Data.');
+    error.oid = oid;
+    throw error;
+  }
+
+  // get cipher
+  oid = asn1.derToOid(capture.encAlgorithm);
+  var cipher = pki.pbe.getCipher(oid, capture.encParameter, password);
+
+  // get encrypted data
+  var encryptedContentAsn1 = _decodePkcs7Data(capture.encryptedContentAsn1);
+  var encrypted = forge.util.createBuffer(encryptedContentAsn1.value);
+
+  cipher.update(encrypted);
+  if(!cipher.finish()) {
+    throw new Error('Failed to decrypt PKCS#12 SafeContents.');
+  }
+
+  return cipher.output.getBytes();
+}
+
+/**
+ * Decode PKCS#12 SafeContents (BER-encoded) into array of Bag objects.
+ *
+ * The safeContents is a BER-encoded SEQUENCE OF SafeBag.
+ *
+ * @param {String} safeContents BER-encoded safeContents.
+ * @param strict true to use strict DER decoding, false not to.
+ * @param {String} password Password to decrypt with (optional).
+ *
+ * @return {Array} Array of Bag objects.
+ */
+function _decodeSafeContents(safeContents, strict, password) {
+  // if strict and no safe contents, return empty safes
+  if(!strict && safeContents.length === 0) {
+    return [];
+  }
+
+  // actually it's BER-encoded
+  safeContents = asn1.fromDer(safeContents, strict);
+
+  if(safeContents.tagClass !== asn1.Class.UNIVERSAL ||
+    safeContents.type !== asn1.Type.SEQUENCE ||
+    safeContents.constructed !== true) {
+    throw new Error(
+      'PKCS#12 SafeContents expected to be a SEQUENCE OF SafeBag.');
+  }
+
+  var res = [];
+  for(var i = 0; i < safeContents.value.length; i++) {
+    var safeBag = safeContents.value[i];
+
+    // validate SafeBag and capture data
+    var capture = {};
+    var errors = [];
+    if(!asn1.validate(safeBag, safeBagValidator, capture, errors)) {
+      var error = new Error('Cannot read SafeBag.');
+      error.errors = errors;
+      throw error;
+    }
+
+    /* Create bag object and push to result array. */
+    var bag = {
+      type: asn1.derToOid(capture.bagId),
+      attributes: _decodeBagAttributes(capture.bagAttributes)
+    };
+    res.push(bag);
+
+    var validator, decoder;
+    var bagAsn1 = capture.bagValue.value[0];
+    switch(bag.type) {
+      case pki.oids.pkcs8ShroudedKeyBag:
+        /* bagAsn1 has a EncryptedPrivateKeyInfo, which we need to decrypt.
+           Afterwards we can handle it like a keyBag,
+           which is a PrivateKeyInfo. */
+        bagAsn1 = pki.decryptPrivateKeyInfo(bagAsn1, password);
+        if(bagAsn1 === null) {
+          throw new Error(
+            'Unable to decrypt PKCS#8 ShroudedKeyBag, wrong password?');
+        }
+
+        /* fall through */
+      case pki.oids.keyBag:
+        /* A PKCS#12 keyBag is a simple PrivateKeyInfo as understood by our
+           PKI module, hence we don't have to do validation/capturing here,
+           just pass what we already got. */
+        bag.key = pki.privateKeyFromAsn1(bagAsn1);
+        continue;  /* Nothing more to do. */
+
+      case pki.oids.certBag:
+        /* A PKCS#12 certBag can wrap both X.509 and sdsi certificates.
+           Therefore put the SafeBag content through another validator to
+           capture the fields.  Afterwards check & store the results. */
+        validator = certBagValidator;
+        decoder = function() {
+          if(asn1.derToOid(capture.certId) !== pki.oids.x509Certificate) {
+            var error = new Error(
+              'Unsupported certificate type, only X.509 supported.');
+            error.oid = asn1.derToOid(capture.certId);
+            throw error;
+          }
+
+          // true=produce cert hash
+          bag.cert = pki.certificateFromAsn1(
+            asn1.fromDer(capture.cert, strict), true);
+        };
+        break;
+
+      default:
+        var error = new Error('Unsupported PKCS#12 SafeBag type.');
+        error.oid = bag.type;
+        throw error;
+    }
+
+    /* Validate SafeBag value (i.e. CertBag, etc.) and capture data if needed. */
+    if(validator !== undefined &&
+       !asn1.validate(bagAsn1, validator, capture, errors)) {
+      var error = new Error('Cannot read PKCS#12 ' + validator.name);
+      error.errors = errors;
+      throw error;
+    }
+
+    /* Call decoder function from above to store the results. */
+    decoder();
+  }
+
+  return res;
+}
+
+/**
+ * Decode PKCS#12 SET OF PKCS12Attribute into JavaScript object.
+ *
+ * @param attributes SET OF PKCS12Attribute (ASN.1 object).
+ *
+ * @return the decoded attributes.
+ */
+function _decodeBagAttributes(attributes) {
+  var decodedAttrs = {};
+
+  if(attributes !== undefined) {
+    for(var i = 0; i < attributes.length; ++i) {
+      var capture = {};
+      var errors = [];
+      if(!asn1.validate(attributes[i], attributeValidator, capture, errors)) {
+        var error = new Error('Cannot read PKCS#12 BagAttribute.');
+        error.errors = errors;
+        throw error;
+      }
+
+      var oid = asn1.derToOid(capture.oid);
+      if(pki.oids[oid] === undefined) {
+        // unsupported attribute type, ignore.
+        continue;
+      }
+
+      decodedAttrs[pki.oids[oid]] = [];
+      for(var j = 0; j < capture.values.length; ++j) {
+        decodedAttrs[pki.oids[oid]].push(capture.values[j].value);
+      }
+    }
+  }
+
+  return decodedAttrs;
+}
+
+/**
+ * Wraps a private key and certificate in a PKCS#12 PFX wrapper. If a
+ * password is provided then the private key will be encrypted.
+ *
+ * An entire certificate chain may also be included. To do this, pass
+ * an array for the "cert" parameter where the first certificate is
+ * the one that is paired with the private key and each subsequent one
+ * verifies the previous one. The certificates may be in PEM format or
+ * have been already parsed by Forge.
+ *
+ * @todo implement password-based-encryption for the whole package
+ *
+ * @param key the private key.
+ * @param cert the certificate (may be an array of certificates in order
+ *          to specify a certificate chain).
+ * @param password the password to use, null for none.
+ * @param options:
+ *          algorithm the encryption algorithm to use
+ *            ('aes128', 'aes192', 'aes256', '3des'), defaults to 'aes128'.
+ *          count the iteration count to use.
+ *          saltSize the salt size to use.
+ *          useMac true to include a MAC, false not to, defaults to true.
+ *          localKeyId the local key ID to use, in hex.
+ *          friendlyName the friendly name to use.
+ *          generateLocalKeyId true to generate a random local key ID,
+ *            false not to, defaults to true.
+ *
+ * @return the PKCS#12 PFX ASN.1 object.
+ */
+p12.toPkcs12Asn1 = function(key, cert, password, options) {
+  // set default options
+  options = options || {};
+  options.saltSize = options.saltSize || 8;
+  options.count = options.count || 2048;
+  options.algorithm = options.algorithm || options.encAlgorithm || 'aes128';
+  if(!('useMac' in options)) {
+    options.useMac = true;
+  }
+  if(!('localKeyId' in options)) {
+    options.localKeyId = null;
+  }
+  if(!('generateLocalKeyId' in options)) {
+    options.generateLocalKeyId = true;
+  }
+
+  var localKeyId = options.localKeyId;
+  var bagAttrs;
+  if(localKeyId !== null) {
+    localKeyId = forge.util.hexToBytes(localKeyId);
+  } else if(options.generateLocalKeyId) {
+    // use SHA-1 of paired cert, if available
+    if(cert) {
+      var pairedCert = forge.util.isArray(cert) ? cert[0] : cert;
+      if(typeof pairedCert === 'string') {
+        pairedCert = pki.certificateFromPem(pairedCert);
+      }
+      var sha1 = forge.md.sha1.create();
+      sha1.update(asn1.toDer(pki.certificateToAsn1(pairedCert)).getBytes());
+      localKeyId = sha1.digest().getBytes();
+    } else {
+      // FIXME: consider using SHA-1 of public key (which can be generated
+      // from private key components), see: cert.generateSubjectKeyIdentifier
+      // generate random bytes
+      localKeyId = forge.random.getBytes(20);
+    }
+  }
+
+  var attrs = [];
+  if(localKeyId !== null) {
+    attrs.push(
+      // localKeyID
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+        // attrId
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+          asn1.oidToDer(pki.oids.localKeyId).getBytes()),
+        // attrValues
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true, [
+          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
+            localKeyId)
+        ])
+      ]));
+  }
+  if('friendlyName' in options) {
+    attrs.push(
+      // friendlyName
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+        // attrId
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+          asn1.oidToDer(pki.oids.friendlyName).getBytes()),
+        // attrValues
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true, [
+          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.BMPSTRING, false,
+            options.friendlyName)
+        ])
+      ]));
+  }
+
+  if(attrs.length > 0) {
+    bagAttrs = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true, attrs);
+  }
+
+  // collect contents for AuthenticatedSafe
+  var contents = [];
+
+  // create safe bag(s) for certificate chain
+  var chain = [];
+  if(cert !== null) {
+    if(forge.util.isArray(cert)) {
+      chain = cert;
+    } else {
+      chain = [cert];
+    }
+  }
+
+  var certSafeBags = [];
+  for(var i = 0; i < chain.length; ++i) {
+    // convert cert from PEM as necessary
+    cert = chain[i];
+    if(typeof cert === 'string') {
+      cert = pki.certificateFromPem(cert);
+    }
+
+    // SafeBag
+    var certBagAttrs = (i === 0) ? bagAttrs : undefined;
+    var certAsn1 = pki.certificateToAsn1(cert);
+    var certSafeBag =
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+        // bagId
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+          asn1.oidToDer(pki.oids.certBag).getBytes()),
+        // bagValue
+        asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
+          // CertBag
+          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+            // certId
+            asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+              asn1.oidToDer(pki.oids.x509Certificate).getBytes()),
+            // certValue (x509Certificate)
+            asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
+              asn1.create(
+                asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
+                asn1.toDer(certAsn1).getBytes())
+            ])])]),
+        // bagAttributes (OPTIONAL)
+        certBagAttrs
+      ]);
+    certSafeBags.push(certSafeBag);
+  }
+
+  if(certSafeBags.length > 0) {
+    // SafeContents
+    var certSafeContents = asn1.create(
+      asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, certSafeBags);
+
+    // ContentInfo
+    var certCI =
+      // PKCS#7 ContentInfo
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+        // contentType
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+          // OID for the content type is 'data'
+          asn1.oidToDer(pki.oids.data).getBytes()),
+        // content
+        asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
+          asn1.create(
+            asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
+            asn1.toDer(certSafeContents).getBytes())
+        ])
+      ]);
+    contents.push(certCI);
+  }
+
+  // create safe contents for private key
+  var keyBag = null;
+  if(key !== null) {
+    // SafeBag
+    var pkAsn1 = pki.wrapRsaPrivateKey(pki.privateKeyToAsn1(key));
+    if(password === null) {
+      // no encryption
+      keyBag = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+        // bagId
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+          asn1.oidToDer(pki.oids.keyBag).getBytes()),
+        // bagValue
+        asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
+          // PrivateKeyInfo
+          pkAsn1
+        ]),
+        // bagAttributes (OPTIONAL)
+        bagAttrs
+      ]);
+    } else {
+      // encrypted PrivateKeyInfo
+      keyBag = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+        // bagId
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+          asn1.oidToDer(pki.oids.pkcs8ShroudedKeyBag).getBytes()),
+        // bagValue
+        asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
+          // EncryptedPrivateKeyInfo
+          pki.encryptPrivateKeyInfo(pkAsn1, password, options)
+        ]),
+        // bagAttributes (OPTIONAL)
+        bagAttrs
+      ]);
+    }
+
+    // SafeContents
+    var keySafeContents =
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [keyBag]);
+
+    // ContentInfo
+    var keyCI =
+      // PKCS#7 ContentInfo
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+        // contentType
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+          // OID for the content type is 'data'
+          asn1.oidToDer(pki.oids.data).getBytes()),
+        // content
+        asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
+          asn1.create(
+            asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
+            asn1.toDer(keySafeContents).getBytes())
+        ])
+      ]);
+    contents.push(keyCI);
+  }
+
+  // create AuthenticatedSafe by stringing together the contents
+  var safe = asn1.create(
+    asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, contents);
+
+  var macData;
+  if(options.useMac) {
+    // MacData
+    var sha1 = forge.md.sha1.create();
+    var macSalt = new forge.util.ByteBuffer(
+      forge.random.getBytes(options.saltSize));
+    var count = options.count;
+    // 160-bit key
+    var key = p12.generateKey(password, macSalt, 3, count, 20);
+    var mac = forge.hmac.create();
+    mac.start(sha1, key);
+    mac.update(asn1.toDer(safe).getBytes());
+    var macValue = mac.getMac();
+    macData = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+      // mac DigestInfo
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+        // digestAlgorithm
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+          // algorithm = SHA-1
+          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+            asn1.oidToDer(pki.oids.sha1).getBytes()),
+          // parameters = Null
+          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
+        ]),
+        // digest
+        asn1.create(
+          asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING,
+          false, macValue.getBytes())
+      ]),
+      // macSalt OCTET STRING
+      asn1.create(
+        asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, macSalt.getBytes()),
+      // iterations INTEGER (XXX: Only support count < 65536)
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+        asn1.integerToDer(count).getBytes()
+      )
+    ]);
+  }
+
+  // PFX
+  return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+    // version (3)
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      asn1.integerToDer(3).getBytes()),
+    // PKCS#7 ContentInfo
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+      // contentType
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+        // OID for the content type is 'data'
+        asn1.oidToDer(pki.oids.data).getBytes()),
+      // content
+      asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
+        asn1.create(
+          asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
+          asn1.toDer(safe).getBytes())
+      ])
+    ]),
+    macData
+  ]);
+};
+
+/**
+ * Derives a PKCS#12 key.
+ *
+ * @param password the password to derive the key material from, null or
+ *          undefined for none.
+ * @param salt the salt, as a ByteBuffer, to use.
+ * @param id the PKCS#12 ID byte (1 = key material, 2 = IV, 3 = MAC).
+ * @param iter the iteration count.
+ * @param n the number of bytes to derive from the password.
+ * @param md the message digest to use, defaults to SHA-1.
+ *
+ * @return a ByteBuffer with the bytes derived from the password.
+ */
+p12.generateKey = forge.pbe.generatePkcs12Key;
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'pkcs12';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define([
+  'require',
+  'module',
+  './asn1',
+  './hmac',
+  './oids',
+  './pkcs7asn1',
+  './pbe',
+  './random',
+  './rsa',
+  './sha1',
+  './util',
+  './x509'
+], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/pkcs7.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/pkcs7.js
new file mode 100644
index 0000000..ffa7413
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/pkcs7.js
@@ -0,0 +1,842 @@
+/**
+ * Javascript implementation of PKCS#7 v1.5. Currently only certain parts of
+ * PKCS#7 are implemented, especially the enveloped-data content type.
+ *
+ * @author Stefan Siegl
+ *
+ * Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
+ *
+ * Currently this implementation only supports ContentType of either
+ * EnvelopedData or EncryptedData on root level.  The top level elements may
+ * contain only a ContentInfo of ContentType Data, i.e. plain data.  Further
+ * nesting is not (yet) supported.
+ *
+ * The Forge validators for PKCS #7's ASN.1 structures are available from
+ * a seperate file pkcs7asn1.js, since those are referenced from other
+ * PKCS standards like PKCS #12.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+// shortcut for ASN.1 API
+var asn1 = forge.asn1;
+
+// shortcut for PKCS#7 API
+var p7 = forge.pkcs7 = forge.pkcs7 || {};
+
+/**
+ * Converts a PKCS#7 message from PEM format.
+ *
+ * @param pem the PEM-formatted PKCS#7 message.
+ *
+ * @return the PKCS#7 message.
+ */
+p7.messageFromPem = function(pem) {
+  var msg = forge.pem.decode(pem)[0];
+
+  if(msg.type !== 'PKCS7') {
+    var error = new Error('Could not convert PKCS#7 message from PEM; PEM ' +
+      'header type is not "PKCS#7".');
+    error.headerType = msg.type;
+    throw error;
+  }
+  if(msg.procType && msg.procType.type === 'ENCRYPTED') {
+    throw new Error('Could not convert PKCS#7 message from PEM; PEM is encrypted.');
+  }
+
+  // convert DER to ASN.1 object
+  var obj = asn1.fromDer(msg.body);
+
+  return p7.messageFromAsn1(obj);
+};
+
+/**
+ * Converts a PKCS#7 message to PEM format.
+ *
+ * @param msg The PKCS#7 message object
+ * @param maxline The maximum characters per line, defaults to 64.
+ *
+ * @return The PEM-formatted PKCS#7 message.
+ */
+p7.messageToPem = function(msg, maxline) {
+  // convert to ASN.1, then DER, then PEM-encode
+  var pemObj = {
+    type: 'PKCS7',
+    body: asn1.toDer(msg.toAsn1()).getBytes()
+  };
+  return forge.pem.encode(pemObj, {maxline: maxline});
+};
+
+/**
+ * Converts a PKCS#7 message from an ASN.1 object.
+ *
+ * @param obj the ASN.1 representation of a ContentInfo.
+ *
+ * @return the PKCS#7 message.
+ */
+p7.messageFromAsn1 = function(obj) {
+  // validate root level ContentInfo and capture data
+  var capture = {};
+  var errors = [];
+  if(!asn1.validate(obj, p7.asn1.contentInfoValidator, capture, errors))
+  {
+    var error = new Error('Cannot read PKCS#7 message. ' +
+      'ASN.1 object is not an PKCS#7 ContentInfo.');
+    error.errors = errors;
+    throw error;
+  }
+
+  var contentType = asn1.derToOid(capture.contentType);
+  var msg;
+
+  switch(contentType) {
+    case forge.pki.oids.envelopedData:
+      msg = p7.createEnvelopedData();
+      break;
+
+    case forge.pki.oids.encryptedData:
+      msg = p7.createEncryptedData();
+      break;
+
+    case forge.pki.oids.signedData:
+      msg = p7.createSignedData();
+      break;
+
+    default:
+      throw new Error('Cannot read PKCS#7 message. ContentType with OID ' +
+        contentType + ' is not (yet) supported.');
+  }
+
+  msg.fromAsn1(capture.content.value[0]);
+  return msg;
+};
+
+/**
+ * Converts a single RecipientInfo from an ASN.1 object.
+ *
+ * @param obj The ASN.1 representation of a RecipientInfo.
+ *
+ * @return The recipientInfo object.
+ */
+var _recipientInfoFromAsn1 = function(obj) {
+  // Validate EnvelopedData content block and capture data.
+  var capture = {};
+  var errors = [];
+  if(!asn1.validate(obj, p7.asn1.recipientInfoValidator, capture, errors))
+  {
+    var error = new Error('Cannot read PKCS#7 message. ' +
+      'ASN.1 object is not an PKCS#7 EnvelopedData.');
+    error.errors = errors;
+    throw error;
+  }
+
+  return {
+    version: capture.version.charCodeAt(0),
+    issuer: forge.pki.RDNAttributesAsArray(capture.issuer),
+    serialNumber: forge.util.createBuffer(capture.serial).toHex(),
+    encryptedContent: {
+      algorithm: asn1.derToOid(capture.encAlgorithm),
+      parameter: capture.encParameter.value,
+      content: capture.encKey
+    }
+  };
+};
+
+/**
+ * Converts a single recipientInfo object to an ASN.1 object.
+ *
+ * @param obj The recipientInfo object.
+ *
+ * @return The ASN.1 representation of a RecipientInfo.
+ */
+var _recipientInfoToAsn1 = function(obj) {
+  return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+    // Version
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      asn1.integerToDer(obj.version).getBytes()),
+    // IssuerAndSerialNumber
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+      // Name
+      forge.pki.distinguishedNameToAsn1({attributes: obj.issuer}),
+      // Serial
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+        forge.util.hexToBytes(obj.serialNumber))
+    ]),
+    // KeyEncryptionAlgorithmIdentifier
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+      // Algorithm
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+        asn1.oidToDer(obj.encryptedContent.algorithm).getBytes()),
+      // Parameter, force NULL, only RSA supported for now.
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
+    ]),
+    // EncryptedKey
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
+      obj.encryptedContent.content)
+  ]);
+};
+
+/**
+ * Map a set of RecipientInfo ASN.1 objects to recipientInfo objects.
+ *
+ * @param objArr Array of ASN.1 representations RecipientInfo (i.e. SET OF).
+ *
+ * @return array of recipientInfo objects.
+ */
+var _recipientInfosFromAsn1 = function(objArr) {
+  var ret = [];
+  for(var i = 0; i < objArr.length; i ++) {
+    ret.push(_recipientInfoFromAsn1(objArr[i]));
+  }
+  return ret;
+};
+
+/**
+ * Map an array of recipientInfo objects to ASN.1 objects.
+ *
+ * @param recipientsArr Array of recipientInfo objects.
+ *
+ * @return Array of ASN.1 representations RecipientInfo.
+ */
+var _recipientInfosToAsn1 = function(recipientsArr) {
+  var ret = [];
+  for(var i = 0; i < recipientsArr.length; i ++) {
+    ret.push(_recipientInfoToAsn1(recipientsArr[i]));
+  }
+  return ret;
+};
+
+/**
+ * Map messages encrypted content to ASN.1 objects.
+ *
+ * @param ec The encryptedContent object of the message.
+ *
+ * @return ASN.1 representation of the encryptedContent object (SEQUENCE).
+ */
+var _encryptedContentToAsn1 = function(ec) {
+  return [
+    // ContentType, always Data for the moment
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+      asn1.oidToDer(forge.pki.oids.data).getBytes()),
+    // ContentEncryptionAlgorithmIdentifier
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+      // Algorithm
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+        asn1.oidToDer(ec.algorithm).getBytes()),
+      // Parameters (IV)
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
+        ec.parameter.getBytes())
+    ]),
+    // [0] EncryptedContent
+    asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
+        ec.content.getBytes())
+    ])
+  ];
+};
+
+/**
+ * Reads the "common part" of an PKCS#7 content block (in ASN.1 format)
+ *
+ * This function reads the "common part" of the PKCS#7 content blocks
+ * EncryptedData and EnvelopedData, i.e. version number and symmetrically
+ * encrypted content block.
+ *
+ * The result of the ASN.1 validate and capture process is returned
+ * to allow the caller to extract further data, e.g. the list of recipients
+ * in case of a EnvelopedData object.
+ *
+ * @param msg the PKCS#7 object to read the data to.
+ * @param obj the ASN.1 representation of the content block.
+ * @param validator the ASN.1 structure validator object to use.
+ *
+ * @return the value map captured by validator object.
+ */
+var _fromAsn1 = function(msg, obj, validator) {
+  var capture = {};
+  var errors = [];
+  if(!asn1.validate(obj, validator, capture, errors)) {
+    var error = new Error('Cannot read PKCS#7 message. ' +
+      'ASN.1 object is not a supported PKCS#7 message.');
+    error.errors = error;
+    throw error;
+  }
+
+  // Check contentType, so far we only support (raw) Data.
+  var contentType = asn1.derToOid(capture.contentType);
+  if(contentType !== forge.pki.oids.data) {
+    throw new Error('Unsupported PKCS#7 message. ' +
+      'Only wrapped ContentType Data supported.');
+  }
+
+  if(capture.encryptedContent) {
+    var content = '';
+    if(forge.util.isArray(capture.encryptedContent)) {
+      for(var i = 0; i < capture.encryptedContent.length; ++i) {
+        if(capture.encryptedContent[i].type !== asn1.Type.OCTETSTRING) {
+          throw new Error('Malformed PKCS#7 message, expecting encrypted ' +
+            'content constructed of only OCTET STRING objects.');
+        }
+        content += capture.encryptedContent[i].value;
+      }
+    } else {
+      content = capture.encryptedContent;
+    }
+    msg.encryptedContent = {
+      algorithm: asn1.derToOid(capture.encAlgorithm),
+      parameter: forge.util.createBuffer(capture.encParameter.value),
+      content: forge.util.createBuffer(content)
+    };
+  }
+
+  if(capture.content) {
+    var content = '';
+    if(forge.util.isArray(capture.content)) {
+      for(var i = 0; i < capture.content.length; ++i) {
+        if(capture.content[i].type !== asn1.Type.OCTETSTRING) {
+          throw new Error('Malformed PKCS#7 message, expecting ' +
+            'content constructed of only OCTET STRING objects.');
+        }
+        content += capture.content[i].value;
+      }
+    } else {
+      content = capture.content;
+    }
+    msg.content = forge.util.createBuffer(content);
+  }
+
+  msg.version = capture.version.charCodeAt(0);
+  msg.rawCapture = capture;
+
+  return capture;
+};
+
+/**
+ * Decrypt the symmetrically encrypted content block of the PKCS#7 message.
+ *
+ * Decryption is skipped in case the PKCS#7 message object already has a
+ * (decrypted) content attribute.  The algorithm, key and cipher parameters
+ * (probably the iv) are taken from the encryptedContent attribute of the
+ * message object.
+ *
+ * @param The PKCS#7 message object.
+ */
+var _decryptContent = function (msg) {
+  if(msg.encryptedContent.key === undefined) {
+    throw new Error('Symmetric key not available.');
+  }
+
+  if(msg.content === undefined) {
+    var ciph;
+
+    switch(msg.encryptedContent.algorithm) {
+      case forge.pki.oids['aes128-CBC']:
+      case forge.pki.oids['aes192-CBC']:
+      case forge.pki.oids['aes256-CBC']:
+        ciph = forge.aes.createDecryptionCipher(msg.encryptedContent.key);
+        break;
+
+      case forge.pki.oids['desCBC']:
+      case forge.pki.oids['des-EDE3-CBC']:
+        ciph = forge.des.createDecryptionCipher(msg.encryptedContent.key);
+        break;
+
+      default:
+        throw new Error('Unsupported symmetric cipher, OID ' +
+          msg.encryptedContent.algorithm);
+    }
+    ciph.start(msg.encryptedContent.parameter);
+    ciph.update(msg.encryptedContent.content);
+
+    if(!ciph.finish()) {
+      throw new Error('Symmetric decryption failed.');
+    }
+
+    msg.content = ciph.output;
+  }
+};
+
+p7.createSignedData = function() {
+  var msg = null;
+  msg = {
+    type: forge.pki.oids.signedData,
+    version: 1,
+    certificates: [],
+    crls: [],
+    // populated during sign()
+    digestAlgorithmIdentifiers: [],
+    contentInfo: null,
+    signerInfos: [],
+
+    fromAsn1: function(obj) {
+      // validate SignedData content block and capture data.
+      _fromAsn1(msg, obj, p7.asn1.signedDataValidator);
+      msg.certificates = [];
+      msg.crls = [];
+      msg.digestAlgorithmIdentifiers = [];
+      msg.contentInfo = null;
+      msg.signerInfos = [];
+
+      var certs = msg.rawCapture.certificates.value;
+      for(var i = 0; i < certs.length; ++i) {
+        msg.certificates.push(forge.pki.certificateFromAsn1(certs[i]));
+      }
+
+      // TODO: parse crls
+    },
+
+    toAsn1: function() {
+      // TODO: add support for more data types here
+      if('content' in msg) {
+        throw new Error('Signing PKCS#7 content not yet implemented.');
+      }
+
+      // degenerate case with no content
+      if(!msg.contentInfo) {
+        msg.sign();
+      }
+
+      var certs = [];
+      for(var i = 0; i < msg.certificates.length; ++i) {
+        certs.push(forge.pki.certificateToAsn1(msg.certificates[0]));
+      }
+
+      var crls = [];
+      // TODO: implement CRLs
+
+      // ContentInfo
+      return asn1.create(
+        asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+          // ContentType
+          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+            asn1.oidToDer(msg.type).getBytes()),
+          // [0] SignedData
+          asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
+            asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+              // Version
+              asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+                asn1.integerToDer(msg.version).getBytes()),
+              // DigestAlgorithmIdentifiers
+              asn1.create(
+                asn1.Class.UNIVERSAL, asn1.Type.SET, true,
+                msg.digestAlgorithmIdentifiers),
+              // ContentInfo
+              msg.contentInfo,
+              // [0] IMPLICIT ExtendedCertificatesAndCertificates
+              asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, certs),
+              // [1] IMPLICIT CertificateRevocationLists
+              asn1.create(asn1.Class.CONTEXT_SPECIFIC, 1, true, crls),
+              // SignerInfos
+              asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true,
+                msg.signerInfos)
+            ])
+          ])
+      ]);
+    },
+
+    /**
+     * Signs the content.
+     *
+     * @param signer the signer (or array of signers) to sign as, for each:
+     *          key the private key to sign with.
+     *          [md] the message digest to use, defaults to sha-1.
+     */
+    sign: function(signer) {
+      if('content' in msg) {
+        throw new Error('PKCS#7 signing not yet implemented.');
+      }
+
+      if(typeof msg.content !== 'object') {
+        // use Data ContentInfo
+        msg.contentInfo = asn1.create(
+          asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+            // ContentType
+            asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+              asn1.oidToDer(forge.pki.oids.data).getBytes())
+          ]);
+
+        // add actual content, if present
+        if('content' in msg) {
+          msg.contentInfo.value.push(
+            // [0] EXPLICIT content
+            asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
+              asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
+                msg.content)
+            ]));
+        }
+      }
+
+      // TODO: generate digest algorithm identifiers
+
+      // TODO: generate signerInfos
+    },
+
+    verify: function() {
+      throw new Error('PKCS#7 signature verification not yet implemented.');
+    },
+
+    /**
+     * Add a certificate.
+     *
+     * @param cert the certificate to add.
+     */
+    addCertificate: function(cert) {
+      // convert from PEM
+      if(typeof cert === 'string') {
+        cert = forge.pki.certificateFromPem(cert);
+      }
+      msg.certificates.push(cert);
+    },
+
+    /**
+     * Add a certificate revokation list.
+     *
+     * @param crl the certificate revokation list to add.
+     */
+    addCertificateRevokationList: function(crl) {
+      throw new Error('PKCS#7 CRL support not yet implemented.');
+    }
+  };
+  return msg;
+};
+
+/**
+ * Creates an empty PKCS#7 message of type EncryptedData.
+ *
+ * @return the message.
+ */
+p7.createEncryptedData = function() {
+  var msg = null;
+  msg = {
+    type: forge.pki.oids.encryptedData,
+    version: 0,
+    encryptedContent: {
+      algorithm: forge.pki.oids['aes256-CBC']
+    },
+
+    /**
+     * Reads an EncryptedData content block (in ASN.1 format)
+     *
+     * @param obj The ASN.1 representation of the EncryptedData content block
+     */
+    fromAsn1: function(obj) {
+      // Validate EncryptedData content block and capture data.
+      _fromAsn1(msg, obj, p7.asn1.encryptedDataValidator);
+    },
+
+    /**
+     * Decrypt encrypted content
+     *
+     * @param key The (symmetric) key as a byte buffer
+     */
+    decrypt: function(key) {
+      if(key !== undefined) {
+        msg.encryptedContent.key = key;
+      }
+      _decryptContent(msg);
+    }
+  };
+  return msg;
+};
+
+/**
+ * Creates an empty PKCS#7 message of type EnvelopedData.
+ *
+ * @return the message.
+ */
+p7.createEnvelopedData = function() {
+  var msg = null;
+  msg = {
+    type: forge.pki.oids.envelopedData,
+    version: 0,
+    recipients: [],
+    encryptedContent: {
+      algorithm: forge.pki.oids['aes256-CBC']
+    },
+
+    /**
+     * Reads an EnvelopedData content block (in ASN.1 format)
+     *
+     * @param obj the ASN.1 representation of the EnvelopedData content block.
+     */
+    fromAsn1: function(obj) {
+      // validate EnvelopedData content block and capture data
+      var capture = _fromAsn1(msg, obj, p7.asn1.envelopedDataValidator);
+      msg.recipients = _recipientInfosFromAsn1(capture.recipientInfos.value);
+    },
+
+    toAsn1: function() {
+      // ContentInfo
+      return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+        // ContentType
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+          asn1.oidToDer(msg.type).getBytes()),
+        // [0] EnvelopedData
+        asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
+          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+            // Version
+            asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+              asn1.integerToDer(msg.version).getBytes()),
+            // RecipientInfos
+            asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true,
+              _recipientInfosToAsn1(msg.recipients)),
+            // EncryptedContentInfo
+            asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true,
+              _encryptedContentToAsn1(msg.encryptedContent))
+          ])
+        ])
+      ]);
+    },
+
+    /**
+     * Find recipient by X.509 certificate's issuer.
+     *
+     * @param cert the certificate with the issuer to look for.
+     *
+     * @return the recipient object.
+     */
+    findRecipient: function(cert) {
+      var sAttr = cert.issuer.attributes;
+
+      for(var i = 0; i < msg.recipients.length; ++i) {
+        var r = msg.recipients[i];
+        var rAttr = r.issuer;
+
+        if(r.serialNumber !== cert.serialNumber) {
+          continue;
+        }
+
+        if(rAttr.length !== sAttr.length) {
+          continue;
+        }
+
+        var match = true;
+        for(var j = 0; j < sAttr.length; ++j) {
+          if(rAttr[j].type !== sAttr[j].type ||
+            rAttr[j].value !== sAttr[j].value) {
+            match = false;
+            break;
+          }
+        }
+
+        if(match) {
+          return r;
+        }
+      }
+
+      return null;
+    },
+
+    /**
+     * Decrypt enveloped content
+     *
+     * @param recipient The recipient object related to the private key
+     * @param privKey The (RSA) private key object
+     */
+    decrypt: function(recipient, privKey) {
+      if(msg.encryptedContent.key === undefined && recipient !== undefined &&
+        privKey !== undefined) {
+        switch(recipient.encryptedContent.algorithm) {
+          case forge.pki.oids.rsaEncryption:
+          case forge.pki.oids.desCBC:
+            var key = privKey.decrypt(recipient.encryptedContent.content);
+            msg.encryptedContent.key = forge.util.createBuffer(key);
+            break;
+
+          default:
+            throw new Error('Unsupported asymmetric cipher, ' +
+              'OID ' + recipient.encryptedContent.algorithm);
+        }
+      }
+
+      _decryptContent(msg);
+    },
+
+    /**
+     * Add (another) entity to list of recipients.
+     *
+     * @param cert The certificate of the entity to add.
+     */
+    addRecipient: function(cert) {
+      msg.recipients.push({
+        version: 0,
+        issuer: cert.issuer.attributes,
+        serialNumber: cert.serialNumber,
+        encryptedContent: {
+          // We simply assume rsaEncryption here, since forge.pki only
+          // supports RSA so far.  If the PKI module supports other
+          // ciphers one day, we need to modify this one as well.
+          algorithm: forge.pki.oids.rsaEncryption,
+          key: cert.publicKey
+        }
+      });
+    },
+
+    /**
+     * Encrypt enveloped content.
+     *
+     * This function supports two optional arguments, cipher and key, which
+     * can be used to influence symmetric encryption.  Unless cipher is
+     * provided, the cipher specified in encryptedContent.algorithm is used
+     * (defaults to AES-256-CBC).  If no key is provided, encryptedContent.key
+     * is (re-)used.  If that one's not set, a random key will be generated
+     * automatically.
+     *
+     * @param [key] The key to be used for symmetric encryption.
+     * @param [cipher] The OID of the symmetric cipher to use.
+     */
+    encrypt: function(key, cipher) {
+      // Part 1: Symmetric encryption
+      if(msg.encryptedContent.content === undefined) {
+        cipher = cipher || msg.encryptedContent.algorithm;
+        key = key || msg.encryptedContent.key;
+
+        var keyLen, ivLen, ciphFn;
+        switch(cipher) {
+          case forge.pki.oids['aes128-CBC']:
+            keyLen = 16;
+            ivLen = 16;
+            ciphFn = forge.aes.createEncryptionCipher;
+            break;
+
+          case forge.pki.oids['aes192-CBC']:
+            keyLen = 24;
+            ivLen = 16;
+            ciphFn = forge.aes.createEncryptionCipher;
+            break;
+
+          case forge.pki.oids['aes256-CBC']:
+            keyLen = 32;
+            ivLen = 16;
+            ciphFn = forge.aes.createEncryptionCipher;
+            break;
+
+          case forge.pki.oids['des-EDE3-CBC']:
+            keyLen = 24;
+            ivLen = 8;
+            ciphFn = forge.des.createEncryptionCipher;
+            break;
+
+          default:
+            throw new Error('Unsupported symmetric cipher, OID ' + cipher);
+        }
+
+        if(key === undefined) {
+          key = forge.util.createBuffer(forge.random.getBytes(keyLen));
+        } else if(key.length() != keyLen) {
+          throw new Error('Symmetric key has wrong length; ' +
+            'got ' + key.length() + ' bytes, expected ' + keyLen + '.');
+        }
+
+        // Keep a copy of the key & IV in the object, so the caller can
+        // use it for whatever reason.
+        msg.encryptedContent.algorithm = cipher;
+        msg.encryptedContent.key = key;
+        msg.encryptedContent.parameter = forge.util.createBuffer(
+          forge.random.getBytes(ivLen));
+
+        var ciph = ciphFn(key);
+        ciph.start(msg.encryptedContent.parameter.copy());
+        ciph.update(msg.content);
+
+        // The finish function does PKCS#7 padding by default, therefore
+        // no action required by us.
+        if(!ciph.finish()) {
+          throw new Error('Symmetric encryption failed.');
+        }
+
+        msg.encryptedContent.content = ciph.output;
+      }
+
+      // Part 2: asymmetric encryption for each recipient
+      for(var i = 0; i < msg.recipients.length; i ++) {
+        var recipient = msg.recipients[i];
+
+        // Nothing to do, encryption already done.
+        if(recipient.encryptedContent.content !== undefined) {
+          continue;
+        }
+
+        switch(recipient.encryptedContent.algorithm) {
+          case forge.pki.oids.rsaEncryption:
+            recipient.encryptedContent.content =
+              recipient.encryptedContent.key.encrypt(
+                msg.encryptedContent.key.data);
+            break;
+
+          default:
+            throw new Error('Unsupported asymmetric cipher, OID ' +
+              recipient.encryptedContent.algorithm);
+        }
+      }
+    }
+  };
+  return msg;
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'pkcs7';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define([
+  'require',
+  'module',
+  './aes',
+  './asn1',
+  './des',
+  './oids',
+  './pem',
+  './pkcs7asn1',
+  './random',
+  './util',
+  './x509'
+], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/pkcs7asn1.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/pkcs7asn1.js
new file mode 100644
index 0000000..f7c4df6
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/pkcs7asn1.js
@@ -0,0 +1,399 @@
+/**
+ * Javascript implementation of PKCS#7 v1.5.  Currently only certain parts of
+ * PKCS#7 are implemented, especially the enveloped-data content type.
+ *
+ * @author Stefan Siegl
+ *
+ * Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
+ *
+ * The ASN.1 representation of PKCS#7 is as follows
+ * (see RFC #2315 for details, http://www.ietf.org/rfc/rfc2315.txt):
+ *
+ * A PKCS#7 message consists of a ContentInfo on root level, which may
+ * contain any number of further ContentInfo nested into it.
+ *
+ * ContentInfo ::= SEQUENCE {
+ *    contentType                ContentType,
+ *    content               [0]  EXPLICIT ANY DEFINED BY contentType OPTIONAL
+ * }
+ *
+ * ContentType ::= OBJECT IDENTIFIER
+ *
+ * EnvelopedData ::= SEQUENCE {
+ *    version                    Version,
+ *    recipientInfos             RecipientInfos,
+ *    encryptedContentInfo       EncryptedContentInfo
+ * }
+ *
+ * EncryptedData ::= SEQUENCE {
+ *    version                    Version,
+ *    encryptedContentInfo       EncryptedContentInfo
+ * }
+ *
+ * Version ::= INTEGER
+ *
+ * RecipientInfos ::= SET OF RecipientInfo
+ *
+ * EncryptedContentInfo ::= SEQUENCE {
+ *   contentType                 ContentType,
+ *   contentEncryptionAlgorithm  ContentEncryptionAlgorithmIdentifier,
+ *   encryptedContent       [0]  IMPLICIT EncryptedContent OPTIONAL
+ * }
+ *
+ * ContentEncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
+ *
+ * The AlgorithmIdentifier contains an Object Identifier (OID) and parameters
+ * for the algorithm, if any. In the case of AES and DES3, there is only one,
+ * the IV.
+ *
+ * AlgorithmIdentifer ::= SEQUENCE {
+ *    algorithm OBJECT IDENTIFIER,
+ *    parameters ANY DEFINED BY algorithm OPTIONAL
+ * }
+ *
+ * EncryptedContent ::= OCTET STRING
+ *
+ * RecipientInfo ::= SEQUENCE {
+ *   version                     Version,
+ *   issuerAndSerialNumber       IssuerAndSerialNumber,
+ *   keyEncryptionAlgorithm      KeyEncryptionAlgorithmIdentifier,
+ *   encryptedKey                EncryptedKey
+ * }
+ *
+ * IssuerAndSerialNumber ::= SEQUENCE {
+ *   issuer                      Name,
+ *   serialNumber                CertificateSerialNumber
+ * }
+ *
+ * CertificateSerialNumber ::= INTEGER
+ *
+ * KeyEncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
+ *
+ * EncryptedKey ::= OCTET STRING
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+// shortcut for ASN.1 API
+var asn1 = forge.asn1;
+
+// shortcut for PKCS#7 API
+var p7v = forge.pkcs7asn1 = forge.pkcs7asn1 || {};
+forge.pkcs7 = forge.pkcs7 || {};
+forge.pkcs7.asn1 = p7v;
+
+var contentInfoValidator = {
+  name: 'ContentInfo',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'ContentInfo.ContentType',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.OID,
+    constructed: false,
+    capture: 'contentType'
+  }, {
+    name: 'ContentInfo.content',
+    tagClass: asn1.Class.CONTEXT_SPECIFIC,
+    type: 0,
+    constructed: true,
+    optional: true,
+    captureAsn1: 'content'
+  }]
+};
+p7v.contentInfoValidator = contentInfoValidator;
+
+var encryptedContentInfoValidator = {
+  name: 'EncryptedContentInfo',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'EncryptedContentInfo.contentType',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.OID,
+    constructed: false,
+    capture: 'contentType'
+  }, {
+    name: 'EncryptedContentInfo.contentEncryptionAlgorithm',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true,
+    value: [{
+      name: 'EncryptedContentInfo.contentEncryptionAlgorithm.algorithm',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.OID,
+      constructed: false,
+      capture: 'encAlgorithm'
+    }, {
+      name: 'EncryptedContentInfo.contentEncryptionAlgorithm.parameter',
+      tagClass: asn1.Class.UNIVERSAL,
+      captureAsn1: 'encParameter'
+    }]
+  }, {
+    name: 'EncryptedContentInfo.encryptedContent',
+    tagClass: asn1.Class.CONTEXT_SPECIFIC,
+    type: 0,
+    /* The PKCS#7 structure output by OpenSSL somewhat differs from what
+     * other implementations do generate.
+     *
+     * OpenSSL generates a structure like this:
+     * SEQUENCE {
+     *    ...
+     *    [0]
+     *       26 DA 67 D2 17 9C 45 3C B1 2A A8 59 2F 29 33 38
+     *       C3 C3 DF 86 71 74 7A 19 9F 40 D0 29 BE 85 90 45
+     *       ...
+     * }
+     *
+     * Whereas other implementations (and this PKCS#7 module) generate:
+     * SEQUENCE {
+     *    ...
+     *    [0] {
+     *       OCTET STRING
+     *          26 DA 67 D2 17 9C 45 3C B1 2A A8 59 2F 29 33 38
+     *          C3 C3 DF 86 71 74 7A 19 9F 40 D0 29 BE 85 90 45
+     *          ...
+     *    }
+     * }
+     *
+     * In order to support both, we just capture the context specific
+     * field here.  The OCTET STRING bit is removed below.
+     */
+    capture: 'encryptedContent',
+    captureAsn1: 'encryptedContentAsn1'
+  }]
+};
+
+p7v.envelopedDataValidator = {
+  name: 'EnvelopedData',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'EnvelopedData.Version',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'version'
+  }, {
+    name: 'EnvelopedData.RecipientInfos',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SET,
+    constructed: true,
+    captureAsn1: 'recipientInfos'
+  }].concat(encryptedContentInfoValidator)
+};
+
+p7v.encryptedDataValidator = {
+  name: 'EncryptedData',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'EncryptedData.Version',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'version'
+  }].concat(encryptedContentInfoValidator)
+};
+
+var signerValidator = {
+  name: 'SignerInfo',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'SignerInfo.Version',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false
+  }, {
+    name: 'SignerInfo.IssuerAndSerialNumber',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true
+  }, {
+    name: 'SignerInfo.DigestAlgorithm',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true
+  }, {
+    name: 'SignerInfo.AuthenticatedAttributes',
+    tagClass: asn1.Class.CONTEXT_SPECIFIC,
+    type: 0,
+    constructed: true,
+    optional: true,
+    capture: 'authenticatedAttributes'
+  }, {
+    name: 'SignerInfo.DigestEncryptionAlgorithm',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true
+  }, {
+    name: 'SignerInfo.EncryptedDigest',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.OCTETSTRING,
+    constructed: false,
+    capture: 'signature'
+  }, {
+    name: 'SignerInfo.UnauthenticatedAttributes',
+    tagClass: asn1.Class.CONTEXT_SPECIFIC,
+    type: 1,
+    constructed: true,
+    optional: true
+  }]
+};
+
+p7v.signedDataValidator = {
+  name: 'SignedData',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'SignedData.Version',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'version'
+  }, {
+    name: 'SignedData.DigestAlgorithms',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SET,
+    constructed: true,
+    captureAsn1: 'digestAlgorithms'
+  },
+  contentInfoValidator,
+  {
+    name: 'SignedData.Certificates',
+    tagClass: asn1.Class.CONTEXT_SPECIFIC,
+    type: 0,
+    optional: true,
+    captureAsn1: 'certificates'
+  }, {
+    name: 'SignedData.CertificateRevocationLists',
+    tagClass: asn1.Class.CONTEXT_SPECIFIC,
+    type: 1,
+    optional: true,
+    captureAsn1: 'crls'
+  }, {
+    name: 'SignedData.SignerInfos',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SET,
+    capture: 'signerInfos',
+    optional: true,
+    value: [signerValidator]
+  }]
+};
+
+p7v.recipientInfoValidator = {
+  name: 'RecipientInfo',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'RecipientInfo.version',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'version'
+  }, {
+    name: 'RecipientInfo.issuerAndSerial',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true,
+    value: [{
+      name: 'RecipientInfo.issuerAndSerial.issuer',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.SEQUENCE,
+      constructed: true,
+      captureAsn1: 'issuer'
+    }, {
+      name: 'RecipientInfo.issuerAndSerial.serialNumber',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.INTEGER,
+      constructed: false,
+      capture: 'serial'
+    }]
+  }, {
+    name: 'RecipientInfo.keyEncryptionAlgorithm',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true,
+    value: [{
+      name: 'RecipientInfo.keyEncryptionAlgorithm.algorithm',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.OID,
+      constructed: false,
+      capture: 'encAlgorithm'
+    }, {
+      name: 'RecipientInfo.keyEncryptionAlgorithm.parameter',
+      tagClass: asn1.Class.UNIVERSAL,
+      constructed: false,
+      captureAsn1: 'encParameter'
+    }]
+  }, {
+    name: 'RecipientInfo.encryptedKey',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.OCTETSTRING,
+    constructed: false,
+    capture: 'encKey'
+  }]
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'pkcs7asn1';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './asn1', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/pki.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/pki.js
new file mode 100644
index 0000000..3df7805
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/pki.js
@@ -0,0 +1,161 @@
+/**
+ * Javascript implementation of a basic Public Key Infrastructure, including
+ * support for RSA public and private keys.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2013 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+// shortcut for asn.1 API
+var asn1 = forge.asn1;
+
+/* Public Key Infrastructure (PKI) implementation. */
+var pki = forge.pki = forge.pki || {};
+
+/**
+ * NOTE: THIS METHOD IS DEPRECATED. Use pem.decode() instead.
+ *
+ * Converts PEM-formatted data to DER.
+ *
+ * @param pem the PEM-formatted data.
+ *
+ * @return the DER-formatted data.
+ */
+pki.pemToDer = function(pem) {
+  var msg = forge.pem.decode(pem)[0];
+  if(msg.procType && msg.procType.type === 'ENCRYPTED') {
+    throw new Error('Could not convert PEM to DER; PEM is encrypted.');
+  }
+  return forge.util.createBuffer(msg.body);
+};
+
+/**
+ * Converts an RSA private key from PEM format.
+ *
+ * @param pem the PEM-formatted private key.
+ *
+ * @return the private key.
+ */
+pki.privateKeyFromPem = function(pem) {
+  var msg = forge.pem.decode(pem)[0];
+
+  if(msg.type !== 'PRIVATE KEY' && msg.type !== 'RSA PRIVATE KEY') {
+    var error = new Error('Could not convert private key from PEM; PEM ' +
+      'header type is not "PRIVATE KEY" or "RSA PRIVATE KEY".');
+    error.headerType = msg.type;
+    throw error;
+  }
+  if(msg.procType && msg.procType.type === 'ENCRYPTED') {
+    throw new Error('Could not convert private key from PEM; PEM is encrypted.');
+  }
+
+  // convert DER to ASN.1 object
+  var obj = asn1.fromDer(msg.body);
+
+  return pki.privateKeyFromAsn1(obj);
+};
+
+/**
+ * Converts an RSA private key to PEM format.
+ *
+ * @param key the private key.
+ * @param maxline the maximum characters per line, defaults to 64.
+ *
+ * @return the PEM-formatted private key.
+ */
+pki.privateKeyToPem = function(key, maxline) {
+  // convert to ASN.1, then DER, then PEM-encode
+  var msg = {
+    type: 'RSA PRIVATE KEY',
+    body: asn1.toDer(pki.privateKeyToAsn1(key)).getBytes()
+  };
+  return forge.pem.encode(msg, {maxline: maxline});
+};
+
+/**
+ * Converts a PrivateKeyInfo to PEM format.
+ *
+ * @param pki the PrivateKeyInfo.
+ * @param maxline the maximum characters per line, defaults to 64.
+ *
+ * @return the PEM-formatted private key.
+ */
+pki.privateKeyInfoToPem = function(pki, maxline) {
+  // convert to DER, then PEM-encode
+  var msg = {
+    type: 'PRIVATE KEY',
+    body: asn1.toDer(pki).getBytes()
+  };
+  return forge.pem.encode(msg, {maxline: maxline});
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'pki';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define([
+  'require',
+  'module',
+  './asn1',
+  './oids',
+  './pbe',
+  './pem',
+  './pbkdf2',
+  './pkcs12',
+  './pss',
+  './rsa',
+  './util',
+  './x509'
+], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/prime.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/prime.js
new file mode 100644
index 0000000..2857c36
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/prime.js
@@ -0,0 +1,337 @@
+/**
+ * Prime number generation API.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+// forge.prime already defined
+if(forge.prime) {
+  return;
+}
+
+/* PRIME API */
+var prime = forge.prime = forge.prime || {};
+
+var BigInteger = forge.jsbn.BigInteger;
+
+// primes are 30k+i for i = 1, 7, 11, 13, 17, 19, 23, 29
+var GCD_30_DELTA = [6, 4, 2, 4, 2, 4, 6, 2];
+var THIRTY = new BigInteger(null);
+THIRTY.fromInt(30);
+var op_or = function(x, y) {return x|y;};
+
+/**
+ * Generates a random probable prime with the given number of bits.
+ *
+ * Alternative algorithms can be specified by name as a string or as an
+ * object with custom options like so:
+ *
+ * {
+ *   name: 'PRIMEINC',
+ *   options: {
+ *     maxBlockTime: <the maximum amount of time to block the main
+ *       thread before allowing I/O other JS to run>,
+ *     millerRabinTests: <the number of miller-rabin tests to run>,
+ *     workerScript: <the worker script URL>,
+ *     workers: <the number of web workers (if supported) to use,
+ *       -1 to use estimated cores minus one>.
+ *     workLoad: the size of the work load, ie: number of possible prime
+ *       numbers for each web worker to check per work assignment,
+ *       (default: 100).
+ *   }
+ * }
+ *
+ * @param bits the number of bits for the prime number.
+ * @param options the options to use.
+ *          [algorithm] the algorithm to use (default: 'PRIMEINC').
+ *          [prng] a custom crypto-secure pseudo-random number generator to use,
+ *            that must define "getBytesSync".
+ *
+ * @return callback(err, num) called once the operation completes.
+ */
+prime.generateProbablePrime = function(bits, options, callback) {
+  if(typeof options === 'function') {
+    callback = options;
+    options = {};
+  }
+  options = options || {};
+
+  // default to PRIMEINC algorithm
+  var algorithm = options.algorithm || 'PRIMEINC';
+  if(typeof algorithm === 'string') {
+    algorithm = {name: algorithm};
+  }
+  algorithm.options = algorithm.options || {};
+
+  // create prng with api that matches BigInteger secure random
+  var prng = options.prng || forge.random;
+  var rng = {
+    // x is an array to fill with bytes
+    nextBytes: function(x) {
+      var b = prng.getBytesSync(x.length);
+      for(var i = 0; i < x.length; ++i) {
+        x[i] = b.charCodeAt(i);
+      }
+    }
+  };
+
+  if(algorithm.name === 'PRIMEINC') {
+    return primeincFindPrime(bits, rng, algorithm.options, callback);
+  }
+
+  throw new Error('Invalid prime generation algorithm: ' + algorithm.name);
+};
+
+function primeincFindPrime(bits, rng, options, callback) {
+  if('workers' in options) {
+    return primeincFindPrimeWithWorkers(bits, rng, options, callback);
+  }
+  return primeincFindPrimeWithoutWorkers(bits, rng, options, callback);
+}
+
+function primeincFindPrimeWithoutWorkers(bits, rng, options, callback) {
+  // initialize random number
+  var num = generateRandom(bits, rng);
+
+  /* Note: All primes are of the form 30k+i for i < 30 and gcd(30, i)=1. The
+  number we are given is always aligned at 30k + 1. Each time the number is
+  determined not to be prime we add to get to the next 'i', eg: if the number
+  was at 30k + 1 we add 6. */
+  var deltaIdx = 0;
+
+  // get required number of MR tests
+  var mrTests = getMillerRabinTests(num.bitLength());
+  if('millerRabinTests' in options) {
+    mrTests = options.millerRabinTests;
+  }
+
+  // find prime nearest to 'num' for maxBlockTime ms
+  // 10 ms gives 5ms of leeway for other calculations before dropping
+  // below 60fps (1000/60 == 16.67), but in reality, the number will
+  // likely be higher due to an 'atomic' big int modPow
+  var maxBlockTime = 10;
+  if('maxBlockTime' in options) {
+    maxBlockTime = options.maxBlockTime;
+  }
+  var start = +new Date();
+  do {
+    // overflow, regenerate random number
+    if(num.bitLength() > bits) {
+      num = generateRandom(bits, rng);
+    }
+    // do primality test
+    if(num.isProbablePrime(mrTests)) {
+      return callback(null, num);
+    }
+    // get next potential prime
+    num.dAddOffset(GCD_30_DELTA[deltaIdx++ % 8], 0);
+  } while(maxBlockTime < 0 || (+new Date() - start < maxBlockTime));
+
+  // keep trying (setImmediate would be better here)
+  forge.util.setImmediate(function() {
+    primeincFindPrimeWithoutWorkers(bits, rng, options, callback);
+  });
+}
+
+function primeincFindPrimeWithWorkers(bits, rng, options, callback) {
+  // web workers unavailable
+  if(typeof Worker === 'undefined') {
+    return primeincFindPrimeWithoutWorkers(bits, rng, options, callback);
+  }
+
+  // initialize random number
+  var num = generateRandom(bits, rng);
+
+  // use web workers to generate keys
+  var numWorkers = options.workers;
+  var workLoad = options.workLoad || 100;
+  var range = workLoad * 30 / 8;
+  var workerScript = options.workerScript || 'forge/prime.worker.js';
+  if(numWorkers === -1) {
+    return forge.util.estimateCores(function(err, cores) {
+      if(err) {
+        // default to 2
+        cores = 2;
+      }
+      numWorkers = cores - 1;
+      generate();
+    });
+  }
+  generate();
+
+  function generate() {
+    // require at least 1 worker
+    numWorkers = Math.max(1, numWorkers);
+
+    // TODO: consider optimizing by starting workers outside getPrime() ...
+    // note that in order to clean up they will have to be made internally
+    // asynchronous which may actually be slower
+
+    // start workers immediately
+    var workers = [];
+    for(var i = 0; i < numWorkers; ++i) {
+      // FIXME: fix path or use blob URLs
+      workers[i] = new Worker(workerScript);
+    }
+    var running = numWorkers;
+
+    // listen for requests from workers and assign ranges to find prime
+    for(var i = 0; i < numWorkers; ++i) {
+      workers[i].addEventListener('message', workerMessage);
+    }
+
+    /* Note: The distribution of random numbers is unknown. Therefore, each
+    web worker is continuously allocated a range of numbers to check for a
+    random number until one is found.
+
+    Every 30 numbers will be checked just 8 times, because prime numbers
+    have the form:
+
+    30k+i, for i < 30 and gcd(30, i)=1 (there are 8 values of i for this)
+
+    Therefore, if we want a web worker to run N checks before asking for
+    a new range of numbers, each range must contain N*30/8 numbers.
+
+    For 100 checks (workLoad), this is a range of 375. */
+
+    var found = false;
+    function workerMessage(e) {
+      // ignore message, prime already found
+      if(found) {
+        return;
+      }
+
+      --running;
+      var data = e.data;
+      if(data.found) {
+        // terminate all workers
+        for(var i = 0; i < workers.length; ++i) {
+          workers[i].terminate();
+        }
+        found = true;
+        return callback(null, new BigInteger(data.prime, 16));
+      }
+
+      // overflow, regenerate random number
+      if(num.bitLength() > bits) {
+        num = generateRandom(bits, rng);
+      }
+
+      // assign new range to check
+      var hex = num.toString(16);
+
+      // start prime search
+      e.target.postMessage({
+        hex: hex,
+        workLoad: workLoad
+      });
+
+      num.dAddOffset(range, 0);
+    }
+  }
+}
+
+/**
+ * Generates a random number using the given number of bits and RNG.
+ *
+ * @param bits the number of bits for the number.
+ * @param rng the random number generator to use.
+ *
+ * @return the random number.
+ */
+function generateRandom(bits, rng) {
+  var num = new BigInteger(bits, rng);
+  // force MSB set
+  var bits1 = bits - 1;
+  if(!num.testBit(bits1)) {
+    num.bitwiseTo(BigInteger.ONE.shiftLeft(bits1), op_or, num);
+  }
+  // align number on 30k+1 boundary
+  num.dAddOffset(31 - num.mod(THIRTY).byteValue(), 0);
+  return num;
+}
+
+/**
+ * Returns the required number of Miller-Rabin tests to generate a
+ * prime with an error probability of (1/2)^80.
+ *
+ * See Handbook of Applied Cryptography Chapter 4, Table 4.4.
+ *
+ * @param bits the bit size.
+ *
+ * @return the required number of iterations.
+ */
+function getMillerRabinTests(bits) {
+  if(bits <= 100) return 27;
+  if(bits <= 150) return 18;
+  if(bits <= 200) return 15;
+  if(bits <= 250) return 12;
+  if(bits <= 300) return 9;
+  if(bits <= 350) return 8;
+  if(bits <= 400) return 7;
+  if(bits <= 500) return 6;
+  if(bits <= 600) return 5;
+  if(bits <= 800) return 4;
+  if(bits <= 1250) return 3;
+  return 2;
+}
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'prime';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './util', './jsbn', './random'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/prime.worker.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/prime.worker.js
new file mode 100644
index 0000000..5fdaa7f
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/prime.worker.js
@@ -0,0 +1,165 @@
+/**
+ * RSA Key Generation Worker.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2013 Digital Bazaar, Inc.
+ */
+importScripts('jsbn.js');
+
+// prime constants
+var LOW_PRIMES = [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,587,593,599,601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691,701,709,719,727,733,739,743,751,757,761,769,773,787,797,809,811,821,823,827,829,839,853,857,859,863,877,881,883,887,907,911,919,929,937,941,947,953,967,971,977,983,991,997];
+var LP_LIMIT = (1 << 26) / LOW_PRIMES[LOW_PRIMES.length - 1];
+
+var BigInteger = forge.jsbn.BigInteger;
+var BIG_TWO = new BigInteger(null);
+BIG_TWO.fromInt(2);
+
+self.addEventListener('message', function(e) {
+  var result = findPrime(e.data);
+  self.postMessage(result);
+});
+
+// start receiving ranges to check
+self.postMessage({found: false});
+
+// primes are 30k+i for i = 1, 7, 11, 13, 17, 19, 23, 29
+var GCD_30_DELTA = [6, 4, 2, 4, 2, 4, 6, 2];
+
+function findPrime(data) {
+  // TODO: abstract based on data.algorithm (PRIMEINC vs. others)
+
+  // create BigInteger from given random bytes
+  var num = new BigInteger(data.hex, 16);
+
+  /* Note: All primes are of the form 30k+i for i < 30 and gcd(30, i)=1. The
+    number we are given is always aligned at 30k + 1. Each time the number is
+    determined not to be prime we add to get to the next 'i', eg: if the number
+    was at 30k + 1 we add 6. */
+  var deltaIdx = 0;
+
+  // find nearest prime
+  var workLoad = data.workLoad;
+  for(var i = 0; i < workLoad; ++i) {
+    // do primality test
+    if(isProbablePrime(num)) {
+      return {found: true, prime: num.toString(16)};
+    }
+    // get next potential prime
+    num.dAddOffset(GCD_30_DELTA[deltaIdx++ % 8], 0);
+  }
+
+  return {found: false};
+}
+
+function isProbablePrime(n) {
+  // divide by low primes, ignore even checks, etc (n alread aligned properly)
+  var i = 1;
+  while(i < LOW_PRIMES.length) {
+    var m = LOW_PRIMES[i];
+    var j = i + 1;
+    while(j < LOW_PRIMES.length && m < LP_LIMIT) {
+      m *= LOW_PRIMES[j++];
+    }
+    m = n.modInt(m);
+    while(i < j) {
+      if(m % LOW_PRIMES[i++] === 0) {
+        return false;
+      }
+    }
+  }
+  return runMillerRabin(n);
+}
+
+// HAC 4.24, Miller-Rabin
+function runMillerRabin(n) {
+  // n1 = n - 1
+  var n1 = n.subtract(BigInteger.ONE);
+
+  // get s and d such that n1 = 2^s * d
+  var s = n1.getLowestSetBit();
+  if(s <= 0) {
+    return false;
+  }
+  var d = n1.shiftRight(s);
+
+  var k = _getMillerRabinTests(n.bitLength());
+  var prng = getPrng();
+  var a;
+  for(var i = 0; i < k; ++i) {
+    // select witness 'a' at random from between 1 and n - 1
+    do {
+      a = new BigInteger(n.bitLength(), prng);
+    } while(a.compareTo(BigInteger.ONE) <= 0 || a.compareTo(n1) >= 0);
+
+    /* See if 'a' is a composite witness. */
+
+    // x = a^d mod n
+    var x = a.modPow(d, n);
+
+    // probably prime
+    if(x.compareTo(BigInteger.ONE) === 0 || x.compareTo(n1) === 0) {
+      continue;
+    }
+
+    var j = s;
+    while(--j) {
+      // x = x^2 mod a
+      x = x.modPowInt(2, n);
+
+      // 'n' is composite because no previous x == -1 mod n
+      if(x.compareTo(BigInteger.ONE) === 0) {
+        return false;
+      }
+      // x == -1 mod n, so probably prime
+      if(x.compareTo(n1) === 0) {
+        break;
+      }
+    }
+
+    // 'x' is first_x^(n1/2) and is not +/- 1, so 'n' is not prime
+    if(j === 0) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+// get pseudo random number generator
+function getPrng() {
+  // create prng with api that matches BigInteger secure random
+  return {
+    // x is an array to fill with bytes
+    nextBytes: function(x) {
+      for(var i = 0; i < x.length; ++i) {
+        x[i] = Math.floor(Math.random() * 0xFF);
+      }
+    }
+  };
+}
+
+/**
+ * Returns the required number of Miller-Rabin tests to generate a
+ * prime with an error probability of (1/2)^80.
+ *
+ * See Handbook of Applied Cryptography Chapter 4, Table 4.4.
+ *
+ * @param bits the bit size.
+ *
+ * @return the required number of iterations.
+ */
+function _getMillerRabinTests(bits) {
+  if(bits <= 100) return 27;
+  if(bits <= 150) return 18;
+  if(bits <= 200) return 15;
+  if(bits <= 250) return 12;
+  if(bits <= 300) return 9;
+  if(bits <= 350) return 8;
+  if(bits <= 400) return 7;
+  if(bits <= 500) return 6;
+  if(bits <= 600) return 5;
+  if(bits <= 800) return 4;
+  if(bits <= 1250) return 3;
+  return 2;
+}
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/prng.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/prng.js
new file mode 100644
index 0000000..72b4594
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/prng.js
@@ -0,0 +1,458 @@
+/**
+ * A javascript implementation of a cryptographically-secure
+ * Pseudo Random Number Generator (PRNG). The Fortuna algorithm is followed
+ * here though the use of SHA-256 is not enforced; when generating an
+ * a PRNG context, the hashing algorithm and block cipher used for
+ * the generator are specified via a plugin.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+var _nodejs = (
+  typeof process !== 'undefined' && process.versions && process.versions.node);
+var _crypto = null;
+if(!forge.disableNativeCode && _nodejs && !process.versions['node-webkit']) {
+  _crypto = require('crypto');
+}
+
+/* PRNG API */
+var prng = forge.prng = forge.prng || {};
+
+/**
+ * Creates a new PRNG context.
+ *
+ * A PRNG plugin must be passed in that will provide:
+ *
+ * 1. A function that initializes the key and seed of a PRNG context. It
+ *   will be given a 16 byte key and a 16 byte seed. Any key expansion
+ *   or transformation of the seed from a byte string into an array of
+ *   integers (or similar) should be performed.
+ * 2. The cryptographic function used by the generator. It takes a key and
+ *   a seed.
+ * 3. A seed increment function. It takes the seed and returns seed + 1.
+ * 4. An api to create a message digest.
+ *
+ * For an example, see random.js.
+ *
+ * @param plugin the PRNG plugin to use.
+ */
+prng.create = function(plugin) {
+  var ctx = {
+    plugin: plugin,
+    key: null,
+    seed: null,
+    time: null,
+    // number of reseeds so far
+    reseeds: 0,
+    // amount of data generated so far
+    generated: 0
+  };
+
+  // create 32 entropy pools (each is a message digest)
+  var md = plugin.md;
+  var pools = new Array(32);
+  for(var i = 0; i < 32; ++i) {
+    pools[i] = md.create();
+  }
+  ctx.pools = pools;
+
+  // entropy pools are written to cyclically, starting at index 0
+  ctx.pool = 0;
+
+  /**
+   * Generates random bytes. The bytes may be generated synchronously or
+   * asynchronously. Web workers must use the asynchronous interface or
+   * else the behavior is undefined.
+   *
+   * @param count the number of random bytes to generate.
+   * @param [callback(err, bytes)] called once the operation completes.
+   *
+   * @return count random bytes as a string.
+   */
+  ctx.generate = function(count, callback) {
+    // do synchronously
+    if(!callback) {
+      return ctx.generateSync(count);
+    }
+
+    // simple generator using counter-based CBC
+    var cipher = ctx.plugin.cipher;
+    var increment = ctx.plugin.increment;
+    var formatKey = ctx.plugin.formatKey;
+    var formatSeed = ctx.plugin.formatSeed;
+    var b = forge.util.createBuffer();
+
+    // reset key for every request
+    ctx.key = null;
+
+    generate();
+
+    function generate(err) {
+      if(err) {
+        return callback(err);
+      }
+
+      // sufficient bytes generated
+      if(b.length() >= count) {
+        return callback(null, b.getBytes(count));
+      }
+
+      // if amount of data generated is greater than 1 MiB, trigger reseed
+      if(ctx.generated > 0xfffff) {
+        ctx.key = null;
+      }
+
+      if(ctx.key === null) {
+        // prevent stack overflow
+        return forge.util.nextTick(function() {
+          _reseed(generate);
+        });
+      }
+
+      // generate the random bytes
+      var bytes = cipher(ctx.key, ctx.seed);
+      ctx.generated += bytes.length;
+      b.putBytes(bytes);
+
+      // generate bytes for a new key and seed
+      ctx.key = formatKey(cipher(ctx.key, increment(ctx.seed)));
+      ctx.seed = formatSeed(cipher(ctx.key, ctx.seed));
+
+      forge.util.setImmediate(generate);
+    }
+  };
+
+  /**
+   * Generates random bytes synchronously.
+   *
+   * @param count the number of random bytes to generate.
+   *
+   * @return count random bytes as a string.
+   */
+  ctx.generateSync = function(count) {
+    // simple generator using counter-based CBC
+    var cipher = ctx.plugin.cipher;
+    var increment = ctx.plugin.increment;
+    var formatKey = ctx.plugin.formatKey;
+    var formatSeed = ctx.plugin.formatSeed;
+
+    // reset key for every request
+    ctx.key = null;
+
+    var b = forge.util.createBuffer();
+    while(b.length() < count) {
+      // if amount of data generated is greater than 1 MiB, trigger reseed
+      if(ctx.generated > 0xfffff) {
+        ctx.key = null;
+      }
+
+      if(ctx.key === null) {
+        _reseedSync();
+      }
+
+      // generate the random bytes
+      var bytes = cipher(ctx.key, ctx.seed);
+      ctx.generated += bytes.length;
+      b.putBytes(bytes);
+
+      // generate bytes for a new key and seed
+      ctx.key = formatKey(cipher(ctx.key, increment(ctx.seed)));
+      ctx.seed = formatSeed(cipher(ctx.key, ctx.seed));
+    }
+
+    return b.getBytes(count);
+  };
+
+  /**
+   * Private function that asynchronously reseeds a generator.
+   *
+   * @param callback(err) called once the operation completes.
+   */
+  function _reseed(callback) {
+    if(ctx.pools[0].messageLength >= 32) {
+      _seed();
+      return callback();
+    }
+    // not enough seed data...
+    var needed = (32 - ctx.pools[0].messageLength) << 5;
+    ctx.seedFile(needed, function(err, bytes) {
+      if(err) {
+        return callback(err);
+      }
+      ctx.collect(bytes);
+      _seed();
+      callback();
+    });
+  }
+
+  /**
+   * Private function that synchronously reseeds a generator.
+   */
+  function _reseedSync() {
+    if(ctx.pools[0].messageLength >= 32) {
+      return _seed();
+    }
+    // not enough seed data...
+    var needed = (32 - ctx.pools[0].messageLength) << 5;
+    ctx.collect(ctx.seedFileSync(needed));
+    _seed();
+  }
+
+  /**
+   * Private function that seeds a generator once enough bytes are available.
+   */
+  function _seed() {
+    // create a plugin-based message digest
+    var md = ctx.plugin.md.create();
+
+    // digest pool 0's entropy and restart it
+    md.update(ctx.pools[0].digest().getBytes());
+    ctx.pools[0].start();
+
+    // digest the entropy of other pools whose index k meet the
+    // condition '2^k mod n == 0' where n is the number of reseeds
+    var k = 1;
+    for(var i = 1; i < 32; ++i) {
+      // prevent signed numbers from being used
+      k = (k === 31) ? 0x80000000 : (k << 2);
+      if(k % ctx.reseeds === 0) {
+        md.update(ctx.pools[i].digest().getBytes());
+        ctx.pools[i].start();
+      }
+    }
+
+    // get digest for key bytes and iterate again for seed bytes
+    var keyBytes = md.digest().getBytes();
+    md.start();
+    md.update(keyBytes);
+    var seedBytes = md.digest().getBytes();
+
+    // update
+    ctx.key = ctx.plugin.formatKey(keyBytes);
+    ctx.seed = ctx.plugin.formatSeed(seedBytes);
+    ctx.reseeds = (ctx.reseeds === 0xffffffff) ? 0 : ctx.reseeds + 1;
+    ctx.generated = 0;
+  }
+
+  /**
+   * The built-in default seedFile. This seedFile is used when entropy
+   * is needed immediately.
+   *
+   * @param needed the number of bytes that are needed.
+   *
+   * @return the random bytes.
+   */
+  function defaultSeedFile(needed) {
+    // use window.crypto.getRandomValues strong source of entropy if available
+    var getRandomValues = null;
+    if(typeof window !== 'undefined') {
+      var _crypto = window.crypto || window.msCrypto;
+      if(_crypto && _crypto.getRandomValues) {
+        getRandomValues = function(arr) {
+          return _crypto.getRandomValues(arr);
+        };
+      }
+    }
+
+    var b = forge.util.createBuffer();
+    if(getRandomValues) {
+      while(b.length() < needed) {
+        // max byte length is 65536 before QuotaExceededError is thrown
+        // http://www.w3.org/TR/WebCryptoAPI/#RandomSource-method-getRandomValues
+        var count = Math.max(1, Math.min(needed - b.length(), 65536) / 4);
+        var entropy = new Uint32Array(Math.floor(count));
+        try {
+          getRandomValues(entropy);
+          for(var i = 0; i < entropy.length; ++i) {
+            b.putInt32(entropy[i]);
+          }
+        } catch(e) {
+          /* only ignore QuotaExceededError */
+          if(!(typeof QuotaExceededError !== 'undefined' &&
+            e instanceof QuotaExceededError)) {
+            throw e;
+          }
+        }
+      }
+    }
+
+    // be sad and add some weak random data
+    if(b.length() < needed) {
+      /* Draws from Park-Miller "minimal standard" 31 bit PRNG,
+      implemented with David G. Carta's optimization: with 32 bit math
+      and without division (Public Domain). */
+      var hi, lo, next;
+      var seed = Math.floor(Math.random() * 0x010000);
+      while(b.length() < needed) {
+        lo = 16807 * (seed & 0xFFFF);
+        hi = 16807 * (seed >> 16);
+        lo += (hi & 0x7FFF) << 16;
+        lo += hi >> 15;
+        lo = (lo & 0x7FFFFFFF) + (lo >> 31);
+        seed = lo & 0xFFFFFFFF;
+
+        // consume lower 3 bytes of seed
+        for(var i = 0; i < 3; ++i) {
+          // throw in more pseudo random
+          next = seed >>> (i << 3);
+          next ^= Math.floor(Math.random() * 0x0100);
+          b.putByte(String.fromCharCode(next & 0xFF));
+        }
+      }
+    }
+
+    return b.getBytes(needed);
+  }
+  // initialize seed file APIs
+  if(_crypto) {
+    // use nodejs async API
+    ctx.seedFile = function(needed, callback) {
+      _crypto.randomBytes(needed, function(err, bytes) {
+        if(err) {
+          return callback(err);
+        }
+        callback(null, bytes.toString());
+      });
+    };
+    // use nodejs sync API
+    ctx.seedFileSync = function(needed) {
+      return _crypto.randomBytes(needed).toString();
+    };
+  } else {
+    ctx.seedFile = function(needed, callback) {
+      try {
+        callback(null, defaultSeedFile(needed));
+      } catch(e) {
+        callback(e);
+      }
+    };
+    ctx.seedFileSync = defaultSeedFile;
+  }
+
+  /**
+   * Adds entropy to a prng ctx's accumulator.
+   *
+   * @param bytes the bytes of entropy as a string.
+   */
+  ctx.collect = function(bytes) {
+    // iterate over pools distributing entropy cyclically
+    var count = bytes.length;
+    for(var i = 0; i < count; ++i) {
+      ctx.pools[ctx.pool].update(bytes.substr(i, 1));
+      ctx.pool = (ctx.pool === 31) ? 0 : ctx.pool + 1;
+    }
+  };
+
+  /**
+   * Collects an integer of n bits.
+   *
+   * @param i the integer entropy.
+   * @param n the number of bits in the integer.
+   */
+  ctx.collectInt = function(i, n) {
+    var bytes = '';
+    for(var x = 0; x < n; x += 8) {
+      bytes += String.fromCharCode((i >> x) & 0xFF);
+    }
+    ctx.collect(bytes);
+  };
+
+  /**
+   * Registers a Web Worker to receive immediate entropy from the main thread.
+   * This method is required until Web Workers can access the native crypto
+   * API. This method should be called twice for each created worker, once in
+   * the main thread, and once in the worker itself.
+   *
+   * @param worker the worker to register.
+   */
+  ctx.registerWorker = function(worker) {
+    // worker receives random bytes
+    if(worker === self) {
+      ctx.seedFile = function(needed, callback) {
+        function listener(e) {
+          var data = e.data;
+          if(data.forge && data.forge.prng) {
+            self.removeEventListener('message', listener);
+            callback(data.forge.prng.err, data.forge.prng.bytes);
+          }
+        }
+        self.addEventListener('message', listener);
+        self.postMessage({forge: {prng: {needed: needed}}});
+      };
+    } else {
+      // main thread sends random bytes upon request
+      var listener = function(e) {
+        var data = e.data;
+        if(data.forge && data.forge.prng) {
+          ctx.seedFile(data.forge.prng.needed, function(err, bytes) {
+            worker.postMessage({forge: {prng: {err: err, bytes: bytes}}});
+          });
+        }
+      };
+      // TODO: do we need to remove the event listener when the worker dies?
+      worker.addEventListener('message', listener);
+    }
+  };
+
+  return ctx;
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'prng';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './md', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/pss.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/pss.js
new file mode 100644
index 0000000..1b284fc
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/pss.js
@@ -0,0 +1,295 @@
+/**
+ * Javascript implementation of PKCS#1 PSS signature padding.
+ *
+ * @author Stefan Siegl
+ *
+ * Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+// shortcut for PSS API
+var pss = forge.pss = forge.pss || {};
+
+/**
+ * Creates a PSS signature scheme object.
+ *
+ * There are several ways to provide a salt for encoding:
+ *
+ * 1. Specify the saltLength only and the built-in PRNG will generate it.
+ * 2. Specify the saltLength and a custom PRNG with 'getBytesSync' defined that
+ *   will be used.
+ * 3. Specify the salt itself as a forge.util.ByteBuffer.
+ *
+ * @param options the options to use:
+ *          md the message digest object to use, a forge md instance.
+ *          mgf the mask generation function to use, a forge mgf instance.
+ *          [saltLength] the length of the salt in octets.
+ *          [prng] the pseudo-random number generator to use to produce a salt.
+ *          [salt] the salt to use when encoding.
+ *
+ * @return a signature scheme object.
+ */
+pss.create = function(options) {
+  // backwards compatibility w/legacy args: hash, mgf, sLen
+  if(arguments.length === 3) {
+    options = {
+      md: arguments[0],
+      mgf: arguments[1],
+      saltLength: arguments[2]
+    };
+  }
+
+  var hash = options.md;
+  var mgf = options.mgf;
+  var hLen = hash.digestLength;
+
+  var salt_ = options.salt || null;
+  if(typeof salt_ === 'string') {
+    // assume binary-encoded string
+    salt_ = forge.util.createBuffer(salt_);
+  }
+
+  var sLen;
+  if('saltLength' in options) {
+    sLen = options.saltLength;
+  } else if(salt_ !== null) {
+    sLen = salt_.length();
+  } else {
+    throw new Error('Salt length not specified or specific salt not given.');
+  }
+
+  if(salt_ !== null && salt_.length() !== sLen) {
+    throw new Error('Given salt length does not match length of given salt.');
+  }
+
+  var prng = options.prng || forge.random;
+
+  var pssobj = {};
+
+  /**
+   * Encodes a PSS signature.
+   *
+   * This function implements EMSA-PSS-ENCODE as per RFC 3447, section 9.1.1.
+   *
+   * @param md the message digest object with the hash to sign.
+   * @param modsBits the length of the RSA modulus in bits.
+   *
+   * @return the encoded message as a binary-encoded string of length
+   *           ceil((modBits - 1) / 8).
+   */
+  pssobj.encode = function(md, modBits) {
+    var i;
+    var emBits = modBits - 1;
+    var emLen = Math.ceil(emBits / 8);
+
+    /* 2. Let mHash = Hash(M), an octet string of length hLen. */
+    var mHash = md.digest().getBytes();
+
+    /* 3. If emLen < hLen + sLen + 2, output "encoding error" and stop. */
+    if(emLen < hLen + sLen + 2) {
+      throw new Error('Message is too long to encrypt.');
+    }
+
+    /* 4. Generate a random octet string salt of length sLen; if sLen = 0,
+     *    then salt is the empty string. */
+    var salt;
+    if(salt_ === null) {
+      salt = prng.getBytesSync(sLen);
+    } else {
+      salt = salt_.bytes();
+    }
+
+    /* 5. Let M' = (0x)00 00 00 00 00 00 00 00 || mHash || salt; */
+    var m_ = new forge.util.ByteBuffer();
+    m_.fillWithByte(0, 8);
+    m_.putBytes(mHash);
+    m_.putBytes(salt);
+
+    /* 6. Let H = Hash(M'), an octet string of length hLen. */
+    hash.start();
+    hash.update(m_.getBytes());
+    var h = hash.digest().getBytes();
+
+    /* 7. Generate an octet string PS consisting of emLen - sLen - hLen - 2
+     *    zero octets.  The length of PS may be 0. */
+    var ps = new forge.util.ByteBuffer();
+    ps.fillWithByte(0, emLen - sLen - hLen - 2);
+
+    /* 8. Let DB = PS || 0x01 || salt; DB is an octet string of length
+     *    emLen - hLen - 1. */
+    ps.putByte(0x01);
+    ps.putBytes(salt);
+    var db = ps.getBytes();
+
+    /* 9. Let dbMask = MGF(H, emLen - hLen - 1). */
+    var maskLen = emLen - hLen - 1;
+    var dbMask = mgf.generate(h, maskLen);
+
+    /* 10. Let maskedDB = DB \xor dbMask. */
+    var maskedDB = '';
+    for(i = 0; i < maskLen; i ++) {
+      maskedDB += String.fromCharCode(db.charCodeAt(i) ^ dbMask.charCodeAt(i));
+    }
+
+    /* 11. Set the leftmost 8emLen - emBits bits of the leftmost octet in
+     *     maskedDB to zero. */
+    var mask = (0xFF00 >> (8 * emLen - emBits)) & 0xFF;
+    maskedDB = String.fromCharCode(maskedDB.charCodeAt(0) & ~mask) +
+      maskedDB.substr(1);
+
+    /* 12. Let EM = maskedDB || H || 0xbc.
+     * 13. Output EM. */
+    return maskedDB + h + String.fromCharCode(0xbc);
+  };
+
+  /**
+   * Verifies a PSS signature.
+   *
+   * This function implements EMSA-PSS-VERIFY as per RFC 3447, section 9.1.2.
+   *
+   * @param mHash the message digest hash, as a binary-encoded string, to
+   *         compare against the signature.
+   * @param em the encoded message, as a binary-encoded string
+   *          (RSA decryption result).
+   * @param modsBits the length of the RSA modulus in bits.
+   *
+   * @return true if the signature was verified, false if not.
+   */
+  pssobj.verify = function(mHash, em, modBits) {
+    var i;
+    var emBits = modBits - 1;
+    var emLen = Math.ceil(emBits / 8);
+
+    /* c. Convert the message representative m to an encoded message EM
+     *    of length emLen = ceil((modBits - 1) / 8) octets, where modBits
+     *    is the length in bits of the RSA modulus n */
+    em = em.substr(-emLen);
+
+    /* 3. If emLen < hLen + sLen + 2, output "inconsistent" and stop. */
+    if(emLen < hLen + sLen + 2) {
+      throw new Error('Inconsistent parameters to PSS signature verification.');
+    }
+
+    /* 4. If the rightmost octet of EM does not have hexadecimal value
+     *    0xbc, output "inconsistent" and stop. */
+    if(em.charCodeAt(emLen - 1) !== 0xbc) {
+      throw new Error('Encoded message does not end in 0xBC.');
+    }
+
+    /* 5. Let maskedDB be the leftmost emLen - hLen - 1 octets of EM, and
+     *    let H be the next hLen octets. */
+    var maskLen = emLen - hLen - 1;
+    var maskedDB = em.substr(0, maskLen);
+    var h = em.substr(maskLen, hLen);
+
+    /* 6. If the leftmost 8emLen - emBits bits of the leftmost octet in
+     *    maskedDB are not all equal to zero, output "inconsistent" and stop. */
+    var mask = (0xFF00 >> (8 * emLen - emBits)) & 0xFF;
+    if((maskedDB.charCodeAt(0) & mask) !== 0) {
+      throw new Error('Bits beyond keysize not zero as expected.');
+    }
+
+    /* 7. Let dbMask = MGF(H, emLen - hLen - 1). */
+    var dbMask = mgf.generate(h, maskLen);
+
+    /* 8. Let DB = maskedDB \xor dbMask. */
+    var db = '';
+    for(i = 0; i < maskLen; i ++) {
+      db += String.fromCharCode(maskedDB.charCodeAt(i) ^ dbMask.charCodeAt(i));
+    }
+
+    /* 9. Set the leftmost 8emLen - emBits bits of the leftmost octet
+     * in DB to zero. */
+    db = String.fromCharCode(db.charCodeAt(0) & ~mask) + db.substr(1);
+
+    /* 10. If the emLen - hLen - sLen - 2 leftmost octets of DB are not zero
+     * or if the octet at position emLen - hLen - sLen - 1 (the leftmost
+     * position is "position 1") does not have hexadecimal value 0x01,
+     * output "inconsistent" and stop. */
+    var checkLen = emLen - hLen - sLen - 2;
+    for(i = 0; i < checkLen; i ++) {
+      if(db.charCodeAt(i) !== 0x00) {
+        throw new Error('Leftmost octets not zero as expected');
+      }
+    }
+
+    if(db.charCodeAt(checkLen) !== 0x01) {
+      throw new Error('Inconsistent PSS signature, 0x01 marker not found');
+    }
+
+    /* 11. Let salt be the last sLen octets of DB. */
+    var salt = db.substr(-sLen);
+
+    /* 12.  Let M' = (0x)00 00 00 00 00 00 00 00 || mHash || salt */
+    var m_ = new forge.util.ByteBuffer();
+    m_.fillWithByte(0, 8);
+    m_.putBytes(mHash);
+    m_.putBytes(salt);
+
+    /* 13. Let H' = Hash(M'), an octet string of length hLen. */
+    hash.start();
+    hash.update(m_.getBytes());
+    var h_ = hash.digest().getBytes();
+
+    /* 14. If H = H', output "consistent." Otherwise, output "inconsistent." */
+    return h === h_;
+  };
+
+  return pssobj;
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'pss';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './random', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/random.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/random.js
new file mode 100644
index 0000000..febc1fd
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/random.js
@@ -0,0 +1,237 @@
+/**
+ * An API for getting cryptographically-secure random bytes. The bytes are
+ * generated using the Fortuna algorithm devised by Bruce Schneier and
+ * Niels Ferguson.
+ *
+ * Getting strong random bytes is not yet easy to do in javascript. The only
+ * truish random entropy that can be collected is from the mouse, keyboard, or
+ * from timing with respect to page loads, etc. This generator makes a poor
+ * attempt at providing random bytes when those sources haven't yet provided
+ * enough entropy to initially seed or to reseed the PRNG.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2009-2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+// forge.random already defined
+if(forge.random && forge.random.getBytes) {
+  return;
+}
+
+(function(jQuery) {
+
+// the default prng plugin, uses AES-128
+var prng_aes = {};
+var _prng_aes_output = new Array(4);
+var _prng_aes_buffer = forge.util.createBuffer();
+prng_aes.formatKey = function(key) {
+  // convert the key into 32-bit integers
+  var tmp = forge.util.createBuffer(key);
+  key = new Array(4);
+  key[0] = tmp.getInt32();
+  key[1] = tmp.getInt32();
+  key[2] = tmp.getInt32();
+  key[3] = tmp.getInt32();
+
+  // return the expanded key
+  return forge.aes._expandKey(key, false);
+};
+prng_aes.formatSeed = function(seed) {
+  // convert seed into 32-bit integers
+  var tmp = forge.util.createBuffer(seed);
+  seed = new Array(4);
+  seed[0] = tmp.getInt32();
+  seed[1] = tmp.getInt32();
+  seed[2] = tmp.getInt32();
+  seed[3] = tmp.getInt32();
+  return seed;
+};
+prng_aes.cipher = function(key, seed) {
+  forge.aes._updateBlock(key, seed, _prng_aes_output, false);
+  _prng_aes_buffer.putInt32(_prng_aes_output[0]);
+  _prng_aes_buffer.putInt32(_prng_aes_output[1]);
+  _prng_aes_buffer.putInt32(_prng_aes_output[2]);
+  _prng_aes_buffer.putInt32(_prng_aes_output[3]);
+  return _prng_aes_buffer.getBytes();
+};
+prng_aes.increment = function(seed) {
+  // FIXME: do we care about carry or signed issues?
+  ++seed[3];
+  return seed;
+};
+prng_aes.md = forge.md.sha256;
+
+/**
+ * Creates a new PRNG.
+ */
+function spawnPrng() {
+  var ctx = forge.prng.create(prng_aes);
+
+  /**
+   * Gets random bytes. If a native secure crypto API is unavailable, this
+   * method tries to make the bytes more unpredictable by drawing from data that
+   * can be collected from the user of the browser, eg: mouse movement.
+   *
+   * If a callback is given, this method will be called asynchronously.
+   *
+   * @param count the number of random bytes to get.
+   * @param [callback(err, bytes)] called once the operation completes.
+   *
+   * @return the random bytes in a string.
+   */
+  ctx.getBytes = function(count, callback) {
+    return ctx.generate(count, callback);
+  };
+
+  /**
+   * Gets random bytes asynchronously. If a native secure crypto API is
+   * unavailable, this method tries to make the bytes more unpredictable by
+   * drawing from data that can be collected from the user of the browser,
+   * eg: mouse movement.
+   *
+   * @param count the number of random bytes to get.
+   *
+   * @return the random bytes in a string.
+   */
+  ctx.getBytesSync = function(count) {
+    return ctx.generate(count);
+  };
+
+  return ctx;
+}
+
+// create default prng context
+var _ctx = spawnPrng();
+
+// add other sources of entropy only if window.crypto.getRandomValues is not
+// available -- otherwise this source will be automatically used by the prng
+var _nodejs = (
+  typeof process !== 'undefined' && process.versions && process.versions.node);
+var getRandomValues = null;
+if(typeof window !== 'undefined') {
+  var _crypto = window.crypto || window.msCrypto;
+  if(_crypto && _crypto.getRandomValues) {
+    getRandomValues = function(arr) {
+      return _crypto.getRandomValues(arr);
+    };
+  }
+}
+if(forge.disableNativeCode || (!_nodejs && !getRandomValues)) {
+  // if this is a web worker, do not use weak entropy, instead register to
+  // receive strong entropy asynchronously from the main thread
+  if(typeof window === 'undefined' || window.document === undefined) {
+    // FIXME:
+  }
+
+  // get load time entropy
+  _ctx.collectInt(+new Date(), 32);
+
+  // add some entropy from navigator object
+  if(typeof(navigator) !== 'undefined') {
+    var _navBytes = '';
+    for(var key in navigator) {
+      try {
+        if(typeof(navigator[key]) == 'string') {
+          _navBytes += navigator[key];
+        }
+      } catch(e) {
+        /* Some navigator keys might not be accessible, e.g. the geolocation
+          attribute throws an exception if touched in Mozilla chrome://
+          context.
+
+          Silently ignore this and just don't use this as a source of
+          entropy. */
+      }
+    }
+    _ctx.collect(_navBytes);
+    _navBytes = null;
+  }
+
+  // add mouse and keyboard collectors if jquery is available
+  if(jQuery) {
+    // set up mouse entropy capture
+    jQuery().mousemove(function(e) {
+      // add mouse coords
+      _ctx.collectInt(e.clientX, 16);
+      _ctx.collectInt(e.clientY, 16);
+    });
+
+    // set up keyboard entropy capture
+    jQuery().keypress(function(e) {
+      _ctx.collectInt(e.charCode, 8);
+    });
+  }
+}
+
+/* Random API */
+if(!forge.random) {
+  forge.random = _ctx;
+} else {
+  // extend forge.random with _ctx
+  for(var key in _ctx) {
+    forge.random[key] = _ctx[key];
+  }
+}
+
+// expose spawn PRNG
+forge.random.createInstance = spawnPrng;
+
+})(typeof(jQuery) !== 'undefined' ? jQuery : null);
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'random';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './aes', './md', './prng', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/rc2.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/rc2.js
new file mode 100644
index 0000000..0a67011
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/rc2.js
@@ -0,0 +1,470 @@
+/**
+ * RC2 implementation.
+ *
+ * @author Stefan Siegl
+ *
+ * Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
+ *
+ * Information on the RC2 cipher is available from RFC #2268,
+ * http://www.ietf.org/rfc/rfc2268.txt
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+var piTable = [
+  0xd9, 0x78, 0xf9, 0xc4, 0x19, 0xdd, 0xb5, 0xed, 0x28, 0xe9, 0xfd, 0x79, 0x4a, 0xa0, 0xd8, 0x9d,
+  0xc6, 0x7e, 0x37, 0x83, 0x2b, 0x76, 0x53, 0x8e, 0x62, 0x4c, 0x64, 0x88, 0x44, 0x8b, 0xfb, 0xa2,
+  0x17, 0x9a, 0x59, 0xf5, 0x87, 0xb3, 0x4f, 0x13, 0x61, 0x45, 0x6d, 0x8d, 0x09, 0x81, 0x7d, 0x32,
+  0xbd, 0x8f, 0x40, 0xeb, 0x86, 0xb7, 0x7b, 0x0b, 0xf0, 0x95, 0x21, 0x22, 0x5c, 0x6b, 0x4e, 0x82,
+  0x54, 0xd6, 0x65, 0x93, 0xce, 0x60, 0xb2, 0x1c, 0x73, 0x56, 0xc0, 0x14, 0xa7, 0x8c, 0xf1, 0xdc,
+  0x12, 0x75, 0xca, 0x1f, 0x3b, 0xbe, 0xe4, 0xd1, 0x42, 0x3d, 0xd4, 0x30, 0xa3, 0x3c, 0xb6, 0x26,
+  0x6f, 0xbf, 0x0e, 0xda, 0x46, 0x69, 0x07, 0x57, 0x27, 0xf2, 0x1d, 0x9b, 0xbc, 0x94, 0x43, 0x03,
+  0xf8, 0x11, 0xc7, 0xf6, 0x90, 0xef, 0x3e, 0xe7, 0x06, 0xc3, 0xd5, 0x2f, 0xc8, 0x66, 0x1e, 0xd7,
+  0x08, 0xe8, 0xea, 0xde, 0x80, 0x52, 0xee, 0xf7, 0x84, 0xaa, 0x72, 0xac, 0x35, 0x4d, 0x6a, 0x2a,
+  0x96, 0x1a, 0xd2, 0x71, 0x5a, 0x15, 0x49, 0x74, 0x4b, 0x9f, 0xd0, 0x5e, 0x04, 0x18, 0xa4, 0xec,
+  0xc2, 0xe0, 0x41, 0x6e, 0x0f, 0x51, 0xcb, 0xcc, 0x24, 0x91, 0xaf, 0x50, 0xa1, 0xf4, 0x70, 0x39,
+  0x99, 0x7c, 0x3a, 0x85, 0x23, 0xb8, 0xb4, 0x7a, 0xfc, 0x02, 0x36, 0x5b, 0x25, 0x55, 0x97, 0x31,
+  0x2d, 0x5d, 0xfa, 0x98, 0xe3, 0x8a, 0x92, 0xae, 0x05, 0xdf, 0x29, 0x10, 0x67, 0x6c, 0xba, 0xc9,
+  0xd3, 0x00, 0xe6, 0xcf, 0xe1, 0x9e, 0xa8, 0x2c, 0x63, 0x16, 0x01, 0x3f, 0x58, 0xe2, 0x89, 0xa9,
+  0x0d, 0x38, 0x34, 0x1b, 0xab, 0x33, 0xff, 0xb0, 0xbb, 0x48, 0x0c, 0x5f, 0xb9, 0xb1, 0xcd, 0x2e,
+  0xc5, 0xf3, 0xdb, 0x47, 0xe5, 0xa5, 0x9c, 0x77, 0x0a, 0xa6, 0x20, 0x68, 0xfe, 0x7f, 0xc1, 0xad
+];
+
+var s = [1, 2, 3, 5];
+
+
+/**
+ * Rotate a word left by given number of bits.
+ *
+ * Bits that are shifted out on the left are put back in on the right
+ * hand side.
+ *
+ * @param word The word to shift left.
+ * @param bits The number of bits to shift by.
+ * @return The rotated word.
+ */
+var rol = function(word, bits) {
+  return ((word << bits) & 0xffff) | ((word & 0xffff) >> (16 - bits));
+};
+
+/**
+ * Rotate a word right by given number of bits.
+ *
+ * Bits that are shifted out on the right are put back in on the left
+ * hand side.
+ *
+ * @param word The word to shift right.
+ * @param bits The number of bits to shift by.
+ * @return The rotated word.
+ */
+var ror = function(word, bits) {
+  return ((word & 0xffff) >> bits) | ((word << (16 - bits)) & 0xffff);
+};
+
+
+/* RC2 API */
+forge.rc2 = forge.rc2 || {};
+
+/**
+ * Perform RC2 key expansion as per RFC #2268, section 2.
+ *
+ * @param key variable-length user key (between 1 and 128 bytes)
+ * @param effKeyBits number of effective key bits (default: 128)
+ * @return the expanded RC2 key (ByteBuffer of 128 bytes)
+ */
+forge.rc2.expandKey = function(key, effKeyBits) {
+  if(typeof key === 'string') {
+    key = forge.util.createBuffer(key);
+  }
+  effKeyBits = effKeyBits || 128;
+
+  /* introduce variables that match the names used in RFC #2268 */
+  var L = key;
+  var T = key.length();
+  var T1 = effKeyBits;
+  var T8 = Math.ceil(T1 / 8);
+  var TM = 0xff >> (T1 & 0x07);
+  var i;
+
+  for(i = T; i < 128; i ++) {
+    L.putByte(piTable[(L.at(i - 1) + L.at(i - T)) & 0xff]);
+  }
+
+  L.setAt(128 - T8, piTable[L.at(128 - T8) & TM]);
+
+  for(i = 127 - T8; i >= 0; i --) {
+    L.setAt(i, piTable[L.at(i + 1) ^ L.at(i + T8)]);
+  }
+
+  return L;
+};
+
+
+/**
+ * Creates a RC2 cipher object.
+ *
+ * @param key the symmetric key to use (as base for key generation).
+ * @param bits the number of effective key bits.
+ * @param encrypt false for decryption, true for encryption.
+ *
+ * @return the cipher.
+ */
+var createCipher = function(key, bits, encrypt) {
+  var _finish = false, _input = null, _output = null, _iv = null;
+  var mixRound, mashRound;
+  var i, j, K = [];
+
+  /* Expand key and fill into K[] Array */
+  key = forge.rc2.expandKey(key, bits);
+  for(i = 0; i < 64; i ++) {
+    K.push(key.getInt16Le());
+  }
+
+  if(encrypt) {
+    /**
+     * Perform one mixing round "in place".
+     *
+     * @param R Array of four words to perform mixing on.
+     */
+    mixRound = function(R) {
+      for(i = 0; i < 4; i++) {
+        R[i] += K[j] + (R[(i + 3) % 4] & R[(i + 2) % 4]) +
+          ((~R[(i + 3) % 4]) & R[(i + 1) % 4]);
+        R[i] = rol(R[i], s[i]);
+        j ++;
+      }
+    };
+
+    /**
+     * Perform one mashing round "in place".
+     *
+     * @param R Array of four words to perform mashing on.
+     */
+    mashRound = function(R) {
+      for(i = 0; i < 4; i ++) {
+        R[i] += K[R[(i + 3) % 4] & 63];
+      }
+    };
+  } else {
+    /**
+     * Perform one r-mixing round "in place".
+     *
+     * @param R Array of four words to perform mixing on.
+     */
+    mixRound = function(R) {
+      for(i = 3; i >= 0; i--) {
+        R[i] = ror(R[i], s[i]);
+        R[i] -= K[j] + (R[(i + 3) % 4] & R[(i + 2) % 4]) +
+          ((~R[(i + 3) % 4]) & R[(i + 1) % 4]);
+        j --;
+      }
+    };
+
+    /**
+     * Perform one r-mashing round "in place".
+     *
+     * @param R Array of four words to perform mashing on.
+     */
+    mashRound = function(R) {
+      for(i = 3; i >= 0; i--) {
+        R[i] -= K[R[(i + 3) % 4] & 63];
+      }
+    };
+  }
+
+  /**
+   * Run the specified cipher execution plan.
+   *
+   * This function takes four words from the input buffer, applies the IV on
+   * it (if requested) and runs the provided execution plan.
+   *
+   * The plan must be put together in form of a array of arrays.  Where the
+   * outer one is simply a list of steps to perform and the inner one needs
+   * to have two elements: the first one telling how many rounds to perform,
+   * the second one telling what to do (i.e. the function to call).
+   *
+   * @param {Array} plan The plan to execute.
+   */
+  var runPlan = function(plan) {
+    var R = [];
+
+    /* Get data from input buffer and fill the four words into R */
+    for(i = 0; i < 4; i ++) {
+      var val = _input.getInt16Le();
+
+      if(_iv !== null) {
+        if(encrypt) {
+          /* We're encrypting, apply the IV first. */
+          val ^= _iv.getInt16Le();
+        } else {
+          /* We're decryption, keep cipher text for next block. */
+          _iv.putInt16Le(val);
+        }
+      }
+
+      R.push(val & 0xffff);
+    }
+
+    /* Reset global "j" variable as per spec. */
+    j = encrypt ? 0 : 63;
+
+    /* Run execution plan. */
+    for(var ptr = 0; ptr < plan.length; ptr ++) {
+      for(var ctr = 0; ctr < plan[ptr][0]; ctr ++) {
+        plan[ptr][1](R);
+      }
+    }
+
+    /* Write back result to output buffer. */
+    for(i = 0; i < 4; i ++) {
+      if(_iv !== null) {
+        if(encrypt) {
+          /* We're encrypting in CBC-mode, feed back encrypted bytes into
+             IV buffer to carry it forward to next block. */
+          _iv.putInt16Le(R[i]);
+        } else {
+          R[i] ^= _iv.getInt16Le();
+        }
+      }
+
+      _output.putInt16Le(R[i]);
+    }
+  };
+
+
+  /* Create cipher object */
+  var cipher = null;
+  cipher = {
+    /**
+     * Starts or restarts the encryption or decryption process, whichever
+     * was previously configured.
+     *
+     * To use the cipher in CBC mode, iv may be given either as a string
+     * of bytes, or as a byte buffer.  For ECB mode, give null as iv.
+     *
+     * @param iv the initialization vector to use, null for ECB mode.
+     * @param output the output the buffer to write to, null to create one.
+     */
+    start: function(iv, output) {
+      if(iv) {
+        /* CBC mode */
+        if(typeof iv === 'string') {
+          iv = forge.util.createBuffer(iv);
+        }
+      }
+
+      _finish = false;
+      _input = forge.util.createBuffer();
+      _output = output || new forge.util.createBuffer();
+      _iv = iv;
+
+      cipher.output = _output;
+    },
+
+    /**
+     * Updates the next block.
+     *
+     * @param input the buffer to read from.
+     */
+    update: function(input) {
+      if(!_finish) {
+        // not finishing, so fill the input buffer with more input
+        _input.putBuffer(input);
+      }
+
+      while(_input.length() >= 8) {
+        runPlan([
+            [ 5, mixRound ],
+            [ 1, mashRound ],
+            [ 6, mixRound ],
+            [ 1, mashRound ],
+            [ 5, mixRound ]
+          ]);
+      }
+    },
+
+    /**
+     * Finishes encrypting or decrypting.
+     *
+     * @param pad a padding function to use, null for PKCS#7 padding,
+     *           signature(blockSize, buffer, decrypt).
+     *
+     * @return true if successful, false on error.
+     */
+    finish: function(pad) {
+      var rval = true;
+
+      if(encrypt) {
+        if(pad) {
+          rval = pad(8, _input, !encrypt);
+        } else {
+          // add PKCS#7 padding to block (each pad byte is the
+          // value of the number of pad bytes)
+          var padding = (_input.length() === 8) ? 8 : (8 - _input.length());
+          _input.fillWithByte(padding, padding);
+        }
+      }
+
+      if(rval) {
+        // do final update
+        _finish = true;
+        cipher.update();
+      }
+
+      if(!encrypt) {
+        // check for error: input data not a multiple of block size
+        rval = (_input.length() === 0);
+        if(rval) {
+          if(pad) {
+            rval = pad(8, _output, !encrypt);
+          } else {
+            // ensure padding byte count is valid
+            var len = _output.length();
+            var count = _output.at(len - 1);
+
+            if(count > len) {
+              rval = false;
+            } else {
+              // trim off padding bytes
+              _output.truncate(count);
+            }
+          }
+        }
+      }
+
+      return rval;
+    }
+  };
+
+  return cipher;
+};
+
+
+/**
+ * Creates an RC2 cipher object to encrypt data in ECB or CBC mode using the
+ * given symmetric key. The output will be stored in the 'output' member
+ * of the returned cipher.
+ *
+ * The key and iv may be given as a string of bytes or a byte buffer.
+ * The cipher is initialized to use 128 effective key bits.
+ *
+ * @param key the symmetric key to use.
+ * @param iv the initialization vector to use.
+ * @param output the buffer to write to, null to create one.
+ *
+ * @return the cipher.
+ */
+forge.rc2.startEncrypting = function(key, iv, output) {
+  var cipher = forge.rc2.createEncryptionCipher(key, 128);
+  cipher.start(iv, output);
+  return cipher;
+};
+
+/**
+ * Creates an RC2 cipher object to encrypt data in ECB or CBC mode using the
+ * given symmetric key.
+ *
+ * The key may be given as a string of bytes or a byte buffer.
+ *
+ * To start encrypting call start() on the cipher with an iv and optional
+ * output buffer.
+ *
+ * @param key the symmetric key to use.
+ *
+ * @return the cipher.
+ */
+forge.rc2.createEncryptionCipher = function(key, bits) {
+  return createCipher(key, bits, true);
+};
+
+/**
+ * Creates an RC2 cipher object to decrypt data in ECB or CBC mode using the
+ * given symmetric key. The output will be stored in the 'output' member
+ * of the returned cipher.
+ *
+ * The key and iv may be given as a string of bytes or a byte buffer.
+ * The cipher is initialized to use 128 effective key bits.
+ *
+ * @param key the symmetric key to use.
+ * @param iv the initialization vector to use.
+ * @param output the buffer to write to, null to create one.
+ *
+ * @return the cipher.
+ */
+forge.rc2.startDecrypting = function(key, iv, output) {
+  var cipher = forge.rc2.createDecryptionCipher(key, 128);
+  cipher.start(iv, output);
+  return cipher;
+};
+
+/**
+ * Creates an RC2 cipher object to decrypt data in ECB or CBC mode using the
+ * given symmetric key.
+ *
+ * The key may be given as a string of bytes or a byte buffer.
+ *
+ * To start decrypting call start() on the cipher with an iv and optional
+ * output buffer.
+ *
+ * @param key the symmetric key to use.
+ *
+ * @return the cipher.
+ */
+forge.rc2.createDecryptionCipher = function(key, bits) {
+  return createCipher(key, bits, false);
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'rc2';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/rsa.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/rsa.js
new file mode 100644
index 0000000..90f8c0a
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/rsa.js
@@ -0,0 +1,1712 @@
+/**
+ * Javascript implementation of basic RSA algorithms.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2014 Digital Bazaar, Inc.
+ *
+ * The only algorithm currently supported for PKI is RSA.
+ *
+ * An RSA key is often stored in ASN.1 DER format. The SubjectPublicKeyInfo
+ * ASN.1 structure is composed of an algorithm of type AlgorithmIdentifier
+ * and a subjectPublicKey of type bit string.
+ *
+ * The AlgorithmIdentifier contains an Object Identifier (OID) and parameters
+ * for the algorithm, if any. In the case of RSA, there aren't any.
+ *
+ * SubjectPublicKeyInfo ::= SEQUENCE {
+ *   algorithm AlgorithmIdentifier,
+ *   subjectPublicKey BIT STRING
+ * }
+ *
+ * AlgorithmIdentifer ::= SEQUENCE {
+ *   algorithm OBJECT IDENTIFIER,
+ *   parameters ANY DEFINED BY algorithm OPTIONAL
+ * }
+ *
+ * For an RSA public key, the subjectPublicKey is:
+ *
+ * RSAPublicKey ::= SEQUENCE {
+ *   modulus            INTEGER,    -- n
+ *   publicExponent     INTEGER     -- e
+ * }
+ *
+ * PrivateKeyInfo ::= SEQUENCE {
+ *   version                   Version,
+ *   privateKeyAlgorithm       PrivateKeyAlgorithmIdentifier,
+ *   privateKey                PrivateKey,
+ *   attributes           [0]  IMPLICIT Attributes OPTIONAL
+ * }
+ *
+ * Version ::= INTEGER
+ * PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
+ * PrivateKey ::= OCTET STRING
+ * Attributes ::= SET OF Attribute
+ *
+ * An RSA private key as the following structure:
+ *
+ * RSAPrivateKey ::= SEQUENCE {
+ *   version Version,
+ *   modulus INTEGER, -- n
+ *   publicExponent INTEGER, -- e
+ *   privateExponent INTEGER, -- d
+ *   prime1 INTEGER, -- p
+ *   prime2 INTEGER, -- q
+ *   exponent1 INTEGER, -- d mod (p-1)
+ *   exponent2 INTEGER, -- d mod (q-1)
+ *   coefficient INTEGER -- (inverse of q) mod p
+ * }
+ *
+ * Version ::= INTEGER
+ *
+ * The OID for the RSA key algorithm is: 1.2.840.113549.1.1.1
+ */
+(function() {
+function initModule(forge) {
+/* ########## Begin module implementation ########## */
+
+if(typeof BigInteger === 'undefined') {
+  var BigInteger = forge.jsbn.BigInteger;
+}
+
+// shortcut for asn.1 API
+var asn1 = forge.asn1;
+
+/*
+ * RSA encryption and decryption, see RFC 2313.
+ */
+forge.pki = forge.pki || {};
+forge.pki.rsa = forge.rsa = forge.rsa || {};
+var pki = forge.pki;
+
+// for finding primes, which are 30k+i for i = 1, 7, 11, 13, 17, 19, 23, 29
+var GCD_30_DELTA = [6, 4, 2, 4, 2, 4, 6, 2];
+
+// validator for a PrivateKeyInfo structure
+var privateKeyValidator = {
+  // PrivateKeyInfo
+  name: 'PrivateKeyInfo',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    // Version (INTEGER)
+    name: 'PrivateKeyInfo.version',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'privateKeyVersion'
+  }, {
+    // privateKeyAlgorithm
+    name: 'PrivateKeyInfo.privateKeyAlgorithm',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true,
+    value: [{
+      name: 'AlgorithmIdentifier.algorithm',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.OID,
+      constructed: false,
+      capture: 'privateKeyOid'
+    }]
+  }, {
+    // PrivateKey
+    name: 'PrivateKeyInfo',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.OCTETSTRING,
+    constructed: false,
+    capture: 'privateKey'
+  }]
+};
+
+// validator for an RSA private key
+var rsaPrivateKeyValidator = {
+  // RSAPrivateKey
+  name: 'RSAPrivateKey',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    // Version (INTEGER)
+    name: 'RSAPrivateKey.version',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'privateKeyVersion'
+  }, {
+    // modulus (n)
+    name: 'RSAPrivateKey.modulus',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'privateKeyModulus'
+  }, {
+    // publicExponent (e)
+    name: 'RSAPrivateKey.publicExponent',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'privateKeyPublicExponent'
+  }, {
+    // privateExponent (d)
+    name: 'RSAPrivateKey.privateExponent',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'privateKeyPrivateExponent'
+  }, {
+    // prime1 (p)
+    name: 'RSAPrivateKey.prime1',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'privateKeyPrime1'
+  }, {
+    // prime2 (q)
+    name: 'RSAPrivateKey.prime2',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'privateKeyPrime2'
+  }, {
+    // exponent1 (d mod (p-1))
+    name: 'RSAPrivateKey.exponent1',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'privateKeyExponent1'
+  }, {
+    // exponent2 (d mod (q-1))
+    name: 'RSAPrivateKey.exponent2',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'privateKeyExponent2'
+  }, {
+    // coefficient ((inverse of q) mod p)
+    name: 'RSAPrivateKey.coefficient',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'privateKeyCoefficient'
+  }]
+};
+
+// validator for an RSA public key
+var rsaPublicKeyValidator = {
+  // RSAPublicKey
+  name: 'RSAPublicKey',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    // modulus (n)
+    name: 'RSAPublicKey.modulus',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'publicKeyModulus'
+  }, {
+    // publicExponent (e)
+    name: 'RSAPublicKey.exponent',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'publicKeyExponent'
+  }]
+};
+
+// validator for an SubjectPublicKeyInfo structure
+// Note: Currently only works with an RSA public key
+var publicKeyValidator = forge.pki.rsa.publicKeyValidator = {
+  name: 'SubjectPublicKeyInfo',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  captureAsn1: 'subjectPublicKeyInfo',
+  value: [{
+    name: 'SubjectPublicKeyInfo.AlgorithmIdentifier',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true,
+    value: [{
+      name: 'AlgorithmIdentifier.algorithm',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.OID,
+      constructed: false,
+      capture: 'publicKeyOid'
+    }]
+  }, {
+    // subjectPublicKey
+    name: 'SubjectPublicKeyInfo.subjectPublicKey',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.BITSTRING,
+    constructed: false,
+    value: [{
+      // RSAPublicKey
+      name: 'SubjectPublicKeyInfo.subjectPublicKey.RSAPublicKey',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.SEQUENCE,
+      constructed: true,
+      optional: true,
+      captureAsn1: 'rsaPublicKey'
+    }]
+  }]
+};
+
+/**
+ * Wrap digest in DigestInfo object.
+ *
+ * This function implements EMSA-PKCS1-v1_5-ENCODE as per RFC 3447.
+ *
+ * DigestInfo ::= SEQUENCE {
+ *   digestAlgorithm DigestAlgorithmIdentifier,
+ *   digest Digest
+ * }
+ *
+ * DigestAlgorithmIdentifier ::= AlgorithmIdentifier
+ * Digest ::= OCTET STRING
+ *
+ * @param md the message digest object with the hash to sign.
+ *
+ * @return the encoded message (ready for RSA encrytion)
+ */
+var emsaPkcs1v15encode = function(md) {
+  // get the oid for the algorithm
+  var oid;
+  if(md.algorithm in pki.oids) {
+    oid = pki.oids[md.algorithm];
+  } else {
+    var error = new Error('Unknown message digest algorithm.');
+    error.algorithm = md.algorithm;
+    throw error;
+  }
+  var oidBytes = asn1.oidToDer(oid).getBytes();
+
+  // create the digest info
+  var digestInfo = asn1.create(
+    asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []);
+  var digestAlgorithm = asn1.create(
+    asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []);
+  digestAlgorithm.value.push(asn1.create(
+    asn1.Class.UNIVERSAL, asn1.Type.OID, false, oidBytes));
+  digestAlgorithm.value.push(asn1.create(
+    asn1.Class.UNIVERSAL, asn1.Type.NULL, false, ''));
+  var digest = asn1.create(
+    asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING,
+    false, md.digest().getBytes());
+  digestInfo.value.push(digestAlgorithm);
+  digestInfo.value.push(digest);
+
+  // encode digest info
+  return asn1.toDer(digestInfo).getBytes();
+};
+
+/**
+ * Performs x^c mod n (RSA encryption or decryption operation).
+ *
+ * @param x the number to raise and mod.
+ * @param key the key to use.
+ * @param pub true if the key is public, false if private.
+ *
+ * @return the result of x^c mod n.
+ */
+var _modPow = function(x, key, pub) {
+  if(pub) {
+    return x.modPow(key.e, key.n);
+  }
+
+  if(!key.p || !key.q) {
+    // allow calculation without CRT params (slow)
+    return x.modPow(key.d, key.n);
+  }
+
+  // pre-compute dP, dQ, and qInv if necessary
+  if(!key.dP) {
+    key.dP = key.d.mod(key.p.subtract(BigInteger.ONE));
+  }
+  if(!key.dQ) {
+    key.dQ = key.d.mod(key.q.subtract(BigInteger.ONE));
+  }
+  if(!key.qInv) {
+    key.qInv = key.q.modInverse(key.p);
+  }
+
+  /* Chinese remainder theorem (CRT) states:
+
+    Suppose n1, n2, ..., nk are positive integers which are pairwise
+    coprime (n1 and n2 have no common factors other than 1). For any
+    integers x1, x2, ..., xk there exists an integer x solving the
+    system of simultaneous congruences (where ~= means modularly
+    congruent so a ~= b mod n means a mod n = b mod n):
+
+    x ~= x1 mod n1
+    x ~= x2 mod n2
+    ...
+    x ~= xk mod nk
+
+    This system of congruences has a single simultaneous solution x
+    between 0 and n - 1. Furthermore, each xk solution and x itself
+    is congruent modulo the product n = n1*n2*...*nk.
+    So x1 mod n = x2 mod n = xk mod n = x mod n.
+
+    The single simultaneous solution x can be solved with the following
+    equation:
+
+    x = sum(xi*ri*si) mod n where ri = n/ni and si = ri^-1 mod ni.
+
+    Where x is less than n, xi = x mod ni.
+
+    For RSA we are only concerned with k = 2. The modulus n = pq, where
+    p and q are coprime. The RSA decryption algorithm is:
+
+    y = x^d mod n
+
+    Given the above:
+
+    x1 = x^d mod p
+    r1 = n/p = q
+    s1 = q^-1 mod p
+    x2 = x^d mod q
+    r2 = n/q = p
+    s2 = p^-1 mod q
+
+    So y = (x1r1s1 + x2r2s2) mod n
+         = ((x^d mod p)q(q^-1 mod p) + (x^d mod q)p(p^-1 mod q)) mod n
+
+    According to Fermat's Little Theorem, if the modulus P is prime,
+    for any integer A not evenly divisible by P, A^(P-1) ~= 1 mod P.
+    Since A is not divisible by P it follows that if:
+    N ~= M mod (P - 1), then A^N mod P = A^M mod P. Therefore:
+
+    A^N mod P = A^(M mod (P - 1)) mod P. (The latter takes less effort
+    to calculate). In order to calculate x^d mod p more quickly the
+    exponent d mod (p - 1) is stored in the RSA private key (the same
+    is done for x^d mod q). These values are referred to as dP and dQ
+    respectively. Therefore we now have:
+
+    y = ((x^dP mod p)q(q^-1 mod p) + (x^dQ mod q)p(p^-1 mod q)) mod n
+
+    Since we'll be reducing x^dP by modulo p (same for q) we can also
+    reduce x by p (and q respectively) before hand. Therefore, let
+
+    xp = ((x mod p)^dP mod p), and
+    xq = ((x mod q)^dQ mod q), yielding:
+
+    y = (xp*q*(q^-1 mod p) + xq*p*(p^-1 mod q)) mod n
+
+    This can be further reduced to a simple algorithm that only
+    requires 1 inverse (the q inverse is used) to be used and stored.
+    The algorithm is called Garner's algorithm. If qInv is the
+    inverse of q, we simply calculate:
+
+    y = (qInv*(xp - xq) mod p) * q + xq
+
+    However, there are two further complications. First, we need to
+    ensure that xp > xq to prevent signed BigIntegers from being used
+    so we add p until this is true (since we will be mod'ing with
+    p anyway). Then, there is a known timing attack on algorithms
+    using the CRT. To mitigate this risk, "cryptographic blinding"
+    should be used. This requires simply generating a random number r between
+    0 and n-1 and its inverse and multiplying x by r^e before calculating y
+    and then multiplying y by r^-1 afterwards.
+  */
+
+  // cryptographic blinding
+  var r;
+  do {
+    r = new BigInteger(
+      forge.util.bytesToHex(forge.random.getBytes(key.n.bitLength() / 8)),
+      16).mod(key.n);
+  } while(r.equals(BigInteger.ZERO));
+  x = x.multiply(r.modPow(key.e, key.n)).mod(key.n);
+
+  // calculate xp and xq
+  var xp = x.mod(key.p).modPow(key.dP, key.p);
+  var xq = x.mod(key.q).modPow(key.dQ, key.q);
+
+  // xp must be larger than xq to avoid signed bit usage
+  while(xp.compareTo(xq) < 0) {
+    xp = xp.add(key.p);
+  }
+
+  // do last step
+  var y = xp.subtract(xq)
+    .multiply(key.qInv).mod(key.p)
+    .multiply(key.q).add(xq);
+
+  // remove effect of random for cryptographic blinding
+  y = y.multiply(r.modInverse(key.n)).mod(key.n);
+
+  return y;
+};
+
+/**
+ * NOTE: THIS METHOD IS DEPRECATED, use 'sign' on a private key object or
+ * 'encrypt' on a public key object instead.
+ *
+ * Performs RSA encryption.
+ *
+ * The parameter bt controls whether to put padding bytes before the
+ * message passed in. Set bt to either true or false to disable padding
+ * completely (in order to handle e.g. EMSA-PSS encoding seperately before),
+ * signaling whether the encryption operation is a public key operation
+ * (i.e. encrypting data) or not, i.e. private key operation (data signing).
+ *
+ * For PKCS#1 v1.5 padding pass in the block type to use, i.e. either 0x01
+ * (for signing) or 0x02 (for encryption). The key operation mode (private
+ * or public) is derived from this flag in that case).
+ *
+ * @param m the message to encrypt as a byte string.
+ * @param key the RSA key to use.
+ * @param bt for PKCS#1 v1.5 padding, the block type to use
+ *   (0x01 for private key, 0x02 for public),
+ *   to disable padding: true = public key, false = private key.
+ *
+ * @return the encrypted bytes as a string.
+ */
+pki.rsa.encrypt = function(m, key, bt) {
+  var pub = bt;
+  var eb;
+
+  // get the length of the modulus in bytes
+  var k = Math.ceil(key.n.bitLength() / 8);
+
+  if(bt !== false && bt !== true) {
+    // legacy, default to PKCS#1 v1.5 padding
+    pub = (bt === 0x02);
+    eb = _encodePkcs1_v1_5(m, key, bt);
+  } else {
+    eb = forge.util.createBuffer();
+    eb.putBytes(m);
+  }
+
+  // load encryption block as big integer 'x'
+  // FIXME: hex conversion inefficient, get BigInteger w/byte strings
+  var x = new BigInteger(eb.toHex(), 16);
+
+  // do RSA encryption
+  var y = _modPow(x, key, pub);
+
+  // convert y into the encrypted data byte string, if y is shorter in
+  // bytes than k, then prepend zero bytes to fill up ed
+  // FIXME: hex conversion inefficient, get BigInteger w/byte strings
+  var yhex = y.toString(16);
+  var ed = forge.util.createBuffer();
+  var zeros = k - Math.ceil(yhex.length / 2);
+  while(zeros > 0) {
+    ed.putByte(0x00);
+    --zeros;
+  }
+  ed.putBytes(forge.util.hexToBytes(yhex));
+  return ed.getBytes();
+};
+
+/**
+ * NOTE: THIS METHOD IS DEPRECATED, use 'decrypt' on a private key object or
+ * 'verify' on a public key object instead.
+ *
+ * Performs RSA decryption.
+ *
+ * The parameter ml controls whether to apply PKCS#1 v1.5 padding
+ * or not.  Set ml = false to disable padding removal completely
+ * (in order to handle e.g. EMSA-PSS later on) and simply pass back
+ * the RSA encryption block.
+ *
+ * @param ed the encrypted data to decrypt in as a byte string.
+ * @param key the RSA key to use.
+ * @param pub true for a public key operation, false for private.
+ * @param ml the message length, if known, false to disable padding.
+ *
+ * @return the decrypted message as a byte string.
+ */
+pki.rsa.decrypt = function(ed, key, pub, ml) {
+  // get the length of the modulus in bytes
+  var k = Math.ceil(key.n.bitLength() / 8);
+
+  // error if the length of the encrypted data ED is not k
+  if(ed.length !== k) {
+    var error = new Error('Encrypted message length is invalid.');
+    error.length = ed.length;
+    error.expected = k;
+    throw error;
+  }
+
+  // convert encrypted data into a big integer
+  // FIXME: hex conversion inefficient, get BigInteger w/byte strings
+  var y = new BigInteger(forge.util.createBuffer(ed).toHex(), 16);
+
+  // y must be less than the modulus or it wasn't the result of
+  // a previous mod operation (encryption) using that modulus
+  if(y.compareTo(key.n) >= 0) {
+    throw new Error('Encrypted message is invalid.');
+  }
+
+  // do RSA decryption
+  var x = _modPow(y, key, pub);
+
+  // create the encryption block, if x is shorter in bytes than k, then
+  // prepend zero bytes to fill up eb
+  // FIXME: hex conversion inefficient, get BigInteger w/byte strings
+  var xhex = x.toString(16);
+  var eb = forge.util.createBuffer();
+  var zeros = k - Math.ceil(xhex.length / 2);
+  while(zeros > 0) {
+    eb.putByte(0x00);
+    --zeros;
+  }
+  eb.putBytes(forge.util.hexToBytes(xhex));
+
+  if(ml !== false) {
+    // legacy, default to PKCS#1 v1.5 padding
+    return _decodePkcs1_v1_5(eb.getBytes(), key, pub);
+  }
+
+  // return message
+  return eb.getBytes();
+};
+
+/**
+ * Creates an RSA key-pair generation state object. It is used to allow
+ * key-generation to be performed in steps. It also allows for a UI to
+ * display progress updates.
+ *
+ * @param bits the size for the private key in bits, defaults to 2048.
+ * @param e the public exponent to use, defaults to 65537 (0x10001).
+ * @param [options] the options to use.
+ *          prng a custom crypto-secure pseudo-random number generator to use,
+ *            that must define "getBytesSync".
+ *          algorithm the algorithm to use (default: 'PRIMEINC').
+ *
+ * @return the state object to use to generate the key-pair.
+ */
+pki.rsa.createKeyPairGenerationState = function(bits, e, options) {
+  // TODO: migrate step-based prime generation code to forge.prime
+
+  // set default bits
+  if(typeof(bits) === 'string') {
+    bits = parseInt(bits, 10);
+  }
+  bits = bits || 2048;
+
+  // create prng with api that matches BigInteger secure random
+  options = options || {};
+  var prng = options.prng || forge.random;
+  var rng = {
+    // x is an array to fill with bytes
+    nextBytes: function(x) {
+      var b = prng.getBytesSync(x.length);
+      for(var i = 0; i < x.length; ++i) {
+        x[i] = b.charCodeAt(i);
+      }
+    }
+  };
+
+  var algorithm = options.algorithm || 'PRIMEINC';
+
+  // create PRIMEINC algorithm state
+  var rval;
+  if(algorithm === 'PRIMEINC') {
+    rval = {
+      algorithm: algorithm,
+      state: 0,
+      bits: bits,
+      rng: rng,
+      eInt: e || 65537,
+      e: new BigInteger(null),
+      p: null,
+      q: null,
+      qBits: bits >> 1,
+      pBits: bits - (bits >> 1),
+      pqState: 0,
+      num: null,
+      keys: null
+    };
+    rval.e.fromInt(rval.eInt);
+  } else {
+    throw new Error('Invalid key generation algorithm: ' + algorithm);
+  }
+
+  return rval;
+};
+
+/**
+ * Attempts to runs the key-generation algorithm for at most n seconds
+ * (approximately) using the given state. When key-generation has completed,
+ * the keys will be stored in state.keys.
+ *
+ * To use this function to update a UI while generating a key or to prevent
+ * causing browser lockups/warnings, set "n" to a value other than 0. A
+ * simple pattern for generating a key and showing a progress indicator is:
+ *
+ * var state = pki.rsa.createKeyPairGenerationState(2048);
+ * var step = function() {
+ *   // step key-generation, run algorithm for 100 ms, repeat
+ *   if(!forge.pki.rsa.stepKeyPairGenerationState(state, 100)) {
+ *     setTimeout(step, 1);
+ *   } else {
+ *     // key-generation complete
+ *     // TODO: turn off progress indicator here
+ *     // TODO: use the generated key-pair in "state.keys"
+ *   }
+ * };
+ * // TODO: turn on progress indicator here
+ * setTimeout(step, 0);
+ *
+ * @param state the state to use.
+ * @param n the maximum number of milliseconds to run the algorithm for, 0
+ *          to run the algorithm to completion.
+ *
+ * @return true if the key-generation completed, false if not.
+ */
+pki.rsa.stepKeyPairGenerationState = function(state, n) {
+  // set default algorithm if not set
+  if(!('algorithm' in state)) {
+    state.algorithm = 'PRIMEINC';
+  }
+
+  // TODO: migrate step-based prime generation code to forge.prime
+  // TODO: abstract as PRIMEINC algorithm
+
+  // do key generation (based on Tom Wu's rsa.js, see jsbn.js license)
+  // with some minor optimizations and designed to run in steps
+
+  // local state vars
+  var THIRTY = new BigInteger(null);
+  THIRTY.fromInt(30);
+  var deltaIdx = 0;
+  var op_or = function(x,y) { return x|y; };
+
+  // keep stepping until time limit is reached or done
+  var t1 = +new Date();
+  var t2;
+  var total = 0;
+  while(state.keys === null && (n <= 0 || total < n)) {
+    // generate p or q
+    if(state.state === 0) {
+      /* Note: All primes are of the form:
+
+        30k+i, for i < 30 and gcd(30, i)=1, where there are 8 values for i
+
+        When we generate a random number, we always align it at 30k + 1. Each
+        time the number is determined not to be prime we add to get to the
+        next 'i', eg: if the number was at 30k + 1 we add 6. */
+      var bits = (state.p === null) ? state.pBits : state.qBits;
+      var bits1 = bits - 1;
+
+      // get a random number
+      if(state.pqState === 0) {
+        state.num = new BigInteger(bits, state.rng);
+        // force MSB set
+        if(!state.num.testBit(bits1)) {
+          state.num.bitwiseTo(
+            BigInteger.ONE.shiftLeft(bits1), op_or, state.num);
+        }
+        // align number on 30k+1 boundary
+        state.num.dAddOffset(31 - state.num.mod(THIRTY).byteValue(), 0);
+        deltaIdx = 0;
+
+        ++state.pqState;
+      } else if(state.pqState === 1) {
+        // try to make the number a prime
+        if(state.num.bitLength() > bits) {
+          // overflow, try again
+          state.pqState = 0;
+          // do primality test
+        } else if(state.num.isProbablePrime(
+          _getMillerRabinTests(state.num.bitLength()))) {
+          ++state.pqState;
+        } else {
+          // get next potential prime
+          state.num.dAddOffset(GCD_30_DELTA[deltaIdx++ % 8], 0);
+        }
+      } else if(state.pqState === 2) {
+        // ensure number is coprime with e
+        state.pqState =
+          (state.num.subtract(BigInteger.ONE).gcd(state.e)
+          .compareTo(BigInteger.ONE) === 0) ? 3 : 0;
+      } else if(state.pqState === 3) {
+        // store p or q
+        state.pqState = 0;
+        if(state.p === null) {
+          state.p = state.num;
+        } else {
+          state.q = state.num;
+        }
+
+        // advance state if both p and q are ready
+        if(state.p !== null && state.q !== null) {
+          ++state.state;
+        }
+        state.num = null;
+      }
+    } else if(state.state === 1) {
+      // ensure p is larger than q (swap them if not)
+      if(state.p.compareTo(state.q) < 0) {
+        state.num = state.p;
+        state.p = state.q;
+        state.q = state.num;
+      }
+      ++state.state;
+    } else if(state.state === 2) {
+      // compute phi: (p - 1)(q - 1) (Euler's totient function)
+      state.p1 = state.p.subtract(BigInteger.ONE);
+      state.q1 = state.q.subtract(BigInteger.ONE);
+      state.phi = state.p1.multiply(state.q1);
+      ++state.state;
+    } else if(state.state === 3) {
+      // ensure e and phi are coprime
+      if(state.phi.gcd(state.e).compareTo(BigInteger.ONE) === 0) {
+        // phi and e are coprime, advance
+        ++state.state;
+      } else {
+        // phi and e aren't coprime, so generate a new p and q
+        state.p = null;
+        state.q = null;
+        state.state = 0;
+      }
+    } else if(state.state === 4) {
+      // create n, ensure n is has the right number of bits
+      state.n = state.p.multiply(state.q);
+
+      // ensure n is right number of bits
+      if(state.n.bitLength() === state.bits) {
+        // success, advance
+        ++state.state;
+      } else {
+        // failed, get new q
+        state.q = null;
+        state.state = 0;
+      }
+    } else if(state.state === 5) {
+      // set keys
+      var d = state.e.modInverse(state.phi);
+      state.keys = {
+        privateKey: pki.rsa.setPrivateKey(
+          state.n, state.e, d, state.p, state.q,
+          d.mod(state.p1), d.mod(state.q1),
+          state.q.modInverse(state.p)),
+        publicKey: pki.rsa.setPublicKey(state.n, state.e)
+      };
+    }
+
+    // update timing
+    t2 = +new Date();
+    total += t2 - t1;
+    t1 = t2;
+  }
+
+  return state.keys !== null;
+};
+
+/**
+ * Generates an RSA public-private key pair in a single call.
+ *
+ * To generate a key-pair in steps (to allow for progress updates and to
+ * prevent blocking or warnings in slow browsers) then use the key-pair
+ * generation state functions.
+ *
+ * To generate a key-pair asynchronously (either through web-workers, if
+ * available, or by breaking up the work on the main thread), pass a
+ * callback function.
+ *
+ * @param [bits] the size for the private key in bits, defaults to 2048.
+ * @param [e] the public exponent to use, defaults to 65537.
+ * @param [options] options for key-pair generation, if given then 'bits'
+ *          and 'e' must *not* be given:
+ *          bits the size for the private key in bits, (default: 2048).
+ *          e the public exponent to use, (default: 65537 (0x10001)).
+ *          workerScript the worker script URL.
+ *          workers the number of web workers (if supported) to use,
+ *            (default: 2).
+ *          workLoad the size of the work load, ie: number of possible prime
+ *            numbers for each web worker to check per work assignment,
+ *            (default: 100).
+ *          e the public exponent to use, defaults to 65537.
+ *          prng a custom crypto-secure pseudo-random number generator to use,
+ *            that must define "getBytesSync".
+ *          algorithm the algorithm to use (default: 'PRIMEINC').
+ * @param [callback(err, keypair)] called once the operation completes.
+ *
+ * @return an object with privateKey and publicKey properties.
+ */
+pki.rsa.generateKeyPair = function(bits, e, options, callback) {
+  // (bits), (options), (callback)
+  if(arguments.length === 1) {
+    if(typeof bits === 'object') {
+      options = bits;
+      bits = undefined;
+    } else if(typeof bits === 'function') {
+      callback = bits;
+      bits = undefined;
+    }
+  } else if(arguments.length === 2) {
+    // (bits, e), (bits, options), (bits, callback), (options, callback)
+    if(typeof bits === 'number') {
+      if(typeof e === 'function') {
+        callback = e;
+        e = undefined;
+      } else if(typeof e !== 'number') {
+        options = e;
+        e = undefined;
+      }
+    } else {
+      options = bits;
+      callback = e;
+      bits = undefined;
+      e = undefined;
+    }
+  } else if(arguments.length === 3) {
+    // (bits, e, options), (bits, e, callback), (bits, options, callback)
+    if(typeof e === 'number') {
+      if(typeof options === 'function') {
+        callback = options;
+        options = undefined;
+      }
+    } else {
+      callback = options;
+      options = e;
+      e = undefined;
+    }
+  }
+  options = options || {};
+  if(bits === undefined) {
+    bits = options.bits || 2048;
+  }
+  if(e === undefined) {
+    e = options.e || 0x10001;
+  }
+  var state = pki.rsa.createKeyPairGenerationState(bits, e, options);
+  if(!callback) {
+    pki.rsa.stepKeyPairGenerationState(state, 0);
+    return state.keys;
+  }
+  _generateKeyPair(state, options, callback);
+};
+
+/**
+ * Sets an RSA public key from BigIntegers modulus and exponent.
+ *
+ * @param n the modulus.
+ * @param e the exponent.
+ *
+ * @return the public key.
+ */
+pki.setRsaPublicKey = pki.rsa.setPublicKey = function(n, e) {
+  var key = {
+    n: n,
+    e: e
+  };
+
+  /**
+   * Encrypts the given data with this public key. Newer applications
+   * should use the 'RSA-OAEP' decryption scheme, 'RSAES-PKCS1-V1_5' is for
+   * legacy applications.
+   *
+   * @param data the byte string to encrypt.
+   * @param scheme the encryption scheme to use:
+   *          'RSAES-PKCS1-V1_5' (default),
+   *          'RSA-OAEP',
+   *          'RAW', 'NONE', or null to perform raw RSA encryption,
+   *          an object with an 'encode' property set to a function
+   *          with the signature 'function(data, key)' that returns
+   *          a binary-encoded string representing the encoded data.
+   * @param schemeOptions any scheme-specific options.
+   *
+   * @return the encrypted byte string.
+   */
+  key.encrypt = function(data, scheme, schemeOptions) {
+    if(typeof scheme === 'string') {
+      scheme = scheme.toUpperCase();
+    } else if(scheme === undefined) {
+      scheme = 'RSAES-PKCS1-V1_5';
+    }
+
+    if(scheme === 'RSAES-PKCS1-V1_5') {
+      scheme = {
+        encode: function(m, key, pub) {
+          return _encodePkcs1_v1_5(m, key, 0x02).getBytes();
+        }
+      };
+    } else if(scheme === 'RSA-OAEP' || scheme === 'RSAES-OAEP') {
+      scheme = {
+        encode: function(m, key) {
+          return forge.pkcs1.encode_rsa_oaep(key, m, schemeOptions);
+        }
+      };
+    } else if(['RAW', 'NONE', 'NULL', null].indexOf(scheme) !== -1) {
+      scheme = { encode: function(e) { return e; } };
+    } else if(typeof scheme === 'string') {
+      throw new Error('Unsupported encryption scheme: "' + scheme + '".');
+    }
+
+    // do scheme-based encoding then rsa encryption
+    var e = scheme.encode(data, key, true);
+    return pki.rsa.encrypt(e, key, true);
+  };
+
+  /**
+   * Verifies the given signature against the given digest.
+   *
+   * PKCS#1 supports multiple (currently two) signature schemes:
+   * RSASSA-PKCS1-V1_5 and RSASSA-PSS.
+   *
+   * By default this implementation uses the "old scheme", i.e.
+   * RSASSA-PKCS1-V1_5, in which case once RSA-decrypted, the
+   * signature is an OCTET STRING that holds a DigestInfo.
+   *
+   * DigestInfo ::= SEQUENCE {
+   *   digestAlgorithm DigestAlgorithmIdentifier,
+   *   digest Digest
+   * }
+   * DigestAlgorithmIdentifier ::= AlgorithmIdentifier
+   * Digest ::= OCTET STRING
+   *
+   * To perform PSS signature verification, provide an instance
+   * of Forge PSS object as the scheme parameter.
+   *
+   * @param digest the message digest hash to compare against the signature,
+   *          as a binary-encoded string.
+   * @param signature the signature to verify, as a binary-encoded string.
+   * @param scheme signature verification scheme to use:
+   *          'RSASSA-PKCS1-V1_5' or undefined for RSASSA PKCS#1 v1.5,
+   *          a Forge PSS object for RSASSA-PSS,
+   *          'NONE' or null for none, DigestInfo will not be expected, but
+   *            PKCS#1 v1.5 padding will still be used.
+   *
+   * @return true if the signature was verified, false if not.
+   */
+   key.verify = function(digest, signature, scheme) {
+     if(typeof scheme === 'string') {
+       scheme = scheme.toUpperCase();
+     } else if(scheme === undefined) {
+       scheme = 'RSASSA-PKCS1-V1_5';
+     }
+
+     if(scheme === 'RSASSA-PKCS1-V1_5') {
+       scheme = {
+         verify: function(digest, d) {
+           // remove padding
+           d = _decodePkcs1_v1_5(d, key, true);
+           // d is ASN.1 BER-encoded DigestInfo
+           var obj = asn1.fromDer(d);
+           // compare the given digest to the decrypted one
+           return digest === obj.value[1].value;
+         }
+       };
+     } else if(scheme === 'NONE' || scheme === 'NULL' || scheme === null) {
+       scheme = {
+         verify: function(digest, d) {
+           // remove padding
+           d = _decodePkcs1_v1_5(d, key, true);
+           return digest === d;
+         }
+       };
+     }
+
+     // do rsa decryption w/o any decoding, then verify -- which does decoding
+     var d = pki.rsa.decrypt(signature, key, true, false);
+     return scheme.verify(digest, d, key.n.bitLength());
+  };
+
+  return key;
+};
+
+/**
+ * Sets an RSA private key from BigIntegers modulus, exponent, primes,
+ * prime exponents, and modular multiplicative inverse.
+ *
+ * @param n the modulus.
+ * @param e the public exponent.
+ * @param d the private exponent ((inverse of e) mod n).
+ * @param p the first prime.
+ * @param q the second prime.
+ * @param dP exponent1 (d mod (p-1)).
+ * @param dQ exponent2 (d mod (q-1)).
+ * @param qInv ((inverse of q) mod p)
+ *
+ * @return the private key.
+ */
+pki.setRsaPrivateKey = pki.rsa.setPrivateKey = function(
+  n, e, d, p, q, dP, dQ, qInv) {
+  var key = {
+    n: n,
+    e: e,
+    d: d,
+    p: p,
+    q: q,
+    dP: dP,
+    dQ: dQ,
+    qInv: qInv
+  };
+
+  /**
+   * Decrypts the given data with this private key. The decryption scheme
+   * must match the one used to encrypt the data.
+   *
+   * @param data the byte string to decrypt.
+   * @param scheme the decryption scheme to use:
+   *          'RSAES-PKCS1-V1_5' (default),
+   *          'RSA-OAEP',
+   *          'RAW', 'NONE', or null to perform raw RSA decryption.
+   * @param schemeOptions any scheme-specific options.
+   *
+   * @return the decrypted byte string.
+   */
+  key.decrypt = function(data, scheme, schemeOptions) {
+    if(typeof scheme === 'string') {
+      scheme = scheme.toUpperCase();
+    } else if(scheme === undefined) {
+      scheme = 'RSAES-PKCS1-V1_5';
+    }
+
+    // do rsa decryption w/o any decoding
+    var d = pki.rsa.decrypt(data, key, false, false);
+
+    if(scheme === 'RSAES-PKCS1-V1_5') {
+      scheme = { decode: _decodePkcs1_v1_5 };
+    } else if(scheme === 'RSA-OAEP' || scheme === 'RSAES-OAEP') {
+      scheme = {
+        decode: function(d, key) {
+          return forge.pkcs1.decode_rsa_oaep(key, d, schemeOptions);
+        }
+      };
+    } else if(['RAW', 'NONE', 'NULL', null].indexOf(scheme) !== -1) {
+      scheme = { decode: function(d) { return d; } };
+    } else {
+      throw new Error('Unsupported encryption scheme: "' + scheme + '".');
+    }
+
+    // decode according to scheme
+    return scheme.decode(d, key, false);
+  };
+
+  /**
+   * Signs the given digest, producing a signature.
+   *
+   * PKCS#1 supports multiple (currently two) signature schemes:
+   * RSASSA-PKCS1-V1_5 and RSASSA-PSS.
+   *
+   * By default this implementation uses the "old scheme", i.e.
+   * RSASSA-PKCS1-V1_5. In order to generate a PSS signature, provide
+   * an instance of Forge PSS object as the scheme parameter.
+   *
+   * @param md the message digest object with the hash to sign.
+   * @param scheme the signature scheme to use:
+   *          'RSASSA-PKCS1-V1_5' or undefined for RSASSA PKCS#1 v1.5,
+   *          a Forge PSS object for RSASSA-PSS,
+   *          'NONE' or null for none, DigestInfo will not be used but
+   *            PKCS#1 v1.5 padding will still be used.
+   *
+   * @return the signature as a byte string.
+   */
+  key.sign = function(md, scheme) {
+    /* Note: The internal implementation of RSA operations is being
+      transitioned away from a PKCS#1 v1.5 hard-coded scheme. Some legacy
+      code like the use of an encoding block identifier 'bt' will eventually
+      be removed. */
+
+    // private key operation
+    var bt = false;
+
+    if(typeof scheme === 'string') {
+      scheme = scheme.toUpperCase();
+    }
+
+    if(scheme === undefined || scheme === 'RSASSA-PKCS1-V1_5') {
+      scheme = { encode: emsaPkcs1v15encode };
+      bt = 0x01;
+    } else if(scheme === 'NONE' || scheme === 'NULL' || scheme === null) {
+      scheme = { encode: function() { return md; } };
+      bt = 0x01;
+    }
+
+    // encode and then encrypt
+    var d = scheme.encode(md, key.n.bitLength());
+    return pki.rsa.encrypt(d, key, bt);
+  };
+
+  return key;
+};
+
+/**
+ * Wraps an RSAPrivateKey ASN.1 object in an ASN.1 PrivateKeyInfo object.
+ *
+ * @param rsaKey the ASN.1 RSAPrivateKey.
+ *
+ * @return the ASN.1 PrivateKeyInfo.
+ */
+pki.wrapRsaPrivateKey = function(rsaKey) {
+  // PrivateKeyInfo
+  return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+    // version (0)
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      asn1.integerToDer(0).getBytes()),
+    // privateKeyAlgorithm
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+      asn1.create(
+        asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+        asn1.oidToDer(pki.oids.rsaEncryption).getBytes()),
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
+    ]),
+    // PrivateKey
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
+      asn1.toDer(rsaKey).getBytes())
+    ]);
+};
+
+/**
+ * Converts a private key from an ASN.1 object.
+ *
+ * @param obj the ASN.1 representation of a PrivateKeyInfo containing an
+ *          RSAPrivateKey or an RSAPrivateKey.
+ *
+ * @return the private key.
+ */
+pki.privateKeyFromAsn1 = function(obj) {
+  // get PrivateKeyInfo
+  var capture = {};
+  var errors = [];
+  if(asn1.validate(obj, privateKeyValidator, capture, errors)) {
+    obj = asn1.fromDer(forge.util.createBuffer(capture.privateKey));
+  }
+
+  // get RSAPrivateKey
+  capture = {};
+  errors = [];
+  if(!asn1.validate(obj, rsaPrivateKeyValidator, capture, errors)) {
+    var error = new Error('Cannot read private key. ' +
+      'ASN.1 object does not contain an RSAPrivateKey.');
+    error.errors = errors;
+    throw error;
+  }
+
+  // Note: Version is currently ignored.
+  // capture.privateKeyVersion
+  // FIXME: inefficient, get a BigInteger that uses byte strings
+  var n, e, d, p, q, dP, dQ, qInv;
+  n = forge.util.createBuffer(capture.privateKeyModulus).toHex();
+  e = forge.util.createBuffer(capture.privateKeyPublicExponent).toHex();
+  d = forge.util.createBuffer(capture.privateKeyPrivateExponent).toHex();
+  p = forge.util.createBuffer(capture.privateKeyPrime1).toHex();
+  q = forge.util.createBuffer(capture.privateKeyPrime2).toHex();
+  dP = forge.util.createBuffer(capture.privateKeyExponent1).toHex();
+  dQ = forge.util.createBuffer(capture.privateKeyExponent2).toHex();
+  qInv = forge.util.createBuffer(capture.privateKeyCoefficient).toHex();
+
+  // set private key
+  return pki.setRsaPrivateKey(
+    new BigInteger(n, 16),
+    new BigInteger(e, 16),
+    new BigInteger(d, 16),
+    new BigInteger(p, 16),
+    new BigInteger(q, 16),
+    new BigInteger(dP, 16),
+    new BigInteger(dQ, 16),
+    new BigInteger(qInv, 16));
+};
+
+/**
+ * Converts a private key to an ASN.1 RSAPrivateKey.
+ *
+ * @param key the private key.
+ *
+ * @return the ASN.1 representation of an RSAPrivateKey.
+ */
+pki.privateKeyToAsn1 = pki.privateKeyToRSAPrivateKey = function(key) {
+  // RSAPrivateKey
+  return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+    // version (0 = only 2 primes, 1 multiple primes)
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      asn1.integerToDer(0).getBytes()),
+    // modulus (n)
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      _bnToBytes(key.n)),
+    // publicExponent (e)
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      _bnToBytes(key.e)),
+    // privateExponent (d)
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      _bnToBytes(key.d)),
+    // privateKeyPrime1 (p)
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      _bnToBytes(key.p)),
+    // privateKeyPrime2 (q)
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      _bnToBytes(key.q)),
+    // privateKeyExponent1 (dP)
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      _bnToBytes(key.dP)),
+    // privateKeyExponent2 (dQ)
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      _bnToBytes(key.dQ)),
+    // coefficient (qInv)
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      _bnToBytes(key.qInv))
+  ]);
+};
+
+/**
+ * Converts a public key from an ASN.1 SubjectPublicKeyInfo or RSAPublicKey.
+ *
+ * @param obj the asn1 representation of a SubjectPublicKeyInfo or RSAPublicKey.
+ *
+ * @return the public key.
+ */
+pki.publicKeyFromAsn1 = function(obj) {
+  // get SubjectPublicKeyInfo
+  var capture = {};
+  var errors = [];
+  if(asn1.validate(obj, publicKeyValidator, capture, errors)) {
+    // get oid
+    var oid = asn1.derToOid(capture.publicKeyOid);
+    if(oid !== pki.oids.rsaEncryption) {
+      var error = new Error('Cannot read public key. Unknown OID.');
+      error.oid = oid;
+      throw error;
+    }
+    obj = capture.rsaPublicKey;
+  }
+
+  // get RSA params
+  errors = [];
+  if(!asn1.validate(obj, rsaPublicKeyValidator, capture, errors)) {
+    var error = new Error('Cannot read public key. ' +
+      'ASN.1 object does not contain an RSAPublicKey.');
+    error.errors = errors;
+    throw error;
+  }
+
+  // FIXME: inefficient, get a BigInteger that uses byte strings
+  var n = forge.util.createBuffer(capture.publicKeyModulus).toHex();
+  var e = forge.util.createBuffer(capture.publicKeyExponent).toHex();
+
+  // set public key
+  return pki.setRsaPublicKey(
+    new BigInteger(n, 16),
+    new BigInteger(e, 16));
+};
+
+/**
+ * Converts a public key to an ASN.1 SubjectPublicKeyInfo.
+ *
+ * @param key the public key.
+ *
+ * @return the asn1 representation of a SubjectPublicKeyInfo.
+ */
+pki.publicKeyToAsn1 = pki.publicKeyToSubjectPublicKeyInfo = function(key) {
+  // SubjectPublicKeyInfo
+  return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+    // AlgorithmIdentifier
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+      // algorithm
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+        asn1.oidToDer(pki.oids.rsaEncryption).getBytes()),
+      // parameters (null)
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
+    ]),
+    // subjectPublicKey
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.BITSTRING, false, [
+      pki.publicKeyToRSAPublicKey(key)
+    ])
+  ]);
+};
+
+/**
+ * Converts a public key to an ASN.1 RSAPublicKey.
+ *
+ * @param key the public key.
+ *
+ * @return the asn1 representation of a RSAPublicKey.
+ */
+pki.publicKeyToRSAPublicKey = function(key) {
+  // RSAPublicKey
+  return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+    // modulus (n)
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      _bnToBytes(key.n)),
+    // publicExponent (e)
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      _bnToBytes(key.e))
+  ]);
+};
+
+/**
+ * Encodes a message using PKCS#1 v1.5 padding.
+ *
+ * @param m the message to encode.
+ * @param key the RSA key to use.
+ * @param bt the block type to use, i.e. either 0x01 (for signing) or 0x02
+ *          (for encryption).
+ *
+ * @return the padded byte buffer.
+ */
+function _encodePkcs1_v1_5(m, key, bt) {
+  var eb = forge.util.createBuffer();
+
+  // get the length of the modulus in bytes
+  var k = Math.ceil(key.n.bitLength() / 8);
+
+  /* use PKCS#1 v1.5 padding */
+  if(m.length > (k - 11)) {
+    var error = new Error('Message is too long for PKCS#1 v1.5 padding.');
+    error.length = m.length;
+    error.max = k - 11;
+    throw error;
+  }
+
+  /* A block type BT, a padding string PS, and the data D shall be
+    formatted into an octet string EB, the encryption block:
+
+    EB = 00 || BT || PS || 00 || D
+
+    The block type BT shall be a single octet indicating the structure of
+    the encryption block. For this version of the document it shall have
+    value 00, 01, or 02. For a private-key operation, the block type
+    shall be 00 or 01. For a public-key operation, it shall be 02.
+
+    The padding string PS shall consist of k-3-||D|| octets. For block
+    type 00, the octets shall have value 00; for block type 01, they
+    shall have value FF; and for block type 02, they shall be
+    pseudorandomly generated and nonzero. This makes the length of the
+    encryption block EB equal to k. */
+
+  // build the encryption block
+  eb.putByte(0x00);
+  eb.putByte(bt);
+
+  // create the padding
+  var padNum = k - 3 - m.length;
+  var padByte;
+  // private key op
+  if(bt === 0x00 || bt === 0x01) {
+    padByte = (bt === 0x00) ? 0x00 : 0xFF;
+    for(var i = 0; i < padNum; ++i) {
+      eb.putByte(padByte);
+    }
+  } else {
+    // public key op
+    // pad with random non-zero values
+    while(padNum > 0) {
+      var numZeros = 0;
+      var padBytes = forge.random.getBytes(padNum);
+      for(var i = 0; i < padNum; ++i) {
+        padByte = padBytes.charCodeAt(i);
+        if(padByte === 0) {
+          ++numZeros;
+        } else {
+          eb.putByte(padByte);
+        }
+      }
+      padNum = numZeros;
+    }
+  }
+
+  // zero followed by message
+  eb.putByte(0x00);
+  eb.putBytes(m);
+
+  return eb;
+}
+
+/**
+ * Decodes a message using PKCS#1 v1.5 padding.
+ *
+ * @param em the message to decode.
+ * @param key the RSA key to use.
+ * @param pub true if the key is a public key, false if it is private.
+ * @param ml the message length, if specified.
+ *
+ * @return the decoded bytes.
+ */
+function _decodePkcs1_v1_5(em, key, pub, ml) {
+  // get the length of the modulus in bytes
+  var k = Math.ceil(key.n.bitLength() / 8);
+
+  /* It is an error if any of the following conditions occurs:
+
+    1. The encryption block EB cannot be parsed unambiguously.
+    2. The padding string PS consists of fewer than eight octets
+      or is inconsisent with the block type BT.
+    3. The decryption process is a public-key operation and the block
+      type BT is not 00 or 01, or the decryption process is a
+      private-key operation and the block type is not 02.
+   */
+
+  // parse the encryption block
+  var eb = forge.util.createBuffer(em);
+  var first = eb.getByte();
+  var bt = eb.getByte();
+  if(first !== 0x00 ||
+    (pub && bt !== 0x00 && bt !== 0x01) ||
+    (!pub && bt != 0x02) ||
+    (pub && bt === 0x00 && typeof(ml) === 'undefined')) {
+    throw new Error('Encryption block is invalid.');
+  }
+
+  var padNum = 0;
+  if(bt === 0x00) {
+    // check all padding bytes for 0x00
+    padNum = k - 3 - ml;
+    for(var i = 0; i < padNum; ++i) {
+      if(eb.getByte() !== 0x00) {
+        throw new Error('Encryption block is invalid.');
+      }
+    }
+  } else if(bt === 0x01) {
+    // find the first byte that isn't 0xFF, should be after all padding
+    padNum = 0;
+    while(eb.length() > 1) {
+      if(eb.getByte() !== 0xFF) {
+        --eb.read;
+        break;
+      }
+      ++padNum;
+    }
+  } else if(bt === 0x02) {
+    // look for 0x00 byte
+    padNum = 0;
+    while(eb.length() > 1) {
+      if(eb.getByte() === 0x00) {
+        --eb.read;
+        break;
+      }
+      ++padNum;
+    }
+  }
+
+  // zero must be 0x00 and padNum must be (k - 3 - message length)
+  var zero = eb.getByte();
+  if(zero !== 0x00 || padNum !== (k - 3 - eb.length())) {
+    throw new Error('Encryption block is invalid.');
+  }
+
+  return eb.getBytes();
+}
+
+/**
+ * Runs the key-generation algorithm asynchronously, either in the background
+ * via Web Workers, or using the main thread and setImmediate.
+ *
+ * @param state the key-pair generation state.
+ * @param [options] options for key-pair generation:
+ *          workerScript the worker script URL.
+ *          workers the number of web workers (if supported) to use,
+ *            (default: 2, -1 to use estimated cores minus one).
+ *          workLoad the size of the work load, ie: number of possible prime
+ *            numbers for each web worker to check per work assignment,
+ *            (default: 100).
+ * @param callback(err, keypair) called once the operation completes.
+ */
+function _generateKeyPair(state, options, callback) {
+  if(typeof options === 'function') {
+    callback = options;
+    options = {};
+  }
+  options = options || {};
+
+  var opts = {
+    algorithm: {
+      name: options.algorithm || 'PRIMEINC',
+      options: {
+        workers: options.workers || 2,
+        workLoad: options.workLoad || 100,
+        workerScript: options.workerScript
+      }
+    }
+  };
+  if('prng' in options) {
+    opts.prng = options.prng;
+  }
+
+  generate();
+
+  function generate() {
+    // find p and then q (done in series to simplify)
+    getPrime(state.pBits, function(err, num) {
+      if(err) {
+        return callback(err);
+      }
+      state.p = num;
+      if(state.q !== null) {
+        return finish(err, state.q);
+      }
+      getPrime(state.qBits, finish);
+    });
+  }
+
+  function getPrime(bits, callback) {
+    forge.prime.generateProbablePrime(bits, opts, callback);
+  }
+
+  function finish(err, num) {
+    if(err) {
+      return callback(err);
+    }
+
+    // set q
+    state.q = num;
+
+    // ensure p is larger than q (swap them if not)
+    if(state.p.compareTo(state.q) < 0) {
+      var tmp = state.p;
+      state.p = state.q;
+      state.q = tmp;
+    }
+
+    // ensure p is coprime with e
+    if(state.p.subtract(BigInteger.ONE).gcd(state.e)
+      .compareTo(BigInteger.ONE) !== 0) {
+      state.p = null;
+      generate();
+      return;
+    }
+
+    // ensure q is coprime with e
+    if(state.q.subtract(BigInteger.ONE).gcd(state.e)
+      .compareTo(BigInteger.ONE) !== 0) {
+      state.q = null;
+      getPrime(state.qBits, finish);
+      return;
+    }
+
+    // compute phi: (p - 1)(q - 1) (Euler's totient function)
+    state.p1 = state.p.subtract(BigInteger.ONE);
+    state.q1 = state.q.subtract(BigInteger.ONE);
+    state.phi = state.p1.multiply(state.q1);
+
+    // ensure e and phi are coprime
+    if(state.phi.gcd(state.e).compareTo(BigInteger.ONE) !== 0) {
+      // phi and e aren't coprime, so generate a new p and q
+      state.p = state.q = null;
+      generate();
+      return;
+    }
+
+    // create n, ensure n is has the right number of bits
+    state.n = state.p.multiply(state.q);
+    if(state.n.bitLength() !== state.bits) {
+      // failed, get new q
+      state.q = null;
+      getPrime(state.qBits, finish);
+      return;
+    }
+
+    // set keys
+    var d = state.e.modInverse(state.phi);
+    state.keys = {
+      privateKey: pki.rsa.setPrivateKey(
+        state.n, state.e, d, state.p, state.q,
+        d.mod(state.p1), d.mod(state.q1),
+        state.q.modInverse(state.p)),
+      publicKey: pki.rsa.setPublicKey(state.n, state.e)
+    };
+
+    callback(null, state.keys);
+  }
+}
+
+/**
+ * Converts a positive BigInteger into 2's-complement big-endian bytes.
+ *
+ * @param b the big integer to convert.
+ *
+ * @return the bytes.
+ */
+function _bnToBytes(b) {
+  // prepend 0x00 if first byte >= 0x80
+  var hex = b.toString(16);
+  if(hex[0] >= '8') {
+    hex = '00' + hex;
+  }
+  return forge.util.hexToBytes(hex);
+}
+
+/**
+ * Returns the required number of Miller-Rabin tests to generate a
+ * prime with an error probability of (1/2)^80.
+ *
+ * See Handbook of Applied Cryptography Chapter 4, Table 4.4.
+ *
+ * @param bits the bit size.
+ *
+ * @return the required number of iterations.
+ */
+function _getMillerRabinTests(bits) {
+  if(bits <= 100) return 27;
+  if(bits <= 150) return 18;
+  if(bits <= 200) return 15;
+  if(bits <= 250) return 12;
+  if(bits <= 300) return 9;
+  if(bits <= 350) return 8;
+  if(bits <= 400) return 7;
+  if(bits <= 500) return 6;
+  if(bits <= 600) return 5;
+  if(bits <= 800) return 4;
+  if(bits <= 1250) return 3;
+  return 2;
+}
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'rsa';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define([
+  'require',
+  'module',
+  './asn1',
+  './jsbn',
+  './oids',
+  './pkcs1',
+  './prime',
+  './random',
+  './util'
+], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/sha1.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/sha1.js
new file mode 100644
index 0000000..53f65d2
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/sha1.js
@@ -0,0 +1,342 @@
+/**
+ * Secure Hash Algorithm with 160-bit digest (SHA-1) implementation.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+var sha1 = forge.sha1 = forge.sha1 || {};
+forge.md = forge.md || {};
+forge.md.algorithms = forge.md.algorithms || {};
+forge.md.sha1 = forge.md.algorithms.sha1 = sha1;
+
+/**
+ * Creates a SHA-1 message digest object.
+ *
+ * @return a message digest object.
+ */
+sha1.create = function() {
+  // do initialization as necessary
+  if(!_initialized) {
+    _init();
+  }
+
+  // SHA-1 state contains five 32-bit integers
+  var _state = null;
+
+  // input buffer
+  var _input = forge.util.createBuffer();
+
+  // used for word storage
+  var _w = new Array(80);
+
+  // message digest object
+  var md = {
+    algorithm: 'sha1',
+    blockLength: 64,
+    digestLength: 20,
+    // 56-bit length of message so far (does not including padding)
+    messageLength: 0,
+    // true 64-bit message length as two 32-bit ints
+    messageLength64: [0, 0]
+  };
+
+  /**
+   * Starts the digest.
+   *
+   * @return this digest object.
+   */
+  md.start = function() {
+    md.messageLength = 0;
+    md.messageLength64 = [0, 0];
+    _input = forge.util.createBuffer();
+    _state = {
+      h0: 0x67452301,
+      h1: 0xEFCDAB89,
+      h2: 0x98BADCFE,
+      h3: 0x10325476,
+      h4: 0xC3D2E1F0
+    };
+    return md;
+  };
+  // start digest automatically for first time
+  md.start();
+
+  /**
+   * Updates the digest with the given message input. The given input can
+   * treated as raw input (no encoding will be applied) or an encoding of
+   * 'utf8' maybe given to encode the input using UTF-8.
+   *
+   * @param msg the message input to update with.
+   * @param encoding the encoding to use (default: 'raw', other: 'utf8').
+   *
+   * @return this digest object.
+   */
+  md.update = function(msg, encoding) {
+    if(encoding === 'utf8') {
+      msg = forge.util.encodeUtf8(msg);
+    }
+
+    // update message length
+    md.messageLength += msg.length;
+    md.messageLength64[0] += (msg.length / 0x100000000) >>> 0;
+    md.messageLength64[1] += msg.length >>> 0;
+
+    // add bytes to input buffer
+    _input.putBytes(msg);
+
+    // process bytes
+    _update(_state, _w, _input);
+
+    // compact input buffer every 2K or if empty
+    if(_input.read > 2048 || _input.length() === 0) {
+      _input.compact();
+    }
+
+    return md;
+  };
+
+   /**
+    * Produces the digest.
+    *
+    * @return a byte buffer containing the digest value.
+    */
+   md.digest = function() {
+    /* Note: Here we copy the remaining bytes in the input buffer and
+    add the appropriate SHA-1 padding. Then we do the final update
+    on a copy of the state so that if the user wants to get
+    intermediate digests they can do so. */
+
+    /* Determine the number of bytes that must be added to the message
+    to ensure its length is congruent to 448 mod 512. In other words,
+    the data to be digested must be a multiple of 512 bits (or 128 bytes).
+    This data includes the message, some padding, and the length of the
+    message. Since the length of the message will be encoded as 8 bytes (64
+    bits), that means that the last segment of the data must have 56 bytes
+    (448 bits) of message and padding. Therefore, the length of the message
+    plus the padding must be congruent to 448 mod 512 because
+    512 - 128 = 448.
+
+    In order to fill up the message length it must be filled with
+    padding that begins with 1 bit followed by all 0 bits. Padding
+    must *always* be present, so if the message length is already
+    congruent to 448 mod 512, then 512 padding bits must be added. */
+
+    // 512 bits == 64 bytes, 448 bits == 56 bytes, 64 bits = 8 bytes
+    // _padding starts with 1 byte with first bit is set in it which
+    // is byte value 128, then there may be up to 63 other pad bytes
+    var padBytes = forge.util.createBuffer();
+    padBytes.putBytes(_input.bytes());
+    // 64 - (remaining msg + 8 bytes msg length) mod 64
+    padBytes.putBytes(
+      _padding.substr(0, 64 - ((md.messageLength64[1] + 8) & 0x3F)));
+
+    /* Now append length of the message. The length is appended in bits
+    as a 64-bit number in big-endian order. Since we store the length in
+    bytes, we must multiply the 64-bit length by 8 (or left shift by 3). */
+    padBytes.putInt32(
+      (md.messageLength64[0] << 3) | (md.messageLength64[0] >>> 28));
+    padBytes.putInt32(md.messageLength64[1] << 3);
+    var s2 = {
+      h0: _state.h0,
+      h1: _state.h1,
+      h2: _state.h2,
+      h3: _state.h3,
+      h4: _state.h4
+    };
+    _update(s2, _w, padBytes);
+    var rval = forge.util.createBuffer();
+    rval.putInt32(s2.h0);
+    rval.putInt32(s2.h1);
+    rval.putInt32(s2.h2);
+    rval.putInt32(s2.h3);
+    rval.putInt32(s2.h4);
+    return rval;
+  };
+
+  return md;
+};
+
+// sha-1 padding bytes not initialized yet
+var _padding = null;
+var _initialized = false;
+
+/**
+ * Initializes the constant tables.
+ */
+function _init() {
+  // create padding
+  _padding = String.fromCharCode(128);
+  _padding += forge.util.fillString(String.fromCharCode(0x00), 64);
+
+  // now initialized
+  _initialized = true;
+}
+
+/**
+ * Updates a SHA-1 state with the given byte buffer.
+ *
+ * @param s the SHA-1 state to update.
+ * @param w the array to use to store words.
+ * @param bytes the byte buffer to update with.
+ */
+function _update(s, w, bytes) {
+  // consume 512 bit (64 byte) chunks
+  var t, a, b, c, d, e, f, i;
+  var len = bytes.length();
+  while(len >= 64) {
+    // the w array will be populated with sixteen 32-bit big-endian words
+    // and then extended into 80 32-bit words according to SHA-1 algorithm
+    // and for 32-79 using Max Locktyukhin's optimization
+
+    // initialize hash value for this chunk
+    a = s.h0;
+    b = s.h1;
+    c = s.h2;
+    d = s.h3;
+    e = s.h4;
+
+    // round 1
+    for(i = 0; i < 16; ++i) {
+      t = bytes.getInt32();
+      w[i] = t;
+      f = d ^ (b & (c ^ d));
+      t = ((a << 5) | (a >>> 27)) + f + e + 0x5A827999 + t;
+      e = d;
+      d = c;
+      c = (b << 30) | (b >>> 2);
+      b = a;
+      a = t;
+    }
+    for(; i < 20; ++i) {
+      t = (w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]);
+      t = (t << 1) | (t >>> 31);
+      w[i] = t;
+      f = d ^ (b & (c ^ d));
+      t = ((a << 5) | (a >>> 27)) + f + e + 0x5A827999 + t;
+      e = d;
+      d = c;
+      c = (b << 30) | (b >>> 2);
+      b = a;
+      a = t;
+    }
+    // round 2
+    for(; i < 32; ++i) {
+      t = (w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]);
+      t = (t << 1) | (t >>> 31);
+      w[i] = t;
+      f = b ^ c ^ d;
+      t = ((a << 5) | (a >>> 27)) + f + e + 0x6ED9EBA1 + t;
+      e = d;
+      d = c;
+      c = (b << 30) | (b >>> 2);
+      b = a;
+      a = t;
+    }
+    for(; i < 40; ++i) {
+      t = (w[i - 6] ^ w[i - 16] ^ w[i - 28] ^ w[i - 32]);
+      t = (t << 2) | (t >>> 30);
+      w[i] = t;
+      f = b ^ c ^ d;
+      t = ((a << 5) | (a >>> 27)) + f + e + 0x6ED9EBA1 + t;
+      e = d;
+      d = c;
+      c = (b << 30) | (b >>> 2);
+      b = a;
+      a = t;
+    }
+    // round 3
+    for(; i < 60; ++i) {
+      t = (w[i - 6] ^ w[i - 16] ^ w[i - 28] ^ w[i - 32]);
+      t = (t << 2) | (t >>> 30);
+      w[i] = t;
+      f = (b & c) | (d & (b ^ c));
+      t = ((a << 5) | (a >>> 27)) + f + e + 0x8F1BBCDC + t;
+      e = d;
+      d = c;
+      c = (b << 30) | (b >>> 2);
+      b = a;
+      a = t;
+    }
+    // round 4
+    for(; i < 80; ++i) {
+      t = (w[i - 6] ^ w[i - 16] ^ w[i - 28] ^ w[i - 32]);
+      t = (t << 2) | (t >>> 30);
+      w[i] = t;
+      f = b ^ c ^ d;
+      t = ((a << 5) | (a >>> 27)) + f + e + 0xCA62C1D6 + t;
+      e = d;
+      d = c;
+      c = (b << 30) | (b >>> 2);
+      b = a;
+      a = t;
+    }
+
+    // update hash state
+    s.h0 = (s.h0 + a) | 0;
+    s.h1 = (s.h1 + b) | 0;
+    s.h2 = (s.h2 + c) | 0;
+    s.h3 = (s.h3 + d) | 0;
+    s.h4 = (s.h4 + e) | 0;
+
+    len -= 64;
+  }
+}
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'sha1';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/sha256.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/sha256.js
new file mode 100644
index 0000000..fdbc4fc
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/sha256.js
@@ -0,0 +1,352 @@
+/**
+ * Secure Hash Algorithm with 256-bit digest (SHA-256) implementation.
+ *
+ * See FIPS 180-2 for details.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+var sha256 = forge.sha256 = forge.sha256 || {};
+forge.md = forge.md || {};
+forge.md.algorithms = forge.md.algorithms || {};
+forge.md.sha256 = forge.md.algorithms.sha256 = sha256;
+
+/**
+ * Creates a SHA-256 message digest object.
+ *
+ * @return a message digest object.
+ */
+sha256.create = function() {
+  // do initialization as necessary
+  if(!_initialized) {
+    _init();
+  }
+
+  // SHA-256 state contains eight 32-bit integers
+  var _state = null;
+
+  // input buffer
+  var _input = forge.util.createBuffer();
+
+  // used for word storage
+  var _w = new Array(64);
+
+  // message digest object
+  var md = {
+    algorithm: 'sha256',
+    blockLength: 64,
+    digestLength: 32,
+    // 56-bit length of message so far (does not including padding)
+    messageLength: 0,
+    // true 64-bit message length as two 32-bit ints
+    messageLength64: [0, 0]
+  };
+
+  /**
+   * Starts the digest.
+   *
+   * @return this digest object.
+   */
+  md.start = function() {
+    md.messageLength = 0;
+    md.messageLength64 = [0, 0];
+    _input = forge.util.createBuffer();
+    _state = {
+      h0: 0x6A09E667,
+      h1: 0xBB67AE85,
+      h2: 0x3C6EF372,
+      h3: 0xA54FF53A,
+      h4: 0x510E527F,
+      h5: 0x9B05688C,
+      h6: 0x1F83D9AB,
+      h7: 0x5BE0CD19
+    };
+    return md;
+  };
+  // start digest automatically for first time
+  md.start();
+
+  /**
+   * Updates the digest with the given message input. The given input can
+   * treated as raw input (no encoding will be applied) or an encoding of
+   * 'utf8' maybe given to encode the input using UTF-8.
+   *
+   * @param msg the message input to update with.
+   * @param encoding the encoding to use (default: 'raw', other: 'utf8').
+   *
+   * @return this digest object.
+   */
+  md.update = function(msg, encoding) {
+    if(encoding === 'utf8') {
+      msg = forge.util.encodeUtf8(msg);
+    }
+
+    // update message length
+    md.messageLength += msg.length;
+    md.messageLength64[0] += (msg.length / 0x100000000) >>> 0;
+    md.messageLength64[1] += msg.length >>> 0;
+
+    // add bytes to input buffer
+    _input.putBytes(msg);
+
+    // process bytes
+    _update(_state, _w, _input);
+
+    // compact input buffer every 2K or if empty
+    if(_input.read > 2048 || _input.length() === 0) {
+      _input.compact();
+    }
+
+    return md;
+  };
+
+  /**
+   * Produces the digest.
+   *
+   * @return a byte buffer containing the digest value.
+   */
+  md.digest = function() {
+    /* Note: Here we copy the remaining bytes in the input buffer and
+    add the appropriate SHA-256 padding. Then we do the final update
+    on a copy of the state so that if the user wants to get
+    intermediate digests they can do so. */
+
+    /* Determine the number of bytes that must be added to the message
+    to ensure its length is congruent to 448 mod 512. In other words,
+    the data to be digested must be a multiple of 512 bits (or 128 bytes).
+    This data includes the message, some padding, and the length of the
+    message. Since the length of the message will be encoded as 8 bytes (64
+    bits), that means that the last segment of the data must have 56 bytes
+    (448 bits) of message and padding. Therefore, the length of the message
+    plus the padding must be congruent to 448 mod 512 because
+    512 - 128 = 448.
+
+    In order to fill up the message length it must be filled with
+    padding that begins with 1 bit followed by all 0 bits. Padding
+    must *always* be present, so if the message length is already
+    congruent to 448 mod 512, then 512 padding bits must be added. */
+
+    // 512 bits == 64 bytes, 448 bits == 56 bytes, 64 bits = 8 bytes
+    // _padding starts with 1 byte with first bit is set in it which
+    // is byte value 128, then there may be up to 63 other pad bytes
+    var padBytes = forge.util.createBuffer();
+    padBytes.putBytes(_input.bytes());
+    // 64 - (remaining msg + 8 bytes msg length) mod 64
+    padBytes.putBytes(
+      _padding.substr(0, 64 - ((md.messageLength64[1] + 8) & 0x3F)));
+
+    /* Now append length of the message. The length is appended in bits
+    as a 64-bit number in big-endian order. Since we store the length in
+    bytes, we must multiply the 64-bit length by 8 (or left shift by 3). */
+    padBytes.putInt32(
+      (md.messageLength64[0] << 3) | (md.messageLength64[0] >>> 28));
+    padBytes.putInt32(md.messageLength64[1] << 3);
+    var s2 = {
+      h0: _state.h0,
+      h1: _state.h1,
+      h2: _state.h2,
+      h3: _state.h3,
+      h4: _state.h4,
+      h5: _state.h5,
+      h6: _state.h6,
+      h7: _state.h7
+    };
+    _update(s2, _w, padBytes);
+    var rval = forge.util.createBuffer();
+    rval.putInt32(s2.h0);
+    rval.putInt32(s2.h1);
+    rval.putInt32(s2.h2);
+    rval.putInt32(s2.h3);
+    rval.putInt32(s2.h4);
+    rval.putInt32(s2.h5);
+    rval.putInt32(s2.h6);
+    rval.putInt32(s2.h7);
+    return rval;
+  };
+
+  return md;
+};
+
+// sha-256 padding bytes not initialized yet
+var _padding = null;
+var _initialized = false;
+
+// table of constants
+var _k = null;
+
+/**
+ * Initializes the constant tables.
+ */
+function _init() {
+  // create padding
+  _padding = String.fromCharCode(128);
+  _padding += forge.util.fillString(String.fromCharCode(0x00), 64);
+
+  // create K table for SHA-256
+  _k = [
+    0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
+    0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+    0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
+    0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
+    0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
+    0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+    0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
+    0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
+    0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
+    0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+    0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
+    0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+    0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
+    0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
+    0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
+    0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2];
+
+  // now initialized
+  _initialized = true;
+}
+
+/**
+ * Updates a SHA-256 state with the given byte buffer.
+ *
+ * @param s the SHA-256 state to update.
+ * @param w the array to use to store words.
+ * @param bytes the byte buffer to update with.
+ */
+function _update(s, w, bytes) {
+  // consume 512 bit (64 byte) chunks
+  var t1, t2, s0, s1, ch, maj, i, a, b, c, d, e, f, g, h;
+  var len = bytes.length();
+  while(len >= 64) {
+    // the w array will be populated with sixteen 32-bit big-endian words
+    // and then extended into 64 32-bit words according to SHA-256
+    for(i = 0; i < 16; ++i) {
+      w[i] = bytes.getInt32();
+    }
+    for(; i < 64; ++i) {
+      // XOR word 2 words ago rot right 17, rot right 19, shft right 10
+      t1 = w[i - 2];
+      t1 =
+        ((t1 >>> 17) | (t1 << 15)) ^
+        ((t1 >>> 19) | (t1 << 13)) ^
+        (t1 >>> 10);
+      // XOR word 15 words ago rot right 7, rot right 18, shft right 3
+      t2 = w[i - 15];
+      t2 =
+        ((t2 >>> 7) | (t2 << 25)) ^
+        ((t2 >>> 18) | (t2 << 14)) ^
+        (t2 >>> 3);
+      // sum(t1, word 7 ago, t2, word 16 ago) modulo 2^32
+      w[i] = (t1 + w[i - 7] + t2 + w[i - 16]) | 0;
+    }
+
+    // initialize hash value for this chunk
+    a = s.h0;
+    b = s.h1;
+    c = s.h2;
+    d = s.h3;
+    e = s.h4;
+    f = s.h5;
+    g = s.h6;
+    h = s.h7;
+
+    // round function
+    for(i = 0; i < 64; ++i) {
+      // Sum1(e)
+      s1 =
+        ((e >>> 6) | (e << 26)) ^
+        ((e >>> 11) | (e << 21)) ^
+        ((e >>> 25) | (e << 7));
+      // Ch(e, f, g) (optimized the same way as SHA-1)
+      ch = g ^ (e & (f ^ g));
+      // Sum0(a)
+      s0 =
+        ((a >>> 2) | (a << 30)) ^
+        ((a >>> 13) | (a << 19)) ^
+        ((a >>> 22) | (a << 10));
+      // Maj(a, b, c) (optimized the same way as SHA-1)
+      maj = (a & b) | (c & (a ^ b));
+
+      // main algorithm
+      t1 = h + s1 + ch + _k[i] + w[i];
+      t2 = s0 + maj;
+      h = g;
+      g = f;
+      f = e;
+      e = (d + t1) | 0;
+      d = c;
+      c = b;
+      b = a;
+      a = (t1 + t2) | 0;
+    }
+
+    // update hash state
+    s.h0 = (s.h0 + a) | 0;
+    s.h1 = (s.h1 + b) | 0;
+    s.h2 = (s.h2 + c) | 0;
+    s.h3 = (s.h3 + d) | 0;
+    s.h4 = (s.h4 + e) | 0;
+    s.h5 = (s.h5 + f) | 0;
+    s.h6 = (s.h6 + g) | 0;
+    s.h7 = (s.h7 + h) | 0;
+    len -= 64;
+  }
+}
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'sha256';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/sha512.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/sha512.js
new file mode 100644
index 0000000..12a9d94
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/sha512.js
@@ -0,0 +1,590 @@
+/**
+ * Secure Hash Algorithm with a 1024-bit block size implementation.
+ *
+ * This includes: SHA-512, SHA-384, SHA-512/224, and SHA-512/256. For
+ * SHA-256 (block size 512 bits), see sha256.js.
+ *
+ * See FIPS 180-4 for details.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+var sha512 = forge.sha512 = forge.sha512 || {};
+forge.md = forge.md || {};
+forge.md.algorithms = forge.md.algorithms || {};
+
+// SHA-512
+forge.md.sha512 = forge.md.algorithms.sha512 = sha512;
+
+// SHA-384
+var sha384 = forge.sha384 = forge.sha512.sha384 = forge.sha512.sha384 || {};
+sha384.create = function() {
+  return sha512.create('SHA-384');
+};
+forge.md.sha384 = forge.md.algorithms.sha384 = sha384;
+
+// SHA-512/256
+forge.sha512.sha256 = forge.sha512.sha256 || {
+  create: function() {
+    return sha512.create('SHA-512/256');
+  }
+};
+forge.md['sha512/256'] = forge.md.algorithms['sha512/256'] =
+  forge.sha512.sha256;
+
+// SHA-512/224
+forge.sha512.sha224 = forge.sha512.sha224 || {
+  create: function() {
+    return sha512.create('SHA-512/224');
+  }
+};
+forge.md['sha512/224'] = forge.md.algorithms['sha512/224'] =
+  forge.sha512.sha224;
+
+/**
+ * Creates a SHA-2 message digest object.
+ *
+ * @param algorithm the algorithm to use (SHA-512, SHA-384, SHA-512/224,
+ *          SHA-512/256).
+ *
+ * @return a message digest object.
+ */
+sha512.create = function(algorithm) {
+  // do initialization as necessary
+  if(!_initialized) {
+    _init();
+  }
+
+  if(typeof algorithm === 'undefined') {
+    algorithm = 'SHA-512';
+  }
+
+  if(!(algorithm in _states)) {
+    throw new Error('Invalid SHA-512 algorithm: ' + algorithm);
+  }
+
+  // SHA-512 state contains eight 64-bit integers (each as two 32-bit ints)
+  var _state = _states[algorithm];
+  var _h = null;
+
+  // input buffer
+  var _input = forge.util.createBuffer();
+
+  // used for 64-bit word storage
+  var _w = new Array(80);
+  for(var wi = 0; wi < 80; ++wi) {
+    _w[wi] = new Array(2);
+  }
+
+  // message digest object
+  var md = {
+    // SHA-512 => sha512
+    algorithm: algorithm.replace('-', '').toLowerCase(),
+    blockLength: 128,
+    digestLength: 64,
+    // 56-bit length of message so far (does not including padding)
+    messageLength: 0,
+    // true 128-bit message length as four 32-bit ints
+    messageLength128: [0, 0, 0, 0]
+  };
+
+  /**
+   * Starts the digest.
+   *
+   * @return this digest object.
+   */
+  md.start = function() {
+    md.messageLength = 0;
+    md.messageLength128 = [0, 0, 0, 0];
+    _input = forge.util.createBuffer();
+    _h = new Array(_state.length);
+    for(var i = 0; i < _state.length; ++i) {
+      _h[i] = _state[i].slice(0);
+    }
+    return md;
+  };
+  // start digest automatically for first time
+  md.start();
+
+  /**
+   * Updates the digest with the given message input. The given input can
+   * treated as raw input (no encoding will be applied) or an encoding of
+   * 'utf8' maybe given to encode the input using UTF-8.
+   *
+   * @param msg the message input to update with.
+   * @param encoding the encoding to use (default: 'raw', other: 'utf8').
+   *
+   * @return this digest object.
+   */
+  md.update = function(msg, encoding) {
+    if(encoding === 'utf8') {
+      msg = forge.util.encodeUtf8(msg);
+    }
+
+    // update message length
+    md.messageLength += msg.length;
+    var len = msg.length;
+    len = [(len / 0x100000000) >>> 0, len >>> 0];
+    for(var i = 3; i >= 0; --i) {
+      md.messageLength128[i] += len[1];
+      len[1] = len[0] + ((md.messageLength128[i] / 0x100000000) >>> 0);
+      md.messageLength128[i] = md.messageLength128[i] >>> 0;
+      len[0] = ((len[1] / 0x100000000) >>> 0);
+    }
+
+    // add bytes to input buffer
+    _input.putBytes(msg);
+
+    // process bytes
+    _update(_h, _w, _input);
+
+    // compact input buffer every 2K or if empty
+    if(_input.read > 2048 || _input.length() === 0) {
+      _input.compact();
+    }
+
+    return md;
+  };
+
+  /**
+   * Produces the digest.
+   *
+   * @return a byte buffer containing the digest value.
+   */
+  md.digest = function() {
+    /* Note: Here we copy the remaining bytes in the input buffer and
+    add the appropriate SHA-512 padding. Then we do the final update
+    on a copy of the state so that if the user wants to get
+    intermediate digests they can do so. */
+
+    /* Determine the number of bytes that must be added to the message
+    to ensure its length is congruent to 896 mod 1024. In other words,
+    the data to be digested must be a multiple of 1024 bits (or 128 bytes).
+    This data includes the message, some padding, and the length of the
+    message. Since the length of the message will be encoded as 16 bytes (128
+    bits), that means that the last segment of the data must have 112 bytes
+    (896 bits) of message and padding. Therefore, the length of the message
+    plus the padding must be congruent to 896 mod 1024 because
+    1024 - 128 = 896.
+
+    In order to fill up the message length it must be filled with
+    padding that begins with 1 bit followed by all 0 bits. Padding
+    must *always* be present, so if the message length is already
+    congruent to 896 mod 1024, then 1024 padding bits must be added. */
+
+    // 1024 bits == 128 bytes, 896 bits == 112 bytes, 128 bits = 16 bytes
+    // _padding starts with 1 byte with first bit is set in it which
+    // is byte value 128, then there may be up to 127 other pad bytes
+    var padBytes = forge.util.createBuffer();
+    padBytes.putBytes(_input.bytes());
+    // 128 - (remaining msg + 16 bytes msg length) mod 128
+    padBytes.putBytes(
+      _padding.substr(0, 128 - ((md.messageLength128[3] + 16) & 0x7F)));
+
+    /* Now append length of the message. The length is appended in bits
+    as a 128-bit number in big-endian order. Since we store the length in
+    bytes, we must multiply the 128-bit length by 8 (or left shift by 3). */
+    var bitLength = [];
+    for(var i = 0; i < 3; ++i) {
+      bitLength[i] = ((md.messageLength128[i] << 3) |
+        (md.messageLength128[i - 1] >>> 28));
+    }
+    // shift the last integer normally
+    bitLength[3] = md.messageLength128[3] << 3;
+    padBytes.putInt32(bitLength[0]);
+    padBytes.putInt32(bitLength[1]);
+    padBytes.putInt32(bitLength[2]);
+    padBytes.putInt32(bitLength[3]);
+    var h = new Array(_h.length);
+    for(var i = 0; i < _h.length; ++i) {
+      h[i] = _h[i].slice(0);
+    }
+    _update(h, _w, padBytes);
+    var rval = forge.util.createBuffer();
+    var hlen;
+    if(algorithm === 'SHA-512') {
+      hlen = h.length;
+    } else if(algorithm === 'SHA-384') {
+      hlen = h.length - 2;
+    } else {
+      hlen = h.length - 4;
+    }
+    for(var i = 0; i < hlen; ++i) {
+      rval.putInt32(h[i][0]);
+      if(i !== hlen - 1 || algorithm !== 'SHA-512/224') {
+        rval.putInt32(h[i][1]);
+      }
+    }
+    return rval;
+  };
+
+  return md;
+};
+
+// sha-512 padding bytes not initialized yet
+var _padding = null;
+var _initialized = false;
+
+// table of constants
+var _k = null;
+
+// initial hash states
+var _states = null;
+
+/**
+ * Initializes the constant tables.
+ */
+function _init() {
+  // create padding
+  _padding = String.fromCharCode(128);
+  _padding += forge.util.fillString(String.fromCharCode(0x00), 128);
+
+  // create K table for SHA-512
+  _k = [
+    [0x428a2f98, 0xd728ae22], [0x71374491, 0x23ef65cd],
+    [0xb5c0fbcf, 0xec4d3b2f], [0xe9b5dba5, 0x8189dbbc],
+    [0x3956c25b, 0xf348b538], [0x59f111f1, 0xb605d019],
+    [0x923f82a4, 0xaf194f9b], [0xab1c5ed5, 0xda6d8118],
+    [0xd807aa98, 0xa3030242], [0x12835b01, 0x45706fbe],
+    [0x243185be, 0x4ee4b28c], [0x550c7dc3, 0xd5ffb4e2],
+    [0x72be5d74, 0xf27b896f], [0x80deb1fe, 0x3b1696b1],
+    [0x9bdc06a7, 0x25c71235], [0xc19bf174, 0xcf692694],
+    [0xe49b69c1, 0x9ef14ad2], [0xefbe4786, 0x384f25e3],
+    [0x0fc19dc6, 0x8b8cd5b5], [0x240ca1cc, 0x77ac9c65],
+    [0x2de92c6f, 0x592b0275], [0x4a7484aa, 0x6ea6e483],
+    [0x5cb0a9dc, 0xbd41fbd4], [0x76f988da, 0x831153b5],
+    [0x983e5152, 0xee66dfab], [0xa831c66d, 0x2db43210],
+    [0xb00327c8, 0x98fb213f], [0xbf597fc7, 0xbeef0ee4],
+    [0xc6e00bf3, 0x3da88fc2], [0xd5a79147, 0x930aa725],
+    [0x06ca6351, 0xe003826f], [0x14292967, 0x0a0e6e70],
+    [0x27b70a85, 0x46d22ffc], [0x2e1b2138, 0x5c26c926],
+    [0x4d2c6dfc, 0x5ac42aed], [0x53380d13, 0x9d95b3df],
+    [0x650a7354, 0x8baf63de], [0x766a0abb, 0x3c77b2a8],
+    [0x81c2c92e, 0x47edaee6], [0x92722c85, 0x1482353b],
+    [0xa2bfe8a1, 0x4cf10364], [0xa81a664b, 0xbc423001],
+    [0xc24b8b70, 0xd0f89791], [0xc76c51a3, 0x0654be30],
+    [0xd192e819, 0xd6ef5218], [0xd6990624, 0x5565a910],
+    [0xf40e3585, 0x5771202a], [0x106aa070, 0x32bbd1b8],
+    [0x19a4c116, 0xb8d2d0c8], [0x1e376c08, 0x5141ab53],
+    [0x2748774c, 0xdf8eeb99], [0x34b0bcb5, 0xe19b48a8],
+    [0x391c0cb3, 0xc5c95a63], [0x4ed8aa4a, 0xe3418acb],
+    [0x5b9cca4f, 0x7763e373], [0x682e6ff3, 0xd6b2b8a3],
+    [0x748f82ee, 0x5defb2fc], [0x78a5636f, 0x43172f60],
+    [0x84c87814, 0xa1f0ab72], [0x8cc70208, 0x1a6439ec],
+    [0x90befffa, 0x23631e28], [0xa4506ceb, 0xde82bde9],
+    [0xbef9a3f7, 0xb2c67915], [0xc67178f2, 0xe372532b],
+    [0xca273ece, 0xea26619c], [0xd186b8c7, 0x21c0c207],
+    [0xeada7dd6, 0xcde0eb1e], [0xf57d4f7f, 0xee6ed178],
+    [0x06f067aa, 0x72176fba], [0x0a637dc5, 0xa2c898a6],
+    [0x113f9804, 0xbef90dae], [0x1b710b35, 0x131c471b],
+    [0x28db77f5, 0x23047d84], [0x32caab7b, 0x40c72493],
+    [0x3c9ebe0a, 0x15c9bebc], [0x431d67c4, 0x9c100d4c],
+    [0x4cc5d4be, 0xcb3e42b6], [0x597f299c, 0xfc657e2a],
+    [0x5fcb6fab, 0x3ad6faec], [0x6c44198c, 0x4a475817]
+  ];
+
+  // initial hash states
+  _states = {};
+  _states['SHA-512'] = [
+    [0x6a09e667, 0xf3bcc908],
+    [0xbb67ae85, 0x84caa73b],
+    [0x3c6ef372, 0xfe94f82b],
+    [0xa54ff53a, 0x5f1d36f1],
+    [0x510e527f, 0xade682d1],
+    [0x9b05688c, 0x2b3e6c1f],
+    [0x1f83d9ab, 0xfb41bd6b],
+    [0x5be0cd19, 0x137e2179]
+  ];
+  _states['SHA-384'] = [
+    [0xcbbb9d5d, 0xc1059ed8],
+    [0x629a292a, 0x367cd507],
+    [0x9159015a, 0x3070dd17],
+    [0x152fecd8, 0xf70e5939],
+    [0x67332667, 0xffc00b31],
+    [0x8eb44a87, 0x68581511],
+    [0xdb0c2e0d, 0x64f98fa7],
+    [0x47b5481d, 0xbefa4fa4]
+  ];
+  _states['SHA-512/256'] = [
+    [0x22312194, 0xFC2BF72C],
+    [0x9F555FA3, 0xC84C64C2],
+    [0x2393B86B, 0x6F53B151],
+    [0x96387719, 0x5940EABD],
+    [0x96283EE2, 0xA88EFFE3],
+    [0xBE5E1E25, 0x53863992],
+    [0x2B0199FC, 0x2C85B8AA],
+    [0x0EB72DDC, 0x81C52CA2]
+  ];
+  _states['SHA-512/224'] = [
+    [0x8C3D37C8, 0x19544DA2],
+    [0x73E19966, 0x89DCD4D6],
+    [0x1DFAB7AE, 0x32FF9C82],
+    [0x679DD514, 0x582F9FCF],
+    [0x0F6D2B69, 0x7BD44DA8],
+    [0x77E36F73, 0x04C48942],
+    [0x3F9D85A8, 0x6A1D36C8],
+    [0x1112E6AD, 0x91D692A1]
+  ];
+
+  // now initialized
+  _initialized = true;
+}
+
+/**
+ * Updates a SHA-512 state with the given byte buffer.
+ *
+ * @param s the SHA-512 state to update.
+ * @param w the array to use to store words.
+ * @param bytes the byte buffer to update with.
+ */
+function _update(s, w, bytes) {
+  // consume 512 bit (128 byte) chunks
+  var t1_hi, t1_lo;
+  var t2_hi, t2_lo;
+  var s0_hi, s0_lo;
+  var s1_hi, s1_lo;
+  var ch_hi, ch_lo;
+  var maj_hi, maj_lo;
+  var a_hi, a_lo;
+  var b_hi, b_lo;
+  var c_hi, c_lo;
+  var d_hi, d_lo;
+  var e_hi, e_lo;
+  var f_hi, f_lo;
+  var g_hi, g_lo;
+  var h_hi, h_lo;
+  var i, hi, lo, w2, w7, w15, w16;
+  var len = bytes.length();
+  while(len >= 128) {
+    // the w array will be populated with sixteen 64-bit big-endian words
+    // and then extended into 64 64-bit words according to SHA-512
+    for(i = 0; i < 16; ++i) {
+      w[i][0] = bytes.getInt32() >>> 0;
+      w[i][1] = bytes.getInt32() >>> 0;
+    }
+    for(; i < 80; ++i) {
+      // for word 2 words ago: ROTR 19(x) ^ ROTR 61(x) ^ SHR 6(x)
+      w2 = w[i - 2];
+      hi = w2[0];
+      lo = w2[1];
+
+      // high bits
+      t1_hi = (
+        ((hi >>> 19) | (lo << 13)) ^ // ROTR 19
+        ((lo >>> 29) | (hi << 3)) ^ // ROTR 61/(swap + ROTR 29)
+        (hi >>> 6)) >>> 0; // SHR 6
+      // low bits
+      t1_lo = (
+        ((hi << 13) | (lo >>> 19)) ^ // ROTR 19
+        ((lo << 3) | (hi >>> 29)) ^ // ROTR 61/(swap + ROTR 29)
+        ((hi << 26) | (lo >>> 6))) >>> 0; // SHR 6
+
+      // for word 15 words ago: ROTR 1(x) ^ ROTR 8(x) ^ SHR 7(x)
+      w15 = w[i - 15];
+      hi = w15[0];
+      lo = w15[1];
+
+      // high bits
+      t2_hi = (
+        ((hi >>> 1) | (lo << 31)) ^ // ROTR 1
+        ((hi >>> 8) | (lo << 24)) ^ // ROTR 8
+        (hi >>> 7)) >>> 0; // SHR 7
+      // low bits
+      t2_lo = (
+        ((hi << 31) | (lo >>> 1)) ^ // ROTR 1
+        ((hi << 24) | (lo >>> 8)) ^ // ROTR 8
+        ((hi << 25) | (lo >>> 7))) >>> 0; // SHR 7
+
+      // sum(t1, word 7 ago, t2, word 16 ago) modulo 2^64 (carry lo overflow)
+      w7 = w[i - 7];
+      w16 = w[i - 16];
+      lo = (t1_lo + w7[1] + t2_lo + w16[1]);
+      w[i][0] = (t1_hi + w7[0] + t2_hi + w16[0] +
+        ((lo / 0x100000000) >>> 0)) >>> 0;
+      w[i][1] = lo >>> 0;
+    }
+
+    // initialize hash value for this chunk
+    a_hi = s[0][0];
+    a_lo = s[0][1];
+    b_hi = s[1][0];
+    b_lo = s[1][1];
+    c_hi = s[2][0];
+    c_lo = s[2][1];
+    d_hi = s[3][0];
+    d_lo = s[3][1];
+    e_hi = s[4][0];
+    e_lo = s[4][1];
+    f_hi = s[5][0];
+    f_lo = s[5][1];
+    g_hi = s[6][0];
+    g_lo = s[6][1];
+    h_hi = s[7][0];
+    h_lo = s[7][1];
+
+    // round function
+    for(i = 0; i < 80; ++i) {
+      // Sum1(e) = ROTR 14(e) ^ ROTR 18(e) ^ ROTR 41(e)
+      s1_hi = (
+        ((e_hi >>> 14) | (e_lo << 18)) ^ // ROTR 14
+        ((e_hi >>> 18) | (e_lo << 14)) ^ // ROTR 18
+        ((e_lo >>> 9) | (e_hi << 23))) >>> 0; // ROTR 41/(swap + ROTR 9)
+      s1_lo = (
+        ((e_hi << 18) | (e_lo >>> 14)) ^ // ROTR 14
+        ((e_hi << 14) | (e_lo >>> 18)) ^ // ROTR 18
+        ((e_lo << 23) | (e_hi >>> 9))) >>> 0; // ROTR 41/(swap + ROTR 9)
+
+      // Ch(e, f, g) (optimized the same way as SHA-1)
+      ch_hi = (g_hi ^ (e_hi & (f_hi ^ g_hi))) >>> 0;
+      ch_lo = (g_lo ^ (e_lo & (f_lo ^ g_lo))) >>> 0;
+
+      // Sum0(a) = ROTR 28(a) ^ ROTR 34(a) ^ ROTR 39(a)
+      s0_hi = (
+        ((a_hi >>> 28) | (a_lo << 4)) ^ // ROTR 28
+        ((a_lo >>> 2) | (a_hi << 30)) ^ // ROTR 34/(swap + ROTR 2)
+        ((a_lo >>> 7) | (a_hi << 25))) >>> 0; // ROTR 39/(swap + ROTR 7)
+      s0_lo = (
+        ((a_hi << 4) | (a_lo >>> 28)) ^ // ROTR 28
+        ((a_lo << 30) | (a_hi >>> 2)) ^ // ROTR 34/(swap + ROTR 2)
+        ((a_lo << 25) | (a_hi >>> 7))) >>> 0; // ROTR 39/(swap + ROTR 7)
+
+      // Maj(a, b, c) (optimized the same way as SHA-1)
+      maj_hi = ((a_hi & b_hi) | (c_hi & (a_hi ^ b_hi))) >>> 0;
+      maj_lo = ((a_lo & b_lo) | (c_lo & (a_lo ^ b_lo))) >>> 0;
+
+      // main algorithm
+      // t1 = (h + s1 + ch + _k[i] + _w[i]) modulo 2^64 (carry lo overflow)
+      lo = (h_lo + s1_lo + ch_lo + _k[i][1] + w[i][1]);
+      t1_hi = (h_hi + s1_hi + ch_hi + _k[i][0] + w[i][0] +
+        ((lo / 0x100000000) >>> 0)) >>> 0;
+      t1_lo = lo >>> 0;
+
+      // t2 = s0 + maj modulo 2^64 (carry lo overflow)
+      lo = s0_lo + maj_lo;
+      t2_hi = (s0_hi + maj_hi + ((lo / 0x100000000) >>> 0)) >>> 0;
+      t2_lo = lo >>> 0;
+
+      h_hi = g_hi;
+      h_lo = g_lo;
+
+      g_hi = f_hi;
+      g_lo = f_lo;
+
+      f_hi = e_hi;
+      f_lo = e_lo;
+
+      // e = (d + t1) modulo 2^64 (carry lo overflow)
+      lo = d_lo + t1_lo;
+      e_hi = (d_hi + t1_hi + ((lo / 0x100000000) >>> 0)) >>> 0;
+      e_lo = lo >>> 0;
+
+      d_hi = c_hi;
+      d_lo = c_lo;
+
+      c_hi = b_hi;
+      c_lo = b_lo;
+
+      b_hi = a_hi;
+      b_lo = a_lo;
+
+      // a = (t1 + t2) modulo 2^64 (carry lo overflow)
+      lo = t1_lo + t2_lo;
+      a_hi = (t1_hi + t2_hi + ((lo / 0x100000000) >>> 0)) >>> 0;
+      a_lo = lo >>> 0;
+    }
+
+    // update hash state (additional modulo 2^64)
+    lo = s[0][1] + a_lo;
+    s[0][0] = (s[0][0] + a_hi + ((lo / 0x100000000) >>> 0)) >>> 0;
+    s[0][1] = lo >>> 0;
+
+    lo = s[1][1] + b_lo;
+    s[1][0] = (s[1][0] + b_hi + ((lo / 0x100000000) >>> 0)) >>> 0;
+    s[1][1] = lo >>> 0;
+
+    lo = s[2][1] + c_lo;
+    s[2][0] = (s[2][0] + c_hi + ((lo / 0x100000000) >>> 0)) >>> 0;
+    s[2][1] = lo >>> 0;
+
+    lo = s[3][1] + d_lo;
+    s[3][0] = (s[3][0] + d_hi + ((lo / 0x100000000) >>> 0)) >>> 0;
+    s[3][1] = lo >>> 0;
+
+    lo = s[4][1] + e_lo;
+    s[4][0] = (s[4][0] + e_hi + ((lo / 0x100000000) >>> 0)) >>> 0;
+    s[4][1] = lo >>> 0;
+
+    lo = s[5][1] + f_lo;
+    s[5][0] = (s[5][0] + f_hi + ((lo / 0x100000000) >>> 0)) >>> 0;
+    s[5][1] = lo >>> 0;
+
+    lo = s[6][1] + g_lo;
+    s[6][0] = (s[6][0] + g_hi + ((lo / 0x100000000) >>> 0)) >>> 0;
+    s[6][1] = lo >>> 0;
+
+    lo = s[7][1] + h_lo;
+    s[7][0] = (s[7][0] + h_hi + ((lo / 0x100000000) >>> 0)) >>> 0;
+    s[7][1] = lo >>> 0;
+
+    len -= 128;
+  }
+}
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'sha512';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/socket.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/socket.js
new file mode 100644
index 0000000..e50e1aa
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/socket.js
@@ -0,0 +1,342 @@
+/**
+ * Socket implementation that uses flash SocketPool class as a backend.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2013 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+// define net namespace
+var net = forge.net = forge.net || {};
+
+// map of flash ID to socket pool
+net.socketPools = {};
+
+/**
+ * Creates a flash socket pool.
+ *
+ * @param options:
+ *          flashId: the dom ID for the flash object element.
+ *          policyPort: the default policy port for sockets, 0 to use the
+ *            flash default.
+ *          policyUrl: the default policy file URL for sockets (if provided
+ *            used instead of a policy port).
+ *          msie: true if the browser is msie, false if not.
+ *
+ * @return the created socket pool.
+ */
+net.createSocketPool = function(options) {
+  // set default
+  options.msie = options.msie || false;
+
+  // initialize the flash interface
+  var spId = options.flashId;
+  var api = document.getElementById(spId);
+  api.init({marshallExceptions: !options.msie});
+
+  // create socket pool entry
+  var sp = {
+    // ID of the socket pool
+    id: spId,
+    // flash interface
+    flashApi: api,
+    // map of socket ID to sockets
+    sockets: {},
+    // default policy port
+    policyPort: options.policyPort || 0,
+    // default policy URL
+    policyUrl: options.policyUrl || null
+  };
+  net.socketPools[spId] = sp;
+
+  // create event handler, subscribe to flash events
+  if(options.msie === true) {
+    sp.handler = function(e) {
+      if(e.id in sp.sockets) {
+        // get handler function
+        var f;
+        switch(e.type) {
+        case 'connect':
+          f = 'connected';
+          break;
+        case 'close':
+          f = 'closed';
+          break;
+        case 'socketData':
+          f = 'data';
+          break;
+        default:
+          f = 'error';
+          break;
+        }
+        /* IE calls javascript on the thread of the external object
+          that triggered the event (in this case flash) ... which will
+          either run concurrently with other javascript or pre-empt any
+          running javascript in the middle of its execution (BAD!) ...
+          calling setTimeout() will schedule the javascript to run on
+          the javascript thread and solve this EVIL problem. */
+        setTimeout(function(){sp.sockets[e.id][f](e);}, 0);
+      }
+    };
+  } else {
+    sp.handler = function(e) {
+      if(e.id in sp.sockets) {
+        // get handler function
+        var f;
+        switch(e.type) {
+        case 'connect':
+          f = 'connected';
+          break;
+        case 'close':
+          f = 'closed';
+          break;
+        case 'socketData':
+          f = 'data';
+          break;
+        default:
+          f = 'error';
+          break;
+        }
+        sp.sockets[e.id][f](e);
+      }
+    };
+  }
+  var handler = 'forge.net.socketPools[\'' + spId + '\'].handler';
+  api.subscribe('connect', handler);
+  api.subscribe('close', handler);
+  api.subscribe('socketData', handler);
+  api.subscribe('ioError', handler);
+  api.subscribe('securityError', handler);
+
+  /**
+   * Destroys a socket pool. The socket pool still needs to be cleaned
+   * up via net.cleanup().
+   */
+  sp.destroy = function() {
+    delete net.socketPools[options.flashId];
+    for(var id in sp.sockets) {
+      sp.sockets[id].destroy();
+    }
+    sp.sockets = {};
+    api.cleanup();
+  };
+
+  /**
+   * Creates a new socket.
+   *
+   * @param options:
+   *          connected: function(event) called when the socket connects.
+   *          closed: function(event) called when the socket closes.
+   *          data: function(event) called when socket data has arrived,
+   *            it can be read from the socket using receive().
+   *          error: function(event) called when a socket error occurs.
+   */
+   sp.createSocket = function(options) {
+     // default to empty options
+     options = options || {};
+
+     // create flash socket
+     var id = api.create();
+
+     // create javascript socket wrapper
+     var socket = {
+       id: id,
+       // set handlers
+       connected: options.connected || function(e){},
+       closed: options.closed || function(e){},
+       data: options.data || function(e){},
+       error: options.error || function(e){}
+     };
+
+     /**
+      * Destroys this socket.
+      */
+     socket.destroy = function() {
+       api.destroy(id);
+       delete sp.sockets[id];
+     };
+
+     /**
+      * Connects this socket.
+      *
+      * @param options:
+      *          host: the host to connect to.
+      *          port: the port to connect to.
+      *          policyPort: the policy port to use (if non-default), 0 to
+      *            use the flash default.
+      *          policyUrl: the policy file URL to use (instead of port).
+      */
+     socket.connect = function(options) {
+       // give precedence to policy URL over policy port
+       // if no policy URL and passed port isn't 0, use default port,
+       // otherwise use 0 for the port
+       var policyUrl = options.policyUrl || null;
+       var policyPort = 0;
+       if(policyUrl === null && options.policyPort !== 0) {
+         policyPort = options.policyPort || sp.policyPort;
+       }
+       api.connect(id, options.host, options.port, policyPort, policyUrl);
+     };
+
+     /**
+      * Closes this socket.
+      */
+     socket.close = function() {
+       api.close(id);
+       socket.closed({
+         id: socket.id,
+         type: 'close',
+         bytesAvailable: 0
+       });
+     };
+
+     /**
+      * Determines if the socket is connected or not.
+      *
+      * @return true if connected, false if not.
+      */
+     socket.isConnected = function() {
+       return api.isConnected(id);
+     };
+
+     /**
+      * Writes bytes to this socket.
+      *
+      * @param bytes the bytes (as a string) to write.
+      *
+      * @return true on success, false on failure.
+      */
+     socket.send = function(bytes) {
+       return api.send(id, forge.util.encode64(bytes));
+     };
+
+     /**
+      * Reads bytes from this socket (non-blocking). Fewer than the number
+      * of bytes requested may be read if enough bytes are not available.
+      *
+      * This method should be called from the data handler if there are
+      * enough bytes available. To see how many bytes are available, check
+      * the 'bytesAvailable' property on the event in the data handler or
+      * call the bytesAvailable() function on the socket. If the browser is
+      * msie, then the bytesAvailable() function should be used to avoid
+      * race conditions. Otherwise, using the property on the data handler's
+      * event may be quicker.
+      *
+      * @param count the maximum number of bytes to read.
+      *
+      * @return the bytes read (as a string) or null on error.
+      */
+     socket.receive = function(count) {
+       var rval = api.receive(id, count).rval;
+       return (rval === null) ? null : forge.util.decode64(rval);
+     };
+
+     /**
+      * Gets the number of bytes available for receiving on the socket.
+      *
+      * @return the number of bytes available for receiving.
+      */
+     socket.bytesAvailable = function() {
+       return api.getBytesAvailable(id);
+     };
+
+     // store and return socket
+     sp.sockets[id] = socket;
+     return socket;
+  };
+
+  return sp;
+};
+
+/**
+ * Destroys a flash socket pool.
+ *
+ * @param options:
+ *          flashId: the dom ID for the flash object element.
+ */
+net.destroySocketPool = function(options) {
+  if(options.flashId in net.socketPools) {
+    var sp = net.socketPools[options.flashId];
+    sp.destroy();
+  }
+};
+
+/**
+ * Creates a new socket.
+ *
+ * @param options:
+ *          flashId: the dom ID for the flash object element.
+ *          connected: function(event) called when the socket connects.
+ *          closed: function(event) called when the socket closes.
+ *          data: function(event) called when socket data has arrived, it
+ *            can be read from the socket using receive().
+ *          error: function(event) called when a socket error occurs.
+ *
+ * @return the created socket.
+ */
+net.createSocket = function(options) {
+  var socket = null;
+  if(options.flashId in net.socketPools) {
+    // get related socket pool
+    var sp = net.socketPools[options.flashId];
+    socket = sp.createSocket(options);
+  }
+  return socket;
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'net';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/ssh.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/ssh.js
new file mode 100644
index 0000000..ef76c82
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/ssh.js
@@ -0,0 +1,295 @@
+/**
+ * Functions to output keys in SSH-friendly formats.
+ *
+ * This is part of the Forge project which may be used under the terms of
+ * either the BSD License or the GNU General Public License (GPL) Version 2.
+ *
+ * See: https://github.com/digitalbazaar/forge/blob/cbebca3780658703d925b61b2caffb1d263a6c1d/LICENSE
+ *
+ * @author https://github.com/shellac
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+var ssh = forge.ssh = forge.ssh || {};
+
+/**
+ * Encodes (and optionally encrypts) a private RSA key as a Putty PPK file.
+ *
+ * @param privateKey the key.
+ * @param passphrase a passphrase to protect the key (falsy for no encryption).
+ * @param comment a comment to include in the key file.
+ *
+ * @return the PPK file as a string.
+ */
+ssh.privateKeyToPutty = function(privateKey, passphrase, comment) {
+  comment = comment || '';
+  passphrase = passphrase || '';
+  var algorithm = 'ssh-rsa';
+  var encryptionAlgorithm = (passphrase === '') ? 'none' : 'aes256-cbc';
+
+  var ppk = 'PuTTY-User-Key-File-2: ' + algorithm + '\r\n';
+  ppk += 'Encryption: ' + encryptionAlgorithm + '\r\n';
+  ppk += 'Comment: ' + comment + '\r\n';
+
+  // public key into buffer for ppk
+  var pubbuffer = forge.util.createBuffer();
+  _addStringToBuffer(pubbuffer, algorithm);
+  _addBigIntegerToBuffer(pubbuffer, privateKey.e);
+  _addBigIntegerToBuffer(pubbuffer, privateKey.n);
+
+  // write public key
+  var pub = forge.util.encode64(pubbuffer.bytes(), 64);
+  var length = Math.floor(pub.length / 66) + 1; // 66 = 64 + \r\n
+  ppk += 'Public-Lines: ' + length + '\r\n';
+  ppk += pub;
+
+  // private key into a buffer
+  var privbuffer = forge.util.createBuffer();
+  _addBigIntegerToBuffer(privbuffer, privateKey.d);
+  _addBigIntegerToBuffer(privbuffer, privateKey.p);
+  _addBigIntegerToBuffer(privbuffer, privateKey.q);
+  _addBigIntegerToBuffer(privbuffer, privateKey.qInv);
+
+  // optionally encrypt the private key
+  var priv;
+  if(!passphrase) {
+    // use the unencrypted buffer
+    priv = forge.util.encode64(privbuffer.bytes(), 64);
+  } else {
+    // encrypt RSA key using passphrase
+    var encLen = privbuffer.length() + 16 - 1;
+    encLen -= encLen % 16;
+
+    // pad private key with sha1-d data -- needs to be a multiple of 16
+    var padding = _sha1(privbuffer.bytes());
+
+    padding.truncate(padding.length() - encLen + privbuffer.length());
+    privbuffer.putBuffer(padding);
+
+    var aeskey = forge.util.createBuffer();
+    aeskey.putBuffer(_sha1('\x00\x00\x00\x00', passphrase));
+    aeskey.putBuffer(_sha1('\x00\x00\x00\x01', passphrase));
+
+    // encrypt some bytes using CBC mode
+    // key is 40 bytes, so truncate *by* 8 bytes
+    var cipher = forge.aes.createEncryptionCipher(aeskey.truncate(8), 'CBC');
+    cipher.start(forge.util.createBuffer().fillWithByte(0, 16));
+    cipher.update(privbuffer.copy());
+    cipher.finish();
+    var encrypted = cipher.output;
+
+    // Note: this appears to differ from Putty -- is forge wrong, or putty?
+    // due to padding we finish as an exact multiple of 16
+    encrypted.truncate(16); // all padding
+
+    priv = forge.util.encode64(encrypted.bytes(), 64);
+  }
+
+  // output private key
+  length = Math.floor(priv.length / 66) + 1; // 64 + \r\n
+  ppk += '\r\nPrivate-Lines: ' + length + '\r\n';
+  ppk += priv;
+
+  // MAC
+  var mackey = _sha1('putty-private-key-file-mac-key', passphrase);
+
+  var macbuffer = forge.util.createBuffer();
+  _addStringToBuffer(macbuffer, algorithm);
+  _addStringToBuffer(macbuffer, encryptionAlgorithm);
+  _addStringToBuffer(macbuffer, comment);
+  macbuffer.putInt32(pubbuffer.length());
+  macbuffer.putBuffer(pubbuffer);
+  macbuffer.putInt32(privbuffer.length());
+  macbuffer.putBuffer(privbuffer);
+
+  var hmac = forge.hmac.create();
+  hmac.start('sha1', mackey);
+  hmac.update(macbuffer.bytes());
+
+  ppk += '\r\nPrivate-MAC: ' + hmac.digest().toHex() + '\r\n';
+
+  return ppk;
+};
+
+/**
+ * Encodes a public RSA key as an OpenSSH file.
+ *
+ * @param key the key.
+ * @param comment a comment.
+ *
+ * @return the public key in OpenSSH format.
+ */
+ssh.publicKeyToOpenSSH = function(key, comment) {
+  var type = 'ssh-rsa';
+  comment = comment || '';
+
+  var buffer = forge.util.createBuffer();
+  _addStringToBuffer(buffer, type);
+  _addBigIntegerToBuffer(buffer, key.e);
+  _addBigIntegerToBuffer(buffer, key.n);
+
+  return type + ' ' + forge.util.encode64(buffer.bytes()) + ' ' + comment;
+};
+
+/**
+ * Encodes a private RSA key as an OpenSSH file.
+ *
+ * @param key the key.
+ * @param passphrase a passphrase to protect the key (falsy for no encryption).
+ *
+ * @return the public key in OpenSSH format.
+ */
+ssh.privateKeyToOpenSSH = function(privateKey, passphrase) {
+  if(!passphrase) {
+    return forge.pki.privateKeyToPem(privateKey);
+  }
+  // OpenSSH private key is just a legacy format, it seems
+  return forge.pki.encryptRsaPrivateKey(privateKey, passphrase,
+    {legacy: true, algorithm: 'aes128'});
+};
+
+/**
+ * Gets the SSH fingerprint for the given public key.
+ *
+ * @param options the options to use.
+ *          [md] the message digest object to use (defaults to forge.md.md5).
+ *          [encoding] an alternative output encoding, such as 'hex'
+ *            (defaults to none, outputs a byte buffer).
+ *          [delimiter] the delimiter to use between bytes for 'hex' encoded
+ *            output, eg: ':' (defaults to none).
+ *
+ * @return the fingerprint as a byte buffer or other encoding based on options.
+ */
+ssh.getPublicKeyFingerprint = function(key, options) {
+  options = options || {};
+  var md = options.md || forge.md.md5.create();
+
+  var type = 'ssh-rsa';
+  var buffer = forge.util.createBuffer();
+  _addStringToBuffer(buffer, type);
+  _addBigIntegerToBuffer(buffer, key.e);
+  _addBigIntegerToBuffer(buffer, key.n);
+
+  // hash public key bytes
+  md.start();
+  md.update(buffer.getBytes());
+  var digest = md.digest();
+  if(options.encoding === 'hex') {
+    var hex = digest.toHex();
+    if(options.delimiter) {
+      return hex.match(/.{2}/g).join(options.delimiter);
+    }
+    return hex;
+  } else if(options.encoding === 'binary') {
+    return digest.getBytes();
+  } else if(options.encoding) {
+    throw new Error('Unknown encoding "' + options.encoding + '".');
+  }
+  return digest;
+};
+
+/**
+ * Adds len(val) then val to a buffer.
+ *
+ * @param buffer the buffer to add to.
+ * @param val a big integer.
+ */
+function _addBigIntegerToBuffer(buffer, val) {
+  var hexVal = val.toString(16);
+  // ensure 2s complement +ve
+  if(hexVal[0] >= '8') {
+    hexVal = '00' + hexVal;
+  }
+  var bytes = forge.util.hexToBytes(hexVal);
+  buffer.putInt32(bytes.length);
+  buffer.putBytes(bytes);
+}
+
+/**
+ * Adds len(val) then val to a buffer.
+ *
+ * @param buffer the buffer to add to.
+ * @param val a string.
+ */
+function _addStringToBuffer(buffer, val) {
+  buffer.putInt32(val.length);
+  buffer.putString(val);
+}
+
+/**
+ * Hashes the arguments into one value using SHA-1.
+ *
+ * @return the sha1 hash of the provided arguments.
+ */
+function _sha1() {
+  var sha = forge.md.sha1.create();
+  var num = arguments.length;
+  for (var i = 0; i < num; ++i) {
+    sha.update(arguments[i]);
+  }
+  return sha.digest();
+}
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'ssh';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define([
+  'require',
+  'module',
+  './aes',
+  './hmac',
+  './md5',
+  './sha1',
+  './util'
+], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/task.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/task.js
new file mode 100644
index 0000000..f49bbf7
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/task.js
@@ -0,0 +1,778 @@
+/**
+ * Support for concurrent task management and synchronization in web
+ * applications.
+ *
+ * @author Dave Longley
+ * @author David I. Lehn <dlehn@digitalbazaar.com>
+ *
+ * Copyright (c) 2009-2013 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+// logging category
+var cat = 'forge.task';
+
+// verbose level
+// 0: off, 1: a little, 2: a whole lot
+// Verbose debug logging is surrounded by a level check to avoid the
+// performance issues with even calling the logging code regardless if it
+// is actually logged.  For performance reasons this should not be set to 2
+// for production use.
+// ex: if(sVL >= 2) forge.log.verbose(....)
+var sVL = 0;
+
+// track tasks for debugging
+var sTasks = {};
+var sNextTaskId = 0;
+// debug access
+forge.debug.set(cat, 'tasks', sTasks);
+
+// a map of task type to task queue
+var sTaskQueues = {};
+// debug access
+forge.debug.set(cat, 'queues', sTaskQueues);
+
+// name for unnamed tasks
+var sNoTaskName = '?';
+
+// maximum number of doNext() recursions before a context swap occurs
+// FIXME: might need to tweak this based on the browser
+var sMaxRecursions = 30;
+
+// time slice for doing tasks before a context swap occurs
+// FIXME: might need to tweak this based on the browser
+var sTimeSlice = 20;
+
+/**
+ * Task states.
+ *
+ * READY: ready to start processing
+ * RUNNING: task or a subtask is running
+ * BLOCKED: task is waiting to acquire N permits to continue
+ * SLEEPING: task is sleeping for a period of time
+ * DONE: task is done
+ * ERROR: task has an error
+ */
+var READY = 'ready';
+var RUNNING = 'running';
+var BLOCKED = 'blocked';
+var SLEEPING = 'sleeping';
+var DONE = 'done';
+var ERROR = 'error';
+
+/**
+ * Task actions.  Used to control state transitions.
+ *
+ * STOP: stop processing
+ * START: start processing tasks
+ * BLOCK: block task from continuing until 1 or more permits are released
+ * UNBLOCK: release one or more permits
+ * SLEEP: sleep for a period of time
+ * WAKEUP: wakeup early from SLEEPING state
+ * CANCEL: cancel further tasks
+ * FAIL: a failure occured
+ */
+var STOP = 'stop';
+var START = 'start';
+var BLOCK = 'block';
+var UNBLOCK = 'unblock';
+var SLEEP = 'sleep';
+var WAKEUP = 'wakeup';
+var CANCEL = 'cancel';
+var FAIL = 'fail';
+
+/**
+ * State transition table.
+ *
+ * nextState = sStateTable[currentState][action]
+ */
+var sStateTable = {};
+
+sStateTable[READY] = {};
+sStateTable[READY][STOP] = READY;
+sStateTable[READY][START] = RUNNING;
+sStateTable[READY][CANCEL] = DONE;
+sStateTable[READY][FAIL] = ERROR;
+
+sStateTable[RUNNING] = {};
+sStateTable[RUNNING][STOP] = READY;
+sStateTable[RUNNING][START] = RUNNING;
+sStateTable[RUNNING][BLOCK] = BLOCKED;
+sStateTable[RUNNING][UNBLOCK] = RUNNING;
+sStateTable[RUNNING][SLEEP] = SLEEPING;
+sStateTable[RUNNING][WAKEUP] = RUNNING;
+sStateTable[RUNNING][CANCEL] = DONE;
+sStateTable[RUNNING][FAIL] = ERROR;
+
+sStateTable[BLOCKED] = {};
+sStateTable[BLOCKED][STOP] = BLOCKED;
+sStateTable[BLOCKED][START] = BLOCKED;
+sStateTable[BLOCKED][BLOCK] = BLOCKED;
+sStateTable[BLOCKED][UNBLOCK] = BLOCKED;
+sStateTable[BLOCKED][SLEEP] = BLOCKED;
+sStateTable[BLOCKED][WAKEUP] = BLOCKED;
+sStateTable[BLOCKED][CANCEL] = DONE;
+sStateTable[BLOCKED][FAIL] = ERROR;
+
+sStateTable[SLEEPING] = {};
+sStateTable[SLEEPING][STOP] = SLEEPING;
+sStateTable[SLEEPING][START] = SLEEPING;
+sStateTable[SLEEPING][BLOCK] = SLEEPING;
+sStateTable[SLEEPING][UNBLOCK] = SLEEPING;
+sStateTable[SLEEPING][SLEEP] = SLEEPING;
+sStateTable[SLEEPING][WAKEUP] = SLEEPING;
+sStateTable[SLEEPING][CANCEL] = DONE;
+sStateTable[SLEEPING][FAIL] = ERROR;
+
+sStateTable[DONE] = {};
+sStateTable[DONE][STOP] = DONE;
+sStateTable[DONE][START] = DONE;
+sStateTable[DONE][BLOCK] = DONE;
+sStateTable[DONE][UNBLOCK] = DONE;
+sStateTable[DONE][SLEEP] = DONE;
+sStateTable[DONE][WAKEUP] = DONE;
+sStateTable[DONE][CANCEL] = DONE;
+sStateTable[DONE][FAIL] = ERROR;
+
+sStateTable[ERROR] = {};
+sStateTable[ERROR][STOP] = ERROR;
+sStateTable[ERROR][START] = ERROR;
+sStateTable[ERROR][BLOCK] = ERROR;
+sStateTable[ERROR][UNBLOCK] = ERROR;
+sStateTable[ERROR][SLEEP] = ERROR;
+sStateTable[ERROR][WAKEUP] = ERROR;
+sStateTable[ERROR][CANCEL] = ERROR;
+sStateTable[ERROR][FAIL] = ERROR;
+
+/**
+ * Creates a new task.
+ *
+ * @param options options for this task
+ *   run: the run function for the task (required)
+ *   name: the run function for the task (optional)
+ *   parent: parent of this task (optional)
+ *
+ * @return the empty task.
+ */
+var Task = function(options) {
+  // task id
+  this.id = -1;
+
+  // task name
+  this.name = options.name || sNoTaskName;
+
+  // task has no parent
+  this.parent = options.parent || null;
+
+  // save run function
+  this.run = options.run;
+
+  // create a queue of subtasks to run
+  this.subtasks = [];
+
+  // error flag
+  this.error = false;
+
+  // state of the task
+  this.state = READY;
+
+  // number of times the task has been blocked (also the number
+  // of permits needed to be released to continue running)
+  this.blocks = 0;
+
+  // timeout id when sleeping
+  this.timeoutId = null;
+
+  // no swap time yet
+  this.swapTime = null;
+
+  // no user data
+  this.userData = null;
+
+  // initialize task
+  // FIXME: deal with overflow
+  this.id = sNextTaskId++;
+  sTasks[this.id] = this;
+  if(sVL >= 1) {
+    forge.log.verbose(cat, '[%s][%s] init', this.id, this.name, this);
+  }
+};
+
+/**
+ * Logs debug information on this task and the system state.
+ */
+Task.prototype.debug = function(msg) {
+  msg = msg || '';
+  forge.log.debug(cat, msg,
+    '[%s][%s] task:', this.id, this.name, this,
+    'subtasks:', this.subtasks.length,
+    'queue:', sTaskQueues);
+};
+
+/**
+ * Adds a subtask to run after task.doNext() or task.fail() is called.
+ *
+ * @param name human readable name for this task (optional).
+ * @param subrun a function to run that takes the current task as
+ *          its first parameter.
+ *
+ * @return the current task (useful for chaining next() calls).
+ */
+Task.prototype.next = function(name, subrun) {
+  // juggle parameters if it looks like no name is given
+  if(typeof(name) === 'function') {
+    subrun = name;
+
+    // inherit parent's name
+    name = this.name;
+  }
+  // create subtask, set parent to this task, propagate callbacks
+  var subtask = new Task({
+    run: subrun,
+    name: name,
+    parent: this
+  });
+  // start subtasks running
+  subtask.state = RUNNING;
+  subtask.type = this.type;
+  subtask.successCallback = this.successCallback || null;
+  subtask.failureCallback = this.failureCallback || null;
+
+  // queue a new subtask
+  this.subtasks.push(subtask);
+
+  return this;
+};
+
+/**
+ * Adds subtasks to run in parallel after task.doNext() or task.fail()
+ * is called.
+ *
+ * @param name human readable name for this task (optional).
+ * @param subrun functions to run that take the current task as
+ *          their first parameter.
+ *
+ * @return the current task (useful for chaining next() calls).
+ */
+Task.prototype.parallel = function(name, subrun) {
+  // juggle parameters if it looks like no name is given
+  if(forge.util.isArray(name)) {
+    subrun = name;
+
+    // inherit parent's name
+    name = this.name;
+  }
+  // Wrap parallel tasks in a regular task so they are started at the
+  // proper time.
+  return this.next(name, function(task) {
+    // block waiting for subtasks
+    var ptask = task;
+    ptask.block(subrun.length);
+
+    // we pass the iterator from the loop below as a parameter
+    // to a function because it is otherwise included in the
+    // closure and changes as the loop changes -- causing i
+    // to always be set to its highest value
+    var startParallelTask = function(pname, pi) {
+      forge.task.start({
+        type: pname,
+        run: function(task) {
+           subrun[pi](task);
+        },
+        success: function(task) {
+           ptask.unblock();
+        },
+        failure: function(task) {
+           ptask.unblock();
+        }
+      });
+    };
+
+    for(var i = 0; i < subrun.length; i++) {
+      // Type must be unique so task starts in parallel:
+      //    name + private string + task id + sub-task index
+      // start tasks in parallel and unblock when the finish
+      var pname = name + '__parallel-' + task.id + '-' + i;
+      var pi = i;
+      startParallelTask(pname, pi);
+    }
+  });
+};
+
+/**
+ * Stops a running task.
+ */
+Task.prototype.stop = function() {
+  this.state = sStateTable[this.state][STOP];
+};
+
+/**
+ * Starts running a task.
+ */
+Task.prototype.start = function() {
+  this.error = false;
+  this.state = sStateTable[this.state][START];
+
+  // try to restart
+  if(this.state === RUNNING) {
+    this.start = new Date();
+    this.run(this);
+    runNext(this, 0);
+  }
+};
+
+/**
+ * Blocks a task until it one or more permits have been released. The
+ * task will not resume until the requested number of permits have
+ * been released with call(s) to unblock().
+ *
+ * @param n number of permits to wait for(default: 1).
+ */
+Task.prototype.block = function(n) {
+  n = typeof(n) === 'undefined' ? 1 : n;
+  this.blocks += n;
+  if(this.blocks > 0) {
+    this.state = sStateTable[this.state][BLOCK];
+  }
+};
+
+/**
+ * Releases a permit to unblock a task. If a task was blocked by
+ * requesting N permits via block(), then it will only continue
+ * running once enough permits have been released via unblock() calls.
+ *
+ * If multiple processes need to synchronize with a single task then
+ * use a condition variable (see forge.task.createCondition). It is
+ * an error to unblock a task more times than it has been blocked.
+ *
+ * @param n number of permits to release (default: 1).
+ *
+ * @return the current block count (task is unblocked when count is 0)
+ */
+Task.prototype.unblock = function(n) {
+  n = typeof(n) === 'undefined' ? 1 : n;
+  this.blocks -= n;
+  if(this.blocks === 0 && this.state !== DONE) {
+    this.state = RUNNING;
+    runNext(this, 0);
+  }
+  return this.blocks;
+};
+
+/**
+ * Sleep for a period of time before resuming tasks.
+ *
+ * @param n number of milliseconds to sleep (default: 0).
+ */
+Task.prototype.sleep = function(n) {
+  n = typeof(n) === 'undefined' ? 0 : n;
+  this.state = sStateTable[this.state][SLEEP];
+  var self = this;
+  this.timeoutId = setTimeout(function() {
+    self.timeoutId = null;
+    self.state = RUNNING;
+    runNext(self, 0);
+  }, n);
+};
+
+/**
+ * Waits on a condition variable until notified. The next task will
+ * not be scheduled until notification. A condition variable can be
+ * created with forge.task.createCondition().
+ *
+ * Once cond.notify() is called, the task will continue.
+ *
+ * @param cond the condition variable to wait on.
+ */
+Task.prototype.wait = function(cond) {
+  cond.wait(this);
+};
+
+/**
+ * If sleeping, wakeup and continue running tasks.
+ */
+Task.prototype.wakeup = function() {
+  if(this.state === SLEEPING) {
+    cancelTimeout(this.timeoutId);
+    this.timeoutId = null;
+    this.state = RUNNING;
+    runNext(this, 0);
+  }
+};
+
+/**
+ * Cancel all remaining subtasks of this task.
+ */
+Task.prototype.cancel = function() {
+  this.state = sStateTable[this.state][CANCEL];
+  // remove permits needed
+  this.permitsNeeded = 0;
+  // cancel timeouts
+  if(this.timeoutId !== null) {
+    cancelTimeout(this.timeoutId);
+    this.timeoutId = null;
+  }
+  // remove subtasks
+  this.subtasks = [];
+};
+
+/**
+ * Finishes this task with failure and sets error flag. The entire
+ * task will be aborted unless the next task that should execute
+ * is passed as a parameter. This allows levels of subtasks to be
+ * skipped. For instance, to abort only this tasks's subtasks, then
+ * call fail(task.parent). To abort this task's subtasks and its
+ * parent's subtasks, call fail(task.parent.parent). To abort
+ * all tasks and simply call the task callback, call fail() or
+ * fail(null).
+ *
+ * The task callback (success or failure) will always, eventually, be
+ * called.
+ *
+ * @param next the task to continue at, or null to abort entirely.
+ */
+Task.prototype.fail = function(next) {
+  // set error flag
+  this.error = true;
+
+  // finish task
+  finish(this, true);
+
+  if(next) {
+    // propagate task info
+    next.error = this.error;
+    next.swapTime = this.swapTime;
+    next.userData = this.userData;
+
+    // do next task as specified
+    runNext(next, 0);
+  } else {
+    if(this.parent !== null) {
+      // finish root task (ensures it is removed from task queue)
+      var parent = this.parent;
+      while(parent.parent !== null) {
+        // propagate task info
+        parent.error = this.error;
+        parent.swapTime = this.swapTime;
+        parent.userData = this.userData;
+        parent = parent.parent;
+      }
+      finish(parent, true);
+    }
+
+    // call failure callback if one exists
+    if(this.failureCallback) {
+      this.failureCallback(this);
+    }
+  }
+};
+
+/**
+ * Asynchronously start a task.
+ *
+ * @param task the task to start.
+ */
+var start = function(task) {
+  task.error = false;
+  task.state = sStateTable[task.state][START];
+  setTimeout(function() {
+    if(task.state === RUNNING) {
+      task.swapTime = +new Date();
+      task.run(task);
+      runNext(task, 0);
+    }
+  }, 0);
+};
+
+/**
+ * Run the next subtask or finish this task.
+ *
+ * @param task the task to process.
+ * @param recurse the recursion count.
+ */
+var runNext = function(task, recurse) {
+  // get time since last context swap (ms), if enough time has passed set
+  // swap to true to indicate that doNext was performed asynchronously
+  // also, if recurse is too high do asynchronously
+  var swap =
+    (recurse > sMaxRecursions) ||
+    (+new Date() - task.swapTime) > sTimeSlice;
+
+  var doNext = function(recurse) {
+    recurse++;
+    if(task.state === RUNNING) {
+      if(swap) {
+        // update swap time
+        task.swapTime = +new Date();
+      }
+
+      if(task.subtasks.length > 0) {
+        // run next subtask
+        var subtask = task.subtasks.shift();
+        subtask.error = task.error;
+        subtask.swapTime = task.swapTime;
+        subtask.userData = task.userData;
+        subtask.run(subtask);
+        if(!subtask.error) {
+           runNext(subtask, recurse);
+        }
+      } else {
+        finish(task);
+
+        if(!task.error) {
+          // chain back up and run parent
+          if(task.parent !== null) {
+            // propagate task info
+            task.parent.error = task.error;
+            task.parent.swapTime = task.swapTime;
+            task.parent.userData = task.userData;
+
+            // no subtasks left, call run next subtask on parent
+            runNext(task.parent, recurse);
+          }
+        }
+      }
+    }
+  };
+
+  if(swap) {
+    // we're swapping, so run asynchronously
+    setTimeout(doNext, 0);
+  } else {
+    // not swapping, so run synchronously
+    doNext(recurse);
+  }
+};
+
+/**
+ * Finishes a task and looks for the next task in the queue to start.
+ *
+ * @param task the task to finish.
+ * @param suppressCallbacks true to suppress callbacks.
+ */
+var finish = function(task, suppressCallbacks) {
+  // subtask is now done
+  task.state = DONE;
+
+  delete sTasks[task.id];
+  if(sVL >= 1) {
+    forge.log.verbose(cat, '[%s][%s] finish',
+      task.id, task.name, task);
+  }
+
+  // only do queue processing for root tasks
+  if(task.parent === null) {
+    // report error if queue is missing
+    if(!(task.type in sTaskQueues)) {
+      forge.log.error(cat,
+        '[%s][%s] task queue missing [%s]',
+        task.id, task.name, task.type);
+    } else if(sTaskQueues[task.type].length === 0) {
+      // report error if queue is empty
+      forge.log.error(cat,
+        '[%s][%s] task queue empty [%s]',
+        task.id, task.name, task.type);
+    } else if(sTaskQueues[task.type][0] !== task) {
+      // report error if this task isn't the first in the queue
+      forge.log.error(cat,
+        '[%s][%s] task not first in queue [%s]',
+        task.id, task.name, task.type);
+    } else {
+      // remove ourselves from the queue
+      sTaskQueues[task.type].shift();
+      // clean up queue if it is empty
+      if(sTaskQueues[task.type].length === 0) {
+        if(sVL >= 1) {
+          forge.log.verbose(cat, '[%s][%s] delete queue [%s]',
+            task.id, task.name, task.type);
+        }
+        /* Note: Only a task can delete a queue of its own type. This
+         is used as a way to synchronize tasks. If a queue for a certain
+         task type exists, then a task of that type is running.
+         */
+        delete sTaskQueues[task.type];
+      } else {
+        // dequeue the next task and start it
+        if(sVL >= 1) {
+          forge.log.verbose(cat,
+            '[%s][%s] queue start next [%s] remain:%s',
+            task.id, task.name, task.type,
+            sTaskQueues[task.type].length);
+        }
+        sTaskQueues[task.type][0].start();
+      }
+    }
+
+    if(!suppressCallbacks) {
+      // call final callback if one exists
+      if(task.error && task.failureCallback) {
+        task.failureCallback(task);
+      } else if(!task.error && task.successCallback) {
+        task.successCallback(task);
+      }
+    }
+  }
+};
+
+/* Tasks API */
+forge.task = forge.task || {};
+
+/**
+ * Starts a new task that will run the passed function asynchronously.
+ *
+ * In order to finish the task, either task.doNext() or task.fail()
+ * *must* be called.
+ *
+ * The task must have a type (a string identifier) that can be used to
+ * synchronize it with other tasks of the same type. That type can also
+ * be used to cancel tasks that haven't started yet.
+ *
+ * To start a task, the following object must be provided as a parameter
+ * (each function takes a task object as its first parameter):
+ *
+ * {
+ *   type: the type of task.
+ *   run: the function to run to execute the task.
+ *   success: a callback to call when the task succeeds (optional).
+ *   failure: a callback to call when the task fails (optional).
+ * }
+ *
+ * @param options the object as described above.
+ */
+forge.task.start = function(options) {
+  // create a new task
+  var task = new Task({
+    run: options.run,
+    name: options.name || sNoTaskName
+  });
+  task.type = options.type;
+  task.successCallback = options.success || null;
+  task.failureCallback = options.failure || null;
+
+  // append the task onto the appropriate queue
+  if(!(task.type in sTaskQueues)) {
+    if(sVL >= 1) {
+      forge.log.verbose(cat, '[%s][%s] create queue [%s]',
+        task.id, task.name, task.type);
+    }
+    // create the queue with the new task
+    sTaskQueues[task.type] = [task];
+    start(task);
+  } else {
+    // push the task onto the queue, it will be run after a task
+    // with the same type completes
+    sTaskQueues[options.type].push(task);
+  }
+};
+
+/**
+ * Cancels all tasks of the given type that haven't started yet.
+ *
+ * @param type the type of task to cancel.
+ */
+forge.task.cancel = function(type) {
+  // find the task queue
+  if(type in sTaskQueues) {
+    // empty all but the current task from the queue
+    sTaskQueues[type] = [sTaskQueues[type][0]];
+  }
+};
+
+/**
+ * Creates a condition variable to synchronize tasks. To make a task wait
+ * on the condition variable, call task.wait(condition). To notify all
+ * tasks that are waiting, call condition.notify().
+ *
+ * @return the condition variable.
+ */
+forge.task.createCondition = function() {
+  var cond = {
+    // all tasks that are blocked
+    tasks: {}
+  };
+
+  /**
+   * Causes the given task to block until notify is called. If the task
+   * is already waiting on this condition then this is a no-op.
+   *
+   * @param task the task to cause to wait.
+   */
+  cond.wait = function(task) {
+    // only block once
+    if(!(task.id in cond.tasks)) {
+       task.block();
+       cond.tasks[task.id] = task;
+    }
+  };
+
+  /**
+   * Notifies all waiting tasks to wake up.
+   */
+  cond.notify = function() {
+    // since unblock() will run the next task from here, make sure to
+    // clear the condition's blocked task list before unblocking
+    var tmp = cond.tasks;
+    cond.tasks = {};
+    for(var id in tmp) {
+      tmp[id].unblock();
+    }
+  };
+
+  return cond;
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'task';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './debug', './log', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/tls.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/tls.js
new file mode 100644
index 0000000..b3bb2e8
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/tls.js
@@ -0,0 +1,4316 @@
+/**
+ * A Javascript implementation of Transport Layer Security (TLS).
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2009-2014 Digital Bazaar, Inc.
+ *
+ * The TLS Handshake Protocol involves the following steps:
+ *
+ * - Exchange hello messages to agree on algorithms, exchange random values,
+ * and check for session resumption.
+ *
+ * - Exchange the necessary cryptographic parameters to allow the client and
+ * server to agree on a premaster secret.
+ *
+ * - Exchange certificates and cryptographic information to allow the client
+ * and server to authenticate themselves.
+ *
+ * - Generate a master secret from the premaster secret and exchanged random
+ * values.
+ *
+ * - Provide security parameters to the record layer.
+ *
+ * - Allow the client and server to verify that their peer has calculated the
+ * same security parameters and that the handshake occurred without tampering
+ * by an attacker.
+ *
+ * Up to 4 different messages may be sent during a key exchange. The server
+ * certificate, the server key exchange, the client certificate, and the
+ * client key exchange.
+ *
+ * A typical handshake (from the client's perspective).
+ *
+ * 1. Client sends ClientHello.
+ * 2. Client receives ServerHello.
+ * 3. Client receives optional Certificate.
+ * 4. Client receives optional ServerKeyExchange.
+ * 5. Client receives ServerHelloDone.
+ * 6. Client sends optional Certificate.
+ * 7. Client sends ClientKeyExchange.
+ * 8. Client sends optional CertificateVerify.
+ * 9. Client sends ChangeCipherSpec.
+ * 10. Client sends Finished.
+ * 11. Client receives ChangeCipherSpec.
+ * 12. Client receives Finished.
+ * 13. Client sends/receives application data.
+ *
+ * To reuse an existing session:
+ *
+ * 1. Client sends ClientHello with session ID for reuse.
+ * 2. Client receives ServerHello with same session ID if reusing.
+ * 3. Client receives ChangeCipherSpec message if reusing.
+ * 4. Client receives Finished.
+ * 5. Client sends ChangeCipherSpec.
+ * 6. Client sends Finished.
+ *
+ * Note: Client ignores HelloRequest if in the middle of a handshake.
+ *
+ * Record Layer:
+ *
+ * The record layer fragments information blocks into TLSPlaintext records
+ * carrying data in chunks of 2^14 bytes or less. Client message boundaries are
+ * not preserved in the record layer (i.e., multiple client messages of the
+ * same ContentType MAY be coalesced into a single TLSPlaintext record, or a
+ * single message MAY be fragmented across several records).
+ *
+ * struct {
+ *   uint8 major;
+ *   uint8 minor;
+ * } ProtocolVersion;
+ *
+ * struct {
+ *   ContentType type;
+ *   ProtocolVersion version;
+ *   uint16 length;
+ *   opaque fragment[TLSPlaintext.length];
+ * } TLSPlaintext;
+ *
+ * type:
+ *   The higher-level protocol used to process the enclosed fragment.
+ *
+ * version:
+ *   The version of the protocol being employed. TLS Version 1.2 uses version
+ *   {3, 3}. TLS Version 1.0 uses version {3, 1}. Note that a client that
+ *   supports multiple versions of TLS may not know what version will be
+ *   employed before it receives the ServerHello.
+ *
+ * length:
+ *   The length (in bytes) of the following TLSPlaintext.fragment. The length
+ *   MUST NOT exceed 2^14 = 16384 bytes.
+ *
+ * fragment:
+ *   The application data. This data is transparent and treated as an
+ *   independent block to be dealt with by the higher-level protocol specified
+ *   by the type field.
+ *
+ * Implementations MUST NOT send zero-length fragments of Handshake, Alert, or
+ * ChangeCipherSpec content types. Zero-length fragments of Application data
+ * MAY be sent as they are potentially useful as a traffic analysis
+ * countermeasure.
+ *
+ * Note: Data of different TLS record layer content types MAY be interleaved.
+ * Application data is generally of lower precedence for transmission than
+ * other content types. However, records MUST be delivered to the network in
+ * the same order as they are protected by the record layer. Recipients MUST
+ * receive and process interleaved application layer traffic during handshakes
+ * subsequent to the first one on a connection.
+ *
+ * struct {
+ *   ContentType type;       // same as TLSPlaintext.type
+ *   ProtocolVersion version;// same as TLSPlaintext.version
+ *   uint16 length;
+ *   opaque fragment[TLSCompressed.length];
+ * } TLSCompressed;
+ *
+ * length:
+ *   The length (in bytes) of the following TLSCompressed.fragment.
+ *   The length MUST NOT exceed 2^14 + 1024.
+ *
+ * fragment:
+ *   The compressed form of TLSPlaintext.fragment.
+ *
+ * Note: A CompressionMethod.null operation is an identity operation; no fields
+ * are altered. In this implementation, since no compression is supported,
+ * uncompressed records are always the same as compressed records.
+ *
+ * Encryption Information:
+ *
+ * The encryption and MAC functions translate a TLSCompressed structure into a
+ * TLSCiphertext. The decryption functions reverse the process. The MAC of the
+ * record also includes a sequence number so that missing, extra, or repeated
+ * messages are detectable.
+ *
+ * struct {
+ *   ContentType type;
+ *   ProtocolVersion version;
+ *   uint16 length;
+ *   select (SecurityParameters.cipher_type) {
+ *     case stream: GenericStreamCipher;
+ *     case block:  GenericBlockCipher;
+ *     case aead:   GenericAEADCipher;
+ *   } fragment;
+ * } TLSCiphertext;
+ *
+ * type:
+ *   The type field is identical to TLSCompressed.type.
+ *
+ * version:
+ *   The version field is identical to TLSCompressed.version.
+ *
+ * length:
+ *   The length (in bytes) of the following TLSCiphertext.fragment.
+ *   The length MUST NOT exceed 2^14 + 2048.
+ *
+ * fragment:
+ *   The encrypted form of TLSCompressed.fragment, with the MAC.
+ *
+ * Note: Only CBC Block Ciphers are supported by this implementation.
+ *
+ * The TLSCompressed.fragment structures are converted to/from block
+ * TLSCiphertext.fragment structures.
+ *
+ * struct {
+ *   opaque IV[SecurityParameters.record_iv_length];
+ *   block-ciphered struct {
+ *     opaque content[TLSCompressed.length];
+ *     opaque MAC[SecurityParameters.mac_length];
+ *     uint8 padding[GenericBlockCipher.padding_length];
+ *     uint8 padding_length;
+ *   };
+ * } GenericBlockCipher;
+ *
+ * The MAC is generated as described in Section 6.2.3.1.
+ *
+ * IV:
+ *   The Initialization Vector (IV) SHOULD be chosen at random, and MUST be
+ *   unpredictable. Note that in versions of TLS prior to 1.1, there was no
+ *   IV field, and the last ciphertext block of the previous record (the "CBC
+ *   residue") was used as the IV. This was changed to prevent the attacks
+ *   described in [CBCATT]. For block ciphers, the IV length is of length
+ *   SecurityParameters.record_iv_length, which is equal to the
+ *   SecurityParameters.block_size.
+ *
+ * padding:
+ *   Padding that is added to force the length of the plaintext to be an
+ *   integral multiple of the block cipher's block length. The padding MAY be
+ *   any length up to 255 bytes, as long as it results in the
+ *   TLSCiphertext.length being an integral multiple of the block length.
+ *   Lengths longer than necessary might be desirable to frustrate attacks on
+ *   a protocol that are based on analysis of the lengths of exchanged
+ *   messages. Each uint8 in the padding data vector MUST be filled with the
+ *   padding length value. The receiver MUST check this padding and MUST use
+ *   the bad_record_mac alert to indicate padding errors.
+ *
+ * padding_length:
+ *   The padding length MUST be such that the total size of the
+ *   GenericBlockCipher structure is a multiple of the cipher's block length.
+ *   Legal values range from zero to 255, inclusive. This length specifies the
+ *   length of the padding field exclusive of the padding_length field itself.
+ *
+ * The encrypted data length (TLSCiphertext.length) is one more than the sum of
+ * SecurityParameters.block_length, TLSCompressed.length,
+ * SecurityParameters.mac_length, and padding_length.
+ *
+ * Example: If the block length is 8 bytes, the content length
+ * (TLSCompressed.length) is 61 bytes, and the MAC length is 20 bytes, then the
+ * length before padding is 82 bytes (this does not include the IV. Thus, the
+ * padding length modulo 8 must be equal to 6 in order to make the total length
+ * an even multiple of 8 bytes (the block length). The padding length can be
+ * 6, 14, 22, and so on, through 254. If the padding length were the minimum
+ * necessary, 6, the padding would be 6 bytes, each containing the value 6.
+ * Thus, the last 8 octets of the GenericBlockCipher before block encryption
+ * would be xx 06 06 06 06 06 06 06, where xx is the last octet of the MAC.
+ *
+ * Note: With block ciphers in CBC mode (Cipher Block Chaining), it is critical
+ * that the entire plaintext of the record be known before any ciphertext is
+ * transmitted. Otherwise, it is possible for the attacker to mount the attack
+ * described in [CBCATT].
+ *
+ * Implementation note: Canvel et al. [CBCTIME] have demonstrated a timing
+ * attack on CBC padding based on the time required to compute the MAC. In
+ * order to defend against this attack, implementations MUST ensure that
+ * record processing time is essentially the same whether or not the padding
+ * is correct. In general, the best way to do this is to compute the MAC even
+ * if the padding is incorrect, and only then reject the packet. For instance,
+ * if the pad appears to be incorrect, the implementation might assume a
+ * zero-length pad and then compute the MAC. This leaves a small timing
+ * channel, since MAC performance depends, to some extent, on the size of the
+ * data fragment, but it is not believed to be large enough to be exploitable,
+ * due to the large block size of existing MACs and the small size of the
+ * timing signal.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+/**
+ * Generates pseudo random bytes by mixing the result of two hash functions,
+ * MD5 and SHA-1.
+ *
+ * prf_TLS1(secret, label, seed) =
+ *   P_MD5(S1, label + seed) XOR P_SHA-1(S2, label + seed);
+ *
+ * Each P_hash function functions as follows:
+ *
+ * P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) +
+ *                        HMAC_hash(secret, A(2) + seed) +
+ *                        HMAC_hash(secret, A(3) + seed) + ...
+ * A() is defined as:
+ *   A(0) = seed
+ *   A(i) = HMAC_hash(secret, A(i-1))
+ *
+ * The '+' operator denotes concatenation.
+ *
+ * As many iterations A(N) as are needed are performed to generate enough
+ * pseudo random byte output. If an iteration creates more data than is
+ * necessary, then it is truncated.
+ *
+ * Therefore:
+ * A(1) = HMAC_hash(secret, A(0))
+ *      = HMAC_hash(secret, seed)
+ * A(2) = HMAC_hash(secret, A(1))
+ *      = HMAC_hash(secret, HMAC_hash(secret, seed))
+ *
+ * Therefore:
+ * P_hash(secret, seed) =
+ *   HMAC_hash(secret, HMAC_hash(secret, A(0)) + seed) +
+ *   HMAC_hash(secret, HMAC_hash(secret, A(1)) + seed) +
+ *   ...
+ *
+ * Therefore:
+ * P_hash(secret, seed) =
+ *   HMAC_hash(secret, HMAC_hash(secret, seed) + seed) +
+ *   HMAC_hash(secret, HMAC_hash(secret, HMAC_hash(secret, seed)) + seed) +
+ *   ...
+ *
+ * @param secret the secret to use.
+ * @param label the label to use.
+ * @param seed the seed value to use.
+ * @param length the number of bytes to generate.
+ *
+ * @return the pseudo random bytes in a byte buffer.
+ */
+var prf_TLS1 = function(secret, label, seed, length) {
+  var rval = forge.util.createBuffer();
+
+  /* For TLS 1.0, the secret is split in half, into two secrets of equal
+    length. If the secret has an odd length then the last byte of the first
+    half will be the same as the first byte of the second. The length of the
+    two secrets is half of the secret rounded up. */
+  var idx = (secret.length >> 1);
+  var slen = idx + (secret.length & 1);
+  var s1 = secret.substr(0, slen);
+  var s2 = secret.substr(idx, slen);
+  var ai = forge.util.createBuffer();
+  var hmac = forge.hmac.create();
+  seed = label + seed;
+
+  // determine the number of iterations that must be performed to generate
+  // enough output bytes, md5 creates 16 byte hashes, sha1 creates 20
+  var md5itr = Math.ceil(length / 16);
+  var sha1itr = Math.ceil(length / 20);
+
+  // do md5 iterations
+  hmac.start('MD5', s1);
+  var md5bytes = forge.util.createBuffer();
+  ai.putBytes(seed);
+  for(var i = 0; i < md5itr; ++i) {
+    // HMAC_hash(secret, A(i-1))
+    hmac.start(null, null);
+    hmac.update(ai.getBytes());
+    ai.putBuffer(hmac.digest());
+
+    // HMAC_hash(secret, A(i) + seed)
+    hmac.start(null, null);
+    hmac.update(ai.bytes() + seed);
+    md5bytes.putBuffer(hmac.digest());
+  }
+
+  // do sha1 iterations
+  hmac.start('SHA1', s2);
+  var sha1bytes = forge.util.createBuffer();
+  ai.clear();
+  ai.putBytes(seed);
+  for(var i = 0; i < sha1itr; ++i) {
+    // HMAC_hash(secret, A(i-1))
+    hmac.start(null, null);
+    hmac.update(ai.getBytes());
+    ai.putBuffer(hmac.digest());
+
+    // HMAC_hash(secret, A(i) + seed)
+    hmac.start(null, null);
+    hmac.update(ai.bytes() + seed);
+    sha1bytes.putBuffer(hmac.digest());
+  }
+
+  // XOR the md5 bytes with the sha1 bytes
+  rval.putBytes(forge.util.xorBytes(
+    md5bytes.getBytes(), sha1bytes.getBytes(), length));
+
+  return rval;
+};
+
+/**
+ * Generates pseudo random bytes using a SHA256 algorithm. For TLS 1.2.
+ *
+ * @param secret the secret to use.
+ * @param label the label to use.
+ * @param seed the seed value to use.
+ * @param length the number of bytes to generate.
+ *
+ * @return the pseudo random bytes in a byte buffer.
+ */
+var prf_sha256 = function(secret, label, seed, length) {
+   // FIXME: implement me for TLS 1.2
+};
+
+/**
+ * Gets a MAC for a record using the SHA-1 hash algorithm.
+ *
+ * @param key the mac key.
+ * @param state the sequence number (array of two 32-bit integers).
+ * @param record the record.
+ *
+ * @return the sha-1 hash (20 bytes) for the given record.
+ */
+var hmac_sha1 = function(key, seqNum, record) {
+  /* MAC is computed like so:
+  HMAC_hash(
+    key, seqNum +
+      TLSCompressed.type +
+      TLSCompressed.version +
+      TLSCompressed.length +
+      TLSCompressed.fragment)
+  */
+  var hmac = forge.hmac.create();
+  hmac.start('SHA1', key);
+  var b = forge.util.createBuffer();
+  b.putInt32(seqNum[0]);
+  b.putInt32(seqNum[1]);
+  b.putByte(record.type);
+  b.putByte(record.version.major);
+  b.putByte(record.version.minor);
+  b.putInt16(record.length);
+  b.putBytes(record.fragment.bytes());
+  hmac.update(b.getBytes());
+  return hmac.digest().getBytes();
+};
+
+/**
+ * Compresses the TLSPlaintext record into a TLSCompressed record using the
+ * deflate algorithm.
+ *
+ * @param c the TLS connection.
+ * @param record the TLSPlaintext record to compress.
+ * @param s the ConnectionState to use.
+ *
+ * @return true on success, false on failure.
+ */
+var deflate = function(c, record, s) {
+  var rval = false;
+
+  try {
+    var bytes = c.deflate(record.fragment.getBytes());
+    record.fragment = forge.util.createBuffer(bytes);
+    record.length = bytes.length;
+    rval = true;
+  } catch(ex) {
+    // deflate error, fail out
+  }
+
+  return rval;
+};
+
+/**
+ * Decompresses the TLSCompressed record into a TLSPlaintext record using the
+ * deflate algorithm.
+ *
+ * @param c the TLS connection.
+ * @param record the TLSCompressed record to decompress.
+ * @param s the ConnectionState to use.
+ *
+ * @return true on success, false on failure.
+ */
+var inflate = function(c, record, s) {
+  var rval = false;
+
+  try {
+    var bytes = c.inflate(record.fragment.getBytes());
+    record.fragment = forge.util.createBuffer(bytes);
+    record.length = bytes.length;
+    rval = true;
+  } catch(ex) {
+    // inflate error, fail out
+  }
+
+  return rval;
+};
+
+/**
+ * Reads a TLS variable-length vector from a byte buffer.
+ *
+ * Variable-length vectors are defined by specifying a subrange of legal
+ * lengths, inclusively, using the notation <floor..ceiling>. When these are
+ * encoded, the actual length precedes the vector's contents in the byte
+ * stream. The length will be in the form of a number consuming as many bytes
+ * as required to hold the vector's specified maximum (ceiling) length. A
+ * variable-length vector with an actual length field of zero is referred to
+ * as an empty vector.
+ *
+ * @param b the byte buffer.
+ * @param lenBytes the number of bytes required to store the length.
+ *
+ * @return the resulting byte buffer.
+ */
+var readVector = function(b, lenBytes) {
+  var len = 0;
+  switch(lenBytes) {
+  case 1:
+    len = b.getByte();
+    break;
+  case 2:
+    len = b.getInt16();
+    break;
+  case 3:
+    len = b.getInt24();
+    break;
+  case 4:
+    len = b.getInt32();
+    break;
+  }
+
+  // read vector bytes into a new buffer
+  return forge.util.createBuffer(b.getBytes(len));
+};
+
+/**
+ * Writes a TLS variable-length vector to a byte buffer.
+ *
+ * @param b the byte buffer.
+ * @param lenBytes the number of bytes required to store the length.
+ * @param v the byte buffer vector.
+ */
+var writeVector = function(b, lenBytes, v) {
+  // encode length at the start of the vector, where the number of bytes for
+  // the length is the maximum number of bytes it would take to encode the
+  // vector's ceiling
+  b.putInt(v.length(), lenBytes << 3);
+  b.putBuffer(v);
+};
+
+/**
+ * The tls implementation.
+ */
+var tls = {};
+
+/**
+ * Version: TLS 1.2 = 3.3, TLS 1.1 = 3.2, TLS 1.0 = 3.1. Both TLS 1.1 and
+ * TLS 1.2 were still too new (ie: openSSL didn't implement them) at the time
+ * of this implementation so TLS 1.0 was implemented instead.
+ */
+tls.Versions = {
+  TLS_1_0: {major: 3, minor: 1},
+  TLS_1_1: {major: 3, minor: 2},
+  TLS_1_2: {major: 3, minor: 3}
+};
+tls.SupportedVersions = [
+  tls.Versions.TLS_1_1,
+  tls.Versions.TLS_1_0
+];
+tls.Version = tls.SupportedVersions[0];
+
+/**
+ * Maximum fragment size. True maximum is 16384, but we fragment before that
+ * to allow for unusual small increases during compression.
+ */
+tls.MaxFragment = 16384 - 1024;
+
+/**
+ * Whether this entity is considered the "client" or "server".
+ * enum { server, client } ConnectionEnd;
+ */
+tls.ConnectionEnd = {
+  server: 0,
+  client: 1
+};
+
+/**
+ * Pseudo-random function algorithm used to generate keys from the master
+ * secret.
+ * enum { tls_prf_sha256 } PRFAlgorithm;
+ */
+tls.PRFAlgorithm = {
+  tls_prf_sha256: 0
+};
+
+/**
+ * Bulk encryption algorithms.
+ * enum { null, rc4, des3, aes } BulkCipherAlgorithm;
+ */
+tls.BulkCipherAlgorithm = {
+  none: null,
+  rc4: 0,
+  des3: 1,
+  aes: 2
+};
+
+/**
+ * Cipher types.
+ * enum { stream, block, aead } CipherType;
+ */
+tls.CipherType = {
+  stream: 0,
+  block: 1,
+  aead: 2
+};
+
+/**
+ * MAC (Message Authentication Code) algorithms.
+ * enum { null, hmac_md5, hmac_sha1, hmac_sha256,
+ *   hmac_sha384, hmac_sha512} MACAlgorithm;
+ */
+tls.MACAlgorithm = {
+  none: null,
+  hmac_md5: 0,
+  hmac_sha1: 1,
+  hmac_sha256: 2,
+  hmac_sha384: 3,
+  hmac_sha512: 4
+};
+
+/**
+ * Compression algorithms.
+ * enum { null(0), deflate(1), (255) } CompressionMethod;
+ */
+tls.CompressionMethod = {
+  none: 0,
+  deflate: 1
+};
+
+/**
+ * TLS record content types.
+ * enum {
+ *   change_cipher_spec(20), alert(21), handshake(22),
+ *   application_data(23), (255)
+ * } ContentType;
+ */
+tls.ContentType = {
+  change_cipher_spec: 20,
+  alert: 21,
+  handshake: 22,
+  application_data: 23,
+  heartbeat: 24
+};
+
+/**
+ * TLS handshake types.
+ * enum {
+ *   hello_request(0), client_hello(1), server_hello(2),
+ *   certificate(11), server_key_exchange (12),
+ *   certificate_request(13), server_hello_done(14),
+ *   certificate_verify(15), client_key_exchange(16),
+ *   finished(20), (255)
+ * } HandshakeType;
+ */
+tls.HandshakeType = {
+  hello_request: 0,
+  client_hello: 1,
+  server_hello: 2,
+  certificate: 11,
+  server_key_exchange: 12,
+  certificate_request: 13,
+  server_hello_done: 14,
+  certificate_verify: 15,
+  client_key_exchange: 16,
+  finished: 20
+};
+
+/**
+ * TLS Alert Protocol.
+ *
+ * enum { warning(1), fatal(2), (255) } AlertLevel;
+ *
+ * enum {
+ *   close_notify(0),
+ *   unexpected_message(10),
+ *   bad_record_mac(20),
+ *   decryption_failed(21),
+ *   record_overflow(22),
+ *   decompression_failure(30),
+ *   handshake_failure(40),
+ *   bad_certificate(42),
+ *   unsupported_certificate(43),
+ *   certificate_revoked(44),
+ *   certificate_expired(45),
+ *   certificate_unknown(46),
+ *   illegal_parameter(47),
+ *   unknown_ca(48),
+ *   access_denied(49),
+ *   decode_error(50),
+ *   decrypt_error(51),
+ *   export_restriction(60),
+ *   protocol_version(70),
+ *   insufficient_security(71),
+ *   internal_error(80),
+ *   user_canceled(90),
+ *   no_renegotiation(100),
+ *   (255)
+ * } AlertDescription;
+ *
+ * struct {
+ *   AlertLevel level;
+ *   AlertDescription description;
+ * } Alert;
+ */
+tls.Alert = {};
+tls.Alert.Level = {
+  warning: 1,
+  fatal: 2
+};
+tls.Alert.Description = {
+  close_notify: 0,
+  unexpected_message: 10,
+  bad_record_mac: 20,
+  decryption_failed: 21,
+  record_overflow: 22,
+  decompression_failure: 30,
+  handshake_failure: 40,
+  bad_certificate: 42,
+  unsupported_certificate: 43,
+  certificate_revoked: 44,
+  certificate_expired: 45,
+  certificate_unknown: 46,
+  illegal_parameter: 47,
+  unknown_ca: 48,
+  access_denied: 49,
+  decode_error: 50,
+  decrypt_error: 51,
+  export_restriction: 60,
+  protocol_version: 70,
+  insufficient_security: 71,
+  internal_error: 80,
+  user_canceled: 90,
+  no_renegotiation: 100
+};
+
+/**
+ * TLS Heartbeat Message types.
+ * enum {
+ *   heartbeat_request(1),
+ *   heartbeat_response(2),
+ *   (255)
+ * } HeartbeatMessageType;
+ */
+tls.HeartbeatMessageType = {
+  heartbeat_request: 1,
+  heartbeat_response: 2
+};
+
+/**
+ * Supported cipher suites.
+ */
+tls.CipherSuites = {};
+
+/**
+ * Gets a supported cipher suite from its 2 byte ID.
+ *
+ * @param twoBytes two bytes in a string.
+ *
+ * @return the matching supported cipher suite or null.
+ */
+tls.getCipherSuite = function(twoBytes) {
+  var rval = null;
+  for(var key in tls.CipherSuites) {
+    var cs = tls.CipherSuites[key];
+    if(cs.id[0] === twoBytes.charCodeAt(0) &&
+      cs.id[1] === twoBytes.charCodeAt(1)) {
+      rval = cs;
+      break;
+    }
+  }
+  return rval;
+};
+
+/**
+ * Called when an unexpected record is encountered.
+ *
+ * @param c the connection.
+ * @param record the record.
+ */
+tls.handleUnexpected = function(c, record) {
+  // if connection is client and closed, ignore unexpected messages
+  var ignore = (!c.open && c.entity === tls.ConnectionEnd.client);
+  if(!ignore) {
+    c.error(c, {
+      message: 'Unexpected message. Received TLS record out of order.',
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.unexpected_message
+      }
+    });
+  }
+};
+
+/**
+ * Called when a client receives a HelloRequest record.
+ *
+ * @param c the connection.
+ * @param record the record.
+ * @param length the length of the handshake message.
+ */
+tls.handleHelloRequest = function(c, record, length) {
+  // ignore renegotiation requests from the server during a handshake, but
+  // if handshaking, send a warning alert that renegotation is denied
+  if(!c.handshaking && c.handshakes > 0) {
+    // send alert warning
+    tls.queue(c, tls.createAlert(c, {
+       level: tls.Alert.Level.warning,
+       description: tls.Alert.Description.no_renegotiation
+    }));
+    tls.flush(c);
+  }
+
+  // continue
+  c.process();
+};
+
+/**
+ * Parses a hello message from a ClientHello or ServerHello record.
+ *
+ * @param record the record to parse.
+ *
+ * @return the parsed message.
+ */
+tls.parseHelloMessage = function(c, record, length) {
+  var msg = null;
+
+  var client = (c.entity === tls.ConnectionEnd.client);
+
+  // minimum of 38 bytes in message
+  if(length < 38) {
+    c.error(c, {
+      message: client ?
+        'Invalid ServerHello message. Message too short.' :
+        'Invalid ClientHello message. Message too short.',
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.illegal_parameter
+      }
+    });
+  } else {
+    // use 'remaining' to calculate # of remaining bytes in the message
+    var b = record.fragment;
+    var remaining = b.length();
+    msg = {
+      version: {
+        major: b.getByte(),
+        minor: b.getByte()
+      },
+      random: forge.util.createBuffer(b.getBytes(32)),
+      session_id: readVector(b, 1),
+      extensions: []
+    };
+    if(client) {
+      msg.cipher_suite = b.getBytes(2);
+      msg.compression_method = b.getByte();
+    } else {
+      msg.cipher_suites = readVector(b, 2);
+      msg.compression_methods = readVector(b, 1);
+    }
+
+    // read extensions if there are any bytes left in the message
+    remaining = length - (remaining - b.length());
+    if(remaining > 0) {
+      // parse extensions
+      var exts = readVector(b, 2);
+      while(exts.length() > 0) {
+        msg.extensions.push({
+          type: [exts.getByte(), exts.getByte()],
+          data: readVector(exts, 2)
+        });
+      }
+
+      // TODO: make extension support modular
+      if(!client) {
+        for(var i = 0; i < msg.extensions.length; ++i) {
+          var ext = msg.extensions[i];
+
+          // support SNI extension
+          if(ext.type[0] === 0x00 && ext.type[1] === 0x00) {
+            // get server name list
+            var snl = readVector(ext.data, 2);
+            while(snl.length() > 0) {
+              // read server name type
+              var snType = snl.getByte();
+
+              // only HostName type (0x00) is known, break out if
+              // another type is detected
+              if(snType !== 0x00) {
+                break;
+              }
+
+              // add host name to server name list
+              c.session.extensions.server_name.serverNameList.push(
+                readVector(snl, 2).getBytes());
+            }
+          }
+        }
+      }
+    }
+
+    // version already set, do not allow version change
+    if(c.session.version) {
+      if(msg.version.major !== c.session.version.major ||
+        msg.version.minor !== c.session.version.minor) {
+        return c.error(c, {
+          message: 'TLS version change is disallowed during renegotiation.',
+          send: true,
+          alert: {
+            level: tls.Alert.Level.fatal,
+            description: tls.Alert.Description.protocol_version
+          }
+        });
+      }
+    }
+
+    // get the chosen (ServerHello) cipher suite
+    if(client) {
+      // FIXME: should be checking configured acceptable cipher suites
+      c.session.cipherSuite = tls.getCipherSuite(msg.cipher_suite);
+    } else {
+      // get a supported preferred (ClientHello) cipher suite
+      // choose the first supported cipher suite
+      var tmp = forge.util.createBuffer(msg.cipher_suites.bytes());
+      while(tmp.length() > 0) {
+        // FIXME: should be checking configured acceptable suites
+        // cipher suites take up 2 bytes
+        c.session.cipherSuite = tls.getCipherSuite(tmp.getBytes(2));
+        if(c.session.cipherSuite !== null) {
+          break;
+        }
+      }
+    }
+
+    // cipher suite not supported
+    if(c.session.cipherSuite === null) {
+      return c.error(c, {
+        message: 'No cipher suites in common.',
+        send: true,
+        alert: {
+          level: tls.Alert.Level.fatal,
+          description: tls.Alert.Description.handshake_failure
+        },
+        cipherSuite: forge.util.bytesToHex(msg.cipher_suite)
+      });
+    }
+
+    // TODO: handle compression methods
+    if(client) {
+      c.session.compressionMethod = msg.compression_method;
+    } else {
+      // no compression
+      c.session.compressionMethod = tls.CompressionMethod.none;
+    }
+  }
+
+  return msg;
+};
+
+/**
+ * Creates security parameters for the given connection based on the given
+ * hello message.
+ *
+ * @param c the TLS connection.
+ * @param msg the hello message.
+ */
+tls.createSecurityParameters = function(c, msg) {
+  /* Note: security params are from TLS 1.2, some values like prf_algorithm
+  are ignored for TLS 1.0/1.1 and the builtin as specified in the spec is
+  used. */
+
+  // TODO: handle other options from server when more supported
+
+  // get client and server randoms
+  var client = (c.entity === tls.ConnectionEnd.client);
+  var msgRandom = msg.random.bytes();
+  var cRandom = client ? c.session.sp.client_random : msgRandom;
+  var sRandom = client ? msgRandom : tls.createRandom().getBytes();
+
+  // create new security parameters
+  c.session.sp = {
+    entity: c.entity,
+    prf_algorithm: tls.PRFAlgorithm.tls_prf_sha256,
+    bulk_cipher_algorithm: null,
+    cipher_type: null,
+    enc_key_length: null,
+    block_length: null,
+    fixed_iv_length: null,
+    record_iv_length: null,
+    mac_algorithm: null,
+    mac_length: null,
+    mac_key_length: null,
+    compression_algorithm: c.session.compressionMethod,
+    pre_master_secret: null,
+    master_secret: null,
+    client_random: cRandom,
+    server_random: sRandom
+  };
+};
+
+/**
+ * Called when a client receives a ServerHello record.
+ *
+ * When a ServerHello message will be sent:
+ *   The server will send this message in response to a client hello message
+ *   when it was able to find an acceptable set of algorithms. If it cannot
+ *   find such a match, it will respond with a handshake failure alert.
+ *
+ * uint24 length;
+ * struct {
+ *   ProtocolVersion server_version;
+ *   Random random;
+ *   SessionID session_id;
+ *   CipherSuite cipher_suite;
+ *   CompressionMethod compression_method;
+ *   select(extensions_present) {
+ *     case false:
+ *       struct {};
+ *     case true:
+ *       Extension extensions<0..2^16-1>;
+ *   };
+ * } ServerHello;
+ *
+ * @param c the connection.
+ * @param record the record.
+ * @param length the length of the handshake message.
+ */
+tls.handleServerHello = function(c, record, length) {
+  var msg = tls.parseHelloMessage(c, record, length);
+  if(c.fail) {
+    return;
+  }
+
+  // ensure server version is compatible
+  if(msg.version.minor <= c.version.minor) {
+    c.version.minor = msg.version.minor;
+  } else {
+    return c.error(c, {
+      message: 'Incompatible TLS version.',
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.protocol_version
+      }
+    });
+  }
+
+  // indicate session version has been set
+  c.session.version = c.version;
+
+  // get the session ID from the message
+  var sessionId = msg.session_id.bytes();
+
+  // if the session ID is not blank and matches the cached one, resume
+  // the session
+  if(sessionId.length > 0 && sessionId === c.session.id) {
+    // resuming session, expect a ChangeCipherSpec next
+    c.expect = SCC;
+    c.session.resuming = true;
+
+    // get new server random
+    c.session.sp.server_random = msg.random.bytes();
+  } else {
+    // not resuming, expect a server Certificate message next
+    c.expect = SCE;
+    c.session.resuming = false;
+
+    // create new security parameters
+    tls.createSecurityParameters(c, msg);
+  }
+
+  // set new session ID
+  c.session.id = sessionId;
+
+  // continue
+  c.process();
+};
+
+/**
+ * Called when a server receives a ClientHello record.
+ *
+ * When a ClientHello message will be sent:
+ *   When a client first connects to a server it is required to send the
+ *   client hello as its first message. The client can also send a client
+ *   hello in response to a hello request or on its own initiative in order
+ *   to renegotiate the security parameters in an existing connection.
+ *
+ * @param c the connection.
+ * @param record the record.
+ * @param length the length of the handshake message.
+ */
+tls.handleClientHello = function(c, record, length) {
+  var msg = tls.parseHelloMessage(c, record, length);
+  if(c.fail) {
+    return;
+  }
+
+  // get the session ID from the message
+  var sessionId = msg.session_id.bytes();
+
+  // see if the given session ID is in the cache
+  var session = null;
+  if(c.sessionCache) {
+    session = c.sessionCache.getSession(sessionId);
+    if(session === null) {
+      // session ID not found
+      sessionId = '';
+    } else if(session.version.major !== msg.version.major ||
+      session.version.minor > msg.version.minor) {
+      // if session version is incompatible with client version, do not resume
+      session = null;
+      sessionId = '';
+    }
+  }
+
+  // no session found to resume, generate a new session ID
+  if(sessionId.length === 0) {
+    sessionId = forge.random.getBytes(32);
+  }
+
+  // update session
+  c.session.id = sessionId;
+  c.session.clientHelloVersion = msg.version;
+  c.session.sp = {};
+  if(session) {
+    // use version and security parameters from resumed session
+    c.version = c.session.version = session.version;
+    c.session.sp = session.sp;
+  } else {
+    // use highest compatible minor version
+    var version;
+    for(var i = 1; i < tls.SupportedVersions.length; ++i) {
+      version = tls.SupportedVersions[i];
+      if(version.minor <= msg.version.minor) {
+        break;
+      }
+    }
+    c.version = {major: version.major, minor: version.minor};
+    c.session.version = c.version;
+  }
+
+  // if a session is set, resume it
+  if(session !== null) {
+    // resuming session, expect a ChangeCipherSpec next
+    c.expect = CCC;
+    c.session.resuming = true;
+
+    // get new client random
+    c.session.sp.client_random = msg.random.bytes();
+  } else {
+    // not resuming, expect a Certificate or ClientKeyExchange
+    c.expect = (c.verifyClient !== false) ? CCE : CKE;
+    c.session.resuming = false;
+
+    // create new security parameters
+    tls.createSecurityParameters(c, msg);
+  }
+
+  // connection now open
+  c.open = true;
+
+  // queue server hello
+  tls.queue(c, tls.createRecord(c, {
+    type: tls.ContentType.handshake,
+    data: tls.createServerHello(c)
+  }));
+
+  if(c.session.resuming) {
+    // queue change cipher spec message
+    tls.queue(c, tls.createRecord(c, {
+      type: tls.ContentType.change_cipher_spec,
+      data: tls.createChangeCipherSpec()
+    }));
+
+    // create pending state
+    c.state.pending = tls.createConnectionState(c);
+
+    // change current write state to pending write state
+    c.state.current.write = c.state.pending.write;
+
+    // queue finished
+    tls.queue(c, tls.createRecord(c, {
+      type: tls.ContentType.handshake,
+      data: tls.createFinished(c)
+    }));
+  } else {
+    // queue server certificate
+    tls.queue(c, tls.createRecord(c, {
+      type: tls.ContentType.handshake,
+      data: tls.createCertificate(c)
+    }));
+
+    if(!c.fail) {
+      // queue server key exchange
+      tls.queue(c, tls.createRecord(c, {
+        type: tls.ContentType.handshake,
+        data: tls.createServerKeyExchange(c)
+      }));
+
+      // request client certificate if set
+      if(c.verifyClient !== false) {
+        // queue certificate request
+        tls.queue(c, tls.createRecord(c, {
+          type: tls.ContentType.handshake,
+          data: tls.createCertificateRequest(c)
+        }));
+      }
+
+      // queue server hello done
+      tls.queue(c, tls.createRecord(c, {
+        type: tls.ContentType.handshake,
+        data: tls.createServerHelloDone(c)
+      }));
+    }
+  }
+
+  // send records
+  tls.flush(c);
+
+  // continue
+  c.process();
+};
+
+/**
+ * Called when a client receives a Certificate record.
+ *
+ * When this message will be sent:
+ *   The server must send a certificate whenever the agreed-upon key exchange
+ *   method is not an anonymous one. This message will always immediately
+ *   follow the server hello message.
+ *
+ * Meaning of this message:
+ *   The certificate type must be appropriate for the selected cipher suite's
+ *   key exchange algorithm, and is generally an X.509v3 certificate. It must
+ *   contain a key which matches the key exchange method, as follows. Unless
+ *   otherwise specified, the signing algorithm for the certificate must be
+ *   the same as the algorithm for the certificate key. Unless otherwise
+ *   specified, the public key may be of any length.
+ *
+ * opaque ASN.1Cert<1..2^24-1>;
+ * struct {
+ *   ASN.1Cert certificate_list<1..2^24-1>;
+ * } Certificate;
+ *
+ * @param c the connection.
+ * @param record the record.
+ * @param length the length of the handshake message.
+ */
+tls.handleCertificate = function(c, record, length) {
+  // minimum of 3 bytes in message
+  if(length < 3) {
+    return c.error(c, {
+      message: 'Invalid Certificate message. Message too short.',
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.illegal_parameter
+      }
+    });
+  }
+
+  var b = record.fragment;
+  var msg = {
+    certificate_list: readVector(b, 3)
+  };
+
+  /* The sender's certificate will be first in the list (chain), each
+    subsequent one that follows will certify the previous one, but root
+    certificates (self-signed) that specify the certificate authority may
+    be omitted under the assumption that clients must already possess it. */
+  var cert, asn1;
+  var certs = [];
+  try {
+    while(msg.certificate_list.length() > 0) {
+      // each entry in msg.certificate_list is a vector with 3 len bytes
+      cert = readVector(msg.certificate_list, 3);
+      asn1 = forge.asn1.fromDer(cert);
+      cert = forge.pki.certificateFromAsn1(asn1, true);
+      certs.push(cert);
+    }
+  } catch(ex) {
+    return c.error(c, {
+      message: 'Could not parse certificate list.',
+      cause: ex,
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.bad_certificate
+      }
+    });
+  }
+
+  // ensure at least 1 certificate was provided if in client-mode
+  // or if verifyClient was set to true to require a certificate
+  // (as opposed to 'optional')
+  var client = (c.entity === tls.ConnectionEnd.client);
+  if((client || c.verifyClient === true) && certs.length === 0) {
+    // error, no certificate
+    c.error(c, {
+      message: client ?
+        'No server certificate provided.' :
+        'No client certificate provided.',
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.illegal_parameter
+      }
+    });
+  } else if(certs.length === 0) {
+    // no certs to verify
+    // expect a ServerKeyExchange or ClientKeyExchange message next
+    c.expect = client ? SKE : CKE;
+  } else {
+    // save certificate in session
+    if(client) {
+      c.session.serverCertificate = certs[0];
+    } else {
+      c.session.clientCertificate = certs[0];
+    }
+
+    if(tls.verifyCertificateChain(c, certs)) {
+      // expect a ServerKeyExchange or ClientKeyExchange message next
+      c.expect = client ? SKE : CKE;
+    }
+  }
+
+  // continue
+  c.process();
+};
+
+/**
+ * Called when a client receives a ServerKeyExchange record.
+ *
+ * When this message will be sent:
+ *   This message will be sent immediately after the server certificate
+ *   message (or the server hello message, if this is an anonymous
+ *   negotiation).
+ *
+ *   The server key exchange message is sent by the server only when the
+ *   server certificate message (if sent) does not contain enough data to
+ *   allow the client to exchange a premaster secret.
+ *
+ * Meaning of this message:
+ *   This message conveys cryptographic information to allow the client to
+ *   communicate the premaster secret: either an RSA public key to encrypt
+ *   the premaster secret with, or a Diffie-Hellman public key with which the
+ *   client can complete a key exchange (with the result being the premaster
+ *   secret.)
+ *
+ * enum {
+ *   dhe_dss, dhe_rsa, dh_anon, rsa, dh_dss, dh_rsa
+ * } KeyExchangeAlgorithm;
+ *
+ * struct {
+ *   opaque dh_p<1..2^16-1>;
+ *   opaque dh_g<1..2^16-1>;
+ *   opaque dh_Ys<1..2^16-1>;
+ * } ServerDHParams;
+ *
+ * struct {
+ *   select(KeyExchangeAlgorithm) {
+ *     case dh_anon:
+ *       ServerDHParams params;
+ *     case dhe_dss:
+ *     case dhe_rsa:
+ *       ServerDHParams params;
+ *       digitally-signed struct {
+ *         opaque client_random[32];
+ *         opaque server_random[32];
+ *         ServerDHParams params;
+ *       } signed_params;
+ *     case rsa:
+ *     case dh_dss:
+ *     case dh_rsa:
+ *       struct {};
+ *   };
+ * } ServerKeyExchange;
+ *
+ * @param c the connection.
+ * @param record the record.
+ * @param length the length of the handshake message.
+ */
+tls.handleServerKeyExchange = function(c, record, length) {
+  // this implementation only supports RSA, no Diffie-Hellman support
+  // so any length > 0 is invalid
+  if(length > 0) {
+    return c.error(c, {
+      message: 'Invalid key parameters. Only RSA is supported.',
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.unsupported_certificate
+      }
+    });
+  }
+
+  // expect an optional CertificateRequest message next
+  c.expect = SCR;
+
+  // continue
+  c.process();
+};
+
+/**
+ * Called when a client receives a ClientKeyExchange record.
+ *
+ * @param c the connection.
+ * @param record the record.
+ * @param length the length of the handshake message.
+ */
+tls.handleClientKeyExchange = function(c, record, length) {
+  // this implementation only supports RSA, no Diffie-Hellman support
+  // so any length < 48 is invalid
+  if(length < 48) {
+    return c.error(c, {
+      message: 'Invalid key parameters. Only RSA is supported.',
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.unsupported_certificate
+      }
+    });
+  }
+
+  var b = record.fragment;
+  var msg = {
+    enc_pre_master_secret: readVector(b, 2).getBytes()
+  };
+
+  // do rsa decryption
+  var privateKey = null;
+  if(c.getPrivateKey) {
+    try {
+      privateKey = c.getPrivateKey(c, c.session.serverCertificate);
+      privateKey = forge.pki.privateKeyFromPem(privateKey);
+    } catch(ex) {
+      c.error(c, {
+        message: 'Could not get private key.',
+        cause: ex,
+        send: true,
+        alert: {
+          level: tls.Alert.Level.fatal,
+          description: tls.Alert.Description.internal_error
+        }
+      });
+    }
+  }
+
+  if(privateKey === null) {
+    return c.error(c, {
+      message: 'No private key set.',
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.internal_error
+      }
+    });
+  }
+
+  try {
+    // decrypt 48-byte pre-master secret
+    var sp = c.session.sp;
+    sp.pre_master_secret = privateKey.decrypt(msg.enc_pre_master_secret);
+
+    // ensure client hello version matches first 2 bytes
+    var version = c.session.clientHelloVersion;
+    if(version.major !== sp.pre_master_secret.charCodeAt(0) ||
+      version.minor !== sp.pre_master_secret.charCodeAt(1)) {
+      // error, do not send alert (see BLEI attack below)
+      throw new Error('TLS version rollback attack detected.');
+    }
+  } catch(ex) {
+    /* Note: Daniel Bleichenbacher [BLEI] can be used to attack a
+      TLS server which is using PKCS#1 encoded RSA, so instead of
+      failing here, we generate 48 random bytes and use that as
+      the pre-master secret. */
+    sp.pre_master_secret = forge.random.getBytes(48);
+  }
+
+  // expect a CertificateVerify message if a Certificate was received that
+  // does not have fixed Diffie-Hellman params, otherwise expect
+  // ChangeCipherSpec
+  c.expect = CCC;
+  if(c.session.clientCertificate !== null) {
+    // only RSA support, so expect CertificateVerify
+    // TODO: support Diffie-Hellman
+    c.expect = CCV;
+  }
+
+  // continue
+  c.process();
+};
+
+/**
+ * Called when a client receives a CertificateRequest record.
+ *
+ * When this message will be sent:
+ *   A non-anonymous server can optionally request a certificate from the
+ *   client, if appropriate for the selected cipher suite. This message, if
+ *   sent, will immediately follow the Server Key Exchange message (if it is
+ *   sent; otherwise, the Server Certificate message).
+ *
+ * enum {
+ *   rsa_sign(1), dss_sign(2), rsa_fixed_dh(3), dss_fixed_dh(4),
+ *   rsa_ephemeral_dh_RESERVED(5), dss_ephemeral_dh_RESERVED(6),
+ *   fortezza_dms_RESERVED(20), (255)
+ * } ClientCertificateType;
+ *
+ * opaque DistinguishedName<1..2^16-1>;
+ *
+ * struct {
+ *   ClientCertificateType certificate_types<1..2^8-1>;
+ *   SignatureAndHashAlgorithm supported_signature_algorithms<2^16-1>;
+ *   DistinguishedName certificate_authorities<0..2^16-1>;
+ * } CertificateRequest;
+ *
+ * @param c the connection.
+ * @param record the record.
+ * @param length the length of the handshake message.
+ */
+tls.handleCertificateRequest = function(c, record, length) {
+  // minimum of 3 bytes in message
+  if(length < 3) {
+    return c.error(c, {
+      message: 'Invalid CertificateRequest. Message too short.',
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.illegal_parameter
+      }
+    });
+  }
+
+  // TODO: TLS 1.2+ has different format including
+  // SignatureAndHashAlgorithm after cert types
+  var b = record.fragment;
+  var msg = {
+    certificate_types: readVector(b, 1),
+    certificate_authorities: readVector(b, 2)
+  };
+
+  // save certificate request in session
+  c.session.certificateRequest = msg;
+
+  // expect a ServerHelloDone message next
+  c.expect = SHD;
+
+  // continue
+  c.process();
+};
+
+/**
+ * Called when a server receives a CertificateVerify record.
+ *
+ * @param c the connection.
+ * @param record the record.
+ * @param length the length of the handshake message.
+ */
+tls.handleCertificateVerify = function(c, record, length) {
+  if(length < 2) {
+    return c.error(c, {
+      message: 'Invalid CertificateVerify. Message too short.',
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.illegal_parameter
+      }
+    });
+  }
+
+  // rewind to get full bytes for message so it can be manually
+  // digested below (special case for CertificateVerify messages because
+  // they must be digested *after* handling as opposed to all others)
+  var b = record.fragment;
+  b.read -= 4;
+  var msgBytes = b.bytes();
+  b.read += 4;
+
+  var msg = {
+    signature: readVector(b, 2).getBytes()
+  };
+
+  // TODO: add support for DSA
+
+  // generate data to verify
+  var verify = forge.util.createBuffer();
+  verify.putBuffer(c.session.md5.digest());
+  verify.putBuffer(c.session.sha1.digest());
+  verify = verify.getBytes();
+
+  try {
+    var cert = c.session.clientCertificate;
+    /*b = forge.pki.rsa.decrypt(
+      msg.signature, cert.publicKey, true, verify.length);
+    if(b !== verify) {*/
+    if(!cert.publicKey.verify(verify, msg.signature, 'NONE')) {
+      throw new Error('CertificateVerify signature does not match.');
+    }
+
+    // digest message now that it has been handled
+    c.session.md5.update(msgBytes);
+    c.session.sha1.update(msgBytes);
+  } catch(ex) {
+    return c.error(c, {
+      message: 'Bad signature in CertificateVerify.',
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.handshake_failure
+      }
+    });
+  }
+
+  // expect ChangeCipherSpec
+  c.expect = CCC;
+
+  // continue
+  c.process();
+};
+
+/**
+ * Called when a client receives a ServerHelloDone record.
+ *
+ * When this message will be sent:
+ *   The server hello done message is sent by the server to indicate the end
+ *   of the server hello and associated messages. After sending this message
+ *   the server will wait for a client response.
+ *
+ * Meaning of this message:
+ *   This message means that the server is done sending messages to support
+ *   the key exchange, and the client can proceed with its phase of the key
+ *   exchange.
+ *
+ *   Upon receipt of the server hello done message the client should verify
+ *   that the server provided a valid certificate if required and check that
+ *   the server hello parameters are acceptable.
+ *
+ * struct {} ServerHelloDone;
+ *
+ * @param c the connection.
+ * @param record the record.
+ * @param length the length of the handshake message.
+ */
+tls.handleServerHelloDone = function(c, record, length) {
+  // len must be 0 bytes
+  if(length > 0) {
+    return c.error(c, {
+      message: 'Invalid ServerHelloDone message. Invalid length.',
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.record_overflow
+      }
+    });
+  }
+
+  if(c.serverCertificate === null) {
+    // no server certificate was provided
+    var error = {
+      message: 'No server certificate provided. Not enough security.',
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.insufficient_security
+      }
+    };
+
+    // call application callback
+    var depth = 0;
+    var ret = c.verify(c, error.alert.description, depth, []);
+    if(ret !== true) {
+      // check for custom alert info
+      if(ret || ret === 0) {
+        // set custom message and alert description
+        if(typeof ret === 'object' && !forge.util.isArray(ret)) {
+          if(ret.message) {
+            error.message = ret.message;
+          }
+          if(ret.alert) {
+            error.alert.description = ret.alert;
+          }
+        } else if(typeof ret === 'number') {
+          // set custom alert description
+          error.alert.description = ret;
+        }
+      }
+
+      // send error
+      return c.error(c, error);
+    }
+  }
+
+  // create client certificate message if requested
+  if(c.session.certificateRequest !== null) {
+    record = tls.createRecord(c, {
+      type: tls.ContentType.handshake,
+      data: tls.createCertificate(c)
+    });
+    tls.queue(c, record);
+  }
+
+  // create client key exchange message
+  record = tls.createRecord(c, {
+     type: tls.ContentType.handshake,
+     data: tls.createClientKeyExchange(c)
+  });
+  tls.queue(c, record);
+
+  // expect no messages until the following callback has been called
+  c.expect = SER;
+
+  // create callback to handle client signature (for client-certs)
+  var callback = function(c, signature) {
+    if(c.session.certificateRequest !== null &&
+      c.session.clientCertificate !== null) {
+      // create certificate verify message
+      tls.queue(c, tls.createRecord(c, {
+        type: tls.ContentType.handshake,
+        data: tls.createCertificateVerify(c, signature)
+      }));
+    }
+
+    // create change cipher spec message
+    tls.queue(c, tls.createRecord(c, {
+      type: tls.ContentType.change_cipher_spec,
+      data: tls.createChangeCipherSpec()
+    }));
+
+    // create pending state
+    c.state.pending = tls.createConnectionState(c);
+
+    // change current write state to pending write state
+    c.state.current.write = c.state.pending.write;
+
+    // create finished message
+    tls.queue(c, tls.createRecord(c, {
+      type: tls.ContentType.handshake,
+      data: tls.createFinished(c)
+    }));
+
+    // expect a server ChangeCipherSpec message next
+    c.expect = SCC;
+
+    // send records
+    tls.flush(c);
+
+    // continue
+    c.process();
+  };
+
+  // if there is no certificate request or no client certificate, do
+  // callback immediately
+  if(c.session.certificateRequest === null ||
+    c.session.clientCertificate === null) {
+    return callback(c, null);
+  }
+
+  // otherwise get the client signature
+  tls.getClientSignature(c, callback);
+};
+
+/**
+ * Called when a ChangeCipherSpec record is received.
+ *
+ * @param c the connection.
+ * @param record the record.
+ */
+tls.handleChangeCipherSpec = function(c, record) {
+  if(record.fragment.getByte() !== 0x01) {
+    return c.error(c, {
+      message: 'Invalid ChangeCipherSpec message received.',
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.illegal_parameter
+      }
+    });
+  }
+
+  // create pending state if:
+  // 1. Resuming session in client mode OR
+  // 2. NOT resuming session in server mode
+  var client = (c.entity === tls.ConnectionEnd.client);
+  if((c.session.resuming && client) || (!c.session.resuming && !client)) {
+    c.state.pending = tls.createConnectionState(c);
+  }
+
+  // change current read state to pending read state
+  c.state.current.read = c.state.pending.read;
+
+  // clear pending state if:
+  // 1. NOT resuming session in client mode OR
+  // 2. resuming a session in server mode
+  if((!c.session.resuming && client) || (c.session.resuming && !client)) {
+    c.state.pending = null;
+  }
+
+  // expect a Finished record next
+  c.expect = client ? SFI : CFI;
+
+  // continue
+  c.process();
+};
+
+/**
+ * Called when a Finished record is received.
+ *
+ * When this message will be sent:
+ *   A finished message is always sent immediately after a change
+ *   cipher spec message to verify that the key exchange and
+ *   authentication processes were successful. It is essential that a
+ *   change cipher spec message be received between the other
+ *   handshake messages and the Finished message.
+ *
+ * Meaning of this message:
+ *   The finished message is the first protected with the just-
+ *   negotiated algorithms, keys, and secrets. Recipients of finished
+ *   messages must verify that the contents are correct.  Once a side
+ *   has sent its Finished message and received and validated the
+ *   Finished message from its peer, it may begin to send and receive
+ *   application data over the connection.
+ *
+ * struct {
+ *   opaque verify_data[verify_data_length];
+ * } Finished;
+ *
+ * verify_data
+ *   PRF(master_secret, finished_label, Hash(handshake_messages))
+ *     [0..verify_data_length-1];
+ *
+ * finished_label
+ *   For Finished messages sent by the client, the string
+ *   "client finished". For Finished messages sent by the server, the
+ *   string "server finished".
+ *
+ * verify_data_length depends on the cipher suite. If it is not specified
+ * by the cipher suite, then it is 12. Versions of TLS < 1.2 always used
+ * 12 bytes.
+ *
+ * @param c the connection.
+ * @param record the record.
+ * @param length the length of the handshake message.
+ */
+tls.handleFinished = function(c, record, length) {
+  // rewind to get full bytes for message so it can be manually
+  // digested below (special case for Finished messages because they
+  // must be digested *after* handling as opposed to all others)
+  var b = record.fragment;
+  b.read -= 4;
+  var msgBytes = b.bytes();
+  b.read += 4;
+
+  // message contains only verify_data
+  var vd = record.fragment.getBytes();
+
+  // ensure verify data is correct
+  b = forge.util.createBuffer();
+  b.putBuffer(c.session.md5.digest());
+  b.putBuffer(c.session.sha1.digest());
+
+  // set label based on entity type
+  var client = (c.entity === tls.ConnectionEnd.client);
+  var label = client ? 'server finished' : 'client finished';
+
+  // TODO: determine prf function and verify length for TLS 1.2
+  var sp = c.session.sp;
+  var vdl = 12;
+  var prf = prf_TLS1;
+  b = prf(sp.master_secret, label, b.getBytes(), vdl);
+  if(b.getBytes() !== vd) {
+    return c.error(c, {
+      message: 'Invalid verify_data in Finished message.',
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.decrypt_error
+      }
+    });
+  }
+
+  // digest finished message now that it has been handled
+  c.session.md5.update(msgBytes);
+  c.session.sha1.update(msgBytes);
+
+  // resuming session as client or NOT resuming session as server
+  if((c.session.resuming && client) || (!c.session.resuming && !client)) {
+    // create change cipher spec message
+    tls.queue(c, tls.createRecord(c, {
+      type: tls.ContentType.change_cipher_spec,
+      data: tls.createChangeCipherSpec()
+    }));
+
+    // change current write state to pending write state, clear pending
+    c.state.current.write = c.state.pending.write;
+    c.state.pending = null;
+
+    // create finished message
+    tls.queue(c, tls.createRecord(c, {
+      type: tls.ContentType.handshake,
+      data: tls.createFinished(c)
+    }));
+  }
+
+  // expect application data next
+  c.expect = client ? SAD : CAD;
+
+  // handshake complete
+  c.handshaking = false;
+  ++c.handshakes;
+
+  // save access to peer certificate
+  c.peerCertificate = client ?
+    c.session.serverCertificate : c.session.clientCertificate;
+
+  // send records
+  tls.flush(c);
+
+  // now connected
+  c.isConnected = true;
+  c.connected(c);
+
+  // continue
+  c.process();
+};
+
+/**
+ * Called when an Alert record is received.
+ *
+ * @param c the connection.
+ * @param record the record.
+ */
+tls.handleAlert = function(c, record) {
+  // read alert
+  var b = record.fragment;
+  var alert = {
+    level: b.getByte(),
+    description: b.getByte()
+  };
+
+  // TODO: consider using a table?
+  // get appropriate message
+  var msg;
+  switch(alert.description) {
+  case tls.Alert.Description.close_notify:
+    msg = 'Connection closed.';
+    break;
+  case tls.Alert.Description.unexpected_message:
+    msg = 'Unexpected message.';
+    break;
+  case tls.Alert.Description.bad_record_mac:
+    msg = 'Bad record MAC.';
+    break;
+  case tls.Alert.Description.decryption_failed:
+    msg = 'Decryption failed.';
+    break;
+  case tls.Alert.Description.record_overflow:
+    msg = 'Record overflow.';
+    break;
+  case tls.Alert.Description.decompression_failure:
+    msg = 'Decompression failed.';
+    break;
+  case tls.Alert.Description.handshake_failure:
+    msg = 'Handshake failure.';
+    break;
+  case tls.Alert.Description.bad_certificate:
+    msg = 'Bad certificate.';
+    break;
+  case tls.Alert.Description.unsupported_certificate:
+    msg = 'Unsupported certificate.';
+    break;
+  case tls.Alert.Description.certificate_revoked:
+    msg = 'Certificate revoked.';
+    break;
+  case tls.Alert.Description.certificate_expired:
+    msg = 'Certificate expired.';
+    break;
+  case tls.Alert.Description.certificate_unknown:
+    msg = 'Certificate unknown.';
+    break;
+  case tls.Alert.Description.illegal_parameter:
+    msg = 'Illegal parameter.';
+    break;
+  case tls.Alert.Description.unknown_ca:
+    msg = 'Unknown certificate authority.';
+    break;
+  case tls.Alert.Description.access_denied:
+    msg = 'Access denied.';
+    break;
+  case tls.Alert.Description.decode_error:
+    msg = 'Decode error.';
+    break;
+  case tls.Alert.Description.decrypt_error:
+    msg = 'Decrypt error.';
+    break;
+  case tls.Alert.Description.export_restriction:
+    msg = 'Export restriction.';
+    break;
+  case tls.Alert.Description.protocol_version:
+    msg = 'Unsupported protocol version.';
+    break;
+  case tls.Alert.Description.insufficient_security:
+    msg = 'Insufficient security.';
+    break;
+  case tls.Alert.Description.internal_error:
+    msg = 'Internal error.';
+    break;
+  case tls.Alert.Description.user_canceled:
+    msg = 'User canceled.';
+    break;
+  case tls.Alert.Description.no_renegotiation:
+    msg = 'Renegotiation not supported.';
+    break;
+  default:
+    msg = 'Unknown error.';
+    break;
+  }
+
+  // close connection on close_notify, not an error
+  if(alert.description === tls.Alert.Description.close_notify) {
+    return c.close();
+  }
+
+  // call error handler
+  c.error(c, {
+    message: msg,
+    send: false,
+    // origin is the opposite end
+    origin: (c.entity === tls.ConnectionEnd.client) ? 'server' : 'client',
+    alert: alert
+  });
+
+  // continue
+  c.process();
+};
+
+/**
+ * Called when a Handshake record is received.
+ *
+ * @param c the connection.
+ * @param record the record.
+ */
+tls.handleHandshake = function(c, record) {
+  // get the handshake type and message length
+  var b = record.fragment;
+  var type = b.getByte();
+  var length = b.getInt24();
+
+  // see if the record fragment doesn't yet contain the full message
+  if(length > b.length()) {
+    // cache the record, clear its fragment, and reset the buffer read
+    // pointer before the type and length were read
+    c.fragmented = record;
+    record.fragment = forge.util.createBuffer();
+    b.read -= 4;
+
+    // continue
+    return c.process();
+  }
+
+  // full message now available, clear cache, reset read pointer to
+  // before type and length
+  c.fragmented = null;
+  b.read -= 4;
+
+  // save the handshake bytes for digestion after handler is found
+  // (include type and length of handshake msg)
+  var bytes = b.bytes(length + 4);
+
+  // restore read pointer
+  b.read += 4;
+
+  // handle expected message
+  if(type in hsTable[c.entity][c.expect]) {
+    // initialize server session
+    if(c.entity === tls.ConnectionEnd.server && !c.open && !c.fail) {
+      c.handshaking = true;
+      c.session = {
+        version: null,
+        extensions: {
+          server_name: {
+            serverNameList: []
+          }
+        },
+        cipherSuite: null,
+        compressionMethod: null,
+        serverCertificate: null,
+        clientCertificate: null,
+        md5: forge.md.md5.create(),
+        sha1: forge.md.sha1.create()
+      };
+    }
+
+    /* Update handshake messages digest. Finished and CertificateVerify
+      messages are not digested here. They can't be digested as part of
+      the verify_data that they contain. These messages are manually
+      digested in their handlers. HelloRequest messages are simply never
+      included in the handshake message digest according to spec. */
+    if(type !== tls.HandshakeType.hello_request &&
+      type !== tls.HandshakeType.certificate_verify &&
+      type !== tls.HandshakeType.finished) {
+      c.session.md5.update(bytes);
+      c.session.sha1.update(bytes);
+    }
+
+    // handle specific handshake type record
+    hsTable[c.entity][c.expect][type](c, record, length);
+  } else {
+    // unexpected record
+    tls.handleUnexpected(c, record);
+  }
+};
+
+/**
+ * Called when an ApplicationData record is received.
+ *
+ * @param c the connection.
+ * @param record the record.
+ */
+tls.handleApplicationData = function(c, record) {
+  // buffer data, notify that its ready
+  c.data.putBuffer(record.fragment);
+  c.dataReady(c);
+
+  // continue
+  c.process();
+};
+
+/**
+ * Called when a Heartbeat record is received.
+ *
+ * @param c the connection.
+ * @param record the record.
+ */
+tls.handleHeartbeat = function(c, record) {
+  // get the heartbeat type and payload
+  var b = record.fragment;
+  var type = b.getByte();
+  var length = b.getInt16();
+  var payload = b.getBytes(length);
+
+  if(type === tls.HeartbeatMessageType.heartbeat_request) {
+    // discard request during handshake or if length is too large
+    if(c.handshaking || length > payload.length) {
+      // continue
+      return c.process();
+    }
+    // retransmit payload
+    tls.queue(c, tls.createRecord(c, {
+      type: tls.ContentType.heartbeat,
+      data: tls.createHeartbeat(
+        tls.HeartbeatMessageType.heartbeat_response, payload)
+    }));
+    tls.flush(c);
+  } else if(type === tls.HeartbeatMessageType.heartbeat_response) {
+    // check payload against expected payload, discard heartbeat if no match
+    if(payload !== c.expectedHeartbeatPayload) {
+      // continue
+      return c.process();
+    }
+
+    // notify that a valid heartbeat was received
+    if(c.heartbeatReceived) {
+      c.heartbeatReceived(c, forge.util.createBuffer(payload));
+    }
+  }
+
+  // continue
+  c.process();
+};
+
+/**
+ * The transistional state tables for receiving TLS records. It maps the
+ * current TLS engine state and a received record to a function to handle the
+ * record and update the state.
+ *
+ * For instance, if the current state is SHE, then the TLS engine is expecting
+ * a ServerHello record. Once a record is received, the handler function is
+ * looked up using the state SHE and the record's content type.
+ *
+ * The resulting function will either be an error handler or a record handler.
+ * The function will take whatever action is appropriate and update the state
+ * for the next record.
+ *
+ * The states are all based on possible server record types. Note that the
+ * client will never specifically expect to receive a HelloRequest or an alert
+ * from the server so there is no state that reflects this. These messages may
+ * occur at any time.
+ *
+ * There are two tables for mapping states because there is a second tier of
+ * types for handshake messages. Once a record with a content type of handshake
+ * is received, the handshake record handler will look up the handshake type in
+ * the secondary map to get its appropriate handler.
+ *
+ * Valid message orders are as follows:
+ *
+ * =======================FULL HANDSHAKE======================
+ * Client                                               Server
+ *
+ * ClientHello                  -------->
+ *                                                 ServerHello
+ *                                                Certificate*
+ *                                          ServerKeyExchange*
+ *                                         CertificateRequest*
+ *                              <--------      ServerHelloDone
+ * Certificate*
+ * ClientKeyExchange
+ * CertificateVerify*
+ * [ChangeCipherSpec]
+ * Finished                     -------->
+ *                                          [ChangeCipherSpec]
+ *                              <--------             Finished
+ * Application Data             <------->     Application Data
+ *
+ * =====================SESSION RESUMPTION=====================
+ * Client                                                Server
+ *
+ * ClientHello                   -------->
+ *                                                  ServerHello
+ *                                           [ChangeCipherSpec]
+ *                               <--------             Finished
+ * [ChangeCipherSpec]
+ * Finished                      -------->
+ * Application Data              <------->     Application Data
+ */
+// client expect states (indicate which records are expected to be received)
+var SHE = 0; // rcv server hello
+var SCE = 1; // rcv server certificate
+var SKE = 2; // rcv server key exchange
+var SCR = 3; // rcv certificate request
+var SHD = 4; // rcv server hello done
+var SCC = 5; // rcv change cipher spec
+var SFI = 6; // rcv finished
+var SAD = 7; // rcv application data
+var SER = 8; // not expecting any messages at this point
+
+// server expect states
+var CHE = 0; // rcv client hello
+var CCE = 1; // rcv client certificate
+var CKE = 2; // rcv client key exchange
+var CCV = 3; // rcv certificate verify
+var CCC = 4; // rcv change cipher spec
+var CFI = 5; // rcv finished
+var CAD = 6; // rcv application data
+var CER = 7; // not expecting any messages at this point
+
+// map client current expect state and content type to function
+var __ = tls.handleUnexpected;
+var R0 = tls.handleChangeCipherSpec;
+var R1 = tls.handleAlert;
+var R2 = tls.handleHandshake;
+var R3 = tls.handleApplicationData;
+var R4 = tls.handleHeartbeat;
+var ctTable = [];
+ctTable[tls.ConnectionEnd.client] = [
+//      CC,AL,HS,AD,HB
+/*SHE*/[__,R1,R2,__,R4],
+/*SCE*/[__,R1,R2,__,R4],
+/*SKE*/[__,R1,R2,__,R4],
+/*SCR*/[__,R1,R2,__,R4],
+/*SHD*/[__,R1,R2,__,R4],
+/*SCC*/[R0,R1,__,__,R4],
+/*SFI*/[__,R1,R2,__,R4],
+/*SAD*/[__,R1,R2,R3,R4],
+/*SER*/[__,R1,R2,__,R4]
+];
+
+// map server current expect state and content type to function
+ctTable[tls.ConnectionEnd.server] = [
+//      CC,AL,HS,AD
+/*CHE*/[__,R1,R2,__,R4],
+/*CCE*/[__,R1,R2,__,R4],
+/*CKE*/[__,R1,R2,__,R4],
+/*CCV*/[__,R1,R2,__,R4],
+/*CCC*/[R0,R1,__,__,R4],
+/*CFI*/[__,R1,R2,__,R4],
+/*CAD*/[__,R1,R2,R3,R4],
+/*CER*/[__,R1,R2,__,R4]
+];
+
+// map client current expect state and handshake type to function
+var H0 = tls.handleHelloRequest;
+var H1 = tls.handleServerHello;
+var H2 = tls.handleCertificate;
+var H3 = tls.handleServerKeyExchange;
+var H4 = tls.handleCertificateRequest;
+var H5 = tls.handleServerHelloDone;
+var H6 = tls.handleFinished;
+var hsTable = [];
+hsTable[tls.ConnectionEnd.client] = [
+//      HR,01,SH,03,04,05,06,07,08,09,10,SC,SK,CR,HD,15,CK,17,18,19,FI
+/*SHE*/[__,__,H1,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__],
+/*SCE*/[H0,__,__,__,__,__,__,__,__,__,__,H2,H3,H4,H5,__,__,__,__,__,__],
+/*SKE*/[H0,__,__,__,__,__,__,__,__,__,__,__,H3,H4,H5,__,__,__,__,__,__],
+/*SCR*/[H0,__,__,__,__,__,__,__,__,__,__,__,__,H4,H5,__,__,__,__,__,__],
+/*SHD*/[H0,__,__,__,__,__,__,__,__,__,__,__,__,__,H5,__,__,__,__,__,__],
+/*SCC*/[H0,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__],
+/*SFI*/[H0,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,H6],
+/*SAD*/[H0,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__],
+/*SER*/[H0,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__]
+];
+
+// map server current expect state and handshake type to function
+// Note: CAD[CH] does not map to FB because renegotation is prohibited
+var H7 = tls.handleClientHello;
+var H8 = tls.handleClientKeyExchange;
+var H9 = tls.handleCertificateVerify;
+hsTable[tls.ConnectionEnd.server] = [
+//      01,CH,02,03,04,05,06,07,08,09,10,CC,12,13,14,CV,CK,17,18,19,FI
+/*CHE*/[__,H7,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__],
+/*CCE*/[__,__,__,__,__,__,__,__,__,__,__,H2,__,__,__,__,__,__,__,__,__],
+/*CKE*/[__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,H8,__,__,__,__],
+/*CCV*/[__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,H9,__,__,__,__,__],
+/*CCC*/[__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__],
+/*CFI*/[__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,H6],
+/*CAD*/[__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__],
+/*CER*/[__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__]
+];
+
+/**
+ * Generates the master_secret and keys using the given security parameters.
+ *
+ * The security parameters for a TLS connection state are defined as such:
+ *
+ * struct {
+ *   ConnectionEnd          entity;
+ *   PRFAlgorithm           prf_algorithm;
+ *   BulkCipherAlgorithm    bulk_cipher_algorithm;
+ *   CipherType             cipher_type;
+ *   uint8                  enc_key_length;
+ *   uint8                  block_length;
+ *   uint8                  fixed_iv_length;
+ *   uint8                  record_iv_length;
+ *   MACAlgorithm           mac_algorithm;
+ *   uint8                  mac_length;
+ *   uint8                  mac_key_length;
+ *   CompressionMethod      compression_algorithm;
+ *   opaque                 master_secret[48];
+ *   opaque                 client_random[32];
+ *   opaque                 server_random[32];
+ * } SecurityParameters;
+ *
+ * Note that this definition is from TLS 1.2. In TLS 1.0 some of these
+ * parameters are ignored because, for instance, the PRFAlgorithm is a
+ * builtin-fixed algorithm combining iterations of MD5 and SHA-1 in TLS 1.0.
+ *
+ * The Record Protocol requires an algorithm to generate keys required by the
+ * current connection state.
+ *
+ * The master secret is expanded into a sequence of secure bytes, which is then
+ * split to a client write MAC key, a server write MAC key, a client write
+ * encryption key, and a server write encryption key. In TLS 1.0 a client write
+ * IV and server write IV are also generated. Each of these is generated from
+ * the byte sequence in that order. Unused values are empty. In TLS 1.2, some
+ * AEAD ciphers may additionally require a client write IV and a server write
+ * IV (see Section 6.2.3.3).
+ *
+ * When keys, MAC keys, and IVs are generated, the master secret is used as an
+ * entropy source.
+ *
+ * To generate the key material, compute:
+ *
+ * master_secret = PRF(pre_master_secret, "master secret",
+ *                     ClientHello.random + ServerHello.random)
+ *
+ * key_block = PRF(SecurityParameters.master_secret,
+ *                 "key expansion",
+ *                 SecurityParameters.server_random +
+ *                 SecurityParameters.client_random);
+ *
+ * until enough output has been generated. Then, the key_block is
+ * partitioned as follows:
+ *
+ * client_write_MAC_key[SecurityParameters.mac_key_length]
+ * server_write_MAC_key[SecurityParameters.mac_key_length]
+ * client_write_key[SecurityParameters.enc_key_length]
+ * server_write_key[SecurityParameters.enc_key_length]
+ * client_write_IV[SecurityParameters.fixed_iv_length]
+ * server_write_IV[SecurityParameters.fixed_iv_length]
+ *
+ * In TLS 1.2, the client_write_IV and server_write_IV are only generated for
+ * implicit nonce techniques as described in Section 3.2.1 of [AEAD]. This
+ * implementation uses TLS 1.0 so IVs are generated.
+ *
+ * Implementation note: The currently defined cipher suite which requires the
+ * most material is AES_256_CBC_SHA256. It requires 2 x 32 byte keys and 2 x 32
+ * byte MAC keys, for a total 128 bytes of key material. In TLS 1.0 it also
+ * requires 2 x 16 byte IVs, so it actually takes 160 bytes of key material.
+ *
+ * @param c the connection.
+ * @param sp the security parameters to use.
+ *
+ * @return the security keys.
+ */
+tls.generateKeys = function(c, sp) {
+  // TLS_RSA_WITH_AES_128_CBC_SHA (required to be compliant with TLS 1.2) &
+  // TLS_RSA_WITH_AES_256_CBC_SHA are the only cipher suites implemented
+  // at present
+
+  // TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA is required to be compliant with
+  // TLS 1.0 but we don't care right now because AES is better and we have
+  // an implementation for it
+
+  // TODO: TLS 1.2 implementation
+  /*
+  // determine the PRF
+  var prf;
+  switch(sp.prf_algorithm) {
+  case tls.PRFAlgorithm.tls_prf_sha256:
+    prf = prf_sha256;
+    break;
+  default:
+    // should never happen
+    throw new Error('Invalid PRF');
+  }
+  */
+
+  // TLS 1.0/1.1 implementation
+  var prf = prf_TLS1;
+
+  // concatenate server and client random
+  var random = sp.client_random + sp.server_random;
+
+  // only create master secret if session is new
+  if(!c.session.resuming) {
+    // create master secret, clean up pre-master secret
+    sp.master_secret = prf(
+      sp.pre_master_secret, 'master secret', random, 48).bytes();
+    sp.pre_master_secret = null;
+  }
+
+  // generate the amount of key material needed
+  random = sp.server_random + sp.client_random;
+  var length = 2 * sp.mac_key_length + 2 * sp.enc_key_length;
+
+  // include IV for TLS/1.0
+  var tls10 = (c.version.major === tls.Versions.TLS_1_0.major &&
+    c.version.minor === tls.Versions.TLS_1_0.minor);
+  if(tls10) {
+    length += 2 * sp.fixed_iv_length;
+  }
+  var km = prf(sp.master_secret, 'key expansion', random, length);
+
+  // split the key material into the MAC and encryption keys
+  var rval = {
+    client_write_MAC_key: km.getBytes(sp.mac_key_length),
+    server_write_MAC_key: km.getBytes(sp.mac_key_length),
+    client_write_key: km.getBytes(sp.enc_key_length),
+    server_write_key: km.getBytes(sp.enc_key_length)
+  };
+
+  // include TLS 1.0 IVs
+  if(tls10) {
+    rval.client_write_IV = km.getBytes(sp.fixed_iv_length);
+    rval.server_write_IV = km.getBytes(sp.fixed_iv_length);
+  }
+
+  return rval;
+};
+
+/**
+ * Creates a new initialized TLS connection state. A connection state has
+ * a read mode and a write mode.
+ *
+ * compression state:
+ *   The current state of the compression algorithm.
+ *
+ * cipher state:
+ *   The current state of the encryption algorithm. This will consist of the
+ *   scheduled key for that connection. For stream ciphers, this will also
+ *   contain whatever state information is necessary to allow the stream to
+ *   continue to encrypt or decrypt data.
+ *
+ * MAC key:
+ *   The MAC key for the connection.
+ *
+ * sequence number:
+ *   Each connection state contains a sequence number, which is maintained
+ *   separately for read and write states. The sequence number MUST be set to
+ *   zero whenever a connection state is made the active state. Sequence
+ *   numbers are of type uint64 and may not exceed 2^64-1. Sequence numbers do
+ *   not wrap. If a TLS implementation would need to wrap a sequence number,
+ *   it must renegotiate instead. A sequence number is incremented after each
+ *   record: specifically, the first record transmitted under a particular
+ *   connection state MUST use sequence number 0.
+ *
+ * @param c the connection.
+ *
+ * @return the new initialized TLS connection state.
+ */
+tls.createConnectionState = function(c) {
+  var client = (c.entity === tls.ConnectionEnd.client);
+
+  var createMode = function() {
+    var mode = {
+      // two 32-bit numbers, first is most significant
+      sequenceNumber: [0, 0],
+      macKey: null,
+      macLength: 0,
+      macFunction: null,
+      cipherState: null,
+      cipherFunction: function(record) {return true;},
+      compressionState: null,
+      compressFunction: function(record) {return true;},
+      updateSequenceNumber: function() {
+        if(mode.sequenceNumber[1] === 0xFFFFFFFF) {
+          mode.sequenceNumber[1] = 0;
+          ++mode.sequenceNumber[0];
+        } else {
+          ++mode.sequenceNumber[1];
+        }
+      }
+    };
+    return mode;
+  };
+  var state = {
+    read: createMode(),
+    write: createMode()
+  };
+
+  // update function in read mode will decrypt then decompress a record
+  state.read.update = function(c, record) {
+    if(!state.read.cipherFunction(record, state.read)) {
+      c.error(c, {
+        message: 'Could not decrypt record or bad MAC.',
+        send: true,
+        alert: {
+          level: tls.Alert.Level.fatal,
+          // doesn't matter if decryption failed or MAC was
+          // invalid, return the same error so as not to reveal
+          // which one occurred
+          description: tls.Alert.Description.bad_record_mac
+        }
+      });
+    } else if(!state.read.compressFunction(c, record, state.read)) {
+      c.error(c, {
+        message: 'Could not decompress record.',
+        send: true,
+        alert: {
+          level: tls.Alert.Level.fatal,
+          description: tls.Alert.Description.decompression_failure
+        }
+      });
+    }
+    return !c.fail;
+  };
+
+  // update function in write mode will compress then encrypt a record
+  state.write.update = function(c, record) {
+    if(!state.write.compressFunction(c, record, state.write)) {
+      // error, but do not send alert since it would require
+      // compression as well
+      c.error(c, {
+        message: 'Could not compress record.',
+        send: false,
+        alert: {
+          level: tls.Alert.Level.fatal,
+          description: tls.Alert.Description.internal_error
+        }
+      });
+    } else if(!state.write.cipherFunction(record, state.write)) {
+      // error, but do not send alert since it would require
+      // encryption as well
+      c.error(c, {
+        message: 'Could not encrypt record.',
+        send: false,
+        alert: {
+          level: tls.Alert.Level.fatal,
+          description: tls.Alert.Description.internal_error
+        }
+      });
+    }
+    return !c.fail;
+  };
+
+  // handle security parameters
+  if(c.session) {
+    var sp = c.session.sp;
+    c.session.cipherSuite.initSecurityParameters(sp);
+
+    // generate keys
+    sp.keys = tls.generateKeys(c, sp);
+    state.read.macKey = client ?
+      sp.keys.server_write_MAC_key : sp.keys.client_write_MAC_key;
+    state.write.macKey = client ?
+      sp.keys.client_write_MAC_key : sp.keys.server_write_MAC_key;
+
+    // cipher suite setup
+    c.session.cipherSuite.initConnectionState(state, c, sp);
+
+    // compression setup
+    switch(sp.compression_algorithm) {
+    case tls.CompressionMethod.none:
+      break;
+    case tls.CompressionMethod.deflate:
+      state.read.compressFunction = inflate;
+      state.write.compressFunction = deflate;
+      break;
+    default:
+      throw new Error('Unsupported compression algorithm.');
+    }
+  }
+
+  return state;
+};
+
+/**
+ * Creates a Random structure.
+ *
+ * struct {
+ *   uint32 gmt_unix_time;
+ *   opaque random_bytes[28];
+ * } Random;
+ *
+ * gmt_unix_time:
+ *   The current time and date in standard UNIX 32-bit format (seconds since
+ *   the midnight starting Jan 1, 1970, UTC, ignoring leap seconds) according
+ *   to the sender's internal clock. Clocks are not required to be set
+ *   correctly by the basic TLS protocol; higher-level or application
+ *   protocols may define additional requirements. Note that, for historical
+ *   reasons, the data element is named using GMT, the predecessor of the
+ *   current worldwide time base, UTC.
+ * random_bytes:
+ *   28 bytes generated by a secure random number generator.
+ *
+ * @return the Random structure as a byte array.
+ */
+tls.createRandom = function() {
+  // get UTC milliseconds
+  var d = new Date();
+  var utc = +d + d.getTimezoneOffset() * 60000;
+  var rval = forge.util.createBuffer();
+  rval.putInt32(utc);
+  rval.putBytes(forge.random.getBytes(28));
+  return rval;
+};
+
+/**
+ * Creates a TLS record with the given type and data.
+ *
+ * @param c the connection.
+ * @param options:
+ *   type: the record type.
+ *   data: the plain text data in a byte buffer.
+ *
+ * @return the created record.
+ */
+tls.createRecord = function(c, options) {
+  if(!options.data) {
+    return null;
+  }
+  var record = {
+    type: options.type,
+    version: {
+      major: c.version.major,
+      minor: c.version.minor
+    },
+    length: options.data.length(),
+    fragment: options.data
+  };
+  return record;
+};
+
+/**
+ * Creates a TLS alert record.
+ *
+ * @param c the connection.
+ * @param alert:
+ *   level: the TLS alert level.
+ *   description: the TLS alert description.
+ *
+ * @return the created alert record.
+ */
+tls.createAlert = function(c, alert) {
+  var b = forge.util.createBuffer();
+  b.putByte(alert.level);
+  b.putByte(alert.description);
+  return tls.createRecord(c, {
+    type: tls.ContentType.alert,
+    data: b
+  });
+};
+
+/* The structure of a TLS handshake message.
+ *
+ * struct {
+ *    HandshakeType msg_type;    // handshake type
+ *    uint24 length;             // bytes in message
+ *    select(HandshakeType) {
+ *       case hello_request:       HelloRequest;
+ *       case client_hello:        ClientHello;
+ *       case server_hello:        ServerHello;
+ *       case certificate:         Certificate;
+ *       case server_key_exchange: ServerKeyExchange;
+ *       case certificate_request: CertificateRequest;
+ *       case server_hello_done:   ServerHelloDone;
+ *       case certificate_verify:  CertificateVerify;
+ *       case client_key_exchange: ClientKeyExchange;
+ *       case finished:            Finished;
+ *    } body;
+ * } Handshake;
+ */
+
+/**
+ * Creates a ClientHello message.
+ *
+ * opaque SessionID<0..32>;
+ * enum { null(0), deflate(1), (255) } CompressionMethod;
+ * uint8 CipherSuite[2];
+ *
+ * struct {
+ *   ProtocolVersion client_version;
+ *   Random random;
+ *   SessionID session_id;
+ *   CipherSuite cipher_suites<2..2^16-2>;
+ *   CompressionMethod compression_methods<1..2^8-1>;
+ *   select(extensions_present) {
+ *     case false:
+ *       struct {};
+ *     case true:
+ *       Extension extensions<0..2^16-1>;
+ *   };
+ * } ClientHello;
+ *
+ * The extension format for extended client hellos and server hellos is:
+ *
+ * struct {
+ *   ExtensionType extension_type;
+ *   opaque extension_data<0..2^16-1>;
+ * } Extension;
+ *
+ * Here:
+ *
+ * - "extension_type" identifies the particular extension type.
+ * - "extension_data" contains information specific to the particular
+ * extension type.
+ *
+ * The extension types defined in this document are:
+ *
+ * enum {
+ *   server_name(0), max_fragment_length(1),
+ *   client_certificate_url(2), trusted_ca_keys(3),
+ *   truncated_hmac(4), status_request(5), (65535)
+ * } ExtensionType;
+ *
+ * @param c the connection.
+ *
+ * @return the ClientHello byte buffer.
+ */
+tls.createClientHello = function(c) {
+  // save hello version
+  c.session.clientHelloVersion = {
+    major: c.version.major,
+    minor: c.version.minor
+  };
+
+  // create supported cipher suites
+  var cipherSuites = forge.util.createBuffer();
+  for(var i = 0; i < c.cipherSuites.length; ++i) {
+    var cs = c.cipherSuites[i];
+    cipherSuites.putByte(cs.id[0]);
+    cipherSuites.putByte(cs.id[1]);
+  }
+  var cSuites = cipherSuites.length();
+
+  // create supported compression methods, null always supported, but
+  // also support deflate if connection has inflate and deflate methods
+  var compressionMethods = forge.util.createBuffer();
+  compressionMethods.putByte(tls.CompressionMethod.none);
+  // FIXME: deflate support disabled until issues with raw deflate data
+  // without zlib headers are resolved
+  /*
+  if(c.inflate !== null && c.deflate !== null) {
+    compressionMethods.putByte(tls.CompressionMethod.deflate);
+  }
+  */
+  var cMethods = compressionMethods.length();
+
+  // create TLS SNI (server name indication) extension if virtual host
+  // has been specified, see RFC 3546
+  var extensions = forge.util.createBuffer();
+  if(c.virtualHost) {
+    // create extension struct
+    var ext = forge.util.createBuffer();
+    ext.putByte(0x00); // type server_name (ExtensionType is 2 bytes)
+    ext.putByte(0x00);
+
+    /* In order to provide the server name, clients MAY include an
+     * extension of type "server_name" in the (extended) client hello.
+     * The "extension_data" field of this extension SHALL contain
+     * "ServerNameList" where:
+     *
+     * struct {
+     *   NameType name_type;
+     *   select(name_type) {
+     *     case host_name: HostName;
+     *   } name;
+     * } ServerName;
+     *
+     * enum {
+     *   host_name(0), (255)
+     * } NameType;
+     *
+     * opaque HostName<1..2^16-1>;
+     *
+     * struct {
+     *   ServerName server_name_list<1..2^16-1>
+     * } ServerNameList;
+     */
+    var serverName = forge.util.createBuffer();
+    serverName.putByte(0x00); // type host_name
+    writeVector(serverName, 2, forge.util.createBuffer(c.virtualHost));
+
+    // ServerNameList is in extension_data
+    var snList = forge.util.createBuffer();
+    writeVector(snList, 2, serverName);
+    writeVector(ext, 2, snList);
+    extensions.putBuffer(ext);
+  }
+  var extLength = extensions.length();
+  if(extLength > 0) {
+    // add extension vector length
+    extLength += 2;
+  }
+
+  // determine length of the handshake message
+  // cipher suites and compression methods size will need to be
+  // updated if more get added to the list
+  var sessionId = c.session.id;
+  var length =
+    sessionId.length + 1 + // session ID vector
+    2 +                    // version (major + minor)
+    4 + 28 +               // random time and random bytes
+    2 + cSuites +          // cipher suites vector
+    1 + cMethods +         // compression methods vector
+    extLength;             // extensions vector
+
+  // build record fragment
+  var rval = forge.util.createBuffer();
+  rval.putByte(tls.HandshakeType.client_hello);
+  rval.putInt24(length);                     // handshake length
+  rval.putByte(c.version.major);             // major version
+  rval.putByte(c.version.minor);             // minor version
+  rval.putBytes(c.session.sp.client_random); // random time + bytes
+  writeVector(rval, 1, forge.util.createBuffer(sessionId));
+  writeVector(rval, 2, cipherSuites);
+  writeVector(rval, 1, compressionMethods);
+  if(extLength > 0) {
+    writeVector(rval, 2, extensions);
+  }
+  return rval;
+};
+
+/**
+ * Creates a ServerHello message.
+ *
+ * @param c the connection.
+ *
+ * @return the ServerHello byte buffer.
+ */
+tls.createServerHello = function(c) {
+  // determine length of the handshake message
+  var sessionId = c.session.id;
+  var length =
+    sessionId.length + 1 + // session ID vector
+    2 +                    // version (major + minor)
+    4 + 28 +               // random time and random bytes
+    2 +                    // chosen cipher suite
+    1;                     // chosen compression method
+
+  // build record fragment
+  var rval = forge.util.createBuffer();
+  rval.putByte(tls.HandshakeType.server_hello);
+  rval.putInt24(length);                     // handshake length
+  rval.putByte(c.version.major);             // major version
+  rval.putByte(c.version.minor);             // minor version
+  rval.putBytes(c.session.sp.server_random); // random time + bytes
+  writeVector(rval, 1, forge.util.createBuffer(sessionId));
+  rval.putByte(c.session.cipherSuite.id[0]);
+  rval.putByte(c.session.cipherSuite.id[1]);
+  rval.putByte(c.session.compressionMethod);
+  return rval;
+};
+
+/**
+ * Creates a Certificate message.
+ *
+ * When this message will be sent:
+ *   This is the first message the client can send after receiving a server
+ *   hello done message and the first message the server can send after
+ *   sending a ServerHello. This client message is only sent if the server
+ *   requests a certificate. If no suitable certificate is available, the
+ *   client should send a certificate message containing no certificates. If
+ *   client authentication is required by the server for the handshake to
+ *   continue, it may respond with a fatal handshake failure alert.
+ *
+ * opaque ASN.1Cert<1..2^24-1>;
+ *
+ * struct {
+ *   ASN.1Cert certificate_list<0..2^24-1>;
+ * } Certificate;
+ *
+ * @param c the connection.
+ *
+ * @return the Certificate byte buffer.
+ */
+tls.createCertificate = function(c) {
+  // TODO: check certificate request to ensure types are supported
+
+  // get a certificate (a certificate as a PEM string)
+  var client = (c.entity === tls.ConnectionEnd.client);
+  var cert = null;
+  if(c.getCertificate) {
+    var hint;
+    if(client) {
+      hint = c.session.certificateRequest;
+    } else {
+      hint = c.session.extensions.server_name.serverNameList;
+    }
+    cert = c.getCertificate(c, hint);
+  }
+
+  // buffer to hold certificate list
+  var certList = forge.util.createBuffer();
+  if(cert !== null) {
+    try {
+      // normalize cert to a chain of certificates
+      if(!forge.util.isArray(cert)) {
+        cert = [cert];
+      }
+      var asn1 = null;
+      for(var i = 0; i < cert.length; ++i) {
+        var msg = forge.pem.decode(cert[i])[0];
+        if(msg.type !== 'CERTIFICATE' &&
+          msg.type !== 'X509 CERTIFICATE' &&
+          msg.type !== 'TRUSTED CERTIFICATE') {
+          var error = new Error('Could not convert certificate from PEM; PEM ' +
+            'header type is not "CERTIFICATE", "X509 CERTIFICATE", or ' +
+            '"TRUSTED CERTIFICATE".');
+          error.headerType = msg.type;
+          throw error;
+        }
+        if(msg.procType && msg.procType.type === 'ENCRYPTED') {
+          throw new Error('Could not convert certificate from PEM; PEM is encrypted.');
+        }
+
+        var der = forge.util.createBuffer(msg.body);
+        if(asn1 === null) {
+          asn1 = forge.asn1.fromDer(der.bytes(), false);
+        }
+
+        // certificate entry is itself a vector with 3 length bytes
+        var certBuffer = forge.util.createBuffer();
+        writeVector(certBuffer, 3, der);
+
+        // add cert vector to cert list vector
+        certList.putBuffer(certBuffer);
+      }
+
+      // save certificate
+      cert = forge.pki.certificateFromAsn1(asn1);
+      if(client) {
+        c.session.clientCertificate = cert;
+      } else {
+        c.session.serverCertificate = cert;
+      }
+    } catch(ex) {
+      return c.error(c, {
+        message: 'Could not send certificate list.',
+        cause: ex,
+        send: true,
+        alert: {
+          level: tls.Alert.Level.fatal,
+          description: tls.Alert.Description.bad_certificate
+        }
+      });
+    }
+  }
+
+  // determine length of the handshake message
+  var length = 3 + certList.length(); // cert list vector
+
+  // build record fragment
+  var rval = forge.util.createBuffer();
+  rval.putByte(tls.HandshakeType.certificate);
+  rval.putInt24(length);
+  writeVector(rval, 3, certList);
+  return rval;
+};
+
+/**
+ * Creates a ClientKeyExchange message.
+ *
+ * When this message will be sent:
+ *   This message is always sent by the client. It will immediately follow the
+ *   client certificate message, if it is sent. Otherwise it will be the first
+ *   message sent by the client after it receives the server hello done
+ *   message.
+ *
+ * Meaning of this message:
+ *   With this message, the premaster secret is set, either though direct
+ *   transmission of the RSA-encrypted secret, or by the transmission of
+ *   Diffie-Hellman parameters which will allow each side to agree upon the
+ *   same premaster secret. When the key exchange method is DH_RSA or DH_DSS,
+ *   client certification has been requested, and the client was able to
+ *   respond with a certificate which contained a Diffie-Hellman public key
+ *   whose parameters (group and generator) matched those specified by the
+ *   server in its certificate, this message will not contain any data.
+ *
+ * Meaning of this message:
+ *   If RSA is being used for key agreement and authentication, the client
+ *   generates a 48-byte premaster secret, encrypts it using the public key
+ *   from the server's certificate or the temporary RSA key provided in a
+ *   server key exchange message, and sends the result in an encrypted
+ *   premaster secret message. This structure is a variant of the client
+ *   key exchange message, not a message in itself.
+ *
+ * struct {
+ *   select(KeyExchangeAlgorithm) {
+ *     case rsa: EncryptedPreMasterSecret;
+ *     case diffie_hellman: ClientDiffieHellmanPublic;
+ *   } exchange_keys;
+ * } ClientKeyExchange;
+ *
+ * struct {
+ *   ProtocolVersion client_version;
+ *   opaque random[46];
+ * } PreMasterSecret;
+ *
+ * struct {
+ *   public-key-encrypted PreMasterSecret pre_master_secret;
+ * } EncryptedPreMasterSecret;
+ *
+ * A public-key-encrypted element is encoded as a vector <0..2^16-1>.
+ *
+ * @param c the connection.
+ *
+ * @return the ClientKeyExchange byte buffer.
+ */
+tls.createClientKeyExchange = function(c) {
+  // create buffer to encrypt
+  var b = forge.util.createBuffer();
+
+  // add highest client-supported protocol to help server avoid version
+  // rollback attacks
+  b.putByte(c.session.clientHelloVersion.major);
+  b.putByte(c.session.clientHelloVersion.minor);
+
+  // generate and add 46 random bytes
+  b.putBytes(forge.random.getBytes(46));
+
+  // save pre-master secret
+  var sp = c.session.sp;
+  sp.pre_master_secret = b.getBytes();
+
+  // RSA-encrypt the pre-master secret
+  var key = c.session.serverCertificate.publicKey;
+  b = key.encrypt(sp.pre_master_secret);
+
+  /* Note: The encrypted pre-master secret will be stored in a
+    public-key-encrypted opaque vector that has the length prefixed using
+    2 bytes, so include those 2 bytes in the handshake message length. This
+    is done as a minor optimization instead of calling writeVector(). */
+
+  // determine length of the handshake message
+  var length = b.length + 2;
+
+  // build record fragment
+  var rval = forge.util.createBuffer();
+  rval.putByte(tls.HandshakeType.client_key_exchange);
+  rval.putInt24(length);
+  // add vector length bytes
+  rval.putInt16(b.length);
+  rval.putBytes(b);
+  return rval;
+};
+
+/**
+ * Creates a ServerKeyExchange message.
+ *
+ * @param c the connection.
+ *
+ * @return the ServerKeyExchange byte buffer.
+ */
+tls.createServerKeyExchange = function(c) {
+  // this implementation only supports RSA, no Diffie-Hellman support,
+  // so this record is empty
+
+  // determine length of the handshake message
+  var length = 0;
+
+  // build record fragment
+  var rval = forge.util.createBuffer();
+  if(length > 0) {
+    rval.putByte(tls.HandshakeType.server_key_exchange);
+    rval.putInt24(length);
+  }
+  return rval;
+};
+
+/**
+ * Gets the signed data used to verify a client-side certificate. See
+ * tls.createCertificateVerify() for details.
+ *
+ * @param c the connection.
+ * @param callback the callback to call once the signed data is ready.
+ */
+tls.getClientSignature = function(c, callback) {
+  // generate data to RSA encrypt
+  var b = forge.util.createBuffer();
+  b.putBuffer(c.session.md5.digest());
+  b.putBuffer(c.session.sha1.digest());
+  b = b.getBytes();
+
+  // create default signing function as necessary
+  c.getSignature = c.getSignature || function(c, b, callback) {
+    // do rsa encryption, call callback
+    var privateKey = null;
+    if(c.getPrivateKey) {
+      try {
+        privateKey = c.getPrivateKey(c, c.session.clientCertificate);
+        privateKey = forge.pki.privateKeyFromPem(privateKey);
+      } catch(ex) {
+        c.error(c, {
+          message: 'Could not get private key.',
+          cause: ex,
+          send: true,
+          alert: {
+            level: tls.Alert.Level.fatal,
+            description: tls.Alert.Description.internal_error
+          }
+        });
+      }
+    }
+    if(privateKey === null) {
+      c.error(c, {
+        message: 'No private key set.',
+        send: true,
+        alert: {
+          level: tls.Alert.Level.fatal,
+          description: tls.Alert.Description.internal_error
+        }
+      });
+    } else {
+      b = privateKey.sign(b, null);
+    }
+    callback(c, b);
+  };
+
+  // get client signature
+  c.getSignature(c, b, callback);
+};
+
+/**
+ * Creates a CertificateVerify message.
+ *
+ * Meaning of this message:
+ *   This structure conveys the client's Diffie-Hellman public value
+ *   (Yc) if it was not already included in the client's certificate.
+ *   The encoding used for Yc is determined by the enumerated
+ *   PublicValueEncoding. This structure is a variant of the client
+ *   key exchange message, not a message in itself.
+ *
+ * When this message will be sent:
+ *   This message is used to provide explicit verification of a client
+ *   certificate. This message is only sent following a client
+ *   certificate that has signing capability (i.e. all certificates
+ *   except those containing fixed Diffie-Hellman parameters). When
+ *   sent, it will immediately follow the client key exchange message.
+ *
+ * struct {
+ *   Signature signature;
+ * } CertificateVerify;
+ *
+ * CertificateVerify.signature.md5_hash
+ *   MD5(handshake_messages);
+ *
+ * Certificate.signature.sha_hash
+ *   SHA(handshake_messages);
+ *
+ * Here handshake_messages refers to all handshake messages sent or
+ * received starting at client hello up to but not including this
+ * message, including the type and length fields of the handshake
+ * messages.
+ *
+ * select(SignatureAlgorithm) {
+ *   case anonymous: struct { };
+ *   case rsa:
+ *     digitally-signed struct {
+ *       opaque md5_hash[16];
+ *       opaque sha_hash[20];
+ *     };
+ *   case dsa:
+ *     digitally-signed struct {
+ *       opaque sha_hash[20];
+ *     };
+ * } Signature;
+ *
+ * In digital signing, one-way hash functions are used as input for a
+ * signing algorithm. A digitally-signed element is encoded as an opaque
+ * vector <0..2^16-1>, where the length is specified by the signing
+ * algorithm and key.
+ *
+ * In RSA signing, a 36-byte structure of two hashes (one SHA and one
+ * MD5) is signed (encrypted with the private key). It is encoded with
+ * PKCS #1 block type 0 or type 1 as described in [PKCS1].
+ *
+ * In DSS, the 20 bytes of the SHA hash are run directly through the
+ * Digital Signing Algorithm with no additional hashing.
+ *
+ * @param c the connection.
+ * @param signature the signature to include in the message.
+ *
+ * @return the CertificateVerify byte buffer.
+ */
+tls.createCertificateVerify = function(c, signature) {
+  /* Note: The signature will be stored in a "digitally-signed" opaque
+    vector that has the length prefixed using 2 bytes, so include those
+    2 bytes in the handshake message length. This is done as a minor
+    optimization instead of calling writeVector(). */
+
+  // determine length of the handshake message
+  var length = signature.length + 2;
+
+  // build record fragment
+  var rval = forge.util.createBuffer();
+  rval.putByte(tls.HandshakeType.certificate_verify);
+  rval.putInt24(length);
+  // add vector length bytes
+  rval.putInt16(signature.length);
+  rval.putBytes(signature);
+  return rval;
+};
+
+/**
+ * Creates a CertificateRequest message.
+ *
+ * @param c the connection.
+ *
+ * @return the CertificateRequest byte buffer.
+ */
+tls.createCertificateRequest = function(c) {
+  // TODO: support other certificate types
+  var certTypes = forge.util.createBuffer();
+
+  // common RSA certificate type
+  certTypes.putByte(0x01);
+
+  // TODO: verify that this data format is correct
+  // add distinguished names from CA store
+  var cAs = forge.util.createBuffer();
+  for(var key in c.caStore.certs) {
+    var cert = c.caStore.certs[key];
+    var dn = forge.pki.distinguishedNameToAsn1(cert.subject);
+    cAs.putBuffer(forge.asn1.toDer(dn));
+  }
+
+  // TODO: TLS 1.2+ has a different format
+
+  // determine length of the handshake message
+  var length =
+    1 + certTypes.length() +
+    2 + cAs.length();
+
+  // build record fragment
+  var rval = forge.util.createBuffer();
+  rval.putByte(tls.HandshakeType.certificate_request);
+  rval.putInt24(length);
+  writeVector(rval, 1, certTypes);
+  writeVector(rval, 2, cAs);
+  return rval;
+};
+
+/**
+ * Creates a ServerHelloDone message.
+ *
+ * @param c the connection.
+ *
+ * @return the ServerHelloDone byte buffer.
+ */
+tls.createServerHelloDone = function(c) {
+  // build record fragment
+  var rval = forge.util.createBuffer();
+  rval.putByte(tls.HandshakeType.server_hello_done);
+  rval.putInt24(0);
+  return rval;
+};
+
+/**
+ * Creates a ChangeCipherSpec message.
+ *
+ * The change cipher spec protocol exists to signal transitions in
+ * ciphering strategies. The protocol consists of a single message,
+ * which is encrypted and compressed under the current (not the pending)
+ * connection state. The message consists of a single byte of value 1.
+ *
+ * struct {
+ *   enum { change_cipher_spec(1), (255) } type;
+ * } ChangeCipherSpec;
+ *
+ * @return the ChangeCipherSpec byte buffer.
+ */
+tls.createChangeCipherSpec = function() {
+  var rval = forge.util.createBuffer();
+  rval.putByte(0x01);
+  return rval;
+};
+
+/**
+ * Creates a Finished message.
+ *
+ * struct {
+ *   opaque verify_data[12];
+ * } Finished;
+ *
+ * verify_data
+ *   PRF(master_secret, finished_label, MD5(handshake_messages) +
+ *   SHA-1(handshake_messages)) [0..11];
+ *
+ * finished_label
+ *   For Finished messages sent by the client, the string "client
+ *   finished". For Finished messages sent by the server, the
+ *   string "server finished".
+ *
+ * handshake_messages
+ *   All of the data from all handshake messages up to but not
+ *   including this message. This is only data visible at the
+ *   handshake layer and does not include record layer headers.
+ *   This is the concatenation of all the Handshake structures as
+ *   defined in 7.4 exchanged thus far.
+ *
+ * @param c the connection.
+ *
+ * @return the Finished byte buffer.
+ */
+tls.createFinished = function(c) {
+  // generate verify_data
+  var b = forge.util.createBuffer();
+  b.putBuffer(c.session.md5.digest());
+  b.putBuffer(c.session.sha1.digest());
+
+  // TODO: determine prf function and verify length for TLS 1.2
+  var client = (c.entity === tls.ConnectionEnd.client);
+  var sp = c.session.sp;
+  var vdl = 12;
+  var prf = prf_TLS1;
+  var label = client ? 'client finished' : 'server finished';
+  b = prf(sp.master_secret, label, b.getBytes(), vdl);
+
+  // build record fragment
+  var rval = forge.util.createBuffer();
+  rval.putByte(tls.HandshakeType.finished);
+  rval.putInt24(b.length());
+  rval.putBuffer(b);
+  return rval;
+};
+
+/**
+ * Creates a HeartbeatMessage (See RFC 6520).
+ *
+ * struct {
+ *   HeartbeatMessageType type;
+ *   uint16 payload_length;
+ *   opaque payload[HeartbeatMessage.payload_length];
+ *   opaque padding[padding_length];
+ * } HeartbeatMessage;
+ *
+ * The total length of a HeartbeatMessage MUST NOT exceed 2^14 or
+ * max_fragment_length when negotiated as defined in [RFC6066].
+ *
+ * type: The message type, either heartbeat_request or heartbeat_response.
+ *
+ * payload_length: The length of the payload.
+ *
+ * payload: The payload consists of arbitrary content.
+ *
+ * padding: The padding is random content that MUST be ignored by the
+ *   receiver. The length of a HeartbeatMessage is TLSPlaintext.length
+ *   for TLS and DTLSPlaintext.length for DTLS. Furthermore, the
+ *   length of the type field is 1 byte, and the length of the
+ *   payload_length is 2. Therefore, the padding_length is
+ *   TLSPlaintext.length - payload_length - 3 for TLS and
+ *   DTLSPlaintext.length - payload_length - 3 for DTLS. The
+ *   padding_length MUST be at least 16.
+ *
+ * The sender of a HeartbeatMessage MUST use a random padding of at
+ * least 16 bytes. The padding of a received HeartbeatMessage message
+ * MUST be ignored.
+ *
+ * If the payload_length of a received HeartbeatMessage is too large,
+ * the received HeartbeatMessage MUST be discarded silently.
+ *
+ * @param c the connection.
+ * @param type the tls.HeartbeatMessageType.
+ * @param payload the heartbeat data to send as the payload.
+ * @param [payloadLength] the payload length to use, defaults to the
+ *          actual payload length.
+ *
+ * @return the HeartbeatRequest byte buffer.
+ */
+tls.createHeartbeat = function(type, payload, payloadLength) {
+  if(typeof payloadLength === 'undefined') {
+    payloadLength = payload.length;
+  }
+  // build record fragment
+  var rval = forge.util.createBuffer();
+  rval.putByte(type);               // heartbeat message type
+  rval.putInt16(payloadLength);     // payload length
+  rval.putBytes(payload);           // payload
+  // padding
+  var plaintextLength = rval.length();
+  var paddingLength = Math.max(16, plaintextLength - payloadLength - 3);
+  rval.putBytes(forge.random.getBytes(paddingLength));
+  return rval;
+};
+
+/**
+ * Fragments, compresses, encrypts, and queues a record for delivery.
+ *
+ * @param c the connection.
+ * @param record the record to queue.
+ */
+tls.queue = function(c, record) {
+  // error during record creation
+  if(!record) {
+    return;
+  }
+
+  // if the record is a handshake record, update handshake hashes
+  if(record.type === tls.ContentType.handshake) {
+    var bytes = record.fragment.bytes();
+    c.session.md5.update(bytes);
+    c.session.sha1.update(bytes);
+    bytes = null;
+  }
+
+  // handle record fragmentation
+  var records;
+  if(record.fragment.length() <= tls.MaxFragment) {
+    records = [record];
+  } else {
+    // fragment data as long as it is too long
+    records = [];
+    var data = record.fragment.bytes();
+    while(data.length > tls.MaxFragment) {
+      records.push(tls.createRecord(c, {
+        type: record.type,
+        data: forge.util.createBuffer(data.slice(0, tls.MaxFragment))
+      }));
+      data = data.slice(tls.MaxFragment);
+    }
+    // add last record
+    if(data.length > 0) {
+      records.push(tls.createRecord(c, {
+        type: record.type,
+        data: forge.util.createBuffer(data)
+      }));
+    }
+  }
+
+  // compress and encrypt all fragmented records
+  for(var i = 0; i < records.length && !c.fail; ++i) {
+    // update the record using current write state
+    var rec = records[i];
+    var s = c.state.current.write;
+    if(s.update(c, rec)) {
+      // store record
+      c.records.push(rec);
+    }
+  }
+};
+
+/**
+ * Flushes all queued records to the output buffer and calls the
+ * tlsDataReady() handler on the given connection.
+ *
+ * @param c the connection.
+ *
+ * @return true on success, false on failure.
+ */
+tls.flush = function(c) {
+  for(var i = 0; i < c.records.length; ++i) {
+    var record = c.records[i];
+
+    // add record header and fragment
+    c.tlsData.putByte(record.type);
+    c.tlsData.putByte(record.version.major);
+    c.tlsData.putByte(record.version.minor);
+    c.tlsData.putInt16(record.fragment.length());
+    c.tlsData.putBuffer(c.records[i].fragment);
+  }
+  c.records = [];
+  return c.tlsDataReady(c);
+};
+
+/**
+ * Maps a pki.certificateError to a tls.Alert.Description.
+ *
+ * @param error the error to map.
+ *
+ * @return the alert description.
+ */
+var _certErrorToAlertDesc = function(error) {
+  switch(error) {
+  case true:
+    return true;
+  case forge.pki.certificateError.bad_certificate:
+    return tls.Alert.Description.bad_certificate;
+  case forge.pki.certificateError.unsupported_certificate:
+    return tls.Alert.Description.unsupported_certificate;
+  case forge.pki.certificateError.certificate_revoked:
+    return tls.Alert.Description.certificate_revoked;
+  case forge.pki.certificateError.certificate_expired:
+    return tls.Alert.Description.certificate_expired;
+  case forge.pki.certificateError.certificate_unknown:
+    return tls.Alert.Description.certificate_unknown;
+  case forge.pki.certificateError.unknown_ca:
+    return tls.Alert.Description.unknown_ca;
+  default:
+    return tls.Alert.Description.bad_certificate;
+  }
+};
+
+/**
+ * Maps a tls.Alert.Description to a pki.certificateError.
+ *
+ * @param desc the alert description.
+ *
+ * @return the certificate error.
+ */
+var _alertDescToCertError = function(desc) {
+  switch(desc) {
+  case true:
+    return true;
+  case tls.Alert.Description.bad_certificate:
+    return forge.pki.certificateError.bad_certificate;
+  case tls.Alert.Description.unsupported_certificate:
+    return forge.pki.certificateError.unsupported_certificate;
+  case tls.Alert.Description.certificate_revoked:
+    return forge.pki.certificateError.certificate_revoked;
+  case tls.Alert.Description.certificate_expired:
+    return forge.pki.certificateError.certificate_expired;
+  case tls.Alert.Description.certificate_unknown:
+    return forge.pki.certificateError.certificate_unknown;
+  case tls.Alert.Description.unknown_ca:
+    return forge.pki.certificateError.unknown_ca;
+  default:
+    return forge.pki.certificateError.bad_certificate;
+  }
+};
+
+/**
+ * Verifies a certificate chain against the given connection's
+ * Certificate Authority store.
+ *
+ * @param c the TLS connection.
+ * @param chain the certificate chain to verify, with the root or highest
+ *          authority at the end.
+ *
+ * @return true if successful, false if not.
+ */
+tls.verifyCertificateChain = function(c, chain) {
+  try {
+    // verify chain
+    forge.pki.verifyCertificateChain(c.caStore, chain,
+      function verify(vfd, depth, chain) {
+        // convert pki.certificateError to tls alert description
+        var desc = _certErrorToAlertDesc(vfd);
+
+        // call application callback
+        var ret = c.verify(c, vfd, depth, chain);
+        if(ret !== true) {
+          if(typeof ret === 'object' && !forge.util.isArray(ret)) {
+            // throw custom error
+            var error = new Error('The application rejected the certificate.');
+            error.send = true;
+            error.alert = {
+              level: tls.Alert.Level.fatal,
+              description: tls.Alert.Description.bad_certificate
+            };
+            if(ret.message) {
+              error.message = ret.message;
+            }
+            if(ret.alert) {
+              error.alert.description = ret.alert;
+            }
+            throw error;
+          }
+
+          // convert tls alert description to pki.certificateError
+          if(ret !== vfd) {
+            ret = _alertDescToCertError(ret);
+          }
+        }
+
+        return ret;
+      });
+  } catch(ex) {
+    // build tls error if not already customized
+    var err = ex;
+    if(typeof err !== 'object' || forge.util.isArray(err)) {
+      err = {
+        send: true,
+        alert: {
+          level: tls.Alert.Level.fatal,
+          description: _certErrorToAlertDesc(ex)
+        }
+      };
+    }
+    if(!('send' in err)) {
+      err.send = true;
+    }
+    if(!('alert' in err)) {
+      err.alert = {
+        level: tls.Alert.Level.fatal,
+        description: _certErrorToAlertDesc(err.error)
+      };
+    }
+
+    // send error
+    c.error(c, err);
+  }
+
+  return !c.fail;
+};
+
+/**
+ * Creates a new TLS session cache.
+ *
+ * @param cache optional map of session ID to cached session.
+ * @param capacity the maximum size for the cache (default: 100).
+ *
+ * @return the new TLS session cache.
+ */
+tls.createSessionCache = function(cache, capacity) {
+  var rval = null;
+
+  // assume input is already a session cache object
+  if(cache && cache.getSession && cache.setSession && cache.order) {
+    rval = cache;
+  } else {
+    // create cache
+    rval = {};
+    rval.cache = cache || {};
+    rval.capacity = Math.max(capacity || 100, 1);
+    rval.order = [];
+
+    // store order for sessions, delete session overflow
+    for(var key in cache) {
+      if(rval.order.length <= capacity) {
+        rval.order.push(key);
+      } else {
+        delete cache[key];
+      }
+    }
+
+    // get a session from a session ID (or get any session)
+    rval.getSession = function(sessionId) {
+      var session = null;
+      var key = null;
+
+      // if session ID provided, use it
+      if(sessionId) {
+        key = forge.util.bytesToHex(sessionId);
+      } else if(rval.order.length > 0) {
+        // get first session from cache
+        key = rval.order[0];
+      }
+
+      if(key !== null && key in rval.cache) {
+        // get cached session and remove from cache
+        session = rval.cache[key];
+        delete rval.cache[key];
+        for(var i in rval.order) {
+          if(rval.order[i] === key) {
+            rval.order.splice(i, 1);
+            break;
+          }
+        }
+      }
+
+      return session;
+    };
+
+    // set a session in the cache
+    rval.setSession = function(sessionId, session) {
+      // remove session from cache if at capacity
+      if(rval.order.length === rval.capacity) {
+        var key = rval.order.shift();
+        delete rval.cache[key];
+      }
+      // add session to cache
+      var key = forge.util.bytesToHex(sessionId);
+      rval.order.push(key);
+      rval.cache[key] = session;
+    };
+  }
+
+  return rval;
+};
+
+/**
+ * Creates a new TLS connection.
+ *
+ * See public createConnection() docs for more details.
+ *
+ * @param options the options for this connection.
+ *
+ * @return the new TLS connection.
+ */
+tls.createConnection = function(options) {
+  var caStore = null;
+  if(options.caStore) {
+    // if CA store is an array, convert it to a CA store object
+    if(forge.util.isArray(options.caStore)) {
+      caStore = forge.pki.createCaStore(options.caStore);
+    } else {
+      caStore = options.caStore;
+    }
+  } else {
+    // create empty CA store
+    caStore = forge.pki.createCaStore();
+  }
+
+  // setup default cipher suites
+  var cipherSuites = options.cipherSuites || null;
+  if(cipherSuites === null) {
+    cipherSuites = [];
+    for(var key in tls.CipherSuites) {
+      cipherSuites.push(tls.CipherSuites[key]);
+    }
+  }
+
+  // set default entity
+  var entity = (options.server || false) ?
+    tls.ConnectionEnd.server : tls.ConnectionEnd.client;
+
+  // create session cache if requested
+  var sessionCache = options.sessionCache ?
+    tls.createSessionCache(options.sessionCache) : null;
+
+  // create TLS connection
+  var c = {
+    version: {major: tls.Version.major, minor: tls.Version.minor},
+    entity: entity,
+    sessionId: options.sessionId,
+    caStore: caStore,
+    sessionCache: sessionCache,
+    cipherSuites: cipherSuites,
+    connected: options.connected,
+    virtualHost: options.virtualHost || null,
+    verifyClient: options.verifyClient || false,
+    verify: options.verify || function(cn, vfd, dpth, cts) {return vfd;},
+    getCertificate: options.getCertificate || null,
+    getPrivateKey: options.getPrivateKey || null,
+    getSignature: options.getSignature || null,
+    input: forge.util.createBuffer(),
+    tlsData: forge.util.createBuffer(),
+    data: forge.util.createBuffer(),
+    tlsDataReady: options.tlsDataReady,
+    dataReady: options.dataReady,
+    heartbeatReceived: options.heartbeatReceived,
+    closed: options.closed,
+    error: function(c, ex) {
+      // set origin if not set
+      ex.origin = ex.origin ||
+        ((c.entity === tls.ConnectionEnd.client) ? 'client' : 'server');
+
+      // send TLS alert
+      if(ex.send) {
+        tls.queue(c, tls.createAlert(c, ex.alert));
+        tls.flush(c);
+      }
+
+      // error is fatal by default
+      var fatal = (ex.fatal !== false);
+      if(fatal) {
+        // set fail flag
+        c.fail = true;
+      }
+
+      // call error handler first
+      options.error(c, ex);
+
+      if(fatal) {
+        // fatal error, close connection, do not clear fail
+        c.close(false);
+      }
+    },
+    deflate: options.deflate || null,
+    inflate: options.inflate || null
+  };
+
+  /**
+   * Resets a closed TLS connection for reuse. Called in c.close().
+   *
+   * @param clearFail true to clear the fail flag (default: true).
+   */
+  c.reset = function(clearFail) {
+    c.version = {major: tls.Version.major, minor: tls.Version.minor};
+    c.record = null;
+    c.session = null;
+    c.peerCertificate = null;
+    c.state = {
+      pending: null,
+      current: null
+    };
+    c.expect = (c.entity === tls.ConnectionEnd.client) ? SHE : CHE;
+    c.fragmented = null;
+    c.records = [];
+    c.open = false;
+    c.handshakes = 0;
+    c.handshaking = false;
+    c.isConnected = false;
+    c.fail = !(clearFail || typeof(clearFail) === 'undefined');
+    c.input.clear();
+    c.tlsData.clear();
+    c.data.clear();
+    c.state.current = tls.createConnectionState(c);
+  };
+
+  // do initial reset of connection
+  c.reset();
+
+  /**
+   * Updates the current TLS engine state based on the given record.
+   *
+   * @param c the TLS connection.
+   * @param record the TLS record to act on.
+   */
+  var _update = function(c, record) {
+    // get record handler (align type in table by subtracting lowest)
+    var aligned = record.type - tls.ContentType.change_cipher_spec;
+    var handlers = ctTable[c.entity][c.expect];
+    if(aligned in handlers) {
+      handlers[aligned](c, record);
+    } else {
+      // unexpected record
+      tls.handleUnexpected(c, record);
+    }
+  };
+
+  /**
+   * Reads the record header and initializes the next record on the given
+   * connection.
+   *
+   * @param c the TLS connection with the next record.
+   *
+   * @return 0 if the input data could be processed, otherwise the
+   *         number of bytes required for data to be processed.
+   */
+  var _readRecordHeader = function(c) {
+    var rval = 0;
+
+    // get input buffer and its length
+    var b = c.input;
+    var len = b.length();
+
+    // need at least 5 bytes to initialize a record
+    if(len < 5) {
+      rval = 5 - len;
+    } else {
+      // enough bytes for header
+      // initialize record
+      c.record = {
+        type: b.getByte(),
+        version: {
+          major: b.getByte(),
+          minor: b.getByte()
+        },
+        length: b.getInt16(),
+        fragment: forge.util.createBuffer(),
+        ready: false
+      };
+
+      // check record version
+      var compatibleVersion = (c.record.version.major === c.version.major);
+      if(compatibleVersion && c.session && c.session.version) {
+        // session version already set, require same minor version
+        compatibleVersion = (c.record.version.minor === c.version.minor);
+      }
+      if(!compatibleVersion) {
+        c.error(c, {
+          message: 'Incompatible TLS version.',
+          send: true,
+          alert: {
+            level: tls.Alert.Level.fatal,
+            description: tls.Alert.Description.protocol_version
+          }
+        });
+      }
+    }
+
+    return rval;
+  };
+
+  /**
+   * Reads the next record's contents and appends its message to any
+   * previously fragmented message.
+   *
+   * @param c the TLS connection with the next record.
+   *
+   * @return 0 if the input data could be processed, otherwise the
+   *         number of bytes required for data to be processed.
+   */
+  var _readRecord = function(c) {
+    var rval = 0;
+
+    // ensure there is enough input data to get the entire record
+    var b = c.input;
+    var len = b.length();
+    if(len < c.record.length) {
+      // not enough data yet, return how much is required
+      rval = c.record.length - len;
+    } else {
+      // there is enough data to parse the pending record
+      // fill record fragment and compact input buffer
+      c.record.fragment.putBytes(b.getBytes(c.record.length));
+      b.compact();
+
+      // update record using current read state
+      var s = c.state.current.read;
+      if(s.update(c, c.record)) {
+        // see if there is a previously fragmented message that the
+        // new record's message fragment should be appended to
+        if(c.fragmented !== null) {
+          // if the record type matches a previously fragmented
+          // record, append the record fragment to it
+          if(c.fragmented.type === c.record.type) {
+            // concatenate record fragments
+            c.fragmented.fragment.putBuffer(c.record.fragment);
+            c.record = c.fragmented;
+          } else {
+            // error, invalid fragmented record
+            c.error(c, {
+              message: 'Invalid fragmented record.',
+              send: true,
+              alert: {
+                level: tls.Alert.Level.fatal,
+                description:
+                  tls.Alert.Description.unexpected_message
+              }
+            });
+          }
+        }
+
+        // record is now ready
+        c.record.ready = true;
+      }
+    }
+
+    return rval;
+  };
+
+  /**
+   * Performs a handshake using the TLS Handshake Protocol, as a client.
+   *
+   * This method should only be called if the connection is in client mode.
+   *
+   * @param sessionId the session ID to use, null to start a new one.
+   */
+  c.handshake = function(sessionId) {
+    // error to call this in non-client mode
+    if(c.entity !== tls.ConnectionEnd.client) {
+      // not fatal error
+      c.error(c, {
+        message: 'Cannot initiate handshake as a server.',
+        fatal: false
+      });
+    } else if(c.handshaking) {
+      // handshake is already in progress, fail but not fatal error
+      c.error(c, {
+        message: 'Handshake already in progress.',
+        fatal: false
+      });
+    } else {
+      // clear fail flag on reuse
+      if(c.fail && !c.open && c.handshakes === 0) {
+        c.fail = false;
+      }
+
+      // now handshaking
+      c.handshaking = true;
+
+      // default to blank (new session)
+      sessionId = sessionId || '';
+
+      // if a session ID was specified, try to find it in the cache
+      var session = null;
+      if(sessionId.length > 0) {
+        if(c.sessionCache) {
+          session = c.sessionCache.getSession(sessionId);
+        }
+
+        // matching session not found in cache, clear session ID
+        if(session === null) {
+          sessionId = '';
+        }
+      }
+
+      // no session given, grab a session from the cache, if available
+      if(sessionId.length === 0 && c.sessionCache) {
+        session = c.sessionCache.getSession();
+        if(session !== null) {
+          sessionId = session.id;
+        }
+      }
+
+      // set up session
+      c.session = {
+        id: sessionId,
+        version: null,
+        cipherSuite: null,
+        compressionMethod: null,
+        serverCertificate: null,
+        certificateRequest: null,
+        clientCertificate: null,
+        sp: {},
+        md5: forge.md.md5.create(),
+        sha1: forge.md.sha1.create()
+      };
+
+      // use existing session information
+      if(session) {
+        // only update version on connection, session version not yet set
+        c.version = session.version;
+        c.session.sp = session.sp;
+      }
+
+      // generate new client random
+      c.session.sp.client_random = tls.createRandom().getBytes();
+
+      // connection now open
+      c.open = true;
+
+      // send hello
+      tls.queue(c, tls.createRecord(c, {
+        type: tls.ContentType.handshake,
+        data: tls.createClientHello(c)
+      }));
+      tls.flush(c);
+    }
+  };
+
+  /**
+   * Called when TLS protocol data has been received from somewhere and should
+   * be processed by the TLS engine.
+   *
+   * @param data the TLS protocol data, as a string, to process.
+   *
+   * @return 0 if the data could be processed, otherwise the number of bytes
+   *         required for data to be processed.
+   */
+  c.process = function(data) {
+    var rval = 0;
+
+    // buffer input data
+    if(data) {
+      c.input.putBytes(data);
+    }
+
+    // process next record if no failure, process will be called after
+    // each record is handled (since handling can be asynchronous)
+    if(!c.fail) {
+      // reset record if ready and now empty
+      if(c.record !== null &&
+        c.record.ready && c.record.fragment.isEmpty()) {
+        c.record = null;
+      }
+
+      // if there is no pending record, try to read record header
+      if(c.record === null) {
+        rval = _readRecordHeader(c);
+      }
+
+      // read the next record (if record not yet ready)
+      if(!c.fail && c.record !== null && !c.record.ready) {
+        rval = _readRecord(c);
+      }
+
+      // record ready to be handled, update engine state
+      if(!c.fail && c.record !== null && c.record.ready) {
+        _update(c, c.record);
+      }
+    }
+
+    return rval;
+  };
+
+  /**
+   * Requests that application data be packaged into a TLS record. The
+   * tlsDataReady handler will be called when the TLS record(s) have been
+   * prepared.
+   *
+   * @param data the application data, as a raw 'binary' encoded string, to
+   *          be sent; to send utf-16/utf-8 string data, use the return value
+   *          of util.encodeUtf8(str).
+   *
+   * @return true on success, false on failure.
+   */
+  c.prepare = function(data) {
+    tls.queue(c, tls.createRecord(c, {
+      type: tls.ContentType.application_data,
+      data: forge.util.createBuffer(data)
+    }));
+    return tls.flush(c);
+  };
+
+  /**
+   * Requests that a heartbeat request be packaged into a TLS record for
+   * transmission. The tlsDataReady handler will be called when TLS record(s)
+   * have been prepared.
+   *
+   * When a heartbeat response has been received, the heartbeatReceived
+   * handler will be called with the matching payload. This handler can
+   * be used to clear a retransmission timer, etc.
+   *
+   * @param payload the heartbeat data to send as the payload in the message.
+   * @param [payloadLength] the payload length to use, defaults to the
+   *          actual payload length.
+   *
+   * @return true on success, false on failure.
+   */
+  c.prepareHeartbeatRequest = function(payload, payloadLength) {
+    if(payload instanceof forge.util.ByteBuffer) {
+      payload = payload.bytes();
+    }
+    if(typeof payloadLength === 'undefined') {
+      payloadLength = payload.length;
+    }
+    c.expectedHeartbeatPayload = payload;
+    tls.queue(c, tls.createRecord(c, {
+      type: tls.ContentType.heartbeat,
+      data: tls.createHeartbeat(
+        tls.HeartbeatMessageType.heartbeat_request, payload, payloadLength)
+    }));
+    return tls.flush(c);
+  };
+
+  /**
+   * Closes the connection (sends a close_notify alert).
+   *
+   * @param clearFail true to clear the fail flag (default: true).
+   */
+  c.close = function(clearFail) {
+    // save session if connection didn't fail
+    if(!c.fail && c.sessionCache && c.session) {
+      // only need to preserve session ID, version, and security params
+      var session = {
+        id: c.session.id,
+        version: c.session.version,
+        sp: c.session.sp
+      };
+      session.sp.keys = null;
+      c.sessionCache.setSession(session.id, session);
+    }
+
+    if(c.open) {
+      // connection no longer open, clear input
+      c.open = false;
+      c.input.clear();
+
+      // if connected or handshaking, send an alert
+      if(c.isConnected || c.handshaking) {
+        c.isConnected = c.handshaking = false;
+
+        // send close_notify alert
+        tls.queue(c, tls.createAlert(c, {
+          level: tls.Alert.Level.warning,
+          description: tls.Alert.Description.close_notify
+        }));
+        tls.flush(c);
+      }
+
+      // call handler
+      c.closed(c);
+    }
+
+    // reset TLS connection, do not clear fail flag
+    c.reset(clearFail);
+  };
+
+  return c;
+};
+
+/* TLS API */
+forge.tls = forge.tls || {};
+
+// expose non-functions
+for(var key in tls) {
+  if(typeof tls[key] !== 'function') {
+    forge.tls[key] = tls[key];
+  }
+}
+
+// expose prf_tls1 for testing
+forge.tls.prf_tls1 = prf_TLS1;
+
+// expose sha1 hmac method
+forge.tls.hmac_sha1 = hmac_sha1;
+
+// expose session cache creation
+forge.tls.createSessionCache = tls.createSessionCache;
+
+/**
+ * Creates a new TLS connection. This does not make any assumptions about the
+ * transport layer that TLS is working on top of, ie: it does not assume there
+ * is a TCP/IP connection or establish one. A TLS connection is totally
+ * abstracted away from the layer is runs on top of, it merely establishes a
+ * secure channel between a client" and a "server".
+ *
+ * A TLS connection contains 4 connection states: pending read and write, and
+ * current read and write.
+ *
+ * At initialization, the current read and write states will be null. Only once
+ * the security parameters have been set and the keys have been generated can
+ * the pending states be converted into current states. Current states will be
+ * updated for each record processed.
+ *
+ * A custom certificate verify callback may be provided to check information
+ * like the common name on the server's certificate. It will be called for
+ * every certificate in the chain. It has the following signature:
+ *
+ * variable func(c, certs, index, preVerify)
+ * Where:
+ * c         The TLS connection
+ * verified  Set to true if certificate was verified, otherwise the alert
+ *           tls.Alert.Description for why the certificate failed.
+ * depth     The current index in the chain, where 0 is the server's cert.
+ * certs     The certificate chain, *NOTE* if the server was anonymous then
+ *           the chain will be empty.
+ *
+ * The function returns true on success and on failure either the appropriate
+ * tls.Alert.Description or an object with 'alert' set to the appropriate
+ * tls.Alert.Description and 'message' set to a custom error message. If true
+ * is not returned then the connection will abort using, in order of
+ * availability, first the returned alert description, second the preVerify
+ * alert description, and lastly the default 'bad_certificate'.
+ *
+ * There are three callbacks that can be used to make use of client-side
+ * certificates where each takes the TLS connection as the first parameter:
+ *
+ * getCertificate(conn, hint)
+ *   The second parameter is a hint as to which certificate should be
+ *   returned. If the connection entity is a client, then the hint will be
+ *   the CertificateRequest message from the server that is part of the
+ *   TLS protocol. If the connection entity is a server, then it will be
+ *   the servername list provided via an SNI extension the ClientHello, if
+ *   one was provided (empty array if not). The hint can be examined to
+ *   determine which certificate to use (advanced). Most implementations
+ *   will just return a certificate. The return value must be a
+ *   PEM-formatted certificate or an array of PEM-formatted certificates
+ *   that constitute a certificate chain, with the first in the array/chain
+ *   being the client's certificate.
+ * getPrivateKey(conn, certificate)
+ *   The second parameter is an forge.pki X.509 certificate object that
+ *   is associated with the requested private key. The return value must
+ *   be a PEM-formatted private key.
+ * getSignature(conn, bytes, callback)
+ *   This callback can be used instead of getPrivateKey if the private key
+ *   is not directly accessible in javascript or should not be. For
+ *   instance, a secure external web service could provide the signature
+ *   in exchange for appropriate credentials. The second parameter is a
+ *   string of bytes to be signed that are part of the TLS protocol. These
+ *   bytes are used to verify that the private key for the previously
+ *   provided client-side certificate is accessible to the client. The
+ *   callback is a function that takes 2 parameters, the TLS connection
+ *   and the RSA encrypted (signed) bytes as a string. This callback must
+ *   be called once the signature is ready.
+ *
+ * @param options the options for this connection:
+ *   server: true if the connection is server-side, false for client.
+ *   sessionId: a session ID to reuse, null for a new connection.
+ *   caStore: an array of certificates to trust.
+ *   sessionCache: a session cache to use.
+ *   cipherSuites: an optional array of cipher suites to use,
+ *     see tls.CipherSuites.
+ *   connected: function(conn) called when the first handshake completes.
+ *   virtualHost: the virtual server name to use in a TLS SNI extension.
+ *   verifyClient: true to require a client certificate in server mode,
+ *     'optional' to request one, false not to (default: false).
+ *   verify: a handler used to custom verify certificates in the chain.
+ *   getCertificate: an optional callback used to get a certificate or
+ *     a chain of certificates (as an array).
+ *   getPrivateKey: an optional callback used to get a private key.
+ *   getSignature: an optional callback used to get a signature.
+ *   tlsDataReady: function(conn) called when TLS protocol data has been
+ *     prepared and is ready to be used (typically sent over a socket
+ *     connection to its destination), read from conn.tlsData buffer.
+ *   dataReady: function(conn) called when application data has
+ *     been parsed from a TLS record and should be consumed by the
+ *     application, read from conn.data buffer.
+ *   closed: function(conn) called when the connection has been closed.
+ *   error: function(conn, error) called when there was an error.
+ *   deflate: function(inBytes) if provided, will deflate TLS records using
+ *     the deflate algorithm if the server supports it.
+ *   inflate: function(inBytes) if provided, will inflate TLS records using
+ *     the deflate algorithm if the server supports it.
+ *
+ * @return the new TLS connection.
+ */
+forge.tls.createConnection = tls.createConnection;
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'tls';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define([
+  'require',
+  'module',
+  './asn1',
+  './hmac',
+  './md',
+  './pem',
+  './pki',
+  './random',
+  './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/tlssocket.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/tlssocket.js
new file mode 100644
index 0000000..9a00ea2
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/tlssocket.js
@@ -0,0 +1,304 @@
+/**
+ * Socket wrapping functions for TLS.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2009-2012 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+/**
+ * Wraps a forge.net socket with a TLS layer.
+ *
+ * @param options:
+ *   sessionId: a session ID to reuse, null for a new connection if no session
+ *     cache is provided or it is empty.
+ *   caStore: an array of certificates to trust.
+ *   sessionCache: a session cache to use.
+ *   cipherSuites: an optional array of cipher suites to use, see
+ *     tls.CipherSuites.
+ *   socket: the socket to wrap.
+ *   virtualHost: the virtual server name to use in a TLS SNI extension.
+ *   verify: a handler used to custom verify certificates in the chain.
+ *   getCertificate: an optional callback used to get a certificate.
+ *   getPrivateKey: an optional callback used to get a private key.
+ *   getSignature: an optional callback used to get a signature.
+ *   deflate: function(inBytes) if provided, will deflate TLS records using
+ *     the deflate algorithm if the server supports it.
+ *   inflate: function(inBytes) if provided, will inflate TLS records using
+ *     the deflate algorithm if the server supports it.
+ *
+ * @return the TLS-wrapped socket.
+ */
+forge.tls.wrapSocket = function(options) {
+  // get raw socket
+  var socket = options.socket;
+
+  // create TLS socket
+  var tlsSocket = {
+    id: socket.id,
+    // set handlers
+    connected: socket.connected || function(e){},
+    closed: socket.closed || function(e){},
+    data: socket.data || function(e){},
+    error: socket.error || function(e){}
+  };
+
+  // create TLS connection
+  var c = forge.tls.createConnection({
+    server: false,
+    sessionId: options.sessionId || null,
+    caStore: options.caStore || [],
+    sessionCache: options.sessionCache || null,
+    cipherSuites: options.cipherSuites || null,
+    virtualHost: options.virtualHost,
+    verify: options.verify,
+    getCertificate: options.getCertificate,
+    getPrivateKey: options.getPrivateKey,
+    getSignature: options.getSignature,
+    deflate: options.deflate,
+    inflate: options.inflate,
+    connected: function(c) {
+      // first handshake complete, call handler
+      if(c.handshakes === 1) {
+        tlsSocket.connected({
+          id: socket.id,
+          type: 'connect',
+          bytesAvailable: c.data.length()
+        });
+      }
+    },
+    tlsDataReady: function(c) {
+      // send TLS data over socket
+      return socket.send(c.tlsData.getBytes());
+    },
+    dataReady: function(c) {
+      // indicate application data is ready
+      tlsSocket.data({
+        id: socket.id,
+        type: 'socketData',
+        bytesAvailable: c.data.length()
+      });
+    },
+    closed: function(c) {
+      // close socket
+      socket.close();
+    },
+    error: function(c, e) {
+      // send error, close socket
+      tlsSocket.error({
+        id: socket.id,
+        type: 'tlsError',
+        message: e.message,
+        bytesAvailable: 0,
+        error: e
+      });
+      socket.close();
+    }
+  });
+
+  // handle doing handshake after connecting
+  socket.connected = function(e) {
+    c.handshake(options.sessionId);
+  };
+
+  // handle closing TLS connection
+  socket.closed = function(e) {
+    if(c.open && c.handshaking) {
+      // error
+      tlsSocket.error({
+        id: socket.id,
+        type: 'ioError',
+        message: 'Connection closed during handshake.',
+        bytesAvailable: 0
+      });
+    }
+    c.close();
+
+    // call socket handler
+    tlsSocket.closed({
+      id: socket.id,
+      type: 'close',
+      bytesAvailable: 0
+    });
+  };
+
+  // handle error on socket
+  socket.error = function(e) {
+    // error
+    tlsSocket.error({
+      id: socket.id,
+      type: e.type,
+      message: e.message,
+      bytesAvailable: 0
+    });
+    c.close();
+  };
+
+  // handle receiving raw TLS data from socket
+  var _requiredBytes = 0;
+  socket.data = function(e) {
+    // drop data if connection not open
+    if(!c.open) {
+      socket.receive(e.bytesAvailable);
+    } else {
+      // only receive if there are enough bytes available to
+      // process a record
+      if(e.bytesAvailable >= _requiredBytes) {
+        var count = Math.max(e.bytesAvailable, _requiredBytes);
+        var data = socket.receive(count);
+        if(data !== null) {
+          _requiredBytes = c.process(data);
+        }
+      }
+    }
+  };
+
+  /**
+   * Destroys this socket.
+   */
+  tlsSocket.destroy = function() {
+    socket.destroy();
+  };
+
+  /**
+   * Sets this socket's TLS session cache. This should be called before
+   * the socket is connected or after it is closed.
+   *
+   * The cache is an object mapping session IDs to internal opaque state.
+   * An application might need to change the cache used by a particular
+   * tlsSocket between connections if it accesses multiple TLS hosts.
+   *
+   * @param cache the session cache to use.
+   */
+  tlsSocket.setSessionCache = function(cache) {
+    c.sessionCache = tls.createSessionCache(cache);
+  };
+
+  /**
+   * Connects this socket.
+   *
+   * @param options:
+   *           host: the host to connect to.
+   *           port: the port to connect to.
+   *           policyPort: the policy port to use (if non-default), 0 to
+   *              use the flash default.
+   *           policyUrl: the policy file URL to use (instead of port).
+   */
+  tlsSocket.connect = function(options) {
+    socket.connect(options);
+  };
+
+  /**
+   * Closes this socket.
+   */
+  tlsSocket.close = function() {
+    c.close();
+  };
+
+  /**
+   * Determines if the socket is connected or not.
+   *
+   * @return true if connected, false if not.
+   */
+  tlsSocket.isConnected = function() {
+    return c.isConnected && socket.isConnected();
+  };
+
+  /**
+   * Writes bytes to this socket.
+   *
+   * @param bytes the bytes (as a string) to write.
+   *
+   * @return true on success, false on failure.
+   */
+  tlsSocket.send = function(bytes) {
+    return c.prepare(bytes);
+  };
+
+  /**
+   * Reads bytes from this socket (non-blocking). Fewer than the number of
+   * bytes requested may be read if enough bytes are not available.
+   *
+   * This method should be called from the data handler if there are enough
+   * bytes available. To see how many bytes are available, check the
+   * 'bytesAvailable' property on the event in the data handler or call the
+   * bytesAvailable() function on the socket. If the browser is msie, then the
+   * bytesAvailable() function should be used to avoid race conditions.
+   * Otherwise, using the property on the data handler's event may be quicker.
+   *
+   * @param count the maximum number of bytes to read.
+   *
+   * @return the bytes read (as a string) or null on error.
+   */
+  tlsSocket.receive = function(count) {
+    return c.data.getBytes(count);
+  };
+
+  /**
+   * Gets the number of bytes available for receiving on the socket.
+   *
+   * @return the number of bytes available for receiving.
+   */
+  tlsSocket.bytesAvailable = function() {
+    return c.data.length();
+  };
+
+  return tlsSocket;
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'tlssocket';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './tls'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/util.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/util.js
new file mode 100644
index 0000000..0290842
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/util.js
@@ -0,0 +1,2953 @@
+/**
+ * Utility functions for web applications.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+/* Utilities API */
+var util = forge.util = forge.util || {};
+
+// define setImmediate and nextTick
+if(typeof process === 'undefined' || !process.nextTick) {
+  if(typeof setImmediate === 'function') {
+    util.setImmediate = setImmediate;
+    util.nextTick = function(callback) {
+      return setImmediate(callback);
+    };
+  } else {
+    util.setImmediate = function(callback) {
+      setTimeout(callback, 0);
+    };
+    util.nextTick = util.setImmediate;
+  }
+} else {
+  util.nextTick = process.nextTick;
+  if(typeof setImmediate === 'function') {
+    util.setImmediate = setImmediate;
+  } else {
+    util.setImmediate = util.nextTick;
+  }
+}
+
+// define isArray
+util.isArray = Array.isArray || function(x) {
+  return Object.prototype.toString.call(x) === '[object Array]';
+};
+
+// define isArrayBuffer
+util.isArrayBuffer = function(x) {
+  return typeof ArrayBuffer !== 'undefined' && x instanceof ArrayBuffer;
+};
+
+// define isArrayBufferView
+var _arrayBufferViews = [];
+if(typeof DataView !== 'undefined') {
+  _arrayBufferViews.push(DataView);
+}
+if(typeof Int8Array !== 'undefined') {
+  _arrayBufferViews.push(Int8Array);
+}
+if(typeof Uint8Array !== 'undefined') {
+  _arrayBufferViews.push(Uint8Array);
+}
+if(typeof Uint8ClampedArray !== 'undefined') {
+  _arrayBufferViews.push(Uint8ClampedArray);
+}
+if(typeof Int16Array !== 'undefined') {
+  _arrayBufferViews.push(Int16Array);
+}
+if(typeof Uint16Array !== 'undefined') {
+  _arrayBufferViews.push(Uint16Array);
+}
+if(typeof Int32Array !== 'undefined') {
+  _arrayBufferViews.push(Int32Array);
+}
+if(typeof Uint32Array !== 'undefined') {
+  _arrayBufferViews.push(Uint32Array);
+}
+if(typeof Float32Array !== 'undefined') {
+  _arrayBufferViews.push(Float32Array);
+}
+if(typeof Float64Array !== 'undefined') {
+  _arrayBufferViews.push(Float64Array);
+}
+util.isArrayBufferView = function(x) {
+  for(var i = 0; i < _arrayBufferViews.length; ++i) {
+    if(x instanceof _arrayBufferViews[i]) {
+      return true;
+    }
+  }
+  return false;
+};
+
+// TODO: set ByteBuffer to best available backing
+util.ByteBuffer = ByteStringBuffer;
+
+/** Buffer w/BinaryString backing */
+
+/**
+ * Constructor for a binary string backed byte buffer.
+ *
+ * @param [b] the bytes to wrap (either encoded as string, one byte per
+ *          character, or as an ArrayBuffer or Typed Array).
+ */
+function ByteStringBuffer(b) {
+  // TODO: update to match DataBuffer API
+
+  // the data in this buffer
+  this.data = '';
+  // the pointer for reading from this buffer
+  this.read = 0;
+
+  if(typeof b === 'string') {
+    this.data = b;
+  } else if(util.isArrayBuffer(b) || util.isArrayBufferView(b)) {
+    // convert native buffer to forge buffer
+    // FIXME: support native buffers internally instead
+    var arr = new Uint8Array(b);
+    try {
+      this.data = String.fromCharCode.apply(null, arr);
+    } catch(e) {
+      for(var i = 0; i < arr.length; ++i) {
+        this.putByte(arr[i]);
+      }
+    }
+  } else if(b instanceof ByteStringBuffer ||
+    (typeof b === 'object' && typeof b.data === 'string' &&
+    typeof b.read === 'number')) {
+    // copy existing buffer
+    this.data = b.data;
+    this.read = b.read;
+  }
+
+  // used for v8 optimization
+  this._constructedStringLength = 0;
+}
+util.ByteStringBuffer = ByteStringBuffer;
+
+/* Note: This is an optimization for V8-based browsers. When V8 concatenates
+  a string, the strings are only joined logically using a "cons string" or
+  "constructed/concatenated string". These containers keep references to one
+  another and can result in very large memory usage. For example, if a 2MB
+  string is constructed by concatenating 4 bytes together at a time, the
+  memory usage will be ~44MB; so ~22x increase. The strings are only joined
+  together when an operation requiring their joining takes place, such as
+  substr(). This function is called when adding data to this buffer to ensure
+  these types of strings are periodically joined to reduce the memory
+  footprint. */
+var _MAX_CONSTRUCTED_STRING_LENGTH = 4096;
+util.ByteStringBuffer.prototype._optimizeConstructedString = function(x) {
+  this._constructedStringLength += x;
+  if(this._constructedStringLength > _MAX_CONSTRUCTED_STRING_LENGTH) {
+    // this substr() should cause the constructed string to join
+    this.data.substr(0, 1);
+    this._constructedStringLength = 0;
+  }
+};
+
+/**
+ * Gets the number of bytes in this buffer.
+ *
+ * @return the number of bytes in this buffer.
+ */
+util.ByteStringBuffer.prototype.length = function() {
+  return this.data.length - this.read;
+};
+
+/**
+ * Gets whether or not this buffer is empty.
+ *
+ * @return true if this buffer is empty, false if not.
+ */
+util.ByteStringBuffer.prototype.isEmpty = function() {
+  return this.length() <= 0;
+};
+
+/**
+ * Puts a byte in this buffer.
+ *
+ * @param b the byte to put.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.putByte = function(b) {
+  return this.putBytes(String.fromCharCode(b));
+};
+
+/**
+ * Puts a byte in this buffer N times.
+ *
+ * @param b the byte to put.
+ * @param n the number of bytes of value b to put.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.fillWithByte = function(b, n) {
+  b = String.fromCharCode(b);
+  var d = this.data;
+  while(n > 0) {
+    if(n & 1) {
+      d += b;
+    }
+    n >>>= 1;
+    if(n > 0) {
+      b += b;
+    }
+  }
+  this.data = d;
+  this._optimizeConstructedString(n);
+  return this;
+};
+
+/**
+ * Puts bytes in this buffer.
+ *
+ * @param bytes the bytes (as a UTF-8 encoded string) to put.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.putBytes = function(bytes) {
+  this.data += bytes;
+  this._optimizeConstructedString(bytes.length);
+  return this;
+};
+
+/**
+ * Puts a UTF-16 encoded string into this buffer.
+ *
+ * @param str the string to put.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.putString = function(str) {
+  return this.putBytes(util.encodeUtf8(str));
+};
+
+/**
+ * Puts a 16-bit integer in this buffer in big-endian order.
+ *
+ * @param i the 16-bit integer.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.putInt16 = function(i) {
+  return this.putBytes(
+    String.fromCharCode(i >> 8 & 0xFF) +
+    String.fromCharCode(i & 0xFF));
+};
+
+/**
+ * Puts a 24-bit integer in this buffer in big-endian order.
+ *
+ * @param i the 24-bit integer.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.putInt24 = function(i) {
+  return this.putBytes(
+    String.fromCharCode(i >> 16 & 0xFF) +
+    String.fromCharCode(i >> 8 & 0xFF) +
+    String.fromCharCode(i & 0xFF));
+};
+
+/**
+ * Puts a 32-bit integer in this buffer in big-endian order.
+ *
+ * @param i the 32-bit integer.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.putInt32 = function(i) {
+  return this.putBytes(
+    String.fromCharCode(i >> 24 & 0xFF) +
+    String.fromCharCode(i >> 16 & 0xFF) +
+    String.fromCharCode(i >> 8 & 0xFF) +
+    String.fromCharCode(i & 0xFF));
+};
+
+/**
+ * Puts a 16-bit integer in this buffer in little-endian order.
+ *
+ * @param i the 16-bit integer.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.putInt16Le = function(i) {
+  return this.putBytes(
+    String.fromCharCode(i & 0xFF) +
+    String.fromCharCode(i >> 8 & 0xFF));
+};
+
+/**
+ * Puts a 24-bit integer in this buffer in little-endian order.
+ *
+ * @param i the 24-bit integer.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.putInt24Le = function(i) {
+  return this.putBytes(
+    String.fromCharCode(i & 0xFF) +
+    String.fromCharCode(i >> 8 & 0xFF) +
+    String.fromCharCode(i >> 16 & 0xFF));
+};
+
+/**
+ * Puts a 32-bit integer in this buffer in little-endian order.
+ *
+ * @param i the 32-bit integer.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.putInt32Le = function(i) {
+  return this.putBytes(
+    String.fromCharCode(i & 0xFF) +
+    String.fromCharCode(i >> 8 & 0xFF) +
+    String.fromCharCode(i >> 16 & 0xFF) +
+    String.fromCharCode(i >> 24 & 0xFF));
+};
+
+/**
+ * Puts an n-bit integer in this buffer in big-endian order.
+ *
+ * @param i the n-bit integer.
+ * @param n the number of bits in the integer.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.putInt = function(i, n) {
+  var bytes = '';
+  do {
+    n -= 8;
+    bytes += String.fromCharCode((i >> n) & 0xFF);
+  } while(n > 0);
+  return this.putBytes(bytes);
+};
+
+/**
+ * Puts a signed n-bit integer in this buffer in big-endian order. Two's
+ * complement representation is used.
+ *
+ * @param i the n-bit integer.
+ * @param n the number of bits in the integer.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.putSignedInt = function(i, n) {
+  if(i < 0) {
+    i += 2 << (n - 1);
+  }
+  return this.putInt(i, n);
+};
+
+/**
+ * Puts the given buffer into this buffer.
+ *
+ * @param buffer the buffer to put into this one.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.putBuffer = function(buffer) {
+  return this.putBytes(buffer.getBytes());
+};
+
+/**
+ * Gets a byte from this buffer and advances the read pointer by 1.
+ *
+ * @return the byte.
+ */
+util.ByteStringBuffer.prototype.getByte = function() {
+  return this.data.charCodeAt(this.read++);
+};
+
+/**
+ * Gets a uint16 from this buffer in big-endian order and advances the read
+ * pointer by 2.
+ *
+ * @return the uint16.
+ */
+util.ByteStringBuffer.prototype.getInt16 = function() {
+  var rval = (
+    this.data.charCodeAt(this.read) << 8 ^
+    this.data.charCodeAt(this.read + 1));
+  this.read += 2;
+  return rval;
+};
+
+/**
+ * Gets a uint24 from this buffer in big-endian order and advances the read
+ * pointer by 3.
+ *
+ * @return the uint24.
+ */
+util.ByteStringBuffer.prototype.getInt24 = function() {
+  var rval = (
+    this.data.charCodeAt(this.read) << 16 ^
+    this.data.charCodeAt(this.read + 1) << 8 ^
+    this.data.charCodeAt(this.read + 2));
+  this.read += 3;
+  return rval;
+};
+
+/**
+ * Gets a uint32 from this buffer in big-endian order and advances the read
+ * pointer by 4.
+ *
+ * @return the word.
+ */
+util.ByteStringBuffer.prototype.getInt32 = function() {
+  var rval = (
+    this.data.charCodeAt(this.read) << 24 ^
+    this.data.charCodeAt(this.read + 1) << 16 ^
+    this.data.charCodeAt(this.read + 2) << 8 ^
+    this.data.charCodeAt(this.read + 3));
+  this.read += 4;
+  return rval;
+};
+
+/**
+ * Gets a uint16 from this buffer in little-endian order and advances the read
+ * pointer by 2.
+ *
+ * @return the uint16.
+ */
+util.ByteStringBuffer.prototype.getInt16Le = function() {
+  var rval = (
+    this.data.charCodeAt(this.read) ^
+    this.data.charCodeAt(this.read + 1) << 8);
+  this.read += 2;
+  return rval;
+};
+
+/**
+ * Gets a uint24 from this buffer in little-endian order and advances the read
+ * pointer by 3.
+ *
+ * @return the uint24.
+ */
+util.ByteStringBuffer.prototype.getInt24Le = function() {
+  var rval = (
+    this.data.charCodeAt(this.read) ^
+    this.data.charCodeAt(this.read + 1) << 8 ^
+    this.data.charCodeAt(this.read + 2) << 16);
+  this.read += 3;
+  return rval;
+};
+
+/**
+ * Gets a uint32 from this buffer in little-endian order and advances the read
+ * pointer by 4.
+ *
+ * @return the word.
+ */
+util.ByteStringBuffer.prototype.getInt32Le = function() {
+  var rval = (
+    this.data.charCodeAt(this.read) ^
+    this.data.charCodeAt(this.read + 1) << 8 ^
+    this.data.charCodeAt(this.read + 2) << 16 ^
+    this.data.charCodeAt(this.read + 3) << 24);
+  this.read += 4;
+  return rval;
+};
+
+/**
+ * Gets an n-bit integer from this buffer in big-endian order and advances the
+ * read pointer by n/8.
+ *
+ * @param n the number of bits in the integer.
+ *
+ * @return the integer.
+ */
+util.ByteStringBuffer.prototype.getInt = function(n) {
+  var rval = 0;
+  do {
+    rval = (rval << 8) + this.data.charCodeAt(this.read++);
+    n -= 8;
+  } while(n > 0);
+  return rval;
+};
+
+/**
+ * Gets a signed n-bit integer from this buffer in big-endian order, using
+ * two's complement, and advances the read pointer by n/8.
+ *
+ * @param n the number of bits in the integer.
+ *
+ * @return the integer.
+ */
+util.ByteStringBuffer.prototype.getSignedInt = function(n) {
+  var x = this.getInt(n);
+  var max = 2 << (n - 2);
+  if(x >= max) {
+    x -= max << 1;
+  }
+  return x;
+};
+
+/**
+ * Reads bytes out into a UTF-8 string and clears them from the buffer.
+ *
+ * @param count the number of bytes to read, undefined or null for all.
+ *
+ * @return a UTF-8 string of bytes.
+ */
+util.ByteStringBuffer.prototype.getBytes = function(count) {
+  var rval;
+  if(count) {
+    // read count bytes
+    count = Math.min(this.length(), count);
+    rval = this.data.slice(this.read, this.read + count);
+    this.read += count;
+  } else if(count === 0) {
+    rval = '';
+  } else {
+    // read all bytes, optimize to only copy when needed
+    rval = (this.read === 0) ? this.data : this.data.slice(this.read);
+    this.clear();
+  }
+  return rval;
+};
+
+/**
+ * Gets a UTF-8 encoded string of the bytes from this buffer without modifying
+ * the read pointer.
+ *
+ * @param count the number of bytes to get, omit to get all.
+ *
+ * @return a string full of UTF-8 encoded characters.
+ */
+util.ByteStringBuffer.prototype.bytes = function(count) {
+  return (typeof(count) === 'undefined' ?
+    this.data.slice(this.read) :
+    this.data.slice(this.read, this.read + count));
+};
+
+/**
+ * Gets a byte at the given index without modifying the read pointer.
+ *
+ * @param i the byte index.
+ *
+ * @return the byte.
+ */
+util.ByteStringBuffer.prototype.at = function(i) {
+  return this.data.charCodeAt(this.read + i);
+};
+
+/**
+ * Puts a byte at the given index without modifying the read pointer.
+ *
+ * @param i the byte index.
+ * @param b the byte to put.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.setAt = function(i, b) {
+  this.data = this.data.substr(0, this.read + i) +
+    String.fromCharCode(b) +
+    this.data.substr(this.read + i + 1);
+  return this;
+};
+
+/**
+ * Gets the last byte without modifying the read pointer.
+ *
+ * @return the last byte.
+ */
+util.ByteStringBuffer.prototype.last = function() {
+  return this.data.charCodeAt(this.data.length - 1);
+};
+
+/**
+ * Creates a copy of this buffer.
+ *
+ * @return the copy.
+ */
+util.ByteStringBuffer.prototype.copy = function() {
+  var c = util.createBuffer(this.data);
+  c.read = this.read;
+  return c;
+};
+
+/**
+ * Compacts this buffer.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.compact = function() {
+  if(this.read > 0) {
+    this.data = this.data.slice(this.read);
+    this.read = 0;
+  }
+  return this;
+};
+
+/**
+ * Clears this buffer.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.clear = function() {
+  this.data = '';
+  this.read = 0;
+  return this;
+};
+
+/**
+ * Shortens this buffer by triming bytes off of the end of this buffer.
+ *
+ * @param count the number of bytes to trim off.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.truncate = function(count) {
+  var len = Math.max(0, this.length() - count);
+  this.data = this.data.substr(this.read, len);
+  this.read = 0;
+  return this;
+};
+
+/**
+ * Converts this buffer to a hexadecimal string.
+ *
+ * @return a hexadecimal string.
+ */
+util.ByteStringBuffer.prototype.toHex = function() {
+  var rval = '';
+  for(var i = this.read; i < this.data.length; ++i) {
+    var b = this.data.charCodeAt(i);
+    if(b < 16) {
+      rval += '0';
+    }
+    rval += b.toString(16);
+  }
+  return rval;
+};
+
+/**
+ * Converts this buffer to a UTF-16 string (standard JavaScript string).
+ *
+ * @return a UTF-16 string.
+ */
+util.ByteStringBuffer.prototype.toString = function() {
+  return util.decodeUtf8(this.bytes());
+};
+
+/** End Buffer w/BinaryString backing */
+
+
+/** Buffer w/UInt8Array backing */
+
+/**
+ * FIXME: Experimental. Do not use yet.
+ *
+ * Constructor for an ArrayBuffer-backed byte buffer.
+ *
+ * The buffer may be constructed from a string, an ArrayBuffer, DataView, or a
+ * TypedArray.
+ *
+ * If a string is given, its encoding should be provided as an option,
+ * otherwise it will default to 'binary'. A 'binary' string is encoded such
+ * that each character is one byte in length and size.
+ *
+ * If an ArrayBuffer, DataView, or TypedArray is given, it will be used
+ * *directly* without any copying. Note that, if a write to the buffer requires
+ * more space, the buffer will allocate a new backing ArrayBuffer to
+ * accommodate. The starting read and write offsets for the buffer may be
+ * given as options.
+ *
+ * @param [b] the initial bytes for this buffer.
+ * @param options the options to use:
+ *          [readOffset] the starting read offset to use (default: 0).
+ *          [writeOffset] the starting write offset to use (default: the
+ *            length of the first parameter).
+ *          [growSize] the minimum amount, in bytes, to grow the buffer by to
+ *            accommodate writes (default: 1024).
+ *          [encoding] the encoding ('binary', 'utf8', 'utf16', 'hex') for the
+ *            first parameter, if it is a string (default: 'binary').
+ */
+function DataBuffer(b, options) {
+  // default options
+  options = options || {};
+
+  // pointers for read from/write to buffer
+  this.read = options.readOffset || 0;
+  this.growSize = options.growSize || 1024;
+
+  var isArrayBuffer = util.isArrayBuffer(b);
+  var isArrayBufferView = util.isArrayBufferView(b);
+  if(isArrayBuffer || isArrayBufferView) {
+    // use ArrayBuffer directly
+    if(isArrayBuffer) {
+      this.data = new DataView(b);
+    } else {
+      // TODO: adjust read/write offset based on the type of view
+      // or specify that this must be done in the options ... that the
+      // offsets are byte-based
+      this.data = new DataView(b.buffer, b.byteOffset, b.byteLength);
+    }
+    this.write = ('writeOffset' in options ?
+      options.writeOffset : this.data.byteLength);
+    return;
+  }
+
+  // initialize to empty array buffer and add any given bytes using putBytes
+  this.data = new DataView(new ArrayBuffer(0));
+  this.write = 0;
+
+  if(b !== null && b !== undefined) {
+    this.putBytes(b);
+  }
+
+  if('writeOffset' in options) {
+    this.write = options.writeOffset;
+  }
+}
+util.DataBuffer = DataBuffer;
+
+/**
+ * Gets the number of bytes in this buffer.
+ *
+ * @return the number of bytes in this buffer.
+ */
+util.DataBuffer.prototype.length = function() {
+  return this.write - this.read;
+};
+
+/**
+ * Gets whether or not this buffer is empty.
+ *
+ * @return true if this buffer is empty, false if not.
+ */
+util.DataBuffer.prototype.isEmpty = function() {
+  return this.length() <= 0;
+};
+
+/**
+ * Ensures this buffer has enough empty space to accommodate the given number
+ * of bytes. An optional parameter may be given that indicates a minimum
+ * amount to grow the buffer if necessary. If the parameter is not given,
+ * the buffer will be grown by some previously-specified default amount
+ * or heuristic.
+ *
+ * @param amount the number of bytes to accommodate.
+ * @param [growSize] the minimum amount, in bytes, to grow the buffer by if
+ *          necessary.
+ */
+util.DataBuffer.prototype.accommodate = function(amount, growSize) {
+  if(this.length() >= amount) {
+    return this;
+  }
+  growSize = Math.max(growSize || this.growSize, amount);
+
+  // grow buffer
+  var src = new Uint8Array(
+    this.data.buffer, this.data.byteOffset, this.data.byteLength);
+  var dst = new Uint8Array(this.length() + growSize);
+  dst.set(src);
+  this.data = new DataView(dst.buffer);
+
+  return this;
+};
+
+/**
+ * Puts a byte in this buffer.
+ *
+ * @param b the byte to put.
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.putByte = function(b) {
+  this.accommodate(1);
+  this.data.setUint8(this.write++, b);
+  return this;
+};
+
+/**
+ * Puts a byte in this buffer N times.
+ *
+ * @param b the byte to put.
+ * @param n the number of bytes of value b to put.
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.fillWithByte = function(b, n) {
+  this.accommodate(n);
+  for(var i = 0; i < n; ++i) {
+    this.data.setUint8(b);
+  }
+  return this;
+};
+
+/**
+ * Puts bytes in this buffer. The bytes may be given as a string, an
+ * ArrayBuffer, a DataView, or a TypedArray.
+ *
+ * @param bytes the bytes to put.
+ * @param [encoding] the encoding for the first parameter ('binary', 'utf8',
+ *          'utf16', 'hex'), if it is a string (default: 'binary').
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.putBytes = function(bytes, encoding) {
+  if(util.isArrayBufferView(bytes)) {
+    var src = new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength);
+    var len = src.byteLength - src.byteOffset;
+    this.accommodate(len);
+    var dst = new Uint8Array(this.data.buffer, this.write);
+    dst.set(src);
+    this.write += len;
+    return this;
+  }
+
+  if(util.isArrayBuffer(bytes)) {
+    var src = new Uint8Array(bytes);
+    this.accommodate(src.byteLength);
+    var dst = new Uint8Array(this.data.buffer);
+    dst.set(src, this.write);
+    this.write += src.byteLength;
+    return this;
+  }
+
+  // bytes is a util.DataBuffer or equivalent
+  if(bytes instanceof util.DataBuffer ||
+    (typeof bytes === 'object' &&
+    typeof bytes.read === 'number' && typeof bytes.write === 'number' &&
+    util.isArrayBufferView(bytes.data))) {
+    var src = new Uint8Array(bytes.data.byteLength, bytes.read, bytes.length());
+    this.accommodate(src.byteLength);
+    var dst = new Uint8Array(bytes.data.byteLength, this.write);
+    dst.set(src);
+    this.write += src.byteLength;
+    return this;
+  }
+
+  if(bytes instanceof util.ByteStringBuffer) {
+    // copy binary string and process as the same as a string parameter below
+    bytes = bytes.data;
+    encoding = 'binary';
+  }
+
+  // string conversion
+  encoding = encoding || 'binary';
+  if(typeof bytes === 'string') {
+    var view;
+
+    // decode from string
+    if(encoding === 'hex') {
+      this.accommodate(Math.ceil(bytes.length / 2));
+      view = new Uint8Array(this.data.buffer, this.write);
+      this.write += util.binary.hex.decode(bytes, view, this.write);
+      return this;
+    }
+    if(encoding === 'base64') {
+      this.accommodate(Math.ceil(bytes.length / 4) * 3);
+      view = new Uint8Array(this.data.buffer, this.write);
+      this.write += util.binary.base64.decode(bytes, view, this.write);
+      return this;
+    }
+
+    // encode text as UTF-8 bytes
+    if(encoding === 'utf8') {
+      // encode as UTF-8 then decode string as raw binary
+      bytes = util.encodeUtf8(bytes);
+      encoding = 'binary';
+    }
+
+    // decode string as raw binary
+    if(encoding === 'binary' || encoding === 'raw') {
+      // one byte per character
+      this.accommodate(bytes.length);
+      view = new Uint8Array(this.data.buffer, this.write);
+      this.write += util.binary.raw.decode(view);
+      return this;
+    }
+
+    // encode text as UTF-16 bytes
+    if(encoding === 'utf16') {
+      // two bytes per character
+      this.accommodate(bytes.length * 2);
+      view = new Uint16Array(this.data.buffer, this.write);
+      this.write += util.text.utf16.encode(view);
+      return this;
+    }
+
+    throw new Error('Invalid encoding: ' + encoding);
+  }
+
+  throw Error('Invalid parameter: ' + bytes);
+};
+
+/**
+ * Puts the given buffer into this buffer.
+ *
+ * @param buffer the buffer to put into this one.
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.putBuffer = function(buffer) {
+  this.putBytes(buffer);
+  buffer.clear();
+  return this;
+};
+
+/**
+ * Puts a string into this buffer.
+ *
+ * @param str the string to put.
+ * @param [encoding] the encoding for the string (default: 'utf16').
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.putString = function(str) {
+  return this.putBytes(str, 'utf16');
+};
+
+/**
+ * Puts a 16-bit integer in this buffer in big-endian order.
+ *
+ * @param i the 16-bit integer.
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.putInt16 = function(i) {
+  this.accommodate(2);
+  this.data.setInt16(this.write, i);
+  this.write += 2;
+  return this;
+};
+
+/**
+ * Puts a 24-bit integer in this buffer in big-endian order.
+ *
+ * @param i the 24-bit integer.
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.putInt24 = function(i) {
+  this.accommodate(3);
+  this.data.setInt16(this.write, i >> 8 & 0xFFFF);
+  this.data.setInt8(this.write, i >> 16 & 0xFF);
+  this.write += 3;
+  return this;
+};
+
+/**
+ * Puts a 32-bit integer in this buffer in big-endian order.
+ *
+ * @param i the 32-bit integer.
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.putInt32 = function(i) {
+  this.accommodate(4);
+  this.data.setInt32(this.write, i);
+  this.write += 4;
+  return this;
+};
+
+/**
+ * Puts a 16-bit integer in this buffer in little-endian order.
+ *
+ * @param i the 16-bit integer.
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.putInt16Le = function(i) {
+  this.accommodate(2);
+  this.data.setInt16(this.write, i, true);
+  this.write += 2;
+  return this;
+};
+
+/**
+ * Puts a 24-bit integer in this buffer in little-endian order.
+ *
+ * @param i the 24-bit integer.
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.putInt24Le = function(i) {
+  this.accommodate(3);
+  this.data.setInt8(this.write, i >> 16 & 0xFF);
+  this.data.setInt16(this.write, i >> 8 & 0xFFFF, true);
+  this.write += 3;
+  return this;
+};
+
+/**
+ * Puts a 32-bit integer in this buffer in little-endian order.
+ *
+ * @param i the 32-bit integer.
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.putInt32Le = function(i) {
+  this.accommodate(4);
+  this.data.setInt32(this.write, i, true);
+  this.write += 4;
+  return this;
+};
+
+/**
+ * Puts an n-bit integer in this buffer in big-endian order.
+ *
+ * @param i the n-bit integer.
+ * @param n the number of bits in the integer.
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.putInt = function(i, n) {
+  this.accommodate(n / 8);
+  do {
+    n -= 8;
+    this.data.setInt8(this.write++, (i >> n) & 0xFF);
+  } while(n > 0);
+  return this;
+};
+
+/**
+ * Puts a signed n-bit integer in this buffer in big-endian order. Two's
+ * complement representation is used.
+ *
+ * @param i the n-bit integer.
+ * @param n the number of bits in the integer.
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.putSignedInt = function(i, n) {
+  this.accommodate(n / 8);
+  if(i < 0) {
+    i += 2 << (n - 1);
+  }
+  return this.putInt(i, n);
+};
+
+/**
+ * Gets a byte from this buffer and advances the read pointer by 1.
+ *
+ * @return the byte.
+ */
+util.DataBuffer.prototype.getByte = function() {
+  return this.data.getInt8(this.read++);
+};
+
+/**
+ * Gets a uint16 from this buffer in big-endian order and advances the read
+ * pointer by 2.
+ *
+ * @return the uint16.
+ */
+util.DataBuffer.prototype.getInt16 = function() {
+  var rval = this.data.getInt16(this.read);
+  this.read += 2;
+  return rval;
+};
+
+/**
+ * Gets a uint24 from this buffer in big-endian order and advances the read
+ * pointer by 3.
+ *
+ * @return the uint24.
+ */
+util.DataBuffer.prototype.getInt24 = function() {
+  var rval = (
+    this.data.getInt16(this.read) << 8 ^
+    this.data.getInt8(this.read + 2));
+  this.read += 3;
+  return rval;
+};
+
+/**
+ * Gets a uint32 from this buffer in big-endian order and advances the read
+ * pointer by 4.
+ *
+ * @return the word.
+ */
+util.DataBuffer.prototype.getInt32 = function() {
+  var rval = this.data.getInt32(this.read);
+  this.read += 4;
+  return rval;
+};
+
+/**
+ * Gets a uint16 from this buffer in little-endian order and advances the read
+ * pointer by 2.
+ *
+ * @return the uint16.
+ */
+util.DataBuffer.prototype.getInt16Le = function() {
+  var rval = this.data.getInt16(this.read, true);
+  this.read += 2;
+  return rval;
+};
+
+/**
+ * Gets a uint24 from this buffer in little-endian order and advances the read
+ * pointer by 3.
+ *
+ * @return the uint24.
+ */
+util.DataBuffer.prototype.getInt24Le = function() {
+  var rval = (
+    this.data.getInt8(this.read) ^
+    this.data.getInt16(this.read + 1, true) << 8);
+  this.read += 3;
+  return rval;
+};
+
+/**
+ * Gets a uint32 from this buffer in little-endian order and advances the read
+ * pointer by 4.
+ *
+ * @return the word.
+ */
+util.DataBuffer.prototype.getInt32Le = function() {
+  var rval = this.data.getInt32(this.read, true);
+  this.read += 4;
+  return rval;
+};
+
+/**
+ * Gets an n-bit integer from this buffer in big-endian order and advances the
+ * read pointer by n/8.
+ *
+ * @param n the number of bits in the integer.
+ *
+ * @return the integer.
+ */
+util.DataBuffer.prototype.getInt = function(n) {
+  var rval = 0;
+  do {
+    rval = (rval << 8) + this.data.getInt8(this.read++);
+    n -= 8;
+  } while(n > 0);
+  return rval;
+};
+
+/**
+ * Gets a signed n-bit integer from this buffer in big-endian order, using
+ * two's complement, and advances the read pointer by n/8.
+ *
+ * @param n the number of bits in the integer.
+ *
+ * @return the integer.
+ */
+util.DataBuffer.prototype.getSignedInt = function(n) {
+  var x = this.getInt(n);
+  var max = 2 << (n - 2);
+  if(x >= max) {
+    x -= max << 1;
+  }
+  return x;
+};
+
+/**
+ * Reads bytes out into a UTF-8 string and clears them from the buffer.
+ *
+ * @param count the number of bytes to read, undefined or null for all.
+ *
+ * @return a UTF-8 string of bytes.
+ */
+util.DataBuffer.prototype.getBytes = function(count) {
+  // TODO: deprecate this method, it is poorly named and
+  // this.toString('binary') replaces it
+  // add a toTypedArray()/toArrayBuffer() function
+  var rval;
+  if(count) {
+    // read count bytes
+    count = Math.min(this.length(), count);
+    rval = this.data.slice(this.read, this.read + count);
+    this.read += count;
+  } else if(count === 0) {
+    rval = '';
+  } else {
+    // read all bytes, optimize to only copy when needed
+    rval = (this.read === 0) ? this.data : this.data.slice(this.read);
+    this.clear();
+  }
+  return rval;
+};
+
+/**
+ * Gets a UTF-8 encoded string of the bytes from this buffer without modifying
+ * the read pointer.
+ *
+ * @param count the number of bytes to get, omit to get all.
+ *
+ * @return a string full of UTF-8 encoded characters.
+ */
+util.DataBuffer.prototype.bytes = function(count) {
+  // TODO: deprecate this method, it is poorly named, add "getString()"
+  return (typeof(count) === 'undefined' ?
+    this.data.slice(this.read) :
+    this.data.slice(this.read, this.read + count));
+};
+
+/**
+ * Gets a byte at the given index without modifying the read pointer.
+ *
+ * @param i the byte index.
+ *
+ * @return the byte.
+ */
+util.DataBuffer.prototype.at = function(i) {
+  return this.data.getUint8(this.read + i);
+};
+
+/**
+ * Puts a byte at the given index without modifying the read pointer.
+ *
+ * @param i the byte index.
+ * @param b the byte to put.
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.setAt = function(i, b) {
+  this.data.setUint8(i, b);
+  return this;
+};
+
+/**
+ * Gets the last byte without modifying the read pointer.
+ *
+ * @return the last byte.
+ */
+util.DataBuffer.prototype.last = function() {
+  return this.data.getUint8(this.write - 1);
+};
+
+/**
+ * Creates a copy of this buffer.
+ *
+ * @return the copy.
+ */
+util.DataBuffer.prototype.copy = function() {
+  return new util.DataBuffer(this);
+};
+
+/**
+ * Compacts this buffer.
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.compact = function() {
+  if(this.read > 0) {
+    var src = new Uint8Array(this.data.buffer, this.read);
+    var dst = new Uint8Array(src.byteLength);
+    dst.set(src);
+    this.data = new DataView(dst);
+    this.write -= this.read;
+    this.read = 0;
+  }
+  return this;
+};
+
+/**
+ * Clears this buffer.
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.clear = function() {
+  this.data = new DataView(new ArrayBuffer(0));
+  this.read = this.write = 0;
+  return this;
+};
+
+/**
+ * Shortens this buffer by triming bytes off of the end of this buffer.
+ *
+ * @param count the number of bytes to trim off.
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.truncate = function(count) {
+  this.write = Math.max(0, this.length() - count);
+  this.read = Math.min(this.read, this.write);
+  return this;
+};
+
+/**
+ * Converts this buffer to a hexadecimal string.
+ *
+ * @return a hexadecimal string.
+ */
+util.DataBuffer.prototype.toHex = function() {
+  var rval = '';
+  for(var i = this.read; i < this.data.byteLength; ++i) {
+    var b = this.data.getUint8(i);
+    if(b < 16) {
+      rval += '0';
+    }
+    rval += b.toString(16);
+  }
+  return rval;
+};
+
+/**
+ * Converts this buffer to a string, using the given encoding. If no
+ * encoding is given, 'utf8' (UTF-8) is used.
+ *
+ * @param [encoding] the encoding to use: 'binary', 'utf8', 'utf16', 'hex',
+ *          'base64' (default: 'utf8').
+ *
+ * @return a string representation of the bytes in this buffer.
+ */
+util.DataBuffer.prototype.toString = function(encoding) {
+  var view = new Uint8Array(this.data, this.read, this.length());
+  encoding = encoding || 'utf8';
+
+  // encode to string
+  if(encoding === 'binary' || encoding === 'raw') {
+    return util.binary.raw.encode(view);
+  }
+  if(encoding === 'hex') {
+    return util.binary.hex.encode(view);
+  }
+  if(encoding === 'base64') {
+    return util.binary.base64.encode(view);
+  }
+
+  // decode to text
+  if(encoding === 'utf8') {
+    return util.text.utf8.decode(view);
+  }
+  if(encoding === 'utf16') {
+    return util.text.utf16.decode(view);
+  }
+
+  throw new Error('Invalid encoding: ' + encoding);
+};
+
+/** End Buffer w/UInt8Array backing */
+
+
+/**
+ * Creates a buffer that stores bytes. A value may be given to put into the
+ * buffer that is either a string of bytes or a UTF-16 string that will
+ * be encoded using UTF-8 (to do the latter, specify 'utf8' as the encoding).
+ *
+ * @param [input] the bytes to wrap (as a string) or a UTF-16 string to encode
+ *          as UTF-8.
+ * @param [encoding] (default: 'raw', other: 'utf8').
+ */
+util.createBuffer = function(input, encoding) {
+  // TODO: deprecate, use new ByteBuffer() instead
+  encoding = encoding || 'raw';
+  if(input !== undefined && encoding === 'utf8') {
+    input = util.encodeUtf8(input);
+  }
+  return new util.ByteBuffer(input);
+};
+
+/**
+ * Fills a string with a particular value. If you want the string to be a byte
+ * string, pass in String.fromCharCode(theByte).
+ *
+ * @param c the character to fill the string with, use String.fromCharCode
+ *          to fill the string with a byte value.
+ * @param n the number of characters of value c to fill with.
+ *
+ * @return the filled string.
+ */
+util.fillString = function(c, n) {
+  var s = '';
+  while(n > 0) {
+    if(n & 1) {
+      s += c;
+    }
+    n >>>= 1;
+    if(n > 0) {
+      c += c;
+    }
+  }
+  return s;
+};
+
+/**
+ * Performs a per byte XOR between two byte strings and returns the result as a
+ * string of bytes.
+ *
+ * @param s1 first string of bytes.
+ * @param s2 second string of bytes.
+ * @param n the number of bytes to XOR.
+ *
+ * @return the XOR'd result.
+ */
+util.xorBytes = function(s1, s2, n) {
+  var s3 = '';
+  var b = '';
+  var t = '';
+  var i = 0;
+  var c = 0;
+  for(; n > 0; --n, ++i) {
+    b = s1.charCodeAt(i) ^ s2.charCodeAt(i);
+    if(c >= 10) {
+      s3 += t;
+      t = '';
+      c = 0;
+    }
+    t += String.fromCharCode(b);
+    ++c;
+  }
+  s3 += t;
+  return s3;
+};
+
+/**
+ * Converts a hex string into a 'binary' encoded string of bytes.
+ *
+ * @param hex the hexadecimal string to convert.
+ *
+ * @return the binary-encoded string of bytes.
+ */
+util.hexToBytes = function(hex) {
+  // TODO: deprecate: "Deprecated. Use util.binary.hex.decode instead."
+  var rval = '';
+  var i = 0;
+  if(hex.length & 1 == 1) {
+    // odd number of characters, convert first character alone
+    i = 1;
+    rval += String.fromCharCode(parseInt(hex[0], 16));
+  }
+  // convert 2 characters (1 byte) at a time
+  for(; i < hex.length; i += 2) {
+    rval += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
+  }
+  return rval;
+};
+
+/**
+ * Converts a 'binary' encoded string of bytes to hex.
+ *
+ * @param bytes the byte string to convert.
+ *
+ * @return the string of hexadecimal characters.
+ */
+util.bytesToHex = function(bytes) {
+  // TODO: deprecate: "Deprecated. Use util.binary.hex.encode instead."
+  return util.createBuffer(bytes).toHex();
+};
+
+/**
+ * Converts an 32-bit integer to 4-big-endian byte string.
+ *
+ * @param i the integer.
+ *
+ * @return the byte string.
+ */
+util.int32ToBytes = function(i) {
+  return (
+    String.fromCharCode(i >> 24 & 0xFF) +
+    String.fromCharCode(i >> 16 & 0xFF) +
+    String.fromCharCode(i >> 8 & 0xFF) +
+    String.fromCharCode(i & 0xFF));
+};
+
+// base64 characters, reverse mapping
+var _base64 =
+  'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
+var _base64Idx = [
+/*43 -43 = 0*/
+/*'+',  1,  2,  3,'/' */
+   62, -1, -1, -1, 63,
+
+/*'0','1','2','3','4','5','6','7','8','9' */
+   52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
+
+/*15, 16, 17,'=', 19, 20, 21 */
+  -1, -1, -1, 64, -1, -1, -1,
+
+/*65 - 43 = 22*/
+/*'A','B','C','D','E','F','G','H','I','J','K','L','M', */
+   0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12,
+
+/*'N','O','P','Q','R','S','T','U','V','W','X','Y','Z' */
+   13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
+
+/*91 - 43 = 48 */
+/*48, 49, 50, 51, 52, 53 */
+  -1, -1, -1, -1, -1, -1,
+
+/*97 - 43 = 54*/
+/*'a','b','c','d','e','f','g','h','i','j','k','l','m' */
+   26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
+
+/*'n','o','p','q','r','s','t','u','v','w','x','y','z' */
+   39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
+];
+
+/**
+ * Base64 encodes a 'binary' encoded string of bytes.
+ *
+ * @param input the binary encoded string of bytes to base64-encode.
+ * @param maxline the maximum number of encoded characters per line to use,
+ *          defaults to none.
+ *
+ * @return the base64-encoded output.
+ */
+util.encode64 = function(input, maxline) {
+  // TODO: deprecate: "Deprecated. Use util.binary.base64.encode instead."
+  var line = '';
+  var output = '';
+  var chr1, chr2, chr3;
+  var i = 0;
+  while(i < input.length) {
+    chr1 = input.charCodeAt(i++);
+    chr2 = input.charCodeAt(i++);
+    chr3 = input.charCodeAt(i++);
+
+    // encode 4 character group
+    line += _base64.charAt(chr1 >> 2);
+    line += _base64.charAt(((chr1 & 3) << 4) | (chr2 >> 4));
+    if(isNaN(chr2)) {
+      line += '==';
+    } else {
+      line += _base64.charAt(((chr2 & 15) << 2) | (chr3 >> 6));
+      line += isNaN(chr3) ? '=' : _base64.charAt(chr3 & 63);
+    }
+
+    if(maxline && line.length > maxline) {
+      output += line.substr(0, maxline) + '\r\n';
+      line = line.substr(maxline);
+    }
+  }
+  output += line;
+  return output;
+};
+
+/**
+ * Base64 decodes a string into a 'binary' encoded string of bytes.
+ *
+ * @param input the base64-encoded input.
+ *
+ * @return the binary encoded string.
+ */
+util.decode64 = function(input) {
+  // TODO: deprecate: "Deprecated. Use util.binary.base64.decode instead."
+
+  // remove all non-base64 characters
+  input = input.replace(/[^A-Za-z0-9\+\/\=]/g, '');
+
+  var output = '';
+  var enc1, enc2, enc3, enc4;
+  var i = 0;
+
+  while(i < input.length) {
+    enc1 = _base64Idx[input.charCodeAt(i++) - 43];
+    enc2 = _base64Idx[input.charCodeAt(i++) - 43];
+    enc3 = _base64Idx[input.charCodeAt(i++) - 43];
+    enc4 = _base64Idx[input.charCodeAt(i++) - 43];
+
+    output += String.fromCharCode((enc1 << 2) | (enc2 >> 4));
+    if(enc3 !== 64) {
+      // decoded at least 2 bytes
+      output += String.fromCharCode(((enc2 & 15) << 4) | (enc3 >> 2));
+      if(enc4 !== 64) {
+        // decoded 3 bytes
+        output += String.fromCharCode(((enc3 & 3) << 6) | enc4);
+      }
+    }
+  }
+
+  return output;
+};
+
+/**
+ * UTF-8 encodes the given UTF-16 encoded string (a standard JavaScript
+ * string). Non-ASCII characters will be encoded as multiple bytes according
+ * to UTF-8.
+ *
+ * @param str the string to encode.
+ *
+ * @return the UTF-8 encoded string.
+ */
+util.encodeUtf8 = function(str) {
+  return unescape(encodeURIComponent(str));
+};
+
+/**
+ * Decodes a UTF-8 encoded string into a UTF-16 string.
+ *
+ * @param str the string to decode.
+ *
+ * @return the UTF-16 encoded string (standard JavaScript string).
+ */
+util.decodeUtf8 = function(str) {
+  return decodeURIComponent(escape(str));
+};
+
+// binary encoding/decoding tools
+// FIXME: Experimental. Do not use yet.
+util.binary = {
+  raw: {},
+  hex: {},
+  base64: {}
+};
+
+/**
+ * Encodes a Uint8Array as a binary-encoded string. This encoding uses
+ * a value between 0 and 255 for each character.
+ *
+ * @param bytes the Uint8Array to encode.
+ *
+ * @return the binary-encoded string.
+ */
+util.binary.raw.encode = function(bytes) {
+  return String.fromCharCode.apply(null, bytes);
+};
+
+/**
+ * Decodes a binary-encoded string to a Uint8Array. This encoding uses
+ * a value between 0 and 255 for each character.
+ *
+ * @param str the binary-encoded string to decode.
+ * @param [output] an optional Uint8Array to write the output to; if it
+ *          is too small, an exception will be thrown.
+ * @param [offset] the start offset for writing to the output (default: 0).
+ *
+ * @return the Uint8Array or the number of bytes written if output was given.
+ */
+util.binary.raw.decode = function(str, output, offset) {
+  var out = output;
+  if(!out) {
+    out = new Uint8Array(str.length);
+  }
+  offset = offset || 0;
+  var j = offset;
+  for(var i = 0; i < str.length; ++i) {
+    out[j++] = str.charCodeAt(i);
+  }
+  return output ? (j - offset) : out;
+};
+
+/**
+ * Encodes a 'binary' string, ArrayBuffer, DataView, TypedArray, or
+ * ByteBuffer as a string of hexadecimal characters.
+ *
+ * @param bytes the bytes to convert.
+ *
+ * @return the string of hexadecimal characters.
+ */
+util.binary.hex.encode = util.bytesToHex;
+
+/**
+ * Decodes a hex-encoded string to a Uint8Array.
+ *
+ * @param hex the hexadecimal string to convert.
+ * @param [output] an optional Uint8Array to write the output to; if it
+ *          is too small, an exception will be thrown.
+ * @param [offset] the start offset for writing to the output (default: 0).
+ *
+ * @return the Uint8Array or the number of bytes written if output was given.
+ */
+util.binary.hex.decode = function(hex, output, offset) {
+  var out = output;
+  if(!out) {
+    out = new Uint8Array(Math.ceil(hex.length / 2));
+  }
+  offset = offset || 0;
+  var i = 0, j = offset;
+  if(hex.length & 1) {
+    // odd number of characters, convert first character alone
+    i = 1;
+    out[j++] = parseInt(hex[0], 16);
+  }
+  // convert 2 characters (1 byte) at a time
+  for(; i < hex.length; i += 2) {
+    out[j++] = parseInt(hex.substr(i, 2), 16);
+  }
+  return output ? (j - offset) : out;
+};
+
+/**
+ * Base64-encodes a Uint8Array.
+ *
+ * @param input the Uint8Array to encode.
+ * @param maxline the maximum number of encoded characters per line to use,
+ *          defaults to none.
+ *
+ * @return the base64-encoded output string.
+ */
+util.binary.base64.encode = function(input, maxline) {
+  var line = '';
+  var output = '';
+  var chr1, chr2, chr3;
+  var i = 0;
+  while(i < input.byteLength) {
+    chr1 = input[i++];
+    chr2 = input[i++];
+    chr3 = input[i++];
+
+    // encode 4 character group
+    line += _base64.charAt(chr1 >> 2);
+    line += _base64.charAt(((chr1 & 3) << 4) | (chr2 >> 4));
+    if(isNaN(chr2)) {
+      line += '==';
+    } else {
+      line += _base64.charAt(((chr2 & 15) << 2) | (chr3 >> 6));
+      line += isNaN(chr3) ? '=' : _base64.charAt(chr3 & 63);
+    }
+
+    if(maxline && line.length > maxline) {
+      output += line.substr(0, maxline) + '\r\n';
+      line = line.substr(maxline);
+    }
+  }
+  output += line;
+  return output;
+};
+
+/**
+ * Decodes a base64-encoded string to a Uint8Array.
+ *
+ * @param input the base64-encoded input string.
+ * @param [output] an optional Uint8Array to write the output to; if it
+ *          is too small, an exception will be thrown.
+ * @param [offset] the start offset for writing to the output (default: 0).
+ *
+ * @return the Uint8Array or the number of bytes written if output was given.
+ */
+util.binary.base64.decode = function(input, output, offset) {
+  var out = output;
+  if(!out) {
+    out = new Uint8Array(Math.ceil(input.length / 4) * 3);
+  }
+
+  // remove all non-base64 characters
+  input = input.replace(/[^A-Za-z0-9\+\/\=]/g, '');
+
+  offset = offset || 0;
+  var enc1, enc2, enc3, enc4;
+  var i = 0, j = offset;
+
+  while(i < input.length) {
+    enc1 = _base64Idx[input.charCodeAt(i++) - 43];
+    enc2 = _base64Idx[input.charCodeAt(i++) - 43];
+    enc3 = _base64Idx[input.charCodeAt(i++) - 43];
+    enc4 = _base64Idx[input.charCodeAt(i++) - 43];
+
+    out[j++] = (enc1 << 2) | (enc2 >> 4);
+    if(enc3 !== 64) {
+      // decoded at least 2 bytes
+      out[j++] = ((enc2 & 15) << 4) | (enc3 >> 2);
+      if(enc4 !== 64) {
+        // decoded 3 bytes
+        out[j++] = ((enc3 & 3) << 6) | enc4;
+      }
+    }
+  }
+  
+  // make sure result is the exact decoded length
+  return output ?
+         (j - offset) :
+         out.subarray(0, j);
+};
+
+// text encoding/decoding tools
+// FIXME: Experimental. Do not use yet.
+util.text = {
+  utf8: {},
+  utf16: {}
+};
+
+/**
+ * Encodes the given string as UTF-8 in a Uint8Array.
+ *
+ * @param str the string to encode.
+ * @param [output] an optional Uint8Array to write the output to; if it
+ *          is too small, an exception will be thrown.
+ * @param [offset] the start offset for writing to the output (default: 0).
+ *
+ * @return the Uint8Array or the number of bytes written if output was given.
+ */
+util.text.utf8.encode = function(str, output, offset) {
+  str = util.encodeUtf8(str);
+  var out = output;
+  if(!out) {
+    out = new Uint8Array(str.length);
+  }
+  offset = offset || 0;
+  var j = offset;
+  for(var i = 0; i < str.length; ++i) {
+    out[j++] = str.charCodeAt(i);
+  }
+  return output ? (j - offset) : out;
+};
+
+/**
+ * Decodes the UTF-8 contents from a Uint8Array.
+ *
+ * @param bytes the Uint8Array to decode.
+ *
+ * @return the resulting string.
+ */
+util.text.utf8.decode = function(bytes) {
+  return util.decodeUtf8(String.fromCharCode.apply(null, bytes));
+};
+
+/**
+ * Encodes the given string as UTF-16 in a Uint8Array.
+ *
+ * @param str the string to encode.
+ * @param [output] an optional Uint8Array to write the output to; if it
+ *          is too small, an exception will be thrown.
+ * @param [offset] the start offset for writing to the output (default: 0).
+ *
+ * @return the Uint8Array or the number of bytes written if output was given.
+ */
+util.text.utf16.encode = function(str, output, offset) {
+  var out = output;
+  if(!out) {
+    out = new Uint8Array(str.length);
+  }
+  var view = new Uint16Array(out);
+  offset = offset || 0;
+  var j = offset;
+  var k = offset;
+  for(var i = 0; i < str.length; ++i) {
+    view[k++] = str.charCodeAt(i);
+    j += 2;
+  }
+  return output ? (j - offset) : out;
+};
+
+/**
+ * Decodes the UTF-16 contents from a Uint8Array.
+ *
+ * @param bytes the Uint8Array to decode.
+ *
+ * @return the resulting string.
+ */
+util.text.utf16.decode = function(bytes) {
+  return String.fromCharCode.apply(null, new Uint16Array(bytes));
+};
+
+/**
+ * Deflates the given data using a flash interface.
+ *
+ * @param api the flash interface.
+ * @param bytes the data.
+ * @param raw true to return only raw deflate data, false to include zlib
+ *          header and trailer.
+ *
+ * @return the deflated data as a string.
+ */
+util.deflate = function(api, bytes, raw) {
+  bytes = util.decode64(api.deflate(util.encode64(bytes)).rval);
+
+  // strip zlib header and trailer if necessary
+  if(raw) {
+    // zlib header is 2 bytes (CMF,FLG) where FLG indicates that
+    // there is a 4-byte DICT (alder-32) block before the data if
+    // its 5th bit is set
+    var start = 2;
+    var flg = bytes.charCodeAt(1);
+    if(flg & 0x20) {
+      start = 6;
+    }
+    // zlib trailer is 4 bytes of adler-32
+    bytes = bytes.substring(start, bytes.length - 4);
+  }
+
+  return bytes;
+};
+
+/**
+ * Inflates the given data using a flash interface.
+ *
+ * @param api the flash interface.
+ * @param bytes the data.
+ * @param raw true if the incoming data has no zlib header or trailer and is
+ *          raw DEFLATE data.
+ *
+ * @return the inflated data as a string, null on error.
+ */
+util.inflate = function(api, bytes, raw) {
+  // TODO: add zlib header and trailer if necessary/possible
+  var rval = api.inflate(util.encode64(bytes)).rval;
+  return (rval === null) ? null : util.decode64(rval);
+};
+
+/**
+ * Sets a storage object.
+ *
+ * @param api the storage interface.
+ * @param id the storage ID to use.
+ * @param obj the storage object, null to remove.
+ */
+var _setStorageObject = function(api, id, obj) {
+  if(!api) {
+    throw new Error('WebStorage not available.');
+  }
+
+  var rval;
+  if(obj === null) {
+    rval = api.removeItem(id);
+  } else {
+    // json-encode and base64-encode object
+    obj = util.encode64(JSON.stringify(obj));
+    rval = api.setItem(id, obj);
+  }
+
+  // handle potential flash error
+  if(typeof(rval) !== 'undefined' && rval.rval !== true) {
+    var error = new Error(rval.error.message);
+    error.id = rval.error.id;
+    error.name = rval.error.name;
+    throw error;
+  }
+};
+
+/**
+ * Gets a storage object.
+ *
+ * @param api the storage interface.
+ * @param id the storage ID to use.
+ *
+ * @return the storage object entry or null if none exists.
+ */
+var _getStorageObject = function(api, id) {
+  if(!api) {
+    throw new Error('WebStorage not available.');
+  }
+
+  // get the existing entry
+  var rval = api.getItem(id);
+
+  /* Note: We check api.init because we can't do (api == localStorage)
+    on IE because of "Class doesn't support Automation" exception. Only
+    the flash api has an init method so this works too, but we need a
+    better solution in the future. */
+
+  // flash returns item wrapped in an object, handle special case
+  if(api.init) {
+    if(rval.rval === null) {
+      if(rval.error) {
+        var error = new Error(rval.error.message);
+        error.id = rval.error.id;
+        error.name = rval.error.name;
+        throw error;
+      }
+      // no error, but also no item
+      rval = null;
+    } else {
+      rval = rval.rval;
+    }
+  }
+
+  // handle decoding
+  if(rval !== null) {
+    // base64-decode and json-decode data
+    rval = JSON.parse(util.decode64(rval));
+  }
+
+  return rval;
+};
+
+/**
+ * Stores an item in local storage.
+ *
+ * @param api the storage interface.
+ * @param id the storage ID to use.
+ * @param key the key for the item.
+ * @param data the data for the item (any javascript object/primitive).
+ */
+var _setItem = function(api, id, key, data) {
+  // get storage object
+  var obj = _getStorageObject(api, id);
+  if(obj === null) {
+    // create a new storage object
+    obj = {};
+  }
+  // update key
+  obj[key] = data;
+
+  // set storage object
+  _setStorageObject(api, id, obj);
+};
+
+/**
+ * Gets an item from local storage.
+ *
+ * @param api the storage interface.
+ * @param id the storage ID to use.
+ * @param key the key for the item.
+ *
+ * @return the item.
+ */
+var _getItem = function(api, id, key) {
+  // get storage object
+  var rval = _getStorageObject(api, id);
+  if(rval !== null) {
+    // return data at key
+    rval = (key in rval) ? rval[key] : null;
+  }
+
+  return rval;
+};
+
+/**
+ * Removes an item from local storage.
+ *
+ * @param api the storage interface.
+ * @param id the storage ID to use.
+ * @param key the key for the item.
+ */
+var _removeItem = function(api, id, key) {
+  // get storage object
+  var obj = _getStorageObject(api, id);
+  if(obj !== null && key in obj) {
+    // remove key
+    delete obj[key];
+
+    // see if entry has no keys remaining
+    var empty = true;
+    for(var prop in obj) {
+      empty = false;
+      break;
+    }
+    if(empty) {
+      // remove entry entirely if no keys are left
+      obj = null;
+    }
+
+    // set storage object
+    _setStorageObject(api, id, obj);
+  }
+};
+
+/**
+ * Clears the local disk storage identified by the given ID.
+ *
+ * @param api the storage interface.
+ * @param id the storage ID to use.
+ */
+var _clearItems = function(api, id) {
+  _setStorageObject(api, id, null);
+};
+
+/**
+ * Calls a storage function.
+ *
+ * @param func the function to call.
+ * @param args the arguments for the function.
+ * @param location the location argument.
+ *
+ * @return the return value from the function.
+ */
+var _callStorageFunction = function(func, args, location) {
+  var rval = null;
+
+  // default storage types
+  if(typeof(location) === 'undefined') {
+    location = ['web', 'flash'];
+  }
+
+  // apply storage types in order of preference
+  var type;
+  var done = false;
+  var exception = null;
+  for(var idx in location) {
+    type = location[idx];
+    try {
+      if(type === 'flash' || type === 'both') {
+        if(args[0] === null) {
+          throw new Error('Flash local storage not available.');
+        }
+        rval = func.apply(this, args);
+        done = (type === 'flash');
+      }
+      if(type === 'web' || type === 'both') {
+        args[0] = localStorage;
+        rval = func.apply(this, args);
+        done = true;
+      }
+    } catch(ex) {
+      exception = ex;
+    }
+    if(done) {
+      break;
+    }
+  }
+
+  if(!done) {
+    throw exception;
+  }
+
+  return rval;
+};
+
+/**
+ * Stores an item on local disk.
+ *
+ * The available types of local storage include 'flash', 'web', and 'both'.
+ *
+ * The type 'flash' refers to flash local storage (SharedObject). In order
+ * to use flash local storage, the 'api' parameter must be valid. The type
+ * 'web' refers to WebStorage, if supported by the browser. The type 'both'
+ * refers to storing using both 'flash' and 'web', not just one or the
+ * other.
+ *
+ * The location array should list the storage types to use in order of
+ * preference:
+ *
+ * ['flash']: flash only storage
+ * ['web']: web only storage
+ * ['both']: try to store in both
+ * ['flash','web']: store in flash first, but if not available, 'web'
+ * ['web','flash']: store in web first, but if not available, 'flash'
+ *
+ * The location array defaults to: ['web', 'flash']
+ *
+ * @param api the flash interface, null to use only WebStorage.
+ * @param id the storage ID to use.
+ * @param key the key for the item.
+ * @param data the data for the item (any javascript object/primitive).
+ * @param location an array with the preferred types of storage to use.
+ */
+util.setItem = function(api, id, key, data, location) {
+  _callStorageFunction(_setItem, arguments, location);
+};
+
+/**
+ * Gets an item on local disk.
+ *
+ * Set setItem() for details on storage types.
+ *
+ * @param api the flash interface, null to use only WebStorage.
+ * @param id the storage ID to use.
+ * @param key the key for the item.
+ * @param location an array with the preferred types of storage to use.
+ *
+ * @return the item.
+ */
+util.getItem = function(api, id, key, location) {
+  return _callStorageFunction(_getItem, arguments, location);
+};
+
+/**
+ * Removes an item on local disk.
+ *
+ * Set setItem() for details on storage types.
+ *
+ * @param api the flash interface.
+ * @param id the storage ID to use.
+ * @param key the key for the item.
+ * @param location an array with the preferred types of storage to use.
+ */
+util.removeItem = function(api, id, key, location) {
+  _callStorageFunction(_removeItem, arguments, location);
+};
+
+/**
+ * Clears the local disk storage identified by the given ID.
+ *
+ * Set setItem() for details on storage types.
+ *
+ * @param api the flash interface if flash is available.
+ * @param id the storage ID to use.
+ * @param location an array with the preferred types of storage to use.
+ */
+util.clearItems = function(api, id, location) {
+  _callStorageFunction(_clearItems, arguments, location);
+};
+
+/**
+ * Parses the scheme, host, and port from an http(s) url.
+ *
+ * @param str the url string.
+ *
+ * @return the parsed url object or null if the url is invalid.
+ */
+util.parseUrl = function(str) {
+  // FIXME: this regex looks a bit broken
+  var regex = /^(https?):\/\/([^:&^\/]*):?(\d*)(.*)$/g;
+  regex.lastIndex = 0;
+  var m = regex.exec(str);
+  var url = (m === null) ? null : {
+    full: str,
+    scheme: m[1],
+    host: m[2],
+    port: m[3],
+    path: m[4]
+  };
+  if(url) {
+    url.fullHost = url.host;
+    if(url.port) {
+      if(url.port !== 80 && url.scheme === 'http') {
+        url.fullHost += ':' + url.port;
+      } else if(url.port !== 443 && url.scheme === 'https') {
+        url.fullHost += ':' + url.port;
+      }
+    } else if(url.scheme === 'http') {
+      url.port = 80;
+    } else if(url.scheme === 'https') {
+      url.port = 443;
+    }
+    url.full = url.scheme + '://' + url.fullHost;
+  }
+  return url;
+};
+
+/* Storage for query variables */
+var _queryVariables = null;
+
+/**
+ * Returns the window location query variables. Query is parsed on the first
+ * call and the same object is returned on subsequent calls. The mapping
+ * is from keys to an array of values. Parameters without values will have
+ * an object key set but no value added to the value array. Values are
+ * unescaped.
+ *
+ * ...?k1=v1&k2=v2:
+ * {
+ *   "k1": ["v1"],
+ *   "k2": ["v2"]
+ * }
+ *
+ * ...?k1=v1&k1=v2:
+ * {
+ *   "k1": ["v1", "v2"]
+ * }
+ *
+ * ...?k1=v1&k2:
+ * {
+ *   "k1": ["v1"],
+ *   "k2": []
+ * }
+ *
+ * ...?k1=v1&k1:
+ * {
+ *   "k1": ["v1"]
+ * }
+ *
+ * ...?k1&k1:
+ * {
+ *   "k1": []
+ * }
+ *
+ * @param query the query string to parse (optional, default to cached
+ *          results from parsing window location search query).
+ *
+ * @return object mapping keys to variables.
+ */
+util.getQueryVariables = function(query) {
+  var parse = function(q) {
+    var rval = {};
+    var kvpairs = q.split('&');
+    for(var i = 0; i < kvpairs.length; i++) {
+      var pos = kvpairs[i].indexOf('=');
+      var key;
+      var val;
+      if(pos > 0) {
+        key = kvpairs[i].substring(0, pos);
+        val = kvpairs[i].substring(pos + 1);
+      } else {
+        key = kvpairs[i];
+        val = null;
+      }
+      if(!(key in rval)) {
+        rval[key] = [];
+      }
+      // disallow overriding object prototype keys
+      if(!(key in Object.prototype) && val !== null) {
+        rval[key].push(unescape(val));
+      }
+    }
+    return rval;
+  };
+
+   var rval;
+   if(typeof(query) === 'undefined') {
+     // set cached variables if needed
+     if(_queryVariables === null) {
+       if(typeof(window) === 'undefined') {
+          // no query variables available
+          _queryVariables = {};
+       } else {
+          // parse window search query
+          _queryVariables = parse(window.location.search.substring(1));
+       }
+     }
+     rval = _queryVariables;
+   } else {
+     // parse given query
+     rval = parse(query);
+   }
+   return rval;
+};
+
+/**
+ * Parses a fragment into a path and query. This method will take a URI
+ * fragment and break it up as if it were the main URI. For example:
+ *    /bar/baz?a=1&b=2
+ * results in:
+ *    {
+ *       path: ["bar", "baz"],
+ *       query: {"k1": ["v1"], "k2": ["v2"]}
+ *    }
+ *
+ * @return object with a path array and query object.
+ */
+util.parseFragment = function(fragment) {
+  // default to whole fragment
+  var fp = fragment;
+  var fq = '';
+  // split into path and query if possible at the first '?'
+  var pos = fragment.indexOf('?');
+  if(pos > 0) {
+    fp = fragment.substring(0, pos);
+    fq = fragment.substring(pos + 1);
+  }
+  // split path based on '/' and ignore first element if empty
+  var path = fp.split('/');
+  if(path.length > 0 && path[0] === '') {
+    path.shift();
+  }
+  // convert query into object
+  var query = (fq === '') ? {} : util.getQueryVariables(fq);
+
+  return {
+    pathString: fp,
+    queryString: fq,
+    path: path,
+    query: query
+  };
+};
+
+/**
+ * Makes a request out of a URI-like request string. This is intended to
+ * be used where a fragment id (after a URI '#') is parsed as a URI with
+ * path and query parts. The string should have a path beginning and
+ * delimited by '/' and optional query parameters following a '?'. The
+ * query should be a standard URL set of key value pairs delimited by
+ * '&'. For backwards compatibility the initial '/' on the path is not
+ * required. The request object has the following API, (fully described
+ * in the method code):
+ *    {
+ *       path: <the path string part>.
+ *       query: <the query string part>,
+ *       getPath(i): get part or all of the split path array,
+ *       getQuery(k, i): get part or all of a query key array,
+ *       getQueryLast(k, _default): get last element of a query key array.
+ *    }
+ *
+ * @return object with request parameters.
+ */
+util.makeRequest = function(reqString) {
+  var frag = util.parseFragment(reqString);
+  var req = {
+    // full path string
+    path: frag.pathString,
+    // full query string
+    query: frag.queryString,
+    /**
+     * Get path or element in path.
+     *
+     * @param i optional path index.
+     *
+     * @return path or part of path if i provided.
+     */
+    getPath: function(i) {
+      return (typeof(i) === 'undefined') ? frag.path : frag.path[i];
+    },
+    /**
+     * Get query, values for a key, or value for a key index.
+     *
+     * @param k optional query key.
+     * @param i optional query key index.
+     *
+     * @return query, values for a key, or value for a key index.
+     */
+    getQuery: function(k, i) {
+      var rval;
+      if(typeof(k) === 'undefined') {
+        rval = frag.query;
+      } else {
+        rval = frag.query[k];
+        if(rval && typeof(i) !== 'undefined') {
+           rval = rval[i];
+        }
+      }
+      return rval;
+    },
+    getQueryLast: function(k, _default) {
+      var rval;
+      var vals = req.getQuery(k);
+      if(vals) {
+        rval = vals[vals.length - 1];
+      } else {
+        rval = _default;
+      }
+      return rval;
+    }
+  };
+  return req;
+};
+
+/**
+ * Makes a URI out of a path, an object with query parameters, and a
+ * fragment. Uses jQuery.param() internally for query string creation.
+ * If the path is an array, it will be joined with '/'.
+ *
+ * @param path string path or array of strings.
+ * @param query object with query parameters. (optional)
+ * @param fragment fragment string. (optional)
+ *
+ * @return string object with request parameters.
+ */
+util.makeLink = function(path, query, fragment) {
+  // join path parts if needed
+  path = jQuery.isArray(path) ? path.join('/') : path;
+
+  var qstr = jQuery.param(query || {});
+  fragment = fragment || '';
+  return path +
+    ((qstr.length > 0) ? ('?' + qstr) : '') +
+    ((fragment.length > 0) ? ('#' + fragment) : '');
+};
+
+/**
+ * Follows a path of keys deep into an object hierarchy and set a value.
+ * If a key does not exist or it's value is not an object, create an
+ * object in it's place. This can be destructive to a object tree if
+ * leaf nodes are given as non-final path keys.
+ * Used to avoid exceptions from missing parts of the path.
+ *
+ * @param object the starting object.
+ * @param keys an array of string keys.
+ * @param value the value to set.
+ */
+util.setPath = function(object, keys, value) {
+  // need to start at an object
+  if(typeof(object) === 'object' && object !== null) {
+    var i = 0;
+    var len = keys.length;
+    while(i < len) {
+      var next = keys[i++];
+      if(i == len) {
+        // last
+        object[next] = value;
+      } else {
+        // more
+        var hasNext = (next in object);
+        if(!hasNext ||
+          (hasNext && typeof(object[next]) !== 'object') ||
+          (hasNext && object[next] === null)) {
+          object[next] = {};
+        }
+        object = object[next];
+      }
+    }
+  }
+};
+
+/**
+ * Follows a path of keys deep into an object hierarchy and return a value.
+ * If a key does not exist, create an object in it's place.
+ * Used to avoid exceptions from missing parts of the path.
+ *
+ * @param object the starting object.
+ * @param keys an array of string keys.
+ * @param _default value to return if path not found.
+ *
+ * @return the value at the path if found, else default if given, else
+ *         undefined.
+ */
+util.getPath = function(object, keys, _default) {
+  var i = 0;
+  var len = keys.length;
+  var hasNext = true;
+  while(hasNext && i < len &&
+    typeof(object) === 'object' && object !== null) {
+    var next = keys[i++];
+    hasNext = next in object;
+    if(hasNext) {
+      object = object[next];
+    }
+  }
+  return (hasNext ? object : _default);
+};
+
+/**
+ * Follow a path of keys deep into an object hierarchy and delete the
+ * last one. If a key does not exist, do nothing.
+ * Used to avoid exceptions from missing parts of the path.
+ *
+ * @param object the starting object.
+ * @param keys an array of string keys.
+ */
+util.deletePath = function(object, keys) {
+  // need to start at an object
+  if(typeof(object) === 'object' && object !== null) {
+    var i = 0;
+    var len = keys.length;
+    while(i < len) {
+      var next = keys[i++];
+      if(i == len) {
+        // last
+        delete object[next];
+      } else {
+        // more
+        if(!(next in object) ||
+          (typeof(object[next]) !== 'object') ||
+          (object[next] === null)) {
+           break;
+        }
+        object = object[next];
+      }
+    }
+  }
+};
+
+/**
+ * Check if an object is empty.
+ *
+ * Taken from:
+ * http://stackoverflow.com/questions/679915/how-do-i-test-for-an-empty-javascript-object-from-json/679937#679937
+ *
+ * @param object the object to check.
+ */
+util.isEmpty = function(obj) {
+  for(var prop in obj) {
+    if(obj.hasOwnProperty(prop)) {
+      return false;
+    }
+  }
+  return true;
+};
+
+/**
+ * Format with simple printf-style interpolation.
+ *
+ * %%: literal '%'
+ * %s,%o: convert next argument into a string.
+ *
+ * @param format the string to format.
+ * @param ... arguments to interpolate into the format string.
+ */
+util.format = function(format) {
+  var re = /%./g;
+  // current match
+  var match;
+  // current part
+  var part;
+  // current arg index
+  var argi = 0;
+  // collected parts to recombine later
+  var parts = [];
+  // last index found
+  var last = 0;
+  // loop while matches remain
+  while((match = re.exec(format))) {
+    part = format.substring(last, re.lastIndex - 2);
+    // don't add empty strings (ie, parts between %s%s)
+    if(part.length > 0) {
+      parts.push(part);
+    }
+    last = re.lastIndex;
+    // switch on % code
+    var code = match[0][1];
+    switch(code) {
+    case 's':
+    case 'o':
+      // check if enough arguments were given
+      if(argi < arguments.length) {
+        parts.push(arguments[argi++ + 1]);
+      } else {
+        parts.push('<?>');
+      }
+      break;
+    // FIXME: do proper formating for numbers, etc
+    //case 'f':
+    //case 'd':
+    case '%':
+      parts.push('%');
+      break;
+    default:
+      parts.push('<%' + code + '?>');
+    }
+  }
+  // add trailing part of format string
+  parts.push(format.substring(last));
+  return parts.join('');
+};
+
+/**
+ * Formats a number.
+ *
+ * http://snipplr.com/view/5945/javascript-numberformat--ported-from-php/
+ */
+util.formatNumber = function(number, decimals, dec_point, thousands_sep) {
+  // http://kevin.vanzonneveld.net
+  // +   original by: Jonas Raoni Soares Silva (http://www.jsfromhell.com)
+  // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
+  // +     bugfix by: Michael White (http://crestidg.com)
+  // +     bugfix by: Benjamin Lupton
+  // +     bugfix by: Allan Jensen (http://www.winternet.no)
+  // +    revised by: Jonas Raoni Soares Silva (http://www.jsfromhell.com)
+  // *     example 1: number_format(1234.5678, 2, '.', '');
+  // *     returns 1: 1234.57
+
+  var n = number, c = isNaN(decimals = Math.abs(decimals)) ? 2 : decimals;
+  var d = dec_point === undefined ? ',' : dec_point;
+  var t = thousands_sep === undefined ?
+   '.' : thousands_sep, s = n < 0 ? '-' : '';
+  var i = parseInt((n = Math.abs(+n || 0).toFixed(c)), 10) + '';
+  var j = (i.length > 3) ? i.length % 3 : 0;
+  return s + (j ? i.substr(0, j) + t : '') +
+    i.substr(j).replace(/(\d{3})(?=\d)/g, '$1' + t) +
+    (c ? d + Math.abs(n - i).toFixed(c).slice(2) : '');
+};
+
+/**
+ * Formats a byte size.
+ *
+ * http://snipplr.com/view/5949/format-humanize-file-byte-size-presentation-in-javascript/
+ */
+util.formatSize = function(size) {
+  if(size >= 1073741824) {
+    size = util.formatNumber(size / 1073741824, 2, '.', '') + ' GiB';
+  } else if(size >= 1048576) {
+    size = util.formatNumber(size / 1048576, 2, '.', '') + ' MiB';
+  } else if(size >= 1024) {
+    size = util.formatNumber(size / 1024, 0) + ' KiB';
+  } else {
+    size = util.formatNumber(size, 0) + ' bytes';
+  }
+  return size;
+};
+
+/**
+ * Converts an IPv4 or IPv6 string representation into bytes (in network order).
+ *
+ * @param ip the IPv4 or IPv6 address to convert.
+ *
+ * @return the 4-byte IPv6 or 16-byte IPv6 address or null if the address can't
+ *         be parsed.
+ */
+util.bytesFromIP = function(ip) {
+  if(ip.indexOf('.') !== -1) {
+    return util.bytesFromIPv4(ip);
+  }
+  if(ip.indexOf(':') !== -1) {
+    return util.bytesFromIPv6(ip);
+  }
+  return null;
+};
+
+/**
+ * Converts an IPv4 string representation into bytes (in network order).
+ *
+ * @param ip the IPv4 address to convert.
+ *
+ * @return the 4-byte address or null if the address can't be parsed.
+ */
+util.bytesFromIPv4 = function(ip) {
+  ip = ip.split('.');
+  if(ip.length !== 4) {
+    return null;
+  }
+  var b = util.createBuffer();
+  for(var i = 0; i < ip.length; ++i) {
+    var num = parseInt(ip[i], 10);
+    if(isNaN(num)) {
+      return null;
+    }
+    b.putByte(num);
+  }
+  return b.getBytes();
+};
+
+/**
+ * Converts an IPv6 string representation into bytes (in network order).
+ *
+ * @param ip the IPv6 address to convert.
+ *
+ * @return the 16-byte address or null if the address can't be parsed.
+ */
+util.bytesFromIPv6 = function(ip) {
+  var blanks = 0;
+  ip = ip.split(':').filter(function(e) {
+    if(e.length === 0) ++blanks;
+    return true;
+  });
+  var zeros = (8 - ip.length + blanks) * 2;
+  var b = util.createBuffer();
+  for(var i = 0; i < 8; ++i) {
+    if(!ip[i] || ip[i].length === 0) {
+      b.fillWithByte(0, zeros);
+      zeros = 0;
+      continue;
+    }
+    var bytes = util.hexToBytes(ip[i]);
+    if(bytes.length < 2) {
+      b.putByte(0);
+    }
+    b.putBytes(bytes);
+  }
+  return b.getBytes();
+};
+
+/**
+ * Converts 4-bytes into an IPv4 string representation or 16-bytes into
+ * an IPv6 string representation. The bytes must be in network order.
+ *
+ * @param bytes the bytes to convert.
+ *
+ * @return the IPv4 or IPv6 string representation if 4 or 16 bytes,
+ *         respectively, are given, otherwise null.
+ */
+util.bytesToIP = function(bytes) {
+  if(bytes.length === 4) {
+    return util.bytesToIPv4(bytes);
+  }
+  if(bytes.length === 16) {
+    return util.bytesToIPv6(bytes);
+  }
+  return null;
+};
+
+/**
+ * Converts 4-bytes into an IPv4 string representation. The bytes must be
+ * in network order.
+ *
+ * @param bytes the bytes to convert.
+ *
+ * @return the IPv4 string representation or null for an invalid # of bytes.
+ */
+util.bytesToIPv4 = function(bytes) {
+  if(bytes.length !== 4) {
+    return null;
+  }
+  var ip = [];
+  for(var i = 0; i < bytes.length; ++i) {
+    ip.push(bytes.charCodeAt(i));
+  }
+  return ip.join('.');
+};
+
+/**
+ * Converts 16-bytes into an IPv16 string representation. The bytes must be
+ * in network order.
+ *
+ * @param bytes the bytes to convert.
+ *
+ * @return the IPv16 string representation or null for an invalid # of bytes.
+ */
+util.bytesToIPv6 = function(bytes) {
+  if(bytes.length !== 16) {
+    return null;
+  }
+  var ip = [];
+  var zeroGroups = [];
+  var zeroMaxGroup = 0;
+  for(var i = 0; i < bytes.length; i += 2) {
+    var hex = util.bytesToHex(bytes[i] + bytes[i + 1]);
+    // canonicalize zero representation
+    while(hex[0] === '0' && hex !== '0') {
+      hex = hex.substr(1);
+    }
+    if(hex === '0') {
+      var last = zeroGroups[zeroGroups.length - 1];
+      var idx = ip.length;
+      if(!last || idx !== last.end + 1) {
+        zeroGroups.push({start: idx, end: idx});
+      } else {
+        last.end = idx;
+        if((last.end - last.start) >
+          (zeroGroups[zeroMaxGroup].end - zeroGroups[zeroMaxGroup].start)) {
+          zeroMaxGroup = zeroGroups.length - 1;
+        }
+      }
+    }
+    ip.push(hex);
+  }
+  if(zeroGroups.length > 0) {
+    var group = zeroGroups[zeroMaxGroup];
+    // only shorten group of length > 0
+    if(group.end - group.start > 0) {
+      ip.splice(group.start, group.end - group.start + 1, '');
+      if(group.start === 0) {
+        ip.unshift('');
+      }
+      if(group.end === 7) {
+        ip.push('');
+      }
+    }
+  }
+  return ip.join(':');
+};
+
+/**
+ * Estimates the number of processes that can be run concurrently. If
+ * creating Web Workers, keep in mind that the main JavaScript process needs
+ * its own core.
+ *
+ * @param options the options to use:
+ *          update true to force an update (not use the cached value).
+ * @param callback(err, max) called once the operation completes.
+ */
+util.estimateCores = function(options, callback) {
+  if(typeof options === 'function') {
+    callback = options;
+    options = {};
+  }
+  options = options || {};
+  if('cores' in util && !options.update) {
+    return callback(null, util.cores);
+  }
+  if(typeof navigator !== 'undefined' &&
+    'hardwareConcurrency' in navigator &&
+    navigator.hardwareConcurrency > 0) {
+    util.cores = navigator.hardwareConcurrency;
+    return callback(null, util.cores);
+  }
+  if(typeof Worker === 'undefined') {
+    // workers not available
+    util.cores = 1;
+    return callback(null, util.cores);
+  }
+  if(typeof Blob === 'undefined') {
+    // can't estimate, default to 2
+    util.cores = 2;
+    return callback(null, util.cores);
+  }
+
+  // create worker concurrency estimation code as blob
+  var blobUrl = URL.createObjectURL(new Blob(['(',
+    function() {
+      self.addEventListener('message', function(e) {
+        // run worker for 4 ms
+        var st = Date.now();
+        var et = st + 4;
+        while(Date.now() < et);
+        self.postMessage({st: st, et: et});
+      });
+    }.toString(),
+  ')()'], {type: 'application/javascript'}));
+
+  // take 5 samples using 16 workers
+  sample([], 5, 16);
+
+  function sample(max, samples, numWorkers) {
+    if(samples === 0) {
+      // get overlap average
+      var avg = Math.floor(max.reduce(function(avg, x) {
+        return avg + x;
+      }, 0) / max.length);
+      util.cores = Math.max(1, avg);
+      URL.revokeObjectURL(blobUrl);
+      return callback(null, util.cores);
+    }
+    map(numWorkers, function(err, results) {
+      max.push(reduce(numWorkers, results));
+      sample(max, samples - 1, numWorkers);
+    });
+  }
+
+  function map(numWorkers, callback) {
+    var workers = [];
+    var results = [];
+    for(var i = 0; i < numWorkers; ++i) {
+      var worker = new Worker(blobUrl);
+      worker.addEventListener('message', function(e) {
+        results.push(e.data);
+        if(results.length === numWorkers) {
+          for(var i = 0; i < numWorkers; ++i) {
+            workers[i].terminate();
+          }
+          callback(null, results);
+        }
+      });
+      workers.push(worker);
+    }
+    for(var i = 0; i < numWorkers; ++i) {
+      workers[i].postMessage(i);
+    }
+  }
+
+  function reduce(numWorkers, results) {
+    // find overlapping time windows
+    var overlaps = [];
+    for(var n = 0; n < numWorkers; ++n) {
+      var r1 = results[n];
+      var overlap = overlaps[n] = [];
+      for(var i = 0; i < numWorkers; ++i) {
+        if(n === i) {
+          continue;
+        }
+        var r2 = results[i];
+        if((r1.st > r2.st && r1.st < r2.et) ||
+          (r2.st > r1.st && r2.st < r1.et)) {
+          overlap.push(i);
+        }
+      }
+    }
+    // get maximum overlaps ... don't include overlapping worker itself
+    // as the main JS process was also being scheduled during the work and
+    // would have to be subtracted from the estimate anyway
+    return overlaps.reduce(function(max, overlap) {
+      return Math.max(max, overlap.length);
+    }, 0);
+  }
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'util';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/x509.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/x509.js
new file mode 100644
index 0000000..6cd8585
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/x509.js
@@ -0,0 +1,3178 @@
+/**
+ * Javascript implementation of X.509 and related components (such as
+ * Certification Signing Requests) of a Public Key Infrastructure.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2014 Digital Bazaar, Inc.
+ *
+ * The ASN.1 representation of an X.509v3 certificate is as follows
+ * (see RFC 2459):
+ *
+ * Certificate ::= SEQUENCE {
+ *   tbsCertificate       TBSCertificate,
+ *   signatureAlgorithm   AlgorithmIdentifier,
+ *   signatureValue       BIT STRING
+ * }
+ *
+ * TBSCertificate ::= SEQUENCE {
+ *   version         [0]  EXPLICIT Version DEFAULT v1,
+ *   serialNumber         CertificateSerialNumber,
+ *   signature            AlgorithmIdentifier,
+ *   issuer               Name,
+ *   validity             Validity,
+ *   subject              Name,
+ *   subjectPublicKeyInfo SubjectPublicKeyInfo,
+ *   issuerUniqueID  [1]  IMPLICIT UniqueIdentifier OPTIONAL,
+ *                        -- If present, version shall be v2 or v3
+ *   subjectUniqueID [2]  IMPLICIT UniqueIdentifier OPTIONAL,
+ *                        -- If present, version shall be v2 or v3
+ *   extensions      [3]  EXPLICIT Extensions OPTIONAL
+ *                        -- If present, version shall be v3
+ * }
+ *
+ * Version ::= INTEGER  { v1(0), v2(1), v3(2) }
+ *
+ * CertificateSerialNumber ::= INTEGER
+ *
+ * Name ::= CHOICE {
+ *   // only one possible choice for now
+ *   RDNSequence
+ * }
+ *
+ * RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
+ *
+ * RelativeDistinguishedName ::= SET OF AttributeTypeAndValue
+ *
+ * AttributeTypeAndValue ::= SEQUENCE {
+ *   type     AttributeType,
+ *   value    AttributeValue
+ * }
+ * AttributeType ::= OBJECT IDENTIFIER
+ * AttributeValue ::= ANY DEFINED BY AttributeType
+ *
+ * Validity ::= SEQUENCE {
+ *   notBefore      Time,
+ *   notAfter       Time
+ * }
+ *
+ * Time ::= CHOICE {
+ *   utcTime        UTCTime,
+ *   generalTime    GeneralizedTime
+ * }
+ *
+ * UniqueIdentifier ::= BIT STRING
+ *
+ * SubjectPublicKeyInfo ::= SEQUENCE {
+ *   algorithm            AlgorithmIdentifier,
+ *   subjectPublicKey     BIT STRING
+ * }
+ *
+ * Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension
+ *
+ * Extension ::= SEQUENCE {
+ *   extnID      OBJECT IDENTIFIER,
+ *   critical    BOOLEAN DEFAULT FALSE,
+ *   extnValue   OCTET STRING
+ * }
+ *
+ * The only key algorithm currently supported for PKI is RSA.
+ *
+ * RSASSA-PSS signatures are described in RFC 3447 and RFC 4055.
+ *
+ * PKCS#10 v1.7 describes certificate signing requests:
+ *
+ * CertificationRequestInfo:
+ *
+ * CertificationRequestInfo ::= SEQUENCE {
+ *   version       INTEGER { v1(0) } (v1,...),
+ *   subject       Name,
+ *   subjectPKInfo SubjectPublicKeyInfo{{ PKInfoAlgorithms }},
+ *   attributes    [0] Attributes{{ CRIAttributes }}
+ * }
+ *
+ * Attributes { ATTRIBUTE:IOSet } ::= SET OF Attribute{{ IOSet }}
+ *
+ * CRIAttributes  ATTRIBUTE  ::= {
+ *   ... -- add any locally defined attributes here -- }
+ *
+ * Attribute { ATTRIBUTE:IOSet } ::= SEQUENCE {
+ *   type   ATTRIBUTE.&id({IOSet}),
+ *   values SET SIZE(1..MAX) OF ATTRIBUTE.&Type({IOSet}{@type})
+ * }
+ *
+ * CertificationRequest ::= SEQUENCE {
+ *   certificationRequestInfo CertificationRequestInfo,
+ *   signatureAlgorithm AlgorithmIdentifier{{ SignatureAlgorithms }},
+ *   signature          BIT STRING
+ * }
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+// shortcut for asn.1 API
+var asn1 = forge.asn1;
+
+/* Public Key Infrastructure (PKI) implementation. */
+var pki = forge.pki = forge.pki || {};
+var oids = pki.oids;
+
+// short name OID mappings
+var _shortNames = {};
+_shortNames['CN'] = oids['commonName'];
+_shortNames['commonName'] = 'CN';
+_shortNames['C'] = oids['countryName'];
+_shortNames['countryName'] = 'C';
+_shortNames['L'] = oids['localityName'];
+_shortNames['localityName'] = 'L';
+_shortNames['ST'] = oids['stateOrProvinceName'];
+_shortNames['stateOrProvinceName'] = 'ST';
+_shortNames['O'] = oids['organizationName'];
+_shortNames['organizationName'] = 'O';
+_shortNames['OU'] = oids['organizationalUnitName'];
+_shortNames['organizationalUnitName'] = 'OU';
+_shortNames['E'] = oids['emailAddress'];
+_shortNames['emailAddress'] = 'E';
+
+// validator for an SubjectPublicKeyInfo structure
+// Note: Currently only works with an RSA public key
+var publicKeyValidator = forge.pki.rsa.publicKeyValidator;
+
+// validator for an X.509v3 certificate
+var x509CertificateValidator = {
+  name: 'Certificate',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'Certificate.TBSCertificate',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true,
+    captureAsn1: 'tbsCertificate',
+    value: [{
+      name: 'Certificate.TBSCertificate.version',
+      tagClass: asn1.Class.CONTEXT_SPECIFIC,
+      type: 0,
+      constructed: true,
+      optional: true,
+      value: [{
+        name: 'Certificate.TBSCertificate.version.integer',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.INTEGER,
+        constructed: false,
+        capture: 'certVersion'
+      }]
+    }, {
+      name: 'Certificate.TBSCertificate.serialNumber',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.INTEGER,
+      constructed: false,
+      capture: 'certSerialNumber'
+    }, {
+      name: 'Certificate.TBSCertificate.signature',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.SEQUENCE,
+      constructed: true,
+      value: [{
+        name: 'Certificate.TBSCertificate.signature.algorithm',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.OID,
+        constructed: false,
+        capture: 'certinfoSignatureOid'
+      }, {
+        name: 'Certificate.TBSCertificate.signature.parameters',
+        tagClass: asn1.Class.UNIVERSAL,
+        optional: true,
+        captureAsn1: 'certinfoSignatureParams'
+      }]
+    }, {
+      name: 'Certificate.TBSCertificate.issuer',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.SEQUENCE,
+      constructed: true,
+      captureAsn1: 'certIssuer'
+    }, {
+      name: 'Certificate.TBSCertificate.validity',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.SEQUENCE,
+      constructed: true,
+      // Note: UTC and generalized times may both appear so the capture
+      // names are based on their detected order, the names used below
+      // are only for the common case, which validity time really means
+      // "notBefore" and which means "notAfter" will be determined by order
+      value: [{
+        // notBefore (Time) (UTC time case)
+        name: 'Certificate.TBSCertificate.validity.notBefore (utc)',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.UTCTIME,
+        constructed: false,
+        optional: true,
+        capture: 'certValidity1UTCTime'
+      }, {
+        // notBefore (Time) (generalized time case)
+        name: 'Certificate.TBSCertificate.validity.notBefore (generalized)',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.GENERALIZEDTIME,
+        constructed: false,
+        optional: true,
+        capture: 'certValidity2GeneralizedTime'
+      }, {
+        // notAfter (Time) (only UTC time is supported)
+        name: 'Certificate.TBSCertificate.validity.notAfter (utc)',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.UTCTIME,
+        constructed: false,
+        optional: true,
+        capture: 'certValidity3UTCTime'
+      }, {
+        // notAfter (Time) (only UTC time is supported)
+        name: 'Certificate.TBSCertificate.validity.notAfter (generalized)',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.GENERALIZEDTIME,
+        constructed: false,
+        optional: true,
+        capture: 'certValidity4GeneralizedTime'
+      }]
+    }, {
+      // Name (subject) (RDNSequence)
+      name: 'Certificate.TBSCertificate.subject',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.SEQUENCE,
+      constructed: true,
+      captureAsn1: 'certSubject'
+    },
+      // SubjectPublicKeyInfo
+      publicKeyValidator,
+    {
+      // issuerUniqueID (optional)
+      name: 'Certificate.TBSCertificate.issuerUniqueID',
+      tagClass: asn1.Class.CONTEXT_SPECIFIC,
+      type: 1,
+      constructed: true,
+      optional: true,
+      value: [{
+        name: 'Certificate.TBSCertificate.issuerUniqueID.id',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.BITSTRING,
+        constructed: false,
+        capture: 'certIssuerUniqueId'
+      }]
+    }, {
+      // subjectUniqueID (optional)
+      name: 'Certificate.TBSCertificate.subjectUniqueID',
+      tagClass: asn1.Class.CONTEXT_SPECIFIC,
+      type: 2,
+      constructed: true,
+      optional: true,
+      value: [{
+        name: 'Certificate.TBSCertificate.subjectUniqueID.id',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.BITSTRING,
+        constructed: false,
+        capture: 'certSubjectUniqueId'
+      }]
+    }, {
+      // Extensions (optional)
+      name: 'Certificate.TBSCertificate.extensions',
+      tagClass: asn1.Class.CONTEXT_SPECIFIC,
+      type: 3,
+      constructed: true,
+      captureAsn1: 'certExtensions',
+      optional: true
+    }]
+  }, {
+    // AlgorithmIdentifier (signature algorithm)
+    name: 'Certificate.signatureAlgorithm',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true,
+    value: [{
+      // algorithm
+      name: 'Certificate.signatureAlgorithm.algorithm',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.OID,
+      constructed: false,
+      capture: 'certSignatureOid'
+    }, {
+      name: 'Certificate.TBSCertificate.signature.parameters',
+      tagClass: asn1.Class.UNIVERSAL,
+      optional: true,
+      captureAsn1: 'certSignatureParams'
+    }]
+  }, {
+    // SignatureValue
+    name: 'Certificate.signatureValue',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.BITSTRING,
+    constructed: false,
+    capture: 'certSignature'
+  }]
+};
+
+var rsassaPssParameterValidator = {
+  name: 'rsapss',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'rsapss.hashAlgorithm',
+    tagClass: asn1.Class.CONTEXT_SPECIFIC,
+    type: 0,
+    constructed: true,
+    value: [{
+      name: 'rsapss.hashAlgorithm.AlgorithmIdentifier',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Class.SEQUENCE,
+      constructed: true,
+      optional: true,
+      value: [{
+        name: 'rsapss.hashAlgorithm.AlgorithmIdentifier.algorithm',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.OID,
+        constructed: false,
+        capture: 'hashOid'
+        /* parameter block omitted, for SHA1 NULL anyhow. */
+      }]
+    }]
+  }, {
+    name: 'rsapss.maskGenAlgorithm',
+    tagClass: asn1.Class.CONTEXT_SPECIFIC,
+    type: 1,
+    constructed: true,
+    value: [{
+      name: 'rsapss.maskGenAlgorithm.AlgorithmIdentifier',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Class.SEQUENCE,
+      constructed: true,
+      optional: true,
+      value: [{
+        name: 'rsapss.maskGenAlgorithm.AlgorithmIdentifier.algorithm',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.OID,
+        constructed: false,
+        capture: 'maskGenOid'
+      }, {
+        name: 'rsapss.maskGenAlgorithm.AlgorithmIdentifier.params',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.SEQUENCE,
+        constructed: true,
+        value: [{
+          name: 'rsapss.maskGenAlgorithm.AlgorithmIdentifier.params.algorithm',
+          tagClass: asn1.Class.UNIVERSAL,
+          type: asn1.Type.OID,
+          constructed: false,
+          capture: 'maskGenHashOid'
+          /* parameter block omitted, for SHA1 NULL anyhow. */
+        }]
+      }]
+    }]
+  }, {
+    name: 'rsapss.saltLength',
+    tagClass: asn1.Class.CONTEXT_SPECIFIC,
+    type: 2,
+    optional: true,
+    value: [{
+      name: 'rsapss.saltLength.saltLength',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Class.INTEGER,
+      constructed: false,
+      capture: 'saltLength'
+    }]
+  }, {
+    name: 'rsapss.trailerField',
+    tagClass: asn1.Class.CONTEXT_SPECIFIC,
+    type: 3,
+    optional: true,
+    value: [{
+      name: 'rsapss.trailer.trailer',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Class.INTEGER,
+      constructed: false,
+      capture: 'trailer'
+    }]
+  }]
+};
+
+// validator for a CertificationRequestInfo structure
+var certificationRequestInfoValidator = {
+  name: 'CertificationRequestInfo',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  captureAsn1: 'certificationRequestInfo',
+  value: [{
+    name: 'CertificationRequestInfo.integer',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'certificationRequestInfoVersion'
+  }, {
+    // Name (subject) (RDNSequence)
+    name: 'CertificationRequestInfo.subject',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true,
+    captureAsn1: 'certificationRequestInfoSubject'
+  },
+  // SubjectPublicKeyInfo
+  publicKeyValidator,
+  {
+    name: 'CertificationRequestInfo.attributes',
+    tagClass: asn1.Class.CONTEXT_SPECIFIC,
+    type: 0,
+    constructed: true,
+    optional: true,
+    capture: 'certificationRequestInfoAttributes',
+    value: [{
+      name: 'CertificationRequestInfo.attributes',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.SEQUENCE,
+      constructed: true,
+      value: [{
+        name: 'CertificationRequestInfo.attributes.type',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.OID,
+        constructed: false
+      }, {
+        name: 'CertificationRequestInfo.attributes.value',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.SET,
+        constructed: true
+      }]
+    }]
+  }]
+};
+
+// validator for a CertificationRequest structure
+var certificationRequestValidator = {
+  name: 'CertificationRequest',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  captureAsn1: 'csr',
+  value: [
+    certificationRequestInfoValidator, {
+    // AlgorithmIdentifier (signature algorithm)
+    name: 'CertificationRequest.signatureAlgorithm',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true,
+    value: [{
+      // algorithm
+      name: 'CertificationRequest.signatureAlgorithm.algorithm',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.OID,
+      constructed: false,
+      capture: 'csrSignatureOid'
+    }, {
+      name: 'CertificationRequest.signatureAlgorithm.parameters',
+      tagClass: asn1.Class.UNIVERSAL,
+      optional: true,
+      captureAsn1: 'csrSignatureParams'
+    }]
+  }, {
+    // signature
+    name: 'CertificationRequest.signature',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.BITSTRING,
+    constructed: false,
+    capture: 'csrSignature'
+  }]
+};
+
+/**
+ * Converts an RDNSequence of ASN.1 DER-encoded RelativeDistinguishedName
+ * sets into an array with objects that have type and value properties.
+ *
+ * @param rdn the RDNSequence to convert.
+ * @param md a message digest to append type and value to if provided.
+ */
+pki.RDNAttributesAsArray = function(rdn, md) {
+  var rval = [];
+
+  // each value in 'rdn' in is a SET of RelativeDistinguishedName
+  var set, attr, obj;
+  for(var si = 0; si < rdn.value.length; ++si) {
+    // get the RelativeDistinguishedName set
+    set = rdn.value[si];
+
+    // each value in the SET is an AttributeTypeAndValue sequence
+    // containing first a type (an OID) and second a value (defined by
+    // the OID)
+    for(var i = 0; i < set.value.length; ++i) {
+      obj = {};
+      attr = set.value[i];
+      obj.type = asn1.derToOid(attr.value[0].value);
+      obj.value = attr.value[1].value;
+      obj.valueTagClass = attr.value[1].type;
+      // if the OID is known, get its name and short name
+      if(obj.type in oids) {
+        obj.name = oids[obj.type];
+        if(obj.name in _shortNames) {
+          obj.shortName = _shortNames[obj.name];
+        }
+      }
+      if(md) {
+        md.update(obj.type);
+        md.update(obj.value);
+      }
+      rval.push(obj);
+    }
+  }
+
+  return rval;
+};
+
+/**
+ * Converts ASN.1 CRIAttributes into an array with objects that have type and
+ * value properties.
+ *
+ * @param attributes the CRIAttributes to convert.
+ */
+pki.CRIAttributesAsArray = function(attributes) {
+  var rval = [];
+
+  // each value in 'attributes' in is a SEQUENCE with an OID and a SET
+  for(var si = 0; si < attributes.length; ++si) {
+    // get the attribute sequence
+    var seq = attributes[si];
+
+    // each value in the SEQUENCE containing first a type (an OID) and
+    // second a set of values (defined by the OID)
+    var type = asn1.derToOid(seq.value[0].value);
+    var values = seq.value[1].value;
+    for(var vi = 0; vi < values.length; ++vi) {
+      var obj = {};
+      obj.type = type;
+      obj.value = values[vi].value;
+      obj.valueTagClass = values[vi].type;
+      // if the OID is known, get its name and short name
+      if(obj.type in oids) {
+        obj.name = oids[obj.type];
+        if(obj.name in _shortNames) {
+          obj.shortName = _shortNames[obj.name];
+        }
+      }
+      // parse extensions
+      if(obj.type === oids.extensionRequest) {
+        obj.extensions = [];
+        for(var ei = 0; ei < obj.value.length; ++ei) {
+          obj.extensions.push(pki.certificateExtensionFromAsn1(obj.value[ei]));
+        }
+      }
+      rval.push(obj);
+    }
+  }
+
+  return rval;
+};
+
+/**
+ * Gets an issuer or subject attribute from its name, type, or short name.
+ *
+ * @param obj the issuer or subject object.
+ * @param options a short name string or an object with:
+ *          shortName the short name for the attribute.
+ *          name the name for the attribute.
+ *          type the type for the attribute.
+ *
+ * @return the attribute.
+ */
+function _getAttribute(obj, options) {
+  if(typeof options === 'string') {
+    options = {shortName: options};
+  }
+
+  var rval = null;
+  var attr;
+  for(var i = 0; rval === null && i < obj.attributes.length; ++i) {
+    attr = obj.attributes[i];
+    if(options.type && options.type === attr.type) {
+      rval = attr;
+    } else if(options.name && options.name === attr.name) {
+      rval = attr;
+    } else if(options.shortName && options.shortName === attr.shortName) {
+      rval = attr;
+    }
+  }
+  return rval;
+}
+
+/**
+ * Converts signature parameters from ASN.1 structure.
+ *
+ * Currently only RSASSA-PSS supported.  The PKCS#1 v1.5 signature scheme had
+ * no parameters.
+ *
+ * RSASSA-PSS-params  ::=  SEQUENCE  {
+ *   hashAlgorithm      [0] HashAlgorithm DEFAULT
+ *                             sha1Identifier,
+ *   maskGenAlgorithm   [1] MaskGenAlgorithm DEFAULT
+ *                             mgf1SHA1Identifier,
+ *   saltLength         [2] INTEGER DEFAULT 20,
+ *   trailerField       [3] INTEGER DEFAULT 1
+ * }
+ *
+ * HashAlgorithm  ::=  AlgorithmIdentifier
+ *
+ * MaskGenAlgorithm  ::=  AlgorithmIdentifier
+ *
+ * AlgorithmIdentifer ::= SEQUENCE {
+ *   algorithm OBJECT IDENTIFIER,
+ *   parameters ANY DEFINED BY algorithm OPTIONAL
+ * }
+ *
+ * @param oid The OID specifying the signature algorithm
+ * @param obj The ASN.1 structure holding the parameters
+ * @param fillDefaults Whether to use return default values where omitted
+ * @return signature parameter object
+ */
+var _readSignatureParameters = function(oid, obj, fillDefaults) {
+  var params = {};
+
+  if(oid !== oids['RSASSA-PSS']) {
+    return params;
+  }
+
+  if(fillDefaults) {
+    params = {
+      hash: {
+        algorithmOid: oids['sha1']
+      },
+      mgf: {
+        algorithmOid: oids['mgf1'],
+        hash: {
+          algorithmOid: oids['sha1']
+        }
+      },
+      saltLength: 20
+    };
+  }
+
+  var capture = {};
+  var errors = [];
+  if(!asn1.validate(obj, rsassaPssParameterValidator, capture, errors)) {
+    var error = new Error('Cannot read RSASSA-PSS parameter block.');
+    error.errors = errors;
+    throw error;
+  }
+
+  if(capture.hashOid !== undefined) {
+    params.hash = params.hash || {};
+    params.hash.algorithmOid = asn1.derToOid(capture.hashOid);
+  }
+
+  if(capture.maskGenOid !== undefined) {
+    params.mgf = params.mgf || {};
+    params.mgf.algorithmOid = asn1.derToOid(capture.maskGenOid);
+    params.mgf.hash = params.mgf.hash || {};
+    params.mgf.hash.algorithmOid = asn1.derToOid(capture.maskGenHashOid);
+  }
+
+  if(capture.saltLength !== undefined) {
+    params.saltLength = capture.saltLength.charCodeAt(0);
+  }
+
+  return params;
+};
+
+/**
+ * Converts an X.509 certificate from PEM format.
+ *
+ * Note: If the certificate is to be verified then compute hash should
+ * be set to true. This will scan the TBSCertificate part of the ASN.1
+ * object while it is converted so it doesn't need to be converted back
+ * to ASN.1-DER-encoding later.
+ *
+ * @param pem the PEM-formatted certificate.
+ * @param computeHash true to compute the hash for verification.
+ * @param strict true to be strict when checking ASN.1 value lengths, false to
+ *          allow truncated values (default: true).
+ *
+ * @return the certificate.
+ */
+pki.certificateFromPem = function(pem, computeHash, strict) {
+  var msg = forge.pem.decode(pem)[0];
+
+  if(msg.type !== 'CERTIFICATE' &&
+    msg.type !== 'X509 CERTIFICATE' &&
+    msg.type !== 'TRUSTED CERTIFICATE') {
+    var error = new Error('Could not convert certificate from PEM; PEM header type ' +
+      'is not "CERTIFICATE", "X509 CERTIFICATE", or "TRUSTED CERTIFICATE".');
+    error.headerType = msg.type;
+    throw error;
+  }
+  if(msg.procType && msg.procType.type === 'ENCRYPTED') {
+    throw new Error('Could not convert certificate from PEM; PEM is encrypted.');
+  }
+
+  // convert DER to ASN.1 object
+  var obj = asn1.fromDer(msg.body, strict);
+
+  return pki.certificateFromAsn1(obj, computeHash);
+};
+
+/**
+ * Converts an X.509 certificate to PEM format.
+ *
+ * @param cert the certificate.
+ * @param maxline the maximum characters per line, defaults to 64.
+ *
+ * @return the PEM-formatted certificate.
+ */
+pki.certificateToPem = function(cert, maxline) {
+  // convert to ASN.1, then DER, then PEM-encode
+  var msg = {
+    type: 'CERTIFICATE',
+    body: asn1.toDer(pki.certificateToAsn1(cert)).getBytes()
+  };
+  return forge.pem.encode(msg, {maxline: maxline});
+};
+
+/**
+ * Converts an RSA public key from PEM format.
+ *
+ * @param pem the PEM-formatted public key.
+ *
+ * @return the public key.
+ */
+pki.publicKeyFromPem = function(pem) {
+  var msg = forge.pem.decode(pem)[0];
+
+  if(msg.type !== 'PUBLIC KEY' && msg.type !== 'RSA PUBLIC KEY') {
+    var error = new Error('Could not convert public key from PEM; PEM header ' +
+      'type is not "PUBLIC KEY" or "RSA PUBLIC KEY".');
+    error.headerType = msg.type;
+    throw error;
+  }
+  if(msg.procType && msg.procType.type === 'ENCRYPTED') {
+    throw new Error('Could not convert public key from PEM; PEM is encrypted.');
+  }
+
+  // convert DER to ASN.1 object
+  var obj = asn1.fromDer(msg.body);
+
+  return pki.publicKeyFromAsn1(obj);
+};
+
+/**
+ * Converts an RSA public key to PEM format (using a SubjectPublicKeyInfo).
+ *
+ * @param key the public key.
+ * @param maxline the maximum characters per line, defaults to 64.
+ *
+ * @return the PEM-formatted public key.
+ */
+pki.publicKeyToPem = function(key, maxline) {
+  // convert to ASN.1, then DER, then PEM-encode
+  var msg = {
+    type: 'PUBLIC KEY',
+    body: asn1.toDer(pki.publicKeyToAsn1(key)).getBytes()
+  };
+  return forge.pem.encode(msg, {maxline: maxline});
+};
+
+/**
+ * Converts an RSA public key to PEM format (using an RSAPublicKey).
+ *
+ * @param key the public key.
+ * @param maxline the maximum characters per line, defaults to 64.
+ *
+ * @return the PEM-formatted public key.
+ */
+pki.publicKeyToRSAPublicKeyPem = function(key, maxline) {
+  // convert to ASN.1, then DER, then PEM-encode
+  var msg = {
+    type: 'RSA PUBLIC KEY',
+    body: asn1.toDer(pki.publicKeyToRSAPublicKey(key)).getBytes()
+  };
+  return forge.pem.encode(msg, {maxline: maxline});
+};
+
+/**
+ * Gets a fingerprint for the given public key.
+ *
+ * @param options the options to use.
+ *          [md] the message digest object to use (defaults to forge.md.sha1).
+ *          [type] the type of fingerprint, such as 'RSAPublicKey',
+ *            'SubjectPublicKeyInfo' (defaults to 'RSAPublicKey').
+ *          [encoding] an alternative output encoding, such as 'hex'
+ *            (defaults to none, outputs a byte buffer).
+ *          [delimiter] the delimiter to use between bytes for 'hex' encoded
+ *            output, eg: ':' (defaults to none).
+ *
+ * @return the fingerprint as a byte buffer or other encoding based on options.
+ */
+pki.getPublicKeyFingerprint = function(key, options) {
+  options = options || {};
+  var md = options.md || forge.md.sha1.create();
+  var type = options.type || 'RSAPublicKey';
+
+  var bytes;
+  switch(type) {
+  case 'RSAPublicKey':
+    bytes = asn1.toDer(pki.publicKeyToRSAPublicKey(key)).getBytes();
+    break;
+  case 'SubjectPublicKeyInfo':
+    bytes = asn1.toDer(pki.publicKeyToAsn1(key)).getBytes();
+    break;
+  default:
+    throw new Error('Unknown fingerprint type "' + options.type + '".');
+  }
+
+  // hash public key bytes
+  md.start();
+  md.update(bytes);
+  var digest = md.digest();
+  if(options.encoding === 'hex') {
+    var hex = digest.toHex();
+    if(options.delimiter) {
+      return hex.match(/.{2}/g).join(options.delimiter);
+    }
+    return hex;
+  } else if(options.encoding === 'binary') {
+    return digest.getBytes();
+  } else if(options.encoding) {
+    throw new Error('Unknown encoding "' + options.encoding + '".');
+  }
+  return digest;
+};
+
+/**
+ * Converts a PKCS#10 certification request (CSR) from PEM format.
+ *
+ * Note: If the certification request is to be verified then compute hash
+ * should be set to true. This will scan the CertificationRequestInfo part of
+ * the ASN.1 object while it is converted so it doesn't need to be converted
+ * back to ASN.1-DER-encoding later.
+ *
+ * @param pem the PEM-formatted certificate.
+ * @param computeHash true to compute the hash for verification.
+ * @param strict true to be strict when checking ASN.1 value lengths, false to
+ *          allow truncated values (default: true).
+ *
+ * @return the certification request (CSR).
+ */
+pki.certificationRequestFromPem = function(pem, computeHash, strict) {
+  var msg = forge.pem.decode(pem)[0];
+
+  if(msg.type !== 'CERTIFICATE REQUEST') {
+    var error = new Error('Could not convert certification request from PEM; ' +
+      'PEM header type is not "CERTIFICATE REQUEST".');
+    error.headerType = msg.type;
+    throw error;
+  }
+  if(msg.procType && msg.procType.type === 'ENCRYPTED') {
+    throw new Error('Could not convert certification request from PEM; ' +
+      'PEM is encrypted.');
+  }
+
+  // convert DER to ASN.1 object
+  var obj = asn1.fromDer(msg.body, strict);
+
+  return pki.certificationRequestFromAsn1(obj, computeHash);
+};
+
+/**
+ * Converts a PKCS#10 certification request (CSR) to PEM format.
+ *
+ * @param csr the certification request.
+ * @param maxline the maximum characters per line, defaults to 64.
+ *
+ * @return the PEM-formatted certification request.
+ */
+pki.certificationRequestToPem = function(csr, maxline) {
+  // convert to ASN.1, then DER, then PEM-encode
+  var msg = {
+    type: 'CERTIFICATE REQUEST',
+    body: asn1.toDer(pki.certificationRequestToAsn1(csr)).getBytes()
+  };
+  return forge.pem.encode(msg, {maxline: maxline});
+};
+
+/**
+ * Creates an empty X.509v3 RSA certificate.
+ *
+ * @return the certificate.
+ */
+pki.createCertificate = function() {
+  var cert = {};
+  cert.version = 0x02;
+  cert.serialNumber = '00';
+  cert.signatureOid = null;
+  cert.signature = null;
+  cert.siginfo = {};
+  cert.siginfo.algorithmOid = null;
+  cert.validity = {};
+  cert.validity.notBefore = new Date();
+  cert.validity.notAfter = new Date();
+
+  cert.issuer = {};
+  cert.issuer.getField = function(sn) {
+    return _getAttribute(cert.issuer, sn);
+  };
+  cert.issuer.addField = function(attr) {
+    _fillMissingFields([attr]);
+    cert.issuer.attributes.push(attr);
+  };
+  cert.issuer.attributes = [];
+  cert.issuer.hash = null;
+
+  cert.subject = {};
+  cert.subject.getField = function(sn) {
+    return _getAttribute(cert.subject, sn);
+  };
+  cert.subject.addField = function(attr) {
+    _fillMissingFields([attr]);
+    cert.subject.attributes.push(attr);
+  };
+  cert.subject.attributes = [];
+  cert.subject.hash = null;
+
+  cert.extensions = [];
+  cert.publicKey = null;
+  cert.md = null;
+
+  /**
+   * Sets the subject of this certificate.
+   *
+   * @param attrs the array of subject attributes to use.
+   * @param uniqueId an optional a unique ID to use.
+   */
+  cert.setSubject = function(attrs, uniqueId) {
+    // set new attributes, clear hash
+    _fillMissingFields(attrs);
+    cert.subject.attributes = attrs;
+    delete cert.subject.uniqueId;
+    if(uniqueId) {
+      cert.subject.uniqueId = uniqueId;
+    }
+    cert.subject.hash = null;
+  };
+
+  /**
+   * Sets the issuer of this certificate.
+   *
+   * @param attrs the array of issuer attributes to use.
+   * @param uniqueId an optional a unique ID to use.
+   */
+  cert.setIssuer = function(attrs, uniqueId) {
+    // set new attributes, clear hash
+    _fillMissingFields(attrs);
+    cert.issuer.attributes = attrs;
+    delete cert.issuer.uniqueId;
+    if(uniqueId) {
+      cert.issuer.uniqueId = uniqueId;
+    }
+    cert.issuer.hash = null;
+  };
+
+  /**
+   * Sets the extensions of this certificate.
+   *
+   * @param exts the array of extensions to use.
+   */
+  cert.setExtensions = function(exts) {
+    for(var i = 0; i < exts.length; ++i) {
+      _fillMissingExtensionFields(exts[i], {cert: cert});
+    }
+    // set new extensions
+    cert.extensions = exts;
+  };
+
+  /**
+   * Gets an extension by its name or id.
+   *
+   * @param options the name to use or an object with:
+   *          name the name to use.
+   *          id the id to use.
+   *
+   * @return the extension or null if not found.
+   */
+  cert.getExtension = function(options) {
+    if(typeof options === 'string') {
+      options = {name: options};
+    }
+
+    var rval = null;
+    var ext;
+    for(var i = 0; rval === null && i < cert.extensions.length; ++i) {
+      ext = cert.extensions[i];
+      if(options.id && ext.id === options.id) {
+        rval = ext;
+      } else if(options.name && ext.name === options.name) {
+        rval = ext;
+      }
+    }
+    return rval;
+  };
+
+  /**
+   * Signs this certificate using the given private key.
+   *
+   * @param key the private key to sign with.
+   * @param md the message digest object to use (defaults to forge.md.sha1).
+   */
+  cert.sign = function(key, md) {
+    // TODO: get signature OID from private key
+    cert.md = md || forge.md.sha1.create();
+    var algorithmOid = oids[cert.md.algorithm + 'WithRSAEncryption'];
+    if(!algorithmOid) {
+      var error = new Error('Could not compute certificate digest. ' +
+        'Unknown message digest algorithm OID.');
+      error.algorithm = cert.md.algorithm;
+      throw error;
+    }
+    cert.signatureOid = cert.siginfo.algorithmOid = algorithmOid;
+
+    // get TBSCertificate, convert to DER
+    cert.tbsCertificate = pki.getTBSCertificate(cert);
+    var bytes = asn1.toDer(cert.tbsCertificate);
+
+    // digest and sign
+    cert.md.update(bytes.getBytes());
+    cert.signature = key.sign(cert.md);
+  };
+
+  /**
+   * Attempts verify the signature on the passed certificate using this
+   * certificate's public key.
+   *
+   * @param child the certificate to verify.
+   *
+   * @return true if verified, false if not.
+   */
+  cert.verify = function(child) {
+    var rval = false;
+
+    if(!cert.issued(child)) {
+      var issuer = child.issuer;
+      var subject = cert.subject;
+      var error = new Error('The parent certificate did not issue the given child ' +
+        'certificate; the child certificate\'s issuer does not match the ' +
+        'parent\'s subject.');
+      error.expectedIssuer = issuer.attributes;
+      error.actualIssuer = subject.attributes;
+      throw error;
+    }
+
+    var md = child.md;
+    if(md === null) {
+      // check signature OID for supported signature types
+      if(child.signatureOid in oids) {
+        var oid = oids[child.signatureOid];
+        switch(oid) {
+        case 'sha1WithRSAEncryption':
+          md = forge.md.sha1.create();
+          break;
+        case 'md5WithRSAEncryption':
+          md = forge.md.md5.create();
+          break;
+        case 'sha256WithRSAEncryption':
+          md = forge.md.sha256.create();
+          break;
+        case 'RSASSA-PSS':
+          md = forge.md.sha256.create();
+          break;
+        }
+      }
+      if(md === null) {
+        var error = new Error('Could not compute certificate digest. ' +
+          'Unknown signature OID.');
+        error.signatureOid = child.signatureOid;
+        throw error;
+      }
+
+      // produce DER formatted TBSCertificate and digest it
+      var tbsCertificate = child.tbsCertificate || pki.getTBSCertificate(child);
+      var bytes = asn1.toDer(tbsCertificate);
+      md.update(bytes.getBytes());
+    }
+
+    if(md !== null) {
+      var scheme;
+
+      switch(child.signatureOid) {
+      case oids.sha1WithRSAEncryption:
+        scheme = undefined;  /* use PKCS#1 v1.5 padding scheme */
+        break;
+      case oids['RSASSA-PSS']:
+        var hash, mgf;
+
+        /* initialize mgf */
+        hash = oids[child.signatureParameters.mgf.hash.algorithmOid];
+        if(hash === undefined || forge.md[hash] === undefined) {
+          var error = new Error('Unsupported MGF hash function.');
+          error.oid = child.signatureParameters.mgf.hash.algorithmOid;
+          error.name = hash;
+          throw error;
+        }
+
+        mgf = oids[child.signatureParameters.mgf.algorithmOid];
+        if(mgf === undefined || forge.mgf[mgf] === undefined) {
+          var error = new Error('Unsupported MGF function.');
+          error.oid = child.signatureParameters.mgf.algorithmOid;
+          error.name = mgf;
+          throw error;
+        }
+
+        mgf = forge.mgf[mgf].create(forge.md[hash].create());
+
+        /* initialize hash function */
+        hash = oids[child.signatureParameters.hash.algorithmOid];
+        if(hash === undefined || forge.md[hash] === undefined) {
+          throw {
+            message: 'Unsupported RSASSA-PSS hash function.',
+            oid: child.signatureParameters.hash.algorithmOid,
+            name: hash
+          };
+        }
+
+        scheme = forge.pss.create(forge.md[hash].create(), mgf,
+          child.signatureParameters.saltLength);
+        break;
+      }
+
+      // verify signature on cert using public key
+      rval = cert.publicKey.verify(
+        md.digest().getBytes(), child.signature, scheme);
+    }
+
+    return rval;
+  };
+
+  /**
+   * Returns true if this certificate's issuer matches the passed
+   * certificate's subject. Note that no signature check is performed.
+   *
+   * @param parent the certificate to check.
+   *
+   * @return true if this certificate's issuer matches the passed certificate's
+   *         subject.
+   */
+  cert.isIssuer = function(parent) {
+    var rval = false;
+
+    var i = cert.issuer;
+    var s = parent.subject;
+
+    // compare hashes if present
+    if(i.hash && s.hash) {
+      rval = (i.hash === s.hash);
+    } else if(i.attributes.length === s.attributes.length) {
+      // all attributes are the same so issuer matches subject
+      rval = true;
+      var iattr, sattr;
+      for(var n = 0; rval && n < i.attributes.length; ++n) {
+        iattr = i.attributes[n];
+        sattr = s.attributes[n];
+        if(iattr.type !== sattr.type || iattr.value !== sattr.value) {
+          // attribute mismatch
+          rval = false;
+        }
+      }
+    }
+
+    return rval;
+  };
+
+  /**
+   * Returns true if this certificate's subject matches the issuer of the
+   * given certificate). Note that not signature check is performed.
+   *
+   * @param child the certificate to check.
+   *
+   * @return true if this certificate's subject matches the passed
+   *         certificate's issuer.
+   */
+  cert.issued = function(child) {
+    return child.isIssuer(cert);
+  };
+
+  /**
+   * Generates the subjectKeyIdentifier for this certificate as byte buffer.
+   *
+   * @return the subjectKeyIdentifier for this certificate as byte buffer.
+   */
+  cert.generateSubjectKeyIdentifier = function() {
+    /* See: 4.2.1.2 section of the the RFC3280, keyIdentifier is either:
+
+      (1) The keyIdentifier is composed of the 160-bit SHA-1 hash of the
+        value of the BIT STRING subjectPublicKey (excluding the tag,
+        length, and number of unused bits).
+
+      (2) The keyIdentifier is composed of a four bit type field with
+        the value 0100 followed by the least significant 60 bits of the
+        SHA-1 hash of the value of the BIT STRING subjectPublicKey
+        (excluding the tag, length, and number of unused bit string bits).
+    */
+
+    // skipping the tag, length, and number of unused bits is the same
+    // as just using the RSAPublicKey (for RSA keys, which are the
+    // only ones supported)
+    return pki.getPublicKeyFingerprint(cert.publicKey, {type: 'RSAPublicKey'});
+  };
+
+  /**
+   * Verifies the subjectKeyIdentifier extension value for this certificate
+   * against its public key. If no extension is found, false will be
+   * returned.
+   *
+   * @return true if verified, false if not.
+   */
+  cert.verifySubjectKeyIdentifier = function() {
+    var oid = oids['subjectKeyIdentifier'];
+    for(var i = 0; i < cert.extensions.length; ++i) {
+      var ext = cert.extensions[i];
+      if(ext.id === oid) {
+        var ski = cert.generateSubjectKeyIdentifier().getBytes();
+        return (forge.util.hexToBytes(ext.subjectKeyIdentifier) === ski);
+      }
+    }
+    return false;
+  };
+
+  return cert;
+};
+
+/**
+ * Converts an X.509v3 RSA certificate from an ASN.1 object.
+ *
+ * Note: If the certificate is to be verified then compute hash should
+ * be set to true. There is currently no implementation for converting
+ * a certificate back to ASN.1 so the TBSCertificate part of the ASN.1
+ * object needs to be scanned before the cert object is created.
+ *
+ * @param obj the asn1 representation of an X.509v3 RSA certificate.
+ * @param computeHash true to compute the hash for verification.
+ *
+ * @return the certificate.
+ */
+pki.certificateFromAsn1 = function(obj, computeHash) {
+  // validate certificate and capture data
+  var capture = {};
+  var errors = [];
+  if(!asn1.validate(obj, x509CertificateValidator, capture, errors)) {
+    var error = new Error('Cannot read X.509 certificate. ' +
+      'ASN.1 object is not an X509v3 Certificate.');
+    error.errors = errors;
+    throw error;
+  }
+
+  // ensure signature is not interpreted as an embedded ASN.1 object
+  if(typeof capture.certSignature !== 'string') {
+    var certSignature = '\x00';
+    for(var i = 0; i < capture.certSignature.length; ++i) {
+      certSignature += asn1.toDer(capture.certSignature[i]).getBytes();
+    }
+    capture.certSignature = certSignature;
+  }
+
+  // get oid
+  var oid = asn1.derToOid(capture.publicKeyOid);
+  if(oid !== pki.oids['rsaEncryption']) {
+    throw new Error('Cannot read public key. OID is not RSA.');
+  }
+
+  // create certificate
+  var cert = pki.createCertificate();
+  cert.version = capture.certVersion ?
+    capture.certVersion.charCodeAt(0) : 0;
+  var serial = forge.util.createBuffer(capture.certSerialNumber);
+  cert.serialNumber = serial.toHex();
+  cert.signatureOid = forge.asn1.derToOid(capture.certSignatureOid);
+  cert.signatureParameters = _readSignatureParameters(
+    cert.signatureOid, capture.certSignatureParams, true);
+  cert.siginfo.algorithmOid = forge.asn1.derToOid(capture.certinfoSignatureOid);
+  cert.siginfo.parameters = _readSignatureParameters(cert.siginfo.algorithmOid,
+    capture.certinfoSignatureParams, false);
+  // skip "unused bits" in signature value BITSTRING
+  var signature = forge.util.createBuffer(capture.certSignature);
+  ++signature.read;
+  cert.signature = signature.getBytes();
+
+  var validity = [];
+  if(capture.certValidity1UTCTime !== undefined) {
+    validity.push(asn1.utcTimeToDate(capture.certValidity1UTCTime));
+  }
+  if(capture.certValidity2GeneralizedTime !== undefined) {
+    validity.push(asn1.generalizedTimeToDate(
+      capture.certValidity2GeneralizedTime));
+  }
+  if(capture.certValidity3UTCTime !== undefined) {
+    validity.push(asn1.utcTimeToDate(capture.certValidity3UTCTime));
+  }
+  if(capture.certValidity4GeneralizedTime !== undefined) {
+    validity.push(asn1.generalizedTimeToDate(
+      capture.certValidity4GeneralizedTime));
+  }
+  if(validity.length > 2) {
+    throw new Error('Cannot read notBefore/notAfter validity times; more ' +
+      'than two times were provided in the certificate.');
+  }
+  if(validity.length < 2) {
+    throw new Error('Cannot read notBefore/notAfter validity times; they ' +
+      'were not provided as either UTCTime or GeneralizedTime.');
+  }
+  cert.validity.notBefore = validity[0];
+  cert.validity.notAfter = validity[1];
+
+  // keep TBSCertificate to preserve signature when exporting
+  cert.tbsCertificate = capture.tbsCertificate;
+
+  if(computeHash) {
+    // check signature OID for supported signature types
+    cert.md = null;
+    if(cert.signatureOid in oids) {
+      var oid = oids[cert.signatureOid];
+      switch(oid) {
+      case 'sha1WithRSAEncryption':
+        cert.md = forge.md.sha1.create();
+        break;
+      case 'md5WithRSAEncryption':
+        cert.md = forge.md.md5.create();
+        break;
+      case 'sha256WithRSAEncryption':
+        cert.md = forge.md.sha256.create();
+        break;
+      case 'RSASSA-PSS':
+        cert.md = forge.md.sha256.create();
+        break;
+      }
+    }
+    if(cert.md === null) {
+      var error = new Error('Could not compute certificate digest. ' +
+        'Unknown signature OID.');
+      error.signatureOid = cert.signatureOid;
+      throw error;
+    }
+
+    // produce DER formatted TBSCertificate and digest it
+    var bytes = asn1.toDer(cert.tbsCertificate);
+    cert.md.update(bytes.getBytes());
+  }
+
+  // handle issuer, build issuer message digest
+  var imd = forge.md.sha1.create();
+  cert.issuer.getField = function(sn) {
+    return _getAttribute(cert.issuer, sn);
+  };
+  cert.issuer.addField = function(attr) {
+    _fillMissingFields([attr]);
+    cert.issuer.attributes.push(attr);
+  };
+  cert.issuer.attributes = pki.RDNAttributesAsArray(capture.certIssuer, imd);
+  if(capture.certIssuerUniqueId) {
+    cert.issuer.uniqueId = capture.certIssuerUniqueId;
+  }
+  cert.issuer.hash = imd.digest().toHex();
+
+  // handle subject, build subject message digest
+  var smd = forge.md.sha1.create();
+  cert.subject.getField = function(sn) {
+    return _getAttribute(cert.subject, sn);
+  };
+  cert.subject.addField = function(attr) {
+    _fillMissingFields([attr]);
+    cert.subject.attributes.push(attr);
+  };
+  cert.subject.attributes = pki.RDNAttributesAsArray(capture.certSubject, smd);
+  if(capture.certSubjectUniqueId) {
+    cert.subject.uniqueId = capture.certSubjectUniqueId;
+  }
+  cert.subject.hash = smd.digest().toHex();
+
+  // handle extensions
+  if(capture.certExtensions) {
+    cert.extensions = pki.certificateExtensionsFromAsn1(capture.certExtensions);
+  } else {
+    cert.extensions = [];
+  }
+
+  // convert RSA public key from ASN.1
+  cert.publicKey = pki.publicKeyFromAsn1(capture.subjectPublicKeyInfo);
+
+  return cert;
+};
+
+/**
+ * Converts an ASN.1 extensions object (with extension sequences as its
+ * values) into an array of extension objects with types and values.
+ *
+ * Supported extensions:
+ *
+ * id-ce-keyUsage OBJECT IDENTIFIER ::=  { id-ce 15 }
+ * KeyUsage ::= BIT STRING {
+ *   digitalSignature        (0),
+ *   nonRepudiation          (1),
+ *   keyEncipherment         (2),
+ *   dataEncipherment        (3),
+ *   keyAgreement            (4),
+ *   keyCertSign             (5),
+ *   cRLSign                 (6),
+ *   encipherOnly            (7),
+ *   decipherOnly            (8)
+ * }
+ *
+ * id-ce-basicConstraints OBJECT IDENTIFIER ::=  { id-ce 19 }
+ * BasicConstraints ::= SEQUENCE {
+ *   cA                      BOOLEAN DEFAULT FALSE,
+ *   pathLenConstraint       INTEGER (0..MAX) OPTIONAL
+ * }
+ *
+ * subjectAltName EXTENSION ::= {
+ *   SYNTAX GeneralNames
+ *   IDENTIFIED BY id-ce-subjectAltName
+ * }
+ *
+ * GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
+ *
+ * GeneralName ::= CHOICE {
+ *   otherName      [0] INSTANCE OF OTHER-NAME,
+ *   rfc822Name     [1] IA5String,
+ *   dNSName        [2] IA5String,
+ *   x400Address    [3] ORAddress,
+ *   directoryName  [4] Name,
+ *   ediPartyName   [5] EDIPartyName,
+ *   uniformResourceIdentifier [6] IA5String,
+ *   IPAddress      [7] OCTET STRING,
+ *   registeredID   [8] OBJECT IDENTIFIER
+ * }
+ *
+ * OTHER-NAME ::= TYPE-IDENTIFIER
+ *
+ * EDIPartyName ::= SEQUENCE {
+ *   nameAssigner [0] DirectoryString {ub-name} OPTIONAL,
+ *   partyName    [1] DirectoryString {ub-name}
+ * }
+ *
+ * @param exts the extensions ASN.1 with extension sequences to parse.
+ *
+ * @return the array.
+ */
+pki.certificateExtensionsFromAsn1 = function(exts) {
+  var rval = [];
+  for(var i = 0; i < exts.value.length; ++i) {
+    // get extension sequence
+    var extseq = exts.value[i];
+    for(var ei = 0; ei < extseq.value.length; ++ei) {
+      rval.push(pki.certificateExtensionFromAsn1(extseq.value[ei]));
+    }
+  }
+
+  return rval;
+};
+
+/**
+ * Parses a single certificate extension from ASN.1.
+ *
+ * @param ext the extension in ASN.1 format.
+ *
+ * @return the parsed extension as an object.
+ */
+pki.certificateExtensionFromAsn1 = function(ext) {
+  // an extension has:
+  // [0] extnID      OBJECT IDENTIFIER
+  // [1] critical    BOOLEAN DEFAULT FALSE
+  // [2] extnValue   OCTET STRING
+  var e = {};
+  e.id = asn1.derToOid(ext.value[0].value);
+  e.critical = false;
+  if(ext.value[1].type === asn1.Type.BOOLEAN) {
+    e.critical = (ext.value[1].value.charCodeAt(0) !== 0x00);
+    e.value = ext.value[2].value;
+  } else {
+    e.value = ext.value[1].value;
+  }
+  // if the oid is known, get its name
+  if(e.id in oids) {
+    e.name = oids[e.id];
+
+    // handle key usage
+    if(e.name === 'keyUsage') {
+      // get value as BIT STRING
+      var ev = asn1.fromDer(e.value);
+      var b2 = 0x00;
+      var b3 = 0x00;
+      if(ev.value.length > 1) {
+        // skip first byte, just indicates unused bits which
+        // will be padded with 0s anyway
+        // get bytes with flag bits
+        b2 = ev.value.charCodeAt(1);
+        b3 = ev.value.length > 2 ? ev.value.charCodeAt(2) : 0;
+      }
+      // set flags
+      e.digitalSignature = (b2 & 0x80) === 0x80;
+      e.nonRepudiation = (b2 & 0x40) === 0x40;
+      e.keyEncipherment = (b2 & 0x20) === 0x20;
+      e.dataEncipherment = (b2 & 0x10) === 0x10;
+      e.keyAgreement = (b2 & 0x08) === 0x08;
+      e.keyCertSign = (b2 & 0x04) === 0x04;
+      e.cRLSign = (b2 & 0x02) === 0x02;
+      e.encipherOnly = (b2 & 0x01) === 0x01;
+      e.decipherOnly = (b3 & 0x80) === 0x80;
+    } else if(e.name === 'basicConstraints') {
+      // handle basic constraints
+      // get value as SEQUENCE
+      var ev = asn1.fromDer(e.value);
+      // get cA BOOLEAN flag (defaults to false)
+      if(ev.value.length > 0 && ev.value[0].type === asn1.Type.BOOLEAN) {
+        e.cA = (ev.value[0].value.charCodeAt(0) !== 0x00);
+      } else {
+        e.cA = false;
+      }
+      // get path length constraint
+      var value = null;
+      if(ev.value.length > 0 && ev.value[0].type === asn1.Type.INTEGER) {
+        value = ev.value[0].value;
+      } else if(ev.value.length > 1) {
+        value = ev.value[1].value;
+      }
+      if(value !== null) {
+        e.pathLenConstraint = asn1.derToInteger(value);
+      }
+    } else if(e.name === 'extKeyUsage') {
+      // handle extKeyUsage
+      // value is a SEQUENCE of OIDs
+      var ev = asn1.fromDer(e.value);
+      for(var vi = 0; vi < ev.value.length; ++vi) {
+        var oid = asn1.derToOid(ev.value[vi].value);
+        if(oid in oids) {
+          e[oids[oid]] = true;
+        } else {
+          e[oid] = true;
+        }
+      }
+    } else if(e.name === 'nsCertType') {
+      // handle nsCertType
+      // get value as BIT STRING
+      var ev = asn1.fromDer(e.value);
+      var b2 = 0x00;
+      if(ev.value.length > 1) {
+        // skip first byte, just indicates unused bits which
+        // will be padded with 0s anyway
+        // get bytes with flag bits
+        b2 = ev.value.charCodeAt(1);
+      }
+      // set flags
+      e.client = (b2 & 0x80) === 0x80;
+      e.server = (b2 & 0x40) === 0x40;
+      e.email = (b2 & 0x20) === 0x20;
+      e.objsign = (b2 & 0x10) === 0x10;
+      e.reserved = (b2 & 0x08) === 0x08;
+      e.sslCA = (b2 & 0x04) === 0x04;
+      e.emailCA = (b2 & 0x02) === 0x02;
+      e.objCA = (b2 & 0x01) === 0x01;
+    } else if(
+      e.name === 'subjectAltName' ||
+      e.name === 'issuerAltName') {
+      // handle subjectAltName/issuerAltName
+      e.altNames = [];
+
+      // ev is a SYNTAX SEQUENCE
+      var gn;
+      var ev = asn1.fromDer(e.value);
+      for(var n = 0; n < ev.value.length; ++n) {
+        // get GeneralName
+        gn = ev.value[n];
+
+        var altName = {
+          type: gn.type,
+          value: gn.value
+        };
+        e.altNames.push(altName);
+
+        // Note: Support for types 1,2,6,7,8
+        switch(gn.type) {
+        // rfc822Name
+        case 1:
+        // dNSName
+        case 2:
+        // uniformResourceIdentifier (URI)
+        case 6:
+          break;
+        // IPAddress
+        case 7:
+          // convert to IPv4/IPv6 string representation
+          altName.ip = forge.util.bytesToIP(gn.value);
+          break;
+        // registeredID
+        case 8:
+          altName.oid = asn1.derToOid(gn.value);
+          break;
+        default:
+          // unsupported
+        }
+      }
+    } else if(e.name === 'subjectKeyIdentifier') {
+      // value is an OCTETSTRING w/the hash of the key-type specific
+      // public key structure (eg: RSAPublicKey)
+      var ev = asn1.fromDer(e.value);
+      e.subjectKeyIdentifier = forge.util.bytesToHex(ev.value);
+    }
+  }
+  return e;
+};
+
+/**
+ * Converts a PKCS#10 certification request (CSR) from an ASN.1 object.
+ *
+ * Note: If the certification request is to be verified then compute hash
+ * should be set to true. There is currently no implementation for converting
+ * a certificate back to ASN.1 so the CertificationRequestInfo part of the
+ * ASN.1 object needs to be scanned before the csr object is created.
+ *
+ * @param obj the asn1 representation of a PKCS#10 certification request (CSR).
+ * @param computeHash true to compute the hash for verification.
+ *
+ * @return the certification request (CSR).
+ */
+pki.certificationRequestFromAsn1 = function(obj, computeHash) {
+  // validate certification request and capture data
+  var capture = {};
+  var errors = [];
+  if(!asn1.validate(obj, certificationRequestValidator, capture, errors)) {
+    var error = new Error('Cannot read PKCS#10 certificate request. ' +
+      'ASN.1 object is not a PKCS#10 CertificationRequest.');
+    error.errors = errors;
+    throw error;
+  }
+
+  // ensure signature is not interpreted as an embedded ASN.1 object
+  if(typeof capture.csrSignature !== 'string') {
+    var csrSignature = '\x00';
+    for(var i = 0; i < capture.csrSignature.length; ++i) {
+      csrSignature += asn1.toDer(capture.csrSignature[i]).getBytes();
+    }
+    capture.csrSignature = csrSignature;
+  }
+
+  // get oid
+  var oid = asn1.derToOid(capture.publicKeyOid);
+  if(oid !== pki.oids.rsaEncryption) {
+    throw new Error('Cannot read public key. OID is not RSA.');
+  }
+
+  // create certification request
+  var csr = pki.createCertificationRequest();
+  csr.version = capture.csrVersion ? capture.csrVersion.charCodeAt(0) : 0;
+  csr.signatureOid = forge.asn1.derToOid(capture.csrSignatureOid);
+  csr.signatureParameters = _readSignatureParameters(
+    csr.signatureOid, capture.csrSignatureParams, true);
+  csr.siginfo.algorithmOid = forge.asn1.derToOid(capture.csrSignatureOid);
+  csr.siginfo.parameters = _readSignatureParameters(
+    csr.siginfo.algorithmOid, capture.csrSignatureParams, false);
+  // skip "unused bits" in signature value BITSTRING
+  var signature = forge.util.createBuffer(capture.csrSignature);
+  ++signature.read;
+  csr.signature = signature.getBytes();
+
+  // keep CertificationRequestInfo to preserve signature when exporting
+  csr.certificationRequestInfo = capture.certificationRequestInfo;
+
+  if(computeHash) {
+    // check signature OID for supported signature types
+    csr.md = null;
+    if(csr.signatureOid in oids) {
+      var oid = oids[csr.signatureOid];
+      switch(oid) {
+      case 'sha1WithRSAEncryption':
+        csr.md = forge.md.sha1.create();
+        break;
+      case 'md5WithRSAEncryption':
+        csr.md = forge.md.md5.create();
+        break;
+      case 'sha256WithRSAEncryption':
+        csr.md = forge.md.sha256.create();
+        break;
+      case 'RSASSA-PSS':
+        csr.md = forge.md.sha256.create();
+        break;
+      }
+    }
+    if(csr.md === null) {
+      var error = new Error('Could not compute certification request digest. ' +
+        'Unknown signature OID.');
+      error.signatureOid = csr.signatureOid;
+      throw error;
+    }
+
+    // produce DER formatted CertificationRequestInfo and digest it
+    var bytes = asn1.toDer(csr.certificationRequestInfo);
+    csr.md.update(bytes.getBytes());
+  }
+
+  // handle subject, build subject message digest
+  var smd = forge.md.sha1.create();
+  csr.subject.getField = function(sn) {
+    return _getAttribute(csr.subject, sn);
+  };
+  csr.subject.addField = function(attr) {
+    _fillMissingFields([attr]);
+    csr.subject.attributes.push(attr);
+  };
+  csr.subject.attributes = pki.RDNAttributesAsArray(
+    capture.certificationRequestInfoSubject, smd);
+  csr.subject.hash = smd.digest().toHex();
+
+  // convert RSA public key from ASN.1
+  csr.publicKey = pki.publicKeyFromAsn1(capture.subjectPublicKeyInfo);
+
+  // convert attributes from ASN.1
+  csr.getAttribute = function(sn) {
+    return _getAttribute(csr, sn);
+  };
+  csr.addAttribute = function(attr) {
+    _fillMissingFields([attr]);
+    csr.attributes.push(attr);
+  };
+  csr.attributes = pki.CRIAttributesAsArray(
+    capture.certificationRequestInfoAttributes || []);
+
+  return csr;
+};
+
+/**
+ * Creates an empty certification request (a CSR or certificate signing
+ * request). Once created, its public key and attributes can be set and then
+ * it can be signed.
+ *
+ * @return the empty certification request.
+ */
+pki.createCertificationRequest = function() {
+  var csr = {};
+  csr.version = 0x00;
+  csr.signatureOid = null;
+  csr.signature = null;
+  csr.siginfo = {};
+  csr.siginfo.algorithmOid = null;
+
+  csr.subject = {};
+  csr.subject.getField = function(sn) {
+    return _getAttribute(csr.subject, sn);
+  };
+  csr.subject.addField = function(attr) {
+    _fillMissingFields([attr]);
+    csr.subject.attributes.push(attr);
+  };
+  csr.subject.attributes = [];
+  csr.subject.hash = null;
+
+  csr.publicKey = null;
+  csr.attributes = [];
+  csr.getAttribute = function(sn) {
+    return _getAttribute(csr, sn);
+  };
+  csr.addAttribute = function(attr) {
+    _fillMissingFields([attr]);
+    csr.attributes.push(attr);
+  };
+  csr.md = null;
+
+  /**
+   * Sets the subject of this certification request.
+   *
+   * @param attrs the array of subject attributes to use.
+   */
+  csr.setSubject = function(attrs) {
+    // set new attributes
+    _fillMissingFields(attrs);
+    csr.subject.attributes = attrs;
+    csr.subject.hash = null;
+  };
+
+  /**
+   * Sets the attributes of this certification request.
+   *
+   * @param attrs the array of attributes to use.
+   */
+  csr.setAttributes = function(attrs) {
+    // set new attributes
+    _fillMissingFields(attrs);
+    csr.attributes = attrs;
+  };
+
+  /**
+   * Signs this certification request using the given private key.
+   *
+   * @param key the private key to sign with.
+   * @param md the message digest object to use (defaults to forge.md.sha1).
+   */
+  csr.sign = function(key, md) {
+    // TODO: get signature OID from private key
+    csr.md = md || forge.md.sha1.create();
+    var algorithmOid = oids[csr.md.algorithm + 'WithRSAEncryption'];
+    if(!algorithmOid) {
+      var error = new Error('Could not compute certification request digest. ' +
+        'Unknown message digest algorithm OID.');
+      error.algorithm = csr.md.algorithm;
+      throw error;
+    }
+    csr.signatureOid = csr.siginfo.algorithmOid = algorithmOid;
+
+    // get CertificationRequestInfo, convert to DER
+    csr.certificationRequestInfo = pki.getCertificationRequestInfo(csr);
+    var bytes = asn1.toDer(csr.certificationRequestInfo);
+
+    // digest and sign
+    csr.md.update(bytes.getBytes());
+    csr.signature = key.sign(csr.md);
+  };
+
+  /**
+   * Attempts verify the signature on the passed certification request using
+   * its public key.
+   *
+   * A CSR that has been exported to a file in PEM format can be verified using
+   * OpenSSL using this command:
+   *
+   * openssl req -in <the-csr-pem-file> -verify -noout -text
+   *
+   * @return true if verified, false if not.
+   */
+  csr.verify = function() {
+    var rval = false;
+
+    var md = csr.md;
+    if(md === null) {
+      // check signature OID for supported signature types
+      if(csr.signatureOid in oids) {
+        var oid = oids[csr.signatureOid];
+        switch(oid) {
+        case 'sha1WithRSAEncryption':
+          md = forge.md.sha1.create();
+          break;
+        case 'md5WithRSAEncryption':
+          md = forge.md.md5.create();
+          break;
+        case 'sha256WithRSAEncryption':
+          md = forge.md.sha256.create();
+          break;
+        case 'RSASSA-PSS':
+          md = forge.md.sha256.create();
+          break;
+        }
+      }
+      if(md === null) {
+        var error = new Error('Could not compute certification request digest. ' +
+          'Unknown signature OID.');
+        error.signatureOid = csr.signatureOid;
+        throw error;
+      }
+
+      // produce DER formatted CertificationRequestInfo and digest it
+      var cri = csr.certificationRequestInfo ||
+        pki.getCertificationRequestInfo(csr);
+      var bytes = asn1.toDer(cri);
+      md.update(bytes.getBytes());
+    }
+
+    if(md !== null) {
+      var scheme;
+
+      switch(csr.signatureOid) {
+      case oids.sha1WithRSAEncryption:
+        /* use PKCS#1 v1.5 padding scheme */
+        break;
+      case oids['RSASSA-PSS']:
+        var hash, mgf;
+
+        /* initialize mgf */
+        hash = oids[csr.signatureParameters.mgf.hash.algorithmOid];
+        if(hash === undefined || forge.md[hash] === undefined) {
+          var error = new Error('Unsupported MGF hash function.');
+          error.oid = csr.signatureParameters.mgf.hash.algorithmOid;
+          error.name = hash;
+          throw error;
+        }
+
+        mgf = oids[csr.signatureParameters.mgf.algorithmOid];
+        if(mgf === undefined || forge.mgf[mgf] === undefined) {
+          var error = new Error('Unsupported MGF function.');
+          error.oid = csr.signatureParameters.mgf.algorithmOid;
+          error.name = mgf;
+          throw error;
+        }
+
+        mgf = forge.mgf[mgf].create(forge.md[hash].create());
+
+        /* initialize hash function */
+        hash = oids[csr.signatureParameters.hash.algorithmOid];
+        if(hash === undefined || forge.md[hash] === undefined) {
+          var error = new Error('Unsupported RSASSA-PSS hash function.');
+          error.oid = csr.signatureParameters.hash.algorithmOid;
+          error.name = hash;
+          throw error;
+        }
+
+        scheme = forge.pss.create(forge.md[hash].create(), mgf,
+          csr.signatureParameters.saltLength);
+        break;
+      }
+
+      // verify signature on csr using its public key
+      rval = csr.publicKey.verify(
+        md.digest().getBytes(), csr.signature, scheme);
+    }
+
+    return rval;
+  };
+
+  return csr;
+};
+
+/**
+ * Converts an X.509 subject or issuer to an ASN.1 RDNSequence.
+ *
+ * @param obj the subject or issuer (distinguished name).
+ *
+ * @return the ASN.1 RDNSequence.
+ */
+function _dnToAsn1(obj) {
+  // create an empty RDNSequence
+  var rval = asn1.create(
+    asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []);
+
+  // iterate over attributes
+  var attr, set;
+  var attrs = obj.attributes;
+  for(var i = 0; i < attrs.length; ++i) {
+    attr = attrs[i];
+    var value = attr.value;
+
+    // reuse tag class for attribute value if available
+    var valueTagClass = asn1.Type.PRINTABLESTRING;
+    if('valueTagClass' in attr) {
+      valueTagClass = attr.valueTagClass;
+
+      if(valueTagClass === asn1.Type.UTF8) {
+        value = forge.util.encodeUtf8(value);
+      }
+      // FIXME: handle more encodings
+    }
+
+    // create a RelativeDistinguishedName set
+    // each value in the set is an AttributeTypeAndValue first
+    // containing the type (an OID) and second the value
+    set = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true, [
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+        // AttributeType
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+          asn1.oidToDer(attr.type).getBytes()),
+        // AttributeValue
+        asn1.create(asn1.Class.UNIVERSAL, valueTagClass, false, value)
+      ])
+    ]);
+    rval.value.push(set);
+  }
+
+  return rval;
+}
+
+/**
+ * Gets all printable attributes (typically of an issuer or subject) in a
+ * simplified JSON format for display.
+ *
+ * @param attrs the attributes.
+ *
+ * @return the JSON for display.
+ */
+function _getAttributesAsJson(attrs) {
+  var rval = {};
+  for(var i = 0; i < attrs.length; ++i) {
+    var attr = attrs[i];
+    if(attr.shortName && (
+      attr.valueTagClass === asn1.Type.UTF8 ||
+      attr.valueTagClass === asn1.Type.PRINTABLESTRING ||
+      attr.valueTagClass === asn1.Type.IA5STRING)) {
+      var value = attr.value;
+      if(attr.valueTagClass === asn1.Type.UTF8) {
+        value = forge.util.encodeUtf8(attr.value);
+      }
+      if(!(attr.shortName in rval)) {
+        rval[attr.shortName] = value;
+      } else if(forge.util.isArray(rval[attr.shortName])) {
+        rval[attr.shortName].push(value);
+      } else {
+        rval[attr.shortName] = [rval[attr.shortName], value];
+      }
+    }
+  }
+  return rval;
+}
+
+/**
+ * Fills in missing fields in attributes.
+ *
+ * @param attrs the attributes to fill missing fields in.
+ */
+function _fillMissingFields(attrs) {
+  var attr;
+  for(var i = 0; i < attrs.length; ++i) {
+    attr = attrs[i];
+
+    // populate missing name
+    if(typeof attr.name === 'undefined') {
+      if(attr.type && attr.type in pki.oids) {
+        attr.name = pki.oids[attr.type];
+      } else if(attr.shortName && attr.shortName in _shortNames) {
+        attr.name = pki.oids[_shortNames[attr.shortName]];
+      }
+    }
+
+    // populate missing type (OID)
+    if(typeof attr.type === 'undefined') {
+      if(attr.name && attr.name in pki.oids) {
+        attr.type = pki.oids[attr.name];
+      } else {
+        var error = new Error('Attribute type not specified.');
+        error.attribute = attr;
+        throw error;
+      }
+    }
+
+    // populate missing shortname
+    if(typeof attr.shortName === 'undefined') {
+      if(attr.name && attr.name in _shortNames) {
+        attr.shortName = _shortNames[attr.name];
+      }
+    }
+
+    // convert extensions to value
+    if(attr.type === oids.extensionRequest) {
+      attr.valueConstructed = true;
+      attr.valueTagClass = asn1.Type.SEQUENCE;
+      if(!attr.value && attr.extensions) {
+        attr.value = [];
+        for(var ei = 0; ei < attr.extensions.length; ++ei) {
+          attr.value.push(pki.certificateExtensionToAsn1(
+            _fillMissingExtensionFields(attr.extensions[ei])));
+        }
+      }
+    }
+
+    if(typeof attr.value === 'undefined') {
+      var error = new Error('Attribute value not specified.');
+      error.attribute = attr;
+      throw error;
+    }
+  }
+}
+
+/**
+ * Fills in missing fields in certificate extensions.
+ *
+ * @param e the extension.
+ * @param [options] the options to use.
+ *          [cert] the certificate the extensions are for.
+ *
+ * @return the extension.
+ */
+function _fillMissingExtensionFields(e, options) {
+  options = options || {};
+
+  // populate missing name
+  if(typeof e.name === 'undefined') {
+    if(e.id && e.id in pki.oids) {
+      e.name = pki.oids[e.id];
+    }
+  }
+
+  // populate missing id
+  if(typeof e.id === 'undefined') {
+    if(e.name && e.name in pki.oids) {
+      e.id = pki.oids[e.name];
+    } else {
+      var error = new Error('Extension ID not specified.');
+      error.extension = e;
+      throw error;
+    }
+  }
+
+  if(typeof e.value !== 'undefined') {
+    return;
+  }
+
+  // handle missing value:
+
+  // value is a BIT STRING
+  if(e.name === 'keyUsage') {
+    // build flags
+    var unused = 0;
+    var b2 = 0x00;
+    var b3 = 0x00;
+    if(e.digitalSignature) {
+      b2 |= 0x80;
+      unused = 7;
+    }
+    if(e.nonRepudiation) {
+      b2 |= 0x40;
+      unused = 6;
+    }
+    if(e.keyEncipherment) {
+      b2 |= 0x20;
+      unused = 5;
+    }
+    if(e.dataEncipherment) {
+      b2 |= 0x10;
+      unused = 4;
+    }
+    if(e.keyAgreement) {
+      b2 |= 0x08;
+      unused = 3;
+    }
+    if(e.keyCertSign) {
+      b2 |= 0x04;
+      unused = 2;
+    }
+    if(e.cRLSign) {
+      b2 |= 0x02;
+      unused = 1;
+    }
+    if(e.encipherOnly) {
+      b2 |= 0x01;
+      unused = 0;
+    }
+    if(e.decipherOnly) {
+      b3 |= 0x80;
+      unused = 7;
+    }
+
+    // create bit string
+    var value = String.fromCharCode(unused);
+    if(b3 !== 0) {
+      value += String.fromCharCode(b2) + String.fromCharCode(b3);
+    } else if(b2 !== 0) {
+      value += String.fromCharCode(b2);
+    }
+    e.value = asn1.create(
+      asn1.Class.UNIVERSAL, asn1.Type.BITSTRING, false, value);
+  } else if(e.name === 'basicConstraints') {
+    // basicConstraints is a SEQUENCE
+    e.value = asn1.create(
+      asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []);
+    // cA BOOLEAN flag defaults to false
+    if(e.cA) {
+      e.value.value.push(asn1.create(
+        asn1.Class.UNIVERSAL, asn1.Type.BOOLEAN, false,
+        String.fromCharCode(0xFF)));
+    }
+    if('pathLenConstraint' in e) {
+      e.value.value.push(asn1.create(
+        asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+        asn1.integerToDer(e.pathLenConstraint).getBytes()));
+    }
+  } else if(e.name === 'extKeyUsage') {
+    // extKeyUsage is a SEQUENCE of OIDs
+    e.value = asn1.create(
+      asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []);
+    var seq = e.value.value;
+    for(var key in e) {
+      if(e[key] !== true) {
+        continue;
+      }
+      // key is name in OID map
+      if(key in oids) {
+        seq.push(asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID,
+          false, asn1.oidToDer(oids[key]).getBytes()));
+      } else if(key.indexOf('.') !== -1) {
+        // assume key is an OID
+        seq.push(asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID,
+          false, asn1.oidToDer(key).getBytes()));
+      }
+    }
+  } else if(e.name === 'nsCertType') {
+    // nsCertType is a BIT STRING
+    // build flags
+    var unused = 0;
+    var b2 = 0x00;
+
+    if(e.client) {
+      b2 |= 0x80;
+      unused = 7;
+    }
+    if(e.server) {
+      b2 |= 0x40;
+      unused = 6;
+    }
+    if(e.email) {
+      b2 |= 0x20;
+      unused = 5;
+    }
+    if(e.objsign) {
+      b2 |= 0x10;
+      unused = 4;
+    }
+    if(e.reserved) {
+      b2 |= 0x08;
+      unused = 3;
+    }
+    if(e.sslCA) {
+      b2 |= 0x04;
+      unused = 2;
+    }
+    if(e.emailCA) {
+      b2 |= 0x02;
+      unused = 1;
+    }
+    if(e.objCA) {
+      b2 |= 0x01;
+      unused = 0;
+    }
+
+    // create bit string
+    var value = String.fromCharCode(unused);
+    if(b2 !== 0) {
+      value += String.fromCharCode(b2);
+    }
+    e.value = asn1.create(
+      asn1.Class.UNIVERSAL, asn1.Type.BITSTRING, false, value);
+  } else if(e.name === 'subjectAltName' || e.name === 'issuerAltName') {
+    // SYNTAX SEQUENCE
+    e.value = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []);
+
+    var altName;
+    for(var n = 0; n < e.altNames.length; ++n) {
+      altName = e.altNames[n];
+      var value = altName.value;
+      // handle IP
+      if(altName.type === 7 && altName.ip) {
+        value = forge.util.bytesFromIP(altName.ip);
+        if(value === null) {
+          var error = new Error(
+            'Extension "ip" value is not a valid IPv4 or IPv6 address.');
+          error.extension = e;
+          throw error;
+        }
+      } else if(altName.type === 8) {
+        // handle OID
+        if(altName.oid) {
+          value = asn1.oidToDer(asn1.oidToDer(altName.oid));
+        } else {
+          // deprecated ... convert value to OID
+          value = asn1.oidToDer(value);
+        }
+      }
+      e.value.value.push(asn1.create(
+        asn1.Class.CONTEXT_SPECIFIC, altName.type, false,
+        value));
+    }
+  } else if(e.name === 'subjectKeyIdentifier' && options.cert) {
+    var ski = options.cert.generateSubjectKeyIdentifier();
+    e.subjectKeyIdentifier = ski.toHex();
+    // OCTETSTRING w/digest
+    e.value = asn1.create(
+      asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, ski.getBytes());
+  }
+
+  // ensure value has been defined by now
+  if(typeof e.value === 'undefined') {
+    var error = new Error('Extension value not specified.');
+    error.extension = e;
+    throw error;
+  }
+
+  return e;
+}
+
+/**
+ * Convert signature parameters object to ASN.1
+ *
+ * @param {String} oid Signature algorithm OID
+ * @param params The signature parametrs object
+ * @return ASN.1 object representing signature parameters
+ */
+function _signatureParametersToAsn1(oid, params) {
+  switch(oid) {
+  case oids['RSASSA-PSS']:
+    var parts = [];
+
+    if(params.hash.algorithmOid !== undefined) {
+      parts.push(asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+            asn1.oidToDer(params.hash.algorithmOid).getBytes()),
+          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
+        ])
+      ]));
+    }
+
+    if(params.mgf.algorithmOid !== undefined) {
+      parts.push(asn1.create(asn1.Class.CONTEXT_SPECIFIC, 1, true, [
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+            asn1.oidToDer(params.mgf.algorithmOid).getBytes()),
+          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+            asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+              asn1.oidToDer(params.mgf.hash.algorithmOid).getBytes()),
+            asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
+          ])
+        ])
+      ]));
+    }
+
+    if(params.saltLength !== undefined) {
+      parts.push(asn1.create(asn1.Class.CONTEXT_SPECIFIC, 2, true, [
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+          asn1.integerToDer(params.saltLength).getBytes())
+      ]));
+    }
+
+    return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, parts);
+
+  default:
+    return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '');
+  }
+}
+
+/**
+ * Converts a certification request's attributes to an ASN.1 set of
+ * CRIAttributes.
+ *
+ * @param csr certification request.
+ *
+ * @return the ASN.1 set of CRIAttributes.
+ */
+function _CRIAttributesToAsn1(csr) {
+  // create an empty context-specific container
+  var rval = asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, []);
+
+  // no attributes, return empty container
+  if(csr.attributes.length === 0) {
+    return rval;
+  }
+
+  // each attribute has a sequence with a type and a set of values
+  var attrs = csr.attributes;
+  for(var i = 0; i < attrs.length; ++i) {
+    var attr = attrs[i];
+    var value = attr.value;
+
+    // reuse tag class for attribute value if available
+    var valueTagClass = asn1.Type.UTF8;
+    if('valueTagClass' in attr) {
+      valueTagClass = attr.valueTagClass;
+    }
+    if(valueTagClass === asn1.Type.UTF8) {
+      value = forge.util.encodeUtf8(value);
+    }
+    var valueConstructed = false;
+    if('valueConstructed' in attr) {
+      valueConstructed = attr.valueConstructed;
+    }
+    // FIXME: handle more encodings
+
+    // create a RelativeDistinguishedName set
+    // each value in the set is an AttributeTypeAndValue first
+    // containing the type (an OID) and second the value
+    var seq = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+      // AttributeType
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+        asn1.oidToDer(attr.type).getBytes()),
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true, [
+        // AttributeValue
+        asn1.create(
+          asn1.Class.UNIVERSAL, valueTagClass, valueConstructed, value)
+      ])
+    ]);
+    rval.value.push(seq);
+  }
+
+  return rval;
+}
+
+/**
+ * Gets the ASN.1 TBSCertificate part of an X.509v3 certificate.
+ *
+ * @param cert the certificate.
+ *
+ * @return the asn1 TBSCertificate.
+ */
+pki.getTBSCertificate = function(cert) {
+  // TBSCertificate
+  var tbs = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+    // version
+    asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
+      // integer
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+        asn1.integerToDer(cert.version).getBytes())
+    ]),
+    // serialNumber
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      forge.util.hexToBytes(cert.serialNumber)),
+    // signature
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+      // algorithm
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+        asn1.oidToDer(cert.siginfo.algorithmOid).getBytes()),
+      // parameters
+      _signatureParametersToAsn1(
+        cert.siginfo.algorithmOid, cert.siginfo.parameters)
+    ]),
+    // issuer
+    _dnToAsn1(cert.issuer),
+    // validity
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+      // notBefore
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.UTCTIME, false,
+        asn1.dateToUtcTime(cert.validity.notBefore)),
+      // notAfter
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.UTCTIME, false,
+        asn1.dateToUtcTime(cert.validity.notAfter))
+    ]),
+    // subject
+    _dnToAsn1(cert.subject),
+    // SubjectPublicKeyInfo
+    pki.publicKeyToAsn1(cert.publicKey)
+  ]);
+
+  if(cert.issuer.uniqueId) {
+    // issuerUniqueID (optional)
+    tbs.value.push(
+      asn1.create(asn1.Class.CONTEXT_SPECIFIC, 1, true, [
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.BITSTRING, false,
+          String.fromCharCode(0x00) +
+          cert.issuer.uniqueId
+        )
+      ])
+    );
+  }
+  if(cert.subject.uniqueId) {
+    // subjectUniqueID (optional)
+    tbs.value.push(
+      asn1.create(asn1.Class.CONTEXT_SPECIFIC, 2, true, [
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.BITSTRING, false,
+          String.fromCharCode(0x00) +
+          cert.subject.uniqueId
+        )
+      ])
+    );
+  }
+
+  if(cert.extensions.length > 0) {
+    // extensions (optional)
+    tbs.value.push(pki.certificateExtensionsToAsn1(cert.extensions));
+  }
+
+  return tbs;
+};
+
+/**
+ * Gets the ASN.1 CertificationRequestInfo part of a
+ * PKCS#10 CertificationRequest.
+ *
+ * @param csr the certification request.
+ *
+ * @return the asn1 CertificationRequestInfo.
+ */
+pki.getCertificationRequestInfo = function(csr) {
+  // CertificationRequestInfo
+  var cri = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+    // version
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      asn1.integerToDer(csr.version).getBytes()),
+    // subject
+    _dnToAsn1(csr.subject),
+    // SubjectPublicKeyInfo
+    pki.publicKeyToAsn1(csr.publicKey),
+    // attributes
+    _CRIAttributesToAsn1(csr)
+  ]);
+
+  return cri;
+};
+
+/**
+ * Converts a DistinguishedName (subject or issuer) to an ASN.1 object.
+ *
+ * @param dn the DistinguishedName.
+ *
+ * @return the asn1 representation of a DistinguishedName.
+ */
+pki.distinguishedNameToAsn1 = function(dn) {
+  return _dnToAsn1(dn);
+};
+
+/**
+ * Converts an X.509v3 RSA certificate to an ASN.1 object.
+ *
+ * @param cert the certificate.
+ *
+ * @return the asn1 representation of an X.509v3 RSA certificate.
+ */
+pki.certificateToAsn1 = function(cert) {
+  // prefer cached TBSCertificate over generating one
+  var tbsCertificate = cert.tbsCertificate || pki.getTBSCertificate(cert);
+
+  // Certificate
+  return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+    // TBSCertificate
+    tbsCertificate,
+    // AlgorithmIdentifier (signature algorithm)
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+      // algorithm
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+        asn1.oidToDer(cert.signatureOid).getBytes()),
+      // parameters
+      _signatureParametersToAsn1(cert.signatureOid, cert.signatureParameters)
+    ]),
+    // SignatureValue
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.BITSTRING, false,
+      String.fromCharCode(0x00) + cert.signature)
+  ]);
+};
+
+/**
+ * Converts X.509v3 certificate extensions to ASN.1.
+ *
+ * @param exts the extensions to convert.
+ *
+ * @return the extensions in ASN.1 format.
+ */
+pki.certificateExtensionsToAsn1 = function(exts) {
+  // create top-level extension container
+  var rval = asn1.create(asn1.Class.CONTEXT_SPECIFIC, 3, true, []);
+
+  // create extension sequence (stores a sequence for each extension)
+  var seq = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []);
+  rval.value.push(seq);
+
+  for(var i = 0; i < exts.length; ++i) {
+    seq.value.push(pki.certificateExtensionToAsn1(exts[i]));
+  }
+
+  return rval;
+};
+
+/**
+ * Converts a single certificate extension to ASN.1.
+ *
+ * @param ext the extension to convert.
+ *
+ * @return the extension in ASN.1 format.
+ */
+pki.certificateExtensionToAsn1 = function(ext) {
+  // create a sequence for each extension
+  var extseq = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []);
+
+  // extnID (OID)
+  extseq.value.push(asn1.create(
+    asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+    asn1.oidToDer(ext.id).getBytes()));
+
+  // critical defaults to false
+  if(ext.critical) {
+    // critical BOOLEAN DEFAULT FALSE
+    extseq.value.push(asn1.create(
+      asn1.Class.UNIVERSAL, asn1.Type.BOOLEAN, false,
+      String.fromCharCode(0xFF)));
+  }
+
+  var value = ext.value;
+  if(typeof ext.value !== 'string') {
+    // value is asn.1
+    value = asn1.toDer(value).getBytes();
+  }
+
+  // extnValue (OCTET STRING)
+  extseq.value.push(asn1.create(
+    asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, value));
+
+  return extseq;
+};
+
+/**
+ * Converts a PKCS#10 certification request to an ASN.1 object.
+ *
+ * @param csr the certification request.
+ *
+ * @return the asn1 representation of a certification request.
+ */
+pki.certificationRequestToAsn1 = function(csr) {
+  // prefer cached CertificationRequestInfo over generating one
+  var cri = csr.certificationRequestInfo ||
+    pki.getCertificationRequestInfo(csr);
+
+  // Certificate
+  return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+    // CertificationRequestInfo
+    cri,
+    // AlgorithmIdentifier (signature algorithm)
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+      // algorithm
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+        asn1.oidToDer(csr.signatureOid).getBytes()),
+      // parameters
+      _signatureParametersToAsn1(csr.signatureOid, csr.signatureParameters)
+    ]),
+    // signature
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.BITSTRING, false,
+      String.fromCharCode(0x00) + csr.signature)
+  ]);
+};
+
+/**
+ * Creates a CA store.
+ *
+ * @param certs an optional array of certificate objects or PEM-formatted
+ *          certificate strings to add to the CA store.
+ *
+ * @return the CA store.
+ */
+pki.createCaStore = function(certs) {
+  // create CA store
+  var caStore = {
+    // stored certificates
+    certs: {}
+  };
+
+  /**
+   * Gets the certificate that issued the passed certificate or its
+   * 'parent'.
+   *
+   * @param cert the certificate to get the parent for.
+   *
+   * @return the parent certificate or null if none was found.
+   */
+  caStore.getIssuer = function(cert) {
+    var rval = getBySubject(cert.issuer);
+
+    // see if there are multiple matches
+    /*if(forge.util.isArray(rval)) {
+      // TODO: resolve multiple matches by checking
+      // authorityKey/subjectKey/issuerUniqueID/other identifiers, etc.
+      // FIXME: or alternatively do authority key mapping
+      // if possible (X.509v1 certs can't work?)
+      throw new Error('Resolving multiple issuer matches not implemented yet.');
+    }*/
+
+    return rval;
+  };
+
+  /**
+   * Adds a trusted certificate to the store.
+   *
+   * @param cert the certificate to add as a trusted certificate (either a
+   *          pki.certificate object or a PEM-formatted certificate).
+   */
+  caStore.addCertificate = function(cert) {
+    // convert from pem if necessary
+    if(typeof cert === 'string') {
+      cert = forge.pki.certificateFromPem(cert);
+    }
+
+    // produce subject hash if it doesn't exist
+    if(!cert.subject.hash) {
+      var md = forge.md.sha1.create();
+      cert.subject.attributes =  pki.RDNAttributesAsArray(
+        _dnToAsn1(cert.subject), md);
+      cert.subject.hash = md.digest().toHex();
+    }
+
+    if(cert.subject.hash in caStore.certs) {
+      // subject hash already exists, append to array
+      var tmp = caStore.certs[cert.subject.hash];
+      if(!forge.util.isArray(tmp)) {
+        tmp = [tmp];
+      }
+      tmp.push(cert);
+    } else {
+      caStore.certs[cert.subject.hash] = cert;
+    }
+  };
+
+  /**
+   * Checks to see if the given certificate is in the store.
+   *
+   * @param cert the certificate to check.
+   *
+   * @return true if the certificate is in the store, false if not.
+   */
+  caStore.hasCertificate = function(cert) {
+    var match = getBySubject(cert.subject);
+    if(!match) {
+      return false;
+    }
+    if(!forge.util.isArray(match)) {
+      match = [match];
+    }
+    // compare DER-encoding of certificates
+    var der1 = asn1.toDer(pki.certificateToAsn1(cert)).getBytes();
+    for(var i = 0; i < match.length; ++i) {
+      var der2 = asn1.toDer(pki.certificateToAsn1(match[i])).getBytes();
+      if(der1 === der2) {
+        return true;
+      }
+    }
+    return false;
+  };
+
+  function getBySubject(subject) {
+    // produce subject hash if it doesn't exist
+    if(!subject.hash) {
+      var md = forge.md.sha1.create();
+      subject.attributes =  pki.RDNAttributesAsArray(_dnToAsn1(subject), md);
+      subject.hash = md.digest().toHex();
+    }
+    return caStore.certs[subject.hash] || null;
+  }
+
+  // auto-add passed in certs
+  if(certs) {
+    // parse PEM-formatted certificates as necessary
+    for(var i = 0; i < certs.length; ++i) {
+      var cert = certs[i];
+      caStore.addCertificate(cert);
+    }
+  }
+
+  return caStore;
+};
+
+/**
+ * Certificate verification errors, based on TLS.
+ */
+pki.certificateError = {
+  bad_certificate: 'forge.pki.BadCertificate',
+  unsupported_certificate: 'forge.pki.UnsupportedCertificate',
+  certificate_revoked: 'forge.pki.CertificateRevoked',
+  certificate_expired: 'forge.pki.CertificateExpired',
+  certificate_unknown: 'forge.pki.CertificateUnknown',
+  unknown_ca: 'forge.pki.UnknownCertificateAuthority'
+};
+
+/**
+ * Verifies a certificate chain against the given Certificate Authority store
+ * with an optional custom verify callback.
+ *
+ * @param caStore a certificate store to verify against.
+ * @param chain the certificate chain to verify, with the root or highest
+ *          authority at the end (an array of certificates).
+ * @param verify called for every certificate in the chain.
+ *
+ * The verify callback has the following signature:
+ *
+ * verified - Set to true if certificate was verified, otherwise the
+ *   pki.certificateError for why the certificate failed.
+ * depth - The current index in the chain, where 0 is the end point's cert.
+ * certs - The certificate chain, *NOTE* an empty chain indicates an anonymous
+ *   end point.
+ *
+ * The function returns true on success and on failure either the appropriate
+ * pki.certificateError or an object with 'error' set to the appropriate
+ * pki.certificateError and 'message' set to a custom error message.
+ *
+ * @return true if successful, error thrown if not.
+ */
+pki.verifyCertificateChain = function(caStore, chain, verify) {
+  /* From: RFC3280 - Internet X.509 Public Key Infrastructure Certificate
+    Section 6: Certification Path Validation
+    See inline parentheticals related to this particular implementation.
+
+    The primary goal of path validation is to verify the binding between
+    a subject distinguished name or a subject alternative name and subject
+    public key, as represented in the end entity certificate, based on the
+    public key of the trust anchor. This requires obtaining a sequence of
+    certificates that support that binding. That sequence should be provided
+    in the passed 'chain'. The trust anchor should be in the given CA
+    store. The 'end entity' certificate is the certificate provided by the
+    end point (typically a server) and is the first in the chain.
+
+    To meet this goal, the path validation process verifies, among other
+    things, that a prospective certification path (a sequence of n
+    certificates or a 'chain') satisfies the following conditions:
+
+    (a) for all x in {1, ..., n-1}, the subject of certificate x is
+          the issuer of certificate x+1;
+
+    (b) certificate 1 is issued by the trust anchor;
+
+    (c) certificate n is the certificate to be validated; and
+
+    (d) for all x in {1, ..., n}, the certificate was valid at the
+          time in question.
+
+    Note that here 'n' is index 0 in the chain and 1 is the last certificate
+    in the chain and it must be signed by a certificate in the connection's
+    CA store.
+
+    The path validation process also determines the set of certificate
+    policies that are valid for this path, based on the certificate policies
+    extension, policy mapping extension, policy constraints extension, and
+    inhibit any-policy extension.
+
+    Note: Policy mapping extension not supported (Not Required).
+
+    Note: If the certificate has an unsupported critical extension, then it
+    must be rejected.
+
+    Note: A certificate is self-issued if the DNs that appear in the subject
+    and issuer fields are identical and are not empty.
+
+    The path validation algorithm assumes the following seven inputs are
+    provided to the path processing logic. What this specific implementation
+    will use is provided parenthetically:
+
+    (a) a prospective certification path of length n (the 'chain')
+    (b) the current date/time: ('now').
+    (c) user-initial-policy-set: A set of certificate policy identifiers
+          naming the policies that are acceptable to the certificate user.
+          The user-initial-policy-set contains the special value any-policy
+          if the user is not concerned about certificate policy
+          (Not implemented. Any policy is accepted).
+    (d) trust anchor information, describing a CA that serves as a trust
+          anchor for the certification path. The trust anchor information
+          includes:
+
+      (1)  the trusted issuer name,
+      (2)  the trusted public key algorithm,
+      (3)  the trusted public key, and
+      (4)  optionally, the trusted public key parameters associated
+             with the public key.
+
+      (Trust anchors are provided via certificates in the CA store).
+
+      The trust anchor information may be provided to the path processing
+      procedure in the form of a self-signed certificate. The trusted anchor
+      information is trusted because it was delivered to the path processing
+      procedure by some trustworthy out-of-band procedure. If the trusted
+      public key algorithm requires parameters, then the parameters are
+      provided along with the trusted public key (No parameters used in this
+      implementation).
+
+    (e) initial-policy-mapping-inhibit, which indicates if policy mapping is
+          allowed in the certification path.
+          (Not implemented, no policy checking)
+
+    (f) initial-explicit-policy, which indicates if the path must be valid
+          for at least one of the certificate policies in the user-initial-
+          policy-set.
+          (Not implemented, no policy checking)
+
+    (g) initial-any-policy-inhibit, which indicates whether the
+          anyPolicy OID should be processed if it is included in a
+          certificate.
+          (Not implemented, so any policy is valid provided that it is
+          not marked as critical) */
+
+  /* Basic Path Processing:
+
+    For each certificate in the 'chain', the following is checked:
+
+    1. The certificate validity period includes the current time.
+    2. The certificate was signed by its parent (where the parent is either
+       the next in the chain or from the CA store). Allow processing to
+       continue to the next step if no parent is found but the certificate is
+       in the CA store.
+    3. TODO: The certificate has not been revoked.
+    4. The certificate issuer name matches the parent's subject name.
+    5. TODO: If the certificate is self-issued and not the final certificate
+       in the chain, skip this step, otherwise verify that the subject name
+       is within one of the permitted subtrees of X.500 distinguished names
+       and that each of the alternative names in the subjectAltName extension
+       (critical or non-critical) is within one of the permitted subtrees for
+       that name type.
+    6. TODO: If the certificate is self-issued and not the final certificate
+       in the chain, skip this step, otherwise verify that the subject name
+       is not within one of the excluded subtrees for X.500 distinguished
+       names and none of the subjectAltName extension names are excluded for
+       that name type.
+    7. The other steps in the algorithm for basic path processing involve
+       handling the policy extension which is not presently supported in this
+       implementation. Instead, if a critical policy extension is found, the
+       certificate is rejected as not supported.
+    8. If the certificate is not the first or if its the only certificate in
+       the chain (having no parent from the CA store or is self-signed) and it
+       has a critical key usage extension, verify that the keyCertSign bit is
+       set. If the key usage extension exists, verify that the basic
+       constraints extension exists. If the basic constraints extension exists,
+       verify that the cA flag is set. If pathLenConstraint is set, ensure that
+       the number of certificates that precede in the chain (come earlier
+       in the chain as implemented below), excluding the very first in the
+       chain (typically the end-entity one), isn't greater than the
+       pathLenConstraint. This constraint limits the number of intermediate
+       CAs that may appear below a CA before only end-entity certificates
+       may be issued. */
+
+  // copy cert chain references to another array to protect against changes
+  // in verify callback
+  chain = chain.slice(0);
+  var certs = chain.slice(0);
+
+  // get current date
+  var now = new Date();
+
+  // verify each cert in the chain using its parent, where the parent
+  // is either the next in the chain or from the CA store
+  var first = true;
+  var error = null;
+  var depth = 0;
+  do {
+    var cert = chain.shift();
+    var parent = null;
+    var selfSigned = false;
+
+    // 1. check valid time
+    if(now < cert.validity.notBefore || now > cert.validity.notAfter) {
+      error = {
+        message: 'Certificate is not valid yet or has expired.',
+        error: pki.certificateError.certificate_expired,
+        notBefore: cert.validity.notBefore,
+        notAfter: cert.validity.notAfter,
+        now: now
+      };
+    }
+
+    // 2. verify with parent from chain or CA store
+    if(error === null) {
+      parent = chain[0] || caStore.getIssuer(cert);
+      if(parent === null) {
+        // check for self-signed cert
+        if(cert.isIssuer(cert)) {
+          selfSigned = true;
+          parent = cert;
+        }
+      }
+
+      if(parent) {
+        // FIXME: current CA store implementation might have multiple
+        // certificates where the issuer can't be determined from the
+        // certificate (happens rarely with, eg: old certificates) so normalize
+        // by always putting parents into an array
+        // TODO: there's may be an extreme degenerate case currently uncovered
+        // where an old intermediate certificate seems to have a matching parent
+        // but none of the parents actually verify ... but the intermediate
+        // is in the CA and it should pass this check; needs investigation
+        var parents = parent;
+        if(!forge.util.isArray(parents)) {
+          parents = [parents];
+        }
+
+        // try to verify with each possible parent (typically only one)
+        var verified = false;
+        while(!verified && parents.length > 0) {
+          parent = parents.shift();
+          try {
+            verified = parent.verify(cert);
+          } catch(ex) {
+            // failure to verify, don't care why, try next one
+          }
+        }
+
+        if(!verified) {
+          error = {
+            message: 'Certificate signature is invalid.',
+            error: pki.certificateError.bad_certificate
+          };
+        }
+      }
+
+      if(error === null && (!parent || selfSigned) &&
+        !caStore.hasCertificate(cert)) {
+        // no parent issuer and certificate itself is not trusted
+        error = {
+          message: 'Certificate is not trusted.',
+          error: pki.certificateError.unknown_ca
+        };
+      }
+    }
+
+    // TODO: 3. check revoked
+
+    // 4. check for matching issuer/subject
+    if(error === null && parent && !cert.isIssuer(parent)) {
+      // parent is not issuer
+      error = {
+        message: 'Certificate issuer is invalid.',
+        error: pki.certificateError.bad_certificate
+      };
+    }
+
+    // 5. TODO: check names with permitted names tree
+
+    // 6. TODO: check names against excluded names tree
+
+    // 7. check for unsupported critical extensions
+    if(error === null) {
+      // supported extensions
+      var se = {
+        keyUsage: true,
+        basicConstraints: true
+      };
+      for(var i = 0; error === null && i < cert.extensions.length; ++i) {
+        var ext = cert.extensions[i];
+        if(ext.critical && !(ext.name in se)) {
+          error = {
+            message:
+              'Certificate has an unsupported critical extension.',
+            error: pki.certificateError.unsupported_certificate
+          };
+        }
+      }
+    }
+
+    // 8. check for CA if cert is not first or is the only certificate
+    // remaining in chain with no parent or is self-signed
+    if(error === null &&
+      (!first || (chain.length === 0 && (!parent || selfSigned)))) {
+      // first check keyUsage extension and then basic constraints
+      var bcExt = cert.getExtension('basicConstraints');
+      var keyUsageExt = cert.getExtension('keyUsage');
+      if(keyUsageExt !== null) {
+        // keyCertSign must be true and there must be a basic
+        // constraints extension
+        if(!keyUsageExt.keyCertSign || bcExt === null) {
+          // bad certificate
+          error = {
+            message:
+              'Certificate keyUsage or basicConstraints conflict ' +
+              'or indicate that the certificate is not a CA. ' +
+              'If the certificate is the only one in the chain or ' +
+              'isn\'t the first then the certificate must be a ' +
+              'valid CA.',
+            error: pki.certificateError.bad_certificate
+          };
+        }
+      }
+      // basic constraints cA flag must be set
+      if(error === null && bcExt !== null && !bcExt.cA) {
+        // bad certificate
+        error = {
+          message:
+            'Certificate basicConstraints indicates the certificate ' +
+            'is not a CA.',
+          error: pki.certificateError.bad_certificate
+        };
+      }
+      // if error is not null and keyUsage is available, then we know it
+      // has keyCertSign and there is a basic constraints extension too,
+      // which means we can check pathLenConstraint (if it exists)
+      if(error === null && keyUsageExt !== null &&
+        'pathLenConstraint' in bcExt) {
+        // pathLen is the maximum # of intermediate CA certs that can be
+        // found between the current certificate and the end-entity (depth 0)
+        // certificate; this number does not include the end-entity (depth 0,
+        // last in the chain) even if it happens to be a CA certificate itself
+        var pathLen = depth - 1;
+        if(pathLen > bcExt.pathLenConstraint) {
+          // pathLenConstraint violated, bad certificate
+          error = {
+            message:
+              'Certificate basicConstraints pathLenConstraint violated.',
+            error: pki.certificateError.bad_certificate
+          };
+        }
+      }
+    }
+
+    // call application callback
+    var vfd = (error === null) ? true : error.error;
+    var ret = verify ? verify(vfd, depth, certs) : vfd;
+    if(ret === true) {
+      // clear any set error
+      error = null;
+    } else {
+      // if passed basic tests, set default message and alert
+      if(vfd === true) {
+        error = {
+          message: 'The application rejected the certificate.',
+          error: pki.certificateError.bad_certificate
+        };
+      }
+
+      // check for custom error info
+      if(ret || ret === 0) {
+        // set custom message and error
+        if(typeof ret === 'object' && !forge.util.isArray(ret)) {
+          if(ret.message) {
+             error.message = ret.message;
+          }
+          if(ret.error) {
+            error.error = ret.error;
+          }
+        } else if(typeof ret === 'string') {
+          // set custom error
+          error.error = ret;
+        }
+      }
+
+      // throw error
+      throw error;
+    }
+
+    // no longer first cert in chain
+    first = false;
+    ++depth;
+  } while(chain.length > 0);
+
+  return true;
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'x509';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge.pki;
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define([
+  'require',
+  'module',
+  './aes',
+  './asn1',
+  './des',
+  './md',
+  './mgf',
+  './oids',
+  './pem',
+  './pss',
+  './rsa',
+  './util'
+], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/xhr.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/xhr.js
new file mode 100644
index 0000000..96082ad
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/js/xhr.js
@@ -0,0 +1,739 @@
+/**
+ * XmlHttpRequest implementation that uses TLS and flash SocketPool.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2013 Digital Bazaar, Inc.
+ */
+(function($) {
+
+// logging category
+var cat = 'forge.xhr';
+
+/*
+XMLHttpRequest interface definition from:
+http://www.w3.org/TR/XMLHttpRequest
+
+interface XMLHttpRequest {
+  // event handler
+  attribute EventListener onreadystatechange;
+
+  // state
+  const unsigned short UNSENT = 0;
+  const unsigned short OPENED = 1;
+  const unsigned short HEADERS_RECEIVED = 2;
+  const unsigned short LOADING = 3;
+  const unsigned short DONE = 4;
+  readonly attribute unsigned short readyState;
+
+  // request
+  void open(in DOMString method, in DOMString url);
+  void open(in DOMString method, in DOMString url, in boolean async);
+  void open(in DOMString method, in DOMString url,
+            in boolean async, in DOMString user);
+  void open(in DOMString method, in DOMString url,
+            in boolean async, in DOMString user, in DOMString password);
+  void setRequestHeader(in DOMString header, in DOMString value);
+  void send();
+  void send(in DOMString data);
+  void send(in Document data);
+  void abort();
+
+  // response
+  DOMString getAllResponseHeaders();
+  DOMString getResponseHeader(in DOMString header);
+  readonly attribute DOMString responseText;
+  readonly attribute Document responseXML;
+  readonly attribute unsigned short status;
+  readonly attribute DOMString statusText;
+};
+*/
+
+// readyStates
+var UNSENT = 0;
+var OPENED = 1;
+var HEADERS_RECEIVED = 2;
+var LOADING = 3;
+var DONE = 4;
+
+// exceptions
+var INVALID_STATE_ERR = 11;
+var SYNTAX_ERR = 12;
+var SECURITY_ERR = 18;
+var NETWORK_ERR = 19;
+var ABORT_ERR = 20;
+
+// private flash socket pool vars
+var _sp = null;
+var _policyPort = 0;
+var _policyUrl = null;
+
+// default client (used if no special URL provided when creating an XHR)
+var _client = null;
+
+// all clients including the default, key'd by full base url
+// (multiple cross-domain http clients are permitted so there may be more
+// than one client in this map)
+// TODO: provide optional clean up API for non-default clients
+var _clients = {};
+
+// the default maximum number of concurrents connections per client
+var _maxConnections = 10;
+
+// local aliases
+if(typeof forge === 'undefined') {
+  forge = {};
+}
+var net = forge.net;
+var http = forge.http;
+
+// define the xhr interface
+var xhrApi = {};
+
+/**
+ * Initializes flash XHR support.
+ *
+ * @param options:
+ *   url: the default base URL to connect to if xhr URLs are relative,
+ *     ie: https://myserver.com.
+ *   flashId: the dom ID of the flash SocketPool.
+ *   policyPort: the port that provides the server's flash policy, 0 to use
+ *     the flash default.
+ *   policyUrl: the policy file URL to use instead of a policy port.
+ *   msie: true if browser is internet explorer, false if not.
+ *   connections: the maximum number of concurrent connections.
+ *   caCerts: a list of PEM-formatted certificates to trust.
+ *   cipherSuites: an optional array of cipher suites to use,
+ *     see forge.tls.CipherSuites.
+ *   verify: optional TLS certificate verify callback to use (see forge.tls
+ *     for details).
+ *   getCertificate: an optional callback used to get a client-side
+ *     certificate (see forge.tls for details).
+ *   getPrivateKey: an optional callback used to get a client-side private
+ *     key (see forge.tls for details).
+ *   getSignature: an optional callback used to get a client-side signature
+ *     (see forge.tls for details).
+ *   persistCookies: true to use persistent cookies via flash local storage,
+ *     false to only keep cookies in javascript.
+ *   primeTlsSockets: true to immediately connect TLS sockets on their
+ *     creation so that they will cache TLS sessions for reuse.
+ */
+xhrApi.init = function(options) {
+  forge.log.debug(cat, 'initializing', options);
+
+  // update default policy port and max connections
+  _policyPort = options.policyPort || _policyPort;
+  _policyUrl = options.policyUrl || _policyUrl;
+  _maxConnections = options.connections || _maxConnections;
+
+  // create the flash socket pool
+  _sp = net.createSocketPool({
+    flashId: options.flashId,
+    policyPort: _policyPort,
+    policyUrl: _policyUrl,
+    msie: options.msie || false
+  });
+
+  // create default http client
+  _client = http.createClient({
+    url: options.url || (
+      window.location.protocol + '//' + window.location.host),
+    socketPool: _sp,
+    policyPort: _policyPort,
+    policyUrl: _policyUrl,
+    connections: options.connections || _maxConnections,
+    caCerts: options.caCerts,
+    cipherSuites: options.cipherSuites,
+    persistCookies: options.persistCookies || true,
+    primeTlsSockets: options.primeTlsSockets || false,
+    verify: options.verify,
+    getCertificate: options.getCertificate,
+    getPrivateKey: options.getPrivateKey,
+    getSignature: options.getSignature
+  });
+  _clients[_client.url.full] = _client;
+
+  forge.log.debug(cat, 'ready');
+};
+
+/**
+ * Called to clean up the clients and socket pool.
+ */
+xhrApi.cleanup = function() {
+  // destroy all clients
+  for(var key in _clients) {
+    _clients[key].destroy();
+  }
+  _clients = {};
+  _client = null;
+
+  // destroy socket pool
+  _sp.destroy();
+  _sp = null;
+};
+
+/**
+ * Sets a cookie.
+ *
+ * @param cookie the cookie with parameters:
+ *   name: the name of the cookie.
+ *   value: the value of the cookie.
+ *   comment: an optional comment string.
+ *   maxAge: the age of the cookie in seconds relative to created time.
+ *   secure: true if the cookie must be sent over a secure protocol.
+ *   httpOnly: true to restrict access to the cookie from javascript
+ *     (inaffective since the cookies are stored in javascript).
+ *   path: the path for the cookie.
+ *   domain: optional domain the cookie belongs to (must start with dot).
+ *   version: optional version of the cookie.
+ *   created: creation time, in UTC seconds, of the cookie.
+ */
+xhrApi.setCookie = function(cookie) {
+  // default cookie expiration to never
+  cookie.maxAge = cookie.maxAge || -1;
+
+  // if the cookie's domain is set, use the appropriate client
+  if(cookie.domain) {
+    // add the cookies to the applicable domains
+    for(var key in _clients) {
+      var client = _clients[key];
+      if(http.withinCookieDomain(client.url, cookie) &&
+        client.secure === cookie.secure) {
+        client.setCookie(cookie);
+      }
+    }
+  } else {
+    // use the default domain
+    // FIXME: should a null domain cookie be added to all clients? should
+    // this be an option?
+    _client.setCookie(cookie);
+  }
+};
+
+/**
+ * Gets a cookie.
+ *
+ * @param name the name of the cookie.
+ * @param path an optional path for the cookie (if there are multiple cookies
+ *          with the same name but different paths).
+ * @param domain an optional domain for the cookie (if not using the default
+ *          domain).
+ *
+ * @return the cookie, cookies (if multiple matches), or null if not found.
+ */
+xhrApi.getCookie = function(name, path, domain) {
+  var rval = null;
+
+  if(domain) {
+    // get the cookies from the applicable domains
+    for(var key in _clients) {
+      var client = _clients[key];
+      if(http.withinCookieDomain(client.url, domain)) {
+        var cookie = client.getCookie(name, path);
+        if(cookie !== null) {
+          if(rval === null) {
+            rval = cookie;
+          } else if(!forge.util.isArray(rval)) {
+            rval = [rval, cookie];
+          } else {
+            rval.push(cookie);
+          }
+        }
+      }
+    }
+  } else {
+    // get cookie from default domain
+    rval = _client.getCookie(name, path);
+  }
+
+  return rval;
+};
+
+/**
+ * Removes a cookie.
+ *
+ * @param name the name of the cookie.
+ * @param path an optional path for the cookie (if there are multiple cookies
+ *          with the same name but different paths).
+ * @param domain an optional domain for the cookie (if not using the default
+ *          domain).
+ *
+ * @return true if a cookie was removed, false if not.
+ */
+xhrApi.removeCookie = function(name, path, domain) {
+  var rval = false;
+
+  if(domain) {
+    // remove the cookies from the applicable domains
+    for(var key in _clients) {
+      var client = _clients[key];
+      if(http.withinCookieDomain(client.url, domain)) {
+        if(client.removeCookie(name, path)) {
+           rval = true;
+        }
+      }
+    }
+  } else {
+    // remove cookie from default domain
+    rval = _client.removeCookie(name, path);
+  }
+
+  return rval;
+};
+
+/**
+ * Creates a new XmlHttpRequest. By default the base URL, flash policy port,
+ * etc, will be used. However, an XHR can be created to point at another
+ * cross-domain URL.
+ *
+ * @param options:
+ *   logWarningOnError: If true and an HTTP error status code is received then
+ *     log a warning, otherwise log a verbose message.
+ *   verbose: If true be very verbose in the output including the response
+ *     event and response body, otherwise only include status, timing, and
+ *     data size.
+ *   logError: a multi-var log function for warnings that takes the log
+ *     category as the first var.
+ *   logWarning: a multi-var log function for warnings that takes the log
+ *     category as the first var.
+ *   logDebug: a multi-var log function for warnings that takes the log
+ *     category as the first var.
+ *   logVerbose: a multi-var log function for warnings that takes the log
+ *     category as the first var.
+ *   url: the default base URL to connect to if xhr URLs are relative,
+ *     eg: https://myserver.com, and note that the following options will be
+ *     ignored if the URL is absent or the same as the default base URL.
+ *   policyPort: the port that provides the server's flash policy, 0 to use
+ *     the flash default.
+ *   policyUrl: the policy file URL to use instead of a policy port.
+ *   connections: the maximum number of concurrent connections.
+ *   caCerts: a list of PEM-formatted certificates to trust.
+ *   cipherSuites: an optional array of cipher suites to use, see
+ *     forge.tls.CipherSuites.
+ *   verify: optional TLS certificate verify callback to use (see forge.tls
+ *     for details).
+ *   getCertificate: an optional callback used to get a client-side
+ *     certificate.
+ *   getPrivateKey: an optional callback used to get a client-side private key.
+ *   getSignature: an optional callback used to get a client-side signature.
+ *   persistCookies: true to use persistent cookies via flash local storage,
+ *     false to only keep cookies in javascript.
+ *   primeTlsSockets: true to immediately connect TLS sockets on their
+ *     creation so that they will cache TLS sessions for reuse.
+ *
+ * @return the XmlHttpRequest.
+ */
+xhrApi.create = function(options) {
+  // set option defaults
+  options = $.extend({
+    logWarningOnError: true,
+    verbose: false,
+    logError: function(){},
+    logWarning: function(){},
+    logDebug: function(){},
+    logVerbose: function(){},
+    url: null
+  }, options || {});
+
+  // private xhr state
+  var _state = {
+    // the http client to use
+    client: null,
+    // request storage
+    request: null,
+    // response storage
+    response: null,
+    // asynchronous, true if doing asynchronous communication
+    asynchronous: true,
+    // sendFlag, true if send has been called
+    sendFlag: false,
+    // errorFlag, true if a network error occurred
+    errorFlag: false
+  };
+
+  // private log functions
+  var _log = {
+    error: options.logError || forge.log.error,
+    warning: options.logWarning || forge.log.warning,
+    debug: options.logDebug || forge.log.debug,
+    verbose: options.logVerbose || forge.log.verbose
+  };
+
+  // create public xhr interface
+  var xhr = {
+    // an EventListener
+    onreadystatechange: null,
+    // readonly, the current readyState
+    readyState: UNSENT,
+    // a string with the response entity-body
+    responseText: '',
+    // a Document for response entity-bodies that are XML
+    responseXML: null,
+    // readonly, returns the HTTP status code (i.e. 404)
+    status: 0,
+    // readonly, returns the HTTP status message (i.e. 'Not Found')
+    statusText: ''
+  };
+
+  // determine which http client to use
+  if(options.url === null) {
+    // use default
+    _state.client = _client;
+  } else {
+    var url = http.parseUrl(options.url);
+    if(!url) {
+      var error = new Error('Invalid url.');
+      error.details = {
+        url: options.url
+      };
+    }
+
+    // find client
+    if(url.full in _clients) {
+      // client found
+      _state.client = _clients[url.full];
+    } else {
+      // create client
+      _state.client = http.createClient({
+        url: options.url,
+        socketPool: _sp,
+        policyPort: options.policyPort || _policyPort,
+        policyUrl: options.policyUrl || _policyUrl,
+        connections: options.connections || _maxConnections,
+        caCerts: options.caCerts,
+        cipherSuites: options.cipherSuites,
+        persistCookies: options.persistCookies || true,
+        primeTlsSockets: options.primeTlsSockets || false,
+        verify: options.verify,
+        getCertificate: options.getCertificate,
+        getPrivateKey: options.getPrivateKey,
+        getSignature: options.getSignature
+      });
+      _clients[url.full] = _state.client;
+    }
+  }
+
+  /**
+   * Opens the request. This method will create the HTTP request to send.
+   *
+   * @param method the HTTP method (i.e. 'GET').
+   * @param url the relative url (the HTTP request path).
+   * @param async always true, ignored.
+   * @param user always null, ignored.
+   * @param password always null, ignored.
+   */
+  xhr.open = function(method, url, async, user, password) {
+    // 1. validate Document if one is associated
+    // TODO: not implemented (not used yet)
+
+    // 2. validate method token
+    // 3. change method to uppercase if it matches a known
+    // method (here we just require it to be uppercase, and
+    // we do not allow the standard methods)
+    // 4. disallow CONNECT, TRACE, or TRACK with a security error
+    switch(method) {
+    case 'DELETE':
+    case 'GET':
+    case 'HEAD':
+    case 'OPTIONS':
+    case 'PATCH':
+    case 'POST':
+    case 'PUT':
+      // valid method
+      break;
+    case 'CONNECT':
+    case 'TRACE':
+    case 'TRACK':
+      throw new Error('CONNECT, TRACE and TRACK methods are disallowed');
+    default:
+      throw new Error('Invalid method: ' + method);;
+    }
+
+    // TODO: other validation steps in algorithm are not implemented
+
+    // 19. set send flag to false
+    // set response body to null
+    // empty list of request headers
+    // set request method to given method
+    // set request URL
+    // set username, password
+    // set asychronous flag
+    _state.sendFlag = false;
+    xhr.responseText = '';
+    xhr.responseXML = null;
+
+    // custom: reset status and statusText
+    xhr.status = 0;
+    xhr.statusText = '';
+
+    // create the HTTP request
+    _state.request = http.createRequest({
+      method: method,
+      path: url
+    });
+
+    // 20. set state to OPENED
+    xhr.readyState = OPENED;
+
+    // 21. dispatch onreadystatechange
+    if(xhr.onreadystatechange) {
+       xhr.onreadystatechange();
+    }
+  };
+
+  /**
+   * Adds an HTTP header field to the request.
+   *
+   * @param header the name of the header field.
+   * @param value the value of the header field.
+   */
+  xhr.setRequestHeader = function(header, value) {
+    // 1. if state is not OPENED or send flag is true, raise exception
+    if(xhr.readyState != OPENED || _state.sendFlag) {
+      throw new Error('XHR not open or sending');
+    }
+
+    // TODO: other validation steps in spec aren't implemented
+
+    // set header
+    _state.request.setField(header, value);
+  };
+
+  /**
+   * Sends the request and any associated data.
+   *
+   * @param data a string or Document object to send, null to send no data.
+   */
+  xhr.send = function(data) {
+    // 1. if state is not OPENED or 2. send flag is true, raise
+    // an invalid state exception
+    if(xhr.readyState != OPENED || _state.sendFlag) {
+      throw new Error('XHR not open or sending');
+    }
+
+    // 3. ignore data if method is GET or HEAD
+    if(data &&
+      _state.request.method !== 'GET' &&
+      _state.request.method !== 'HEAD') {
+      // handle non-IE case
+      if(typeof(XMLSerializer) !== 'undefined') {
+        if(data instanceof Document) {
+          var xs = new XMLSerializer();
+          _state.request.body = xs.serializeToString(data);
+        } else {
+          _state.request.body = data;
+        }
+      } else {
+        // poorly implemented IE case
+        if(typeof(data.xml) !== 'undefined') {
+          _state.request.body = data.xml;
+        } else {
+          _state.request.body = data;
+        }
+      }
+    }
+
+    // 4. release storage mutex (not used)
+
+    // 5. set error flag to false
+    _state.errorFlag = false;
+
+    // 6. if asynchronous is true (must be in this implementation)
+
+    // 6.1 set send flag to true
+    _state.sendFlag = true;
+
+    // 6.2 dispatch onreadystatechange
+    if(xhr.onreadystatechange) {
+      xhr.onreadystatechange();
+    }
+
+    // create send options
+    var options = {};
+    options.request = _state.request;
+    options.headerReady = function(e) {
+      // make cookies available for ease of use/iteration
+      xhr.cookies = _state.client.cookies;
+
+      // TODO: update document.cookie with any cookies where the
+      // script's domain matches
+
+      // headers received
+      xhr.readyState = HEADERS_RECEIVED;
+      xhr.status = e.response.code;
+      xhr.statusText = e.response.message;
+      _state.response = e.response;
+      if(xhr.onreadystatechange) {
+        xhr.onreadystatechange();
+      }
+      if(!_state.response.aborted) {
+        // now loading body
+        xhr.readyState = LOADING;
+        if(xhr.onreadystatechange) {
+           xhr.onreadystatechange();
+        }
+      }
+    };
+    options.bodyReady = function(e) {
+      xhr.readyState = DONE;
+      var ct = e.response.getField('Content-Type');
+      // Note: this null/undefined check is done outside because IE
+      // dies otherwise on a "'null' is null" error
+      if(ct) {
+        if(ct.indexOf('text/xml') === 0 ||
+          ct.indexOf('application/xml') === 0 ||
+          ct.indexOf('+xml') !== -1) {
+          try {
+            var doc = new ActiveXObject('MicrosoftXMLDOM');
+            doc.async = false;
+            doc.loadXML(e.response.body);
+            xhr.responseXML = doc;
+          } catch(ex) {
+            var parser = new DOMParser();
+            xhr.responseXML = parser.parseFromString(ex.body, 'text/xml');
+          }
+        }
+      }
+
+      var length = 0;
+      if(e.response.body !== null) {
+        xhr.responseText = e.response.body;
+        length = e.response.body.length;
+      }
+      // build logging output
+      var req = _state.request;
+      var output =
+        req.method + ' ' + req.path + ' ' +
+        xhr.status + ' ' + xhr.statusText + ' ' +
+        length + 'B ' +
+        (e.request.connectTime + e.request.time + e.response.time) +
+        'ms';
+      var lFunc;
+      if(options.verbose) {
+        lFunc = (xhr.status >= 400 && options.logWarningOnError) ?
+          _log.warning : _log.verbose;
+        lFunc(cat, output,
+          e, e.response.body ? '\n' + e.response.body : '\nNo content');
+      } else {
+        lFunc = (xhr.status >= 400 && options.logWarningOnError) ?
+          _log.warning : _log.debug;
+        lFunc(cat, output);
+      }
+      if(xhr.onreadystatechange) {
+        xhr.onreadystatechange();
+      }
+    };
+    options.error = function(e) {
+      var req = _state.request;
+      _log.error(cat, req.method + ' ' + req.path, e);
+
+      // 1. set response body to null
+      xhr.responseText = '';
+      xhr.responseXML = null;
+
+      // 2. set error flag to true (and reset status)
+      _state.errorFlag = true;
+      xhr.status = 0;
+      xhr.statusText = '';
+
+      // 3. set state to done
+      xhr.readyState = DONE;
+
+      // 4. asyc flag is always true, so dispatch onreadystatechange
+      if(xhr.onreadystatechange) {
+        xhr.onreadystatechange();
+      }
+    };
+
+    // 7. send request
+    _state.client.send(options);
+  };
+
+  /**
+   * Aborts the request.
+   */
+  xhr.abort = function() {
+    // 1. abort send
+    // 2. stop network activity
+    _state.request.abort();
+
+    // 3. set response to null
+    xhr.responseText = '';
+    xhr.responseXML = null;
+
+    // 4. set error flag to true (and reset status)
+    _state.errorFlag = true;
+    xhr.status = 0;
+    xhr.statusText = '';
+
+    // 5. clear user headers
+    _state.request = null;
+    _state.response = null;
+
+    // 6. if state is DONE or UNSENT, or if OPENED and send flag is false
+    if(xhr.readyState === DONE || xhr.readyState === UNSENT ||
+     (xhr.readyState === OPENED && !_state.sendFlag)) {
+      // 7. set ready state to unsent
+      xhr.readyState = UNSENT;
+    } else {
+      // 6.1 set state to DONE
+      xhr.readyState = DONE;
+
+      // 6.2 set send flag to false
+      _state.sendFlag = false;
+
+      // 6.3 dispatch onreadystatechange
+      if(xhr.onreadystatechange) {
+        xhr.onreadystatechange();
+      }
+
+      // 7. set state to UNSENT
+      xhr.readyState = UNSENT;
+    }
+  };
+
+  /**
+   * Gets all response headers as a string.
+   *
+   * @return the HTTP-encoded response header fields.
+   */
+  xhr.getAllResponseHeaders = function() {
+    var rval = '';
+    if(_state.response !== null) {
+      var fields = _state.response.fields;
+      $.each(fields, function(name, array) {
+        $.each(array, function(i, value) {
+          rval += name + ': ' + value + '\r\n';
+        });
+      });
+    }
+    return rval;
+  };
+
+  /**
+   * Gets a single header field value or, if there are multiple
+   * fields with the same name, a comma-separated list of header
+   * values.
+   *
+   * @return the header field value(s) or null.
+   */
+  xhr.getResponseHeader = function(header) {
+    var rval = null;
+    if(_state.response !== null) {
+      if(header in _state.response.fields) {
+        rval = _state.response.fields[header];
+        if(forge.util.isArray(rval)) {
+          rval = rval.join();
+        }
+      }
+    }
+    return rval;
+  };
+
+  return xhr;
+};
+
+// expose public api
+forge.xhr = xhrApi;
+
+})(jQuery);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/minify.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/minify.js
new file mode 100644
index 0000000..36430a2
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/minify.js
@@ -0,0 +1,10 @@
+({
+  name: 'node_modules/almond/almond',
+  include: ['js/forge'],
+  out: 'js/forge.min.js',
+  wrap: {
+    startFile: 'start.frag',
+    endFile: 'end.frag'
+  },
+  preserveLicenseComments: false
+})
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/mod_fsp/README b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/mod_fsp/README
new file mode 100644
index 0000000..0331e01
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/mod_fsp/README
@@ -0,0 +1,135 @@
+=======
+mod_fsp
+=======
+
+mod_fsp is an Apache2 module for providing a Flash Socket Policy on the
+same port that HTTP is served. The cross-domain policy that is served is
+specified via a configuration option 'FSPPolicyFile'.
+
+If a flash application sends a policy file request to an Apache server
+that has enabled and configured the mod_fsp module over its HTTP port,
+then the configured cross-domain policy will be returned as the response.
+
+========
+Building
+========
+
+To build the mod_fsp source code you can use Apache2's module
+build and installation tool: 'apxs2' which is, at the time of
+this writing, available on debian in the package:
+
+apache2-threaded-dev
+
+To compile mod_fsp you would run the following command:
+
+apxs2 -c mod_fsp.c
+
+============
+Installation
+============
+
+To install mod_fsp you the following command as root:
+
+apxs2 -c -i -a mod_fsp.c
+
+You must then restart your apache2 process, typically like so:
+
+/etc/init.d/apache2 restart
+
+===================
+Manual Installation
+===================
+
+To manually enable mod_dsp on your Apache2 server, you must copy the
+module file to the appropriate directory and create a load file.
+
+The module file:
+
+fsp.so (The library extension may vary if you are not using linux).
+
+Must be copied to Apache's module installation directory which is
+typically located (on a debian system):
+
+/usr/lib/apache2/modules
+
+The load file:
+
+fsp.load
+
+Must be created in Apache2's 'mods-available' directory, typically
+located (on a debian system):
+
+/etc/apache2/mods-available
+
+The load file should contain:
+
+LoadModule fsp_module         /usr/lib/apache2/modules/mod_fsp.so
+
+If your Apache module installation directory is different from
+the one listed above, you will need to set the correct one in the
+fsp.load file.
+
+To actually enable the module you must create a symbolic link in
+Apache's 'mods-enabled' directory, typically located (on debian):
+
+/etc/apache2/mods-enabled
+
+By typing (from that directory):
+
+ln -s ../mods-available/fsp.load fsp.load
+
+=============
+Configuration
+=============
+
+Once mod_fsp is installed, it must be configured. There is currently
+only one configuration option for mod_fsp: 'FSPPolicyFile'. This
+configuration option will set the file that mod_fsp will look in
+on apache startup for the cross-domain policy to serve. This option
+can be provided on a per-port basis. Each port can use a different
+one, but VirtualServers on a single port will use the same one. This
+is a limitation of the design by Adobe.
+
+Note: The cross-domain policy may fail to be served if the configuration
+option isn't added in the first VirtualHost entry (for a given port) read
+by Apache.
+
+An example of this configuration in use:
+
+<VirtualHost *:80>
+   ServerName example.com
+   DocumentRoot /var/www/example.com
+   ErrorLog /var/log/apache2/example.com-error.log
+   CustomLog /var/log/apache2/example.com-access.log vhost_combined
+
+   # mod_fsp config option
+   FSPPolicyFile /etc/apache2/crossdomain/crossdomain.xml
+
+   <Directory /var/www/example.com>
+      Options Indexes FollowSymLinks MultiViews
+      AllowOverride All
+      Order allow,deny
+      allow from all
+   </Directory>
+
+</VirtualHost>
+
+And example of the most permissive cross-domain policy file for flash:
+
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE cross-domain-policy SYSTEM
+"http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">
+<cross-domain-policy>
+<site-control permitted-cross-domain-policies="all"/>
+<allow-access-from domain="*" to-ports="*"/>
+<allow-http-request-headers-from domain="*" headers="*"/>
+</cross-domain-policy>
+
+==================
+Note about SSL/TLS
+==================
+
+Flash currently has no built-in SSL/TLS support so there is no
+reason to specify an 'FSPPolicyFile' option for SSL servers. The
+Flash player cannot directly communicate with them when doing
+internal look ups of policy files.
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/mod_fsp/mod_fsp.c b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/mod_fsp/mod_fsp.c
new file mode 100644
index 0000000..8beb824
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/mod_fsp/mod_fsp.c
@@ -0,0 +1,415 @@
+/**
+ * Flash Socket Policy Apache Module.
+ *
+ * This module provides a flash socket policy file on the same port that
+ * serves HTTP on Apache. This can help simplify setting up a server that
+ * supports cross-domain communication with flash.
+ *
+ * Quick note about Apache memory handling: Data is allocated from pools and
+ * is not manually returned to those pools. The pools are typically considered
+ * short-lived and will be cleaned up automatically by Apache.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010 Digital Bazaar, Inc. All rights reserved.
+ */
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+
+#include "ap_compat.h"
+
+#include <string.h>
+
+// length of a policy file request
+#define PFR_LENGTH 23
+
+// declare main module
+module AP_MODULE_DECLARE_DATA fsp_module;
+
+// configuration for the module
+typedef struct fsp_config
+{
+   // the cross-domain policy to serve
+   char* policy;
+   apr_size_t policy_length;
+} fsp_config;
+
+// filter state for keeping track of detected policy file requests
+typedef struct filter_state
+{
+   fsp_config* cfg;
+   int checked;
+   int found;
+} filter_state;
+
+// for registering hooks, filters, etc.
+static void fsp_register_hooks(apr_pool_t *p);
+static int fsp_pre_connection(conn_rec *c, void *csd);
+
+// filter handler declarations
+static apr_status_t fsp_input_filter(
+   ap_filter_t* f, apr_bucket_brigade* bb,
+	ap_input_mode_t mode, apr_read_type_e block, apr_off_t nbytes);
+static int fsp_output_filter(ap_filter_t* f, apr_bucket_brigade* bb);
+
+/**
+ * Registers the hooks for this module.
+ *
+ * @param p the pool to allocate from, if necessary.
+ */
+static void fsp_register_hooks(apr_pool_t* p)
+{
+   // registers the pre-connection hook to handle adding filters
+   ap_hook_pre_connection(
+      fsp_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
+
+   // will parse a policy file request, to be added in pre_connection
+   ap_register_input_filter(
+      "fsp_request", fsp_input_filter,
+      NULL, AP_FTYPE_CONNECTION);
+
+   // will emit a cross-domain policy response to be added in pre_connection
+   ap_register_output_filter(
+      "fsp_response", fsp_output_filter,
+      NULL, AP_FTYPE_CONNECTION);
+}
+
+/**
+ * A hook that is called before a connection is handled. This function will
+ * get the module configuration and add the flash socket policy filters if
+ * a cross-domain policy has been specified in the configuration.
+ *
+ * @param c the connection.
+ * @param csd the connection socket descriptor.
+ *
+ * @return OK on success.
+ */
+static int fsp_pre_connection(conn_rec* c, void* csd)
+{
+   // only install filters if a policy was specified in the module config
+   fsp_config* cfg = ap_get_module_config(
+      c->base_server->module_config, &fsp_module);
+   if(cfg->policy != NULL)
+   {
+      // allocate filter state
+      filter_state* state = apr_palloc(c->pool, sizeof(filter_state));
+      if(state != NULL)
+      {
+         // initialize state
+         state->cfg = cfg;
+         state->checked = state->found = 0;
+
+         // add filters
+         ap_add_input_filter("fsp_request", state, NULL, c);
+         ap_add_output_filter("fsp_response", state, NULL, c);
+      }
+   }
+
+   return OK;
+}
+
+/**
+ * Searches the input request for a flash socket policy request. This request,
+ * unfortunately, does not follow the HTTP protocol and cannot be handled
+ * via a special HTTP handler. Instead, it is a short xml string followed by
+ * a null character:
+ *
+ * '<policy-file-request/>\0'
+ *
+ * A peek into the incoming data checks the first character of the stream to
+ * see if it is '<' (as opposed to typically something else for HTTP). If it
+ * is not, then this function returns and HTTP input is read normally. If it
+ * is, then the remaining bytes in the policy-file-request are read and
+ * checked. If a match is found, then the filter state will be updated to
+ * inform the output filter to send a cross-domain policy as a response. If
+ * no match is found, HTTP traffic will proceed as usual.
+ *
+ * @param f the input filter.
+ * @param state the filter state.
+ *
+ * @return APR_SUCCESS on success, some other status on failure.
+ */
+static apr_status_t find_policy_file_request(
+   ap_filter_t* f, filter_state* state)
+{
+   apr_status_t rval = APR_SUCCESS;
+
+   // create a temp buffer for speculative reads
+   apr_bucket_brigade* tmp = apr_brigade_create(f->c->pool, f->c->bucket_alloc);
+
+   // FIXME: not sure how blocking mode works ... can it return fewer than
+   // the number of specified bytes?
+
+   // peek at the first PFR_LENGTH bytes
+   rval = ap_get_brigade(
+      f->next, tmp, AP_MODE_SPECULATIVE, APR_BLOCK_READ, PFR_LENGTH);
+   if(rval == APR_SUCCESS)
+   {
+      // quickly check the first bucket for the beginning of a pfr
+      const char* data;
+      apr_size_t length;
+      apr_bucket* b = APR_BRIGADE_FIRST(tmp);
+      rval = apr_bucket_read(b, &data, &length, APR_BLOCK_READ);
+      if(rval == APR_SUCCESS && length > 0 && data[0] == '<')
+      {
+         // possible policy file request, fill local buffer
+         char pfr[PFR_LENGTH];
+         char* ptr = pfr;
+         memcpy(ptr, data, length);
+         ptr += length;
+         memset(ptr, '\0', PFR_LENGTH - length);
+         b = APR_BUCKET_NEXT(b);
+         while(rval == APR_SUCCESS && b != APR_BRIGADE_SENTINEL(tmp))
+         {
+            rval = apr_bucket_read(b, &data, &length, APR_BLOCK_READ);
+            if(rval == APR_SUCCESS)
+            {
+               memcpy(ptr, data, length);
+               ptr += length;
+               b = APR_BUCKET_NEXT(b);
+            }
+         }
+
+         if(rval == APR_SUCCESS)
+         {
+            // see if pfr is a policy file request: '<policy-file-request/>\0'
+            if((ptr - pfr == PFR_LENGTH) && (pfr[PFR_LENGTH - 1] == '\0') &&
+               (strncmp(pfr, "<policy-file-request/>", PFR_LENGTH -1) == 0))
+            {
+               // pfr found
+               state->found = 1;
+            }
+         }
+      }
+   }
+
+   return rval;
+}
+
+/**
+ * Handles incoming data. If an attempt has not yet been made to look for
+ * a policy request (it is the beginning of the connection), then one is
+ * made. Otherwise this filter does nothing.
+ *
+ * If an attempt is made to find a policy request and one is not found, then
+ * reads proceed as normal. If one is found, then the filter state is modified
+ * to inform the output filter to send a policy request and the return value
+ * of this filter is EOF indicating that the connection should close after
+ * sending the cross-domain policy.
+ *
+ * @param f the input filter.
+ * @param bb the brigate to fill with input from the next filters in the chain
+ *           and then process (look for a policy file request).
+ * @param mode the type of read requested (ie: AP_MODE_GETLINE means read until
+ *           a CRLF is found, AP_MODE_GETBYTES means 'nbytes' of data, etc).
+ * @param block APR_BLOCK_READ or APR_NONBLOCK_READ, indicates the type of
+ *           blocking to do when trying to read.
+ * @param nbytes used if the read mode is appropriate to specify the number of
+ *           bytes to read (set to 0 for AP_MODE_GETLINE).
+ *
+ * @return the status of the input (ie: APR_SUCCESS for read success, APR_EOF
+ *         for end of stream, APR_EAGAIN to read again when non-blocking).
+ */
+static apr_status_t fsp_input_filter(
+   ap_filter_t* f, apr_bucket_brigade* bb,
+	ap_input_mode_t mode, apr_read_type_e block, apr_off_t nbytes)
+{
+   apr_status_t rval = APR_SUCCESS;
+
+   filter_state* state = f->ctx;
+   if(state->checked == 1)
+   {
+      // already checked for policy file request, just read from other filters
+      rval = ap_get_brigade(f->next, bb, mode, block, nbytes);
+   }
+   else
+   {
+      // try to find a policy file request
+      rval = find_policy_file_request(f, state);
+      state->checked = 1;
+
+      if(rval == APR_SUCCESS)
+      {
+         if(state->found)
+         {
+            // do read of PFR_LENGTH bytes, consider end of stream
+            rval = ap_get_brigade(
+               f->next, bb, AP_MODE_READBYTES, APR_BLOCK_READ, PFR_LENGTH);
+            rval = APR_EOF;
+         }
+         else
+         {
+            // do normal read
+            rval = ap_get_brigade(f->next, bb, mode, block, nbytes);
+         }
+      }
+   }
+
+   return rval;
+}
+
+/**
+ * Handles outgoing data. If the filter state indicates that a cross-domain
+ * policy should be sent then it is added to the outgoing brigade of data. If
+ * a policy request was not detected, then this filter makes no changes to
+ * the outgoing data.
+ *
+ * @param f the output filter.
+ * @param bb the outgoing brigade of data.
+ *
+ * @return APR_SUCCESS on success, some other status on error.
+ */
+static int fsp_output_filter(ap_filter_t* f, apr_bucket_brigade* bb)
+{
+   apr_status_t rval = APR_SUCCESS;
+
+   filter_state* state = f->ctx;
+   if(state->found)
+   {
+      // found policy-file-request, add response bucket
+      // bucket is immortal because the data is stored in the configuration
+      // and doesn't need to be copied
+      apr_bucket* head = apr_bucket_immortal_create(
+         state->cfg->policy, state->cfg->policy_length, bb->bucket_alloc);
+      APR_BRIGADE_INSERT_HEAD(bb, head);
+   }
+
+   if(rval == APR_SUCCESS)
+   {
+      // pass brigade to next filter
+      rval = ap_pass_brigade(f->next, bb);
+   }
+
+   return rval;
+}
+
+/**
+ * Creates the configuration for this module.
+ *
+ * @param p the pool to allocate from.
+ * @param s the server the configuration is for.
+ *
+ * @return the configuration data.
+ */
+static void* fsp_create_config(apr_pool_t* p, server_rec* s)
+{
+   // allocate config
+   fsp_config* cfg = apr_palloc(p, sizeof(fsp_config));
+
+   // no default policy
+   cfg->policy = NULL;
+   cfg->policy_length = 0;
+   return cfg;
+}
+
+/**
+ * Sets the policy file to use from the configuration.
+ *
+ * @param parms the command directive parameters.
+ * @param userdata NULL, not used.
+ * @param arg the string argument to the command directive (the file with
+ *           the cross-domain policy to serve as content).
+ *
+ * @return NULL on success, otherwise an error string to display.
+ */
+static const char* fsp_set_policy_file(
+   cmd_parms* parms, void* userdata, const char* arg)
+{
+   const char* rval = NULL;
+
+   apr_pool_t* pool = (apr_pool_t*)parms->pool;
+   fsp_config* cfg = ap_get_module_config(
+      parms->server->module_config, &fsp_module);
+
+   // ensure command is in the correct context
+   rval = ap_check_cmd_context(parms, NOT_IN_DIR_LOC_FILE|NOT_IN_LIMIT);
+   if(rval == NULL)
+   {
+      // get canonical file name
+      char* fname = ap_server_root_relative(pool, arg);
+      if(fname == NULL)
+      {
+         rval = (const char*)apr_psprintf(
+            pool, "%s: Invalid policy file '%s'",
+            parms->cmd->name, arg);
+      }
+      else
+      {
+         // try to open the file
+         apr_status_t rv;
+         apr_file_t* fd;
+         apr_finfo_t finfo;
+         rv = apr_file_open(&fd, fname, APR_READ, APR_OS_DEFAULT, pool);
+         if(rv == APR_SUCCESS)
+         {
+            // stat file
+            rv = apr_file_info_get(&finfo, APR_FINFO_NORM, fd);
+            if(rv == APR_SUCCESS)
+            {
+               // ensure file is not empty
+               apr_size_t length = (apr_size_t)finfo.size;
+               if(length <= 0)
+               {
+                  rval = (const char*)apr_psprintf(
+                     pool, "%s: policy file '%s' is empty",
+                     parms->cmd->name, fname);
+               }
+               // read file
+               else
+               {
+                  char* buf = (char*)apr_palloc(pool, length + 1);
+                  buf[length] = '\0';
+                  rv = apr_file_read_full(fd, buf, length, NULL);
+                  if(rv == APR_SUCCESS)
+                  {
+                     // TODO: validate file
+                     // save policy string
+                     cfg->policy = buf;
+                     cfg->policy_length = length + 1;
+                  }
+               }
+
+               // close the file
+               apr_file_close(fd);
+            }
+         }
+
+         // handle error case
+         if(rv != APR_SUCCESS)
+         {
+            char errmsg[120];
+            rval = (const char*)apr_psprintf(
+               pool, "%s: Invalid policy file '%s' (%s)",
+               parms->cmd->name, fname,
+               apr_strerror(rv, errmsg, sizeof(errmsg)));
+         }
+      }
+   }
+
+   return rval;
+}
+
+// table of configuration directives
+static const command_rec fsp_cmds[] =
+{
+   AP_INIT_TAKE1(
+      "FSPPolicyFile", /* the directive */
+      fsp_set_policy_file, /* function to call when directive is found */
+      NULL, /* user data to pass to function, not used */
+      RSRC_CONF, /* indicates the directive appears outside of <Location> */
+      "FSPPolicyFile (string) The cross-domain policy file to use"), /* docs */
+   {NULL}
+};
+
+// module setup
+module AP_MODULE_DECLARE_DATA fsp_module =
+{
+    STANDARD20_MODULE_STUFF,    /* stuff declared in every 2.0 mod       */
+    NULL,                       /* create per-directory config structure */
+    NULL,                       /* merge per-directory config structures */
+    fsp_create_config,          /* create per-server config structure    */
+    NULL,                       /* merge per-server config structures    */
+    fsp_cmds,                   /* command apr_table_t                   */
+    fsp_register_hooks          /* register hooks                        */
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/README.md b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/README.md
new file mode 100644
index 0000000..1be00fa
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/README.md
@@ -0,0 +1,34 @@
+Running all the tests (headless-browser and node.js)
+====================================================
+
+    npm install
+    npm test
+
+Running the browser-based tests
+===============================
+
+    npm install
+    node server.js
+
+Then go to http://localhost:8083/.
+
+Testing Require.js optimised version of the JavaScript
+------------------------------------------------------
+
+    npm install -g requirejs
+    r.js -o build.js
+
+You will now have a single optimised JS file at ui/test.min.js, containing the
+tests and all the forge dependencies.
+
+Now edit ui/index.html and change `data-main="test"` to `data-main="test.min"`,
+then reload http://localhost:8083/.
+
+Building a minimized single file for all forge modules
+------------------------------------------------------
+
+    npm install -g requirejs
+    r.js -o minify.js
+
+You will now have forge.min.js, in the 'js' directory, which will contain all
+forge modules.
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/build.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/build.js
new file mode 100644
index 0000000..30ba7b5
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/build.js
@@ -0,0 +1,8 @@
+({
+    paths: {
+        forge: '../js'
+    },
+    name: 'ui/test.js',
+    out: 'ui/test.min.js',
+    preserveLicenseComments: false
+})
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/minify.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/minify.js
new file mode 100644
index 0000000..69d96a9
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/minify.js
@@ -0,0 +1,8 @@
+({
+    paths: {
+        forge: '../js'
+    },
+    name: '../js/forge',
+    out: '../js/forge.min.js',
+    preserveLicenseComments: false
+})
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/package.json b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/package.json
new file mode 100644
index 0000000..f60f52a
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/package.json
@@ -0,0 +1,17 @@
+{
+    "name": "forge-nodejs-example",
+    "version": "0.1.0",
+    "private": true,
+    "main": "server.js",
+    "dependencies": {
+        "express": "~3.1.0",
+        "mocha": "~1.8.2",
+        "chai": "~1.5.0",
+        "grunt": "~0.4.1",
+        "grunt-mocha": "~0.3.1"
+    },
+    "scripts": {
+        "test": "mocha -t 20000 -R spec test/*.js",
+        "run": "node server"
+    }
+}
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/server.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/server.js
new file mode 100644
index 0000000..175bd56
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/server.js
@@ -0,0 +1,46 @@
+var PATH = require('path');
+var express = require('express');
+var PORT = 8083;
+
+exports.main = function(callback) {
+  try {
+    var app = express();
+
+    mountStaticDir(app, /^\/forge\/(.*)$/, PATH.join(__dirname, '../js'));
+    mountStaticDir(app, /^\/test\/(.*)$/, PATH.join(__dirname, 'test'));
+    mountStaticDir(app, /^\/mocha\/(.*)$/, PATH.join(__dirname, 'node_modules/mocha'));
+    mountStaticDir(app, /^\/chai\/(.*)$/, PATH.join(__dirname, 'node_modules/chai'));
+    app.get(/^\//, express.static(PATH.join(__dirname, 'ui')));
+
+    var server = app.listen(PORT);
+
+    console.log('open http://localhost:' + PORT + '/');
+
+    return callback(null, {
+      server: server,
+      port: PORT
+    });
+  } catch(err) {
+    return callback(err);
+  }
+};
+
+function mountStaticDir(app, route, path) {
+  app.get(route, function(req, res, next) {
+    var originalUrl = req.url;
+    req.url = req.params[0];
+    express.static(path)(req, res, function() {
+      req.url = originalUrl;
+      return next.apply(null, arguments);
+    });
+  });
+}
+
+if(require.main === module) {
+  exports.main(function(err) {
+    if(err) {
+      console.error(err.stack);
+      process.exit(1);
+    }
+  });
+}
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/aes.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/aes.js
new file mode 100644
index 0000000..ddd91a4
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/aes.js
@@ -0,0 +1,1213 @@
+(function() {
+
+function Tests(ASSERT, CIPHER, AES, UTIL) {
+  describe('aes', function() {
+    it('should encrypt a single block with a 128-bit key', function() {
+      var key = [0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f];
+      var block = [0x00112233, 0x44556677, 0x8899aabb, 0xccddeeff];
+
+      var output = [];
+      var w = AES._expandKey(key, false);
+      AES._updateBlock(w, block, output, false);
+
+      var out = UTIL.createBuffer();
+      out.putInt32(output[0]);
+      out.putInt32(output[1]);
+      out.putInt32(output[2]);
+      out.putInt32(output[3]);
+
+      ASSERT.equal(out.toHex(), '69c4e0d86a7b0430d8cdb78070b4c55a');
+    });
+
+    it('should decrypt a single block with a 128-bit key', function() {
+      var key = [0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f];
+      var block = [0x69c4e0d8, 0x6a7b0430, 0xd8cdb780, 0x70b4c55a];
+
+      var output = [];
+      var w = AES._expandKey(key, true);
+      AES._updateBlock(w, block, output, true);
+
+      var out = UTIL.createBuffer();
+      out.putInt32(output[0]);
+      out.putInt32(output[1]);
+      out.putInt32(output[2]);
+      out.putInt32(output[3]);
+
+      ASSERT.equal(out.toHex(), '00112233445566778899aabbccddeeff');
+    });
+
+    it('should encrypt a single block with a 192-bit key', function() {
+        var key = [
+          0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f,
+          0x10111213, 0x14151617];
+        var block = [0x00112233, 0x44556677, 0x8899aabb, 0xccddeeff];
+
+        var output = [];
+        var w = AES._expandKey(key, false);
+        AES._updateBlock(w, block, output, false);
+
+        var out = UTIL.createBuffer();
+        out.putInt32(output[0]);
+        out.putInt32(output[1]);
+        out.putInt32(output[2]);
+        out.putInt32(output[3]);
+
+        ASSERT.equal(out.toHex(), 'dda97ca4864cdfe06eaf70a0ec0d7191');
+    });
+
+    it('should decrypt a single block with a 192-bit key', function() {
+        var key = [
+          0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f,
+          0x10111213, 0x14151617];
+        var block = [0xdda97ca4, 0x864cdfe0, 0x6eaf70a0, 0xec0d7191];
+
+        var output = [];
+        var w = AES._expandKey(key, true);
+        AES._updateBlock(w, block, output, true);
+
+        var out = UTIL.createBuffer();
+        out.putInt32(output[0]);
+        out.putInt32(output[1]);
+        out.putInt32(output[2]);
+        out.putInt32(output[3]);
+
+        ASSERT.equal(out.toHex(), '00112233445566778899aabbccddeeff');
+    });
+
+    it('should encrypt a single block with a 256-bit key', function() {
+        var key = [
+          0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f,
+          0x10111213, 0x14151617, 0x18191a1b, 0x1c1d1e1f];
+        var block = [0x00112233, 0x44556677, 0x8899aabb, 0xccddeeff];
+
+        var output = [];
+        var w = AES._expandKey(key, false);
+        AES._updateBlock(w, block, output, false);
+
+        var out = UTIL.createBuffer();
+        out.putInt32(output[0]);
+        out.putInt32(output[1]);
+        out.putInt32(output[2]);
+        out.putInt32(output[3]);
+
+        ASSERT.equal(out.toHex(), '8ea2b7ca516745bfeafc49904b496089');
+    });
+
+    it('should decrypt a single block with a 256-bit key', function() {
+        var key = [
+          0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f,
+          0x10111213, 0x14151617, 0x18191a1b, 0x1c1d1e1f];
+        var block = [0x8ea2b7ca, 0x516745bf, 0xeafc4990, 0x4b496089];
+
+        var output = [];
+        var w = AES._expandKey(key, true);
+        AES._updateBlock(w, block, output, true);
+
+        var out = UTIL.createBuffer();
+        out.putInt32(output[0]);
+        out.putInt32(output[1]);
+        out.putInt32(output[2]);
+        out.putInt32(output[3]);
+
+        ASSERT.equal(out.toHex(), '00112233445566778899aabbccddeeff');
+    });
+
+    // AES-128-CBC
+    (function() {
+      var keys = [
+        '06a9214036b8a15b512e03d534120006',
+        'c286696d887c9aa0611bbb3e2025a45a',
+        '6c3ea0477630ce21a2ce334aa746c2cd',
+        '56e47a38c5598974bc46903dba290349'
+      ];
+
+      var ivs = [
+        '3dafba429d9eb430b422da802c9fac41',
+        '562e17996d093d28ddb3ba695a2e6f58',
+        'c782dc4c098c66cbd9cd27d825682c81',
+        '8ce82eefbea0da3c44699ed7db51b7d9'
+      ];
+
+      var inputs = [
+        'Single block msg',
+        '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f',
+        'This is a 48-byte message (exactly 3 AES blocks)',
+        'a0a1a2a3a4a5a6a7a8a9aaabacadaeaf' +
+          'b0b1b2b3b4b5b6b7b8b9babbbcbdbebf' +
+          'c0c1c2c3c4c5c6c7c8c9cacbcccdcecf' +
+          'd0d1d2d3d4d5d6d7d8d9dadbdcdddedf'
+      ];
+
+      var outputs = [
+        'e353779c1079aeb82708942dbe77181a',
+        'd296cd94c2cccf8a3a863028b5e1dc0a7586602d253cfff91b8266bea6d61ab1',
+        'd0a02b3836451753d493665d33f0e886' +
+          '2dea54cdb293abc7506939276772f8d5' +
+          '021c19216bad525c8579695d83ba2684',
+        'c30e32ffedc0774e6aff6af0869f71aa' +
+          '0f3af07a9a31a9c684db207eb0ef8e4e' +
+          '35907aa632c3ffdf868bb7b29d3d46ad' +
+          '83ce9f9a102ee99d49a53e87f4c3da55'
+      ];
+
+      for(var i = 0; i < keys.length; ++i) {
+        (function(i) {
+          var key = UTIL.hexToBytes(keys[i]);
+          var iv = UTIL.hexToBytes(ivs[i]);
+          var input = (i & 1) ? UTIL.hexToBytes(inputs[i]) : inputs[i];
+          var output = UTIL.hexToBytes(outputs[i]);
+
+          it('should aes-128-cbc encrypt: ' + inputs[i], function() {
+            // encrypt w/no padding
+            var cipher = CIPHER.createCipher('AES-CBC', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(input));
+            cipher.finish(function(){return true;});
+            ASSERT.equal(cipher.output.toHex(), outputs[i]);
+          });
+
+          it('should aes-128-cbc decrypt: ' + outputs[i], function() {
+            // decrypt w/no padding
+            var cipher = CIPHER.createDecipher('AES-CBC', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(output));
+            cipher.finish(function(){return true;});
+            var out = (i & 1) ? cipher.output.toHex() : cipher.output.bytes();
+            ASSERT.equal(out, inputs[i]);
+          });
+        })(i);
+      }
+    })();
+
+    // AES-192-CBC
+    (function() {
+      var keys = [
+        '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b',
+        '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b',
+        '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b',
+        '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b'
+      ];
+
+      var ivs = [
+        '000102030405060708090A0B0C0D0E0F',
+        '4F021DB243BC633D7178183A9FA071E8',
+        'B4D9ADA9AD7DEDF4E5E738763F69145A',
+        '571B242012FB7AE07FA9BAAC3DF102E0'
+      ];
+
+      var inputs = [
+        '6bc1bee22e409f96e93d7e117393172a',
+        'ae2d8a571e03ac9c9eb76fac45af8e51',
+        '30c81c46a35ce411e5fbc1191a0a52ef',
+        'f69f2445df4f9b17ad2b417be66c3710'
+      ];
+
+      var outputs = [
+        '4f021db243bc633d7178183a9fa071e8',
+        'b4d9ada9ad7dedf4e5e738763f69145a',
+        '571b242012fb7ae07fa9baac3df102e0',
+        '08b0e27988598881d920a9e64f5615cd'
+      ];
+
+      for(var i = 0; i < keys.length; ++i) {
+        (function(i) {
+          var key = UTIL.hexToBytes(keys[i]);
+          var iv = UTIL.hexToBytes(ivs[i]);
+          var input = UTIL.hexToBytes(inputs[i]);
+          var output = UTIL.hexToBytes(outputs[i]);
+
+          it('should aes-192-cbc encrypt: ' + inputs[i], function() {
+            // encrypt w/no padding
+            var cipher = CIPHER.createCipher('AES-CBC', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(input));
+            cipher.finish(function(){return true;});
+            ASSERT.equal(cipher.output.toHex(), outputs[i]);
+          });
+
+          it('should aes-192-cbc decrypt: ' + outputs[i], function() {
+            // decrypt w/no padding
+            var cipher = CIPHER.createDecipher('AES-CBC', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(output));
+            cipher.finish(function(){return true;});
+            var out = cipher.output.toHex();
+            ASSERT.equal(out, inputs[i]);
+          });
+        })(i);
+      }
+    })();
+
+    // AES-256-CBC
+    (function() {
+      var keys = [
+        '603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4',
+        '603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4',
+        '603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4',
+        '603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4'
+      ];
+
+      var ivs = [
+        '000102030405060708090A0B0C0D0E0F',
+        'F58C4C04D6E5F1BA779EABFB5F7BFBD6',
+        '9CFC4E967EDB808D679F777BC6702C7D',
+        '39F23369A9D9BACFA530E26304231461'
+      ];
+
+      var inputs = [
+        '6bc1bee22e409f96e93d7e117393172a',
+        'ae2d8a571e03ac9c9eb76fac45af8e51',
+        '30c81c46a35ce411e5fbc1191a0a52ef',
+        'f69f2445df4f9b17ad2b417be66c3710'
+      ];
+
+      var outputs = [
+        'f58c4c04d6e5f1ba779eabfb5f7bfbd6',
+        '9cfc4e967edb808d679f777bc6702c7d',
+        '39f23369a9d9bacfa530e26304231461',
+        'b2eb05e2c39be9fcda6c19078c6a9d1b'
+      ];
+
+      for(var i = 0; i < keys.length; ++i) {
+        (function(i) {
+          var key = UTIL.hexToBytes(keys[i]);
+          var iv = UTIL.hexToBytes(ivs[i]);
+          var input = UTIL.hexToBytes(inputs[i]);
+          var output = UTIL.hexToBytes(outputs[i]);
+
+          it('should aes-256-cbc encrypt: ' + inputs[i], function() {
+            // encrypt w/no padding
+            var cipher = CIPHER.createCipher('AES-CBC', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(input));
+            cipher.finish(function(){return true;});
+            ASSERT.equal(cipher.output.toHex(), outputs[i]);
+          });
+
+          it('should aes-256-cbc decrypt: ' + outputs[i], function() {
+            // decrypt w/no padding
+            var cipher = CIPHER.createDecipher('AES-CBC', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(output));
+            cipher.finish(function(){return true;});
+            var out = cipher.output.toHex();
+            ASSERT.equal(out, inputs[i]);
+          });
+        })(i);
+      }
+    })();
+
+    // AES-128-CFB
+    (function() {
+      var keys = [
+        '00000000000000000000000000000000',
+        '2b7e151628aed2a6abf7158809cf4f3c',
+        '2b7e151628aed2a6abf7158809cf4f3c',
+        '2b7e151628aed2a6abf7158809cf4f3c',
+        '2b7e151628aed2a6abf7158809cf4f3c',
+        '00000000000000000000000000000000'
+      ];
+
+      var ivs = [
+        '80000000000000000000000000000000',
+        '000102030405060708090a0b0c0d0e0f',
+        '3B3FD92EB72DAD20333449F8E83CFB4A',
+        'C8A64537A0B3A93FCDE3CDAD9F1CE58B',
+        '26751F67A3CBB140B1808CF187A4F4DF',
+        '60f9ff04fac1a25657bf5b36b5efaf75'
+      ];
+
+      var inputs = [
+        '00000000000000000000000000000000',
+        '6bc1bee22e409f96e93d7e117393172a',
+        'ae2d8a571e03ac9c9eb76fac45af8e51',
+        '30c81c46a35ce411e5fbc1191a0a52ef',
+        'f69f2445df4f9b17ad2b417be66c3710',
+        'This is a 48-byte message (exactly 3 AES blocks)'
+      ];
+
+      var outputs = [
+        '3ad78e726c1ec02b7ebfe92b23d9ec34',
+        '3b3fd92eb72dad20333449f8e83cfb4a',
+        'c8a64537a0b3a93fcde3cdad9f1ce58b',
+        '26751f67a3cbb140b1808cf187a4f4df',
+        'c04b05357c5d1c0eeac4c66f9ff7f2e6',
+        '52396a2ba1ba420c5e5b699a814944d8' +
+          'f4e7fbf984a038319fbc0b4ee45cfa6f' +
+          '07b2564beab5b5e92dbd44cb345f49b4'
+      ];
+
+      for(var i = 0; i < keys.length; ++i) {
+        (function(i) {
+          var key = UTIL.hexToBytes(keys[i]);
+          var iv = UTIL.hexToBytes(ivs[i]);
+          var input = (i !== 5) ? UTIL.hexToBytes(inputs[i]) : inputs[i];
+          var output = UTIL.hexToBytes(outputs[i]);
+
+          it('should aes-128-cfb encrypt: ' + inputs[i], function() {
+            // encrypt
+            var cipher = CIPHER.createCipher('AES-CFB', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(input));
+            cipher.finish();
+            ASSERT.equal(cipher.output.toHex(), outputs[i]);
+          });
+
+          it('should aes-128-cfb decrypt: ' + outputs[i], function() {
+            // decrypt
+            var cipher = CIPHER.createDecipher('AES-CFB', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(output));
+            cipher.finish();
+            var out = (i !== 5) ?
+              cipher.output.toHex() : cipher.output.getBytes();
+            ASSERT.equal(out, inputs[i]);
+          });
+        })(i);
+      }
+    })();
+
+    // AES-192-CFB
+    (function() {
+      var keys = [
+        '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b',
+        '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b',
+        '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b',
+        '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b'
+      ];
+
+      var ivs = [
+        '000102030405060708090A0B0C0D0E0F',
+        'CDC80D6FDDF18CAB34C25909C99A4174',
+        '67CE7F7F81173621961A2B70171D3D7A',
+        '2E1E8A1DD59B88B1C8E60FED1EFAC4C9'
+      ];
+
+      var inputs = [
+        '6bc1bee22e409f96e93d7e117393172a',
+        'ae2d8a571e03ac9c9eb76fac45af8e51',
+        '30c81c46a35ce411e5fbc1191a0a52ef',
+        'f69f2445df4f9b17ad2b417be66c3710'
+      ];
+
+      var outputs = [
+        'cdc80d6fddf18cab34c25909c99a4174',
+        '67ce7f7f81173621961a2b70171d3d7a',
+        '2e1e8a1dd59b88b1c8e60fed1efac4c9',
+        'c05f9f9ca9834fa042ae8fba584b09ff'
+      ];
+
+      for(var i = 0; i < keys.length; ++i) {
+        (function(i) {
+          var key = UTIL.hexToBytes(keys[i]);
+          var iv = UTIL.hexToBytes(ivs[i]);
+          var input = UTIL.hexToBytes(inputs[i]);
+          var output = UTIL.hexToBytes(outputs[i]);
+
+          it('should aes-192-cfb encrypt: ' + inputs[i], function() {
+            // encrypt
+            var cipher = CIPHER.createCipher('AES-CFB', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(input));
+            cipher.finish();
+            ASSERT.equal(cipher.output.toHex(), outputs[i]);
+          });
+
+          it('should aes-192-cfb decrypt: ' + outputs[i], function() {
+            // decrypt
+            var cipher = CIPHER.createDecipher('AES-CFB', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(output));
+            cipher.finish();
+            var out = cipher.output.toHex();
+            ASSERT.equal(out, inputs[i]);
+          });
+        })(i);
+      }
+    })();
+
+    // AES-256-CFB
+    (function() {
+      var keys = [
+        '861009ec4d599fab1f40abc76e6f89880cff5833c79c548c99f9045f191cd90b'
+      ];
+
+      var ivs = [
+        'd927ad81199aa7dcadfdb4e47b6dc694'
+      ];
+
+      var inputs = [
+        'MY-DATA-AND-HERE-IS-MORE-DATA'
+      ];
+
+      var outputs = [
+        '80eb666a9fc9e263faf71e87ffc94451d7d8df7cfcf2606470351dd5ac'
+      ];
+
+      for(var i = 0; i < keys.length; ++i) {
+        (function(i) {
+          var key = UTIL.hexToBytes(keys[i]);
+          var iv = UTIL.hexToBytes(ivs[i]);
+          var input = inputs[i];
+          var output = UTIL.hexToBytes(outputs[i]);
+
+          it('should aes-256-cfb encrypt: ' + inputs[i], function() {
+            // encrypt
+            var cipher = CIPHER.createCipher('AES-CFB', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(input));
+            cipher.finish();
+            ASSERT.equal(cipher.output.toHex(), outputs[i]);
+          });
+
+          it('should aes-256-cfb decrypt: ' + outputs[i], function() {
+            // decrypt
+            var cipher = CIPHER.createDecipher('AES-CFB', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(output));
+            cipher.finish();
+            var out = cipher.output.getBytes();
+            ASSERT.equal(out, inputs[i]);
+          });
+        })(i);
+      }
+    })();
+
+    // AES-128-OFB
+    (function() {
+      var keys = [
+        '00000000000000000000000000000000',
+        '00000000000000000000000000000000'
+      ];
+
+      var ivs = [
+        '80000000000000000000000000000000',
+        'c8ca0d6a35dbeac776e911ee16bea7d3'
+      ];
+
+      var inputs = [
+        '00000000000000000000000000000000',
+        'This is a 48-byte message (exactly 3 AES blocks)'
+      ];
+
+      var outputs = [
+        '3ad78e726c1ec02b7ebfe92b23d9ec34',
+        '39c0190727a76b2a90963426f63689cf' +
+          'cdb8a2be8e20c5e877a81a724e3611f6' +
+          '2ecc386f2e941b2441c838906002be19'
+      ];
+
+      for(var i = 0; i < keys.length; ++i) {
+        (function(i) {
+          var key = UTIL.hexToBytes(keys[i]);
+          var iv = UTIL.hexToBytes(ivs[i]);
+          var input = (i !== 1) ? UTIL.hexToBytes(inputs[i]) : inputs[i];
+          var output = UTIL.hexToBytes(outputs[i]);
+
+          it('should aes-128-ofb encrypt: ' + inputs[i], function() {
+            // encrypt
+            var cipher = CIPHER.createCipher('AES-OFB', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(input));
+            cipher.finish();
+            ASSERT.equal(cipher.output.toHex(), outputs[i]);
+          });
+
+          it('should aes-128-ofb decrypt: ' + outputs[i], function() {
+            // decrypt
+            var cipher = CIPHER.createDecipher('AES-OFB', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(output));
+            cipher.finish();
+            var out = (i !== 1) ?
+              cipher.output.toHex() : cipher.output.getBytes();
+            ASSERT.equal(out, inputs[i]);
+          });
+        })(i);
+      }
+    })();
+
+    // AES-192-OFB
+    (function() {
+      var keys = [
+        '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b',
+        '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b',
+        '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b',
+        '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b'
+      ];
+
+      var ivs = [
+        '000102030405060708090A0B0C0D0E0F',
+        'A609B38DF3B1133DDDFF2718BA09565E',
+        '52EF01DA52602FE0975F78AC84BF8A50',
+        'BD5286AC63AABD7EB067AC54B553F71D'
+      ];
+
+      var inputs = [
+        '6bc1bee22e409f96e93d7e117393172a',
+        'ae2d8a571e03ac9c9eb76fac45af8e51',
+        '30c81c46a35ce411e5fbc1191a0a52ef',
+        'f69f2445df4f9b17ad2b417be66c3710'
+      ];
+
+      var outputs = [
+        'cdc80d6fddf18cab34c25909c99a4174',
+        'fcc28b8d4c63837c09e81700c1100401',
+        '8d9a9aeac0f6596f559c6d4daf59a5f2',
+        '6d9f200857ca6c3e9cac524bd9acc92a'
+      ];
+
+      for(var i = 0; i < keys.length; ++i) {
+        (function(i) {
+          var key = UTIL.hexToBytes(keys[i]);
+          var iv = UTIL.hexToBytes(ivs[i]);
+          var input = UTIL.hexToBytes(inputs[i]);
+          var output = UTIL.hexToBytes(outputs[i]);
+
+          it('should aes-192-ofb encrypt: ' + inputs[i], function() {
+            // encrypt
+            var cipher = CIPHER.createCipher('AES-OFB', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(input));
+            cipher.finish();
+            ASSERT.equal(cipher.output.toHex(), outputs[i]);
+          });
+
+          it('should aes-192-ofb decrypt: ' + outputs[i], function() {
+            // decrypt
+            var cipher = CIPHER.createDecipher('AES-OFB', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(output));
+            cipher.finish();
+            var out = cipher.output.toHex();
+            ASSERT.equal(out, inputs[i]);
+          });
+        })(i);
+      }
+    })();
+
+    // AES-256-OFB
+    (function() {
+      var keys = [
+        '603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4',
+        '603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4',
+        '603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4',
+        '603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4'
+      ];
+
+      var ivs = [
+        '000102030405060708090A0B0C0D0E0F',
+        'B7BF3A5DF43989DD97F0FA97EBCE2F4A',
+        'E1C656305ED1A7A6563805746FE03EDC',
+        '41635BE625B48AFC1666DD42A09D96E7'
+      ];
+
+      var inputs = [
+        '6bc1bee22e409f96e93d7e117393172a',
+        'ae2d8a571e03ac9c9eb76fac45af8e51',
+        '30c81c46a35ce411e5fbc1191a0a52ef',
+        'f69f2445df4f9b17ad2b417be66c3710'
+      ];
+
+      var outputs = [
+        'dc7e84bfda79164b7ecd8486985d3860',
+        '4febdc6740d20b3ac88f6ad82a4fb08d',
+        '71ab47a086e86eedf39d1c5bba97c408',
+        '0126141d67f37be8538f5a8be740e484'
+      ];
+
+      for(var i = 0; i < keys.length; ++i) {
+        (function(i) {
+          var key = UTIL.hexToBytes(keys[i]);
+          var iv = UTIL.hexToBytes(ivs[i]);
+          var input = UTIL.hexToBytes(inputs[i]);
+          var output = UTIL.hexToBytes(outputs[i]);
+
+          it('should aes-256-ofb encrypt: ' + inputs[i], function() {
+            // encrypt
+            var cipher = CIPHER.createCipher('AES-OFB', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(input));
+            cipher.finish();
+            ASSERT.equal(cipher.output.toHex(), outputs[i]);
+          });
+
+          it('should aes-256-ofb decrypt: ' + outputs[i], function() {
+            // decrypt
+            var cipher = CIPHER.createDecipher('AES-OFB', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(output));
+            cipher.finish();
+            var out = cipher.output.toHex();
+            ASSERT.equal(out, inputs[i]);
+          });
+        })(i);
+      }
+    })();
+
+    // AES-128-CTR
+    (function() {
+      var keys = [
+        '2b7e151628aed2a6abf7158809cf4f3c',
+        '00000000000000000000000000000000'
+      ];
+
+      var ivs = [
+        'f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff',
+        '650cdb80ff9fc758342d2bd99ee2abcf'
+      ];
+
+      var inputs = [
+        '6bc1bee22e409f96e93d7e117393172a',
+        'This is a 48-byte message (exactly 3 AES blocks)'
+      ];
+
+      var outputs = [
+        '874d6191b620e3261bef6864990db6ce',
+        '5ede11d00e9a76ec1d5e7e811ea3dd1c' +
+          'e09ee941210f825d35718d3282796f1c' +
+          '07c3f1cb424f2b365766ab5229f5b5a4'
+      ];
+
+      for(var i = 0; i < keys.length; ++i) {
+        (function(i) {
+          var key = UTIL.hexToBytes(keys[i]);
+          var iv = UTIL.hexToBytes(ivs[i]);
+          var input = (i !== 1) ? UTIL.hexToBytes(inputs[i]) : inputs[i];
+          var output = UTIL.hexToBytes(outputs[i]);
+
+          it('should aes-128-ctr encrypt: ' + inputs[i], function() {
+            // encrypt
+            var cipher = CIPHER.createCipher('AES-CTR', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(input));
+            cipher.finish();
+            ASSERT.equal(cipher.output.toHex(), outputs[i]);
+          });
+
+          it('should aes-128-ctr decrypt: ' + outputs[i], function() {
+            // decrypt
+            var cipher = CIPHER.createDecipher('AES-CTR', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(output));
+            cipher.finish();
+            var out = (i !== 1) ?
+              cipher.output.toHex() : cipher.output.getBytes();
+            ASSERT.equal(out, inputs[i]);
+          });
+        })(i);
+      }
+    })();
+
+    // AES-192-CTR
+    (function() {
+      var keys = [
+        '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b'
+      ];
+
+      var ivs = [
+        'f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff'
+      ];
+
+      var inputs = [
+        '6bc1bee22e409f96e93d7e117393172a' +
+          'ae2d8a571e03ac9c9eb76fac45af8e51' +
+          '30c81c46a35ce411e5fbc1191a0a52ef' +
+          'f69f2445df4f9b17ad2b417be66c3710'
+      ];
+
+      var outputs = [
+        '1abc932417521ca24f2b0459fe7e6e0b' +
+          '090339ec0aa6faefd5ccc2c6f4ce8e94' +
+          '1e36b26bd1ebc670d1bd1d665620abf7' +
+          '4f78a7f6d29809585a97daec58c6b050'
+      ];
+
+      for(var i = 0; i < keys.length; ++i) {
+        (function(i) {
+          var key = UTIL.hexToBytes(keys[i]);
+          var iv = UTIL.hexToBytes(ivs[i]);
+          var input = UTIL.hexToBytes(inputs[i]);
+          var output = UTIL.hexToBytes(outputs[i]);
+
+          it('should aes-192-ctr encrypt: ' + inputs[i], function() {
+            // encrypt
+            var cipher = CIPHER.createCipher('AES-CTR', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(input));
+            cipher.finish();
+            ASSERT.equal(cipher.output.toHex(), outputs[i]);
+          });
+
+          it('should aes-192-ctr decrypt: ' + outputs[i], function() {
+            // decrypt
+            var cipher = CIPHER.createDecipher('AES-CTR', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(output));
+            cipher.finish();
+            var out = cipher.output.toHex();
+            ASSERT.equal(out, inputs[i]);
+          });
+        })(i);
+      }
+    })();
+
+    // AES-256-CTR
+    (function() {
+      var keys = [
+        '603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4'
+      ];
+
+      var ivs = [
+        'f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff'
+      ];
+
+      var inputs = [
+        '6bc1bee22e409f96e93d7e117393172a' +
+          'ae2d8a571e03ac9c9eb76fac45af8e51' +
+          '30c81c46a35ce411e5fbc1191a0a52ef' +
+          'f69f2445df4f9b17ad2b417be66c3710'
+      ];
+
+      var outputs = [
+        '601ec313775789a5b7a7f504bbf3d228' +
+          'f443e3ca4d62b59aca84e990cacaf5c5' +
+          '2b0930daa23de94ce87017ba2d84988d' +
+          'dfc9c58db67aada613c2dd08457941a6'
+      ];
+
+      for(var i = 0; i < keys.length; ++i) {
+        (function(i) {
+          var key = UTIL.hexToBytes(keys[i]);
+          var iv = UTIL.hexToBytes(ivs[i]);
+          var input = UTIL.hexToBytes(inputs[i]);
+          var output = UTIL.hexToBytes(outputs[i]);
+
+          it('should aes-256-ctr encrypt: ' + inputs[i], function() {
+            // encrypt
+            var cipher = CIPHER.createCipher('AES-CTR', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(input));
+            cipher.finish();
+            ASSERT.equal(cipher.output.toHex(), outputs[i]);
+          });
+
+          it('should aes-256-ctr decrypt: ' + outputs[i], function() {
+            // decrypt
+            var cipher = CIPHER.createDecipher('AES-CTR', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(output));
+            cipher.finish();
+            var out = cipher.output.toHex();
+            ASSERT.equal(out, inputs[i]);
+          });
+        })(i);
+      }
+    })();
+
+    // AES-128-GCM
+    (function() {
+      var keys = [
+        '00000000000000000000000000000000',
+        '00000000000000000000000000000000',
+        'feffe9928665731c6d6a8f9467308308',
+        'feffe9928665731c6d6a8f9467308308',
+        'feffe9928665731c6d6a8f9467308308',
+        'feffe9928665731c6d6a8f9467308308'
+      ];
+
+      var ivs = [
+        '000000000000000000000000',
+        '000000000000000000000000',
+        'cafebabefacedbaddecaf888',
+        'cafebabefacedbaddecaf888',
+        'cafebabefacedbad',
+        '9313225df88406e555909c5aff5269aa' +
+          '6a7a9538534f7da1e4c303d2a318a728' +
+          'c3c0c95156809539fcf0e2429a6b5254' +
+          '16aedbf5a0de6a57a637b39b'
+      ];
+
+      var adatas = [
+        '',
+        '',
+        '',
+        'feedfacedeadbeeffeedfacedeadbeef' +
+          'abaddad2',
+        'feedfacedeadbeeffeedfacedeadbeef' +
+          'abaddad2',
+        'feedfacedeadbeeffeedfacedeadbeef' +
+          'abaddad2'
+      ];
+
+      var inputs = [
+        '',
+        '00000000000000000000000000000000',
+        'd9313225f88406e5a55909c5aff5269a' +
+          '86a7a9531534f7da2e4c303d8a318a72' +
+          '1c3c0c95956809532fcf0e2449a6b525' +
+          'b16aedf5aa0de657ba637b391aafd255',
+        'd9313225f88406e5a55909c5aff5269a' +
+          '86a7a9531534f7da2e4c303d8a318a72' +
+          '1c3c0c95956809532fcf0e2449a6b525' +
+          'b16aedf5aa0de657ba637b39',
+        'd9313225f88406e5a55909c5aff5269a' +
+          '86a7a9531534f7da2e4c303d8a318a72' +
+          '1c3c0c95956809532fcf0e2449a6b525' +
+          'b16aedf5aa0de657ba637b39',
+        'd9313225f88406e5a55909c5aff5269a' +
+          '86a7a9531534f7da2e4c303d8a318a72' +
+          '1c3c0c95956809532fcf0e2449a6b525' +
+          'b16aedf5aa0de657ba637b39'
+      ];
+
+      var outputs = [
+        '',
+        '0388dace60b6a392f328c2b971b2fe78',
+        '42831ec2217774244b7221b784d0d49c' +
+          'e3aa212f2c02a4e035c17e2329aca12e' +
+          '21d514b25466931c7d8f6a5aac84aa05' +
+          '1ba30b396a0aac973d58e091473f5985',
+        '42831ec2217774244b7221b784d0d49c' +
+          'e3aa212f2c02a4e035c17e2329aca12e' +
+          '21d514b25466931c7d8f6a5aac84aa05' +
+          '1ba30b396a0aac973d58e091',
+        '61353b4c2806934a777ff51fa22a4755' +
+          '699b2a714fcdc6f83766e5f97b6c7423' +
+          '73806900e49f24b22b097544d4896b42' +
+          '4989b5e1ebac0f07c23f4598',
+        '8ce24998625615b603a033aca13fb894' +
+          'be9112a5c3a211a8ba262a3cca7e2ca7' +
+          '01e4a9a4fba43c90ccdcb281d48c7c6f' +
+          'd62875d2aca417034c34aee5'
+      ];
+
+      var tags = [
+        '58e2fccefa7e3061367f1d57a4e7455a',
+        'ab6e47d42cec13bdf53a67b21257bddf',
+        '4d5c2af327cd64a62cf35abd2ba6fab4',
+        '5bc94fbc3221a5db94fae95ae7121a47',
+        '3612d2e79e3b0785561be14aaca2fccb',
+        '619cc5aefffe0bfa462af43c1699d050'
+      ];
+
+      for(var i = 0; i < keys.length; ++i) {
+        (function(i) {
+          var key = UTIL.hexToBytes(keys[i]);
+          var iv = UTIL.hexToBytes(ivs[i]);
+          var adata = UTIL.hexToBytes(adatas[i]);
+          var input = UTIL.hexToBytes(inputs[i]);
+          var output = UTIL.hexToBytes(outputs[i]);
+
+          it('should aes-128-gcm encrypt: ' + inputs[i], function() {
+            // encrypt
+            var cipher = CIPHER.createCipher('AES-GCM', key);
+            cipher.start({iv: iv, additionalData: adata});
+            cipher.update(UTIL.createBuffer(input));
+            cipher.finish();
+            ASSERT.equal(cipher.output.toHex(), outputs[i]);
+            ASSERT.equal(cipher.mode.tag.toHex(), tags[i]);
+          });
+
+          it('should aes-128-gcm decrypt: ' + outputs[i], function() {
+            // decrypt
+            var cipher = CIPHER.createDecipher('AES-GCM', key);
+            cipher.start({
+              iv: iv,
+              additionalData: adata,
+              tag: UTIL.hexToBytes(tags[i])
+            });
+            cipher.update(UTIL.createBuffer(output));
+            var pass = cipher.finish();
+            ASSERT.equal(cipher.mode.tag.toHex(), tags[i]);
+            ASSERT.equal(pass, true);
+            ASSERT.equal(cipher.output.toHex(), inputs[i]);
+          });
+        })(i);
+      }
+    })();
+
+    // AES-192-GCM
+    (function() {
+      var keys = [
+        '00000000000000000000000000000000' +
+          '0000000000000000',
+        '00000000000000000000000000000000' +
+          '0000000000000000',
+        'feffe9928665731c6d6a8f9467308308' +
+          'feffe9928665731c',
+        'feffe9928665731c6d6a8f9467308308' +
+          'feffe9928665731c',
+        'feffe9928665731c6d6a8f9467308308' +
+          'feffe9928665731c',
+        'feffe9928665731c6d6a8f9467308308' +
+          'feffe9928665731c'
+      ];
+
+      var ivs = [
+        '000000000000000000000000',
+        '000000000000000000000000',
+        'cafebabefacedbaddecaf888',
+        'cafebabefacedbaddecaf888',
+        'cafebabefacedbad',
+        '9313225df88406e555909c5aff5269aa' +
+          '6a7a9538534f7da1e4c303d2a318a728' +
+          'c3c0c95156809539fcf0e2429a6b5254' +
+          '16aedbf5a0de6a57a637b39b'
+      ];
+
+      var adatas = [
+        '',
+        '',
+        '',
+        'feedfacedeadbeeffeedfacedeadbeef' +
+          'abaddad2',
+        'feedfacedeadbeeffeedfacedeadbeef' +
+          'abaddad2',
+        'feedfacedeadbeeffeedfacedeadbeef' +
+          'abaddad2'
+      ];
+
+      var inputs = [
+        '',
+        '00000000000000000000000000000000',
+        'd9313225f88406e5a55909c5aff5269a' +
+          '86a7a9531534f7da2e4c303d8a318a72' +
+          '1c3c0c95956809532fcf0e2449a6b525' +
+          'b16aedf5aa0de657ba637b391aafd255',
+        'd9313225f88406e5a55909c5aff5269a' +
+          '86a7a9531534f7da2e4c303d8a318a72' +
+          '1c3c0c95956809532fcf0e2449a6b525' +
+          'b16aedf5aa0de657ba637b39',
+        'd9313225f88406e5a55909c5aff5269a' +
+          '86a7a9531534f7da2e4c303d8a318a72' +
+          '1c3c0c95956809532fcf0e2449a6b525' +
+          'b16aedf5aa0de657ba637b39',
+        'd9313225f88406e5a55909c5aff5269a' +
+          '86a7a9531534f7da2e4c303d8a318a72' +
+          '1c3c0c95956809532fcf0e2449a6b525' +
+          'b16aedf5aa0de657ba637b39'
+      ];
+
+      var outputs = [
+        '',
+        '98e7247c07f0fe411c267e4384b0f600',
+        '3980ca0b3c00e841eb06fac4872a2757' +
+          '859e1ceaa6efd984628593b40ca1e19c' +
+          '7d773d00c144c525ac619d18c84a3f47' +
+          '18e2448b2fe324d9ccda2710acade256',
+        '3980ca0b3c00e841eb06fac4872a2757' +
+          '859e1ceaa6efd984628593b40ca1e19c' +
+          '7d773d00c144c525ac619d18c84a3f47' +
+          '18e2448b2fe324d9ccda2710',
+        '0f10f599ae14a154ed24b36e25324db8' +
+          'c566632ef2bbb34f8347280fc4507057' +
+          'fddc29df9a471f75c66541d4d4dad1c9' +
+          'e93a19a58e8b473fa0f062f7',
+        'd27e88681ce3243c4830165a8fdcf9ff' +
+          '1de9a1d8e6b447ef6ef7b79828666e45' +
+          '81e79012af34ddd9e2f037589b292db3' +
+          'e67c036745fa22e7e9b7373b'
+      ];
+
+      var tags = [
+        'cd33b28ac773f74ba00ed1f312572435',
+        '2ff58d80033927ab8ef4d4587514f0fb',
+        '9924a7c8587336bfb118024db8674a14',
+        '2519498e80f1478f37ba55bd6d27618c',
+        '65dcc57fcf623a24094fcca40d3533f8',
+        'dcf566ff291c25bbb8568fc3d376a6d9'
+      ];
+
+      for(var i = 0; i < keys.length; ++i) {
+        (function(i) {
+          var key = UTIL.hexToBytes(keys[i]);
+          var iv = UTIL.hexToBytes(ivs[i]);
+          var adata = UTIL.hexToBytes(adatas[i]);
+          var input = UTIL.hexToBytes(inputs[i]);
+          var output = UTIL.hexToBytes(outputs[i]);
+
+          it('should aes-128-gcm encrypt: ' + inputs[i], function() {
+            // encrypt
+            var cipher = CIPHER.createCipher('AES-GCM', key);
+            cipher.start({iv: iv, additionalData: adata});
+            cipher.update(UTIL.createBuffer(input));
+            cipher.finish();
+            ASSERT.equal(cipher.output.toHex(), outputs[i]);
+            ASSERT.equal(cipher.mode.tag.toHex(), tags[i]);
+          });
+
+          it('should aes-128-gcm decrypt: ' + outputs[i], function() {
+            // decrypt
+            var cipher = CIPHER.createDecipher('AES-GCM', key);
+            cipher.start({
+              iv: iv,
+              additionalData: adata,
+              tag: UTIL.hexToBytes(tags[i])
+            });
+            cipher.update(UTIL.createBuffer(output));
+            var pass = cipher.finish();
+            ASSERT.equal(cipher.mode.tag.toHex(), tags[i]);
+            ASSERT.equal(pass, true);
+            ASSERT.equal(cipher.output.toHex(), inputs[i]);
+          });
+        })(i);
+      }
+    })();
+
+    // AES-256-GCM
+    (function() {
+      var keys = [
+        '00000000000000000000000000000000' +
+          '00000000000000000000000000000000',
+        '00000000000000000000000000000000' +
+          '00000000000000000000000000000000',
+        'feffe9928665731c6d6a8f9467308308' +
+          'feffe9928665731c6d6a8f9467308308',
+        'feffe9928665731c6d6a8f9467308308' +
+          'feffe9928665731c6d6a8f9467308308',
+        'feffe9928665731c6d6a8f9467308308' +
+          'feffe9928665731c6d6a8f9467308308',
+        'feffe9928665731c6d6a8f9467308308' +
+          'feffe9928665731c6d6a8f9467308308'
+      ];
+
+      var ivs = [
+        '000000000000000000000000',
+        '000000000000000000000000',
+        'cafebabefacedbaddecaf888',
+        'cafebabefacedbaddecaf888',
+        'cafebabefacedbad',
+        '9313225df88406e555909c5aff5269aa' +
+          '6a7a9538534f7da1e4c303d2a318a728' +
+          'c3c0c95156809539fcf0e2429a6b5254' +
+          '16aedbf5a0de6a57a637b39b'
+      ];
+
+      var adatas = [
+        '',
+        '',
+        '',
+        'feedfacedeadbeeffeedfacedeadbeef' +
+          'abaddad2',
+        'feedfacedeadbeeffeedfacedeadbeef' +
+          'abaddad2',
+        'feedfacedeadbeeffeedfacedeadbeef' +
+          'abaddad2'
+      ];
+
+      var inputs = [
+        '',
+        '00000000000000000000000000000000',
+        'd9313225f88406e5a55909c5aff5269a' +
+          '86a7a9531534f7da2e4c303d8a318a72' +
+          '1c3c0c95956809532fcf0e2449a6b525' +
+          'b16aedf5aa0de657ba637b391aafd255',
+        'd9313225f88406e5a55909c5aff5269a' +
+          '86a7a9531534f7da2e4c303d8a318a72' +
+          '1c3c0c95956809532fcf0e2449a6b525' +
+          'b16aedf5aa0de657ba637b39',
+        'd9313225f88406e5a55909c5aff5269a' +
+          '86a7a9531534f7da2e4c303d8a318a72' +
+          '1c3c0c95956809532fcf0e2449a6b525' +
+          'b16aedf5aa0de657ba637b39',
+        'd9313225f88406e5a55909c5aff5269a' +
+          '86a7a9531534f7da2e4c303d8a318a72' +
+          '1c3c0c95956809532fcf0e2449a6b525' +
+          'b16aedf5aa0de657ba637b39'
+      ];
+
+      var outputs = [
+        '',
+        'cea7403d4d606b6e074ec5d3baf39d18',
+        '522dc1f099567d07f47f37a32a84427d' +
+          '643a8cdcbfe5c0c97598a2bd2555d1aa' +
+          '8cb08e48590dbb3da7b08b1056828838' +
+          'c5f61e6393ba7a0abcc9f662898015ad',
+        '522dc1f099567d07f47f37a32a84427d' +
+          '643a8cdcbfe5c0c97598a2bd2555d1aa' +
+          '8cb08e48590dbb3da7b08b1056828838' +
+          'c5f61e6393ba7a0abcc9f662',
+        'c3762df1ca787d32ae47c13bf19844cb' +
+          'af1ae14d0b976afac52ff7d79bba9de0' +
+          'feb582d33934a4f0954cc2363bc73f78' +
+          '62ac430e64abe499f47c9b1f',
+        '5a8def2f0c9e53f1f75d7853659e2a20' +
+          'eeb2b22aafde6419a058ab4f6f746bf4' +
+          '0fc0c3b780f244452da3ebf1c5d82cde' +
+          'a2418997200ef82e44ae7e3f'
+      ];
+
+      var tags = [
+        '530f8afbc74536b9a963b4f1c4cb738b',
+        'd0d1c8a799996bf0265b98b5d48ab919',
+        'b094dac5d93471bdec1a502270e3cc6c',
+        '76fc6ece0f4e1768cddf8853bb2d551b',
+        '3a337dbf46a792c45e454913fe2ea8f2',
+        'a44a8266ee1c8eb0c8b5d4cf5ae9f19a'
+      ];
+
+      for(var i = 0; i < keys.length; ++i) {
+        (function(i) {
+          var key = UTIL.hexToBytes(keys[i]);
+          var iv = UTIL.hexToBytes(ivs[i]);
+          var adata = UTIL.hexToBytes(adatas[i]);
+          var input = UTIL.hexToBytes(inputs[i]);
+          var output = UTIL.hexToBytes(outputs[i]);
+
+          it('should aes-128-gcm encrypt: ' + inputs[i], function() {
+            // encrypt
+            var cipher = CIPHER.createCipher('AES-GCM', key);
+            cipher.start({iv: iv, additionalData: adata});
+            cipher.update(UTIL.createBuffer(input));
+            cipher.finish();
+            ASSERT.equal(cipher.output.toHex(), outputs[i]);
+            ASSERT.equal(cipher.mode.tag.toHex(), tags[i]);
+          });
+
+          it('should aes-128-gcm decrypt: ' + outputs[i], function() {
+            // decrypt
+            var cipher = CIPHER.createDecipher('AES-GCM', key);
+            cipher.start({
+              iv: iv,
+              additionalData: adata,
+              tag: UTIL.hexToBytes(tags[i])
+            });
+            cipher.update(UTIL.createBuffer(output));
+            var pass = cipher.finish();
+            ASSERT.equal(cipher.mode.tag.toHex(), tags[i]);
+            ASSERT.equal(pass, true);
+            ASSERT.equal(cipher.output.toHex(), inputs[i]);
+          });
+        })(i);
+      }
+    })();
+  });
+}
+
+// check for AMD
+var forge = {};
+if(typeof define === 'function') {
+  define([
+    'forge/cipher',
+    'forge/aes',
+    'forge/util'
+  ], function(CIPHER, AES, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      CIPHER(forge),
+      AES(forge),
+      UTIL(forge)
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/cipher')(forge),
+    require('../../js/aes')(forge),
+    require('../../js/util')(forge));
+}
+
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/asn1.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/asn1.js
new file mode 100644
index 0000000..7d0880e
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/asn1.js
@@ -0,0 +1,262 @@
+(function() {
+
+function Tests(ASSERT, ASN1, UTIL) {
+  describe('asn1', function() {
+    // TODO: add more ASN.1 coverage
+
+    it('should convert an OID to DER', function() {
+      ASSERT.equal(ASN1.oidToDer('1.2.840.113549').toHex(), '2a864886f70d');
+    });
+
+    it('should convert an OID from DER', function() {
+      var der = UTIL.hexToBytes('2a864886f70d');
+      ASSERT.equal(ASN1.derToOid(der), '1.2.840.113549');
+    });
+
+    it('should convert INTEGER 0 to DER', function() {
+      ASSERT.equal(ASN1.integerToDer(0).toHex(), '00');
+    });
+
+    it('should convert INTEGER 1 to DER', function() {
+      ASSERT.equal(ASN1.integerToDer(1).toHex(), '01');
+    });
+
+    it('should convert INTEGER 127 to DER', function() {
+      ASSERT.equal(ASN1.integerToDer(127).toHex(), '7f');
+    });
+
+    it('should convert INTEGER 128 to DER', function() {
+      ASSERT.equal(ASN1.integerToDer(128).toHex(), '0080');
+    });
+
+    it('should convert INTEGER 256 to DER', function() {
+      ASSERT.equal(ASN1.integerToDer(256).toHex(), '0100');
+    });
+
+    it('should convert INTEGER -128 to DER', function() {
+      ASSERT.equal(ASN1.integerToDer(-128).toHex(), '80');
+    });
+
+    it('should convert INTEGER -129 to DER', function() {
+      ASSERT.equal(ASN1.integerToDer(-129).toHex(), 'ff7f');
+    });
+
+    it('should convert INTEGER 32768 to DER', function() {
+      ASSERT.equal(ASN1.integerToDer(32768).toHex(), '008000');
+    });
+
+    it('should convert INTEGER -32768 to DER', function() {
+      ASSERT.equal(ASN1.integerToDer(-32768).toHex(), '8000');
+    });
+
+    it('should convert INTEGER -32769 to DER', function() {
+      ASSERT.equal(ASN1.integerToDer(-32769).toHex(), 'ff7fff');
+    });
+
+    it('should convert INTEGER 8388608 to DER', function() {
+      ASSERT.equal(ASN1.integerToDer(8388608).toHex(), '00800000');
+    });
+
+    it('should convert INTEGER -8388608 to DER', function() {
+      ASSERT.equal(ASN1.integerToDer(-8388608).toHex(), '800000');
+    });
+
+    it('should convert INTEGER -8388609 to DER', function() {
+      ASSERT.equal(ASN1.integerToDer(-8388609).toHex(), 'ff7fffff');
+    });
+
+    it('should convert INTEGER 2147483647 to DER', function() {
+      ASSERT.equal(ASN1.integerToDer(2147483647).toHex(), '7fffffff');
+    });
+
+    it('should convert INTEGER -2147483648 to DER', function() {
+      ASSERT.equal(ASN1.integerToDer(-2147483648).toHex(), '80000000');
+    });
+
+    it('should convert INTEGER 0 from DER', function() {
+      var der = UTIL.hexToBytes('00');
+      ASSERT.equal(ASN1.derToInteger(der), 0);
+    });
+
+    it('should convert INTEGER 1 from DER', function() {
+      var der = UTIL.hexToBytes('01');
+      ASSERT.equal(ASN1.derToInteger(der), 1);
+    });
+
+    it('should convert INTEGER 127 from DER', function() {
+      var der = UTIL.hexToBytes('7f');
+      ASSERT.equal(ASN1.derToInteger(der), 127);
+    });
+
+    it('should convert INTEGER 128 from DER', function() {
+      var der = UTIL.hexToBytes('0080');
+      ASSERT.equal(ASN1.derToInteger(der), 128);
+    });
+
+    it('should convert INTEGER 256 from DER', function() {
+      var der = UTIL.hexToBytes('0100');
+      ASSERT.equal(ASN1.derToInteger(der), 256);
+    });
+
+    it('should convert INTEGER -128 from DER', function() {
+      var der = UTIL.hexToBytes('80');
+      ASSERT.equal(ASN1.derToInteger(der), -128);
+    });
+
+    it('should convert INTEGER -129 from DER', function() {
+      var der = UTIL.hexToBytes('ff7f');
+      ASSERT.equal(ASN1.derToInteger(der), -129);
+    });
+
+    it('should convert INTEGER 32768 from DER', function() {
+      var der = UTIL.hexToBytes('008000');
+      ASSERT.equal(ASN1.derToInteger(der), 32768);
+    });
+
+    it('should convert INTEGER -32768 from DER', function() {
+      var der = UTIL.hexToBytes('8000');
+      ASSERT.equal(ASN1.derToInteger(der), -32768);
+    });
+
+    it('should convert INTEGER -32769 from DER', function() {
+      var der = UTIL.hexToBytes('ff7fff');
+      ASSERT.equal(ASN1.derToInteger(der), -32769);
+    });
+
+    it('should convert INTEGER 8388608 from DER', function() {
+      var der = UTIL.hexToBytes('00800000');
+      ASSERT.equal(ASN1.derToInteger(der), 8388608);
+    });
+
+    it('should convert INTEGER -8388608 from DER', function() {
+      var der = UTIL.hexToBytes('800000');
+      ASSERT.equal(ASN1.derToInteger(der), -8388608);
+    });
+
+    it('should convert INTEGER -8388609 from DER', function() {
+      var der = UTIL.hexToBytes('ff7fffff');
+      ASSERT.equal(ASN1.derToInteger(der), -8388609);
+    });
+
+    it('should convert INTEGER 2147483647 from DER', function() {
+      var der = UTIL.hexToBytes('7fffffff');
+      ASSERT.equal(ASN1.derToInteger(der), 2147483647);
+    });
+
+    it('should convert INTEGER -2147483648 from DER', function() {
+      var der = UTIL.hexToBytes('80000000');
+      ASSERT.equal(ASN1.derToInteger(der), -2147483648);
+    });
+
+    (function() {
+      var tests = [{
+        in: '20110223123400',
+        out: 1298464440000
+      }, {
+        in: '20110223123400.1',
+        out: 1298464440100
+      }, {
+        in: '20110223123400.123',
+        out: 1298464440123
+      }];
+      for(var i = 0; i < tests.length; ++i) {
+        var test = tests[i];
+        it('should convert local generalized time "' + test.in + '" to a Date', function() {
+          var d = ASN1.generalizedTimeToDate(test.in);
+          var localOffset = d.getTimezoneOffset() * 60000;
+          ASSERT.equal(d.getTime(), test.out + localOffset);
+        });
+      }
+    })();
+
+    (function() {
+      var tests = [{
+        in: '20110223123400Z', // Wed Feb 23 12:34:00.000 UTC 2011
+        out: 1298464440000
+      }, {
+        in: '20110223123400.1Z', // Wed Feb 23 12:34:00.100 UTC 2011
+        out: 1298464440100
+      }, {
+        in: '20110223123400.123Z', // Wed Feb 23 12:34:00.123 UTC 2011
+        out: 1298464440123
+      }, {
+        in: '20110223123400+0200', // Wed Feb 23 10:34:00.000 UTC 2011
+        out: 1298457240000
+      }, {
+        in: '20110223123400.1+0200', // Wed Feb 23 10:34:00.100 UTC 2011
+        out: 1298457240100
+      }, {
+        in: '20110223123400.123+0200', // Wed Feb 23 10:34:00.123 UTC 2011
+        out: 1298457240123
+      }, {
+        in: '20110223123400-0200', // Wed Feb 23 14:34:00.000 UTC 2011
+        out: 1298471640000
+      }, {
+        in: '20110223123400.1-0200', // Wed Feb 23 14:34:00.100 UTC 2011
+        out: 1298471640100
+      }, {
+        in: '20110223123400.123-0200', // Wed Feb 23 14:34:00.123 UTC 2011
+        out: 1298471640123
+      }];
+      for(var i = 0; i < tests.length; ++i) {
+        var test = tests[i];
+        it('should convert utc generalized time "' + test.in + '" to a Date', function() {
+          var d = ASN1.generalizedTimeToDate(test.in);
+          ASSERT.equal(d.getTime(), test.out);
+        });
+      }
+    })();
+
+    (function() {
+      var tests = [{
+        in: '1102231234Z', // Wed Feb 23 12:34:00 UTC 2011
+        out: 1298464440000
+      }, {
+        in: '1102231234+0200', // Wed Feb 23 10:34:00 UTC 2011
+        out: 1298457240000
+      }, {
+        in: '1102231234-0200', // Wed Feb 23 14:34:00 UTC 2011
+        out: 1298471640000
+      }, {
+        in: '110223123456Z', // Wed Feb 23 12:34:56 UTC 2011
+        out: 1298464496000
+      }, {
+        in: '110223123456+0200', // Wed Feb 23 10:34:56 UTC 2011
+        out: 1298457296000
+      }, {
+        in: '110223123456-0200', // Wed Feb 23 14:34:56 UTC 2011
+        out: 1298471696000
+      }];
+      for(var i = 0; i < tests.length; ++i) {
+        var test = tests[i];
+        it('should convert utc time "' + test.in + '" to a Date', function() {
+          var d = ASN1.utcTimeToDate(test.in);
+          ASSERT.equal(d.getTime(), test.out);
+        });
+      }
+    })();
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/asn1',
+    'forge/util'
+  ], function(ASN1, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      ASN1(),
+      UTIL()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/asn1')(),
+    require('../../js/util')());
+}
+
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/browser.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/browser.js
new file mode 100644
index 0000000..a96b2d6
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/browser.js
@@ -0,0 +1,41 @@
+var server = require('../server');
+var grunt = require('grunt');
+
+describe('browser', function() {
+  it('should run tests', function(done) {
+    this.timeout(60 * 1000 * 5);
+
+    return server.main(function(err, info) {
+      if(err) {
+        return done(err);
+      }
+
+      grunt.initConfig({
+        mocha: {
+          all: {
+            options: {
+              reporter: 'List',
+              urls: ['http://localhost:' + info.port + '/index.html']
+            }
+          }
+        }
+      });
+
+      grunt.loadNpmTasks('grunt-mocha');
+
+      grunt.registerInitTask('default', function() {
+        grunt.task.run(['mocha']);
+      });
+      grunt.tasks(['default'], {
+        //debug: true
+      }, function() {
+        if(err) {
+          return done(err);
+        }
+        // finish immediately
+        done(null);
+        return info.server.close();
+      });
+    });
+  });
+});
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/csr.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/csr.js
new file mode 100644
index 0000000..340c09f
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/csr.js
@@ -0,0 +1,148 @@
+(function() {
+
+function Tests(ASSERT, PKI) {
+  var _pem = {
+    privateKey: '-----BEGIN RSA PRIVATE KEY-----\r\n' +
+      'MIICXQIBAAKBgQDL0EugUiNGMWscLAVM0VoMdhDZEJOqdsUMpx9U0YZI7szokJqQ\r\n' +
+      'NIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMGTkP3VF29vXBo+dLq5e+8VyAy\r\n' +
+      'Q3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGtvnM+z0MYDdKo80efzwIDAQAB\r\n' +
+      'AoGAIzkGONi5G+JifmXlLJdplom486p3upf4Ce2/7mqfaG9MnkyPSairKD/JXvfh\r\n' +
+      'NNWkkN8DKKDKBcVVElPgORYT0qwrWc7ueLBMUCbRXb1ZyfEulimG0R3kjUh7NYau\r\n' +
+      'DaIkVgfykXGSQMZx8FoaT6L080zd+0emKDDYRrb+/kgJNJECQQDoUZoiC2K/DWNY\r\n' +
+      'h3/ppZ0ane2y4SBmJUHJVMPQ2CEgxsrJTxet668ckNCKaOP/3VFPoWC41f17DvKq\r\n' +
+      'noYINNntAkEA4JbZBZBVUrQFhHlrpXT4jzqtO2RlKZzEq8qmFZfEErxOT1WMyyCi\r\n' +
+      'lAQ5gUKardo1Kf0omC8Xq/uO9ZYdED55KwJBALs6cJ65UFaq4oLJiQPzLd7yokuE\r\n' +
+      'dcj8g71PLBTW6jPxIiMFNA89nz3FU9wIVp+xbMNhSoMMKqIPVPC+m0Rn260CQQDA\r\n' +
+      'I83fWK/mZWUjBM33a68KumRiH238v8XyQxj7+C8i6D8G2GXvkigFAehAkb7LZZd+\r\n' +
+      'KLuGFyPlWv3fVWHf99KpAkBQFKk3MRMl6IGJZUEFQe4l5whm8LkGU4acSqv9B3xt\r\n' +
+      'qROkCrsFrMPqjuuzEmyHoQZ64r2PLJg7FOuyhBnQUOt4\r\n' +
+      '-----END RSA PRIVATE KEY-----\r\n',
+    publicKey: '-----BEGIN PUBLIC KEY-----\r\n' +
+      'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDL0EugUiNGMWscLAVM0VoMdhDZ\r\n' +
+      'EJOqdsUMpx9U0YZI7szokJqQNIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMG\r\n' +
+      'TkP3VF29vXBo+dLq5e+8VyAyQ3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGt\r\n' +
+      'vnM+z0MYDdKo80efzwIDAQAB\r\n' +
+      '-----END PUBLIC KEY-----\r\n'
+  };
+
+  describe('csr', function() {
+    it('should generate a certification request', function() {
+      var keys = {
+        privateKey: PKI.privateKeyFromPem(_pem.privateKey),
+        publicKey: PKI.publicKeyFromPem(_pem.publicKey)
+      };
+      var csr = PKI.createCertificationRequest();
+      csr.publicKey = keys.publicKey;
+      csr.setSubject([{
+        name: 'commonName',
+        value: 'example.org'
+      }, {
+        name: 'countryName',
+        value: 'US'
+      }, {
+        shortName: 'ST',
+        value: 'Virginia'
+      }, {
+        name: 'localityName',
+        value: 'Blacksburg'
+      }, {
+        name: 'organizationName',
+        value: 'Test'
+      }, {
+        shortName: 'OU',
+        value: 'Test'
+      }]);
+      // add optional attributes
+      csr.setAttributes([{
+        name: 'challengePassword',
+        value: 'password'
+      }, {
+        name: 'unstructuredName',
+        value: 'My company'
+      }, {
+        name: 'extensionRequest',
+        extensions: [{
+          name: 'subjectAltName',
+          altNames: [{
+            // type 2 is DNS
+            type: 2,
+            value: 'test.domain.com'
+          }, {
+            type: 2,
+            value: 'other.domain.com'
+          }, {
+            type: 2,
+            value: 'www.domain.net'
+          }]
+        }]
+      }]);
+
+      // sign certification request
+      csr.sign(keys.privateKey);
+
+      var pem = PKI.certificationRequestToPem(csr);
+      csr = PKI.certificationRequestFromPem(pem);
+      ASSERT.ok(csr.getAttribute({name: 'extensionRequest'}));
+      ASSERT.equal(csr.getAttribute({name: 'extensionRequest'}).extensions[0].name, 'subjectAltName');
+      ASSERT.deepEqual(csr.getAttribute({name: 'extensionRequest'}).extensions[0].altNames, [{
+        // type 2 is DNS
+        type: 2,
+        value: 'test.domain.com'
+      }, {
+        type: 2,
+        value: 'other.domain.com'
+      }, {
+        type: 2,
+        value: 'www.domain.net'
+      }]);
+      ASSERT.ok(csr.verify());
+    });
+
+    it('should load an OpenSSL-generated certification request', function() {
+      var pem = '-----BEGIN CERTIFICATE REQUEST-----\r\n' +
+        'MIICdTCCAV0CAQAwMDEVMBMGA1UEAwwMTXlDb21tb25OYW1lMRcwFQYDVQQKDA5N\r\n' +
+        'eU9yZ2FuaXphdGlvbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKRU\r\n' +
+        'zrAMbiiSjAYYl3PWsOrNwY0VtemgRZc0t7+3FlWp1e8uIA3KxZFZY875wo0QOvD+\r\n' +
+        'AdNv5+YnokgzOi83F3T4yewBSR0TiO3Pa4tL4C7CzWnhYliC/owk5bHCV0HLkYUW\r\n' +
+        'F6z7Lx3HyhoxlKmrHySSPPZRLKp7KcwxbjFc2EfhQV21I73Z1mCG6MEp7cN2qBbQ\r\n' +
+        'PyOMNjAUibOWs4JJEdUjWhm86EZm9+qfgpL5tlpZCe+kXySrKTp56mMsfSOQvlol\r\n' +
+        'pRO8pP9AUjaEqRikCZ745I/9W7dHNPUoyxkWV5jRDwcT7s652+L6oxtoqVOXpg28\r\n' +
+        'uAL0kUZQMa8wkYUKZiMCAwEAAaAAMA0GCSqGSIb3DQEBBQUAA4IBAQCXQH+ut6tr\r\n' +
+        'Z/FdIDOljrc7uh8XpFRKS3GqC/PJsEwrV7d3CX5HuWPTuPc9FU5FQ88w6evXEA0o\r\n' +
+        'ijxHuydeXmdjpy433vXWo1TaRSXh1WaBMG5pW/SlGZK9/Hr1P0v7KN/KCY5nXxoQ\r\n' +
+        'k3Ndg9HzGrYnRoJVXzvdQeBGwCoJFk4FH+Rxa/F03VTUU5nwx66TsL9JUp9pnbI7\r\n' +
+        'MR6DIA97LnTmut8Xp0Uurw+zsS5rif9iv0BKHd7eGpNNGl0RXu8E5dbT0zD90TSa\r\n' +
+        'P5WjxjvY+Udg8XZU+UwT3kcyTEFpiQdkzTIKXg0dFurfUE9XG/9aic9oMZ/IBZz9\r\n' +
+        'a535a7e9RkbJ\r\n' +
+        '-----END CERTIFICATE REQUEST-----\r\n';
+
+      var csr = PKI.certificationRequestFromPem(pem);
+      ASSERT.equal(csr.subject.getField('CN').value, 'MyCommonName');
+      ASSERT.equal(csr.subject.getField('O').value, 'MyOrganization');
+      ASSERT.equal(csr.signatureOid, PKI.oids.sha1WithRSAEncryption);
+      ASSERT.equal(csr.publicKey.e.toString(16), '10001');
+      ASSERT.equal(csr.publicKey.n.toString(16).toUpperCase(), 'A454CEB00C6E28928C06189773D6B0EACDC18D15B5E9A0459734B7BFB71655A9D5EF2E200DCAC5915963CEF9C28D103AF0FE01D36FE7E627A248333A2F371774F8C9EC01491D1388EDCF6B8B4BE02EC2CD69E1625882FE8C24E5B1C25741CB91851617ACFB2F1DC7CA1A3194A9AB1F24923CF6512CAA7B29CC316E315CD847E1415DB523BDD9D66086E8C129EDC376A816D03F238C36301489B396B3824911D5235A19BCE84666F7EA9F8292F9B65A5909EFA45F24AB293A79EA632C7D2390BE5A25A513BCA4FF40523684A918A4099EF8E48FFD5BB74734F528CB19165798D10F0713EECEB9DBE2FAA31B68A95397A60DBCB802F491465031AF3091850A6623');
+      ASSERT.ok(csr.verify());
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/pki'
+  ], function(PKI) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      PKI()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/pki')());
+}
+
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/des.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/des.js
new file mode 100644
index 0000000..8be2c68
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/des.js
@@ -0,0 +1,155 @@
+(function() {
+
+function Tests(ASSERT, CIPHER, DES, UTIL) {
+  describe('des', function() {
+    // OpenSSL equivalent:
+    // openssl enc -des-ecb -K a1c06b381adf3651 -nosalt
+    it('should des-ecb encrypt: foobar', function() {
+      var key = new UTIL.createBuffer(
+        UTIL.hexToBytes('a1c06b381adf3651'));
+
+      var cipher = CIPHER.createCipher('DES-ECB', key);
+      cipher.start();
+      cipher.update(UTIL.createBuffer('foobar'));
+      cipher.finish();
+      ASSERT.equal(cipher.output.toHex(), 'b705ffcf3dff06b3');
+    });
+
+    // OpenSSL equivalent:
+    // openssl enc -d -des-ecb -K a1c06b381adf3651 -nosalt
+    it('should des-ecb decrypt: b705ffcf3dff06b3', function() {
+      var key = new UTIL.createBuffer(
+        UTIL.hexToBytes('a1c06b381adf3651'));
+
+      var decipher = CIPHER.createDecipher('DES-ECB', key);
+      decipher.start();
+      decipher.update(UTIL.createBuffer(UTIL.hexToBytes('b705ffcf3dff06b3')));
+      decipher.finish();
+      ASSERT.equal(decipher.output.getBytes(), 'foobar');
+    });
+
+    // OpenSSL equivalent:
+    // openssl enc -des -K a1c06b381adf3651 -iv 818bcf76efc59662 -nosalt
+    it('should des-cbc encrypt: foobar', function() {
+      var key = new UTIL.createBuffer(
+        UTIL.hexToBytes('a1c06b381adf3651'));
+      var iv = new UTIL.createBuffer(
+        UTIL.hexToBytes('818bcf76efc59662'));
+
+      var cipher = CIPHER.createCipher('DES-CBC', key);
+      cipher.start({iv: iv});
+      cipher.update(UTIL.createBuffer('foobar'));
+      cipher.finish();
+      ASSERT.equal(cipher.output.toHex(), '3261e5839a990454');
+    });
+
+    // OpenSSL equivalent:
+    // openssl enc -d -des -K a1c06b381adf3651 -iv 818bcf76efc59662 -nosalt
+    it('should des-cbc decrypt: 3261e5839a990454', function() {
+      var key = new UTIL.createBuffer(
+        UTIL.hexToBytes('a1c06b381adf3651'));
+      var iv = new UTIL.createBuffer(
+        UTIL.hexToBytes('818bcf76efc59662'));
+
+      var decipher = CIPHER.createDecipher('DES-CBC', key);
+      decipher.start({iv: iv});
+      decipher.update(UTIL.createBuffer(UTIL.hexToBytes('3261e5839a990454')));
+      decipher.finish();
+      ASSERT.equal(decipher.output.getBytes(), 'foobar');
+    });
+
+    // OpenSSL equivalent:
+    // openssl enc -des-ede3 -K a1c06b381adf36517e84575552777779da5e3d9f994b05b5 -nosalt
+    it('should 3des-ecb encrypt: foobar', function() {
+      var key = new UTIL.createBuffer(
+        UTIL.hexToBytes('a1c06b381adf36517e84575552777779da5e3d9f994b05b5'));
+
+      var cipher = CIPHER.createCipher('3DES-ECB', key);
+      cipher.start();
+      cipher.update(UTIL.createBuffer('foobar'));
+      cipher.finish();
+      ASSERT.equal(cipher.output.toHex(), 'fce8b1ee8c6440d1');
+    });
+
+    // OpenSSL equivalent:
+    // openssl enc -d -des-ede3 -K a1c06b381adf36517e84575552777779da5e3d9f994b05b5 -nosalt
+    it('should 3des-ecb decrypt: fce8b1ee8c6440d1', function() {
+      var key = new UTIL.createBuffer(
+        UTIL.hexToBytes('a1c06b381adf36517e84575552777779da5e3d9f994b05b5'));
+
+      var decipher = CIPHER.createDecipher('3DES-ECB', key);
+      decipher.start();
+      decipher.update(UTIL.createBuffer(UTIL.hexToBytes('fce8b1ee8c6440d1')));
+      decipher.finish();
+      ASSERT.equal(decipher.output.getBytes(), 'foobar');
+    });
+
+    // OpenSSL equivalent:
+    // openssl enc -des3 -K a1c06b381adf36517e84575552777779da5e3d9f994b05b5 -iv 818bcf76efc59662 -nosalt
+    it('should 3des-cbc encrypt "foobar", restart, and encrypt "foobar,,"', function() {
+      var key = new UTIL.createBuffer(
+        UTIL.hexToBytes('a1c06b381adf36517e84575552777779da5e3d9f994b05b5'));
+      var iv = new UTIL.createBuffer(
+        UTIL.hexToBytes('818bcf76efc59662'));
+
+      var cipher = CIPHER.createCipher('3DES-CBC', key);
+      cipher.start({iv: iv.copy()});
+      cipher.update(UTIL.createBuffer('foobar'));
+      cipher.finish();
+      ASSERT.equal(cipher.output.toHex(), '209225f7687ca0b2');
+
+      cipher.start({iv: iv.copy()});
+      cipher.update(UTIL.createBuffer('foobar,,'));
+      cipher.finish();
+      ASSERT.equal(cipher.output.toHex(), '57156174c48dfc37293831bf192a6742');
+    });
+
+    // OpenSSL equivalent:
+    // openssl enc -d -des3 -K a1c06b381adf36517e84575552777779da5e3d9f994b05b5 -iv 818bcf76efc59662 -nosalt
+    it('should 3des-cbc decrypt "209225f7687ca0b2", restart, and decrypt "57156174c48dfc37293831bf192a6742,,"', function() {
+      var key = new UTIL.createBuffer(
+        UTIL.hexToBytes('a1c06b381adf36517e84575552777779da5e3d9f994b05b5'));
+      var iv = new UTIL.createBuffer(
+        UTIL.hexToBytes('818bcf76efc59662'));
+
+      var decipher = CIPHER.createDecipher('3DES-CBC', key);
+      decipher.start({iv: iv.copy()});
+      decipher.update(UTIL.createBuffer(UTIL.hexToBytes('209225f7687ca0b2')));
+      decipher.finish();
+      ASSERT.equal(decipher.output.getBytes(), 'foobar');
+
+      decipher.start({iv: iv.copy()});
+      decipher.update(
+        UTIL.createBuffer(UTIL.hexToBytes('57156174c48dfc37293831bf192a6742')));
+      decipher.finish();
+      ASSERT.equal(decipher.output.getBytes(), 'foobar,,');
+    });
+  });
+}
+
+// check for AMD
+var forge = {};
+if(typeof define === 'function') {
+  define([
+    'forge/cipher',
+    'forge/des',
+    'forge/util'
+  ], function(CIPHER, DES, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      CIPHER(forge),
+      DES(forge),
+      UTIL(forge)
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/cipher')(forge),
+    require('../../js/des')(forge),
+    require('../../js/util')(forge));
+}
+
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/hmac.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/hmac.js
new file mode 100644
index 0000000..404b36b
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/hmac.js
@@ -0,0 +1,85 @@
+(function() {
+
+function Tests(ASSERT, HMAC, UTIL) {
+  describe('hmac', function() {
+    it('should md5 hash "Hi There", 16-byte key', function() {
+      var key = UTIL.hexToBytes('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b');
+      var hmac = HMAC.create();
+      hmac.start('MD5', key);
+      hmac.update('Hi There');
+      ASSERT.equal(hmac.digest().toHex(), '9294727a3638bb1c13f48ef8158bfc9d');
+    });
+
+    it('should md5 hash "what do ya want for nothing?", "Jefe" key', function() {
+      var hmac = HMAC.create();
+      hmac.start('MD5', 'Jefe');
+      hmac.update('what do ya want for nothing?');
+      ASSERT.equal(hmac.digest().toHex(), '750c783e6ab0b503eaa86e310a5db738');
+    });
+
+    it('should md5 hash "Test Using Larger Than Block-Size Key - Hash Key First", 80-byte key', function() {
+      var key = UTIL.hexToBytes(
+        'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
+        'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
+        'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
+        'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
+      var hmac = HMAC.create();
+      hmac.start('MD5', key);
+      hmac.update('Test Using Larger Than Block-Size Key - Hash Key First');
+      ASSERT.equal(hmac.digest().toHex(), '6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd');
+    });
+
+    it('should sha1 hash "Hi There", 20-byte key', function() {
+      var key = UTIL.hexToBytes('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b');
+      var hmac = HMAC.create();
+      hmac.start('SHA1', key);
+      hmac.update('Hi There');
+      ASSERT.equal(
+        hmac.digest().toHex(), 'b617318655057264e28bc0b6fb378c8ef146be00');
+    });
+
+    it('should sha1 hash "what do ya want for nothing?", "Jefe" key', function() {
+      var hmac = HMAC.create();
+      hmac.start('SHA1', 'Jefe');
+      hmac.update('what do ya want for nothing?');
+      ASSERT.equal(
+        hmac.digest().toHex(), 'effcdf6ae5eb2fa2d27416d5f184df9c259a7c79');
+    });
+
+    it('should sha1 hash "Test Using Larger Than Block-Size Key - Hash Key First", 80-byte key', function() {
+      var key = UTIL.hexToBytes(
+        'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
+        'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
+        'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
+        'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
+      var hmac = HMAC.create();
+      hmac.start('SHA1', key);
+      hmac.update('Test Using Larger Than Block-Size Key - Hash Key First');
+      ASSERT.equal(
+        hmac.digest().toHex(), 'aa4ae5e15272d00e95705637ce8a3b55ed402112');
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/hmac',
+    'forge/util'
+  ], function(HMAC, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      HMAC(),
+      UTIL()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/hmac')(),
+    require('../../js/util')());
+}
+
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/kem.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/kem.js
new file mode 100644
index 0000000..0415abe
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/kem.js
@@ -0,0 +1,198 @@
+(function() {
+
+function Tests(ASSERT, KEM, MD, RSA, UTIL, JSBN, RANDOM) {
+
+  function FixedSecureRandom(str) {
+    var bytes = UTIL.hexToBytes(str);
+    this.getBytesSync = function(count) {
+      // prepend zeros
+      return UTIL.fillString(String.fromCharCode(0), bytes.length - count) +
+        bytes;
+    };
+  }
+
+  describe('kem', function() {
+    it('should generate and encrypt a symmetric key and decrypt it 10x', function() {
+      for(var i = 0; i < 10; ++i) {
+        var kdf = new KEM.kdf1(MD.sha256.create());
+        var kem = KEM.rsa.create(kdf);
+
+        var pair = RSA.generateKeyPair(512);
+
+        var result = kem.encrypt(pair.publicKey, 256);
+        var key1 = result.key;
+        var key2 = kem.decrypt(pair.privateKey, result.encapsulation, 256);
+
+        ASSERT.equal(UTIL.bytesToHex(key1), UTIL.bytesToHex(key2));
+      }
+    });
+  });
+
+  /**
+   * According to section "C.6 Test vectors for RSA-KEM" from ISO-18033-2 final
+   * draft.
+   */
+  describe('C.6 Test vectors for RSA-KEM from ISO-18033-2 final', function() {
+    it('should pass test vector C.6.1', function() {
+      var n = '5888113332502691251761936431009284884966640757179802337490546478326238537107326596800820237597139824869184990638749556269785797065508097452399642780486933';
+      var e = '65537';
+      var d = '3202313555859948186315374524474173995679783580392140237044349728046479396037520308981353808895461806395564474639124525446044708705259675840210989546479265';
+
+      var C0 = '4603e5324cab9cef8365c817052d954d44447b1667099edc69942d32cd594e4ffcf268ae3836e2c35744aaa53ae201fe499806b67dedaa26bf72ecbd117a6fc0';
+      var K = '5f8de105b5e96b2e490ddecbd147dd1def7e3b8e0e6a26eb7b956ccb8b3bdc1ca975bc57c3989e8fbad31a224655d800c46954840ff32052cdf0d640562bdfadfa263cfccf3c52b29f2af4a1869959bc77f854cf15bd7a25192985a842dbff8e13efee5b7e7e55bbe4d389647c686a9a9ab3fb889b2d7767d3837eea4e0a2f04';
+
+      var kdf = new KEM.kdf1(MD.sha1.create());
+      var rnd = new FixedSecureRandom('032e45326fa859a72ec235acff929b15d1372e30b207255f0611b8f785d764374152e0ac009e509e7ba30cd2f1778e113b64e135cf4e2292c75efe5288edfda4');
+      var kem = KEM.rsa.create(kdf, {prng: rnd});
+
+      var rsaPublicKey = RSA.setPublicKey(
+        new JSBN.BigInteger(n), new JSBN.BigInteger(e));
+      var rsaPrivateKey = RSA.setPrivateKey(
+        new JSBN.BigInteger(n), null, new JSBN.BigInteger(d));
+
+      var result = kem.encrypt(rsaPublicKey, 128);
+      ASSERT.equal(UTIL.bytesToHex(result.encapsulation), C0);
+      ASSERT.equal(UTIL.bytesToHex(result.key), K);
+
+      var decryptedKey = kem.decrypt(rsaPrivateKey, result.encapsulation, 128);
+      ASSERT.equal(UTIL.bytesToHex(decryptedKey), K);
+    });
+
+    it('should pass test vector C.6.2', function() {
+      var n = '5888113332502691251761936431009284884966640757179802337490546478326238537107326596800820237597139824869184990638749556269785797065508097452399642780486933';
+      var e = '65537';
+      var d = '3202313555859948186315374524474173995679783580392140237044349728046479396037520308981353808895461806395564474639124525446044708705259675840210989546479265';
+
+      var C0 = '4603e5324cab9cef8365c817052d954d44447b1667099edc69942d32cd594e4ffcf268ae3836e2c35744aaa53ae201fe499806b67dedaa26bf72ecbd117a6fc0';
+      var K = '0e6a26eb7b956ccb8b3bdc1ca975bc57c3989e8fbad31a224655d800c46954840ff32052cdf0d640562bdfadfa263cfccf3c52b29f2af4a1869959bc77f854cf15bd7a25192985a842dbff8e13efee5b7e7e55bbe4d389647c686a9a9ab3fb889b2d7767d3837eea4e0a2f04b53ca8f50fb31225c1be2d0126c8c7a4753b0807';
+
+      var kdf = new KEM.kdf2(MD.sha1.create());
+      var rnd = new FixedSecureRandom('032e45326fa859a72ec235acff929b15d1372e30b207255f0611b8f785d764374152e0ac009e509e7ba30cd2f1778e113b64e135cf4e2292c75efe5288edfda4');
+      var kem = KEM.rsa.create(kdf, {prng: rnd});
+
+      var rsaPublicKey = RSA.setPublicKey(
+        new JSBN.BigInteger(n), new JSBN.BigInteger(e));
+      var rsaPrivateKey = RSA.setPrivateKey(
+        new JSBN.BigInteger(n), null, new JSBN.BigInteger(d));
+
+      var result = kem.encrypt(rsaPublicKey, 128);
+      ASSERT.equal(UTIL.bytesToHex(result.encapsulation), C0);
+      ASSERT.equal(UTIL.bytesToHex(result.key), K);
+
+      var decryptedKey = kem.decrypt(rsaPrivateKey, result.encapsulation, 128);
+      ASSERT.equal(UTIL.bytesToHex(decryptedKey), K);
+    });
+
+    it('should pass test vector C.6.3', function() {
+      var n = '5888113332502691251761936431009284884966640757179802337490546478326238537107326596800820237597139824869184990638749556269785797065508097452399642780486933';
+      var e = '65537';
+      var d = '3202313555859948186315374524474173995679783580392140237044349728046479396037520308981353808895461806395564474639124525446044708705259675840210989546479265';
+
+      var C0 = '4603e5324cab9cef8365c817052d954d44447b1667099edc69942d32cd594e4ffcf268ae3836e2c35744aaa53ae201fe499806b67dedaa26bf72ecbd117a6fc0';
+      var K = '09e2decf2a6e1666c2f6071ff4298305e2643fd510a2403db42a8743cb989de86e668d168cbe604611ac179f819a3d18412e9eb45668f2923c087c12fee0c5a0d2a8aa70185401fbbd99379ec76c663e875a60b4aacb1319fa11c3365a8b79a44669f26fb555c80391847b05eca1cb5cf8c2d531448d33fbaca19f6410ee1fcb';
+
+      var kdf = new KEM.kdf1(MD.sha256.create(), 20);
+      var rnd = new FixedSecureRandom('032e45326fa859a72ec235acff929b15d1372e30b207255f0611b8f785d764374152e0ac009e509e7ba30cd2f1778e113b64e135cf4e2292c75efe5288edfda4');
+      var kem = KEM.rsa.create(kdf, {prng: rnd});
+
+      var rsaPublicKey = RSA.setPublicKey(
+        new JSBN.BigInteger(n), new JSBN.BigInteger(e));
+      var rsaPrivateKey = RSA.setPrivateKey(
+        new JSBN.BigInteger(n), null, new JSBN.BigInteger(d));
+
+      var result = kem.encrypt(rsaPublicKey, 128);
+      ASSERT.equal(UTIL.bytesToHex(result.encapsulation), C0);
+      ASSERT.equal(UTIL.bytesToHex(result.key), K);
+
+      var decryptedKey = kem.decrypt(rsaPrivateKey, result.encapsulation, 128);
+      ASSERT.equal(UTIL.bytesToHex(decryptedKey), K);
+    });
+
+    it('should pass test vector C.6.4', function() {
+      var n = '5888113332502691251761936431009284884966640757179802337490546478326238537107326596800820237597139824869184990638749556269785797065508097452399642780486933';
+      var e = '65537';
+      var d = '3202313555859948186315374524474173995679783580392140237044349728046479396037520308981353808895461806395564474639124525446044708705259675840210989546479265';
+
+      var C0 = '4603e5324cab9cef8365c817052d954d44447b1667099edc69942d32cd594e4ffcf268ae3836e2c35744aaa53ae201fe499806b67dedaa26bf72ecbd117a6fc0';
+      var K = '10a2403db42a8743cb989de86e668d168cbe604611ac179f819a3d18412e9eb45668f2923c087c12fee0c5a0d2a8aa70185401fbbd99379ec76c663e875a60b4aacb1319fa11c3365a8b79a44669f26fb555c80391847b05eca1cb5cf8c2d531448d33fbaca19f6410ee1fcb260892670e0814c348664f6a7248aaf998a3acc6';
+
+      var kdf = new KEM.kdf2(MD.sha256.create(), 20);
+      var rnd = new FixedSecureRandom('00032e45326fa859a72ec235acff929b15d1372e30b207255f0611b8f785d764374152e0ac009e509e7ba30cd2f1778e113b64e135cf4e2292c75efe5288edfda4');
+      var kem = KEM.rsa.create(kdf, {prng: rnd});
+
+      var rsaPublicKey = RSA.setPublicKey(
+        new JSBN.BigInteger(n), new JSBN.BigInteger(e));
+      var rsaPrivateKey = RSA.setPrivateKey(
+        new JSBN.BigInteger(n), null, new JSBN.BigInteger(d));
+
+      var result = kem.encrypt(rsaPublicKey, 128);
+      ASSERT.equal(UTIL.bytesToHex(result.encapsulation), C0);
+      ASSERT.equal(UTIL.bytesToHex(result.key), K);
+
+      var decryptedKey = kem.decrypt(rsaPrivateKey, result.encapsulation, 128);
+      ASSERT.equal(UTIL.bytesToHex(decryptedKey), K);
+    });
+  });
+
+  describe('prepended zeros test', function() {
+    it('should pass when random has leading zeros', function() {
+      var n = '5888113332502691251761936431009284884966640757179802337490546478326238537107326596800820237597139824869184990638749556269785797065508097452399642780486933';
+      var e = '65537';
+      var d = '3202313555859948186315374524474173995679783580392140237044349728046479396037520308981353808895461806395564474639124525446044708705259675840210989546479265';
+
+      var C0 = '5f268a76c1aed04bc195a143d7ee768bee0aad308d16196274a02d9c1a72bbe10cbf718de323fc0135c5f8129f96ac8f504d9623960dc54cd87bddee94f5a0b2';
+      var K = '8bf41e59dc1b83142ee32569a347a94539e48c98347c685a29e3aa8b7a3ea714d68c1a43c4a760c9d4a45149b0ce8b681e98076bdd4393394c7832a7fa71848257772ac38a4e7fbe96e8bb383becbb7242841946e82e35d9ef1667245fc82601e7edf53b897f5ce2b6bce8e1e3212abd5a8a99a0c9b99472e22a313dac396383';
+
+      var kdf = new KEM.kdf1(MD.sha1.create());
+      var rnd = new FixedSecureRandom('000e45326fa859a72ec235acff929b15d1372e30b207255f0611b8f785d764374152e0ac009e509e7ba30cd2f1778e113b64e135cf4e2292c75efe5288edfda4');
+      var kem = KEM.rsa.create(kdf, {prng: rnd});
+
+      var rsaPublicKey = RSA.setPublicKey(
+        new JSBN.BigInteger(n), new JSBN.BigInteger(e));
+      var rsaPrivateKey = RSA.setPrivateKey(
+        new JSBN.BigInteger(n), null, new JSBN.BigInteger(d));
+
+      var result = kem.encrypt(rsaPublicKey, 128);
+      ASSERT.equal(UTIL.bytesToHex(result.encapsulation), C0);
+      ASSERT.equal(UTIL.bytesToHex(result.key), K);
+
+      var decryptedKey = kem.decrypt(rsaPrivateKey, result.encapsulation, 128);
+      ASSERT.equal(UTIL.bytesToHex(decryptedKey), K);
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/kem',
+    'forge/md',
+    'forge/rsa',
+    'forge/util',
+    'forge/jsbn',
+    'forge/random'
+  ], function(KEM, MD, RSA, UTIL, JSBN, RANDOM) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      KEM(),
+      MD(),
+      RSA(),
+      UTIL(),
+      JSBN(),
+      RANDOM()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/kem')(),
+    require('../../js/md')(),
+    require('../../js/rsa')(),
+    require('../../js/util')(),
+    require('../../js/jsbn')(),
+    require('../../js/random')());
+}
+
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/md5.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/md5.js
new file mode 100644
index 0000000..5ab3d58
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/md5.js
@@ -0,0 +1,117 @@
+(function() {
+
+function Tests(ASSERT, MD5, UTIL) {
+  describe('md5', function() {
+    it('should digest the empty string', function() {
+      var md = MD5.create();
+      ASSERT.equal(md.digest().toHex(), 'd41d8cd98f00b204e9800998ecf8427e');
+    });
+
+    it('should digest "abc"', function() {
+      var md = MD5.create();
+      md.update('abc');
+      ASSERT.equal(md.digest().toHex(), '900150983cd24fb0d6963f7d28e17f72');
+    });
+
+    it('should digest "The quick brown fox jumps over the lazy dog"', function() {
+      var md = MD5.create();
+      md.update('The quick brown fox jumps over the lazy dog');
+      ASSERT.equal(md.digest().toHex(), '9e107d9d372bb6826bd81d3542a419d6');
+    });
+
+    it('should digest "c\'\u00e8"', function() {
+      var md = MD5.create();
+      md.update("c\'\u00e8", 'utf8');
+      ASSERT.equal(md.digest().toHex(), '8ef7c2941d78fe89f31e614437c9db59');
+    });
+
+    it('should digest "THIS IS A MESSAGE"', function() {
+      var md = MD5.create();
+      md.start();
+      md.update('THIS IS ');
+      md.update('A MESSAGE');
+      // do twice to check continuing digest
+      ASSERT.equal(md.digest().toHex(), '78eebfd9d42958e3f31244f116ab7bbe');
+      ASSERT.equal(md.digest().toHex(), '78eebfd9d42958e3f31244f116ab7bbe');
+    });
+
+    it('should digest a long message', function() {
+      var input = UTIL.hexToBytes(
+        '0100002903018d32e9c6dc423774c4c39a5a1b78f44cc2cab5f676d39' +
+        'f703d29bfa27dfeb870000002002f01000200004603014c2c1e835d39' +
+        'da71bc0857eb04c2b50fe90dbb2a8477fe7364598d6f0575999c20a6c' +
+        '7248c5174da6d03ac711888f762fc4ed54f7254b32273690de849c843' +
+        '073d002f000b0003d20003cf0003cc308203c8308202b0a0030201020' +
+        '20100300d06092a864886f70d0101050500308186310b300906035504' +
+        '0613025553311d301b060355040a13144469676974616c2042617a616' +
+        '1722c20496e632e31443042060355040b133b4269746d756e6b206c6f' +
+        '63616c686f73742d6f6e6c7920436572746966696361746573202d204' +
+        '17574686f72697a6174696f6e20766961204254503112301006035504' +
+        '0313096c6f63616c686f7374301e170d3130303231343137303931395' +
+        'a170d3230303231333137303931395a308186310b3009060355040613' +
+        '025553311d301b060355040a13144469676974616c2042617a6161722' +
+        'c20496e632e31443042060355040b133b4269746d756e6b206c6f6361' +
+        '6c686f73742d6f6e6c7920436572746966696361746573202d2041757' +
+        '4686f72697a6174696f6e207669612042545031123010060355040313' +
+        '096c6f63616c686f737430820122300d06092a864886f70d010101050' +
+        '00382010f003082010a0282010100dc436f17d6909d8a9d6186ea218e' +
+        'b5c86b848bae02219bd56a71203daf07e81bc19e7e98134136bcb0128' +
+        '81864bf03b3774652ad5eab85dba411a5114ffeac09babce75f313143' +
+        '45512cd87c91318b2e77433270a52185fc16f428c3ca412ad6e9484bc' +
+        '2fb87abb4e8fb71bf0f619e31a42340b35967f06c24a741a31c979c0b' +
+        'b8921a90a47025fbeb8adca576979e70a56830c61170c9647c18c0794' +
+        'd68c0df38f3aac5fc3b530e016ea5659715339f3f3c209cdee9dbe794' +
+        'b5af92530c5754c1d874b78974bfad994e0dfc582275e79feb522f6e4' +
+        'bcc2b2945baedfb0dbdaebb605f9483ff0bea29ecd5f4d6f2769965d1' +
+        'b3e04f8422716042680011ff676f0203010001a33f303d300c0603551' +
+        'd130101ff04023000300e0603551d0f0101ff0404030204f0301d0603' +
+        '551d250416301406082b0601050507030106082b06010505070302300' +
+        'd06092a864886f70d010105050003820101009c4562be3f2d8d8e3880' +
+        '85a697f2f106eaeff4992a43f198fe3dcf15c8229cf1043f061a38204' +
+        'f73d86f4fb6348048cc5279ed719873aa10e3773d92b629c2c3fcce04' +
+        '012c81ba3b4ec451e9644ec5191078402d845e05d02c7b4d974b45882' +
+        '76e5037aba7ef26a8bddeb21e10698c82f425e767dc401adf722fa73a' +
+        'b78cfa069bd69052d7ca6a75cc9225550e315d71c5f8764362ea4dbc6' +
+        'ecb837a8471043c5a7f826a71af145a053090bd4bccca6a2c552841cd' +
+        'b1908a8352f49283d2e641acdef667c7543af441a16f8294251e2ac37' +
+        '6fa507b53ae418dd038cd20cef1e7bfbf5ae03a7c88d93d843abaabbd' +
+        'c5f3431132f3e559d2dd414c3eda38a210b80e0000001000010201002' +
+        '6a220b7be857402819b78d81080d01a682599bbd00902985cc64edf8e' +
+        '520e4111eb0e1729a14ffa3498ca259cc9ad6fc78fa130d968ebdb78d' +
+        'c0b950c0aa44355f13ba678419185d7e4608fe178ca6b2cef33e41937' +
+        '78d1a70fe4d0dfcb110be4bbb4dbaa712177655728f914ab4c0f6c4ae' +
+        'f79a46b3d996c82b2ebe9ed1748eb5cace7dc44fb67e73f452a047f2e' +
+        'd199b3d50d5db960acf03244dc8efa4fc129faf8b65f9e52e62b55447' +
+        '22bd17d2358e817a777618a4265a3db277fc04851a82a91fe6cdcb812' +
+        '7f156e0b4a5d1f54ce2742eb70c895f5f8b85f5febe69bc73e891f928' +
+        '0826860a0c2ef94c7935e6215c3c4cd6b0e43e80cca396d913d36be');
+
+      var md = MD5.create();
+      md.update(input);
+      ASSERT.equal(md.digest().toHex(), 'd15a2da0e92c3da55dc573f885b6e653');
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/md5',
+    'forge/util'
+  ], function(MD5, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      MD5(),
+      UTIL()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/md5')(),
+    require('../../js/util')());
+}
+
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/mgf1.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/mgf1.js
new file mode 100644
index 0000000..6c54ff1
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/mgf1.js
@@ -0,0 +1,39 @@
+(function() {
+
+function Tests(ASSERT, MGF, MD, UTIL) {
+  describe('mgf1', function() {
+    it('should digest the empty string', function() {
+      var seed = UTIL.hexToBytes('032e45326fa859a72ec235acff929b15d1372e30b207255f0611b8f785d764374152e0ac009e509e7ba30cd2f1778e113b64e135cf4e2292c75efe5288edfda4');
+      var expect = UTIL.hexToBytes('5f8de105b5e96b2e490ddecbd147dd1def7e3b8e0e6a26eb7b956ccb8b3bdc1ca975bc57c3989e8fbad31a224655d800c46954840ff32052cdf0d640562bdfadfa263cfccf3c52b29f2af4a1869959bc77f854cf15bd7a25192985a842dbff8e13efee5b7e7e55bbe4d389647c686a9a9ab3fb889b2d7767d3837eea4e0a2f04');
+      var mgf = MGF.mgf1.create(MD.sha1.create());
+      var result = mgf.generate(seed, expect.length);
+      ASSERT.equal(result, expect);
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/mgf',
+    'forge/md',
+    'forge/util'
+  ], function(MGF, MD, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      MGF(),
+      MD(),
+      UTIL()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/mgf')(),
+    require('../../js/md')(),
+    require('../../js/util')());
+}
+
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/pbkdf2.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/pbkdf2.js
new file mode 100644
index 0000000..0b53e27
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/pbkdf2.js
@@ -0,0 +1,123 @@
+(function() {
+
+function Tests(ASSERT, PBKDF2, MD, UTIL) {
+  describe('pbkdf2', function() {
+    it('should derive a password with hmac-sha-1 c=1', function() {
+      var dkHex = UTIL.bytesToHex(PBKDF2('password', 'salt', 1, 20));
+      ASSERT.equal(dkHex, '0c60c80f961f0e71f3a9b524af6012062fe037a6');
+    });
+
+    it('should derive a password with hmac-sha-1 c=2', function() {
+      var dkHex = UTIL.bytesToHex(PBKDF2('password', 'salt', 2, 20));
+      ASSERT.equal(dkHex, 'ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957');
+    });
+
+    it('should derive a password with hmac-sha-1 c=5 keylen=8', function() {
+      var salt = UTIL.hexToBytes('1234567878563412');
+      var dkHex = UTIL.bytesToHex(PBKDF2('password', salt, 5, 8));
+      ASSERT.equal(dkHex, 'd1daa78615f287e6');
+    });
+
+    it('should derive a password with hmac-sha-1 c=4096', function() {
+      // Note: might be too slow on old browsers
+      var dkHex = UTIL.bytesToHex(PBKDF2('password', 'salt', 4096, 20));
+      ASSERT.equal(dkHex, '4b007901b765489abead49d926f721d065a429c1');
+    });
+
+    /*
+    it('should derive a password with hmac-sha-1 c=16777216', function() {
+      // Note: too slow
+      var dkHex = UTIL.bytesToHex(PBKDF2('password', 'salt', 16777216, 20));
+      ASSERT.equal(dkHex, 'eefe3d61cd4da4e4e9945b3d6ba2158c2634e984');
+    });*/
+
+    it('should derive a password with hmac-sha-256 c=1000', function() {
+      // Note: might be too slow on old browsers
+      var salt = '4bcda0d1c689fe465c5b8a817f0ddf3d';
+      var md = MD.sha256.create();
+      var dkHex = UTIL.bytesToHex(PBKDF2('password', salt, 1000, 48, md));
+      ASSERT.equal(dkHex, '9da8a5f4ae605f35e82e5beac5f362df15c4255d88f738d641466a4107f9970238e768e72af29ac89a1b16ff277b31d2');
+    });
+
+    it('should asynchronously derive a password with hmac-sha-1 c=1', function(done) {
+      PBKDF2('password', 'salt', 1, 20, function(err, dk) {
+        var dkHex = UTIL.bytesToHex(dk);
+        ASSERT.equal(dkHex, '0c60c80f961f0e71f3a9b524af6012062fe037a6');
+        done();
+      });
+    });
+
+    it('should asynchronously derive a password with hmac-sha-1 c=2', function(done) {
+      PBKDF2('password', 'salt', 2, 20, function(err, dk) {
+        var dkHex = UTIL.bytesToHex(dk);
+        ASSERT.equal(dkHex, 'ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957');
+        done();
+      });
+    });
+
+    it('should asynchronously derive a password with hmac-sha-1 c=5 keylen=8', function(done) {
+      var salt = UTIL.hexToBytes('1234567878563412');
+      PBKDF2('password', salt, 5, 8, function(err, dk) {
+        var dkHex = UTIL.bytesToHex(dk);
+        ASSERT.equal(dkHex, 'd1daa78615f287e6');
+        done();
+      });
+    });
+
+    it('should asynchronously derive a password with hmac-sha-1 c=4096', function(done) {
+      // Note: might be too slow on old browsers
+      PBKDF2('password', 'salt', 4096, 20, function(err, dk) {
+        var dkHex = UTIL.bytesToHex(dk);
+        ASSERT.equal(dkHex, '4b007901b765489abead49d926f721d065a429c1');
+        done();
+      });
+    });
+
+    /*
+    it('should asynchronously derive a password with hmac-sha-1 c=16777216', function(done) {
+      // Note: too slow
+      PBKDF2('password', 'salt', 16777216, 20, function(err, dk) {
+        var dkHex = UTIL.bytesToHex(dk);
+        ASSERT.equal(dkHex, 'eefe3d61cd4da4e4e9945b3d6ba2158c2634e984');
+        done();
+      });
+    });*/
+
+    it('should asynchronously derive a password with hmac-sha-256 c=1000', function(done) {
+      // Note: might be too slow on old browsers
+      var salt = '4bcda0d1c689fe465c5b8a817f0ddf3d';
+      var md = MD.sha256.create();
+      PBKDF2('password', salt, 1000, 48, md, function(err, dk) {
+        var dkHex = UTIL.bytesToHex(dk);
+        ASSERT.equal(dkHex, '9da8a5f4ae605f35e82e5beac5f362df15c4255d88f738d641466a4107f9970238e768e72af29ac89a1b16ff277b31d2');
+        done();
+      });
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/pbkdf2',
+    'forge/md',
+    'forge/util'
+  ], function(PBKDF2, MD, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      PBKDF2(),
+      MD(),
+      UTIL()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/pbkdf2')(),
+    require('../../js/md')(),
+    require('../../js/util')());
+}
+
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/pem.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/pem.js
new file mode 100644
index 0000000..6b405cb
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/pem.js
@@ -0,0 +1,104 @@
+(function() {
+
+function Tests(ASSERT, PEM) {
+  var _input = '-----BEGIN PRIVACY-ENHANCED MESSAGE-----\r\n' +
+    'Proc-Type: 4,ENCRYPTED\r\n' +
+    'Content-Domain: RFC822\r\n' +
+    'DEK-Info: DES-CBC,F8143EDE5960C597\r\n' +
+    'Originator-ID-Symmetric: linn@zendia.enet.dec.com,,\r\n' +
+    'Recipient-ID-Symmetric: linn@zendia.enet.dec.com,ptf-kmc,3\r\n' +
+    'Key-Info: DES-ECB,RSA-MD2,9FD3AAD2F2691B9A,\r\n' +
+    ' B70665BB9BF7CBCDA60195DB94F727D3\r\n' +
+    'Recipient-ID-Symmetric: pem-dev@tis.com,ptf-kmc,4\r\n' +
+    'Key-Info: DES-ECB,RSA-MD2,161A3F75DC82EF26,\r\n' +
+    ' E2EF532C65CBCFF79F83A2658132DB47\r\n' +
+    '\r\n' +
+    'LLrHB0eJzyhP+/fSStdW8okeEnv47jxe7SJ/iN72ohNcUk2jHEUSoH1nvNSIWL9M\r\n' +
+    '8tEjmF/zxB+bATMtPjCUWbz8Lr9wloXIkjHUlBLpvXR0UrUzYbkNpk0agV2IzUpk\r\n' +
+    'J6UiRRGcDSvzrsoK+oNvqu6z7Xs5Xfz5rDqUcMlK1Z6720dcBWGGsDLpTpSCnpot\r\n' +
+    'dXd/H5LMDWnonNvPCwQUHg==\r\n' +
+    '-----END PRIVACY-ENHANCED MESSAGE-----\r\n' +
+    '-----BEGIN PRIVACY-ENHANCED MESSAGE-----\r\n' +
+    'Proc-Type: 4,ENCRYPTED\r\n' +
+    'Content-Domain: RFC822\r\n' +
+    'DEK-Info: DES-CBC,BFF968AA74691AC1\r\n' +
+    'Originator-Certificate:\r\n' +
+    ' MIIBlTCCAScCAWUwDQYJKoZIhvcNAQECBQAwUTELMAkGA1UEBhMCVVMxIDAeBgNV\r\n' +
+    ' BAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMQ8wDQYDVQQLEwZCZXRhIDExDzAN\r\n' +
+    ' BgNVBAsTBk5PVEFSWTAeFw05MTA5MDQxODM4MTdaFw05MzA5MDMxODM4MTZaMEUx\r\n' +
+    ' CzAJBgNVBAYTAlVTMSAwHgYDVQQKExdSU0EgRGF0YSBTZWN1cml0eSwgSW5jLjEU\r\n' +
+    ' MBIGA1UEAxMLVGVzdCBVc2VyIDEwWTAKBgRVCAEBAgICAANLADBIAkEAwHZHl7i+\r\n' +
+    ' yJcqDtjJCowzTdBJrdAiLAnSC+CnnjOJELyuQiBgkGrgIh3j8/x0fM+YrsyF1u3F\r\n' +
+    ' LZPVtzlndhYFJQIDAQABMA0GCSqGSIb3DQEBAgUAA1kACKr0PqphJYw1j+YPtcIq\r\n' +
+    ' iWlFPuN5jJ79Khfg7ASFxskYkEMjRNZV/HZDZQEhtVaU7Jxfzs2wfX5byMp2X3U/\r\n' +
+    ' 5XUXGx7qusDgHQGs7Jk9W8CW1fuSWUgN4w==\r\n' +
+    'Key-Info: RSA,\r\n' +
+    ' I3rRIGXUGWAF8js5wCzRTkdhO34PTHdRZY9Tuvm03M+NM7fx6qc5udixps2Lng0+\r\n' +
+    ' wGrtiUm/ovtKdinz6ZQ/aQ==\r\n' +
+    'Issuer-Certificate:\r\n' +
+    ' MIIB3DCCAUgCAQowDQYJKoZIhvcNAQECBQAwTzELMAkGA1UEBhMCVVMxIDAeBgNV\r\n' +
+    ' BAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMQ8wDQYDVQQLEwZCZXRhIDExDTAL\r\n' +
+    ' BgNVBAsTBFRMQ0EwHhcNOTEwOTAxMDgwMDAwWhcNOTIwOTAxMDc1OTU5WjBRMQsw\r\n' +
+    ' CQYDVQQGEwJVUzEgMB4GA1UEChMXUlNBIERhdGEgU2VjdXJpdHksIEluYy4xDzAN\r\n' +
+    ' BgNVBAsTBkJldGEgMTEPMA0GA1UECxMGTk9UQVJZMHAwCgYEVQgBAQICArwDYgAw\r\n' +
+    ' XwJYCsnp6lQCxYykNlODwutF/jMJ3kL+3PjYyHOwk+/9rLg6X65B/LD4bJHtO5XW\r\n' +
+    ' cqAz/7R7XhjYCm0PcqbdzoACZtIlETrKrcJiDYoP+DkZ8k1gCk7hQHpbIwIDAQAB\r\n' +
+    ' MA0GCSqGSIb3DQEBAgUAA38AAICPv4f9Gx/tY4+p+4DB7MV+tKZnvBoy8zgoMGOx\r\n' +
+    ' dD2jMZ/3HsyWKWgSF0eH/AJB3qr9zosG47pyMnTf3aSy2nBO7CMxpUWRBcXUpE+x\r\n' +
+    ' EREZd9++32ofGBIXaialnOgVUn0OzSYgugiQ077nJLDUj0hQehCizEs5wUJ35a5h\r\n' +
+    'MIC-Info: RSA-MD5,RSA,\r\n' +
+    ' UdFJR8u/TIGhfH65ieewe2lOW4tooa3vZCvVNGBZirf/7nrgzWDABz8w9NsXSexv\r\n' +
+    ' AjRFbHoNPzBuxwmOAFeA0HJszL4yBvhG\r\n' +
+    'Recipient-ID-Asymmetric:\r\n' +
+    ' MFExCzAJBgNVBAYTAlVTMSAwHgYDVQQKExdSU0EgRGF0YSBTZWN1cml0eSwgSW5j\r\n' +
+    ' LjEPMA0GA1UECxMGQmV0YSAxMQ8wDQYDVQQLEwZOT1RBUlk=,66\r\n' +
+    'Key-Info: RSA,\r\n' +
+    ' O6BS1ww9CTyHPtS3bMLD+L0hejdvX6Qv1HK2ds2sQPEaXhX8EhvVphHYTjwekdWv\r\n' +
+    ' 7x0Z3Jx2vTAhOYHMcqqCjA==\r\n' +
+    '\r\n' +
+    'qeWlj/YJ2Uf5ng9yznPbtD0mYloSwIuV9FRYx+gzY+8iXd/NQrXHfi6/MhPfPF3d\r\n' +
+    'jIqCJAxvld2xgqQimUzoS1a4r7kQQ5c/Iua4LqKeq3ciFzEv/MbZhA==\r\n' +
+    '-----END PRIVACY-ENHANCED MESSAGE-----\r\n' +
+    '-----BEGIN RSA PRIVATE KEY-----\r\n' +
+    'MIIBPAIBAAJBALjXU+IdHkSkdBscgXf+EBoa55ruAIsU50uDFjFBkp+rWFt5AOGF\r\n' +
+    '9xL1/HNIby5M64BCw021nJTZKEOmXKdmzYsCAwEAAQJBAApyYRNOgf9vLAC8Q7T8\r\n' +
+    'bvyKuLxQ50b1D319EywFgLv1Yn0s/F9F+Rew6c04Q0pIqmuOGUM7z94ul/y5OlNJ\r\n' +
+    '2cECIQDveEW1ib2+787l7Y0tMeDzf/HQl4MAWdcxXWOeUFK+7QIhAMWZsukutEn9\r\n' +
+    '9/yqFMt8bL/dclfNn1IAgUL4+dMJ7zdXAiEAhaxGhVKxN28XuCOFhe/s2R/XdQ/O\r\n' +
+    'UZjU1bqCzDGcLvUCIGYmxu71Tg7SVFkyM/3eHPozKOFrU2m5CRnuTHhlMl2RAiEA\r\n' +
+    '0vhM5TEmmNWz0anPVabqDj9TA0z5MsDJQcn5NmO9xnw=\r\n' +
+    '-----END RSA PRIVATE KEY-----\r\n';
+
+  describe('pem', function() {
+    it('should decode and re-encode PEM messages', function() {
+      var msgs = PEM.decode(_input);
+
+      var output = '';
+      for(var i = 0; i < msgs.length; ++i) {
+        output += PEM.encode(msgs[i]);
+      }
+
+      ASSERT.equal(output, _input);
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/pem'
+  ], function(PEM) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      PEM()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/pem')());
+}
+
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/pkcs1.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/pkcs1.js
new file mode 100644
index 0000000..889eb6d
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/pkcs1.js
@@ -0,0 +1,1105 @@
+(function() {
+
+function Tests(ASSERT, PKI, PKCS1, MD, JSBN, UTIL) {
+  var BigInteger = JSBN.BigInteger;
+
+  // RSA's test vectors for Forge's RSA-OAEP implementation:
+  // http://www.rsa.com/rsalabs/node.asp?id=2125
+  describe('pkcs1', function() {
+    it('should detect invalid RSAES-OAEP padding', function() {
+      var keys = makeKey();
+
+      // provide the seed to test the same input each time
+      var seed = UTIL.decode64('JRTfRpV1WmeyiOr0kFw27sZv0v0=');
+
+      // test decrypting corrupted data: flip every bit (skip first byte to
+      // avoid triggering other invalid encryption error) in the message this
+      // tests the padding error handling
+      var encoded = PKCS1.encode_rsa_oaep(
+        keys.publicKey, 'datadatadatadata', {seed: seed});
+      var encrypted = keys.publicKey.encrypt(encoded, null);
+      var bitLength = encrypted.length * 8;
+      // FIXME: test it too slow to run all the time -- temporary
+      // change only does partial checks, need a longer term fix
+      bitLength /= 8;
+      for(var bit = 8; bit < bitLength; ++bit) {
+        var byteIndex = bit / 8;
+        var bitInByte = bit % 8;
+
+        var out = encrypted.substring(0, byteIndex);
+        var mask = 0x1 << bitInByte;
+        out += String.fromCharCode(encrypted.charCodeAt(byteIndex) ^ mask);
+        out += encrypted.substring(byteIndex + 1);
+
+        try {
+          var decrypted = keys.privateKey.decrypt(out, null);
+          PKCS1.decode_rsa_oaep(keys.privateKey, decrypted);
+          throw {
+            message: 'Expected an exception.'
+          };
+        } catch(e) {
+          ASSERT.equal(e.message, 'Invalid RSAES-OAEP padding.');
+        }
+      }
+    });
+
+    it('should detect leading zero bytes', function() {
+      var keys = makeKey();
+      var message = UTIL.fillString('\x00', 80);
+      var encoded = PKCS1.encode_rsa_oaep(keys.publicKey, message);
+      var ciphertext = keys.publicKey.encrypt(encoded, null);
+      var decrypted = keys.privateKey.decrypt(ciphertext, null);
+      var decoded = PKCS1.decode_rsa_oaep(keys.privateKey, decrypted);
+      ASSERT.equal(message, decoded);
+    });
+
+    testOAEP();
+    testOAEPSHA256();
+
+    function testOAEP() {
+      var modulus, exponent, d, p, q, dP, dQ, qInv, pubkey, privateKey;
+      var examples;
+
+      // Example 1: A 1024-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'qLOyhK+OtQs4cDSoYPFGxJGfMYdjzWxVmMiuSBGh4KvEx+CwgtaTpef87Wdc9GaFEncsDLxkp0LGxjD1M8jMcvYq6DPEC/JYQumEu3i9v5fAEH1VvbZi9cTg+rmEXLUUjvc5LdOq/5OuHmtme7PUJHYW1PW6ENTP0ibeiNOfFvs=';
+      exponent = 'AQAB';
+      d = 'UzOc/befyEZqZVxzFqyoXFX9j23YmP2vEZUX709S6P2OJY35P+4YD6DkqylpPNg7FSpVPUrE0YEri5+lrw5/Vf5zBN9BVwkm8zEfFcTWWnMsSDEW7j09LQrzVJrZv3y/t4rYhPhNW+sEck3HNpsx3vN9DPU56c/N095lNynq1dE=';
+      p = '0yc35yZ//hNBstXA0VCoG1hvsxMr7S+NUmKGSpy58wrzi+RIWY1BOhcu+4AsIazxwRxSDC8mpHHcrSEurHyjnQ==';
+      q = 'zIhT0dVNpjD6wAT0cfKBx7iYLYIkpJDtvrM9Pj1cyTxHZXA9HdeRZC8fEWoN2FK+JBmyr3K/6aAw6GCwKItddw==';
+      dP = 'DhK/FxjpzvVZm6HDiC/oBGqQh07vzo8szCDk8nQfsKM6OEiuyckwX77L0tdoGZZ9RnGsxkMeQDeWjbN4eOaVwQ==';
+      dQ = 'lSl7D5Wi+mfQBwfWCd/U/AXIna/C721upVvsdx6jM3NNklHnkILs2oZu/vE8RZ4aYxOGt+NUyJn18RLKhdcVgw==';
+      qInv = 'T0VsUCSTvcDtKrdWo6btTWc1Kml9QhbpMhKxJ6Y9VBHOb6mNXb79cyY+NygUJ0OBgWbtfdY2h90qjKHS9PvY4Q==';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 1.1',
+        message: 'ZigZThIHPbA7qUzanvlTI5fVDbp5uYcASv7+NA==',
+        seed: 'GLd26iEGnWl3ajPpa61I4d2gpe8=',
+        encrypted: 'NU/me0oSbV01/jbHd3kaP3uhPe9ITi05CK/3IvrUaPshaW3pXQvpEcLTF0+K/MIBA197bY5pQC3lRRYYwhpTX6nXv8W43Z/CQ/jPkn2zEyLW6IHqqRqZYXDmV6BaJmQm2YyIAD+Ed8EicJSg2foejEAkMJzh7My1IQA11HrHLoo='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 1.2',
+        message: 'dQxAR/VH6OQUEYVlIymKybriRe+vE5f75W+d1Q==',
+        seed: 'DMdCzkqbfzL5UbyyUe/ZJf5P418=',
+        encrypted: 'ZA2xrMWOBWj+VAfl+bcB3/jDyR5xbFNvx/zsbLW3HBFlmI1KJ54Vd9cw/Hopky4/AMgVFSNtjY4xAXp6Cd9DUtkEzet5qlg63MMeppikwFKD2rqQib5UkfZ8Gk7kjcdLu+ZkOu+EZnm0yzlaNS1e0RWRLfaW/+BwKTKUbXFJK0Q='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 1.3',
+        message: '2Urggy5kRc5CMxywbVMagrHbS6rTD3RtyRbfJNTjwkUf/1mmQj6w4dAtT+ZGz2md/YGMbpewUQ==',
+        seed: 'JRTfRpV1WmeyiOr0kFw27sZv0v0=',
+        encrypted: 'Qjc27QNfYCavJ2w1wLN0GzZeX3bKCRtOjCni8L7+5gNZWqgyLWAtLmJeleuBsvHJck6CLsp224YYzwnFNDUDpDYINbWQO8Y344efsF4O8yaF1a7FBnzXzJb+SyZwturDBmsfz1aGtoWJqvt9YpsC2PhiXKODNiTUgA+wgbHPlOs='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 1.4',
+        message: 'UuZQ2Y5/KgSLT4aFIVO5fgHdMW80ahn2eoU=',
+        seed: 'xENaPhoYpotoIENikKN877hds/s=',
+        encrypted: 'RerUylUeZiyYAPGsqCg7BSXmq64wvktKunYvpA/T044iq+/Gl5T267vAXduxEhYkfS9BL9D7qHxuOs2IiBNkb9DkjnhSBPnD9z1tgjlWJyLd3Ydx/sSLg6Me5vWSxM/UvIgXTzsToRKq47n3uA4PxvclW6iA3H2AIeIq1qhfB1U='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 1.5',
+        message: 'jaif2eX5dKKf7/tGK0kYD2z56AI=',
+        seed: 'sxjELfO+D4P+qCP1p7R+1eQlo7U=',
+        encrypted: 'NvbjTZSo002qy6M6ITnQCthak0WoYFHnMHFiAFa5IOIZAFhVohOg8jiXzc1zG0UlfHd/6QggK+/dC1g4axJE6gz1OaBdXRAynaROEwMP12Dc1kTP7yCU0ZENP0M+HHxt0YvB8t9/ZD1mL7ndN+rZBZGQ9PpmyjnoacTrRJy9xDk='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 1.6',
+        message: 'JlIQUIRCcQ==',
+        seed: '5OwJgsIzbzpnf2o1YXTrDOiHq8I=',
+        encrypted: 'Qs7iYXsezqTbP0gpOG+9Ydr78DjhgNg3yWNm3yTAl7SrD6xr31kNghyfEGQuaBrQW414s3jA9Gzi+tY/dOCtPfBrB11+tfVjb41AO5BZynYbXGK7UqpFAC6nC6rOCN7SQ7nYy9YqaK3iZYMrVlZOQ6b6Qu0ZmgmXaXQt8VOeglU='
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha1', examples);
+
+      // Example 2: A 1025-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'AZR8f86QQl9HJ55whR8l1eYjFv6KHfGTcePmKOJgVD5JAe9ggfaMC4FBGQ0q6Nq6fRJQ7G22NulE7Dcih3x8HQpn8UsWlMXwN5RRpD5Joy3eg2cLc9qRocmbwjtDamAFXGEPC6+ZwaB5VluVo/FSZjLR1Npg8g7aJeZTxPACdm9F';
+      exponent = 'AQAB';
+      d = 'CCPyD6212okIip0AiT4h+kobEfvJPGSjvguq6pf7O5PD/3E3BMGcljwdEHqumQVHOfeeAuGG3ob4em3e/qbYzNHTyBpHv6clW+IGAaSksvCKFnteJ51xWxtFW91+qyRZQdl2i5rO+zzNpZUto87nJSW0UBZjqO4VyemS2SRi/jk=';
+      p = 'AVnb3gSjPvBvtgi4CxkPTT4ivME6yOSggQM6v6QW7bCzOKoItXMJ6lpSQOfcblQ3jGlBTDHZfdsfQG2zdpzEGkM=';
+      q = 'AStlLzBAOzi0CZX9b/QaGsyK2nA3Mja3IC05su4wz7RtsJUR9vMHzGHMIWBsGKdbimL4It8DG6DfDa/VUG9Wi9c=';
+      dP = 'Q271CN5zZRnC2kxYDZjILLdFKj+1763Ducd4mhvGWE95Wt270yQ5x0aGVS7LbCwwek069/U57sFXJIx7MfGiVQ==';
+      dQ = 'ASsVqJ89+ys5Bz5z8CvdDBp7N53UNfBc3eLv+eRilIt87GLukFDV4IFuB4WoVrSRCNy3XzaDh00cpjKaGQEwZv8=';
+      qInv = 'AnDbF9WRSwGNdhGLJDiac1Dsg2sAY6IXISNv2O222JtR5+64e2EbcTLLfqc1bCMVHB53UVB8eG2e4XlBcKjI6A==';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 2.1',
+        message: 'j/AMqmBccCgwY02abD1CxlK1jPHZL+xXC+7n',
+        seed: 'jEB7XsKJnlCZxT6M55O/lOcbF4I=',
+        encrypted: 'AYGviSK5/LTXnZLr4ZgVmS/AwUOdi81JE5ig9K06Mppb2ThVYNtTJoPIt9oE5LEq7Wqs30ccNMnNqJGt3MLfNFZlOqY4Lprlm1RFUlfrCZ1WK74QRT8rbRPFnALhDx+Ku12g0FcJMtrPLQkB23KdD+/MBU5wlo6lQMgbBLyu/nIO'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 2.2',
+        message: 'LQ==',
+        seed: 'tgDPPC5QbX8Wd4yRDTqLAD7uYdU=',
+        encrypted: 'AYdZ/x32OyeSQQViMUQWqK6vKsY0tG+UCrgtZNvxZe7jMBHadJ1Lq24vzRgSnJ5JJ32EUxErQpoiKoRxsHCZOZjnWIYcTT9tdJ2RxCkNMyx6SrP36jX/OgfUl8lV/w/8lQBrYsbSloENm/qwJBlseTQBLC35eO8pmrojmUDLoQJF'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 2.3',
+        message: 'dPyIxRvJD3evnV6aSnATPUtOCzTaPDfH744=',
+        seed: 'pzdoruqpH52MHtb50rY0Z/B8yuM=',
+        encrypted: 'AYgCurBMYDJegcSWIxHyvnwq3OkwQaAHGciPlXV18sefG3vIztEVxwazEcCKLZhso7apM2sUfCnG8ilAnd7GUb0f3VoLf2EMmTf9tKOnYjZLizIGtOpIX9CY0I9j1KqLsml9Ant1DDLX906vUYDS6bZrF8svpVUjvCgNoQ0UviBT'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 2.4',
+        message: 'p+sqUDaTHSfU6JEybZlpL/rdqb9+/T405iLErcCF9yHf6IUHLHiiA7FRc5vlQPqMFToQ8Ao=',
+        seed: 'mns7DnCL2W+BkOyrT7mys4BagVY=',
+        encrypted: 'AKRXjLwXYximOPun0B3xV0avRNT2zZbX58SVy/QlsJxknTK/iG2kj7r5iaIRcYfK+x+1gDF2kOPM1EaSC3r4KzHbWATYfQFRSsv6kVbngvhn9r7ZRJ4OmiwJvOzGqgh2NpZeNLPsdm8v4uQwGKL93rFAYWoOnYLlMxAk7gZS/HZB'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 2.5',
+        message: 'LvKwZvhUwz873LtZlKQ15z1sbA==',
+        seed: '6zzrvErcFrtI6IyK7A40r39Cf9M=',
+        encrypted: 'AOvF9f2nfP2tPINkGpAl531y2Kb7M6gQ9ZUPjXTHPo2THoY02GqxJGJWrge2AFtxt/L7mDUSGDMc5puP+9ydoIu8nHBPh23rnfn8LsBlyth/kJCweswXqn+ZeyespIgG6Jf3cdlRQf5FJtilMBtnhifvq3B/1A++vW55KiVhPnrs'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 2.6',
+        message: 'in+zRMi2yyzy7x9kP5oyGPbhm7qJwA==',
+        seed: 'TEXPTVfJjj1tIJWtxRxInrUN/4Q=',
+        encrypted: 'AQg57CDCe5BS5Vvvubd+b8JukHXXpUN4xkar31HkRb1XFd6BeJ9W8YA9kXB2Sp6Ty3h5hpQCPuc5POBLxdj4xaUsFx1Dg346ymL2CesKpf+wlg7wQZjddU9X9/vmq/dlzxGLTKRDsjtaqyZvlSMmrEWBEAZEMl+LchrNXQT/FO86'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha1', examples);
+
+      // Example 3: A 1026-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'ArWP7AOahgcApNe2Ri+T5s3UkRYd3XT06BC0DjwWUgBqXCd7J3TBEwWky6taeO+lfheobfej+jb8Sx0iSfIux8LdakYyMqzOqQbWbr6AtXBLEHKdpvgzI0q7Xv3UopLL+tM7TTP6ehS4w5e1bjrNISA0KLd836M6bacGs9iw/EPp';
+      exponent = 'AQAB';
+      d = 'FbSKW1aDqUZw4jtXGPgU+g4T+FA49QcRGCy6YVEFgfPSLH4jLvk34i5VHWi4bi+MsarYvi5Ij13379J54/Vo1Orzb4DPcUGs5g/MkRP7bEqEH9ULvHxRL/y+/yFIeqgR6zyoxiAFNGqG3oa/odipSP0/NIwi6q3zM8PObOEyCP0=';
+      p = 'Ab8B0hbXNZXPAnDCvreNQKDYRH0x2pGamD9+6ngbd9hf43Gz6Tc+e2khfTFQoC2JWN5/rZ1VUWCVi0RUEn4Ofq8=';
+      q = 'AY0zmWWBZts4KYFteylUFnWenJGYf1stiuzWOwS0i9ey/PIpu3+KbciLoT3S45rVW20aBhYHCPlwC+gLj9N0TOc=';
+      dP = 'BsCiSdIKby7nXIi0lNU/aq6ZqkJ8iMKLFjp2lEXl85DPQMJ0/W6mMppc58fOA6IVg5buKnhFeG4J4ohalyjk5Q==';
+      dQ = '0dJ8Kf7dkthsNI7dDMv6wU90bgUc4dGBHfNdYfLuHJfUvygEgC9kJxh7qOkKivRCQ7QHmwNEXmAuKfpRk+ZP6Q==';
+      qInv = 'jLL3Vr2JQbHTt3DlrTHuNzsorNpp/5tvQP5Xi58a+4WDb5Yn03rP9zwneeY0uyYBHCyPfzNhriqepl7WieNjmg==';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 3.1',
+        message: 'CHggtWno+o0=',
+        seed: 'jO1rGWKQgFeQ6QkHQBXmogsMSJQ=',
+        encrypted: 'AmoEhdlq69lrQ4IIUJm5Yuaivew9kMjbYl4UNy3oXi1be6q2XI+vkbtVBPtJWvzlyYiz9qUuIOHWy9NWbFzR8rgxi7VCzA6iXEqrmTKvogdg6t3seEOWoH6g7yTU5vTTflBSp6MeFGqkgKERu+kmQBMH4A9BADOEK22C/lzk366A'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 3.2',
+        message: 'RlOsrxcZYLAfUqe+Y6OrIdw2jsQ7UNguw3geBA==',
+        seed: 'tCkdZWdVCEjMFWlnyAm6q2ylB/A=',
+        encrypted: 'Ak24nHgCmJvgeDhHhjCElBvyCddhmH44+Xy19vG8iNpypQtz668RyHnE+V3ze4ULj2XXYi4lsbiJ6A/oC6yiBp1uDh2CmVP8RZBp3pjql5i0UeVX6Zq/j+PZzPkJbrvz5SVdO04cbS7K3wZ6NZ7qhkBazUfV4WVRfMr9R9bb7kv1'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 3.3',
+        message: '2UzQ4I+kBO2J',
+        seed: 'zoko9gWVWCVACLrdl5T63NL9H2U=',
+        encrypted: 'Ajm85oEDJEFSiHfW0ci7KKo7yX8d9YRWNhiZV5doOETKhmZHMvS+16CqsIOqq/tyOPWC4wlYwgJOROVwQ7l5UP1UPal3yQzd5TN9YYRC+Z5g13g6tZzm3Z1pxHrR6WK+wi0FiVz/jT9k7VJh2SsmeFEDk0hJkLo/fwaBiub/zoo6'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 3.4',
+        message: 'bMZBtrYeb5Y5dNrSOpATKE7x',
+        seed: 'bil59S1oFKV9g7CQBUiI8RmluaM=',
+        encrypted: 'AplMYq/Xb0mLof0s9kKFf8qB9Dc8sI8cuu5vAlw7UStCw+h3kRNHZkgDnb4Ek/kkYpL6wolQYA58DzLt+cgbnexFw73gzI2IR1kBaZB7fcWZHOspuwcU1hPZbfDxLsXY01B8jueueN2D8hb6Yd4QA2OspIp+kUrp9C3fvpQ7Cdmg'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 3.5',
+        message: '31FRgyth9PJYkftBcvMo0u3fg3H/z9vpl5OSlfMOymkYAXz9oRU796avh1kyIw==',
+        seed: 'LXYL/jjFneNM3IuMeKOOZihKLSc=',
+        encrypted: 'AWIEL/aWlZKmFnAxgRojmDTOY4q/VP7IuZR4Eir+LuZ/jFsYsDOYBb/bxaTmcgs3xZz7qUJGTFl/9TKhGYIVRf0uWbEU5h2vcYIFKfUCnPUklUMnw07F5vW6fvzE3pQ6uK1O14exRUMp9w23mKOo9NkvgnTispSK3mJ86O4z5Dxg'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 3.6',
+        message: 'PDutiTxUSm1SCrAiMZGIyNUEt6eIuFCQO4WXLqoYVS4RNKetYJiCYlT/erZys9jrMVj6xtTLrvE=',
+        seed: '8XR3nF/Tz+AHuty3o2ybVb/Pvw4=',
+        encrypted: 'ABEgUeddBklDvER4B15DSC/VnO4Ged5ok+7DqUPapJC5aRyT38BGS2YjufPb0+cAgyZPA0s3T3QWThoAdjcl5XR0S6C524NDTzHflvbiom9tjro0i9RobCI4rAfDeqw3hdHH7qL4Gf2RSReY7Y6c715Dt4Gw4CduN8Q/+UktAFcw'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha1', examples);
+
+      // Example 4: A 1027-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'BRJAtswABPpI0BNGccB4x8jew7Pi8lvCVkRnM52ziFPQa4XupbLeNTv/QqwuRryX+uaslhjalTelyPVTweNXYlmR1hCNzXiF+zolQT9T78rZSMs1zZua6cHGdibRE9V93kxb6na7W7felsANBzculoWm11z50jn6FI1wkxtfP7A5';
+      exponent = 'AQAB';
+      d = 'BBH/yjt8penpvn/jioUQXjU4ltsFxXlq7NKnJRYes2UchimpuGK5BNewx7N/jLWhwrVAAQGKAKHrLK/k7k6UksNIvCvtq0ueu/Bk6O/zIrkAn47sZTkF9A34ijzcSdRWf3VifUGspiQSm0agt8aY5eZfK3uhAsdJoQE1tlQNBAE=';
+      p = 'AnRYwZ7BY2kZ5zbJryXWCaUbj1YdGca/aUPdHuGriko/IyEAvUC4jezGuiNVSLbveSoRyd6CPQp5IscJW266VwE=';
+      q = 'AhDumzOrYXFuJ9JRvUZfSzWhojLi2gCQHClL8iNQzkkNCZ9kK1N1YS22O6HyA4ZJK/BNNLPCK865CdE0QbU7UTk=';
+      dP = 'OfoCi4JuiMESG3UKiyQvqaNcW2a9/R+mN9PMSKhKT0V6GU53J+Sfe8xuWlpBJlf8RwxzIuvDdBbvRYwweowJAQ==';
+      dQ = 'AV2ZqEGVlDl5+p4b4sPBtp9DL0b9A+R9W++7v9ax0Tcdg++zMKPgIJQrL+0RXl0CviT9kskBnRzs1t1M8eVMyJk=';
+      qInv = 'AfC3AVFws/XkIiO6MDAcQabYfLtw4wy308Z9JUc9sfbL8D4/kSbj6XloJ5qGWywrQmUkz8UqaD0x7TDrmEvkEro=';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 4.1',
+        message: 'SoZglTTuQ0psvKP36WLnbUVeMmTBn2Bfbl/2E3xlxW1/s0TNUryTN089FmyfDG+cUGutGTMJctI=',
+        seed: 'HKwZzpk971X5ggP2hSiWyVzMofM=',
+        encrypted: 'BMzhlhSEXglBUqP+GOVOMzDETl77xkrhaIbLGGkBTMV4Gx+PngRThNARKhNcoNEunIio5AY0Ft6q44RPYNbpb+FVFF9FJbmjRDHKN2YYD3DhWl5djosaUW/4cGCfE/iWk1ztGIJ5pY7RPQcRQnfXXGVoYH4KsJL9gDoiPkqO4LGo'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 4.2',
+        message: 'sK3E8/4R2lnOmSdz2QWZQ8AwRkl+6dn5oG3xFm20bZj1jSfsB0wC7ubL4kSci5/FCAxcP0QzCSUS7EaqeTdDyA==',
+        seed: '9UXViXWF49txqgy42nbFHQMq6WM=',
+        encrypted: 'AJe2mMYWVkWzA0hvv1oqRHnA7oWIm1QabwuFjWtll7E7hU60+DmvAzmagNeb2mV4yEH5DWRXFbKA03FDmS3RhsgLlJt3XK6XNw5OyXRDE2xtpITpcP/bEyOiCEeCHTsYOB3hO7SarqZlMMSkuCcfPq4XLNNm4H5mNvEBnSoortFe'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 4.3',
+        message: 'v21C5wFwex0CBrDItFoccmQf8SiJIZqCveqWW155qWsNAWPtnVeOya2iDy+88eo8QInYNBm6gbDGDzYG2pk=',
+        seed: 'rZl/7vcw1up75g0NxS5y6sv90nU=',
+        encrypted: 'AwH5NenEery0isu+CYldn1lxrxSDnaT/lUF+5FPR/XcxkHK7cpfhtV11Yc2dG7JMGpo3xhmGQwgkKASHnYbr0AHc5Rg5deFQaYm3DlqDQ0FU1cv9aiR4fmDrDGWNKsGTMC0RksbmItShKtS1OSO8okbfMcY5XjdwLGp4rggfudBl'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 4.4',
+        message: '+y7xEvXnZuuUAZKXk0eU974vb8HFjg==',
+        seed: 'E2RU31cw9zyAen5A2MGjEqxbndM=',
+        encrypted: 'AtEQrTCvtye+tpHdDPF9CvGh5/oMwEDsGkuiakLFnQp5ai4iyPNXzMmLZRms62gulF5iy3NGFKUpQHzUUr7j5E/s6EI8wZ5VVIuLmUuEnH7N5JM+dgN+HQzkQnWwhxDGjkMBMLkpcw7XfgmwFWQsVZPwTk/7lBB5gQKo6W/9/hHk'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 4.5',
+        message: 'KMzUR7uehRZtq7nlt9GtrcS5058gTpbV5EDOmtkovBwihA==',
+        seed: 'vKgFf4JLLqJX8oYUB+72PTMghoE=',
+        encrypted: 'ANu4p0OdkO/ZGaN3xU+uj+EexYw7hYNi4jrRuKRDEHmQZrmTR6pSVpHSrcWNmwbjTyiMFwOQxfDhHAqjZFlZ8Y7nno8r6NesXCPQYfGN10uMXypY/LXrDFT5nwGoMkdWgpJTZYM0CUjXqMl8Ss0emNHincMg6XomBTKoqnp1ih7C'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 4.6',
+        message: '8iJCdR7GsQ==',
+        seed: 'Ln4eF/ZHtd3QM+FUcvkPaBLzrE4=',
+        encrypted: 'AKX/pHaMi77K7i23fo8u7JlZWTNUVSCDXlun25ST0+F83e/mpfVnYkRxkI204tg6D77mBgj8hASVA7IjSgfcg7J7IoR62JIP9C9nTvebdigLACM9K1G4yycDqdQr+8glDJbsMsBR5X8bS6Uo24nDfkxU4n5uZKxpY1roh9lUFhmp'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha1', examples);
+
+      // Example 5: A 1028-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'Cq3z+cEl5diR8xrESOmT3v5YD4ArRfnX8iulAh6cR1drWh5oAxup205tq+TZah1vPSZyaM/0CABfEY78rbmYiNHCNEZxZrKiuEmgWoicBgrA2gxfrotV8wm6YucDdC+gMm8tELARAhSJ/0l3cBkNiV/Tn1IpPDnv1zppi9q58Q7Z';
+      exponent = 'AQAB';
+      d = 'AlbrTLpwZ/LSvlQNzf9FgqNrfTHRyQmbshS3mEhGaiaPgPWKSawEwONkiTSgIGwEU3wZsjZkOmCCcyFE33X6IXWI95RoK+iRaCdtxybFwMvbhNMbvybQpDr0lXF/fVKKz+40FWH2/zyuBcV4+EcNloL5wNBy+fYGi1bViA9oK+LF';
+      p = 'A7DTli9tF1Scv8oRKUNI3PDn45+MK8aCTyFktgbWh4YNrh5jI5PP7fUTIoIpBp4vYOSs1+YzpDYGP4I4X0iZNwc=';
+      q = 'AuTDLi9Rcmm3ByMJ8AwOMTZffOKLI2uCkS3yOavzlXLPDtYEsCmC5TVkxS1qBTl95cBSov3cFB73GJg2NGrrMx8=';
+      dP = 'AehLEZ0lFh+mewAlalvZtkXSsjLssFsBUYACmohiKtw/CbOurN5hYat83iLCrSbneX31TgcsvTsmc4ALPkM429U=';
+      dQ = '65CqGkATW0zqBxl87ciBm+Hny/8lR2YhFvRlpKn0h6sS87pP7xOCImWmUpfZi3ve2TcuP/6Bo4s+lgD+0FV1Tw==';
+      qInv = 'AS9/gTj5QEBi64WkKSRSCzj1u4hqAZb0i7jc6mD9kswCfxjngVijSlxdX4YKD2wEBxp9ATEsBlBi8etIt50cg8s=';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 5.1',
+        message: 'r3GpAeOmHTEy8Pwf20dPnqZXklf/wk0WQXAUWz296A==',
+        seed: 'RMkuKD93uUmcYD2WNmDIfS+TlGE=',
+        encrypted: 'A2BGpKR9ntO6mokTnBBQOOt0krBaXWi/1TrM/0WX96aGUbR7SkYn2Sfkhe7XtFZkIOi0CYeeXWBuriUdIqXfeZ95IL/BF7mSVypTsSYxRrzqAzhcxehTyaEByMPhvaMaUZgHSWxsteXvtAiCOjUrj6BmH7Zk763Vk965n/9e0ADl'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 5.2',
+        message: 'o7hEoII5qKxBYFrxemz9pNNQE2WFkDpBenkmh2BRmktKwzA+xz8Ph8+zI5k=',
+        seed: 'yyj1hgZZ/O7knD7q/OYlpwgDvTI=',
+        encrypted: 'A9brZU7c5hW8WfRVJl7U5aGCI8u5vk5AabRzgE1d6W9U3KqmA9BJxdlKoUcN/NIlQGa3x7Yf8fb2dw4yFcUTmf1ONOxQgrxI8ImECtBDVK5m3A8b0Y5GGjPMEli0Q6KDem3yZ1mqIwIzSYb4c4DJzJ1Tvp+ZYF0smpfaewkVpKet'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 5.3',
+        message: 'MIsOy9LHbLd/xvcMXt0jP9LyCSnWKfAmlTu2Ko9KOjFL3hld6FtfgW2iqrB00my2rN3zI647nGeKw88S+93n',
+        seed: 'IoX0DXcEgvmp76LHLLOsVXFtwMo=',
+        encrypted: 'B3CVIYFkn5+fB/9ib/OiLDXEYkQ9kF1Fap/Qv/Q8rCynqfVU6UeLmsw6yDiwIED/0+GEfeLkJTkp+d2e5ARDJamwXKu4CLLuhA004V0QWj8feydpWhoHotc/4I7KqjycnU1aif+JDVRyfXrkDA7BqN2GFl2O4sY2gUEBaki1W2ln'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 5.4',
+        message: 'FcW57hGF',
+        seed: 'SfpF06eN0Q39V3OZ0esAr37tVRM=',
+        encrypted: 'CBK3Z2jry2QtBAJY5fREGgGFIb2WaH5sXomfzWwXWI/1moLMiuA6S0WzEpmvF4jDKffc0oX4z0ztgmBrl2EmcaRb7coTNEIUTRYX0RT4AoV/D51zl1HFej+e5ACRLGHi5pkr4DGkPdSPproU7vfEIrXtxOevoE/dOPQC0ci7cZq/'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 5.5',
+        message: 'IQJuaADH+nKPyqug0ZauKNeirE/9irznlPCYX2DIpnNydzZdP+oR24kjogKa',
+        seed: '8Ch0EyNMxQNHJKCUxFhrh6/xM/w=',
+        encrypted: 'B7YOFOyVS/0p5g0AR+eJ9R1XGGxjWJkDMGeTztP2gkHHQ1KaumpjdPkuGeAWPvozaX4Zb3Zh36qkeqxr3l5R3rUHxyxYmiyhaT2WsUYDgSSbLNuerER2nySJxdPS+Z8O48fuW/ZKWsecQr1DPxSb6MtZVINhZAWVUTyXr3vCUJcj'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 5.6',
+        message: 'VB43totsiHK4TAI=',
+        seed: '2fukXJbyHm4m0p6yzctlhb6cs0E=',
+        encrypted: 'CMNtTdozQjsu1oMNhfZBG6Hc9HCh+uDr7+58CJ8lbO90y5bqacOPYPOavuRBKby0yS3n95diOyAHTj2cKJlwHtkHHh76C92E1MPlEwMC2PAkC6ukuEpxzAMvIjWl/w+uJ3w+j5ESvvRMmuINF1/JpAWL/JMLoxsC4uT0REg3EPJK'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha1', examples);
+
+      // Example 6: A 1029-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'ErF/ba0uzRn/RtwT94YPCeDgz7Z3s4pSWSMFzq8CLBZtuQ0ErCnjP33RLZ+vZuCBa7Y+rSZ8x9RsF8N74hS8oqItcjpk5EQHQ2tvyWVymu/CVU83bNXc6mgpN4CmK/OdAClIWhYLu55dwJctIaUE9S5e4CiqQWMy9RCy6c/19yKv';
+      exponent = 'AQAB';
+      d = 'ApXso1YGGDaVWc7NMDqpz9r8HZ8GlZ33X/75KaqJaWG80ZDcaZftp/WWPnJNB7TcEfMGXlrpfZaDURIoC5CEuxTyoh69ToidQbnEEy7BlW/KuLsv7QV1iEk2Uixf99MyYZBIJOfK3uTguzctJFfPeOK9EoYij/g/EHMc5jyQz/P5';
+      p = 'BKbOi3NY36ab3PdCYXAFr7U4X186WKJO90oiqMBct8w469TMnZqdeJpizQ9g8MuUHTQjyWku+k/jrf8pDEdJo4s=';
+      q = 'BATJqAM3H+20xb4588ALAJ5eCKY74eQANc2spQEcxwHPfuvLmfD/4Xz9Ckv3vv0t1TaslG23l/28Sr6PKTSbke0=';
+      dP = 'A5Ycj3YKor1RVMeq/XciWzus0BOa57WUjqMxH8zYb7lcda+nZyhLmy3lWVcvFdjQRMfrg6G+X63yzDd8DYR1KUs=';
+      dQ = 'AiGX4GZ0IZaqvAP6L+605wsVy3h9YXrNMbt1x7wjStcG98SNIYLR8P+cIo3PQZZ7bAum0sCtEQobhXgx7CReLLE=';
+      qInv = 'BAHEwMU9RdvbXp2W0P7PQnXfCXS8Sgc2tKdMMmkFPvtoas4kBuIsngWN20rlQGJ64v2wgmHo5+S8vJlNqvowXEU=';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 6.1',
+        message: 'QEbKi6ozR8on9J4NgfnMHXG+m6UX1A==',
+        seed: '3Q9s/kFeiOWkaaUfu6bf1ArbQ4Q=',
+        encrypted: 'BjDuvNKFbCT3mIBuQfnmc0Xtqc7aOGrMn6yuoe7tBqzlg3CXGNnRafrfQU1cdvkploM+8wW3Wx5LlfZiog+u3DuuDEgnqL+KiO29V+wgOieoQfAuQ6YVurGoysBwHeNN6972KgiAibVew26nUi/T7I0GtqBz5t+DMVO8Cu/ZO9Gj'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 6.2',
+        message: 'XMcsYCMd8Ds9QPm1eTG8MRCflyUn8osZ50gMcojLPJKyJRIhTkvmyRR5Ldq99X+qiqc=',
+        seed: 'jRS9lGoTURSPXK4u2aDGU+hevYU=',
+        encrypted: 'Drw3N2FzpP0vicxVwspismsR1Rw8fOSeiEX3TnYHMXxDa8jSO5Zn3+udCHI0tHvGg3F1rlwFWfa4HX0iQW0+UPSsUz2PCBLy2555H+nHdayLatD1Na2c6yOkoCAUxYqz+NMWFJmiYPOTSOcUriodNEMgj9i3Isz9+zk+mAEfmeY/'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 6.3',
+        message: 'sg5lEwMJL0vMtDBwwPhtIwSTYu2WZC/FYywn20pS49gx8qsGiyOxSYecAC9r8/7ul1kRElYs',
+        seed: 'bAdbxFUg8WXAv16kxd8ZG8nvDkQ=',
+        encrypted: 'Cpi/EJNhk5RDbPaNjzji8Vj96OpU80NfI5uNBrgyGEQgJHau7ZYAlJJIDOOo1wVJjEyMaPAVAdyB22CPYAhzUMjDsL0unvaoFFi3yAG4ny5P6Z1JALpqS15althl3Gdsd1WSh5QTDWKAqBYKGQ8t8+p8+aoCcdiOnmkF7PHFFS1l'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 6.4',
+        message: 'aE4wOMXAQfc=',
+        seed: 'O7w71mN9/hKEaQECm/WwwHEDQ5w=',
+        encrypted: 'AI56Z8rPtcTiS+x97hSRF/GVmM6MRYCP74jGCP+c1uaVJjuaPArUuLpMlSOOlqhCK4U1YpyNU4I3RHmtE/o5l0skL5p1nur5yDrVqMoYlAoBYrp1WHbfJj9L1QxlJcVgkCZ8Hw4JzgiZoM81nogSCr2b+JNEWzyud9Ngc1mumlL4'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 6.5',
+        message: 'MkiMsmLQQdbk3TX5h788ppbbHwasKaRGkw==',
+        seed: 'tGtBiT6L7zJvZ1k4OoMHHa5/yrw=',
+        encrypted: 'AAA0dEFse2i9+WHDhXN5RNfx9AyzlTQ8aTzAtP5jsx/t8erurJzMBnizHcMuCXdIlRTE8JCF9imKllPwGupARf9YLuiHviauV1tz7vfzd0kh43Wj0ZrdoMoxqhhJiHwfQsrJZ396L06SP25ahos4wITvGHWU3J9/BI/qLgKVU4Sr'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 6.6',
+        message: 'ULoUvoRicgJ5wwa6',
+        seed: 'CiQDMSpB49UvBg+8E6Z95c92Cac=',
+        encrypted: 'CgJt2l/IeF972b91Mntj6F4sD97l2ttl69ysmuHelcksZyq0M6p6jmnOam2Il/rErEpU3oQa5eW7znaHh515Y0zqejBoQGXHFNUkCbkoJWu/U+q81SMetyWVBFNzmb0pFktybTOkbacBNgpBaKCRzKty1Epi/tJGwP/qWxNIq1Rw'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha1', examples);
+
+      // Example 7: A 1030-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'MRF58Lz8m508oxXQDvMNe906LPrpkRv+3LlIs6R4LQcytqtEqkvwN0GmRNwBvsPmmwGgM+Z12KzXxJJcaxrsMRkFHf2Jdi0hXUVHX/y1n5CBSGI/NxdxVvauht16fF9D3B4fkIJUBYooSl8GwAIXk6h/GsX+/33K7mnF5Ro3ieNz';
+      exponent = 'AQAB';
+      d = 'Bwz8/y/rgnbidDLEXf7kj0m3kX1lMOHwyjRg8y4CdhdEh8VuIqRdJQDXd1SVIZ19Flqc872Swyr5qY2NycwpaACtyUoKVPtA80KRv4TujqErbxCTWcbTVCpQ+cdn9c//BaaBwuZW+3fKqttL6UaNirzU35j1jobSBT+hNJ90jiGx';
+      p = 'B0kmLBEc1HDsJWbms3MvwJMpRpqhkHHTucAZBlFMbx0muqFL6rCXHIt+YRpPeQCdb+p3aSjKJShbDeNkPRo/jHE=';
+      q = 'BrweUOlsAr9jbp7qi4mbvr92Ud533UdMPpvCO62BgrYZBMfZffvr+x4AEIh4tuZ+QVOR1nlCwrK/m0Q1+IsMsCM=';
+      dP = 'A7x+p/CqsUOrxs6LlxGGNqMBcuTP4CyPoN2jt7qvkPgJKYKYVSX0iL38tL1ybiJjmsZKMJKrf/y/HVM0z6ULW/E=';
+      dQ = 'AmKmqinCo8Z9xTRsBjga/Zh6o8yTz7/s9U/dn514fX9ZpSPTmJedoTei9jgf6UgB98lNohUY3DTLQIcMRpeZStk=';
+      qInv = 'ZJ1MF7buFyHnctA4mlWcPTzflVDUV8RrA3t0ZBsdUhZq+KITyDliBs37pEIvGNb2Hby10hTJcb9IKuuXanNwwg==';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 7.1',
+        message: 'R6rpCQ==',
+        seed: 'Q90JoH/0ysccqkYy7l4cHa7kzY8=',
+        encrypted: 'FojkzneUu6bLcBQWns1VnO3iowtWpSto2f4Yzxlz75eyoDFTlRx1X2KUqkmtvbVYRatodfs5hsk+z5J5YoQNKC+eVM6LaQ98DLi71zRA2VcdGxbNkmD56rR4PMSC5SI9xglzhxeD7Cewrg/UdzLLwoahc/ySsA+0umgkZHzZPIXB'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 7.2',
+        message: 'HZsuIiPZvBO/ufFiznNdtIunxo9oIqChp7auFlg05w==',
+        seed: 'Opw87HuE+b063svGc+yZ1UsivJs=',
+        encrypted: 'EFLtOXsuAeHQ7hxQvyQ2P5XlBPSgNDSgj9giV07WuXNu27XzkNsQMhR5qKE5NQ4r1Jd8N3jvMx8+eK4RiyaEUfIKLwHUcfXVPFZpNxcbLbwtS95FmleZ8DctZXQjmyMj0kXQu4HChrY8iaNhAXM35JAviKRn9MfyRL/Vq0ZDf/O2'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 7.3',
+        message: '2Xb8',
+        seed: 'dqdeW2FXpVbPiIS7LkXCk91UXPU=',
+        encrypted: 'IVXNhD/ySk7outt2lCYAKKSQgTuos2mky/EG7BSOUphwf1llvn0QHBBJ6oWEwkzWNFWtnBBNaGKC0/uAOkwRwcLpuRxxeIAdG2ZA8AP1co3wB7ikzMkrzgXkGicnjXyFAYxSQUMTpQd3iQAdTwGRC3Kq0F0iCqFKWHM6dIm8VFVr'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 7.4',
+        message: '1HOGI98iOqQ4Q9+EZ1NMQdAT4MgDxiTiY2ZrI5veQKXymuuN5549qmHdA3D0m9SwE4NLmCEq72scXuNzs8s=',
+        seed: 'eGYxSmrW8rJQo1lB2yj1hktYWFk=',
+        encrypted: 'CrFMNzrrfUMo0KqtjAlNiLnrCYuV8hBUopCCUivnwnoxKHi2N5F+PYGebDxWjbXYQ4ArBtUdnpiivgv0DAMUI7AO37/4Mg77kXG9IERlOky5xRIvbGXoPNouw8EmAnqcGla6h00P6iPzgLgs8kC4z1QABHWMTHfZNBV6dPP8Er+s'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 7.5',
+        message: 'u0cjHKXqHTrUbJk0XZqKYQ==',
+        seed: 'shZu1HLVjbEMqyxrAAzM8Qp9xQk=',
+        encrypted: 'AoOHoxgndDR5i02X9GAGjfUpj6ulBBuhF2Ghy3MWskGEEU7FACV+JYntO2B6HrvpemzC4CvxtoH0IxKjO3p32OeFXEpt4D48BGQ/eGuRomSg1oBeLOqR5oF363pk2SVeTyfnE7fM7ADcIA69IcLqK7iQ/q5JQt+UHcP5eJDtNHR4'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 7.6',
+        message: 'IYSCcJXTXD+G9gDo5ZdUATKW',
+        seed: 'Umc73iyhZsKqRhMawdyAjWfX07E=',
+        encrypted: 'FMZ4qUrWBSXvOelZsvO6XAl6lP+RK2fbrOgFNcGHq9R9B1QgsYchUrugj3/DHzE7v5JzyRL8TAFJqbDPt5gH40brMyBpYRvsD/m80Wjx98M+dzE86kVLlOJUnuzwAuKs9/by0oRdT+CqsuWpLd9oxICuESR5NdH2JXSEIhauZ0EV'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha1', examples);
+
+      // Example 8: A 1031-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'W98OMNMh3aUUf4gkCPppGVSA34+A0/bov1gYUE82QnypsfVUC5xlqPaXTPhEeiRNkoAgG7Sfy75jeNGUTNIn4jD5bj0Q+Bnc7ydsZKALKktnAefQHeX6veOx6aDfgvRjE1nNImaWR/uxcXJGE07XtJfP/73EK1nHOpbtkBZiEt/3';
+      exponent = 'AQAB';
+      d = 'D30enlqqJf0T5KBmOuFE4NFfXNGLzbCd8sx+ZOPF6RWtYmRTBBYdCYxxW7eri9AdB+rz/tfH7QivKopi70SrFrMg4Ur3Kkj5av4mKgrkz2XmNekQeQzU7lzqdopLJjn35vZ3s/C7a+MrdXR9iQkDbwJk9Y1AHNuhMXFhV6dez2Mx';
+      p = 'CgLvhEjZ+ti70NAEyMKql1HvlyHBsNAyNqVLDflHy67VolXuno4g1JHqFyP+CUcEqXYuiK/RbrtZlEEsqWbcT58=';
+      q = 'CS02Ln7ToL/Z6f0ObAMBtt8pFZz1DMg7mwz01u6nGmHgArRuCuny3mLSW110UtSYuByaxvxYWT1MP7T11y37sKk=';
+      dP = 'B8cUEK8QOWLbNnQE43roULqk6cKd2SFFgVKUpnx9HG3tJjqgMKm2M65QMD4UA10a8BQSPrpoeCAwjY68hbaVfX0=';
+      dQ = 'rix1OAwCwBatBYkbMwHeiB8orhFxGCtrLIO+p8UV7KnKKYx7HKtYF6WXBo/IUGDeTaigFjeKrkPH+We8w3kEuQ==';
+      qInv = 'BZjRBZ462k9jIHUsCdgF/30fGuDQF67u6c76DX3X/3deRLV4Mi9kBdYhHaGVGWZqqH/cTNjIj2tuPWfpYdy7o9A=';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 8.1',
+        message: 'BQt1Xl5ogPe56daSp0w3quRJsxv+pt7/g3R6iX9sLIJbsa2/hQo8lplLXeWzPLx9SheROnln',
+        seed: 'dwb/yh7PsevuKlXlxuJM0nl6QSU=',
+        encrypted: 'CbNoPYousPspW2LtH7kpC3FEV7eCUxn0ZHhyr4ibMECUcgIK0SkSvxmxHUgZ9JYUgk/9hNCcChfn0XMJ0SkZeQQQqimVaZ9qhtvjJCtazCOvRWkQgNaxroEPs+MFcIfwlwCSzgC+lWL/QFO2Jizgyqk+E3I9LjpboHXUXw1htUth'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 8.2',
+        message: 'TraNzZPKmxnfERvUNgj1VwJv5KodXPrCJ6PrWrlUjBigbd7SP4GCWYay/NcRCezvfv+Ihz8HXCqgxGn2nJK8',
+        seed: 'o3F9oUO03P+8dCZlqPqVBYVUg0M=',
+        encrypted: 'Ls8VyXxaFbFHaumGs3G1eiQoT0oWKo0MgYLnkF55IlbxgSul+D8fehMOQtzAIjKETtwUoxpo7peuVko4OjQRZWQkxfYt22Rgk8Nnvh/NpCbPAKBtist+V3dvu9hVrD31BvwWsdfD8hEPPYBo6R4YY2ODHIQJaA2NqezYzx+iDuOd'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 8.3',
+        message: 'hgSsVjKMGrWtkXhh',
+        seed: '7gYgkHPMoCa7Jk5Rhb+MaLdzn4Y=',
+        encrypted: 'S8iRMKWy2rt8L8+Q610Or55oG3FGo48xc6PZz+xS6p4KQZMuZIqdaTRMUNp2P1GgPJV2ITHoBSJU3NIkjLpA/TFmd4bOBaK3tTGsnaye1YSlm2d8GortjF0V1owFVp4r54C/fbY4/Sv9KoWrJ2hg83dzOPypif/XQ9E+4I4MqYk/'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 8.4',
+        message: '/dpfv27DYanZpKxoryFqBob0OLHg5cNrlV904QfznA3dzA==',
+        seed: 'mQrVc9xIqXMjW22CVDYY8ulVEF0=',
+        encrypted: 'LkVoR9j8Nv8BR9aZNZS5OXIn1Xd1LHnQ+QT8sDnU2BL+pgWntXTdgsp4b5N1I0hDjun1tUVJhdXw4WmePnrRdaMuFfA96wQquf4d2dsbuG+MCJzLRefvDF7nyptykMprFb7UcDl4ioqT/4Pg6NYkTHEAY2Le72m29Bb7PGhDg/vQ'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 8.5',
+        message: 'Sl9JFL7iXePGk0HeBw==',
+        seed: '7MY7KPB1byL1Ksjm7BJRpuwwRxg=',
+        encrypted: 'H7k1b9XEsXltsuv30NOTzIEK32FF3vwvznFPedk4ANXirCEeqLvsyktlS5TDsYsw3Vds403JVDbvV6CUFWRZIzWaXXtBce8iwkZw8bIp02A+kfdmcbffl+cxfJdzRHbV89F9Ic+Ctbqfg98uWI02mE/RtYRGi9I7LodfMvaJU/ey'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 8.6',
+        message: 'jgfWb3uICnJWOrzT81CSvDNAn7f4jyRyvg==',
+        seed: 'OSXHGzYtQKCm3kIUVXm6Hn3UWfw=',
+        encrypted: 'Ov2cZgAUeyF5jYGMZVoPTJIS2ybQsN/cKnWUzLPSL1vx18PhEs1z/H1QnHqLr908J00TmQCflgnsS+ZHfkU/B1qjPbOChwwcNAmu85LXOGrjppa5mpS02gWJRH6VXRbJixdgKlm9c2J5/Nj7KAxEYtWQv6m/E/7VcOr96XMwosIQ'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha1', examples);
+
+      // Example 9: A 1536-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'zyzUHjTKOnKOpcuK/2TDbSe971Nk4zb9aNMSPFoZaowocBPoU9UVbVjRUZVFIPtPbXsXq7aBd2WQnFdhGWWdkCsZBu2KKxDBVcJNEkUo2rnurjeb6sZuSkEXhty4/QBi68Aw3hIZoEwqjBt90xMeTWtsruLjGl7UGsFQmy7x7iqxg2S+VoypQcJezIT/nWQ7XsGqrhAqINc/R5t4D9bakQdSEtnqwDoGdNiZ66LkMfTES2Fba6IjK9SzO67XPWJd';
+      exponent = 'AQAB';
+      d = 'GYwUHiNxWpK8z2oRmlvBE4lGjSgR9UjXJ+F7SrDrmG1vIR77U7cffMvqh+5px17mFQCMUzLetSvzkKvfv+N9cgU2gVmyY4wd4ybiHSIlHw+1hIs78VAF0qdDMPCv6RbuYszBNE0dg6cJ5gZ2JzhA9/N3QkpeCk2nXwGzH/doGc+cv90hUkPDkXwD7zgZkxLlZ7O/eu06tFfzce+KFCP0W2jG4oLsERu6KDO5h/1p+tg7wbjGE8Xh6hbBHtEl6n7B';
+      p = '/I1sBL7E65qBksp5AMvlNuLotRnezzOyRZeYxpCd9PF2230jGQ/HK4hlpxiviV8bzZFFKYAnQjtgXnCkfPWDkKjD6I/IxI6LMuPaIQ374+iB6lZ0tqNIwh6T+eVepl79';
+      q = '0gDUXniKrOpgakAdBGD4fdXBAn4S3BoNdYbok52c94m0D1GsBEKWHefSHMIeBcgxVcHyqpGTOHz9+VbLSNFTuicEBvm7ulN9SYfZ4vmULXoUy//+p0/s3ako0j4ln17h';
+      dP = '2xaAL3mi8NRfNY1p/TPkS4H66ChiLpOlQlPpl9AbB0N1naDoErSqTmyL6rIyjVQxlVpBimf/JqjFyAel2jVOBe8xzIz3WPRjcylQsD4mVyb7lOOdalcqJiRKsI23V1Kt';
+      dQ = 'oKMXz+ffFCP4em3uhFH04rSmflSX8ptPHk6DC5+t2UARZwJvVZblo5yXgX4PXxbifhnsmQLgHX6m+5qjx2Cv7h44G2neasnAdYWgatnEugC/dcitL6iYpHnoCuKU/tKh';
+      qInv = 'CyHzNcNTNC60TDqiREV4DC1lW5QBdMrjjHyKTmSTwLqf0wN0gmewg7mnpsth5C2zYrjJiW23Bk4CrVrmFYfaFbRknJBZSQn+s328tlS+tyaOyAHlqLSqORG+vYhULwW+';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 9.1',
+        message: '9zX9VbqSWSw7Urj5xPaaqhy++P6IrdCVWVQSRn+c9OwLiWxZ7aFiEOdUnIq7EM28IaEuyba1uP0vEDmetg==',
+        seed: 'jsll8TSj7Jkx6SocoNyBadXqcFw=',
+        encrypted: 'JnvNEYrKsfyLqByF1zADy4YQ+lXB2X2o1Ip8fwaJak23UaooQlW502rWXzdlPYKfGzf5e4ABlCVFsvwsVac3bKehvksXYMjgWjPlqiUmuNmOMXCI54NMdVsqWbEmMaGCwF1dQ6sXeSZPhFb1Fc5X399RLVST2re3M43Et9eNucCRrDuvU3pp/H9UnZefDv+alP2kFpvU0dGaacmeM8O1VJDVAbObHtrhGP9nk6FTJhWE06Xzn25oLj0XyM0SYfpy'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 9.2',
+        message: 'gbkGYFAVpjqr5C3fEeGXiRL1QEx0dLJtzj7Ugr+WHsyBi/QgxUZZ',
+        seed: '7LG4sl+lDNqwjlYEKGf0r1gm0Ww=',
+        encrypted: 'k6yfBnHsKay7RE7/waV0E1HWD9sOOT+/dUrPDeSXYaFIQd93cum8gnc5ZqFYTE1yuuoAEY+D81zKblN8vU2BH1WDspeD2KbZTNMb5w1vUmwQ/wnG+nzgaXlaP80FEf1fy1ZLzIDqnHjzi4ABJTnYpN32/oHpzdt/UNu7vMfl2GCXzPTsSRifuL8xi+bVoHFdUWtJrxkSWM0y3IM85utGc8A6Gbus6IzFSJX2NswMHsiQltEc4jWiZcoXZCMqaJro'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 9.3',
+        message: '/TJkKd+biQ4JtUsYuPNPHiQ=',
+        seed: '6JuwMsbOYiy9tTvJRmAU6nf3d8A=',
+        encrypted: 'gevdlQVLDIIu+a12k/Woet+0tMTOcN8t+E7UnATaWLpfwgoZ4abot6OQCyJ5bcToae5rQnktFajs61bAnGmRToE86o9pMeS47W9CGvKY1ZXJf0eJx8qmEsfvNgmEwhuT7cVAEGi1r0x4qHcbmE1TuOqK3y9qfUoLp2x14d2fZY8g3tSkYHHUbXeRtWgD2P6n8LD45Brj8JODpvlYX+d1Pqr/0r+UVjEIvuzCB7u1NfX8xwXw3en3CMYvSanJA3HT'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 9.4',
+        message: '8UWbXwyS8BoPcjouVmJITY+MCiD8KdrWrNQ7tfPv/fThtj4H/f5mKNDXTKGb8taeSgq/htKTklp5Z3L4CI4=',
+        seed: 'YG87mcC5zNdx6qKeoOTIhPMYnMw=',
+        encrypted: 'vMNflM3mbLETZiXWJblEMqNbIvPS+hGmE/8PylvVf4e5AszcHNCuvLBxXuhp0dH+OV9nkwA/XspGUFnIhmDURv9fCBhVICJVfjjAimfq2ZEmIlTxBoKXXsVjl3aFN/SXevbV9qrOt/sl3sWTcjAjH9iXivSRGaKfKeQkq4JytHVieS1clPd0uIKdCw2fGoye3fN1dNX6JI7vqcUnH8XsJXnIG91htBD6Yf425CQiHBE63bJ1ZkyAHTTKjGNR5KhY'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 9.5',
+        message: 'U+boxynW+cMZ3TF+dLDbjkzMol88gwV0bhN6xjpj7zc557WVq7lujVXlT3vUGrQzN4/7kR0=',
+        seed: '/LxCFALp7KvGCCr6QLpfJlIshA4=',
+        encrypted: 'Iyr7ySf6CML2onuH1KXLCcB9wm+uc9c6kFWIOfT9ZtKBuH7HNLziN7oWZpjtgpEGp95pQs1s3OeP7Y0uTYFCjmZJDQNiZM75KvlB0+NQVf45geFNKcu5pPZ0cwY7rseaEXn1oXycGDLyg4/X1eWbuWWdVtzooBnt7xuzrMxpfMbMenePYKBkx/b11SnGIQJi4APeWD6B4xZ7iZcfuMDhXUT//vibU9jWTdeX0Vm1bSsI6lMH6hLCQb1Y1O4nih8u'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 9.6',
+        message: 'trKOohmNDBAIvGQ=',
+        seed: 'I6reDh4Iu5uaeNIwKlL5whsuG6I=',
+        encrypted: 'Q4zH3AimjaJJ5CUF+Fc7pg4sJ3PVspD0z53/cY6EIIHDg+ZwJKDylZTqmHudJeS3OPKFlw0ZWrs6jIBU49eda5yagye6WW8SWeJxJmdHZpB9jVgv86hHYVSSmtsebRI1ssy07I9mO6nMZwqSvr2FPI2/acZDbQFvYa3YNulHMkUENCB/n9TEPewqEqlY76Ae/iZpiZteYEwlXFX7cWbeVYnjaVl7sJFowG3V2xd+BqF0DrLVyC+uym2S/O6ZMbqf'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha1', examples);
+
+      // Example 10: A 2048-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'rkXtVgHOxrjMBfgDk1xnTdvg11xMCf15UfxrDK7DE6jfOZcMUYv/ul7Wjz8NfyKkAp1BPxrgfk6+nkF3ziPn9UBLVp5O4b3PPB+wPvETgC1PhV65tRNLWnyAha3K5vovoUF+w3Y74XGwxit2Dt4jwSrZK5gIhMZB9aj6wmva1KAzgaIv4bdUiFCUyCUG1AGaU1ooav6ycbubpZLeGNz2AMKu6uVuAvfPefwUzzvcfNhP67v5UMqQMEsiGaeqBjrvosPBmA5WDNZK/neVhbYQdle5V4V+/eYBCYirfeQX/IjY84TE5ucsP5Q+DDHAxKXMNvh52KOsnX1Zhg6q2muDuw==';
+      exponent = 'AQAB';
+      d = 'BWsEIW/l81SsdyUKS2sMhSWoXFmwvYDFZFCiLV9DjllqMzqodeKR3UP0jLiLnV/A1Jn5/NHDl/mvwHDNnjmMjRnmHbfHQQprJnXfv100W4BNIBrdUC1c4t/LCRzpmXu+vlcwbzg+TViBA/A29+hdGTTRUqMj5KjbRR1vSlsbDxAswVDgL+7iuI3qStTBusyyTYQHLRTh0kpncfdAjuMFZPuG1Dk6NLzwt4hQHRkzA/E6IoSwAfD2Ser3kyjUrFxDCrRBSSCpRg7Rt7xA7GU+h20Jq8UJrkW1JRkBFqDCYQGEgphQnBw786SD5ydAVOFelwdQNumJ9gkygHtSV3UeeQ==';
+      p = '7PWuzR5VFf/6y9daKBbG6/SQGM37RjjhhdZqc5a2+AkPgBjH/ZXMNLhX3BfwzGUWuxNGq01YLK2te0EDNSOHtwM40IQEfJ2VObZJYgSz3W6kQkmSB77AH5ZCh/9jNsOYRlgzaEb1bkaGGIHBAjPSF2vxWl6W3ceAvIaKp30852k=';
+      q = 'vEbEZPxqxMp4Ow6wijyEG3cvfpsvKLq9WIroheGgxh5IWKD7JawpmZDzW+hRZMJZuhF1zdcZJwcTUYSZK2wpt0bdDSyr4UKDX30UjMFhUktKCZRtSLgoRz8c52tstohsNFwD4F9B1RtcOpCj8kBzx9dKT+JdnPIcdZYPP8OGMYM=';
+      dP = 'xzVkVx0A+xXQij3plXpQkV1xJulELaz0K8guhi5Wc/9qAI7U0uN0YX34nxehYLQ7f9qctra3QhhgmBX31FyiY8FZqjLSctEn+vS8jKLXc3jorrGbCtfaPLPeCucxSYD2K21LCoddHfA8G645zNgz72zX4tlSi/CE0flp55Tp9sE=';
+      dQ = 'Jlizf235wQML4dtoEX+p2H456itpO35tOi9wlHQT7sYULhj7jfy2rFRdfIagrUj4RXFw8O+ya8SBJsU+/R0WkgGY3CoRB9woLbaoDNMGI2C6P6E/cOQxL/GmzWuPxM2cXD2xfG1qVyEvc64p9hkye61ZsVOFhYW6Tii2CmKkXkk=';
+      qInv = 'bzhSazklCFU07z5BWoNu3ouGFYosfL/sywvYNDBP7Gg7qNT0ecQz1DQW5jJpYjzqEAd22Fr/QB0//2EO5lQRzjsTY9Y6lwnu3kJkfOpWFJPVRXCoecGGgs2XcQuWIF7DERfXO182Ij+t1ui6kN18DuYdROFjJR4gx/ZuswURfLg=';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 10.1',
+        message: 'i7pr+CpsD4bV8XVul5VocLCJU7BrTrIFvBaU7g==',
+        seed: 'R+GrcRn+5WyV7l6q2G9A0KpjvTM=',
+        encrypted: 'U+pdwIzSYPs7hYVnKH+pFVLDCy/r+6IT8K6HcC0GjRm6sH/ldFI9+0ITnWjDxa/u4L/ky3lpy/OCuATW5hOWFE4tDmB0H4mTwwFLWLmxlXqLq80jr4VPTDVvsWYqpyv8x+WGVZ3EKA0WDBJnhacj6+6+/3HxFZRECq74fRB5Ood0ojnUoEyH/hRnudr4UgjsbHJVeUqWzCkUL5qL1Bjjwf1nNEsM0IKd87K+xgJTGWKTxrNNP3XTLyE91Fxic9UFrfTM7RBXy3WPwmru+kQSVe1OZMGZ7gdefxZkYYL9tGRzm2irXa/w5j6VUgFoJPBUv008jJCpe7a2VTKE60KfzA=='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 10.2',
+        message: '5q0YHwU7WKkE8kV1EDc+Vw==',
+        seed: 'bRf1tMH/rDUdGVv3sJ0J8JpAec8=',
+        encrypted: 'orGkMKnWV+L6HCu17UP/slwFowj+kJPAEDF5X1h0QAEQgorlj7m1gc6d3dPlSa4EoJhUWb3mxiZZTnsF3EJ4sqFGXBNoQIgjyF6W3GbDowmDxjlmT8RWmjf+IeWhlbV3bu0t+NjTYa9obnUCKbvWY/FhhopQYV4MM3vsDKNf7AuxnDbrLgu8wFgvodk6rNsGEGP1nyzh7kNgXl2J7KGD0qzf6fgQEQIq07Q6PdQX2slLThHqgbGSlm6WaxgggucZZGB7T4AC82KZhEoR8q4PrqwurnD49PmAiKzc0KxVbp/MxRFSGQj60m8ExkIBRQMFd4dYsFOL+LW7FEqCjmKXlQ=='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 10.3',
+        message: 'UQos9g6Gb6I0BVPJTqOfvCVjEeg+lEVLQSQ=',
+        seed: 'OFOHUU3szHx0DdjN+druSaHL/VQ=',
+        encrypted: 'mIbD5nZKi5qE6EFI69jDsaqAUDgaePZocUwW2c/Spu3FaXnFNdne47RLhcGL6JKJkjcXEUciFtld2pjS7oNHybFN/9/4SqSNJawG99fmU5islnsc6Qkl9n3OBJt/gS2wdCmXp01E/oHb4Oej/q8uXECviI1VDdu+O8IGV6KVQ/j8KRO5vRphsqsiVuxAm719wNF3F+olxD9C7Sffhzi/SvxnZv96/whZVV7ig5IPTIpjxKc0DLr93DOezbSwUVAC+WyTK1t5Fnr2mcCtP8z98PROhacCYr8uGP40uFBYmXXoZ/+WnUjqvyEicVRs3AWmnstSblKHDINvMHvXmHgO3g=='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 10.4',
+        message: 'vN0ZDaO30wDfmgbiLKrip18QyR/2Z7fBa96LUwZKJkmpQEXJ',
+        seed: 'XKymoPdkFhqWhPhdkrbg7zfKi2U=',
+        encrypted: 'Yxjp+1wNBeUwfhaDQ26QMpOsRkI1iqoiPXFjATq6h+Lf2o5gxoYOKaHpJoYWPqC5F18ynKOxMaHt06d3Wai5e61qT49DlvKM9vOcpYES5IFg1uID2qWFbzrKX/7Vd69JlAjj39Iz4+YE2+NKnEyQgt5lUnysYzHSncgOBQig+nEi5/Mp9sylz6NNTR2kF4BUV+AIvsVJ5Hj/nhKnY8R30Vu7ePW2m9V4MPwsTtaG15vHKpXYX4gTTGsK/laozPvIVYKLszm9F5Cc8dcN4zNa4HA5CT5gbWVTZd5lULhyzW3h1EDuAxthlF9imtijU7DUCTnpajxFDSqNXu6fZ4CTyA=='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 10.5',
+        message: 'p91sfcJLRvndXx6RraTDs9+UfodyMqk=',
+        seed: 'lbyp44WYlLPdhp+n7NW7xkAb8+Q=',
+        encrypted: 'dSkIcsz9SkUFZg1lH1babaoJyhMB2JBjL2qZLz1WXO5GSv3tQO07W+k1ZxTqWqdlX0oTZsLxfHKPbyxaXR+OKEKbxOb48s/42o3A4KmAjkX9CeovpAyyts5v//XA4VnRG2jZCoX3uE4QOwnmgmZkgMZXUFwJKSWUaKMUeG106rExVzzyNL9X232eZsxnSBkuAC3A3uqTBYXwgx/c2bwz1R957S/8Frz01ZgS/OvKo/kGmw5EVobWRMJcz2O0Vu5fpv/pbxnN91H+2erzWVd1Tb9L/qUhaqGETcUHyy0IDnIuuhUDCMK1/xGTYg8XZuz0SBuvuUO9KSh38hNspJSroA=='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 10.6',
+        message: '6vGnOhsMRglTfeac2SKLvPuajKjGw++vBW/kp/RjTtALfDnsaSLXuOosBOus',
+        seed: 'n0fd9C6X7qhWqb28cU6zrCL26zI=',
+        encrypted: 'LSB6c0Mqj7TAMFGz9zsophdkCY36NMR6IJlfgRWqaBZnm1V+gtvuWEkIxuaXgtfes029Za8GPVf8p2pf0GlJL9YGjZmE0gk1BWWmLlx38jA4wSyxDGY0cJtUfEb2tKcJvYXKEi10Rl75d2LCl2Pgbbx6nnOMeL/KAQLcXnnWW5c/KCQMqrLhYaeLV9JiRX7YGV1T48eunaAhiDxtt8JK/dIyLqyXKtPDVMX87x4UbDoCkPtnrfAHBm4AQo0s7BjOWPkyhpje/vSy617HaRj94cGYy7OLevxnYmqa7+xDIr/ZDSVjSByaIh94yCcsgtG2KrkU4cafavbvMMpSYNtKRg=='
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha1', examples);
+    }
+
+    function testOAEPSHA256() {
+      var modulus, exponent, d, p, q, dP, dQ, qInv, pubkey, privateKey;
+      var examples;
+
+      // Example 1: A 1024-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'qLOyhK+OtQs4cDSoYPFGxJGfMYdjzWxVmMiuSBGh4KvEx+CwgtaTpef87Wdc9GaFEncsDLxkp0LGxjD1M8jMcvYq6DPEC/JYQumEu3i9v5fAEH1VvbZi9cTg+rmEXLUUjvc5LdOq/5OuHmtme7PUJHYW1PW6ENTP0ibeiNOfFvs=';
+      exponent = 'AQAB';
+      d = 'UzOc/befyEZqZVxzFqyoXFX9j23YmP2vEZUX709S6P2OJY35P+4YD6DkqylpPNg7FSpVPUrE0YEri5+lrw5/Vf5zBN9BVwkm8zEfFcTWWnMsSDEW7j09LQrzVJrZv3y/t4rYhPhNW+sEck3HNpsx3vN9DPU56c/N095lNynq1dE=';
+      p = '0yc35yZ//hNBstXA0VCoG1hvsxMr7S+NUmKGSpy58wrzi+RIWY1BOhcu+4AsIazxwRxSDC8mpHHcrSEurHyjnQ==';
+      q = 'zIhT0dVNpjD6wAT0cfKBx7iYLYIkpJDtvrM9Pj1cyTxHZXA9HdeRZC8fEWoN2FK+JBmyr3K/6aAw6GCwKItddw==';
+      dP = 'DhK/FxjpzvVZm6HDiC/oBGqQh07vzo8szCDk8nQfsKM6OEiuyckwX77L0tdoGZZ9RnGsxkMeQDeWjbN4eOaVwQ==';
+      dQ = 'lSl7D5Wi+mfQBwfWCd/U/AXIna/C721upVvsdx6jM3NNklHnkILs2oZu/vE8RZ4aYxOGt+NUyJn18RLKhdcVgw==';
+      qInv = 'T0VsUCSTvcDtKrdWo6btTWc1Kml9QhbpMhKxJ6Y9VBHOb6mNXb79cyY+NygUJ0OBgWbtfdY2h90qjKHS9PvY4Q==';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 1.1',
+        message: 'ZigZThIHPbA7qUzanvlTI5fVDbp5uYcASv7+NA==',
+        seed: 'GLd26iEGnWl3ajPpa61I4d2gpe8Yt3bqIQadaXdqM+k=',
+        encrypted: 'W1QN+A1CKWotV6aZW7NYnUy7SmZd34SiX0jiPiLj9+8sZW6O/L7793+IFFSO3VKbPWhrjJPyR3ZmZ+yHDCzTDkRth+s5FN3nuFtlD3XQmmh0+x60PvAUiXJnAMcwxV96wHKjsUNPSnE1fsrCPBpIO5ZRaJ1pIF6R25IeuMwDujo='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 1.2',
+        message: 'dQxAR/VH6OQUEYVlIymKybriRe+vE5f75W+d1Q==',
+        seed: 'DMdCzkqbfzL5UbyyUe/ZJf5P418Mx0LOSpt/MvlRvLI=',
+        encrypted: 'jsKSyOW1BkucnZpnt9fS72P/lamWQqexXEDPVs8uzGlFj24Rj+cqGYVlt7i9nTmOGj2YrvM8swUTJQCYIF+QBiKbkcA7WBTBXfiUlkHvpWQD0bLwOkp1CmwfpF4sq2gTsCuSaGzZAc50ZAIOvpldizU7uOCwNNGOlERcFkvhfEE='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 1.3',
+        message: '2Urggy5kRc5CMxywbVMagrHbS6rTD3RtyRbfJNTjwkUf/1mmQj6w4dAtT+ZGz2md/YGMbpewUQ==',
+        seed: 'JRTfRpV1WmeyiOr0kFw27sZv0v0lFN9GlXVaZ7KI6vQ=',
+        encrypted: 'LcQ1BhOH4Vs0XX8/QJ6q/L0vSs9BUXfA20lQ6mwAt/gvUaUOvKJWBujoxt1QgpRnU6WuH7cSCFWXuKNnrhofpFF3CBTLIUbHZFoou0A4Roi4vFGFvYYu96Boy+oWivwB9/BKs1QMQeHADgNwUgqVD15+q27yHdfIH7kGp+DiGas='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 1.4',
+        message: 'UuZQ2Y5/KgSLT4aFIVO5fgHdMW80ahn2eoU=',
+        seed: 'xENaPhoYpotoIENikKN877hds/vEQ1o+Ghimi2ggQ2I=',
+        encrypted: 'ZMkqw9CM3SuY2zPBr8/9QbgXaVon4O4AKIufl3i7RVPD07fiTOnXF0aSWKUcdXNhE6ZcXc0Ha97/S5aw6mQKYfbmjaSq/H45s2nfZYTNIa74OgsV1DTDDLSF6/3J2UKhsG0LGIFaV9cNjfucDA5KbfQbzTq8u/+WN06J6nbInrI='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 1.5',
+        message: 'jaif2eX5dKKf7/tGK0kYD2z56AI=',
+        seed: 'sxjELfO+D4P+qCP1p7R+1eQlo7WzGMQt874Pg/6oI/U=',
+        encrypted: 'NzKEr8KhWRbX/VHniUE8ap0HzdDEWOyfl7dfNHXjL4h/320dmK633rGUvlA7sE4z9yuMj/xF++9ZeBzN6oSPLhVJV/aivUfcC8J99lwwp49W7phnvkUA4WUSmUeX+XRhwj8cR27mf5lu/6kKKbgasdt4BHqXcc5jOZICnld6vdE='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 1.6',
+        message: 'JlIQUIRCcQ==',
+        seed: '5OwJgsIzbzpnf2o1YXTrDOiHq8Lk7AmCwjNvOmd/ajU=',
+        encrypted: 'nfQEzsDY2gS9UYXF85t+u0Tm7HrOmmf+LqxCD+6N4XD36NoQ96PE9Squ83PvxKy8Bj8Q0N2L8E5Z5/9AWxLPCBqOkqkqIqO7ZDQMmpHml3H1yz82rpAzAQi6acZDSFQAW8NKhg4nEEwfwKdaGQcI0JZm6FrTQUuXskOqFUT0NJc='
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha256', examples);
+
+      // Example 2: A 1025-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'AZR8f86QQl9HJ55whR8l1eYjFv6KHfGTcePmKOJgVD5JAe9ggfaMC4FBGQ0q6Nq6fRJQ7G22NulE7Dcih3x8HQpn8UsWlMXwN5RRpD5Joy3eg2cLc9qRocmbwjtDamAFXGEPC6+ZwaB5VluVo/FSZjLR1Npg8g7aJeZTxPACdm9F';
+      exponent = 'AQAB';
+      d = 'CCPyD6212okIip0AiT4h+kobEfvJPGSjvguq6pf7O5PD/3E3BMGcljwdEHqumQVHOfeeAuGG3ob4em3e/qbYzNHTyBpHv6clW+IGAaSksvCKFnteJ51xWxtFW91+qyRZQdl2i5rO+zzNpZUto87nJSW0UBZjqO4VyemS2SRi/jk=';
+      p = 'AVnb3gSjPvBvtgi4CxkPTT4ivME6yOSggQM6v6QW7bCzOKoItXMJ6lpSQOfcblQ3jGlBTDHZfdsfQG2zdpzEGkM=';
+      q = 'AStlLzBAOzi0CZX9b/QaGsyK2nA3Mja3IC05su4wz7RtsJUR9vMHzGHMIWBsGKdbimL4It8DG6DfDa/VUG9Wi9c=';
+      dP = 'Q271CN5zZRnC2kxYDZjILLdFKj+1763Ducd4mhvGWE95Wt270yQ5x0aGVS7LbCwwek069/U57sFXJIx7MfGiVQ==';
+      dQ = 'ASsVqJ89+ys5Bz5z8CvdDBp7N53UNfBc3eLv+eRilIt87GLukFDV4IFuB4WoVrSRCNy3XzaDh00cpjKaGQEwZv8=';
+      qInv = 'AnDbF9WRSwGNdhGLJDiac1Dsg2sAY6IXISNv2O222JtR5+64e2EbcTLLfqc1bCMVHB53UVB8eG2e4XlBcKjI6A==';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 2.1',
+        message: 'j/AMqmBccCgwY02abD1CxlK1jPHZL+xXC+7n',
+        seed: 'jEB7XsKJnlCZxT6M55O/lOcbF4KMQHtewomeUJnFPow=',
+        encrypted: 'AR3o2JwhHLKUfOLZ26KXD9INUK1/fWJzdZix7E545qladDYdpHRaE5zBP9nf6IPmZvBUPq75n1E4suxm+Bom7crf9be1HXCFZnmR/wo92CKg4D1zRlBwr/3Gitr3h9rU6N+tid2x9yOYj955rf3Bq4j6wmjYQpWphbhBIBMoliyJ'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 2.2',
+        message: 'LQ==',
+        seed: 'tgDPPC5QbX8Wd4yRDTqLAD7uYdW2AM88LlBtfxZ3jJE=',
+        encrypted: 'AIeYuAD2aYZYnEu1YK+INur95FfP2pTz8/k4r3xwL4bVMufgvzWFLdVK24fP96jTteLkrX6HjmebBVeUhSWG3ahebh3LH5yVS9yx+xHzM1Jxc8X1rS+kYgdCGWFbszMF/vP0ogisy5XthHqcoHNEM4Rzln7ugrXuS+dNuuPEjIAf'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 2.3',
+        message: 'dPyIxRvJD3evnV6aSnATPUtOCzTaPDfH744=',
+        seed: 'pzdoruqpH52MHtb50rY0Z/B8yuOnN2iu6qkfnYwe1vk=',
+        encrypted: 'AMkW9IJHAFs0JbfwRZhrRITtj1bQVDLcjFCwYxHMDBlSHIqpDzSAL8aMxiUq41Feo9S2O/1ZTXIiK8baJpWs9y+BPqgi1lABB6JJIvU2QZYMzWK0XgjkWk12g6HSPFhuK4yf+LQ1UYpbKVquUdZ9POOCR8S7yS+tdful6qP8Wpkm'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 2.4',
+        message: 'p+sqUDaTHSfU6JEybZlpL/rdqb9+/T405iLErcCF9yHf6IUHLHiiA7FRc5vlQPqMFToQ8Ao=',
+        seed: 'mns7DnCL2W+BkOyrT7mys4BagVaaezsOcIvZb4GQ7Ks=',
+        encrypted: 'AJ6YQ3DNjd7YXZzjHASKxPmwFbHKwoEpof+P+Li3+o6Xa95C21XyWZF0iCXc5USp5jwLt66T6G3aYQkEpoyFGvSPA3NV6tOUabopdmslYCkOwuOIsFLiuzkJc4Hu6nWXeJtTVtHn7FmzQgzQOMjuty1YConfe78YuQvyE3IAKkr2'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 2.5',
+        message: 'LvKwZvhUwz873LtZlKQ15z1sbA==',
+        seed: '6zzrvErcFrtI6IyK7A40r39Cf9PrPOu8StwWu0jojIo=',
+        encrypted: 'AMv457W0EOt8RH+LAEoMQ7dKjZamzOdwTHJepDkaGGoQHi2z8coCiVemL5XYZ+ctjPBdw3y3nlMn1sif9i3WCzY26ram8PL5eVYk7Bm3XBjv9wuhw1RZmLFzKfJS+3vi+RTFhwjyyeaJrc07f5E7Cu7CVWNh3Oe3lvSF3TB2HUI8'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 2.6',
+        message: 'in+zRMi2yyzy7x9kP5oyGPbhm7qJwA==',
+        seed: 'TEXPTVfJjj1tIJWtxRxInrUN/4RMRc9NV8mOPW0gla0=',
+        encrypted: 'AJ5iMVr3Q6ZZlqLj/x8wWewQBcUMnRoaS2lrejzqRk12Bw120fXolT6pgo20OtM6/ZpZSN7vCpmPOYgCf93MOqKpN1pqumUH33+iP1a+tos5351SidwwNb2hLy3JfhkapvjB+c9JvbIolIgr+xeWhWPmMDam/Du/y+EsBOdZrbYc'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha256', examples);
+
+      // Example 3: A 1026-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'ArWP7AOahgcApNe2Ri+T5s3UkRYd3XT06BC0DjwWUgBqXCd7J3TBEwWky6taeO+lfheobfej+jb8Sx0iSfIux8LdakYyMqzOqQbWbr6AtXBLEHKdpvgzI0q7Xv3UopLL+tM7TTP6ehS4w5e1bjrNISA0KLd836M6bacGs9iw/EPp';
+      exponent = 'AQAB';
+      d = 'FbSKW1aDqUZw4jtXGPgU+g4T+FA49QcRGCy6YVEFgfPSLH4jLvk34i5VHWi4bi+MsarYvi5Ij13379J54/Vo1Orzb4DPcUGs5g/MkRP7bEqEH9ULvHxRL/y+/yFIeqgR6zyoxiAFNGqG3oa/odipSP0/NIwi6q3zM8PObOEyCP0=';
+      p = 'Ab8B0hbXNZXPAnDCvreNQKDYRH0x2pGamD9+6ngbd9hf43Gz6Tc+e2khfTFQoC2JWN5/rZ1VUWCVi0RUEn4Ofq8=';
+      q = 'AY0zmWWBZts4KYFteylUFnWenJGYf1stiuzWOwS0i9ey/PIpu3+KbciLoT3S45rVW20aBhYHCPlwC+gLj9N0TOc=';
+      dP = 'BsCiSdIKby7nXIi0lNU/aq6ZqkJ8iMKLFjp2lEXl85DPQMJ0/W6mMppc58fOA6IVg5buKnhFeG4J4ohalyjk5Q==';
+      dQ = '0dJ8Kf7dkthsNI7dDMv6wU90bgUc4dGBHfNdYfLuHJfUvygEgC9kJxh7qOkKivRCQ7QHmwNEXmAuKfpRk+ZP6Q==';
+      qInv = 'jLL3Vr2JQbHTt3DlrTHuNzsorNpp/5tvQP5Xi58a+4WDb5Yn03rP9zwneeY0uyYBHCyPfzNhriqepl7WieNjmg==';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 3.1',
+        message: 'CHggtWno+o0=',
+        seed: 'jO1rGWKQgFeQ6QkHQBXmogsMSJSM7WsZYpCAV5DpCQc=',
+        encrypted: 'AJqBCgTJGSHjv2OR0lObiDY2gZmWdutHfVeadCdFr2W4mS3ZHwet283wbtY/bsM8w0rVxNAPh3NZNrcRt56NhoT0NzD2IK3WNy39Im/CfbicvC6Vq2PyXUh1iza+90PUM3jECPP5NsOx658MzEnYyFZFb9izZIna6YLsXwkWoHVO'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 3.2',
+        message: 'RlOsrxcZYLAfUqe+Y6OrIdw2jsQ7UNguw3geBA==',
+        seed: 'tCkdZWdVCEjMFWlnyAm6q2ylB/C0KR1lZ1UISMwVaWc=',
+        encrypted: 'ARCj8j/hSsscyXtuINlyU0HuC+d7wZc7bSekF60BJFWKeKa1p28d4KsJXmdqI22sxha7PgkI9bgpfgdBd8KHp12g5y68uXiwRyPOvv8s6YDKmJFhbW13LHbE3iZHch2YG1eHi/20M/IrsAqCuk/W5Q/dP5eSVM1hLT9LBVsX3rIH'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 3.3',
+        message: '2UzQ4I+kBO2J',
+        seed: 'zoko9gWVWCVACLrdl5T63NL9H2XOiSj2BZVYJUAIut0=',
+        encrypted: 'Anfa/o/QML7UxLCHcSUWFPUWhcp955u97b5wLqXuLnWqoeQ3POhwasFh3/ow2lkzjjIdU47jkYJEk6A0dNgYiBuDg57/KN5yS2Px/QOSV+2nYEzPgSUHGyZacrHVkj/ZVyZ+ni7Iyf/QkNTfvPGxqmZtX6cq095jgdG1ELgYsTdr'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 3.4',
+        message: 'bMZBtrYeb5Y5dNrSOpATKE7x',
+        seed: 'bil59S1oFKV9g7CQBUiI8RmluaNuKXn1LWgUpX2DsJA=',
+        encrypted: 'AalUnNYX91mP0FrqphpfhU22832WgnjDNRU1pkpSrd5eD7t7Q1YhYE+pKds6glA8i1AE/li216hJs2IbCJMddyaXrDzT8V9/UfIUaSkLfcRYBrTn9DEDOTjY1Xnn38poLOFykpZbAz5hdbOh0qG39qFgl5QZG0+aTBd1tmlMZBfO'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 3.5',
+        message: '31FRgyth9PJYkftBcvMo0u3fg3H/z9vpl5OSlfMOymkYAXz9oRU796avh1kyIw==',
+        seed: 'LXYL/jjFneNM3IuMeKOOZihKLSctdgv+OMWd40zci4w=',
+        encrypted: 'AGgQQYTuy9dW6e3SwV5UFYbEtqQD7TDtxcrMYOmYlTPgTwIFpo4GbQbtgD9BMFAW7a1lIzLxKEld49jH6m95Xgtq/BAVFl/gXin5MMbiZfRTOl38miBTg5a6IS9w6tcrWIBeY5Z5n4iCuUqF9r/m9TqvxWF0aMP2VGVKZn+LHMVj'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 3.6',
+        message: 'PDutiTxUSm1SCrAiMZGIyNUEt6eIuFCQO4WXLqoYVS4RNKetYJiCYlT/erZys9jrMVj6xtTLrvE=',
+        seed: '8XR3nF/Tz+AHuty3o2ybVb/Pvw7xdHecX9PP4Ae63Lc=',
+        encrypted: 'Aps8BQrRkPPwpNIjHw3NBznsDvp1hIHmlbG5wRERr9+Ar4ervO2GA/MMUVNijdZEtFnCGjbLwpM6RKzCk96jJX1bIgzq7hnmIzwKmq2Ue4qqO29rQL39jpCS87BBo/YKMbkYsPc2yYSDMBMOe9VDG63pvDgFGrlk/3Yfz1km3+/Y'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha256', examples);
+
+      // Example 4: A 1027-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'BRJAtswABPpI0BNGccB4x8jew7Pi8lvCVkRnM52ziFPQa4XupbLeNTv/QqwuRryX+uaslhjalTelyPVTweNXYlmR1hCNzXiF+zolQT9T78rZSMs1zZua6cHGdibRE9V93kxb6na7W7felsANBzculoWm11z50jn6FI1wkxtfP7A5';
+      exponent = 'AQAB';
+      d = 'BBH/yjt8penpvn/jioUQXjU4ltsFxXlq7NKnJRYes2UchimpuGK5BNewx7N/jLWhwrVAAQGKAKHrLK/k7k6UksNIvCvtq0ueu/Bk6O/zIrkAn47sZTkF9A34ijzcSdRWf3VifUGspiQSm0agt8aY5eZfK3uhAsdJoQE1tlQNBAE=';
+      p = 'AnRYwZ7BY2kZ5zbJryXWCaUbj1YdGca/aUPdHuGriko/IyEAvUC4jezGuiNVSLbveSoRyd6CPQp5IscJW266VwE=';
+      q = 'AhDumzOrYXFuJ9JRvUZfSzWhojLi2gCQHClL8iNQzkkNCZ9kK1N1YS22O6HyA4ZJK/BNNLPCK865CdE0QbU7UTk=';
+      dP = 'OfoCi4JuiMESG3UKiyQvqaNcW2a9/R+mN9PMSKhKT0V6GU53J+Sfe8xuWlpBJlf8RwxzIuvDdBbvRYwweowJAQ==';
+      dQ = 'AV2ZqEGVlDl5+p4b4sPBtp9DL0b9A+R9W++7v9ax0Tcdg++zMKPgIJQrL+0RXl0CviT9kskBnRzs1t1M8eVMyJk=';
+      qInv = 'AfC3AVFws/XkIiO6MDAcQabYfLtw4wy308Z9JUc9sfbL8D4/kSbj6XloJ5qGWywrQmUkz8UqaD0x7TDrmEvkEro=';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 4.1',
+        message: 'SoZglTTuQ0psvKP36WLnbUVeMmTBn2Bfbl/2E3xlxW1/s0TNUryTN089FmyfDG+cUGutGTMJctI=',
+        seed: 'HKwZzpk971X5ggP2hSiWyVzMofMcrBnOmT3vVfmCA/Y=',
+        encrypted: 'AooWJVOiXRikAgxb8XW7nkDMKIcrCgZNTV0sY352+QatjTq4go6/DtieHvIgUgb/QYBYlOPOZkdiMWXtOFdapIMRFraGeq4mKhEVmSM8G5mpVgc62nVR0jX49AXeuw7kMGxnKTV4whJanPYYQRoOb0L4Mf+8uJ5QdqBE03Ohupsp'
+    /* FIXME: could not convert 4.2', to SHA-256, message too long
+      }, {
+        title: 'RSAES-OAEP Encryption Example 4.2',
+        message: 'sK3E8/4R2lnOmSdz2QWZQ8AwRkl+6dn5oG3xFm20bZj1jSfsB0wC7ubL4kSci5/FCAxcP0QzCSUS7EaqeTdDyA==',
+        seed: '9UXViXWF49txqgy42nbFHQMq6WM=',
+        encrypted: 'AJe2mMYWVkWzA0hvv1oqRHnA7oWIm1QabwuFjWtll7E7hU60+DmvAzmagNeb2mV4yEH5DWRXFbKA03FDmS3RhsgLlJt3XK6XNw5OyXRDE2xtpITpcP/bEyOiCEeCHTsYOB3hO7SarqZlMMSkuCcfPq4XLNNm4H5mNvEBnSoortFe'
+    */
+      }, {
+        title: 'RSAES-OAEP Encryption Example 4.3',
+        message: 'v21C5wFwex0CBrDItFoccmQf8SiJIZqCveqWW155qWsNAWPtnVeOya2iDy+88eo8QInYNBm6gbDGDzYG2pk=',
+        seed: 'rZl/7vcw1up75g0NxS5y6sv90nWtmX/u9zDW6nvmDQ0=',
+        encrypted: 'AtYYko32Vmzn3ZtrsDQH9Mw/cSQk9pePdwQZJ6my7gYXWYpBdhbEN/fH7LMmvjtHnKLLTDazfF1HT0tTG6E+TY002cy+fMUvdRn0rfmFkNeHeqVOABP2EmI4eXFCBbbIlpshLxbA3vDTzPPZZqwMN+KPG4O11wmS9DcyHYtpsIOU'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 4.4',
+        message: '+y7xEvXnZuuUAZKXk0eU974vb8HFjg==',
+        seed: 'E2RU31cw9zyAen5A2MGjEqxbndMTZFTfVzD3PIB6fkA=',
+        encrypted: 'AZX8z/njjTP/ApNNF+BNGUjlczSK/7iKULnZhiAKo4LJ0XaTzTtvL9jacr+OkRxSPUCpQeK36wXdi9qjsl3SO9D7APyzN1nNE5Nu5YstiAfEMVNpdRYGdgpUasEZ4jshBRGXYW28uTMcFWRtzrlol9Lc7IhIkldTXZsR9zg11KFn'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 4.5',
+        message: 'KMzUR7uehRZtq7nlt9GtrcS5058gTpbV5EDOmtkovBwihA==',
+        seed: 'vKgFf4JLLqJX8oYUB+72PTMghoG8qAV/gksuolfyhhQ=',
+        encrypted: 'A8GIo5X2qOS6MdKjYJg+h3hi2endxxeb3F5A8v+MbC7/8WbBJnzOvKLb6YMukOfAqutJiGGzdPQM9fopdhbRwS/Ovw4ksvmNBVM+Q26CFPqvdhV8P0WxmeYTxGFGrLgma+fwxpe7L6mj300Jq6Y/5kfTEJSXNdKuLRn0JsIg8LSD'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 4.6',
+        message: '8iJCdR7GsQ==',
+        seed: 'Ln4eF/ZHtd3QM+FUcvkPaBLzrE4ufh4X9ke13dAz4VQ=',
+        encrypted: 'AM9cnO14EVBadGQYTnkkbm/vYwqmnYvnAutrc4bZR3XC0DNhhuUlzFosUSmaC1LrjKcFfcqZOwzlev5uZycR7tUlLihC6lf4o0khAjUb03Dj+ubNsDKNCOA6vP63N3p6jSVIak7EPu0KtBLuNpIyW5mdkMuwssV7CNLOQ6qG7zxZ'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha256', examples);
+
+      // Example 5: A 1028-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'Cq3z+cEl5diR8xrESOmT3v5YD4ArRfnX8iulAh6cR1drWh5oAxup205tq+TZah1vPSZyaM/0CABfEY78rbmYiNHCNEZxZrKiuEmgWoicBgrA2gxfrotV8wm6YucDdC+gMm8tELARAhSJ/0l3cBkNiV/Tn1IpPDnv1zppi9q58Q7Z';
+      exponent = 'AQAB';
+      d = 'AlbrTLpwZ/LSvlQNzf9FgqNrfTHRyQmbshS3mEhGaiaPgPWKSawEwONkiTSgIGwEU3wZsjZkOmCCcyFE33X6IXWI95RoK+iRaCdtxybFwMvbhNMbvybQpDr0lXF/fVKKz+40FWH2/zyuBcV4+EcNloL5wNBy+fYGi1bViA9oK+LF';
+      p = 'A7DTli9tF1Scv8oRKUNI3PDn45+MK8aCTyFktgbWh4YNrh5jI5PP7fUTIoIpBp4vYOSs1+YzpDYGP4I4X0iZNwc=';
+      q = 'AuTDLi9Rcmm3ByMJ8AwOMTZffOKLI2uCkS3yOavzlXLPDtYEsCmC5TVkxS1qBTl95cBSov3cFB73GJg2NGrrMx8=';
+      dP = 'AehLEZ0lFh+mewAlalvZtkXSsjLssFsBUYACmohiKtw/CbOurN5hYat83iLCrSbneX31TgcsvTsmc4ALPkM429U=';
+      dQ = '65CqGkATW0zqBxl87ciBm+Hny/8lR2YhFvRlpKn0h6sS87pP7xOCImWmUpfZi3ve2TcuP/6Bo4s+lgD+0FV1Tw==';
+      qInv = 'AS9/gTj5QEBi64WkKSRSCzj1u4hqAZb0i7jc6mD9kswCfxjngVijSlxdX4YKD2wEBxp9ATEsBlBi8etIt50cg8s=';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 5.1',
+        message: 'r3GpAeOmHTEy8Pwf20dPnqZXklf/wk0WQXAUWz296A==',
+        seed: 'RMkuKD93uUmcYD2WNmDIfS+TlGFEyS4oP3e5SZxgPZY=',
+        encrypted: 'BOGyBDRo+2G7OC79yDEzJwwLMPuhIduDVaaBdb5svHj/ZAkVlyGVnH0j+ECliT42Nhvp4kZts+9cJ0W+ui7Q9KXbjmX033MpxrvSV1Ik//kHhX6xTn51UGpaOTiqofjM3QTTi9DVzRtAarWd/c8oAldrGok1vs+tJEDbcA5KvZz7'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 5.2',
+        message: 'o7hEoII5qKxBYFrxemz9pNNQE2WFkDpBenkmh2BRmktKwzA+xz8Ph8+zI5k=',
+        seed: 'yyj1hgZZ/O7knD7q/OYlpwgDvTLLKPWGBln87uScPuo=',
+        encrypted: 'AeleoSbRCOqBTpyWGLCrZ2G3mfjCYzMvupBy+q+sOKAfBzaLCjCehEUmIZFhe+CvtmVvyKGFqOwHUWgvks9vFU7Gfi580aZm7d4FtaGGVHBO6Q32/6IS7a+KSk7L6rPWwBTI+kyxb5SW12HTEowheKkFTda06tU0l4Ji45xP2tyh'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 5.3',
+        message: 'MIsOy9LHbLd/xvcMXt0jP9LyCSnWKfAmlTu2Ko9KOjFL3hld6FtfgW2iqrB00my2rN3zI647nGeKw88S+93n',
+        seed: 'IoX0DXcEgvmp76LHLLOsVXFtwMoihfQNdwSC+anvosc=',
+        encrypted: 'Ci3TBMV4P0o59ap6Wztb9LQHfJzYSOAaYaiXjk85Q9FYhAREaeS5YXhegKbbphMIS5i1SYJShmwpYu/t8SGHiX/72v6NnRgafDKzttROuF/HJoFkTBKH6C9NKke+mxoDy/YVZ9qYzFY6PwzB4pTDwku9s5Ha4DmRBlFdA/z713a4'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 5.4',
+        message: 'FcW57hGF',
+        seed: 'SfpF06eN0Q39V3OZ0esAr37tVRNJ+kXTp43RDf1Xc5k=',
+        encrypted: 'AcMQiclY0MMdT9K4kPqZ7JYHTaSolc8B3huHcQ4U5mG11/9XjzWjTLha8Liy0w909aaPbaB7+ZQTebg7x3F4yeWFRmnAJMaIFGBW/oA952mEaJ+FR2HO0xfRPzCRCaaU7cyOxy0gnR8d9FMunt9fhbffM9TvOfR6YDE5Duz6Jg0W'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 5.5',
+        message: 'IQJuaADH+nKPyqug0ZauKNeirE/9irznlPCYX2DIpnNydzZdP+oR24kjogKa',
+        seed: '8Ch0EyNMxQNHJKCUxFhrh6/xM/zwKHQTI0zFA0ckoJQ=',
+        encrypted: 'A/gvyZ/MNHUG3JGcisvAw/h1bhviZsqIsEM5+gBpLfj7d8iq28yZa6z5cnS4cJWywHxyQMgt9BAd37im/f5WcIcB+TZS9uegFSdgaetpYf4ft/1wMlcdc1ReCrTrCKPFHLLczeifyrnJSVvQD84kQY21b4fW9uLbSiGO0Ly94il1'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 5.6',
+        message: 'VB43totsiHK4TAI=',
+        seed: '2fukXJbyHm4m0p6yzctlhb6cs0HZ+6RclvIebibSnrI=',
+        encrypted: 'AWmTYpBHOqRBGy1h5mF88hMmBVNLN++kXAqQr4PKszqorigNQZbvwbOdWYNLsXydyvKi55ds8tTvXf4rRBswyuNmbtT0t2FVCTnTjNzA1cMSahInbdKfL/1wib3CjyQmC0TbbIa3kkAdXkiYytSafDxwNyan1OWtJcMLkJ6l8WRm'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha256', examples);
+
+      // Example 6: A 1029-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'ErF/ba0uzRn/RtwT94YPCeDgz7Z3s4pSWSMFzq8CLBZtuQ0ErCnjP33RLZ+vZuCBa7Y+rSZ8x9RsF8N74hS8oqItcjpk5EQHQ2tvyWVymu/CVU83bNXc6mgpN4CmK/OdAClIWhYLu55dwJctIaUE9S5e4CiqQWMy9RCy6c/19yKv';
+      exponent = 'AQAB';
+      d = 'ApXso1YGGDaVWc7NMDqpz9r8HZ8GlZ33X/75KaqJaWG80ZDcaZftp/WWPnJNB7TcEfMGXlrpfZaDURIoC5CEuxTyoh69ToidQbnEEy7BlW/KuLsv7QV1iEk2Uixf99MyYZBIJOfK3uTguzctJFfPeOK9EoYij/g/EHMc5jyQz/P5';
+      p = 'BKbOi3NY36ab3PdCYXAFr7U4X186WKJO90oiqMBct8w469TMnZqdeJpizQ9g8MuUHTQjyWku+k/jrf8pDEdJo4s=';
+      q = 'BATJqAM3H+20xb4588ALAJ5eCKY74eQANc2spQEcxwHPfuvLmfD/4Xz9Ckv3vv0t1TaslG23l/28Sr6PKTSbke0=';
+      dP = 'A5Ycj3YKor1RVMeq/XciWzus0BOa57WUjqMxH8zYb7lcda+nZyhLmy3lWVcvFdjQRMfrg6G+X63yzDd8DYR1KUs=';
+      dQ = 'AiGX4GZ0IZaqvAP6L+605wsVy3h9YXrNMbt1x7wjStcG98SNIYLR8P+cIo3PQZZ7bAum0sCtEQobhXgx7CReLLE=';
+      qInv = 'BAHEwMU9RdvbXp2W0P7PQnXfCXS8Sgc2tKdMMmkFPvtoas4kBuIsngWN20rlQGJ64v2wgmHo5+S8vJlNqvowXEU=';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 6.1',
+        message: 'QEbKi6ozR8on9J4NgfnMHXG+m6UX1A==',
+        seed: '3Q9s/kFeiOWkaaUfu6bf1ArbQ4TdD2z+QV6I5aRppR8=',
+        encrypted: 'C3d3hdR81ybq+Wuf6QUfy2KHVuQjrsvVH2HuE2LbJT2o2ZPdrDHIoGphdGj+GWNTrcV/d8iPLJlZ0CR3O2e2b7wLUVPMorv1HidYA8B8eJxkg5FIsPuK836LchnGqQlE7ObiWOjSuIw4lZ/ULCfOYejelr6PJXSxWgQUlV78sbvP'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 6.2',
+        message: 'XMcsYCMd8Ds9QPm1eTG8MRCflyUn8osZ50gMcojLPJKyJRIhTkvmyRR5Ldq99X+qiqc=',
+        seed: 'jRS9lGoTURSPXK4u2aDGU+hevYWNFL2UahNRFI9cri4=',
+        encrypted: 'DXAHBh/uWFjxl/kIwrzm0MXeHNH5MSmoPc0mjn00UcCUFmOwTQipPmLmephH+rNOOfCQVvwP5wysU3/w2hjmk/rl6Jb4qNc+KqDiij7fKSKhPGTvY3aiXZ2LflnJ3yv4LdT9KvvWsZrHEWfsEG+fQZW4c1OMEpOMC4N44nc88yRm'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 6.3',
+        message: 'sg5lEwMJL0vMtDBwwPhtIwSTYu2WZC/FYywn20pS49gx8qsGiyOxSYecAC9r8/7ul1kRElYs',
+        seed: 'bAdbxFUg8WXAv16kxd8ZG8nvDkRsB1vEVSDxZcC/XqQ=',
+        encrypted: 'AMe1ZPYbk4lABLKDLhwJMM4AfK46Jyilp/vQ9M921AamJzanoNGdlj6ZEFkbIO68hc/Wp4Qr43iWtjcasgpLw2NS0vroRi91VI5k9BZgXtgNG7Z9FBOtPjM61Um2PWSFpAyfaZS7zoJlfRKciEa+XUKa4VGly4fYSXXAbUJV2YHc'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 6.4',
+        message: 'aE4wOMXAQfc=',
+        seed: 'O7w71mN9/hKEaQECm/WwwHEDQ5w7vDvWY33+EoRpAQI=',
+        encrypted: 'AJS/vpVJKJuLwnnzENVQChT5MCBa0mLxw/a9nt+6Zj4FL8nucIl7scjXSOkwBDPcyCWr7gqdtjjZ9z6RCQv0HfjmVKI2M6AxI2MYuzwftIQldbhCRqo8AlyK3XKjfcK+Rzvii53W8Xw4Obbsv9OCLnCrrbK8aO3XKgrHPmDthH7x'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 6.5',
+        message: 'MkiMsmLQQdbk3TX5h788ppbbHwasKaRGkw==',
+        seed: 'tGtBiT6L7zJvZ1k4OoMHHa5/yry0a0GJPovvMm9nWTg=',
+        encrypted: 'CmNUKnNQco5hWHxCISdwN5M7LbL7YJ0u7bfH82LulE32VdATd3vcJmRiFtcczNNudMlHVhl6/ZsDVY1zymLrK2kLIYWeG9Iag3rQ5xhjLAdpMYBBuwjrJ8Oqc4+2qH57bBveynuE5xRpd9p+CkkiRP7x7g4B/iAwrmFxPtrxV/q/'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 6.6',
+        message: 'ULoUvoRicgJ5wwa6',
+        seed: 'CiQDMSpB49UvBg+8E6Z95c92CacKJAMxKkHj1S8GD7w=',
+        encrypted: 'DpQAu4uQ4zbkpP/f698+a5f3MhAXCi3QTcP7vXmQVlkH0CFlCnDESNG36Jk2ybe3VmzE2deBHBKI9a5cHUzM9Lsa/AoxnbD5qd2fJt9k19dSRtDWZUR/Bn/AdVHwstzsX/vRLe6qOk9Kf01OZcvKrmlWh2IBLs8/6sEJhBWXNAKj'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha256', examples);
+
+      // Example 7: A 1030-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'MRF58Lz8m508oxXQDvMNe906LPrpkRv+3LlIs6R4LQcytqtEqkvwN0GmRNwBvsPmmwGgM+Z12KzXxJJcaxrsMRkFHf2Jdi0hXUVHX/y1n5CBSGI/NxdxVvauht16fF9D3B4fkIJUBYooSl8GwAIXk6h/GsX+/33K7mnF5Ro3ieNz';
+      exponent = 'AQAB';
+      d = 'Bwz8/y/rgnbidDLEXf7kj0m3kX1lMOHwyjRg8y4CdhdEh8VuIqRdJQDXd1SVIZ19Flqc872Swyr5qY2NycwpaACtyUoKVPtA80KRv4TujqErbxCTWcbTVCpQ+cdn9c//BaaBwuZW+3fKqttL6UaNirzU35j1jobSBT+hNJ90jiGx';
+      p = 'B0kmLBEc1HDsJWbms3MvwJMpRpqhkHHTucAZBlFMbx0muqFL6rCXHIt+YRpPeQCdb+p3aSjKJShbDeNkPRo/jHE=';
+      q = 'BrweUOlsAr9jbp7qi4mbvr92Ud533UdMPpvCO62BgrYZBMfZffvr+x4AEIh4tuZ+QVOR1nlCwrK/m0Q1+IsMsCM=';
+      dP = 'A7x+p/CqsUOrxs6LlxGGNqMBcuTP4CyPoN2jt7qvkPgJKYKYVSX0iL38tL1ybiJjmsZKMJKrf/y/HVM0z6ULW/E=';
+      dQ = 'AmKmqinCo8Z9xTRsBjga/Zh6o8yTz7/s9U/dn514fX9ZpSPTmJedoTei9jgf6UgB98lNohUY3DTLQIcMRpeZStk=';
+      qInv = 'ZJ1MF7buFyHnctA4mlWcPTzflVDUV8RrA3t0ZBsdUhZq+KITyDliBs37pEIvGNb2Hby10hTJcb9IKuuXanNwwg==';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 7.1',
+        message: 'R6rpCQ==',
+        seed: 'Q90JoH/0ysccqkYy7l4cHa7kzY9D3Qmgf/TKxxyqRjI=',
+        encrypted: 'CdXefX8LEW8SqnT1ly7/dvScQdke1rrSIBF4NcFO/G+jg0u7yjsqLLfTa8voI44Ue3W6lVuj5SkVYaP9i7VPmCWA4nFfeleuy23gbHylm4gokcCmzcAm2RLfPQYPnxIb3hoQ2C3wXo/aWoLIMFPYzI19g5uY90XMEchAci3FVC/a'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 7.2',
+        message: 'HZsuIiPZvBO/ufFiznNdtIunxo9oIqChp7auFlg05w==',
+        seed: 'Opw87HuE+b063svGc+yZ1UsivJs6nDzse4T5vTrey8Y=',
+        encrypted: 'DDar5/aikhAVropPgT3SVzSMRtdS9sEmVBEqg9my/3na0Okz51EcAy436TOZVsM0exezvKYsVbDQhtOM0Mn9r6oyBsqzUR4lx6Gt2rYDYC4X1aMsJSVcQs9pDqeAWfIAmDIIQH/3IN2uJ6u4Xl2+gFCpp8RP0F//Rj2llnEsnRsl'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 7.3',
+        message: '2Xb8',
+        seed: 'dqdeW2FXpVbPiIS7LkXCk91UXPV2p15bYVelVs+IhLs=',
+        encrypted: 'GpTkYrRFNyD9Jw1Pc1TSPSfc9Yb8k4Fw1l4kCwqPodlAboKMJe+yuXoGgVeB7Jb7JTQklGpQc1keZUzUUVZO0Q4qYUelFFe5lWM2uhq21VCbvcchrMTP6Wjts05hVgJHklLKF5cOtBGpQC0FlkwCYoXUAk//wejNycM/ZXw+ozkB'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 7.4',
+        message: '1HOGI98iOqQ4Q9+EZ1NMQdAT4MgDxiTiY2ZrI5veQKXymuuN5549qmHdA3D0m9SwE4NLmCEq72scXuNzs8s=',
+        seed: 'eGYxSmrW8rJQo1lB2yj1hktYWFl4ZjFKatbyslCjWUE=',
+        encrypted: 'G5GJEPP4ifUCg+3OHEq41DFvaucXwgdSGyuDX6/yQ1+e30d0OIjIFv4JTUXv6Oi8/uADg+EN5Ug+lEyf0RNSS4wfKgRfAaXK6x1U8dh48g/bED27ZCZ+MjhAkUcjMO0h4m3nNfLxAju7nxO2cJzNI9n1TBCMngJBco0zzhOvMZaN'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 7.5',
+        message: 'u0cjHKXqHTrUbJk0XZqKYQ==',
+        seed: 'shZu1HLVjbEMqyxrAAzM8Qp9xQmyFm7UctWNsQyrLGs=',
+        encrypted: 'HebBc/6i18c2FbG7ibWuxyQgtiN1uhtxyNsXw1Kuz8zo7RkBkt5JZEwucKyXFSwI6drZlK6QaqCRZwPQsdc2wnZlQzbkilVf1TiACqzDdpKX5i+SbCTUsOyGETV3vtxFe7/SatEKseFSLEWkIfZxAFcisIs5hWmLJdqfWQeYuMrK'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 7.6',
+        message: 'IYSCcJXTXD+G9gDo5ZdUATKW',
+        seed: 'Umc73iyhZsKqRhMawdyAjWfX07FSZzveLKFmwqpGExo=',
+        encrypted: 'DX+W3vsdnJfe63BVUFYgCAG1VmTqG/DbQ4nZgWTUGHhuijUshLtz07dHar21GJ9Ory8QQPX67PgKGnBMp0fJBnqKO3boMOEcc52HEnQxOWIW2h2rgmDtnaYtvK85pddXzhbuXkXg4DDnoMy+4XzgbLfArK12deGB0wruBbQyv3Ar'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha256', examples);
+
+      // Example 8: A 1031-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'W98OMNMh3aUUf4gkCPppGVSA34+A0/bov1gYUE82QnypsfVUC5xlqPaXTPhEeiRNkoAgG7Sfy75jeNGUTNIn4jD5bj0Q+Bnc7ydsZKALKktnAefQHeX6veOx6aDfgvRjE1nNImaWR/uxcXJGE07XtJfP/73EK1nHOpbtkBZiEt/3';
+      exponent = 'AQAB';
+      d = 'D30enlqqJf0T5KBmOuFE4NFfXNGLzbCd8sx+ZOPF6RWtYmRTBBYdCYxxW7eri9AdB+rz/tfH7QivKopi70SrFrMg4Ur3Kkj5av4mKgrkz2XmNekQeQzU7lzqdopLJjn35vZ3s/C7a+MrdXR9iQkDbwJk9Y1AHNuhMXFhV6dez2Mx';
+      p = 'CgLvhEjZ+ti70NAEyMKql1HvlyHBsNAyNqVLDflHy67VolXuno4g1JHqFyP+CUcEqXYuiK/RbrtZlEEsqWbcT58=';
+      q = 'CS02Ln7ToL/Z6f0ObAMBtt8pFZz1DMg7mwz01u6nGmHgArRuCuny3mLSW110UtSYuByaxvxYWT1MP7T11y37sKk=';
+      dP = 'B8cUEK8QOWLbNnQE43roULqk6cKd2SFFgVKUpnx9HG3tJjqgMKm2M65QMD4UA10a8BQSPrpoeCAwjY68hbaVfX0=';
+      dQ = 'rix1OAwCwBatBYkbMwHeiB8orhFxGCtrLIO+p8UV7KnKKYx7HKtYF6WXBo/IUGDeTaigFjeKrkPH+We8w3kEuQ==';
+      qInv = 'BZjRBZ462k9jIHUsCdgF/30fGuDQF67u6c76DX3X/3deRLV4Mi9kBdYhHaGVGWZqqH/cTNjIj2tuPWfpYdy7o9A=';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 8.1',
+        message: 'BQt1Xl5ogPe56daSp0w3quRJsxv+pt7/g3R6iX9sLIJbsa2/hQo8lplLXeWzPLx9SheROnln',
+        seed: 'dwb/yh7PsevuKlXlxuJM0nl6QSV3Bv/KHs+x6+4qVeU=',
+        encrypted: 'DZZvGJ61GU6OOkaPl2t8iLNAB1VwLjl3RKd/tcu19Vz9j68fjCFBQvASq9FK6Sul/N6sXIVsi4ypx/1m77bErYJqiGwkE8sQz/g4ViwQmeCvpfbCoq00B5LxklezvhnM5OeSxFtO/8AtYimLrJ3sUmDYk7xkDI20/Lb8/pyOFsjH'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 8.2',
+        message: 'TraNzZPKmxnfERvUNgj1VwJv5KodXPrCJ6PrWrlUjBigbd7SP4GCWYay/NcRCezvfv+Ihz8HXCqgxGn2nJK8',
+        seed: 'o3F9oUO03P+8dCZlqPqVBYVUg0OjcX2hQ7Tc/7x0JmU=',
+        encrypted: 'DwsnkHG2jNLgSU4LEvbkOuSaQ9+br9t3dwen8KDGmLkKVJgWGu+TNxyyo2gsBCw7S4eqEFrl49ENEhMehdjrHCBLrEBrhbHxgncKrwIXmcjX1DOCrQXEfbT4keig8TaXkasow5qby9Ded6MWcLu1dZnXPfiuiXaOYajMGJ1D3/y7'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 8.3',
+        message: 'hgSsVjKMGrWtkXhh',
+        seed: '7gYgkHPMoCa7Jk5Rhb+MaLdzn4buBiCQc8ygJrsmTlE=',
+        encrypted: 'PAKF3K/lSKcZKWQDr56LmmVqSltcaKEfS7G6+rwG239qszt8eKG6fMYJsP4h7ZfXyV1zuIZXTVhXgiRQbA9os0AhkWiMJJouhsAn60R20BOLQOtQxlXxVOvUMPGuG5EP2O+nTI0VCXky5kHNJdfJolSW+pJLVdSu4mX9Ooga1CSx'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 8.4',
+        message: '/dpfv27DYanZpKxoryFqBob0OLHg5cNrlV904QfznA3dzA==',
+        seed: 'mQrVc9xIqXMjW22CVDYY8ulVEF2ZCtVz3EipcyNbbYI=',
+        encrypted: 'LcCLDDj2S5ZOKwpvobOk6rfBWMBbxs3eWR+Edk3lKaoEAFFD5sQv0AaIs3r7yI8sOir9HvS6GKf+jc9t31zIDCIJc3sKVyrNZfEeUFSvihjbPZDo6IaZ8Jau8woE2p1z7n9rG+cbMwKuILRPSEN4hE0QSA/qz0wcye6bjb6NbK20'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 8.5',
+        message: 'Sl9JFL7iXePGk0HeBw==',
+        seed: '7MY7KPB1byL1Ksjm7BJRpuwwRxjsxjso8HVvIvUqyOY=',
+        encrypted: 'U+PBTMw6peNHnFQ3pwix/KvVeWu1nSKQGcr9QPpYNgHhuFZK6ARR7XhKxFtDoUMP+iVTXprcow4lr1Uaw4PnEx+cPe0Khl15R8GLiuh5Vm9p3lTPW1u58iP2Oa81pQZTB5AuFiD0fnFZsl+BYfjDVah3cMIu83KOBRPOdLY0j8iq'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 8.6',
+        message: 'jgfWb3uICnJWOrzT81CSvDNAn7f4jyRyvg==',
+        seed: 'OSXHGzYtQKCm3kIUVXm6Hn3UWfw5JccbNi1AoKbeQhQ=',
+        encrypted: 'WK9hbyje7E0PLeXtWaJxqD4cFkdL5x4vawlKJSOO1OKyZ6uaY8vMYBhBO47xRIqtab5Ul5UGCwdvwPR69PpARsiiSfJHVavkihXixHGgZViGTMU/7J7ftSiNT9hAwrj4JL4f1+8RhTp6WKRzsXAEKpvLK0TrzQL3ioF3QtTsiu/a'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha256', examples);
+
+      // Example 9: A 1536-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'zyzUHjTKOnKOpcuK/2TDbSe971Nk4zb9aNMSPFoZaowocBPoU9UVbVjRUZVFIPtPbXsXq7aBd2WQnFdhGWWdkCsZBu2KKxDBVcJNEkUo2rnurjeb6sZuSkEXhty4/QBi68Aw3hIZoEwqjBt90xMeTWtsruLjGl7UGsFQmy7x7iqxg2S+VoypQcJezIT/nWQ7XsGqrhAqINc/R5t4D9bakQdSEtnqwDoGdNiZ66LkMfTES2Fba6IjK9SzO67XPWJd';
+      exponent = 'AQAB';
+      d = 'GYwUHiNxWpK8z2oRmlvBE4lGjSgR9UjXJ+F7SrDrmG1vIR77U7cffMvqh+5px17mFQCMUzLetSvzkKvfv+N9cgU2gVmyY4wd4ybiHSIlHw+1hIs78VAF0qdDMPCv6RbuYszBNE0dg6cJ5gZ2JzhA9/N3QkpeCk2nXwGzH/doGc+cv90hUkPDkXwD7zgZkxLlZ7O/eu06tFfzce+KFCP0W2jG4oLsERu6KDO5h/1p+tg7wbjGE8Xh6hbBHtEl6n7B';
+      p = '/I1sBL7E65qBksp5AMvlNuLotRnezzOyRZeYxpCd9PF2230jGQ/HK4hlpxiviV8bzZFFKYAnQjtgXnCkfPWDkKjD6I/IxI6LMuPaIQ374+iB6lZ0tqNIwh6T+eVepl79';
+      q = '0gDUXniKrOpgakAdBGD4fdXBAn4S3BoNdYbok52c94m0D1GsBEKWHefSHMIeBcgxVcHyqpGTOHz9+VbLSNFTuicEBvm7ulN9SYfZ4vmULXoUy//+p0/s3ako0j4ln17h';
+      dP = '2xaAL3mi8NRfNY1p/TPkS4H66ChiLpOlQlPpl9AbB0N1naDoErSqTmyL6rIyjVQxlVpBimf/JqjFyAel2jVOBe8xzIz3WPRjcylQsD4mVyb7lOOdalcqJiRKsI23V1Kt';
+      dQ = 'oKMXz+ffFCP4em3uhFH04rSmflSX8ptPHk6DC5+t2UARZwJvVZblo5yXgX4PXxbifhnsmQLgHX6m+5qjx2Cv7h44G2neasnAdYWgatnEugC/dcitL6iYpHnoCuKU/tKh';
+      qInv = 'CyHzNcNTNC60TDqiREV4DC1lW5QBdMrjjHyKTmSTwLqf0wN0gmewg7mnpsth5C2zYrjJiW23Bk4CrVrmFYfaFbRknJBZSQn+s328tlS+tyaOyAHlqLSqORG+vYhULwW+';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 9.1',
+        message: '9zX9VbqSWSw7Urj5xPaaqhy++P6IrdCVWVQSRn+c9OwLiWxZ7aFiEOdUnIq7EM28IaEuyba1uP0vEDmetg==',
+        seed: 'jsll8TSj7Jkx6SocoNyBadXqcFyOyWXxNKPsmTHpKhw=',
+        encrypted: 'kuBqApUSIPP0yfNvL0I57K2hReD8CcPhiYZFlPPmdM0cVFQHvdPMjQ2GcEekoBMk2+JR2H3IY6QF0JcANECuoepAuEvks/XolStfJNyUVUO3vLbWGlA1JOOSPiWElIdM0hmLN5In0DizqQit7R0mzH3Y1vUGPBhzOnNgNVQgrWVvqJugjG1SPY7LaZxIjhnz3O/8EkCpxLWcyWkFLX+ujnxIKxAqjmvEztiwLLlcWbJKILOy7KE1lctyh58wYP6e'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 9.2',
+        message: 'gbkGYFAVpjqr5C3fEeGXiRL1QEx0dLJtzj7Ugr+WHsyBi/QgxUZZ',
+        seed: '7LG4sl+lDNqwjlYEKGf0r1gm0WzssbiyX6UM2rCOVgQ=',
+        encrypted: 'iNEGT/cssEWuXX1C+SWyMK1hhUjRdNz9l8FeCBhftw8I5JUY1EDXPi2hUpESGxbJOjbyricua+QVvfP/UeoPfOxCcpRSoA3DlviB0ExCJTpCb2NSv9qXtw6Z7qEqk2YyQD7mAsGb2/Y3ug3KkKrF68MAxsWFV3tmL2NV2h+yfW6qz1dVAbAIUZeRuLIbaLdY9F7O4yPC68zkaX9NcTg0tOtnAJth9fMAOFX8sVbvKBgeOuVHV1A8HcAbmqkLMIyp'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 9.3',
+        message: '/TJkKd+biQ4JtUsYuPNPHiQ=',
+        seed: '6JuwMsbOYiy9tTvJRmAU6nf3d8Dom7Ayxs5iLL21O8k=',
+        encrypted: 'JiwfWprF58xVjVRR9B9r0mhomwU5IzkxXCZDgYJwYUcacmrz+KRLKMmtCMN7DLA2lOsfK+72mU+RLmhwfAAhBYmLGR8dLLstazb5xzU9wIM9u3jAl5iyyMLSo6wk/3SH0f7vC2bnFtMkhoHsd3VSTpzl5Q+SqX/4Q1JAMGWMMiHdyjCH+WaXNdTrboPEnPVtTcBGthkkYu8r/G0IkBR6OMPCZFgl/J4uiRTGCRbZx7UC02g6+qNMQY+ksygV6R8w'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 9.4',
+        message: '8UWbXwyS8BoPcjouVmJITY+MCiD8KdrWrNQ7tfPv/fThtj4H/f5mKNDXTKGb8taeSgq/htKTklp5Z3L4CI4=',
+        seed: 'YG87mcC5zNdx6qKeoOTIhPMYnMxgbzuZwLnM13Hqop4=',
+        encrypted: 'YXo+2y1QMWzjHkLtCW6DjdJ6fS5qdm+VHALYLFhG/dI1GmOwGOiOrFqesc5KPtWE73N5nJ680e6iYQYdFIsny6a4VH9mq/2Lr6qasMgM27znPzK8l6uQ1pTcDu1fJ4gCJABshzVEXzeTWx6OyKZsOFL8eXiNCwQpyfP9eH0tRjc+F75H3dnzX6AEVff4t0yKjDqp7aRMxFZGidcMJ6KetiNXUg1dhs/lHzItdQ7oMSUAgMnHYAvJDGqy5L4F8XXM'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 9.5',
+        message: 'U+boxynW+cMZ3TF+dLDbjkzMol88gwV0bhN6xjpj7zc557WVq7lujVXlT3vUGrQzN4/7kR0=',
+        seed: '/LxCFALp7KvGCCr6QLpfJlIshA78vEIUAunsq8YIKvo=',
+        encrypted: 'fwY+yhF2kyhotPKPlHEXcTOqVRG8Kg9bDJE/cSPUOoyVoiV0j57o9xpEYtZBuM5RanPUsTDcYNvorKqP5mbN81JV3SmEkIRTL7JoHGpJAMDHFjXBfpAwgUCPhfJ2+CUCIyOoPZqlt4w+K9l+WeFZYDatr0HC1NO+stbvWq358HRdX27TexTocG5OEB4l9gqhnUYD2JHNlGidsm0vzFQJoIMaH26x9Kgosg6tZQ0t3jdoeLbTCSxOMM9dDQjjK447'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 9.6',
+        message: 'trKOohmNDBAIvGQ=',
+        seed: 'I6reDh4Iu5uaeNIwKlL5whsuG6Ijqt4OHgi7m5p40jA=',
+        encrypted: 'PISd/61VECapJ7gfG4J2OroSl69kvIZD2uuqmiro3E4pmXfpdOW/q+1WCr574Pjsj/xrIUdgmNMAl8QjciO/nArYi0IFco1tCRLNZqMDGjzZifHIcDNCsvnKg/VRmkPrjXbndebLqMtw7taeVztYq1HKVAoGsdIvLkuhmsK0Iaesp+/8xka40c9hWwcXHsG+I7pevwFarxQQbuUjXSkZ2ObWgzgGSiGCw9QNUGpO0usATLSd0AFkeE+IM/KAwJCy'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha256', examples);
+
+      // Example 10: A 2048-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'rkXtVgHOxrjMBfgDk1xnTdvg11xMCf15UfxrDK7DE6jfOZcMUYv/ul7Wjz8NfyKkAp1BPxrgfk6+nkF3ziPn9UBLVp5O4b3PPB+wPvETgC1PhV65tRNLWnyAha3K5vovoUF+w3Y74XGwxit2Dt4jwSrZK5gIhMZB9aj6wmva1KAzgaIv4bdUiFCUyCUG1AGaU1ooav6ycbubpZLeGNz2AMKu6uVuAvfPefwUzzvcfNhP67v5UMqQMEsiGaeqBjrvosPBmA5WDNZK/neVhbYQdle5V4V+/eYBCYirfeQX/IjY84TE5ucsP5Q+DDHAxKXMNvh52KOsnX1Zhg6q2muDuw==';
+      exponent = 'AQAB';
+      d = 'BWsEIW/l81SsdyUKS2sMhSWoXFmwvYDFZFCiLV9DjllqMzqodeKR3UP0jLiLnV/A1Jn5/NHDl/mvwHDNnjmMjRnmHbfHQQprJnXfv100W4BNIBrdUC1c4t/LCRzpmXu+vlcwbzg+TViBA/A29+hdGTTRUqMj5KjbRR1vSlsbDxAswVDgL+7iuI3qStTBusyyTYQHLRTh0kpncfdAjuMFZPuG1Dk6NLzwt4hQHRkzA/E6IoSwAfD2Ser3kyjUrFxDCrRBSSCpRg7Rt7xA7GU+h20Jq8UJrkW1JRkBFqDCYQGEgphQnBw786SD5ydAVOFelwdQNumJ9gkygHtSV3UeeQ==';
+      p = '7PWuzR5VFf/6y9daKBbG6/SQGM37RjjhhdZqc5a2+AkPgBjH/ZXMNLhX3BfwzGUWuxNGq01YLK2te0EDNSOHtwM40IQEfJ2VObZJYgSz3W6kQkmSB77AH5ZCh/9jNsOYRlgzaEb1bkaGGIHBAjPSF2vxWl6W3ceAvIaKp30852k=';
+      q = 'vEbEZPxqxMp4Ow6wijyEG3cvfpsvKLq9WIroheGgxh5IWKD7JawpmZDzW+hRZMJZuhF1zdcZJwcTUYSZK2wpt0bdDSyr4UKDX30UjMFhUktKCZRtSLgoRz8c52tstohsNFwD4F9B1RtcOpCj8kBzx9dKT+JdnPIcdZYPP8OGMYM=';
+      dP = 'xzVkVx0A+xXQij3plXpQkV1xJulELaz0K8guhi5Wc/9qAI7U0uN0YX34nxehYLQ7f9qctra3QhhgmBX31FyiY8FZqjLSctEn+vS8jKLXc3jorrGbCtfaPLPeCucxSYD2K21LCoddHfA8G645zNgz72zX4tlSi/CE0flp55Tp9sE=';
+      dQ = 'Jlizf235wQML4dtoEX+p2H456itpO35tOi9wlHQT7sYULhj7jfy2rFRdfIagrUj4RXFw8O+ya8SBJsU+/R0WkgGY3CoRB9woLbaoDNMGI2C6P6E/cOQxL/GmzWuPxM2cXD2xfG1qVyEvc64p9hkye61ZsVOFhYW6Tii2CmKkXkk=';
+      qInv = 'bzhSazklCFU07z5BWoNu3ouGFYosfL/sywvYNDBP7Gg7qNT0ecQz1DQW5jJpYjzqEAd22Fr/QB0//2EO5lQRzjsTY9Y6lwnu3kJkfOpWFJPVRXCoecGGgs2XcQuWIF7DERfXO182Ij+t1ui6kN18DuYdROFjJR4gx/ZuswURfLg=';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 10.1',
+        message: 'i7pr+CpsD4bV8XVul5VocLCJU7BrTrIFvBaU7g==',
+        seed: 'R+GrcRn+5WyV7l6q2G9A0KpjvTNH4atxGf7lbJXuXqo=',
+        encrypted: 'iXCnHvFRO1zd7U4HnjDMCLRvnKZj6OVRMZv8VZCAyxdA1T4AUORzxWzAtAAA541iVjEs1n5MIrDkymBDk3cM1oha9XCGsXeazZpW2z2+4aeaM3mv/oz3QYfEGiet415sHNnikAQ9ZmYg2uzBNUOS90h0qRAWFdUV5Tyxo1HZ0slg37Ikvyu2d6tgWRAAjgiAGK7IzlU4muAfQ4GiLpvElfm+0vch7lhlrk7t5TErhEF7RWQe16lVBva7azIxlGyyrqOhYrmQ+6JQpmPnsmEKYpSxTUP2tLzoSH5e+Y0CSaD7ZB20PWILB+7PKRueJ23hlMYmnAgQBePWSUdsljXAgA=='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 10.2',
+        message: '5q0YHwU7WKkE8kV1EDc+Vw==',
+        seed: 'bRf1tMH/rDUdGVv3sJ0J8JpAec9tF/W0wf+sNR0ZW/c=',
+        encrypted: 'I3uBbIiYuvEYFA5OtRycm8zxMuuEoZMNRsPspeKZIGhcnQkqH8XEM8iJMeL6ZKA0hJm3jj4z1Xz7ra3tqMyTiw3vGKocjsYdXchK+ar3Atj/jXkdJLeIiqfTBA+orCKoPbrBXLllt4dqkhc3lbq0Z5lTBeh6caklDnmJGIMnxkiG3vON/uVpIR6LMBg+IudMCMOv2f++RpBhhrI8iJOsPbnebdMIrxviVaVxT22GUNehadT8WrHI/qKv+p1rCpD3AAyXAhJy7KKp1l+nPCy1IY1prey+YgBxCAnlHuHv2V7q1FZTRXMJe3iKubLeiX6SfAKU1sivpoqk5ntMSMgAfw=='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 10.3',
+        message: 'UQos9g6Gb6I0BVPJTqOfvCVjEeg+lEVLQSQ=',
+        seed: 'OFOHUU3szHx0DdjN+druSaHL/VQ4U4dRTezMfHQN2M0=',
+        encrypted: 'n3scq/IYyBWbaN4Xd+mKJ0bZQR10yiSYzdjV1D1K3xiH11Tvhbj59PdRQXflSxE1QMhxN0jp9/tsErIlXqSnBH2XsTX6glPwJmdgXj7gZ1Aj+wsl15PctCcZv0I/4imvBBOSEd5TRmag3oU7gmbpKQCSHi6Hp2z5H/xEHekrRZemX7Dwl6A8tzVhCBpPweKNpe34OLMrHcdyb0k/uyabEHtzoZLpiOgHRjqi7SHr2ene9PPOswH7hc87xkiKtiFOpCeCScF6asFeiUTn5sf5tuPHVGqjKskwxcm/ToW3hm7ChnQdYcPRlnHrMeLBJt6o6xdrg6+SvsnNzctLxes0gA=='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 10.4',
+        message: 'vN0ZDaO30wDfmgbiLKrip18QyR/2Z7fBa96LUwZKJkmpQEXJ',
+        seed: 'XKymoPdkFhqWhPhdkrbg7zfKi2VcrKag92QWGpaE+F0=',
+        encrypted: 'KWbozLkoxbGfY0Dixr8GE/JD+MDAXIUFzm7K5AYscTvyAh9EDkLfDc/i8Y9Cjz/GXWsrRAlzO9PmLj4rECjbaNdkyzgYUiXSVV0SWmEF62nhZcScf+5QWHgsv6syu2VXdkz9nW4O3LWir2M/HqJ6kmpKVm5o7TqeYZ7GrY25FUnFDM8DpXOZqOImHVAoh8Tim9d2V9lk2D2Av6Tdsa4SIyBDj5VcX3OVoTbqdkKj5It9ANHjXaqGwqEyj7j1cQrRzrbGVbib3qzvoFvGWoo5yzr3D8J8z/UXJ4sBkumcjrphFTDe9qQcJ5FI82ZZsChJssRcZl4ApFosoljixk0WkA=='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 10.5',
+        message: 'p91sfcJLRvndXx6RraTDs9+UfodyMqk=',
+        seed: 'lbyp44WYlLPdhp+n7NW7xkAb8+SVvKnjhZiUs92Gn6c=',
+        encrypted: 'D7UPhV1nPwixcgg47HSlk/8yDLEDSXxoyo6H7MMopUTYwCmAjtnpWp4oWGg0sACoUlzKpR3PN21a4xru1txalcOkceylsQI9AIFvLhZyS20HbvQeExT9zQGyJaDhygC/6gPifgELk7x5QUqsd+TL/MQdgBZqbLO0skLOqNG3KrTMmN0oeWgxgjMmWnyBH0qkUpV5SlRN2P3nIHd/DZrkDn/qJG0MpXh6AeNHhvSgv8gyDG2Vzdf04OgvZLJTJaTdqHuXz93t7+PQ+QfKOG0wCEf5gOaYpkFarorv9XPLhzuOauN+Dd2IPzgKH5+wjbTZlZzEn+xRyDXK7s6GL/XOZw=='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 10.6',
+        message: '6vGnOhsMRglTfeac2SKLvPuajKjGw++vBW/kp/RjTtALfDnsaSLXuOosBOus',
+        seed: 'n0fd9C6X7qhWqb28cU6zrCL26zKfR930LpfuqFapvbw=',
+        encrypted: 'FO6Mv81w3SW/oVGIgdfAbIOW1eK8/UFdwryWg3ek0URFK09jNQtAaxT+66Yn5EJrTWh8fgRn1spnAOUsY5eq7iGpRsPGE86MLNonOvrBIht4Z+IDum55EgmwCrlfyiGe2fX4Xv1ifCQMSHd3OJTujAosVI3vPJaSsbTW6FqOFkM5m9uPqrdd+yhQ942wN4m4d4TG/YPx5gf62fbCRHOfvA5qSpO0XGQ45u+sWBAtOfzxmaYtf7WRAlu+JvIjTp8I2lAfVEuuW9+TJattx9RXN8jaWOBsceLIOfE6bkgad50UX5PyEtapnJOG1j0bh5PZ//oKtIASarB3PwdWM1EQTQ=='
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha256', examples);
+    }
+
+    function _bytesToBigInteger(bytes) {
+      var buffer = UTIL.createBuffer(bytes);
+      var hex = buffer.toHex();
+      return new BigInteger(hex, 16);
+    }
+
+    function _base64ToBn(s) {
+      var decoded = UTIL.decode64(s);
+      return _bytesToBigInteger(decoded);
+    }
+
+    function checkOAEPEncryptExamples(publicKey, privateKey, md, examples) {
+      if(md === 'sha1') {
+        md = MD.sha1.create();
+      } else if(md === 'sha256') {
+        md = MD.sha256.create();
+      }
+
+      for(var i = 0; i < examples.length; ++i) {
+        var ex = examples[i];
+        it('should test ' + ex.title, function() {
+          checkOAEPEncrypt(
+            publicKey, privateKey, md, ex.message, ex.seed, ex.encrypted);
+        });
+      }
+    }
+
+    function checkOAEPEncrypt(
+      publicKey, privateKey, md, message, seed, expected) {
+      var message = UTIL.decode64(message);
+      var seed = UTIL.decode64(seed);
+      var encoded = PKCS1.encode_rsa_oaep(
+        publicKey, message, {seed: seed, md: md});
+      var ciphertext = publicKey.encrypt(encoded, null);
+      ASSERT.equal(expected, UTIL.encode64(ciphertext));
+
+      var decrypted = privateKey.decrypt(ciphertext, null);
+      var decoded = PKCS1.decode_rsa_oaep(privateKey, decrypted, {md: md});
+      ASSERT.equal(message, decoded);
+
+      // test with higher-level API, default label, and generating a seed
+      ciphertext = publicKey.encrypt(message, 'RSA-OAEP', {md: md});
+      decoded = privateKey.decrypt(ciphertext, 'RSA-OAEP', {md: md});
+      ASSERT.equal(message, decoded);
+    }
+
+    function decodeBase64PublicKey(modulus, exponent) {
+      modulus = _base64ToBn(modulus);
+      exponent = _base64ToBn(exponent);
+      return PKI.setRsaPublicKey(modulus, exponent);
+    }
+
+    function decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv) {
+      modulus = _base64ToBn(modulus);
+      exponent = _base64ToBn(exponent);
+      d = _base64ToBn(d);
+      p = _base64ToBn(p);
+      q = _base64ToBn(q);
+      dP = _base64ToBn(dP);
+      dQ = _base64ToBn(dQ);
+      qInv = _base64ToBn(qInv);
+      return PKI.setRsaPrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+    }
+
+    function makeKey() {
+      var modulus, exponent, d, p, q, dP, dQ, qInv, pubkey, privateKey;
+
+      // Example 1: A 1024-bit RSA Key Pair
+      modulus = 'qLOyhK+OtQs4cDSoYPFGxJGfMYdjzWxVmMiuSBGh4KvEx+CwgtaTpef87Wdc9GaFEncsDLxkp0LGxjD1M8jMcvYq6DPEC/JYQumEu3i9v5fAEH1VvbZi9cTg+rmEXLUUjvc5LdOq/5OuHmtme7PUJHYW1PW6ENTP0ibeiNOfFvs=';
+      exponent = 'AQAB';
+      d = 'UzOc/befyEZqZVxzFqyoXFX9j23YmP2vEZUX709S6P2OJY35P+4YD6DkqylpPNg7FSpVPUrE0YEri5+lrw5/Vf5zBN9BVwkm8zEfFcTWWnMsSDEW7j09LQrzVJrZv3y/t4rYhPhNW+sEck3HNpsx3vN9DPU56c/N095lNynq1dE=';
+      p = '0yc35yZ//hNBstXA0VCoG1hvsxMr7S+NUmKGSpy58wrzi+RIWY1BOhcu+4AsIazxwRxSDC8mpHHcrSEurHyjnQ==';
+      q = 'zIhT0dVNpjD6wAT0cfKBx7iYLYIkpJDtvrM9Pj1cyTxHZXA9HdeRZC8fEWoN2FK+JBmyr3K/6aAw6GCwKItddw==';
+      dP = 'DhK/FxjpzvVZm6HDiC/oBGqQh07vzo8szCDk8nQfsKM6OEiuyckwX77L0tdoGZZ9RnGsxkMeQDeWjbN4eOaVwQ==';
+      dQ = 'lSl7D5Wi+mfQBwfWCd/U/AXIna/C721upVvsdx6jM3NNklHnkILs2oZu/vE8RZ4aYxOGt+NUyJn18RLKhdcVgw==';
+      qInv = 'T0VsUCSTvcDtKrdWo6btTWc1Kml9QhbpMhKxJ6Y9VBHOb6mNXb79cyY+NygUJ0OBgWbtfdY2h90qjKHS9PvY4Q==';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      return {publicKey: pubkey, privateKey: privateKey};
+    }
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/pki',
+    'forge/pkcs1',
+    'forge/md',
+    'forge/jsbn',
+    'forge/util'
+  ], function(PKI, PKCS1, MD, JSBN, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      PKI(),
+      PKCS1(),
+      MD(),
+      JSBN(),
+      UTIL()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/pki')(),
+    require('../../js/pkcs1')(),
+    require('../../js/md')(),
+    require('../../js/jsbn')(),
+    require('../../js/util')());
+}
+
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/pkcs12.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/pkcs12.js
new file mode 100644
index 0000000..bbf1eea
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/pkcs12.js
@@ -0,0 +1,650 @@
+(function() {
+
+function Tests(ASSERT, FORGE) {
+  var forge = FORGE();
+  var PKCS12 = forge.pkcs12;
+  var ASN1 = forge.asn1;
+  var PKI = forge.pki;
+  var UTIL = forge.util;
+
+  var _data;
+  describe('pkcs12', function() {
+    it('should create certificate-only p12', function() {
+      var p12Asn = PKCS12.toPkcs12Asn1(null, _data.certificate, null, {
+        useMac: false,
+        generateLocalKeyId: false
+      });
+      var p12Der = ASN1.toDer(p12Asn).getBytes();
+
+      /* The generated PKCS#12 file lacks a MAC, therefore pass -nomacver to
+        OpenSSL: openssl pkcs12 -nomacver -nodes -in pkcs12_certonly.p12 */
+      ASSERT.equal(p12Der, UTIL.decode64(_data.p12certonly));
+    });
+
+    it('should create key-only p12', function() {
+      var privateKey = PKI.privateKeyFromPem(_data.privateKey);
+      var p12Asn = PKCS12.toPkcs12Asn1(privateKey, null, null, {
+        useMac: false,
+        generateLocalKeyId: false
+      });
+      var p12Der = ASN1.toDer(p12Asn).getBytes();
+
+      /* The generated PKCS#12 file lacks a MAC, therefore pass -nomacver to
+        OpenSSL: openssl pkcs12 -nomacver -nodes -in pkcs12_keyonly.p12 */
+      ASSERT.equal(p12Der, UTIL.decode64(_data.p12keyonly));
+    });
+
+    it('should create encrypted-key-only p12', function() {
+      /* Note we need to mock the PRNG, since the PKCS#12 file uses encryption
+        which otherwise would differ each time due to the randomized IV. */
+      var oldRandomGenerate = forge.random.generate;
+      forge.random.generate = function(num) {
+        return UTIL.createBuffer().fillWithByte(0, num).getBytes();
+      };
+
+      var privateKey = PKI.privateKeyFromPem(_data.privateKey);
+      var p12Asn = PKCS12.toPkcs12Asn1(privateKey, null, 'nopass', {
+        useMac: false,
+        generateLocalKeyId: false
+      });
+      var p12Der = ASN1.toDer(p12Asn).getBytes();
+
+      // restore old random function
+      forge.random.generate = oldRandomGenerate;
+
+      /* The generated PKCS#12 file lacks a MAC, therefore pass -nomacver to
+        OpenSSL: openssl pkcs12 -nomacver -in pkcs12_enckeyonly.p12 */
+      ASSERT.equal(p12Der, UTIL.decode64(_data.p12enckeyonly));
+    });
+
+    it('should import certificate-only p12', function() {
+      var p12Der = UTIL.decode64(_data.p12certonly);
+      var p12Asn1 = ASN1.fromDer(p12Der);
+      var p12 = PKCS12.pkcs12FromAsn1(p12Asn1);
+      ASSERT.equal(p12.version, 3);
+
+      // PKCS#12 PFX has exactly one SafeContents; it is not encrypted
+      ASSERT.equal(p12.safeContents.length, 1);
+      ASSERT.equal(p12.safeContents[0].encrypted, false);
+
+      // SafeContents has one SafeBag which has one CertBag with the cert
+      ASSERT.equal(p12.safeContents[0].safeBags.length, 1);
+      ASSERT.equal(p12.safeContents[0].safeBags[0].type, PKI.oids.certBag);
+
+      // check cert's serial number
+      ASSERT.equal(
+        p12.safeContents[0].safeBags[0].cert.serialNumber,
+        '00d4541c40d835e2f3');
+    });
+
+    it('should import key-only p12', function() {
+      var p12Der = UTIL.decode64(_data.p12keyonly);
+      var p12Asn1 = ASN1.fromDer(p12Der);
+      var p12 = PKCS12.pkcs12FromAsn1(p12Asn1);
+      ASSERT.equal(p12.version, 3);
+
+      // PKCS#12 PFX has exactly one SafeContents; it is not encrypted
+      ASSERT.equal(p12.safeContents.length, 1);
+      ASSERT.equal(p12.safeContents[0].encrypted, false);
+
+      // SafeContents has one SafeBag which has one KeyBag with the key
+      ASSERT.equal(p12.safeContents[0].safeBags.length, 1);
+      ASSERT.equal(p12.safeContents[0].safeBags[0].type, PKI.oids.keyBag);
+
+      // compare the key from the PFX by comparing both primes
+      var expected = PKI.privateKeyFromPem(_data.privateKey);
+      ASSERT.deepEqual(p12.safeContents[0].safeBags[0].key.p, expected.p);
+      ASSERT.deepEqual(p12.safeContents[0].safeBags[0].key.q, expected.q);
+    });
+
+    it('should import encrypted-key-only p12', function() {
+      var p12Der = UTIL.decode64(_data.p12enckeyonly);
+      var p12Asn1 = ASN1.fromDer(p12Der);
+      var p12 = PKCS12.pkcs12FromAsn1(p12Asn1, 'nopass');
+      ASSERT.equal(p12.version, 3);
+
+      // PKCS#12 PFX has exactly one SafeContents; it is *not* encrypted,
+      // only the key itself is encrypted (shrouded)
+      ASSERT.equal(p12.safeContents.length, 1);
+      ASSERT.equal(p12.safeContents[0].encrypted, false);
+
+      // SafeContents has one SafeBag which has one shrouded KeyBag with the key
+      ASSERT.equal(p12.safeContents[0].safeBags.length, 1);
+      ASSERT.equal(
+        p12.safeContents[0].safeBags[0].type, PKI.oids.pkcs8ShroudedKeyBag);
+
+      // compare the key from the PFX by comparing both primes
+      var expected = PKI.privateKeyFromPem(_data.privateKey);
+      ASSERT.deepEqual(p12.safeContents[0].safeBags[0].key.p, expected.p);
+      ASSERT.deepEqual(p12.safeContents[0].safeBags[0].key.q, expected.q);
+    });
+
+    it('should import an encrypted-key-only p12', function() {
+      var p12Der = UTIL.decode64(_data.p12enckeyonly);
+      var p12Asn1 = ASN1.fromDer(p12Der);
+      var p12 = PKCS12.pkcs12FromAsn1(p12Asn1, 'nopass');
+      ASSERT.equal(p12.version, 3);
+
+      // PKCS#12 PFX has exactly one SafeContents; it is *not* encrypted,
+      // only the key itself is encrypted (shrouded)
+      ASSERT.equal(p12.safeContents.length, 1);
+      ASSERT.equal(p12.safeContents[0].encrypted, false);
+
+      // SafeContents has one SafeBag which has one shrouded KeyBag with the key
+      ASSERT.equal(p12.safeContents[0].safeBags.length, 1);
+      ASSERT.equal(
+        p12.safeContents[0].safeBags[0].type, PKI.oids.pkcs8ShroudedKeyBag);
+
+      // compare the key from the PFX by comparing both primes
+      var expected = PKI.privateKeyFromPem(_data.privateKey);
+      ASSERT.deepEqual(p12.safeContents[0].safeBags[0].key.p, expected.p);
+      ASSERT.deepEqual(p12.safeContents[0].safeBags[0].key.q, expected.q);
+    });
+
+    it('should import an encrypted p12 with keys and certificates', function() {
+      var p12Der = UTIL.decode64(_data.p12encmixed);
+      var p12Asn1 = ASN1.fromDer(p12Der);
+      var p12 = PKCS12.pkcs12FromAsn1(p12Asn1, '123456');
+      ASSERT.equal(p12.version, 3);
+
+      // PKCS#12 PFX has two SafeContents; first is *not* encrypted but
+      // contains two shrouded keys, second is encrypted and has six
+      // certificates
+      ASSERT.equal(p12.safeContents.length, 2);
+
+      ASSERT.equal(p12.safeContents[0].encrypted, false);
+      ASSERT.equal(p12.safeContents[0].safeBags.length, 2);
+      ASSERT.equal(p12.safeContents[0].safeBags[0].type, PKI.oids.pkcs8ShroudedKeyBag);
+      ASSERT.equal(p12.safeContents[0].safeBags[0].attributes.friendlyName.length, 1);
+      ASSERT.equal(p12.safeContents[0].safeBags[0].attributes.friendlyName[0], 'encryptionkey');
+      ASSERT.equal(p12.safeContents[0].safeBags[0].attributes.localKeyId.length, 1);
+      ASSERT.equal(p12.safeContents[0].safeBags[0].attributes.localKeyId[0], 'Time 1311855238964');
+
+      ASSERT.equal(p12.safeContents[0].safeBags[1].type, PKI.oids.pkcs8ShroudedKeyBag);
+      ASSERT.equal(p12.safeContents[0].safeBags[1].attributes.friendlyName.length, 1);
+      ASSERT.equal(p12.safeContents[0].safeBags[1].attributes.friendlyName[0], 'signaturekey');
+      ASSERT.equal(p12.safeContents[0].safeBags[1].attributes.localKeyId.length, 1);
+      ASSERT.equal(p12.safeContents[0].safeBags[1].attributes.localKeyId[0], 'Time 1311855238863');
+
+      ASSERT.equal(p12.safeContents[1].encrypted, true);
+      ASSERT.equal(p12.safeContents[1].safeBags.length, 6);
+
+      ASSERT.equal(p12.safeContents[1].safeBags[0].type, PKI.oids.certBag);
+      ASSERT.equal(p12.safeContents[1].safeBags[0].attributes.friendlyName.length, 1);
+      ASSERT.equal(p12.safeContents[1].safeBags[0].attributes.friendlyName[0], 'CN=1002753325,2.5.4.5=#130b3130303237353333323543');
+      ASSERT.equal(p12.safeContents[1].safeBags[0].attributes.localKeyId.length, 1);
+      ASSERT.equal(p12.safeContents[1].safeBags[0].attributes.localKeyId[0], 'Time 1311855238964');
+
+      ASSERT.equal(p12.safeContents[1].safeBags[1].type, PKI.oids.certBag);
+      ASSERT.equal(p12.safeContents[1].safeBags[1].attributes.friendlyName.length, 1);
+      ASSERT.equal(p12.safeContents[1].safeBags[1].attributes.friendlyName[0], 'CN=ElsterSoftTestCA,OU=CA,O=Elster,C=DE');
+
+      ASSERT.equal(p12.safeContents[1].safeBags[2].type, PKI.oids.certBag);
+      ASSERT.equal(p12.safeContents[1].safeBags[2].attributes.friendlyName.length, 1);
+      ASSERT.equal(p12.safeContents[1].safeBags[2].attributes.friendlyName[0], 'CN=ElsterRootCA,OU=RootCA,O=Elster,C=DE');
+
+      ASSERT.equal(p12.safeContents[1].safeBags[3].type, PKI.oids.certBag);
+      ASSERT.equal(p12.safeContents[1].safeBags[3].attributes.friendlyName.length, 1);
+      ASSERT.equal(p12.safeContents[1].safeBags[3].attributes.friendlyName[0], 'CN=1002753325,2.5.4.5=#130b3130303237353333323541');
+      ASSERT.equal(p12.safeContents[1].safeBags[3].attributes.localKeyId.length, 1);
+      ASSERT.equal(p12.safeContents[1].safeBags[3].attributes.localKeyId[0], 'Time 1311855238863');
+
+      ASSERT.equal(p12.safeContents[1].safeBags[4].type, PKI.oids.certBag);
+      ASSERT.equal(p12.safeContents[1].safeBags[4].attributes.friendlyName.length, 1);
+      ASSERT.equal(p12.safeContents[1].safeBags[4].attributes.friendlyName[0], 'CN=ElsterSoftTestCA,OU=CA,O=Elster,C=DE');
+
+      ASSERT.equal(p12.safeContents[1].safeBags[5].type, PKI.oids.certBag);
+      ASSERT.equal(p12.safeContents[1].safeBags[5].attributes.friendlyName.length, 1);
+      ASSERT.equal(p12.safeContents[1].safeBags[5].attributes.friendlyName[0], 'CN=ElsterRootCA,OU=RootCA,O=Elster,C=DE');
+    });
+
+    it('should get bags by friendly name', function() {
+      var p12Der = UTIL.decode64(_data.p12encmixed);
+      var p12Asn1 = ASN1.fromDer(p12Der);
+      var p12 = PKCS12.pkcs12FromAsn1(p12Asn1, '123456');
+      var bags = p12.getBags({friendlyName: 'signaturekey'});
+
+      ASSERT.equal(bags.friendlyName.length, 1);
+      ASSERT.equal(bags.friendlyName[0].attributes.friendlyName[0], 'signaturekey');
+    });
+
+    it('should get cert bags by friendly name', function() {
+      var p12Der = UTIL.decode64(_data.p12encmixed);
+      var p12Asn1 = ASN1.fromDer(p12Der);
+      var p12 = PKCS12.pkcs12FromAsn1(p12Asn1, '123456');
+      var bags = p12.getBags({
+        friendlyName: 'CN=1002753325,2.5.4.5=#130b3130303237353333323543',
+        bagType: PKI.oids.certBag
+      });
+
+      ASSERT.equal(bags.friendlyName.length, 1);
+      ASSERT.equal(bags.friendlyName[0].attributes.friendlyName[0], 'CN=1002753325,2.5.4.5=#130b3130303237353333323543');
+    });
+
+    it('should get all cert bags', function() {
+      var p12Der = UTIL.decode64(_data.p12encmixed);
+      var p12Asn1 = ASN1.fromDer(p12Der);
+      var p12 = PKCS12.pkcs12FromAsn1(p12Asn1, '123456');
+      var bags = p12.getBags({
+        bagType: PKI.oids.certBag
+      });
+
+      ASSERT.equal(bags[PKI.oids.certBag].length, 6);
+      for(var i = 0; i < bags[PKI.oids.certBag].length; ++i) {
+        ASSERT.equal(bags[PKI.oids.certBag][i].type, PKI.oids.certBag);
+      }
+    });
+
+    it('should get bags by local key ID', function() {
+      var p12Der = UTIL.decode64(_data.p12encmixed);
+      var p12Asn1 = ASN1.fromDer(p12Der);
+      var p12 = PKCS12.pkcs12FromAsn1(p12Asn1, '123456');
+      var bags = p12.getBags({localKeyId: 'Time 1311855238863'});
+
+      ASSERT.equal(bags.localKeyId.length, 2);
+      ASSERT.equal(bags.localKeyId[0].attributes.localKeyId[0], 'Time 1311855238863');
+      ASSERT.equal(bags.localKeyId[1].attributes.localKeyId[0], 'Time 1311855238863');
+    });
+
+    it('should get cert bags by local key ID', function() {
+      var p12Der = UTIL.decode64(_data.p12encmixed);
+      var p12Asn1 = ASN1.fromDer(p12Der);
+      var p12 = PKCS12.pkcs12FromAsn1(p12Asn1, '123456');
+      var bags = p12.getBags({
+        localKeyId: 'Time 1311855238863',
+        bagType: PKI.oids.certBag
+      });
+
+      ASSERT.equal(bags.localKeyId.length, 1);
+      ASSERT.equal(bags.localKeyId[0].attributes.localKeyId[0], 'Time 1311855238863');
+      ASSERT.equal(bags.localKeyId[0].type, PKI.oids.certBag);
+    });
+
+    it('should generate a PKCS#12 mac key', function() {
+      var salt = 'A15D6AA8F8DAFC352F9EE1C192F09966EB85D17B';
+      salt = UTIL.createBuffer(UTIL.hexToBytes(salt));
+      var expected = '03e46727268575c6ebd6bff828d0d09b0c914201263ca543';
+      var key = PKCS12.generateKey('123456', salt, 1, 1024, 24);
+      ASSERT.equal(key.toHex(), expected);
+    });
+  });
+
+  _data = {
+    certificate: '-----BEGIN CERTIFICATE-----\r\n' +
+      'MIIDtDCCApwCCQDUVBxA2DXi8zANBgkqhkiG9w0BAQUFADCBmzELMAkGA1UEBhMC\r\n' +
+      'REUxEjAQBgNVBAgMCUZyYW5jb25pYTEQMA4GA1UEBwwHQW5zYmFjaDEVMBMGA1UE\r\n' +
+      'CgwMU3RlZmFuIFNpZWdsMRIwEAYDVQQLDAlHZWllcmxlaW4xFjAUBgNVBAMMDUdl\r\n' +
+      'aWVybGVpbiBERVYxIzAhBgkqhkiG9w0BCQEWFHN0ZXNpZUBicm9rZW5waXBlLmRl\r\n' +
+      'MB4XDTEyMDMxODIyNTc0M1oXDTEzMDMxODIyNTc0M1owgZsxCzAJBgNVBAYTAkRF\r\n' +
+      'MRIwEAYDVQQIDAlGcmFuY29uaWExEDAOBgNVBAcMB0Fuc2JhY2gxFTATBgNVBAoM\r\n' +
+      'DFN0ZWZhbiBTaWVnbDESMBAGA1UECwwJR2VpZXJsZWluMRYwFAYDVQQDDA1HZWll\r\n' +
+      'cmxlaW4gREVWMSMwIQYJKoZIhvcNAQkBFhRzdGVzaWVAYnJva2VucGlwZS5kZTCC\r\n' +
+      'ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMsAbQ4fWevHqP1K1y/ewpMS\r\n' +
+      '3vYovBto7IsKBq0v3NmC2kPf3NhyaSKfjOOS5uAPONLffLck+iGdOLLFia6OSpM6\r\n' +
+      '0tyQIV9lHoRh7fOEYORab0Z+aBUZcEGT9yotBOraX1YbKc5f9XO+80eG4XYvb5ua\r\n' +
+      '1NHrxWqe4w2p3zGJCKO+wHpvGkbKz0nfu36jwWz5aihfHi9hp/xs8mfH86mIKiD7\r\n' +
+      'f2X2KeZ1PK9RvppA0X3lLb2VLOqMt+FHWicyZ/wjhQZ4oW55ln2yYJUQ+adlgaYn\r\n' +
+      'PrtnsxmbTxM+99oF0F2/HmGrNs8nLZSva1Vy+hmjmWz6/O8ZxhiIj7oBRqYcAocC\r\n' +
+      'AwEAATANBgkqhkiG9w0BAQUFAAOCAQEAvfvtu31GFBO5+mFjPAoR4BlzKq/H3EPO\r\n' +
+      'qS8cm/TjHgDRALwSnwKYCFs/bXqE4iOTD6otV4TusX3EPbqL2vzZQEcZn6paU/oZ\r\n' +
+      'ZVXwQqMqY5tf2teQiNxqxNmSIEPRHOr2QVBVIx2YF4Po89KGUqJ9u/3/10lDqRwp\r\n' +
+      'sReijr5UKv5aygEcnwcW8+Ne4rTx934UDsutKG20dr5trZfWQRVS9fS9CFwJehEX\r\n' +
+      'HAMUc/0++80NhfQthmWZWlWM1R3dr4TrIPtWdn5z0MtGeDvqBk7HjGrhcVS6kAsy\r\n' +
+      'Z9y/lfLPjBuxlQAHztEJCWgI4TW3/RLhgfg2gI1noM2n84Cdmisfkg==\r\n' +
+      '-----END CERTIFICATE-----\r\n',
+    privateKey: '-----BEGIN RSA PRIVATE KEY-----\r\n' +
+      'MIIEowIBAAKCAQEAywBtDh9Z68eo/UrXL97CkxLe9ii8G2jsiwoGrS/c2YLaQ9/c\r\n' +
+      '2HJpIp+M45Lm4A840t98tyT6IZ04ssWJro5KkzrS3JAhX2UehGHt84Rg5FpvRn5o\r\n' +
+      'FRlwQZP3Ki0E6tpfVhspzl/1c77zR4bhdi9vm5rU0evFap7jDanfMYkIo77Aem8a\r\n' +
+      'RsrPSd+7fqPBbPlqKF8eL2Gn/GzyZ8fzqYgqIPt/ZfYp5nU8r1G+mkDRfeUtvZUs\r\n' +
+      '6oy34UdaJzJn/COFBnihbnmWfbJglRD5p2WBpic+u2ezGZtPEz732gXQXb8eYas2\r\n' +
+      'zyctlK9rVXL6GaOZbPr87xnGGIiPugFGphwChwIDAQABAoIBAAjMA+3QvfzRsikH\r\n' +
+      'zTtt09C7yJ2yNjSZ32ZHEPMAV/m1CfBXCyL2EkhF0b0q6IZdIoFA3g6xs4UxYvuc\r\n' +
+      'Q9Mkp2ap7elQ9aFEqIXkGIOtAOXkZV4QrEH90DeHSfax7LygqfD5TF59Gg3iAHjh\r\n' +
+      'B3Qvqg58LyzJosx0BjLZYaqr3Yv67GkqyflpF/roPGdClHpahAi5PBkHiNhNTAUU\r\n' +
+      'LJRGvMegXGZkUKgGMAiGCk0N96OZwrinMKO6YKGdtgwVWC2wbJY0trElaiwXozSt\r\n' +
+      'NmP6KTQp94C7rcVO6v1lZiOfhBe5Kc8QHUU+GYydgdjqm6Rdow/yLHOALAVtXSeb\r\n' +
+      'U+tPfcECgYEA6Qi+qF+gtPincEDBxRtoKwAlRkALt8kly8bYiGcUmd116k/5bmPw\r\n' +
+      'd0tBUOQbqRa1obYC88goOVzp9LInAcBSSrexhVaPAF4nrkwYXMOq+76MiH17WUfQ\r\n' +
+      'MgVM2IB48PBjNk1s3Crj6j1cxxkctqmCnVaI9HlU2PPZ3xjaklfv/NsCgYEA3wH8\r\n' +
+      'mehUhiAp7vuhd+hfomFw74cqgHC9v0saiYGckpMafh9MJGc4U5GrN1kYeb/CFkSx\r\n' +
+      '1hOytD3YBKoaKKoYagaMQcjxf6HnEF0f/5OiQkUQpWmgC9lNnE4XTWjnwqaTS5L9\r\n' +
+      'D+H50SiI3VjHymGXTRJeKpAIwV74AxxrnVofqsUCgYAwmL1B2adm9g/c7fQ6yatg\r\n' +
+      'hEhBrSuEaTMzmsUfNPfr2m4zrffjWH4WMqBtYRSPn4fDMHTPJ+eThtfXSqutxtCi\r\n' +
+      'ekpP9ywdNIVr6LyP49Ita6Bc+mYVyU8Wj1pmL+yIumjGM0FHbL5Y4/EMKCV/xjvR\r\n' +
+      '2fD3orHaCIhf6QvzxtjqTwKBgFm6UemXKlMhI94tTsWRMNGEBU3LA9XUBvSuAkpr\r\n' +
+      'ZRUwrQssCpXnFinBxbMqXQe3mR8emrM5D8En1P/jdU0BS3t1kP9zG4AwI2lZHuPV\r\n' +
+      'ggbKBS2Y9zVtRKXsYcHawM13+nIA/WNjmAGJHrB45UJPy/HNvye+9lbfoEiYKdCR\r\n' +
+      'D4bFAoGBAIm9jcZkIwLa9kLAWH995YYYSGRY4KC29XZr2io2mog+BAjhFt1sqebt\r\n' +
+      'R8sRHNiIP2mcUECMOcaS+tcayi+8KTHWxIEed9qDmFu6XBbePfe/L6yxPSagcixH\r\n' +
+      'BK0KuK/fgTPvZCmIs8hUIC+AxhXKnqn4fIWoO54xLsALc0gEjs2d\r\n' +
+      '-----END RSA PRIVATE KEY-----\r\n',
+    p12certonly:
+      'MIIEHgIBAzCCBBcGCSqGSIb3DQEHAaCCBAgEggQEMIIEADCCA/wGCSqGSIb3DQEH\r\n' +
+      'AaCCA+0EggPpMIID5TCCA+EGCyqGSIb3DQEMCgEDoIID0DCCA8wGCiqGSIb3DQEJ\r\n' +
+      'FgGgggO8BIIDuDCCA7QwggKcAgkA1FQcQNg14vMwDQYJKoZIhvcNAQEFBQAwgZsx\r\n' +
+      'CzAJBgNVBAYTAkRFMRIwEAYDVQQIDAlGcmFuY29uaWExEDAOBgNVBAcMB0Fuc2Jh\r\n' +
+      'Y2gxFTATBgNVBAoMDFN0ZWZhbiBTaWVnbDESMBAGA1UECwwJR2VpZXJsZWluMRYw\r\n' +
+      'FAYDVQQDDA1HZWllcmxlaW4gREVWMSMwIQYJKoZIhvcNAQkBFhRzdGVzaWVAYnJv\r\n' +
+      'a2VucGlwZS5kZTAeFw0xMjAzMTgyMjU3NDNaFw0xMzAzMTgyMjU3NDNaMIGbMQsw\r\n' +
+      'CQYDVQQGEwJERTESMBAGA1UECAwJRnJhbmNvbmlhMRAwDgYDVQQHDAdBbnNiYWNo\r\n' +
+      'MRUwEwYDVQQKDAxTdGVmYW4gU2llZ2wxEjAQBgNVBAsMCUdlaWVybGVpbjEWMBQG\r\n' +
+      'A1UEAwwNR2VpZXJsZWluIERFVjEjMCEGCSqGSIb3DQEJARYUc3Rlc2llQGJyb2tl\r\n' +
+      'bnBpcGUuZGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDLAG0OH1nr\r\n' +
+      'x6j9Stcv3sKTEt72KLwbaOyLCgatL9zZgtpD39zYcmkin4zjkubgDzjS33y3JPoh\r\n' +
+      'nTiyxYmujkqTOtLckCFfZR6EYe3zhGDkWm9GfmgVGXBBk/cqLQTq2l9WGynOX/Vz\r\n' +
+      'vvNHhuF2L2+bmtTR68VqnuMNqd8xiQijvsB6bxpGys9J37t+o8Fs+WooXx4vYaf8\r\n' +
+      'bPJnx/OpiCog+39l9inmdTyvUb6aQNF95S29lSzqjLfhR1onMmf8I4UGeKFueZZ9\r\n' +
+      'smCVEPmnZYGmJz67Z7MZm08TPvfaBdBdvx5hqzbPJy2Ur2tVcvoZo5ls+vzvGcYY\r\n' +
+      'iI+6AUamHAKHAgMBAAEwDQYJKoZIhvcNAQEFBQADggEBAL377bt9RhQTufphYzwK\r\n' +
+      'EeAZcyqvx9xDzqkvHJv04x4A0QC8Ep8CmAhbP216hOIjkw+qLVeE7rF9xD26i9r8\r\n' +
+      '2UBHGZ+qWlP6GWVV8EKjKmObX9rXkIjcasTZkiBD0Rzq9kFQVSMdmBeD6PPShlKi\r\n' +
+      'fbv9/9dJQ6kcKbEXoo6+VCr+WsoBHJ8HFvPjXuK08fd+FA7LrShttHa+ba2X1kEV\r\n' +
+      'UvX0vQhcCXoRFxwDFHP9PvvNDYX0LYZlmVpVjNUd3a+E6yD7VnZ+c9DLRng76gZO\r\n' +
+      'x4xq4XFUupALMmfcv5Xyz4wbsZUAB87RCQloCOE1t/0S4YH4NoCNZ6DNp/OAnZor\r\n' +
+      'H5I=',
+    p12enckeyonly:
+      'MIIFcQIBAzCCBWoGCSqGSIb3DQEHAaCCBVsEggVXMIIFUzCCBU8GCSqGSIb3DQEH\r\n' +
+      'AaCCBUAEggU8MIIFODCCBTQGCyqGSIb3DQEMCgECoIIFIzCCBR8wSQYJKoZIhvcN\r\n' +
+      'AQUNMDwwGwYJKoZIhvcNAQUMMA4ECAAAAAAAAAAAAgIIADAdBglghkgBZQMEAQIE\r\n' +
+      'EAAAAAAAAAAAAAAAAAAAAAAEggTQQHIbPs0naCmJGgmtvFNmUlv9sHkm2A/vWHjY\r\n' +
+      'B8UavyYUz3IMtDCWZBoWHWp/izLDroCSxkabxyzqlSbYdGug1QY9y9RP6TjP6uaw\r\n' +
+      'SFurDe7beTRB3d8Oe2AMEmUQtaPE/zQI52aWse8RNh5P1I1wQEzVvk8/hf2eLdLQ\r\n' +
+      'cxUb0flz65Nkr4tVPsAmXfbepiyPm+lISi7syNfO6d7424CsGYXD3VCtDxbS5r0m\r\n' +
+      'L7OIkMfr7JhkvlrcdgrBY5r8/67MtfaJrMe0FR90UJd6ST++2FyhbilSz2BI6Twu\r\n' +
+      'wNICvkbThwY/LLxOCPKm4AgEj/81pYy6z2eWG59pD8fju4IOJUt3AGoPZoCQrbmD\r\n' +
+      'MDahpYgey6bo8ti9H08HhvP9keOgI2HUCQoObL0c2GF+fv6m/EJ59hpH9qeryfT4\r\n' +
+      'HAzSfd4h0YszF32a23+dGEgXAA492m00lZ/uM5nTF0RIQsqj5BJSxEEBpYequF4A\r\n' +
+      'MNCsjKl90HPSIndNSUfgN0us8FtmrzBNbmIATFE9juKHWag3p751ertsGv6e/Ofm\r\n' +
+      'xAhgF21j8ZhwXKjsVY4uYVFYLWkCLSD4gF8/ijWg873XZKzjPuy8w3SAAcya8vaQ\r\n' +
+      'buzzk5zgN0g5T+JxCAdP50FH68rVG2dhfY1BDFe8xY6mxSfs/wUj5EVD9jdqlYpu\r\n' +
+      '/o3IFtdksSra8eOwO2F/kw69x11wZaYpZaRzbIM2x1pDARkAtnbdvdSEXMOT7htA\r\n' +
+      'OYAJiZuhW0czOgumGGhIO8TBxwMh3/WjkUdlJ1JNhl6M+JHlmIKerCuP6kCLJsAp\r\n' +
+      '+RyRRp6pKa4t09G5rjAjCUiPqkCKRSf40ijhn+lLhj8ZHKLDyw4FCMo6NvQvFsDp\r\n' +
+      'dbCbbHzWGZXEspT56jGbuju1DQCiy+uVKYhigU1PvRXrxpCHKcv65iqnwPRgGE6X\r\n' +
+      'dPSGfjsLDbATvSrVv1DvJNTH9wyCSQt/eqBXFWkQeFEqKXij0atkdHL6GXRo57PX\r\n' +
+      'KZgeul2Xxd2J5SYNCUJf8IL4UOfHRMB4GaGGt9LTpPq2bI9fju1vVE4BjL1gSYIi\r\n' +
+      'cvynjH7mpzVwq+Cxj4uCo8aZQKWB0BI7c4cDaFmKPIFD47QFZUNgYCv9WfNljOe/\r\n' +
+      '+CqRbxNFUsXCR4hEoYmdn0EEI2b1F6Hkz/dDrLH33My4Gp14N8MVkASWtecxvbfa\r\n' +
+      'xkj5SiC5NZQ2TZtt3DX508BPFSqJRjb83I7qhNjWxqFUxS1ma9PF/AQzUgNLw+Gz\r\n' +
+      't5fpB3hD+33fWE8y4RbiUsBU+O56qaN9luOZLa/eVwo+I9F1LgXsS29iv6LvHO5m\r\n' +
+      '+IfzHM+FROS1XhzM+t8rxTK7VmBHqmPrKcjtnYXZh0eA9YIhTEeRdlEO8q4dsKFv\r\n' +
+      'sbQZ3+65DW6pbDbe/3CGqf43w5vbTvhsRSYqC9ojKjnUtoJ8gY+b7GPNUVsgxQCh\r\n' +
+      'jfqqZoVmhBihTO5hgeHJf+ilCbw5cPCEXobAxMfdPaasBV5xDBcvDDl7Sv16feYk\r\n' +
+      'OZJ6bm9wRkqbQUsWYMgYLCfs/FDe1kfkSeS8JYlmFIkHZL6K3LqkULnqPfQdnlMp\r\n' +
+      '1PYGlPTdp+6XcqNBVORyXkOXF7PyrOw7vRefEuGcBvZ4TT0jmHE3KxKEvJwbVsne\r\n' +
+      'H4/s3xo=',
+    p12keyonly:
+      'MIIFEAIBAzCCBQkGCSqGSIb3DQEHAaCCBPoEggT2MIIE8jCCBO4GCSqGSIb3DQEH\r\n' +
+      'AaCCBN8EggTbMIIE1zCCBNMGCyqGSIb3DQEMCgEBoIIEwjCCBL4CAQAwDQYJKoZI\r\n' +
+      'hvcNAQEBBQAEggSoMIIEpAIBAAKCAQEAywBtDh9Z68eo/UrXL97CkxLe9ii8G2js\r\n' +
+      'iwoGrS/c2YLaQ9/c2HJpIp+M45Lm4A840t98tyT6IZ04ssWJro5KkzrS3JAhX2Ue\r\n' +
+      'hGHt84Rg5FpvRn5oFRlwQZP3Ki0E6tpfVhspzl/1c77zR4bhdi9vm5rU0evFap7j\r\n' +
+      'DanfMYkIo77Aem8aRsrPSd+7fqPBbPlqKF8eL2Gn/GzyZ8fzqYgqIPt/ZfYp5nU8\r\n' +
+      'r1G+mkDRfeUtvZUs6oy34UdaJzJn/COFBnihbnmWfbJglRD5p2WBpic+u2ezGZtP\r\n' +
+      'Ez732gXQXb8eYas2zyctlK9rVXL6GaOZbPr87xnGGIiPugFGphwChwIDAQABAoIB\r\n' +
+      'AQAIzAPt0L380bIpB807bdPQu8idsjY0md9mRxDzAFf5tQnwVwsi9hJIRdG9KuiG\r\n' +
+      'XSKBQN4OsbOFMWL7nEPTJKdmqe3pUPWhRKiF5BiDrQDl5GVeEKxB/dA3h0n2sey8\r\n' +
+      'oKnw+UxefRoN4gB44Qd0L6oOfC8syaLMdAYy2WGqq92L+uxpKsn5aRf66DxnQpR6\r\n' +
+      'WoQIuTwZB4jYTUwFFCyURrzHoFxmZFCoBjAIhgpNDfejmcK4pzCjumChnbYMFVgt\r\n' +
+      'sGyWNLaxJWosF6M0rTZj+ik0KfeAu63FTur9ZWYjn4QXuSnPEB1FPhmMnYHY6puk\r\n' +
+      'XaMP8ixzgCwFbV0nm1PrT33BAoGBAOkIvqhfoLT4p3BAwcUbaCsAJUZAC7fJJcvG\r\n' +
+      '2IhnFJnddepP+W5j8HdLQVDkG6kWtaG2AvPIKDlc6fSyJwHAUkq3sYVWjwBeJ65M\r\n' +
+      'GFzDqvu+jIh9e1lH0DIFTNiAePDwYzZNbNwq4+o9XMcZHLapgp1WiPR5VNjz2d8Y\r\n' +
+      '2pJX7/zbAoGBAN8B/JnoVIYgKe77oXfoX6JhcO+HKoBwvb9LGomBnJKTGn4fTCRn\r\n' +
+      'OFORqzdZGHm/whZEsdYTsrQ92ASqGiiqGGoGjEHI8X+h5xBdH/+TokJFEKVpoAvZ\r\n' +
+      'TZxOF01o58Kmk0uS/Q/h+dEoiN1Yx8phl00SXiqQCMFe+AMca51aH6rFAoGAMJi9\r\n' +
+      'QdmnZvYP3O30OsmrYIRIQa0rhGkzM5rFHzT369puM63341h+FjKgbWEUj5+HwzB0\r\n' +
+      'zyfnk4bX10qrrcbQonpKT/csHTSFa+i8j+PSLWugXPpmFclPFo9aZi/siLpoxjNB\r\n' +
+      'R2y+WOPxDCglf8Y70dnw96Kx2giIX+kL88bY6k8CgYBZulHplypTISPeLU7FkTDR\r\n' +
+      'hAVNywPV1Ab0rgJKa2UVMK0LLAqV5xYpwcWzKl0Ht5kfHpqzOQ/BJ9T/43VNAUt7\r\n' +
+      'dZD/cxuAMCNpWR7j1YIGygUtmPc1bUSl7GHB2sDNd/pyAP1jY5gBiR6weOVCT8vx\r\n' +
+      'zb8nvvZW36BImCnQkQ+GxQKBgQCJvY3GZCMC2vZCwFh/feWGGEhkWOCgtvV2a9oq\r\n' +
+      'NpqIPgQI4RbdbKnm7UfLERzYiD9pnFBAjDnGkvrXGsovvCkx1sSBHnfag5hbulwW\r\n' +
+      '3j33vy+ssT0moHIsRwStCriv34Ez72QpiLPIVCAvgMYVyp6p+HyFqDueMS7AC3NI\r\n' +
+      'BI7NnQ==',
+    p12encmixed:
+      'MIIpiwIBAzCCKUUGCSqGSIb3DQEHAaCCKTYEgikyMIIpLjCCCtMGCSqGSIb3DQEH\r\n' +
+      'AaCCCsQEggrAMIIKvDCCBVsGCyqGSIb3DQEMCgECoIIE+jCCBPYwKAYKKoZIhvcN\r\n' +
+      'AQwBAzAaBBShXWqo+Nr8NS+e4cGS8Jlm64XRewICBAAEggTIMtqzJpmSCGAYKTj8\r\n' +
+      '1t3U0mGNAErZt0UA2EP9dcGvyG+0W+PMorwwduveGz5ymdqh+8mdbGOTTGKqLVmB\r\n' +
+      '9vR2826foDSgjB+x+fSX9UtSvf9xwF0J6VGPt64RP4J3c+5ntd/gleJCpeBW/div\r\n' +
+      'ieeSRAJ0JX/JthDvO1VyzBOb8w5lakK/mCvLpcbUMIMnF6M/TT1rreqtl8GSx9+n\r\n' +
+      '+ne4tBWCUYAZfYHuKd2tCpT+lpP8pmZ7BYEvgyWPmYkNTkbPMAct1nJcN3L89Rt0\r\n' +
+      'Yw2fg58pvzY0WlHK3H5rB4J7835jTnZLAYz2sjnlDXndwXtiH9AU3X3KQpSDHrkd\r\n' +
+      'ypBQfPHVwB7f+UiKYx5KYNjT1ph2y4DoBV6e4tnQs4KjixtKnom/9ipqOwjP2o6+\r\n' +
+      '4rkZf3I+g8ZrTQygbmQBCfdduNwnhImf7XJezK2RoW9/b9WoSPGSuzsVmt7h+hYs\r\n' +
+      'hGGx5mdk+iJTmst5MrdNx4byKLW+ThrFJ+eqUwm/d+ODQolu+gckOTyaUvNfjYy7\r\n' +
+      'JYi7wnzKAiOyNHwoP+Efpwb+eDffDyg0u/SuEc3qulWr61hfvjihK7w9Vo21kW3r\r\n' +
+      'a6vSL6mS9XBJjvJFdf5sEMiNTUxR7Zw4AsKUysgyiXRFM2hkxuwImFozyK+Er4OJ\r\n' +
+      'ydi3NpzcAL2+a8JzB35QztRxnypn/v/bWIyt89fW1mrstkCwvNRBaYnI4AGDN0C7\r\n' +
+      'jw5aYbOcdg3PbV69+5i4RCRkN2GU6LTovSaBvfBWxrJHav8pfinLhduOckwrWckx\r\n' +
+      'O38vc0fSuTUQaXgL8fXofX6L0139l9fN2MfndI8+39JOlzXNCsldpX+Nt2PI2Awm\r\n' +
+      'AgKEpLA3jbjazqvOmZUBxh0RVozzVu+JTbGWvkalEcmyncCuKSFZkMlP3SNrn4PS\r\n' +
+      'tpHlohTPBPHpxgJce0O6ylxgUZkUsSDatE0joWW/YJ+us0bqeGej5OLvmI9/L9iH\r\n' +
+      '2jCFIN79WVG06GsNuiKON12KPL4J/B4rv9bguQHdcPGJcVXtKv1Uzscpy1uQcqZc\r\n' +
+      'qVzl+Om4fbb0mg+vRXi9FQu//U35yK95NjF6KyniVF0riZWA6bb8dO4YdO4q9IYZ\r\n' +
+      'OFeoLQ/Zl4Zg58ytsUsqoFW6yK7itGUAV1y4BPME4pqkQAI5EVgaFnng9Gdcq9hN\r\n' +
+      '3VHHJLUiCjMLCmWrzt5dTgNCLrvF60bDnM5w9VAkR1xvNzYL/ui0j5A5fbpFc7jz\r\n' +
+      '1JcwFilP9qD94MPBOoPRNJNRxDl1bigdBtR50VTo7tNt48sSZHdVWPGMaqjDndRg\r\n' +
+      'ur3EJeQVMUvVf/5L9hDaZdqxJ9x6Va+5f4a4Or3ttOCb1qCawqutx6IcOc26EAVy\r\n' +
+      'nQ47UXQ2j/AjDoG+T8u34+TQsiVyC5X96TezAfPk5Vp8KUBjhBy15Z0YlnxXw4Up\r\n' +
+      'KzFPMfWOLTiElbJGaLtD7MXrXMQcdK9S2d/MR01zM8QuLwDH4OJfSJ53mlgsFmRG\r\n' +
+      'x7L+nZS7961GpoGHIZRRWvi7yejNpzxBUN7rIERgUqVQeh3lLDeDz8XKT83Hzd5R\r\n' +
+      '4AufZHsVg4K1xHxczD1NVoc2O/GM40vyPiK2ms1mQPiTckyF1jrsfKYDwbkzE3yi\r\n' +
+      'tJXp7Wlc5IHVQqULMU4wKQYJKoZIhvcNAQkUMRweGgBlAG4AYwByAHkAcAB0AGkA\r\n' +
+      'bwBuAGsAZQB5MCEGCSqGSIb3DQEJFTEUBBJUaW1lIDEzMTE4NTUyMzg5NjQwggVZ\r\n' +
+      'BgsqhkiG9w0BDAoBAqCCBPowggT2MCgGCiqGSIb3DQEMAQMwGgQUVHez67zL2YSj\r\n' +
+      'TqMZjS54S+FO+3wCAgQABIIEyDFgx9KJvdvBoednovcwUJeTWhvvMl6owrJ2FhVY\r\n' +
+      'FjahfYv7vLAKUeQiqnepRcATUzSHJgDDKlnW+0UDSGUqUoabbJhAqtHqrHFevGS2\r\n' +
+      'YpPNCfi7C2XTm4+F1MNmlmZhsM8gIY+2lmVpjRm+DvymKBzRuEw81xcF+RFDdOTX\r\n' +
+      '/ka6l5leRoFWTbfTnpIxA5QBVvEH52UkDw3hcrmVIct0v60IseiOqiL/4IpbpjmF\r\n' +
+      '3/rQdinE2sckujcEJD8edu1zbZzZ7KIbklWpPvcMRqCQSgrTuW1/lBuyVH3cvoFp\r\n' +
+      'FtaAw60f6X1ezKmiwA0nYIwahGVmyG4iektxO76zzBPkhL5HPD8BuJX+TRE9hYrZ\r\n' +
+      'tk161/hKFznWpPEC5ferEEEQi0vB2In1uz7L9LkpmC/to1k8MI1A/0yhY5xXUh4H\r\n' +
+      'hmp50OEsBnaXjDqPgtZeukqObjKbOSS4zL1WZ5vohJWQdF+C04d93MZoieUSz0yr\r\n' +
+      '1vSQ/JIr51NRKdffCgoZSFayv5vzFyTu9FKlUBgGEUMEzBdZ200v5ho7fHXWp1gW\r\n' +
+      'TyZK1pdVAC6tnKIgfSdkG7txHUDru120G7AdFXoFeRo7zalxGiGx5RIn3b/qfmyO\r\n' +
+      'MxcJX9wpFck9IcnN8L/S7hbxt9yAINshOyEM0rUXz0fjVZfcckKLso87YXCGJ7+X\r\n' +
+      '6HYe8bs7/uID7Yz7svD1iwnBlEPQInENZBEPuj6dtMYhMXXMHrY5nwNkXBGQioET\r\n' +
+      'O6xLjigPX7AUSuCCIRuoHGfo54HxV5uCon2/ibDuhTr46FrTKxQl2xv3M6VoWF/+\r\n' +
+      '0vLiCGKDa/aT5dZhdZ9OqC56mr6dFf8fSntMBBBxtUmcLVySa33G5UCInSrnTgu0\r\n' +
+      'fY8XGgud/V++xs5lr6jxFQjTdc0ec4muRBOflAvxGq/KKmhbO6h2sa9Ldmr9EABY\r\n' +
+      'jRaMz63WvObklE1m1IajjiceVXNLwJCwf37J7HKp1836WiWl/psIRSpsV0gdeb7y\r\n' +
+      'kEx54sEkbwtu8TNga6PbWUzwVEamFSGkAIxAnCCBj7W0okoLw+z1+FAby2lnMSSP\r\n' +
+      'F9g6aEEACt0h7fgOb6AEi9NCqfrpiZADwW1E0FRYOf8sIy/z6NPQGft3aIlUG9DA\r\n' +
+      'EZAm5IdZ0umQLMqeG30ZkC88W+ERhdIpVpwuHevdRyDwwN6SZ2+AZd5it1EOCLrC\r\n' +
+      '8CSWXyCNaSkPyoPzE0CpeayyhxYkQNg2KiLEOsOOOmSFpQx+R4QQjJL+chuX8T/D\r\n' +
+      'rxrgUgnPWPTDRI5iTexcCBlPdMbeyxfpwIWU0ZZsQxK1eBdizIzw/2JTSyHYVZuq\r\n' +
+      'nhznMaQHH0oA2PGqZw0y7Vu9iRzGU3RrEBBdGnZIwdz9agBc6BxqtLQ5tLKNLCBS\r\n' +
+      'BZjrCbWe9yBarQOFOpVPiczt/oJju/d5jC9Sj1QDppjLTiajZlcoY+mHGqcbzoe4\r\n' +
+      'wVN9+ZetkrGk4zDc8MPYMbHIxLR58P3noVZ6s84h1rhA8lKCg9vvG0jffcuveIRu\r\n' +
+      'AosyBT0v0qVRUWMIXJKpJSivKPykbQm6J+bAoK8+l3yCJ0AWpDcw5Wo5XqV/t4px\r\n' +
+      'xr95ikcr1+ANBRxa/TAl4oYuoqhlkx7Q8i/XCSudpXrswWcfR5ipc0tBzDFMMCcG\r\n' +
+      'CSqGSIb3DQEJFDEaHhgAcwBpAGcAbgBhAHQAdQByAGUAawBlAHkwIQYJKoZIhvcN\r\n' +
+      'AQkVMRQEElRpbWUgMTMxMTg1NTIzODg2MzCCHlMGCSqGSIb3DQEHBqCCHkQwgh5A\r\n' +
+      'AgEAMIIeOQYJKoZIhvcNAQcBMCgGCiqGSIb3DQEMAQYwGgQUQmWgPDEzodyfX1/t\r\n' +
+      '0lON23fzMeUCAgQAgIIeAAxfoaegDbtpKNtbR/bKgGGecS1491HJMR22X5mHI5EV\r\n' +
+      'UxPyuyM2bHky/U1eGix06P0ExQMV5kh/Eb+6vRLn+l0pTci53Ps2ITKFXvmqZ5Zx\r\n' +
+      'yjFtU3LCzN/qh5rFsLpPLdFn4oNrBveXWNPJrIj3Sf93To2GkLFQQ2aINNHe76k3\r\n' +
+      'O4jp6Kz4DKFrnyrY/fDDhHuGnkvHXBXPO+413PIV4Jgmv1zULkB94WpcJ35gsBGV\r\n' +
+      '3Njt7F0X10ZE3VN/tXlEPjaSv5k4zpG5Pe18Q4LnrPSN+XLfFLRnnYJiDlQkvO91\r\n' +
+      'AOxqlAkUq4jAGbTBUeSt+8P5HaliAPDJA43/tEWrb7fX68VpIblm4Y38AIoZOL8u\r\n' +
+      'uafg3WctcD9cy2rP6e0kblkcG4DLrwp/EezeXDxbOsdViiLU5SL1/RhO/0cqB2zw\r\n' +
+      '2scYLc6nJkxzC3iyzhukyn4834SAj+reJMzyiy9VviGQlDz4HFC+P9kYKOqbdW9N\r\n' +
+      'YYLYluHWjnNzE1enaYSPNPuR1U9UhSN/wKVwmdXsLRK0+ee7xpryxpTeUNAwacGR\r\n' +
+      'R7uWiXVBj+xQ2AG5qmW4fe1wxrZIL5bD1Z98ss3YLyUESUIv3K6MxkXZdp+gXv97\r\n' +
+      'jN6j2r536fGpA6jWehjsjk03tL5Zjv4i0PZLUFj16T1uXMzmaKplVd1HYY6bhBl6\r\n' +
+      '7lJerTtrGnPpybAeVn5KFsct0HchWIOkAeqOy3tIqi3R1msIrtR5FviFCgFYS5sV\r\n' +
+      'ly+T+rNdjQM4FrRk6y/IcCqoQTE6By8cYafRH58X07ix1+k5IFlrTbPrA8w1qQ6T\r\n' +
+      'wI5ww0hf4aE3W7brXMlmLYBfwfkTWLH/yDQsXBLsma0y1G7Ixn0BLuo6FBm3ayC2\r\n' +
+      'LEkN+iB+zEeC54oHE450Bhv1TOS80PrLzDW7wL789adWKXHgMmug9sT67gBbaFeU\r\n' +
+      'u3Z8VTISGxmbrEJQAMEoLuQiujHSfpPb5zK02+A363r+bLt1VbIs5jrYMvaB7qrk\r\n' +
+      '7nVJbVYlPscGwUQUEq4YbCjlg77rhY6d61LIcguG5snF2JTnR74Gu72JpqkrrtA9\r\n' +
+      'QHQ/njBnmIenXkqCzwcjyqiKUmPXazC/un7Hl3ZUp7BbbvfCJ1VNqtQJTdyS6kZ0\r\n' +
+      'ZIURy6R4uenoOw9BJfTzLEi+on3af1XXavb8apGlTVCxs8hL4F2IR1A3bkB8fMHv\r\n' +
+      '5oie2te80GKp+B+r0VrEdOLy6BkLgEfuwrpcsIjz+6z83UhfMhgKAcXYJVUC/mmf\r\n' +
+      'xO4hZ3AHKLCgVql8D4NoYPanQYEKx2sEoTDsCzsoh+E6OYhe4CiSBYwB4s5fKX1w\r\n' +
+      '5LVz7crf8Pg+WfffeP8Y/tDOiShz4luB7YVzw+sAy9Xil5+KmPO11yeDwIe3bdvu\r\n' +
+      'SeTAgzZ4lx7aZUpQ2cGaswo5Ix3Q7z2WkooYxCi4+XVw+BhRO0pVuyQB04v5rr1W\r\n' +
+      'EFlDAsC+RVcUw8gyM+tSxm5vqP7H6oEJT00tBYNAX/9ztDpoX4i2276s+6Iszz8B\r\n' +
+      'kqqTfasb41xzUdFf1PpSzqVGKDi4lAftfedn4JFuQHhcI4MhtxwwecKUL3uHXWiW\r\n' +
+      '3o++dAjO7ybfBm3j0WIKGVwxfON5KnVetSOofc3ZahSklUqEuyaQ/X93FT4amYMJ\r\n' +
+      'U7NwbLmrCuYe19/+0lt0ySSSBPNNJcV8r+/P0m4gR7athre9aSn/gU2rRrpYfXUS\r\n' +
+      'SIskLLPn26naLqLW5eEqF9KBg77pGXoXA4guavjUtxEeDCL0ncqAPlhNlRc7NTw5\r\n' +
+      'MGy65ozntamlGrAWK96fMesmF81ZFHyBH4XImDkIvEr62hmJUJuTh3lBhIGAmqwo\r\n' +
+      'jcYdAkibrZh3RmhYNzuSAPoBOd959fOwb9SVltDea49XAopKTodL6FPX4UQbCuES\r\n' +
+      'oml4ZBvRs+ykU+q1to+0QdoY8x0vzkEL1cOOEcbAQebK3kw3GmNZSi6+dzh+gC3C\r\n' +
+      'xrt53S6VrPlO5kpvlMjUjd5LDTIa05Kz+pIXVXUJSY5zNEWtQ54ne3TIHoqpT8oB\r\n' +
+      '8aQ+AnUKznf3Q5S3hQSA0P/zeIcbLwUvDGwk5GI+X38vNm6zbg+fhLwKi0E87fGE\r\n' +
+      '4ZM1w+D5Cfzl6AOP8QTnM9Az/g7z+nlslfh1uS4O87WNnETXyFqOKuMK5MsAYBmg\r\n' +
+      'mctsteL7lHbOcmATAX0MjGfewqvh3uVm18xg3S8RHbsQ42IC6NGDS7YfYI/ePrGu\r\n' +
+      'hdaTeUJuQVm8vSseL5vTeINLbWG7znV4avDgFDx6V+lL77relJ6dQQkRoCf/SNc4\r\n' +
+      'r3v2I1Dd7I77+AT/uBZ3laKsqQcUhcjhEb2iLzjWpZNnO54VhqILwVD8AU8QMQpu\r\n' +
+      'bjMxDXNKY9nhDWZtCoSEcbmvReo5dYXLCAjUokd2utwT8xTj+D7MADWKLTIfUO4H\r\n' +
+      '/OKq26mKCZq/6xgoLzXpiQeDxBfojJA4HWvZTmPlqH2VzIihKNFgP3QD1TH/nPCp\r\n' +
+      'LP0SULTuszYNMTbOOmPj8sYK57dVJUJc2/TiUr1rbxSEEnBL/y4BBUmWxESzNJuO\r\n' +
+      'ohJcR8rnyeklB5tnB5KzYuJqb5Do8PX7h7sGKZWOX0JAQkyq6QNSpJPR3PQ4tTSo\r\n' +
+      'vt2pn/+3Uj+9uEvMpYroJEaOMKW3kGL+PUxLg5xMmOWR86jGqHmfY/zx/sx0eiYL\r\n' +
+      'xXlD7KzaNuBLKGnTv/7fK7OzNc9bmS+pIeru8jtPIm6i6u+mQ/byIehjIPYxR1m/\r\n' +
+      'WBu7LJv4LALDHUh93Fnr92sdWkiV9yU5m5dMIgWzcT2Qis148p+y+w1teqJEnYsN\r\n' +
+      '7Ea1cRRbG/HXB4EmOuwI9oDU5so4gYqQKwv0YvL1P93AzxN0v5iA8g9JIcWD8jun\r\n' +
+      'CeyV90HiPa/shqk/xMbwQTypfFK0kGSPPeCJNBeAmLlM4RoTdGHY7pSoYyuRaRZj\r\n' +
+      'TgBfHT4WxQA5Wttp/rLlbJbyt0vabH15gyjE0WpYOItPh11ONchShJXh5/6zEyDS\r\n' +
+      'Nyn6TjFLmoDqHRSIxNraYQd2q7e11v9CJX0eoqljjst0LAWPFZ7X4m+kSQtoTdzt\r\n' +
+      'tuiPqkBY8wFokG/Mo0mDKwfTT1ZYSdygJZr8ZrNF+hXmBJN9mm4/0S+hN4Vtx/wm\r\n' +
+      'KKWeUOysbqOl3r0EHhh0Mugo2ApTABBDwzoLy7UDXfmJT8T7M0hh+ZT1Pja+G1YL\r\n' +
+      '/HrGHx8eAQQj0c4hyqePC86jgSh3iIkaBJFgEpytfJAnRZ/qr50YK5G7J6R2EjrL\r\n' +
+      '8IGcABSimIidvn0gQ0fBB//LR3con+KjugsA8cTC/cTwfSoyWr9K9FhjpmQ0rxUZ\r\n' +
+      'uE12auhTB2dNdCoOwai97jREVngGaL5GTqUqowNeUUXbedhQI5sRKphrRoinYjJ1\r\n' +
+      'uPuJDLVEsur2pkenLZLZn4l0Srgq1KAOBuZzqqDT6adxfQn3eKN6H0XHja9HMYU5\r\n' +
+      'eXNDEZyT+W6Xg4HcHtiH751LF62SR74GR1HiU3B1XXryXpxyBMGbzdypIDRR6PZb\r\n' +
+      '4o6na3Kx8nyYztI6KZ1Y4PukFqsYuCjFqjJDf9KtFM9eJyedSlsYGd2XDVMUpIlC\r\n' +
+      'NQ9VbRk+hDoH+J74upvX3cbASGkjmuu6sIKdt+quV2vdbyCKukayeWBXVP8bCW4Z\r\n' +
+      'ft0UIf8QIPmkP6GQ3F2qn/SjJI7WhrdGh04tpC0QuMdRLzJnd+R/tjA/QisCWxAW\r\n' +
+      '3WETPDphJMTqKHAUx/86VDSGV013uCrOkTXvuGJLOTl3mdbEj2+0+DLOE2APBsJc\r\n' +
+      'O0Lt5P0Oouigbj+wSVz20Fg7QhXO8Wep7S0krHAXJv3FdV0Cdn6MXaxeCBOfY4Rf\r\n' +
+      'MDUmN/xaiMk2mz7dfDRhg8OADNacg60RylM9jEQ1UclXNlzEBUseY7x3R7qqyeXz\r\n' +
+      '8zDQeCXj+PHFBm48fEvKhP5sqHNNZsB5cy53y6nVwM2Nb9XBOmVajX2kUSgQE3GQ\r\n' +
+      'HdCZE45Gx9FNP+tG6fYRnOx33ABnJdYwcN4s7xNwBXlTFp2t4CLWPDjwXUSBPudh\r\n' +
+      '2Hy/IzXic86kMmpl09WvPY89eFQ9o1laR4y7M5vnx+GMpCGkxmYZBbOZIGESVoy0\r\n' +
+      '70R7mkVJDFpPQg8FONTNzJki4ggZ2osWBy9fHbE1DvM+MqZe+4zpikjeMwoqmsK4\r\n' +
+      'flvcaautoiwLChpiG0/tjMw13JLPMW3ZMwQDfZXYta2ngT35X2iKcgZTykNSeVJe\r\n' +
+      'bB+ABC1Q9+R9/xlmlrBIUzzZHjNWr2FqDfDvbIIhURYmOqojtncOCttvEg2BUKSU\r\n' +
+      'DdHwTay9R34YmeM6GjzjAcJWY5PJUy+kYnD5Drkp0CNL3LSxoCuQEMqudFz/fMU/\r\n' +
+      'C3PogT6Ncnkr1FVu4uvs3ujG2ufu2YaGrLcYw2/N1yOZJWnnz07goD94VtyojMNc\r\n' +
+      'WTmKni3mHDobNYwHWiRW+g1vxptOH+u5efUlDuz9nnn6cOnqR73Xuht3wDOpyn/N\r\n' +
+      'VfvhZLRa3xeTTVHFqQFU+oqPjTV9H6y58zWpGhu8HOvsBcMmU/FQS6mfK7ebPGGi\r\n' +
+      'jboKbwLpHYFewi01tYgfqwn6hMMrzYPjJY1tsYJ8NIAsmRHkG70t70PVeTQ8cJoC\r\n' +
+      'Fm2IFDsZV/iTDdrBqvRcyBo0XmONAQQKr7rk/90eM4fd8nxGm/cAp/67NotSWQHa\r\n' +
+      'ZFdxhOPSBr6VBiS8SAfF1rIra8funxtQ5Yk04FPjsVotkm2nkMt4gntoM2b3w23Q\r\n' +
+      'GBaNcyPikhkQ8UC80Fbz6UzyLBKbZqCDI/GSa1A4BSvp0vy1pndHzrynyytF4t80\r\n' +
+      'r3I7e0M0SEHWYJFGmQ9szh3cXePvk0p5KJIu1BzPH6AoIK0dNRXQXAINnsxmpkeJ\r\n' +
+      '7pAkz0rIVxZ4SyH4TrZcXxnVJ0Gte9kd/95XSEZDyvT9Arhs/0jHzotxaua6wpK3\r\n' +
+      'JFF4BEmRPE7U3PsPJQN1fm6mqOdmaCE0UrnLhaMf8uMzYOoXVV8A5eRIDtgJ3X8V\r\n' +
+      'k6UkNbDt8mVlctLdkNM9tKkClaF4JnvyYYX16HS5sAJiZnM8vW46nh4KsYIVRqAf\r\n' +
+      'DaAeJzxRTSInaW52tuDqrBPVnl5XiAKbrved1fOUSSorI+SptHzaHcIH20h2DuSJ\r\n' +
+      'ryQnLseZ+F3sb7wdAUtQb6eMNvu7L3s1vBxKqKKlwAVuZEqQI/GT/5WAB34iul3U\r\n' +
+      'hAZZX0xKfweRp27xLRyUiqGFAsOaoDIwRiDhVKJZVCwIa3dSKCW8jvmC+EaeSyKG\r\n' +
+      'Wx7gGnJm9XovdI1hi/zHM60ABejiMnDeAACcvsCJqKXE/9YDFQF+yW30OSZ2AOUL\r\n' +
+      'UWnyD493R347W2oPzV1HbYLd//2gIQFFeMDv0AWfJGv4K0JkZ/pGpaPAoee6Pd1C\r\n' +
+      'OjcxbWhvbEwXDtVRFztCjgNd/rp4t+YQ9QbMczK3HplpMyYjIs0WdTU3gNWqmTEf\r\n' +
+      'guOokh7tqlOHQso0gg3ax65vc2k9V2yLJz2CDkVkATKpJOjV4sNWGPnB4129xact\r\n' +
+      'p9JfGDAWniAE4cYW/etNTXhNWJTzkSlb5Ad5JPPQ4p/lB97Xr/Krwjp1o3h2JTgC\r\n' +
+      'IBfqb9g7WQ/B8EL0AwnoHxPDTdXAHOCiUr0y1M1w36thr56AVR97/R02k2XI3dxv\r\n' +
+      'oS/bCgNtFFSao2O7uANqtU/SMHMl0BrR8dk+4924Wu0m06iNDZB8NU0jU5bqxcW6\r\n' +
+      'wzf/rjqwIndehfpH7MkeCk6rM0JiVku/EKoCfg9DOAA2rLIiyWO2+mm5UWiT60a0\r\n' +
+      'kmGwwrAxduugMnfVdb5fI8F+IyXYCH8Iwi6qpFvSLm4F/++0WP6pD1Xov6cRu9Eq\r\n' +
+      'nQ4FcCFQJ62ymKlZ0+qZ1ywftKTRwNNlPfZezkqJm17sDI02AUAjGotxrSdDfca5\r\n' +
+      'ViRxq+HJiQGVCUo4fEl4iMzSWaBLeQr9nSijB76dyq1e89NMXS0L3Uo6B7gbKm2i\r\n' +
+      'AjRTUEN2LIGM7TiRC4kZRRMrgVcBBDAtuyY/sMDZ6bUageLXlAPSGZ+VY/a+usok\r\n' +
+      'pxP+U88X7mkxuvvPIG7yKaxymdB993pRaVvLuPVcZRDmXIFrTSP5wxejRQpIvwNR\r\n' +
+      'UeYwGQs1gAuM3l6N7trX99j6WBzZr09YRVPgehh5N3s/omrEMDMcExlmAdVOYNij\r\n' +
+      'UN5NOZgPZrHTev4BtZa53FKttvGT9Ly9iLtle218tQyJRK7UQ/APZJzidpcy3p/x\r\n' +
+      'U9AgXG9+horGLG4/HAmpZh4VH+8wXpiUxsC2rXLb0cAoFg03gStLvqXU93UU6KSn\r\n' +
+      'xC0FYZZAqeFDdKbk4IMirklafEu+j45I+57RiCr7mpOyDI4o5FItWMzSxFo06ciw\r\n' +
+      'aUT4eQf+pUFrBz0yUvgJArh3+VZdRhd8vycuxrYgfp9q4H1n2hOEOi/eeQCuJH36\r\n' +
+      'RnAkToyRYwCepD3di2tf5FL2cW2DPMj69o7dIUHEn76SKVtgwmv5Q86rBWTecAn1\r\n' +
+      'qkUXMst6qxyZCqHMsrQ0Bf7lcB9nSTvPXHzbJjLg0QRYi4qZzU46Vmo5bLw0l8R/\r\n' +
+      '66Wyv+OIastQdCB6S1JtRnE2zvR7nRA/TgfmbJBklgEUY9KeyRzh1Vkp7aykuMXV\r\n' +
+      '9bsND+1swzKgqTGxCyMMqIP6OQsr9AVlO4MsR8XCTOY4F/dTaCRHWXC/uvtuar/y\r\n' +
+      '8vFQeaUPSR10+XGxYb7tnaaBqdVy9MMuwz7Y3jYgvbfxku6aXJMyWFBRqCRskOZa\r\n' +
+      'GQOMmb0j9QH/bl6goHBfCJjSSU+vkVytQf7ZtWyD+k4+R3X+nQEex0Eb+2nfzh3i\r\n' +
+      'ZHSO7cqRz12/B8CmQ75L8suRcRrqINMdAZfmARp5s0UtmHYKbOcrxd4l625rUwTJ\r\n' +
+      't0vih8+BK6k1F6oT1kCR6ZyfIHhh8dn22SYJAQFW3+WZsaPjLgkh0ihcyfhLfKMC\r\n' +
+      'K3YvF/dt9rQDorwNwp5+xiuGUrwk7SLbc7wmNCFiD5nER3AhUSuGzQLfZzjeqYgK\r\n' +
+      'Wge2QCPwtwzaHNp51c5QMvKqQfsg12P81qs3Jl/j+xKpzLh2vLYlnq8OuFd3lR6x\r\n' +
+      'q0Rya6j4o+AqW/v1CJBRhS0qXTW/bHvPm8uU16Uw9W4AGPnISbLQh5sfOKkKgNN/\r\n' +
+      'jTogehgId2rZ1VfhW7n9xvPkk2NEt+YmXHn7EuPri6GXPIDhaLWLaSpa8PYW+jxx\r\n' +
+      'T0CDjYQkT/Q/TfuX3yzGHXKhMInKxjqihd1RQ2OIBLBF8/1UFNLM82XntXt2TJXK\r\n' +
+      'kUQYAIJxH23h9ZBH2K3T2gNjOqLmiqE0C4QEW8xNO75TWiYm8j+sX2LmdYmXZP8C\r\n' +
+      'iMlyE2shMVriN3t457D8S5a1aEvATDFxM4YL5k5OsZ6HrQ6PrnzZfrWXh5OxoxAU\r\n' +
+      '+FCXxpRi6lwY3yNi3kUteexRLZGrEz2FRPemDLsevShRqnsy/0OA/05TA6JxLVpd\r\n' +
+      'Dd7ZWUBcIJZ7lQKMzfCAdWR20J7ngEuiUksQDo5h9/727aO/fbVh+aLVYY1EF+0p\r\n' +
+      '8gbM3/hyoGd8pujWqU1U7jLQACAp5zsy7xvnbiXYl42SaF1PFUP5aZrAPBcj0Fru\r\n' +
+      't8SnPjys2JE172lCkQQOBglanklkpRiBDWYxG8josUyASo7EzddOneLNoMNl8+ZO\r\n' +
+      'ZZYN6BRIioChYDsrrPZiootTU5DYC8a0/AcDsV6PQ48SlInCKtuAOi8nHJDVUzBI\r\n' +
+      'QkDd13kAeIFEMOJUV17xh7eLpbe10bv1B8zUiMbvBTzWPXZHEbuNlWiGy960J4t3\r\n' +
+      'x6NGEAfIjYg9+aMCf7uiEWd48s+nrKWymn7Ewg7llyMfK2Vsa9PVMilopGx42y51\r\n' +
+      'HMIzSV4TjOxSAJmXFZs55w57Rqjx3+LP9P7Ilpde4Lh35hD6yX5hZW+gnQs+B/j8\r\n' +
+      'DkBDeIYtMSz4tHqiK6rBUD/KnNUYYmOOGUi/bPyS4TH0ycbSFp1xx+rS/86Uh8YK\r\n' +
+      'wSOVkKvL2VhGE5G0RSSvYLUkEPcjA8K+EaHf8aCWpnGmpr3rT7F00JFhmH/kDXcU\r\n' +
+      'rtatu8Lniqm0sIV84nVEqHF9Vgz1D2d2/VYfLWlMDM5Mb8IWVUi8fjNFQf32bTCZ\r\n' +
+      'ZYTNUSushCwwpo2R8akkURsev+zstIzw73MGldj2AJ6y/0h51Z4dpQuJbwsKIw4g\r\n' +
+      '5MH42cM4PwiQ7hpqDeGLoyfeAMRFnme/HZCsgBCv247KXdpuYolORXBwjiqhlXYl\r\n' +
+      '6W5aUXp7H+Idz+ahq+nEdsGR57lX1dCC731i8x7/0fl7LEAPGCgr3A0UqTesBKqV\r\n' +
+      '5iq03xmxLhXEyv5QJVPCmG2067Wuoi9hMbXWb/IuX6TV2GACuZ54x9ftWtrPtZ7J\r\n' +
+      'bJEst/IK1SvODlNpk3Z8jcx8YFS7RzjrI3CuVrn45HXF5yHlzwiyBnaFiuBXaDFk\r\n' +
+      'kFGnTIxDrDfBsxCN7v3snuf+eW41SaXv8BHAvi4A+cv5vpSduEGY+aZWdgLDsnxw\r\n' +
+      '+zU5GUhNuT28YKEYzyTnMTdo/QL1KZkFqRDqANeRK3V24OaxHt6sbxYuRLGphytc\r\n' +
+      'uUnB6ICpHgwASejiJY/hWhm5PLI3jxdXAa7XOg7asESz1yo7FrJIwW7UlnNBOneA\r\n' +
+      'yuYFdB0usNx+E63hsw+TJ9Sg0+t+mG2+Fr1hE2qEahF2BrrB9LW0xuTXmAeW2qHp\r\n' +
+      'cOVLJigo9QsEy3Y/sPuDJC0z9MnsKefglpSZyGBxkpKtVN7ePHl/hmMBRD6W1aZ0\r\n' +
+      '8bdl0Ljj6SoT9DB8qqyUX3Km/5xSWguvp2hMa1s/J+dJAzOOGx9P94QOgggrImOR\r\n' +
+      'yhMa/3i5qA9QPzT0ivMtQwS5HaGL6Hjv6jkmK1FzfCoOE8d6+9AuhvvbfZs3c0Wf\r\n' +
+      '31F5e09s6fPqXTk3Dw6TsiED+NjtTTywPEaNgjldpPjZPBpAl6pNx/i9KghBmaCG\r\n' +
+      'LDsvFJ/BqZf1qYFKE47Ozf8jQ4b+ZgU37awZAKERnoEvPdJ3gv5H+pyjbYbacLG4\r\n' +
+      '2jF/pRzhiF0eRBuqY/5DrgMe1dkI9TNvBFzsX4YFOxZWca/kc26JhCajuH8MaTyW\r\n' +
+      'LzOeIg6QKane6HBBxRvoOBMIa40oBhffbOi5FKukKUFS3xlPL3EwdS/aZK61vCR2\r\n' +
+      'NPS7Y/d2vk80aNVRZAm2FBcmBWF6q7A825S7HqwM1izmlmqC6yWYXGofP8PuYfww\r\n' +
+      'eWW5rm+3URjcRM54K5Ob7rfKu3q7zUwUAB6R7YM9pgeDbaARyE7mB0MmpB+3UqO8\r\n' +
+      'F5heKtELqIskZGAiCKxGPKERoHPItKTV77ZCZ+ql0FjlJSrXVZ1P/9i/BiwdYmij\r\n' +
+      'vhjCEtDcHWPXXIra6Hf5hTIUJ7conZ9ldGhHliV6Rso7ST1FGIsqrgYDyt1/+Vo4\r\n' +
+      'hNBaUhWOHh65EKRblCW04v71KyaL8ms7Pevgcz4NZFtUwv3v2qI+OqdWBFFbc9Lr\r\n' +
+      'cfiyt5XbZeUD4GiI5/wDVk0b07ev7xyoedeB7GvXgmb13D1vCtHYubeDyI+V7zlM\r\n' +
+      'GXPvCkIPhj34fK6vhtHJIfHL3+8vf6emd7h4Ziakod9G0HYJTKbugpCmi6ejW8G9\r\n' +
+      'X5Kzrn9c8HD7bNCUtwNFV0unoZUN3ReVAOLNn2N0LUfHBrlq/XwseHovUbzSomYT\r\n' +
+      'Xtr/w+tiLSMSRLsJzAu0LJHgNtYPsPIavpim0OLTPg7JBmnzWoyEFCXcLvjNry6c\r\n' +
+      'yCgA4RgfmBcJzXS1Uyf/TUM9IFoeTbGo9dIziygUdWXxrUzx2Uyak53xZXEX82cB\r\n' +
+      'kC/v1+VCq668xgthc9pEEHIsqxKuRCUXj53xYThI5gSJke3XYrCdk3R8rh8FdkkQ\r\n' +
+      'E/4WFpZ8kqraFXSYlfYvGHYd31cbJoSxjTIISd5US85KaOH2n3HN0d017xfwaSqS\r\n' +
+      'I1l1iutPvcc+wxydp7On+uQAP4GiV1uPmuN0s0lu81j7ye9nS+fjxlXiukHQu1mF\r\n' +
+      'c5IdEASgborfk+mrVpl/hpeLJH4LZIGPaZgr3KDBZPDMgqDCXBphL+GjJYPXyW7I\r\n' +
+      't3QRCKMTNHCO7E3e7eet7k2ADSjrN1eZuzo7FxCU6cv+oCQUWPzaRYWb6gzr2QV4\r\n' +
+      'snvwM2sGc0Mkg1QnJAzT6zrtfVZ2uh2VwkN93u8KxwiiCRn53rHn46uW1djNHmIe\r\n' +
+      '4E2vS4IWoCmy59lGxV6UEfsjEGxC+pDv33xX69aDf8vN6VON8B4ooHwdg+GMe2Us\r\n' +
+      'N7sQkhf1ykdR0tmJnG8yr0DfGfxbcJArEv8wcZh89M0oOY7iKx/hq4n4DSVHLmDg\r\n' +
+      'obV4S2+c5aRrVFWQiw+/OjA9MCEwCQYFKw4DAhoFAAQUXolDwewLkmOH6dGcPdhJ\r\n' +
+      'JeUrAz0EFHRZbCAQ2bUo5B8DAFM8VJLi/+A2AgIEAA=='
+  };
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/forge'
+  ], function(FORGE) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      FORGE
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/forge'));
+}
+
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/pkcs7.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/pkcs7.js
new file mode 100644
index 0000000..2c4e793
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/pkcs7.js
@@ -0,0 +1,350 @@
+(function() {
+
+function Tests(ASSERT, PKCS7, PKI, AES, DES, UTIL) {
+  var _pem = {
+    p7: '-----BEGIN PKCS7-----\r\n' +
+      'MIICTgYJKoZIhvcNAQcDoIICPzCCAjsCAQAxggHGMIIBwgIBADCBqTCBmzELMAkG\r\n' +
+      'A1UEBhMCREUxEjAQBgNVBAgMCUZyYW5jb25pYTEQMA4GA1UEBwwHQW5zYmFjaDEV\r\n' +
+      'MBMGA1UECgwMU3RlZmFuIFNpZWdsMRIwEAYDVQQLDAlHZWllcmxlaW4xFjAUBgNV\r\n' +
+      'BAMMDUdlaWVybGVpbiBERVYxIzAhBgkqhkiG9w0BCQEWFHN0ZXNpZUBicm9rZW5w\r\n' +
+      'aXBlLmRlAgkA1FQcQNg14vMwDQYJKoZIhvcNAQEBBQAEggEAJhWQz5SniCd1w3A8\r\n' +
+      'uKVZEfc8Tp21I7FMfFqou+UOVsZCq7kcEa9uv2DIj3o7zD8wbLK1fuyFi4SJxTwx\r\n' +
+      'kR0a6V4bbonIpXPPJ1f615dc4LydAi2tv5w14LJ1Js5XCgGVnkAmQHDaW3EHXB7X\r\n' +
+      'T4w9PR3+tcS/5YAnWaM6Es38zCKHd7TnHpuakplIkwSK9rBFAyA1g/IyTPI+ktrE\r\n' +
+      'EHcVuJcz/7eTlF6wJEa2HL8F1TVWuL0p/0GsJP/8y0MYGdCdtr+TIVo//3YGhoBl\r\n' +
+      'N4tnheFT/jRAzfCZtflDdgAukW24CekrJ1sG2M42p5cKQ5rGFQtzNy/n8EjtUutO\r\n' +
+      'HD5YITBsBgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBBmlpfy3WrYj3uWW7+xNEiH\r\n' +
+      'gEAm2mfSF5xFPLEqqFkvKTM4w8PfhnF0ehmfQNApvoWQRQanNWLCT+Q9GHx6DCFj\r\n' +
+      'TUHl+53x88BrCl1E7FhYPs92\r\n' +
+      '-----END PKCS7-----\r\n',
+    certificate: '-----BEGIN CERTIFICATE-----\r\n' +
+      'MIIDtDCCApwCCQDUVBxA2DXi8zANBgkqhkiG9w0BAQUFADCBmzELMAkGA1UEBhMC\r\n' +
+      'REUxEjAQBgNVBAgMCUZyYW5jb25pYTEQMA4GA1UEBwwHQW5zYmFjaDEVMBMGA1UE\r\n' +
+      'CgwMU3RlZmFuIFNpZWdsMRIwEAYDVQQLDAlHZWllcmxlaW4xFjAUBgNVBAMMDUdl\r\n' +
+      'aWVybGVpbiBERVYxIzAhBgkqhkiG9w0BCQEWFHN0ZXNpZUBicm9rZW5waXBlLmRl\r\n' +
+      'MB4XDTEyMDMxODIyNTc0M1oXDTEzMDMxODIyNTc0M1owgZsxCzAJBgNVBAYTAkRF\r\n' +
+      'MRIwEAYDVQQIDAlGcmFuY29uaWExEDAOBgNVBAcMB0Fuc2JhY2gxFTATBgNVBAoM\r\n' +
+      'DFN0ZWZhbiBTaWVnbDESMBAGA1UECwwJR2VpZXJsZWluMRYwFAYDVQQDDA1HZWll\r\n' +
+      'cmxlaW4gREVWMSMwIQYJKoZIhvcNAQkBFhRzdGVzaWVAYnJva2VucGlwZS5kZTCC\r\n' +
+      'ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMsAbQ4fWevHqP1K1y/ewpMS\r\n' +
+      '3vYovBto7IsKBq0v3NmC2kPf3NhyaSKfjOOS5uAPONLffLck+iGdOLLFia6OSpM6\r\n' +
+      '0tyQIV9lHoRh7fOEYORab0Z+aBUZcEGT9yotBOraX1YbKc5f9XO+80eG4XYvb5ua\r\n' +
+      '1NHrxWqe4w2p3zGJCKO+wHpvGkbKz0nfu36jwWz5aihfHi9hp/xs8mfH86mIKiD7\r\n' +
+      'f2X2KeZ1PK9RvppA0X3lLb2VLOqMt+FHWicyZ/wjhQZ4oW55ln2yYJUQ+adlgaYn\r\n' +
+      'PrtnsxmbTxM+99oF0F2/HmGrNs8nLZSva1Vy+hmjmWz6/O8ZxhiIj7oBRqYcAocC\r\n' +
+      'AwEAATANBgkqhkiG9w0BAQUFAAOCAQEAvfvtu31GFBO5+mFjPAoR4BlzKq/H3EPO\r\n' +
+      'qS8cm/TjHgDRALwSnwKYCFs/bXqE4iOTD6otV4TusX3EPbqL2vzZQEcZn6paU/oZ\r\n' +
+      'ZVXwQqMqY5tf2teQiNxqxNmSIEPRHOr2QVBVIx2YF4Po89KGUqJ9u/3/10lDqRwp\r\n' +
+      'sReijr5UKv5aygEcnwcW8+Ne4rTx934UDsutKG20dr5trZfWQRVS9fS9CFwJehEX\r\n' +
+      'HAMUc/0++80NhfQthmWZWlWM1R3dr4TrIPtWdn5z0MtGeDvqBk7HjGrhcVS6kAsy\r\n' +
+      'Z9y/lfLPjBuxlQAHztEJCWgI4TW3/RLhgfg2gI1noM2n84Cdmisfkg==\r\n' +
+      '-----END CERTIFICATE-----\r\n',
+    privateKey: '-----BEGIN RSA PRIVATE KEY-----\r\n' +
+      'MIIEowIBAAKCAQEAywBtDh9Z68eo/UrXL97CkxLe9ii8G2jsiwoGrS/c2YLaQ9/c\r\n' +
+      '2HJpIp+M45Lm4A840t98tyT6IZ04ssWJro5KkzrS3JAhX2UehGHt84Rg5FpvRn5o\r\n' +
+      'FRlwQZP3Ki0E6tpfVhspzl/1c77zR4bhdi9vm5rU0evFap7jDanfMYkIo77Aem8a\r\n' +
+      'RsrPSd+7fqPBbPlqKF8eL2Gn/GzyZ8fzqYgqIPt/ZfYp5nU8r1G+mkDRfeUtvZUs\r\n' +
+      '6oy34UdaJzJn/COFBnihbnmWfbJglRD5p2WBpic+u2ezGZtPEz732gXQXb8eYas2\r\n' +
+      'zyctlK9rVXL6GaOZbPr87xnGGIiPugFGphwChwIDAQABAoIBAAjMA+3QvfzRsikH\r\n' +
+      'zTtt09C7yJ2yNjSZ32ZHEPMAV/m1CfBXCyL2EkhF0b0q6IZdIoFA3g6xs4UxYvuc\r\n' +
+      'Q9Mkp2ap7elQ9aFEqIXkGIOtAOXkZV4QrEH90DeHSfax7LygqfD5TF59Gg3iAHjh\r\n' +
+      'B3Qvqg58LyzJosx0BjLZYaqr3Yv67GkqyflpF/roPGdClHpahAi5PBkHiNhNTAUU\r\n' +
+      'LJRGvMegXGZkUKgGMAiGCk0N96OZwrinMKO6YKGdtgwVWC2wbJY0trElaiwXozSt\r\n' +
+      'NmP6KTQp94C7rcVO6v1lZiOfhBe5Kc8QHUU+GYydgdjqm6Rdow/yLHOALAVtXSeb\r\n' +
+      'U+tPfcECgYEA6Qi+qF+gtPincEDBxRtoKwAlRkALt8kly8bYiGcUmd116k/5bmPw\r\n' +
+      'd0tBUOQbqRa1obYC88goOVzp9LInAcBSSrexhVaPAF4nrkwYXMOq+76MiH17WUfQ\r\n' +
+      'MgVM2IB48PBjNk1s3Crj6j1cxxkctqmCnVaI9HlU2PPZ3xjaklfv/NsCgYEA3wH8\r\n' +
+      'mehUhiAp7vuhd+hfomFw74cqgHC9v0saiYGckpMafh9MJGc4U5GrN1kYeb/CFkSx\r\n' +
+      '1hOytD3YBKoaKKoYagaMQcjxf6HnEF0f/5OiQkUQpWmgC9lNnE4XTWjnwqaTS5L9\r\n' +
+      'D+H50SiI3VjHymGXTRJeKpAIwV74AxxrnVofqsUCgYAwmL1B2adm9g/c7fQ6yatg\r\n' +
+      'hEhBrSuEaTMzmsUfNPfr2m4zrffjWH4WMqBtYRSPn4fDMHTPJ+eThtfXSqutxtCi\r\n' +
+      'ekpP9ywdNIVr6LyP49Ita6Bc+mYVyU8Wj1pmL+yIumjGM0FHbL5Y4/EMKCV/xjvR\r\n' +
+      '2fD3orHaCIhf6QvzxtjqTwKBgFm6UemXKlMhI94tTsWRMNGEBU3LA9XUBvSuAkpr\r\n' +
+      'ZRUwrQssCpXnFinBxbMqXQe3mR8emrM5D8En1P/jdU0BS3t1kP9zG4AwI2lZHuPV\r\n' +
+      'ggbKBS2Y9zVtRKXsYcHawM13+nIA/WNjmAGJHrB45UJPy/HNvye+9lbfoEiYKdCR\r\n' +
+      'D4bFAoGBAIm9jcZkIwLa9kLAWH995YYYSGRY4KC29XZr2io2mog+BAjhFt1sqebt\r\n' +
+      'R8sRHNiIP2mcUECMOcaS+tcayi+8KTHWxIEed9qDmFu6XBbePfe/L6yxPSagcixH\r\n' +
+      'BK0KuK/fgTPvZCmIs8hUIC+AxhXKnqn4fIWoO54xLsALc0gEjs2d\r\n' +
+      '-----END RSA PRIVATE KEY-----\r\n',
+    encryptedData: '-----BEGIN PKCS7-----\r\n' +
+      'MIGHBgkqhkiG9w0BBwagejB4AgEAMHMGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQI\r\n' +
+      'upMFou5X3DWAUAqObuHSlewM0ZtHzWk9MAmtYb7MSb//OBMKVfLCdbmrS5BpKm9J\r\n' +
+      'gzwiDR5Od7xgfkqasLS2lOdKAvJ5jZjjTpAyrjBKpShqK9gtXDuO0zH+\r\n' +
+      '-----END PKCS7-----\r\n',
+    p7IndefiniteLength: '-----BEGIN PKCS7-----\r\n' +
+      'MIAGCSqGSIb3DQEHA6CAMIACAQAxggHGMIIBwgIBADCBqTCBmzELMAkGA1UEBhMC\r\n' +
+      'REUxEjAQBgNVBAgMCUZyYW5jb25pYTEQMA4GA1UEBwwHQW5zYmFjaDEVMBMGA1UE\r\n' +
+      'CgwMU3RlZmFuIFNpZWdsMRIwEAYDVQQLDAlHZWllcmxlaW4xFjAUBgNVBAMMDUdl\r\n' +
+      'aWVybGVpbiBERVYxIzAhBgkqhkiG9w0BCQEWFHN0ZXNpZUBicm9rZW5waXBlLmRl\r\n' +
+      'AgkA1FQcQNg14vMwDQYJKoZIhvcNAQEBBQAEggEAlWCH+E25c4jfff+m0eAxxMmE\r\n' +
+      'WWaftdsk4ZpAVAr7HsvxJ35bj1mhwTh7rBTg929JBKt6ZaQ4I800jCNxD2O40V6z\r\n' +
+      'lB7JNRqzgBwfeuU2nV6FB7v1984NBi1jQx6EfxOcusE6RL/63HqJdFbmq3Tl55gF\r\n' +
+      'dm3JdjmHbCXqwPhuwOXU4yhkpV1RJcrYhPLe3OrLAH7ZfoE0nPJPOX9HPTZ6ReES\r\n' +
+      'NToS7I9D9k7rCa8fAP7pgjO96GJGBtCHG1VXB9NX4w+xRDbgVPOeHXqqxwZhqpW2\r\n' +
+      'usBU4+B+MnFLjquOPoySXFfdJFwTP61TPClUdyIne5FFP6EYf98mdtnkjxHo1TCA\r\n' +
+      'BgkqhkiG9w0BBwEwFAYIKoZIhvcNAwcECFNtpqBmU3M9oIAESM+yyQLkreETS0Kc\r\n' +
+      'o01yl6dqqNBczH5FNTK88ypz38/jzjo47+DURlvGzjHJibiDsCz9KyiVmgbRrtvH\r\n' +
+      '08rfnMbrU+grCkkx9wQI1GnLrYhr87oAAAAAAAAAAAAA\r\n' +
+      '-----END PKCS7-----\r\n',
+    p73des: '-----BEGIN PKCS7-----\r\n' +
+      'MIICTQYJKoZIhvcNAQcDoIICPjCCAjoCAQAxggHGMIIBwgIBADCBqTCBmzELMAkG\r\n' +
+      'A1UEBhMCREUxEjAQBgNVBAgMCUZyYW5jb25pYTEQMA4GA1UEBwwHQW5zYmFjaDEV\r\n' +
+      'MBMGA1UECgwMU3RlZmFuIFNpZWdsMRIwEAYDVQQLDAlHZWllcmxlaW4xFjAUBgNV\r\n' +
+      'BAMMDUdlaWVybGVpbiBERVYxIzAhBgkqhkiG9w0BCQEWFHN0ZXNpZUBicm9rZW5w\r\n' +
+      'aXBlLmRlAgkA1FQcQNg14vMwDQYJKoZIhvcNAQEBBQAEggEAS6K+sQvdKcK6YafJ\r\n' +
+      'maDPjBzyjf5jtBgVrFgBXTCRIp/Z2zAXa70skfxhbwTgmilYTacA7jPGRrnLmvBc\r\n' +
+      'BjhyCKM3dRUyYgh1K1ka0w1prvLmRk6Onf5df1ZQn3AJMIujJZcCOhbV1ByLInve\r\n' +
+      'xn02KNHstGmdHM/JGyPCp+iYGprhUozVSpNCKS+R33EbsT0sAxamfqdAblT9+5Qj\r\n' +
+      '4CABvW11a1clPV7STwBbAKbZaLs8mDeoWP0yHvBtJ7qzZdSgJJA2oU7SDv4icwEe\r\n' +
+      'Ahccbe2HWkLRw8G5YG9XcWx5PnQQhhnXMxkLoSMIYxItyL/cRORbpDohd+otAo66\r\n' +
+      'WLH1ODBrBgkqhkiG9w0BBwEwFAYIKoZIhvcNAwcECD5EWJMv1fd7gEj1w3WM1KsM\r\n' +
+      'L8GDk9JoqA8t9v3oXCT0nAMXoNpHZMnv+0UHHVljlSXBTQxwUP5VMY/ddquJ5O3N\r\n' +
+      'rDEqqJuHB+KPIsW1kxrdplU=\r\n' +
+      '-----END PKCS7-----\r\n'
+  };
+
+  describe('pkcs7', function() {
+    it('should import message from PEM', function() {
+      var p7 = PKCS7.messageFromPem(_pem.p7);
+
+      ASSERT.equal(p7.type, PKI.oids.envelopedData);
+      ASSERT.equal(p7.version, 0);
+
+      ASSERT.equal(p7.recipients.length, 1);
+      ASSERT.equal(p7.recipients[0].version, 0);
+      ASSERT.equal(p7.recipients[0].serialNumber, '00d4541c40d835e2f3');
+
+      // Test converted RDN, which is constructed of seven parts.
+      ASSERT.equal(p7.recipients[0].issuer.length, 7);
+      ASSERT.equal(p7.recipients[0].issuer[0].type, '2.5.4.6');
+      ASSERT.equal(p7.recipients[0].issuer[0].value, 'DE');
+      ASSERT.equal(p7.recipients[0].issuer[1].type, '2.5.4.8');
+      ASSERT.equal(p7.recipients[0].issuer[1].value, 'Franconia');
+      ASSERT.equal(p7.recipients[0].issuer[2].type, '2.5.4.7');
+      ASSERT.equal(p7.recipients[0].issuer[2].value, 'Ansbach');
+      ASSERT.equal(p7.recipients[0].issuer[3].type, '2.5.4.10');
+      ASSERT.equal(p7.recipients[0].issuer[3].value, 'Stefan Siegl');
+      ASSERT.equal(p7.recipients[0].issuer[4].type, '2.5.4.11');
+      ASSERT.equal(p7.recipients[0].issuer[4].value, 'Geierlein');
+      ASSERT.equal(p7.recipients[0].issuer[5].type, '2.5.4.3');
+      ASSERT.equal(p7.recipients[0].issuer[5].value, 'Geierlein DEV');
+      ASSERT.equal(p7.recipients[0].issuer[6].type, '1.2.840.113549.1.9.1');
+      ASSERT.equal(p7.recipients[0].issuer[6].value, 'stesie@brokenpipe.de');
+
+      ASSERT.equal(p7.recipients[0].encryptedContent.algorithm, PKI.oids.rsaEncryption);
+      ASSERT.equal(p7.recipients[0].encryptedContent.content.length, 256);
+
+      ASSERT.equal(p7.encryptedContent.algorithm, PKI.oids['aes256-CBC']);
+      ASSERT.equal(p7.encryptedContent.parameter.data.length, 16);  // IV
+    });
+
+    it('should import indefinite length message from PEM', function() {
+      ASSERT.doesNotThrow(function() {
+        var p7 = PKCS7.messageFromPem(_pem.p7IndefiniteLength);
+        ASSERT.equal(p7.type, PKI.oids.envelopedData);
+        ASSERT.equal(p7.encryptedContent.parameter.toHex(), '536da6a06653733d');
+        ASSERT.equal(p7.encryptedContent.content.length(), 80);
+      });
+    });
+
+    it('should find recipient by serial number', function() {
+      var p7 = PKCS7.messageFromPem(_pem.p7);
+      var cert = PKI.certificateFromPem(_pem.certificate);
+
+      var ri = p7.findRecipient(cert);
+      ASSERT.equal(ri.serialNumber, '00d4541c40d835e2f3');
+
+      // modify certificate so it doesn't match recipient any more
+      cert.serialNumber = '1234567890abcdef42';
+      ri = p7.findRecipient(cert);
+      ASSERT.equal(ri, null);
+    });
+
+    it('should aes-decrypt message', function() {
+      var p7 = PKCS7.messageFromPem(_pem.p7);
+      var privateKey = PKI.privateKeyFromPem(_pem.privateKey);
+      p7.decrypt(p7.recipients[0], privateKey);
+
+      // symmetric key must be 32 bytes long (AES 256 key)
+      ASSERT.equal(p7.encryptedContent.key.data.length, 32);
+      ASSERT.equal(
+        p7.content,
+        'Today is Boomtime, the 9th day of Discord in the YOLD 3178\r\n');
+    });
+
+    it('should 3des-decrypt message', function() {
+      var p7 = PKCS7.messageFromPem(_pem.p73des);
+      var privateKey = PKI.privateKeyFromPem(_pem.privateKey);
+      p7.decrypt(p7.recipients[0], privateKey);
+
+      // symmetric key must be 24 bytes long (DES3 key)
+      ASSERT.equal(p7.encryptedContent.key.data.length, 24);
+      ASSERT.equal(
+        p7.content,
+        'Today is Prickle-Prickle, ' +
+        'the 16th day of Discord in the YOLD 3178\r\n');
+    });
+
+    it('should add a recipient', function() {
+      var p7 = PKCS7.createEnvelopedData();
+
+      // initially there should be no recipients
+      ASSERT.equal(p7.recipients.length, 0);
+
+      var cert = PKI.certificateFromPem(_pem.certificate);
+      p7.addRecipient(cert);
+
+      ASSERT.equal(p7.recipients.length, 1);
+      ASSERT.deepEqual(p7.recipients[0].serialNumber, cert.serialNumber);
+      ASSERT.deepEqual(p7.recipients[0].issuer, cert.subject.attributes);
+      ASSERT.deepEqual(p7.recipients[0].encryptedContent.key, cert.publicKey);
+    });
+
+    it('should aes-encrypt a message', function() {
+      var p7 = PKCS7.createEnvelopedData();
+      var cert = PKI.certificateFromPem(_pem.certificate);
+      var privateKey = PKI.privateKeyFromPem(_pem.privateKey);
+
+      p7.addRecipient(cert);
+      p7.content = UTIL.createBuffer('Just a little test');
+
+      // pre-condition, PKCS#7 module should default to AES-256-CBC
+      ASSERT.equal(p7.encryptedContent.algorithm, PKI.oids['aes256-CBC']);
+      p7.encrypt();
+
+      // since we did not provide a key, a random key should have been created
+      // automatically, AES256 requires 32 bytes of key material
+      ASSERT.equal(p7.encryptedContent.key.data.length, 32);
+
+      // furthermore an IV must be generated, AES256 has 16 byte IV
+      ASSERT.equal(p7.encryptedContent.parameter.data.length, 16);
+
+      // content is 18 bytes long, AES has 16 byte blocksize,
+      // with padding that makes 32 bytes
+      ASSERT.equal(p7.encryptedContent.content.data.length, 32);
+
+      // RSA encryption should yield 256 bytes
+      ASSERT.equal(p7.recipients[0].encryptedContent.content.length, 256);
+
+      // rewind Key & IV
+      p7.encryptedContent.key.read = 0;
+      p7.encryptedContent.parameter.read = 0;
+
+      // decryption of the asym. encrypted data should reveal the symmetric key
+      var decryptedKey = privateKey.decrypt(
+        p7.recipients[0].encryptedContent.content);
+      ASSERT.equal(decryptedKey, p7.encryptedContent.key.data);
+
+      // decryption of sym. encrypted data should reveal the content
+      var ciph = AES.createDecryptionCipher(decryptedKey);
+      ciph.start(p7.encryptedContent.parameter);
+      ciph.update(p7.encryptedContent.content);
+      ciph.finish();
+      ASSERT.equal(ciph.output, 'Just a little test');
+    });
+
+    it('should 3des-ede-encrypt a message', function() {
+      var p7 = PKCS7.createEnvelopedData();
+      var cert = PKI.certificateFromPem(_pem.certificate);
+      var privateKey = PKI.privateKeyFromPem(_pem.privateKey);
+
+      p7.addRecipient(cert);
+      p7.content = UTIL.createBuffer('Just a little test');
+      p7.encryptedContent.algorithm = PKI.oids['des-EDE3-CBC'];
+      p7.encrypt();
+
+      // since we did not provide a key, a random key should have been created
+      // automatically, 3DES-EDE requires 24 bytes of key material
+      ASSERT.equal(p7.encryptedContent.key.data.length, 24);
+
+      // furthermore an IV must be generated, DES3 has 8 byte IV
+      ASSERT.equal(p7.encryptedContent.parameter.data.length, 8);
+
+      // content is 18 bytes long, DES has 8 byte blocksize,
+      // with padding that makes 24 bytes
+      ASSERT.equal(p7.encryptedContent.content.data.length, 24);
+
+      // RSA encryption should yield 256 bytes
+      ASSERT.equal(p7.recipients[0].encryptedContent.content.length, 256);
+
+      // rewind Key & IV
+      p7.encryptedContent.key.read = 0;
+      p7.encryptedContent.parameter.read = 0;
+
+      // decryption of the asym. encrypted data should reveal the symmetric key
+      var decryptedKey = privateKey.decrypt(
+        p7.recipients[0].encryptedContent.content);
+      ASSERT.equal(decryptedKey, p7.encryptedContent.key.data);
+
+      // decryption of sym. encrypted data should reveal the content
+      var ciph = DES.createDecryptionCipher(decryptedKey);
+      ciph.start(p7.encryptedContent.parameter);
+      ciph.update(p7.encryptedContent.content);
+      ciph.finish();
+      ASSERT.equal(ciph.output, 'Just a little test');
+    });
+
+    it('should export message to PEM', function() {
+      var p7 = PKCS7.createEnvelopedData();
+      p7.addRecipient(PKI.certificateFromPem(_pem.certificate));
+      p7.content = UTIL.createBuffer('Just a little test');
+      p7.encrypt();
+
+      var pem = PKCS7.messageToPem(p7);
+
+      // convert back from PEM to new PKCS#7 object, decrypt, and test
+      p7 = PKCS7.messageFromPem(pem);
+      p7.decrypt(p7.recipients[0], PKI.privateKeyFromPem(_pem.privateKey));
+      ASSERT.equal(p7.content, 'Just a little test');
+    });
+
+    it('should decrypt encrypted data from PEM', function() {
+      var result = '1f8b08000000000000000b2e494d4bcc5308ce4c4dcfd15130b0b430d4b7343732b03437d05170cc2b4e4a4cced051b034343532d25170492d2d294ecec849cc4b0100bf52f02437000000';
+      var key = 'b96e4a4c0a3555d31e1b295647cc5cfe74081918cb7f797b';
+      key = UTIL.createBuffer(UTIL.hexToBytes(key));
+
+      ASSERT.doesNotThrow(function() {
+        var p7 = PKCS7.messageFromPem(_pem.encryptedData);
+        ASSERT.equal(p7.type, PKI.oids.encryptedData);
+        ASSERT.equal(p7.encryptedContent.algorithm, PKI.oids['des-EDE3-CBC']);
+        ASSERT.equal(p7.encryptedContent.parameter.toHex(), 'ba9305a2ee57dc35');
+        ASSERT.equal(p7.encryptedContent.content.length(), 80);
+
+        p7.decrypt(key);
+        ASSERT.equal(p7.content.toHex(), result);
+      });
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/pkcs7',
+    'forge/pki',
+    'forge/aes',
+    'forge/des',
+    'forge/util'
+  ], function(PKCS7, PKI, AES, DES, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      PKCS7(),
+      PKI(),
+      AES(),
+      DES(),
+      UTIL()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/pkcs7')(),
+    require('../../js/pki')(),
+    require('../../js/aes')(),
+    require('../../js/des')(),
+    require('../../js/util')());
+}
+
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/random.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/random.js
new file mode 100644
index 0000000..efeec2b
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/random.js
@@ -0,0 +1,70 @@
+(function() {
+
+function Tests(ASSERT, RANDOM, UTIL) {
+  var random = RANDOM();
+
+  describe('random', function() {
+    it('should generate 10 random bytes', function() {
+      random.getBytes(16);
+      random.getBytes(24);
+      random.getBytes(32);
+
+      var b = random.getBytes(10);
+      ASSERT.equal(b.length, 10);
+    });
+
+    it('should use a synchronous seed file', function() {
+      var rand = RANDOM();
+      rand.seedFileSync = function(needed) {
+        return UTIL.fillString('a', needed);
+      };
+      var b = rand.getBytes(10);
+      ASSERT.equal(UTIL.bytesToHex(b), '80a7901a239c3e606319');
+    });
+
+    it('should use an asynchronous seed file', function(done) {
+      var rand = RANDOM();
+      rand.seedFile = function(needed, callback) {
+        callback(null, UTIL.fillString('a', needed));
+      };
+      rand.getBytes(10, function(err, b) {
+        ASSERT.equal(err, null);
+        ASSERT.equal(UTIL.bytesToHex(b), '80a7901a239c3e606319');
+        done();
+      });
+    });
+
+    it('should collect some random bytes', function() {
+      var rand = RANDOM();
+      rand.seedFileSync = function(needed) {
+        return UTIL.fillString('a', needed);
+      };
+      rand.collect('bbb');
+      var b = rand.getBytes(10);
+      ASSERT.equal(UTIL.bytesToHex(b), 'ff8d213516047c94ca46');
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/random',
+    'forge/util'
+  ], function(RANDOM, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      RANDOM,
+      UTIL()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/random'),
+    require('../../js/util')());
+}
+
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/rc2.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/rc2.js
new file mode 100644
index 0000000..2acbe7b
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/rc2.js
@@ -0,0 +1,109 @@
+(function() {
+
+function Tests(ASSERT, RC2, UTIL) {
+  describe('rc2', function() {
+    it('should expand a 128-bit key', function() {
+      var key = UTIL.hexToBytes('88bca90e90875a7f0f79c384627bafb2');
+      var expect = '71ab26462f0b9333609d4476e48ab72438c2194b70a47085d84b6af1dc72119023b94fe80aee2b6b45f27f923d9be1570da3ce8b16ad7f78db166ffbc28a836a4392cf0b748085dae4b69bdc2a4679cdfc09d84317016987e0c5b765c91dc612b1f44d7921b3e2c46447508bd2ac02e119e0f42a89c719675da320cf3e8958cd';
+      ASSERT.equal(RC2.expandKey(key).toHex(), expect);
+    });
+
+    it('should expand a 40-bit key', function() {
+      var key = UTIL.hexToBytes('88bca90e90');
+      var expect = 'af136d2243b94a0878d7a604f8d6d9fd64a698fd6ebc613e641f0d1612055ef6cb55966db8f32bfd9246dae99880be8a91433adf54ea546d9daad62db7a55f6c7790aa87ba67de0e9ea9128dfc7ccdddd7c47c33d2bb7f823729977f083b5dc1f5bb09000b98e12cdaaf22f80dcc88c37d2c2fd80402f8a30a9e41d356669471';
+      ASSERT.equal(RC2.expandKey(key, 40).toHex(), expect);
+    });
+
+    it('should rc2-ecb encrypt zeros', function() {
+      var key = UTIL.hexToBytes('88bca90e90875a7f0f79c384627bafb2');
+      var input = new UTIL.createBuffer().fillWithByte(0, 8);
+      var cipher = RC2.startEncrypting(key, null, null);
+      cipher.update(input);
+      cipher.finish();
+      ASSERT.equal(cipher.output.toHex(), '2269552ab0f85ca6e35b3b2ce4e02191');
+    });
+
+    it('should rc2-ecb encrypt: vegan', function() {
+      var key = UTIL.hexToBytes('88bca90e90875a7f0f79c384627bafb2');
+      var input = new UTIL.createBuffer('vegan');
+      var cipher = RC2.startEncrypting(key, null, null);
+      cipher.update(input);
+      cipher.finish();
+      ASSERT.equal(cipher.output.toHex(), '2194adaf4d517e3a');
+    });
+
+    it('should rc2-ecb decrypt: 2194adaf4d517e3a', function() {
+      var key = UTIL.hexToBytes('88bca90e90875a7f0f79c384627bafb2');
+      var input = new UTIL.createBuffer(UTIL.hexToBytes('2194adaf4d517e3a'));
+      var cipher = RC2.startDecrypting(key, null, null);
+      cipher.update(input);
+      cipher.finish();
+      ASSERT.equal(cipher.output.getBytes(), 'vegan');
+    });
+
+    it('should rc2-cbc encrypt: revolution', function() {
+      var key = UTIL.hexToBytes('88bca90e90875a7f0f79c384627bafb2');
+      var iv = new UTIL.createBuffer(UTIL.hexToBytes('0123456789abcdef'));
+      var input = new UTIL.createBuffer('revolution');
+      var cipher = RC2.startEncrypting(key, iv, null);
+      cipher.update(input);
+      cipher.finish();
+      ASSERT.equal(cipher.output.toHex(), '50cfd16e0fd7f20b17a622eb2a469b7e');
+    });
+
+    it('should rc2-cbc decrypt: 50cfd16e0fd7f20b17a622eb2a469b7e', function() {
+      var key = UTIL.hexToBytes('88bca90e90875a7f0f79c384627bafb2');
+      var iv = new UTIL.createBuffer(UTIL.hexToBytes('0123456789abcdef'));
+      var input = new UTIL.createBuffer(
+        UTIL.hexToBytes('50cfd16e0fd7f20b17a622eb2a469b7e'));
+      var cipher = RC2.startDecrypting(key, iv, null);
+      cipher.update(input);
+      cipher.finish();
+      ASSERT.equal(cipher.output, 'revolution');
+    });
+
+    it('should rc2-cbc encrypt w/binary string iv: revolution', function() {
+      var key = UTIL.hexToBytes('88bca90e90875a7f0f79c384627bafb2');
+      var iv = UTIL.hexToBytes('0123456789abcdef');
+      var input = new UTIL.createBuffer('revolution');
+      var cipher = RC2.startEncrypting(key, iv, null);
+      cipher.update(input);
+      cipher.finish();
+      ASSERT.equal(cipher.output.toHex(), '50cfd16e0fd7f20b17a622eb2a469b7e');
+    });
+
+    it('should rc2-cbc decrypt w/binary string iv: 50cfd16e0fd7f20b17a622eb2a469b7e', function() {
+      var key = UTIL.hexToBytes('88bca90e90875a7f0f79c384627bafb2');
+      var iv = UTIL.hexToBytes('0123456789abcdef');
+      var input = new UTIL.createBuffer(
+        UTIL.hexToBytes('50cfd16e0fd7f20b17a622eb2a469b7e'));
+      var cipher = RC2.startDecrypting(key, iv, null);
+      cipher.update(input);
+      cipher.finish();
+      ASSERT.equal(cipher.output, 'revolution');
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/rc2',
+    'forge/util'
+  ], function(RC2, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      RC2(),
+      UTIL()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/rc2')(),
+    require('../../js/util')());
+}
+
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/rsa.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/rsa.js
new file mode 100644
index 0000000..434d7a3
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/rsa.js
@@ -0,0 +1,602 @@
+(function() {
+
+function Tests(ASSERT, PKI, RSA, MD, MGF, PSS, RANDOM, UTIL) {
+  var _pem = {
+    privateKey: '-----BEGIN RSA PRIVATE KEY-----\r\n' +
+      'MIICXQIBAAKBgQDL0EugUiNGMWscLAVM0VoMdhDZEJOqdsUMpx9U0YZI7szokJqQ\r\n' +
+      'NIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMGTkP3VF29vXBo+dLq5e+8VyAy\r\n' +
+      'Q3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGtvnM+z0MYDdKo80efzwIDAQAB\r\n' +
+      'AoGAIzkGONi5G+JifmXlLJdplom486p3upf4Ce2/7mqfaG9MnkyPSairKD/JXvfh\r\n' +
+      'NNWkkN8DKKDKBcVVElPgORYT0qwrWc7ueLBMUCbRXb1ZyfEulimG0R3kjUh7NYau\r\n' +
+      'DaIkVgfykXGSQMZx8FoaT6L080zd+0emKDDYRrb+/kgJNJECQQDoUZoiC2K/DWNY\r\n' +
+      'h3/ppZ0ane2y4SBmJUHJVMPQ2CEgxsrJTxet668ckNCKaOP/3VFPoWC41f17DvKq\r\n' +
+      'noYINNntAkEA4JbZBZBVUrQFhHlrpXT4jzqtO2RlKZzEq8qmFZfEErxOT1WMyyCi\r\n' +
+      'lAQ5gUKardo1Kf0omC8Xq/uO9ZYdED55KwJBALs6cJ65UFaq4oLJiQPzLd7yokuE\r\n' +
+      'dcj8g71PLBTW6jPxIiMFNA89nz3FU9wIVp+xbMNhSoMMKqIPVPC+m0Rn260CQQDA\r\n' +
+      'I83fWK/mZWUjBM33a68KumRiH238v8XyQxj7+C8i6D8G2GXvkigFAehAkb7LZZd+\r\n' +
+      'KLuGFyPlWv3fVWHf99KpAkBQFKk3MRMl6IGJZUEFQe4l5whm8LkGU4acSqv9B3xt\r\n' +
+      'qROkCrsFrMPqjuuzEmyHoQZ64r2PLJg7FOuyhBnQUOt4\r\n' +
+      '-----END RSA PRIVATE KEY-----\r\n',
+    privateKeyInfo: '-----BEGIN PRIVATE KEY-----\r\n' +
+      'MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMvQS6BSI0Yxaxws\r\n' +
+      'BUzRWgx2ENkQk6p2xQynH1TRhkjuzOiQmpA0jCiSJDoSic2dZIyUi/LjoCGeVFif\r\n' +
+      '57N5N5Tt4wZOQ/dUXb29cGj50url77xXIDJDcXMzXAji2ziFEAIXzDqarKBdDuL9\r\n' +
+      'IO7z+tepEa2+cz7PQxgN0qjzR5/PAgMBAAECgYAjOQY42Lkb4mJ+ZeUsl2mWibjz\r\n' +
+      'qne6l/gJ7b/uap9ob0yeTI9JqKsoP8le9+E01aSQ3wMooMoFxVUSU+A5FhPSrCtZ\r\n' +
+      'zu54sExQJtFdvVnJ8S6WKYbRHeSNSHs1hq4NoiRWB/KRcZJAxnHwWhpPovTzTN37\r\n' +
+      'R6YoMNhGtv7+SAk0kQJBAOhRmiILYr8NY1iHf+mlnRqd7bLhIGYlQclUw9DYISDG\r\n' +
+      'yslPF63rrxyQ0Ipo4//dUU+hYLjV/XsO8qqehgg02e0CQQDgltkFkFVStAWEeWul\r\n' +
+      'dPiPOq07ZGUpnMSryqYVl8QSvE5PVYzLIKKUBDmBQpqt2jUp/SiYLxer+471lh0Q\r\n' +
+      'PnkrAkEAuzpwnrlQVqrigsmJA/Mt3vKiS4R1yPyDvU8sFNbqM/EiIwU0Dz2fPcVT\r\n' +
+      '3AhWn7Fsw2FKgwwqog9U8L6bRGfbrQJBAMAjzd9Yr+ZlZSMEzfdrrwq6ZGIfbfy/\r\n' +
+      'xfJDGPv4LyLoPwbYZe+SKAUB6ECRvstll34ou4YXI+Va/d9VYd/30qkCQFAUqTcx\r\n' +
+      'EyXogYllQQVB7iXnCGbwuQZThpxKq/0HfG2pE6QKuwWsw+qO67MSbIehBnrivY8s\r\n' +
+      'mDsU67KEGdBQ63g=\r\n' +
+      '-----END PRIVATE KEY-----\r\n',
+    publicKey: '-----BEGIN PUBLIC KEY-----\r\n' +
+      'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDL0EugUiNGMWscLAVM0VoMdhDZ\r\n' +
+      'EJOqdsUMpx9U0YZI7szokJqQNIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMG\r\n' +
+      'TkP3VF29vXBo+dLq5e+8VyAyQ3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGt\r\n' +
+      'vnM+z0MYDdKo80efzwIDAQAB\r\n' +
+      '-----END PUBLIC KEY-----\r\n'
+  };
+  var _signature =
+    '9200ece65cdaed36bcc20b94c65af852e4f88f0b4fe5b249d54665f815992ac4' +
+    '3a1399e65d938c6a7f16dd39d971a53ca66523209dbbfbcb67afa579dbb0c220' +
+    '672813d9e6f4818f29b9becbb29da2032c5e422da97e0c39bfb7a2e7d568615a' +
+    '5073af0337ff215a8e1b2332d668691f4fb731440055420c24ac451dd3c913f4';
+
+  describe('rsa', function() {
+    it('should generate 512 bit key pair', function() {
+      var pair = RSA.generateKeyPair(512);
+      ASSERT.equal(PKI.privateKeyToPem(pair.privateKey).indexOf('-----BEGIN RSA PRIVATE KEY-----'), 0);
+      ASSERT.equal(PKI.publicKeyToPem(pair.publicKey).indexOf('-----BEGIN PUBLIC KEY-----'), 0);
+
+      // sign and verify
+      var md = MD.sha1.create();
+      md.update('0123456789abcdef');
+      var signature = pair.privateKey.sign(md);
+      ASSERT.ok(pair.publicKey.verify(md.digest().getBytes(), signature));
+    });
+
+    it('should generate the same 512 bit key pair', function() {
+      var prng = RANDOM.createInstance();
+      prng.seedFileSync = function(needed) {
+        return UTIL.fillString('a', needed);
+      };
+      var pair = RSA.generateKeyPair(512, {prng: prng});
+      var pem = {
+        privateKey: PKI.privateKeyToPem(pair.privateKey),
+        publicKey: PKI.publicKeyToPem(pair.publicKey)
+      };
+      ASSERT.equal(pem.privateKey.indexOf('-----BEGIN RSA PRIVATE KEY-----'), 0);
+      ASSERT.equal(pem.publicKey.indexOf('-----BEGIN PUBLIC KEY-----'), 0);
+
+      // sign and verify
+      var md = MD.sha1.create();
+      md.update('0123456789abcdef');
+      var signature = pair.privateKey.sign(md);
+      ASSERT.ok(pair.publicKey.verify(md.digest().getBytes(), signature));
+
+      // create same key pair by using same PRNG
+      prng = RANDOM.createInstance();
+      prng.seedFileSync = function(needed) {
+        return UTIL.fillString('a', needed);
+      };
+      var pair2 = RSA.generateKeyPair(512, {prng: prng});
+      var pem2 = {
+        privateKey: PKI.privateKeyToPem(pair2.privateKey),
+        publicKey: PKI.publicKeyToPem(pair2.publicKey)
+      };
+      ASSERT.equal(pem.privateKey, pem2.privateKey);
+      ASSERT.equal(pem.publicKey, pem2.publicKey);
+    });
+
+    it('should convert private key to/from PEM', function() {
+      var privateKey = PKI.privateKeyFromPem(_pem.privateKey);
+      ASSERT.equal(PKI.privateKeyToPem(privateKey), _pem.privateKey);
+    });
+
+    it('should convert public key to/from PEM', function() {
+      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
+      ASSERT.equal(PKI.publicKeyToPem(publicKey), _pem.publicKey);
+    });
+
+    it('should convert a PKCS#8 PrivateKeyInfo to/from PEM', function() {
+      var privateKey = PKI.privateKeyFromPem(_pem.privateKeyInfo);
+      var rsaPrivateKey = PKI.privateKeyToAsn1(privateKey);
+      var pki = PKI.wrapRsaPrivateKey(rsaPrivateKey);
+      ASSERT.equal(PKI.privateKeyInfoToPem(pki), _pem.privateKeyInfo);
+    });
+
+    (function() {
+      var algorithms = ['aes128', 'aes192', 'aes256', '3des', 'des'];
+      for(var i = 0; i < algorithms.length; ++i) {
+        var algorithm = algorithms[i];
+        it('should PKCS#8 encrypt and decrypt private key with ' + algorithm, function() {
+          var privateKey = PKI.privateKeyFromPem(_pem.privateKey);
+          var encryptedPem = PKI.encryptRsaPrivateKey(
+             privateKey, 'password', {algorithm: algorithm});
+          privateKey = PKI.decryptRsaPrivateKey(encryptedPem, 'password');
+          ASSERT.equal(PKI.privateKeyToPem(privateKey), _pem.privateKey);
+        });
+      }
+    })();
+
+    (function() {
+      var algorithms = ['aes128', 'aes192', 'aes256', '3des', 'des'];
+      for(var i = 0; i < algorithms.length; ++i) {
+        var algorithm = algorithms[i];
+        it('should legacy (OpenSSL style) encrypt and decrypt private key with ' + algorithm, function() {
+          var privateKey = PKI.privateKeyFromPem(_pem.privateKey);
+          var encryptedPem = PKI.encryptRsaPrivateKey(
+             privateKey, 'password', {algorithm: algorithm, legacy: true});
+          privateKey = PKI.decryptRsaPrivateKey(encryptedPem, 'password');
+          ASSERT.equal(PKI.privateKeyToPem(privateKey), _pem.privateKey);
+        });
+      }
+    })();
+
+    it('should verify signature', function() {
+      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
+      var md = MD.sha1.create();
+      md.update('0123456789abcdef');
+      var signature = UTIL.hexToBytes(_signature);
+      ASSERT.ok(publicKey.verify(md.digest().getBytes(), signature));
+    });
+
+    it('should sign and verify', function() {
+      var privateKey = PKI.privateKeyFromPem(_pem.privateKey);
+      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
+      var md = MD.sha1.create();
+      md.update('0123456789abcdef');
+      var signature = privateKey.sign(md);
+      ASSERT.ok(publicKey.verify(md.digest().getBytes(), signature));
+    });
+
+    it('should generate missing CRT parameters, sign, and verify', function() {
+      var privateKey = PKI.privateKeyFromPem(_pem.privateKey);
+
+      // remove dQ, dP, and qInv
+      privateKey = RSA.setPrivateKey(
+        privateKey.n, privateKey.e, privateKey.d,
+        privateKey.p, privateKey.q);
+
+      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
+      var md = MD.sha1.create();
+      md.update('0123456789abcdef');
+      var signature = privateKey.sign(md);
+      ASSERT.ok(publicKey.verify(md.digest().getBytes(), signature));
+    });
+
+    it('should sign and verify with a private key containing only e, n, and d parameters', function() {
+      var privateKey = PKI.privateKeyFromPem(_pem.privateKey);
+
+      // remove all CRT parameters from private key, so that it consists
+      // only of e, n and d (which make a perfectly valid private key, but its
+      // operations are slower)
+      privateKey = RSA.setPrivateKey(
+        privateKey.n, privateKey.e, privateKey.d);
+
+      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
+      var md = MD.sha1.create();
+      md.update('0123456789abcdef');
+      var signature = privateKey.sign(md);
+      ASSERT.ok(publicKey.verify(md.digest().getBytes(), signature));
+    });
+
+    (function() {
+      var tests = [{
+        keySize: 1024,
+        privateKeyPem: '-----BEGIN RSA PRIVATE KEY-----\r\n' +
+          'MIICWwIBAAKBgQDCjvkkLWNTeYXqEsqGiVCW/pDt3/qAodNMHcU9gOU2rxeWwiRu\r\n' +
+          'OhhLqmMxXHLi0oP5Xmg0m7zdOiLMEyzzyRzdp21aqp3k5qtuSDkZcf1prsp1jpYm\r\n' +
+          '6z9EGpaSHb64BCuUsQGmUPKutd5RERKHGZXtiRuvvIyue7ETq6VjXrOUHQIDAQAB\r\n' +
+          'AoGAOKeBjTNaVRhyEnNeXkbmHNIMSfiK7aIx8VxJ71r1ZDMgX1oxWZe5M29uaxVM\r\n' +
+          'rxg2Lgt7tLYVDSa8s0hyMptBuBdy3TJUWruDx85uwCrWnMerCt/iKVBS22fv5vm0\r\n' +
+          'LEq/4gjgIVTZwgqbVxGsBlKcY2VzxAfYqYzU8EOZBeNhZdECQQDy+PJAPcUN2xOs\r\n' +
+          '6qy66S91x6y3vMjs900OeX4+bgT4VSVKmLpqRTPizzcL07tT4+Y+pAAOX6VstZvZ\r\n' +
+          '6iFDL5rPAkEAzP1+gaRczboKoJWKJt0uEMUmztcY9NXJFDmjVLqzKwKjcAoGgIal\r\n' +
+          'h+uBFT9VJ16QajC7KxTRLlarzmMvspItUwJAeUMNhEpPwm6ID1DADDi82wdgiALM\r\n' +
+          'NJfn+UVhYD8Ac//qsKQwxUDseFH6owh1AZVIIBMxg/rwUKUCt2tGVoW3uQJAIt6M\r\n' +
+          'Aml/D8+xtxc45NuC1n9y1oRoTl1/Ut1rFyKbD5nnS0upR3uf9LruvjqDtaq0Thvz\r\n' +
+          '+qQT4RoFJ5pfprSO2QJAdMkfNWRqECfAhZyQuUrapeWU3eQ0wjvktIynCIwiBDd2\r\n' +
+          'MfjmVXzBJhMk6dtINt+vBEITVQEOdtyTgDt0y3n2Lw==\r\n' +
+          '-----END RSA PRIVATE KEY-----\r\n',
+        publicKeyPem: '-----BEGIN PUBLIC KEY-----\r\n' +
+          'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCjvkkLWNTeYXqEsqGiVCW/pDt\r\n' +
+          '3/qAodNMHcU9gOU2rxeWwiRuOhhLqmMxXHLi0oP5Xmg0m7zdOiLMEyzzyRzdp21a\r\n' +
+          'qp3k5qtuSDkZcf1prsp1jpYm6z9EGpaSHb64BCuUsQGmUPKutd5RERKHGZXtiRuv\r\n' +
+          'vIyue7ETq6VjXrOUHQIDAQAB\r\n' +
+          '-----END PUBLIC KEY-----\r\n',
+        encrypted: 'jsej3OoacmJ1VjWrlw68F+drnQORAuKAqVu6RMbz1xSXjzA355vctrJZXolRU0mvzuu/6VuNynkKGGyRJ6DHt85CvwTMChw4tOMV4Dy6bgnUt3j+DZA2sWTwFhOlpzvNQMK70QpuqrXtOZmAO59EwoDeJkW/iH6t4YzNOVYo9Jg=',
+        signature: 'GT0/3EV2zrXxPd1ydijJq3R7lkI4c0GtcprgpG04dSECv/xyXtikuzivxv7XzUdHpu6QiYmM0xE4D4i7LK3Mzy+f7aB4o/dg8XXO3htLiBzVI+ZJCRh06RdYctPtclAWmyZikZ8Etw3NnA/ldKuG4jApbwRb21UFm5gYLrJ4SP4=',
+        signaturePss: 'F4xffaANDBjhFxeSJx8ANuBbdhaWZjUHRQh4ueYQMPPCaR2mpwdqxE04sbgNgIiZzBuLIAI4HpTMMoDk3Rruhjefx3+9UhzTxgB0hRI+KzRChRs+ToltWWDZdYzt9T8hfTlELeqT4V8HgjDuteO/IAvIVlRIBwMNv53Iebu1FY4=',
+        signatureWithAbcSalt: 'GYA/Zp8G+jqG2Fu7Um+XP7Cr/yaVdzJN8lyt57Lw6gFflia2CPbOVMLyqLzD7fKoE8UD0Rc6DF8k04xhEu60sudw2nxGHeDvpL4M9du0uYra/WSr9kv7xNjAW62NyNerDngHD2J7O8gQ07TZiTXkrfS724vQab5xZL/+FhvisMY=',
+        signatureWithCustomPrng: 'LzWcUpUYK+URDp72hJbz1GVEp0rG0LHjd+Pdh2w5rfQFbUThbmXDl3X6DUT5UZr5RjUSHtc2usvH+w49XskyIJJO929sUk9EkMJMK/6QAnYYEp5BA+48pdGNNMZyjIbhyl9Y4lInzFPX8XYMM8o+tdSK+hj+dW5OPdnwWbDtR7U='
+      }, {
+        keySize: 1025,
+        privateKeyPem: '-----BEGIN RSA PRIVATE KEY-----\r\n' +
+          'MIICXgIBAAKBgQGIkej4PDlAigUh5fbbHp1WXuTHhOdQfAke+LoH0TM4uzn0QmgK\r\n' +
+          'SJqxzB1COJ5o0DwZw/NR+CNy7NUrly+vmh2YPwsaqN+AsYBF9qsF93oN8/TBtaL/\r\n' +
+          'GRoRGpDcCglkj1kZnDaWR79NsG8mC0TrvQCkcCLOP0c2Ux1hRbntOetGXwIDAQAB\r\n' +
+          'AoGBAIaJWsoX+ZcAthmT8jHOICXFh6pJBe0zVPzkSPz82Q0MPSRUzcsYbsuYJD7Z\r\n' +
+          'oJBTLQW3feANpjhwqe2ydok7y//ONm3Th53Bcu8jLfoatg4KYxNFIwXEO10mPOld\r\n' +
+          'VuDIGrBkTABe6q2P5PeUKGCKLT6i/u/2OTXTrQiJbQ0gU8thAkEBjqcFivWMXo34\r\n' +
+          'Cb9/EgfWCCtv9edRMexgvcFMysRsbHJHDK9JjRLobZltwtAv3cY7F3a/Cu1afg+g\r\n' +
+          'jAzm5E3gowJBAPwYFHTLzaZToxFKNQztWrPsXF6YfqHpPUUIpT4UzL6DhGG0M00U\r\n' +
+          'qMyhkYRRqmGOSrSovjg2hjM2643MUUWxUxUCQDPkk/khu5L3YglKzyy2rmrD1MAq\r\n' +
+          'y0v3XCR3TBq89Ows+AizrJxbkLvrk/kfBowU6M5GG9o9SWFNgXWZnFittocCQQDT\r\n' +
+          'e1P1419DUFi1UX6NuLTlybx3sxBQvf0jY6xUF1jn3ib5XBXJbTJqcIRF78iyjI9J\r\n' +
+          'XWIugDc20bTsQOJRSAA9AkEBU8kpueHBaiXTikqqlK9wvc2Lp476hgyKVmVyBGye\r\n' +
+          '9TLTWkTCzDPtManLy47YtXkXnmyazS+DlKFU61XAGEnZfg==\r\n' +
+          '-----END RSA PRIVATE KEY-----\r\n',
+        publicKeyPem: '-----BEGIN PUBLIC KEY-----\r\n' +
+          'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQGIkej4PDlAigUh5fbbHp1WXuTH\r\n' +
+          'hOdQfAke+LoH0TM4uzn0QmgKSJqxzB1COJ5o0DwZw/NR+CNy7NUrly+vmh2YPwsa\r\n' +
+          'qN+AsYBF9qsF93oN8/TBtaL/GRoRGpDcCglkj1kZnDaWR79NsG8mC0TrvQCkcCLO\r\n' +
+          'P0c2Ux1hRbntOetGXwIDAQAB\r\n' +
+          '-----END PUBLIC KEY-----\r\n',
+        encrypted: 'AOVeCUN8BOVkZvt4mxyNn/yCYE1MZ40A3e/osh6EvCBcJ09hyYbx7bzKSrdkhRnDyW0pGtgP352CollasllQZ9HlfI2Wy9zKM0aYZZn8OHBA+60Tc3xHHDGznLZqggUKuhoNpj+faVZ1uzb285eTpQQa+4mLUue2svJD4ViM8+ng',
+        signature: 'AFSx0axDYXlF2rO3ofgUhYSI8ZlIWtJUUZ62PhgdBp9O5zFqMX3DXoiov1e7NenSOz1khvTSMctFWzKP3GU3F0yewe+Yd3UAZE0dM8vAxigSSfAchUkBDmp9OFuszUie63zwWwpG+gXtvyfueZs1RniBvW1ZmXJvS+HFgX4ouzwd',
+        signaturePss: 'AQvBdhAXDpu+7RpcybMgwuTUk6w+qa08Lcq3G1xHY4kC7ZUzauZd/Jn9e0ePKApDqs7eDNAOV+dQkU2wiH/uBg6VGelzb0hFwcpSLyBW92Vw0q3GlzY7myWn8qnNzasrt110zFflWQa1GiuzH/C8f+Z82/MzlWDxloJIYbq2PRC8',
+        signatureWithAbcSalt: 'AW4bKnG/0TGvAZgqX5Dk+fXpUNgX7INFelE46d3m+spaMTG5XalY0xP1sxWfaE/+Zl3FmZcfTNtfOCo0eNRO1h1+GZZfp32ZQZmZvkdUG+dUQp318LNzgygrVf/5iIX+QKV5/soSDuAHBzS7yDfMgzJfnXNpFE/zPLOgZIoOIuLq',
+        signatureWithCustomPrng: 'AVxfCyGC/7Y3kz//eYFEuWQijjR7eR05AM36CwDlLsVkDRtXoeVzz2yTFBdP+i+QgQ73C/I3lLtvXTwfleorvIX9YncVBeGDQXssmULxzqsM3izaLfJXCRAGx9ErL1Az10+fAqPZpq954OVSDqrR/61Q7CsMY7CiQO3nfIIaxgVL'
+      }, {
+        keySize: 1031,
+        privateKeyPem: '-----BEGIN RSA PRIVATE KEY-----\r\n' +
+          'MIICXwIBAAKBgWyeKqA2oA4klYrKT9hjjutYQksJNN0cxwaQwIm9AYiLxOsYtT/C\r\n' +
+          'ovJx5Oy1EvkbYQbfvYsGISUx9bW8yasZkTHR55IbW3+UptvQjTDtdxBQTgQOpsAh\r\n' +
+          'BJtZYY3OmyH9Sj3F3oB//oyriNoj0QYyfsvlO8UsMmLzpnf6qfZBDHA/9QIDAQAB\r\n' +
+          'AoGBBj/3ne5muUmbnTfU7lOUNrCGaADonMx6G0ObAJHyk6PPOePbEgcmDyNEk+Y7\r\n' +
+          'aEAODjIzmttIbvZ39/Qb+o9nDmCSZC9VxiYPP+rjOzPglCDT5ks2Xcjwzd3If6Ya\r\n' +
+          'Uw6P31Y760OCYeTb4Ib+8zz5q51CkjkdX5Hq/Yu+lZn0Vx7BAkENo83VfL+bwxTm\r\n' +
+          'V7vR6gXqTD5IuuIGHL3uTmMNNURAP6FQDHu//duipys83iMChcOeXtboE16qYrO0\r\n' +
+          '9KC0cqL4JQJBB/aYo/auVUGZA6f50YBp0b2slGMk9TBQG0iQefuuSyH4kzKnt2e3\r\n' +
+          'Q40SBmprcM+DfttWJ11bouec++goXjz+95ECQQyiTWYRxulgKVuyqCYnvpLnTEnR\r\n' +
+          '0MoYlVTHBriVPkLErYaYCYgse+SNM1+N4p/Thv6KmkUcq/Lmuc5DSRfbl1iBAkEE\r\n' +
+          '7GKtJQvd7EO1bfpXnARQx+tWhwHHkgpFBBVHReMZ0rQEFhJ5o2c8HZEiZFNvGO2c\r\n' +
+          '1fErP14zlu2JFZ03vpCI8QJBCQz9HL28VNjafSAF2mon/SNjKablRjoGGKSoSdyA\r\n' +
+          'DHDZ/LeRsTp2dg8+bSiG1R+vPqw0f/BT+ux295Sy9ocGEM8=\r\n' +
+          '-----END RSA PRIVATE KEY-----\r\n',
+        publicKeyPem: '-----BEGIN PUBLIC KEY-----\r\n' +
+          'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgWyeKqA2oA4klYrKT9hjjutYQksJ\r\n' +
+          'NN0cxwaQwIm9AYiLxOsYtT/CovJx5Oy1EvkbYQbfvYsGISUx9bW8yasZkTHR55Ib\r\n' +
+          'W3+UptvQjTDtdxBQTgQOpsAhBJtZYY3OmyH9Sj3F3oB//oyriNoj0QYyfsvlO8Us\r\n' +
+          'MmLzpnf6qfZBDHA/9QIDAQAB\r\n' +
+          '-----END PUBLIC KEY-----\r\n',
+        encrypted: 'ShSS4/fEAkuS6XiQakhOpWp82IXaaCaDNtsndU4uokvriqgCGZyqc+IkIk3eVmZ8bn4vVIRR43ydFuvGgsptVjizOdLGZudph3TJ1clcYEMcCXk4z5HaEu0bx5SW9jmzHhE/z+WV8PB48q7y7C2qtmPmfttG2NMsNLBvkiaDopRO',
+        signature: 'Z3vYgRdezrWmdA3NC1Uz2CcHRTcE+/C2idGZA1FjUGqFztAHQ31k0QW/F5zuJdKvg8LQU45S3KxW+OQpbGPL98QbzJLhml88mFGe6OinLXJbi7UQWrtXwamc2jMdiXwovSLbXaXy6PX2QW089iC8XuAZftVi3T/IKV0458FQQprg',
+        signaturePss: 'R6QsK6b3QinIPZPamm/dP0Zndqti1TzAkFTRSZJaRSa1u2zuvZC5QHF4flDjEtHosWeDyxrBE7PHGQZ0b1bHv9qgHGsJCMwaQPj3AWj9fjYmx7b86KM2vHr8q/vqDaa9pTvVRSSwvD6fwoZPc9twQEfdjdDBAiy23yLDzk/zZiwM',
+        signatureWithAbcSalt: 'Ep9qx4/FPNcWTixWhvL2IAyJR69o5I4MIJi3cMAhDmpuTvAaL/ThQwFWkBPPOPT4Jbumnu6ELjPNjo72wa00e5k64qnZgy1pauBPMlXRlKehRc9UJZ6+xot642z8Qs+rt89OgbYTsvlyr8lzXooUHz/lPpfawYCqd7maRMs8YlYM',
+        signatureWithCustomPrng: 'NHAwyn2MdM5ez/WbDNbu2A2JNS+cRiWk/zBoh0lg3aq/RsBS0nrYr4AGiC5jt6KWVcN4AIVOomYtX2k+MhLoemN2t2rDj/+LXOeU7kgCAz0q0ED2NFQz7919JU+PuYXMy03qTMfl5jbvStdi/00eQHjJKGEH+xAgrDcED2lrhtCu'
+      }, {
+        keySize: 1032,
+        privateKeyPem: '-----BEGIN RSA PRIVATE KEY-----\r\n' +
+          'MIICYQIBAAKBggDPhzn5I3GecxWt5DKbP+VhM2AFNSOL0+VbYEOR1hnlZdLbxGK4\r\n' +
+          'cPQzMr2qT6dyttJcsgWr3xKobPkz7vsTZzQATSiekm5Js5dGpaj5lrq/x2+WTZvn\r\n' +
+          '55x9M5Y5dlpusDMKcC3KaIX/axc+MbvPFzo6Eli7JLCWdBg01eKo30knil0CAwEA\r\n' +
+          'AQKBggCNl/sjFF7SOD1jbt5kdL0hi7cI9o+xOLs1lEGmAEmc7dNnZN/ibhb/06/6\r\n' +
+          'wuxB5aEz47bg5IvLZMbG+1hNjc26D0J6Y3Ltwrg8f4ZMdDrh4v0DZ8hy/HbEpMrJ\r\n' +
+          'Td5dk3mtw9FLow10MB5udPLTDKhfDpTcWiObKm2STtFeBk3xeEECQQ6Cx6bZxQJ1\r\n' +
+          'zCxflV5Xi8BgAQaUKMqygugte+HpOLflL0j1fuZ0rPosUyDOEFkTzOsPxBYYOU8i\r\n' +
+          'Gzan1GvW3WwRAkEOTTRt849wpgC9xx2pF0IrYEVmv5gEMy3IiRfCNgEoBwpTWVf4\r\n' +
+          'QFpN3V/9GFz0WQEEYo6OTmkNcC3Of5zbHhu1jQJBBGxXAYQ2KnbP4uLL/DMBdYWO\r\n' +
+          'Knw1JvxdLPrYXVejI2MoE7xJj2QXajbirAhEMXL4rtpicj22EmoaE4H7HVgkrJEC\r\n' +
+          'QQq2V5w4AGwvW4TLHXNnYX/eB33z6ujScOuxjGNDUlBqHZja5iKkCUAjnl+UnSPF\r\n' +
+          'exaOwBrlrpiLOzRer94MylKNAkEBmI58bqfkI5OCGDArAsJ0Ih58V0l1UW35C1SX\r\n' +
+          '4yDoXSM5A/xQu2BJbXO4jPe3PnDvCVCEyKpbCK6bWbe26Y7zuw==\r\n' +
+          '-----END RSA PRIVATE KEY-----\r\n',
+        publicKeyPem: '-----BEGIN PUBLIC KEY-----\r\n' +
+          'MIGgMA0GCSqGSIb3DQEBAQUAA4GOADCBigKBggDPhzn5I3GecxWt5DKbP+VhM2AF\r\n' +
+          'NSOL0+VbYEOR1hnlZdLbxGK4cPQzMr2qT6dyttJcsgWr3xKobPkz7vsTZzQATSie\r\n' +
+          'km5Js5dGpaj5lrq/x2+WTZvn55x9M5Y5dlpusDMKcC3KaIX/axc+MbvPFzo6Eli7\r\n' +
+          'JLCWdBg01eKo30knil0CAwEAAQ==\r\n' +
+          '-----END PUBLIC KEY-----\r\n',
+        encrypted: 'pKTbv+xgXPDc+wbjsANFu1/WTcmy4aZFKXKnxddHbU5S0Dpdj2OqCACiBwu1oENPMgPAJ27XRbFtKG+eS8tX47mKP2Fo0Bi+BPFtzuQ1bj3zUzTwzjemT+PU+a4Tho/eKjPhm6xrwGAoQH2VEDEpvcYf+SRmGFJpJ/zPUrSxgffj',
+        signature: 'R9WBFprCfcIC4zY9SmBpEM0E+cr5j4gMn3Ido5mktoR9VBoJqC6eR6lubIPvZZUz9e4yUSYX0squ56Q9Y0yZFQjTHgsrlmhB2YW8kpv4h8P32Oz2TLcMJK9R2tIh9vvyxwBkd/Ml1qG60GnOFUFzxUad9VIlzaF1PFR6EfnkgBUW',
+        signaturePss: 'v9UBd4XzBxSRz8yhWKjUkFpBX4Fr2G+ImjqbePL4sAZvYw1tWL+aUQpzG8eOyMxxE703VDh9nIZULYI/uIb9HYHQoGYQ3WoUaWqtZg1x8pZP+Ad7ilUWk5ImRl57fTznNQiVdwlkS5Wgheh1yJCES570a4eujiK9OyB0ba4rKIcM',
+        signatureWithAbcSalt: 'HCm0FI1jE6wQgwwi0ZwPTkGjssxAPtRh6tWXhNd2J2IoJYj9oQMMjCEElnvQFBa/l00sIsw2YV1tKyoTABaSTGV4vlJcDF+K0g/wiAf30TRUZo72DZKDNdyffDlH0wBDkNVW+F6uqdciJqBC6zz+unNh7x+FRwYaY8xhudIPXdyP',
+        signatureWithCustomPrng: 'AGyN8xu+0yfCR1tyB9mCXcTGb2vdLnsX9ro2Qy5KV6Hw5YMVNltAt65dKR4Y8pfu6D4WUyyJRUtJ8td2ZHYzIVtWY6bG1xFt5rkjTVg4v1tzQgUQq8AHvRE2qLzwDXhazJ1e6Id2Nuxb1uInFyRC6/gLmiPga1WRDEVvFenuIA48'
+      }];
+      for(var i = 0; i < tests.length; ++i) {
+        createTests(tests[i]);
+      }
+
+      it('should ensure maximum message length for a 1024-bit key is exceeded', function() {
+        /* For PKCS#1 v1.5, the message must be padded with at least eight bytes,
+          two zero bytes and one byte telling what the block type is. This is 11
+          extra bytes are added to the message. The test uses a message of 118
+          bytes.Together with the 11 extra bytes the encryption block needs to be
+          at least 129 bytes long. This requires a key of 1025-bits. */
+        var key = PKI.publicKeyFromPem(tests[0].publicKeyPem);
+        var message = UTIL.createBuffer().fillWithByte(0, 118);
+        ASSERT.throws(function() {
+          key.encrypt(message.getBytes());
+        });
+      });
+
+      it('should ensure maximum message length for a 1025-bit key is not exceeded', function() {
+        var key = PKI.publicKeyFromPem(tests[1].publicKeyPem);
+        var message = UTIL.createBuffer().fillWithByte(0, 118);
+        ASSERT.doesNotThrow(function() {
+          key.encrypt(message.getBytes());
+        });
+      });
+
+      /**
+       * Creates RSA encryption & decryption tests.
+       *
+       * Uses different key sizes (1024, 1025, 1031, 1032). The test functions are
+       * generated from "templates" below, one for each key size to provide sensible
+       * output.
+       *
+       * Key material in was created with OpenSSL using these commands:
+       *
+       * openssl genrsa -out rsa_1024_private.pem 1024
+       * openssl rsa -in rsa_1024_private.pem -out rsa_1024_public.pem \
+       *   -outform PEM -pubout
+       * echo 'too many secrets' | openssl rsautl -encrypt \
+       *   -inkey rsa_1024_public.pem -pubin -out rsa_1024_encrypted.bin
+       *
+       * echo -n 'just testing' | openssl dgst -sha1 -binary > tosign.sha1
+       * openssl pkeyutl -sign -in tosign.sha1 -inkey rsa_1024_private.pem \
+       *   -out rsa_1024_sig.bin -pkeyopt digest:sha1
+       * openssl pkeyutl -sign -in tosign.sha1 -inkey rsa_1024_private.pem \
+       *   -out rsa_1024_sigpss.bin -pkeyopt digest:sha1 \
+       *   -pkeyopt rsa_padding_mode:pss -pkeyopt rsa_pss_saltlen:20
+       *
+       * OpenSSL commands for signature verification:
+       *
+       * openssl pkeyutl -verify -in tosign.sha1 -sigfile rsa_1024_sig.bin \
+       *   -pubin -inkey rsa_1024_public.pem -pkeyopt digest:sha1
+       * openssl pkeyutl -verify -in tosign.sha1 -sigfile rsa_1025_sigpss.bin \
+       *   -pubin -inkey rsa_1025_public.pem -pkeyopt digest:sha1 \
+       *   -pkeyopt rsa_padding_mode:pss -pkeyopt rsa_pss_saltlen:20
+       */
+      function createTests(params) {
+        var keySize = params.keySize;
+
+        it('should rsa encrypt using a ' + keySize + '-bit key', function() {
+          var message = "it need's to be about 20% cooler"; // it need's better grammar too
+
+          /* First step, do public key encryption */
+          var key = PKI.publicKeyFromPem(params.publicKeyPem);
+          var data = key.encrypt(message);
+
+          /* Second step, use private key decryption to verify successful
+            encryption. The encrypted message differs every time, since it is
+            padded with random data. Therefore just rely on the decryption
+            routine to work, which is tested seperately against an externally
+            provided encrypted message. */
+          key = PKI.privateKeyFromPem(params.privateKeyPem);
+          ASSERT.equal(key.decrypt(data), message);
+        });
+
+        it('should rsa decrypt using a ' + keySize + '-bit key', function() {
+          var data = UTIL.decode64(params.encrypted);
+          var key = PKI.privateKeyFromPem(params.privateKeyPem);
+          ASSERT.equal(key.decrypt(data), 'too many secrets\n');
+        });
+
+        it('should rsa sign using a ' + keySize + '-bit key and PKCS#1 v1.5 padding', function() {
+          var key = PKI.privateKeyFromPem(params.privateKeyPem);
+
+          var md = MD.sha1.create();
+          md.start();
+          md.update('just testing');
+
+          var signature = UTIL.decode64(params.signature);
+          ASSERT.equal(key.sign(md), signature);
+        });
+
+        it('should verify an rsa signature using a ' + keySize + '-bit key and PKCS#1 v1.5 padding', function() {
+          var signature = UTIL.decode64(params.signature);
+          var key = PKI.publicKeyFromPem(params.publicKeyPem);
+
+          var md = MD.sha1.create();
+          md.start();
+          md.update('just testing');
+
+          ASSERT.equal(key.verify(md.digest().getBytes(), signature), true);
+        });
+
+        /* Note: signatures are *not* deterministic (the point of RSASSA-PSS),
+          so they can't be compared easily -- instead they are just verified
+          using the verify() function which is tested against OpenSSL-generated
+          signatures. */
+        it('should rsa sign using a ' + keySize + '-bit key and PSS padding', function() {
+          var privateKey = PKI.privateKeyFromPem(params.privateKeyPem);
+          var publicKey = PKI.publicKeyFromPem(params.publicKeyPem);
+
+          var md = MD.sha1.create();
+          md.start();
+          md.update('just testing');
+
+          // create signature
+          var pss = PSS.create(
+            MD.sha1.create(), MGF.mgf1.create(MD.sha1.create()), 20);
+          var signature = privateKey.sign(md, pss);
+
+          // verify signature
+          md.start();
+          md.update('just testing');
+          ASSERT.equal(
+            publicKey.verify(md.digest().getBytes(), signature, pss), true);
+        });
+
+        it('should verify an rsa signature using a ' + keySize + '-bit key and PSS padding', function() {
+          var signature = UTIL.decode64(params.signaturePss);
+          var key = PKI.publicKeyFromPem(params.publicKeyPem);
+
+          var md = MD.sha1.create();
+          md.start();
+          md.update('just testing');
+
+          var pss = PSS.create(
+            MD.sha1.create(), MGF.mgf1.create(MD.sha1.create()), 20);
+          ASSERT.equal(
+            key.verify(md.digest().getBytes(), signature, pss), true);
+        });
+
+        it('should rsa sign using a ' + keySize + '-bit key and PSS padding using pss named-param API', function() {
+          var privateKey = PKI.privateKeyFromPem(params.privateKeyPem);
+          var publicKey = PKI.publicKeyFromPem(params.publicKeyPem);
+
+          var md = MD.sha1.create();
+          md.start();
+          md.update('just testing');
+
+          // create signature
+          var pss = PSS.create({
+            md: MD.sha1.create(),
+            mgf: MGF.mgf1.create(MD.sha1.create()),
+            saltLength: 20
+          });
+          var signature = privateKey.sign(md, pss);
+
+          // verify signature
+          md.start();
+          md.update('just testing');
+          ASSERT.equal(
+            publicKey.verify(md.digest().getBytes(), signature, pss), true);
+        });
+
+        it('should verify an rsa signature using a ' + keySize + '-bit key and PSS padding using pss named-param API', function() {
+          var signature = UTIL.decode64(params.signaturePss);
+          var key = PKI.publicKeyFromPem(params.publicKeyPem);
+
+          var md = MD.sha1.create();
+          md.start();
+          md.update('just testing');
+
+          var pss = PSS.create({
+            md: MD.sha1.create(),
+            mgf: MGF.mgf1.create(MD.sha1.create()),
+            saltLength: 20
+          });
+          ASSERT.equal(
+            key.verify(md.digest().getBytes(), signature, pss), true);
+        });
+
+        it('should rsa sign using a ' + keySize + '-bit key and PSS padding using salt "abc"', function() {
+          var privateKey = PKI.privateKeyFromPem(params.privateKeyPem);
+
+          var md = MD.sha1.create();
+          md.start();
+          md.update('just testing');
+
+          // create signature
+          var pss = PSS.create({
+            md: MD.sha1.create(),
+            mgf: MGF.mgf1.create(MD.sha1.create()),
+            salt: UTIL.createBuffer('abc')
+          });
+          var signature = privateKey.sign(md, pss);
+          var b64 = UTIL.encode64(signature);
+          ASSERT.equal(b64, params.signatureWithAbcSalt);
+        });
+
+        it('should verify an rsa signature using a ' + keySize + '-bit key and PSS padding using salt "abc"', function() {
+          var signature = UTIL.decode64(params.signatureWithAbcSalt);
+          var key = PKI.publicKeyFromPem(params.publicKeyPem);
+
+          var md = MD.sha1.create();
+          md.start();
+          md.update('just testing');
+
+          var pss = PSS.create({
+            md: MD.sha1.create(),
+            mgf: MGF.mgf1.create(MD.sha1.create()),
+            saltLength: 3
+          });
+          ASSERT.equal(
+            key.verify(md.digest().getBytes(), signature, pss), true);
+        });
+
+        it('should rsa sign using a ' + keySize + '-bit key and PSS padding using custom PRNG', function() {
+          var prng = RANDOM.createInstance();
+          prng.seedFileSync = function(needed) {
+            return UTIL.fillString('a', needed);
+          };
+          var privateKey = PKI.privateKeyFromPem(params.privateKeyPem);
+
+          var md = MD.sha1.create();
+          md.start();
+          md.update('just testing');
+
+          // create signature
+          var pss = PSS.create({
+            md: MD.sha1.create(),
+            mgf: MGF.mgf1.create(MD.sha1.create()),
+            saltLength: 20,
+            prng: prng
+          });
+          var signature = privateKey.sign(md, pss);
+          var b64 = UTIL.encode64(signature);
+          ASSERT.equal(b64, params.signatureWithCustomPrng);
+        });
+
+        it('should verify an rsa signature using a ' + keySize + '-bit key and PSS padding using custom PRNG', function() {
+          var prng = RANDOM.createInstance();
+          prng.seedFileSync = function(needed) {
+            return UTIL.fillString('a', needed);
+          };
+          var signature = UTIL.decode64(params.signatureWithCustomPrng);
+          var key = PKI.publicKeyFromPem(params.publicKeyPem);
+
+          var md = MD.sha1.create();
+          md.start();
+          md.update('just testing');
+
+          var pss = PSS.create({
+            md: MD.sha1.create(),
+            mgf: MGF.mgf1.create(MD.sha1.create()),
+            saltLength: 20,
+            prng: prng
+          });
+          ASSERT.equal(
+            key.verify(md.digest().getBytes(), signature, pss), true);
+        });
+      }
+    })();
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/pki',
+    'forge/rsa',
+    'forge/md',
+    'forge/mgf',
+    'forge/pss',
+    'forge/random',
+    'forge/util'
+  ], function(PKI, RSA, MD, MGF, PSS, RANDOM, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      PKI(),
+      RSA(),
+      MD(),
+      MGF(),
+      PSS(),
+      RANDOM(),
+      UTIL()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/pki')(),
+    require('../../js/rsa')(),
+    require('../../js/md')(),
+    require('../../js/mgf')(),
+    require('../../js/pss')(),
+    require('../../js/random')(),
+    require('../../js/util')());
+}
+
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/sha1.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/sha1.js
new file mode 100644
index 0000000..3ffd985
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/sha1.js
@@ -0,0 +1,75 @@
+(function() {
+
+function Tests(ASSERT, SHA1, UTIL) {
+  describe('sha1', function() {
+    it('should digest the empty string', function() {
+      var md = SHA1.create();
+      ASSERT.equal(
+        md.digest().toHex(), 'da39a3ee5e6b4b0d3255bfef95601890afd80709');
+    });
+
+    it('should digest "abc"', function() {
+      var md = SHA1.create();
+      md.update('abc');
+      ASSERT.equal(
+        md.digest().toHex(), 'a9993e364706816aba3e25717850c26c9cd0d89d');
+    });
+
+    it('should digest "The quick brown fox jumps over the lazy dog"', function() {
+      var md = SHA1.create();
+      md.update('The quick brown fox jumps over the lazy dog');
+      ASSERT.equal(
+        md.digest().toHex(), '2fd4e1c67a2d28fced849ee1bb76e7391b93eb12');
+    });
+
+    it('should digest "c\'\u00e8"', function() {
+      var md = SHA1.create();
+      md.update("c\'\u00e8", 'utf8');
+      ASSERT.equal(
+        md.digest().toHex(), '98c9a3f804daa73b68a5660d032499a447350c0d');
+    });
+
+    it('should digest "THIS IS A MESSAGE"', function() {
+      var md = SHA1.create();
+      md.start();
+      md.update('THIS IS ');
+      md.update('A MESSAGE');
+      // do twice to check continuing digest
+      ASSERT.equal(
+        md.digest().toHex(), '5f24f4d6499fd2d44df6c6e94be8b14a796c071d');
+      ASSERT.equal(
+        md.digest().toHex(), '5f24f4d6499fd2d44df6c6e94be8b14a796c071d');
+    });
+
+    it('should digest a long message', function() {
+      // Note: might be too slow on old browsers
+      var md = SHA1.create();
+      md.update(UTIL.fillString('a', 1000000));
+      ASSERT.equal(
+        md.digest().toHex(), '34aa973cd4c4daa4f61eeb2bdbad27316534016f');
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/sha1',
+    'forge/util'
+  ], function(SHA1, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      SHA1(),
+      UTIL()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/sha1')(),
+    require('../../js/util')());
+}
+
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/sha256.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/sha256.js
new file mode 100644
index 0000000..2d5eb8a
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/sha256.js
@@ -0,0 +1,81 @@
+(function() {
+
+function Tests(ASSERT, SHA256, UTIL) {
+  describe('sha256', function() {
+    it('should digest the empty string', function() {
+      var md = SHA256.create();
+      ASSERT.equal(
+        md.digest().toHex(),
+        'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855');
+    });
+
+    it('should digest "abc"', function() {
+      var md = SHA256.create();
+      md.update('abc');
+      ASSERT.equal(
+        md.digest().toHex(),
+        'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad');
+    });
+
+    it('should digest "The quick brown fox jumps over the lazy dog"', function() {
+      var md = SHA256.create();
+      md.update('The quick brown fox jumps over the lazy dog');
+      ASSERT.equal(
+        md.digest().toHex(),
+        'd7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592');
+    });
+
+    it('should digest "c\'\u00e8"', function() {
+      var md = SHA256.create();
+      md.update("c\'\u00e8", 'utf8');
+      ASSERT.equal(
+        md.digest().toHex(),
+        '1aa15c717afffd312acce2217ce1c2e5dabca53c92165999132ec9ca5decdaca');
+    });
+
+    it('should digest "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"', function() {
+      var md = SHA256.create();
+      md.start();
+      md.update('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq');
+      // do twice to check continuing digest
+      ASSERT.equal(
+        md.digest().toHex(),
+        '248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1');
+      ASSERT.equal(
+        md.digest().toHex(),
+        '248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1');
+    });
+
+    it('should digest a long message', function() {
+      // Note: might be too slow on old browsers
+      var md = SHA256.create();
+      md.update(UTIL.fillString('a', 1000000));
+      ASSERT.equal(
+        md.digest().toHex(),
+        'cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0');
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/sha256',
+    'forge/util'
+  ], function(SHA256, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      SHA256(),
+      UTIL()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/sha256')(),
+    require('../../js/util')());
+}
+
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/sha512.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/sha512.js
new file mode 100644
index 0000000..3cbc4dc
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/sha512.js
@@ -0,0 +1,174 @@
+(function() {
+
+function Tests(ASSERT, SHA512, UTIL) {
+  describe('sha512', function() {
+    it('should digest the empty string', function() {
+      var md = SHA512.create();
+      ASSERT.equal(
+        md.digest().toHex(),
+        'cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e');
+    });
+
+    it('should digest "abc"', function() {
+      var md = SHA512.create();
+      md.update('abc');
+      ASSERT.equal(
+        md.digest().toHex(),
+        'ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f');
+    });
+
+    it('should digest "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"', function() {
+      var md = SHA512.create();
+      md.update('abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu');
+      ASSERT.equal(
+        md.digest().toHex(),
+        '8e959b75dae313da8cf4f72814fc143f8f7779c6eb9f7fa17299aeadb6889018501d289e4900f7e4331b99dec4b5433ac7d329eeb6dd26545e96e55b874be909');
+    });
+
+    it('should digest "The quick brown fox jumps over the lazy dog"', function() {
+      var md = SHA512.create();
+      md.update('The quick brown fox jumps over the lazy dog');
+      ASSERT.equal(
+        md.digest().toHex(),
+        '07e547d9586f6a73f73fbac0435ed76951218fb7d0c8d788a309d785436bbb642e93a252a954f23912547d1e8a3b5ed6e1bfd7097821233fa0538f3db854fee6');
+    });
+
+    it('should digest "c\'\u00e8"', function() {
+      var md = SHA512.create();
+      md.update("c\'\u00e8", 'utf8');
+      ASSERT.equal(
+        md.digest().toHex(),
+        '9afdc0390dd91e81c63f858d1c6fcd9f949f3fc89dbdaed9e4211505bad63d8e8787797e2e9ea651285eb6954e51c4f0299837c3108cb40f1420bca1d237355c');
+    });
+
+    it('should digest "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"', function() {
+      var md = SHA512.create();
+      md.start();
+      md.update('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq');
+      // do twice to check continuing digest
+      ASSERT.equal(
+        md.digest().toHex(),
+        '204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c33596fd15c13b1b07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445');
+      ASSERT.equal(
+        md.digest().toHex(),
+        '204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c33596fd15c13b1b07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445');
+    });
+  });
+
+  SHA384 = SHA512.sha384;
+
+  describe('sha384', function() {
+    it('should digest the empty string', function() {
+      var md = SHA384.create();
+      ASSERT.equal(
+        md.digest().toHex(),
+        '38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b');
+    });
+
+    it('should digest "abc"', function() {
+      var md = SHA384.create();
+      md.update('abc');
+      ASSERT.equal(
+        md.digest().toHex(),
+        'cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7');
+    });
+
+    it('should digest "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"', function() {
+      var md = SHA384.create();
+      md.update('abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu');
+      ASSERT.equal(
+        md.digest().toHex(),
+        '09330c33f71147e83d192fc782cd1b4753111b173b3b05d22fa08086e3b0f712fcc7c71a557e2db966c3e9fa91746039');
+    });
+
+    it('should digest "The quick brown fox jumps over the lazy dog"', function() {
+      var md = SHA384.create();
+      md.update('The quick brown fox jumps over the lazy dog');
+      ASSERT.equal(
+        md.digest().toHex(),
+        'ca737f1014a48f4c0b6dd43cb177b0afd9e5169367544c494011e3317dbf9a509cb1e5dc1e85a941bbee3d7f2afbc9b1');
+    });
+
+    it('should digest "c\'\u00e8"', function() {
+      var md = SHA384.create();
+      md.update("c\'\u00e8", 'utf8');
+      ASSERT.equal(
+        md.digest().toHex(),
+        '382ec8a92d50abf57f7d0f934ff3969d6d354d30c96f1616678a920677867aba49521d2d535c0f285a3c2961c2034ea3');
+    });
+
+    it('should digest "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"', function() {
+      var md = SHA384.create();
+      md.start();
+      md.update('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq');
+      // do twice to check continuing digest
+      ASSERT.equal(
+        md.digest().toHex(),
+        '3391fdddfc8dc7393707a65b1b4709397cf8b1d162af05abfe8f450de5f36bc6b0455a8520bc4e6f5fe95b1fe3c8452b');
+      ASSERT.equal(
+        md.digest().toHex(),
+        '3391fdddfc8dc7393707a65b1b4709397cf8b1d162af05abfe8f450de5f36bc6b0455a8520bc4e6f5fe95b1fe3c8452b');
+    });
+  });
+
+  SHA256 = SHA512.sha256;
+
+  describe('sha512/256', function() {
+    it('should digest the empty string', function() {
+      var md = SHA256.create();
+      ASSERT.equal(
+        md.digest().toHex(),
+        'c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a');
+    });
+
+    it('should digest "The quick brown fox jumps over the lazy dog"', function() {
+      var md = SHA256.create();
+      md.update('The quick brown fox jumps over the lazy dog');
+      ASSERT.equal(
+        md.digest().toHex(),
+        'dd9d67b371519c339ed8dbd25af90e976a1eeefd4ad3d889005e532fc5bef04d');
+    });
+  });
+
+  SHA224 = SHA512.sha224;
+
+  describe('sha512/224', function() {
+    it('should digest the empty string', function() {
+      var md = SHA224.create();
+      ASSERT.equal(
+        md.digest().toHex(),
+        '6ed0dd02806fa89e25de060c19d3ac86cabb87d6a0ddd05c333b84f4');
+    });
+
+    it('should digest "The quick brown fox jumps over the lazy dog"', function() {
+      var md = SHA224.create();
+      md.update('The quick brown fox jumps over the lazy dog');
+      ASSERT.equal(
+        md.digest().toHex(),
+        '944cd2847fb54558d4775db0485a50003111c8e5daa63fe722c6aa37');
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/sha512',
+    'forge/util'
+  ], function(SHA512, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      SHA512(),
+      UTIL()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/sha512')(),
+    require('../../js/util')());
+}
+
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/ssh.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/ssh.js
new file mode 100644
index 0000000..c90eb26
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/ssh.js
@@ -0,0 +1,193 @@
+(function() {
+
+function Tests(ASSERT, forge) {
+
+  // Original RSA key generated by openssh
+  var keystr =
+    '-----BEGIN RSA PRIVATE KEY-----\r\n' +
+    'MIIEogIBAAKCAQEA301O+LBnd6ngw9i0Pb5iTwlQ1s37ay3DNpisjh+jPDDvaIZq\r\n' +
+    'PC+44YV7RaR1ib2wEoQfg4DwqVw5wlA2x97Txngb+sJe0yUfmM9vMTxhXWuURVR6\r\n' +
+    '6DGd8t2eOBN8qDoYlpvdVf8Go5Btrb6NCexUbZtFS1AmumK2zQKxIizgVLK8dx4G\r\n' +
+    'NYbqxibUxgYHbwRpu8ROrogVrZbMW1cOb4JE+DzG1FvfHSdzkxb9e5ARcsuLxCdP\r\n' +
+    'ItUMVY8jjgER6b5lK+Nzr57hFdr08RjWGBldNGrCFqDdm+1WkGAHTRrg/i/MD8BU\r\n' +
+    '8NCFUBpQTSrhALkGqBdGZPN/PrXonXjhcd11awIDAQABAoIBAHGMESUaJnLN2jIc\r\n' +
+    'RoLDBaBk/0tLEJaOfZ6Mgen/InUf+Q0wlGKobZ2Xz3g5SV9SKm8v6gpnjXjBIcmy\r\n' +
+    'GjkGEK/yMWAQaEF7thZxHHxv1J65bnrWm2zolgWCNcsT9aZhbFFhTmpFNO4FKhBY\r\n' +
+    'PcWW+9OESfci+Z57RbL3tHTJVwUZrthtagpFEgVceq18GLSS9N4TWJ8HX1CDxf9R\r\n' +
+    '4gzEy3+8oC0QONx+bgsWUmzNk9QNNW6yPV5NYZ3SwNLVgw3+6m8WDaqo5FWK7y2f\r\n' +
+    'pa63DUzXlg5uSYnk4OFVxI6TLpmNQcjigpXnsd1PwcynW0AM83jzIYu0F31ppU61\r\n' +
+    '8flrqfECgYEA8j/joUve1F4lTt2U34RF+59giZ1r0ME4LafNEgN/ZLMB8ghToSqr\r\n' +
+    'qzNnJnSppkAKTa2NVZGgJkvqn+qysM1HNm/3ksdUcE5yijgc3E17PNdBJwwNLZfP\r\n' +
+    'p9jB/ZPaNMduGqltYamOVkbg/qS7O4rcUHstrGGGtJ9fveH+cu87emMCgYEA6/oX\r\n' +
+    '6A2fW88hw4hZwV2pCk6gumi89vXbKbhnD6j907TW583xIqXYsQBb7x/KGyPf65+k\r\n' +
+    'Sou9MRyLhRu7qcc8INpSnFsra8ZQosP+ao8tUTq7p7N0H27qG5liTeAAksvk/xnq\r\n' +
+    '2VdL1YDRpo4tmRD7TAj8uc1sgXqdsBCPrqq4Q1kCgYAHcNjwEmGEymOA+aNh/jEc\r\n' +
+    'Gngfofs2zUiJdncBD6RxFmJ/6auP7ryZJJoNf1XaqmrmmecWcsOliX1qbg4RCi0e\r\n' +
+    'ye+jzYWVcYNpJXIVfjfD1aTFq0QYW2pgcHL88/am2l1SalPWxRt/IOw2Rh8OJCTC\r\n' +
+    'QBZWDiTSFXceYPus0hZUmwKBgCc2FYbfzJ0q3Adrvs5cy9wEmLyg7tVyoQpbs/Rs\r\n' +
+    'NlFZeWRnWixRtqIi1yPy+lhsK6cxjdE9SyDAB4cExrg9fQZQgO2uUJbGC1wgiUQX\r\n' +
+    'qoYW5lvFfARFH+2aHTWnhTDfZJvnKJkY4mcF0tCES5tlsPw/eg89zUvunglFlzqE\r\n' +
+    '771xAoGAdYRG1PIkAzjuh785rc35885dsaXChZx1+7rfZ+AclyisRsmES9UfqL6M\r\n' +
+    '+SuluaBSWJQUBS7iaHazUeXmy5t+HBjSSLuAOHoZUckwDWD3EM7GHjFlWJOCArI3\r\n' +
+    'hOYlsXSyl07rApzg/t+HxXcNpLZGJTgRwrRGF2OipUL0VPlTdRc=\r\n' +
+    '-----END RSA PRIVATE KEY-----\r\n';
+
+  var key = forge.pki.privateKeyFromPem(keystr);
+
+  describe('ssh', function() {
+    it('should convert keys to openssh public keys', function() {
+      var expect =
+        'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDfTU74sGd3qeDD2LQ9vmJPCVD' +
+        'WzftrLcM2mKyOH6M8MO9ohmo8L7jhhXtFpHWJvbAShB+DgPCpXDnCUDbH3tPGeB' +
+        'v6wl7TJR+Yz28xPGFda5RFVHroMZ3y3Z44E3yoOhiWm91V/wajkG2tvo0J7FRtm' +
+        '0VLUCa6YrbNArEiLOBUsrx3HgY1hurGJtTGBgdvBGm7xE6uiBWtlsxbVw5vgkT4' +
+        'PMbUW98dJ3OTFv17kBFyy4vEJ08i1QxVjyOOARHpvmUr43OvnuEV2vTxGNYYGV0' +
+        '0asIWoN2b7VaQYAdNGuD+L8wPwFTw0IVQGlBNKuEAuQaoF0Zk838+teideOFx3X' +
+        'Vr A comment';
+
+      ASSERT.equal(forge.ssh.publicKeyToOpenSSH(key, 'A comment'), expect);
+    });
+
+    it('should convert keys to putty unencrypted keys', function() {
+      var expect =
+        'PuTTY-User-Key-File-2: ssh-rsa\r\n' +
+        'Encryption: none\r\n' +
+        'Comment: imported-openssh-key\r\n' +
+        'Public-Lines: 6\r\n' +
+        'AAAAB3NzaC1yc2EAAAADAQABAAABAQDfTU74sGd3qeDD2LQ9vmJPCVDWzftrLcM2\r\n' +
+        'mKyOH6M8MO9ohmo8L7jhhXtFpHWJvbAShB+DgPCpXDnCUDbH3tPGeBv6wl7TJR+Y\r\n' +
+        'z28xPGFda5RFVHroMZ3y3Z44E3yoOhiWm91V/wajkG2tvo0J7FRtm0VLUCa6YrbN\r\n' +
+        'ArEiLOBUsrx3HgY1hurGJtTGBgdvBGm7xE6uiBWtlsxbVw5vgkT4PMbUW98dJ3OT\r\n' +
+        'Fv17kBFyy4vEJ08i1QxVjyOOARHpvmUr43OvnuEV2vTxGNYYGV00asIWoN2b7VaQ\r\n' +
+        'YAdNGuD+L8wPwFTw0IVQGlBNKuEAuQaoF0Zk838+teideOFx3XVr\r\n' +
+        'Private-Lines: 14\r\n' +
+        'AAABAHGMESUaJnLN2jIcRoLDBaBk/0tLEJaOfZ6Mgen/InUf+Q0wlGKobZ2Xz3g5\r\n' +
+        'SV9SKm8v6gpnjXjBIcmyGjkGEK/yMWAQaEF7thZxHHxv1J65bnrWm2zolgWCNcsT\r\n' +
+        '9aZhbFFhTmpFNO4FKhBYPcWW+9OESfci+Z57RbL3tHTJVwUZrthtagpFEgVceq18\r\n' +
+        'GLSS9N4TWJ8HX1CDxf9R4gzEy3+8oC0QONx+bgsWUmzNk9QNNW6yPV5NYZ3SwNLV\r\n' +
+        'gw3+6m8WDaqo5FWK7y2fpa63DUzXlg5uSYnk4OFVxI6TLpmNQcjigpXnsd1Pwcyn\r\n' +
+        'W0AM83jzIYu0F31ppU618flrqfEAAACBAPI/46FL3tReJU7dlN+ERfufYImda9DB\r\n' +
+        'OC2nzRIDf2SzAfIIU6Eqq6szZyZ0qaZACk2tjVWRoCZL6p/qsrDNRzZv95LHVHBO\r\n' +
+        'coo4HNxNezzXQScMDS2Xz6fYwf2T2jTHbhqpbWGpjlZG4P6kuzuK3FB7LaxhhrSf\r\n' +
+        'X73h/nLvO3pjAAAAgQDr+hfoDZ9bzyHDiFnBXakKTqC6aLz29dspuGcPqP3TtNbn\r\n' +
+        'zfEipdixAFvvH8obI9/rn6RKi70xHIuFG7upxzwg2lKcWytrxlCiw/5qjy1ROrun\r\n' +
+        's3QfbuobmWJN4ACSy+T/GerZV0vVgNGmji2ZEPtMCPy5zWyBep2wEI+uqrhDWQAA\r\n' +
+        'AIB1hEbU8iQDOO6Hvzmtzfnzzl2xpcKFnHX7ut9n4ByXKKxGyYRL1R+ovoz5K6W5\r\n' +
+        'oFJYlBQFLuJodrNR5ebLm34cGNJIu4A4ehlRyTANYPcQzsYeMWVYk4ICsjeE5iWx\r\n' +
+        'dLKXTusCnOD+34fFdw2ktkYlOBHCtEYXY6KlQvRU+VN1Fw==\r\n' +
+        'Private-MAC: 87fa1011848453317d8e41b00c927e9d17dc334e\r\n';
+
+      ASSERT.equal(forge.ssh.privateKeyToPutty(key, '', 'imported-openssh-key'), expect);
+    });
+
+    it('should convert keys to putty encrypted keys', function() {
+      var expect =
+        'PuTTY-User-Key-File-2: ssh-rsa\r\n' +
+        'Encryption: aes256-cbc\r\n' +
+        'Comment: imported-openssh-key\r\n' +
+        'Public-Lines: 6\r\n' +
+        'AAAAB3NzaC1yc2EAAAADAQABAAABAQDfTU74sGd3qeDD2LQ9vmJPCVDWzftrLcM2\r\n' +
+        'mKyOH6M8MO9ohmo8L7jhhXtFpHWJvbAShB+DgPCpXDnCUDbH3tPGeBv6wl7TJR+Y\r\n' +
+        'z28xPGFda5RFVHroMZ3y3Z44E3yoOhiWm91V/wajkG2tvo0J7FRtm0VLUCa6YrbN\r\n' +
+        'ArEiLOBUsrx3HgY1hurGJtTGBgdvBGm7xE6uiBWtlsxbVw5vgkT4PMbUW98dJ3OT\r\n' +
+        'Fv17kBFyy4vEJ08i1QxVjyOOARHpvmUr43OvnuEV2vTxGNYYGV00asIWoN2b7VaQ\r\n' +
+        'YAdNGuD+L8wPwFTw0IVQGlBNKuEAuQaoF0Zk838+teideOFx3XVr\r\n' +
+        'Private-Lines: 14\r\n' +
+        'EiVwpacmA7mhmGBTPXeIZZPkeRDtb4LOWzI+68cA5oM7UJTpBxh9zsnpAdWg2knP\r\n' +
+        'snA5gpTSq0CJV9HWb8yAY3R5izflQ493fCbJzuPFTW6RJ2y/D5dUmDWccfMczPNT\r\n' +
+        'GEDOfslCPe+Yz1h0y0y5NI5WwJowxv3nL9FTlQ9KDVrdoSizkVbTh4CJTrvqIc7c\r\n' +
+        'sI/HU25OHS8kcIZPiPL8m40ot254rkbPCWrskm9H4n+EC+BwJNtEic9ZQfGPWOVl\r\n' +
+        't8JxY35mHqGIlfhyVkdts/Rx7kwOHY+GPXDVRnHQZQOkFtVqCFGx8mL83CspIEq0\r\n' +
+        'V8LaHuvxiadA4OEeR0azuDhfVJXvrUpHd4CPjAzJu4doHm98GJAyrz3mtCyxIguH\r\n' +
+        'k8zKVJzbNawy5T43l5x9cR6VKzcl6d4V14vphiovxc8DG/J7RHBd4d1y6wxo5ZMY\r\n' +
+        'qqQ0E6VHtq6auBZjnGzx0P/1lGjpZdxUf4OVTaZ+stCxX5wAH9exF+gdZAlk20Gp\r\n' +
+        'Atg60boQuolHBiH66dQynyHp+6yuLPLKXy74EO+AEB3HvNK7aIQV8rCD7I7HEa12\r\n' +
+        'xxqIH4l0FWQKHXNIrK45uo6Hdg1siYp9zU4FFgDcNGOZJsT6+etPp1sgAeBuBR4F\r\n' +
+        'pnuX1KZzRTbG1kcRrTOjsMh0bKfZAn0+uwyuPBtwEnLziGoCXU+9+XO85zcPF2j1\r\n' +
+        'Ip5AWAOBI82SKMLu51Dpdz1rwSZOPFWnHxRxfnUiQa9Kc7qBGrMxy1UNECAwMGzp\r\n' +
+        'ljKesqZvoANo0voiodALXGs7kSpmXjcbHnUUt0SI/QHyajXabIiLHGf6lfvStYLP\r\n' +
+        'L5bLfswsMqG/2lonYWrPl9YC0WzvfRpbHgpI9G1eLjFFRlaqJ3EpZf5Dy26Z0eh0\r\n' +
+        'Private-MAC: 23aa5b6e2a411107c59e1e6c3bca06247e3c9627\r\n';
+
+      ASSERT.equal(forge.ssh.privateKeyToPutty(key, 'passphrase', 'imported-openssh-key'), expect);
+    });
+
+    it('should convert keys to openssh encrypted private keys', function() {
+      var expect =
+        '-----BEGIN RSA PRIVATE KEY-----\n' +
+        'Proc-Type: 4,ENCRYPTED\n' +
+        'DEK-Info: AES-128-CBC,2616162F269429AA628E42C3BD5A0027\n' +
+        '\n' +
+        'p8+mGWeQxZrRg6OeeFqgEX8sXGGUqWJuK4XhtgRpxAQaSg8bK6m/ahArEonjzgrO\n' +
+        'XMLow7N0aXqGJzL+n4c4EzL7e4SquzeYZLq0UCs8vbWE5GdTT6BxisWIJqzOaQW3\n' +
+        'd3OqS2lM5o47cuADMIMp015b0dJn5nwJall20GSI1XnpTUHIJ1oFv7fW/s5g39VD\n' +
+        'DSVmPzJEMhcTa8BskHrKITV6l+TuivGqrHH0LCYCfQ3IBLiRZrPINQLLkaHR6kis\n' +
+        '4qvFEMhQGAz0GrifwEob9+FPzDAHHnYTS0kG1jhZ3p92vaUi8sPxyv5ndRXOSZZg\n' +
+        'vh6Cdrk62myG/rHbsBRrrpa+Ka+BX4ofedwP3SBHPwqBpksYhEF7MxsWKhmHY+d0\n' +
+        'YINHrj0w+yfw4H3n1+0w4wajlHVUncp7RP8KKMtG3vvvfF1loWpLbyF0s6fgq7p4\n' +
+        '7kt1LcnRKB3U2IZYfMHuv94+5q0BKfGF6NmRpmgdJojyS2IXZyFaJRqrCa0mKcP9\n' +
+        'PtDZFPTEy0VeNTL8KqjweEmjK3/JtZPVgXXkPWlFMw3Hc/dtP/l6VssGLk8P/DHD\n' +
+        'yknagPsfa70iydTvDO+nrtS57DkoUqhMsU3BhCjMzZg+/bo1JgmxQUWT//PoQLSB\n' +
+        'Z7/F59AfHzZIkWgqfvGRzX3y+G1M1l11dX658iWmN0kZ5MyHU0qwU9hVI6P/fcfk\n' +
+        '6MQKc/MzPnkjw/knREtYMtHrVsKrDVDizfguGFKFC8FVhhrDOFZUnza0nh6nt1HZ\n' +
+        'Xk156MhATenWdHBW4Rn3ec1aMOD16k2SRIHd+nyJzx51H3PUdTtXBWqFNGggzoJG\n' +
+        '99ax3jD6pTLQY3BG146QKQ0csItMTIdwZPAidkzv8VVXC7HaqXk1K1pgfJT6mD4P\n' +
+        'LaNbuA9r7mNiNoPzwzk0h3BomBTMXZpAyL9Jlre9jTu6lpyN/TkOzHhs/I1/lvKQ\n' +
+        'Uki7BXv65Jq6RqkTbNc5plxBYggdzLGurr0ZIBDsoN6uXkzaM+fCMlJU8+MgAcBb\n' +
+        'x88bj8h3t4akPd/WaSsWKeOzB3Uaw3ztYCpwSVv1F+N0u6C6zGo+9VFAQZh1uKvC\n' +
+        'G9U5hvAG7WEoQ801/fvKj93lqLDhOarPJC8CcfMLwoIqj7zah7TtBYq8VsuF7Fsx\n' +
+        'dEWNFiXhtFqUhgBMt1tcOXGiXA79NucSVzUBNzrRZKlLDYm2TKKyOqEbG9vF/YxL\n' +
+        'ZG3TwmcFj0sko2K61ya7FGpKmfPQCJJoi0tIfnW4eIVcyeRpXYi5B2xqvSXcTdUx\n' +
+        '5y5Vpuu1CzrMZr50b3sSOFjcOXE5211RS8SHpOMWY+JDDB4vF4Dv94fqEIgnFtrR\n' +
+        'oQgk3DueWb1x09NcJtEZsW6lT3Jw19ursb++XSejFZ9Xu5ED8fbewgGo2w/N5j1H\n' +
+        'vQEnFkGcL1jLlLqp9PlvPIE4a///wy1y0XbnKMJs+dKxiesKVx1zZ1WDcK2Qgv4r\n' +
+        'G+RsZzHZuCjUyty1+SMVOYM6+3zW6bjXN58xI3XeSxgE/JaJKjLWBZWx5+eU7b6a\n' +
+        '04mJDMhnpdLHG97m9p90L1yuudiJfq6ngha41xxv9xLmNatfrtStCrq/DR0KHm0K\n' +
+        '-----END RSA PRIVATE KEY-----\n';
+
+      // Unable to test -- uses a random IV that I can't control
+      //ASSERT.equal(forge.ssh.rsaPrivateKeyAsOpenSSH(key, 'passphrase'), expect);
+    });
+
+    it('should convert keys to openssh unencrypted private keys', function() {
+      var expect = keystr;
+      ASSERT.equal(forge.ssh.privateKeyToOpenSSH(key, ''), expect);
+    });
+
+    it('should get an MD5 SSH fingerprint', function() {
+      var fp = forge.ssh.getPublicKeyFingerprint(key);
+      ASSERT.equal(fp.toHex(), '46549abeb89422a0955d4041ae7322ec');
+    });
+
+    it('should get a hex MD5 SSH fingerprint', function() {
+      var fp = forge.ssh.getPublicKeyFingerprint(key, {encoding: 'hex'});
+      ASSERT.equal(fp, '46549abeb89422a0955d4041ae7322ec');
+    });
+
+    it('should get a hex, colon-delimited MD5 SSH fingerprint', function() {
+      var fp = forge.ssh.getPublicKeyFingerprint(
+        key, {encoding: 'hex', delimiter: ':'});
+      ASSERT.equal(fp, '46:54:9a:be:b8:94:22:a0:95:5d:40:41:ae:73:22:ec');
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/forge'
+  ], function(forge) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      forge
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/forge'));
+}
+
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/tls.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/tls.js
new file mode 100644
index 0000000..d9ce944
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/tls.js
@@ -0,0 +1,191 @@
+(function() {
+
+function Tests(ASSERT, forge) {
+  describe('tls', function() {
+    it('should test TLS 1.0 PRF', function() {
+      // Note: This test vector is originally from:
+      // http://www.imc.org/ietf-tls/mail-archive/msg01589.html
+      // But that link is now dead.
+      var secret = forge.util.createBuffer().fillWithByte(0xAB, 48).getBytes();
+      var seed = forge.util.createBuffer().fillWithByte(0xCD, 64).getBytes();
+      var bytes = forge.tls.prf_tls1(secret, 'PRF Testvector',  seed, 104);
+      var expect =
+        'd3d4d1e349b5d515044666d51de32bab258cb521' +
+        'b6b053463e354832fd976754443bcf9a296519bc' +
+        '289abcbc1187e4ebd31e602353776c408aafb74c' +
+        'bc85eff69255f9788faa184cbb957a9819d84a5d' +
+        '7eb006eb459d3ae8de9810454b8b2d8f1afbc655' +
+        'a8c9a013';
+      ASSERT.equal(bytes.toHex(), expect);
+    });
+
+    it('should establish a TLS connection and transfer data', function(done) {
+      var end = {};
+      var data = {};
+
+      createCertificate('server', data);
+      createCertificate('client', data);
+      data.client.connection = {};
+      data.server.connection = {};
+
+      end.client = forge.tls.createConnection({
+        server: false,
+        caStore: [data.server.cert],
+        sessionCache: {},
+        cipherSuites: [
+          forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+          forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+        virtualHost: 'server',
+        verify: function(c, verified, depth, certs) {
+          data.client.connection.commonName =
+            certs[0].subject.getField('CN').value;
+          data.client.connection.certVerified = verified;
+          return true;
+        },
+        connected: function(c) {
+          c.prepare('Hello Server');
+        },
+        getCertificate: function(c, hint) {
+          return data.client.cert;
+        },
+        getPrivateKey: function(c, cert) {
+          return data.client.privateKey;
+        },
+        tlsDataReady: function(c) {
+          end.server.process(c.tlsData.getBytes());
+        },
+        dataReady: function(c) {
+          data.client.connection.data = c.data.getBytes();
+          c.close();
+        },
+        closed: function(c) {
+          ASSERT.equal(data.client.connection.commonName, 'server');
+          ASSERT.equal(data.client.connection.certVerified, true);
+          ASSERT.equal(data.client.connection.data, 'Hello Client');
+          done();
+        },
+        error: function(c, error) {
+          ASSERT.equal(error.message, undefined);
+        }
+      });
+
+      end.server = forge.tls.createConnection({
+        server: true,
+        caStore: [data.client.cert],
+        sessionCache: {},
+        cipherSuites: [
+          forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+          forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+        connected: function(c) {
+        },
+        verifyClient: true,
+        verify: function(c, verified, depth, certs) {
+          data.server.connection.commonName =
+            certs[0].subject.getField('CN').value;
+          data.server.connection.certVerified = verified;
+          return true;
+        },
+        getCertificate: function(c, hint) {
+          data.server.connection.certHint = hint[0];
+          return data.server.cert;
+        },
+        getPrivateKey: function(c, cert) {
+          return data.server.privateKey;
+        },
+        tlsDataReady: function(c) {
+          end.client.process(c.tlsData.getBytes());
+        },
+        dataReady: function(c) {
+          data.server.connection.data = c.data.getBytes();
+          c.prepare('Hello Client');
+          c.close();
+        },
+        closed: function(c) {
+          ASSERT.equal(data.server.connection.certHint, 'server');
+          ASSERT.equal(data.server.connection.commonName, 'client');
+          ASSERT.equal(data.server.connection.certVerified, true);
+          ASSERT.equal(data.server.connection.data, 'Hello Server');
+        },
+        error: function(c, error) {
+          ASSERT.equal(error.message, undefined);
+        }
+      });
+
+      end.client.handshake();
+
+      function createCertificate(cn, data) {
+        var keys = forge.pki.rsa.generateKeyPair(512);
+        var cert = forge.pki.createCertificate();
+        cert.publicKey = keys.publicKey;
+        cert.serialNumber = '01';
+        cert.validity.notBefore = new Date();
+        cert.validity.notAfter = new Date();
+        cert.validity.notAfter.setFullYear(
+          cert.validity.notBefore.getFullYear() + 1);
+        var attrs = [{
+          name: 'commonName',
+          value: cn
+        }, {
+          name: 'countryName',
+          value: 'US'
+        }, {
+          shortName: 'ST',
+          value: 'Virginia'
+        }, {
+          name: 'localityName',
+          value: 'Blacksburg'
+        }, {
+          name: 'organizationName',
+          value: 'Test'
+        }, {
+          shortName: 'OU',
+          value: 'Test'
+        }];
+        cert.setSubject(attrs);
+        cert.setIssuer(attrs);
+        cert.setExtensions([{
+          name: 'basicConstraints',
+          cA: true
+        }, {
+          name: 'keyUsage',
+          keyCertSign: true,
+          digitalSignature: true,
+          nonRepudiation: true,
+          keyEncipherment: true,
+          dataEncipherment: true
+        }, {
+          name: 'subjectAltName',
+          altNames: [{
+            type: 6, // URI
+            value: 'https://myuri.com/webid#me'
+          }]
+        }]);
+        cert.sign(keys.privateKey);
+        data[cn] = {
+          cert: forge.pki.certificateToPem(cert),
+          privateKey: forge.pki.privateKeyToPem(keys.privateKey)
+        };
+      }
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/forge'
+  ], function(forge) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      forge
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/forge'));
+}
+
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/util.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/util.js
new file mode 100644
index 0000000..57104a1
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/util.js
@@ -0,0 +1,406 @@
+(function() {
+
+function Tests(ASSERT, UTIL) {
+  // custom assertion to test array-like objects
+  function assertArrayEqual(actual, expected) {
+    ASSERT.equal(actual.length, expected.length);
+    for (var idx = 0; idx < expected.length; idx++) {
+      ASSERT.equal(actual[idx], expected[idx]);
+    }
+  }
+
+  describe('util', function() {
+    it('should put bytes into a buffer', function() {
+      var b = UTIL.createBuffer();
+      b.putByte(1);
+      b.putByte(2);
+      b.putByte(3);
+      b.putByte(4);
+      b.putInt32(4);
+      b.putByte(1);
+      b.putByte(2);
+      b.putByte(3);
+      b.putInt32(4294967295);
+      var hex = b.toHex();
+      ASSERT.equal(hex, '0102030400000004010203ffffffff');
+
+      var bytes = [];
+      while(b.length() > 0) {
+        bytes.push(b.getByte());
+      }
+      ASSERT.deepEqual(
+        bytes, [1, 2, 3, 4, 0, 0, 0, 4, 1, 2, 3, 255, 255, 255, 255]);
+    });
+
+    it('should put bytes from an Uint8Array into a buffer', function() {
+      if(typeof Uint8Array === 'undefined') {
+        return;
+      }
+      var data = [1, 2, 3, 4, 0, 0, 0, 4, 1, 2, 3, 255, 255, 255, 255];
+      var ab = new Uint8Array(data);
+      var b = UTIL.createBuffer(ab);
+      var hex = b.toHex();
+      ASSERT.equal(hex, '0102030400000004010203ffffffff');
+
+      var bytes = [];
+      while(b.length() > 0) {
+        bytes.push(b.getByte());
+      }
+      ASSERT.deepEqual(bytes, data);
+    });
+
+    it('should convert bytes from hex', function() {
+      var hex = '0102030400000004010203ffffffff';
+      var b = UTIL.createBuffer();
+      b.putBytes(UTIL.hexToBytes(hex));
+      ASSERT.equal(b.toHex(), hex);
+    });
+
+    it('should put 0 into a buffer using two\'s complement', function() {
+      var b = UTIL.createBuffer();
+      b.putSignedInt(0, 8);
+      ASSERT.equal(b.toHex(), '00');
+    });
+
+    it('should put 0 into a buffer using two\'s complement w/2 bytes', function() {
+      var b = UTIL.createBuffer();
+      b.putSignedInt(0, 16);
+      ASSERT.equal(b.toHex(), '0000');
+    });
+
+    it('should put 127 into a buffer using two\'s complement', function() {
+      var b = UTIL.createBuffer();
+      b.putSignedInt(127, 8);
+      ASSERT.equal(b.toHex(), '7f');
+    });
+
+    it('should put 127 into a buffer using two\'s complement w/2 bytes', function() {
+      var b = UTIL.createBuffer();
+      b.putSignedInt(127, 16);
+      ASSERT.equal(b.toHex(), '007f');
+    });
+
+    it('should put 128 into a buffer using two\'s complement', function() {
+      var b = UTIL.createBuffer();
+      b.putSignedInt(128, 16);
+      ASSERT.equal(b.toHex(), '0080');
+    });
+
+    it('should put 256 into a buffer using two\'s complement', function() {
+      var b = UTIL.createBuffer();
+      b.putSignedInt(256, 16);
+      ASSERT.equal(b.toHex(), '0100');
+    });
+
+    it('should put -128 into a buffer using two\'s complement', function() {
+      var b = UTIL.createBuffer();
+      b.putSignedInt(-128, 8);
+      ASSERT.equal(b.toHex(), '80');
+    });
+
+    it('should put -129 into a buffer using two\'s complement', function() {
+      var b = UTIL.createBuffer();
+      b.putSignedInt(-129, 16);
+      ASSERT.equal(b.toHex(), 'ff7f');
+    });
+
+    it('should get 0 from a buffer using two\'s complement', function() {
+      var x = 0;
+      var n = 8;
+      var b = UTIL.createBuffer();
+      b.putSignedInt(x, n);
+      ASSERT.equal(b.getSignedInt(x, n), x);
+    });
+
+    it('should get 127 from a buffer using two\'s complement', function() {
+      var x = 127;
+      var n = 8;
+      var b = UTIL.createBuffer();
+      b.putSignedInt(x, n);
+      ASSERT.equal(b.getSignedInt(n), x);
+    });
+
+    it('should get 128 from a buffer using two\'s complement', function() {
+      var x = 128;
+      var n = 16;
+      var b = UTIL.createBuffer();
+      b.putSignedInt(x, n);
+      ASSERT.equal(b.getSignedInt(n), x);
+    });
+
+    it('should get 256 from a buffer using two\'s complement', function() {
+      var x = 256;
+      var n = 16;
+      var b = UTIL.createBuffer();
+      b.putSignedInt(x, n);
+      ASSERT.equal(b.getSignedInt(n), x);
+    });
+
+    it('should get -128 from a buffer using two\'s complement', function() {
+      var x = -128;
+      var n = 8;
+      var b = UTIL.createBuffer();
+      b.putSignedInt(x, n);
+      ASSERT.equal(b.getSignedInt(n), x);
+    });
+
+    it('should get -129 from a buffer using two\'s complement', function() {
+      var x = -129;
+      var n = 16;
+      var b = UTIL.createBuffer();
+      b.putSignedInt(x, n);
+      ASSERT.equal(b.getSignedInt(n), x);
+    });
+
+    it('should base64 encode some bytes', function() {
+      var s1 = '00010203050607080A0B0C0D0F1011121415161719';
+      var s2 = 'MDAwMTAyMDMwNTA2MDcwODBBMEIwQzBEMEYxMDExMTIxNDE1MTYxNzE5';
+      ASSERT.equal(UTIL.encode64(s1), s2);
+    });
+
+    it('should base64 decode some bytes', function() {
+      var s1 = '00010203050607080A0B0C0D0F1011121415161719';
+      var s2 = 'MDAwMTAyMDMwNTA2MDcwODBBMEIwQzBEMEYxMDExMTIxNDE1MTYxNzE5';
+      ASSERT.equal(UTIL.decode64(s2), s1);
+    });
+    
+    it('should base64 encode some bytes using util.binary.base64', function() {
+      var s1 = new Uint8Array([
+        0x30, 0x30, 0x30, 0x31, 0x30, 0x32, 0x30, 0x33, 0x30,
+        0x35, 0x30, 0x36, 0x30, 0x37, 0x30, 0x38, 0x30, 0x41,
+        0x30, 0x42, 0x30, 0x43, 0x30, 0x44, 0x30, 0x46, 0x31,
+        0x30, 0x31, 0x31, 0x31, 0x32, 0x31, 0x34, 0x31, 0x35,
+        0x31, 0x36, 0x31, 0x37, 0x31, 0x39]);
+      var s2 = 'MDAwMTAyMDMwNTA2MDcwODBBMEIwQzBEMEYxMDExMTIxNDE1MTYxNzE5';
+      ASSERT.equal(UTIL.binary.base64.encode(s1), s2);
+    });
+
+    it('should base64 encode some odd-length bytes using util.binary.base64', function() {
+      var s1 = new Uint8Array([
+        0x30, 0x30, 0x30, 0x31, 0x30, 0x32, 0x30, 0x33, 0x30,
+        0x35, 0x30, 0x36, 0x30, 0x37, 0x30, 0x38, 0x30, 0x41,
+        0x30, 0x42, 0x30, 0x43, 0x30, 0x44, 0x30, 0x46, 0x31,
+        0x30, 0x31, 0x31, 0x31, 0x32, 0x31, 0x34, 0x31, 0x35,
+        0x31, 0x36, 0x31, 0x37, 0x31, 0x39, 0x31, 0x41, 0x31,
+        0x42]);
+      var s2 = 'MDAwMTAyMDMwNTA2MDcwODBBMEIwQzBEMEYxMDExMTIxNDE1MTYxNzE5MUExQg==';
+      ASSERT.equal(UTIL.binary.base64.encode(s1), s2);
+    });
+
+    it('should base64 decode some bytes using util.binary.base64', function() {
+      var s1 = new Uint8Array([
+        0x30, 0x30, 0x30, 0x31, 0x30, 0x32, 0x30, 0x33, 0x30,
+        0x35, 0x30, 0x36, 0x30, 0x37, 0x30, 0x38, 0x30, 0x41,
+        0x30, 0x42, 0x30, 0x43, 0x30, 0x44, 0x30, 0x46, 0x31,
+        0x30, 0x31, 0x31, 0x31, 0x32, 0x31, 0x34, 0x31, 0x35,
+        0x31, 0x36, 0x31, 0x37, 0x31, 0x39]);
+      var s2 = 'MDAwMTAyMDMwNTA2MDcwODBBMEIwQzBEMEYxMDExMTIxNDE1MTYxNzE5';
+      ASSERT.deepEqual(UTIL.binary.base64.decode(s2), s1);
+    });
+      
+    it('should base64 decode some odd-length bytes using util.binary.base64', function() {
+      var s1 = new Uint8Array([
+        0x30, 0x30, 0x30, 0x31, 0x30, 0x32, 0x30, 0x33, 0x30,
+        0x35, 0x30, 0x36, 0x30, 0x37, 0x30, 0x38, 0x30, 0x41,
+        0x30, 0x42, 0x30, 0x43, 0x30, 0x44, 0x30, 0x46, 0x31,
+        0x30, 0x31, 0x31, 0x31, 0x32, 0x31, 0x34, 0x31, 0x35,
+        0x31, 0x36, 0x31, 0x37, 0x31, 0x39, 0x31, 0x41, 0x31,
+        0x42]);
+      var s2 = 'MDAwMTAyMDMwNTA2MDcwODBBMEIwQzBEMEYxMDExMTIxNDE1MTYxNzE5MUExQg==';
+      assertArrayEqual(UTIL.binary.base64.decode(s2), s1);
+    });
+      
+    it('should convert IPv4 0.0.0.0 textual address to 4-byte address', function() {
+      var bytes = UTIL.bytesFromIP('0.0.0.0');
+      var b = UTIL.createBuffer().fillWithByte(0, 4);
+      ASSERT.equal(bytes, b.getBytes());
+    });
+
+    it('should convert IPv4 127.0.0.1 textual address to 4-byte address', function() {
+      var bytes = UTIL.bytesFromIP('127.0.0.1');
+      var b = UTIL.createBuffer();
+      b.putByte(127);
+      b.putByte(0);
+      b.putByte(0);
+      b.putByte(1);
+      ASSERT.equal(bytes, b.getBytes());
+    });
+
+    it('should convert IPv6 :: textual address to 16-byte address', function() {
+      var bytes = UTIL.bytesFromIP('::');
+      var b = UTIL.createBuffer().fillWithByte(0, 16);
+      ASSERT.equal(bytes, b.getBytes());
+    });
+
+    it('should convert IPv6 ::0 textual address to 16-byte address', function() {
+      var bytes = UTIL.bytesFromIP('::0');
+      var b = UTIL.createBuffer().fillWithByte(0, 16);
+      ASSERT.equal(bytes, b.getBytes());
+    });
+
+    it('should convert IPv6 0:: textual address to 16-byte address', function() {
+      var bytes = UTIL.bytesFromIP('0::');
+      var b = UTIL.createBuffer().fillWithByte(0, 16);
+      ASSERT.equal(bytes, b.getBytes());
+    });
+
+    it('should convert IPv6 ::1 textual address to 16-byte address', function() {
+      var bytes = UTIL.bytesFromIP('::1');
+      var b = UTIL.createBuffer().fillWithByte(0, 14);
+      b.putBytes(UTIL.hexToBytes('0001'));
+      ASSERT.equal(bytes, b.getBytes());
+    });
+
+    it('should convert IPv6 1:: textual address to 16-byte address', function() {
+      var bytes = UTIL.bytesFromIP('1::');
+      var b = UTIL.createBuffer();
+      b.putBytes(UTIL.hexToBytes('0001'));
+      b.fillWithByte(0, 14);
+      ASSERT.equal(bytes, b.getBytes());
+    });
+
+    it('should convert IPv6 1::1 textual address to 16-byte address', function() {
+      var bytes = UTIL.bytesFromIP('1::1');
+      var b = UTIL.createBuffer();
+      b.putBytes(UTIL.hexToBytes('0001'));
+      b.fillWithByte(0, 12);
+      b.putBytes(UTIL.hexToBytes('0001'));
+      ASSERT.equal(bytes, b.getBytes());
+    });
+
+    it('should convert IPv6 1::1:0 textual address to 16-byte address', function() {
+      var bytes = UTIL.bytesFromIP('1::1:0');
+      var b = UTIL.createBuffer();
+      b.putBytes(UTIL.hexToBytes('0001'));
+      b.fillWithByte(0, 10);
+      b.putBytes(UTIL.hexToBytes('0001'));
+      b.putBytes(UTIL.hexToBytes('0000'));
+      ASSERT.equal(bytes, b.getBytes());
+    });
+
+    it('should convert IPv6 2001:db8:0:1:1:1:1:1 textual address to 16-byte address', function() {
+      var bytes = UTIL.bytesFromIP('2001:db8:0:1:1:1:1:1');
+      var b = UTIL.createBuffer();
+      b.putBytes(UTIL.hexToBytes('2001'));
+      b.putBytes(UTIL.hexToBytes('0db8'));
+      b.putBytes(UTIL.hexToBytes('0000'));
+      b.putBytes(UTIL.hexToBytes('0001'));
+      b.putBytes(UTIL.hexToBytes('0001'));
+      b.putBytes(UTIL.hexToBytes('0001'));
+      b.putBytes(UTIL.hexToBytes('0001'));
+      b.putBytes(UTIL.hexToBytes('0001'));
+      ASSERT.equal(bytes, b.getBytes());
+    });
+
+    it('should convert IPv4 0.0.0.0 byte address to textual representation', function() {
+      var addr = '0.0.0.0';
+      var bytes = UTIL.createBuffer().fillWithByte(0, 4).getBytes();
+      var addr = UTIL.bytesToIP(bytes);
+      ASSERT.equal(addr, '0.0.0.0');
+    });
+
+    it('should convert IPv4 0.0.0.0 byte address to textual representation', function() {
+      var addr = '127.0.0.1';
+      var bytes = UTIL.bytesFromIP(addr);
+      var addr = UTIL.bytesToIP(bytes);
+      ASSERT.equal(addr, '127.0.0.1');
+    });
+
+    it('should convert IPv6 :: byte address to canonical textual representation (RFC 5952)', function() {
+      var addr = '::';
+      var bytes = UTIL.createBuffer().fillWithByte(0, 16).getBytes();
+      var addr = UTIL.bytesToIP(bytes);
+      ASSERT.equal(addr, '::');
+    });
+
+    it('should convert IPv6 ::1 byte address to canonical textual representation (RFC 5952)', function() {
+      var addr = '::1';
+      var bytes = UTIL.bytesFromIP(addr);
+      var addr = UTIL.bytesToIP(bytes);
+      ASSERT.equal(addr, '::1');
+    });
+
+    it('should convert IPv6 1:: byte address to canonical textual representation (RFC 5952)', function() {
+      var addr = '1::';
+      var bytes = UTIL.bytesFromIP(addr);
+      var addr = UTIL.bytesToIP(bytes);
+      ASSERT.equal(addr, '1::');
+    });
+
+    it('should convert IPv6 0:0:0:0:0:0:0:1 byte address to canonical textual representation (RFC 5952)', function() {
+      var addr = '0:0:0:0:0:0:0:1';
+      var bytes = UTIL.bytesFromIP(addr);
+      var addr = UTIL.bytesToIP(bytes);
+      ASSERT.equal(addr, '::1');
+    });
+
+    it('should convert IPv6 1:0:0:0:0:0:0:0 byte address to canonical textual representation (RFC 5952)', function() {
+      var addr = '1:0:0:0:0:0:0:0';
+      var bytes = UTIL.bytesFromIP(addr);
+      var addr = UTIL.bytesToIP(bytes);
+      ASSERT.equal(addr, '1::');
+    });
+
+    it('should convert IPv6 1::1 byte address to canonical textual representation (RFC 5952)', function() {
+      var addr = '1::1';
+      var bytes = UTIL.bytesFromIP(addr);
+      var addr = UTIL.bytesToIP(bytes);
+      ASSERT.equal(addr, '1::1');
+    });
+
+    it('should convert IPv6 1:0:0:0:0:0:0:1 byte address to canonical textual representation (RFC 5952)', function() {
+      var addr = '1:0:0:0:0:0:0:1';
+      var bytes = UTIL.bytesFromIP(addr);
+      var addr = UTIL.bytesToIP(bytes);
+      ASSERT.equal(addr, '1::1');
+    });
+
+    it('should convert IPv6 1:0000:0000:0000:0000:0000:0000:1 byte address to canonical textual representation (RFC 5952)', function() {
+      var addr = '1:0000:0000:0000:0000:0000:0000:1';
+      var bytes = UTIL.bytesFromIP(addr);
+      var addr = UTIL.bytesToIP(bytes);
+      ASSERT.equal(addr, '1::1');
+    });
+
+    it('should convert IPv6 1:0:0:1:1:1:0:1 byte address to canonical textual representation (RFC 5952)', function() {
+      var addr = '1:0:0:1:1:1:0:1';
+      var bytes = UTIL.bytesFromIP(addr);
+      var addr = UTIL.bytesToIP(bytes);
+      ASSERT.equal(addr, '1::1:1:1:0:1');
+    });
+
+    it('should convert IPv6 1:0:1:1:1:0:0:1 byte address to canonical textual representation (RFC 5952)', function() {
+      var addr = '1:0:1:1:1:0:0:1';
+      var bytes = UTIL.bytesFromIP(addr);
+      var addr = UTIL.bytesToIP(bytes);
+      ASSERT.equal(addr, '1:0:1:1:1::1');
+    });
+
+    it('should convert IPv6 2001:db8:0:1:1:1:1:1 byte address to canonical textual representation (RFC 5952)', function() {
+      var addr = '2001:db8:0:1:1:1:1:1';
+      var bytes = UTIL.bytesFromIP(addr);
+      var addr = UTIL.bytesToIP(bytes);
+      ASSERT.equal(addr, '2001:db8:0:1:1:1:1:1');
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/util'
+  ], function(UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      UTIL()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/util')());
+}
+
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/x509.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/x509.js
new file mode 100644
index 0000000..47a9e7f
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/test/x509.js
@@ -0,0 +1,734 @@
+(function() {
+
+function Tests(ASSERT, PKI, MD, UTIL) {
+  var _pem = {
+    privateKey: '-----BEGIN RSA PRIVATE KEY-----\r\n' +
+      'MIICXQIBAAKBgQDL0EugUiNGMWscLAVM0VoMdhDZEJOqdsUMpx9U0YZI7szokJqQ\r\n' +
+      'NIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMGTkP3VF29vXBo+dLq5e+8VyAy\r\n' +
+      'Q3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGtvnM+z0MYDdKo80efzwIDAQAB\r\n' +
+      'AoGAIzkGONi5G+JifmXlLJdplom486p3upf4Ce2/7mqfaG9MnkyPSairKD/JXvfh\r\n' +
+      'NNWkkN8DKKDKBcVVElPgORYT0qwrWc7ueLBMUCbRXb1ZyfEulimG0R3kjUh7NYau\r\n' +
+      'DaIkVgfykXGSQMZx8FoaT6L080zd+0emKDDYRrb+/kgJNJECQQDoUZoiC2K/DWNY\r\n' +
+      'h3/ppZ0ane2y4SBmJUHJVMPQ2CEgxsrJTxet668ckNCKaOP/3VFPoWC41f17DvKq\r\n' +
+      'noYINNntAkEA4JbZBZBVUrQFhHlrpXT4jzqtO2RlKZzEq8qmFZfEErxOT1WMyyCi\r\n' +
+      'lAQ5gUKardo1Kf0omC8Xq/uO9ZYdED55KwJBALs6cJ65UFaq4oLJiQPzLd7yokuE\r\n' +
+      'dcj8g71PLBTW6jPxIiMFNA89nz3FU9wIVp+xbMNhSoMMKqIPVPC+m0Rn260CQQDA\r\n' +
+      'I83fWK/mZWUjBM33a68KumRiH238v8XyQxj7+C8i6D8G2GXvkigFAehAkb7LZZd+\r\n' +
+      'KLuGFyPlWv3fVWHf99KpAkBQFKk3MRMl6IGJZUEFQe4l5whm8LkGU4acSqv9B3xt\r\n' +
+      'qROkCrsFrMPqjuuzEmyHoQZ64r2PLJg7FOuyhBnQUOt4\r\n' +
+      '-----END RSA PRIVATE KEY-----\r\n',
+    publicKey: '-----BEGIN PUBLIC KEY-----\r\n' +
+      'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDL0EugUiNGMWscLAVM0VoMdhDZ\r\n' +
+      'EJOqdsUMpx9U0YZI7szokJqQNIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMG\r\n' +
+      'TkP3VF29vXBo+dLq5e+8VyAyQ3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGt\r\n' +
+      'vnM+z0MYDdKo80efzwIDAQAB\r\n' +
+      '-----END PUBLIC KEY-----\r\n',
+    certificate: '-----BEGIN CERTIFICATE-----\r\n' +
+      'MIIDIjCCAougAwIBAgIJANE2aHSbwpaRMA0GCSqGSIb3DQEBBQUAMGoxCzAJBgNV\r\n' +
+      'BAYTAlVTMREwDwYDVQQIEwhWaXJnaW5pYTETMBEGA1UEBxMKQmxhY2tzYnVyZzEN\r\n' +
+      'MAsGA1UEChMEVGVzdDENMAsGA1UECxMEVGVzdDEVMBMGA1UEAxMMbXlzZXJ2ZXIu\r\n' +
+      'Y29tMB4XDTEwMDYxOTE3MzYyOFoXDTExMDYxOTE3MzYyOFowajELMAkGA1UEBhMC\r\n' +
+      'VVMxETAPBgNVBAgTCFZpcmdpbmlhMRMwEQYDVQQHEwpCbGFja3NidXJnMQ0wCwYD\r\n' +
+      'VQQKEwRUZXN0MQ0wCwYDVQQLEwRUZXN0MRUwEwYDVQQDEwxteXNlcnZlci5jb20w\r\n' +
+      'gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMvQS6BSI0YxaxwsBUzRWgx2ENkQ\r\n' +
+      'k6p2xQynH1TRhkjuzOiQmpA0jCiSJDoSic2dZIyUi/LjoCGeVFif57N5N5Tt4wZO\r\n' +
+      'Q/dUXb29cGj50url77xXIDJDcXMzXAji2ziFEAIXzDqarKBdDuL9IO7z+tepEa2+\r\n' +
+      'cz7PQxgN0qjzR5/PAgMBAAGjgc8wgcwwHQYDVR0OBBYEFPV1Y+DHXW6bA/r9sv1y\r\n' +
+      'NJ8jAwMAMIGcBgNVHSMEgZQwgZGAFPV1Y+DHXW6bA/r9sv1yNJ8jAwMAoW6kbDBq\r\n' +
+      'MQswCQYDVQQGEwJVUzERMA8GA1UECBMIVmlyZ2luaWExEzARBgNVBAcTCkJsYWNr\r\n' +
+      'c2J1cmcxDTALBgNVBAoTBFRlc3QxDTALBgNVBAsTBFRlc3QxFTATBgNVBAMTDG15\r\n' +
+      'c2VydmVyLmNvbYIJANE2aHSbwpaRMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEF\r\n' +
+      'BQADgYEARdH2KOlJWTC1CS2y/PAvg4uiM31PXMC1hqSdJlnLM1MY4hRfuf9VyTeX\r\n' +
+      'Y6FdybcyDLSxKn9id+g9229ci9/s9PI+QmD5vXd8yZyScLc2JkYB4GC6+9D1+/+x\r\n' +
+      's2hzMxuK6kzZlP+0l9LGcraMQPGRydjCARZZm4Uegln9rh85XFQ=\r\n' +
+      '-----END CERTIFICATE-----\r\n'
+  };
+
+  describe('x509', function() {
+    it('should convert certificate to/from PEM', function() {
+      var certificate = PKI.certificateFromPem(_pem.certificate);
+      ASSERT.equal(PKI.certificateToPem(certificate), _pem.certificate);
+    });
+
+    it('should verify self-signed certificate', function() {
+      var certificate = PKI.certificateFromPem(_pem.certificate);
+      ASSERT.ok(certificate.verify(certificate));
+    });
+
+    it('should generate and verify a self-signed certificate', function() {
+      var keys = {
+        privateKey: PKI.privateKeyFromPem(_pem.privateKey),
+        publicKey: PKI.publicKeyFromPem(_pem.publicKey)
+      };
+      var attrs = [{
+        name: 'commonName',
+        value: 'example.org'
+      }, {
+        name: 'countryName',
+        value: 'US'
+      }, {
+        shortName: 'ST',
+        value: 'Virginia'
+      }, {
+        name: 'localityName',
+        value: 'Blacksburg'
+      }, {
+        name: 'organizationName',
+        value: 'Test'
+      }, {
+        shortName: 'OU',
+        value: 'Test'
+      }];
+      var cert = createCertificate({
+        publicKey: keys.publicKey,
+        signingKey: keys.privateKey,
+        serialNumber: '01',
+        subject: attrs,
+        issuer: attrs,
+        isCA: true
+      });
+
+      var pem = PKI.certificateToPem(cert);
+      cert = PKI.certificateFromPem(pem);
+
+      // verify certificate chain
+      var caStore = PKI.createCaStore();
+      caStore.addCertificate(cert);
+      PKI.verifyCertificateChain(caStore, [cert], function(vfd, depth, chain) {
+        ASSERT.equal(vfd, true);
+        ASSERT.ok(cert.verifySubjectKeyIdentifier());
+        return true;
+      });
+    });
+
+    it('should generate and fail to verify a self-signed certificate that is not in the CA store', function() {
+      var keys = {
+        privateKey: PKI.privateKeyFromPem(_pem.privateKey),
+        publicKey: PKI.publicKeyFromPem(_pem.publicKey)
+      };
+      var attrs = [{
+        name: 'commonName',
+        value: 'example.org'
+      }, {
+        name: 'countryName',
+        value: 'US'
+      }, {
+        shortName: 'ST',
+        value: 'Virginia'
+      }, {
+        name: 'localityName',
+        value: 'Blacksburg'
+      }, {
+        name: 'organizationName',
+        value: 'Test'
+      }, {
+        shortName: 'OU',
+        value: 'Test'
+      }];
+      var cert = createCertificate({
+        publicKey: keys.publicKey,
+        signingKey: keys.privateKey,
+        serialNumber: '01',
+        subject: attrs,
+        issuer: attrs,
+        isCA: true
+      });
+
+      var pem = PKI.certificateToPem(cert);
+      cert = PKI.certificateFromPem(pem);
+
+      // verify certificate chain
+      var caStore = PKI.createCaStore();
+      PKI.verifyCertificateChain(caStore, [cert], function(vfd, depth, chain) {
+        ASSERT.equal(vfd, PKI.certificateError.unknown_ca);
+        return true;
+      });
+    });
+
+    it('should verify certificate chain ending with intermediate certificate from CA store', function() {
+      var keys = {
+        privateKey: PKI.privateKeyFromPem(_pem.privateKey),
+        publicKey: PKI.publicKeyFromPem(_pem.publicKey)
+      };
+      var entity = [{
+        name: 'commonName',
+        value: 'example.org'
+      }, {
+        name: 'countryName',
+        value: 'US'
+      }, {
+        shortName: 'ST',
+        value: 'Virginia'
+      }, {
+        name: 'localityName',
+        value: 'Blacksburg'
+      }, {
+        name: 'organizationName',
+        value: 'Test'
+      }, {
+        shortName: 'OU',
+        value: 'Test'
+      }];
+      var intermediate = [{
+        name: 'commonName',
+        value: 'intermediate'
+      }, {
+        name: 'countryName',
+        value: 'US'
+      }, {
+        shortName: 'ST',
+        value: 'Virginia'
+      }, {
+        name: 'localityName',
+        value: 'Blacksburg'
+      }, {
+        name: 'organizationName',
+        value: 'Test'
+      }, {
+        shortName: 'OU',
+        value: 'Test'
+      }];
+      var root = [{
+        name: 'commonName',
+        value: 'root'
+      }, {
+        name: 'countryName',
+        value: 'US'
+      }, {
+        shortName: 'ST',
+        value: 'Virginia'
+      }, {
+        name: 'localityName',
+        value: 'Blacksburg'
+      }, {
+        name: 'organizationName',
+        value: 'Test'
+      }, {
+        shortName: 'OU',
+        value: 'Test'
+      }];
+
+      var intermediateCert = createCertificate({
+        publicKey: keys.publicKey,
+        signingKey: keys.privateKey,
+        serialNumber: '01',
+        subject: intermediate,
+        issuer: root,
+        isCA: true
+      });
+
+      var entityCert = createCertificate({
+        publicKey: keys.publicKey,
+        signingKey: keys.privateKey,
+        serialNumber: '01',
+        subject: entity,
+        issuer: intermediate,
+        isCA: false
+      });
+
+      // verify certificate chain
+      var caStore = PKI.createCaStore();
+      caStore.addCertificate(intermediateCert);
+      var chain = [entityCert, intermediateCert];
+      PKI.verifyCertificateChain(caStore, chain, function(vfd, depth, chain) {
+        ASSERT.equal(vfd, true);
+        return true;
+      });
+    });
+
+    it('should fail to verify certificate chain ending with non-CA intermediate certificate from CA store', function() {
+      var keys = {
+        privateKey: PKI.privateKeyFromPem(_pem.privateKey),
+        publicKey: PKI.publicKeyFromPem(_pem.publicKey)
+      };
+      var entity = [{
+        name: 'commonName',
+        value: 'example.org'
+      }, {
+        name: 'countryName',
+        value: 'US'
+      }, {
+        shortName: 'ST',
+        value: 'Virginia'
+      }, {
+        name: 'localityName',
+        value: 'Blacksburg'
+      }, {
+        name: 'organizationName',
+        value: 'Test'
+      }, {
+        shortName: 'OU',
+        value: 'Test'
+      }];
+      var intermediate = [{
+        name: 'commonName',
+        value: 'intermediate'
+      }, {
+        name: 'countryName',
+        value: 'US'
+      }, {
+        shortName: 'ST',
+        value: 'Virginia'
+      }, {
+        name: 'localityName',
+        value: 'Blacksburg'
+      }, {
+        name: 'organizationName',
+        value: 'Test'
+      }, {
+        shortName: 'OU',
+        value: 'Test'
+      }];
+      var root = [{
+        name: 'commonName',
+        value: 'root'
+      }, {
+        name: 'countryName',
+        value: 'US'
+      }, {
+        shortName: 'ST',
+        value: 'Virginia'
+      }, {
+        name: 'localityName',
+        value: 'Blacksburg'
+      }, {
+        name: 'organizationName',
+        value: 'Test'
+      }, {
+        shortName: 'OU',
+        value: 'Test'
+      }];
+
+      var intermediateCert = createCertificate({
+        publicKey: keys.publicKey,
+        signingKey: keys.privateKey,
+        serialNumber: '01',
+        subject: intermediate,
+        issuer: root,
+        isCA: false
+      });
+
+      var entityCert = createCertificate({
+        publicKey: keys.publicKey,
+        signingKey: keys.privateKey,
+        serialNumber: '01',
+        subject: entity,
+        issuer: intermediate,
+        isCA: false
+      });
+
+      // verify certificate chain
+      var caStore = PKI.createCaStore();
+      caStore.addCertificate(intermediateCert);
+      var chain = [entityCert, intermediateCert];
+      PKI.verifyCertificateChain(caStore, chain, function(vfd, depth, chain) {
+        if(depth === 0) {
+          ASSERT.equal(vfd, true);
+        } else {
+          ASSERT.equal(vfd, PKI.certificateError.bad_certificate);
+        }
+        return true;
+      });
+    });
+
+    it('should verify certificate with sha1WithRSAEncryption signature', function() {
+      var certPem = '-----BEGIN CERTIFICATE-----\r\n' +
+        'MIIDZDCCAs2gAwIBAgIKQ8fjjgAAAABh3jANBgkqhkiG9w0BAQUFADBGMQswCQYD\r\n' +
+        'VQQGEwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzEiMCAGA1UEAxMZR29vZ2xlIElu\r\n' +
+        'dGVybmV0IEF1dGhvcml0eTAeFw0xMjA2MjcxMzU5MTZaFw0xMzA2MDcxOTQzMjda\r\n' +
+        'MGcxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1N\r\n' +
+        'b3VudGFpbiBWaWV3MRMwEQYDVQQKEwpHb29nbGUgSW5jMRYwFAYDVQQDEw13d3cu\r\n' +
+        'Z29vZ2xlLmRlMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCw2Hw3vNy5QMSd\r\n' +
+        '0/iMCS8lwZk9lnEk2NmrJt6vGJfRGlBprtHp5lpMFMoi+x8m8EwGVxXHGp7hLyN/\r\n' +
+        'gXuUjL7/DY9fxxx9l77D+sDZz7jfUfWmhS03Ra1FbT6myF8miVZFChJ8XgWzioJY\r\n' +
+        'gyNdRUC9149yrXdPWrSmSVaT0+tUCwIDAQABo4IBNjCCATIwHQYDVR0lBBYwFAYI\r\n' +
+        'KwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBTiQGhrO3785rMPIKZ/zQEl5RyS\r\n' +
+        '0TAfBgNVHSMEGDAWgBS/wDDr9UMRPme6npH7/Gra42sSJDBbBgNVHR8EVDBSMFCg\r\n' +
+        'TqBMhkpodHRwOi8vd3d3LmdzdGF0aWMuY29tL0dvb2dsZUludGVybmV0QXV0aG9y\r\n' +
+        'aXR5L0dvb2dsZUludGVybmV0QXV0aG9yaXR5LmNybDBmBggrBgEFBQcBAQRaMFgw\r\n' +
+        'VgYIKwYBBQUHMAKGSmh0dHA6Ly93d3cuZ3N0YXRpYy5jb20vR29vZ2xlSW50ZXJu\r\n' +
+        'ZXRBdXRob3JpdHkvR29vZ2xlSW50ZXJuZXRBdXRob3JpdHkuY3J0MAwGA1UdEwEB\r\n' +
+        '/wQCMAAwDQYJKoZIhvcNAQEFBQADgYEAVJ0qt/MBvHEPuWHeH51756qy+lBNygLA\r\n' +
+        'Xp5Gq+xHUTOzRty61BR05zv142hYAGWvpvnEOJ/DI7V3QlXK8a6dQ+du97obQJJx\r\n' +
+        '7ekqtfxVzmlSb23halYSoXmWgP8Tq0VUDsgsSLE7fS8JuO1soXUVKj1/6w189HL6\r\n' +
+        'LsngXwZSuL0=\r\n' +
+        '-----END CERTIFICATE-----\r\n';
+      var issuerPem = '-----BEGIN CERTIFICATE-----\r\n' +
+        'MIICsDCCAhmgAwIBAgIDC2dxMA0GCSqGSIb3DQEBBQUAME4xCzAJBgNVBAYTAlVT\r\n' +
+        'MRAwDgYDVQQKEwdFcXVpZmF4MS0wKwYDVQQLEyRFcXVpZmF4IFNlY3VyZSBDZXJ0\r\n' +
+        'aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDkwNjA4MjA0MzI3WhcNMTMwNjA3MTk0MzI3\r\n' +
+        'WjBGMQswCQYDVQQGEwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzEiMCAGA1UEAxMZ\r\n' +
+        'R29vZ2xlIEludGVybmV0IEF1dGhvcml0eTCBnzANBgkqhkiG9w0BAQEFAAOBjQAw\r\n' +
+        'gYkCgYEAye23pIucV+eEPkB9hPSP0XFjU5nneXQUr0SZMyCSjXvlKAy6rWxJfoNf\r\n' +
+        'NFlOCnowzdDXxFdF7dWq1nMmzq0yE7jXDx07393cCDaob1FEm8rWIFJztyaHNWrb\r\n' +
+        'qeXUWaUr/GcZOfqTGBhs3t0lig4zFEfC7wFQeeT9adGnwKziV28CAwEAAaOBozCB\r\n' +
+        'oDAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFL/AMOv1QxE+Z7qekfv8atrjaxIk\r\n' +
+        'MB8GA1UdIwQYMBaAFEjmaPkr0rKV10fYIyAQTzOYkJ/UMBIGA1UdEwEB/wQIMAYB\r\n' +
+        'Af8CAQAwOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2NybC5nZW90cnVzdC5jb20v\r\n' +
+        'Y3Jscy9zZWN1cmVjYS5jcmwwDQYJKoZIhvcNAQEFBQADgYEAuIojxkiWsRF8YHde\r\n' +
+        'BZqrocb6ghwYB8TrgbCoZutJqOkM0ymt9e8kTP3kS8p/XmOrmSfLnzYhLLkQYGfN\r\n' +
+        '0rTw8Ktx5YtaiScRhKqOv5nwnQkhClIZmloJ0pC3+gz4fniisIWvXEyZ2VxVKfml\r\n' +
+        'UUIuOss4jHg7y/j7lYe8vJD5UDI=\r\n' +
+        '-----END CERTIFICATE-----\r\n';
+      var cert = PKI.certificateFromPem(certPem, true);
+      var issuer = PKI.certificateFromPem(issuerPem);
+      ASSERT.ok(issuer.verify(cert));
+    });
+
+    it('should verify certificate with sha256WithRSAEncryption signature', function() {
+      var certPem = '-----BEGIN CERTIFICATE-----\r\n' +
+        'MIIDuzCCAqOgAwIBAgIEO5vZjDANBgkqhkiG9w0BAQsFADBGMQswCQYDVQQGEwJE\r\n' +
+        'RTEPMA0GA1UEChMGRWxzdGVyMQswCQYDVQQLEwJDQTEZMBcGA1UEAxMQRWxzdGVy\r\n' +
+        'U29mdFRlc3RDQTAeFw0xMDA5MTUwNTM4MjRaFw0xMzA5MTUwNTM4MjRaMCsxFDAS\r\n' +
+        'BgNVBAUTCzEwMDIzMTQ5OTRDMRMwEQYDVQQDEwoxMDAyMzE0OTk0MIGfMA0GCSqG\r\n' +
+        'SIb3DQEBAQUAA4GNADCBiQKBgQCLPqjbwjsugzw6+qwwm/pdzDwk7ASIsBYJ17GT\r\n' +
+        'qyT0zCnYmdDDGWsYc+xxFVVIi8xBt6Mlq8Rwj+02UJhY9qm6zRA9MqFZC3ih+HoW\r\n' +
+        'xq7H8N2d10N0rX6h5PSjkF5fU5ugncZmppsRGJ9DNXgwjpf/CsH2rqThUzK4xfrq\r\n' +
+        'jpDS/wIDAQABo4IBTjCCAUowDgYDVR0PAQH/BAQDAgUgMAwGA1UdEwEB/wQCMAAw\r\n' +
+        'HQYDVR0OBBYEFF1h7H37OQivS57GD8+nK6VsgMPTMIGXBgNVHR8EgY8wgYwwgYmg\r\n' +
+        'gYaggYOGgYBsZGFwOi8vMTkyLjE2OC42LjI0OjM4OS9sJTNkQ0ElMjBaZXJ0aWZp\r\n' +
+        'a2F0ZSxvdSUzZENBLGNuJTNkRWxzdGVyU29mdFRlc3RDQSxkYyUzZHdpZXNlbCxk\r\n' +
+        'YyUzZGVsc3RlcixkYyUzZGRlPz9iYXNlPyhvYmplY3RDbGFzcz0qKTBxBgNVHSME\r\n' +
+        'ajBogBRBILMYmlZu//pj3wjDe2UPkq7jk6FKpEgwRjELMAkGA1UEBhMCREUxDzAN\r\n' +
+        'BgNVBAoTBkVsc3RlcjEPMA0GA1UECxMGUm9vdENBMRUwEwYDVQQDEwxFbHN0ZXJS\r\n' +
+        'b290Q0GCBDuayikwDQYJKoZIhvcNAQELBQADggEBAK8Z1+/VNyU5w/EiyhFH5aRE\r\n' +
+        'Mzxo0DahqKEm4pW5haBgKubJwZGs+CrBZR70TPbZGgJd36eyMgeXb/06lBnxewii\r\n' +
+        'I/aY6wMTviQTpqFnz5m0Le8UhH+hY1bqNG/vf6J+1gbOSrZyhAUV+MDJbL/OkzX4\r\n' +
+        'voVAfUBqSODod0f5wCW2RhvBmB9E62baP6qizdxyPA4iV16H4C0etd/7coLX6NZC\r\n' +
+        'oz3Yu0IRTQCH+YrpfIbxGb0grNhtCTfFpa287fuzu8mIEvLNr8GibhBXmQg7iJ+y\r\n' +
+        'q0VIyZLY8k6jEPrUB5Iv5ouSR19Dda/2+xJPlT/bosuNcErEuk/yKAHWAzwm1wQ=\r\n' +
+        '-----END CERTIFICATE-----\r\n';
+      var issuerPem = '-----BEGIN CERTIFICATE-----\r\n' +
+        'MIIESjCCAzKgAwIBAgIEO5rKKTANBgkqhkiG9w0BAQsFADBGMQswCQYDVQQGEwJE\r\n' +
+        'RTEPMA0GA1UEChMGRWxzdGVyMQ8wDQYDVQQLEwZSb290Q0ExFTATBgNVBAMTDEVs\r\n' +
+        'c3RlclJvb3RDQTAeFw0wOTA3MjgwODE5MTFaFw0xNDA3MjgwODE5MTFaMEYxCzAJ\r\n' +
+        'BgNVBAYTAkRFMQ8wDQYDVQQKEwZFbHN0ZXIxCzAJBgNVBAsTAkNBMRkwFwYDVQQD\r\n' +
+        'ExBFbHN0ZXJTb2Z0VGVzdENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC\r\n' +
+        'AQEAv5uoKLnxXQe75iqwqgyt3H6MDAx/wvUVs26+2+yHpEUb/2gA3L8E+NChSb9E\r\n' +
+        'aNgxxoac3Yhvxzq2mPpih3vkY7Xw512Tm8l/OPbT8kbmBJmYZneFALXHytAIZiEf\r\n' +
+        'e0ZYNKAlClFIgNP5bE9UjTqVEEoSiUhpTubM6c5xEYVznnwPBoYQ0ari7RTDYnME\r\n' +
+        'HK4vMfoeBeWHYPiEygNHnGUG8d3merRC/lQASUtL6ikmLWKCKHfyit5ACzPNKAtw\r\n' +
+        'IzHAzD5ek0BpcUTci8hUsKz2ZvmoZcjPyj63veQuMYS5cTMgr3bfz9uz1xuoEDsb\r\n' +
+        'Sv9rQX9Iw3N7yMpxTDJTqGKhYwIDAQABo4IBPjCCATowDgYDVR0PAQH/BAQDAgEG\r\n' +
+        'MBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFEEgsxiaVm7/+mPfCMN7ZQ+S\r\n' +
+        'ruOTMIGXBgNVHR8EgY8wgYwwgYmggYaggYOGgYBsZGFwOi8vMTkyLjE2OC42LjI0\r\n' +
+        'OjM4OS9sJTNkQ0ElMjBaZXJ0aWZpa2F0ZSxvdSUzZFJvb3RDQSxjbiUzZEVsc3Rl\r\n' +
+        'clJvb3RDQSxkYyUzZHdpZXNlbCxkYyUzZGVsc3RlcixkYyUzZGRlPz9iYXNlPyhv\r\n' +
+        'YmplY3RDbGFzcz0qKTBbBgNVHSMEVDBSoUqkSDBGMQswCQYDVQQGEwJERTEPMA0G\r\n' +
+        'A1UEChMGRWxzdGVyMQ8wDQYDVQQLEwZSb290Q0ExFTATBgNVBAMTDEVsc3RlclJv\r\n' +
+        'b3RDQYIEO5rKADANBgkqhkiG9w0BAQsFAAOCAQEAFauDnfHSbgRmbFkpQUXM5wKi\r\n' +
+        'K5STAaVps201iAjacX5EsOs5L37VUMoT9G2DAE8Z6B1pIiR3zcd3UpiHnFlUTC0f\r\n' +
+        'ZdOCXzDkOfziKY/RzuUsLNFUhBizCIA0+XcKgm3dSA5ex8fChLJddSYheSLuPua7\r\n' +
+        'iNMuzaU2YnevbMwpdEsl55Qr/uzcc0YM/mCuM4vsNFyFml91SQyPPmdR3VvGOoGl\r\n' +
+        'qS1R0HSoPJUvr0N0kARwD7kO3ikcJ6FxBCsgVLZHGJC+q8PQNZmVMbfgjH4nAyP8\r\n' +
+        'u7Qe03v2WLW0UgKu2g0UcQXWXbovktpZoK0fUOwv3bqsZ0K1IjVvMKG8OysUvA==\r\n' +
+        '-----END CERTIFICATE-----\r\n';
+      var cert = PKI.certificateFromPem(certPem, true);
+      var issuer = PKI.certificateFromPem(issuerPem);
+      ASSERT.ok(issuer.verify(cert));
+    });
+
+    it('should import certificate with sha256 RSASSA-PSS signature', function() {
+      var certPem = '-----BEGIN CERTIFICATE-----\r\n' +
+        'MIIERzCCAvugAwIBAgIEO50CcjBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQC\r\n' +
+        'AQUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCASAwRjELMAkGA1UE\r\n' +
+        'BhMCREUxDzANBgNVBAoTBkVsc3RlcjELMAkGA1UECxMCQ0ExGTAXBgNVBAMTEEVs\r\n' +
+        'c3RlclNvZnRUZXN0Q0EwHhcNMTEwNzI4MTIxMzU3WhcNMTQwNzI4MTIxMzU3WjAr\r\n' +
+        'MRQwEgYDVQQFEwsxMDAyNzUzMzI1QzETMBEGA1UEAxMKMTAwMjc1MzMyNTCCASIw\r\n' +
+        'DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALHCogo7LVUkxWsMIc/0jgH2PCLt\r\n' +
+        'ukbATPehxWBG1XUPrz53lWgFJzlZaKLlLVVnXrfULaifuOKlZP6SM1JQlL1JuYgY\r\n' +
+        'AdgZyHjderNIk5NsSTmefwonSn/ukri5IRTH420oHtSjxk6+/DXlWnQy5OzTN6Bq\r\n' +
+        'jVJo8L+TTmf2jWuEam5cWa+YVP2k3tIqX5yMUDFjKO4znHdtIkHnBE0Kx03rWQRB\r\n' +
+        'TSYWDgDm2gttdOs9JVeuW0nnwQj27uo9gOR0iyaUjVrKLZ95p6zpXhM4uMSVRNeo\r\n' +
+        'LqkdqP2n+4pDXZVqLNgjkHQUS/xq9Q/kYgT2J7wkGfYxP9to7TG7vra1eOECAwEA\r\n' +
+        'AaOB7zCB7DAOBgNVHQ8BAf8EBAMCBSAwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU\r\n' +
+        'NDJ2BZIk6ukLqkdmttH12bu2leswOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2Ny\r\n' +
+        'bC5lbHN0ZXIuZGUvRWxzdGVyU29mdFRlc3RDQS5jcmwwcQYDVR0jBGowaIAU1R9A\r\n' +
+        'HmpdzaxK3v+ihQsEpAFgzOKhSqRIMEYxCzAJBgNVBAYTAkRFMQ8wDQYDVQQKEwZF\r\n' +
+        'bHN0ZXIxDzANBgNVBAsTBlJvb3RDQTEVMBMGA1UEAxMMRWxzdGVyUm9vdENBggQ7\r\n' +
+        'msqPMEEGCSqGSIb3DQEBCjA0oA8wDQYJYIZIAWUDBAIBBQChHDAaBgkqhkiG9w0B\r\n' +
+        'AQgwDQYJYIZIAWUDBAIBBQCiAwIBIAOCAQEAJBYNRZpe+z3yPPLV539Yci6OfjVg\r\n' +
+        'vs1e3rvSvcpFaqRJ8vZ8WNx3uuRQZ6B4Z3YEc00UJAOl3wU6KhamyryK2YvCrPg+\r\n' +
+        'TS5QDXNaO2z/rAnY1wWSlwBPlhqpMRrNv9cRXBcgK5YxprjytCVYN0UHdintgYxG\r\n' +
+        'fg7QYiFb00UXxAza1AFrpG+RqySFfO1scmu4kgpeb6A3USnQ0r6rZz6dt6NqgZZ6\r\n' +
+        'oUpDOCvnS9XSOWuvJirV8hIU0KAagguTbwfTqt9nt0wDlwZpemsJZ4Vvnvy8d9Jf\r\n' +
+        'zA68EWHbZLr2QP9hb3sHCWJgplMsTJnUwRfi2hf5KNtP8Xg5DSLMfTEbhw==\r\n' +
+        '-----END CERTIFICATE-----\r\n';
+      var cert = PKI.certificateFromPem(certPem, true);
+
+      ASSERT.equal(cert.signatureOid, PKI.oids['RSASSA-PSS']);
+      ASSERT.equal(cert.signatureParameters.hash.algorithmOid, PKI.oids['sha256']);
+      ASSERT.equal(cert.signatureParameters.mgf.algorithmOid, PKI.oids['mgf1']);
+      ASSERT.equal(cert.signatureParameters.mgf.hash.algorithmOid, PKI.oids['sha256']);
+      ASSERT.equal(cert.siginfo.algorithmOid, PKI.oids['RSASSA-PSS']);
+      ASSERT.equal(cert.siginfo.parameters.hash.algorithmOid, PKI.oids['sha256']);
+      ASSERT.equal(cert.siginfo.parameters.mgf.algorithmOid, PKI.oids['mgf1']);
+      ASSERT.equal(cert.siginfo.parameters.mgf.hash.algorithmOid, PKI.oids['sha256']);
+    });
+
+    it('should export certificate with sha256 RSASSA-PSS signature', function() {
+      var certPem = '-----BEGIN CERTIFICATE-----\r\n' +
+        'MIIERzCCAvugAwIBAgIEO50CcjBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQC\r\n' +
+        'AQUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCASAwRjELMAkGA1UE\r\n' +
+        'BhMCREUxDzANBgNVBAoTBkVsc3RlcjELMAkGA1UECxMCQ0ExGTAXBgNVBAMTEEVs\r\n' +
+        'c3RlclNvZnRUZXN0Q0EwHhcNMTEwNzI4MTIxMzU3WhcNMTQwNzI4MTIxMzU3WjAr\r\n' +
+        'MRQwEgYDVQQFEwsxMDAyNzUzMzI1QzETMBEGA1UEAxMKMTAwMjc1MzMyNTCCASIw\r\n' +
+        'DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALHCogo7LVUkxWsMIc/0jgH2PCLt\r\n' +
+        'ukbATPehxWBG1XUPrz53lWgFJzlZaKLlLVVnXrfULaifuOKlZP6SM1JQlL1JuYgY\r\n' +
+        'AdgZyHjderNIk5NsSTmefwonSn/ukri5IRTH420oHtSjxk6+/DXlWnQy5OzTN6Bq\r\n' +
+        'jVJo8L+TTmf2jWuEam5cWa+YVP2k3tIqX5yMUDFjKO4znHdtIkHnBE0Kx03rWQRB\r\n' +
+        'TSYWDgDm2gttdOs9JVeuW0nnwQj27uo9gOR0iyaUjVrKLZ95p6zpXhM4uMSVRNeo\r\n' +
+        'LqkdqP2n+4pDXZVqLNgjkHQUS/xq9Q/kYgT2J7wkGfYxP9to7TG7vra1eOECAwEA\r\n' +
+        'AaOB7zCB7DAOBgNVHQ8BAf8EBAMCBSAwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU\r\n' +
+        'NDJ2BZIk6ukLqkdmttH12bu2leswOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2Ny\r\n' +
+        'bC5lbHN0ZXIuZGUvRWxzdGVyU29mdFRlc3RDQS5jcmwwcQYDVR0jBGowaIAU1R9A\r\n' +
+        'HmpdzaxK3v+ihQsEpAFgzOKhSqRIMEYxCzAJBgNVBAYTAkRFMQ8wDQYDVQQKEwZF\r\n' +
+        'bHN0ZXIxDzANBgNVBAsTBlJvb3RDQTEVMBMGA1UEAxMMRWxzdGVyUm9vdENBggQ7\r\n' +
+        'msqPMEEGCSqGSIb3DQEBCjA0oA8wDQYJYIZIAWUDBAIBBQChHDAaBgkqhkiG9w0B\r\n' +
+        'AQgwDQYJYIZIAWUDBAIBBQCiAwIBIAOCAQEAJBYNRZpe+z3yPPLV539Yci6OfjVg\r\n' +
+        'vs1e3rvSvcpFaqRJ8vZ8WNx3uuRQZ6B4Z3YEc00UJAOl3wU6KhamyryK2YvCrPg+\r\n' +
+        'TS5QDXNaO2z/rAnY1wWSlwBPlhqpMRrNv9cRXBcgK5YxprjytCVYN0UHdintgYxG\r\n' +
+        'fg7QYiFb00UXxAza1AFrpG+RqySFfO1scmu4kgpeb6A3USnQ0r6rZz6dt6NqgZZ6\r\n' +
+        'oUpDOCvnS9XSOWuvJirV8hIU0KAagguTbwfTqt9nt0wDlwZpemsJZ4Vvnvy8d9Jf\r\n' +
+        'zA68EWHbZLr2QP9hb3sHCWJgplMsTJnUwRfi2hf5KNtP8Xg5DSLMfTEbhw==\r\n' +
+        '-----END CERTIFICATE-----\r\n';
+      var cert = PKI.certificateFromPem(certPem, true);
+      ASSERT.equal(PKI.certificateToPem(cert), certPem);
+    });
+
+    it('should verify certificate with sha256 RSASSA-PSS signature', function() {
+      var certPem = '-----BEGIN CERTIFICATE-----\r\n' +
+        'MIIERzCCAvugAwIBAgIEO50CcjBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQC\r\n' +
+        'AQUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCASAwRjELMAkGA1UE\r\n' +
+        'BhMCREUxDzANBgNVBAoTBkVsc3RlcjELMAkGA1UECxMCQ0ExGTAXBgNVBAMTEEVs\r\n' +
+        'c3RlclNvZnRUZXN0Q0EwHhcNMTEwNzI4MTIxMzU3WhcNMTQwNzI4MTIxMzU3WjAr\r\n' +
+        'MRQwEgYDVQQFEwsxMDAyNzUzMzI1QzETMBEGA1UEAxMKMTAwMjc1MzMyNTCCASIw\r\n' +
+        'DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALHCogo7LVUkxWsMIc/0jgH2PCLt\r\n' +
+        'ukbATPehxWBG1XUPrz53lWgFJzlZaKLlLVVnXrfULaifuOKlZP6SM1JQlL1JuYgY\r\n' +
+        'AdgZyHjderNIk5NsSTmefwonSn/ukri5IRTH420oHtSjxk6+/DXlWnQy5OzTN6Bq\r\n' +
+        'jVJo8L+TTmf2jWuEam5cWa+YVP2k3tIqX5yMUDFjKO4znHdtIkHnBE0Kx03rWQRB\r\n' +
+        'TSYWDgDm2gttdOs9JVeuW0nnwQj27uo9gOR0iyaUjVrKLZ95p6zpXhM4uMSVRNeo\r\n' +
+        'LqkdqP2n+4pDXZVqLNgjkHQUS/xq9Q/kYgT2J7wkGfYxP9to7TG7vra1eOECAwEA\r\n' +
+        'AaOB7zCB7DAOBgNVHQ8BAf8EBAMCBSAwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU\r\n' +
+        'NDJ2BZIk6ukLqkdmttH12bu2leswOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2Ny\r\n' +
+        'bC5lbHN0ZXIuZGUvRWxzdGVyU29mdFRlc3RDQS5jcmwwcQYDVR0jBGowaIAU1R9A\r\n' +
+        'HmpdzaxK3v+ihQsEpAFgzOKhSqRIMEYxCzAJBgNVBAYTAkRFMQ8wDQYDVQQKEwZF\r\n' +
+        'bHN0ZXIxDzANBgNVBAsTBlJvb3RDQTEVMBMGA1UEAxMMRWxzdGVyUm9vdENBggQ7\r\n' +
+        'msqPMEEGCSqGSIb3DQEBCjA0oA8wDQYJYIZIAWUDBAIBBQChHDAaBgkqhkiG9w0B\r\n' +
+        'AQgwDQYJYIZIAWUDBAIBBQCiAwIBIAOCAQEAJBYNRZpe+z3yPPLV539Yci6OfjVg\r\n' +
+        'vs1e3rvSvcpFaqRJ8vZ8WNx3uuRQZ6B4Z3YEc00UJAOl3wU6KhamyryK2YvCrPg+\r\n' +
+        'TS5QDXNaO2z/rAnY1wWSlwBPlhqpMRrNv9cRXBcgK5YxprjytCVYN0UHdintgYxG\r\n' +
+        'fg7QYiFb00UXxAza1AFrpG+RqySFfO1scmu4kgpeb6A3USnQ0r6rZz6dt6NqgZZ6\r\n' +
+        'oUpDOCvnS9XSOWuvJirV8hIU0KAagguTbwfTqt9nt0wDlwZpemsJZ4Vvnvy8d9Jf\r\n' +
+        'zA68EWHbZLr2QP9hb3sHCWJgplMsTJnUwRfi2hf5KNtP8Xg5DSLMfTEbhw==\r\n' +
+        '-----END CERTIFICATE-----\r\n';
+      var issuerPem = '-----BEGIN CERTIFICATE-----\r\n' +
+        'MIIEZDCCAxigAwIBAgIEO5rKjzBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQC\r\n' +
+        'AQUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCASAwRjELMAkGA1UE\r\n' +
+        'BhMCREUxDzANBgNVBAoTBkVsc3RlcjEPMA0GA1UECxMGUm9vdENBMRUwEwYDVQQD\r\n' +
+        'EwxFbHN0ZXJSb290Q0EwHhcNMTEwNzI4MTExNzI4WhcNMTYwNzI4MTExNzI4WjBG\r\n' +
+        'MQswCQYDVQQGEwJERTEPMA0GA1UEChMGRWxzdGVyMQswCQYDVQQLEwJDQTEZMBcG\r\n' +
+        'A1UEAxMQRWxzdGVyU29mdFRlc3RDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\r\n' +
+        'AQoCggEBAMFpz3sXnXq4ZUBdYdpG5DJVfITLXYwXPfEJzr1pLRfJ2eMPn7k3B/2g\r\n' +
+        'bvuH30zKmDRjlfV51sFw4l1l+cQugzy5FEOrfE6g7IvhpBUf9SQujVWtE3BoSRR5\r\n' +
+        'pSGbIWC7sm2SG0drpoCRpL0xmWZlAUS5mz7hBecJC/kKkKeOxUg5h492XQgWd0ow\r\n' +
+        '6GlyQBxJCmxgQBMnLS0stecs234hR5gvTHic50Ey+gRMPsTyco2Fm0FqvXtBuOsj\r\n' +
+        'zAW7Nk2hnM6awFHVMDBLm+ClE1ww0dLW0ujkdoGsTEbvmM/w8MBI6WAiWaanjK/x\r\n' +
+        'lStmQeUVXKd+AfuzT/FIPrwANcC1qGUCAwEAAaOB8TCB7jAOBgNVHQ8BAf8EBAMC\r\n' +
+        'AQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU1R9AHmpdzaxK3v+ihQsE\r\n' +
+        'pAFgzOIwNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5lbHN0ZXIuZGUvRWxz\r\n' +
+        'dGVyUm9vdENBLmNybDBxBgNVHSMEajBogBS3zfTokckL2H/fTojW02K+metEcaFK\r\n' +
+        'pEgwRjELMAkGA1UEBhMCREUxDzANBgNVBAoTBkVsc3RlcjEPMA0GA1UECxMGUm9v\r\n' +
+        'dENBMRUwEwYDVQQDEwxFbHN0ZXJSb290Q0GCBDuaylowQQYJKoZIhvcNAQEKMDSg\r\n' +
+        'DzANBglghkgBZQMEAgEFAKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgEFAKID\r\n' +
+        'AgEgA4IBAQBjT107fBmSfQNUYCpXIlaS/pogPoCahuC1g8QDtq6IEVXoEgXCzfVN\r\n' +
+        'JYHyYOQOraP4O2LEcXpo/oc0MMpVF06QLKG/KieUE0mrZnsCJ3GLSJkM8tI8bRHj\r\n' +
+        '8tAhlViMacUqnKKufCs/rIN25JB57+sCyFqjtRbSt88e+xqFJ5I79Btp/bNtoj2G\r\n' +
+        'OJGl997EPua9/yJuvdA9W67WO/KwEh+FqylB1pAapccOHqttwu4QJIc/XJfG5rrf\r\n' +
+        '8QZz8HIuOcroA4zIrprQ1nJRCuMII04ueDtBVz1eIEmqNEUkr09JdK8M0LKH0lMK\r\n' +
+        'Ysgjai/P2mPVVwhVw6dHzUVRLXh3xIQr\r\n' +
+        '-----END CERTIFICATE-----\r\n';
+      var cert = PKI.certificateFromPem(certPem, true);
+      var issuer = PKI.certificateFromPem(issuerPem);
+      ASSERT.ok(issuer.verify(cert));
+    });
+  });
+
+  describe('public key fingerprints', function() {
+    it('should get a SHA-1 RSAPublicKey fingerprint', function() {
+      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
+      var fp = PKI.getPublicKeyFingerprint(publicKey, {type: 'RSAPublicKey'});
+      ASSERT.equal(fp.toHex(), 'f57563e0c75d6e9b03fafdb2fd72349f23030300');
+    });
+
+    it('should get a SHA-1 SubjectPublicKeyInfo fingerprint', function() {
+      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
+      var fp = PKI.getPublicKeyFingerprint(
+        publicKey, {type: 'SubjectPublicKeyInfo'});
+      ASSERT.equal(fp.toHex(), '984724bc548bbc2c8acbac044bd8d518abd26dd8');
+    });
+
+    it('should get a hex SHA-1 RSAPublicKey fingerprint', function() {
+      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
+      var fp = PKI.getPublicKeyFingerprint(
+        publicKey, {type: 'RSAPublicKey', encoding: 'hex'});
+      ASSERT.equal(fp, 'f57563e0c75d6e9b03fafdb2fd72349f23030300');
+    });
+
+    it('should get a hex, colon-delimited SHA-1 RSAPublicKey fingerprint', function() {
+      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
+      var fp = PKI.getPublicKeyFingerprint(
+        publicKey, {type: 'RSAPublicKey', encoding: 'hex', delimiter: ':'});
+      ASSERT.equal(
+        fp, 'f5:75:63:e0:c7:5d:6e:9b:03:fa:fd:b2:fd:72:34:9f:23:03:03:00');
+    });
+
+    it('should get a hex, colon-delimited SHA-1 SubjectPublicKeyInfo fingerprint', function() {
+      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
+      var fp = PKI.getPublicKeyFingerprint(
+        publicKey, {
+          type: 'SubjectPublicKeyInfo',
+          encoding: 'hex',
+          delimiter: ':'
+        });
+      ASSERT.equal(
+        fp, '98:47:24:bc:54:8b:bc:2c:8a:cb:ac:04:4b:d8:d5:18:ab:d2:6d:d8');
+    });
+
+    it('should get an MD5 RSAPublicKey fingerprint', function() {
+      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
+      var fp = PKI.getPublicKeyFingerprint(
+        publicKey, {md: MD.md5.create(), type: 'RSAPublicKey'});
+      ASSERT.equal(fp.toHex(), 'c7da180cc48d31a071d31a78bc43d9d7');
+    });
+
+    it('should get an MD5 SubjectPublicKeyInfo fingerprint', function() {
+      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
+      var fp = PKI.getPublicKeyFingerprint(
+        publicKey, {md: MD.md5.create(), type: 'SubjectPublicKeyInfo'});
+      ASSERT.equal(fp.toHex(), 'e5c5ba577fe24fb8a678d8d58f539cd7');
+    });
+
+    it('should get a hex MD5 RSAPublicKey fingerprint', function() {
+      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
+      var fp = PKI.getPublicKeyFingerprint(
+        publicKey,
+        {md: MD.md5.create(), type: 'RSAPublicKey', encoding: 'hex'});
+      ASSERT.equal(fp, 'c7da180cc48d31a071d31a78bc43d9d7');
+    });
+
+    it('should get a hex, colon-delimited MD5 RSAPublicKey fingerprint', function() {
+      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
+      var fp = PKI.getPublicKeyFingerprint(
+        publicKey, {
+          md: MD.md5.create(),
+          type: 'RSAPublicKey',
+          encoding: 'hex',
+          delimiter: ':'
+        });
+      ASSERT.equal(fp, 'c7:da:18:0c:c4:8d:31:a0:71:d3:1a:78:bc:43:d9:d7');
+    });
+
+    it('should get a hex, colon-delimited MD5 SubjectPublicKeyInfo fingerprint', function() {
+      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
+      var fp = PKI.getPublicKeyFingerprint(
+        publicKey, {
+          md: MD.md5.create(),
+          type: 'SubjectPublicKeyInfo',
+          encoding: 'hex',
+          delimiter: ':'
+        });
+      ASSERT.equal(fp, 'e5:c5:ba:57:7f:e2:4f:b8:a6:78:d8:d5:8f:53:9c:d7');
+    });
+  });
+
+  function createCertificate(options) {
+    var publicKey = options.publicKey;
+    var signingKey = options.signingKey;
+    var subject = options.subject;
+    var issuer = options.issuer;
+    var isCA = options.isCA;
+    var serialNumber = options.serialNumber || '01';
+
+    var cert = PKI.createCertificate();
+    cert.publicKey = publicKey;
+    cert.serialNumber = serialNumber;
+    cert.validity.notBefore = new Date();
+    cert.validity.notAfter = new Date();
+    cert.validity.notAfter.setFullYear(
+      cert.validity.notBefore.getFullYear() + 1);
+    cert.setSubject(subject);
+    cert.setIssuer(issuer);
+    var extensions = [];
+    if(isCA) {
+      extensions.push({
+        name: 'basicConstraints',
+        cA: true
+      });
+    }
+    extensions.push({
+      name: 'keyUsage',
+      keyCertSign: true,
+      digitalSignature: true,
+      nonRepudiation: true,
+      keyEncipherment: true,
+      dataEncipherment: true
+    }, {
+      name: 'extKeyUsage',
+      serverAuth: true,
+      clientAuth: true,
+      codeSigning: true,
+      emailProtection: true,
+      timeStamping: true
+    }, {
+      name: 'nsCertType',
+      client: true,
+      server: true,
+      email: true,
+      objsign: true,
+      sslCA: true,
+      emailCA: true,
+      objCA: true
+    }, {
+      name: 'subjectAltName',
+      altNames: [{
+        type: 6, // URI
+        value: 'http://example.org/webid#me'
+      }]
+    }, {
+      name: 'subjectKeyIdentifier'
+    });
+    // FIXME: add authorityKeyIdentifier extension
+    cert.setExtensions(extensions);
+
+    cert.sign(signingKey);
+
+    return cert;
+  }
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/pki',
+    'forge/md',
+    'forge/util'
+  ], function(PKI, MD, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      PKI(),
+      MD(),
+      UTIL()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/pki')(),
+    require('../../js/md')(),
+    require('../../js/util')());
+}
+
+})();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/ui/index.html b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/ui/index.html
new file mode 100644
index 0000000..25e81b4
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/ui/index.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <link rel="stylesheet" href="mocha/mocha.css">
+    <script src="mocha/mocha.js" type="text/javascript" charset="utf-8"></script>
+    <script src="chai/chai.js" type="text/javascript" charset="utf-8"></script>
+    <script src="require.js" data-main="test" type="text/javascript" charset="utf-8"></script>
+</head>
+<body>
+    <div id="mocha"></div>
+</body>
+</html>
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/ui/require.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/ui/require.js
new file mode 100644
index 0000000..7df5d90
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/ui/require.js
@@ -0,0 +1,35 @@
+/*
+ RequireJS 2.1.5 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.
+ Available via the MIT or new BSD license.
+ see: http://github.com/jrburke/requirejs for details
+*/
+var requirejs,require,define;
+(function(aa){function I(b){return"[object Function]"===L.call(b)}function J(b){return"[object Array]"===L.call(b)}function y(b,c){if(b){var d;for(d=0;d<b.length&&(!b[d]||!c(b[d],d,b));d+=1);}}function M(b,c){if(b){var d;for(d=b.length-1;-1<d&&(!b[d]||!c(b[d],d,b));d-=1);}}function s(b,c){return ga.call(b,c)}function m(b,c){return s(b,c)&&b[c]}function G(b,c){for(var d in b)if(s(b,d)&&c(b[d],d))break}function R(b,c,d,m){c&&G(c,function(c,j){if(d||!s(b,j))m&&"string"!==typeof c?(b[j]||(b[j]={}),R(b[j],
+c,d,m)):b[j]=c});return b}function u(b,c){return function(){return c.apply(b,arguments)}}function ba(b){if(!b)return b;var c=aa;y(b.split("."),function(b){c=c[b]});return c}function B(b,c,d,m){c=Error(c+"\nhttp://requirejs.org/docs/errors.html#"+b);c.requireType=b;c.requireModules=m;d&&(c.originalError=d);return c}function ha(b){function c(a,f,b){var e,n,c,g,d,S,i,h=f&&f.split("/");e=h;var j=k.map,l=j&&j["*"];if(a&&"."===a.charAt(0))if(f){e=m(k.pkgs,f)?h=[f]:h.slice(0,h.length-1);f=a=e.concat(a.split("/"));
+for(e=0;f[e];e+=1)if(n=f[e],"."===n)f.splice(e,1),e-=1;else if(".."===n)if(1===e&&(".."===f[2]||".."===f[0]))break;else 0<e&&(f.splice(e-1,2),e-=2);e=m(k.pkgs,f=a[0]);a=a.join("/");e&&a===f+"/"+e.main&&(a=f)}else 0===a.indexOf("./")&&(a=a.substring(2));if(b&&j&&(h||l)){f=a.split("/");for(e=f.length;0<e;e-=1){c=f.slice(0,e).join("/");if(h)for(n=h.length;0<n;n-=1)if(b=m(j,h.slice(0,n).join("/")))if(b=m(b,c)){g=b;d=e;break}if(g)break;!S&&(l&&m(l,c))&&(S=m(l,c),i=e)}!g&&S&&(g=S,d=i);g&&(f.splice(0,d,
+g),a=f.join("/"))}return a}function d(a){A&&y(document.getElementsByTagName("script"),function(f){if(f.getAttribute("data-requiremodule")===a&&f.getAttribute("data-requirecontext")===i.contextName)return f.parentNode.removeChild(f),!0})}function z(a){var f=m(k.paths,a);if(f&&J(f)&&1<f.length)return d(a),f.shift(),i.require.undef(a),i.require([a]),!0}function h(a){var f,b=a?a.indexOf("!"):-1;-1<b&&(f=a.substring(0,b),a=a.substring(b+1,a.length));return[f,a]}function j(a,f,b,e){var n,C,g=null,d=f?f.name:
+null,j=a,l=!0,k="";a||(l=!1,a="_@r"+(M+=1));a=h(a);g=a[0];a=a[1];g&&(g=c(g,d,e),C=m(q,g));a&&(g?k=C&&C.normalize?C.normalize(a,function(a){return c(a,d,e)}):c(a,d,e):(k=c(a,d,e),a=h(k),g=a[0],k=a[1],b=!0,n=i.nameToUrl(k)));b=g&&!C&&!b?"_unnormalized"+(Q+=1):"";return{prefix:g,name:k,parentMap:f,unnormalized:!!b,url:n,originalName:j,isDefine:l,id:(g?g+"!"+k:k)+b}}function r(a){var f=a.id,b=m(p,f);b||(b=p[f]=new i.Module(a));return b}function t(a,f,b){var e=a.id,n=m(p,e);if(s(q,e)&&(!n||n.defineEmitComplete))"defined"===
+f&&b(q[e]);else r(a).on(f,b)}function v(a,f){var b=a.requireModules,e=!1;if(f)f(a);else if(y(b,function(f){if(f=m(p,f))f.error=a,f.events.error&&(e=!0,f.emit("error",a))}),!e)l.onError(a)}function w(){T.length&&(ia.apply(H,[H.length-1,0].concat(T)),T=[])}function x(a){delete p[a];delete V[a]}function F(a,f,b){var e=a.map.id;a.error?a.emit("error",a.error):(f[e]=!0,y(a.depMaps,function(e,c){var g=e.id,d=m(p,g);d&&(!a.depMatched[c]&&!b[g])&&(m(f,g)?(a.defineDep(c,q[g]),a.check()):F(d,f,b))}),b[e]=!0)}
+function D(){var a,f,b,e,n=(b=1E3*k.waitSeconds)&&i.startTime+b<(new Date).getTime(),c=[],g=[],h=!1,j=!0;if(!W){W=!0;G(V,function(b){a=b.map;f=a.id;if(b.enabled&&(a.isDefine||g.push(b),!b.error))if(!b.inited&&n)z(f)?h=e=!0:(c.push(f),d(f));else if(!b.inited&&(b.fetched&&a.isDefine)&&(h=!0,!a.prefix))return j=!1});if(n&&c.length)return b=B("timeout","Load timeout for modules: "+c,null,c),b.contextName=i.contextName,v(b);j&&y(g,function(a){F(a,{},{})});if((!n||e)&&h)if((A||da)&&!X)X=setTimeout(function(){X=
+0;D()},50);W=!1}}function E(a){s(q,a[0])||r(j(a[0],null,!0)).init(a[1],a[2])}function K(a){var a=a.currentTarget||a.srcElement,b=i.onScriptLoad;a.detachEvent&&!Y?a.detachEvent("onreadystatechange",b):a.removeEventListener("load",b,!1);b=i.onScriptError;(!a.detachEvent||Y)&&a.removeEventListener("error",b,!1);return{node:a,id:a&&a.getAttribute("data-requiremodule")}}function L(){var a;for(w();H.length;){a=H.shift();if(null===a[0])return v(B("mismatch","Mismatched anonymous define() module: "+a[a.length-
+1]));E(a)}}var W,Z,i,N,X,k={waitSeconds:7,baseUrl:"./",paths:{},pkgs:{},shim:{},config:{}},p={},V={},$={},H=[],q={},U={},M=1,Q=1;N={require:function(a){return a.require?a.require:a.require=i.makeRequire(a.map)},exports:function(a){a.usingExports=!0;if(a.map.isDefine)return a.exports?a.exports:a.exports=q[a.map.id]={}},module:function(a){return a.module?a.module:a.module={id:a.map.id,uri:a.map.url,config:function(){return k.config&&m(k.config,a.map.id)||{}},exports:q[a.map.id]}}};Z=function(a){this.events=
+m($,a.id)||{};this.map=a;this.shim=m(k.shim,a.id);this.depExports=[];this.depMaps=[];this.depMatched=[];this.pluginMaps={};this.depCount=0};Z.prototype={init:function(a,b,c,e){e=e||{};if(!this.inited){this.factory=b;if(c)this.on("error",c);else this.events.error&&(c=u(this,function(a){this.emit("error",a)}));this.depMaps=a&&a.slice(0);this.errback=c;this.inited=!0;this.ignore=e.ignore;e.enabled||this.enabled?this.enable():this.check()}},defineDep:function(a,b){this.depMatched[a]||(this.depMatched[a]=
+!0,this.depCount-=1,this.depExports[a]=b)},fetch:function(){if(!this.fetched){this.fetched=!0;i.startTime=(new Date).getTime();var a=this.map;if(this.shim)i.makeRequire(this.map,{enableBuildCallback:!0})(this.shim.deps||[],u(this,function(){return a.prefix?this.callPlugin():this.load()}));else return a.prefix?this.callPlugin():this.load()}},load:function(){var a=this.map.url;U[a]||(U[a]=!0,i.load(this.map.id,a))},check:function(){if(this.enabled&&!this.enabling){var a,b,c=this.map.id;b=this.depExports;
+var e=this.exports,n=this.factory;if(this.inited)if(this.error)this.emit("error",this.error);else{if(!this.defining){this.defining=!0;if(1>this.depCount&&!this.defined){if(I(n)){if(this.events.error)try{e=i.execCb(c,n,b,e)}catch(d){a=d}else e=i.execCb(c,n,b,e);this.map.isDefine&&((b=this.module)&&void 0!==b.exports&&b.exports!==this.exports?e=b.exports:void 0===e&&this.usingExports&&(e=this.exports));if(a)return a.requireMap=this.map,a.requireModules=[this.map.id],a.requireType="define",v(this.error=
+a)}else e=n;this.exports=e;if(this.map.isDefine&&!this.ignore&&(q[c]=e,l.onResourceLoad))l.onResourceLoad(i,this.map,this.depMaps);x(c);this.defined=!0}this.defining=!1;this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=!0)}}else this.fetch()}},callPlugin:function(){var a=this.map,b=a.id,d=j(a.prefix);this.depMaps.push(d);t(d,"defined",u(this,function(e){var n,d;d=this.map.name;var g=this.map.parentMap?this.map.parentMap.name:null,h=
+i.makeRequire(a.parentMap,{enableBuildCallback:!0});if(this.map.unnormalized){if(e.normalize&&(d=e.normalize(d,function(a){return c(a,g,!0)})||""),e=j(a.prefix+"!"+d,this.map.parentMap),t(e,"defined",u(this,function(a){this.init([],function(){return a},null,{enabled:!0,ignore:!0})})),d=m(p,e.id)){this.depMaps.push(e);if(this.events.error)d.on("error",u(this,function(a){this.emit("error",a)}));d.enable()}}else n=u(this,function(a){this.init([],function(){return a},null,{enabled:!0})}),n.error=u(this,
+function(a){this.inited=!0;this.error=a;a.requireModules=[b];G(p,function(a){0===a.map.id.indexOf(b+"_unnormalized")&&x(a.map.id)});v(a)}),n.fromText=u(this,function(e,c){var d=a.name,g=j(d),C=O;c&&(e=c);C&&(O=!1);r(g);s(k.config,b)&&(k.config[d]=k.config[b]);try{l.exec(e)}catch(ca){return v(B("fromtexteval","fromText eval for "+b+" failed: "+ca,ca,[b]))}C&&(O=!0);this.depMaps.push(g);i.completeLoad(d);h([d],n)}),e.load(a.name,h,n,k)}));i.enable(d,this);this.pluginMaps[d.id]=d},enable:function(){V[this.map.id]=
+this;this.enabling=this.enabled=!0;y(this.depMaps,u(this,function(a,b){var c,e;if("string"===typeof a){a=j(a,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap);this.depMaps[b]=a;if(c=m(N,a.id)){this.depExports[b]=c(this);return}this.depCount+=1;t(a,"defined",u(this,function(a){this.defineDep(b,a);this.check()}));this.errback&&t(a,"error",this.errback)}c=a.id;e=p[c];!s(N,c)&&(e&&!e.enabled)&&i.enable(a,this)}));G(this.pluginMaps,u(this,function(a){var b=m(p,a.id);b&&!b.enabled&&i.enable(a,
+this)}));this.enabling=!1;this.check()},on:function(a,b){var c=this.events[a];c||(c=this.events[a]=[]);c.push(b)},emit:function(a,b){y(this.events[a],function(a){a(b)});"error"===a&&delete this.events[a]}};i={config:k,contextName:b,registry:p,defined:q,urlFetched:U,defQueue:H,Module:Z,makeModuleMap:j,nextTick:l.nextTick,onError:v,configure:function(a){a.baseUrl&&"/"!==a.baseUrl.charAt(a.baseUrl.length-1)&&(a.baseUrl+="/");var b=k.pkgs,c=k.shim,e={paths:!0,config:!0,map:!0};G(a,function(a,b){e[b]?
+"map"===b?(k.map||(k.map={}),R(k[b],a,!0,!0)):R(k[b],a,!0):k[b]=a});a.shim&&(G(a.shim,function(a,b){J(a)&&(a={deps:a});if((a.exports||a.init)&&!a.exportsFn)a.exportsFn=i.makeShimExports(a);c[b]=a}),k.shim=c);a.packages&&(y(a.packages,function(a){a="string"===typeof a?{name:a}:a;b[a.name]={name:a.name,location:a.location||a.name,main:(a.main||"main").replace(ja,"").replace(ea,"")}}),k.pkgs=b);G(p,function(a,b){!a.inited&&!a.map.unnormalized&&(a.map=j(b))});if(a.deps||a.callback)i.require(a.deps||[],
+a.callback)},makeShimExports:function(a){return function(){var b;a.init&&(b=a.init.apply(aa,arguments));return b||a.exports&&ba(a.exports)}},makeRequire:function(a,f){function d(e,c,h){var g,k;f.enableBuildCallback&&(c&&I(c))&&(c.__requireJsBuild=!0);if("string"===typeof e){if(I(c))return v(B("requireargs","Invalid require call"),h);if(a&&s(N,e))return N[e](p[a.id]);if(l.get)return l.get(i,e,a,d);g=j(e,a,!1,!0);g=g.id;return!s(q,g)?v(B("notloaded",'Module name "'+g+'" has not been loaded yet for context: '+
+b+(a?"":". Use require([])"))):q[g]}L();i.nextTick(function(){L();k=r(j(null,a));k.skipMap=f.skipMap;k.init(e,c,h,{enabled:!0});D()});return d}f=f||{};R(d,{isBrowser:A,toUrl:function(b){var d,f=b.lastIndexOf("."),g=b.split("/")[0];if(-1!==f&&(!("."===g||".."===g)||1<f))d=b.substring(f,b.length),b=b.substring(0,f);return i.nameToUrl(c(b,a&&a.id,!0),d,!0)},defined:function(b){return s(q,j(b,a,!1,!0).id)},specified:function(b){b=j(b,a,!1,!0).id;return s(q,b)||s(p,b)}});a||(d.undef=function(b){w();var c=
+j(b,a,!0),d=m(p,b);delete q[b];delete U[c.url];delete $[b];d&&(d.events.defined&&($[b]=d.events),x(b))});return d},enable:function(a){m(p,a.id)&&r(a).enable()},completeLoad:function(a){var b,c,e=m(k.shim,a)||{},d=e.exports;for(w();H.length;){c=H.shift();if(null===c[0]){c[0]=a;if(b)break;b=!0}else c[0]===a&&(b=!0);E(c)}c=m(p,a);if(!b&&!s(q,a)&&c&&!c.inited){if(k.enforceDefine&&(!d||!ba(d)))return z(a)?void 0:v(B("nodefine","No define call for "+a,null,[a]));E([a,e.deps||[],e.exportsFn])}D()},nameToUrl:function(a,
+b,c){var e,d,h,g,j,i;if(l.jsExtRegExp.test(a))g=a+(b||"");else{e=k.paths;d=k.pkgs;g=a.split("/");for(j=g.length;0<j;j-=1)if(i=g.slice(0,j).join("/"),h=m(d,i),i=m(e,i)){J(i)&&(i=i[0]);g.splice(0,j,i);break}else if(h){a=a===h.name?h.location+"/"+h.main:h.location;g.splice(0,j,a);break}g=g.join("/");g+=b||(/\?/.test(g)||c?"":".js");g=("/"===g.charAt(0)||g.match(/^[\w\+\.\-]+:/)?"":k.baseUrl)+g}return k.urlArgs?g+((-1===g.indexOf("?")?"?":"&")+k.urlArgs):g},load:function(a,b){l.load(i,a,b)},execCb:function(a,
+b,c,d){return b.apply(d,c)},onScriptLoad:function(a){if("load"===a.type||ka.test((a.currentTarget||a.srcElement).readyState))P=null,a=K(a),i.completeLoad(a.id)},onScriptError:function(a){var b=K(a);if(!z(b.id))return v(B("scripterror","Script error",a,[b.id]))}};i.require=i.makeRequire();return i}var l,w,x,D,t,E,P,K,Q,fa,la=/(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg,ma=/[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,ea=/\.js$/,ja=/^\.\//;w=Object.prototype;var L=w.toString,ga=w.hasOwnProperty,ia=
+Array.prototype.splice,A=!!("undefined"!==typeof window&&navigator&&document),da=!A&&"undefined"!==typeof importScripts,ka=A&&"PLAYSTATION 3"===navigator.platform?/^complete$/:/^(complete|loaded)$/,Y="undefined"!==typeof opera&&"[object Opera]"===opera.toString(),F={},r={},T=[],O=!1;if("undefined"===typeof define){if("undefined"!==typeof requirejs){if(I(requirejs))return;r=requirejs;requirejs=void 0}"undefined"!==typeof require&&!I(require)&&(r=require,require=void 0);l=requirejs=function(b,c,d,z){var h,
+j="_";!J(b)&&"string"!==typeof b&&(h=b,J(c)?(b=c,c=d,d=z):b=[]);h&&h.context&&(j=h.context);(z=m(F,j))||(z=F[j]=l.s.newContext(j));h&&z.configure(h);return z.require(b,c,d)};l.config=function(b){return l(b)};l.nextTick="undefined"!==typeof setTimeout?function(b){setTimeout(b,4)}:function(b){b()};require||(require=l);l.version="2.1.5";l.jsExtRegExp=/^\/|:|\?|\.js$/;l.isBrowser=A;w=l.s={contexts:F,newContext:ha};l({});y(["toUrl","undef","defined","specified"],function(b){l[b]=function(){var c=F._;return c.require[b].apply(c,
+arguments)}});if(A&&(x=w.head=document.getElementsByTagName("head")[0],D=document.getElementsByTagName("base")[0]))x=w.head=D.parentNode;l.onError=function(b){throw b;};l.load=function(b,c,d){var l=b&&b.config||{},h;if(A)return h=l.xhtml?document.createElementNS("http://www.w3.org/1999/xhtml","html:script"):document.createElement("script"),h.type=l.scriptType||"text/javascript",h.charset="utf-8",h.async=!0,h.setAttribute("data-requirecontext",b.contextName),h.setAttribute("data-requiremodule",c),
+h.attachEvent&&!(h.attachEvent.toString&&0>h.attachEvent.toString().indexOf("[native code"))&&!Y?(O=!0,h.attachEvent("onreadystatechange",b.onScriptLoad)):(h.addEventListener("load",b.onScriptLoad,!1),h.addEventListener("error",b.onScriptError,!1)),h.src=d,K=h,D?x.insertBefore(h,D):x.appendChild(h),K=null,h;if(da)try{importScripts(d),b.completeLoad(c)}catch(j){b.onError(B("importscripts","importScripts failed for "+c+" at "+d,j,[c]))}};A&&M(document.getElementsByTagName("script"),function(b){x||(x=
+b.parentNode);if(t=b.getAttribute("data-main"))return r.baseUrl||(E=t.split("/"),Q=E.pop(),fa=E.length?E.join("/")+"/":"./",r.baseUrl=fa,t=Q),t=t.replace(ea,""),r.deps=r.deps?r.deps.concat(t):[t],!0});define=function(b,c,d){var l,h;"string"!==typeof b&&(d=c,c=b,b=null);J(c)||(d=c,c=[]);!c.length&&I(d)&&d.length&&(d.toString().replace(la,"").replace(ma,function(b,d){c.push(d)}),c=(1===d.length?["require"]:["require","exports","module"]).concat(c));if(O){if(!(l=K))P&&"interactive"===P.readyState||M(document.getElementsByTagName("script"),
+function(b){if("interactive"===b.readyState)return P=b}),l=P;l&&(b||(b=l.getAttribute("data-requiremodule")),h=F[l.getAttribute("data-requirecontext")])}(h?h.defQueue:T).push([b,c,d])};define.amd={jQuery:!0};l.exec=function(b){return eval(b)};l(r)}})(this);
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/ui/test.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/ui/test.js
new file mode 100644
index 0000000..e4d15d7
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/ui/test.js
@@ -0,0 +1,36 @@
+var ASSERT = chai.assert;
+mocha.setup({
+    ui: 'bdd',
+    timeout: 20000
+});
+requirejs.config({
+    paths: {
+        forge: 'forge',
+        test: 'test'
+    }
+});
+requirejs([
+    'test/util',
+    'test/md5',
+    'test/sha1',
+    'test/sha256',
+    'test/hmac',
+    'test/pbkdf2',
+    'test/mgf1',
+    'test/random',
+    'test/asn1',
+    'test/pem',
+    'test/rsa',
+    'test/kem',
+    'test/pkcs1',
+    'test/x509',
+    'test/csr',
+    'test/aes',
+    'test/rc2',
+    'test/des',
+    'test/pkcs7',
+    'test/pkcs12',
+    'test/tls'
+], function() {
+    mocha.run();
+});
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/ui/test.min.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/ui/test.min.js
new file mode 100644
index 0000000..e94e9e0
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/nodejs/ui/test.min.js
@@ -0,0 +1 @@
+(function(){function e(e){var t=e.util=e.util||{};typeof process=="undefined"||!process.nextTick?typeof setImmediate=="function"?(t.setImmediate=setImmediate,t.nextTick=function(e){return setImmediate(e)}):(t.setImmediate=function(e){setTimeout(e,0)},t.nextTick=t.setImmediate):(t.nextTick=process.nextTick,typeof setImmediate=="function"?t.setImmediate=setImmediate:t.setImmediate=t.nextTick),t.isArray=Array.isArray||function(e){return Object.prototype.toString.call(e)==="[object Array]"},t.ByteBuffer=function(e){this.data=e||"",this.read=0},t.ByteBuffer.prototype.length=function(){return this.data.length-this.read},t.ByteBuffer.prototype.isEmpty=function(){return this.length()<=0},t.ByteBuffer.prototype.putByte=function(e){return this.data+=String.fromCharCode(e),this},t.ByteBuffer.prototype.fillWithByte=function(e,t){e=String.fromCharCode(e);var n=this.data;while(t>0)t&1&&(n+=e),t>>>=1,t>0&&(e+=e);return this.data=n,this},t.ByteBuffer.prototype.putBytes=function(e){return this.data+=e,this},t.ByteBuffer.prototype.putString=function(e){return this.data+=t.encodeUtf8(e),this},t.ByteBuffer.prototype.putInt16=function(e){return this.data+=String.fromCharCode(e>>8&255)+String.fromCharCode(e&255),this},t.ByteBuffer.prototype.putInt24=function(e){return this.data+=String.fromCharCode(e>>16&255)+String.fromCharCode(e>>8&255)+String.fromCharCode(e&255),this},t.ByteBuffer.prototype.putInt32=function(e){return this.data+=String.fromCharCode(e>>24&255)+String.fromCharCode(e>>16&255)+String.fromCharCode(e>>8&255)+String.fromCharCode(e&255),this},t.ByteBuffer.prototype.putInt16Le=function(e){return this.data+=String.fromCharCode(e&255)+String.fromCharCode(e>>8&255),this},t.ByteBuffer.prototype.putInt24Le=function(e){return this.data+=String.fromCharCode(e&255)+String.fromCharCode(e>>8&255)+String.fromCharCode(e>>16&255),this},t.ByteBuffer.prototype.putInt32Le=function(e){return this.data+=String.fromCharCode(e&255)+String.fromCharCode(e>>8&255)+String.fromCharCode(e>>16&255)+String.fromCharCode(e>>24&255),this},t.ByteBuffer.prototype.putInt=function(e,t){do t-=8,this.data+=String.fromCharCode(e>>t&255);while(t>0);return this},t.ByteBuffer.prototype.putBuffer=function(e){return this.data+=e.getBytes(),this},t.ByteBuffer.prototype.getByte=function(){return this.data.charCodeAt(this.read++)},t.ByteBuffer.prototype.getInt16=function(){var e=this.data.charCodeAt(this.read)<<8^this.data.charCodeAt(this.read+1);return this.read+=2,e},t.ByteBuffer.prototype.getInt24=function(){var e=this.data.charCodeAt(this.read)<<16^this.data.charCodeAt(this.read+1)<<8^this.data.charCodeAt(this.read+2);return this.read+=3,e},t.ByteBuffer.prototype.getInt32=function(){var e=this.data.charCodeAt(this.read)<<24^this.data.charCodeAt(this.read+1)<<16^this.data.charCodeAt(this.read+2)<<8^this.data.charCodeAt(this.read+3);return this.read+=4,e},t.ByteBuffer.prototype.getInt16Le=function(){var e=this.data.charCodeAt(this.read)^this.data.charCodeAt(this.read+1)<<8;return this.read+=2,e},t.ByteBuffer.prototype.getInt24Le=function(){var e=this.data.charCodeAt(this.read)^this.data.charCodeAt(this.read+1)<<8^this.data.charCodeAt(this.read+2)<<16;return this.read+=3,e},t.ByteBuffer.prototype.getInt32Le=function(){var e=this.data.charCodeAt(this.read)^this.data.charCodeAt(this.read+1)<<8^this.data.charCodeAt(this.read+2)<<16^this.data.charCodeAt(this.read+3)<<24;return this.read+=4,e},t.ByteBuffer.prototype.getInt=function(e){var t=0;do t=(t<<8)+this.data.charCodeAt(this.read++),e-=8;while(e>0);return t},t.ByteBuffer.prototype.getBytes=function(e){var t;return e?(e=Math.min(this.length(),e),t=this.data.slice(this.read,this.read+e),this.read+=e):e===0?t="":(t=this.read===0?this.data:this.data.slice(this.read),this.clear()),t},t.ByteBuffer.prototype.bytes=function(e){return typeof e=="undefined"?this.data.slice(this.read):this.data.slice(this.read,this.read+e)},t.ByteBuffer.prototype.at=function(e){return this.data.charCodeAt(this.read+e)},t.ByteBuffer.prototype.setAt=function(e,t){return this.data=this.data.substr(0,this.read+e)+String.fromCharCode(t)+this.data.substr(this.read+e+1),this},t.ByteBuffer.prototype.last=function(){return this.data.charCodeAt(this.data.length-1)},t.ByteBuffer.prototype.copy=function(){var e=t.createBuffer(this.data);return e.read=this.read,e},t.ByteBuffer.prototype.compact=function(){return this.read>0&&(this.data=this.data.slice(this.read),this.read=0),this},t.ByteBuffer.prototype.clear=function(){return this.data="",this.read=0,this},t.ByteBuffer.prototype.truncate=function(e){var t=Math.max(0,this.length()-e);return this.data=this.data.substr(this.read,t),this.read=0,this},t.ByteBuffer.prototype.toHex=function(){var e="";for(var t=this.read;t<this.data.length;++t){var n=this.data.charCodeAt(t);n<16&&(e+="0"),e+=n.toString(16)}return e},t.ByteBuffer.prototype.toString=function(){return t.decodeUtf8(this.bytes())},t.createBuffer=function(e,n){return n=n||"raw",e!==undefined&&n==="utf8"&&(e=t.encodeUtf8(e)),new t.ByteBuffer(e)},t.fillString=function(e,t){var n="";while(t>0)t&1&&(n+=e),t>>>=1,t>0&&(e+=e);return n},t.xorBytes=function(e,t,n){var r="",i="",s="",o=0,u=0;for(;n>0;--n,++o)i=e.charCodeAt(o)^t.charCodeAt(o),u>=10&&(r+=s,s="",u=0),s+=String.fromCharCode(i),++u;return r+=s,r},t.hexToBytes=function(e){var t="",n=0;e.length&!0&&(n=1,t+=String.fromCharCode(parseInt(e[0],16)));for(;n<e.length;n+=2)t+=String.fromCharCode(parseInt(e.substr(n,2),16));return t},t.bytesToHex=function(e){return t.createBuffer(e).toHex()},t.int32ToBytes=function(e){return String.fromCharCode(e>>24&255)+String.fromCharCode(e>>16&255)+String.fromCharCode(e>>8&255)+String.fromCharCode(e&255)};var n="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",r=[62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,64,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51];t.encode64=function(e,t){var r="",i="",s,o,u,a=0;while(a<e.length)s=e.charCodeAt(a++),o=e.charCodeAt(a++),u=e.charCodeAt(a++),r+=n.charAt(s>>2),r+=n.charAt((s&3)<<4|o>>4),isNaN(o)?r+="==":(r+=n.charAt((o&15)<<2|u>>6),r+=isNaN(u)?"=":n.charAt(u&63)),t&&r.length>t&&(i+=r.substr(0,t)+"\r\n",r=r.substr(t));return i+=r,i},t.decode64=function(e){e=e.replace(/[^A-Za-z0-9\+\/\=]/g,"");var t="",n,i,s,o,u=0;while(u<e.length)n=r[e.charCodeAt(u++)-43],i=r[e.charCodeAt(u++)-43],s=r[e.charCodeAt(u++)-43],o=r[e.charCodeAt(u++)-43],t+=String.fromCharCode(n<<2|i>>4),s!==64&&(t+=String.fromCharCode((i&15)<<4|s>>2),o!==64&&(t+=String.fromCharCode((s&3)<<6|o)));return t},t.encodeUtf8=function(e){return unescape(encodeURIComponent(e))},t.decodeUtf8=function(e){return decodeURIComponent(escape(e))},t.deflate=function(e,n,r){n=t.decode64(e.deflate(t.encode64(n)).rval);if(r){var i=2,s=n.charCodeAt(1);s&32&&(i=6),n=n.substring(i,n.length-4)}return n},t.inflate=function(e,n,r){var i=e.inflate(t.encode64(n)).rval;return i===null?null:t.decode64(i)};var i=function(e,n,r){if(!e)throw{message:"WebStorage not available."};var i;r===null?i=e.removeItem(n):(r=t.encode64(JSON.stringify(r)),i=e.setItem(n,r));if(typeof i!="undefined"&&i.rval!==!0)throw i.error},s=function(e,n){if(!e)throw{message:"WebStorage not available."};var r=e.getItem(n);if(e.init)if(r.rval===null){if(r.error)throw r.error;r=null}else r=r.rval;return r!==null&&(r=JSON.parse(t.decode64(r))),r},o=function(e,t,n,r){var o=s(e,t);o===null&&(o={}),o[n]=r,i(e,t,o)},u=function(e,t,n){var r=s(e,t);return r!==null&&(r=n in r?r[n]:null),r},a=function(e,t,n){var r=s(e,t);if(r!==null&&n in r){delete r[n];var o=!0;for(var u in r){o=!1;break}o&&(r=null),i(e,t,r)}},f=function(e,t){i(e,t,null)},l=function(e,t,n){var r=null;typeof n=="undefined"&&(n=["web","flash"]);var i,s=!1,o=null;for(var u in n){i=n[u];try{if(i==="flash"||i==="both"){if(t[0]===null)throw{message:"Flash local storage not available."};r=e.apply(this,t),s=i==="flash"}if(i==="web"||i==="both")t[0]=localStorage,r=e.apply(this,t),s=!0}catch(a){o=a}if(s)break}if(!s)throw o;return r};t.setItem=function(e,t,n,r,i){l(o,arguments,i)},t.getItem=function(e,t,n,r){return l(u,arguments,r)},t.removeItem=function(e,t,n,r){l(a,arguments,r)},t.clearItems=function(e,t,n){l(f,arguments,n)},t.parseUrl=function(e){var t=/^(https?):\/\/([^:&^\/]*):?(\d*)(.*)$/g;t.lastIndex=0;var n=t.exec(e),r=n===null?null:{full:e,scheme:n[1],host:n[2],port:n[3],path:n[4]};return r&&(r.fullHost=r.host,r.port?r.port!==80&&r.scheme==="http"?r.fullHost+=":"+r.port:r.port!==443&&r.scheme==="https"&&(r.fullHost+=":"+r.port):r.scheme==="http"?r.port=80:r.scheme==="https"&&(r.port=443),r.full=r.scheme+"://"+r.fullHost),r};var c=null;t.getQueryVariables=function(e){var t=function(e){var t={},n=e.split("&");for(var r=0;r<n.length;r++){var i=n[r].indexOf("="),s,o;i>0?(s=n[r].substring(0,i),o=n[r].substring(i+1)):(s=n[r],o=null),s in t||(t[s]=[]),!(s in Object.prototype)&&o!==null&&t[s].push(unescape(o))}return t},n;return typeof e=="undefined"?(c===null&&(typeof window=="undefined"?c={}:c=t(window.location.search.substring(1))),n=c):n=t(e),n},t.parseFragment=function(e){var n=e,r="",i=e.indexOf("?");i>0&&(n=e.substring(0,i),r=e.substring(i+1));var s=n.split("/");s.length>0&&s[0]===""&&s.shift();var o=r===""?{}:t.getQueryVariables(r);return{pathString:n,queryString:r,path:s,query:o}},t.makeRequest=function(e){var n=t.parseFragment(e),r={path:n.pathString,query:n.queryString,getPath:function(e){return typeof e=="undefined"?n.path:n.path[e]},getQuery:function(e,t){var r;return typeof e=="undefined"?r=n.query:(r=n.query[e],r&&typeof t!="undefined"&&(r=r[t])),r},getQueryLast:function(e,t){var n,i=r.getQuery(e);return i?n=i[i.length-1]:n=t,n}};return r},t.makeLink=function(e,t,n){e=jQuery.isArray(e)?e.join("/"):e;var r=jQuery.param(t||{});return n=n||"",e+(r.length>0?"?"+r:"")+(n.length>0?"#"+n:"")},t.setPath=function(e,t,n){if(typeof e=="object"&&e!==null){var r=0,i=t.length;while(r<i){var s=t[r++];if(r==i)e[s]=n;else{var o=s in e;if(!o||o&&typeof e[s]!="object"||o&&e[s]===null)e[s]={};e=e[s]}}}},t.getPath=function(e,t,n){var r=0,i=t.length,s=!0;while(s&&r<i&&typeof e=="object"&&e!==null){var o=t[r++];s=o in e,s&&(e=e[o])}return s?e:n},t.deletePath=function(e,t){if(typeof e=="object"&&e!==null){var n=0,r=t.length;while(n<r){var i=t[n++];if(n==r)delete e[i];else{if(!(i in e&&typeof e[i]=="object"&&e[i]!==null))break;e=e[i]}}}},t.isEmpty=function(e){for(var t in e)if(e.hasOwnProperty(t))return!1;return!0},t.format=function(e){var t=/%./g,n,r,i=0,s=[],o=0;while(n=t.exec(e)){r=e.substring(o,t.lastIndex-2),r.length>0&&s.push(r),o=t.lastIndex;var u=n[0][1];switch(u){case"s":case"o":i<arguments.length?s.push(arguments[i++ +1]):s.push("<?>");break;case"%":s.push("%");break;default:s.push("<%"+u+"?>")}}return s.push(e.substring(o)),s.join("")},t.formatNumber=function(e,t,n,r){var i=e,s=isNaN(t=Math.abs(t))?2:t,o=n===undefined?",":n,u=r===undefined?".":r,a=i<0?"-":"",f=parseInt(i=Math.abs(+i||0).toFixed(s),10)+"",l=f.length>3?f.length%3:0;return a+(l?f.substr(0,l)+u:"")+f.substr(l).replace(/(\d{3})(?=\d)/g,"$1"+u)+(s?o+Math.abs(i-f).toFixed(s).slice(2):"")},t.formatSize=function(e){return e>=1073741824?e=t.formatNumber(e/1073741824,2,".","")+" GiB":e>=1048576?e=t.formatNumber(e/1048576,2,".","")+" MiB":e>=1024?e=t.formatNumber(e/1024,0)+" KiB":e=t.formatNumber(e,0)+" bytes",e}}var t="util";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/util",["require","module"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})})(),function(){function e(e,t){describe("util",function(){it("should put bytes into a buffer",function(){var n=t.createBuffer();n.putByte(1),n.putByte(2),n.putByte(3),n.putByte(4),n.putInt32(4),n.putByte(1),n.putByte(2),n.putByte(3),n.putInt32(4294967295);var r=n.toHex();e.equal(r,"0102030400000004010203ffffffff");var i=[];while(n.length()>0)i.push(n.getByte());e.deepEqual(i,[1,2,3,4,0,0,0,4,1,2,3,255,255,255,255])}),it("should convert bytes from hex",function(){var n="0102030400000004010203ffffffff",r=t.createBuffer();r.putBytes(t.hexToBytes(n)),e.equal(r.toHex(),n)}),it("should base64 encode some bytes",function(){var n="00010203050607080A0B0C0D0F1011121415161719",r="MDAwMTAyMDMwNTA2MDcwODBBMEIwQzBEMEYxMDExMTIxNDE1MTYxNzE5";e.equal(t.encode64(n),r)}),it("should base64 decode some bytes",function(){var n="00010203050607080A0B0C0D0F1011121415161719",r="MDAwMTAyMDMwNTA2MDcwODBBMEIwQzBEMEYxMDExMTIxNDE1MTYxNzE5";e.equal(t.decode64(r),n)})})}typeof define=="function"?define("test/util",["forge/util"],function(t){e(ASSERT,t())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/util")())}(),function(){function e(e){var t=e.md5=e.md5||{};e.md=e.md||{},e.md.algorithms=e.md.algorithms||{},e.md.md5=e.md.algorithms.md5=t;var n=null,r=null,i=null,s=null,o=!1,u=function(){n=String.fromCharCode(128),n+=e.util.fillString(String.fromCharCode(0),64),r=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,1,6,11,0,5,10,15,4,9,14,3,8,13,2,7,12,5,8,11,14,1,4,7,10,13,0,3,6,9,12,15,2,0,7,14,5,12,3,10,1,8,15,6,13,4,11,2,9],i=[7,12,17,22,7,12,17,22,7,12,17,22,7,12,17,22,5,9,14,20,5,9,14,20,5,9,14,20,5,9,14,20,4,11,16,23,4,11,16,23,4,11,16,23,4,11,16,23,6,10,15,21,6,10,15,21,6,10,15,21,6,10,15,21],s=new Array(64);for(var t=0;t<64;++t)s[t]=Math.floor(Math.abs(Math.sin(t+1))*4294967296);o=!0},a=function(e,t,n){var o,u,a,f,l,c,h,p,d=n.length();while(d>=64){u=e.h0,a=e.h1,f=e.h2,l=e.h3;for(p=0;p<16;++p)t[p]=n.getInt32Le(),c=l^a&(f^l),o=u+c+s[p]+t[p],h=i[p],u=l,l=f,f=a,a+=o<<h|o>>>32-h;for(;p<32;++p)c=f^l&(a^f),o=u+c+s[p]+t[r[p]],h=i[p],u=l,l=f,f=a,a+=o<<h|o>>>32-h;for(;p<48;++p)c=a^f^l,o=u+c+s[p]+t[r[p]],h=i[p],u=l,l=f,f=a,a+=o<<h|o>>>32-h;for(;p<64;++p)c=f^(a|~l),o=u+c+s[p]+t[r[p]],h=i[p],u=l,l=f,f=a,a+=o<<h|o>>>32-h;e.h0=e.h0+u&4294967295,e.h1=e.h1+a&4294967295,e.h2=e.h2+f&4294967295,e.h3=e.h3+l&4294967295,d-=64}};t.create=function(){o||u();var t=null,r=e.util.createBuffer(),i=new Array(16),s={algorithm:"md5",blockLength:64,digestLength:16,messageLength:0};return s.start=function(){return s.messageLength=0,r=e.util.createBuffer(),t={h0:1732584193,h1:4023233417,h2:2562383102,h3:271733878},s},s.start(),s.update=function(n,o){return o==="utf8"&&(n=e.util.encodeUtf8(n)),s.messageLength+=n.length,r.putBytes(n),a(t,i,r),(r.read>2048||r.length()===0)&&r.compact(),s},s.digest=function(){var o=s.messageLength,u=e.util.createBuffer();u.putBytes(r.bytes()),u.putBytes(n.substr(0,64-(o+8)%64)),u.putInt32Le(o<<3&4294967295),u.putInt32Le(o>>>29&255);var f={h0:t.h0,h1:t.h1,h2:t.h2,h3:t.h3};a(f,i,u);var l=e.util.createBuffer();return l.putInt32Le(f.h0),l.putInt32Le(f.h1),l.putInt32Le(f.h2),l.putInt32Le(f.h3),l},s}}var t="md5";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/md5",["require","module","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e,t,n){describe("md5",function(){it("should digest the empty string",function(){var n=t.create();e.equal(n.digest().toHex(),"d41d8cd98f00b204e9800998ecf8427e")}),it('should digest "abc"',function(){var n=t.create();n.update("abc"),e.equal(n.digest().toHex(),"900150983cd24fb0d6963f7d28e17f72")}),it('should digest "The quick brown fox jumps over the lazy dog"',function(){var n=t.create();n.update("The quick brown fox jumps over the lazy dog"),e.equal(n.digest().toHex(),"9e107d9d372bb6826bd81d3542a419d6")}),it('should digest "c\'è"',function(){var n=t.create();n.update("c'è","utf8"),e.equal(n.digest().toHex(),"8ef7c2941d78fe89f31e614437c9db59")}),it('should digest "THIS IS A MESSAGE"',function(){var n=t.create();n.start(),n.update("THIS IS "),n.update("A MESSAGE"),e.equal(n.digest().toHex(),"78eebfd9d42958e3f31244f116ab7bbe"),e.equal(n.digest().toHex(),"78eebfd9d42958e3f31244f116ab7bbe")}),it("should digest a long message",function(){var r=n.hexToBytes("0100002903018d32e9c6dc423774c4c39a5a1b78f44cc2cab5f676d39f703d29bfa27dfeb870000002002f01000200004603014c2c1e835d39da71bc0857eb04c2b50fe90dbb2a8477fe7364598d6f0575999c20a6c7248c5174da6d03ac711888f762fc4ed54f7254b32273690de849c843073d002f000b0003d20003cf0003cc308203c8308202b0a003020102020100300d06092a864886f70d0101050500308186310b3009060355040613025553311d301b060355040a13144469676974616c2042617a6161722c20496e632e31443042060355040b133b4269746d756e6b206c6f63616c686f73742d6f6e6c7920436572746966696361746573202d20417574686f72697a6174696f6e207669612042545031123010060355040313096c6f63616c686f7374301e170d3130303231343137303931395a170d3230303231333137303931395a308186310b3009060355040613025553311d301b060355040a13144469676974616c2042617a6161722c20496e632e31443042060355040b133b4269746d756e6b206c6f63616c686f73742d6f6e6c7920436572746966696361746573202d20417574686f72697a6174696f6e207669612042545031123010060355040313096c6f63616c686f737430820122300d06092a864886f70d01010105000382010f003082010a0282010100dc436f17d6909d8a9d6186ea218eb5c86b848bae02219bd56a71203daf07e81bc19e7e98134136bcb012881864bf03b3774652ad5eab85dba411a5114ffeac09babce75f31314345512cd87c91318b2e77433270a52185fc16f428c3ca412ad6e9484bc2fb87abb4e8fb71bf0f619e31a42340b35967f06c24a741a31c979c0bb8921a90a47025fbeb8adca576979e70a56830c61170c9647c18c0794d68c0df38f3aac5fc3b530e016ea5659715339f3f3c209cdee9dbe794b5af92530c5754c1d874b78974bfad994e0dfc582275e79feb522f6e4bcc2b2945baedfb0dbdaebb605f9483ff0bea29ecd5f4d6f2769965d1b3e04f8422716042680011ff676f0203010001a33f303d300c0603551d130101ff04023000300e0603551d0f0101ff0404030204f0301d0603551d250416301406082b0601050507030106082b06010505070302300d06092a864886f70d010105050003820101009c4562be3f2d8d8e388085a697f2f106eaeff4992a43f198fe3dcf15c8229cf1043f061a38204f73d86f4fb6348048cc5279ed719873aa10e3773d92b629c2c3fcce04012c81ba3b4ec451e9644ec5191078402d845e05d02c7b4d974b4588276e5037aba7ef26a8bddeb21e10698c82f425e767dc401adf722fa73ab78cfa069bd69052d7ca6a75cc9225550e315d71c5f8764362ea4dbc6ecb837a8471043c5a7f826a71af145a053090bd4bccca6a2c552841cdb1908a8352f49283d2e641acdef667c7543af441a16f8294251e2ac376fa507b53ae418dd038cd20cef1e7bfbf5ae03a7c88d93d843abaabbdc5f3431132f3e559d2dd414c3eda38a210b80e00000010000102010026a220b7be857402819b78d81080d01a682599bbd00902985cc64edf8e520e4111eb0e1729a14ffa3498ca259cc9ad6fc78fa130d968ebdb78dc0b950c0aa44355f13ba678419185d7e4608fe178ca6b2cef33e4193778d1a70fe4d0dfcb110be4bbb4dbaa712177655728f914ab4c0f6c4aef79a46b3d996c82b2ebe9ed1748eb5cace7dc44fb67e73f452a047f2ed199b3d50d5db960acf03244dc8efa4fc129faf8b65f9e52e62b5544722bd17d2358e817a777618a4265a3db277fc04851a82a91fe6cdcb8127f156e0b4a5d1f54ce2742eb70c895f5f8b85f5febe69bc73e891f9280826860a0c2ef94c7935e6215c3c4cd6b0e43e80cca396d913d36be"),i=t.create();i.update(r),e.equal(i.digest().toHex(),"d15a2da0e92c3da55dc573f885b6e653")})})}typeof define=="function"?define("test/md5",["forge/md5","forge/util"],function(t,n){e(ASSERT,t(),n())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/md5")(),require("../../js/util")())}(),function(){function e(e){var t=e.sha1=e.sha1||{};e.md=e.md||{},e.md.algorithms=e.md.algorithms||{},e.md.sha1=e.md.algorithms.sha1=t;var n=null,r=!1,i=function(){n=String.fromCharCode(128),n+=e.util.fillString(String.fromCharCode(0),64),r=!0},s=function(e,t,n){var r,i,s,o,u,a,f,l,c=n.length();while(c>=64){i=e.h0,s=e.h1,o=e.h2,u=e.h3,a=e.h4;for(l=0;l<16;++l)r=n.getInt32(),t[l]=r,f=u^s&(o^u),r=(i<<5|i>>>27)+f+a+1518500249+r,a=u,u=o,o=s<<30|s>>>2,s=i,i=r;for(;l<20;++l)r=t[l-3]^t[l-8]^t[l-14]^t[l-16],r=r<<1|r>>>31,t[l]=r,f=u^s&(o^u),r=(i<<5|i>>>27)+f+a+1518500249+r,a=u,u=o,o=s<<30|s>>>2,s=i,i=r;for(;l<32;++l)r=t[l-3]^t[l-8]^t[l-14]^t[l-16],r=r<<1|r>>>31,t[l]=r,f=s^o^u,r=(i<<5|i>>>27)+f+a+1859775393+r,a=u,u=o,o=s<<30|s>>>2,s=i,i=r;for(;l<40;++l)r=t[l-6]^t[l-16]^t[l-28]^t[l-32],r=r<<2|r>>>30,t[l]=r,f=s^o^u,r=(i<<5|i>>>27)+f+a+1859775393+r,a=u,u=o,o=s<<30|s>>>2,s=i,i=r;for(;l<60;++l)r=t[l-6]^t[l-16]^t[l-28]^t[l-32],r=r<<2|r>>>30,t[l]=r,f=s&o|u&(s^o),r=(i<<5|i>>>27)+f+a+2400959708+r,a=u,u=o,o=s<<30|s>>>2,s=i,i=r;for(;l<80;++l)r=t[l-6]^t[l-16]^t[l-28]^t[l-32],r=r<<2|r>>>30,t[l]=r,f=s^o^u,r=(i<<5|i>>>27)+f+a+3395469782+r,a=u,u=o,o=s<<30|s>>>2,s=i,i=r;e.h0+=i,e.h1+=s,e.h2+=o,e.h3+=u,e.h4+=a,c-=64}};t.create=function(){r||i();var t=null,o=e.util.createBuffer(),u=new Array(80),a={algorithm:"sha1",blockLength:64,digestLength:20,messageLength:0};return a.start=function(){return a.messageLength=0,o=e.util.createBuffer(),t={h0:1732584193,h1:4023233417,h2:2562383102,h3:271733878,h4:3285377520},a},a.start(),a.update=function(n,r){return r==="utf8"&&(n=e.util.encodeUtf8(n)),a.messageLength+=n.length,o.putBytes(n),s(t,u,o),(o.read>2048||o.length()===0)&&o.compact(),a},a.digest=function(){var r=a.messageLength,i=e.util.createBuffer();i.putBytes(o.bytes()),i.putBytes(n.substr(0,64-(r+8)%64)),i.putInt32(r>>>29&255),i.putInt32(r<<3&4294967295);var f={h0:t.h0,h1:t.h1,h2:t.h2,h3:t.h3,h4:t.h4};s(f,u,i);var l=e.util.createBuffer();return l.putInt32(f.h0),l.putInt32(f.h1),l.putInt32(f.h2),l.putInt32(f.h3),l.putInt32(f.h4),l},a}}var t="sha1";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/sha1",["require","module","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e,t,n){describe("sha1",function(){it("should digest the empty string",function(){var n=t.create();e.equal(n.digest().toHex(),"da39a3ee5e6b4b0d3255bfef95601890afd80709")}),it('should digest "abc"',function(){var n=t.create();n.update("abc"),e.equal(n.digest().toHex(),"a9993e364706816aba3e25717850c26c9cd0d89d")}),it('should digest "The quick brown fox jumps over the lazy dog"',function(){var n=t.create();n.update("The quick brown fox jumps over the lazy dog"),e.equal(n.digest().toHex(),"2fd4e1c67a2d28fced849ee1bb76e7391b93eb12")}),it('should digest "c\'è"',function(){var n=t.create();n.update("c'è","utf8"),e.equal(n.digest().toHex(),"98c9a3f804daa73b68a5660d032499a447350c0d")}),it('should digest "THIS IS A MESSAGE"',function(){var n=t.create();n.start(),n.update("THIS IS "),n.update("A MESSAGE"),e.equal(n.digest().toHex(),"5f24f4d6499fd2d44df6c6e94be8b14a796c071d"),e.equal(n.digest().toHex(),"5f24f4d6499fd2d44df6c6e94be8b14a796c071d")}),it("should digest a long message",function(){var r=t.create();r.update(n.fillString("a",1e6)),e.equal(r.digest().toHex(),"34aa973cd4c4daa4f61eeb2bdbad27316534016f")})})}typeof define=="function"?define("test/sha1",["forge/sha1","forge/util"],function(t,n){e(ASSERT,t(),n())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/sha1")(),require("../../js/util")())}(),function(){function e(e){var t=e.sha256=e.sha256||{};e.md=e.md||{},e.md.algorithms=e.md.algorithms||{},e.md.sha256=e.md.algorithms.sha256=t;var n=null,r=!1,i=null,s=function(){n=String.fromCharCode(128),n+=e.util.fillString(String.fromCharCode(0),64),i=[1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298],r=!0},o=function(e,t,n){var r,s,o,u,a,f,l,c,h,p,d,v,m,g,y,b=n.length();while(b>=64){for(l=0;l<16;++l)t[l]=n.getInt32();for(;l<64;++l)r=t[l-2],r=(r>>>17|r<<15)^(r>>>19|r<<13)^r>>>10,s=t[l-15],s=(s>>>7|s<<25)^(s>>>18|s<<14)^s>>>3,t[l]=r+t[l-7]+s+t[l-16]&4294967295;c=e.h0,h=e.h1,p=e.h2,d=e.h3,v=e.h4,m=e.h5,g=e.h6,y=e.h7;for(l=0;l<64;++l)u=(v>>>6|v<<26)^(v>>>11|v<<21)^(v>>>25|v<<7),a=g^v&(m^g),o=(c>>>2|c<<30)^(c>>>13|c<<19)^(c>>>22|c<<10),f=c&h|p&(c^h),r=y+u+a+i[l]+t[l],s=o+f,y=g,g=m,m=v,v=d+r&4294967295,d=p,p=h,h=c,c=r+s&4294967295;e.h0=e.h0+c&4294967295,e.h1=e.h1+h&4294967295,e.h2=e.h2+p&4294967295,e.h3=e.h3+d&4294967295,e.h4=e.h4+v&4294967295,e.h5=e.h5+m&4294967295,e.h6=e.h6+g&4294967295,e.h7=e.h7+y&4294967295,b-=64}};t.create=function(){r||s();var t=null,i=e.util.createBuffer(),u=new Array(64),a={algorithm:"sha256",blockLength:64,digestLength:32,messageLength:0};return a.start=function(){return a.messageLength=0,i=e.util.createBuffer(),t={h0:1779033703,h1:3144134277,h2:1013904242,h3:2773480762,h4:1359893119,h5:2600822924,h6:528734635,h7:1541459225},a},a.start(),a.update=function(n,r){return r==="utf8"&&(n=e.util.encodeUtf8(n)),a.messageLength+=n.length,i.putBytes(n),o(t,u,i),(i.read>2048||i.length()===0)&&i.compact(),a},a.digest=function(){var r=a.messageLength,s=e.util.createBuffer();s.putBytes(i.bytes()),s.putBytes(n.substr(0,64-(r+8)%64)),s.putInt32(r>>>29&255),s.putInt32(r<<3&4294967295);var f={h0:t.h0,h1:t.h1,h2:t.h2,h3:t.h3,h4:t.h4,h5:t.h5,h6:t.h6,h7:t.h7};o(f,u,s);var l=e.util.createBuffer();return l.putInt32(f.h0),l.putInt32(f.h1),l.putInt32(f.h2),l.putInt32(f.h3),l.putInt32(f.h4),l.putInt32(f.h5),l.putInt32(f.h6),l.putInt32(f.h7),l},a}}var t="sha256";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/sha256",["require","module","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e,t,n){describe("sha256",function(){it("should digest the empty string",function(){var n=t.create();e.equal(n.digest().toHex(),"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")}),it('should digest "abc"',function(){var n=t.create();n.update("abc"),e.equal(n.digest().toHex(),"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad")}),it('should digest "The quick brown fox jumps over the lazy dog"',function(){var n=t.create();n.update("The quick brown fox jumps over the lazy dog"),e.equal(n.digest().toHex(),"d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592")}),it('should digest "c\'è"',function(){var n=t.create();n.update("c'è","utf8"),e.equal(n.digest().toHex(),"1aa15c717afffd312acce2217ce1c2e5dabca53c92165999132ec9ca5decdaca")}),it('should digest "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"',function(){var n=t.create();n.start(),n.update("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"),e.equal(n.digest().toHex(),"248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1"),e.equal(n.digest().toHex(),"248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1")}),it("should digest a long message",function(){var r=t.create();r.update(n.fillString("a",1e6)),e.equal(r.digest().toHex(),"cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0")})})}typeof define=="function"?define("test/sha256",["forge/sha256","forge/util"],function(t,n){e(ASSERT,t(),n())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/sha256")(),require("../../js/util")())}(),function(){function e(e){e.md=e.md||{},e.md.algorithms={md5:e.md5,sha1:e.sha1,sha256:e.sha256},e.md.md5=e.md5,e.md.sha1=e.sha1,e.md.sha256=e.sha256}var t="md";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/md",["require","module","./md5","./sha1","./sha256"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){var t=e.hmac=e.hmac||{};t.create=function(){var t=null,n=null,r=null,i=null,s={};return s.start=function(s,o){if(s!==null)if(typeof s=="string"){s=s.toLowerCase();if(!(s in e.md.algorithms))throw'Unknown hash algorithm "'+s+'"';n=e.md.algorithms[s].create()}else n=s;if(o===null)o=t;else{if(typeof o=="string")o=e.util.createBuffer(o);else if(e.util.isArray(o)){var u=o;o=e.util.createBuffer();for(var a=0;a<u.length;++a)o.putByte(u[a])}var f=o.length();f>n.blockLength&&(n.start(),n.update(o.bytes()),o=n.digest()),r=e.util.createBuffer(),i=e.util.createBuffer(),f=o.length();for(var a=0;a<f;++a){var u=o.at(a);r.putByte(54^u),i.putByte(92^u)}if(f<n.blockLength){var u=n.blockLength-f;for(var a=0;a<u;++a)r.putByte(54),i.putByte(92)}t=o,r=r.bytes(),i=i.bytes()}n.start(),n.update(r)},s.update=function(e){n.update(e)},s.getMac=function(){var e=n.digest().bytes();return n.start(),n.update(i),n.update(e),n.digest()},s.digest=s.getMac,s}}var t="hmac";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/hmac",["require","module","./md","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e,t,n){describe("hmac",function(){it('should md5 hash "Hi There", 16-byte key',function(){var r=n.hexToBytes("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"),i=t.create();i.start("MD5",r),i.update("Hi There"),e.equal(i.digest().toHex(),"9294727a3638bb1c13f48ef8158bfc9d")}),it('should md5 hash "what do ya want for nothing?", "Jefe" key',function(){var n=t.create();n.start("MD5","Jefe"),n.update("what do ya want for nothing?"),e.equal(n.digest().toHex(),"750c783e6ab0b503eaa86e310a5db738")}),it('should md5 hash "Test Using Larger Than Block-Size Key - Hash Key First", 80-byte key',function(){var r=n.hexToBytes("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),i=t.create();i.start("MD5",r),i.update("Test Using Larger Than Block-Size Key - Hash Key First"),e.equal(i.digest().toHex(),"6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd")}),it('should sha1 hash "Hi There", 20-byte key',function(){var r=n.hexToBytes("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"),i=t.create();i.start("SHA1",r),i.update("Hi There"),e.equal(i.digest().toHex(),"b617318655057264e28bc0b6fb378c8ef146be00")}),it('should sha1 hash "what do ya want for nothing?", "Jefe" key',function(){var n=t.create();n.start("SHA1","Jefe"),n.update("what do ya want for nothing?"),e.equal(n.digest().toHex(),"effcdf6ae5eb2fa2d27416d5f184df9c259a7c79")}),it('should sha1 hash "Test Using Larger Than Block-Size Key - Hash Key First", 80-byte key',function(){var r=n.hexToBytes("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),i=t.create();i.start("SHA1",r),i.update("Test Using Larger Than Block-Size Key - Hash Key First"),e.equal(i.digest().toHex(),"aa4ae5e15272d00e95705637ce8a3b55ed402112")})})}typeof define=="function"?define("test/hmac",["forge/hmac","forge/util"],function(t,n){e(ASSERT,t(),n())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/hmac")(),require("../../js/util")())}(),function(){function e(e){var t=e.pkcs5=e.pkcs5||{};e.pbkdf2=t.pbkdf2=function(t,n,r,i,s){if(typeof s=="undefined"||s===null)s=e.md.sha1.create();var o=s.digestLength;if(i>4294967295*o)throw{message:"Derived key is too long."};var u=Math.ceil(i/o),a=i-(u-1)*o,f=e.hmac.create();f.start(s,t);var l="",c,h,p;for(var d=1;d<=u;++d){f.update(n),f.update(e.util.int32ToBytes(d)),c=p=f.digest().getBytes();for(var v=2;v<=r;++v)f.start(null,null),f.update(p),h=f.digest().getBytes(),c=e.util.xorBytes(c,h,o),p=h;l+=d<u?c:c.substr(0,a)}return l}}var t="pbkdf2";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/pbkdf2",["require","module","./hmac","./md","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e,t,n){describe("pbkdf2",function(){it("should derive a password with hmac-sha-1 c=1",function(){var r=n.bytesToHex(t("password","salt",1,20));e.equal(r,"0c60c80f961f0e71f3a9b524af6012062fe037a6")}),it("should derive a password with hmac-sha-1 c=2",function(){var r=n.bytesToHex(t("password","salt",2,20));e.equal(r,"ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957")}),it("should derive a password with hmac-sha-1 c=5 keylen=8",function(){var r=n.hexToBytes("1234567878563412"),i=n.bytesToHex(t("password",r,5,8));e.equal(i,"d1daa78615f287e6")}),it("should derive a password with hmac-sha-1 c=4096",function(){var r=n.bytesToHex(t("password","salt",4096,20));e.equal(r,"4b007901b765489abead49d926f721d065a429c1")})})}typeof define=="function"?define("test/pbkdf2",["forge/pbkdf2","forge/util"],function(t,n){e(ASSERT,t(),n())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/pbkdf2")(),require("../../js/util")())}(),function(){function e(e){e.mgf=e.mgf||{};var t=e.mgf.mgf1=e.mgf1=e.mgf1||{};t.create=function(t){var n={generate:function(n,r){var i=new e.util.ByteBuffer,s=Math.ceil(r/t.digestLength);for(var o=0;o<s;o++){var u=new e.util.ByteBuffer;u.putInt32(o),t.start(),t.update(n+u.getBytes()),i.putBuffer(t.digest())}return i.truncate(i.length()-r),i.getBytes()}};return n}}var t="mgf1";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/mgf1",["require","module","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){e.mgf=e.mgf||{},e.mgf.mgf1=e.mgf1}var t="mgf";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/mgf",["require","module","./mgf1"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e,t,n,r){describe("mgf1",function(){it("should digest the empty string",function(){var i=r.hexToBytes("032e45326fa859a72ec235acff929b15d1372e30b207255f0611b8f785d764374152e0ac009e509e7ba30cd2f1778e113b64e135cf4e2292c75efe5288edfda4"),s=r.hexToBytes("5f8de105b5e96b2e490ddecbd147dd1def7e3b8e0e6a26eb7b956ccb8b3bdc1ca975bc57c3989e8fbad31a224655d800c46954840ff32052cdf0d640562bdfadfa263cfccf3c52b29f2af4a1869959bc77f854cf15bd7a25192985a842dbff8e13efee5b7e7e55bbe4d389647c686a9a9ab3fb889b2d7767d3837eea4e0a2f04"),o=t.mgf1.create(n.sha1.create()),u=o.generate(i,s.length);e.equal(u,s)})})}typeof define=="function"?define("test/mgf1",["forge/mgf","forge/md","forge/util"],function(t,n,r){e(ASSERT,t(),n(),r())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/mgf")(),require("../../js/md")(),require("../../js/util")())}(),function(){function e(e){var t=!1,n=4,r,i,s,o,u,a=function(){t=!0,s=[0,1,2,4,8,16,32,64,128,27,54];var e=new Array(256);for(var n=0;n<128;++n)e[n]=n<<1,e[n+128]=n+128<<1^283;r=new Array(256),i=new Array(256),o=new Array(4),u=new Array(4);for(var n=0;n<4;++n)o[n]=new Array(256),u[n]=new Array(256);var a=0,f=0,l,c,h,p,d,v,m;for(var n=0;n<256;++n){p=f^f<<1^f<<2^f<<3^f<<4,p=p>>8^p&255^99,r[a]=p,i[p]=a,d=e[p],l=e[a],c=e[l],h=e[c],v=d<<24^p<<16^p<<8^(p^d),m=(l^c^h)<<24^(a^h)<<16^(a^c^h)<<8^(a^l^h);for(var g=0;g<4;++g)o[g][a]=v,u[g][p]=m,v=v<<24|v>>>8,m=m<<24|m>>>8;a===0?a=f=1:(a=l^e[e[e[l^h]]],f^=e[e[f]])}},f=function(e,t){var i=e.slice(0),o,a=1,f=i.length,l=f+6+1,c=n*l;for(var h=f;h<c;++h)o=i[h-1],h%f===0?(o=r[o>>>16&255]<<24^r[o>>>8&255]<<16^r[o&255]<<8^r[o>>>24]^s[a]<<24,a++):f>6&&h%f===4&&(o=r[o>>>24]<<24^r[o>>>16&255]<<16^r[o>>>8&255]<<8^r[o&255]),i[h]=i[h-f]^o;if(t){var p,d=u[0],v=u[1],m=u[2],g=u[3],y=i.slice(0),c=i.length;for(var h=0,b=c-n;h<c;h+=n,b-=n)if(h===0||h===c-n)y[h]=i[b],y[h+1]=i[b+3],y[h+2]=i[b+2],y[h+3]=i[b+1];else for(var w=0;w<n;++w)p=i[b+w],y[h+(3&-w)]=d[r[p>>>24]]^v[r[p>>>16&255]]^m[r[p>>>8&255]]^g[r[p&255]];i=y}return i},l=function(e,t,n,s){var a=e.length/4-1,f,l,c,h,p;s?(f=u[0],l=u[1],c=u[2],h=u[3],p=i):(f=o[0],l=o[1],c=o[2],h=o[3],p=r);var d,v,m,g,y,b,w;d=t[0]^e[0],v=t[s?3:1]^e[1],m=t[2]^e[2],g=t[s?1:3]^e[3];var E=3;for(var S=1;S<a;++S)y=f[d>>>24]^l[v>>>16&255]^c[m>>>8&255]^h[g&255]^e[++E],b=f[v>>>24]^l[m>>>16&255]^c[g>>>8&255]^h[d&255]^e[++E],w=f[m>>>24]^l[g>>>16&255]^c[d>>>8&255]^h[v&255]^e[++E],g=f[g>>>24]^l[d>>>16&255]^c[v>>>8&255]^h[m&255]^e[++E],d=y,v=b,m=w;n[0]=p[d>>>24]<<24^p[v>>>16&255]<<16^p[m>>>8&255]<<8^p[g&255]^e[++E],n[s?3:1]=p[v>>>24]<<24^p[m>>>16&255]<<16^p[g>>>8&255]<<8^p[d&255]^e[++E],n[2]=p[m>>>24]<<24^p[g>>>16&255]<<16^p[d>>>8&255]<<8^p[v&255]^e[++E],n[s?1:3]=p[g>>>24]<<24^p[d>>>16&255]<<16^p[v>>>8&255]<<8^p[m&255]^e[++E]},c=function(r,i,s,o,u){function C(){if(o)for(var e=0;e<n;++e)E[e]=b.getInt32();else for(var e=0;e<n;++e)E[e]=x[e]^b.getInt32();l(g,E,S,o);if(o){for(var e=0;e<n;++e)w.putInt32(x[e]^S[e]);x=E.slice(0)}else{for(var e=0;e<n;++e)w.putInt32(S[e]);x=S}}function k(){l(g,E,S,!1);for(var e=0;e<n;++e)E[e]=b.getInt32();for(var e=0;e<n;++e){var t=E[e]^S[e];o||(E[e]=t),w.putInt32(t)}}function L(){l(g,E,S,!1);for(var e=0;e<n;++e)E[e]=b.getInt32();for(var e=0;e<n;++e)w.putInt32(E[e]^S[e]),E[e]=S[e]}function A(){l(g,E,S,!1);for(var e=n-1;e>=0;--e){if(E[e]!==4294967295){++E[e];break}E[e]=0}for(var e=0;e<n;++e)w.putInt32(b.getInt32()^S[e])}var c=null;t||a(),u=(u||"CBC").toUpperCase();if(typeof r!="string"||r.length!==16&&r.length!==24&&r.length!==32){if(e.util.isArray(r)&&(r.length===16||r.length===24||r.length===32)){var h=r,r=e.util.createBuffer();for(var p=0;p<h.length;++p)r.putByte(h[p])}}else r=e.util.createBuffer(r);if(!e.util.isArray(r)){var h=r;r=[];var d=h.length();if(d===16||d===24||d===32){d>>>=2;for(var p=0;p<d;++p)r.push(h.getInt32())}}if(!e.util.isArray(r)||r.length!==4&&r.length!==6&&r.length!==8)return c;var v=["CFB","OFB","CTR"].indexOf(u)!==-1,m=u==="CBC",g=f(r,o&&!v),y=n<<2,b,w,E,S,x,T,N;c={output:null};if(u==="CBC")N=C;else if(u==="CFB")N=k;else if(u==="OFB")N=L;else{if(u!=="CTR")throw{message:'Unsupported block cipher mode of operation: "'+u+'"'};N=A}return c.update=function(e){T||b.putBuffer(e);while(b.length()>=y||b.length()>0&&T)N()},c.finish=function(e){var t=!0,r=b.length()%y;if(!o)if(e)t=e(y,b,o);else if(m){var i=b.length()===y?y:y-b.length();b.fillWithByte(i,i)}t&&(T=!0,c.update());if(o){m&&(t=r===0);if(t)if(e)t=e(y,w,o);else if(m){var s=w.length(),u=w.at(s-1);u>n<<2?t=!1:w.truncate(u)}}return!m&&!e&&r>0&&w.truncate(y-r),t},c.start=function(t,r){t===null&&(t=x.slice(0));if(typeof t=="string"&&t.length===16)t=e.util.createBuffer(t);else if(e.util.isArray(t)&&t.length===16){var i=t,t=e.util.createBuffer();for(var s=0;s<16;++s)t.putByte(i[s])}if(!e.util.isArray(t)){var i=t;t=new Array(4),t[0]=i.getInt32(),t[1]=i.getInt32(),t[2]=i.getInt32(),t[3]=i.getInt32()}b=e.util.createBuffer(),w=r||e.util.createBuffer(),x=t.slice(0),E=new Array(n),S=new Array(n),T=!1,c.output=w;if(["CFB","OFB","CTR"].indexOf(u)!==-1){for(var s=0;s<n;++s)E[s]=x[s];x=null}},i!==null&&c.start(i,s),c};e.aes=e.aes||{},e.aes.startEncrypting=function(e,t,n,r){return c(e,t,n,!1,r)},e.aes.createEncryptionCipher=function(e,t){return c(e,null,null,!1,t)},e.aes.startDecrypting=function(e,t,n,r){return c(e,t,n,!0,r)},e.aes.createDecryptionCipher=function(e,t){return c(e,null,null,!0,t)},e.aes._expandKey=function(e,n){return t||a(),f(e,n)},e.aes._updateBlock=l}var t="aes";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/aes",["require","module","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){var t=typeof process!="undefined"&&process.versions&&process.versions.node,n=null;!e.disableNativeCode&&t&&(n=require("crypto"));var r=e.prng=e.prng||{};r.create=function(t){function u(e){if(r.pools[0].messageLength>=32)return f(),e();var t=32-r.pools[0].messageLength<<5;r.seedFile(t,function(t,n){if(t)return e(t);r.collect(n),f(),e()})}function a(){if(r.pools[0].messageLength>=32)return f();var e=32-r.pools[0].messageLength<<5;r.collect(r.seedFileSync(e)),f()}function f(){var t=e.md.sha1.create();t.update(r.pools[0].digest().getBytes()),r.pools[0].start();var n=1;for(var i=1;i<32;++i)n=n===31?2147483648:n<<2,n%r.reseeds===0&&(t.update(r.pools[i].digest().getBytes()),r.pools[i].start());var s=t.digest().getBytes();t.start(),t.update(s);var o=t.digest().getBytes();r.key=r.plugin.formatKey(s),r.seed=r.plugin.formatSeed(o),++r.reseeds,r.generated=0,r.time=+(new Date)}function l(t){var n=e.util.createBuffer();if(typeof window!="undefined"&&window.crypto&&window.crypto.getRandomValues){var r=new Uint32Array(t/4);try{window.crypto.getRandomValues(r);for(var i=0;i<r.length;++i)n.putInt32(r[i])}catch(s){}}if(n.length()<t){var o,u,a,f=Math.floor(Math.random()*65535);while(n.length()<t){u=16807*(f&65535),o=16807*(f>>16),u+=(o&32767)<<16,u+=o>>15,u=(u&2147483647)+(u>>31),f=u&4294967295;for(var i=0;i<3;++i)a=f>>>(i<<3),a^=Math.floor(Math.random()*255),n.putByte(String.fromCharCode(a&255))}}return n.getBytes()}var r={plugin:t,key:null,seed:null,time:null,reseeds:0,generated:0},i=t.md,s=new Array(32);for(var o=0;o<32;++o)s[o]=i.create();return r.pools=s,r.pool=0,r.generate=function(t,n){function l(c){if(c)return n(c);if(f.length()>=t)return n(null,f.getBytes(t));if(r.generated>=1048576){var h=+(new Date);if(r.time===null||h-r.time>100)r.key=null}if(r.key===null)return u(l);var p=i(r.key,r.seed);r.generated+=p.length,f.putBytes(p),r.key=o(i(r.key,s(r.seed))),r.seed=a(i(r.key,r.seed)),e.util.setImmediate(l)}if(!n)return r.generateSync(t);var i=r.plugin.cipher,s=r.plugin.increment,o=r.plugin.formatKey,a=r.plugin.formatSeed,f=e.util.createBuffer();l()},r.generateSync=function(t){var n=r.plugin.cipher,i=r.plugin.increment,s=r.plugin.formatKey,o=r.plugin.formatSeed,u=e.util.createBuffer();while(u.length()<t){if(r.generated>=1048576){var f=+(new Date);if(r.time===null||f-r.time>100)r.key=null}r.key===null&&a();var l=n(r.key,r.seed);r.generated+=l.length,u.putBytes(l),r.key=s(n(r.key,i(r.seed))),r.seed=o(n(r.key,r.seed))}return u.getBytes(t)},n?(r.seedFile=function(e,t){n.randomBytes(e,function(e,n){if(e)return t(e);t(null,n.toString())})},r.seedFileSync=function(e){return n.randomBytes(e).toString()}):(r.seedFile=function(e,t){try{t(null,l(e))}catch(n){t(n)}},r.seedFileSync=l),r.collect=function(e){var t=e.length;for(var n=0;n<t;++n)r.pools[r.pool].update(e.substr(n,1)),r.pool=r.pool===31?0:r.pool+1},r.collectInt=function(e,t){var n="";for(var i=0;i<t;i+=8)n+=String.fromCharCode(e>>i&255);r.collect(n)},r.registerWorker=function(e){if(e===self)r.seedFile=function(e,t){function n(e){var r=e.data;r.forge&&r.forge.prng&&(self.removeEventListener("message",n),t(r.forge.prng.err,r.forge.prng.bytes))}self.addEventListener("message",n),self.postMessage({forge:{prng:{needed:e}}})};else{function t(t){var n=t.data;n.forge&&n.forge.prng&&r.seedFile(n.forge.prng.needed,function(t,n){e.postMessage({forge:{prng:{err:t,bytes:n}}})})}e.addEventListener("message",t)}},r}}var t="prng";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/prng",["require","module","./md","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){if(e.random&&e.random.getBytes)return;(function(t){var n={},r=new Array(4),i=e.util.createBuffer();n.formatKey=function(t){var n=e.util.createBuffer(t);return t=new Array(4),t[0]=n.getInt32(),t[1]=n.getInt32(),t[2]=n.getInt32(),t[3]=n.getInt32(),e.aes._expandKey(t,!1)},n.formatSeed=function(t){var n=e.util.createBuffer(t);return t=new Array(4),t[0]=n.getInt32(),t[1]=n.getInt32(),t[2]=n.getInt32(),t[3]=n.getInt32(),t},n.cipher=function(t,n){return e.aes._updateBlock(t,n,r,!1),i.putInt32(r[0]),i.putInt32(r[1]),i.putInt32(r[2]),i.putInt32(r[3]),i.getBytes()},n.increment=function(e){return++e[3],e},n.md=e.md.sha1;var s=e.prng.create(n),o=typeof process!="undefined"&&process.versions&&process.versions.node;if(e.disableNativeCode||!o&&(typeof window=="undefined"||!window.crypto||!window.crypto.getRandomValues)){typeof window=="undefined"||window.document===undefined,s.collectInt(+(new Date),32);if(typeof navigator!="undefined"){var u="";for(var a in navigator)try{typeof navigator[a]=="string"&&(u+=navigator[a])}catch(f){}s.collect(u),u=null}t&&(t().mousemove(function(e){s.collectInt(e.clientX,16),s.collectInt(e.clientY,16)}),t().keypress(function(e){s.collectInt(e.charCode,8)}))}if(!e.random)e.random=s;else for(var a in s)e.random[a]=s[a];e.random.getBytes=function(t,n){return e.random.generate(t,n)},e.random.getBytesSync=function(t){return e.random.generate(t)}})(typeof jQuery!="undefined"?jQuery:null)}var t="random";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/random",["require","module","./aes","./md","./prng","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e,t,n){var r=t();describe("random",function(){it("should generate 10 random bytes",function(){r.getBytes(16),r.getBytes(24),r.getBytes(32);var t=r.getBytes(10);e.equal(t.length,10)}),it("should use a synchronous seed file",function(){var r=t();r.seedFileSync=function(e){return n.fillString("a",e)};var i=r.getBytes(10);e.equal(n.bytesToHex(i),"a44857544b3df0fcac84")}),it("should use an asynchronous seed file",function(r){var i=t();i.seedFile=function(e,t){t(null,n.fillString("a",e))},i.getBytes(10,function(t,i){e.equal(t,null),e.equal(n.bytesToHex(i),"a44857544b3df0fcac84"),r()})}),it("should collect some random bytes",function(){var r=t();r.seedFileSync=function(e){return n.fillString("a",e)},r.collect("bbb");var i=r.getBytes(10);e.equal(n.bytesToHex(i),"8274fa6e0a192d670ddb")})})}typeof define=="function"?define("test/random",["forge/random","forge/util"],function(t,n){e(ASSERT,t,n())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/random"),require("../../js/util")())}(),function(){function e(e){e.pki=e.pki||{};var t=e.pki.oids=e.oids=e.oids||{};t["1.2.840.113549.1.1.1"]="rsaEncryption",t.rsaEncryption="1.2.840.113549.1.1.1",t["1.2.840.113549.1.1.4"]="md5WithRSAEncryption",t.md5WithRSAEncryption="1.2.840.113549.1.1.4",t["1.2.840.113549.1.1.5"]="sha1WithRSAEncryption",t.sha1WithRSAEncryption="1.2.840.113549.1.1.5",t["1.2.840.113549.1.1.7"]="RSAES-OAEP",t["RSAES-OAEP"]="1.2.840.113549.1.1.7",t["1.2.840.113549.1.1.8"]="mgf1",t.mgf1="1.2.840.113549.1.1.8",t["1.2.840.113549.1.1.9"]="pSpecified",t.pSpecified="1.2.840.113549.1.1.9",t["1.2.840.113549.1.1.10"]="RSASSA-PSS",t["RSASSA-PSS"]="1.2.840.113549.1.1.10",t["1.2.840.113549.1.1.11"]="sha256WithRSAEncryption",t.sha256WithRSAEncryption="1.2.840.113549.1.1.11",t["1.2.840.113549.1.1.12"]="sha384WithRSAEncryption",t.sha384WithRSAEncryption="1.2.840.113549.1.1.12",t["1.2.840.113549.1.1.13"]="sha512WithRSAEncryption",t.sha512WithRSAEncryption="1.2.840.113549.1.1.13",t["1.3.14.3.2.26"]="sha1",t.sha1="1.3.14.3.2.26",t["2.16.840.1.101.3.4.2.1"]="sha256",t.sha256="2.16.840.1.101.3.4.2.1",t["2.16.840.1.101.3.4.2.2"]="sha384",t.sha384="2.16.840.1.101.3.4.2.2",t["2.16.840.1.101.3.4.2.3"]="sha512",t.sha512="2.16.840.1.101.3.4.2.3",t["1.2.840.113549.2.5"]="md5",t.md5="1.2.840.113549.2.5",t["1.2.840.113549.1.7.1"]="data",t.data="1.2.840.113549.1.7.1",t["1.2.840.113549.1.7.2"]="signedData",t.signedData="1.2.840.113549.1.7.2",t["1.2.840.113549.1.7.3"]="envelopedData",t.envelopedData="1.2.840.113549.1.7.3",t["1.2.840.113549.1.7.4"]="signedAndEnvelopedData",t.signedAndEnvelopedData="1.2.840.113549.1.7.4",t["1.2.840.113549.1.7.5"]="digestedData",t.digestedData="1.2.840.113549.1.7.5",t["1.2.840.113549.1.7.6"]="encryptedData",t.encryptedData="1.2.840.113549.1.7.6",t["1.2.840.113549.1.9.1"]="emailAddress",t.emailAddress="1.2.840.113549.1.9.1",t["1.2.840.113549.1.9.2"]="unstructuredName",t.unstructuredName="1.2.840.113549.1.9.2",t["1.2.840.113549.1.9.3"]="contentType",t.contentType="1.2.840.113549.1.9.3",t["1.2.840.113549.1.9.4"]="messageDigest",t.messageDigest="1.2.840.113549.1.9.4",t["1.2.840.113549.1.9.5"]="signingTime",t.signingTime="1.2.840.113549.1.9.5",t["1.2.840.113549.1.9.6"]="counterSignature",t.counterSignature="1.2.840.113549.1.9.6",t["1.2.840.113549.1.9.7"]="challengePassword",t.challengePassword="1.2.840.113549.1.9.7",t["1.2.840.113549.1.9.8"]="unstructuredAddress",t.unstructuredAddress="1.2.840.113549.1.9.8",t["1.2.840.113549.1.9.20"]="friendlyName",t.friendlyName="1.2.840.113549.1.9.20",t["1.2.840.113549.1.9.21"]="localKeyId",t.localKeyId="1.2.840.113549.1.9.21",t["1.2.840.113549.1.9.22.1"]="x509Certificate",t.x509Certificate="1.2.840.113549.1.9.22.1",t["1.2.840.113549.1.12.10.1.1"]="keyBag",t.keyBag="1.2.840.113549.1.12.10.1.1",t["1.2.840.113549.1.12.10.1.2"]="pkcs8ShroudedKeyBag",t.pkcs8ShroudedKeyBag="1.2.840.113549.1.12.10.1.2",t["1.2.840.113549.1.12.10.1.3"]="certBag",t.certBag="1.2.840.113549.1.12.10.1.3",t["1.2.840.113549.1.12.10.1.4"]="crlBag",t.crlBag="1.2.840.113549.1.12.10.1.4",t["1.2.840.113549.1.12.10.1.5"]="secretBag",t.secretBag="1.2.840.113549.1.12.10.1.5",t["1.2.840.113549.1.12.10.1.6"]="safeContentsBag",t.safeContentsBag="1.2.840.113549.1.12.10.1.6",t["1.2.840.113549.1.5.13"]="pkcs5PBES2",t.pkcs5PBES2="1.2.840.113549.1.5.13",t["1.2.840.113549.1.5.12"]="pkcs5PBKDF2",t.pkcs5PBKDF2="1.2.840.113549.1.5.12",t["1.2.840.113549.1.12.1.1"]="pbeWithSHAAnd128BitRC4",t.pbeWithSHAAnd128BitRC4="1.2.840.113549.1.12.1.1",t["1.2.840.113549.1.12.1.2"]="pbeWithSHAAnd40BitRC4",t.pbeWithSHAAnd40BitRC4="1.2.840.113549.1.12.1.2",t["1.2.840.113549.1.12.1.3"]="pbeWithSHAAnd3-KeyTripleDES-CBC",t["pbeWithSHAAnd3-KeyTripleDES-CBC"]="1.2.840.113549.1.12.1.3",t["1.2.840.113549.1.12.1.4"]="pbeWithSHAAnd2-KeyTripleDES-CBC",t["pbeWithSHAAnd2-KeyTripleDES-CBC"]="1.2.840.113549.1.12.1.4",t["1.2.840.113549.1.12.1.5"]="pbeWithSHAAnd128BitRC2-CBC",t["pbeWithSHAAnd128BitRC2-CBC"]="1.2.840.113549.1.12.1.5",t["1.2.840.113549.1.12.1.6"]="pbewithSHAAnd40BitRC2-CBC",t["pbewithSHAAnd40BitRC2-CBC"]="1.2.840.113549.1.12.1.6",t["1.2.840.113549.3.7"]="des-EDE3-CBC",t["des-EDE3-CBC"]="1.2.840.113549.3.7",t["2.16.840.1.101.3.4.1.2"]="aes128-CBC",t["aes128-CBC"]="2.16.840.1.101.3.4.1.2",t["2.16.840.1.101.3.4.1.22"]="aes192-CBC",t["aes192-CBC"]="2.16.840.1.101.3.4.1.22",t["2.16.840.1.101.3.4.1.42"]="aes256-CBC",t["aes256-CBC"]="2.16.840.1.101.3.4.1.42",t["2.5.4.3"]="commonName",t.commonName="2.5.4.3",t["2.5.4.5"]="serialName",t.serialName="2.5.4.5",t["2.5.4.6"]="countryName",t.countryName="2.5.4.6",t["2.5.4.7"]="localityName",t.localityName="2.5.4.7",t["2.5.4.8"]="stateOrProvinceName",t.stateOrProvinceName="2.5.4.8",t["2.5.4.10"]="organizationName",t.organizationName="2.5.4.10",t["2.5.4.11"]="organizationalUnitName",t.organizationalUnitName="2.5.4.11",t["2.16.840.1.113730.1.1"]="nsCertType",t.nsCertType="2.16.840.1.113730.1.1",t["2.5.29.1"]="authorityKeyIdentifier",t["2.5.29.2"]="keyAttributes",t["2.5.29.3"]="certificatePolicies",t["2.5.29.4"]="keyUsageRestriction",t["2.5.29.5"]="policyMapping",t["2.5.29.6"]="subtreesConstraint",t["2.5.29.7"]="subjectAltName",t["2.5.29.8"]="issuerAltName",t["2.5.29.9"]="subjectDirectoryAttributes",t["2.5.29.10"]="basicConstraints",t["2.5.29.11"]="nameConstraints",t["2.5.29.12"]="policyConstraints",t["2.5.29.13"]="basicConstraints",t["2.5.29.14"]="subjectKeyIdentifier",t.subjectKeyIdentifier="2.5.29.14",t["2.5.29.15"]="keyUsage",t.keyUsage="2.5.29.15",t["2.5.29.16"]="privateKeyUsagePeriod",t["2.5.29.17"]="subjectAltName",t.subjectAltName="2.5.29.17",t["2.5.29.18"]="issuerAltName",t.issuerAltName="2.5.29.18",t["2.5.29.19"]="basicConstraints",t.basicConstraints="2.5.29.19",t["2.5.29.20"]="cRLNumber",t["2.5.29.21"]="cRLReason",t["2.5.29.22"]="expirationDate",t["2.5.29.23"]="instructionCode",t["2.5.29.24"]="invalidityDate",t["2.5.29.25"]="cRLDistributionPoints",t["2.5.29.26"]="issuingDistributionPoint",t["2.5.29.27"]="deltaCRLIndicator",t["2.5.29.28"]="issuingDistributionPoint",t["2.5.29.29"]="certificateIssuer",t["2.5.29.30"]="nameConstraints",t["2.5.29.31"]="cRLDistributionPoints",t["2.5.29.32"]="certificatePolicies",t["2.5.29.33"]="policyMappings",t["2.5.29.34"]="policyConstraints",t["2.5.29.35"]="authorityKeyIdentifier",t["2.5.29.36"]="policyConstraints",t["2.5.29.37"]="extKeyUsage",t.extKeyUsage="2.5.29.37",t["2.5.29.46"]="freshestCRL",t["2.5.29.54"]="inhibitAnyPolicy",t["1.3.6.1.5.5.7.3.1"]="serverAuth",t.serverAuth="1.3.6.1.5.5.7.3.1",t["1.3.6.1.5.5.7.3.2"]="clientAuth",t.clientAuth="1.3.6.1.5.5.7.3.2",t["1.3.6.1.5.5.7.3.3"]="codeSigning",t.codeSigning="1.3.6.1.5.5.7.3.3",t["1.3.6.1.5.5.7.3.4"]="emailProtection",t.emailProtection="1.3.6.1.5.5.7.3.4",t["1.3.6.1.5.5.7.3.8"]="timeStamping",t.timeStamping="1.3.6.1.5.5.7.3.8"}var t="oids";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/oids",["require","module"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){var t=e.asn1=e.asn1||{};t.Class={UNIVERSAL:0,APPLICATION:64,CONTEXT_SPECIFIC:128,PRIVATE:192},t.Type={NONE:0,BOOLEAN:1,INTEGER:2,BITSTRING:3,OCTETSTRING:4,NULL:5,OID:6,ODESC:7,EXTERNAL:8,REAL:9,ENUMERATED:10,EMBEDDED:11,UTF8:12,ROID:13,SEQUENCE:16,SET:17,PRINTABLESTRING:19,IA5STRING:22,UTCTIME:23,GENERALIZEDTIME:24,BMPSTRING:30},t.create=function(t,n,r,i){if(e.util.isArray(i)){var s=[];for(var o=0;o<i.length;++o)i[o]!==undefined&&s.push(i[o]);i=s}return{tagClass:t,type:n,constructed:r,composed:r||e.util.isArray(i),value:i}};var n=function(e){var t=e.getByte();if(t===128)return undefined;var n,r=t&128;return r?n=e.getInt((t&127)<<3):n=t,n};t.fromDer=function(r,i){i===undefined&&(i=!0),typeof r=="string"&&(r=e.util.createBuffer(r));if(r.length()<2)throw{message:"Too few bytes to parse DER.",bytes:r.length()};var s=r.getByte(),o=s&192,u=s&31,a=n(r);if(r.length()<a){if(i)throw{message:"Too few bytes to read ASN.1 value.",detail:r.length()+" < "+a};a=r.length()}var f,l=(s&32)===32,c=l;if(!c&&o===t.Class.UNIVERSAL&&u===t.Type.BITSTRING&&a>1){var h=r.read,p=r.getByte();if(p===0){s=r.getByte();var d=s&192;if(d===t.Class.UNIVERSAL||d===t.Class.CONTEXT_SPECIFIC)try{var v=n(r);c=v===a-(r.read-h),c&&(++h,--a)}catch(m){}}r.read=h}if(c){f=[];if(a===undefined)for(;;){if(r.bytes(2)===String.fromCharCode(0,0)){r.getBytes(2);break}f.push(t.fromDer(r,i))}else{var g=r.length();while(a>0)f.push(t.fromDer(r,i)),a-=g-r.length(),g=r.length()}}else{if(a===undefined)throw{message:"Non-constructed ASN.1 object of indefinite length."};if(u===t.Type.BMPSTRING){f="";for(var y=0;y<a;y+=2)f+=String.fromCharCode(r.getInt16())}else f=r.getBytes(a)}return t.create(o,u,l,f)},t.toDer=function(n){var r=e.util.createBuffer(),i=n.tagClass|n.type,s=e.util.createBuffer();if(n.composed){n.constructed?i|=32:s.putByte(0);for(var o=0;o<n.value.length;++o)n.value[o]!==undefined&&s.putBuffer(t.toDer(n.value[o]))}else if(n.type===t.Type.BMPSTRING)for(var o=0;o<n.value.length;++o)s.putInt16(n.value.charCodeAt(o));else s.putBytes(n.value);r.putByte(i);if(s.length()<=127)r.putByte(s.length()&127);else{var u=s.length(),a="";do a+=String.fromCharCode(u&255),u>>>=8;while(u>0);r.putByte(a.length|128);for(var o=a.length-1;o>=0;--o)r.putByte(a.charCodeAt(o))}return r.putBuffer(s),r},t.oidToDer=function(t){var n=t.split("."),r=e.util.createBuffer();r.putByte(40*parseInt(n[0],10)+parseInt(n[1],10));var i,s,o,u;for(var a=2;a<n.length;++a){i=!0,s=[],o=parseInt(n[a],10);do u=o&127,o>>>=7,i||(u|=128),s.push(u),i=!1;while(o>0);for(var f=s.length-1;f>=0;--f)r.putByte(s[f])}return r},t.derToOid=function(t){var n;typeof t=="string"&&(t=e.util.createBuffer(t));var r=t.getByte();n=Math.floor(r/40)+"."+r%40;var i=0;while(t.length()>0)r=t.getByte(),i<<=7,r&128?i+=r&127:(n+="."+(i+r),i=0);return n},t.utcTimeToDate=function(e){var t=new Date,n=parseInt(e.substr(0,2),10);n=n>=50?1900+n:2e3+n;var r=parseInt(e.substr(2,2),10)-1,i=parseInt(e.substr(4,2),10),s=parseInt(e.substr(6,2),10),o=parseInt(e.substr(8,2),10),u=0;if(e.length>11){var a=e.charAt(10),f=10;a!=="+"&&a!=="-"&&(u=parseInt(e.substr(10,2),10),f+=2)}t.setUTCFullYear(n,r,i),t.setUTCHours(s,o,u,0);if(f){a=e.charAt(f);if(a==="+"||a==="-"){var l=parseInt(e.substr(f+1,2),10),c=parseInt(e.substr(f+4,2),10),h=l*60+c;h*=6e4,a==="+"?t.setTime(+t-h):t.setTime(+t+h)}}return t},t.generalizedTimeToDate=function(e){var t=new Date,n=parseInt(e.substr(0,4),10),r=parseInt(e.substr(4,2),10)-1,i=parseInt(e.substr(6,2),10),s=parseInt(e.substr(8,2),10),o=parseInt(e.substr(10,2),10),u=parseInt(e.substr(12,2),10),a=0,f=0,l=!1;e.charAt(e.length-1)==="Z"&&(l=!0);var c=e.length-5,h=e.charAt(c);if(h==="+"||h==="-"){var p=parseInt(e.substr(c+1,2),10),d=parseInt(e.substr(c+4,2),10);f=p*60+d,f*=6e4,h==="+"&&(f*=-1),l=!0}return e.charAt(14)==="."&&(a=parseFloat(e.substr(14),10)*1e3),l?(t.setUTCFullYear(n,r,i),t.setUTCHours(s,o,u,a),t.setTime(+t+f)):(t.setFullYear(n,r,i),t.setHours(s,o,u,a)),t},t.dateToUtcTime=function(e){var t="",n=[];n.push((""+e.getUTCFullYear()).substr(2)),n.push(""+(e.getUTCMonth()+1)),n.push(""+e.getUTCDate()),n.push(""+e.getUTCHours()),n.push(""+e.getUTCMinutes()),n.push(""+e.getUTCSeconds());for(var r=0;r<n.length;++r)n[r].length<2&&(t+="0"),t+=n[r];return t+="Z",t},t.validate=function(n,r,i,s){var o=!1;if(n.tagClass!==r.tagClass&&typeof r.tagClass!="undefined"||n.type!==r.type&&typeof r.type!="undefined")s&&(n.tagClass!==r.tagClass&&s.push("["+r.name+"] "+'Expected tag class "'+r.tagClass+'", got "'+n.tagClass+'"'),n.type!==r.type&&s.push("["+r.name+"] "+'Expected type "'+r.type+'", got "'+n.type+'"'));else if(n.constructed===r.constructed||typeof r.constructed=="undefined"){o=!0;if(r.value&&e.util.isArray(r.value)){var u=0;for(var a=0;o&&a<r.value.length;++a)o=r.value[a].optional||!1,n.value[u]&&(o=t.validate(n.value[u],r.value[a],i,s),o?++u:r.value[a].optional&&(o=!0)),!o&&s&&s.push("["+r.name+"] "+'Tag class "'+r.tagClass+'", type "'+r.type+'" expected value length "'+r.value.length+'", got "'+n.value.length+'"')}o&&i&&(r.capture&&(i[r.capture]=n.value),r.captureAsn1&&(i[r.captureAsn1]=n))}else s&&s.push("["+r.name+"] "+'Expected constructed "'+r.constructed+'", got "'+n.constructed+'"');return o};var r=/[^\\u0000-\\u00ff]/;t.prettyPrint=function(n,i,s){var o="";i=i||0,s=s||2,i>0&&(o+="\n");var u="";for(var a=0;a<i*s;++a)u+=" ";o+=u+"Tag: ";switch(n.tagClass){case t.Class.UNIVERSAL:o+="Universal:";break;case t.Class.APPLICATION:o+="Application:";break;case t.Class.CONTEXT_SPECIFIC:o+="Context-Specific:";break;case t.Class.PRIVATE:o+="Private:"}if(n.tagClass===t.Class.UNIVERSAL){o+=n.type;switch(n.type){case t.Type.NONE:o+=" (None)";break;case t.Type.BOOLEAN:o+=" (Boolean)";break;case t.Type.BITSTRING:o+=" (Bit string)";break;case t.Type.INTEGER:o+=" (Integer)";break;case t.Type.OCTETSTRING:o+=" (Octet string)";break;case t.Type.NULL:o+=" (Null)";break;case t.Type.OID:o+=" (Object Identifier)";break;case t.Type.ODESC:o+=" (Object Descriptor)";break;case t.Type.EXTERNAL:o+=" (External or Instance of)";break;case t.Type.REAL:o+=" (Real)";break;case t.Type.ENUMERATED:o+=" (Enumerated)";break;case t.Type.EMBEDDED:o+=" (Embedded PDV)";break;case t.Type.UTF8:o+=" (UTF8)";break;case t.Type.ROID:o+=" (Relative Object Identifier)";break;case t.Type.SEQUENCE:o+=" (Sequence)";break;case t.Type.SET:o+=" (Set)";break;case t.Type.PRINTABLESTRING:o+=" (Printable String)";break;case t.Type.IA5String:o+=" (IA5String (ASCII))";break;case t.Type.UTCTIME:o+=" (UTC time)";break;case t.Type.GENERALIZEDTIME:o+=" (Generalized time)";break;case t.Type.BMPSTRING:o+=" (BMP String)"}}else o+=n.type;o+="\n",o+=u+"Constructed: "+n.constructed+"\n";if(n.composed){var f=0,l="";for(var a=0;a<n.value.length;++a)n.value[a]!==undefined&&(f+=1,l+=t.prettyPrint(n.value[a],i+1,s),a+1<n.value.length&&(l+=","));o+=u+"Sub values: "+f+l}else{o+=u+"Value: ";if(n.type===t.Type.OID){var c=t.derToOid(n.value);o+=c,e.pki&&e.pki.oids&&c in e.pki.oids&&(o+=" ("+e.pki.oids[c]+")")}else r.test(n.value)?o+="0x"+e.util.createBuffer(n.value,"utf8").toHex():n.value.length===0?o+="[null]":o+=n.value}return o}}var t="asn1";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/asn1",["require","module","./util","./oids"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e,t,n){describe("asn1",function(){it("should convert an OID to DER",function(){e.equal(t.oidToDer("1.2.840.113549").toHex(),"2a864886f70d")}),it("should convert an OID from DER",function(){var r=n.hexToBytes("2a864886f70d");e.equal(t.derToOid(r),"1.2.840.113549")}),function(){var n=[{"in":"20110223123400",out:129846444e4},{"in":"20110223123400.1",out:1298464440100},{"in":"20110223123400.123",out:1298464440123}];for(var r=0;r<n.length;++r){var i=n[r];it('should convert local generalized time "'+i.in+'" to a Date',function(){var n=t.generalizedTimeToDate(i.in),r=n.getTimezoneOffset()*6e4;e.equal(n.getTime(),i.out+r)})}}(),function(){var n=[{"in":"20110223123400Z",out:129846444e4},{"in":"20110223123400.1Z",out:1298464440100},{"in":"20110223123400.123Z",out:1298464440123},{"in":"20110223123400+0200",out:129845724e4},{"in":"20110223123400.1+0200",out:1298457240100},{"in":"20110223123400.123+0200",out:1298457240123},{"in":"20110223123400-0200",out:129847164e4},{"in":"20110223123400.1-0200",out:1298471640100},{"in":"20110223123400.123-0200",out:1298471640123}];for(var r=0;r<n.length;++r){var i=n[r];it('should convert utc generalized time "'+i.in+'" to a Date',function(){var n=t.generalizedTimeToDate(i.in);e.equal(n.getTime(),i.out)})}}(),function(){var n=[{"in":"1102231234Z",out:129846444e4},{"in":"1102231234+0200",out:129845724e4},{"in":"1102231234-0200",out:129847164e4},{"in":"110223123456Z",out:1298464496e3},{"in":"110223123456+0200",out:1298457296e3},{"in":"110223123456-0200",out:1298471696e3}];for(var r=0;r<n.length;++r){var i=n[r];it('should convert utc time "'+i.in+'" to a Date',function(){var n=t.utcTimeToDate(i.in);e.equal(n.getTime(),i.out)})}}()})}typeof define=="function"?define("test/asn1",["forge/asn1","forge/util"],function(t,n){e(ASSERT,t(),n())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/asn1")(),require("../../js/util")())}(),function(){function e(e){function n(e){var t=e.name+": ",n=[];for(var r=0;r<e.values.length;++r)n.push(e.values[r].replace(/^(\S+\r\n)/,function(e,t){return" "+t}));t+=n.join(",")+"\r\n";var i=0,s=-1;for(var r=0;r<t.length;++r,++i)if(i>65&&s!==-1){var o=t[s];o===","?(++s,t=t.substr(0,s)+"\r\n "+t.substr(s)):t=t.substr(0,s)+"\r\n"+o+t.substr(s+1),i=r-s-1,s=-1,++r}else if(t[r]===" "||t[r]==="	"||t[r]===",")s=r;return t}function r(e){return e.replace(/^\s+/,"")}var t=e.pem=e.pem||{};t.encode=function(t,r){r=r||{};var i="-----BEGIN "+t.type+"-----\r\n",s;t.procType&&(s={name:"Proc-Type",values:[String(t.procType.version),t.procType.type]},i+=n(s)),t.contentDomain&&(s={name:"Content-Domain",values:[t.contentDomain]},i+=n(s)),t.dekInfo&&(s={name:"DEK-Info",values:[t.dekInfo.algorithm]},t.dekInfo.parameters&&s.values.push(t.dekInfo.parameters),i+=n(s));if(t.headers)for(var o=0;o<t.headers.length;++o)i+=n(t.headers[o]);return t.procType&&(i+="\r\n"),i+=e.util.encode64(t.body,r.maxline||64)+"\r\n",i+="-----END "+t.type+"-----\r\n",i},t.decode=function(t){var n=[],i=/\s*-----BEGIN ([A-Z0-9- ]+)-----\r?\n([\x21-\x7e\s]+?(?:\r?\n\r?\n))?([:A-Za-z0-9+\/=\s]+?)-----END \1-----/g,s=/([\x21-\x7e]+):\s*([\x21-\x7e\s^:]+)/,o=/\r?\n/,u;for(;;){u=i.exec(t);if(!u)break;var a={type:u[1],procType:null,contentDomain:null,dekInfo:null,headers:[],body:e.util.decode64(u[3])};n.push(a);if(!u[2])continue;var f=u[2].split(o),l=0;while(u&&l<f.length){var c=f[l].replace(/\s+$/,"");for(var h=l+1;h<f.length;++h){var p=f[h];if(!/\s/.test(p[0]))break;c+=p,l=h}u=c.match(s);if(u){var d={name:u[1],values:[]},v=u[2].split(",");for(var m=0;m<v.length;++m)d.values.push(r(v[m]));if(!a.procType){if(d.name!=="Proc-Type")throw{message:'Invalid PEM formatted message. The first encapsulated header must be "Proc-Type".'};if(d.values.length!==2)throw{message:'Invalid PEM formatted message. The "Proc-Type" header must have two subfields.'};a.procType={version:v[0],type:v[1]}}else if(!a.contentDomain&&d.name==="Content-Domain")a.contentDomain=v[0]||"";else if(!a.dekInfo&&d.name==="DEK-Info"){if(d.values.length===0)throw{message:'Invalid PEM formatted message. The "DEK-Info" header must have at least one subfield.'};a.dekInfo={algorithm:v[0],parameters:v[1]||null}}else a.headers.push(d)}++l}if(a.procType==="ENCRYPTED"&&!a.dekInfo)throw{message:'Invalid PEM formatted message. The "DEK-Info" header must be present if "Proc-Type" is "ENCRYPTED".'}}if(n.length===0)throw{message:"Invalid PEM formatted message."};return n}}var t="pem";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/pem",["require","module","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e,t){var n="-----BEGIN PRIVACY-ENHANCED MESSAGE-----\r\nProc-Type: 4,ENCRYPTED\r\nContent-Domain: RFC822\r\nDEK-Info: DES-CBC,F8143EDE5960C597\r\nOriginator-ID-Symmetric: linn@zendia.enet.dec.com,,\r\nRecipient-ID-Symmetric: linn@zendia.enet.dec.com,ptf-kmc,3\r\nKey-Info: DES-ECB,RSA-MD2,9FD3AAD2F2691B9A,\r\n B70665BB9BF7CBCDA60195DB94F727D3\r\nRecipient-ID-Symmetric: pem-dev@tis.com,ptf-kmc,4\r\nKey-Info: DES-ECB,RSA-MD2,161A3F75DC82EF26,\r\n E2EF532C65CBCFF79F83A2658132DB47\r\n\r\nLLrHB0eJzyhP+/fSStdW8okeEnv47jxe7SJ/iN72ohNcUk2jHEUSoH1nvNSIWL9M\r\n8tEjmF/zxB+bATMtPjCUWbz8Lr9wloXIkjHUlBLpvXR0UrUzYbkNpk0agV2IzUpk\r\nJ6UiRRGcDSvzrsoK+oNvqu6z7Xs5Xfz5rDqUcMlK1Z6720dcBWGGsDLpTpSCnpot\r\ndXd/H5LMDWnonNvPCwQUHg==\r\n-----END PRIVACY-ENHANCED MESSAGE-----\r\n-----BEGIN PRIVACY-ENHANCED MESSAGE-----\r\nProc-Type: 4,ENCRYPTED\r\nContent-Domain: RFC822\r\nDEK-Info: DES-CBC,BFF968AA74691AC1\r\nOriginator-Certificate:\r\n MIIBlTCCAScCAWUwDQYJKoZIhvcNAQECBQAwUTELMAkGA1UEBhMCVVMxIDAeBgNV\r\n BAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMQ8wDQYDVQQLEwZCZXRhIDExDzAN\r\n BgNVBAsTBk5PVEFSWTAeFw05MTA5MDQxODM4MTdaFw05MzA5MDMxODM4MTZaMEUx\r\n CzAJBgNVBAYTAlVTMSAwHgYDVQQKExdSU0EgRGF0YSBTZWN1cml0eSwgSW5jLjEU\r\n MBIGA1UEAxMLVGVzdCBVc2VyIDEwWTAKBgRVCAEBAgICAANLADBIAkEAwHZHl7i+\r\n yJcqDtjJCowzTdBJrdAiLAnSC+CnnjOJELyuQiBgkGrgIh3j8/x0fM+YrsyF1u3F\r\n LZPVtzlndhYFJQIDAQABMA0GCSqGSIb3DQEBAgUAA1kACKr0PqphJYw1j+YPtcIq\r\n iWlFPuN5jJ79Khfg7ASFxskYkEMjRNZV/HZDZQEhtVaU7Jxfzs2wfX5byMp2X3U/\r\n 5XUXGx7qusDgHQGs7Jk9W8CW1fuSWUgN4w==\r\nKey-Info: RSA,\r\n I3rRIGXUGWAF8js5wCzRTkdhO34PTHdRZY9Tuvm03M+NM7fx6qc5udixps2Lng0+\r\n wGrtiUm/ovtKdinz6ZQ/aQ==\r\nIssuer-Certificate:\r\n MIIB3DCCAUgCAQowDQYJKoZIhvcNAQECBQAwTzELMAkGA1UEBhMCVVMxIDAeBgNV\r\n BAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMQ8wDQYDVQQLEwZCZXRhIDExDTAL\r\n BgNVBAsTBFRMQ0EwHhcNOTEwOTAxMDgwMDAwWhcNOTIwOTAxMDc1OTU5WjBRMQsw\r\n CQYDVQQGEwJVUzEgMB4GA1UEChMXUlNBIERhdGEgU2VjdXJpdHksIEluYy4xDzAN\r\n BgNVBAsTBkJldGEgMTEPMA0GA1UECxMGTk9UQVJZMHAwCgYEVQgBAQICArwDYgAw\r\n XwJYCsnp6lQCxYykNlODwutF/jMJ3kL+3PjYyHOwk+/9rLg6X65B/LD4bJHtO5XW\r\n cqAz/7R7XhjYCm0PcqbdzoACZtIlETrKrcJiDYoP+DkZ8k1gCk7hQHpbIwIDAQAB\r\n MA0GCSqGSIb3DQEBAgUAA38AAICPv4f9Gx/tY4+p+4DB7MV+tKZnvBoy8zgoMGOx\r\n dD2jMZ/3HsyWKWgSF0eH/AJB3qr9zosG47pyMnTf3aSy2nBO7CMxpUWRBcXUpE+x\r\n EREZd9++32ofGBIXaialnOgVUn0OzSYgugiQ077nJLDUj0hQehCizEs5wUJ35a5h\r\nMIC-Info: RSA-MD5,RSA,\r\n UdFJR8u/TIGhfH65ieewe2lOW4tooa3vZCvVNGBZirf/7nrgzWDABz8w9NsXSexv\r\n AjRFbHoNPzBuxwmOAFeA0HJszL4yBvhG\r\nRecipient-ID-Asymmetric:\r\n MFExCzAJBgNVBAYTAlVTMSAwHgYDVQQKExdSU0EgRGF0YSBTZWN1cml0eSwgSW5j\r\n LjEPMA0GA1UECxMGQmV0YSAxMQ8wDQYDVQQLEwZOT1RBUlk=,66\r\nKey-Info: RSA,\r\n O6BS1ww9CTyHPtS3bMLD+L0hejdvX6Qv1HK2ds2sQPEaXhX8EhvVphHYTjwekdWv\r\n 7x0Z3Jx2vTAhOYHMcqqCjA==\r\n\r\nqeWlj/YJ2Uf5ng9yznPbtD0mYloSwIuV9FRYx+gzY+8iXd/NQrXHfi6/MhPfPF3d\r\njIqCJAxvld2xgqQimUzoS1a4r7kQQ5c/Iua4LqKeq3ciFzEv/MbZhA==\r\n-----END PRIVACY-ENHANCED MESSAGE-----\r\n-----BEGIN RSA PRIVATE KEY-----\r\nMIIBPAIBAAJBALjXU+IdHkSkdBscgXf+EBoa55ruAIsU50uDFjFBkp+rWFt5AOGF\r\n9xL1/HNIby5M64BCw021nJTZKEOmXKdmzYsCAwEAAQJBAApyYRNOgf9vLAC8Q7T8\r\nbvyKuLxQ50b1D319EywFgLv1Yn0s/F9F+Rew6c04Q0pIqmuOGUM7z94ul/y5OlNJ\r\n2cECIQDveEW1ib2+787l7Y0tMeDzf/HQl4MAWdcxXWOeUFK+7QIhAMWZsukutEn9\r\n9/yqFMt8bL/dclfNn1IAgUL4+dMJ7zdXAiEAhaxGhVKxN28XuCOFhe/s2R/XdQ/O\r\nUZjU1bqCzDGcLvUCIGYmxu71Tg7SVFkyM/3eHPozKOFrU2m5CRnuTHhlMl2RAiEA\r\n0vhM5TEmmNWz0anPVabqDj9TA0z5MsDJQcn5NmO9xnw=\r\n-----END RSA PRIVATE KEY-----\r\n";describe("pem",function(){it("should decode and re-encode PEM messages",function(){var r=t.decode(n),i="";for(var s=0;s<r.length;++s)i+=t.encode(r[s]);e.equal(i,n)})})}typeof define=="function"?define("test/pem",["forge/pem"],function(t){e(ASSERT,t())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/pem")())}(),function(){function e(e){function f(e){var t=[0,4,536870912,536870916,65536,65540,536936448,536936452,512,516,536871424,536871428,66048,66052,536936960,536936964],n=[0,1,1048576,1048577,67108864,67108865,68157440,68157441,256,257,1048832,1048833,67109120,67109121,68157696,68157697],r=[0,8,2048,2056,16777216,16777224,16779264,16779272,0,8,2048,2056,16777216,16777224,16779264,16779272],i=[0,2097152,134217728,136314880,8192,2105344,134225920,136323072,131072,2228224,134348800,136445952,139264,2236416,134356992,136454144],s=[0,262144,16,262160,0,262144,16,262160,4096,266240,4112,266256,4096,266240,4112,266256],o=[0,1024,32,1056,0,1024,32,1056,33554432,33555456,33554464,33555488,33554432,33555456,33554464,33555488],u=[0,268435456,524288,268959744,2,268435458,524290,268959746,0,268435456,524288,268959744,2,268435458,524290,268959746],a=[0,65536,2048,67584,536870912,536936448,536872960,536938496,131072,196608,133120,198656,537001984,537067520,537004032,537069568],f=[0,262144,0,262144,2,262146,2,262146,33554432,33816576,33554432,33816576,33554434,33816578,33554434,33816578],l=[0,268435456,8,268435464,0,268435456,8,268435464,1024,268436480,1032,268436488,1024,268436480,1032,268436488],c=[0,32,0,32,1048576,1048608,1048576,1048608,8192,8224,8192,8224,1056768,1056800,1056768,1056800],h=[0,16777216,512,16777728,2097152,18874368,2097664,18874880,67108864,83886080,67109376,83886592,69206016,85983232,69206528,85983744],p=[0,4096,134217728,134221824,524288,528384,134742016,134746112,16,4112,134217744,134221840,524304,528400,134742032,134746128],d=[0,4,256,260,0,4,256,260,1,5,257,261,1,5,257,261],v=e.length()>8?3:1,m=[],g=[0,0,1,1,1,1,1,1,0,1,1,1,1,1,1,0],y=0,b;for(var w=0;w<v;w++){var E=e.getInt32(),S=e.getInt32();b=(E>>>4^S)&252645135,S^=b,E^=b<<4,b=(S>>>-16^E)&65535,E^=b,S^=b<<-16,b=(E>>>2^S)&858993459,S^=b,E^=b<<2,b=(S>>>-16^E)&65535,E^=b,S^=b<<-16,b=(E>>>1^S)&1431655765,S^=b,E^=b<<1,b=(S>>>8^E)&16711935,E^=b,S^=b<<8,b=(E>>>1^S)&1431655765,S^=b,E^=b<<1,b=E<<8|S>>>20&240,E=S<<24|S<<8&16711680|S>>>8&65280|S>>>24&240,S=b;for(var x=0;x<g.length;x++){g[x]?(E=E<<2|E>>>26,S=S<<2|S>>>26):(E=E<<1|E>>>27,S=S<<1|S>>>27),E&=-15,S&=-15;var T=t[E>>>28]|n[E>>>24&15]|r[E>>>20&15]|i[E>>>16&15]|s[E>>>12&15]|o[E>>>8&15]|u[E>>>4&15],N=a[S>>>28]|f[S>>>24&15]|l[S>>>20&15]|c[S>>>16&15]|h[S>>>12&15]|p[S>>>8&15]|d[S>>>4&15];b=(N>>>16^T)&65535,m[y++]=T^b,m[y++]=N^b<<16}}return m}var t=[16843776,0,65536,16843780,16842756,66564,4,65536,1024,16843776,16843780,1024,16778244,16842756,16777216,4,1028,16778240,16778240,66560,66560,16842752,16842752,16778244,65540,16777220,16777220,65540,0,1028,66564,16777216,65536,16843780,4,16842752,16843776,16777216,16777216,1024,16842756,65536,66560,16777220,1024,4,16778244,66564,16843780,65540,16842752,16778244,16777220,1028,66564,16843776,1028,16778240,16778240,0,65540,66560,0,16842756],n=[-2146402272,-2147450880,32768,1081376,1048576,32,-2146435040,-2147450848,-2147483616,-2146402272,-2146402304,-2147483648,-2147450880,1048576,32,-2146435040,1081344,1048608,-2147450848,0,-2147483648,32768,1081376,-2146435072,1048608,-2147483616,0,1081344,32800,-2146402304,-2146435072,32800,0,1081376,-2146435040,1048576,-2147450848,-2146435072,-2146402304,32768,-2146435072,-2147450880,32,-2146402272,1081376,32,32768,-2147483648,32800,-2146402304,1048576,-2147483616,1048608,-2147450848,-2147483616,1048608,1081344,0,-2147450880,32800,-2147483648,-2146435040,-2146402272,1081344],r=[520,134349312,0,134348808,134218240,0,131592,134218240,131080,134217736,134217736,131072,134349320,131080,134348800,520,134217728,8,134349312,512,131584,134348800,134348808,131592,134218248,131584,131072,134218248,8,134349320,512,134217728,134349312,134217728,131080,520,131072,134349312,134218240,0,512,131080,134349320,134218240,134217736,512,0,134348808,134218248,131072,134217728,134349320,8,131592,131584,134217736,134348800,134218248,520,134348800,131592,8,134348808,131584],i=[8396801,8321,8321,128,8396928,8388737,8388609,8193,0,8396800,8396800,8396929,129,0,8388736,8388609,1,8192,8388608,8396801,128,8388608,8193,8320,8388737,1,8320,8388736,8192,8396928,8396929,129,8388736,8388609,8396800,8396929,129,0,0,8396800,8320,8388736,8388737,1,8396801,8321,8321,128,8396929,129,1,8192,8388609,8193,8396928,8388737,8193,8320,8388608,8396801,128,8388608,8192,8396928],s=[256,34078976,34078720,1107296512,524288,256,1073741824,34078720,1074266368,524288,33554688,1074266368,1107296512,1107820544,524544,1073741824,33554432,1074266112,1074266112,0,1073742080,1107820800,1107820800,33554688,1107820544,1073742080,0,1107296256,34078976,33554432,1107296256,524544,524288,1107296512,256,33554432,1073741824,34078720,1107296512,1074266368,33554688,1073741824,1107820544,34078976,1074266368,256,33554432,1107820544,1107820800,524544,1107296256,1107820800,34078720,0,1074266112,1107296256,524544,33554688,1073742080,524288,0,1074266112,34078976,1073742080],o=[536870928,541065216,16384,541081616,541065216,16,541081616,4194304,536887296,4210704,4194304,536870928,4194320,536887296,536870912,16400,0,4194320,536887312,16384,4210688,536887312,16,541065232,541065232,0,4210704,541081600,16400,4210688,541081600,536870912,536887296,16,541065232,4210688,541081616,4194304,16400,536870928,4194304,536887296,536870912,16400,536870928,541081616,4210688,541065216,4210704,541081600,0,541065232,16,16384,541065216,4210704,16384,4194320,536887312,0,541081600,536870912,4194320,536887312],u=[2097152,69206018,67110914,0,2048,67110914,2099202,69208064,69208066,2097152,0,67108866,2,67108864,69206018,2050,67110912,2099202,2097154,67110912,67108866,69206016,69208064,2097154,69206016,2048,2050,69208066,2099200,2,67108864,2099200,67108864,2099200,2097152,67110914,67110914,69206018,69206018,2,2097154,67108864,67110912,2097152,69208064,2050,2099202,69208064,2050,67108866,69208066,69206016,2099200,0,2,69208066,0,2099202,69206016,2048,67108866,67110912,2048,2097154],a=[268439616,4096,262144,268701760,268435456,268439616,64,268435456,262208,268697600,268701760,266240,268701696,266304,4096,64,268697600,268435520,268439552,4160,266240,262208,268697664,268701696,4160,0,0,268697664,268435520,268439552,266304,262144,266304,262144,268701696,4096,64,268697664,4096,266304,268439552,64,268435520,268697600,268697664,268435456,262144,268439616,0,268701760,262208,268435520,268697600,268439552,268439616,0,268701760,266240,266240,4160,4160,262208,268435456,268701696],l=function(l,c){typeof l=="string"&&(l.length===8||l.length===24)&&(l=e.util.createBuffer(l));var h=f(l),p=1,d=0,v=0,m=0,g=0,y=!1,b=null,w=null,E=h.length===32?3:9,S;E===3?S=c?[0,32,2]:[30,-2,-2]:S=c?[0,32,2,62,30,-2,64,96,2]:[94,62,-2,32,64,2,30,-2,-2];var x=null;return x={start:function(t,n){t?(typeof t=="string"&&t.length===8&&(t=e.util.createBuffer(t)),p=1,d=t.getInt32(),m=t.getInt32()):p=0,y=!1,b=e.util.createBuffer(),w=n||e.util.createBuffer(),x.output=w},update:function(e){y||b.putBuffer(e);while(b.length()>=8){var f,l=b.getInt32(),x=b.getInt32();p===1&&(c?(l^=d,x^=m):(v=d,g=m,d=l,m=x)),f=(l>>>4^x)&252645135,x^=f,l^=f<<4,f=(l>>>16^x)&65535,x^=f,l^=f<<16,f=(x>>>2^l)&858993459,l^=f,x^=f<<2,f=(x>>>8^l)&16711935,l^=f,x^=f<<8,f=(l>>>1^x)&1431655765,x^=f,l^=f<<1,l=l<<1|l>>>31,x=x<<1|x>>>31;for(var T=0;T<E;T+=3){var N=S[T+1],C=S[T+2];for(var k=S[T];k!=N;k+=C){var L=x^h[k],A=(x>>>4|x<<28)^h[k+1];f=l,l=x,x=f^(n[L>>>24&63]|i[L>>>16&63]|o[L>>>8&63]|a[L&63]|t[A>>>24&63]|r[A>>>16&63]|s[A>>>8&63]|u[A&63])}f=l,l=x,x=f}l=l>>>1|l<<31,x=x>>>1|x<<31,f=(l>>>1^x)&1431655765,x^=f,l^=f<<1,f=(x>>>8^l)&16711935,l^=f,x^=f<<8,f=(x>>>2^l)&858993459,l^=f,x^=f<<2,f=(l>>>16^x)&65535,x^=f,l^=f<<16,f=(l>>>4^x)&252645135,x^=f,l^=f<<4,p===1&&(c?(d=l,m=x):(l^=v,x^=g)),w.putInt32(l),w.putInt32(x)}},finish:function(e){var t=!0;if(c)if(e)t=e(8,b,!c);else{var n=b.length()===8?8:8-b.length();b.fillWithByte(n,n)}t&&(y=!0,x.update());if(!c){t=b.length()===0;if(t)if(e)t=e(8,w,!c);else{var r=w.length(),i=w.at(r-1);i>r?t=!1:w.truncate(i)}}return t}},x};e.des=e.des||{},e.des.startEncrypting=function(e,t,n){var r=l(e,!0);return r.start(t,n),r},e.des.createEncryptionCipher=function(e){return l(e,!0)},e.des.startDecrypting=function(e,t,n){var r=l(e,!1);return r.start(t,n),r},e.des.createDecryptionCipher=function(e){return l(e,!1)}}var t="des";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/des",["require","module","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){function i(e,t,n){this.data=[],e!=null&&("number"==typeof e?this.fromNumber(e,t,n):t==null&&"string"!=typeof e?this.fromString(e,256):this.fromString(e,t))}function s(){return new i(null)}function o(e,t,n,r,i,s){while(--s>=0){var o=t*this.data[e++]+n.data[r]+i;i=Math.floor(o/67108864),n.data[r++]=o&67108863}return i}function u(e,t,n,r,i,s){var o=t&32767,u=t>>15;while(--s>=0){var a=this.data[e]&32767,f=this.data[e++]>>15,l=u*a+f*o;a=o*a+((l&32767)<<15)+n.data[r]+(i&1073741823),i=(a>>>30)+(l>>>15)+u*f+(i>>>30),n.data[r++]=a&1073741823}return i}function a(e,t,n,r,i,s){var o=t&16383,u=t>>14;while(--s>=0){var a=this.data[e]&16383,f=this.data[e++]>>14,l=u*a+f*o;a=o*a+((l&16383)<<14)+n.data[r]+i,i=(a>>28)+(l>>14)+u*f,n.data[r++]=a&268435455}return i}function d(e){return l.charAt(e)}function v(e,t){var n=c[e.charCodeAt(t)];return n==null?-1:n}function m(e){for(var t=this.t-1;t>=0;--t)e.data[t]=this.data[t];e.t=this.t,e.s=this.s}function g(e){this.t=1,this.s=e<0?-1:0,e>0?this.data[0]=e:e<-1?this.data[0]=e+DV:this.t=0}function y(e){var t=s();return t.fromInt(e),t}function b(e,t){var n;if(t==16)n=4;else if(t==8)n=3;else if(t==256)n=8;else if(t==2)n=1;else if(t==32)n=5;else{if(t!=4){this.fromRadix(e,t);return}n=2}this.t=0,this.s=0;var r=e.length,s=!1,o=0;while(--r>=0){var u=n==8?e[r]&255:v(e,r);if(u<0){e.charAt(r)=="-"&&(s=!0);continue}s=!1,o==0?this.data[this.t++]=u:o+n>this.DB?(this.data[this.t-1]|=(u&(1<<this.DB-o)-1)<<o,this.data[this.t++]=u>>this.DB-o):this.data[this.t-1]|=u<<o,o+=n,o>=this.DB&&(o-=this.DB)}n==8&&(e[0]&128)!=0&&(this.s=-1,o>0&&(this.data[this.t-1]|=(1<<this.DB-o)-1<<o)),this.clamp(),s&&i.ZERO.subTo(this,this)}function w(){var e=this.s&this.DM;while(this.t>0&&this.data[this.t-1]==e)--this.t}function E(e){if(this.s<0)return"-"+this.negate().toString(e);var t;if(e==16)t=4;else if(e==8)t=3;else if(e==2)t=1;else if(e==32)t=5;else{if(e!=4)return this.toRadix(e);t=2}var n=(1<<t)-1,r,i=!1,s="",o=this.t,u=this.DB-o*this.DB%t;if(o-->0){u<this.DB&&(r=this.data[o]>>u)>0&&(i=!0,s=d(r));while(o>=0)u<t?(r=(this.data[o]&(1<<u)-1)<<t-u,r|=this.data[--o]>>(u+=this.DB-t)):(r=this.data[o]>>(u-=t)&n,u<=0&&(u+=this.DB,--o)),r>0&&(i=!0),i&&(s+=d(r))}return i?s:"0"}function S(){var e=s();return i.ZERO.subTo(this,e),e}function x(){return this.s<0?this.negate():this}function T(e){var t=this.s-e.s;if(t!=0)return t;var n=this.t;t=n-e.t;if(t!=0)return this.s<0?-t:t;while(--n>=0)if((t=this.data[n]-e.data[n])!=0)return t;return 0}function N(e){var t=1,n;return(n=e>>>16)!=0&&(e=n,t+=16),(n=e>>8)!=0&&(e=n,t+=8),(n=e>>4)!=0&&(e=n,t+=4),(n=e>>2)!=0&&(e=n,t+=2),(n=e>>1)!=0&&(e=n,t+=1),t}function C(){return this.t<=0?0:this.DB*(this.t-1)+N(this.data[this.t-1]^this.s&this.DM)}function k(e,t){var n;for(n=this.t-1;n>=0;--n)t.data[n+e]=this.data[n];for(n=e-1;n>=0;--n)t.data[n]=0;t.t=this.t+e,t.s=this.s}function L(e,t){for(var n=e;n<this.t;++n)t.data[n-e]=this.data[n];t.t=Math.max(this.t-e,0),t.s=this.s}function A(e,t){var n=e%this.DB,r=this.DB-n,i=(1<<r)-1,s=Math.floor(e/this.DB),o=this.s<<n&this.DM,u;for(u=this.t-1;u>=0;--u)t.data[u+s+1]=this.data[u]>>r|o,o=(this.data[u]&i)<<n;for(u=s-1;u>=0;--u)t.data[u]=0;t.data[s]=o,t.t=this.t+s+1,t.s=this.s,t.clamp()}function O(e,t){t.s=this.s;var n=Math.floor(e/this.DB);if(n>=this.t){t.t=0;return}var r=e%this.DB,i=this.DB-r,s=(1<<r)-1;t.data[0]=this.data[n]>>r;for(var o=n+1;o<this.t;++o)t.data[o-n-1]|=(this.data[o]&s)<<i,t.data[o-n]=this.data[o]>>r;r>0&&(t.data[this.t-n-1]|=(this.s&s)<<i),t.t=this.t-n,t.clamp()}function M(e,t){var n=0,r=0,i=Math.min(e.t,this.t);while(n<i)r+=this.data[n]-e.data[n],t.data[n++]=r&this.DM,r>>=this.DB;if(e.t<this.t){r-=e.s;while(n<this.t)r+=this.data[n],t.data[n++]=r&this.DM,r>>=this.DB;r+=this.s}else{r+=this.s;while(n<e.t)r-=e.data[n],t.data[n++]=r&this.DM,r>>=this.DB;r-=e.s}t.s=r<0?-1:0,r<-1?t.data[n++]=this.DV+r:r>0&&(t.data[n++]=r),t.t=n,t.clamp()}function _(e,t){var n=this.abs(),r=e.abs(),s=n.t;t.t=s+r.t;while(--s>=0)t.data[s]=0;for(s=0;s<r.t;++s)t.data[s+n.t]=n.am(0,r.data[s],t,s,0,n.t);t.s=0,t.clamp(),this.s!=e.s&&i.ZERO.subTo(t,t)}function D(e){var t=this.abs(),n=e.t=2*t.t;while(--n>=0)e.data[n]=0;for(n=0;n<t.t-1;++n){var r=t.am(n,t.data[n],e,2*n,0,1);(e.data[n+t.t]+=t.am(n+1,2*t.data[n],e,2*n+1,r,t.t-n-1))>=t.DV&&(e.data[n+t.t]-=t.DV,e.data[n+t.t+1]=1)}e.t>0&&(e.data[e.t-1]+=t.am(n,t.data[n],e,2*n,0,1)),e.s=0,e.clamp()}function P(e,t,n){var r=e.abs();if(r.t<=0)return;var o=this.abs();if(o.t<r.t){t!=null&&t.fromInt(0),n!=null&&this.copyTo(n);return}n==null&&(n=s());var u=s(),a=this.s,f=e.s,l=this.DB-N(r.data[r.t-1]);l>0?(r.lShiftTo(l,u),o.lShiftTo(l,n)):(r.copyTo(u),o.copyTo(n));var c=u.t,h=u.data[c-1];if(h==0)return;var p=h*(1<<this.F1)+(c>1?u.data[c-2]>>this.F2:0),d=this.FV/p,v=(1<<this.F1)/p,m=1<<this.F2,g=n.t,y=g-c,b=t==null?s():t;u.dlShiftTo(y,b),n.compareTo(b)>=0&&(n.data[n.t++]=1,n.subTo(b,n)),i.ONE.dlShiftTo(c,b),b.subTo(u,u);while(u.t<c)u.data[u.t++]=0;while(--y>=0){var w=n.data[--g]==h?this.DM:Math.floor(n.data[g]*d+(n.data[g-1]+m)*v);if((n.data[g]+=u.am(0,w,n,y,0,c))<w){u.dlShiftTo(y,b),n.subTo(b,n);while(n.data[g]<--w)n.subTo(b,n)}}t!=null&&(n.drShiftTo(c,t),a!=f&&i.ZERO.subTo(t,t)),n.t=c,n.clamp(),l>0&&n.rShiftTo(l,n),a<0&&i.ZERO.subTo(n,n)}function H(e){var t=s();return this.abs().divRemTo(e,null,t),this.s<0&&t.compareTo(i.ZERO)>0&&e.subTo(t,t),t}function B(e){this.m=e}function j(e){return e.s<0||e.compareTo(this.m)>=0?e.mod(this.m):e}function F(e){return e}function I(e){e.divRemTo(this.m,null,e)}function q(e,t,n){e.multiplyTo(t,n),this.reduce(n)}function R(e,t){e.squareTo(t),this.reduce(t)}function U(){if(this.t<1)return 0;var e=this.data[0];if((e&1)==0)return 0;var t=e&3;return t=t*(2-(e&15)*t)&15,t=t*(2-(e&255)*t)&255,t=t*(2-((e&65535)*t&65535))&65535,t=t*(2-e*t%this.DV)%this.DV,t>0?this.DV-t:-t}function z(e){this.m=e,this.mp=e.invDigit(),this.mpl=this.mp&32767,this.mph=this.mp>>15,this.um=(1<<e.DB-15)-1,this.mt2=2*e.t}function W(e){var t=s();return e.abs().dlShiftTo(this.m.t,t),t.divRemTo(this.m,null,t),e.s<0&&t.compareTo(i.ZERO)>0&&this.m.subTo(t,t),t}function X(e){var t=s();return e.copyTo(t),this.reduce(t),t}function V(e){while(e.t<=this.mt2)e.data[e.t++]=0;for(var t=0;t<this.m.t;++t){var n=e.data[t]&32767,r=n*this.mpl+((n*this.mph+(e.data[t]>>15)*this.mpl&this.um)<<15)&e.DM;n=t+this.m.t,e.data[n]+=this.m.am(0,r,e,t,0,this.m.t);while(e.data[n]>=e.DV)e.data[n]-=e.DV,e.data[++n]++}e.clamp(),e.drShiftTo(this.m.t,e),e.compareTo(this.m)>=0&&e.subTo(this.m,e)}function $(e,t){e.squareTo(t),this.reduce(t)}function J(e,t,n){e.multiplyTo(t,n),this.reduce(n)}function K(){return(this.t>0?this.data[0]&1:this.s)==0}function Q(e,t){if(e>4294967295||e<1)return i.ONE;var n=s(),r=s(),o=t.convert(this),u=N(e)-1;o.copyTo(n);while(--u>=0){t.sqrTo(n,r);if((e&1<<u)>0)t.mulTo(r,o,n);else{var a=n;n=r,r=a}}return t.revert(n)}function G(e,t){var n;return e<256||t.isEven()?n=new B(t):n=new z(t),this.exp(e,n)}function Y(){var e=s();return this.copyTo(e),e}function Z(){if(this.s<0){if(this.t==1)return this.data[0]-this.DV;if(this.t==0)return-1}else{if(this.t==1)return this.data[0];if(this.t==0)return 0}return(this.data[1]&(1<<32-this.DB)-1)<<this.DB|this.data[0]}function et(){return this.t==0?this.s:this.data[0]<<24>>24}function tt(){return this.t==0?this.s:this.data[0]<<16>>16}function nt(e){return Math.floor(Math.LN2*this.DB/Math.log(e))}function rt(){return this.s<0?-1:this.t<=0||this.t==1&&this.data[0]<=0?0:1}function it(e){e==null&&(e=10);if(this.signum()==0||e<2||e>36)return"0";var t=this.chunkSize(e),n=Math.pow(e,t),r=y(n),i=s(),o=s(),u="";this.divRemTo(r,i,o);while(i.signum()>0)u=(n+o.intValue()).toString(e).substr(1)+u,i.divRemTo(r,i,o);return o.intValue().toString(e)+u}function st(e,t){this.fromInt(0),t==null&&(t=10);var n=this.chunkSize(t),r=Math.pow(t,n),s=!1,o=0,u=0;for(var a=0;a<e.length;++a){var f=v(e,a);if(f<0){e.charAt(a)=="-"&&this.signum()==0&&(s=!0);continue}u=t*u+f,++o>=n&&(this.dMultiply(r),this.dAddOffset(u,0),o=0,u=0)}o>0&&(this.dMultiply(Math.pow(t,o)),this.dAddOffset(u,0)),s&&i.ZERO.subTo(this,this)}function ot(e,t,n){if("number"==typeof t)if(e<2)this.fromInt(1);else{this.fromNumber(e,n),this.testBit(e-1)||this.bitwiseTo(i.ONE.shiftLeft(e-1),dt,this),this.isEven()&&this.dAddOffset(1,0);while(!this.isProbablePrime(t))this.dAddOffset(2,0),this.bitLength()>e&&this.subTo(i.ONE.shiftLeft(e-1),this)}else{var r=new Array,s=e&7;r.length=(e>>3)+1,t.nextBytes(r),s>0?r[0]&=(1<<s)-1:r[0]=0,this.fromString(r,256)}}function ut(){var e=this.t,t=new Array;t[0]=this.s;var n=this.DB-e*this.DB%8,r,i=0;if(e-->0){n<this.DB&&(r=this.data[e]>>n)!=(this.s&this.DM)>>n&&(t[i++]=r|this.s<<this.DB-n);while(e>=0){n<8?(r=(this.data[e]&(1<<n)-1)<<8-n,r|=this.data[--e]>>(n+=this.DB-8)):(r=this.data[e]>>(n-=8)&255,n<=0&&(n+=this.DB,--e)),(r&128)!=0&&(r|=-256),i==0&&(this.s&128)!=(r&128)&&++i;if(i>0||r!=this.s)t[i++]=r}}return t}function at(e){return this.compareTo(e)==0}function ft(e){return this.compareTo(e)<0?this:e}function lt(e){return this.compareTo(e)>0?this:e}function ct(e,t,n){var r,i,s=Math.min(e.t,this.t);for(r=0;r<s;++r)n.data[r]=t(this.data[r],e.data[r]);if(e.t<this.t){i=e.s&this.DM;for(r=s;r<this.t;++r)n.data[r]=t(this.data[r],i);n.t=this.t}else{i=this.s&this.DM;for(r=s;r<e.t;++r)n.data[r]=t(i,e.data[r]);n.t=e.t}n.s=t(this.s,e.s),n.clamp()}function ht(e,t){return e&t}function pt(e){var t=s();return this.bitwiseTo(e,ht,t),t}function dt(e,t){return e|t}function vt(e){var t=s();return this.bitwiseTo(e,dt,t),t}function mt(e,t){return e^t}function gt(e){var t=s();return this.bitwiseTo(e,mt,t),t}function yt(e,t){return e&~t}function bt(e){var t=s();return this.bitwiseTo(e,yt,t),t}function wt(){var e=s();for(var t=0;t<this.t;++t)e.data[t]=this.DM&~this.data[t];return e.t=this.t,e.s=~this.s,e}function Et(e){var t=s();return e<0?this.rShiftTo(-e,t):this.lShiftTo(e,t),t}function St(e){var t=s();return e<0?this.lShiftTo(-e,t):this.rShiftTo(e,t),t}function xt(e){if(e==0)return-1;var t=0;return(e&65535)==0&&(e>>=16,t+=16),(e&255)==0&&(e>>=8,t+=8),(e&15)==0&&(e>>=4,t+=4),(e&3)==0&&(e>>=2,t+=2),(e&1)==0&&++t,t}function Tt(){for(var e=0;e<this.t;++e)if(this.data[e]!=0)return e*this.DB+xt(this.data[e]);return this.s<0?this.t*this.DB:-1}function Nt(e){var t=0;while(e!=0)e&=e-1,++t;return t}function Ct(){var e=0,t=this.s&this.DM;for(var n=0;n<this.t;++n)e+=Nt(this.data[n]^t);return e}function kt(e){var t=Math.floor(e/this.DB);return t>=this.t?this.s!=0:(this.data[t]&1<<e%this.DB)!=0}function Lt(e,t){var n=i.ONE.shiftLeft(e);return this.bitwiseTo(n,t,n),n}function At(e){return this.changeBit(e,dt)}function Ot(e){return this.changeBit(e,yt)}function Mt(e){return this.changeBit(e,mt)}function _t(e,t){var n=0,r=0,i=Math.min(e.t,this.t);while(n<i)r+=this.data[n]+e.data[n],t.data[n++]=r&this.DM,r>>=this.DB;if(e.t<this.t){r+=e.s;while(n<this.t)r+=this.data[n],t.data[n++]=r&this.DM,r>>=this.DB;r+=this.s}else{r+=this.s;while(n<e.t)r+=e.data[n],t.data[n++]=r&this.DM,r>>=this.DB;r+=e.s}t.s=r<0?-1:0,r>0?t.data[n++]=r:r<-1&&(t.data[n++]=this.DV+r),t.t=n,t.clamp()}function Dt(e){var t=s();return this.addTo(e,t),t}function Pt(e){var t=s();return this.subTo(e,t),t}function Ht(e){var t=s();return this.multiplyTo(e,t),t}function Bt(e){var t=s();return this.divRemTo(e,t,null),t}function jt(e){var t=s();return this.divRemTo(e,null,t),t}function Ft(e){var t=s(),n=s();return this.divRemTo(e,t,n),new Array(t,n)}function It(e){this.data[this.t]=this.am(0,e-1,this,0,0,this.t),++this.t,this.clamp()}function qt(e,t){if(e==0)return;while(this.t<=t)this.data[this.t++]=0;this.data[t]+=e;while(this.data[t]>=this.DV)this.data[t]-=this.DV,++t>=this.t&&(this.data[this.t++]=0),++this.data[t]}function Rt(){}function Ut(e){return e}function zt(e,t,n){e.multiplyTo(t,n)}function Wt(e,t){e.squareTo(t)}function Xt(e){return this.exp(e,new Rt)}function Vt(e,t,n){var r=Math.min(this.t+e.t,t);n.s=0,n.t=r;while(r>0)n.data[--r]=0;var i;for(i=n.t-this.t;r<i;++r)n.data[r+this.t]=this.am(0,e.data[r],n,r,0,this.t);for(i=Math.min(e.t,t);r<i;++r)this.am(0,e.data[r],n,r,0,t-r);n.clamp()}function $t(e,t,n){--t;var r=n.t=this.t+e.t-t;n.s=0;while(--r>=0)n.data[r]=0;for(r=Math.max(t-this.t,0);r<e.t;++r)n.data[this.t+r-t]=this.am(t-r,e.data[r],n,0,0,this.t+r-t);n.clamp(),n.drShiftTo(1,n)}function Jt(e){this.r2=s(),this.q3=s(),i.ONE.dlShiftTo(2*e.t,this.r2),this.mu=this.r2.divide(e),this.m=e}function Kt(e){if(e.s<0||e.t>2*this.m.t)return e.mod(this.m);if(e.compareTo(this.m)<0)return e;var t=s();return e.copyTo(t),this.reduce(t),t}function Qt(e){return e}function Gt(e){e.drShiftTo(this.m.t-1,this.r2),e.t>this.m.t+1&&(e.t=this.m.t+1,e.clamp()),this.mu.multiplyUpperTo(this.r2,this.m.t+1,this.q3),this.m.multiplyLowerTo(this.q3,this.m.t+1,this.r2);while(e.compareTo(this.r2)<0)e.dAddOffset(1,this.m.t+1);e.subTo(this.r2,e);while(e.compareTo(this.m)>=0)e.subTo(this.m,e)}function Yt(e,t){e.squareTo(t),this.reduce(t)}function Zt(e,t,n){e.multiplyTo(t,n),this.reduce(n)}function en(e,t){var n=e.bitLength(),r,i=y(1),o;if(n<=0)return i;n<18?r=1:n<48?r=3:n<144?r=4:n<768?r=5:r=6,n<8?o=new B(t):t.isEven()?o=new Jt(t):o=new z(t);var u=new Array,a=3,f=r-1,l=(1<<r)-1;u[1]=o.convert(this);if(r>1){var c=s();o.sqrTo(u[1],c);while(a<=l)u[a]=s(),o.mulTo(c,u[a-2],u[a]),a+=2}var h=e.t-1,p,d=!0,v=s(),m;n=N(e.data[h])-1;while(h>=0){n>=f?p=e.data[h]>>n-f&l:(p=(e.data[h]&(1<<n+1)-1)<<f-n,h>0&&(p|=e.data[h-1]>>this.DB+n-f)),a=r;while((p&1)==0)p>>=1,--a;(n-=a)<0&&(n+=this.DB,--h);if(d)u[p].copyTo(i),d=!1;else{while(a>1)o.sqrTo(i,v),o.sqrTo(v,i),a-=2;a>0?o.sqrTo(i,v):(m=i,i=v,v=m),o.mulTo(v,u[p],i)}while(h>=0&&(e.data[h]&1<<n)==0)o.sqrTo(i,v),m=i,i=v,v=m,--n<0&&(n=this.DB-1,--h)}return o.revert(i)}function tn(e){var t=this.s<0?this.negate():this.clone(),n=e.s<0?e.negate():e.clone();if(t.compareTo(n)<0){var r=t;t=n,n=r}var i=t.getLowestSetBit(),s=n.getLowestSetBit();if(s<0)return t;i<s&&(s=i),s>0&&(t.rShiftTo(s,t),n.rShiftTo(s,n));while(t.signum()>0)(i=t.getLowestSetBit())>0&&t.rShiftTo(i,t),(i=n.getLowestSetBit())>0&&n.rShiftTo(i,n),t.compareTo(n)>=0?(t.subTo(n,t),t.rShiftTo(1,t)):(n.subTo(t,n),n.rShiftTo(1,n));return s>0&&n.lShiftTo(s,n),n}function nn(e){if(e<=0)return 0;var t=this.DV%e,n=this.s<0?e-1:0;if(this.t>0)if(t==0)n=this.data[0]%e;else for(var r=this.t-1;r>=0;--r)n=(t*n+this.data[r])%e;return n}function rn(e){var t=e.isEven();if(this.isEven()&&t||e.signum()==0)return i.ZERO;var n=e.clone(),r=this.clone(),s=y(1),o=y(0),u=y(0),a=y(1);while(n.signum()!=0){while(n.isEven()){n.rShiftTo(1,n);if(t){if(!s.isEven()||!o.isEven())s.addTo(this,s),o.subTo(e,o);s.rShiftTo(1,s)}else o.isEven()||o.subTo(e,o);o.rShiftTo(1,o)}while(r.isEven()){r.rShiftTo(1,r);if(t){if(!u.isEven()||!a.isEven())u.addTo(this,u),a.subTo(e,a);u.rShiftTo(1,u)}else a.isEven()||a.subTo(e,a);a.rShiftTo(1,a)}n.compareTo(r)>=0?(n.subTo(r,n),t&&s.subTo(u,s),o.subTo(a,o)):(r.subTo(n,r),t&&u.subTo(s,u),a.subTo(o,a))}return r.compareTo(i.ONE)!=0?i.ZERO:a.compareTo(e)>=0?a.subtract(e):a.signum()<0?(a.addTo(e,a),a.signum()<0?a.add(e):a):a}function un(e){var t,n=this.abs();if(n.t==1&&n.data[0]<=sn[sn.length-1]){for(t=0;t<sn.length;++t)if(n.data[0]==sn[t])return!0;return!1}if(n.isEven())return!1;t=1;while(t<sn.length){var r=sn[t],i=t+1;while(i<sn.length&&r<on)r*=sn[i++];r=n.modInt(r);while(t<i)if(r%sn[t++]==0)return!1}return n.millerRabin(e)}function an(e){var t=this.subtract(i.ONE),n=t.getLowestSetBit();if(n<=0)return!1;var r=t.shiftRight(n);e=e+1>>1,e>sn.length&&(e=sn.length);var o=s();for(var u=0;u<e;++u){o.fromInt(sn[u]);var a=o.modPow(r,this);if(a.compareTo(i.ONE)!=0&&a.compareTo(t)!=0){var f=1;while(f++<n&&a.compareTo(t)!=0){a=a.modPowInt(2,this);if(a.compareTo(i.ONE)==0)return!1}if(a.compareTo(t)!=0)return!1}}return!0}var t,n=0xdeadbeefcafe,r=(n&16777215)==15715070;typeof navigator=="undefined"?(i.prototype.am=a,t=28):r&&navigator.appName=="Microsoft Internet Explorer"?(i.prototype.am=u,t=30):r&&navigator.appName!="Netscape"?(i.prototype.am=o,t=26):(i.prototype.am=a,t=28),i.prototype.DB=t,i.prototype.DM=(1<<t)-1,i.prototype.DV=1<<t;var f=52;i.prototype.FV=Math.pow(2,f),i.prototype.F1=f-t,i.prototype.F2=2*t-f;var l="0123456789abcdefghijklmnopqrstuvwxyz",c=new Array,h,p;h="0".charCodeAt(0);for(p=0;p<=9;++p)c[h++]=p;h="a".charCodeAt(0);for(p=10;p<36;++p)c[h++]=p;h="A".charCodeAt(0);for(p=10;p<36;++p)c[h++]=p;B.prototype.convert=j,B.prototype.revert=F,B.prototype.reduce=I,B.prototype.mulTo=q,B.prototype.sqrTo=R,z.prototype.convert=W,z.prototype.revert=X,z.prototype.reduce=V,z.prototype.mulTo=J,z.prototype.sqrTo=$,i.prototype.copyTo=m,i.prototype.fromInt=g,i.prototype.fromString=b,i.prototype.clamp=w,i.prototype.dlShiftTo=k,i.prototype.drShiftTo=L,i.prototype.lShiftTo=A,i.prototype.rShiftTo=O,i.prototype.subTo=M,i.prototype.multiplyTo=_,i.prototype.squareTo=D,i.prototype.divRemTo=P,i.prototype.invDigit=U,i.prototype.isEven=K,i.prototype.exp=Q,i.prototype.toString=E,i.prototype.negate=S,i.prototype.abs=x,i.prototype.compareTo=T,i.prototype.bitLength=C,i.prototype.mod=H,i.prototype.modPowInt=G,i.ZERO=y(0),i.ONE=y(1),Rt.prototype.convert=Ut,Rt.prototype.revert=Ut,Rt.prototype.mulTo=zt,Rt.prototype.sqrTo=Wt,Jt.prototype.convert=Kt,Jt.prototype.revert=Qt,Jt.prototype.reduce=Gt,Jt.prototype.mulTo=Zt,Jt.prototype.sqrTo=Yt;var sn=[2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,503,509],on=(1<<26)/sn[sn.length-1];i.prototype.chunkSize=nt,i.prototype.toRadix=it,i.prototype.fromRadix=st,i.prototype.fromNumber=ot,i.prototype.bitwiseTo=ct,i.prototype.changeBit=Lt,i.prototype.addTo=_t,i.prototype.dMultiply=It,i.prototype.dAddOffset=qt,i.prototype.multiplyLowerTo=Vt,i.prototype.multiplyUpperTo=$t,i.prototype.modInt=nn,i.prototype.millerRabin=an,i.prototype.clone=Y,i.prototype.intValue=Z,i.prototype.byteValue=et,i.prototype.shortValue=tt,i.prototype.signum=rt,i.prototype.toByteArray=ut,i.prototype.equals=at,i.prototype.min=ft,i.prototype.max=lt,i.prototype.and=pt,i.prototype.or=vt,i.prototype.xor=gt,i.prototype.andNot=bt,i.prototype.not=wt,i.prototype.shiftLeft=Et,i.prototype.shiftRight=St,i.prototype.getLowestSetBit=Tt,i.prototype.bitCount=Ct,i.prototype.testBit=kt,i.prototype.setBit=At,i.prototype.clearBit=Ot,i.prototype.flipBit=Mt,i.prototype.add=Dt,i.prototype.subtract=Pt,i.prototype.multiply=Ht,i.prototype.divide=Bt,i.prototype.remainder=jt,i.prototype.divideAndRemainder=Ft,i.prototype.modPow=en,i.prototype.modInverse=rn,i.prototype.pow=Xt,i.prototype.gcd=tn,i.prototype.isProbablePrime=un,e.jsbn=e.jsbn||{},e.jsbn.BigInteger=i}var t="jsbn";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/jsbn",["require","module"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){var t=e.asn1,n=e.pkcs7asn1=e.pkcs7asn1||{};e.pkcs7=e.pkcs7||{},e.pkcs7.asn1=n;var r={name:"ContentInfo",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"ContentInfo.ContentType",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"contentType"},{name:"ContentInfo.content",tagClass:t.Class.CONTEXT_SPECIFIC,type:0,constructed:!0,optional:!0,captureAsn1:"content"}]};n.contentInfoValidator=r;var i={name:"EncryptedContentInfo",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"EncryptedContentInfo.contentType",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"contentType"},{name:"EncryptedContentInfo.contentEncryptionAlgorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"EncryptedContentInfo.contentEncryptionAlgorithm.algorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"encAlgorithm"},{name:"EncryptedContentInfo.contentEncryptionAlgorithm.parameter",tagClass:t.Class.UNIVERSAL,captureAsn1:"encParameter"}]},{name:"EncryptedContentInfo.encryptedContent",tagClass:t.Class.CONTEXT_SPECIFIC,type:0,capture:"encContent"}]};n.envelopedDataValidator={name:"EnvelopedData",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"EnvelopedData.Version",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"version"},{name:"EnvelopedData.RecipientInfos",tagClass:t.Class.UNIVERSAL,type:t.Type.SET,constructed:!0,captureAsn1:"recipientInfos"}].concat(i)},n.encryptedDataValidator={name:"EncryptedData",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"EncryptedData.Version",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"version"}].concat(i)};var s={name:"SignerInfo",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"SignerInfo.Version",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1},{name:"SignerInfo.IssuerAndSerialNumber",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0},{name:"SignerInfo.DigestAlgorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0},{name:"SignerInfo.AuthenticatedAttributes",tagClass:t.Class.CONTEXT_SPECIFIC,type:0,constructed:!0,optional:!0,capture:"authenticatedAttributes"},{name:"SignerInfo.DigestEncryptionAlgorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0},{name:"SignerInfo.EncryptedDigest",tagClass:t.Class.UNIVERSAL,type:t.Type.OCTETSTRING,constructed:!1,capture:"signature"},{name:"SignerInfo.UnauthenticatedAttributes",tagClass:t.Class.CONTEXT_SPECIFIC,type:1,constructed:!0,optional:!0}]};n.signedDataValidator={name:"SignedData",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"SignedData.Version",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"version"},{name:"SignedData.DigestAlgorithms",tagClass:t.Class.UNIVERSAL,type:t.Type.SET,constructed:!0,captureAsn1:"digestAlgorithms"},r,{name:"SignedData.Certificates",tagClass:t.Class.CONTEXT_SPECIFIC,type:0,optional:!0,captureAsn1:"certificates"},{name:"SignedData.CertificateRevocationLists",tagClass:t.Class.CONTEXT_SPECIFIC,type:1,optional:!0,captureAsn1:"crls"},{name:"SignedData.SignerInfos",tagClass:t.Class.UNIVERSAL,type:t.Type.SET,capture:"signerInfos",value:[s]}]},n.recipientInfoValidator={name:"RecipientInfo",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"RecipientInfo.version",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"version"},{name:"RecipientInfo.issuerAndSerial",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"RecipientInfo.issuerAndSerial.issuer",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,captureAsn1:"issuer"},{name:"RecipientInfo.issuerAndSerial.serialNumber",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"serial"}]},{name:"RecipientInfo.keyEncryptionAlgorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"RecipientInfo.keyEncryptionAlgorithm.algorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"encAlgorithm"},{name:"RecipientInfo.keyEncryptionAlgorithm.parameter",tagClass:t.Class.UNIVERSAL,constructed:!1,captureAsn1:"encParameter"}]},{name:"RecipientInfo.encryptedKey",tagClass:t.Class.UNIVERSAL,type:t.Type.OCTETSTRING,constructed:!1,capture:"encKey"}]}}var t="pkcs7asn1";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/pkcs7asn1",["require","module","./asn1","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){function f(e,t,n,r){var i=[];for(var s=0;s<e.length;s++)for(var o=0;o<e[s].safeBags.length;o++){var u=e[s].safeBags[o];if(r!==undefined&&u.type!==r)continue;u.attributes[t]!==undefined&&u.attributes[t].indexOf(n)>=0&&i.push(u)}return i}function l(e,r,s,o){r=t.fromDer(r,s);if(r.tagClass!==t.Class.UNIVERSAL||r.type!==t.Type.SEQUENCE||r.constructed!==!0)throw{message:"PKCS#12 AuthenticatedSafe expected to be a SEQUENCE OF ContentInfo"};for(var u=0;u<r.value.length;u++){var a=r.value[u],f={},l=[];if(!t.validate(a,i,f,l))throw{message:"Cannot read ContentInfo.",errors:l};var p={encrypted:!1},d=null,v=f.content.value[0];switch(t.derToOid(f.contentType)){case n.oids.data:if(v.tagClass!==t.Class.UNIVERSAL||v.type!==t.Type.OCTETSTRING)throw{message:"PKCS#12 SafeContents Data is not an OCTET STRING."};d=v.value;break;case n.oids.encryptedData:if(o===undefined)throw{message:"Found PKCS#12 Encrypted SafeContents Data but no password available."};d=c(v,o),p.encrypted=!0;break;default:throw{message:"Unsupported PKCS#12 contentType.",contentType:t.derToOid(f.contentType)}}p.safeBags=h(d,s,o),e.safeContents.push(p)}}function c(r,i){var s={},o=[];if(!t.validate(r,e.pkcs7.asn1.encryptedDataValidator,s,o))throw{message:"Cannot read EncryptedContentInfo. ",errors:o};var u=t.derToOid(s.contentType);if(u!==n.oids.data)throw{message:"PKCS#12 EncryptedContentInfo ContentType is not Data.",oid:u};u=t.derToOid(s.encAlgorithm);var a=n.pbe.getCipher(u,s.encParameter,i),f=e.util.createBuffer(s.encContent);a.update(f);if(!a.finish())throw{message:"Failed to decrypt PKCS#12 SafeContents."};return a.output.getBytes()}function h(e,r,i){e=t.fromDer(e,r);if(e.tagClass!==t.Class.UNIVERSAL||e.type!==t.Type.SEQUENCE||e.constructed!==!0)throw{message:"PKCS#12 SafeContents expected to be a SEQUENCE OF SafeBag"};var s=[];for(var u=0;u<e.value.length;u++){var f=e.value[u],l={},c=[];if(!t.validate(f,o,l,c))throw{message:"Cannot read SafeBag.",errors:c};var h={type:t.derToOid(l.bagId),attributes:p(l.bagAttributes)};s.push(h);var d,v,m=l.bagValue.value[0];switch(h.type){case n.oids.pkcs8ShroudedKeyBag:if(i===undefined)throw{message:"Found PKCS#8 ShroudedKeyBag but no password available."};m=n.decryptPrivateKeyInfo(m,i);if(m===null)throw{message:"Unable to decrypt PKCS#8 ShroudedKeyBag, wrong password?"};case n.oids.keyBag:h.key=n.privateKeyFromAsn1(m);continue;case n.oids.certBag:d=a,v=function(){if(t.derToOid(l.certId)!==n.oids.x509Certificate)throw{message:"Unsupported certificate type, only X.509 supported.",oid:t.derToOid(l.certId)};h.cert=n.certificateFromAsn1(t.fromDer(l.cert,r),!0)};break;default:throw{message:"Unsupported PKCS#12 SafeBag type.",oid:h.type}}if(d!==undefined&&!t.validate(m,d,l,c))throw{message:"Cannot read PKCS#12 "+d.name,errors:c};v()}return s}function p(e){var r={};if(e!==undefined)for(var i=0;i<e.length;++i){var s={},o=[];if(!t.validate(e[i],u,s,o))throw{message:"Cannot read PKCS#12 BagAttribute.",errors:o};var a=t.derToOid(s.oid);if(n.oids[a]===undefined)continue;r[n.oids[a]]=[];for(var f=0;f<s.values.length;++f)r[n.oids[a]].push(s.values[f].value)}return r}var t=e.asn1,n=e.pki,r=e.pkcs12=e.pkcs12||{},i={name:"ContentInfo",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"ContentInfo.contentType",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"contentType"},{name:"ContentInfo.content",tagClass:t.Class.CONTEXT_SPECIFIC,constructed:!0,captureAsn1:"content"}]},s={name:"PFX",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"PFX.version",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"version"},i,{name:"PFX.macData",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,optional:!0,captureAsn1:"mac",value:[{name:"PFX.macData.mac",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"PFX.macData.mac.digestAlgorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"PFX.macData.mac.digestAlgorithm.algorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"macAlgorithm"},{name:"PFX.macData.mac.digestAlgorithm.parameters",tagClass:t.Class.UNIVERSAL,captureAsn1:"macAlgorithmParameters"}]},{name:"PFX.macData.mac.digest",tagClass:t.Class.UNIVERSAL,type:t.Type.OCTETSTRING,constructed:!1,capture:"macDigest"}]},{name:"PFX.macData.macSalt",tagClass:t.Class.UNIVERSAL,type:t.Type.OCTETSTRING,constructed:!1,capture:"macSalt"},{name:"PFX.macData.iterations",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,optional:!0,capture:"macIterations"}]}]},o={name:"SafeBag",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"SafeBag.bagId",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"bagId"},{name:"SafeBag.bagValue",tagClass:t.Class.CONTEXT_SPECIFIC,constructed:!0,captureAsn1:"bagValue"},{name:"SafeBag.bagAttributes",tagClass:t.Class.UNIVERSAL,type:t.Type.SET,constructed:!0,optional:!0,capture:"bagAttributes"}]},u={name:"Attribute",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"Attribute.attrId",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"oid"},{name:"Attribute.attrValues",tagClass:t.Class.UNIVERSAL,type:t.Type.SET,constructed:!0,capture:"values"}]},a={name:"CertBag",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"CertBag.certId",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"certId"},{name:"CertBag.certValue",tagClass:t.Class.CONTEXT_SPECIFIC,constructed:!0,value:[{name:"CertBag.certValue[0]",tagClass:t.Class.UNIVERSAL,type:t.Class.OCTETSTRING,constructed:!1,capture:"cert"}]}]};r.pkcs12FromAsn1=function(i,o,u){typeof o=="string"?(u=o,o=!0):o===undefined&&(o=!0);var a={},c=[];if(!t.validate(i,s,a,c))throw{message:"Cannot read PKCS#12 PFX. ASN.1 object is not an PKCS#12 PFX.",errors:c};var h={version:a.version.charCodeAt(0),safeContents:[],getBags:function(t){var n={},r;return"localKeyId"in t?r=t.localKeyId:"localKeyIdHex"in t&&(r=e.util.hexToBytes(t.localKeyIdHex)),r!==undefined&&(n.localKeyId=f(h.safeContents,"localKeyId",r,t.bagType)),"friendlyName"in t&&(n.friendlyName=f(h.safeContents,"friendlyName",t.friendlyName,t.bagType)),n},getBagsByFriendlyName:function(e,t){return f(h.safeContents,"friendlyName",e,t)},getBagsByLocalKeyId:function(e,t){return f(h.safeContents,"localKeyId",e,t)}};if(a.version.charCodeAt(0)!==3)throw{message:"PKCS#12 PFX of version other than 3 not supported.",version:a.version.charCodeAt(0)};if(t.derToOid(a.contentType)!==n.oids.data)throw{message:"Only PKCS#12 PFX in password integrity mode supported.",oid:t.derToOid(a.contentType)};var p=a.content.value[0];if(p.tagClass!==t.Class.UNIVERSAL||p.type!==t.Type.OCTETSTRING)throw{message:"PKCS#12 authSafe content data is not an OCTET STRING."};if(a.mac){var d=null,v=0,m=t.derToOid(a.macAlgorithm);switch(m){case n.oids.sha1:d=e.md.sha1.create(),v=20;break;case n.oids.sha256:d=e.md.sha256.create(),v=32;break;case n.oids.sha384:d=e.md.sha384.create(),v=48;break;case n.oids.sha512:d=e.md.sha512.create(),v=64;break;case n.oids.md5:d=e.md.md5.create(),v=16}if(d===null)throw{message:"PKCS#12 uses unsupported MAC algorithm: "+m};var g=new e.util.ByteBuffer(a.macSalt),y="macIterations"in a?parseInt(e.util.bytesToHex(a.macIterations),16):1,b=r.generateKey(u||"",g,3,y,v,d),w=e.hmac.create();w.start(d,b),w.update(p.value);var E=w.getMac();if(E.getBytes()!==a.macDigest)throw{message:"PKCS#12 MAC could not be verified. Invalid password?"}}return l(h,p.value,o,u),h},r.toPkcs12Asn1=function(i,s,o,u){u=u||{},u.saltSize=u.saltSize||8,u.count=u.count||2048,u.algorithm=u.algorithm||u.encAlgorithm||"aes128","useMac"in u||(u.useMac=!0),"localKeyId"in u||(u.localKeyId=null),"generateLocalKeyId"in u||(u.generateLocalKeyId=!0);var a=u.localKeyId,f=undefined;if(a!==null)a=e.util.hexToBytes(a);else if(u.generateLocalKeyId)if(s){var l=e.util.isArray(s)?s[0]:s;typeof l=="string"&&(l=n.certificateFromPem(l));var c=e.md.sha1.create();c.update(t.toDer(n.certificateToAsn1(l)).getBytes()),a=c.digest().getBytes()}else a=e.random.getBytes(20);var h=[];a!==null&&h.push(t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.oids.localKeyId).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.SET,!0,[t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,a)])])),"friendlyName"in u&&h.push(t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.oids.friendlyName).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.SET,!0,[t.create(t.Class.UNIVERSAL,t.Type.BMPSTRING,!1,u.friendlyName)])])),h.length>0&&(f=t.create(t.Class.UNIVERSAL,t.Type.SET,!0,h));var p=[],d=[];s!==null&&(e.util.isArray(s)?d=s:d=[s]);var v=[];for(var m=0;m<d.length;++m){s=d[m],typeof s=="string"&&(s=n.certificateFromPem(s));var g=m===0?f:undefined,y=n.certificateToAsn1(s),b=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.oids.certBag).getBytes()),t.create(t.Class.CONTEXT_SPECIFIC,0,!0,[t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.oids.x509Certificate).getBytes()),t.create(t.Class.CONTEXT_SPECIFIC,0,!0,[t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,t.toDer(y).getBytes())])])]),g]);v.push(b)}if(v.length>0){var w=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,v),E=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.oids.data).getBytes()),t.create(t.Class.CONTEXT_SPECIFIC,0,!0,[t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,t.toDer(w).getBytes())])]);p.push(E)}var S=null;if(i!==null){var x=n.wrapRsaPrivateKey(n.privateKeyToAsn1(i));o===null?S=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.oids.keyBag).getBytes()),t.create(t.Class.CONTEXT_SPECIFIC,0,!0,[x]),f]):S=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.oids.pkcs8ShroudedKeyBag).getBytes()),t.create(t.Class.CONTEXT_SPECIFIC,0,!0,[n.encryptPrivateKeyInfo(x,o,u)]),f]);var T=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[S]),N=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.oids.data).getBytes()),t.create(t.Class.CONTEXT_SPECIFIC,0,!0,[t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,t.toDer(T).getBytes())])]);p.push(N)}var C=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,p),k=undefined;if(u.useMac){var c=e.md.sha1.create(),L=new e.util.ByteBuffer(e.random.getBytes(u.saltSize)),A=u.count,i=r.generateKey(o||"",L,3,A,20),O=e.hmac.create();O.start(c,i),O.update(t.toDer(C).getBytes());var M=O.getMac();k=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.oids.sha1).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.NULL,!1,"")]),t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,M.getBytes())]),t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,L.getBytes()),t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,e.util.hexToBytes(A.toString(16)))])}return t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,String.fromCharCode(3)),t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.oids.data).getBytes()),t.create(t.Class.CONTEXT_SPECIFIC,0,!0,[t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,t.toDer(C).getBytes())])]),k])},r.generateKey=function(t,n,r,i,s,o){var u,a;if(typeof o=="undefined"||o===null)o=e.md.sha1.create();var f=o.digestLength,l=o.blockLength,c=new e.util.ByteBuffer,h=new e.util.ByteBuffer;for(a=0;a<t.length;a++)h.putInt16(t.charCodeAt(a));h.putInt16(0);var p=h.length(),d=n.length(),v=new e.util.ByteBuffer;v.fillWithByte(r,l);var m=l*Math.ceil(d/l),g=new e.util.ByteBuffer;for(a=0;a<m;a++)g.putByte(n.at(a%d));var y=l*Math.ceil(p/l),b=new e.util.ByteBuffer;for(a=0;a<y;a++)b.putByte(h.at(a%p));var w=g;w.putBuffer(b);var E=Math.ceil(s/f);for(var S=1;S<=E;S++){var x=new e.util.ByteBuffer;x.putBytes(v.bytes()),x.putBytes(w.bytes());for(var T=0;T<i;T++)o.start(),o.update(x.getBytes()),x=o.digest();var N=new e.util.ByteBuffer;for(a=0;a<l;a++)N.putByte(x.at(a%f));var C=Math.ceil(d/l)+Math.ceil(p/l),k=new e.util.ByteBuffer;for(u=0;u<C;u++){var L=new e.util.ByteBuffer(w.getBytes(l)),A=511;for(a=N.length()-1;a>=0;a--)A>>=8,A+=N.at(a)+L.at(a),L.setAt(a,A&255);k.putBuffer(L)}w=k,c.putBuffer(x)}return c.truncate(c.length()-s),c}}var t="pkcs12";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/pkcs12",["require","module","./asn1","./sha1","./pkcs7asn1","./pki","./util","./random","./hmac"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){var t=e.pss=e.pss||{};t.create=function(t,n,r){var i=t.digestLength,s={};return s.verify=function(s,o,u){var a,f=u-1,l=Math.ceil(f/8);o=o.substr(-l);if(l<i+r+2)throw{message:"Inconsistent parameters to PSS signature verification."};if(o.charCodeAt(l-1)!==188)throw{message:"Encoded message does not end in 0xBC."};var c=l-i-1,h=o.substr(0,c),p=o.substr(c,i),d=65280>>8*l-f&255;if((h.charCodeAt(0)&d)!==0)throw{message:"Bits beyond keysize not zero as expected."};var v=n.generate(p,c),m="";for(a=0;a<c;a++)m+=String.fromCharCode(h.charCodeAt(a)^v.charCodeAt(a));m=String.fromCharCode(m.charCodeAt(0)&~d)+m.substr(1);var g=l-i-r-2;for(a=0;a<g;a++)if(m.charCodeAt(a)!==0)throw{message:"Leftmost octets not zero as expected"};if(m.charCodeAt(g)!==1)throw{message:"Inconsistent PSS signature, 0x01 marker not found"};var y=m.substr(-r),b=new e.util.ByteBuffer;b.fillWithByte(0,8),b.putBytes(s),b.putBytes(y),t.start(),t.update(b.getBytes());var w=t.digest().getBytes();return p===w},s.encode=function(s,o){var u,a=o-1,f=Math.ceil(a/8),l=s.digest().getBytes();if(f<i+r+2)throw{message:"Message is too long to encrypt"};var c=e.random.getBytes(r),h=new e.util.ByteBuffer;h.fillWithByte(0,8),h.putBytes(l),h.putBytes(c),t.start(),t.update(h.getBytes());var p=t.digest().getBytes(),d=new e.util.ByteBuffer;d.fillWithByte(0,f-r-i-2),d.putByte(1),d.putBytes(c);var v=d.getBytes(),m=f-i-1,g=n.generate(p,m),y="";for(u=0;u<m;u++)y+=String.fromCharCode(v.charCodeAt(u)^g.charCodeAt(u));var b=65280>>8*f-a&255;return y=String.fromCharCode(y.charCodeAt(0)&~b)+y.substr(1),y+p+String.fromCharCode(188)},s}}var t="pss";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/pss",["require","module","./random","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){var t=[217,120,249,196,25,221,181,237,40,233,253,121,74,160,216,157,198,126,55,131,43,118,83,142,98,76,100,136,68,139,251,162,23,154,89,245,135,179,79,19,97,69,109,141,9,129,125,50,189,143,64,235,134,183,123,11,240,149,33,34,92,107,78,130,84,214,101,147,206,96,178,28,115,86,192,20,167,140,241,220,18,117,202,31,59,190,228,209,66,61,212,48,163,60,182,38,111,191,14,218,70,105,7,87,39,242,29,155,188,148,67,3,248,17,199,246,144,239,62,231,6,195,213,47,200,102,30,215,8,232,234,222,128,82,238,247,132,170,114,172,53,77,106,42,150,26,210,113,90,21,73,116,75,159,208,94,4,24,164,236,194,224,65,110,15,81,203,204,36,145,175,80,161,244,112,57,153,124,58,133,35,184,180,122,252,2,54,91,37,85,151,49,45,93,250,152,227,138,146,174,5,223,41,16,103,108,186,201,211,0,230,207,225,158,168,44,99,22,1,63,88,226,137,169,13,56,52,27,171,51,255,176,187,72,12,95,185,177,205,46,197,243,219,71,229,165,156,119,10,166,32,104,254,127,193,173],n=[1,2,3,5],r=function(e,t){return e<<t&65535|(e&65535)>>16-t},i=function(e,t){return(e&65535)>>t|e<<16-t&65535};e.rc2=e.rc2||{},e.rc2.expandKey=function(n,r){typeof n=="string"&&(n=e.util.createBuffer(n)),r=r||128;var i=n,s=n.length(),o=r,u=Math.ceil(o/8),a=255>>(o&7),f;for(f=s;f<128;f++)i.putByte(t[i.at(f-1)+i.at(f-s)&255]);i.setAt(128-u,t[i.at(128-u)&a]);for(f=127-u;f>=0;f--)i.setAt(f,t[i.at(f+1)^i.at(f+u)]);return i};var s=function(t,s,o){var u=!1,a=null,f=null,l=null,c,h,p,d,v=[];t=e.rc2.expandKey(t,s);for(p=0;p<64;p++)v.push(t.getInt16Le());o?(c=function(e){for(p=0;p<4;p++)e[p]+=v[d]+(e[(p+3)%4]&e[(p+2)%4])+(~e[(p+3)%4]&e[(p+1)%4]),e[p]=r(e[p],n[p]),d++},h=function(e){for(p=0;p<4;p++)e[p]+=v[e[(p+3)%4]&63]}):(c=function(e){for(p=3;p>=0;p--)e[p]=i(e[p],n[p]),e[p]-=v[d]+(e[(p+3)%4]&e[(p+2)%4])+(~e[(p+3)%4]&e[(p+1)%4]),d--},h=function(e){for(p=3;p>=0;p--)e[p]-=v[e[(p+3)%4]&63]});var m=function(e){var t=[];for(p=0;p<4;p++){var n=a.getInt16Le();l!==null&&(o?n^=l.getInt16Le():l.putInt16Le(n)),t.push(n&65535)}d=o?0:63;for(var r=0;r<e.length;r++)for(var i=0;i<e[r][0];i++)e[r][1](t);for(p=0;p<4;p++)l!==null&&(o?l.putInt16Le(t[p]):t[p]^=l.getInt16Le()),f.putInt16Le(t[p])},g=null;return g={start:function(n,r){n&&typeof t=="string"&&n.length===8&&(n=e.util.createBuffer(n)),u=!1,a=e.util.createBuffer(),f=r||new e.util.createBuffer,l=n,g.output=f},update:function(e){u||a.putBuffer(e);while(a.length()>=8)m([[5,c],[1,h],[6,c],[1,h],[5,c]])},finish:function(e){var t=!0;if(o)if(e)t=e(8,a,!o);else{var n=a.length()===8?8:8-a.length();a.fillWithByte(n,n)}t&&(u=!0,g.update());if(!o){t=a.length()===0;if(t)if(e)t=e(8,f,!o);else{var r=f.length(),i=f.at(r-1);i>r?t=!1:f.truncate(i)}}return t}},g};e.rc2.startEncrypting=function(t,n,r){var i=e.rc2.createEncryptionCipher(t,128);return i.start(n,r),i},e.rc2.createEncryptionCipher=function(e,t){return s(e,t,!0)},e.rc2.startDecrypting=function(t,n,r){var i=e.rc2.createDecryptionCipher(t,128);return i.start(n,r),i},e.rc2.createDecryptionCipher=function(e,t){return s(e,t,!1)}}var t="rc2";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/rc2",["require","module","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){function n(e,t,n){var r="",i=Math.ceil(t/n.digestLength);for(var s=0;s<i;++s){var o=String.fromCharCode(s>>24&255,s>>16&255,s>>8&255,s&255);n.start(),n.update(e+o),r+=n.digest().getBytes()}return r.substring(0,t)}var t=e.pkcs1=e.pkcs1||{};t.encode_rsa_oaep=function(t,r,i){var s=undefined,o=undefined,u=undefined;typeof i=="string"?(s=i,o=arguments[3]||undefined,u=arguments[4]||undefined):i&&(s=i.label||undefined,o=i.seed||undefined,u=i.md||undefined),u?u.start():u=e.md.sha1.create();var a=Math.ceil(t.n.bitLength()/8),f=a-2*u.digestLength-2;if(r.length>f)throw{message:"RSAES-OAEP input message length is too long.",length:r.length,maxLength:f};s||(s=""),u.update(s,"raw");var l=u.digest(),c="",h=f-r.length;for(var p=0;p<h;p++)c+="\0";var d=l.getBytes()+c+""+r;if(!o)o=e.random.getBytes(u.digestLength);else if(o.length!==u.digestLength)throw{message:"Invalid RSAES-OAEP seed. The seed length must match the digest length.",seedLength:o.length,digestLength:u.digestLength};var v=n(o,a-u.digestLength-1,u),m=e.util.xorBytes(d,v,d.length),g=n(m,u.digestLength,u),y=e.util.xorBytes(o,g,o.length);return"\0"+y+m},t.decode_rsa_oaep=function(t,r,i){var s=undefined,o=undefined;typeof i=="string"?(s=i,o=arguments[3]||undefined):i&&(s=i.label||undefined,o=i.md||undefined);var u=Math.ceil(t.n.bitLength()/8);if(r.length!==u)throw{message:"RSAES-OAEP encoded message length is invalid.",length:r.length,expectedLength:u};o===undefined?o=e.md.sha1.create():o.start();if(u<2*o.digestLength+2)throw{message:"RSAES-OAEP key is too short for the hash function."};s||(s=""),o.update(s,"raw");var a=o.digest().getBytes(),f=r.charAt(0),l=r.substring(1,o.digestLength+1),c=r.substring(1+o.digestLength),h=n(c,o.digestLength,o),p=e.util.xorBytes(l,h,l.length),d=n(p,u-o.digestLength-1,o),v=e.util.xorBytes(c,d,c.length),m=v.substring(0,o.digestLength),g=f!=="\0";for(var y=0;y<o.digestLength;++y)g|=a.charAt(y)!==m.charAt(y);var b=1,w=o.digestLength;for(var E=o.digestLength;E<v.length;E++){var S=v.charCodeAt(E),x=S&1^1,T=b?65534:0;g|=S&T,b&=x,w+=b}if(g||v.charCodeAt(w)!==1)throw{message:"Invalid RSAES-OAEP padding."};return v.substring(w+1)}}var t="pkcs1";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/pkcs1",["require","module","./util","./random","./sha1"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){function o(t,n,r){var i=e.util.createBuffer(),s=Math.ceil(n.n.bitLength()/8);if(t.length>s-11)throw{message:"Message is too long for PKCS#1 v1.5 padding.",length:t.length,max:s-11};i.putByte(0),i.putByte(r);var o=s-3-t.length,u;if(r===0||r===1){u=r===0?0:255;for(var a=0;a<o;++a)i.putByte(u)}else for(var a=0;a<o;++a)u=Math.floor(Math.random()*255)+1,i.putByte(u);return i.putByte(0),i.putBytes(t),i}function u(t,n,r,i){var s=Math.ceil(n.n.bitLength()/8),o=e.util.createBuffer(t),u=o.getByte(),a=o.getByte();if(u!==0||r&&a!==0&&a!==1||!r&&a!=2||r&&a===0&&typeof i=="undefined")throw{message:"Encryption block is invalid."};var f=0;if(a===0){f=s-3-i;for(var l=0;l<f;++l)if(o.getByte()!==0)throw{message:"Encryption block is invalid."}}else if(a===1){f=0;while(o.length()>1){if(o.getByte()!==255){--o.read;break}++f}}else if(a===2){f=0;while(o.length()>1){if(o.getByte()===0){--o.read;break}++f}}var c=o.getByte();if(c!==0||f!==s-3-o.length())throw{message:"Encryption block is invalid."};return o.getBytes()}function a(t,n,r){function c(){h(t.pBits,function(e,n){if(e)return r(e);t.p=n,h(t.qBits,p)})}function h(e,n){function p(){var n=e-1,r=new BigInteger(e,t.rng);return r.testBit(n)||r.bitwiseTo(BigInteger.ONE.shiftLeft(n),l,r),r.dAddOffset(31-r.mod(f).byteValue(),0),r}function v(i){if(d)return;--c;var s=i.data;if(s.found){for(var a=0;a<r.length;++a)r[a].terminate();return d=!0,n(null,new BigInteger(s.prime,16))}h.bitLength()>e&&(h=p());var f=h.toString(16);i.target.postMessage({e:t.eInt,hex:f,workLoad:o}),h.dAddOffset(u,0)}var r=[];for(var i=0;i<s;++i)r[i]=new Worker(a);var c=s,h=p();for(var i=0;i<s;++i)r[i].addEventListener("message",v);var d=!1}function p(n,i){t.q=i;if(t.p.compareTo(t.q)<0){var s=t.p;t.p=t.q,t.q=s}t.p1=t.p.subtract(BigInteger.ONE),t.q1=t.q.subtract(BigInteger.ONE),t.phi=t.p1.multiply(t.q1);if(t.phi.gcd(t.e).compareTo(BigInteger.ONE)!==0){t.p=t.q=null,c();return}t.n=t.p.multiply(t.q);if(t.n.bitLength()!==t.bits){t.q=null,h(t.qBits,p);return}var o=t.e.modInverse(t.phi);t.keys={privateKey:e.pki.rsa.setPrivateKey(t.n,t.e,o,t.p,t.q,o.mod(t.p1),o.mod(t.q1),t.q.modInverse(t.p)),publicKey:e.pki.rsa.setPublicKey(t.n,t.e)},r(null,t.keys)}typeof n=="function"&&(r=n,n={});if(typeof Worker=="undefined"){function i(){if(e.pki.rsa.stepKeyPairGenerationState(t,10))return r(null,t.keys);e.util.setImmediate(i)}return i()}var s=n.workers||2,o=n.workLoad||100,u=o*30/8,a=n.workerScript||"forge/prime.worker.js",f=new BigInteger(null);f.fromInt(30);var l=function(e,t){return e|t};c()}typeof BigInteger=="undefined"&&(BigInteger=e.jsbn.BigInteger);var t=e.asn1;e.pki=e.pki||{},e.pki.rsa=e.rsa=e.rsa||{};var n=e.pki,r=[6,4,2,4,2,4,6,2],i=function(n){var r;if(n.algorithm in e.pki.oids){r=e.pki.oids[n.algorithm];var i=t.oidToDer(r).getBytes(),s=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[]),o=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[]);o.value.push(t.create(t.Class.UNIVERSAL,t.Type.OID,!1,i)),o.value.push(t.create(t.Class.UNIVERSAL,t.Type.NULL,!1,""));var u=t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,n.digest().getBytes());return s.value.push(o),s.value.push(u),t.toDer(s).getBytes()}throw{message:"Unknown message digest algorithm.",algorithm:n.algorithm}},s=function(e,t,n){var r;if(n)r=e.modPow(t.e,t.n);else{t.dP||(t.dP=t.d.mod(t.p.subtract(BigInteger.ONE))),t.dQ||(t.dQ=t.d.mod(t.q.subtract(BigInteger.ONE))),t.qInv||(t.qInv=t.q.modInverse(t.p));var i=e.mod(t.p).modPow(t.dP,t.p),s=e.mod(t.q).modPow(t.dQ,t.q);while(i.compareTo(s)<0)i=i.add(t.p);r=i.subtract(s).multiply(t.qInv).mod(t.p).multiply(t.q).add(s)}return r};n.rsa.encrypt=function(t,n,r){var i=r,u,a=Math.ceil(n.n.bitLength()/8);r!==!1&&r!==!0?(i=r===2,u=o(t,n,r)):(u=e.util.createBuffer(),u.putBytes(t));var f=new BigInteger(u.toHex(),16),l=s(f,n,i),c=l.toString(16),h=e.util.createBuffer(),p=a-Math.ceil(c.length/2);while(p>0)h.putByte(0),--p;return h.putBytes(e.util.hexToBytes(c)),h.getBytes()},n.rsa.decrypt=function(t,n,r,i){var o=Math.ceil(n.n.bitLength()/8);if(t.length!==o)throw{message:"Encrypted message length is invalid.",length:t.length,expected:o};var a=new BigInteger(e.util.createBuffer(t).toHex(),16);if(a.compareTo(n.n)>=0)throw{message:"Encrypted message is invalid."};var f=s(a,n,r),l=f.toString(16),c=e.util.createBuffer(),h=o-Math.ceil(l.length/2);while(h>0)c.putByte(0),--h;return c.putBytes(e.util.hexToBytes(l)),i!==!1?u(c.getBytes(),n,r):c.getBytes()},n.rsa.createKeyPairGenerationState=function(t,n){typeof t=="string"&&(t=parseInt(t,10)),t=t||1024;var r={nextBytes:function(t){var n=e.random.getBytes(t.length);for(var r=0;r<t.length;++r)t[r]=n.charCodeAt(r)}},i={state:0,bits:t,rng:r,eInt:n||65537,e:new BigInteger(null),p:null,q:null,qBits:t>>1,pBits:t-(t>>1),pqState:0,num:null,keys:null};return i.e.fromInt(i.eInt),i},n.rsa.stepKeyPairGenerationState=function(t,n){var i=new BigInteger(null);i.fromInt(30);var s=0,o=function(e,t){return e|t},u=+(new Date),a,f=0;while(t.keys===null&&(n<=0||f<n)){if(t.state===0){var l=t.p===null?t.pBits:t.qBits,c=l-1;t.pqState===0?(t.num=new BigInteger(l,t.rng),t.num.testBit(c)||t.num.bitwiseTo(BigInteger.ONE.shiftLeft(c),o,t.num),t.num.dAddOffset(31-t.num.mod(i).byteValue(),0),s=0,++t.pqState):t.pqState===1?t.num.bitLength()>l?t.pqState=0:t.num.isProbablePrime(1)?++t.pqState:t.num.dAddOffset(r[s++%8],0):t.pqState===2?t.pqState=t.num.subtract(BigInteger.ONE).gcd(t.e).compareTo(BigInteger.ONE)===0?3:0:t.pqState===3&&(t.pqState=0,t.num.isProbablePrime(10)&&(t.p===null?t.p=t.num:t.q=t.num,t.p!==null&&t.q!==null&&++t.state),t.num=null)}else if(t.state===1)t.p.compareTo(t.q)<0&&(t.num=t.p,t.p=t.q,t.q=t.num),++t.state;else if(t.state===2)t.p1=t.p.subtract(BigInteger.ONE),t.q1=t.q.subtract(BigInteger.ONE),t.phi=t.p1.multiply(t.q1),++t.state;else if(t.state===3)t.phi.gcd(t.e).compareTo(BigInteger.ONE)===0?++t.state:(t.p=null,t.q=null,t.state=0);else if(t.state===4)t.n=t.p.multiply(t.q),t.n.bitLength()===t.bits?++t.state:(t.q=null,t.state=0);else if(t.state===5){var h=t.e.modInverse(t.phi);t.keys={privateKey:e.pki.rsa.setPrivateKey(t.n,t.e,h,t.p,t.q,h.mod(t.p1),h.mod(t.q1),t.q.modInverse(t.p)),publicKey:e.pki.rsa.setPublicKey(t.n,t.e)}}a=+(new Date),f+=a-u,u=a}return t.keys!==null},n.rsa.generateKeyPair=function(e,t,r,i){arguments.length===1?typeof e=="object"?(r=e,e=undefined):typeof e=="function"&&(i=e,e=undefined):arguments.length===2?(typeof e=="number"?typeof t=="function"?i=t:r=t:(r=e,i=t,e=undefined),t=undefined):arguments.length===3&&(typeof t=="number"?typeof r=="function"&&(i=r,r=undefined):(i=r,r=t,t=undefined)),r=r||{},e===undefined&&(e=r.bits||1024),t===undefined&&(t=r.e||65537);var s=n.rsa.createKeyPairGenerationState(e,t);if(!i)return n.rsa.stepKeyPairGenerationState(s,0),s.keys;a(s,r,i)},n.rsa.setPublicKey=function(r,i){var s={n:r,e:i};return s.encrypt=function(t,r,i){typeof r=="string"?r=r.toUpperCase():r===undefined&&(r="RSAES-PKCS1-V1_5");if(r==="RSAES-PKCS1-V1_5")r={encode:function(e,t,n){return o(e,t,2).getBytes()}};else if(r==="RSA-OAEP"||r==="RSAES-OAEP")r={encode:function(t,n){return e.pkcs1.encode_rsa_oaep(n,t,i)}};else{if(["RAW","NONE","NULL",null].indexOf(r)===-1)throw{message:'Unsupported encryption scheme: "'+r+'".'};r={encode:function(e){return e}}}var u=r.encode(t,s,!0);return n.rsa.encrypt(u,s,!0)},s.verify=function(e,r,i){typeof i=="string"?i=i.toUpperCase():i===undefined&&(i="RSASSA-PKCS1-V1_5");if(i==="RSASSA-PKCS1-V1_5")i={verify:function(e,n){n=u(n,s,!0);var r=t.fromDer(n);return e===r.value[1].value}};else if(i==="NONE"||i==="NULL"||i===null)i={verify:function(e,t){return t=u(t,s,!0),e===t}};var o=n.rsa.decrypt(r,s,!0,!1);return i.verify(e,o,s.n.bitLength())},s},n.rsa.setPrivateKey=function(t,r,s,o,a,f,l,c){var h={n:t,e:r,d:s,p:o,q:a,dP:f,dQ:l,qInv:c};return h.decrypt=function(t,r,i){typeof r=="string"?r=r.toUpperCase():r===undefined&&(r="RSAES-PKCS1-V1_5");var s=n.rsa.decrypt(t,h,!1,!1);if(r==="RSAES-PKCS1-V1_5")r={decode:u};else if(r==="RSA-OAEP"||r==="RSAES-OAEP")r={decode:function(t,n){return e.pkcs1.decode_rsa_oaep(n,t,i)}};else{if(["RAW","NONE","NULL",null].indexOf(r)===-1)throw{message:'Unsupported encryption scheme: "'+r+'".'};r={decode:function(e){return e}}}return r.decode(s,h,!1)},h.sign=function(e,t){var r=!1;typeof t=="string"&&(t=t.toUpperCase());if(t===undefined||t==="RSASSA-PKCS1-V1_5")t={encode:i},r=1;else if(t==="NONE"||t==="NULL"||t===null)t={encode:function(){return e}},r=1;var s=t.encode(e,h.n.bitLength());return n.rsa.encrypt(s,h,r)},h}}var t="rsa";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/rsa",["require","module","./asn1","./oids","./random","./util","./jsbn","./pkcs1"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){function w(n){var r=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[]),i,s,o=n.attributes;for(var u=0;u<o.length;++u){i=o[u];var a=i.value,f=t.Type.PRINTABLESTRING;"valueTagClass"in i&&(f=i.valueTagClass,f===t.Type.UTF8&&(a=e.util.encodeUtf8(a))),s=t.create(t.Class.UNIVERSAL,t.Type.SET,!0,[t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(i.type).getBytes()),t.create(t.Class.UNIVERSAL,f,!1,a)])]),r.value.push(s)}return r}function E(e){var n=t.create(t.Class.CONTEXT_SPECIFIC,3,!0,[]),r=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[]);n.value.push(r);var i,s;for(var o=0;o<e.length;++o){i=e[o],s=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[]),r.value.push(s),s.value.push(t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(i.id).getBytes())),i.critical&&s.value.push(t.create(t.Class.UNIVERSAL,t.Type.BOOLEAN,!1,String.fromCharCode(255)));var u=i.value;typeof i.value!="string"&&(u=t.toDer(u).getBytes()),s.value.push(t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,u))}return n}function S(e,n){switch(e){case r["RSASSA-PSS"]:var i=[];return n.hash.algorithmOid!==undefined&&i.push(t.create(t.Class.CONTEXT_SPECIFIC,0,!0,[t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.hash.algorithmOid).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.NULL,!1,"")])])),n.mgf.algorithmOid!==undefined&&i.push(t.create(t.Class.CONTEXT_SPECIFIC,1,!0,[t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.mgf.algorithmOid).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.mgf.hash.algorithmOid).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.NULL,!1,"")])])])),n.saltLength!==undefined&&i.push(t.create(t.Class.CONTEXT_SPECIFIC,2,!0,[t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,String.fromCharCode(n.saltLength))])),t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,i);default:return t.create(t.Class.UNIVERSAL,t.Type.NULL,!1,"")}}function x(n){var r=t.create(t.Class.CONTEXT_SPECIFIC,0,!0,[]);if(n.attributes.length===0)return r;var i=n.attributes;for(var s=0;s<i.length;++s){var o=i[s],u=o.value,a=t.Type.UTF8;"valueTagClass"in o&&(a=o.valueTagClass),a===t.Type.UTF8&&(u=e.util.encodeUtf8(u));var f=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(o.type).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.SET,!0,[t.create(t.Class.UNIVERSAL,a,!1,u)])]);r.value.push(f)}return r}function T(e,t,n){var r=[N(e+t)];for(var i=16,s=1;i<n;++s,i+=16)r.push(N(r[s-1]+e+t));return r.join("").substr(0,n)}function N(t){return e.md.md5.create().update(t).digest().getBytes()}typeof BigInteger=="undefined"&&(BigInteger=e.jsbn.BigInteger);var t=e.asn1,n=e.pki=e.pki||{},r=n.oids;n.pbe={};var i={};i.CN=r.commonName,i.commonName="CN",i.C=r.countryName,i.countryName="C",i.L=r.localityName,i.localityName="L",i.ST=r.stateOrProvinceName,i.stateOrProvinceName="ST",i.O=r.organizationName,i.organizationName="O",i.OU=r.organizationalUnitName,i.organizationalUnitName="OU",i.E=r.emailAddress,i.emailAddress="E";var s={name:"SubjectPublicKeyInfo",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,captureAsn1:"subjectPublicKeyInfo",value:[{name:"SubjectPublicKeyInfo.AlgorithmIdentifier",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"AlgorithmIdentifier.algorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"publicKeyOid"}]},{name:"SubjectPublicKeyInfo.subjectPublicKey",tagClass:t.Class.UNIVERSAL,type:t.Type.BITSTRING,constructed:!1,value:[{name:"SubjectPublicKeyInfo.subjectPublicKey.RSAPublicKey",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,optional:!0,captureAsn1:"rsaPublicKey"}]}]},o={name:"RSAPublicKey",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"RSAPublicKey.modulus",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"publicKeyModulus"},{name:"RSAPublicKey.exponent",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"publicKeyExponent"}]},u={name:"Certificate",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"Certificate.TBSCertificate",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,captureAsn1:"tbsCertificate",value:[{name:"Certificate.TBSCertificate.version",tagClass:t.Class.CONTEXT_SPECIFIC,type:0,constructed:!0,optional:!0,value:[{name:"Certificate.TBSCertificate.version.integer",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"certVersion"}]},{name:"Certificate.TBSCertificate.serialNumber",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"certSerialNumber"},{name:"Certificate.TBSCertificate.signature",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"Certificate.TBSCertificate.signature.algorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"certinfoSignatureOid"},{name:"Certificate.TBSCertificate.signature.parameters",tagClass:t.Class.UNIVERSAL,optional:!0,captureAsn1:"certinfoSignatureParams"}]},{name:"Certificate.TBSCertificate.issuer",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,captureAsn1:"certIssuer"},{name:"Certificate.TBSCertificate.validity",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"Certificate.TBSCertificate.validity.notBefore (utc)",tagClass:t.Class.UNIVERSAL,type:t.Type.UTCTIME,constructed:!1,optional:!0,capture:"certValidity1UTCTime"},{name:"Certificate.TBSCertificate.validity.notBefore (generalized)",tagClass:t.Class.UNIVERSAL,type:t.Type.GENERALIZEDTIME,constructed:!1,optional:!0,capture:"certValidity2GeneralizedTime"},{name:"Certificate.TBSCertificate.validity.notAfter (utc)",tagClass:t.Class.UNIVERSAL,type:t.Type.UTCTIME,constructed:!1,optional:!0,capture:"certValidity3UTCTime"},{name:"Certificate.TBSCertificate.validity.notAfter (generalized)",tagClass:t.Class.UNIVERSAL,type:t.Type.GENERALIZEDTIME,constructed:!1,optional:!0,capture:"certValidity4GeneralizedTime"}]},{name:"Certificate.TBSCertificate.subject",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,captureAsn1:"certSubject"},s,{name:"Certificate.TBSCertificate.issuerUniqueID",tagClass:t.Class.CONTEXT_SPECIFIC,type:1,constructed:!0,optional:!0,value:[{name:"Certificate.TBSCertificate.issuerUniqueID.id",tagClass:t.Class.UNIVERSAL,type:t.Type.BITSTRING,constructed:!1,capture:"certIssuerUniqueId"}]},{name:"Certificate.TBSCertificate.subjectUniqueID",tagClass:t.Class.CONTEXT_SPECIFIC,type:2,constructed:!0,optional:!0,value:[{name:"Certificate.TBSCertificate.subjectUniqueID.id",tagClass:t.Class.UNIVERSAL,type:t.Type.BITSTRING,constructed:!1,capture:"certSubjectUniqueId"}]},{name:"Certificate.TBSCertificate.extensions",tagClass:t.Class.CONTEXT_SPECIFIC,type:3,constructed:!0,captureAsn1:"certExtensions",optional:!0}]},{name:"Certificate.signatureAlgorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"Certificate.signatureAlgorithm.algorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"certSignatureOid"},{name:"Certificate.TBSCertificate.signature.parameters",tagClass:t.Class.UNIVERSAL,optional:!0,captureAsn1:"certSignatureParams"}]},{name:"Certificate.signatureValue",tagClass:t.Class.UNIVERSAL,type:t.Type.BITSTRING,constructed:!1,capture:"certSignature"}]},a={name:"PrivateKeyInfo",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"PrivateKeyInfo.version",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"privateKeyVersion"},{name:"PrivateKeyInfo.privateKeyAlgorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"AlgorithmIdentifier.algorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"privateKeyOid"}]},{name:"PrivateKeyInfo",tagClass:t.Class.UNIVERSAL,type:t.Type.OCTETSTRING,constructed:!1,capture:"privateKey"}]},f={name:"RSAPrivateKey",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"RSAPrivateKey.version",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"privateKeyVersion"},{name:"RSAPrivateKey.modulus",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"privateKeyModulus"},{name:"RSAPrivateKey.publicExponent",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"privateKeyPublicExponent"},{name:"RSAPrivateKey.privateExponent",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"privateKeyPrivateExponent"},{name:"RSAPrivateKey.prime1",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"privateKeyPrime1"},{name:"RSAPrivateKey.prime2",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"privateKeyPrime2"},{name:"RSAPrivateKey.exponent1",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"privateKeyExponent1"},{name:"RSAPrivateKey.exponent2",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"privateKeyExponent2"},{name:"RSAPrivateKey.coefficient",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"privateKeyCoefficient"}]},l={name:"EncryptedPrivateKeyInfo",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"EncryptedPrivateKeyInfo.encryptionAlgorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"AlgorithmIdentifier.algorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"encryptionOid"},{name:"AlgorithmIdentifier.parameters",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,captureAsn1:"encryptionParams"}]},{name:"EncryptedPrivateKeyInfo.encryptedData",tagClass:t.Class.UNIVERSAL,type:t.Type.OCTETSTRING,constructed:!1,capture:"encryptedData"}]},c={name:"PBES2Algorithms",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"PBES2Algorithms.keyDerivationFunc",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"PBES2Algorithms.keyDerivationFunc.oid",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"kdfOid"},{name:"PBES2Algorithms.params",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"PBES2Algorithms.params.salt",tagClass:t.Class.UNIVERSAL,type:t.Type.OCTETSTRING,constructed:!1,capture:"kdfSalt"},{name:"PBES2Algorithms.params.iterationCount",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,onstructed:!0,capture:"kdfIterationCount"}]}]},{name:"PBES2Algorithms.encryptionScheme",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"PBES2Algorithms.encryptionScheme.oid",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"encOid"},{name:"PBES2Algorithms.encryptionScheme.iv",tagClass:t.Class.UNIVERSAL,type:t.Type.OCTETSTRING,constructed:!1,capture:"encIv"}]}]},h={name:"pkcs-12PbeParams",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"pkcs-12PbeParams.salt",tagClass:t.Class.UNIVERSAL,type:t.Type.OCTETSTRING,constructed:!1,capture:"salt"},{name:"pkcs-12PbeParams.iterations",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"iterations"}]},p={name:"rsapss",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"rsapss.hashAlgorithm",tagClass:t.Class.CONTEXT_SPECIFIC,type:0,constructed:!0,value:[{name:"rsapss.hashAlgorithm.AlgorithmIdentifier",tagClass:t.Class.UNIVERSAL,type:t.Class.SEQUENCE,constructed:!0,optional:!0,value:[{name:"rsapss.hashAlgorithm.AlgorithmIdentifier.algorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"hashOid"}]}]},{name:"rsapss.maskGenAlgorithm",tagClass:t.Class.CONTEXT_SPECIFIC,type:1,constructed:!0,value:[{name:"rsapss.maskGenAlgorithm.AlgorithmIdentifier",tagClass:t.Class.UNIVERSAL,type:t.Class.SEQUENCE,constructed:!0,optional:!0,value:[{name:"rsapss.maskGenAlgorithm.AlgorithmIdentifier.algorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"maskGenOid"},{name:"rsapss.maskGenAlgorithm.AlgorithmIdentifier.params",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"rsapss.maskGenAlgorithm.AlgorithmIdentifier.params.algorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"maskGenHashOid"}]}]}]},{name:"rsapss.saltLength",tagClass:t.Class.CONTEXT_SPECIFIC,type:2,optional:!0,value:[{name:"rsapss.saltLength.saltLength",tagClass:t.Class.UNIVERSAL,type:t.Class.INTEGER,constructed:!1,capture:"saltLength"}]},{name:"rsapss.trailerField",tagClass:t.Class.CONTEXT_SPECIFIC,type:3,optional:!0,value:[{name:"rsapss.trailer.trailer",tagClass:t.Class.UNIVERSAL,type:t.Class.INTEGER,constructed:!1,capture:"trailer"}]}]},d={name:"CertificationRequestInfo",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,captureAsn1:"certificationRequestInfo",value:[{name:"CertificationRequestInfo.integer",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"certificationRequestInfoVersion"},{name:"CertificationRequestInfo.subject",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,captureAsn1:"certificationRequestInfoSubject"},s,{name:"CertificationRequestInfo.attributes",tagClass:t.Class.CONTEXT_SPECIFIC,type:0,constructed:!0,optional:!0,capture:"certificationRequestInfoAttributes",value:[{name:"CertificationRequestInfo.attributes",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"CertificationRequestInfo.attributes.type",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1},{name:"CertificationRequestInfo.attributes.value",tagClass:t.Class.UNIVERSAL,type:t.Type.SET,constructed:!0}]}]}]},v={name:"CertificationRequest",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,captureAsn1:"csr",value:[d,{name:"CertificationRequest.signatureAlgorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"CertificationRequest.signatureAlgorithm.algorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"csrSignatureOid"},{name:"CertificationRequest.signatureAlgorithm.parameters",tagClass:t.Class.UNIVERSAL,optional:!0,captureAsn1:"csrSignatureParams"}]},{name:"CertificationRequest.signature",tagClass:t.Class.UNIVERSAL,type:t.Type.BITSTRING,constructed:!1,capture:"csrSignature"}]};n.RDNAttributesAsArray=function(e,n){var s=[],o,u,a;for(var f=0;f<e.value.length;++f){o=e.value[f];for(var l=0;l<o.value.length;++l)a={},u=o.value[l],a.type=t.derToOid(u.value[0].value),a.value=u.value[1].value,a.valueTagClass=u.value[1].type,a.type in r&&(a.name=r[a.type],a.name in i&&(a.shortName=i[a.name])),n&&(n.update(a.type),n.update(a.value)),s.push(a)}return s},n.CRIAttributesAsArray=function(e){var n=[];for(var s=0;s<e.length;++s){var o=e[s],u=t.derToOid(o.value[0].value),a=o.value[1].value;for(var f=0;f<a.length;++f){var l={};l.type=u,l.value=a[f].value,l.valueTagClass=a[f].type,l.type in r&&(l.name=r[l.type],l.name in i&&(l.shortName=i[l.name])),n.push(l)}}return n};var m=function(e,t){typeof t=="string"&&(t={shortName:t});var n=null,r;for(var i=0;n===null&&i<e.attributes.length;++i)r=e.attributes[i],t.type&&t.type===r.type?n=r:t.name&&t.name===r.name?n=r:t.shortName&&t.shortName===r.shortName&&(n=r);return n},g=function(n){var i=[],s,o,u;for(var a=0;a<n.value.length;++a){u=n.value[a];for(var f=0;f<u.value.length;++f){o=u.value[f],s={},s.id=t.derToOid(o.value[0].value),s.critical=!1,o.value[1].type===t.Type.BOOLEAN?(s.critical=o.value[1].value.charCodeAt(0)!==0,s.value=o.value[2].value):s.value=o.value[1].value;if(s.id in r){s.name=r[s.id];if(s.name==="keyUsage"){var l=t.fromDer(s.value),c=0,h=0;l.value.length>1&&(c=l.value.charCodeAt(1),h=l.value.length>2?l.value.charCodeAt(2):0),s.digitalSignature=(c&128)===128,s.nonRepudiation=(c&64)===64,s.keyEncipherment=(c&32)===32,s.dataEncipherment=(c&16)===16,s.keyAgreement=(c&8)===8,s.keyCertSign=(c&4)===4,s.cRLSign=(c&2)===2,s.encipherOnly=(c&1)===1,s.decipherOnly=(h&128)===128}else if(s.name==="basicConstraints"){var l=t.fromDer(s.value);l.value.length>0?s.cA=l.value[0].value.charCodeAt(0)!==0:s.cA=!1;if(l.value.length>1){var p=e.util.createBuffer(l.value[1].value);s.pathLenConstraint=p.getInt(p.length()<<3)}}else if(s.name==="extKeyUsage"){var l=t.fromDer(s.value);for(var d=0;d<l.value.length;++d){var v=t.derToOid(l.value[d].value);v in r?s[r[v]]=!0:s[v]=!0}}else if(s.name==="nsCertType"){var l=t.fromDer(s.value),c=0;l.value.length>1&&(c=l.value.charCodeAt(1)),s.client=(c&128)===128,s.server=(c&64)===64,s.email=(c&32)===32,s.objsign=(c&16)===16,s.reserved=(c&8)===8,s.sslCA=(c&4)===4,s.emailCA=(c&2)===2,s.objCA=(c&1)===1}else if(s.name==="subjectAltName"||s.name==="issuerAltName"){s.altNames=[];var m,l=t.fromDer(s.value);for(var g=0;g<l.value.length;++g){m=l.value[g];var y={type:m.type,value:m.value};s.altNames.push(y);switch(m.type){case 1:case 2:case 6:break;case 7:break;case 8:y.oid=t.derToOid(m.value);break;default:}}}else if(s.name==="subjectKeyIdentifier"){var l=t.fromDer(s.value);s.subjectKeyIdentifier=e.util.bytesToHex(l.value)}}i.push(s)}}return i};n.pemToDer=function(t){var n=e.pem.decode(t)[0];if(n.procType&&n.procType.type==="ENCRYPTED")throw{message:"Could not convert PEM to DER; PEM is encrypted."};return e.util.createBuffer(n.body)};var y=function(t){var n=t.toString(16);return n[0]>="8"&&(n="00"+n),e.util.hexToBytes(n)},b=function(e,n,i){var s={};if(e!==r["RSASSA-PSS"])return s;i&&(s={hash:{algorithmOid:r.sha1},mgf:{algorithmOid:r.mgf1,hash:{algorithmOid:r.sha1}},saltLength:20});var o={},u=[];if(!t.validate(n,p,o,u))throw{message:"Cannot read RSASSA-PSS parameter block.",errors:u};return o.hashOid!==undefined&&(s.hash=s.hash||{},s.hash.algorithmOid=t.derToOid(o.hashOid)),o.maskGenOid!==undefined&&(s.mgf=s.mgf||{},s.mgf.algorithmOid=t.derToOid(o.maskGenOid),s.mgf.hash=s.mgf.hash||{},s.mgf.hash.algorithmOid=t.derToOid(o.maskGenHashOid)),o.saltLength!==undefined&&(s.saltLength=o.saltLength.charCodeAt(0)),s};n.certificateFromPem=function(r,i,s){var o=e.pem.decode(r)[0];if(o.type!=="CERTIFICATE"&&o.type!=="X509 CERTIFICATE"&&o.type!=="TRUSTED CERTIFICATE")throw{message:'Could not convert certificate from PEM; PEM header type is not "CERTIFICATE", "X509 CERTIFICATE", or "TRUSTED CERTIFICATE".',headerType:o.type};if(o.procType&&o.procType.type==="ENCRYPTED")throw{message:"Could not convert certificate from PEM; PEM is encrypted."};var u=t.fromDer(o.body,s);return n.certificateFromAsn1(u,i)},n.certificateToPem=function(r,i){var s={type:"CERTIFICATE",body:t.toDer(n.certificateToAsn1(r)).getBytes()};return e.pem.encode(s,{maxline:i})},n.publicKeyFromPem=function(r){var i=e.pem.decode(r)[0];if(i.type!=="PUBLIC KEY"&&i.type!=="RSA PUBLIC KEY")throw{message:'Could not convert public key from PEM; PEM header type is not "PUBLIC KEY" or "RSA PUBLIC KEY".',headerType:i.type};if(i.procType&&i.procType.type==="ENCRYPTED")throw{message:"Could not convert public key from PEM; PEM is encrypted."};var s=t.fromDer(i.body);return n.publicKeyFromAsn1(s)},n.publicKeyToPem=function(r,i){var s={type:"PUBLIC KEY",body:t.toDer(n.publicKeyToAsn1(r)).getBytes()};return e.pem.encode(s,{maxline:i})},n.publicKeyToRSAPublicKeyPem=function(r,i){var s={type:"RSA PUBLIC KEY",body:t.toDer(n.publicKeyToRSAPublicKey(r)).getBytes()};return e.pem.encode(s,{maxline:i})},n.privateKeyFromPem=function(r){var i=e.pem.decode(r)[0];if(i.type!=="PRIVATE KEY"&&i.type!=="RSA PRIVATE KEY")throw{message:'Could not convert private key from PEM; PEM header type is not "PRIVATE KEY" or "RSA PRIVATE KEY".',headerType:i.type};if(i.procType&&i.procType.type==="ENCRYPTED")throw{message:"Could not convert private key from PEM; PEM is encrypted."};var s=t.fromDer(i.body);return n.privateKeyFromAsn1(s)},n.privateKeyToPem=function(r,i){var s={type:"RSA PRIVATE KEY",body:t.toDer(n.privateKeyToAsn1(r)).getBytes()};return e.pem.encode(s,{maxline:i})},n.certificationRequestFromPem=function(r,i,s){var o=e.pem.decode(r)[0];if(o.type!=="CERTIFICATE REQUEST")throw{message:'Could not convert certification request from PEM; PEM header type is not "CERTIFICATE REQUEST".',headerType:o.type};if(o.procType&&o.procType.type==="ENCRYPTED")throw{message:"Could not convert certification request from PEM; PEM is encrypted."};var u=t.fromDer(o.body,s);return n.certificationRequestFromAsn1(u,i)},n.certificationRequestToPem=function(r,i){var s={type:"CERTIFICATE REQUEST",body:t.toDer(n.certificationRequestToAsn1(r)).getBytes()};return e.pem.encode(s,{maxline:i})},n.createCertificate=function(){var s={};s.version=2,s.serialNumber="00",s.signatureOid=null,s.signature=null,s.siginfo={},s.siginfo.algorithmOid=null,s.validity={},s.validity.notBefore=new Date,s.validity.notAfter=new Date,s.issuer={},s.issuer.getField=function(e){return m(s.issuer,e)},s.issuer.addField=function(e){o([e]),s.issuer.attributes.push(e)},s.issuer.attributes=[],s.issuer.hash=null,s.subject={},s.subject.getField=function(e){return m(s.subject,e)},s.subject.addField=function(e){o([e]),s.subject.attributes.push(e)},s.subject.attributes=[],s.subject.hash=null,s.extensions=[],s.publicKey=null,s.md=null;var o=function(e){var t;for(var r=0;r<e.length;++r){t=e[r],typeof t.name=="undefined"&&(t.type&&t.type in n.oids?t.name=n.oids[t.type]:t.shortName&&t.shortName in i&&(t.name=n.oids[i[t.shortName]]));if(typeof t.type=="undefined"){if(!(t.name&&t.name in n.oids))throw{message:"Attribute type not specified.",attribute:t};t.type=n.oids[t.name]}typeof t.shortName=="undefined"&&t.name&&t.name in i&&(t.shortName=i[t.name]);if(typeof t.value=="undefined")throw{message:"Attribute value not specified.",attribute:t}}};return s.setSubject=function(e,t){o(e),s.subject.attributes=e,delete s.subject.uniqueId,t&&(s.subject.uniqueId=t),s.subject.hash=null},s.setIssuer=function(e,t){o(e),s.issuer.attributes=e,delete s.issuer.uniqueId,t&&(s.issuer.uniqueId=t),s.issuer.hash=null},s.setExtensions=function(i){var o;for(var u=0;u<i.length;++u){o=i[u],typeof o.name=="undefined"&&o.id&&o.id in n.oids&&(o.name=n.oids[o.id]);if(typeof o.id=="undefined"){if(!(o.name&&o.name in n.oids))throw{message:"Extension ID not specified.",extension:o};o.id=n.oids[o.name]}if(typeof o.value=="undefined"){if(o.name==="keyUsage"){var a=0,f=0,l=0;o.digitalSignature&&(f|=128,a=7),o.nonRepudiation&&(f|=64,a=6),o.keyEncipherment&&(f|=32,a=5),o.dataEncipherment&&(f|=16,a=4),o.keyAgreement&&(f|=8,a=3),o.keyCertSign&&(f|=4,a=2),o.cRLSign&&(f|=2,a=1),o.encipherOnly&&(f|=1,a=0),o.decipherOnly&&(l|=128,a=7);var c=String.fromCharCode(a);l!==0?c+=String.fromCharCode(f)+String.fromCharCode(l):f!==0&&(c+=String.fromCharCode(f)),o.value=t.create(t.Class.UNIVERSAL,t.Type.BITSTRING,!1,c)}else if(o.name==="basicConstraints"){o.value=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[]),o.cA&&o.value.value.push(t.create(t.Class.UNIVERSAL,t.Type.BOOLEAN,!1,String.fromCharCode(255)));if(o.pathLenConstraint){var h=o.pathLenConstraint,p=e.util.createBuffer();p.putInt(h,h.toString(2).length),o.value.value.push(t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,p.getBytes()))}}else if(o.name==="extKeyUsage"){o.value=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[]);var d=o.value.value;for(var v in o){if(o[v]!==!0)continue;v in r?d.push(t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(r[v]).getBytes())):v.indexOf(".")!==-1&&d.push(t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(v).getBytes()))}}else if(o.name==="nsCertType"){var a=0,f=0;o.client&&(f|=128,a=7),o.server&&(f|=64,a=6),o.email&&(f|=32,a=5),o.objsign&&(f|=16,a=4),o.reserved&&(f|=8,a=3),o.sslCA&&(f|=4,a=2),o.emailCA&&(f|=2,a=1),o.objCA&&(f|=1,a=0);var c=String.fromCharCode(a);f!==0&&(c+=String.fromCharCode(f)),o.value=t.create(t.Class.UNIVERSAL,t.Type.BITSTRING,!1,c)}else if(o.name==="subjectAltName"||o.name==="issuerAltName"){o.value=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[]);var m;for(var g=0;g<o.altNames.length;++g){m=o.altNames[g];var c=m.value;m.type===8&&(c=t.oidToDer(c)),o.value.value.push(t.create(t.Class.CONTEXT_SPECIFIC,m.type,!1,c))}}else if(o.name==="subjectKeyIdentifier"){var y=s.generateSubjectKeyIdentifier();o.subjectKeyIdentifier=y.toHex(),o.value=t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,y.getBytes())}if(typeof o.value=="undefined")throw{message:"Extension value not specified.",extension:o}}}s.extensions=i},s.getExtension=function(e){typeof e=="string"&&(e={name:e});var t=null,n;for(var r=0;t===null&&r<s.extensions.length;++r)n=s.extensions[r],e.id&&n.id===e.id?t=n:e.name&&n.name===e.name&&(t=n);return t},s.sign=function(i,o){s.md=o||e.md.sha1.create();var u=r[s.md.algorithm+"WithRSAEncryption"];if(!u)throw{message:"Could not compute certificate digest. Unknown message digest algorithm OID.",algorithm:s.md.algorithm};s.signatureOid=s.siginfo.algorithmOid=u,s.tbsCertificate=n.getTBSCertificate(s);var a=t.toDer(s.tbsCertificate);s.md.update(a.getBytes()),s.signature=i.sign(s.md)},s.verify=function(i){var o=!1,u=i.md;if(u===null){if(i.signatureOid in r){var a=r[i.signatureOid];switch(a){case"sha1WithRSAEncryption":u=e.md.sha1.create();break;case"md5WithRSAEncryption":u=e.md.md5.create();break;case"sha256WithRSAEncryption":u=e.md.sha256.create();break;case"RSASSA-PSS":u=e.md.sha256.create()}}if(u===null)throw{message:"Could not compute certificate digest. Unknown signature OID.",signatureOid:i.signatureOid};var f=i.tbsCertificate||n.getTBSCertificate(i),l=t.toDer(f);u.update(l.getBytes())}if(u!==null){var c=undefined;switch(i.signatureOid){case r.sha1WithRSAEncryption:c=undefined;break;case r["RSASSA-PSS"]:var h,p;h=r[i.signatureParameters.mgf.hash.algorithmOid];if(h===undefined||e.md[h]===undefined)throw{message:"Unsupported MGF hash function.",oid:i.signatureParameters.mgf.hash.algorithmOid,name:h};p=r[i.signatureParameters.mgf.algorithmOid];if(p===undefined||e.mgf[p]===undefined)throw{message:"Unsupported MGF function.",oid:i.signatureParameters.mgf.algorithmOid,name:p};p=e.mgf[p].create(e.md[h].create()),h=r[i.signatureParameters.hash.algorithmOid];if(h===undefined||e.md[h]===undefined)throw{message:"Unsupported RSASSA-PSS hash function.",oid:i.signatureParameters.hash.algorithmOid,name:h};c=e.pss.create(e.md[h].create(),p,i.signatureParameters.saltLength)}o=s.publicKey.verify(u.digest().getBytes(),i.signature,c)}return o},s.isIssuer=function(e){var t=!1,n=s.issuer,r=e.subject;if(n.hash&&r.hash)t=n.hash===r.hash;else if(n.attributes.length===r.attributes.length){t=!0;var i,o;for(var u=0;t&&u<n.attributes.length;++u){i=n.attributes[u],o=r.attributes[u];if(i.type!==o.type||i.value!==o.value)t=!1}}return t},s.generateSubjectKeyIdentifier=function(){var r=t.toDer(n.publicKeyToRSAPublicKey(s.publicKey)),i=e.md.sha1.create();return i.update(r.getBytes()),i.digest()},s.verifySubjectKeyIdentifier=function(){var t=!1,n=r.subjectKeyIdentifier;for(var i=0;i<s.extensions.length;++i){var o=s.extensions[i];if(o.id===n){var u=s.generateSubjectKeyIdentifier().getBytes();return e.util.hexToBytes(o.subjectKeyIdentifier)===u}}return!1},s},n.certificateFromAsn1=function(i,s){var o={},a=[];if(!t.validate(i,u,o,a))throw{message:"Cannot read X.509 certificate. ASN.1 object is not an X509v3 Certificate.",errors:a};if(typeof o.certSignature!="string"){var f="\0";for(var l=0;l<o.certSignature.length;++l)f+=t.toDer(o.certSignature[l]).getBytes();o.certSignature=f}var c=t.derToOid(o.publicKeyOid);if(c!==n.oids.rsaEncryption)throw{message:"Cannot read public key. OID is not RSA."};var h=n.createCertificate();h.version=o.certVersion?o.certVersion.charCodeAt(0):0;var p=e.util.createBuffer(o.certSerialNumber);h.serialNumber=p.toHex(),h.signatureOid=e.asn1.derToOid(o.certSignatureOid),h.signatureParameters=b(h.signatureOid,o.certSignatureParams,!0),h.siginfo.algorithmOid=e.asn1.derToOid(o.certinfoSignatureOid),h.siginfo.parameters=b(h.siginfo.algorithmOid,o.certinfoSignatureParams,!1);var d=e.util.createBuffer(o.certSignature);++d.read,h.signature=d.getBytes();var v=[];o.certValidity1UTCTime!==undefined&&v.push(t.utcTimeToDate(o.certValidity1UTCTime)),o.certValidity2GeneralizedTime!==undefined&&v.push(t.generalizedTimeToDate(o.certValidity2GeneralizedTime)),o.certValidity3UTCTime!==undefined&&v.push(t.utcTimeToDate(o.certValidity3UTCTime)),o.certValidity4GeneralizedTime!==undefined&&v.push(t.generalizedTimeToDate(o.certValidity4GeneralizedTime));if(v.length>2)throw{message:"Cannot read notBefore/notAfter validity times; more than two times were provided in the certificate."};if(v.length<2)throw{message:"Cannot read notBefore/notAfter validity times; they were not provided as either UTCTime or GeneralizedTime."};h.validity.notBefore=v[0],h.validity.notAfter=v[1],h.tbsCertificate=o.tbsCertificate;if(s){h.md=null;if(h.signatureOid in r){var c=r[h.signatureOid];switch(c){case"sha1WithRSAEncryption":h.md=e.md.sha1.create();break;case"md5WithRSAEncryption":h.md=e.md.md5.create();break;case"sha256WithRSAEncryption":h.md=e.md.sha256.create();break;case"RSASSA-PSS":h.md=e.md.sha256.create()}}if(h.md===null)throw{message:"Could not compute certificate digest. Unknown signature OID.",signatureOid:h.signatureOid};var y=t.toDer(h.tbsCertificate);h.md.update(y.getBytes())}var w=e.md.sha1.create();h.issuer.getField=function(e){return m(h.issuer,e)},h.issuer.addField=function(e){_fillMissingFields([e]),h.issuer.attributes.push(e)},h.issuer.attributes=n.RDNAttributesAsArray(o.certIssuer,w),o.certIssuerUniqueId&&(h.issuer.uniqueId=o.certIssuerUniqueId),h.issuer.hash=w.digest().toHex();var E=e.md.sha1.create();return h.subject.getField=function(e){return m(h.subject,e)},h.subject.addField=function(e){_fillMissingFields([e]),h.subject.attributes.push(e)},h.subject.attributes=n.RDNAttributesAsArray(o.certSubject,E),o.certSubjectUniqueId&&(h.subject.uniqueId=o.certSubjectUniqueId),h.subject.hash=E.digest().toHex(),o.certExtensions?h.extensions=g(o.certExtensions):h.extensions=[],h.publicKey=n.publicKeyFromAsn1(o.subjectPublicKeyInfo),h},n.certificationRequestFromAsn1=function(i,s){var o={},u=[];if(!t.validate(i,v,o,u))throw{message:"Cannot read PKCS#10 certificate request. ASN.1 object is not a PKCS#10 CertificationRequest.",errors:u};if(typeof o.csrSignature!="string"){var a="\0";for(var f=0;f<o.csrSignature.length;++f)a+=t.toDer(o.csrSignature[f]).getBytes();o.csrSignature=a}var l=t.derToOid(o.publicKeyOid);if(l!==n.oids.rsaEncryption)throw{message:"Cannot read public key. OID is not RSA."};var c=n.createCertificationRequest();c.version=o.csrVersion?o.csrVersion.charCodeAt(0):0,c.signatureOid=e.asn1.derToOid(o.csrSignatureOid),c.signatureParameters=b(c.signatureOid,o.csrSignatureParams,!0),c.siginfo.algorithmOid=e.asn1.derToOid(o.csrSignatureOid),c.siginfo.parameters=b(c.siginfo.algorithmOid,o.csrSignatureParams,!1);var h=e.util.createBuffer(o.csrSignature);++h.read,c.signature=h.getBytes(),c.certificationRequestInfo=o.certificationRequestInfo;if(s){c.md=null;if(c.signatureOid in r){var l=r[c.signatureOid];switch(l){case"sha1WithRSAEncryption":c.md=e.md.sha1.create();break;case"md5WithRSAEncryption":c.md=e.md.md5.create();break;case"sha256WithRSAEncryption":c.md=e.md.sha256.create();break;case"RSASSA-PSS":c.md=e.md.sha256.create()}}if(c.md===null)throw{message:"Could not compute certification request digest. Unknown signature OID.",signatureOid:c.signatureOid};var p=t.toDer(c.certificationRequestInfo);c.md.update(p.getBytes())}var d=e.md.sha1.create();return c.subject.getField=function(e){return m(c.subject,e)},c.subject.addField=function(e){_fillMissingFields([e]),c.subject.attributes.push(e)},c.subject.attributes=n.RDNAttributesAsArray(o.certificationRequestInfoSubject,d),c.subject.hash=d.digest().toHex(),c.publicKey=n.publicKeyFromAsn1(o.subjectPublicKeyInfo),c.getAttribute=function(e){return m(c.attributes,e)},c.addAttribute=function(e){_fillMissingFields([e]),c.attributes.push(e)},c.attributes=n.CRIAttributesAsArray(o.certificationRequestInfoAttributes),c},n.createCertificationRequest=function(){var s={};s.version=0,s.signatureOid=null,s.signature=null,s.siginfo={},s.siginfo.algorithmOid=null,s.subject={},s.subject.getField=function(e){return m(s.subject,e)},s.subject.addField=function(e){o([e]),s.subject.attributes.push(e)},s.subject.attributes=[],s.subject.hash=null,s.publicKey=null,s.attributes=[],s.getAttribute=function(e){return m(s.attributes,e)},s.addAttribute=function(e){o([e]),s.attributes.push(e)},s.md=null;var o=function(e){var t;for(var r=0;r<e.length;++r){t=e[r],typeof t.name=="undefined"&&(t.type&&t.type in n.oids?t.name=n.oids[t.type]:t.shortName&&t.shortName in i&&(t.name=n.oids[i[t.shortName]]));if(typeof t.type=="undefined"){if(!(t.name&&t.name in n.oids))throw{message:"Attribute type not specified.",attribute:t};t.type=n.oids[t.name]}typeof t.shortName=="undefined"&&t.name&&t.name in i&&(t.shortName=i[t.name]);if(typeof t.value=="undefined")throw{message:"Attribute value not specified.",attribute:t}}};return s.setSubject=function(e){o(e),s.subject.attributes=e,s.subject.hash=null},s.setAttributes=function(e){o(e),s.attributes=e},s.sign=function(i,o){s.md=o||e.md.sha1.create();var u=r[s.md.algorithm+"WithRSAEncryption"];if(!u)throw{message:"Could not compute certification request digest. Unknown message digest algorithm OID.",algorithm:s.md.algorithm};s.signatureOid=s.siginfo.algorithmOid=u,s.certificationRequestInfo=n.getCertificationRequestInfo(s);var a=t.toDer(s.certificationRequestInfo);s.md.update(a.getBytes()),s.signature=i.sign(s.md)},s.verify=function(){var i=!1,o=s.md;if(o===null){if(s.signatureOid in r){var u=r[s.signatureOid];switch(u){case"sha1WithRSAEncryption":o=e.md.sha1.create();break;case"md5WithRSAEncryption":o=e.md.md5.create();break;case"sha256WithRSAEncryption":o=e.md.sha256.create();break;case"RSASSA-PSS":o=e.md.sha256.create()}}if(o===null)throw{message:"Could not compute certification request digest. Unknown signature OID.",signatureOid:s.signatureOid};var a=s.certificationRequestInfo||n.getCertificationRequestInfo(s),f=t.toDer(a);o.update(f.getBytes())}if(o!==null){var l=undefined;switch(s.signatureOid){case r.sha1WithRSAEncryption:l=undefined;break;case r["RSASSA-PSS"]:var c,h;c=r[s.signatureParameters.mgf.hash.algorithmOid];if(c===undefined||e.md[c]===undefined)throw{message:"Unsupported MGF hash function.",oid:s.signatureParameters.mgf.hash.algorithmOid,name:c};h=r[s.signatureParameters.mgf.algorithmOid];if(h===undefined||e.mgf[h]===undefined)throw{message:"Unsupported MGF function.",oid:s.signatureParameters.mgf.algorithmOid,name:h};h=e.mgf[h].create(e.md[c].create()),c=r[s.signatureParameters.hash.algorithmOid];if(c===undefined||e.md[c]===undefined)throw{message:"Unsupported RSASSA-PSS hash function.",oid:s.signatureParameters.hash.algorithmOid,name:c};l=e.pss.create(e.md[c].create(),h,s.signatureParameters.saltLength)}i=s.publicKey.verify(o.digest().getBytes(),s.signature,l)}return i},s},n.getTBSCertificate=function(r){var i=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.CONTEXT_SPECIFIC,0,!0,[t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,String.fromCharCode(r.version))]),t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,e.util.hexToBytes(r.serialNumber)),t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(r.siginfo.algorithmOid).getBytes()),S(r.siginfo.algorithmOid,r.siginfo.parameters)]),w(r.issuer),t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.UTCTIME,!1,t.dateToUtcTime(r.validity.notBefore)),t.create(t.Class.UNIVERSAL,t.Type.UTCTIME,!1,t.dateToUtcTime(r.validity.notAfter))]),w(r.subject),n.publicKeyToAsn1(r.publicKey)]);return r.issuer.uniqueId&&i.value.push(t.create(t.Class.CONTEXT_SPECIFIC,1,!0,[t.create(t.Class.UNIVERSAL,t.Type.BITSTRING,!1,String.fromCharCode(0)+r.issuer.uniqueId)])),r.subject.uniqueId&&i.value.push(t.create(t.Class.CONTEXT_SPECIFIC,2,!0,[t.create(t.Class.UNIVERSAL,t.Type.BITSTRING,!1,String.fromCharCode(0)+r.subject.uniqueId)])),r.extensions.length>0&&i.value.push(E(r.extensions)),i},n.getCertificationRequestInfo=function(e){var r=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,String.fromCharCode(e.version)),w(e.subject),n.publicKeyToAsn1(e.publicKey),x(e)]);return r},n.distinguishedNameToAsn1=function(e){return w(e)},n.certificateToAsn1=function(e){var r=e.tbsCertificate||n.getTBSCertificate(e);return t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[r,t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(e.signatureOid).getBytes()),S(e.signatureOid,e.signatureParameters)]),t.create(t.Class.UNIVERSAL,t.Type.BITSTRING,!1,String.fromCharCode(0)+e.signature)])},n.certificationRequestToAsn1=function(e){var r=e.certificationRequestInfo||n.getCertificationRequestInfo(e);return t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[r,t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(e.signatureOid).getBytes()),S(e.signatureOid,e.signatureParameters)]),t.create(t.Class.UNIVERSAL,t.Type.BITSTRING,!1,String.fromCharCode(0)+e.signature)])},n.createCaStore=function(t){var r={certs:{}};r.getIssuer=function(t){var i=null;if(!t.issuer.hash){var s=e.md.sha1.create();t.issuer.attributes=n.RDNAttributesAsArray(w(t.issuer),s),t.issuer.hash=s.digest().toHex()}if(t.issuer.hash in r.certs){i=r.certs[t.issuer.hash];if(e.util.isArray(i))throw{message:"Resolving multiple issuer matches not implemented yet."}}return i},r.addCertificate=function(t){typeof t=="string"&&(t=e.pki.certificateFromPem(t));if(!t.subject.hash){var i=e.md.sha1.create();t.subject.attributes=n.RDNAttributesAsArray(w(t.subject),i),t.subject.hash=i.digest().toHex()}if(t.subject.hash in r.certs){var s=r.certs[t.subject.hash];e.util.isArray(s)||(s=[s]),s.push(t)}else r.certs[t.subject.hash]=t};if(t)for(var i=0;i<t.length;++i){var s=t[i];r.addCertificate(s)}return r},n.certificateError={bad_certificate:"forge.pki.BadCertificate",unsupported_certificate:"forge.pki.UnsupportedCertificate",certificate_revoked:"forge.pki.CertificateRevoked",certificate_expired:"forge.pki.CertificateExpired",certificate_unknown:"forge.pki.CertificateUnknown",unknown_ca:"forge.pki.UnknownCertificateAuthority"},n.verifyCertificateChain=function(t,r,i){r=r.slice(0);var s=r.slice(0),o=new Date,u=!0,a=null,f=0,l=null;do{var c=r.shift();if(o<c.validity.notBefore||o>c.validity.notAfter)a={message:"Certificate is not valid yet or has expired.",error:n.certificateError.certificate_expired,notBefore:c.validity.notBefore,notAfter:c.validity.notAfter,now:o};else{var h=!1;if(r.length>0){l=r[0];try{h=l.verify(c)}catch(p){}}else{var d=t.getIssuer(c);if(d===null)a={message:"Certificate is not trusted.",error:n.certificateError.unknown_ca};else{e.util.isArray(d)||(d=[d]);while(!h&&d.length>0){l=d.shift();try{h=l.verify(c)}catch(p){}}}}a===null&&!h&&(a={message:"Certificate signature is invalid.",error:n.certificateError.bad_certificate})}a===null&&!c.isIssuer(l)&&(a={message:"Certificate issuer is invalid.",error:n.certificateError.bad_certificate});if(a===null){var v={keyUsage:!0,basicConstraints:!0};for(var m=0;a===null&&m<c.extensions.length;++m){var g=c.extensions[m];g.critical&&!(g.name in v)&&(a={message:"Certificate has an unsupported critical extension.",error:n.certificateError.unsupported_certificate})}}if(!u||r.length===0&&!l){var y=c.getExtension("basicConstraints"),b=c.getExtension("keyUsage");b!==null&&(!b.keyCertSign||y===null)&&(a={message:"Certificate keyUsage or basicConstraints conflict or indicate that the certificate is not a CA. If the certificate is the only one in the chain or isn't the first then the certificate must be a valid CA.",error:n.certificateError.bad_certificate}),a===null&&y!==null&&!y.cA&&(a={message:"Certificate basicConstraints indicates the certificate is not a CA.",error:n.certificateError.bad_certificate})}var w=a===null?!0:a.error,E=i?i(w,f,s):w;if(E!==!0){w===!0&&(a={message:"The application rejected the certificate.",error:n.certificateError.bad_certificate});if(E||E===0)typeof E=="object"&&!e.util.isArray(E)?(E.message&&(a.message=E.message),E.error&&(a.error=E.error)):typeof E=="string"&&(a.error=E);throw a}a=null,u=!1,++f}while(r.length>0);return!0},n.publicKeyFromAsn1=function(r){var i={},u=[];if(t.validate(r,s,i,u)){var a=t.derToOid(i.publicKeyOid);if(a!==n.oids.rsaEncryption)throw{message:"Cannot read public key. Unknown OID.",oid:a};r=i.rsaPublicKey}u=[];if(!t.validate(r,o,i,u))throw{message:"Cannot read public key. ASN.1 object does not contain an RSAPublicKey.",errors:u};var f=e.util.createBuffer(i.publicKeyModulus).toHex(),l=e.util.createBuffer(i.publicKeyExponent).toHex();return n.setRsaPublicKey(new BigInteger(f,16),new BigInteger(l,16))},n.publicKeyToAsn1=n.publicKeyToSubjectPublicKeyInfo=function(e){return t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.oids.rsaEncryption).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.NULL,!1,"")]),t.create(t.Class.UNIVERSAL,t.Type.BITSTRING,!1,[n.publicKeyToRSAPublicKey(e)])])},n.publicKeyToRSAPublicKey=function(e){return t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,y(e.n)),t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,y(e.e))])},n.privateKeyFromAsn1=function(r){var i={},s=[];t.validate(r,a,i,s)&&(r=t.fromDer(e.util.createBuffer(i.privateKey))),i={},s=[];if(!t.validate(r,f,i,s))throw{message:"Cannot read private key. ASN.1 object does not contain an RSAPrivateKey.",errors:s};var o,u,l,c,h,p,d,v;return o=e.util.createBuffer(i.privateKeyModulus).toHex(),u=e.util.createBuffer(i.privateKeyPublicExponent).toHex(),l=e.util.createBuffer(i.privateKeyPrivateExponent).toHex(),c=e.util.createBuffer(i.privateKeyPrime1).toHex(),h=e.util.createBuffer(i.privateKeyPrime2).toHex(),p=e.util.createBuffer(i.privateKeyExponent1).toHex(),d=e.util.createBuffer(i.privateKeyExponent2).toHex(),v=e.util.createBuffer(i.privateKeyCoefficient).toHex(),n.setRsaPrivateKey(new BigInteger(o,16),new BigInteger(u,16),new BigInteger(l,16),new BigInteger(c,16),new BigInteger(h,16),new BigInteger(p,16),new BigInteger(d,16),new BigInteger(v,16))},n.privateKeyToAsn1=n.privateKeyToRSAPrivateKey=function(e){return t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,String.fromCharCode(0)),t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,y(e.n)),t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,y(e.e)),t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,y(e.d)),t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,y(e.p)),t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,y(e.q)),t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,y(e.dP)),t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,y(e.dQ)),t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,y(e.qInv))])},n.wrapRsaPrivateKey=function(e){return t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,"\0"),t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(r.rsaEncryption).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.NULL,!1,"")]),t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,t.toDer(e).getBytes())])},n.encryptPrivateKeyInfo=function(n,i,s){s=s||{},s.saltSize=s.saltSize||8,s.count=s.count||2048,s.algorithm=s.algorithm||"aes128";var o=e.random.getBytes(s.saltSize),u=s.count,a=e.util.createBuffer();a.putInt16(u);var f,l,c;if(s.algorithm.indexOf("aes")===0){var h;if(s.algorithm==="aes128")f=16,h=r["aes128-CBC"];else if(s.algorithm==="aes192")f=24,h=r["aes192-CBC"];else{if(s.algorithm!=="aes256")throw{message:"Cannot encrypt private key. Unknown encryption algorithm.",algorithm:s.algorithm};f=32,h=r["aes256-CBC"]}var p=e.pkcs5.pbkdf2(i,o,u,f),d=e.random.getBytes(16),v=e.aes.createEncryptionCipher(p);v.start(d),v.update(t.toDer(n)),v.finish(),c=v.output.getBytes(),l=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(r.pkcs5PBES2).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(r.pkcs5PBKDF2).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,o),t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,a.getBytes())])]),t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(h).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,d)])])])}else{if(s.algorithm!=="3des")throw{message:"Cannot encrypt private key. Unknown encryption algorithm.",algorithm:s.algorithm};f=24;var m=new e.util.ByteBuffer(o),p=e.pkcs12.generateKey(i,m,1,u,f),d=e.pkcs12.generateKey(i,m,2,u,f),v=e.des.createEncryptionCipher(p);v.start(d),v.update(t.toDer(n)),v.finish(),c=v.output.getBytes(),l=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(r["pbeWithSHAAnd3-KeyTripleDES-CBC"]).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,o),t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,a.getBytes())])])}var g=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[l,t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,c)]);return g},n.pbe.getCipherForPBES2=function(r,i,s){var o={},u=[];if(!t.validate(i,c,o,u))throw{message:"Cannot read password-based-encryption algorithm parameters. ASN.1 object is not a supported EncryptedPrivateKeyInfo.",errors:u};r=t.derToOid(o.kdfOid);if(r!==n.oids.pkcs5PBKDF2)throw{message:"Cannot read encrypted private key. Unsupported key derivation function OID.",oid:r,supportedOids:["pkcs5PBKDF2"]};r=t.derToOid(o.encOid);if(r!==n.oids["aes128-CBC"]&&r!==n.oids["aes192-CBC"]&&r!==n.oids["aes256-CBC"])throw{message:"Cannot read encrypted private key. Unsupported encryption scheme OID.",oid:r,supportedOids:["aes128-CBC","aes192-CBC","aes256-CBC"]};var a=o.kdfSalt,f=e.util.createBuffer(o.kdfIterationCount);f=f.getInt(f.length()<<3);var l;r===n.oids["aes128-CBC"]?l=16:r===n.oids["aes192-CBC"]?l=24:r===n.oids["aes256-CBC"]&&(l=32);var h=e.pkcs5.pbkdf2(s,a,f,l),p=o.encIv,d=e.aes.createDecryptionCipher(h);return d.start(p),d},n.pbe.getCipherForPKCS12PBE=function(r,i,s){var o={},u=[];if(!t.validate(i,h,o,u))throw{message:"Cannot read password-based-encryption algorithm parameters. ASN.1 object is not a supported EncryptedPrivateKeyInfo.",errors:u};var a=e.util.createBuffer(o.salt),f=e.util.createBuffer(o.iterations);f=f.getInt(f.length()<<3);var l,c,p;switch(r){case n.oids["pbeWithSHAAnd3-KeyTripleDES-CBC"]:l=24,c=8,p=e.des.startDecrypting;break;case n.oids["pbewithSHAAnd40BitRC2-CBC"]:l=5,c=8,p=function(t,n){var r=e.rc2.createDecryptionCipher(t,40);return r.start(n,null),r};break;default:throw{message:"Cannot read PKCS #12 PBE data block. Unsupported OID.",oid:r}}var d=e.pkcs12.generateKey(s,a,1,f,l),v=e.pkcs12.generateKey(s,a,2,f,c);return p(d,v)},n.pbe.getCipher=function(e,t,r){switch(e){case n.oids.pkcs5PBES2:return n.pbe.getCipherForPBES2(e,t,r);case n.oids["pbeWithSHAAnd3-KeyTripleDES-CBC"]:case n.oids["pbewithSHAAnd40BitRC2-CBC"]:return n.pbe.getCipherForPKCS12PBE(e,t,r);default:throw{message:"Cannot read encrypted PBE data block. Unsupported OID.",oid:e,supportedOids:["pkcs5PBES2","pbeWithSHAAnd3-KeyTripleDES-CBC","pbewithSHAAnd40BitRC2-CBC"]}}},n.decryptPrivateKeyInfo=function(r,i){var s=null,o={},u=[];if(!t.validate(r,l,o,u))throw{message:"Cannot read encrypted private key. ASN.1 object is not a supported EncryptedPrivateKeyInfo.",errors:u};var a=t.derToOid(o.encryptionOid),f=n.pbe.getCipher(a,o.encryptionParams,i),c=e.util.createBuffer(o.encryptedData);return f.update(c),f.finish()&&(s=t.fromDer(f.output)),s},n.encryptedPrivateKeyToPem=function(n,r){var i={type:"ENCRYPTED PRIVATE KEY",body:t.toDer(n).getBytes()};return e.pem.encode(i,{maxline:r})},n.encryptedPrivateKeyFromPem=function(n){var r=e.pem.decode(n)[0];if(r.type!=="ENCRYPTED PRIVATE KEY")throw{message:'Could not convert encrypted private key from PEM; PEM header type is "ENCRYPTED PRIVATE KEY".',headerType:r.type};if(r.procType&&r.procType.type==="ENCRYPTED")throw{message:"Could not convert encrypted private key from PEM; PEM is encrypted."};return t.fromDer(r.body)},n.encryptRsaPrivateKey=function(r,i,s){s=s||{};if(!s.legacy){var o=n.wrapRsaPrivateKey(n.privateKeyToAsn1(r));return o=n.encryptPrivateKeyInfo(o,i,s),n.encryptedPrivateKeyToPem(o)}var u,a,f,l;switch(s.algorithm){case"aes128":u="AES-128-CBC",f=16,a=e.random.getBytes(16),l=e.aes.createEncryptionCipher;break;case"aes192":u="AES-192-CBC",f=24,a=e.random.getBytes(16),l=e.aes.createEncryptionCipher;break;case"aes256":u="AES-256-CBC",f=32,a=e.random.getBytes(16),l=e.aes.createEncryptionCipher;break;case"3des":u="DES-EDE3-CBC",f=24,a=e.random.getBytes(8),l=e.des.createEncryptionCipher;break;default:throw{message:'Could not encrypt RSA private key; unsupported encryption algorithm "'+s.algorithm+'".',algorithm:s.algorithm}}var c=T(i,a.substr(0,8),f),h=l(c);h.start(a),h.update(t.toDer(n.privateKeyToAsn1(r))),h.finish();var p={type:"RSA PRIVATE KEY",procType:{version:"4",type:"ENCRYPTED"},dekInfo:{algorithm:u,parameters:e.util.bytesToHex(a).toUpperCase()},body:h.output.getBytes()};return e.pem.encode(p)},n.decryptRsaPrivateKey=function(r,i){var s=null,o=e.pem.decode(r)[0];if(o.type!=="ENCRYPTED PRIVATE KEY"&&o.type!=="PRIVATE KEY"&&o.type!=="RSA PRIVATE KEY")throw{message:'Could not convert private key from PEM; PEM header type is not "ENCRYPTED PRIVATE KEY", "PRIVATE KEY", or "RSA PRIVATE KEY".',headerType:o.type};if(o.procType&&o.procType.type==="ENCRYPTED"){var u,a;switch(o.dekInfo.algorithm){case"DES-EDE3-CBC":u=24,a=e.des.createDecryptionCipher;break;case"AES-128-CBC":u=16,a=e.aes.createDecryptionCipher;break;case"AES-192-CBC":u=24,a=e.aes.createDecryptionCipher;break;case"AES-256-CBC":u=32,a=e.aes.createDecryptionCipher;break;case"RC2-40-CBC":u=5,a=function(t){return e.rc2.createDecryptionCipher(t,40)};break;case"RC2-64-CBC":u=8,a=function(t){return e.rc2.createDecryptionCipher(t,64)};break;case"RC2-128-CBC":u=16,a=function(t){return e.rc2.createDecryptionCipher(t,128)};break;default:throw{message:'Could not decrypt private key; unsupported encryption algorithm "'+o.dekInfo.algorithm+'".',algorithm:o.dekInfo.algorithm}}var f=e.util.hexToBytes(o.dekInfo.parameters),l=T(i,f.substr(0,8),u),c=a(l);c.start(f),c.update(e.util.createBuffer(o.body));if(!c.finish())return s;s=c.output.getBytes()}else s=o.body;return o.type==="ENCRYPTED PRIVATE KEY"?s=n.decryptPrivateKeyInfo(t.fromDer(s),i):s=t.fromDer(s),s!==null&&(s=n.privateKeyFromAsn1(s)),s},n.setRsaPublicKey=n.rsa.setPublicKey,n.setRsaPrivateKey=n.rsa.setPrivateKey}var t="pki";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/pki",["require","module","./aes","./asn1","./des","./jsbn","./md","./mgf","./oids","./pem","./pbkdf2","./pkcs12","./pss","./random","./rc2","./rsa","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e,t,n,r,i,s,o){var u={privateKey:"-----BEGIN RSA PRIVATE KEY-----\r\nMIICXQIBAAKBgQDL0EugUiNGMWscLAVM0VoMdhDZEJOqdsUMpx9U0YZI7szokJqQ\r\nNIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMGTkP3VF29vXBo+dLq5e+8VyAy\r\nQ3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGtvnM+z0MYDdKo80efzwIDAQAB\r\nAoGAIzkGONi5G+JifmXlLJdplom486p3upf4Ce2/7mqfaG9MnkyPSairKD/JXvfh\r\nNNWkkN8DKKDKBcVVElPgORYT0qwrWc7ueLBMUCbRXb1ZyfEulimG0R3kjUh7NYau\r\nDaIkVgfykXGSQMZx8FoaT6L080zd+0emKDDYRrb+/kgJNJECQQDoUZoiC2K/DWNY\r\nh3/ppZ0ane2y4SBmJUHJVMPQ2CEgxsrJTxet668ckNCKaOP/3VFPoWC41f17DvKq\r\nnoYINNntAkEA4JbZBZBVUrQFhHlrpXT4jzqtO2RlKZzEq8qmFZfEErxOT1WMyyCi\r\nlAQ5gUKardo1Kf0omC8Xq/uO9ZYdED55KwJBALs6cJ65UFaq4oLJiQPzLd7yokuE\r\ndcj8g71PLBTW6jPxIiMFNA89nz3FU9wIVp+xbMNhSoMMKqIPVPC+m0Rn260CQQDA\r\nI83fWK/mZWUjBM33a68KumRiH238v8XyQxj7+C8i6D8G2GXvkigFAehAkb7LZZd+\r\nKLuGFyPlWv3fVWHf99KpAkBQFKk3MRMl6IGJZUEFQe4l5whm8LkGU4acSqv9B3xt\r\nqROkCrsFrMPqjuuzEmyHoQZ64r2PLJg7FOuyhBnQUOt4\r\n-----END RSA PRIVATE KEY-----\r\n",publicKey:"-----BEGIN PUBLIC KEY-----\r\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDL0EugUiNGMWscLAVM0VoMdhDZ\r\nEJOqdsUMpx9U0YZI7szokJqQNIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMG\r\nTkP3VF29vXBo+dLq5e+8VyAyQ3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGt\r\nvnM+z0MYDdKo80efzwIDAQAB\r\n-----END PUBLIC KEY-----\r\n"},a="9200ece65cdaed36bcc20b94c65af852e4f88f0b4fe5b249d54665f815992ac43a1399e65d938c6a7f16dd39d971a53ca66523209dbbfbcb67afa579dbb0c220672813d9e6f4818f29b9becbb29da2032c5e422da97e0c39bfb7a2e7d568615a5073af0337ff215a8e1b2332d668691f4fb731440055420c24ac451dd3c913f4";describe("rsa",function(){it("should generate 512 bit key pair",function(){var i=n.generateKeyPair(512);e.equal(t.privateKeyToPem(i.privateKey).indexOf("-----BEGIN RSA PRIVATE KEY-----"),0),e.equal(t.publicKeyToPem(i.publicKey).indexOf("-----BEGIN PUBLIC KEY-----"),0);var s=r.sha1.create();s.update("0123456789abcdef");var o=i.privateKey.sign(s);e.ok(i.publicKey.verify(s.digest().getBytes(),o))}),it("should convert private key to/from PEM",function(){var n=t.privateKeyFromPem(u.privateKey);e.equal(t.privateKeyToPem(n),u.privateKey)}),it("should convert public key to/from PEM",function(){var n=t.publicKeyFromPem(u.publicKey);e.equal(t.publicKeyToPem(n),u.publicKey)}),function(){var n=["aes128","aes192","aes256","3des"];for(var r=0;r<n.length;++r){var i=n[r];it("should PKCS#8 encrypt and decrypt private key with "+i,function(){var n=t.privateKeyFromPem(u.privateKey),r=t.encryptRsaPrivateKey(n,"password",{algorithm:i}),n=t.decryptRsaPrivateKey(r,"password");e.equal(t.privateKeyToPem(n),u.privateKey)})}}(),function(){var n=["aes128","aes192","aes256","3des"];for(var r=0;r<n.length;++r){var i=n[r];it("should legacy (OpenSSL style) encrypt and decrypt private key with "+i,function(){var n=t.privateKeyFromPem(u.privateKey),r=t.encryptRsaPrivateKey(n,"password",{algorithm:i,legacy:!0}),n=t.decryptRsaPrivateKey(r,"password");e.equal(t.privateKeyToPem(n),u.privateKey)})}}(),it("should verify signature",function(){var n=t.publicKeyFromPem(u.publicKey),i=r.sha1.create();i.update("0123456789abcdef");var s=o.hexToBytes(a);e.ok(n.verify(i.digest().getBytes(),s))}),it("should sign and verify",function(){var n=t.privateKeyFromPem(u.privateKey),i=t.publicKeyFromPem(u.publicKey),s=r.sha1.create();s.update("0123456789abcdef");var o=n.sign(s);e.ok(i.verify(s.digest().getBytes(),o))}),function(){function a(n){var u=n.keySize;it("should rsa encrypt using a "+u+"-bit key",function(){var r="it need's to be about 20% cooler",i=t.publicKeyFromPem(n.publicKeyPem),s=i.encrypt(r);i=t.privateKeyFromPem(n.privateKeyPem),e.equal(i.decrypt(s),r)}),it("should rsa decrypt using a "+u+"-bit key",function(){var r=o.decode64(n.encrypted),i=t.privateKeyFromPem(n.privateKeyPem);e.equal(i.decrypt(r),"too many secrets\n")}),it("should rsa sign using a "+u+"-bit key and PKCS#1 v1.5 padding",function(){var i=t.privateKeyFromPem(n.privateKeyPem),s=r.sha1.create();s.start(),s.update("just testing");var u=o.decode64(n.signature);e.equal(i.sign(s),u)}),it("should verify an rsa signature using a "+u+"-bit key and PKCS#1 v1.5 padding",function(){var i=o.decode64(n.signature),s=t.publicKeyFromPem(n.publicKeyPem),u=r.sha1.create();u.start(),u.update("just testing"),e.equal(s.verify(u.digest().getBytes(),i),!0)}),it("should rsa sign using a "+u+"-bit key and PSS padding",function(){var o=t.privateKeyFromPem(n.privateKeyPem),u=t.publicKeyFromPem(n.publicKeyPem),a=r.sha1.create();a.start(),a.update("just testing");var f=s.create(r.sha1.create(),i.mgf1.create(r.sha1.create()),20),l=o.sign(a,f);a.start(),a.update("just testing"),e.equal(u.verify(a.digest().getBytes(),l,f),!0)}),it("should verify an rsa signature using a "+u+"-bit key and PSS padding",function(){var u=o.decode64(n.signaturePss),a=t.publicKeyFromPem(n.publicKeyPem),f=r.sha1.create();f.start(),f.update("just testing");var l=s.create(r.sha1.create(),i.mgf1.create(r.sha1.create()),20);e.equal(a.verify(f.digest().getBytes(),u,l),!0)})}var n=[{keySize:1024,privateKeyPem:"-----BEGIN RSA PRIVATE KEY-----\r\nMIICWwIBAAKBgQDCjvkkLWNTeYXqEsqGiVCW/pDt3/qAodNMHcU9gOU2rxeWwiRu\r\nOhhLqmMxXHLi0oP5Xmg0m7zdOiLMEyzzyRzdp21aqp3k5qtuSDkZcf1prsp1jpYm\r\n6z9EGpaSHb64BCuUsQGmUPKutd5RERKHGZXtiRuvvIyue7ETq6VjXrOUHQIDAQAB\r\nAoGAOKeBjTNaVRhyEnNeXkbmHNIMSfiK7aIx8VxJ71r1ZDMgX1oxWZe5M29uaxVM\r\nrxg2Lgt7tLYVDSa8s0hyMptBuBdy3TJUWruDx85uwCrWnMerCt/iKVBS22fv5vm0\r\nLEq/4gjgIVTZwgqbVxGsBlKcY2VzxAfYqYzU8EOZBeNhZdECQQDy+PJAPcUN2xOs\r\n6qy66S91x6y3vMjs900OeX4+bgT4VSVKmLpqRTPizzcL07tT4+Y+pAAOX6VstZvZ\r\n6iFDL5rPAkEAzP1+gaRczboKoJWKJt0uEMUmztcY9NXJFDmjVLqzKwKjcAoGgIal\r\nh+uBFT9VJ16QajC7KxTRLlarzmMvspItUwJAeUMNhEpPwm6ID1DADDi82wdgiALM\r\nNJfn+UVhYD8Ac//qsKQwxUDseFH6owh1AZVIIBMxg/rwUKUCt2tGVoW3uQJAIt6M\r\nAml/D8+xtxc45NuC1n9y1oRoTl1/Ut1rFyKbD5nnS0upR3uf9LruvjqDtaq0Thvz\r\n+qQT4RoFJ5pfprSO2QJAdMkfNWRqECfAhZyQuUrapeWU3eQ0wjvktIynCIwiBDd2\r\nMfjmVXzBJhMk6dtINt+vBEITVQEOdtyTgDt0y3n2Lw==\r\n-----END RSA PRIVATE KEY-----\r\n",publicKeyPem:"-----BEGIN PUBLIC KEY-----\r\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCjvkkLWNTeYXqEsqGiVCW/pDt\r\n3/qAodNMHcU9gOU2rxeWwiRuOhhLqmMxXHLi0oP5Xmg0m7zdOiLMEyzzyRzdp21a\r\nqp3k5qtuSDkZcf1prsp1jpYm6z9EGpaSHb64BCuUsQGmUPKutd5RERKHGZXtiRuv\r\nvIyue7ETq6VjXrOUHQIDAQAB\r\n-----END PUBLIC KEY-----\r\n",encrypted:"jsej3OoacmJ1VjWrlw68F+drnQORAuKAqVu6RMbz1xSXjzA355vctrJZXolRU0mvzuu/6VuNynkKGGyRJ6DHt85CvwTMChw4tOMV4Dy6bgnUt3j+DZA2sWTwFhOlpzvNQMK70QpuqrXtOZmAO59EwoDeJkW/iH6t4YzNOVYo9Jg=",signature:"GT0/3EV2zrXxPd1ydijJq3R7lkI4c0GtcprgpG04dSECv/xyXtikuzivxv7XzUdHpu6QiYmM0xE4D4i7LK3Mzy+f7aB4o/dg8XXO3htLiBzVI+ZJCRh06RdYctPtclAWmyZikZ8Etw3NnA/ldKuG4jApbwRb21UFm5gYLrJ4SP4=",signaturePss:"F4xffaANDBjhFxeSJx8ANuBbdhaWZjUHRQh4ueYQMPPCaR2mpwdqxE04sbgNgIiZzBuLIAI4HpTMMoDk3Rruhjefx3+9UhzTxgB0hRI+KzRChRs+ToltWWDZdYzt9T8hfTlELeqT4V8HgjDuteO/IAvIVlRIBwMNv53Iebu1FY4="},{keySize:1025,privateKeyPem:"-----BEGIN RSA PRIVATE KEY-----\r\nMIICXgIBAAKBgQGIkej4PDlAigUh5fbbHp1WXuTHhOdQfAke+LoH0TM4uzn0QmgK\r\nSJqxzB1COJ5o0DwZw/NR+CNy7NUrly+vmh2YPwsaqN+AsYBF9qsF93oN8/TBtaL/\r\nGRoRGpDcCglkj1kZnDaWR79NsG8mC0TrvQCkcCLOP0c2Ux1hRbntOetGXwIDAQAB\r\nAoGBAIaJWsoX+ZcAthmT8jHOICXFh6pJBe0zVPzkSPz82Q0MPSRUzcsYbsuYJD7Z\r\noJBTLQW3feANpjhwqe2ydok7y//ONm3Th53Bcu8jLfoatg4KYxNFIwXEO10mPOld\r\nVuDIGrBkTABe6q2P5PeUKGCKLT6i/u/2OTXTrQiJbQ0gU8thAkEBjqcFivWMXo34\r\nCb9/EgfWCCtv9edRMexgvcFMysRsbHJHDK9JjRLobZltwtAv3cY7F3a/Cu1afg+g\r\njAzm5E3gowJBAPwYFHTLzaZToxFKNQztWrPsXF6YfqHpPUUIpT4UzL6DhGG0M00U\r\nqMyhkYRRqmGOSrSovjg2hjM2643MUUWxUxUCQDPkk/khu5L3YglKzyy2rmrD1MAq\r\ny0v3XCR3TBq89Ows+AizrJxbkLvrk/kfBowU6M5GG9o9SWFNgXWZnFittocCQQDT\r\ne1P1419DUFi1UX6NuLTlybx3sxBQvf0jY6xUF1jn3ib5XBXJbTJqcIRF78iyjI9J\r\nXWIugDc20bTsQOJRSAA9AkEBU8kpueHBaiXTikqqlK9wvc2Lp476hgyKVmVyBGye\r\n9TLTWkTCzDPtManLy47YtXkXnmyazS+DlKFU61XAGEnZfg==\r\n-----END RSA PRIVATE KEY-----\r\n",publicKeyPem:"-----BEGIN PUBLIC KEY-----\r\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQGIkej4PDlAigUh5fbbHp1WXuTH\r\nhOdQfAke+LoH0TM4uzn0QmgKSJqxzB1COJ5o0DwZw/NR+CNy7NUrly+vmh2YPwsa\r\nqN+AsYBF9qsF93oN8/TBtaL/GRoRGpDcCglkj1kZnDaWR79NsG8mC0TrvQCkcCLO\r\nP0c2Ux1hRbntOetGXwIDAQAB\r\n-----END PUBLIC KEY-----\r\n",encrypted:"AOVeCUN8BOVkZvt4mxyNn/yCYE1MZ40A3e/osh6EvCBcJ09hyYbx7bzKSrdkhRnDyW0pGtgP352CollasllQZ9HlfI2Wy9zKM0aYZZn8OHBA+60Tc3xHHDGznLZqggUKuhoNpj+faVZ1uzb285eTpQQa+4mLUue2svJD4ViM8+ng",signature:"AFSx0axDYXlF2rO3ofgUhYSI8ZlIWtJUUZ62PhgdBp9O5zFqMX3DXoiov1e7NenSOz1khvTSMctFWzKP3GU3F0yewe+Yd3UAZE0dM8vAxigSSfAchUkBDmp9OFuszUie63zwWwpG+gXtvyfueZs1RniBvW1ZmXJvS+HFgX4ouzwd",signaturePss:"AQvBdhAXDpu+7RpcybMgwuTUk6w+qa08Lcq3G1xHY4kC7ZUzauZd/Jn9e0ePKApDqs7eDNAOV+dQkU2wiH/uBg6VGelzb0hFwcpSLyBW92Vw0q3GlzY7myWn8qnNzasrt110zFflWQa1GiuzH/C8f+Z82/MzlWDxloJIYbq2PRC8"},{keySize:1031,privateKeyPem:"-----BEGIN RSA PRIVATE KEY-----\r\nMIICXwIBAAKBgWyeKqA2oA4klYrKT9hjjutYQksJNN0cxwaQwIm9AYiLxOsYtT/C\r\novJx5Oy1EvkbYQbfvYsGISUx9bW8yasZkTHR55IbW3+UptvQjTDtdxBQTgQOpsAh\r\nBJtZYY3OmyH9Sj3F3oB//oyriNoj0QYyfsvlO8UsMmLzpnf6qfZBDHA/9QIDAQAB\r\nAoGBBj/3ne5muUmbnTfU7lOUNrCGaADonMx6G0ObAJHyk6PPOePbEgcmDyNEk+Y7\r\naEAODjIzmttIbvZ39/Qb+o9nDmCSZC9VxiYPP+rjOzPglCDT5ks2Xcjwzd3If6Ya\r\nUw6P31Y760OCYeTb4Ib+8zz5q51CkjkdX5Hq/Yu+lZn0Vx7BAkENo83VfL+bwxTm\r\nV7vR6gXqTD5IuuIGHL3uTmMNNURAP6FQDHu//duipys83iMChcOeXtboE16qYrO0\r\n9KC0cqL4JQJBB/aYo/auVUGZA6f50YBp0b2slGMk9TBQG0iQefuuSyH4kzKnt2e3\r\nQ40SBmprcM+DfttWJ11bouec++goXjz+95ECQQyiTWYRxulgKVuyqCYnvpLnTEnR\r\n0MoYlVTHBriVPkLErYaYCYgse+SNM1+N4p/Thv6KmkUcq/Lmuc5DSRfbl1iBAkEE\r\n7GKtJQvd7EO1bfpXnARQx+tWhwHHkgpFBBVHReMZ0rQEFhJ5o2c8HZEiZFNvGO2c\r\n1fErP14zlu2JFZ03vpCI8QJBCQz9HL28VNjafSAF2mon/SNjKablRjoGGKSoSdyA\r\nDHDZ/LeRsTp2dg8+bSiG1R+vPqw0f/BT+ux295Sy9ocGEM8=\r\n-----END RSA PRIVATE KEY-----\r\n",publicKeyPem:"-----BEGIN PUBLIC KEY-----\r\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgWyeKqA2oA4klYrKT9hjjutYQksJ\r\nNN0cxwaQwIm9AYiLxOsYtT/CovJx5Oy1EvkbYQbfvYsGISUx9bW8yasZkTHR55Ib\r\nW3+UptvQjTDtdxBQTgQOpsAhBJtZYY3OmyH9Sj3F3oB//oyriNoj0QYyfsvlO8Us\r\nMmLzpnf6qfZBDHA/9QIDAQAB\r\n-----END PUBLIC KEY-----\r\n",encrypted:"ShSS4/fEAkuS6XiQakhOpWp82IXaaCaDNtsndU4uokvriqgCGZyqc+IkIk3eVmZ8bn4vVIRR43ydFuvGgsptVjizOdLGZudph3TJ1clcYEMcCXk4z5HaEu0bx5SW9jmzHhE/z+WV8PB48q7y7C2qtmPmfttG2NMsNLBvkiaDopRO",signature:"Z3vYgRdezrWmdA3NC1Uz2CcHRTcE+/C2idGZA1FjUGqFztAHQ31k0QW/F5zuJdKvg8LQU45S3KxW+OQpbGPL98QbzJLhml88mFGe6OinLXJbi7UQWrtXwamc2jMdiXwovSLbXaXy6PX2QW089iC8XuAZftVi3T/IKV0458FQQprg",signaturePss:"R6QsK6b3QinIPZPamm/dP0Zndqti1TzAkFTRSZJaRSa1u2zuvZC5QHF4flDjEtHosWeDyxrBE7PHGQZ0b1bHv9qgHGsJCMwaQPj3AWj9fjYmx7b86KM2vHr8q/vqDaa9pTvVRSSwvD6fwoZPc9twQEfdjdDBAiy23yLDzk/zZiwM"},{keySize:1032,privateKeyPem:"-----BEGIN RSA PRIVATE KEY-----\r\nMIICYQIBAAKBggDPhzn5I3GecxWt5DKbP+VhM2AFNSOL0+VbYEOR1hnlZdLbxGK4\r\ncPQzMr2qT6dyttJcsgWr3xKobPkz7vsTZzQATSiekm5Js5dGpaj5lrq/x2+WTZvn\r\n55x9M5Y5dlpusDMKcC3KaIX/axc+MbvPFzo6Eli7JLCWdBg01eKo30knil0CAwEA\r\nAQKBggCNl/sjFF7SOD1jbt5kdL0hi7cI9o+xOLs1lEGmAEmc7dNnZN/ibhb/06/6\r\nwuxB5aEz47bg5IvLZMbG+1hNjc26D0J6Y3Ltwrg8f4ZMdDrh4v0DZ8hy/HbEpMrJ\r\nTd5dk3mtw9FLow10MB5udPLTDKhfDpTcWiObKm2STtFeBk3xeEECQQ6Cx6bZxQJ1\r\nzCxflV5Xi8BgAQaUKMqygugte+HpOLflL0j1fuZ0rPosUyDOEFkTzOsPxBYYOU8i\r\nGzan1GvW3WwRAkEOTTRt849wpgC9xx2pF0IrYEVmv5gEMy3IiRfCNgEoBwpTWVf4\r\nQFpN3V/9GFz0WQEEYo6OTmkNcC3Of5zbHhu1jQJBBGxXAYQ2KnbP4uLL/DMBdYWO\r\nKnw1JvxdLPrYXVejI2MoE7xJj2QXajbirAhEMXL4rtpicj22EmoaE4H7HVgkrJEC\r\nQQq2V5w4AGwvW4TLHXNnYX/eB33z6ujScOuxjGNDUlBqHZja5iKkCUAjnl+UnSPF\r\nexaOwBrlrpiLOzRer94MylKNAkEBmI58bqfkI5OCGDArAsJ0Ih58V0l1UW35C1SX\r\n4yDoXSM5A/xQu2BJbXO4jPe3PnDvCVCEyKpbCK6bWbe26Y7zuw==\r\n-----END RSA PRIVATE KEY-----\r\n",publicKeyPem:"-----BEGIN PUBLIC KEY-----\r\nMIGgMA0GCSqGSIb3DQEBAQUAA4GOADCBigKBggDPhzn5I3GecxWt5DKbP+VhM2AF\r\nNSOL0+VbYEOR1hnlZdLbxGK4cPQzMr2qT6dyttJcsgWr3xKobPkz7vsTZzQATSie\r\nkm5Js5dGpaj5lrq/x2+WTZvn55x9M5Y5dlpusDMKcC3KaIX/axc+MbvPFzo6Eli7\r\nJLCWdBg01eKo30knil0CAwEAAQ==\r\n-----END PUBLIC KEY-----\r\n",encrypted:"pKTbv+xgXPDc+wbjsANFu1/WTcmy4aZFKXKnxddHbU5S0Dpdj2OqCACiBwu1oENPMgPAJ27XRbFtKG+eS8tX47mKP2Fo0Bi+BPFtzuQ1bj3zUzTwzjemT+PU+a4Tho/eKjPhm6xrwGAoQH2VEDEpvcYf+SRmGFJpJ/zPUrSxgffj",signature:"R9WBFprCfcIC4zY9SmBpEM0E+cr5j4gMn3Ido5mktoR9VBoJqC6eR6lubIPvZZUz9e4yUSYX0squ56Q9Y0yZFQjTHgsrlmhB2YW8kpv4h8P32Oz2TLcMJK9R2tIh9vvyxwBkd/Ml1qG60GnOFUFzxUad9VIlzaF1PFR6EfnkgBUW",signaturePss:"v9UBd4XzBxSRz8yhWKjUkFpBX4Fr2G+ImjqbePL4sAZvYw1tWL+aUQpzG8eOyMxxE703VDh9nIZULYI/uIb9HYHQoGYQ3WoUaWqtZg1x8pZP+Ad7ilUWk5ImRl57fTznNQiVdwlkS5Wgheh1yJCES570a4eujiK9OyB0ba4rKIcM"}];for(var u=0;u<n.length;++u)a(n[u]);it("should ensure maximum message length for a 1024-bit key is exceeded",function(){var r=t.publicKeyFromPem(n[0].publicKeyPem),i=o.createBuffer().fillWithByte(0,118);e.throws(function(){r.encrypt(i.getBytes())})}),it("should ensure maximum message length for a 1025-bit key is not exceeded",function(){var r=t.publicKeyFromPem(n[1].publicKeyPem),i=o.createBuffer().fillWithByte(0,118);e.doesNotThrow(function(){r.encrypt(i.getBytes())})})}()})}typeof define=="function"?define("test/rsa",["forge/pki","forge/rsa","forge/md","forge/mgf","forge/pss","forge/util"],function(t,n,r,i,s,o){e(ASSERT,t(),n(),r(),i(),s(),o())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/pki")(),require("../../js/rsa")(),require("../../js/md")(),require("../../js/mgf")(),require("../../js/pss")(),require("../../js/util")())}(),function(){function e(e,t,n,r,i){describe("pkcs1",function(){function s(){var e,t,n,r,i,s,o,u,a,l,p;e="qLOyhK+OtQs4cDSoYPFGxJGfMYdjzWxVmMiuSBGh4KvEx+CwgtaTpef87Wdc9GaFEncsDLxkp0LGxjD1M8jMcvYq6DPEC/JYQumEu3i9v5fAEH1VvbZi9cTg+rmEXLUUjvc5LdOq/5OuHmtme7PUJHYW1PW6ENTP0ibeiNOfFvs=",t="AQAB",n="UzOc/befyEZqZVxzFqyoXFX9j23YmP2vEZUX709S6P2OJY35P+4YD6DkqylpPNg7FSpVPUrE0YEri5+lrw5/Vf5zBN9BVwkm8zEfFcTWWnMsSDEW7j09LQrzVJrZv3y/t4rYhPhNW+sEck3HNpsx3vN9DPU56c/N095lNynq1dE=",r="0yc35yZ//hNBstXA0VCoG1hvsxMr7S+NUmKGSpy58wrzi+RIWY1BOhcu+4AsIazxwRxSDC8mpHHcrSEurHyjnQ==",i="zIhT0dVNpjD6wAT0cfKBx7iYLYIkpJDtvrM9Pj1cyTxHZXA9HdeRZC8fEWoN2FK+JBmyr3K/6aAw6GCwKItddw==",s="DhK/FxjpzvVZm6HDiC/oBGqQh07vzo8szCDk8nQfsKM6OEiuyckwX77L0tdoGZZ9RnGsxkMeQDeWjbN4eOaVwQ==",o="lSl7D5Wi+mfQBwfWCd/U/AXIna/C721upVvsdx6jM3NNklHnkILs2oZu/vE8RZ4aYxOGt+NUyJn18RLKhdcVgw==",u="T0VsUCSTvcDtKrdWo6btTWc1Kml9QhbpMhKxJ6Y9VBHOb6mNXb79cyY+NygUJ0OBgWbtfdY2h90qjKHS9PvY4Q==",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 1.1",message:"ZigZThIHPbA7qUzanvlTI5fVDbp5uYcASv7+NA==",seed:"GLd26iEGnWl3ajPpa61I4d2gpe8=",encrypted:"NU/me0oSbV01/jbHd3kaP3uhPe9ITi05CK/3IvrUaPshaW3pXQvpEcLTF0+K/MIBA197bY5pQC3lRRYYwhpTX6nXv8W43Z/CQ/jPkn2zEyLW6IHqqRqZYXDmV6BaJmQm2YyIAD+Ed8EicJSg2foejEAkMJzh7My1IQA11HrHLoo="},{title:"RSAES-OAEP Encryption Example 1.2",message:"dQxAR/VH6OQUEYVlIymKybriRe+vE5f75W+d1Q==",seed:"DMdCzkqbfzL5UbyyUe/ZJf5P418=",encrypted:"ZA2xrMWOBWj+VAfl+bcB3/jDyR5xbFNvx/zsbLW3HBFlmI1KJ54Vd9cw/Hopky4/AMgVFSNtjY4xAXp6Cd9DUtkEzet5qlg63MMeppikwFKD2rqQib5UkfZ8Gk7kjcdLu+ZkOu+EZnm0yzlaNS1e0RWRLfaW/+BwKTKUbXFJK0Q="},{title:"RSAES-OAEP Encryption Example 1.3",message:"2Urggy5kRc5CMxywbVMagrHbS6rTD3RtyRbfJNTjwkUf/1mmQj6w4dAtT+ZGz2md/YGMbpewUQ==",seed:"JRTfRpV1WmeyiOr0kFw27sZv0v0=",encrypted:"Qjc27QNfYCavJ2w1wLN0GzZeX3bKCRtOjCni8L7+5gNZWqgyLWAtLmJeleuBsvHJck6CLsp224YYzwnFNDUDpDYINbWQO8Y344efsF4O8yaF1a7FBnzXzJb+SyZwturDBmsfz1aGtoWJqvt9YpsC2PhiXKODNiTUgA+wgbHPlOs="},{title:"RSAES-OAEP Encryption Example 1.4",message:"UuZQ2Y5/KgSLT4aFIVO5fgHdMW80ahn2eoU=",seed:"xENaPhoYpotoIENikKN877hds/s=",encrypted:"RerUylUeZiyYAPGsqCg7BSXmq64wvktKunYvpA/T044iq+/Gl5T267vAXduxEhYkfS9BL9D7qHxuOs2IiBNkb9DkjnhSBPnD9z1tgjlWJyLd3Ydx/sSLg6Me5vWSxM/UvIgXTzsToRKq47n3uA4PxvclW6iA3H2AIeIq1qhfB1U="},{title:"RSAES-OAEP Encryption Example 1.5",message:"jaif2eX5dKKf7/tGK0kYD2z56AI=",seed:"sxjELfO+D4P+qCP1p7R+1eQlo7U=",encrypted:"NvbjTZSo002qy6M6ITnQCthak0WoYFHnMHFiAFa5IOIZAFhVohOg8jiXzc1zG0UlfHd/6QggK+/dC1g4axJE6gz1OaBdXRAynaROEwMP12Dc1kTP7yCU0ZENP0M+HHxt0YvB8t9/ZD1mL7ndN+rZBZGQ9PpmyjnoacTrRJy9xDk="},{title:"RSAES-OAEP Encryption Example 1.6",message:"JlIQUIRCcQ==",seed:"5OwJgsIzbzpnf2o1YXTrDOiHq8I=",encrypted:"Qs7iYXsezqTbP0gpOG+9Ydr78DjhgNg3yWNm3yTAl7SrD6xr31kNghyfEGQuaBrQW414s3jA9Gzi+tY/dOCtPfBrB11+tfVjb41AO5BZynYbXGK7UqpFAC6nC6rOCN7SQ7nYy9YqaK3iZYMrVlZOQ6b6Qu0ZmgmXaXQt8VOeglU="}],f(a,l,"sha1",p),e="AZR8f86QQl9HJ55whR8l1eYjFv6KHfGTcePmKOJgVD5JAe9ggfaMC4FBGQ0q6Nq6fRJQ7G22NulE7Dcih3x8HQpn8UsWlMXwN5RRpD5Joy3eg2cLc9qRocmbwjtDamAFXGEPC6+ZwaB5VluVo/FSZjLR1Npg8g7aJeZTxPACdm9F",t="AQAB",n="CCPyD6212okIip0AiT4h+kobEfvJPGSjvguq6pf7O5PD/3E3BMGcljwdEHqumQVHOfeeAuGG3ob4em3e/qbYzNHTyBpHv6clW+IGAaSksvCKFnteJ51xWxtFW91+qyRZQdl2i5rO+zzNpZUto87nJSW0UBZjqO4VyemS2SRi/jk=",r="AVnb3gSjPvBvtgi4CxkPTT4ivME6yOSggQM6v6QW7bCzOKoItXMJ6lpSQOfcblQ3jGlBTDHZfdsfQG2zdpzEGkM=",i="AStlLzBAOzi0CZX9b/QaGsyK2nA3Mja3IC05su4wz7RtsJUR9vMHzGHMIWBsGKdbimL4It8DG6DfDa/VUG9Wi9c=",s="Q271CN5zZRnC2kxYDZjILLdFKj+1763Ducd4mhvGWE95Wt270yQ5x0aGVS7LbCwwek069/U57sFXJIx7MfGiVQ==",o="ASsVqJ89+ys5Bz5z8CvdDBp7N53UNfBc3eLv+eRilIt87GLukFDV4IFuB4WoVrSRCNy3XzaDh00cpjKaGQEwZv8=",u="AnDbF9WRSwGNdhGLJDiac1Dsg2sAY6IXISNv2O222JtR5+64e2EbcTLLfqc1bCMVHB53UVB8eG2e4XlBcKjI6A==",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 2.1",message:"j/AMqmBccCgwY02abD1CxlK1jPHZL+xXC+7n",seed:"jEB7XsKJnlCZxT6M55O/lOcbF4I=",encrypted:"AYGviSK5/LTXnZLr4ZgVmS/AwUOdi81JE5ig9K06Mppb2ThVYNtTJoPIt9oE5LEq7Wqs30ccNMnNqJGt3MLfNFZlOqY4Lprlm1RFUlfrCZ1WK74QRT8rbRPFnALhDx+Ku12g0FcJMtrPLQkB23KdD+/MBU5wlo6lQMgbBLyu/nIO"},{title:"RSAES-OAEP Encryption Example 2.2",message:"LQ==",seed:"tgDPPC5QbX8Wd4yRDTqLAD7uYdU=",encrypted:"AYdZ/x32OyeSQQViMUQWqK6vKsY0tG+UCrgtZNvxZe7jMBHadJ1Lq24vzRgSnJ5JJ32EUxErQpoiKoRxsHCZOZjnWIYcTT9tdJ2RxCkNMyx6SrP36jX/OgfUl8lV/w/8lQBrYsbSloENm/qwJBlseTQBLC35eO8pmrojmUDLoQJF"},{title:"RSAES-OAEP Encryption Example 2.3",message:"dPyIxRvJD3evnV6aSnATPUtOCzTaPDfH744=",seed:"pzdoruqpH52MHtb50rY0Z/B8yuM=",encrypted:"AYgCurBMYDJegcSWIxHyvnwq3OkwQaAHGciPlXV18sefG3vIztEVxwazEcCKLZhso7apM2sUfCnG8ilAnd7GUb0f3VoLf2EMmTf9tKOnYjZLizIGtOpIX9CY0I9j1KqLsml9Ant1DDLX906vUYDS6bZrF8svpVUjvCgNoQ0UviBT"},{title:"RSAES-OAEP Encryption Example 2.4",message:"p+sqUDaTHSfU6JEybZlpL/rdqb9+/T405iLErcCF9yHf6IUHLHiiA7FRc5vlQPqMFToQ8Ao=",seed:"mns7DnCL2W+BkOyrT7mys4BagVY=",encrypted:"AKRXjLwXYximOPun0B3xV0avRNT2zZbX58SVy/QlsJxknTK/iG2kj7r5iaIRcYfK+x+1gDF2kOPM1EaSC3r4KzHbWATYfQFRSsv6kVbngvhn9r7ZRJ4OmiwJvOzGqgh2NpZeNLPsdm8v4uQwGKL93rFAYWoOnYLlMxAk7gZS/HZB"},{title:"RSAES-OAEP Encryption Example 2.5",message:"LvKwZvhUwz873LtZlKQ15z1sbA==",seed:"6zzrvErcFrtI6IyK7A40r39Cf9M=",encrypted:"AOvF9f2nfP2tPINkGpAl531y2Kb7M6gQ9ZUPjXTHPo2THoY02GqxJGJWrge2AFtxt/L7mDUSGDMc5puP+9ydoIu8nHBPh23rnfn8LsBlyth/kJCweswXqn+ZeyespIgG6Jf3cdlRQf5FJtilMBtnhifvq3B/1A++vW55KiVhPnrs"},{title:"RSAES-OAEP Encryption Example 2.6",message:"in+zRMi2yyzy7x9kP5oyGPbhm7qJwA==",seed:"TEXPTVfJjj1tIJWtxRxInrUN/4Q=",encrypted:"AQg57CDCe5BS5Vvvubd+b8JukHXXpUN4xkar31HkRb1XFd6BeJ9W8YA9kXB2Sp6Ty3h5hpQCPuc5POBLxdj4xaUsFx1Dg346ymL2CesKpf+wlg7wQZjddU9X9/vmq/dlzxGLTKRDsjtaqyZvlSMmrEWBEAZEMl+LchrNXQT/FO86"}],f(a,l,"sha1",p),e="ArWP7AOahgcApNe2Ri+T5s3UkRYd3XT06BC0DjwWUgBqXCd7J3TBEwWky6taeO+lfheobfej+jb8Sx0iSfIux8LdakYyMqzOqQbWbr6AtXBLEHKdpvgzI0q7Xv3UopLL+tM7TTP6ehS4w5e1bjrNISA0KLd836M6bacGs9iw/EPp",t="AQAB",n="FbSKW1aDqUZw4jtXGPgU+g4T+FA49QcRGCy6YVEFgfPSLH4jLvk34i5VHWi4bi+MsarYvi5Ij13379J54/Vo1Orzb4DPcUGs5g/MkRP7bEqEH9ULvHxRL/y+/yFIeqgR6zyoxiAFNGqG3oa/odipSP0/NIwi6q3zM8PObOEyCP0=",r="Ab8B0hbXNZXPAnDCvreNQKDYRH0x2pGamD9+6ngbd9hf43Gz6Tc+e2khfTFQoC2JWN5/rZ1VUWCVi0RUEn4Ofq8=",i="AY0zmWWBZts4KYFteylUFnWenJGYf1stiuzWOwS0i9ey/PIpu3+KbciLoT3S45rVW20aBhYHCPlwC+gLj9N0TOc=",s="BsCiSdIKby7nXIi0lNU/aq6ZqkJ8iMKLFjp2lEXl85DPQMJ0/W6mMppc58fOA6IVg5buKnhFeG4J4ohalyjk5Q==",o="0dJ8Kf7dkthsNI7dDMv6wU90bgUc4dGBHfNdYfLuHJfUvygEgC9kJxh7qOkKivRCQ7QHmwNEXmAuKfpRk+ZP6Q==",u="jLL3Vr2JQbHTt3DlrTHuNzsorNpp/5tvQP5Xi58a+4WDb5Yn03rP9zwneeY0uyYBHCyPfzNhriqepl7WieNjmg==",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 3.1",message:"CHggtWno+o0=",seed:"jO1rGWKQgFeQ6QkHQBXmogsMSJQ=",encrypted:"AmoEhdlq69lrQ4IIUJm5Yuaivew9kMjbYl4UNy3oXi1be6q2XI+vkbtVBPtJWvzlyYiz9qUuIOHWy9NWbFzR8rgxi7VCzA6iXEqrmTKvogdg6t3seEOWoH6g7yTU5vTTflBSp6MeFGqkgKERu+kmQBMH4A9BADOEK22C/lzk366A"},{title:"RSAES-OAEP Encryption Example 3.2",message:"RlOsrxcZYLAfUqe+Y6OrIdw2jsQ7UNguw3geBA==",seed:"tCkdZWdVCEjMFWlnyAm6q2ylB/A=",encrypted:"Ak24nHgCmJvgeDhHhjCElBvyCddhmH44+Xy19vG8iNpypQtz668RyHnE+V3ze4ULj2XXYi4lsbiJ6A/oC6yiBp1uDh2CmVP8RZBp3pjql5i0UeVX6Zq/j+PZzPkJbrvz5SVdO04cbS7K3wZ6NZ7qhkBazUfV4WVRfMr9R9bb7kv1"},{title:"RSAES-OAEP Encryption Example 3.3",message:"2UzQ4I+kBO2J",seed:"zoko9gWVWCVACLrdl5T63NL9H2U=",encrypted:"Ajm85oEDJEFSiHfW0ci7KKo7yX8d9YRWNhiZV5doOETKhmZHMvS+16CqsIOqq/tyOPWC4wlYwgJOROVwQ7l5UP1UPal3yQzd5TN9YYRC+Z5g13g6tZzm3Z1pxHrR6WK+wi0FiVz/jT9k7VJh2SsmeFEDk0hJkLo/fwaBiub/zoo6"},{title:"RSAES-OAEP Encryption Example 3.4",message:"bMZBtrYeb5Y5dNrSOpATKE7x",seed:"bil59S1oFKV9g7CQBUiI8RmluaM=",encrypted:"AplMYq/Xb0mLof0s9kKFf8qB9Dc8sI8cuu5vAlw7UStCw+h3kRNHZkgDnb4Ek/kkYpL6wolQYA58DzLt+cgbnexFw73gzI2IR1kBaZB7fcWZHOspuwcU1hPZbfDxLsXY01B8jueueN2D8hb6Yd4QA2OspIp+kUrp9C3fvpQ7Cdmg"},{title:"RSAES-OAEP Encryption Example 3.5",message:"31FRgyth9PJYkftBcvMo0u3fg3H/z9vpl5OSlfMOymkYAXz9oRU796avh1kyIw==",seed:"LXYL/jjFneNM3IuMeKOOZihKLSc=",encrypted:"AWIEL/aWlZKmFnAxgRojmDTOY4q/VP7IuZR4Eir+LuZ/jFsYsDOYBb/bxaTmcgs3xZz7qUJGTFl/9TKhGYIVRf0uWbEU5h2vcYIFKfUCnPUklUMnw07F5vW6fvzE3pQ6uK1O14exRUMp9w23mKOo9NkvgnTispSK3mJ86O4z5Dxg"},{title:"RSAES-OAEP Encryption Example 3.6",message:"PDutiTxUSm1SCrAiMZGIyNUEt6eIuFCQO4WXLqoYVS4RNKetYJiCYlT/erZys9jrMVj6xtTLrvE=",seed:"8XR3nF/Tz+AHuty3o2ybVb/Pvw4=",encrypted:"ABEgUeddBklDvER4B15DSC/VnO4Ged5ok+7DqUPapJC5aRyT38BGS2YjufPb0+cAgyZPA0s3T3QWThoAdjcl5XR0S6C524NDTzHflvbiom9tjro0i9RobCI4rAfDeqw3hdHH7qL4Gf2RSReY7Y6c715Dt4Gw4CduN8Q/+UktAFcw"}],f(a,l,"sha1",p),e="BRJAtswABPpI0BNGccB4x8jew7Pi8lvCVkRnM52ziFPQa4XupbLeNTv/QqwuRryX+uaslhjalTelyPVTweNXYlmR1hCNzXiF+zolQT9T78rZSMs1zZua6cHGdibRE9V93kxb6na7W7felsANBzculoWm11z50jn6FI1wkxtfP7A5",t="AQAB",n="BBH/yjt8penpvn/jioUQXjU4ltsFxXlq7NKnJRYes2UchimpuGK5BNewx7N/jLWhwrVAAQGKAKHrLK/k7k6UksNIvCvtq0ueu/Bk6O/zIrkAn47sZTkF9A34ijzcSdRWf3VifUGspiQSm0agt8aY5eZfK3uhAsdJoQE1tlQNBAE=",r="AnRYwZ7BY2kZ5zbJryXWCaUbj1YdGca/aUPdHuGriko/IyEAvUC4jezGuiNVSLbveSoRyd6CPQp5IscJW266VwE=",i="AhDumzOrYXFuJ9JRvUZfSzWhojLi2gCQHClL8iNQzkkNCZ9kK1N1YS22O6HyA4ZJK/BNNLPCK865CdE0QbU7UTk=",s="OfoCi4JuiMESG3UKiyQvqaNcW2a9/R+mN9PMSKhKT0V6GU53J+Sfe8xuWlpBJlf8RwxzIuvDdBbvRYwweowJAQ==",o="AV2ZqEGVlDl5+p4b4sPBtp9DL0b9A+R9W++7v9ax0Tcdg++zMKPgIJQrL+0RXl0CviT9kskBnRzs1t1M8eVMyJk=",u="AfC3AVFws/XkIiO6MDAcQabYfLtw4wy308Z9JUc9sfbL8D4/kSbj6XloJ5qGWywrQmUkz8UqaD0x7TDrmEvkEro=",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 4.1",message:"SoZglTTuQ0psvKP36WLnbUVeMmTBn2Bfbl/2E3xlxW1/s0TNUryTN089FmyfDG+cUGutGTMJctI=",seed:"HKwZzpk971X5ggP2hSiWyVzMofM=",encrypted:"BMzhlhSEXglBUqP+GOVOMzDETl77xkrhaIbLGGkBTMV4Gx+PngRThNARKhNcoNEunIio5AY0Ft6q44RPYNbpb+FVFF9FJbmjRDHKN2YYD3DhWl5djosaUW/4cGCfE/iWk1ztGIJ5pY7RPQcRQnfXXGVoYH4KsJL9gDoiPkqO4LGo"},{title:"RSAES-OAEP Encryption Example 4.2",message:"sK3E8/4R2lnOmSdz2QWZQ8AwRkl+6dn5oG3xFm20bZj1jSfsB0wC7ubL4kSci5/FCAxcP0QzCSUS7EaqeTdDyA==",seed:"9UXViXWF49txqgy42nbFHQMq6WM=",encrypted:"AJe2mMYWVkWzA0hvv1oqRHnA7oWIm1QabwuFjWtll7E7hU60+DmvAzmagNeb2mV4yEH5DWRXFbKA03FDmS3RhsgLlJt3XK6XNw5OyXRDE2xtpITpcP/bEyOiCEeCHTsYOB3hO7SarqZlMMSkuCcfPq4XLNNm4H5mNvEBnSoortFe"},{title:"RSAES-OAEP Encryption Example 4.3",message:"v21C5wFwex0CBrDItFoccmQf8SiJIZqCveqWW155qWsNAWPtnVeOya2iDy+88eo8QInYNBm6gbDGDzYG2pk=",seed:"rZl/7vcw1up75g0NxS5y6sv90nU=",encrypted:"AwH5NenEery0isu+CYldn1lxrxSDnaT/lUF+5FPR/XcxkHK7cpfhtV11Yc2dG7JMGpo3xhmGQwgkKASHnYbr0AHc5Rg5deFQaYm3DlqDQ0FU1cv9aiR4fmDrDGWNKsGTMC0RksbmItShKtS1OSO8okbfMcY5XjdwLGp4rggfudBl"},{title:"RSAES-OAEP Encryption Example 4.4",message:"+y7xEvXnZuuUAZKXk0eU974vb8HFjg==",seed:"E2RU31cw9zyAen5A2MGjEqxbndM=",encrypted:"AtEQrTCvtye+tpHdDPF9CvGh5/oMwEDsGkuiakLFnQp5ai4iyPNXzMmLZRms62gulF5iy3NGFKUpQHzUUr7j5E/s6EI8wZ5VVIuLmUuEnH7N5JM+dgN+HQzkQnWwhxDGjkMBMLkpcw7XfgmwFWQsVZPwTk/7lBB5gQKo6W/9/hHk"},{title:"RSAES-OAEP Encryption Example 4.5",message:"KMzUR7uehRZtq7nlt9GtrcS5058gTpbV5EDOmtkovBwihA==",seed:"vKgFf4JLLqJX8oYUB+72PTMghoE=",encrypted:"ANu4p0OdkO/ZGaN3xU+uj+EexYw7hYNi4jrRuKRDEHmQZrmTR6pSVpHSrcWNmwbjTyiMFwOQxfDhHAqjZFlZ8Y7nno8r6NesXCPQYfGN10uMXypY/LXrDFT5nwGoMkdWgpJTZYM0CUjXqMl8Ss0emNHincMg6XomBTKoqnp1ih7C"},{title:"RSAES-OAEP Encryption Example 4.6",message:"8iJCdR7GsQ==",seed:"Ln4eF/ZHtd3QM+FUcvkPaBLzrE4=",encrypted:"AKX/pHaMi77K7i23fo8u7JlZWTNUVSCDXlun25ST0+F83e/mpfVnYkRxkI204tg6D77mBgj8hASVA7IjSgfcg7J7IoR62JIP9C9nTvebdigLACM9K1G4yycDqdQr+8glDJbsMsBR5X8bS6Uo24nDfkxU4n5uZKxpY1roh9lUFhmp"}],f(a,l,"sha1",p),e="Cq3z+cEl5diR8xrESOmT3v5YD4ArRfnX8iulAh6cR1drWh5oAxup205tq+TZah1vPSZyaM/0CABfEY78rbmYiNHCNEZxZrKiuEmgWoicBgrA2gxfrotV8wm6YucDdC+gMm8tELARAhSJ/0l3cBkNiV/Tn1IpPDnv1zppi9q58Q7Z",t="AQAB",n="AlbrTLpwZ/LSvlQNzf9FgqNrfTHRyQmbshS3mEhGaiaPgPWKSawEwONkiTSgIGwEU3wZsjZkOmCCcyFE33X6IXWI95RoK+iRaCdtxybFwMvbhNMbvybQpDr0lXF/fVKKz+40FWH2/zyuBcV4+EcNloL5wNBy+fYGi1bViA9oK+LF",r="A7DTli9tF1Scv8oRKUNI3PDn45+MK8aCTyFktgbWh4YNrh5jI5PP7fUTIoIpBp4vYOSs1+YzpDYGP4I4X0iZNwc=",i="AuTDLi9Rcmm3ByMJ8AwOMTZffOKLI2uCkS3yOavzlXLPDtYEsCmC5TVkxS1qBTl95cBSov3cFB73GJg2NGrrMx8=",s="AehLEZ0lFh+mewAlalvZtkXSsjLssFsBUYACmohiKtw/CbOurN5hYat83iLCrSbneX31TgcsvTsmc4ALPkM429U=",o="65CqGkATW0zqBxl87ciBm+Hny/8lR2YhFvRlpKn0h6sS87pP7xOCImWmUpfZi3ve2TcuP/6Bo4s+lgD+0FV1Tw==",u="AS9/gTj5QEBi64WkKSRSCzj1u4hqAZb0i7jc6mD9kswCfxjngVijSlxdX4YKD2wEBxp9ATEsBlBi8etIt50cg8s=",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 5.1",message:"r3GpAeOmHTEy8Pwf20dPnqZXklf/wk0WQXAUWz296A==",seed:"RMkuKD93uUmcYD2WNmDIfS+TlGE=",encrypted:"A2BGpKR9ntO6mokTnBBQOOt0krBaXWi/1TrM/0WX96aGUbR7SkYn2Sfkhe7XtFZkIOi0CYeeXWBuriUdIqXfeZ95IL/BF7mSVypTsSYxRrzqAzhcxehTyaEByMPhvaMaUZgHSWxsteXvtAiCOjUrj6BmH7Zk763Vk965n/9e0ADl"},{title:"RSAES-OAEP Encryption Example 5.2",message:"o7hEoII5qKxBYFrxemz9pNNQE2WFkDpBenkmh2BRmktKwzA+xz8Ph8+zI5k=",seed:"yyj1hgZZ/O7knD7q/OYlpwgDvTI=",encrypted:"A9brZU7c5hW8WfRVJl7U5aGCI8u5vk5AabRzgE1d6W9U3KqmA9BJxdlKoUcN/NIlQGa3x7Yf8fb2dw4yFcUTmf1ONOxQgrxI8ImECtBDVK5m3A8b0Y5GGjPMEli0Q6KDem3yZ1mqIwIzSYb4c4DJzJ1Tvp+ZYF0smpfaewkVpKet"},{title:"RSAES-OAEP Encryption Example 5.3",message:"MIsOy9LHbLd/xvcMXt0jP9LyCSnWKfAmlTu2Ko9KOjFL3hld6FtfgW2iqrB00my2rN3zI647nGeKw88S+93n",seed:"IoX0DXcEgvmp76LHLLOsVXFtwMo=",encrypted:"B3CVIYFkn5+fB/9ib/OiLDXEYkQ9kF1Fap/Qv/Q8rCynqfVU6UeLmsw6yDiwIED/0+GEfeLkJTkp+d2e5ARDJamwXKu4CLLuhA004V0QWj8feydpWhoHotc/4I7KqjycnU1aif+JDVRyfXrkDA7BqN2GFl2O4sY2gUEBaki1W2ln"},{title:"RSAES-OAEP Encryption Example 5.4",message:"FcW57hGF",seed:"SfpF06eN0Q39V3OZ0esAr37tVRM=",encrypted:"CBK3Z2jry2QtBAJY5fREGgGFIb2WaH5sXomfzWwXWI/1moLMiuA6S0WzEpmvF4jDKffc0oX4z0ztgmBrl2EmcaRb7coTNEIUTRYX0RT4AoV/D51zl1HFej+e5ACRLGHi5pkr4DGkPdSPproU7vfEIrXtxOevoE/dOPQC0ci7cZq/"},{title:"RSAES-OAEP Encryption Example 5.5",message:"IQJuaADH+nKPyqug0ZauKNeirE/9irznlPCYX2DIpnNydzZdP+oR24kjogKa",seed:"8Ch0EyNMxQNHJKCUxFhrh6/xM/w=",encrypted:"B7YOFOyVS/0p5g0AR+eJ9R1XGGxjWJkDMGeTztP2gkHHQ1KaumpjdPkuGeAWPvozaX4Zb3Zh36qkeqxr3l5R3rUHxyxYmiyhaT2WsUYDgSSbLNuerER2nySJxdPS+Z8O48fuW/ZKWsecQr1DPxSb6MtZVINhZAWVUTyXr3vCUJcj"},{title:"RSAES-OAEP Encryption Example 5.6",message:"VB43totsiHK4TAI=",seed:"2fukXJbyHm4m0p6yzctlhb6cs0E=",encrypted:"CMNtTdozQjsu1oMNhfZBG6Hc9HCh+uDr7+58CJ8lbO90y5bqacOPYPOavuRBKby0yS3n95diOyAHTj2cKJlwHtkHHh76C92E1MPlEwMC2PAkC6ukuEpxzAMvIjWl/w+uJ3w+j5ESvvRMmuINF1/JpAWL/JMLoxsC4uT0REg3EPJK"}],f(a,l,"sha1",p),e="ErF/ba0uzRn/RtwT94YPCeDgz7Z3s4pSWSMFzq8CLBZtuQ0ErCnjP33RLZ+vZuCBa7Y+rSZ8x9RsF8N74hS8oqItcjpk5EQHQ2tvyWVymu/CVU83bNXc6mgpN4CmK/OdAClIWhYLu55dwJctIaUE9S5e4CiqQWMy9RCy6c/19yKv",t="AQAB",n="ApXso1YGGDaVWc7NMDqpz9r8HZ8GlZ33X/75KaqJaWG80ZDcaZftp/WWPnJNB7TcEfMGXlrpfZaDURIoC5CEuxTyoh69ToidQbnEEy7BlW/KuLsv7QV1iEk2Uixf99MyYZBIJOfK3uTguzctJFfPeOK9EoYij/g/EHMc5jyQz/P5",r="BKbOi3NY36ab3PdCYXAFr7U4X186WKJO90oiqMBct8w469TMnZqdeJpizQ9g8MuUHTQjyWku+k/jrf8pDEdJo4s=",i="BATJqAM3H+20xb4588ALAJ5eCKY74eQANc2spQEcxwHPfuvLmfD/4Xz9Ckv3vv0t1TaslG23l/28Sr6PKTSbke0=",s="A5Ycj3YKor1RVMeq/XciWzus0BOa57WUjqMxH8zYb7lcda+nZyhLmy3lWVcvFdjQRMfrg6G+X63yzDd8DYR1KUs=",o="AiGX4GZ0IZaqvAP6L+605wsVy3h9YXrNMbt1x7wjStcG98SNIYLR8P+cIo3PQZZ7bAum0sCtEQobhXgx7CReLLE=",u="BAHEwMU9RdvbXp2W0P7PQnXfCXS8Sgc2tKdMMmkFPvtoas4kBuIsngWN20rlQGJ64v2wgmHo5+S8vJlNqvowXEU=",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 6.1",message:"QEbKi6ozR8on9J4NgfnMHXG+m6UX1A==",seed:"3Q9s/kFeiOWkaaUfu6bf1ArbQ4Q=",encrypted:"BjDuvNKFbCT3mIBuQfnmc0Xtqc7aOGrMn6yuoe7tBqzlg3CXGNnRafrfQU1cdvkploM+8wW3Wx5LlfZiog+u3DuuDEgnqL+KiO29V+wgOieoQfAuQ6YVurGoysBwHeNN6972KgiAibVew26nUi/T7I0GtqBz5t+DMVO8Cu/ZO9Gj"},{title:"RSAES-OAEP Encryption Example 6.2",message:"XMcsYCMd8Ds9QPm1eTG8MRCflyUn8osZ50gMcojLPJKyJRIhTkvmyRR5Ldq99X+qiqc=",seed:"jRS9lGoTURSPXK4u2aDGU+hevYU=",encrypted:"Drw3N2FzpP0vicxVwspismsR1Rw8fOSeiEX3TnYHMXxDa8jSO5Zn3+udCHI0tHvGg3F1rlwFWfa4HX0iQW0+UPSsUz2PCBLy2555H+nHdayLatD1Na2c6yOkoCAUxYqz+NMWFJmiYPOTSOcUriodNEMgj9i3Isz9+zk+mAEfmeY/"},{title:"RSAES-OAEP Encryption Example 6.3",message:"sg5lEwMJL0vMtDBwwPhtIwSTYu2WZC/FYywn20pS49gx8qsGiyOxSYecAC9r8/7ul1kRElYs",seed:"bAdbxFUg8WXAv16kxd8ZG8nvDkQ=",encrypted:"Cpi/EJNhk5RDbPaNjzji8Vj96OpU80NfI5uNBrgyGEQgJHau7ZYAlJJIDOOo1wVJjEyMaPAVAdyB22CPYAhzUMjDsL0unvaoFFi3yAG4ny5P6Z1JALpqS15althl3Gdsd1WSh5QTDWKAqBYKGQ8t8+p8+aoCcdiOnmkF7PHFFS1l"},{title:"RSAES-OAEP Encryption Example 6.4",message:"aE4wOMXAQfc=",seed:"O7w71mN9/hKEaQECm/WwwHEDQ5w=",encrypted:"AI56Z8rPtcTiS+x97hSRF/GVmM6MRYCP74jGCP+c1uaVJjuaPArUuLpMlSOOlqhCK4U1YpyNU4I3RHmtE/o5l0skL5p1nur5yDrVqMoYlAoBYrp1WHbfJj9L1QxlJcVgkCZ8Hw4JzgiZoM81nogSCr2b+JNEWzyud9Ngc1mumlL4"},{title:"RSAES-OAEP Encryption Example 6.5",message:"MkiMsmLQQdbk3TX5h788ppbbHwasKaRGkw==",seed:"tGtBiT6L7zJvZ1k4OoMHHa5/yrw=",encrypted:"AAA0dEFse2i9+WHDhXN5RNfx9AyzlTQ8aTzAtP5jsx/t8erurJzMBnizHcMuCXdIlRTE8JCF9imKllPwGupARf9YLuiHviauV1tz7vfzd0kh43Wj0ZrdoMoxqhhJiHwfQsrJZ396L06SP25ahos4wITvGHWU3J9/BI/qLgKVU4Sr"},{title:"RSAES-OAEP Encryption Example 6.6",message:"ULoUvoRicgJ5wwa6",seed:"CiQDMSpB49UvBg+8E6Z95c92Cac=",encrypted:"CgJt2l/IeF972b91Mntj6F4sD97l2ttl69ysmuHelcksZyq0M6p6jmnOam2Il/rErEpU3oQa5eW7znaHh515Y0zqejBoQGXHFNUkCbkoJWu/U+q81SMetyWVBFNzmb0pFktybTOkbacBNgpBaKCRzKty1Epi/tJGwP/qWxNIq1Rw"}],f(a,l,"sha1",p),e="MRF58Lz8m508oxXQDvMNe906LPrpkRv+3LlIs6R4LQcytqtEqkvwN0GmRNwBvsPmmwGgM+Z12KzXxJJcaxrsMRkFHf2Jdi0hXUVHX/y1n5CBSGI/NxdxVvauht16fF9D3B4fkIJUBYooSl8GwAIXk6h/GsX+/33K7mnF5Ro3ieNz",t="AQAB",n="Bwz8/y/rgnbidDLEXf7kj0m3kX1lMOHwyjRg8y4CdhdEh8VuIqRdJQDXd1SVIZ19Flqc872Swyr5qY2NycwpaACtyUoKVPtA80KRv4TujqErbxCTWcbTVCpQ+cdn9c//BaaBwuZW+3fKqttL6UaNirzU35j1jobSBT+hNJ90jiGx",r="B0kmLBEc1HDsJWbms3MvwJMpRpqhkHHTucAZBlFMbx0muqFL6rCXHIt+YRpPeQCdb+p3aSjKJShbDeNkPRo/jHE=",i="BrweUOlsAr9jbp7qi4mbvr92Ud533UdMPpvCO62BgrYZBMfZffvr+x4AEIh4tuZ+QVOR1nlCwrK/m0Q1+IsMsCM=",s="A7x+p/CqsUOrxs6LlxGGNqMBcuTP4CyPoN2jt7qvkPgJKYKYVSX0iL38tL1ybiJjmsZKMJKrf/y/HVM0z6ULW/E=",o="AmKmqinCo8Z9xTRsBjga/Zh6o8yTz7/s9U/dn514fX9ZpSPTmJedoTei9jgf6UgB98lNohUY3DTLQIcMRpeZStk=",u="ZJ1MF7buFyHnctA4mlWcPTzflVDUV8RrA3t0ZBsdUhZq+KITyDliBs37pEIvGNb2Hby10hTJcb9IKuuXanNwwg==",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 7.1",message:"R6rpCQ==",seed:"Q90JoH/0ysccqkYy7l4cHa7kzY8=",encrypted:"FojkzneUu6bLcBQWns1VnO3iowtWpSto2f4Yzxlz75eyoDFTlRx1X2KUqkmtvbVYRatodfs5hsk+z5J5YoQNKC+eVM6LaQ98DLi71zRA2VcdGxbNkmD56rR4PMSC5SI9xglzhxeD7Cewrg/UdzLLwoahc/ySsA+0umgkZHzZPIXB"},{title:"RSAES-OAEP Encryption Example 7.2",message:"HZsuIiPZvBO/ufFiznNdtIunxo9oIqChp7auFlg05w==",seed:"Opw87HuE+b063svGc+yZ1UsivJs=",encrypted:"EFLtOXsuAeHQ7hxQvyQ2P5XlBPSgNDSgj9giV07WuXNu27XzkNsQMhR5qKE5NQ4r1Jd8N3jvMx8+eK4RiyaEUfIKLwHUcfXVPFZpNxcbLbwtS95FmleZ8DctZXQjmyMj0kXQu4HChrY8iaNhAXM35JAviKRn9MfyRL/Vq0ZDf/O2"},{title:"RSAES-OAEP Encryption Example 7.3",message:"2Xb8",seed:"dqdeW2FXpVbPiIS7LkXCk91UXPU=",encrypted:"IVXNhD/ySk7outt2lCYAKKSQgTuos2mky/EG7BSOUphwf1llvn0QHBBJ6oWEwkzWNFWtnBBNaGKC0/uAOkwRwcLpuRxxeIAdG2ZA8AP1co3wB7ikzMkrzgXkGicnjXyFAYxSQUMTpQd3iQAdTwGRC3Kq0F0iCqFKWHM6dIm8VFVr"},{title:"RSAES-OAEP Encryption Example 7.4",message:"1HOGI98iOqQ4Q9+EZ1NMQdAT4MgDxiTiY2ZrI5veQKXymuuN5549qmHdA3D0m9SwE4NLmCEq72scXuNzs8s=",seed:"eGYxSmrW8rJQo1lB2yj1hktYWFk=",encrypted:"CrFMNzrrfUMo0KqtjAlNiLnrCYuV8hBUopCCUivnwnoxKHi2N5F+PYGebDxWjbXYQ4ArBtUdnpiivgv0DAMUI7AO37/4Mg77kXG9IERlOky5xRIvbGXoPNouw8EmAnqcGla6h00P6iPzgLgs8kC4z1QABHWMTHfZNBV6dPP8Er+s"},{title:"RSAES-OAEP Encryption Example 7.5",message:"u0cjHKXqHTrUbJk0XZqKYQ==",seed:"shZu1HLVjbEMqyxrAAzM8Qp9xQk=",encrypted:"AoOHoxgndDR5i02X9GAGjfUpj6ulBBuhF2Ghy3MWskGEEU7FACV+JYntO2B6HrvpemzC4CvxtoH0IxKjO3p32OeFXEpt4D48BGQ/eGuRomSg1oBeLOqR5oF363pk2SVeTyfnE7fM7ADcIA69IcLqK7iQ/q5JQt+UHcP5eJDtNHR4"},{title:"RSAES-OAEP Encryption Example 7.6",message:"IYSCcJXTXD+G9gDo5ZdUATKW",seed:"Umc73iyhZsKqRhMawdyAjWfX07E=",encrypted:"FMZ4qUrWBSXvOelZsvO6XAl6lP+RK2fbrOgFNcGHq9R9B1QgsYchUrugj3/DHzE7v5JzyRL8TAFJqbDPt5gH40brMyBpYRvsD/m80Wjx98M+dzE86kVLlOJUnuzwAuKs9/by0oRdT+CqsuWpLd9oxICuESR5NdH2JXSEIhauZ0EV"}],f(a,l,"sha1",p),e="W98OMNMh3aUUf4gkCPppGVSA34+A0/bov1gYUE82QnypsfVUC5xlqPaXTPhEeiRNkoAgG7Sfy75jeNGUTNIn4jD5bj0Q+Bnc7ydsZKALKktnAefQHeX6veOx6aDfgvRjE1nNImaWR/uxcXJGE07XtJfP/73EK1nHOpbtkBZiEt/3",t="AQAB",n="D30enlqqJf0T5KBmOuFE4NFfXNGLzbCd8sx+ZOPF6RWtYmRTBBYdCYxxW7eri9AdB+rz/tfH7QivKopi70SrFrMg4Ur3Kkj5av4mKgrkz2XmNekQeQzU7lzqdopLJjn35vZ3s/C7a+MrdXR9iQkDbwJk9Y1AHNuhMXFhV6dez2Mx",r="CgLvhEjZ+ti70NAEyMKql1HvlyHBsNAyNqVLDflHy67VolXuno4g1JHqFyP+CUcEqXYuiK/RbrtZlEEsqWbcT58=",i="CS02Ln7ToL/Z6f0ObAMBtt8pFZz1DMg7mwz01u6nGmHgArRuCuny3mLSW110UtSYuByaxvxYWT1MP7T11y37sKk=",s="B8cUEK8QOWLbNnQE43roULqk6cKd2SFFgVKUpnx9HG3tJjqgMKm2M65QMD4UA10a8BQSPrpoeCAwjY68hbaVfX0=",o="rix1OAwCwBatBYkbMwHeiB8orhFxGCtrLIO+p8UV7KnKKYx7HKtYF6WXBo/IUGDeTaigFjeKrkPH+We8w3kEuQ==",u="BZjRBZ462k9jIHUsCdgF/30fGuDQF67u6c76DX3X/3deRLV4Mi9kBdYhHaGVGWZqqH/cTNjIj2tuPWfpYdy7o9A=",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 8.1",message:"BQt1Xl5ogPe56daSp0w3quRJsxv+pt7/g3R6iX9sLIJbsa2/hQo8lplLXeWzPLx9SheROnln",seed:"dwb/yh7PsevuKlXlxuJM0nl6QSU=",encrypted:"CbNoPYousPspW2LtH7kpC3FEV7eCUxn0ZHhyr4ibMECUcgIK0SkSvxmxHUgZ9JYUgk/9hNCcChfn0XMJ0SkZeQQQqimVaZ9qhtvjJCtazCOvRWkQgNaxroEPs+MFcIfwlwCSzgC+lWL/QFO2Jizgyqk+E3I9LjpboHXUXw1htUth"},{title:"RSAES-OAEP Encryption Example 8.2",message:"TraNzZPKmxnfERvUNgj1VwJv5KodXPrCJ6PrWrlUjBigbd7SP4GCWYay/NcRCezvfv+Ihz8HXCqgxGn2nJK8",seed:"o3F9oUO03P+8dCZlqPqVBYVUg0M=",encrypted:"Ls8VyXxaFbFHaumGs3G1eiQoT0oWKo0MgYLnkF55IlbxgSul+D8fehMOQtzAIjKETtwUoxpo7peuVko4OjQRZWQkxfYt22Rgk8Nnvh/NpCbPAKBtist+V3dvu9hVrD31BvwWsdfD8hEPPYBo6R4YY2ODHIQJaA2NqezYzx+iDuOd"},{title:"RSAES-OAEP Encryption Example 8.3",message:"hgSsVjKMGrWtkXhh",seed:"7gYgkHPMoCa7Jk5Rhb+MaLdzn4Y=",encrypted:"S8iRMKWy2rt8L8+Q610Or55oG3FGo48xc6PZz+xS6p4KQZMuZIqdaTRMUNp2P1GgPJV2ITHoBSJU3NIkjLpA/TFmd4bOBaK3tTGsnaye1YSlm2d8GortjF0V1owFVp4r54C/fbY4/Sv9KoWrJ2hg83dzOPypif/XQ9E+4I4MqYk/"},{title:"RSAES-OAEP Encryption Example 8.4",message:"/dpfv27DYanZpKxoryFqBob0OLHg5cNrlV904QfznA3dzA==",seed:"mQrVc9xIqXMjW22CVDYY8ulVEF0=",encrypted:"LkVoR9j8Nv8BR9aZNZS5OXIn1Xd1LHnQ+QT8sDnU2BL+pgWntXTdgsp4b5N1I0hDjun1tUVJhdXw4WmePnrRdaMuFfA96wQquf4d2dsbuG+MCJzLRefvDF7nyptykMprFb7UcDl4ioqT/4Pg6NYkTHEAY2Le72m29Bb7PGhDg/vQ"},{title:"RSAES-OAEP Encryption Example 8.5",message:"Sl9JFL7iXePGk0HeBw==",seed:"7MY7KPB1byL1Ksjm7BJRpuwwRxg=",encrypted:"H7k1b9XEsXltsuv30NOTzIEK32FF3vwvznFPedk4ANXirCEeqLvsyktlS5TDsYsw3Vds403JVDbvV6CUFWRZIzWaXXtBce8iwkZw8bIp02A+kfdmcbffl+cxfJdzRHbV89F9Ic+Ctbqfg98uWI02mE/RtYRGi9I7LodfMvaJU/ey"},{title:"RSAES-OAEP Encryption Example 8.6",message:"jgfWb3uICnJWOrzT81CSvDNAn7f4jyRyvg==",seed:"OSXHGzYtQKCm3kIUVXm6Hn3UWfw=",encrypted:"Ov2cZgAUeyF5jYGMZVoPTJIS2ybQsN/cKnWUzLPSL1vx18PhEs1z/H1QnHqLr908J00TmQCflgnsS+ZHfkU/B1qjPbOChwwcNAmu85LXOGrjppa5mpS02gWJRH6VXRbJixdgKlm9c2J5/Nj7KAxEYtWQv6m/E/7VcOr96XMwosIQ"}],f(a,l,"sha1",p),e="zyzUHjTKOnKOpcuK/2TDbSe971Nk4zb9aNMSPFoZaowocBPoU9UVbVjRUZVFIPtPbXsXq7aBd2WQnFdhGWWdkCsZBu2KKxDBVcJNEkUo2rnurjeb6sZuSkEXhty4/QBi68Aw3hIZoEwqjBt90xMeTWtsruLjGl7UGsFQmy7x7iqxg2S+VoypQcJezIT/nWQ7XsGqrhAqINc/R5t4D9bakQdSEtnqwDoGdNiZ66LkMfTES2Fba6IjK9SzO67XPWJd",t="AQAB",n="GYwUHiNxWpK8z2oRmlvBE4lGjSgR9UjXJ+F7SrDrmG1vIR77U7cffMvqh+5px17mFQCMUzLetSvzkKvfv+N9cgU2gVmyY4wd4ybiHSIlHw+1hIs78VAF0qdDMPCv6RbuYszBNE0dg6cJ5gZ2JzhA9/N3QkpeCk2nXwGzH/doGc+cv90hUkPDkXwD7zgZkxLlZ7O/eu06tFfzce+KFCP0W2jG4oLsERu6KDO5h/1p+tg7wbjGE8Xh6hbBHtEl6n7B",r="/I1sBL7E65qBksp5AMvlNuLotRnezzOyRZeYxpCd9PF2230jGQ/HK4hlpxiviV8bzZFFKYAnQjtgXnCkfPWDkKjD6I/IxI6LMuPaIQ374+iB6lZ0tqNIwh6T+eVepl79",i="0gDUXniKrOpgakAdBGD4fdXBAn4S3BoNdYbok52c94m0D1GsBEKWHefSHMIeBcgxVcHyqpGTOHz9+VbLSNFTuicEBvm7ulN9SYfZ4vmULXoUy//+p0/s3ako0j4ln17h",s="2xaAL3mi8NRfNY1p/TPkS4H66ChiLpOlQlPpl9AbB0N1naDoErSqTmyL6rIyjVQxlVpBimf/JqjFyAel2jVOBe8xzIz3WPRjcylQsD4mVyb7lOOdalcqJiRKsI23V1Kt",o="oKMXz+ffFCP4em3uhFH04rSmflSX8ptPHk6DC5+t2UARZwJvVZblo5yXgX4PXxbifhnsmQLgHX6m+5qjx2Cv7h44G2neasnAdYWgatnEugC/dcitL6iYpHnoCuKU/tKh",u="CyHzNcNTNC60TDqiREV4DC1lW5QBdMrjjHyKTmSTwLqf0wN0gmewg7mnpsth5C2zYrjJiW23Bk4CrVrmFYfaFbRknJBZSQn+s328tlS+tyaOyAHlqLSqORG+vYhULwW+",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 9.1",message:"9zX9VbqSWSw7Urj5xPaaqhy++P6IrdCVWVQSRn+c9OwLiWxZ7aFiEOdUnIq7EM28IaEuyba1uP0vEDmetg==",seed:"jsll8TSj7Jkx6SocoNyBadXqcFw=",encrypted:"JnvNEYrKsfyLqByF1zADy4YQ+lXB2X2o1Ip8fwaJak23UaooQlW502rWXzdlPYKfGzf5e4ABlCVFsvwsVac3bKehvksXYMjgWjPlqiUmuNmOMXCI54NMdVsqWbEmMaGCwF1dQ6sXeSZPhFb1Fc5X399RLVST2re3M43Et9eNucCRrDuvU3pp/H9UnZefDv+alP2kFpvU0dGaacmeM8O1VJDVAbObHtrhGP9nk6FTJhWE06Xzn25oLj0XyM0SYfpy"},{title:"RSAES-OAEP Encryption Example 9.2",message:"gbkGYFAVpjqr5C3fEeGXiRL1QEx0dLJtzj7Ugr+WHsyBi/QgxUZZ",seed:"7LG4sl+lDNqwjlYEKGf0r1gm0Ww=",encrypted:"k6yfBnHsKay7RE7/waV0E1HWD9sOOT+/dUrPDeSXYaFIQd93cum8gnc5ZqFYTE1yuuoAEY+D81zKblN8vU2BH1WDspeD2KbZTNMb5w1vUmwQ/wnG+nzgaXlaP80FEf1fy1ZLzIDqnHjzi4ABJTnYpN32/oHpzdt/UNu7vMfl2GCXzPTsSRifuL8xi+bVoHFdUWtJrxkSWM0y3IM85utGc8A6Gbus6IzFSJX2NswMHsiQltEc4jWiZcoXZCMqaJro"},{title:"RSAES-OAEP Encryption Example 9.3",message:"/TJkKd+biQ4JtUsYuPNPHiQ=",seed:"6JuwMsbOYiy9tTvJRmAU6nf3d8A=",encrypted:"gevdlQVLDIIu+a12k/Woet+0tMTOcN8t+E7UnATaWLpfwgoZ4abot6OQCyJ5bcToae5rQnktFajs61bAnGmRToE86o9pMeS47W9CGvKY1ZXJf0eJx8qmEsfvNgmEwhuT7cVAEGi1r0x4qHcbmE1TuOqK3y9qfUoLp2x14d2fZY8g3tSkYHHUbXeRtWgD2P6n8LD45Brj8JODpvlYX+d1Pqr/0r+UVjEIvuzCB7u1NfX8xwXw3en3CMYvSanJA3HT"},{title:"RSAES-OAEP Encryption Example 9.4",message:"8UWbXwyS8BoPcjouVmJITY+MCiD8KdrWrNQ7tfPv/fThtj4H/f5mKNDXTKGb8taeSgq/htKTklp5Z3L4CI4=",seed:"YG87mcC5zNdx6qKeoOTIhPMYnMw=",encrypted:"vMNflM3mbLETZiXWJblEMqNbIvPS+hGmE/8PylvVf4e5AszcHNCuvLBxXuhp0dH+OV9nkwA/XspGUFnIhmDURv9fCBhVICJVfjjAimfq2ZEmIlTxBoKXXsVjl3aFN/SXevbV9qrOt/sl3sWTcjAjH9iXivSRGaKfKeQkq4JytHVieS1clPd0uIKdCw2fGoye3fN1dNX6JI7vqcUnH8XsJXnIG91htBD6Yf425CQiHBE63bJ1ZkyAHTTKjGNR5KhY"},{title:"RSAES-OAEP Encryption Example 9.5",message:"U+boxynW+cMZ3TF+dLDbjkzMol88gwV0bhN6xjpj7zc557WVq7lujVXlT3vUGrQzN4/7kR0=",seed:"/LxCFALp7KvGCCr6QLpfJlIshA4=",encrypted:"Iyr7ySf6CML2onuH1KXLCcB9wm+uc9c6kFWIOfT9ZtKBuH7HNLziN7oWZpjtgpEGp95pQs1s3OeP7Y0uTYFCjmZJDQNiZM75KvlB0+NQVf45geFNKcu5pPZ0cwY7rseaEXn1oXycGDLyg4/X1eWbuWWdVtzooBnt7xuzrMxpfMbMenePYKBkx/b11SnGIQJi4APeWD6B4xZ7iZcfuMDhXUT//vibU9jWTdeX0Vm1bSsI6lMH6hLCQb1Y1O4nih8u"},{title:"RSAES-OAEP Encryption Example 9.6",message:"trKOohmNDBAIvGQ=",seed:"I6reDh4Iu5uaeNIwKlL5whsuG6I=",encrypted:"Q4zH3AimjaJJ5CUF+Fc7pg4sJ3PVspD0z53/cY6EIIHDg+ZwJKDylZTqmHudJeS3OPKFlw0ZWrs6jIBU49eda5yagye6WW8SWeJxJmdHZpB9jVgv86hHYVSSmtsebRI1ssy07I9mO6nMZwqSvr2FPI2/acZDbQFvYa3YNulHMkUENCB/n9TEPewqEqlY76Ae/iZpiZteYEwlXFX7cWbeVYnjaVl7sJFowG3V2xd+BqF0DrLVyC+uym2S/O6ZMbqf"}],f(a,l,"sha1",p),e="rkXtVgHOxrjMBfgDk1xnTdvg11xMCf15UfxrDK7DE6jfOZcMUYv/ul7Wjz8NfyKkAp1BPxrgfk6+nkF3ziPn9UBLVp5O4b3PPB+wPvETgC1PhV65tRNLWnyAha3K5vovoUF+w3Y74XGwxit2Dt4jwSrZK5gIhMZB9aj6wmva1KAzgaIv4bdUiFCUyCUG1AGaU1ooav6ycbubpZLeGNz2AMKu6uVuAvfPefwUzzvcfNhP67v5UMqQMEsiGaeqBjrvosPBmA5WDNZK/neVhbYQdle5V4V+/eYBCYirfeQX/IjY84TE5ucsP5Q+DDHAxKXMNvh52KOsnX1Zhg6q2muDuw==",t="AQAB",n="BWsEIW/l81SsdyUKS2sMhSWoXFmwvYDFZFCiLV9DjllqMzqodeKR3UP0jLiLnV/A1Jn5/NHDl/mvwHDNnjmMjRnmHbfHQQprJnXfv100W4BNIBrdUC1c4t/LCRzpmXu+vlcwbzg+TViBA/A29+hdGTTRUqMj5KjbRR1vSlsbDxAswVDgL+7iuI3qStTBusyyTYQHLRTh0kpncfdAjuMFZPuG1Dk6NLzwt4hQHRkzA/E6IoSwAfD2Ser3kyjUrFxDCrRBSSCpRg7Rt7xA7GU+h20Jq8UJrkW1JRkBFqDCYQGEgphQnBw786SD5ydAVOFelwdQNumJ9gkygHtSV3UeeQ==",r="7PWuzR5VFf/6y9daKBbG6/SQGM37RjjhhdZqc5a2+AkPgBjH/ZXMNLhX3BfwzGUWuxNGq01YLK2te0EDNSOHtwM40IQEfJ2VObZJYgSz3W6kQkmSB77AH5ZCh/9jNsOYRlgzaEb1bkaGGIHBAjPSF2vxWl6W3ceAvIaKp30852k=",i="vEbEZPxqxMp4Ow6wijyEG3cvfpsvKLq9WIroheGgxh5IWKD7JawpmZDzW+hRZMJZuhF1zdcZJwcTUYSZK2wpt0bdDSyr4UKDX30UjMFhUktKCZRtSLgoRz8c52tstohsNFwD4F9B1RtcOpCj8kBzx9dKT+JdnPIcdZYPP8OGMYM=",s="xzVkVx0A+xXQij3plXpQkV1xJulELaz0K8guhi5Wc/9qAI7U0uN0YX34nxehYLQ7f9qctra3QhhgmBX31FyiY8FZqjLSctEn+vS8jKLXc3jorrGbCtfaPLPeCucxSYD2K21LCoddHfA8G645zNgz72zX4tlSi/CE0flp55Tp9sE=",o="Jlizf235wQML4dtoEX+p2H456itpO35tOi9wlHQT7sYULhj7jfy2rFRdfIagrUj4RXFw8O+ya8SBJsU+/R0WkgGY3CoRB9woLbaoDNMGI2C6P6E/cOQxL/GmzWuPxM2cXD2xfG1qVyEvc64p9hkye61ZsVOFhYW6Tii2CmKkXkk=",u="bzhSazklCFU07z5BWoNu3ouGFYosfL/sywvYNDBP7Gg7qNT0ecQz1DQW5jJpYjzqEAd22Fr/QB0//2EO5lQRzjsTY9Y6lwnu3kJkfOpWFJPVRXCoecGGgs2XcQuWIF7DERfXO182Ij+t1ui6kN18DuYdROFjJR4gx/ZuswURfLg=",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 10.1",message:"i7pr+CpsD4bV8XVul5VocLCJU7BrTrIFvBaU7g==",seed:"R+GrcRn+5WyV7l6q2G9A0KpjvTM=",encrypted:"U+pdwIzSYPs7hYVnKH+pFVLDCy/r+6IT8K6HcC0GjRm6sH/ldFI9+0ITnWjDxa/u4L/ky3lpy/OCuATW5hOWFE4tDmB0H4mTwwFLWLmxlXqLq80jr4VPTDVvsWYqpyv8x+WGVZ3EKA0WDBJnhacj6+6+/3HxFZRECq74fRB5Ood0ojnUoEyH/hRnudr4UgjsbHJVeUqWzCkUL5qL1Bjjwf1nNEsM0IKd87K+xgJTGWKTxrNNP3XTLyE91Fxic9UFrfTM7RBXy3WPwmru+kQSVe1OZMGZ7gdefxZkYYL9tGRzm2irXa/w5j6VUgFoJPBUv008jJCpe7a2VTKE60KfzA=="},{title:"RSAES-OAEP Encryption Example 10.2",message:"5q0YHwU7WKkE8kV1EDc+Vw==",seed:"bRf1tMH/rDUdGVv3sJ0J8JpAec8=",encrypted:"orGkMKnWV+L6HCu17UP/slwFowj+kJPAEDF5X1h0QAEQgorlj7m1gc6d3dPlSa4EoJhUWb3mxiZZTnsF3EJ4sqFGXBNoQIgjyF6W3GbDowmDxjlmT8RWmjf+IeWhlbV3bu0t+NjTYa9obnUCKbvWY/FhhopQYV4MM3vsDKNf7AuxnDbrLgu8wFgvodk6rNsGEGP1nyzh7kNgXl2J7KGD0qzf6fgQEQIq07Q6PdQX2slLThHqgbGSlm6WaxgggucZZGB7T4AC82KZhEoR8q4PrqwurnD49PmAiKzc0KxVbp/MxRFSGQj60m8ExkIBRQMFd4dYsFOL+LW7FEqCjmKXlQ=="},{title:"RSAES-OAEP Encryption Example 10.3",message:"UQos9g6Gb6I0BVPJTqOfvCVjEeg+lEVLQSQ=",seed:"OFOHUU3szHx0DdjN+druSaHL/VQ=",encrypted:"mIbD5nZKi5qE6EFI69jDsaqAUDgaePZocUwW2c/Spu3FaXnFNdne47RLhcGL6JKJkjcXEUciFtld2pjS7oNHybFN/9/4SqSNJawG99fmU5islnsc6Qkl9n3OBJt/gS2wdCmXp01E/oHb4Oej/q8uXECviI1VDdu+O8IGV6KVQ/j8KRO5vRphsqsiVuxAm719wNF3F+olxD9C7Sffhzi/SvxnZv96/whZVV7ig5IPTIpjxKc0DLr93DOezbSwUVAC+WyTK1t5Fnr2mcCtP8z98PROhacCYr8uGP40uFBYmXXoZ/+WnUjqvyEicVRs3AWmnstSblKHDINvMHvXmHgO3g=="},{title:"RSAES-OAEP Encryption Example 10.4",message:"vN0ZDaO30wDfmgbiLKrip18QyR/2Z7fBa96LUwZKJkmpQEXJ",seed:"XKymoPdkFhqWhPhdkrbg7zfKi2U=",encrypted:"Yxjp+1wNBeUwfhaDQ26QMpOsRkI1iqoiPXFjATq6h+Lf2o5gxoYOKaHpJoYWPqC5F18ynKOxMaHt06d3Wai5e61qT49DlvKM9vOcpYES5IFg1uID2qWFbzrKX/7Vd69JlAjj39Iz4+YE2+NKnEyQgt5lUnysYzHSncgOBQig+nEi5/Mp9sylz6NNTR2kF4BUV+AIvsVJ5Hj/nhKnY8R30Vu7ePW2m9V4MPwsTtaG15vHKpXYX4gTTGsK/laozPvIVYKLszm9F5Cc8dcN4zNa4HA5CT5gbWVTZd5lULhyzW3h1EDuAxthlF9imtijU7DUCTnpajxFDSqNXu6fZ4CTyA=="},{title:"RSAES-OAEP Encryption Example 10.5",message:"p91sfcJLRvndXx6RraTDs9+UfodyMqk=",seed:"lbyp44WYlLPdhp+n7NW7xkAb8+Q=",encrypted:"dSkIcsz9SkUFZg1lH1babaoJyhMB2JBjL2qZLz1WXO5GSv3tQO07W+k1ZxTqWqdlX0oTZsLxfHKPbyxaXR+OKEKbxOb48s/42o3A4KmAjkX9CeovpAyyts5v//XA4VnRG2jZCoX3uE4QOwnmgmZkgMZXUFwJKSWUaKMUeG106rExVzzyNL9X232eZsxnSBkuAC3A3uqTBYXwgx/c2bwz1R957S/8Frz01ZgS/OvKo/kGmw5EVobWRMJcz2O0Vu5fpv/pbxnN91H+2erzWVd1Tb9L/qUhaqGETcUHyy0IDnIuuhUDCMK1/xGTYg8XZuz0SBuvuUO9KSh38hNspJSroA=="},{title:"RSAES-OAEP Encryption Example 10.6",message:"6vGnOhsMRglTfeac2SKLvPuajKjGw++vBW/kp/RjTtALfDnsaSLXuOosBOus",seed:"n0fd9C6X7qhWqb28cU6zrCL26zI=",encrypted:"LSB6c0Mqj7TAMFGz9zsophdkCY36NMR6IJlfgRWqaBZnm1V+gtvuWEkIxuaXgtfes029Za8GPVf8p2pf0GlJL9YGjZmE0gk1BWWmLlx38jA4wSyxDGY0cJtUfEb2tKcJvYXKEi10Rl75d2LCl2Pgbbx6nnOMeL/KAQLcXnnWW5c/KCQMqrLhYaeLV9JiRX7YGV1T48eunaAhiDxtt8JK/dIyLqyXKtPDVMX87x4UbDoCkPtnrfAHBm4AQo0s7BjOWPkyhpje/vSy617HaRj94cGYy7OLevxnYmqa7+xDIr/ZDSVjSByaIh94yCcsgtG2KrkU4cafavbvMMpSYNtKRg=="}],f(a,l,"sha1",p)}function o(){var e,t,n,r,i,s,o,u,a,l,p;e="qLOyhK+OtQs4cDSoYPFGxJGfMYdjzWxVmMiuSBGh4KvEx+CwgtaTpef87Wdc9GaFEncsDLxkp0LGxjD1M8jMcvYq6DPEC/JYQumEu3i9v5fAEH1VvbZi9cTg+rmEXLUUjvc5LdOq/5OuHmtme7PUJHYW1PW6ENTP0ibeiNOfFvs=",t="AQAB",n="UzOc/befyEZqZVxzFqyoXFX9j23YmP2vEZUX709S6P2OJY35P+4YD6DkqylpPNg7FSpVPUrE0YEri5+lrw5/Vf5zBN9BVwkm8zEfFcTWWnMsSDEW7j09LQrzVJrZv3y/t4rYhPhNW+sEck3HNpsx3vN9DPU56c/N095lNynq1dE=",r="0yc35yZ//hNBstXA0VCoG1hvsxMr7S+NUmKGSpy58wrzi+RIWY1BOhcu+4AsIazxwRxSDC8mpHHcrSEurHyjnQ==",i="zIhT0dVNpjD6wAT0cfKBx7iYLYIkpJDtvrM9Pj1cyTxHZXA9HdeRZC8fEWoN2FK+JBmyr3K/6aAw6GCwKItddw==",s="DhK/FxjpzvVZm6HDiC/oBGqQh07vzo8szCDk8nQfsKM6OEiuyckwX77L0tdoGZZ9RnGsxkMeQDeWjbN4eOaVwQ==",o="lSl7D5Wi+mfQBwfWCd/U/AXIna/C721upVvsdx6jM3NNklHnkILs2oZu/vE8RZ4aYxOGt+NUyJn18RLKhdcVgw==",u="T0VsUCSTvcDtKrdWo6btTWc1Kml9QhbpMhKxJ6Y9VBHOb6mNXb79cyY+NygUJ0OBgWbtfdY2h90qjKHS9PvY4Q==",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 1.1",message:"ZigZThIHPbA7qUzanvlTI5fVDbp5uYcASv7+NA==",seed:"GLd26iEGnWl3ajPpa61I4d2gpe8Yt3bqIQadaXdqM+k=",encrypted:"W1QN+A1CKWotV6aZW7NYnUy7SmZd34SiX0jiPiLj9+8sZW6O/L7793+IFFSO3VKbPWhrjJPyR3ZmZ+yHDCzTDkRth+s5FN3nuFtlD3XQmmh0+x60PvAUiXJnAMcwxV96wHKjsUNPSnE1fsrCPBpIO5ZRaJ1pIF6R25IeuMwDujo="},{title:"RSAES-OAEP Encryption Example 1.2",message:"dQxAR/VH6OQUEYVlIymKybriRe+vE5f75W+d1Q==",seed:"DMdCzkqbfzL5UbyyUe/ZJf5P418Mx0LOSpt/MvlRvLI=",encrypted:"jsKSyOW1BkucnZpnt9fS72P/lamWQqexXEDPVs8uzGlFj24Rj+cqGYVlt7i9nTmOGj2YrvM8swUTJQCYIF+QBiKbkcA7WBTBXfiUlkHvpWQD0bLwOkp1CmwfpF4sq2gTsCuSaGzZAc50ZAIOvpldizU7uOCwNNGOlERcFkvhfEE="},{title:"RSAES-OAEP Encryption Example 1.3",message:"2Urggy5kRc5CMxywbVMagrHbS6rTD3RtyRbfJNTjwkUf/1mmQj6w4dAtT+ZGz2md/YGMbpewUQ==",seed:"JRTfRpV1WmeyiOr0kFw27sZv0v0lFN9GlXVaZ7KI6vQ=",encrypted:"LcQ1BhOH4Vs0XX8/QJ6q/L0vSs9BUXfA20lQ6mwAt/gvUaUOvKJWBujoxt1QgpRnU6WuH7cSCFWXuKNnrhofpFF3CBTLIUbHZFoou0A4Roi4vFGFvYYu96Boy+oWivwB9/BKs1QMQeHADgNwUgqVD15+q27yHdfIH7kGp+DiGas="},{title:"RSAES-OAEP Encryption Example 1.4",message:"UuZQ2Y5/KgSLT4aFIVO5fgHdMW80ahn2eoU=",seed:"xENaPhoYpotoIENikKN877hds/vEQ1o+Ghimi2ggQ2I=",encrypted:"ZMkqw9CM3SuY2zPBr8/9QbgXaVon4O4AKIufl3i7RVPD07fiTOnXF0aSWKUcdXNhE6ZcXc0Ha97/S5aw6mQKYfbmjaSq/H45s2nfZYTNIa74OgsV1DTDDLSF6/3J2UKhsG0LGIFaV9cNjfucDA5KbfQbzTq8u/+WN06J6nbInrI="},{title:"RSAES-OAEP Encryption Example 1.5",message:"jaif2eX5dKKf7/tGK0kYD2z56AI=",seed:"sxjELfO+D4P+qCP1p7R+1eQlo7WzGMQt874Pg/6oI/U=",encrypted:"NzKEr8KhWRbX/VHniUE8ap0HzdDEWOyfl7dfNHXjL4h/320dmK633rGUvlA7sE4z9yuMj/xF++9ZeBzN6oSPLhVJV/aivUfcC8J99lwwp49W7phnvkUA4WUSmUeX+XRhwj8cR27mf5lu/6kKKbgasdt4BHqXcc5jOZICnld6vdE="},{title:"RSAES-OAEP Encryption Example 1.6",message:"JlIQUIRCcQ==",seed:"5OwJgsIzbzpnf2o1YXTrDOiHq8Lk7AmCwjNvOmd/ajU=",encrypted:"nfQEzsDY2gS9UYXF85t+u0Tm7HrOmmf+LqxCD+6N4XD36NoQ96PE9Squ83PvxKy8Bj8Q0N2L8E5Z5/9AWxLPCBqOkqkqIqO7ZDQMmpHml3H1yz82rpAzAQi6acZDSFQAW8NKhg4nEEwfwKdaGQcI0JZm6FrTQUuXskOqFUT0NJc="}],f(a,l,"sha256",p),e="AZR8f86QQl9HJ55whR8l1eYjFv6KHfGTcePmKOJgVD5JAe9ggfaMC4FBGQ0q6Nq6fRJQ7G22NulE7Dcih3x8HQpn8UsWlMXwN5RRpD5Joy3eg2cLc9qRocmbwjtDamAFXGEPC6+ZwaB5VluVo/FSZjLR1Npg8g7aJeZTxPACdm9F",t="AQAB",n="CCPyD6212okIip0AiT4h+kobEfvJPGSjvguq6pf7O5PD/3E3BMGcljwdEHqumQVHOfeeAuGG3ob4em3e/qbYzNHTyBpHv6clW+IGAaSksvCKFnteJ51xWxtFW91+qyRZQdl2i5rO+zzNpZUto87nJSW0UBZjqO4VyemS2SRi/jk=",r="AVnb3gSjPvBvtgi4CxkPTT4ivME6yOSggQM6v6QW7bCzOKoItXMJ6lpSQOfcblQ3jGlBTDHZfdsfQG2zdpzEGkM=",i="AStlLzBAOzi0CZX9b/QaGsyK2nA3Mja3IC05su4wz7RtsJUR9vMHzGHMIWBsGKdbimL4It8DG6DfDa/VUG9Wi9c=",s="Q271CN5zZRnC2kxYDZjILLdFKj+1763Ducd4mhvGWE95Wt270yQ5x0aGVS7LbCwwek069/U57sFXJIx7MfGiVQ==",o="ASsVqJ89+ys5Bz5z8CvdDBp7N53UNfBc3eLv+eRilIt87GLukFDV4IFuB4WoVrSRCNy3XzaDh00cpjKaGQEwZv8=",u="AnDbF9WRSwGNdhGLJDiac1Dsg2sAY6IXISNv2O222JtR5+64e2EbcTLLfqc1bCMVHB53UVB8eG2e4XlBcKjI6A==",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 2.1",message:"j/AMqmBccCgwY02abD1CxlK1jPHZL+xXC+7n",seed:"jEB7XsKJnlCZxT6M55O/lOcbF4KMQHtewomeUJnFPow=",encrypted:"AR3o2JwhHLKUfOLZ26KXD9INUK1/fWJzdZix7E545qladDYdpHRaE5zBP9nf6IPmZvBUPq75n1E4suxm+Bom7crf9be1HXCFZnmR/wo92CKg4D1zRlBwr/3Gitr3h9rU6N+tid2x9yOYj955rf3Bq4j6wmjYQpWphbhBIBMoliyJ"},{title:"RSAES-OAEP Encryption Example 2.2",message:"LQ==",seed:"tgDPPC5QbX8Wd4yRDTqLAD7uYdW2AM88LlBtfxZ3jJE=",encrypted:"AIeYuAD2aYZYnEu1YK+INur95FfP2pTz8/k4r3xwL4bVMufgvzWFLdVK24fP96jTteLkrX6HjmebBVeUhSWG3ahebh3LH5yVS9yx+xHzM1Jxc8X1rS+kYgdCGWFbszMF/vP0ogisy5XthHqcoHNEM4Rzln7ugrXuS+dNuuPEjIAf"},{title:"RSAES-OAEP Encryption Example 2.3",message:"dPyIxRvJD3evnV6aSnATPUtOCzTaPDfH744=",seed:"pzdoruqpH52MHtb50rY0Z/B8yuOnN2iu6qkfnYwe1vk=",encrypted:"AMkW9IJHAFs0JbfwRZhrRITtj1bQVDLcjFCwYxHMDBlSHIqpDzSAL8aMxiUq41Feo9S2O/1ZTXIiK8baJpWs9y+BPqgi1lABB6JJIvU2QZYMzWK0XgjkWk12g6HSPFhuK4yf+LQ1UYpbKVquUdZ9POOCR8S7yS+tdful6qP8Wpkm"},{title:"RSAES-OAEP Encryption Example 2.4",message:"p+sqUDaTHSfU6JEybZlpL/rdqb9+/T405iLErcCF9yHf6IUHLHiiA7FRc5vlQPqMFToQ8Ao=",seed:"mns7DnCL2W+BkOyrT7mys4BagVaaezsOcIvZb4GQ7Ks=",encrypted:"AJ6YQ3DNjd7YXZzjHASKxPmwFbHKwoEpof+P+Li3+o6Xa95C21XyWZF0iCXc5USp5jwLt66T6G3aYQkEpoyFGvSPA3NV6tOUabopdmslYCkOwuOIsFLiuzkJc4Hu6nWXeJtTVtHn7FmzQgzQOMjuty1YConfe78YuQvyE3IAKkr2"},{title:"RSAES-OAEP Encryption Example 2.5",message:"LvKwZvhUwz873LtZlKQ15z1sbA==",seed:"6zzrvErcFrtI6IyK7A40r39Cf9PrPOu8StwWu0jojIo=",encrypted:"AMv457W0EOt8RH+LAEoMQ7dKjZamzOdwTHJepDkaGGoQHi2z8coCiVemL5XYZ+ctjPBdw3y3nlMn1sif9i3WCzY26ram8PL5eVYk7Bm3XBjv9wuhw1RZmLFzKfJS+3vi+RTFhwjyyeaJrc07f5E7Cu7CVWNh3Oe3lvSF3TB2HUI8"},{title:"RSAES-OAEP Encryption Example 2.6",message:"in+zRMi2yyzy7x9kP5oyGPbhm7qJwA==",seed:"TEXPTVfJjj1tIJWtxRxInrUN/4RMRc9NV8mOPW0gla0=",encrypted:"AJ5iMVr3Q6ZZlqLj/x8wWewQBcUMnRoaS2lrejzqRk12Bw120fXolT6pgo20OtM6/ZpZSN7vCpmPOYgCf93MOqKpN1pqumUH33+iP1a+tos5351SidwwNb2hLy3JfhkapvjB+c9JvbIolIgr+xeWhWPmMDam/Du/y+EsBOdZrbYc"}],f(a,l,"sha256",p),e="ArWP7AOahgcApNe2Ri+T5s3UkRYd3XT06BC0DjwWUgBqXCd7J3TBEwWky6taeO+lfheobfej+jb8Sx0iSfIux8LdakYyMqzOqQbWbr6AtXBLEHKdpvgzI0q7Xv3UopLL+tM7TTP6ehS4w5e1bjrNISA0KLd836M6bacGs9iw/EPp",t="AQAB",n="FbSKW1aDqUZw4jtXGPgU+g4T+FA49QcRGCy6YVEFgfPSLH4jLvk34i5VHWi4bi+MsarYvi5Ij13379J54/Vo1Orzb4DPcUGs5g/MkRP7bEqEH9ULvHxRL/y+/yFIeqgR6zyoxiAFNGqG3oa/odipSP0/NIwi6q3zM8PObOEyCP0=",r="Ab8B0hbXNZXPAnDCvreNQKDYRH0x2pGamD9+6ngbd9hf43Gz6Tc+e2khfTFQoC2JWN5/rZ1VUWCVi0RUEn4Ofq8=",i="AY0zmWWBZts4KYFteylUFnWenJGYf1stiuzWOwS0i9ey/PIpu3+KbciLoT3S45rVW20aBhYHCPlwC+gLj9N0TOc=",s="BsCiSdIKby7nXIi0lNU/aq6ZqkJ8iMKLFjp2lEXl85DPQMJ0/W6mMppc58fOA6IVg5buKnhFeG4J4ohalyjk5Q==",o="0dJ8Kf7dkthsNI7dDMv6wU90bgUc4dGBHfNdYfLuHJfUvygEgC9kJxh7qOkKivRCQ7QHmwNEXmAuKfpRk+ZP6Q==",u="jLL3Vr2JQbHTt3DlrTHuNzsorNpp/5tvQP5Xi58a+4WDb5Yn03rP9zwneeY0uyYBHCyPfzNhriqepl7WieNjmg==",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 3.1",message:"CHggtWno+o0=",seed:"jO1rGWKQgFeQ6QkHQBXmogsMSJSM7WsZYpCAV5DpCQc=",encrypted:"AJqBCgTJGSHjv2OR0lObiDY2gZmWdutHfVeadCdFr2W4mS3ZHwet283wbtY/bsM8w0rVxNAPh3NZNrcRt56NhoT0NzD2IK3WNy39Im/CfbicvC6Vq2PyXUh1iza+90PUM3jECPP5NsOx658MzEnYyFZFb9izZIna6YLsXwkWoHVO"},{title:"RSAES-OAEP Encryption Example 3.2",message:"RlOsrxcZYLAfUqe+Y6OrIdw2jsQ7UNguw3geBA==",seed:"tCkdZWdVCEjMFWlnyAm6q2ylB/C0KR1lZ1UISMwVaWc=",encrypted:"ARCj8j/hSsscyXtuINlyU0HuC+d7wZc7bSekF60BJFWKeKa1p28d4KsJXmdqI22sxha7PgkI9bgpfgdBd8KHp12g5y68uXiwRyPOvv8s6YDKmJFhbW13LHbE3iZHch2YG1eHi/20M/IrsAqCuk/W5Q/dP5eSVM1hLT9LBVsX3rIH"},{title:"RSAES-OAEP Encryption Example 3.3",message:"2UzQ4I+kBO2J",seed:"zoko9gWVWCVACLrdl5T63NL9H2XOiSj2BZVYJUAIut0=",encrypted:"Anfa/o/QML7UxLCHcSUWFPUWhcp955u97b5wLqXuLnWqoeQ3POhwasFh3/ow2lkzjjIdU47jkYJEk6A0dNgYiBuDg57/KN5yS2Px/QOSV+2nYEzPgSUHGyZacrHVkj/ZVyZ+ni7Iyf/QkNTfvPGxqmZtX6cq095jgdG1ELgYsTdr"},{title:"RSAES-OAEP Encryption Example 3.4",message:"bMZBtrYeb5Y5dNrSOpATKE7x",seed:"bil59S1oFKV9g7CQBUiI8RmluaNuKXn1LWgUpX2DsJA=",encrypted:"AalUnNYX91mP0FrqphpfhU22832WgnjDNRU1pkpSrd5eD7t7Q1YhYE+pKds6glA8i1AE/li216hJs2IbCJMddyaXrDzT8V9/UfIUaSkLfcRYBrTn9DEDOTjY1Xnn38poLOFykpZbAz5hdbOh0qG39qFgl5QZG0+aTBd1tmlMZBfO"},{title:"RSAES-OAEP Encryption Example 3.5",message:"31FRgyth9PJYkftBcvMo0u3fg3H/z9vpl5OSlfMOymkYAXz9oRU796avh1kyIw==",seed:"LXYL/jjFneNM3IuMeKOOZihKLSctdgv+OMWd40zci4w=",encrypted:"AGgQQYTuy9dW6e3SwV5UFYbEtqQD7TDtxcrMYOmYlTPgTwIFpo4GbQbtgD9BMFAW7a1lIzLxKEld49jH6m95Xgtq/BAVFl/gXin5MMbiZfRTOl38miBTg5a6IS9w6tcrWIBeY5Z5n4iCuUqF9r/m9TqvxWF0aMP2VGVKZn+LHMVj"},{title:"RSAES-OAEP Encryption Example 3.6",message:"PDutiTxUSm1SCrAiMZGIyNUEt6eIuFCQO4WXLqoYVS4RNKetYJiCYlT/erZys9jrMVj6xtTLrvE=",seed:"8XR3nF/Tz+AHuty3o2ybVb/Pvw7xdHecX9PP4Ae63Lc=",encrypted:"Aps8BQrRkPPwpNIjHw3NBznsDvp1hIHmlbG5wRERr9+Ar4ervO2GA/MMUVNijdZEtFnCGjbLwpM6RKzCk96jJX1bIgzq7hnmIzwKmq2Ue4qqO29rQL39jpCS87BBo/YKMbkYsPc2yYSDMBMOe9VDG63pvDgFGrlk/3Yfz1km3+/Y"}],f(a,l,"sha256",p),e="BRJAtswABPpI0BNGccB4x8jew7Pi8lvCVkRnM52ziFPQa4XupbLeNTv/QqwuRryX+uaslhjalTelyPVTweNXYlmR1hCNzXiF+zolQT9T78rZSMs1zZua6cHGdibRE9V93kxb6na7W7felsANBzculoWm11z50jn6FI1wkxtfP7A5",t="AQAB",n="BBH/yjt8penpvn/jioUQXjU4ltsFxXlq7NKnJRYes2UchimpuGK5BNewx7N/jLWhwrVAAQGKAKHrLK/k7k6UksNIvCvtq0ueu/Bk6O/zIrkAn47sZTkF9A34ijzcSdRWf3VifUGspiQSm0agt8aY5eZfK3uhAsdJoQE1tlQNBAE=",r="AnRYwZ7BY2kZ5zbJryXWCaUbj1YdGca/aUPdHuGriko/IyEAvUC4jezGuiNVSLbveSoRyd6CPQp5IscJW266VwE=",i="AhDumzOrYXFuJ9JRvUZfSzWhojLi2gCQHClL8iNQzkkNCZ9kK1N1YS22O6HyA4ZJK/BNNLPCK865CdE0QbU7UTk=",s="OfoCi4JuiMESG3UKiyQvqaNcW2a9/R+mN9PMSKhKT0V6GU53J+Sfe8xuWlpBJlf8RwxzIuvDdBbvRYwweowJAQ==",o="AV2ZqEGVlDl5+p4b4sPBtp9DL0b9A+R9W++7v9ax0Tcdg++zMKPgIJQrL+0RXl0CviT9kskBnRzs1t1M8eVMyJk=",u="AfC3AVFws/XkIiO6MDAcQabYfLtw4wy308Z9JUc9sfbL8D4/kSbj6XloJ5qGWywrQmUkz8UqaD0x7TDrmEvkEro=",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 4.1",message:"SoZglTTuQ0psvKP36WLnbUVeMmTBn2Bfbl/2E3xlxW1/s0TNUryTN089FmyfDG+cUGutGTMJctI=",seed:"HKwZzpk971X5ggP2hSiWyVzMofMcrBnOmT3vVfmCA/Y=",encrypted:"AooWJVOiXRikAgxb8XW7nkDMKIcrCgZNTV0sY352+QatjTq4go6/DtieHvIgUgb/QYBYlOPOZkdiMWXtOFdapIMRFraGeq4mKhEVmSM8G5mpVgc62nVR0jX49AXeuw7kMGxnKTV4whJanPYYQRoOb0L4Mf+8uJ5QdqBE03Ohupsp"},{title:"RSAES-OAEP Encryption Example 4.3",message:"v21C5wFwex0CBrDItFoccmQf8SiJIZqCveqWW155qWsNAWPtnVeOya2iDy+88eo8QInYNBm6gbDGDzYG2pk=",seed:"rZl/7vcw1up75g0NxS5y6sv90nWtmX/u9zDW6nvmDQ0=",encrypted:"AtYYko32Vmzn3ZtrsDQH9Mw/cSQk9pePdwQZJ6my7gYXWYpBdhbEN/fH7LMmvjtHnKLLTDazfF1HT0tTG6E+TY002cy+fMUvdRn0rfmFkNeHeqVOABP2EmI4eXFCBbbIlpshLxbA3vDTzPPZZqwMN+KPG4O11wmS9DcyHYtpsIOU"},{title:"RSAES-OAEP Encryption Example 4.4",message:"+y7xEvXnZuuUAZKXk0eU974vb8HFjg==",seed:"E2RU31cw9zyAen5A2MGjEqxbndMTZFTfVzD3PIB6fkA=",encrypted:"AZX8z/njjTP/ApNNF+BNGUjlczSK/7iKULnZhiAKo4LJ0XaTzTtvL9jacr+OkRxSPUCpQeK36wXdi9qjsl3SO9D7APyzN1nNE5Nu5YstiAfEMVNpdRYGdgpUasEZ4jshBRGXYW28uTMcFWRtzrlol9Lc7IhIkldTXZsR9zg11KFn"},{title:"RSAES-OAEP Encryption Example 4.5",message:"KMzUR7uehRZtq7nlt9GtrcS5058gTpbV5EDOmtkovBwihA==",seed:"vKgFf4JLLqJX8oYUB+72PTMghoG8qAV/gksuolfyhhQ=",encrypted:"A8GIo5X2qOS6MdKjYJg+h3hi2endxxeb3F5A8v+MbC7/8WbBJnzOvKLb6YMukOfAqutJiGGzdPQM9fopdhbRwS/Ovw4ksvmNBVM+Q26CFPqvdhV8P0WxmeYTxGFGrLgma+fwxpe7L6mj300Jq6Y/5kfTEJSXNdKuLRn0JsIg8LSD"},{title:"RSAES-OAEP Encryption Example 4.6",message:"8iJCdR7GsQ==",seed:"Ln4eF/ZHtd3QM+FUcvkPaBLzrE4ufh4X9ke13dAz4VQ=",encrypted:"AM9cnO14EVBadGQYTnkkbm/vYwqmnYvnAutrc4bZR3XC0DNhhuUlzFosUSmaC1LrjKcFfcqZOwzlev5uZycR7tUlLihC6lf4o0khAjUb03Dj+ubNsDKNCOA6vP63N3p6jSVIak7EPu0KtBLuNpIyW5mdkMuwssV7CNLOQ6qG7zxZ"}],f(a,l,"sha256",p),e="Cq3z+cEl5diR8xrESOmT3v5YD4ArRfnX8iulAh6cR1drWh5oAxup205tq+TZah1vPSZyaM/0CABfEY78rbmYiNHCNEZxZrKiuEmgWoicBgrA2gxfrotV8wm6YucDdC+gMm8tELARAhSJ/0l3cBkNiV/Tn1IpPDnv1zppi9q58Q7Z",t="AQAB",n="AlbrTLpwZ/LSvlQNzf9FgqNrfTHRyQmbshS3mEhGaiaPgPWKSawEwONkiTSgIGwEU3wZsjZkOmCCcyFE33X6IXWI95RoK+iRaCdtxybFwMvbhNMbvybQpDr0lXF/fVKKz+40FWH2/zyuBcV4+EcNloL5wNBy+fYGi1bViA9oK+LF",r="A7DTli9tF1Scv8oRKUNI3PDn45+MK8aCTyFktgbWh4YNrh5jI5PP7fUTIoIpBp4vYOSs1+YzpDYGP4I4X0iZNwc=",i="AuTDLi9Rcmm3ByMJ8AwOMTZffOKLI2uCkS3yOavzlXLPDtYEsCmC5TVkxS1qBTl95cBSov3cFB73GJg2NGrrMx8=",s="AehLEZ0lFh+mewAlalvZtkXSsjLssFsBUYACmohiKtw/CbOurN5hYat83iLCrSbneX31TgcsvTsmc4ALPkM429U=",o="65CqGkATW0zqBxl87ciBm+Hny/8lR2YhFvRlpKn0h6sS87pP7xOCImWmUpfZi3ve2TcuP/6Bo4s+lgD+0FV1Tw==",u="AS9/gTj5QEBi64WkKSRSCzj1u4hqAZb0i7jc6mD9kswCfxjngVijSlxdX4YKD2wEBxp9ATEsBlBi8etIt50cg8s=",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 5.1",message:"r3GpAeOmHTEy8Pwf20dPnqZXklf/wk0WQXAUWz296A==",seed:"RMkuKD93uUmcYD2WNmDIfS+TlGFEyS4oP3e5SZxgPZY=",encrypted:"BOGyBDRo+2G7OC79yDEzJwwLMPuhIduDVaaBdb5svHj/ZAkVlyGVnH0j+ECliT42Nhvp4kZts+9cJ0W+ui7Q9KXbjmX033MpxrvSV1Ik//kHhX6xTn51UGpaOTiqofjM3QTTi9DVzRtAarWd/c8oAldrGok1vs+tJEDbcA5KvZz7"},{title:"RSAES-OAEP Encryption Example 5.2",message:"o7hEoII5qKxBYFrxemz9pNNQE2WFkDpBenkmh2BRmktKwzA+xz8Ph8+zI5k=",seed:"yyj1hgZZ/O7knD7q/OYlpwgDvTLLKPWGBln87uScPuo=",encrypted:"AeleoSbRCOqBTpyWGLCrZ2G3mfjCYzMvupBy+q+sOKAfBzaLCjCehEUmIZFhe+CvtmVvyKGFqOwHUWgvks9vFU7Gfi580aZm7d4FtaGGVHBO6Q32/6IS7a+KSk7L6rPWwBTI+kyxb5SW12HTEowheKkFTda06tU0l4Ji45xP2tyh"},{title:"RSAES-OAEP Encryption Example 5.3",message:"MIsOy9LHbLd/xvcMXt0jP9LyCSnWKfAmlTu2Ko9KOjFL3hld6FtfgW2iqrB00my2rN3zI647nGeKw88S+93n",seed:"IoX0DXcEgvmp76LHLLOsVXFtwMoihfQNdwSC+anvosc=",encrypted:"Ci3TBMV4P0o59ap6Wztb9LQHfJzYSOAaYaiXjk85Q9FYhAREaeS5YXhegKbbphMIS5i1SYJShmwpYu/t8SGHiX/72v6NnRgafDKzttROuF/HJoFkTBKH6C9NKke+mxoDy/YVZ9qYzFY6PwzB4pTDwku9s5Ha4DmRBlFdA/z713a4"},{title:"RSAES-OAEP Encryption Example 5.4",message:"FcW57hGF",seed:"SfpF06eN0Q39V3OZ0esAr37tVRNJ+kXTp43RDf1Xc5k=",encrypted:"AcMQiclY0MMdT9K4kPqZ7JYHTaSolc8B3huHcQ4U5mG11/9XjzWjTLha8Liy0w909aaPbaB7+ZQTebg7x3F4yeWFRmnAJMaIFGBW/oA952mEaJ+FR2HO0xfRPzCRCaaU7cyOxy0gnR8d9FMunt9fhbffM9TvOfR6YDE5Duz6Jg0W"},{title:"RSAES-OAEP Encryption Example 5.5",message:"IQJuaADH+nKPyqug0ZauKNeirE/9irznlPCYX2DIpnNydzZdP+oR24kjogKa",seed:"8Ch0EyNMxQNHJKCUxFhrh6/xM/zwKHQTI0zFA0ckoJQ=",encrypted:"A/gvyZ/MNHUG3JGcisvAw/h1bhviZsqIsEM5+gBpLfj7d8iq28yZa6z5cnS4cJWywHxyQMgt9BAd37im/f5WcIcB+TZS9uegFSdgaetpYf4ft/1wMlcdc1ReCrTrCKPFHLLczeifyrnJSVvQD84kQY21b4fW9uLbSiGO0Ly94il1"},{title:"RSAES-OAEP Encryption Example 5.6",message:"VB43totsiHK4TAI=",seed:"2fukXJbyHm4m0p6yzctlhb6cs0HZ+6RclvIebibSnrI=",encrypted:"AWmTYpBHOqRBGy1h5mF88hMmBVNLN++kXAqQr4PKszqorigNQZbvwbOdWYNLsXydyvKi55ds8tTvXf4rRBswyuNmbtT0t2FVCTnTjNzA1cMSahInbdKfL/1wib3CjyQmC0TbbIa3kkAdXkiYytSafDxwNyan1OWtJcMLkJ6l8WRm"}],f(a,l,"sha256",p),e="ErF/ba0uzRn/RtwT94YPCeDgz7Z3s4pSWSMFzq8CLBZtuQ0ErCnjP33RLZ+vZuCBa7Y+rSZ8x9RsF8N74hS8oqItcjpk5EQHQ2tvyWVymu/CVU83bNXc6mgpN4CmK/OdAClIWhYLu55dwJctIaUE9S5e4CiqQWMy9RCy6c/19yKv",t="AQAB",n="ApXso1YGGDaVWc7NMDqpz9r8HZ8GlZ33X/75KaqJaWG80ZDcaZftp/WWPnJNB7TcEfMGXlrpfZaDURIoC5CEuxTyoh69ToidQbnEEy7BlW/KuLsv7QV1iEk2Uixf99MyYZBIJOfK3uTguzctJFfPeOK9EoYij/g/EHMc5jyQz/P5",r="BKbOi3NY36ab3PdCYXAFr7U4X186WKJO90oiqMBct8w469TMnZqdeJpizQ9g8MuUHTQjyWku+k/jrf8pDEdJo4s=",i="BATJqAM3H+20xb4588ALAJ5eCKY74eQANc2spQEcxwHPfuvLmfD/4Xz9Ckv3vv0t1TaslG23l/28Sr6PKTSbke0=",s="A5Ycj3YKor1RVMeq/XciWzus0BOa57WUjqMxH8zYb7lcda+nZyhLmy3lWVcvFdjQRMfrg6G+X63yzDd8DYR1KUs=",o="AiGX4GZ0IZaqvAP6L+605wsVy3h9YXrNMbt1x7wjStcG98SNIYLR8P+cIo3PQZZ7bAum0sCtEQobhXgx7CReLLE=",u="BAHEwMU9RdvbXp2W0P7PQnXfCXS8Sgc2tKdMMmkFPvtoas4kBuIsngWN20rlQGJ64v2wgmHo5+S8vJlNqvowXEU=",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 6.1",message:"QEbKi6ozR8on9J4NgfnMHXG+m6UX1A==",seed:"3Q9s/kFeiOWkaaUfu6bf1ArbQ4TdD2z+QV6I5aRppR8=",encrypted:"C3d3hdR81ybq+Wuf6QUfy2KHVuQjrsvVH2HuE2LbJT2o2ZPdrDHIoGphdGj+GWNTrcV/d8iPLJlZ0CR3O2e2b7wLUVPMorv1HidYA8B8eJxkg5FIsPuK836LchnGqQlE7ObiWOjSuIw4lZ/ULCfOYejelr6PJXSxWgQUlV78sbvP"},{title:"RSAES-OAEP Encryption Example 6.2",message:"XMcsYCMd8Ds9QPm1eTG8MRCflyUn8osZ50gMcojLPJKyJRIhTkvmyRR5Ldq99X+qiqc=",seed:"jRS9lGoTURSPXK4u2aDGU+hevYWNFL2UahNRFI9cri4=",encrypted:"DXAHBh/uWFjxl/kIwrzm0MXeHNH5MSmoPc0mjn00UcCUFmOwTQipPmLmephH+rNOOfCQVvwP5wysU3/w2hjmk/rl6Jb4qNc+KqDiij7fKSKhPGTvY3aiXZ2LflnJ3yv4LdT9KvvWsZrHEWfsEG+fQZW4c1OMEpOMC4N44nc88yRm"},{title:"RSAES-OAEP Encryption Example 6.3",message:"sg5lEwMJL0vMtDBwwPhtIwSTYu2WZC/FYywn20pS49gx8qsGiyOxSYecAC9r8/7ul1kRElYs",seed:"bAdbxFUg8WXAv16kxd8ZG8nvDkRsB1vEVSDxZcC/XqQ=",encrypted:"AMe1ZPYbk4lABLKDLhwJMM4AfK46Jyilp/vQ9M921AamJzanoNGdlj6ZEFkbIO68hc/Wp4Qr43iWtjcasgpLw2NS0vroRi91VI5k9BZgXtgNG7Z9FBOtPjM61Um2PWSFpAyfaZS7zoJlfRKciEa+XUKa4VGly4fYSXXAbUJV2YHc"},{title:"RSAES-OAEP Encryption Example 6.4",message:"aE4wOMXAQfc=",seed:"O7w71mN9/hKEaQECm/WwwHEDQ5w7vDvWY33+EoRpAQI=",encrypted:"AJS/vpVJKJuLwnnzENVQChT5MCBa0mLxw/a9nt+6Zj4FL8nucIl7scjXSOkwBDPcyCWr7gqdtjjZ9z6RCQv0HfjmVKI2M6AxI2MYuzwftIQldbhCRqo8AlyK3XKjfcK+Rzvii53W8Xw4Obbsv9OCLnCrrbK8aO3XKgrHPmDthH7x"},{title:"RSAES-OAEP Encryption Example 6.5",message:"MkiMsmLQQdbk3TX5h788ppbbHwasKaRGkw==",seed:"tGtBiT6L7zJvZ1k4OoMHHa5/yry0a0GJPovvMm9nWTg=",encrypted:"CmNUKnNQco5hWHxCISdwN5M7LbL7YJ0u7bfH82LulE32VdATd3vcJmRiFtcczNNudMlHVhl6/ZsDVY1zymLrK2kLIYWeG9Iag3rQ5xhjLAdpMYBBuwjrJ8Oqc4+2qH57bBveynuE5xRpd9p+CkkiRP7x7g4B/iAwrmFxPtrxV/q/"},{title:"RSAES-OAEP Encryption Example 6.6",message:"ULoUvoRicgJ5wwa6",seed:"CiQDMSpB49UvBg+8E6Z95c92CacKJAMxKkHj1S8GD7w=",encrypted:"DpQAu4uQ4zbkpP/f698+a5f3MhAXCi3QTcP7vXmQVlkH0CFlCnDESNG36Jk2ybe3VmzE2deBHBKI9a5cHUzM9Lsa/AoxnbD5qd2fJt9k19dSRtDWZUR/Bn/AdVHwstzsX/vRLe6qOk9Kf01OZcvKrmlWh2IBLs8/6sEJhBWXNAKj"}],f(a,l,"sha256",p),e="MRF58Lz8m508oxXQDvMNe906LPrpkRv+3LlIs6R4LQcytqtEqkvwN0GmRNwBvsPmmwGgM+Z12KzXxJJcaxrsMRkFHf2Jdi0hXUVHX/y1n5CBSGI/NxdxVvauht16fF9D3B4fkIJUBYooSl8GwAIXk6h/GsX+/33K7mnF5Ro3ieNz",t="AQAB",n="Bwz8/y/rgnbidDLEXf7kj0m3kX1lMOHwyjRg8y4CdhdEh8VuIqRdJQDXd1SVIZ19Flqc872Swyr5qY2NycwpaACtyUoKVPtA80KRv4TujqErbxCTWcbTVCpQ+cdn9c//BaaBwuZW+3fKqttL6UaNirzU35j1jobSBT+hNJ90jiGx",r="B0kmLBEc1HDsJWbms3MvwJMpRpqhkHHTucAZBlFMbx0muqFL6rCXHIt+YRpPeQCdb+p3aSjKJShbDeNkPRo/jHE=",i="BrweUOlsAr9jbp7qi4mbvr92Ud533UdMPpvCO62BgrYZBMfZffvr+x4AEIh4tuZ+QVOR1nlCwrK/m0Q1+IsMsCM=",s="A7x+p/CqsUOrxs6LlxGGNqMBcuTP4CyPoN2jt7qvkPgJKYKYVSX0iL38tL1ybiJjmsZKMJKrf/y/HVM0z6ULW/E=",o="AmKmqinCo8Z9xTRsBjga/Zh6o8yTz7/s9U/dn514fX9ZpSPTmJedoTei9jgf6UgB98lNohUY3DTLQIcMRpeZStk=",u="ZJ1MF7buFyHnctA4mlWcPTzflVDUV8RrA3t0ZBsdUhZq+KITyDliBs37pEIvGNb2Hby10hTJcb9IKuuXanNwwg==",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 7.1",message:"R6rpCQ==",seed:"Q90JoH/0ysccqkYy7l4cHa7kzY9D3Qmgf/TKxxyqRjI=",encrypted:"CdXefX8LEW8SqnT1ly7/dvScQdke1rrSIBF4NcFO/G+jg0u7yjsqLLfTa8voI44Ue3W6lVuj5SkVYaP9i7VPmCWA4nFfeleuy23gbHylm4gokcCmzcAm2RLfPQYPnxIb3hoQ2C3wXo/aWoLIMFPYzI19g5uY90XMEchAci3FVC/a"},{title:"RSAES-OAEP Encryption Example 7.2",message:"HZsuIiPZvBO/ufFiznNdtIunxo9oIqChp7auFlg05w==",seed:"Opw87HuE+b063svGc+yZ1UsivJs6nDzse4T5vTrey8Y=",encrypted:"DDar5/aikhAVropPgT3SVzSMRtdS9sEmVBEqg9my/3na0Okz51EcAy436TOZVsM0exezvKYsVbDQhtOM0Mn9r6oyBsqzUR4lx6Gt2rYDYC4X1aMsJSVcQs9pDqeAWfIAmDIIQH/3IN2uJ6u4Xl2+gFCpp8RP0F//Rj2llnEsnRsl"},{title:"RSAES-OAEP Encryption Example 7.3",message:"2Xb8",seed:"dqdeW2FXpVbPiIS7LkXCk91UXPV2p15bYVelVs+IhLs=",encrypted:"GpTkYrRFNyD9Jw1Pc1TSPSfc9Yb8k4Fw1l4kCwqPodlAboKMJe+yuXoGgVeB7Jb7JTQklGpQc1keZUzUUVZO0Q4qYUelFFe5lWM2uhq21VCbvcchrMTP6Wjts05hVgJHklLKF5cOtBGpQC0FlkwCYoXUAk//wejNycM/ZXw+ozkB"},{title:"RSAES-OAEP Encryption Example 7.4",message:"1HOGI98iOqQ4Q9+EZ1NMQdAT4MgDxiTiY2ZrI5veQKXymuuN5549qmHdA3D0m9SwE4NLmCEq72scXuNzs8s=",seed:"eGYxSmrW8rJQo1lB2yj1hktYWFl4ZjFKatbyslCjWUE=",encrypted:"G5GJEPP4ifUCg+3OHEq41DFvaucXwgdSGyuDX6/yQ1+e30d0OIjIFv4JTUXv6Oi8/uADg+EN5Ug+lEyf0RNSS4wfKgRfAaXK6x1U8dh48g/bED27ZCZ+MjhAkUcjMO0h4m3nNfLxAju7nxO2cJzNI9n1TBCMngJBco0zzhOvMZaN"},{title:"RSAES-OAEP Encryption Example 7.5",message:"u0cjHKXqHTrUbJk0XZqKYQ==",seed:"shZu1HLVjbEMqyxrAAzM8Qp9xQmyFm7UctWNsQyrLGs=",encrypted:"HebBc/6i18c2FbG7ibWuxyQgtiN1uhtxyNsXw1Kuz8zo7RkBkt5JZEwucKyXFSwI6drZlK6QaqCRZwPQsdc2wnZlQzbkilVf1TiACqzDdpKX5i+SbCTUsOyGETV3vtxFe7/SatEKseFSLEWkIfZxAFcisIs5hWmLJdqfWQeYuMrK"},{title:"RSAES-OAEP Encryption Example 7.6",message:"IYSCcJXTXD+G9gDo5ZdUATKW",seed:"Umc73iyhZsKqRhMawdyAjWfX07FSZzveLKFmwqpGExo=",encrypted:"DX+W3vsdnJfe63BVUFYgCAG1VmTqG/DbQ4nZgWTUGHhuijUshLtz07dHar21GJ9Ory8QQPX67PgKGnBMp0fJBnqKO3boMOEcc52HEnQxOWIW2h2rgmDtnaYtvK85pddXzhbuXkXg4DDnoMy+4XzgbLfArK12deGB0wruBbQyv3Ar"}],f(a,l,"sha256",p),e="W98OMNMh3aUUf4gkCPppGVSA34+A0/bov1gYUE82QnypsfVUC5xlqPaXTPhEeiRNkoAgG7Sfy75jeNGUTNIn4jD5bj0Q+Bnc7ydsZKALKktnAefQHeX6veOx6aDfgvRjE1nNImaWR/uxcXJGE07XtJfP/73EK1nHOpbtkBZiEt/3",t="AQAB",n="D30enlqqJf0T5KBmOuFE4NFfXNGLzbCd8sx+ZOPF6RWtYmRTBBYdCYxxW7eri9AdB+rz/tfH7QivKopi70SrFrMg4Ur3Kkj5av4mKgrkz2XmNekQeQzU7lzqdopLJjn35vZ3s/C7a+MrdXR9iQkDbwJk9Y1AHNuhMXFhV6dez2Mx",r="CgLvhEjZ+ti70NAEyMKql1HvlyHBsNAyNqVLDflHy67VolXuno4g1JHqFyP+CUcEqXYuiK/RbrtZlEEsqWbcT58=",i="CS02Ln7ToL/Z6f0ObAMBtt8pFZz1DMg7mwz01u6nGmHgArRuCuny3mLSW110UtSYuByaxvxYWT1MP7T11y37sKk=",s="B8cUEK8QOWLbNnQE43roULqk6cKd2SFFgVKUpnx9HG3tJjqgMKm2M65QMD4UA10a8BQSPrpoeCAwjY68hbaVfX0=",o="rix1OAwCwBatBYkbMwHeiB8orhFxGCtrLIO+p8UV7KnKKYx7HKtYF6WXBo/IUGDeTaigFjeKrkPH+We8w3kEuQ==",u="BZjRBZ462k9jIHUsCdgF/30fGuDQF67u6c76DX3X/3deRLV4Mi9kBdYhHaGVGWZqqH/cTNjIj2tuPWfpYdy7o9A=",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 8.1",message:"BQt1Xl5ogPe56daSp0w3quRJsxv+pt7/g3R6iX9sLIJbsa2/hQo8lplLXeWzPLx9SheROnln",seed:"dwb/yh7PsevuKlXlxuJM0nl6QSV3Bv/KHs+x6+4qVeU=",encrypted:"DZZvGJ61GU6OOkaPl2t8iLNAB1VwLjl3RKd/tcu19Vz9j68fjCFBQvASq9FK6Sul/N6sXIVsi4ypx/1m77bErYJqiGwkE8sQz/g4ViwQmeCvpfbCoq00B5LxklezvhnM5OeSxFtO/8AtYimLrJ3sUmDYk7xkDI20/Lb8/pyOFsjH"},{title:"RSAES-OAEP Encryption Example 8.2",message:"TraNzZPKmxnfERvUNgj1VwJv5KodXPrCJ6PrWrlUjBigbd7SP4GCWYay/NcRCezvfv+Ihz8HXCqgxGn2nJK8",seed:"o3F9oUO03P+8dCZlqPqVBYVUg0OjcX2hQ7Tc/7x0JmU=",encrypted:"DwsnkHG2jNLgSU4LEvbkOuSaQ9+br9t3dwen8KDGmLkKVJgWGu+TNxyyo2gsBCw7S4eqEFrl49ENEhMehdjrHCBLrEBrhbHxgncKrwIXmcjX1DOCrQXEfbT4keig8TaXkasow5qby9Ded6MWcLu1dZnXPfiuiXaOYajMGJ1D3/y7"},{title:"RSAES-OAEP Encryption Example 8.3",message:"hgSsVjKMGrWtkXhh",seed:"7gYgkHPMoCa7Jk5Rhb+MaLdzn4buBiCQc8ygJrsmTlE=",encrypted:"PAKF3K/lSKcZKWQDr56LmmVqSltcaKEfS7G6+rwG239qszt8eKG6fMYJsP4h7ZfXyV1zuIZXTVhXgiRQbA9os0AhkWiMJJouhsAn60R20BOLQOtQxlXxVOvUMPGuG5EP2O+nTI0VCXky5kHNJdfJolSW+pJLVdSu4mX9Ooga1CSx"},{title:"RSAES-OAEP Encryption Example 8.4",message:"/dpfv27DYanZpKxoryFqBob0OLHg5cNrlV904QfznA3dzA==",seed:"mQrVc9xIqXMjW22CVDYY8ulVEF2ZCtVz3EipcyNbbYI=",encrypted:"LcCLDDj2S5ZOKwpvobOk6rfBWMBbxs3eWR+Edk3lKaoEAFFD5sQv0AaIs3r7yI8sOir9HvS6GKf+jc9t31zIDCIJc3sKVyrNZfEeUFSvihjbPZDo6IaZ8Jau8woE2p1z7n9rG+cbMwKuILRPSEN4hE0QSA/qz0wcye6bjb6NbK20"},{title:"RSAES-OAEP Encryption Example 8.5",message:"Sl9JFL7iXePGk0HeBw==",seed:"7MY7KPB1byL1Ksjm7BJRpuwwRxjsxjso8HVvIvUqyOY=",encrypted:"U+PBTMw6peNHnFQ3pwix/KvVeWu1nSKQGcr9QPpYNgHhuFZK6ARR7XhKxFtDoUMP+iVTXprcow4lr1Uaw4PnEx+cPe0Khl15R8GLiuh5Vm9p3lTPW1u58iP2Oa81pQZTB5AuFiD0fnFZsl+BYfjDVah3cMIu83KOBRPOdLY0j8iq"},{title:"RSAES-OAEP Encryption Example 8.6",message:"jgfWb3uICnJWOrzT81CSvDNAn7f4jyRyvg==",seed:"OSXHGzYtQKCm3kIUVXm6Hn3UWfw5JccbNi1AoKbeQhQ=",encrypted:"WK9hbyje7E0PLeXtWaJxqD4cFkdL5x4vawlKJSOO1OKyZ6uaY8vMYBhBO47xRIqtab5Ul5UGCwdvwPR69PpARsiiSfJHVavkihXixHGgZViGTMU/7J7ftSiNT9hAwrj4JL4f1+8RhTp6WKRzsXAEKpvLK0TrzQL3ioF3QtTsiu/a"}],f(a,l,"sha256",p),e="zyzUHjTKOnKOpcuK/2TDbSe971Nk4zb9aNMSPFoZaowocBPoU9UVbVjRUZVFIPtPbXsXq7aBd2WQnFdhGWWdkCsZBu2KKxDBVcJNEkUo2rnurjeb6sZuSkEXhty4/QBi68Aw3hIZoEwqjBt90xMeTWtsruLjGl7UGsFQmy7x7iqxg2S+VoypQcJezIT/nWQ7XsGqrhAqINc/R5t4D9bakQdSEtnqwDoGdNiZ66LkMfTES2Fba6IjK9SzO67XPWJd",t="AQAB",n="GYwUHiNxWpK8z2oRmlvBE4lGjSgR9UjXJ+F7SrDrmG1vIR77U7cffMvqh+5px17mFQCMUzLetSvzkKvfv+N9cgU2gVmyY4wd4ybiHSIlHw+1hIs78VAF0qdDMPCv6RbuYszBNE0dg6cJ5gZ2JzhA9/N3QkpeCk2nXwGzH/doGc+cv90hUkPDkXwD7zgZkxLlZ7O/eu06tFfzce+KFCP0W2jG4oLsERu6KDO5h/1p+tg7wbjGE8Xh6hbBHtEl6n7B",r="/I1sBL7E65qBksp5AMvlNuLotRnezzOyRZeYxpCd9PF2230jGQ/HK4hlpxiviV8bzZFFKYAnQjtgXnCkfPWDkKjD6I/IxI6LMuPaIQ374+iB6lZ0tqNIwh6T+eVepl79",i="0gDUXniKrOpgakAdBGD4fdXBAn4S3BoNdYbok52c94m0D1GsBEKWHefSHMIeBcgxVcHyqpGTOHz9+VbLSNFTuicEBvm7ulN9SYfZ4vmULXoUy//+p0/s3ako0j4ln17h",s="2xaAL3mi8NRfNY1p/TPkS4H66ChiLpOlQlPpl9AbB0N1naDoErSqTmyL6rIyjVQxlVpBimf/JqjFyAel2jVOBe8xzIz3WPRjcylQsD4mVyb7lOOdalcqJiRKsI23V1Kt",o="oKMXz+ffFCP4em3uhFH04rSmflSX8ptPHk6DC5+t2UARZwJvVZblo5yXgX4PXxbifhnsmQLgHX6m+5qjx2Cv7h44G2neasnAdYWgatnEugC/dcitL6iYpHnoCuKU/tKh",u="CyHzNcNTNC60TDqiREV4DC1lW5QBdMrjjHyKTmSTwLqf0wN0gmewg7mnpsth5C2zYrjJiW23Bk4CrVrmFYfaFbRknJBZSQn+s328tlS+tyaOyAHlqLSqORG+vYhULwW+",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 9.1",message:"9zX9VbqSWSw7Urj5xPaaqhy++P6IrdCVWVQSRn+c9OwLiWxZ7aFiEOdUnIq7EM28IaEuyba1uP0vEDmetg==",seed:"jsll8TSj7Jkx6SocoNyBadXqcFyOyWXxNKPsmTHpKhw=",encrypted:"kuBqApUSIPP0yfNvL0I57K2hReD8CcPhiYZFlPPmdM0cVFQHvdPMjQ2GcEekoBMk2+JR2H3IY6QF0JcANECuoepAuEvks/XolStfJNyUVUO3vLbWGlA1JOOSPiWElIdM0hmLN5In0DizqQit7R0mzH3Y1vUGPBhzOnNgNVQgrWVvqJugjG1SPY7LaZxIjhnz3O/8EkCpxLWcyWkFLX+ujnxIKxAqjmvEztiwLLlcWbJKILOy7KE1lctyh58wYP6e"},{title:"RSAES-OAEP Encryption Example 9.2",message:"gbkGYFAVpjqr5C3fEeGXiRL1QEx0dLJtzj7Ugr+WHsyBi/QgxUZZ",seed:"7LG4sl+lDNqwjlYEKGf0r1gm0WzssbiyX6UM2rCOVgQ=",encrypted:"iNEGT/cssEWuXX1C+SWyMK1hhUjRdNz9l8FeCBhftw8I5JUY1EDXPi2hUpESGxbJOjbyricua+QVvfP/UeoPfOxCcpRSoA3DlviB0ExCJTpCb2NSv9qXtw6Z7qEqk2YyQD7mAsGb2/Y3ug3KkKrF68MAxsWFV3tmL2NV2h+yfW6qz1dVAbAIUZeRuLIbaLdY9F7O4yPC68zkaX9NcTg0tOtnAJth9fMAOFX8sVbvKBgeOuVHV1A8HcAbmqkLMIyp"},{title:"RSAES-OAEP Encryption Example 9.3",message:"/TJkKd+biQ4JtUsYuPNPHiQ=",seed:"6JuwMsbOYiy9tTvJRmAU6nf3d8Dom7Ayxs5iLL21O8k=",encrypted:"JiwfWprF58xVjVRR9B9r0mhomwU5IzkxXCZDgYJwYUcacmrz+KRLKMmtCMN7DLA2lOsfK+72mU+RLmhwfAAhBYmLGR8dLLstazb5xzU9wIM9u3jAl5iyyMLSo6wk/3SH0f7vC2bnFtMkhoHsd3VSTpzl5Q+SqX/4Q1JAMGWMMiHdyjCH+WaXNdTrboPEnPVtTcBGthkkYu8r/G0IkBR6OMPCZFgl/J4uiRTGCRbZx7UC02g6+qNMQY+ksygV6R8w"},{title:"RSAES-OAEP Encryption Example 9.4",message:"8UWbXwyS8BoPcjouVmJITY+MCiD8KdrWrNQ7tfPv/fThtj4H/f5mKNDXTKGb8taeSgq/htKTklp5Z3L4CI4=",seed:"YG87mcC5zNdx6qKeoOTIhPMYnMxgbzuZwLnM13Hqop4=",encrypted:"YXo+2y1QMWzjHkLtCW6DjdJ6fS5qdm+VHALYLFhG/dI1GmOwGOiOrFqesc5KPtWE73N5nJ680e6iYQYdFIsny6a4VH9mq/2Lr6qasMgM27znPzK8l6uQ1pTcDu1fJ4gCJABshzVEXzeTWx6OyKZsOFL8eXiNCwQpyfP9eH0tRjc+F75H3dnzX6AEVff4t0yKjDqp7aRMxFZGidcMJ6KetiNXUg1dhs/lHzItdQ7oMSUAgMnHYAvJDGqy5L4F8XXM"},{title:"RSAES-OAEP Encryption Example 9.5",message:"U+boxynW+cMZ3TF+dLDbjkzMol88gwV0bhN6xjpj7zc557WVq7lujVXlT3vUGrQzN4/7kR0=",seed:"/LxCFALp7KvGCCr6QLpfJlIshA78vEIUAunsq8YIKvo=",encrypted:"fwY+yhF2kyhotPKPlHEXcTOqVRG8Kg9bDJE/cSPUOoyVoiV0j57o9xpEYtZBuM5RanPUsTDcYNvorKqP5mbN81JV3SmEkIRTL7JoHGpJAMDHFjXBfpAwgUCPhfJ2+CUCIyOoPZqlt4w+K9l+WeFZYDatr0HC1NO+stbvWq358HRdX27TexTocG5OEB4l9gqhnUYD2JHNlGidsm0vzFQJoIMaH26x9Kgosg6tZQ0t3jdoeLbTCSxOMM9dDQjjK447"},{title:"RSAES-OAEP Encryption Example 9.6",message:"trKOohmNDBAIvGQ=",seed:"I6reDh4Iu5uaeNIwKlL5whsuG6Ijqt4OHgi7m5p40jA=",encrypted:"PISd/61VECapJ7gfG4J2OroSl69kvIZD2uuqmiro3E4pmXfpdOW/q+1WCr574Pjsj/xrIUdgmNMAl8QjciO/nArYi0IFco1tCRLNZqMDGjzZifHIcDNCsvnKg/VRmkPrjXbndebLqMtw7taeVztYq1HKVAoGsdIvLkuhmsK0Iaesp+/8xka40c9hWwcXHsG+I7pevwFarxQQbuUjXSkZ2ObWgzgGSiGCw9QNUGpO0usATLSd0AFkeE+IM/KAwJCy"}],f(a,l,"sha256",p),e="rkXtVgHOxrjMBfgDk1xnTdvg11xMCf15UfxrDK7DE6jfOZcMUYv/ul7Wjz8NfyKkAp1BPxrgfk6+nkF3ziPn9UBLVp5O4b3PPB+wPvETgC1PhV65tRNLWnyAha3K5vovoUF+w3Y74XGwxit2Dt4jwSrZK5gIhMZB9aj6wmva1KAzgaIv4bdUiFCUyCUG1AGaU1ooav6ycbubpZLeGNz2AMKu6uVuAvfPefwUzzvcfNhP67v5UMqQMEsiGaeqBjrvosPBmA5WDNZK/neVhbYQdle5V4V+/eYBCYirfeQX/IjY84TE5ucsP5Q+DDHAxKXMNvh52KOsnX1Zhg6q2muDuw==",t="AQAB",n="BWsEIW/l81SsdyUKS2sMhSWoXFmwvYDFZFCiLV9DjllqMzqodeKR3UP0jLiLnV/A1Jn5/NHDl/mvwHDNnjmMjRnmHbfHQQprJnXfv100W4BNIBrdUC1c4t/LCRzpmXu+vlcwbzg+TViBA/A29+hdGTTRUqMj5KjbRR1vSlsbDxAswVDgL+7iuI3qStTBusyyTYQHLRTh0kpncfdAjuMFZPuG1Dk6NLzwt4hQHRkzA/E6IoSwAfD2Ser3kyjUrFxDCrRBSSCpRg7Rt7xA7GU+h20Jq8UJrkW1JRkBFqDCYQGEgphQnBw786SD5ydAVOFelwdQNumJ9gkygHtSV3UeeQ==",r="7PWuzR5VFf/6y9daKBbG6/SQGM37RjjhhdZqc5a2+AkPgBjH/ZXMNLhX3BfwzGUWuxNGq01YLK2te0EDNSOHtwM40IQEfJ2VObZJYgSz3W6kQkmSB77AH5ZCh/9jNsOYRlgzaEb1bkaGGIHBAjPSF2vxWl6W3ceAvIaKp30852k=",i="vEbEZPxqxMp4Ow6wijyEG3cvfpsvKLq9WIroheGgxh5IWKD7JawpmZDzW+hRZMJZuhF1zdcZJwcTUYSZK2wpt0bdDSyr4UKDX30UjMFhUktKCZRtSLgoRz8c52tstohsNFwD4F9B1RtcOpCj8kBzx9dKT+JdnPIcdZYPP8OGMYM=",s="xzVkVx0A+xXQij3plXpQkV1xJulELaz0K8guhi5Wc/9qAI7U0uN0YX34nxehYLQ7f9qctra3QhhgmBX31FyiY8FZqjLSctEn+vS8jKLXc3jorrGbCtfaPLPeCucxSYD2K21LCoddHfA8G645zNgz72zX4tlSi/CE0flp55Tp9sE=",o="Jlizf235wQML4dtoEX+p2H456itpO35tOi9wlHQT7sYULhj7jfy2rFRdfIagrUj4RXFw8O+ya8SBJsU+/R0WkgGY3CoRB9woLbaoDNMGI2C6P6E/cOQxL/GmzWuPxM2cXD2xfG1qVyEvc64p9hkye61ZsVOFhYW6Tii2CmKkXkk=",u="bzhSazklCFU07z5BWoNu3ouGFYosfL/sywvYNDBP7Gg7qNT0ecQz1DQW5jJpYjzqEAd22Fr/QB0//2EO5lQRzjsTY9Y6lwnu3kJkfOpWFJPVRXCoecGGgs2XcQuWIF7DERfXO182Ij+t1ui6kN18DuYdROFjJR4gx/ZuswURfLg=",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 10.1",message:"i7pr+CpsD4bV8XVul5VocLCJU7BrTrIFvBaU7g==",seed:"R+GrcRn+5WyV7l6q2G9A0KpjvTNH4atxGf7lbJXuXqo=",encrypted:"iXCnHvFRO1zd7U4HnjDMCLRvnKZj6OVRMZv8VZCAyxdA1T4AUORzxWzAtAAA541iVjEs1n5MIrDkymBDk3cM1oha9XCGsXeazZpW2z2+4aeaM3mv/oz3QYfEGiet415sHNnikAQ9ZmYg2uzBNUOS90h0qRAWFdUV5Tyxo1HZ0slg37Ikvyu2d6tgWRAAjgiAGK7IzlU4muAfQ4GiLpvElfm+0vch7lhlrk7t5TErhEF7RWQe16lVBva7azIxlGyyrqOhYrmQ+6JQpmPnsmEKYpSxTUP2tLzoSH5e+Y0CSaD7ZB20PWILB+7PKRueJ23hlMYmnAgQBePWSUdsljXAgA=="},{title:"RSAES-OAEP Encryption Example 10.2",message:"5q0YHwU7WKkE8kV1EDc+Vw==",seed:"bRf1tMH/rDUdGVv3sJ0J8JpAec9tF/W0wf+sNR0ZW/c=",encrypted:"I3uBbIiYuvEYFA5OtRycm8zxMuuEoZMNRsPspeKZIGhcnQkqH8XEM8iJMeL6ZKA0hJm3jj4z1Xz7ra3tqMyTiw3vGKocjsYdXchK+ar3Atj/jXkdJLeIiqfTBA+orCKoPbrBXLllt4dqkhc3lbq0Z5lTBeh6caklDnmJGIMnxkiG3vON/uVpIR6LMBg+IudMCMOv2f++RpBhhrI8iJOsPbnebdMIrxviVaVxT22GUNehadT8WrHI/qKv+p1rCpD3AAyXAhJy7KKp1l+nPCy1IY1prey+YgBxCAnlHuHv2V7q1FZTRXMJe3iKubLeiX6SfAKU1sivpoqk5ntMSMgAfw=="},{title:"RSAES-OAEP Encryption Example 10.3",message:"UQos9g6Gb6I0BVPJTqOfvCVjEeg+lEVLQSQ=",seed:"OFOHUU3szHx0DdjN+druSaHL/VQ4U4dRTezMfHQN2M0=",encrypted:"n3scq/IYyBWbaN4Xd+mKJ0bZQR10yiSYzdjV1D1K3xiH11Tvhbj59PdRQXflSxE1QMhxN0jp9/tsErIlXqSnBH2XsTX6glPwJmdgXj7gZ1Aj+wsl15PctCcZv0I/4imvBBOSEd5TRmag3oU7gmbpKQCSHi6Hp2z5H/xEHekrRZemX7Dwl6A8tzVhCBpPweKNpe34OLMrHcdyb0k/uyabEHtzoZLpiOgHRjqi7SHr2ene9PPOswH7hc87xkiKtiFOpCeCScF6asFeiUTn5sf5tuPHVGqjKskwxcm/ToW3hm7ChnQdYcPRlnHrMeLBJt6o6xdrg6+SvsnNzctLxes0gA=="},{title:"RSAES-OAEP Encryption Example 10.4",message:"vN0ZDaO30wDfmgbiLKrip18QyR/2Z7fBa96LUwZKJkmpQEXJ",seed:"XKymoPdkFhqWhPhdkrbg7zfKi2VcrKag92QWGpaE+F0=",encrypted:"KWbozLkoxbGfY0Dixr8GE/JD+MDAXIUFzm7K5AYscTvyAh9EDkLfDc/i8Y9Cjz/GXWsrRAlzO9PmLj4rECjbaNdkyzgYUiXSVV0SWmEF62nhZcScf+5QWHgsv6syu2VXdkz9nW4O3LWir2M/HqJ6kmpKVm5o7TqeYZ7GrY25FUnFDM8DpXOZqOImHVAoh8Tim9d2V9lk2D2Av6Tdsa4SIyBDj5VcX3OVoTbqdkKj5It9ANHjXaqGwqEyj7j1cQrRzrbGVbib3qzvoFvGWoo5yzr3D8J8z/UXJ4sBkumcjrphFTDe9qQcJ5FI82ZZsChJssRcZl4ApFosoljixk0WkA=="},{title:"RSAES-OAEP Encryption Example 10.5",message:"p91sfcJLRvndXx6RraTDs9+UfodyMqk=",seed:"lbyp44WYlLPdhp+n7NW7xkAb8+SVvKnjhZiUs92Gn6c=",encrypted:"D7UPhV1nPwixcgg47HSlk/8yDLEDSXxoyo6H7MMopUTYwCmAjtnpWp4oWGg0sACoUlzKpR3PN21a4xru1txalcOkceylsQI9AIFvLhZyS20HbvQeExT9zQGyJaDhygC/6gPifgELk7x5QUqsd+TL/MQdgBZqbLO0skLOqNG3KrTMmN0oeWgxgjMmWnyBH0qkUpV5SlRN2P3nIHd/DZrkDn/qJG0MpXh6AeNHhvSgv8gyDG2Vzdf04OgvZLJTJaTdqHuXz93t7+PQ+QfKOG0wCEf5gOaYpkFarorv9XPLhzuOauN+Dd2IPzgKH5+wjbTZlZzEn+xRyDXK7s6GL/XOZw=="},{title:"RSAES-OAEP Encryption Example 10.6",message:"6vGnOhsMRglTfeac2SKLvPuajKjGw++vBW/kp/RjTtALfDnsaSLXuOosBOus",seed:"n0fd9C6X7qhWqb28cU6zrCL26zKfR930LpfuqFapvbw=",encrypted:"FO6Mv81w3SW/oVGIgdfAbIOW1eK8/UFdwryWg3ek0URFK09jNQtAaxT+66Yn5EJrTWh8fgRn1spnAOUsY5eq7iGpRsPGE86MLNonOvrBIht4Z+IDum55EgmwCrlfyiGe2fX4Xv1ifCQMSHd3OJTujAosVI3vPJaSsbTW6FqOFkM5m9uPqrdd+yhQ942wN4m4d4TG/YPx5gf62fbCRHOfvA5qSpO0XGQ45u+sWBAtOfzxmaYtf7WRAlu+JvIjTp8I2lAfVEuuW9+TJattx9RXN8jaWOBsceLIOfE6bkgad50UX5PyEtapnJOG1j0bh5PZ//oKtIASarB3PwdWM1EQTQ=="}],f(a,l,"sha256",p)}function u(e){var t=i.createBuffer(e),n=t.toHex();return new BigInteger(n,16)}function a(e){var t=i.decode64(e);return u(t)}function f(e,t,n,i){n==="sha1"?n=r.sha1.create():n==="sha256"&&(n=r.sha256.create());for(var s=0;s<i.length;++s){var o=i[s];it("should test "+o.title,function(){l(e,t,n,o.message,o.seed,o.encrypted)})}}function l(t,r,s,o,u,a){var o=i.decode64(o),u=i.decode64(u),f=n.encode_rsa_oaep(t,o,{seed:u,md:s}),l=t.encrypt(f,null);e.equal(a,i.encode64(l));var c=r.decrypt(l,null),h=n.decode_rsa_oaep(r,c,{md:s});e.equal(o,h),l=t.encrypt(o,"RSA-OAEP",{md:s}),h=r.decrypt(l,"RSA-OAEP",{md:s}),e.equal(o,h)}function c(e,n){return e=a(e),n=a(n),t.setRsaPublicKey(e,n)}function h(e,n,r,i,s,o,u,f){return e=a(e),n=a(n),r=a(r),i=a(i),s=a(s),o=a(o),u=a(u),f=a(f),t.setRsaPrivateKey(e,n,r,i,s,o,u,f)}function p(){var e,t,n,r,i,s,o,u,a,f;return e="qLOyhK+OtQs4cDSoYPFGxJGfMYdjzWxVmMiuSBGh4KvEx+CwgtaTpef87Wdc9GaFEncsDLxkp0LGxjD1M8jMcvYq6DPEC/JYQumEu3i9v5fAEH1VvbZi9cTg+rmEXLUUjvc5LdOq/5OuHmtme7PUJHYW1PW6ENTP0ibeiNOfFvs=",t="AQAB",n="UzOc/befyEZqZVxzFqyoXFX9j23YmP2vEZUX709S6P2OJY35P+4YD6DkqylpPNg7FSpVPUrE0YEri5+lrw5/Vf5zBN9BVwkm8zEfFcTWWnMsSDEW7j09LQrzVJrZv3y/t4rYhPhNW+sEck3HNpsx3vN9DPU56c/N095lNynq1dE=",r="0yc35yZ//hNBstXA0VCoG1hvsxMr7S+NUmKGSpy58wrzi+RIWY1BOhcu+4AsIazxwRxSDC8mpHHcrSEurHyjnQ==",i="zIhT0dVNpjD6wAT0cfKBx7iYLYIkpJDtvrM9Pj1cyTxHZXA9HdeRZC8fEWoN2FK+JBmyr3K/6aAw6GCwKItddw==",s="DhK/FxjpzvVZm6HDiC/oBGqQh07vzo8szCDk8nQfsKM6OEiuyckwX77L0tdoGZZ9RnGsxkMeQDeWjbN4eOaVwQ==",o="lSl7D5Wi+mfQBwfWCd/U/AXIna/C721upVvsdx6jM3NNklHnkILs2oZu/vE8RZ4aYxOGt+NUyJn18RLKhdcVgw==",u="T0VsUCSTvcDtKrdWo6btTWc1Kml9QhbpMhKxJ6Y9VBHOb6mNXb79cyY+NygUJ0OBgWbtfdY2h90qjKHS9PvY4Q==",a=c(e,t),f=h(e,t,n,r,i,s,o,u),{publicKey:a,privateKey:f}}it("should detect invalid RSAES-OAEP padding",function(){var t=p(),r=i.decode64("JRTfRpV1WmeyiOr0kFw27sZv0v0="),s=n.encode_rsa_oaep(t.publicKey,"datadatadatadata",{seed:r}),o=t.publicKey.encrypt(s,null),u=o.length*8;u/=8;for(var a=8;a<u;++a){var f=a/8,l=a%8,c=o.substring(0,f),h=1<<l;c+=String.fromCharCode(o.charCodeAt(f)^h),c+=o.substring(f+1);try{var d=t.privateKey.decrypt(c,null);throw n.decode_rsa_oaep(t.privateKey,d),{message:"Expected an exception."}}catch(v){e.equal(v.message,"Invalid RSAES-OAEP padding.")}}}),it("should detect leading zero bytes",function(){var t=p(),r=i.fillString("\0",80),s=n.encode_rsa_oaep(t.publicKey,r),o=t.publicKey.encrypt(s,null),u=t.privateKey.decrypt(o,null),a=n.decode_rsa_oaep(t.privateKey,u);e.equal(r,a)}),s(),o()})}typeof define=="function"?define("test/pkcs1",["forge/pki","forge/pkcs1","forge/md","forge/util"],function(t,n,r,i){e(ASSERT,t(),n(),r(),i())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/pki")(),require("../../js/pkcs1")(),require("../../js/md")(),require("../../js/util")())}(),function(){function e(e,t,n,r){var i={privateKey:"-----BEGIN RSA PRIVATE KEY-----\r\nMIICXQIBAAKBgQDL0EugUiNGMWscLAVM0VoMdhDZEJOqdsUMpx9U0YZI7szokJqQ\r\nNIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMGTkP3VF29vXBo+dLq5e+8VyAy\r\nQ3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGtvnM+z0MYDdKo80efzwIDAQAB\r\nAoGAIzkGONi5G+JifmXlLJdplom486p3upf4Ce2/7mqfaG9MnkyPSairKD/JXvfh\r\nNNWkkN8DKKDKBcVVElPgORYT0qwrWc7ueLBMUCbRXb1ZyfEulimG0R3kjUh7NYau\r\nDaIkVgfykXGSQMZx8FoaT6L080zd+0emKDDYRrb+/kgJNJECQQDoUZoiC2K/DWNY\r\nh3/ppZ0ane2y4SBmJUHJVMPQ2CEgxsrJTxet668ckNCKaOP/3VFPoWC41f17DvKq\r\nnoYINNntAkEA4JbZBZBVUrQFhHlrpXT4jzqtO2RlKZzEq8qmFZfEErxOT1WMyyCi\r\nlAQ5gUKardo1Kf0omC8Xq/uO9ZYdED55KwJBALs6cJ65UFaq4oLJiQPzLd7yokuE\r\ndcj8g71PLBTW6jPxIiMFNA89nz3FU9wIVp+xbMNhSoMMKqIPVPC+m0Rn260CQQDA\r\nI83fWK/mZWUjBM33a68KumRiH238v8XyQxj7+C8i6D8G2GXvkigFAehAkb7LZZd+\r\nKLuGFyPlWv3fVWHf99KpAkBQFKk3MRMl6IGJZUEFQe4l5whm8LkGU4acSqv9B3xt\r\nqROkCrsFrMPqjuuzEmyHoQZ64r2PLJg7FOuyhBnQUOt4\r\n-----END RSA PRIVATE KEY-----\r\n",publicKey:"-----BEGIN PUBLIC KEY-----\r\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDL0EugUiNGMWscLAVM0VoMdhDZ\r\nEJOqdsUMpx9U0YZI7szokJqQNIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMG\r\nTkP3VF29vXBo+dLq5e+8VyAyQ3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGt\r\nvnM+z0MYDdKo80efzwIDAQAB\r\n-----END PUBLIC KEY-----\r\n",certificate:"-----BEGIN CERTIFICATE-----\r\nMIIDIjCCAougAwIBAgIJANE2aHSbwpaRMA0GCSqGSIb3DQEBBQUAMGoxCzAJBgNV\r\nBAYTAlVTMREwDwYDVQQIEwhWaXJnaW5pYTETMBEGA1UEBxMKQmxhY2tzYnVyZzEN\r\nMAsGA1UEChMEVGVzdDENMAsGA1UECxMEVGVzdDEVMBMGA1UEAxMMbXlzZXJ2ZXIu\r\nY29tMB4XDTEwMDYxOTE3MzYyOFoXDTExMDYxOTE3MzYyOFowajELMAkGA1UEBhMC\r\nVVMxETAPBgNVBAgTCFZpcmdpbmlhMRMwEQYDVQQHEwpCbGFja3NidXJnMQ0wCwYD\r\nVQQKEwRUZXN0MQ0wCwYDVQQLEwRUZXN0MRUwEwYDVQQDEwxteXNlcnZlci5jb20w\r\ngZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMvQS6BSI0YxaxwsBUzRWgx2ENkQ\r\nk6p2xQynH1TRhkjuzOiQmpA0jCiSJDoSic2dZIyUi/LjoCGeVFif57N5N5Tt4wZO\r\nQ/dUXb29cGj50url77xXIDJDcXMzXAji2ziFEAIXzDqarKBdDuL9IO7z+tepEa2+\r\ncz7PQxgN0qjzR5/PAgMBAAGjgc8wgcwwHQYDVR0OBBYEFPV1Y+DHXW6bA/r9sv1y\r\nNJ8jAwMAMIGcBgNVHSMEgZQwgZGAFPV1Y+DHXW6bA/r9sv1yNJ8jAwMAoW6kbDBq\r\nMQswCQYDVQQGEwJVUzERMA8GA1UECBMIVmlyZ2luaWExEzARBgNVBAcTCkJsYWNr\r\nc2J1cmcxDTALBgNVBAoTBFRlc3QxDTALBgNVBAsTBFRlc3QxFTATBgNVBAMTDG15\r\nc2VydmVyLmNvbYIJANE2aHSbwpaRMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEF\r\nBQADgYEARdH2KOlJWTC1CS2y/PAvg4uiM31PXMC1hqSdJlnLM1MY4hRfuf9VyTeX\r\nY6FdybcyDLSxKn9id+g9229ci9/s9PI+QmD5vXd8yZyScLc2JkYB4GC6+9D1+/+x\r\ns2hzMxuK6kzZlP+0l9LGcraMQPGRydjCARZZm4Uegln9rh85XFQ=\r\n-----END CERTIFICATE-----\r\n"};describe("x509",function(){it("should convert certificate to/from PEM",function(){var n=t.certificateFromPem(i.certificate);e.equal(t.certificateToPem(n),i.certificate)}),it("should verify self-signed certificate",function(){var n=t.certificateFromPem(i.certificate);e.ok(n.verify(n))}),it("should generate a self-signed certificate",function(){var n={privateKey:t.privateKeyFromPem(i.privateKey),publicKey:t.publicKeyFromPem(i.publicKey)},r=t.createCertificate();r.publicKey=n.publicKey,r.serialNumber="01",r.validity.notBefore=new Date,r.validity.notAfter=new Date,r.validity.notAfter.setFullYear(r.validity.notBefore.getFullYear()+1);var s=[{name:"commonName",value:"example.org"},{name:"countryName",value:"US"},{shortName:"ST",value:"Virginia"},{name:"localityName",value:"Blacksburg"},{name:"organizationName",value:"Test"},{shortName:"OU",value:"Test"}];r.setSubject(s),r.setIssuer(s),r.setExtensions([{name:"basicConstraints",cA:!0},{name:"keyUsage",keyCertSign:!0,digitalSignature:!0,nonRepudiation:!0,keyEncipherment:!0,dataEncipherment:!0},{name:"extKeyUsage",serverAuth:!0,clientAuth:!0,codeSigning:!0,emailProtection:!0,timeStamping:!0},{name:"nsCertType",client:!0,server:!0,email:!0,objsign:!0,sslCA:!0,emailCA:!0,objCA:!0},{name:"subjectAltName",altNames:[{type:6,value:"http://example.org/webid#me"}]},{name:"subjectKeyIdentifier"}]),r.sign(n.privateKey);var o=t.certificateToPem(r);r=t.certificateFromPem(o);var u=t.createCaStore();u.addCertificate(r),t.verifyCertificateChain(u,[r],function(t,n,i){return e.equal(t,!0),e.ok(r.verifySubjectKeyIdentifier()),!0})}),it("should verify certificate with sha1WithRSAEncryption signature",function(){var n="-----BEGIN CERTIFICATE-----\r\nMIIDZDCCAs2gAwIBAgIKQ8fjjgAAAABh3jANBgkqhkiG9w0BAQUFADBGMQswCQYD\r\nVQQGEwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzEiMCAGA1UEAxMZR29vZ2xlIElu\r\ndGVybmV0IEF1dGhvcml0eTAeFw0xMjA2MjcxMzU5MTZaFw0xMzA2MDcxOTQzMjda\r\nMGcxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1N\r\nb3VudGFpbiBWaWV3MRMwEQYDVQQKEwpHb29nbGUgSW5jMRYwFAYDVQQDEw13d3cu\r\nZ29vZ2xlLmRlMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCw2Hw3vNy5QMSd\r\n0/iMCS8lwZk9lnEk2NmrJt6vGJfRGlBprtHp5lpMFMoi+x8m8EwGVxXHGp7hLyN/\r\ngXuUjL7/DY9fxxx9l77D+sDZz7jfUfWmhS03Ra1FbT6myF8miVZFChJ8XgWzioJY\r\ngyNdRUC9149yrXdPWrSmSVaT0+tUCwIDAQABo4IBNjCCATIwHQYDVR0lBBYwFAYI\r\nKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBTiQGhrO3785rMPIKZ/zQEl5RyS\r\n0TAfBgNVHSMEGDAWgBS/wDDr9UMRPme6npH7/Gra42sSJDBbBgNVHR8EVDBSMFCg\r\nTqBMhkpodHRwOi8vd3d3LmdzdGF0aWMuY29tL0dvb2dsZUludGVybmV0QXV0aG9y\r\naXR5L0dvb2dsZUludGVybmV0QXV0aG9yaXR5LmNybDBmBggrBgEFBQcBAQRaMFgw\r\nVgYIKwYBBQUHMAKGSmh0dHA6Ly93d3cuZ3N0YXRpYy5jb20vR29vZ2xlSW50ZXJu\r\nZXRBdXRob3JpdHkvR29vZ2xlSW50ZXJuZXRBdXRob3JpdHkuY3J0MAwGA1UdEwEB\r\n/wQCMAAwDQYJKoZIhvcNAQEFBQADgYEAVJ0qt/MBvHEPuWHeH51756qy+lBNygLA\r\nXp5Gq+xHUTOzRty61BR05zv142hYAGWvpvnEOJ/DI7V3QlXK8a6dQ+du97obQJJx\r\n7ekqtfxVzmlSb23halYSoXmWgP8Tq0VUDsgsSLE7fS8JuO1soXUVKj1/6w189HL6\r\nLsngXwZSuL0=\r\n-----END CERTIFICATE-----\r\n",r="-----BEGIN CERTIFICATE-----\r\nMIICsDCCAhmgAwIBAgIDC2dxMA0GCSqGSIb3DQEBBQUAME4xCzAJBgNVBAYTAlVT\r\nMRAwDgYDVQQKEwdFcXVpZmF4MS0wKwYDVQQLEyRFcXVpZmF4IFNlY3VyZSBDZXJ0\r\naWZpY2F0ZSBBdXRob3JpdHkwHhcNMDkwNjA4MjA0MzI3WhcNMTMwNjA3MTk0MzI3\r\nWjBGMQswCQYDVQQGEwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzEiMCAGA1UEAxMZ\r\nR29vZ2xlIEludGVybmV0IEF1dGhvcml0eTCBnzANBgkqhkiG9w0BAQEFAAOBjQAw\r\ngYkCgYEAye23pIucV+eEPkB9hPSP0XFjU5nneXQUr0SZMyCSjXvlKAy6rWxJfoNf\r\nNFlOCnowzdDXxFdF7dWq1nMmzq0yE7jXDx07393cCDaob1FEm8rWIFJztyaHNWrb\r\nqeXUWaUr/GcZOfqTGBhs3t0lig4zFEfC7wFQeeT9adGnwKziV28CAwEAAaOBozCB\r\noDAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFL/AMOv1QxE+Z7qekfv8atrjaxIk\r\nMB8GA1UdIwQYMBaAFEjmaPkr0rKV10fYIyAQTzOYkJ/UMBIGA1UdEwEB/wQIMAYB\r\nAf8CAQAwOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2NybC5nZW90cnVzdC5jb20v\r\nY3Jscy9zZWN1cmVjYS5jcmwwDQYJKoZIhvcNAQEFBQADgYEAuIojxkiWsRF8YHde\r\nBZqrocb6ghwYB8TrgbCoZutJqOkM0ymt9e8kTP3kS8p/XmOrmSfLnzYhLLkQYGfN\r\n0rTw8Ktx5YtaiScRhKqOv5nwnQkhClIZmloJ0pC3+gz4fniisIWvXEyZ2VxVKfml\r\nUUIuOss4jHg7y/j7lYe8vJD5UDI=\r\n-----END CERTIFICATE-----\r\n",i=t.certificateFromPem(n,!0),s=t.certificateFromPem(r);e.ok(s.verify(i))}),it("should verify certificate with sha256WithRSAEncryption signature",function(){var n="-----BEGIN CERTIFICATE-----\r\nMIIDuzCCAqOgAwIBAgIEO5vZjDANBgkqhkiG9w0BAQsFADBGMQswCQYDVQQGEwJE\r\nRTEPMA0GA1UEChMGRWxzdGVyMQswCQYDVQQLEwJDQTEZMBcGA1UEAxMQRWxzdGVy\r\nU29mdFRlc3RDQTAeFw0xMDA5MTUwNTM4MjRaFw0xMzA5MTUwNTM4MjRaMCsxFDAS\r\nBgNVBAUTCzEwMDIzMTQ5OTRDMRMwEQYDVQQDEwoxMDAyMzE0OTk0MIGfMA0GCSqG\r\nSIb3DQEBAQUAA4GNADCBiQKBgQCLPqjbwjsugzw6+qwwm/pdzDwk7ASIsBYJ17GT\r\nqyT0zCnYmdDDGWsYc+xxFVVIi8xBt6Mlq8Rwj+02UJhY9qm6zRA9MqFZC3ih+HoW\r\nxq7H8N2d10N0rX6h5PSjkF5fU5ugncZmppsRGJ9DNXgwjpf/CsH2rqThUzK4xfrq\r\njpDS/wIDAQABo4IBTjCCAUowDgYDVR0PAQH/BAQDAgUgMAwGA1UdEwEB/wQCMAAw\r\nHQYDVR0OBBYEFF1h7H37OQivS57GD8+nK6VsgMPTMIGXBgNVHR8EgY8wgYwwgYmg\r\ngYaggYOGgYBsZGFwOi8vMTkyLjE2OC42LjI0OjM4OS9sJTNkQ0ElMjBaZXJ0aWZp\r\na2F0ZSxvdSUzZENBLGNuJTNkRWxzdGVyU29mdFRlc3RDQSxkYyUzZHdpZXNlbCxk\r\nYyUzZGVsc3RlcixkYyUzZGRlPz9iYXNlPyhvYmplY3RDbGFzcz0qKTBxBgNVHSME\r\najBogBRBILMYmlZu//pj3wjDe2UPkq7jk6FKpEgwRjELMAkGA1UEBhMCREUxDzAN\r\nBgNVBAoTBkVsc3RlcjEPMA0GA1UECxMGUm9vdENBMRUwEwYDVQQDEwxFbHN0ZXJS\r\nb290Q0GCBDuayikwDQYJKoZIhvcNAQELBQADggEBAK8Z1+/VNyU5w/EiyhFH5aRE\r\nMzxo0DahqKEm4pW5haBgKubJwZGs+CrBZR70TPbZGgJd36eyMgeXb/06lBnxewii\r\nI/aY6wMTviQTpqFnz5m0Le8UhH+hY1bqNG/vf6J+1gbOSrZyhAUV+MDJbL/OkzX4\r\nvoVAfUBqSODod0f5wCW2RhvBmB9E62baP6qizdxyPA4iV16H4C0etd/7coLX6NZC\r\noz3Yu0IRTQCH+YrpfIbxGb0grNhtCTfFpa287fuzu8mIEvLNr8GibhBXmQg7iJ+y\r\nq0VIyZLY8k6jEPrUB5Iv5ouSR19Dda/2+xJPlT/bosuNcErEuk/yKAHWAzwm1wQ=\r\n-----END CERTIFICATE-----\r\n",r="-----BEGIN CERTIFICATE-----\r\nMIIESjCCAzKgAwIBAgIEO5rKKTANBgkqhkiG9w0BAQsFADBGMQswCQYDVQQGEwJE\r\nRTEPMA0GA1UEChMGRWxzdGVyMQ8wDQYDVQQLEwZSb290Q0ExFTATBgNVBAMTDEVs\r\nc3RlclJvb3RDQTAeFw0wOTA3MjgwODE5MTFaFw0xNDA3MjgwODE5MTFaMEYxCzAJ\r\nBgNVBAYTAkRFMQ8wDQYDVQQKEwZFbHN0ZXIxCzAJBgNVBAsTAkNBMRkwFwYDVQQD\r\nExBFbHN0ZXJTb2Z0VGVzdENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC\r\nAQEAv5uoKLnxXQe75iqwqgyt3H6MDAx/wvUVs26+2+yHpEUb/2gA3L8E+NChSb9E\r\naNgxxoac3Yhvxzq2mPpih3vkY7Xw512Tm8l/OPbT8kbmBJmYZneFALXHytAIZiEf\r\ne0ZYNKAlClFIgNP5bE9UjTqVEEoSiUhpTubM6c5xEYVznnwPBoYQ0ari7RTDYnME\r\nHK4vMfoeBeWHYPiEygNHnGUG8d3merRC/lQASUtL6ikmLWKCKHfyit5ACzPNKAtw\r\nIzHAzD5ek0BpcUTci8hUsKz2ZvmoZcjPyj63veQuMYS5cTMgr3bfz9uz1xuoEDsb\r\nSv9rQX9Iw3N7yMpxTDJTqGKhYwIDAQABo4IBPjCCATowDgYDVR0PAQH/BAQDAgEG\r\nMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFEEgsxiaVm7/+mPfCMN7ZQ+S\r\nruOTMIGXBgNVHR8EgY8wgYwwgYmggYaggYOGgYBsZGFwOi8vMTkyLjE2OC42LjI0\r\nOjM4OS9sJTNkQ0ElMjBaZXJ0aWZpa2F0ZSxvdSUzZFJvb3RDQSxjbiUzZEVsc3Rl\r\nclJvb3RDQSxkYyUzZHdpZXNlbCxkYyUzZGVsc3RlcixkYyUzZGRlPz9iYXNlPyhv\r\nYmplY3RDbGFzcz0qKTBbBgNVHSMEVDBSoUqkSDBGMQswCQYDVQQGEwJERTEPMA0G\r\nA1UEChMGRWxzdGVyMQ8wDQYDVQQLEwZSb290Q0ExFTATBgNVBAMTDEVsc3RlclJv\r\nb3RDQYIEO5rKADANBgkqhkiG9w0BAQsFAAOCAQEAFauDnfHSbgRmbFkpQUXM5wKi\r\nK5STAaVps201iAjacX5EsOs5L37VUMoT9G2DAE8Z6B1pIiR3zcd3UpiHnFlUTC0f\r\nZdOCXzDkOfziKY/RzuUsLNFUhBizCIA0+XcKgm3dSA5ex8fChLJddSYheSLuPua7\r\niNMuzaU2YnevbMwpdEsl55Qr/uzcc0YM/mCuM4vsNFyFml91SQyPPmdR3VvGOoGl\r\nqS1R0HSoPJUvr0N0kARwD7kO3ikcJ6FxBCsgVLZHGJC+q8PQNZmVMbfgjH4nAyP8\r\nu7Qe03v2WLW0UgKu2g0UcQXWXbovktpZoK0fUOwv3bqsZ0K1IjVvMKG8OysUvA==\r\n-----END CERTIFICATE-----\r\n",i=t.certificateFromPem(n,!0),s=t.certificateFromPem(r);e.ok(s.verify(i))}),it("should import certificate with sha256 RSASSA-PSS signature",function(){var n="-----BEGIN CERTIFICATE-----\r\nMIIERzCCAvugAwIBAgIEO50CcjBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQC\r\nAQUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCASAwRjELMAkGA1UE\r\nBhMCREUxDzANBgNVBAoTBkVsc3RlcjELMAkGA1UECxMCQ0ExGTAXBgNVBAMTEEVs\r\nc3RlclNvZnRUZXN0Q0EwHhcNMTEwNzI4MTIxMzU3WhcNMTQwNzI4MTIxMzU3WjAr\r\nMRQwEgYDVQQFEwsxMDAyNzUzMzI1QzETMBEGA1UEAxMKMTAwMjc1MzMyNTCCASIw\r\nDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALHCogo7LVUkxWsMIc/0jgH2PCLt\r\nukbATPehxWBG1XUPrz53lWgFJzlZaKLlLVVnXrfULaifuOKlZP6SM1JQlL1JuYgY\r\nAdgZyHjderNIk5NsSTmefwonSn/ukri5IRTH420oHtSjxk6+/DXlWnQy5OzTN6Bq\r\njVJo8L+TTmf2jWuEam5cWa+YVP2k3tIqX5yMUDFjKO4znHdtIkHnBE0Kx03rWQRB\r\nTSYWDgDm2gttdOs9JVeuW0nnwQj27uo9gOR0iyaUjVrKLZ95p6zpXhM4uMSVRNeo\r\nLqkdqP2n+4pDXZVqLNgjkHQUS/xq9Q/kYgT2J7wkGfYxP9to7TG7vra1eOECAwEA\r\nAaOB7zCB7DAOBgNVHQ8BAf8EBAMCBSAwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU\r\nNDJ2BZIk6ukLqkdmttH12bu2leswOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2Ny\r\nbC5lbHN0ZXIuZGUvRWxzdGVyU29mdFRlc3RDQS5jcmwwcQYDVR0jBGowaIAU1R9A\r\nHmpdzaxK3v+ihQsEpAFgzOKhSqRIMEYxCzAJBgNVBAYTAkRFMQ8wDQYDVQQKEwZF\r\nbHN0ZXIxDzANBgNVBAsTBlJvb3RDQTEVMBMGA1UEAxMMRWxzdGVyUm9vdENBggQ7\r\nmsqPMEEGCSqGSIb3DQEBCjA0oA8wDQYJYIZIAWUDBAIBBQChHDAaBgkqhkiG9w0B\r\nAQgwDQYJYIZIAWUDBAIBBQCiAwIBIAOCAQEAJBYNRZpe+z3yPPLV539Yci6OfjVg\r\nvs1e3rvSvcpFaqRJ8vZ8WNx3uuRQZ6B4Z3YEc00UJAOl3wU6KhamyryK2YvCrPg+\r\nTS5QDXNaO2z/rAnY1wWSlwBPlhqpMRrNv9cRXBcgK5YxprjytCVYN0UHdintgYxG\r\nfg7QYiFb00UXxAza1AFrpG+RqySFfO1scmu4kgpeb6A3USnQ0r6rZz6dt6NqgZZ6\r\noUpDOCvnS9XSOWuvJirV8hIU0KAagguTbwfTqt9nt0wDlwZpemsJZ4Vvnvy8d9Jf\r\nzA68EWHbZLr2QP9hb3sHCWJgplMsTJnUwRfi2hf5KNtP8Xg5DSLMfTEbhw==\r\n-----END CERTIFICATE-----\r\n",r=t.certificateFromPem(n,!0);e.equal(r.signatureOid,t.oids["RSASSA-PSS"]),e.equal(r.signatureParameters.hash.algorithmOid,t.oids.sha256),e.equal(r.signatureParameters.mgf.algorithmOid,t.oids.mgf1),e.equal(r.signatureParameters.mgf.hash.algorithmOid,t.oids.sha256),e.equal(r.siginfo.algorithmOid,t.oids["RSASSA-PSS"]),e.equal(r.siginfo.parameters.hash.algorithmOid,t.oids.sha256),e.equal(r.siginfo.parameters.mgf.algorithmOid,t.oids.mgf1),e.equal(r.siginfo.parameters.mgf.hash.algorithmOid,t.oids.sha256)}),it("should export certificate with sha256 RSASSA-PSS signature",function(){var n="-----BEGIN CERTIFICATE-----\r\nMIIERzCCAvugAwIBAgIEO50CcjBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQC\r\nAQUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCASAwRjELMAkGA1UE\r\nBhMCREUxDzANBgNVBAoTBkVsc3RlcjELMAkGA1UECxMCQ0ExGTAXBgNVBAMTEEVs\r\nc3RlclNvZnRUZXN0Q0EwHhcNMTEwNzI4MTIxMzU3WhcNMTQwNzI4MTIxMzU3WjAr\r\nMRQwEgYDVQQFEwsxMDAyNzUzMzI1QzETMBEGA1UEAxMKMTAwMjc1MzMyNTCCASIw\r\nDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALHCogo7LVUkxWsMIc/0jgH2PCLt\r\nukbATPehxWBG1XUPrz53lWgFJzlZaKLlLVVnXrfULaifuOKlZP6SM1JQlL1JuYgY\r\nAdgZyHjderNIk5NsSTmefwonSn/ukri5IRTH420oHtSjxk6+/DXlWnQy5OzTN6Bq\r\njVJo8L+TTmf2jWuEam5cWa+YVP2k3tIqX5yMUDFjKO4znHdtIkHnBE0Kx03rWQRB\r\nTSYWDgDm2gttdOs9JVeuW0nnwQj27uo9gOR0iyaUjVrKLZ95p6zpXhM4uMSVRNeo\r\nLqkdqP2n+4pDXZVqLNgjkHQUS/xq9Q/kYgT2J7wkGfYxP9to7TG7vra1eOECAwEA\r\nAaOB7zCB7DAOBgNVHQ8BAf8EBAMCBSAwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU\r\nNDJ2BZIk6ukLqkdmttH12bu2leswOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2Ny\r\nbC5lbHN0ZXIuZGUvRWxzdGVyU29mdFRlc3RDQS5jcmwwcQYDVR0jBGowaIAU1R9A\r\nHmpdzaxK3v+ihQsEpAFgzOKhSqRIMEYxCzAJBgNVBAYTAkRFMQ8wDQYDVQQKEwZF\r\nbHN0ZXIxDzANBgNVBAsTBlJvb3RDQTEVMBMGA1UEAxMMRWxzdGVyUm9vdENBggQ7\r\nmsqPMEEGCSqGSIb3DQEBCjA0oA8wDQYJYIZIAWUDBAIBBQChHDAaBgkqhkiG9w0B\r\nAQgwDQYJYIZIAWUDBAIBBQCiAwIBIAOCAQEAJBYNRZpe+z3yPPLV539Yci6OfjVg\r\nvs1e3rvSvcpFaqRJ8vZ8WNx3uuRQZ6B4Z3YEc00UJAOl3wU6KhamyryK2YvCrPg+\r\nTS5QDXNaO2z/rAnY1wWSlwBPlhqpMRrNv9cRXBcgK5YxprjytCVYN0UHdintgYxG\r\nfg7QYiFb00UXxAza1AFrpG+RqySFfO1scmu4kgpeb6A3USnQ0r6rZz6dt6NqgZZ6\r\noUpDOCvnS9XSOWuvJirV8hIU0KAagguTbwfTqt9nt0wDlwZpemsJZ4Vvnvy8d9Jf\r\nzA68EWHbZLr2QP9hb3sHCWJgplMsTJnUwRfi2hf5KNtP8Xg5DSLMfTEbhw==\r\n-----END CERTIFICATE-----\r\n",r=t.certificateFromPem(n,!0);e.equal(t.certificateToPem(r),n)}),it("should verify certificate with sha256 RSASSA-PSS signature",function(){var n="-----BEGIN CERTIFICATE-----\r\nMIIERzCCAvugAwIBAgIEO50CcjBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQC\r\nAQUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCASAwRjELMAkGA1UE\r\nBhMCREUxDzANBgNVBAoTBkVsc3RlcjELMAkGA1UECxMCQ0ExGTAXBgNVBAMTEEVs\r\nc3RlclNvZnRUZXN0Q0EwHhcNMTEwNzI4MTIxMzU3WhcNMTQwNzI4MTIxMzU3WjAr\r\nMRQwEgYDVQQFEwsxMDAyNzUzMzI1QzETMBEGA1UEAxMKMTAwMjc1MzMyNTCCASIw\r\nDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALHCogo7LVUkxWsMIc/0jgH2PCLt\r\nukbATPehxWBG1XUPrz53lWgFJzlZaKLlLVVnXrfULaifuOKlZP6SM1JQlL1JuYgY\r\nAdgZyHjderNIk5NsSTmefwonSn/ukri5IRTH420oHtSjxk6+/DXlWnQy5OzTN6Bq\r\njVJo8L+TTmf2jWuEam5cWa+YVP2k3tIqX5yMUDFjKO4znHdtIkHnBE0Kx03rWQRB\r\nTSYWDgDm2gttdOs9JVeuW0nnwQj27uo9gOR0iyaUjVrKLZ95p6zpXhM4uMSVRNeo\r\nLqkdqP2n+4pDXZVqLNgjkHQUS/xq9Q/kYgT2J7wkGfYxP9to7TG7vra1eOECAwEA\r\nAaOB7zCB7DAOBgNVHQ8BAf8EBAMCBSAwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU\r\nNDJ2BZIk6ukLqkdmttH12bu2leswOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2Ny\r\nbC5lbHN0ZXIuZGUvRWxzdGVyU29mdFRlc3RDQS5jcmwwcQYDVR0jBGowaIAU1R9A\r\nHmpdzaxK3v+ihQsEpAFgzOKhSqRIMEYxCzAJBgNVBAYTAkRFMQ8wDQYDVQQKEwZF\r\nbHN0ZXIxDzANBgNVBAsTBlJvb3RDQTEVMBMGA1UEAxMMRWxzdGVyUm9vdENBggQ7\r\nmsqPMEEGCSqGSIb3DQEBCjA0oA8wDQYJYIZIAWUDBAIBBQChHDAaBgkqhkiG9w0B\r\nAQgwDQYJYIZIAWUDBAIBBQCiAwIBIAOCAQEAJBYNRZpe+z3yPPLV539Yci6OfjVg\r\nvs1e3rvSvcpFaqRJ8vZ8WNx3uuRQZ6B4Z3YEc00UJAOl3wU6KhamyryK2YvCrPg+\r\nTS5QDXNaO2z/rAnY1wWSlwBPlhqpMRrNv9cRXBcgK5YxprjytCVYN0UHdintgYxG\r\nfg7QYiFb00UXxAza1AFrpG+RqySFfO1scmu4kgpeb6A3USnQ0r6rZz6dt6NqgZZ6\r\noUpDOCvnS9XSOWuvJirV8hIU0KAagguTbwfTqt9nt0wDlwZpemsJZ4Vvnvy8d9Jf\r\nzA68EWHbZLr2QP9hb3sHCWJgplMsTJnUwRfi2hf5KNtP8Xg5DSLMfTEbhw==\r\n-----END CERTIFICATE-----\r\n",r="-----BEGIN CERTIFICATE-----\r\nMIIEZDCCAxigAwIBAgIEO5rKjzBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQC\r\nAQUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCASAwRjELMAkGA1UE\r\nBhMCREUxDzANBgNVBAoTBkVsc3RlcjEPMA0GA1UECxMGUm9vdENBMRUwEwYDVQQD\r\nEwxFbHN0ZXJSb290Q0EwHhcNMTEwNzI4MTExNzI4WhcNMTYwNzI4MTExNzI4WjBG\r\nMQswCQYDVQQGEwJERTEPMA0GA1UEChMGRWxzdGVyMQswCQYDVQQLEwJDQTEZMBcG\r\nA1UEAxMQRWxzdGVyU29mdFRlc3RDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\r\nAQoCggEBAMFpz3sXnXq4ZUBdYdpG5DJVfITLXYwXPfEJzr1pLRfJ2eMPn7k3B/2g\r\nbvuH30zKmDRjlfV51sFw4l1l+cQugzy5FEOrfE6g7IvhpBUf9SQujVWtE3BoSRR5\r\npSGbIWC7sm2SG0drpoCRpL0xmWZlAUS5mz7hBecJC/kKkKeOxUg5h492XQgWd0ow\r\n6GlyQBxJCmxgQBMnLS0stecs234hR5gvTHic50Ey+gRMPsTyco2Fm0FqvXtBuOsj\r\nzAW7Nk2hnM6awFHVMDBLm+ClE1ww0dLW0ujkdoGsTEbvmM/w8MBI6WAiWaanjK/x\r\nlStmQeUVXKd+AfuzT/FIPrwANcC1qGUCAwEAAaOB8TCB7jAOBgNVHQ8BAf8EBAMC\r\nAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU1R9AHmpdzaxK3v+ihQsE\r\npAFgzOIwNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5lbHN0ZXIuZGUvRWxz\r\ndGVyUm9vdENBLmNybDBxBgNVHSMEajBogBS3zfTokckL2H/fTojW02K+metEcaFK\r\npEgwRjELMAkGA1UEBhMCREUxDzANBgNVBAoTBkVsc3RlcjEPMA0GA1UECxMGUm9v\r\ndENBMRUwEwYDVQQDEwxFbHN0ZXJSb290Q0GCBDuaylowQQYJKoZIhvcNAQEKMDSg\r\nDzANBglghkgBZQMEAgEFAKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgEFAKID\r\nAgEgA4IBAQBjT107fBmSfQNUYCpXIlaS/pogPoCahuC1g8QDtq6IEVXoEgXCzfVN\r\nJYHyYOQOraP4O2LEcXpo/oc0MMpVF06QLKG/KieUE0mrZnsCJ3GLSJkM8tI8bRHj\r\n8tAhlViMacUqnKKufCs/rIN25JB57+sCyFqjtRbSt88e+xqFJ5I79Btp/bNtoj2G\r\nOJGl997EPua9/yJuvdA9W67WO/KwEh+FqylB1pAapccOHqttwu4QJIc/XJfG5rrf\r\n8QZz8HIuOcroA4zIrprQ1nJRCuMII04ueDtBVz1eIEmqNEUkr09JdK8M0LKH0lMK\r\nYsgjai/P2mPVVwhVw6dHzUVRLXh3xIQr\r\n-----END CERTIFICATE-----\r\n",i=t.certificateFromPem(n,!0),s=t.certificateFromPem(r);e.ok(s.verify(i))})})}typeof define=="function"?define("test/x509",["forge/pki","forge/md","forge/util"],function(t,n,r){e(ASSERT,t(),n(),r())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/pki")(),require("../../js/md")(),require("../../js/util")())}(),function(){function e(e,t){var n={privateKey:"-----BEGIN RSA PRIVATE KEY-----\r\nMIICXQIBAAKBgQDL0EugUiNGMWscLAVM0VoMdhDZEJOqdsUMpx9U0YZI7szokJqQ\r\nNIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMGTkP3VF29vXBo+dLq5e+8VyAy\r\nQ3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGtvnM+z0MYDdKo80efzwIDAQAB\r\nAoGAIzkGONi5G+JifmXlLJdplom486p3upf4Ce2/7mqfaG9MnkyPSairKD/JXvfh\r\nNNWkkN8DKKDKBcVVElPgORYT0qwrWc7ueLBMUCbRXb1ZyfEulimG0R3kjUh7NYau\r\nDaIkVgfykXGSQMZx8FoaT6L080zd+0emKDDYRrb+/kgJNJECQQDoUZoiC2K/DWNY\r\nh3/ppZ0ane2y4SBmJUHJVMPQ2CEgxsrJTxet668ckNCKaOP/3VFPoWC41f17DvKq\r\nnoYINNntAkEA4JbZBZBVUrQFhHlrpXT4jzqtO2RlKZzEq8qmFZfEErxOT1WMyyCi\r\nlAQ5gUKardo1Kf0omC8Xq/uO9ZYdED55KwJBALs6cJ65UFaq4oLJiQPzLd7yokuE\r\ndcj8g71PLBTW6jPxIiMFNA89nz3FU9wIVp+xbMNhSoMMKqIPVPC+m0Rn260CQQDA\r\nI83fWK/mZWUjBM33a68KumRiH238v8XyQxj7+C8i6D8G2GXvkigFAehAkb7LZZd+\r\nKLuGFyPlWv3fVWHf99KpAkBQFKk3MRMl6IGJZUEFQe4l5whm8LkGU4acSqv9B3xt\r\nqROkCrsFrMPqjuuzEmyHoQZ64r2PLJg7FOuyhBnQUOt4\r\n-----END RSA PRIVATE KEY-----\r\n",publicKey:"-----BEGIN PUBLIC KEY-----\r\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDL0EugUiNGMWscLAVM0VoMdhDZ\r\nEJOqdsUMpx9U0YZI7szokJqQNIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMG\r\nTkP3VF29vXBo+dLq5e+8VyAyQ3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGt\r\nvnM+z0MYDdKo80efzwIDAQAB\r\n-----END PUBLIC KEY-----\r\n"};describe("csr",function(){it("should generate a certification request",function(){var r={privateKey:t.privateKeyFromPem(n.privateKey),publicKey:t.publicKeyFromPem(n.publicKey)},i=t.createCertificationRequest();i.publicKey=r.publicKey,i.setSubject([{name:"commonName",value:"example.org"},{name:"countryName",value:"US"},{shortName:"ST",value:"Virginia"},{name:"localityName",value:"Blacksburg"},{name:"organizationName",value:"Test"},{shortName:"OU",value:"Test"}]),i.setAttributes([{name:"challengePassword",value:"password"},{name:"unstructuredName",value:"My company"}]),i.sign(r.privateKey);var s=t.certificationRequestToPem(i);i=t.certificationRequestFromPem(s),e.ok(i.verify())})})}typeof define=="function"?define("test/csr",["forge/pki"],function(t){e(ASSERT,t())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/pki")())}(),function(){function e(e,t,n){describe("aes",function(){it("should encrypt a single block with a 128-bit key",function(){var r=[66051,67438087,134810123,202182159],i=[1122867,1146447479,2291772091,3437096703],s=[],o=t._expandKey(r,!1);t._updateBlock(o,i,s,!1);var u=n.createBuffer();u.putInt32(s[0]),u.putInt32(s[1]),u.putInt32(s[2]),u.putInt32(s[3]),e.equal(u.toHex(),"69c4e0d86a7b0430d8cdb78070b4c55a")}),it("should decrypt a single block with a 128-bit key",function(){var r=[66051,67438087,134810123,202182159],i=[1774510296,1786446896,3637360512,1890895194],s=[],o=t._expandKey(r,!0);t._updateBlock(o,i,s,!0);var u=n.createBuffer();u.putInt32(s[0]),u.putInt32(s[1]),u.putInt32(s[2]),u.putInt32(s[3]),e.equal(u.toHex(),"00112233445566778899aabbccddeeff")}),it("should encrypt a single block with a 192-bit key",function(){var r=[66051,67438087,134810123,202182159,269554195,336926231],i=[1122867,1146447479,2291772091,3437096703],s=[],o=t._expandKey(r,!1);t._updateBlock(o,i,s,!1);var u=n.createBuffer();u.putInt32(s[0]),u.putInt32(s[1]),u.putInt32(s[2]),u.putInt32(s[3]),e.equal(u.toHex(),"dda97ca4864cdfe06eaf70a0ec0d7191")}),it("should decrypt a single block with a 192-bit key",function(){var r=[66051,67438087,134810123,202182159,269554195,336926231],i=[3718872228,2253184992,1856991392,3960304017],s=[],o=t._expandKey(r,!0);t._updateBlock(o,i,s,!0);var u=n.createBuffer();u.putInt32(s[0]),u.putInt32(s[1]),u.putInt32(s[2]),u.putInt32(s[3]),e.equal(u.toHex(),"00112233445566778899aabbccddeeff")}),it("should encrypt a single block with a 256-bit key",function(){var r=[66051,67438087,134810123,202182159,269554195,336926231,404298267,471670303],i=[1122867,1146447479,2291772091,3437096703],s=[],o=t._expandKey(r,!1);t._updateBlock(o,i,s,!1);var u=n.createBuffer();u.putInt32(s[0]),u.putInt32(s[1]),u.putInt32(s[2]),u.putInt32(s[3]),e.equal(u.toHex(),"8ea2b7ca516745bfeafc49904b496089")}),it("should decrypt a single block with a 256-bit key",function(){var r=[66051,67438087,134810123,202182159,269554195,336926231,404298267,471670303],i=[2393028554,1365722559,3942402448,1263100041],s=[],o=t._expandKey(r,!0);t._updateBlock(o,i,s,!0);var u=n.createBuffer();u.putInt32(s[0]),u.putInt32(s[1]),u.putInt32(s[2]),u.putInt32(s[3]),e.equal(u.toHex(),"00112233445566778899aabbccddeeff")}),function(){var r=["06a9214036b8a15b512e03d534120006","c286696d887c9aa0611bbb3e2025a45a","6c3ea0477630ce21a2ce334aa746c2cd","56e47a38c5598974bc46903dba290349"],i=["3dafba429d9eb430b422da802c9fac41","562e17996d093d28ddb3ba695a2e6f58","c782dc4c098c66cbd9cd27d825682c81","8ce82eefbea0da3c44699ed7db51b7d9"],s=["Single block msg","000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","This is a 48-byte message (exactly 3 AES blocks)","a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedf"],o=["e353779c1079aeb82708942dbe77181a","d296cd94c2cccf8a3a863028b5e1dc0a7586602d253cfff91b8266bea6d61ab1","d0a02b3836451753d493665d33f0e8862dea54cdb293abc7506939276772f8d5021c19216bad525c8579695d83ba2684","c30e32ffedc0774e6aff6af0869f71aa0f3af07a9a31a9c684db207eb0ef8e4e35907aa632c3ffdf868bb7b29d3d46ad83ce9f9a102ee99d49a53e87f4c3da55"];for(var u=0;u<r.length;++u)(function(u){var a=n.hexToBytes(r[u]),f=n.hexToBytes(i[u]),l=u&1?n.hexToBytes(s[u]):s[u],c=n.hexToBytes(o[u]);it("should aes-128-cbc encrypt: "+s[u],function(){var r=t.createEncryptionCipher(a,"CBC");r.start(f),r.update(n.createBuffer(l)),r.finish(function(){return!0}),e.equal(r.output.toHex(),o[u])}),it("should aes-128-cbc decrypt: "+o[u],function(){var r=t.createDecryptionCipher(a,"CBC");r.start(f),r.update(n.createBuffer(c)),r.finish(function(){return!0});var i=u&1?r.output.toHex():r.output.bytes();e.equal(i,s[u])})})(u)}(),function(){var r=["00000000000000000000000000000000","2b7e151628aed2a6abf7158809cf4f3c","2b7e151628aed2a6abf7158809cf4f3c","2b7e151628aed2a6abf7158809cf4f3c","2b7e151628aed2a6abf7158809cf4f3c","00000000000000000000000000000000"],i=["80000000000000000000000000000000","000102030405060708090a0b0c0d0e0f","3B3FD92EB72DAD20333449F8E83CFB4A","C8A64537A0B3A93FCDE3CDAD9F1CE58B","26751F67A3CBB140B1808CF187A4F4DF","60f9ff04fac1a25657bf5b36b5efaf75"],s=["00000000000000000000000000000000","6bc1bee22e409f96e93d7e117393172a","ae2d8a571e03ac9c9eb76fac45af8e51","30c81c46a35ce411e5fbc1191a0a52ef","f69f2445df4f9b17ad2b417be66c3710","This is a 48-byte message (exactly 3 AES blocks)"],o=["3ad78e726c1ec02b7ebfe92b23d9ec34","3b3fd92eb72dad20333449f8e83cfb4a","c8a64537a0b3a93fcde3cdad9f1ce58b","26751f67a3cbb140b1808cf187a4f4df","c04b05357c5d1c0eeac4c66f9ff7f2e6","52396a2ba1ba420c5e5b699a814944d8f4e7fbf984a038319fbc0b4ee45cfa6f07b2564beab5b5e92dbd44cb345f49b4"];for(var u=0;u<r.length;++u)(function(u){var a=n.hexToBytes(r[u]),f=n.hexToBytes(i[u]),l=u!==5?n.hexToBytes(s[u]):s[u],c=n.hexToBytes(o[u]);it("should aes-128-cfb encrypt: "+s[u],function(){var r=t.createEncryptionCipher(a,"CFB");r.start(f),r.update(n.createBuffer(l)),r.finish(),e.equal(r.output.toHex(),o[u])}),it("should aes-128-cfb decrypt: "+o[u],function(){var r=t.createDecryptionCipher(a,"CFB");r.start(f),r.update(n.createBuffer(c)),r.finish();var i=u!==5?r.output.toHex():r.output.getBytes();e.equal(i,s[u])})})(u)}(),function(){var r=["00000000000000000000000000000000","00000000000000000000000000000000"],i=["80000000000000000000000000000000","c8ca0d6a35dbeac776e911ee16bea7d3"],s=["00000000000000000000000000000000","This is a 48-byte message (exactly 3 AES blocks)"],o=["3ad78e726c1ec02b7ebfe92b23d9ec34","39c0190727a76b2a90963426f63689cfcdb8a2be8e20c5e877a81a724e3611f62ecc386f2e941b2441c838906002be19"];for(var u=0;u<r.length;++u)(function(u){var a=n.hexToBytes(r[u]),f=n.hexToBytes(i[u]),l=u!==1?n.hexToBytes(s[u]):s[u],c=n.hexToBytes(o[u]);it("should aes-128-ofb encrypt: "+s[u],function(){var r=t.createEncryptionCipher(a,"OFB");r.start(f),r.update(n.createBuffer(l)),r.finish(),e.equal(r.output.toHex(),o[u])}),it("should aes-128-ofb decrypt: "+o[u],function(){var r=t.createDecryptionCipher(a,"OFB");r.start(f),r.update(n.createBuffer(c)),r.finish();var i=u!==1?r.output.toHex():r.output.getBytes();e.equal(i,s[u])})})(u)}(),function(){var r=["2b7e151628aed2a6abf7158809cf4f3c","00000000000000000000000000000000"],i=["f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff","650cdb80ff9fc758342d2bd99ee2abcf"],s=["6bc1bee22e409f96e93d7e117393172a","This is a 48-byte message (exactly 3 AES blocks)"],o=["874d6191b620e3261bef6864990db6ce","5ede11d00e9a76ec1d5e7e811ea3dd1ce09ee941210f825d35718d3282796f1c07c3f1cb424f2b365766ab5229f5b5a4"];for(var u=0;u<r.length;++u)(function(u){var a=n.hexToBytes(r[u]),f=n.hexToBytes(i[u]),l=u!==1?n.hexToBytes(s[u]):s[u],c=n.hexToBytes(o[u]);it("should aes-128-ctr encrypt: "+s[u],function(){var r=t.createEncryptionCipher(a,"CTR");r.start(f),r.update(n.createBuffer(l)),r.finish(),e.equal(r.output.toHex(),o[u])}),it("should aes-128-ctr decrypt: "+o[u],function(){var r=t.createDecryptionCipher(a,"CTR");r.start(f),r.update(n.createBuffer(c)),r.finish();var i=u!==1?r.output.toHex():r.output.getBytes();e.equal(i,s[u])})})(u)}(),function(){var r=["861009ec4d599fab1f40abc76e6f89880cff5833c79c548c99f9045f191cd90b"],i=["d927ad81199aa7dcadfdb4e47b6dc694"],s=["MY-DATA-AND-HERE-IS-MORE-DATA"],o=["80eb666a9fc9e263faf71e87ffc94451d7d8df7cfcf2606470351dd5ac"];for(var u=0;u<r.length;++u)(function(u){var a=n.hexToBytes(r[u]),f=n.hexToBytes(i[u]),l=s[u],c=n.hexToBytes(o[u]);it("should aes-256-cfb encrypt: "+s[u],function(){var r=t.createEncryptionCipher(a,"CFB");r.start(f),r.update(n.createBuffer(l)),r.finish(),e.equal(r.output.toHex(),o[u])}),it("should aes-256-cfb decrypt: "+o[u],function(){var r=t.createDecryptionCipher(a,"CFB");r.start(f),r.update(n.createBuffer(c)),r.finish();var i=r.output.getBytes();e.equal(i,s[u])})})(u)}()})}typeof define=="function"?define("test/aes",["forge/aes","forge/util"],function(t,n){e(ASSERT,t(),n())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/aes")(),require("../../js/util")())}(),function(){function e(e,t,n){describe("rc2",function(){it("should expand a 128-bit key",function(){var r=n.hexToBytes("88bca90e90875a7f0f79c384627bafb2"),i="71ab26462f0b9333609d4476e48ab72438c2194b70a47085d84b6af1dc72119023b94fe80aee2b6b45f27f923d9be1570da3ce8b16ad7f78db166ffbc28a836a4392cf0b748085dae4b69bdc2a4679cdfc09d84317016987e0c5b765c91dc612b1f44d7921b3e2c46447508bd2ac02e119e0f42a89c719675da320cf3e8958cd";e.equal(t.expandKey(r).toHex(),i)}),it("should expand a 40-bit key",function(){var r=n.hexToBytes("88bca90e90"),i="af136d2243b94a0878d7a604f8d6d9fd64a698fd6ebc613e641f0d1612055ef6cb55966db8f32bfd9246dae99880be8a91433adf54ea546d9daad62db7a55f6c7790aa87ba67de0e9ea9128dfc7ccdddd7c47c33d2bb7f823729977f083b5dc1f5bb09000b98e12cdaaf22f80dcc88c37d2c2fd80402f8a30a9e41d356669471";e.equal(t.expandKey(r,40).toHex(),i)}),it("should rc2-ecb encrypt zeros",function(){var r=n.hexToBytes("88bca90e90875a7f0f79c384627bafb2"),i=(new n.createBuffer).fillWithByte(0,8),s=t.startEncrypting(r,null,null);s.update(i),s.finish(),e.equal(s.output.toHex(),"2269552ab0f85ca6e35b3b2ce4e02191")}),it("should rc2-ecb encrypt: vegan",function(){var r=n.hexToBytes("88bca90e90875a7f0f79c384627bafb2"),i=new n.createBuffer("vegan"),s=t.startEncrypting(r,null,null);s.update(i),s.finish(),e.equal(s.output.toHex(),"2194adaf4d517e3a")}),it("should rc2-ecb decrypt: 2194adaf4d517e3a",function(){var r=n.hexToBytes("88bca90e90875a7f0f79c384627bafb2"),i=new n.createBuffer(n.hexToBytes("2194adaf4d517e3a")),s=t.startDecrypting(r,null,null);s.update(i),s.finish(),e.equal(s.output.getBytes(),"vegan")}),it("should rc2-cbc encrypt: revolution",function(){var r=n.hexToBytes("88bca90e90875a7f0f79c384627bafb2"),i=new n.createBuffer(n.hexToBytes("0123456789abcdef")),s=new n.createBuffer("revolution"),o=t.startEncrypting(r,i,null);o.update(s),o.finish(),e.equal(o.output.toHex(),"50cfd16e0fd7f20b17a622eb2a469b7e")}),it("should rc2-cbc decrypt: 50cfd16e0fd7f20b17a622eb2a469b7e",function(){var r=n.hexToBytes("88bca90e90875a7f0f79c384627bafb2"),i=new n.createBuffer(n.hexToBytes("0123456789abcdef")),s=new n.createBuffer(n.hexToBytes("50cfd16e0fd7f20b17a622eb2a469b7e")),o=t.startDecrypting(r,i,null);o.update(s),o.finish(),e.equal(o.output,"revolution")})})}typeof define=="function"?define("test/rc2",["forge/rc2","forge/util"],function(t,n){e(ASSERT,t(),n())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/rc2")(),require("../../js/util")())}(),function(){function e(e,t,n){describe("des",function(){it("should des-ecb encrypt: foobar",function(){var r=new n.createBuffer(n.hexToBytes("a1c06b381adf3651")),i=t.startEncrypting(r,null,null);i.update(n.createBuffer("foobar")),i.finish(),e.equal(i.output.toHex(),"b705ffcf3dff06b3")}),it("should des-ecb decrypt: b705ffcf3dff06b3",function(){var r=new n.createBuffer(n.hexToBytes("a1c06b381adf3651")),i=t.startDecrypting(r,null,null);i.update(n.createBuffer(n.hexToBytes("b705ffcf3dff06b3"))),i.finish(),e.equal(i.output.getBytes(),"foobar")}),it("should des-cbc encrypt: foobar",function(){var r=new n.createBuffer(n.hexToBytes("a1c06b381adf3651")),i=new n.createBuffer(n.hexToBytes("818bcf76efc59662")),s=t.startEncrypting(r,i,null);s.update(n.createBuffer("foobar")),s.finish(),e.equal(s.output.toHex(),"3261e5839a990454")}),it("should des-cbc decrypt: 3261e5839a990454",function(){var r=new n.createBuffer(n.hexToBytes("a1c06b381adf3651")),i=new n.createBuffer(n.hexToBytes("818bcf76efc59662")),s=t.startDecrypting(r,i,null);s.update(n.createBuffer(n.hexToBytes("3261e5839a990454"))),s.finish(),e.equal(s.output.getBytes(),"foobar")}),it("should 3des-ecb encrypt: foobar",function(){var r=new n.createBuffer(n.hexToBytes("a1c06b381adf36517e84575552777779da5e3d9f994b05b5")),i=t.startEncrypting(r,null,null);i.update(n.createBuffer("foobar")),i.finish(),e.equal(i.output.toHex(),"fce8b1ee8c6440d1")}),it("should 3des-ecb decrypt: fce8b1ee8c6440d1",function(){var r=new n.createBuffer(n.hexToBytes("a1c06b381adf36517e84575552777779da5e3d9f994b05b5")),i=t.startDecrypting(r,null,null);i.update(n.createBuffer(n.hexToBytes("fce8b1ee8c6440d1"))),i.finish(),e.equal(i.output.getBytes(),"foobar")}),it('should 3des-cbc encrypt "foobar", restart, and encrypt "foobar,,"',function(){var r=new n.createBuffer(n.hexToBytes("a1c06b381adf36517e84575552777779da5e3d9f994b05b5")),i=new n.createBuffer(n.hexToBytes("818bcf76efc59662")),s=t.startEncrypting(r,i.copy(),null);s.update(n.createBuffer("foobar")),s.finish(),e.equal(s.output.toHex(),"209225f7687ca0b2"),s.start(i.copy()),s.update(n.createBuffer("foobar,,")),s.finish(),e.equal(s.output.toHex(),"57156174c48dfc37293831bf192a6742")}),it('should 3des-cbc decrypt "209225f7687ca0b2", restart, and decrypt "57156174c48dfc37293831bf192a6742,,"',function(){var r=new n.createBuffer(n.hexToBytes("a1c06b381adf36517e84575552777779da5e3d9f994b05b5")),i=new n.createBuffer(n.hexToBytes("818bcf76efc59662")),s=t.startDecrypting(r,i.copy(),null);s.update(n.createBuffer(n.hexToBytes("209225f7687ca0b2"))),s.finish(),e.equal(s.output.getBytes(),"foobar"),s.start(i.copy()),s.update(n.createBuffer(n.hexToBytes("57156174c48dfc37293831bf192a6742"))),s.finish(),e.equal(s.output.getBytes(),"foobar,,")})})}typeof define=="function"?define("test/des",["forge/des","forge/util"],function(t,n){e(ASSERT,t(),n())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/des")(),require("../../js/util")())}(),function(){function e(e){var t=e.asn1,n=e.pkcs7=e.pkcs7||{};n.messageFromPem=function(r){var i=e.pem.decode(r)[0];if(i.type!=="PKCS7")throw{message:'Could not convert PKCS#7 message from PEM; PEM header type is not "PKCS#7".',headerType:i.type};if(i.procType&&i.procType.type==="ENCRYPTED")throw{message:"Could not convert PKCS#7 message from PEM; PEM is encrypted."};var s=t.fromDer(i.body);return n.messageFromAsn1(s)},n.messageToPem=function(n,r){var i={type:"PKCS7",body:t.toDer(n.toAsn1()).getBytes()};return e.pem.encode(i,{maxline:r})},n.messageFromAsn1=function(r){var i={},s=[];if(!t.validate(r,n.asn1.contentInfoValidator,i,s))throw{message:"Cannot read PKCS#7 message. ASN.1 object is not an PKCS#7 ContentInfo.",errors:s};var o=t.derToOid(i.contentType),u;switch(o){case e.pki.oids.envelopedData:u=n.createEnvelopedData();break;case e.pki.oids.encryptedData:u=n.createEncryptedData();break;case e.pki.oids.signedData:u=n.createSignedData();break;default:throw{message:"Cannot read PKCS#7 message. ContentType with OID "+o+" is not (yet) supported."}}return u.fromAsn1(i.content.value[0]),u};var r=function(r){var i={},s=[];if(!t.validate(r,n.asn1.recipientInfoValidator,i,s))throw{message:"Cannot read PKCS#7 message. ASN.1 object is not an PKCS#7 EnvelopedData.",errors:s};return{version:i.version.charCodeAt(0),issuer:e.pki.RDNAttributesAsArray(i.issuer),serialNumber:e.util.createBuffer(i.serial).toHex(),encContent:{algorithm:t.derToOid(i.encAlgorithm),parameter:i.encParameter.value,content:i.encKey}}},i=function(n){return t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,String.fromCharCode(n.version)),t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[e.pki.distinguishedNameToAsn1({attributes:n.issuer}),t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,e.util.hexToBytes(n.serialNumber))]),t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.encContent.algorithm).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.NULL,!1,"")]),t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,n.encContent.content)])},s=function(e){var t=[];for(var n=0;n<e.length;n++)t.push(r(e[n]));return t},o=function(e){var t=[];for(var n=0;n<e.length;n++)t.push(i(e[n]));return t},u=function(n){return[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(e.pki.oids.data).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.algorithm).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,n.parameter.getBytes())]),t.create(t.Class.CONTEXT_SPECIFIC,0,!0,[t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,n.content.getBytes())])]},a=function(n,r,i){var s={},o=[];if(!t.validate(r,i,s,o))throw{message:"Cannot read PKCS#7 message. ASN.1 object is not an PKCS#7 EnvelopedData.",errors:o};var u=t.derToOid(s.contentType);if(u!==e.pki.oids.data)throw{message:"Unsupported PKCS#7 message. Only contentType Data supported within EnvelopedData."};if(s.encContent){var a="";if(e.util.isArray(s.encContent))for(var f=0;f<s.encContent.length;++f){if(s.encContent[f].type!==t.Type.OCTETSTRING)throw{message:"Malformed PKCS#7 message, expecting encrypted content constructed of only OCTET STRING objects."};a+=s.encContent[f].value}else a=s.encContent;n.encContent={algorithm:t.derToOid(s.encAlgorithm),parameter:e.util.createBuffer(s.encParameter.value),content:e.util.createBuffer(a)}}if(s.content){var a="";if(e.util.isArray(s.content))for(var f=0;f<s.content.length;++f){if(s.content[f].type!==t.Type.OCTETSTRING)throw{message:"Malformed PKCS#7 message, expecting content constructed of only OCTET STRING objects."};a+=s.content[f].value}else a=s.content;n.content=e.util.createBuffer(a)}return n.version=s.version.charCodeAt(0),n.rawCapture=s,s},f=function(t){if(t.encContent.key===undefined)throw{message:"Symmetric key not available."};if(t.content===undefined){var n;switch(t.encContent.algorithm){case e.pki.oids["aes128-CBC"]:case e.pki.oids["aes192-CBC"]:case e.pki.oids["aes256-CBC"]:n=e.aes.createDecryptionCipher(t.encContent.key);break;case e.pki.oids["des-EDE3-CBC"]:n=e.des.createDecryptionCipher(t.encContent.key);break;default:throw{message:"Unsupported symmetric cipher, OID "+t.encContent.algorithm}}n.start(t.encContent.parameter),n.update(t.encContent.content);if(!n.finish())throw{message:"Symmetric decryption failed."};t.content=n.output}};n.createSignedData=function(){var t=null;return t={type:e.pki.oids.signedData,version:1,fromAsn1:function(e){a(t,e,n.asn1.signedDataValidator)}},t},n.createEncryptedData=function(){var t=null;return t={type:e.pki.oids.encryptedData,version:0,encContent:{algorithm:e.pki.oids["aes256-CBC"]},fromAsn1:function(e){a(t,e,n.asn1.encryptedDataValidator)},decrypt:function(e){e!==undefined&&(t.encContent.key=e),f(t)}},t},n.createEnvelopedData=function(){var r=null;return r={type:e.pki.oids.envelopedData,version:0,recipients:[],encContent:{algorithm:e.pki.oids["aes256-CBC"]},fromAsn1:function(e){var t=a(r,e,n.asn1.envelopedDataValidator);r.recipients=s(t.recipientInfos.value)},toAsn1:function(){return t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(r.type).getBytes()),t.create(t.Class.CONTEXT_SPECIFIC,0,!0,[t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,String.fromCharCode(r.version)),t.create(t.Class.UNIVERSAL,t.Type.SET,!0,o(r.recipients)),t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,u(r.encContent))])])])},findRecipient:function(e){var t=e.issuer.attributes;for(var n=0;n<r.recipients.length;++n){var i=r.recipients[n],s=i.issuer;if(i.serialNumber!==e.serialNumber)continue;if(s.length!==t.length)continue;var o=!0;for(var u=0;u<t.length;++u)if(s[u].type!==t[u].type||s[u].value!==t[u].value){o=!1;break}if(o)return i}return null},decrypt:function(t,n){if(r.encContent.key===undefined&&t!==undefined&&n!==undefined)switch(t.encContent.algorithm){case e.pki.oids.rsaEncryption:var i=n.decrypt(t.encContent.content);r.encContent.key=e.util.createBuffer(i);break;default:throw{message:"Unsupported asymmetric cipher, OID "+t.encContent.algorithm}}f(r)},addRecipient:function(t){r.recipients.push({version:0,issuer:t.subject.attributes,serialNumber:t.serialNumber,encContent:{algorithm:e.pki.oids.rsaEncryption,key:t.publicKey}})},encrypt:function(t,n){if(r.encContent.content===undefined){n=n||r.encContent.algorithm,t=t||r.encContent.key;var i,s,o;switch(n){case e.pki.oids["aes128-CBC"]:i=16,s=16,o=e.aes.createEncryptionCipher;break;case e.pki.oids["aes192-CBC"]:i=24,s=16,o=e.aes.createEncryptionCipher;break;case e.pki.oids["aes256-CBC"]:i=32,s=16,o=e.aes.createEncryptionCipher;break;case e.pki.oids["des-EDE3-CBC"]:i=24,s=8,o=e.des.createEncryptionCipher;break;default:throw{message:"Unsupported symmetric cipher, OID "+n}}if(t===undefined)t=e.util.createBuffer(e.random.getBytes(i));else if(t.length()!=i)throw{message:"Symmetric key has wrong length, got "+t.length()+" bytes, expected "+i};r.encContent.algorithm=n,r.encContent.key=t,r.encContent.parameter=e.util.createBuffer(e.random.getBytes(s));var u=o(t);u.start(r.encContent.parameter.copy()),u.update(r.content);if(!u.finish())throw{message:"Symmetric encryption failed."};r.encContent.content=u.output}for(var a=0;a<r.recipients.length;a++){var f=r.recipients[a];if(f.encContent.content!==undefined)continue;switch(f.encContent.algorithm){case e.pki.oids.rsaEncryption:f.encContent.content=f.encContent.key.encrypt(r.encContent.key.data);break;default:throw{message:"Unsupported asymmetric cipher, OID "+f.encContent.algorithm}}}}},r}}var t="pkcs7";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/pkcs7",["require","module","./aes","./asn1","./des","./pem","./pkcs7asn1","./pki","./random","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e,t,n,r,i,s){var o={p7:"-----BEGIN PKCS7-----\r\nMIICTgYJKoZIhvcNAQcDoIICPzCCAjsCAQAxggHGMIIBwgIBADCBqTCBmzELMAkG\r\nA1UEBhMCREUxEjAQBgNVBAgMCUZyYW5jb25pYTEQMA4GA1UEBwwHQW5zYmFjaDEV\r\nMBMGA1UECgwMU3RlZmFuIFNpZWdsMRIwEAYDVQQLDAlHZWllcmxlaW4xFjAUBgNV\r\nBAMMDUdlaWVybGVpbiBERVYxIzAhBgkqhkiG9w0BCQEWFHN0ZXNpZUBicm9rZW5w\r\naXBlLmRlAgkA1FQcQNg14vMwDQYJKoZIhvcNAQEBBQAEggEAJhWQz5SniCd1w3A8\r\nuKVZEfc8Tp21I7FMfFqou+UOVsZCq7kcEa9uv2DIj3o7zD8wbLK1fuyFi4SJxTwx\r\nkR0a6V4bbonIpXPPJ1f615dc4LydAi2tv5w14LJ1Js5XCgGVnkAmQHDaW3EHXB7X\r\nT4w9PR3+tcS/5YAnWaM6Es38zCKHd7TnHpuakplIkwSK9rBFAyA1g/IyTPI+ktrE\r\nEHcVuJcz/7eTlF6wJEa2HL8F1TVWuL0p/0GsJP/8y0MYGdCdtr+TIVo//3YGhoBl\r\nN4tnheFT/jRAzfCZtflDdgAukW24CekrJ1sG2M42p5cKQ5rGFQtzNy/n8EjtUutO\r\nHD5YITBsBgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBBmlpfy3WrYj3uWW7+xNEiH\r\ngEAm2mfSF5xFPLEqqFkvKTM4w8PfhnF0ehmfQNApvoWQRQanNWLCT+Q9GHx6DCFj\r\nTUHl+53x88BrCl1E7FhYPs92\r\n-----END PKCS7-----\r\n",certificate:"-----BEGIN CERTIFICATE-----\r\nMIIDtDCCApwCCQDUVBxA2DXi8zANBgkqhkiG9w0BAQUFADCBmzELMAkGA1UEBhMC\r\nREUxEjAQBgNVBAgMCUZyYW5jb25pYTEQMA4GA1UEBwwHQW5zYmFjaDEVMBMGA1UE\r\nCgwMU3RlZmFuIFNpZWdsMRIwEAYDVQQLDAlHZWllcmxlaW4xFjAUBgNVBAMMDUdl\r\naWVybGVpbiBERVYxIzAhBgkqhkiG9w0BCQEWFHN0ZXNpZUBicm9rZW5waXBlLmRl\r\nMB4XDTEyMDMxODIyNTc0M1oXDTEzMDMxODIyNTc0M1owgZsxCzAJBgNVBAYTAkRF\r\nMRIwEAYDVQQIDAlGcmFuY29uaWExEDAOBgNVBAcMB0Fuc2JhY2gxFTATBgNVBAoM\r\nDFN0ZWZhbiBTaWVnbDESMBAGA1UECwwJR2VpZXJsZWluMRYwFAYDVQQDDA1HZWll\r\ncmxlaW4gREVWMSMwIQYJKoZIhvcNAQkBFhRzdGVzaWVAYnJva2VucGlwZS5kZTCC\r\nASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMsAbQ4fWevHqP1K1y/ewpMS\r\n3vYovBto7IsKBq0v3NmC2kPf3NhyaSKfjOOS5uAPONLffLck+iGdOLLFia6OSpM6\r\n0tyQIV9lHoRh7fOEYORab0Z+aBUZcEGT9yotBOraX1YbKc5f9XO+80eG4XYvb5ua\r\n1NHrxWqe4w2p3zGJCKO+wHpvGkbKz0nfu36jwWz5aihfHi9hp/xs8mfH86mIKiD7\r\nf2X2KeZ1PK9RvppA0X3lLb2VLOqMt+FHWicyZ/wjhQZ4oW55ln2yYJUQ+adlgaYn\r\nPrtnsxmbTxM+99oF0F2/HmGrNs8nLZSva1Vy+hmjmWz6/O8ZxhiIj7oBRqYcAocC\r\nAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAvfvtu31GFBO5+mFjPAoR4BlzKq/H3EPO\r\nqS8cm/TjHgDRALwSnwKYCFs/bXqE4iOTD6otV4TusX3EPbqL2vzZQEcZn6paU/oZ\r\nZVXwQqMqY5tf2teQiNxqxNmSIEPRHOr2QVBVIx2YF4Po89KGUqJ9u/3/10lDqRwp\r\nsReijr5UKv5aygEcnwcW8+Ne4rTx934UDsutKG20dr5trZfWQRVS9fS9CFwJehEX\r\nHAMUc/0++80NhfQthmWZWlWM1R3dr4TrIPtWdn5z0MtGeDvqBk7HjGrhcVS6kAsy\r\nZ9y/lfLPjBuxlQAHztEJCWgI4TW3/RLhgfg2gI1noM2n84Cdmisfkg==\r\n-----END CERTIFICATE-----\r\n",privateKey:"-----BEGIN RSA PRIVATE KEY-----\r\nMIIEowIBAAKCAQEAywBtDh9Z68eo/UrXL97CkxLe9ii8G2jsiwoGrS/c2YLaQ9/c\r\n2HJpIp+M45Lm4A840t98tyT6IZ04ssWJro5KkzrS3JAhX2UehGHt84Rg5FpvRn5o\r\nFRlwQZP3Ki0E6tpfVhspzl/1c77zR4bhdi9vm5rU0evFap7jDanfMYkIo77Aem8a\r\nRsrPSd+7fqPBbPlqKF8eL2Gn/GzyZ8fzqYgqIPt/ZfYp5nU8r1G+mkDRfeUtvZUs\r\n6oy34UdaJzJn/COFBnihbnmWfbJglRD5p2WBpic+u2ezGZtPEz732gXQXb8eYas2\r\nzyctlK9rVXL6GaOZbPr87xnGGIiPugFGphwChwIDAQABAoIBAAjMA+3QvfzRsikH\r\nzTtt09C7yJ2yNjSZ32ZHEPMAV/m1CfBXCyL2EkhF0b0q6IZdIoFA3g6xs4UxYvuc\r\nQ9Mkp2ap7elQ9aFEqIXkGIOtAOXkZV4QrEH90DeHSfax7LygqfD5TF59Gg3iAHjh\r\nB3Qvqg58LyzJosx0BjLZYaqr3Yv67GkqyflpF/roPGdClHpahAi5PBkHiNhNTAUU\r\nLJRGvMegXGZkUKgGMAiGCk0N96OZwrinMKO6YKGdtgwVWC2wbJY0trElaiwXozSt\r\nNmP6KTQp94C7rcVO6v1lZiOfhBe5Kc8QHUU+GYydgdjqm6Rdow/yLHOALAVtXSeb\r\nU+tPfcECgYEA6Qi+qF+gtPincEDBxRtoKwAlRkALt8kly8bYiGcUmd116k/5bmPw\r\nd0tBUOQbqRa1obYC88goOVzp9LInAcBSSrexhVaPAF4nrkwYXMOq+76MiH17WUfQ\r\nMgVM2IB48PBjNk1s3Crj6j1cxxkctqmCnVaI9HlU2PPZ3xjaklfv/NsCgYEA3wH8\r\nmehUhiAp7vuhd+hfomFw74cqgHC9v0saiYGckpMafh9MJGc4U5GrN1kYeb/CFkSx\r\n1hOytD3YBKoaKKoYagaMQcjxf6HnEF0f/5OiQkUQpWmgC9lNnE4XTWjnwqaTS5L9\r\nD+H50SiI3VjHymGXTRJeKpAIwV74AxxrnVofqsUCgYAwmL1B2adm9g/c7fQ6yatg\r\nhEhBrSuEaTMzmsUfNPfr2m4zrffjWH4WMqBtYRSPn4fDMHTPJ+eThtfXSqutxtCi\r\nekpP9ywdNIVr6LyP49Ita6Bc+mYVyU8Wj1pmL+yIumjGM0FHbL5Y4/EMKCV/xjvR\r\n2fD3orHaCIhf6QvzxtjqTwKBgFm6UemXKlMhI94tTsWRMNGEBU3LA9XUBvSuAkpr\r\nZRUwrQssCpXnFinBxbMqXQe3mR8emrM5D8En1P/jdU0BS3t1kP9zG4AwI2lZHuPV\r\nggbKBS2Y9zVtRKXsYcHawM13+nIA/WNjmAGJHrB45UJPy/HNvye+9lbfoEiYKdCR\r\nD4bFAoGBAIm9jcZkIwLa9kLAWH995YYYSGRY4KC29XZr2io2mog+BAjhFt1sqebt\r\nR8sRHNiIP2mcUECMOcaS+tcayi+8KTHWxIEed9qDmFu6XBbePfe/L6yxPSagcixH\r\nBK0KuK/fgTPvZCmIs8hUIC+AxhXKnqn4fIWoO54xLsALc0gEjs2d\r\n-----END RSA PRIVATE KEY-----\r\n",encryptedData:"-----BEGIN PKCS7-----\r\nMIGHBgkqhkiG9w0BBwagejB4AgEAMHMGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQI\r\nupMFou5X3DWAUAqObuHSlewM0ZtHzWk9MAmtYb7MSb//OBMKVfLCdbmrS5BpKm9J\r\ngzwiDR5Od7xgfkqasLS2lOdKAvJ5jZjjTpAyrjBKpShqK9gtXDuO0zH+\r\n-----END PKCS7-----\r\n",p7IndefiniteLength:"-----BEGIN PKCS7-----\r\nMIAGCSqGSIb3DQEHA6CAMIACAQAxggHGMIIBwgIBADCBqTCBmzELMAkGA1UEBhMC\r\nREUxEjAQBgNVBAgMCUZyYW5jb25pYTEQMA4GA1UEBwwHQW5zYmFjaDEVMBMGA1UE\r\nCgwMU3RlZmFuIFNpZWdsMRIwEAYDVQQLDAlHZWllcmxlaW4xFjAUBgNVBAMMDUdl\r\naWVybGVpbiBERVYxIzAhBgkqhkiG9w0BCQEWFHN0ZXNpZUBicm9rZW5waXBlLmRl\r\nAgkA1FQcQNg14vMwDQYJKoZIhvcNAQEBBQAEggEAlWCH+E25c4jfff+m0eAxxMmE\r\nWWaftdsk4ZpAVAr7HsvxJ35bj1mhwTh7rBTg929JBKt6ZaQ4I800jCNxD2O40V6z\r\nlB7JNRqzgBwfeuU2nV6FB7v1984NBi1jQx6EfxOcusE6RL/63HqJdFbmq3Tl55gF\r\ndm3JdjmHbCXqwPhuwOXU4yhkpV1RJcrYhPLe3OrLAH7ZfoE0nPJPOX9HPTZ6ReES\r\nNToS7I9D9k7rCa8fAP7pgjO96GJGBtCHG1VXB9NX4w+xRDbgVPOeHXqqxwZhqpW2\r\nusBU4+B+MnFLjquOPoySXFfdJFwTP61TPClUdyIne5FFP6EYf98mdtnkjxHo1TCA\r\nBgkqhkiG9w0BBwEwFAYIKoZIhvcNAwcECFNtpqBmU3M9oIAESM+yyQLkreETS0Kc\r\no01yl6dqqNBczH5FNTK88ypz38/jzjo47+DURlvGzjHJibiDsCz9KyiVmgbRrtvH\r\n08rfnMbrU+grCkkx9wQI1GnLrYhr87oAAAAAAAAAAAAA\r\n-----END PKCS7-----\r\n",p73des:"-----BEGIN PKCS7-----\r\nMIICTQYJKoZIhvcNAQcDoIICPjCCAjoCAQAxggHGMIIBwgIBADCBqTCBmzELMAkG\r\nA1UEBhMCREUxEjAQBgNVBAgMCUZyYW5jb25pYTEQMA4GA1UEBwwHQW5zYmFjaDEV\r\nMBMGA1UECgwMU3RlZmFuIFNpZWdsMRIwEAYDVQQLDAlHZWllcmxlaW4xFjAUBgNV\r\nBAMMDUdlaWVybGVpbiBERVYxIzAhBgkqhkiG9w0BCQEWFHN0ZXNpZUBicm9rZW5w\r\naXBlLmRlAgkA1FQcQNg14vMwDQYJKoZIhvcNAQEBBQAEggEAS6K+sQvdKcK6YafJ\r\nmaDPjBzyjf5jtBgVrFgBXTCRIp/Z2zAXa70skfxhbwTgmilYTacA7jPGRrnLmvBc\r\nBjhyCKM3dRUyYgh1K1ka0w1prvLmRk6Onf5df1ZQn3AJMIujJZcCOhbV1ByLInve\r\nxn02KNHstGmdHM/JGyPCp+iYGprhUozVSpNCKS+R33EbsT0sAxamfqdAblT9+5Qj\r\n4CABvW11a1clPV7STwBbAKbZaLs8mDeoWP0yHvBtJ7qzZdSgJJA2oU7SDv4icwEe\r\nAhccbe2HWkLRw8G5YG9XcWx5PnQQhhnXMxkLoSMIYxItyL/cRORbpDohd+otAo66\r\nWLH1ODBrBgkqhkiG9w0BBwEwFAYIKoZIhvcNAwcECD5EWJMv1fd7gEj1w3WM1KsM\r\nL8GDk9JoqA8t9v3oXCT0nAMXoNpHZMnv+0UHHVljlSXBTQxwUP5VMY/ddquJ5O3N\r\nrDEqqJuHB+KPIsW1kxrdplU=\r\n-----END PKCS7-----\r\n"};describe("pkcs7",function(){it("should import message from PEM",function(){var r=t.messageFromPem(o.p7);e.equal(r.type,n.oids.envelopedData),e.equal(r.version,0),e.equal(r.recipients.length,1),e.equal(r.recipients[0].version,0),e.equal(r.recipients[0].serialNumber,"00d4541c40d835e2f3"),e.equal(r.recipients[0].issuer.length,7),e.equal(r.recipients[0].issuer[0].type,"2.5.4.6"),e.equal(r.recipients[0].issuer[0].value,"DE"),e.equal(r.recipients[0].issuer[1].type,"2.5.4.8"),e.equal(r.recipients[0].issuer[1].value,"Franconia"),e.equal(r.recipients[0].issuer[2].type,"2.5.4.7"),e.equal(r.recipients[0].issuer[2].value,"Ansbach"),e.equal(r.recipients[0].issuer[3].type,"2.5.4.10"),e.equal(r.recipients[0].issuer[3].value,"Stefan Siegl"),e.equal(r.recipients[0].issuer[4].type,"2.5.4.11"),e.equal(r.recipients[0].issuer[4].value,"Geierlein"),e.equal(r.recipients[0].issuer[5].type,"2.5.4.3"),e.equal(r.recipients[0].issuer[5].value,"Geierlein DEV"),e.equal(r.recipients[0].issuer[6].type,"1.2.840.113549.1.9.1"),e.equal(r.recipients[0].issuer[6].value,"stesie@brokenpipe.de"),e.equal(r.recipients[0].encContent.algorithm,n.oids.rsaEncryption),e.equal(r.recipients[0].encContent.content.length,256),e.equal(r.encContent.algorithm,n.oids["aes256-CBC"]),e.equal(r.encContent.parameter.data.length,16)}),it("should import indefinite length message from PEM",function(){e.doesNotThrow(function(){var r=t.messageFromPem(o.p7IndefiniteLength);e.equal(r.type,n.oids.envelopedData),e.equal(r.encContent.parameter.toHex(),"536da6a06653733d"),e.equal(r.encContent.content.length(),80)})}),it("should find recipient by serial number",function(){var r=t.messageFromPem(o.p7),i=n.certificateFromPem(o.certificate),s=r.findRecipient(i);e.equal(s.serialNumber,"00d4541c40d835e2f3"),i.serialNumber="1234567890abcdef42",s=r.findRecipient(i),e.equal(s,null)}),it("should aes-decrypt message",function(){var r=t.messageFromPem(o.p7),i=n.privateKeyFromPem(o.privateKey);r.decrypt(r.recipients[0],i),e.equal(r.encContent.key.data.length,32),e.equal(r.content,"Today is Boomtime, the 9th day of Discord in the YOLD 3178\r\n")}),it("should 3des-decrypt message",function(){var r=t.messageFromPem(o.p73des),i=n.privateKeyFromPem(o.privateKey);r.decrypt(r.recipients[0],i),e.equal(r.encContent.key.data.length,24),e.equal(r.content,"Today is Prickle-Prickle, the 16th day of Discord in the YOLD 3178\r\n")}),it("should add a recipient",function(){var r=t.createEnvelopedData();e.equal(r.recipients.length,0);var i=n.certificateFromPem(o.certificate);r.addRecipient(i),e.equal(r.recipients.length,1),e.deepEqual(r.recipients[0].serialNumber,i.serialNumber),e.deepEqual(r.recipients[0].issuer,i.subject.attributes),e.deepEqual(r.recipients[0].encContent.key,i.publicKey)}),it("should aes-encrypt a message",function(){var i=t.createEnvelopedData(),u=n.certificateFromPem(o.certificate),a=n.privateKeyFromPem(o.privateKey);i.addRecipient(u),i.content=s.createBuffer("Just a little test"),e.equal(i.encContent.algorithm,n.oids["aes256-CBC"]),i.encrypt(),e.equal(i.encContent.key.data.length,32),e.equal(i.encContent.parameter.data.length,16),e.equal(i.encContent.content.data.length,32),e.equal(i.recipients[0].encContent.content.length,256),i.encContent.key.read=0,i.encContent.parameter.read=0;var f=a.decrypt(i.recipients[0].encContent.content);e.equal(f,i.encContent.key.data);var l=r.createDecryptionCipher(f);l.start(i.encContent.parameter),l.update(i.encContent.content),l.finish(),e.equal(l.output,"Just a little test")}),it("should 3des-ede-encrypt a message",function(){var r=t.createEnvelopedData(),u=n.certificateFromPem(o.certificate),a=n.privateKeyFromPem(o.privateKey);r.addRecipient(u),r.content=s.createBuffer("Just a little test"),r.encContent.algorithm=n.oids["des-EDE3-CBC"],r.encrypt(),e.equal(r.encContent.key.data.length,24),e.equal(r.encContent.parameter.data.length,8),e.equal(r.encContent.content.data.length,24),e.equal(r.recipients[0].encContent.content.length,256),r.encContent.key.read=0,r.encContent.parameter.read=0;var f=a.decrypt(r.recipients[0].encContent.content);e.equal(f,r.encContent.key.data);var l=i.createDecryptionCipher(f);l.start(r.encContent.parameter),l.update(r.encContent.content),l.finish(),e.equal(l.output,"Just a little test")}),it("should export message to PEM",function(){var r=t.createEnvelopedData();r.addRecipient(n.certificateFromPem(o.certificate)),r.content=s.createBuffer("Just a little test"),r.encrypt();var i=t.messageToPem(r);r=t.messageFromPem(i),r.decrypt(r.recipients[0],n.privateKeyFromPem(o.privateKey)),e.equal(r.content,"Just a little test")}),it("should decrypt encrypted data from PEM",function(){var r="1f8b08000000000000000b2e494d4bcc5308ce4c4dcfd15130b0b430d4b7343732b03437d05170cc2b4e4a4cced051b034343532d25170492d2d294ecec849cc4b0100bf52f02437000000",i="b96e4a4c0a3555d31e1b295647cc5cfe74081918cb7f797b";i=s.createBuffer(s.hexToBytes(i)),e.doesNotThrow(function(){var u=t.messageFromPem(o.encryptedData);e.equal(u.type,n.oids.encryptedData),e.equal(u.encContent.algorithm,n.oids["des-EDE3-CBC"]),e.equal(u.encContent.parameter.toHex(),"ba9305a2ee57dc35"),e.equal(u.encContent.content.length(),80),u.decrypt(i),e.equal(u.content.getBytes(),s.hexToBytes(r))})})})}typeof define=="function"?define("test/pkcs7",["forge/pkcs7","forge/pki","forge/aes","forge/des","forge/util"],function(t,n,r,i,s){e(ASSERT,t(),n(),r(),i(),s())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/pkcs7")(),require("../../js/pki")(),require("../../js/aes")(),require("../../js/des")(),require("../../js/util")())}(),function(){function e(e){var t=function(t,n,r,i){var s=e.util.createBuffer(),o=t.length>>1,u=o+(t.length&1),a=t.substr(0,u),f=t.substr(o,u),l=e.util.createBuffer(),c=e.hmac.create();r=n+r;var h=Math.ceil(i/16),p=Math.ceil(i/20);c.start("MD5",a);var d=e.util.createBuffer();l.putBytes(r);for(var v=0;v<h;++v)c.start(null,null),c.update(l.getBytes()),l.putBuffer(c.digest()),c.start(null,null),c.update(l.bytes()+r),d.putBuffer(c.digest());c.start("SHA1",f);var m=e.util.createBuffer();l.clear(),l.putBytes(r);for(var v=0;v<p;++v)c.start(null,null),c.update(l.getBytes()),l.putBuffer(c.digest()),c.start(null,null),c.update(l.bytes()+r),m.putBuffer(c.digest());return s.putBytes(e.util.xorBytes(d.getBytes(),m.getBytes(),i)),s},n=function(e,t,n,r){},r=function(t,n,r){var i=e.hmac.create();i.start("SHA1",t);var s=e.util.createBuffer();return s.putInt32(n[0]),s.putInt32(n[1]),s.putByte(r.type),s.putByte(r.version.major),s.putByte(r.version.minor),s.putInt16(r.length),s.putBytes(r.fragment.bytes()),i.update(s.getBytes()),i.digest().getBytes()},i=function(t,n,r){var i=!1;try{var s=t.deflate(n.fragment.getBytes());n.fragment=e.util.createBuffer(s),n.length=s.length,i=!0}catch(o){}return i},s=function(t,n,r){var i=!1;try{var s=t.inflate(n.fragment.getBytes());n.fragment=e.util.createBuffer(s),n.length=s.length,i=!0}catch(o){}return i},o=function(t,n){var r=0;switch(n){case 1:r=t.getByte();break;case 2:r=t.getInt16();break;case 3:r=t.getInt24();break;case 4:r=t.getInt32()}return e.util.createBuffer(t.getBytes(r))},u=function(e,t,n){e.putInt(n.length(),t<<3),e.putBuffer(n)},a={};a.Version={major:3,minor:1},a.MaxFragment=15360,a.ConnectionEnd={server:0,client:1},a.PRFAlgorithm={tls_prf_sha256:0},a.BulkCipherAlgorithm={none:null,rc4:0,des3:1,aes:2},a.CipherType={stream:0,block:1,aead:2},a.MACAlgorithm={none:null,hmac_md5:0,hmac_sha1:1,hmac_sha256:2,hmac_sha384:3,hmac_sha512:4},a.CompressionMethod={none:0,deflate:1},a.ContentType={change_cipher_spec:20,alert:21,handshake:22,application_data:23},a.HandshakeType={hello_request:0,client_hello:1,server_hello:2,certificate:11,server_key_exchange:12,certificate_request:13,server_hello_done:14,certificate_verify:15,client_key_exchange:16,finished:20},a.Alert={},a.Alert.Level={warning:1,fatal:2},a.Alert.Description={close_notify:0,unexpected_message:10,bad_record_mac:20,decryption_failed:21,record_overflow:22,decompression_failure:30,handshake_failure:40,bad_certificate:42,unsupported_certificate:43,certificate_revoked:44,certificate_expired:45,certificate_unknown:46,illegal_parameter:47,unknown_ca:48,access_denied:49,decode_error:50,decrypt_error:51,export_restriction:60,protocol_version:70,insufficient_security:71,internal_error:80,user_canceled:90,no_renegotiation:100},a.CipherSuites={},a.getCipherSuite=function(e){var t=null;for(var n in a.CipherSuites){var r=a.CipherSuites[n];if(r.id[0]===e.charCodeAt(0)&&r.id[1]===e.charCodeAt(1)){t=r;break}}return t},a.handleUnexpected=function(e,t){var n=!e.open&&e.entity===a.ConnectionEnd.client;n||e.error(e,{message:"Unexpected message. Received TLS record out of order.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.unexpected_message}})},a.handleHelloRequest=function(e,t,n){!e.handshaking&&e.handshakes>0&&(a.queue(e,a.createAlert({level:a.Alert.Level.warning,description:a.Alert.Description.no_renegotiation})),a.flush(e)),e.process()},a.parseHelloMessage=function(t,n,r){var i=null,s=t.entity===a.ConnectionEnd.client;if(r<38)t.error(t,{message:s?"Invalid ServerHello message. Message too short.":"Invalid ClientHello message. Message too short.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.illegal_parameter}});else{var u=n.fragment,f=u.length();i={version:{major:u.getByte(),minor:u.getByte()},random:e.util.createBuffer(u.getBytes(32)),session_id:o(u,1),extensions:[]},s?(i.cipher_suite=u.getBytes(2),i.compression_method=u.getByte()):(i.cipher_suites=o(u,2),i.compression_methods=o(u,1)),f=r-(f-u.length());if(f>0){var l=o(u,2);while(l.length()>0)i.extensions.push({type:[l.getByte(),l.getByte()],data:o(l,2)});if(!s)for(var c=0;c<i.extensions.length;++c){var h=i.extensions[c];if(h.type[0]===0&&h.type[1]===0){var p=o(h.data,2);while(p.length()>0){var d=p.getByte();if(d!==0)break;t.session.serverNameList.push(o(p,2).getBytes())}}}}(i.version.major!==a.Version.major||i.version.minor!==a.Version.minor)&&t.error(t,{message:"Incompatible TLS version.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.protocol_version}});if(s)t.session.cipherSuite=a.getCipherSuite(i.cipher_suite);else{var v=e.util.createBuffer(i.cipher_suites.bytes());while(v.length()>0){t.session.cipherSuite=a.getCipherSuite(v.getBytes(2));if(t.session.cipherSuite!==null)break}}if(t.session.cipherSuite===null)return t.error(t,{message:"No cipher suites in common.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.handshake_failure},cipherSuite:e.util.bytesToHex(i.cipher_suite)});s?t.session.compressionMethod=i.compression_method:t.session.compressionMethod=a.CompressionMethod.none}return i},a.createSecurityParameters=function(e,t){var n=e.entity===a.ConnectionEnd.client,r=t.random.bytes(),i=n?e.session.sp.client_random:r,s=n?r:a.createRandom().getBytes();e.session.sp={entity:e.entity,prf_algorithm:a.PRFAlgorithm.tls_prf_sha256,bulk_cipher_algorithm:null,cipher_type:null,enc_key_length:null,block_length:null,fixed_iv_length:null,record_iv_length:null,mac_algorithm:null,mac_length:null,mac_key_length:null,compression_algorithm:e.session.compressionMethod,pre_master_secret:null,master_secret:null,client_random:i,server_random:s}},a.handleServerHello=function(e,t,n){var r=a.parseHelloMessage(e,t,n);if(!e.fail){var i=r.session_id.bytes();i===e.session.id?(e.expect=d,e.session.resuming=!0,e.session.sp.server_random=r.random.bytes()):(e.expect=l,e.session.resuming=!1,a.createSecurityParameters(e,r)),e.session.id=i,e.process()}},a.handleClientHello=function(t,n,r){var i=a.parseHelloMessage(t,n,r);if(!t.fail){var s=i.session_id.bytes(),o=null;t.sessionCache&&(o=t.sessionCache.getSession(s),o===null&&(s="")),s.length===0&&(s=e.random.getBytes(32)),t.session.id=s,t.session.clientHelloVersion=i.version,t.session.sp=o?o.sp:{},o!==null?(t.expect=S,t.session.resuming=!0,t.session.sp.client_random=i.random.bytes()):(t.expect=t.verifyClient!==!1?b:w,t.session.resuming=!1,a.createSecurityParameters(t,i)),t.open=!0,a.queue(t,a.createRecord({type:a.ContentType.handshake,data:a.createServerHello(t)})),t.session.resuming?(a.queue(t,a.createRecord({type:a.ContentType.change_cipher_spec,data:a.createChangeCipherSpec()})),t.state.pending=a.createConnectionState(t),t.state.current.write=t.state.pending.write,a.queue(t,a.createRecord({type:a.ContentType.handshake,data:a.createFinished(t)}))):(a.queue(t,a.createRecord({type:a.ContentType.handshake,data:a.createCertificate(t)})),t.fail||(a.queue(t,a.createRecord({type:a.ContentType.handshake,data:a.createServerKeyExchange(t)})),t.verifyClient!==!1&&a.queue(t,a.createRecord({type:a.ContentType.handshake,data:a.createCertificateRequest(t)})),a.queue(t,a.createRecord({type:a.ContentType.handshake,data:a.createServerHelloDone(t)})))),a.flush(t),t.process()}},a.handleCertificate=function(t,n,r){if(r<3)t.error(t,{message:"Invalid Certificate message. Message too short.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.illegal_parameter}});else{var i=n.fragment,s={certificate_list:o(i,3)},u,f,l=[];try{while(s.certificate_list.length()>0)u=o(s.certificate_list,3),f=e.asn1.fromDer(u),u=e.pki.certificateFromAsn1(f,!0),l.push(u)}catch(h){t.error(t,{message:"Could not parse certificate list.",cause:h,send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.bad_certificate}})}if(!t.fail){var p=t.entity===a.ConnectionEnd.client;!p&&t.verifyClient!==!0||l.length!==0?l.length===0?t.expect=p?c:w:(p?t.session.serverCertificate=l[0]:t.session.clientCertificate=l[0],a.verifyCertificateChain(t,l)&&(t.expect=p?c:w)):t.error(t,{message:p?"No server certificate provided.":"No client certificate provided.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.illegal_parameter}}),t.process()}}},a.handleServerKeyExchange=function(e,t,n){n>0?e.error(e,{message:"Invalid key parameters. Only RSA is supported.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.unsupported_certificate}}):(e.expect=h,e.process())},a.handleClientKeyExchange=function(t,n,r){if(r<48)t.error(t,{message:"Invalid key parameters. Only RSA is supported.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.unsupported_certificate}});else{var i=n.fragment,s={enc_pre_master_secret:o(i,2).getBytes()},u=null;if(t.getPrivateKey)try{u=t.getPrivateKey(t,t.session.serverCertificate),u=e.pki.privateKeyFromPem(u)}catch(f){t.error(t,{message:"Could not get private key.",cause:f,send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.internal_error}})}if(u===null)t.error(t,{message:"No private key set.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.internal_error}});else try{var l=t.session.sp;l.pre_master_secret=u.decrypt(s.enc_pre_master_secret);var c=t.session.clientHelloVersion;if(c.major!==l.pre_master_secret.charCodeAt(0)||c.minor!==l.pre_master_secret.charCodeAt(1))throw{message:"TLS version rollback attack detected."}}catch(f){l.pre_master_secret=e.random.getBytes(48)}}t.fail||(t.expect=S,t.session.clientCertificate!==null&&(t.expect=E),t.process())},a.handleCertificateRequest=function(e,t,n){if(n<3)e.error(e,{message:"Invalid CertificateRequest. Message too short.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.illegal_parameter}});else{var r=t.fragment,i={certificate_types:o(r,1),certificate_authorities:o(r,2)};e.session.certificateRequest=i,e.expect=p,e.process()}},a.handleCertificateVerify=function(t,n,r){if(r<2)t.error(t,{message:"Invalid CertificateVerify. Message too short.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.illegal_parameter}});else{var i=n.fragment;i.read-=4;var s=i.bytes();i.read+=4;var u={signature:o(i,2).getBytes()},f=e.util.createBuffer();f.putBuffer(t.session.md5.digest()),f.putBuffer(t.session.sha1.digest()),f=f.getBytes();try{var l=t.session.clientCertificate;if(!l.publicKey.verify(f,u.signature,"NONE"))throw{message:"CertificateVerify signature does not match."};t.session.md5.update(s),t.session.sha1.update(s)}catch(c){t.error(t,{message:"Bad signature in CertificateVerify.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.handshake_failure}})}t.fail||(t.expect=S,t.process())}},a.handleServerHelloDone=function(t,n,r){if(r>0)t.error(t,{message:"Invalid ServerHelloDone message. Invalid length.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.record_overflow}});else if(t.serverCertificate===null){var i={message:"No server certificate provided. Not enough security.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.insufficient_security}},s=t.verify(t,i.alert.description,depth,[]);if(s===!0)i=null;else{if(s||s===0)typeof s=="object"&&!e.util.isArray(s)?(s.message&&(i.message=s.message),s.alert&&(i.alert.description=s.alert)):typeof s=="number"&&(i.alert.description=s);t.error(t,i)}}!t.fail&&t.session.certificateRequest!==null&&(n=a.createRecord({type:a.ContentType.handshake,data:a.createCertificate(t)}),a.queue(t,n));if(!t.fail){n=a.createRecord({type:a.ContentType.handshake,data:a.createClientKeyExchange(t)}),a.queue(t,n),t.expect=g;var o=function(e,t){e.session.certificateRequest!==null&&e.session.clientCertificate!==null&&a.queue(e,a.createRecord({type:a.ContentType.handshake,data:a.createCertificateVerify(e,t)})),a.queue(e,a.createRecord({type:a.ContentType.change_cipher_spec,data:a.createChangeCipherSpec()})),e.state.pending=a.createConnectionState(e),e.state.current.write=e.state.pending.write,a.queue(e,a.createRecord({type:a.ContentType.handshake,data:a.createFinished(e)})),e.expect=d,a.flush(e),e.process()};t.session.certificateRequest===null||t.session.clientCertificate===null?o(t,null):a.getClientSignature(t,o)}},a.handleChangeCipherSpec=function(e,t){if(t.fragment.getByte()!==1)e.error(e,{message:"Invalid ChangeCipherSpec message received.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.illegal_parameter}});else{var n=e.entity===a.ConnectionEnd.client;if(e.session.resuming&&n||!e.session.resuming&&!n)e.state.pending=a.createConnectionState(e);e.state.current.read=e.state.pending.read;if(!e.session.resuming&&n||e.session.resuming&&!n)e.state.pending=null;e.expect=n?v:x,e.process()}},a.handleFinished=function(n,r,i){var s=r.fragment;s.read-=4;var o=s.bytes();s.read+=4;var u=r.fragment.getBytes();s=e.util.createBuffer(),s.putBuffer(n.session.md5.digest()),s.putBuffer(n.session.sha1.digest());var f=n.entity===a.ConnectionEnd.client,l=f?"server finished":"client finished",c=n.session.sp,h=12,p=t;s=p(c.master_secret,l,s.getBytes(),h);if(s.getBytes()!==u)n.error(n,{message:"Invalid verify_data in Finished message.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.decrypt_error}});else{n.session.md5.update(o),n.session.sha1.update(o);if(n.session.resuming&&f||!n.session.resuming&&!f)a.queue(n,a.createRecord({type:a.ContentType.change_cipher_spec,data:a.createChangeCipherSpec()})),n.state.current.write=n.state.pending.write,n.state.pending=null,a.queue(n,a.createRecord({type:a.ContentType.handshake,data:a.createFinished(n)}));n.expect=f?m:T,n.handshaking=!1,++n.handshakes,n.peerCertificate=f?n.session.serverCertificate:n.session.clientCertificate,n.sessionCache?(n.session={id:n.session.id,sp:n.session.sp},n.session.sp.keys=null):n.session=null,a.flush(n),n.isConnected=!0,n.connected(n),n.process()}},a.handleAlert=function(e,t){var n=t.fragment,r={level:n.getByte(),description:n.getByte()},i;switch(r.description){case a.Alert.Description.close_notify:i="Connection closed.";break;case a.Alert.Description.unexpected_message:i="Unexpected message.";break;case a.Alert.Description.bad_record_mac:i="Bad record MAC.";break;case a.Alert.Description.decryption_failed:i="Decryption failed.";break;case a.Alert.Description.record_overflow:i="Record overflow.";break;case a.Alert.Description.decompression_failure:i="Decompression failed.";break;case a.Alert.Description.handshake_failure:i="Handshake failure.";break;case a.Alert.Description.bad_certificate:i="Bad certificate.";break;case a.Alert.Description.unsupported_certificate:i="Unsupported certificate.";break;case a.Alert.Description.certificate_revoked:i="Certificate revoked.";break;case a.Alert.Description.certificate_expired:i="Certificate expired.";break;case a.Alert.Description.certificate_unknown:i="Certificate unknown.";break;case a.Alert.Description.illegal_parameter:i="Illegal parameter.";break;case a.Alert.Description.unknown_ca:i="Unknown certificate authority.";break;case a.Alert.Description.access_denied:i="Access denied.";break;case a.Alert.Description.decode_error:i="Decode error.";break;case a.Alert.Description.decrypt_error:i="Decrypt error.";break;case a.Alert.Description.export_restriction:i="Export restriction.";break;case a.Alert.Description.protocol_version:i="Unsupported protocol version.";break;case a.Alert.Description.insufficient_security:i="Insufficient security.";break;case a.Alert.Description.internal_error:i="Internal error.";break;case a.Alert.Description.user_canceled:i="User canceled.";break;case a.Alert.Description.no_renegotiation:i="Renegotiation not supported.";break;default:i="Unknown error."}r.description===a.Alert.Description.close_notify?e.close():(e.error(e,{message:i,send:!1,origin:e.entity===a.ConnectionEnd.client?"server":"client",alert:r}),e.process())},a.handleHandshake=function(t,n){var r=n.fragment,i=r.getByte(),s=r.getInt24();if(s>r.length())t.fragmented=n,n.fragment=e.util.createBuffer(),r.read-=4,t.process();else{t.fragmented=null,r.read-=4;var o=r.bytes(s+4);r.read+=4,i in I[t.entity][t.expect]?(t.entity===a.ConnectionEnd.server&&!t.open&&!t.fail&&(t.handshaking=!0,t.session={serverNameList:[],cipherSuite:null,compressionMethod:null,serverCertificate:null,clientCertificate:null,md5:e.md.md5.create(),sha1:e.md.sha1.create()}),i!==a.HandshakeType.hello_request&&i!==a.HandshakeType.certificate_verify&&i!==a.HandshakeType.finished&&(t.session.md5.update(o),t.session.sha1.update(o)),I[t.entity][t.expect][i](t,n,s)):a.handleUnexpected(t,n)}},a.handleApplicationData=function(e,t){e.data.putBuffer(t.fragment),e.dataReady(e),e.process()};var f=0,l=1,c=2,h=3,p=4,d=5,v=6,m=7,g=8,y=0,b=1,w=2,E=3,S=4,x=5,T=6,N=7,C=a.handleUnexpected,k=a.handleChangeCipherSpec,L=a.handleAlert,A=a.handleHandshake,O=a.handleApplicationData,M=[];M[a.ConnectionEnd.client]=[[C,L,A,C],[C,L,A,C],[C,L,A,C],[C,L,A,C],[C,L,A,C],[k,L,C,C],[C,L,A,C],[C,L,A,O],[C,L,A,C]],M[a.ConnectionEnd.server]=[[C,L,A,C],[C,L,A,C],[C,L,A,C],[C,L,A,C],[k,L,C,C],[C,L,A,C],[C,L,A,O],[C,L,A,C]];var _=a.handleHelloRequest,D=a.handleServerHello,P=a.handleCertificate,H=a.handleServerKeyExchange,B=a.handleCertificateRequest,j=a.handleServerHelloDone,F=a.handleFinished,I=[];I[a.ConnectionEnd.client]=[[C,C,D,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C],[_,C,C,C,C,C,C,C,C,C,C,P,H,B,j,C,C,C,C,C,C],[_,C,C,C,C,C,C,C,C,C,C,C,H,B,j,C,C,C,C,C,C],[_,C,C,C,C,C,C,C,C,C,C,C,C,B,j,C,C,C,C,C,C],[_,C,C,C,C,C,C,C,C,C,C,C,C,C,j,C,C,C,C,C,C],[_,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C],[_,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,F],[_,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C],[_,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C]];var q=a.handleClientHello,R=a.handleClientKeyExchange,U=a.handleCertificateVerify;I[a.ConnectionEnd.server]=[[C,q,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C],[C,C,C,C,C,C,C,C,C,C,C,P,C,C,C,C,C,C,C,C,C],[C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,R,C,C,C,C],[C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,U,C,C,C,C,C],[C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C],[C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,F],[C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C],[C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C]],a.generateKeys=function(e,n){var r=t,i=n.client_random+n.server_random;e.session.resuming||(n.master_secret=r(n.pre_master_secret,"master secret",i,48).bytes(),n.pre_master_secret=null),i=n.server_random+n.client_random;var s=2*n.mac_key_length+2*n.enc_key_length+2*n.fixed_iv_length,o=r(n.master_secret,"key expansion",i,s);return{client_write_MAC_key:o.getBytes(n.mac_key_length),server_write_MAC_key:o.getBytes(n.mac_key_length),client_write_key:o.getBytes(n.enc_key_length),server_write_key:o.getBytes(n.enc_key_length),client_write_IV:o.getBytes(n.fixed_iv_length),server_write_IV:o.getBytes(n.fixed_iv_length)}},a.createConnectionState=function(e){var t=e.entity===a.ConnectionEnd.client,n=function(){var e={sequenceNumber:[0,0],macKey:null,macLength:0,macFunction:null,cipherState:null,cipherFunction:function(e){return!0},compressionState:null,compressFunction:function(e){return!0},updateSequenceNumber:function(){e.sequenceNumber[1]===4294967295?(e.sequenceNumber[1]=0,++e.sequenceNumber[0]):++e.sequenceNumber[1]}};return e},r={read:n(),write:n()};r.read.update=function(e,t){return r.read.cipherFunction(t,r.read)?r.read.compressFunction(e,t,r.read)||e.error(e,{message:"Could not decompress record.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.decompression_failure}}):e.error(e,{message:"Could not decrypt record or bad MAC.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.bad_record_mac}}),!e.fail},r.write.update=function(e,t){return r.write.compressFunction(e,t,r.write)?r.write.cipherFunction(t,r.write)||e.error(e,{message:"Could not encrypt record.",send:!1,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.internal_error}}):e.error(e,{message:"Could not compress record.",send:!1,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.internal_error}}),!e.fail};if(e.session){var o=e.session.sp;e.session.cipherSuite.initSecurityParameters(o),o.keys=a.generateKeys(e,o),r.read.macKey=t?o.keys.server_write_MAC_key:o.keys.client_write_MAC_key,r.write.macKey=t?o.keys.client_write_MAC_key:o.keys.server_write_MAC_key,e.session.cipherSuite.initConnectionState(r,e,o);switch(o.compression_algorithm){case a.CompressionMethod.none:break;case a.CompressionMethod.deflate:r.read.compressFunction=s,r.write.compressFunction=i;break;default:throw{message:"Unsupported compression algorithm."}}}return r},a.createRandom=function(){var t=new Date,n=+t+t.getTimezoneOffset()*6e4,r=e.util.createBuffer();return r.putInt32(n),r.putBytes(e.random.getBytes(28)),r},a.createRecord=function(e){if(!e.data)return null;var t={type:e.type,version:{major:a.Version.major,minor:a.Version.minor},length:e.data.length(),fragment:e.data};return t},a.createAlert=function(t){var n=e.util.createBuffer();return n.putByte(t.level),n.putByte(t.description),a.createRecord({type:a.ContentType.alert,data:n})},a.createClientHello=function(t){var n=e.util.createBuffer();for(var r=0;r<t.cipherSuites.length;++r){var i=t.cipherSuites[r];n.putByte(i.id[0]),n.putByte(i.id[1])}var s=n.length(),o=e.util.createBuffer();o.putByte(a.CompressionMethod.none);var f=o.length(),l=e.util.createBuffer();if(t.virtualHost){var c=e.util.createBuffer();c.putByte(0),c.putByte(0);var h=e.util.createBuffer();h.putByte(0),u(h,2,e.util.createBuffer(t.virtualHost));var p=e.util.createBuffer();u(p,2,h),u(c,2,p),l.putBuffer(c)}var d=l.length();d>0&&(d+=2);var v=t.session.id,m=v.length+1+2+4+28+2+s+1+f+d,g=e.util.createBuffer();return g.putByte(a.HandshakeType.client_hello),g.putInt24(m),g.putByte(a.Version.major),g.putByte(a.Version.minor),g.putBytes(t.session.sp.client_random),u(g,1,e.util.createBuffer(v)),u(g,2,n),u(g,1,o),d>0&&u(g,2,l),g},a.createServerHello=function(t){var n=t.session.id,r=n.length+1+2+4+28+2+1,i=e.util.createBuffer();return i.putByte(a.HandshakeType.server_hello),i.putInt24(r),i.putByte(a.Version.major),i.putByte(a.Version.minor),i.putBytes(t.session.sp.server_random),u(i,1,e.util.createBuffer(n)),i.putByte(t.session.cipherSuite.id[0]),i.putByte(t.session.cipherSuite.id[1]),i.putByte(t.session.compressionMethod),i},a.createCertificate=function(t){var n=t.entity===a.ConnectionEnd.client,r=null;t.getCertificate&&(r=t.getCertificate(t,n?t.session.certificateRequest:t.session.serverNameList));var i=e.util.createBuffer();if(r!==null)try{e.util.isArray(r)||(r=[r]);var s=null;for(var o=0;o<r.length;++o){var f=e.pem.decode(r[o])[0];if(f.type!=="CERTIFICATE"&&f.type!=="X509 CERTIFICATE"&&f.type!=="TRUSTED CERTIFICATE")throw{message:'Could not convert certificate from PEM; PEM header type is not "CERTIFICATE", "X509 CERTIFICATE", or "TRUSTED CERTIFICATE".',headerType:f.type};if(f.procType&&f.procType.type==="ENCRYPTED")throw{message:"Could not convert certificate from PEM; PEM is encrypted."};var l=e.util.createBuffer(f.body);s===null&&(s=e.asn1.fromDer(l.bytes(),!1));var c=e.util.createBuffer();u(c,3,l),i.putBuffer(c)}r=e.pki.certificateFromAsn1(s),n?t.session.clientCertificate=r:t.session.serverCertificate=r}catch(h){return t.error(t,{message:"Could not send certificate list.",cause:h,send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.bad_certificate}})}var p=3+i.length(),d=e.util.createBuffer();return d.putByte(a.HandshakeType.certificate),d.putInt24(p),u(d,3,i),d},a.createClientKeyExchange=function(t){var n=e.util.createBuffer();n.putByte(a.Version.major),n.putByte(a.Version.minor),n.putBytes(e.random.getBytes(46));var r=t.session.sp;r.pre_master_secret=n.getBytes();var i=t.session.serverCertificate.publicKey;n=i.encrypt(r.pre_master_secret);var s=n.length+2,o=e.util.createBuffer();return o.putByte(a.HandshakeType.client_key_exchange),o.putInt24(s),o.putInt16(n.length),o.putBytes(n),o},a.createServerKeyExchange=function(t){var n=0,r=e.util.createBuffer();return n>0&&(r.putByte(a.HandshakeType.server_key_exchange),r.putInt24(n)),r},a.getClientSignature=function(t,n){var r=e.util.createBuffer();r.putBuffer(t.session.md5.digest()),r.putBuffer(t.session.sha1.digest()),r=r.getBytes(),t.getSignature=t.getSignature||function(t,n,r){var i=null;if(t.getPrivateKey)try{i=t.getPrivateKey(t,t.session.clientCertificate),i=e.pki.privateKeyFromPem(i)}catch(s){t.error(t,{message:"Could not get private key.",cause:s,send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.internal_error}})}i===null?t.error(t,{message:"No private key set.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.internal_error}}):n=i.sign(n,null),r(t,n)},t.getSignature(t,r,n)},a.createCertificateVerify=function(t,n){var r=n.length+2,i=e.util.createBuffer();return i.putByte(a.HandshakeType.certificate_verify),i.putInt24(r),i.putInt16(n.length),i.putBytes(n),i},a.createCertificateRequest=function(t){var n=e.util.createBuffer();n.putByte(1);var r=e.util.createBuffer();for(var i in t.caStore.certs){var s=t.caStore.certs[i],o=e.pki.distinguishedNameToAsn1(s.subject);r.putBuffer(e.asn1.toDer(o))}var f=1+n.length()+2+r.length(),l=e.util.createBuffer();return l.putByte(a.HandshakeType.certificate_request),l.putInt24(f),u(l,1,n),u(l,2,r),l},a.createServerHelloDone=function(t){var n=e.util.createBuffer();return n.putByte(a.HandshakeType.server_hello_done),n.putInt24(0),n},a.createChangeCipherSpec=function(){var t=e.util.createBuffer();return t.putByte(1),t},a.createFinished=function(n){var r=e.util.createBuffer();r.putBuffer(n.session.md5.digest()),r.putBuffer(n.session.sha1.digest());var i=n.entity===a.ConnectionEnd.client,s=n.session.sp,o=12,u=t,f=i?"client finished":"server finished";r=u(s.master_secret,f,r.getBytes(),o);var l=e.util.createBuffer();return l.putByte(a.HandshakeType.finished),l.putInt24(r.length()),l.putBuffer(r),l},a.queue=function(t,n){if(!n)return;if(n.type===a.ContentType.handshake){var r=n.fragment.bytes();t.session.md5.update(r),t.session.sha1.update(r),r=null}var i;if(n.fragment.length()<=a.MaxFragment)i=[n];else{i=[];var s=n.fragment.bytes();while(s.length>a.MaxFragment)i.push(a.createRecord({type:n.type,data:e.util.createBuffer(s.slice(0,a.MaxFragment))})),s=s.slice(a.MaxFragment);s.length>0&&i.push(a.createRecord({type:n.type,data:e.util.createBuffer(s)}))}for(var o=0;o<i.length&&!t.fail;++o){var u=i[o],f=t.state.current.write;f.update(t,u)&&t.records.push(u)}},a.flush=function(e){for(var t=0;t<e.records.length;++t){var n=e.records[t];e.tlsData.putByte(n.type),e.tlsData.putByte(n.version.major),e.tlsData.putByte(n.version.minor),e.tlsData.putInt16(n.fragment.length()),e.tlsData.putBuffer(e.records[t].fragment)}return e.records=[],e.tlsDataReady(e)};var z=function(t){switch(t){case!0:return!0;case e.pki.certificateError.bad_certificate:return a.Alert.Description.bad_certificate;case e.pki.certificateError.unsupported_certificate:return a.Alert.Description.unsupported_certificate;case e.pki.certificateError.certificate_revoked:return a.Alert.Description.certificate_revoked;case e.pki.certificateError.certificate_expired:return a.Alert.Description.certificate_expired;case e.pki.certificateError.certificate_unknown:return a.Alert.Description.certificate_unknown;case e.pki.certificateError.unknown_ca:return a.Alert.Description.unknown_ca;default:return a.Alert.Description.bad_certificate}},W=function(t){switch(t){case!0:return!0;case a.Alert.Description.bad_certificate:return e.pki.certificateError.bad_certificate;case a.Alert.Description.unsupported_certificate:return e.pki.certificateError.unsupported_certificate;case a.Alert.Description.certificate_revoked:return e.pki.certificateError.certificate_revoked;case a.Alert.Description.certificate_expired:return e.pki.certificateError.certificate_expired;case a.Alert.Description.certificate_unknown:return e.pki.certificateError.certificate_unknown;case a.Alert.Description.unknown_ca:return e.pki.certificateError.unknown_ca;default:return e.pki.certificateError.bad_certificate}};a.verifyCertificateChain=function(t,n){try{e.pki.verifyCertificateChain(t.caStore,n,function(r,i,s){var o=z(r),u=t.verify(t,r,i,s);if(u!==!0){if(typeof u=="object"&&!e.util.isArray(u)){var f={message:"The application rejected the certificate.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.bad_certificate}};throw u.message&&(f.message=u.message),u.alert&&(f.alert.description=u.alert),f}u!==r&&(u=W(u))}return u})}catch(r){if(typeof r!="object"||e.util.isArray(r))r={send:!0,alert:{level:a.Alert.Level.fatal,description:z(r)}};"send"in r||(r.send=!0),"alert"in r||(r.alert={level:a.Alert.Level.fatal,description:z(r.error)}),t.error(t,r)}return!t.fail},a.createSessionCache=function(t,n){var r=null;if(t&&t.getSession&&t.setSession&&t.order)r=t;else{r={},r.cache=t||{},r.capacity=Math.max(n||100,1),r.order=[];for(var i in t)r.order.length<=n?r.order.push(i):delete t[i];r.getSession=function(t){var n=null,i=null;t?i=e.util.bytesToHex(t):r.order.length>0&&(i=r.order[0]);if(i!==null&&i in r.cache){n=r.cache[i],delete r.cache[i];for(var s in r.order)if(r.order[s]===i){r.order.splice(s,1);break}}return n},r.setSession=function(t,n){if(r.order.length===r.capacity){var i=r.order.shift();delete r.cache[i]}var i=e.util.bytesToHex(t);r.order.push(i),r.cache[i]=n}}return r},a.createConnection=function(t){var n=null;t.caStore?e.util.isArray(t.caStore)?n=e.pki.createCaStore(t.caStore):n=t.caStore:n=e.pki.createCaStore();var r=t.cipherSuites||null;if(r===null){r=[];for(var i in a.CipherSuites)r.push(a.CipherSuites[i])}var s=t.server||!1?a.ConnectionEnd.server:a.ConnectionEnd.client,o=t.sessionCache?a.createSessionCache(t.sessionCache):null,u={entity:s,sessionId:t.sessionId,caStore:n,sessionCache:o,cipherSuites:r,connected:t.connected,virtualHost:t.virtualHost||null,verifyClient:t.verifyClient||!1,verify:t.verify||function(e,t,n,r){return t},getCertificate:t.getCertificate||null,getPrivateKey:t.getPrivateKey||null,getSignature:t.getSignature||null,input:e.util.createBuffer(),tlsData:e.util.createBuffer(),data:e.util.createBuffer(),tlsDataReady:t.tlsDataReady,dataReady:t.dataReady,closed:t.closed,error:function(e,n){n.origin=n.origin||(e.entity===a.ConnectionEnd.client?"client":"server"),n.send&&(a.queue(e,a.createAlert(n.alert)),a.flush(e));var r=n.fatal!==!1;r&&(e.fail=!0),t.error(e,n),r&&e.close(!1)},deflate:t.deflate||null,inflate:t.inflate||null};u.reset=function(e){u.record=null,u.session=null,u.peerCertificate=null,u.state={pending:null,current:null},u.expect=u.entity===a.ConnectionEnd.client?f:y,u.fragmented=null,u.records=[],u.open=!1,u.handshakes=0,u.handshaking=!1,u.isConnected=!1,u.fail=!e&&typeof e!="undefined",u.input.clear(),u.tlsData.clear(),u.data.clear(),u.state.current=a.createConnectionState(u)},u.reset();var l=function(e,t){var n=t.type-a.ContentType.change_cipher_spec,r=M[e.entity][e.expect];n in r?r[n](e,t):a.handleUnexpected(e,t)},c=function(t){var n=0,r=t.input,i=r.length();return i<5?n=5-i:(t.record={type:r.getByte(),version:{major:r.getByte(),minor:r.getByte()},length:r.getInt16(),fragment:e.util.createBuffer(),ready:!1},(t.record.version.major!==a.Version.major||t.record.version.minor!==a.Version.minor)&&t.error(t,{message:"Incompatible TLS version.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.protocol_version}})),n},h=function(e){var t=0,n=e.input,r=n.length();if(r<e.record.length)t=e.record.length-r;else{e.record.fragment.putBytes(n.getBytes(e.record.length));var i=e.state.current.read;i.update(e,e.record)&&(e.fragmented!==null&&(e.fragmented.type===e.record.type?(e.fragmented.fragment.putBuffer(e.record.fragment),e.record=e.fragmented):e.error(e,{message:"Invalid fragmented record.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.unexpected_message}})),e.record.ready=!0)}return t};return u.handshake=function(t){if(u.entity!==a.ConnectionEnd.client)u.error(u,{message:"Cannot initiate handshake as a server.",fatal:!1});else if(u.handshaking)u.error(u,{message:"Handshake already in progress.",fatal:!1});else{u.fail&&!u.open&&u.handshakes===0&&(u.fail=!1),u.handshaking=!0,t=t||"";var n=null;t.length>0&&(u.sessionCache&&(n=u.sessionCache.getSession(t)),n===null&&(t="")),t.length===0&&u.sessionCache&&(n=u.sessionCache.getSession(),n!==null&&(t=n.id)),u.session={id:t,cipherSuite:null,compressionMethod:null,serverCertificate:null,certificateRequest:null,clientCertificate:null,sp:n?n.sp:{},md5:e.md.md5.create(),sha1:e.md.sha1.create()},u.session.sp.client_random=a.createRandom().getBytes(),u.open=!0,a.queue(u,a.createRecord({type:a.ContentType.handshake,data:a.createClientHello(u)})),a.flush(u)}},u.process=function(e){var t=0;return e&&u.input.putBytes(e),u.fail||(u.record!==null&&u.record.ready&&u.record.fragment.isEmpty()&&(u.record=null),u.record===null&&(t=c(u)),!u.fail&&u.record!==null&&!u.record.ready&&(t=h(u)),!u.fail&&u.record!==null&&u.record.ready&&l(u,u.record)),t},u.prepare=function(t){return a.queue(u,a.createRecord({type:a.ContentType.application_data,data:e.util.createBuffer(t)})),a.flush(u)},u.close=function(e){!u.fail&&u.sessionCache&&u.session&&u.sessionCache.setSession(u.session.id,u.session);if(u.open){u.open=!1,u.input.clear();if(u.isConnected||u.handshaking)u.isConnected=u.handshaking=!1,a.queue(u,a.createAlert({level:a.Alert.Level.warning,description:a.Alert.Description.close_notify})),a.flush(u);u.closed(u)}u.reset(e)},u},e.tls=e.tls||{};for(var X in a)typeof a[X]!="function"&&(e.tls[X]=a[X]);e.tls.prf_tls1=t,e.tls.hmac_sha1=r,e.tls.createSessionCache=a.createSessionCache,e.tls.createConnection=a.createConnection}var t="tls";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/tls",["require","module","./asn1","./hmac","./md","./pem","./pki","./random","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){function n(n,i,s){var u=i.entity===e.tls.ConnectionEnd.client;n.read.cipherState={init:!1,cipher:e.aes.createDecryptionCipher(u?s.keys.server_write_key:s.keys.client_write_key),iv:u?s.keys.server_write_IV:s.keys.client_write_IV},n.write.cipherState={init:!1,cipher:e.aes.createEncryptionCipher(u?s.keys.client_write_key:s.keys.server_write_key),iv:u?s.keys.client_write_IV:s.keys.server_write_IV},n.read.cipherFunction=o,n.write.cipherFunction=r,n.read.macLength=n.write.macLength=s.mac_length,n.read.macFunction=n.write.macFunction=t.hmac_sha1}function r(t,n){var r=!1,s=n.macFunction(n.macKey,n.sequenceNumber,t);t.fragment.putBytes(s),n.updateSequenceNumber();var o;t.version.minor>1?o=e.random.getBytes(16):o=n.cipherState.init?null:n.cipherState.iv,n.cipherState.init=!0;var u=n.cipherState.cipher;return u.start(o),t.version.minor>1&&u.output.putBytes(o),u.update(t.fragment),u.finish(i)&&(t.fragment=u.output,t.length=t.fragment.length(),r=!0),r}function i(e,t,n){if(!n){var r=e-t.length()%e;t.fillWithByte(r-1,r)}return!0}function s(e,t,n){var r=!0;if(n){var i=t.length(),s=t.last();for(var o=i-1-s;o<i-1;++o)r=r&&t.at(o)==s;r&&t.truncate(s+1)}return r}function o(t,n){var r=!1,i=n.cipherState.init?null:n.cipherState.iv;n.cipherState.init=!0;var o=n.cipherState.cipher;o.start(i),o.update(t.fragment),r=o.finish(s);var u=n.macLength,a="";for(var f=0;f<u;++f)a+=String.fromCharCode(0);var l=o.output.length();l>=u?(t.fragment=o.output.getBytes(l-u),a=o.output.getBytes(u)):t.fragment=o.output.getBytes(),t.fragment=e.util.createBuffer(t.fragment),t.length=t.fragment.length();var c=n.macFunction(n.macKey,n.sequenceNumber,t);return n.updateSequenceNumber(),r=c===a&&r,r}var t=e.tls;t.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA={id:[0,47],name:"TLS_RSA_WITH_AES_128_CBC_SHA",initSecurityParameters:function(e){e.bulk_cipher_algorithm=t.BulkCipherAlgorithm.aes,e.cipher_type=t.CipherType.block,e.enc_key_length=16,e.block_length=16,e.fixed_iv_length=16,e.record_iv_length=16,e.mac_algorithm=t.MACAlgorithm.hmac_sha1,e.mac_length=20,e.mac_key_length=20},initConnectionState:n},t.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA={id:[0,53],name:"TLS_RSA_WITH_AES_256_CBC_SHA",initSecurityParameters:function(e){e.bulk_cipher_algorithm=t.BulkCipherAlgorithm.aes,e.cipher_type=t.CipherType.block,e.enc_key_length=32,e.block_length=16,e.fixed_iv_length=16,e.record_iv_length=16,e.mac_algorithm=t.MACAlgorithm.hmac_sha1,e.mac_length=20,e.mac_key_length=20},initConnectionState:n}}var t="aesCipherSuites";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/aesCipherSuites",["require","module","./aes","./tls"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){e.debug=e.debug||{},e.debug.storage={},e.debug.get=function(t,n){var r;return typeof t=="undefined"?r=e.debug.storage:t in e.debug.storage&&(typeof n=="undefined"?r=e.debug.storage[t]:r=e.debug.storage[t][n]),r},e.debug.set=function(t,n,r){t in e.debug.storage||(e.debug.storage[t]={}),e.debug.storage[t][n]=r},e.debug.clear=function(t,n){typeof t=="undefined"?e.debug.storage={}:t in e.debug.storage&&(typeof n=="undefined"?delete e.debug.storage[t]:delete e.debug.storage[t][n])}}var t="debug";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/debug",["require","module"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){e.log=e.log||{},e.log.levels=["none","error","warning","info","debug","verbose","max"];var t={},n=[],r=null;e.log.LEVEL_LOCKED=2,e.log.NO_LEVEL_CHECK=4,e.log.INTERPOLATE=8;for(var i=0;i<e.log.levels.length;++i){var s=e.log.levels[i];t[s]={index:i,name:s.toUpperCase()}}e.log.logMessage=function(r){var i=t[r.level].index;for(var s=0;s<n.length;++s){var o=n[s];if(o.flags&e.log.NO_LEVEL_CHECK)o.f(r);else{var u=t[o.level].index;i<=u&&o.f(o,r)}}},e.log.prepareStandard=function(e){"standard"in e||(e.standard=t[e.level].name+" ["+e.category+"] "+e.message)},e.log.prepareFull=function(t){if(!("full"in t)){var n=[t.message];n=n.concat([]||t.arguments),t.full=e.util.format.apply(this,n)}},e.log.prepareStandardFull=function(t){"standardFull"in t||(e.log.prepareStandard(t),t.standardFull=t.standard)};var o=["error","warning","info","debug","verbose"];for(var i=0;i<o.length;++i)(function(t){e.log[t]=function(n,r){var i=Array.prototype.slice.call(arguments).slice(2),s={timestamp:new Date,level:t,category:n,message:r,arguments:i};e.log.logMessage(s)}})(o[i]);e.log.makeLogger=function(t){var n={flags:0,f:t};return e.log.setLevel(n,"none"),n},e.log.setLevel=function(t,n){var r=!1;if(t&&!(t.flags&e.log.LEVEL_LOCKED))for(var i=0;i<e.log.levels.length;++i){var s=e.log.levels[i];if(n==s){t.level=n,r=!0;break}}return r},e.log.lock=function(t,n){typeof n=="undefined"||n?t.flags|=e.log.LEVEL_LOCKED:t.flags&=~e.log.LEVEL_LOCKED},e.log.addLogger=function(e){n.push(e)};if(typeof console!="undefined"&&"log"in console){var u;if(console.error&&console.warn&&console.info&&console.debug){var a={error:console.error,warning:console.warn,info:console.info,debug:console.debug,verbose:console.debug},f=function(t,n){e.log.prepareStandard(n);var r=a[n.level],i=[n.standard];i=i.concat(n.arguments.slice()),r.apply(console,i)};u=e.log.makeLogger(f)}else{var f=function(t,n){e.log.prepareStandardFull(n),console.log(n.standardFull)};u=e.log.makeLogger(f)}e.log.setLevel(u,"debug"),e.log.addLogger(u),r=u}else console={log:function(){}};if(r!==null){var l=e.util.getQueryVariables();"console.level"in l&&e.log.setLevel(r,l["console.level"].slice(-1)[0]);if("console.lock"in l){var c=l["console.lock"].slice(-1)[0];c=="true"&&e.log.lock(r)}}e.log.consoleLogger=r}var t="log";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/log",["require","module","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){var t="forge.task",n=0,r={},i=0;e.debug.set(t,"tasks",r);var s={};e.debug.set(t,"queues",s);var o="?",u=30,a=20,f="ready",l="running",c="blocked",h="sleeping",p="done",d="error",v="stop",m="start",g="block",y="unblock",b="sleep",w="wakeup",E="cancel",S="fail",x={};x[f]={},x[f][v]=f,x[f][m]=l,x[f][E]=p,x[f][S]=d,x[l]={},x[l][v]=f,x[l][m]=l,x[l][g]=c,x[l][y]=l,x[l][b]=h,x[l][w]=l,x[l][E]=p,x[l][S]=d,x[c]={},x[c][v]=c,x[c][m]=c,x[c][g]=c,x[c][y]=c,x[c][b]=c,x[c][w]=c,x[c][E]=p,x[c][S]=d,x[h]={},x[h][v]=h,x[h][m]=h,x[h][g]=h,x[h][y]=h,x[h][b]=h,x[h][w]=h,x[h][E]=p,x[h][S]=d,x[p]={},x[p][v]=p,x[p][m]=p,x[p][g]=p,x[p][y]=p,x[p][b]=p,x[p][w]=p,x[p][E]=p,x[p][S]=d,x[d]={},x[d][v]=d,x[d][m]=d,x[d][g]=d,x[d][y]=d,x[d][b]=d,x[d][w]=d,x[d][E]=d,x[d][S]=d;var T=function(s){this.id=-1,this.name=s.name||o,this.parent=s.parent||null,this.run=s.run,this.subtasks=[],this.error=!1,this.state=f,this.blocks=0,this.timeoutId=null,this.swapTime=null,this.userData=null,this.id=i++,r[this.id]=this,n>=1&&e.log.verbose(t,"[%s][%s] init",this.id,this.name,this)};T.prototype.debug=function(n){n=n||"",e.log.debug(t,n,"[%s][%s] task:",this.id,this.name,this,"subtasks:",this.subtasks.length,"queue:",s)},T.prototype.next=function(e,t){typeof e=="function"&&(t=e,e=this.name);var n=new T({run:t,name:e,parent:this});return n.state=l,n.type=this.type,n.successCallback=this.successCallback||null,n.failureCallback=this.failureCallback||null,this.subtasks.push(n),this},T.prototype.parallel=function(t,n){return e.util.isArray(t)&&(n=t,t=this.name),this.next(t,function(r){var i=r;i.block(n.length);var s=function(t,r){e.task.start({type:t,run:function(e){n[r](e)},success:function(e){i.unblock()},failure:function(e){i.unblock()}})};for(var o=0;o<n.length;o++){var u=t+"__parallel-"+r.id+"-"+o,a=o;s(u,a)}})},T.prototype.stop=function(){this.state=x[this.state][v]},T.prototype.start=function(){this.error=!1,this.state=x[this.state][m],this.state===l&&(this.start=new Date,this.run(this),C(this,0))},T.prototype.block=function(e){e=typeof e=="undefined"?1:e,this.blocks+=e,this.blocks>0&&(this.state=x[this.state][g])},T.prototype.unblock=function(e){return e=typeof e=="undefined"?1:e,this.blocks-=e,this.blocks===0&&this.state!==p&&(this.state=l,C(this,0)),this.blocks},T.prototype.sleep=function(e){e=typeof e=="undefined"?0:e,this.state=x[this.state][b];var t=this;this.timeoutId=setTimeout(function(){t.timeoutId=null,t.state=l,C(t,0)},e)},T.prototype.wait=function(e){e.wait(this)},T.prototype.wakeup=function(){this.state===h&&(cancelTimeout(this.timeoutId),this.timeoutId=null,this.state=l,C(this,0))},T.prototype.cancel=function(){this.state=x[this.state][E],this.permitsNeeded=0,this.timeoutId!==null&&(cancelTimeout(this.timeoutId),this.timeoutId=null),this.subtasks=[]},T.prototype.fail=function(e){this.error=!0,k(this,!0);if(e)e.error=this.error,e.swapTime=this.swapTime,e.userData=this.userData,C(e,0);else{if(this.parent!==null){var t=this.parent;while(t.parent!==null)t.error=this.error,t.swapTime=this.swapTime,t.userData=this.userData,t=t.parent;k(t,!0)}this.failureCallback&&this.failureCallback(this)}};var N=function(e){e.error=!1,e.state=x[e.state][m],setTimeout(function(){e.state===l&&(e.swapTime=+(new Date),e.run(e),C(e,0))},0)},C=function(e,t){var n=t>u||+(new Date)-e.swapTime>a,r=function(t){t++;if(e.state===l){n&&(e.swapTime=+(new Date));if(e.subtasks.length>0){var r=e.subtasks.shift();r.error=e.error,r.swapTime=e.swapTime,r.userData=e.userData,r.run(r),r.error||C(r,t)}else k(e),e.error||e.parent!==null&&(e.parent.error=e.error,e.parent.swapTime=e.swapTime,e.parent.userData=e.userData,C(e.parent,t))}};n?setTimeout(r,0):r(t)},k=function(i,o){i.state=p,delete r[i.id],n>=1&&e.log.verbose(t,"[%s][%s] finish",i.id,i.name,i),i.parent===null&&(i.type in s?s[i.type].length===0?e.log.error(t,"[%s][%s] task queue empty [%s]",i.id,i.name,i.type):s[i.type][0]!==i?e.log.error(t,"[%s][%s] task not first in queue [%s]",i.id,i.name,i.type):(s[i.type].shift(),s[i.type].length===0?(n>=1&&e.log.verbose(t,"[%s][%s] delete queue [%s]",i.id,i.name,i.type),delete s[i.type]):(n>=1&&e.log.verbose(t,"[%s][%s] queue start next [%s] remain:%s",i.id,i.name,i.type,s[i.type].length),s[i.type][0].start())):e.log.error(t,"[%s][%s] task queue missing [%s]",i.id,i.name,i.type),o||(i.error&&i.failureCallback?i.failureCallback(i):!i.error&&i.successCallback&&i.successCallback(i)))};e.task=e.task||{},e.task.start=function(r){var i=new T({run:r.run,name:r.name||o});i.type=r.type,i.successCallback=r.success||null,i.failureCallback=r.failure||null,i.type in s?s[r.type].push(i):(n>=1&&e.log.verbose(t,"[%s][%s] create queue [%s]",i.id,i.name,i.type),s[i.type]=[i],N(i))},e.task.cancel=function(e){e in s&&(s[e]=[s[e][0]])},e.task.createCondition=function(){var e={tasks:{}};return e.wait=function(t){t.id in e.tasks||(t.block(),e.tasks[t.id]=t)},e.notify=function(){var t=e.tasks;e.tasks={};for(var n in t)t[n].unblock()},e}}var t="task";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/task",["require","module","./debug","./log","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){var e="forge";if(typeof define!="function"){if(typeof module!="object"||!module.exports){typeof forge=="undefined"&&(forge={disableNativeCode:!1});return}var t=!0;define=function(e,t){t(require,module)}}var n,r=function(t,r){r.exports=function(r){var i=n.map(function(e){return t(e)});r=r||{},r.defined=r.defined||{};if(r.defined[e])return r[e];r.defined[e]=!0;for(var s=0;s<i.length;++s)i[s](r);return r},r.exports.disableNativeCode=!1,r.exports(r.exports)},i=define;define=function(e,r){return n=typeof e=="string"?r.slice(2):e.slice(2),t?(delete define,i.apply(null,Array.prototype.slice.call(arguments,0))):(define=i,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/forge",["require","module","./aes","./aesCipherSuites","./asn1","./debug","./des","./hmac","./log","./pbkdf2","./pem","./pkcs7","./pkcs1","./pkcs12","./pki","./prng","./pss","./random","./rc2","./task","./tls","./util","./md","./mgf1"],function(){r.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e,t){describe("tls",function(){it("should test TLS 1.0 PRF",function(){var n=t.util.createBuffer().fillWithByte(171,48).getBytes(),r=t.util.createBuffer().fillWithByte(205,64).getBytes(),i=t.tls.prf_tls1(n,"PRF Testvector",r,104),s="d3d4d1e349b5d515044666d51de32bab258cb521b6b053463e354832fd976754443bcf9a296519bc289abcbc1187e4ebd31e602353776c408aafb74cbc85eff69255f9788faa184cbb957a9819d84a5d7eb006eb459d3ae8de9810454b8b2d8f1afbc655a8c9a013";e.equal(i.toHex(),s)}),it("should establish a TLS connection and transfer data",function(n){function s(e,n){var r=t.pki.rsa.generateKeyPair(512),i=t.pki.createCertificate();i.publicKey=r.publicKey,i.serialNumber="01",i.validity.notBefore=new Date,i.validity.notAfter=new Date,i.validity.notAfter.setFullYear(i.validity.notBefore.getFullYear()+1);var s=[{name:"commonName",value:e},{name:"countryName",value:"US"},{shortName:"ST",value:"Virginia"},{name:"localityName",value:"Blacksburg"},{name:"organizationName",value:"Test"},{shortName:"OU",value:"Test"}];i.setSubject(s),i.setIssuer(s),i.setExtensions([{name:"basicConstraints",cA:!0},{name:"keyUsage",keyCertSign:!0,digitalSignature:!0,nonRepudiation:!0,keyEncipherment:!0,dataEncipherment:!0},{name:"subjectAltName",altNames:[{type:6,value:"https://myuri.com/webid#me"}]}]),i.sign(r.privateKey),n[e]={cert:t.pki.certificateToPem(i),privateKey:t.pki.privateKeyToPem(r.privateKey)}}var r={},i={};s("server",i),s("client",i),i.client.connection={},i.server.connection={},r.client=t.tls.createConnection({server:!1,caStore:[i.server.cert],sessionCache:{},cipherSuites:[t.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,t.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],virtualHost:"server",verify:function(e,t,n,r){return i.client.connection.commonName=r[0].subject.getField("CN").value,i.client.connection.certVerified=t,!0},connected:function(e){e.prepare("Hello Server")},getCertificate:function(e,t){return i.client.cert},getPrivateKey:function(e,t){return i.client.privateKey},tlsDataReady:function(e){r.server.process(e.tlsData.getBytes())},dataReady:function(e){i.client.connection.data=e.data.getBytes(),e.close()},closed:function(t){e.equal(i.client.connection.commonName,"server"),e.equal(i.client.connection.certVerified,!0),e.equal(i.client.connection.data,"Hello Client"),n()},error:function(t,n){e.equal(n.message,undefined)}}),r.server=t.tls.createConnection({server:!0,caStore:[i.client.cert],sessionCache:{},cipherSuites:[t.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,t.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],connected:function(e){},verifyClient:!0,verify:function(e,t,n,r){return i.server.connection.commonName=r[0].subject.getField("CN").value,i.server.connection.certVerified=t,!0},getCertificate:function(e,t){return i.server.connection.certHint=t[0],i.server.cert},getPrivateKey:function(e,t){return i.server.privateKey},tlsDataReady:function(e){r.client.process(e.tlsData.getBytes())},dataReady:function(e){i.server.connection.data=e.data.getBytes(),e.prepare("Hello Client"),e.close()},closed:function(t){e.equal(i.server.connection.certHint,"server"),e.equal(i.server.connection.commonName,"client"),e.equal(i.server.connection.certVerified,!0),e.equal(i.server.connection.data,"Hello Server")},error:function(t,n){e.equal(n.message,undefined)}}),r.client.handshake()})})}typeof define=="function"?define("test/tls",["forge/forge"],function(t){e(ASSERT,t)}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/forge"))}();var ASSERT=chai.assert;mocha.setup({ui:"bdd"}),requirejs.config({paths:{forge:"forge",test:"test"}}),requirejs(["test/util","test/md5","test/sha1","test/sha256","test/hmac","test/pbkdf2","test/mgf1","test/random","test/asn1","test/pem","test/rsa","test/pkcs1","test/x509","test/csr","test/aes","test/rc2","test/des","test/pkcs7","test/tls"],function(){mocha.run()}),define("ui/test.js",function(){});
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/package.json b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/package.json
new file mode 100644
index 0000000..9de0a11
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/package.json
@@ -0,0 +1,113 @@
+{
+  "name": "node-forge",
+  "version": "0.6.21",
+  "description": "JavaScript implementations of network transports, cryptography, ciphers, PKI, message digests, and various utilities.",
+  "homepage": "http://github.com/digitalbazaar/forge",
+  "author": {
+    "name": "Digital Bazaar, Inc.",
+    "email": "support@digitalbazaar.com",
+    "url": "http://digitalbazaar.com/"
+  },
+  "contributors": [
+    {
+      "name": "Dave Longley",
+      "email": "dlongley@digitalbazaar.com"
+    },
+    {
+      "name": "Stefan Siegl",
+      "email": "stesie@brokenpipe.de"
+    },
+    {
+      "name": "Christoph Dorn",
+      "email": "christoph@christophdorn.com"
+    }
+  ],
+  "devDependencies": {
+    "almond": "~0.2.6",
+    "jscs": "^1.8.1",
+    "requirejs": "~2.1.8"
+  },
+  "repository": {
+    "type": "git",
+    "url": "http://github.com/digitalbazaar/forge"
+  },
+  "bugs": {
+    "url": "https://github.com/digitalbazaar/forge/issues",
+    "email": "support@digitalbazaar.com"
+  },
+  "licenses": [
+    {
+      "type": "BSD",
+      "url": "https://github.com/digitalbazaar/forge/raw/master/LICENSE"
+    }
+  ],
+  "main": "js/forge.js",
+  "engines": {
+    "node": "*"
+  },
+  "keywords": [
+    "aes",
+    "asn",
+    "asn.1",
+    "cbc",
+    "crypto",
+    "cryptography",
+    "csr",
+    "des",
+    "gcm",
+    "hmac",
+    "http",
+    "https",
+    "md5",
+    "network",
+    "pkcs",
+    "pki",
+    "prng",
+    "rc2",
+    "rsa",
+    "sha1",
+    "sha256",
+    "sha384",
+    "sha512",
+    "ssh",
+    "tls",
+    "x.509",
+    "x509"
+  ],
+  "scripts": {
+    "bundle": "r.js -o minify.js optimize=none out=js/forge.bundle.js",
+    "minify": "r.js -o minify.js",
+    "jscs": "jscs *.js js/*.js minify.js nodejs/*.js nodejs/test/*.js nodejs/ui/*.js tests/*.js",
+    "jshint": "jshint *.js js/*.js minify.js nodejs/*.js nodejs/test/*.js nodejs/ui/*.js tests/*.js"
+  },
+  "gitHead": "875d46d0ef05f1b2a8a93b533a882c3a862f7fcf",
+  "_id": "node-forge@0.6.21",
+  "_shasum": "7dadde911be009c7aae9150e780aea21d4f8bd09",
+  "_from": "node-forge@^0.6.12",
+  "_npmVersion": "1.4.28",
+  "_npmUser": {
+    "name": "dlongley",
+    "email": "dlongley@digitalbazaar.com"
+  },
+  "maintainers": [
+    {
+      "name": "davidlehn",
+      "email": "dil@lehn.org"
+    },
+    {
+      "name": "dlongley",
+      "email": "dlongley@digitalbazaar.com"
+    },
+    {
+      "name": "msporny",
+      "email": "msporny@digitalbazaar.com"
+    }
+  ],
+  "dist": {
+    "shasum": "7dadde911be009c7aae9150e780aea21d4f8bd09",
+    "tarball": "http://registry.npmjs.org/node-forge/-/node-forge-0.6.21.tgz"
+  },
+  "directories": {},
+  "_resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.6.21.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/setup/configure.ac b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/setup/configure.ac
new file mode 100644
index 0000000..0d94441
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/setup/configure.ac
@@ -0,0 +1,202 @@
+# Configure script for Digital Bazaar Bitmunk product line
+# Usage: Run ./configure once 
+# Author: Manu Sporny
+
+AC_PREREQ([2.60])
+AC_INIT([forge],[0.1.0],[support@digitalbazaar.com])
+AC_CONFIG_AUX_DIR(setup)
+
+# Setup standard build environment variables
+# FIXME: allow changing via configure option
+FORGE_DIR=`(cd ${srcdir} && pwd)`
+AC_SUBST(FORGE_DIR)
+DATE_YMD=`date +%Y%m%d`
+PACKAGE_DATE_VERSION=${PACKAGE_VERSION}-${DATE_YMD}
+AC_SUBST(DATE_RFC_2822)
+AC_SUBST(PACKAGE_DATE_VERSION)
+
+dnl ----------------- docs -----------------
+
+AC_ARG_ENABLE([docs],
+   AS_HELP_STRING([--enable-docs], [build documentation [no]]),
+   [ 
+      case "${enableval}" in
+         yes) BUILD_DOCS=yes ;;
+         no) BUILD_DOCS=no ;;
+         *) AC_MSG_ERROR(bad value ${enableval} for --enable-docs) ;;
+      esac
+   ], [BUILD_DOCS=no]) dnl Default value
+
+AC_SUBST(BUILD_DOCS)
+
+dnl ----------------- tests -----------------
+
+AC_ARG_ENABLE([tests],
+   AC_HELP_STRING([--disable-tests], [disable building test apps [no]]),
+   [
+   case "${enableval}" in
+      yes) BUILD_TESTS=yes ;;
+      no)  BUILD_TESTS=no ;;
+      *)   AC_MSG_ERROR(bad value ${enableval} for --disable-tests) ;;
+   esac
+   ], [BUILD_TESTS=no]) dnl Default value
+
+AC_SUBST(BUILD_TESTS)
+
+dnl ----------------- build flash -----------------
+
+AC_ARG_ENABLE([flash],
+   AC_HELP_STRING([--disable-flash], [disable building Flash [no]]),
+   [
+   case "${enableval}" in
+      yes) BUILD_FLASH=yes ;;
+      no)  BUILD_FLASH=no ;;
+      *)   AC_MSG_ERROR(bad value ${enableval} for --disable-flash) ;;
+   esac
+   ], [BUILD_FLASH=yes]) dnl Default value
+
+AC_ARG_ENABLE([pre-built-flash],
+   AC_HELP_STRING([--disable-pre-built-flash],
+      [disable use of pre-built Flash [no]]),
+   [
+   case "${enableval}" in
+      yes) USE_PRE_BUILT_FLASH=yes ;;
+      no)  USE_PRE_BUILT_FLASH=no ;;
+      *)   AC_MSG_ERROR(bad value ${enableval} for --disable-flash) ;;
+   esac
+   ], [USE_PRE_BUILT_FLASH=yes]) dnl Default value
+
+AC_SUBST(BUILD_FLASH)
+AC_SUBST(USE_PRE_BUILT_FLASH)
+
+dnl ----------------- mxmlc -----------------
+
+AC_ARG_WITH([mxmlc],
+   AC_HELP_STRING([--with-mxmlc=PATH],
+      [use PATH for mxmlc]),
+   [
+      case "${withval}" in
+         yes|no) AC_MSG_ERROR(bad value ${withval} for --with-mxmlc) ;;
+         *)      MXMLC="${withval}" ;;
+      esac
+      if test "x$MXMLC" = x -o ! -x "$MXMLC"; then
+         AC_MSG_ERROR([mxmlc not found at "$MXMLC"])
+      fi
+   ])
+
+if test "$BUILD_FLASH" = "yes" ; then
+   dnl Need to try to find mxmlc
+   if test "x$MXMLC" = x; then
+      AC_CHECK_PROGS(MXMLC, mxmlc /usr/lib/flex3/bin/mxmlc,, $PATH /)
+   fi
+   dnl Check that mxmlc was found
+   if test "x$MXMLC" = x; then
+      if test "$USE_PRE_BUILT_FLASH" = "yes"; then
+         dnl Check pre-built SWF is present
+         if test -r "$FORGE_DIR/swf/SocketPool.swf"; then
+            AC_MSG_NOTICE([Using pre-built Flash])
+            BUILD_FLASH=no
+         else
+            AC_MSG_ERROR([mxmlc and pre-built Flash not found])
+         fi
+      else
+         AC_MSG_ERROR([mxmlc not found, try --with-mxmlc])
+      fi
+   fi
+fi
+
+AC_SUBST(MXMLC)
+
+dnl ----------------- mxmlc debug -----------------
+
+AC_ARG_ENABLE([mxmlc-debug],
+   AC_HELP_STRING([--enable-mxmlc-debug], [enable mxmlc debug mode [no]]),
+   [
+   case "${enableval}" in
+      yes) MXMLC_DEBUG_MODE=yes ;;
+      no)  MXMLC_DEBUG_MODE=no ;;
+      *)   AC_MSG_ERROR(bad value ${enableval} for --enable-mxmlc-debug) ;;
+   esac
+   ], [MXMLC_DEBUG_MODE=no]) dnl Default value
+
+AC_SUBST(MXMLC_DEBUG_MODE)
+
+dnl ----------------- end of options -----------------
+
+echo -e "\n--------- Configuring Build Environment -----------"
+
+PKG_PROG_PKG_CONFIG
+
+# Checking for standard build tools
+AC_PROG_CPP
+AC_PROG_INSTALL
+AS_PATH_PYTHON([2.7])
+
+if test "x$PYTHON" != x; then
+   save_CPPFLAGS="$CPPFLAGS"
+   AC_CHECK_PROGS([PYTHON_CONFIG], [python-config]) 
+   if test "x$PYTHON_CONFIG" != x; then
+      CPPFLAGS="$CPPFLAGS `$PYTHON_CONFIG --cflags`"
+   fi
+   AC_CHECK_HEADERS([Python.h],
+      [BUILD_PYTHON_MODULES=yes],
+      [BUILD_PYTHON_MODULES=no
+      AC_MSG_WARN([Python.h not found, SSL bindings will not be build.])])
+   CPPFLAGS="$save_CPPFLAGS"
+else
+   AC_MSG_WARN([Need at least Python 2.7 to build SSL bindings.])
+fi
+
+AC_SUBST(BUILD_PYTHON_MODULES)
+
+dnl ----------------------------------
+
+dnl NOTE:
+dnl This code was used previously to autogenerate the .gitignore file but due
+dnl to the current more common use of just the js files, it's likely people
+dnl who checkout the code will never run the build scripts. The files are now
+dnl just hardcoded into .gitignore and should be updated by hand as needed.
+dnl
+dnl # Generating files
+dnl AC_CONFIG_FILES([
+dnl    .gitignore
+dnl    Makefile
+dnl ])
+dnl
+dnl # add newlines to internal output file list
+dnl CONFIGURE_GENERATED_FILES="`echo $ac_config_files | tr ' ' '\n'`"
+dnl AC_SUBST(CONFIGURE_GENERATED_FILES)
+
+AC_OUTPUT
+
+# Dump the build configuration
+
+echo -e "\n--------- Forge Build Environment -----------"
+echo "Forge Version     : $PACKAGE_NAME $PACKAGE_DATE_VERSION"
+
+if test "x$BUILD_FLASH" = "xyes" ; then
+   echo "Adobe Flash       : Flash building enabled"
+   echo "MXMLC             : $MXMLC"
+   echo "MXMLC Debug flags : $MXMLC_DEBUG_MODE"
+else
+   echo "Adobe Flash       : using pre-built Flash"
+fi
+
+if test "x$BUILD_PYTHON_MODULES" = "xyes" ; then
+   echo "Python            : $PYTHON (version $PYTHON_VERSION)"
+else
+   echo "Python            : development environment not found"
+fi
+
+if test "x$BUILD_DOCS" = "xyes"; then
+   echo "Documentation     : enabled"
+else
+   echo "Documentation     : disabled (use --enable-docs to enable)"
+fi
+
+if test "x$BUILD_TESTS" = "xyes"; then
+   echo "Tests             : enabled"
+else
+   echo "Tests             : disabled (use --enable-tests to enable)"
+fi
+
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/setup/install-sh b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/setup/install-sh
new file mode 100755
index 0000000..d4744f0
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/setup/install-sh
@@ -0,0 +1,269 @@
+#!/bin/sh
+#
+# install - install a program, script, or datafile
+#
+# This originates from X11R5 (mit/util/scripts/install.sh), which was
+# later released in X11R6 (xc/config/util/install.sh) with the
+# following copyright and license.
+#
+# Copyright (C) 1994 X Consortium
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC-
+# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+# Except as contained in this notice, the name of the X Consortium shall not
+# be used in advertising or otherwise to promote the sale, use or other deal-
+# ings in this Software without prior written authorization from the X Consor-
+# tium.
+#
+#
+# FSF changes to this file are in the public domain.
+#
+# Calling this script install-sh is preferred over install.sh, to prevent
+# `make' implicit rules from creating a file called install from it
+# when there is no Makefile.
+#
+# This script is compatible with the BSD install script, but was written
+# from scratch.  It can only install one file at a time, a restriction
+# shared with many OS's install programs.
+
+
+# set DOITPROG to echo to test this script
+
+# Don't use :- since 4.3BSD and earlier shells don't like it.
+doit="${DOITPROG-}"
+
+
+# put in absolute paths if you don't have them in your path; or use env. vars.
+
+mvprog="${MVPROG-mv}"
+cpprog="${CPPROG-cp}"
+chmodprog="${CHMODPROG-chmod}"
+chownprog="${CHOWNPROG-chown}"
+chgrpprog="${CHGRPPROG-chgrp}"
+stripprog="${STRIPPROG-strip}"
+rmprog="${RMPROG-rm}"
+mkdirprog="${MKDIRPROG-mkdir}"
+
+transformbasename=""
+transform_arg=""
+instcmd="$mvprog"
+chmodcmd="$chmodprog 0755"
+chowncmd=""
+chgrpcmd=""
+stripcmd=""
+rmcmd="$rmprog -f"
+mvcmd="$mvprog"
+src=""
+dst=""
+dir_arg=""
+
+while [ x"$1" != x ]; do
+    case $1 in
+	-c) instcmd="$cpprog"
+	    shift
+	    continue;;
+
+	-d) dir_arg=true
+	    shift
+	    continue;;
+
+	-m) chmodcmd="$chmodprog $2"
+	    shift
+	    shift
+	    continue;;
+
+	-o) chowncmd="$chownprog $2"
+	    shift
+	    shift
+	    continue;;
+
+	-g) chgrpcmd="$chgrpprog $2"
+	    shift
+	    shift
+	    continue;;
+
+	-s) stripcmd="$stripprog"
+	    shift
+	    continue;;
+
+	-t=*) transformarg=`echo $1 | sed 's/-t=//'`
+	    shift
+	    continue;;
+
+	-b=*) transformbasename=`echo $1 | sed 's/-b=//'`
+	    shift
+	    continue;;
+
+	*)  if [ x"$src" = x ]
+	    then
+		src=$1
+	    else
+		# this colon is to work around a 386BSD /bin/sh bug
+		:
+		dst=$1
+	    fi
+	    shift
+	    continue;;
+    esac
+done
+
+if [ x"$src" = x ]
+then
+	echo "install:	no input file specified"
+	exit 1
+else
+	true
+fi
+
+if [ x"$dir_arg" != x ]; then
+	dst=$src
+	src=""
+	
+	if [ -d $dst ]; then
+		instcmd=:
+		chmodcmd=""
+	else
+		instcmd=mkdir
+	fi
+else
+
+# Waiting for this to be detected by the "$instcmd $src $dsttmp" command
+# might cause directories to be created, which would be especially bad 
+# if $src (and thus $dsttmp) contains '*'.
+
+	if [ -f $src -o -d $src ]
+	then
+		true
+	else
+		echo "install:  $src does not exist"
+		exit 1
+	fi
+	
+	if [ x"$dst" = x ]
+	then
+		echo "install:	no destination specified"
+		exit 1
+	else
+		true
+	fi
+
+# If destination is a directory, append the input filename; if your system
+# does not like double slashes in filenames, you may need to add some logic
+
+	if [ -d $dst ]
+	then
+		dst="$dst"/`basename $src`
+	else
+		true
+	fi
+fi
+
+## this sed command emulates the dirname command
+dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'`
+
+# Make sure that the destination directory exists.
+#  this part is taken from Noah Friedman's mkinstalldirs script
+
+# Skip lots of stat calls in the usual case.
+if [ ! -d "$dstdir" ]; then
+defaultIFS=' 	
+'
+IFS="${IFS-${defaultIFS}}"
+
+oIFS="${IFS}"
+# Some sh's can't handle IFS=/ for some reason.
+IFS='%'
+set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'`
+IFS="${oIFS}"
+
+pathcomp=''
+
+while [ $# -ne 0 ] ; do
+	pathcomp="${pathcomp}${1}"
+	shift
+
+	if [ ! -d "${pathcomp}" ] ;
+        then
+		$mkdirprog "${pathcomp}"
+	else
+		true
+	fi
+
+	pathcomp="${pathcomp}/"
+done
+fi
+
+if [ x"$dir_arg" != x ]
+then
+	$doit $instcmd $dst &&
+
+	if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else true ; fi &&
+	if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else true ; fi &&
+	if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else true ; fi &&
+	if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else true ; fi
+else
+
+# If we're going to rename the final executable, determine the name now.
+
+	if [ x"$transformarg" = x ] 
+	then
+		dstfile=`basename $dst`
+	else
+		dstfile=`basename $dst $transformbasename | 
+			sed $transformarg`$transformbasename
+	fi
+
+# don't allow the sed command to completely eliminate the filename
+
+	if [ x"$dstfile" = x ] 
+	then
+		dstfile=`basename $dst`
+	else
+		true
+	fi
+
+# Make a temp file name in the proper directory.
+
+	dsttmp=$dstdir/#inst.$$#
+
+# Move or copy the file name to the temp name
+
+	$doit $instcmd $src $dsttmp &&
+
+	trap "rm -f ${dsttmp}" 0 &&
+
+# and set any options; do chmod last to preserve setuid bits
+
+# If any of these fail, we abort the whole thing.  If we want to
+# ignore errors from any of these, just make sure not to ignore
+# errors from the above "$doit $instcmd $src $dsttmp" command.
+
+	if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else true;fi &&
+	if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else true;fi &&
+	if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else true;fi &&
+	if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else true;fi &&
+
+# Now rename the file to the real destination.
+
+	$doit $rmcmd -f $dstdir/$dstfile &&
+	$doit $mvcmd $dsttmp $dstdir/$dstfile 
+
+fi &&
+
+
+exit 0
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/setup/m4/as-python.m4 b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/setup/m4/as-python.m4
new file mode 100644
index 0000000..84d4e36
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/setup/m4/as-python.m4
@@ -0,0 +1,156 @@
+## ------------------------
+## Python file handling
+## From Andrew Dalke
+## Updated by James Henstridge
+## Updated by Andy Wingo to loop through possible pythons
+## ------------------------
+
+# AS_PATH_PYTHON([MINIMUM-VERSION])
+
+# Adds support for distributing Python modules and packages.  To
+# install modules, copy them to $(pythondir), using the python_PYTHON
+# automake variable.  To install a package with the same name as the
+# automake package, install to $(pkgpythondir), or use the
+# pkgpython_PYTHON automake variable.
+
+# The variables $(pyexecdir) and $(pkgpyexecdir) are provided as
+# locations to install python extension modules (shared libraries).
+# Another macro is required to find the appropriate flags to compile
+# extension modules.
+
+# If your package is configured with a different prefix to python,
+# users will have to add the install directory to the PYTHONPATH
+# environment variable, or create a .pth file (see the python
+# documentation for details).
+
+# If the MINIMUM-VERSION argument is passed, AS_PATH_PYTHON will
+# cause an error if the version of python installed on the system
+# doesn't meet the requirement.  MINIMUM-VERSION should consist of
+# numbers and dots only.
+
+# Updated to loop over all possible python binaries by Andy Wingo
+# <wingo@pobox.com>
+# Updated to only warn and unset PYTHON if no good one is found
+
+AC_DEFUN([AS_PATH_PYTHON],
+ [
+  dnl Find a version of Python.  I could check for python versions 1.4
+  dnl or earlier, but the default installation locations changed from
+  dnl $prefix/lib/site-python in 1.4 to $prefix/lib/python1.5/site-packages
+  dnl in 1.5, and I don't want to maintain that logic.
+
+  dnl should we do the version check?
+  PYTHON_CANDIDATES="$PYTHON python python2 \
+                     python2.7 python2.6 pyton2.5 python2.4 python2.3 \
+                     python2.2 python2.1 python2.0 \
+                     python1.6 python1.5"
+  dnl Declare PYTHON as a special var
+  AC_ARG_VAR([PYTHON], [path to Python interpreter])
+  ifelse([$1],[],
+         [AC_PATH_PROG(PYTHON, $PYTHON_CANDIDATES)],
+         [
+     AC_MSG_NOTICE(Looking for Python version >= $1)
+    changequote(<<, >>)dnl
+    prog="
+import sys, string
+minver = '$1'
+# split string by '.' and convert to numeric
+minver_info = map(string.atoi, string.split(minver, '.'))
+# we can now do comparisons on the two lists:
+if sys.version_info >= tuple(minver_info):
+    sys.exit(0)
+else:
+    sys.exit(1)"
+    changequote([, ])dnl
+
+    python_good=false
+    for python_candidate in $PYTHON_CANDIDATES; do
+      unset PYTHON
+      AC_PATH_PROG(PYTHON, $python_candidate) 1> /dev/null 2> /dev/null
+
+      if test "x$PYTHON" = "x"; then continue; fi
+
+      if $PYTHON -c "$prog" 1>&AC_FD_CC 2>&AC_FD_CC; then
+        AC_MSG_CHECKING(["$PYTHON":])
+        AC_MSG_RESULT([okay])
+        python_good=true
+        break;
+      else
+        dnl clear the cache val
+        unset ac_cv_path_PYTHON
+      fi
+    done
+  ])
+
+  if test "$python_good" != "true"; then
+    AC_MSG_WARN([No suitable version of python found])
+    PYTHON=
+  else
+
+  AC_MSG_CHECKING([local Python configuration])
+
+  dnl Query Python for its version number.  Getting [:3] seems to be
+  dnl the best way to do this; it's what "site.py" does in the standard
+  dnl library.  Need to change quote character because of [:3]
+
+  AC_SUBST(PYTHON_VERSION)
+  changequote(<<, >>)dnl
+  PYTHON_VERSION=`$PYTHON -c "import sys; print sys.version[:3]"`
+  changequote([, ])dnl
+
+
+  dnl Use the values of $prefix and $exec_prefix for the corresponding
+  dnl values of PYTHON_PREFIX and PYTHON_EXEC_PREFIX.  These are made
+  dnl distinct variables so they can be overridden if need be.  However,
+  dnl general consensus is that you shouldn't need this ability.
+
+  AC_SUBST(PYTHON_PREFIX)
+  PYTHON_PREFIX='${prefix}'
+
+  AC_SUBST(PYTHON_EXEC_PREFIX)
+  PYTHON_EXEC_PREFIX='${exec_prefix}'
+
+  dnl At times (like when building shared libraries) you may want
+  dnl to know which OS platform Python thinks this is.
+
+  AC_SUBST(PYTHON_PLATFORM)
+  PYTHON_PLATFORM=`$PYTHON -c "import sys; print sys.platform"`
+
+
+  dnl Set up 4 directories:
+
+  dnl pythondir -- where to install python scripts.  This is the
+  dnl   site-packages directory, not the python standard library
+  dnl   directory like in previous automake betas.  This behaviour
+  dnl   is more consistent with lispdir.m4 for example.
+  dnl
+  dnl Also, if the package prefix isn't the same as python's prefix,
+  dnl then the old $(pythondir) was pretty useless.
+
+  AC_SUBST(pythondir)
+  pythondir=$PYTHON_PREFIX"/lib/python"$PYTHON_VERSION/site-packages
+
+  dnl pkgpythondir -- $PACKAGE directory under pythondir.  Was
+  dnl   PYTHON_SITE_PACKAGE in previous betas, but this naming is
+  dnl   more consistent with the rest of automake.
+  dnl   Maybe this should be put in python.am?
+
+  AC_SUBST(pkgpythondir)
+  pkgpythondir=\${pythondir}/$PACKAGE
+
+  dnl pyexecdir -- directory for installing python extension modules
+  dnl   (shared libraries)  Was PYTHON_SITE_EXEC in previous betas.
+
+  AC_SUBST(pyexecdir)
+  pyexecdir=$PYTHON_EXEC_PREFIX"/lib/python"$PYTHON_VERSION/site-packages
+
+  dnl pkgpyexecdir -- $(pyexecdir)/$(PACKAGE)
+  dnl   Maybe this should be put in python.am?
+
+  AC_SUBST(pkgpyexecdir)
+  pkgpyexecdir=\${pyexecdir}/$PACKAGE
+
+  AC_MSG_RESULT([looks good])
+
+  fi
+])
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/start.frag b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/start.frag
new file mode 100644
index 0000000..dad9d0f
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/start.frag
@@ -0,0 +1,7 @@
+(function(root, factory) {
+  if(typeof define === 'function' && define.amd) {
+    define([], factory);
+  } else {
+    root.forge = factory();
+  }
+})(this, function() {
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/aes-speed.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/aes-speed.js
new file mode 100644
index 0000000..6c9922a
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/aes-speed.js
@@ -0,0 +1,54 @@
+var forge = require('../js/forge');
+
+aes_128('GCM');
+
+function aes_128(mode) {
+  var size = 4096;
+  var key = forge.random.getBytes(16);
+  var iv = forge.random.getBytes(mode === 'GCM' ? 12 : 16);
+  var plain = forge.util.createBuffer().fillWithByte(0, size);
+
+  // run for 5 seconds
+  var start = Date.now();
+
+  var now;
+  var totalEncrypt = 0;
+  var totalDecrypt = 0;
+  var count = 0;
+  var passed = 0;
+  while(passed < 5000) {
+    var input = forge.util.createBuffer(plain);
+
+    // encrypt, only measuring update() and finish()
+    var cipher = forge.aes.createEncryptionCipher(key, mode);
+    cipher.start(iv);
+    now = Date.now();
+    cipher.update(input);
+    cipher.finish();
+    totalEncrypt += Date.now() - now;
+
+    var ciphertext = cipher.output;
+    var tag = cipher.tag;
+
+    // decrypt, only measuring update() and finish()
+    cipher = forge.aes.createDecryptionCipher(key, mode);
+    cipher.start(iv, {tag: tag});
+    now = Date.now();
+    cipher.update(ciphertext);
+    if(!cipher.finish()) {
+      throw new Error('Decryption error.');
+    }
+    totalDecrypt += Date.now() - now;
+
+    ++count;
+    passed = Date.now() - start;
+  }
+
+  count = count * size / 1000;
+  totalEncrypt /= 1000;
+  totalDecrypt /= 1000;
+
+  console.log('times in 1000s of bytes/sec processed.');
+  console.log('encrypt: ' + (count / totalEncrypt) + ' k/sec');
+  console.log('decrypt: ' + (count / totalDecrypt) + ' k/sec');
+}
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/common.html b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/common.html
new file mode 100644
index 0000000..0fb4705
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/common.html
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+   <head>
+      <title>Forge Common Test</title>
+      <link type="text/css" rel="stylesheet" media="all" href="screen.css" />
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
+      <script type="text/javascript" src="forge/debug.js"></script>
+      <script type="text/javascript" src="forge/util.js"></script>
+      <script type="text/javascript" src="forge/log.js"></script>
+      <script type="text/javascript" src="forge/task.js"></script>
+      <script type="text/javascript" src="forge/md5.js"></script>
+      <script type="text/javascript" src="forge/sha1.js"></script>
+      <script type="text/javascript" src="forge/sha256.js"></script>
+      <script type="text/javascript" src="forge/hmac.js"></script>
+      <script type="text/javascript" src="forge/pbkdf2.js"></script>
+      <script type="text/javascript" src="forge/aes.js"></script>
+      <script type="text/javascript" src="forge/pem.js"></script>
+      <script type="text/javascript" src="forge/asn1.js"></script>
+      <script type="text/javascript" src="forge/jsbn.js"></script>
+      <script type="text/javascript" src="forge/prng.js"></script>
+      <script type="text/javascript" src="forge/random.js"></script>
+      <script type="text/javascript" src="forge/oids.js"></script>
+      <script type="text/javascript" src="forge/rsa.js"></script>
+      <script type="text/javascript" src="forge/pbe.js"></script>
+      <script type="text/javascript" src="forge/x509.js"></script>
+      <script type="text/javascript" src="forge/pki.js"></script>
+      <script type="text/javascript" src="forge/tls.js"></script>
+      <script type="text/javascript" src="forge/aesCipherSuites.js"></script>
+      <script type="text/javascript" src="common.js"></script>
+
+      <link type="text/css" rel="stylesheet" media="all" href="screen.css" />
+      <style type="text/css">
+         .ready { color: inherit; background: inherit; }
+         .testing { color: black; background: yellow; }
+         .pass{ color: white; background: green; }
+         .fail{ color: white; background: red; }
+      </style>
+   </head>
+<body>
+<div class="nav"><a href="index.html">Forge Tests</a> / Common</div>
+
+<div class="header">
+   <h1>Common Tests</h1>
+</div>
+
+<div class="content">
+
+<fieldset class="section">
+   <ul>
+      <li>Test various Forge components.</li>
+      <li>See JavaScript console for more detailed output.</li>
+   </ul>
+</fieldset>
+
+<fieldset class="section">
+<legend>Control</legend>
+   <button id="start">Start</button>
+   <button id="reset">Reset</button>
+   <br/>
+   <input id="scroll" type="checkbox" />Scroll Tests
+   <br/>
+   <button id="keygen">Generate RSA key pair</button>
+   <button id="certgen">Generate RSA certificate</button>
+   <input id="bits" value="1024"/>bits
+</fieldset>
+
+<fieldset class="section">
+<legend>Progress</legend>
+Status: <span id="status">?</span><br/>
+Pass: <span id="pass">?</span>/<span id="total">?</span><br/>
+Fail: <span id="fail">?</span>
+</fieldset>
+
+<fieldset class="section">
+<legend>Tests</legend>
+<div id="tests"></div>
+</fieldset>
+
+</div>
+
+</body>
+</html>
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/common.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/common.js
new file mode 100644
index 0000000..57dfbc4
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/common.js
@@ -0,0 +1,2199 @@
+/**
+ * Forge Common Tests
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2009-2012 Digital Bazaar, Inc. All rights reserved.
+ */
+jQuery(function($)
+{
+   // logging category
+   var cat = 'forge.tests.common';
+
+   // local alias
+   var forge = window.forge;
+
+   var tests = [];
+   var passed = 0;
+   var failed = 0;
+
+   var init = function()
+   {
+      passed = failed = 0;
+      $('.ready,.testing,.pass,.fail')
+         .removeClass('ready testing pass fail');
+      $('#status')
+         .text('Ready.')
+         .addClass('ready');
+      $('#total').text(tests.length);
+      $('#pass').text(passed);
+      $('#fail').text(failed);
+      $('.expect').empty();
+      $('.result').empty();
+      $('.time').empty();
+      $('.timePer').empty();
+      $('#start').attr('disabled', '');
+   };
+
+   var start = function()
+   {
+      $('#start').attr('disabled', 'true');
+      // meta! use tasks to run the task tests
+      forge.task.start({
+         type: 'test',
+         run: function(task) {
+            task.next('starting', function(task) {
+               forge.log.debug(cat, 'start');
+               $('#status')
+                  .text('Testing...')
+                  .addClass('testing')
+                  .removeClass('idle');
+            });
+            $.each(tests, function(i, test) {
+               task.next('test', function(task) {
+                  var title = $('li:first', test.container);
+                  if($('#scroll:checked').length === 1)
+                  {
+                     $('html,body').animate({scrollTop: title.offset().top});
+                  }
+                  title.addClass('testing');
+                  test.run(task, test);
+               });
+               task.next('test', function(task) {
+                  $('li:first', test.container).removeClass('testing');
+               });
+            });
+            task.next('success', function(task) {
+               forge.log.debug(cat, 'done');
+               if(failed === 0) {
+                  $('#status')
+                     .text('PASS')
+                     .addClass('pass')
+                     .removeClass('testing');
+               } else {
+                  // FIXME: should just be hitting failure() below
+                  $('#status')
+                     .text('FAIL')
+                     .addClass('fail')
+                     .removeClass('testing');
+               }
+            });
+         },
+         failure: function() {
+            $('#status')
+               .text('FAIL')
+               .addClass('fail')
+               .removeClass('testing');
+         }
+      });
+   };
+
+   $('#start').click(function() {
+      start();
+   });
+
+   $('#reset').click(function() {
+      init();
+   });
+
+   $('#keygen').click(function() {
+      var bits = $('#bits')[0].value;
+      var keys = forge.pki.rsa.generateKeyPair(bits);
+      forge.log.debug(cat, 'generating ' + bits + '-bit RSA key-pair...');
+      setTimeout(function()
+      {
+         forge.log.debug(cat, 'private key:', keys.privateKey);
+         forge.log.debug(cat, forge.pki.privateKeyToPem(keys.privateKey));
+         forge.log.debug(cat, 'public key:', keys.publicKey);
+         forge.log.debug(cat, forge.pki.publicKeyToPem(keys.publicKey));
+
+         forge.log.debug(cat, 'testing sign/verify...');
+         setTimeout(function()
+         {
+            // do sign/verify test
+            try
+            {
+               var md = forge.md.sha1.create();
+               md.update('foo');
+               var signature = keys.privateKey.sign(md);
+               keys.publicKey.verify(md.digest().getBytes(), signature);
+               forge.log.debug(cat, 'sign/verify success');
+            }
+            catch(ex)
+            {
+               forge.log.error(cat, 'sign/verify failure', ex);
+            }
+         }, 0);
+      }, 0);
+   });
+
+   $('#certgen').click(function() {
+      var bits = $('#bits')[0].value;
+      forge.log.debug(cat, 'generating ' + bits +
+         '-bit RSA key-pair and certificate...');
+      setTimeout(function()
+      {
+         try
+         {
+            var keys = forge.pki.rsa.generateKeyPair(bits);
+            var cert = forge.pki.createCertificate();
+            cert.serialNumber = '01';
+            cert.validity.notBefore = new Date();
+            cert.validity.notAfter = new Date();
+            cert.validity.notAfter.setFullYear(
+               cert.validity.notBefore.getFullYear() + 1);
+            var attrs = [{
+               name: 'commonName',
+               value: 'mycert'
+            }, {
+               name: 'countryName',
+               value: 'US'
+            }, {
+               shortName: 'ST',
+               value: 'Virginia'
+            }, {
+               name: 'localityName',
+               value: 'Blacksburg'
+            }, {
+               name: 'organizationName',
+               value: 'Test'
+            }, {
+               shortName: 'OU',
+               value: 'Test'
+            }];
+            cert.setSubject(attrs);
+            cert.setIssuer(attrs);
+            cert.setExtensions([{
+               name: 'basicConstraints',
+               cA: true
+            }, {
+               name: 'keyUsage',
+               keyCertSign: true
+            }, {
+               name: 'subjectAltName',
+               altNames: [{
+                  type: 6, // URI
+                  value: 'http://localhost/dataspace/person/myname#this'
+               }]
+            }]);
+            // FIXME: add subjectKeyIdentifier extension
+            // FIXME: add authorityKeyIdentifier extension
+            cert.publicKey = keys.publicKey;
+
+            // self-sign certificate
+            cert.sign(keys.privateKey);
+
+            forge.log.debug(cat, 'certificate:', cert);
+            //forge.log.debug(cat,
+            //   forge.asn1.prettyPrint(forge.pki.certificateToAsn1(cert)));
+            forge.log.debug(cat, forge.pki.certificateToPem(cert));
+
+            // verify certificate
+            forge.log.debug(cat, 'verified', cert.verify(cert));
+         }
+         catch(ex)
+         {
+            forge.log.error(cat, ex, ex.message ? ex.message : '');
+         }
+      }, 0);
+   });
+
+   var addTest = function(name, run)
+   {
+      var container = $('<ul><li>Test ' + name + '</li><ul/></ul>');
+      var expect = $('<li>Expect: <span class="expect"/></li>');
+      var result = $('<li>Result: <span class="result"/></li>');
+      var time = $('<li>Time: <span class="time"/></li>');
+      var timePer = $('<li>Time Per Iteration: <span class="timePer"/></li>');
+      $('ul', container)
+         .append(expect)
+         .append(result)
+         .append(time)
+         .append(timePer);
+      $('#tests').append(container);
+      var test = {
+         container: container,
+         startTime: null,
+         run: function(task, test) {
+            test.startTime = new Date();
+            run(task, test);
+         },
+         expect: $('span', expect),
+         result: $('span', result),
+         check: function() {
+            var e = test.expect.text();
+            var r = test.result.text();
+            (e == r) ? test.pass() : test.fail();
+         },
+         pass: function(iterations) {
+            var dt = new Date() - test.startTime;
+            if(!iterations)
+            {
+               iterations = 1;
+            }
+            var dti = (dt / iterations);
+            passed += 1;
+            $('#pass').text(passed);
+            $('li:first', container).addClass('pass');
+            $('span.time', container).html(dt + 'ms');
+            $('span.timePer', container).html(dti + 'ms');
+         },
+         fail: function(iterations) {
+            var dt = new Date() - test.startTime;
+            if(!iterations)
+            {
+               iterations = 1;
+            }
+            var dti = (dt / iterations);
+            failed += 1;
+            $('#fail').text(failed);
+            $('li:first', container).addClass('fail');
+            $('span.time', container).html(dt + 'ms');
+            $('span.timePer', container).html(dti + 'ms');
+         }
+      };
+      tests.push(test);
+   };
+
+   addTest('buffer put bytes', function(task, test)
+   {
+      ba = forge.util.createBuffer();
+      ba.putByte(1);
+      ba.putByte(2);
+      ba.putByte(3);
+      ba.putByte(4);
+      ba.putInt32(4);
+      ba.putByte(1);
+      ba.putByte(2);
+      ba.putByte(3);
+      ba.putInt32(4294967295);
+      var hex = ba.toHex();
+      var bytes = [];
+      while(ba.length() > 0)
+      {
+         bytes.push(ba.getByte());
+      }
+      var expect = [1, 2, 3, 4, 0, 0, 0, 4, 1, 2, 3, 255, 255, 255, 255];
+      var exHex = '0102030400000004010203ffffffff';
+      test.expect.html(exHex);
+      test.result.html(hex);
+
+      test.check();
+   });
+
+   addTest('buffer from hex', function(task, test)
+   {
+      var exHex = '0102030400000004010203ffffffff';
+      test.expect.html(exHex);
+
+      var buf = forge.util.createBuffer();
+      buf.putBytes(forge.util.hexToBytes(exHex));
+      test.result.html(buf.toHex());
+
+      test.check();
+   });
+
+   addTest('base64 encode', function(task, test)
+   {
+      var s1 = '00010203050607080A0B0C0D0F1011121415161719';
+      var s2 = 'MDAwMTAyMDMwNTA2MDcwODBBMEIwQzBEMEYxMDExMTIxNDE1MTYxNzE5';
+      test.expect.html(s2);
+
+      var out = forge.util.encode64(s1);
+      test.result.html(out);
+
+      test.check();
+   });
+
+   addTest('base64 decode', function(task, test)
+   {
+      var s1 = '00010203050607080A0B0C0D0F1011121415161719';
+      var s2 = 'MDAwMTAyMDMwNTA2MDcwODBBMEIwQzBEMEYxMDExMTIxNDE1MTYxNzE5';
+      test.expect.html(s1);
+
+      var out = forge.util.decode64(s2);
+      test.result.html(out);
+
+      test.check();
+   });
+
+   addTest('md5 empty', function(task, test)
+   {
+      var expect = 'd41d8cd98f00b204e9800998ecf8427e';
+      test.expect.html(expect);
+      var md = forge.md.md5.create();
+      test.result.html(md.digest().toHex());
+      test.check();
+   });
+
+   addTest('md5 "abc"', function(task, test)
+   {
+      var expect = '900150983cd24fb0d6963f7d28e17f72';
+      test.expect.html(expect);
+      var md = forge.md.md5.create();
+      md.update('abc');
+      test.result.html(md.digest().toHex());
+      test.check();
+   });
+
+   addTest('md5 "The quick brown fox jumps over the lazy dog"',
+      function(task, test)
+   {
+      var expect = '9e107d9d372bb6826bd81d3542a419d6';
+      test.expect.html(expect);
+      var md = forge.md.md5.create();
+      md.start();
+      md.update('The quick brown fox jumps over the lazy dog');
+      test.result.html(md.digest().toHex());
+      test.check();
+   });
+
+   // c'è
+   addTest('md5 "c\'\u00e8"', function(task, test)
+   {
+      var expect = '8ef7c2941d78fe89f31e614437c9db59';
+      test.expect.html(expect);
+      var md = forge.md.md5.create();
+      md.update("c'\u00e8", 'utf8');
+      test.result.html(md.digest().toHex());
+      test.check();
+   });
+
+   addTest('md5 "THIS IS A MESSAGE"',
+   function(task, test)
+   {
+      var expect = '78eebfd9d42958e3f31244f116ab7bbe';
+      test.expect.html(expect);
+      var md = forge.md.md5.create();
+      md.start();
+      md.update('THIS IS ');
+      md.update('A MESSAGE');
+      // do twice to check continuing digest
+      test.result.html(md.digest().toHex());
+      test.result.html(md.digest().toHex());
+      test.check();
+   });
+
+   addTest('md5 long message',
+   function(task, test)
+   {
+      var input = forge.util.createBuffer();
+      input.putBytes(forge.util.hexToBytes(
+         '0100002903018d32e9c6dc423774c4c39a5a1b78f44cc2cab5f676d39' +
+         'f703d29bfa27dfeb870000002002f0100'));
+      input.putBytes(forge.util.hexToBytes(
+         '0200004603014c2c1e835d39da71bc0857eb04c2b50fe90dbb2a8477f' +
+         'e7364598d6f0575999c20a6c7248c5174da6d03ac711888f762fc4ed5' +
+         '4f7254b32273690de849c843073d002f00'));
+      input.putBytes(forge.util.hexToBytes(
+         '0b0003d20003cf0003cc308203c8308202b0a003020102020100300d0' +
+         '6092a864886f70d0101050500308186310b3009060355040613025553' +
+         '311d301b060355040a13144469676974616c2042617a6161722c20496' +
+         'e632e31443042060355040b133b4269746d756e6b206c6f63616c686f' +
+         '73742d6f6e6c7920436572746966696361746573202d20417574686f7' +
+         '2697a6174696f6e207669612042545031123010060355040313096c6f' +
+         '63616c686f7374301e170d3130303231343137303931395a170d32303' +
+         '03231333137303931395a308186310b3009060355040613025553311d' +
+         '301b060355040a13144469676974616c2042617a6161722c20496e632' +
+         'e31443042060355040b133b4269746d756e6b206c6f63616c686f7374' +
+         '2d6f6e6c7920436572746966696361746573202d20417574686f72697' +
+         'a6174696f6e207669612042545031123010060355040313096c6f6361' +
+         '6c686f737430820122300d06092a864886f70d01010105000382010f0' +
+         '03082010a0282010100dc436f17d6909d8a9d6186ea218eb5c86b848b' +
+         'ae02219bd56a71203daf07e81bc19e7e98134136bcb012881864bf03b' +
+         '3774652ad5eab85dba411a5114ffeac09babce75f31314345512cd87c' +
+         '91318b2e77433270a52185fc16f428c3ca412ad6e9484bc2fb87abb4e' +
+         '8fb71bf0f619e31a42340b35967f06c24a741a31c979c0bb8921a90a4' +
+         '7025fbeb8adca576979e70a56830c61170c9647c18c0794d68c0df38f' +
+         '3aac5fc3b530e016ea5659715339f3f3c209cdee9dbe794b5af92530c' +
+         '5754c1d874b78974bfad994e0dfc582275e79feb522f6e4bcc2b2945b' +
+         'aedfb0dbdaebb605f9483ff0bea29ecd5f4d6f2769965d1b3e04f8422' +
+         '716042680011ff676f0203010001a33f303d300c0603551d130101ff0' +
+         '4023000300e0603551d0f0101ff0404030204f0301d0603551d250416' +
+         '301406082b0601050507030106082b06010505070302300d06092a864' +
+         '886f70d010105050003820101009c4562be3f2d8d8e388085a697f2f1' +
+         '06eaeff4992a43f198fe3dcf15c8229cf1043f061a38204f73d86f4fb' +
+         '6348048cc5279ed719873aa10e3773d92b629c2c3fcce04012c81ba3b' +
+         '4ec451e9644ec5191078402d845e05d02c7b4d974b4588276e5037aba' +
+         '7ef26a8bddeb21e10698c82f425e767dc401adf722fa73ab78cfa069b' +
+         'd69052d7ca6a75cc9225550e315d71c5f8764362ea4dbc6ecb837a847' +
+         '1043c5a7f826a71af145a053090bd4bccca6a2c552841cdb1908a8352' +
+         'f49283d2e641acdef667c7543af441a16f8294251e2ac376fa507b53a' +
+         'e418dd038cd20cef1e7bfbf5ae03a7c88d93d843abaabbdc5f3431132' +
+         'f3e559d2dd414c3eda38a210b8'));
+      input.putBytes(forge.util.hexToBytes('0e000000'));
+      input.putBytes(forge.util.hexToBytes(
+         '10000102010026a220b7be857402819b78d81080d01a682599bbd0090' +
+         '2985cc64edf8e520e4111eb0e1729a14ffa3498ca259cc9ad6fc78fa1' +
+         '30d968ebdb78dc0b950c0aa44355f13ba678419185d7e4608fe178ca6' +
+         'b2cef33e4193778d1a70fe4d0dfcb110be4bbb4dbaa712177655728f9' +
+         '14ab4c0f6c4aef79a46b3d996c82b2ebe9ed1748eb5cace7dc44fb67e' +
+         '73f452a047f2ed199b3d50d5db960acf03244dc8efa4fc129faf8b65f' +
+         '9e52e62b5544722bd17d2358e817a777618a4265a3db277fc04851a82' +
+         'a91fe6cdcb8127f156e0b4a5d1f54ce2742eb70c895f5f8b85f5febe6' +
+         '9bc73e891f9280826860a0c2ef94c7935e6215c3c4cd6b0e43e80cca3' +
+         '96d913d36be'));
+
+      var expect = 'd15a2da0e92c3da55dc573f885b6e653';
+      test.expect.html(expect);
+
+      var md = forge.md.md5.create();
+      md.start();
+      md.update(input.getBytes());
+      test.result.html(md.digest().toHex());
+
+      test.check();
+   });
+
+   addTest('sha-1 empty', function(task, test)
+   {
+      var expect = 'da39a3ee5e6b4b0d3255bfef95601890afd80709';
+      test.expect.html(expect);
+      var md = forge.md.sha1.create();
+      test.result.html(md.digest().toHex());
+      test.check();
+   });
+
+   addTest('sha-1 "abc"', function(task, test)
+   {
+      var expect = 'a9993e364706816aba3e25717850c26c9cd0d89d';
+      test.expect.html(expect);
+      var md = forge.md.sha1.create();
+      md.update('abc');
+      test.result.html(md.digest().toHex());
+      test.check();
+   });
+
+   addTest('sha-1 "The quick brown fox jumps over the lazy dog"',
+      function(task, test)
+   {
+      var expect = '2fd4e1c67a2d28fced849ee1bb76e7391b93eb12';
+      test.expect.html(expect);
+      var md = forge.md.sha1.create();
+      md.start();
+      md.update('The quick brown fox jumps over the lazy dog');
+      test.result.html(md.digest().toHex());
+      test.check();
+   });
+
+   // c'è
+   addTest('sha-1 "c\'\u00e8"', function(task, test)
+   {
+      var expect = '98c9a3f804daa73b68a5660d032499a447350c0d';
+      test.expect.html(expect);
+      var md = forge.md.sha1.create();
+      md.update("c'\u00e8", 'utf8');
+      test.result.html(md.digest().toHex());
+      test.check();
+   });
+
+   addTest('sha-1 "THIS IS A MESSAGE"',
+   function(task, test)
+   {
+      var expect = '5f24f4d6499fd2d44df6c6e94be8b14a796c071d';
+      test.expect.html(expect);
+      var md = forge.md.sha1.create();
+      md.start();
+      md.update('THIS IS ');
+      md.update('A MESSAGE');
+      // do twice to check continuing digest
+      test.result.html(md.digest().toHex());
+      test.result.html(md.digest().toHex());
+      test.check();
+   });
+
+   // other browsers too slow for this test
+   if($.browser.webkit)
+   {
+      addTest('sha-1 long message',
+      function(task, test)
+      {
+         var expect = '34aa973cd4c4daa4f61eeb2bdbad27316534016f';
+         test.expect.html(expect);
+         var md = forge.md.sha1.create();
+         md.start();
+         md.update(forge.util.fillString('a', 1000000));
+         // do twice to check continuing digest
+         test.result.html(md.digest().toHex());
+         test.result.html(md.digest().toHex());
+         test.check();
+      });
+   }
+
+   addTest('sha-256 "abc"', function(task, test)
+   {
+      var expect =
+         'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad';
+      test.expect.html(expect);
+      var md = forge.md.sha256.create();
+      md.update('abc');
+      test.result.html(md.digest().toHex());
+      test.check();
+   });
+
+   // c'è
+   addTest('sha-256 "c\'\u00e8"', function(task, test)
+   {
+      var expect =
+         '1aa15c717afffd312acce2217ce1c2e5dabca53c92165999132ec9ca5decdaca';
+      test.expect.html(expect);
+      var md = forge.md.sha256.create();
+      md.update("c'\u00e8", 'utf8');
+      test.result.html(md.digest().toHex());
+      test.check();
+   });
+
+   addTest('sha-256 "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"',
+   function(task, test)
+   {
+      var expect =
+         '248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1';
+      test.expect.html(expect);
+      var md = forge.md.sha256.create();
+      md.start();
+      md.update('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq');
+      test.result.html(md.digest().toHex());
+      test.check();
+   });
+
+   // other browsers too slow for this test
+   if($.browser.webkit)
+   {
+      addTest('sha-256 long message',
+      function(task, test)
+      {
+         var expect =
+            'cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0';
+         test.expect.html(expect);
+         var md = forge.md.sha256.create();
+         md.start();
+         md.update(forge.util.fillString('a', 1000000));
+         // do twice to check continuing digest
+         test.result.html(md.digest().toHex());
+         test.result.html(md.digest().toHex());
+         test.check();
+      });
+   }
+
+   addTest('hmac md5 "Hi There", 16-byte key', function(task, test)
+   {
+      var expect = '9294727a3638bb1c13f48ef8158bfc9d';
+      test.expect.html(expect);
+      var key = forge.util.hexToBytes(
+         '0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b');
+      var hmac = forge.hmac.create();
+      hmac.start('MD5', key);
+      hmac.update('Hi There');
+      test.result.html(hmac.digest().toHex());
+      test.check();
+   });
+
+   addTest('hmac md5 "what do ya want for nothing?", "Jefe" key',
+      function(task, test)
+   {
+      var expect = '750c783e6ab0b503eaa86e310a5db738';
+      test.expect.html(expect);
+      var hmac = forge.hmac.create();
+      hmac.start('MD5', 'Jefe');
+      hmac.update('what do ya want for nothing?');
+      test.result.html(hmac.digest().toHex());
+      test.check();
+   });
+
+   addTest('hmac md5 "Test Using Larger Than Block-Size Key - ' +
+      'Hash Key First", 80-byte key', function(task, test)
+   {
+      var expect = '6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd';
+      test.expect.html(expect);
+      var key = forge.util.hexToBytes(
+         'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
+         'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
+         'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
+         'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
+      var hmac = forge.hmac.create();
+      hmac.start('MD5', key);
+      hmac.update('Test Using Larger Than Block-Size Key - Hash Key First');
+      test.result.html(hmac.digest().toHex());
+      test.check();
+   });
+
+   addTest('hmac sha-1 "Hi There", 20-byte key', function(task, test)
+   {
+      var expect = 'b617318655057264e28bc0b6fb378c8ef146be00';
+      test.expect.html(expect);
+      var key = forge.util.hexToBytes(
+         '0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b');
+      var hmac = forge.hmac.create();
+      hmac.start('SHA1', key);
+      hmac.update('Hi There');
+      test.result.html(hmac.digest().toHex());
+      test.check();
+   });
+
+   addTest('hmac sha-1 "what do ya want for nothing?", "Jefe" key',
+      function(task, test)
+   {
+      var expect = 'effcdf6ae5eb2fa2d27416d5f184df9c259a7c79';
+      test.expect.html(expect);
+      var hmac = forge.hmac.create();
+      hmac.start('SHA1', 'Jefe');
+      hmac.update('what do ya want for nothing?');
+      test.result.html(hmac.digest().toHex());
+      test.check();
+   });
+
+   addTest('hmac sha-1 "Test Using Larger Than Block-Size Key - ' +
+      'Hash Key First", 80-byte key', function(task, test)
+   {
+      var expect = 'aa4ae5e15272d00e95705637ce8a3b55ed402112';
+      test.expect.html(expect);
+      var key = forge.util.hexToBytes(
+         'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
+         'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
+         'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
+         'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
+      var hmac = forge.hmac.create();
+      hmac.start('SHA1', key);
+      hmac.update('Test Using Larger Than Block-Size Key - Hash Key First');
+      test.result.html(hmac.digest().toHex());
+      test.check();
+   });
+
+   addTest('pbkdf2 hmac-sha-1 c=1', function(task, test)
+   {
+      var expect = '0c60c80f961f0e71f3a9b524af6012062fe037a6';
+      var dk = forge.pkcs5.pbkdf2('password', 'salt', 1, 20);
+      test.expect.html(expect);
+      test.result.html(forge.util.bytesToHex(dk));
+      test.check();
+   });
+
+   addTest('pbkdf2 hmac-sha-1 c=2', function(task, test)
+   {
+      var expect = 'ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957';
+      var dk = forge.pkcs5.pbkdf2('password', 'salt', 2, 20);
+      test.expect.html(expect);
+      test.result.html(forge.util.bytesToHex(dk));
+      test.check();
+   });
+
+   addTest('pbkdf2 hmac-sha-1 c=2', function(task, test)
+   {
+      var expect = 'ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957';
+      var dk = forge.pkcs5.pbkdf2('password', 'salt', 2, 20);
+      test.expect.html(expect);
+      test.result.html(forge.util.bytesToHex(dk));
+      test.check();
+   });
+
+   addTest('pbkdf2 hmac-sha-1 c=5 keylen=8', function(task, test)
+   {
+      var expect = 'd1daa78615f287e6';
+      var salt = forge.util.hexToBytes('1234567878563412');
+      var dk = forge.pkcs5.pbkdf2('password', salt, 5, 8);
+      test.expect.html(expect);
+      test.result.html(forge.util.bytesToHex(dk));
+      test.check();
+   });
+
+   // other browsers too slow for this test
+   if($.browser.webkit)
+   {
+      addTest('pbkdf2 hmac-sha-1 c=4096', function(task, test)
+      {
+         var expect = '4b007901b765489abead49d926f721d065a429c1';
+         var dk = forge.pkcs5.pbkdf2('password', 'salt', 4096, 20);
+         test.expect.html(expect);
+         test.result.html(forge.util.bytesToHex(dk));
+         test.check();
+      });
+   }
+
+   /* too slow for javascript
+   addTest('pbkdf2 hmac-sha-1 c=16777216', function(task, test)
+   {
+      var expect = 'eefe3d61cd4da4e4e9945b3d6ba2158c2634e984';
+      var dk = forge.pkcs5.pbkdf2('password', 'salt', 16777216, 20);
+      test.expect.html(expect);
+      test.result.html(forge.util.bytesToHex(dk));
+      test.check();
+   });*/
+
+   addTest('aes-128 encrypt', function(task, test)
+   {
+      var block = [];
+      block.push(0x00112233);
+      block.push(0x44556677);
+      block.push(0x8899aabb);
+      block.push(0xccddeeff);
+      var plain = block;
+
+      var key = [];
+      key.push(0x00010203);
+      key.push(0x04050607);
+      key.push(0x08090a0b);
+      key.push(0x0c0d0e0f);
+
+      var expect = [];
+      expect.push(0x69c4e0d8);
+      expect.push(0x6a7b0430);
+      expect.push(0xd8cdb780);
+      expect.push(0x70b4c55a);
+
+      test.expect.html('69c4e0d86a7b0430d8cdb78070b4c55a');
+
+      var output = [];
+      var w = forge.aes._expandKey(key, false);
+      forge.aes._updateBlock(w, block, output, false);
+
+      var out = forge.util.createBuffer();
+      out.putInt32(output[0]);
+      out.putInt32(output[1]);
+      out.putInt32(output[2]);
+      out.putInt32(output[3]);
+      test.result.html(out.toHex());
+
+      test.check();
+   });
+
+   addTest('aes-128 decrypt', function(task, test)
+   {
+      var block = [];
+      block.push(0x69c4e0d8);
+      block.push(0x6a7b0430);
+      block.push(0xd8cdb780);
+      block.push(0x70b4c55a);
+
+      var key = [];
+      key.push(0x00010203);
+      key.push(0x04050607);
+      key.push(0x08090a0b);
+      key.push(0x0c0d0e0f);
+
+      var expect = [];
+      expect.push(0x00112233);
+      expect.push(0x44556677);
+      expect.push(0x8899aabb);
+      expect.push(0xccddeeff);
+
+      test.expect.html('00112233445566778899aabbccddeeff');
+
+      var output = [];
+      w = forge.aes._expandKey(key, true);
+      forge.aes._updateBlock(w, block, output, true);
+
+      var out = forge.util.createBuffer();
+      out.putInt32(output[0]);
+      out.putInt32(output[1]);
+      out.putInt32(output[2]);
+      out.putInt32(output[3]);
+      test.result.html(out.toHex());
+
+      test.check();
+   });
+
+   addTest('aes-192 encrypt', function(task, test)
+   {
+      var block = [];
+      block.push(0x00112233);
+      block.push(0x44556677);
+      block.push(0x8899aabb);
+      block.push(0xccddeeff);
+      var plain = block;
+
+      var key = [];
+      key.push(0x00010203);
+      key.push(0x04050607);
+      key.push(0x08090a0b);
+      key.push(0x0c0d0e0f);
+      key.push(0x10111213);
+      key.push(0x14151617);
+
+      var expect = [];
+      expect.push(0xdda97ca4);
+      expect.push(0x864cdfe0);
+      expect.push(0x6eaf70a0);
+      expect.push(0xec0d7191);
+
+      test.expect.html('dda97ca4864cdfe06eaf70a0ec0d7191');
+
+      var output = [];
+      var w = forge.aes._expandKey(key, false);
+      forge.aes._updateBlock(w, block, output, false);
+
+      var out = forge.util.createBuffer();
+      out.putInt32(output[0]);
+      out.putInt32(output[1]);
+      out.putInt32(output[2]);
+      out.putInt32(output[3]);
+      test.result.html(out.toHex());
+
+      test.check();
+   });
+
+   addTest('aes-192 decrypt', function(task, test)
+   {
+      var block = [];
+      block.push(0xdda97ca4);
+      block.push(0x864cdfe0);
+      block.push(0x6eaf70a0);
+      block.push(0xec0d7191);
+
+      var key = [];
+      key.push(0x00010203);
+      key.push(0x04050607);
+      key.push(0x08090a0b);
+      key.push(0x0c0d0e0f);
+      key.push(0x10111213);
+      key.push(0x14151617);
+
+      var expect = [];
+      expect.push(0x00112233);
+      expect.push(0x44556677);
+      expect.push(0x8899aabb);
+      expect.push(0xccddeeff);
+
+      test.expect.html('00112233445566778899aabbccddeeff');
+
+      var output = [];
+      w = forge.aes._expandKey(key, true);
+      forge.aes._updateBlock(w, block, output, true);
+
+      var out = forge.util.createBuffer();
+      out.putInt32(output[0]);
+      out.putInt32(output[1]);
+      out.putInt32(output[2]);
+      out.putInt32(output[3]);
+      test.result.html(out.toHex());
+
+      test.check();
+   });
+
+   addTest('aes-256 encrypt', function(task, test)
+   {
+      var block = [];
+      block.push(0x00112233);
+      block.push(0x44556677);
+      block.push(0x8899aabb);
+      block.push(0xccddeeff);
+      var plain = block;
+
+      var key = [];
+      key.push(0x00010203);
+      key.push(0x04050607);
+      key.push(0x08090a0b);
+      key.push(0x0c0d0e0f);
+      key.push(0x10111213);
+      key.push(0x14151617);
+      key.push(0x18191a1b);
+      key.push(0x1c1d1e1f);
+
+      var expect = [];
+      expect.push(0x8ea2b7ca);
+      expect.push(0x516745bf);
+      expect.push(0xeafc4990);
+      expect.push(0x4b496089);
+
+      test.expect.html('8ea2b7ca516745bfeafc49904b496089');
+
+      var output = [];
+      var w = forge.aes._expandKey(key, false);
+      forge.aes._updateBlock(w, block, output, false);
+
+      var out = forge.util.createBuffer();
+      out.putInt32(output[0]);
+      out.putInt32(output[1]);
+      out.putInt32(output[2]);
+      out.putInt32(output[3]);
+      test.result.html(out.toHex());
+
+      test.check();
+   });
+
+   addTest('aes-256 decrypt', function(task, test)
+   {
+      var block = [];
+      block.push(0x8ea2b7ca);
+      block.push(0x516745bf);
+      block.push(0xeafc4990);
+      block.push(0x4b496089);
+
+      var key = [];
+      key.push(0x00010203);
+      key.push(0x04050607);
+      key.push(0x08090a0b);
+      key.push(0x0c0d0e0f);
+      key.push(0x10111213);
+      key.push(0x14151617);
+      key.push(0x18191a1b);
+      key.push(0x1c1d1e1f);
+
+      var expect = [];
+      expect.push(0x00112233);
+      expect.push(0x44556677);
+      expect.push(0x8899aabb);
+      expect.push(0xccddeeff);
+
+      test.expect.html('00112233445566778899aabbccddeeff');
+
+      var output = [];
+      w = forge.aes._expandKey(key, true);
+      forge.aes._updateBlock(w, block, output, true);
+
+      var out = forge.util.createBuffer();
+      out.putInt32(output[0]);
+      out.putInt32(output[1]);
+      out.putInt32(output[2]);
+      out.putInt32(output[3]);
+      test.result.html(out.toHex());
+
+      test.check();
+   });
+
+   (function()
+   {
+      var keys = [
+         '06a9214036b8a15b512e03d534120006',
+         'c286696d887c9aa0611bbb3e2025a45a',
+         '6c3ea0477630ce21a2ce334aa746c2cd',
+         '56e47a38c5598974bc46903dba290349'
+      ];
+
+      var ivs = [
+         '3dafba429d9eb430b422da802c9fac41',
+         '562e17996d093d28ddb3ba695a2e6f58',
+         'c782dc4c098c66cbd9cd27d825682c81',
+         '8ce82eefbea0da3c44699ed7db51b7d9'
+      ];
+
+      var inputs = [
+         'Single block msg',
+         '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f',
+         'This is a 48-byte message (exactly 3 AES blocks)',
+         'a0a1a2a3a4a5a6a7a8a9aaabacadaeaf' +
+            'b0b1b2b3b4b5b6b7b8b9babbbcbdbebf' +
+            'c0c1c2c3c4c5c6c7c8c9cacbcccdcecf' +
+            'd0d1d2d3d4d5d6d7d8d9dadbdcdddedf'
+      ];
+
+      var outputs = [
+         'e353779c1079aeb82708942dbe77181a',
+         'd296cd94c2cccf8a3a863028b5e1dc0a7586602d253cfff91b8266bea6d61ab1',
+         'd0a02b3836451753d493665d33f0e886' +
+            '2dea54cdb293abc7506939276772f8d5' +
+            '021c19216bad525c8579695d83ba2684',
+         'c30e32ffedc0774e6aff6af0869f71aa' +
+            '0f3af07a9a31a9c684db207eb0ef8e4e' +
+            '35907aa632c3ffdf868bb7b29d3d46ad' +
+            '83ce9f9a102ee99d49a53e87f4c3da55'
+      ];
+
+      for(var i = 0; i < keys.length; ++i)
+      {
+         (function(i)
+         {
+            var key = forge.util.hexToBytes(keys[i]);
+            var iv = forge.util.hexToBytes(ivs[i]);
+            var input = (i & 1) ? forge.util.hexToBytes(inputs[i]) : inputs[i];
+            var output = forge.util.hexToBytes(outputs[i]);
+
+            addTest('aes-128 cbc encrypt', function(task, test)
+            {
+               // encrypt w/no padding
+               test.expect.html(outputs[i]);
+               var cipher = forge.aes.createEncryptionCipher(key);
+               cipher.start(iv);
+               cipher.update(forge.util.createBuffer(input));
+               cipher.finish(function(){return true;});
+               test.result.html(cipher.output.toHex());
+               test.check();
+            });
+
+            addTest('aes-128 cbc decrypt', function(task, test)
+            {
+               // decrypt w/no padding
+               test.expect.html(inputs[i]);
+               var cipher = forge.aes.createDecryptionCipher(key);
+               cipher.start(iv);
+               cipher.update(forge.util.createBuffer(output));
+               cipher.finish(function(){return true;});
+               var out = (i & 1) ?
+                  cipher.output.toHex() : cipher.output.bytes();
+               test.result.html(out);
+               test.check();
+            });
+         })(i);
+      }
+   })();
+
+   (function()
+   {
+      var keys = [
+         '00000000000000000000000000000000',
+         '2b7e151628aed2a6abf7158809cf4f3c',
+         '2b7e151628aed2a6abf7158809cf4f3c',
+         '2b7e151628aed2a6abf7158809cf4f3c',
+         '2b7e151628aed2a6abf7158809cf4f3c',
+         '00000000000000000000000000000000'
+      ];
+
+      var ivs = [
+         '80000000000000000000000000000000',
+         '000102030405060708090a0b0c0d0e0f',
+         '3B3FD92EB72DAD20333449F8E83CFB4A',
+         'C8A64537A0B3A93FCDE3CDAD9F1CE58B',
+         '26751F67A3CBB140B1808CF187A4F4DF',
+         '60f9ff04fac1a25657bf5b36b5efaf75'
+      ];
+
+      var inputs = [
+         '00000000000000000000000000000000',
+         '6bc1bee22e409f96e93d7e117393172a',
+         'ae2d8a571e03ac9c9eb76fac45af8e51',
+         '30c81c46a35ce411e5fbc1191a0a52ef',
+         'f69f2445df4f9b17ad2b417be66c3710',
+         'This is a 48-byte message (exactly 3 AES blocks)'
+      ];
+
+      var outputs = [
+         '3ad78e726c1ec02b7ebfe92b23d9ec34',
+         '3b3fd92eb72dad20333449f8e83cfb4a',
+         'c8a64537a0b3a93fcde3cdad9f1ce58b',
+         '26751f67a3cbb140b1808cf187a4f4df',
+         'c04b05357c5d1c0eeac4c66f9ff7f2e6',
+         '52396a2ba1ba420c5e5b699a814944d8' +
+           'f4e7fbf984a038319fbc0b4ee45cfa6f' +
+           '07b2564beab5b5e92dbd44cb345f49b4'
+      ];
+
+      for(var i = 0; i < keys.length; ++i)
+      {
+         (function(i)
+         {
+            var key = forge.util.hexToBytes(keys[i]);
+            var iv = forge.util.hexToBytes(ivs[i]);
+            var input = (i !== 5) ?
+               forge.util.hexToBytes(inputs[i]) : inputs[i];
+            var output = forge.util.hexToBytes(outputs[i]);
+
+            addTest('aes-128 cfb encrypt', function(task, test)
+            {
+               // encrypt
+               test.expect.html(outputs[i]);
+               var cipher = forge.aes.createEncryptionCipher(key, 'CFB');
+               cipher.start(iv);
+               cipher.update(forge.util.createBuffer(input));
+               cipher.finish();
+               test.result.html(cipher.output.toHex());
+               test.check();
+            });
+
+            addTest('aes-128 cfb decrypt', function(task, test)
+            {
+               // decrypt
+               test.expect.html(inputs[i]);
+               var cipher = forge.aes.createDecryptionCipher(key, 'CFB');
+               cipher.start(iv);
+               cipher.update(forge.util.createBuffer(output));
+               cipher.finish();
+               var out = (i !== 5) ?
+                 cipher.output.toHex() : cipher.output.getBytes();
+               test.result.html(out);
+               test.check();
+            });
+         })(i);
+      }
+   })();
+
+   (function()
+   {
+      var keys = [
+         '861009ec4d599fab1f40abc76e6f89880cff5833c79c548c99f9045f191cd90b'
+      ];
+
+      var ivs = [
+         'd927ad81199aa7dcadfdb4e47b6dc694'
+      ];
+
+      var inputs = [
+         'MY-DATA-AND-HERE-IS-MORE-DATA'
+      ];
+
+      var outputs = [
+         '80eb666a9fc9e263faf71e87ffc94451d7d8df7cfcf2606470351dd5ac'
+      ];
+
+      for(var i = 0; i < keys.length; ++i)
+      {
+         (function(i)
+         {
+            var key = forge.util.hexToBytes(keys[i]);
+            var iv = forge.util.hexToBytes(ivs[i]);
+            var input = inputs[i];
+            var output = forge.util.hexToBytes(outputs[i]);
+
+            addTest('aes-256 cfb encrypt', function(task, test)
+            {
+               // encrypt
+               test.expect.html(outputs[i]);
+               var cipher = forge.aes.createEncryptionCipher(key, 'CFB');
+               cipher.start(iv);
+               cipher.update(forge.util.createBuffer(input));
+               cipher.finish();
+               test.result.html(cipher.output.toHex());
+               test.check();
+            });
+
+            addTest('aes-256 cfb decrypt', function(task, test)
+            {
+               // decrypt
+               test.expect.html(inputs[i]);
+               var cipher = forge.aes.createDecryptionCipher(key, 'CFB');
+               cipher.start(iv);
+               cipher.update(forge.util.createBuffer(output));
+               cipher.finish();
+               var out = cipher.output.getBytes();
+               test.result.html(out);
+               test.check();
+            });
+         })(i);
+      }
+   })();
+
+   (function()
+   {
+      var keys = [
+         '00000000000000000000000000000000',
+         '00000000000000000000000000000000'
+      ];
+
+      var ivs = [
+         '80000000000000000000000000000000',
+         'c8ca0d6a35dbeac776e911ee16bea7d3'
+      ];
+
+      var inputs = [
+         '00000000000000000000000000000000',
+         'This is a 48-byte message (exactly 3 AES blocks)'
+      ];
+
+      var outputs = [
+         '3ad78e726c1ec02b7ebfe92b23d9ec34',
+         '39c0190727a76b2a90963426f63689cf' +
+           'cdb8a2be8e20c5e877a81a724e3611f6' +
+           '2ecc386f2e941b2441c838906002be19'
+      ];
+
+      for(var i = 0; i < keys.length; ++i)
+      {
+         (function(i)
+         {
+            var key = forge.util.hexToBytes(keys[i]);
+            var iv = forge.util.hexToBytes(ivs[i]);
+            var input = (i !== 1) ?
+               forge.util.hexToBytes(inputs[i]) : inputs[i];
+            var output = forge.util.hexToBytes(outputs[i]);
+
+            addTest('aes-128 ofb encrypt', function(task, test)
+            {
+               // encrypt w/no padding
+               test.expect.html(outputs[i]);
+               var cipher = forge.aes.createEncryptionCipher(key, 'OFB');
+               cipher.start(iv);
+               cipher.update(forge.util.createBuffer(input));
+               cipher.finish(function(){return true;});
+               test.result.html(cipher.output.toHex());
+               test.check();
+            });
+
+            addTest('aes-128 ofb decrypt', function(task, test)
+            {
+               // decrypt w/no padding
+               test.expect.html(inputs[i]);
+               var cipher = forge.aes.createDecryptionCipher(key, 'OFB');
+               cipher.start(iv);
+               cipher.update(forge.util.createBuffer(output));
+               cipher.finish(function(){return true;});
+               var out = (i !== 1) ?
+                 cipher.output.toHex() : cipher.output.getBytes();
+               test.result.html(out);
+               test.check();
+            });
+         })(i);
+      }
+   })();
+
+   (function()
+   {
+      var keys = [
+         '00000000000000000000000000000000',
+         '2b7e151628aed2a6abf7158809cf4f3c'
+      ];
+
+      var ivs = [
+         '650cdb80ff9fc758342d2bd99ee2abcf',
+         'f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff'
+      ];
+
+      var inputs = [
+         'This is a 48-byte message (exactly 3 AES blocks)',
+         '6bc1bee22e409f96e93d7e117393172a'
+      ];
+
+      var outputs = [
+         '5ede11d00e9a76ec1d5e7e811ea3dd1c' +
+           'e09ee941210f825d35718d3282796f1c' +
+           '07c3f1cb424f2b365766ab5229f5b5a4',
+         '874d6191b620e3261bef6864990db6ce'
+      ];
+
+      for(var i = 0; i < keys.length; ++i)
+      {
+         (function(i)
+         {
+            var key = forge.util.hexToBytes(keys[i]);
+            var iv = forge.util.hexToBytes(ivs[i]);
+            var input = (i !== 0) ?
+               forge.util.hexToBytes(inputs[i]) : inputs[i];
+            var output = forge.util.hexToBytes(outputs[i]);
+
+            addTest('aes-128 ctr encrypt', function(task, test)
+            {
+               // encrypt w/no padding
+               test.expect.html(outputs[i]);
+               var cipher = forge.aes.createEncryptionCipher(key, 'CTR');
+               cipher.start(iv);
+               cipher.update(forge.util.createBuffer(input));
+               cipher.finish(function(){return true;});
+               test.result.html(cipher.output.toHex());
+               test.check();
+            });
+
+            addTest('aes-128 ctr decrypt', function(task, test)
+            {
+               // decrypt w/no padding
+               test.expect.html(inputs[i]);
+               var cipher = forge.aes.createDecryptionCipher(key, 'CTR');
+               cipher.start(iv);
+               cipher.update(forge.util.createBuffer(output));
+               cipher.finish(function(){return true;});
+               var out = (i !== 0) ?
+                 cipher.output.toHex() : cipher.output.getBytes();
+               test.result.html(out);
+               test.check();
+            });
+         })(i);
+      }
+   })();
+
+   addTest('private key encryption', function(task, test)
+   {
+      var _privateKey =
+         '-----BEGIN RSA PRIVATE KEY-----\r\n' +
+         'MIICXQIBAAKBgQDL0EugUiNGMWscLAVM0VoMdhDZEJOqdsUMpx9U0YZI7szokJqQ\r\n' +
+         'NIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMGTkP3VF29vXBo+dLq5e+8VyAy\r\n' +
+         'Q3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGtvnM+z0MYDdKo80efzwIDAQAB\r\n' +
+         'AoGAIzkGONi5G+JifmXlLJdplom486p3upf4Ce2/7mqfaG9MnkyPSairKD/JXvfh\r\n' +
+         'NNWkkN8DKKDKBcVVElPgORYT0qwrWc7ueLBMUCbRXb1ZyfEulimG0R3kjUh7NYau\r\n' +
+         'DaIkVgfykXGSQMZx8FoaT6L080zd+0emKDDYRrb+/kgJNJECQQDoUZoiC2K/DWNY\r\n' +
+         'h3/ppZ0ane2y4SBmJUHJVMPQ2CEgxsrJTxet668ckNCKaOP/3VFPoWC41f17DvKq\r\n' +
+         'noYINNntAkEA4JbZBZBVUrQFhHlrpXT4jzqtO2RlKZzEq8qmFZfEErxOT1WMyyCi\r\n' +
+         'lAQ5gUKardo1Kf0omC8Xq/uO9ZYdED55KwJBALs6cJ65UFaq4oLJiQPzLd7yokuE\r\n' +
+         'dcj8g71PLBTW6jPxIiMFNA89nz3FU9wIVp+xbMNhSoMMKqIPVPC+m0Rn260CQQDA\r\n' +
+         'I83fWK/mZWUjBM33a68KumRiH238v8XyQxj7+C8i6D8G2GXvkigFAehAkb7LZZd+\r\n' +
+         'KLuGFyPlWv3fVWHf99KpAkBQFKk3MRMl6IGJZUEFQe4l5whm8LkGU4acSqv9B3xt\r\n' +
+         'qROkCrsFrMPqjuuzEmyHoQZ64r2PLJg7FOuyhBnQUOt4\r\n' +
+         '-----END RSA PRIVATE KEY-----';
+      var pk = forge.pki.privateKeyFromPem(_privateKey);
+      var pem1 = forge.pki.privateKeyToPem(pk);
+      var pem2 = forge.pki.encryptRsaPrivateKey(
+         pk, 'password', {'encAlg': 'aes128'});
+      var privateKey = forge.pki.decryptRsaPrivateKey(pem2, 'password');
+      var pem3 = forge.pki.privateKeyToPem(privateKey);
+      if(pem1 === pem3)
+      {
+         test.pass();
+      }
+      else
+      {
+         test.fail();
+      }
+   });
+
+   addTest('random', function(task, test)
+   {
+     forge.random.getBytes(16);
+     forge.random.getBytes(24);
+     forge.random.getBytes(32);
+
+      var b = forge.random.getBytes(10);
+      test.result.html(forge.util.bytesToHex(b));
+      if(b.length === 10)
+      {
+         test.pass();
+      }
+      else
+      {
+         test.fail();
+      }
+   });
+
+   addTest('asn.1 oid => der', function(task, test)
+   {
+      test.expect.html('2a864886f70d');
+      test.result.html(forge.asn1.oidToDer('1.2.840.113549').toHex());
+      test.check();
+   });
+
+   addTest('asn.1 der => oid', function(task, test)
+   {
+      var der = '2a864886f70d';
+      test.expect.html('1.2.840.113549');
+      test.result.html(forge.asn1.derToOid(forge.util.hexToBytes(der)));
+      test.check();
+   });
+
+   (function()
+   {
+      var _privateKey =
+      '-----BEGIN RSA PRIVATE KEY-----\r\n' +
+      'MIICXQIBAAKBgQDL0EugUiNGMWscLAVM0VoMdhDZEJOqdsUMpx9U0YZI7szokJqQ\r\n' +
+      'NIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMGTkP3VF29vXBo+dLq5e+8VyAy\r\n' +
+      'Q3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGtvnM+z0MYDdKo80efzwIDAQAB\r\n' +
+      'AoGAIzkGONi5G+JifmXlLJdplom486p3upf4Ce2/7mqfaG9MnkyPSairKD/JXvfh\r\n' +
+      'NNWkkN8DKKDKBcVVElPgORYT0qwrWc7ueLBMUCbRXb1ZyfEulimG0R3kjUh7NYau\r\n' +
+      'DaIkVgfykXGSQMZx8FoaT6L080zd+0emKDDYRrb+/kgJNJECQQDoUZoiC2K/DWNY\r\n' +
+      'h3/ppZ0ane2y4SBmJUHJVMPQ2CEgxsrJTxet668ckNCKaOP/3VFPoWC41f17DvKq\r\n' +
+      'noYINNntAkEA4JbZBZBVUrQFhHlrpXT4jzqtO2RlKZzEq8qmFZfEErxOT1WMyyCi\r\n' +
+      'lAQ5gUKardo1Kf0omC8Xq/uO9ZYdED55KwJBALs6cJ65UFaq4oLJiQPzLd7yokuE\r\n' +
+      'dcj8g71PLBTW6jPxIiMFNA89nz3FU9wIVp+xbMNhSoMMKqIPVPC+m0Rn260CQQDA\r\n' +
+      'I83fWK/mZWUjBM33a68KumRiH238v8XyQxj7+C8i6D8G2GXvkigFAehAkb7LZZd+\r\n' +
+      'KLuGFyPlWv3fVWHf99KpAkBQFKk3MRMl6IGJZUEFQe4l5whm8LkGU4acSqv9B3xt\r\n' +
+      'qROkCrsFrMPqjuuzEmyHoQZ64r2PLJg7FOuyhBnQUOt4\r\n' +
+      '-----END RSA PRIVATE KEY-----\r\n';
+
+      var _publicKey =
+      '-----BEGIN PUBLIC KEY-----\r\n' +
+      'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDL0EugUiNGMWscLAVM0VoMdhDZ\r\n' +
+      'EJOqdsUMpx9U0YZI7szokJqQNIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMG\r\n' +
+      'TkP3VF29vXBo+dLq5e+8VyAyQ3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGt\r\n' +
+      'vnM+z0MYDdKo80efzwIDAQAB\r\n' +
+      '-----END PUBLIC KEY-----\r\n';
+
+      var _certificate =
+      '-----BEGIN CERTIFICATE-----\r\n' +
+      'MIIDIjCCAougAwIBAgIJANE2aHSbwpaRMA0GCSqGSIb3DQEBBQUAMGoxCzAJBgNV\r\n' +
+      'BAYTAlVTMREwDwYDVQQIEwhWaXJnaW5pYTETMBEGA1UEBxMKQmxhY2tzYnVyZzEN\r\n' +
+      'MAsGA1UEChMEVGVzdDENMAsGA1UECxMEVGVzdDEVMBMGA1UEAxMMbXlzZXJ2ZXIu\r\n' +
+      'Y29tMB4XDTEwMDYxOTE3MzYyOFoXDTExMDYxOTE3MzYyOFowajELMAkGA1UEBhMC\r\n' +
+      'VVMxETAPBgNVBAgTCFZpcmdpbmlhMRMwEQYDVQQHEwpCbGFja3NidXJnMQ0wCwYD\r\n' +
+      'VQQKEwRUZXN0MQ0wCwYDVQQLEwRUZXN0MRUwEwYDVQQDEwxteXNlcnZlci5jb20w\r\n' +
+      'gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMvQS6BSI0YxaxwsBUzRWgx2ENkQ\r\n' +
+      'k6p2xQynH1TRhkjuzOiQmpA0jCiSJDoSic2dZIyUi/LjoCGeVFif57N5N5Tt4wZO\r\n' +
+      'Q/dUXb29cGj50url77xXIDJDcXMzXAji2ziFEAIXzDqarKBdDuL9IO7z+tepEa2+\r\n' +
+      'cz7PQxgN0qjzR5/PAgMBAAGjgc8wgcwwHQYDVR0OBBYEFPV1Y+DHXW6bA/r9sv1y\r\n' +
+      'NJ8jAwMAMIGcBgNVHSMEgZQwgZGAFPV1Y+DHXW6bA/r9sv1yNJ8jAwMAoW6kbDBq\r\n' +
+      'MQswCQYDVQQGEwJVUzERMA8GA1UECBMIVmlyZ2luaWExEzARBgNVBAcTCkJsYWNr\r\n' +
+      'c2J1cmcxDTALBgNVBAoTBFRlc3QxDTALBgNVBAsTBFRlc3QxFTATBgNVBAMTDG15\r\n' +
+      'c2VydmVyLmNvbYIJANE2aHSbwpaRMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEF\r\n' +
+      'BQADgYEARdH2KOlJWTC1CS2y/PAvg4uiM31PXMC1hqSdJlnLM1MY4hRfuf9VyTeX\r\n' +
+      'Y6FdybcyDLSxKn9id+g9229ci9/s9PI+QmD5vXd8yZyScLc2JkYB4GC6+9D1+/+x\r\n' +
+      's2hzMxuK6kzZlP+0l9LGcraMQPGRydjCARZZm4Uegln9rh85XFQ=\r\n' +
+      '-----END CERTIFICATE-----\r\n';
+
+      var _signature =
+         '9200ece65cdaed36bcc20b94c65af852e4f88f0b4fe5b249d54665f815992ac4' +
+         '3a1399e65d938c6a7f16dd39d971a53ca66523209dbbfbcb67afa579dbb0c220' +
+         '672813d9e6f4818f29b9becbb29da2032c5e422da97e0c39bfb7a2e7d568615a' +
+         '5073af0337ff215a8e1b2332d668691f4fb731440055420c24ac451dd3c913f4';
+
+      addTest('private key from pem/to pem', function(task, test)
+      {
+         try
+         {
+            // convert from pem
+            var key = forge.pki.privateKeyFromPem(_privateKey);
+            //forge.log.debug(cat, 'privateKey', key);
+
+            // convert back to pem
+            var pem = forge.pki.privateKeyToPem(key);
+            test.expect.html(_privateKey);
+            test.result.html(pem);
+            test.check();
+         }
+         catch(ex)
+         {
+            forge.log.error('test', ex);
+            test.fail();
+         }
+      });
+
+      addTest('public key from pem/to pem', function(task, test)
+      {
+         try
+         {
+            // convert from pem
+            var key = forge.pki.publicKeyFromPem(_publicKey);
+            //forge.log.debug(cat, 'publicKey', key);
+
+            // convert back to pem
+            var pem = forge.pki.publicKeyToPem(key);
+            test.expect.html(_publicKey);
+            test.result.html(pem);
+            test.check();
+         }
+         catch(ex)
+         {
+            forge.log.error('test', ex);
+            test.fail();
+         }
+      });
+
+      addTest('certificate key from pem/to pem', function(task, test)
+      {
+         try
+         {
+            var cert = forge.pki.certificateFromPem(_certificate);
+            /*
+            forge.log.debug(cat, 'cert', cert);
+            forge.log.debug(cat, 'CN', cert.subject.getField('CN').value);
+            forge.log.debug(cat, 'C',
+               cert.subject.getField({shortName: 'C'}).value);
+            forge.log.debug(cat, 'stateOrProvinceName',
+               cert.subject.getField({name: 'stateOrProvinceName'}).value);
+            forge.log.debug(cat, '2.5.4.7',
+               cert.subject.getField({type: '2.5.4.7'}).value);
+            */
+            // convert back to pem
+            var pem = forge.pki.certificateToPem(cert);
+            test.expect.html(_certificate);
+            test.result.html(pem);
+            test.check();
+         }
+         catch(ex)
+         {
+            forge.log.error('test', ex);
+            test.fail();
+         }
+      });
+
+      addTest('verify signature', function(task, test)
+      {
+         try
+         {
+            var key = forge.pki.publicKeyFromPem(_publicKey);
+            var md = forge.md.sha1.create();
+            md.update('0123456789abcdef');
+            var signature = forge.util.hexToBytes(_signature);
+            var success = key.verify(md.digest().getBytes(), signature);
+            if(success)
+            {
+               test.pass();
+            }
+            else
+            {
+               test.fail();
+            }
+         }
+         catch(ex)
+         {
+            forge.log.error('test', ex);
+            test.fail();
+         }
+      });
+
+      addTest('sign and verify', function(task, test)
+      {
+         try
+         {
+            var privateKey = forge.pki.privateKeyFromPem(_privateKey);
+            var publicKey = forge.pki.publicKeyFromPem(_publicKey);
+
+            // do sign
+            var md = forge.md.sha1.create();
+            md.update('0123456789abcdef');
+            var st = +new Date();
+            var signature = privateKey.sign(md);
+            var et = +new Date();
+            //forge.log.debug(cat, 'sign time', (et - st) + 'ms');
+
+            // do verify
+            st = +new Date();
+            var success = publicKey.verify(md.digest().getBytes(), signature);
+            et = +new Date();
+            //forge.log.debug(cat, 'verify time', (et - st) + 'ms');
+            if(success)
+            {
+               test.pass();
+            }
+            else
+            {
+               test.fail();
+            }
+         }
+         catch(ex)
+         {
+            forge.log.error('test', ex);
+            test.fail();
+         }
+      });
+
+      addTest('certificate verify', function(task, test)
+      {
+         try
+         {
+            var cert = forge.pki.certificateFromPem(_certificate, true);
+            //forge.log.debug(cat, 'cert', cert);
+            var success = cert.verify(cert);
+            if(success)
+            {
+               test.pass();
+            }
+            else
+            {
+               test.fail();
+            }
+         }
+         catch(ex)
+         {
+            forge.log.error('test', ex);
+            test.fail();
+         }
+      });
+   })();
+
+   addTest('TLS prf', function(task, test)
+   {
+      // Note: This test vector is originally from:
+      // http://www.imc.org/ietf-tls/mail-archive/msg01589.html
+      // But that link is now dead.
+      var secret = forge.util.createBuffer();
+      for(var i = 0; i < 48; ++i)
+      {
+         secret.putByte(0xAB);
+      }
+      secret = secret.getBytes();
+      var seed = forge.util.createBuffer();
+      for(var i = 0; i < 64; ++i)
+      {
+         seed.putByte(0xCD);
+      }
+      seed = seed.getBytes();
+
+      var bytes = forge.tls.prf_tls1(secret, 'PRF Testvector',  seed, 104);
+      var expect =
+         'd3d4d1e349b5d515044666d51de32bab258cb521' +
+         'b6b053463e354832fd976754443bcf9a296519bc' +
+         '289abcbc1187e4ebd31e602353776c408aafb74c' +
+         'bc85eff69255f9788faa184cbb957a9819d84a5d' +
+         '7eb006eb459d3ae8de9810454b8b2d8f1afbc655' +
+         'a8c9a013';
+      test.expect.html(expect);
+      test.result.html(bytes.toHex());
+      test.check();
+   });
+
+   // function to create certificate
+   var createCert = function(keys, cn, data)
+   {
+      var cert = forge.pki.createCertificate();
+      cert.serialNumber = '01';
+      cert.validity.notBefore = new Date();
+      cert.validity.notAfter = new Date();
+      cert.validity.notAfter.setFullYear(
+         cert.validity.notBefore.getFullYear() + 1);
+      var attrs = [{
+         name: 'commonName',
+         value: cn
+      }, {
+         name: 'countryName',
+         value: 'US'
+      }, {
+         shortName: 'ST',
+         value: 'Virginia'
+      }, {
+         name: 'localityName',
+         value: 'Blacksburg'
+      }, {
+         name: 'organizationName',
+         value: 'Test'
+      }, {
+         shortName: 'OU',
+         value: 'Test'
+      }];
+      cert.setSubject(attrs);
+      cert.setIssuer(attrs);
+      cert.setExtensions([{
+         name: 'basicConstraints',
+         cA: true
+      }, {
+         name: 'keyUsage',
+         keyCertSign: true,
+         digitalSignature: true,
+         nonRepudiation: true,
+         keyEncipherment: true,
+         dataEncipherment: true
+      }, {
+         name: 'subjectAltName',
+         altNames: [{
+            type: 6, // URI
+            value: 'http://myuri.com/webid#me'
+         }]
+      }]);
+      // FIXME: add subjectKeyIdentifier extension
+      // FIXME: add authorityKeyIdentifier extension
+      cert.publicKey = keys.publicKey;
+
+      // self-sign certificate
+      cert.sign(keys.privateKey);
+
+      // save data
+      data[cn] = {
+         cert: forge.pki.certificateToPem(cert),
+         privateKey: forge.pki.privateKeyToPem(keys.privateKey)
+      };
+   };
+
+   var generateCert = function(task, test, cn, data)
+   {
+      task.block();
+
+      // create key-generation state and function to step algorithm
+      test.result.html(
+         'Generating 512-bit key-pair and certificate for \"' + cn + '\".');
+      var state = forge.pki.rsa.createKeyPairGenerationState(512);
+      var kgTime = +new Date();
+      var step = function()
+      {
+         // step key-generation
+         if(!forge.pki.rsa.stepKeyPairGenerationState(state, 1000))
+         {
+            test.result.html(test.result.html() + '.');
+            setTimeout(step, 1);
+         }
+         // key-generation complete
+         else
+         {
+            kgTime = +new Date() - kgTime;
+            forge.log.debug(cat, 'Total key-gen time', kgTime + 'ms');
+            try
+            {
+               createCert(state.keys, cn, data);
+               test.result.html(
+                  test.result.html() + 'done. Time=' + kgTime + 'ms. ');
+               task.unblock();
+            }
+            catch(ex)
+            {
+               forge.log.error(cat, ex, ex.message ? ex.message : '');
+               test.result.html(ex.message);
+               test.fail();
+               task.fail();
+            }
+         }
+      };
+
+      // run key-gen algorithm
+      setTimeout(step, 0);
+   };
+
+   var clientSessionCache1 = forge.tls.createSessionCache();
+   var serverSessionCache1 = forge.tls.createSessionCache();
+   addTest('TLS connection, w/o client-certificate', function(task, test)
+   {
+      var data = {};
+
+      task.next('generate server certifcate', function(task)
+      {
+         generateCert(task, test, 'server', data);
+      });
+
+      task.next('starttls', function(task)
+      {
+         test.result.html(test.result.html() + 'Starting TLS...');
+
+         var end =
+         {
+            client: null,
+            server: null
+         };
+         var success = false;
+
+         // create client
+         end.client = forge.tls.createConnection(
+         {
+            server: false,
+            caStore: [data.server.cert],
+            sessionCache: clientSessionCache1,
+            // optional cipher suites in order of preference
+            cipherSuites: [
+               forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+               forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+            virtualHost: 'server',
+            verify: function(c, verified, depth, certs)
+            {
+               test.result.html(test.result.html() +
+                  'Client verifying certificate w/CN: \"' +
+                  certs[0].subject.getField('CN').value +
+                  '\", verified: ' + verified + '...');
+               if(verified !== true)
+               {
+                  test.fail();
+                  task.fail();
+               }
+               return verified;
+            },
+            connected: function(c)
+            {
+               test.result.html(test.result.html() + 'Client connected...');
+
+               // send message to server
+               setTimeout(function()
+               {
+                  c.prepare('Hello Server');
+               }, 1);
+            },
+            tlsDataReady: function(c)
+            {
+               // send TLS data to server
+               end.server.process(c.tlsData.getBytes());
+            },
+            dataReady: function(c)
+            {
+               var response = c.data.getBytes();
+               test.result.html(test.result.html() +
+                  'Client received \"' + response + '\"');
+               success = (response === 'Hello Client');
+               c.close();
+            },
+            closed: function(c)
+            {
+               test.result.html(test.result.html() + 'Client disconnected.');
+               test.result.html(success ? 'Success' : 'Failure');
+               if(success)
+               {
+                  test.expect.html('Success');
+                  task.unblock();
+                  test.pass();
+               }
+               else
+               {
+                  console.log('closed fail');
+                  test.fail();
+                  task.fail();
+               }
+            },
+            error: function(c, error)
+            {
+               test.result.html(test.result.html() + 'Error: ' + error.message);
+               test.fail();
+               task.fail();
+            }
+         });
+
+         // create server
+         end.server = forge.tls.createConnection(
+         {
+            server: true,
+            sessionCache: serverSessionCache1,
+            // optional cipher suites in order of preference
+            cipherSuites: [
+               forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+               forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+            connected: function(c)
+            {
+               test.result.html(test.result.html() + 'Server connected...');
+            },
+            getCertificate: function(c, hint)
+            {
+               test.result.html(test.result.html() +
+                  'Server getting certificate for \"' + hint[0] + '\"...');
+               return data.server.cert;
+            },
+            getPrivateKey: function(c, cert)
+            {
+               return data.server.privateKey;
+            },
+            tlsDataReady: function(c)
+            {
+               // send TLS data to client
+               end.client.process(c.tlsData.getBytes());
+            },
+            dataReady: function(c)
+            {
+               test.result.html(test.result.html() +
+                  'Server received \"' + c.data.getBytes() + '\"');
+
+               // send response
+               c.prepare('Hello Client');
+               c.close();
+            },
+            closed: function(c)
+            {
+               test.result.html(test.result.html() + 'Server disconnected.');
+            },
+            error: function(c, error)
+            {
+               test.result.html(test.result.html() + 'Error: ' + error.message);
+               test.fail();
+               task.fail();
+            }
+         });
+
+         // start handshake
+         task.block();
+         end.client.handshake();
+      });
+   });
+
+   var clientSessionCache2 = forge.tls.createSessionCache();
+   var serverSessionCache2 = forge.tls.createSessionCache();
+   addTest('TLS connection, w/optional client-certificate', function(task, test)
+   {
+      var data = {};
+
+      task.next('generate server certifcate', function(task)
+      {
+         generateCert(task, test, 'server', data);
+      });
+
+      // client-cert generated but not sent in this test
+      task.next('generate client certifcate', function(task)
+      {
+         generateCert(task, test, 'client', data);
+      });
+
+      task.next('starttls', function(task)
+      {
+         test.result.html(test.result.html() + 'Starting TLS...');
+
+         var end =
+         {
+            client: null,
+            server: null
+         };
+         var success = false;
+
+         // create client
+         end.client = forge.tls.createConnection(
+         {
+            server: false,
+            caStore: [data.server.cert],
+            sessionCache: clientSessionCache2,
+            // supported cipher suites in order of preference
+            cipherSuites: [
+               forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+               forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+            virtualHost: 'server',
+            verify: function(c, verified, depth, certs)
+            {
+               test.result.html(test.result.html() +
+                  'Client verifying certificate w/CN: \"' +
+                  certs[0].subject.getField('CN').value +
+                  '\", verified: ' + verified + '...');
+               if(verified !== true)
+               {
+                  test.fail();
+                  task.fail();
+               }
+               return verified;
+            },
+            connected: function(c)
+            {
+               test.result.html(test.result.html() + 'Client connected...');
+
+               // send message to server
+               setTimeout(function()
+               {
+                  c.prepare('Hello Server');
+               }, 1);
+            },
+            tlsDataReady: function(c)
+            {
+               // send TLS data to server
+               end.server.process(c.tlsData.getBytes());
+            },
+            dataReady: function(c)
+            {
+               var response = c.data.getBytes();
+               test.result.html(test.result.html() +
+                  'Client received \"' + response + '\"');
+               success = (response === 'Hello Client');
+               c.close();
+            },
+            closed: function(c)
+            {
+               test.result.html(test.result.html() + 'Client disconnected.');
+               test.result.html(success ? 'Success' : 'Failure');
+               if(success)
+               {
+                  test.expect.html('Success');
+                  task.unblock();
+                  test.pass();
+               }
+               else
+               {
+                  console.log('closed fail');
+                  test.fail();
+                  task.fail();
+               }
+            },
+            error: function(c, error)
+            {
+               test.result.html(test.result.html() + 'Error: ' + error.message);
+               test.fail();
+               task.fail();
+            }
+         });
+
+         // create server
+         end.server = forge.tls.createConnection(
+         {
+            server: true,
+            caStore: [data.client.cert],
+            sessionCache: serverSessionCache2,
+            // supported cipher suites in order of preference
+            cipherSuites: [
+               forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+               forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+            connected: function(c)
+            {
+               test.result.html(test.result.html() + 'Server connected...');
+            },
+            verifyClient: 'optional',
+            verify: function(c, verified, depth, certs)
+            {
+               test.result.html(test.result.html() +
+                  'Server verifying certificate w/CN: \"' +
+                  certs[0].subject.getField('CN').value +
+                  '\", verified: ' + verified + '...');
+               if(verified !== true)
+               {
+                  test.fail();
+                  task.fail();
+               }
+               return verified;
+            },
+            getCertificate: function(c, hint)
+            {
+               test.result.html(test.result.html() +
+                  'Server getting certificate for \"' + hint[0] + '\"...');
+               return data.server.cert;
+            },
+            getPrivateKey: function(c, cert)
+            {
+               return data.server.privateKey;
+            },
+            tlsDataReady: function(c)
+            {
+               // send TLS data to client
+               end.client.process(c.tlsData.getBytes());
+            },
+            dataReady: function(c)
+            {
+               test.result.html(test.result.html() +
+                  'Server received \"' + c.data.getBytes() + '\"');
+
+               // send response
+               c.prepare('Hello Client');
+               c.close();
+            },
+            closed: function(c)
+            {
+               test.result.html(test.result.html() + 'Server disconnected.');
+            },
+            error: function(c, error)
+            {
+               test.result.html(test.result.html() + 'Error: ' + error.message);
+               test.fail();
+               task.fail();
+            }
+         });
+
+         // start handshake
+         task.block();
+         end.client.handshake();
+      });
+   });
+
+   var clientSessionCache3 = forge.tls.createSessionCache();
+   var serverSessionCache3 = forge.tls.createSessionCache();
+   addTest('TLS connection, w/client-certificate', function(task, test)
+   {
+      var data = {};
+
+      task.next('generate server certifcate', function(task)
+      {
+         generateCert(task, test, 'server', data);
+      });
+
+      task.next('generate client certifcate', function(task)
+      {
+         generateCert(task, test, 'client', data);
+      });
+
+      task.next('starttls', function(task)
+      {
+         test.result.html(test.result.html() + 'Starting TLS...');
+
+         var end =
+         {
+            client: null,
+            server: null
+         };
+         var success = false;
+
+         // create client
+         end.client = forge.tls.createConnection(
+         {
+            server: false,
+            caStore: [data.server.cert],
+            sessionCache: clientSessionCache3,
+            // supported cipher suites in order of preference
+            cipherSuites: [
+               forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+               forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+            virtualHost: 'server',
+            verify: function(c, verified, depth, certs)
+            {
+               test.result.html(test.result.html() +
+                  'Client verifying certificate w/CN: \"' +
+                  certs[0].subject.getField('CN').value +
+                  '\", verified: ' + verified + '...');
+               if(verified !== true)
+               {
+                  test.fail();
+                  task.fail();
+               }
+               return verified;
+            },
+            connected: function(c)
+            {
+               test.result.html(test.result.html() + 'Client connected...');
+
+               // send message to server
+               setTimeout(function()
+               {
+                  c.prepare('Hello Server');
+               }, 1);
+            },
+            getCertificate: function(c, hint)
+            {
+               test.result.html(test.result.html() +
+                  'Client getting certificate ...');
+               return data.client.cert;
+            },
+            getPrivateKey: function(c, cert)
+            {
+               return data.client.privateKey;
+            },
+            tlsDataReady: function(c)
+            {
+               // send TLS data to server
+               end.server.process(c.tlsData.getBytes());
+            },
+            dataReady: function(c)
+            {
+               var response = c.data.getBytes();
+               test.result.html(test.result.html() +
+                  'Client received \"' + response + '\"');
+               success = (response === 'Hello Client');
+               c.close();
+            },
+            closed: function(c)
+            {
+               test.result.html(test.result.html() + 'Client disconnected.');
+               test.result.html(success ? 'Success' : 'Failure');
+               if(success)
+               {
+                  test.expect.html('Success');
+                  task.unblock();
+                  test.pass();
+               }
+               else
+               {
+                  console.log('closed fail');
+                  test.fail();
+                  task.fail();
+               }
+            },
+            error: function(c, error)
+            {
+               test.result.html(test.result.html() + 'Error: ' + error.message);
+               test.fail();
+               task.fail();
+            }
+         });
+
+         // create server
+         end.server = forge.tls.createConnection(
+         {
+            server: true,
+            caStore: [data.client.cert],
+            sessionCache: serverSessionCache3,
+            // supported cipher suites in order of preference
+            cipherSuites: [
+               forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+               forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+            connected: function(c)
+            {
+               test.result.html(test.result.html() + 'Server connected...');
+            },
+            verifyClient: true, // use 'optional' to request but not require
+            verify: function(c, verified, depth, certs)
+            {
+               test.result.html(test.result.html() +
+                  'Server verifying certificate w/CN: \"' +
+                  certs[0].subject.getField('CN').value +
+                  '\", verified: ' + verified + '...');
+               if(verified !== true)
+               {
+                  test.fail();
+                  task.fail();
+               }
+               return verified;
+            },
+            getCertificate: function(c, hint)
+            {
+               test.result.html(test.result.html() +
+                  'Server getting certificate for \"' + hint[0] + '\"...');
+               return data.server.cert;
+            },
+            getPrivateKey: function(c, cert)
+            {
+               return data.server.privateKey;
+            },
+            tlsDataReady: function(c)
+            {
+               // send TLS data to client
+               end.client.process(c.tlsData.getBytes());
+            },
+            dataReady: function(c)
+            {
+               test.result.html(test.result.html() +
+                  'Server received \"' + c.data.getBytes() + '\"');
+
+               // send response
+               c.prepare('Hello Client');
+               c.close();
+            },
+            closed: function(c)
+            {
+               test.result.html(test.result.html() + 'Server disconnected.');
+            },
+            error: function(c, error)
+            {
+               test.result.html(test.result.html() + 'Error: ' + error.message);
+               test.fail();
+               task.fail();
+            }
+         });
+
+         // start handshake
+         task.block();
+         end.client.handshake();
+      });
+   });
+
+   init();
+});
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/favicon.ico b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/favicon.ico
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/favicon.ico
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/flash/Test.as b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/flash/Test.as
new file mode 100644
index 0000000..7c03727
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/flash/Test.as
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2010 Digital Bazaar, Inc. All rights reserved.
+ * 
+ * @author Dave Longley
+ */
+package
+{
+   import flash.display.Sprite;
+   
+   public class Test extends Sprite
+   {
+      import flash.events.*;
+      import flash.net.*;
+      
+      import flash.external.ExternalInterface;
+      import flash.system.Security;
+      
+      public function Test()
+      {
+         try
+         {
+            // FIXME: replace 'localhost' with cross-domain host to hit
+            var xhost:String = "localhost";
+            Security.loadPolicyFile("xmlsocket://" + xhost + ":80");
+            
+            var loader:URLLoader = new URLLoader();
+            loader.addEventListener(
+               Event.COMPLETE, completeHandler);
+            loader.addEventListener(
+               Event.OPEN, openHandler);
+            loader.addEventListener(
+               ProgressEvent.PROGRESS, progressHandler);
+            loader.addEventListener(
+               SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
+            loader.addEventListener(
+               HTTPStatusEvent.HTTP_STATUS, httpStatusHandler);
+            loader.addEventListener(
+               IOErrorEvent.IO_ERROR, ioErrorHandler);
+            
+            var request:URLRequest = new URLRequest(
+               "http://" + xhost + "/index.html");
+            loader.load(request);
+         }
+         catch(e:Error)
+         {
+            log("error=" + e.errorID + "," + e.name + "," + e.message);
+            throw e;
+         }
+      }
+      
+      private function log(obj:Object):void
+      {
+         if(obj is String)
+         {
+            var str:String = obj as String;
+            ExternalInterface.call("console.log", "Test", str);
+         }
+         else if(obj is Error)
+         {
+            var e:Error = obj as Error;
+            log("error=" + e.errorID + "," + e.name + "," + e.message);
+         }
+      }
+      
+      private function completeHandler(event:Event):void
+      {
+         var loader:URLLoader = URLLoader(event.target);
+         log("complete: " + loader.data);
+      }
+
+      private function openHandler(event:Event):void
+      {
+         log("open: " + event);
+      }
+
+      private function progressHandler(event:ProgressEvent):void
+      {
+         log("progress:" + event.bytesLoaded + " total: " + event.bytesTotal);
+      }
+
+      private function securityErrorHandler(event:SecurityErrorEvent):void
+      {
+         log("securityError: " + event);
+      }
+
+      private function httpStatusHandler(event:HTTPStatusEvent):void
+      {
+         log("httpStatus: " + event);
+      }
+
+      private function ioErrorHandler(event:IOErrorEvent):void
+      {
+         log("ioError: " + event);
+      }
+   }
+}
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/flash/build-flash.xml b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/flash/build-flash.xml
new file mode 100644
index 0000000..f037c58
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/flash/build-flash.xml
@@ -0,0 +1,7 @@
+<flex-config>
+   <compiler>
+      <source-path>
+         <path-element>.</path-element>
+      </source-path>
+   </compiler>
+</flex-config>
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/flash/index.html b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/flash/index.html
new file mode 100644
index 0000000..26a10b8
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/flash/index.html
@@ -0,0 +1,27 @@
+<html>
+   <head>
+      <link type="text/css" rel="stylesheet" media="all" href="screen.css" />
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js"></script>
+      
+      <script type="text/javascript">
+      //<![CDATA[
+      swfobject.embedSWF(
+         'Test.swf', 'test', '0', '0', '9.0.0',
+         false, {}, {allowscriptaccess: 'always'}, {});
+      //]]>
+      </script>
+   </head>
+   <body>
+      <div class="header">
+         <h1>Flash Cross-Domain URLLoader Test</h1>
+      </div>
+
+      <div class="content">
+
+      <div id="test">
+         <p>Could not load the flash test.</p>
+      </div>
+
+      </div>
+   </body>
+</html>
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/forge_ssl/forge/__init__.py b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/forge_ssl/forge/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/forge_ssl/forge/__init__.py
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/forge_ssl/forge/_ssl.c b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/forge_ssl/forge/_ssl.c
new file mode 100644
index 0000000..bdef8c9
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/forge_ssl/forge/_ssl.c
@@ -0,0 +1,1770 @@
+/* SSL socket module
+
+   SSL support based on patches by Brian E Gallew and Laszlo Kovacs.
+   Re-worked a bit by Bill Janssen to add server-side support and
+   certificate decoding.  Chris Stawarz contributed some non-blocking
+   patches.
+
+   This module is imported by ssl.py. It should *not* be used
+   directly.
+
+   XXX should partial writes be enabled, SSL_MODE_ENABLE_PARTIAL_WRITE?
+
+   XXX integrate several "shutdown modes" as suggested in
+       http://bugs.python.org/issue8108#msg102867 ?
+*/
+
+#include "Python.h"
+
+#ifdef WITH_THREAD
+#include "pythread.h"
+#define PySSL_BEGIN_ALLOW_THREADS { \
+            PyThreadState *_save = NULL;  \
+            if (_ssl_locks_count>0) {_save = PyEval_SaveThread();}
+#define PySSL_BLOCK_THREADS     if (_ssl_locks_count>0){PyEval_RestoreThread(_save)};
+#define PySSL_UNBLOCK_THREADS   if (_ssl_locks_count>0){_save = PyEval_SaveThread()};
+#define PySSL_END_ALLOW_THREADS if (_ssl_locks_count>0){PyEval_RestoreThread(_save);} \
+         }
+
+#else   /* no WITH_THREAD */
+
+#define PySSL_BEGIN_ALLOW_THREADS
+#define PySSL_BLOCK_THREADS
+#define PySSL_UNBLOCK_THREADS
+#define PySSL_END_ALLOW_THREADS
+
+#endif
+
+enum py_ssl_error {
+    /* these mirror ssl.h */
+    PY_SSL_ERROR_NONE,
+    PY_SSL_ERROR_SSL,
+    PY_SSL_ERROR_WANT_READ,
+    PY_SSL_ERROR_WANT_WRITE,
+    PY_SSL_ERROR_WANT_X509_LOOKUP,
+    PY_SSL_ERROR_SYSCALL,     /* look at error stack/return value/errno */
+    PY_SSL_ERROR_ZERO_RETURN,
+    PY_SSL_ERROR_WANT_CONNECT,
+    /* start of non ssl.h errorcodes */
+    PY_SSL_ERROR_EOF,         /* special case of SSL_ERROR_SYSCALL */
+    PY_SSL_ERROR_INVALID_ERROR_CODE
+};
+
+enum py_ssl_server_or_client {
+    PY_SSL_CLIENT,
+    PY_SSL_SERVER
+};
+
+enum py_ssl_cert_requirements {
+    PY_SSL_CERT_NONE,
+    PY_SSL_CERT_OPTIONAL,
+    PY_SSL_CERT_REQUIRED
+};
+
+enum py_ssl_version {
+    PY_SSL_VERSION_SSL2,
+    PY_SSL_VERSION_SSL3,
+    PY_SSL_VERSION_SSL23,
+    PY_SSL_VERSION_TLS1
+};
+
+enum py_ssl_sess_cache_mode {
+    PY_SSL_SESS_CACHE_OFF,
+    PY_SSL_SESS_CACHE_CLIENT,
+    PY_SSL_SESS_CACHE_SERVER,
+    PY_SSL_SESS_CACHE_BOTH
+};
+
+/* Include symbols from _socket module */
+#include "socketmodule.h"
+
+#if defined(HAVE_POLL_H)
+#include <poll.h>
+#elif defined(HAVE_SYS_POLL_H)
+#include <sys/poll.h>
+#endif
+
+/* Include OpenSSL header files */
+#include "openssl/rsa.h"
+#include "openssl/crypto.h"
+#include "openssl/x509.h"
+#include "openssl/x509v3.h"
+#include "openssl/pem.h"
+#include "openssl/ssl.h"
+#include "openssl/err.h"
+#include "openssl/rand.h"
+
+/* SSL error object */
+static PyObject *PySSLErrorObject;
+
+#ifdef WITH_THREAD
+
+/* serves as a flag to see whether we've initialized the SSL thread support. */
+/* 0 means no, greater than 0 means yes */
+
+static unsigned int _ssl_locks_count = 0;
+
+#endif /* def WITH_THREAD */
+
+/* SSL socket object */
+
+#define X509_NAME_MAXLEN 256
+
+/* RAND_* APIs got added to OpenSSL in 0.9.5 */
+#if OPENSSL_VERSION_NUMBER >= 0x0090500fL
+# define HAVE_OPENSSL_RAND 1
+#else
+# undef HAVE_OPENSSL_RAND
+#endif
+
+typedef struct {
+    PyObject_HEAD
+    PySocketSockObject *Socket;         /* Socket on which we're layered */
+    int                 inherited;
+    SSL_CTX*            ctx;
+    SSL*                ssl;
+    X509*               peer_cert;
+    char                server[X509_NAME_MAXLEN];
+    char                issuer[X509_NAME_MAXLEN];
+    int                 shutdown_seen_zero;
+
+} PySSLObject;
+
+static PyTypeObject PySSL_Type;
+static PyObject *PySSL_SSLwrite(PySSLObject *self, PyObject *args);
+static PyObject *PySSL_SSLread(PySSLObject *self, PyObject *args);
+static int check_socket_and_wait_for_timeout(PySocketSockObject *s,
+                                             int writing);
+static PyObject *PySSL_peercert(PySSLObject *self, PyObject *args);
+static PyObject *PySSL_cipher(PySSLObject *self);
+
+#define PySSLObject_Check(v)    (Py_TYPE(v) == &PySSL_Type)
+
+typedef enum {
+    SOCKET_IS_NONBLOCKING,
+    SOCKET_IS_BLOCKING,
+    SOCKET_HAS_TIMED_OUT,
+    SOCKET_HAS_BEEN_CLOSED,
+    SOCKET_TOO_LARGE_FOR_SELECT,
+    SOCKET_OPERATION_OK
+} timeout_state;
+
+/* Wrap error strings with filename and line # */
+#define STRINGIFY1(x) #x
+#define STRINGIFY2(x) STRINGIFY1(x)
+#define ERRSTR1(x,y,z) (x ":" y ": " z)
+#define ERRSTR(x) ERRSTR1("_ssl.c", STRINGIFY2(__LINE__), x)
+
+/* XXX It might be helpful to augment the error message generated
+   below with the name of the SSL function that generated the error.
+   I expect it's obvious most of the time.
+*/
+
+static PyObject *
+PySSL_SetError(PySSLObject *obj, int ret, char *filename, int lineno)
+{
+    PyObject *v;
+    char buf[2048];
+    char *errstr;
+    int err;
+    enum py_ssl_error p = PY_SSL_ERROR_NONE;
+
+    assert(ret <= 0);
+
+    if (obj->ssl != NULL) {
+        err = SSL_get_error(obj->ssl, ret);
+
+        switch (err) {
+        case SSL_ERROR_ZERO_RETURN:
+            errstr = "TLS/SSL connection has been closed";
+            p = PY_SSL_ERROR_ZERO_RETURN;
+            break;
+        case SSL_ERROR_WANT_READ:
+            errstr = "The operation did not complete (read)";
+            p = PY_SSL_ERROR_WANT_READ;
+            break;
+        case SSL_ERROR_WANT_WRITE:
+            p = PY_SSL_ERROR_WANT_WRITE;
+            errstr = "The operation did not complete (write)";
+            break;
+        case SSL_ERROR_WANT_X509_LOOKUP:
+            p = PY_SSL_ERROR_WANT_X509_LOOKUP;
+            errstr = "The operation did not complete (X509 lookup)";
+            break;
+        case SSL_ERROR_WANT_CONNECT:
+            p = PY_SSL_ERROR_WANT_CONNECT;
+            errstr = "The operation did not complete (connect)";
+            break;
+        case SSL_ERROR_SYSCALL:
+        {
+            unsigned long e = ERR_get_error();
+            if (e == 0) {
+                if (ret == 0 || !obj->Socket) {
+                    p = PY_SSL_ERROR_EOF;
+                    errstr = "EOF occurred in violation of protocol";
+                } else if (ret == -1) {
+                    /* underlying BIO reported an I/O error */
+                    ERR_clear_error();
+                    return obj->Socket->errorhandler();
+                } else { /* possible? */
+                    p = PY_SSL_ERROR_SYSCALL;
+                    errstr = "Some I/O error occurred";
+                }
+            } else {
+                p = PY_SSL_ERROR_SYSCALL;
+                /* XXX Protected by global interpreter lock */
+                errstr = ERR_error_string(e, NULL);
+            }
+            break;
+        }
+        case SSL_ERROR_SSL:
+        {
+            unsigned long e = ERR_get_error();
+            p = PY_SSL_ERROR_SSL;
+            if (e != 0)
+                /* XXX Protected by global interpreter lock */
+                errstr = ERR_error_string(e, NULL);
+            else {              /* possible? */
+                errstr = "A failure in the SSL library occurred";
+            }
+            break;
+        }
+        default:
+            p = PY_SSL_ERROR_INVALID_ERROR_CODE;
+            errstr = "Invalid error code";
+        }
+    } else {
+        errstr = ERR_error_string(ERR_peek_last_error(), NULL);
+    }
+    PyOS_snprintf(buf, sizeof(buf), "_ssl.c:%d: %s", lineno, errstr);
+    ERR_clear_error();
+    v = Py_BuildValue("(is)", p, buf);
+    if (v != NULL) {
+        PyErr_SetObject(PySSLErrorObject, v);
+        Py_DECREF(v);
+    }
+    return NULL;
+}
+
+static PyObject *
+_setSSLError (char *errstr, int errcode, char *filename, int lineno) {
+
+    char buf[2048];
+    PyObject *v;
+
+    if (errstr == NULL) {
+        errcode = ERR_peek_last_error();
+        errstr = ERR_error_string(errcode, NULL);
+    }
+    PyOS_snprintf(buf, sizeof(buf), "_ssl.c:%d: %s", lineno, errstr);
+    ERR_clear_error();
+    v = Py_BuildValue("(is)", errcode, buf);
+    if (v != NULL) {
+        PyErr_SetObject(PySSLErrorObject, v);
+        Py_DECREF(v);
+    }
+    return NULL;
+}
+
+static PySSLObject *
+newPySSLObject(PySSLObject *ssl_object, PySocketSockObject *Sock,
+               char *key_file, char *cert_file,
+               enum py_ssl_server_or_client socket_type,
+               enum py_ssl_cert_requirements certreq,
+               enum py_ssl_version proto_version,
+               enum py_ssl_sess_cache_mode cache_mode,
+               char *sess_id_ctx,
+               char *cacerts_file)
+{
+    PySSLObject *self;
+    char *errstr = NULL;
+    int ret;
+    int verification_mode;
+    int sess_cache_mode;
+
+    self = PyObject_New(PySSLObject, &PySSL_Type); /* Create new object */
+    if (self == NULL)
+        return NULL;
+    memset(self->server, '\0', sizeof(char) * X509_NAME_MAXLEN);
+    memset(self->issuer, '\0', sizeof(char) * X509_NAME_MAXLEN);
+    self->peer_cert = NULL;
+    self->inherited = 0;
+    self->ssl = NULL;
+    self->ctx = NULL;
+    self->Socket = NULL;
+
+    /* Make sure the SSL error state is initialized */
+    (void) ERR_get_state();
+    ERR_clear_error();
+
+    if ((key_file && !cert_file) || (!key_file && cert_file)) {
+        errstr = ERRSTR("Both the key & certificate files "
+                        "must be specified");
+        goto fail;
+    }
+
+    if ((socket_type == PY_SSL_SERVER) && (ssl_object == NULL) &&
+        ((key_file == NULL) || (cert_file == NULL))) {
+        errstr = ERRSTR("Both the key & certificate files "
+                        "must be specified for server-side operation");
+        goto fail;
+    }
+
+    if (ssl_object != NULL) {
+        self->inherited = 1;
+        self->ctx = ssl_object->ctx;
+    } else {
+        self->inherited = 0;
+
+        PySSL_BEGIN_ALLOW_THREADS
+        if (proto_version == PY_SSL_VERSION_TLS1)
+            self->ctx = SSL_CTX_new(TLSv1_method()); /* Set up context */
+        else if (proto_version == PY_SSL_VERSION_SSL3)
+            self->ctx = SSL_CTX_new(SSLv3_method()); /* Set up context */
+        else if (proto_version == PY_SSL_VERSION_SSL2)
+            self->ctx = SSL_CTX_new(SSLv2_method()); /* Set up context */
+        else if (proto_version == PY_SSL_VERSION_SSL23)
+            self->ctx = SSL_CTX_new(SSLv23_method()); /* Set up context */
+        PySSL_END_ALLOW_THREADS
+    }
+
+    if (self->ctx == NULL) {
+        errstr = ERRSTR("Invalid SSL protocol variant specified.");
+        goto fail;
+    }
+
+    if (self->inherited == 0 && certreq != PY_SSL_CERT_NONE) {
+        if (cacerts_file == NULL) {
+            errstr = ERRSTR("No root certificates specified for "
+                            "verification of other-side certificates.");
+            goto fail;
+        } else {
+            PySSL_BEGIN_ALLOW_THREADS
+            ret = SSL_CTX_load_verify_locations(self->ctx,
+                                                cacerts_file,
+                                                NULL);
+            PySSL_END_ALLOW_THREADS
+            if (ret != 1) {
+                _setSSLError(NULL, 0, __FILE__, __LINE__);
+                goto fail;
+            }
+        }
+    }
+    if (self->inherited == 0 && key_file) {
+        PySSL_BEGIN_ALLOW_THREADS
+        ret = SSL_CTX_use_PrivateKey_file(self->ctx, key_file,
+                                          SSL_FILETYPE_PEM);
+        PySSL_END_ALLOW_THREADS
+        if (ret != 1) {
+            _setSSLError(NULL, ret, __FILE__, __LINE__);
+            goto fail;
+        }
+
+        PySSL_BEGIN_ALLOW_THREADS
+        ret = SSL_CTX_use_certificate_chain_file(self->ctx,
+                                                 cert_file);
+        PySSL_END_ALLOW_THREADS
+        if (ret != 1) {
+            /*
+            fprintf(stderr, "ret is %d, errcode is %lu, %lu, with file \"%s\"\n",
+                ret, ERR_peek_error(), ERR_peek_last_error(), cert_file);
+                */
+            if (ERR_peek_last_error() != 0) {
+                _setSSLError(NULL, ret, __FILE__, __LINE__);
+                goto fail;
+            }
+        }
+    }
+
+    if (self->inherited == 0) {
+        /* ssl compatibility */
+        SSL_CTX_set_options(self->ctx, SSL_OP_ALL);
+
+        /* session cache mode */
+        PySSL_BEGIN_ALLOW_THREADS
+        sess_cache_mode = SSL_SESS_CACHE_SERVER;
+        if (cache_mode == PY_SSL_SESS_CACHE_OFF)
+            sess_cache_mode = SSL_SESS_CACHE_OFF;
+        else if (cache_mode == PY_SSL_SESS_CACHE_CLIENT)
+            sess_cache_mode = SSL_SESS_CACHE_CLIENT;
+        else if (cache_mode == PY_SSL_SESS_CACHE_SERVER)
+            sess_cache_mode = SSL_SESS_CACHE_SERVER;
+        else if (cache_mode == PY_SSL_SESS_CACHE_BOTH)
+            sess_cache_mode = SSL_SESS_CACHE_BOTH;
+        SSL_CTX_set_session_cache_mode(self->ctx, sess_cache_mode);
+
+        /* session id context */
+        if (sess_id_ctx != NULL)
+           SSL_CTX_set_session_id_context(self->ctx,
+                                          (const unsigned char*)sess_id_ctx,
+                                          strlen(sess_id_ctx));
+        PySSL_END_ALLOW_THREADS
+
+        verification_mode = SSL_VERIFY_NONE;
+        if (certreq == PY_SSL_CERT_OPTIONAL)
+            verification_mode = SSL_VERIFY_PEER;
+        else if (certreq == PY_SSL_CERT_REQUIRED)
+            verification_mode = (SSL_VERIFY_PEER |
+                                 SSL_VERIFY_FAIL_IF_NO_PEER_CERT);
+        SSL_CTX_set_verify(self->ctx, verification_mode,
+                           NULL); /* set verify lvl */
+    }
+
+    self->ssl = SSL_new(self->ctx); /* New ssl struct */
+    SSL_set_fd(self->ssl, Sock->sock_fd);       /* Set the socket for SSL */
+#ifdef SSL_MODE_AUTO_RETRY
+    SSL_set_mode(self->ssl, SSL_MODE_AUTO_RETRY);
+#endif
+
+    /* If the socket is in non-blocking mode or timeout mode, set the BIO
+     * to non-blocking mode (blocking is the default)
+     */
+    if (Sock->sock_timeout >= 0.0) {
+        /* Set both the read and write BIO's to non-blocking mode */
+        BIO_set_nbio(SSL_get_rbio(self->ssl), 1);
+        BIO_set_nbio(SSL_get_wbio(self->ssl), 1);
+    }
+
+    PySSL_BEGIN_ALLOW_THREADS
+    if (socket_type == PY_SSL_CLIENT)
+        SSL_set_connect_state(self->ssl);
+    else
+        SSL_set_accept_state(self->ssl);
+    PySSL_END_ALLOW_THREADS
+
+    self->Socket = Sock;
+    Py_INCREF(self->Socket);
+    return self;
+ fail:
+    if (errstr)
+        PyErr_SetString(PySSLErrorObject, errstr);
+    Py_DECREF(self);
+    return NULL;
+}
+
+static PyObject *
+PySSL_sslwrap(PyObject *self, PyObject *args)
+{
+    PySocketSockObject *Sock;
+    int server_side = 0;
+    int verification_mode = PY_SSL_CERT_NONE;
+    int protocol = PY_SSL_VERSION_SSL23;
+    int sess_cache_mode = PY_SSL_SESS_CACHE_SERVER;
+    char *sess_id_ctx = NULL;
+    char *key_file = NULL;
+    char *cert_file = NULL;
+    char *cacerts_file = NULL;
+
+    if (!PyArg_ParseTuple(args, "O!i|zziiizz:sslwrap",
+                          PySocketModule.Sock_Type,
+                          &Sock,
+                          &server_side,
+                          &key_file, &cert_file,
+                          &verification_mode, &protocol,
+                          &sess_cache_mode, &sess_id_ctx,
+                          &cacerts_file))
+        return NULL;
+
+    /*
+    fprintf(stderr,
+        "server_side is %d, keyfile %p, certfile %p, verify_mode %d, "
+        "protocol %d, sess_cache_mode %d, sess_id_ctx %p, certs %p\n",
+        server_side, key_file, cert_file, verification_mode,
+        protocol, sess_cache_mode, sess_id_ctx, cacerts_file);
+     */
+
+    return (PyObject *) newPySSLObject(NULL, Sock, key_file, cert_file,
+                                       server_side, verification_mode,
+                                       protocol, sess_cache_mode, sess_id_ctx,
+                                       cacerts_file);
+}
+
+PyDoc_STRVAR(sslwrap_doc,
+"sslwrap(socket, server_side, [keyfile, certfile, certs_mode, protocol,\n"
+"                              sess_cache_mode, sess_id_ctx, cacertsfile]) -> sslobject");
+
+/* SSL object methods */
+
+static PyObject *
+PySSL_SSLwrap_accepted(PySSLObject *self, PyObject *args)
+{
+    PySocketSockObject *Sock;
+
+    if (!PyArg_ParseTuple(args, "O!:sslwrap",
+                          PySocketModule.Sock_Type,
+                          &Sock))
+        return NULL;
+
+    return (PyObject *) newPySSLObject(self, Sock, NULL, NULL,
+                                       PY_SSL_SERVER, 0, 0, 0, NULL, NULL);
+}
+
+PyDoc_STRVAR(PySSL_SSLwrap_accepted_doc,
+"wrap_accepted(socket) -> sslobject");
+
+static PyObject *PySSL_SSLdo_handshake(PySSLObject *self)
+{
+    int ret;
+    int err;
+    int sockstate, nonblocking;
+
+    /* just in case the blocking state of the socket has been changed */
+    nonblocking = (self->Socket->sock_timeout >= 0.0);
+    BIO_set_nbio(SSL_get_rbio(self->ssl), nonblocking);
+    BIO_set_nbio(SSL_get_wbio(self->ssl), nonblocking);
+
+    /* Actually negotiate SSL connection */
+    /* XXX If SSL_do_handshake() returns 0, it's also a failure. */
+    sockstate = 0;
+    do {
+        PySSL_BEGIN_ALLOW_THREADS
+        ret = SSL_do_handshake(self->ssl);
+        err = SSL_get_error(self->ssl, ret);
+        PySSL_END_ALLOW_THREADS
+        if(PyErr_CheckSignals()) {
+            return NULL;
+        }
+        if (err == SSL_ERROR_WANT_READ) {
+            sockstate = check_socket_and_wait_for_timeout(self->Socket, 0);
+        } else if (err == SSL_ERROR_WANT_WRITE) {
+            sockstate = check_socket_and_wait_for_timeout(self->Socket, 1);
+        } else {
+            sockstate = SOCKET_OPERATION_OK;
+        }
+        if (sockstate == SOCKET_HAS_TIMED_OUT) {
+            PyErr_SetString(PySSLErrorObject,
+                            ERRSTR("The handshake operation timed out"));
+            return NULL;
+        } else if (sockstate == SOCKET_HAS_BEEN_CLOSED) {
+            PyErr_SetString(PySSLErrorObject,
+                            ERRSTR("Underlying socket has been closed."));
+            return NULL;
+        } else if (sockstate == SOCKET_TOO_LARGE_FOR_SELECT) {
+            PyErr_SetString(PySSLErrorObject,
+                            ERRSTR("Underlying socket too large for select()."));
+            return NULL;
+        } else if (sockstate == SOCKET_IS_NONBLOCKING) {
+            break;
+        }
+    } while (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE);
+    if (ret < 1)
+        return PySSL_SetError(self, ret, __FILE__, __LINE__);
+
+    if (self->peer_cert)
+        X509_free (self->peer_cert);
+    PySSL_BEGIN_ALLOW_THREADS
+    if ((self->peer_cert = SSL_get_peer_certificate(self->ssl))) {
+        X509_NAME_oneline(X509_get_subject_name(self->peer_cert),
+                          self->server, X509_NAME_MAXLEN);
+        X509_NAME_oneline(X509_get_issuer_name(self->peer_cert),
+                          self->issuer, X509_NAME_MAXLEN);
+    }
+    PySSL_END_ALLOW_THREADS
+
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+static PyObject *
+PySSL_server(PySSLObject *self)
+{
+    return PyString_FromString(self->server);
+}
+
+static PyObject *
+PySSL_issuer(PySSLObject *self)
+{
+    return PyString_FromString(self->issuer);
+}
+
+static PyObject *
+_create_tuple_for_attribute (ASN1_OBJECT *name, ASN1_STRING *value) {
+
+    char namebuf[X509_NAME_MAXLEN];
+    int buflen;
+    PyObject *name_obj;
+    PyObject *value_obj;
+    PyObject *attr;
+    unsigned char *valuebuf = NULL;
+
+    buflen = OBJ_obj2txt(namebuf, sizeof(namebuf), name, 0);
+    if (buflen < 0) {
+        _setSSLError(NULL, 0, __FILE__, __LINE__);
+        goto fail;
+    }
+    name_obj = PyString_FromStringAndSize(namebuf, buflen);
+    if (name_obj == NULL)
+        goto fail;
+
+    buflen = ASN1_STRING_to_UTF8(&valuebuf, value);
+    if (buflen < 0) {
+        _setSSLError(NULL, 0, __FILE__, __LINE__);
+        Py_DECREF(name_obj);
+        goto fail;
+    }
+    value_obj = PyUnicode_DecodeUTF8((char *) valuebuf,
+                                     buflen, "strict");
+    OPENSSL_free(valuebuf);
+    if (value_obj == NULL) {
+        Py_DECREF(name_obj);
+        goto fail;
+    }
+    attr = PyTuple_New(2);
+    if (attr == NULL) {
+        Py_DECREF(name_obj);
+        Py_DECREF(value_obj);
+        goto fail;
+    }
+    PyTuple_SET_ITEM(attr, 0, name_obj);
+    PyTuple_SET_ITEM(attr, 1, value_obj);
+    return attr;
+
+  fail:
+    return NULL;
+}
+
+static PyObject *
+_create_tuple_for_X509_NAME (X509_NAME *xname)
+{
+    PyObject *dn = NULL;    /* tuple which represents the "distinguished name" */
+    PyObject *rdn = NULL;   /* tuple to hold a "relative distinguished name" */
+    PyObject *rdnt;
+    PyObject *attr = NULL;   /* tuple to hold an attribute */
+    int entry_count = X509_NAME_entry_count(xname);
+    X509_NAME_ENTRY *entry;
+    ASN1_OBJECT *name;
+    ASN1_STRING *value;
+    int index_counter;
+    int rdn_level = -1;
+    int retcode;
+
+    dn = PyList_New(0);
+    if (dn == NULL)
+        return NULL;
+    /* now create another tuple to hold the top-level RDN */
+    rdn = PyList_New(0);
+    if (rdn == NULL)
+        goto fail0;
+
+    for (index_counter = 0;
+         index_counter < entry_count;
+         index_counter++)
+    {
+        entry = X509_NAME_get_entry(xname, index_counter);
+
+        /* check to see if we've gotten to a new RDN */
+        if (rdn_level >= 0) {
+            if (rdn_level != entry->set) {
+                /* yes, new RDN */
+                /* add old RDN to DN */
+                rdnt = PyList_AsTuple(rdn);
+                Py_DECREF(rdn);
+                if (rdnt == NULL)
+                    goto fail0;
+                retcode = PyList_Append(dn, rdnt);
+                Py_DECREF(rdnt);
+                if (retcode < 0)
+                    goto fail0;
+                /* create new RDN */
+                rdn = PyList_New(0);
+                if (rdn == NULL)
+                    goto fail0;
+            }
+        }
+        rdn_level = entry->set;
+
+        /* now add this attribute to the current RDN */
+        name = X509_NAME_ENTRY_get_object(entry);
+        value = X509_NAME_ENTRY_get_data(entry);
+        attr = _create_tuple_for_attribute(name, value);
+        /*
+        fprintf(stderr, "RDN level %d, attribute %s: %s\n",
+            entry->set,
+            PyString_AS_STRING(PyTuple_GET_ITEM(attr, 0)),
+            PyString_AS_STRING(PyTuple_GET_ITEM(attr, 1)));
+        */
+        if (attr == NULL)
+            goto fail1;
+        retcode = PyList_Append(rdn, attr);
+        Py_DECREF(attr);
+        if (retcode < 0)
+            goto fail1;
+    }
+    /* now, there's typically a dangling RDN */
+    if ((rdn != NULL) && (PyList_Size(rdn) > 0)) {
+        rdnt = PyList_AsTuple(rdn);
+        Py_DECREF(rdn);
+        if (rdnt == NULL)
+            goto fail0;
+        retcode = PyList_Append(dn, rdnt);
+        Py_DECREF(rdnt);
+        if (retcode < 0)
+            goto fail0;
+    }
+
+    /* convert list to tuple */
+    rdnt = PyList_AsTuple(dn);
+    Py_DECREF(dn);
+    if (rdnt == NULL)
+        return NULL;
+    return rdnt;
+
+  fail1:
+    Py_XDECREF(rdn);
+
+  fail0:
+    Py_XDECREF(dn);
+    return NULL;
+}
+
+static PyObject *
+_get_peer_alt_names (X509 *certificate) {
+
+    /* this code follows the procedure outlined in
+       OpenSSL's crypto/x509v3/v3_prn.c:X509v3_EXT_print()
+       function to extract the STACK_OF(GENERAL_NAME),
+       then iterates through the stack to add the
+       names. */
+
+    int i, j;
+    PyObject *peer_alt_names = Py_None;
+    PyObject *v, *t;
+    X509_EXTENSION *ext = NULL;
+    GENERAL_NAMES *names = NULL;
+    GENERAL_NAME *name;
+    X509V3_EXT_METHOD *method;
+    BIO *biobuf = NULL;
+    char buf[2048];
+    char *vptr;
+    int len;
+    const unsigned char *p;
+
+    if (certificate == NULL)
+        return peer_alt_names;
+
+    /* get a memory buffer */
+    biobuf = BIO_new(BIO_s_mem());
+
+    i = 0;
+    while ((i = X509_get_ext_by_NID(
+                    certificate, NID_subject_alt_name, i)) >= 0) {
+
+        if (peer_alt_names == Py_None) {
+            peer_alt_names = PyList_New(0);
+            if (peer_alt_names == NULL)
+                goto fail;
+        }
+
+        /* now decode the altName */
+        ext = X509_get_ext(certificate, i);
+        if(!(method = X509V3_EXT_get(ext))) {
+            PyErr_SetString(PySSLErrorObject,
+                            ERRSTR("No method for internalizing subjectAltName!"));
+            goto fail;
+        }
+
+        p = ext->value->data;
+        if (method->it)
+            names = (GENERAL_NAMES*) (ASN1_item_d2i(NULL,
+                                                    &p,
+                                                    ext->value->length,
+                                                    ASN1_ITEM_ptr(method->it)));
+        else
+            names = (GENERAL_NAMES*) (method->d2i(NULL,
+                                                  &p,
+                                                  ext->value->length));
+
+        for(j = 0; j < sk_GENERAL_NAME_num(names); j++) {
+
+            /* get a rendering of each name in the set of names */
+
+            name = sk_GENERAL_NAME_value(names, j);
+            if (name->type == GEN_DIRNAME) {
+
+                /* we special-case DirName as a tuple of tuples of attributes */
+
+                t = PyTuple_New(2);
+                if (t == NULL) {
+                    goto fail;
+                }
+
+                v = PyString_FromString("DirName");
+                if (v == NULL) {
+                    Py_DECREF(t);
+                    goto fail;
+                }
+                PyTuple_SET_ITEM(t, 0, v);
+
+                v = _create_tuple_for_X509_NAME (name->d.dirn);
+                if (v == NULL) {
+                    Py_DECREF(t);
+                    goto fail;
+                }
+                PyTuple_SET_ITEM(t, 1, v);
+
+            } else {
+
+                /* for everything else, we use the OpenSSL print form */
+
+                (void) BIO_reset(biobuf);
+                GENERAL_NAME_print(biobuf, name);
+                len = BIO_gets(biobuf, buf, sizeof(buf)-1);
+                if (len < 0) {
+                    _setSSLError(NULL, 0, __FILE__, __LINE__);
+                    goto fail;
+                }
+                vptr = strchr(buf, ':');
+                if (vptr == NULL)
+                    goto fail;
+                t = PyTuple_New(2);
+                if (t == NULL)
+                    goto fail;
+                v = PyString_FromStringAndSize(buf, (vptr - buf));
+                if (v == NULL) {
+                    Py_DECREF(t);
+                    goto fail;
+                }
+                PyTuple_SET_ITEM(t, 0, v);
+                v = PyString_FromStringAndSize((vptr + 1), (len - (vptr - buf + 1)));
+                if (v == NULL) {
+                    Py_DECREF(t);
+                    goto fail;
+                }
+                PyTuple_SET_ITEM(t, 1, v);
+            }
+
+            /* and add that rendering to the list */
+
+            if (PyList_Append(peer_alt_names, t) < 0) {
+                Py_DECREF(t);
+                goto fail;
+            }
+            Py_DECREF(t);
+        }
+    }
+    BIO_free(biobuf);
+    if (peer_alt_names != Py_None) {
+        v = PyList_AsTuple(peer_alt_names);
+        Py_DECREF(peer_alt_names);
+        return v;
+    } else {
+        return peer_alt_names;
+    }
+
+
+  fail:
+    if (biobuf != NULL)
+        BIO_free(biobuf);
+
+    if (peer_alt_names != Py_None) {
+        Py_XDECREF(peer_alt_names);
+    }
+
+    return NULL;
+}
+
+static PyObject *
+_decode_certificate (X509 *certificate, int verbose) {
+
+    PyObject *retval = NULL;
+    BIO *biobuf = NULL;
+    PyObject *peer;
+    PyObject *peer_alt_names = NULL;
+    PyObject *issuer;
+    PyObject *version;
+    PyObject *sn_obj;
+    ASN1_INTEGER *serialNumber;
+    char buf[2048];
+    int len;
+    ASN1_TIME *notBefore, *notAfter;
+    PyObject *pnotBefore, *pnotAfter;
+
+    retval = PyDict_New();
+    if (retval == NULL)
+        return NULL;
+
+    peer = _create_tuple_for_X509_NAME(
+        X509_get_subject_name(certificate));
+    if (peer == NULL)
+        goto fail0;
+    if (PyDict_SetItemString(retval, (const char *) "subject", peer) < 0) {
+        Py_DECREF(peer);
+        goto fail0;
+    }
+    Py_DECREF(peer);
+
+    if (verbose) {
+        issuer = _create_tuple_for_X509_NAME(
+            X509_get_issuer_name(certificate));
+        if (issuer == NULL)
+            goto fail0;
+        if (PyDict_SetItemString(retval, (const char *)"issuer", issuer) < 0) {
+            Py_DECREF(issuer);
+            goto fail0;
+        }
+        Py_DECREF(issuer);
+
+        version = PyInt_FromLong(X509_get_version(certificate) + 1);
+        if (PyDict_SetItemString(retval, "version", version) < 0) {
+            Py_DECREF(version);
+            goto fail0;
+        }
+        Py_DECREF(version);
+    }
+
+    /* get a memory buffer */
+    biobuf = BIO_new(BIO_s_mem());
+
+    if (verbose) {
+
+        (void) BIO_reset(biobuf);
+        serialNumber = X509_get_serialNumber(certificate);
+        /* should not exceed 20 octets, 160 bits, so buf is big enough */
+        i2a_ASN1_INTEGER(biobuf, serialNumber);
+        len = BIO_gets(biobuf, buf, sizeof(buf)-1);
+        if (len < 0) {
+            _setSSLError(NULL, 0, __FILE__, __LINE__);
+            goto fail1;
+        }
+        sn_obj = PyString_FromStringAndSize(buf, len);
+        if (sn_obj == NULL)
+            goto fail1;
+        if (PyDict_SetItemString(retval, "serialNumber", sn_obj) < 0) {
+            Py_DECREF(sn_obj);
+            goto fail1;
+        }
+        Py_DECREF(sn_obj);
+
+        (void) BIO_reset(biobuf);
+        notBefore = X509_get_notBefore(certificate);
+        ASN1_TIME_print(biobuf, notBefore);
+        len = BIO_gets(biobuf, buf, sizeof(buf)-1);
+        if (len < 0) {
+            _setSSLError(NULL, 0, __FILE__, __LINE__);
+            goto fail1;
+        }
+        pnotBefore = PyString_FromStringAndSize(buf, len);
+        if (pnotBefore == NULL)
+            goto fail1;
+        if (PyDict_SetItemString(retval, "notBefore", pnotBefore) < 0) {
+            Py_DECREF(pnotBefore);
+            goto fail1;
+        }
+        Py_DECREF(pnotBefore);
+    }
+
+    (void) BIO_reset(biobuf);
+    notAfter = X509_get_notAfter(certificate);
+    ASN1_TIME_print(biobuf, notAfter);
+    len = BIO_gets(biobuf, buf, sizeof(buf)-1);
+    if (len < 0) {
+        _setSSLError(NULL, 0, __FILE__, __LINE__);
+        goto fail1;
+    }
+    pnotAfter = PyString_FromStringAndSize(buf, len);
+    if (pnotAfter == NULL)
+        goto fail1;
+    if (PyDict_SetItemString(retval, "notAfter", pnotAfter) < 0) {
+        Py_DECREF(pnotAfter);
+        goto fail1;
+    }
+    Py_DECREF(pnotAfter);
+
+    /* Now look for subjectAltName */
+
+    peer_alt_names = _get_peer_alt_names(certificate);
+    if (peer_alt_names == NULL)
+        goto fail1;
+    else if (peer_alt_names != Py_None) {
+        if (PyDict_SetItemString(retval, "subjectAltName",
+                                 peer_alt_names) < 0) {
+            Py_DECREF(peer_alt_names);
+            goto fail1;
+        }
+        Py_DECREF(peer_alt_names);
+    }
+
+    BIO_free(biobuf);
+    return retval;
+
+  fail1:
+    if (biobuf != NULL)
+        BIO_free(biobuf);
+  fail0:
+    Py_XDECREF(retval);
+    return NULL;
+}
+
+
+static PyObject *
+PySSL_test_decode_certificate (PyObject *mod, PyObject *args) {
+
+    PyObject *retval = NULL;
+    char *filename = NULL;
+    X509 *x=NULL;
+    BIO *cert;
+    int verbose = 1;
+
+    if (!PyArg_ParseTuple(args, "s|i:test_decode_certificate", &filename, &verbose))
+        return NULL;
+
+    if ((cert=BIO_new(BIO_s_file())) == NULL) {
+        PyErr_SetString(PySSLErrorObject, "Can't malloc memory to read file");
+        goto fail0;
+    }
+
+    if (BIO_read_filename(cert,filename) <= 0) {
+        PyErr_SetString(PySSLErrorObject, "Can't open file");
+        goto fail0;
+    }
+
+    x = PEM_read_bio_X509_AUX(cert,NULL, NULL, NULL);
+    if (x == NULL) {
+        PyErr_SetString(PySSLErrorObject, "Error decoding PEM-encoded file");
+        goto fail0;
+    }
+
+    retval = _decode_certificate(x, verbose);
+
+  fail0:
+
+    if (cert != NULL) BIO_free(cert);
+    return retval;
+}
+
+
+static PyObject *
+PySSL_peercert(PySSLObject *self, PyObject *args)
+{
+    PyObject *retval = NULL;
+    int len;
+    int verification;
+    PyObject *binary_mode = Py_None;
+
+    if (!PyArg_ParseTuple(args, "|O:peer_certificate", &binary_mode))
+        return NULL;
+
+    if (!self->peer_cert)
+        Py_RETURN_NONE;
+
+    if (PyObject_IsTrue(binary_mode)) {
+        /* return cert in DER-encoded format */
+
+        unsigned char *bytes_buf = NULL;
+
+        bytes_buf = NULL;
+        len = i2d_X509(self->peer_cert, &bytes_buf);
+        if (len < 0) {
+            PySSL_SetError(self, len, __FILE__, __LINE__);
+            return NULL;
+        }
+        retval = PyString_FromStringAndSize((const char *) bytes_buf, len);
+        OPENSSL_free(bytes_buf);
+        return retval;
+
+    } else {
+
+        verification = SSL_CTX_get_verify_mode(self->ctx);
+        if ((verification & SSL_VERIFY_PEER) == 0)
+            return PyDict_New();
+        else
+            return _decode_certificate (self->peer_cert, 0);
+    }
+}
+
+PyDoc_STRVAR(PySSL_peercert_doc,
+"peer_certificate([der=False]) -> certificate\n\
+\n\
+Returns the certificate for the peer.  If no certificate was provided,\n\
+returns None.  If a certificate was provided, but not validated, returns\n\
+an empty dictionary.  Otherwise returns a dict containing information\n\
+about the peer certificate.\n\
+\n\
+If the optional argument is True, returns a DER-encoded copy of the\n\
+peer certificate, or None if no certificate was provided.  This will\n\
+return the certificate even if it wasn't validated.");
+
+static PyObject *PySSL_cipher (PySSLObject *self) {
+
+    PyObject *retval, *v;
+    SSL_CIPHER *current;
+    char *cipher_name;
+    char *cipher_protocol;
+
+    if (self->ssl == NULL)
+        return Py_None;
+    current = SSL_get_current_cipher(self->ssl);
+    if (current == NULL)
+        return Py_None;
+
+    retval = PyTuple_New(3);
+    if (retval == NULL)
+        return NULL;
+
+    cipher_name = (char *) SSL_CIPHER_get_name(current);
+    if (cipher_name == NULL) {
+        PyTuple_SET_ITEM(retval, 0, Py_None);
+    } else {
+        v = PyString_FromString(cipher_name);
+        if (v == NULL)
+            goto fail0;
+        PyTuple_SET_ITEM(retval, 0, v);
+    }
+    cipher_protocol = SSL_CIPHER_get_version(current);
+    if (cipher_protocol == NULL) {
+        PyTuple_SET_ITEM(retval, 1, Py_None);
+    } else {
+        v = PyString_FromString(cipher_protocol);
+        if (v == NULL)
+            goto fail0;
+        PyTuple_SET_ITEM(retval, 1, v);
+    }
+    v = PyInt_FromLong(SSL_CIPHER_get_bits(current, NULL));
+    if (v == NULL)
+        goto fail0;
+    PyTuple_SET_ITEM(retval, 2, v);
+    return retval;
+
+  fail0:
+    Py_DECREF(retval);
+    return NULL;
+}
+
+static void PySSL_dealloc(PySSLObject *self)
+{
+    if (self->peer_cert)        /* Possible not to have one? */
+        X509_free (self->peer_cert);
+    if (self->ssl)
+        SSL_free(self->ssl);
+    if (self->ctx && self->inherited == 0)
+        SSL_CTX_free(self->ctx);
+    Py_XDECREF(self->Socket);
+    PyObject_Del(self);
+}
+
+/* If the socket has a timeout, do a select()/poll() on the socket.
+   The argument writing indicates the direction.
+   Returns one of the possibilities in the timeout_state enum (above).
+ */
+
+static int
+check_socket_and_wait_for_timeout(PySocketSockObject *s, int writing)
+{
+    fd_set fds;
+    struct timeval tv;
+    int rc;
+
+    /* Nothing to do unless we're in timeout mode (not non-blocking) */
+    if (s->sock_timeout < 0.0)
+        return SOCKET_IS_BLOCKING;
+    else if (s->sock_timeout == 0.0)
+        return SOCKET_IS_NONBLOCKING;
+
+    /* Guard against closed socket */
+    if (s->sock_fd < 0)
+        return SOCKET_HAS_BEEN_CLOSED;
+
+    /* Prefer poll, if available, since you can poll() any fd
+     * which can't be done with select(). */
+#ifdef HAVE_POLL
+    {
+        struct pollfd pollfd;
+        int timeout;
+
+        pollfd.fd = s->sock_fd;
+        pollfd.events = writing ? POLLOUT : POLLIN;
+
+        /* s->sock_timeout is in seconds, timeout in ms */
+        timeout = (int)(s->sock_timeout * 1000 + 0.5);
+        PySSL_BEGIN_ALLOW_THREADS
+        rc = poll(&pollfd, 1, timeout);
+        PySSL_END_ALLOW_THREADS
+
+        goto normal_return;
+    }
+#endif
+
+    /* Guard against socket too large for select*/
+#ifndef Py_SOCKET_FD_CAN_BE_GE_FD_SETSIZE
+    if (s->sock_fd >= FD_SETSIZE)
+        return SOCKET_TOO_LARGE_FOR_SELECT;
+#endif
+
+    /* Construct the arguments to select */
+    tv.tv_sec = (int)s->sock_timeout;
+    tv.tv_usec = (int)((s->sock_timeout - tv.tv_sec) * 1e6);
+    FD_ZERO(&fds);
+    FD_SET(s->sock_fd, &fds);
+
+    /* See if the socket is ready */
+    PySSL_BEGIN_ALLOW_THREADS
+    if (writing)
+        rc = select(s->sock_fd+1, NULL, &fds, NULL, &tv);
+    else
+        rc = select(s->sock_fd+1, &fds, NULL, NULL, &tv);
+    PySSL_END_ALLOW_THREADS
+
+#ifdef HAVE_POLL
+normal_return:
+#endif
+    /* Return SOCKET_TIMED_OUT on timeout, SOCKET_OPERATION_OK otherwise
+       (when we are able to write or when there's something to read) */
+    return rc == 0 ? SOCKET_HAS_TIMED_OUT : SOCKET_OPERATION_OK;
+}
+
+static PyObject *PySSL_SSLwrite(PySSLObject *self, PyObject *args)
+{
+    char *data;
+    int len;
+    int count;
+    int sockstate;
+    int err;
+    int nonblocking;
+
+    if (!PyArg_ParseTuple(args, "s#:write", &data, &count))
+        return NULL;
+
+    /* just in case the blocking state of the socket has been changed */
+    nonblocking = (self->Socket->sock_timeout >= 0.0);
+    BIO_set_nbio(SSL_get_rbio(self->ssl), nonblocking);
+    BIO_set_nbio(SSL_get_wbio(self->ssl), nonblocking);
+
+    sockstate = check_socket_and_wait_for_timeout(self->Socket, 1);
+    if (sockstate == SOCKET_HAS_TIMED_OUT) {
+        PyErr_SetString(PySSLErrorObject,
+                        "The write operation timed out");
+        return NULL;
+    } else if (sockstate == SOCKET_HAS_BEEN_CLOSED) {
+        PyErr_SetString(PySSLErrorObject,
+                        "Underlying socket has been closed.");
+        return NULL;
+    } else if (sockstate == SOCKET_TOO_LARGE_FOR_SELECT) {
+        PyErr_SetString(PySSLErrorObject,
+                        "Underlying socket too large for select().");
+        return NULL;
+    }
+    do {
+        err = 0;
+        PySSL_BEGIN_ALLOW_THREADS
+        len = SSL_write(self->ssl, data, count);
+        err = SSL_get_error(self->ssl, len);
+        PySSL_END_ALLOW_THREADS
+        if(PyErr_CheckSignals()) {
+            return NULL;
+        }
+        if (err == SSL_ERROR_WANT_READ) {
+            sockstate = check_socket_and_wait_for_timeout(self->Socket, 0);
+        } else if (err == SSL_ERROR_WANT_WRITE) {
+            sockstate = check_socket_and_wait_for_timeout(self->Socket, 1);
+        } else {
+            sockstate = SOCKET_OPERATION_OK;
+        }
+        if (sockstate == SOCKET_HAS_TIMED_OUT) {
+            PyErr_SetString(PySSLErrorObject,
+                            "The write operation timed out");
+            return NULL;
+        } else if (sockstate == SOCKET_HAS_BEEN_CLOSED) {
+            PyErr_SetString(PySSLErrorObject,
+                            "Underlying socket has been closed.");
+            return NULL;
+        } else if (sockstate == SOCKET_IS_NONBLOCKING) {
+            break;
+        }
+    } while (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE);
+    if (len > 0)
+        return PyInt_FromLong(len);
+    else
+        return PySSL_SetError(self, len, __FILE__, __LINE__);
+}
+
+PyDoc_STRVAR(PySSL_SSLwrite_doc,
+"write(s) -> len\n\
+\n\
+Writes the string s into the SSL object.  Returns the number\n\
+of bytes written.");
+
+static PyObject *PySSL_SSLpending(PySSLObject *self)
+{
+    int count = 0;
+
+    PySSL_BEGIN_ALLOW_THREADS
+    count = SSL_pending(self->ssl);
+    PySSL_END_ALLOW_THREADS
+    if (count < 0)
+        return PySSL_SetError(self, count, __FILE__, __LINE__);
+    else
+        return PyInt_FromLong(count);
+}
+
+PyDoc_STRVAR(PySSL_SSLpending_doc,
+"pending() -> count\n\
+\n\
+Returns the number of already decrypted bytes available for read,\n\
+pending on the connection.\n");
+
+static PyObject *PySSL_SSLread(PySSLObject *self, PyObject *args)
+{
+    PyObject *buf;
+    int count = 0;
+    int len = 1024;
+    int sockstate;
+    int err;
+    int nonblocking;
+
+    if (!PyArg_ParseTuple(args, "|i:read", &len))
+        return NULL;
+
+    if (!(buf = PyString_FromStringAndSize((char *) 0, len)))
+        return NULL;
+
+    /* just in case the blocking state of the socket has been changed */
+    nonblocking = (self->Socket->sock_timeout >= 0.0);
+    BIO_set_nbio(SSL_get_rbio(self->ssl), nonblocking);
+    BIO_set_nbio(SSL_get_wbio(self->ssl), nonblocking);
+
+    /* first check if there are bytes ready to be read */
+    PySSL_BEGIN_ALLOW_THREADS
+    count = SSL_pending(self->ssl);
+    PySSL_END_ALLOW_THREADS
+
+    if (!count) {
+        sockstate = check_socket_and_wait_for_timeout(self->Socket, 0);
+        if (sockstate == SOCKET_HAS_TIMED_OUT) {
+            PyErr_SetString(PySSLErrorObject,
+                            "The read operation timed out");
+            Py_DECREF(buf);
+            return NULL;
+        } else if (sockstate == SOCKET_TOO_LARGE_FOR_SELECT) {
+            PyErr_SetString(PySSLErrorObject,
+                            "Underlying socket too large for select().");
+            Py_DECREF(buf);
+            return NULL;
+        } else if (sockstate == SOCKET_HAS_BEEN_CLOSED) {
+            if (SSL_get_shutdown(self->ssl) !=
+                SSL_RECEIVED_SHUTDOWN)
+            {
+                Py_DECREF(buf);
+                PyErr_SetString(PySSLErrorObject,
+                                "Socket closed without SSL shutdown handshake");
+                return NULL;
+            } else {
+                /* should contain a zero-length string */
+                _PyString_Resize(&buf, 0);
+                return buf;
+            }
+        }
+    }
+    do {
+        err = 0;
+        PySSL_BEGIN_ALLOW_THREADS
+        count = SSL_read(self->ssl, PyString_AsString(buf), len);
+        err = SSL_get_error(self->ssl, count);
+        PySSL_END_ALLOW_THREADS
+        if(PyErr_CheckSignals()) {
+            Py_DECREF(buf);
+            return NULL;
+        }
+        if (err == SSL_ERROR_WANT_READ) {
+            sockstate = check_socket_and_wait_for_timeout(self->Socket, 0);
+        } else if (err == SSL_ERROR_WANT_WRITE) {
+            sockstate = check_socket_and_wait_for_timeout(self->Socket, 1);
+        } else if ((err == SSL_ERROR_ZERO_RETURN) &&
+                   (SSL_get_shutdown(self->ssl) ==
+                    SSL_RECEIVED_SHUTDOWN))
+        {
+            _PyString_Resize(&buf, 0);
+            return buf;
+        } else {
+            sockstate = SOCKET_OPERATION_OK;
+        }
+        if (sockstate == SOCKET_HAS_TIMED_OUT) {
+            PyErr_SetString(PySSLErrorObject,
+                            "The read operation timed out");
+            Py_DECREF(buf);
+            return NULL;
+        } else if (sockstate == SOCKET_IS_NONBLOCKING) {
+            break;
+        }
+    } while (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE);
+    if (count <= 0) {
+        Py_DECREF(buf);
+        return PySSL_SetError(self, count, __FILE__, __LINE__);
+    }
+    if (count != len)
+        _PyString_Resize(&buf, count);
+    return buf;
+}
+
+PyDoc_STRVAR(PySSL_SSLread_doc,
+"read([len]) -> string\n\
+\n\
+Read up to len bytes from the SSL socket.");
+
+static PyObject *PySSL_SSLshutdown(PySSLObject *self)
+{
+    int err, ssl_err, sockstate, nonblocking;
+    int zeros = 0;
+
+    /* Guard against closed socket */
+    if (self->Socket->sock_fd < 0) {
+        PyErr_SetString(PySSLErrorObject,
+                        "Underlying socket has been closed.");
+        return NULL;
+    }
+
+    /* Just in case the blocking state of the socket has been changed */
+    nonblocking = (self->Socket->sock_timeout >= 0.0);
+    BIO_set_nbio(SSL_get_rbio(self->ssl), nonblocking);
+    BIO_set_nbio(SSL_get_wbio(self->ssl), nonblocking);
+
+    while (1) {
+        PySSL_BEGIN_ALLOW_THREADS
+        /* Disable read-ahead so that unwrap can work correctly.
+         * Otherwise OpenSSL might read in too much data,
+         * eating clear text data that happens to be
+         * transmitted after the SSL shutdown.
+         * Should be safe to call repeatedly everytime this
+         * function is used and the shutdown_seen_zero != 0
+         * condition is met.
+         */
+        if (self->shutdown_seen_zero)
+            SSL_set_read_ahead(self->ssl, 0);
+        err = SSL_shutdown(self->ssl);
+        PySSL_END_ALLOW_THREADS
+        /* If err == 1, a secure shutdown with SSL_shutdown() is complete */
+        if (err > 0)
+            break;
+        if (err == 0) {
+            /* Don't loop endlessly; instead preserve legacy
+               behaviour of trying SSL_shutdown() only twice.
+               This looks necessary for OpenSSL < 0.9.8m */
+            if (++zeros > 1)
+                break;
+            /* Shutdown was sent, now try receiving */
+            self->shutdown_seen_zero = 1;
+            continue;
+        }
+
+        /* Possibly retry shutdown until timeout or failure */
+        ssl_err = SSL_get_error(self->ssl, err);
+        if (ssl_err == SSL_ERROR_WANT_READ)
+            sockstate = check_socket_and_wait_for_timeout(self->Socket, 0);
+        else if (ssl_err == SSL_ERROR_WANT_WRITE)
+            sockstate = check_socket_and_wait_for_timeout(self->Socket, 1);
+        else
+            break;
+        if (sockstate == SOCKET_HAS_TIMED_OUT) {
+            if (ssl_err == SSL_ERROR_WANT_READ)
+                PyErr_SetString(PySSLErrorObject,
+                                "The read operation timed out");
+            else
+                PyErr_SetString(PySSLErrorObject,
+                                "The write operation timed out");
+            return NULL;
+        }
+        else if (sockstate == SOCKET_TOO_LARGE_FOR_SELECT) {
+            PyErr_SetString(PySSLErrorObject,
+                            "Underlying socket too large for select().");
+            return NULL;
+        }
+        else if (sockstate != SOCKET_OPERATION_OK)
+            /* Retain the SSL error code */
+            break;
+    }
+
+    if (err < 0)
+        return PySSL_SetError(self, err, __FILE__, __LINE__);
+    else {
+        Py_INCREF(self->Socket);
+        return (PyObject *) (self->Socket);
+    }
+}
+
+PyDoc_STRVAR(PySSL_SSLshutdown_doc,
+"shutdown(s) -> socket\n\
+\n\
+Does the SSL shutdown handshake with the remote end, and returns\n\
+the underlying socket object.");
+
+static PyMethodDef PySSLMethods[] = {
+    {"wrap_accepted", (PyCFunction)PySSL_SSLwrap_accepted, METH_VARARGS,
+     PySSL_SSLwrap_accepted_doc},
+    {"do_handshake", (PyCFunction)PySSL_SSLdo_handshake, METH_NOARGS},
+    {"write", (PyCFunction)PySSL_SSLwrite, METH_VARARGS,
+     PySSL_SSLwrite_doc},
+    {"read", (PyCFunction)PySSL_SSLread, METH_VARARGS,
+     PySSL_SSLread_doc},
+    {"pending", (PyCFunction)PySSL_SSLpending, METH_NOARGS,
+     PySSL_SSLpending_doc},
+    {"server", (PyCFunction)PySSL_server, METH_NOARGS},
+    {"issuer", (PyCFunction)PySSL_issuer, METH_NOARGS},
+    {"peer_certificate", (PyCFunction)PySSL_peercert, METH_VARARGS,
+     PySSL_peercert_doc},
+    {"cipher", (PyCFunction)PySSL_cipher, METH_NOARGS},
+    {"shutdown", (PyCFunction)PySSL_SSLshutdown, METH_NOARGS,
+     PySSL_SSLshutdown_doc},
+    {NULL, NULL}
+};
+
+static PyObject *PySSL_getattr(PySSLObject *self, char *name)
+{
+    return Py_FindMethod(PySSLMethods, (PyObject *)self, name);
+}
+
+static PyTypeObject PySSL_Type = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "ssl.SSLContext",                   /*tp_name*/
+    sizeof(PySSLObject),                /*tp_basicsize*/
+    0,                                  /*tp_itemsize*/
+    /* methods */
+    (destructor)PySSL_dealloc,          /*tp_dealloc*/
+    0,                                  /*tp_print*/
+    (getattrfunc)PySSL_getattr,         /*tp_getattr*/
+    0,                                  /*tp_setattr*/
+    0,                                  /*tp_compare*/
+    0,                                  /*tp_repr*/
+    0,                                  /*tp_as_number*/
+    0,                                  /*tp_as_sequence*/
+    0,                                  /*tp_as_mapping*/
+    0,                                  /*tp_hash*/
+};
+
+#ifdef HAVE_OPENSSL_RAND
+
+/* helper routines for seeding the SSL PRNG */
+static PyObject *
+PySSL_RAND_add(PyObject *self, PyObject *args)
+{
+    char *buf;
+    int len;
+    double entropy;
+
+    if (!PyArg_ParseTuple(args, "s#d:RAND_add", &buf, &len, &entropy))
+        return NULL;
+    RAND_add(buf, len, entropy);
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+PyDoc_STRVAR(PySSL_RAND_add_doc,
+"RAND_add(string, entropy)\n\
+\n\
+Mix string into the OpenSSL PRNG state.  entropy (a float) is a lower\n\
+bound on the entropy contained in string.  See RFC 1750.");
+
+static PyObject *
+PySSL_RAND_status(PyObject *self)
+{
+    return PyInt_FromLong(RAND_status());
+}
+
+PyDoc_STRVAR(PySSL_RAND_status_doc,
+"RAND_status() -> 0 or 1\n\
+\n\
+Returns 1 if the OpenSSL PRNG has been seeded with enough data and 0 if not.\n\
+It is necessary to seed the PRNG with RAND_add() on some platforms before\n\
+using the ssl() function.");
+
+static PyObject *
+PySSL_RAND_egd(PyObject *self, PyObject *arg)
+{
+    int bytes;
+
+    if (!PyString_Check(arg))
+        return PyErr_Format(PyExc_TypeError,
+                            "RAND_egd() expected string, found %s",
+                            Py_TYPE(arg)->tp_name);
+    bytes = RAND_egd(PyString_AS_STRING(arg));
+    if (bytes == -1) {
+        PyErr_SetString(PySSLErrorObject,
+                        "EGD connection failed or EGD did not return "
+                        "enough data to seed the PRNG");
+        return NULL;
+    }
+    return PyInt_FromLong(bytes);
+}
+
+PyDoc_STRVAR(PySSL_RAND_egd_doc,
+"RAND_egd(path) -> bytes\n\
+\n\
+Queries the entropy gather daemon (EGD) on the socket named by 'path'.\n\
+Returns number of bytes read.  Raises SSLError if connection to EGD\n\
+fails or if it does provide enough data to seed PRNG.");
+
+#endif
+
+/* List of functions exported by this module. */
+
+static PyMethodDef PySSL_methods[] = {
+    {"sslwrap",             PySSL_sslwrap,
+     METH_VARARGS, sslwrap_doc},
+    {"_test_decode_cert",   PySSL_test_decode_certificate,
+     METH_VARARGS},
+#ifdef HAVE_OPENSSL_RAND
+    {"RAND_add",            PySSL_RAND_add, METH_VARARGS,
+     PySSL_RAND_add_doc},
+    {"RAND_egd",            PySSL_RAND_egd, METH_O,
+     PySSL_RAND_egd_doc},
+    {"RAND_status",         (PyCFunction)PySSL_RAND_status, METH_NOARGS,
+     PySSL_RAND_status_doc},
+#endif
+    {NULL,                  NULL}            /* Sentinel */
+};
+
+
+#ifdef WITH_THREAD
+
+/* an implementation of OpenSSL threading operations in terms
+   of the Python C thread library */
+
+static PyThread_type_lock *_ssl_locks = NULL;
+
+static unsigned long _ssl_thread_id_function (void) {
+    return PyThread_get_thread_ident();
+}
+
+static void _ssl_thread_locking_function (int mode, int n, const char *file, int line) {
+    /* this function is needed to perform locking on shared data
+       structures. (Note that OpenSSL uses a number of global data
+       structures that will be implicitly shared whenever multiple threads
+       use OpenSSL.) Multi-threaded applications will crash at random if
+       it is not set.
+
+       locking_function() must be able to handle up to CRYPTO_num_locks()
+       different mutex locks. It sets the n-th lock if mode & CRYPTO_LOCK, and
+       releases it otherwise.
+
+       file and line are the file number of the function setting the
+       lock. They can be useful for debugging.
+    */
+
+    if ((_ssl_locks == NULL) ||
+        (n < 0) || ((unsigned)n >= _ssl_locks_count))
+        return;
+
+    if (mode & CRYPTO_LOCK) {
+        PyThread_acquire_lock(_ssl_locks[n], 1);
+    } else {
+        PyThread_release_lock(_ssl_locks[n]);
+    }
+}
+
+static int _setup_ssl_threads(void) {
+
+    unsigned int i;
+
+    if (_ssl_locks == NULL) {
+        _ssl_locks_count = CRYPTO_num_locks();
+        _ssl_locks = (PyThread_type_lock *)
+            malloc(sizeof(PyThread_type_lock) * _ssl_locks_count);
+        if (_ssl_locks == NULL)
+            return 0;
+        memset(_ssl_locks, 0, sizeof(PyThread_type_lock) * _ssl_locks_count);
+        for (i = 0;  i < _ssl_locks_count;  i++) {
+            _ssl_locks[i] = PyThread_allocate_lock();
+            if (_ssl_locks[i] == NULL) {
+                unsigned int j;
+                for (j = 0;  j < i;  j++) {
+                    PyThread_free_lock(_ssl_locks[j]);
+                }
+                free(_ssl_locks);
+                return 0;
+            }
+        }
+        CRYPTO_set_locking_callback(_ssl_thread_locking_function);
+        CRYPTO_set_id_callback(_ssl_thread_id_function);
+    }
+    return 1;
+}
+
+#endif  /* def HAVE_THREAD */
+
+PyDoc_STRVAR(module_doc,
+"Implementation module for SSL socket operations.  See the socket module\n\
+for documentation.");
+
+PyMODINIT_FUNC
+init_forge_ssl(void)
+{
+    PyObject *m, *d;
+
+    Py_TYPE(&PySSL_Type) = &PyType_Type;
+
+    m = Py_InitModule3("_forge_ssl", PySSL_methods, module_doc);
+    if (m == NULL)
+        return;
+    d = PyModule_GetDict(m);
+
+    /* Load _socket module and its C API */
+    if (PySocketModule_ImportModuleAndAPI())
+        return;
+
+    /* Init OpenSSL */
+    SSL_load_error_strings();
+    SSL_library_init();
+#ifdef WITH_THREAD
+    /* note that this will start threading if not already started */
+    if (!_setup_ssl_threads()) {
+        return;
+    }
+#endif
+    OpenSSL_add_all_algorithms();
+
+    /* Add symbols to module dict */
+    PySSLErrorObject = PyErr_NewException("ssl.SSLError",
+                                          PySocketModule.error,
+                                          NULL);
+    if (PySSLErrorObject == NULL)
+        return;
+    if (PyDict_SetItemString(d, "SSLError", PySSLErrorObject) != 0)
+        return;
+    if (PyDict_SetItemString(d, "SSLType",
+                             (PyObject *)&PySSL_Type) != 0)
+        return;
+    PyModule_AddIntConstant(m, "SSL_ERROR_ZERO_RETURN",
+                            PY_SSL_ERROR_ZERO_RETURN);
+    PyModule_AddIntConstant(m, "SSL_ERROR_WANT_READ",
+                            PY_SSL_ERROR_WANT_READ);
+    PyModule_AddIntConstant(m, "SSL_ERROR_WANT_WRITE",
+                            PY_SSL_ERROR_WANT_WRITE);
+    PyModule_AddIntConstant(m, "SSL_ERROR_WANT_X509_LOOKUP",
+                            PY_SSL_ERROR_WANT_X509_LOOKUP);
+    PyModule_AddIntConstant(m, "SSL_ERROR_SYSCALL",
+                            PY_SSL_ERROR_SYSCALL);
+    PyModule_AddIntConstant(m, "SSL_ERROR_SSL",
+                            PY_SSL_ERROR_SSL);
+    PyModule_AddIntConstant(m, "SSL_ERROR_WANT_CONNECT",
+                            PY_SSL_ERROR_WANT_CONNECT);
+    /* non ssl.h errorcodes */
+    PyModule_AddIntConstant(m, "SSL_ERROR_EOF",
+                            PY_SSL_ERROR_EOF);
+    PyModule_AddIntConstant(m, "SSL_ERROR_INVALID_ERROR_CODE",
+                            PY_SSL_ERROR_INVALID_ERROR_CODE);
+    /* cert requirements */
+    PyModule_AddIntConstant(m, "CERT_NONE",
+                            PY_SSL_CERT_NONE);
+    PyModule_AddIntConstant(m, "CERT_OPTIONAL",
+                            PY_SSL_CERT_OPTIONAL);
+    PyModule_AddIntConstant(m, "CERT_REQUIRED",
+                            PY_SSL_CERT_REQUIRED);
+
+    /* protocol versions */
+    PyModule_AddIntConstant(m, "PROTOCOL_SSLv2",
+                            PY_SSL_VERSION_SSL2);
+    PyModule_AddIntConstant(m, "PROTOCOL_SSLv3",
+                            PY_SSL_VERSION_SSL3);
+    PyModule_AddIntConstant(m, "PROTOCOL_SSLv23",
+                            PY_SSL_VERSION_SSL23);
+    PyModule_AddIntConstant(m, "PROTOCOL_TLSv1",
+                            PY_SSL_VERSION_TLS1);
+
+    /* session cache modes */
+    PyModule_AddIntConstant(m, "SESS_CACHE_OFF",
+                            PY_SSL_SESS_CACHE_OFF);
+    PyModule_AddIntConstant(m, "SESS_CACHE_CLIENT",
+                            PY_SSL_SESS_CACHE_CLIENT);
+    PyModule_AddIntConstant(m, "SESS_CACHE_SERVER",
+                            PY_SSL_SESS_CACHE_SERVER);
+    PyModule_AddIntConstant(m, "SESS_CACHE_BOTH",
+                            PY_SSL_SESS_CACHE_BOTH);
+}
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/forge_ssl/forge/socketmodule.h b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/forge_ssl/forge/socketmodule.h
new file mode 100644
index 0000000..a4415b5
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/forge_ssl/forge/socketmodule.h
@@ -0,0 +1,268 @@
+/* Socket module header file */
+
+/* Includes needed for the sockaddr_* symbols below */
+#ifndef MS_WINDOWS
+#ifdef __VMS
+#   include <socket.h>
+# else
+#   include <sys/socket.h>
+# endif
+# include <netinet/in.h>
+# if !(defined(__BEOS__) || defined(__CYGWIN__) || (defined(PYOS_OS2) && defined(PYCC_VACPP)))
+#  include <netinet/tcp.h>
+# endif
+
+#else /* MS_WINDOWS */
+# include <winsock2.h>
+# include <ws2tcpip.h>
+/* VC6 is shipped with old platform headers, and does not have MSTcpIP.h
+ * Separate SDKs have all the functions we want, but older ones don't have
+ * any version information.
+ * I use SIO_GET_MULTICAST_FILTER to detect a decent SDK.
+ */
+# ifdef SIO_GET_MULTICAST_FILTER
+#  include <MSTcpIP.h> /* for SIO_RCVALL */
+#  define HAVE_ADDRINFO
+#  define HAVE_SOCKADDR_STORAGE
+#  define HAVE_GETADDRINFO
+#  define HAVE_GETNAMEINFO
+#  define ENABLE_IPV6
+# else
+typedef int socklen_t;
+# endif /* IPPROTO_IPV6 */
+#endif /* MS_WINDOWS */
+
+#ifdef HAVE_SYS_UN_H
+# include <sys/un.h>
+#else
+# undef AF_UNIX
+#endif
+
+#ifdef HAVE_LINUX_NETLINK_H
+# ifdef HAVE_ASM_TYPES_H
+#  include <asm/types.h>
+# endif
+# include <linux/netlink.h>
+#else
+#  undef AF_NETLINK
+#endif
+
+#ifdef HAVE_BLUETOOTH_BLUETOOTH_H
+/*
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/rfcomm.h>
+#include <bluetooth/l2cap.h>
+#include <bluetooth/sco.h>
+#include <bluetooth/hci.h>
+*/
+#endif
+
+#ifdef HAVE_BLUETOOTH_H
+#include <bluetooth.h>
+#endif
+
+#ifdef HAVE_NETPACKET_PACKET_H
+# include <sys/ioctl.h>
+# include <net/if.h>
+# include <netpacket/packet.h>
+#endif
+
+#ifdef HAVE_LINUX_TIPC_H
+# include <linux/tipc.h>
+#endif
+
+#ifndef Py__SOCKET_H
+#define Py__SOCKET_H
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Python module and C API name */
+#define PySocket_MODULE_NAME    "_socket"
+#define PySocket_CAPI_NAME      "CAPI"
+
+/* Abstract the socket file descriptor type */
+#ifdef MS_WINDOWS
+typedef SOCKET SOCKET_T;
+#       ifdef MS_WIN64
+#               define SIZEOF_SOCKET_T 8
+#       else
+#               define SIZEOF_SOCKET_T 4
+#       endif
+#else
+typedef int SOCKET_T;
+#       define SIZEOF_SOCKET_T SIZEOF_INT
+#endif
+
+/* Socket address */
+typedef union sock_addr {
+    struct sockaddr_in in;
+#ifdef AF_UNIX
+    struct sockaddr_un un;
+#endif
+#ifdef AF_NETLINK
+    struct sockaddr_nl nl;
+#endif
+#ifdef ENABLE_IPV6
+    struct sockaddr_in6 in6;
+    struct sockaddr_storage storage;
+#endif
+#ifdef HAVE_BLUETOOTH_BLUETOOTH_H
+/*
+    struct sockaddr_l2 bt_l2;
+    struct sockaddr_rc bt_rc;
+    struct sockaddr_sco bt_sco;
+    struct sockaddr_hci bt_hci;
+*/
+#endif
+#ifdef HAVE_NETPACKET_PACKET_H
+    struct sockaddr_ll ll;
+#endif
+} sock_addr_t;
+
+/* The object holding a socket.  It holds some extra information,
+   like the address family, which is used to decode socket address
+   arguments properly. */
+
+typedef struct {
+    PyObject_HEAD
+    SOCKET_T sock_fd;           /* Socket file descriptor */
+    int sock_family;            /* Address family, e.g., AF_INET */
+    int sock_type;              /* Socket type, e.g., SOCK_STREAM */
+    int sock_proto;             /* Protocol type, usually 0 */
+    PyObject *(*errorhandler)(void); /* Error handler; checks
+                                        errno, returns NULL and
+                                        sets a Python exception */
+    double sock_timeout;                 /* Operation timeout in seconds;
+                                        0.0 means non-blocking */
+} PySocketSockObject;
+
+/* --- C API ----------------------------------------------------*/
+
+/* Short explanation of what this C API export mechanism does
+   and how it works:
+
+    The _ssl module needs access to the type object defined in
+    the _socket module. Since cross-DLL linking introduces a lot of
+    problems on many platforms, the "trick" is to wrap the
+    C API of a module in a struct which then gets exported to
+    other modules via a PyCObject.
+
+    The code in socketmodule.c defines this struct (which currently
+    only contains the type object reference, but could very
+    well also include other C APIs needed by other modules)
+    and exports it as PyCObject via the module dictionary
+    under the name "CAPI".
+
+    Other modules can now include the socketmodule.h file
+    which defines the needed C APIs to import and set up
+    a static copy of this struct in the importing module.
+
+    After initialization, the importing module can then
+    access the C APIs from the _socket module by simply
+    referring to the static struct, e.g.
+
+    Load _socket module and its C API; this sets up the global
+    PySocketModule:
+
+    if (PySocketModule_ImportModuleAndAPI())
+        return;
+
+
+    Now use the C API as if it were defined in the using
+    module:
+
+    if (!PyArg_ParseTuple(args, "O!|zz:ssl",
+
+                          PySocketModule.Sock_Type,
+
+                          (PyObject*)&Sock,
+                          &key_file, &cert_file))
+        return NULL;
+
+    Support could easily be extended to export more C APIs/symbols
+    this way. Currently, only the type object is exported,
+    other candidates would be socket constructors and socket
+    access functions.
+
+*/
+
+/* C API for usage by other Python modules */
+typedef struct {
+    PyTypeObject *Sock_Type;
+    PyObject *error;
+} PySocketModule_APIObject;
+
+/* XXX The net effect of the following appears to be to define a function
+   XXX named PySocketModule_APIObject in _ssl.c.  It's unclear why it isn't
+   XXX defined there directly.
+
+   >>> It's defined here because other modules might also want to use
+   >>> the C API.
+
+*/
+#ifndef PySocket_BUILDING_SOCKET
+
+/* --- C API ----------------------------------------------------*/
+
+/* Interfacestructure to C API for other modules.
+   Call PySocketModule_ImportModuleAndAPI() to initialize this
+   structure. After that usage is simple:
+
+   if (!PyArg_ParseTuple(args, "O!|zz:ssl",
+                         &PySocketModule.Sock_Type, (PyObject*)&Sock,
+                         &key_file, &cert_file))
+     return NULL;
+   ...
+*/
+
+static
+PySocketModule_APIObject PySocketModule;
+
+/* You *must* call this before using any of the functions in
+   PySocketModule and check its outcome; otherwise all accesses will
+   result in a segfault. Returns 0 on success. */
+
+#ifndef DPRINTF
+# define DPRINTF if (0) printf
+#endif
+
+static
+int PySocketModule_ImportModuleAndAPI(void)
+{
+    PyObject *mod = 0, *v = 0;
+    char *apimodule = PySocket_MODULE_NAME;
+    char *apiname = PySocket_CAPI_NAME;
+    void *api;
+
+    DPRINTF("Importing the %s C API...\n", apimodule);
+    mod = PyImport_ImportModuleNoBlock(apimodule);
+    if (mod == NULL)
+        goto onError;
+    DPRINTF(" %s package found\n", apimodule);
+    v = PyObject_GetAttrString(mod, apiname);
+    if (v == NULL)
+        goto onError;
+    Py_DECREF(mod);
+    DPRINTF(" API object %s found\n", apiname);
+    api = PyCObject_AsVoidPtr(v);
+    if (api == NULL)
+        goto onError;
+    Py_DECREF(v);
+    memcpy(&PySocketModule, api, sizeof(PySocketModule));
+    DPRINTF(" API object loaded and initialized.\n");
+    return 0;
+
+ onError:
+    DPRINTF(" not found.\n");
+    Py_XDECREF(mod);
+    Py_XDECREF(v);
+    return -1;
+}
+
+#endif /* !PySocket_BUILDING_SOCKET */
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* !Py__SOCKET_H */
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/forge_ssl/forge/ssl.py b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/forge_ssl/forge/ssl.py
new file mode 100644
index 0000000..aa9fc14
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/forge_ssl/forge/ssl.py
@@ -0,0 +1,486 @@
+# Wrapper module for _ssl, providing some additional facilities
+# implemented in Python.  Written by Bill Janssen.
+
+"""\
+This module provides some more Pythonic support for SSL.
+
+Object types:
+
+  SSLSocket -- subtype of socket.socket which does SSL over the socket
+
+Exceptions:
+
+  SSLError -- exception raised for I/O errors
+
+Functions:
+
+  cert_time_to_seconds -- convert time string used for certificate
+                          notBefore and notAfter functions to integer
+                          seconds past the Epoch (the time values
+                          returned from time.time())
+
+  fetch_server_certificate (HOST, PORT) -- fetch the certificate provided
+                          by the server running on HOST at port PORT.  No
+                          validation of the certificate is performed.
+
+Integer constants:
+
+SSL_ERROR_ZERO_RETURN
+SSL_ERROR_WANT_READ
+SSL_ERROR_WANT_WRITE
+SSL_ERROR_WANT_X509_LOOKUP
+SSL_ERROR_SYSCALL
+SSL_ERROR_SSL
+SSL_ERROR_WANT_CONNECT
+
+SSL_ERROR_EOF
+SSL_ERROR_INVALID_ERROR_CODE
+
+The following group define certificate requirements that one side is
+allowing/requiring from the other side:
+
+CERT_NONE - no certificates from the other side are required (or will
+            be looked at if provided)
+CERT_OPTIONAL - certificates are not required, but if provided will be
+                validated, and if validation fails, the connection will
+                also fail
+CERT_REQUIRED - certificates are required, and will be validated, and
+                if validation fails, the connection will also fail
+
+The following constants identify various SSL protocol variants:
+
+PROTOCOL_SSLv2
+PROTOCOL_SSLv3
+PROTOCOL_SSLv23
+PROTOCOL_TLSv1
+
+The following constants identify various SSL session caching modes:
+
+SESS_CACHE_OFF
+SESS_CACHE_CLIENT
+SESS_CACHE_SERVER
+SESS_CACHE_BOTH
+"""
+
+import textwrap
+
+import _forge_ssl             # if we can't import it, let the error propagate
+
+from _forge_ssl import SSLError
+from _forge_ssl import CERT_NONE, CERT_OPTIONAL, CERT_REQUIRED
+from _forge_ssl import PROTOCOL_SSLv2, PROTOCOL_SSLv3, PROTOCOL_SSLv23, PROTOCOL_TLSv1
+from _forge_ssl import SESS_CACHE_OFF, SESS_CACHE_CLIENT, SESS_CACHE_SERVER, SESS_CACHE_BOTH
+from _forge_ssl import RAND_status, RAND_egd, RAND_add
+from _forge_ssl import \
+     SSL_ERROR_ZERO_RETURN, \
+     SSL_ERROR_WANT_READ, \
+     SSL_ERROR_WANT_WRITE, \
+     SSL_ERROR_WANT_X509_LOOKUP, \
+     SSL_ERROR_SYSCALL, \
+     SSL_ERROR_SSL, \
+     SSL_ERROR_WANT_CONNECT, \
+     SSL_ERROR_EOF, \
+     SSL_ERROR_INVALID_ERROR_CODE
+
+from socket import socket, _fileobject, _delegate_methods
+from socket import error as socket_error
+from socket import getnameinfo as _getnameinfo
+import base64        # for DER-to-PEM translation
+import errno
+
+class SSLSocket(socket):
+
+    """This class implements a subtype of socket.socket that wraps
+    the underlying OS socket in an SSL context when necessary, and
+    provides read and write methods over that channel."""
+
+    def __init__(self, parent_socket, sock, keyfile=None, certfile=None,
+                 server_side=False, cert_reqs=CERT_NONE,
+                 ssl_version=PROTOCOL_SSLv23,
+                 sess_cache_mode=SESS_CACHE_SERVER,
+                 sess_id_ctx=None,
+                 ca_certs=None,
+                 do_handshake_on_connect=True,
+                 suppress_ragged_eofs=True):
+        socket.__init__(self, _sock=sock._sock)
+        # The initializer for socket overrides the methods send(), recv(), etc.
+        # in the instancce, which we don't need -- but we want to provide the
+        # methods defined in SSLSocket.
+        for attr in _delegate_methods:
+            try:
+                delattr(self, attr)
+            except AttributeError:
+                pass
+
+        if certfile and not keyfile:
+            keyfile = certfile
+
+        create = True
+        connected = False
+        if not server_side:
+            # see if it's connected
+            try:
+                socket.getpeername(self)
+                connected = True
+            except socket_error, e:
+                if e.errno != errno.ENOTCONN:
+                    raise
+                # no, no connection yet
+                self._sslobj = None
+                create = False
+        if create:
+            # yes, create the SSL object
+            if parent_socket == None:
+                self._sslobj = _forge_ssl.sslwrap(
+                                                 self._sock,
+                                                 server_side,
+                                                 keyfile, certfile,
+                                                 cert_reqs, ssl_version,
+                                                 sess_cache_mode, sess_id_ctx,
+                                                 ca_certs)
+            else:
+                self._sslobj = parent_socket._sslobj.wrap_accepted(self._sock)
+
+        if connected and do_handshake_on_connect:
+            self.do_handshake()
+        self.keyfile = keyfile
+        self.certfile = certfile
+        self.cert_reqs = cert_reqs
+        self.ssl_version = ssl_version
+        self.sess_cache_mode = sess_cache_mode
+        self.sess_id_ctx = sess_id_ctx
+        self.ca_certs = ca_certs
+        self.do_handshake_on_connect = do_handshake_on_connect
+        self.suppress_ragged_eofs = suppress_ragged_eofs
+        self._makefile_refs = 0
+
+    def read(self, len=1024):
+
+        """Read up to LEN bytes and return them.
+        Return zero-length string on EOF."""
+
+        try:
+            return self._sslobj.read(len)
+        except SSLError, x:
+            if x.args[0] == SSL_ERROR_EOF and self.suppress_ragged_eofs:
+                return ''
+            else:
+                raise
+
+    def write(self, data):
+
+        """Write DATA to the underlying SSL channel.  Returns
+        number of bytes of DATA actually transmitted."""
+
+        return self._sslobj.write(data)
+
+    def getpeercert(self, binary_form=False):
+
+        """Returns a formatted version of the data in the
+        certificate provided by the other end of the SSL channel.
+        Return None if no certificate was provided, {} if a
+        certificate was provided, but not validated."""
+
+        return self._sslobj.peer_certificate(binary_form)
+
+    def cipher(self):
+
+        if not self._sslobj:
+            return None
+        else:
+            return self._sslobj.cipher()
+
+    def send(self, data, flags=0):
+        if self._sslobj:
+            if flags != 0:
+                raise ValueError(
+                    "non-zero flags not allowed in calls to send() on %s" %
+                    self.__class__)
+            while True:
+                try:
+                    v = self._sslobj.write(data)
+                except SSLError, x:
+                    if x.args[0] == SSL_ERROR_WANT_READ:
+                        return 0
+                    elif x.args[0] == SSL_ERROR_WANT_WRITE:
+                        return 0
+                    else:
+                        raise
+                else:
+                    return v
+        else:
+            return socket.send(self, data, flags)
+
+    def sendto(self, data, addr, flags=0):
+        if self._sslobj:
+            raise ValueError("sendto not allowed on instances of %s" %
+                             self.__class__)
+        else:
+            return socket.sendto(self, data, addr, flags)
+
+    def sendall(self, data, flags=0):
+        if self._sslobj:
+            if flags != 0:
+                raise ValueError(
+                    "non-zero flags not allowed in calls to sendall() on %s" %
+                    self.__class__)
+            amount = len(data)
+            count = 0
+            while (count < amount):
+                v = self.send(data[count:])
+                count += v
+            return amount
+        else:
+            return socket.sendall(self, data, flags)
+
+    def recv(self, buflen=1024, flags=0):
+        if self._sslobj:
+            if flags != 0:
+                raise ValueError(
+                    "non-zero flags not allowed in calls to recv() on %s" %
+                    self.__class__)
+            return self.read(buflen)
+        else:
+            return socket.recv(self, buflen, flags)
+
+    def recv_into(self, buffer, nbytes=None, flags=0):
+        if buffer and (nbytes is None):
+            nbytes = len(buffer)
+        elif nbytes is None:
+            nbytes = 1024
+        if self._sslobj:
+            if flags != 0:
+                raise ValueError(
+                  "non-zero flags not allowed in calls to recv_into() on %s" %
+                  self.__class__)
+            tmp_buffer = self.read(nbytes)
+            v = len(tmp_buffer)
+            buffer[:v] = tmp_buffer
+            return v
+        else:
+            return socket.recv_into(self, buffer, nbytes, flags)
+
+    def recvfrom(self, addr, buflen=1024, flags=0):
+        if self._sslobj:
+            raise ValueError("recvfrom not allowed on instances of %s" %
+                             self.__class__)
+        else:
+            return socket.recvfrom(self, addr, buflen, flags)
+
+    def recvfrom_into(self, buffer, nbytes=None, flags=0):
+        if self._sslobj:
+            raise ValueError("recvfrom_into not allowed on instances of %s" %
+                             self.__class__)
+        else:
+            return socket.recvfrom_into(self, buffer, nbytes, flags)
+
+    def pending(self):
+        if self._sslobj:
+            return self._sslobj.pending()
+        else:
+            return 0
+
+    def unwrap(self):
+        if self._sslobj:
+            try:
+                # if connected then shutdown
+                self.getpeername()
+                s = self._sslobj.shutdown()
+            except:
+                s = self._sock
+            self._sslobj = None
+            return s
+        else:
+            raise ValueError("No SSL wrapper around " + str(self))
+
+    def shutdown(self, how):
+        self._sslobj = None
+        socket.shutdown(self, how)
+
+    def close(self):
+        if self._makefile_refs < 1:
+            if self._sslobj:
+                self.unwrap()
+            socket.close(self)
+        else:
+            self._makefile_refs -= 1
+
+    def do_handshake(self):
+
+        """Perform a TLS/SSL handshake."""
+
+        self._sslobj.do_handshake()
+
+    def connect(self, addr):
+
+        """Connects to remote ADDR, and then wraps the connection in
+        an SSL channel."""
+
+        # Here we assume that the socket is client-side, and not
+        # connected at the time of the call.  We connect it, then wrap it.
+        if self._sslobj:
+            raise ValueError("attempt to connect already-connected SSLSocket!")
+        socket.connect(self, addr)
+        self._sslobj = _forge_ssl.sslwrap(self._sock, False,
+                                          self.keyfile, self.certfile,
+                                          self.cert_reqs, self.ssl_version,
+                                          self.sess_cache_mode,
+                                          self.sess_id_ctx,
+                                          self.ca_certs)
+        if self.do_handshake_on_connect:
+            self.do_handshake()
+
+    def accept(self):
+
+        """Accepts a new connection from a remote client, and returns
+        a tuple containing that new connection wrapped with a server-side
+        SSL channel, and the address of the remote client."""
+
+        newsock, addr = socket.accept(self)
+        return (SSLSocket(self,
+                          newsock,
+                          keyfile=self.keyfile,
+                          certfile=self.certfile,
+                          server_side=True,
+                          cert_reqs=self.cert_reqs,
+                          ssl_version=self.ssl_version,
+                          sess_cache_mode=self.sess_cache_mode,
+                          sess_id_ctx=self.sess_id_ctx,
+                          ca_certs=self.ca_certs,
+                          do_handshake_on_connect=self.do_handshake_on_connect,
+                          suppress_ragged_eofs=self.suppress_ragged_eofs),
+                addr)
+
+    def makefile(self, mode='r', bufsize=-1):
+
+        """Make and return a file-like object that
+        works with the SSL connection.  Just use the code
+        from the socket module."""
+
+        self._makefile_refs += 1
+        # close=True so as to decrement the reference count when done with
+        # the file-like object.
+        return _fileobject(self, mode, bufsize, close=True)
+
+
+
+def wrap_socket(sock, parent_socket=None, keyfile=None, certfile=None,
+                server_side=False, cert_reqs=CERT_NONE,
+                ssl_version=PROTOCOL_SSLv23,
+                sess_cache_mode=SESS_CACHE_SERVER,
+                sess_id_ctx=None,
+                ca_certs=None,
+                do_handshake_on_connect=True,
+                suppress_ragged_eofs=True):
+
+    return SSLSocket(parent_socket,
+                     sock, keyfile=keyfile, certfile=certfile,
+                     server_side=server_side, cert_reqs=cert_reqs,
+                     ssl_version=ssl_version,
+                     sess_cache_mode=sess_cache_mode,
+                     sess_id_ctx=sess_id_ctx,
+                     ca_certs=ca_certs,
+                     do_handshake_on_connect=do_handshake_on_connect,
+                     suppress_ragged_eofs=suppress_ragged_eofs)
+
+
+# some utility functions
+
+def cert_time_to_seconds(cert_time):
+
+    """Takes a date-time string in standard ASN1_print form
+    ("MON DAY 24HOUR:MINUTE:SEC YEAR TIMEZONE") and return
+    a Python time value in seconds past the epoch."""
+
+    import time
+    return time.mktime(time.strptime(cert_time, "%b %d %H:%M:%S %Y GMT"))
+
+PEM_HEADER = "-----BEGIN CERTIFICATE-----"
+PEM_FOOTER = "-----END CERTIFICATE-----"
+
+def DER_cert_to_PEM_cert(der_cert_bytes):
+
+    """Takes a certificate in binary DER format and returns the
+    PEM version of it as a string."""
+
+    if hasattr(base64, 'standard_b64encode'):
+        # preferred because older API gets line-length wrong
+        f = base64.standard_b64encode(der_cert_bytes)
+        return (PEM_HEADER + '\n' +
+                textwrap.fill(f, 64) + '\n' +
+                PEM_FOOTER + '\n')
+    else:
+        return (PEM_HEADER + '\n' +
+                base64.encodestring(der_cert_bytes) +
+                PEM_FOOTER + '\n')
+
+def PEM_cert_to_DER_cert(pem_cert_string):
+
+    """Takes a certificate in ASCII PEM format and returns the
+    DER-encoded version of it as a byte sequence"""
+
+    if not pem_cert_string.startswith(PEM_HEADER):
+        raise ValueError("Invalid PEM encoding; must start with %s"
+                         % PEM_HEADER)
+    if not pem_cert_string.strip().endswith(PEM_FOOTER):
+        raise ValueError("Invalid PEM encoding; must end with %s"
+                         % PEM_FOOTER)
+    d = pem_cert_string.strip()[len(PEM_HEADER):-len(PEM_FOOTER)]
+    return base64.decodestring(d)
+
+def get_server_certificate(addr, ssl_version=PROTOCOL_SSLv3, ca_certs=None):
+
+    """Retrieve the certificate from the server at the specified address,
+    and return it as a PEM-encoded string.
+    If 'ca_certs' is specified, validate the server cert against it.
+    If 'ssl_version' is specified, use it in the connection attempt."""
+
+    host, port = addr
+    if (ca_certs is not None):
+        cert_reqs = CERT_REQUIRED
+    else:
+        cert_reqs = CERT_NONE
+    s = wrap_socket(socket(), ssl_version=ssl_version,
+                    cert_reqs=cert_reqs, ca_certs=ca_certs)
+    s.connect(addr)
+    dercert = s.getpeercert(True)
+    s.close()
+    return DER_cert_to_PEM_cert(dercert)
+
+def get_protocol_name(protocol_code):
+    if protocol_code == PROTOCOL_TLSv1:
+        return "TLSv1"
+    elif protocol_code == PROTOCOL_SSLv23:
+        return "SSLv23"
+    elif protocol_code == PROTOCOL_SSLv2:
+        return "SSLv2"
+    elif protocol_code == PROTOCOL_SSLv3:
+        return "SSLv3"
+    else:
+        return "<unknown>"
+
+
+# a replacement for the old socket.ssl function
+
+def sslwrap_simple(sock, keyfile=None, certfile=None):
+
+    """A replacement for the old socket.ssl function.  Designed
+    for compability with Python 2.5 and earlier.  Will disappear in
+    Python 3.0."""
+
+    if hasattr(sock, "_sock"):
+        sock = sock._sock
+
+    ssl_sock = _forge_ssl.sslwrap(sock, 0, keyfile, certfile,
+                                  CERT_NONE, PROTOCOL_SSLv23,
+                                  SESS_CACHE_SERVER, None, None)
+    try:
+        sock.getpeername()
+    except:
+        # no, no connection yet
+        pass
+    else:
+        # yes, do the handshake
+        ssl_sock.do_handshake()
+
+    return ssl_sock
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/forge_ssl/setup.py b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/forge_ssl/setup.py
new file mode 100644
index 0000000..350ae37
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/forge_ssl/setup.py
@@ -0,0 +1,12 @@
+#!/usr/bin/env python
+
+from distutils.core import setup, Extension
+
+ssl = Extension('_forge_ssl',
+        sources = ['forge/_ssl.c'])
+
+setup (name = 'Forge SSL',
+        version = '1.0',
+        description = 'Python SSL with session cache support.',
+        ext_modules = [ssl],
+        py_modules = ['forge.ssl'])
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/form.html b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/form.html
new file mode 100644
index 0000000..cfe9f94
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/form.html
@@ -0,0 +1,150 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+   <head>
+      <title>Forge Form Test</title>
+      <link type="text/css" rel="stylesheet" media="all" href="screen.css" />
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
+      <script type="text/javascript" src="forge/debug.js"></script>
+      <script type="text/javascript" src="forge/util.js"></script>
+      <script type="text/javascript" src="forge/log.js"></script>
+      <script type="text/javascript" src="forge/form.js"></script>
+      <script type="text/javascript" src="form.js"></script>
+
+      <link type="text/css" rel="stylesheet" media="all" href="screen.css" />
+   </head>
+<body>
+<div class="nav"><a href="index.html">Forge Tests</a> / Form</div>
+
+<div class="header">
+   <h1>Form Tests</h1>
+</div>
+
+<div class="content">
+
+<form id="form-1" class="ajax standard" method="post" action="">
+   <fieldset>
+      <legend>Text</legend>
+
+      <p>
+         <input name="text1" type="text" value="value1" />
+         <input name="text2.sub1" type="text" value="value2" />
+         <input name="text2.sub2[]" type="text" value="value3" />
+         <input name="text2.sub2[]" type="text" value="value4" />
+         <input name="text2.sub3[0]" type="text" value="value5" />
+         <input name="text2.sub4[0][0]" type="text" value="value6" />
+         <input name="text2.sub4[0][]" type="text" value="value7" />
+         <input name="text2.sub5[foo][]" type="text" value="value8" />
+         <input name="text2.sub5[dotted.name]" type="text" value="value9" />
+         <input name="text2.sub6[]" type="text" value="value10" />
+         <input name="text2.sub7[].@" type="text" value="value11" />
+         <input name="text2.sub7[].@" type="text" value="value12" />
+         <input name="text2.sub8[][].@" type="text" value="value13" />
+      </p>
+
+      <p>
+         <label>username <input name="username" type="text" value="username" /></label>
+         <label>password <input name="password" type="password" value="password" /></label>
+      </p>
+
+      <p>
+         <label>password1.1 <input name="password1" type="password" value="password" /></label>
+         <label>password1.2 <input name="password1" type="password" value="password" /></label>
+      </p>
+   </fieldset>
+
+   <fieldset>
+      <legend>Checkbox</legend>
+
+      <p>
+         <label><input name="checkbox1" type="checkbox" value="c1" /> C1</label>
+         <label><input name="checkbox1" type="checkbox" value="c2" /> C1</label>
+         <label><input name="checkbox1" type="checkbox" value="c3" /> C1</label>
+      </p>
+
+      <p>
+         <label><input name="checkbox2[]" type="checkbox" value="c1" /> C2[]</label>
+         <label><input name="checkbox2[]" type="checkbox" value="c2" /> C2[]</label>
+         <label><input name="checkbox2[3]" type="checkbox" value="c3" /> C2[3]</label>
+         <label><input name="checkbox2[]" type="checkbox" value="c4" /> C2[]</label>
+         <label><input name="checkbox2.sub1" type="checkbox" value="c4" /> C2.sub1</label>
+      </p>
+
+      <p>
+         <label><input name="checkbox3.sub1" type="checkbox" value="c1" /> C3.s1</label>
+         <label><input name="checkbox3.sub2" type="checkbox" value="c2" /> C3.s2</label>
+         <label><input name="checkbox3.sub2" type="checkbox" value="c3" /> C3.s2</label>
+         <label><input name="checkbox3[]" type="checkbox" value="c4" /> C3[]</label>
+      </p>
+   </fieldset>
+
+   <fieldset>
+      <legend>Radio</legend>
+
+      <p>
+         <label><input name="radio1" type="radio" value="r1" /> R1</label>
+         <label><input name="radio1" type="radio" value="r2" /> R1</label>
+         <label><input name="radio1" type="radio" value="r3" /> R1</label>
+         <label><input name="radio1" type="radio" value="r4" /> R1</label>
+      </p>
+
+      <p>
+         <label><input name="radio2.sub1" type="radio" value="r1" /> R2.s1</label>
+         <label><input name="radio2.sub1" type="radio" value="r2" /> R2.s1</label>
+         <label><input name="radio2.sub2" type="radio" value="r3" /> R2.s2</label>
+         <label><input name="radio2.sub2" type="radio" value="r4" /> R2.s2</label>
+      </p>
+   </fieldset>
+
+   <fieldset>
+      <legend>Select</legend>
+      <p>
+         <select name="select1">
+            <option value="select1">Select 1</option>
+            <option value="select2">Select 2</option>
+            <option value="select3">Select 3</option>
+         </select>
+      </p>
+
+      <p>
+         <select name="select2" multiple="multiple">
+            <option value="select1">Select 1</option>
+            <option value="select2">Select 2</option>
+            <option value="select3">Select 3</option>
+         </select>
+      </p>
+   </fieldset>
+
+   <fieldset>
+      <legend>Text Area</legend>
+
+      <textarea name="textarea">Test text.</textarea>
+   </fieldset>
+
+   <fieldset>
+      <legend>Buttons</legend>
+
+      <p>
+         <button name="button1" type="submit" value="submit">Submit Form</button>
+         <button name="button2" type="reset" value="reset">Reset Form</button>
+      </p>
+   </fieldset>
+
+   <fieldset>
+      <legend>Input Buttons</legend>
+      
+      <p>
+         <input name="submit" type="submit" value="Submit Form" />
+         <input name="reset" type="reset" value="Reset Form" />
+      </p>
+   </fieldset>
+</form>
+
+<p>Result:</p>
+<div id="result"></div>
+
+</div>
+
+</body>
+</html>
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/form.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/form.js
new file mode 100644
index 0000000..abfbab0
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/form.js
@@ -0,0 +1,40 @@
+/**
+ * Forge Form Tests
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2011 Digital Bazaar, Inc. All rights reserved.
+ */
+(function($) {
+$(document).ready(function()
+{
+   // logging category
+   var cat = 'forge.tests.form';
+   
+   // local alias
+   var forge = window.forge;
+   
+   $('form.ajax').each(function(i, form)
+   {
+      $(form).submit(function()
+      {
+         try
+         {
+            var f = forge.form.serialize($(this));
+            forge.log.debug(cat, 'result:', JSON.stringify(f));
+            $('#result').html(JSON.stringify(f));
+            
+            /* dictionary test
+            var f = forge.form.serialize($(this), '.', {'username':'user'});
+            forge.log.debug(cat, 'result:', JSON.stringify(f));
+            */
+         }
+         catch(e)
+         {
+            console.log('exception', e.stack);
+         }
+         return false;
+      });
+   });
+});
+})(jQuery);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/heartbleed.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/heartbleed.js
new file mode 100644
index 0000000..b38869a
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/heartbleed.js
@@ -0,0 +1,55 @@
+var forge = require('../js/forge');
+var net = require('net');
+
+var socket = new net.Socket();
+
+var client = forge.tls.createConnection({
+  server: false,
+  verify: function(connection, verified, depth, certs) {
+    // skip verification for testing
+    return true;
+  },
+  connected: function(connection) {
+    // heartbleeds 2k
+    console.log('[tls] connected');
+    connection.prepareHeartbeatRequest('', 2048);
+    setTimeout(function() {
+      client.close();
+    }, 1000);
+  },
+  tlsDataReady: function(connection) {
+    // encrypted data is ready to be sent to the server
+    var data = connection.tlsData.getBytes();
+    socket.write(data, 'binary');
+  },
+  dataReady: function(connection) {
+    // clear data from the server is ready
+    var data = connection.data.getBytes();
+    console.log('[tls] received from the server: ' + data);
+  },
+  heartbeatReceived: function(c, payload) {
+    console.log('Heartbleed:\n' + payload.toHex());
+    client.close();
+  },
+  closed: function() {
+    console.log('[tls] disconnected');
+    socket.end();
+  },
+  error: function(connection, error) {
+    console.log('[tls] error', error);
+  }
+});
+
+socket.on('connect', function() {
+  console.log('[socket] connected');
+  client.handshake();
+});
+socket.on('data', function(data) {
+  client.process(data.toString('binary'));
+});
+socket.on('end', function() {
+  console.log('[socket] disconnected');
+});
+
+// connect
+socket.connect(443, 'yahoo.com');
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/http.html b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/http.html
new file mode 100644
index 0000000..3bdf941
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/http.html
@@ -0,0 +1,229 @@
+<html>
+   <head>
+      <link type="text/css" rel="stylesheet" media="all" href="screen.css" />
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js"></script>
+      <script type="text/javascript" src="forge/util.js"></script>
+      <script type="text/javascript" src="forge/socket.js"></script>
+      <script type="text/javascript" src="forge/http.js"></script>
+      <script type="text/javascript" src="forge/log.js"></script>
+      
+      <script type="text/javascript">
+      //<![CDATA[
+      // logging category
+      var cat = 'forge.tests.http';
+
+      window.forge.socketPool =
+      {
+         ready: function()
+         {
+            forge.log.debug(cat, 'SocketPool ready.');
+         }
+      };
+
+      swfobject.embedSWF(
+         'forge/SocketPool.swf', 'socketPool', '0', '0', '9.0.0',
+         false, {}, {allowscriptaccess: 'always'}, {});
+      
+      // local aliases
+      var net = window.forge.net;
+      var http = window.forge.http;
+      var util = window.forge.util;
+
+      var client;
+      
+      function client_init()
+      {
+         try
+         {
+            var sp = net.createSocketPool({
+               flashId: 'socketPool',
+               policyPort: 19945,
+               msie: false
+            });
+            client = http.createClient({
+               //url: 'http://' + window.location.host,
+               socketPool: sp,
+               connections: 10
+            });
+            
+            document.getElementById('feedback').innerHTML =
+               'HTTP client created';
+         }
+         catch(ex)
+         {
+            forge.log.error(cat, ex);
+         }
+         return false;
+      }
+      
+      function client_cleanup()
+      {
+         var sp = client.socketPool;
+         client.destroy();
+         sp.destroy();
+         document.getElementById('feedback').innerHTML =
+            'HTTP client cleaned up';
+         return false;
+      }
+
+      function client_send()
+      {
+         var request = http.createRequest({
+            method: 'GET',
+            path: '/'
+            //body: 'echo=foo',
+            //headers: [{'Content-Type': 'application/x-www-form-urlencoded'}]
+         });
+         
+         client.send({
+            request: request,
+            connected: function(e)
+            {
+               forge.log.debug(cat, 'connected', e);
+            },
+            headerReady: function(e)
+            {
+               forge.log.debug(cat, 'header ready', e);
+            },
+            bodyReady: function(e)
+            {
+               forge.log.debug(cat, 'body ready', e);
+            },
+            error: function(e)
+            {
+               forge.log.error(cat, 'error', e);
+            }
+         });
+         document.getElementById('feedback').innerHTML =
+            'HTTP request sent';
+         return false;
+      }
+
+      function client_send_10()
+      {
+         for(var i = 0; i < 10; ++i)
+         {
+            client_send();
+         }
+         return false;
+      }
+
+      function client_stress()
+      {
+         for(var i = 0; i < 10; ++i)
+         {
+            setTimeout(function()
+            {
+               for(var i = 0; i < 10; ++i)
+               {
+                  client_send();
+               }
+            }, 0);
+         }
+         return false;
+      }
+      
+      function client_cookies()
+      {
+         var cookie =
+         {
+            name: 'test-cookie',
+            value: 'test-value',
+            maxAge: -1,
+            secure: false,
+            path: '/'
+         };
+         client.setCookie(cookie);
+         forge.log.debug(cat, 'cookie', client.getCookie('test-cookie'));
+      }
+
+      function client_clear_cookies()
+      {
+         client.clearCookies();
+      }
+
+      function request_add_cookies()
+      {
+         var cookie1 =
+         {
+            name: 'test-cookie1',
+            value: 'test-value1',
+            maxAge: -1,
+            secure: false,
+            path: '/'
+         };
+         var cookie2 =
+         {
+            name: 'test-cookie2',
+            value: 'test-value2',
+            maxAge: -1,
+            secure: false,
+            path: '/'
+         };
+         var request = http.createRequest({
+            method: 'GET',
+            path: '/'
+         });
+         request.addCookie(cookie1);
+         request.addCookie(cookie2);
+         forge.log.debug(cat, 'request', request.toString());
+      }
+
+      function response_get_cookies()
+      {
+         var response = http.createResponse();
+         response.appendField('Set-Cookie',
+            'test-cookie1=test-value1; max-age=0; path=/; secure');
+         response.appendField('Set-Cookie',
+            'test-cookie2=test-value2; ' +
+            'expires=Thu, 21-Aug-2008 23:47:25 GMT; path=/');
+         var cookies = response.getCookies();
+         forge.log.debug(cat, 'cookies', cookies);
+      }
+      
+      //]]>
+      </script>
+   </head>
+   <body>
+      <div class="nav"><a href="index.html">Forge Tests</a> / HTTP</div>
+
+      <div class="header">
+         <h1>HTTP Test</h1>
+      </div>
+
+      <div class="content">
+
+      <div id="socketPool">
+         <p>Could not load the flash SocketPool.</p>
+      </div>
+
+      <fieldset class="section">
+         <ul>
+            <li>Use the controls below to test the HTTP client.</li>
+            <li>You currently need a JavaScript console to view the output.</li>
+         </ul>
+      </fieldset>
+
+      <fieldset class="section">
+      <legend>Controls</legend>
+         <button id="init" onclick="javascript:return client_init();">init</button>
+         <button id="cleanup" onclick="javascript:return client_cleanup();">cleanup</button>
+         <button id="send" onclick="javascript:return client_send();">send</button>
+         <button id="send10" onclick="javascript:return client_send_10();">send 10</button>
+         <button id="stress" onclick="javascript:return client_stress();">stress</button>
+         <button id="client_cookies" onclick="javascript:return client_cookies();">cookies</button>
+         <button id="clear_cookies" onclick="javascript:return client_clear_cookies();">clear cookies</button>
+         <button id="add_cookies" onclick="javascript:return request_add_cookies();">add cookies</button>
+         <button id="get_cookies" onclick="javascript:return response_get_cookies();">get cookies</button>
+      </fieldset>
+
+      <fieldset class="section">
+      <legend>Feedback</legend>
+      <p>Feedback from the flash SocketPool:</p>
+      <div id="feedback">
+      None
+      </div>
+
+      </div>
+   </body>
+</html>
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/index.html b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/index.html
new file mode 100644
index 0000000..2e27c8a
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/index.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+   <meta name="author" content="Digital Bazaar, Inc.; http://digitalbazaar.com/" />
+   <meta name="description" content="Forge Tests" />
+   <link type="text/css" rel="stylesheet" media="all" href="screen.css" />
+   <title>Forge Tests</title>
+</head>
+
+<body>
+  <div class="header">
+     <h1>Forge Tests</h1>
+  </div>
+  <div class="content">
+     <fieldset class="section">
+       <ul>
+         <li>These are various tests of the Forge JavaScript libraries.</li>
+         <li>Please see the code and documentation for details.</li>
+       </ul>
+     </fieldset>
+     <fieldset class="section">
+        <legend>Tests</legend>
+        <ul>
+           <li><a href="common.html">Common</a>
+              <sup>1,2</sup>
+           </li>
+           <li><a href="form.html">Form</a>
+              <sup>1,2</sup>
+           </li>
+           <li><a href="performance.html">Performance</a>
+              <sup>1,2</sup>
+           </li>
+           <li><a href="tasks.html">Tasks</a>
+              <sup>1,2</sup>
+           </li>
+           <li><a href="socketPool.html">SocketPool</a>
+              <sup>1</sup>
+           </li>
+           <li><a href="http.html">HTTP</a>
+              <sup>1</sup>
+           </li>
+           <li><a href="tls.html">TLS</a>
+              <sup>2</sup>
+           </li>
+           <li><a href="xhr.html">XHR</a>
+              <sup>1,2</sup>
+           </li>
+           <li><a href="webid.html">Web ID</a>
+              <sup>1,2</sup>
+           </li>
+        </ul>
+        <div>
+           <span><sup>1</sup>: Test works over HTTP</span><br/>
+           <span><sup>2</sup>: Test works over HTTPS</span>
+        </div>
+     </fieldset>
+  </div>
+  <div class="footer">
+     Copyright &copy; 2010 <a href="http://digitalbazaar.com/">Digital Bazaar, Inc.</a>
+  </div>
+</body>
+</html>
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/keygen.html b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/keygen.html
new file mode 100644
index 0000000..22e2432
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/keygen.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="utf-8" />
+  <script type="text/javascript" src="forge/util.js"></script>
+  <script type="text/javascript" src="forge/sha256.js"></script>
+  <script type="text/javascript" src="forge/cipher.js"></script>
+  <script type="text/javascript" src="forge/cipherModes.js"></script>
+  <script type="text/javascript" src="forge/aes.js"></script>
+  <script type="text/javascript" src="forge/prng.js"></script>
+  <script type="text/javascript" src="forge/random.js"></script>
+  <script type="text/javascript" src="forge/jsbn.js"></script>
+  <script type="text/javascript" src="forge/asn1.js"></script>
+  <script type="text/javascript" src="forge/pem.js"></script>
+  <script type="text/javascript" src="forge/rsa.js"></script>
+</head>
+
+<body>
+
+<script type="text/javascript">
+
+function async() {
+  var bits = 2048;
+  console.log('Generating ' + bits + '-bit key-pair...');
+  var st = +new Date();
+  forge.pki.rsa.generateKeyPair({
+    bits: bits,
+    workers: -1,
+    /*workLoad: 100,*/
+    workerScript: 'forge/prime.worker.js'
+  }, function(err, keypair) {
+    var et = +new Date();
+    console.log('Key-pair created in ' + (et - st) + 'ms.');
+    //console.log('private', forge.pki.privateKeyToPem(keypair.privateKey));
+    //console.log('public', forge.pki.publicKeyToPem(keypair.publicKey));
+  });
+}
+
+function sync() {
+  var bits = 2048;
+  console.log('Generating ' + bits + '-bit key-pair...');
+  var st = +new Date();
+  var keypair = forge.pki.rsa.generateKeyPair(bits);
+  var et = +new Date();
+  console.log('Key-pair created in ' + (et - st) + 'ms.');
+  //console.log('private', forge.pki.privateKeyToPem(keypair.privateKey));
+  //console.log('public', forge.pki.publicKeyToPem(keypair.publicKey));
+}
+
+async();
+//sync();
+
+</script>
+
+</body>
+</html>
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/login.css b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/login.css
new file mode 100644
index 0000000..3a1cb05
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/login.css
@@ -0,0 +1,26 @@
+/* WebID CSS */
+
+* {
+margin: 0;
+padding: 0;
+}
+
+p {
+margin: 10px 0;
+}
+
+body {
+margin: 10px;
+font-family: helvetica,arial,sans-serif;
+font-size: 14px;
+}
+
+#header {
+padding: 5px;
+background-color: #ddf;
+border: 2px solid #000;
+}
+
+#header h1 {
+font-size: 20px;
+}
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/loginDemo.html b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/loginDemo.html
new file mode 100644
index 0000000..a1aecd4
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/loginDemo.html
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+   <head>
+      <title>Web ID Login</title>
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js"></script>
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
+      <script type="text/javascript" src="forge/debug.js"></script>
+      <script type="text/javascript" src="forge/util.js"></script>
+      <script type="text/javascript" src="forge/log.js"></script>
+      <script type="text/javascript" src="forge/task.js"></script>
+      <script type="text/javascript" src="forge/socket.js"></script>
+      <script type="text/javascript" src="forge/md5.js"></script>
+      <script type="text/javascript" src="forge/sha1.js"></script>
+      <script type="text/javascript" src="forge/hmac.js"></script>
+      <script type="text/javascript" src="forge/aes.js"></script>
+      <script type="text/javascript" src="forge/pem.js"></script>
+      <script type="text/javascript" src="forge/asn1.js"></script>
+      <script type="text/javascript" src="forge/jsbn.js"></script>
+      <script type="text/javascript" src="forge/prng.js"></script>
+      <script type="text/javascript" src="forge/random.js"></script>
+      <script type="text/javascript" src="forge/oids.js"></script>
+      <script type="text/javascript" src="forge/rsa.js"></script>
+      <script type="text/javascript" src="forge/pbe.js"></script>
+      <script type="text/javascript" src="forge/x509.js"></script>
+      <script type="text/javascript" src="forge/pki.js"></script>
+      <script type="text/javascript" src="forge/tls.js"></script>
+      <script type="text/javascript" src="forge/aesCipherSuites.js"></script>
+      <script type="text/javascript" src="forge/http.js"></script>
+      <script type="text/javascript" src="forge/xhr.js"></script>
+      <script type="text/javascript" src="loginDemo.js"></script>
+      
+      <link type="text/css" rel="stylesheet" media="all" href="login.css" />
+   </head>
+<body>
+
+<div id="header">
+   <h1>Web ID Login</h1>
+</div>
+
+<div id="content">
+   <p>Please select a Web ID to log in to <span id="domain">this website</span>.</p>
+
+   <div id="webids"></div>
+
+   <p>Do not select an identity if you do not trust this website.</p>
+
+   <!-- div used to hold the flash socket pool implementation -->
+   <div id="socketPool">
+      <p>Could not load the flash SocketPool.</p>
+   </div>
+</div>
+
+</body>
+</html>
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/loginDemo.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/loginDemo.js
new file mode 100644
index 0000000..859e1f0
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/loginDemo.js
@@ -0,0 +1,149 @@
+/**
+ * Forge Web ID Tests
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010 Digital Bazaar, Inc. All rights reserved.
+ */
+(function($)
+{
+   // load flash socket pool
+   window.forge.socketPool = {};
+   window.forge.socketPool.ready = function()
+   {
+      // init page
+      init($);
+   };
+   swfobject.embedSWF(
+      'forge/SocketPool.swf', 'socketPool', '0', '0', '9.0.0',
+      false, {}, {allowscriptaccess: 'always'}, {});
+})(jQuery);
+
+var init = function($)
+{
+   // logging category
+   var cat = 'forge.tests.loginDemo';
+   
+   // local alias
+   var forge = window.forge;
+   try
+   {
+      // get query variables
+      var query = forge.util.getQueryVariables();
+      var domain = query.domain || '';
+      var auth = query.auth || '';
+      var redirect = query.redirect || '';
+      var pport = query.pport || 843;
+      redirect = 'https://' + domain + '/' + redirect;
+      if(domain)
+      {
+         $('#domain').html('`' + domain + '`');
+      } 
+      
+      // for chosen webid
+      var chosen = null;
+      
+      // init forge xhr
+      forge.xhr.init({
+         flashId: 'socketPool',
+         msie: $.browser.msie,
+         url: 'https://' + domain,
+         policyPort: pport,
+         connections: 1,
+         caCerts: [],
+         verify: function(c, verified, depth, certs)
+         {
+            // don't care about cert verification for test
+            return true;
+         },
+         getCertificate: function(c)
+         {
+            forge.log.debug(cat, 'using cert', chosen.certificate);
+            return chosen.certificate;
+         },
+         getPrivateKey: function(c)
+         {
+            //forge.log.debug(cat, 'using private key', chosen.privateKey);
+            return chosen.privateKey;
+         }
+      });
+      
+      // get flash API
+      var flashApi = document.getElementById('socketPool');
+      
+      // get web ids collection
+      var webids = forge.util.getItem(
+         flashApi, 'forge.test.webid', 'webids');
+      webids = webids || {};
+      
+      var id = 0;
+      var list = $('<ul/>');
+      for(var key in webids)
+      {
+         (function(webid)
+         {
+            var cert = forge.pki.certificateFromPem(webid.certificate);
+            var item = $('<li/>');
+            var button = $('<button>');
+            button.attr('id', '' + (webid + id++));
+            button.html('Choose');
+            button.click(function()
+            {
+               button.attr('disabled', 'disabled');
+               
+               // set chosen webid
+               chosen = webid;
+               
+               // do webid call
+               $.ajax(
+               {
+                  type: 'GET',
+                  url: '/' + auth,
+                  success: function(data, textStatus, xhr)
+                  {
+                     if(data !== '')
+                     {
+                        forge.log.debug(cat, 'authentication completed');
+                        forge.log.debug(cat, data);
+                        window.name = data;
+                     }
+                     else
+                     {
+                        forge.log.debug(cat, 'authentication failed');
+                        window.name = '';
+                     }
+                     window.location = redirect;
+                  },
+                  error: function(xhr, textStatus, errorThrown)
+                  {
+                     forge.log.error(cat, 'authentication failed');
+                  },
+                  xhr: forge.xhr.create
+               });
+            });
+            item.append(button);
+            item.append(' ' + key + '<br/>');
+            
+            // display certificate attributes
+            var attr;
+            for(var n = 0; n < cert.subject.attributes.length; ++n)
+            {
+               attr = cert.subject.attributes[n];
+               item.append(attr.name + ': ' + attr.value + '<br/>');
+            }
+            
+            list.append(item);
+         })(webids[key]);
+      }
+      if(list.html() === '<ul/>')
+      {
+         list.append('None');
+      }
+      
+      $('#webids').append(list);
+   }
+   catch(ex)
+   {
+      forge.log.error(cat, ex);
+   }
+};
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/nodejs-create-cert.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/nodejs-create-cert.js
new file mode 100644
index 0000000..d1666eb
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/nodejs-create-cert.js
@@ -0,0 +1,110 @@
+var forge = require('../js/forge');
+
+console.log('Generating 1024-bit key-pair...');
+var keys = forge.pki.rsa.generateKeyPair(1024);
+console.log('Key-pair created.');
+
+console.log('Creating self-signed certificate...');
+var cert = forge.pki.createCertificate();
+cert.publicKey = keys.publicKey;
+cert.serialNumber = '01';
+cert.validity.notBefore = new Date();
+cert.validity.notAfter = new Date();
+cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1);
+var attrs = [{
+  name: 'commonName',
+  value: 'example.org'
+}, {
+  name: 'countryName',
+  value: 'US'
+}, {
+  shortName: 'ST',
+  value: 'Virginia'
+}, {
+  name: 'localityName',
+  value: 'Blacksburg'
+}, {
+  name: 'organizationName',
+  value: 'Test'
+}, {
+  shortName: 'OU',
+  value: 'Test'
+}];
+cert.setSubject(attrs);
+cert.setIssuer(attrs);
+cert.setExtensions([{
+  name: 'basicConstraints',
+  cA: true/*,
+  pathLenConstraint: 4*/
+}, {
+  name: 'keyUsage',
+  keyCertSign: true,
+  digitalSignature: true,
+  nonRepudiation: true,
+  keyEncipherment: true,
+  dataEncipherment: true
+}, {
+  name: 'extKeyUsage',
+  serverAuth: true,
+  clientAuth: true,
+  codeSigning: true,
+  emailProtection: true,
+  timeStamping: true
+}, {
+  name: 'nsCertType',
+  client: true,
+  server: true,
+  email: true,
+  objsign: true,
+  sslCA: true,
+  emailCA: true,
+  objCA: true
+}, {
+  name: 'subjectAltName',
+  altNames: [{
+    type: 6, // URI
+    value: 'http://example.org/webid#me'
+  }, {
+    type: 7, // IP
+    ip: '127.0.0.1'
+  }]
+}, {
+  name: 'subjectKeyIdentifier'
+}]);
+// FIXME: add authorityKeyIdentifier extension
+
+// self-sign certificate
+cert.sign(keys.privateKey/*, forge.md.sha256.create()*/);
+console.log('Certificate created.');
+
+// PEM-format keys and cert
+var pem = {
+  privateKey: forge.pki.privateKeyToPem(keys.privateKey),
+  publicKey: forge.pki.publicKeyToPem(keys.publicKey),
+  certificate: forge.pki.certificateToPem(cert)
+};
+
+console.log('\nKey-Pair:');
+console.log(pem.privateKey);
+console.log(pem.publicKey);
+
+console.log('\nCertificate:');
+console.log(pem.certificate);
+
+// verify certificate
+var caStore = forge.pki.createCaStore();
+caStore.addCertificate(cert);
+try {
+  forge.pki.verifyCertificateChain(caStore, [cert],
+    function(vfd, depth, chain) {
+      if(vfd === true) {
+        console.log('SubjectKeyIdentifier verified: ' +
+          cert.verifySubjectKeyIdentifier());
+        console.log('Certificate verified.');
+      }
+      return true;
+  });
+} catch(ex) {
+  console.log('Certificate verification failure: ' +
+    JSON.stringify(ex, null, 2));
+}
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/nodejs-create-csr.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/nodejs-create-csr.js
new file mode 100644
index 0000000..1cb335f
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/nodejs-create-csr.js
@@ -0,0 +1,66 @@
+var forge = require('../js/forge');
+
+console.log('Generating 1024-bit key-pair...');
+var keys = forge.pki.rsa.generateKeyPair(1024);
+console.log('Key-pair created.');
+
+console.log('Creating certification request (CSR) ...');
+var csr = forge.pki.createCertificationRequest();
+csr.publicKey = keys.publicKey;
+csr.setSubject([{
+  name: 'commonName',
+  value: 'example.org'
+}, {
+  name: 'countryName',
+  value: 'US'
+}, {
+  shortName: 'ST',
+  value: 'Virginia'
+}, {
+  name: 'localityName',
+  value: 'Blacksburg'
+}, {
+  name: 'organizationName',
+  value: 'Test'
+}, {
+  shortName: 'OU',
+  value: 'Test'
+}]);
+// add optional attributes
+csr.setAttributes([{
+  name: 'challengePassword',
+  value: 'password'
+}, {
+  name: 'unstructuredName',
+  value: 'My company'
+}]);
+
+// sign certification request
+csr.sign(keys.privateKey/*, forge.md.sha256.create()*/);
+console.log('Certification request (CSR) created.');
+
+// PEM-format keys and csr
+var pem = {
+  privateKey: forge.pki.privateKeyToPem(keys.privateKey),
+  publicKey: forge.pki.publicKeyToPem(keys.publicKey),
+  csr: forge.pki.certificationRequestToPem(csr)
+};
+
+console.log('\nKey-Pair:');
+console.log(pem.privateKey);
+console.log(pem.publicKey);
+
+console.log('\nCertification Request (CSR):');
+console.log(pem.csr);
+
+// verify certification request
+try {
+  if(csr.verify()) {
+    console.log('Certification request (CSR) verified.');
+  } else {
+    throw new Error('Signature not verified.');
+  }
+} catch(err) {
+  console.log('Certification request (CSR) verification failure: ' +
+    JSON.stringify(err, null, 2));
+}
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/nodejs-create-pkcs12.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/nodejs-create-pkcs12.js
new file mode 100644
index 0000000..e52eefa
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/nodejs-create-pkcs12.js
@@ -0,0 +1,160 @@
+var forge = require('../js/forge');
+
+try {
+  // generate a keypair
+  console.log('Generating 1024-bit key-pair...');
+  var keys = forge.pki.rsa.generateKeyPair(1024);
+  console.log('Key-pair created.');
+
+  // create a certificate
+  console.log('Creating self-signed certificate...');
+  var cert = forge.pki.createCertificate();
+  cert.publicKey = keys.publicKey;
+  cert.serialNumber = '01';
+  cert.validity.notBefore = new Date();
+  cert.validity.notAfter = new Date();
+  cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1);
+  var attrs = [{
+    name: 'commonName',
+    value: 'example.org'
+  }, {
+    name: 'countryName',
+    value: 'US'
+  }, {
+    shortName: 'ST',
+    value: 'Virginia'
+  }, {
+    name: 'localityName',
+    value: 'Blacksburg'
+  }, {
+    name: 'organizationName',
+    value: 'Test'
+  }, {
+    shortName: 'OU',
+    value: 'Test'
+  }];
+  cert.setSubject(attrs);
+  cert.setIssuer(attrs);
+  cert.setExtensions([{
+    name: 'basicConstraints',
+    cA: true
+  }, {
+    name: 'keyUsage',
+    keyCertSign: true,
+    digitalSignature: true,
+    nonRepudiation: true,
+    keyEncipherment: true,
+    dataEncipherment: true
+  }, {
+    name: 'subjectAltName',
+    altNames: [{
+      type: 6, // URI
+      value: 'http://example.org/webid#me'
+    }]
+  }]);
+
+  // self-sign certificate
+  cert.sign(keys.privateKey);
+  console.log('Certificate created.');
+
+  // create PKCS12
+  console.log('\nCreating PKCS#12...');
+  var password = 'password';
+  var newPkcs12Asn1 = forge.pkcs12.toPkcs12Asn1(
+    keys.privateKey, [cert], password,
+    {generateLocalKeyId: true, friendlyName: 'test'});
+  var newPkcs12Der = forge.asn1.toDer(newPkcs12Asn1).getBytes();
+
+  console.log('\nBase64-encoded new PKCS#12:');
+  console.log(forge.util.encode64(newPkcs12Der));
+
+  // create CA store (w/own certificate in this example)
+  var caStore = forge.pki.createCaStore([cert]);
+
+  console.log('\nLoading new PKCS#12 to confirm...');
+  loadPkcs12(newPkcs12Der, password, caStore);
+} catch(ex) {
+  if(ex.stack) {
+    console.log(ex.stack);
+  } else {
+    console.log('Error', ex);
+  }
+}
+
+function loadPkcs12(pkcs12Der, password, caStore) {
+  var pkcs12Asn1 = forge.asn1.fromDer(pkcs12Der);
+  var pkcs12 = forge.pkcs12.pkcs12FromAsn1(pkcs12Asn1, false, password);
+
+  // load keypair and cert chain from safe content(s) and map to key ID
+  var map = {};
+  for(var sci = 0; sci < pkcs12.safeContents.length; ++sci) {
+    var safeContents = pkcs12.safeContents[sci];
+    console.log('safeContents ' + (sci + 1));
+
+    for(var sbi = 0; sbi < safeContents.safeBags.length; ++sbi) {
+      var safeBag = safeContents.safeBags[sbi];
+      console.log('safeBag.type: ' + safeBag.type);
+
+      var localKeyId = null;
+      if(safeBag.attributes.localKeyId) {
+        localKeyId = forge.util.bytesToHex(
+          safeBag.attributes.localKeyId[0]);
+        console.log('localKeyId: ' + localKeyId);
+        if(!(localKeyId in map)) {
+          map[localKeyId] = {
+            privateKey: null,
+            certChain: []
+          };
+        }
+      } else {
+        // no local key ID, skip bag
+        continue;
+      }
+
+      // this bag has a private key
+      if(safeBag.type === forge.pki.oids.pkcs8ShroudedKeyBag) {
+        console.log('found private key');
+        map[localKeyId].privateKey = safeBag.key;
+      } else if(safeBag.type === forge.pki.oids.certBag) {
+        // this bag has a certificate
+        console.log('found certificate');
+        map[localKeyId].certChain.push(safeBag.cert);
+      }
+    }
+  }
+
+  console.log('\nPKCS#12 Info:');
+
+  for(var localKeyId in map) {
+    var entry = map[localKeyId];
+    console.log('\nLocal Key ID: ' + localKeyId);
+    if(entry.privateKey) {
+      var privateKeyP12Pem = forge.pki.privateKeyToPem(entry.privateKey);
+      var encryptedPrivateKeyP12Pem = forge.pki.encryptRsaPrivateKey(
+        entry.privateKey, password);
+
+      console.log('\nPrivate Key:');
+      console.log(privateKeyP12Pem);
+      console.log('Encrypted Private Key (password: "' + password + '"):');
+      console.log(encryptedPrivateKeyP12Pem);
+    } else {
+      console.log('');
+    }
+    if(entry.certChain.length > 0) {
+      console.log('Certificate chain:');
+      var certChain = entry.certChain;
+      for(var i = 0; i < certChain.length; ++i) {
+        var certP12Pem = forge.pki.certificateToPem(certChain[i]);
+        console.log(certP12Pem);
+      }
+
+      var chainVerified = false;
+      try {
+        chainVerified = forge.pki.verifyCertificateChain(caStore, certChain);
+      } catch(ex) {
+        chainVerified = ex;
+      }
+      console.log('Certificate chain verified: ', chainVerified);
+    }
+  }
+}
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/nodejs-imap.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/nodejs-imap.js
new file mode 100644
index 0000000..ba024ef
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/nodejs-imap.js
@@ -0,0 +1,46 @@
+var forge = require('../js/forge');
+var net = require('net');
+
+var socket = new net.Socket();
+
+var client = forge.tls.createConnection({
+  server: false,
+  verify: function(connection, verified, depth, certs) {
+    // skip verification for testing
+    return true;
+  },
+  connected: function(connection) {
+    console.log('[tls] connected');
+  },
+  tlsDataReady: function(connection) {
+    // encrypted data is ready to be sent to the server
+    var data = connection.tlsData.getBytes();
+    socket.write(data, 'binary');
+  },
+  dataReady: function(connection) {
+    // clear data from the server is ready
+    var data = connection.data.getBytes();
+    console.log('[tls] received from the server: ' + data);
+    client.close();
+  },
+  closed: function() {
+    console.log('[tls] disconnected');
+  },
+  error: function(connection, error) {
+    console.log('[tls] error', error);
+  }
+});
+
+socket.on('connect', function() {
+  console.log('[socket] connected');
+  client.handshake();
+});
+socket.on('data', function(data) {
+  client.process(data.toString('binary'));
+});
+socket.on('end', function() {
+  console.log('[socket] disconnected');
+});
+
+// connect to gmail's imap server
+socket.connect(993, 'imap.gmail.com');
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/nodejs-tls.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/nodejs-tls.js
new file mode 100644
index 0000000..5be6acd
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/nodejs-tls.js
@@ -0,0 +1,189 @@
+var forge = require('../js/forge');
+
+// function to create certificate
+var createCert = function(cn, data) {
+  console.log(
+    'Generating 512-bit key-pair and certificate for \"' + cn + '\".');
+  var keys = forge.pki.rsa.generateKeyPair(512);
+  console.log('key-pair created.');
+
+  var cert = forge.pki.createCertificate();
+  cert.serialNumber = '01';
+  cert.validity.notBefore = new Date();
+  cert.validity.notAfter = new Date();
+  cert.validity.notAfter.setFullYear(
+    cert.validity.notBefore.getFullYear() + 1);
+  var attrs = [{
+    name: 'commonName',
+    value: cn
+  }, {
+    name: 'countryName',
+    value: 'US'
+  }, {
+    shortName: 'ST',
+    value: 'Virginia'
+  }, {
+    name: 'localityName',
+    value: 'Blacksburg'
+  }, {
+    name: 'organizationName',
+    value: 'Test'
+  }, {
+    shortName: 'OU',
+    value: 'Test'
+  }];
+  cert.setSubject(attrs);
+  cert.setIssuer(attrs);
+  cert.setExtensions([{
+    name: 'basicConstraints',
+    cA: true
+  }, {
+    name: 'keyUsage',
+    keyCertSign: true,
+    digitalSignature: true,
+    nonRepudiation: true,
+    keyEncipherment: true,
+    dataEncipherment: true
+  }, {
+    name: 'subjectAltName',
+    altNames: [{
+      type: 6, // URI
+      value: 'http://myuri.com/webid#me'
+    }]
+  }]);
+  // FIXME: add subjectKeyIdentifier extension
+  // FIXME: add authorityKeyIdentifier extension
+  cert.publicKey = keys.publicKey;
+
+  // self-sign certificate
+  cert.sign(keys.privateKey);
+
+  // save data
+  data[cn] = {
+    cert: forge.pki.certificateToPem(cert),
+    privateKey: forge.pki.privateKeyToPem(keys.privateKey)
+  };
+
+  console.log('certificate created for \"' + cn + '\": \n' + data[cn].cert);
+};
+
+var end = {};
+var data = {};
+
+// create certificate for server and client
+createCert('server', data);
+createCert('client', data);
+
+var success = false;
+
+// create TLS client
+end.client = forge.tls.createConnection({
+  server: false,
+  caStore: [data.server.cert],
+  sessionCache: {},
+  // supported cipher suites in order of preference
+  cipherSuites: [
+    forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+    forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+  virtualHost: 'server',
+  verify: function(c, verified, depth, certs) {
+    console.log(
+      'TLS Client verifying certificate w/CN: \"' +
+      certs[0].subject.getField('CN').value +
+      '\", verified: ' + verified + '...');
+    return verified;
+  },
+  connected: function(c) {
+    console.log('Client connected...');
+
+    // send message to server
+    setTimeout(function() {
+      c.prepareHeartbeatRequest('heartbeat');
+      c.prepare('Hello Server');
+    }, 1);
+  },
+  getCertificate: function(c, hint) {
+    console.log('Client getting certificate ...');
+    return data.client.cert;
+  },
+  getPrivateKey: function(c, cert) {
+    return data.client.privateKey;
+  },
+  tlsDataReady: function(c) {
+    // send TLS data to server
+    end.server.process(c.tlsData.getBytes());
+  },
+  dataReady: function(c) {
+    var response = c.data.getBytes();
+    console.log('Client received \"' + response + '\"');
+    success = (response === 'Hello Client');
+    c.close();
+  },
+  heartbeatReceived: function(c, payload) {
+    console.log('Client received heartbeat: ' + payload.getBytes());
+  },
+  closed: function(c) {
+    console.log('Client disconnected.');
+    if(success) {
+      console.log('PASS');
+    } else {
+      console.log('FAIL');
+    }
+  },
+  error: function(c, error) {
+    console.log('Client error: ' + error.message);
+  }
+});
+
+// create TLS server
+end.server = forge.tls.createConnection({
+  server: true,
+  caStore: [data.client.cert],
+  sessionCache: {},
+  // supported cipher suites in order of preference
+  cipherSuites: [
+    forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+    forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+  connected: function(c) {
+    console.log('Server connected');
+    c.prepareHeartbeatRequest('heartbeat');
+  },
+  verifyClient: true,
+  verify: function(c, verified, depth, certs) {
+    console.log(
+      'Server verifying certificate w/CN: \"' +
+      certs[0].subject.getField('CN').value +
+      '\", verified: ' + verified + '...');
+    return verified;
+  },
+  getCertificate: function(c, hint) {
+    console.log('Server getting certificate for \"' + hint[0] + '\"...');
+    return data.server.cert;
+  },
+  getPrivateKey: function(c, cert) {
+    return data.server.privateKey;
+  },
+  tlsDataReady: function(c) {
+    // send TLS data to client
+    end.client.process(c.tlsData.getBytes());
+  },
+  dataReady: function(c) {
+    console.log('Server received \"' + c.data.getBytes() + '\"');
+
+    // send response
+    c.prepare('Hello Client');
+    c.close();
+  },
+  heartbeatReceived: function(c, payload) {
+    console.log('Server received heartbeat: ' + payload.getBytes());
+  },
+  closed: function(c) {
+    console.log('Server disconnected.');
+  },
+  error: function(c, error) {
+    console.log('Server error: ' + error.message);
+  }
+});
+
+console.log('created TLS client and server, doing handshake...');
+end.client.handshake();
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/nodejs-ws-webid.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/nodejs-ws-webid.js
new file mode 100644
index 0000000..fae0b82
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/nodejs-ws-webid.js
@@ -0,0 +1,491 @@
+var forge = require('../js/forge');
+var fs = require('fs');
+var http = require('http');
+//var rdf = require('./rdflib');
+var sys = require('sys');
+var urllib = require('url');
+var ws = require('./ws');
+
+// remove xmlns from input
+var normalizeNs = function(input, ns) {
+  var rval = null;
+
+  // primitive
+  if(typeof input === 'string' ||
+    typeof input === 'number' ||
+    typeof input === 'boolean') {
+    rval = input;
+  }
+  // array
+  else if(forge.util.isArray(input)) {
+    rval = [];
+    for(var i = 0; i < input.length; ++i) {
+      rval.push(normalizeNs(input[i], ns));
+    }
+  }
+  // object
+  else {
+    if('@' in input) {
+      // copy namespace map
+      var newNs = {};
+      for(var key in ns) {
+        newNs[key] = ns[key];
+      }
+      ns = newNs;
+
+      // update namespace map
+      for(var key in input['@']) {
+        if(key.indexOf('xmlns:') === 0) {
+          ns[key.substr(6)] = input['@'][key];
+        }
+      }
+    }
+
+    rval = {};
+    for(var key in input) {
+      if(key.indexOf('xmlns:') !== 0) {
+        var value = input[key];
+        var colon = key.indexOf(':');
+        if(colon !== -1) {
+          var prefix = key.substr(0, colon);
+          if(prefix in ns) {
+            key = ns[prefix] + key.substr(colon + 1);
+          }
+        }
+        rval[key] = normalizeNs(value, ns);
+      }
+    }
+  }
+
+  return rval;
+};
+
+// gets public key from WebID rdf
+var getPublicKey = function(data, uri, callback) {
+  // FIXME: use RDF library to simplify code below
+  //var kb = new rdf.RDFParser(rdf.IndexedFormula(), uri).loadBuf(data);
+  //var CERT = rdf.Namespace('http://www.w3.org/ns/auth/cert#');
+  //var RSA  = rdf.Namespace('http://www.w3.org/ns/auth/rsa#');
+  var RDF = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
+  var CERT = 'http://www.w3.org/ns/auth/cert#';
+  var RSA  = 'http://www.w3.org/ns/auth/rsa#';
+  var desc = RDF + 'Description';
+  var about = RDF + 'about';
+  var type = RDF + 'type';
+  var resource = RDF + 'resource';
+  var publicKey = RSA + 'RSAPublicKey';
+  var modulus = RSA + 'modulus';
+  var exponent = RSA + 'public_exponent';
+  var identity = CERT + 'identity';
+  var hex = CERT + 'hex';
+  var decimal = CERT + 'decimal';
+
+  // gets a resource identifer from a node
+  var getResource = function(node, key) {
+    var rval = null;
+
+    // special case 'about'
+    if(key === about) {
+      if('@' in node && about in node['@']) {
+        rval = node['@'][about];
+      }
+    }
+    // any other resource
+    else if(
+       key in node &&
+       typeof node[key] === 'object' && !forge.util.isArray(node[key]) &&
+       '@' in node[key] && resource in node[key]['@']) {
+      rval = node[key]['@'][resource];
+    }
+
+    return rval;
+  };
+
+  // parse XML
+  uri = urllib.parse(uri);
+  var xml2js = require('./xml2js');
+  var parser = new xml2js.Parser();
+  parser.addListener('end', function(result) {
+    // normalize namespaces
+    result = normalizeNs(result, {});
+
+    // find grab all public keys whose identity matches hash from uri
+    var keys = [];
+    if(desc in result) {
+      // normalize RDF descriptions to array
+      if(!forge.util.isArray(result[desc])) {
+        desc = [result[desc]];
+      }
+      else {
+        desc = result[desc];
+      }
+
+      // collect properties for all resources
+      var graph = {};
+      for(var i = 0; i < desc.length; ++i) {
+        var node = desc[i];
+        var res = {};
+        for(var key in node) {
+          var obj = getResource(node, key);
+          res[key] = (obj === null) ? node[key] : obj;
+        }
+        graph[getResource(node, about) || ''] = res;
+      }
+
+      // for every public key w/identity that matches the uri hash
+      // save the public key modulus and exponent
+      for(var r in graph) {
+        var props = graph[r];
+        if(identity in props &&
+          type in props &&
+          props[type] === publicKey &&
+          props[identity] === uri.hash &&
+          modulus in props &&
+          exponent in props &&
+          props[modulus] in graph &&
+          props[exponent] in graph &&
+          hex in graph[props[modulus]] &&
+          decimal in graph[props[exponent]]) {
+          keys.push({
+            modulus: graph[props[modulus]][hex],
+            exponent: graph[props[exponent]][decimal]
+          });
+        }
+      }
+    }
+
+    sys.log('Public keys from RDF: ' + JSON.stringify(keys));
+    callback(keys);
+  });
+  parser.parseString(data);
+};
+
+// compares two public keys for equality
+var comparePublicKeys = function(key1, key2) {
+  return key1.modulus === key2.modulus && key1.exponent === key2.exponent;
+};
+
+// gets the RDF data from a URL
+var fetchUrl = function(url, callback, redirects) {
+  // allow 3 redirects by default
+  if(typeof(redirects) === 'undefined') {
+    redirects = 3;
+  }
+
+  sys.log('Fetching URL: \"' + url + '\"');
+
+  // parse URL
+  url = forge.util.parseUrl(url);
+  var client = http.createClient(
+    url.port, url.fullHost, url.scheme === 'https');
+  var request = client.request('GET', url.path, {
+    'Host': url.host,
+    'Accept': 'application/rdf+xml'
+  });
+  request.addListener('response', function(response) {
+    var body = '';
+
+    // error, return empty body
+    if(response.statusCode >= 400) {
+       callback(body);
+    }
+    // follow redirect
+    else if(response.statusCode === 302) {
+      if(redirects > 0) {
+        // follow redirect
+        fetchUrl(response.headers.location, callback, --redirects);
+      }
+      else {
+        // return empty body
+        callback(body);
+      }
+    }
+    // handle data
+    else {
+      response.setEncoding('utf8');
+      response.addListener('data', function(chunk) {
+        body += chunk;
+      });
+      response.addListener('end', function() {
+        callback(body);
+      });
+    }
+  });
+  request.end();
+};
+
+// does WebID authentication
+var authenticateWebId = function(c, state) {
+  var auth = false;
+
+  // get client-certificate
+  var cert = c.peerCertificate;
+
+  // get public key from certificate
+  var publicKey = {
+    modulus: cert.publicKey.n.toString(16).toLowerCase(),
+    exponent: cert.publicKey.e.toString(10)
+  };
+
+  sys.log(
+    'Server verifying certificate w/CN: \"' +
+    cert.subject.getField('CN').value + '\"\n' +
+    'Public Key: ' + JSON.stringify(publicKey));
+
+  // build queue of subject alternative names to authenticate with
+  var altNames = [];
+  var ext = cert.getExtension({name: 'subjectAltName'});
+  if(ext !== null && ext.altNames) {
+    for(var i = 0; i < ext.altNames.length; ++i) {
+      var altName = ext.altNames[i];
+      if(altName.type === 6) {
+        altNames.push(altName.value);
+      }
+    }
+  }
+
+  // create authentication processor
+  var authNext = function() {
+    if(!auth) {
+      // no more alt names, auth failed
+      if(altNames.length === 0) {
+        sys.log('WebID authentication FAILED.');
+        c.prepare(JSON.stringify({
+          success: false,
+          error: 'Not Authenticated'
+        }));
+        c.close();
+      }
+      // try next alt name
+      else {
+        // fetch URL
+        var url = altNames.shift();
+        fetchUrl(url, function(body) {
+          // get public key
+          getPublicKey(body, url, function(keys) {
+            // compare public keys from RDF until one matches
+            for(var i = 0; !auth && i < keys.length; ++i) {
+              auth = comparePublicKeys(keys[i], publicKey);
+            }
+            if(auth) {
+              // send authenticated notice to client
+              sys.log('WebID authentication PASSED.');
+              state.authenticated = true;
+              c.prepare(JSON.stringify({
+                success: true,
+                cert: forge.pki.certificateToPem(cert),
+                webID: url,
+                rdf: forge.util.encode64(body)
+              }));
+            }
+            else {
+              // try next alt name
+              authNext();
+            }
+          });
+        });
+      }
+    }
+  };
+
+  // do auth
+  authNext();
+};
+
+// creates credentials (private key + certificate)
+var createCredentials = function(cn, credentials) {
+  sys.log('Generating 512-bit key-pair and certificate for \"' + cn + '\".');
+  var keys = forge.pki.rsa.generateKeyPair(512);
+  sys.log('key-pair created.');
+
+  var cert = forge.pki.createCertificate();
+  cert.serialNumber = '01';
+  cert.validity.notBefore = new Date();
+  cert.validity.notAfter = new Date();
+  cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1);
+  var attrs = [{
+    name: 'commonName',
+    value: cn
+  }, {
+    name: 'countryName',
+    value: 'US'
+  }, {
+    shortName: 'ST',
+    value: 'Virginia'
+  }, {
+    name: 'localityName',
+    value: 'Blacksburg'
+  }, {
+    name: 'organizationName',
+    value: 'Test'
+  }, {
+    shortName: 'OU',
+    value: 'Test'
+  }];
+  cert.setSubject(attrs);
+  cert.setIssuer(attrs);
+  cert.setExtensions([{
+    name: 'basicConstraints',
+    cA: true
+  }, {
+    name: 'keyUsage',
+    keyCertSign: true,
+    digitalSignature: true,
+    nonRepudiation: true,
+    keyEncipherment: true,
+    dataEncipherment: true
+  }, {
+    name: 'subjectAltName',
+    altNames: [{
+      type: 6, // URI
+      value: 'http://myuri.com/webid#me'
+    }]
+  }]);
+  // FIXME: add subjectKeyIdentifier extension
+  // FIXME: add authorityKeyIdentifier extension
+  cert.publicKey = keys.publicKey;
+
+  // self-sign certificate
+  cert.sign(keys.privateKey);
+
+  // save credentials
+  credentials.key = forge.pki.privateKeyToPem(keys.privateKey);
+  credentials.cert = forge.pki.certificateToPem(cert);
+
+  sys.log('Certificate created for \"' + cn + '\": \n' + credentials.cert);
+};
+
+// initialize credentials
+var credentials = {
+  key: null,
+  cert: null
+};
+
+// read private key from file
+var readPrivateKey = function(filename) {
+  credentials.key = fs.readFileSync(filename);
+  // try to parse from PEM as test
+  forge.pki.privateKeyFromPem(credentials.key);
+};
+
+// read certificate from file
+var readCertificate = function(filename) {
+  credentials.cert = fs.readFileSync(filename);
+  // try to parse from PEM as test
+  forge.pki.certificateFromPem(credentials.cert);
+};
+
+// parse command line options
+var opts = require('opts');
+var options = [
+{ short       : 'v'
+, long        : 'version'
+, description : 'Show version and exit'
+, callback    : function() { console.log('v1.0'); process.exit(1); }
+},
+{ short       : 'p'
+, long        : 'port'
+, description : 'The port to listen for WebSocket connections on'
+, value       : true
+},
+{ long        : 'key'
+, description : 'The server private key file to use in PEM format'
+, value       : true
+, callback    : readPrivateKey
+},
+{ long        : 'cert'
+, description : 'The server certificate file to use in PEM format'
+, value       : true
+, callback    : readCertificate
+}
+];
+opts.parse(options, true);
+
+// create credentials for server
+if(credentials.key === null || credentials.cert === null) {
+  createCredentials('server', credentials);
+}
+
+// function to create TLS server connection
+var createTls = function(websocket) {
+  var state = {
+    authenticated: false
+  };
+  return forge.tls.createConnection({
+    server: true,
+    caStore: [],
+    sessionCache: {},
+    // supported cipher suites in order of preference
+    cipherSuites: [
+       forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+       forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+    connected: function(c) {
+      sys.log('Server connected');
+
+      // do WebID authentication
+      try {
+        authenticateWebId(c, state);
+      }
+      catch(ex) {
+        c.close();
+      }
+    },
+    verifyClient: true,
+    verify: function(c, verified, depth, certs) {
+      // accept certs w/unknown-CA (48)
+      if(verified === 48) {
+        verified = true;
+      }
+      return verified;
+    },
+    getCertificate: function(c, hint) {
+       sys.log('Server using certificate for \"' + hint[0] + '\"');
+       return credentials.cert;
+    },
+    getPrivateKey: function(c, cert) {
+       return credentials.key;
+    },
+    tlsDataReady: function(c) {
+       // send base64-encoded TLS data over websocket
+       websocket.write(forge.util.encode64(c.tlsData.getBytes()));
+    },
+    dataReady: function(c) {
+      // ignore any data until connection is authenticated
+      if(state.authenticated) {
+        sys.log('Server received \"' + c.data.getBytes() + '\"');
+      }
+    },
+    closed: function(c) {
+      sys.log('Server disconnected');
+      websocket.end();
+    },
+    error: function(c, error) {
+      sys.log('Server error: ' + error.message);
+    }
+  });
+};
+
+// create websocket server
+var port = opts.get('port') || 8080;
+ws.createServer(function(websocket) {
+  // create TLS server connection
+  var tls = createTls(websocket);
+
+  websocket.addListener('connect', function(resource) {
+    sys.log('WebSocket connected: ' + resource);
+
+    // close connection after 30 second timeout
+    setTimeout(websocket.end, 30 * 1000);
+  });
+
+  websocket.addListener('data', function(data) {
+    // base64-decode data and process it
+    tls.process(forge.util.decode64(data));
+  });
+
+  websocket.addListener('close', function() {
+    sys.log('WebSocket closed');
+  });
+}).listen(port);
+
+sys.log('WebSocket WebID server running on port ' + port);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/nodejs-ws.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/nodejs-ws.js
new file mode 100644
index 0000000..164962d
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/nodejs-ws.js
@@ -0,0 +1,166 @@
+var sys = require('sys');
+var ws = require('./ws');
+var forge = require('../js/forge');
+
+// function to create certificate
+var createCert = function(cn, data)
+{
+   sys.puts(
+      'Generating 512-bit key-pair and certificate for \"' + cn + '\".');
+   var keys = forge.pki.rsa.generateKeyPair(512);
+   sys.puts('key-pair created.');
+
+   var cert = forge.pki.createCertificate();
+   cert.serialNumber = '01';
+   cert.validity.notBefore = new Date();
+   cert.validity.notAfter = new Date();
+   cert.validity.notAfter.setFullYear(
+      cert.validity.notBefore.getFullYear() + 1);
+   var attrs = [{
+      name: 'commonName',
+      value: cn
+   }, {
+      name: 'countryName',
+      value: 'US'
+   }, {
+      shortName: 'ST',
+      value: 'Virginia'
+   }, {
+      name: 'localityName',
+      value: 'Blacksburg'
+   }, {
+      name: 'organizationName',
+      value: 'Test'
+   }, {
+      shortName: 'OU',
+      value: 'Test'
+   }];
+   cert.setSubject(attrs);
+   cert.setIssuer(attrs);
+   cert.setExtensions([{
+      name: 'basicConstraints',
+      cA: true
+   }, {
+      name: 'keyUsage',
+      keyCertSign: true,
+      digitalSignature: true,
+      nonRepudiation: true,
+      keyEncipherment: true,
+      dataEncipherment: true
+   }, {
+      name: 'subjectAltName',
+      altNames: [{
+         type: 6, // URI
+         value: 'http://myuri.com/webid#me'
+      }]
+   }]);
+   // FIXME: add subjectKeyIdentifier extension
+   // FIXME: add authorityKeyIdentifier extension
+   cert.publicKey = keys.publicKey;
+   
+   // self-sign certificate
+   cert.sign(keys.privateKey);
+   
+   // save data
+   data[cn] = {
+      cert: forge.pki.certificateToPem(cert),
+      privateKey: forge.pki.privateKeyToPem(keys.privateKey)
+   };
+   
+   sys.puts('certificate created for \"' + cn + '\": \n' + data[cn].cert);
+};
+
+var data = {};
+
+// create certificate for server
+createCert('server', data);
+
+// function to create TLS server connection
+var createTls = function(websocket)
+{
+   return forge.tls.createConnection(
+   {
+      server: true,
+      caStore: [],
+      sessionCache: {},
+      // supported cipher suites in order of preference
+      cipherSuites: [
+         forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+         forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+      connected: function(c)
+      {
+         sys.puts('Server connected');
+      },
+      verifyClient: true,
+      verify: function(c, verified, depth, certs)
+      {
+         sys.puts(
+            'Server verifying certificate w/CN: \"' +
+            certs[0].subject.getField('CN').value +
+            '\", verified: ' + verified + '...');
+         
+         // accept any certificate (could actually do WebID authorization from
+         // here within the protocol)
+         return true;
+      },
+      getCertificate: function(c, hint)
+      {
+         sys.puts('Server getting certificate for \"' + hint[0] + '\"...');
+         return data.server.cert;
+      },
+      getPrivateKey: function(c, cert)
+      {
+         return data.server.privateKey;
+      },
+      tlsDataReady: function(c)
+      {
+         // send base64-encoded TLS data over websocket
+         websocket.write(forge.util.encode64(c.tlsData.getBytes()));
+      },
+      dataReady: function(c)
+      {
+         sys.puts('Server received \"' + c.data.getBytes() + '\"');
+         
+         // send response
+         c.prepare('Hello Client');
+      },
+      closed: function(c)
+      {
+         sys.puts('Server disconnected.');
+         websocket.end();
+      },
+      error: function(c, error)
+      {
+         sys.puts('Server error: ' + error.message);
+      }
+   });
+};
+
+// create websocket server
+var port = 8080;
+ws.createServer(function(websocket)
+{
+   // create TLS server connection
+   var tls = createTls(websocket);
+   
+   websocket.addListener('connect', function(resource)
+   {
+      sys.puts('connected: ' + resource);
+      
+      // close connection after 10 seconds
+      setTimeout(websocket.end, 10 * 1000);
+   });
+   
+   websocket.addListener('data', function(data)
+   {
+      // base64-decode data and process it
+      tls.process(forge.util.decode64(data));
+   });
+   
+   websocket.addListener('close', function()
+   {
+      sys.puts('closed');
+   });
+}).listen(port);
+
+sys.puts('server running on port ' + port);
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/performance.html b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/performance.html
new file mode 100644
index 0000000..9acbcc5
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/performance.html
@@ -0,0 +1,550 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+   <head>
+      <title>Forge Performance Tests</title>
+      <link type="text/css" rel="stylesheet" media="all" href="screen.css" />
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
+      <script type="text/javascript" src="forge/debug.js"></script>
+      <script type="text/javascript" src="forge/util.js"></script>
+      <script type="text/javascript" src="forge/log.js"></script>
+      <script type="text/javascript" src="forge/task.js"></script>
+      <script type="text/javascript" src="forge/md5.js"></script>
+      <script type="text/javascript" src="forge/sha1.js"></script>
+      <script type="text/javascript" src="forge/sha256.js"></script>
+      <script type="text/javascript" src="forge/hmac.js"></script>
+      <script type="text/javascript" src="forge/aes.js"></script>
+      <script type="text/javascript" src="forge/pem.js"></script>
+      <script type="text/javascript" src="forge/asn1.js"></script>
+      <script type="text/javascript" src="forge/jsbn.js"></script>
+      <script type="text/javascript" src="forge/prng.js"></script>
+      <script type="text/javascript" src="forge/random.js"></script>
+      <script type="text/javascript" src="forge/oids.js"></script>
+      <script type="text/javascript" src="forge/rsa.js"></script>
+      <script type="text/javascript" src="forge/pbe.js"></script>
+      <script type="text/javascript" src="forge/x509.js"></script>
+      <script type="text/javascript" src="forge/pki.js"></script>
+      <script type="text/javascript" src="forge/tls.js"></script>
+      <script type="text/javascript" src="forge/aesCipherSuites.js"></script>
+
+      <script type="text/javascript">
+      //<![CDATA[
+      // logging category
+      var cat = 'forge.tests.performance';
+
+      var test_random = function()
+      {
+         forge.log.debug(cat, 'painting canvas...');
+         setTimeout(function()
+         {
+	         var canvas = document.getElementById("canvas");
+	         var ctx = canvas.getContext("2d");
+	         var imgData = ctx.createImageData(canvas.width, canvas.height);
+
+	         // generate random bytes
+	         var bytes = forge.random.getBytes(canvas.width * canvas.height * 3);
+	         var n = 0;
+	         for(var x = 0; x < imgData.width; x++)
+	         {
+	            for(var y = 0; y < imgData.height; y++)
+	            {
+	               // index of the pixel in the array
+	               var idx = (x + y * imgData.width) * 4;
+
+	               // set values
+	               imgData.data[idx + 0] = bytes.charCodeAt(n++); // Red
+	               imgData.data[idx + 1] = bytes.charCodeAt(n++); // Green
+	               imgData.data[idx + 2] = bytes.charCodeAt(n++); // Blue
+	               imgData.data[idx + 3] = 255;                   // Alpha
+	            }
+	         }
+
+	         ctx.putImageData(imgData, 0, 0);
+	         forge.log.debug(cat, 'done');
+         }, 0);
+      };
+
+      var canvas_clear = function()
+      {
+         var canvas = document.getElementById("canvas");
+         var ctx = canvas.getContext("2d");
+         ctx.clearRect(0, 0, canvas.width, canvas.height);
+      };
+
+      var test_buffer_fill = function()
+      {
+         forge.log.debug(cat,
+            'buffer fill optimized vs. slow running for about 5 seconds...');
+
+         setTimeout(function()
+         {
+	         // run slow fill for 2.5 seconds
+	         var st, et;
+	         var b = '';
+	         var passed = 0;
+	         while(passed < 2500)
+	         {
+	            st = +new Date();
+	            b += 'b';
+	            et = +new Date();
+	            passed += (et - st);
+	         }
+
+	         // do optimized fill
+	         var buf = forge.util.createBuffer();
+	         st = +new Date();
+	         buf.fillWithByte('b'.charCodeAt(0), b.length);
+	         et = +new Date();
+
+	         forge.log.debug(cat, 'fill times', (et - st) + ' < ' + passed);
+         });
+      };
+
+      var test_buffer_xor = function()
+      {
+         forge.log.debug(cat,
+            'buffer xor optimized vs. slow running for about 5 seconds...');
+
+         setTimeout(function()
+         {
+	         // run slow xor for 2.5 seconds
+	         var st, et;
+	         var output = forge.util.createBuffer();
+	         var passed = 0;
+	         while(passed < 2500)
+	         {
+	            st = +new Date();
+	            output.putByte(0x01 ^ 0x02);
+	            et = +new Date();
+	            passed += (et - st);
+	         }
+
+	         // do optimized xor
+	         var count = output.length();
+	         var b1 = forge.util.createBuffer();
+	         b1.fillWithByte(0x01, count);
+	         var b2 = forge.util.createBuffer();
+	         b2.fillWithByte(0x02, count);
+
+	         st = +new Date();
+	         output = forge.util.xorBytes(b1.getBytes(), b2.getBytes(), count);
+	         et = +new Date();
+
+	         forge.log.debug(cat, 'xor times', (et - st) + ' < ' + passed);
+         });
+      };
+
+      // TODO: follow the same format as the hash tests
+      var test_base64_encode = function()
+      {
+         forge.log.debug(cat, 'base64 encode running for about 5 seconds...');
+
+         setTimeout(function()
+         {
+	         // get starting time to make test run for only 5 seconds
+	         var start = +new Date();
+
+	         // build data to encode
+	         var str = '';
+	         for(var i = 0; i < 100; i++)
+	         {
+	            str += '00010203050607080A0B0C0D0F1011121415161719';
+	         }
+
+	         // keep encoding for 5 seconds, keep track of total and count
+	         var total = 0, count = 0, st, et;
+	         var passed = 0;
+	         while(passed < 5000)
+	         {
+	            st = +new Date();
+	            forge.util.encode64(str);
+	            et = +new Date();
+	            ++count;
+	            total += (et - st);
+	            passed = +new Date() - start;
+	         }
+
+	         total /= 1000;
+	         var kb = 4200/1024;
+	         forge.log.debug(cat,
+	            'encode:', (count*kb/total) + ' KiB/s');
+         });
+      };
+
+      // TODO: follow the same format as the hash tests
+      var test_base64_decode = function()
+      {
+         forge.log.debug(cat, 'base64 decode running for about 5 seconds...');
+
+         setTimeout(function()
+         {
+	         // get starting time to make test run for only 5 seconds
+	         var start = +new Date();
+
+	         // build data to decode
+	         var str = '';
+	         for(var i = 0; i < 100; i++)
+	         {
+	            str +=
+		            'MDAwMTAyMDMwNTA2MDcwODBBMEIwQzBEMEYxMDExMTIxNDE1MTYxNzE5';
+	         }
+
+	         // keep encoding for 5 seconds, keep track of total and count
+	         var total = 0, count = 0, st, et;
+	         var passed = 0;
+	         while(passed < 5000)
+	         {
+	            st = +new Date();
+	            forge.util.decode64(str);
+	            et = +new Date();
+	            ++count;
+	            total += (et - st);
+	            passed = +new Date() - start;
+	         }
+
+	         total /= 1000;
+	         var kb = 5600/1024;
+	         forge.log.debug(cat,
+	            'decode:', (count*kb/total) + ' KiB/s');
+         });
+      };
+
+      var test_md5 = function()
+      {
+         // create input data
+         var input = ['0123456789abcdef', '', '', '', ''];
+         for(var i = 0; i < 4; ++i)
+         {
+            input[1] += input[0];
+         }
+         for(var i = 0; i < 4; ++i)
+         {
+            input[2] += input[1];
+         }
+         for(var i = 0; i < 4; ++i)
+         {
+            input[3] += input[2];
+         }
+         for(var i = 0; i < 8; ++i)
+         {
+            input[4] += input[3];
+         }
+
+         var md = forge.md.md5.create();
+
+         forge.log.debug(cat, 'md5 times in 1000s of bytes/sec processed:');
+
+         var st, et;
+         var output =
+            ['  16 bytes: ',
+             '  64 bytes: ',
+             ' 256 bytes: ',
+             '1024 bytes: ',
+             '8192 bytes: '];
+         var s = [16, 64, 256, 1024, 8192];
+         var t = [0, 0, 0, 0, 0];
+         for(var n = 0; n < 5; ++n)
+         {
+            var f = function(n)
+            {
+               setTimeout(function()
+               {
+                  // run for 2 seconds each
+                  var count = 0;
+                  while(t[n] < 2000)
+                  {
+                     md.start();
+                     st = +new Date();
+                     md.update(input[n]);
+                     md.digest();
+                     et = +new Date();
+                     t[n] = t[n] + (et - st);
+                     ++count;
+                  }
+                  t[n] /= 1000;
+                  forge.log.debug(cat,
+                     output[n], (count*s[n]/t[n]/1000) + 'k/sec');
+               }, 0);
+            }(n);
+         }
+      };
+
+      var test_sha1 = function()
+      {
+         // create input data
+         var input = ['0123456789abcdef', '', '', '', ''];
+         for(var i = 0; i < 4; ++i)
+         {
+            input[1] += input[0];
+         }
+         for(var i = 0; i < 4; ++i)
+         {
+            input[2] += input[1];
+         }
+         for(var i = 0; i < 4; ++i)
+         {
+            input[3] += input[2];
+         }
+         for(var i = 0; i < 8; ++i)
+         {
+            input[4] += input[3];
+         }
+
+         var md = forge.md.sha1.create();
+
+         forge.log.debug(cat, 'sha-1 times in 1000s of bytes/sec processed:');
+
+         var st, et;
+         var output =
+            ['  16 bytes: ',
+             '  64 bytes: ',
+             ' 256 bytes: ',
+             '1024 bytes: ',
+             '8192 bytes: '];
+         var s = [16, 64, 256, 1024, 8192];
+         var t = [0, 0, 0, 0, 0];
+         for(var n = 0; n < 5; ++n)
+         {
+            var f = function(n)
+            {
+               setTimeout(function()
+               {
+                  // run for 2 seconds each
+                  var count = 0;
+                  while(t[n] < 2000)
+                  {
+                     md.start();
+                     st = +new Date();
+                     md.update(input[n]);
+                     md.digest();
+                     et = +new Date();
+                     t[n] = t[n] + (et - st);
+                     ++count;
+                  }
+                  t[n] /= 1000;
+                  forge.log.debug(cat,
+                     output[n], (count*s[n]/t[n]/1000) + 'k/sec');
+               }, 0);
+            }(n);
+         }
+      };
+
+      var test_sha256 = function()
+      {
+         // create input data
+         var input = ['0123456789abcdef', '', '', '', ''];
+         for(var i = 0; i < 4; ++i)
+         {
+            input[1] += input[0];
+         }
+         for(var i = 0; i < 4; ++i)
+         {
+            input[2] += input[1];
+         }
+         for(var i = 0; i < 4; ++i)
+         {
+            input[3] += input[2];
+         }
+         for(var i = 0; i < 8; ++i)
+         {
+            input[4] += input[3];
+         }
+
+         var md = forge.md.sha256.create();
+
+         forge.log.debug(cat, 'sha-256 times in 1000s of bytes/sec processed:');
+
+         var st, et;
+         var output =
+            ['  16 bytes: ',
+             '  64 bytes: ',
+             ' 256 bytes: ',
+             '1024 bytes: ',
+             '8192 bytes: '];
+         var s = [16, 64, 256, 1024, 8192];
+         var t = [0, 0, 0, 0, 0];
+         for(var n = 0; n < 5; ++n)
+         {
+            var f = function(n)
+            {
+               setTimeout(function()
+               {
+                  // run for 2 seconds each
+                  var count = 0;
+                  while(t[n] < 2000)
+                  {
+                     md.start();
+                     st = +new Date();
+                     md.update(input[n]);
+                     md.digest();
+                     et = +new Date();
+                     t[n] = t[n] + (et - st);
+                     ++count;
+                  }
+                  t[n] /= 1000;
+                  forge.log.debug(cat,
+                     output[n], (count*s[n]/t[n]/1000) + 'k/sec');
+               }, 0);
+            }(n);
+         }
+      };
+
+      // TODO: follow the same format as the hash tests
+      var aes_128 = function()
+      {
+         forge.log.debug(cat, 'running AES-128 for 5 seconds...');
+
+         var block = [];
+         block.push(0x00112233);
+         block.push(0x44556677);
+         block.push(0x8899aabb);
+         block.push(0xccddeeff);
+
+         var key = [];
+         key.push(0x00010203);
+         key.push(0x04050607);
+         key.push(0x08090a0b);
+         key.push(0x0c0d0e0f);
+
+         setTimeout(function()
+         {
+            // run for 5 seconds
+            var start = +new Date();
+	         var now;
+	         var totalEncrypt = 0;
+	         var totalDecrypt = 0;
+	         var count = 0;
+            var passed = 0;
+	         while(passed < 5000)
+	         {
+	            var output = [];
+	            var w = forge.aes._expandKey(key, false);
+	            now = +new Date();
+	            forge.aes._updateBlock(w, block, output, false);
+	            totalEncrypt += +new Date() - now;
+
+	            block = output;
+	            output = [];
+	            w = forge.aes._expandKey(key, true);
+	            now = +new Date();
+	            forge.aes._updateBlock(w, block, output, true);
+	            totalDecrypt += +new Date() - now;
+
+               ++count;
+	            passed = +new Date() - start;
+	         }
+
+	         count = count * 16 / 1000;
+	         totalEncrypt /= 1000;
+	         totalDecrypt /= 1000;
+	         forge.log.debug(cat, 'times in 1000s of bytes/sec processed.');
+	         forge.log.debug(cat,
+	   	      'encrypt: ' + (count*16 / totalEncrypt) + ' k/sec');
+	         forge.log.debug(cat,
+	   	      'decrypt: ' + (count*16 / totalDecrypt) + ' k/sec');
+         }, 0);
+      };
+
+      // TODO: follow the same format as the hash tests
+      var aes_128_cbc = function()
+      {
+         forge.log.debug(cat, 'running AES-128 CBC for 5 seconds...');
+
+         var key = forge.random.getBytes(16);
+         var iv = forge.random.getBytes(16);
+         var plain = forge.random.getBytes(16);
+
+         setTimeout(function()
+         {
+            // run for 5 seconds
+            var start = +new Date();
+	         var now;
+	         var totalEncrypt = 0;
+	         var totalDecrypt = 0;
+	         var cipher;
+	         var count = 0;
+	         var passed = 0;
+	         while(passed < 5000)
+	         {
+               var input = forge.util.createBuffer(plain);
+
+               // encrypt, only measuring update() and finish()
+               cipher = forge.aes.startEncrypting(key, iv);
+               now = +new Date();
+               cipher.update(input);
+               cipher.finish();
+               totalEncrypt += +new Date() - now;
+
+               // decrypt, only measuring update() and finish()
+               var ct = cipher.output;
+               cipher = forge.aes.startDecrypting(key, iv);
+               now = +new Date();
+               cipher.update(ct);
+               cipher.finish();
+               totalDecrypt += +new Date() - now;
+
+               ++count;
+               passed = +new Date() - start;
+	         }
+
+	         // 32 bytes encrypted because of 16 bytes of padding
+	         count = count * 32 / 1000;
+            totalEncrypt /= 1000;
+            totalDecrypt /= 1000;
+            forge.log.debug(cat, 'times in 1000s of bytes/sec processed.');
+            forge.log.debug(cat,
+               'encrypt: ' + (count / totalEncrypt) + ' k/sec');
+            forge.log.debug(cat,
+               'decrypt: ' + (count / totalDecrypt) + ' k/sec');
+         }, 0);
+      };
+
+      //]]>
+      </script>
+   </head>
+   <body>
+      <div class="nav"><a href="index.html">Forge Tests</a> / Performance</div>
+
+      <div class="header">
+         <h1>Performance Tests</h1>
+      </div>
+
+      <div class="content">
+
+         <fieldset class="section">
+            <ul>
+               <li>Use the controls below to run Forge performance tests.</li>
+               <li>You currently need a JavaScript console to view the output.</li>
+            </ul>
+         </fieldset>
+
+         <fieldset class="section">
+            <legend>Tests</legend>
+
+         <div id="random_controls">
+            <button id="random" onclick="javascript:return test_random();">paint random</button>
+            <button id="clear" onclick="javascript:return canvas_clear();">clear</button>
+         </div>
+         <canvas id="canvas" width="100" height="100"></canvas>
+         <div id="buffer_controls">
+            <button id="buffer_fill" onclick="javascript:return test_buffer_fill();">buffer fill</button>
+            <button id="buffer_xor" onclick="javascript:return test_buffer_xor();">buffer xor</button>
+         </div>
+         <div id="base64_controls">
+            <button id="base64_encode" onclick="javascript:return test_base64_encode();">base64 encode</button>
+            <button id="base64_decode" onclick="javascript:return test_base64_decode();">base64 decode</button>
+         </div>
+         <div id="hash_controls">
+            <button id="md5" onclick="javascript:return test_md5();">md5</button>
+            <button id="sha1" onclick="javascript:return test_sha1();">sha1</button>
+            <button id="sha256" onclick="javascript:return test_sha256();">sha256</button>
+         </div>
+         <div id="aes_controls">
+            <button id="aes_128" onclick="javascript:return aes_128();">aes-128</button>
+            <button id="aes_128_cbc" onclick="javascript:return aes_128_cbc();">aes-128 cbc</button>
+         </div>
+         </fieldset>
+      </div>
+   </body>
+</html>
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/policyserver.py b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/policyserver.py
new file mode 100755
index 0000000..bda8afe
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/policyserver.py
@@ -0,0 +1,112 @@
+#!/usr/bin/env python
+
+"""
+Flash Socket Policy Server.
+
+- Starts Flash socket policy file server.
+- Defaults to port 843.
+- NOTE: Most operating systems require administrative privileges to use
+  ports under 1024.
+
+  $ ./policyserver.py [options]
+"""
+
+"""
+Also consider Adobe's solutions:
+http://www.adobe.com/devnet/flashplayer/articles/socket_policy_files.html
+"""
+
+from multiprocessing import Process
+from optparse import OptionParser
+import SocketServer
+import logging
+
+# Set address reuse for all TCPServers
+SocketServer.TCPServer.allow_reuse_address = True
+
+# Static socket policy file string.
+# NOTE: This format is very strict. Edit with care.
+socket_policy_file = """\
+<?xml version="1.0"?>\
+<!DOCTYPE cross-domain-policy\
+ SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">\
+<cross-domain-policy>\
+<allow-access-from domain="*" to-ports="*"/>\
+</cross-domain-policy>\0"""
+
+
+class PolicyHandler(SocketServer.BaseRequestHandler):
+    """
+    The RequestHandler class for our server.
+
+    Returns a policy file when requested.
+    """
+
+    def handle(self):
+        """Send policy string if proper request string is received."""
+        # get some data
+        # TODO: make this more robust (while loop, etc)
+        self.data = self.request.recv(1024).rstrip('\0')
+        logging.debug("%s wrote:%s" % (self.client_address[0], repr(self.data)))
+        # if policy file request, send the file.
+        if self.data == "<policy-file-request/>":
+            logging.info("Policy server request from %s." % (self.client_address[0]))
+            self.request.send(socket_policy_file)
+        else:
+            logging.info("Policy server received junk from %s: \"%s\"" % \
+                    (self.client_address[0], repr(self.data)))
+
+
+class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
+    def serve_forever(self):
+        """Handle one request at a time until shutdown or keyboard interrupt."""
+        try:
+           SocketServer.BaseServer.serve_forever(self)
+        except KeyboardInterrupt:
+           return
+
+
+def main():
+    """Run socket policy file servers."""
+    usage = "Usage: %prog [options]"
+    parser = OptionParser(usage=usage)
+    parser.add_option("", "--host", dest="host", metavar="HOST",
+            default="localhost", help="bind to HOST")
+    parser.add_option("-p", "--port", dest="port", metavar="PORT",
+            default=843, type="int", help="serve on PORT")
+    parser.add_option("-d", "--debug", dest="debug", action="store_true",
+            default=False, help="debugging output")
+    parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
+            default=False, help="verbose output")
+    (options, args) = parser.parse_args()
+
+    # setup logging
+    if options.debug:
+        lvl = logging.DEBUG
+    elif options.verbose:
+        lvl = logging.INFO
+    else:
+        lvl = logging.WARNING
+    logging.basicConfig(level=lvl, format="%(levelname)-8s %(message)s")
+
+    # log basic info
+    logging.info("Flash Socket Policy Server. Use ctrl-c to exit.")
+    
+    # create policy server
+    logging.info("Socket policy serving on %s:%d." % (options.host, options.port))
+    policyd = ThreadedTCPServer((options.host, options.port), PolicyHandler)
+
+    # start server
+    policy_p = Process(target=policyd.serve_forever)
+    policy_p.start()
+
+    while policy_p.is_alive():
+        try:
+            policy_p.join(1)
+        except KeyboardInterrupt:
+            logging.info("Stopping test server...")
+
+
+if __name__ == "__main__":
+    main()
+
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/result.txt b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/result.txt
new file mode 100644
index 0000000..7cb007c
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/result.txt
@@ -0,0 +1 @@
+expected result
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/screen.css b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/screen.css
new file mode 100644
index 0000000..365a39f
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/screen.css
@@ -0,0 +1,61 @@
+/* CSS for Forge tests */
+body {
+   background: white;
+   color: black;
+   margin: 0;
+   padding: 0;
+}
+.warning {
+   border: thin solid red;
+   background: #7FF;
+}
+div.nav {
+   background: white;
+   border-bottom: thin solid black;
+   padding: .5em;
+   padding-top: .2em;
+   padding-bottom: .2em;
+}
+div.header {
+   border-bottom: thin solid black;
+   padding: .5em;
+}
+div.content {
+   padding: .5em;
+   background: #DDD;
+}
+div.footer {
+   border-top: thin solid black;
+   font-size: 80%;
+   padding: .5em;
+}
+canvas {
+   background: black;
+}
+table, td, th {
+   border: thin solid black;
+   border-collapse: collapse;
+}
+td, th {
+   padding: .2em;
+}
+span.sp-state-on {
+   font-weight: bold;
+}
+table#feedback {
+   width: 100%;
+}
+table#feedback th, table#feedback td {
+   width: 33%;
+}
+fieldset.section {
+   margin: .5em;
+   border: 2px solid black;
+   background: #FFF;
+}
+fieldset.section legend {
+   padding: .2em;
+   border: 2px solid black;
+   background: #DDF;
+   font-weight: bold;
+}
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/server.crt b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/server.crt
new file mode 100644
index 0000000..6952426
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/server.crt
@@ -0,0 +1,26 @@
+-----BEGIN CERTIFICATE-----
+MIIEaDCCA1CgAwIBAgIJAJuj0AjEWncuMA0GCSqGSIb3DQEBBQUAMH8xCzAJBgNV
+BAYTAlVTMREwDwYDVQQIEwhWaXJnaW5pYTETMBEGA1UEBxMKQmxhY2tzYnVyZzEd
+MBsGA1UEChMURGlnaXRhbCBCYXphYXIsIEluYy4xGjAYBgNVBAsTEUZvcmdlIFRl
+c3QgU2VydmVyMQ0wCwYDVQQDEwR0ZXN0MB4XDTEwMDcxMzE3MjAzN1oXDTMwMDcw
+ODE3MjAzN1owfzELMAkGA1UEBhMCVVMxETAPBgNVBAgTCFZpcmdpbmlhMRMwEQYD
+VQQHEwpCbGFja3NidXJnMR0wGwYDVQQKExREaWdpdGFsIEJhemFhciwgSW5jLjEa
+MBgGA1UECxMRRm9yZ2UgVGVzdCBTZXJ2ZXIxDTALBgNVBAMTBHRlc3QwggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCm/FobjqK8CVP/Xbnpyhf1tpoyaiFf
+ShUOmlWqL5rLe0Q0dDR/Zur+sLMUv/1T4wOfFkjjxvZ0Sk5NIjK3Wy2UA41a+M3J
+RTbCFrg4ujsovFaD4CDmV7Rek0qJB3m5Gp7hgu5vfL/v+WrwxnQObNq+IrTMSA15
+cO4LzNIPj9K1LN2dB+ucT7xTQFHAfvLLgLlCLiberoabF4rEhgTMTbmMtFVKSt+P
+xgQIYPnhw1WuAvE9hFesRQFdfARLqIZk92FeHkgtHv9BAunktJemcidbowTCTBaM
+/njcgi1Tei/LFkph/FCVyGER0pekJNHX626bAQSLo/srsWfmcll9rK6bAgMBAAGj
+geYwgeMwHQYDVR0OBBYEFCau5k6jxezjULlLuo/liswJlBF8MIGzBgNVHSMEgasw
+gaiAFCau5k6jxezjULlLuo/liswJlBF8oYGEpIGBMH8xCzAJBgNVBAYTAlVTMREw
+DwYDVQQIEwhWaXJnaW5pYTETMBEGA1UEBxMKQmxhY2tzYnVyZzEdMBsGA1UEChMU
+RGlnaXRhbCBCYXphYXIsIEluYy4xGjAYBgNVBAsTEUZvcmdlIFRlc3QgU2VydmVy
+MQ0wCwYDVQQDEwR0ZXN0ggkAm6PQCMRady4wDAYDVR0TBAUwAwEB/zANBgkqhkiG
+9w0BAQUFAAOCAQEAnP/2mzFWaoGx6+KAfY8pcgnF48IoyKPx5cAQyzpMo+uRwrln
+INcDGwNx6p6rkjFbK27TME9ReCk+xQuVGaKOtqErKECXWDtD+0M35noyaOwWIFu2
+7gPZ0uGJ1n9ZMe/S9yZmmusaIrc66rX4o+fslUlH0g3SrH7yf83M8aOC2pEyCsG0
+mNNfwSFWfmu+1GMRHXJQ/qT8qBX8ZPhzRY2BAS6vr+eh3gwXR6yXLA8Xm1+e+iDU
+gGTQoYkixDIL2nhvd4AFFlE977BiE+0sMS1eJKUUbQ36MLAWb5oOZKHrphEvqMKA
+eGDO3qoDqB5TkZC3x38DXBDvAZ01d9s0fvveag==
+-----END CERTIFICATE-----
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/server.key b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/server.key
new file mode 100644
index 0000000..4024097
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/server.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEApvxaG46ivAlT/1256coX9baaMmohX0oVDppVqi+ay3tENHQ0
+f2bq/rCzFL/9U+MDnxZI48b2dEpOTSIyt1stlAONWvjNyUU2wha4OLo7KLxWg+Ag
+5le0XpNKiQd5uRqe4YLub3y/7/lq8MZ0DmzaviK0zEgNeXDuC8zSD4/StSzdnQfr
+nE+8U0BRwH7yy4C5Qi4m3q6GmxeKxIYEzE25jLRVSkrfj8YECGD54cNVrgLxPYRX
+rEUBXXwES6iGZPdhXh5ILR7/QQLp5LSXpnInW6MEwkwWjP543IItU3ovyxZKYfxQ
+lchhEdKXpCTR1+tumwEEi6P7K7Fn5nJZfayumwIDAQABAoIBAFGPbEuNbXq+a6KN
+GuNP7Ef9em8pW0d5nbNWOoU3XzoH6RZds86ObDUeBTobVBaHCRvI/K0UXwgJyxjt
+nSvlguuKmJ5Ya9rkzYwbILvEamTJKNCcxjT7nYOcGYm4dwGsOPIYy3D006LYhh04
+MTNig6zessQcZUhtmjd1QRyMuPP4PaWVO79ic01jxZR/ip6tN/FjCYclPRi/FRi8
+bQVuGEVLW2qHgQbDKPpcXFyFjIqt7c9dL97/3eeIDp+SgdQ6bPi80J7v9p2MRyBP
+7OPhX8ZDsAiZr4G4N0EbEzmWWpVSjAI3Nlmk8SLT4lu42KKyoZLtuKPjEOVI3/TR
+0ktsc/ECgYEA27AHLnsv07Yqe7Z2bmv+GP8PKlwrPSHwqU/3Z5/1V590N+jo15N4
+lb7gvBUwwvXIxQQQVYJqRimqNQYVfT6+xRtQdtdqInxv2hvhc/cKPEiIHNpRh7OI
+w7I59yNMlCnqLeRBiCOmd7ruCWoMGw+VLhsyArwUTXuqUK2oYN9qWm8CgYEAwpZF
+XNm8xCFa+YeqP+WQzwK/0yUxHmYZs7ofh9ZIgHtqwHNKO/OA8+nGsZBaIl5xiyT4
+uZ/qZi2EkYzOmx0iSathiWQpSyc9kB+nOTdMHyhBOj8CgbTRRXIMjDQ6bz78Z09F
+Nxenhwk2gSVr3oB2FG/BWc1rlmVlmGJIIX3QtJUCgYBfLhLOdpywExqw4srI6Iz8
+c3U0mx44rD3CfVzpTopTXkhR+Nz4mXIDHuHrWxr3PNmxUiNpiMlWgLK3ql0hGFA6
+wazI8GeRbWxgiPfS8FNE7v/Z0FTGgGhesRcgFfEVuFs3as9hlmCHOzvqZEG+b6/o
+e+vc93OsZknSDosG/YTsjQKBgHrb7HGinLftI4a3rLvpU1QRNVK4gdnit0muM6hN
+mLtesVlPschGh935ddW5Ad//Z4tmTZDOMm5PQQuxLuXrMDH5fn0D+7qSzSEJi0jp
+7Csj/IMtM4T3yMYjK17+vwJsb2s/NsGBMupk28ARA5mZ3HQs15S+ybZM0Se0rjxP
+Nw49AoGBAKrLTOtZta0DSGt7tURwQK1mERuGM8ZZdXjvIVTJIIknohD2u3/T+O4+
+ekFTUd6GQKOFd/hZ52t4wcRs7c7KE1Xen7vRHc8c6c3TkF9ldpLmK2AWT8WifQO6
+9Fjx2Wf8HM+CbrokQYH/OHSV9Xft8BRTOPHGUJlp1UsYikSwp4fW
+-----END RSA PRIVATE KEY-----
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/server.py b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/server.py
new file mode 100755
index 0000000..b5a5f06
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/server.py
@@ -0,0 +1,184 @@
+#!/usr/bin/env python
+
+"""
+SSL server for Forge tests.
+
+- The server changes to the directory of the server script.
+- SSL uses "server.key" and "server.crt".
+- Sever performs basic static file serving.
+- Starts Flash cross domain policy file server.
+- Defaults to HTTP/HTTPS port 19400.
+- Defaults to Flash socket policy port 19945.
+
+  $ ./server.py [options]
+
+If you just need a simple HTTP server, also consider:
+  $ python -m SimpleHTTPServer 19400
+"""
+
+from multiprocessing import Process
+from optparse import OptionParser
+import SimpleHTTPServer
+import SocketServer
+import os
+import sys
+import time
+
+# Try to import special Forge SSL module with session cache support
+# Using the built directory directly
+python_version = "python" + sys.version[:3]
+sys.path.insert(0, os.path.join(
+    os.path.dirname(os.path.realpath(__file__)),
+    "..", "dist", "forge_ssl", "lib", python_version, "site-packages"))
+try:
+    from forge import ssl
+    have_ssl_sessions = True
+    have_ssl = True
+except ImportError:
+    have_ssl_sessions = False
+    try:
+        import ssl
+        have_ssl = True
+    except ImportError:
+        have_ssl = False
+
+# Set address reuse for all TCPServers
+SocketServer.TCPServer.allow_reuse_address = True
+
+# The policy file
+# NOTE: This format is very strict. Edit with care.
+policy_file = """\
+<?xml version="1.0"?>\
+<!DOCTYPE cross-domain-policy\
+ SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">\
+<cross-domain-policy>\
+<allow-access-from domain="*" to-ports="*"/>\
+</cross-domain-policy>\0"""
+
+
+class PolicyHandler(SocketServer.BaseRequestHandler):
+    """
+    The RequestHandler class for our server.
+
+    Returns a policy file when requested.
+    """
+
+    def handle(self):
+        # get some data
+        # TODO: make this more robust (while loop, etc)
+        self.data = self.request.recv(1024).rstrip('\0')
+        #print "%s wrote:" % self.client_address[0]
+        #print repr(self.data)
+        # if policy file request, send the file.
+        if self.data == "<policy-file-request/>":
+            print "Policy server request from %s." % (self.client_address[0])
+            self.request.send(policy_file)
+        else:
+            print "Policy server received junk from %s: \"%s\"" % \
+                    (self.client_address[0], repr(self.data))
+
+
+def create_policy_server(options):
+    """Start a policy server"""
+    print "Policy serving from %d." % (options.policy_port)
+    policyd = SocketServer.TCPServer((options.host, options.policy_port), PolicyHandler)
+    return policyd
+
+
+class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
+    pass
+
+
+def create_http_server(options, script_dir):
+    """Start a static file server"""
+    # use UTF-8 encoding for javascript files
+    m = SimpleHTTPServer.SimpleHTTPRequestHandler.extensions_map
+    m['.js'] = 'application/javascript;charset=UTF-8'
+
+    Handler = SimpleHTTPServer.SimpleHTTPRequestHandler
+#    httpd = SocketServer.TCPServer((options.host, options.port), Handler)
+    httpd = ThreadedTCPServer((options.host, options.port), Handler)
+    if options.tls:
+        if not have_ssl:
+            raise Exception("SSL support from Python 2.7 or later is required.")
+
+        # setup session args if we session support
+        sess_args = {}
+        if have_ssl_sessions:
+            sess_args["sess_id_ctx"] = "forgetest"
+        else:
+            print "Forge SSL with session cache not available, using standard version."
+
+        httpd.socket = ssl.wrap_socket(
+            httpd.socket,
+            keyfile="server.key",
+            certfile="server.crt",
+            server_side=True,
+            **sess_args)
+
+    print "Serving from \"%s\"." % (script_dir)
+    print "%s://%s:%d/" % \
+            (("https" if options.tls else "http"),
+            httpd.server_address[0],
+            options.port)
+    return httpd
+
+
+def serve_forever(server):
+    """Serve until shutdown or keyboard interrupt."""
+    try:
+       server.serve_forever()
+    except KeyboardInterrupt:
+       return
+
+
+def main():
+    """Start static file and policy servers"""
+    usage = "Usage: %prog [options]"
+    parser = OptionParser(usage=usage)
+    parser.add_option("", "--host", dest="host", metavar="HOST",
+            default="localhost", help="bind to HOST")
+    parser.add_option("-p", "--port", dest="port", type="int",
+            help="serve on PORT", metavar="PORT", default=19400)
+    parser.add_option("-P", "--policy-port", dest="policy_port", type="int",
+            help="serve policy file on PORT", metavar="PORT", default=19945)
+    parser.add_option("", "--tls", dest="tls", action="store_true",
+            help="serve HTTPS", default=False)
+    (options, args) = parser.parse_args()
+
+    # Change to script dir so SSL and test files are in current dir.
+    script_dir = os.path.dirname(os.path.realpath(__file__))
+    os.chdir(script_dir)
+
+    print "Forge Test Server. Use ctrl-c to exit."
+    
+    # create policy and http servers
+    httpd = create_http_server(options, script_dir)
+    policyd = create_policy_server(options)
+
+    # start servers
+    server_p = Process(target=serve_forever, args=(httpd,))
+    policy_p = Process(target=serve_forever, args=(policyd,))
+    server_p.start()
+    policy_p.start()
+
+    processes = [server_p, policy_p]
+
+    while len(processes) > 0:
+        try:
+            for p in processes:
+               if p.is_alive():
+                  p.join(1)
+               else:
+                  processes.remove(p)
+        except KeyboardInterrupt:
+            print "\nStopping test server..."
+            # processes each receive interrupt
+            # so no need to shutdown
+            #httpd.shutdown();
+            #policyd.shutdown();
+
+
+if __name__ == "__main__":
+    main()
+
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/socketPool.html b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/socketPool.html
new file mode 100644
index 0000000..33a095f
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/socketPool.html
@@ -0,0 +1,299 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+   <head>
+      <link type="text/css" rel="stylesheet" media="all" href="screen.css" />
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js"></script>
+      <script type="text/javascript" src="forge/util.js"></script>
+      <script type="text/javascript" src="forge/socket.js"></script>
+      <script type="text/javascript" src="forge/log.js"></script>
+      
+      <script type="text/javascript">
+      //<![CDATA[
+      // logging category
+      var cat = 'forge.tests.socketPool';
+
+      // feedback types
+      var ERROR = '';
+      var USER = 'user';
+      var SOCKETPOOL = 'socketpool'
+      var SOCKET = 'socket'
+
+      function addFeedback(type, text)
+      {
+          var row = $('<tr/>')
+             .append($('<td/>').html('&nbsp;'))
+             .append($('<td/>').html('&nbsp;'))
+             .append($('<td/>').html('&nbsp;'));
+          switch(type)
+          {
+             case USER:
+                row.children().eq(0).html(text);
+                break;
+             case SOCKETPOOL:
+                row.children().eq(1).html(text);
+                break;
+             case SOCKET:
+                row.children().eq(2).html(text);
+                break;
+             default:
+                var msg = 'ERROR: bad feedback type:' + type;
+                row.children().eq(1).html(msg);
+                forge.log.error(cat, msg);
+          }
+          $('#feedback').append(row);
+          forge.log.debug(cat, '[' + type + ']', text);
+      };
+
+      function _setState(stateSel)
+      {
+         $('.sp-control').filter(stateSel).removeAttr('disabled');
+         $('.sp-control').filter(':not(' + stateSel + ')').attr('disabled', 'disabled');
+         $('.sp-state').filter(stateSel).addClass('sp-state-on');
+         $('.sp-state').filter(':not(' + stateSel + ')').removeClass('sp-state-on');
+      };
+
+      function setState(state)
+      {
+         switch(state)
+         {
+            case 'ready':
+               _setState('.sp-ready');
+               break;
+            case 'initialized':
+               _setState('.sp-ready,.sp-initialized');
+               break;
+            case 'created':
+               _setState('.sp-ready,.sp-initialized,.sp-created');
+               break;
+            case 'connected':
+               _setState('.sp-ready,.sp-initialized,.sp-created,.sp-connected');
+               break;
+            default:
+               addFeedback(ERROR, 'ERROR: bad state: ' + state);
+         };
+      };
+
+      window.forge.socketPool =
+      {
+         ready: function()
+         {
+            $(document).ready(function() {
+               addFeedback(SOCKETPOOL, 'Ready');
+               setState('ready');
+            });
+         }
+      };
+      
+      swfobject.embedSWF(
+         "forge/SocketPool.swf", "socketPool", "0", "0", "9.0.0",
+         false, {}, {allowscriptaccess: 'always'}, {});
+      
+      // local alias
+      var net = window.forge.net;
+
+      // socket to use
+      var socket;
+
+      $(document).ready(function() {
+         addFeedback(USER, 'Ready');
+         $('#host').val(window.location.hostname);
+         $('#port').val(window.location.port);
+      });
+
+      function sp_init()
+      {
+         net.createSocketPool({
+            flashId: 'socketPool',
+            policyPort: parseInt($('#policyPort').val()),
+            msie: false
+         });
+         addFeedback(SOCKETPOOL, 'Initialized');
+         setState('initialized');
+         return false;
+      };
+      
+      function sp_cleanup()
+      {
+         net.destroySocketPool({flashId: 'socketPool'});
+         addFeedback(SOCKETPOOL, 'Cleaned up');
+         setState('ready');
+         return false;
+      };
+
+      function sp_create()
+      {
+         socket = net.createSocket({
+            flashId: 'socketPool',
+            connected: function(e)
+            {
+               forge.log.debug(cat, 'connected', e);
+               addFeedback(SOCKET, 'Connected');
+            },
+            closed: function(e)
+            {
+               forge.log.debug(cat, 'closed', e);
+               addFeedback(SOCKET, 'Closed. Type: ' + e.type);
+               setState('created');
+            },
+            data: function(e)
+            {
+               forge.log.debug(cat, 'data received', e);
+               forge.log.debug(cat, 'bytes available', socket.bytesAvailable());
+               addFeedback(SOCKET,
+                  'Data available: ' +
+                  socket.bytesAvailable() +' bytes');
+               var bytes = socket.receive(e.bytesAvailable);
+               forge.log.debug(cat, 'bytes received', bytes);
+            },
+            error: function(e)
+            {
+               forge.log.error(cat, 'error', e);
+               addFeedback(SOCKET, 'Error: ' + e);
+            }
+         });
+         addFeedback(SOCKETPOOL, 'Created socket');
+         setState('created');
+         return false;
+      };
+      
+      function sp_destroy()
+      {
+         socket.destroy();
+         addFeedback(USER, 'Request socket destroy');
+         setState('initialized');
+         return false;
+      };
+
+      function sp_connect()
+      {
+         socket.connect({
+            host: $('#host').val(),
+            port: parseInt($('#port').val()),
+            policyPort: parseInt($('#policyPort').val())
+         });
+         addFeedback(USER, 'Request socket connect');
+         setState('connected');
+      };
+
+      function sp_isConnected()
+      {
+         var connected = socket.isConnected();
+         addFeedback(USER, 'Socket connected check: ' + connected);
+      };
+
+      function sp_send()
+      {
+         socket.send('GET ' + $('#path').val() + ' HTTP/1.0\r\n\r\n');
+         addFeedback(USER, 'Send GET request');
+      };
+
+      function sp_close()
+      {
+         socket.close();
+         addFeedback(USER, 'Requst socket close');
+         setState('created');
+      };
+      //]]>
+      </script>
+   </head>
+   <body>
+      <div class="nav"><a href="index.html">Forge Tests</a> / SocketPool</div>
+
+      <div class="header">
+         <h1>SocketPool Test</h1>
+      </div>
+
+      <div class="content">
+         <!-- div used to hold the flash socket pool implemenation -->
+         <div id="socketPool">
+            <p>Could not load the flash SocketPool.</p>
+         </div>
+   
+         <fieldset class="section">
+            <ul>
+               <li>This page tests a single socket connection to the local test server.</li>
+               <li>Note that the selected server must serve a Flash cross-domain policy file on the selected policy port.</li>
+               <li>Additional output available in the JavaScript console.</li>
+            </ul>
+         </fieldset>
+   
+         <fieldset class="section">
+            <legend>State</legend>
+            <p>State:
+               <span class="sp-state sp-ready">Ready</span> &raquo;
+               <span class="sp-state sp-initialized">Initialized</span> &raquo;
+               <span class="sp-state sp-created">Created</span> &raquo;
+               <span class="sp-state sp-connected">Connected</span>
+            </p>
+         </fieldset>
+   
+         <fieldset class="section">
+            <legend>Controls</legend>
+            <div id="controls">
+               <table>
+                  <tr><th>Action</th><th>Description</th></tr>
+                  <tr>
+                     <td><button id="init" disabled="disabled" class="sp-control sp-ready"
+                        onclick="javascript:return sp_init();">init</button></td>
+                     <td>Initialize SocketPool system.</td>
+                  </tr>
+                  <tr>
+                     <td><button id="cleanup" disabled="disabled" class="sp-control sp-initialized"
+                        onclick="javascript:return sp_cleanup();">cleanup</button></td>
+                     <td>Cleanup SocketPool system.</td>
+                  </tr>
+                  <tr>
+                     <td><button id="create" disabled="disabled" class="sp-control sp-initialized"
+                        onclick="javascript:return sp_create();">create socket</button></td>
+                     <td>Create a new test socket.</td>
+                  </tr>
+                  <tr>
+                     <td><button id="destroy" disabled="disabled" class="sp-control sp-created"
+                        onclick="javascript:return sp_destroy();">destroy socket</button></td>
+                     <td>Destroy the test socket.</td>
+                  </tr>
+                  <tr>
+                     <td><button id="connect" disabled="disabled" class="sp-control sp-created"
+                        onclick="javascript:return sp_connect();">connect</button></td>
+                     <td>Connect the socket to
+                        host: <input id="host"/>
+                        port: <input id="port"/>
+                        policy port: <input id="policyPort" value="19945"/>
+                     </td>
+                  </tr>
+                  <tr>
+                     <td><button id="isConnected" disabled="disabled" class="sp-control sp-created"
+                        onclick="javascript:return sp_isConnected();">is connected</button></td>
+                     <td>Check if socket is connected.</td>
+                  </tr>
+                  <tr>
+                     <td><button id="send" disabled="disabled" class="sp-control sp-connected"
+                        onclick="javascript:return sp_send();">send</button></td>
+                     <td>Send a GET request for
+                        path: <input id="path" value="/"/>
+                     </td>
+                  </tr>
+                  <tr>
+                     <td><button id="close" disabled="disabled" class="sp-control sp-connected"
+                        onclick="javascript:return sp_close();">close</button></td>
+                     <td>Close the test socket.</td>
+                  </tr>
+               </table>
+            </div>
+         </fieldset>
+   
+         <fieldset class="section">
+            <legend>Feedback</legend>
+            <table id="feedback">
+               <tr>
+                  <th>User</th>
+                  <th>SocketPool</th>
+                  <th>Socket</th>
+               </tr>
+            </table>
+         </fieldset>
+      </div>
+   </body>
+</html>
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/tasks.html b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/tasks.html
new file mode 100644
index 0000000..eba0173
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/tasks.html
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+   <head>
+      <title>Forge Tasks Test</title>
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
+      <script type="text/javascript" src="forge/debug.js"></script>
+      <script type="text/javascript" src="forge/util.js"></script>
+      <script type="text/javascript" src="forge/log.js"></script>
+      <script type="text/javascript" src="forge/task.js"></script>
+      <script type="text/javascript" src="tasks.js"></script>
+      <link type="text/css" rel="stylesheet" media="all" href="screen.css" />
+      <style type="text/css">
+         .ready { color: inherit; background: inherit; }
+         .testing { color: black; background: yellow; }
+         .pass{ color: white; background: green; }
+         .fail{ color: white; background: red; }
+      </style>
+   </head>
+<body>
+<div class="nav"><a href="index.html">Forge Tests</a> / Tasks</div>
+
+<div class="header">
+   <h1>Task Tests</h1>
+</div>
+
+<div class="content">
+
+<fieldset class="section">
+<legend>Control</legend>
+   <button id="start">Start</button>
+   <button id="reset">Reset</button>
+   <br/>
+   <input id="scroll" type="checkbox" checked="checked" />Scroll Tests
+</fieldset>
+
+<fieldset class="section">
+<legend>Progress</legend>
+Status: <span id="status">?</span><br/>
+Pass: <span id="pass">?</span>/<span id="total">?</span><br/>
+Fail: <span id="fail">?</span>
+</fieldset>
+
+<fieldset class="section">
+<legend>Tests</legend>
+<div id="tests"></div>
+</fieldset>
+
+</div>
+
+</body>
+</html>
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/tasks.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/tasks.js
new file mode 100644
index 0000000..dd3ffde
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/tasks.js
@@ -0,0 +1,378 @@
+/**
+ * Forge Tasks Test
+ *
+ * @author David I. Lehn <dlehn@digitalbazaar.com>
+ *
+ * Copyright (c) 2009-2010 Digital Bazaar, Inc. All rights reserved.
+ */
+jQuery(function($)
+{
+   var cat = 'forge.tests.tasks';
+
+   var tests = [];
+   var passed = 0;
+   var failed = 0;
+   
+   var init = function() {
+      passed = failed = 0;
+      $('.ready,.testing,.pass,.fail')
+         .removeClass('ready testing pass fail');
+      $('#status')
+         .text('Ready.')
+         .addClass('ready');
+      $('#total').text(tests.length);
+      $('#pass').text(passed);
+      $('#fail').text(failed);
+      $('.expect').empty();
+      $('.result').empty();
+      $('.time').empty();
+      $('#start').removeAttr('disabled');
+   };
+
+   var start = function()
+   {
+      $('#start').attr('disabled', 'disabled');
+      // meta! use tasks to run the task tests
+      forge.task.start({
+         type: 'test',
+         run: function(task) {
+            task.next('starting', function(task) {
+               forge.log.debug(cat, 'start');
+               $('#status')
+                  .text('Testing...')
+                  .addClass('testing')
+                  .removeClass('idle');
+            });
+            $.each(tests, function(i, test) {
+               task.next('test', function(task) {
+                  var title = $('li:first', test.container);
+                  if($('#scroll:checked').length === 1)
+                  {
+                     $('html,body').animate({scrollTop: title.offset().top});
+                  }
+                  title.addClass('testing');
+                  test.run(task, test);
+               });
+               task.next('test', function(task) {
+                  $('li:first', test.container).removeClass('testing');
+               });
+            });
+            task.next('success', function(task) {
+               forge.log.debug(cat, 'done');
+               if(failed === 0) {
+                  $('#status')
+                     .text('PASS')
+                     .addClass('pass')
+                     .removeClass('testing');
+               } else {
+                  // FIXME: should just be hitting failure() below
+                  $('#status')
+                     .text('FAIL')
+                     .addClass('fail')
+                     .removeClass('testing');
+               }
+            });
+         },
+         failure: function() {
+            $('#status')
+               .text('FAIL')
+               .addClass('fail')
+               .removeClass('testing');
+         }
+      });
+   };
+
+   $('#start').click(function() {
+      start();
+   });
+   
+   $('#reset').click(function() {
+      init();
+   });
+   
+   var addTest = function(name, run)
+   {
+      var container = $('<ul><li>Test ' + name + '</li><ul/></ul>');
+      var expect = $('<li>Expect: <span class="expect"/></li>');
+      var result = $('<li>Result: <span class="result"/></li>');
+      var time = $('<li>Time: <span class="time"/></li>');
+      $('ul', container).append(expect).append(result).append(time);
+      $('#tests').append(container);
+      var test = {
+         container: container,
+         startTime: null,
+         run: function(task, test) {
+            test.startTime = new Date();
+            run(task, test);
+         },
+         expect: $('span', expect),
+         result: $('span', result),
+         check: function() {
+            var e = test.expect.text();
+            var r = test.result.text();
+            (e == r) ? test.pass() : test.fail();
+         },
+         pass: function() {
+            passed += 1;
+            $('#pass').text(passed);
+            $('li:first', container).addClass('pass');
+            var dt = new Date() - test.startTime;
+            $('span.time', container).html(dt);
+         },
+         fail: function() {
+            failed += 1;
+            $('#fail').text(failed);
+            $('li:first', container).addClass('fail');
+            var dt = new Date() - test.startTime;
+            $('span.time', container).html(dt);
+         }
+      };
+      tests.push(test);
+   };
+
+   addTest('pass', function(task, test) {
+      test.pass();
+   });
+
+   addTest('check', function(task, test) {
+      test.check();
+   });
+
+   addTest('task 1', function(task, test) {
+      task.next(function(task) {
+         test.pass();
+      });
+   });
+
+   addTest('task check()', function(task, test) {
+      test.expect.append('check');
+      task.next(function(task) {
+         test.result.append('check');
+      });
+      task.next(function(task) {
+         test.check();
+      });
+   });
+
+   addTest('serial 20', function(task, test) {
+      // total
+      var n = 20;
+      // counter used in the tasks
+      var taskn = 0;
+      for(var i = 0; i < n; ++i) {
+         test.expect.append(i + ' ');
+         task.next(function(task) {
+            test.result.append(taskn++ + ' ');
+         });
+      }
+      task.next(function(task) {
+         test.check();
+      });
+   });
+
+   addTest('ajax block', function(task, test) {
+      test.expect.append('.');
+      task.next(function(task) {
+         task.parent.block();
+         $.ajax({
+            type: 'GET',
+            url: 'tasks.html',
+            success: function() {
+               test.result.append('.');
+               task.parent.unblock();
+            }
+         });
+      });
+      task.next(function(task) {
+         test.check();
+      });
+   });
+
+   addTest('serial ajax', function(task, test) {
+      var n = 10;
+      for(var i = 0; i < n; ++i)
+      {
+         test.expect.append(i + ' ');
+      }
+      task.next(function(task) {
+         // create parallel functions
+         task.parent.block(n);
+         for(var i = 0; i < n; ++i)
+         {
+            // pass value into closure
+            (function(i)
+            {
+               // serial tasks
+               task.next(function(ajaxTask)
+               {
+                  $.ajax({
+                     type: 'GET',
+                     url: 'tasks.html',
+                     success: function() {
+                        // results use top level task
+                        test.result.append(i + ' ');
+                        task.parent.unblock();
+                     }
+                  });
+               });
+            })(i);
+         }
+      });
+      task.next(function(task) {
+         test.check();
+      });
+   });
+
+   addTest('parallel ajax', function(task, test) {
+      task.next(function(task) {
+         var n = 10;
+         // create parallel functions
+         var tasks = [];
+         for(var i = 0; i < n; ++i)
+         {
+            // pass value into closure
+            (function(i)
+            {
+               tasks.push(function(ajaxTask)
+               {
+                  $.ajax({
+                     type: 'GET',
+                     url: 'tasks.html',
+                     success: function() {
+                        // results use top level task
+                        test.result.append(i + ' ');
+                     }
+                  });
+               });
+            })(i);
+         }
+         // launch in parallel
+         task.parallel(tasks);
+      });
+      task.next(function(task) {
+         test.pass();
+      });
+   });
+
+   addTest('linear empty tasks rate', function(task, test) {
+      test.expect.append('-');
+      // total
+      var n = 100;
+      var start = new Date();
+      for(var i = 0; i < n; ++i) {
+         // empty task
+         task.next(function(task) {});
+      }
+      task.next(function(task) {
+         var dt = (new Date() - start) / 1000;
+         var res = $('<ul/>')
+            .append('<li>Tasks: ' + n + '</li>')
+            .append('<li>Time: ' + dt + 's</li>')
+            .append('<li>Rate: ' + n/dt + ' tasks/s</li>')
+            .append('<li>Task Time: ' + 1000*dt/n + ' ms/tasks</li>');
+         test.result.html(res);
+         test.pass();
+      });
+   });
+
+   addTest('sleep', function(task, test) {
+      test.expect.append('-');
+      var st = 1000;
+      var start = new Date();
+      task.next(function(task) {
+         task.sleep(st);
+      });
+      task.next(function(task) {
+         var dt = new Date() - start;
+         var res = $('<ul/>')
+            .append('<li>Sleep Time : ' + st + 'ms</li>')
+            .append('<li>Real Time: ' + dt + 'ms</li>')
+            .append('<li>Diff: ' + (dt-st) + 'ms</li>');
+         test.result.html(res);
+         test.pass();
+      });
+   });
+   
+   addTest('serial 20 + sleep', function(task, test) {
+      // total
+      var n = 20;
+      // counter used in the tasks
+      var taskn = 0;
+      for(var i = 0; i < n; ++i) {
+         test.expect.append(i + ' ');
+         task.next(function(task) {
+            task.sleep(20);
+            test.result.append(taskn++ + ' ');
+         });
+      }
+      task.next(function(task) {
+         test.check();
+      });
+   });
+   
+   addTest('concurrent tasks', function(task, test)
+   {
+      var colors = [
+         'red',
+         'green',
+         'blue',
+         'black',
+         'purple',
+         'goldenrod',
+         'maroon',
+         'gray',
+         'teal',
+         'magenta'
+      ];
+      var count = colors.length;
+      task.next(function(task)
+      {
+         var main = task;
+         task.block(count);
+         
+         var tasks = [];
+         for(var i = 0; i < count; ++i)
+         {
+            var makefunction = function(index)
+            {
+               return function(task)
+               {
+                  // total
+                  var n = 20;
+                  // counter used in the tasks
+                  var taskn = 0;
+                  for(var j = 0; j < n; j++)
+                  {
+                     task.next(function(task)
+                     {
+                        test.result.append(
+                           '<span style=\"color:' + colors[index] + ';\">' +
+                           taskn++ + '</span> ');
+                     });
+                  }
+                  task.next(function(task)
+                  {
+                     main.unblock();
+                  });
+               };
+            };
+            tasks.push(
+            {
+               type: 'concurrent' + i,
+               run: makefunction(i)
+            });
+         }
+         
+         for(var i = 0; i < count; ++i)
+         {
+            forge.task.start(tasks[i]);
+         }
+      });
+      
+      task.next(function(task) {
+         test.pass();
+      });
+   });
+
+   init();
+});
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/tls.html b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/tls.html
new file mode 100644
index 0000000..92501b8
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/tls.html
@@ -0,0 +1,426 @@
+<html>
+   <head>
+      <link type="text/css" rel="stylesheet" media="all" href="screen.css" />
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js"></script>
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
+      <script type="text/javascript" src="forge/debug.js"></script>
+      <script type="text/javascript" src="forge/util.js"></script>
+      <script type="text/javascript" src="forge/log.js"></script>
+      <script type="text/javascript" src="forge/socket.js"></script>
+      <script type="text/javascript" src="forge/md5.js"></script>
+      <script type="text/javascript" src="forge/sha1.js"></script>
+      <script type="text/javascript" src="forge/hmac.js"></script>
+      <script type="text/javascript" src="forge/aes.js"></script>
+      <script type="text/javascript" src="forge/pem.js"></script>
+      <script type="text/javascript" src="forge/asn1.js"></script>
+      <script type="text/javascript" src="forge/jsbn.js"></script>
+      <script type="text/javascript" src="forge/prng.js"></script>
+      <script type="text/javascript" src="forge/random.js"></script>
+      <script type="text/javascript" src="forge/oids.js"></script>
+      <script type="text/javascript" src="forge/rsa.js"></script>
+      <script type="text/javascript" src="forge/pbe.js"></script>
+      <script type="text/javascript" src="forge/x509.js"></script>
+      <script type="text/javascript" src="forge/pki.js"></script>
+      <script type="text/javascript" src="forge/tls.js"></script>
+      <script type="text/javascript" src="forge/aesCipherSuites.js"></script>
+      <script type="text/javascript" src="forge/tlssocket.js"></script>
+      <script type="text/javascript" src="forge/http.js"></script>
+      <script type="text/javascript" src="ws-webid.js"></script>
+      
+      <script type="text/javascript">
+      //<![CDATA[
+      // logging category
+      var cat = 'forge.tests.tls';
+
+      swfobject.embedSWF(
+         'forge/SocketPool.swf', 'socketPool', '0', '0', '9.0.0',
+         false, {}, {allowscriptaccess: 'always'}, {});
+      
+      // CA certificate for test server
+      var certificatePem =
+         '-----BEGIN CERTIFICATE-----\r\n' +
+         'MIIEaDCCA1CgAwIBAgIJAJuj0AjEWncuMA0GCSqGSIb3DQEBBQUAMH8xCzAJBgNV\r\n' +
+         'BAYTAlVTMREwDwYDVQQIEwhWaXJnaW5pYTETMBEGA1UEBxMKQmxhY2tzYnVyZzEd\r\n' +
+         'MBsGA1UEChMURGlnaXRhbCBCYXphYXIsIEluYy4xGjAYBgNVBAsTEUZvcmdlIFRl\r\n' +
+         'c3QgU2VydmVyMQ0wCwYDVQQDEwR0ZXN0MB4XDTEwMDcxMzE3MjAzN1oXDTMwMDcw\r\n' +
+         'ODE3MjAzN1owfzELMAkGA1UEBhMCVVMxETAPBgNVBAgTCFZpcmdpbmlhMRMwEQYD\r\n' +
+         'VQQHEwpCbGFja3NidXJnMR0wGwYDVQQKExREaWdpdGFsIEJhemFhciwgSW5jLjEa\r\n' +
+         'MBgGA1UECxMRRm9yZ2UgVGVzdCBTZXJ2ZXIxDTALBgNVBAMTBHRlc3QwggEiMA0G\r\n' +
+         'CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCm/FobjqK8CVP/Xbnpyhf1tpoyaiFf\r\n' +
+         'ShUOmlWqL5rLe0Q0dDR/Zur+sLMUv/1T4wOfFkjjxvZ0Sk5NIjK3Wy2UA41a+M3J\r\n' +
+         'RTbCFrg4ujsovFaD4CDmV7Rek0qJB3m5Gp7hgu5vfL/v+WrwxnQObNq+IrTMSA15\r\n' +
+         'cO4LzNIPj9K1LN2dB+ucT7xTQFHAfvLLgLlCLiberoabF4rEhgTMTbmMtFVKSt+P\r\n' +
+         'xgQIYPnhw1WuAvE9hFesRQFdfARLqIZk92FeHkgtHv9BAunktJemcidbowTCTBaM\r\n' +
+         '/njcgi1Tei/LFkph/FCVyGER0pekJNHX626bAQSLo/srsWfmcll9rK6bAgMBAAGj\r\n' +
+         'geYwgeMwHQYDVR0OBBYEFCau5k6jxezjULlLuo/liswJlBF8MIGzBgNVHSMEgasw\r\n' +
+         'gaiAFCau5k6jxezjULlLuo/liswJlBF8oYGEpIGBMH8xCzAJBgNVBAYTAlVTMREw\r\n' +
+         'DwYDVQQIEwhWaXJnaW5pYTETMBEGA1UEBxMKQmxhY2tzYnVyZzEdMBsGA1UEChMU\r\n' +
+         'RGlnaXRhbCBCYXphYXIsIEluYy4xGjAYBgNVBAsTEUZvcmdlIFRlc3QgU2VydmVy\r\n' +
+         'MQ0wCwYDVQQDEwR0ZXN0ggkAm6PQCMRady4wDAYDVR0TBAUwAwEB/zANBgkqhkiG\r\n' +
+         '9w0BAQUFAAOCAQEAnP/2mzFWaoGx6+KAfY8pcgnF48IoyKPx5cAQyzpMo+uRwrln\r\n' +
+         'INcDGwNx6p6rkjFbK27TME9ReCk+xQuVGaKOtqErKECXWDtD+0M35noyaOwWIFu2\r\n' +
+         '7gPZ0uGJ1n9ZMe/S9yZmmusaIrc66rX4o+fslUlH0g3SrH7yf83M8aOC2pEyCsG0\r\n' +
+         'mNNfwSFWfmu+1GMRHXJQ/qT8qBX8ZPhzRY2BAS6vr+eh3gwXR6yXLA8Xm1+e+iDU\r\n' +
+         'gGTQoYkixDIL2nhvd4AFFlE977BiE+0sMS1eJKUUbQ36MLAWb5oOZKHrphEvqMKA\r\n' +
+         'eGDO3qoDqB5TkZC3x38DXBDvAZ01d9s0fvveag==\r\n' +
+         '-----END CERTIFICATE-----';
+      
+      // local aliases
+      var net = window.forge.net;
+      var tls = window.forge.tls;
+      var http = window.forge.http;
+      var util = window.forge.util;
+
+      var client;
+      
+      function client_init(primed)
+      {
+         try
+         {
+            var sp = net.createSocketPool({
+               flashId: 'socketPool',
+               policyPort: 19945,
+               msie: false
+            });
+            client = http.createClient({
+               //url: 'https://localhost:4433',
+               url: 'https://' + window.location.host,
+               socketPool: sp,
+               connections: 10,
+               caCerts: [certificatePem],
+               // optional cipher suites in order of preference
+               cipherSuites: [
+                  tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+                  tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+               verify: function(c, verified, depth, certs)
+               {
+                  forge.log.debug(cat,
+                     'TLS certificate ' + depth + ' verified', verified);
+                  // Note: change to always true to test verifying without cert
+                  //return verified;
+                  // FIXME: temporarily accept any cert to allow hitting any bpe
+                  if(verified !== true)
+                  {
+                     forge.log.warning(cat, 
+                        'Certificate NOT verified. Ignored for test.');
+                  }
+                  return true;
+               },
+               primeTlsSockets: primed
+            });
+            document.getElementById('feedback').innerHTML =
+               'http client created';
+         }
+         catch(ex)
+         {
+            forge.log.error(cat, ex);
+         }
+         
+         return false;
+      }
+      
+      function client_cleanup()
+      {
+         var sp = client.socketPool;
+         client.destroy();
+         sp.destroy();
+         document.getElementById('feedback').innerHTML =
+            'http client cleaned up';
+         return false;
+      }
+
+      function client_send()
+      {
+         /*
+         var request = http.createRequest({
+            method: 'POST',
+            path: '/',
+            body: 'echo=foo',
+            headers: [{'Content-Type': 'application/x-www-form-urlencoded'}]
+         });
+         */
+         var request = http.createRequest({
+            method: 'GET',
+            path: '/'
+         });
+         
+         client.send({
+            request: request,
+            connected: function(e)
+            {
+               forge.log.debug(cat, 'connected', e);
+            },
+            headerReady: function(e)
+            {
+               forge.log.debug(cat, 'header ready', e);
+            },
+            bodyReady: function(e)
+            {
+               forge.log.debug(cat, 'body ready', e);
+
+               // FIXME: current test server doesn't seem to handle keep-alive
+               // correctly, so close connection 
+               e.socket.close();
+            },
+            error: function(e)
+            {
+               forge.log.error(cat, 'error', e);
+            }
+         });
+         document.getElementById('feedback').innerHTML =
+            'http request sent';
+         return false;
+      }
+
+      function client_send_10()
+      {
+         for(var i = 0; i < 10; ++i)
+         {
+            client_send();
+         }
+         return false;
+      }
+
+      function client_stress()
+      {
+         for(var i = 0; i < 10; ++i)
+         {
+            setTimeout(function()
+            {
+               for(var i = 0; i < 10; ++i)
+               {
+                  client_send();
+               }
+            }, 0);
+         }
+         return false;
+      }
+
+      function client_cookies()
+      {
+         var cookie =
+         {
+            name: 'test-cookie',
+            value: 'test-value',
+            maxAge: -1,
+            secure: true,
+            path: '/'
+         };
+         client.setCookie(cookie);
+         forge.log.debug(cat, 'cookie', client.getCookie('test-cookie'));
+      }
+
+      function client_clear_cookies()
+      {
+         client.clearCookies();
+      }
+      
+      function websocket_test()
+      {
+         // create certificate
+         var cn = 'client';
+         console.log(
+            'Generating 512-bit key-pair and certificate for \"' + cn + '\".');
+         var keys = forge.pki.rsa.generateKeyPair(512);
+         console.log('key-pair created.');
+
+         var cert = forge.pki.createCertificate();
+         cert.serialNumber = '01';
+         cert.validity.notBefore = new Date();
+         cert.validity.notAfter = new Date();
+         cert.validity.notAfter.setFullYear(
+            cert.validity.notBefore.getFullYear() + 1);
+         var attrs = [{
+            name: 'commonName',
+            value: cn
+         }, {
+            name: 'countryName',
+            value: 'US'
+         }, {
+            shortName: 'ST',
+            value: 'Virginia'
+         }, {
+            name: 'localityName',
+            value: 'Blacksburg'
+         }, {
+            name: 'organizationName',
+            value: 'Test'
+         }, {
+            shortName: 'OU',
+            value: 'Test'
+         }];
+         cert.setSubject(attrs);
+         cert.setIssuer(attrs);
+         cert.setExtensions([{
+            name: 'basicConstraints',
+            cA: true
+         }, {
+            name: 'keyUsage',
+            keyCertSign: true,
+            digitalSignature: true,
+            nonRepudiation: true,
+            keyEncipherment: true,
+            dataEncipherment: true
+         }, {
+            name: 'subjectAltName',
+            altNames: [{
+               type: 6, // URI
+               value: 'http://myuri.com/webid#me'
+            }]
+         }]);
+         // FIXME: add subjectKeyIdentifier extension
+         // FIXME: add authorityKeyIdentifier extension
+         cert.publicKey = keys.publicKey;
+         
+         // self-sign certificate
+         cert.sign(keys.privateKey);
+         
+         // save cert and private key in PEM format
+         cert = forge.pki.certificateToPem(cert);
+         privateKey = forge.pki.privateKeyToPem(keys.privateKey);
+         console.log('certificate created for \"' + cn + '\": \n' + cert);
+
+         // create websocket
+         var ws = new WebSocket('ws://localhost:8080');
+         console.log('created websocket', ws);
+
+         // create TLS client
+         var success = false;
+         var tls = forge.tls.createConnection(
+         {
+            server: false,
+            caStore: [],
+            sessionCache: {},
+            // supported cipher suites in order of preference
+            cipherSuites: [
+               forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+               forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+            virtualHost: 'server',
+            verify: function(c, verified, depth, certs)
+            {
+               console.log(
+                  'TLS Client verifying certificate w/CN: \"' +
+                  certs[0].subject.getField('CN').value +
+                  '\", verified: ' + verified + '...');
+               // accept any certificate from the server for this test
+               return true;
+            },
+            connected: function(c)
+            {
+               console.log('Client connected...');
+               
+               // send message to server
+               setTimeout(function()
+               {
+                  c.prepare('Hello Server');
+               }, 1);
+            },
+            getCertificate: function(c, hint)
+            {
+               console.log('Client getting certificate ...');
+               return cert;
+            },
+            getPrivateKey: function(c, cert)
+            {
+               return privateKey;
+            },
+            tlsDataReady: function(c)
+            {
+               // send base64-encoded TLS data to server
+               ws.send(forge.util.encode64(c.tlsData.getBytes()));
+            },
+            dataReady: function(c)
+            {
+               var response = c.data.getBytes();
+               console.log('Client received \"' + response + '\"');
+               success = (response === 'Hello Client');
+               c.close();
+            },
+            closed: function(c)
+            {
+               console.log('Client disconnected.');
+               if(success)
+               {
+                  console.log('PASS');
+               }
+               else
+               {
+                  console.log('FAIL');
+               }
+            },
+            error: function(c, error)
+            {
+               console.log('Client error: ' + error.message);
+            }
+         });
+
+         ws.onopen = function(evt)
+         {
+            console.log('websocket connected');
+
+            // do TLS handshake
+            tls.handshake();
+         };
+         ws.onmessage = function(evt)
+         {
+            // base64-decode data and process it
+            tls.process(forge.util.decode64(evt.data));
+         };
+         ws.onclose = function(evt)
+         {
+            console.log('websocket closed');
+         };
+      }
+      
+      //]]>
+      </script>
+   </head>
+   <body>
+      <div class="nav"><a href="index.html">Forge Tests</a> / TLS</div>
+
+      <div class="header">
+         <h1>TLS Test</h1>
+      </div>
+
+      <div class="content">
+
+      <!-- div used to hold the flash socket pool implemenation -->
+      <div id="socketPool">
+         <p>Could not load the flash SocketPool.</p>
+      </div>
+
+      <fieldset class="section">
+         <ul>
+           <li>Use the controls below to test the HTTP client over TLS.</li>
+           <li>You currently need a JavaScript console to view the output.</li>
+           <li>This test connects to a TLS server so you must have one running. The easiest way to run this test is to start the test server with --tls and load this page over HTTPS.</li>
+         </ul>
+      </fieldset>
+
+      <fieldset class="section">
+      <legend>Controls</legend>
+      <div id="controls">
+         <button id="init" onclick="javascript:return client_init(false);">init</button>
+         <button id="init_primed" onclick="javascript:return client_init(true);">init primed</button>
+         <button id="cleanup" onclick="javascript:return client_cleanup();">cleanup</button>
+         <button id="send" onclick="javascript:return client_send();">send</button>
+         <button id="send10" onclick="javascript:return client_send_10();">send 10</button>
+         <button id="stress" onclick="javascript:return client_stress();">stress</button>
+         <button id="client_cookies" onclick="javascript:return client_cookies();">cookies</button>
+         <button id="clear_cookies" onclick="javascript:return client_clear_cookies();">clear cookies</button>
+         <button id="websocket" onclick="javascript:return websocket_test();">websocket test</button>
+         <button id="websocket-webid" onclick="javascript:return websocket_webid('localhost', 8080);">websocket webid test</button>
+      </div>
+      </fieldset>
+
+      <fieldset class="section">
+      <legend>Feedback</legend>
+      <p>Feedback from the flash SocketPool:</p>
+      <div id="feedback">
+      None
+      </div>
+      </fieldset>
+
+      </div>
+   </body>
+</html>
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/webid.html b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/webid.html
new file mode 100644
index 0000000..8c8d795
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/webid.html
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+   <head>
+      <title>Web ID Test</title>
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js"></script>
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
+      <script type="text/javascript" src="forge/debug.js"></script>
+      <script type="text/javascript" src="forge/util.js"></script>
+      <script type="text/javascript" src="forge/log.js"></script>
+      <script type="text/javascript" src="forge/task.js"></script>
+      <script type="text/javascript" src="forge/socket.js"></script>
+      <script type="text/javascript" src="forge/md5.js"></script>
+      <script type="text/javascript" src="forge/sha1.js"></script>
+      <script type="text/javascript" src="forge/hmac.js"></script>
+      <script type="text/javascript" src="forge/aes.js"></script>
+      <script type="text/javascript" src="forge/pem.js"></script>
+      <script type="text/javascript" src="forge/asn1.js"></script>
+      <script type="text/javascript" src="forge/jsbn.js"></script>
+      <script type="text/javascript" src="forge/prng.js"></script>
+      <script type="text/javascript" src="forge/random.js"></script>
+      <script type="text/javascript" src="forge/oids.js"></script>
+      <script type="text/javascript" src="forge/rsa.js"></script>
+      <script type="text/javascript" src="forge/pbe.js"></script>
+      <script type="text/javascript" src="forge/x509.js"></script>
+      <script type="text/javascript" src="forge/pki.js"></script>
+      <script type="text/javascript" src="forge/tls.js"></script>
+      <script type="text/javascript" src="forge/aesCipherSuites.js"></script>
+      <script type="text/javascript" src="forge/tlssocket.js"></script>
+      <script type="text/javascript" src="forge/http.js"></script>
+      <script type="text/javascript" src="forge/xhr.js"></script>
+      <script type="text/javascript" src="webid.js"></script>
+
+      <link type="text/css" rel="stylesheet" media="all" href="screen.css" />
+      <style type="text/css">
+         .ready { color: inherit; background: inherit; }
+         .testing { color: black; background: yellow; }
+         .pass{ color: white; background: green; }
+         .fail{ color: white; background: red; }
+      </style>
+   </head>
+<body>
+<div class="nav"><a href="index.html">Forge Tests</a> / Web ID</div>
+
+<div class="header">
+   <h1>Web ID Tests</h1>
+</div>
+
+<div class="content">
+
+<!-- div used to hold the flash socket pool implementation -->
+<div id="socketPool">
+   <p>Could not load the flash SocketPool.</p>
+</div>
+
+<fieldset class="section">
+   <ul>
+     <li>Use the controls below to test Web ID.</li>
+     <li>Use 512 bits or less on slower JavaScript implementations.</li>
+   </ul>
+</fieldset>
+
+<fieldset class="section">
+<legend>Control</legend>
+   <table>
+      <tr>
+         <td rowspan="3"><button id="create">Create Web ID</button></td>
+         <td>Bits</td>
+         <td><input id="bits" size=8 value="1024"/></td>
+      </tr>
+      <tr>
+         <td>URI</td>
+         <td><input id="uri" size=60 value="http://localhost/dataspace/person/myname#this"/></td>
+      </tr>
+      <tr>
+         <td>Common Name</td>
+         <td><input id="commonName" size=20 value="mycert"/></td>
+      </tr>
+      <tr>
+         <td><button id="show">Show Web IDs</button></td>
+         <td>&nbsp;</td>
+         <td>&nbsp;</td>
+      </tr>
+      <tr>
+         <td><button id="clear">Delete Web IDs</button></td>
+         <td>&nbsp;</td>
+         <td>&nbsp;</td>
+      </tr>
+      <tr>
+         <td><button id="authenticate">Authenticate using Web ID</button></td>
+         <td>URI</td>
+         <td><input id="webid" size=60 value="http://localhost/dataspace/person/myname#this"/></td>
+      </tr>
+   </table>
+</fieldset>
+
+<fieldset class="section">
+<legend>Progress</legend>
+   <div id="progress"></div>
+</fieldset>
+
+<fieldset class="section">
+<legend>Available Web IDs</legend>
+<div id="webids"></div>
+</fieldset>
+
+</div>
+</body>
+</html>
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/webid.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/webid.js
new file mode 100644
index 0000000..7c07ab9
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/webid.js
@@ -0,0 +1,313 @@
+/**
+ * Forge Web ID Tests
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010 Digital Bazaar, Inc. All rights reserved.
+ */
+(function($)
+{
+   // load flash socket pool
+   window.forge.socketPool = {};
+   window.forge.socketPool.ready = function()
+   {
+      // init forge xhr
+      forge.xhr.init({
+         flashId: 'socketPool',
+         policyPort: 19945,
+         msie: $.browser.msie,
+         connections: 10,
+         caCerts: [],
+         verify: function(c, verified, depth, certs)
+         {
+            // don't care about cert verification for test
+            return true;
+         }
+      });
+   };
+   swfobject.embedSWF(
+      'forge/SocketPool.swf', 'socketPool', '0', '0', '9.0.0',
+      false, {}, {allowscriptaccess: 'always'}, {});
+})(jQuery);
+
+jQuery(function($)
+{
+   var cat = 'forge.tests.webid';
+   
+   // local alias
+   var forge = window.forge;
+
+   $('#create').click(function()
+   {
+      var bits = $('#bits')[0].value;
+      var uri = $('#uri')[0].value;
+      var commonName = $('#commonName')[0].value;
+      forge.log.debug(cat, 'generating ' + bits +
+         '-bit RSA key-pair and certificate...');
+      
+      // function to create cert
+      var createCert = function(keys)
+      {
+         try
+         {
+            var cert = forge.pki.createCertificate();
+            cert.serialNumber = '01';
+            cert.validity.notBefore = new Date();
+            cert.validity.notAfter = new Date();
+            cert.validity.notAfter.setFullYear(
+               cert.validity.notBefore.getFullYear() + 1);
+            var attrs = [{
+               name: 'commonName',
+               value: commonName
+            }, {
+               name: 'countryName',
+               value: 'US'
+            }, {
+               shortName: 'ST',
+               value: 'Virginia'
+            }, {
+               name: 'localityName',
+               value: 'Blacksburg'
+            }, {
+               name: 'organizationName',
+               value: 'Test'
+            }, {
+               shortName: 'OU',
+               value: 'Test'
+            }];
+            cert.setSubject(attrs);
+            cert.setIssuer(attrs);
+            cert.setExtensions([{
+               name: 'basicConstraints',
+               cA: true
+            }, {
+               name: 'keyUsage',
+               keyCertSign: true,
+               digitalSignature: true,
+               nonRepudiation: true,
+               keyEncipherment: true,
+               dataEncipherment: true
+            }, {
+               name: 'subjectAltName',
+               altNames: [{
+                  type: 6, // URI
+                  value: uri
+               }]
+            }]);
+            // FIXME: add subjectKeyIdentifier extension
+            // FIXME: add authorityKeyIdentifier extension
+            cert.publicKey = keys.publicKey;
+            
+            // self-sign certificate
+            cert.sign(keys.privateKey);
+            
+            // verify certificate
+            forge.log.debug('verified', cert.verify(cert));
+            
+            forge.log.debug(cat, 'certificate:', cert);
+            //forge.log.debug(cat, 
+            //   forge.asn1.prettyPrint(forge.pki.certificateToAsn1(cert)));
+            var keyPem = forge.pki.privateKeyToPem(keys.privateKey);
+            var certPem = forge.pki.certificateToPem(cert);
+            forge.log.debug(cat, keyPem);
+            forge.log.debug(cat, certPem);
+            
+            forge.log.debug(cat, 'storing certificate and private key...');
+            try
+            {
+               // get flash API
+               var flashApi = document.getElementById('socketPool');
+               
+               // get web ids collection
+               var webids = forge.util.getItem(
+                  flashApi, 'forge.test.webid', 'webids');
+               webids = webids || {};
+               
+               // add web id
+               webids[uri] = {
+                  certificate: certPem,
+                  privateKey: keyPem
+               };
+               
+               // update web ids collection
+               forge.util.setItem(
+                  flashApi, 'forge.test.webid', 'webids', webids);
+               
+               forge.log.debug(cat, 'certificate and private key stored');
+               $('#show').click();
+            }
+            catch(ex)
+            {
+               forge.log.error(cat, ex);
+            }
+         }
+         catch(ex)
+         {
+            forge.log.error(cat, ex, ex.message ? ex.message : '');
+         }
+      };
+      
+      // create key-generation state and function to step algorithm
+      var progress = $('#progress');
+      progress.html('Generating ' + bits + '-bit key-pair.');
+      var state = forge.pki.rsa.createKeyPairGenerationState(bits);
+      var kgTime = +new Date();
+      var step = function()
+      {
+         // step key-generation
+         if(!forge.pki.rsa.stepKeyPairGenerationState(state, 1000))
+         {
+            progress.html(progress.html() + '.');
+            setTimeout(step, 1);
+         }
+         // key-generation complete
+         else
+         {
+            kgTime = +new Date() - kgTime;
+            forge.log.debug(cat, 'Total key-gen time', kgTime + 'ms');
+            createCert(state.keys);
+            progress.html(progress.html() + 'done. Time=' + kgTime + 'ms');
+         }
+      };
+      
+      // run key-gen algorithm
+      setTimeout(step, 0);
+   });
+
+   $('#show').click(function()
+   {  
+      forge.log.debug(cat, 'get stored web IDs...');
+      try
+      {
+         // get flash API
+         var flashApi = document.getElementById('socketPool');
+         
+         // get web ids collection
+         var webids = forge.util.getItem(
+            flashApi, 'forge.test.webid', 'webids');
+         webids = webids || {};
+         
+         var html = '<ul>';
+         var webid, cert;
+         for(var key in webids)
+         {
+            webid = webids[key];
+            cert = forge.pki.certificateFromPem(webid.certificate);
+            html += '<li><p>' + key + '</p>';
+            
+            var attr;
+            for(var n = 0; n < cert.subject.attributes.length; ++n)
+            {
+               attr = cert.subject.attributes[n];
+               html += attr.name + ': ' + attr.value + '<br/>';
+            }
+            
+            //html += '<p>' + webid.certificate + '</p></li>';
+            html += '</li>';
+         }
+         if(html === '<ul>')
+         {
+            html = 'None';
+         }
+         else
+         {
+            html += '</ul>';
+         }
+         
+         $('#webids').html(html);
+         
+         forge.log.debug(cat, 'Web IDs retrieved');
+      }
+      catch(ex)
+      {
+         forge.log.error(cat, ex);
+      }
+   });
+   
+   $('#clear').click(function()
+   {  
+      forge.log.debug(cat, 'clearing all web IDs...');
+      try
+      {
+         // get flash API
+         var flashApi = document.getElementById('socketPool');
+         forge.util.clearItems(flashApi, 'forge.test.webid');
+         $('#webids').html('None');
+         forge.log.debug(cat, 'Web IDs cleared');
+      }
+      catch(ex)
+      {
+         forge.log.error(cat, ex);
+      }
+   });
+   
+   $('#authenticate').click(function()
+   {
+      forge.log.debug(cat, 'doing Web ID authentication...');
+      
+      try
+      {
+         // get flash API
+         var flashApi = document.getElementById('socketPool');
+         
+         // get web ids collection
+         var webids = forge.util.getItem(
+            flashApi, 'forge.test.webid', 'webids');
+         webids = webids || {};
+         
+         var uri = $('#webid')[0].value;
+         var webid = webids[uri];
+         
+         $.ajax(
+         {
+            type: 'GET',
+            url: '/',
+            success: function(data, textStatus, xhr)
+            {
+               if(data !== '')
+               {
+                  forge.log.debug(cat, 'authentication completed');
+                  forge.log.debug(cat, data);
+               }
+               else
+               {
+                  forge.log.error(cat, 'authentication failed');
+               }
+            },
+            error: function(xhr, textStatus, errorThrown)
+            {
+               forge.log.error(cat, 'authentication failed');
+            },
+            xhr: function()
+            {
+               return forge.xhr.create({
+                  // FIXME: change URL
+                  url: 'https://localhost:4433',
+                  connections: 10,
+                  caCerts: [],
+                  verify: function(c, verified, depth, certs)
+                  {
+                     // don't care about cert verification for test
+                     return true;
+                  },
+                  getCertificate: function(c)
+                  {
+                     //forge.log.debug(cat, 'using cert', webid.certificate);
+                     return webid.certificate;
+                  },
+                  getPrivateKey: function(c)
+                  {
+                     //forge.log.debug(cat,
+                     //   'using private key', webid.privateKey);
+                     return webid.privateKey;
+                  }
+               });
+            }
+         });      
+      }
+      catch(ex)
+      {
+         forge.log.error(cat, ex);
+      }
+   });
+});
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/ws-webid.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/ws-webid.js
new file mode 100644
index 0000000..2ce5816
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/ws-webid.js
@@ -0,0 +1,132 @@
+var websocket_webid = function(host, port)
+{
+   var cat = 'ws';
+   
+   // TODO: get private key and certificate from local storage
+   var privateKey =
+   '-----BEGIN RSA PRIVATE KEY-----\r\n' +
+'MIICXAIBAAKBgQCTmE8QLARsC57Z1OrOaLM6AS3fn70N7BvlU7z7yw8UpcJA/jOl\r\n' +
+'NWu7eS9uzuckdVZ9FE0+x3DRvhtDI6K+18dcrUWtl5ADWXcs1QS3/7bGh7IybFyY\r\n' +
+'0xT4VzLHcx6K4PNmfkjAQdyOz/EsuRqZ/ngIQ2tdHdkkzdQPECbTvFeG2wIDAQAB\r\n' +
+'AoGAds3l7l2QHaxo7GzfqNBMXEdwto2tLxS8C6eQ+pkkBXm72HcF+Vj75AcTMD2p\r\n' +
+'fwZYXQxHdV4yqRI+fZeku7uTA/3yBAAvNobbEN5jtHnq0ZTO/HO8HuHkKrCvD8c3\r\n' +
+'0rJV6lNIuaARI9jZFf6HVchW3PMjKUpYhTs/sFhRxmsMpTkCQQDu8TPzXRmN1aw8\r\n' +
+'tSI2Nyn8QUy9bw/12tlVaZIhrcVCiJl7JHGqSCowTqZlwmJIjd4W0zWjTvS7tEeO\r\n' +
+'FaZHtP8lAkEAniGvm8S9zyzmhWRRIuU6EE2dtTbeAa5aSOK3nBaaNu2cHUxWle+J\r\n' +
+'8lE4uequ9wqDG1AfOLobPmHReccmOI6N/wJAIP/I1/RkohT/a4bsiaZGsyLlkUf0\r\n' +
+'YVTvLP+ege44zv6Ei+A1nnnG8dL64hTdc/27zVUwFDTEUeQM+c99nmudzQJBAApY\r\n' +
+'qeTHOqQTjAGuTqC53tKyQV9Z96yke8PJEbpkwDJX2Z8RH5kv0xbHua5wbII9bdab\r\n' +
+'p29OvfmW7N3K6fVJXoECQHK8FDC0i8v1Ui8FoBmt+Z1c1+/9TCEE0abUQ6rfOUbm\r\n' +
+'XHMMac/n4qDs0OoCjR4u46dpoK+WN7zcg56tToFPVow=\r\n' +
+'-----END RSA PRIVATE KEY-----';
+  var certificate =
+  '-----BEGIN CERTIFICATE-----\r\n' +
+'MIICgDCCAemgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMRMwEQYDVQQDEwpKb2hu\r\n' +
+'IFNtaXRoMRMwEQYDVQQHEwpCbGFja3NidXJnMREwDwYDVQQIEwhWaXJnaW5pYTEL\r\n' +
+'MAkGA1UEBhMCVVMxDDAKBgNVBAoTA0ZvbzAeFw0xMDExMjYxNzUxMzJaFw0xMTEx\r\n' +
+'MjYxNzUxMzJaMFgxEzARBgNVBAMTCkpvaG4gU21pdGgxEzARBgNVBAcTCkJsYWNr\r\n' +
+'c2J1cmcxETAPBgNVBAgTCFZpcmdpbmlhMQswCQYDVQQGEwJVUzEMMAoGA1UEChMD\r\n' +
+'Rm9vMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCTmE8QLARsC57Z1OrOaLM6\r\n' +
+'AS3fn70N7BvlU7z7yw8UpcJA/jOlNWu7eS9uzuckdVZ9FE0+x3DRvhtDI6K+18dc\r\n' +
+'rUWtl5ADWXcs1QS3/7bGh7IybFyY0xT4VzLHcx6K4PNmfkjAQdyOz/EsuRqZ/ngI\r\n' +
+'Q2tdHdkkzdQPECbTvFeG2wIDAQABo1owWDAMBgNVHRMEBTADAQH/MAsGA1UdDwQE\r\n' +
+'AwIC9DA7BgNVHREENDAyhjBodHRwOi8vd2ViaWQuZGlnaXRhbGJhemFhci5jb20v\r\n' +
+'aWRzLzE1MzQ1NzI2NDcjbWUwDQYJKoZIhvcNAQEFBQADgYEAPNm8albI4w6anynw\r\n' +
+'XE/+00sCVks9BbgTcIpRqZPGqSuTRwoYW35isNLDqFqIUdVREMvFrEn3nOlOyKi0\r\n' +
+'29G8JtLHFSXZsqf38Zou/bGAhtEH1AVEbM2bRtEnG8IW24jL8hiciz4htxmjnkHN\r\n' +
+'JnQ8SQtUSWplGnz0vMFEOv6JbnI=\r\n' +
+'-----END CERTIFICATE-----';
+
+   // create websocket
+   var ws = new WebSocket('ws://' + host + ':' + port);
+   forge.log.debug(cat, 'Created WebSocket', ws);
+
+   // create TLS client
+   var success = false;
+   var tls = forge.tls.createConnection(
+   {
+      server: false,
+      caStore: [],
+      sessionCache: {},
+      // supported cipher suites in order of preference
+      cipherSuites: [
+         forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+         forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+      virtualHost: host,
+      verify: function(c, verified, depth, certs)
+      {
+         forge.log.debug(cat, 
+            'TLS Client verifying certificate w/CN: \"' +
+            certs[0].subject.getField('CN').value + '\"');
+         // accept any certificate from the server for this test
+         return true;
+      },
+      connected: function(c)
+      {
+         forge.log.debug(cat, 'Client connected');
+      },
+      getCertificate: function(c, hint)
+      {
+         forge.log.debug(cat, 'Client using client-certificate');
+         return certificate;
+      },
+      getPrivateKey: function(c, cert)
+      {
+         return privateKey;
+      },
+      tlsDataReady: function(c)
+      {
+         // send base64-encoded TLS data to server
+         ws.send(forge.util.encode64(c.tlsData.getBytes()));
+      },
+      dataReady: function(c)
+      {
+         var response = c.data.getBytes();
+         forge.log.debug(cat, 'Client received \"' + response + '\"');
+         try
+         {
+            response = JSON.parse(response);
+            success = response.success;
+            
+            // TODO: call window.authenticate on response json, just like
+            // w/flash version
+         }
+         catch(ex) {}
+         c.close();
+      },
+      closed: function(c)
+      {
+         forge.log.debug(cat, 'Client disconnected');
+         if(success)
+         {
+            forge.log.debug(cat, 'PASS');
+         }
+         else
+         {
+            forge.log.debug(cat, 'FAIL');
+         }
+      },
+      error: function(c, error)
+      {
+         forge.log.debug(cat, 'Client error: ' + error.message);
+      }
+   });
+
+   ws.onopen = function(evt)
+   {
+      forge.log.debug(cat, 'WebSocket connected');
+
+      // do TLS handshake
+      tls.handshake();
+   };
+   ws.onmessage = function(evt)
+   {
+      // base64-decode data and process it
+      tls.process(forge.util.decode64(evt.data));
+   };
+   ws.onclose = function(evt)
+   {
+      forge.log.debug(cat, 'WebSocket closed');
+   };
+};
+
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/ws.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/ws.js
new file mode 100644
index 0000000..ba0b39d
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/ws.js
@@ -0,0 +1,237 @@
+// Github: http://github.com/ncr/node.ws.js
+// Compatible with node v0.1.91
+// Author: Jacek Becela
+// Contributors:
+//   Michael Stillwell  http://github.com/ithinkihaveacat
+//   Nick Chapman       http://github.com/nchapman
+//   Dmitriy Shalashov  http://github.com/skaurus
+//   Johan Dahlberg
+//   Andreas Kompanez
+//   Samuel Cyprian		http://github.com/samcyp
+// License: MIT
+// Based on: http://github.com/Guille/node.websocket.js
+
+function nano(template, data) {
+  return template.replace(/\{([\w\.]*)}/g, function (str, key) {
+    var keys = key.split("."), value = data[keys.shift()];
+    keys.forEach(function (key) { value = value[key];});
+    return value;
+  });
+}
+
+function pack(num) {
+  var result = '';
+  result += String.fromCharCode(num >> 24 & 0xFF);
+  result += String.fromCharCode(num >> 16 & 0xFF);
+  result += String.fromCharCode(num >> 8 & 0xFF);
+  result += String.fromCharCode(num & 0xFF);
+  return result;
+}
+
+var sys  = require("sys"),
+  net    = require("net"),
+  crypto = require("crypto"),
+  requiredHeaders = {
+    'get': /^GET (\/[^\s]*)/,
+    'upgrade': /^WebSocket$/,
+    'connection': /^Upgrade$/,
+    'host': /^(.+)$/,
+    'origin': /^(.+)$/
+  },
+  handshakeTemplate75 = [
+    'HTTP/1.1 101 Web Socket Protocol Handshake', 
+    'Upgrade: WebSocket', 
+    'Connection: Upgrade',
+    'WebSocket-Origin: {origin}',
+    'WebSocket-Location: {protocol}://{host}{resource}',
+    '',
+    ''
+  ].join("\r\n"),
+  handshakeTemplate76 = [
+    'HTTP/1.1 101 WebSocket Protocol Handshake', // note a diff here
+    'Upgrade: WebSocket',
+    'Connection: Upgrade',
+    'Sec-WebSocket-Origin: {origin}',
+    'Sec-WebSocket-Location: {protocol}://{host}{resource}',
+    '',
+    '{data}'
+  ].join("\r\n"),
+  flashPolicy = '<cross-domain-policy><allow-access-from domain="*" to-ports="*" /></cross-domain-policy>';
+
+
+
+exports.createSecureServer = function (websocketListener, credentials, options) {
+	if (!options) options = {};
+	options.secure = credentials;
+	return this.createServer(websocketListener, options);
+};
+
+exports.createServer = function (websocketListener, options) {
+  if (!options) options = {};
+  if (!options.flashPolicy) options.flashPolicy = flashPolicy;
+  // The value should be a crypto credentials
+  if (!options.secure) options.secure = null;
+
+  return net.createServer(function (socket) {
+	//Secure WebSockets
+	var wsProtocol = 'ws';
+	if(options.secure) {
+	  wsProtocol = 'wss';
+	  socket.setSecure(options.secure);
+	}
+    socket.setTimeout(0);
+    socket.setNoDelay(true);
+    socket.setKeepAlive(true, 0);
+
+    var emitter = new process.EventEmitter(),
+      handshaked = false,
+      buffer = "";
+      
+    function handle(data) {
+      buffer += data;
+      
+      var chunks = buffer.split("\ufffd"),
+        count = chunks.length - 1; // last is "" or a partial packet
+        
+      for(var i = 0; i < count; i++) {
+        var chunk = chunks[i];
+        if(chunk[0] == "\u0000") {
+          emitter.emit("data", chunk.slice(1));
+        } else {
+          socket.end();
+          return;
+        }
+      }
+      
+      buffer = chunks[count];
+    }
+
+    function handshake(data) {
+      var _headers = data.split("\r\n");
+
+      if ( /<policy-file-request.*>/.exec(_headers[0]) ) {
+        socket.write( options.flashPolicy );
+        socket.end();
+        return;
+      }
+
+      // go to more convenient hash form
+      var headers = {}, upgradeHead, len = _headers.length;
+      if ( _headers[0].match(/^GET /) ) {
+        headers["get"] = _headers[0];
+      } else {
+        socket.end();
+        return;
+      }
+      if ( _headers[ _headers.length - 1 ] ) {
+        upgradeHead = _headers[ _headers.length - 1 ];
+        len--;
+      }
+      while (--len) { // _headers[0] will be skipped
+        var header = _headers[len];
+        if (!header) continue;
+
+        var split = header.split(": ", 2); // second parameter actually seems to not work in node
+        headers[ split[0].toLowerCase() ] = split[1];
+      }
+
+      // check if we have all needed headers and fetch data from them
+      var data = {}, match;
+      for (var header in requiredHeaders) {
+        //           regexp                          actual header value
+        if ( match = requiredHeaders[ header ].exec( headers[header] ) ) {
+          data[header] = match;
+        } else {
+          socket.end();
+          return;
+        }
+      }
+
+      // draft auto-sensing
+      if ( headers["sec-websocket-key1"] && headers["sec-websocket-key2"] && upgradeHead ) { // 76
+        var strkey1 = headers["sec-websocket-key1"]
+          , strkey2 = headers["sec-websocket-key2"]
+
+          , numkey1 = parseInt(strkey1.replace(/[^\d]/g, ""), 10)
+          , numkey2 = parseInt(strkey2.replace(/[^\d]/g, ""), 10)
+
+          , spaces1 = strkey1.replace(/[^\ ]/g, "").length
+          , spaces2 = strkey2.replace(/[^\ ]/g, "").length;
+
+        if (spaces1 == 0 || spaces2 == 0 || numkey1 % spaces1 != 0 || numkey2 % spaces2 != 0) {
+          socket.end();
+          return;
+        }
+
+        var hash = crypto.createHash("md5")
+        , key1 = pack(parseInt(numkey1/spaces1))
+        , key2 = pack(parseInt(numkey2/spaces2));
+        
+        hash.update(key1);
+        hash.update(key2);
+        hash.update(upgradeHead);
+
+        socket.write(nano(handshakeTemplate76, {
+          protocol: wsProtocol,
+          resource: data.get[1],
+          host:     data.host[1],
+          origin:   data.origin[1],
+          data:     hash.digest("binary")
+        }), "binary");
+
+      } else { // 75
+        socket.write(nano(handshakeTemplate75, {
+          protocol: wsProtocol,
+          resource: data.get[1],
+          host:     data.host[1],
+          origin:   data.origin[1]
+        }));
+
+      }
+
+      handshaked = true;
+      emitter.emit("connect", data.get[1]);
+    }
+
+    socket.addListener("data", function (data) {
+      if(handshaked) {
+        handle(data.toString("utf8"));
+      } else {
+        handshake(data.toString("binary")); // because of draft76 handshakes
+      }
+    }).addListener("end", function () {
+      socket.end();
+    }).addListener("close", function () {
+      if (handshaked) { // don't emit close from policy-requests
+        emitter.emit("close");
+      }
+    }).addListener("error", function (exception) {
+      if (emitter.listeners("error").length > 0) {
+        emitter.emit("error", exception);
+      } else {
+        throw exception;
+      }
+    });
+
+    emitter.remoteAddress = socket.remoteAddress;
+    
+    emitter.write = function (data) {
+      try {
+        socket.write('\u0000', 'binary');
+        socket.write(data, 'utf8');
+        socket.write('\uffff', 'binary');
+      } catch(e) { 
+        // Socket not open for writing, 
+        // should get "close" event just before.
+        socket.end();
+      }
+    };
+    
+    emitter.end = function () {
+      socket.end();
+    };
+    
+    websocketListener(emitter); // emits: "connect", "data", "close", provides: write(data), end()
+  });
+};
+
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/xhr.html b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/xhr.html
new file mode 100644
index 0000000..aaa721c
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/xhr.html
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+   <head>
+      <title>Forge XmlHttpRequest Test</title>
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js"></script>
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
+      <script type="text/javascript" src="forge/debug.js"></script>
+      <script type="text/javascript" src="forge/util.js"></script>
+      <script type="text/javascript" src="forge/log.js"></script>
+      <script type="text/javascript" src="forge/task.js"></script>
+      <script type="text/javascript" src="forge/socket.js"></script>
+      <script type="text/javascript" src="forge/md5.js"></script>
+      <script type="text/javascript" src="forge/sha1.js"></script>
+      <script type="text/javascript" src="forge/hmac.js"></script>
+      <script type="text/javascript" src="forge/aes.js"></script>
+      <script type="text/javascript" src="forge/pem.js"></script>
+      <script type="text/javascript" src="forge/asn1.js"></script>
+      <script type="text/javascript" src="forge/jsbn.js"></script>
+      <script type="text/javascript" src="forge/prng.js"></script>
+      <script type="text/javascript" src="forge/random.js"></script>
+      <script type="text/javascript" src="forge/oids.js"></script>
+      <script type="text/javascript" src="forge/rsa.js"></script>
+      <script type="text/javascript" src="forge/pbe.js"></script>
+      <script type="text/javascript" src="forge/x509.js"></script>
+      <script type="text/javascript" src="forge/pki.js"></script>
+      <script type="text/javascript" src="forge/tls.js"></script>
+      <script type="text/javascript" src="forge/aesCipherSuites.js"></script>
+      <script type="text/javascript" src="forge/tlssocket.js"></script>
+      <script type="text/javascript" src="forge/http.js"></script>
+      <script type="text/javascript" src="forge/xhr.js"></script>
+      <script type="text/javascript" src="xhr.js"></script>
+      
+      <link type="text/css" rel="stylesheet" media="all" href="screen.css" />
+      <style type="text/css">
+         .ready { color: inherit; background: inherit; }
+         .testing { color: black; background: yellow; }
+         .pass{ color: white; background: green; }
+         .fail{ color: white; background: red; }
+      </style>
+   </head>
+<body>
+<div class="nav"><a href="index.html">Forge Tests</a> / XHR</div>
+
+<div class="header">
+   <h1>XmlHttpRequest Tests</h1>
+</div>
+
+<div class="content">
+
+<!-- div used to hold the flash socket pool implemenation -->
+<div id="socketPool">
+   <p>Could not load the flash SocketPool.</p>
+</div>
+
+<fieldset class="section">
+   <ul>
+     <li>Use the controls below to test the XHR wrapper.</li>
+   </ul>
+</fieldset>
+
+<fieldset class="section">
+<legend>Control</legend>
+   <button id="start">Start</button>
+   <button id="reset">Reset</button>
+   <br/>
+   <input id="scroll" type="checkbox" checked="checked" />Scroll Tests
+   <br/>
+   <button id="stress">Stress</button>
+</fieldset>
+
+<fieldset class="section">
+<legend>Progress</legend>
+Status: <span id="status">?</span><br/>
+Pass: <span id="pass">?</span>/<span id="total">?</span><br/>
+Fail: <span id="fail">?</span>
+</fieldset>
+
+<fieldset class="section">
+<legend>Tests</legend>
+<div id="tests"></div>
+</fieldset>
+
+</div>
+</body>
+</html>
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/xhr.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/xhr.js
new file mode 100644
index 0000000..78f91ad
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/node-forge/tests/xhr.js
@@ -0,0 +1,590 @@
+/**
+ * Forge XmlHttpRequest Test
+ *
+ * @author Dave Longley
+ * @author David I. Lehn <dlehn@digitalbazaar.com>
+ *
+ * Copyright (c) 2009-2010 Digital Bazaar, Inc. All rights reserved.
+ */
+(function($)
+{
+   // load flash socket pool
+   window.forge.socketPool = {};
+   window.forge.socketPool.ready = function()
+   {
+      // init forge xhr
+      forge.xhr.init({
+         flashId: 'socketPool',
+         policyPort: 19945,
+         msie: $.browser.msie,
+         connections: 10,
+         caCerts: [],
+         verify: function(c, verified, depth, certs)
+         {
+            // don't care about cert verification for test
+            return true;
+         }
+      });
+   };
+   swfobject.embedSWF(
+      'forge/SocketPool.swf', 'socketPool', '0', '0', '9.0.0',
+      false, {}, {allowscriptaccess: 'always'}, {});
+})(jQuery);
+
+jQuery(function($)
+{
+   var cat = 'forge.tests.xhr';
+
+   var tests = [];
+   var passed = 0;
+   var failed = 0;
+
+   var init = function() {
+      passed = failed = 0;
+      $('.ready,.testing,.pass,.fail')
+         .removeClass('ready testing pass fail');
+      $('#status')
+         .text('Ready.')
+         .addClass('ready');
+      $('#total').text(tests.length);
+      $('#pass').text(passed);
+      $('#fail').text(failed);
+      $('.expect').empty();
+      $('.result').empty();
+      $('.time').empty();
+      $('.timePer').empty();
+      $('#start').removeAttr('disabled');
+   };
+
+   var start = function()
+   {
+      $('#start').attr('disabled', 'disabled');
+      // meta! use tasks to run the task tests
+      forge.task.start({
+         type: 'test',
+         run: function(task) {
+            task.next('starting', function(task) {
+               forge.log.debug(cat, 'start');
+               $('#status')
+                  .text('Testing...')
+                  .addClass('testing')
+                  .removeClass('idle');
+            });
+            $.each(tests, function(i, test) {
+               task.next('test', function(task) {
+                  var title = $('li:first', test.container);
+                  if($('#scroll:checked').length === 1)
+                  {
+                     $('html,body').animate({scrollTop: title.offset().top});
+                  }
+                  title.addClass('testing');
+                  test.run(task, test);
+               });
+               task.next('test', function(task) {
+                  $('li:first', test.container).removeClass('testing');
+               });
+            });
+            task.next('success', function(task) {
+               forge.log.debug(cat, 'done');
+               if(failed === 0) {
+                  $('#status')
+                     .text('PASS')
+                     .addClass('pass')
+                     .removeClass('testing');
+               } else {
+                  // FIXME: should just be hitting failure() below
+                  $('#status')
+                     .text('FAIL')
+                     .addClass('fail')
+                     .removeClass('testing');
+               }
+            });
+         },
+         failure: function() {
+            $('#status')
+               .text('FAIL')
+               .addClass('fail')
+               .removeClass('testing');
+         }
+      });
+   };
+
+   $('#start').click(function() {
+      start();
+   });
+
+   $('#reset').click(function() {
+      init();
+   });
+
+   var stressStats =
+   {
+      sent: 0,
+      success: 0,
+      error: 0
+   };
+   var stressStatsMessage = function()
+   {
+      return 'received:' + (stressStats.success + stressStats.error) + '/' +
+         stressStats.sent + ' errors:' + stressStats.error;
+   };
+
+   $('#stress').click(function() {
+      for(var i = 1; i <= 100; ++i)
+      {
+         (function(seqnum)
+         {
+            setTimeout(function()
+            {
+               $.ajax(
+               {
+                  type: 'GET',
+                  url: '/result.txt?seq=' + seqnum,
+                  beforeSend: function(xhr)
+                  {
+                     ++stressStats.sent;
+                     xhr.setRequestHeader('Connection', 'close');
+                  },
+                  success: function(data, textStatus, xhr)
+                  {
+                     ++stressStats.success;
+                     forge.log.debug(cat, 'xhr connection completed' +
+                        ' seq:' + seqnum +
+                        ' datalen:' + data.length + ' ' +
+                        stressStatsMessage());
+                  },
+                  error: function(xhr, textStatus, errorThrown)
+                  {
+                     ++stressStats.error;
+                     forge.log.error(cat, 'xhr connection failed' +
+                        ' seq:' + seqnum + ' ' +
+                        stressStatsMessage(), arguments);
+                  },
+                  xhr: forge.xhr.create
+               });
+            }, 0);
+         })(i);
+      }
+      return false;
+   });
+
+   /**
+    * Creates a simple XMLHttpRequest wrapper. For testing.
+    */
+   var createWrapper = function()
+   {
+      var UNSENT = 0;
+      var OPENED = 1;
+      var HEADERS_RECEIVED = 2;
+      var LOADING = 3;
+      var DONE = 4;
+
+      var toWrap = new XMLHttpRequest();
+
+      // create xhr wrapper object
+      var xhr =
+      {
+         // FIXME: an EventListener
+         onreadystatechange: null,
+         // FIXME: readonly
+         readyState: UNSENT,
+         // FIXME: a string
+         responseText: null,
+         // FIXME: a document
+         responseXML: null,
+         // FIXME: readonly, returns the HTTP status code
+         status: 0,
+         // FIXME: readonly, returns the HTTP status message
+         statusText: null,
+
+         // FIXME: async, user, and password are optional
+         open: function(method, url, async, user, password)
+         {
+            toWrap.open(method, url, async, user, password);
+         },
+
+         setRequestHeader: function(header, value)
+         {
+            toWrap.setRequestHeader(header, value);
+         },
+
+         // FIXME: data can be a string or a document
+         send: function(data)
+         {
+            toWrap.send(data);
+         },
+
+         abort: function()
+         {
+            toWrap.abort();
+            toWrap.onreadystatechange = null;
+            toWrap = null;
+         },
+
+         // FIXME: return all response headers as a string
+         getAllResponseHeaders: function()
+         {
+            return toWrap.getAllResponseHeaders();
+         },
+
+         // FIXME: return header field value
+         getResponseHeader: function(header)
+         {
+            return toWrap.getResponseHeader(header);
+         }
+      };
+
+      toWrap.onreadystatechange = function()
+      {
+         // copy attributes
+         xhr.readyState = toWrap.readyState;
+         xhr.responseText = toWrap.responseText;
+         xhr.responseXML = toWrap.responseXML;
+
+         if(toWrap.readyState == HEADERS_RECEIVED)
+         {
+            xhr.status = toWrap.status;
+            xhr.statusText = toWrap.statusText;
+         }
+
+         if(xhr.onreadystatechange)
+         {
+            //forge.log.debug(cat, 'wrapper orsc', toWrap);
+            xhr.onreadystatechange();
+         }
+      };
+
+      return xhr;
+   };
+
+   var addTest = function(name, run)
+   {
+      var container = $('<ul><li>Test ' + name + '</li><ul/></ul>');
+      var expect = $('<li>Expect: <span class="expect"/></li>');
+      var result = $('<li>Result: <span class="result"/></li>');
+      var time = $('<li>Time: <span class="time"/></li>');
+      var timePer = $('<li>Time Per Iteration: <span class="timePer"/></li>');
+      $('ul', container)
+         .append(expect)
+         .append(result)
+         .append(time)
+         .append(timePer);
+      $('#tests').append(container);
+      var test = {
+         container: container,
+         startTime: null,
+         run: function(task, test) {
+            test.startTime = new Date();
+            run(task, test);
+         },
+         expect: $('span', expect),
+         result: $('span', result),
+         check: function() {
+            var e = test.expect.text();
+            var r = test.result.text();
+            (e == r) ? test.pass() : test.fail();
+         },
+         pass: function(iterations) {
+            var dt = new Date() - test.startTime;
+            if(!iterations)
+            {
+               iterations = 1;
+            }
+            var dti = (dt / iterations);
+            passed += 1;
+            $('#pass').text(passed);
+            $('li:first', container).addClass('pass');
+            $('span.time', container).html(dt + 'ms');
+            $('span.timePer', container).html(dti + 'ms');
+         },
+         fail: function(iterations) {
+            var dt = new Date() - test.startTime;
+            if(!iterations)
+            {
+               iterations = 1;
+            }
+            var dti = (dt / iterations);
+            failed += 1;
+            $('#fail').text(failed);
+            $('li:first', container).addClass('fail');
+            $('span.time', container).html(dt + 'ms');
+            $('span.timePer', container).html(dti + 'ms');
+         }
+      };
+      tests.push(test);
+   };
+
+   addTest('builtin xhr', function(task, test)
+   {
+      task.block();
+
+      $.ajax(
+      {
+         type: 'GET',
+         url: '/result.txt',
+         success: function(data)
+         {
+            test.expect.html('expected result');
+            test.result.html(data);
+            task.unblock();
+         },
+         error: function()
+         {
+            task.fail();
+         }
+      });
+
+      task.next(function(task)
+      {
+         test.pass();
+      });
+   });
+
+   addTest('builtin xhr (10 serial)', function(task, test)
+   {
+      var N = 10;
+      for(var i = 0; i < N; i++)
+      {
+         task.next(function(task)
+         {
+            task.parent.block();
+
+            $.ajax(
+            {
+               type: 'GET',
+               url: '/result.txt',
+               success: function(data, textStatus)
+               {
+                  test.result.append('.');
+                  task.parent.unblock();
+               },
+               error: function(xhr, textStatus, errorThrown)
+               {
+                  task.fail(N);
+               }
+            });
+         });
+      }
+
+      task.next(function(task)
+      {
+         test.pass(N);
+      });
+   });
+
+   addTest('builtin xhr (10 parallel)', function(task, test)
+   {
+      var N = 10;
+      task.block(N);
+      for(var i = 0; i < N; i++)
+      {
+         $.ajax(
+         {
+            type: 'GET',
+            url: '/result.txt',
+            success: function(data, textStatus)
+            {
+               test.result.append('.');
+               task.unblock();
+            },
+            error: function(xhr, textStatus, errorThrown)
+            {
+               task.fail(N);
+            }
+         });
+      }
+
+      task.next(function(task)
+      {
+         test.pass(N);
+      });
+   });
+
+   // test only works with non-IE
+   if(!$.browser.msie)
+   {
+      addTest('generic wrapper xhr', function(task, test)
+      {
+         task.block();
+
+         $.ajax(
+         {
+            type: 'GET',
+            url: '/result.txt',
+            success: function(data)
+            {
+               test.expect.html('expected result');
+               test.result.html(data);
+               task.unblock();
+            },
+            error: function()
+            {
+               task.fail();
+            },
+            xhr: createWrapper
+         });
+
+         task.next(function(task)
+         {
+            test.pass();
+         });
+      });
+
+      addTest('generic wrapper xhr (10 serial)', function(task, test)
+      {
+         var N = 10;
+         for(var i = 0; i < N; i++)
+         {
+            task.next(function(task)
+            {
+               task.parent.block();
+
+               $.ajax(
+               {
+                  type: 'GET',
+                  url: '/result.txt',
+                  success: function(data, textStatus)
+                  {
+                     test.result.append('.');
+                     task.parent.unblock();
+                  },
+                  error: function(xhr, textStatus, errorThrown)
+                  {
+                     task.fail(N);
+                  },
+                  xhr: createWrapper
+               });
+            });
+         }
+
+         task.next(function(task)
+         {
+            test.pass(N);
+         });
+      });
+
+      addTest('generic wrapper xhr (10 parallel)', function(task, test)
+      {
+         var N = 10;
+         task.block(N);
+         for(var i = 0; i < N; i++)
+         {
+            $.ajax(
+            {
+               type: 'GET',
+               url: '/result.txt',
+               success: function(data, textStatus)
+               {
+                  test.result.append('.');
+                  task.unblock();
+               },
+               error: function(xhr, textStatus, errorThrown)
+               {
+                  task.fail(N);
+               },
+               xhr: createWrapper
+            });
+         }
+
+         task.next(function(task)
+         {
+            test.pass(N);
+         });
+      });
+   }
+
+   for(var i = 0; i < 3; i++) {
+   addTest('TLS xhr ' + i, function(task, test)
+   {
+      task.block();
+
+      $.ajax(
+      {
+         type: 'GET',
+         url: '/result.txt',
+         success: function(data, textStatus, xhr)
+         {
+            test.expect.html('expected result');
+            test.result.html(data);
+            task.unblock();
+         },
+         error: function(xhr, textStatus, errorThrown)
+         {
+            task.fail();
+         },
+         xhr: forge.xhr.create
+      });
+
+      task.next(function(task)
+      {
+         test.pass();
+      });
+   });
+   }
+
+   addTest('TLS xhr (10 serial)', function(task, test)
+   {
+      var N = 10;
+      for(var i = 0; i < N; i++)
+      {
+         task.next(function(task)
+         {
+            task.parent.block();
+
+            $.ajax(
+            {
+               type: 'GET',
+               url: '/result.txt',
+               success: function(data, textStatus, xhr)
+               {
+                  test.result.append('.');
+                  task.parent.unblock();
+               },
+               error: function(xhr, textStatus, errorThrown)
+               {
+                  task.fail(N);
+               },
+               xhr: forge.xhr.create
+            });
+         });
+      }
+
+      task.next(function(task)
+      {
+         test.pass(N);
+      });
+   });
+
+   addTest('TLS xhr (10 parallel) ' +
+      '(hit "Reset" then "Start" to speed up - uses SSL session cache)',
+      function(task, test)
+   {
+      var N = 10;
+      task.block(N);
+      for(var i = 0; i < N; i++)
+      {
+         $.ajax(
+         {
+            type: 'GET',
+            url: '/result.txt',
+            success: function(data, textStatus, xhr)
+            {
+               test.result.append('.');
+               task.unblock();
+            },
+            error: function(xhr, textStatus, errorThrown)
+            {
+               task.fail(N);
+            },
+            xhr: forge.xhr.create
+         });
+      }
+
+      task.next(function(task)
+      {
+         test.pass(N);
+      });
+   });
+
+   init();
+});
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/.npmignore b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/.npmignore
new file mode 100644
index 0000000..13abef4
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/.npmignore
@@ -0,0 +1,3 @@
+node_modules
+node_modules/*
+npm_debug.log
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/.travis.yml b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/.travis.yml
new file mode 100644
index 0000000..dad2273
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/.travis.yml
@@ -0,0 +1,4 @@
+language: node_js
+node_js:
+  - 0.8
+  - "0.10"
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/LICENCE b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/LICENCE
new file mode 100644
index 0000000..171dd97
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/LICENCE
@@ -0,0 +1,22 @@
+Copyright (c) 2011 Dominic Tarr
+
+Permission is hereby granted, free of charge, 
+to any person obtaining a copy of this software and 
+associated documentation files (the "Software"), to 
+deal in the Software without restriction, including 
+without limitation the rights to use, copy, modify, 
+merge, publish, distribute, sublicense, and/or sell 
+copies of the Software, and to permit persons to whom 
+the Software is furnished to do so, 
+subject to the following conditions:
+
+The above copyright notice and this permission notice 
+shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 
+ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/examples/pretty.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/examples/pretty.js
new file mode 100644
index 0000000..2e89131
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/examples/pretty.js
@@ -0,0 +1,26 @@
+
+var inspect = require('util').inspect
+var es      = require('event-stream')     //load event-stream
+var split   = require('../')
+
+if(!module.parent) {
+  es.pipe(                            //pipe joins streams together
+    process.openStdin(),              //open stdin
+    split(),                       //split stream to break on newlines
+    es.map(function (data, callback) {//turn this async function into a stream
+      var j 
+      try {
+        j = JSON.parse(data)          //try to parse input into json
+      } catch (err) {
+        return callback(null, data)   //if it fails just pass it anyway
+      }
+      callback(null, inspect(j))      //render it nicely
+    }),
+    process.stdout                    // pipe it to stdout !
+    )
+  }
+  
+// run this
+// 
+// curl -sS registry.npmjs.org/event-stream | node pretty.js 
+//
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/index.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/index.js
new file mode 100644
index 0000000..ca57e0f
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/index.js
@@ -0,0 +1,59 @@
+//filter will reemit the data if cb(err,pass) pass is truthy
+
+// reduce is more tricky
+// maybe we want to group the reductions or emit progress updates occasionally
+// the most basic reduce just emits one 'data' event after it has recieved 'end'
+
+
+var through = require('through')
+var Decoder = require('string_decoder').StringDecoder
+
+module.exports = split
+
+//TODO pass in a function to map across the lines.
+
+function split (matcher, mapper) {
+  var decoder = new Decoder()
+  var soFar = ''
+  if('function' === typeof matcher)
+    mapper = matcher, matcher = null
+  if (!matcher)
+    matcher = /\r?\n/
+
+  function emit(stream, piece) {
+    if(mapper) {
+      try {
+        piece = mapper(piece)
+      }
+      catch (err) {
+        return stream.emit('error', err)
+      }
+      if('undefined' !== typeof piece)
+        stream.queue(piece)
+    }
+    else
+      stream.queue(piece)
+  }
+
+  function next (stream, buffer) { 
+    var pieces = (soFar + buffer).split(matcher)
+    soFar = pieces.pop()
+
+    for (var i = 0; i < pieces.length; i++) {
+      var piece = pieces[i]
+      emit(stream, piece)
+    }
+  }
+
+  return through(function (b) {
+    next(this, decoder.write(b))
+  },
+  function () {
+    if(decoder.end) 
+      next(this, decoder.end())
+    if(soFar != null)
+      emit(this, soFar)
+    this.queue(null)
+  })
+}
+
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/node_modules/through/.travis.yml b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/node_modules/through/.travis.yml
new file mode 100644
index 0000000..c693a93
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/node_modules/through/.travis.yml
@@ -0,0 +1,5 @@
+language: node_js
+node_js:
+  - 0.6
+  - 0.8
+  - "0.10"
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/node_modules/through/LICENSE.APACHE2 b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/node_modules/through/LICENSE.APACHE2
new file mode 100644
index 0000000..6366c04
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/node_modules/through/LICENSE.APACHE2
@@ -0,0 +1,15 @@
+Apache License, Version 2.0
+
+Copyright (c) 2011 Dominic Tarr
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/node_modules/through/LICENSE.MIT b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/node_modules/through/LICENSE.MIT
new file mode 100644
index 0000000..6eafbd7
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/node_modules/through/LICENSE.MIT
@@ -0,0 +1,24 @@
+The MIT License
+
+Copyright (c) 2011 Dominic Tarr
+
+Permission is hereby granted, free of charge, 
+to any person obtaining a copy of this software and 
+associated documentation files (the "Software"), to 
+deal in the Software without restriction, including 
+without limitation the rights to use, copy, modify, 
+merge, publish, distribute, sublicense, and/or sell 
+copies of the Software, and to permit persons to whom 
+the Software is furnished to do so, 
+subject to the following conditions:
+
+The above copyright notice and this permission notice 
+shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 
+ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/node_modules/through/index.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/node_modules/through/index.js
new file mode 100644
index 0000000..7b935bf
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/node_modules/through/index.js
@@ -0,0 +1,108 @@
+var Stream = require('stream')
+
+// through
+//
+// a stream that does nothing but re-emit the input.
+// useful for aggregating a series of changing but not ending streams into one stream)
+
+exports = module.exports = through
+through.through = through
+
+//create a readable writable stream.
+
+function through (write, end, opts) {
+  write = write || function (data) { this.queue(data) }
+  end = end || function () { this.queue(null) }
+
+  var ended = false, destroyed = false, buffer = [], _ended = false
+  var stream = new Stream()
+  stream.readable = stream.writable = true
+  stream.paused = false
+
+//  stream.autoPause   = !(opts && opts.autoPause   === false)
+  stream.autoDestroy = !(opts && opts.autoDestroy === false)
+
+  stream.write = function (data) {
+    write.call(this, data)
+    return !stream.paused
+  }
+
+  function drain() {
+    while(buffer.length && !stream.paused) {
+      var data = buffer.shift()
+      if(null === data)
+        return stream.emit('end')
+      else
+        stream.emit('data', data)
+    }
+  }
+
+  stream.queue = stream.push = function (data) {
+//    console.error(ended)
+    if(_ended) return stream
+    if(data == null) _ended = true
+    buffer.push(data)
+    drain()
+    return stream
+  }
+
+  //this will be registered as the first 'end' listener
+  //must call destroy next tick, to make sure we're after any
+  //stream piped from here.
+  //this is only a problem if end is not emitted synchronously.
+  //a nicer way to do this is to make sure this is the last listener for 'end'
+
+  stream.on('end', function () {
+    stream.readable = false
+    if(!stream.writable && stream.autoDestroy)
+      process.nextTick(function () {
+        stream.destroy()
+      })
+  })
+
+  function _end () {
+    stream.writable = false
+    end.call(stream)
+    if(!stream.readable && stream.autoDestroy)
+      stream.destroy()
+  }
+
+  stream.end = function (data) {
+    if(ended) return
+    ended = true
+    if(arguments.length) stream.write(data)
+    _end() // will emit or queue
+    return stream
+  }
+
+  stream.destroy = function () {
+    if(destroyed) return
+    destroyed = true
+    ended = true
+    buffer.length = 0
+    stream.writable = stream.readable = false
+    stream.emit('close')
+    return stream
+  }
+
+  stream.pause = function () {
+    if(stream.paused) return
+    stream.paused = true
+    return stream
+  }
+
+  stream.resume = function () {
+    if(stream.paused) {
+      stream.paused = false
+      stream.emit('resume')
+    }
+    drain()
+    //may have become paused again,
+    //as drain emits 'data'.
+    if(!stream.paused)
+      stream.emit('drain')
+    return stream
+  }
+  return stream
+}
+
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/node_modules/through/package.json b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/node_modules/through/package.json
new file mode 100644
index 0000000..fe9fbdb
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/node_modules/through/package.json
@@ -0,0 +1,65 @@
+{
+  "name": "through",
+  "version": "2.3.6",
+  "description": "simplified stream construction",
+  "main": "index.js",
+  "scripts": {
+    "test": "set -e; for t in test/*.js; do node $t; done"
+  },
+  "devDependencies": {
+    "stream-spec": "~0.3.5",
+    "tape": "~2.3.2",
+    "from": "~0.1.3"
+  },
+  "keywords": [
+    "stream",
+    "streams",
+    "user-streams",
+    "pipe"
+  ],
+  "author": {
+    "name": "Dominic Tarr",
+    "email": "dominic.tarr@gmail.com",
+    "url": "dominictarr.com"
+  },
+  "license": "MIT",
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/dominictarr/through.git"
+  },
+  "homepage": "http://github.com/dominictarr/through",
+  "testling": {
+    "browsers": [
+      "ie/8..latest",
+      "ff/15..latest",
+      "chrome/20..latest",
+      "safari/5.1..latest"
+    ],
+    "files": "test/*.js"
+  },
+  "gitHead": "19ed9b7e84efe7c3e3c8be80f29390b1620e13c0",
+  "bugs": {
+    "url": "https://github.com/dominictarr/through/issues"
+  },
+  "_id": "through@2.3.6",
+  "_shasum": "26681c0f524671021d4e29df7c36bce2d0ecf2e8",
+  "_from": "through@2",
+  "_npmVersion": "1.4.26",
+  "_npmUser": {
+    "name": "dominictarr",
+    "email": "dominic.tarr@gmail.com"
+  },
+  "maintainers": [
+    {
+      "name": "dominictarr",
+      "email": "dominic.tarr@gmail.com"
+    }
+  ],
+  "dist": {
+    "shasum": "26681c0f524671021d4e29df7c36bce2d0ecf2e8",
+    "tarball": "http://registry.npmjs.org/through/-/through-2.3.6.tgz"
+  },
+  "directories": {},
+  "_resolved": "https://registry.npmjs.org/through/-/through-2.3.6.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/node_modules/through/readme.markdown b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/node_modules/through/readme.markdown
new file mode 100644
index 0000000..cb34c81
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/node_modules/through/readme.markdown
@@ -0,0 +1,64 @@
+#through
+
+[![build status](https://secure.travis-ci.org/dominictarr/through.png)](http://travis-ci.org/dominictarr/through)
+[![testling badge](https://ci.testling.com/dominictarr/through.png)](https://ci.testling.com/dominictarr/through)
+
+Easy way to create a `Stream` that is both `readable` and `writable`. 
+
+* Pass in optional `write` and `end` methods.
+* `through` takes care of pause/resume logic if you use `this.queue(data)` instead of `this.emit('data', data)`.
+* Use `this.pause()` and `this.resume()` to manage flow.
+* Check `this.paused` to see current flow state. (`write` always returns `!this.paused`).
+
+This function is the basis for most of the synchronous streams in 
+[event-stream](http://github.com/dominictarr/event-stream).
+
+``` js
+var through = require('through')
+
+through(function write(data) {
+    this.queue(data) //data *must* not be null
+  },
+  function end () { //optional
+    this.queue(null)
+  })
+```
+
+Or, can also be used _without_ buffering on pause, use `this.emit('data', data)`,
+and this.emit('end')
+
+``` js
+var through = require('through')
+
+through(function write(data) {
+    this.emit('data', data)
+    //this.pause() 
+  },
+  function end () { //optional
+    this.emit('end')
+  })
+```
+
+## Extended Options
+
+You will probably not need these 99% of the time.
+
+### autoDestroy=false
+
+By default, `through` emits close when the writable
+and readable side of the stream has ended.
+If that is not desired, set `autoDestroy=false`.
+
+``` js
+var through = require('through')
+
+//like this
+var ts = through(write, end, {autoDestroy: false})
+//or like this
+var ts = through(write, end)
+ts.autoDestroy = false
+```
+
+## License
+
+MIT / Apache2
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/node_modules/through/test/async.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/node_modules/through/test/async.js
new file mode 100644
index 0000000..46bdbae
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/node_modules/through/test/async.js
@@ -0,0 +1,28 @@
+var from = require('from')
+var through = require('../')
+
+var tape = require('tape')
+
+tape('simple async example', function (t) {
+ 
+  var n = 0, expected = [1,2,3,4,5], actual = []
+  from(expected)
+  .pipe(through(function(data) {
+    this.pause()
+    n ++
+    setTimeout(function(){
+      console.log('pushing data', data)
+      this.push(data)
+      this.resume()
+    }.bind(this), 300)
+  })).pipe(through(function(data) {
+    console.log('pushing data second time', data);
+    this.push(data)
+  })).on('data', function (d) {
+    actual.push(d)
+  }).on('end', function() {
+    t.deepEqual(actual, expected)
+    t.end()
+  })
+
+})
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/node_modules/through/test/auto-destroy.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/node_modules/through/test/auto-destroy.js
new file mode 100644
index 0000000..9a8fd00
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/node_modules/through/test/auto-destroy.js
@@ -0,0 +1,30 @@
+var test = require('tape')
+var through = require('../')
+
+// must emit end before close.
+
+test('end before close', function (assert) {
+  var ts = through()
+  ts.autoDestroy = false
+  var ended = false, closed = false
+
+  ts.on('end', function () {
+    assert.ok(!closed)
+    ended = true
+  })
+  ts.on('close', function () {
+    assert.ok(ended)
+    closed = true
+  })
+
+  ts.write(1)
+  ts.write(2)
+  ts.write(3)
+  ts.end()
+  assert.ok(ended)
+  assert.notOk(closed)
+  ts.destroy()
+  assert.ok(closed)
+  assert.end()
+})
+
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/node_modules/through/test/buffering.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/node_modules/through/test/buffering.js
new file mode 100644
index 0000000..b0084bf
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/node_modules/through/test/buffering.js
@@ -0,0 +1,71 @@
+var test = require('tape')
+var through = require('../')
+
+// must emit end before close.
+
+test('buffering', function(assert) {
+  var ts = through(function (data) {
+    this.queue(data)
+  }, function () {
+    this.queue(null)
+  })
+
+  var ended = false,  actual = []
+
+  ts.on('data', actual.push.bind(actual))
+  ts.on('end', function () {
+    ended = true
+  })
+
+  ts.write(1)
+  ts.write(2)
+  ts.write(3)
+  assert.deepEqual(actual, [1, 2, 3])
+  ts.pause()
+  ts.write(4)
+  ts.write(5)
+  ts.write(6)
+  assert.deepEqual(actual, [1, 2, 3])
+  ts.resume()
+  assert.deepEqual(actual, [1, 2, 3, 4, 5, 6])
+  ts.pause()
+  ts.end()
+  assert.ok(!ended)
+  ts.resume()
+  assert.ok(ended)
+  assert.end()
+})
+
+test('buffering has data in queue, when ends', function (assert) {
+
+  /*
+   * If stream ends while paused with data in the queue,
+   * stream should still emit end after all data is written
+   * on resume.
+   */
+
+  var ts = through(function (data) {
+    this.queue(data)
+  }, function () {
+    this.queue(null)
+  })
+
+  var ended = false,  actual = []
+
+  ts.on('data', actual.push.bind(actual))
+  ts.on('end', function () {
+    ended = true
+  })
+
+  ts.pause()
+  ts.write(1)
+  ts.write(2)
+  ts.write(3)
+  ts.end()
+  assert.deepEqual(actual, [], 'no data written yet, still paused')
+  assert.ok(!ended, 'end not emitted yet, still paused')
+  ts.resume()
+  assert.deepEqual(actual, [1, 2, 3], 'resumed, all data should be delivered')
+  assert.ok(ended, 'end should be emitted once all data was delivered')
+  assert.end();
+})
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/node_modules/through/test/end.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/node_modules/through/test/end.js
new file mode 100644
index 0000000..fa113f5
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/node_modules/through/test/end.js
@@ -0,0 +1,45 @@
+var test = require('tape')
+var through = require('../')
+
+// must emit end before close.
+
+test('end before close', function (assert) {
+  var ts = through()
+  var ended = false, closed = false
+
+  ts.on('end', function () {
+    assert.ok(!closed)
+    ended = true
+  })
+  ts.on('close', function () {
+    assert.ok(ended)
+    closed = true
+  })
+
+  ts.write(1)
+  ts.write(2)
+  ts.write(3)
+  ts.end()
+  assert.ok(ended)
+  assert.ok(closed)
+  assert.end()
+})
+
+test('end only once', function (t) {
+
+  var ts = through()
+  var ended = false, closed = false
+
+  ts.on('end', function () {
+    t.equal(ended, false)
+    ended = true
+  })
+
+  ts.queue(null)
+  ts.queue(null)
+  ts.queue(null)
+
+  ts.resume()
+
+  t.end()
+})
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/node_modules/through/test/index.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/node_modules/through/test/index.js
new file mode 100644
index 0000000..8d1517e
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/node_modules/through/test/index.js
@@ -0,0 +1,117 @@
+
+var test = require('tape')
+var spec = require('stream-spec')
+var through = require('../')
+
+/*
+  I'm using these two functions, and not streams and pipe
+  so there is less to break. if this test fails it must be
+  the implementation of _through_
+*/
+
+function write(array, stream) {
+  array = array.slice()
+  function next() {
+    while(array.length)
+      if(stream.write(array.shift()) === false)
+        return stream.once('drain', next)
+    
+    stream.end()
+  }
+
+  next()
+}
+
+function read(stream, callback) {
+  var actual = []
+  stream.on('data', function (data) {
+    actual.push(data)
+  })
+  stream.once('end', function () {
+    callback(null, actual)
+  })
+  stream.once('error', function (err) {
+    callback(err)
+  })
+}
+
+test('simple defaults', function(assert) {
+
+  var l = 1000
+    , expected = []
+
+  while(l--) expected.push(l * Math.random())
+
+  var t = through()
+  var s = spec(t).through().pausable()
+
+  read(t, function (err, actual) {
+    assert.ifError(err)
+    assert.deepEqual(actual, expected)
+    assert.end()
+  })
+
+  t.on('close', s.validate)
+
+  write(expected, t)
+});
+
+test('simple functions', function(assert) {
+
+  var l = 1000
+    , expected = [] 
+
+  while(l--) expected.push(l * Math.random())
+
+  var t = through(function (data) {
+      this.emit('data', data*2)
+    }) 
+  var s = spec(t).through().pausable()
+      
+
+  read(t, function (err, actual) {
+    assert.ifError(err)
+    assert.deepEqual(actual, expected.map(function (data) {
+      return data*2
+    }))
+    assert.end()
+  })
+
+  t.on('close', s.validate)
+
+  write(expected, t)
+})
+
+test('pauses', function(assert) {
+
+  var l = 1000
+    , expected = [] 
+
+  while(l--) expected.push(l) //Math.random())
+
+  var t = through()    
+ 
+  var s = spec(t)
+      .through()
+      .pausable()
+
+  t.on('data', function () {
+    if(Math.random() > 0.1) return
+    t.pause()
+    process.nextTick(function () {
+      t.resume()
+    })
+  })
+
+  read(t, function (err, actual) {
+    assert.ifError(err)
+    assert.deepEqual(actual, expected)
+  })
+
+  t.on('close', function () {
+    s.validate()
+    assert.end()
+  })
+
+  write(expected, t)
+})
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/package.json b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/package.json
new file mode 100644
index 0000000..c726916
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/package.json
@@ -0,0 +1,39 @@
+{
+  "name": "split",
+  "version": "0.2.10",
+  "description": "split a Text Stream into a Line Stream",
+  "homepage": "http://github.com/dominictarr/split",
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/dominictarr/split.git"
+  },
+  "dependencies": {
+    "through": "2"
+  },
+  "devDependencies": {
+    "asynct": "*",
+    "it-is": "1",
+    "ubelt": "~2.9",
+    "stream-spec": "~0.2",
+    "event-stream": "~3.0.2"
+  },
+  "scripts": {
+    "test": "asynct test/"
+  },
+  "author": {
+    "name": "Dominic Tarr",
+    "email": "dominic.tarr@gmail.com",
+    "url": "http://bit.ly/dominictarr"
+  },
+  "optionalDependencies": {},
+  "engines": {
+    "node": "*"
+  },
+  "readme": "# Split (matcher)\n\n[![build status](https://secure.travis-ci.org/dominictarr/split.png)](http://travis-ci.org/dominictarr/split)\n\nBreak up a stream and reassemble it so that each line is a chunk. matcher may be a `String`, or a `RegExp` \n\nExample, read every line in a file ...\n\n``` js\n  fs.createReadStream(file)\n    .pipe(split())\n    .on('data', function (line) {\n      //each chunk now is a seperate line!\n    })\n\n```\n\n`split` takes the same arguments as `string.split` except it defaults to '/\\r?\\n/' instead of ',', and the optional `limit` paremeter is ignored.\n[String#split](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/split)\n\n# NDJ - Newline Delimited Json\n\n`split` accepts a function which transforms each line.\n\n``` js\nfs.createReadStream(file)\n  .pipe(split(JSON.parse))\n  .on('data', function (obj) {\n    //each chunk now is a a js object\n  })\n  .on('error', function (err) {\n    //syntax errors will land here\n    //note, this ends the stream.\n  })\n```\n\n# License\n\nMIT\n",
+  "readmeFilename": "readme.markdown",
+  "bugs": {
+    "url": "https://github.com/dominictarr/split/issues"
+  },
+  "_id": "split@0.2.10",
+  "_from": "split@~0.2.10"
+}
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/readme.markdown b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/readme.markdown
new file mode 100644
index 0000000..55b8ba8
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/readme.markdown
@@ -0,0 +1,39 @@
+# Split (matcher)
+
+[![build status](https://secure.travis-ci.org/dominictarr/split.png)](http://travis-ci.org/dominictarr/split)
+
+Break up a stream and reassemble it so that each line is a chunk. matcher may be a `String`, or a `RegExp` 
+
+Example, read every line in a file ...
+
+``` js
+  fs.createReadStream(file)
+    .pipe(split())
+    .on('data', function (line) {
+      //each chunk now is a seperate line!
+    })
+
+```
+
+`split` takes the same arguments as `string.split` except it defaults to '/\r?\n/' instead of ',', and the optional `limit` paremeter is ignored.
+[String#split](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/split)
+
+# NDJ - Newline Delimited Json
+
+`split` accepts a function which transforms each line.
+
+``` js
+fs.createReadStream(file)
+  .pipe(split(JSON.parse))
+  .on('data', function (obj) {
+    //each chunk now is a a js object
+  })
+  .on('error', function (err) {
+    //syntax errors will land here
+    //note, this ends the stream.
+  })
+```
+
+# License
+
+MIT
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/test/partitioned_unicode.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/test/partitioned_unicode.js
new file mode 100644
index 0000000..aff3d5d
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/test/partitioned_unicode.js
@@ -0,0 +1,34 @@
+var it = require('it-is').style('colour')
+  , split = require('..')
+
+exports ['split data with partitioned unicode character'] = function (test) {
+  var s = split(/,/g)
+    , caughtError = false
+    , rows = []
+
+  s.on('error', function (err) {
+    caughtError = true
+  })
+ 
+  s.on('data', function (row) { rows.push(row) })
+
+  var x = 'テスト試験今日とても,よい天気で'
+  unicodeData = new Buffer(x);
+
+  // partition of 日
+  piece1 = unicodeData.slice(0, 20);
+  piece2 = unicodeData.slice(20, unicodeData.length);
+
+  s.write(piece1);
+  s.write(piece2);
+
+  s.end()
+
+  it(caughtError).equal(false)
+
+  it(rows).deepEqual(['テスト試験今日とても', 'よい天気で']);
+
+  it(rows).deepEqual(x.split(','))
+
+  test.done()
+}
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/test/split.asynct.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/test/split.asynct.js
new file mode 100644
index 0000000..fb15b28
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/test/split.asynct.js
@@ -0,0 +1,85 @@
+var es = require('event-stream')
+  , it = require('it-is').style('colour')
+  , d = require('ubelt')
+  , split = require('..')
+  , join = require('path').join
+  , fs = require('fs')
+  , Stream = require('stream').Stream
+  , spec = require('stream-spec')
+
+exports ['split() works like String#split'] = function (test) {
+  var readme = join(__filename)
+    , expected = fs.readFileSync(readme, 'utf-8').split('\n')
+    , cs = split()
+    , actual = []
+    , ended = false
+    , x = spec(cs).through()
+
+  var a = new Stream ()
+  
+  a.write = function (l) {
+    actual.push(l.trim())
+  }
+  a.end = function () {
+
+      ended = true
+      expected.forEach(function (v,k) {
+        //String.split will append an empty string ''
+        //if the string ends in a split pattern.
+        //es.split doesn't which was breaking this test.
+        //clearly, appending the empty string is correct.
+        //tests are passing though. which is the current job.
+        if(v)
+          it(actual[k]).like(v)
+      })
+      //give the stream time to close
+      process.nextTick(function () {
+        test.done()
+        x.validate()
+      })
+  }
+  a.writable = true
+  
+  fs.createReadStream(readme, {flags: 'r'}).pipe(cs)
+  cs.pipe(a) 
+  
+}
+
+exports ['split() takes mapper function'] = function (test) {
+  var readme = join(__filename)
+    , expected = fs.readFileSync(readme, 'utf-8').split('\n')
+    , cs = split(function (line) { return line.toUpperCase() })
+    , actual = []
+    , ended = false
+    , x = spec(cs).through()
+
+  var a = new Stream ()
+  
+  a.write = function (l) {
+    actual.push(l.trim())
+  }
+  a.end = function () {
+
+      ended = true
+      expected.forEach(function (v,k) {
+        //String.split will append an empty string ''
+        //if the string ends in a split pattern.
+        //es.split doesn't which was breaking this test.
+        //clearly, appending the empty string is correct.
+        //tests are passing though. which is the current job.
+        if(v)
+          it(actual[k]).equal(v.trim().toUpperCase())
+      })
+      //give the stream time to close
+      process.nextTick(function () {
+        test.done()
+        x.validate()
+      })
+  }
+  a.writable = true
+  
+  fs.createReadStream(readme, {flags: 'r'}).pipe(cs)
+  cs.pipe(a) 
+  
+}
+
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/test/try_catch.asynct.js b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/test/try_catch.asynct.js
new file mode 100644
index 0000000..39e49f7
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/node_modules/split/test/try_catch.asynct.js
@@ -0,0 +1,51 @@
+var it = require('it-is').style('colour')
+  , split = require('..')
+
+exports ['emit mapper exceptions as error events'] = function (test) {
+  var s = split(JSON.parse)
+    , caughtError = false
+    , rows = []
+ 
+  s.on('error', function (err) {
+    caughtError = true
+  })
+ 
+  s.on('data', function (row) { rows.push(row) })
+
+  s.write('{"a":1}\n{"')
+  it(caughtError).equal(false)
+  it(rows).deepEqual([ { a: 1 } ])
+
+  s.write('b":2}\n{"c":}\n')
+  it(caughtError).equal(true)
+  it(rows).deepEqual([ { a: 1 }, { b: 2 } ])
+
+  s.end()
+  test.done()
+}
+
+exports ['mapper error events on trailing chunks'] = function (test) {
+  var s = split(JSON.parse)
+    , caughtError = false
+    , rows = []
+ 
+  s.on('error', function (err) {
+    caughtError = true
+  })
+ 
+  s.on('data', function (row) { rows.push(row) })
+
+  s.write('{"a":1}\n{"')
+  it(caughtError).equal(false)
+  it(rows).deepEqual([ { a: 1 } ])
+
+  s.write('b":2}\n{"c":}')
+  it(caughtError).equal(false)
+  it(rows).deepEqual([ { a: 1 }, { b: 2 } ])
+
+  s.end()
+  it(caughtError).equal(true)
+  it(rows).deepEqual([ { a: 1 }, { b: 2 } ])
+
+  test.done()
+}
diff --git a/node_modules/node-firefox-find-devices/node_modules/adbkit/package.json b/node_modules/node-firefox-find-devices/node_modules/adbkit/package.json
new file mode 100644
index 0000000..275ef4e
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/node_modules/adbkit/package.json
@@ -0,0 +1,88 @@
+{
+  "name": "adbkit",
+  "version": "2.1.6",
+  "description": "A pure Node.js client for the Android Debug Bridge.",
+  "keywords": [
+    "adb",
+    "adbkit",
+    "android",
+    "logcat",
+    "monkey"
+  ],
+  "bugs": {
+    "url": "https://github.com/CyberAgent/adbkit/issues"
+  },
+  "license": "Apache-2.0",
+  "author": {
+    "name": "CyberAgent, Inc.",
+    "email": "npm@cyberagent.co.jp",
+    "url": "http://www.cyberagent.co.jp/"
+  },
+  "main": "./index",
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/CyberAgent/adbkit.git"
+  },
+  "scripts": {
+    "postpublish": "grunt clean",
+    "prepublish": "grunt test coffee",
+    "test": "grunt test"
+  },
+  "dependencies": {
+    "adbkit-logcat": "~1.0.3",
+    "adbkit-monkey": "~1.0.1",
+    "bluebird": "~1.1.0",
+    "commander": "^2.3.0",
+    "debug": "~0.7.4",
+    "node-forge": "^0.6.12",
+    "split": "~0.2.10"
+  },
+  "devDependencies": {
+    "bench": "~0.3.5",
+    "chai": "~1.9.0",
+    "coffee-script": "~1.7.1",
+    "grunt": "~0.4.2",
+    "grunt-cli": "~0.1.13",
+    "grunt-coffeelint": "0.0.8",
+    "grunt-contrib-clean": "~0.5.0",
+    "grunt-contrib-coffee": "~0.10.0",
+    "grunt-contrib-watch": "~0.5.3",
+    "grunt-exec": "~0.4.3",
+    "grunt-jsonlint": "~1.0.4",
+    "grunt-notify": "~0.2.16",
+    "mocha": "~1.17.1",
+    "sinon": "~1.8.1",
+    "sinon-chai": "~2.5.0",
+    "coffeelint": "~1.0.8"
+  },
+  "engines": {
+    "node": ">= 0.10.4"
+  },
+  "gitHead": "c74e6a5617fb046cfa93708e5b2be0289cde20e6",
+  "homepage": "https://github.com/CyberAgent/adbkit",
+  "_id": "adbkit@2.1.6",
+  "_shasum": "10f016de49483c255b549a1d28aec79882178f2a",
+  "_from": "adbkit@^2.1.6",
+  "_npmVersion": "1.4.28",
+  "_npmUser": {
+    "name": "sorccu",
+    "email": "simo@shoqolate.com"
+  },
+  "maintainers": [
+    {
+      "name": "sorccu",
+      "email": "simo@shoqolate.com"
+    },
+    {
+      "name": "cyberagent",
+      "email": "npm@cyberagent.co.jp"
+    }
+  ],
+  "dist": {
+    "shasum": "10f016de49483c255b549a1d28aec79882178f2a",
+    "tarball": "http://registry.npmjs.org/adbkit/-/adbkit-2.1.6.tgz"
+  },
+  "directories": {},
+  "_resolved": "https://registry.npmjs.org/adbkit/-/adbkit-2.1.6.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-find-devices/package.json b/node_modules/node-firefox-find-devices/package.json
new file mode 100644
index 0000000..eca15e0
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/package.json
@@ -0,0 +1,61 @@
+{
+  "name": "node-firefox-find-devices",
+  "version": "1.0.0",
+  "description": "Find attached FirefoxOS devices using ADB",
+  "main": "index.js",
+  "dependencies": {
+    "es6-promise": "^2.0.1",
+    "adbkit": "^2.1.6"
+  },
+  "devDependencies": {
+    "gulp": "^3.8.10",
+    "gulp-jscs": "^1.3.1",
+    "gulp-jshint": "^1.9.0",
+    "gulp-json-lint": "0.0.1",
+    "gulp-nodeunit": "0.0.5",
+    "gulp-replace": "^0.5.0",
+    "jshint-stylish": "^1.0.0",
+    "mockery": "^1.4.0",
+    "node-firefox-build-tools": "^0.1.0",
+    "nodemock": "^0.3.4"
+  },
+  "scripts": {
+    "gulp": "gulp",
+    "test": "gulp test"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/mozilla/node-firefox-find-devices.git"
+  },
+  "keywords": [
+    "firefox",
+    "developer tools",
+    "b2g",
+    "firefox os",
+    "firefoxos",
+    "fxos",
+    "ports"
+  ],
+  "author": {
+    "name": "Mozilla",
+    "url": "https://mozilla.org/"
+  },
+  "contributors": [
+    {
+      "name": "Les Orchard",
+      "email": "me@lmorchard.com",
+      "url": "http://lmorchard.com"
+    }
+  ],
+  "license": "Apache 2.0",
+  "bugs": {
+    "url": "https://github.com/mozilla/node-firefox-find-devices/issues"
+  },
+  "homepage": "https://github.com/mozilla/node-firefox-find-devices",
+  "readme": "# node-firefox-find-devices [![Build Status](https://secure.travis-ci.org/mozilla/node-firefox-find-devices.png?branch=master)](http://travis-ci.org/mozilla/node-firefox-find-devices)\n\n> Find attached FirefoxOS devices using ADB.\n\n[![Install with NPM](https://nodei.co/npm/node-firefox-find-devices.png?downloads=true&stars=true)](https://nodei.co/npm/node-firefox-find-devices/)\n\nThis is part of the [node-firefox](https://github.com/mozilla/node-firefox) project.\n\nWhen Firefox OS devices are plugged in via USB, they can be found using the\nAndroid Debug Bridge. Once found, a test for the presence of Firefox OS on the\ndevice can separate them from normal Android devices.\n\n## Installation\n\n### From git\n\n```bash\ngit clone https://github.com/mozilla/node-firefox-find-devices.git\ncd node-firefox-find-devices\nnpm install\n```\n\nIf you want to update later on:\n\n```bash\ncd node-firefox-find-devices\ngit pull origin master\nnpm install\n```\n\n### npm\n\n```bash\nnpm install node-firefox-find-devices\n```\n\n## Usage\n\n```javascript\nfindDevices() // returns a Promise\n```\n\n### Finding devices\n\n```javascript\nvar findDevices = require('node-firefox-find-devices');\n\n// Return all listening runtimes\nfindDevices().then(function(results) {\n  console.log(results);\n});\n```\n\n## Running the tests\n\nAfter installing, you can simply run the following from the module folder:\n\n```bash\nnpm test\n```\n\nTo add a new unit test file, create a new file in the `tests/unit` folder. Any file that matches `test.*.js` will be run as a test by the appropriate test runner, based on the folder location.\n\nWe use `gulp` behind the scenes to run the test; if you don't have it installed globally you can use `npm gulp` from inside the project's root folder to run `gulp`.\n\n### Code quality and style\n\nBecause we have multiple contributors working on our projects, we value consistent code styles. It makes it easier to read code written by many people! :-)\n\nOur tests include unit tests as well as code quality (\"linting\") tests that make sure our test pass a style guide and [JSHint](http://jshint.com/). Instead of submitting code with the wrong indentation or a different style, run the tests and you will be told where your code quality/style differs from ours and instructions on how to fix it.\n\nThis program is free software; it is distributed under an\n[Apache License](https://github.com/mozilla/node-firefox-find-devices/blob/master/LICENSE).\n\n## Copyright\n\nCopyright (c) 2015 [Mozilla](https://mozilla.org)\n([Contributors](https://github.com/mozilla/node-firefox-find-devices/graphs/contributors)).\n",
+  "readmeFilename": "README.md",
+  "gitHead": "d12f9aaeca4ed669da0817c130e3df5fb05dd57d",
+  "_id": "node-firefox-find-devices@1.0.0",
+  "_shasum": "f84df2a6cffa5de83253441e0443070ce063e8ca",
+  "_from": "node-firefox-find-devices@"
+}
diff --git a/node_modules/node-firefox-find-devices/tests/unit/test.index.js b/node_modules/node-firefox-find-devices/tests/unit/test.index.js
new file mode 100644
index 0000000..547bfc1
--- /dev/null
+++ b/node_modules/node-firefox-find-devices/tests/unit/test.index.js
@@ -0,0 +1,87 @@
+'use strict';
+
+/* global -Promise */
+var Promise = require('es6-promise').Promise;
+var mockery = require('mockery');
+var nodemock = require('nodemock');
+
+module.exports = {
+
+  'findDevices() should enumerate ADB devices and scan for b2g installations': function(test) {
+
+    var fxosId = '8675309';
+    var androidId = 'ILIKEROBOTS';
+
+    var mocked = nodemock
+      .mock('listDevices')
+      .returns(new Promise(function(resolve, reject) {
+        resolve([
+          { id: fxosId, type: 'device' },
+          { id: androidId, type: 'device' }
+        ]);
+      }));
+
+    var mockShell = function(deviceId, command) {
+      return new Promise(function(resolve, reject) {
+        // Note: '0' means FxOS *was* detected.
+        var result = (deviceId === fxosId) ? '0' : '1';
+        resolve(result + '\r\n');
+      });
+    };
+
+    mocked.mock('createClient').returns({
+      listDevices: mocked.listDevices,
+      shell: mockShell
+    });
+
+    mockery.registerMock('adbkit', {
+      createClient: mocked.createClient,
+      util: {
+        readAll: function(input) {
+          return new Buffer(input, 'utf8');
+        }
+      }
+    });
+
+    // Enable mocks on a clear import cache
+    mockery.enable({
+      warnOnReplace: false,
+      warnOnUnregistered: false,
+      useCleanCache: true
+    });
+
+    // Require a freshly imported findDevices for this test
+    var findDevices = require('../../index');
+
+    findDevices().catch(function(err) {
+      test.ifError(err);
+      test.done();
+    }).then(function(results) {
+
+      // Ensure all the mocks were called, and with the expected parameters
+      test.ok(mocked.assert());
+
+      var foundFirefoxOS = false;
+      var foundAndroid = false;
+
+      results.forEach(function(result) {
+        if (result.id === fxosId) {
+          foundFirefoxOS = true;
+        }
+        if (result.id === androidId) {
+          foundAndroid = true;
+        }
+      });
+
+      // Ensure we did not find the Android device
+      test.ok(!foundAndroid);
+
+      // Ensure we found the Firefox OS device
+      test.ok(foundFirefoxOS);
+
+      test.done();
+
+    });
+  }
+
+};
diff --git a/node_modules/node-firefox-find-ports/.npmignore b/node_modules/node-firefox-find-ports/.npmignore
new file mode 100644
index 0000000..ae7e1e3
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/.npmignore
@@ -0,0 +1,4 @@
+node_modules
+npm-debug.log
+.DS_Store
+*.sw?
diff --git a/node_modules/node-firefox-find-ports/.travis.yml b/node_modules/node-firefox-find-ports/.travis.yml
new file mode 100644
index 0000000..0394ba7
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/.travis.yml
@@ -0,0 +1,3 @@
+language: node_js
+node_js:
+- 0.10
diff --git a/node_modules/node-firefox-find-ports/LICENSE b/node_modules/node-firefox-find-ports/LICENSE
new file mode 100644
index 0000000..6ab716d
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/LICENSE
@@ -0,0 +1,13 @@
+Copyright 2014 Mozilla
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/node_modules/node-firefox-find-ports/README.md b/node_modules/node-firefox-find-ports/README.md
new file mode 100644
index 0000000..559b868
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/README.md
@@ -0,0 +1,185 @@
+# node-firefox-find-ports [![Build Status](https://secure.travis-ci.org/mozilla/node-firefox-find-ports.png?branch=master)](http://travis-ci.org/mozilla/node-firefox-find-ports)
+
+> Find ports where debuggable runtimes are listening.
+
+[![Install with NPM](https://nodei.co/npm/node-firefox-find-ports.png?downloads=true&stars=true)](https://nodei.co/npm/node-firefox-find-ports/)
+
+This is part of the [node-firefox](https://github.com/mozilla/node-firefox) project.
+
+When runtimes have remote debugging enabled, they start a server that listens
+for incoming connections. Devices connected via USB can also have their remote
+debugging sockets forwarded to local TCP/IP ports by the Android Debug Bridge
+(adb). 
+  
+This module can find these runtimes and in which port they are listening.
+
+## Installation
+
+### From git
+
+```bash
+git clone https://github.com/mozilla/node-firefox-find-ports.git
+cd node-firefox-find-ports
+npm install
+```
+
+If you want to update later on:
+
+```bash
+cd node-firefox-find-ports
+git pull origin master
+npm install
+```
+
+### npm
+
+```bash
+npm install node-firefox-find-ports
+```
+
+## Usage
+
+```javascript
+findPorts(options) // returns a Promise
+```
+
+where `options` is a plain Object with any of the following:
+
+* `firefox`: look for Firefox Desktop instances
+* `firefoxOSSimulator`: look for Firefox OS Simulators
+* `firefoxOSDevice`: look for local ports forwarded to connected devices
+* `ignoreMultiplePortsPerDevice`: if there are multiple local ports forwarded to the same remote debugging port on a device, report only the first that is found (default: `true`)
+* `detailed`: query each found runtime for more information, such as the version, build time, processor, etc. The additional data will be added to the entry under a new `device` field.
+
+If no `options` are provided, or if `options` is an empty `Object` (`{}`), then `findPorts` will look for any runtimes, of any type.
+
+### Finding ports
+
+```javascript
+var findPorts = require('node-firefox-find-ports');
+
+// Return all listening runtimes
+findPorts().then(function(results) {
+  console.log(results);
+});
+
+// Returns only Firefox OS simulators, this time with error handling
+findPorts({ firefoxOSSimulator: true }).then(function(results) {
+  console.log(results);
+}, function(err) {
+  console.log(err);
+});
+```
+
+The output from the above code might look like the following:
+```javascript
+[ { type: 'b2g', port: 56567, pid: 45876 },
+  { type: 'firefox', port: 6000, pid: 3718 },
+  { type: 'device', port: 8001, deviceId: '3739ced5' } ]
+```
+
+Use the `detailed` option for additional information:
+```javascript
+// Returns only Firefox OS simulators, with extra detailed output
+findPorts({
+  firefoxOSSimulator: true, 
+  firefoxOSDevice: true,
+  detailed: true
+}).then(function(results) {
+  console.log(results);
+});
+```
+
+Detailed output includes a lot more info:
+```javascript
+[ { type: 'b2g',
+    port: 56567,
+    pid: 45876,
+    device:
+     { appid: '{3c2e2abc-06d4-11e1-ac3b-374f68613e61}',
+       apptype: 'b2g',
+       vendor: 'Mozilla',
+       name: 'B2G',
+       version: '2.2.0.0-prerelease',
+       appbuildid: '20141123160201',
+       platformbuildid: '20141123160201',
+       platformversion: '36.0a1',
+       geckobuildid: '20141123160201',
+       geckoversion: '36.0a1',
+       changeset: '8c02f3280d0c',
+       useragent: 'Mozilla/5.0 (Mobile; rv:36.0) Gecko/20100101 Firefox/36.0',
+       locale: 'en-US',
+       os: 'B2G',
+       hardware: null,
+       processor: 'x86_64',
+       compiler: 'gcc3',
+       dpi: 258,
+       brandName: null,
+       channel: 'default',
+       profile: 'profile',
+       width: 1680,
+       height: 1050 },
+    release: '2.2.0.0-prerelease' },
+  { type: 'device',
+    port: 8001,
+    deviceId: '3739ced5',
+    device:
+     { appid: '{3c2e2abc-06d4-11e1-ac3b-374f68613e61}',
+       apptype: 'b2g',
+       vendor: 'Mozilla',
+       name: 'B2G',
+       version: '3.0.0.0-prerelease',
+       appbuildid: '20150320064705',
+       platformbuildid: '20150320064705',
+       platformversion: '39.0a1',
+       geckobuildid: '20150320064705',
+       geckoversion: '39.0a1',
+       changeset: 'b2e71f32548f',
+       locale: 'en-US',
+       os: 'B2G',
+       hardware: 'qcom',
+       processor: 'arm',
+       compiler: 'eabi',
+       brandName: null,
+       channel: 'nightly',
+       profile: 'default',
+       dpi: 254,
+       useragent: 'Mozilla/5.0 (Mobile; rv:39.0) Gecko/39.0 Firefox/39.0',
+       width: 320,
+       height: 569 },
+    release: '3.0.0.0-prerelease' } ]
+```
+
+## Running the tests
+
+After installing, you can simply run the following from the module folder:
+
+```bash
+npm test
+```
+
+To add a new unit test file, create a new file in the `tests/unit` folder. Any file that matches `test.*.js` will be run as a test by the appropriate test runner, based on the folder location.
+
+We use `gulp` behind the scenes to run the test; if you don't have it installed globally you can use `npm gulp` from inside the project's root folder to run `gulp`.
+
+### Code quality and style
+
+Because we have multiple contributors working on our projects, we value consistent code styles. It makes it easier to read code written by many people! :-)
+
+Our tests include unit tests as well as code quality ("linting") tests that make sure our test pass a style guide and [JSHint](http://jshint.com/). Instead of submitting code with the wrong indentation or a different style, run the tests and you will be told where your code quality/style differs from ours and instructions on how to fix it.
+
+## History
+
+This is based on initial work on [fx-ports](https://github.com/nicola/fx-ports) by Nicola Greco.
+
+The command line utility binary has been removed for this initial iteration, since pretty much all the existing applications using this module were just using the JS code directly, not the binary.
+
+## License
+
+This program is free software; it is distributed under an
+[Apache License](https://github.com/mozilla/node-firefox-find-ports/blob/master/LICENSE).
+
+## Copyright
+
+Copyright (c) 2014 [Mozilla](https://mozilla.org)
+([Contributors](https://github.com/mozilla/node-firefox-find-ports/graphs/contributors)).
diff --git a/node_modules/node-firefox-find-ports/examples/findFirefoxOSSimulatorPorts.js b/node_modules/node-firefox-find-ports/examples/findFirefoxOSSimulatorPorts.js
new file mode 100644
index 0000000..cb5bf1b
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/examples/findFirefoxOSSimulatorPorts.js
@@ -0,0 +1,13 @@
+'use strict';
+
+var discoverPorts = require('../index.js');
+
+discoverPorts({ b2g: true }).then(function(simulators) {
+
+  var ports = simulators.map(function(simulator) {
+    return simulator.port;
+  });
+
+  console.log(ports);
+  
+});
diff --git a/node_modules/node-firefox-find-ports/examples/usage.js b/node_modules/node-firefox-find-ports/examples/usage.js
new file mode 100644
index 0000000..7287500
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/examples/usage.js
@@ -0,0 +1,37 @@
+'use strict';
+
+var findPorts = require('../');
+
+// We request to find any port, we don't care if it's a simulator or Firefox Desktop,
+// but we want detailed results
+findPorts({ detailed: true }).then(function(results) {
+  if (results.length === 0) {
+    console.log('No runtime found');
+  } else {
+    console.log('Found ' + results.length + ' runtimes (of any type)');
+    results.forEach(logEntry);
+  }
+}, onError);
+
+// Just finding ports where Firefox OS simulators are listening
+findPorts({ firefoxOSSimulator: true }).then(function(results) {
+  if (results.length === 0) {
+    console.log('Did not find any simulator running');
+  } else {
+    console.log('Found ' + results.length + ' simulators');
+    results.forEach(logEntry);
+  }
+}, onError);
+
+function logEntry(entry, index) {
+  var str = index + 1 + ') type: ' + entry.type + ' port: ' + entry.port;
+  if (entry.device) {
+    var device = entry.device;
+    str += ' version: ' + device.version + ' gecko: ' + device.geckoversion;
+  }
+  console.log(str);
+}
+
+function onError(err) {
+  console.log(err);
+}
diff --git a/node_modules/node-firefox-find-ports/gulpfile.js b/node_modules/node-firefox-find-ports/gulpfile.js
new file mode 100644
index 0000000..f5e5ab9
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/gulpfile.js
@@ -0,0 +1,3 @@
+var gulp = require('gulp');
+
+require('node-firefox-build-tools').loadGulpTasks(gulp);
diff --git a/node_modules/node-firefox-find-ports/index.js b/node_modules/node-firefox-find-ports/index.js
new file mode 100644
index 0000000..45b253e
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/index.js
@@ -0,0 +1,203 @@
+'use strict';
+
+// See https://github.com/jshint/jshint/issues/1747 for context
+/* global -Promise */
+
+var Promise = require('es6-promise').Promise;
+var exec = require('shelljs').exec;
+var FirefoxClient = require('firefox-client');
+var os = process.platform;
+
+var parsers = require('./lib/parsers');
+
+var commands = {
+  darwin: 'lsof -i -n -P -sTCP:LISTEN',
+  linux: 'netstat -lnptu',
+  win32: ['tasklist', 'netstat -ano']
+};
+
+var adb = require('adbkit');
+var REMOTE_DEBUGGER_SOCKET = 'localfilesystem:/data/local/debugger-socket';
+
+module.exports = findPorts;
+
+function findPorts(opts) {
+  opts = opts || {};
+
+  var isDefaultSearch = !opts.firefox &&
+                        !opts.firefoxOSSimulator &&
+                        !opts.firefoxOSDevice;
+  if (isDefaultSearch) {
+    // By default, search for all kinds of debugging ports.
+    opts.firefox = opts.firefoxOSSimulator = opts.firefoxOSDevice = true;
+  }
+
+  if (!opts.hasOwnProperty('ignoreMultiplePortsPerDevice')) {
+    // By default, ignore multiple debugging ports found for the same device.
+    opts.ignoreMultiplePortsPerDevice = true;
+  }
+
+  return findLocalPorts(opts).then(function(results) {
+    return findForwardedPorts(opts, results);
+  }).then(function(results) {
+    return expandResults(opts, results);
+  });
+}
+
+
+function findLocalPorts(opts) {
+  return new Promise(function(resolve, reject) {
+
+    var search = [];
+    if (opts.firefox) {
+      search.push('firefox');
+    }
+    if (opts.firefoxOSSimulator) {
+      search.push('b2g');
+    }
+    if (search.length === 0) {
+      return resolve([]);
+    }
+
+    var command = commands[os];
+    var parser = parsers[os];
+
+    if (parser === undefined) {
+      return reject(new Error(os + ' not supported yet'));
+    }
+
+    var lines = Array.isArray(command) ?
+                command.map(execAndSplitLines) :
+                execAndSplitLines(command);
+
+    var results = parser(lines, search);
+
+    return resolve(results);
+
+  });
+}
+
+
+function findForwardedPorts(opts, results) {
+
+  if (!opts.firefoxOSDevice) {
+    return results;
+  }
+
+  return adb.createClient().listForwards().then(function(ports) {
+
+    var seenDevices = {};
+
+    var deviceResults = ports.filter(function(port) {
+
+      // Filter out multiple ports per device when necessary.
+      if (opts.ignoreMultiplePortsPerDevice) {
+        var seenKey = port.serial;
+        if (opts.ignoreMultiplePortsPerDevice && seenKey in seenDevices) {
+          return false;
+        }
+        seenDevices[seenKey] = true;
+      }
+
+      // We only want local TCP/IP ports forwarded to remote debugging sockets.
+      return port.remote === REMOTE_DEBUGGER_SOCKET &&
+             port.local.indexOf('tcp:') === 0;
+
+    }).map(function(port) {
+
+      // Extract the port number from tcp:{port}
+      var portNumber = parseInt(port.local.substr(4));
+
+      return {
+        type: 'device',
+        port: portNumber,
+        deviceId: port.serial
+      };
+
+    });
+
+    return results.concat(deviceResults);
+
+  });
+
+}
+
+
+function expandResults(opts, results) {
+
+  if (opts.release && opts.release.length > 0) {
+    opts.detailed = true;
+  }
+
+  if (!opts.detailed) {
+    return results;
+  }
+
+  return Promise.all(results.map(getDeviceInfo))
+    .then(function(detailedResults) {
+      return filterByRelease(detailedResults, opts.release);
+    });
+}
+
+
+function execAndSplitLines(command) {
+  return exec(command, { silent: true }).output.split('\n');
+}
+
+
+function getDeviceInfo(instance) {
+
+  return new Promise(function(resolve, reject) {
+
+    var client = new FirefoxClient();
+
+    client.connect(instance.port, function(err) {
+
+      if (err) {
+        return reject(err);
+      }
+
+      client.getDevice(function(err, device) {
+
+        if (err) {
+          return reject(err);
+        }
+
+        device.getDescription(function(err, deviceDescription) {
+
+          if (err) {
+            return reject(err);
+          }
+
+          instance.device = deviceDescription;
+          instance.release = deviceDescription.version;
+          client.disconnect();
+          resolve(instance);
+
+        });
+
+      });
+
+    });
+
+  });
+
+}
+
+
+function filterByRelease(results, release) {
+
+  if (!release) {
+    return results;
+  }
+
+  if (typeof release === 'string') {
+    release = [ release ];
+  }
+
+  return results.filter(function(result) {
+    var regex = new RegExp('^(' + release.join('|') + ')');
+    return regex.exec(result.device.version);
+  });
+
+}
diff --git a/node_modules/node-firefox-find-ports/lib/parsers.js b/node_modules/node-firefox-find-ports/lib/parsers.js
new file mode 100644
index 0000000..730ca0f
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/lib/parsers.js
@@ -0,0 +1,115 @@
+'use strict';
+
+/*
+ * We will discard connections on port 2828 as those are the ones that Marionette uses
+ * For more info: https://developer.mozilla.org/en-US/docs/Marionette_Test_Runner
+ */
+
+var MARIONETTE_PORT = 2828;
+
+function parserDarwin(lines, search) {
+  var ports = [];
+  // Example syntax:
+  // b2g-bin   25779 mozilla   21u  IPv4 0xbbcbf2cee7ddc2a7      0t0  TCP 127.0.0.1:8000 (LISTEN)
+  var regex = new RegExp(
+    '^(' + search.join('|') +
+    ')(?:-bin)?[\\ ]+([0-9]+).*[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+:([0-9]+)'
+  );
+
+  lines.forEach(function(line) {
+    var matches = regex.exec(line);
+    var pid = matches ? Number(matches[2]) : null;
+    var port = matches ? Number(matches[3]) : null;
+
+    if (port && port !== MARIONETTE_PORT) {
+      ports.push({ type: matches[1], port: port, pid: pid });
+    }
+  });
+  return ports;
+}
+
+function parserLinux(lines, search) {
+  var ports = [];
+  // Example syntax:
+  // tcp        0      0 127.0.0.1:6000          0.0.0.0:*              LISTEN      3718/firefox
+  var regex = new RegExp(
+    'tcp.*[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+:([0-9]+).*LISTEN[\\ ]+([0-9]+)\\/(' +
+    search.join('|') + ')(?:-bin)?'
+  );
+
+  lines.forEach(function(line) {
+    var matches = regex.exec(line);
+    var pid = matches ? Number(matches[2]) : null;
+    var port = matches ? Number(matches[1]) : null;
+
+    if (port && port !== MARIONETTE_PORT) {
+      ports.push({ type: matches[3], port: port, pid: pid });
+    }
+  });
+  return ports;
+}
+
+function parserWin32(lines, search) {
+  var ports = [];
+  if (!isNonEmptyArray(lines)) {
+    return [];
+  }
+
+  var tasklistLines = lines[0];
+  if (!isNonEmptyArray(tasklistLines)) {
+    return [];
+  }
+
+  var netstatLines = lines[1];
+  if (!isNonEmptyArray(netstatLines)) {
+    return [];
+  }
+
+  // Example tasklist syntax:
+  // b2g-bin.exe                  16180 RDP-Tcp#2                  1    129,560 K
+  var pidMap = {};
+  var tasklistRegex = new RegExp(
+    '^(' + search.join('|') + ')(?:-bin)?\\.exe\\s+([\\d]+)'
+  );
+
+  // Build a process name to pid map from tasklist
+  tasklistLines.forEach(function(line) {
+    var matches = tasklistRegex.exec(line);
+    if (matches) {
+      pidMap[matches[2]] = matches[1];
+    }
+  });
+
+  // Example netstat syntax:
+  //   TCP    127.0.0.1:2828         0.0.0.0:0              LISTENING       16180
+  //   TCP    127.0.0.1:61291        0.0.0.0:0              LISTENING       16180
+  var netstatRegex = new RegExp(
+    '^\\s+TCP\\s+\\d+\\.\\d+\\.\\d+\\.\\d+:(\\d+)\\s+' +
+    '\\d+\\.\\d+\\.\\d+\\.\\d+:\\d+\\s+LISTENING\\s+(\\d+)'
+  );
+
+  // Scrape out the listening ports from netstat that match pid map
+  netstatLines.forEach(function(line) {
+    var matches = netstatRegex.exec(line);
+    if (matches) {
+      var port = Number(matches[1]);
+      var pid = Number(matches[2]);
+      var type = pidMap[pid];
+      if (type && port && port !== MARIONETTE_PORT) {
+        ports.push({ type: pidMap[pid], port: port, pid: pid });
+      }
+    }
+  });
+
+  return ports;
+}
+
+function isNonEmptyArray(val) {
+  return Array.isArray(val) && val.length > 0;
+}
+
+module.exports = {
+  darwin: parserDarwin,
+  linux: parserLinux,
+  win32: parserWin32
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/.bin/shjs b/node_modules/node-firefox-find-ports/node_modules/.bin/shjs
new file mode 120000
index 0000000..a044997
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/.bin/shjs
@@ -0,0 +1 @@
+../shelljs/bin/shjs
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/LICENSE b/node_modules/node-firefox-find-ports/node_modules/adbkit/LICENSE
new file mode 100644
index 0000000..2ffff3d
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/LICENSE
@@ -0,0 +1,13 @@
+Copyright © CyberAgent, Inc. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/README.md b/node_modules/node-firefox-find-ports/node_modules/adbkit/README.md
new file mode 100644
index 0000000..c8557ce
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/README.md
@@ -0,0 +1,1089 @@
+# adbkit
+
+**adbkit** is a pure [Node.js][nodejs] client for the [Android Debug Bridge][adb-site] server. It can be used either as a library in your own application, or simply as a convenient utility for playing with your device.
+
+Most of the `adb` command line tool's functionality is supported (including pushing/pulling files, installing APKs and processing logs), with some added functionality such as being able to generate touch/key events and take screenshots. Some shims are provided for older devices, but we have not and will not test anything below Android 2.3.
+
+Internally, we use this library to drive a multitude of Android devices from a variety of manufacturers, so we can say with a fairly high degree of confidence that it will most likely work with your device(s), too.
+
+## Requirements
+
+* [Node.js][nodejs] >= 0.10
+* The `adb` command line tool
+
+Please note that although it may happen at some point, **this project is NOT an implementation of the ADB _server_**. The target host (where the devices are connected) must still have ADB installed and either already running (e.g. via `adb start-server`) or available in `$PATH`. An attempt will be made to start the server locally via the aforementioned command if the initial connection fails. This is the only case where we fall back to the `adb` binary.
+
+When targeting a remote host, starting the server is entirely your responsibility.
+
+Alternatively, you may want to consider using the Chrome [ADB][chrome-adb] extension, as it includes the ADB server and can be started/stopped quite easily.
+
+## Getting started
+
+Install via NPM:
+
+```bash
+npm install --save adbkit
+```
+
+We use [debug][node-debug], and our debug namespace is `adb`. Some of the dependencies may provide debug output of their own. To see the debug output, set the `DEBUG` environment variable. For example, run your program with `DEBUG=adb:* node app.js`.
+
+Note that even though the module is written in [CoffeeScript][coffeescript], only the compiled JavaScript is published to [NPM][npm], which means that it can easily be used with pure JavaScript codebases, too.
+
+### Examples
+
+The examples may be a bit verbose, but that's because we're trying to keep them as close to real-life code as possible, with flow control and error handling taken care of.
+
+#### Checking for NFC support
+
+```js
+var Promise = require('bluebird')
+var adb = require('adbkit')
+var client = adb.createClient()
+
+client.listDevices()
+  .then(function(devices) {
+    return Promise.filter(devices, function(device) {
+      return client.getFeatures(device.id)
+        .then(function(features) {
+          return features['android.hardware.nfc']
+        })
+    })
+  })
+  .then(function(supportedDevices) {
+    console.log('The following devices support NFC:', supportedDevices)
+  })
+  .catch(function(err) {
+    console.error('Something went wrong:', err.stack)
+  })
+```
+
+#### Installing an APK
+
+```js
+var Promise = require('bluebird')
+var adb = require('adbkit')
+var client = adb.createClient()
+var apk = 'vendor/app.apk'
+
+client.listDevices()
+  .then(function(devices) {
+    return Promise.map(devices, function(device) {
+      return client.install(device.id, apk)
+    })
+  })
+  .then(function() {
+    console.log('Installed %s on all connected devices', apk)
+  })
+  .catch(function(err) {
+    console.error('Something went wrong:', err.stack)
+  })
+```
+
+#### Tracking devices
+
+```js
+var adb = require('adbkit')
+var client = adb.createClient()
+
+client.trackDevices()
+  .then(function(tracker) {
+    tracker.on('add', function(device) {
+      console.log('Device %s was plugged in', device.id)
+    })
+    tracker.on('remove', function(device) {
+      console.log('Device %s was unplugged', device.id)
+    })
+    tracker.on('end', function() {
+      console.log('Tracking stopped')
+    })
+  })
+  .catch(function(err) {
+    console.error('Something went wrong:', err.stack)
+  })
+```
+
+#### Pulling a file from all connected devices
+
+```js
+var Promise = require('bluebird')
+var fs = require('fs')
+var adb = require('adbkit')
+var client = adb.createClient()
+
+client.listDevices()
+  .then(function(devices) {
+    return Promise.map(devices, function(device) {
+      return client.pull(device.id, '/system/build.prop')
+        .then(function(transfer) {
+          return new Promise(function(resolve, reject) {
+            var fn = '/tmp/' + device.id + '.build.prop'
+            transfer.on('progress', function(stats) {
+              console.log('[%s] Pulled %d bytes so far',
+                device.id,
+                stats.bytesTransferred)
+            })
+            transfer.on('end', function() {
+              console.log('[%s] Pull complete', device.id)
+              resolve(device.id)
+            })
+            transfer.on('error', reject)
+            transfer.pipe(fs.createWriteStream(fn))
+          })
+        })
+    })
+  })
+  .then(function() {
+    console.log('Done pulling /system/build.prop from all connected devices')
+  })
+  .catch(function(err) {
+    console.error('Something went wrong:', err.stack)
+  })
+```
+
+#### Pushing a file to all connected devices
+
+```js
+var Promise = require('bluebird')
+var adb = require('adbkit')
+var client = adb.createClient()
+
+client.listDevices()
+  .then(function(devices) {
+    return Promise.map(devices, function(device) {
+      return client.push(device.id, 'temp/foo.txt', '/data/local/tmp/foo.txt')
+        .then(function(transfer) {
+          return new Promise(function(resolve, reject) {
+            transfer.on('progress', function(stats) {
+              console.log('[%s] Pushed %d bytes so far',
+                device.id,
+                stats.bytesTransferred)
+            })
+            transfer.on('end', function() {
+              console.log('[%s] Push complete', device.id)
+              resolve()
+            })
+            transfer.on('error', reject)
+          })
+        })
+    })
+  })
+  .then(function() {
+    console.log('Done pushing foo.txt to all connected devices')
+  })
+  .catch(function(err) {
+    console.error('Something went wrong:', err.stack)
+  })
+```
+
+#### List files in a folder
+
+```js
+var Promise = require('bluebird')
+var adb = require('adbkit')
+var client = adb.createClient()
+
+client.listDevices()
+  .then(function(devices) {
+    return Promise.map(devices, function(device) {
+      return client.readdir(device.id, '/sdcard')
+        .then(function(files) {
+          // Synchronous, so we don't have to care about returning at the
+          // right time
+          files.forEach(function(file) {
+            if (file.isFile()) {
+              console.log('[%s] Found file "%s"', device.id, file.name)
+            }
+          })
+        })
+    })
+  })
+  .then(function() {
+    console.log('Done checking /sdcard files on connected devices')
+  })
+  .catch(function(err) {
+    console.error('Something went wrong:', err.stack)
+  })
+```
+
+## API
+
+### ADB
+
+#### adb.createClient([options])
+
+Creates a client instance with the provided options. Note that this will not automatically establish a connection, it will only be done when necessary.
+
+* **options** An object compatible with [Net.connect][net-connect]'s options:
+    - **port** The port where the ADB server is listening. Defaults to `5037`.
+    - **host** The host of the ADB server. Defaults to `'localhost'`.
+    - **bin** As the sole exception, this option provides the path to the `adb` binary, used for starting the server locally if initial connection fails. Defaults to `'adb'`.
+* Returns: The client instance.
+
+#### adb.util.parsePublicKey(androidKey[, callback])
+
+Parses an Android-formatted mincrypt public key (e.g. `~/.android/adbkey.pub`).
+
+* **androidKey** The key String or [`Buffer`][node-buffer] to parse. Not a filename.
+* **callback(err, output)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **key** The key as a [forge.pki](https://github.com/digitalbazaar/forge#rsa) public key. You may need [node-forge](https://github.com/digitalbazaar/forge) for complicated operations. Additionally the following properties are present:
+      * **fingerprint** The key fingerprint, like it would display on a device. Note that this is different than the one you'd get from `forge.ssh.getPublicKeyFingerprint(key)`, because the device fingerprint is based on the original format.
+      * **comment** The key comment, if any.
+* Returns: `Promise`
+* Resolves with: `key` (see callback)
+
+#### adb.util.readAll(stream[, callback])
+
+Takes a [`Stream`][node-stream] and reads everything it outputs until the stream ends. Then it resolves with the collected output. Convenient with `client.shell()`.
+
+* **stream** The [`Stream`][node-stream] to read.
+* **callback(err, output)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **output** All the output as a [`Buffer`][node-buffer]. Use `output.toString('utf-8')` to get a readable string from it.
+* Returns: `Promise`
+* Resolves with: `output` (see callback)
+
+### Client
+
+#### client.clear(serial, pkg[, callback])
+
+Deletes all data associated with a package from the device. This is roughly analogous to `adb shell pm clear <pkg>`.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **pkg** The package name. This is NOT the APK.
+* **callback(err)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+* Returns: `Promise`
+* Resolves with: `true`
+
+#### client.connect(host[, port]&#91;, callback])
+
+Connects to the given device, which must have its ADB daemon running in tcp mode (see `client.tcpip()`) and be accessible on the same network. Same as `adb connect <host>:<port>`.
+
+* **host** The target host. Can also contain the port, in which case the port argument is not used and can be skipped.
+* **port** Optional. The target port. Defaults to `5555`.
+* **callback(err, id)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **id** The connected device ID. Can be used as `serial` in other commands.
+* Returns: `Promise`
+* Resolves with: `id` (see callback)
+
+##### Example - switch to TCP mode and set up a forward for Chrome devtools
+
+Note: be careful with using `client.listDevices()` together with `client.tcpip()` and other similar methods that modify the connection with ADB. You might have the same device twice in your device list (i.e. one device connected via both USB and TCP), which can cause havoc if run simultaneously.
+
+```javascript
+var Promise = require('bluebird')
+var client = require('adbkit').createClient()
+
+client.listDevices()
+  .then(function(devices) {
+    return Promise.map(devices, function(device) {
+      return client.tcpip(device.id)
+        .then(function(port) {
+          // Switching to TCP mode causes ADB to lose the device for a
+          // moment, so let's just wait till we get it back.
+          return client.waitForDevice(device.id).return(port)
+        })
+        .then(function(port) {
+          return client.getDHCPIpAddress(device.id)
+            .then(function(ip) {
+              return client.connect(ip, port)
+            })
+            .then(function(id) {
+              // It can take a moment for the connection to happen.
+              return client.waitForDevice(id)
+            })
+            .then(function(id) {
+              return client.forward(id, 'tcp:9222', 'localabstract:chrome_devtools_remote')
+                .then(function() {
+                  console.log('Setup devtools on "%s"', id)
+                })
+            })
+        })
+    })
+  })
+```
+
+#### client.disconnect(host[, port]&#91;, callback])
+
+Disconnects from the given device, which should have been connected via `client.connect()` or just `adb connect <host>:<port>`.
+
+* **host** The target host. Can also contain the port, in which case the port argument is not used and can be skipped. In other words you can just put the `id` you got from `client.connect()` here and it will be fine.
+* **port** Optional. The target port. Defaults to `5555`.
+* **callback(err, id)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **id** The disconnected device ID. Will no longer be usable as a `serial` in other commands until you've connected again.
+* Returns: `Promise`
+* Resolves with: `id` (see callback)
+
+#### client.forward(serial, local, remote[, callback])
+
+Forwards socket connections from the ADB server host (local) to the device (remote). This is analogous to `adb forward <local> <remote>`. It's important to note that if you are connected to a remote ADB server, the forward will be created on that host.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **local** A string representing the local endpoint on the ADB host. At time of writing, can be one of:
+    - `tcp:<port>`
+    - `localabstract:<unix domain socket name>`
+    - `localreserved:<unix domain socket name>`
+    - `localfilesystem:<unix domain socket name>`
+    - `dev:<character device name>`
+* **remote** A string representing the remote endpoint on the device. At time of writing, can be one of:
+    - Any value accepted by the `local` argument
+    - `jdwp:<process pid>`
+* **callback(err)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+* Returns: `Promise`
+* Resolves with: `true`
+
+#### client.framebuffer(serial[, format]&#91;, callback])
+
+Fetches the current **raw** framebuffer (i.e. what is visible on the screen) from the device, and optionally converts it into something more usable by using [GraphicsMagick][graphicsmagick]'s `gm` command, which must be available in `$PATH` if conversion is desired. Note that we don't bother supporting really old framebuffer formats such as RGB_565. If for some mysterious reason you happen to run into a `>=2.3` device that uses RGB_565, let us know.
+
+Note that high-resolution devices can have quite massive framebuffers. For example, a device with a resolution of 1920x1080 and 32 bit colors would have a roughly 8MB (`1920*1080*4` byte) RGBA framebuffer. Empirical tests point to about 5MB/s bandwidth limit for the ADB USB connection, which means that it can take ~1.6 seconds for the raw data to arrive, or even more if the USB connection is already congested. Using a conversion will further slow down completion.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **format** The desired output format. Any output format supported by [GraphicsMagick][graphicsmagick] (such as `'png'`) is supported. Defaults to `'raw'` for raw framebuffer data.
+* **callback(err, framebuffer)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **framebuffer** The possibly converted framebuffer stream. The stream also has a `meta` property with the following values:
+        * **version** The framebuffer version. Useful for patching possible backwards-compatibility issues.
+        * **bpp** Bits per pixel (i.e. color depth).
+        * **size** The raw byte size of the framebuffer.
+        * **width** The horizontal resolution of the framebuffer. This SHOULD always be the same as screen width. We have not encountered any device with incorrect framebuffer metadata, but according to rumors there might be some.
+        * **height** The vertical resolution of the framebuffer. This SHOULD always be the same as screen height.
+        * **red_offset** The bit offset of the red color in a pixel.
+        * **red_length** The bit length of the red color in a pixel.
+        * **blue_offset** The bit offset of the blue color in a pixel.
+        * **blue_length** The bit length of the blue color in a pixel.
+        * **green_offset** The bit offset of the green color in a pixel.
+        * **green_length** The bit length of the green color in a pixel.
+        * **alpha_offset** The bit offset of alpha in a pixel.
+        * **alpha_length** The bit length of alpha in a pixel. `0` when not available.
+        * **format** The framebuffer format for convenience. This can be one of `'bgr'`,  `'bgra'`, `'rgb'`, `'rgba'`.
+* Returns: `Promise`
+* Resolves with: `framebuffer` (see callback)
+
+#### client.getDevicePath(serial[, callback])
+
+Gets the device path of the device identified by the given serial number.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err, path)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **path** The device path. This corresponds to the device path in `client.listDevicesWithPaths()`.
+* Returns: `Promise`
+* Resolves with: `path` (see callback)
+
+#### client.getDHCPIpAddress(serial[, iface]&#91;, callback])
+
+Attemps to retrieve the IP address of the device. Roughly analogous to `adb shell getprop dhcp.<iface>.ipaddress`.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **iface** Optional. The network interface. Defaults to `'wlan0'`.
+* **callback(err, ip)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **ip** The IP address as a `String`.
+* Returns: `Promise`
+* Resolves with: `ip` (see callback)
+
+#### client.getFeatures(serial[, callback])
+
+Retrieves the features of the device identified by the given serial number. This is analogous to `adb shell pm list features`. Useful for checking whether hardware features such as NFC are available (you'd check for `'android.hardware.nfc'`).
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err, features)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **features** An object of device features. Each key corresponds to a device feature, with the value being either `true` for a boolean feature, or the feature value as a string (e.g. `'0x20000'` for `reqGlEsVersion`).
+* Returns: `Promise`
+* Resolves with: `features` (see callback)
+
+#### client.getPackages(serial[, callback])
+
+Retrieves the list of packages present on the device. This is analogous to `adb shell pm list packages`. If you just want to see if something's installed, consider using `client.isInstalled()` instead.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err, packages)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **packages** An array of package names.
+* Returns: `Promise`
+* Resolves with: `packages` (see callback)
+
+#### client.getProperties(serial[, callback])
+
+Retrieves the properties of the device identified by the given serial number. This is analogous to `adb shell getprop`.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err, properties)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **properties** An object of device properties. Each key corresponds to a device property. Convenient for accessing things like `'ro.product.model'`.
+* Returns: `Promise`
+* Resolves with: `properties` (see callback)
+
+#### client.getSerialNo(serial[, callback])
+
+Gets the serial number of the device identified by the given serial number. With our API this doesn't really make much sense, but it has been implemented for completeness. _FYI: in the raw ADB protocol you can specify a device in other ways, too._
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err, serial)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **serial** The serial number of the device.
+* Returns: `Promise`
+* Resolves with: `serial` (see callback)
+
+#### client.getState(serial[, callback])
+
+Gets the state of the device identified by the given serial number.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err, state)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **state** The device state. This corresponds to the device type in `client.listDevices()`.
+* Returns: `Promise`
+* Resolves with: `state` (see callback)
+
+#### client.install(serial, apk[, callback])
+
+Installs the APK on the device, replacing any previously installed version. This is roughly analogous to `adb install -r <apk>`.
+
+Note that if the call seems to stall, you may have to accept a dialog on the phone first.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **apk** When `String`, interpreted as a path to an APK file. When [`Stream`][node-stream], installs directly from the stream, which must be a valid APK.
+* **callback(err)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise. It may have a `.code` property containing the error code reported by the device.
+* Returns: `Promise`
+* Resolves with: `true`
+
+##### Example - install an APK from a URL
+
+This example requires the [request](https://www.npmjs.org/package/request) module. It also doesn't do any error handling (404 responses, timeouts, invalid URLs etc).
+
+```javascript
+var client = require('adbkit').createClient()
+var request = require('request')
+var Readable = require('stream').Readable
+
+// The request module implements old-style streams, so we have to wrap it.
+client.install('<serial>', new Readable().wrap(request('http://example.org/app.apk')))
+  .then(function() {
+    console.log('Installed')
+  })
+```
+
+#### client.installRemote(serial, apk[, callback])
+
+Installs an APK file which must already be located on the device file system, and replaces any previously installed version. Useful if you've previously pushed the file to the device for some reason (perhaps to have direct access to `client.push()`'s transfer stats). This is roughly analogous to `adb shell pm install -r <apk>` followed by `adb shell rm -f <apk>`.
+
+Note that if the call seems to stall, you may have to accept a dialog on the phone first.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **apk** The path to the APK file on the device. The file will be removed when the command completes.
+* **callback(err)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+* Returns: `Promise`
+* Resolves with: `true`
+
+#### client.isInstalled(serial, pkg[, callback])
+
+Tells you if the specific package is installed or not. This is analogous to `adb shell pm path <pkg>` and some output parsing.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **pkg** The package name. This is NOT the APK.
+* **callback(err, installed)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **installed** `true` if the package is installed, `false` otherwise.
+* Returns: `Promise`
+* Resolves with: `installed` (see callback)
+
+#### client.kill([callback])
+
+This kills the ADB server. Note that the next connection will attempt to start the server again when it's unable to connect.
+
+* **callback(err)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+* Returns: `Promise`
+* Resolves with: `true`
+
+#### client.listDevices([callback])
+
+Gets the list of currently connected devices and emulators.
+
+* **callback(err, devices)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **devices** An array of device objects. The device objects are plain JavaScript objects with two properties: `id` and `type`.
+        * **id** The ID of the device. For real devices, this is usually the USB identifier.
+        * **type** The device type. Values include `'emulator'` for emulators, `'device'` for devices, and `'offline'` for offline devices. `'offline'` can occur for example during boot, in low-battery conditions or when the ADB connection has not yet been approved on the device.
+* Returns: `Promise`
+* Resolves with: `devices` (see callback)
+
+#### client.listDevicesWithPaths([callback])
+
+Like `client.listDevices()`, but includes the "path" of every device.
+
+* **callback(err, devices)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **devices** An array of device objects. The device objects are plain JavaScript objects with the following properties:
+        * **id** See `client.listDevices()`.
+        * **type** See `client.listDevices()`.
+        * **path** The device path. This can be something like `usb:FD120000` for real devices.
+* Returns: `Promise`
+* Resolves with: `devices` (see callback)
+
+#### client.listForwards(serial[, callback])
+
+Lists forwarded connections on the device. This is analogous to `adb forward --list`.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err, forwards)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **forwards** An array of forward objects with the following properties:
+        * **serial** The device serial.
+        * **local** The local endpoint. Same format as `client.forward()`'s `local` argument.
+        * **remote** The remote endpoint on the device. Same format as `client.forward()`'s `remote` argument.
+* Returns: `Promise`
+* Resolves with: `forwards` (see callback)
+
+#### client.openLocal(serial, path[, callback])
+
+Opens a direct connection to a unix domain socket in the given path.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **path** The path to the socket. Prefixed with `'localfilesystem:'` by default, include another prefix (e.g. `'localabstract:'`) in the path to override.
+* **callback(err, conn)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **conn** The connection (i.e. [`net.Socket`][node-net]). Read and write as you please. Call `conn.end()` to end the connection.
+* Returns: `Promise`
+* Resolves with: `conn` (see callback)
+
+#### client.openLog(serial, name[, callback])
+
+Opens a direct connection to a binary log file, providing access to the raw log data. Note that it is usually much more convenient to use the `client.openLogcat()` method, described separately.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **name** The name of the log. Available logs include `'main'`, `'system'`, `'radio'` and `'events'`.
+* **callback(err, log)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **log** The binary log stream. Call `log.end()` when you wish to stop receiving data.
+* Returns: `Promise`
+* Resolves with: `log` (see callback)
+
+#### client.openLogcat(serial[, options]&#91;, callback])
+
+Calls the `logcat` utility on the device and hands off the connection to [adbkit-logcat][adbkit-logcat], a pure Node.js Logcat client. This is analogous to `adb logcat -B`, but the event stream will be parsed for you and a separate event will be emitted for every log entry, allowing for easy processing.
+
+For more information, check out the [adbkit-logcat][adbkit-logcat] documentation.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **options** Optional. The following options are supported:
+    - **clear** When `true`, clears logcat before opening the reader. Not set by default.
+* **callback(err, logcat)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **logcat** The Logcat client. Please see the [adbkit-logcat][adbkit-logcat] documentation for details.
+* Returns: `Promise`
+* Resolves with: `logcat` (see callback)
+
+#### client.openMonkey(serial[, port]&#91;, callback])
+
+Starts the built-in `monkey` utility on the device, connects to it using `client.openTcp()` and hands the connection to [adbkit-monkey][adbkit-monkey], a pure Node.js Monkey client. This allows you to create touch and key events, among other things.
+
+For more information, check out the [adbkit-monkey][adbkit-monkey] documentation.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **port** Optional. The device port where you'd like Monkey to run at. Defaults to `1080`.
+* **callback(err, monkey)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **monkey** The Monkey client. Please see the [adbkit-monkey][adbkit-monkey] documentation for details.
+* Returns: `Promise`
+* Resolves with: `monkey` (see callback)
+
+#### client.openProcStat(serial[, callback])
+
+Tracks `/proc/stat` and emits useful information, such as CPU load. A single sync service instance is used to download the `/proc/stat` file for processing. While doing this does consume some resources, it is very light and should not be a problem.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err, stats)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **stats** The `/proc/stat` tracker, which is an [`EventEmitter`][node-events]. Call `stat.end()` to stop tracking. The following events are available:
+        * **load** **(loads)** Emitted when a CPU load calculation is available.
+            - **loads** CPU loads of **online** CPUs. Each key is a CPU id (e.g. `'cpu0'`, `'cpu1'`) and the value an object with the following properties:
+                * **user** Percentage (0-100) of ticks spent on user programs.
+                * **nice** Percentage (0-100) of ticks spent on `nice`d user programs.
+                * **system** Percentage (0-100) of ticks spent on system programs.
+                * **idle** Percentage (0-100) of ticks spent idling.
+                * **iowait** Percentage (0-100) of ticks spent waiting for IO.
+                * **irq** Percentage (0-100) of ticks spent on hardware interrupts.
+                * **softirq** Percentage (0-100) of ticks spent on software interrupts.
+                * **steal** Percentage (0-100) of ticks stolen by others.
+                * **guest** Percentage (0-100) of ticks spent by a guest.
+                * **guestnice** Percentage (0-100) of ticks spent by a `nice`d guest.
+                * **total** Total. Always 100.
+* Returns: `Promise`
+* Resolves with: `stats` (see callback)
+
+#### client.openTcp(serial, port[, host]&#91;, callback])
+
+Opens a direct TCP connection to a port on the device, without any port forwarding required.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **port** The port number to connect to.
+* **host** Optional. The host to connect to. Allegedly this is supposed to establish a connection to the given host from the device, but we have not been able to get it to work at all. Skip the host and everything works great.
+* **callback(err, conn)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **conn** The TCP connection (i.e. [`net.Socket`][node-net]). Read and write as you please. Call `conn.end()` to end the connection.
+* Returns: `Promise`
+* Resolves with: `conn` (see callback)
+
+#### client.pull(serial, path[, callback])
+
+A convenience shortcut for `sync.pull()`, mainly for one-off use cases. The connection cannot be reused, resulting in poorer performance over multiple calls. However, the Sync client will be closed automatically for you, so that's one less thing to worry about.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **path** See `sync.pull()` for details.
+* **callback(err, transfer)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **transfer** A `PullTransfer` instance (see below)
+* Returns: `Promise`
+* Resolves with: `transfer` (see callback)
+
+#### client.push(serial, contents, path[, mode]&#91;, callback])
+
+A convenience shortcut for `sync.push()`, mainly for one-off use cases. The connection cannot be reused, resulting in poorer performance over multiple calls. However, the Sync client will be closed automatically for you, so that's one less thing to worry about.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **contents** See `sync.push()` for details.
+* **path** See `sync.push()` for details.
+* **mode** See `sync.push()` for details.
+* **callback(err, transfer)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **transfer** A `PushTransfer` instance (see below)
+* Returns: `Promise`
+* Resolves with: `transfer` (see callback)
+
+#### client.readdir(serial, path[, callback])
+
+A convenience shortcut for `sync.readdir()`, mainly for one-off use cases. The connection cannot be reused, resulting in poorer performance over multiple calls. However, the Sync client will be closed automatically for you, so that's one less thing to worry about.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **path** See `sync.readdir()` for details.
+* **callback(err, files)** Optional. Use this or the returned `Promise`. See `sync.readdir()` for details.
+* Returns: `Promise`
+* Resolves with: See `sync.readdir()` for details.
+
+#### client.reboot(serial[, callback])
+
+Reboots the device. Similar to `adb reboot`. Note that the method resolves when ADB reports that the device has been rebooted (i.e. the reboot command was successful), not when the device becomes available again.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+* Returns: `Promise`
+* Resolves with: `true`
+
+#### client.remount(serial[, callback])
+
+Attempts to remount the `/system` partition in read-write mode. This will usually only work on emulators and developer devices.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+* Returns: `Promise`
+* Resolves with: `true`
+
+#### client.screencap(serial[, callback])
+
+Takes a screenshot in PNG format using the built-in `screencap` utility. This is analogous to `adb shell screencap -p`. Sadly, the utility is not available on most Android `<=2.3` devices, but a silent fallback to the `client.framebuffer()` command in PNG mode is attempted, so you should have its dependencies installed just in case.
+
+Generating the PNG on the device naturally requires considerably more processing time on that side. However, as the data transferred over USB easily decreases by ~95%, and no conversion being required on the host, this method is usually several times faster than using the framebuffer. Naturally, this benefit does not apply if we're forced to fall back to the framebuffer.
+
+For convenience purposes, if the screencap command fails (e.g. because it doesn't exist on older Androids), we fall back to `client.framebuffer(serial, 'png')`, which is slower and has additional installation requirements.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err, screencap)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **screencap** The PNG stream.
+* Returns: `Promise`
+* Resolves with: `screencap` (see callback)
+
+#### client.shell(serial, command[, callback])
+
+Runs a shell command on the device. Note that you'll be limited to the permissions of the `shell` user, which ADB uses.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **command** The shell command to execute. When `String`, the command is run as-is. When `Array`, the elements will be rudimentarily escaped (for convenience, not security) and joined to form a command.
+* **callback(err, output)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **output** A Buffer containing all the output. Call `output.toString('utf-8')` to get a readable String from it.
+* Returns: `Promise`
+* Resolves with: `output` (see callback)
+
+##### Example
+
+```js
+var Promise = require('bluebird')
+var adb = require('adbkit')
+var client = adb.createClient()
+
+client.listDevices()
+  .then(function(devices) {
+    return Promise.map(devices, function(device) {
+      return client.shell(device.id, 'echo $RANDOM')
+        // Use the readAll() utility to read all the content without
+        // having to deal with the events. `output` will be a Buffer
+        // containing all the output.
+        .then(adb.util.readAll)
+        .then(function(output) {
+          console.log('[%s] %s', device.id, output.toString().trim())
+        })
+    })
+  })
+  .then(function() {
+    console.log('Done.')
+  })
+  .catch(function(err) {
+    console.error('Something went wrong:', err.stack)
+  })
+```
+
+#### client.startActivity(serial, options[, callback])
+
+Starts the configured activity on the device. Roughly analogous to `adb shell am start <options>`.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **options** The activity configuration. The following options are available:
+    - **debug** Set to `true` to enable debugging.
+    - **wait** Set to `true` to wait for the activity to launch.
+    - **user** The user to run as. Not set by default. If the option is unsupported by the device, an attempt will be made to run the same command again without the user option.
+    - **action** The action.
+    - **data** The data URI, if any.
+    - **mimeType** The mime type, if any.
+    - **category** The category. For multiple categories, pass an `Array`.
+    - **component** The component.
+    - **flags** Numeric flags.
+    - **extras** Any extra data.
+        * When an `Array`, each item must be an `Object` the following properties:
+            - **key** The key name.
+            - **type** The type, which can be one of `'string'`, `'null'`, `'bool'`, `'int'`, `'long'`, `'float'`, `'uri'`, `'component'`.
+            - **value** The value. Optional and unused if type is `'null'`. If an `Array`, type is automatically set to be an array of `<type>`.
+        * When an `Object`, each key is treated as the key name. Simple values like `null`, `String`, `Boolean` and `Number` are type-mapped automatically (`Number` maps to `'int'`) and can be used as-is. For more complex types, like arrays and URIs, set the value to be an `Object` like in the Array syntax (see above), but leave out the `key` property.
+* **callback(err)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+* Returns: `Promise`
+* Resolves with: `true`
+
+#### client.startService(serial, options[, callback])
+
+Starts the configured service on the device. Roughly analogous to `adb shell am startservice <options>`.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **options** The service configuration. The following options are available:
+    - **user** The user to run as. Defaults to `0`. If the option is unsupported by the device, an attempt will be made to run the same command again without the user option.
+    - **action** See `client.startActivity()` for details.
+    - **data** See `client.startActivity()` for details.
+    - **mimeType** See `client.startActivity()` for details.
+    - **category** See `client.startActivity()` for details.
+    - **component** See `client.startActivity()` for details.
+    - **flags** See `client.startActivity()` for details.
+    - **extras** See `client.startActivity()` for details.
+* Returns: `Promise`
+* Resolves with: `true`
+
+#### client.stat(serial, path[, callback])
+
+A convenience shortcut for `sync.stat()`, mainly for one-off use cases. The connection cannot be reused, resulting in poorer performance over multiple calls. However, the Sync client will be closed automatically for you, so that's one less thing to worry about.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **path** See `sync.stat()` for details.
+* **callback(err, stats)** Optional. Use this or the returned `Promise`. See `sync.stat()` for details.
+* Returns: `Promise`
+* Resolves with: See `sync.stat()` for details.
+
+#### client.syncService(serial[, callback])
+
+Establishes a new Sync connection that can be used to push and pull files. This method provides the most freedom and the best performance for repeated use, but can be a bit cumbersome to use. For simple use cases, consider using `client.stat()`, `client.push()` and `client.pull()`.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err, sync)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **sync** The Sync client. See below for details. Call `sync.end()` when done.
+* Returns: `Promise`
+* Resolves with: `sync` (see callback)
+
+#### client.tcpip(serial, port[, callback])
+
+Puts the device's ADB daemon into tcp mode, allowing you to use `adb connect` or `client.connect()` to connect to it. Note that the device will still be visible to ADB as a regular USB-connected device until you unplug it. Same as `adb tcpip <port>`.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **port** Optional. The port the device should listen on. Defaults to `5555`.
+* **callback(err, port)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **port** The port the device started listening on.
+* Returns: `Promise`
+* Resolves with: `port` (see callback)
+
+#### client.trackDevices([callback])
+
+Gets a device tracker. Events will be emitted when devices are added, removed, or their type changes (i.e. to/from `offline`). Note that the same events will be emitted for the initially connected devices also, so that you don't need to use both `client.listDevices()` and `client.trackDevices()`.
+
+Note that as the tracker will keep a connection open, you must call `tracker.end()` if you wish to stop tracking devices.
+
+* **callback(err, tracker)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **tracker** The device tracker, which is an [`EventEmitter`][node-events]. The following events are available:
+        * **add** **(device)** Emitted when a new device is connected, once per device. See `client.listDevices()` for details on the device object.
+        * **remove** **(device)** Emitted when a device is unplugged, once per device. This does not include `offline` devices, those devices are connected but unavailable to ADB. See `client.listDevices()` for details on the device object.
+        * **change** **(device)** Emitted when the `type` property of a device changes, once per device. The current value of `type` is the new value. This event usually occurs the type changes from `'device'` to `'offline'` or the other way around. See `client.listDevices()` for details on the device object and the `'offline'` type.
+        * **changeSet** **(changes)** Emitted once for all changes reported by ADB in a single run. Multiple changes can occur when, for example, a USB hub is connected/unplugged and the device list changes quickly. If you wish to process all changes at once, use this event instead of the once-per-device ones. Keep in mind that the other events will still be emitted, though.
+            - **changes** An object with the following properties always present:
+                * **added** An array of added device objects, each one as in the `add` event. Empty if none.
+                * **removed** An array of removed device objects, each one as in the `remove` event. Empty if none.
+                * **changed** An array of changed device objects, each one as in the `change` event. Empty if none.
+        * **end** Emitted when the underlying connection ends.
+        * **error** **(err)** Emitted if there's an error.
+* Returns: `Promise`
+* Resolves with: `tracker` (see callback)
+
+#### client.trackJdwp(serial[, callback])
+
+Starts a JDWP tracker for the given device.
+
+Note that as the tracker will keep a connection open, you must call `tracker.end()` if you wish to stop tracking JDWP processes.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err, tracker)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **tracker** The JDWP tracker, which is an [`EventEmitter`][node-events]. The following events are available:
+        * **add** **(pid)** Emitted when a new JDWP process becomes available, once per pid.
+        * **remove** **(pid)** Emitted when a JDWP process becomes unavailable, once per pid.
+        * **changeSet** **(changes, pids)** All changes in a single event.
+            - **changes** An object with the following properties always present:
+                * **added** An array of pids that were added. Empty if none.
+                * **removed** An array of pids that were removed. Empty if none.
+            - **pids** All currently active pids (including pids from previous runs).
+        * **end** Emitted when the underlying connection ends.
+        * **error** **(err)** Emitted if there's an error.
+* Returns: `Promise`
+* Resolves with: `tracker` (see callback)
+
+#### client.uninstall(serial, pkg[, callback])
+
+Uninstalls the package from the device. This is roughly analogous to `adb uninstall <pkg>`.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **pkg** The package name. This is NOT the APK.
+* **callback(err)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+* Returns: `Promise`
+* Resolves with: `true`
+
+#### client.usb(serial[, callback])
+
+Puts the device's ADB daemon back into USB mode. Reverses `client.tcpip()`. Same as `adb usb`.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+* Returns: `Promise`
+* Resolves with: `true`
+
+#### client.version([callback])
+
+Queries the ADB server for its version. This is mainly useful for backwards-compatibility purposes.
+
+* **callback(err, version)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **version** The version of the ADB server.
+* Returns: `Promise`
+* Resolves with: `version` (see callback)
+
+#### client.waitBootComplete(serial[, callback])
+
+Waits until the device has finished booting. Note that the device must already be seen by ADB. This is roughly analogous to periodically checking `adb shell getprop sys.boot_completed`.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err)** Optional. Use this or the returned `Promise`.
+    - **err** `null` if the device has completed booting, `Error` otherwise (can occur if the connection dies while checking).
+* Returns: `Promise`
+* Resolves with: `true`
+
+#### client.waitForDevice(serial[, callback])
+
+Waits until ADB can see the device. Note that you must know the serial in advance. Other than that, works like `adb -s serial wait-for-device`. If you're planning on reacting to random devices being plugged in and out, consider using `client.trackDevices()` instead.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err, id)** Optional. Use this or the returned `Promise`.
+    - **err** `null` if the device has completed booting, `Error` otherwise (can occur if the connection dies while checking).
+    - **id** The device ID. Can be useful for chaining.
+* Returns: `Promise`
+* Resolves with: `id` (see callback)
+
+### Sync
+
+#### sync.end()
+
+Closes the Sync connection, allowing Node to quit (assuming nothing else is keeping it alive, of course).
+
+* Returns: The sync instance.
+
+#### sync.pull(path)
+
+Pulls a file from the device as a `PullTransfer` [`Stream`][node-stream].
+
+* **path** The path to pull from.
+* Returns: A `PullTransfer` instance. See below for details.
+
+#### sync.push(contents, path[, mode])
+
+Attempts to identify `contents` and calls the appropriate `push*` method for it.
+
+* **contents** When `String`, treated as a local file path and forwarded to `sync.pushFile()`. Otherwise, treated as a [`Stream`][node-stream] and forwarded to `sync.pushStream()`.
+* **path** The path to push to.
+* **mode** Optional. The mode of the file. Defaults to `0644`.
+* Returns: A `PushTransfer` instance. See below for details.
+
+#### sync.pushFile(file, path[, mode])
+
+Pushes a local file to the given path. Note that the path must be writable by the ADB user (usually `shell`). When in doubt, use `'/data/local/tmp'` with an appropriate filename.
+
+* **file** The local file path.
+* **path** See `sync.push()` for details.
+* **mode** See `sync.push()` for details.
+* Returns: See `sync.push()` for details.
+
+#### sync.pushStream(stream, path[, mode])
+
+Pushes a [`Stream`][node-stream] to the given path. Note that the path must be writable by the ADB user (usually `shell`). When in doubt, use `'/data/local/tmp'` with an appropriate filename.
+
+* **stream** The readable stream.
+* **path** See `sync.push()` for details.
+* **mode** See `sync.push()` for details.
+* Returns: See `sync.push()` for details.
+
+#### sync.readdir(path[, callback])
+
+Retrieves a list of directory entries (e.g. files) in the given path, not including the `.` and `..` entries, just like [`fs.readdir`][node-fs]. If given a non-directory path, no entries are returned.
+
+* **path** The path.
+* **callback(err, files)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **files** An `Array` of [`fs.Stats`][node-fs-stats]-compatible instances. While the `stats.is*` methods are available, only the following properties are supported (in addition to the `name` field which contains the filename):
+        * **name** The filename.
+        * **mode** The raw mode.
+        * **size** The file size.
+        * **mtime** The time of last modification as a `Date`.
+* Returns: `Promise`
+* Resolves with: `files` (see callback)
+
+#### sync.stat(path[, callback])
+
+Retrieves information about the given path.
+
+* **path** The path.
+* **callback(err, stats)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **stats** An [`fs.Stats`][node-fs-stats] instance. While the `stats.is*` methods are available, only the following properties are supported:
+        * **mode** The raw mode.
+        * **size** The file size.
+        * **mtime** The time of last modification as a `Date`.
+* Returns: `Promise`
+* Resolves with: `stats` (see callback)
+
+#### sync.tempFile(path)
+
+A simple helper method for creating appropriate temporary filenames for pushing files. This is essentially the same as taking the basename of the file and appending it to `'/data/local/tmp/'`.
+
+* **path** The path of the file.
+* Returns: An appropriate temporary file path.
+
+### PushTransfer
+
+A simple EventEmitter, mainly for keeping track of the progress.
+
+List of events:
+
+* **progress** **(stats)** Emitted when a chunk has been flushed to the ADB connection.
+    - **stats** An object with the following stats about the transfer:
+        * **bytesTransferred** The number of bytes transferred so far.
+* **error** **(err)** Emitted on error.
+    - **err** An `Error`.
+* **end** Emitted when the transfer has successfully completed.
+
+#### pushTransfer.cancel()
+
+Cancels the transfer by ending both the stream that is being pushed and the sync connection. This will most likely end up creating a broken file on your device. **Use at your own risk.** Also note that you must create a new sync connection if you wish to continue using the sync service.
+
+* Returns: The pushTransfer instance.
+
+### PullTransfer
+
+`PullTransfer` is a [`Stream`][node-stream]. Use [`fs.createWriteStream()`][node-fs] to pipe the stream to a file if necessary.
+
+List of events:
+
+* **progress** **(stats)** Emitted when a new chunk is received.
+    - **stats** An object with the following stats about the transfer:
+        * **bytesTransferred** The number of bytes transferred so far.
+* **error** **(err)** Emitted on error.
+    - **err** An `Error`.
+* **end** Emitted when the transfer has successfully completed.
+
+#### pullTransfer.cancel()
+
+Cancels the transfer by ending the connection. Can be useful for reading endless streams of data, such as `/dev/urandom` or `/dev/zero`, perhaps for benchmarking use. Note that you must create a new sync connection if you wish to continue using the sync service.
+
+* Returns: The pullTransfer instance.
+
+# Incompatible changes in version 2.x
+
+Previously, we made extensive use of callbacks in almost every feature. While this normally works okay, ADB connections can be quite fickle, and it was starting to become difficult to handle every possible error. For example, we'd often fail to properly clean up after ourselves when a connection suddenly died in an unexpected place, causing memory and resource leaks.
+
+In version 2, we've replaced nearly all callbacks with [Promises](http://promisesaplus.com/) (using [Bluebird](https://github.com/petkaantonov/bluebird)), allowing for much more reliable error propagation and resource cleanup (thanks to `.finally()`). Additionally, many commands can now be cancelled on the fly, and although unimplemented at this point, we'll also be able to report progress on long-running commands without any changes to the API.
+
+Unfortunately, some API changes were required for this change. `client.framebuffer()`'s callback, for example, previously accepted more than one argument, which doesn't translate into Promises so well. Thankfully, it made sense to combine the arguments anyway, and we were able to do it quite cleanly.
+
+Furthermore, most API methods were returning the current instance for chaining purposes. While perhaps useful in some contexts, most of the time it probably didn't quite do what users expected, as chained calls were run in parallel rather than in serial fashion. Now every applicable API method returns a Promise, which is an incompatible but welcome change. This will also allow you to hook into `yield` and coroutines in Node 0.12.
+
+**However, all methods still accept (and will accept in the future) callbacks for those who prefer them.**
+
+Test coverage was also massively improved, although we've still got ways to go.
+
+## More information
+
+* [Android Debug Bridge][adb-site]
+    - [SERVICES.TXT][adb-services] (ADB socket protocol)
+* [Android ADB Protocols][adb-protocols] (a blog post explaining the protocol)
+* [adb.js][adb-js] (another Node.js ADB implementation)
+* [ADB Chrome extension][chrome-adb]
+
+## Contributing
+
+See [CONTRIBUTING.md](CONTRIBUTING.md).
+
+## License
+
+See [LICENSE](LICENSE).
+
+Copyright © CyberAgent, Inc. All Rights Reserved.
+
+[nodejs]: <http://nodejs.org/>
+[coffeescript]: <http://coffeescript.org/>
+[npm]: <https://npmjs.org/>
+[adb-js]: <https://github.com/flier/adb.js>
+[adb-site]: <http://developer.android.com/tools/help/adb.html>
+[adb-services]: <https://github.com/android/platform_system_core/blob/master/adb/SERVICES.TXT>
+[adb-protocols]: <http://blogs.kgsoft.co.uk/2013_03_15_prg.htm>
+[file_sync_service.h]: <https://github.com/android/platform_system_core/blob/master/adb/file_sync_service.h>
+[chrome-adb]: <https://chrome.google.com/webstore/detail/adb/dpngiggdglpdnjdoaefidgiigpemgage>
+[node-debug]: <https://npmjs.org/package/debug>
+[net-connect]: <http://nodejs.org/api/net.html#net_net_connect_options_connectionlistener>
+[node-events]: <http://nodejs.org/api/events.html>
+[node-stream]: <http://nodejs.org/api/stream.html>
+[node-buffer]: <http://nodejs.org/api/buffer.html>
+[node-net]: <http://nodejs.org/api/net.html>
+[node-fs]: <http://nodejs.org/api/fs.html>
+[node-fs-stats]: <http://nodejs.org/api/fs.html#fs_class_fs_stats>
+[node-gm]: <https://github.com/aheckmann/gm>
+[graphicsmagick]: <http://www.graphicsmagick.org/>
+[imagemagick]: <http://www.imagemagick.org/>
+[adbkit-logcat]: <https://npmjs.org/package/adbkit-logcat>
+[adbkit-monkey]: <https://npmjs.org/package/adbkit-monkey>
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/index.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/index.js
new file mode 100644
index 0000000..a4c08ac
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/index.js
@@ -0,0 +1,15 @@
+(function() {
+  var Path;
+
+  Path = require('path');
+
+  module.exports = (function() {
+    switch (Path.extname(__filename)) {
+      case '.coffee':
+        return require('./src/adb');
+      default:
+        return require('./lib/adb');
+    }
+  })();
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb.js
new file mode 100644
index 0000000..50e8e1b
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb.js
@@ -0,0 +1,27 @@
+(function() {
+  var Adb, Client, Keycode, util;
+
+  Client = require('./adb/client');
+
+  Keycode = require('./adb/keycode');
+
+  util = require('./adb/util');
+
+  Adb = (function() {
+    function Adb() {}
+
+    Adb.createClient = function(options) {
+      return new Client(options);
+    };
+
+    return Adb;
+
+  })();
+
+  Adb.Keycode = Keycode;
+
+  Adb.util = util;
+
+  module.exports = Adb;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/auth.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/auth.js
new file mode 100644
index 0000000..868ed63
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/auth.js
@@ -0,0 +1,81 @@
+(function() {
+  var Auth, BigInteger, Promise, forge;
+
+  Promise = require('bluebird');
+
+  forge = require('node-forge');
+
+  BigInteger = forge.jsbn.BigInteger;
+
+
+  /*
+  The stucture of an ADB RSAPublicKey is as follows:
+  
+       *define RSANUMBYTES 256           // 2048 bit key length
+       *define RSANUMWORDS (RSANUMBYTES / sizeof(uint32_t))
+  
+      typedef struct RSAPublicKey {
+          int len;                  // Length of n[] in number of uint32_t
+          uint32_t n0inv;           // -1 / n[0] mod 2^32
+          uint32_t n[RSANUMWORDS];  // modulus as little endian array
+          uint32_t rr[RSANUMWORDS]; // R^2 as little endian array
+          int exponent;             // 3 or 65537
+      } RSAPublicKey;
+   */
+
+  Auth = (function() {
+    var RE, readPublicKeyFromStruct;
+
+    function Auth() {}
+
+    RE = /^((?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?) (.*)$/;
+
+    readPublicKeyFromStruct = function(struct, comment) {
+      var e, key, len, md, n, offset;
+      if (!struct.length) {
+        throw new Error("Invalid public key");
+      }
+      offset = 0;
+      len = struct.readUInt32LE(offset) * 4;
+      offset += 4;
+      if (struct.length !== 4 + 4 + len + len + 4) {
+        throw new Error("Invalid public key");
+      }
+      offset += 4;
+      n = new Buffer(len);
+      struct.copy(n, 0, offset, offset + len);
+      [].reverse.call(n);
+      offset += len;
+      offset += len;
+      e = struct.readUInt32LE(offset);
+      if (!(e === 3 || e === 65537)) {
+        throw new Error("Invalid exponent " + e + ", only 3 and 65537 are supported");
+      }
+      key = forge.pki.setRsaPublicKey(new BigInteger(n.toString('hex'), 16), new BigInteger(e.toString(), 10));
+      md = forge.md.md5.create();
+      md.update(struct.toString('binary'));
+      key.fingerprint = md.digest().toHex().match(/../g).join(':');
+      key.comment = comment;
+      return key;
+    };
+
+    Auth.parsePublicKey = function(buffer) {
+      return new Promise(function(resolve, reject) {
+        var comment, match, struct;
+        if (match = RE.exec(buffer)) {
+          struct = new Buffer(match[1], 'base64');
+          comment = match[2];
+          return resolve(readPublicKeyFromStruct(struct, comment));
+        } else {
+          return reject(new Error("Unrecognizable public key format"));
+        }
+      });
+    };
+
+    return Auth;
+
+  })();
+
+  module.exports = Auth;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/client.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/client.js
new file mode 100644
index 0000000..abfc40e
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/client.js
@@ -0,0 +1,552 @@
+(function() {
+  var ClearCommand, Client, Connection, ForwardCommand, FrameBufferCommand, GetDevicePathCommand, GetFeaturesCommand, GetPackagesCommand, GetPropertiesCommand, GetSerialNoCommand, GetStateCommand, HostConnectCommand, HostDevicesCommand, HostDevicesWithPathsCommand, HostDisconnectCommand, HostKillCommand, HostTrackDevicesCommand, HostTransportCommand, HostVersionCommand, InstallCommand, IsInstalledCommand, ListForwardsCommand, LocalCommand, LogCommand, Logcat, LogcatCommand, Monkey, MonkeyCommand, Parser, ProcStat, Promise, RebootCommand, RemountCommand, ScreencapCommand, ShellCommand, StartActivityCommand, StartServiceCommand, Sync, SyncCommand, TcpCommand, TcpIpCommand, TcpUsbServer, TrackJdwpCommand, UninstallCommand, UsbCommand, WaitBootCompleteCommand, WaitForDeviceCommand, debug;
+
+  Monkey = require('adbkit-monkey');
+
+  Logcat = require('adbkit-logcat');
+
+  Promise = require('bluebird');
+
+  debug = require('debug')('adb:client');
+
+  Connection = require('./connection');
+
+  Sync = require('./sync');
+
+  Parser = require('./parser');
+
+  ProcStat = require('./proc/stat');
+
+  HostVersionCommand = require('./command/host/version');
+
+  HostConnectCommand = require('./command/host/connect');
+
+  HostDevicesCommand = require('./command/host/devices');
+
+  HostDevicesWithPathsCommand = require('./command/host/deviceswithpaths');
+
+  HostDisconnectCommand = require('./command/host/disconnect');
+
+  HostTrackDevicesCommand = require('./command/host/trackdevices');
+
+  HostKillCommand = require('./command/host/kill');
+
+  HostTransportCommand = require('./command/host/transport');
+
+  ClearCommand = require('./command/host-transport/clear');
+
+  FrameBufferCommand = require('./command/host-transport/framebuffer');
+
+  GetFeaturesCommand = require('./command/host-transport/getfeatures');
+
+  GetPackagesCommand = require('./command/host-transport/getpackages');
+
+  GetPropertiesCommand = require('./command/host-transport/getproperties');
+
+  InstallCommand = require('./command/host-transport/install');
+
+  IsInstalledCommand = require('./command/host-transport/isinstalled');
+
+  LocalCommand = require('./command/host-transport/local');
+
+  LogcatCommand = require('./command/host-transport/logcat');
+
+  LogCommand = require('./command/host-transport/log');
+
+  MonkeyCommand = require('./command/host-transport/monkey');
+
+  RebootCommand = require('./command/host-transport/reboot');
+
+  RemountCommand = require('./command/host-transport/remount');
+
+  ScreencapCommand = require('./command/host-transport/screencap');
+
+  ShellCommand = require('./command/host-transport/shell');
+
+  StartActivityCommand = require('./command/host-transport/startactivity');
+
+  StartServiceCommand = require('./command/host-transport/startservice');
+
+  SyncCommand = require('./command/host-transport/sync');
+
+  TcpCommand = require('./command/host-transport/tcp');
+
+  TcpIpCommand = require('./command/host-transport/tcpip');
+
+  TrackJdwpCommand = require('./command/host-transport/trackjdwp');
+
+  UninstallCommand = require('./command/host-transport/uninstall');
+
+  UsbCommand = require('./command/host-transport/usb');
+
+  WaitBootCompleteCommand = require('./command/host-transport/waitbootcomplete');
+
+  ForwardCommand = require('./command/host-serial/forward');
+
+  GetDevicePathCommand = require('./command/host-serial/getdevicepath');
+
+  GetSerialNoCommand = require('./command/host-serial/getserialno');
+
+  GetStateCommand = require('./command/host-serial/getstate');
+
+  ListForwardsCommand = require('./command/host-serial/listforwards');
+
+  WaitForDeviceCommand = require('./command/host-serial/waitfordevice');
+
+  TcpUsbServer = require('./tcpusb/server');
+
+  Client = (function() {
+    var NoUserOptionError;
+
+    function Client(options) {
+      var _base, _base1;
+      this.options = options != null ? options : {};
+      (_base = this.options).port || (_base.port = 5037);
+      (_base1 = this.options).bin || (_base1.bin = 'adb');
+    }
+
+    Client.prototype.createTcpUsbBridge = function(serial, options) {
+      return new TcpUsbServer(this, serial, options);
+    };
+
+    Client.prototype.connection = function() {
+      var conn, connectListener, errorListener, resolver;
+      resolver = Promise.defer();
+      conn = new Connection(this.options).on('error', errorListener = function(err) {
+        return resolver.reject(err);
+      }).on('connect', connectListener = function() {
+        return resolver.resolve(conn);
+      }).connect();
+      return resolver.promise["finally"](function() {
+        conn.removeListener('error', errorListener);
+        return conn.removeListener('connect', connectListener);
+      });
+    };
+
+    Client.prototype.version = function(callback) {
+      return this.connection().then(function(conn) {
+        return new HostVersionCommand(conn).execute();
+      }).nodeify(callback);
+    };
+
+    Client.prototype.connect = function(host, port, callback) {
+      var _ref;
+      if (port == null) {
+        port = 5555;
+      }
+      if (typeof port === 'function') {
+        callback = port;
+        port = 5555;
+      }
+      if (host.indexOf(':') !== -1) {
+        _ref = host.split(':', 2), host = _ref[0], port = _ref[1];
+      }
+      return this.connection().then(function(conn) {
+        return new HostConnectCommand(conn).execute(host, port);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.disconnect = function(host, port, callback) {
+      var _ref;
+      if (port == null) {
+        port = 5555;
+      }
+      if (typeof port === 'function') {
+        callback = port;
+        port = 5555;
+      }
+      if (host.indexOf(':') !== -1) {
+        _ref = host.split(':', 2), host = _ref[0], port = _ref[1];
+      }
+      return this.connection().then(function(conn) {
+        return new HostDisconnectCommand(conn).execute(host, port);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.listDevices = function(callback) {
+      return this.connection().then(function(conn) {
+        return new HostDevicesCommand(conn).execute();
+      }).nodeify(callback);
+    };
+
+    Client.prototype.listDevicesWithPaths = function(callback) {
+      return this.connection().then(function(conn) {
+        return new HostDevicesWithPathsCommand(conn).execute();
+      }).nodeify(callback);
+    };
+
+    Client.prototype.trackDevices = function(callback) {
+      return this.connection().then(function(conn) {
+        return new HostTrackDevicesCommand(conn).execute();
+      }).nodeify(callback);
+    };
+
+    Client.prototype.kill = function(callback) {
+      return this.connection().then(function(conn) {
+        return new HostKillCommand(conn).execute();
+      }).nodeify(callback);
+    };
+
+    Client.prototype.getSerialNo = function(serial, callback) {
+      return this.connection().then(function(conn) {
+        return new GetSerialNoCommand(conn).execute(serial);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.getDevicePath = function(serial, callback) {
+      return this.connection().then(function(conn) {
+        return new GetDevicePathCommand(conn).execute(serial);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.getState = function(serial, callback) {
+      return this.connection().then(function(conn) {
+        return new GetStateCommand(conn).execute(serial);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.getProperties = function(serial, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new GetPropertiesCommand(transport).execute();
+      }).nodeify(callback);
+    };
+
+    Client.prototype.getFeatures = function(serial, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new GetFeaturesCommand(transport).execute();
+      }).nodeify(callback);
+    };
+
+    Client.prototype.getPackages = function(serial, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new GetPackagesCommand(transport).execute();
+      }).nodeify(callback);
+    };
+
+    Client.prototype.getDHCPIpAddress = function(serial, iface, callback) {
+      if (iface == null) {
+        iface = 'wlan0';
+      }
+      if (typeof iface === 'function') {
+        callback = iface;
+        iface = 'wlan0';
+      }
+      return this.getProperties(serial).then(function(properties) {
+        var ip;
+        if (ip = properties["dhcp." + iface + ".ipaddress"]) {
+          return ip;
+        }
+        throw new Error("Unable to find ipaddress for '" + iface + "'");
+      });
+    };
+
+    Client.prototype.forward = function(serial, local, remote, callback) {
+      return this.connection().then(function(conn) {
+        return new ForwardCommand(conn).execute(serial, local, remote);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.listForwards = function(serial, callback) {
+      return this.connection().then(function(conn) {
+        return new ListForwardsCommand(conn).execute(serial);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.transport = function(serial, callback) {
+      return this.connection().then(function(conn) {
+        return new HostTransportCommand(conn).execute(serial)["return"](conn);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.shell = function(serial, command, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new ShellCommand(transport).execute(command);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.reboot = function(serial, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new RebootCommand(transport).execute();
+      }).nodeify(callback);
+    };
+
+    Client.prototype.remount = function(serial, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new RemountCommand(transport).execute();
+      }).nodeify(callback);
+    };
+
+    Client.prototype.trackJdwp = function(serial, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new TrackJdwpCommand(transport).execute();
+      }).nodeify(callback);
+    };
+
+    Client.prototype.framebuffer = function(serial, format, callback) {
+      if (format == null) {
+        format = 'raw';
+      }
+      if (typeof format === 'function') {
+        callback = format;
+        format = 'raw';
+      }
+      return this.transport(serial).then(function(transport) {
+        return new FrameBufferCommand(transport).execute(format);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.screencap = function(serial, callback) {
+      return this.transport(serial).then((function(_this) {
+        return function(transport) {
+          return new ScreencapCommand(transport).execute()["catch"](function(err) {
+            debug("Emulating screencap command due to '" + err + "'");
+            return _this.framebuffer(serial, 'png');
+          });
+        };
+      })(this)).nodeify(callback);
+    };
+
+    Client.prototype.openLocal = function(serial, path, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new LocalCommand(transport).execute(path);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.openLog = function(serial, name, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new LogCommand(transport).execute(name);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.openTcp = function(serial, port, host, callback) {
+      if (typeof host === 'function') {
+        callback = host;
+        host = void 0;
+      }
+      return this.transport(serial).then(function(transport) {
+        return new TcpCommand(transport).execute(port, host);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.openMonkey = function(serial, port, callback) {
+      var tryConnect;
+      if (port == null) {
+        port = 1080;
+      }
+      if (typeof port === 'function') {
+        callback = port;
+        port = 1080;
+      }
+      tryConnect = (function(_this) {
+        return function(times) {
+          return _this.openTcp(serial, port).then(function(stream) {
+            return Monkey.connectStream(stream);
+          })["catch"](function(err) {
+            if (times -= 1) {
+              debug("Monkey can't be reached, trying " + times + " more times");
+              return Promise.delay(100).then(function() {
+                return tryConnect(times);
+              });
+            } else {
+              throw err;
+            }
+          });
+        };
+      })(this);
+      return tryConnect(1)["catch"]((function(_this) {
+        return function(err) {
+          return _this.transport(serial).then(function(transport) {
+            return new MonkeyCommand(transport).execute(port);
+          }).then(function(out) {
+            return tryConnect(20).then(function(monkey) {
+              return monkey.once('end', function() {
+                return out.end();
+              });
+            });
+          });
+        };
+      })(this)).nodeify(callback);
+    };
+
+    Client.prototype.openLogcat = function(serial, options, callback) {
+      if (typeof options === 'function') {
+        callback = options;
+        options = {};
+      }
+      return this.transport(serial).then(function(transport) {
+        return new LogcatCommand(transport).execute(options);
+      }).then(function(stream) {
+        return Logcat.readStream(stream, {
+          fixLineFeeds: false
+        });
+      }).nodeify(callback);
+    };
+
+    Client.prototype.openProcStat = function(serial, callback) {
+      return this.syncService(serial).then(function(sync) {
+        return new ProcStat(sync);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.clear = function(serial, pkg, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new ClearCommand(transport).execute(pkg);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.install = function(serial, apk, callback) {
+      var temp;
+      temp = Sync.temp(typeof apk === 'string' ? apk : '_stream.apk');
+      return this.push(serial, apk, temp).then((function(_this) {
+        return function(transfer) {
+          var endListener, errorListener, resolver;
+          resolver = Promise.defer();
+          transfer.on('error', errorListener = function(err) {
+            return resolver.reject(err);
+          });
+          transfer.on('end', endListener = function() {
+            return resolver.resolve(_this.installRemote(serial, temp));
+          });
+          return resolver.promise["finally"](function() {
+            transfer.removeListener('error', errorListener);
+            return transfer.removeListener('end', endListener);
+          });
+        };
+      })(this)).nodeify(callback);
+    };
+
+    Client.prototype.installRemote = function(serial, apk, callback) {
+      return this.transport(serial).then((function(_this) {
+        return function(transport) {
+          return new InstallCommand(transport).execute(apk).then(function() {
+            return _this.shell(serial, ['rm', '-f', apk]);
+          }).then(function(stream) {
+            return new Parser(stream).readAll();
+          }).then(function(out) {
+            return true;
+          });
+        };
+      })(this)).nodeify(callback);
+    };
+
+    Client.prototype.uninstall = function(serial, pkg, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new UninstallCommand(transport).execute(pkg);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.isInstalled = function(serial, pkg, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new IsInstalledCommand(transport).execute(pkg);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.startActivity = function(serial, options, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new StartActivityCommand(transport).execute(options);
+      })["catch"](NoUserOptionError, (function(_this) {
+        return function() {
+          options.user = null;
+          return _this.startActivity(serial, options);
+        };
+      })(this)).nodeify(callback);
+    };
+
+    Client.prototype.startService = function(serial, options, callback) {
+      return this.transport(serial).then(function(transport) {
+        if (!(options.user || options.user === null)) {
+          options.user = 0;
+        }
+        return new StartServiceCommand(transport).execute(options);
+      })["catch"](NoUserOptionError, (function(_this) {
+        return function() {
+          options.user = null;
+          return _this.startService(serial, options);
+        };
+      })(this)).nodeify(callback);
+    };
+
+    Client.prototype.syncService = function(serial, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new SyncCommand(transport).execute();
+      }).nodeify(callback);
+    };
+
+    Client.prototype.stat = function(serial, path, callback) {
+      return this.syncService(serial).then(function(sync) {
+        return sync.stat(path)["finally"](function() {
+          return sync.end();
+        });
+      }).nodeify(callback);
+    };
+
+    Client.prototype.readdir = function(serial, path, callback) {
+      return this.syncService(serial).then(function(sync) {
+        return sync.readdir(path)["finally"](function() {
+          return sync.end();
+        });
+      }).nodeify(callback);
+    };
+
+    Client.prototype.pull = function(serial, path, callback) {
+      return this.syncService(serial).then(function(sync) {
+        return sync.pull(path).on('end', function() {
+          return sync.end();
+        });
+      }).nodeify(callback);
+    };
+
+    Client.prototype.push = function(serial, contents, path, mode, callback) {
+      if (typeof mode === 'function') {
+        callback = mode;
+        mode = void 0;
+      }
+      return this.syncService(serial).then(function(sync) {
+        return sync.push(contents, path, mode).on('end', function() {
+          return sync.end();
+        });
+      }).nodeify(callback);
+    };
+
+    Client.prototype.tcpip = function(serial, port, callback) {
+      if (port == null) {
+        port = 5555;
+      }
+      if (typeof port === 'function') {
+        callback = port;
+        port = 5555;
+      }
+      return this.transport(serial).then(function(transport) {
+        return new TcpIpCommand(transport).execute(port);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.usb = function(serial, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new UsbCommand(transport).execute();
+      }).nodeify(callback);
+    };
+
+    Client.prototype.waitBootComplete = function(serial, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new WaitBootCompleteCommand(transport).execute();
+      }).nodeify(callback);
+    };
+
+    Client.prototype.waitForDevice = function(serial, callback) {
+      return this.connection().then(function(conn) {
+        return new WaitForDeviceCommand(conn).execute(serial);
+      }).nodeify(callback);
+    };
+
+    NoUserOptionError = function(err) {
+      return err.message.indexOf('--user') !== -1;
+    };
+
+    return Client;
+
+  })();
+
+  module.exports = Client;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command.js
new file mode 100644
index 0000000..a01f7d3
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command.js
@@ -0,0 +1,48 @@
+(function() {
+  var Command, Parser, Protocol, debug;
+
+  debug = require('debug')('adb:command');
+
+  Parser = require('./parser');
+
+  Protocol = require('./protocol');
+
+  Command = (function() {
+    var RE_SQUOT;
+
+    RE_SQUOT = /'/g;
+
+    function Command(connection) {
+      this.connection = connection;
+      this.parser = this.connection.parser;
+      this.protocol = Protocol;
+    }
+
+    Command.prototype.execute = function() {
+      throw new Exception('Missing implementation');
+    };
+
+    Command.prototype._send = function(data) {
+      var encoded;
+      encoded = Protocol.encodeData(data);
+      debug("Send '" + encoded + "'");
+      this.connection.write(encoded);
+      return this;
+    };
+
+    Command.prototype._escape = function(arg) {
+      switch (typeof arg) {
+        case 'number':
+          return arg;
+        default:
+          return "'" + arg.toString().replace(RE_SQUOT, "'\"'\"'") + "'";
+      }
+    };
+
+    return Command;
+
+  })();
+
+  module.exports = Command;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-serial/forward.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-serial/forward.js
new file mode 100644
index 0000000..597d37f
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-serial/forward.js
@@ -0,0 +1,48 @@
+(function() {
+  var Command, ForwardCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  ForwardCommand = (function(_super) {
+    __extends(ForwardCommand, _super);
+
+    function ForwardCommand() {
+      return ForwardCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    ForwardCommand.prototype.execute = function(serial, local, remote) {
+      this._send("host-serial:" + serial + ":forward:" + local + ";" + remote);
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readAscii(4).then(function(reply) {
+                switch (reply) {
+                  case Protocol.OKAY:
+                    return true;
+                  case Protocol.FAIL:
+                    return _this.parser.readError();
+                  default:
+                    return _this.parser.unexpected(reply, 'OKAY or FAIL');
+                }
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return ForwardCommand;
+
+  })(Command);
+
+  module.exports = ForwardCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-serial/getdevicepath.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-serial/getdevicepath.js
new file mode 100644
index 0000000..b171cae
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-serial/getdevicepath.js
@@ -0,0 +1,41 @@
+(function() {
+  var Command, GetDevicePathCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  GetDevicePathCommand = (function(_super) {
+    __extends(GetDevicePathCommand, _super);
+
+    function GetDevicePathCommand() {
+      return GetDevicePathCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    GetDevicePathCommand.prototype.execute = function(serial) {
+      this._send("host-serial:" + serial + ":get-devpath");
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readValue().then(function(value) {
+                return value.toString();
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return GetDevicePathCommand;
+
+  })(Command);
+
+  module.exports = GetDevicePathCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-serial/getserialno.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-serial/getserialno.js
new file mode 100644
index 0000000..28f858e
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-serial/getserialno.js
@@ -0,0 +1,41 @@
+(function() {
+  var Command, GetSerialNoCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  GetSerialNoCommand = (function(_super) {
+    __extends(GetSerialNoCommand, _super);
+
+    function GetSerialNoCommand() {
+      return GetSerialNoCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    GetSerialNoCommand.prototype.execute = function(serial) {
+      this._send("host-serial:" + serial + ":get-serialno");
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readValue().then(function(value) {
+                return value.toString();
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return GetSerialNoCommand;
+
+  })(Command);
+
+  module.exports = GetSerialNoCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-serial/getstate.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-serial/getstate.js
new file mode 100644
index 0000000..fa6378f
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-serial/getstate.js
@@ -0,0 +1,41 @@
+(function() {
+  var Command, GetStateCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  GetStateCommand = (function(_super) {
+    __extends(GetStateCommand, _super);
+
+    function GetStateCommand() {
+      return GetStateCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    GetStateCommand.prototype.execute = function(serial) {
+      this._send("host-serial:" + serial + ":get-state");
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readValue().then(function(value) {
+                return value.toString();
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return GetStateCommand;
+
+  })(Command);
+
+  module.exports = GetStateCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-serial/listforwards.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-serial/listforwards.js
new file mode 100644
index 0000000..e426d9d
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-serial/listforwards.js
@@ -0,0 +1,59 @@
+(function() {
+  var Command, ListForwardsCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  ListForwardsCommand = (function(_super) {
+    __extends(ListForwardsCommand, _super);
+
+    function ListForwardsCommand() {
+      return ListForwardsCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    ListForwardsCommand.prototype.execute = function(serial) {
+      this._send("host-serial:" + serial + ":list-forward");
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readValue().then(function(value) {
+                return _this._parseForwards(value);
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    ListForwardsCommand.prototype._parseForwards = function(value) {
+      var forward, forwards, local, remote, serial, _i, _len, _ref, _ref1;
+      forwards = [];
+      _ref = value.toString().split('\n');
+      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+        forward = _ref[_i];
+        if (forward) {
+          _ref1 = forward.split(/\s+/), serial = _ref1[0], local = _ref1[1], remote = _ref1[2];
+          forwards.push({
+            serial: serial,
+            local: local,
+            remote: remote
+          });
+        }
+      }
+      return forwards;
+    };
+
+    return ListForwardsCommand;
+
+  })(Command);
+
+  module.exports = ListForwardsCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-serial/waitfordevice.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-serial/waitfordevice.js
new file mode 100644
index 0000000..efa1a0b
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-serial/waitfordevice.js
@@ -0,0 +1,48 @@
+(function() {
+  var Command, Protocol, WaitForDeviceCommand,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  WaitForDeviceCommand = (function(_super) {
+    __extends(WaitForDeviceCommand, _super);
+
+    function WaitForDeviceCommand() {
+      return WaitForDeviceCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    WaitForDeviceCommand.prototype.execute = function(serial) {
+      this._send("host-serial:" + serial + ":wait-for-any");
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readAscii(4).then(function(reply) {
+                switch (reply) {
+                  case Protocol.OKAY:
+                    return serial;
+                  case Protocol.FAIL:
+                    return _this.parser.readError();
+                  default:
+                    return _this.parser.unexpected(reply, 'OKAY or FAIL');
+                }
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return WaitForDeviceCommand;
+
+  })(Command);
+
+  module.exports = WaitForDeviceCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/clear.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/clear.js
new file mode 100644
index 0000000..7a8acb0
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/clear.js
@@ -0,0 +1,47 @@
+(function() {
+  var ClearCommand, Command, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  ClearCommand = (function(_super) {
+    __extends(ClearCommand, _super);
+
+    function ClearCommand() {
+      return ClearCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    ClearCommand.prototype.execute = function(pkg) {
+      this._send("shell:pm clear " + pkg);
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.searchLine(/^(Success|Failed)$/).then(function(result) {
+                switch (result[0]) {
+                  case 'Success':
+                    return true;
+                  case 'Failed':
+                    _this.connection.end();
+                    throw new Error("Package '" + pkg + "' could not be cleared");
+                }
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return ClearCommand;
+
+  })(Command);
+
+  module.exports = ClearCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/framebuffer.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/framebuffer.js
new file mode 100644
index 0000000..5689f3c
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/framebuffer.js
@@ -0,0 +1,117 @@
+(function() {
+  var Assert, Command, FrameBufferCommand, Protocol, RgbTransform, debug, spawn,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Assert = require('assert');
+
+  spawn = require('child_process').spawn;
+
+  debug = require('debug')('adb:command:framebuffer');
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  RgbTransform = require('../../framebuffer/rgbtransform');
+
+  FrameBufferCommand = (function(_super) {
+    __extends(FrameBufferCommand, _super);
+
+    function FrameBufferCommand() {
+      return FrameBufferCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    FrameBufferCommand.prototype.execute = function(format) {
+      this._send('framebuffer:');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readBytes(52).then(function(header) {
+                var meta, stream;
+                meta = _this._parseHeader(header);
+                switch (format) {
+                  case 'raw':
+                    stream = _this.parser.raw();
+                    stream.meta = meta;
+                    return stream;
+                  default:
+                    stream = _this._convert(meta);
+                    stream.meta = meta;
+                    return stream;
+                }
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    FrameBufferCommand.prototype._convert = function(meta, format, raw) {
+      var proc, transform;
+      debug("Converting raw framebuffer stream into " + (format.toUpperCase()));
+      switch (meta.format) {
+        case 'rgb':
+        case 'rgba':
+          break;
+        default:
+          debug("Silently transforming '" + meta.format + "' into 'rgb' for `gm`");
+          transform = new RgbTransform(meta);
+          meta.format = 'rgb';
+          raw = this.parser.raw().pipe(transform);
+      }
+      proc = spawn('gm', ['convert', '-size', "" + meta.width + "x" + meta.height, "" + meta.format + ":-", "" + format + ":-"]);
+      raw.pipe(proc.stdin);
+      return proc.stdout;
+    };
+
+    FrameBufferCommand.prototype._parseHeader = function(header) {
+      var meta, offset;
+      meta = {};
+      offset = 0;
+      meta.version = header.readUInt32LE(offset);
+      if (meta.version === 16) {
+        throw new Error('Old-style raw images are not supported');
+      }
+      offset += 4;
+      meta.bpp = header.readUInt32LE(offset);
+      offset += 4;
+      meta.size = header.readUInt32LE(offset);
+      offset += 4;
+      meta.width = header.readUInt32LE(offset);
+      offset += 4;
+      meta.height = header.readUInt32LE(offset);
+      offset += 4;
+      meta.red_offset = header.readUInt32LE(offset);
+      offset += 4;
+      meta.red_length = header.readUInt32LE(offset);
+      offset += 4;
+      meta.blue_offset = header.readUInt32LE(offset);
+      offset += 4;
+      meta.blue_length = header.readUInt32LE(offset);
+      offset += 4;
+      meta.green_offset = header.readUInt32LE(offset);
+      offset += 4;
+      meta.green_length = header.readUInt32LE(offset);
+      offset += 4;
+      meta.alpha_offset = header.readUInt32LE(offset);
+      offset += 4;
+      meta.alpha_length = header.readUInt32LE(offset);
+      meta.format = meta.blue_offset === 0 ? 'bgr' : 'rgb';
+      if (meta.bpp === 32 || meta.alpha_length) {
+        meta.format += 'a';
+      }
+      return meta;
+    };
+
+    return FrameBufferCommand;
+
+  })(Command);
+
+  module.exports = FrameBufferCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/getfeatures.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/getfeatures.js
new file mode 100644
index 0000000..be6083d
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/getfeatures.js
@@ -0,0 +1,54 @@
+(function() {
+  var Command, GetFeaturesCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  GetFeaturesCommand = (function(_super) {
+    var RE_FEATURE;
+
+    __extends(GetFeaturesCommand, _super);
+
+    function GetFeaturesCommand() {
+      return GetFeaturesCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    RE_FEATURE = /^feature:(.*?)(?:=(.*?))?\r?$/gm;
+
+    GetFeaturesCommand.prototype.execute = function() {
+      this._send('shell:pm list features 2>/dev/null');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readAll().then(function(data) {
+                return _this._parseFeatures(data.toString());
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    GetFeaturesCommand.prototype._parseFeatures = function(value) {
+      var features, match;
+      features = {};
+      while (match = RE_FEATURE.exec(value)) {
+        features[match[1]] = match[2] || true;
+      }
+      return features;
+    };
+
+    return GetFeaturesCommand;
+
+  })(Command);
+
+  module.exports = GetFeaturesCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/getpackages.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/getpackages.js
new file mode 100644
index 0000000..6e98fdf
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/getpackages.js
@@ -0,0 +1,54 @@
+(function() {
+  var Command, GetPackagesCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  GetPackagesCommand = (function(_super) {
+    var RE_PACKAGE;
+
+    __extends(GetPackagesCommand, _super);
+
+    function GetPackagesCommand() {
+      return GetPackagesCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    RE_PACKAGE = /^package:(.*?)\r?$/gm;
+
+    GetPackagesCommand.prototype.execute = function() {
+      this._send('shell:pm list packages 2>/dev/null');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readAll().then(function(data) {
+                return _this._parsePackages(data.toString());
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    GetPackagesCommand.prototype._parsePackages = function(value) {
+      var features, match;
+      features = [];
+      while (match = RE_PACKAGE.exec(value)) {
+        features.push(match[1]);
+      }
+      return features;
+    };
+
+    return GetPackagesCommand;
+
+  })(Command);
+
+  module.exports = GetPackagesCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/getproperties.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/getproperties.js
new file mode 100644
index 0000000..1c3756b
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/getproperties.js
@@ -0,0 +1,56 @@
+(function() {
+  var Command, GetPropertiesCommand, LineTransform, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  LineTransform = require('../../linetransform');
+
+  GetPropertiesCommand = (function(_super) {
+    var RE_KEYVAL;
+
+    __extends(GetPropertiesCommand, _super);
+
+    function GetPropertiesCommand() {
+      return GetPropertiesCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    RE_KEYVAL = /^\[([\s\S]*?)\]: \[([\s\S]*?)\]\r?$/gm;
+
+    GetPropertiesCommand.prototype.execute = function() {
+      this._send('shell:getprop');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readAll().then(function(data) {
+                return _this._parseProperties(data.toString());
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    GetPropertiesCommand.prototype._parseProperties = function(value) {
+      var match, properties;
+      properties = {};
+      while (match = RE_KEYVAL.exec(value)) {
+        properties[match[1]] = match[2];
+      }
+      return properties;
+    };
+
+    return GetPropertiesCommand;
+
+  })(Command);
+
+  module.exports = GetPropertiesCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/install.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/install.js
new file mode 100644
index 0000000..634dbab
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/install.js
@@ -0,0 +1,51 @@
+(function() {
+  var Command, InstallCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  InstallCommand = (function(_super) {
+    __extends(InstallCommand, _super);
+
+    function InstallCommand() {
+      return InstallCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    InstallCommand.prototype.execute = function(apk) {
+      this._send("shell:pm install -r '" + apk + "'");
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.searchLine(/^(Success|Failure \[(.*?)\])$/).then(function(match) {
+                var code, err;
+                if (match[1] === 'Success') {
+                  return true;
+                } else {
+                  code = match[2];
+                  err = new Error("" + apk + " could not be installed [" + code + "]");
+                  err.code = code;
+                  throw err;
+                }
+              })["finally"](function() {
+                return _this.parser.readAll();
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return InstallCommand;
+
+  })(Command);
+
+  module.exports = InstallCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/isinstalled.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/isinstalled.js
new file mode 100644
index 0000000..463c0f9
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/isinstalled.js
@@ -0,0 +1,50 @@
+(function() {
+  var Command, IsInstalledCommand, Parser, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  Parser = require('../../parser');
+
+  IsInstalledCommand = (function(_super) {
+    __extends(IsInstalledCommand, _super);
+
+    function IsInstalledCommand() {
+      return IsInstalledCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    IsInstalledCommand.prototype.execute = function(pkg) {
+      this._send("shell:pm path " + pkg + " 2>/dev/null");
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readAscii(8).then(function(reply) {
+                switch (reply) {
+                  case 'package:':
+                    return true;
+                  default:
+                    return _this.parser.unexpected(reply, "'package:'");
+                }
+              })["catch"](Parser.PrematureEOFError, function(err) {
+                return false;
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return IsInstalledCommand;
+
+  })(Command);
+
+  module.exports = IsInstalledCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/local.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/local.js
new file mode 100644
index 0000000..fc764a1
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/local.js
@@ -0,0 +1,39 @@
+(function() {
+  var Command, LocalCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  LocalCommand = (function(_super) {
+    __extends(LocalCommand, _super);
+
+    function LocalCommand() {
+      return LocalCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    LocalCommand.prototype.execute = function(path) {
+      this._send(/:/.test(path) ? path : "localfilesystem:" + path);
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.raw();
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return LocalCommand;
+
+  })(Command);
+
+  module.exports = LocalCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/log.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/log.js
new file mode 100644
index 0000000..1e8ddc9
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/log.js
@@ -0,0 +1,39 @@
+(function() {
+  var Command, LogCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  LogCommand = (function(_super) {
+    __extends(LogCommand, _super);
+
+    function LogCommand() {
+      return LogCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    LogCommand.prototype.execute = function(name) {
+      this._send("log:" + name);
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.raw();
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return LogCommand;
+
+  })(Command);
+
+  module.exports = LogCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/logcat.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/logcat.js
new file mode 100644
index 0000000..778ec34
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/logcat.js
@@ -0,0 +1,49 @@
+(function() {
+  var Command, LineTransform, LogcatCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  LineTransform = require('../../linetransform');
+
+  LogcatCommand = (function(_super) {
+    __extends(LogcatCommand, _super);
+
+    function LogcatCommand() {
+      return LogcatCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    LogcatCommand.prototype.execute = function(options) {
+      var cmd;
+      if (options == null) {
+        options = {};
+      }
+      cmd = 'logcat -B *:I 2>/dev/null';
+      if (options.clear) {
+        cmd = "logcat -c 2>/dev/null && " + cmd;
+      }
+      this._send("shell:" + cmd);
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.raw().pipe(new LineTransform);
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return LogcatCommand;
+
+  })(Command);
+
+  module.exports = LogcatCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/monkey.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/monkey.js
new file mode 100644
index 0000000..d913e9e
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/monkey.js
@@ -0,0 +1,45 @@
+(function() {
+  var Command, MonkeyCommand, Promise, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Promise = require('bluebird');
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  MonkeyCommand = (function(_super) {
+    __extends(MonkeyCommand, _super);
+
+    function MonkeyCommand() {
+      return MonkeyCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    MonkeyCommand.prototype.execute = function(port) {
+      this._send("shell:EXTERNAL_STORAGE=/data/local/tmp monkey --port " + port + " -v");
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.searchLine(/^:Monkey:/).timeout(1000).then(function() {
+                return _this.parser.raw();
+              })["catch"](Promise.TimeoutError, function(err) {
+                return _this.parser.raw();
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return MonkeyCommand;
+
+  })(Command);
+
+  module.exports = MonkeyCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/reboot.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/reboot.js
new file mode 100644
index 0000000..57a8b51
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/reboot.js
@@ -0,0 +1,39 @@
+(function() {
+  var Command, Protocol, RebootCommand,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  RebootCommand = (function(_super) {
+    __extends(RebootCommand, _super);
+
+    function RebootCommand() {
+      return RebootCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    RebootCommand.prototype.execute = function() {
+      this._send('reboot:');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readAll()["return"](true);
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return RebootCommand;
+
+  })(Command);
+
+  module.exports = RebootCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/remount.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/remount.js
new file mode 100644
index 0000000..173f45d
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/remount.js
@@ -0,0 +1,39 @@
+(function() {
+  var Command, Protocol, RemountCommand,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  RemountCommand = (function(_super) {
+    __extends(RemountCommand, _super);
+
+    function RemountCommand() {
+      return RemountCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    RemountCommand.prototype.execute = function() {
+      this._send('remount:');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return true;
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return RemountCommand;
+
+  })(Command);
+
+  module.exports = RemountCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/screencap.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/screencap.js
new file mode 100644
index 0000000..30098c2
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/screencap.js
@@ -0,0 +1,57 @@
+(function() {
+  var Command, LineTransform, Parser, Promise, Protocol, ScreencapCommand,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Promise = require('bluebird');
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  Parser = require('../../parser');
+
+  LineTransform = require('../../linetransform');
+
+  ScreencapCommand = (function(_super) {
+    __extends(ScreencapCommand, _super);
+
+    function ScreencapCommand() {
+      return ScreencapCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    ScreencapCommand.prototype.execute = function() {
+      this._send('shell:screencap -p 2>/dev/null');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          var endListener, out, readableListener, resolver;
+          switch (reply) {
+            case Protocol.OKAY:
+              resolver = Promise.defer();
+              out = _this.parser.raw().pipe(new LineTransform);
+              out.on('readable', readableListener = function() {
+                return resolver.resolve(out);
+              });
+              out.on('end', endListener = function() {
+                return resolver.reject(new Error('Unable to run screencap command'));
+              });
+              return resolver.promise["finally"](function() {
+                out.removeListener('end', endListener);
+                return out.removeListener('readable', readableListener);
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return ScreencapCommand;
+
+  })(Command);
+
+  module.exports = ScreencapCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/shell.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/shell.js
new file mode 100644
index 0000000..4ac2074
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/shell.js
@@ -0,0 +1,42 @@
+(function() {
+  var Command, Protocol, ShellCommand,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  ShellCommand = (function(_super) {
+    __extends(ShellCommand, _super);
+
+    function ShellCommand() {
+      return ShellCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    ShellCommand.prototype.execute = function(command) {
+      if (Array.isArray(command)) {
+        command = command.map(this._escape).join(' ');
+      }
+      this._send("shell:" + command);
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.raw();
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return ShellCommand;
+
+  })(Command);
+
+  module.exports = ShellCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/startactivity.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/startactivity.js
new file mode 100644
index 0000000..572abda
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/startactivity.js
@@ -0,0 +1,187 @@
+(function() {
+  var Command, Parser, Protocol, StartActivityCommand,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  Parser = require('../../parser');
+
+  StartActivityCommand = (function(_super) {
+    var EXTRA_TYPES, RE_ERROR;
+
+    __extends(StartActivityCommand, _super);
+
+    function StartActivityCommand() {
+      return StartActivityCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    RE_ERROR = /^Error: (.*)$/;
+
+    EXTRA_TYPES = {
+      string: 's',
+      "null": 'sn',
+      bool: 'z',
+      int: 'i',
+      long: 'l',
+      float: 'l',
+      uri: 'u',
+      component: 'cn'
+    };
+
+    StartActivityCommand.prototype.execute = function(options) {
+      var args;
+      args = this._intentArgs(options);
+      if (options.debug) {
+        args.push('-D');
+      }
+      if (options.wait) {
+        args.push('-W');
+      }
+      if (options.user || options.user === 0) {
+        args.push('--user', this._escape(options.user));
+      }
+      return this._run('start', args);
+    };
+
+    StartActivityCommand.prototype._run = function(command, args) {
+      this._send("shell:am " + command + " " + (args.join(' ')));
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.searchLine(RE_ERROR)["finally"](function() {
+                return _this.connection.end();
+              }).then(function(match) {
+                throw new Error(match[1]);
+              })["catch"](Parser.PrematureEOFError, function(err) {
+                return true;
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    StartActivityCommand.prototype._intentArgs = function(options) {
+      var args;
+      args = [];
+      if (options.extras) {
+        args.push.apply(args, this._formatExtras(options.extras));
+      }
+      if (options.action) {
+        args.push('-a', this._escape(options.action));
+      }
+      if (options.data) {
+        args.push('-d', this._escape(options.data));
+      }
+      if (options.mimeType) {
+        args.push('-t', this._escape(options.mimeType));
+      }
+      if (options.category) {
+        if (Array.isArray(options.category)) {
+          options.category.forEach((function(_this) {
+            return function(category) {
+              return args.push('-c', _this._escape(category));
+            };
+          })(this));
+        } else {
+          args.push('-c', this._escape(options.category));
+        }
+      }
+      if (options.component) {
+        args.push('-n', this._escape(options.component));
+      }
+      if (options.flags) {
+        args.push('-f', this._escape(options.flags));
+      }
+      return args;
+    };
+
+    StartActivityCommand.prototype._formatExtras = function(extras) {
+      if (!extras) {
+        return [];
+      }
+      if (Array.isArray(extras)) {
+        return extras.reduce((function(_this) {
+          return function(all, extra) {
+            return all.concat(_this._formatLongExtra(extra));
+          };
+        })(this), []);
+      } else {
+        return Object.keys(extras).reduce((function(_this) {
+          return function(all, key) {
+            return all.concat(_this._formatShortExtra(key, extras[key]));
+          };
+        })(this), []);
+      }
+    };
+
+    StartActivityCommand.prototype._formatShortExtra = function(key, value) {
+      var sugared;
+      sugared = {
+        key: key
+      };
+      if (value === null) {
+        sugared.type = 'null';
+      } else if (Array.isArray(value)) {
+        throw new Error("Refusing to format array value '" + key + "' using short syntax; empty array would cause unpredictable results due to unknown type. Please use long syntax instead.");
+      } else {
+        switch (typeof value) {
+          case 'string':
+            sugared.type = 'string';
+            sugared.value = value;
+            break;
+          case 'boolean':
+            sugared.type = 'bool';
+            sugared.value = value;
+            break;
+          case 'number':
+            sugared.type = 'int';
+            sugared.value = value;
+            break;
+          case 'object':
+            sugared = value;
+            sugared.key = key;
+        }
+      }
+      return this._formatLongExtra(sugared);
+    };
+
+    StartActivityCommand.prototype._formatLongExtra = function(extra) {
+      var args, type;
+      args = [];
+      if (!extra.type) {
+        extra.type = 'string';
+      }
+      type = EXTRA_TYPES[extra.type];
+      if (!type) {
+        throw new Error("Unsupported type '" + extra.type + "' for extra '" + extra.key + "'");
+      }
+      if (extra.type === 'null') {
+        args.push("--e" + type);
+        args.push(this._escape(extra.key));
+      } else if (Array.isArray(extra.value)) {
+        args.push("--e" + type + "a");
+        args.push(this._escape(extra.key));
+        args.push(this._escape(extra.value.join(',')));
+      } else {
+        args.push("--e" + type);
+        args.push(this._escape(extra.key));
+        args.push(this._escape(extra.value));
+      }
+      return args;
+    };
+
+    return StartActivityCommand;
+
+  })(Command);
+
+  module.exports = StartActivityCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/startservice.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/startservice.js
new file mode 100644
index 0000000..a119a36
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/startservice.js
@@ -0,0 +1,36 @@
+(function() {
+  var Command, Parser, Protocol, StartActivityCommand, StartServiceCommand,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  Parser = require('../../parser');
+
+  StartActivityCommand = require('./startactivity');
+
+  StartServiceCommand = (function(_super) {
+    __extends(StartServiceCommand, _super);
+
+    function StartServiceCommand() {
+      return StartServiceCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    StartServiceCommand.prototype.execute = function(options) {
+      var args;
+      args = this._intentArgs(options);
+      if (options.user || options.user === 0) {
+        args.push('--user', this._escape(options.user));
+      }
+      return this._run('startservice', args);
+    };
+
+    return StartServiceCommand;
+
+  })(StartActivityCommand);
+
+  module.exports = StartServiceCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/sync.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/sync.js
new file mode 100644
index 0000000..638cc6e
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/sync.js
@@ -0,0 +1,41 @@
+(function() {
+  var Command, Protocol, Sync, SyncCommand,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  Sync = require('../../sync');
+
+  SyncCommand = (function(_super) {
+    __extends(SyncCommand, _super);
+
+    function SyncCommand() {
+      return SyncCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    SyncCommand.prototype.execute = function() {
+      this._send('sync:');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return new Sync(_this.connection);
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return SyncCommand;
+
+  })(Command);
+
+  module.exports = SyncCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/tcp.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/tcp.js
new file mode 100644
index 0000000..05c7ec2
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/tcp.js
@@ -0,0 +1,39 @@
+(function() {
+  var Command, Protocol, TcpCommand,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  TcpCommand = (function(_super) {
+    __extends(TcpCommand, _super);
+
+    function TcpCommand() {
+      return TcpCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    TcpCommand.prototype.execute = function(port, host) {
+      this._send(("tcp:" + port) + (host ? ":" + host : ''));
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.raw();
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return TcpCommand;
+
+  })(Command);
+
+  module.exports = TcpCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/tcpip.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/tcpip.js
new file mode 100644
index 0000000..821b5fa
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/tcpip.js
@@ -0,0 +1,51 @@
+(function() {
+  var Command, LineTransform, Protocol, TcpIpCommand,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  LineTransform = require('../../linetransform');
+
+  TcpIpCommand = (function(_super) {
+    var RE_OK;
+
+    __extends(TcpIpCommand, _super);
+
+    function TcpIpCommand() {
+      return TcpIpCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    RE_OK = /restarting in/;
+
+    TcpIpCommand.prototype.execute = function(port) {
+      this._send("tcpip:" + port);
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readAll().then(function(value) {
+                if (RE_OK.test(value)) {
+                  return port;
+                } else {
+                  throw new Error(value.toString().trim());
+                }
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return TcpIpCommand;
+
+  })(Command);
+
+  module.exports = TcpIpCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/trackjdwp.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/trackjdwp.js
new file mode 100644
index 0000000..07ec6a9
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/trackjdwp.js
@@ -0,0 +1,123 @@
+(function() {
+  var Command, EventEmitter, Parser, Promise, Protocol, TrackJdwpCommand,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  EventEmitter = require('events').EventEmitter;
+
+  Promise = require('bluebird');
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  Parser = require('../../parser');
+
+  TrackJdwpCommand = (function(_super) {
+    var Tracker;
+
+    __extends(TrackJdwpCommand, _super);
+
+    function TrackJdwpCommand() {
+      return TrackJdwpCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    TrackJdwpCommand.prototype.execute = function() {
+      this._send('track-jdwp');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return new Tracker(_this);
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    Tracker = (function(_super1) {
+      __extends(Tracker, _super1);
+
+      function Tracker(command) {
+        this.command = command;
+        this.pids = [];
+        this.pidMap = Object.create(null);
+        this.reader = this.read()["catch"](Parser.PrematureEOFError, (function(_this) {
+          return function(err) {
+            return _this.emit('end');
+          };
+        })(this))["catch"](Promise.CancellationError, (function(_this) {
+          return function(err) {
+            _this.command.connection.end();
+            return _this.emit('end');
+          };
+        })(this))["catch"]((function(_this) {
+          return function(err) {
+            _this.command.connection.end();
+            _this.emit('error', err);
+            return _this.emit('end');
+          };
+        })(this));
+      }
+
+      Tracker.prototype.read = function() {
+        return this.command.parser.readValue().cancellable().then((function(_this) {
+          return function(list) {
+            var maybeEmpty, pids;
+            pids = list.toString().split('\n');
+            if (maybeEmpty = pids.pop()) {
+              pids.push(maybeEmpty);
+            }
+            return _this.update(pids);
+          };
+        })(this));
+      };
+
+      Tracker.prototype.update = function(newList) {
+        var changeSet, newMap, pid, _i, _j, _len, _len1, _ref;
+        changeSet = {
+          removed: [],
+          added: []
+        };
+        newMap = Object.create(null);
+        for (_i = 0, _len = newList.length; _i < _len; _i++) {
+          pid = newList[_i];
+          if (!this.pidMap[pid]) {
+            changeSet.added.push(pid);
+            this.emit('add', pid);
+            newMap[pid] = pid;
+          }
+        }
+        _ref = this.pids;
+        for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) {
+          pid = _ref[_j];
+          if (!newMap[pid]) {
+            changeSet.removed.push(pid);
+            this.emit('remove', pid);
+          }
+        }
+        this.pids = newList;
+        this.pidMap = newMap;
+        this.emit('changeSet', changeSet, newList);
+        return this;
+      };
+
+      Tracker.prototype.end = function() {
+        this.reader.cancel();
+        return this;
+      };
+
+      return Tracker;
+
+    })(EventEmitter);
+
+    return TrackJdwpCommand;
+
+  })(Command);
+
+  module.exports = TrackJdwpCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/uninstall.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/uninstall.js
new file mode 100644
index 0000000..0fdb286
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/uninstall.js
@@ -0,0 +1,47 @@
+(function() {
+  var Command, Protocol, UninstallCommand,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  UninstallCommand = (function(_super) {
+    __extends(UninstallCommand, _super);
+
+    function UninstallCommand() {
+      return UninstallCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    UninstallCommand.prototype.execute = function(pkg) {
+      this._send("shell:pm uninstall " + pkg + " 2>/dev/null");
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readAscii(7).then(function(reply) {
+                switch (reply) {
+                  case 'Success':
+                  case 'Failure':
+                    return true;
+                  default:
+                    return _this.parser.unexpected(reply, "'Success' or 'Failure'");
+                }
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, "OKAY or FAIL");
+          }
+        };
+      })(this));
+    };
+
+    return UninstallCommand;
+
+  })(Command);
+
+  module.exports = UninstallCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/usb.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/usb.js
new file mode 100644
index 0000000..8c5e797
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/usb.js
@@ -0,0 +1,51 @@
+(function() {
+  var Command, LineTransform, Protocol, UsbCommand,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  LineTransform = require('../../linetransform');
+
+  UsbCommand = (function(_super) {
+    var RE_OK;
+
+    __extends(UsbCommand, _super);
+
+    function UsbCommand() {
+      return UsbCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    RE_OK = /restarting in/;
+
+    UsbCommand.prototype.execute = function() {
+      this._send('usb:');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readAll().then(function(value) {
+                if (RE_OK.test(value)) {
+                  return true;
+                } else {
+                  throw new Error(value.toString().trim());
+                }
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return UsbCommand;
+
+  })(Command);
+
+  module.exports = UsbCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/waitbootcomplete.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/waitbootcomplete.js
new file mode 100644
index 0000000..739e397
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host-transport/waitbootcomplete.js
@@ -0,0 +1,45 @@
+(function() {
+  var Command, Protocol, WaitBootCompleteCommand, debug,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  debug = require('debug')('adb:command:waitboot');
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  WaitBootCompleteCommand = (function(_super) {
+    __extends(WaitBootCompleteCommand, _super);
+
+    function WaitBootCompleteCommand() {
+      return WaitBootCompleteCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    WaitBootCompleteCommand.prototype.execute = function() {
+      this._send('shell:while getprop sys.boot_completed 2>/dev/null; do sleep 1; done');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.searchLine(/^1$/)["finally"](function() {
+                return _this.connection.end();
+              }).then(function() {
+                return true;
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return WaitBootCompleteCommand;
+
+  })(Command);
+
+  module.exports = WaitBootCompleteCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host/connect.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host/connect.js
new file mode 100644
index 0000000..fd9da1c
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host/connect.js
@@ -0,0 +1,49 @@
+(function() {
+  var Command, ConnectCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  ConnectCommand = (function(_super) {
+    var RE_OK;
+
+    __extends(ConnectCommand, _super);
+
+    function ConnectCommand() {
+      return ConnectCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    RE_OK = /connected to|already connected/;
+
+    ConnectCommand.prototype.execute = function(host, port) {
+      this._send("host:connect:" + host + ":" + port);
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readValue().then(function(value) {
+                if (RE_OK.test(value)) {
+                  return "" + host + ":" + port;
+                } else {
+                  throw new Error(value.toString());
+                }
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return ConnectCommand;
+
+  })(Command);
+
+  module.exports = ConnectCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host/devices.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host/devices.js
new file mode 100644
index 0000000..e9d5a46
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host/devices.js
@@ -0,0 +1,67 @@
+(function() {
+  var Command, HostDevicesCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  HostDevicesCommand = (function(_super) {
+    __extends(HostDevicesCommand, _super);
+
+    function HostDevicesCommand() {
+      return HostDevicesCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    HostDevicesCommand.prototype.execute = function() {
+      this._send('host:devices');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this._readDevices();
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    HostDevicesCommand.prototype._readDevices = function() {
+      return this.parser.readValue().then((function(_this) {
+        return function(value) {
+          return _this._parseDevices(value);
+        };
+      })(this));
+    };
+
+    HostDevicesCommand.prototype._parseDevices = function(value) {
+      var devices, id, line, type, _i, _len, _ref, _ref1;
+      devices = [];
+      if (!value.length) {
+        return devices;
+      }
+      _ref = value.toString('ascii').split('\n');
+      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+        line = _ref[_i];
+        if (line) {
+          _ref1 = line.split('\t'), id = _ref1[0], type = _ref1[1];
+          devices.push({
+            id: id,
+            type: type
+          });
+        }
+      }
+      return devices;
+    };
+
+    return HostDevicesCommand;
+
+  })(Command);
+
+  module.exports = HostDevicesCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host/deviceswithpaths.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host/deviceswithpaths.js
new file mode 100644
index 0000000..a63cdf1
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host/deviceswithpaths.js
@@ -0,0 +1,68 @@
+(function() {
+  var Command, HostDevicesWithPathsCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  HostDevicesWithPathsCommand = (function(_super) {
+    __extends(HostDevicesWithPathsCommand, _super);
+
+    function HostDevicesWithPathsCommand() {
+      return HostDevicesWithPathsCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    HostDevicesWithPathsCommand.prototype.execute = function() {
+      this._send('host:devices-l');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this._readDevices();
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    HostDevicesWithPathsCommand.prototype._readDevices = function() {
+      return this.parser.readValue().then((function(_this) {
+        return function(value) {
+          return _this._parseDevices(value);
+        };
+      })(this));
+    };
+
+    HostDevicesWithPathsCommand.prototype._parseDevices = function(value) {
+      var devices, id, line, path, type, _i, _len, _ref, _ref1;
+      devices = [];
+      if (!value.length) {
+        return devices;
+      }
+      _ref = value.toString('ascii').split('\n');
+      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+        line = _ref[_i];
+        if (line) {
+          _ref1 = line.split(/\s+/), id = _ref1[0], type = _ref1[1], path = _ref1[2];
+          devices.push({
+            id: id,
+            type: type,
+            path: path
+          });
+        }
+      }
+      return devices;
+    };
+
+    return HostDevicesWithPathsCommand;
+
+  })(Command);
+
+  module.exports = HostDevicesWithPathsCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host/disconnect.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host/disconnect.js
new file mode 100644
index 0000000..9cf978e
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host/disconnect.js
@@ -0,0 +1,49 @@
+(function() {
+  var Command, DisconnectCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  DisconnectCommand = (function(_super) {
+    var RE_OK;
+
+    __extends(DisconnectCommand, _super);
+
+    function DisconnectCommand() {
+      return DisconnectCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    RE_OK = /^$/;
+
+    DisconnectCommand.prototype.execute = function(host, port) {
+      this._send("host:disconnect:" + host + ":" + port);
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readValue().then(function(value) {
+                if (RE_OK.test(value)) {
+                  return "" + host + ":" + port;
+                } else {
+                  throw new Error(value.toString());
+                }
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return DisconnectCommand;
+
+  })(Command);
+
+  module.exports = DisconnectCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host/kill.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host/kill.js
new file mode 100644
index 0000000..74d531c
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host/kill.js
@@ -0,0 +1,39 @@
+(function() {
+  var Command, HostKillCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  HostKillCommand = (function(_super) {
+    __extends(HostKillCommand, _super);
+
+    function HostKillCommand() {
+      return HostKillCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    HostKillCommand.prototype.execute = function() {
+      this._send('host:kill');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return true;
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return HostKillCommand;
+
+  })(Command);
+
+  module.exports = HostKillCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host/trackdevices.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host/trackdevices.js
new file mode 100644
index 0000000..103793a
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host/trackdevices.js
@@ -0,0 +1,43 @@
+(function() {
+  var Command, HostDevicesCommand, HostTrackDevicesCommand, Protocol, Tracker,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  Tracker = require('../../tracker');
+
+  HostDevicesCommand = require('./devices');
+
+  HostTrackDevicesCommand = (function(_super) {
+    __extends(HostTrackDevicesCommand, _super);
+
+    function HostTrackDevicesCommand() {
+      return HostTrackDevicesCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    HostTrackDevicesCommand.prototype.execute = function() {
+      this._send('host:track-devices');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return new Tracker(_this);
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return HostTrackDevicesCommand;
+
+  })(HostDevicesCommand);
+
+  module.exports = HostTrackDevicesCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host/transport.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host/transport.js
new file mode 100644
index 0000000..8264c4f
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host/transport.js
@@ -0,0 +1,39 @@
+(function() {
+  var Command, HostTransportCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  HostTransportCommand = (function(_super) {
+    __extends(HostTransportCommand, _super);
+
+    function HostTransportCommand() {
+      return HostTransportCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    HostTransportCommand.prototype.execute = function(serial) {
+      this._send("host:transport:" + serial);
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return true;
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return HostTransportCommand;
+
+  })(Command);
+
+  module.exports = HostTransportCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host/version.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host/version.js
new file mode 100644
index 0000000..48accc1
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/command/host/version.js
@@ -0,0 +1,45 @@
+(function() {
+  var Command, HostVersionCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  HostVersionCommand = (function(_super) {
+    __extends(HostVersionCommand, _super);
+
+    function HostVersionCommand() {
+      return HostVersionCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    HostVersionCommand.prototype.execute = function() {
+      this._send('host:version');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readValue().then(function(value) {
+                return _this._parseVersion(value);
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this._parseVersion(reply);
+          }
+        };
+      })(this));
+    };
+
+    HostVersionCommand.prototype._parseVersion = function(version) {
+      return parseInt(version, 16);
+    };
+
+    return HostVersionCommand;
+
+  })(Command);
+
+  module.exports = HostVersionCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/connection.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/connection.js
new file mode 100644
index 0000000..bccd503
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/connection.js
@@ -0,0 +1,108 @@
+(function() {
+  var Connection, EventEmitter, Net, Parser, debug, execFile,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Net = require('net');
+
+  debug = require('debug')('adb:connection');
+
+  EventEmitter = require('events').EventEmitter;
+
+  execFile = require('child_process').execFile;
+
+  Parser = require('./parser');
+
+  Connection = (function(_super) {
+    __extends(Connection, _super);
+
+    function Connection(options) {
+      this.options = options;
+      this.socket = null;
+      this.parser = null;
+      this.triedStarting = false;
+    }
+
+    Connection.prototype.connect = function() {
+      this.socket = Net.connect(this.options);
+      this.parser = new Parser(this.socket);
+      this.socket.on('connect', (function(_this) {
+        return function() {
+          return _this.emit('connect');
+        };
+      })(this));
+      this.socket.on('end', (function(_this) {
+        return function() {
+          return _this.emit('end');
+        };
+      })(this));
+      this.socket.on('drain', (function(_this) {
+        return function() {
+          return _this.emit('drain');
+        };
+      })(this));
+      this.socket.on('timeout', (function(_this) {
+        return function() {
+          return _this.emit('timeout');
+        };
+      })(this));
+      this.socket.on('error', (function(_this) {
+        return function(err) {
+          return _this._handleError(err);
+        };
+      })(this));
+      this.socket.on('close', (function(_this) {
+        return function(hadError) {
+          return _this.emit('close', hadError);
+        };
+      })(this));
+      return this;
+    };
+
+    Connection.prototype.end = function() {
+      this.socket.end();
+      return this;
+    };
+
+    Connection.prototype.write = function(data, callback) {
+      this.socket.write(data, callback);
+      return this;
+    };
+
+    Connection.prototype.startServer = function(callback) {
+      debug("Starting ADB server via '" + this.options.bin + " start-server'");
+      return this._exec(['start-server'], {}, callback);
+    };
+
+    Connection.prototype._exec = function(args, options, callback) {
+      debug("CLI: " + this.options.bin + " " + (args.join(' ')));
+      execFile(this.options.bin, args, options, callback);
+      return this;
+    };
+
+    Connection.prototype._handleError = function(err) {
+      if (err.code === 'ECONNREFUSED' && !this.triedStarting) {
+        debug("Connection was refused, let's try starting the server once");
+        this.triedStarting = true;
+        this.startServer((function(_this) {
+          return function(err) {
+            if (err) {
+              return _this._handleError(err);
+            }
+            return _this.connect();
+          };
+        })(this));
+      } else {
+        debug("Connection had an error: " + err.message);
+        this.emit('error', err);
+        this.end();
+      }
+    };
+
+    return Connection;
+
+  })(EventEmitter);
+
+  module.exports = Connection;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/framebuffer/rgbtransform.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/framebuffer/rgbtransform.js
new file mode 100644
index 0000000..e759722
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/framebuffer/rgbtransform.js
@@ -0,0 +1,58 @@
+(function() {
+  var Assert, RgbTransform, Stream,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Assert = require('assert');
+
+  Stream = require('stream');
+
+  RgbTransform = (function(_super) {
+    __extends(RgbTransform, _super);
+
+    function RgbTransform(meta, options) {
+      this.meta = meta;
+      this._buffer = new Buffer('');
+      Assert.ok(this.meta.bpp === 24 || this.meta.bpp === 32, 'Only 24-bit and 32-bit raw images with 8-bits per color are supported');
+      this._r_pos = this.meta.red_offset / 8;
+      this._g_pos = this.meta.green_offset / 8;
+      this._b_pos = this.meta.blue_offset / 8;
+      this._a_pos = this.meta.alpha_offset / 8;
+      this._pixel_bytes = this.meta.bpp / 8;
+      RgbTransform.__super__.constructor.call(this, options);
+    }
+
+    RgbTransform.prototype._transform = function(chunk, encoding, done) {
+      var b, g, r, sourceCursor, target, targetCursor;
+      if (this._buffer.length) {
+        this._buffer = Buffer.concat([this._buffer, chunk], this._buffer.length + chunk.length);
+      } else {
+        this._buffer = chunk;
+      }
+      sourceCursor = 0;
+      targetCursor = 0;
+      target = this._pixel_bytes === 3 ? this._buffer : new Buffer(Math.max(4, chunk.length / this._pixel_bytes * 3));
+      while (this._buffer.length - sourceCursor >= this._pixel_bytes) {
+        r = this._buffer[sourceCursor + this._r_pos];
+        g = this._buffer[sourceCursor + this._g_pos];
+        b = this._buffer[sourceCursor + this._b_pos];
+        target[targetCursor + 0] = r;
+        target[targetCursor + 1] = g;
+        target[targetCursor + 2] = b;
+        sourceCursor += this._pixel_bytes;
+        targetCursor += 3;
+      }
+      if (targetCursor) {
+        this.push(target.slice(0, targetCursor));
+        this._buffer = this._buffer.slice(sourceCursor);
+      }
+      done();
+    };
+
+    return RgbTransform;
+
+  })(Stream.Transform);
+
+  module.exports = RgbTransform;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/keycode.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/keycode.js
new file mode 100644
index 0000000..0ca3c08
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/keycode.js
@@ -0,0 +1,228 @@
+(function() {
+  module.exports = {
+    KEYCODE_UNKNOWN: 0,
+    KEYCODE_SOFT_LEFT: 1,
+    KEYCODE_SOFT_RIGHT: 2,
+    KEYCODE_HOME: 3,
+    KEYCODE_BACK: 4,
+    KEYCODE_CALL: 5,
+    KEYCODE_ENDCALL: 6,
+    KEYCODE_0: 7,
+    KEYCODE_1: 8,
+    KEYCODE_2: 9,
+    KEYCODE_3: 10,
+    KEYCODE_4: 11,
+    KEYCODE_5: 12,
+    KEYCODE_6: 13,
+    KEYCODE_7: 14,
+    KEYCODE_8: 15,
+    KEYCODE_9: 16,
+    KEYCODE_STAR: 17,
+    KEYCODE_POUND: 18,
+    KEYCODE_DPAD_UP: 19,
+    KEYCODE_DPAD_DOWN: 20,
+    KEYCODE_DPAD_LEFT: 21,
+    KEYCODE_DPAD_RIGHT: 22,
+    KEYCODE_DPAD_CENTER: 23,
+    KEYCODE_VOLUME_UP: 24,
+    KEYCODE_VOLUME_DOWN: 25,
+    KEYCODE_POWER: 26,
+    KEYCODE_CAMERA: 27,
+    KEYCODE_CLEAR: 28,
+    KEYCODE_A: 29,
+    KEYCODE_B: 30,
+    KEYCODE_C: 31,
+    KEYCODE_D: 32,
+    KEYCODE_E: 33,
+    KEYCODE_F: 34,
+    KEYCODE_G: 35,
+    KEYCODE_H: 36,
+    KEYCODE_I: 37,
+    KEYCODE_J: 38,
+    KEYCODE_K: 39,
+    KEYCODE_L: 40,
+    KEYCODE_M: 41,
+    KEYCODE_N: 42,
+    KEYCODE_O: 43,
+    KEYCODE_P: 44,
+    KEYCODE_Q: 45,
+    KEYCODE_R: 46,
+    KEYCODE_S: 47,
+    KEYCODE_T: 48,
+    KEYCODE_U: 49,
+    KEYCODE_V: 50,
+    KEYCODE_W: 51,
+    KEYCODE_X: 52,
+    KEYCODE_Y: 53,
+    KEYCODE_Z: 54,
+    KEYCODE_COMMA: 55,
+    KEYCODE_PERIOD: 56,
+    KEYCODE_ALT_LEFT: 57,
+    KEYCODE_ALT_RIGHT: 58,
+    KEYCODE_SHIFT_LEFT: 59,
+    KEYCODE_SHIFT_RIGHT: 60,
+    KEYCODE_TAB: 61,
+    KEYCODE_SPACE: 62,
+    KEYCODE_SYM: 63,
+    KEYCODE_EXPLORER: 64,
+    KEYCODE_ENVELOPE: 65,
+    KEYCODE_ENTER: 66,
+    KEYCODE_DEL: 67,
+    KEYCODE_GRAVE: 68,
+    KEYCODE_MINUS: 69,
+    KEYCODE_EQUALS: 70,
+    KEYCODE_LEFT_BRACKET: 71,
+    KEYCODE_RIGHT_BRACKET: 72,
+    KEYCODE_BACKSLASH: 73,
+    KEYCODE_SEMICOLON: 74,
+    KEYCODE_APOSTROPHE: 75,
+    KEYCODE_SLASH: 76,
+    KEYCODE_AT: 77,
+    KEYCODE_NUM: 78,
+    KEYCODE_HEADSETHOOK: 79,
+    KEYCODE_FOCUS: 80,
+    KEYCODE_PLUS: 81,
+    KEYCODE_MENU: 82,
+    KEYCODE_NOTIFICATION: 83,
+    KEYCODE_SEARCH: 84,
+    KEYCODE_MEDIA_PLAY_PAUSE: 85,
+    KEYCODE_MEDIA_STOP: 86,
+    KEYCODE_MEDIA_NEXT: 87,
+    KEYCODE_MEDIA_PREVIOUS: 88,
+    KEYCODE_MEDIA_REWIND: 89,
+    KEYCODE_MEDIA_FAST_FORWARD: 90,
+    KEYCODE_MUTE: 91,
+    KEYCODE_PAGE_UP: 92,
+    KEYCODE_PAGE_DOWN: 93,
+    KEYCODE_PICTSYMBOLS: 94,
+    KEYCODE_SWITCH_CHARSET: 95,
+    KEYCODE_BUTTON_A: 96,
+    KEYCODE_BUTTON_B: 97,
+    KEYCODE_BUTTON_C: 98,
+    KEYCODE_BUTTON_X: 99,
+    KEYCODE_BUTTON_Y: 100,
+    KEYCODE_BUTTON_Z: 101,
+    KEYCODE_BUTTON_L1: 102,
+    KEYCODE_BUTTON_R1: 103,
+    KEYCODE_BUTTON_L2: 104,
+    KEYCODE_BUTTON_R2: 105,
+    KEYCODE_BUTTON_THUMBL: 106,
+    KEYCODE_BUTTON_THUMBR: 107,
+    KEYCODE_BUTTON_START: 108,
+    KEYCODE_BUTTON_SELECT: 109,
+    KEYCODE_BUTTON_MODE: 110,
+    KEYCODE_ESCAPE: 111,
+    KEYCODE_FORWARD_DEL: 112,
+    KEYCODE_CTRL_LEFT: 113,
+    KEYCODE_CTRL_RIGHT: 114,
+    KEYCODE_CAPS_LOCK: 115,
+    KEYCODE_SCROLL_LOCK: 116,
+    KEYCODE_META_LEFT: 117,
+    KEYCODE_META_RIGHT: 118,
+    KEYCODE_FUNCTION: 119,
+    KEYCODE_SYSRQ: 120,
+    KEYCODE_BREAK: 121,
+    KEYCODE_MOVE_HOME: 122,
+    KEYCODE_MOVE_END: 123,
+    KEYCODE_INSERT: 124,
+    KEYCODE_FORWARD: 125,
+    KEYCODE_MEDIA_PLAY: 126,
+    KEYCODE_MEDIA_PAUSE: 127,
+    KEYCODE_MEDIA_CLOSE: 128,
+    KEYCODE_MEDIA_EJECT: 129,
+    KEYCODE_MEDIA_RECORD: 130,
+    KEYCODE_F1: 131,
+    KEYCODE_F2: 132,
+    KEYCODE_F3: 133,
+    KEYCODE_F4: 134,
+    KEYCODE_F5: 135,
+    KEYCODE_F6: 136,
+    KEYCODE_F7: 137,
+    KEYCODE_F8: 138,
+    KEYCODE_F9: 139,
+    KEYCODE_F10: 140,
+    KEYCODE_F11: 141,
+    KEYCODE_F12: 142,
+    KEYCODE_NUM_LOCK: 143,
+    KEYCODE_NUMPAD_0: 144,
+    KEYCODE_NUMPAD_1: 145,
+    KEYCODE_NUMPAD_2: 146,
+    KEYCODE_NUMPAD_3: 147,
+    KEYCODE_NUMPAD_4: 148,
+    KEYCODE_NUMPAD_5: 149,
+    KEYCODE_NUMPAD_6: 150,
+    KEYCODE_NUMPAD_7: 151,
+    KEYCODE_NUMPAD_8: 152,
+    KEYCODE_NUMPAD_9: 153,
+    KEYCODE_NUMPAD_DIVIDE: 154,
+    KEYCODE_NUMPAD_MULTIPLY: 155,
+    KEYCODE_NUMPAD_SUBTRACT: 156,
+    KEYCODE_NUMPAD_ADD: 157,
+    KEYCODE_NUMPAD_DOT: 158,
+    KEYCODE_NUMPAD_COMMA: 159,
+    KEYCODE_NUMPAD_ENTER: 160,
+    KEYCODE_NUMPAD_EQUALS: 161,
+    KEYCODE_NUMPAD_LEFT_PAREN: 162,
+    KEYCODE_NUMPAD_RIGHT_PAREN: 163,
+    KEYCODE_VOLUME_MUTE: 164,
+    KEYCODE_INFO: 165,
+    KEYCODE_CHANNEL_UP: 166,
+    KEYCODE_CHANNEL_DOWN: 167,
+    KEYCODE_ZOOM_IN: 168,
+    KEYCODE_ZOOM_OUT: 169,
+    KEYCODE_TV: 170,
+    KEYCODE_WINDOW: 171,
+    KEYCODE_GUIDE: 172,
+    KEYCODE_DVR: 173,
+    KEYCODE_BOOKMARK: 174,
+    KEYCODE_CAPTIONS: 175,
+    KEYCODE_SETTINGS: 176,
+    KEYCODE_TV_POWER: 177,
+    KEYCODE_TV_INPUT: 178,
+    KEYCODE_STB_POWER: 179,
+    KEYCODE_STB_INPUT: 180,
+    KEYCODE_AVR_POWER: 181,
+    KEYCODE_AVR_INPUT: 182,
+    KEYCODE_PROG_RED: 183,
+    KEYCODE_PROG_GREEN: 184,
+    KEYCODE_PROG_YELLOW: 185,
+    KEYCODE_PROG_BLUE: 186,
+    KEYCODE_APP_SWITCH: 187,
+    KEYCODE_BUTTON_1: 188,
+    KEYCODE_BUTTON_2: 189,
+    KEYCODE_BUTTON_3: 190,
+    KEYCODE_BUTTON_4: 191,
+    KEYCODE_BUTTON_5: 192,
+    KEYCODE_BUTTON_6: 193,
+    KEYCODE_BUTTON_7: 194,
+    KEYCODE_BUTTON_8: 195,
+    KEYCODE_BUTTON_9: 196,
+    KEYCODE_BUTTON_10: 197,
+    KEYCODE_BUTTON_11: 198,
+    KEYCODE_BUTTON_12: 199,
+    KEYCODE_BUTTON_13: 200,
+    KEYCODE_BUTTON_14: 201,
+    KEYCODE_BUTTON_15: 202,
+    KEYCODE_BUTTON_16: 203,
+    KEYCODE_LANGUAGE_SWITCH: 204,
+    KEYCODE_MANNER_MODE: 205,
+    KEYCODE_3D_MODE: 206,
+    KEYCODE_CONTACTS: 207,
+    KEYCODE_CALENDAR: 208,
+    KEYCODE_MUSIC: 209,
+    KEYCODE_CALCULATOR: 210,
+    KEYCODE_ZENKAKU_HANKAKU: 211,
+    KEYCODE_EISU: 212,
+    KEYCODE_MUHENKAN: 213,
+    KEYCODE_HENKAN: 214,
+    KEYCODE_KATAKANA_HIRAGANA: 215,
+    KEYCODE_YEN: 216,
+    KEYCODE_RO: 217,
+    KEYCODE_KANA: 218,
+    KEYCODE_ASSIST: 219,
+    KEYCODE_BRIGHTNESS_DOWN: 220,
+    KEYCODE_BRIGHTNESS_UP: 221,
+    KEYCODE_MEDIA_AUDIO_TRACK: 222
+  };
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/linetransform.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/linetransform.js
new file mode 100644
index 0000000..05efce1
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/linetransform.js
@@ -0,0 +1,58 @@
+(function() {
+  var LineTransform, Stream,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Stream = require('stream');
+
+  LineTransform = (function(_super) {
+    __extends(LineTransform, _super);
+
+    function LineTransform(options) {
+      this.savedR = null;
+      LineTransform.__super__.constructor.call(this, options);
+    }
+
+    LineTransform.prototype._transform = function(chunk, encoding, done) {
+      var hi, last, lo;
+      lo = 0;
+      hi = 0;
+      if (this.savedR) {
+        if (chunk[0] !== 0x0a) {
+          this.push(this.savedR);
+        }
+        this.savedR = null;
+      }
+      last = chunk.length - 1;
+      while (hi <= last) {
+        if (chunk[hi] === 0x0d) {
+          if (hi === last) {
+            this.savedR = chunk.slice(last);
+            break;
+          } else if (chunk[hi + 1] === 0x0a) {
+            this.push(chunk.slice(lo, hi));
+            lo = hi + 1;
+          }
+        }
+        hi += 1;
+      }
+      if (hi !== lo) {
+        this.push(chunk.slice(lo, hi));
+      }
+      done();
+    };
+
+    LineTransform.prototype._flush = function(done) {
+      if (this.savedR) {
+        this.push(this.savedR);
+      }
+      return done();
+    };
+
+    return LineTransform;
+
+  })(Stream.Transform);
+
+  module.exports = LineTransform;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/parser.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/parser.js
new file mode 100644
index 0000000..65c50f6
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/parser.js
@@ -0,0 +1,247 @@
+(function() {
+  var Parser, Promise, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Promise = require('bluebird');
+
+  Protocol = require('./protocol');
+
+  Parser = (function() {
+    Parser.FailError = (function(_super) {
+      __extends(FailError, _super);
+
+      function FailError(message) {
+        Error.call(this);
+        this.name = 'FailError';
+        this.message = "Failure: '" + message + "'";
+        Error.captureStackTrace(this, Parser.FailError);
+      }
+
+      return FailError;
+
+    })(Error);
+
+    Parser.PrematureEOFError = (function(_super) {
+      __extends(PrematureEOFError, _super);
+
+      function PrematureEOFError(howManyMissing) {
+        Error.call(this);
+        this.name = 'PrematureEOFError';
+        this.message = "Premature end of stream, needed " + howManyMissing + " more bytes";
+        this.missingBytes = howManyMissing;
+        Error.captureStackTrace(this, Parser.PrematureEOFError);
+      }
+
+      return PrematureEOFError;
+
+    })(Error);
+
+    Parser.UnexpectedDataError = (function(_super) {
+      __extends(UnexpectedDataError, _super);
+
+      function UnexpectedDataError(unexpected, expected) {
+        Error.call(this);
+        this.name = 'UnexpectedDataError';
+        this.message = "Unexpected '" + unexpected + "', was expecting " + expected;
+        this.unexpected = unexpected;
+        this.expected = expected;
+        Error.captureStackTrace(this, Parser.UnexpectedDataError);
+      }
+
+      return UnexpectedDataError;
+
+    })(Error);
+
+    function Parser(stream) {
+      this.stream = stream;
+    }
+
+    Parser.prototype.raw = function() {
+      return this.stream;
+    };
+
+    Parser.prototype.readAll = function() {
+      var all, endListener, errorListener, resolver, tryRead;
+      all = new Buffer(0);
+      resolver = Promise.defer();
+      tryRead = (function(_this) {
+        return function() {
+          var chunk, _results;
+          _results = [];
+          while (chunk = _this.stream.read()) {
+            _results.push(all = Buffer.concat([all, chunk]));
+          }
+          return _results;
+        };
+      })(this);
+      this.stream.on('readable', tryRead);
+      this.stream.on('error', errorListener = function(err) {
+        return resolver.reject(err);
+      });
+      this.stream.on('end', endListener = function() {
+        return resolver.resolve(all);
+      });
+      tryRead();
+      return resolver.promise.cancellable()["finally"]((function(_this) {
+        return function() {
+          _this.stream.removeListener('readable', tryRead);
+          _this.stream.removeListener('error', errorListener);
+          return _this.stream.removeListener('end', endListener);
+        };
+      })(this));
+    };
+
+    Parser.prototype.readAscii = function(howMany) {
+      return this.readBytes(howMany).then(function(chunk) {
+        return chunk.toString('ascii');
+      });
+    };
+
+    Parser.prototype.readBytes = function(howMany) {
+      var endListener, errorListener, resolver, tryRead;
+      resolver = Promise.defer();
+      tryRead = (function(_this) {
+        return function() {
+          var chunk;
+          if (howMany) {
+            if (chunk = _this.stream.read(howMany)) {
+              howMany -= chunk.length;
+              if (howMany === 0) {
+                return resolver.resolve(chunk);
+              }
+            }
+          } else {
+            return resolver.resolve(new Buffer(0));
+          }
+        };
+      })(this);
+      endListener = function() {
+        return resolver.reject(new Parser.PrematureEOFError(howMany));
+      };
+      errorListener = function(err) {
+        return resolver.reject(err);
+      };
+      this.stream.on('readable', tryRead);
+      this.stream.on('error', errorListener);
+      this.stream.on('end', endListener);
+      tryRead();
+      return resolver.promise.cancellable()["finally"]((function(_this) {
+        return function() {
+          _this.stream.removeListener('readable', tryRead);
+          _this.stream.removeListener('error', errorListener);
+          return _this.stream.removeListener('end', endListener);
+        };
+      })(this));
+    };
+
+    Parser.prototype.readByteFlow = function(howMany) {
+      var endListener, errorListener, resolver, tryRead;
+      resolver = Promise.defer();
+      tryRead = (function(_this) {
+        return function() {
+          var chunk, _results;
+          if (howMany) {
+            _results = [];
+            while (chunk = _this.stream.read(howMany) || _this.stream.read()) {
+              howMany -= chunk.length;
+              if (howMany === 0) {
+                resolver.progress(chunk);
+                resolver.resolve();
+                break;
+              }
+              _results.push(resolver.progress(chunk));
+            }
+            return _results;
+          } else {
+            return resolver.resolve();
+          }
+        };
+      })(this);
+      endListener = function() {
+        return resolver.reject(new Parser.PrematureEOFError(howMany));
+      };
+      errorListener = function(err) {
+        return resolver.reject(err);
+      };
+      this.stream.on('readable', tryRead);
+      this.stream.on('error', errorListener);
+      this.stream.on('end', endListener);
+      tryRead();
+      return resolver.promise.cancellable()["finally"]((function(_this) {
+        return function() {
+          _this.stream.removeListener('readable', tryRead);
+          _this.stream.removeListener('error', errorListener);
+          return _this.stream.removeListener('end', endListener);
+        };
+      })(this));
+    };
+
+    Parser.prototype.readError = function() {
+      return this.readValue().then(function(value) {
+        return Promise.reject(new Parser.FailError(value.toString()));
+      });
+    };
+
+    Parser.prototype.readValue = function() {
+      return this.readAscii(4).then((function(_this) {
+        return function(value) {
+          var length;
+          length = Protocol.decodeLength(value);
+          return _this.readBytes(length);
+        };
+      })(this));
+    };
+
+    Parser.prototype.readUntil = function(code) {
+      var read, skipped;
+      skipped = new Buffer(0);
+      read = (function(_this) {
+        return function() {
+          return _this.readBytes(1).then(function(chunk) {
+            if (chunk[0] === code) {
+              return skipped;
+            } else {
+              skipped = Buffer.concat([skipped, chunk]);
+              return read();
+            }
+          });
+        };
+      })(this);
+      return read();
+    };
+
+    Parser.prototype.searchLine = function(re) {
+      return this.readLine().then((function(_this) {
+        return function(line) {
+          var match;
+          if (match = re.exec(line)) {
+            return match;
+          } else {
+            return _this.searchLine(re);
+          }
+        };
+      })(this));
+    };
+
+    Parser.prototype.readLine = function() {
+      return this.readUntil(0x0a).then(function(line) {
+        if (line[line.length - 1] === 0x0d) {
+          return line.slice(0, -1);
+        } else {
+          return line;
+        }
+      });
+    };
+
+    Parser.prototype.unexpected = function(data, expected) {
+      return Promise.reject(new Parser.UnexpectedDataError(data, expected));
+    };
+
+    return Parser;
+
+  })();
+
+  module.exports = Parser;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/proc/stat.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/proc/stat.js
new file mode 100644
index 0000000..76ac4e9
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/proc/stat.js
@@ -0,0 +1,140 @@
+(function() {
+  var EventEmitter, Parser, ProcStat, split,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  EventEmitter = require('events').EventEmitter;
+
+  split = require('split');
+
+  Parser = require('../parser');
+
+  ProcStat = (function(_super) {
+    var RE_COLSEP, RE_CPULINE;
+
+    __extends(ProcStat, _super);
+
+    RE_CPULINE = /^cpu[0-9]+ .*$/mg;
+
+    RE_COLSEP = /\ +/g;
+
+    function ProcStat(sync) {
+      this.sync = sync;
+      this.interval = 1000;
+      this.stats = this._emptyStats();
+      this._ignore = {};
+      this._timer = setInterval((function(_this) {
+        return function() {
+          return _this.update();
+        };
+      })(this), this.interval);
+      this.update();
+    }
+
+    ProcStat.prototype.end = function() {
+      clearInterval(this._timer);
+      this.sync.end();
+      return this.sync = null;
+    };
+
+    ProcStat.prototype.update = function() {
+      return new Parser(this.sync.pull('/proc/stat')).readAll().then((function(_this) {
+        return function(out) {
+          return _this._parse(out);
+        };
+      })(this))["catch"]((function(_this) {
+        return function(err) {
+          _this._error(err);
+        };
+      })(this));
+    };
+
+    ProcStat.prototype._parse = function(out) {
+      var cols, line, match, stats, total, type, val, _i, _len;
+      stats = this._emptyStats();
+      while (match = RE_CPULINE.exec(out)) {
+        line = match[0];
+        cols = line.split(RE_COLSEP);
+        type = cols.shift();
+        if (this._ignore[type] === line) {
+          continue;
+        }
+        total = 0;
+        for (_i = 0, _len = cols.length; _i < _len; _i++) {
+          val = cols[_i];
+          total += +val;
+        }
+        stats.cpus[type] = {
+          line: line,
+          user: +cols[0] || 0,
+          nice: +cols[1] || 0,
+          system: +cols[2] || 0,
+          idle: +cols[3] || 0,
+          iowait: +cols[4] || 0,
+          irq: +cols[5] || 0,
+          softirq: +cols[6] || 0,
+          steal: +cols[7] || 0,
+          guest: +cols[8] || 0,
+          guestnice: +cols[9] || 0,
+          total: total
+        };
+      }
+      return this._set(stats);
+    };
+
+    ProcStat.prototype._set = function(stats) {
+      var cur, found, id, loads, m, old, ticks, _ref;
+      loads = {};
+      found = false;
+      _ref = stats.cpus;
+      for (id in _ref) {
+        cur = _ref[id];
+        old = this.stats.cpus[id];
+        if (!old) {
+          continue;
+        }
+        ticks = cur.total - old.total;
+        if (ticks > 0) {
+          found = true;
+          m = 100 / ticks;
+          loads[id] = {
+            user: Math.floor(m * (cur.user - old.user)),
+            nice: Math.floor(m * (cur.nice - old.nice)),
+            system: Math.floor(m * (cur.system - old.system)),
+            idle: Math.floor(m * (cur.idle - old.idle)),
+            iowait: Math.floor(m * (cur.iowait - old.iowait)),
+            irq: Math.floor(m * (cur.irq - old.irq)),
+            softirq: Math.floor(m * (cur.softirq - old.softirq)),
+            steal: Math.floor(m * (cur.steal - old.steal)),
+            guest: Math.floor(m * (cur.guest - old.guest)),
+            guestnice: Math.floor(m * (cur.guestnice - old.guestnice)),
+            total: 100
+          };
+        } else {
+          this._ignore[id] = cur.line;
+          delete stats.cpus[id];
+        }
+      }
+      if (found) {
+        this.emit('load', loads);
+      }
+      return this.stats = stats;
+    };
+
+    ProcStat.prototype._error = function(err) {
+      return this.emit('error', err);
+    };
+
+    ProcStat.prototype._emptyStats = function() {
+      return {
+        cpus: {}
+      };
+    };
+
+    return ProcStat;
+
+  })(EventEmitter);
+
+  module.exports = ProcStat;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/protocol.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/protocol.js
new file mode 100644
index 0000000..72ac5f3
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/protocol.js
@@ -0,0 +1,48 @@
+(function() {
+  var Protocol;
+
+  Protocol = (function() {
+    function Protocol() {}
+
+    Protocol.OKAY = 'OKAY';
+
+    Protocol.FAIL = 'FAIL';
+
+    Protocol.STAT = 'STAT';
+
+    Protocol.LIST = 'LIST';
+
+    Protocol.DENT = 'DENT';
+
+    Protocol.RECV = 'RECV';
+
+    Protocol.DATA = 'DATA';
+
+    Protocol.DONE = 'DONE';
+
+    Protocol.SEND = 'SEND';
+
+    Protocol.QUIT = 'QUIT';
+
+    Protocol.decodeLength = function(length) {
+      return parseInt(length, 16);
+    };
+
+    Protocol.encodeLength = function(length) {
+      return ('0000' + length.toString(16)).slice(-4).toUpperCase();
+    };
+
+    Protocol.encodeData = function(data) {
+      if (!Buffer.isBuffer(data)) {
+        data = new Buffer(data);
+      }
+      return Buffer.concat([new Buffer(Protocol.encodeLength(data.length)), data]);
+    };
+
+    return Protocol;
+
+  })();
+
+  module.exports = Protocol;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/sync.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/sync.js
new file mode 100644
index 0000000..8c157a9
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/sync.js
@@ -0,0 +1,337 @@
+(function() {
+  var Entry, EventEmitter, Fs, Path, Promise, Protocol, PullTransfer, PushTransfer, Stats, Sync, debug,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Fs = require('fs');
+
+  Path = require('path');
+
+  Promise = require('bluebird');
+
+  EventEmitter = require('events').EventEmitter;
+
+  debug = require('debug')('adb:sync');
+
+  Protocol = require('./protocol');
+
+  Stats = require('./sync/stats');
+
+  Entry = require('./sync/entry');
+
+  PushTransfer = require('./sync/pushtransfer');
+
+  PullTransfer = require('./sync/pulltransfer');
+
+  Sync = (function(_super) {
+    var DATA_MAX_LENGTH, DEFAULT_CHMOD, TEMP_PATH;
+
+    __extends(Sync, _super);
+
+    TEMP_PATH = '/data/local/tmp';
+
+    DEFAULT_CHMOD = 0x1a4;
+
+    DATA_MAX_LENGTH = 65536;
+
+    Sync.temp = function(path) {
+      return "" + TEMP_PATH + "/" + (Path.basename(path));
+    };
+
+    function Sync(connection) {
+      this.connection = connection;
+      this.parser = this.connection.parser;
+    }
+
+    Sync.prototype.stat = function(path, callback) {
+      this._sendCommandWithArg(Protocol.STAT, path);
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.STAT:
+              return _this.parser.readBytes(12).then(function(stat) {
+                var mode, mtime, size;
+                mode = stat.readUInt32LE(0);
+                size = stat.readUInt32LE(4);
+                mtime = stat.readUInt32LE(8);
+                if (mode === 0) {
+                  return _this._enoent(path);
+                } else {
+                  return new Stats(mode, size, mtime);
+                }
+              });
+            case Protocol.FAIL:
+              return _this._readError();
+            default:
+              return _this.parser.unexpected(reply, 'STAT or FAIL');
+          }
+        };
+      })(this)).nodeify(callback);
+    };
+
+    Sync.prototype.readdir = function(path, callback) {
+      var files, readNext;
+      files = [];
+      readNext = (function(_this) {
+        return function() {
+          return _this.parser.readAscii(4).then(function(reply) {
+            switch (reply) {
+              case Protocol.DENT:
+                return _this.parser.readBytes(16).then(function(stat) {
+                  var mode, mtime, namelen, size;
+                  mode = stat.readUInt32LE(0);
+                  size = stat.readUInt32LE(4);
+                  mtime = stat.readUInt32LE(8);
+                  namelen = stat.readUInt32LE(12);
+                  return _this.parser.readBytes(namelen).then(function(name) {
+                    name = name.toString();
+                    if (!(name === '.' || name === '..')) {
+                      files.push(new Entry(name, mode, size, mtime));
+                    }
+                    return readNext();
+                  });
+                });
+              case Protocol.DONE:
+                return _this.parser.readBytes(16).then(function(zero) {
+                  return files;
+                });
+              case Protocol.FAIL:
+                return _this._readError();
+              default:
+                return _this.parser.unexpected(reply, 'DENT, DONE or FAIL');
+            }
+          });
+        };
+      })(this);
+      this._sendCommandWithArg(Protocol.LIST, path);
+      return readNext().nodeify(callback);
+    };
+
+    Sync.prototype.push = function(contents, path, mode) {
+      if (typeof contents === 'string') {
+        return this.pushFile(contents, path, mode);
+      } else {
+        return this.pushStream(contents, path, mode);
+      }
+    };
+
+    Sync.prototype.pushFile = function(file, path, mode) {
+      if (mode == null) {
+        mode = DEFAULT_CHMOD;
+      }
+      mode || (mode = DEFAULT_CHMOD);
+      return this.pushStream(Fs.createReadStream(file), path, mode);
+    };
+
+    Sync.prototype.pushStream = function(stream, path, mode) {
+      if (mode == null) {
+        mode = DEFAULT_CHMOD;
+      }
+      mode |= Stats.S_IFREG;
+      this._sendCommandWithArg(Protocol.SEND, "" + path + "," + mode);
+      return this._writeData(stream, Math.floor(Date.now() / 1000));
+    };
+
+    Sync.prototype.pull = function(path) {
+      this._sendCommandWithArg(Protocol.RECV, "" + path);
+      return this._readData();
+    };
+
+    Sync.prototype.end = function() {
+      this.connection.end();
+      return this;
+    };
+
+    Sync.prototype.tempFile = function(path) {
+      return Sync.temp(path);
+    };
+
+    Sync.prototype._writeData = function(stream, timeStamp) {
+      var readReply, reader, transfer, writeData, writer;
+      transfer = new PushTransfer;
+      writeData = (function(_this) {
+        return function() {
+          var endListener, errorListener, readableListener, resolver, track, waitForDrain, writeNext, writer;
+          resolver = Promise.defer();
+          writer = Promise.resolve().cancellable();
+          stream.on('end', endListener = function() {
+            return writer.then(function() {
+              _this._sendCommandWithLength(Protocol.DONE, timeStamp);
+              return resolver.resolve();
+            });
+          });
+          waitForDrain = function() {
+            var drainListener;
+            resolver = Promise.defer();
+            _this.connection.on('drain', drainListener = function() {
+              return resolver.resolve();
+            });
+            return resolver.promise["finally"](function() {
+              return _this.connection.removeListener('drain', drainListener);
+            });
+          };
+          track = function() {
+            return transfer.pop();
+          };
+          writeNext = function() {
+            var chunk;
+            if (chunk = stream.read(DATA_MAX_LENGTH) || stream.read()) {
+              _this._sendCommandWithLength(Protocol.DATA, chunk.length);
+              transfer.push(chunk.length);
+              if (_this.connection.write(chunk, track)) {
+                return writeNext();
+              } else {
+                return waitForDrain().then(writeNext);
+              }
+            } else {
+              return Promise.resolve();
+            }
+          };
+          stream.on('readable', readableListener = function() {
+            return writer.then(writeNext);
+          });
+          stream.on('error', errorListener = function(err) {
+            return resolver.reject(err);
+          });
+          return resolver.promise["finally"](function() {
+            stream.removeListener('end', endListener);
+            stream.removeListener('readable', readableListener);
+            stream.removeListener('error', errorListener);
+            return writer.cancel();
+          });
+        };
+      })(this);
+      readReply = (function(_this) {
+        return function() {
+          return _this.parser.readAscii(4).then(function(reply) {
+            switch (reply) {
+              case Protocol.OKAY:
+                return _this.parser.readBytes(4).then(function(zero) {
+                  return true;
+                });
+              case Protocol.FAIL:
+                return _this._readError();
+              default:
+                return _this.parser.unexpected(reply, 'OKAY or FAIL');
+            }
+          });
+        };
+      })(this);
+      writer = writeData().cancellable()["catch"](Promise.CancellationError, (function(_this) {
+        return function(err) {
+          return _this.connection.end();
+        };
+      })(this))["catch"](function(err) {
+        transfer.emit('error', err);
+        return reader.cancel();
+      });
+      reader = readReply().cancellable()["catch"](Promise.CancellationError, function(err) {
+        return true;
+      })["catch"](function(err) {
+        transfer.emit('error', err);
+        return writer.cancel();
+      })["finally"](function() {
+        return transfer.end();
+      });
+      transfer.on('cancel', function() {
+        writer.cancel();
+        return reader.cancel();
+      });
+      return transfer;
+    };
+
+    Sync.prototype._readData = function() {
+      var cancelListener, readNext, reader, transfer;
+      transfer = new PullTransfer;
+      readNext = (function(_this) {
+        return function() {
+          return _this.parser.readAscii(4).cancellable().then(function(reply) {
+            switch (reply) {
+              case Protocol.DATA:
+                return _this.parser.readBytes(4).then(function(lengthData) {
+                  var length;
+                  length = lengthData.readUInt32LE(0);
+                  return _this.parser.readByteFlow(length).progressed(function(chunk) {
+                    return transfer.write(chunk);
+                  }).then(function() {
+                    return readNext();
+                  });
+                });
+              case Protocol.DONE:
+                return _this.parser.readBytes(4).then(function(zero) {
+                  return true;
+                });
+              case Protocol.FAIL:
+                return _this._readError();
+              default:
+                return _this.parser.unexpected(reply, 'DATA, DONE or FAIL');
+            }
+          });
+        };
+      })(this);
+      reader = readNext()["catch"](Promise.CancellationError, (function(_this) {
+        return function(err) {
+          return _this.connection.end();
+        };
+      })(this))["catch"](function(err) {
+        return transfer.emit('error', err);
+      })["finally"](function() {
+        transfer.removeListener('cancel', cancelListener);
+        return transfer.end();
+      });
+      transfer.on('cancel', cancelListener = function() {
+        return reader.cancel();
+      });
+      return transfer;
+    };
+
+    Sync.prototype._readError = function() {
+      return this.parser.readBytes(4).then((function(_this) {
+        return function(zero) {
+          return _this.parser.readAll().then(function(buf) {
+            return Promise.reject(new Parser.FailError(buf.toString()));
+          });
+        };
+      })(this));
+    };
+
+    Sync.prototype._sendCommandWithLength = function(cmd, length) {
+      var payload;
+      if (cmd !== Protocol.DATA) {
+        debug(cmd);
+      }
+      payload = new Buffer(cmd.length + 4);
+      payload.write(cmd, 0, cmd.length);
+      payload.writeUInt32LE(length, cmd.length);
+      return this.connection.write(payload);
+    };
+
+    Sync.prototype._sendCommandWithArg = function(cmd, arg) {
+      var payload, pos;
+      debug("" + cmd + " " + arg);
+      payload = new Buffer(cmd.length + 4 + arg.length);
+      pos = 0;
+      payload.write(cmd, pos, cmd.length);
+      pos += cmd.length;
+      payload.writeUInt32LE(arg.length, pos);
+      pos += 4;
+      payload.write(arg, pos);
+      return this.connection.write(payload);
+    };
+
+    Sync.prototype._enoent = function(path) {
+      var err;
+      err = new Error("ENOENT, no such file or directory '" + path + "'");
+      err.errno = 34;
+      err.code = 'ENOENT';
+      err.path = path;
+      return Promise.reject(err);
+    };
+
+    return Sync;
+
+  })(EventEmitter);
+
+  module.exports = Sync;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/sync/entry.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/sync/entry.js
new file mode 100644
index 0000000..1025e82
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/sync/entry.js
@@ -0,0 +1,26 @@
+(function() {
+  var Entry, Stats,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Stats = require('./stats');
+
+  Entry = (function(_super) {
+    __extends(Entry, _super);
+
+    function Entry(name, mode, size, mtime) {
+      this.name = name;
+      Entry.__super__.constructor.call(this, mode, size, mtime);
+    }
+
+    Entry.prototype.toString = function() {
+      return this.name;
+    };
+
+    return Entry;
+
+  })(Stats);
+
+  module.exports = Entry;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/sync/pulltransfer.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/sync/pulltransfer.js
new file mode 100644
index 0000000..1032956
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/sync/pulltransfer.js
@@ -0,0 +1,34 @@
+(function() {
+  var PullTransfer, Stream,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Stream = require('stream');
+
+  PullTransfer = (function(_super) {
+    __extends(PullTransfer, _super);
+
+    function PullTransfer() {
+      this.stats = {
+        bytesTransferred: 0
+      };
+      PullTransfer.__super__.constructor.call(this);
+    }
+
+    PullTransfer.prototype.cancel = function() {
+      return this.emit('cancel');
+    };
+
+    PullTransfer.prototype.write = function(chunk, encoding, callback) {
+      this.stats.bytesTransferred += chunk.length;
+      this.emit('progress', this.stats);
+      return PullTransfer.__super__.write.call(this, chunk, encoding, callback);
+    };
+
+    return PullTransfer;
+
+  })(Stream.PassThrough);
+
+  module.exports = PullTransfer;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/sync/pushtransfer.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/sync/pushtransfer.js
new file mode 100644
index 0000000..a966c3c
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/sync/pushtransfer.js
@@ -0,0 +1,43 @@
+(function() {
+  var EventEmitter, PushTransfer,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  EventEmitter = require('events').EventEmitter;
+
+  PushTransfer = (function(_super) {
+    __extends(PushTransfer, _super);
+
+    function PushTransfer() {
+      this._stack = [];
+      this.stats = {
+        bytesTransferred: 0
+      };
+    }
+
+    PushTransfer.prototype.cancel = function() {
+      return this.emit('cancel');
+    };
+
+    PushTransfer.prototype.push = function(byteCount) {
+      return this._stack.push(byteCount);
+    };
+
+    PushTransfer.prototype.pop = function() {
+      var byteCount;
+      byteCount = this._stack.pop();
+      this.stats.bytesTransferred += byteCount;
+      return this.emit('progress', this.stats);
+    };
+
+    PushTransfer.prototype.end = function() {
+      return this.emit('end');
+    };
+
+    return PushTransfer;
+
+  })(EventEmitter);
+
+  module.exports = PushTransfer;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/sync/stats.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/sync/stats.js
new file mode 100644
index 0000000..39d2c9b
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/sync/stats.js
@@ -0,0 +1,57 @@
+(function() {
+  var Fs, Stats,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Fs = require('fs');
+
+  Stats = (function(_super) {
+    __extends(Stats, _super);
+
+    Stats.S_IFMT = 0xf000;
+
+    Stats.S_IFSOCK = 0xc000;
+
+    Stats.S_IFLNK = 0xa000;
+
+    Stats.S_IFREG = 0x8000;
+
+    Stats.S_IFBLK = 0x6000;
+
+    Stats.S_IFDIR = 0x4000;
+
+    Stats.S_IFCHR = 0x2000;
+
+    Stats.S_IFIFO = 0x1000;
+
+    Stats.S_ISUID = 0x800;
+
+    Stats.S_ISGID = 0x400;
+
+    Stats.S_ISVTX = 0x200;
+
+    Stats.S_IRWXU = 0x1c0;
+
+    Stats.S_IRUSR = 0x100;
+
+    Stats.S_IWUSR = 0x80;
+
+    Stats.S_IXUSR = 0x40;
+
+    Stats.S_IRWXG = 0x38;
+
+    Stats.S_IRGRP = 0x20;
+
+    function Stats(mode, size, mtime) {
+      this.mode = mode;
+      this.size = size;
+      this.mtime = new Date(mtime * 1000);
+    }
+
+    return Stats;
+
+  })(Fs.Stats);
+
+  module.exports = Stats;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/tcpusb/rollingcounter.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/tcpusb/rollingcounter.js
new file mode 100644
index 0000000..f339afa
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/tcpusb/rollingcounter.js
@@ -0,0 +1,24 @@
+(function() {
+  var RollingCounter;
+
+  RollingCounter = (function() {
+    function RollingCounter(max, min) {
+      this.max = max;
+      this.min = min != null ? min : 1;
+      this.now = this.min;
+    }
+
+    RollingCounter.prototype.next = function() {
+      if (!(this.now < this.max)) {
+        this.now = this.min;
+      }
+      return ++this.now;
+    };
+
+    return RollingCounter;
+
+  })();
+
+  module.exports = RollingCounter;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/tcpusb/server.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/tcpusb/server.js
new file mode 100644
index 0000000..0d13739
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/tcpusb/server.js
@@ -0,0 +1,72 @@
+(function() {
+  var EventEmitter, Net, Server, Socket,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Net = require('net');
+
+  EventEmitter = require('events').EventEmitter;
+
+  Socket = require('./socket');
+
+  Server = (function(_super) {
+    __extends(Server, _super);
+
+    function Server(client, serial, options) {
+      this.client = client;
+      this.serial = serial;
+      this.options = options;
+      this.connections = [];
+      this.server = Net.createServer();
+      this.server.on('error', (function(_this) {
+        return function(err) {
+          return _this.emit('error', err);
+        };
+      })(this));
+      this.server.on('listening', (function(_this) {
+        return function() {
+          return _this.emit('listening');
+        };
+      })(this));
+      this.server.on('close', (function(_this) {
+        return function() {
+          return _this.emit('close');
+        };
+      })(this));
+      this.server.on('connection', (function(_this) {
+        return function(conn) {
+          var socket;
+          socket = new Socket(_this.client, _this.serial, conn, _this.options);
+          _this.connections.push(socket);
+          return _this.emit('connection', socket);
+        };
+      })(this));
+    }
+
+    Server.prototype.listen = function() {
+      this.server.listen.apply(this.server, arguments);
+      return this;
+    };
+
+    Server.prototype.close = function() {
+      this.server.close();
+      return this;
+    };
+
+    Server.prototype.end = function() {
+      var conn, _i, _len, _ref;
+      _ref = this.connections;
+      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+        conn = _ref[_i];
+        conn.end();
+      }
+      return this;
+    };
+
+    return Server;
+
+  })(EventEmitter);
+
+  module.exports = Server;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/tcpusb/servicemap.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/tcpusb/servicemap.js
new file mode 100644
index 0000000..d4b3ccc
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/tcpusb/servicemap.js
@@ -0,0 +1,43 @@
+(function() {
+  var ServiceMap;
+
+  ServiceMap = (function() {
+    function ServiceMap() {
+      this.remotes = Object.create(null);
+    }
+
+    ServiceMap.prototype.end = function() {
+      var remote, remoteId, _ref;
+      _ref = this.remotes;
+      for (remoteId in _ref) {
+        remote = _ref[remoteId];
+        remote.end();
+      }
+      this.remotes = Object.create(null);
+    };
+
+    ServiceMap.prototype.put = function(remoteId, socket) {
+      return this.remotes[remoteId] = socket;
+    };
+
+    ServiceMap.prototype.get = function(remoteId) {
+      return this.remotes[remoteId] || null;
+    };
+
+    ServiceMap.prototype.remove = function(remoteId) {
+      var remote;
+      if (remote = this.remotes[remoteId]) {
+        delete this.remotes[remoteId];
+        return remote;
+      } else {
+        return null;
+      }
+    };
+
+    return ServiceMap;
+
+  })();
+
+  module.exports = ServiceMap;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/tcpusb/socket.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/tcpusb/socket.js
new file mode 100644
index 0000000..67fa54f
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/tcpusb/socket.js
@@ -0,0 +1,391 @@
+(function() {
+  var Auth, EventEmitter, Forge, Parser, Promise, Protocol, RollingCounter, ServiceMap, Socket, crypto, debug,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  crypto = require('crypto');
+
+  EventEmitter = require('events').EventEmitter;
+
+  Promise = require('bluebird');
+
+  Forge = require('node-forge');
+
+  debug = require('debug')('adb:tcpusb:socket');
+
+  Parser = require('../parser');
+
+  Protocol = require('../protocol');
+
+  Auth = require('../auth');
+
+  ServiceMap = require('./servicemap');
+
+  RollingCounter = require('./rollingcounter');
+
+  Socket = (function(_super) {
+    var AUTH_RSAPUBLICKEY, AUTH_SIGNATURE, AUTH_TOKEN, A_AUTH, A_CLSE, A_CNXN, A_OKAY, A_OPEN, A_SYNC, A_WRTE, TOKEN_LENGTH, UINT32_MAX;
+
+    __extends(Socket, _super);
+
+    A_SYNC = 0x434e5953;
+
+    A_CNXN = 0x4e584e43;
+
+    A_OPEN = 0x4e45504f;
+
+    A_OKAY = 0x59414b4f;
+
+    A_CLSE = 0x45534c43;
+
+    A_WRTE = 0x45545257;
+
+    A_AUTH = 0x48545541;
+
+    UINT32_MAX = 0xFFFFFFFF;
+
+    AUTH_TOKEN = 1;
+
+    AUTH_SIGNATURE = 2;
+
+    AUTH_RSAPUBLICKEY = 3;
+
+    TOKEN_LENGTH = 20;
+
+    function Socket(client, serial, socket, options) {
+      var _base;
+      this.client = client;
+      this.serial = serial;
+      this.socket = socket;
+      this.options = options != null ? options : {};
+      (_base = this.options).auth || (_base.auth = Promise.resolve(true));
+      this.ended = false;
+      this.parser = new Parser(this.socket);
+      this.version = 1;
+      this.maxPayload = 4096;
+      this.authorized = false;
+      this.syncToken = new RollingCounter(UINT32_MAX);
+      this.remoteId = new RollingCounter(UINT32_MAX);
+      this.services = new ServiceMap;
+      this.remoteAddress = this.socket.remoteAddress;
+      this.token = null;
+      this.signature = null;
+      this._inputLoop();
+    }
+
+    Socket.prototype.end = function() {
+      if (!this.ended) {
+        this.socket.end();
+        this.services.end();
+        this.emit('end');
+        this.ended = true;
+      }
+      return this;
+    };
+
+    Socket.prototype._inputLoop = function() {
+      return this._readMessage().then((function(_this) {
+        return function(message) {
+          return _this._route(message);
+        };
+      })(this)).then((function(_this) {
+        return function() {
+          return setImmediate(_this._inputLoop.bind(_this));
+        };
+      })(this))["catch"](Parser.PrematureEOFError, (function(_this) {
+        return function() {
+          return _this.end();
+        };
+      })(this))["catch"]((function(_this) {
+        return function(err) {
+          debug("Error: " + err.message);
+          _this.emit('error', err);
+          return _this.end();
+        };
+      })(this));
+    };
+
+    Socket.prototype._readMessage = function() {
+      return this.parser.readBytes(24).then(function(header) {
+        return {
+          command: header.readUInt32LE(0),
+          arg0: header.readUInt32LE(4),
+          arg1: header.readUInt32LE(8),
+          length: header.readUInt32LE(12),
+          check: header.readUInt32LE(16),
+          magic: header.readUInt32LE(20)
+        };
+      }).then((function(_this) {
+        return function(message) {
+          return _this.parser.readBytes(message.length).then(function(data) {
+            message.data = data;
+            return message;
+          });
+        };
+      })(this)).then((function(_this) {
+        return function(message) {
+          return _this._validateMessage(message);
+        };
+      })(this));
+    };
+
+    Socket.prototype._route = function(message) {
+      if (this.ended) {
+        return;
+      }
+      this.emit('userActivity', message);
+      switch (message.command) {
+        case A_SYNC:
+          return this._handleSyncMessage(message);
+        case A_CNXN:
+          return this._handleConnectionMessage(message);
+        case A_OPEN:
+          return this._handleOpenMessage(message);
+        case A_OKAY:
+          return this._handleOkayMessage(message);
+        case A_CLSE:
+          return this._handleCloseMessage(message);
+        case A_WRTE:
+          return this._handleWriteMessage(message);
+        case A_AUTH:
+          return this._handleAuthMessage(message);
+        default:
+          return this.emit('error', new Error("Unknown command " + message.command));
+      }
+    };
+
+    Socket.prototype._handleSyncMessage = function(message) {
+      return this._writeHeader(A_SYNC, 1, this.syncToken.next(), 0);
+    };
+
+    Socket.prototype._handleConnectionMessage = function(message) {
+      debug('A_CNXN', message);
+      return this._createToken().then((function(_this) {
+        return function(token) {
+          _this.token = token;
+          return _this._writeMessage(A_AUTH, AUTH_TOKEN, 0, _this.token);
+        };
+      })(this));
+    };
+
+    Socket.prototype._handleAuthMessage = function(message) {
+      debug('A_AUTH', message);
+      switch (message.arg0) {
+        case AUTH_SIGNATURE:
+          if (!this.signature) {
+            this.signature = message.data;
+          }
+          return this._writeMessage(A_AUTH, AUTH_TOKEN, 0, this.token);
+        case AUTH_RSAPUBLICKEY:
+          return Auth.parsePublicKey(message.data.slice(0, -1)).then((function(_this) {
+            return function(key) {
+              var digest, sig;
+              digest = _this.token.toString('binary');
+              sig = _this.signature.toString('binary');
+              if (key.verify(digest, sig)) {
+                return _this.options.auth(key).then(function() {
+                  return _this._deviceId().then(function(id) {
+                    _this.authorized = true;
+                    return _this._writeMessage(A_CNXN, _this.version, _this.maxPayload, "device::" + id);
+                  });
+                })["catch"](function(err) {
+                  return _this.end();
+                });
+              } else {
+                return _this.end();
+              }
+            };
+          })(this));
+        default:
+          return this.end();
+      }
+    };
+
+    Socket.prototype._handleOpenMessage = function(message) {
+      var command, localId, remoteId, service;
+      if (!this.authorized) {
+        return;
+      }
+      debug('A_OPEN', message);
+      localId = message.arg0;
+      remoteId = this.remoteId.next();
+      service = message.data.slice(0, -1);
+      command = service.toString().split(':', 1)[0];
+      return this.client.transport(this.serial).then((function(_this) {
+        return function(transport) {
+          var parser, pump;
+          if (_this.ended) {
+            return;
+          }
+          debug("Calling " + service);
+          _this.services.put(remoteId, transport);
+          transport.write(Protocol.encodeData(service));
+          parser = transport.parser;
+          pump = function() {
+            return new Promise(function(resolve, reject) {
+              var maybeRead, out;
+              out = parser.raw();
+              maybeRead = function() {
+                var chunk, _results;
+                _results = [];
+                while (chunk = _this._readChunk(out)) {
+                  _results.push(_this._writeMessage(A_WRTE, remoteId, localId, chunk));
+                }
+                return _results;
+              };
+              out.on('readable', maybeRead);
+              return out.on('end', resolve);
+            });
+          };
+          parser.readAscii(4).then(function(reply) {
+            switch (reply) {
+              case Protocol.OKAY:
+                _this._writeHeader(A_OKAY, remoteId, localId);
+                return pump();
+              case Protocol.FAIL:
+                return parser.readError();
+              default:
+                return parser.unexpected(reply, 'OKAY or FAIL');
+            }
+          })["catch"](Parser.PrematureEOFError, function() {
+            return true;
+          })["finally"](function() {
+            return _this._close(remoteId, localId);
+          })["catch"](Parser.FailError, function(err) {
+            debug("Unable to open transport: " + err);
+            return _this.end();
+          });
+        };
+      })(this));
+    };
+
+    Socket.prototype._handleOkayMessage = function(message) {
+      var localId, remoteId;
+      if (!this.authorized) {
+        return;
+      }
+      debug('A_OKAY', message);
+      localId = message.arg0;
+      return remoteId = message.arg1;
+    };
+
+    Socket.prototype._handleCloseMessage = function(message) {
+      var localId, remoteId;
+      if (!this.authorized) {
+        return;
+      }
+      debug('A_CLSE', message);
+      localId = message.arg0;
+      remoteId = message.arg1;
+      return this._close(remoteId, localId);
+    };
+
+    Socket.prototype._handleWriteMessage = function(message) {
+      var localId, remote, remoteId;
+      if (!this.authorized) {
+        return;
+      }
+      debug('A_WRTE', message);
+      localId = message.arg0;
+      remoteId = message.arg1;
+      if (remote = this.services.get(remoteId)) {
+        remote.write(message.data);
+        this._writeHeader(A_OKAY, remoteId, localId);
+      } else {
+        debug("A_WRTE to unknown socket pair " + localId + "/" + remoteId);
+      }
+      return true;
+    };
+
+    Socket.prototype._close = function(remoteId, localId) {
+      var remote;
+      if (remote = this.services.remove(remoteId)) {
+        remote.end();
+        return this._writeHeader(A_CLSE, remoteId, localId);
+      }
+    };
+
+    Socket.prototype._writeHeader = function(command, arg0, arg1, length, checksum) {
+      var header;
+      if (this.ended) {
+        return;
+      }
+      header = new Buffer(24);
+      header.writeUInt32LE(command, 0);
+      header.writeUInt32LE(arg0 || 0, 4);
+      header.writeUInt32LE(arg1 || 0, 8);
+      header.writeUInt32LE(length || 0, 12);
+      header.writeUInt32LE(checksum || 0, 16);
+      header.writeUInt32LE(this._magic(command), 20);
+      return this.socket.write(header);
+    };
+
+    Socket.prototype._writeMessage = function(command, arg0, arg1, data) {
+      if (this.ended) {
+        return;
+      }
+      if (!Buffer.isBuffer(data)) {
+        data = new Buffer(data);
+      }
+      this._writeHeader(command, arg0, arg1, data.length, this._checksum(data));
+      return this.socket.write(data);
+    };
+
+    Socket.prototype._validateMessage = function(message) {
+      if (message.magic !== this._magic(message.command)) {
+        throw new Error("Command failed magic check");
+      }
+      if (message.check !== this._checksum(message.data)) {
+        throw new Error("Message checksum doesn't match received message");
+      }
+      return message;
+    };
+
+    Socket.prototype._readChunk = function(stream) {
+      return stream.read(this.maxPayload) || stream.read();
+    };
+
+    Socket.prototype._checksum = function(data) {
+      var char, sum, _i, _len;
+      if (!Buffer.isBuffer(data)) {
+        throw new Error("Unable to calculate checksum of non-Buffer");
+      }
+      sum = 0;
+      for (_i = 0, _len = data.length; _i < _len; _i++) {
+        char = data[_i];
+        sum += char;
+      }
+      return sum;
+    };
+
+    Socket.prototype._magic = function(command) {
+      return (command ^ 0xffffffff) >>> 0;
+    };
+
+    Socket.prototype._createToken = function() {
+      return Promise.promisify(crypto.randomBytes)(TOKEN_LENGTH);
+    };
+
+    Socket.prototype._deviceId = function() {
+      return this.client.getProperties(this.serial).then(function(properties) {
+        var prop;
+        return ((function() {
+          var _i, _len, _ref, _results;
+          _ref = ['ro.product.name', 'ro.product.model', 'ro.product.device'];
+          _results = [];
+          for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+            prop = _ref[_i];
+            _results.push("" + prop + "=" + properties[prop] + ";");
+          }
+          return _results;
+        })()).join('');
+      });
+    };
+
+    return Socket;
+
+  })(EventEmitter);
+
+  module.exports = Socket;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/tracker.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/tracker.js
new file mode 100644
index 0000000..ca96230
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/tracker.js
@@ -0,0 +1,92 @@
+(function() {
+  var EventEmitter, Parser, Promise, Tracker,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  EventEmitter = require('events').EventEmitter;
+
+  Promise = require('bluebird');
+
+  Parser = require('./parser');
+
+  Tracker = (function(_super) {
+    __extends(Tracker, _super);
+
+    function Tracker(command) {
+      this.command = command;
+      this.deviceList = [];
+      this.deviceMap = {};
+      this.reader = this.read()["catch"](Parser.PrematureEOFError, (function(_this) {
+        return function(err) {
+          return _this.emit('end');
+        };
+      })(this))["catch"](Promise.CancellationError, (function(_this) {
+        return function(err) {
+          _this.command.connection.end();
+          return _this.emit('end');
+        };
+      })(this))["catch"]((function(_this) {
+        return function(err) {
+          _this.emit('error', err);
+          return _this.emit('end');
+        };
+      })(this));
+    }
+
+    Tracker.prototype.read = function() {
+      return this.command._readDevices().cancellable().then((function(_this) {
+        return function(list) {
+          _this.update(list);
+          return _this.read();
+        };
+      })(this));
+    };
+
+    Tracker.prototype.update = function(newList) {
+      var changeSet, device, newMap, oldDevice, _i, _j, _len, _len1, _ref;
+      changeSet = {
+        removed: [],
+        changed: [],
+        added: []
+      };
+      newMap = {};
+      for (_i = 0, _len = newList.length; _i < _len; _i++) {
+        device = newList[_i];
+        oldDevice = this.deviceMap[device.id];
+        if (oldDevice) {
+          if (oldDevice.type !== device.type) {
+            changeSet.changed.push(device);
+            this.emit('change', device, oldDevice);
+          }
+        } else {
+          changeSet.added.push(device);
+          this.emit('add', device);
+        }
+        newMap[device.id] = device;
+      }
+      _ref = this.deviceList;
+      for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) {
+        device = _ref[_j];
+        if (!newMap[device.id]) {
+          changeSet.removed.push(device);
+          this.emit('remove', device);
+        }
+      }
+      this.emit('changeSet', changeSet);
+      this.deviceList = newList;
+      this.deviceMap = newMap;
+      return this;
+    };
+
+    Tracker.prototype.end = function() {
+      this.reader.cancel();
+      return this;
+    };
+
+    return Tracker;
+
+  })(EventEmitter);
+
+  module.exports = Tracker;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/util.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/util.js
new file mode 100644
index 0000000..8207028
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/adb/util.js
@@ -0,0 +1,16 @@
+(function() {
+  var Auth, Parser;
+
+  Parser = require('./parser');
+
+  Auth = require('./auth');
+
+  module.exports.readAll = function(stream, callback) {
+    return new Parser(stream).readAll(stream).nodeify(callback);
+  };
+
+  module.exports.parsePublicKey = function(keyString, callback) {
+    return Auth.parsePublicKey(keyString).nodeify(callback);
+  };
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/cli.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/cli.js
new file mode 100644
index 0000000..11dc9cf
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/lib/cli.js
@@ -0,0 +1,42 @@
+(function() {
+  var Auth, Promise, forge, fs, pkg, program;
+
+  fs = require('fs');
+
+  program = require('commander');
+
+  Promise = require('bluebird');
+
+  forge = require('node-forge');
+
+  pkg = require('../package');
+
+  Auth = require('./adb/auth');
+
+  Promise.longStackTraces();
+
+  program.version(pkg.version);
+
+  program.command('pubkey-convert <file>').option('-f, --format <format>', 'format (pem or openssh)', String, 'pem').description('Converts an ADB-generated public key into PEM format.').action(function(file, options) {
+    return Auth.parsePublicKey(fs.readFileSync(file)).then(function(key) {
+      switch (options.format.toLowerCase()) {
+        case 'pem':
+          return console.log(forge.pki.publicKeyToPem(key).trim());
+        case 'openssh':
+          return console.log(forge.ssh.publicKeyToOpenSSH(key, 'adbkey').trim());
+        default:
+          console.error("Unsupported format '" + options.format + "'");
+          return process.exit(1);
+      }
+    });
+  });
+
+  program.command('pubkey-fingerprint <file>').description('Outputs the fingerprint of an ADB-generated public key.').action(function(file) {
+    return Auth.parsePublicKey(fs.readFileSync(file)).then(function(key) {
+      return console.log('%s %s', key.fingerprint, key.comment);
+    });
+  });
+
+  program.parse(process.argv);
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-logcat/LICENSE b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-logcat/LICENSE
new file mode 100644
index 0000000..2ffff3d
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-logcat/LICENSE
@@ -0,0 +1,13 @@
+Copyright © CyberAgent, Inc. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-logcat/README.md b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-logcat/README.md
new file mode 100644
index 0000000..df54e35
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-logcat/README.md
@@ -0,0 +1,240 @@
+# adbkit-logcat
+
+**adbkit-logcat** provides a [Node.js][nodejs] interface for working with output produced by the Android [`logcat` tool][logcat-site]. It takes a log stream (that you must create separately), parses it, and emits log entries in real-time as they occur. Possible use cases include storing logs in a database, forwarding logs via [MessagePack][msgpack], or just advanced filtering.
+
+## Getting started
+
+Install via NPM:
+
+```bash
+npm install --save adbkit-logcat
+```
+
+Note that while adbkit-logcat is written in CoffeeScript, it is compiled to JavaScript before publishing to NPM, which means that you are not required to use CoffeeScript.
+
+### Examples
+
+#### Output all log messages
+
+##### JavaScript
+
+```javascript
+var logcat = require('adbkit-logcat');
+var spawn = require('child_process').spawn;
+
+// Retrieve a binary log stream
+var proc = spawn('adb', ['logcat', '-B']);
+
+// Connect logcat to the stream
+reader = logcat.readStream(proc.stdout);
+reader.on('entry', function(entry) {
+  console.log(entry.message);
+});
+
+// Make sure we don't leave anything hanging
+process.on('exit', function() {
+  proc.kill();
+});
+```
+
+##### CoffeeScript
+
+```coffeescript
+Logcat = require 'adbkit-logcat'
+{spawn} = require 'child_process'
+
+# Retrieve a binary log stream
+proc = spawn 'adb', ['logcat', '-B']
+
+# Connect logcat to the stream
+reader = Logcat.readStream proc.stdout
+reader.on 'entry', (entry) ->
+  console.log entry.message
+
+# Make sure we don't leave anything hanging
+process.on 'exit', ->
+  proc.kill()
+```
+
+## API
+
+### Logcat
+
+#### logcat.Priority
+
+Exposes `Priority`. See below for details.
+
+#### logcat.Reader
+
+Exposes `Reader`. See below for details.
+
+#### logcat.readStream(stream[, options])
+
+Creates a logcat reader instance from the provided logcat event [`Stream`][node-stream]. Note that you must create the stream separately.
+
+* **stream** The event stream to read.
+* **options** Optional. The following options are supported:
+    - **format** The format of the stream. Currently, the only supported value is `'binary'`, which (for example) `adb logcat -B` produces. Defaults to `'binary'`.
+    - **fixLineFeeds** All programs run via the ADB shell transform any `'\n'` in the output to `'\r\n'`, which breaks binary content. If set, this option reverses the transformation before parsing the stream. Defaults to `true`.
+* Returns: The `Reader` instance.
+
+### Priority
+
+#### Constants
+
+The following static properties are available:
+
+* **Priority.UNKNOWN** i.e. `0`.
+* **Priority.DEFAULT** i.e. `1`. Not available when reading a stream.
+* **Priority.VERBOSE** i.e. `2`.
+* **Priority.DEBUG** i.e. `3`.
+* **Priority.INFO** i.e. `4`.
+* **Priority.WARN** i.e. `5`.
+* **Priority.ERROR** i.e. `6`.
+* **Priority.FATAL** i.e. `7`.
+* **Priority.SILENT** i.e. `8`. Not available when reading a stream.
+
+#### Priority.fromLetter(letter)
+
+Static method to convert the given `letter` into a numeric priority. For example, `Priority.fromName('d')` would return `Priority.DEBUG`.
+
+* **letter** The priority as a `String`. Any single, case-insensitive character matching the first character of any `Priority` constant is accepted.
+* Returns: The priority as a `Number`, or `undefined`.
+
+#### Priority.fromName(name)
+
+Static method to convert the given `name` into a numeric priority. For example, `Priority.fromName('debug')` (or `Priority.fromName('d')`) would return `Priority.DEBUG`.
+
+* **name** The priority as a `String`. Any full, case-insensitive match of the `Priority` constants is accepted. If no match is found, falls back to `Priority.fromLetter()`.
+* Returns: The priority as a `Number`, or `undefined`.
+
+#### Priority.toLetter(priority)
+
+Static method to convert the numeric priority into its letter representation. For example, `Priority.toLetter(Priority.DEBUG)` would return `'D'`.
+
+* **priority** The priority as a `Number`. Any `Priority` constant value is accepted.
+* Returns: The priority as a `String` letter, or `undefined`.
+
+#### Priority.toName(priority)
+
+Static method to convert the numeric priority into its full string representation. For example, `Priority.toLetter(Priority.DEBUG)` would return `'DEBUG'`.
+
+* **priority** The priority as a `Number`. Any `Priority` constant value is accepted.
+* Returns: The priority as a `String`, or `undefined`.
+
+### Reader
+
+A reader instance, which is an [`EventEmitter`][node-events].
+
+#### Events
+
+The following events are available:
+
+* **error** **(err)** Emitted when an error occurs.
+    * **err** An `Error`.
+* **end** Emitted when the stream ends.
+* **finish** Emitted when the stream finishes.
+* **entry** **(entry)** Emitted when the stream finishes.
+    * **entry** A log `Entry`. See below for details.
+
+#### constructor([options])
+
+For advanced users. Manually constructs a `Reader` instance. Useful for testing and/or playing around. Normally you would use `logcat.readStream()` to create the instance.
+
+* **options** See `logcat.readStream()` for details.
+* Returns: N/A
+
+#### reader.connect(stream)
+
+For advanced users. When instantiated manually (not via `logcat.readStream()`), connects the `Reader` instance to the given stream.
+
+* **stream** See `logcat.readStream()` for details.
+* Returns: The `Reader` instance.
+
+#### reader.end()
+
+Convenience method for ending the stream.
+
+* Returns: The `Reader` instance.
+
+#### reader.exclude(tag)
+
+Skip entries with the provided tag. Alias for `reader.include(tag, Priority.SILENT)`. Note that even skipped events have to be parsed so that they can be ignored.
+
+* **tag** The tag string to exclude. If `'*'`, works the same as `reader.excludeAll()`.
+* Returns: The `Reader` instance.
+
+#### reader.excludeAll()
+
+Skip **ALL** entries. Alias for `reader.includeAll(Priority.SILENT)`. Any entries you wish to see must be included via `include()`/`includeAll()`.
+
+* Returns: The `Reader` instance.
+
+#### reader.include(tag[, priority])
+
+Include all entries with the given tag and a priority higher or equal to the given `priority`.
+
+* **tag** The tag string to include. If `'*'`, works the same as `reader.includeAll(priority)`.
+* **priority** Optional. A lower bound for the priority. Any numeric `Priority` constant or any `String` value accepted by `Priority.fromName()` is accepted. Defaults to `Priority.DEBUG`.
+* Returns: The `Reader` instance.
+
+#### reader.includeAll([priority])
+
+Include all entries with a priority higher or equal to the given `priority`.
+
+* **tag** The tag string to exclude.
+* **priority** Optional. See `reader.include()` for details.
+* Returns: The `Reader` instance.
+
+#### reader.resetFilters()
+
+Resets all inclusions/exclusions.
+
+* Returns: The `Reader` instance.
+
+### Entry
+
+A log entry.
+
+#### Properties
+
+The following properties are available:
+
+* **date** Event time as a `Date`.
+* **pid** Process ID as a `Number`.
+* **tid** Thread ID as a `Number`.
+* **priority** Event priority as a `Number`. You can use `logcat.Priority` to convert the value into a `String`.
+* **tag** Event tag as a `String`.
+* **message** Message as a `String`.
+
+#### entry.toBinary()
+
+Converts the entry back to the binary log format.
+
+* Returns: The binary event as a [`Buffer`][node-buffer].
+
+## More information
+
+* Liblog
+    - [logprint.c][logprint-source]
+* Logcat
+    - [logcat.cpp][logcat-source]
+
+## Contributing
+
+See [CONTRIBUTING.md](CONTRIBUTING.md).
+
+## License
+
+See [LICENSE](LICENSE).
+
+Copyright © CyberAgent, Inc. All Rights Reserved.
+
+[nodejs]: <http://nodejs.org/>
+[msgpack]: <http://msgpack.org/>
+[logcat-site]: <http://developer.android.com/tools/help/logcat.html>
+[logprint-source]: <https://github.com/android/platform_system_core/blob/master/liblog/logprint.c>
+[logcat-source]: <https://github.com/android/platform_system_core/blob/master/logcat/logcat.cpp>
+[node-stream]: <http://nodejs.org/api/stream.html>
+[node-events]: <http://nodejs.org/api/events.html>
+[node-buffer]: <http://nodejs.org/api/buffer.html>
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-logcat/index.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-logcat/index.js
new file mode 100644
index 0000000..607433c
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-logcat/index.js
@@ -0,0 +1,15 @@
+(function() {
+  var Path;
+
+  Path = require('path');
+
+  module.exports = (function() {
+    switch (Path.extname(__filename)) {
+      case '.coffee':
+        return require('./src/logcat');
+      default:
+        return require('./lib/logcat');
+    }
+  })();
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat.js
new file mode 100644
index 0000000..d224f07
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat.js
@@ -0,0 +1,25 @@
+(function() {
+  var Logcat, Priority, Reader;
+
+  Reader = require('./logcat/reader');
+
+  Priority = require('./logcat/priority');
+
+  Logcat = (function() {
+    function Logcat() {}
+
+    Logcat.readStream = function(stream, options) {
+      return new Reader(options).connect(stream);
+    };
+
+    return Logcat;
+
+  })();
+
+  Logcat.Reader = Reader;
+
+  Logcat.Priority = Priority;
+
+  module.exports = Logcat;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/entry.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/entry.js
new file mode 100644
index 0000000..1a7f5d2
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/entry.js
@@ -0,0 +1,76 @@
+(function() {
+  var Entry;
+
+  Entry = (function() {
+    function Entry() {
+      this.date = null;
+      this.pid = -1;
+      this.tid = -1;
+      this.priority = null;
+      this.tag = null;
+      this.message = null;
+    }
+
+    Entry.prototype.setDate = function(date) {
+      this.date = date;
+    };
+
+    Entry.prototype.setPid = function(pid) {
+      this.pid = pid;
+    };
+
+    Entry.prototype.setTid = function(tid) {
+      this.tid = tid;
+    };
+
+    Entry.prototype.setPriority = function(priority) {
+      this.priority = priority;
+    };
+
+    Entry.prototype.setTag = function(tag) {
+      this.tag = tag;
+    };
+
+    Entry.prototype.setMessage = function(message) {
+      this.message = message;
+    };
+
+    Entry.prototype.toBinary = function() {
+      var buffer, cursor, length;
+      length = 20;
+      length += 1;
+      length += this.tag.length;
+      length += 1;
+      length += this.message.length;
+      length += 1;
+      buffer = new Buffer(length);
+      cursor = 0;
+      buffer.writeUInt16LE(length - 20, cursor);
+      cursor += 4;
+      buffer.writeInt32LE(this.pid, cursor);
+      cursor += 4;
+      buffer.writeInt32LE(this.tid, cursor);
+      cursor += 4;
+      buffer.writeInt32LE(Math.floor(this.date.getTime() / 1000), cursor);
+      cursor += 4;
+      buffer.writeInt32LE((this.date.getTime() % 1000) * 1000000, cursor);
+      cursor += 4;
+      buffer[cursor] = this.priority;
+      cursor += 1;
+      buffer.write(this.tag, cursor, this.tag.length);
+      cursor += this.tag.length;
+      buffer[cursor] = 0x00;
+      cursor += 1;
+      buffer.write(this.message, cursor, this.message.length);
+      cursor += this.message.length;
+      buffer[cursor] = 0x00;
+      return buffer;
+    };
+
+    return Entry;
+
+  })();
+
+  module.exports = Entry;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/parser.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/parser.js
new file mode 100644
index 0000000..e3c2939
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/parser.js
@@ -0,0 +1,32 @@
+(function() {
+  var EventEmitter, Parser, _ref,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  EventEmitter = require('events').EventEmitter;
+
+  Parser = (function(_super) {
+    __extends(Parser, _super);
+
+    function Parser() {
+      _ref = Parser.__super__.constructor.apply(this, arguments);
+      return _ref;
+    }
+
+    Parser.get = function(type) {
+      var parser;
+      parser = require("./parser/" + type);
+      return new parser();
+    };
+
+    Parser.prototype.parse = function() {
+      throw new Error("parse() is unimplemented");
+    };
+
+    return Parser;
+
+  })(EventEmitter);
+
+  module.exports = Parser;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/parser/binary.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/parser/binary.js
new file mode 100644
index 0000000..b861ace
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/parser/binary.js
@@ -0,0 +1,78 @@
+(function() {
+  var Binary, Entry, Parser, Priority,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Parser = require('../parser');
+
+  Entry = require('../entry');
+
+  Priority = require('../priority');
+
+  Binary = (function(_super) {
+    var HEADER_LENGTH;
+
+    __extends(Binary, _super);
+
+    HEADER_LENGTH = 20;
+
+    function Binary() {
+      this.buffer = new Buffer('');
+    }
+
+    Binary.prototype.parse = function(chunk) {
+      var cursor, data, entry, length, nsec, sec;
+      this.buffer = Buffer.concat([this.buffer, chunk]);
+      while (this.buffer.length > HEADER_LENGTH) {
+        cursor = 0;
+        length = this.buffer.readUInt16LE(cursor);
+        if (this.buffer.length < HEADER_LENGTH + length) {
+          break;
+        }
+        entry = new Entry;
+        cursor += 4;
+        entry.setPid(this.buffer.readInt32LE(cursor));
+        cursor += 4;
+        entry.setTid(this.buffer.readInt32LE(cursor));
+        cursor += 4;
+        sec = this.buffer.readInt32LE(cursor);
+        cursor += 4;
+        nsec = this.buffer.readInt32LE(cursor);
+        entry.setDate(new Date(sec * 1000 + nsec / 1000000));
+        cursor += 4;
+        data = this.buffer.slice(cursor, cursor + length);
+        cursor += length;
+        this.buffer = this.buffer.slice(cursor);
+        this._processEntry(entry, data);
+      }
+      if (this.buffer.length) {
+        this.emit('wait');
+      } else {
+        this.emit('drain');
+      }
+    };
+
+    Binary.prototype._processEntry = function(entry, data) {
+      var cursor, length;
+      entry.setPriority(data[0]);
+      cursor = 1;
+      length = data.length;
+      while (cursor < length) {
+        if (data[cursor] === 0) {
+          entry.setTag(data.slice(1, cursor).toString());
+          entry.setMessage(data.slice(cursor + 1, length - 1).toString());
+          this.emit('entry', entry);
+          return;
+        }
+        cursor += 1;
+      }
+      this.emit('error', new Error("Unprocessable entry data '" + data + "'"));
+    };
+
+    return Binary;
+
+  })(Parser);
+
+  module.exports = Binary;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/priority.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/priority.js
new file mode 100644
index 0000000..532ea62
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/priority.js
@@ -0,0 +1,89 @@
+(function() {
+  var Priority;
+
+  Priority = (function() {
+    var letterNames, letters, names;
+
+    function Priority() {}
+
+    Priority.UNKNOWN = 0;
+
+    Priority.DEFAULT = 1;
+
+    Priority.VERBOSE = 2;
+
+    Priority.DEBUG = 3;
+
+    Priority.INFO = 4;
+
+    Priority.WARN = 5;
+
+    Priority.ERROR = 6;
+
+    Priority.FATAL = 7;
+
+    Priority.SILENT = 8;
+
+    names = {
+      0: 'UNKNOWN',
+      1: 'DEFAULT',
+      2: 'VERBOSE',
+      3: 'DEBUG',
+      4: 'INFO',
+      5: 'WARN',
+      6: 'ERROR',
+      7: 'FATAL',
+      8: 'SILENT'
+    };
+
+    letters = {
+      '?': Priority.UNKNOWN,
+      'V': Priority.VERBOSE,
+      'D': Priority.DEBUG,
+      'I': Priority.INFO,
+      'W': Priority.WARN,
+      'E': Priority.ERROR,
+      'F': Priority.FATAL,
+      'S': Priority.SILENT
+    };
+
+    letterNames = {
+      0: '?',
+      1: '?',
+      2: 'V',
+      3: 'D',
+      4: 'I',
+      5: 'W',
+      6: 'E',
+      7: 'F',
+      8: 'S'
+    };
+
+    Priority.fromName = function(name) {
+      var value;
+      value = Priority[name.toUpperCase()];
+      if (value || value === 0) {
+        return value;
+      }
+      return Priority.fromLetter(name);
+    };
+
+    Priority.toName = function(value) {
+      return names[value];
+    };
+
+    Priority.fromLetter = function(letter) {
+      return letters[letter.toUpperCase()];
+    };
+
+    Priority.toLetter = function(value) {
+      return letterNames[value];
+    };
+
+    return Priority;
+
+  })();
+
+  module.exports = Priority;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/reader.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/reader.js
new file mode 100644
index 0000000..7f07626
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/reader.js
@@ -0,0 +1,137 @@
+(function() {
+  var EventEmitter, Parser, Priority, Reader, Transform,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  EventEmitter = require('events').EventEmitter;
+
+  Parser = require('./parser');
+
+  Transform = require('./transform');
+
+  Priority = require('./priority');
+
+  Reader = (function(_super) {
+    __extends(Reader, _super);
+
+    Reader.ANY = '*';
+
+    function Reader(options) {
+      var _base;
+      this.options = options != null ? options : {};
+      (_base = this.options).format || (_base.format = 'binary');
+      if (this.options.fixLineFeeds == null) {
+        this.options.fixLineFeeds = true;
+      }
+      this.filters = {
+        all: -1,
+        tags: {}
+      };
+      this.parser = Parser.get(this.options.format);
+      this.stream = null;
+    }
+
+    Reader.prototype.exclude = function(tag) {
+      if (tag === Reader.ANY) {
+        return this.excludeAll();
+      }
+      this.filters.tags[tag] = Priority.SILENT;
+      return this;
+    };
+
+    Reader.prototype.excludeAll = function() {
+      this.filters.all = Priority.SILENT;
+      return this;
+    };
+
+    Reader.prototype.include = function(tag, priority) {
+      if (priority == null) {
+        priority = Priority.DEBUG;
+      }
+      if (tag === Reader.ANY) {
+        return this.includeAll(priority);
+      }
+      this.filters.tags[tag] = this._priority(priority);
+      return this;
+    };
+
+    Reader.prototype.includeAll = function(priority) {
+      if (priority == null) {
+        priority = Priority.DEBUG;
+      }
+      this.filters.all = this._priority(priority);
+      return this;
+    };
+
+    Reader.prototype.resetFilters = function() {
+      this.filters.all = -1;
+      this.filters.tags = {};
+      return this;
+    };
+
+    Reader.prototype._hook = function() {
+      var transform,
+        _this = this;
+      if (this.options.fixLineFeeds) {
+        transform = this.stream.pipe(new Transform);
+        transform.on('data', function(data) {
+          return _this.parser.parse(data);
+        });
+      } else {
+        this.stream.on('data', function(data) {
+          return _this.parser.parse(data);
+        });
+      }
+      this.stream.on('error', function(err) {
+        return _this.emit('error', err);
+      });
+      this.stream.on('end', function() {
+        return _this.emit('end');
+      });
+      this.stream.on('finish', function() {
+        return _this.emit('finish');
+      });
+      this.parser.on('entry', function(entry) {
+        if (_this._filter(entry)) {
+          return _this.emit('entry', entry);
+        }
+      });
+      this.parser.on('error', function(err) {
+        return _this.emit('error', err);
+      });
+    };
+
+    Reader.prototype._filter = function(entry) {
+      var priority;
+      priority = this.filters.tags[entry.tag];
+      if (!(priority >= 0)) {
+        priority = this.filters.all;
+      }
+      return entry.priority >= priority;
+    };
+
+    Reader.prototype._priority = function(priority) {
+      if (typeof priority === 'number') {
+        return priority;
+      }
+      return Priority.fromName(priority);
+    };
+
+    Reader.prototype.connect = function(stream) {
+      this.stream = stream;
+      this._hook();
+      return this;
+    };
+
+    Reader.prototype.end = function() {
+      this.stream.end();
+      return this;
+    };
+
+    return Reader;
+
+  })(EventEmitter);
+
+  module.exports = Reader;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/transform.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/transform.js
new file mode 100644
index 0000000..b6fe7a2
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/transform.js
@@ -0,0 +1,51 @@
+(function() {
+  var Stream, Transform,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Stream = require('stream');
+
+  Transform = (function(_super) {
+    __extends(Transform, _super);
+
+    function Transform(options) {
+      this.savedR = null;
+      Transform.__super__.constructor.call(this, options);
+    }
+
+    Transform.prototype._transform = function(chunk, encoding, done) {
+      var hi, last, lo;
+      lo = 0;
+      hi = 0;
+      if (this.savedR) {
+        if (chunk[0] !== 0x0a) {
+          this.push(this.savedR);
+        }
+        this.savedR = null;
+      }
+      last = chunk.length - 1;
+      while (hi <= last) {
+        if (chunk[hi] === 0x0d) {
+          if (hi === last) {
+            this.savedR = chunk.slice(last);
+            break;
+          } else if (chunk[hi + 1] === 0x0a) {
+            this.push(chunk.slice(lo, hi));
+            lo = hi + 1;
+          }
+        }
+        hi += 1;
+      }
+      if (hi !== lo) {
+        this.push(chunk.slice(lo, hi));
+      }
+      done();
+    };
+
+    return Transform;
+
+  })(Stream.Transform);
+
+  module.exports = Transform;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-logcat/package.json b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-logcat/package.json
new file mode 100644
index 0000000..6fe20fd
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-logcat/package.json
@@ -0,0 +1,75 @@
+{
+  "name": "adbkit-logcat",
+  "version": "1.0.3",
+  "description": "A Node.js interface for working with Android's logcat output.",
+  "keywords": [
+    "adb",
+    "adbkit",
+    "logcat"
+  ],
+  "bugs": {
+    "url": "https://github.com/CyberAgent/adbkit-logcat/issues"
+  },
+  "license": "Apache-2.0",
+  "author": {
+    "name": "CyberAgent, Inc.",
+    "email": "npm@cyberagent.co.jp",
+    "url": "http://www.cyberagent.co.jp/"
+  },
+  "main": "./index",
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/CyberAgent/adbkit-logcat.git"
+  },
+  "scripts": {
+    "postpublish": "grunt clean",
+    "prepublish": "grunt coffee",
+    "test": "grunt test"
+  },
+  "dependencies": {},
+  "devDependencies": {
+    "chai": "~1.8.1",
+    "coffee-script": "~1.6.3",
+    "grunt": "~0.4.1",
+    "grunt-cli": "~0.1.11",
+    "grunt-coffeelint": "~0.0.7",
+    "grunt-contrib-clean": "~0.5.0",
+    "grunt-contrib-coffee": "~0.7.0",
+    "grunt-contrib-watch": "~0.5.3",
+    "grunt-exec": "~0.4.2",
+    "grunt-jsonlint": "~1.0.2",
+    "grunt-notify": "~0.2.16",
+    "mocha": "~1.14.0",
+    "sinon": "~1.7.3",
+    "sinon-chai": "~2.4.0"
+  },
+  "engines": {
+    "node": ">= 0.10.4"
+  },
+  "homepage": "https://github.com/CyberAgent/adbkit-logcat",
+  "_id": "adbkit-logcat@1.0.3",
+  "dist": {
+    "shasum": "8b5fef57086c02c9e24004af5706ee508d83db07",
+    "tarball": "http://registry.npmjs.org/adbkit-logcat/-/adbkit-logcat-1.0.3.tgz"
+  },
+  "_from": "adbkit-logcat@~1.0.3",
+  "_npmVersion": "1.4.3",
+  "_npmUser": {
+    "name": "sorccu",
+    "email": "simo@shoqolate.com"
+  },
+  "maintainers": [
+    {
+      "name": "sorccu",
+      "email": "simo@shoqolate.com"
+    },
+    {
+      "name": "cyberagent",
+      "email": "npm@cyberagent.co.jp"
+    }
+  ],
+  "directories": {},
+  "_shasum": "8b5fef57086c02c9e24004af5706ee508d83db07",
+  "_resolved": "https://registry.npmjs.org/adbkit-logcat/-/adbkit-logcat-1.0.3.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/LICENSE b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/LICENSE
new file mode 100644
index 0000000..2ffff3d
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/LICENSE
@@ -0,0 +1,13 @@
+Copyright © CyberAgent, Inc. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/README.md b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/README.md
new file mode 100644
index 0000000..97977bb
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/README.md
@@ -0,0 +1,469 @@
+# adbkit-monkey
+
+**adbkit-monkey** provides a [Node.js][nodejs] interface for working with the Android [`monkey` tool][monkey-site]. Albeit undocumented, they monkey program can be started in TCP mode with the `--port` argument. In this mode, it accepts a [range of commands][monkey-proto] that can be used to interact with the UI in a non-random manner. This mode is also used internally by the [`monkeyrunner` tool][monkeyrunner-site], although the documentation claims no relation to the monkey tool.
+
+## Getting started
+
+Install via NPM:
+
+```bash
+npm install --save adbkit-monkey
+```
+
+Note that while adbkit-monkey is written in CoffeeScript, it is compiled to JavaScript before publishing to NPM, which means that you are not required to use CoffeeScript.
+
+### Examples
+
+The following examples assume that monkey is already running (via `adb shell monkey --port 1080`) and a port forwarding (`adb forward tcp:1080 tcp:1080`) has been set up.
+
+#### Press the home button
+
+```javascript
+var assert = require('assert');
+var monkey = require('adbkit-monkey');
+
+var client = monkey.connect({ port: 1080 });
+
+client.press(3 /* KEYCODE_HOME */, function(err) {
+  assert.ifError(err);
+  console.log('Pressed home button');
+  client.end();
+});
+```
+
+#### Drag out the notification bar
+
+```javascript
+var assert = require('assert');
+var monkey = require('adbkit-monkey');
+
+var client = monkey.connect({ port: 1080 });
+
+client.multi()
+  .touchDown(100, 0)
+  .sleep(5)
+  .touchMove(100, 20)
+  .sleep(5)
+  .touchMove(100, 40)
+  .sleep(5)
+  .touchMove(100, 60)
+  .sleep(5)
+  .touchMove(100, 80)
+  .sleep(5)
+  .touchMove(100, 100)
+  .sleep(5)
+  .touchUp(100, 100)
+  .sleep(5)
+  .execute(function(err) {
+    assert.ifError(err);
+    console.log('Dragged out the notification bar');
+    client.end();
+  });
+```
+
+#### Get display size
+
+```javascript
+var assert = require('assert');
+var monkey = require('adbkit-monkey');
+
+var client = monkey.connect({ port: 1080 });
+
+client.getDisplayWidth(function(err, width) {
+  assert.ifError(err);
+  client.getDisplayHeight(function(err, height) {
+    assert.ifError(err);
+    console.log('Display size is %dx%d', width, height);
+    client.end();
+  });
+});
+```
+
+#### Type text
+
+Note that you should manually focus a text field first.
+
+```javascript
+var assert = require('assert');
+var monkey = require('adbkit-monkey');
+
+var client = monkey.connect({ port: 1080 });
+
+client.type('hello monkey!', function(err) {
+  assert.ifError(err);
+  console.log('Said hello to monkey');
+  client.end();
+});
+```
+
+## API
+
+### Monkey
+
+#### monkey.connect(options)
+
+Uses [Net.connect()][node-net] to open a new TCP connection to monkey. Useful when combined with `adb forward`.
+
+* **options** Any options [`Net.connect()`][node-net] accepts.
+* Returns: A new monkey `Client` instance.
+
+#### monkey.connectStream(stream)
+
+Attaches a monkey client to an existing monkey protocol stream.
+
+* **stream** The monkey protocol [`Stream`][node-stream].
+* Returns: A new monkey `Client` instance.
+
+### Client
+
+Implements `Api`. See below for details.
+
+#### Events
+
+The following events are available:
+
+* **error** **(err)** Emitted when an error occurs.
+    * **err** An `Error`.
+* **end** Emitted when the stream ends.
+* **finish** Emitted when the stream finishes.
+
+#### client.end()
+
+Ends the underlying stream/connection.
+
+* Returns: The `Client` instance.
+
+#### client.multi()
+
+Returns a new API wrapper that buffers commands for simultaneous delivery instead of sending them individually. When used with `api.sleep()`, allows simple gestures to be executed.
+
+* Returns: A new `Multi` instance. See `Multi` below.
+
+#### client.send(command, callback)
+
+Sends a raw protocol command to monkey.
+
+* **command** The command to send. When `String`, a single command is sent. When `Array`, a series of commands is sent at once.
+* **callback(err, value, command)** Called when monkey responds to the command. If multiple commands were sent, the callback will be called once for each command.
+    * **err** `null` when successful, `Error` otherwise.
+    * **value** The response value, if any.
+    * **command** The command the response is for.
+* Returns: The `Client` instance.
+
+### Api
+
+The monkey API implemented by `Client` and `Multi`.
+
+#### api.done(callback)
+
+Closes the current monkey session and allows a new session to connect.
+
+* **callback(err)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+* Returns: The `Api` implementation instance.
+
+#### api.flipClose(callback)
+
+Simulates closing the keyboard.
+
+* **callback(err)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+* Returns: The `Api` implementation instance.
+
+#### api.flipOpen(callback)
+
+Simulates opening the keyboard.
+
+* **callback(err)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+* Returns: The `Api` implementation instance.
+
+#### api.get(name, callback)
+
+Gets the value of a variable. Use `api.list()` to retrieve a list of supported variables.
+
+* **name** The name of the variable.
+* **callback(err, value)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+    * **value** The value of the variable.
+* Returns: The `Api` implementation instance.
+
+#### api.getAmCurrentAction(callback)
+
+Alias for `api.get('am.current.action', callback)`.
+
+#### api.getAmCurrentCategories(callback)
+
+Alias for `api.get('am.current.categories', callback)`.
+
+#### api.getAmCurrentCompClass(callback)
+
+Alias for `api.get('am.current.comp.class', callback)`.
+
+#### api.getAmCurrentCompPackage(callback)
+
+Alias for `api.get('am.current.comp.package', callback)`.
+
+#### api.getCurrentData(callback)
+
+Alias for `api.get('am.current.data', callback)`.
+
+#### api.getAmCurrentPackage(callback)
+
+Alias for `api.get('am.current.package', callback)`.
+
+#### api.getBuildBoard(callback)
+
+Alias for `api.get('build.board', callback)`.
+
+#### api.getBuildBrand(callback)
+
+Alias for `api.get('build.brand', callback)`.
+
+#### api.getBuildCpuAbi(callback)
+
+Alias for `api.get('build.cpu_abi', callback)`.
+
+#### api.getBuildDevice(callback)
+
+Alias for `api.get('build.device', callback)`.
+
+#### api.getBuildDisplay(callback)
+
+Alias for `api.get('build.display', callback)`.
+
+#### api.getBuildFingerprint(callback)
+
+Alias for `api.get('build.fingerprint', callback)`.
+
+#### api.getBuildHost(callback)
+
+Alias for `api.get('build.host', callback)`.
+
+#### api.getBuildId(callback)
+
+Alias for `api.get('build.id', callback)`.
+
+#### api.getBuildManufacturer(callback)
+
+Alias for `api.get('build.manufacturer', callback)`.
+
+#### api.getBuildModel(callback)
+
+Alias for `api.get('build.model', callback)`.
+
+#### api.getBuildProduct(callback)
+
+Alias for `api.get('build.product', callback)`.
+
+#### api.getBuildTags(callback)
+
+Alias for `api.get('build.tags', callback)`.
+
+#### api.getBuildType(callback)
+
+Alias for `api.get('build.type', callback)`.
+
+#### api.getBuildUser(callback)
+
+Alias for `api.get('build.user', callback)`.
+
+#### api.getBuildVersionCodename(callback)
+
+Alias for `api.get('build.version.codename', callback)`.
+
+#### api.getBuildVersionIncremental(callback)
+
+Alias for `api.get('build.version.incremental', callback)`.
+
+#### api.getBuildVersionRelease(callback)
+
+Alias for `api.get('build.version.release', callback)`.
+
+#### api.getBuildVersionSdk(callback)
+
+Alias for `api.get('build.version.sdk', callback)`.
+
+#### api.getClockMillis(callback)
+
+Alias for `api.get('clock.millis', callback)`.
+
+#### api.getClockRealtime(callback)
+
+Alias for `api.get('clock.realtime', callback)`.
+
+#### api.getClockUptime(callback)
+
+Alias for `api.get('clock.uptime', callback)`.
+
+#### api.getDisplayDensity(callback)
+
+Alias for `api.get('display.density', callback)`.
+
+#### api.getDisplayHeight(callback)
+
+Alias for `api.get('display.height', callback)`. Note that the height may exclude any virtual home button row.
+
+#### api.getDisplayWidth(callback)
+
+Alias for `api.get('display.width', callback)`.
+
+#### api.keyDown(keyCode, callback)
+
+Sends a key down event. Should be coupled with `api.keyUp()`. Note that `api.press()` performs the two events automatically.
+
+* **keyCode** The [key code][android-keycodes]. All monkeys support numeric keycodes, and some support automatic conversion from key names to key codes (e.g. `'home'` to `KEYCODE_HOME`). This will not work for number keys however. The most portable method is to simply use numeric key codes.
+* **callback(err)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+* Returns: The `Api` implementation instance.
+
+#### api.keyUp(keyCode, callback)
+
+Sends a key up event. Should be coupled with `api.keyDown()`. Note that `api.press()` performs the two events automatically.
+
+* **keyCode** See `api.keyDown()`.
+* **callback(err)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+* Returns: The `Api` implementation instance.
+
+#### api.list(callback)
+
+Lists supported variables.
+
+* **callback(err, vars)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+    * **vars** An array of supported variable names, to be used with `api.get()`.
+* Returns: The `Api` implementation instance.
+
+#### api.press(keyCode, callback)
+
+Sends a key press event.
+
+* **keyCode** See `api.keyDown()`.
+* **callback(err)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+* Returns: The `Api` implementation instance.
+
+#### api.quit(callback)
+
+Closes the current monkey session and quits monkey.
+
+* **callback(err)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+* Returns: The `Api` implementation instance.
+
+#### api.sleep(ms, callback)
+
+Sleeps for the given duration. Can be useful for simulating gestures.
+
+* **ms** How many milliseconds to sleep for.
+* **callback(err)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+* Returns: The `Api` implementation instance.
+
+#### api.tap(x, y, callback)
+
+Taps the given coordinates.
+
+* **x** The x coordinate.
+* **y** The y coordinate.
+* **callback(err)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+* Returns: The `Api` implementation instance.
+
+#### api.touchDown(x, y, callback)
+
+Sends a touch down event on the given coordinates.
+
+* **x** The x coordinate.
+* **y** The y coordinate.
+* **callback(err)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+* Returns: The `Api` implementation instance.
+
+#### api.touchMove(x, y, callback)
+
+Sends a touch move event on the given coordinates.
+
+* **x** The x coordinate.
+* **y** The y coordinate.
+* **callback(err)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+* Returns: The `Api` implementation instance.
+
+#### api.touchUp(x, y, callback)
+
+Sends a touch up event on the given coordinates.
+
+* **x** The x coordinate.
+* **y** The y coordinate.
+* **callback(err)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+* Returns: The `Api` implementation instance.
+
+#### api.trackball(x, y, callback)
+
+Sends a trackball event on the given coordinates.
+
+* **x** The x coordinate.
+* **y** The y coordinate.
+* **callback(err)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+* Returns: The `Api` implementation instance.
+
+#### api.type(text, callback)
+
+Types the given text.
+
+* **text** A text `String`. Note that only characters for which [key codes][android-keycodes] exist can be entered. Also note that any IME in use may or may not transform the text.
+* **callback(err)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+* Returns: The `Api` implementation instance.
+
+#### api.wake(callback)
+
+Wakes the device from sleep and allows user input.
+
+* **callback(err)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+* Returns: The `Api` implementation instance.
+
+### Multi
+
+Buffers `Api` commands and delivers them simultaneously for greater control over timing.
+
+Implements all `Api` methods, but without the last `callback` parameter.
+
+#### multi.execute(callback)
+
+Sends all buffered commands.
+
+* **callback(err, values)** Called when monkey has responded to all commands (i.e. just once at the end).
+    * **err** `null` when successful, `Error` otherwise.
+    * **values** An array of all response values, identical to individual `Api` responses.
+
+## More information
+
+* [Monkey][monkey-site]
+    - [Source code][monkey-source]
+    - [Protocol][monkey-proto]
+* [Monkeyrunner][monkeyrunner-site]
+
+## Contributing
+
+See [CONTRIBUTING.md](CONTRIBUTING.md).
+
+## License
+
+See [LICENSE](LICENSE).
+
+Copyright © CyberAgent, Inc. All Rights Reserved.
+
+[nodejs]: <http://nodejs.org/>
+[monkey-site]: <http://developer.android.com/tools/help/monkey.html>
+[monkey-source]: <https://github.com/android/platform_development/blob/master/cmds/monkey/>
+[monkey-proto]: <https://github.com/android/platform_development/blob/master/cmds/monkey/README.NETWORK.txt>
+[monkeyrunner-site]: <http://developer.android.com/tools/help/monkeyrunner_concepts.html>
+[node-net]: <http://nodejs.org/api/net.html>
+[node-stream]: <http://nodejs.org/api/stream.html>
+[android-keycodes]: <http://developer.android.com/reference/android/view/KeyEvent.html>
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/index.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/index.js
new file mode 100644
index 0000000..a81226d
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/index.js
@@ -0,0 +1,15 @@
+(function() {
+  var Path;
+
+  Path = require('path');
+
+  module.exports = (function() {
+    switch (Path.extname(__filename)) {
+      case '.coffee':
+        return require('./src/monkey');
+      default:
+        return require('./lib/monkey');
+    }
+  })();
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey.js
new file mode 100644
index 0000000..9e81c56
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey.js
@@ -0,0 +1,29 @@
+(function() {
+  var Client, Connection, Monkey;
+
+  Client = require('./monkey/client');
+
+  Connection = require('./monkey/connection');
+
+  Monkey = (function() {
+    function Monkey() {}
+
+    Monkey.connect = function(options) {
+      return new Connection().connect(options);
+    };
+
+    Monkey.connectStream = function(stream) {
+      return new Client().connect(stream);
+    };
+
+    return Monkey;
+
+  })();
+
+  Monkey.Connection = Connection;
+
+  Monkey.Client = Client;
+
+  module.exports = Monkey;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/api.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/api.js
new file mode 100644
index 0000000..34fce09
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/api.js
@@ -0,0 +1,276 @@
+(function() {
+  var Api, EventEmitter, _ref,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  EventEmitter = require('events').EventEmitter;
+
+  Api = (function(_super) {
+    __extends(Api, _super);
+
+    function Api() {
+      _ref = Api.__super__.constructor.apply(this, arguments);
+      return _ref;
+    }
+
+    Api.prototype.send = function() {
+      throw new Error("send is not implemented");
+    };
+
+    Api.prototype.keyDown = function(keyCode, callback) {
+      this.send("key down " + keyCode, callback);
+      return this;
+    };
+
+    Api.prototype.keyUp = function(keyCode, callback) {
+      this.send("key up " + keyCode, callback);
+      return this;
+    };
+
+    Api.prototype.touchDown = function(x, y, callback) {
+      this.send("touch down " + x + " " + y, callback);
+      return this;
+    };
+
+    Api.prototype.touchUp = function(x, y, callback) {
+      this.send("touch up " + x + " " + y, callback);
+      return this;
+    };
+
+    Api.prototype.touchMove = function(x, y, callback) {
+      this.send("touch move " + x + " " + y, callback);
+      return this;
+    };
+
+    Api.prototype.trackball = function(dx, dy, callback) {
+      this.send("trackball " + dx + " " + dy, callback);
+      return this;
+    };
+
+    Api.prototype.flipOpen = function(callback) {
+      this.send("flip open", callback);
+      return this;
+    };
+
+    Api.prototype.flipClose = function(callback) {
+      this.send("flip close", callback);
+      return this;
+    };
+
+    Api.prototype.wake = function(callback) {
+      this.send("wake", callback);
+      return this;
+    };
+
+    Api.prototype.tap = function(x, y, callback) {
+      this.send("tap " + x + " " + y, callback);
+      return this;
+    };
+
+    Api.prototype.press = function(keyCode, callback) {
+      this.send("press " + keyCode, callback);
+      return this;
+    };
+
+    Api.prototype.type = function(str, callback) {
+      str = str.replace(/"/g, '\\"');
+      if (str.indexOf(' ') === -1) {
+        this.send("type " + str, callback);
+      } else {
+        this.send("type \"" + str + "\"", callback);
+      }
+      return this;
+    };
+
+    Api.prototype.list = function(callback) {
+      var _this = this;
+      this.send("listvar", function(err, vars) {
+        if (err) {
+          return _this(callback(err));
+        }
+        if (err) {
+          return callback(err);
+        } else {
+          return callback(null, vars.split(/\s+/g));
+        }
+      });
+      return this;
+    };
+
+    Api.prototype.get = function(name, callback) {
+      this.send("getvar " + name, callback);
+      return this;
+    };
+
+    Api.prototype.quit = function(callback) {
+      this.send("quit", callback);
+      return this;
+    };
+
+    Api.prototype.done = function(callback) {
+      this.send("done", callback);
+      return this;
+    };
+
+    Api.prototype.sleep = function(ms, callback) {
+      this.send("sleep " + ms, callback);
+      return this;
+    };
+
+    Api.prototype.getAmCurrentAction = function(callback) {
+      this.get('am.current.action', callback);
+      return this;
+    };
+
+    Api.prototype.getAmCurrentCategories = function(callback) {
+      this.get('am.current.categories', callback);
+      return this;
+    };
+
+    Api.prototype.getAmCurrentCompClass = function(callback) {
+      this.get('am.current.comp.class', callback);
+      return this;
+    };
+
+    Api.prototype.getAmCurrentCompPackage = function(callback) {
+      this.get('am.current.comp.package', callback);
+      return this;
+    };
+
+    Api.prototype.getAmCurrentData = function(callback) {
+      this.get('am.current.data', callback);
+      return this;
+    };
+
+    Api.prototype.getAmCurrentPackage = function(callback) {
+      this.get('am.current.package', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildBoard = function(callback) {
+      this.get('build.board', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildBrand = function(callback) {
+      this.get('build.brand', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildCpuAbi = function(callback) {
+      this.get('build.cpu_abi', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildDevice = function(callback) {
+      this.get('build.device', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildDisplay = function(callback) {
+      this.get('build.display', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildFingerprint = function(callback) {
+      this.get('build.fingerprint', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildHost = function(callback) {
+      this.get('build.host', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildId = function(callback) {
+      this.get('build.id', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildManufacturer = function(callback) {
+      this.get('build.manufacturer', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildModel = function(callback) {
+      this.get('build.model', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildProduct = function(callback) {
+      this.get('build.product', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildTags = function(callback) {
+      this.get('build.tags', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildType = function(callback) {
+      this.get('build.type', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildUser = function(callback) {
+      this.get('build.user', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildVersionCodename = function(callback) {
+      this.get('build.version.codename', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildVersionIncremental = function(callback) {
+      this.get('build.version.incremental', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildVersionRelease = function(callback) {
+      this.get('build.version.release', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildVersionSdk = function(callback) {
+      this.get('build.version.sdk', callback);
+      return this;
+    };
+
+    Api.prototype.getClockMillis = function(callback) {
+      this.get('clock.millis', callback);
+      return this;
+    };
+
+    Api.prototype.getClockRealtime = function(callback) {
+      this.get('clock.realtime', callback);
+      return this;
+    };
+
+    Api.prototype.getClockUptime = function(callback) {
+      this.get('clock.uptime', callback);
+      return this;
+    };
+
+    Api.prototype.getDisplayDensity = function(callback) {
+      this.get('display.density', callback);
+      return this;
+    };
+
+    Api.prototype.getDisplayHeight = function(callback) {
+      this.get('display.height', callback);
+      return this;
+    };
+
+    Api.prototype.getDisplayWidth = function(callback) {
+      this.get('display.width', callback);
+      return this;
+    };
+
+    return Api;
+
+  })(EventEmitter);
+
+  module.exports = Api;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/client.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/client.js
new file mode 100644
index 0000000..b6e1278
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/client.js
@@ -0,0 +1,98 @@
+(function() {
+  var Api, Client, Command, Multi, Parser, Queue, Reply,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Api = require('./api');
+
+  Command = require('./command');
+
+  Reply = require('./reply');
+
+  Queue = require('./queue');
+
+  Multi = require('./multi');
+
+  Parser = require('./parser');
+
+  Client = (function(_super) {
+    __extends(Client, _super);
+
+    function Client() {
+      this.commandQueue = new Queue;
+      this.parser = new Parser;
+      this.stream = null;
+    }
+
+    Client.prototype._hook = function() {
+      var _this = this;
+      this.stream.on('data', function(data) {
+        return _this.parser.parse(data);
+      });
+      this.stream.on('error', function(err) {
+        return _this.emit('error', err);
+      });
+      this.stream.on('end', function() {
+        return _this.emit('end');
+      });
+      this.stream.on('finish', function() {
+        return _this.emit('finish');
+      });
+      this.parser.on('reply', function(reply) {
+        return _this._consume(reply);
+      });
+      this.parser.on('error', function(err) {
+        return _this.emit('error', err);
+      });
+    };
+
+    Client.prototype._consume = function(reply) {
+      var command;
+      if (command = this.commandQueue.dequeue()) {
+        if (reply.isError()) {
+          command.callback(reply.toError(), null, command.command);
+        } else {
+          command.callback(null, reply.value, command.command);
+        }
+      } else {
+        throw new Error("Command queue depleted, but replies still coming in");
+      }
+    };
+
+    Client.prototype.connect = function(stream) {
+      this.stream = stream;
+      this._hook();
+      return this;
+    };
+
+    Client.prototype.end = function() {
+      this.stream.end();
+      return this;
+    };
+
+    Client.prototype.send = function(commands, callback) {
+      var command, _i, _len;
+      if (Array.isArray(commands)) {
+        for (_i = 0, _len = commands.length; _i < _len; _i++) {
+          command = commands[_i];
+          this.commandQueue.enqueue(new Command(command, callback));
+        }
+        this.stream.write("" + (commands.join('\n')) + "\n");
+      } else {
+        this.commandQueue.enqueue(new Command(commands, callback));
+        this.stream.write("" + commands + "\n");
+      }
+      return this;
+    };
+
+    Client.prototype.multi = function() {
+      return new Multi(this);
+    };
+
+    return Client;
+
+  })(Api);
+
+  module.exports = Client;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/command.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/command.js
new file mode 100644
index 0000000..41062e0
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/command.js
@@ -0,0 +1,17 @@
+(function() {
+  var Command;
+
+  Command = (function() {
+    function Command(command, callback) {
+      this.command = command;
+      this.callback = callback;
+      this.next = null;
+    }
+
+    return Command;
+
+  })();
+
+  module.exports = Command;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/connection.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/connection.js
new file mode 100644
index 0000000..b52ee14
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/connection.js
@@ -0,0 +1,42 @@
+(function() {
+  var Client, Connection, Net, _ref,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Net = require('net');
+
+  Client = require('./client');
+
+  Connection = (function(_super) {
+    __extends(Connection, _super);
+
+    function Connection() {
+      _ref = Connection.__super__.constructor.apply(this, arguments);
+      return _ref;
+    }
+
+    Connection.prototype.connect = function(options) {
+      var stream;
+      stream = Net.connect(options);
+      stream.setNoDelay(true);
+      return Connection.__super__.connect.call(this, stream);
+    };
+
+    Connection.prototype._hook = function() {
+      var _this = this;
+      this.stream.on('connect', function() {
+        return _this.emit('connect');
+      });
+      this.stream.on('close', function(hadError) {
+        return _this.emit('close', hadError);
+      });
+      return Connection.__super__._hook.call(this);
+    };
+
+    return Connection;
+
+  })(Client);
+
+  module.exports = Connection;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/multi.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/multi.js
new file mode 100644
index 0000000..80e8214
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/multi.js
@@ -0,0 +1,85 @@
+(function() {
+  var Api, Command, Multi,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Api = require('./api');
+
+  Command = require('./command');
+
+  Multi = (function(_super) {
+    __extends(Multi, _super);
+
+    function Multi(monkey) {
+      var _this = this;
+      this.monkey = monkey;
+      this.commands = [];
+      this.replies = [];
+      this.errors = [];
+      this.counter = 0;
+      this.sent = false;
+      this.callback = null;
+      this.collector = function(err, result, cmd) {
+        if (err) {
+          _this.errors.push("" + cmd + ": " + err.message);
+        }
+        _this.replies.push(result);
+        _this.counter -= 1;
+        return _this._maybeFinish();
+      };
+    }
+
+    Multi.prototype._maybeFinish = function() {
+      var _this = this;
+      if (this.counter === 0) {
+        if (this.errors.length) {
+          setImmediate(function() {
+            return _this.callback(new Error(_this.errors.join(', ')));
+          });
+        } else {
+          setImmediate(function() {
+            return _this.callback(null, _this.replies);
+          });
+        }
+      }
+    };
+
+    Multi.prototype._forbidReuse = function() {
+      if (this.sent) {
+        throw new Error("Reuse not supported");
+      }
+    };
+
+    Multi.prototype.send = function(command) {
+      this._forbidReuse();
+      this.commands.push(new Command(command, this.collector));
+    };
+
+    Multi.prototype.execute = function(callback) {
+      var command, parts, _i, _len, _ref;
+      this._forbidReuse();
+      this.counter = this.commands.length;
+      this.sent = true;
+      this.callback = callback;
+      if (this.counter === 0) {
+        return;
+      }
+      parts = [];
+      _ref = this.commands;
+      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+        command = _ref[_i];
+        this.monkey.commandQueue.enqueue(command);
+        parts.push(command.command);
+      }
+      parts.push('');
+      this.commands = [];
+      this.monkey.stream.write(parts.join('\n'));
+    };
+
+    return Multi;
+
+  })(Api);
+
+  module.exports = Multi;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/parser.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/parser.js
new file mode 100644
index 0000000..75dcb9f
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/parser.js
@@ -0,0 +1,66 @@
+(function() {
+  var EventEmitter, Parser, Reply,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  EventEmitter = require('events').EventEmitter;
+
+  Reply = require('./reply');
+
+  Parser = (function(_super) {
+    __extends(Parser, _super);
+
+    function Parser(options) {
+      this.column = 0;
+      this.buffer = new Buffer('');
+    }
+
+    Parser.prototype.parse = function(chunk) {
+      this.buffer = Buffer.concat([this.buffer, chunk]);
+      while (this.column < this.buffer.length) {
+        if (this.buffer[this.column] === 0x0a) {
+          this._parseLine(this.buffer.slice(0, this.column));
+          this.buffer = this.buffer.slice(this.column + 1);
+          this.column = 0;
+        }
+        this.column += 1;
+      }
+      if (this.buffer.length) {
+        this.emit('wait');
+      } else {
+        this.emit('drain');
+      }
+    };
+
+    Parser.prototype._parseLine = function(line) {
+      switch (line[0]) {
+        case 0x4f:
+          if (line.length === 2) {
+            this.emit('reply', new Reply(Reply.OK, null));
+          } else {
+            this.emit('reply', new Reply(Reply.OK, line.toString('ascii', 3)));
+          }
+          break;
+        case 0x45:
+          if (line.length === 5) {
+            this.emit('reply', new Reply(Reply.ERROR, null));
+          } else {
+            this.emit('reply', new Reply(Reply.ERROR, line.toString('ascii', 6)));
+          }
+          break;
+        default:
+          this._complain(line);
+      }
+    };
+
+    Parser.prototype._complain = function(line) {
+      this.emit('error', new SyntaxError("Unparseable line '" + line + "'"));
+    };
+
+    return Parser;
+
+  })(EventEmitter);
+
+  module.exports = Parser;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/queue.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/queue.js
new file mode 100644
index 0000000..19d6615
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/queue.js
@@ -0,0 +1,38 @@
+(function() {
+  var Queue;
+
+  Queue = (function() {
+    function Queue() {
+      this.head = null;
+      this.tail = null;
+    }
+
+    Queue.prototype.enqueue = function(item) {
+      if (this.tail) {
+        this.tail.next = item;
+      } else {
+        this.head = item;
+      }
+      this.tail = item;
+    };
+
+    Queue.prototype.dequeue = function() {
+      var item;
+      item = this.head;
+      if (item) {
+        if (item === this.tail) {
+          this.tail = null;
+        }
+        this.head = item.next;
+        item.next = null;
+      }
+      return item;
+    };
+
+    return Queue;
+
+  })();
+
+  module.exports = Queue;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/reply.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/reply.js
new file mode 100644
index 0000000..349c920
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/reply.js
@@ -0,0 +1,31 @@
+(function() {
+  var Reply;
+
+  Reply = (function() {
+    Reply.ERROR = 'ERROR';
+
+    Reply.OK = 'OK';
+
+    function Reply(type, value) {
+      this.type = type;
+      this.value = value;
+    }
+
+    Reply.prototype.isError = function() {
+      return this.type === Reply.ERROR;
+    };
+
+    Reply.prototype.toError = function() {
+      if (!this.isError()) {
+        throw new Error('toError() cannot be called for non-errors');
+      }
+      return new Error(this.value);
+    };
+
+    return Reply;
+
+  })();
+
+  module.exports = Reply;
+
+}).call(this);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/node_modules/async/LICENSE b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/node_modules/async/LICENSE
new file mode 100644
index 0000000..b7f9d50
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/node_modules/async/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2010 Caolan McMahon
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/node_modules/async/README.md b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/node_modules/async/README.md
new file mode 100644
index 0000000..951f76e
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/node_modules/async/README.md
@@ -0,0 +1,1425 @@
+# Async.js
+
+Async is a utility module which provides straight-forward, powerful functions
+for working with asynchronous JavaScript. Although originally designed for
+use with [node.js](http://nodejs.org), it can also be used directly in the
+browser. Also supports [component](https://github.com/component/component).
+
+Async provides around 20 functions that include the usual 'functional'
+suspects (map, reduce, filter, each…) as well as some common patterns
+for asynchronous control flow (parallel, series, waterfall…). All these
+functions assume you follow the node.js convention of providing a single
+callback as the last argument of your async function.
+
+
+## Quick Examples
+
+```javascript
+async.map(['file1','file2','file3'], fs.stat, function(err, results){
+    // results is now an array of stats for each file
+});
+
+async.filter(['file1','file2','file3'], fs.exists, function(results){
+    // results now equals an array of the existing files
+});
+
+async.parallel([
+    function(){ ... },
+    function(){ ... }
+], callback);
+
+async.series([
+    function(){ ... },
+    function(){ ... }
+]);
+```
+
+There are many more functions available so take a look at the docs below for a
+full list. This module aims to be comprehensive, so if you feel anything is
+missing please create a GitHub issue for it.
+
+## Common Pitfalls
+
+### Binding a context to an iterator
+
+This section is really about bind, not about async. If you are wondering how to
+make async execute your iterators in a given context, or are confused as to why
+a method of another library isn't working as an iterator, study this example:
+
+```js
+// Here is a simple object with an (unnecessarily roundabout) squaring method
+var AsyncSquaringLibrary = {
+  squareExponent: 2,
+  square: function(number, callback){ 
+    var result = Math.pow(number, this.squareExponent);
+    setTimeout(function(){
+      callback(null, result);
+    }, 200);
+  }
+};
+
+async.map([1, 2, 3], AsyncSquaringLibrary.square, function(err, result){
+  // result is [NaN, NaN, NaN]
+  // This fails because the `this.squareExponent` expression in the square
+  // function is not evaluated in the context of AsyncSquaringLibrary, and is
+  // therefore undefined.
+});
+
+async.map([1, 2, 3], AsyncSquaringLibrary.square.bind(AsyncSquaringLibrary), function(err, result){
+  // result is [1, 4, 9]
+  // With the help of bind we can attach a context to the iterator before
+  // passing it to async. Now the square function will be executed in its 
+  // 'home' AsyncSquaringLibrary context and the value of `this.squareExponent`
+  // will be as expected.
+});
+```
+
+## Download
+
+The source is available for download from
+[GitHub](http://github.com/caolan/async).
+Alternatively, you can install using Node Package Manager (npm):
+
+    npm install async
+
+__Development:__ [async.js](https://github.com/caolan/async/raw/master/lib/async.js) - 29.6kb Uncompressed
+
+## In the Browser
+
+So far it's been tested in IE6, IE7, IE8, FF3.6 and Chrome 5. Usage:
+
+```html
+<script type="text/javascript" src="async.js"></script>
+<script type="text/javascript">
+
+    async.map(data, asyncProcess, function(err, results){
+        alert(results);
+    });
+
+</script>
+```
+
+## Documentation
+
+### Collections
+
+* [each](#each)
+* [eachSeries](#eachSeries)
+* [eachLimit](#eachLimit)
+* [map](#map)
+* [mapSeries](#mapSeries)
+* [mapLimit](#mapLimit)
+* [filter](#filter)
+* [filterSeries](#filterSeries)
+* [reject](#reject)
+* [rejectSeries](#rejectSeries)
+* [reduce](#reduce)
+* [reduceRight](#reduceRight)
+* [detect](#detect)
+* [detectSeries](#detectSeries)
+* [sortBy](#sortBy)
+* [some](#some)
+* [every](#every)
+* [concat](#concat)
+* [concatSeries](#concatSeries)
+
+### Control Flow
+
+* [series](#series)
+* [parallel](#parallel)
+* [parallelLimit](#parallellimittasks-limit-callback)
+* [whilst](#whilst)
+* [doWhilst](#doWhilst)
+* [until](#until)
+* [doUntil](#doUntil)
+* [forever](#forever)
+* [waterfall](#waterfall)
+* [compose](#compose)
+* [applyEach](#applyEach)
+* [applyEachSeries](#applyEachSeries)
+* [queue](#queue)
+* [cargo](#cargo)
+* [auto](#auto)
+* [iterator](#iterator)
+* [apply](#apply)
+* [nextTick](#nextTick)
+* [times](#times)
+* [timesSeries](#timesSeries)
+
+### Utils
+
+* [memoize](#memoize)
+* [unmemoize](#unmemoize)
+* [log](#log)
+* [dir](#dir)
+* [noConflict](#noConflict)
+
+
+## Collections
+
+<a name="forEach" />
+<a name="each" />
+### each(arr, iterator, callback)
+
+Applies an iterator function to each item in an array, in parallel.
+The iterator is called with an item from the list and a callback for when it
+has finished. If the iterator passes an error to this callback, the main
+callback for the each function is immediately called with the error.
+
+Note, that since this function applies the iterator to each item in parallel
+there is no guarantee that the iterator functions will complete in order.
+
+__Arguments__
+
+* arr - An array to iterate over.
+* iterator(item, callback) - A function to apply to each item in the array.
+  The iterator is passed a callback(err) which must be called once it has 
+  completed. If no error has occured, the callback should be run without 
+  arguments or with an explicit null argument.
+* callback(err) - A callback which is called after all the iterator functions
+  have finished, or an error has occurred.
+
+__Example__
+
+```js
+// assuming openFiles is an array of file names and saveFile is a function
+// to save the modified contents of that file:
+
+async.each(openFiles, saveFile, function(err){
+    // if any of the saves produced an error, err would equal that error
+});
+```
+
+---------------------------------------
+
+<a name="forEachSeries" />
+<a name="eachSeries" />
+### eachSeries(arr, iterator, callback)
+
+The same as each only the iterator is applied to each item in the array in
+series. The next iterator is only called once the current one has completed
+processing. This means the iterator functions will complete in order.
+
+
+---------------------------------------
+
+<a name="forEachLimit" />
+<a name="eachLimit" />
+### eachLimit(arr, limit, iterator, callback)
+
+The same as each only no more than "limit" iterators will be simultaneously 
+running at any time.
+
+Note that the items are not processed in batches, so there is no guarantee that
+ the first "limit" iterator functions will complete before any others are 
+started.
+
+__Arguments__
+
+* arr - An array to iterate over.
+* limit - The maximum number of iterators to run at any time.
+* iterator(item, callback) - A function to apply to each item in the array.
+  The iterator is passed a callback(err) which must be called once it has 
+  completed. If no error has occured, the callback should be run without 
+  arguments or with an explicit null argument.
+* callback(err) - A callback which is called after all the iterator functions
+  have finished, or an error has occurred.
+
+__Example__
+
+```js
+// Assume documents is an array of JSON objects and requestApi is a
+// function that interacts with a rate-limited REST api.
+
+async.eachLimit(documents, 20, requestApi, function(err){
+    // if any of the saves produced an error, err would equal that error
+});
+```
+
+---------------------------------------
+
+<a name="map" />
+### map(arr, iterator, callback)
+
+Produces a new array of values by mapping each value in the given array through
+the iterator function. The iterator is called with an item from the array and a
+callback for when it has finished processing. The callback takes 2 arguments, 
+an error and the transformed item from the array. If the iterator passes an
+error to this callback, the main callback for the map function is immediately
+called with the error.
+
+Note, that since this function applies the iterator to each item in parallel
+there is no guarantee that the iterator functions will complete in order, however
+the results array will be in the same order as the original array.
+
+__Arguments__
+
+* arr - An array to iterate over.
+* iterator(item, callback) - A function to apply to each item in the array.
+  The iterator is passed a callback(err, transformed) which must be called once 
+  it has completed with an error (which can be null) and a transformed item.
+* callback(err, results) - A callback which is called after all the iterator
+  functions have finished, or an error has occurred. Results is an array of the
+  transformed items from the original array.
+
+__Example__
+
+```js
+async.map(['file1','file2','file3'], fs.stat, function(err, results){
+    // results is now an array of stats for each file
+});
+```
+
+---------------------------------------
+
+<a name="mapSeries" />
+### mapSeries(arr, iterator, callback)
+
+The same as map only the iterator is applied to each item in the array in
+series. The next iterator is only called once the current one has completed
+processing. The results array will be in the same order as the original.
+
+
+---------------------------------------
+
+<a name="mapLimit" />
+### mapLimit(arr, limit, iterator, callback)
+
+The same as map only no more than "limit" iterators will be simultaneously 
+running at any time.
+
+Note that the items are not processed in batches, so there is no guarantee that
+ the first "limit" iterator functions will complete before any others are 
+started.
+
+__Arguments__
+
+* arr - An array to iterate over.
+* limit - The maximum number of iterators to run at any time.
+* iterator(item, callback) - A function to apply to each item in the array.
+  The iterator is passed a callback(err, transformed) which must be called once 
+  it has completed with an error (which can be null) and a transformed item.
+* callback(err, results) - A callback which is called after all the iterator
+  functions have finished, or an error has occurred. Results is an array of the
+  transformed items from the original array.
+
+__Example__
+
+```js
+async.mapLimit(['file1','file2','file3'], 1, fs.stat, function(err, results){
+    // results is now an array of stats for each file
+});
+```
+
+---------------------------------------
+
+<a name="filter" />
+### filter(arr, iterator, callback)
+
+__Alias:__ select
+
+Returns a new array of all the values which pass an async truth test.
+_The callback for each iterator call only accepts a single argument of true or
+false, it does not accept an error argument first!_ This is in-line with the
+way node libraries work with truth tests like fs.exists. This operation is
+performed in parallel, but the results array will be in the same order as the
+original.
+
+__Arguments__
+
+* arr - An array to iterate over.
+* iterator(item, callback) - A truth test to apply to each item in the array.
+  The iterator is passed a callback(truthValue) which must be called with a 
+  boolean argument once it has completed.
+* callback(results) - A callback which is called after all the iterator
+  functions have finished.
+
+__Example__
+
+```js
+async.filter(['file1','file2','file3'], fs.exists, function(results){
+    // results now equals an array of the existing files
+});
+```
+
+---------------------------------------
+
+<a name="filterSeries" />
+### filterSeries(arr, iterator, callback)
+
+__alias:__ selectSeries
+
+The same as filter only the iterator is applied to each item in the array in
+series. The next iterator is only called once the current one has completed
+processing. The results array will be in the same order as the original.
+
+---------------------------------------
+
+<a name="reject" />
+### reject(arr, iterator, callback)
+
+The opposite of filter. Removes values that pass an async truth test.
+
+---------------------------------------
+
+<a name="rejectSeries" />
+### rejectSeries(arr, iterator, callback)
+
+The same as reject, only the iterator is applied to each item in the array
+in series.
+
+
+---------------------------------------
+
+<a name="reduce" />
+### reduce(arr, memo, iterator, callback)
+
+__aliases:__ inject, foldl
+
+Reduces a list of values into a single value using an async iterator to return
+each successive step. Memo is the initial state of the reduction. This
+function only operates in series. For performance reasons, it may make sense to
+split a call to this function into a parallel map, then use the normal
+Array.prototype.reduce on the results. This function is for situations where
+each step in the reduction needs to be async, if you can get the data before
+reducing it then it's probably a good idea to do so.
+
+__Arguments__
+
+* arr - An array to iterate over.
+* memo - The initial state of the reduction.
+* iterator(memo, item, callback) - A function applied to each item in the
+  array to produce the next step in the reduction. The iterator is passed a
+  callback(err, reduction) which accepts an optional error as its first 
+  argument, and the state of the reduction as the second. If an error is 
+  passed to the callback, the reduction is stopped and the main callback is 
+  immediately called with the error.
+* callback(err, result) - A callback which is called after all the iterator
+  functions have finished. Result is the reduced value.
+
+__Example__
+
+```js
+async.reduce([1,2,3], 0, function(memo, item, callback){
+    // pointless async:
+    process.nextTick(function(){
+        callback(null, memo + item)
+    });
+}, function(err, result){
+    // result is now equal to the last value of memo, which is 6
+});
+```
+
+---------------------------------------
+
+<a name="reduceRight" />
+### reduceRight(arr, memo, iterator, callback)
+
+__Alias:__ foldr
+
+Same as reduce, only operates on the items in the array in reverse order.
+
+
+---------------------------------------
+
+<a name="detect" />
+### detect(arr, iterator, callback)
+
+Returns the first value in a list that passes an async truth test. The
+iterator is applied in parallel, meaning the first iterator to return true will
+fire the detect callback with that result. That means the result might not be
+the first item in the original array (in terms of order) that passes the test.
+
+If order within the original array is important then look at detectSeries.
+
+__Arguments__
+
+* arr - An array to iterate over.
+* iterator(item, callback) - A truth test to apply to each item in the array.
+  The iterator is passed a callback(truthValue) which must be called with a 
+  boolean argument once it has completed.
+* callback(result) - A callback which is called as soon as any iterator returns
+  true, or after all the iterator functions have finished. Result will be
+  the first item in the array that passes the truth test (iterator) or the
+  value undefined if none passed.
+
+__Example__
+
+```js
+async.detect(['file1','file2','file3'], fs.exists, function(result){
+    // result now equals the first file in the list that exists
+});
+```
+
+---------------------------------------
+
+<a name="detectSeries" />
+### detectSeries(arr, iterator, callback)
+
+The same as detect, only the iterator is applied to each item in the array
+in series. This means the result is always the first in the original array (in
+terms of array order) that passes the truth test.
+
+
+---------------------------------------
+
+<a name="sortBy" />
+### sortBy(arr, iterator, callback)
+
+Sorts a list by the results of running each value through an async iterator.
+
+__Arguments__
+
+* arr - An array to iterate over.
+* iterator(item, callback) - A function to apply to each item in the array.
+  The iterator is passed a callback(err, sortValue) which must be called once it
+  has completed with an error (which can be null) and a value to use as the sort
+  criteria.
+* callback(err, results) - A callback which is called after all the iterator
+  functions have finished, or an error has occurred. Results is the items from
+  the original array sorted by the values returned by the iterator calls.
+
+__Example__
+
+```js
+async.sortBy(['file1','file2','file3'], function(file, callback){
+    fs.stat(file, function(err, stats){
+        callback(err, stats.mtime);
+    });
+}, function(err, results){
+    // results is now the original array of files sorted by
+    // modified date
+});
+```
+
+---------------------------------------
+
+<a name="some" />
+### some(arr, iterator, callback)
+
+__Alias:__ any
+
+Returns true if at least one element in the array satisfies an async test.
+_The callback for each iterator call only accepts a single argument of true or
+false, it does not accept an error argument first!_ This is in-line with the
+way node libraries work with truth tests like fs.exists. Once any iterator
+call returns true, the main callback is immediately called.
+
+__Arguments__
+
+* arr - An array to iterate over.
+* iterator(item, callback) - A truth test to apply to each item in the array.
+  The iterator is passed a callback(truthValue) which must be called with a 
+  boolean argument once it has completed.
+* callback(result) - A callback which is called as soon as any iterator returns
+  true, or after all the iterator functions have finished. Result will be
+  either true or false depending on the values of the async tests.
+
+__Example__
+
+```js
+async.some(['file1','file2','file3'], fs.exists, function(result){
+    // if result is true then at least one of the files exists
+});
+```
+
+---------------------------------------
+
+<a name="every" />
+### every(arr, iterator, callback)
+
+__Alias:__ all
+
+Returns true if every element in the array satisfies an async test.
+_The callback for each iterator call only accepts a single argument of true or
+false, it does not accept an error argument first!_ This is in-line with the
+way node libraries work with truth tests like fs.exists.
+
+__Arguments__
+
+* arr - An array to iterate over.
+* iterator(item, callback) - A truth test to apply to each item in the array.
+  The iterator is passed a callback(truthValue) which must be called with a 
+  boolean argument once it has completed.
+* callback(result) - A callback which is called after all the iterator
+  functions have finished. Result will be either true or false depending on
+  the values of the async tests.
+
+__Example__
+
+```js
+async.every(['file1','file2','file3'], fs.exists, function(result){
+    // if result is true then every file exists
+});
+```
+
+---------------------------------------
+
+<a name="concat" />
+### concat(arr, iterator, callback)
+
+Applies an iterator to each item in a list, concatenating the results. Returns the
+concatenated list. The iterators are called in parallel, and the results are
+concatenated as they return. There is no guarantee that the results array will
+be returned in the original order of the arguments passed to the iterator function.
+
+__Arguments__
+
+* arr - An array to iterate over
+* iterator(item, callback) - A function to apply to each item in the array.
+  The iterator is passed a callback(err, results) which must be called once it 
+  has completed with an error (which can be null) and an array of results.
+* callback(err, results) - A callback which is called after all the iterator
+  functions have finished, or an error has occurred. Results is an array containing
+  the concatenated results of the iterator function.
+
+__Example__
+
+```js
+async.concat(['dir1','dir2','dir3'], fs.readdir, function(err, files){
+    // files is now a list of filenames that exist in the 3 directories
+});
+```
+
+---------------------------------------
+
+<a name="concatSeries" />
+### concatSeries(arr, iterator, callback)
+
+Same as async.concat, but executes in series instead of parallel.
+
+
+## Control Flow
+
+<a name="series" />
+### series(tasks, [callback])
+
+Run an array of functions in series, each one running once the previous
+function has completed. If any functions in the series pass an error to its
+callback, no more functions are run and the callback for the series is
+immediately called with the value of the error. Once the tasks have completed,
+the results are passed to the final callback as an array.
+
+It is also possible to use an object instead of an array. Each property will be
+run as a function and the results will be passed to the final callback as an object
+instead of an array. This can be a more readable way of handling results from
+async.series.
+
+
+__Arguments__
+
+* tasks - An array or object containing functions to run, each function is passed
+  a callback(err, result) it must call on completion with an error (which can
+  be null) and an optional result value.
+* callback(err, results) - An optional callback to run once all the functions
+  have completed. This function gets a results array (or object) containing all 
+  the result arguments passed to the task callbacks.
+
+__Example__
+
+```js
+async.series([
+    function(callback){
+        // do some stuff ...
+        callback(null, 'one');
+    },
+    function(callback){
+        // do some more stuff ...
+        callback(null, 'two');
+    }
+],
+// optional callback
+function(err, results){
+    // results is now equal to ['one', 'two']
+});
+
+
+// an example using an object instead of an array
+async.series({
+    one: function(callback){
+        setTimeout(function(){
+            callback(null, 1);
+        }, 200);
+    },
+    two: function(callback){
+        setTimeout(function(){
+            callback(null, 2);
+        }, 100);
+    }
+},
+function(err, results) {
+    // results is now equal to: {one: 1, two: 2}
+});
+```
+
+---------------------------------------
+
+<a name="parallel" />
+### parallel(tasks, [callback])
+
+Run an array of functions in parallel, without waiting until the previous
+function has completed. If any of the functions pass an error to its
+callback, the main callback is immediately called with the value of the error.
+Once the tasks have completed, the results are passed to the final callback as an
+array.
+
+It is also possible to use an object instead of an array. Each property will be
+run as a function and the results will be passed to the final callback as an object
+instead of an array. This can be a more readable way of handling results from
+async.parallel.
+
+
+__Arguments__
+
+* tasks - An array or object containing functions to run, each function is passed 
+  a callback(err, result) it must call on completion with an error (which can
+  be null) and an optional result value.
+* callback(err, results) - An optional callback to run once all the functions
+  have completed. This function gets a results array (or object) containing all 
+  the result arguments passed to the task callbacks.
+
+__Example__
+
+```js
+async.parallel([
+    function(callback){
+        setTimeout(function(){
+            callback(null, 'one');
+        }, 200);
+    },
+    function(callback){
+        setTimeout(function(){
+            callback(null, 'two');
+        }, 100);
+    }
+],
+// optional callback
+function(err, results){
+    // the results array will equal ['one','two'] even though
+    // the second function had a shorter timeout.
+});
+
+
+// an example using an object instead of an array
+async.parallel({
+    one: function(callback){
+        setTimeout(function(){
+            callback(null, 1);
+        }, 200);
+    },
+    two: function(callback){
+        setTimeout(function(){
+            callback(null, 2);
+        }, 100);
+    }
+},
+function(err, results) {
+    // results is now equals to: {one: 1, two: 2}
+});
+```
+
+---------------------------------------
+
+<a name="parallel" />
+### parallelLimit(tasks, limit, [callback])
+
+The same as parallel only the tasks are executed in parallel with a maximum of "limit" 
+tasks executing at any time.
+
+Note that the tasks are not executed in batches, so there is no guarantee that 
+the first "limit" tasks will complete before any others are started.
+
+__Arguments__
+
+* tasks - An array or object containing functions to run, each function is passed 
+  a callback(err, result) it must call on completion with an error (which can
+  be null) and an optional result value.
+* limit - The maximum number of tasks to run at any time.
+* callback(err, results) - An optional callback to run once all the functions
+  have completed. This function gets a results array (or object) containing all 
+  the result arguments passed to the task callbacks.
+
+---------------------------------------
+
+<a name="whilst" />
+### whilst(test, fn, callback)
+
+Repeatedly call fn, while test returns true. Calls the callback when stopped,
+or an error occurs.
+
+__Arguments__
+
+* test() - synchronous truth test to perform before each execution of fn.
+* fn(callback) - A function to call each time the test passes. The function is
+  passed a callback(err) which must be called once it has completed with an 
+  optional error argument.
+* callback(err) - A callback which is called after the test fails and repeated
+  execution of fn has stopped.
+
+__Example__
+
+```js
+var count = 0;
+
+async.whilst(
+    function () { return count < 5; },
+    function (callback) {
+        count++;
+        setTimeout(callback, 1000);
+    },
+    function (err) {
+        // 5 seconds have passed
+    }
+);
+```
+
+---------------------------------------
+
+<a name="doWhilst" />
+### doWhilst(fn, test, callback)
+
+The post check version of whilst. To reflect the difference in the order of operations `test` and `fn` arguments are switched. `doWhilst` is to `whilst` as `do while` is to `while` in plain JavaScript.
+
+---------------------------------------
+
+<a name="until" />
+### until(test, fn, callback)
+
+Repeatedly call fn, until test returns true. Calls the callback when stopped,
+or an error occurs.
+
+The inverse of async.whilst.
+
+---------------------------------------
+
+<a name="doUntil" />
+### doUntil(fn, test, callback)
+
+Like doWhilst except the test is inverted. Note the argument ordering differs from `until`.
+
+---------------------------------------
+
+<a name="forever" />
+### forever(fn, callback)
+
+Calls the asynchronous function 'fn' repeatedly, in series, indefinitely.
+If an error is passed to fn's callback then 'callback' is called with the
+error, otherwise it will never be called.
+
+---------------------------------------
+
+<a name="waterfall" />
+### waterfall(tasks, [callback])
+
+Runs an array of functions in series, each passing their results to the next in
+the array. However, if any of the functions pass an error to the callback, the
+next function is not executed and the main callback is immediately called with
+the error.
+
+__Arguments__
+
+* tasks - An array of functions to run, each function is passed a 
+  callback(err, result1, result2, ...) it must call on completion. The first
+  argument is an error (which can be null) and any further arguments will be 
+  passed as arguments in order to the next task.
+* callback(err, [results]) - An optional callback to run once all the functions
+  have completed. This will be passed the results of the last task's callback.
+
+
+
+__Example__
+
+```js
+async.waterfall([
+    function(callback){
+        callback(null, 'one', 'two');
+    },
+    function(arg1, arg2, callback){
+        callback(null, 'three');
+    },
+    function(arg1, callback){
+        // arg1 now equals 'three'
+        callback(null, 'done');
+    }
+], function (err, result) {
+   // result now equals 'done'    
+});
+```
+
+---------------------------------------
+<a name="compose" />
+### compose(fn1, fn2...)
+
+Creates a function which is a composition of the passed asynchronous
+functions. Each function consumes the return value of the function that
+follows. Composing functions f(), g() and h() would produce the result of
+f(g(h())), only this version uses callbacks to obtain the return values.
+
+Each function is executed with the `this` binding of the composed function.
+
+__Arguments__
+
+* functions... - the asynchronous functions to compose
+
+
+__Example__
+
+```js
+function add1(n, callback) {
+    setTimeout(function () {
+        callback(null, n + 1);
+    }, 10);
+}
+
+function mul3(n, callback) {
+    setTimeout(function () {
+        callback(null, n * 3);
+    }, 10);
+}
+
+var add1mul3 = async.compose(mul3, add1);
+
+add1mul3(4, function (err, result) {
+   // result now equals 15
+});
+```
+
+---------------------------------------
+<a name="applyEach" />
+### applyEach(fns, args..., callback)
+
+Applies the provided arguments to each function in the array, calling the
+callback after all functions have completed. If you only provide the first
+argument then it will return a function which lets you pass in the
+arguments as if it were a single function call.
+
+__Arguments__
+
+* fns - the asynchronous functions to all call with the same arguments
+* args... - any number of separate arguments to pass to the function
+* callback - the final argument should be the callback, called when all
+  functions have completed processing
+
+
+__Example__
+
+```js
+async.applyEach([enableSearch, updateSchema], 'bucket', callback);
+
+// partial application example:
+async.each(
+    buckets,
+    async.applyEach([enableSearch, updateSchema]),
+    callback
+);
+```
+
+---------------------------------------
+
+<a name="applyEachSeries" />
+### applyEachSeries(arr, iterator, callback)
+
+The same as applyEach only the functions are applied in series.
+
+---------------------------------------
+
+<a name="queue" />
+### queue(worker, concurrency)
+
+Creates a queue object with the specified concurrency. Tasks added to the
+queue will be processed in parallel (up to the concurrency limit). If all
+workers are in progress, the task is queued until one is available. Once
+a worker has completed a task, the task's callback is called.
+
+__Arguments__
+
+* worker(task, callback) - An asynchronous function for processing a queued
+  task, which must call its callback(err) argument when finished, with an 
+  optional error as an argument.
+* concurrency - An integer for determining how many worker functions should be
+  run in parallel.
+
+__Queue objects__
+
+The queue object returned by this function has the following properties and
+methods:
+
+* length() - a function returning the number of items waiting to be processed.
+* concurrency - an integer for determining how many worker functions should be
+  run in parallel. This property can be changed after a queue is created to
+  alter the concurrency on-the-fly.
+* push(task, [callback]) - add a new task to the queue, the callback is called
+  once the worker has finished processing the task.
+  instead of a single task, an array of tasks can be submitted. the respective callback is used for every task in the list.
+* unshift(task, [callback]) - add a new task to the front of the queue.
+* saturated - a callback that is called when the queue length hits the concurrency and further tasks will be queued
+* empty - a callback that is called when the last item from the queue is given to a worker
+* drain - a callback that is called when the last item from the queue has returned from the worker
+
+__Example__
+
+```js
+// create a queue object with concurrency 2
+
+var q = async.queue(function (task, callback) {
+    console.log('hello ' + task.name);
+    callback();
+}, 2);
+
+
+// assign a callback
+q.drain = function() {
+    console.log('all items have been processed');
+}
+
+// add some items to the queue
+
+q.push({name: 'foo'}, function (err) {
+    console.log('finished processing foo');
+});
+q.push({name: 'bar'}, function (err) {
+    console.log('finished processing bar');
+});
+
+// add some items to the queue (batch-wise)
+
+q.push([{name: 'baz'},{name: 'bay'},{name: 'bax'}], function (err) {
+    console.log('finished processing bar');
+});
+
+// add some items to the front of the queue
+
+q.unshift({name: 'bar'}, function (err) {
+    console.log('finished processing bar');
+});
+```
+
+---------------------------------------
+
+<a name="cargo" />
+### cargo(worker, [payload])
+
+Creates a cargo object with the specified payload. Tasks added to the
+cargo will be processed altogether (up to the payload limit). If the
+worker is in progress, the task is queued until it is available. Once
+the worker has completed some tasks, each callback of those tasks is called.
+
+__Arguments__
+
+* worker(tasks, callback) - An asynchronous function for processing an array of
+  queued tasks, which must call its callback(err) argument when finished, with 
+  an optional error as an argument.
+* payload - An optional integer for determining how many tasks should be
+  processed per round; if omitted, the default is unlimited.
+
+__Cargo objects__
+
+The cargo object returned by this function has the following properties and
+methods:
+
+* length() - a function returning the number of items waiting to be processed.
+* payload - an integer for determining how many tasks should be
+  process per round. This property can be changed after a cargo is created to
+  alter the payload on-the-fly.
+* push(task, [callback]) - add a new task to the queue, the callback is called
+  once the worker has finished processing the task.
+  instead of a single task, an array of tasks can be submitted. the respective callback is used for every task in the list.
+* saturated - a callback that is called when the queue length hits the concurrency and further tasks will be queued
+* empty - a callback that is called when the last item from the queue is given to a worker
+* drain - a callback that is called when the last item from the queue has returned from the worker
+
+__Example__
+
+```js
+// create a cargo object with payload 2
+
+var cargo = async.cargo(function (tasks, callback) {
+    for(var i=0; i<tasks.length; i++){
+      console.log('hello ' + tasks[i].name);
+    }
+    callback();
+}, 2);
+
+
+// add some items
+
+cargo.push({name: 'foo'}, function (err) {
+    console.log('finished processing foo');
+});
+cargo.push({name: 'bar'}, function (err) {
+    console.log('finished processing bar');
+});
+cargo.push({name: 'baz'}, function (err) {
+    console.log('finished processing baz');
+});
+```
+
+---------------------------------------
+
+<a name="auto" />
+### auto(tasks, [callback])
+
+Determines the best order for running functions based on their requirements.
+Each function can optionally depend on other functions being completed first,
+and each function is run as soon as its requirements are satisfied. If any of
+the functions pass an error to their callback, that function will not complete
+(so any other functions depending on it will not run) and the main callback
+will be called immediately with the error. Functions also receive an object
+containing the results of functions which have completed so far.
+
+Note, all functions are called with a results object as a second argument, 
+so it is unsafe to pass functions in the tasks object which cannot handle the
+extra argument. For example, this snippet of code:
+
+```js
+async.auto({
+  readData: async.apply(fs.readFile, 'data.txt', 'utf-8')
+}, callback);
+```
+
+will have the effect of calling readFile with the results object as the last
+argument, which will fail:
+
+```js
+fs.readFile('data.txt', 'utf-8', cb, {});
+```
+
+Instead, wrap the call to readFile in a function which does not forward the 
+results object:
+
+```js
+async.auto({
+  readData: function(cb, results){
+    fs.readFile('data.txt', 'utf-8', cb);
+  }
+}, callback);
+```
+
+__Arguments__
+
+* tasks - An object literal containing named functions or an array of
+  requirements, with the function itself the last item in the array. The key
+  used for each function or array is used when specifying requirements. The 
+  function receives two arguments: (1) a callback(err, result) which must be 
+  called when finished, passing an error (which can be null) and the result of 
+  the function's execution, and (2) a results object, containing the results of
+  the previously executed functions.
+* callback(err, results) - An optional callback which is called when all the
+  tasks have been completed. The callback will receive an error as an argument
+  if any tasks pass an error to their callback. Results will always be passed
+	but if an error occurred, no other tasks will be performed, and the results
+	object will only contain partial results.
+  
+
+__Example__
+
+```js
+async.auto({
+    get_data: function(callback){
+        // async code to get some data
+    },
+    make_folder: function(callback){
+        // async code to create a directory to store a file in
+        // this is run at the same time as getting the data
+    },
+    write_file: ['get_data', 'make_folder', function(callback){
+        // once there is some data and the directory exists,
+        // write the data to a file in the directory
+        callback(null, filename);
+    }],
+    email_link: ['write_file', function(callback, results){
+        // once the file is written let's email a link to it...
+        // results.write_file contains the filename returned by write_file.
+    }]
+});
+```
+
+This is a fairly trivial example, but to do this using the basic parallel and
+series functions would look like this:
+
+```js
+async.parallel([
+    function(callback){
+        // async code to get some data
+    },
+    function(callback){
+        // async code to create a directory to store a file in
+        // this is run at the same time as getting the data
+    }
+],
+function(err, results){
+    async.series([
+        function(callback){
+            // once there is some data and the directory exists,
+            // write the data to a file in the directory
+        },
+        function(callback){
+            // once the file is written let's email a link to it...
+        }
+    ]);
+});
+```
+
+For a complicated series of async tasks using the auto function makes adding
+new tasks much easier and makes the code more readable.
+
+
+---------------------------------------
+
+<a name="iterator" />
+### iterator(tasks)
+
+Creates an iterator function which calls the next function in the array,
+returning a continuation to call the next one after that. It's also possible to
+'peek' the next iterator by doing iterator.next().
+
+This function is used internally by the async module but can be useful when
+you want to manually control the flow of functions in series.
+
+__Arguments__
+
+* tasks - An array of functions to run.
+
+__Example__
+
+```js
+var iterator = async.iterator([
+    function(){ sys.p('one'); },
+    function(){ sys.p('two'); },
+    function(){ sys.p('three'); }
+]);
+
+node> var iterator2 = iterator();
+'one'
+node> var iterator3 = iterator2();
+'two'
+node> iterator3();
+'three'
+node> var nextfn = iterator2.next();
+node> nextfn();
+'three'
+```
+
+---------------------------------------
+
+<a name="apply" />
+### apply(function, arguments..)
+
+Creates a continuation function with some arguments already applied, a useful
+shorthand when combined with other control flow functions. Any arguments
+passed to the returned function are added to the arguments originally passed
+to apply.
+
+__Arguments__
+
+* function - The function you want to eventually apply all arguments to.
+* arguments... - Any number of arguments to automatically apply when the
+  continuation is called.
+
+__Example__
+
+```js
+// using apply
+
+async.parallel([
+    async.apply(fs.writeFile, 'testfile1', 'test1'),
+    async.apply(fs.writeFile, 'testfile2', 'test2'),
+]);
+
+
+// the same process without using apply
+
+async.parallel([
+    function(callback){
+        fs.writeFile('testfile1', 'test1', callback);
+    },
+    function(callback){
+        fs.writeFile('testfile2', 'test2', callback);
+    }
+]);
+```
+
+It's possible to pass any number of additional arguments when calling the
+continuation:
+
+```js
+node> var fn = async.apply(sys.puts, 'one');
+node> fn('two', 'three');
+one
+two
+three
+```
+
+---------------------------------------
+
+<a name="nextTick" />
+### nextTick(callback)
+
+Calls the callback on a later loop around the event loop. In node.js this just
+calls process.nextTick, in the browser it falls back to setImmediate(callback)
+if available, otherwise setTimeout(callback, 0), which means other higher priority
+events may precede the execution of the callback.
+
+This is used internally for browser-compatibility purposes.
+
+__Arguments__
+
+* callback - The function to call on a later loop around the event loop.
+
+__Example__
+
+```js
+var call_order = [];
+async.nextTick(function(){
+    call_order.push('two');
+    // call_order now equals ['one','two']
+});
+call_order.push('one')
+```
+
+<a name="times" />
+### times(n, callback)
+
+Calls the callback n times and accumulates results in the same manner
+you would use with async.map.
+
+__Arguments__
+
+* n - The number of times to run the function.
+* callback - The function to call n times.
+
+__Example__
+
+```js
+// Pretend this is some complicated async factory
+var createUser = function(id, callback) {
+  callback(null, {
+    id: 'user' + id
+  })
+}
+// generate 5 users
+async.times(5, function(n, next){
+    createUser(n, function(err, user) {
+      next(err, user)
+    })
+}, function(err, users) {
+  // we should now have 5 users
+});
+```
+
+<a name="timesSeries" />
+### timesSeries(n, callback)
+
+The same as times only the iterator is applied to each item in the array in
+series. The next iterator is only called once the current one has completed
+processing. The results array will be in the same order as the original.
+
+
+## Utils
+
+<a name="memoize" />
+### memoize(fn, [hasher])
+
+Caches the results of an async function. When creating a hash to store function
+results against, the callback is omitted from the hash and an optional hash
+function can be used.
+
+The cache of results is exposed as the `memo` property of the function returned
+by `memoize`.
+
+__Arguments__
+
+* fn - the function you to proxy and cache results from.
+* hasher - an optional function for generating a custom hash for storing
+  results, it has all the arguments applied to it apart from the callback, and
+  must be synchronous.
+
+__Example__
+
+```js
+var slow_fn = function (name, callback) {
+    // do something
+    callback(null, result);
+};
+var fn = async.memoize(slow_fn);
+
+// fn can now be used as if it were slow_fn
+fn('some name', function () {
+    // callback
+});
+```
+
+<a name="unmemoize" />
+### unmemoize(fn)
+
+Undoes a memoized function, reverting it to the original, unmemoized
+form. Comes handy in tests.
+
+__Arguments__
+
+* fn - the memoized function
+
+<a name="log" />
+### log(function, arguments)
+
+Logs the result of an async function to the console. Only works in node.js or
+in browsers that support console.log and console.error (such as FF and Chrome).
+If multiple arguments are returned from the async function, console.log is
+called on each argument in order.
+
+__Arguments__
+
+* function - The function you want to eventually apply all arguments to.
+* arguments... - Any number of arguments to apply to the function.
+
+__Example__
+
+```js
+var hello = function(name, callback){
+    setTimeout(function(){
+        callback(null, 'hello ' + name);
+    }, 1000);
+};
+```
+```js
+node> async.log(hello, 'world');
+'hello world'
+```
+
+---------------------------------------
+
+<a name="dir" />
+### dir(function, arguments)
+
+Logs the result of an async function to the console using console.dir to
+display the properties of the resulting object. Only works in node.js or
+in browsers that support console.dir and console.error (such as FF and Chrome).
+If multiple arguments are returned from the async function, console.dir is
+called on each argument in order.
+
+__Arguments__
+
+* function - The function you want to eventually apply all arguments to.
+* arguments... - Any number of arguments to apply to the function.
+
+__Example__
+
+```js
+var hello = function(name, callback){
+    setTimeout(function(){
+        callback(null, {hello: name});
+    }, 1000);
+};
+```
+```js
+node> async.dir(hello, 'world');
+{hello: 'world'}
+```
+
+---------------------------------------
+
+<a name="noConflict" />
+### noConflict()
+
+Changes the value of async back to its original value, returning a reference to the
+async object.
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/node_modules/async/component.json b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/node_modules/async/component.json
new file mode 100644
index 0000000..bbb0115
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/node_modules/async/component.json
@@ -0,0 +1,11 @@
+{
+  "name": "async",
+  "repo": "caolan/async",
+  "description": "Higher-order functions and common patterns for asynchronous code",
+  "version": "0.1.23",
+  "keywords": [],
+  "dependencies": {},
+  "development": {},
+  "main": "lib/async.js",
+  "scripts": [ "lib/async.js" ]
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/node_modules/async/lib/async.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/node_modules/async/lib/async.js
new file mode 100755
index 0000000..1eebb15
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/node_modules/async/lib/async.js
@@ -0,0 +1,958 @@
+/*global setImmediate: false, setTimeout: false, console: false */
+(function () {
+
+    var async = {};
+
+    // global on the server, window in the browser
+    var root, previous_async;
+
+    root = this;
+    if (root != null) {
+      previous_async = root.async;
+    }
+
+    async.noConflict = function () {
+        root.async = previous_async;
+        return async;
+    };
+
+    function only_once(fn) {
+        var called = false;
+        return function() {
+            if (called) throw new Error("Callback was already called.");
+            called = true;
+            fn.apply(root, arguments);
+        }
+    }
+
+    //// cross-browser compatiblity functions ////
+
+    var _each = function (arr, iterator) {
+        if (arr.forEach) {
+            return arr.forEach(iterator);
+        }
+        for (var i = 0; i < arr.length; i += 1) {
+            iterator(arr[i], i, arr);
+        }
+    };
+
+    var _map = function (arr, iterator) {
+        if (arr.map) {
+            return arr.map(iterator);
+        }
+        var results = [];
+        _each(arr, function (x, i, a) {
+            results.push(iterator(x, i, a));
+        });
+        return results;
+    };
+
+    var _reduce = function (arr, iterator, memo) {
+        if (arr.reduce) {
+            return arr.reduce(iterator, memo);
+        }
+        _each(arr, function (x, i, a) {
+            memo = iterator(memo, x, i, a);
+        });
+        return memo;
+    };
+
+    var _keys = function (obj) {
+        if (Object.keys) {
+            return Object.keys(obj);
+        }
+        var keys = [];
+        for (var k in obj) {
+            if (obj.hasOwnProperty(k)) {
+                keys.push(k);
+            }
+        }
+        return keys;
+    };
+
+    //// exported async module functions ////
+
+    //// nextTick implementation with browser-compatible fallback ////
+    if (typeof process === 'undefined' || !(process.nextTick)) {
+        if (typeof setImmediate === 'function') {
+            async.nextTick = function (fn) {
+                // not a direct alias for IE10 compatibility
+                setImmediate(fn);
+            };
+            async.setImmediate = async.nextTick;
+        }
+        else {
+            async.nextTick = function (fn) {
+                setTimeout(fn, 0);
+            };
+            async.setImmediate = async.nextTick;
+        }
+    }
+    else {
+        async.nextTick = process.nextTick;
+        if (typeof setImmediate !== 'undefined') {
+            async.setImmediate = function (fn) {
+              // not a direct alias for IE10 compatibility
+              setImmediate(fn);
+            };
+        }
+        else {
+            async.setImmediate = async.nextTick;
+        }
+    }
+
+    async.each = function (arr, iterator, callback) {
+        callback = callback || function () {};
+        if (!arr.length) {
+            return callback();
+        }
+        var completed = 0;
+        _each(arr, function (x) {
+            iterator(x, only_once(function (err) {
+                if (err) {
+                    callback(err);
+                    callback = function () {};
+                }
+                else {
+                    completed += 1;
+                    if (completed >= arr.length) {
+                        callback(null);
+                    }
+                }
+            }));
+        });
+    };
+    async.forEach = async.each;
+
+    async.eachSeries = function (arr, iterator, callback) {
+        callback = callback || function () {};
+        if (!arr.length) {
+            return callback();
+        }
+        var completed = 0;
+        var iterate = function () {
+            iterator(arr[completed], function (err) {
+                if (err) {
+                    callback(err);
+                    callback = function () {};
+                }
+                else {
+                    completed += 1;
+                    if (completed >= arr.length) {
+                        callback(null);
+                    }
+                    else {
+                        iterate();
+                    }
+                }
+            });
+        };
+        iterate();
+    };
+    async.forEachSeries = async.eachSeries;
+
+    async.eachLimit = function (arr, limit, iterator, callback) {
+        var fn = _eachLimit(limit);
+        fn.apply(null, [arr, iterator, callback]);
+    };
+    async.forEachLimit = async.eachLimit;
+
+    var _eachLimit = function (limit) {
+
+        return function (arr, iterator, callback) {
+            callback = callback || function () {};
+            if (!arr.length || limit <= 0) {
+                return callback();
+            }
+            var completed = 0;
+            var started = 0;
+            var running = 0;
+
+            (function replenish () {
+                if (completed >= arr.length) {
+                    return callback();
+                }
+
+                while (running < limit && started < arr.length) {
+                    started += 1;
+                    running += 1;
+                    iterator(arr[started - 1], function (err) {
+                        if (err) {
+                            callback(err);
+                            callback = function () {};
+                        }
+                        else {
+                            completed += 1;
+                            running -= 1;
+                            if (completed >= arr.length) {
+                                callback();
+                            }
+                            else {
+                                replenish();
+                            }
+                        }
+                    });
+                }
+            })();
+        };
+    };
+
+
+    var doParallel = function (fn) {
+        return function () {
+            var args = Array.prototype.slice.call(arguments);
+            return fn.apply(null, [async.each].concat(args));
+        };
+    };
+    var doParallelLimit = function(limit, fn) {
+        return function () {
+            var args = Array.prototype.slice.call(arguments);
+            return fn.apply(null, [_eachLimit(limit)].concat(args));
+        };
+    };
+    var doSeries = function (fn) {
+        return function () {
+            var args = Array.prototype.slice.call(arguments);
+            return fn.apply(null, [async.eachSeries].concat(args));
+        };
+    };
+
+
+    var _asyncMap = function (eachfn, arr, iterator, callback) {
+        var results = [];
+        arr = _map(arr, function (x, i) {
+            return {index: i, value: x};
+        });
+        eachfn(arr, function (x, callback) {
+            iterator(x.value, function (err, v) {
+                results[x.index] = v;
+                callback(err);
+            });
+        }, function (err) {
+            callback(err, results);
+        });
+    };
+    async.map = doParallel(_asyncMap);
+    async.mapSeries = doSeries(_asyncMap);
+    async.mapLimit = function (arr, limit, iterator, callback) {
+        return _mapLimit(limit)(arr, iterator, callback);
+    };
+
+    var _mapLimit = function(limit) {
+        return doParallelLimit(limit, _asyncMap);
+    };
+
+    // reduce only has a series version, as doing reduce in parallel won't
+    // work in many situations.
+    async.reduce = function (arr, memo, iterator, callback) {
+        async.eachSeries(arr, function (x, callback) {
+            iterator(memo, x, function (err, v) {
+                memo = v;
+                callback(err);
+            });
+        }, function (err) {
+            callback(err, memo);
+        });
+    };
+    // inject alias
+    async.inject = async.reduce;
+    // foldl alias
+    async.foldl = async.reduce;
+
+    async.reduceRight = function (arr, memo, iterator, callback) {
+        var reversed = _map(arr, function (x) {
+            return x;
+        }).reverse();
+        async.reduce(reversed, memo, iterator, callback);
+    };
+    // foldr alias
+    async.foldr = async.reduceRight;
+
+    var _filter = function (eachfn, arr, iterator, callback) {
+        var results = [];
+        arr = _map(arr, function (x, i) {
+            return {index: i, value: x};
+        });
+        eachfn(arr, function (x, callback) {
+            iterator(x.value, function (v) {
+                if (v) {
+                    results.push(x);
+                }
+                callback();
+            });
+        }, function (err) {
+            callback(_map(results.sort(function (a, b) {
+                return a.index - b.index;
+            }), function (x) {
+                return x.value;
+            }));
+        });
+    };
+    async.filter = doParallel(_filter);
+    async.filterSeries = doSeries(_filter);
+    // select alias
+    async.select = async.filter;
+    async.selectSeries = async.filterSeries;
+
+    var _reject = function (eachfn, arr, iterator, callback) {
+        var results = [];
+        arr = _map(arr, function (x, i) {
+            return {index: i, value: x};
+        });
+        eachfn(arr, function (x, callback) {
+            iterator(x.value, function (v) {
+                if (!v) {
+                    results.push(x);
+                }
+                callback();
+            });
+        }, function (err) {
+            callback(_map(results.sort(function (a, b) {
+                return a.index - b.index;
+            }), function (x) {
+                return x.value;
+            }));
+        });
+    };
+    async.reject = doParallel(_reject);
+    async.rejectSeries = doSeries(_reject);
+
+    var _detect = function (eachfn, arr, iterator, main_callback) {
+        eachfn(arr, function (x, callback) {
+            iterator(x, function (result) {
+                if (result) {
+                    main_callback(x);
+                    main_callback = function () {};
+                }
+                else {
+                    callback();
+                }
+            });
+        }, function (err) {
+            main_callback();
+        });
+    };
+    async.detect = doParallel(_detect);
+    async.detectSeries = doSeries(_detect);
+
+    async.some = function (arr, iterator, main_callback) {
+        async.each(arr, function (x, callback) {
+            iterator(x, function (v) {
+                if (v) {
+                    main_callback(true);
+                    main_callback = function () {};
+                }
+                callback();
+            });
+        }, function (err) {
+            main_callback(false);
+        });
+    };
+    // any alias
+    async.any = async.some;
+
+    async.every = function (arr, iterator, main_callback) {
+        async.each(arr, function (x, callback) {
+            iterator(x, function (v) {
+                if (!v) {
+                    main_callback(false);
+                    main_callback = function () {};
+                }
+                callback();
+            });
+        }, function (err) {
+            main_callback(true);
+        });
+    };
+    // all alias
+    async.all = async.every;
+
+    async.sortBy = function (arr, iterator, callback) {
+        async.map(arr, function (x, callback) {
+            iterator(x, function (err, criteria) {
+                if (err) {
+                    callback(err);
+                }
+                else {
+                    callback(null, {value: x, criteria: criteria});
+                }
+            });
+        }, function (err, results) {
+            if (err) {
+                return callback(err);
+            }
+            else {
+                var fn = function (left, right) {
+                    var a = left.criteria, b = right.criteria;
+                    return a < b ? -1 : a > b ? 1 : 0;
+                };
+                callback(null, _map(results.sort(fn), function (x) {
+                    return x.value;
+                }));
+            }
+        });
+    };
+
+    async.auto = function (tasks, callback) {
+        callback = callback || function () {};
+        var keys = _keys(tasks);
+        if (!keys.length) {
+            return callback(null);
+        }
+
+        var results = {};
+
+        var listeners = [];
+        var addListener = function (fn) {
+            listeners.unshift(fn);
+        };
+        var removeListener = function (fn) {
+            for (var i = 0; i < listeners.length; i += 1) {
+                if (listeners[i] === fn) {
+                    listeners.splice(i, 1);
+                    return;
+                }
+            }
+        };
+        var taskComplete = function () {
+            _each(listeners.slice(0), function (fn) {
+                fn();
+            });
+        };
+
+        addListener(function () {
+            if (_keys(results).length === keys.length) {
+                callback(null, results);
+                callback = function () {};
+            }
+        });
+
+        _each(keys, function (k) {
+            var task = (tasks[k] instanceof Function) ? [tasks[k]]: tasks[k];
+            var taskCallback = function (err) {
+                var args = Array.prototype.slice.call(arguments, 1);
+                if (args.length <= 1) {
+                    args = args[0];
+                }
+                if (err) {
+                    var safeResults = {};
+                    _each(_keys(results), function(rkey) {
+                        safeResults[rkey] = results[rkey];
+                    });
+                    safeResults[k] = args;
+                    callback(err, safeResults);
+                    // stop subsequent errors hitting callback multiple times
+                    callback = function () {};
+                }
+                else {
+                    results[k] = args;
+                    async.setImmediate(taskComplete);
+                }
+            };
+            var requires = task.slice(0, Math.abs(task.length - 1)) || [];
+            var ready = function () {
+                return _reduce(requires, function (a, x) {
+                    return (a && results.hasOwnProperty(x));
+                }, true) && !results.hasOwnProperty(k);
+            };
+            if (ready()) {
+                task[task.length - 1](taskCallback, results);
+            }
+            else {
+                var listener = function () {
+                    if (ready()) {
+                        removeListener(listener);
+                        task[task.length - 1](taskCallback, results);
+                    }
+                };
+                addListener(listener);
+            }
+        });
+    };
+
+    async.waterfall = function (tasks, callback) {
+        callback = callback || function () {};
+        if (tasks.constructor !== Array) {
+          var err = new Error('First argument to waterfall must be an array of functions');
+          return callback(err);
+        }
+        if (!tasks.length) {
+            return callback();
+        }
+        var wrapIterator = function (iterator) {
+            return function (err) {
+                if (err) {
+                    callback.apply(null, arguments);
+                    callback = function () {};
+                }
+                else {
+                    var args = Array.prototype.slice.call(arguments, 1);
+                    var next = iterator.next();
+                    if (next) {
+                        args.push(wrapIterator(next));
+                    }
+                    else {
+                        args.push(callback);
+                    }
+                    async.setImmediate(function () {
+                        iterator.apply(null, args);
+                    });
+                }
+            };
+        };
+        wrapIterator(async.iterator(tasks))();
+    };
+
+    var _parallel = function(eachfn, tasks, callback) {
+        callback = callback || function () {};
+        if (tasks.constructor === Array) {
+            eachfn.map(tasks, function (fn, callback) {
+                if (fn) {
+                    fn(function (err) {
+                        var args = Array.prototype.slice.call(arguments, 1);
+                        if (args.length <= 1) {
+                            args = args[0];
+                        }
+                        callback.call(null, err, args);
+                    });
+                }
+            }, callback);
+        }
+        else {
+            var results = {};
+            eachfn.each(_keys(tasks), function (k, callback) {
+                tasks[k](function (err) {
+                    var args = Array.prototype.slice.call(arguments, 1);
+                    if (args.length <= 1) {
+                        args = args[0];
+                    }
+                    results[k] = args;
+                    callback(err);
+                });
+            }, function (err) {
+                callback(err, results);
+            });
+        }
+    };
+
+    async.parallel = function (tasks, callback) {
+        _parallel({ map: async.map, each: async.each }, tasks, callback);
+    };
+
+    async.parallelLimit = function(tasks, limit, callback) {
+        _parallel({ map: _mapLimit(limit), each: _eachLimit(limit) }, tasks, callback);
+    };
+
+    async.series = function (tasks, callback) {
+        callback = callback || function () {};
+        if (tasks.constructor === Array) {
+            async.mapSeries(tasks, function (fn, callback) {
+                if (fn) {
+                    fn(function (err) {
+                        var args = Array.prototype.slice.call(arguments, 1);
+                        if (args.length <= 1) {
+                            args = args[0];
+                        }
+                        callback.call(null, err, args);
+                    });
+                }
+            }, callback);
+        }
+        else {
+            var results = {};
+            async.eachSeries(_keys(tasks), function (k, callback) {
+                tasks[k](function (err) {
+                    var args = Array.prototype.slice.call(arguments, 1);
+                    if (args.length <= 1) {
+                        args = args[0];
+                    }
+                    results[k] = args;
+                    callback(err);
+                });
+            }, function (err) {
+                callback(err, results);
+            });
+        }
+    };
+
+    async.iterator = function (tasks) {
+        var makeCallback = function (index) {
+            var fn = function () {
+                if (tasks.length) {
+                    tasks[index].apply(null, arguments);
+                }
+                return fn.next();
+            };
+            fn.next = function () {
+                return (index < tasks.length - 1) ? makeCallback(index + 1): null;
+            };
+            return fn;
+        };
+        return makeCallback(0);
+    };
+
+    async.apply = function (fn) {
+        var args = Array.prototype.slice.call(arguments, 1);
+        return function () {
+            return fn.apply(
+                null, args.concat(Array.prototype.slice.call(arguments))
+            );
+        };
+    };
+
+    var _concat = function (eachfn, arr, fn, callback) {
+        var r = [];
+        eachfn(arr, function (x, cb) {
+            fn(x, function (err, y) {
+                r = r.concat(y || []);
+                cb(err);
+            });
+        }, function (err) {
+            callback(err, r);
+        });
+    };
+    async.concat = doParallel(_concat);
+    async.concatSeries = doSeries(_concat);
+
+    async.whilst = function (test, iterator, callback) {
+        if (test()) {
+            iterator(function (err) {
+                if (err) {
+                    return callback(err);
+                }
+                async.whilst(test, iterator, callback);
+            });
+        }
+        else {
+            callback();
+        }
+    };
+
+    async.doWhilst = function (iterator, test, callback) {
+        iterator(function (err) {
+            if (err) {
+                return callback(err);
+            }
+            if (test()) {
+                async.doWhilst(iterator, test, callback);
+            }
+            else {
+                callback();
+            }
+        });
+    };
+
+    async.until = function (test, iterator, callback) {
+        if (!test()) {
+            iterator(function (err) {
+                if (err) {
+                    return callback(err);
+                }
+                async.until(test, iterator, callback);
+            });
+        }
+        else {
+            callback();
+        }
+    };
+
+    async.doUntil = function (iterator, test, callback) {
+        iterator(function (err) {
+            if (err) {
+                return callback(err);
+            }
+            if (!test()) {
+                async.doUntil(iterator, test, callback);
+            }
+            else {
+                callback();
+            }
+        });
+    };
+
+    async.queue = function (worker, concurrency) {
+        if (concurrency === undefined) {
+            concurrency = 1;
+        }
+        function _insert(q, data, pos, callback) {
+          if(data.constructor !== Array) {
+              data = [data];
+          }
+          _each(data, function(task) {
+              var item = {
+                  data: task,
+                  callback: typeof callback === 'function' ? callback : null
+              };
+
+              if (pos) {
+                q.tasks.unshift(item);
+              } else {
+                q.tasks.push(item);
+              }
+
+              if (q.saturated && q.tasks.length === concurrency) {
+                  q.saturated();
+              }
+              async.setImmediate(q.process);
+          });
+        }
+
+        var workers = 0;
+        var q = {
+            tasks: [],
+            concurrency: concurrency,
+            saturated: null,
+            empty: null,
+            drain: null,
+            push: function (data, callback) {
+              _insert(q, data, false, callback);
+            },
+            unshift: function (data, callback) {
+              _insert(q, data, true, callback);
+            },
+            process: function () {
+                if (workers < q.concurrency && q.tasks.length) {
+                    var task = q.tasks.shift();
+                    if (q.empty && q.tasks.length === 0) {
+                        q.empty();
+                    }
+                    workers += 1;
+                    var next = function () {
+                        workers -= 1;
+                        if (task.callback) {
+                            task.callback.apply(task, arguments);
+                        }
+                        if (q.drain && q.tasks.length + workers === 0) {
+                            q.drain();
+                        }
+                        q.process();
+                    };
+                    var cb = only_once(next);
+                    worker(task.data, cb);
+                }
+            },
+            length: function () {
+                return q.tasks.length;
+            },
+            running: function () {
+                return workers;
+            }
+        };
+        return q;
+    };
+
+    async.cargo = function (worker, payload) {
+        var working     = false,
+            tasks       = [];
+
+        var cargo = {
+            tasks: tasks,
+            payload: payload,
+            saturated: null,
+            empty: null,
+            drain: null,
+            push: function (data, callback) {
+                if(data.constructor !== Array) {
+                    data = [data];
+                }
+                _each(data, function(task) {
+                    tasks.push({
+                        data: task,
+                        callback: typeof callback === 'function' ? callback : null
+                    });
+                    if (cargo.saturated && tasks.length === payload) {
+                        cargo.saturated();
+                    }
+                });
+                async.setImmediate(cargo.process);
+            },
+            process: function process() {
+                if (working) return;
+                if (tasks.length === 0) {
+                    if(cargo.drain) cargo.drain();
+                    return;
+                }
+
+                var ts = typeof payload === 'number'
+                            ? tasks.splice(0, payload)
+                            : tasks.splice(0);
+
+                var ds = _map(ts, function (task) {
+                    return task.data;
+                });
+
+                if(cargo.empty) cargo.empty();
+                working = true;
+                worker(ds, function () {
+                    working = false;
+
+                    var args = arguments;
+                    _each(ts, function (data) {
+                        if (data.callback) {
+                            data.callback.apply(null, args);
+                        }
+                    });
+
+                    process();
+                });
+            },
+            length: function () {
+                return tasks.length;
+            },
+            running: function () {
+                return working;
+            }
+        };
+        return cargo;
+    };
+
+    var _console_fn = function (name) {
+        return function (fn) {
+            var args = Array.prototype.slice.call(arguments, 1);
+            fn.apply(null, args.concat([function (err) {
+                var args = Array.prototype.slice.call(arguments, 1);
+                if (typeof console !== 'undefined') {
+                    if (err) {
+                        if (console.error) {
+                            console.error(err);
+                        }
+                    }
+                    else if (console[name]) {
+                        _each(args, function (x) {
+                            console[name](x);
+                        });
+                    }
+                }
+            }]));
+        };
+    };
+    async.log = _console_fn('log');
+    async.dir = _console_fn('dir');
+    /*async.info = _console_fn('info');
+    async.warn = _console_fn('warn');
+    async.error = _console_fn('error');*/
+
+    async.memoize = function (fn, hasher) {
+        var memo = {};
+        var queues = {};
+        hasher = hasher || function (x) {
+            return x;
+        };
+        var memoized = function () {
+            var args = Array.prototype.slice.call(arguments);
+            var callback = args.pop();
+            var key = hasher.apply(null, args);
+            if (key in memo) {
+                callback.apply(null, memo[key]);
+            }
+            else if (key in queues) {
+                queues[key].push(callback);
+            }
+            else {
+                queues[key] = [callback];
+                fn.apply(null, args.concat([function () {
+                    memo[key] = arguments;
+                    var q = queues[key];
+                    delete queues[key];
+                    for (var i = 0, l = q.length; i < l; i++) {
+                      q[i].apply(null, arguments);
+                    }
+                }]));
+            }
+        };
+        memoized.memo = memo;
+        memoized.unmemoized = fn;
+        return memoized;
+    };
+
+    async.unmemoize = function (fn) {
+      return function () {
+        return (fn.unmemoized || fn).apply(null, arguments);
+      };
+    };
+
+    async.times = function (count, iterator, callback) {
+        var counter = [];
+        for (var i = 0; i < count; i++) {
+            counter.push(i);
+        }
+        return async.map(counter, iterator, callback);
+    };
+
+    async.timesSeries = function (count, iterator, callback) {
+        var counter = [];
+        for (var i = 0; i < count; i++) {
+            counter.push(i);
+        }
+        return async.mapSeries(counter, iterator, callback);
+    };
+
+    async.compose = function (/* functions... */) {
+        var fns = Array.prototype.reverse.call(arguments);
+        return function () {
+            var that = this;
+            var args = Array.prototype.slice.call(arguments);
+            var callback = args.pop();
+            async.reduce(fns, args, function (newargs, fn, cb) {
+                fn.apply(that, newargs.concat([function () {
+                    var err = arguments[0];
+                    var nextargs = Array.prototype.slice.call(arguments, 1);
+                    cb(err, nextargs);
+                }]))
+            },
+            function (err, results) {
+                callback.apply(that, [err].concat(results));
+            });
+        };
+    };
+
+    var _applyEach = function (eachfn, fns /*args...*/) {
+        var go = function () {
+            var that = this;
+            var args = Array.prototype.slice.call(arguments);
+            var callback = args.pop();
+            return eachfn(fns, function (fn, cb) {
+                fn.apply(that, args.concat([cb]));
+            },
+            callback);
+        };
+        if (arguments.length > 2) {
+            var args = Array.prototype.slice.call(arguments, 2);
+            return go.apply(this, args);
+        }
+        else {
+            return go;
+        }
+    };
+    async.applyEach = doParallel(_applyEach);
+    async.applyEachSeries = doSeries(_applyEach);
+
+    async.forever = function (fn, callback) {
+        function next(err) {
+            if (err) {
+                if (callback) {
+                    return callback(err);
+                }
+                throw err;
+            }
+            fn(next);
+        }
+        next();
+    };
+
+    // AMD / RequireJS
+    if (typeof define !== 'undefined' && define.amd) {
+        define([], function () {
+            return async;
+        });
+    }
+    // Node.js
+    else if (typeof module !== 'undefined' && module.exports) {
+        module.exports = async;
+    }
+    // included directly via <script> tag
+    else {
+        root.async = async;
+    }
+
+}());
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/node_modules/async/package.json b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/node_modules/async/package.json
new file mode 100644
index 0000000..5648629
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/node_modules/async/package.json
@@ -0,0 +1,45 @@
+{
+  "name": "async",
+  "description": "Higher-order functions and common patterns for asynchronous code",
+  "main": "./lib/async",
+  "author": {
+    "name": "Caolan McMahon"
+  },
+  "version": "0.2.10",
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/caolan/async.git"
+  },
+  "bugs": {
+    "url": "https://github.com/caolan/async/issues"
+  },
+  "licenses": [
+    {
+      "type": "MIT",
+      "url": "https://github.com/caolan/async/raw/master/LICENSE"
+    }
+  ],
+  "devDependencies": {
+    "nodeunit": ">0.0.0",
+    "uglify-js": "1.2.x",
+    "nodelint": ">0.0.0"
+  },
+  "jam": {
+    "main": "lib/async.js",
+    "include": [
+      "lib/async.js",
+      "README.md",
+      "LICENSE"
+    ]
+  },
+  "scripts": {
+    "test": "nodeunit test/test-async.js"
+  },
+  "readme": "# Async.js\n\nAsync is a utility module which provides straight-forward, powerful functions\nfor working with asynchronous JavaScript. Although originally designed for\nuse with [node.js](http://nodejs.org), it can also be used directly in the\nbrowser. Also supports [component](https://github.com/component/component).\n\nAsync provides around 20 functions that include the usual 'functional'\nsuspects (map, reduce, filter, each…) as well as some common patterns\nfor asynchronous control flow (parallel, series, waterfall…). All these\nfunctions assume you follow the node.js convention of providing a single\ncallback as the last argument of your async function.\n\n\n## Quick Examples\n\n```javascript\nasync.map(['file1','file2','file3'], fs.stat, function(err, results){\n    // results is now an array of stats for each file\n});\n\nasync.filter(['file1','file2','file3'], fs.exists, function(results){\n    // results now equals an array of the existing files\n});\n\nasync.parallel([\n    function(){ ... },\n    function(){ ... }\n], callback);\n\nasync.series([\n    function(){ ... },\n    function(){ ... }\n]);\n```\n\nThere are many more functions available so take a look at the docs below for a\nfull list. This module aims to be comprehensive, so if you feel anything is\nmissing please create a GitHub issue for it.\n\n## Common Pitfalls\n\n### Binding a context to an iterator\n\nThis section is really about bind, not about async. If you are wondering how to\nmake async execute your iterators in a given context, or are confused as to why\na method of another library isn't working as an iterator, study this example:\n\n```js\n// Here is a simple object with an (unnecessarily roundabout) squaring method\nvar AsyncSquaringLibrary = {\n  squareExponent: 2,\n  square: function(number, callback){ \n    var result = Math.pow(number, this.squareExponent);\n    setTimeout(function(){\n      callback(null, result);\n    }, 200);\n  }\n};\n\nasync.map([1, 2, 3], AsyncSquaringLibrary.square, function(err, result){\n  // result is [NaN, NaN, NaN]\n  // This fails because the `this.squareExponent` expression in the square\n  // function is not evaluated in the context of AsyncSquaringLibrary, and is\n  // therefore undefined.\n});\n\nasync.map([1, 2, 3], AsyncSquaringLibrary.square.bind(AsyncSquaringLibrary), function(err, result){\n  // result is [1, 4, 9]\n  // With the help of bind we can attach a context to the iterator before\n  // passing it to async. Now the square function will be executed in its \n  // 'home' AsyncSquaringLibrary context and the value of `this.squareExponent`\n  // will be as expected.\n});\n```\n\n## Download\n\nThe source is available for download from\n[GitHub](http://github.com/caolan/async).\nAlternatively, you can install using Node Package Manager (npm):\n\n    npm install async\n\n__Development:__ [async.js](https://github.com/caolan/async/raw/master/lib/async.js) - 29.6kb Uncompressed\n\n## In the Browser\n\nSo far it's been tested in IE6, IE7, IE8, FF3.6 and Chrome 5. Usage:\n\n```html\n<script type=\"text/javascript\" src=\"async.js\"></script>\n<script type=\"text/javascript\">\n\n    async.map(data, asyncProcess, function(err, results){\n        alert(results);\n    });\n\n</script>\n```\n\n## Documentation\n\n### Collections\n\n* [each](#each)\n* [eachSeries](#eachSeries)\n* [eachLimit](#eachLimit)\n* [map](#map)\n* [mapSeries](#mapSeries)\n* [mapLimit](#mapLimit)\n* [filter](#filter)\n* [filterSeries](#filterSeries)\n* [reject](#reject)\n* [rejectSeries](#rejectSeries)\n* [reduce](#reduce)\n* [reduceRight](#reduceRight)\n* [detect](#detect)\n* [detectSeries](#detectSeries)\n* [sortBy](#sortBy)\n* [some](#some)\n* [every](#every)\n* [concat](#concat)\n* [concatSeries](#concatSeries)\n\n### Control Flow\n\n* [series](#series)\n* [parallel](#parallel)\n* [parallelLimit](#parallellimittasks-limit-callback)\n* [whilst](#whilst)\n* [doWhilst](#doWhilst)\n* [until](#until)\n* [doUntil](#doUntil)\n* [forever](#forever)\n* [waterfall](#waterfall)\n* [compose](#compose)\n* [applyEach](#applyEach)\n* [applyEachSeries](#applyEachSeries)\n* [queue](#queue)\n* [cargo](#cargo)\n* [auto](#auto)\n* [iterator](#iterator)\n* [apply](#apply)\n* [nextTick](#nextTick)\n* [times](#times)\n* [timesSeries](#timesSeries)\n\n### Utils\n\n* [memoize](#memoize)\n* [unmemoize](#unmemoize)\n* [log](#log)\n* [dir](#dir)\n* [noConflict](#noConflict)\n\n\n## Collections\n\n<a name=\"forEach\" />\n<a name=\"each\" />\n### each(arr, iterator, callback)\n\nApplies an iterator function to each item in an array, in parallel.\nThe iterator is called with an item from the list and a callback for when it\nhas finished. If the iterator passes an error to this callback, the main\ncallback for the each function is immediately called with the error.\n\nNote, that since this function applies the iterator to each item in parallel\nthere is no guarantee that the iterator functions will complete in order.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* iterator(item, callback) - A function to apply to each item in the array.\n  The iterator is passed a callback(err) which must be called once it has \n  completed. If no error has occured, the callback should be run without \n  arguments or with an explicit null argument.\n* callback(err) - A callback which is called after all the iterator functions\n  have finished, or an error has occurred.\n\n__Example__\n\n```js\n// assuming openFiles is an array of file names and saveFile is a function\n// to save the modified contents of that file:\n\nasync.each(openFiles, saveFile, function(err){\n    // if any of the saves produced an error, err would equal that error\n});\n```\n\n---------------------------------------\n\n<a name=\"forEachSeries\" />\n<a name=\"eachSeries\" />\n### eachSeries(arr, iterator, callback)\n\nThe same as each only the iterator is applied to each item in the array in\nseries. The next iterator is only called once the current one has completed\nprocessing. This means the iterator functions will complete in order.\n\n\n---------------------------------------\n\n<a name=\"forEachLimit\" />\n<a name=\"eachLimit\" />\n### eachLimit(arr, limit, iterator, callback)\n\nThe same as each only no more than \"limit\" iterators will be simultaneously \nrunning at any time.\n\nNote that the items are not processed in batches, so there is no guarantee that\n the first \"limit\" iterator functions will complete before any others are \nstarted.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* limit - The maximum number of iterators to run at any time.\n* iterator(item, callback) - A function to apply to each item in the array.\n  The iterator is passed a callback(err) which must be called once it has \n  completed. If no error has occured, the callback should be run without \n  arguments or with an explicit null argument.\n* callback(err) - A callback which is called after all the iterator functions\n  have finished, or an error has occurred.\n\n__Example__\n\n```js\n// Assume documents is an array of JSON objects and requestApi is a\n// function that interacts with a rate-limited REST api.\n\nasync.eachLimit(documents, 20, requestApi, function(err){\n    // if any of the saves produced an error, err would equal that error\n});\n```\n\n---------------------------------------\n\n<a name=\"map\" />\n### map(arr, iterator, callback)\n\nProduces a new array of values by mapping each value in the given array through\nthe iterator function. The iterator is called with an item from the array and a\ncallback for when it has finished processing. The callback takes 2 arguments, \nan error and the transformed item from the array. If the iterator passes an\nerror to this callback, the main callback for the map function is immediately\ncalled with the error.\n\nNote, that since this function applies the iterator to each item in parallel\nthere is no guarantee that the iterator functions will complete in order, however\nthe results array will be in the same order as the original array.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* iterator(item, callback) - A function to apply to each item in the array.\n  The iterator is passed a callback(err, transformed) which must be called once \n  it has completed with an error (which can be null) and a transformed item.\n* callback(err, results) - A callback which is called after all the iterator\n  functions have finished, or an error has occurred. Results is an array of the\n  transformed items from the original array.\n\n__Example__\n\n```js\nasync.map(['file1','file2','file3'], fs.stat, function(err, results){\n    // results is now an array of stats for each file\n});\n```\n\n---------------------------------------\n\n<a name=\"mapSeries\" />\n### mapSeries(arr, iterator, callback)\n\nThe same as map only the iterator is applied to each item in the array in\nseries. The next iterator is only called once the current one has completed\nprocessing. The results array will be in the same order as the original.\n\n\n---------------------------------------\n\n<a name=\"mapLimit\" />\n### mapLimit(arr, limit, iterator, callback)\n\nThe same as map only no more than \"limit\" iterators will be simultaneously \nrunning at any time.\n\nNote that the items are not processed in batches, so there is no guarantee that\n the first \"limit\" iterator functions will complete before any others are \nstarted.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* limit - The maximum number of iterators to run at any time.\n* iterator(item, callback) - A function to apply to each item in the array.\n  The iterator is passed a callback(err, transformed) which must be called once \n  it has completed with an error (which can be null) and a transformed item.\n* callback(err, results) - A callback which is called after all the iterator\n  functions have finished, or an error has occurred. Results is an array of the\n  transformed items from the original array.\n\n__Example__\n\n```js\nasync.mapLimit(['file1','file2','file3'], 1, fs.stat, function(err, results){\n    // results is now an array of stats for each file\n});\n```\n\n---------------------------------------\n\n<a name=\"filter\" />\n### filter(arr, iterator, callback)\n\n__Alias:__ select\n\nReturns a new array of all the values which pass an async truth test.\n_The callback for each iterator call only accepts a single argument of true or\nfalse, it does not accept an error argument first!_ This is in-line with the\nway node libraries work with truth tests like fs.exists. This operation is\nperformed in parallel, but the results array will be in the same order as the\noriginal.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* iterator(item, callback) - A truth test to apply to each item in the array.\n  The iterator is passed a callback(truthValue) which must be called with a \n  boolean argument once it has completed.\n* callback(results) - A callback which is called after all the iterator\n  functions have finished.\n\n__Example__\n\n```js\nasync.filter(['file1','file2','file3'], fs.exists, function(results){\n    // results now equals an array of the existing files\n});\n```\n\n---------------------------------------\n\n<a name=\"filterSeries\" />\n### filterSeries(arr, iterator, callback)\n\n__alias:__ selectSeries\n\nThe same as filter only the iterator is applied to each item in the array in\nseries. The next iterator is only called once the current one has completed\nprocessing. The results array will be in the same order as the original.\n\n---------------------------------------\n\n<a name=\"reject\" />\n### reject(arr, iterator, callback)\n\nThe opposite of filter. Removes values that pass an async truth test.\n\n---------------------------------------\n\n<a name=\"rejectSeries\" />\n### rejectSeries(arr, iterator, callback)\n\nThe same as reject, only the iterator is applied to each item in the array\nin series.\n\n\n---------------------------------------\n\n<a name=\"reduce\" />\n### reduce(arr, memo, iterator, callback)\n\n__aliases:__ inject, foldl\n\nReduces a list of values into a single value using an async iterator to return\neach successive step. Memo is the initial state of the reduction. This\nfunction only operates in series. For performance reasons, it may make sense to\nsplit a call to this function into a parallel map, then use the normal\nArray.prototype.reduce on the results. This function is for situations where\neach step in the reduction needs to be async, if you can get the data before\nreducing it then it's probably a good idea to do so.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* memo - The initial state of the reduction.\n* iterator(memo, item, callback) - A function applied to each item in the\n  array to produce the next step in the reduction. The iterator is passed a\n  callback(err, reduction) which accepts an optional error as its first \n  argument, and the state of the reduction as the second. If an error is \n  passed to the callback, the reduction is stopped and the main callback is \n  immediately called with the error.\n* callback(err, result) - A callback which is called after all the iterator\n  functions have finished. Result is the reduced value.\n\n__Example__\n\n```js\nasync.reduce([1,2,3], 0, function(memo, item, callback){\n    // pointless async:\n    process.nextTick(function(){\n        callback(null, memo + item)\n    });\n}, function(err, result){\n    // result is now equal to the last value of memo, which is 6\n});\n```\n\n---------------------------------------\n\n<a name=\"reduceRight\" />\n### reduceRight(arr, memo, iterator, callback)\n\n__Alias:__ foldr\n\nSame as reduce, only operates on the items in the array in reverse order.\n\n\n---------------------------------------\n\n<a name=\"detect\" />\n### detect(arr, iterator, callback)\n\nReturns the first value in a list that passes an async truth test. The\niterator is applied in parallel, meaning the first iterator to return true will\nfire the detect callback with that result. That means the result might not be\nthe first item in the original array (in terms of order) that passes the test.\n\nIf order within the original array is important then look at detectSeries.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* iterator(item, callback) - A truth test to apply to each item in the array.\n  The iterator is passed a callback(truthValue) which must be called with a \n  boolean argument once it has completed.\n* callback(result) - A callback which is called as soon as any iterator returns\n  true, or after all the iterator functions have finished. Result will be\n  the first item in the array that passes the truth test (iterator) or the\n  value undefined if none passed.\n\n__Example__\n\n```js\nasync.detect(['file1','file2','file3'], fs.exists, function(result){\n    // result now equals the first file in the list that exists\n});\n```\n\n---------------------------------------\n\n<a name=\"detectSeries\" />\n### detectSeries(arr, iterator, callback)\n\nThe same as detect, only the iterator is applied to each item in the array\nin series. This means the result is always the first in the original array (in\nterms of array order) that passes the truth test.\n\n\n---------------------------------------\n\n<a name=\"sortBy\" />\n### sortBy(arr, iterator, callback)\n\nSorts a list by the results of running each value through an async iterator.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* iterator(item, callback) - A function to apply to each item in the array.\n  The iterator is passed a callback(err, sortValue) which must be called once it\n  has completed with an error (which can be null) and a value to use as the sort\n  criteria.\n* callback(err, results) - A callback which is called after all the iterator\n  functions have finished, or an error has occurred. Results is the items from\n  the original array sorted by the values returned by the iterator calls.\n\n__Example__\n\n```js\nasync.sortBy(['file1','file2','file3'], function(file, callback){\n    fs.stat(file, function(err, stats){\n        callback(err, stats.mtime);\n    });\n}, function(err, results){\n    // results is now the original array of files sorted by\n    // modified date\n});\n```\n\n---------------------------------------\n\n<a name=\"some\" />\n### some(arr, iterator, callback)\n\n__Alias:__ any\n\nReturns true if at least one element in the array satisfies an async test.\n_The callback for each iterator call only accepts a single argument of true or\nfalse, it does not accept an error argument first!_ This is in-line with the\nway node libraries work with truth tests like fs.exists. Once any iterator\ncall returns true, the main callback is immediately called.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* iterator(item, callback) - A truth test to apply to each item in the array.\n  The iterator is passed a callback(truthValue) which must be called with a \n  boolean argument once it has completed.\n* callback(result) - A callback which is called as soon as any iterator returns\n  true, or after all the iterator functions have finished. Result will be\n  either true or false depending on the values of the async tests.\n\n__Example__\n\n```js\nasync.some(['file1','file2','file3'], fs.exists, function(result){\n    // if result is true then at least one of the files exists\n});\n```\n\n---------------------------------------\n\n<a name=\"every\" />\n### every(arr, iterator, callback)\n\n__Alias:__ all\n\nReturns true if every element in the array satisfies an async test.\n_The callback for each iterator call only accepts a single argument of true or\nfalse, it does not accept an error argument first!_ This is in-line with the\nway node libraries work with truth tests like fs.exists.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* iterator(item, callback) - A truth test to apply to each item in the array.\n  The iterator is passed a callback(truthValue) which must be called with a \n  boolean argument once it has completed.\n* callback(result) - A callback which is called after all the iterator\n  functions have finished. Result will be either true or false depending on\n  the values of the async tests.\n\n__Example__\n\n```js\nasync.every(['file1','file2','file3'], fs.exists, function(result){\n    // if result is true then every file exists\n});\n```\n\n---------------------------------------\n\n<a name=\"concat\" />\n### concat(arr, iterator, callback)\n\nApplies an iterator to each item in a list, concatenating the results. Returns the\nconcatenated list. The iterators are called in parallel, and the results are\nconcatenated as they return. There is no guarantee that the results array will\nbe returned in the original order of the arguments passed to the iterator function.\n\n__Arguments__\n\n* arr - An array to iterate over\n* iterator(item, callback) - A function to apply to each item in the array.\n  The iterator is passed a callback(err, results) which must be called once it \n  has completed with an error (which can be null) and an array of results.\n* callback(err, results) - A callback which is called after all the iterator\n  functions have finished, or an error has occurred. Results is an array containing\n  the concatenated results of the iterator function.\n\n__Example__\n\n```js\nasync.concat(['dir1','dir2','dir3'], fs.readdir, function(err, files){\n    // files is now a list of filenames that exist in the 3 directories\n});\n```\n\n---------------------------------------\n\n<a name=\"concatSeries\" />\n### concatSeries(arr, iterator, callback)\n\nSame as async.concat, but executes in series instead of parallel.\n\n\n## Control Flow\n\n<a name=\"series\" />\n### series(tasks, [callback])\n\nRun an array of functions in series, each one running once the previous\nfunction has completed. If any functions in the series pass an error to its\ncallback, no more functions are run and the callback for the series is\nimmediately called with the value of the error. Once the tasks have completed,\nthe results are passed to the final callback as an array.\n\nIt is also possible to use an object instead of an array. Each property will be\nrun as a function and the results will be passed to the final callback as an object\ninstead of an array. This can be a more readable way of handling results from\nasync.series.\n\n\n__Arguments__\n\n* tasks - An array or object containing functions to run, each function is passed\n  a callback(err, result) it must call on completion with an error (which can\n  be null) and an optional result value.\n* callback(err, results) - An optional callback to run once all the functions\n  have completed. This function gets a results array (or object) containing all \n  the result arguments passed to the task callbacks.\n\n__Example__\n\n```js\nasync.series([\n    function(callback){\n        // do some stuff ...\n        callback(null, 'one');\n    },\n    function(callback){\n        // do some more stuff ...\n        callback(null, 'two');\n    }\n],\n// optional callback\nfunction(err, results){\n    // results is now equal to ['one', 'two']\n});\n\n\n// an example using an object instead of an array\nasync.series({\n    one: function(callback){\n        setTimeout(function(){\n            callback(null, 1);\n        }, 200);\n    },\n    two: function(callback){\n        setTimeout(function(){\n            callback(null, 2);\n        }, 100);\n    }\n},\nfunction(err, results) {\n    // results is now equal to: {one: 1, two: 2}\n});\n```\n\n---------------------------------------\n\n<a name=\"parallel\" />\n### parallel(tasks, [callback])\n\nRun an array of functions in parallel, without waiting until the previous\nfunction has completed. If any of the functions pass an error to its\ncallback, the main callback is immediately called with the value of the error.\nOnce the tasks have completed, the results are passed to the final callback as an\narray.\n\nIt is also possible to use an object instead of an array. Each property will be\nrun as a function and the results will be passed to the final callback as an object\ninstead of an array. This can be a more readable way of handling results from\nasync.parallel.\n\n\n__Arguments__\n\n* tasks - An array or object containing functions to run, each function is passed \n  a callback(err, result) it must call on completion with an error (which can\n  be null) and an optional result value.\n* callback(err, results) - An optional callback to run once all the functions\n  have completed. This function gets a results array (or object) containing all \n  the result arguments passed to the task callbacks.\n\n__Example__\n\n```js\nasync.parallel([\n    function(callback){\n        setTimeout(function(){\n            callback(null, 'one');\n        }, 200);\n    },\n    function(callback){\n        setTimeout(function(){\n            callback(null, 'two');\n        }, 100);\n    }\n],\n// optional callback\nfunction(err, results){\n    // the results array will equal ['one','two'] even though\n    // the second function had a shorter timeout.\n});\n\n\n// an example using an object instead of an array\nasync.parallel({\n    one: function(callback){\n        setTimeout(function(){\n            callback(null, 1);\n        }, 200);\n    },\n    two: function(callback){\n        setTimeout(function(){\n            callback(null, 2);\n        }, 100);\n    }\n},\nfunction(err, results) {\n    // results is now equals to: {one: 1, two: 2}\n});\n```\n\n---------------------------------------\n\n<a name=\"parallel\" />\n### parallelLimit(tasks, limit, [callback])\n\nThe same as parallel only the tasks are executed in parallel with a maximum of \"limit\" \ntasks executing at any time.\n\nNote that the tasks are not executed in batches, so there is no guarantee that \nthe first \"limit\" tasks will complete before any others are started.\n\n__Arguments__\n\n* tasks - An array or object containing functions to run, each function is passed \n  a callback(err, result) it must call on completion with an error (which can\n  be null) and an optional result value.\n* limit - The maximum number of tasks to run at any time.\n* callback(err, results) - An optional callback to run once all the functions\n  have completed. This function gets a results array (or object) containing all \n  the result arguments passed to the task callbacks.\n\n---------------------------------------\n\n<a name=\"whilst\" />\n### whilst(test, fn, callback)\n\nRepeatedly call fn, while test returns true. Calls the callback when stopped,\nor an error occurs.\n\n__Arguments__\n\n* test() - synchronous truth test to perform before each execution of fn.\n* fn(callback) - A function to call each time the test passes. The function is\n  passed a callback(err) which must be called once it has completed with an \n  optional error argument.\n* callback(err) - A callback which is called after the test fails and repeated\n  execution of fn has stopped.\n\n__Example__\n\n```js\nvar count = 0;\n\nasync.whilst(\n    function () { return count < 5; },\n    function (callback) {\n        count++;\n        setTimeout(callback, 1000);\n    },\n    function (err) {\n        // 5 seconds have passed\n    }\n);\n```\n\n---------------------------------------\n\n<a name=\"doWhilst\" />\n### doWhilst(fn, test, callback)\n\nThe post check version of whilst. To reflect the difference in the order of operations `test` and `fn` arguments are switched. `doWhilst` is to `whilst` as `do while` is to `while` in plain JavaScript.\n\n---------------------------------------\n\n<a name=\"until\" />\n### until(test, fn, callback)\n\nRepeatedly call fn, until test returns true. Calls the callback when stopped,\nor an error occurs.\n\nThe inverse of async.whilst.\n\n---------------------------------------\n\n<a name=\"doUntil\" />\n### doUntil(fn, test, callback)\n\nLike doWhilst except the test is inverted. Note the argument ordering differs from `until`.\n\n---------------------------------------\n\n<a name=\"forever\" />\n### forever(fn, callback)\n\nCalls the asynchronous function 'fn' repeatedly, in series, indefinitely.\nIf an error is passed to fn's callback then 'callback' is called with the\nerror, otherwise it will never be called.\n\n---------------------------------------\n\n<a name=\"waterfall\" />\n### waterfall(tasks, [callback])\n\nRuns an array of functions in series, each passing their results to the next in\nthe array. However, if any of the functions pass an error to the callback, the\nnext function is not executed and the main callback is immediately called with\nthe error.\n\n__Arguments__\n\n* tasks - An array of functions to run, each function is passed a \n  callback(err, result1, result2, ...) it must call on completion. The first\n  argument is an error (which can be null) and any further arguments will be \n  passed as arguments in order to the next task.\n* callback(err, [results]) - An optional callback to run once all the functions\n  have completed. This will be passed the results of the last task's callback.\n\n\n\n__Example__\n\n```js\nasync.waterfall([\n    function(callback){\n        callback(null, 'one', 'two');\n    },\n    function(arg1, arg2, callback){\n        callback(null, 'three');\n    },\n    function(arg1, callback){\n        // arg1 now equals 'three'\n        callback(null, 'done');\n    }\n], function (err, result) {\n   // result now equals 'done'    \n});\n```\n\n---------------------------------------\n<a name=\"compose\" />\n### compose(fn1, fn2...)\n\nCreates a function which is a composition of the passed asynchronous\nfunctions. Each function consumes the return value of the function that\nfollows. Composing functions f(), g() and h() would produce the result of\nf(g(h())), only this version uses callbacks to obtain the return values.\n\nEach function is executed with the `this` binding of the composed function.\n\n__Arguments__\n\n* functions... - the asynchronous functions to compose\n\n\n__Example__\n\n```js\nfunction add1(n, callback) {\n    setTimeout(function () {\n        callback(null, n + 1);\n    }, 10);\n}\n\nfunction mul3(n, callback) {\n    setTimeout(function () {\n        callback(null, n * 3);\n    }, 10);\n}\n\nvar add1mul3 = async.compose(mul3, add1);\n\nadd1mul3(4, function (err, result) {\n   // result now equals 15\n});\n```\n\n---------------------------------------\n<a name=\"applyEach\" />\n### applyEach(fns, args..., callback)\n\nApplies the provided arguments to each function in the array, calling the\ncallback after all functions have completed. If you only provide the first\nargument then it will return a function which lets you pass in the\narguments as if it were a single function call.\n\n__Arguments__\n\n* fns - the asynchronous functions to all call with the same arguments\n* args... - any number of separate arguments to pass to the function\n* callback - the final argument should be the callback, called when all\n  functions have completed processing\n\n\n__Example__\n\n```js\nasync.applyEach([enableSearch, updateSchema], 'bucket', callback);\n\n// partial application example:\nasync.each(\n    buckets,\n    async.applyEach([enableSearch, updateSchema]),\n    callback\n);\n```\n\n---------------------------------------\n\n<a name=\"applyEachSeries\" />\n### applyEachSeries(arr, iterator, callback)\n\nThe same as applyEach only the functions are applied in series.\n\n---------------------------------------\n\n<a name=\"queue\" />\n### queue(worker, concurrency)\n\nCreates a queue object with the specified concurrency. Tasks added to the\nqueue will be processed in parallel (up to the concurrency limit). If all\nworkers are in progress, the task is queued until one is available. Once\na worker has completed a task, the task's callback is called.\n\n__Arguments__\n\n* worker(task, callback) - An asynchronous function for processing a queued\n  task, which must call its callback(err) argument when finished, with an \n  optional error as an argument.\n* concurrency - An integer for determining how many worker functions should be\n  run in parallel.\n\n__Queue objects__\n\nThe queue object returned by this function has the following properties and\nmethods:\n\n* length() - a function returning the number of items waiting to be processed.\n* concurrency - an integer for determining how many worker functions should be\n  run in parallel. This property can be changed after a queue is created to\n  alter the concurrency on-the-fly.\n* push(task, [callback]) - add a new task to the queue, the callback is called\n  once the worker has finished processing the task.\n  instead of a single task, an array of tasks can be submitted. the respective callback is used for every task in the list.\n* unshift(task, [callback]) - add a new task to the front of the queue.\n* saturated - a callback that is called when the queue length hits the concurrency and further tasks will be queued\n* empty - a callback that is called when the last item from the queue is given to a worker\n* drain - a callback that is called when the last item from the queue has returned from the worker\n\n__Example__\n\n```js\n// create a queue object with concurrency 2\n\nvar q = async.queue(function (task, callback) {\n    console.log('hello ' + task.name);\n    callback();\n}, 2);\n\n\n// assign a callback\nq.drain = function() {\n    console.log('all items have been processed');\n}\n\n// add some items to the queue\n\nq.push({name: 'foo'}, function (err) {\n    console.log('finished processing foo');\n});\nq.push({name: 'bar'}, function (err) {\n    console.log('finished processing bar');\n});\n\n// add some items to the queue (batch-wise)\n\nq.push([{name: 'baz'},{name: 'bay'},{name: 'bax'}], function (err) {\n    console.log('finished processing bar');\n});\n\n// add some items to the front of the queue\n\nq.unshift({name: 'bar'}, function (err) {\n    console.log('finished processing bar');\n});\n```\n\n---------------------------------------\n\n<a name=\"cargo\" />\n### cargo(worker, [payload])\n\nCreates a cargo object with the specified payload. Tasks added to the\ncargo will be processed altogether (up to the payload limit). If the\nworker is in progress, the task is queued until it is available. Once\nthe worker has completed some tasks, each callback of those tasks is called.\n\n__Arguments__\n\n* worker(tasks, callback) - An asynchronous function for processing an array of\n  queued tasks, which must call its callback(err) argument when finished, with \n  an optional error as an argument.\n* payload - An optional integer for determining how many tasks should be\n  processed per round; if omitted, the default is unlimited.\n\n__Cargo objects__\n\nThe cargo object returned by this function has the following properties and\nmethods:\n\n* length() - a function returning the number of items waiting to be processed.\n* payload - an integer for determining how many tasks should be\n  process per round. This property can be changed after a cargo is created to\n  alter the payload on-the-fly.\n* push(task, [callback]) - add a new task to the queue, the callback is called\n  once the worker has finished processing the task.\n  instead of a single task, an array of tasks can be submitted. the respective callback is used for every task in the list.\n* saturated - a callback that is called when the queue length hits the concurrency and further tasks will be queued\n* empty - a callback that is called when the last item from the queue is given to a worker\n* drain - a callback that is called when the last item from the queue has returned from the worker\n\n__Example__\n\n```js\n// create a cargo object with payload 2\n\nvar cargo = async.cargo(function (tasks, callback) {\n    for(var i=0; i<tasks.length; i++){\n      console.log('hello ' + tasks[i].name);\n    }\n    callback();\n}, 2);\n\n\n// add some items\n\ncargo.push({name: 'foo'}, function (err) {\n    console.log('finished processing foo');\n});\ncargo.push({name: 'bar'}, function (err) {\n    console.log('finished processing bar');\n});\ncargo.push({name: 'baz'}, function (err) {\n    console.log('finished processing baz');\n});\n```\n\n---------------------------------------\n\n<a name=\"auto\" />\n### auto(tasks, [callback])\n\nDetermines the best order for running functions based on their requirements.\nEach function can optionally depend on other functions being completed first,\nand each function is run as soon as its requirements are satisfied. If any of\nthe functions pass an error to their callback, that function will not complete\n(so any other functions depending on it will not run) and the main callback\nwill be called immediately with the error. Functions also receive an object\ncontaining the results of functions which have completed so far.\n\nNote, all functions are called with a results object as a second argument, \nso it is unsafe to pass functions in the tasks object which cannot handle the\nextra argument. For example, this snippet of code:\n\n```js\nasync.auto({\n  readData: async.apply(fs.readFile, 'data.txt', 'utf-8')\n}, callback);\n```\n\nwill have the effect of calling readFile with the results object as the last\nargument, which will fail:\n\n```js\nfs.readFile('data.txt', 'utf-8', cb, {});\n```\n\nInstead, wrap the call to readFile in a function which does not forward the \nresults object:\n\n```js\nasync.auto({\n  readData: function(cb, results){\n    fs.readFile('data.txt', 'utf-8', cb);\n  }\n}, callback);\n```\n\n__Arguments__\n\n* tasks - An object literal containing named functions or an array of\n  requirements, with the function itself the last item in the array. The key\n  used for each function or array is used when specifying requirements. The \n  function receives two arguments: (1) a callback(err, result) which must be \n  called when finished, passing an error (which can be null) and the result of \n  the function's execution, and (2) a results object, containing the results of\n  the previously executed functions.\n* callback(err, results) - An optional callback which is called when all the\n  tasks have been completed. The callback will receive an error as an argument\n  if any tasks pass an error to their callback. Results will always be passed\n\tbut if an error occurred, no other tasks will be performed, and the results\n\tobject will only contain partial results.\n  \n\n__Example__\n\n```js\nasync.auto({\n    get_data: function(callback){\n        // async code to get some data\n    },\n    make_folder: function(callback){\n        // async code to create a directory to store a file in\n        // this is run at the same time as getting the data\n    },\n    write_file: ['get_data', 'make_folder', function(callback){\n        // once there is some data and the directory exists,\n        // write the data to a file in the directory\n        callback(null, filename);\n    }],\n    email_link: ['write_file', function(callback, results){\n        // once the file is written let's email a link to it...\n        // results.write_file contains the filename returned by write_file.\n    }]\n});\n```\n\nThis is a fairly trivial example, but to do this using the basic parallel and\nseries functions would look like this:\n\n```js\nasync.parallel([\n    function(callback){\n        // async code to get some data\n    },\n    function(callback){\n        // async code to create a directory to store a file in\n        // this is run at the same time as getting the data\n    }\n],\nfunction(err, results){\n    async.series([\n        function(callback){\n            // once there is some data and the directory exists,\n            // write the data to a file in the directory\n        },\n        function(callback){\n            // once the file is written let's email a link to it...\n        }\n    ]);\n});\n```\n\nFor a complicated series of async tasks using the auto function makes adding\nnew tasks much easier and makes the code more readable.\n\n\n---------------------------------------\n\n<a name=\"iterator\" />\n### iterator(tasks)\n\nCreates an iterator function which calls the next function in the array,\nreturning a continuation to call the next one after that. It's also possible to\n'peek' the next iterator by doing iterator.next().\n\nThis function is used internally by the async module but can be useful when\nyou want to manually control the flow of functions in series.\n\n__Arguments__\n\n* tasks - An array of functions to run.\n\n__Example__\n\n```js\nvar iterator = async.iterator([\n    function(){ sys.p('one'); },\n    function(){ sys.p('two'); },\n    function(){ sys.p('three'); }\n]);\n\nnode> var iterator2 = iterator();\n'one'\nnode> var iterator3 = iterator2();\n'two'\nnode> iterator3();\n'three'\nnode> var nextfn = iterator2.next();\nnode> nextfn();\n'three'\n```\n\n---------------------------------------\n\n<a name=\"apply\" />\n### apply(function, arguments..)\n\nCreates a continuation function with some arguments already applied, a useful\nshorthand when combined with other control flow functions. Any arguments\npassed to the returned function are added to the arguments originally passed\nto apply.\n\n__Arguments__\n\n* function - The function you want to eventually apply all arguments to.\n* arguments... - Any number of arguments to automatically apply when the\n  continuation is called.\n\n__Example__\n\n```js\n// using apply\n\nasync.parallel([\n    async.apply(fs.writeFile, 'testfile1', 'test1'),\n    async.apply(fs.writeFile, 'testfile2', 'test2'),\n]);\n\n\n// the same process without using apply\n\nasync.parallel([\n    function(callback){\n        fs.writeFile('testfile1', 'test1', callback);\n    },\n    function(callback){\n        fs.writeFile('testfile2', 'test2', callback);\n    }\n]);\n```\n\nIt's possible to pass any number of additional arguments when calling the\ncontinuation:\n\n```js\nnode> var fn = async.apply(sys.puts, 'one');\nnode> fn('two', 'three');\none\ntwo\nthree\n```\n\n---------------------------------------\n\n<a name=\"nextTick\" />\n### nextTick(callback)\n\nCalls the callback on a later loop around the event loop. In node.js this just\ncalls process.nextTick, in the browser it falls back to setImmediate(callback)\nif available, otherwise setTimeout(callback, 0), which means other higher priority\nevents may precede the execution of the callback.\n\nThis is used internally for browser-compatibility purposes.\n\n__Arguments__\n\n* callback - The function to call on a later loop around the event loop.\n\n__Example__\n\n```js\nvar call_order = [];\nasync.nextTick(function(){\n    call_order.push('two');\n    // call_order now equals ['one','two']\n});\ncall_order.push('one')\n```\n\n<a name=\"times\" />\n### times(n, callback)\n\nCalls the callback n times and accumulates results in the same manner\nyou would use with async.map.\n\n__Arguments__\n\n* n - The number of times to run the function.\n* callback - The function to call n times.\n\n__Example__\n\n```js\n// Pretend this is some complicated async factory\nvar createUser = function(id, callback) {\n  callback(null, {\n    id: 'user' + id\n  })\n}\n// generate 5 users\nasync.times(5, function(n, next){\n    createUser(n, function(err, user) {\n      next(err, user)\n    })\n}, function(err, users) {\n  // we should now have 5 users\n});\n```\n\n<a name=\"timesSeries\" />\n### timesSeries(n, callback)\n\nThe same as times only the iterator is applied to each item in the array in\nseries. The next iterator is only called once the current one has completed\nprocessing. The results array will be in the same order as the original.\n\n\n## Utils\n\n<a name=\"memoize\" />\n### memoize(fn, [hasher])\n\nCaches the results of an async function. When creating a hash to store function\nresults against, the callback is omitted from the hash and an optional hash\nfunction can be used.\n\nThe cache of results is exposed as the `memo` property of the function returned\nby `memoize`.\n\n__Arguments__\n\n* fn - the function you to proxy and cache results from.\n* hasher - an optional function for generating a custom hash for storing\n  results, it has all the arguments applied to it apart from the callback, and\n  must be synchronous.\n\n__Example__\n\n```js\nvar slow_fn = function (name, callback) {\n    // do something\n    callback(null, result);\n};\nvar fn = async.memoize(slow_fn);\n\n// fn can now be used as if it were slow_fn\nfn('some name', function () {\n    // callback\n});\n```\n\n<a name=\"unmemoize\" />\n### unmemoize(fn)\n\nUndoes a memoized function, reverting it to the original, unmemoized\nform. Comes handy in tests.\n\n__Arguments__\n\n* fn - the memoized function\n\n<a name=\"log\" />\n### log(function, arguments)\n\nLogs the result of an async function to the console. Only works in node.js or\nin browsers that support console.log and console.error (such as FF and Chrome).\nIf multiple arguments are returned from the async function, console.log is\ncalled on each argument in order.\n\n__Arguments__\n\n* function - The function you want to eventually apply all arguments to.\n* arguments... - Any number of arguments to apply to the function.\n\n__Example__\n\n```js\nvar hello = function(name, callback){\n    setTimeout(function(){\n        callback(null, 'hello ' + name);\n    }, 1000);\n};\n```\n```js\nnode> async.log(hello, 'world');\n'hello world'\n```\n\n---------------------------------------\n\n<a name=\"dir\" />\n### dir(function, arguments)\n\nLogs the result of an async function to the console using console.dir to\ndisplay the properties of the resulting object. Only works in node.js or\nin browsers that support console.dir and console.error (such as FF and Chrome).\nIf multiple arguments are returned from the async function, console.dir is\ncalled on each argument in order.\n\n__Arguments__\n\n* function - The function you want to eventually apply all arguments to.\n* arguments... - Any number of arguments to apply to the function.\n\n__Example__\n\n```js\nvar hello = function(name, callback){\n    setTimeout(function(){\n        callback(null, {hello: name});\n    }, 1000);\n};\n```\n```js\nnode> async.dir(hello, 'world');\n{hello: 'world'}\n```\n\n---------------------------------------\n\n<a name=\"noConflict\" />\n### noConflict()\n\nChanges the value of async back to its original value, returning a reference to the\nasync object.\n",
+  "readmeFilename": "README.md",
+  "homepage": "https://github.com/caolan/async",
+  "_id": "async@0.2.10",
+  "_shasum": "b6bbe0b0674b9d719708ca38de8c237cb526c3d1",
+  "_from": "async@~0.2.9",
+  "_resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz"
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/package.json b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/package.json
new file mode 100644
index 0000000..473485c
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/adbkit-monkey/package.json
@@ -0,0 +1,79 @@
+{
+  "name": "adbkit-monkey",
+  "version": "1.0.1",
+  "description": "A Node.js interface to the Android monkey tool.",
+  "keywords": [
+    "adb",
+    "adbkit",
+    "monkey",
+    "monkeyrunner"
+  ],
+  "bugs": {
+    "url": "https://github.com/CyberAgent/adbkit-monkey/issues"
+  },
+  "license": "Apache-2.0",
+  "author": {
+    "name": "CyberAgent, Inc.",
+    "email": "npm@cyberagent.co.jp",
+    "url": "http://www.cyberagent.co.jp/"
+  },
+  "main": "./index",
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/CyberAgent/adbkit-monkey.git"
+  },
+  "scripts": {
+    "postpublish": "grunt clean",
+    "prepublish": "grunt coffee",
+    "test": "grunt test"
+  },
+  "dependencies": {
+    "async": "~0.2.9"
+  },
+  "devDependencies": {
+    "chai": "~1.8.1",
+    "coffee-script": "~1.6.3",
+    "grunt": "~0.4.1",
+    "grunt-cli": "~0.1.11",
+    "grunt-coffeelint": "~0.0.7",
+    "grunt-contrib-clean": "~0.5.0",
+    "grunt-contrib-coffee": "~0.7.0",
+    "grunt-contrib-watch": "~0.5.3",
+    "grunt-exec": "~0.4.2",
+    "grunt-jsonlint": "~1.0.2",
+    "grunt-notify": "~0.2.16",
+    "mocha": "~1.14.0",
+    "sinon": "~1.7.3",
+    "sinon-chai": "~2.4.0"
+  },
+  "engines": {
+    "node": ">= 0.10.4"
+  },
+  "readme": "# adbkit-monkey\n\n**adbkit-monkey** provides a [Node.js][nodejs] interface for working with the Android [`monkey` tool][monkey-site]. Albeit undocumented, they monkey program can be started in TCP mode with the `--port` argument. In this mode, it accepts a [range of commands][monkey-proto] that can be used to interact with the UI in a non-random manner. This mode is also used internally by the [`monkeyrunner` tool][monkeyrunner-site], although the documentation claims no relation to the monkey tool.\n\n## Getting started\n\nInstall via NPM:\n\n```bash\nnpm install --save adbkit-monkey\n```\n\nNote that while adbkit-monkey is written in CoffeeScript, it is compiled to JavaScript before publishing to NPM, which means that you are not required to use CoffeeScript.\n\n### Examples\n\nThe following examples assume that monkey is already running (via `adb shell monkey --port 1080`) and a port forwarding (`adb forward tcp:1080 tcp:1080`) has been set up.\n\n#### Press the home button\n\n```javascript\nvar assert = require('assert');\nvar monkey = require('adbkit-monkey');\n\nvar client = monkey.connect({ port: 1080 });\n\nclient.press(3 /* KEYCODE_HOME */, function(err) {\n  assert.ifError(err);\n  console.log('Pressed home button');\n  client.end();\n});\n```\n\n#### Drag out the notification bar\n\n```javascript\nvar assert = require('assert');\nvar monkey = require('adbkit-monkey');\n\nvar client = monkey.connect({ port: 1080 });\n\nclient.multi()\n  .touchDown(100, 0)\n  .sleep(5)\n  .touchMove(100, 20)\n  .sleep(5)\n  .touchMove(100, 40)\n  .sleep(5)\n  .touchMove(100, 60)\n  .sleep(5)\n  .touchMove(100, 80)\n  .sleep(5)\n  .touchMove(100, 100)\n  .sleep(5)\n  .touchUp(100, 100)\n  .sleep(5)\n  .execute(function(err) {\n    assert.ifError(err);\n    console.log('Dragged out the notification bar');\n    client.end();\n  });\n```\n\n#### Get display size\n\n```javascript\nvar assert = require('assert');\nvar monkey = require('adbkit-monkey');\n\nvar client = monkey.connect({ port: 1080 });\n\nclient.getDisplayWidth(function(err, width) {\n  assert.ifError(err);\n  client.getDisplayHeight(function(err, height) {\n    assert.ifError(err);\n    console.log('Display size is %dx%d', width, height);\n    client.end();\n  });\n});\n```\n\n#### Type text\n\nNote that you should manually focus a text field first.\n\n```javascript\nvar assert = require('assert');\nvar monkey = require('adbkit-monkey');\n\nvar client = monkey.connect({ port: 1080 });\n\nclient.type('hello monkey!', function(err) {\n  assert.ifError(err);\n  console.log('Said hello to monkey');\n  client.end();\n});\n```\n\n## API\n\n### Monkey\n\n#### monkey.connect(options)\n\nUses [Net.connect()][node-net] to open a new TCP connection to monkey. Useful when combined with `adb forward`.\n\n* **options** Any options [`Net.connect()`][node-net] accepts.\n* Returns: A new monkey `Client` instance.\n\n#### monkey.connectStream(stream)\n\nAttaches a monkey client to an existing monkey protocol stream.\n\n* **stream** The monkey protocol [`Stream`][node-stream].\n* Returns: A new monkey `Client` instance.\n\n### Client\n\nImplements `Api`. See below for details.\n\n#### Events\n\nThe following events are available:\n\n* **error** **(err)** Emitted when an error occurs.\n    * **err** An `Error`.\n* **end** Emitted when the stream ends.\n* **finish** Emitted when the stream finishes.\n\n#### client.end()\n\nEnds the underlying stream/connection.\n\n* Returns: The `Client` instance.\n\n#### client.multi()\n\nReturns a new API wrapper that buffers commands for simultaneous delivery instead of sending them individually. When used with `api.sleep()`, allows simple gestures to be executed.\n\n* Returns: A new `Multi` instance. See `Multi` below.\n\n#### client.send(command, callback)\n\nSends a raw protocol command to monkey.\n\n* **command** The command to send. When `String`, a single command is sent. When `Array`, a series of commands is sent at once.\n* **callback(err, value, command)** Called when monkey responds to the command. If multiple commands were sent, the callback will be called once for each command.\n    * **err** `null` when successful, `Error` otherwise.\n    * **value** The response value, if any.\n    * **command** The command the response is for.\n* Returns: The `Client` instance.\n\n### Api\n\nThe monkey API implemented by `Client` and `Multi`.\n\n#### api.done(callback)\n\nCloses the current monkey session and allows a new session to connect.\n\n* **callback(err)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n* Returns: The `Api` implementation instance.\n\n#### api.flipClose(callback)\n\nSimulates closing the keyboard.\n\n* **callback(err)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n* Returns: The `Api` implementation instance.\n\n#### api.flipOpen(callback)\n\nSimulates opening the keyboard.\n\n* **callback(err)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n* Returns: The `Api` implementation instance.\n\n#### api.get(name, callback)\n\nGets the value of a variable. Use `api.list()` to retrieve a list of supported variables.\n\n* **name** The name of the variable.\n* **callback(err, value)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n    * **value** The value of the variable.\n* Returns: The `Api` implementation instance.\n\n#### api.getAmCurrentAction(callback)\n\nAlias for `api.get('am.current.action', callback)`.\n\n#### api.getAmCurrentCategories(callback)\n\nAlias for `api.get('am.current.categories', callback)`.\n\n#### api.getAmCurrentCompClass(callback)\n\nAlias for `api.get('am.current.comp.class', callback)`.\n\n#### api.getAmCurrentCompPackage(callback)\n\nAlias for `api.get('am.current.comp.package', callback)`.\n\n#### api.getCurrentData(callback)\n\nAlias for `api.get('am.current.data', callback)`.\n\n#### api.getAmCurrentPackage(callback)\n\nAlias for `api.get('am.current.package', callback)`.\n\n#### api.getBuildBoard(callback)\n\nAlias for `api.get('build.board', callback)`.\n\n#### api.getBuildBrand(callback)\n\nAlias for `api.get('build.brand', callback)`.\n\n#### api.getBuildCpuAbi(callback)\n\nAlias for `api.get('build.cpu_abi', callback)`.\n\n#### api.getBuildDevice(callback)\n\nAlias for `api.get('build.device', callback)`.\n\n#### api.getBuildDisplay(callback)\n\nAlias for `api.get('build.display', callback)`.\n\n#### api.getBuildFingerprint(callback)\n\nAlias for `api.get('build.fingerprint', callback)`.\n\n#### api.getBuildHost(callback)\n\nAlias for `api.get('build.host', callback)`.\n\n#### api.getBuildId(callback)\n\nAlias for `api.get('build.id', callback)`.\n\n#### api.getBuildManufacturer(callback)\n\nAlias for `api.get('build.manufacturer', callback)`.\n\n#### api.getBuildModel(callback)\n\nAlias for `api.get('build.model', callback)`.\n\n#### api.getBuildProduct(callback)\n\nAlias for `api.get('build.product', callback)`.\n\n#### api.getBuildTags(callback)\n\nAlias for `api.get('build.tags', callback)`.\n\n#### api.getBuildType(callback)\n\nAlias for `api.get('build.type', callback)`.\n\n#### api.getBuildUser(callback)\n\nAlias for `api.get('build.user', callback)`.\n\n#### api.getBuildVersionCodename(callback)\n\nAlias for `api.get('build.version.codename', callback)`.\n\n#### api.getBuildVersionIncremental(callback)\n\nAlias for `api.get('build.version.incremental', callback)`.\n\n#### api.getBuildVersionRelease(callback)\n\nAlias for `api.get('build.version.release', callback)`.\n\n#### api.getBuildVersionSdk(callback)\n\nAlias for `api.get('build.version.sdk', callback)`.\n\n#### api.getClockMillis(callback)\n\nAlias for `api.get('clock.millis', callback)`.\n\n#### api.getClockRealtime(callback)\n\nAlias for `api.get('clock.realtime', callback)`.\n\n#### api.getClockUptime(callback)\n\nAlias for `api.get('clock.uptime', callback)`.\n\n#### api.getDisplayDensity(callback)\n\nAlias for `api.get('display.density', callback)`.\n\n#### api.getDisplayHeight(callback)\n\nAlias for `api.get('display.height', callback)`. Note that the height may exclude any virtual home button row.\n\n#### api.getDisplayWidth(callback)\n\nAlias for `api.get('display.width', callback)`.\n\n#### api.keyDown(keyCode, callback)\n\nSends a key down event. Should be coupled with `api.keyUp()`. Note that `api.press()` performs the two events automatically.\n\n* **keyCode** The [key code][android-keycodes]. All monkeys support numeric keycodes, and some support automatic conversion from key names to key codes (e.g. `'home'` to `KEYCODE_HOME`). This will not work for number keys however. The most portable method is to simply use numeric key codes.\n* **callback(err)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n* Returns: The `Api` implementation instance.\n\n#### api.keyUp(keyCode, callback)\n\nSends a key up event. Should be coupled with `api.keyDown()`. Note that `api.press()` performs the two events automatically.\n\n* **keyCode** See `api.keyDown()`.\n* **callback(err)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n* Returns: The `Api` implementation instance.\n\n#### api.list(callback)\n\nLists supported variables.\n\n* **callback(err, vars)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n    * **vars** An array of supported variable names, to be used with `api.get()`.\n* Returns: The `Api` implementation instance.\n\n#### api.press(keyCode, callback)\n\nSends a key press event.\n\n* **keyCode** See `api.keyDown()`.\n* **callback(err)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n* Returns: The `Api` implementation instance.\n\n#### api.quit(callback)\n\nCloses the current monkey session and quits monkey.\n\n* **callback(err)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n* Returns: The `Api` implementation instance.\n\n#### api.sleep(ms, callback)\n\nSleeps for the given duration. Can be useful for simulating gestures.\n\n* **ms** How many milliseconds to sleep for.\n* **callback(err)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n* Returns: The `Api` implementation instance.\n\n#### api.tap(x, y, callback)\n\nTaps the given coordinates.\n\n* **x** The x coordinate.\n* **y** The y coordinate.\n* **callback(err)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n* Returns: The `Api` implementation instance.\n\n#### api.touchDown(x, y, callback)\n\nSends a touch down event on the given coordinates.\n\n* **x** The x coordinate.\n* **y** The y coordinate.\n* **callback(err)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n* Returns: The `Api` implementation instance.\n\n#### api.touchMove(x, y, callback)\n\nSends a touch move event on the given coordinates.\n\n* **x** The x coordinate.\n* **y** The y coordinate.\n* **callback(err)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n* Returns: The `Api` implementation instance.\n\n#### api.touchUp(x, y, callback)\n\nSends a touch up event on the given coordinates.\n\n* **x** The x coordinate.\n* **y** The y coordinate.\n* **callback(err)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n* Returns: The `Api` implementation instance.\n\n#### api.trackball(x, y, callback)\n\nSends a trackball event on the given coordinates.\n\n* **x** The x coordinate.\n* **y** The y coordinate.\n* **callback(err)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n* Returns: The `Api` implementation instance.\n\n#### api.type(text, callback)\n\nTypes the given text.\n\n* **text** A text `String`. Note that only characters for which [key codes][android-keycodes] exist can be entered. Also note that any IME in use may or may not transform the text.\n* **callback(err)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n* Returns: The `Api` implementation instance.\n\n#### api.wake(callback)\n\nWakes the device from sleep and allows user input.\n\n* **callback(err)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n* Returns: The `Api` implementation instance.\n\n### Multi\n\nBuffers `Api` commands and delivers them simultaneously for greater control over timing.\n\nImplements all `Api` methods, but without the last `callback` parameter.\n\n#### multi.execute(callback)\n\nSends all buffered commands.\n\n* **callback(err, values)** Called when monkey has responded to all commands (i.e. just once at the end).\n    * **err** `null` when successful, `Error` otherwise.\n    * **values** An array of all response values, identical to individual `Api` responses.\n\n## More information\n\n* [Monkey][monkey-site]\n    - [Source code][monkey-source]\n    - [Protocol][monkey-proto]\n* [Monkeyrunner][monkeyrunner-site]\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md).\n\n## License\n\nSee [LICENSE](LICENSE).\n\nCopyright © CyberAgent, Inc. All Rights Reserved.\n\n[nodejs]: <http://nodejs.org/>\n[monkey-site]: <http://developer.android.com/tools/help/monkey.html>\n[monkey-source]: <https://github.com/android/platform_development/blob/master/cmds/monkey/>\n[monkey-proto]: <https://github.com/android/platform_development/blob/master/cmds/monkey/README.NETWORK.txt>\n[monkeyrunner-site]: <http://developer.android.com/tools/help/monkeyrunner_concepts.html>\n[node-net]: <http://nodejs.org/api/net.html>\n[node-stream]: <http://nodejs.org/api/stream.html>\n[android-keycodes]: <http://developer.android.com/reference/android/view/KeyEvent.html>\n",
+  "readmeFilename": "README.md",
+  "homepage": "https://github.com/CyberAgent/adbkit-monkey",
+  "_id": "adbkit-monkey@1.0.1",
+  "dist": {
+    "shasum": "f291be701a2efc567a63fc7aa6afcded31430be1",
+    "tarball": "http://registry.npmjs.org/adbkit-monkey/-/adbkit-monkey-1.0.1.tgz"
+  },
+  "_from": "adbkit-monkey@~1.0.1",
+  "_npmVersion": "1.3.14",
+  "_npmUser": {
+    "name": "sorccu",
+    "email": "simo@shoqolate.com"
+  },
+  "maintainers": [
+    {
+      "name": "sorccu",
+      "email": "simo@shoqolate.com"
+    },
+    {
+      "name": "cyberagent",
+      "email": "npm@cyberagent.co.jp"
+    }
+  ],
+  "directories": {},
+  "_shasum": "f291be701a2efc567a63fc7aa6afcded31430be1",
+  "_resolved": "https://registry.npmjs.org/adbkit-monkey/-/adbkit-monkey-1.0.1.tgz"
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/.npmignore b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/.npmignore
new file mode 100644
index 0000000..36ee3f7
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/.npmignore
@@ -0,0 +1,30 @@
+node_modules/*
+todo.txt
+npm-debug.log
+test/*
+benchmark/*
+browser/*
+src/*
+async
+sync
+mixed
+bench.json
+js/browser
+js/browser/*
+js/debug
+js/debug/*
+reader.js
+read.txt
+bench
+.editorconfig
+.jshintrc
+ast_passes.js
+mocharun.js
+throwaway.js
+throwaway.html
+bluebird.sublime-workspace
+bluebird.sublime-project
+changelog.js
+.travis.yml
+sauce_connect.log
+bump.js
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/API.md b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/API.md
new file mode 100644
index 0000000..7e940c2
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/API.md
@@ -0,0 +1,1741 @@
+#API Reference
+
+- [Core](#core)
+    - [`new Promise(Function<Function resolve, Function reject> resolver)`](#new-promisefunctionfunction-resolve-function-reject-resolver---promise)
+    - [`.then([Function fulfilledHandler] [, Function rejectedHandler ] [, Function progressHandler ])`](#thenfunction-fulfilledhandler--function-rejectedhandler---function-progresshandler----promise)
+    - [`.catch(Function handler)`](#catchfunction-handler---promise)
+    - [`.catch([Function ErrorClass|Function predicate...], Function handler)`](#catchfunction-errorclassfunction-predicate-function-handler---promise)
+    - [`.error( [rejectedHandler] )`](#error-rejectedhandler----promise)
+    - [`.finally(Function handler)`](#finallyfunction-handler---promise)
+    - [`.tap(Function handler)`](#tapfunction-handler---promise)
+    - [`.bind(dynamic thisArg)`](#binddynamic-thisarg---promise)
+    - [`.done([Function fulfilledHandler] [, Function rejectedHandler ] [, Function progressHandler ])`](#donefunction-fulfilledhandler--function-rejectedhandler---function-progresshandler----promise)
+    - [`Promise.try(Function fn [, Array<dynamic>|dynamic arguments] [, dynamic ctx] )`](#promisetryfunction-fn--arraydynamicdynamic-arguments--dynamic-ctx----promise)
+    - [`Promise.method(Function fn)`](#promisemethodfunction-fn---function)
+    - [`Promise.resolve(dynamic value)`](#promiseresolvedynamic-value---promise)
+    - [`Promise.reject(dynamic reason)`](#promiserejectdynamic-reason---promise)
+    - [`Promise.defer()`](#promisedefer---promiseresolver)
+    - [`Promise.cast(dynamic value)`](#promisecastdynamic-value---promise)
+    - [`Promise.bind(dynamic thisArg)`](#promisebinddynamic-thisarg---promise)
+    - [`Promise.is(dynamic value)`](#promiseisdynamic-value---boolean)
+    - [`Promise.longStackTraces()`](#promiselongstacktraces---void)
+- [Progression](#progression)
+    - [`.progressed(Function handler)`](#progressedfunction-handler---promise)
+- [Promise resolution](#promise-resolution)
+    - [`.resolve(dynamic value)`](#resolvedynamic-value---undefined)
+    - [`.reject(dynamic reason)`](#rejectdynamic-reason---undefined)
+    - [`.progress(dynamic value)`](#progressdynamic-value---undefined)
+    - [`.callback`](#callback---function)
+- [Timers](#timers)
+    - [`.delay(int ms)`](#delayint-ms---promise)
+    - [`.timeout(int ms [, String message])`](#timeoutint-ms--string-message---promise)
+    - [`Promise.delay([dynamic value], int ms)`](#promisedelaydynamic-value-int-ms---promise)
+- [Promisification](#promisification)
+    - [`Promise.promisify(Function nodeFunction [, dynamic receiver])`](#promisepromisifyfunction-nodefunction--dynamic-receiver---function)
+    - [`Promise.promisify(Object target)`](#promisepromisifyobject-target---object)
+    - [`Promise.promisifyAll(Object target)`](#promisepromisifyallobject-target---object)
+    - [`.nodeify([Function callback])`](#nodeifyfunction-callback---promise)
+- [Cancellation](#cancellation)
+    - [`.cancellable()`](#cancellable---promise)
+    - [`.cancel()`](#cancel---promise)
+    - [`.fork([Function fulfilledHandler] [, Function rejectedHandler ] [, Function progressHandler ])`](#forkfunction-fulfilledhandler--function-rejectedhandler---function-progresshandler----promise)
+    - [`.uncancellable()`](#uncancellable---promise)
+    - [`.isCancellable()`](#iscancellable---boolean)
+- [Synchronous inspection](#synchronous-inspection)
+    - [`.isFulfilled()`](#isfulfilled---boolean)
+    - [`.isRejected()`](#isrejected---boolean)
+    - [`.isPending()`](#isdefer---boolean)
+    - [`.isResolved()`](#isresolved---boolean)
+    - [`.inspect()`](#inspect---promiseinspection)
+- [Generators](#generators)
+    - [`Promise.coroutine(GeneratorFunction generatorFunction)`](#promisecoroutinegeneratorfunction-generatorfunction---function)
+    - [`Promise.coroutine.addYieldHandler(function handler)`](#promisecoroutineaddyieldhandlerfunction-handler---void)
+- [Utility](#utility)
+    - [`.call(String propertyName [, dynamic arg...])`](#callstring-propertyname--dynamic-arg---promise)
+    - [`.get(String propertyName)`](#getstring-propertyname---promise)
+    - [`.return(dynamic value)`](#returndynamic-value---promise)
+    - [`.throw(dynamic reason)`](#throwdynamic-reason---promise)
+    - [`.toString()`](#tostring---string)
+    - [`.toJSON()`](#tojson---object)
+    - [`Promise.noConflict()`](#promisenoconflict---object)
+    - [`Promise.onPossiblyUnhandledRejection(Function handler)`](#promiseonpossiblyunhandledrejectionfunction-handler---undefined)
+- [Collections](#collections)
+    - [`.all()`](#all---promise)
+    - [`.props()`](#props---promise)
+    - [`.settle()`](#settle---promise)
+    - [`.any()`](#any---promise)
+    - [`.race()`](#race---promise)
+    - [`.some(int count)`](#someint-count---promise)
+    - [`.spread([Function fulfilledHandler] [, Function rejectedHandler ])`](#spreadfunction-fulfilledhandler--function-rejectedhandler----promise)
+    - [`.map(Function mapper)`](#mapfunction-mapper---promise)
+    - [`.reduce(Function reducer [, dynamic initialValue])`](#reducefunction-reducer--dynamic-initialvalue---promise)
+    - [`.filter(Function filterer)`](#filterfunction-filterer---promise)
+    - [`Promise.all(Array<dynamic>|Promise values)`](#promiseallarraydynamicpromise-values---promise)
+    - [`Promise.props(Object|Promise object)`](#promisepropsobjectpromise-object---promise)
+    - [`Promise.settle(Array<dynamic>|Promise values)`](#promisesettlearraydynamicpromise-values---promise)
+    - [`Promise.any(Array<dynamic>|Promise values)`](#promiseanyarraydynamicpromise-values---promise)
+    - [`Promise.race(Array|Promise promises)`](#promiseracearraypromise-promises---promise)
+    - [`Promise.some(Array<dynamic>|Promise values, int count)`](#promisesomearraydynamicpromise-values-int-count---promise)
+    - [`Promise.join([dynamic value...])`](#promisejoindynamic-value---promise)
+    - [`Promise.map(Array<dynamic>|Promise values, Function mapper)`](#promisemaparraydynamicpromise-values-function-mapper---promise)
+    - [`Promise.reduce(Array<dynamic>|Promise values, Function reducer [, dynamic initialValue])`](#promisereducearraydynamicpromise-values-function-reducer--dynamic-initialvalue---promise)
+    - [`Promise.filter(Array<dynamic>|Promise values, Function filterer)`](#promisefilterarraydynamicpromise-values-function-filterer---promise)
+
+##Core
+
+Core methods of `Promise` instances and core static methods of the Promise class.
+
+#####`new Promise(Function<Function resolve, Function reject> resolver)` -> `Promise`
+
+Create a new promise. The passed in function will receive functions `resolve` and `reject` as its arguments which can be called to seal the fate of the created promise.
+
+Example:
+
+```js
+function ajaxGetAsync(url) {
+    return new Promise(function (resolve, reject) {
+        var xhr = new XMLHttpRequest;
+        xhr.addEventListener("error", reject);
+        xhr.addEventListener("load", resolve);
+        xhr.open("GET", url);
+        xhr.send(null);
+    });
+}
+```
+
+If you pass a promise object to the `resolve` function, the created promise will follow the state of that promise.
+
+<hr>
+
+To make sure a function that returns a promise is following the implicit but critically important contract of promises, you can start a function with `new Promise` if you cannot start a chain immediately:
+
+```js
+function getConnection(urlString) {
+    return new Promise(function(resolve) {
+        //Without new Promise, this throwing will throw an actual exception
+        var params = parse(urlString);
+        resolve(getAdapater(params).getConnection());
+    });
+}
+```
+
+The above ensures `getConnection()` fulfills the contract of a promise-returning function of never throwing a synchronous exception. Also see [`Promise.try`](#promisetryfunction-fn--arraydynamicdynamic-arguments--dynamic-ctx----promise) and [`Promise.method`](#promisemethodfunction-fn---function)
+
+<hr>
+
+#####`.then([Function fulfilledHandler] [, Function rejectedHandler ] [, Function progressHandler ])` -> `Promise`
+
+[Promises/A+ `.then()`](http://promises-aplus.github.io/promises-spec/) with progress handler. Returns a new promise chained from this promise. The new promise will be rejected or resolved dedefer on the passed `fulfilledHandler`, `rejectedHandler` and the state of this promise.
+
+Example:
+
+```js
+promptAsync("Which url to visit?").then(function(url){
+    return ajaxGetAsync(url);
+}).then(function(contents){
+    alertAsync("The contents were: " + contents);
+}).catch(function(e){
+    alertAsync("Exception " + e);
+});
+```
+
+<hr>
+
+#####`.catch(Function handler)` -> `Promise`
+
+This is a catch-all exception handler, shortcut for calling `.then(null, handler)` on this promise. Any exception happening in a `.then`-chain will propagate to nearest `.catch` handler.
+
+*For compatibility with earlier ECMAScript version, an alias `.caught()` is provided for `.catch()`.*
+
+<hr>
+
+#####`.catch([Function ErrorClass|Function predicate...], Function handler)` -> `Promise`
+
+This extends `.catch` to work more like catch-clauses in languages like Java or C#. Instead of manually checking `instanceof` or `.name === "SomeError"`, you may specify a number of error constructors which are eligible for this catch handler. The catch handler that is first met that has eligible constructors specified, is the one that will be called.
+
+Example:
+
+```js
+somePromise.then(function(){
+    return a.b.c.d();
+}).catch(TypeError, function(e){
+    //If a is defined, will end up here because
+    //it is a type error to reference property of undefined
+}).catch(ReferenceError, function(e){
+    //Will end up here if a wasn't defined at all
+}).catch(function(e){
+    //Generic catch-the rest, error wasn't TypeError nor
+    //ReferenceError
+});
+ ```
+
+You may also add multiple filters for a catch handler:
+
+```js
+somePromise.then(function(){
+    return a.b.c.d();
+}).catch(TypeError, ReferenceError, function(e){
+    //Will end up here on programmer error
+}).catch(NetworkError, TimeoutError, function(e){
+    //Will end up here on expected everyday network errors
+}).catch(function(e){
+    //Catch any unexpected errors
+});
+```
+
+For a parameter to be considered a type of error that you want to filter, you need the constructor to have its `.prototype` property be `instanceof Error`.
+
+Such a constructor can be minimally created like so:
+
+```js
+function MyCustomError() {}
+MyCustomError.prototype = Object.create(Error.prototype);
+```
+
+Using it:
+
+```js
+Promise.resolve().then(function(){
+    throw new MyCustomError();
+}).catch(MyCustomError, function(e){
+    //will end up here now
+});
+```
+
+However if you  want stack traces and cleaner string output, then you should do:
+
+*in Node.js and other V8 environments, with support for `Error.captureStackTrace`*
+
+```js
+function MyCustomError(message) {
+    this.message = message;
+    this.name = "MyCustomError";
+    Error.captureStackTrace(this, MyCustomError);
+}
+MyCustomError.prototype = Object.create(Error.prototype);
+MyCustomError.prototype.constructor = MyCustomError;
+```
+
+Using CoffeeScript's `class` for the same:
+
+```coffee
+class MyCustomError extends Error
+  constructor: (@message) ->
+    @name = "MyCustomError"
+    Error.captureStackTrace(this, MyCustomError)
+```
+
+This method also supports predicate-based filters. If you pass a
+predicate function instead of an error constructor, the predicate will receive
+the error as an argument. The return result of the predicate will be used
+determine whether the error handler should be called.
+
+Predicates should allow for very fine grained control over caught errors:
+pattern matching, error-type sets with set operations and many other techniques
+can be implemented on top of them.
+
+Example of using a predicate-based filter:
+
+```js
+var Promise = require("bluebird");
+var request = Promise.promisify(require("request"));
+
+function clientError(e) {
+    return e.code >= 400 && e.code < 500;
+}
+
+request("http://www.google.com").then(function(contents){
+    console.log(contents);
+}).catch(clientError, function(e){
+   //A client error like 400 Bad Request happened
+});
+```
+
+*For compatibility with earlier ECMAScript version, an alias `.caught()` is provided for `.catch()`.*
+
+<hr>
+
+#####`.error( [rejectedHandler] )` -> `Promise`
+
+Like `.catch` but instead of catching all types of exceptions, it only catches those that don't originate from thrown errors but rather from explicit rejections.
+
+*Note, "errors" mean errors, as in objects that are `instanceof Error` - not strings, numbers and so on. See [a string is not an error](http://www.devthought.com/2011/12/22/a-string-is-not-an-error/).*
+
+For example, if a promisified function errbacks the node-style callback with an error, that could be caught with `.error()`. However if the node-style callback **throws** an error, only `.catch` would catch that.
+
+In the following example you might want to handle just the `SyntaxError` from JSON.parse and Filesystem errors from `fs` but let programmer errors bubble as unhandled rejections:
+
+```js
+var fs = Promise.promisifyAll(require("fs"));
+
+fs.readFileAsync("myfile.json").then(JSON.parse).then(function (json) {
+    console.log("Successful json")
+}).catch(SyntaxError, function (e) {
+    console.error("file contains invalid json");
+}).error(function (e) {
+    console.error("unable to read file, because: ", e.message);
+});
+```
+
+Now, because there is no catch-all handler, if you typed `console.lag` (causes an error you don't expect), you will see:
+
+```
+Possibly unhandled TypeError: Object #<Console> has no method 'lag'
+    at application.js:8:13
+From previous event:
+    at Object.<anonymous> (application.js:7:4)
+    at Module._compile (module.js:449:26)
+    at Object.Module._extensions..js (module.js:467:10)
+    at Module.load (module.js:349:32)
+    at Function.Module._load (module.js:305:12)
+    at Function.Module.runMain (module.js:490:10)
+    at startup (node.js:121:16)
+    at node.js:761:3
+```
+
+*( If you don't get the above - you need to enable [long stack traces](#promiselongstacktraces---void) )*
+
+And if the file contains invalid JSON:
+
+```
+file contains invalid json
+```
+
+And if the `fs` module causes an error like file not found:
+
+```
+unable to read file, because:  ENOENT, open 'not_there.txt'
+```
+
+<hr>
+
+#####`.finally(Function handler)` -> `Promise`
+
+Pass a handler that will be called regardless of this promise's fate. Returns a new promise chained from this promise. There are special semantics for `.finally()` in that the final value cannot be modified from the handler.
+
+Consider the example:
+
+```js
+function anyway() {
+    $("#ajax-loader-animation").hide();
+}
+
+function ajaxGetAsync(url) {
+    return new Promise(function (resolve, reject) {
+        var xhr = new XMLHttpRequest;
+        xhr.addEventListener("error", reject);
+        xhr.addEventListener("load", resolve);
+        xhr.open("GET", url);
+        xhr.send(null);
+    }).then(anyway, anyway);
+}
+```
+
+This example doesn't work as intended because the `then` handler actually swallows the exception and returns `undefined` for any further chainers.
+
+The situation can be fixed with `.finally`:
+
+```js
+function ajaxGetAsync(url) {
+    return new Promise(function (resolve, reject) {
+        var xhr = new XMLHttpRequest;
+        xhr.addEventListener("error", reject);
+        xhr.addEventListener("load", resolve);
+        xhr.open("GET", url);
+        xhr.send(null);
+    }).finally(function(){
+        $("#ajax-loader-animation").hide();
+    });
+}
+```
+
+Now the animation is hidden but an exception or the actual return value will automatically skip the finally and propagate to further chainers. This is more in line with the synchronous `finally` keyword.
+
+The `.finally` works like [Q's finally method](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback).
+
+*For compatibility with earlier ECMAScript version, an alias `.lastly()` is provided for `.finally()`.*
+
+<hr>
+
+#####`.tap(Function handler)` -> `Promise`
+
+Like `.finally` that is not called for rejections.
+
+```js
+getUser().tap(function(user) {
+    //Like in finally, if you return a promise from the handler
+    //the promise is awaited for before passing the original value through
+    return recordStatsAsync();
+}).then(function(user) {
+    //user is the user from getUser(), not recordStatsAsync()
+});
+```
+
+<hr>
+
+#####`.bind(dynamic thisArg)` -> `Promise`
+
+Create a promise that follows this promise, but is bound to the given `thisArg` value. A bound promise will call its handlers with the bound value set to `this`. Additionally promises derived from a bound promise will also be bound promises with the same `thisArg` binding as the original promise.
+
+<hr>
+
+Without arrow functions that provide lexical `this`, the correspondence between async and sync code breaks down when writing object-oriented code. `.bind()` alleviates this.
+
+Consider:
+
+```js
+MyClass.prototype.method = function() {
+    try {
+        var contents = fs.readFileSync(this.file);
+        var url = urlParse(contents);
+        var result = this.httpGetSync(url);
+        var refined = this.refine(result);
+        return this.writeRefinedSync(refined);
+    }
+    catch (e) {
+        this.error(e.stack);
+    }
+};
+```
+
+The above has a direct translation:
+
+```js
+MyClass.prototype.method = function() {
+    return fs.readFileAsync(this.file).bind(this)
+    .then(function(contents) {
+        var url = urlParse(contents);
+        return this.httpGetAsync(url);
+    }).then(function(result){
+        var refined = this.refine(result);
+        return this.writeRefinedAsync(refined);
+    }).catch(function(e){
+        this.error(e.stack);
+    });
+};
+```
+
+`.bind()` is the most efficient way of utilizing `this` with promises. The handler functions in the above code are not closures and can therefore even be hoisted out if needed. There is literally no overhead when propagating the bound value from one promise to another.
+
+<hr>
+
+`.bind()` also has a useful side purpose - promise handlers don't need to share a function to use shared state:
+
+```js
+somethingAsync().bind({})
+.then(function (aValue, bValue) {
+    this.aValue = aValue;
+    this.bValue = bValue;
+    return somethingElseAsync(aValue, bValue);
+})
+.then(function (cValue) {
+    return this.aValue + this.bValue + cValue;
+});
+```
+
+The above without `.bind()` could be achieved with:
+
+```js
+var scope = {};
+somethingAsync()
+.then(function (aValue, bValue) {
+    scope.aValue = aValue;
+    scope.bValue = bValue;
+    return somethingElseAsync(aValue, bValue);
+})
+.then(function (cValue) {
+    return scope.aValue + scope.bValue + cValue;
+});
+```
+
+However, there are many differences when you look closer:
+
+- Requires a statement so cannot be used in an expression context
+- If not there already, an additional wrapper function is required to avoid leaking or sharing `scope`
+- The handler functions are now closures, thus less efficient and not reusable
+
+<hr>
+
+Note that bind is only propagated with promise transformation. If you create new promise chains inside a handler, those chains are not bound to the "upper" `this`:
+
+```js
+something().bind(var1).then(function(){
+    //`this` is var1 here
+    return Promise.all(getStuff()).then(function(results){
+        //`this` is undefined here
+        //refine results here etc
+    });
+}).then(function(){
+    //`this` is var1 here
+});
+```
+
+However, if you are utilizing the full bluebird API offering, you will *almost never* need to resort to nesting promises in the first place. The above should be written more like:
+
+```js
+something().bind(var1).then(function() {
+    //`this` is var1 here
+    return getStuff();
+}).map(function(result){
+    //`this` is var1 here
+    //refine result here
+}).then(function(){
+    //`this` is var1 here
+});
+```
+
+Also see [this Stackoverflow answer](http://stackoverflow.com/a/19467053/995876) on a good example on how utilizing the collection instance methods like [`.map()`](#mapfunction-mapper---promise) can clean up code.
+
+<hr>
+
+If you don't want to return a bound promise to the consumers of a promise, you can rebind the chain at the end:
+
+```js
+MyClass.prototype.method = function() {
+    return fs.readFileAsync(this.file).bind(this)
+    .then(function(contents) {
+        var url = urlParse(contents);
+        return this.httpGetAsync(url);
+    }).then(function(result){
+        var refined = this.refine(result);
+        return this.writeRefinedAsync(refined);
+    }).catch(function(e){
+        this.error(e.stack);
+    }).bind(); //The `thisArg` is implicitly undefined - I.E. the default promise `this` value
+};
+```
+
+Rebinding can also be abused to do something gratuitous like this:
+
+```js
+Promise.resolve("my-element")
+    .bind(document)
+    .then(document.getElementById)
+    .bind(console)
+    .then(console.log);
+```
+
+The above does `console.log(document.getElementById("my-element"));`. The `.bind()`s are necessary because in browser neither of the methods can be called as a stand-alone function.
+
+<hr>
+
+#####`.done([Function fulfilledHandler] [, Function rejectedHandler ] [, Function progressHandler ])` -> `Promise`
+
+Like `.then()`, but any unhandled rejection that ends up here will be thrown as an error.
+
+<hr>
+
+#####`Promise.try(Function fn [, Array<dynamic>|dynamic arguments] [, dynamic ctx] )` -> `Promise`
+
+Start the chain of promises with `Promise.try`. Any synchronous exceptions will be turned into rejections on the returned promise.
+
+```js
+function getUserById(id) {
+    return Promise.try(function(){
+        if (typeof id !== "number") {
+            throw new Error("id must be a number");
+        }
+        return db.getUserById(id);
+    });
+}
+```
+
+Now if someone uses this function, they will catch all errors in their Promise `.catch` handlers instead of having to handle both synchronous and asynchronous exception flows.
+
+Note about second argument: if it's specifically a true array, its values become respective arguments for the function call. Otherwise it is passed as is as the first argument for the function call.
+
+*For compatibility with earlier ECMAScript version, an alias `Promise.attempt()` is provided for `Promise.try()`.*
+
+<hr>
+
+#####`Promise.method(Function fn)` -> `Function`
+
+Returns a new function that wraps the given function `fn`. The new function will always return a promise that is fulfilled with the original functions return values or rejected with thrown exceptions from the original function.
+
+This method is convenient when a function can sometimes return synchronously or throw synchronously.
+
+Example without using `Promise.method`:
+
+```js
+MyClass.prototype.method = function(input) {
+    if (!this.isValid(input)) {
+        return Promise.reject(new TypeError("input is not valid"));
+    }
+
+    if (this.cache(input)) {
+        return Promise.resolve(this.someCachedValue);
+    }
+
+    return db.queryAsync(input).bind(this).then(function(value) {
+        this.someCachedValue = value;
+        return value;
+    });
+};
+```
+
+Using the same function `Promise.method`, there is no need to manually wrap direct return or throw values into a promise:
+
+```js
+MyClass.prototype.method = Promise.method(function(input) {
+    if (!this.isValid(input)) {
+        throw new TypeError("input is not valid");
+    }
+
+    if (this.cachedFor(input)) {
+        return this.someCachedValue;
+    }
+
+    return db.queryAsync(input).bind(this).then(function(value) {
+        this.someCachedValue = value;
+        return value;
+    });
+});
+```
+
+<hr>
+
+#####`Promise.resolve(dynamic value)` -> `Promise`
+
+Create a promise that is resolved with the given `value`. If `value` is a thenable or promise, the returned promise will assume its state.
+
+<hr>
+
+#####`Promise.reject(dynamic reason)` -> `Promise`
+
+Create a promise that is rejected with the given `reason`.
+
+<hr>
+
+#####`Promise.defer()` -> `PromiseResolver`
+
+Create a promise with undecided fate and return a `PromiseResolver` to control it. See [Promise resolution](#promise-resolution).
+
+The use of `Promise.defer` is discouraged - it is much more awkward and error-prone than using `new Promise`.
+
+<hr>
+
+#####`Promise.cast(dynamic value)` -> `Promise`
+
+Cast the given `value` to a trusted promise. If `value` is already a trusted `Promise`, it is returned as is. If `value` is not a thenable, a fulfilled Promise is returned with `value` as its fulfillment value. If `value` is a thenable (Promise-like object, like those returned by jQuery's `$.ajax`), returns a trusted Promise that assimilates the state of the thenable.
+
+Example: (`$` is jQuery)
+
+```js
+Promise.cast($.get("http://www.google.com")).then(function(){
+    //Returning a thenable from a handler is automatically
+    //cast to a trusted Promise as per Promises/A+ specification
+    return $.post("http://www.yahoo.com");
+}).then(function(){
+
+}).catch(function(e){
+    //jQuery doesn't throw real errors so use catch-all
+    console.log(e.statusText);
+});
+```
+
+<hr>
+
+#####`Promise.bind(dynamic thisArg)` -> `Promise`
+
+Sugar for `Promise.resolve(undefined).bind(thisArg);`. See [`.bind()`](#binddynamic-thisarg---promise).
+
+<hr>
+
+#####`Promise.is(dynamic value)` -> `boolean`
+
+See if `value` is a trusted Promise.
+
+```js
+Promise.is($.get("http://www.google.com")); //false , thenable returned from $.get is not a `Promise` instance
+Promise.is(Promise.cast($.get("http://www.google.com"))) //true, `.cast` cast the thenable into a `Promise`
+```
+
+Trusted Promises are promises originating in the currently used copy of Bluebird. Promises originating in other libraries are called thenables and are _not_ trusted promises. This method is used for checking if a passed value is of the same type as `Promise` itself creates.
+
+<hr>
+
+#####`Promise.longStackTraces()` -> `void`
+
+Call this right after the library is loaded to enabled long stack traces. Long stack traces cannot be disabled after being enabled, and cannot be enabled after promises have alread been created. Long stack traces imply a substantial performance penalty, around 4-5x for throughput and 0.5x for latency.
+
+Long stack traces are enabled by default in the debug build.
+
+To enable them in all instances of bluebird in node.js, use the environment variable `BLUEBIRD_DEBUG`:
+
+```
+BLUEBIRD_DEBUG=1 node server.js
+```
+
+You should enabled long stack traces if you want better debugging experience. For example:
+
+```js
+Promise.longStackTraces();
+Promise.resolve().then(function outer() {
+    return Promise.resolve().then(function inner() {
+        return Promise.resolve().then(function evenMoreInner() {
+            a.b.c.d()
+        }).catch(function catcher(e){
+            console.error(e.stack);
+        });
+    });
+});
+```
+
+Gives
+
+    ReferenceError: a is not defined
+        at evenMoreInner (<anonymous>:6:13)
+    From previous event:
+        at inner (<anonymous>:5:24)
+    From previous event:
+        at outer (<anonymous>:4:20)
+    From previous event:
+        at <anonymous>:3:9
+        at Object.InjectedScript._evaluateOn (<anonymous>:581:39)
+        at Object.InjectedScript._evaluateAndWrap (<anonymous>:540:52)
+        at Object.InjectedScript.evaluate (<anonymous>:459:21)
+
+While with long stack traces disabled, you would get:
+
+    ReferenceError: a is not defined
+        at evenMoreInner (<anonymous>:6:13)
+        at tryCatch1 (<anonymous>:41:19)
+        at Promise$_resolvePromise [as _resolvePromise] (<anonymous>:1739:13)
+        at Promise$_resolveLast [as _resolveLast] (<anonymous>:1520:14)
+        at Async$_consumeFunctionBuffer [as _consumeFunctionBuffer] (<anonymous>:560:33)
+        at Async$consumeFunctionBuffer (<anonymous>:515:14)
+        at MutationObserver.Promise$_Deferred (<anonymous>:433:17)
+
+On client side, long stack traces currently only work in Firefox and Chrome.
+
+<hr>
+
+##Progression
+
+#####`.progressed(Function handler)` -> `Promise`
+
+Shorthand for `.then(null, null, handler);`. Attach a progress handler that will be called if this promise is progressed. Returns a new promise chained from this promise.
+
+<hr>
+
+##Promise resolution
+
+A `PromiseResolver` can be used to control the fate of a promise. It is like "Deferred" known in jQuery. The `PromiseResolver` objects have a `.promise` property which returns a reference to the controlled promise that can be passed to clients. `.promise` of a `PromiseResolver` is not a getter function to match other implementations.
+
+The methods of a `PromiseResolver` have no effect if the fate of the underlying promise is already decided (follow, reject, fulfill).
+
+The use of `Promise.defer` and deferred objects is discouraged - it is much more awkward and error-prone than using `new Promise`.
+
+<hr>
+
+#####`.resolve(dynamic value)` -> `undefined`
+
+Resolve the underlying promise with `value` as the resolution value. If `value` is a thenable or a promise, the underlying promise will assume its state.
+
+<hr>
+
+#####`.reject(dynamic reason)` -> `undefined`
+
+Reject the underlying promise with `reason` as the rejection reason.
+
+<hr>
+
+#####`.progress(dynamic value)` -> `undefined`
+
+Progress the underlying promise with `value` as the progression value.
+
+Example
+
+```js
+function delay(ms) {
+    var resolver = Promise.defer();
+    var now = Date.now();
+    setTimeout(function(){
+        resolver.resolve(Date.now() - now);
+    }, ms);
+    return resolver.promise;
+}
+
+delay(500).then(function(ms){
+    console.log(ms + " ms passed");
+});
+```
+
+<hr>
+
+#####`.callback` -> `Function`
+
+Gives you a callback representation of the `PromiseResolver`. Note that this is not a method but a property. The callback accepts error object in first argument and success values on the 2nd parameter and the rest, I.E. node js conventions.
+
+If the the callback is called with multiple success values, the resolver fullfills its promise with an array of the values.
+
+```js
+var fs = require("fs");
+function readAbc() {
+    var resolver = Promise.defer();
+    fs.readFile("abc.txt", resolver.callback);
+    return resolver.promise;
+}
+
+readAbc()
+.then(function(abcContents) {
+    console.log(abcContents);
+})
+.catch(function(e) {
+    console.error(e);
+});
+```
+
+This example is an alternative to automatic promisification of node functions.
+
+*Performance tips*
+
+The `callback` is actually an accessor property (except on legacy browsers where it's eager data property) - so save the result if you need to call it multiple times.
+
+This is more efficient way of promisification than using `new Promise`.
+
+<hr>
+
+##Timers
+
+Methods to delay and time promises out.
+
+#####`.delay(int ms)` -> `Promise`
+
+Same as calling [`Promise.delay(this, ms)`](#promisedelaydynamic-value-int-ms---promise). With the exception that if this promise is [bound](#binddynamic-thisarg---promise) to a value, the returned promise is bound to that value too.
+
+<hr>
+
+#####`.timeout(int ms [, String message])` -> `Promise`
+
+Returns a promise that will be fulfilled with this promise's fulfillment value or rejection reason. However, if this promise is not fulfilled or rejected within `ms` milliseconds, the returned promise is rejected with a `Promise.TimeoutError` instance.
+
+You may specify a custom error message with the `message` parameter.
+
+The example function `fetchContent` tries to fetch the contents of a web page with a 50ms timeout and sleeping 100ms between each retry. If there is no response after 5 retries, then the returned promise is rejected with a `ServerError` (made up error type). Additionally the whole process can be cancelled from outside at any point.
+
+```js
+function fetchContent(retries) {
+    if (!retries) retries = 0;
+    var jqXHR = $.get("http://www.slowpage.com");
+    //Cast the jQuery promise into a bluebird promise
+    return Promise.cast(jqXHR)
+        .cancellable()
+        .timeout(50)
+        .catch(Promise.TimeoutError, function() {
+            if (retries < 5) {
+                return Promise.delay(100).then(function(){
+                    return fetchContent(retries+1);
+                });
+            }
+            else {
+                throw new ServerError("not responding after 5 retries");
+            }
+        })
+        .catch(Promise.CancellationError, function(er) {
+            jqXHR.abort();
+            throw er; //Don't swallow it
+        });
+}
+```
+
+<hr>
+
+#####`Promise.delay([dynamic value], int ms)` -> `Promise`
+
+Returns a promise that will be fulfilled with `value` (or `undefined`) after given `ms` milliseconds. If `value` is a promise, the delay will start counting down when it is fulfilled and the returned promise will be fulfilled with the fulfillment value of the `value` promise.
+
+```js
+Promise.delay(500).then(function(){
+    console.log("500 ms passed");
+    return "Hello world";
+}).delay(500).then(function(helloWorldString) {
+    console.log(helloWorldString);
+    console.log("another 500 ms passed") ;
+});
+```
+
+<hr>
+
+##Promisification
+
+#####`Promise.promisify(Function nodeFunction [, dynamic receiver])` -> `Function`
+
+Returns a function that will wrap the given `nodeFunction`. Instead of taking a callback, the returned function will return a promise whose fate is decided by the callback behavior of the given node function. The node function should conform to node.js convention of accepting a callback as last argument and calling that callback with error as the first argument and success value on the second argument.
+
+If the `nodeFunction` calls its callback with multiple success values, the fulfillment value will be an array of them.
+
+If you pass a `receiver`, the `nodeFunction` will be called as a method on the `receiver`.
+
+Example of promisifying the asynchronous `readFile` of node.js `fs`-module:
+
+```js
+var readFile = Promise.promisify(require("fs").readFile);
+
+readFile("myfile.js", "utf8").then(function(contents){
+    return eval(contents);
+}).then(function(result){
+    console.log("The result of evaluating myfile.js", result);
+}).catch(SyntaxError, function(e){
+    console.log("File had syntax error", e);
+//Catch any other error
+}).catch(function(e){
+    console.log("Error reading file", e);
+});
+```
+
+Note that if the node function is a method of some object, you need to pass the object as the second argument like so:
+
+```js
+var redisGet = Promise.promisify(redisClient.get, redisClient);
+redisGet.then(function(){
+    //...
+});
+```
+
+**Tip**
+
+Use [`.spread`](#spreadfunction-fulfilledhandler--function-rejectedhandler----promise) with APIs that have multiple success values:
+
+```js
+var Promise = require("bluebird");
+var request = Promise.promisify(require('request'));
+request("http://www.google.com").spread(function(request, body) {
+    console.log(body);
+}).catch(function(err) {
+    console.error(err);
+});
+```
+
+The above uses [request](https://github.com/mikeal/request) library which has a callback signature of multiple success values.
+
+<hr>
+
+#####`Promise.promisify(Object target)` -> `Object`
+
+This overload has been **deprecated**. The overload will continue working for now. The recommended method for promisifying multiple methods at once is [`Promise.promisifyAll(Object target)`](#promisepromisifyallobject-target---object)
+
+<hr>
+
+#####`Promise.promisifyAll(Object target)` -> `Object`
+
+Promisifies the entire object by going through the object's properties and creating an async equivalent of each function on the object and its prototype chain. The promisified method name will be the original method name postfixed with `Async`. Returns the input object.
+
+Note that the original methods on the object are not overwritten but new methods are created with the `Async`-postfix. For example, if you `promisifyAll()` the node.js `fs` object use `fs.statAsync()` to call the promisified `stat` method.
+
+Example:
+
+```js
+Promise.promisifyAll(RedisClient.prototype);
+
+//Later on, all redis client instances have promise returning functions:
+
+redisClient.hexistsAsync("myhash", "field").then(function(v){
+
+}).catch(function(e){
+
+});
+```
+
+If you don't want to write on foreign prototypes, you can sub-class the target and promisify your subclass:
+
+```js
+function MyRedisClient() {
+    RedisClient.apply(this, arguments);
+}
+MyRedisClient.prototype = Object.create(RedisClient.prototype);
+MyRedisClient.prototype.constructor = MyRedisClient;
+Promise.promisify(MyRedisClient.prototype);
+```
+
+The promisified methods will be written on the `MyRedisClient.prototype` instead. This specific example doesn't actually work with `node_redis` because the `createClient` factory is hardcoded to instantiate `RedisClient` from closure.
+
+
+It also works on singletons or specific instances:
+
+```js
+var fs = Promise.promisifyAll(require("fs"));
+
+fs.readFileAsync("myfile.js", "utf8").then(function(contents){
+    console.log(contents);
+}).catch(function(e){
+    console.error(e.stack);
+});
+```
+
+The entire prototype chain of the object is promisified on the object. Only enumerable are considered. If the object already has a promisified version of the method, it will be skipped. The target methods are assumed to conform to node.js callback convention of accepting a callback as last argument and calling that callback with error as the first argument and success value on the second argument. If the node method calls its callback with multiple success values, the fulfillment value will be an array of them.
+
+If a method already has `"Async"` postfix, it will be duplicated. E.g. `getAsync`'s promisified name is `getAsyncAsync`.
+
+<hr>
+
+#####`.nodeify([Function callback])` -> `Promise`
+
+Register a node-style callback on this promise. When this promise is is either fulfilled or rejected, the node callback will be called back with the node.js convention where error reason is the first argument and success value is the second argument. The error argument will be `null` in case of success.
+
+Returns back this promise instead of creating a new one. If the `callback` argument is not a function, this method does not do anything.
+
+This can be used to create APIs that both accept node-style callbacks and return promises:
+
+```js
+function getDataFor(input, callback) {
+    return dataFromDataBase(input).nodeify(callback);
+}
+```
+
+The above function can then make everyone happy.
+
+Promises:
+
+```js
+getDataFor("me").then(function(dataForMe) {
+    console.log(dataForMe);
+});
+```
+
+Normal callbacks:
+
+```js
+getDataFor("me", function(err, dataForMe) {
+    if( err ) {
+        console.error( err );
+    }
+    console.log(dataForMe);
+});
+```
+
+There is no effect on peformance if the user doesn't actually pass a node-style callback function.
+
+<hr>
+
+##Cancellation
+
+By default, a promise is not cancellable. A promise can be marked as cancellable with `.cancellable()`. A cancellable promise can be cancelled if it's not resolved. Cancelling a promise propagates to the farthest cancellable ancestor of the target promise that is still pending, and rejects that promise with `CancellationError`. The rejection will then propagate back to the original promise and to its descendants. This roughly follows the semantics described [here](https://github.com/promises-aplus/cancellation-spec/issues/7).
+
+Promises marked with `.cancellable()` return cancellable promises automatically.
+
+If you are the resolver for a promise, you can react to a cancel in your promise by catching the `CancellationError`:
+
+```js
+function ajaxGetAsync(url) {
+    var xhr = new XMLHttpRequest;
+    return new Promise(function (resolve, reject) {
+        xhr.addEventListener("error", reject);
+        xhr.addEventListener("load", resolve);
+        xhr.open("GET", url);
+        xhr.send(null);
+    }).cancellable().catch(Promise.CancellationError, function(e) {
+        xhr.abort();
+        throw e; //Don't swallow it
+    });
+}
+```
+
+<hr>
+
+#####`.cancellable()` -> `Promise`
+
+Marks this promise as cancellable. Promises by default are not cancellable after v0.11 and must be marked as such for [`.cancel()`](#cancel---promise) to have any effect. Marking a promise as cancellable is infectious and you don't need to remark any descendant promise.
+
+If you have code written prior v0.11 using cancellation, add calls to `.cancellable()` at the starts of promise chains that need to support
+cancellation in themselves or somewhere in their descendants.
+
+<hr>
+
+#####`.cancel()` -> `Promise`
+
+Cancel this promise. The cancellation will propagate
+to farthest cancellable ancestor promise which is still pending.
+
+That ancestor will then be rejected with a `CancellationError` (get a reference from `Promise.CancellationError`)
+object as the rejection reason.
+
+In a promise rejection handler you may check for a cancellation
+by seeing if the reason object has `.name === "Cancel"`.
+
+Promises are by default not cancellable. Use [`.cancellable()`](#cancellable---promise) to mark a promise as cancellable.
+
+<hr>
+
+#####`.fork([Function fulfilledHandler] [, Function rejectedHandler ] [, Function progressHandler ])` -> `Promise`
+
+Like `.then()`, but cancellation of the the returned promise
+or any of its descendant will not propagate cancellation
+to this promise or this promise's ancestors.
+
+<hr>
+
+#####`.uncancellable()` -> `Promise`
+
+Create an uncancellable promise based on this promise.
+
+<hr>
+
+#####`.isCancellable()` -> `boolean`
+
+See if this promise can be cancelled.
+
+<hr>
+
+##Synchronous inspection
+
+Because `.then()` must give asynchronous guarantees, it cannot be used to inspect a given promise's state synchronously. The following code won't work:
+
+```js
+var wasFulfilled = false;
+var wasRejected = false;
+var resolutionValueOrRejectionReason = null;
+somePromise.then(function(v){
+    wasFulfilled = true;
+    resolutionValueOrRejectionReason = v
+}).catch(function(v){
+    wasRejected = true;
+    resolutionValueOrRejectionReason = v
+});
+//Using the variables won't work here because .then must be called asynchronously
+```
+
+Synchronous inspection API allows you to do this like so:
+
+```js
+var inspection = somePromise.inspect();
+
+if(inspection.isFulfilled()){
+    console.log("Was fulfilled with", inspection.value());
+}
+```
+
+<hr>
+
+#####`.isFulfilled()` -> `boolean`
+
+See if this `promise` has been fulfilled.
+
+<hr>
+
+#####`.isRejected()` -> `boolean`
+
+See if this `promise` has been rejected.
+
+<hr>
+
+#####`.isPending()` -> `boolean`
+
+See if this `promise` is still defer.
+
+<hr>
+
+#####`.isResolved()` -> `boolean`
+
+See if this `promise` is resolved -> either fulfilled or rejected.
+
+<hr>
+
+#####`.inspect()` -> `PromiseInspection`
+
+Synchronously inspect the state of this `promise`. The `PromiseInspection` will represent the state of the promise as snapshotted at the time of calling `.inspect()`. It will have the following methods:
+
+`.isFulfilled()` -> `boolean`
+
+See if the underlying promise was fulfilled at the creation time of this inspection object.
+
+`.isRejected()` -> `boolean`
+
+See if the underlying promise was rejected at the creation time of this inspection object.
+
+`.isPending()` -> `boolean`
+
+See if the underlying promise was defer at the creation time of this inspection object.
+
+`.value()` -> `dynamic`, throws `TypeError`
+
+Get the fulfillment value of the underlying promise. Throws if the promise wasn't fulfilled at the creation time of this inspection object.
+
+`.error()` -> `dynamic`, throws `TypeError`
+
+Get the rejection reason for the underlying promise. Throws if the promise wasn't rejected at the creation time of this inspection object.
+
+<hr>
+
+##Generators
+
+Using ECMAScript6 generators feature to implement C# 5.0 `async/await` like syntax.
+
+#####`Promise.coroutine(GeneratorFunction generatorFunction)` -> `Function`
+
+Returns a function that can use `yield` to run asynchronous code synchronously. This feature requires the support of generators which are drafted in the next version of the language. Node version greater than `0.11.2` is required and needs to be executed with the `--harmony-generators` (or `--harmony`) command-line switch.
+
+This is the recommended, simplest and most performant way of using asynchronous generators with bluebird. It is even faster than typical promise code because the creation of new anonymous function identities at runtime can be completely avoided without obfuscating your code.
+
+```js
+var Promise = require("bluebird");
+
+function delay(ms) {
+    return new Promise(function(f){
+        setTimeout(f, ms);
+    });
+}
+
+function PingPong() {
+
+}
+
+PingPong.prototype.ping = Promise.coroutine(function* (val) {
+    console.log("Ping?", val)
+    yield delay(500)
+    this.pong(val+1)
+});
+
+PingPong.prototype.pong = Promise.coroutine(function* (val) {
+    console.log("Pong!", val)
+    yield delay(500);
+    this.ping(val+1)
+});
+
+var a = new PingPong();
+a.ping(0);
+```
+
+Running the example with node version at least 0.11.2:
+
+    $ node --harmony test.js
+    Ping? 0
+    Pong! 1
+    Ping? 2
+    Pong! 3
+    Ping? 4
+    Pong! 5
+    Ping? 6
+    Pong! 7
+    Ping? 8
+    ...
+
+When called, the coroutine function will start an instance of the generator and returns a promise for its final value.
+
+Doing `Promise.coroutine(function*(){})` is almost like using the C# `async` keyword to mark the function, with `yield` working as the `await` keyword. Promises are somewhat like `Task`s.
+
+**Tip**
+
+If you yield an array then its elements are implicitly waited for. You may add your own custom special treatments with [`Promise.coroutine.addYieldHandler`](#promisecoroutineaddyieldhandlerfunction-handler---void)
+
+You can combine it with ES6 destructuring for some neat syntax:
+
+```js
+var getData = Promise.coroutine(function* (urlA, urlB) {
+    [resultA, resultB] = yield [http.getAsync(urlA), http.getAsync(urlB)];
+    //use resultA
+    //use resultB
+});
+```
+
+You might wonder why not just do this?
+
+```js
+var getData = Promise.coroutine(function* (urlA, urlB) {
+    var resultA = yield http.getAsync(urlA);
+    var resultB = yield http.getAsync(urlB);
+});
+```
+
+The problem with the above is that the requests are not done in parallel. It will completely wait for request A to complete before even starting request B. In the array syntax both requests fire off at the same time in parallel.
+
+<hr>
+
+#####`Promise.coroutine.addYieldHandler(function handler)` -> `void`
+
+By default you can only yield Promises, Thenables and Arrays inside coroutines. You can use this function to add yielding support for arbitrary types.
+
+For example, if you wanted `yield 500` to be same as `yield Promise.delay(500)`:
+
+```js
+Promise.coroutine.addYieldHandler(function(value) {
+     if (typeof value === "number") return Promise.delay(value);
+});
+```
+
+Yield handlers are called when you yield something that is not supported by default. The first yield handler to return a promise or a thenable will be used.
+If no yield handler returns a promise or a thenable then an error is raised.
+
+An example of implementing callback support with `addYieldHandler`:
+
+*This is a demonstration of how powerful the feature is and not the recommended usage. For best performance you need to use `promisifyAll` and yield promises directly.*
+
+```js
+var Promise = require("bluebird");
+var fs = require("fs");
+
+var _ = (function() {
+    var promise = null;
+    Promise.coroutine.addYieldHandler(function(v) {
+        if (v === void 0 && promise != null) {
+            return promise;
+        }
+        promise = null;
+    });
+    return function() {
+        var def = Promise.defer();
+        promise = def.promise;
+        return def.callback;
+    };
+})();
+
+
+var readFileJSON = Promise.coroutine(function* (fileName) {
+   var contents = yield fs.readFile(fileName, "utf8", _());
+   return JSON.parse(contents);
+});
+```
+
+An example of implementing thunks support with `addYieldHandler`:
+
+*This is a demonstration of how powerful the feature is and not the recommended usage. For best performance you need to use `promisifyAll` and yield promises directly.*
+
+```js
+var Promise = require("bluebird");
+var fs = require("fs");
+
+Promise.coroutine.addYieldHandler(function(v) {
+    if (typeof v === "function") {
+        var def = Promise.defer();
+        try { v(def.callback); } catch(e) { def.reject(e); }
+        return def.promise;
+    }
+});
+
+var readFileThunk = function(fileName, encoding) {
+    return function(cb) {
+        return fs.readFile(fileName, encoding, cb);
+    };
+};
+
+var readFileJSON = Promise.coroutine(function* (fileName) {
+   var contents = yield readFileThunk(fileName, "utf8");
+   return JSON.parse(contents);
+});
+```
+
+##Utility
+
+Functions that could potentially be handy in some situations.
+
+#####`.call(String propertyName [, dynamic arg...])` -> `Promise`
+
+This is a convenience method for doing:
+
+```js
+promise.then(function(obj){
+    return obj[propertyName].call(obj, arg...);
+});
+```
+
+<hr>
+
+#####`.get(String propertyName)` -> `Promise`
+
+This is a convenience method for doing:
+
+```js
+promise.then(function(obj){
+    return obj[propertyName];
+});
+```
+
+<hr>
+
+#####`.return(dynamic value)` -> `Promise`
+
+Convenience method for:
+
+```js
+.then(function() {
+   return value;
+});
+```
+
+in the case where `value` doesn't change its value.
+
+That means `value` is bound at the time of calling `.return()` so this will not work as expected:
+
+```js
+function getData() {
+    var data;
+
+    return query().then(function(result) {
+        data = result;
+    }).return(data);
+}
+```
+
+because `data` is `undefined` at the time `.return` is called.
+
+*For compatibility with earlier ECMAScript version, an alias `.thenReturn()` is provided for `.return()`.*
+
+<hr>
+
+#####`.throw(dynamic reason)` -> `Promise`
+
+Convenience method for:
+
+```js
+.then(function() {
+   throw reason;
+});
+```
+
+Same limitations apply as with `.return()`.
+
+*For compatibility with earlier ECMAScript version, an alias `.thenThrow()` is provided for `.throw()`.*
+
+<hr>
+
+#####`.toString()` -> `String`
+
+<hr>
+
+#####`.toJSON()` -> `Object`
+
+This is implicitly called by `JSON.stringify` when serializing the object. Returns a serialized representation of the `Promise`.
+
+<hr>
+
+#####`Promise.noConflict()` -> `Object`
+
+This is relevant to browser environments with no module loader.
+
+Release control of the `Promise` namespace to whatever it was before this library was loaded. Returns a reference to the library namespace so you can attach it to something else.
+
+```html
+<!-- the other promise library must be loaded first -->
+<script type="text/javascript" src="/scripts/other_promise.js"></script>
+<script type="text/javascript" src="/scripts/bluebird_debug.js"></script>
+<script type="text/javascript">
+//Release control right after
+var Bluebird = Promise.noConflict();
+
+//Cast a promise from some other Promise library using the Promise namespace to Bluebird:
+var promise = Bluebird.cast(new Promise());
+</script>
+```
+
+<hr>
+
+#####`Promise.onPossiblyUnhandledRejection(Function handler)` -> `undefined`
+
+Add `handler` as the handler to call when there is a possibly unhandled rejection. The default handler logs the error stack to stderr or `console.error` in browsers.
+
+```html
+Promise.onPossiblyUnhandledRejection(function(e, promise){
+    throw e;
+});
+```
+
+Passing no value or a non-function will have the effect of removing any kind of handling for possibly unhandled rejections.
+
+<hr>
+
+##Collections
+
+Methods of `Promise` instances and core static methods of the Promise class to deal with
+collections of promises or mixed promises and values.
+
+#####`.all()` -> `Promise`
+
+Same as calling [Promise.all\(thisPromise\)](#promiseallarraydynamicpromise-values---promise). With the exception that if this promise is [bound](#binddynamic-thisarg---promise) to a value, the returned promise is bound to that value too.
+
+<hr>
+
+#####`.props()` -> `Promise`
+
+Same as calling [Promise.props\(thisPromise\)](#promisepropsobjectpromise-object---promise). With the exception that if this promise is [bound](#binddynamic-thisarg---promise) to a value, the returned promise is bound to that value too.
+
+<hr>
+
+#####`.settle()` -> `Promise`
+
+Same as calling [Promise.settle\(thisPromise\)](#promisesettlearraydynamicpromise-values---promise). With the exception that if this promise is [bound](#binddynamic-thisarg---promise) to a value, the returned promise is bound to that value too.
+
+<hr>
+
+#####`.any()` -> `Promise`
+
+Same as calling [Promise.any\(thisPromise\)](#promiseanyarraydynamicpromise-values---promise). With the exception that if this promise is [bound](#binddynamic-thisarg---promise) to a value, the returned promise is bound to that value too.
+
+<hr>
+
+#####`.race()` -> `Promise`
+
+Same as calling [Promise.race\(thisPromise\)](#promiseracearraypromise-promises---promise). With the exception that if this promise is [bound](#binddynamic-thisarg---promise) to a value, the returned promise is bound to that value too.
+
+<hr>
+
+
+#####`.some(int count)` -> `Promise`
+
+Same as calling [Promise.some\(thisPromise, count\)](#promisesomearraydynamicpromise-values-int-count---promise). With the exception that if this promise is [bound](#binddynamic-thisarg---promise) to a value, the returned promise is bound to that value too.
+
+<hr>
+
+#####`.spread([Function fulfilledHandler] [, Function rejectedHandler ])` -> `Promise`
+
+Like calling `.then`, but the fulfillment value or rejection reason is assumed to be an array, which is flattened to the formal parameters of the handlers.
+
+```js
+Promise.all([task1, task2, task3]).spread(function(result1, result2, result3){
+
+});
+```
+
+Normally when using `.then` the code would be like:
+
+```js
+Promise.all([task1, task2, task3]).then(function(results){
+    var result1 = results[0];
+    var result2 = results[1];
+    var result3 = results[2];
+});
+```
+
+This is useful when the `results` array contains items that are not conceptually items of the same list.
+
+<hr>
+
+#####`.map(Function mapper)` -> `Promise`
+
+Same as calling [Promise.map\(thisPromise, mapper\)](#promisemaparraydynamicpromise-values-function-mapper---promise). With the exception that if this promise is [bound](#binddynamic-thisarg---promise) to a value, the returned promise is bound to that value too.
+
+<hr>
+
+#####`.reduce(Function reducer [, dynamic initialValue])` -> `Promise`
+
+Same as calling [Promise.reduce\(thisPromise, Function reducer, initialValue\)](#promisereducearraydynamicpromise-values-function-reducer--dynamic-initialvalue---promise). With the exception that if this promise is [bound](#binddynamic-thisarg---promise) to a value, the returned promise is bound to that value too.
+
+<hr>
+
+#####`.filter(Function filterer)` -> `Promise`
+
+Same as calling [`Promise.filter(thisPromise, filterer)`](#promisefilterarraydynamicpromise-values-function-filterer---promise). With the exception that if this promise is [bound](#binddynamic-thisarg---promise) to a value, the returned promise is bound to that value too.
+
+In this example, a list of websites are pinged with 100ms timeout. [`.settle()`](#settle---promise) is used to wait until all pings are either fulfilled or rejected. Then the settled
+list of [`PromiseInspections`](#inspect---promiseinspection) is filtered for those that fulfilled (responded in under 100ms) and [`mapped`](#promisemaparraydynamicpromise-values-function-mapper---promise) to the actual fulfillment value.
+
+```js
+pingWebsitesAsync({timeout: 100}).settle()
+.filter(function(inspection){
+    return inspection.isFulfilled();
+})
+.map(function(inspection){
+    return inspection.value();
+})
+.then(function(websites){
+   //List of website names which answered
+});
+```
+
+The above pattern is actually reusable and can be captured in a method:
+
+```js
+Promise.prototype.settledWithFulfill = function() {
+    return this.settle()
+        .filter(function(inspection){
+            return inspection.isFulfilled();
+        })
+        .map(function(inspection){
+            return inspection.value();
+        });
+};
+```
+
+<hr>
+
+#####`Promise.all(Array<dynamic>|Promise values)` -> `Promise`
+
+Given an array, or a promise of an array, which contains promises (or a mix of promises and values) return a promise that is fulfilled when all the items in the array are fulfilled. The promise's fulfillment value is an array with fulfillment values at respective positions to the original array. If any promise in the array rejects, the returned promise is rejected with the rejection reason.
+
+In this example we create a promise that is fulfilled only when the pictures, comments and tweets are all loaded.
+
+```js
+Promise.all([getPictures(), getComments(), getTweets()]).then(function(results){
+    //Everything loaded and good to go
+    var pictures = results[0];
+    var comments = results[1];
+    var tweets = results[2];
+}).catch(function(e){
+    alertAsync("error when getting your stuff");
+});
+```
+
+See [`.spread()`](#spreadfunction-fulfilledhandler--function-rejectedhandler----promise) for a more convenient way to extract the fulfillment values.
+
+*The original array is not modified. The input array sparsity is retained in the resulting array.*
+
+<hr>
+
+#####`Promise.props(Object|Promise object)` -> `Promise`
+
+Like [`Promise.all`](#promiseallarraydynamic-values---promise) but for object properties instead of array items. Returns a promise that is fulfilled when all the properties of the object are fulfilled. The promise's fulfillment value is an object with fulfillment values at respective keys to the original object. If any promise in the object rejects, the returned promise is rejected with the rejection reason.
+
+If `object` is a trusted `Promise`, then it will be treated as a promise for object rather than for its properties. All other objects are treated for their properties as is returned by `Object.keys` - the object's own enumerable properties.
+
+```js
+Promise.props({
+    pictures: getPictures(),
+    comments: getComments(),
+    tweets: getTweets()
+}).then(function(result){
+    console.log(result.tweets, result.pictures, result.comments);
+});
+```
+
+Note that if you have no use for the result object other than retrieving the properties, it is more convenient to use [`Promise.all`](#promiseallarraydynamic-values---promise) and [`.spread()`](#spreadfunction-fulfilledhandler--function-rejectedhandler----promise):
+
+```js
+Promise.all([getPictures(), getComments(), getTweets()])
+.spread(function(pictures, comments, tweets) {
+    console.log(pictures, comments, tweets);
+});
+```
+
+*The original object is not modified.*
+
+<hr>
+
+#####`Promise.settle(Array<dynamic>|Promise values)` -> `Promise`
+
+Given an array, or a promise of an array, which contains promises (or a mix of promises and values) return a promise that is fulfilled when all the items in the array are either fulfilled or rejected. The fulfillment value is an array of [`PromiseInspection`](#inspect---promiseinspection) instances at respective positions in relation to the input array.
+
+*The original array is not modified. The input array sparsity is retained in the resulting array.*
+
+<hr>
+
+#####`Promise.any(Array<dynamic>|Promise values)` -> `Promise`
+
+Like [`Promise.some\(\)`](#someint-count---promise), with 1 as `count`. However, if the promise fulfills, the fulfillment value is not an array of 1 but the value directly.
+
+<hr>
+
+#####`Promise.race(Array|Promise promises)` -> `Promise`
+
+Given an array, or a promise of an array, which contains promises (or a mix of promises and values) return a promise that is fulfilled or rejected as soon as a promise in the array is fulfilled or rejected with the respective rejection reason or fulfillment value.
+
+Example of implementing a timeout in terms of `Promise.race`:
+
+```js
+var Promise = require("bluebird");
+var fs = Promise.promisifyAll(require("fs"));
+
+function delay(ms) {
+    return new Promise(function (v) {
+        setTimeout(v, ms);
+    });
+}
+
+function timeout(promise, time) {
+    var timeout = delay(time).then(function () {
+        throw new Promise.TimeoutError("Operation timed out after " + time + " ms");
+    });
+
+    return Promise.race([promise, timeout]);
+}
+
+timeout(fs.readFileAsync("slowfile.txt"), 300).then(function (contents) {
+    console.log("Here are the contents", contents);
+}).
+catch(Promise.TimeoutError, function (e) {
+    console.error("Sorry retrieving file took too long");
+});
+```
+
+**Note** If you pass empty array or a sparse array with no values, or a promise/thenable for such, it will be forever pending.
+
+<hr>
+
+#####`Promise.some(Array<dynamic>|Promise values, int count)` -> `Promise`
+
+Initiate a competetive race between multiple promises or values (values will become immediately fulfilled promises). When `count` amount of promises have been fulfilled, the returned promise is fulfilled with an array that contains the fulfillment values of the winners in order of resolution.
+
+This example pings 4 nameservers, and logs the fastest 2 on console:
+
+```js
+Promise.some([
+    ping("ns1.example.com"),
+    ping("ns2.example.com"),
+    ping("ns3.example.com"),
+    ping("ns4.example.com")
+], 2).spread(function(first, second) {
+    console.log(first, second);
+});
+```
+
+If too many promises are rejected so that the promise can never become fulfilled, it will be immediately rejected with an array of rejection reasons in the order they were thrown in.
+
+*The original array is not modified.*
+
+<hr>
+
+#####`Promise.join([dynamic value...])` -> `Promise`
+
+Like [`Promise.all\(\)`](#promiseallarraydynamic-values---promise) but instead of having to pass an array, the array is generated from the passed variadic arguments.
+
+So instead of:
+
+```js
+Promise.all([a, b]).spread(function(aResult, bResult) {
+
+});
+```
+
+You can do:
+
+```js
+Promise.join(a, b).spread(function(aResult, bResult) {
+
+});
+```
+
+<hr>
+
+#####`Promise.map(Array<dynamic>|Promise values, Function mapper)` -> `Promise`
+
+Map an array, or a promise of an array, which contains a promises (or a mix of promises and values) with the given `mapper` function with the signature `(item, index, arrayLength)` where `item` is the resolved value of a respective promise in the input array. If any promise in the input array is rejected the returned promise is rejected as well.
+
+If the `mapper` function returns promises or thenables, the returned promise will wait for all the mapped results to be resolved as well.
+
+*(TODO: an example where this is useful)*
+
+*The original array is not modified.*
+
+<hr>
+
+#####`Promise.reduce(Array<dynamic>|Promise values, Function reducer [, dynamic initialValue])` -> `Promise`
+
+Reduce an array, or a promise of an array, which contains a promises (or a mix of promises and values) with the given `reducer` function with the signature `(total, current, index, arrayLength)` where `item` is the resolved value of a respective promise in the input array. If any promise in the input array is rejected the returned promise is rejected as well.
+
+If the reducer function returns a promise or a thenable, the result for the promise is awaited for before continuing with next iteration.
+
+Read given files sequentially while summing their contents as an integer. Each file contains just the text `10`.
+
+```js
+Promise.reduce(["file1.txt", "file2.txt", "file3.txt"], function(total, fileName) {
+    return fs.readFileAsync(fileName, "utf8").then(function(contents) {
+        return total + parseInt(contents, 10);
+    });
+}, 0).then(function(total) {
+    //Total is 30
+});
+```
+
+*The original array is not modified. If `intialValue` is `undefined` (or a promise that resolves to `undefined`) and the array contains only 1 item, the callback will not be called and `undefined` is returned. If the array is empty, the callback will not be called and `initialValue` is returned (which may be `undefined`).*
+
+<hr>
+
+#####`Promise.filter(Array<dynamic>|Promise values, Function filterer)` -> `Promise`
+
+Filter an array, or a promise of an array, which contains a promises (or a mix of promises and values) with the given `filterer` function with the signature `(item, index, arrayLength)` where `item` is the resolved value of a respective promise in the input array. If any promise in the input array is rejected the returned promise is rejected as well.
+
+The return values from the filtered functions are coerced to booleans, with the exception of promises and thenables which are awaited for their eventual result.
+
+[See the instance method `.filter()` for an example.](#filterfunction-filterer---promise)
+
+*The original array is not modified.
+
+<hr>
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/CONTRIBUTING.md b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/CONTRIBUTING.md
new file mode 100644
index 0000000..7042bcb
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/CONTRIBUTING.md
@@ -0,0 +1,68 @@
+# Contributing to bluebird
+
+1. [Directory structure](#directory-structure)
+2. [Style guide](#style-guide)
+3. [Scripts and macros](#scripts-and-macros)
+4. [JSHint](#jshint)
+5. [Testing](#testing)
+
+## Directory structure
+
+- `/benchmark` contains benchmark scripts and stats of benchmarks
+
+- `/browser` contains scripts and output for browser testing environment
+
+- `/js` contains automatically generated build output. **NOTE** never commit any changes to these files to git.
+
+    - `/js/browser` contains a file suitable for use in browsers
+    - `/js/main` contains the main build to be used with node. The npm package points to /js/main/bluebird.js
+    - `/js/debug` contains the debug build to be used with node. Used when running tests
+    - `/js/zalgo` contains the zalgo build not to be used by any mortals.
+
+- `/node_modules` contains development dependencies such as grunt
+
+- `/src` contains the source code
+
+- `/test/mocha` contains tests using the mocha testing framework
+
+## Scripts and macros
+
+Scripts and macros are necessary for the code the code to remain readable and performant. For example, there is no way to turn the `arguments` object into an array without using a build step macro unless you want to compromise readability or performance.
+
+`/ast_passes.js` contains functions called ast passes that will parse input source code into an AST, modify it in some way and spit out new source code with the changes reflected.
+
+`/src/constants.js` contains declarations for constants that will be inlined in the resulting code during in the build step. JavaScript lacks a way to express constants, particularly if you are expecting the performance implications.
+
+`/Gruntfile.js` contains task definitions to be used with the Grunt build framework. It for example sets up source code transformations.
+
+`/bench` a bash script to run benchmarks.
+
+`/mocharun.js` a hack script to make mocha work when running multiple tests in parallel processes
+
+## JSHint
+
+Due to JSHint globals being dynamic, the JSHint rules are declared in `/Gruntfile.js`.
+
+## Style guide
+
+Use the same style as is used in the surrounding code.
+
+###Whitespace
+
+- No more than 80 columns per line
+- 4 space indentation
+- No trailing whitespace
+- LF at end of files
+- Curly braces can be left out of single statement `if/else/else if`s when it is obvious there will never be multiple statements such as null check at the top of a function for an early return.
+- Add an additional new line between logical sections of code.
+
+###Variables
+
+- Use multiple `var` statements instead of a single one with comma separator. Do not declare variables until you need them.
+
+###Equality and type checks
+
+- Always use `===` except when checking for null or undefined. To check for null or undefined, use `x == null`.
+- For checks that can be done with `typeof`: do not make helper functions, save results of `typeof` to a variable or make the type string a non-constant. Always write the check in the form `typeof expression === "constant string"` even if it feels like repeating yourself.
+
+## Testing
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/Gruntfile.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/Gruntfile.js
new file mode 100644
index 0000000..77fc162
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/Gruntfile.js
@@ -0,0 +1,725 @@
+"use strict";
+Error.stackTraceLimit = 100;
+var astPasses = require("./ast_passes.js");
+var node11 = parseInt(process.versions.node.split(".")[1], 10) >= 11;
+var Q = require("q");
+Q.longStackSupport = true;
+
+module.exports = function( grunt ) {
+    var isCI = !!grunt.option("ci");
+
+
+    function getBrowsers() {
+        //Terse format to generate the verbose format required by sauce
+        var browsers = {
+            "internet explorer|WIN8": ["10"],
+            "internet explorer|WIN8.1": ["11"],
+            "firefox|Windows 7": ["3.5", "3.6", "4", "25"],
+            "firefox|WIN8.1": null,
+            "chrome|Windows 7": null,
+            "safari|Windows 7": ["5"],
+            "iphone|OS X 10.8": ["6.0"]
+        };
+
+        var ret = [];
+        for( var browserAndPlatform in browsers) {
+            var split = browserAndPlatform.split("|");
+            var browser = split[0];
+            var platform = split[1];
+            var versions = browsers[browserAndPlatform];
+            if( versions != null ) {
+                for( var i = 0, len = versions.length; i < len; ++i ) {
+                    ret.push({
+                        browserName: browser,
+                        platform: platform,
+                        version: versions[i]
+                    });
+                }
+            }
+            else {
+                ret.push({
+                    browserName: browser,
+                    platform: platform
+                });
+            }
+        }
+        return ret;
+    }
+
+
+    var optionalModuleDependencyMap = {
+        "timers.js": ['Promise', 'INTERNAL'],
+        "any.js": ['Promise', 'Promise$_CreatePromiseArray', 'PromiseArray'],
+        "race.js": ['Promise', 'INTERNAL'],
+        "call_get.js": ['Promise'],
+        "filter.js": ['Promise', 'Promise$_CreatePromiseArray', 'PromiseArray', 'apiRejection'],
+        "generators.js": ['Promise', 'apiRejection', 'INTERNAL'],
+        "map.js": ['Promise', 'Promise$_CreatePromiseArray', 'PromiseArray', 'apiRejection'],
+        "nodeify.js": ['Promise'],
+        "promisify.js": ['Promise', 'INTERNAL'],
+        "props.js": ['Promise', 'PromiseArray'],
+        "reduce.js": ['Promise', 'Promise$_CreatePromiseArray', 'PromiseArray', 'apiRejection', 'INTERNAL'],
+        "settle.js": ['Promise', 'Promise$_CreatePromiseArray', 'PromiseArray'],
+        "some.js": ['Promise', 'Promise$_CreatePromiseArray', 'PromiseArray', 'apiRejection'],
+        "progress.js": ['Promise', 'isPromiseArrayProxy'],
+        "cancel.js": ['Promise', 'INTERNAL'],
+        "synchronous_inspection.js": ['Promise']
+
+    };
+
+    var optionalModuleRequireMap = {
+        "timers.js": true,
+        "race.js": true,
+        "any.js": true,
+        "call_get.js": true,
+        "filter.js": true,
+        "generators.js": true,
+        "map.js": true,
+        "nodeify.js": true,
+        "promisify.js": true,
+        "props.js": true,
+        "reduce.js": true,
+        "settle.js": true,
+        "some.js": true,
+        "progress.js": true,
+        "cancel.js": true,
+        "synchronous_inspection.js": true
+
+    };
+
+    function getOptionalRequireCode( srcs ) {
+        return srcs.reduce(function(ret, cur, i){
+            if( optionalModuleRequireMap[cur] ) {
+                ret += "require('./"+cur+"')("+ optionalModuleDependencyMap[cur] +");\n";
+            }
+            return ret;
+        }, "") + "\nPromise.prototype = Promise.prototype;\nreturn Promise;\n";
+    }
+
+    function getBrowserBuildHeader( sources ) {
+        var header = "/**\n * bluebird build version " + gruntConfig.pkg.version + "\n";
+        var enabledFeatures = ["core"];
+        var disabledFeatures = [];
+        featureLoop: for( var key in optionalModuleRequireMap ) {
+            for( var i = 0, len = sources.length; i < len; ++i ) {
+                var source = sources[i];
+                if( source.fileName === key ) {
+                    enabledFeatures.push( key.replace( ".js", "") );
+                    continue featureLoop;
+                }
+            }
+            disabledFeatures.push( key.replace( ".js", "") );
+        }
+
+        header += ( " * Features enabled: " + enabledFeatures.join(", ") + "\n" );
+
+        if( disabledFeatures.length ) {
+            header += " * Features disabled: " + disabledFeatures.join(", ") + "\n";
+        }
+        header += "*/\n";
+        return header;
+    }
+
+    function applyOptionalRequires( src, optionalRequireCode ) {
+        return src.replace( /};([^}]*)$/, optionalRequireCode + "\n};$1");
+    }
+
+    var CONSTANTS_FILE = './src/constants.js';
+    var BUILD_DEBUG_DEST = "./js/debug/bluebird.js";
+
+    var license;
+    function getLicense() {
+        if( !license ) {
+            var fs = require("fs");
+            var text = fs.readFileSync("LICENSE", "utf8");
+            text = text.split("\n").map(function(line, index){
+                return " * " + line;
+            }).join("\n")
+            license = "/**\n" + text + "\n */\n";
+        }
+        return license
+    }
+
+    var preserved;
+    function getLicensePreserve() {
+        if( !preserved ) {
+            var fs = require("fs");
+            var text = fs.readFileSync("LICENSE", "utf8");
+            text = text.split("\n").map(function(line, index){
+                if( index === 0 ) {
+                    return " * @preserve " + line;
+                }
+                return " * " + line;
+            }).join("\n")
+            preserved = "/**\n" + text + "\n */\n";
+        }
+        return preserved;
+    }
+
+    function writeFile( dest, content ) {
+        grunt.file.write( dest, content );
+        grunt.log.writeln('File "' + dest + '" created.');
+    }
+
+    function writeFileAsync( dest, content ) {
+        var fs = require("fs");
+        return Q.nfcall(fs.writeFile, dest, content).then(function(){
+            grunt.log.writeln('File "' + dest + '" created.');
+        });
+    }
+
+    var gruntConfig = {};
+
+    var getGlobals = function() {
+        var fs = require("fs");
+        var file = "./src/constants.js";
+        var contents = fs.readFileSync(file, "utf8");
+        var rconstantname = /CONSTANT\(\s*([^,]+)/g;
+        var m;
+        var globals = {
+            Error: true,
+            args: true,
+            INLINE_SLICE: false,
+            TypeError: true,
+            RangeError: true,
+            __DEBUG__: false,
+            __BROWSER__: false,
+            process: true,
+            "console": false,
+            "require": false,
+            "module": false,
+            "define": false
+        };
+        while( ( m = rconstantname.exec( contents ) ) ) {
+            globals[m[1]] = false;
+        }
+        return globals;
+    }
+
+    gruntConfig.pkg = grunt.file.readJSON("package.json");
+
+    gruntConfig.jshint = {
+        all: {
+            options: {
+                globals: getGlobals(),
+
+                "bitwise": false,
+                "camelcase": true,
+                "curly": true,
+                "eqeqeq": true,
+                "es3": true,
+                "forin": true,
+                "immed": true,
+                "latedef": false,
+                "newcap": true,
+                "noarg": true,
+                "noempty": true,
+                "nonew": true,
+                "plusplus": false,
+                "quotmark": "double",
+                "undef": true,
+                "unused": true,
+                "strict": false,
+                "trailing": true,
+                "maxparams": 6,
+                "maxlen": 80,
+
+                "asi": false,
+                "boss": true,
+                "eqnull": true,
+                "evil": true,
+                "expr": false,
+                "funcscope": false,
+                "globalstrict": false,
+                "lastsemic": false,
+                "laxcomma": false,
+                "laxbreak": false,
+                "loopfunc": true,
+                "multistr": true,
+                "proto": false,
+                "scripturl": true,
+                "smarttabs": false,
+                "shadow": true,
+                "sub": true,
+                "supernew": false,
+                "validthis": true,
+
+                "browser": true,
+                "jquery": true,
+                "devel": true,
+
+
+                '-W014': true,
+                '-W116': true,
+                '-W106': true,
+                '-W064': true,
+                '-W097': true
+            },
+
+            files: {
+                src: [
+                    "./src/finally.js",
+                    "./src/direct_resolve.js",
+                    "./src/synchronous_inspection.js",
+                    "./src/thenables.js",
+                    "./src/progress.js",
+                    "./src/cancel.js",
+                    "./src/any.js",
+                    "./src/race.js",
+                    "./src/call_get.js",
+                    "./src/filter.js",
+                    "./src/generators.js",
+                    "./src/map.js",
+                    "./src/nodeify.js",
+                    "./src/promisify.js",
+                    "./src/props.js",
+                    "./src/reduce.js",
+                    "./src/settle.js",
+                    "./src/some.js",
+                    "./src/util.js",
+                    "./src/schedule.js",
+                    "./src/queue.js",
+                    "./src/errors.js",
+                    "./src/captured_trace.js",
+                    "./src/async.js",
+                    "./src/catch_filter.js",
+                    "./src/promise.js",
+                    "./src/promise_array.js",
+                    "./src/settled_promise_array.js",
+                    "./src/some_promise_array.js",
+                    "./src/properties_promise_array.js",
+                    "./src/promise_inspection.js",
+                    "./src/promise_resolver.js",
+                    "./src/promise_spawn.js"
+                ]
+            }
+        }
+    };
+
+    if( !isCI ) {
+        gruntConfig.jshint.all.options.reporter = require("jshint-stylish");
+    }
+
+    gruntConfig.connect = {
+        server: {
+            options: {
+                base: "./browser",
+                port: 9999
+            }
+        }
+    };
+
+    gruntConfig.watch = {};
+
+    gruntConfig["saucelabs-mocha"] = {
+        all: {
+            options: {
+                urls: ["http://127.0.0.1:9999/index.html"],
+                tunnelTimeout: 5,
+                build: process.env.TRAVIS_JOB_ID,
+                concurrency: 3,
+                browsers: getBrowsers(),
+                testname: "mocha tests",
+                tags: ["master"]
+            }
+        }
+    };
+
+    gruntConfig.bump = {
+      options: {
+        files: ['package.json'],
+        updateConfigs: [],
+        commit: true,
+        commitMessage: 'Release v%VERSION%',
+        commitFiles: ['-a'],
+        createTag: true,
+        tagName: 'v%VERSION%',
+        tagMessage: 'Version %VERSION%',
+        false: true,
+        pushTo: 'master',
+        gitDescribeOptions: '--tags --always --abbrev=1 --dirty=-d' // options to use with '$ git describe'
+      }
+    };
+
+    grunt.initConfig(gruntConfig);
+    grunt.loadNpmTasks("grunt-contrib-connect");
+    grunt.loadNpmTasks("grunt-saucelabs");
+    grunt.loadNpmTasks('grunt-contrib-jshint');
+    grunt.loadNpmTasks('grunt-contrib-watch');
+    grunt.loadNpmTasks('grunt-contrib-concat');
+
+    function runIndependentTest( file, cb , env) {
+        var fs = require("fs");
+        var path = require("path");
+        var sys = require('sys');
+        var spawn = require('child_process').spawn;
+        var p = path.join(process.cwd(), "test");
+
+        var stdio = [
+            'ignore',
+            grunt.option("verbose")
+                ? process.stdout
+                : 'ignore',
+            process.stderr
+        ];
+        var flags = node11 ? ["--harmony-generators"] : [];
+        flags.push("--allow-natives-syntax");
+        if( file.indexOf( "mocha/") > -1 || file === "aplus.js" ) {
+            var node = spawn(typeof node11 === "string" ? node11 : 'node',
+                flags.concat(["../mocharun.js", file]),
+                {cwd: p, stdio: stdio, env: env});
+        }
+        else {
+            var node = spawn('node', flags.concat(["./"+file]),
+                             {cwd: p, stdio: stdio, env:env});
+        }
+        node.on('exit', exit );
+
+        function exit( code ) {
+            if( code !== 0 ) {
+                cb(new Error("process didn't exit normally. Code: " + code));
+            }
+            else {
+                cb(null);
+            }
+        }
+
+
+    }
+
+    function buildMain( sources, optionalRequireCode ) {
+        var fs = require("fs");
+        var Q = require("q");
+        var root = cleanDirectory("./js/main/");
+
+        return Q.all(sources.map(function( source ) {
+            var src = astPasses.removeAsserts( source.sourceCode, source.fileName );
+            src = astPasses.inlineExpansion( src, source.fileName );
+            src = astPasses.expandConstants( src, source.fileName );
+            src = src.replace( /__DEBUG__/g, "false" );
+            src = src.replace( /__BROWSER__/g, "false" );
+            if( source.fileName === "promise.js" ) {
+                src = applyOptionalRequires( src, optionalRequireCode );
+            }
+            var path = root + source.fileName;
+            return writeFileAsync(path, src);
+        }));
+    }
+
+    function buildDebug( sources, optionalRequireCode ) {
+        var fs = require("fs");
+        var Q = require("q");
+        var root = cleanDirectory("./js/debug/");
+
+        return Q.nfcall(require('mkdirp'), root).then(function(){
+            return Q.all(sources.map(function( source ) {
+                var src = astPasses.expandAsserts( source.sourceCode, source.fileName );
+                src = astPasses.inlineExpansion( src, source.fileName );
+                src = astPasses.expandConstants( src, source.fileName );
+                src = src.replace( /__DEBUG__/g, "true" );
+                src = src.replace( /__BROWSER__/g, "false" );
+                if( source.fileName === "promise.js" ) {
+                    src = applyOptionalRequires( src, optionalRequireCode );
+                }
+                var path = root + source.fileName;
+                return writeFileAsync(path, src);
+            }));
+        });
+    }
+
+    function buildZalgo( sources, optionalRequireCode ) {
+        var fs = require("fs");
+        var Q = require("q");
+        var root = cleanDirectory("./js/zalgo/");
+
+        return Q.all(sources.map(function( source ) {
+            var src = astPasses.removeAsserts( source.sourceCode, source.fileName );
+            src = astPasses.inlineExpansion( src, source.fileName );
+            src = astPasses.expandConstants( src, source.fileName );
+            src = astPasses.asyncConvert( src, "async", "invoke", source.fileName);
+            src = src.replace( /__DEBUG__/g, "false" );
+            src = src.replace( /__BROWSER__/g, "false" );
+            if( source.fileName === "promise.js" ) {
+                src = applyOptionalRequires( src, optionalRequireCode );
+            }
+            var path = root + source.fileName;
+            return writeFileAsync(path, src);
+        }));
+    }
+
+    function buildBrowser( sources ) {
+        var fs = require("fs");
+        var browserify = require("browserify");
+        var b = browserify("./js/main/bluebird.js");
+        var dest = "./js/browser/bluebird.js";
+
+        var header = getBrowserBuildHeader( sources );
+
+        return Q.nbind(b.bundle, b)({
+                detectGlobals: false,
+                standalone: "Promise"
+        }).then(function(src) {
+            return writeFileAsync( dest,
+                getLicensePreserve() + src )
+        }).then(function() {
+            return Q.nfcall(fs.readFile, dest, "utf8" );
+        }).then(function( src ) {
+            src = header + src;
+            return Q.nfcall(fs.writeFile, dest, src );
+        });
+    }
+
+    function cleanDirectory(dir) {
+        if (isCI) return dir;
+        var fs = require("fs");
+        require("rimraf").sync(dir);
+        fs.mkdirSync(dir);
+        return dir;
+    }
+
+    function getOptionalPathsFromOption( opt ) {
+        opt = (opt + "").toLowerCase().split(/\s+/g);
+        return optionalPaths.filter(function(v){
+            v = v.replace("./src/", "").replace( ".js", "" ).toLowerCase();
+            return opt.indexOf(v) > -1;
+        });
+    }
+
+    var optionalPaths = [
+        "./src/timers.js",
+        "./src/synchronous_inspection.js",
+        "./src/any.js",
+        "./src/race.js",
+        "./src/call_get.js",
+        "./src/filter.js",
+        "./src/generators.js",
+        "./src/map.js",
+        "./src/nodeify.js",
+        "./src/promisify.js",
+        "./src/props.js",
+        "./src/reduce.js",
+        "./src/settle.js",
+        "./src/some.js",
+        "./src/progress.js",
+        "./src/cancel.js"
+    ];
+
+    var mandatoryPaths = [
+        "./src/finally.js",
+        "./src/es5.js",
+        "./src/bluebird.js",
+        "./src/thenables.js",
+        "./src/assert.js",
+        "./src/global.js",
+        "./src/util.js",
+        "./src/schedule.js",
+        "./src/queue.js",
+        "./src/errors.js",
+        "./src/errors_api_rejection.js",
+        "./src/captured_trace.js",
+        "./src/async.js",
+        "./src/catch_filter.js",
+        "./src/promise.js",
+        "./src/promise_array.js",
+        "./src/settled_promise_array.js",
+        "./src/some_promise_array.js",
+        "./src/properties_promise_array.js",
+        "./src/promise_inspection.js",
+        "./src/promise_resolver.js",
+        "./src/promise_spawn.js",
+        "./src/direct_resolve.js"
+    ];
+
+
+
+    function build( paths, isCI ) {
+        var fs = require("fs");
+        astPasses.readConstants(fs.readFileSync(CONSTANTS_FILE, "utf8"), CONSTANTS_FILE);
+        if( !paths ) {
+            paths = optionalPaths.concat(mandatoryPaths);
+        }
+        var optionalRequireCode = getOptionalRequireCode(paths.map(function(v) {
+            return v.replace("./src/", "");
+        }));
+
+        var Q = require("q");
+
+        var promises = [];
+        var sources = paths.map(function(v){
+            var promise = Q.nfcall(fs.readFile, v, "utf8");
+            promises.push(promise);
+            var ret = {};
+
+            ret.fileName = v.replace("./src/", "");
+            ret.sourceCode = promise.then(function(v){
+                ret.sourceCode = v;
+            });
+            return ret;
+        });
+
+        //Perform common AST passes on all builds
+        return Q.all(promises.slice()).then(function(){
+            sources.forEach( function( source ) {
+                var src = source.sourceCode
+                src = astPasses.removeComments(src, source.fileName);
+                src = getLicense() + src;
+                source.sourceCode = src;
+            });
+
+            if( isCI ) {
+                return buildDebug( sources, optionalRequireCode );
+            }
+            else {
+                return Q.all([
+                    buildMain( sources, optionalRequireCode ).then( function() {
+                        return buildBrowser( sources );
+                    }),
+                    buildDebug( sources, optionalRequireCode ),
+                    buildZalgo( sources, optionalRequireCode )
+                ]);
+            }
+        });
+    }
+
+    String.prototype.contains = function String$contains( str ) {
+        return this.indexOf( str ) >= 0;
+    };
+
+    function isSlowTest( file ) {
+        return file.contains("2.3.3") ||
+            file.contains("bind") ||
+            file.contains("unhandled_rejections");
+    }
+
+    function testRun( testOption ) {
+        var fs = require("fs");
+        var path = require("path");
+        var done = this.async();
+
+        var totalTests = 0;
+        var testsDone = 0;
+        function testDone() {
+            testsDone++;
+            if( testsDone >= totalTests ) {
+                done();
+            }
+        }
+        var files;
+        if( testOption === "aplus" ) {
+            files = fs.readdirSync("test/mocha").filter(function(f){
+                return /^\d+\.\d+\.\d+/.test(f);
+            }).map(function( f ){
+                return "mocha/" + f;
+            });
+        }
+        else {
+            files = testOption === "all"
+                ? fs.readdirSync('test')
+                    .concat(fs.readdirSync('test/mocha')
+                        .map(function(fileName){
+                            return "mocha/" + fileName
+                        })
+                    )
+                : [testOption + ".js" ];
+
+
+            if( testOption !== "all" &&
+                !fs.existsSync( "./test/" + files[0] ) ) {
+                files[0] = "mocha/" + files[0];
+            }
+        }
+        files = files.filter(function(fileName){
+            if( !node11 && fileName.indexOf("generator") > -1 ) {
+                return false;
+            }
+            return /\.js$/.test(fileName);
+        }).map(function(f){
+            return f.replace( /(\d)(\d)(\d)/, "$1.$2.$3" );
+        });
+
+
+        var slowTests = files.filter(isSlowTest);
+        files = files.filter(function(file){
+            return !isSlowTest(file);
+        });
+
+        function runFile(file) {
+            totalTests++;
+            grunt.log.writeln("Running test " + file );
+            var env = undefined;
+            if (file.indexOf("bluebird-debug-env-flag") >= 0) {
+                env = Object.create(process.env);
+                env["BLUEBIRD_DEBUG"] = true;
+            }
+            runIndependentTest(file, function(err) {
+                if( err ) throw new Error(err + " " + file + " failed");
+                grunt.log.writeln("Test " + file + " succeeded");
+                testDone();
+                if( files.length > 0 ) {
+                    runFile( files.shift() );
+                }
+            }, env);
+        }
+
+        slowTests.forEach(runFile);
+
+        var maxParallelProcesses = 10;
+        var len = Math.min( files.length, maxParallelProcesses );
+        for( var i = 0; i < len; ++i ) {
+            runFile( files.shift() );
+        }
+    }
+
+    grunt.registerTask( "build", function() {
+
+        var done = this.async();
+        var features = grunt.option("features");
+        var paths = null;
+        if( features ) {
+            paths = getOptionalPathsFromOption( features ).concat( mandatoryPaths );
+        }
+
+        build( paths, isCI ).then(function() {
+            done();
+        }).catch(function(e) {
+            if( e.fileName && e.stack ) {
+                console.log(e.scriptSrc);
+                var stack = e.stack.split("\n");
+                stack[0] = stack[0] + " " + e.fileName;
+                console.error(stack.join("\n"));
+                if (!grunt.option("verbose")) {
+                    console.error("use --verbose to see the source code");
+                }
+
+            }
+            else {
+                console.error(e.stack);
+            }
+            done(false);
+        });
+    });
+
+    grunt.registerTask( "testrun", function(){
+        var testOption = grunt.option("run");
+        var node11path = grunt.option("node11");
+
+        if (typeof node11path === "string" && node11path) {
+            node11 = node11path;
+        }
+
+        if( !testOption ) testOption = "all";
+        else {
+            testOption = ("" + testOption);
+            testOption = testOption
+                .replace( /\.js$/, "" )
+                .replace( /[^a-zA-Z0-9_-]/g, "" );
+        }
+        testRun.call( this, testOption );
+    });
+
+    grunt.registerTask( "test", ["jshint", "build", "testrun"] );
+    grunt.registerTask( "test-browser", ["connect", "saucelabs-mocha"]);
+    grunt.registerTask( "default", ["jshint", "build"] );
+    grunt.registerTask( "dev", ["connect", "watch"] );
+
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/LICENSE b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/LICENSE
new file mode 100644
index 0000000..9ed7b98
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2014 Petka Antonov
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:</p>
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/README.md b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/README.md
new file mode 100644
index 0000000..7d4d1ae
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/README.md
@@ -0,0 +1,696 @@
+[![Build Status](https://travis-ci.org/petkaantonov/bluebird.png?branch=master)](https://travis-ci.org/petkaantonov/bluebird)
+
+<a href="http://promisesaplus.com/">
+    <img src="http://promisesaplus.com/assets/logo-small.png" alt="Promises/A+ logo"
+         title="Promises/A+ 1.0 compliant" align="right" />
+</a>
+
+#Introduction
+
+Bluebird is a fully featured [promise](#what-are-promises-and-why-should-i-use-them) library with focus on innovative features and performance
+
+#Topics
+
+- [Features](#features)
+- [Quick start](#quick-start)
+- [API Reference and examples](https://github.com/petkaantonov/bluebird/blob/master/API.md)
+- [What are promises and why should I use them?](#what-are-promises-and-why-should-i-use-them)
+- [Questions and issues](#questions-and-issues)
+- [Error handling](#error-handling)
+- [Development](#development)
+    - [Testing](#testing)
+    - [Benchmarking](#benchmarks)
+    - [Custom builds](#custom-builds)
+    - [For library authors](#for-library-authors)
+- [What is the sync build?](#what-is-the-sync-build)
+- [License](#license)
+- [Snippets for common problems](https://github.com/petkaantonov/bluebird/wiki/Snippets)
+- [Promise anti-patterns](https://github.com/petkaantonov/bluebird/wiki/Promise-anti-patterns)
+- [Changelog](https://github.com/petkaantonov/bluebird/blob/master/changelog.md)
+- [Optimization guide](#optimization-guide)
+
+#Features:
+
+- [Promises A+ 2.0.2](http://promisesaplus.com)
+- [Cancellation](https://github.com/promises-aplus)
+- [Progression](https://github.com/promises-aplus/progress-spec)
+- [Synchronous inspection](https://github.com/promises-aplus/synchronous-inspection-spec)
+- [`.bind`](https://github.com/petkaantonov/bluebird/blob/master/API.md#binddynamic-thisarg---promise)
+- [Complete parallel for C# 5.0 async and await](https://github.com/petkaantonov/bluebird/blob/master/API.md#promisecoroutinegeneratorfunction-generatorfunction---function)
+- [Collection methods](https://github.com/petkaantonov/bluebird/blob/master/API.md#collections) such as All, any, some, settle, map, filter, reduce, spread, join, race...
+- [Practical debugging solutions](#error-handling) such as unhandled rejection reporting, typed catches, catching only what you expect and very long, relevant stack traces without losing perf
+- [Sick performance](https://github.com/petkaantonov/bluebird/tree/master/benchmark/stats)
+
+Passes [AP2](https://github.com/petkaantonov/bluebird/tree/master/test/mocha), [AP3](https://github.com/petkaantonov/bluebird/tree/master/test/mocha), [Cancellation](https://github.com/petkaantonov/bluebird/blob/master/test/mocha/cancel.js), [Progress](https://github.com/petkaantonov/bluebird/blob/master/test/mocha/q_progress.js) tests and more. See [testing](#testing).
+
+<hr>
+
+#Quick start
+
+##Node.js
+
+    npm install bluebird
+
+Then:
+
+```js
+var Promise = require("bluebird");
+```
+
+##Browsers
+
+Download the [bluebird.js](https://github.com/petkaantonov/bluebird/tree/master/js/browser) file. And then use a script tag:
+
+```html
+<script type="text/javascript" src="/scripts/bluebird.js"></script>
+```
+
+The global variable `Promise` becomes available after the above script tag.
+
+####Browser support
+
+Browsers that [implement ECMA-262, edition 3](http://en.wikipedia.org/wiki/Ecmascript#Implementations) and later are supported.
+
+[![Selenium Test Status](https://saucelabs.com/browser-matrix/petka_antonov.svg)](https://saucelabs.com/u/petka_antonov)
+
+*IE7 and IE8 had to be removed from tests due to SauceLabs bug but are supported and pass all tests*
+
+**Note** that in ECMA-262, edition 3 (IE7, IE8 etc) it is not possible to use methods that have keyword names like `.catch` and `.finally`. The [API documentation](https://github.com/petkaantonov/bluebird/blob/master/API.md) always lists a compatible alternative name that you can use if you need to support these browsers. For example `.catch` is replaced with `.caught` and `.finally` with `.lastly`.
+
+Also, [long stack trace](https://github.com/petkaantonov/bluebird/blob/master/API.md#promiselongstacktraces---void) support is only available in Chrome and Firefox.
+
+<sub>Previously bluebird required es5-shim.js and es5-sham.js to support Edition 3 - these are **no longer required** as of **0.10.4**.</sub>
+
+After quick start, see [API Reference and examples](https://github.com/petkaantonov/bluebird/blob/master/API.md)
+
+<hr>
+
+#What are promises and why should I use them?
+
+You should use promises to turn this:
+
+```js
+readFile("file.json", function(err, val) {
+    if( err ) {
+        console.error("unable to read file");
+    }
+    else {
+        try {
+            val = JSON.parse(val);
+            console.log(val.success);
+        }
+        catch( e ) {
+            console.error("invalid json in file");
+        }
+    }
+});
+```
+
+Into this:
+
+```js
+readFile("file.json").then(JSON.parse).then(function(val) {
+    console.log(val.success);
+})
+.catch(SyntaxError, function(e) {
+    console.error("invalid json in file");
+})
+.catch(function(e){
+    console.error("unable to read file")
+});
+```
+
+Actually you might notice the latter has a lot in common with code that would do the same using synchronous I/O:
+
+```js
+try {
+    var val = JSON.parse(readFile("file.json"));
+    console.log(val.success);
+}
+//Syntax actually not supported in JS but drives the point
+catch(SyntaxError e) {
+    console.error("invalid json in file");
+}
+catch(Error e) {
+    console.error("unable to read file")
+}
+```
+
+And that is the point - being able to have something that is a lot like `return` and `throw` in synchronous code.
+
+You can also use promises to improve code that was written with callback helpers:
+
+
+```js
+//Copyright Plato http://stackoverflow.com/a/19385911/995876
+//CC BY-SA 2.5
+mapSeries(URLs, function (URL, done) {
+    var options = {};
+    needle.get(URL, options, function (error, response, body) {
+        if (error) {
+            return done(error)
+        }
+        try {
+            var ret = JSON.parse(body);
+            return done(null, ret);
+        }
+        catch (e) {
+            done(e);
+        }
+    });
+}, function (err, results) {
+    if (err) {
+        console.log(err)
+    } else {
+        console.log('All Needle requests successful');
+        // results is a 1 to 1 mapping in order of URLs > needle.body
+        processAndSaveAllInDB(results, function (err) {
+            if (err) {
+                return done(err)
+            }
+            console.log('All Needle requests saved');
+            done(null);
+        });
+    }
+});
+```
+
+Is more pleasing to the eye when done with promises:
+
+```js
+Promise.promisifyAll(needle);
+var options = {};
+
+var current = Promise.resolve();
+Promise.map(URLs, function(URL) {
+    current = current.then(function () {
+        return needle.getAsync(URL, options);
+    });
+    return current;
+}).map(function(responseAndBody){
+    return JSON.parse(responseAndBody[1]);
+}).then(function (results) {
+    return processAndSaveAllInDB(results);
+}).then(function(){
+    console.log('All Needle requests saved');
+}).catch(function (e) {
+    console.log(e);
+});
+```
+
+Also promises don't just give you correspondences for synchronous features but can also be used as limited event emitters or callback aggregators.
+
+More reading:
+
+ - [Promise nuggets](http://spion.github.io/promise-nuggets/)
+ - [Why I am switching to promises](http://spion.github.io/posts/why-i-am-switching-to-promises.html)
+ - [What is the the point of promises](http://domenic.me/2012/10/14/youre-missing-the-point-of-promises/#toc_1)
+ - [Snippets for common problems](https://github.com/petkaantonov/bluebird/wiki/Snippets)
+ - [Promise anti-patterns](https://github.com/petkaantonov/bluebird/wiki/Promise-anti-patterns)
+
+#Questions and issues
+
+If you find a bug in bluebird or have a feature request, file an issue in the [github issue tracker](https://github.com/petkaantonov/bluebird/issues). Anything else, such as questions for help in using the library, should be posted in [StackOverflow](http://stackoverflow.com/questions/tagged/bluebird) under tags `promise` and `bluebird`.
+
+#Error handling
+
+This is a problem every promise library needs to handle in some way. Unhandled rejections/exceptions don't really have a good agreed-on asynchronous correspondence. The problem is that it is impossible to predict the future and know if a rejected promise will eventually be handled.
+
+There are two common pragmatic attempts at solving the problem that promise libraries do.
+
+The more popular one is to have the user explicitly communicate that they are done and any unhandled rejections should be thrown, like so:
+
+```js
+download().then(...).then(...).done();
+```
+
+For handling this problem, in my opinion, this is completely unacceptable and pointless. The user must remember to explicitly call `.done` and that cannot be justified when the problem is forgetting to create an error handler in the first place.
+
+The second approach, which is what bluebird by default takes, is to call a registered handler if a rejection is unhandled by the start of a second turn. The default handler is to write the stack trace to stderr or `console.error` in browsers. This is close to what happens with synchronous code - your code doens't work as expected and you open console and see a stack trace. Nice.
+
+Of course this is not perfect, if your code for some reason needs to swoop in and attach error handler to some promise after the promise has been hanging around a while then you will see annoying messages. In that case you can use the `.done()` method to signal that any hanging exceptions should be thrown.
+
+If you want to override the default handler for these possibly unhandled rejections, you can pass yours like so:
+
+```js
+Promise.onPossiblyUnhandledRejection(function(error){
+    throw error;
+});
+```
+
+If you want to also enable long stack traces, call:
+
+```js
+Promise.longStackTraces();
+```
+
+right after the library is loaded.
+
+In node.js use the environment flag `BLUEBIRD_DEBUG`:
+
+```
+BLUEBIRD_DEBUG=1 node server.js
+```
+
+to enable long stack traces in all instances of bluebird.
+
+Long stack traces cannot be disabled after being enabled, and cannot be enabled after promises have alread been created. Long stack traces imply a substantial performance penalty, even after using every trick to optimize them.
+
+Long stack traces are enabled by default in the debug build.
+
+####Expected and unexpected errors
+
+A practical problem with Promises/A+ is that it models Javascript `try-catch` too closely for its own good. Therefore by default promises inherit `try-catch` warts such as the inability to specify the error types that the catch block is eligible for. It is an anti-pattern in every other language to use catch-all handlers because they swallow exceptions that you might not know about.
+
+Now, Javascript does have a perfectly fine and working way of creating error type hierarchies. It is still quite awkward to use them with the built-in `try-catch` however:
+
+```js
+try {
+    //code
+}
+catch(e) {
+    if( e instanceof WhatIWantError) {
+        //handle
+    }
+    else {
+        throw e;
+    }
+}
+```
+
+Without such checking, unexpected errors would be silently swallowed. However, with promises, bluebird brings the future (hopefully) here now and extends the `.catch` to [accept potential error type eligibility](https://github.com/petkaantonov/bluebird/blob/master/API.md#catchfunction-errorclass-function-handler---promise).
+
+For instance here it is expected that some evil or incompetent entity will try to crash our server from `SyntaxError` by providing syntactically invalid JSON:
+
+```js
+getJSONFromSomewhere().then(function(jsonString) {
+    return JSON.parse(jsonString);
+}).then(function(object) {
+    console.log("it was valid json: ", object);
+}).catch(SyntaxError, function(e){
+    console.log("don't be evil");
+});
+```
+
+Here any kind of unexpected error will automatically reported on stderr along with a stack trace because we only register a handler for the expected `SyntaxError`.
+
+Ok, so, that's pretty neat. But actually not many libraries define error types and it is in fact a complete ghetto out there with ad hoc strings being attached as some arbitrary property name like `.name`, `.type`, `.code`, not having any property at all or even throwing strings as errors and so on. So how can we still listen for expected errors?
+
+Bluebird defines a special error type `RejectionError` (you can get a reference from `Promise.RejectionError`). This type of error is given as rejection reason by promisified methods when
+their underlying library gives an untyped, but expected error. Primitives such as strings, and error objects that are directly created like `new Error("database didn't respond")` are considered untyped.
+
+Example of such library is the node core library `fs`. So if we promisify it, we can catch just the errors we want pretty easily and have programmer errors be redirected to unhandled rejection handler so that we notice them:
+
+```js
+//Read more about promisification in the API Reference:
+//https://github.com/petkaantonov/bluebird/blob/master/API.md
+var fs = Promise.promisifyAll(require("fs"));
+
+fs.readFileAsync("myfile.json").then(JSON.parse).then(function (json) {
+    console.log("Successful json")
+}).catch(SyntaxError, function (e) {
+    console.error("file contains invalid json");
+}).catch(Promise.RejectionError, function (e) {
+    console.error("unable to read file, because: ", e.message);
+});
+```
+
+The last `catch` handler is only invoked when the `fs` module explicitly used the `err` argument convention of async callbacks to inform of an expected error. The `RejectionError` instance will contain the original error in its `.cause` property but it does have a direct copy of the `.message` and `.stack` too. In this code any unexpected error - be it in our code or the `fs` module - would not be caught by these handlers and therefore not swallowed.
+
+Since a `catch` handler typed to `Promise.RejectionError` is expected to be used very often, it has a neat shorthand:
+
+```js
+.error(function (e) {
+    console.error("unable to read file, because: ", e.message);
+});
+```
+
+See [API documentation for `.error()`](https://github.com/petkaantonov/bluebird/blob/master/API.md#error-rejectedhandler----promise)
+
+Finally, Bluebird also supports predicate-based filters. If you pass a
+predicate function instead of an error type, the predicate will receive
+the error as an argument. The return result will be used determine whether
+the error handler should be called.
+
+Predicates should allow for very fine grained control over caught errors:
+pattern matching, error typesets with set operations and many other techniques
+can be implemented on top of them.
+
+Example of using a predicate-based filter:
+
+```js
+var Promise = require("bluebird");
+var request = Promise.promisify(require("request"));
+
+function clientError(e) {
+    return e.code >= 400 && e.code < 500;
+}
+
+request("http://www.google.com").then(function(contents){
+    console.log(contents);
+}).catch(clientError, function(e){
+   //A client error like 400 Bad Request happened
+});
+```
+
+**Danger:** The JavaScript language allows throwing primitive values like strings. Throwing primitives can lead to worse or no stack traces. Primitives [are not exceptions](http://www.devthought.com/2011/12/22/a-string-is-not-an-error/). You should consider always throwing Error objects when handling exceptions.
+
+<hr>
+
+####How do long stack traces differ from e.g. Q?
+
+Bluebird attempts to have more elaborate traces. Consider:
+
+```js
+Error.stackTraceLimit = 25;
+Q.longStackSupport = true;
+Q().then(function outer() {
+    return Q().then(function inner() {
+        return Q().then(function evenMoreInner() {
+            a.b.c.d();
+        }).catch(function catcher(e){
+            console.error(e.stack);
+        });
+    })
+});
+```
+
+You will see
+
+    ReferenceError: a is not defined
+        at evenMoreInner (<anonymous>:7:13)
+    From previous event:
+        at inner (<anonymous>:6:20)
+
+Compare to:
+
+```js
+Error.stackTraceLimit = 25;
+Promise.longStackTraces();
+Promise.resolve().then(function outer() {
+    return Promise.resolve().then(function inner() {
+        return Promise.resolve().then(function evenMoreInner() {
+            a.b.c.d()
+        }).catch(function catcher(e){
+            console.error(e.stack);
+        });
+    });
+});
+```
+
+    ReferenceError: a is not defined
+        at evenMoreInner (<anonymous>:7:13)
+    From previous event:
+        at inner (<anonymous>:6:36)
+    From previous event:
+        at outer (<anonymous>:5:32)
+    From previous event:
+        at <anonymous>:4:21
+        at Object.InjectedScript._evaluateOn (<anonymous>:572:39)
+        at Object.InjectedScript._evaluateAndWrap (<anonymous>:531:52)
+        at Object.InjectedScript.evaluate (<anonymous>:450:21)
+
+
+A better and more practical example of the differences can be seen in gorgikosev's [debuggability competition](https://github.com/spion/async-compare#debuggability).
+
+<hr>
+
+####Can I use long stack traces in production?
+
+Probably yes. Bluebird uses multiple innovative techniques to optimize long stack traces. Even with long stack traces, it is still way faster than similarly featured implementations that don't have long stack traces enabled and about same speed as minimal implementations. A slowdown of 4-5x is expected, not 50x.
+
+What techniques are used?
+
+#####V8 API second argument
+
+This technique utilizes the [slightly under-documented](https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi#Stack_trace_collection_for_custom_exceptions) second argument of V8 `Error.captureStackTrace`. It turns out that the second argument can actually be used to make V8 skip all library internal stack frames [for free](https://github.com/v8/v8/blob/b5fabb9225e1eb1c20fd527b037e3f877296e52a/src/isolate.cc#L665). It only requires propagation of callers manually in library internals but this is not visible to you as user at all.
+
+Without this technique, every promise (well not every, see second technique) created would have to waste time creating and collecting library internal frames which will just be thrown away anyway. It also allows one to use smaller stack trace limits because skipped frames are not counted towards the limit whereas with collecting everything upfront and filtering afterwards would likely have to use higher limits to get more user stack frames in.
+
+#####Sharing stack traces
+
+Consider:
+
+```js
+function getSomethingAsync(fileName) {
+    return readFileAsync(fileName).then(function(){
+        //...
+    }).then(function() {
+        //...
+    }).then(function() {
+        //...
+    });
+}
+```
+
+Everytime you call this function it creates 4 promises and in a straight-forward long stack traces implementation it would collect 4 almost identical stack traces. Bluebird has a light weight internal data-structure (kcnown as context stack in the source code) to help tracking when traces can be re-used and this example would only collect one trace.
+
+#####Lazy formatting
+
+After a stack trace has been collected on an object, one must be careful not to reference the `.stack` property until necessary. Referencing the property causes
+an expensive format call and the stack property is turned into a string which uses much more memory.
+
+What about [Q #111](https://github.com/kriskowal/q/issues/111)?
+
+Long stack traces is not inherently the problem. For example with latest Q with stack traces disabled:
+
+```js
+var Q = require("q");
+
+
+function test(i){
+    if (i <= 0){
+       return Q.when('done')
+   } else {
+       return Q.when(i-1).then(test)
+   }
+}
+test(1000000000).then(function(output){console.log(output) });
+```
+
+After 2 minutes of running this, it will give:
+
+```js
+FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - process out of memory
+```
+
+So the problem with this is how much absolute memory is used per promise - not whether long traces are enabled or not.
+
+For some purpose, let's say 100000 parallel pending promises in memory at the same time is the maximum. You would then roughly use 100MB for them instead of 10MB with stack traces disabled.For comparison, just creating 100000 functions alone will use 14MB if they're closures. All numbers can be halved for 32-bit node.
+
+<hr>
+
+#Development
+
+For development tasks such as running benchmarks or testing, you need to clone the repository and install dev-dependencies.
+
+Install [node](http://nodejs.org/), [npm](https://npmjs.org/), and [grunt](http://gruntjs.com/).
+
+    git clone git@github.com:petkaantonov/bluebird.git
+    cd bluebird
+    npm install
+
+##Testing
+
+To run all tests, run `grunt test`. Note that 10 processes are created to run the tests in parallel. The stdout of tests is ignored by default and everything will stop at the first failure.
+
+Individual files can be run with `grunt test --run=filename` where `filename` is a test file name in `/test` folder or `/test/mocha` folder. The `.js` prefix is not needed. The dots for AP compliance tests are not needed, so to run `/test/mocha/2.3.3.js` for instance:
+
+    grunt test --run=233
+
+When trying to get a test to pass, run only that individual test file with `--verbose` to see the output from that test:
+
+    grunt test --run=233 --verbose
+
+The reason for the unusual way of testing is because the majority of tests are from different libraries using different testing frameworks and because it takes forever to test sequentially.
+
+
+###Testing in browsers
+
+To test in browsers:
+
+    cd browser
+    setup
+
+Then open the `index.html` in your browser. Requires bash (on windows the mingw32 that comes with git works fine too).
+
+You may also [visit the github hosted page](http://petkaantonov.github.io/bluebird/browser/).
+
+Keep the test tab active because some tests are timing-sensitive and will fail if the browser is throttling timeouts. Chrome will do this for example when the tab is not active.
+
+##Benchmarks
+
+To run a benchmark, run the given command for a benchmark while on the project root. Requires bash (on windows the mingw32 that comes with git works fine too).
+
+Node 0.11.2+ is required to run the generator examples.
+
+###1\. DoxBee sequential
+
+Currently the most relevant benchmark is @gorkikosev's benchmark in the article [Analysis of generators and other async patterns in node](http://spion.github.io/posts/analysis-generators-and-other-async-patterns-node.html). The benchmark emulates a situation where n amount of users are making a request in parallel to execute some mixed async/sync action. The benchmark has been modified to include a warm-up phase to minimize any JITing during timed sections.
+
+Command: `bench doxbee`
+
+###2\. Made-up parallel
+
+This made-up scenario runs 15 shimmed queries in parallel.
+
+Command: `bench parallel`
+
+##Custom builds
+
+Custom builds for browsers are supported through a command-line utility.
+
+
+
+
+<table>
+    <caption>The following features can be disabled</caption>
+    <thead>
+        <tr>
+            <th>Feature(s)</th>
+            <th>Command line identifier</th>
+        </tr>
+    </thead>
+    <tbody>
+
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#any---promise"><code>.any</code></a> and <a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#promiseanyarraydynamicpromise-values---promise"><code>Promise.any</code></a></td><td><code>any</code></td></tr>
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#race---promise"><code>.race</code></a> and <a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#promiseracearraypromise-promises---promise"><code>Promise.race</code></a></td><td><code>race</code></td></tr>
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#callstring-propertyname--dynamic-arg---promise"><code>.call</code></a> and <a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#getstring-propertyname---promise"><code>.get</code></a></td><td><code>call_get</code></td></tr>
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#filterfunction-filterer---promise"><code>.filter</code></a> and <a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#promisefilterarraydynamicpromise-values-function-filterer---promise"><code>Promise.filter</code></a></td><td><code>filter</code></td></tr>
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#mapfunction-mapper---promise"><code>.map</code></a> and <a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#promisemaparraydynamicpromise-values-function-mapper---promise"><code>Promise.map</code></a></td><td><code>map</code></td></tr>
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#reducefunction-reducer--dynamic-initialvalue---promise"><code>.reduce</code></a> and <a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#promisereducearraydynamicpromise-values-function-reducer--dynamic-initialvalue---promise"><code>Promise.reduce</code></a></td><td><code>reduce</code></td></tr>
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#props---promise"><code>.props</code></a> and <a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#promisepropsobjectpromise-object---promise"><code>Promise.props</code></a></td><td><code>props</code></td></tr>
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#settle---promise"><code>.settle</code></a> and <a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#promisesettlearraydynamicpromise-values---promise"><code>Promise.settle</code></a></td><td><code>settle</code></td></tr>
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#someint-count---promise"><code>.some</code></a> and <a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#promisesomearraydynamicpromise-values-int-count---promise"><code>Promise.some</code></a></td><td><code>some</code></td></tr>
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#nodeifyfunction-callback---promise"><code>.nodeify</code></a></td><td><code>nodeify</code></td></tr>
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#promisecoroutinegeneratorfunction-generatorfunction---function"><code>Promise.coroutine</code></a> and <a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#promisespawngeneratorfunction-generatorfunction---promise"><code>Promise.spawn</code></a></td><td><code>generators</code></td></tr>
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#progression">Progression</a></td><td><code>progress</code></td></tr>
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#promisification">Promisification</a></td><td><code>promisify</code></td></tr>
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#cancellation">Cancellation</a></td><td><code>cancel</code></td></tr>
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#synchronous-inspection">Synchronous inspection</a></td><td><code>synchronous_inspection</code></td></tr>
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#timers">Timers</a></td><td><code>timers</code></td></tr>
+
+    </tbody>
+</table>
+
+
+Make sure you have cloned the repo somewhere and did `npm install` successfully.
+
+After that you can run:
+
+    grunt build --features="core"
+
+
+The above builds the most minimal build you can get. You can add more features separated by spaces from the above list:
+
+    grunt build --features="core filter map reduce"
+
+The custom build file will be found from `/js/browser/bluebird.js`. It will have a comment that lists the disabled and enabled features.
+
+Note that the build leaves the `/js/main` etc folders with same features so if you use the folder for node.js at the same time, don't forget to build
+a full version afterwards (after having taken a copy of the bluebird.js somewhere):
+
+    grunt build
+
+<hr>
+
+##For library authors
+
+Building a library that depends on bluebird? You should know about a few features.
+
+If your library needs to do something obtrusive like adding or modifying methods on the `Promise` prototype, uses long stack traces or uses a custom unhandled rejection handler then... that's totally ok as long as you don't use `require("bluebird")`. Instead you should create a file
+that creates an isolated copy. For example, creating a file called `bluebird-extended.js` that contains:
+
+```js
+                //NOTE the function call right after
+module.exports = require("bluebird/js/main/promise")();
+```
+
+Your library can then use `var Promise = require("bluebird-extended");` and do whatever it wants with it. Then if the application or other library uses their own bluebird promises they will all play well together because of Promises/A+ thenable assimilation magic.
+
+You should also know about [`.nodeify()`](https://github.com/petkaantonov/bluebird/blob/master/API.md#nodeifyfunction-callback---promise) which makes it easy to provide a dual callback/promise API.
+
+<hr>
+
+##What is the sync build?
+
+You may now use sync build by:
+
+    var Promise = require("bluebird/zalgo");
+
+The sync build is provided to see how forced asynchronity affects benchmarks. It should not be used in real code due to the implied hazards.
+
+The normal async build gives Promises/A+ guarantees about asynchronous resolution of promises. Some people think this affects performance or just plain love their code having a possibility
+of stack overflow errors and non-deterministic behavior.
+
+The sync build skips the async call trampoline completely, e.g code like:
+
+    async.invoke( this.fn, this, val );
+
+Appears as this in the sync build:
+
+    this.fn(val);
+
+This should pressure the CPU slightly less and thus the sync build should perform better. Indeed it does, but only marginally. The biggest performance boosts are from writing efficient Javascript, not from compromising determinism.
+
+Note that while some benchmarks are waiting for the next event tick, the CPU is actually not in use during that time. So the resulting benchmark result is not completely accurate because on node.js you only care about how much the CPU is taxed. Any time spent on CPU is time the whole process (or server) is paralyzed. And it is not graceful like it would be with threads.
+
+
+```js
+var cache = new Map(); //ES6 Map or DataStructures/Map or whatever...
+function getResult(url) {
+    var resolver = Promise.pending();
+    if (cache.has(url)) {
+        resolver.resolve(cache.get(url));
+    }
+    else {
+        http.get(url, function(err, content) {
+            if (err) resolver.reject(err);
+            else {
+                cache.set(url, content);
+                resolver.resolve(content);
+            }
+        });
+    }
+    return resolver.promise;
+}
+
+
+
+//The result of console.log is truly random without async guarantees
+function guessWhatItPrints( url ) {
+    var i = 3;
+    getResult(url).then(function(){
+        i = 4;
+    });
+    console.log(i);
+}
+```
+
+#Optimization guide
+
+Articles about optimization will be periodically posted in [the wiki section](https://github.com/petkaantonov/bluebird/wiki), polishing edits are welcome.
+
+A single cohesive guide compiled from the articles will probably be done eventually.
+
+#License
+
+Copyright (c) 2014 Petka Antonov
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:</p>
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/benchmark/package.json b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/benchmark/package.json
new file mode 100644
index 0000000..19cefed
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/benchmark/package.json
@@ -0,0 +1,34 @@
+{
+  "name": "async-compare",
+  "version": "0.1.1",
+  "description": "Compare the performance and code of multiple async patterns",
+  "main": "perf.js",
+  "dependencies": {
+    "async": "~0.2.9",
+    "deferred": "~0.6.6",
+    "kew": "~0.3.0",
+    "liar": "~0.6.0",
+    "optimist": "~0.6.0",
+    "q": "~1.0.0",
+    "rsvp": "~3.0.0",
+    "text-table": "~0.2.0",
+    "when": "~3.0.0",
+    "vow": "~0.4.1",
+    "davy": "~0.2.0"
+  },
+  "devDependencies": {},
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "keywords": [
+    "generators",
+    "fibers",
+    "promises",
+    "callbacks",
+    "comparison",
+    "compare",
+    "async"
+  ],
+  "author": "spion",
+  "license": "MIT"
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/bower.json b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/bower.json
new file mode 100644
index 0000000..396073a
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/bower.json
@@ -0,0 +1,35 @@
+{
+  "name": "bluebird",
+  "version": "1.0.0",
+  "homepage": "https://github.com/petkaantonov/bluebird",
+  "authors": [
+    "Petka Antonov <petka_antonov@hotmail.com>"
+  ],
+  "description": "Bluebird is a full featured promise library with unmatched performance.",
+  "main": "js/browser/bluebird.js",
+  "license": "MIT",
+  "ignore": [
+    "**/.*",
+    "benchmark",
+    "bower_components",
+    "./browser",
+    "js/zalgo",
+    "node_modules",
+    "test"
+  ],
+  "keywords": [
+    "promise",
+    "performance",
+    "promises",
+    "promises-a",
+    "promises-aplus",
+    "async",
+    "await",
+    "deferred",
+    "deferreds",
+    "future",
+    "flow control",
+    "dsl",
+    "fluent interface"
+  ]
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/bundle.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/bundle.js
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/bundle.js
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/changelog.md b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/changelog.md
new file mode 100644
index 0000000..9ae6be2
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/changelog.md
@@ -0,0 +1,1135 @@
+## 1.1.1 (2014-03-18)
+
+Bugfixes:
+
+ - [#138](https://github.com/petkaantonov/bluebird/issues/138)
+ - [#144](https://github.com/petkaantonov/bluebird/issues/144)
+ - [#148](https://github.com/petkaantonov/bluebird/issues/148)
+ - [#151](https://github.com/petkaantonov/bluebird/issues/151)
+
+## 1.1.0 (2014-03-08)
+
+Features:
+
+ - Implement [`Promise.prototype.tap()`](https://github.com/petkaantonov/bluebird/blob/master/API.md#tapfunction-handler---promise)
+ - Implement [`Promise.coroutine.addYieldHandler()`](https://github.com/petkaantonov/bluebird/blob/master/API.md#promisecoroutineaddyieldhandlerfunction-handler---void)
+ - Deprecate `Promise.prototype.spawn`
+
+Bugfixes:
+
+ - Fix already rejected promises being reported as unhandled when handled through collection methods
+ - Fix browserisfy crashing from checking `process.version.indexOf`
+
+## 1.0.8 (2014-03-03)
+
+Bugfixes:
+
+ - Fix active domain being lost across asynchronous boundaries in Node.JS 10.xx
+
+## 1.0.7 (2014-02-25)
+
+Bugfixes:
+
+ - Fix handled errors being reported
+
+## 1.0.6 (2014-02-17)
+
+Bugfixes:
+
+ -  Fix bug with unhandled rejections not being reported
+    when using `Promise.try` or `Promise.method` without
+    attaching further handlers
+
+## 1.0.5 (2014-02-15)
+
+Features:
+
+ - Node.js performance: promisified functions try to check amount of passed arguments in most optimal order
+ - Node.js promisified functions will have same `.length` as the original function minus one (for the callback parameter)
+
+## 1.0.4 (2014-02-09)
+
+Features:
+
+ - Possibly unhandled rejection handler will always get a stack trace, even if the rejection or thrown error was not an error
+ - Unhandled rejections are tracked per promise, not per error. So if you create multiple branches from a single ancestor and that ancestor gets rejected, each branch with no error handler with the end will cause a possibly unhandled rejection handler invocation
+
+Bugfixes:
+
+ - Fix unhandled non-writable objects or primitives not reported by possibly unhandled rejection handler
+
+## 1.0.3 (2014-02-05)
+
+Bugfixes:
+
+ - [#93](https://github.com/petkaantonov/bluebird/issues/88)
+
+## 1.0.2 (2014-02-04)
+
+Features:
+
+ - Significantly improve performance of foreign bluebird thenables
+
+Bugfixes:
+
+ - [#88](https://github.com/petkaantonov/bluebird/issues/88)
+
+## 1.0.1 (2014-01-28)
+
+Features:
+
+ - Error objects that have property `.isAsync = true` will now be caught by `.error()`
+
+Bugfixes:
+
+ - Fix TypeError and RangeError shims not working without `new` operator
+
+## 1.0.0 (2014-01-12)
+
+Features:
+
+ - `.filter`, `.map`, and `.reduce` no longer skip sparse array holes. This is a backwards incompatible change.
+ - Like `.map` and `.filter`, `.reduce` now allows returning promises and thenables from the iteration function.
+
+Bugfixes:
+
+ - [#58](https://github.com/petkaantonov/bluebird/issues/58)
+ - [#61](https://github.com/petkaantonov/bluebird/issues/61)
+ - [#64](https://github.com/petkaantonov/bluebird/issues/64)
+ - [#60](https://github.com/petkaantonov/bluebird/issues/60)
+
+## 0.11.6-1 (2013-12-29)
+
+## 0.11.6-0 (2013-12-29)
+
+Features:
+
+ - You may now return promises and thenables from the filterer function used in `Promise.filter` and `Promise.prototype.filter`.
+
+ - `.error()` now catches additional sources of rejections:
+
+    - Rejections originating from `Promise.reject`
+
+    - Rejections originating from thenables using
+    the `reject` callback
+
+    - Rejections originating from promisified callbacks
+    which use the `errback` argument
+
+    - Rejections originating from `new Promise` constructor
+    where the `reject` callback is called explicitly
+
+    - Rejections originating from `PromiseResolver` where
+    `.reject()` method is called explicitly
+
+Bugfixes:
+
+ - Fix `captureStackTrace` being called when it was `null`
+ - Fix `Promise.map` not unwrapping thenables
+
+## 0.11.5-1 (2013-12-15)
+
+## 0.11.5-0 (2013-12-03)
+
+Features:
+
+ - Improve performance of collection methods
+ - Improve performance of promise chains
+
+## 0.11.4-1 (2013-12-02)
+
+## 0.11.4-0 (2013-12-02)
+
+Bugfixes:
+
+ - Fix `Promise.some` behavior with arguments like negative integers, 0...
+ - Fix stack traces of synchronously throwing promisified functions'
+
+## 0.11.3-0 (2013-12-02)
+
+Features:
+
+ - Improve performance of generators
+
+Bugfixes:
+
+ - Fix critical bug with collection methods.
+
+## 0.11.2-0 (2013-12-02)
+
+Features:
+
+ - Improve performance of all collection methods
+
+## 0.11.1-0 (2013-12-02)
+
+Features:
+
+- Improve overall performance.
+- Improve performance of promisified functions.
+- Improve performance of catch filters.
+- Improve performance of .finally.
+
+Bugfixes:
+
+- Fix `.finally()` rejecting if passed non-function. It will now ignore non-functions like `.then`.
+- Fix `.finally()` not converting thenables returned from the handler to promises.
+- `.spread()` now rejects if the ultimate value given to it is not spreadable.
+
+## 0.11.0-0 (2013-12-02)
+
+Features:
+
+ - Improve overall performance when not using `.bind()` or cancellation.
+ - Promises are now not cancellable by default. This is backwards incompatible change - see [`.cancellable()`](https://github.com/petkaantonov/bluebird/blob/master/API.md#cancellable---promise)
+ - [`Promise.delay`](https://github.com/petkaantonov/bluebird/blob/master/API.md#promisedelaydynamic-value-int-ms---promise)
+ - [`.delay()`](https://github.com/petkaantonov/bluebird/blob/master/API.md#delayint-ms---promise)
+ - [`.timeout()`](https://github.com/petkaantonov/bluebird/blob/master/API.md#timeoutint-ms--string-message---promise)
+
+## 0.10.14-0 (2013-12-01)
+
+Bugfixes:
+
+ - Fix race condition when mixing 3rd party asynchrony.
+
+## 0.10.13-1 (2013-11-30)
+
+## 0.10.13-0 (2013-11-30)
+
+Bugfixes:
+
+ - Fix another bug with progression.
+
+## 0.10.12-0 (2013-11-30)
+
+Bugfixes:
+
+ - Fix bug with progression.
+
+## 0.10.11-4 (2013-11-29)
+
+## 0.10.11-2 (2013-11-29)
+
+Bugfixes:
+
+ - Fix `.race()` not propagating bound values.
+
+## 0.10.11-1 (2013-11-29)
+
+Features:
+
+ - Improve performance of `Promise.race`
+
+## 0.10.11-0 (2013-11-29)
+
+Bugfixes:
+
+ - Fixed `Promise.promisifyAll` invoking property accessors. Only data properties with function values are considered.
+
+## 0.10.10-0 (2013-11-28)
+
+Features:
+
+ - Disable long stack traces in browsers by default. Call `Promise.longStackTraces()` to enable them.
+
+## 0.10.9-1 (2013-11-27)
+
+Bugfixes:
+
+ - Fail early when `new Promise` is constructed incorrectly
+
+## 0.10.9-0 (2013-11-27)
+
+Bugfixes:
+
+ - Promise.props now takes a [thenable-for-collection](https://github.com/petkaantonov/bluebird/blob/f41edac61b7c421608ff439bb5a09b7cffeadcf9/test/mocha/props.js#L197-L217)
+ - All promise collection methods now reject when a promise-or-thenable-for-collection turns out not to give a collection
+
+## 0.10.8-0 (2013-11-25)
+
+Features:
+
+ - All static collection methods take thenable-for-collection
+
+## 0.10.7-0 (2013-11-25)
+
+Features:
+
+ - throw TypeError when thenable resolves with itself
+ - Make .race() and Promise.race() forever pending on empty collections
+
+## 0.10.6-0 (2013-11-25)
+
+Bugfixes:
+
+ - Promise.resolve and PromiseResolver.resolve follow thenables too.
+
+## 0.10.5-0 (2013-11-24)
+
+Bugfixes:
+
+ - Fix infinite loop when thenable resolves with itself
+
+## 0.10.4-1 (2013-11-24)
+
+Bugfixes:
+
+ - Fix a file missing from build. (Critical fix)
+
+## 0.10.4-0 (2013-11-24)
+
+Features:
+
+ - Remove dependency of es5-shim and es5-sham when using ES3.
+
+## 0.10.3-0 (2013-11-24)
+
+Features:
+
+ - Improve performance of `Promise.method`
+
+## 0.10.2-1 (2013-11-24)
+
+Features:
+
+ - Rename PromiseResolver#asCallback to PromiseResolver#callback
+
+## 0.10.2-0 (2013-11-24)
+
+Features:
+
+ - Remove memoization of thenables
+
+## 0.10.1-0 (2013-11-21)
+
+Features:
+
+ - Add methods `Promise.resolve()`, `Promise.reject()`, `Promise.defer()` and `.resolve()`.
+
+## 0.10.0-1 (2013-11-17)
+
+## 0.10.0-0 (2013-11-17)
+
+Features:
+
+ - Implement `Promise.method()`
+ - Implement `.return()`
+ - Implement `.throw()`
+
+Bugfixes:
+
+ - Fix promises being able to use themselves as resolution or follower value
+
+## 0.9.11-1 (2013-11-14)
+
+Features:
+
+ - Implicit `Promise.all()` when yielding an array from generators
+
+## 0.9.11-0 (2013-11-13)
+
+Bugfixes:
+
+ - Fix `.spread` not unwrapping thenables
+
+## 0.9.10-2 (2013-11-13)
+
+Features:
+
+ - Improve performance of promisified functions on V8
+
+Bugfixes:
+
+ - Report unhandled rejections even when long stack traces are disabled
+ - Fix `.error()` showing up in stack traces
+
+## 0.9.10-1 (2013-11-05)
+
+Bugfixes:
+
+ - Catch filter method calls showing in stack traces
+
+## 0.9.10-0 (2013-11-05)
+
+Bugfixes:
+
+ - Support primitives in catch filters
+
+## 0.9.9-0 (2013-11-05)
+
+Features:
+
+ - Add `Promise.race()` and `.race()`
+
+## 0.9.8-0 (2013-11-01)
+
+Bugfixes:
+
+ - Fix bug with `Promise.try` not unwrapping returned promises and thenables
+
+## 0.9.7-0 (2013-10-29)
+
+Bugfixes:
+
+ - Fix bug with build files containing duplicated code for promise.js
+
+## 0.9.6-0 (2013-10-28)
+
+Features:
+
+ - Improve output of reporting unhandled non-errors
+ - Implement RejectionError wrapping and `.error()` method
+
+## 0.9.5-0 (2013-10-27)
+
+Features:
+
+ - Allow fresh copies of the library to be made
+
+## 0.9.4-1 (2013-10-27)
+
+## 0.9.4-0 (2013-10-27)
+
+Bugfixes:
+
+ - Rollback non-working multiple fresh copies feature
+
+## 0.9.3-0 (2013-10-27)
+
+Features:
+
+ - Allow fresh copies of the library to be made
+ - Add more components to customized builds
+
+## 0.9.2-1 (2013-10-25)
+
+## 0.9.2-0 (2013-10-25)
+
+Features:
+
+ - Allow custom builds
+
+## 0.9.1-1 (2013-10-22)
+
+Bugfixes:
+
+ - Fix unhandled rethrown exceptions not reported
+
+## 0.9.1-0 (2013-10-22)
+
+Features:
+
+ - Improve performance of `Promise.try`
+ - Extend `Promise.try` to accept arguments and ctx to make it more usable in promisification of synchronous functions.
+
+## 0.9.0-0 (2013-10-18)
+
+Features:
+
+ - Implement `.bind` and `Promise.bind`
+
+Bugfixes:
+
+ - Fix `.some()` when argument is a pending promise that later resolves to an array
+
+## 0.8.5-1 (2013-10-17)
+
+Features:
+
+ - Enable process wide long stack traces through BLUEBIRD_DEBUG environment variable
+
+## 0.8.5-0 (2013-10-16)
+
+Features:
+
+ - Improve performance of all collection methods
+
+Bugfixes:
+
+ - Fix .finally passing the value to handlers
+ - Remove kew from benchmarks due to bugs in the library breaking the benchmark
+ - Fix some bluebird library calls potentially appearing in stack traces
+
+## 0.8.4-1 (2013-10-15)
+
+Bugfixes:
+
+ - Fix .pending() call showing in long stack traces
+
+## 0.8.4-0 (2013-10-15)
+
+Bugfixes:
+
+ - Fix PromiseArray and its sub-classes swallowing possibly unhandled rejections
+
+## 0.8.3-3 (2013-10-14)
+
+Bugfixes:
+
+ - Fix AMD-declaration using named module.
+
+## 0.8.3-2 (2013-10-14)
+
+Features:
+
+ - The mortals that can handle it may now release Zalgo by `require("bluebird/zalgo");`
+
+## 0.8.3-1 (2013-10-14)
+
+Bugfixes:
+
+ - Fix memory leak when using the same promise to attach handlers over and over again
+
+## 0.8.3-0 (2013-10-13)
+
+Features:
+
+ - Add `Promise.props()` and `Promise.prototype.props()`. They work like `.all()` for object properties.
+
+Bugfixes:
+
+ - Fix bug with .some returning garbage when sparse arrays have rejections
+
+## 0.8.2-2 (2013-10-13)
+
+Features:
+
+ - Improve performance of `.reduce()` when `initialValue` can be synchronously cast to a value
+
+## 0.8.2-1 (2013-10-12)
+
+Bugfixes:
+
+ - Fix .npmignore having irrelevant files
+
+## 0.8.2-0 (2013-10-12)
+
+Features:
+
+ - Improve performance of `.some()`
+
+## 0.8.1-0 (2013-10-11)
+
+Bugfixes:
+
+ - Remove uses of dynamic evaluation (`new Function`, `eval` etc) when strictly not necessary. Use feature detection to use static evaluation to avoid errors when dynamic evaluation is prohibited.
+
+## 0.8.0-3 (2013-10-10)
+
+Features:
+
+ - Add `.asCallback` property to `PromiseResolver`s
+
+## 0.8.0-2 (2013-10-10)
+
+## 0.8.0-1 (2013-10-09)
+
+Features:
+
+ - Improve overall performance. Be able to sustain infinite recursion when using promises.
+
+## 0.8.0-0 (2013-10-09)
+
+Bugfixes:
+
+ - Fix stackoverflow error when function calls itself "synchronously" from a promise handler
+
+## 0.7.12-2 (2013-10-09)
+
+Bugfixes:
+
+ - Fix safari 6 not using `MutationObserver` as a scheduler
+ - Fix process exceptions interfering with internal queue flushing
+
+## 0.7.12-1 (2013-10-09)
+
+Bugfixes:
+
+ - Don't try to detect if generators are available to allow shims to be used
+
+## 0.7.12-0 (2013-10-08)
+
+Features:
+
+ - Promisification now consider all functions on the object and its prototype chain
+ - Individual promisifcation uses current `this` if no explicit receiver is given
+ - Give better stack traces when promisified callbacks throw or errback primitives such as strings by wrapping them in an `Error` object.
+
+Bugfixes:
+
+ - Fix runtime APIs throwing synchronous errors
+
+## 0.7.11-0 (2013-10-08)
+
+Features:
+
+ - Deprecate `Promise.promisify(Object target)` in favor of `Promise.promisifyAll(Object target)` to avoid confusion with function objects
+ - Coroutines now throw error when a non-promise is `yielded`
+
+## 0.7.10-1 (2013-10-05)
+
+Features:
+
+ - Make tests pass Internet Explorer 8
+
+## 0.7.10-0 (2013-10-05)
+
+Features:
+
+ - Create browser tests
+
+## 0.7.9-1 (2013-10-03)
+
+Bugfixes:
+
+ - Fix promise cast bug when thenable fulfills using itself as the fulfillment value
+
+## 0.7.9-0 (2013-10-03)
+
+Features:
+
+ - More performance improvements when long stack traces are enabled
+
+## 0.7.8-1 (2013-10-02)
+
+Features:
+
+ - Performance improvements when long stack traces are enabled
+
+## 0.7.8-0 (2013-10-02)
+
+Bugfixes:
+
+ - Fix promisified methods not turning synchronous exceptions into rejections
+
+## 0.7.7-1 (2013-10-02)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.7-0 (2013-10-01)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.6-0 (2013-09-29)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.5-0 (2013-09-28)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.4-1 (2013-09-28)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.4-0 (2013-09-28)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.3-1 (2013-09-28)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.3-0 (2013-09-27)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.2-0 (2013-09-27)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.1-5 (2013-09-26)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.1-4 (2013-09-25)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.1-3 (2013-09-25)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.1-2 (2013-09-24)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.1-1 (2013-09-24)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.1-0 (2013-09-24)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.0-1 (2013-09-23)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.0-0 (2013-09-23)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.6.5-2 (2013-09-20)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.6.5-1 (2013-09-18)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.6.5-0 (2013-09-18)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.6.4-1 (2013-09-18)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.6.4-0 (2013-09-18)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.6.3-4 (2013-09-18)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.6.3-3 (2013-09-18)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.6.3-2 (2013-09-16)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.6.3-1 (2013-09-16)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.6.3-0 (2013-09-15)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.6.2-1 (2013-09-14)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.6.2-0 (2013-09-14)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.6.1-0 (2013-09-14)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.6.0-0 (2013-09-13)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.9-6 (2013-09-12)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.9-5 (2013-09-12)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.9-4 (2013-09-12)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.9-3 (2013-09-11)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.9-2 (2013-09-11)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.9-1 (2013-09-11)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.9-0 (2013-09-11)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.8-1 (2013-09-11)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.8-0 (2013-09-11)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.7-0 (2013-09-11)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.6-1 (2013-09-10)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.6-0 (2013-09-10)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.5-1 (2013-09-10)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.5-0 (2013-09-09)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.4-1 (2013-09-08)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.4-0 (2013-09-08)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.3-0 (2013-09-07)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.2-0 (2013-09-07)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.1-0 (2013-09-07)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.0-0 (2013-09-07)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.4.0-0 (2013-09-06)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.3.0-1 (2013-09-06)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.3.0 (2013-09-06)
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/any.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/any.js
new file mode 100644
index 0000000..8d174cf
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/any.js
@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, Promise$_CreatePromiseArray, PromiseArray) {
+
+    var SomePromiseArray = require("./some_promise_array.js")(PromiseArray);
+    function Promise$_Any(promises, useBound, caller) {
+        var ret = Promise$_CreatePromiseArray(
+            promises,
+            SomePromiseArray,
+            caller,
+            useBound === true && promises._isBound()
+                ? promises._boundTo
+                : void 0
+       );
+        var promise = ret.promise();
+        if (promise.isRejected()) {
+            return promise;
+        }
+        ret.setHowMany(1);
+        ret.setUnwrap();
+        ret.init();
+        return promise;
+    }
+
+    Promise.any = function Promise$Any(promises) {
+        return Promise$_Any(promises, false, Promise.any);
+    };
+
+    Promise.prototype.any = function Promise$any() {
+        return Promise$_Any(this, true, this.any);
+    };
+
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/assert.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/assert.js
new file mode 100644
index 0000000..4adb8c2
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/assert.js
@@ -0,0 +1,83 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = (function(){
+    var AssertionError = (function() {
+        function AssertionError(a) {
+            this.constructor$(a);
+            this.message = a;
+            this.name = "AssertionError";
+        }
+        AssertionError.prototype = new Error();
+        AssertionError.prototype.constructor = AssertionError;
+        AssertionError.prototype.constructor$ = Error;
+        return AssertionError;
+    })();
+
+    function getParams(args) {
+        var params = [];
+        for (var i = 0; i < args.length; ++i) params.push("arg" + i);
+        return params;
+    }
+
+    function nativeAssert(callName, args, expect) {
+        try {
+            var params = getParams(args);
+            var constructorArgs = params;
+            constructorArgs.push("return " +
+                    callName + "("+ params.join(",") + ");");
+            var fn = Function.apply(null, constructorArgs);
+            return fn.apply(null, args);
+        }
+        catch (e) {
+            if (!(e instanceof SyntaxError)) {
+                throw e;
+            }
+            else {
+                return expect;
+            }
+        }
+    }
+
+    return function assert(boolExpr, message) {
+        if (boolExpr === true) return;
+
+        if (typeof boolExpr === "string" &&
+            boolExpr.charAt(0) === "%") {
+            var nativeCallName = boolExpr;
+            var $_len = arguments.length;var args = new Array($_len - 2); for(var $_i = 2; $_i < $_len; ++$_i) {args[$_i - 2] = arguments[$_i];}
+            if (nativeAssert(nativeCallName, args, message) === message) return;
+            message = (nativeCallName + " !== " + message);
+        }
+
+        var ret = new AssertionError(message);
+        if (Error.captureStackTrace) {
+            Error.captureStackTrace(ret, assert);
+        }
+        if (console && console.error) {
+            console.error(ret.stack + "");
+        }
+        throw ret;
+
+    };
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/async.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/async.js
new file mode 100644
index 0000000..6f32b10
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/async.js
@@ -0,0 +1,111 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var schedule = require("./schedule.js");
+var Queue = require("./queue.js");
+var errorObj = require("./util.js").errorObj;
+var tryCatch1 = require("./util.js").tryCatch1;
+var process = require("./global.js").process;
+
+function Async() {
+    this._isTickUsed = false;
+    this._length = 0;
+    this._lateBuffer = new Queue();
+    this._functionBuffer = new Queue(25000 * 3);
+    var self = this;
+    this.consumeFunctionBuffer = function Async$consumeFunctionBuffer() {
+        self._consumeFunctionBuffer();
+    };
+}
+
+Async.prototype.haveItemsQueued = function Async$haveItemsQueued() {
+    return this._length > 0;
+};
+
+Async.prototype.invokeLater = function Async$invokeLater(fn, receiver, arg) {
+    if (process !== void 0 &&
+        process.domain != null &&
+        !fn.domain) {
+        fn = process.domain.bind(fn);
+    }
+    this._lateBuffer.push(fn, receiver, arg);
+    this._queueTick();
+};
+
+Async.prototype.invoke = function Async$invoke(fn, receiver, arg) {
+    if (process !== void 0 &&
+        process.domain != null &&
+        !fn.domain) {
+        fn = process.domain.bind(fn);
+    }
+    var functionBuffer = this._functionBuffer;
+    functionBuffer.push(fn, receiver, arg);
+    this._length = functionBuffer.length();
+    this._queueTick();
+};
+
+Async.prototype._consumeFunctionBuffer =
+function Async$_consumeFunctionBuffer() {
+    var functionBuffer = this._functionBuffer;
+    while(functionBuffer.length() > 0) {
+        var fn = functionBuffer.shift();
+        var receiver = functionBuffer.shift();
+        var arg = functionBuffer.shift();
+        fn.call(receiver, arg);
+    }
+    this._reset();
+    this._consumeLateBuffer();
+};
+
+Async.prototype._consumeLateBuffer = function Async$_consumeLateBuffer() {
+    var buffer = this._lateBuffer;
+    while(buffer.length() > 0) {
+        var fn = buffer.shift();
+        var receiver = buffer.shift();
+        var arg = buffer.shift();
+        var res = tryCatch1(fn, receiver, arg);
+        if (res === errorObj) {
+            this._queueTick();
+            if (fn.domain != null) {
+                fn.domain.emit("error", res.e);
+            }
+            else {
+                throw res.e;
+            }
+        }
+    }
+};
+
+Async.prototype._queueTick = function Async$_queue() {
+    if (!this._isTickUsed) {
+        schedule(this.consumeFunctionBuffer);
+        this._isTickUsed = true;
+    }
+};
+
+Async.prototype._reset = function Async$_reset() {
+    this._isTickUsed = false;
+    this._length = 0;
+};
+
+module.exports = new Async();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/bluebird.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/bluebird.js
new file mode 100644
index 0000000..6fd85f1
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/bluebird.js
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var Promise = require("./promise.js")();
+module.exports = Promise;
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/call_get.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/call_get.js
new file mode 100644
index 0000000..2a3c1f5
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/call_get.js
@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise) {
+    Promise.prototype.call = function Promise$call(propertyName) {
+        var $_len = arguments.length;var args = new Array($_len - 1); for(var $_i = 1; $_i < $_len; ++$_i) {args[$_i - 1] = arguments[$_i];}
+
+        return this._then(function(obj) {
+                return obj[propertyName].apply(obj, args);
+            },
+            void 0,
+            void 0,
+            void 0,
+            void 0,
+            this.call
+       );
+    };
+
+    function Promise$getter(obj) {
+        var prop = typeof this === "string"
+            ? this
+            : ("" + this);
+        return obj[prop];
+    }
+    Promise.prototype.get = function Promise$get(propertyName) {
+        return this._then(
+            Promise$getter,
+            void 0,
+            void 0,
+            propertyName,
+            void 0,
+            this.get
+       );
+    };
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/cancel.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/cancel.js
new file mode 100644
index 0000000..542b488
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/cancel.js
@@ -0,0 +1,77 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, INTERNAL) {
+    var errors = require("./errors.js");
+    var async = require("./async.js");
+    var CancellationError = errors.CancellationError;
+    var SYNC_TOKEN = {};
+
+    Promise.prototype._cancel = function Promise$_cancel() {
+        if (!this.isCancellable()) return this;
+        var parent;
+        if ((parent = this._cancellationParent) !== void 0) {
+            parent.cancel(SYNC_TOKEN);
+            return;
+        }
+        var err = new CancellationError();
+        this._attachExtraTrace(err);
+        this._rejectUnchecked(err);
+    };
+
+    Promise.prototype.cancel = function Promise$cancel(token) {
+        if (!this.isCancellable()) return this;
+        if (token === SYNC_TOKEN) {
+            this._cancel();
+            return this;
+        }
+        async.invokeLater(this._cancel, this, void 0);
+        return this;
+    };
+
+    Promise.prototype.cancellable = function Promise$cancellable() {
+        if (this._cancellable()) return this;
+        this._setCancellable();
+        this._cancellationParent = void 0;
+        return this;
+    };
+
+    Promise.prototype.uncancellable = function Promise$uncancellable() {
+        var ret = new Promise(INTERNAL);
+        ret._setTrace(this.uncancellable, this);
+        ret._follow(this);
+        ret._unsetCancellable();
+        if (this._isBound()) ret._setBoundTo(this._boundTo);
+        return ret;
+    };
+
+    Promise.prototype.fork =
+    function Promise$fork(didFulfill, didReject, didProgress) {
+        var ret = this._then(didFulfill, didReject, didProgress,
+            void 0, void 0, this.fork);
+
+        ret._setCancellable();
+        ret._cancellationParent = void 0;
+        return ret;
+    };
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/captured_trace.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/captured_trace.js
new file mode 100644
index 0000000..af34f11
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/captured_trace.js
@@ -0,0 +1,237 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function() {
+var inherits = require("./util.js").inherits;
+var defineProperty = require("./es5.js").defineProperty;
+
+var rignore = new RegExp(
+    "\\b(?:[\\w.]*Promise(?:Array|Spawn)?\\$_\\w+|" +
+    "tryCatch(?:1|2|Apply)|new \\w*PromiseArray|" +
+    "\\w*PromiseArray\\.\\w*PromiseArray|" +
+    "setTimeout|CatchFilter\\$_\\w+|makeNodePromisified|processImmediate|" +
+    "process._tickCallback|nextTick|Async\\$\\w+)\\b"
+);
+
+var rtraceline = null;
+var formatStack = null;
+var areNamesMangled = false;
+
+function formatNonError(obj) {
+    var str;
+    if (typeof obj === "function") {
+        str = "[function " +
+            (obj.name || "anonymous") +
+            "]";
+    }
+    else {
+        str = obj.toString();
+        var ruselessToString = /\[object [a-zA-Z0-9$_]+\]/;
+        if (ruselessToString.test(str)) {
+            try {
+                var newStr = JSON.stringify(obj);
+                str = newStr;
+            }
+            catch(e) {
+
+            }
+        }
+        if (str.length === 0) {
+            str = "(empty array)";
+        }
+    }
+    return ("(<" + snip(str) + ">, no stack trace)");
+}
+
+function snip(str) {
+    var maxChars = 41;
+    if (str.length < maxChars) {
+        return str;
+    }
+    return str.substr(0, maxChars - 3) + "...";
+}
+
+function CapturedTrace(ignoreUntil, isTopLevel) {
+    if (!areNamesMangled) {
+    }
+    this.captureStackTrace(ignoreUntil, isTopLevel);
+
+}
+inherits(CapturedTrace, Error);
+
+CapturedTrace.prototype.captureStackTrace =
+function CapturedTrace$captureStackTrace(ignoreUntil, isTopLevel) {
+    captureStackTrace(this, ignoreUntil, isTopLevel);
+};
+
+CapturedTrace.possiblyUnhandledRejection =
+function CapturedTrace$PossiblyUnhandledRejection(reason) {
+    if (typeof console === "object") {
+        var message;
+        if (typeof reason === "object" || typeof reason === "function") {
+            var stack = reason.stack;
+            message = "Possibly unhandled " + formatStack(stack, reason);
+        }
+        else {
+            message = "Possibly unhandled " + String(reason);
+        }
+        if (typeof console.error === "function" ||
+            typeof console.error === "object") {
+            console.error(message);
+        }
+        else if (typeof console.log === "function" ||
+            typeof console.error === "object") {
+            console.log(message);
+        }
+    }
+};
+
+areNamesMangled = CapturedTrace.prototype.captureStackTrace.name !==
+    "CapturedTrace$captureStackTrace";
+
+CapturedTrace.combine = function CapturedTrace$Combine(current, prev) {
+    var curLast = current.length - 1;
+    for (var i = prev.length - 1; i >= 0; --i) {
+        var line = prev[i];
+        if (current[curLast] === line) {
+            current.pop();
+            curLast--;
+        }
+        else {
+            break;
+        }
+    }
+
+    current.push("From previous event:");
+    var lines = current.concat(prev);
+
+    var ret = [];
+
+
+    for (var i = 0, len = lines.length; i < len; ++i) {
+
+        if ((rignore.test(lines[i]) ||
+            (i > 0 && !rtraceline.test(lines[i])) &&
+            lines[i] !== "From previous event:")
+       ) {
+            continue;
+        }
+        ret.push(lines[i]);
+    }
+    return ret;
+};
+
+CapturedTrace.isSupported = function CapturedTrace$IsSupported() {
+    return typeof captureStackTrace === "function";
+};
+
+var captureStackTrace = (function stackDetection() {
+    if (typeof Error.stackTraceLimit === "number" &&
+        typeof Error.captureStackTrace === "function") {
+        rtraceline = /^\s*at\s*/;
+        formatStack = function(stack, error) {
+            if (typeof stack === "string") return stack;
+
+            if (error.name !== void 0 &&
+                error.message !== void 0) {
+                return error.name + ". " + error.message;
+            }
+            return formatNonError(error);
+
+
+        };
+        var captureStackTrace = Error.captureStackTrace;
+        return function CapturedTrace$_captureStackTrace(
+            receiver, ignoreUntil) {
+            captureStackTrace(receiver, ignoreUntil);
+        };
+    }
+    var err = new Error();
+
+    if (!areNamesMangled && typeof err.stack === "string" &&
+        typeof "".startsWith === "function" &&
+        (err.stack.startsWith("stackDetection@")) &&
+        stackDetection.name === "stackDetection") {
+
+        defineProperty(Error, "stackTraceLimit", {
+            writable: true,
+            enumerable: false,
+            configurable: false,
+            value: 25
+        });
+        rtraceline = /@/;
+        var rline = /[@\n]/;
+
+        formatStack = function(stack, error) {
+            if (typeof stack === "string") {
+                return (error.name + ". " + error.message + "\n" + stack);
+            }
+
+            if (error.name !== void 0 &&
+                error.message !== void 0) {
+                return error.name + ". " + error.message;
+            }
+            return formatNonError(error);
+        };
+
+        return function captureStackTrace(o, fn) {
+            var name = fn.name;
+            var stack = new Error().stack;
+            var split = stack.split(rline);
+            var i, len = split.length;
+            for (i = 0; i < len; i += 2) {
+                if (split[i] === name) {
+                    break;
+                }
+            }
+            split = split.slice(i + 2);
+            len = split.length - 2;
+            var ret = "";
+            for (i = 0; i < len; i += 2) {
+                ret += split[i];
+                ret += "@";
+                ret += split[i + 1];
+                ret += "\n";
+            }
+            o.stack = ret;
+        };
+    }
+    else {
+        formatStack = function(stack, error) {
+            if (typeof stack === "string") return stack;
+
+            if ((typeof error === "object" ||
+                typeof error === "function") &&
+                error.name !== void 0 &&
+                error.message !== void 0) {
+                return error.name + ". " + error.message;
+            }
+            return formatNonError(error);
+        };
+
+        return null;
+    }
+})();
+
+return CapturedTrace;
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/catch_filter.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/catch_filter.js
new file mode 100644
index 0000000..8b42af1
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/catch_filter.js
@@ -0,0 +1,93 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(NEXT_FILTER) {
+var util = require("./util.js");
+var errors = require("./errors.js");
+var tryCatch1 = util.tryCatch1;
+var errorObj = util.errorObj;
+var keys = require("./es5.js").keys;
+
+function CatchFilter(instances, callback, promise) {
+    this._instances = instances;
+    this._callback = callback;
+    this._promise = promise;
+}
+
+function CatchFilter$_safePredicate(predicate, e) {
+    var safeObject = {};
+    var retfilter = tryCatch1(predicate, safeObject, e);
+
+    if (retfilter === errorObj) return retfilter;
+
+    var safeKeys = keys(safeObject);
+    if (safeKeys.length) {
+        errorObj.e = new TypeError(
+            "Catch filter must inherit from Error "
+          + "or be a simple predicate function");
+        return errorObj;
+    }
+    return retfilter;
+}
+
+CatchFilter.prototype.doFilter = function CatchFilter$_doFilter(e) {
+    var cb = this._callback;
+    var promise = this._promise;
+    var boundTo = promise._isBound() ? promise._boundTo : void 0;
+    for (var i = 0, len = this._instances.length; i < len; ++i) {
+        var item = this._instances[i];
+        var itemIsErrorType = item === Error ||
+            (item != null && item.prototype instanceof Error);
+
+        if (itemIsErrorType && e instanceof item) {
+            var ret = tryCatch1(cb, boundTo, e);
+            if (ret === errorObj) {
+                NEXT_FILTER.e = ret.e;
+                return NEXT_FILTER;
+            }
+            return ret;
+        } else if (typeof item === "function" && !itemIsErrorType) {
+            var shouldHandle = CatchFilter$_safePredicate(item, e);
+            if (shouldHandle === errorObj) {
+                var trace = errors.canAttach(errorObj.e)
+                    ? errorObj.e
+                    : new Error(errorObj.e + "");
+                this._promise._attachExtraTrace(trace);
+                e = errorObj.e;
+                break;
+            } else if (shouldHandle) {
+                var ret = tryCatch1(cb, boundTo, e);
+                if (ret === errorObj) {
+                    NEXT_FILTER.e = ret.e;
+                    return NEXT_FILTER;
+                }
+                return ret;
+            }
+        }
+    }
+    NEXT_FILTER.e = e;
+    return NEXT_FILTER;
+};
+
+return CatchFilter;
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/direct_resolve.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/direct_resolve.js
new file mode 100644
index 0000000..f4d5384
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/direct_resolve.js
@@ -0,0 +1,83 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var util = require("./util.js");
+var isPrimitive = util.isPrimitive;
+var wrapsPrimitiveReceiver = util.wrapsPrimitiveReceiver;
+
+module.exports = function(Promise) {
+var returner = function Promise$_returner() {
+    return this;
+};
+var thrower = function Promise$_thrower() {
+    throw this;
+};
+
+var wrapper = function Promise$_wrapper(value, action) {
+    if (action === 1) {
+        return function Promise$_thrower() {
+            throw value;
+        };
+    }
+    else if (action === 2) {
+        return function Promise$_returner() {
+            return value;
+        };
+    }
+};
+
+
+Promise.prototype["return"] =
+Promise.prototype.thenReturn =
+function Promise$thenReturn(value) {
+    if (wrapsPrimitiveReceiver && isPrimitive(value)) {
+        return this._then(
+            wrapper(value, 2),
+            void 0,
+            void 0,
+            void 0,
+            void 0,
+            this.thenReturn
+       );
+    }
+    return this._then(returner, void 0, void 0,
+                        value, void 0, this.thenReturn);
+};
+
+Promise.prototype["throw"] =
+Promise.prototype.thenThrow =
+function Promise$thenThrow(reason) {
+    if (wrapsPrimitiveReceiver && isPrimitive(reason)) {
+        return this._then(
+            wrapper(reason, 1),
+            void 0,
+            void 0,
+            void 0,
+            void 0,
+            this.thenThrow
+       );
+    }
+    return this._then(thrower, void 0, void 0,
+                        reason, void 0, this.thenThrow);
+};
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/errors.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/errors.js
new file mode 100644
index 0000000..35fb66e
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/errors.js
@@ -0,0 +1,114 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var global = require("./global.js");
+var Objectfreeze = require("./es5.js").freeze;
+var util = require("./util.js");
+var inherits = util.inherits;
+var notEnumerableProp = util.notEnumerableProp;
+var Error = global.Error;
+
+function markAsOriginatingFromRejection(e) {
+    try {
+        notEnumerableProp(e, "isAsync", true);
+    }
+    catch(ignore) {}
+}
+
+function originatesFromRejection(e) {
+    if (e == null) return false;
+    return ((e instanceof RejectionError) ||
+        e["isAsync"] === true);
+}
+
+function isError(obj) {
+    return obj instanceof Error;
+}
+
+function canAttach(obj) {
+    return isError(obj);
+}
+
+function subError(nameProperty, defaultMessage) {
+    function SubError(message) {
+        if (!(this instanceof SubError)) return new SubError(message);
+        this.message = typeof message === "string" ? message : defaultMessage;
+        this.name = nameProperty;
+        if (Error.captureStackTrace) {
+            Error.captureStackTrace(this, this.constructor);
+        }
+    }
+    inherits(SubError, Error);
+    return SubError;
+}
+
+var TypeError = global.TypeError;
+if (typeof TypeError !== "function") {
+    TypeError = subError("TypeError", "type error");
+}
+var RangeError = global.RangeError;
+if (typeof RangeError !== "function") {
+    RangeError = subError("RangeError", "range error");
+}
+var CancellationError = subError("CancellationError", "cancellation error");
+var TimeoutError = subError("TimeoutError", "timeout error");
+
+function RejectionError(message) {
+    this.name = "RejectionError";
+    this.message = message;
+    this.cause = message;
+    this.isAsync = true;
+
+    if (message instanceof Error) {
+        this.message = message.message;
+        this.stack = message.stack;
+    }
+    else if (Error.captureStackTrace) {
+        Error.captureStackTrace(this, this.constructor);
+    }
+
+}
+inherits(RejectionError, Error);
+
+var key = "__BluebirdErrorTypes__";
+var errorTypes = global[key];
+if (!errorTypes) {
+    errorTypes = Objectfreeze({
+        CancellationError: CancellationError,
+        TimeoutError: TimeoutError,
+        RejectionError: RejectionError
+    });
+    notEnumerableProp(global, key, errorTypes);
+}
+
+module.exports = {
+    Error: Error,
+    TypeError: TypeError,
+    RangeError: RangeError,
+    CancellationError: errorTypes.CancellationError,
+    RejectionError: errorTypes.RejectionError,
+    TimeoutError: errorTypes.TimeoutError,
+    originatesFromRejection: originatesFromRejection,
+    markAsOriginatingFromRejection: markAsOriginatingFromRejection,
+    canAttach: canAttach
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/errors_api_rejection.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/errors_api_rejection.js
new file mode 100644
index 0000000..e953e3b
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/errors_api_rejection.js
@@ -0,0 +1,38 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise) {
+var TypeError = require('./errors.js').TypeError;
+
+function apiRejection(msg) {
+    var error = new TypeError(msg);
+    var ret = Promise.rejected(error);
+    var parent = ret._peekContext();
+    if (parent != null) {
+        parent._attachExtraTrace(error);
+    }
+    return ret;
+}
+
+return apiRejection;
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/es5.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/es5.js
new file mode 100644
index 0000000..e22a0a9
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/es5.js
@@ -0,0 +1,89 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+var isES5 = (function(){
+    "use strict";
+    return this === void 0;
+})();
+
+if (isES5) {
+    module.exports = {
+        freeze: Object.freeze,
+        defineProperty: Object.defineProperty,
+        keys: Object.keys,
+        getPrototypeOf: Object.getPrototypeOf,
+        isArray: Array.isArray,
+        isES5: isES5
+    };
+}
+
+else {
+    var has = {}.hasOwnProperty;
+    var str = {}.toString;
+    var proto = {}.constructor.prototype;
+
+    function ObjectKeys(o) {
+        var ret = [];
+        for (var key in o) {
+            if (has.call(o, key)) {
+                ret.push(key);
+            }
+        }
+        return ret;
+    }
+
+    function ObjectDefineProperty(o, key, desc) {
+        o[key] = desc.value;
+        return o;
+    }
+
+    function ObjectFreeze(obj) {
+        return obj;
+    }
+
+    function ObjectGetPrototypeOf(obj) {
+        try {
+            return Object(obj).constructor.prototype;
+        }
+        catch (e) {
+            return proto;
+        }
+    }
+
+    function ArrayIsArray(obj) {
+        try {
+            return str.call(obj) === "[object Array]";
+        }
+        catch(e) {
+            return false;
+        }
+    }
+
+    module.exports = {
+        isArray: ArrayIsArray,
+        keys: ObjectKeys,
+        defineProperty: ObjectDefineProperty,
+        freeze: ObjectFreeze,
+        getPrototypeOf: ObjectGetPrototypeOf,
+        isES5: isES5
+    };
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/filter.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/filter.js
new file mode 100644
index 0000000..a4b8ae7
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/filter.js
@@ -0,0 +1,53 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise) {
+    var isArray = require("./util.js").isArray;
+
+    function Promise$_filter(booleans) {
+        var values = this._settledValue;
+        var len = values.length;
+        var ret = new Array(len);
+        var j = 0;
+
+        for (var i = 0; i < len; ++i) {
+            if (booleans[i]) ret[j++] = values[i];
+
+        }
+        ret.length = j;
+        return ret;
+    }
+
+    var ref = {ref: null};
+    Promise.filter = function Promise$Filter(promises, fn) {
+        return Promise.map(promises, fn, ref)
+            ._then(Promise$_filter, void 0, void 0,
+                    ref.ref, void 0, Promise.filter);
+    };
+
+    Promise.prototype.filter = function Promise$filter(fn) {
+        return this.map(fn, ref)
+            ._then(Promise$_filter, void 0, void 0,
+                    ref.ref, void 0, this.filter);
+    };
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/finally.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/finally.js
new file mode 100644
index 0000000..ef1e0ef
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/finally.js
@@ -0,0 +1,132 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, NEXT_FILTER) {
+    var util = require("./util.js");
+    var wrapsPrimitiveReceiver = util.wrapsPrimitiveReceiver;
+    var isPrimitive = util.isPrimitive;
+    var thrower = util.thrower;
+
+
+    function returnThis() {
+        return this;
+    }
+    function throwThis() {
+        throw this;
+    }
+    function makeReturner(r) {
+        return function Promise$_returner() {
+            return r;
+        };
+    }
+    function makeThrower(r) {
+        return function Promise$_thrower() {
+            throw r;
+        };
+    }
+    function promisedFinally(ret, reasonOrValue, isFulfilled) {
+        var useConstantFunction =
+                        wrapsPrimitiveReceiver && isPrimitive(reasonOrValue);
+
+        if (isFulfilled) {
+            return ret._then(
+                useConstantFunction
+                    ? returnThis
+                    : makeReturner(reasonOrValue),
+                thrower, void 0, reasonOrValue, void 0, promisedFinally);
+        }
+        else {
+            return ret._then(
+                useConstantFunction
+                    ? throwThis
+                    : makeThrower(reasonOrValue),
+                thrower, void 0, reasonOrValue, void 0, promisedFinally);
+        }
+    }
+
+    function finallyHandler(reasonOrValue) {
+        var promise = this.promise;
+        var handler = this.handler;
+
+        var ret = promise._isBound()
+                        ? handler.call(promise._boundTo)
+                        : handler();
+
+        if (ret !== void 0) {
+            var maybePromise = Promise._cast(ret, finallyHandler, void 0);
+            if (maybePromise instanceof Promise) {
+                return promisedFinally(maybePromise, reasonOrValue,
+                                        promise.isFulfilled());
+            }
+        }
+
+        if (promise.isRejected()) {
+            NEXT_FILTER.e = reasonOrValue;
+            return NEXT_FILTER;
+        }
+        else {
+            return reasonOrValue;
+        }
+    }
+
+    function tapHandler(value) {
+        var promise = this.promise;
+        var handler = this.handler;
+
+        var ret = promise._isBound()
+                        ? handler.call(promise._boundTo, value)
+                        : handler(value);
+
+        if (ret !== void 0) {
+            var maybePromise = Promise._cast(ret, tapHandler, void 0);
+            if (maybePromise instanceof Promise) {
+                return promisedFinally(maybePromise, value, true);
+            }
+        }
+        return value;
+    }
+
+    Promise.prototype._passThroughHandler =
+    function Promise$_passThroughHandler(handler, isFinally, caller) {
+        if (typeof handler !== "function") return this.then();
+
+        var promiseAndHandler = {
+            promise: this,
+            handler: handler
+        };
+
+        return this._then(
+                isFinally ? finallyHandler : tapHandler,
+                isFinally ? finallyHandler : void 0, void 0,
+                promiseAndHandler, void 0, caller);
+    };
+
+    Promise.prototype.lastly =
+    Promise.prototype["finally"] = function Promise$finally(handler) {
+        return this._passThroughHandler(handler, true, this.lastly);
+    };
+
+    Promise.prototype.tap = function Promise$tap(handler) {
+        return this._passThroughHandler(handler, false, this.tap);
+    };
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/generators.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/generators.js
new file mode 100644
index 0000000..9632ae7
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/generators.js
@@ -0,0 +1,56 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, apiRejection, INTERNAL) {
+    var PromiseSpawn = require("./promise_spawn.js")(Promise, INTERNAL);
+    var errors = require("./errors.js");
+    var TypeError = errors.TypeError;
+    var deprecated = require("./util.js").deprecated;
+
+    Promise.coroutine = function Promise$Coroutine(generatorFunction) {
+        if (typeof generatorFunction !== "function") {
+            throw new TypeError("generatorFunction must be a function");
+        }
+        var PromiseSpawn$ = PromiseSpawn;
+        return function anonymous() {
+            var generator = generatorFunction.apply(this, arguments);
+            var spawn = new PromiseSpawn$(void 0, void 0, anonymous);
+            spawn._generator = generator;
+            spawn._next(void 0);
+            return spawn.promise();
+        };
+    };
+
+    Promise.coroutine.addYieldHandler = PromiseSpawn.addYieldHandler;
+
+    Promise.spawn = function Promise$Spawn(generatorFunction) {
+        deprecated("Promise.spawn is deprecated. Use Promise.coroutine instead.");
+        if (typeof generatorFunction !== "function") {
+            return apiRejection("generatorFunction must be a function");
+        }
+        var spawn = new PromiseSpawn(generatorFunction, this, Promise.spawn);
+        var ret = spawn.promise();
+        spawn._run(Promise.spawn);
+        return ret;
+    };
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/global.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/global.js
new file mode 100644
index 0000000..1ab1947
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/global.js
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = (function(){
+    if (typeof this !== "undefined") {
+        return this;
+    }
+    if (typeof process !== "undefined" &&
+        typeof global !== "undefined" &&
+        typeof process.execPath === "string") {
+        return global;
+    }
+    if (typeof window !== "undefined" &&
+        typeof document !== "undefined" &&
+        typeof navigator !== "undefined" && navigator !== null &&
+        typeof navigator.appName === "string") {
+            if(window.wrappedJSObject !== undefined){
+                return window.wrappedJSObject;
+            }
+        return window;
+    }
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/map.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/map.js
new file mode 100644
index 0000000..b2a36b0
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/map.js
@@ -0,0 +1,127 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(
+    Promise, Promise$_CreatePromiseArray, PromiseArray, apiRejection) {
+
+    function Promise$_mapper(values) {
+        var fn = this;
+        var receiver = void 0;
+
+        if (typeof fn !== "function")  {
+            receiver = fn.receiver;
+            fn = fn.fn;
+        }
+        var shouldDefer = false;
+
+        var ret = new Array(values.length);
+
+        if (receiver === void 0) {
+            for (var i = 0, len = values.length; i < len; ++i) {
+                var value = fn(values[i], i, len);
+                if (!shouldDefer) {
+                    var maybePromise = Promise._cast(value,
+                            Promise$_mapper, void 0);
+                    if (maybePromise instanceof Promise) {
+                        if (maybePromise.isFulfilled()) {
+                            ret[i] = maybePromise._settledValue;
+                            continue;
+                        }
+                        else {
+                            shouldDefer = true;
+                        }
+                        value = maybePromise;
+                    }
+                }
+                ret[i] = value;
+            }
+        }
+        else {
+            for (var i = 0, len = values.length; i < len; ++i) {
+                var value = fn.call(receiver, values[i], i, len);
+                if (!shouldDefer) {
+                    var maybePromise = Promise._cast(value,
+                            Promise$_mapper, void 0);
+                    if (maybePromise instanceof Promise) {
+                        if (maybePromise.isFulfilled()) {
+                            ret[i] = maybePromise._settledValue;
+                            continue;
+                        }
+                        else {
+                            shouldDefer = true;
+                        }
+                        value = maybePromise;
+                    }
+                }
+                ret[i] = value;
+            }
+        }
+        return shouldDefer
+            ? Promise$_CreatePromiseArray(ret, PromiseArray,
+                Promise$_mapper, void 0).promise()
+            : ret;
+    }
+
+    function Promise$_Map(promises, fn, useBound, caller, ref) {
+        if (typeof fn !== "function") {
+            return apiRejection("fn must be a function");
+        }
+
+        if (useBound === true && promises._isBound()) {
+            fn = {
+                fn: fn,
+                receiver: promises._boundTo
+            };
+        }
+
+        var ret = Promise$_CreatePromiseArray(
+            promises,
+            PromiseArray,
+            caller,
+            useBound === true && promises._isBound()
+                ? promises._boundTo
+                : void 0
+       ).promise();
+
+        if (ref !== void 0) {
+            ref.ref = ret;
+        }
+
+        return ret._then(
+            Promise$_mapper,
+            void 0,
+            void 0,
+            fn,
+            void 0,
+            caller
+       );
+    }
+
+    Promise.prototype.map = function Promise$map(fn, ref) {
+        return Promise$_Map(this, fn, true, this.map, ref);
+    };
+
+    Promise.map = function Promise$Map(promises, fn, ref) {
+        return Promise$_Map(promises, fn, false, Promise.map, ref);
+    };
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/nodeify.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/nodeify.js
new file mode 100644
index 0000000..9fe25f9
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/nodeify.js
@@ -0,0 +1,64 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise) {
+    var util = require("./util.js");
+    var async = require("./async.js");
+    var tryCatch2 = util.tryCatch2;
+    var tryCatch1 = util.tryCatch1;
+    var errorObj = util.errorObj;
+
+    function thrower(r) {
+        throw r;
+    }
+
+    function Promise$_successAdapter(val, receiver) {
+        var nodeback = this;
+        var ret = tryCatch2(nodeback, receiver, null, val);
+        if (ret === errorObj) {
+            async.invokeLater(thrower, void 0, ret.e);
+        }
+    }
+    function Promise$_errorAdapter(reason, receiver) {
+        var nodeback = this;
+        var ret = tryCatch1(nodeback, receiver, reason);
+        if (ret === errorObj) {
+            async.invokeLater(thrower, void 0, ret.e);
+        }
+    }
+
+    Promise.prototype.nodeify = function Promise$nodeify(nodeback) {
+        if (typeof nodeback == "function") {
+            this._then(
+                Promise$_successAdapter,
+                Promise$_errorAdapter,
+                void 0,
+                nodeback,
+                this._isBound() ? this._boundTo : null,
+                this.nodeify
+            );
+            return;
+        }
+        return this;
+    };
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/progress.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/progress.js
new file mode 100644
index 0000000..106bc58
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/progress.js
@@ -0,0 +1,113 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, isPromiseArrayProxy) {
+    var util = require("./util.js");
+    var async = require("./async.js");
+    var errors = require("./errors.js");
+    var tryCatch1 = util.tryCatch1;
+    var errorObj = util.errorObj;
+
+    Promise.prototype.progressed = function Promise$progressed(handler) {
+        return this._then(void 0, void 0, handler,
+                            void 0, void 0, this.progressed);
+    };
+
+    Promise.prototype._progress = function Promise$_progress(progressValue) {
+        if (this._isFollowingOrFulfilledOrRejected()) return;
+        this._progressUnchecked(progressValue);
+
+    };
+
+    Promise.prototype._progressHandlerAt =
+    function Promise$_progressHandlerAt(index) {
+        if (index === 0) return this._progressHandler0;
+        return this[index + 2 - 5];
+    };
+
+    Promise.prototype._doProgressWith =
+    function Promise$_doProgressWith(progression) {
+        var progressValue = progression.value;
+        var handler = progression.handler;
+        var promise = progression.promise;
+        var receiver = progression.receiver;
+
+        this._pushContext();
+        var ret = tryCatch1(handler, receiver, progressValue);
+        this._popContext();
+
+        if (ret === errorObj) {
+            if (ret.e != null &&
+                ret.e.name !== "StopProgressPropagation") {
+                var trace = errors.canAttach(ret.e)
+                    ? ret.e : new Error(ret.e + "");
+                promise._attachExtraTrace(trace);
+                promise._progress(ret.e);
+            }
+        }
+        else if (Promise.is(ret)) {
+            ret._then(promise._progress, null, null, promise, void 0,
+                this._progress);
+        }
+        else {
+            promise._progress(ret);
+        }
+    };
+
+
+    Promise.prototype._progressUnchecked =
+    function Promise$_progressUnchecked(progressValue) {
+        if (!this.isPending()) return;
+        var len = this._length();
+
+        for (var i = 0; i < len; i += 5) {
+            var handler = this._progressHandlerAt(i);
+            var promise = this._promiseAt(i);
+            if (!Promise.is(promise)) {
+                var receiver = this._receiverAt(i);
+                if (typeof handler === "function") {
+                    handler.call(receiver, progressValue, promise);
+                }
+                else if (Promise.is(receiver) && receiver._isProxied()) {
+                    receiver._progressUnchecked(progressValue);
+                }
+                else if (isPromiseArrayProxy(receiver, promise)) {
+                    receiver._promiseProgressed(progressValue, promise);
+                }
+                continue;
+            }
+
+            if (typeof handler === "function") {
+                async.invoke(this._doProgressWith, this, {
+                    handler: handler,
+                    promise: promise,
+                    receiver: this._receiverAt(i),
+                    value: progressValue
+                });
+            }
+            else {
+                async.invoke(promise._progress, promise, progressValue);
+            }
+        }
+    };
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/promise.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/promise.js
new file mode 100644
index 0000000..a5fe1d6
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/promise.js
@@ -0,0 +1,1167 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function() {
+var global = require("./global.js");
+var util = require("./util.js");
+var async = require("./async.js");
+var errors = require("./errors.js");
+
+var INTERNAL = function(){};
+var APPLY = {};
+var NEXT_FILTER = {e: null};
+
+var PromiseArray = require("./promise_array.js")(Promise, INTERNAL);
+var CapturedTrace = require("./captured_trace.js")();
+var CatchFilter = require("./catch_filter.js")(NEXT_FILTER);
+var PromiseResolver = require("./promise_resolver.js");
+
+var isArray = util.isArray;
+
+var errorObj = util.errorObj;
+var tryCatch1 = util.tryCatch1;
+var tryCatch2 = util.tryCatch2;
+var tryCatchApply = util.tryCatchApply;
+var RangeError = errors.RangeError;
+var TypeError = errors.TypeError;
+var CancellationError = errors.CancellationError;
+var TimeoutError = errors.TimeoutError;
+var RejectionError = errors.RejectionError;
+var originatesFromRejection = errors.originatesFromRejection;
+var markAsOriginatingFromRejection = errors.markAsOriginatingFromRejection;
+var canAttach = errors.canAttach;
+var thrower = util.thrower;
+var apiRejection = require("./errors_api_rejection")(Promise);
+
+
+var makeSelfResolutionError = function Promise$_makeSelfResolutionError() {
+    return new TypeError("circular promise resolution chain");
+};
+
+function isPromise(obj) {
+    if (obj === void 0) return false;
+    return obj instanceof Promise;
+}
+
+function isPromiseArrayProxy(receiver, promiseSlotValue) {
+    if (receiver instanceof PromiseArray) {
+        return promiseSlotValue >= 0;
+    }
+    return false;
+}
+
+function Promise(resolver) {
+    if (typeof resolver !== "function") {
+        throw new TypeError("the promise constructor requires a resolver function");
+    }
+    if (this.constructor !== Promise) {
+        throw new TypeError("the promise constructor cannot be invoked directly");
+    }
+    this._bitField = 0;
+    this._fulfillmentHandler0 = void 0;
+    this._rejectionHandler0 = void 0;
+    this._promise0 = void 0;
+    this._receiver0 = void 0;
+    this._settledValue = void 0;
+    this._boundTo = void 0;
+    if (resolver !== INTERNAL) this._resolveFromResolver(resolver);
+}
+
+Promise.prototype.bind = function Promise$bind(thisArg) {
+    var ret = new Promise(INTERNAL);
+    if (debugging) ret._setTrace(this.bind, this);
+    ret._follow(this);
+    ret._setBoundTo(thisArg);
+    if (this._cancellable()) {
+        ret._setCancellable();
+        ret._cancellationParent = this;
+    }
+    return ret;
+};
+
+Promise.prototype.toString = function Promise$toString() {
+    return "[object Promise]";
+};
+
+Promise.prototype.caught = Promise.prototype["catch"] =
+function Promise$catch(fn) {
+    var len = arguments.length;
+    if (len > 1) {
+        var catchInstances = new Array(len - 1),
+            j = 0, i;
+        for (i = 0; i < len - 1; ++i) {
+            var item = arguments[i];
+            if (typeof item === "function") {
+                catchInstances[j++] = item;
+            }
+            else {
+                var catchFilterTypeError =
+                    new TypeError(
+                        "A catch filter must be an error constructor "
+                        + "or a filter function");
+
+                this._attachExtraTrace(catchFilterTypeError);
+                async.invoke(this._reject, this, catchFilterTypeError);
+                return;
+            }
+        }
+        catchInstances.length = j;
+        fn = arguments[i];
+
+        this._resetTrace(this.caught);
+        var catchFilter = new CatchFilter(catchInstances, fn, this);
+        return this._then(void 0, catchFilter.doFilter, void 0,
+            catchFilter, void 0, this.caught);
+    }
+    return this._then(void 0, fn, void 0, void 0, void 0, this.caught);
+};
+
+Promise.prototype.then =
+function Promise$then(didFulfill, didReject, didProgress) {
+    return this._then(didFulfill, didReject, didProgress,
+        void 0, void 0, this.then);
+};
+
+
+Promise.prototype.done =
+function Promise$done(didFulfill, didReject, didProgress) {
+    var promise = this._then(didFulfill, didReject, didProgress,
+        void 0, void 0, this.done);
+    promise._setIsFinal();
+};
+
+Promise.prototype.spread = function Promise$spread(didFulfill, didReject) {
+    return this._then(didFulfill, didReject, void 0,
+        APPLY, void 0, this.spread);
+};
+
+Promise.prototype.isFulfilled = function Promise$isFulfilled() {
+    return (this._bitField & 268435456) > 0;
+};
+
+
+Promise.prototype.isRejected = function Promise$isRejected() {
+    return (this._bitField & 134217728) > 0;
+};
+
+Promise.prototype.isPending = function Promise$isPending() {
+    return !this.isResolved();
+};
+
+
+Promise.prototype.isResolved = function Promise$isResolved() {
+    return (this._bitField & 402653184) > 0;
+};
+
+
+Promise.prototype.isCancellable = function Promise$isCancellable() {
+    return !this.isResolved() &&
+        this._cancellable();
+};
+
+Promise.prototype.toJSON = function Promise$toJSON() {
+    var ret = {
+        isFulfilled: false,
+        isRejected: false,
+        fulfillmentValue: void 0,
+        rejectionReason: void 0
+    };
+    if (this.isFulfilled()) {
+        ret.fulfillmentValue = this._settledValue;
+        ret.isFulfilled = true;
+    }
+    else if (this.isRejected()) {
+        ret.rejectionReason = this._settledValue;
+        ret.isRejected = true;
+    }
+    return ret;
+};
+
+Promise.prototype.all = function Promise$all() {
+    return Promise$_all(this, true, this.all);
+};
+
+
+Promise.is = isPromise;
+
+function Promise$_all(promises, useBound, caller) {
+    return Promise$_CreatePromiseArray(
+        promises,
+        PromiseArray,
+        caller,
+        useBound === true && promises._isBound()
+            ? promises._boundTo
+            : void 0
+   ).promise();
+}
+Promise.all = function Promise$All(promises) {
+    return Promise$_all(promises, false, Promise.all);
+};
+
+Promise.join = function Promise$Join() {
+    var $_len = arguments.length;var args = new Array($_len); for(var $_i = 0; $_i < $_len; ++$_i) {args[$_i] = arguments[$_i];}
+    return Promise$_CreatePromiseArray(
+        args, PromiseArray, Promise.join, void 0).promise();
+};
+
+Promise.resolve = Promise.fulfilled =
+function Promise$Resolve(value, caller) {
+    var ret = new Promise(INTERNAL);
+    if (debugging) ret._setTrace(typeof caller === "function"
+        ? caller
+        : Promise.resolve, void 0);
+    if (ret._tryFollow(value)) {
+        return ret;
+    }
+    ret._cleanValues();
+    ret._setFulfilled();
+    ret._settledValue = value;
+    return ret;
+};
+
+Promise.reject = Promise.rejected = function Promise$Reject(reason) {
+    var ret = new Promise(INTERNAL);
+    if (debugging) ret._setTrace(Promise.reject, void 0);
+    markAsOriginatingFromRejection(reason);
+    ret._cleanValues();
+    ret._setRejected();
+    ret._settledValue = reason;
+    if (!canAttach(reason)) {
+        var trace = new Error(reason + "");
+        ret._setCarriedStackTrace(trace);
+    }
+    ret._ensurePossibleRejectionHandled();
+    return ret;
+};
+
+Promise.prototype.error = function Promise$_error(fn) {
+    return this.caught(originatesFromRejection, fn);
+};
+
+Promise.prototype._resolveFromSyncValue =
+function Promise$_resolveFromSyncValue(value, caller) {
+    if (value === errorObj) {
+        this._cleanValues();
+        this._setRejected();
+        this._settledValue = value.e;
+        this._ensurePossibleRejectionHandled();
+    }
+    else {
+        var maybePromise = Promise._cast(value, caller, void 0);
+        if (maybePromise instanceof Promise) {
+            this._follow(maybePromise);
+        }
+        else {
+            this._cleanValues();
+            this._setFulfilled();
+            this._settledValue = value;
+        }
+    }
+};
+
+Promise.method = function Promise$_Method(fn) {
+    if (typeof fn !== "function") {
+        throw new TypeError("fn must be a function");
+    }
+    return function Promise$_method() {
+        var value;
+        switch(arguments.length) {
+        case 0: value = tryCatch1(fn, this, void 0); break;
+        case 1: value = tryCatch1(fn, this, arguments[0]); break;
+        case 2: value = tryCatch2(fn, this, arguments[0], arguments[1]); break;
+        default:
+            var $_len = arguments.length;var args = new Array($_len); for(var $_i = 0; $_i < $_len; ++$_i) {args[$_i] = arguments[$_i];}
+            value = tryCatchApply(fn, args, this); break;
+        }
+        var ret = new Promise(INTERNAL);
+        if (debugging) ret._setTrace(Promise$_method, void 0);
+        ret._resolveFromSyncValue(value, Promise$_method);
+        return ret;
+    };
+};
+
+Promise.attempt = Promise["try"] = function Promise$_Try(fn, args, ctx) {
+
+    if (typeof fn !== "function") {
+        return apiRejection("fn must be a function");
+    }
+    var value = isArray(args)
+        ? tryCatchApply(fn, args, ctx)
+        : tryCatch1(fn, ctx, args);
+
+    var ret = new Promise(INTERNAL);
+    if (debugging) ret._setTrace(Promise.attempt, void 0);
+    ret._resolveFromSyncValue(value, Promise.attempt);
+    return ret;
+};
+
+Promise.defer = Promise.pending = function Promise$Defer(caller) {
+    var promise = new Promise(INTERNAL);
+    if (debugging) promise._setTrace(typeof caller === "function"
+                              ? caller : Promise.defer, void 0);
+    return new PromiseResolver(promise);
+};
+
+Promise.bind = function Promise$Bind(thisArg) {
+    var ret = new Promise(INTERNAL);
+    if (debugging) ret._setTrace(Promise.bind, void 0);
+    ret._setFulfilled();
+    ret._setBoundTo(thisArg);
+    return ret;
+};
+
+Promise.cast = function Promise$_Cast(obj, caller) {
+    if (typeof caller !== "function") {
+        caller = Promise.cast;
+    }
+    var ret = Promise._cast(obj, caller, void 0);
+    if (!(ret instanceof Promise)) {
+        return Promise.resolve(ret, caller);
+    }
+    return ret;
+};
+
+Promise.onPossiblyUnhandledRejection =
+function Promise$OnPossiblyUnhandledRejection(fn) {
+    if (typeof fn === "function") {
+        CapturedTrace.possiblyUnhandledRejection = fn;
+    }
+    else {
+        CapturedTrace.possiblyUnhandledRejection = void 0;
+    }
+};
+
+var debugging = false || !!(
+    typeof process !== "undefined" &&
+    typeof process.execPath === "string" &&
+    typeof process.env === "object" &&
+    (process.env["BLUEBIRD_DEBUG"] ||
+        process.env["NODE_ENV"] === "development")
+);
+
+
+Promise.longStackTraces = function Promise$LongStackTraces() {
+    if (async.haveItemsQueued() &&
+        debugging === false
+   ) {
+        throw new Error("cannot enable long stack traces after promises have been created");
+    }
+    debugging = CapturedTrace.isSupported();
+};
+
+Promise.hasLongStackTraces = function Promise$HasLongStackTraces() {
+    return debugging && CapturedTrace.isSupported();
+};
+
+Promise.prototype._setProxyHandlers =
+function Promise$_setProxyHandlers(receiver, promiseSlotValue) {
+    var index = this._length();
+
+    if (index >= 1048575 - 5) {
+        index = 0;
+        this._setLength(0);
+    }
+    if (index === 0) {
+        this._promise0 = promiseSlotValue;
+        this._receiver0 = receiver;
+    }
+    else {
+        var i = index - 5;
+        this[i + 3] = promiseSlotValue;
+        this[i + 4] = receiver;
+        this[i + 0] =
+        this[i + 1] =
+        this[i + 2] = void 0;
+    }
+    this._setLength(index + 5);
+};
+
+Promise.prototype._proxyPromiseArray =
+function Promise$_proxyPromiseArray(promiseArray, index) {
+    this._setProxyHandlers(promiseArray, index);
+};
+
+Promise.prototype._proxyPromise = function Promise$_proxyPromise(promise) {
+    promise._setProxied();
+    this._setProxyHandlers(promise, -1);
+};
+
+Promise.prototype._then =
+function Promise$_then(
+    didFulfill,
+    didReject,
+    didProgress,
+    receiver,
+    internalData,
+    caller
+) {
+    var haveInternalData = internalData !== void 0;
+    var ret = haveInternalData ? internalData : new Promise(INTERNAL);
+
+    if (debugging && !haveInternalData) {
+        var haveSameContext = this._peekContext() === this._traceParent;
+        ret._traceParent = haveSameContext ? this._traceParent : this;
+        ret._setTrace(typeof caller === "function"
+                ? caller
+                : this._then, this);
+    }
+
+    if (!haveInternalData && this._isBound()) {
+        ret._setBoundTo(this._boundTo);
+    }
+
+    var callbackIndex =
+        this._addCallbacks(didFulfill, didReject, didProgress, ret, receiver);
+
+    if (!haveInternalData && this._cancellable()) {
+        ret._setCancellable();
+        ret._cancellationParent = this;
+    }
+
+    if (this.isResolved()) {
+        async.invoke(this._queueSettleAt, this, callbackIndex);
+    }
+
+    return ret;
+};
+
+Promise.prototype._length = function Promise$_length() {
+    return this._bitField & 1048575;
+};
+
+Promise.prototype._isFollowingOrFulfilledOrRejected =
+function Promise$_isFollowingOrFulfilledOrRejected() {
+    return (this._bitField & 939524096) > 0;
+};
+
+Promise.prototype._isFollowing = function Promise$_isFollowing() {
+    return (this._bitField & 536870912) === 536870912;
+};
+
+Promise.prototype._setLength = function Promise$_setLength(len) {
+    this._bitField = (this._bitField & -1048576) |
+        (len & 1048575);
+};
+
+Promise.prototype._setFulfilled = function Promise$_setFulfilled() {
+    this._bitField = this._bitField | 268435456;
+};
+
+Promise.prototype._setRejected = function Promise$_setRejected() {
+    this._bitField = this._bitField | 134217728;
+};
+
+Promise.prototype._setFollowing = function Promise$_setFollowing() {
+    this._bitField = this._bitField | 536870912;
+};
+
+Promise.prototype._setIsFinal = function Promise$_setIsFinal() {
+    this._bitField = this._bitField | 33554432;
+};
+
+Promise.prototype._isFinal = function Promise$_isFinal() {
+    return (this._bitField & 33554432) > 0;
+};
+
+Promise.prototype._cancellable = function Promise$_cancellable() {
+    return (this._bitField & 67108864) > 0;
+};
+
+Promise.prototype._setCancellable = function Promise$_setCancellable() {
+    this._bitField = this._bitField | 67108864;
+};
+
+Promise.prototype._unsetCancellable = function Promise$_unsetCancellable() {
+    this._bitField = this._bitField & (~67108864);
+};
+
+Promise.prototype._setRejectionIsUnhandled =
+function Promise$_setRejectionIsUnhandled() {
+    this._bitField = this._bitField | 2097152;
+};
+
+Promise.prototype._unsetRejectionIsUnhandled =
+function Promise$_unsetRejectionIsUnhandled() {
+    this._bitField = this._bitField & (~2097152);
+};
+
+Promise.prototype._isRejectionUnhandled =
+function Promise$_isRejectionUnhandled() {
+    return (this._bitField & 2097152) > 0;
+};
+
+Promise.prototype._setCarriedStackTrace =
+function Promise$_setCarriedStackTrace(capturedTrace) {
+    this._bitField = this._bitField | 1048576;
+    this._fulfillmentHandler0 = capturedTrace;
+};
+
+Promise.prototype._unsetCarriedStackTrace =
+function Promise$_unsetCarriedStackTrace() {
+    this._bitField = this._bitField & (~1048576);
+    this._fulfillmentHandler0 = void 0;
+};
+
+Promise.prototype._isCarryingStackTrace =
+function Promise$_isCarryingStackTrace() {
+    return (this._bitField & 1048576) > 0;
+};
+
+Promise.prototype._getCarriedStackTrace =
+function Promise$_getCarriedStackTrace() {
+    return this._isCarryingStackTrace()
+        ? this._fulfillmentHandler0
+        : void 0;
+};
+
+Promise.prototype._receiverAt = function Promise$_receiverAt(index) {
+    var ret;
+    if (index === 0) {
+        ret = this._receiver0;
+    }
+    else {
+        ret = this[index + 4 - 5];
+    }
+    if (this._isBound() && ret === void 0) {
+        return this._boundTo;
+    }
+    return ret;
+};
+
+Promise.prototype._promiseAt = function Promise$_promiseAt(index) {
+    if (index === 0) return this._promise0;
+    return this[index + 3 - 5];
+};
+
+Promise.prototype._fulfillmentHandlerAt =
+function Promise$_fulfillmentHandlerAt(index) {
+    if (index === 0) return this._fulfillmentHandler0;
+    return this[index + 0 - 5];
+};
+
+Promise.prototype._rejectionHandlerAt =
+function Promise$_rejectionHandlerAt(index) {
+    if (index === 0) return this._rejectionHandler0;
+    return this[index + 1 - 5];
+};
+
+Promise.prototype._unsetAt = function Promise$_unsetAt(index) {
+     if (index === 0) {
+        this._rejectionHandler0 =
+        this._progressHandler0 =
+        this._promise0 =
+        this._receiver0 = void 0;
+        if (!this._isCarryingStackTrace()) {
+            this._fulfillmentHandler0 = void 0;
+        }
+    }
+    else {
+        this[index - 5 + 0] =
+        this[index - 5 + 1] =
+        this[index - 5 + 2] =
+        this[index - 5 + 3] =
+        this[index - 5 + 4] = void 0;
+    }
+};
+
+Promise.prototype._resolveFromResolver =
+function Promise$_resolveFromResolver(resolver) {
+    var promise = this;
+    var localDebugging = debugging;
+    if (localDebugging) {
+        this._setTrace(this._resolveFromResolver, void 0);
+        this._pushContext();
+    }
+    function Promise$_resolver(val) {
+        if (promise._tryFollow(val)) {
+            return;
+        }
+        promise._fulfill(val);
+    }
+    function Promise$_rejecter(val) {
+        var trace = canAttach(val) ? val : new Error(val + "");
+        promise._attachExtraTrace(trace);
+        markAsOriginatingFromRejection(val);
+        promise._reject(val, trace === val ? void 0 : trace);
+    }
+    var r = tryCatch2(resolver, void 0, Promise$_resolver, Promise$_rejecter);
+    if (localDebugging) this._popContext();
+
+    if (r !== void 0 && r === errorObj) {
+        var e = r.e;
+        var trace = canAttach(e) ? e : new Error(e + "");
+        promise._reject(e, trace);
+    }
+};
+
+Promise.prototype._addCallbacks = function Promise$_addCallbacks(
+    fulfill,
+    reject,
+    progress,
+    promise,
+    receiver
+) {
+    var index = this._length();
+
+    if (index >= 1048575 - 5) {
+        index = 0;
+        this._setLength(0);
+    }
+
+    if (index === 0) {
+        this._promise0 = promise;
+        if (receiver !== void 0) this._receiver0 = receiver;
+        if (typeof fulfill === "function" && !this._isCarryingStackTrace())
+            this._fulfillmentHandler0 = fulfill;
+        if (typeof reject === "function") this._rejectionHandler0 = reject;
+        if (typeof progress === "function") this._progressHandler0 = progress;
+    }
+    else {
+        var i = index - 5;
+        this[i + 3] = promise;
+        this[i + 4] = receiver;
+        this[i + 0] = typeof fulfill === "function"
+                                            ? fulfill : void 0;
+        this[i + 1] = typeof reject === "function"
+                                            ? reject : void 0;
+        this[i + 2] = typeof progress === "function"
+                                            ? progress : void 0;
+    }
+    this._setLength(index + 5);
+    return index;
+};
+
+
+
+Promise.prototype._setBoundTo = function Promise$_setBoundTo(obj) {
+    if (obj !== void 0) {
+        this._bitField = this._bitField | 8388608;
+        this._boundTo = obj;
+    }
+    else {
+        this._bitField = this._bitField & (~8388608);
+    }
+};
+
+Promise.prototype._isBound = function Promise$_isBound() {
+    return (this._bitField & 8388608) === 8388608;
+};
+
+Promise.prototype._spreadSlowCase =
+function Promise$_spreadSlowCase(targetFn, promise, values, boundTo) {
+    var promiseForAll =
+            Promise$_CreatePromiseArray
+                (values, PromiseArray, this._spreadSlowCase, boundTo)
+            .promise()
+            ._then(function() {
+                return targetFn.apply(boundTo, arguments);
+            }, void 0, void 0, APPLY, void 0, this._spreadSlowCase);
+
+    promise._follow(promiseForAll);
+};
+
+Promise.prototype._callSpread =
+function Promise$_callSpread(handler, promise, value, localDebugging) {
+    var boundTo = this._isBound() ? this._boundTo : void 0;
+    if (isArray(value)) {
+        var caller = this._settlePromiseFromHandler;
+        for (var i = 0, len = value.length; i < len; ++i) {
+            if (isPromise(Promise._cast(value[i], caller, void 0))) {
+                this._spreadSlowCase(handler, promise, value, boundTo);
+                return;
+            }
+        }
+    }
+    if (localDebugging) promise._pushContext();
+    return tryCatchApply(handler, value, boundTo);
+};
+
+Promise.prototype._callHandler =
+function Promise$_callHandler(
+    handler, receiver, promise, value, localDebugging) {
+    var x;
+    if (receiver === APPLY && !this.isRejected()) {
+        x = this._callSpread(handler, promise, value, localDebugging);
+    }
+    else {
+        if (localDebugging) promise._pushContext();
+        x = tryCatch1(handler, receiver, value);
+    }
+    if (localDebugging) promise._popContext();
+    return x;
+};
+
+Promise.prototype._settlePromiseFromHandler =
+function Promise$_settlePromiseFromHandler(
+    handler, receiver, value, promise
+) {
+    if (!isPromise(promise)) {
+        handler.call(receiver, value, promise);
+        return;
+    }
+
+    var localDebugging = debugging;
+    var x = this._callHandler(handler, receiver,
+                                promise, value, localDebugging);
+
+    if (promise._isFollowing()) return;
+
+    if (x === errorObj || x === promise || x === NEXT_FILTER) {
+        var err = x === promise
+                    ? makeSelfResolutionError()
+                    : x.e;
+        var trace = canAttach(err) ? err : new Error(err + "");
+        if (x !== NEXT_FILTER) promise._attachExtraTrace(trace);
+        promise._rejectUnchecked(err, trace);
+    }
+    else {
+        var castValue = Promise._cast(x,
+                    localDebugging ? this._settlePromiseFromHandler : void 0,
+                    promise);
+
+        if (isPromise(castValue)) {
+            if (castValue.isRejected() &&
+                !castValue._isCarryingStackTrace() &&
+                !canAttach(castValue._settledValue)) {
+                var trace = new Error(castValue._settledValue + "");
+                promise._attachExtraTrace(trace);
+                castValue._setCarriedStackTrace(trace);
+            }
+            promise._follow(castValue);
+            if (castValue._cancellable()) {
+                promise._cancellationParent = castValue;
+                promise._setCancellable();
+            }
+        }
+        else {
+            promise._fulfillUnchecked(x);
+        }
+    }
+};
+
+Promise.prototype._follow =
+function Promise$_follow(promise) {
+    this._setFollowing();
+
+    if (promise.isPending()) {
+        if (promise._cancellable() ) {
+            this._cancellationParent = promise;
+            this._setCancellable();
+        }
+        promise._proxyPromise(this);
+    }
+    else if (promise.isFulfilled()) {
+        this._fulfillUnchecked(promise._settledValue);
+    }
+    else {
+        this._rejectUnchecked(promise._settledValue,
+            promise._getCarriedStackTrace());
+    }
+
+    if (promise._isRejectionUnhandled()) promise._unsetRejectionIsUnhandled();
+
+    if (debugging &&
+        promise._traceParent == null) {
+        promise._traceParent = this;
+    }
+};
+
+Promise.prototype._tryFollow =
+function Promise$_tryFollow(value) {
+    if (this._isFollowingOrFulfilledOrRejected() ||
+        value === this) {
+        return false;
+    }
+    var maybePromise = Promise._cast(value, this._tryFollow, void 0);
+    if (!isPromise(maybePromise)) {
+        return false;
+    }
+    this._follow(maybePromise);
+    return true;
+};
+
+Promise.prototype._resetTrace = function Promise$_resetTrace(caller) {
+    if (debugging) {
+        var context = this._peekContext();
+        var isTopLevel = context === void 0;
+        this._trace = new CapturedTrace(
+            typeof caller === "function"
+            ? caller
+            : this._resetTrace,
+            isTopLevel
+       );
+    }
+};
+
+Promise.prototype._setTrace = function Promise$_setTrace(caller, parent) {
+    if (debugging) {
+        var context = this._peekContext();
+        this._traceParent = context;
+        var isTopLevel = context === void 0;
+        if (parent !== void 0 &&
+            parent._traceParent === context) {
+            this._trace = parent._trace;
+        }
+        else {
+            this._trace = new CapturedTrace(
+                typeof caller === "function"
+                ? caller
+                : this._setTrace,
+                isTopLevel
+           );
+        }
+    }
+    return this;
+};
+
+Promise.prototype._attachExtraTrace =
+function Promise$_attachExtraTrace(error) {
+    if (debugging) {
+        var promise = this;
+        var stack = error.stack;
+        stack = typeof stack === "string"
+            ? stack.split("\n") : [];
+        var headerLineCount = 1;
+
+        while(promise != null &&
+            promise._trace != null) {
+            stack = CapturedTrace.combine(
+                stack,
+                promise._trace.stack.split("\n")
+           );
+            promise = promise._traceParent;
+        }
+
+        var max = Error.stackTraceLimit + headerLineCount;
+        var len = stack.length;
+        if (len  > max) {
+            stack.length = max;
+        }
+        if (stack.length <= headerLineCount) {
+            error.stack = "(No stack trace)";
+        }
+        else {
+            error.stack = stack.join("\n");
+        }
+    }
+};
+
+Promise.prototype._cleanValues = function Promise$_cleanValues() {
+    if (this._cancellable()) {
+        this._cancellationParent = void 0;
+    }
+};
+
+Promise.prototype._fulfill = function Promise$_fulfill(value) {
+    if (this._isFollowingOrFulfilledOrRejected()) return;
+    this._fulfillUnchecked(value);
+};
+
+Promise.prototype._reject =
+function Promise$_reject(reason, carriedStackTrace) {
+    if (this._isFollowingOrFulfilledOrRejected()) return;
+    this._rejectUnchecked(reason, carriedStackTrace);
+};
+
+Promise.prototype._settlePromiseAt = function Promise$_settlePromiseAt(index) {
+    var handler = this.isFulfilled()
+        ? this._fulfillmentHandlerAt(index)
+        : this._rejectionHandlerAt(index);
+
+    var value = this._settledValue;
+    var receiver = this._receiverAt(index);
+    var promise = this._promiseAt(index);
+
+    if (typeof handler === "function") {
+        this._settlePromiseFromHandler(handler, receiver, value, promise);
+    }
+    else {
+        var done = false;
+        var isFulfilled = this.isFulfilled();
+        if (receiver !== void 0) {
+            if (receiver instanceof Promise &&
+                receiver._isProxied()) {
+                receiver._unsetProxied();
+
+                if (isFulfilled) receiver._fulfillUnchecked(value);
+                else receiver._rejectUnchecked(value,
+                    this._getCarriedStackTrace());
+                done = true;
+            }
+            else if (isPromiseArrayProxy(receiver, promise)) {
+
+                if (isFulfilled) receiver._promiseFulfilled(value, promise);
+                else receiver._promiseRejected(value, promise);
+
+                done = true;
+            }
+        }
+
+        if (!done) {
+
+            if (isFulfilled) promise._fulfill(value);
+            else promise._reject(value, this._getCarriedStackTrace());
+
+        }
+    }
+
+    if (index >= 256) {
+        this._queueGC();
+    }
+};
+
+Promise.prototype._isProxied = function Promise$_isProxied() {
+    return (this._bitField & 4194304) === 4194304;
+};
+
+Promise.prototype._setProxied = function Promise$_setProxied() {
+    this._bitField = this._bitField | 4194304;
+};
+
+Promise.prototype._unsetProxied = function Promise$_unsetProxied() {
+    this._bitField = this._bitField & (~4194304);
+};
+
+Promise.prototype._isGcQueued = function Promise$_isGcQueued() {
+    return (this._bitField & -1073741824) === -1073741824;
+};
+
+Promise.prototype._setGcQueued = function Promise$_setGcQueued() {
+    this._bitField = this._bitField | -1073741824;
+};
+
+Promise.prototype._unsetGcQueued = function Promise$_unsetGcQueued() {
+    this._bitField = this._bitField & (~-1073741824);
+};
+
+Promise.prototype._queueGC = function Promise$_queueGC() {
+    if (this._isGcQueued()) return;
+    this._setGcQueued();
+    async.invokeLater(this._gc, this, void 0);
+};
+
+Promise.prototype._gc = function Promise$gc() {
+    var len = this._length();
+    this._unsetAt(0);
+    for (var i = 0; i < len; i++) {
+        delete this[i];
+    }
+    this._setLength(0);
+    this._unsetGcQueued();
+};
+
+Promise.prototype._queueSettleAt = function Promise$_queueSettleAt(index) {
+    if (this._isRejectionUnhandled()) this._unsetRejectionIsUnhandled();
+    async.invoke(this._settlePromiseAt, this, index);
+};
+
+Promise.prototype._fulfillUnchecked =
+function Promise$_fulfillUnchecked(value) {
+    if (!this.isPending()) return;
+    if (value === this) {
+        var err = makeSelfResolutionError();
+        this._attachExtraTrace(err);
+        return this._rejectUnchecked(err, void 0);
+    }
+    this._cleanValues();
+    this._setFulfilled();
+    this._settledValue = value;
+    var len = this._length();
+
+    if (len > 0) {
+        async.invoke(this._fulfillPromises, this, len);
+    }
+};
+
+Promise.prototype._rejectUncheckedCheckError =
+function Promise$_rejectUncheckedCheckError(reason) {
+    var trace = canAttach(reason) ? reason : new Error(reason + "");
+    this._rejectUnchecked(reason, trace === reason ? void 0 : trace);
+};
+
+Promise.prototype._rejectUnchecked =
+function Promise$_rejectUnchecked(reason, trace) {
+    if (!this.isPending()) return;
+    if (reason === this) {
+        var err = makeSelfResolutionError();
+        this._attachExtraTrace(err);
+        return this._rejectUnchecked(err);
+    }
+    this._cleanValues();
+    this._setRejected();
+    this._settledValue = reason;
+
+    if (this._isFinal()) {
+        async.invokeLater(thrower, void 0, trace === void 0 ? reason : trace);
+        return;
+    }
+    var len = this._length();
+
+    if (trace !== void 0) this._setCarriedStackTrace(trace);
+
+    if (len > 0) {
+        async.invoke(this._rejectPromises, this, null);
+    }
+    else {
+        this._ensurePossibleRejectionHandled();
+    }
+};
+
+Promise.prototype._rejectPromises = function Promise$_rejectPromises() {
+    var len = this._length();
+    for (var i = 0; i < len; i+= 5) {
+        this._settlePromiseAt(i);
+    }
+    this._unsetCarriedStackTrace();
+};
+
+Promise.prototype._fulfillPromises = function Promise$_fulfillPromises(len) {
+    len = this._length();
+    for (var i = 0; i < len; i+= 5) {
+        this._settlePromiseAt(i);
+    }
+};
+
+Promise.prototype._ensurePossibleRejectionHandled =
+function Promise$_ensurePossibleRejectionHandled() {
+    this._setRejectionIsUnhandled();
+    if (CapturedTrace.possiblyUnhandledRejection !== void 0) {
+        async.invokeLater(this._notifyUnhandledRejection, this, void 0);
+    }
+};
+
+Promise.prototype._notifyUnhandledRejection =
+function Promise$_notifyUnhandledRejection() {
+    if (this._isRejectionUnhandled()) {
+        var reason = this._settledValue;
+        var trace = this._getCarriedStackTrace();
+
+        this._unsetRejectionIsUnhandled();
+
+        if (trace !== void 0) {
+            this._unsetCarriedStackTrace();
+            reason = trace;
+        }
+        if (typeof CapturedTrace.possiblyUnhandledRejection === "function") {
+            CapturedTrace.possiblyUnhandledRejection(reason, this);
+        }
+    }
+};
+
+var contextStack = [];
+Promise.prototype._peekContext = function Promise$_peekContext() {
+    var lastIndex = contextStack.length - 1;
+    if (lastIndex >= 0) {
+        return contextStack[lastIndex];
+    }
+    return void 0;
+
+};
+
+Promise.prototype._pushContext = function Promise$_pushContext() {
+    if (!debugging) return;
+    contextStack.push(this);
+};
+
+Promise.prototype._popContext = function Promise$_popContext() {
+    if (!debugging) return;
+    contextStack.pop();
+};
+
+function Promise$_CreatePromiseArray(
+    promises, PromiseArrayConstructor, caller, boundTo) {
+
+    var list = null;
+    if (isArray(promises)) {
+        list = promises;
+    }
+    else {
+        list = Promise._cast(promises, caller, void 0);
+        if (list !== promises) {
+            list._setBoundTo(boundTo);
+        }
+        else if (!isPromise(list)) {
+            list = null;
+        }
+    }
+    if (list !== null) {
+        return new PromiseArrayConstructor(
+            list,
+            typeof caller === "function"
+                ? caller
+                : Promise$_CreatePromiseArray,
+            boundTo
+       );
+    }
+    return {
+        promise: function() {return apiRejection("expecting an array, a promise or a thenable");}
+    };
+}
+
+var old = global.Promise;
+
+Promise.noConflict = function() {
+    if (global.Promise === Promise) {
+        global.Promise = old;
+    }
+    return Promise;
+};
+
+if (!CapturedTrace.isSupported()) {
+    Promise.longStackTraces = function(){};
+    debugging = false;
+}
+
+Promise._makeSelfResolutionError = makeSelfResolutionError;
+require("./finally.js")(Promise, NEXT_FILTER);
+require("./direct_resolve.js")(Promise);
+require("./thenables.js")(Promise, INTERNAL);
+Promise.RangeError = RangeError;
+Promise.CancellationError = CancellationError;
+Promise.TimeoutError = TimeoutError;
+Promise.TypeError = TypeError;
+Promise.RejectionError = RejectionError;
+
+util.toFastProperties(Promise);
+util.toFastProperties(Promise.prototype);
+require('./timers.js')(Promise,INTERNAL);
+require('./synchronous_inspection.js')(Promise);
+require('./any.js')(Promise,Promise$_CreatePromiseArray,PromiseArray);
+require('./race.js')(Promise,INTERNAL);
+require('./call_get.js')(Promise);
+require('./filter.js')(Promise,Promise$_CreatePromiseArray,PromiseArray,apiRejection);
+require('./generators.js')(Promise,apiRejection,INTERNAL);
+require('./map.js')(Promise,Promise$_CreatePromiseArray,PromiseArray,apiRejection);
+require('./nodeify.js')(Promise);
+require('./promisify.js')(Promise,INTERNAL);
+require('./props.js')(Promise,PromiseArray);
+require('./reduce.js')(Promise,Promise$_CreatePromiseArray,PromiseArray,apiRejection,INTERNAL);
+require('./settle.js')(Promise,Promise$_CreatePromiseArray,PromiseArray);
+require('./some.js')(Promise,Promise$_CreatePromiseArray,PromiseArray,apiRejection);
+require('./progress.js')(Promise,isPromiseArrayProxy);
+require('./cancel.js')(Promise,INTERNAL);
+
+Promise.prototype = Promise.prototype;
+return Promise;
+
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/promise_array.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/promise_array.js
new file mode 100644
index 0000000..377f0ec
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/promise_array.js
@@ -0,0 +1,233 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, INTERNAL) {
+var canAttach = require("./errors.js").canAttach;
+var util = require("./util.js");
+var async = require("./async.js");
+var hasOwn = {}.hasOwnProperty;
+var isArray = util.isArray;
+
+function toResolutionValue(val) {
+    switch(val) {
+    case -1: return void 0;
+    case -2: return [];
+    case -3: return {};
+    }
+}
+
+function PromiseArray(values, caller, boundTo) {
+    var promise = this._promise = new Promise(INTERNAL);
+    var parent = void 0;
+    if (Promise.is(values)) {
+        parent = values;
+        if (values._cancellable()) {
+            promise._setCancellable();
+            promise._cancellationParent = values;
+        }
+        if (values._isBound()) {
+            promise._setBoundTo(boundTo);
+        }
+    }
+    promise._setTrace(caller, parent);
+    this._values = values;
+    this._length = 0;
+    this._totalResolved = 0;
+    this._init(void 0, -2);
+}
+PromiseArray.PropertiesPromiseArray = function() {};
+
+PromiseArray.prototype.length = function PromiseArray$length() {
+    return this._length;
+};
+
+PromiseArray.prototype.promise = function PromiseArray$promise() {
+    return this._promise;
+};
+
+PromiseArray.prototype._init =
+function PromiseArray$_init(_, resolveValueIfEmpty) {
+    var values = this._values;
+    if (Promise.is(values)) {
+        if (values.isFulfilled()) {
+            values = values._settledValue;
+            if (!isArray(values)) {
+                var err = new Promise.TypeError("expecting an array, a promise or a thenable");
+                this.__hardReject__(err);
+                return;
+            }
+            this._values = values;
+        }
+        else if (values.isPending()) {
+            values._then(
+                this._init,
+                this._reject,
+                void 0,
+                this,
+                resolveValueIfEmpty,
+                this.constructor
+           );
+            return;
+        }
+        else {
+            this._reject(values._settledValue);
+            return;
+        }
+    }
+
+    if (values.length === 0) {
+        this._resolve(toResolutionValue(resolveValueIfEmpty));
+        return;
+    }
+    var len = values.length;
+    var newLen = len;
+    var newValues;
+    if (this instanceof PromiseArray.PropertiesPromiseArray) {
+        newValues = this._values;
+    }
+    else {
+        newValues = new Array(len);
+    }
+    var isDirectScanNeeded = false;
+    for (var i = 0; i < len; ++i) {
+        var promise = values[i];
+        if (promise === void 0 && !hasOwn.call(values, i)) {
+            newLen--;
+            continue;
+        }
+        var maybePromise = Promise._cast(promise, void 0, void 0);
+        if (maybePromise instanceof Promise) {
+            if (maybePromise.isPending()) {
+                maybePromise._proxyPromiseArray(this, i);
+            }
+            else {
+                maybePromise._unsetRejectionIsUnhandled();
+                isDirectScanNeeded = true;
+            }
+        }
+        else {
+            isDirectScanNeeded = true;
+        }
+        newValues[i] = maybePromise;
+    }
+    if (newLen === 0) {
+        if (resolveValueIfEmpty === -2) {
+            this._resolve(newValues);
+        }
+        else {
+            this._resolve(toResolutionValue(resolveValueIfEmpty));
+        }
+        return;
+    }
+    this._values = newValues;
+    this._length = newLen;
+    if (isDirectScanNeeded) {
+        var scanMethod = newLen === len
+            ? this._scanDirectValues
+            : this._scanDirectValuesHoled;
+        async.invoke(scanMethod, this, len);
+    }
+};
+
+PromiseArray.prototype._settlePromiseAt =
+function PromiseArray$_settlePromiseAt(index) {
+    var value = this._values[index];
+    if (!Promise.is(value)) {
+        this._promiseFulfilled(value, index);
+    }
+    else if (value.isFulfilled()) {
+        this._promiseFulfilled(value._settledValue, index);
+    }
+    else if (value.isRejected()) {
+        this._promiseRejected(value._settledValue, index);
+    }
+};
+
+PromiseArray.prototype._scanDirectValuesHoled =
+function PromiseArray$_scanDirectValuesHoled(len) {
+    for (var i = 0; i < len; ++i) {
+        if (this._isResolved()) {
+            break;
+        }
+        if (hasOwn.call(this._values, i)) {
+            this._settlePromiseAt(i);
+        }
+    }
+};
+
+PromiseArray.prototype._scanDirectValues =
+function PromiseArray$_scanDirectValues(len) {
+    for (var i = 0; i < len; ++i) {
+        if (this._isResolved()) {
+            break;
+        }
+        this._settlePromiseAt(i);
+    }
+};
+
+PromiseArray.prototype._isResolved = function PromiseArray$_isResolved() {
+    return this._values === null;
+};
+
+PromiseArray.prototype._resolve = function PromiseArray$_resolve(value) {
+    this._values = null;
+    this._promise._fulfill(value);
+};
+
+PromiseArray.prototype.__hardReject__ =
+PromiseArray.prototype._reject = function PromiseArray$_reject(reason) {
+    this._values = null;
+    var trace = canAttach(reason) ? reason : new Error(reason + "");
+    this._promise._attachExtraTrace(trace);
+    this._promise._reject(reason, trace);
+};
+
+PromiseArray.prototype._promiseProgressed =
+function PromiseArray$_promiseProgressed(progressValue, index) {
+    if (this._isResolved()) return;
+    this._promise._progress({
+        index: index,
+        value: progressValue
+    });
+};
+
+
+PromiseArray.prototype._promiseFulfilled =
+function PromiseArray$_promiseFulfilled(value, index) {
+    if (this._isResolved()) return;
+    this._values[index] = value;
+    var totalResolved = ++this._totalResolved;
+    if (totalResolved >= this._length) {
+        this._resolve(this._values);
+    }
+};
+
+PromiseArray.prototype._promiseRejected =
+function PromiseArray$_promiseRejected(reason, index) {
+    if (this._isResolved()) return;
+    this._totalResolved++;
+    this._reject(reason);
+};
+
+return PromiseArray;
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/promise_inspection.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/promise_inspection.js
new file mode 100644
index 0000000..0aa233b
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/promise_inspection.js
@@ -0,0 +1,66 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var TypeError = require("./errors.js").TypeError;
+
+function PromiseInspection(promise) {
+    if (promise !== void 0) {
+        this._bitField = promise._bitField;
+        this._settledValue = promise.isResolved()
+            ? promise._settledValue
+            : void 0;
+    }
+    else {
+        this._bitField = 0;
+        this._settledValue = void 0;
+    }
+}
+PromiseInspection.prototype.isFulfilled =
+function PromiseInspection$isFulfilled() {
+    return (this._bitField & 268435456) > 0;
+};
+
+PromiseInspection.prototype.isRejected =
+function PromiseInspection$isRejected() {
+    return (this._bitField & 134217728) > 0;
+};
+
+PromiseInspection.prototype.isPending = function PromiseInspection$isPending() {
+    return (this._bitField & 402653184) === 0;
+};
+
+PromiseInspection.prototype.value = function PromiseInspection$value() {
+    if (!this.isFulfilled()) {
+        throw new TypeError("cannot get fulfillment value of a non-fulfilled promise");
+    }
+    return this._settledValue;
+};
+
+PromiseInspection.prototype.error = function PromiseInspection$error() {
+    if (!this.isRejected()) {
+        throw new TypeError("cannot get rejection reason of a non-rejected promise");
+    }
+    return this._settledValue;
+};
+
+module.exports = PromiseInspection;
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/promise_resolver.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/promise_resolver.js
new file mode 100644
index 0000000..53ead00
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/promise_resolver.js
@@ -0,0 +1,152 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var util = require("./util.js");
+var maybeWrapAsError = util.maybeWrapAsError;
+var errors = require("./errors.js");
+var TimeoutError = errors.TimeoutError;
+var RejectionError = errors.RejectionError;
+var async = require("./async.js");
+var haveGetters = util.haveGetters;
+var es5 = require("./es5.js");
+
+function isUntypedError(obj) {
+    return obj instanceof Error &&
+        es5.getPrototypeOf(obj) === Error.prototype;
+}
+
+function wrapAsRejectionError(obj) {
+    var ret;
+    if (isUntypedError(obj)) {
+        ret = new RejectionError(obj);
+    }
+    else {
+        ret = obj;
+    }
+    errors.markAsOriginatingFromRejection(ret);
+    return ret;
+}
+
+function nodebackForPromise(promise) {
+    function PromiseResolver$_callback(err, value) {
+        if (promise === null) return;
+
+        if (err) {
+            var wrapped = wrapAsRejectionError(maybeWrapAsError(err));
+            promise._attachExtraTrace(wrapped);
+            promise._reject(wrapped);
+        }
+        else {
+            if (arguments.length > 2) {
+                var $_len = arguments.length;var args = new Array($_len - 1); for(var $_i = 1; $_i < $_len; ++$_i) {args[$_i - 1] = arguments[$_i];}
+                promise._fulfill(args);
+            }
+            else {
+                promise._fulfill(value);
+            }
+        }
+
+        promise = null;
+    }
+    return PromiseResolver$_callback;
+}
+
+
+var PromiseResolver;
+if (!haveGetters) {
+    PromiseResolver = function PromiseResolver(promise) {
+        this.promise = promise;
+        this.asCallback = nodebackForPromise(promise);
+        this.callback = this.asCallback;
+    };
+}
+else {
+    PromiseResolver = function PromiseResolver(promise) {
+        this.promise = promise;
+    };
+}
+if (haveGetters) {
+    var prop = {
+        get: function() {
+            return nodebackForPromise(this.promise);
+        }
+    };
+    es5.defineProperty(PromiseResolver.prototype, "asCallback", prop);
+    es5.defineProperty(PromiseResolver.prototype, "callback", prop);
+}
+
+PromiseResolver._nodebackForPromise = nodebackForPromise;
+
+PromiseResolver.prototype.toString = function PromiseResolver$toString() {
+    return "[object PromiseResolver]";
+};
+
+PromiseResolver.prototype.resolve =
+PromiseResolver.prototype.fulfill = function PromiseResolver$resolve(value) {
+    var promise = this.promise;
+    if (promise._tryFollow(value)) {
+        return;
+    }
+    async.invoke(promise._fulfill, promise, value);
+};
+
+PromiseResolver.prototype.reject = function PromiseResolver$reject(reason) {
+    var promise = this.promise;
+    errors.markAsOriginatingFromRejection(reason);
+    var trace = errors.canAttach(reason) ? reason : new Error(reason + "");
+    promise._attachExtraTrace(trace);
+    async.invoke(promise._reject, promise, reason);
+    if (trace !== reason) {
+        async.invoke(this._setCarriedStackTrace, this, trace);
+    }
+};
+
+PromiseResolver.prototype.progress =
+function PromiseResolver$progress(value) {
+    async.invoke(this.promise._progress, this.promise, value);
+};
+
+PromiseResolver.prototype.cancel = function PromiseResolver$cancel() {
+    async.invoke(this.promise.cancel, this.promise, void 0);
+};
+
+PromiseResolver.prototype.timeout = function PromiseResolver$timeout() {
+    this.reject(new TimeoutError("timeout"));
+};
+
+PromiseResolver.prototype.isResolved = function PromiseResolver$isResolved() {
+    return this.promise.isResolved();
+};
+
+PromiseResolver.prototype.toJSON = function PromiseResolver$toJSON() {
+    return this.promise.toJSON();
+};
+
+PromiseResolver.prototype._setCarriedStackTrace =
+function PromiseResolver$_setCarriedStackTrace(trace) {
+    if (this.promise.isRejected()) {
+        this.promise._setCarriedStackTrace(trace);
+    }
+};
+
+module.exports = PromiseResolver;
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/promise_spawn.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/promise_spawn.js
new file mode 100644
index 0000000..2d6525f
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/promise_spawn.js
@@ -0,0 +1,131 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, INTERNAL) {
+var errors = require("./errors.js");
+var TypeError = errors.TypeError;
+var util = require("./util.js");
+var isArray = util.isArray;
+var errorObj = util.errorObj;
+var tryCatch1 = util.tryCatch1;
+var yieldHandlers = [];
+
+function promiseFromYieldHandler(value) {
+    var _yieldHandlers = yieldHandlers;
+    var _errorObj = errorObj;
+    var _Promise = Promise;
+    var len = _yieldHandlers.length;
+    for (var i = 0; i < len; ++i) {
+        var result = tryCatch1(_yieldHandlers[i], void 0, value);
+        if (result === _errorObj) {
+            return _Promise.reject(_errorObj.e);
+        }
+        var maybePromise = _Promise._cast(result,
+            promiseFromYieldHandler, void 0);
+        if (maybePromise instanceof _Promise) return maybePromise;
+    }
+    return null;
+}
+
+function PromiseSpawn(generatorFunction, receiver, caller) {
+    var promise = this._promise = new Promise(INTERNAL);
+    promise._setTrace(caller, void 0);
+    this._generatorFunction = generatorFunction;
+    this._receiver = receiver;
+    this._generator = void 0;
+}
+
+PromiseSpawn.prototype.promise = function PromiseSpawn$promise() {
+    return this._promise;
+};
+
+PromiseSpawn.prototype._run = function PromiseSpawn$_run() {
+    this._generator = this._generatorFunction.call(this._receiver);
+    this._receiver =
+        this._generatorFunction = void 0;
+    this._next(void 0);
+};
+
+PromiseSpawn.prototype._continue = function PromiseSpawn$_continue(result) {
+    if (result === errorObj) {
+        this._generator = void 0;
+        var trace = errors.canAttach(result.e)
+            ? result.e : new Error(result.e + "");
+        this._promise._attachExtraTrace(trace);
+        this._promise._reject(result.e, trace);
+        return;
+    }
+
+    var value = result.value;
+    if (result.done === true) {
+        this._generator = void 0;
+        if (!this._promise._tryFollow(value)) {
+            this._promise._fulfill(value);
+        }
+    }
+    else {
+        var maybePromise = Promise._cast(value, PromiseSpawn$_continue, void 0);
+        if (!(maybePromise instanceof Promise)) {
+            if (isArray(maybePromise)) {
+                maybePromise = Promise.all(maybePromise);
+            }
+            else {
+                maybePromise = promiseFromYieldHandler(maybePromise);
+            }
+            if (maybePromise === null) {
+                this._throw(new TypeError("A value was yielded that could not be treated as a promise"));
+                return;
+            }
+        }
+        maybePromise._then(
+            this._next,
+            this._throw,
+            void 0,
+            this,
+            null,
+            void 0
+       );
+    }
+};
+
+PromiseSpawn.prototype._throw = function PromiseSpawn$_throw(reason) {
+    if (errors.canAttach(reason))
+        this._promise._attachExtraTrace(reason);
+    this._continue(
+        tryCatch1(this._generator["throw"], this._generator, reason)
+   );
+};
+
+PromiseSpawn.prototype._next = function PromiseSpawn$_next(value) {
+    this._continue(
+        tryCatch1(this._generator.next, this._generator, value)
+   );
+};
+
+PromiseSpawn.addYieldHandler = function PromiseSpawn$AddYieldHandler(fn) {
+    if (typeof fn !== "function") throw new TypeError("fn must be a function");
+    yieldHandlers.push(fn);
+};
+
+return PromiseSpawn;
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/promisify.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/promisify.js
new file mode 100644
index 0000000..a550fd0
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/promisify.js
@@ -0,0 +1,278 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, INTERNAL) {
+var THIS = {};
+var util = require("./util.js");
+var es5 = require("./es5.js");
+var nodebackForPromise = require("./promise_resolver.js")
+    ._nodebackForPromise;
+var withAppended = util.withAppended;
+var maybeWrapAsError = util.maybeWrapAsError;
+var canEvaluate = util.canEvaluate;
+var notEnumerableProp = util.notEnumerableProp;
+var deprecated = util.deprecated;
+var roriginal = new RegExp("__beforePromisified__" + "$");
+var hasProp = {}.hasOwnProperty;
+function isPromisified(fn) {
+    return fn.__isPromisified__ === true;
+}
+var inheritedMethods = (function() {
+    if (es5.isES5) {
+        var create = Object.create;
+        var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
+        return function(cur) {
+            var original = cur;
+            var ret = [];
+            var visitedKeys = create(null);
+            while (cur !== null) {
+                var keys = es5.keys(cur);
+                for (var i = 0, len = keys.length; i < len; ++i) {
+                    var key = keys[i];
+                    if (visitedKeys[key] ||
+                        roriginal.test(key) ||
+                        hasProp.call(original, key + "__beforePromisified__")
+                   ) {
+                        continue;
+                    }
+                    visitedKeys[key] = true;
+                    var desc = getOwnPropertyDescriptor(cur, key);
+                    if (desc != null &&
+                        typeof desc.value === "function" &&
+                        !isPromisified(desc.value)) {
+                        ret.push(key, desc.value);
+                    }
+                }
+                cur = es5.getPrototypeOf(cur);
+            }
+            return ret;
+        };
+    }
+    else {
+        return function(obj) {
+            var ret = [];
+            /*jshint forin:false */
+            for (var key in obj) {
+                if (roriginal.test(key) ||
+                    hasProp.call(obj, key + "__beforePromisified__")) {
+                    continue;
+                }
+                var fn = obj[key];
+                if (typeof fn === "function" &&
+                    !isPromisified(fn)) {
+                    ret.push(key, fn);
+                }
+            }
+            return ret;
+        };
+    }
+})();
+
+function switchCaseArgumentOrder(likelyArgumentCount) {
+    var ret = [likelyArgumentCount];
+    var min = Math.max(0, likelyArgumentCount - 1 - 5);
+    for(var i = likelyArgumentCount - 1; i >= min; --i) {
+        if (i === likelyArgumentCount) continue;
+        ret.push(i);
+    }
+    for(var i = likelyArgumentCount + 1; i <= 5; ++i) {
+        ret.push(i);
+    }
+    return ret;
+}
+
+function parameterDeclaration(parameterCount) {
+    var ret = new Array(parameterCount);
+    for(var i = 0; i < ret.length; ++i) {
+        ret[i] = "_arg" + i;
+    }
+    return ret.join(", ");
+}
+
+function parameterCount(fn) {
+    if (typeof fn.length === "number") {
+        return Math.max(Math.min(fn.length, 1023 + 1), 0);
+    }
+    return 0;
+}
+
+function propertyAccess(id) {
+    var rident = /^[a-z$_][a-z$_0-9]*$/i;
+
+    if (rident.test(id)) {
+        return "." + id;
+    }
+    else return "['" + id.replace(/(['\\])/g, "\\$1") + "']";
+}
+
+function makeNodePromisifiedEval(callback, receiver, originalName, fn) {
+    var newParameterCount = Math.max(0, parameterCount(fn) - 1);
+    var argumentOrder = switchCaseArgumentOrder(newParameterCount);
+
+    var callbackName = (typeof originalName === "string" ?
+        originalName + "Async" :
+        "promisified");
+
+    function generateCallForArgumentCount(count) {
+        var args = new Array(count);
+        for (var i = 0, len = args.length; i < len; ++i) {
+            args[i] = "arguments[" + i + "]";
+        }
+        var comma = count > 0 ? "," : "";
+
+        if (typeof callback === "string" &&
+            receiver === THIS) {
+            return "this" + propertyAccess(callback) + "("+args.join(",") +
+                comma +" fn);"+
+                "break;";
+        }
+        return (receiver === void 0
+            ? "callback("+args.join(",")+ comma +" fn);"
+            : "callback.call("+(receiver === THIS
+                ? "this"
+                : "receiver")+", "+args.join(",") + comma + " fn);") +
+        "break;";
+    }
+
+    function generateArgumentSwitchCase() {
+        var ret = "";
+        for(var i = 0; i < argumentOrder.length; ++i) {
+            ret += "case " + argumentOrder[i] +":" +
+                generateCallForArgumentCount(argumentOrder[i]);
+        }
+        ret += "default: var args = new Array(len + 1);" +
+            "var i = 0;" +
+            "for (var i = 0; i < len; ++i) { " +
+            "   args[i] = arguments[i];" +
+            "}" +
+            "args[i] = fn;" +
+
+            (typeof callback === "string"
+            ? "this" + propertyAccess(callback) + ".apply("
+            : "callback.apply(") +
+
+            (receiver === THIS ? "this" : "receiver") +
+            ", args); break;";
+        return ret;
+    }
+
+    return new Function("Promise", "callback", "receiver",
+            "withAppended", "maybeWrapAsError", "nodebackForPromise",
+            "INTERNAL",
+        "var ret = function " + callbackName +
+        "(" + parameterDeclaration(newParameterCount) + ") {\"use strict\";" +
+        "var len = arguments.length;" +
+        "var promise = new Promise(INTERNAL);"+
+        "promise._setTrace(" + callbackName + ", void 0);" +
+        "var fn = nodebackForPromise(promise);"+
+        "try {" +
+        "switch(len) {" +
+        generateArgumentSwitchCase() +
+        "}" +
+        "}" +
+        "catch(e){ " +
+        "var wrapped = maybeWrapAsError(e);" +
+        "promise._attachExtraTrace(wrapped);" +
+        "promise._reject(wrapped);" +
+        "}" +
+        "return promise;" +
+        "" +
+        "}; ret.__isPromisified__ = true; return ret;"
+   )(Promise, callback, receiver, withAppended,
+        maybeWrapAsError, nodebackForPromise, INTERNAL);
+}
+
+function makeNodePromisifiedClosure(callback, receiver) {
+    function promisified() {
+        var _receiver = receiver;
+        if (receiver === THIS) _receiver = this;
+        if (typeof callback === "string") {
+            callback = _receiver[callback];
+        }
+        var promise = new Promise(INTERNAL);
+        promise._setTrace(promisified, void 0);
+        var fn = nodebackForPromise(promise);
+        try {
+            callback.apply(_receiver, withAppended(arguments, fn));
+        }
+        catch(e) {
+            var wrapped = maybeWrapAsError(e);
+            promise._attachExtraTrace(wrapped);
+            promise._reject(wrapped);
+        }
+        return promise;
+    }
+    promisified.__isPromisified__ = true;
+    return promisified;
+}
+
+var makeNodePromisified = canEvaluate
+    ? makeNodePromisifiedEval
+    : makeNodePromisifiedClosure;
+
+function _promisify(callback, receiver, isAll) {
+    if (isAll) {
+        var methods = inheritedMethods(callback);
+        for (var i = 0, len = methods.length; i < len; i+= 2) {
+            var key = methods[i];
+            var fn = methods[i+1];
+            var originalKey = key + "__beforePromisified__";
+            var promisifiedKey = key + "Async";
+            notEnumerableProp(callback, originalKey, fn);
+            callback[promisifiedKey] =
+                makeNodePromisified(originalKey, THIS,
+                    key, fn);
+        }
+        util.toFastProperties(callback);
+        return callback;
+    }
+    else {
+        return makeNodePromisified(callback, receiver, void 0, callback);
+    }
+}
+
+Promise.promisify = function Promise$Promisify(fn, receiver) {
+    if (typeof fn === "object" && fn !== null) {
+        deprecated("Promise.promisify for promisifying entire objects is deprecated. Use Promise.promisifyAll instead.");
+        return _promisify(fn, receiver, true);
+    }
+    if (typeof fn !== "function") {
+        throw new TypeError("fn must be a function");
+    }
+    if (isPromisified(fn)) {
+        return fn;
+    }
+    return _promisify(
+        fn,
+        arguments.length < 2 ? THIS : receiver,
+        false);
+};
+
+Promise.promisifyAll = function Promise$PromisifyAll(target) {
+    if (typeof target !== "function" && typeof target !== "object") {
+        throw new TypeError("the target of promisifyAll must be an object or a function");
+    }
+    return _promisify(target, void 0, true);
+};
+};
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/properties_promise_array.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/properties_promise_array.js
new file mode 100644
index 0000000..85f5990
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/properties_promise_array.js
@@ -0,0 +1,77 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, PromiseArray) {
+var util = require("./util.js");
+var inherits = util.inherits;
+var es5 = require("./es5.js");
+
+function PropertiesPromiseArray(obj, caller, boundTo) {
+    var keys = es5.keys(obj);
+    var values = new Array(keys.length);
+    for (var i = 0, len = values.length; i < len; ++i) {
+        values[i] = obj[keys[i]];
+    }
+    this.constructor$(values, caller, boundTo);
+    if (!this._isResolved()) {
+        for (var i = 0, len = keys.length; i < len; ++i) {
+            values.push(keys[i]);
+        }
+    }
+}
+inherits(PropertiesPromiseArray, PromiseArray);
+
+PropertiesPromiseArray.prototype._init =
+function PropertiesPromiseArray$_init() {
+    this._init$(void 0, -3) ;
+};
+
+PropertiesPromiseArray.prototype._promiseFulfilled =
+function PropertiesPromiseArray$_promiseFulfilled(value, index) {
+    if (this._isResolved()) return;
+    this._values[index] = value;
+    var totalResolved = ++this._totalResolved;
+    if (totalResolved >= this._length) {
+        var val = {};
+        var keyOffset = this.length();
+        for (var i = 0, len = this.length(); i < len; ++i) {
+            val[this._values[i + keyOffset]] = this._values[i];
+        }
+        this._resolve(val);
+    }
+};
+
+PropertiesPromiseArray.prototype._promiseProgressed =
+function PropertiesPromiseArray$_promiseProgressed(value, index) {
+    if (this._isResolved()) return;
+
+    this._promise._progress({
+        key: this._values[index + this.length()],
+        value: value
+    });
+};
+
+PromiseArray.PropertiesPromiseArray = PropertiesPromiseArray;
+
+return PropertiesPromiseArray;
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/props.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/props.js
new file mode 100644
index 0000000..3cdba43
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/props.js
@@ -0,0 +1,65 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, PromiseArray) {
+    var PropertiesPromiseArray = require("./properties_promise_array.js")(
+        Promise, PromiseArray);
+    var util = require("./util.js");
+    var apiRejection = require("./errors_api_rejection")(Promise);
+    var isObject = util.isObject;
+
+    function Promise$_Props(promises, useBound, caller) {
+        var ret;
+        var castValue = Promise._cast(promises, caller, void 0);
+
+        if (!isObject(castValue)) {
+            return apiRejection("cannot await properties of a non-object");
+        }
+        else if (Promise.is(castValue)) {
+            ret = castValue._then(Promise.props, void 0, void 0,
+                            void 0, void 0, caller);
+        }
+        else {
+            ret = new PropertiesPromiseArray(
+                castValue,
+                caller,
+                useBound === true && castValue._isBound()
+                            ? castValue._boundTo
+                            : void 0
+           ).promise();
+            useBound = false;
+        }
+        if (useBound === true && castValue._isBound()) {
+            ret._setBoundTo(castValue._boundTo);
+        }
+        return ret;
+    }
+
+    Promise.prototype.props = function Promise$props() {
+        return Promise$_Props(this, true, this.props);
+    };
+
+    Promise.props = function Promise$Props(promises) {
+        return Promise$_Props(promises, false, Promise.props);
+    };
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/queue.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/queue.js
new file mode 100644
index 0000000..bbd3f1b
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/queue.js
@@ -0,0 +1,135 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+function arrayCopy(src, srcIndex, dst, dstIndex, len) {
+    for (var j = 0; j < len; ++j) {
+        dst[j + dstIndex] = src[j + srcIndex];
+    }
+}
+
+function pow2AtLeast(n) {
+    n = n >>> 0;
+    n = n - 1;
+    n = n | (n >> 1);
+    n = n | (n >> 2);
+    n = n | (n >> 4);
+    n = n | (n >> 8);
+    n = n | (n >> 16);
+    return n + 1;
+}
+
+function getCapacity(capacity) {
+    if (typeof capacity !== "number") return 16;
+    return pow2AtLeast(
+        Math.min(
+            Math.max(16, capacity), 1073741824)
+   );
+}
+
+function Queue(capacity) {
+    this._capacity = getCapacity(capacity);
+    this._length = 0;
+    this._front = 0;
+    this._makeCapacity();
+}
+
+Queue.prototype._willBeOverCapacity =
+function Queue$_willBeOverCapacity(size) {
+    return this._capacity < size;
+};
+
+Queue.prototype._pushOne = function Queue$_pushOne(arg) {
+    var length = this.length();
+    this._checkCapacity(length + 1);
+    var i = (this._front + length) & (this._capacity - 1);
+    this[i] = arg;
+    this._length = length + 1;
+};
+
+Queue.prototype.push = function Queue$push(fn, receiver, arg) {
+    var length = this.length() + 3;
+    if (this._willBeOverCapacity(length)) {
+        this._pushOne(fn);
+        this._pushOne(receiver);
+        this._pushOne(arg);
+        return;
+    }
+    var j = this._front + length - 3;
+    this._checkCapacity(length);
+    var wrapMask = this._capacity - 1;
+    this[(j + 0) & wrapMask] = fn;
+    this[(j + 1) & wrapMask] = receiver;
+    this[(j + 2) & wrapMask] = arg;
+    this._length = length;
+};
+
+Queue.prototype.shift = function Queue$shift() {
+    var front = this._front,
+        ret = this[front];
+
+    this[front] = void 0;
+    this._front = (front + 1) & (this._capacity - 1);
+    this._length--;
+    return ret;
+};
+
+Queue.prototype.length = function Queue$length() {
+    return this._length;
+};
+
+Queue.prototype._makeCapacity = function Queue$_makeCapacity() {
+    var len = this._capacity;
+    for (var i = 0; i < len; ++i) {
+        this[i] = void 0;
+    }
+};
+
+Queue.prototype._checkCapacity = function Queue$_checkCapacity(size) {
+    if (this._capacity < size) {
+        this._resizeTo(this._capacity << 3);
+    }
+};
+
+Queue.prototype._resizeTo = function Queue$_resizeTo(capacity) {
+    var oldFront = this._front;
+    var oldCapacity = this._capacity;
+    var oldQueue = new Array(oldCapacity);
+    var length = this.length();
+
+    arrayCopy(this, 0, oldQueue, 0, oldCapacity);
+    this._capacity = capacity;
+    this._makeCapacity();
+    this._front = 0;
+    if (oldFront + length <= oldCapacity) {
+        arrayCopy(oldQueue, oldFront, this, 0, length);
+    }
+    else {        var lengthBeforeWrapping =
+            length - ((oldFront + length) & (oldCapacity - 1));
+
+        arrayCopy(oldQueue, oldFront, this, 0, lengthBeforeWrapping);
+        arrayCopy(oldQueue, 0, this, lengthBeforeWrapping,
+                    length - lengthBeforeWrapping);
+    }
+};
+
+module.exports = Queue;
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/race.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/race.js
new file mode 100644
index 0000000..82b8ce1
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/race.js
@@ -0,0 +1,85 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, INTERNAL) {
+    var apiRejection = require("./errors_api_rejection.js")(Promise);
+    var isArray = require("./util.js").isArray;
+
+    var raceLater = function Promise$_raceLater(promise) {
+        return promise.then(function Promise$_lateRacer(array) {
+            return Promise$_Race(array, Promise$_lateRacer, promise);
+        });
+    };
+
+    var hasOwn = {}.hasOwnProperty;
+    function Promise$_Race(promises, caller, parent) {
+        var maybePromise = Promise._cast(promises, caller, void 0);
+
+        if (Promise.is(maybePromise)) {
+            return raceLater(maybePromise);
+        }
+        else if (!isArray(promises)) {
+            return apiRejection("expecting an array, a promise or a thenable");
+        }
+
+        var ret = new Promise(INTERNAL);
+        ret._setTrace(caller, parent);
+        if (parent !== void 0) {
+            if (parent._isBound()) {
+                ret._setBoundTo(parent._boundTo);
+            }
+            if (parent._cancellable()) {
+                ret._setCancellable();
+                ret._cancellationParent = parent;
+            }
+        }
+        var fulfill = ret._fulfill;
+        var reject = ret._reject;
+        for (var i = 0, len = promises.length; i < len; ++i) {
+            var val = promises[i];
+
+            if (val === void 0 && !(hasOwn.call(promises, i))) {
+                continue;
+            }
+
+            Promise.cast(val)._then(
+                fulfill,
+                reject,
+                void 0,
+                ret,
+                null,
+                caller
+           );
+        }
+        return ret;
+    }
+
+    Promise.race = function Promise$Race(promises) {
+        return Promise$_Race(promises, Promise.race, void 0);
+    };
+
+    Promise.prototype.race = function Promise$race() {
+        return Promise$_Race(this, this.race, void 0);
+    };
+
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/reduce.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/reduce.js
new file mode 100644
index 0000000..e9ef95b
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/reduce.js
@@ -0,0 +1,173 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(
+    Promise, Promise$_CreatePromiseArray,
+    PromiseArray, apiRejection, INTERNAL) {
+
+    function Reduction(callback, index, accum, items, receiver) {
+        this.promise = new Promise(INTERNAL);
+        this.index = index;
+        this.length = items.length;
+        this.items = items;
+        this.callback = callback;
+        this.receiver = receiver;
+        this.accum = accum;
+    }
+
+    Reduction.prototype.reject = function Reduction$reject(e) {
+        this.promise._reject(e);
+    };
+
+    Reduction.prototype.fulfill = function Reduction$fulfill(value, index) {
+        this.accum = value;
+        this.index = index + 1;
+        this.iterate();
+    };
+
+    Reduction.prototype.iterate = function Reduction$iterate() {
+        var i = this.index;
+        var len = this.length;
+        var items = this.items;
+        var result = this.accum;
+        var receiver = this.receiver;
+        var callback = this.callback;
+        var iterate = this.iterate;
+
+        for(; i < len; ++i) {
+            result = Promise._cast(
+                callback.call(
+                    receiver,
+                    result,
+                    items[i],
+                    i,
+                    len
+                ),
+                iterate,
+                void 0
+            );
+
+            if (result instanceof Promise) {
+                result._then(
+                    this.fulfill, this.reject, void 0, this, i, iterate);
+                return;
+            }
+        }
+        this.promise._fulfill(result);
+    };
+
+    function Promise$_reducer(fulfilleds, initialValue) {
+        var fn = this;
+        var receiver = void 0;
+        if (typeof fn !== "function")  {
+            receiver = fn.receiver;
+            fn = fn.fn;
+        }
+        var len = fulfilleds.length;
+        var accum = void 0;
+        var startIndex = 0;
+
+        if (initialValue !== void 0) {
+            accum = initialValue;
+            startIndex = 0;
+        }
+        else {
+            startIndex = 1;
+            if (len > 0) accum = fulfilleds[0];
+        }
+        var i = startIndex;
+
+        if (i >= len) {
+            return accum;
+        }
+
+        var reduction = new Reduction(fn, i, accum, fulfilleds, receiver);
+        reduction.iterate();
+        return reduction.promise;
+    }
+
+    function Promise$_unpackReducer(fulfilleds) {
+        var fn = this.fn;
+        var initialValue = this.initialValue;
+        return Promise$_reducer.call(fn, fulfilleds, initialValue);
+    }
+
+    function Promise$_slowReduce(
+        promises, fn, initialValue, useBound, caller) {
+        return initialValue._then(function callee(initialValue) {
+            return Promise$_Reduce(
+                promises, fn, initialValue, useBound, callee);
+        }, void 0, void 0, void 0, void 0, caller);
+    }
+
+    function Promise$_Reduce(promises, fn, initialValue, useBound, caller) {
+        if (typeof fn !== "function") {
+            return apiRejection("fn must be a function");
+        }
+
+        if (useBound === true && promises._isBound()) {
+            fn = {
+                fn: fn,
+                receiver: promises._boundTo
+            };
+        }
+
+        if (initialValue !== void 0) {
+            if (Promise.is(initialValue)) {
+                if (initialValue.isFulfilled()) {
+                    initialValue = initialValue._settledValue;
+                }
+                else {
+                    return Promise$_slowReduce(promises,
+                        fn, initialValue, useBound, caller);
+                }
+            }
+
+            return Promise$_CreatePromiseArray(promises, PromiseArray, caller,
+                useBound === true && promises._isBound()
+                    ? promises._boundTo
+                    : void 0)
+                .promise()
+                ._then(Promise$_unpackReducer, void 0, void 0, {
+                    fn: fn,
+                    initialValue: initialValue
+                }, void 0, Promise.reduce);
+        }
+        return Promise$_CreatePromiseArray(promises, PromiseArray, caller,
+                useBound === true && promises._isBound()
+                    ? promises._boundTo
+                    : void 0).promise()
+            ._then(Promise$_reducer, void 0, void 0, fn, void 0, caller);
+    }
+
+
+    Promise.reduce = function Promise$Reduce(promises, fn, initialValue) {
+        return Promise$_Reduce(promises, fn,
+            initialValue, false, Promise.reduce);
+    };
+
+    Promise.prototype.reduce = function Promise$reduce(fn, initialValue) {
+        return Promise$_Reduce(this, fn, initialValue,
+                                true, this.reduce);
+    };
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/schedule.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/schedule.js
new file mode 100644
index 0000000..ae2271e
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/schedule.js
@@ -0,0 +1,121 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var global = require("./global.js");
+var schedule;
+if (typeof process !== "undefined" && process !== null &&
+    typeof process.cwd === "function" &&
+    typeof process.nextTick === "function" &&
+    typeof process.version === "string") {
+    schedule = function Promise$_Scheduler(fn) {
+        process.nextTick(fn);
+    };
+}
+else if ((typeof global.MutationObserver === "function" ||
+        typeof global.WebkitMutationObserver === "function" ||
+        typeof global.WebKitMutationObserver === "function") &&
+        typeof document !== "undefined" &&
+        typeof document.createElement === "function") {
+
+
+    schedule = (function(){
+        var MutationObserver = global.MutationObserver ||
+            global.WebkitMutationObserver ||
+            global.WebKitMutationObserver;
+        var div = document.createElement("div");
+        var queuedFn = void 0;
+        var observer = new MutationObserver(
+            function Promise$_Scheduler() {
+                var fn = queuedFn;
+                queuedFn = void 0;
+                fn();
+            }
+       );
+        observer.observe(div, {
+            attributes: true
+        });
+        return function Promise$_Scheduler(fn) {
+            queuedFn = fn;
+            div.setAttribute("class", "foo");
+        };
+
+    })();
+}
+else if (typeof global.postMessage === "function" &&
+    typeof global.importScripts !== "function" &&
+    typeof global.addEventListener === "function" &&
+    typeof global.removeEventListener === "function") {
+
+    var MESSAGE_KEY = "bluebird_message_key_" + Math.random();
+    schedule = (function(){
+        var queuedFn = void 0;
+
+        function Promise$_Scheduler(e) {
+            if (e.source === global &&
+                e.data === MESSAGE_KEY) {
+                var fn = queuedFn;
+                queuedFn = void 0;
+                fn();
+            }
+        }
+
+        global.addEventListener("message", Promise$_Scheduler, false);
+
+        return function Promise$_Scheduler(fn) {
+            queuedFn = fn;
+            global.postMessage(
+                MESSAGE_KEY, "*"
+           );
+        };
+
+    })();
+}
+else if (typeof global.MessageChannel === "function") {
+    schedule = (function(){
+        var queuedFn = void 0;
+
+        var channel = new global.MessageChannel();
+        channel.port1.onmessage = function Promise$_Scheduler() {
+                var fn = queuedFn;
+                queuedFn = void 0;
+                fn();
+        };
+
+        return function Promise$_Scheduler(fn) {
+            queuedFn = fn;
+            channel.port2.postMessage(null);
+        };
+    })();
+}
+else if (global.setTimeout) {
+    schedule = function Promise$_Scheduler(fn) {
+        setTimeout(fn, 4);
+    };
+}
+else {
+    schedule = function Promise$_Scheduler(fn) {
+        fn();
+    };
+}
+
+module.exports = schedule;
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/settle.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/settle.js
new file mode 100644
index 0000000..863882f
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/settle.js
@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports =
+    function(Promise, Promise$_CreatePromiseArray, PromiseArray) {
+
+    var SettledPromiseArray = require("./settled_promise_array.js")(
+        Promise, PromiseArray);
+
+    function Promise$_Settle(promises, useBound, caller) {
+        return Promise$_CreatePromiseArray(
+            promises,
+            SettledPromiseArray,
+            caller,
+            useBound === true && promises._isBound()
+                ? promises._boundTo
+                : void 0
+       ).promise();
+    }
+
+    Promise.settle = function Promise$Settle(promises) {
+        return Promise$_Settle(promises, false, Promise.settle);
+    };
+
+    Promise.prototype.settle = function Promise$settle() {
+        return Promise$_Settle(this, true, this.settle);
+    };
+
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/settled_promise_array.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/settled_promise_array.js
new file mode 100644
index 0000000..fd25d4d
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/settled_promise_array.js
@@ -0,0 +1,60 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, PromiseArray) {
+var PromiseInspection = require("./promise_inspection.js");
+var util = require("./util.js");
+var inherits = util.inherits;
+function SettledPromiseArray(values, caller, boundTo) {
+    this.constructor$(values, caller, boundTo);
+}
+inherits(SettledPromiseArray, PromiseArray);
+
+SettledPromiseArray.prototype._promiseResolved =
+function SettledPromiseArray$_promiseResolved(index, inspection) {
+    this._values[index] = inspection;
+    var totalResolved = ++this._totalResolved;
+    if (totalResolved >= this._length) {
+        this._resolve(this._values);
+    }
+};
+
+SettledPromiseArray.prototype._promiseFulfilled =
+function SettledPromiseArray$_promiseFulfilled(value, index) {
+    if (this._isResolved()) return;
+    var ret = new PromiseInspection();
+    ret._bitField = 268435456;
+    ret._settledValue = value;
+    this._promiseResolved(index, ret);
+};
+SettledPromiseArray.prototype._promiseRejected =
+function SettledPromiseArray$_promiseRejected(reason, index) {
+    if (this._isResolved()) return;
+    var ret = new PromiseInspection();
+    ret._bitField = 134217728;
+    ret._settledValue = reason;
+    this._promiseResolved(index, ret);
+};
+
+return SettledPromiseArray;
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/some.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/some.js
new file mode 100644
index 0000000..21c4ecd
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/some.js
@@ -0,0 +1,57 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports =
+function(Promise, Promise$_CreatePromiseArray, PromiseArray, apiRejection) {
+
+    var SomePromiseArray = require("./some_promise_array.js")(PromiseArray);
+    function Promise$_Some(promises, howMany, useBound, caller) {
+        if ((howMany | 0) !== howMany || howMany < 0) {
+            return apiRejection("expecting a positive integer");
+        }
+        var ret = Promise$_CreatePromiseArray(
+            promises,
+            SomePromiseArray,
+            caller,
+            useBound === true && promises._isBound()
+                ? promises._boundTo
+                : void 0
+       );
+        var promise = ret.promise();
+        if (promise.isRejected()) {
+            return promise;
+        }
+        ret.setHowMany(howMany);
+        ret.init();
+        return promise;
+    }
+
+    Promise.some = function Promise$Some(promises, howMany) {
+        return Promise$_Some(promises, howMany, false, Promise.some);
+    };
+
+    Promise.prototype.some = function Promise$some(count) {
+        return Promise$_Some(this, count, true, this.some);
+    };
+
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/some_promise_array.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/some_promise_array.js
new file mode 100644
index 0000000..d3b89d4
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/some_promise_array.js
@@ -0,0 +1,131 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function (PromiseArray) {
+var util = require("./util.js");
+var RangeError = require("./errors.js").RangeError;
+var inherits = util.inherits;
+var isArray = util.isArray;
+
+function SomePromiseArray(values, caller, boundTo) {
+    this.constructor$(values, caller, boundTo);
+    this._howMany = 0;
+    this._unwrap = false;
+    this._initialized = false;
+}
+inherits(SomePromiseArray, PromiseArray);
+
+SomePromiseArray.prototype._init = function SomePromiseArray$_init() {
+    if (!this._initialized) {
+        return;
+    }
+    if (this._howMany === 0) {
+        this._resolve([]);
+        return;
+    }
+    this._init$(void 0, -2);
+    var isArrayResolved = isArray(this._values);
+    this._holes = isArrayResolved ? this._values.length - this.length() : 0;
+
+    if (!this._isResolved() &&
+        isArrayResolved &&
+        this._howMany > this._canPossiblyFulfill()) {
+        var message = "(Promise.some) input array contains less than " +
+                        this._howMany  + " promises";
+        this._reject(new RangeError(message));
+    }
+};
+
+SomePromiseArray.prototype.init = function SomePromiseArray$init() {
+    this._initialized = true;
+    this._init();
+};
+
+SomePromiseArray.prototype.setUnwrap = function SomePromiseArray$setUnwrap() {
+    this._unwrap = true;
+};
+
+SomePromiseArray.prototype.howMany = function SomePromiseArray$howMany() {
+    return this._howMany;
+};
+
+SomePromiseArray.prototype.setHowMany =
+function SomePromiseArray$setHowMany(count) {
+    if (this._isResolved()) return;
+    this._howMany = count;
+};
+
+SomePromiseArray.prototype._promiseFulfilled =
+function SomePromiseArray$_promiseFulfilled(value) {
+    if (this._isResolved()) return;
+    this._addFulfilled(value);
+    if (this._fulfilled() === this.howMany()) {
+        this._values.length = this.howMany();
+        if (this.howMany() === 1 && this._unwrap) {
+            this._resolve(this._values[0]);
+        }
+        else {
+            this._resolve(this._values);
+        }
+    }
+
+};
+SomePromiseArray.prototype._promiseRejected =
+function SomePromiseArray$_promiseRejected(reason) {
+    if (this._isResolved()) return;
+    this._addRejected(reason);
+    if (this.howMany() > this._canPossiblyFulfill()) {
+        if (this._values.length === this.length()) {
+            this._reject([]);
+        }
+        else {
+            this._reject(this._values.slice(this.length() + this._holes));
+        }
+    }
+};
+
+SomePromiseArray.prototype._fulfilled = function SomePromiseArray$_fulfilled() {
+    return this._totalResolved;
+};
+
+SomePromiseArray.prototype._rejected = function SomePromiseArray$_rejected() {
+    return this._values.length - this.length() - this._holes;
+};
+
+SomePromiseArray.prototype._addRejected =
+function SomePromiseArray$_addRejected(reason) {
+    this._values.push(reason);
+};
+
+SomePromiseArray.prototype._addFulfilled =
+function SomePromiseArray$_addFulfilled(value) {
+    this._values[this._totalResolved++] = value;
+};
+
+SomePromiseArray.prototype._canPossiblyFulfill =
+function SomePromiseArray$_canPossiblyFulfill() {
+    return this.length() - this._rejected();
+};
+
+return SomePromiseArray;
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/synchronous_inspection.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/synchronous_inspection.js
new file mode 100644
index 0000000..dcbdc90
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/synchronous_inspection.js
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise) {
+    var PromiseInspection = require("./promise_inspection.js");
+
+    Promise.prototype.inspect = function Promise$inspect() {
+        return new PromiseInspection(this);
+    };
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/thenables.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/thenables.js
new file mode 100644
index 0000000..510da18
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/thenables.js
@@ -0,0 +1,138 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, INTERNAL) {
+    var util = require("./util.js");
+    var canAttach = require("./errors.js").canAttach;
+    var errorObj = util.errorObj;
+    var isObject = util.isObject;
+
+    function getThen(obj) {
+        try {
+            return obj.then;
+        }
+        catch(e) {
+            errorObj.e = e;
+            return errorObj;
+        }
+    }
+
+    function Promise$_Cast(obj, caller, originalPromise) {
+        if (isObject(obj)) {
+            if (obj instanceof Promise) {
+                return obj;
+            }
+            else if (isAnyBluebirdPromise(obj)) {
+                var ret = new Promise(INTERNAL);
+                ret._setTrace(caller, void 0);
+                obj._then(
+                    ret._fulfillUnchecked,
+                    ret._rejectUncheckedCheckError,
+                    ret._progressUnchecked,
+                    ret,
+                    null,
+                    void 0
+                );
+                ret._setFollowing();
+                return ret;
+            }
+            var then = getThen(obj);
+            if (then === errorObj) {
+                caller = typeof caller === "function" ? caller : Promise$_Cast;
+                if (originalPromise !== void 0 && canAttach(then.e)) {
+                    originalPromise._attachExtraTrace(then.e);
+                }
+                return Promise.reject(then.e, caller);
+            }
+            else if (typeof then === "function") {
+                caller = typeof caller === "function" ? caller : Promise$_Cast;
+                return Promise$_doThenable(obj, then, caller, originalPromise);
+            }
+        }
+        return obj;
+    }
+
+    var hasProp = {}.hasOwnProperty;
+    function isAnyBluebirdPromise(obj) {
+        return hasProp.call(obj, "_promise0");
+    }
+
+    function Promise$_doThenable(x, then, caller, originalPromise) {
+        var resolver = Promise.defer(caller);
+        var called = false;
+        try {
+            then.call(
+                x,
+                Promise$_resolveFromThenable,
+                Promise$_rejectFromThenable,
+                Promise$_progressFromThenable
+            );
+        }
+        catch(e) {
+            if (!called) {
+                called = true;
+                var trace = canAttach(e) ? e : new Error(e + "");
+                if (originalPromise !== void 0) {
+                    originalPromise._attachExtraTrace(trace);
+                }
+                resolver.promise._reject(e, trace);
+            }
+        }
+        return resolver.promise;
+
+        function Promise$_resolveFromThenable(y) {
+            if (called) return;
+            called = true;
+
+            if (x === y) {
+                var e = Promise._makeSelfResolutionError();
+                if (originalPromise !== void 0) {
+                    originalPromise._attachExtraTrace(e);
+                }
+                resolver.promise._reject(e, void 0);
+                return;
+            }
+            resolver.resolve(y);
+        }
+
+        function Promise$_rejectFromThenable(r) {
+            if (called) return;
+            called = true;
+            var trace = canAttach(r) ? r : new Error(r + "");
+            if (originalPromise !== void 0) {
+                originalPromise._attachExtraTrace(trace);
+            }
+            resolver.promise._reject(r, trace);
+        }
+
+        function Promise$_progressFromThenable(v) {
+            if (called) return;
+            var promise = resolver.promise;
+            if (typeof promise._progress === "function") {
+                promise._progress(v);
+            }
+        }
+    }
+
+    Promise._cast = Promise$_Cast;
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/timers.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/timers.js
new file mode 100644
index 0000000..8edcac2
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/timers.js
@@ -0,0 +1,114 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+
+var global = require("./global.js");
+var setTimeout = function(fn, time) {
+    var $_len = arguments.length;var args = new Array($_len - 2); for(var $_i = 2; $_i < $_len; ++$_i) {args[$_i - 2] = arguments[$_i];}
+    global.setTimeout(function() {
+        fn.apply(void 0, args);
+    }, time);
+};
+
+var pass = {};
+global.setTimeout( function(_) {
+    if(_ === pass) {
+        setTimeout = global.setTimeout;
+    }
+}, 1, pass);
+
+module.exports = function(Promise, INTERNAL) {
+    var util = require("./util.js");
+    var errors = require("./errors.js");
+    var apiRejection = require("./errors_api_rejection")(Promise);
+    var TimeoutError = Promise.TimeoutError;
+
+    var afterTimeout = function Promise$_afterTimeout(promise, message, ms) {
+        if (!promise.isPending()) return;
+        if (typeof message !== "string") {
+            message = "operation timed out after" + " " + ms + " ms"
+        }
+        var err = new TimeoutError(message);
+        errors.markAsOriginatingFromRejection(err);
+        promise._attachExtraTrace(err);
+        promise._rejectUnchecked(err);
+    };
+
+    var afterDelay = function Promise$_afterDelay(value, promise) {
+        promise._fulfill(value);
+    };
+
+    Promise.delay = function Promise$Delay(value, ms, caller) {
+        if (ms === void 0) {
+            ms = value;
+            value = void 0;
+        }
+        ms = +ms;
+        if (typeof caller !== "function") {
+            caller = Promise.delay;
+        }
+        var maybePromise = Promise._cast(value, caller, void 0);
+        var promise = new Promise(INTERNAL);
+
+        if (Promise.is(maybePromise)) {
+            if (maybePromise._isBound()) {
+                promise._setBoundTo(maybePromise._boundTo);
+            }
+            if (maybePromise._cancellable()) {
+                promise._setCancellable();
+                promise._cancellationParent = maybePromise;
+            }
+            promise._setTrace(caller, maybePromise);
+            promise._follow(maybePromise);
+            return promise.then(function(value) {
+                return Promise.delay(value, ms);
+            });
+        }
+        else {
+            promise._setTrace(caller, void 0);
+            setTimeout(afterDelay, ms, value, promise);
+        }
+        return promise;
+    };
+
+    Promise.prototype.delay = function Promise$delay(ms) {
+        return Promise.delay(this, ms, this.delay);
+    };
+
+    Promise.prototype.timeout = function Promise$timeout(ms, message) {
+        ms = +ms;
+
+        var ret = new Promise(INTERNAL);
+        ret._setTrace(this.timeout, this);
+
+        if (this._isBound()) ret._setBoundTo(this._boundTo);
+        if (this._cancellable()) {
+            ret._setCancellable();
+            ret._cancellationParent = this;
+        }
+        ret._follow(this);
+        setTimeout(afterTimeout, ms, ret, message, ms);
+        return ret;
+    };
+
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/util.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/util.js
new file mode 100644
index 0000000..fbd34e9
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/main/util.js
@@ -0,0 +1,193 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var global = require("./global.js");
+var es5 = require("./es5.js");
+var haveGetters = (function(){
+    try {
+        var o = {};
+        es5.defineProperty(o, "f", {
+            get: function () {
+                return 3;
+            }
+        });
+        return o.f === 3;
+    }
+    catch (e) {
+        return false;
+    }
+
+})();
+
+var canEvaluate = (function() {
+    if (typeof window !== "undefined" && window !== null &&
+        typeof window.document !== "undefined" &&
+        typeof navigator !== "undefined" && navigator !== null &&
+        typeof navigator.appName === "string" &&
+        window === global) {
+        return false;
+    }
+    return true;
+})();
+
+function deprecated(msg) {
+    if (typeof console !== "undefined" && console !== null &&
+        typeof console.warn === "function") {
+        console.warn("Bluebird: " + msg);
+    }
+}
+
+var errorObj = {e: {}};
+function tryCatch1(fn, receiver, arg) {
+    try {
+        return fn.call(receiver, arg);
+    }
+    catch (e) {
+        errorObj.e = e;
+        return errorObj;
+    }
+}
+
+function tryCatch2(fn, receiver, arg, arg2) {
+    try {
+        return fn.call(receiver, arg, arg2);
+    }
+    catch (e) {
+        errorObj.e = e;
+        return errorObj;
+    }
+}
+
+function tryCatchApply(fn, args, receiver) {
+    try {
+        return fn.apply(receiver, args);
+    }
+    catch (e) {
+        errorObj.e = e;
+        return errorObj;
+    }
+}
+
+var inherits = function(Child, Parent) {
+    var hasProp = {}.hasOwnProperty;
+
+    function T() {
+        this.constructor = Child;
+        this.constructor$ = Parent;
+        for (var propertyName in Parent.prototype) {
+            if (hasProp.call(Parent.prototype, propertyName) &&
+                propertyName.charAt(propertyName.length-1) !== "$"
+           ) {
+                this[propertyName + "$"] = Parent.prototype[propertyName];
+            }
+        }
+    }
+    T.prototype = Parent.prototype;
+    Child.prototype = new T();
+    return Child.prototype;
+};
+
+function asString(val) {
+    return typeof val === "string" ? val : ("" + val);
+}
+
+function isPrimitive(val) {
+    return val == null || val === true || val === false ||
+        typeof val === "string" || typeof val === "number";
+
+}
+
+function isObject(value) {
+    return !isPrimitive(value);
+}
+
+function maybeWrapAsError(maybeError) {
+    if (!isPrimitive(maybeError)) return maybeError;
+
+    return new Error(asString(maybeError));
+}
+
+function withAppended(target, appendee) {
+    var len = target.length;
+    var ret = new Array(len + 1);
+    var i;
+    for (i = 0; i < len; ++i) {
+        ret[i] = target[i];
+    }
+    ret[i] = appendee;
+    return ret;
+}
+
+
+function notEnumerableProp(obj, name, value) {
+    if (isPrimitive(obj)) return obj;
+    var descriptor = {
+        value: value,
+        configurable: true,
+        enumerable: false,
+        writable: true
+    };
+    es5.defineProperty(obj, name, descriptor);
+    return obj;
+}
+
+
+var wrapsPrimitiveReceiver = (function() {
+    return this !== "string";
+}).call("string");
+
+function thrower(r) {
+    throw r;
+}
+
+
+function toFastProperties(obj) {
+    /*jshint -W027*/
+    function f() {}
+    f.prototype = obj;
+    return f;
+    eval(obj);
+}
+
+var ret = {
+    thrower: thrower,
+    isArray: es5.isArray,
+    haveGetters: haveGetters,
+    notEnumerableProp: notEnumerableProp,
+    isPrimitive: isPrimitive,
+    isObject: isObject,
+    canEvaluate: canEvaluate,
+    deprecated: deprecated,
+    errorObj: errorObj,
+    tryCatch1: tryCatch1,
+    tryCatch2: tryCatch2,
+    tryCatchApply: tryCatchApply,
+    inherits: inherits,
+    withAppended: withAppended,
+    asString: asString,
+    maybeWrapAsError: maybeWrapAsError,
+    wrapsPrimitiveReceiver: wrapsPrimitiveReceiver,
+    toFastProperties: toFastProperties
+};
+
+module.exports = ret;
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/any.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/any.js
new file mode 100644
index 0000000..8d174cf
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/any.js
@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, Promise$_CreatePromiseArray, PromiseArray) {
+
+    var SomePromiseArray = require("./some_promise_array.js")(PromiseArray);
+    function Promise$_Any(promises, useBound, caller) {
+        var ret = Promise$_CreatePromiseArray(
+            promises,
+            SomePromiseArray,
+            caller,
+            useBound === true && promises._isBound()
+                ? promises._boundTo
+                : void 0
+       );
+        var promise = ret.promise();
+        if (promise.isRejected()) {
+            return promise;
+        }
+        ret.setHowMany(1);
+        ret.setUnwrap();
+        ret.init();
+        return promise;
+    }
+
+    Promise.any = function Promise$Any(promises) {
+        return Promise$_Any(promises, false, Promise.any);
+    };
+
+    Promise.prototype.any = function Promise$any() {
+        return Promise$_Any(this, true, this.any);
+    };
+
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/assert.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/assert.js
new file mode 100644
index 0000000..4adb8c2
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/assert.js
@@ -0,0 +1,83 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = (function(){
+    var AssertionError = (function() {
+        function AssertionError(a) {
+            this.constructor$(a);
+            this.message = a;
+            this.name = "AssertionError";
+        }
+        AssertionError.prototype = new Error();
+        AssertionError.prototype.constructor = AssertionError;
+        AssertionError.prototype.constructor$ = Error;
+        return AssertionError;
+    })();
+
+    function getParams(args) {
+        var params = [];
+        for (var i = 0; i < args.length; ++i) params.push("arg" + i);
+        return params;
+    }
+
+    function nativeAssert(callName, args, expect) {
+        try {
+            var params = getParams(args);
+            var constructorArgs = params;
+            constructorArgs.push("return " +
+                    callName + "("+ params.join(",") + ");");
+            var fn = Function.apply(null, constructorArgs);
+            return fn.apply(null, args);
+        }
+        catch (e) {
+            if (!(e instanceof SyntaxError)) {
+                throw e;
+            }
+            else {
+                return expect;
+            }
+        }
+    }
+
+    return function assert(boolExpr, message) {
+        if (boolExpr === true) return;
+
+        if (typeof boolExpr === "string" &&
+            boolExpr.charAt(0) === "%") {
+            var nativeCallName = boolExpr;
+            var $_len = arguments.length;var args = new Array($_len - 2); for(var $_i = 2; $_i < $_len; ++$_i) {args[$_i - 2] = arguments[$_i];}
+            if (nativeAssert(nativeCallName, args, message) === message) return;
+            message = (nativeCallName + " !== " + message);
+        }
+
+        var ret = new AssertionError(message);
+        if (Error.captureStackTrace) {
+            Error.captureStackTrace(ret, assert);
+        }
+        if (console && console.error) {
+            console.error(ret.stack + "");
+        }
+        throw ret;
+
+    };
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/async.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/async.js
new file mode 100644
index 0000000..6f32b10
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/async.js
@@ -0,0 +1,111 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var schedule = require("./schedule.js");
+var Queue = require("./queue.js");
+var errorObj = require("./util.js").errorObj;
+var tryCatch1 = require("./util.js").tryCatch1;
+var process = require("./global.js").process;
+
+function Async() {
+    this._isTickUsed = false;
+    this._length = 0;
+    this._lateBuffer = new Queue();
+    this._functionBuffer = new Queue(25000 * 3);
+    var self = this;
+    this.consumeFunctionBuffer = function Async$consumeFunctionBuffer() {
+        self._consumeFunctionBuffer();
+    };
+}
+
+Async.prototype.haveItemsQueued = function Async$haveItemsQueued() {
+    return this._length > 0;
+};
+
+Async.prototype.invokeLater = function Async$invokeLater(fn, receiver, arg) {
+    if (process !== void 0 &&
+        process.domain != null &&
+        !fn.domain) {
+        fn = process.domain.bind(fn);
+    }
+    this._lateBuffer.push(fn, receiver, arg);
+    this._queueTick();
+};
+
+Async.prototype.invoke = function Async$invoke(fn, receiver, arg) {
+    if (process !== void 0 &&
+        process.domain != null &&
+        !fn.domain) {
+        fn = process.domain.bind(fn);
+    }
+    var functionBuffer = this._functionBuffer;
+    functionBuffer.push(fn, receiver, arg);
+    this._length = functionBuffer.length();
+    this._queueTick();
+};
+
+Async.prototype._consumeFunctionBuffer =
+function Async$_consumeFunctionBuffer() {
+    var functionBuffer = this._functionBuffer;
+    while(functionBuffer.length() > 0) {
+        var fn = functionBuffer.shift();
+        var receiver = functionBuffer.shift();
+        var arg = functionBuffer.shift();
+        fn.call(receiver, arg);
+    }
+    this._reset();
+    this._consumeLateBuffer();
+};
+
+Async.prototype._consumeLateBuffer = function Async$_consumeLateBuffer() {
+    var buffer = this._lateBuffer;
+    while(buffer.length() > 0) {
+        var fn = buffer.shift();
+        var receiver = buffer.shift();
+        var arg = buffer.shift();
+        var res = tryCatch1(fn, receiver, arg);
+        if (res === errorObj) {
+            this._queueTick();
+            if (fn.domain != null) {
+                fn.domain.emit("error", res.e);
+            }
+            else {
+                throw res.e;
+            }
+        }
+    }
+};
+
+Async.prototype._queueTick = function Async$_queue() {
+    if (!this._isTickUsed) {
+        schedule(this.consumeFunctionBuffer);
+        this._isTickUsed = true;
+    }
+};
+
+Async.prototype._reset = function Async$_reset() {
+    this._isTickUsed = false;
+    this._length = 0;
+};
+
+module.exports = new Async();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/bluebird.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/bluebird.js
new file mode 100644
index 0000000..6fd85f1
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/bluebird.js
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var Promise = require("./promise.js")();
+module.exports = Promise;
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/call_get.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/call_get.js
new file mode 100644
index 0000000..2a3c1f5
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/call_get.js
@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise) {
+    Promise.prototype.call = function Promise$call(propertyName) {
+        var $_len = arguments.length;var args = new Array($_len - 1); for(var $_i = 1; $_i < $_len; ++$_i) {args[$_i - 1] = arguments[$_i];}
+
+        return this._then(function(obj) {
+                return obj[propertyName].apply(obj, args);
+            },
+            void 0,
+            void 0,
+            void 0,
+            void 0,
+            this.call
+       );
+    };
+
+    function Promise$getter(obj) {
+        var prop = typeof this === "string"
+            ? this
+            : ("" + this);
+        return obj[prop];
+    }
+    Promise.prototype.get = function Promise$get(propertyName) {
+        return this._then(
+            Promise$getter,
+            void 0,
+            void 0,
+            propertyName,
+            void 0,
+            this.get
+       );
+    };
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/cancel.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/cancel.js
new file mode 100644
index 0000000..542b488
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/cancel.js
@@ -0,0 +1,77 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, INTERNAL) {
+    var errors = require("./errors.js");
+    var async = require("./async.js");
+    var CancellationError = errors.CancellationError;
+    var SYNC_TOKEN = {};
+
+    Promise.prototype._cancel = function Promise$_cancel() {
+        if (!this.isCancellable()) return this;
+        var parent;
+        if ((parent = this._cancellationParent) !== void 0) {
+            parent.cancel(SYNC_TOKEN);
+            return;
+        }
+        var err = new CancellationError();
+        this._attachExtraTrace(err);
+        this._rejectUnchecked(err);
+    };
+
+    Promise.prototype.cancel = function Promise$cancel(token) {
+        if (!this.isCancellable()) return this;
+        if (token === SYNC_TOKEN) {
+            this._cancel();
+            return this;
+        }
+        async.invokeLater(this._cancel, this, void 0);
+        return this;
+    };
+
+    Promise.prototype.cancellable = function Promise$cancellable() {
+        if (this._cancellable()) return this;
+        this._setCancellable();
+        this._cancellationParent = void 0;
+        return this;
+    };
+
+    Promise.prototype.uncancellable = function Promise$uncancellable() {
+        var ret = new Promise(INTERNAL);
+        ret._setTrace(this.uncancellable, this);
+        ret._follow(this);
+        ret._unsetCancellable();
+        if (this._isBound()) ret._setBoundTo(this._boundTo);
+        return ret;
+    };
+
+    Promise.prototype.fork =
+    function Promise$fork(didFulfill, didReject, didProgress) {
+        var ret = this._then(didFulfill, didReject, didProgress,
+            void 0, void 0, this.fork);
+
+        ret._setCancellable();
+        ret._cancellationParent = void 0;
+        return ret;
+    };
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/captured_trace.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/captured_trace.js
new file mode 100644
index 0000000..af34f11
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/captured_trace.js
@@ -0,0 +1,237 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function() {
+var inherits = require("./util.js").inherits;
+var defineProperty = require("./es5.js").defineProperty;
+
+var rignore = new RegExp(
+    "\\b(?:[\\w.]*Promise(?:Array|Spawn)?\\$_\\w+|" +
+    "tryCatch(?:1|2|Apply)|new \\w*PromiseArray|" +
+    "\\w*PromiseArray\\.\\w*PromiseArray|" +
+    "setTimeout|CatchFilter\\$_\\w+|makeNodePromisified|processImmediate|" +
+    "process._tickCallback|nextTick|Async\\$\\w+)\\b"
+);
+
+var rtraceline = null;
+var formatStack = null;
+var areNamesMangled = false;
+
+function formatNonError(obj) {
+    var str;
+    if (typeof obj === "function") {
+        str = "[function " +
+            (obj.name || "anonymous") +
+            "]";
+    }
+    else {
+        str = obj.toString();
+        var ruselessToString = /\[object [a-zA-Z0-9$_]+\]/;
+        if (ruselessToString.test(str)) {
+            try {
+                var newStr = JSON.stringify(obj);
+                str = newStr;
+            }
+            catch(e) {
+
+            }
+        }
+        if (str.length === 0) {
+            str = "(empty array)";
+        }
+    }
+    return ("(<" + snip(str) + ">, no stack trace)");
+}
+
+function snip(str) {
+    var maxChars = 41;
+    if (str.length < maxChars) {
+        return str;
+    }
+    return str.substr(0, maxChars - 3) + "...";
+}
+
+function CapturedTrace(ignoreUntil, isTopLevel) {
+    if (!areNamesMangled) {
+    }
+    this.captureStackTrace(ignoreUntil, isTopLevel);
+
+}
+inherits(CapturedTrace, Error);
+
+CapturedTrace.prototype.captureStackTrace =
+function CapturedTrace$captureStackTrace(ignoreUntil, isTopLevel) {
+    captureStackTrace(this, ignoreUntil, isTopLevel);
+};
+
+CapturedTrace.possiblyUnhandledRejection =
+function CapturedTrace$PossiblyUnhandledRejection(reason) {
+    if (typeof console === "object") {
+        var message;
+        if (typeof reason === "object" || typeof reason === "function") {
+            var stack = reason.stack;
+            message = "Possibly unhandled " + formatStack(stack, reason);
+        }
+        else {
+            message = "Possibly unhandled " + String(reason);
+        }
+        if (typeof console.error === "function" ||
+            typeof console.error === "object") {
+            console.error(message);
+        }
+        else if (typeof console.log === "function" ||
+            typeof console.error === "object") {
+            console.log(message);
+        }
+    }
+};
+
+areNamesMangled = CapturedTrace.prototype.captureStackTrace.name !==
+    "CapturedTrace$captureStackTrace";
+
+CapturedTrace.combine = function CapturedTrace$Combine(current, prev) {
+    var curLast = current.length - 1;
+    for (var i = prev.length - 1; i >= 0; --i) {
+        var line = prev[i];
+        if (current[curLast] === line) {
+            current.pop();
+            curLast--;
+        }
+        else {
+            break;
+        }
+    }
+
+    current.push("From previous event:");
+    var lines = current.concat(prev);
+
+    var ret = [];
+
+
+    for (var i = 0, len = lines.length; i < len; ++i) {
+
+        if ((rignore.test(lines[i]) ||
+            (i > 0 && !rtraceline.test(lines[i])) &&
+            lines[i] !== "From previous event:")
+       ) {
+            continue;
+        }
+        ret.push(lines[i]);
+    }
+    return ret;
+};
+
+CapturedTrace.isSupported = function CapturedTrace$IsSupported() {
+    return typeof captureStackTrace === "function";
+};
+
+var captureStackTrace = (function stackDetection() {
+    if (typeof Error.stackTraceLimit === "number" &&
+        typeof Error.captureStackTrace === "function") {
+        rtraceline = /^\s*at\s*/;
+        formatStack = function(stack, error) {
+            if (typeof stack === "string") return stack;
+
+            if (error.name !== void 0 &&
+                error.message !== void 0) {
+                return error.name + ". " + error.message;
+            }
+            return formatNonError(error);
+
+
+        };
+        var captureStackTrace = Error.captureStackTrace;
+        return function CapturedTrace$_captureStackTrace(
+            receiver, ignoreUntil) {
+            captureStackTrace(receiver, ignoreUntil);
+        };
+    }
+    var err = new Error();
+
+    if (!areNamesMangled && typeof err.stack === "string" &&
+        typeof "".startsWith === "function" &&
+        (err.stack.startsWith("stackDetection@")) &&
+        stackDetection.name === "stackDetection") {
+
+        defineProperty(Error, "stackTraceLimit", {
+            writable: true,
+            enumerable: false,
+            configurable: false,
+            value: 25
+        });
+        rtraceline = /@/;
+        var rline = /[@\n]/;
+
+        formatStack = function(stack, error) {
+            if (typeof stack === "string") {
+                return (error.name + ". " + error.message + "\n" + stack);
+            }
+
+            if (error.name !== void 0 &&
+                error.message !== void 0) {
+                return error.name + ". " + error.message;
+            }
+            return formatNonError(error);
+        };
+
+        return function captureStackTrace(o, fn) {
+            var name = fn.name;
+            var stack = new Error().stack;
+            var split = stack.split(rline);
+            var i, len = split.length;
+            for (i = 0; i < len; i += 2) {
+                if (split[i] === name) {
+                    break;
+                }
+            }
+            split = split.slice(i + 2);
+            len = split.length - 2;
+            var ret = "";
+            for (i = 0; i < len; i += 2) {
+                ret += split[i];
+                ret += "@";
+                ret += split[i + 1];
+                ret += "\n";
+            }
+            o.stack = ret;
+        };
+    }
+    else {
+        formatStack = function(stack, error) {
+            if (typeof stack === "string") return stack;
+
+            if ((typeof error === "object" ||
+                typeof error === "function") &&
+                error.name !== void 0 &&
+                error.message !== void 0) {
+                return error.name + ". " + error.message;
+            }
+            return formatNonError(error);
+        };
+
+        return null;
+    }
+})();
+
+return CapturedTrace;
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/catch_filter.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/catch_filter.js
new file mode 100644
index 0000000..8b42af1
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/catch_filter.js
@@ -0,0 +1,93 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(NEXT_FILTER) {
+var util = require("./util.js");
+var errors = require("./errors.js");
+var tryCatch1 = util.tryCatch1;
+var errorObj = util.errorObj;
+var keys = require("./es5.js").keys;
+
+function CatchFilter(instances, callback, promise) {
+    this._instances = instances;
+    this._callback = callback;
+    this._promise = promise;
+}
+
+function CatchFilter$_safePredicate(predicate, e) {
+    var safeObject = {};
+    var retfilter = tryCatch1(predicate, safeObject, e);
+
+    if (retfilter === errorObj) return retfilter;
+
+    var safeKeys = keys(safeObject);
+    if (safeKeys.length) {
+        errorObj.e = new TypeError(
+            "Catch filter must inherit from Error "
+          + "or be a simple predicate function");
+        return errorObj;
+    }
+    return retfilter;
+}
+
+CatchFilter.prototype.doFilter = function CatchFilter$_doFilter(e) {
+    var cb = this._callback;
+    var promise = this._promise;
+    var boundTo = promise._isBound() ? promise._boundTo : void 0;
+    for (var i = 0, len = this._instances.length; i < len; ++i) {
+        var item = this._instances[i];
+        var itemIsErrorType = item === Error ||
+            (item != null && item.prototype instanceof Error);
+
+        if (itemIsErrorType && e instanceof item) {
+            var ret = tryCatch1(cb, boundTo, e);
+            if (ret === errorObj) {
+                NEXT_FILTER.e = ret.e;
+                return NEXT_FILTER;
+            }
+            return ret;
+        } else if (typeof item === "function" && !itemIsErrorType) {
+            var shouldHandle = CatchFilter$_safePredicate(item, e);
+            if (shouldHandle === errorObj) {
+                var trace = errors.canAttach(errorObj.e)
+                    ? errorObj.e
+                    : new Error(errorObj.e + "");
+                this._promise._attachExtraTrace(trace);
+                e = errorObj.e;
+                break;
+            } else if (shouldHandle) {
+                var ret = tryCatch1(cb, boundTo, e);
+                if (ret === errorObj) {
+                    NEXT_FILTER.e = ret.e;
+                    return NEXT_FILTER;
+                }
+                return ret;
+            }
+        }
+    }
+    NEXT_FILTER.e = e;
+    return NEXT_FILTER;
+};
+
+return CatchFilter;
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/direct_resolve.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/direct_resolve.js
new file mode 100644
index 0000000..f4d5384
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/direct_resolve.js
@@ -0,0 +1,83 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var util = require("./util.js");
+var isPrimitive = util.isPrimitive;
+var wrapsPrimitiveReceiver = util.wrapsPrimitiveReceiver;
+
+module.exports = function(Promise) {
+var returner = function Promise$_returner() {
+    return this;
+};
+var thrower = function Promise$_thrower() {
+    throw this;
+};
+
+var wrapper = function Promise$_wrapper(value, action) {
+    if (action === 1) {
+        return function Promise$_thrower() {
+            throw value;
+        };
+    }
+    else if (action === 2) {
+        return function Promise$_returner() {
+            return value;
+        };
+    }
+};
+
+
+Promise.prototype["return"] =
+Promise.prototype.thenReturn =
+function Promise$thenReturn(value) {
+    if (wrapsPrimitiveReceiver && isPrimitive(value)) {
+        return this._then(
+            wrapper(value, 2),
+            void 0,
+            void 0,
+            void 0,
+            void 0,
+            this.thenReturn
+       );
+    }
+    return this._then(returner, void 0, void 0,
+                        value, void 0, this.thenReturn);
+};
+
+Promise.prototype["throw"] =
+Promise.prototype.thenThrow =
+function Promise$thenThrow(reason) {
+    if (wrapsPrimitiveReceiver && isPrimitive(reason)) {
+        return this._then(
+            wrapper(reason, 1),
+            void 0,
+            void 0,
+            void 0,
+            void 0,
+            this.thenThrow
+       );
+    }
+    return this._then(thrower, void 0, void 0,
+                        reason, void 0, this.thenThrow);
+};
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/errors.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/errors.js
new file mode 100644
index 0000000..35fb66e
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/errors.js
@@ -0,0 +1,114 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var global = require("./global.js");
+var Objectfreeze = require("./es5.js").freeze;
+var util = require("./util.js");
+var inherits = util.inherits;
+var notEnumerableProp = util.notEnumerableProp;
+var Error = global.Error;
+
+function markAsOriginatingFromRejection(e) {
+    try {
+        notEnumerableProp(e, "isAsync", true);
+    }
+    catch(ignore) {}
+}
+
+function originatesFromRejection(e) {
+    if (e == null) return false;
+    return ((e instanceof RejectionError) ||
+        e["isAsync"] === true);
+}
+
+function isError(obj) {
+    return obj instanceof Error;
+}
+
+function canAttach(obj) {
+    return isError(obj);
+}
+
+function subError(nameProperty, defaultMessage) {
+    function SubError(message) {
+        if (!(this instanceof SubError)) return new SubError(message);
+        this.message = typeof message === "string" ? message : defaultMessage;
+        this.name = nameProperty;
+        if (Error.captureStackTrace) {
+            Error.captureStackTrace(this, this.constructor);
+        }
+    }
+    inherits(SubError, Error);
+    return SubError;
+}
+
+var TypeError = global.TypeError;
+if (typeof TypeError !== "function") {
+    TypeError = subError("TypeError", "type error");
+}
+var RangeError = global.RangeError;
+if (typeof RangeError !== "function") {
+    RangeError = subError("RangeError", "range error");
+}
+var CancellationError = subError("CancellationError", "cancellation error");
+var TimeoutError = subError("TimeoutError", "timeout error");
+
+function RejectionError(message) {
+    this.name = "RejectionError";
+    this.message = message;
+    this.cause = message;
+    this.isAsync = true;
+
+    if (message instanceof Error) {
+        this.message = message.message;
+        this.stack = message.stack;
+    }
+    else if (Error.captureStackTrace) {
+        Error.captureStackTrace(this, this.constructor);
+    }
+
+}
+inherits(RejectionError, Error);
+
+var key = "__BluebirdErrorTypes__";
+var errorTypes = global[key];
+if (!errorTypes) {
+    errorTypes = Objectfreeze({
+        CancellationError: CancellationError,
+        TimeoutError: TimeoutError,
+        RejectionError: RejectionError
+    });
+    notEnumerableProp(global, key, errorTypes);
+}
+
+module.exports = {
+    Error: Error,
+    TypeError: TypeError,
+    RangeError: RangeError,
+    CancellationError: errorTypes.CancellationError,
+    RejectionError: errorTypes.RejectionError,
+    TimeoutError: errorTypes.TimeoutError,
+    originatesFromRejection: originatesFromRejection,
+    markAsOriginatingFromRejection: markAsOriginatingFromRejection,
+    canAttach: canAttach
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/errors_api_rejection.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/errors_api_rejection.js
new file mode 100644
index 0000000..e953e3b
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/errors_api_rejection.js
@@ -0,0 +1,38 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise) {
+var TypeError = require('./errors.js').TypeError;
+
+function apiRejection(msg) {
+    var error = new TypeError(msg);
+    var ret = Promise.rejected(error);
+    var parent = ret._peekContext();
+    if (parent != null) {
+        parent._attachExtraTrace(error);
+    }
+    return ret;
+}
+
+return apiRejection;
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/es5.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/es5.js
new file mode 100644
index 0000000..e22a0a9
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/es5.js
@@ -0,0 +1,89 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+var isES5 = (function(){
+    "use strict";
+    return this === void 0;
+})();
+
+if (isES5) {
+    module.exports = {
+        freeze: Object.freeze,
+        defineProperty: Object.defineProperty,
+        keys: Object.keys,
+        getPrototypeOf: Object.getPrototypeOf,
+        isArray: Array.isArray,
+        isES5: isES5
+    };
+}
+
+else {
+    var has = {}.hasOwnProperty;
+    var str = {}.toString;
+    var proto = {}.constructor.prototype;
+
+    function ObjectKeys(o) {
+        var ret = [];
+        for (var key in o) {
+            if (has.call(o, key)) {
+                ret.push(key);
+            }
+        }
+        return ret;
+    }
+
+    function ObjectDefineProperty(o, key, desc) {
+        o[key] = desc.value;
+        return o;
+    }
+
+    function ObjectFreeze(obj) {
+        return obj;
+    }
+
+    function ObjectGetPrototypeOf(obj) {
+        try {
+            return Object(obj).constructor.prototype;
+        }
+        catch (e) {
+            return proto;
+        }
+    }
+
+    function ArrayIsArray(obj) {
+        try {
+            return str.call(obj) === "[object Array]";
+        }
+        catch(e) {
+            return false;
+        }
+    }
+
+    module.exports = {
+        isArray: ArrayIsArray,
+        keys: ObjectKeys,
+        defineProperty: ObjectDefineProperty,
+        freeze: ObjectFreeze,
+        getPrototypeOf: ObjectGetPrototypeOf,
+        isES5: isES5
+    };
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/filter.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/filter.js
new file mode 100644
index 0000000..a4b8ae7
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/filter.js
@@ -0,0 +1,53 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise) {
+    var isArray = require("./util.js").isArray;
+
+    function Promise$_filter(booleans) {
+        var values = this._settledValue;
+        var len = values.length;
+        var ret = new Array(len);
+        var j = 0;
+
+        for (var i = 0; i < len; ++i) {
+            if (booleans[i]) ret[j++] = values[i];
+
+        }
+        ret.length = j;
+        return ret;
+    }
+
+    var ref = {ref: null};
+    Promise.filter = function Promise$Filter(promises, fn) {
+        return Promise.map(promises, fn, ref)
+            ._then(Promise$_filter, void 0, void 0,
+                    ref.ref, void 0, Promise.filter);
+    };
+
+    Promise.prototype.filter = function Promise$filter(fn) {
+        return this.map(fn, ref)
+            ._then(Promise$_filter, void 0, void 0,
+                    ref.ref, void 0, this.filter);
+    };
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/finally.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/finally.js
new file mode 100644
index 0000000..ef1e0ef
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/finally.js
@@ -0,0 +1,132 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, NEXT_FILTER) {
+    var util = require("./util.js");
+    var wrapsPrimitiveReceiver = util.wrapsPrimitiveReceiver;
+    var isPrimitive = util.isPrimitive;
+    var thrower = util.thrower;
+
+
+    function returnThis() {
+        return this;
+    }
+    function throwThis() {
+        throw this;
+    }
+    function makeReturner(r) {
+        return function Promise$_returner() {
+            return r;
+        };
+    }
+    function makeThrower(r) {
+        return function Promise$_thrower() {
+            throw r;
+        };
+    }
+    function promisedFinally(ret, reasonOrValue, isFulfilled) {
+        var useConstantFunction =
+                        wrapsPrimitiveReceiver && isPrimitive(reasonOrValue);
+
+        if (isFulfilled) {
+            return ret._then(
+                useConstantFunction
+                    ? returnThis
+                    : makeReturner(reasonOrValue),
+                thrower, void 0, reasonOrValue, void 0, promisedFinally);
+        }
+        else {
+            return ret._then(
+                useConstantFunction
+                    ? throwThis
+                    : makeThrower(reasonOrValue),
+                thrower, void 0, reasonOrValue, void 0, promisedFinally);
+        }
+    }
+
+    function finallyHandler(reasonOrValue) {
+        var promise = this.promise;
+        var handler = this.handler;
+
+        var ret = promise._isBound()
+                        ? handler.call(promise._boundTo)
+                        : handler();
+
+        if (ret !== void 0) {
+            var maybePromise = Promise._cast(ret, finallyHandler, void 0);
+            if (maybePromise instanceof Promise) {
+                return promisedFinally(maybePromise, reasonOrValue,
+                                        promise.isFulfilled());
+            }
+        }
+
+        if (promise.isRejected()) {
+            NEXT_FILTER.e = reasonOrValue;
+            return NEXT_FILTER;
+        }
+        else {
+            return reasonOrValue;
+        }
+    }
+
+    function tapHandler(value) {
+        var promise = this.promise;
+        var handler = this.handler;
+
+        var ret = promise._isBound()
+                        ? handler.call(promise._boundTo, value)
+                        : handler(value);
+
+        if (ret !== void 0) {
+            var maybePromise = Promise._cast(ret, tapHandler, void 0);
+            if (maybePromise instanceof Promise) {
+                return promisedFinally(maybePromise, value, true);
+            }
+        }
+        return value;
+    }
+
+    Promise.prototype._passThroughHandler =
+    function Promise$_passThroughHandler(handler, isFinally, caller) {
+        if (typeof handler !== "function") return this.then();
+
+        var promiseAndHandler = {
+            promise: this,
+            handler: handler
+        };
+
+        return this._then(
+                isFinally ? finallyHandler : tapHandler,
+                isFinally ? finallyHandler : void 0, void 0,
+                promiseAndHandler, void 0, caller);
+    };
+
+    Promise.prototype.lastly =
+    Promise.prototype["finally"] = function Promise$finally(handler) {
+        return this._passThroughHandler(handler, true, this.lastly);
+    };
+
+    Promise.prototype.tap = function Promise$tap(handler) {
+        return this._passThroughHandler(handler, false, this.tap);
+    };
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/generators.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/generators.js
new file mode 100644
index 0000000..9632ae7
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/generators.js
@@ -0,0 +1,56 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, apiRejection, INTERNAL) {
+    var PromiseSpawn = require("./promise_spawn.js")(Promise, INTERNAL);
+    var errors = require("./errors.js");
+    var TypeError = errors.TypeError;
+    var deprecated = require("./util.js").deprecated;
+
+    Promise.coroutine = function Promise$Coroutine(generatorFunction) {
+        if (typeof generatorFunction !== "function") {
+            throw new TypeError("generatorFunction must be a function");
+        }
+        var PromiseSpawn$ = PromiseSpawn;
+        return function anonymous() {
+            var generator = generatorFunction.apply(this, arguments);
+            var spawn = new PromiseSpawn$(void 0, void 0, anonymous);
+            spawn._generator = generator;
+            spawn._next(void 0);
+            return spawn.promise();
+        };
+    };
+
+    Promise.coroutine.addYieldHandler = PromiseSpawn.addYieldHandler;
+
+    Promise.spawn = function Promise$Spawn(generatorFunction) {
+        deprecated("Promise.spawn is deprecated. Use Promise.coroutine instead.");
+        if (typeof generatorFunction !== "function") {
+            return apiRejection("generatorFunction must be a function");
+        }
+        var spawn = new PromiseSpawn(generatorFunction, this, Promise.spawn);
+        var ret = spawn.promise();
+        spawn._run(Promise.spawn);
+        return ret;
+    };
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/global.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/global.js
new file mode 100644
index 0000000..1ab1947
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/global.js
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = (function(){
+    if (typeof this !== "undefined") {
+        return this;
+    }
+    if (typeof process !== "undefined" &&
+        typeof global !== "undefined" &&
+        typeof process.execPath === "string") {
+        return global;
+    }
+    if (typeof window !== "undefined" &&
+        typeof document !== "undefined" &&
+        typeof navigator !== "undefined" && navigator !== null &&
+        typeof navigator.appName === "string") {
+            if(window.wrappedJSObject !== undefined){
+                return window.wrappedJSObject;
+            }
+        return window;
+    }
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/map.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/map.js
new file mode 100644
index 0000000..b2a36b0
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/map.js
@@ -0,0 +1,127 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(
+    Promise, Promise$_CreatePromiseArray, PromiseArray, apiRejection) {
+
+    function Promise$_mapper(values) {
+        var fn = this;
+        var receiver = void 0;
+
+        if (typeof fn !== "function")  {
+            receiver = fn.receiver;
+            fn = fn.fn;
+        }
+        var shouldDefer = false;
+
+        var ret = new Array(values.length);
+
+        if (receiver === void 0) {
+            for (var i = 0, len = values.length; i < len; ++i) {
+                var value = fn(values[i], i, len);
+                if (!shouldDefer) {
+                    var maybePromise = Promise._cast(value,
+                            Promise$_mapper, void 0);
+                    if (maybePromise instanceof Promise) {
+                        if (maybePromise.isFulfilled()) {
+                            ret[i] = maybePromise._settledValue;
+                            continue;
+                        }
+                        else {
+                            shouldDefer = true;
+                        }
+                        value = maybePromise;
+                    }
+                }
+                ret[i] = value;
+            }
+        }
+        else {
+            for (var i = 0, len = values.length; i < len; ++i) {
+                var value = fn.call(receiver, values[i], i, len);
+                if (!shouldDefer) {
+                    var maybePromise = Promise._cast(value,
+                            Promise$_mapper, void 0);
+                    if (maybePromise instanceof Promise) {
+                        if (maybePromise.isFulfilled()) {
+                            ret[i] = maybePromise._settledValue;
+                            continue;
+                        }
+                        else {
+                            shouldDefer = true;
+                        }
+                        value = maybePromise;
+                    }
+                }
+                ret[i] = value;
+            }
+        }
+        return shouldDefer
+            ? Promise$_CreatePromiseArray(ret, PromiseArray,
+                Promise$_mapper, void 0).promise()
+            : ret;
+    }
+
+    function Promise$_Map(promises, fn, useBound, caller, ref) {
+        if (typeof fn !== "function") {
+            return apiRejection("fn must be a function");
+        }
+
+        if (useBound === true && promises._isBound()) {
+            fn = {
+                fn: fn,
+                receiver: promises._boundTo
+            };
+        }
+
+        var ret = Promise$_CreatePromiseArray(
+            promises,
+            PromiseArray,
+            caller,
+            useBound === true && promises._isBound()
+                ? promises._boundTo
+                : void 0
+       ).promise();
+
+        if (ref !== void 0) {
+            ref.ref = ret;
+        }
+
+        return ret._then(
+            Promise$_mapper,
+            void 0,
+            void 0,
+            fn,
+            void 0,
+            caller
+       );
+    }
+
+    Promise.prototype.map = function Promise$map(fn, ref) {
+        return Promise$_Map(this, fn, true, this.map, ref);
+    };
+
+    Promise.map = function Promise$Map(promises, fn, ref) {
+        return Promise$_Map(promises, fn, false, Promise.map, ref);
+    };
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/nodeify.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/nodeify.js
new file mode 100644
index 0000000..9fe25f9
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/nodeify.js
@@ -0,0 +1,64 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise) {
+    var util = require("./util.js");
+    var async = require("./async.js");
+    var tryCatch2 = util.tryCatch2;
+    var tryCatch1 = util.tryCatch1;
+    var errorObj = util.errorObj;
+
+    function thrower(r) {
+        throw r;
+    }
+
+    function Promise$_successAdapter(val, receiver) {
+        var nodeback = this;
+        var ret = tryCatch2(nodeback, receiver, null, val);
+        if (ret === errorObj) {
+            async.invokeLater(thrower, void 0, ret.e);
+        }
+    }
+    function Promise$_errorAdapter(reason, receiver) {
+        var nodeback = this;
+        var ret = tryCatch1(nodeback, receiver, reason);
+        if (ret === errorObj) {
+            async.invokeLater(thrower, void 0, ret.e);
+        }
+    }
+
+    Promise.prototype.nodeify = function Promise$nodeify(nodeback) {
+        if (typeof nodeback == "function") {
+            this._then(
+                Promise$_successAdapter,
+                Promise$_errorAdapter,
+                void 0,
+                nodeback,
+                this._isBound() ? this._boundTo : null,
+                this.nodeify
+            );
+            return;
+        }
+        return this;
+    };
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/progress.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/progress.js
new file mode 100644
index 0000000..668f5f7
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/progress.js
@@ -0,0 +1,111 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, isPromiseArrayProxy) {
+    var util = require("./util.js");
+    var async = require("./async.js");
+    var errors = require("./errors.js");
+    var tryCatch1 = util.tryCatch1;
+    var errorObj = util.errorObj;
+
+    Promise.prototype.progressed = function Promise$progressed(handler) {
+        return this._then(void 0, void 0, handler,
+                            void 0, void 0, this.progressed);
+    };
+
+    Promise.prototype._progress = function Promise$_progress(progressValue) {
+        if (this._isFollowingOrFulfilledOrRejected()) return;
+        this._progressUnchecked(progressValue);
+
+    };
+
+    Promise.prototype._progressHandlerAt =
+    function Promise$_progressHandlerAt(index) {
+        if (index === 0) return this._progressHandler0;
+        return this[index + 2 - 5];
+    };
+
+    Promise.prototype._doProgressWith =
+    function Promise$_doProgressWith(progression) {
+        var progressValue = progression.value;
+        var handler = progression.handler;
+        var promise = progression.promise;
+        var receiver = progression.receiver;
+
+        this._pushContext();
+        var ret = tryCatch1(handler, receiver, progressValue);
+        this._popContext();
+
+        if (ret === errorObj) {
+            if (ret.e != null &&
+                ret.e.name !== "StopProgressPropagation") {
+                var trace = errors.canAttach(ret.e)
+                    ? ret.e : new Error(ret.e + "");
+                promise._attachExtraTrace(trace);
+                promise._progress(ret.e);
+            }
+        }
+        else if (Promise.is(ret)) {
+            ret._then(promise._progress, null, null, promise, void 0,
+                this._progress);
+        }
+        else {
+            promise._progress(ret);
+        }
+    };
+
+
+    Promise.prototype._progressUnchecked =
+    function Promise$_progressUnchecked(progressValue) {
+        if (!this.isPending()) return;
+        var len = this._length();
+
+        for (var i = 0; i < len; i += 5) {
+            var handler = this._progressHandlerAt(i);
+            var promise = this._promiseAt(i);
+            if (!Promise.is(promise)) {
+                var receiver = this._receiverAt(i);
+                if (typeof handler === "function") {
+                    handler.call(receiver, progressValue, promise);
+                }
+                else if (Promise.is(receiver) && receiver._isProxied()) {
+                    receiver._progressUnchecked(progressValue);
+                }
+                else if (isPromiseArrayProxy(receiver, promise)) {
+                    receiver._promiseProgressed(progressValue, promise);
+                }
+                continue;
+            }
+
+            if (typeof handler === "function") {
+                this._doProgressWith(({handler: handler,
+promise: promise,
+receiver: this._receiverAt(i),
+value: progressValue}));
+            }
+            else {
+                promise._progress(progressValue);
+            }
+        }
+    };
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/promise.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/promise.js
new file mode 100644
index 0000000..f1c9ddc
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/promise.js
@@ -0,0 +1,1167 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function() {
+var global = require("./global.js");
+var util = require("./util.js");
+var async = require("./async.js");
+var errors = require("./errors.js");
+
+var INTERNAL = function(){};
+var APPLY = {};
+var NEXT_FILTER = {e: null};
+
+var PromiseArray = require("./promise_array.js")(Promise, INTERNAL);
+var CapturedTrace = require("./captured_trace.js")();
+var CatchFilter = require("./catch_filter.js")(NEXT_FILTER);
+var PromiseResolver = require("./promise_resolver.js");
+
+var isArray = util.isArray;
+
+var errorObj = util.errorObj;
+var tryCatch1 = util.tryCatch1;
+var tryCatch2 = util.tryCatch2;
+var tryCatchApply = util.tryCatchApply;
+var RangeError = errors.RangeError;
+var TypeError = errors.TypeError;
+var CancellationError = errors.CancellationError;
+var TimeoutError = errors.TimeoutError;
+var RejectionError = errors.RejectionError;
+var originatesFromRejection = errors.originatesFromRejection;
+var markAsOriginatingFromRejection = errors.markAsOriginatingFromRejection;
+var canAttach = errors.canAttach;
+var thrower = util.thrower;
+var apiRejection = require("./errors_api_rejection")(Promise);
+
+
+var makeSelfResolutionError = function Promise$_makeSelfResolutionError() {
+    return new TypeError("circular promise resolution chain");
+};
+
+function isPromise(obj) {
+    if (obj === void 0) return false;
+    return obj instanceof Promise;
+}
+
+function isPromiseArrayProxy(receiver, promiseSlotValue) {
+    if (receiver instanceof PromiseArray) {
+        return promiseSlotValue >= 0;
+    }
+    return false;
+}
+
+function Promise(resolver) {
+    if (typeof resolver !== "function") {
+        throw new TypeError("the promise constructor requires a resolver function");
+    }
+    if (this.constructor !== Promise) {
+        throw new TypeError("the promise constructor cannot be invoked directly");
+    }
+    this._bitField = 0;
+    this._fulfillmentHandler0 = void 0;
+    this._rejectionHandler0 = void 0;
+    this._promise0 = void 0;
+    this._receiver0 = void 0;
+    this._settledValue = void 0;
+    this._boundTo = void 0;
+    if (resolver !== INTERNAL) this._resolveFromResolver(resolver);
+}
+
+Promise.prototype.bind = function Promise$bind(thisArg) {
+    var ret = new Promise(INTERNAL);
+    if (debugging) ret._setTrace(this.bind, this);
+    ret._follow(this);
+    ret._setBoundTo(thisArg);
+    if (this._cancellable()) {
+        ret._setCancellable();
+        ret._cancellationParent = this;
+    }
+    return ret;
+};
+
+Promise.prototype.toString = function Promise$toString() {
+    return "[object Promise]";
+};
+
+Promise.prototype.caught = Promise.prototype["catch"] =
+function Promise$catch(fn) {
+    var len = arguments.length;
+    if (len > 1) {
+        var catchInstances = new Array(len - 1),
+            j = 0, i;
+        for (i = 0; i < len - 1; ++i) {
+            var item = arguments[i];
+            if (typeof item === "function") {
+                catchInstances[j++] = item;
+            }
+            else {
+                var catchFilterTypeError =
+                    new TypeError(
+                        "A catch filter must be an error constructor "
+                        + "or a filter function");
+
+                this._attachExtraTrace(catchFilterTypeError);
+                this._reject(catchFilterTypeError);
+                return;
+            }
+        }
+        catchInstances.length = j;
+        fn = arguments[i];
+
+        this._resetTrace(this.caught);
+        var catchFilter = new CatchFilter(catchInstances, fn, this);
+        return this._then(void 0, catchFilter.doFilter, void 0,
+            catchFilter, void 0, this.caught);
+    }
+    return this._then(void 0, fn, void 0, void 0, void 0, this.caught);
+};
+
+Promise.prototype.then =
+function Promise$then(didFulfill, didReject, didProgress) {
+    return this._then(didFulfill, didReject, didProgress,
+        void 0, void 0, this.then);
+};
+
+
+Promise.prototype.done =
+function Promise$done(didFulfill, didReject, didProgress) {
+    var promise = this._then(didFulfill, didReject, didProgress,
+        void 0, void 0, this.done);
+    promise._setIsFinal();
+};
+
+Promise.prototype.spread = function Promise$spread(didFulfill, didReject) {
+    return this._then(didFulfill, didReject, void 0,
+        APPLY, void 0, this.spread);
+};
+
+Promise.prototype.isFulfilled = function Promise$isFulfilled() {
+    return (this._bitField & 268435456) > 0;
+};
+
+
+Promise.prototype.isRejected = function Promise$isRejected() {
+    return (this._bitField & 134217728) > 0;
+};
+
+Promise.prototype.isPending = function Promise$isPending() {
+    return !this.isResolved();
+};
+
+
+Promise.prototype.isResolved = function Promise$isResolved() {
+    return (this._bitField & 402653184) > 0;
+};
+
+
+Promise.prototype.isCancellable = function Promise$isCancellable() {
+    return !this.isResolved() &&
+        this._cancellable();
+};
+
+Promise.prototype.toJSON = function Promise$toJSON() {
+    var ret = {
+        isFulfilled: false,
+        isRejected: false,
+        fulfillmentValue: void 0,
+        rejectionReason: void 0
+    };
+    if (this.isFulfilled()) {
+        ret.fulfillmentValue = this._settledValue;
+        ret.isFulfilled = true;
+    }
+    else if (this.isRejected()) {
+        ret.rejectionReason = this._settledValue;
+        ret.isRejected = true;
+    }
+    return ret;
+};
+
+Promise.prototype.all = function Promise$all() {
+    return Promise$_all(this, true, this.all);
+};
+
+
+Promise.is = isPromise;
+
+function Promise$_all(promises, useBound, caller) {
+    return Promise$_CreatePromiseArray(
+        promises,
+        PromiseArray,
+        caller,
+        useBound === true && promises._isBound()
+            ? promises._boundTo
+            : void 0
+   ).promise();
+}
+Promise.all = function Promise$All(promises) {
+    return Promise$_all(promises, false, Promise.all);
+};
+
+Promise.join = function Promise$Join() {
+    var $_len = arguments.length;var args = new Array($_len); for(var $_i = 0; $_i < $_len; ++$_i) {args[$_i] = arguments[$_i];}
+    return Promise$_CreatePromiseArray(
+        args, PromiseArray, Promise.join, void 0).promise();
+};
+
+Promise.resolve = Promise.fulfilled =
+function Promise$Resolve(value, caller) {
+    var ret = new Promise(INTERNAL);
+    if (debugging) ret._setTrace(typeof caller === "function"
+        ? caller
+        : Promise.resolve, void 0);
+    if (ret._tryFollow(value)) {
+        return ret;
+    }
+    ret._cleanValues();
+    ret._setFulfilled();
+    ret._settledValue = value;
+    return ret;
+};
+
+Promise.reject = Promise.rejected = function Promise$Reject(reason) {
+    var ret = new Promise(INTERNAL);
+    if (debugging) ret._setTrace(Promise.reject, void 0);
+    markAsOriginatingFromRejection(reason);
+    ret._cleanValues();
+    ret._setRejected();
+    ret._settledValue = reason;
+    if (!canAttach(reason)) {
+        var trace = new Error(reason + "");
+        ret._setCarriedStackTrace(trace);
+    }
+    ret._ensurePossibleRejectionHandled();
+    return ret;
+};
+
+Promise.prototype.error = function Promise$_error(fn) {
+    return this.caught(originatesFromRejection, fn);
+};
+
+Promise.prototype._resolveFromSyncValue =
+function Promise$_resolveFromSyncValue(value, caller) {
+    if (value === errorObj) {
+        this._cleanValues();
+        this._setRejected();
+        this._settledValue = value.e;
+        this._ensurePossibleRejectionHandled();
+    }
+    else {
+        var maybePromise = Promise._cast(value, caller, void 0);
+        if (maybePromise instanceof Promise) {
+            this._follow(maybePromise);
+        }
+        else {
+            this._cleanValues();
+            this._setFulfilled();
+            this._settledValue = value;
+        }
+    }
+};
+
+Promise.method = function Promise$_Method(fn) {
+    if (typeof fn !== "function") {
+        throw new TypeError("fn must be a function");
+    }
+    return function Promise$_method() {
+        var value;
+        switch(arguments.length) {
+        case 0: value = tryCatch1(fn, this, void 0); break;
+        case 1: value = tryCatch1(fn, this, arguments[0]); break;
+        case 2: value = tryCatch2(fn, this, arguments[0], arguments[1]); break;
+        default:
+            var $_len = arguments.length;var args = new Array($_len); for(var $_i = 0; $_i < $_len; ++$_i) {args[$_i] = arguments[$_i];}
+            value = tryCatchApply(fn, args, this); break;
+        }
+        var ret = new Promise(INTERNAL);
+        if (debugging) ret._setTrace(Promise$_method, void 0);
+        ret._resolveFromSyncValue(value, Promise$_method);
+        return ret;
+    };
+};
+
+Promise.attempt = Promise["try"] = function Promise$_Try(fn, args, ctx) {
+
+    if (typeof fn !== "function") {
+        return apiRejection("fn must be a function");
+    }
+    var value = isArray(args)
+        ? tryCatchApply(fn, args, ctx)
+        : tryCatch1(fn, ctx, args);
+
+    var ret = new Promise(INTERNAL);
+    if (debugging) ret._setTrace(Promise.attempt, void 0);
+    ret._resolveFromSyncValue(value, Promise.attempt);
+    return ret;
+};
+
+Promise.defer = Promise.pending = function Promise$Defer(caller) {
+    var promise = new Promise(INTERNAL);
+    if (debugging) promise._setTrace(typeof caller === "function"
+                              ? caller : Promise.defer, void 0);
+    return new PromiseResolver(promise);
+};
+
+Promise.bind = function Promise$Bind(thisArg) {
+    var ret = new Promise(INTERNAL);
+    if (debugging) ret._setTrace(Promise.bind, void 0);
+    ret._setFulfilled();
+    ret._setBoundTo(thisArg);
+    return ret;
+};
+
+Promise.cast = function Promise$_Cast(obj, caller) {
+    if (typeof caller !== "function") {
+        caller = Promise.cast;
+    }
+    var ret = Promise._cast(obj, caller, void 0);
+    if (!(ret instanceof Promise)) {
+        return Promise.resolve(ret, caller);
+    }
+    return ret;
+};
+
+Promise.onPossiblyUnhandledRejection =
+function Promise$OnPossiblyUnhandledRejection(fn) {
+    if (typeof fn === "function") {
+        CapturedTrace.possiblyUnhandledRejection = fn;
+    }
+    else {
+        CapturedTrace.possiblyUnhandledRejection = void 0;
+    }
+};
+
+var debugging = false || !!(
+    typeof process !== "undefined" &&
+    typeof process.execPath === "string" &&
+    typeof process.env === "object" &&
+    (process.env["BLUEBIRD_DEBUG"] ||
+        process.env["NODE_ENV"] === "development")
+);
+
+
+Promise.longStackTraces = function Promise$LongStackTraces() {
+    if (async.haveItemsQueued() &&
+        debugging === false
+   ) {
+        throw new Error("cannot enable long stack traces after promises have been created");
+    }
+    debugging = CapturedTrace.isSupported();
+};
+
+Promise.hasLongStackTraces = function Promise$HasLongStackTraces() {
+    return debugging && CapturedTrace.isSupported();
+};
+
+Promise.prototype._setProxyHandlers =
+function Promise$_setProxyHandlers(receiver, promiseSlotValue) {
+    var index = this._length();
+
+    if (index >= 1048575 - 5) {
+        index = 0;
+        this._setLength(0);
+    }
+    if (index === 0) {
+        this._promise0 = promiseSlotValue;
+        this._receiver0 = receiver;
+    }
+    else {
+        var i = index - 5;
+        this[i + 3] = promiseSlotValue;
+        this[i + 4] = receiver;
+        this[i + 0] =
+        this[i + 1] =
+        this[i + 2] = void 0;
+    }
+    this._setLength(index + 5);
+};
+
+Promise.prototype._proxyPromiseArray =
+function Promise$_proxyPromiseArray(promiseArray, index) {
+    this._setProxyHandlers(promiseArray, index);
+};
+
+Promise.prototype._proxyPromise = function Promise$_proxyPromise(promise) {
+    promise._setProxied();
+    this._setProxyHandlers(promise, -1);
+};
+
+Promise.prototype._then =
+function Promise$_then(
+    didFulfill,
+    didReject,
+    didProgress,
+    receiver,
+    internalData,
+    caller
+) {
+    var haveInternalData = internalData !== void 0;
+    var ret = haveInternalData ? internalData : new Promise(INTERNAL);
+
+    if (debugging && !haveInternalData) {
+        var haveSameContext = this._peekContext() === this._traceParent;
+        ret._traceParent = haveSameContext ? this._traceParent : this;
+        ret._setTrace(typeof caller === "function"
+                ? caller
+                : this._then, this);
+    }
+
+    if (!haveInternalData && this._isBound()) {
+        ret._setBoundTo(this._boundTo);
+    }
+
+    var callbackIndex =
+        this._addCallbacks(didFulfill, didReject, didProgress, ret, receiver);
+
+    if (!haveInternalData && this._cancellable()) {
+        ret._setCancellable();
+        ret._cancellationParent = this;
+    }
+
+    if (this.isResolved()) {
+        this._queueSettleAt(callbackIndex);
+    }
+
+    return ret;
+};
+
+Promise.prototype._length = function Promise$_length() {
+    return this._bitField & 1048575;
+};
+
+Promise.prototype._isFollowingOrFulfilledOrRejected =
+function Promise$_isFollowingOrFulfilledOrRejected() {
+    return (this._bitField & 939524096) > 0;
+};
+
+Promise.prototype._isFollowing = function Promise$_isFollowing() {
+    return (this._bitField & 536870912) === 536870912;
+};
+
+Promise.prototype._setLength = function Promise$_setLength(len) {
+    this._bitField = (this._bitField & -1048576) |
+        (len & 1048575);
+};
+
+Promise.prototype._setFulfilled = function Promise$_setFulfilled() {
+    this._bitField = this._bitField | 268435456;
+};
+
+Promise.prototype._setRejected = function Promise$_setRejected() {
+    this._bitField = this._bitField | 134217728;
+};
+
+Promise.prototype._setFollowing = function Promise$_setFollowing() {
+    this._bitField = this._bitField | 536870912;
+};
+
+Promise.prototype._setIsFinal = function Promise$_setIsFinal() {
+    this._bitField = this._bitField | 33554432;
+};
+
+Promise.prototype._isFinal = function Promise$_isFinal() {
+    return (this._bitField & 33554432) > 0;
+};
+
+Promise.prototype._cancellable = function Promise$_cancellable() {
+    return (this._bitField & 67108864) > 0;
+};
+
+Promise.prototype._setCancellable = function Promise$_setCancellable() {
+    this._bitField = this._bitField | 67108864;
+};
+
+Promise.prototype._unsetCancellable = function Promise$_unsetCancellable() {
+    this._bitField = this._bitField & (~67108864);
+};
+
+Promise.prototype._setRejectionIsUnhandled =
+function Promise$_setRejectionIsUnhandled() {
+    this._bitField = this._bitField | 2097152;
+};
+
+Promise.prototype._unsetRejectionIsUnhandled =
+function Promise$_unsetRejectionIsUnhandled() {
+    this._bitField = this._bitField & (~2097152);
+};
+
+Promise.prototype._isRejectionUnhandled =
+function Promise$_isRejectionUnhandled() {
+    return (this._bitField & 2097152) > 0;
+};
+
+Promise.prototype._setCarriedStackTrace =
+function Promise$_setCarriedStackTrace(capturedTrace) {
+    this._bitField = this._bitField | 1048576;
+    this._fulfillmentHandler0 = capturedTrace;
+};
+
+Promise.prototype._unsetCarriedStackTrace =
+function Promise$_unsetCarriedStackTrace() {
+    this._bitField = this._bitField & (~1048576);
+    this._fulfillmentHandler0 = void 0;
+};
+
+Promise.prototype._isCarryingStackTrace =
+function Promise$_isCarryingStackTrace() {
+    return (this._bitField & 1048576) > 0;
+};
+
+Promise.prototype._getCarriedStackTrace =
+function Promise$_getCarriedStackTrace() {
+    return this._isCarryingStackTrace()
+        ? this._fulfillmentHandler0
+        : void 0;
+};
+
+Promise.prototype._receiverAt = function Promise$_receiverAt(index) {
+    var ret;
+    if (index === 0) {
+        ret = this._receiver0;
+    }
+    else {
+        ret = this[index + 4 - 5];
+    }
+    if (this._isBound() && ret === void 0) {
+        return this._boundTo;
+    }
+    return ret;
+};
+
+Promise.prototype._promiseAt = function Promise$_promiseAt(index) {
+    if (index === 0) return this._promise0;
+    return this[index + 3 - 5];
+};
+
+Promise.prototype._fulfillmentHandlerAt =
+function Promise$_fulfillmentHandlerAt(index) {
+    if (index === 0) return this._fulfillmentHandler0;
+    return this[index + 0 - 5];
+};
+
+Promise.prototype._rejectionHandlerAt =
+function Promise$_rejectionHandlerAt(index) {
+    if (index === 0) return this._rejectionHandler0;
+    return this[index + 1 - 5];
+};
+
+Promise.prototype._unsetAt = function Promise$_unsetAt(index) {
+     if (index === 0) {
+        this._rejectionHandler0 =
+        this._progressHandler0 =
+        this._promise0 =
+        this._receiver0 = void 0;
+        if (!this._isCarryingStackTrace()) {
+            this._fulfillmentHandler0 = void 0;
+        }
+    }
+    else {
+        this[index - 5 + 0] =
+        this[index - 5 + 1] =
+        this[index - 5 + 2] =
+        this[index - 5 + 3] =
+        this[index - 5 + 4] = void 0;
+    }
+};
+
+Promise.prototype._resolveFromResolver =
+function Promise$_resolveFromResolver(resolver) {
+    var promise = this;
+    var localDebugging = debugging;
+    if (localDebugging) {
+        this._setTrace(this._resolveFromResolver, void 0);
+        this._pushContext();
+    }
+    function Promise$_resolver(val) {
+        if (promise._tryFollow(val)) {
+            return;
+        }
+        promise._fulfill(val);
+    }
+    function Promise$_rejecter(val) {
+        var trace = canAttach(val) ? val : new Error(val + "");
+        promise._attachExtraTrace(trace);
+        markAsOriginatingFromRejection(val);
+        promise._reject(val, trace === val ? void 0 : trace);
+    }
+    var r = tryCatch2(resolver, void 0, Promise$_resolver, Promise$_rejecter);
+    if (localDebugging) this._popContext();
+
+    if (r !== void 0 && r === errorObj) {
+        var e = r.e;
+        var trace = canAttach(e) ? e : new Error(e + "");
+        promise._reject(e, trace);
+    }
+};
+
+Promise.prototype._addCallbacks = function Promise$_addCallbacks(
+    fulfill,
+    reject,
+    progress,
+    promise,
+    receiver
+) {
+    var index = this._length();
+
+    if (index >= 1048575 - 5) {
+        index = 0;
+        this._setLength(0);
+    }
+
+    if (index === 0) {
+        this._promise0 = promise;
+        if (receiver !== void 0) this._receiver0 = receiver;
+        if (typeof fulfill === "function" && !this._isCarryingStackTrace())
+            this._fulfillmentHandler0 = fulfill;
+        if (typeof reject === "function") this._rejectionHandler0 = reject;
+        if (typeof progress === "function") this._progressHandler0 = progress;
+    }
+    else {
+        var i = index - 5;
+        this[i + 3] = promise;
+        this[i + 4] = receiver;
+        this[i + 0] = typeof fulfill === "function"
+                                            ? fulfill : void 0;
+        this[i + 1] = typeof reject === "function"
+                                            ? reject : void 0;
+        this[i + 2] = typeof progress === "function"
+                                            ? progress : void 0;
+    }
+    this._setLength(index + 5);
+    return index;
+};
+
+
+
+Promise.prototype._setBoundTo = function Promise$_setBoundTo(obj) {
+    if (obj !== void 0) {
+        this._bitField = this._bitField | 8388608;
+        this._boundTo = obj;
+    }
+    else {
+        this._bitField = this._bitField & (~8388608);
+    }
+};
+
+Promise.prototype._isBound = function Promise$_isBound() {
+    return (this._bitField & 8388608) === 8388608;
+};
+
+Promise.prototype._spreadSlowCase =
+function Promise$_spreadSlowCase(targetFn, promise, values, boundTo) {
+    var promiseForAll =
+            Promise$_CreatePromiseArray
+                (values, PromiseArray, this._spreadSlowCase, boundTo)
+            .promise()
+            ._then(function() {
+                return targetFn.apply(boundTo, arguments);
+            }, void 0, void 0, APPLY, void 0, this._spreadSlowCase);
+
+    promise._follow(promiseForAll);
+};
+
+Promise.prototype._callSpread =
+function Promise$_callSpread(handler, promise, value, localDebugging) {
+    var boundTo = this._isBound() ? this._boundTo : void 0;
+    if (isArray(value)) {
+        var caller = this._settlePromiseFromHandler;
+        for (var i = 0, len = value.length; i < len; ++i) {
+            if (isPromise(Promise._cast(value[i], caller, void 0))) {
+                this._spreadSlowCase(handler, promise, value, boundTo);
+                return;
+            }
+        }
+    }
+    if (localDebugging) promise._pushContext();
+    return tryCatchApply(handler, value, boundTo);
+};
+
+Promise.prototype._callHandler =
+function Promise$_callHandler(
+    handler, receiver, promise, value, localDebugging) {
+    var x;
+    if (receiver === APPLY && !this.isRejected()) {
+        x = this._callSpread(handler, promise, value, localDebugging);
+    }
+    else {
+        if (localDebugging) promise._pushContext();
+        x = tryCatch1(handler, receiver, value);
+    }
+    if (localDebugging) promise._popContext();
+    return x;
+};
+
+Promise.prototype._settlePromiseFromHandler =
+function Promise$_settlePromiseFromHandler(
+    handler, receiver, value, promise
+) {
+    if (!isPromise(promise)) {
+        handler.call(receiver, value, promise);
+        return;
+    }
+
+    var localDebugging = debugging;
+    var x = this._callHandler(handler, receiver,
+                                promise, value, localDebugging);
+
+    if (promise._isFollowing()) return;
+
+    if (x === errorObj || x === promise || x === NEXT_FILTER) {
+        var err = x === promise
+                    ? makeSelfResolutionError()
+                    : x.e;
+        var trace = canAttach(err) ? err : new Error(err + "");
+        if (x !== NEXT_FILTER) promise._attachExtraTrace(trace);
+        promise._rejectUnchecked(err, trace);
+    }
+    else {
+        var castValue = Promise._cast(x,
+                    localDebugging ? this._settlePromiseFromHandler : void 0,
+                    promise);
+
+        if (isPromise(castValue)) {
+            if (castValue.isRejected() &&
+                !castValue._isCarryingStackTrace() &&
+                !canAttach(castValue._settledValue)) {
+                var trace = new Error(castValue._settledValue + "");
+                promise._attachExtraTrace(trace);
+                castValue._setCarriedStackTrace(trace);
+            }
+            promise._follow(castValue);
+            if (castValue._cancellable()) {
+                promise._cancellationParent = castValue;
+                promise._setCancellable();
+            }
+        }
+        else {
+            promise._fulfillUnchecked(x);
+        }
+    }
+};
+
+Promise.prototype._follow =
+function Promise$_follow(promise) {
+    this._setFollowing();
+
+    if (promise.isPending()) {
+        if (promise._cancellable() ) {
+            this._cancellationParent = promise;
+            this._setCancellable();
+        }
+        promise._proxyPromise(this);
+    }
+    else if (promise.isFulfilled()) {
+        this._fulfillUnchecked(promise._settledValue);
+    }
+    else {
+        this._rejectUnchecked(promise._settledValue,
+            promise._getCarriedStackTrace());
+    }
+
+    if (promise._isRejectionUnhandled()) promise._unsetRejectionIsUnhandled();
+
+    if (debugging &&
+        promise._traceParent == null) {
+        promise._traceParent = this;
+    }
+};
+
+Promise.prototype._tryFollow =
+function Promise$_tryFollow(value) {
+    if (this._isFollowingOrFulfilledOrRejected() ||
+        value === this) {
+        return false;
+    }
+    var maybePromise = Promise._cast(value, this._tryFollow, void 0);
+    if (!isPromise(maybePromise)) {
+        return false;
+    }
+    this._follow(maybePromise);
+    return true;
+};
+
+Promise.prototype._resetTrace = function Promise$_resetTrace(caller) {
+    if (debugging) {
+        var context = this._peekContext();
+        var isTopLevel = context === void 0;
+        this._trace = new CapturedTrace(
+            typeof caller === "function"
+            ? caller
+            : this._resetTrace,
+            isTopLevel
+       );
+    }
+};
+
+Promise.prototype._setTrace = function Promise$_setTrace(caller, parent) {
+    if (debugging) {
+        var context = this._peekContext();
+        this._traceParent = context;
+        var isTopLevel = context === void 0;
+        if (parent !== void 0 &&
+            parent._traceParent === context) {
+            this._trace = parent._trace;
+        }
+        else {
+            this._trace = new CapturedTrace(
+                typeof caller === "function"
+                ? caller
+                : this._setTrace,
+                isTopLevel
+           );
+        }
+    }
+    return this;
+};
+
+Promise.prototype._attachExtraTrace =
+function Promise$_attachExtraTrace(error) {
+    if (debugging) {
+        var promise = this;
+        var stack = error.stack;
+        stack = typeof stack === "string"
+            ? stack.split("\n") : [];
+        var headerLineCount = 1;
+
+        while(promise != null &&
+            promise._trace != null) {
+            stack = CapturedTrace.combine(
+                stack,
+                promise._trace.stack.split("\n")
+           );
+            promise = promise._traceParent;
+        }
+
+        var max = Error.stackTraceLimit + headerLineCount;
+        var len = stack.length;
+        if (len  > max) {
+            stack.length = max;
+        }
+        if (stack.length <= headerLineCount) {
+            error.stack = "(No stack trace)";
+        }
+        else {
+            error.stack = stack.join("\n");
+        }
+    }
+};
+
+Promise.prototype._cleanValues = function Promise$_cleanValues() {
+    if (this._cancellable()) {
+        this._cancellationParent = void 0;
+    }
+};
+
+Promise.prototype._fulfill = function Promise$_fulfill(value) {
+    if (this._isFollowingOrFulfilledOrRejected()) return;
+    this._fulfillUnchecked(value);
+};
+
+Promise.prototype._reject =
+function Promise$_reject(reason, carriedStackTrace) {
+    if (this._isFollowingOrFulfilledOrRejected()) return;
+    this._rejectUnchecked(reason, carriedStackTrace);
+};
+
+Promise.prototype._settlePromiseAt = function Promise$_settlePromiseAt(index) {
+    var handler = this.isFulfilled()
+        ? this._fulfillmentHandlerAt(index)
+        : this._rejectionHandlerAt(index);
+
+    var value = this._settledValue;
+    var receiver = this._receiverAt(index);
+    var promise = this._promiseAt(index);
+
+    if (typeof handler === "function") {
+        this._settlePromiseFromHandler(handler, receiver, value, promise);
+    }
+    else {
+        var done = false;
+        var isFulfilled = this.isFulfilled();
+        if (receiver !== void 0) {
+            if (receiver instanceof Promise &&
+                receiver._isProxied()) {
+                receiver._unsetProxied();
+
+                if (isFulfilled) receiver._fulfillUnchecked(value);
+                else receiver._rejectUnchecked(value,
+                    this._getCarriedStackTrace());
+                done = true;
+            }
+            else if (isPromiseArrayProxy(receiver, promise)) {
+
+                if (isFulfilled) receiver._promiseFulfilled(value, promise);
+                else receiver._promiseRejected(value, promise);
+
+                done = true;
+            }
+        }
+
+        if (!done) {
+
+            if (isFulfilled) promise._fulfill(value);
+            else promise._reject(value, this._getCarriedStackTrace());
+
+        }
+    }
+
+    if (index >= 256) {
+        this._queueGC();
+    }
+};
+
+Promise.prototype._isProxied = function Promise$_isProxied() {
+    return (this._bitField & 4194304) === 4194304;
+};
+
+Promise.prototype._setProxied = function Promise$_setProxied() {
+    this._bitField = this._bitField | 4194304;
+};
+
+Promise.prototype._unsetProxied = function Promise$_unsetProxied() {
+    this._bitField = this._bitField & (~4194304);
+};
+
+Promise.prototype._isGcQueued = function Promise$_isGcQueued() {
+    return (this._bitField & -1073741824) === -1073741824;
+};
+
+Promise.prototype._setGcQueued = function Promise$_setGcQueued() {
+    this._bitField = this._bitField | -1073741824;
+};
+
+Promise.prototype._unsetGcQueued = function Promise$_unsetGcQueued() {
+    this._bitField = this._bitField & (~-1073741824);
+};
+
+Promise.prototype._queueGC = function Promise$_queueGC() {
+    if (this._isGcQueued()) return;
+    this._setGcQueued();
+    async.invokeLater(this._gc, this, void 0);
+};
+
+Promise.prototype._gc = function Promise$gc() {
+    var len = this._length();
+    this._unsetAt(0);
+    for (var i = 0; i < len; i++) {
+        delete this[i];
+    }
+    this._setLength(0);
+    this._unsetGcQueued();
+};
+
+Promise.prototype._queueSettleAt = function Promise$_queueSettleAt(index) {
+    if (this._isRejectionUnhandled()) this._unsetRejectionIsUnhandled();
+    this._settlePromiseAt(index);
+};
+
+Promise.prototype._fulfillUnchecked =
+function Promise$_fulfillUnchecked(value) {
+    if (!this.isPending()) return;
+    if (value === this) {
+        var err = makeSelfResolutionError();
+        this._attachExtraTrace(err);
+        return this._rejectUnchecked(err, void 0);
+    }
+    this._cleanValues();
+    this._setFulfilled();
+    this._settledValue = value;
+    var len = this._length();
+
+    if (len > 0) {
+        this._fulfillPromises(len);
+    }
+};
+
+Promise.prototype._rejectUncheckedCheckError =
+function Promise$_rejectUncheckedCheckError(reason) {
+    var trace = canAttach(reason) ? reason : new Error(reason + "");
+    this._rejectUnchecked(reason, trace === reason ? void 0 : trace);
+};
+
+Promise.prototype._rejectUnchecked =
+function Promise$_rejectUnchecked(reason, trace) {
+    if (!this.isPending()) return;
+    if (reason === this) {
+        var err = makeSelfResolutionError();
+        this._attachExtraTrace(err);
+        return this._rejectUnchecked(err);
+    }
+    this._cleanValues();
+    this._setRejected();
+    this._settledValue = reason;
+
+    if (this._isFinal()) {
+        async.invokeLater(thrower, void 0, trace === void 0 ? reason : trace);
+        return;
+    }
+    var len = this._length();
+
+    if (trace !== void 0) this._setCarriedStackTrace(trace);
+
+    if (len > 0) {
+        this._rejectPromises(null);
+    }
+    else {
+        this._ensurePossibleRejectionHandled();
+    }
+};
+
+Promise.prototype._rejectPromises = function Promise$_rejectPromises() {
+    var len = this._length();
+    for (var i = 0; i < len; i+= 5) {
+        this._settlePromiseAt(i);
+    }
+    this._unsetCarriedStackTrace();
+};
+
+Promise.prototype._fulfillPromises = function Promise$_fulfillPromises(len) {
+    len = this._length();
+    for (var i = 0; i < len; i+= 5) {
+        this._settlePromiseAt(i);
+    }
+};
+
+Promise.prototype._ensurePossibleRejectionHandled =
+function Promise$_ensurePossibleRejectionHandled() {
+    this._setRejectionIsUnhandled();
+    if (CapturedTrace.possiblyUnhandledRejection !== void 0) {
+        async.invokeLater(this._notifyUnhandledRejection, this, void 0);
+    }
+};
+
+Promise.prototype._notifyUnhandledRejection =
+function Promise$_notifyUnhandledRejection() {
+    if (this._isRejectionUnhandled()) {
+        var reason = this._settledValue;
+        var trace = this._getCarriedStackTrace();
+
+        this._unsetRejectionIsUnhandled();
+
+        if (trace !== void 0) {
+            this._unsetCarriedStackTrace();
+            reason = trace;
+        }
+        if (typeof CapturedTrace.possiblyUnhandledRejection === "function") {
+            CapturedTrace.possiblyUnhandledRejection(reason, this);
+        }
+    }
+};
+
+var contextStack = [];
+Promise.prototype._peekContext = function Promise$_peekContext() {
+    var lastIndex = contextStack.length - 1;
+    if (lastIndex >= 0) {
+        return contextStack[lastIndex];
+    }
+    return void 0;
+
+};
+
+Promise.prototype._pushContext = function Promise$_pushContext() {
+    if (!debugging) return;
+    contextStack.push(this);
+};
+
+Promise.prototype._popContext = function Promise$_popContext() {
+    if (!debugging) return;
+    contextStack.pop();
+};
+
+function Promise$_CreatePromiseArray(
+    promises, PromiseArrayConstructor, caller, boundTo) {
+
+    var list = null;
+    if (isArray(promises)) {
+        list = promises;
+    }
+    else {
+        list = Promise._cast(promises, caller, void 0);
+        if (list !== promises) {
+            list._setBoundTo(boundTo);
+        }
+        else if (!isPromise(list)) {
+            list = null;
+        }
+    }
+    if (list !== null) {
+        return new PromiseArrayConstructor(
+            list,
+            typeof caller === "function"
+                ? caller
+                : Promise$_CreatePromiseArray,
+            boundTo
+       );
+    }
+    return {
+        promise: function() {return apiRejection("expecting an array, a promise or a thenable");}
+    };
+}
+
+var old = global.Promise;
+
+Promise.noConflict = function() {
+    if (global.Promise === Promise) {
+        global.Promise = old;
+    }
+    return Promise;
+};
+
+if (!CapturedTrace.isSupported()) {
+    Promise.longStackTraces = function(){};
+    debugging = false;
+}
+
+Promise._makeSelfResolutionError = makeSelfResolutionError;
+require("./finally.js")(Promise, NEXT_FILTER);
+require("./direct_resolve.js")(Promise);
+require("./thenables.js")(Promise, INTERNAL);
+Promise.RangeError = RangeError;
+Promise.CancellationError = CancellationError;
+Promise.TimeoutError = TimeoutError;
+Promise.TypeError = TypeError;
+Promise.RejectionError = RejectionError;
+
+util.toFastProperties(Promise);
+util.toFastProperties(Promise.prototype);
+require('./timers.js')(Promise,INTERNAL);
+require('./synchronous_inspection.js')(Promise);
+require('./any.js')(Promise,Promise$_CreatePromiseArray,PromiseArray);
+require('./race.js')(Promise,INTERNAL);
+require('./call_get.js')(Promise);
+require('./filter.js')(Promise,Promise$_CreatePromiseArray,PromiseArray,apiRejection);
+require('./generators.js')(Promise,apiRejection,INTERNAL);
+require('./map.js')(Promise,Promise$_CreatePromiseArray,PromiseArray,apiRejection);
+require('./nodeify.js')(Promise);
+require('./promisify.js')(Promise,INTERNAL);
+require('./props.js')(Promise,PromiseArray);
+require('./reduce.js')(Promise,Promise$_CreatePromiseArray,PromiseArray,apiRejection,INTERNAL);
+require('./settle.js')(Promise,Promise$_CreatePromiseArray,PromiseArray);
+require('./some.js')(Promise,Promise$_CreatePromiseArray,PromiseArray,apiRejection);
+require('./progress.js')(Promise,isPromiseArrayProxy);
+require('./cancel.js')(Promise,INTERNAL);
+
+Promise.prototype = Promise.prototype;
+return Promise;
+
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/promise_array.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/promise_array.js
new file mode 100644
index 0000000..6d43606
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/promise_array.js
@@ -0,0 +1,233 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, INTERNAL) {
+var canAttach = require("./errors.js").canAttach;
+var util = require("./util.js");
+var async = require("./async.js");
+var hasOwn = {}.hasOwnProperty;
+var isArray = util.isArray;
+
+function toResolutionValue(val) {
+    switch(val) {
+    case -1: return void 0;
+    case -2: return [];
+    case -3: return {};
+    }
+}
+
+function PromiseArray(values, caller, boundTo) {
+    var promise = this._promise = new Promise(INTERNAL);
+    var parent = void 0;
+    if (Promise.is(values)) {
+        parent = values;
+        if (values._cancellable()) {
+            promise._setCancellable();
+            promise._cancellationParent = values;
+        }
+        if (values._isBound()) {
+            promise._setBoundTo(boundTo);
+        }
+    }
+    promise._setTrace(caller, parent);
+    this._values = values;
+    this._length = 0;
+    this._totalResolved = 0;
+    this._init(void 0, -2);
+}
+PromiseArray.PropertiesPromiseArray = function() {};
+
+PromiseArray.prototype.length = function PromiseArray$length() {
+    return this._length;
+};
+
+PromiseArray.prototype.promise = function PromiseArray$promise() {
+    return this._promise;
+};
+
+PromiseArray.prototype._init =
+function PromiseArray$_init(_, resolveValueIfEmpty) {
+    var values = this._values;
+    if (Promise.is(values)) {
+        if (values.isFulfilled()) {
+            values = values._settledValue;
+            if (!isArray(values)) {
+                var err = new Promise.TypeError("expecting an array, a promise or a thenable");
+                this.__hardReject__(err);
+                return;
+            }
+            this._values = values;
+        }
+        else if (values.isPending()) {
+            values._then(
+                this._init,
+                this._reject,
+                void 0,
+                this,
+                resolveValueIfEmpty,
+                this.constructor
+           );
+            return;
+        }
+        else {
+            this._reject(values._settledValue);
+            return;
+        }
+    }
+
+    if (values.length === 0) {
+        this._resolve(toResolutionValue(resolveValueIfEmpty));
+        return;
+    }
+    var len = values.length;
+    var newLen = len;
+    var newValues;
+    if (this instanceof PromiseArray.PropertiesPromiseArray) {
+        newValues = this._values;
+    }
+    else {
+        newValues = new Array(len);
+    }
+    var isDirectScanNeeded = false;
+    for (var i = 0; i < len; ++i) {
+        var promise = values[i];
+        if (promise === void 0 && !hasOwn.call(values, i)) {
+            newLen--;
+            continue;
+        }
+        var maybePromise = Promise._cast(promise, void 0, void 0);
+        if (maybePromise instanceof Promise) {
+            if (maybePromise.isPending()) {
+                maybePromise._proxyPromiseArray(this, i);
+            }
+            else {
+                maybePromise._unsetRejectionIsUnhandled();
+                isDirectScanNeeded = true;
+            }
+        }
+        else {
+            isDirectScanNeeded = true;
+        }
+        newValues[i] = maybePromise;
+    }
+    if (newLen === 0) {
+        if (resolveValueIfEmpty === -2) {
+            this._resolve(newValues);
+        }
+        else {
+            this._resolve(toResolutionValue(resolveValueIfEmpty));
+        }
+        return;
+    }
+    this._values = newValues;
+    this._length = newLen;
+    if (isDirectScanNeeded) {
+        var scanMethod = newLen === len
+            ? this._scanDirectValues
+            : this._scanDirectValuesHoled;
+        scanMethod.call(this, len);
+    }
+};
+
+PromiseArray.prototype._settlePromiseAt =
+function PromiseArray$_settlePromiseAt(index) {
+    var value = this._values[index];
+    if (!Promise.is(value)) {
+        this._promiseFulfilled(value, index);
+    }
+    else if (value.isFulfilled()) {
+        this._promiseFulfilled(value._settledValue, index);
+    }
+    else if (value.isRejected()) {
+        this._promiseRejected(value._settledValue, index);
+    }
+};
+
+PromiseArray.prototype._scanDirectValuesHoled =
+function PromiseArray$_scanDirectValuesHoled(len) {
+    for (var i = 0; i < len; ++i) {
+        if (this._isResolved()) {
+            break;
+        }
+        if (hasOwn.call(this._values, i)) {
+            this._settlePromiseAt(i);
+        }
+    }
+};
+
+PromiseArray.prototype._scanDirectValues =
+function PromiseArray$_scanDirectValues(len) {
+    for (var i = 0; i < len; ++i) {
+        if (this._isResolved()) {
+            break;
+        }
+        this._settlePromiseAt(i);
+    }
+};
+
+PromiseArray.prototype._isResolved = function PromiseArray$_isResolved() {
+    return this._values === null;
+};
+
+PromiseArray.prototype._resolve = function PromiseArray$_resolve(value) {
+    this._values = null;
+    this._promise._fulfill(value);
+};
+
+PromiseArray.prototype.__hardReject__ =
+PromiseArray.prototype._reject = function PromiseArray$_reject(reason) {
+    this._values = null;
+    var trace = canAttach(reason) ? reason : new Error(reason + "");
+    this._promise._attachExtraTrace(trace);
+    this._promise._reject(reason, trace);
+};
+
+PromiseArray.prototype._promiseProgressed =
+function PromiseArray$_promiseProgressed(progressValue, index) {
+    if (this._isResolved()) return;
+    this._promise._progress({
+        index: index,
+        value: progressValue
+    });
+};
+
+
+PromiseArray.prototype._promiseFulfilled =
+function PromiseArray$_promiseFulfilled(value, index) {
+    if (this._isResolved()) return;
+    this._values[index] = value;
+    var totalResolved = ++this._totalResolved;
+    if (totalResolved >= this._length) {
+        this._resolve(this._values);
+    }
+};
+
+PromiseArray.prototype._promiseRejected =
+function PromiseArray$_promiseRejected(reason, index) {
+    if (this._isResolved()) return;
+    this._totalResolved++;
+    this._reject(reason);
+};
+
+return PromiseArray;
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/promise_inspection.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/promise_inspection.js
new file mode 100644
index 0000000..0aa233b
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/promise_inspection.js
@@ -0,0 +1,66 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var TypeError = require("./errors.js").TypeError;
+
+function PromiseInspection(promise) {
+    if (promise !== void 0) {
+        this._bitField = promise._bitField;
+        this._settledValue = promise.isResolved()
+            ? promise._settledValue
+            : void 0;
+    }
+    else {
+        this._bitField = 0;
+        this._settledValue = void 0;
+    }
+}
+PromiseInspection.prototype.isFulfilled =
+function PromiseInspection$isFulfilled() {
+    return (this._bitField & 268435456) > 0;
+};
+
+PromiseInspection.prototype.isRejected =
+function PromiseInspection$isRejected() {
+    return (this._bitField & 134217728) > 0;
+};
+
+PromiseInspection.prototype.isPending = function PromiseInspection$isPending() {
+    return (this._bitField & 402653184) === 0;
+};
+
+PromiseInspection.prototype.value = function PromiseInspection$value() {
+    if (!this.isFulfilled()) {
+        throw new TypeError("cannot get fulfillment value of a non-fulfilled promise");
+    }
+    return this._settledValue;
+};
+
+PromiseInspection.prototype.error = function PromiseInspection$error() {
+    if (!this.isRejected()) {
+        throw new TypeError("cannot get rejection reason of a non-rejected promise");
+    }
+    return this._settledValue;
+};
+
+module.exports = PromiseInspection;
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/promise_resolver.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/promise_resolver.js
new file mode 100644
index 0000000..ea14069
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/promise_resolver.js
@@ -0,0 +1,152 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var util = require("./util.js");
+var maybeWrapAsError = util.maybeWrapAsError;
+var errors = require("./errors.js");
+var TimeoutError = errors.TimeoutError;
+var RejectionError = errors.RejectionError;
+var async = require("./async.js");
+var haveGetters = util.haveGetters;
+var es5 = require("./es5.js");
+
+function isUntypedError(obj) {
+    return obj instanceof Error &&
+        es5.getPrototypeOf(obj) === Error.prototype;
+}
+
+function wrapAsRejectionError(obj) {
+    var ret;
+    if (isUntypedError(obj)) {
+        ret = new RejectionError(obj);
+    }
+    else {
+        ret = obj;
+    }
+    errors.markAsOriginatingFromRejection(ret);
+    return ret;
+}
+
+function nodebackForPromise(promise) {
+    function PromiseResolver$_callback(err, value) {
+        if (promise === null) return;
+
+        if (err) {
+            var wrapped = wrapAsRejectionError(maybeWrapAsError(err));
+            promise._attachExtraTrace(wrapped);
+            promise._reject(wrapped);
+        }
+        else {
+            if (arguments.length > 2) {
+                var $_len = arguments.length;var args = new Array($_len - 1); for(var $_i = 1; $_i < $_len; ++$_i) {args[$_i - 1] = arguments[$_i];}
+                promise._fulfill(args);
+            }
+            else {
+                promise._fulfill(value);
+            }
+        }
+
+        promise = null;
+    }
+    return PromiseResolver$_callback;
+}
+
+
+var PromiseResolver;
+if (!haveGetters) {
+    PromiseResolver = function PromiseResolver(promise) {
+        this.promise = promise;
+        this.asCallback = nodebackForPromise(promise);
+        this.callback = this.asCallback;
+    };
+}
+else {
+    PromiseResolver = function PromiseResolver(promise) {
+        this.promise = promise;
+    };
+}
+if (haveGetters) {
+    var prop = {
+        get: function() {
+            return nodebackForPromise(this.promise);
+        }
+    };
+    es5.defineProperty(PromiseResolver.prototype, "asCallback", prop);
+    es5.defineProperty(PromiseResolver.prototype, "callback", prop);
+}
+
+PromiseResolver._nodebackForPromise = nodebackForPromise;
+
+PromiseResolver.prototype.toString = function PromiseResolver$toString() {
+    return "[object PromiseResolver]";
+};
+
+PromiseResolver.prototype.resolve =
+PromiseResolver.prototype.fulfill = function PromiseResolver$resolve(value) {
+    var promise = this.promise;
+    if (promise._tryFollow(value)) {
+        return;
+    }
+    promise._fulfill(value);
+};
+
+PromiseResolver.prototype.reject = function PromiseResolver$reject(reason) {
+    var promise = this.promise;
+    errors.markAsOriginatingFromRejection(reason);
+    var trace = errors.canAttach(reason) ? reason : new Error(reason + "");
+    promise._attachExtraTrace(trace);
+    promise._reject(reason);
+    if (trace !== reason) {
+        this._setCarriedStackTrace(trace);
+    }
+};
+
+PromiseResolver.prototype.progress =
+function PromiseResolver$progress(value) {
+    this.promise._progress(value);
+};
+
+PromiseResolver.prototype.cancel = function PromiseResolver$cancel() {
+    this.promise.cancel((void 0));
+};
+
+PromiseResolver.prototype.timeout = function PromiseResolver$timeout() {
+    this.reject(new TimeoutError("timeout"));
+};
+
+PromiseResolver.prototype.isResolved = function PromiseResolver$isResolved() {
+    return this.promise.isResolved();
+};
+
+PromiseResolver.prototype.toJSON = function PromiseResolver$toJSON() {
+    return this.promise.toJSON();
+};
+
+PromiseResolver.prototype._setCarriedStackTrace =
+function PromiseResolver$_setCarriedStackTrace(trace) {
+    if (this.promise.isRejected()) {
+        this.promise._setCarriedStackTrace(trace);
+    }
+};
+
+module.exports = PromiseResolver;
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/promise_spawn.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/promise_spawn.js
new file mode 100644
index 0000000..2d6525f
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/promise_spawn.js
@@ -0,0 +1,131 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, INTERNAL) {
+var errors = require("./errors.js");
+var TypeError = errors.TypeError;
+var util = require("./util.js");
+var isArray = util.isArray;
+var errorObj = util.errorObj;
+var tryCatch1 = util.tryCatch1;
+var yieldHandlers = [];
+
+function promiseFromYieldHandler(value) {
+    var _yieldHandlers = yieldHandlers;
+    var _errorObj = errorObj;
+    var _Promise = Promise;
+    var len = _yieldHandlers.length;
+    for (var i = 0; i < len; ++i) {
+        var result = tryCatch1(_yieldHandlers[i], void 0, value);
+        if (result === _errorObj) {
+            return _Promise.reject(_errorObj.e);
+        }
+        var maybePromise = _Promise._cast(result,
+            promiseFromYieldHandler, void 0);
+        if (maybePromise instanceof _Promise) return maybePromise;
+    }
+    return null;
+}
+
+function PromiseSpawn(generatorFunction, receiver, caller) {
+    var promise = this._promise = new Promise(INTERNAL);
+    promise._setTrace(caller, void 0);
+    this._generatorFunction = generatorFunction;
+    this._receiver = receiver;
+    this._generator = void 0;
+}
+
+PromiseSpawn.prototype.promise = function PromiseSpawn$promise() {
+    return this._promise;
+};
+
+PromiseSpawn.prototype._run = function PromiseSpawn$_run() {
+    this._generator = this._generatorFunction.call(this._receiver);
+    this._receiver =
+        this._generatorFunction = void 0;
+    this._next(void 0);
+};
+
+PromiseSpawn.prototype._continue = function PromiseSpawn$_continue(result) {
+    if (result === errorObj) {
+        this._generator = void 0;
+        var trace = errors.canAttach(result.e)
+            ? result.e : new Error(result.e + "");
+        this._promise._attachExtraTrace(trace);
+        this._promise._reject(result.e, trace);
+        return;
+    }
+
+    var value = result.value;
+    if (result.done === true) {
+        this._generator = void 0;
+        if (!this._promise._tryFollow(value)) {
+            this._promise._fulfill(value);
+        }
+    }
+    else {
+        var maybePromise = Promise._cast(value, PromiseSpawn$_continue, void 0);
+        if (!(maybePromise instanceof Promise)) {
+            if (isArray(maybePromise)) {
+                maybePromise = Promise.all(maybePromise);
+            }
+            else {
+                maybePromise = promiseFromYieldHandler(maybePromise);
+            }
+            if (maybePromise === null) {
+                this._throw(new TypeError("A value was yielded that could not be treated as a promise"));
+                return;
+            }
+        }
+        maybePromise._then(
+            this._next,
+            this._throw,
+            void 0,
+            this,
+            null,
+            void 0
+       );
+    }
+};
+
+PromiseSpawn.prototype._throw = function PromiseSpawn$_throw(reason) {
+    if (errors.canAttach(reason))
+        this._promise._attachExtraTrace(reason);
+    this._continue(
+        tryCatch1(this._generator["throw"], this._generator, reason)
+   );
+};
+
+PromiseSpawn.prototype._next = function PromiseSpawn$_next(value) {
+    this._continue(
+        tryCatch1(this._generator.next, this._generator, value)
+   );
+};
+
+PromiseSpawn.addYieldHandler = function PromiseSpawn$AddYieldHandler(fn) {
+    if (typeof fn !== "function") throw new TypeError("fn must be a function");
+    yieldHandlers.push(fn);
+};
+
+return PromiseSpawn;
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/promisify.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/promisify.js
new file mode 100644
index 0000000..a550fd0
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/promisify.js
@@ -0,0 +1,278 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, INTERNAL) {
+var THIS = {};
+var util = require("./util.js");
+var es5 = require("./es5.js");
+var nodebackForPromise = require("./promise_resolver.js")
+    ._nodebackForPromise;
+var withAppended = util.withAppended;
+var maybeWrapAsError = util.maybeWrapAsError;
+var canEvaluate = util.canEvaluate;
+var notEnumerableProp = util.notEnumerableProp;
+var deprecated = util.deprecated;
+var roriginal = new RegExp("__beforePromisified__" + "$");
+var hasProp = {}.hasOwnProperty;
+function isPromisified(fn) {
+    return fn.__isPromisified__ === true;
+}
+var inheritedMethods = (function() {
+    if (es5.isES5) {
+        var create = Object.create;
+        var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
+        return function(cur) {
+            var original = cur;
+            var ret = [];
+            var visitedKeys = create(null);
+            while (cur !== null) {
+                var keys = es5.keys(cur);
+                for (var i = 0, len = keys.length; i < len; ++i) {
+                    var key = keys[i];
+                    if (visitedKeys[key] ||
+                        roriginal.test(key) ||
+                        hasProp.call(original, key + "__beforePromisified__")
+                   ) {
+                        continue;
+                    }
+                    visitedKeys[key] = true;
+                    var desc = getOwnPropertyDescriptor(cur, key);
+                    if (desc != null &&
+                        typeof desc.value === "function" &&
+                        !isPromisified(desc.value)) {
+                        ret.push(key, desc.value);
+                    }
+                }
+                cur = es5.getPrototypeOf(cur);
+            }
+            return ret;
+        };
+    }
+    else {
+        return function(obj) {
+            var ret = [];
+            /*jshint forin:false */
+            for (var key in obj) {
+                if (roriginal.test(key) ||
+                    hasProp.call(obj, key + "__beforePromisified__")) {
+                    continue;
+                }
+                var fn = obj[key];
+                if (typeof fn === "function" &&
+                    !isPromisified(fn)) {
+                    ret.push(key, fn);
+                }
+            }
+            return ret;
+        };
+    }
+})();
+
+function switchCaseArgumentOrder(likelyArgumentCount) {
+    var ret = [likelyArgumentCount];
+    var min = Math.max(0, likelyArgumentCount - 1 - 5);
+    for(var i = likelyArgumentCount - 1; i >= min; --i) {
+        if (i === likelyArgumentCount) continue;
+        ret.push(i);
+    }
+    for(var i = likelyArgumentCount + 1; i <= 5; ++i) {
+        ret.push(i);
+    }
+    return ret;
+}
+
+function parameterDeclaration(parameterCount) {
+    var ret = new Array(parameterCount);
+    for(var i = 0; i < ret.length; ++i) {
+        ret[i] = "_arg" + i;
+    }
+    return ret.join(", ");
+}
+
+function parameterCount(fn) {
+    if (typeof fn.length === "number") {
+        return Math.max(Math.min(fn.length, 1023 + 1), 0);
+    }
+    return 0;
+}
+
+function propertyAccess(id) {
+    var rident = /^[a-z$_][a-z$_0-9]*$/i;
+
+    if (rident.test(id)) {
+        return "." + id;
+    }
+    else return "['" + id.replace(/(['\\])/g, "\\$1") + "']";
+}
+
+function makeNodePromisifiedEval(callback, receiver, originalName, fn) {
+    var newParameterCount = Math.max(0, parameterCount(fn) - 1);
+    var argumentOrder = switchCaseArgumentOrder(newParameterCount);
+
+    var callbackName = (typeof originalName === "string" ?
+        originalName + "Async" :
+        "promisified");
+
+    function generateCallForArgumentCount(count) {
+        var args = new Array(count);
+        for (var i = 0, len = args.length; i < len; ++i) {
+            args[i] = "arguments[" + i + "]";
+        }
+        var comma = count > 0 ? "," : "";
+
+        if (typeof callback === "string" &&
+            receiver === THIS) {
+            return "this" + propertyAccess(callback) + "("+args.join(",") +
+                comma +" fn);"+
+                "break;";
+        }
+        return (receiver === void 0
+            ? "callback("+args.join(",")+ comma +" fn);"
+            : "callback.call("+(receiver === THIS
+                ? "this"
+                : "receiver")+", "+args.join(",") + comma + " fn);") +
+        "break;";
+    }
+
+    function generateArgumentSwitchCase() {
+        var ret = "";
+        for(var i = 0; i < argumentOrder.length; ++i) {
+            ret += "case " + argumentOrder[i] +":" +
+                generateCallForArgumentCount(argumentOrder[i]);
+        }
+        ret += "default: var args = new Array(len + 1);" +
+            "var i = 0;" +
+            "for (var i = 0; i < len; ++i) { " +
+            "   args[i] = arguments[i];" +
+            "}" +
+            "args[i] = fn;" +
+
+            (typeof callback === "string"
+            ? "this" + propertyAccess(callback) + ".apply("
+            : "callback.apply(") +
+
+            (receiver === THIS ? "this" : "receiver") +
+            ", args); break;";
+        return ret;
+    }
+
+    return new Function("Promise", "callback", "receiver",
+            "withAppended", "maybeWrapAsError", "nodebackForPromise",
+            "INTERNAL",
+        "var ret = function " + callbackName +
+        "(" + parameterDeclaration(newParameterCount) + ") {\"use strict\";" +
+        "var len = arguments.length;" +
+        "var promise = new Promise(INTERNAL);"+
+        "promise._setTrace(" + callbackName + ", void 0);" +
+        "var fn = nodebackForPromise(promise);"+
+        "try {" +
+        "switch(len) {" +
+        generateArgumentSwitchCase() +
+        "}" +
+        "}" +
+        "catch(e){ " +
+        "var wrapped = maybeWrapAsError(e);" +
+        "promise._attachExtraTrace(wrapped);" +
+        "promise._reject(wrapped);" +
+        "}" +
+        "return promise;" +
+        "" +
+        "}; ret.__isPromisified__ = true; return ret;"
+   )(Promise, callback, receiver, withAppended,
+        maybeWrapAsError, nodebackForPromise, INTERNAL);
+}
+
+function makeNodePromisifiedClosure(callback, receiver) {
+    function promisified() {
+        var _receiver = receiver;
+        if (receiver === THIS) _receiver = this;
+        if (typeof callback === "string") {
+            callback = _receiver[callback];
+        }
+        var promise = new Promise(INTERNAL);
+        promise._setTrace(promisified, void 0);
+        var fn = nodebackForPromise(promise);
+        try {
+            callback.apply(_receiver, withAppended(arguments, fn));
+        }
+        catch(e) {
+            var wrapped = maybeWrapAsError(e);
+            promise._attachExtraTrace(wrapped);
+            promise._reject(wrapped);
+        }
+        return promise;
+    }
+    promisified.__isPromisified__ = true;
+    return promisified;
+}
+
+var makeNodePromisified = canEvaluate
+    ? makeNodePromisifiedEval
+    : makeNodePromisifiedClosure;
+
+function _promisify(callback, receiver, isAll) {
+    if (isAll) {
+        var methods = inheritedMethods(callback);
+        for (var i = 0, len = methods.length; i < len; i+= 2) {
+            var key = methods[i];
+            var fn = methods[i+1];
+            var originalKey = key + "__beforePromisified__";
+            var promisifiedKey = key + "Async";
+            notEnumerableProp(callback, originalKey, fn);
+            callback[promisifiedKey] =
+                makeNodePromisified(originalKey, THIS,
+                    key, fn);
+        }
+        util.toFastProperties(callback);
+        return callback;
+    }
+    else {
+        return makeNodePromisified(callback, receiver, void 0, callback);
+    }
+}
+
+Promise.promisify = function Promise$Promisify(fn, receiver) {
+    if (typeof fn === "object" && fn !== null) {
+        deprecated("Promise.promisify for promisifying entire objects is deprecated. Use Promise.promisifyAll instead.");
+        return _promisify(fn, receiver, true);
+    }
+    if (typeof fn !== "function") {
+        throw new TypeError("fn must be a function");
+    }
+    if (isPromisified(fn)) {
+        return fn;
+    }
+    return _promisify(
+        fn,
+        arguments.length < 2 ? THIS : receiver,
+        false);
+};
+
+Promise.promisifyAll = function Promise$PromisifyAll(target) {
+    if (typeof target !== "function" && typeof target !== "object") {
+        throw new TypeError("the target of promisifyAll must be an object or a function");
+    }
+    return _promisify(target, void 0, true);
+};
+};
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/properties_promise_array.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/properties_promise_array.js
new file mode 100644
index 0000000..85f5990
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/properties_promise_array.js
@@ -0,0 +1,77 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, PromiseArray) {
+var util = require("./util.js");
+var inherits = util.inherits;
+var es5 = require("./es5.js");
+
+function PropertiesPromiseArray(obj, caller, boundTo) {
+    var keys = es5.keys(obj);
+    var values = new Array(keys.length);
+    for (var i = 0, len = values.length; i < len; ++i) {
+        values[i] = obj[keys[i]];
+    }
+    this.constructor$(values, caller, boundTo);
+    if (!this._isResolved()) {
+        for (var i = 0, len = keys.length; i < len; ++i) {
+            values.push(keys[i]);
+        }
+    }
+}
+inherits(PropertiesPromiseArray, PromiseArray);
+
+PropertiesPromiseArray.prototype._init =
+function PropertiesPromiseArray$_init() {
+    this._init$(void 0, -3) ;
+};
+
+PropertiesPromiseArray.prototype._promiseFulfilled =
+function PropertiesPromiseArray$_promiseFulfilled(value, index) {
+    if (this._isResolved()) return;
+    this._values[index] = value;
+    var totalResolved = ++this._totalResolved;
+    if (totalResolved >= this._length) {
+        var val = {};
+        var keyOffset = this.length();
+        for (var i = 0, len = this.length(); i < len; ++i) {
+            val[this._values[i + keyOffset]] = this._values[i];
+        }
+        this._resolve(val);
+    }
+};
+
+PropertiesPromiseArray.prototype._promiseProgressed =
+function PropertiesPromiseArray$_promiseProgressed(value, index) {
+    if (this._isResolved()) return;
+
+    this._promise._progress({
+        key: this._values[index + this.length()],
+        value: value
+    });
+};
+
+PromiseArray.PropertiesPromiseArray = PropertiesPromiseArray;
+
+return PropertiesPromiseArray;
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/props.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/props.js
new file mode 100644
index 0000000..3cdba43
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/props.js
@@ -0,0 +1,65 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, PromiseArray) {
+    var PropertiesPromiseArray = require("./properties_promise_array.js")(
+        Promise, PromiseArray);
+    var util = require("./util.js");
+    var apiRejection = require("./errors_api_rejection")(Promise);
+    var isObject = util.isObject;
+
+    function Promise$_Props(promises, useBound, caller) {
+        var ret;
+        var castValue = Promise._cast(promises, caller, void 0);
+
+        if (!isObject(castValue)) {
+            return apiRejection("cannot await properties of a non-object");
+        }
+        else if (Promise.is(castValue)) {
+            ret = castValue._then(Promise.props, void 0, void 0,
+                            void 0, void 0, caller);
+        }
+        else {
+            ret = new PropertiesPromiseArray(
+                castValue,
+                caller,
+                useBound === true && castValue._isBound()
+                            ? castValue._boundTo
+                            : void 0
+           ).promise();
+            useBound = false;
+        }
+        if (useBound === true && castValue._isBound()) {
+            ret._setBoundTo(castValue._boundTo);
+        }
+        return ret;
+    }
+
+    Promise.prototype.props = function Promise$props() {
+        return Promise$_Props(this, true, this.props);
+    };
+
+    Promise.props = function Promise$Props(promises) {
+        return Promise$_Props(promises, false, Promise.props);
+    };
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/queue.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/queue.js
new file mode 100644
index 0000000..bbd3f1b
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/queue.js
@@ -0,0 +1,135 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+function arrayCopy(src, srcIndex, dst, dstIndex, len) {
+    for (var j = 0; j < len; ++j) {
+        dst[j + dstIndex] = src[j + srcIndex];
+    }
+}
+
+function pow2AtLeast(n) {
+    n = n >>> 0;
+    n = n - 1;
+    n = n | (n >> 1);
+    n = n | (n >> 2);
+    n = n | (n >> 4);
+    n = n | (n >> 8);
+    n = n | (n >> 16);
+    return n + 1;
+}
+
+function getCapacity(capacity) {
+    if (typeof capacity !== "number") return 16;
+    return pow2AtLeast(
+        Math.min(
+            Math.max(16, capacity), 1073741824)
+   );
+}
+
+function Queue(capacity) {
+    this._capacity = getCapacity(capacity);
+    this._length = 0;
+    this._front = 0;
+    this._makeCapacity();
+}
+
+Queue.prototype._willBeOverCapacity =
+function Queue$_willBeOverCapacity(size) {
+    return this._capacity < size;
+};
+
+Queue.prototype._pushOne = function Queue$_pushOne(arg) {
+    var length = this.length();
+    this._checkCapacity(length + 1);
+    var i = (this._front + length) & (this._capacity - 1);
+    this[i] = arg;
+    this._length = length + 1;
+};
+
+Queue.prototype.push = function Queue$push(fn, receiver, arg) {
+    var length = this.length() + 3;
+    if (this._willBeOverCapacity(length)) {
+        this._pushOne(fn);
+        this._pushOne(receiver);
+        this._pushOne(arg);
+        return;
+    }
+    var j = this._front + length - 3;
+    this._checkCapacity(length);
+    var wrapMask = this._capacity - 1;
+    this[(j + 0) & wrapMask] = fn;
+    this[(j + 1) & wrapMask] = receiver;
+    this[(j + 2) & wrapMask] = arg;
+    this._length = length;
+};
+
+Queue.prototype.shift = function Queue$shift() {
+    var front = this._front,
+        ret = this[front];
+
+    this[front] = void 0;
+    this._front = (front + 1) & (this._capacity - 1);
+    this._length--;
+    return ret;
+};
+
+Queue.prototype.length = function Queue$length() {
+    return this._length;
+};
+
+Queue.prototype._makeCapacity = function Queue$_makeCapacity() {
+    var len = this._capacity;
+    for (var i = 0; i < len; ++i) {
+        this[i] = void 0;
+    }
+};
+
+Queue.prototype._checkCapacity = function Queue$_checkCapacity(size) {
+    if (this._capacity < size) {
+        this._resizeTo(this._capacity << 3);
+    }
+};
+
+Queue.prototype._resizeTo = function Queue$_resizeTo(capacity) {
+    var oldFront = this._front;
+    var oldCapacity = this._capacity;
+    var oldQueue = new Array(oldCapacity);
+    var length = this.length();
+
+    arrayCopy(this, 0, oldQueue, 0, oldCapacity);
+    this._capacity = capacity;
+    this._makeCapacity();
+    this._front = 0;
+    if (oldFront + length <= oldCapacity) {
+        arrayCopy(oldQueue, oldFront, this, 0, length);
+    }
+    else {        var lengthBeforeWrapping =
+            length - ((oldFront + length) & (oldCapacity - 1));
+
+        arrayCopy(oldQueue, oldFront, this, 0, lengthBeforeWrapping);
+        arrayCopy(oldQueue, 0, this, lengthBeforeWrapping,
+                    length - lengthBeforeWrapping);
+    }
+};
+
+module.exports = Queue;
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/race.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/race.js
new file mode 100644
index 0000000..82b8ce1
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/race.js
@@ -0,0 +1,85 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, INTERNAL) {
+    var apiRejection = require("./errors_api_rejection.js")(Promise);
+    var isArray = require("./util.js").isArray;
+
+    var raceLater = function Promise$_raceLater(promise) {
+        return promise.then(function Promise$_lateRacer(array) {
+            return Promise$_Race(array, Promise$_lateRacer, promise);
+        });
+    };
+
+    var hasOwn = {}.hasOwnProperty;
+    function Promise$_Race(promises, caller, parent) {
+        var maybePromise = Promise._cast(promises, caller, void 0);
+
+        if (Promise.is(maybePromise)) {
+            return raceLater(maybePromise);
+        }
+        else if (!isArray(promises)) {
+            return apiRejection("expecting an array, a promise or a thenable");
+        }
+
+        var ret = new Promise(INTERNAL);
+        ret._setTrace(caller, parent);
+        if (parent !== void 0) {
+            if (parent._isBound()) {
+                ret._setBoundTo(parent._boundTo);
+            }
+            if (parent._cancellable()) {
+                ret._setCancellable();
+                ret._cancellationParent = parent;
+            }
+        }
+        var fulfill = ret._fulfill;
+        var reject = ret._reject;
+        for (var i = 0, len = promises.length; i < len; ++i) {
+            var val = promises[i];
+
+            if (val === void 0 && !(hasOwn.call(promises, i))) {
+                continue;
+            }
+
+            Promise.cast(val)._then(
+                fulfill,
+                reject,
+                void 0,
+                ret,
+                null,
+                caller
+           );
+        }
+        return ret;
+    }
+
+    Promise.race = function Promise$Race(promises) {
+        return Promise$_Race(promises, Promise.race, void 0);
+    };
+
+    Promise.prototype.race = function Promise$race() {
+        return Promise$_Race(this, this.race, void 0);
+    };
+
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/reduce.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/reduce.js
new file mode 100644
index 0000000..e9ef95b
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/reduce.js
@@ -0,0 +1,173 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(
+    Promise, Promise$_CreatePromiseArray,
+    PromiseArray, apiRejection, INTERNAL) {
+
+    function Reduction(callback, index, accum, items, receiver) {
+        this.promise = new Promise(INTERNAL);
+        this.index = index;
+        this.length = items.length;
+        this.items = items;
+        this.callback = callback;
+        this.receiver = receiver;
+        this.accum = accum;
+    }
+
+    Reduction.prototype.reject = function Reduction$reject(e) {
+        this.promise._reject(e);
+    };
+
+    Reduction.prototype.fulfill = function Reduction$fulfill(value, index) {
+        this.accum = value;
+        this.index = index + 1;
+        this.iterate();
+    };
+
+    Reduction.prototype.iterate = function Reduction$iterate() {
+        var i = this.index;
+        var len = this.length;
+        var items = this.items;
+        var result = this.accum;
+        var receiver = this.receiver;
+        var callback = this.callback;
+        var iterate = this.iterate;
+
+        for(; i < len; ++i) {
+            result = Promise._cast(
+                callback.call(
+                    receiver,
+                    result,
+                    items[i],
+                    i,
+                    len
+                ),
+                iterate,
+                void 0
+            );
+
+            if (result instanceof Promise) {
+                result._then(
+                    this.fulfill, this.reject, void 0, this, i, iterate);
+                return;
+            }
+        }
+        this.promise._fulfill(result);
+    };
+
+    function Promise$_reducer(fulfilleds, initialValue) {
+        var fn = this;
+        var receiver = void 0;
+        if (typeof fn !== "function")  {
+            receiver = fn.receiver;
+            fn = fn.fn;
+        }
+        var len = fulfilleds.length;
+        var accum = void 0;
+        var startIndex = 0;
+
+        if (initialValue !== void 0) {
+            accum = initialValue;
+            startIndex = 0;
+        }
+        else {
+            startIndex = 1;
+            if (len > 0) accum = fulfilleds[0];
+        }
+        var i = startIndex;
+
+        if (i >= len) {
+            return accum;
+        }
+
+        var reduction = new Reduction(fn, i, accum, fulfilleds, receiver);
+        reduction.iterate();
+        return reduction.promise;
+    }
+
+    function Promise$_unpackReducer(fulfilleds) {
+        var fn = this.fn;
+        var initialValue = this.initialValue;
+        return Promise$_reducer.call(fn, fulfilleds, initialValue);
+    }
+
+    function Promise$_slowReduce(
+        promises, fn, initialValue, useBound, caller) {
+        return initialValue._then(function callee(initialValue) {
+            return Promise$_Reduce(
+                promises, fn, initialValue, useBound, callee);
+        }, void 0, void 0, void 0, void 0, caller);
+    }
+
+    function Promise$_Reduce(promises, fn, initialValue, useBound, caller) {
+        if (typeof fn !== "function") {
+            return apiRejection("fn must be a function");
+        }
+
+        if (useBound === true && promises._isBound()) {
+            fn = {
+                fn: fn,
+                receiver: promises._boundTo
+            };
+        }
+
+        if (initialValue !== void 0) {
+            if (Promise.is(initialValue)) {
+                if (initialValue.isFulfilled()) {
+                    initialValue = initialValue._settledValue;
+                }
+                else {
+                    return Promise$_slowReduce(promises,
+                        fn, initialValue, useBound, caller);
+                }
+            }
+
+            return Promise$_CreatePromiseArray(promises, PromiseArray, caller,
+                useBound === true && promises._isBound()
+                    ? promises._boundTo
+                    : void 0)
+                .promise()
+                ._then(Promise$_unpackReducer, void 0, void 0, {
+                    fn: fn,
+                    initialValue: initialValue
+                }, void 0, Promise.reduce);
+        }
+        return Promise$_CreatePromiseArray(promises, PromiseArray, caller,
+                useBound === true && promises._isBound()
+                    ? promises._boundTo
+                    : void 0).promise()
+            ._then(Promise$_reducer, void 0, void 0, fn, void 0, caller);
+    }
+
+
+    Promise.reduce = function Promise$Reduce(promises, fn, initialValue) {
+        return Promise$_Reduce(promises, fn,
+            initialValue, false, Promise.reduce);
+    };
+
+    Promise.prototype.reduce = function Promise$reduce(fn, initialValue) {
+        return Promise$_Reduce(this, fn, initialValue,
+                                true, this.reduce);
+    };
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/schedule.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/schedule.js
new file mode 100644
index 0000000..ae2271e
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/schedule.js
@@ -0,0 +1,121 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var global = require("./global.js");
+var schedule;
+if (typeof process !== "undefined" && process !== null &&
+    typeof process.cwd === "function" &&
+    typeof process.nextTick === "function" &&
+    typeof process.version === "string") {
+    schedule = function Promise$_Scheduler(fn) {
+        process.nextTick(fn);
+    };
+}
+else if ((typeof global.MutationObserver === "function" ||
+        typeof global.WebkitMutationObserver === "function" ||
+        typeof global.WebKitMutationObserver === "function") &&
+        typeof document !== "undefined" &&
+        typeof document.createElement === "function") {
+
+
+    schedule = (function(){
+        var MutationObserver = global.MutationObserver ||
+            global.WebkitMutationObserver ||
+            global.WebKitMutationObserver;
+        var div = document.createElement("div");
+        var queuedFn = void 0;
+        var observer = new MutationObserver(
+            function Promise$_Scheduler() {
+                var fn = queuedFn;
+                queuedFn = void 0;
+                fn();
+            }
+       );
+        observer.observe(div, {
+            attributes: true
+        });
+        return function Promise$_Scheduler(fn) {
+            queuedFn = fn;
+            div.setAttribute("class", "foo");
+        };
+
+    })();
+}
+else if (typeof global.postMessage === "function" &&
+    typeof global.importScripts !== "function" &&
+    typeof global.addEventListener === "function" &&
+    typeof global.removeEventListener === "function") {
+
+    var MESSAGE_KEY = "bluebird_message_key_" + Math.random();
+    schedule = (function(){
+        var queuedFn = void 0;
+
+        function Promise$_Scheduler(e) {
+            if (e.source === global &&
+                e.data === MESSAGE_KEY) {
+                var fn = queuedFn;
+                queuedFn = void 0;
+                fn();
+            }
+        }
+
+        global.addEventListener("message", Promise$_Scheduler, false);
+
+        return function Promise$_Scheduler(fn) {
+            queuedFn = fn;
+            global.postMessage(
+                MESSAGE_KEY, "*"
+           );
+        };
+
+    })();
+}
+else if (typeof global.MessageChannel === "function") {
+    schedule = (function(){
+        var queuedFn = void 0;
+
+        var channel = new global.MessageChannel();
+        channel.port1.onmessage = function Promise$_Scheduler() {
+                var fn = queuedFn;
+                queuedFn = void 0;
+                fn();
+        };
+
+        return function Promise$_Scheduler(fn) {
+            queuedFn = fn;
+            channel.port2.postMessage(null);
+        };
+    })();
+}
+else if (global.setTimeout) {
+    schedule = function Promise$_Scheduler(fn) {
+        setTimeout(fn, 4);
+    };
+}
+else {
+    schedule = function Promise$_Scheduler(fn) {
+        fn();
+    };
+}
+
+module.exports = schedule;
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/settle.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/settle.js
new file mode 100644
index 0000000..863882f
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/settle.js
@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports =
+    function(Promise, Promise$_CreatePromiseArray, PromiseArray) {
+
+    var SettledPromiseArray = require("./settled_promise_array.js")(
+        Promise, PromiseArray);
+
+    function Promise$_Settle(promises, useBound, caller) {
+        return Promise$_CreatePromiseArray(
+            promises,
+            SettledPromiseArray,
+            caller,
+            useBound === true && promises._isBound()
+                ? promises._boundTo
+                : void 0
+       ).promise();
+    }
+
+    Promise.settle = function Promise$Settle(promises) {
+        return Promise$_Settle(promises, false, Promise.settle);
+    };
+
+    Promise.prototype.settle = function Promise$settle() {
+        return Promise$_Settle(this, true, this.settle);
+    };
+
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/settled_promise_array.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/settled_promise_array.js
new file mode 100644
index 0000000..fd25d4d
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/settled_promise_array.js
@@ -0,0 +1,60 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, PromiseArray) {
+var PromiseInspection = require("./promise_inspection.js");
+var util = require("./util.js");
+var inherits = util.inherits;
+function SettledPromiseArray(values, caller, boundTo) {
+    this.constructor$(values, caller, boundTo);
+}
+inherits(SettledPromiseArray, PromiseArray);
+
+SettledPromiseArray.prototype._promiseResolved =
+function SettledPromiseArray$_promiseResolved(index, inspection) {
+    this._values[index] = inspection;
+    var totalResolved = ++this._totalResolved;
+    if (totalResolved >= this._length) {
+        this._resolve(this._values);
+    }
+};
+
+SettledPromiseArray.prototype._promiseFulfilled =
+function SettledPromiseArray$_promiseFulfilled(value, index) {
+    if (this._isResolved()) return;
+    var ret = new PromiseInspection();
+    ret._bitField = 268435456;
+    ret._settledValue = value;
+    this._promiseResolved(index, ret);
+};
+SettledPromiseArray.prototype._promiseRejected =
+function SettledPromiseArray$_promiseRejected(reason, index) {
+    if (this._isResolved()) return;
+    var ret = new PromiseInspection();
+    ret._bitField = 134217728;
+    ret._settledValue = reason;
+    this._promiseResolved(index, ret);
+};
+
+return SettledPromiseArray;
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/some.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/some.js
new file mode 100644
index 0000000..21c4ecd
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/some.js
@@ -0,0 +1,57 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports =
+function(Promise, Promise$_CreatePromiseArray, PromiseArray, apiRejection) {
+
+    var SomePromiseArray = require("./some_promise_array.js")(PromiseArray);
+    function Promise$_Some(promises, howMany, useBound, caller) {
+        if ((howMany | 0) !== howMany || howMany < 0) {
+            return apiRejection("expecting a positive integer");
+        }
+        var ret = Promise$_CreatePromiseArray(
+            promises,
+            SomePromiseArray,
+            caller,
+            useBound === true && promises._isBound()
+                ? promises._boundTo
+                : void 0
+       );
+        var promise = ret.promise();
+        if (promise.isRejected()) {
+            return promise;
+        }
+        ret.setHowMany(howMany);
+        ret.init();
+        return promise;
+    }
+
+    Promise.some = function Promise$Some(promises, howMany) {
+        return Promise$_Some(promises, howMany, false, Promise.some);
+    };
+
+    Promise.prototype.some = function Promise$some(count) {
+        return Promise$_Some(this, count, true, this.some);
+    };
+
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/some_promise_array.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/some_promise_array.js
new file mode 100644
index 0000000..d3b89d4
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/some_promise_array.js
@@ -0,0 +1,131 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function (PromiseArray) {
+var util = require("./util.js");
+var RangeError = require("./errors.js").RangeError;
+var inherits = util.inherits;
+var isArray = util.isArray;
+
+function SomePromiseArray(values, caller, boundTo) {
+    this.constructor$(values, caller, boundTo);
+    this._howMany = 0;
+    this._unwrap = false;
+    this._initialized = false;
+}
+inherits(SomePromiseArray, PromiseArray);
+
+SomePromiseArray.prototype._init = function SomePromiseArray$_init() {
+    if (!this._initialized) {
+        return;
+    }
+    if (this._howMany === 0) {
+        this._resolve([]);
+        return;
+    }
+    this._init$(void 0, -2);
+    var isArrayResolved = isArray(this._values);
+    this._holes = isArrayResolved ? this._values.length - this.length() : 0;
+
+    if (!this._isResolved() &&
+        isArrayResolved &&
+        this._howMany > this._canPossiblyFulfill()) {
+        var message = "(Promise.some) input array contains less than " +
+                        this._howMany  + " promises";
+        this._reject(new RangeError(message));
+    }
+};
+
+SomePromiseArray.prototype.init = function SomePromiseArray$init() {
+    this._initialized = true;
+    this._init();
+};
+
+SomePromiseArray.prototype.setUnwrap = function SomePromiseArray$setUnwrap() {
+    this._unwrap = true;
+};
+
+SomePromiseArray.prototype.howMany = function SomePromiseArray$howMany() {
+    return this._howMany;
+};
+
+SomePromiseArray.prototype.setHowMany =
+function SomePromiseArray$setHowMany(count) {
+    if (this._isResolved()) return;
+    this._howMany = count;
+};
+
+SomePromiseArray.prototype._promiseFulfilled =
+function SomePromiseArray$_promiseFulfilled(value) {
+    if (this._isResolved()) return;
+    this._addFulfilled(value);
+    if (this._fulfilled() === this.howMany()) {
+        this._values.length = this.howMany();
+        if (this.howMany() === 1 && this._unwrap) {
+            this._resolve(this._values[0]);
+        }
+        else {
+            this._resolve(this._values);
+        }
+    }
+
+};
+SomePromiseArray.prototype._promiseRejected =
+function SomePromiseArray$_promiseRejected(reason) {
+    if (this._isResolved()) return;
+    this._addRejected(reason);
+    if (this.howMany() > this._canPossiblyFulfill()) {
+        if (this._values.length === this.length()) {
+            this._reject([]);
+        }
+        else {
+            this._reject(this._values.slice(this.length() + this._holes));
+        }
+    }
+};
+
+SomePromiseArray.prototype._fulfilled = function SomePromiseArray$_fulfilled() {
+    return this._totalResolved;
+};
+
+SomePromiseArray.prototype._rejected = function SomePromiseArray$_rejected() {
+    return this._values.length - this.length() - this._holes;
+};
+
+SomePromiseArray.prototype._addRejected =
+function SomePromiseArray$_addRejected(reason) {
+    this._values.push(reason);
+};
+
+SomePromiseArray.prototype._addFulfilled =
+function SomePromiseArray$_addFulfilled(value) {
+    this._values[this._totalResolved++] = value;
+};
+
+SomePromiseArray.prototype._canPossiblyFulfill =
+function SomePromiseArray$_canPossiblyFulfill() {
+    return this.length() - this._rejected();
+};
+
+return SomePromiseArray;
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/synchronous_inspection.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/synchronous_inspection.js
new file mode 100644
index 0000000..dcbdc90
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/synchronous_inspection.js
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise) {
+    var PromiseInspection = require("./promise_inspection.js");
+
+    Promise.prototype.inspect = function Promise$inspect() {
+        return new PromiseInspection(this);
+    };
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/thenables.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/thenables.js
new file mode 100644
index 0000000..510da18
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/thenables.js
@@ -0,0 +1,138 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, INTERNAL) {
+    var util = require("./util.js");
+    var canAttach = require("./errors.js").canAttach;
+    var errorObj = util.errorObj;
+    var isObject = util.isObject;
+
+    function getThen(obj) {
+        try {
+            return obj.then;
+        }
+        catch(e) {
+            errorObj.e = e;
+            return errorObj;
+        }
+    }
+
+    function Promise$_Cast(obj, caller, originalPromise) {
+        if (isObject(obj)) {
+            if (obj instanceof Promise) {
+                return obj;
+            }
+            else if (isAnyBluebirdPromise(obj)) {
+                var ret = new Promise(INTERNAL);
+                ret._setTrace(caller, void 0);
+                obj._then(
+                    ret._fulfillUnchecked,
+                    ret._rejectUncheckedCheckError,
+                    ret._progressUnchecked,
+                    ret,
+                    null,
+                    void 0
+                );
+                ret._setFollowing();
+                return ret;
+            }
+            var then = getThen(obj);
+            if (then === errorObj) {
+                caller = typeof caller === "function" ? caller : Promise$_Cast;
+                if (originalPromise !== void 0 && canAttach(then.e)) {
+                    originalPromise._attachExtraTrace(then.e);
+                }
+                return Promise.reject(then.e, caller);
+            }
+            else if (typeof then === "function") {
+                caller = typeof caller === "function" ? caller : Promise$_Cast;
+                return Promise$_doThenable(obj, then, caller, originalPromise);
+            }
+        }
+        return obj;
+    }
+
+    var hasProp = {}.hasOwnProperty;
+    function isAnyBluebirdPromise(obj) {
+        return hasProp.call(obj, "_promise0");
+    }
+
+    function Promise$_doThenable(x, then, caller, originalPromise) {
+        var resolver = Promise.defer(caller);
+        var called = false;
+        try {
+            then.call(
+                x,
+                Promise$_resolveFromThenable,
+                Promise$_rejectFromThenable,
+                Promise$_progressFromThenable
+            );
+        }
+        catch(e) {
+            if (!called) {
+                called = true;
+                var trace = canAttach(e) ? e : new Error(e + "");
+                if (originalPromise !== void 0) {
+                    originalPromise._attachExtraTrace(trace);
+                }
+                resolver.promise._reject(e, trace);
+            }
+        }
+        return resolver.promise;
+
+        function Promise$_resolveFromThenable(y) {
+            if (called) return;
+            called = true;
+
+            if (x === y) {
+                var e = Promise._makeSelfResolutionError();
+                if (originalPromise !== void 0) {
+                    originalPromise._attachExtraTrace(e);
+                }
+                resolver.promise._reject(e, void 0);
+                return;
+            }
+            resolver.resolve(y);
+        }
+
+        function Promise$_rejectFromThenable(r) {
+            if (called) return;
+            called = true;
+            var trace = canAttach(r) ? r : new Error(r + "");
+            if (originalPromise !== void 0) {
+                originalPromise._attachExtraTrace(trace);
+            }
+            resolver.promise._reject(r, trace);
+        }
+
+        function Promise$_progressFromThenable(v) {
+            if (called) return;
+            var promise = resolver.promise;
+            if (typeof promise._progress === "function") {
+                promise._progress(v);
+            }
+        }
+    }
+
+    Promise._cast = Promise$_Cast;
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/timers.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/timers.js
new file mode 100644
index 0000000..8edcac2
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/timers.js
@@ -0,0 +1,114 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+
+var global = require("./global.js");
+var setTimeout = function(fn, time) {
+    var $_len = arguments.length;var args = new Array($_len - 2); for(var $_i = 2; $_i < $_len; ++$_i) {args[$_i - 2] = arguments[$_i];}
+    global.setTimeout(function() {
+        fn.apply(void 0, args);
+    }, time);
+};
+
+var pass = {};
+global.setTimeout( function(_) {
+    if(_ === pass) {
+        setTimeout = global.setTimeout;
+    }
+}, 1, pass);
+
+module.exports = function(Promise, INTERNAL) {
+    var util = require("./util.js");
+    var errors = require("./errors.js");
+    var apiRejection = require("./errors_api_rejection")(Promise);
+    var TimeoutError = Promise.TimeoutError;
+
+    var afterTimeout = function Promise$_afterTimeout(promise, message, ms) {
+        if (!promise.isPending()) return;
+        if (typeof message !== "string") {
+            message = "operation timed out after" + " " + ms + " ms"
+        }
+        var err = new TimeoutError(message);
+        errors.markAsOriginatingFromRejection(err);
+        promise._attachExtraTrace(err);
+        promise._rejectUnchecked(err);
+    };
+
+    var afterDelay = function Promise$_afterDelay(value, promise) {
+        promise._fulfill(value);
+    };
+
+    Promise.delay = function Promise$Delay(value, ms, caller) {
+        if (ms === void 0) {
+            ms = value;
+            value = void 0;
+        }
+        ms = +ms;
+        if (typeof caller !== "function") {
+            caller = Promise.delay;
+        }
+        var maybePromise = Promise._cast(value, caller, void 0);
+        var promise = new Promise(INTERNAL);
+
+        if (Promise.is(maybePromise)) {
+            if (maybePromise._isBound()) {
+                promise._setBoundTo(maybePromise._boundTo);
+            }
+            if (maybePromise._cancellable()) {
+                promise._setCancellable();
+                promise._cancellationParent = maybePromise;
+            }
+            promise._setTrace(caller, maybePromise);
+            promise._follow(maybePromise);
+            return promise.then(function(value) {
+                return Promise.delay(value, ms);
+            });
+        }
+        else {
+            promise._setTrace(caller, void 0);
+            setTimeout(afterDelay, ms, value, promise);
+        }
+        return promise;
+    };
+
+    Promise.prototype.delay = function Promise$delay(ms) {
+        return Promise.delay(this, ms, this.delay);
+    };
+
+    Promise.prototype.timeout = function Promise$timeout(ms, message) {
+        ms = +ms;
+
+        var ret = new Promise(INTERNAL);
+        ret._setTrace(this.timeout, this);
+
+        if (this._isBound()) ret._setBoundTo(this._boundTo);
+        if (this._cancellable()) {
+            ret._setCancellable();
+            ret._cancellationParent = this;
+        }
+        ret._follow(this);
+        setTimeout(afterTimeout, ms, ret, message, ms);
+        return ret;
+    };
+
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/util.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/util.js
new file mode 100644
index 0000000..fbd34e9
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/util.js
@@ -0,0 +1,193 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var global = require("./global.js");
+var es5 = require("./es5.js");
+var haveGetters = (function(){
+    try {
+        var o = {};
+        es5.defineProperty(o, "f", {
+            get: function () {
+                return 3;
+            }
+        });
+        return o.f === 3;
+    }
+    catch (e) {
+        return false;
+    }
+
+})();
+
+var canEvaluate = (function() {
+    if (typeof window !== "undefined" && window !== null &&
+        typeof window.document !== "undefined" &&
+        typeof navigator !== "undefined" && navigator !== null &&
+        typeof navigator.appName === "string" &&
+        window === global) {
+        return false;
+    }
+    return true;
+})();
+
+function deprecated(msg) {
+    if (typeof console !== "undefined" && console !== null &&
+        typeof console.warn === "function") {
+        console.warn("Bluebird: " + msg);
+    }
+}
+
+var errorObj = {e: {}};
+function tryCatch1(fn, receiver, arg) {
+    try {
+        return fn.call(receiver, arg);
+    }
+    catch (e) {
+        errorObj.e = e;
+        return errorObj;
+    }
+}
+
+function tryCatch2(fn, receiver, arg, arg2) {
+    try {
+        return fn.call(receiver, arg, arg2);
+    }
+    catch (e) {
+        errorObj.e = e;
+        return errorObj;
+    }
+}
+
+function tryCatchApply(fn, args, receiver) {
+    try {
+        return fn.apply(receiver, args);
+    }
+    catch (e) {
+        errorObj.e = e;
+        return errorObj;
+    }
+}
+
+var inherits = function(Child, Parent) {
+    var hasProp = {}.hasOwnProperty;
+
+    function T() {
+        this.constructor = Child;
+        this.constructor$ = Parent;
+        for (var propertyName in Parent.prototype) {
+            if (hasProp.call(Parent.prototype, propertyName) &&
+                propertyName.charAt(propertyName.length-1) !== "$"
+           ) {
+                this[propertyName + "$"] = Parent.prototype[propertyName];
+            }
+        }
+    }
+    T.prototype = Parent.prototype;
+    Child.prototype = new T();
+    return Child.prototype;
+};
+
+function asString(val) {
+    return typeof val === "string" ? val : ("" + val);
+}
+
+function isPrimitive(val) {
+    return val == null || val === true || val === false ||
+        typeof val === "string" || typeof val === "number";
+
+}
+
+function isObject(value) {
+    return !isPrimitive(value);
+}
+
+function maybeWrapAsError(maybeError) {
+    if (!isPrimitive(maybeError)) return maybeError;
+
+    return new Error(asString(maybeError));
+}
+
+function withAppended(target, appendee) {
+    var len = target.length;
+    var ret = new Array(len + 1);
+    var i;
+    for (i = 0; i < len; ++i) {
+        ret[i] = target[i];
+    }
+    ret[i] = appendee;
+    return ret;
+}
+
+
+function notEnumerableProp(obj, name, value) {
+    if (isPrimitive(obj)) return obj;
+    var descriptor = {
+        value: value,
+        configurable: true,
+        enumerable: false,
+        writable: true
+    };
+    es5.defineProperty(obj, name, descriptor);
+    return obj;
+}
+
+
+var wrapsPrimitiveReceiver = (function() {
+    return this !== "string";
+}).call("string");
+
+function thrower(r) {
+    throw r;
+}
+
+
+function toFastProperties(obj) {
+    /*jshint -W027*/
+    function f() {}
+    f.prototype = obj;
+    return f;
+    eval(obj);
+}
+
+var ret = {
+    thrower: thrower,
+    isArray: es5.isArray,
+    haveGetters: haveGetters,
+    notEnumerableProp: notEnumerableProp,
+    isPrimitive: isPrimitive,
+    isObject: isObject,
+    canEvaluate: canEvaluate,
+    deprecated: deprecated,
+    errorObj: errorObj,
+    tryCatch1: tryCatch1,
+    tryCatch2: tryCatch2,
+    tryCatchApply: tryCatchApply,
+    inherits: inherits,
+    withAppended: withAppended,
+    asString: asString,
+    maybeWrapAsError: maybeWrapAsError,
+    wrapsPrimitiveReceiver: wrapsPrimitiveReceiver,
+    toFastProperties: toFastProperties
+};
+
+module.exports = ret;
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/package.json b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/package.json
new file mode 100644
index 0000000..5980304
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/package.json
@@ -0,0 +1,85 @@
+{
+  "name": "bluebird",
+  "description": "Full featured Promises/A+ implementation with exceptionally good performance",
+  "version": "1.1.1",
+  "keywords": [
+    "promise",
+    "performance",
+    "promises",
+    "promises-a",
+    "promises-aplus",
+    "async",
+    "await",
+    "deferred",
+    "deferreds",
+    "future",
+    "flow control",
+    "dsl",
+    "fluent interface"
+  ],
+  "scripts": {
+    "test": "grunt test"
+  },
+  "homepage": "https://github.com/petkaantonov/bluebird",
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/petkaantonov/bluebird.git"
+  },
+  "bugs": {
+    "url": "http://github.com/petkaantonov/bluebird/issues"
+  },
+  "license": "MIT",
+  "author": {
+    "name": "Petka Antonov",
+    "email": "petka_antonov@hotmail.com",
+    "url": "http://github.com/petkaantonov/"
+  },
+  "devDependencies": {
+    "grunt": "~0.4.1",
+    "grunt-contrib-jshint": "~0.6.4",
+    "grunt-contrib-watch": "latest",
+    "grunt-contrib-connect": "latest",
+    "grunt-saucelabs": "latest",
+    "acorn": "~0.3.1",
+    "mocha": "~1.12.1",
+    "q": "~0.9.7",
+    "when": "~2.4.0",
+    "deferred": "~0.6.5",
+    "rsvp": "~2.0.4",
+    "avow": "~2.0.1",
+    "jsdom": "~0.8.4",
+    "jquery-browserify": "~1.8.1",
+    "sinon": "~1.7.3",
+    "kew": "~0.2.2",
+    "browserify": "~2.35.0",
+    "concurrent": "~0.3.2",
+    "text-table": "~0.2.0",
+    "grunt-cli": "~0.1.9",
+    "jshint-stylish": "~0.1.3",
+    "semver-utils": "~1.1.0",
+    "rimraf": "~2.2.6",
+    "mkdirp": "~0.3.5"
+  },
+  "main": "./js/main/bluebird.js",
+  "_id": "bluebird@1.1.1",
+  "dist": {
+    "shasum": "744e9980145e2ebc41a9e34826f913096667fb33",
+    "tarball": "http://registry.npmjs.org/bluebird/-/bluebird-1.1.1.tgz"
+  },
+  "_from": "bluebird@~1.1.0",
+  "_npmVersion": "1.4.3",
+  "_npmUser": {
+    "name": "esailija",
+    "email": "petka_antonov@hotmail.com"
+  },
+  "maintainers": [
+    {
+      "name": "esailija",
+      "email": "petka_antonov@hotmail.com"
+    }
+  ],
+  "directories": {},
+  "_shasum": "744e9980145e2ebc41a9e34826f913096667fb33",
+  "_resolved": "https://registry.npmjs.org/bluebird/-/bluebird-1.1.1.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/zalgo.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/zalgo.js
new file mode 100644
index 0000000..1357352
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/bluebird/zalgo.js
@@ -0,0 +1 @@
+module.exports = require('./js/zalgo/bluebird.js');
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/commander/History.md b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/commander/History.md
new file mode 100644
index 0000000..3a69559
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/commander/History.md
@@ -0,0 +1,239 @@
+
+2.7.1 / 2015-03-11
+==================
+
+ * Revert #347 (fix collisions when option and first arg have same name) which causes a bug in #367.
+
+2.7.0 / 2015-03-09
+==================
+
+ * Fix git-style bug when installed globally. Close #335 #349 @zhiyelee
+ * Fix collisions when option and first arg have same name. Close #346 #347 @tonylukasavage
+ * Add support for camelCase on `opts()`. Close #353  @nkzawa
+ * Add node.js 0.12 and io.js to travis.yml
+ * Allow RegEx options. #337 @palanik
+ * Fixes exit code when sub-command failing.  Close #260 #332 @pirelenito
+ * git-style `bin` files in $PATH make sense. Close #196 #327  @zhiyelee
+
+2.6.0 / 2014-12-30
+==================
+
+  * added `Command#allowUnknownOption` method. Close #138 #318 @doozr @zhiyelee
+  * Add application description to the help msg. Close #112 @dalssoft
+
+2.5.1 / 2014-12-15
+==================
+
+  * fixed two bugs incurred by variadic arguments. Close #291 @Quentin01 #302 @zhiyelee
+
+2.5.0 / 2014-10-24
+==================
+
+ * add support for variadic arguments. Closes #277 @whitlockjc
+
+2.4.0 / 2014-10-17
+==================
+
+ * fixed a bug on executing the coercion function of subcommands option. Closes #270
+ * added `Command.prototype.name` to retrieve command name. Closes #264 #266 @tonylukasavage
+ * added `Command.prototype.opts` to retrieve all the options as a simple object of key-value pairs. Closes #262 @tonylukasavage
+ * fixed a bug on subcommand name. Closes #248 @jonathandelgado
+ * fixed function normalize doesn’t honor option terminator. Closes #216 @abbr
+
+2.3.0 / 2014-07-16
+==================
+
+ * add command alias'. Closes PR #210
+ * fix: Typos. Closes #99
+ * fix: Unused fs module. Closes #217
+
+2.2.0 / 2014-03-29
+==================
+
+ * add passing of previous option value
+ * fix: support subcommands on windows. Closes #142
+ * Now the defaultValue passed as the second argument of the coercion function.
+
+2.1.0 / 2013-11-21
+==================
+
+ * add: allow cflag style option params, unit test, fixes #174
+
+2.0.0 / 2013-07-18
+==================
+
+ * remove input methods (.prompt, .confirm, etc)
+
+1.3.2 / 2013-07-18
+==================
+
+ * add support for sub-commands to co-exist with the original command
+
+1.3.1 / 2013-07-18
+==================
+
+ * add quick .runningCommand hack so you can opt-out of other logic when running a sub command
+
+1.3.0 / 2013-07-09
+==================
+
+ * add EACCES error handling
+ * fix sub-command --help
+
+1.2.0 / 2013-06-13
+==================
+
+ * allow "-" hyphen as an option argument
+ * support for RegExp coercion
+
+1.1.1 / 2012-11-20
+==================
+
+  * add more sub-command padding
+  * fix .usage() when args are present. Closes #106
+
+1.1.0 / 2012-11-16
+==================
+
+  * add git-style executable subcommand support. Closes #94
+
+1.0.5 / 2012-10-09
+==================
+
+  * fix `--name` clobbering. Closes #92
+  * fix examples/help. Closes #89
+
+1.0.4 / 2012-09-03
+==================
+
+  * add `outputHelp()` method.
+
+1.0.3 / 2012-08-30
+==================
+
+  * remove invalid .version() defaulting
+
+1.0.2 / 2012-08-24
+==================
+
+  * add `--foo=bar` support [arv]
+  * fix password on node 0.8.8. Make backward compatible with 0.6 [focusaurus]
+
+1.0.1 / 2012-08-03
+==================
+
+  * fix issue #56
+  * fix tty.setRawMode(mode) was moved to tty.ReadStream#setRawMode() (i.e. process.stdin.setRawMode())
+
+1.0.0 / 2012-07-05
+==================
+
+  * add support for optional option descriptions
+  * add defaulting of `.version()` to package.json's version
+
+0.6.1 / 2012-06-01
+==================
+
+  * Added: append (yes or no) on confirmation
+  * Added: allow node.js v0.7.x
+
+0.6.0 / 2012-04-10
+==================
+
+  * Added `.prompt(obj, callback)` support. Closes #49
+  * Added default support to .choose(). Closes #41
+  * Fixed the choice example
+
+0.5.1 / 2011-12-20
+==================
+
+  * Fixed `password()` for recent nodes. Closes #36
+
+0.5.0 / 2011-12-04
+==================
+
+  * Added sub-command option support [itay]
+
+0.4.3 / 2011-12-04
+==================
+
+  * Fixed custom help ordering. Closes #32
+
+0.4.2 / 2011-11-24
+==================
+
+  * Added travis support
+  * Fixed: line-buffered input automatically trimmed. Closes #31
+
+0.4.1 / 2011-11-18
+==================
+
+  * Removed listening for "close" on --help
+
+0.4.0 / 2011-11-15
+==================
+
+  * Added support for `--`. Closes #24
+
+0.3.3 / 2011-11-14
+==================
+
+  * Fixed: wait for close event when writing help info [Jerry Hamlet]
+
+0.3.2 / 2011-11-01
+==================
+
+  * Fixed long flag definitions with values [felixge]
+
+0.3.1 / 2011-10-31
+==================
+
+  * Changed `--version` short flag to `-V` from `-v`
+  * Changed `.version()` so it's configurable [felixge]
+
+0.3.0 / 2011-10-31
+==================
+
+  * Added support for long flags only. Closes #18
+
+0.2.1 / 2011-10-24
+==================
+
+  * "node": ">= 0.4.x < 0.7.0". Closes #20
+
+0.2.0 / 2011-09-26
+==================
+
+  * Allow for defaults that are not just boolean. Default peassignment only occurs for --no-*, optional, and required arguments. [Jim Isaacs]
+
+0.1.0 / 2011-08-24
+==================
+
+  * Added support for custom `--help` output
+
+0.0.5 / 2011-08-18
+==================
+
+  * Changed: when the user enters nothing prompt for password again
+  * Fixed issue with passwords beginning with numbers [NuckChorris]
+
+0.0.4 / 2011-08-15
+==================
+
+  * Fixed `Commander#args`
+
+0.0.3 / 2011-08-15
+==================
+
+  * Added default option value support
+
+0.0.2 / 2011-08-15
+==================
+
+  * Added mask support to `Command#password(str[, mask], fn)`
+  * Added `Command#password(str, fn)`
+
+0.0.1 / 2010-01-03
+==================
+
+  * Initial release
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/commander/LICENSE b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/commander/LICENSE
new file mode 100644
index 0000000..10f997a
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/commander/LICENSE
@@ -0,0 +1,22 @@
+(The MIT License)
+
+Copyright (c) 2011 TJ Holowaychuk <tj@vision-media.ca>
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/commander/Readme.md b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/commander/Readme.md
new file mode 100644
index 0000000..4e091d2
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/commander/Readme.md
@@ -0,0 +1,311 @@
+# Commander.js
+
+
+[![Build Status](https://api.travis-ci.org/tj/commander.js.svg)](http://travis-ci.org/tj/commander.js)
+[![NPM Version](http://img.shields.io/npm/v/commander.svg?style=flat)](https://www.npmjs.org/package/commander)
+[![NPM Downloads](https://img.shields.io/npm/dm/commander.svg?style=flat)](https://www.npmjs.org/package/commander)
+[![Join the chat at https://gitter.im/tj/commander.js](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/tj/commander.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+
+  The complete solution for [node.js](http://nodejs.org) command-line interfaces, inspired by Ruby's [commander](https://github.com/tj/commander).  
+  [API documentation](http://tj.github.com/commander.js/)
+
+
+## Installation
+
+    $ npm install commander
+
+## Option parsing
+
+ Options with commander are defined with the `.option()` method, also serving as documentation for the options. The example below parses args and options from `process.argv`, leaving remaining args as the `program.args` array which were not consumed by options.
+
+```js
+#!/usr/bin/env node
+
+/**
+ * Module dependencies.
+ */
+
+var program = require('commander');
+
+program
+  .version('0.0.1')
+  .option('-p, --peppers', 'Add peppers')
+  .option('-P, --pineapple', 'Add pineapple')
+  .option('-b, --bbq', 'Add bbq sauce')
+  .option('-c, --cheese [type]', 'Add the specified type of cheese [marble]', 'marble')
+  .parse(process.argv);
+
+console.log('you ordered a pizza with:');
+if (program.peppers) console.log('  - peppers');
+if (program.pineapple) console.log('  - pineapple');
+if (program.bbq) console.log('  - bbq');
+console.log('  - %s cheese', program.cheese);
+```
+
+ Short flags may be passed as a single arg, for example `-abc` is equivalent to `-a -b -c`. Multi-word options such as "--template-engine" are camel-cased, becoming `program.templateEngine` etc.
+
+
+## Coercion
+
+```js
+function range(val) {
+  return val.split('..').map(Number);
+}
+
+function list(val) {
+  return val.split(',');
+}
+
+function collect(val, memo) {
+  memo.push(val);
+  return memo;
+}
+
+function increaseVerbosity(v, total) {
+  return total + 1;
+}
+
+program
+  .version('0.0.1')
+  .usage('[options] <file ...>')
+  .option('-i, --integer <n>', 'An integer argument', parseInt)
+  .option('-f, --float <n>', 'A float argument', parseFloat)
+  .option('-r, --range <a>..<b>', 'A range', range)
+  .option('-l, --list <items>', 'A list', list)
+  .option('-o, --optional [value]', 'An optional value')
+  .option('-c, --collect [value]', 'A repeatable value', collect, [])
+  .option('-v, --verbose', 'A value that can be increased', increaseVerbosity, 0)
+  .parse(process.argv);
+
+console.log(' int: %j', program.integer);
+console.log(' float: %j', program.float);
+console.log(' optional: %j', program.optional);
+program.range = program.range || [];
+console.log(' range: %j..%j', program.range[0], program.range[1]);
+console.log(' list: %j', program.list);
+console.log(' collect: %j', program.collect);
+console.log(' verbosity: %j', program.verbose);
+console.log(' args: %j', program.args);
+```
+
+## Regular Expression
+```js
+program
+  .version('0.0.1')
+  .option('-s --size <size>', 'Pizza size', /^(large|medium|small)$/i, 'medium')
+  .option('-d --drink [drink]', 'Drink', /^(coke|pepsi|izze)$/i)
+  .parse(process.argv);
+  
+console.log(' size: %j', program.size);
+console.log(' drink: %j', program.drink);
+```
+
+## Variadic arguments
+
+ The last argument of a command can be variadic, and only the last argument.  To make an argument variadic you have to
+ append `...` to the argument name.  Here is an example:
+
+```js
+#!/usr/bin/env node
+
+/**
+ * Module dependencies.
+ */
+
+var program = require('commander');
+
+program
+  .version('0.0.1')
+  .command('rmdir <dir> [otherDirs...]')
+  .action(function (dir, otherDirs) {
+    console.log('rmdir %s', dir);
+    if (otherDirs) {
+      otherDirs.forEach(function (oDir) {
+        console.log('rmdir %s', oDir);
+      });
+    }
+  });
+
+program.parse(process.argv);
+```
+
+ An `Array` is used for the value of a variadic argument.  This applies to `program.args` as well as the argument passed
+ to your action as demonstrated above.
+
+## Git-style sub-commands
+
+```js
+// file: ./examples/pm
+var program = require('..');
+
+program
+  .version('0.0.1')
+  .command('install [name]', 'install one or more packages')
+  .command('search [query]', 'search with optional query')
+  .command('list', 'list packages installed')
+  .parse(process.argv);
+```
+
+When `.command()` is invoked with a description argument, no `.action(callback)` should be called to handle sub-commands, otherwise there will be an error. This tells commander that you're going to use separate executables for sub-commands, much like `git(1)` and other popular tools.  
+The commander will try to search the executables in the directory of the entry script (like `./examples/pm`) with the name `program-command`, like `pm-install`, `pm-search`.
+
+If the program is designed to installed globally, make sure the executables have proper modes, like `755`.
+
+## Automated --help
+
+ The help information is auto-generated based on the information commander already knows about your program, so the following `--help` info is for free:
+
+```  
+ $ ./examples/pizza --help
+
+   Usage: pizza [options]
+
+   An application for pizzas ordering
+
+   Options:
+
+     -h, --help           output usage information
+     -V, --version        output the version number
+     -p, --peppers        Add peppers
+     -P, --pineapple      Add pineapple
+     -b, --bbq            Add bbq sauce
+     -c, --cheese <type>  Add the specified type of cheese [marble]
+     -C, --no-cheese      You do not want any cheese
+
+```
+
+## Custom help
+
+ You can display arbitrary `-h, --help` information
+ by listening for "--help". Commander will automatically
+ exit once you are done so that the remainder of your program
+ does not execute causing undesired behaviours, for example
+ in the following executable "stuff" will not output when
+ `--help` is used.
+
+```js
+#!/usr/bin/env node
+
+/**
+ * Module dependencies.
+ */
+
+var program = require('commander');
+
+program
+  .version('0.0.1')
+  .option('-f, --foo', 'enable some foo')
+  .option('-b, --bar', 'enable some bar')
+  .option('-B, --baz', 'enable some baz');
+
+// must be before .parse() since
+// node's emit() is immediate
+
+program.on('--help', function(){
+  console.log('  Examples:');
+  console.log('');
+  console.log('    $ custom-help --help');
+  console.log('    $ custom-help -h');
+  console.log('');
+});
+
+program.parse(process.argv);
+
+console.log('stuff');
+```
+
+Yields the following help output when `node script-name.js -h` or `node script-name.js --help` are run:
+
+```
+
+Usage: custom-help [options]
+
+Options:
+
+  -h, --help     output usage information
+  -V, --version  output the version number
+  -f, --foo      enable some foo
+  -b, --bar      enable some bar
+  -B, --baz      enable some baz
+
+Examples:
+
+  $ custom-help --help
+  $ custom-help -h
+
+```
+
+## .outputHelp()
+
+Output help information without exiting.
+
+If you want to display help by default (e.g. if no command was provided), you can use something like:
+
+```js
+var program = require('commander');
+
+program
+  .version('0.0.1')
+  .command('getstream [url]', 'get stream URL')
+  .parse(process.argv);
+
+  if (!process.argv.slice(2).length) {
+    program.outputHelp();
+  }
+```
+
+## .help()
+
+  Output help information and exit immediately.
+
+## Examples
+
+```js
+var program = require('commander');
+
+program
+  .version('0.0.1')
+  .option('-C, --chdir <path>', 'change the working directory')
+  .option('-c, --config <path>', 'set config path. defaults to ./deploy.conf')
+  .option('-T, --no-tests', 'ignore test hook')
+
+program
+  .command('setup [env]')
+  .description('run setup commands for all envs')
+  .option("-s, --setup_mode [mode]", "Which setup mode to use")
+  .action(function(env, options){
+    var mode = options.setup_mode || "normal";
+    env = env || 'all';
+    console.log('setup for %s env(s) with %s mode', env, mode);
+  });
+
+program
+  .command('exec <cmd>')
+  .alias('ex')
+  .description('execute the given remote cmd')
+  .option("-e, --exec_mode <mode>", "Which exec mode to use")
+  .action(function(cmd, options){
+    console.log('exec "%s" using %s mode', cmd, options.exec_mode);
+  }).on('--help', function() {
+    console.log('  Examples:');
+    console.log();
+    console.log('    $ deploy exec sequential');
+    console.log('    $ deploy exec async');
+    console.log();
+  });
+
+program
+  .command('*')
+  .action(function(env){
+    console.log('deploying "%s"', env);
+  });
+
+program.parse(process.argv);
+```
+
+More Demos can be found in the [examples](https://github.com/tj/commander.js/tree/master/examples) directory.
+
+## License
+
+MIT
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/commander/index.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/commander/index.js
new file mode 100644
index 0000000..1abf4df
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/commander/index.js
@@ -0,0 +1,1051 @@
+
+/**
+ * Module dependencies.
+ */
+
+var EventEmitter = require('events').EventEmitter;
+var spawn = require('child_process').spawn;
+var readlink = require('graceful-readlink').readlinkSync;
+var path = require('path');
+var dirname = path.dirname;
+var basename = path.basename;
+var fs = require('fs');
+
+/**
+ * Expose the root command.
+ */
+
+exports = module.exports = new Command();
+
+/**
+ * Expose `Command`.
+ */
+
+exports.Command = Command;
+
+/**
+ * Expose `Option`.
+ */
+
+exports.Option = Option;
+
+/**
+ * Initialize a new `Option` with the given `flags` and `description`.
+ *
+ * @param {String} flags
+ * @param {String} description
+ * @api public
+ */
+
+function Option(flags, description) {
+  this.flags = flags;
+  this.required = ~flags.indexOf('<');
+  this.optional = ~flags.indexOf('[');
+  this.bool = !~flags.indexOf('-no-');
+  flags = flags.split(/[ ,|]+/);
+  if (flags.length > 1 && !/^[[<]/.test(flags[1])) this.short = flags.shift();
+  this.long = flags.shift();
+  this.description = description || '';
+}
+
+/**
+ * Return option name.
+ *
+ * @return {String}
+ * @api private
+ */
+
+Option.prototype.name = function() {
+  return this.long
+    .replace('--', '')
+    .replace('no-', '');
+};
+
+/**
+ * Check if `arg` matches the short or long flag.
+ *
+ * @param {String} arg
+ * @return {Boolean}
+ * @api private
+ */
+
+Option.prototype.is = function(arg) {
+  return arg == this.short || arg == this.long;
+};
+
+/**
+ * Initialize a new `Command`.
+ *
+ * @param {String} name
+ * @api public
+ */
+
+function Command(name) {
+  this.commands = [];
+  this.options = [];
+  this._execs = [];
+  this._allowUnknownOption = false;
+  this._args = [];
+  this._name = name;
+}
+
+/**
+ * Inherit from `EventEmitter.prototype`.
+ */
+
+Command.prototype.__proto__ = EventEmitter.prototype;
+
+/**
+ * Add command `name`.
+ *
+ * The `.action()` callback is invoked when the
+ * command `name` is specified via __ARGV__,
+ * and the remaining arguments are applied to the
+ * function for access.
+ *
+ * When the `name` is "*" an un-matched command
+ * will be passed as the first arg, followed by
+ * the rest of __ARGV__ remaining.
+ *
+ * Examples:
+ *
+ *      program
+ *        .version('0.0.1')
+ *        .option('-C, --chdir <path>', 'change the working directory')
+ *        .option('-c, --config <path>', 'set config path. defaults to ./deploy.conf')
+ *        .option('-T, --no-tests', 'ignore test hook')
+ *
+ *      program
+ *        .command('setup')
+ *        .description('run remote setup commands')
+ *        .action(function() {
+ *          console.log('setup');
+ *        });
+ *
+ *      program
+ *        .command('exec <cmd>')
+ *        .description('run the given remote command')
+ *        .action(function(cmd) {
+ *          console.log('exec "%s"', cmd);
+ *        });
+ *
+ *      program
+ *        .command('teardown <dir> [otherDirs...]')
+ *        .description('run teardown commands')
+ *        .action(function(dir, otherDirs) {
+ *          console.log('dir "%s"', dir);
+ *          if (otherDirs) {
+ *            otherDirs.forEach(function (oDir) {
+ *              console.log('dir "%s"', oDir);
+ *            });
+ *          }
+ *        });
+ *
+ *      program
+ *        .command('*')
+ *        .description('deploy the given env')
+ *        .action(function(env) {
+ *          console.log('deploying "%s"', env);
+ *        });
+ *
+ *      program.parse(process.argv);
+  *
+ * @param {String} name
+ * @param {String} [desc] for git-style sub-commands
+ * @return {Command} the new command
+ * @api public
+ */
+
+Command.prototype.command = function(name, desc) {
+  var args = name.split(/ +/);
+  var cmd = new Command(args.shift());
+
+  if (desc) {
+    cmd.description(desc);
+    this.executables = true;
+    this._execs[cmd._name] = true;
+  }
+
+  this.commands.push(cmd);
+  cmd.parseExpectedArgs(args);
+  cmd.parent = this;
+
+  if (desc) return this;
+  return cmd;
+};
+
+/**
+ * Add an implicit `help [cmd]` subcommand
+ * which invokes `--help` for the given command.
+ *
+ * @api private
+ */
+
+Command.prototype.addImplicitHelpCommand = function() {
+  this.command('help [cmd]', 'display help for [cmd]');
+};
+
+/**
+ * Parse expected `args`.
+ *
+ * For example `["[type]"]` becomes `[{ required: false, name: 'type' }]`.
+ *
+ * @param {Array} args
+ * @return {Command} for chaining
+ * @api public
+ */
+
+Command.prototype.parseExpectedArgs = function(args) {
+  if (!args.length) return;
+  var self = this;
+  args.forEach(function(arg) {
+    var argDetails = {
+      required: false,
+      name: '',
+      variadic: false
+    };
+
+    switch (arg[0]) {
+      case '<':
+        argDetails.required = true;
+        argDetails.name = arg.slice(1, -1);
+        break;
+      case '[':
+        argDetails.name = arg.slice(1, -1);
+        break;
+    }
+
+    if (argDetails.name.length > 3 && argDetails.name.slice(-3) === '...') {
+      argDetails.variadic = true;
+      argDetails.name = argDetails.name.slice(0, -3);
+    }
+    if (argDetails.name) {
+      self._args.push(argDetails);
+    }
+  });
+  return this;
+};
+
+/**
+ * Register callback `fn` for the command.
+ *
+ * Examples:
+ *
+ *      program
+ *        .command('help')
+ *        .description('display verbose help')
+ *        .action(function() {
+ *           // output help here
+ *        });
+ *
+ * @param {Function} fn
+ * @return {Command} for chaining
+ * @api public
+ */
+
+Command.prototype.action = function(fn) {
+  var self = this;
+  var listener = function(args, unknown) {
+    // Parse any so-far unknown options
+    args = args || [];
+    unknown = unknown || [];
+
+    var parsed = self.parseOptions(unknown);
+
+    // Output help if necessary
+    outputHelpIfNecessary(self, parsed.unknown);
+
+    // If there are still any unknown options, then we simply
+    // die, unless someone asked for help, in which case we give it
+    // to them, and then we die.
+    if (parsed.unknown.length > 0) {
+      self.unknownOption(parsed.unknown[0]);
+    }
+
+    // Leftover arguments need to be pushed back. Fixes issue #56
+    if (parsed.args.length) args = parsed.args.concat(args);
+
+    self._args.forEach(function(arg, i) {
+      if (arg.required && null == args[i]) {
+        self.missingArgument(arg.name);
+      } else if (arg.variadic) {
+        if (i !== self._args.length - 1) {
+          self.variadicArgNotLast(arg.name);
+        }
+
+        args[i] = args.splice(i);
+      }
+    });
+
+    // Always append ourselves to the end of the arguments,
+    // to make sure we match the number of arguments the user
+    // expects
+    if (self._args.length) {
+      args[self._args.length] = self;
+    } else {
+      args.push(self);
+    }
+
+    fn.apply(self, args);
+  };
+  this.parent.on(this._name, listener);
+  if (this._alias) this.parent.on(this._alias, listener);
+  return this;
+};
+
+/**
+ * Define option with `flags`, `description` and optional
+ * coercion `fn`.
+ *
+ * The `flags` string should contain both the short and long flags,
+ * separated by comma, a pipe or space. The following are all valid
+ * all will output this way when `--help` is used.
+ *
+ *    "-p, --pepper"
+ *    "-p|--pepper"
+ *    "-p --pepper"
+ *
+ * Examples:
+ *
+ *     // simple boolean defaulting to false
+ *     program.option('-p, --pepper', 'add pepper');
+ *
+ *     --pepper
+ *     program.pepper
+ *     // => Boolean
+ *
+ *     // simple boolean defaulting to true
+ *     program.option('-C, --no-cheese', 'remove cheese');
+ *
+ *     program.cheese
+ *     // => true
+ *
+ *     --no-cheese
+ *     program.cheese
+ *     // => false
+ *
+ *     // required argument
+ *     program.option('-C, --chdir <path>', 'change the working directory');
+ *
+ *     --chdir /tmp
+ *     program.chdir
+ *     // => "/tmp"
+ *
+ *     // optional argument
+ *     program.option('-c, --cheese [type]', 'add cheese [marble]');
+ *
+ * @param {String} flags
+ * @param {String} description
+ * @param {Function|Mixed} fn or default
+ * @param {Mixed} defaultValue
+ * @return {Command} for chaining
+ * @api public
+ */
+
+Command.prototype.option = function(flags, description, fn, defaultValue) {
+  var self = this
+    , option = new Option(flags, description)
+    , oname = option.name()
+    , name = camelcase(oname);
+
+  // default as 3rd arg
+  if (typeof fn != 'function') {
+    if (fn instanceof RegExp) {
+      var regex = fn;
+      fn = function(val, def) {
+        var m = regex.exec(val);
+        return m ? m[0] : def;
+      }
+    }
+    else {
+      defaultValue = fn;
+      fn = null;
+    }
+  }
+
+  // preassign default value only for --no-*, [optional], or <required>
+  if (false == option.bool || option.optional || option.required) {
+    // when --no-* we make sure default is true
+    if (false == option.bool) defaultValue = true;
+    // preassign only if we have a default
+    if (undefined !== defaultValue) self[name] = defaultValue;
+  }
+
+  // register the option
+  this.options.push(option);
+
+  // when it's passed assign the value
+  // and conditionally invoke the callback
+  this.on(oname, function(val) {
+    // coercion
+    if (null !== val && fn) val = fn(val, undefined === self[name]
+      ? defaultValue
+      : self[name]);
+
+    // unassigned or bool
+    if ('boolean' == typeof self[name] || 'undefined' == typeof self[name]) {
+      // if no value, bool true, and we have a default, then use it!
+      if (null == val) {
+        self[name] = option.bool
+          ? defaultValue || true
+          : false;
+      } else {
+        self[name] = val;
+      }
+    } else if (null !== val) {
+      // reassign
+      self[name] = val;
+    }
+  });
+
+  return this;
+};
+
+/**
+ * Allow unknown options on the command line.
+ *
+ * @param {Boolean} arg if `true` or omitted, no error will be thrown
+ * for unknown options.
+ * @api public
+ */
+Command.prototype.allowUnknownOption = function(arg) {
+    this._allowUnknownOption = arguments.length === 0 || arg;
+    return this;
+};
+
+/**
+ * Parse `argv`, settings options and invoking commands when defined.
+ *
+ * @param {Array} argv
+ * @return {Command} for chaining
+ * @api public
+ */
+
+Command.prototype.parse = function(argv) {
+  // implicit help
+  if (this.executables) this.addImplicitHelpCommand();
+
+  // store raw args
+  this.rawArgs = argv;
+
+  // guess name
+  this._name = this._name || basename(argv[1], '.js');
+
+  // process argv
+  var parsed = this.parseOptions(this.normalize(argv.slice(2)));
+  var args = this.args = parsed.args;
+
+  var result = this.parseArgs(this.args, parsed.unknown);
+
+  // executable sub-commands
+  var name = result.args[0];
+  if (this._execs[name] && typeof this._execs[name] != "function") {
+    return this.executeSubCommand(argv, args, parsed.unknown);
+  }
+
+  return result;
+};
+
+/**
+ * Execute a sub-command executable.
+ *
+ * @param {Array} argv
+ * @param {Array} args
+ * @param {Array} unknown
+ * @api private
+ */
+
+Command.prototype.executeSubCommand = function(argv, args, unknown) {
+  args = args.concat(unknown);
+
+  if (!args.length) this.help();
+  if ('help' == args[0] && 1 == args.length) this.help();
+
+  // <cmd> --help
+  if ('help' == args[0]) {
+    args[0] = args[1];
+    args[1] = '--help';
+  }
+
+  // executable
+  var f = argv[1];
+  var link = readlink(f);
+  if (link !== f && link.charAt(0) !== '/') {
+    link = path.join(dirname(f), link)
+  }
+  var dir = dirname(link);
+  var bin = basename(f, '.js') + '-' + args[0];
+
+  // prefer local `./<bin>` to bin in the $PATH
+  var local = path.join(dir, bin);
+  try {
+    // for versions before node v0.8 when there weren't `fs.existsSync`
+    if (fs.statSync(local).isFile()) {
+      bin = local;
+    }
+  } catch (e) {}
+
+  // run it
+  args = args.slice(1);
+
+  var proc;
+  if (process.platform !== 'win32') {
+    proc = spawn(bin, args, { stdio: 'inherit', customFds: [0, 1, 2] });
+  } else {
+    args.unshift(local);
+    proc = spawn(process.execPath, args, { stdio: 'inherit'});
+  }
+
+  proc.on('close', process.exit.bind(process));
+  proc.on('error', function(err) {
+    if (err.code == "ENOENT") {
+      console.error('\n  %s(1) does not exist, try --help\n', bin);
+    } else if (err.code == "EACCES") {
+      console.error('\n  %s(1) not executable. try chmod or run with root\n', bin);
+    }
+    process.exit(1);
+  });
+
+  this.runningCommand = proc;
+};
+
+/**
+ * Normalize `args`, splitting joined short flags. For example
+ * the arg "-abc" is equivalent to "-a -b -c".
+ * This also normalizes equal sign and splits "--abc=def" into "--abc def".
+ *
+ * @param {Array} args
+ * @return {Array}
+ * @api private
+ */
+
+Command.prototype.normalize = function(args) {
+  var ret = []
+    , arg
+    , lastOpt
+    , index;
+
+  for (var i = 0, len = args.length; i < len; ++i) {
+    arg = args[i];
+    if (i > 0) {
+      lastOpt = this.optionFor(args[i-1]);
+    }
+
+    if (arg === '--') {
+      // Honor option terminator
+      ret = ret.concat(args.slice(i));
+      break;
+    } else if (lastOpt && lastOpt.required) {
+      ret.push(arg);
+    } else if (arg.length > 1 && '-' == arg[0] && '-' != arg[1]) {
+      arg.slice(1).split('').forEach(function(c) {
+        ret.push('-' + c);
+      });
+    } else if (/^--/.test(arg) && ~(index = arg.indexOf('='))) {
+      ret.push(arg.slice(0, index), arg.slice(index + 1));
+    } else {
+      ret.push(arg);
+    }
+  }
+
+  return ret;
+};
+
+/**
+ * Parse command `args`.
+ *
+ * When listener(s) are available those
+ * callbacks are invoked, otherwise the "*"
+ * event is emitted and those actions are invoked.
+ *
+ * @param {Array} args
+ * @return {Command} for chaining
+ * @api private
+ */
+
+Command.prototype.parseArgs = function(args, unknown) {
+  var name;
+
+  if (args.length) {
+    name = args[0];
+    if (this.listeners(name).length) {
+      this.emit(args.shift(), args, unknown);
+    } else {
+      this.emit('*', args);
+    }
+  } else {
+    outputHelpIfNecessary(this, unknown);
+
+    // If there were no args and we have unknown options,
+    // then they are extraneous and we need to error.
+    if (unknown.length > 0) {
+      this.unknownOption(unknown[0]);
+    }
+  }
+
+  return this;
+};
+
+/**
+ * Return an option matching `arg` if any.
+ *
+ * @param {String} arg
+ * @return {Option}
+ * @api private
+ */
+
+Command.prototype.optionFor = function(arg) {
+  for (var i = 0, len = this.options.length; i < len; ++i) {
+    if (this.options[i].is(arg)) {
+      return this.options[i];
+    }
+  }
+};
+
+/**
+ * Parse options from `argv` returning `argv`
+ * void of these options.
+ *
+ * @param {Array} argv
+ * @return {Array}
+ * @api public
+ */
+
+Command.prototype.parseOptions = function(argv) {
+  var args = []
+    , len = argv.length
+    , literal
+    , option
+    , arg;
+
+  var unknownOptions = [];
+
+  // parse options
+  for (var i = 0; i < len; ++i) {
+    arg = argv[i];
+
+    // literal args after --
+    if ('--' == arg) {
+      literal = true;
+      continue;
+    }
+
+    if (literal) {
+      args.push(arg);
+      continue;
+    }
+
+    // find matching Option
+    option = this.optionFor(arg);
+
+    // option is defined
+    if (option) {
+      // requires arg
+      if (option.required) {
+        arg = argv[++i];
+        if (null == arg) return this.optionMissingArgument(option);
+        this.emit(option.name(), arg);
+      // optional arg
+      } else if (option.optional) {
+        arg = argv[i+1];
+        if (null == arg || ('-' == arg[0] && '-' != arg)) {
+          arg = null;
+        } else {
+          ++i;
+        }
+        this.emit(option.name(), arg);
+      // bool
+      } else {
+        this.emit(option.name());
+      }
+      continue;
+    }
+
+    // looks like an option
+    if (arg.length > 1 && '-' == arg[0]) {
+      unknownOptions.push(arg);
+
+      // If the next argument looks like it might be
+      // an argument for this option, we pass it on.
+      // If it isn't, then it'll simply be ignored
+      if (argv[i+1] && '-' != argv[i+1][0]) {
+        unknownOptions.push(argv[++i]);
+      }
+      continue;
+    }
+
+    // arg
+    args.push(arg);
+  }
+
+  return { args: args, unknown: unknownOptions };
+};
+
+/**
+ * Return an object containing options as key-value pairs
+ *
+ * @return {Object}
+ * @api public
+ */
+Command.prototype.opts = function() {
+  var result = {}
+    , len = this.options.length;
+
+  for (var i = 0 ; i < len; i++) {
+    var key = camelcase(this.options[i].name());
+    result[key] = key === 'version' ? this._version : this[key];
+  }
+  return result;
+};
+
+/**
+ * Argument `name` is missing.
+ *
+ * @param {String} name
+ * @api private
+ */
+
+Command.prototype.missingArgument = function(name) {
+  console.error();
+  console.error("  error: missing required argument `%s'", name);
+  console.error();
+  process.exit(1);
+};
+
+/**
+ * `Option` is missing an argument, but received `flag` or nothing.
+ *
+ * @param {String} option
+ * @param {String} flag
+ * @api private
+ */
+
+Command.prototype.optionMissingArgument = function(option, flag) {
+  console.error();
+  if (flag) {
+    console.error("  error: option `%s' argument missing, got `%s'", option.flags, flag);
+  } else {
+    console.error("  error: option `%s' argument missing", option.flags);
+  }
+  console.error();
+  process.exit(1);
+};
+
+/**
+ * Unknown option `flag`.
+ *
+ * @param {String} flag
+ * @api private
+ */
+
+Command.prototype.unknownOption = function(flag) {
+  if (this._allowUnknownOption) return;
+  console.error();
+  console.error("  error: unknown option `%s'", flag);
+  console.error();
+  process.exit(1);
+};
+
+/**
+ * Variadic argument with `name` is not the last argument as required.
+ *
+ * @param {String} name
+ * @api private
+ */
+
+Command.prototype.variadicArgNotLast = function(name) {
+  console.error();
+  console.error("  error: variadic arguments must be last `%s'", name);
+  console.error();
+  process.exit(1);
+};
+
+/**
+ * Set the program version to `str`.
+ *
+ * This method auto-registers the "-V, --version" flag
+ * which will print the version number when passed.
+ *
+ * @param {String} str
+ * @param {String} flags
+ * @return {Command} for chaining
+ * @api public
+ */
+
+Command.prototype.version = function(str, flags) {
+  if (0 == arguments.length) return this._version;
+  this._version = str;
+  flags = flags || '-V, --version';
+  this.option(flags, 'output the version number');
+  this.on('version', function() {
+    process.stdout.write(str + '\n');
+    process.exit(0);
+  });
+  return this;
+};
+
+/**
+ * Set the description to `str`.
+ *
+ * @param {String} str
+ * @return {String|Command}
+ * @api public
+ */
+
+Command.prototype.description = function(str) {
+  if (0 == arguments.length) return this._description;
+  this._description = str;
+  return this;
+};
+
+/**
+ * Set an alias for the command
+ *
+ * @param {String} alias
+ * @return {String|Command}
+ * @api public
+ */
+
+Command.prototype.alias = function(alias) {
+  if (0 == arguments.length) return this._alias;
+  this._alias = alias;
+  return this;
+};
+
+/**
+ * Set / get the command usage `str`.
+ *
+ * @param {String} str
+ * @return {String|Command}
+ * @api public
+ */
+
+Command.prototype.usage = function(str) {
+  var args = this._args.map(function(arg) {
+    return humanReadableArgName(arg);
+  });
+
+  var usage = '[options]'
+    + (this.commands.length ? ' [command]' : '')
+    + (this._args.length ? ' ' + args.join(' ') : '');
+
+  if (0 == arguments.length) return this._usage || usage;
+  this._usage = str;
+
+  return this;
+};
+
+/**
+ * Get the name of the command
+ *
+ * @param {String} name
+ * @return {String|Command}
+ * @api public
+ */
+
+Command.prototype.name = function() {
+  return this._name;
+};
+
+/**
+ * Return the largest option length.
+ *
+ * @return {Number}
+ * @api private
+ */
+
+Command.prototype.largestOptionLength = function() {
+  return this.options.reduce(function(max, option) {
+    return Math.max(max, option.flags.length);
+  }, 0);
+};
+
+/**
+ * Return help for options.
+ *
+ * @return {String}
+ * @api private
+ */
+
+Command.prototype.optionHelp = function() {
+  var width = this.largestOptionLength();
+
+  // Prepend the help information
+  return [pad('-h, --help', width) + '  ' + 'output usage information']
+    .concat(this.options.map(function(option) {
+      return pad(option.flags, width) + '  ' + option.description;
+      }))
+    .join('\n');
+};
+
+/**
+ * Return command help documentation.
+ *
+ * @return {String}
+ * @api private
+ */
+
+Command.prototype.commandHelp = function() {
+  if (!this.commands.length) return '';
+
+  var commands = this.commands.map(function(cmd) {
+    var args = cmd._args.map(function(arg) {
+      return humanReadableArgName(arg);
+    }).join(' ');
+
+    return [
+      cmd._name
+        + (cmd._alias
+          ? '|' + cmd._alias
+          : '')
+        + (cmd.options.length
+          ? ' [options]'
+          : '')
+        + ' ' + args
+    , cmd.description()
+    ];
+  });
+
+  var width = commands.reduce(function(max, command) {
+    return Math.max(max, command[0].length);
+  }, 0);
+
+  return [
+      ''
+    , '  Commands:'
+    , ''
+    , commands.map(function(cmd) {
+      return pad(cmd[0], width) + '  ' + cmd[1];
+    }).join('\n').replace(/^/gm, '    ')
+    , ''
+  ].join('\n');
+};
+
+/**
+ * Return program help documentation.
+ *
+ * @return {String}
+ * @api private
+ */
+
+Command.prototype.helpInformation = function() {
+  var desc = [];
+  if (this._description) {
+    desc = [
+      '  ' + this._description
+      , ''
+    ];
+  }
+
+  var cmdName = this._name;
+  if (this._alias) {
+    cmdName = cmdName + '|' + this._alias;
+  }
+  var usage = [
+    ''
+    ,'  Usage: ' + cmdName + ' ' + this.usage()
+    , ''
+  ];
+
+  var cmds = [];
+  var commandHelp = this.commandHelp();
+  if (commandHelp) cmds = [commandHelp];
+
+  var options = [
+    '  Options:'
+    , ''
+    , '' + this.optionHelp().replace(/^/gm, '    ')
+    , ''
+    , ''
+  ];
+
+  return usage
+    .concat(cmds)
+    .concat(desc)
+    .concat(options)
+    .join('\n');
+};
+
+/**
+ * Output help information for this command
+ *
+ * @api public
+ */
+
+Command.prototype.outputHelp = function() {
+  process.stdout.write(this.helpInformation());
+  this.emit('--help');
+};
+
+/**
+ * Output help information and exit.
+ *
+ * @api public
+ */
+
+Command.prototype.help = function() {
+  this.outputHelp();
+  process.exit();
+};
+
+/**
+ * Camel-case the given `flag`
+ *
+ * @param {String} flag
+ * @return {String}
+ * @api private
+ */
+
+function camelcase(flag) {
+  return flag.split('-').reduce(function(str, word) {
+    return str + word[0].toUpperCase() + word.slice(1);
+  });
+}
+
+/**
+ * Pad `str` to `width`.
+ *
+ * @param {String} str
+ * @param {Number} width
+ * @return {String}
+ * @api private
+ */
+
+function pad(str, width) {
+  var len = Math.max(0, width - str.length);
+  return str + Array(len + 1).join(' ');
+}
+
+/**
+ * Output help information if necessary
+ *
+ * @param {Command} command to output help for
+ * @param {Array} array of options to search for -h or --help
+ * @api private
+ */
+
+function outputHelpIfNecessary(cmd, options) {
+  options = options || [];
+  for (var i = 0; i < options.length; i++) {
+    if (options[i] == '--help' || options[i] == '-h') {
+      cmd.outputHelp();
+      process.exit(0);
+    }
+  }
+}
+
+/**
+ * Takes an argument an returns its human readable equivalent for help usage.
+ *
+ * @param {Object} arg
+ * @return {String}
+ * @api private
+ */
+
+function humanReadableArgName(arg) {
+  var nameOutput = arg.name + (arg.variadic === true ? '...' : '');
+
+  return arg.required
+    ? '<' + nameOutput + '>'
+    : '[' + nameOutput + ']'
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/.npmignore b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/.npmignore
new file mode 100644
index 0000000..3ac7d16
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/.npmignore
@@ -0,0 +1,3 @@
+.idea/
+.DS_Store
+node_modules/
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/.travis.yml b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/.travis.yml
new file mode 100644
index 0000000..baf9be7
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/.travis.yml
@@ -0,0 +1,5 @@
+language: node_js
+node_js:
+  - "0.10"
+  - "0.12"
+  - "io.js"
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/LICENSE b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/LICENSE
new file mode 100644
index 0000000..d1f842f
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Zhiye Li
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/README.md b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/README.md
new file mode 100644
index 0000000..fc63b50
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/README.md
@@ -0,0 +1,17 @@
+# graceful-readlink
+[![NPM Version](http://img.shields.io/npm/v/graceful-readlink.svg?style=flat)](https://www.npmjs.org/package/graceful-readlink)
+[![NPM Downloads](https://img.shields.io/npm/dm/graceful-readlink.svg?style=flat)](https://www.npmjs.org/package/graceful-readlink)
+
+
+## Usage
+
+```js
+var readlinkSync = require('graceful-readlink').readlinkSync;
+console.log(readlinkSync(f));
+// output
+//  the file pointed to when `f` is a symbolic link
+//  the `f` itself when `f` is not a symbolic link
+```
+## Licence
+
+MIT License
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/index.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/index.js
new file mode 100644
index 0000000..7e9fc70
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/index.js
@@ -0,0 +1,12 @@
+var fs = require('fs')
+  , lstat = fs.lstatSync;
+
+exports.readlinkSync = function (p) {
+  if (lstat(p).isSymbolicLink()) {
+    return fs.readlinkSync(p);
+  } else {
+    return p;
+  }
+};
+
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/package.json b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/package.json
new file mode 100644
index 0000000..00bc841
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/package.json
@@ -0,0 +1,48 @@
+{
+  "name": "graceful-readlink",
+  "version": "1.0.1",
+  "description": "graceful fs.readlink",
+  "main": "index.js",
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/zhiyelee/graceful-readlink.git"
+  },
+  "homepage": "https://github.com/zhiyelee/graceful-readlink",
+  "bugs": {
+    "url": "https://github.com/zhiyelee/graceful-readlink/issues"
+  },
+  "keywords": [
+    "fs.readlink",
+    "readlink"
+  ],
+  "author": {
+    "name": "zhiyelee"
+  },
+  "license": "MIT",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "gitHead": "f6655275bebef706fb63fd01b5f062a7052419a5",
+  "_id": "graceful-readlink@1.0.1",
+  "_shasum": "4cafad76bc62f02fa039b2f94e9a3dd3a391a725",
+  "_from": "graceful-readlink@>= 1.0.0",
+  "_npmVersion": "2.1.17",
+  "_nodeVersion": "0.11.14",
+  "_npmUser": {
+    "name": "zhiyelee",
+    "email": "zhiyelee@gmail.com"
+  },
+  "maintainers": [
+    {
+      "name": "zhiyelee",
+      "email": "zhiyelee@gmail.com"
+    }
+  ],
+  "dist": {
+    "shasum": "4cafad76bc62f02fa039b2f94e9a3dd3a391a725",
+    "tarball": "http://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz"
+  },
+  "directories": {},
+  "_resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/commander/package.json b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/commander/package.json
new file mode 100644
index 0000000..d39156f
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/commander/package.json
@@ -0,0 +1,74 @@
+{
+  "name": "commander",
+  "version": "2.7.1",
+  "description": "the complete solution for node.js command-line programs",
+  "keywords": [
+    "command",
+    "option",
+    "parser"
+  ],
+  "author": {
+    "name": "TJ Holowaychuk",
+    "email": "tj@vision-media.ca"
+  },
+  "license": "MIT",
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/tj/commander.js.git"
+  },
+  "devDependencies": {
+    "should": ">= 0.0.1"
+  },
+  "scripts": {
+    "test": "make test"
+  },
+  "main": "index",
+  "engines": {
+    "node": ">= 0.6.x"
+  },
+  "files": [
+    "index.js"
+  ],
+  "dependencies": {
+    "graceful-readlink": ">= 1.0.0"
+  },
+  "gitHead": "103654f8f32c010ad1e62cefc9ab92d7c8d18c8e",
+  "bugs": {
+    "url": "https://github.com/tj/commander.js/issues"
+  },
+  "homepage": "https://github.com/tj/commander.js",
+  "_id": "commander@2.7.1",
+  "_shasum": "5d419a2bbed2c32ee3e4dca9bb45ab83ecc3065a",
+  "_from": "commander@^2.3.0",
+  "_npmVersion": "2.1.17",
+  "_nodeVersion": "0.11.14",
+  "_npmUser": {
+    "name": "zhiyelee",
+    "email": "zhiyelee@gmail.com"
+  },
+  "maintainers": [
+    {
+      "name": "tjholowaychuk",
+      "email": "tj@vision-media.ca"
+    },
+    {
+      "name": "somekittens",
+      "email": "rkoutnik@gmail.com"
+    },
+    {
+      "name": "zhiyelee",
+      "email": "zhiyelee@gmail.com"
+    },
+    {
+      "name": "thethomaseffect",
+      "email": "thethomaseffect@gmail.com"
+    }
+  ],
+  "dist": {
+    "shasum": "5d419a2bbed2c32ee3e4dca9bb45ab83ecc3065a",
+    "tarball": "http://registry.npmjs.org/commander/-/commander-2.7.1.tgz"
+  },
+  "directories": {},
+  "_resolved": "https://registry.npmjs.org/commander/-/commander-2.7.1.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/debug/Readme.md b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/debug/Readme.md
new file mode 100644
index 0000000..c5a34e8
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/debug/Readme.md
@@ -0,0 +1,115 @@
+# debug
+
+  tiny node.js debugging utility modelled after node core's debugging technique.
+
+## Installation
+
+```
+$ npm install debug
+```
+
+## Usage
+
+ With `debug` you simply invoke the exported function to generate your debug function, passing it a name which will determine if a noop function is returned, or a decorated `console.error`, so all of the `console` format string goodies you're used to work fine. A unique color is selected per-function for visibility.
+ 
+Example _app.js_:
+
+```js
+var debug = require('debug')('http')
+  , http = require('http')
+  , name = 'My App';
+
+// fake app
+
+debug('booting %s', name);
+
+http.createServer(function(req, res){
+  debug(req.method + ' ' + req.url);
+  res.end('hello\n');
+}).listen(3000, function(){
+  debug('listening');
+});
+
+// fake worker of some kind
+
+require('./worker');
+```
+
+Example _worker.js_:
+
+```js
+var debug = require('debug')('worker');
+
+setInterval(function(){
+  debug('doing some work');
+}, 1000);
+```
+
+ The __DEBUG__ environment variable is then used to enable these based on space or comma-delimited names. Here are some examples:
+
+  ![debug http and worker](http://f.cl.ly/items/18471z1H402O24072r1J/Screenshot.png)
+
+  ![debug worker](http://f.cl.ly/items/1X413v1a3M0d3C2c1E0i/Screenshot.png)
+
+## Millisecond diff
+
+  When actively developing an application it can be useful to see when the time spent between one `debug()` call and the next. Suppose for example you invoke `debug()` before requesting a resource, and after as well, the "+NNNms" will show you how much time was spent between calls.
+
+  ![](http://f.cl.ly/items/2i3h1d3t121M2Z1A3Q0N/Screenshot.png)
+
+  When stderr is not a TTY, `Date#toUTCString()` is used, making it more useful for logging the debug information as shown below:
+  _(NOTE: Debug now uses stderr instead of stdout, so the correct shell command for this example is actually `DEBUG=* node example/worker 2> out &`)_
+  
+  ![](http://f.cl.ly/items/112H3i0e0o0P0a2Q2r11/Screenshot.png)
+  
+## Conventions
+
+ If you're using this in one or more of your libraries, you _should_ use the name of your library so that developers may toggle debugging as desired without guessing names. If you have more than one debuggers you _should_ prefix them with your library name and use ":" to separate features. For example "bodyParser" from Connect would then be "connect:bodyParser". 
+
+## Wildcards
+
+  The "*" character may be used as a wildcard. Suppose for example your library has debuggers named "connect:bodyParser", "connect:compress", "connect:session", instead of listing all three with `DEBUG=connect:bodyParser,connect.compress,connect:session`, you may simply do `DEBUG=connect:*`, or to run everything using this module simply use `DEBUG=*`.
+
+  You can also exclude specific debuggers by prefixing them with a "-" character.  For example, `DEBUG=* -connect:*` would include all debuggers except those starting with "connect:".
+
+## Browser support
+
+ Debug works in the browser as well, currently persisted by `localStorage`. For example if you have `worker:a` and `worker:b` as shown below, and wish to debug both type `debug.enable('worker:*')` in the console and refresh the page, this will remain until you disable with `debug.disable()`. 
+
+```js
+a = debug('worker:a');
+b = debug('worker:b');
+
+setInterval(function(){
+  a('doing some work');
+}, 1000);
+
+setInterval(function(){
+  a('doing some work');
+}, 1200);
+```
+
+## License 
+
+(The MIT License)
+
+Copyright (c) 2011 TJ Holowaychuk &lt;tj@vision-media.ca&gt;
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/debug/debug.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/debug/debug.js
new file mode 100644
index 0000000..509dc0d
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/debug/debug.js
@@ -0,0 +1,137 @@
+
+/**
+ * Expose `debug()` as the module.
+ */
+
+module.exports = debug;
+
+/**
+ * Create a debugger with the given `name`.
+ *
+ * @param {String} name
+ * @return {Type}
+ * @api public
+ */
+
+function debug(name) {
+  if (!debug.enabled(name)) return function(){};
+
+  return function(fmt){
+    fmt = coerce(fmt);
+
+    var curr = new Date;
+    var ms = curr - (debug[name] || curr);
+    debug[name] = curr;
+
+    fmt = name
+      + ' '
+      + fmt
+      + ' +' + debug.humanize(ms);
+
+    // This hackery is required for IE8
+    // where `console.log` doesn't have 'apply'
+    window.console
+      && console.log
+      && Function.prototype.apply.call(console.log, console, arguments);
+  }
+}
+
+/**
+ * The currently active debug mode names.
+ */
+
+debug.names = [];
+debug.skips = [];
+
+/**
+ * Enables a debug mode by name. This can include modes
+ * separated by a colon and wildcards.
+ *
+ * @param {String} name
+ * @api public
+ */
+
+debug.enable = function(name) {
+  try {
+    localStorage.debug = name;
+  } catch(e){}
+
+  var split = (name || '').split(/[\s,]+/)
+    , len = split.length;
+
+  for (var i = 0; i < len; i++) {
+    name = split[i].replace('*', '.*?');
+    if (name[0] === '-') {
+      debug.skips.push(new RegExp('^' + name.substr(1) + '$'));
+    }
+    else {
+      debug.names.push(new RegExp('^' + name + '$'));
+    }
+  }
+};
+
+/**
+ * Disable debug output.
+ *
+ * @api public
+ */
+
+debug.disable = function(){
+  debug.enable('');
+};
+
+/**
+ * Humanize the given `ms`.
+ *
+ * @param {Number} m
+ * @return {String}
+ * @api private
+ */
+
+debug.humanize = function(ms) {
+  var sec = 1000
+    , min = 60 * 1000
+    , hour = 60 * min;
+
+  if (ms >= hour) return (ms / hour).toFixed(1) + 'h';
+  if (ms >= min) return (ms / min).toFixed(1) + 'm';
+  if (ms >= sec) return (ms / sec | 0) + 's';
+  return ms + 'ms';
+};
+
+/**
+ * Returns true if the given mode name is enabled, false otherwise.
+ *
+ * @param {String} name
+ * @return {Boolean}
+ * @api public
+ */
+
+debug.enabled = function(name) {
+  for (var i = 0, len = debug.skips.length; i < len; i++) {
+    if (debug.skips[i].test(name)) {
+      return false;
+    }
+  }
+  for (var i = 0, len = debug.names.length; i < len; i++) {
+    if (debug.names[i].test(name)) {
+      return true;
+    }
+  }
+  return false;
+};
+
+/**
+ * Coerce `val`.
+ */
+
+function coerce(val) {
+  if (val instanceof Error) return val.stack || val.message;
+  return val;
+}
+
+// persist
+
+try {
+  if (window.localStorage) debug.enable(localStorage.debug);
+} catch(e){}
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/debug/index.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/debug/index.js
new file mode 100644
index 0000000..e02c13b
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/debug/index.js
@@ -0,0 +1,5 @@
+if ('undefined' == typeof window) {
+  module.exports = require('./lib/debug');
+} else {
+  module.exports = require('./debug');
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/debug/lib/debug.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/debug/lib/debug.js
new file mode 100644
index 0000000..3b0a918
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/debug/lib/debug.js
@@ -0,0 +1,147 @@
+/**
+ * Module dependencies.
+ */
+
+var tty = require('tty');
+
+/**
+ * Expose `debug()` as the module.
+ */
+
+module.exports = debug;
+
+/**
+ * Enabled debuggers.
+ */
+
+var names = []
+  , skips = [];
+
+(process.env.DEBUG || '')
+  .split(/[\s,]+/)
+  .forEach(function(name){
+    name = name.replace('*', '.*?');
+    if (name[0] === '-') {
+      skips.push(new RegExp('^' + name.substr(1) + '$'));
+    } else {
+      names.push(new RegExp('^' + name + '$'));
+    }
+  });
+
+/**
+ * Colors.
+ */
+
+var colors = [6, 2, 3, 4, 5, 1];
+
+/**
+ * Previous debug() call.
+ */
+
+var prev = {};
+
+/**
+ * Previously assigned color.
+ */
+
+var prevColor = 0;
+
+/**
+ * Is stdout a TTY? Colored output is disabled when `true`.
+ */
+
+var isatty = tty.isatty(2);
+
+/**
+ * Select a color.
+ *
+ * @return {Number}
+ * @api private
+ */
+
+function color() {
+  return colors[prevColor++ % colors.length];
+}
+
+/**
+ * Humanize the given `ms`.
+ *
+ * @param {Number} m
+ * @return {String}
+ * @api private
+ */
+
+function humanize(ms) {
+  var sec = 1000
+    , min = 60 * 1000
+    , hour = 60 * min;
+
+  if (ms >= hour) return (ms / hour).toFixed(1) + 'h';
+  if (ms >= min) return (ms / min).toFixed(1) + 'm';
+  if (ms >= sec) return (ms / sec | 0) + 's';
+  return ms + 'ms';
+}
+
+/**
+ * Create a debugger with the given `name`.
+ *
+ * @param {String} name
+ * @return {Type}
+ * @api public
+ */
+
+function debug(name) {
+  function disabled(){}
+  disabled.enabled = false;
+
+  var match = skips.some(function(re){
+    return re.test(name);
+  });
+
+  if (match) return disabled;
+
+  match = names.some(function(re){
+    return re.test(name);
+  });
+
+  if (!match) return disabled;
+  var c = color();
+
+  function colored(fmt) {
+    fmt = coerce(fmt);
+
+    var curr = new Date;
+    var ms = curr - (prev[name] || curr);
+    prev[name] = curr;
+
+    fmt = '  \u001b[9' + c + 'm' + name + ' '
+      + '\u001b[3' + c + 'm\u001b[90m'
+      + fmt + '\u001b[3' + c + 'm'
+      + ' +' + humanize(ms) + '\u001b[0m';
+
+    console.error.apply(this, arguments);
+  }
+
+  function plain(fmt) {
+    fmt = coerce(fmt);
+
+    fmt = new Date().toUTCString()
+      + ' ' + name + ' ' + fmt;
+    console.error.apply(this, arguments);
+  }
+
+  colored.enabled = plain.enabled = true;
+
+  return isatty || process.env.DEBUG_COLORS
+    ? colored
+    : plain;
+}
+
+/**
+ * Coerce `val`.
+ */
+
+function coerce(val) {
+  if (val instanceof Error) return val.stack || val.message;
+  return val;
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/debug/package.json b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/debug/package.json
new file mode 100644
index 0000000..a88241d
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/debug/package.json
@@ -0,0 +1,65 @@
+{
+  "name": "debug",
+  "version": "0.7.4",
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/visionmedia/debug.git"
+  },
+  "description": "small debugging utility",
+  "keywords": [
+    "debug",
+    "log",
+    "debugger"
+  ],
+  "author": {
+    "name": "TJ Holowaychuk",
+    "email": "tj@vision-media.ca"
+  },
+  "dependencies": {},
+  "devDependencies": {
+    "mocha": "*"
+  },
+  "main": "lib/debug.js",
+  "browser": "./debug.js",
+  "engines": {
+    "node": "*"
+  },
+  "files": [
+    "lib/debug.js",
+    "debug.js",
+    "index.js"
+  ],
+  "component": {
+    "scripts": {
+      "debug/index.js": "index.js",
+      "debug/debug.js": "debug.js"
+    }
+  },
+  "bugs": {
+    "url": "https://github.com/visionmedia/debug/issues"
+  },
+  "homepage": "https://github.com/visionmedia/debug",
+  "_id": "debug@0.7.4",
+  "dist": {
+    "shasum": "06e1ea8082c2cb14e39806e22e2f6f757f92af39",
+    "tarball": "http://registry.npmjs.org/debug/-/debug-0.7.4.tgz"
+  },
+  "_from": "debug@~0.7.4",
+  "_npmVersion": "1.3.13",
+  "_npmUser": {
+    "name": "tjholowaychuk",
+    "email": "tj@vision-media.ca"
+  },
+  "maintainers": [
+    {
+      "name": "tjholowaychuk",
+      "email": "tj@vision-media.ca"
+    }
+  ],
+  "directories": {},
+  "_shasum": "06e1ea8082c2cb14e39806e22e2f6f757f92af39",
+  "_resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz",
+  "readme": "# debug\n\n  tiny node.js debugging utility modelled after node core's debugging technique.\n\n## Installation\n\n```\n$ npm install debug\n```\n\n## Usage\n\n With `debug` you simply invoke the exported function to generate your debug function, passing it a name which will determine if a noop function is returned, or a decorated `console.error`, so all of the `console` format string goodies you're used to work fine. A unique color is selected per-function for visibility.\n \nExample _app.js_:\n\n```js\nvar debug = require('debug')('http')\n  , http = require('http')\n  , name = 'My App';\n\n// fake app\n\ndebug('booting %s', name);\n\nhttp.createServer(function(req, res){\n  debug(req.method + ' ' + req.url);\n  res.end('hello\\n');\n}).listen(3000, function(){\n  debug('listening');\n});\n\n// fake worker of some kind\n\nrequire('./worker');\n```\n\nExample _worker.js_:\n\n```js\nvar debug = require('debug')('worker');\n\nsetInterval(function(){\n  debug('doing some work');\n}, 1000);\n```\n\n The __DEBUG__ environment variable is then used to enable these based on space or comma-delimited names. Here are some examples:\n\n  ![debug http and worker](http://f.cl.ly/items/18471z1H402O24072r1J/Screenshot.png)\n\n  ![debug worker](http://f.cl.ly/items/1X413v1a3M0d3C2c1E0i/Screenshot.png)\n\n## Millisecond diff\n\n  When actively developing an application it can be useful to see when the time spent between one `debug()` call and the next. Suppose for example you invoke `debug()` before requesting a resource, and after as well, the \"+NNNms\" will show you how much time was spent between calls.\n\n  ![](http://f.cl.ly/items/2i3h1d3t121M2Z1A3Q0N/Screenshot.png)\n\n  When stderr is not a TTY, `Date#toUTCString()` is used, making it more useful for logging the debug information as shown below:\n  _(NOTE: Debug now uses stderr instead of stdout, so the correct shell command for this example is actually `DEBUG=* node example/worker 2> out &`)_\n  \n  ![](http://f.cl.ly/items/112H3i0e0o0P0a2Q2r11/Screenshot.png)\n  \n## Conventions\n\n If you're using this in one or more of your libraries, you _should_ use the name of your library so that developers may toggle debugging as desired without guessing names. If you have more than one debuggers you _should_ prefix them with your library name and use \":\" to separate features. For example \"bodyParser\" from Connect would then be \"connect:bodyParser\". \n\n## Wildcards\n\n  The \"*\" character may be used as a wildcard. Suppose for example your library has debuggers named \"connect:bodyParser\", \"connect:compress\", \"connect:session\", instead of listing all three with `DEBUG=connect:bodyParser,connect.compress,connect:session`, you may simply do `DEBUG=connect:*`, or to run everything using this module simply use `DEBUG=*`.\n\n  You can also exclude specific debuggers by prefixing them with a \"-\" character.  For example, `DEBUG=* -connect:*` would include all debuggers except those starting with \"connect:\".\n\n## Browser support\n\n Debug works in the browser as well, currently persisted by `localStorage`. For example if you have `worker:a` and `worker:b` as shown below, and wish to debug both type `debug.enable('worker:*')` in the console and refresh the page, this will remain until you disable with `debug.disable()`. \n\n```js\na = debug('worker:a');\nb = debug('worker:b');\n\nsetInterval(function(){\n  a('doing some work');\n}, 1000);\n\nsetInterval(function(){\n  a('doing some work');\n}, 1200);\n```\n\n## License \n\n(The MIT License)\n\nCopyright (c) 2011 TJ Holowaychuk &lt;tj@vision-media.ca&gt;\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n'Software'), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n",
+  "readmeFilename": "Readme.md",
+  "scripts": {}
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/.jscsrc b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/.jscsrc
new file mode 100644
index 0000000..06245e6
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/.jscsrc
@@ -0,0 +1,64 @@
+{
+  "excludeFiles": [
+    "./js/forge.bundle.js",
+    "./js/forge.min.js",
+    "./js/jsbn.js",
+    "./nodejs/ui/require.js",
+    "./nodejs/ui/test.min.js"
+  ],
+  "disallowKeywords": ["with"],
+  "disallowKeywordsOnNewLine": ["else", "catch"],
+  // FIXME: enable this?
+  //"disallowImplicitTypeConversion": ["string"],
+  "disallowMixedSpacesAndTabs": true,
+  "disallowMultipleLineBreaks": true,
+  // FIXME: enable this or do we prefer to
+  // use w/angular directive templates?
+  //"disallowMultipleLineStrings": true,
+  "disallowNewlineBeforeBlockStatements": true,
+  "disallowSpaceAfterObjectKeys": true,
+  "disallowSpaceAfterPrefixUnaryOperators": ["++", "--", "+", "-", "~", "!"],
+  "disallowSpaceBeforeBinaryOperators": [","],
+  "disallowSpaceBeforePostfixUnaryOperators": ["++", "--"],
+  "disallowSpacesInAnonymousFunctionExpression": {
+    "beforeOpeningRoundBrace": true
+  },
+  "disallowSpacesInFunctionDeclaration": {
+    "beforeOpeningRoundBrace": true
+  },
+  "disallowSpacesInNamedFunctionExpression": {
+    "beforeOpeningRoundBrace": true
+  },
+  "disallowSpacesInsideParentheses": true,
+  "disallowTrailingComma": true,
+  "disallowTrailingWhitespace": true,
+  "requireCommaBeforeLineBreak": true,
+  "requireCurlyBraces": ["if", "else", "for", "while", "do", "try", "catch"],
+  "requireLineFeedAtFileEnd": true,
+  "requireSpaceAfterBinaryOperators": ["?", ":", "+", "-", "/", "*", "%", "==", "===", "!=", "!==", ">", ">=", "<", "<=", "&&", "||"],
+  "requireSpaceBeforeBinaryOperators": ["?", ":", "+", "-", "/", "*", "%", "==", "===", "!=", "!==", ">", ">=", "<", "<=", "&&", "||"],
+  "requireSpaceAfterKeywords": [
+    "else",
+    "do",
+    "return",
+    "try"
+  ],
+  "requireSpaceBeforeBlockStatements": true,
+  "requireSpacesInConditionalExpression": {
+    "afterTest": true,
+    "beforeConsequent": true,
+    "afterConsequent": true,
+    "beforeAlternate": true
+  },
+  "requireSpacesInFunction": {
+    "beforeOpeningCurlyBrace": true
+  },
+  "safeContextKeyword": "self",
+  "validateLineBreaks": "LF",
+  // FIXME: enable doc checks (update to use newer jscs jsdoc module)
+  //"validateJSDoc": {
+  //  "checkParamNames": true,
+  //  "requireParamTypes": true
+  //},
+  "validateParameterSeparator": ", "
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/.jshintignore b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/.jshintignore
new file mode 100644
index 0000000..c50f474
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/.jshintignore
@@ -0,0 +1,6 @@
+js/forge.bundle.js
+minify.js
+nodejs/build.js
+nodejs/minify.js
+nodejs/ui/require.js
+nodejs/ui/test.min.js
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/.jshintrc b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/.jshintrc
new file mode 100644
index 0000000..26d261c
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/.jshintrc
@@ -0,0 +1,3 @@
+{
+  "sub": true
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/.npmignore b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/.npmignore
new file mode 100644
index 0000000..50980d3
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/.npmignore
@@ -0,0 +1,22 @@
+*.py[co]
+*.sw[nop]
+*~
+.bower.json
+.cdtproject
+.classpath
+.cproject
+.project
+.settings
+Makefile
+TAGS
+aclocal.m4
+autom4te.cache
+build
+config.log
+config.status
+configure
+dist
+js/forge.bundle.js
+js/forge.min.js
+node_modules
+tests/forge
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/.travis.yml b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/.travis.yml
new file mode 100644
index 0000000..c4aa766
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/.travis.yml
@@ -0,0 +1,10 @@
+language: node_js
+node_js:
+  - "0.10"
+install: (cd nodejs && npm install)
+script: 
+  - (cd nodejs && npm test)
+notifications:
+  email:
+    on_success: change
+    on_failure: change
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/HACKING.md b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/HACKING.md
new file mode 100644
index 0000000..762dfe1
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/HACKING.md
@@ -0,0 +1,37 @@
+Hacking on forge
+================
+
+Want to hack on forge? Great! Here are a few notes:
+
+Code
+----
+
+* In general, follow a common [Node.js Style Guide][].
+* Use version X.Y.Z-dev in dev mode.
+* Use version X.Y.Z for releases.
+
+Versioning
+----------
+
+* Follow the [Semantic Versioning][] guidelines.
+
+Release Process
+---------------
+
+* commit changes
+* `$EDITOR package.json`: update to release version and remove `-dev` suffix.
+* `git commit package.json -m "Release {version}."`
+* `git tag {version}`
+* `$EDITOR package.json`: update to next version and add `-dev` suffix.
+* `git commit package.json -m "Start {next-version}."`
+* `git push`
+* `git push --tags`
+
+To ensure a clean upload, use a clean updated checkout, and run the following:
+
+* `git checkout {version}`
+* `npm publish`
+
+[Node.js Style Guide]: http://nodeguide.com/style.html
+[jshint]: http://www.jshint.com/install/
+[Semantic Versioning]: http://semver.org/
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/LICENSE b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/LICENSE
new file mode 100644
index 0000000..1bba5ce
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/LICENSE
@@ -0,0 +1,331 @@
+You may use the Forge project under the terms of either the BSD License or the 
+GNU General Public License (GPL) Version 2.
+
+The BSD License is recommended for most projects. It is simple and easy to 
+understand and it places almost no restrictions on what you can do with the
+Forge project.
+
+If the GPL suits your project better you are also free to use Forge under 
+that license.
+
+You don't have to do anything special to choose one license or the other and
+you don't have to notify anyone which license you are using. You are free to
+use this project in commercial projects as long as the copyright header is
+left intact.
+
+If you are a commercial entity and use this set of libraries in your
+commercial software then reasonable payment to Digital Bazaar, if you can
+afford it, is not required but is expected and would be appreciated. If this
+library saves you time, then it's saving you money. The cost of developing
+the Forge software was on the order of several hundred hours and tens of
+thousands of dollars. We are attempting to strike a balance between helping
+the development community while not being taken advantage of by lucrative
+commercial entities for our efforts.
+
+-------------------------------------------------------------------------------
+New BSD License (3-clause)
+Copyright (c) 2010, Digital Bazaar, Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of Digital Bazaar, Inc. nor the
+      names of its contributors may be used to endorse or promote products
+      derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL DIGITAL BAZAAR BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+-------------------------------------------------------------------------------
+        GNU GENERAL PUBLIC LICENSE
+           Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+          Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+        GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+          NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/Makefile.in b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/Makefile.in
new file mode 100644
index 0000000..eb4495e
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/Makefile.in
@@ -0,0 +1,110 @@
+# Makefile for Forge
+
+# Top-level build and dist dir
+BUILD_DIR=@FORGE_DIR@/build
+TOP_DIST_DIR=@FORGE_DIR@/dist
+DIST_DIR=$(TOP_DIST_DIR)/forge
+
+_FLASH := $(DIST_DIR)/SocketPool.swf
+ifeq (@BUILD_FLASH@,yes)
+FLASH := $(_FLASH)
+else
+ifeq (@USE_PRE_BUILT_FLASH@,yes)
+FLASH := $(_FLASH)
+endif
+endif
+JS_SOURCES := $(wildcard js/*.js)
+JS_DIST := $(JS_SOURCES:js/%.js=$(DIST_DIR)/%.js)
+JS_DIST_MIN := $(JS_DIST:%.js=%.min.js)
+TESTS_FORGE_LINK := @FORGE_DIR@/tests/forge
+
+ifeq (@BUILD_PYTHON_MODULES@,yes)
+SSL_SESSIONS_DIR = \
+	$(TOP_DIST_DIR)/forge_ssl/lib/python@PYTHON_VERSION@/site-packages
+SSL_SESSIONS_FILES = \
+	$(SSL_SESSIONS_DIR)/_forge_ssl.so \
+	$(SSL_SESSIONS_DIR)/forge/ssl.py
+endif
+
+# Whether or not to print commands as they are being executed, helpful for
+# debugging the build system.
+ifdef PRINT_COMMANDS
+PCMD=
+else
+PCMD=@
+endif
+
+.PHONY: all build-all update-all verbose clean verbose-commands
+
+# debug flags for flash build
+ifeq (@MXMLC_DEBUG_MODE@,yes)
+FLASH_FLAGS = \
+	-debug=true \
+	-define=CONFIG::debugging,true \
+	-define=CONFIG::release,false
+else
+FLASH_FLAGS = \
+	-debug=false \
+	-define=CONFIG::debugging,false \
+	-define=CONFIG::release,true
+endif
+
+all: $(BUILD_DIR) $(DIST_DIR) $(FLASH) $(JS_DIST) $(TESTS_FORGE_LINK) $(SSL_SESSIONS_FILES)
+	@echo "forge build complete."
+
+build-all: all
+
+update-all:
+	@git pull && ./build-setup && make all
+
+$(BUILD_DIR):
+	$(PCMD) mkdir -p $@
+$(DIST_DIR):
+	$(PCMD) mkdir -p $@
+
+ifeq (@BUILD_FLASH@,yes)
+$(DIST_DIR)/SocketPool.swf: flash/SocketPool.as flash/PooledSocket.as flash/SocketEvent.as
+	@echo "Building $@..."
+	$(PCMD) @MXMLC@ $(FLASH_FLAGS) \
+		-load-config+=build-flash.xml \
+		-output=$@ $<
+else
+ifeq (@USE_PRE_BUILT_FLASH@,yes)
+$(DIST_DIR)/SocketPool.swf: @FORGE_DIR@/swf/SocketPool.swf
+	@echo "Copying pre-built $(@F)..."
+	$(PCMD) cp $< $@
+endif
+endif
+
+$(DIST_DIR)/%.js: js/%.js
+	@echo "Linking $@..."
+	$(PCMD) ln -sf $(realpath $<) $@
+
+$(TESTS_FORGE_LINK): $(DIST_DIR)
+	@echo "Linking $@..."
+	$(PCMD) ln -sf $(realpath $<) $@
+
+ifeq (@BUILD_PYTHON_MODULES@,yes)
+$(SSL_SESSIONS_DIR)/_forge_ssl.so: \
+	@FORGE_DIR@/tests/forge_ssl/forge/_ssl.c \
+	@FORGE_DIR@/tests/forge_ssl/forge/socketmodule.h \
+	@FORGE_DIR@/tests/forge_ssl/setup.py
+$(SSL_SESSIONS_DIR)/forge/ssl.py: \
+	@FORGE_DIR@/tests/forge_ssl/forge/ssl.py \
+	@FORGE_DIR@/tests/forge_ssl/setup.py
+	(cd @FORGE_DIR@/tests/forge_ssl && \
+	@PYTHON@ setup.py \
+	   build --build-base $(BUILD_DIR) \
+	   install --prefix=$(TOP_DIST_DIR)/forge_ssl)
+	@# fix distutils timestamp issue
+	@# (sub-seconds of source file are truncated on target so rebuild is
+	@# always triggered)
+	@touch $@
+endif
+
+clean:
+	$(PCMD) rm -rf $(BUILD_DIR) $(TOP_DIST_DIR)
+	@echo "Removed all generated files."
+
+verbose-commands:
+	PRINT_COMMANDS=true $(MAKE) all
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/README.md b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/README.md
new file mode 100644
index 0000000..00eb178
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/README.md
@@ -0,0 +1,1782 @@
+# Forge
+
+[![Build Status][travis-ci-png]][travis-ci-site]
+[travis-ci-png]: https://travis-ci.org/digitalbazaar/forge.png?branch=master
+[travis-ci-site]: https://travis-ci.org/digitalbazaar/forge
+
+A native implementation of [TLS][] (and various other cryptographic tools) in
+[JavaScript][].
+
+## Introduction
+
+The Forge software is a fully native implementation of the [TLS][] protocol in
+JavaScript as well as a set of tools for developing Web Apps that utilize many
+network resources.
+
+## Performance
+
+Forge is fast. Benchmarks against other popular JavaScript cryptography
+libraries can be found here:
+
+http://dominictarr.github.io/crypto-bench/
+
+http://cryptojs.altervista.org/test/simulate-threading-speed_test.html
+
+## Getting Started
+------------------
+
+### Node.js ###
+
+If you want to use forge with [node.js][], it is available through `npm`:
+
+https://npmjs.org/package/node-forge
+
+Installation:
+
+    npm install node-forge
+
+You can then use forge as a regular module:
+
+    var forge = require('node-forge');
+
+### Requirements ###
+
+* General
+  * Optional: GNU autotools for the build infrastructure if using Flash.
+* Building a Browser Bundle:
+  * nodejs
+  * npm
+* Testing
+  * nodejs
+  * Optional: Python and OpenSSL development environment to build
+  * a special SSL module with session cache support for testing with flash.
+  * http://www.python.org/dev/
+  * http://www.openssl.org/
+  * Debian users should install python-dev and libssl-dev.
+* Optional: Flash
+  * A pre-built SocketPool.swf is included.
+  * Adobe Flex 3 SDK to build the Flash socket code.
+  * http://opensource.adobe.com/wiki/display/flexsdk/
+
+### Building a browser bundle ###
+
+To create a minimized JavaScript bundle, run the following:
+
+```
+npm install
+npm run minify
+```
+
+This will create a single minimized file that can be included in
+the browser:
+
+```
+js/forge.min.js
+```
+
+Include the file via:
+
+```html
+<script src="js/forge.min.js"></script>
+```
+
+Note that the minify script depends on the requirejs package,
+and that the requirejs binary 'r.js' assumes that the name of
+the node binary is 'node' not 'nodejs', as it is on some
+systems. You may need to change the hashbang line to use
+'nodejs' or run the command manually.
+
+To create a single non-minimized file that can be included in
+the browser:
+
+```
+npm install
+npm run bundle
+```
+
+This will create:
+
+```
+js/forge.bundle.js
+```
+
+Include the file via:
+
+```html
+<script src="js/forge.bundle.js"></script>
+```
+
+The above bundles will synchronously create a global 'forge' object.
+
+Keep in mind that these bundles will not include any WebWorker
+scripts (eg: prime.worker.js) or their dependencies, so these will
+need to be accessible from the browser if any WebWorkers are used.
+
+### Testing with NodeJS & RequireJS ###
+
+A test server for [node.js][] can be found at `./nodejs`. The following are included:
+
+  * Example of how to use `forge` within NodeJS in the form of a [mocha](http://visionmedia.github.io/mocha/) test.
+  * Example of how to serve `forge` to the browser using [RequireJS](http://requirejs.org/).
+
+To run:
+
+    cd nodejs
+    npm install
+    npm test
+    npm start
+
+
+### Old build system that includes flash support ###
+
+To build the whole project, including Flash, run the following:
+
+    $ ./build-setup
+    $ make
+
+This will create the SWF, symlink all the JavaScript files, and build a Python
+SSL module for testing. To see configure options, run `./configure --help`.
+
+### Old test system including flash support ###
+
+A test server is provided which can be run in TLS mode and non-TLS mode. Use
+the --help option to get help for configuring ports. The server will print out
+the local URL you can vist to run tests.
+
+Some of the simplier tests should be run with just the non-TLS server::
+
+    $ ./tests/server.py
+
+More advanced tests need TLS enabled::
+
+    $ ./tests/server.py --tls
+
+## Contributing
+---------------
+
+Any contributions (eg: PRs) that are accepted will be brought under the same
+license used by the rest of the Forge project. This license allows Forge to
+be used under the terms of either the BSD License or the GNU General Public
+License (GPL) Version 2.
+
+See: [LICENSE](https://github.com/digitalbazaar/forge/blob/cbebca3780658703d925b61b2caffb1d263a6c1d/LICENSE)
+
+If a contribution contains 3rd party source code with its own license, it
+may retain it, so long as that license is compatible with the Forge license.
+
+## Documentation
+----------------
+
+### Transports
+
+* [TLS](#tls)
+* [HTTP](#http)
+* [SSH](#ssh)
+* [XHR](#xhr)
+* [Sockets](#socket)
+
+### Ciphers
+
+* [CIPHER](#cipher)
+* [AES](#aes)
+* [DES](#des)
+* [RC2](#rc2)
+
+### PKI
+
+* [RSA](#rsa)
+* [RSA-KEM](#rsakem)
+* [X.509](#x509)
+* [PKCS#5](#pkcs5)
+* [PKCS#7](#pkcs7)
+* [PKCS#8](#pkcs8)
+* [PKCS#10](#pkcs10)
+* [PKCS#12](#pkcs12)
+* [ASN.1](#asn)
+
+### Message Digests
+
+* [SHA1](#sha1)
+* [SHA256](#sha256)
+* [SHA384](#sha384)
+* [SHA512](#sha512)
+* [MD5](#md5)
+* [HMAC](#hmac)
+
+### Utilities
+
+* [Prime](#prime)
+* [PRNG](#prng)
+* [Tasks](#task)
+* [Utilities](#util)
+* [Logging](#log)
+* [Debugging](#debug)
+* [Flash Socket Policy Module](#fsp)
+
+---------------------------------------
+
+If at any time you wish to disable the use of native code, where available,
+for particular forge features like its secure random number generator, you
+may set the ```disableNativeCode``` flag on ```forge``` to ```true```. It
+is not recommended that you set this flag as native code is typically more
+performant and may have stronger security properties. It may be useful to
+set this flag to test certain features that you plan to run in environments
+that are different from your testing environment.
+
+To disable native code when including forge in the browser:
+
+```js
+forge = {disableNativeCode: true};
+// now include forge script file(s)
+// Note: with this approach, script files *must*
+// be included after initializing the global forge var
+
+// alternatively, include script files first and then call
+forge = forge({disableNativeCode: true});
+
+// Note: forge will be permanently reconfigured now;
+// to avoid this but use the same "forge" var name,
+// you can wrap your code in a function to shadow the
+// global var, eg:
+(function(forge) {
+  // ...
+})(forge({disableNativeCode: true}));
+```
+
+To disable native code when using node.js:
+
+```js
+var forge = require('node-forge')({disableNativeCode: true});
+```
+
+---------------------------------------
+## Transports
+
+<a name="tls" />
+### TLS
+
+Provides a native javascript client and server-side [TLS][] implementation.
+
+__Examples__
+
+```js
+// create TLS client
+var client = forge.tls.createConnection({
+  server: false,
+  caStore: /* Array of PEM-formatted certs or a CA store object */,
+  sessionCache: {},
+  // supported cipher suites in order of preference
+  cipherSuites: [
+    forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+    forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+  virtualHost: 'example.com',
+  verify: function(connection, verified, depth, certs) {
+    if(depth === 0) {
+      var cn = certs[0].subject.getField('CN').value;
+      if(cn !== 'example.com') {
+        verified = {
+          alert: forge.tls.Alert.Description.bad_certificate,
+          message: 'Certificate common name does not match hostname.'
+        };
+      }
+    }
+    return verified;
+  },
+  connected: function(connection) {
+    console.log('connected');
+    // send message to server
+    connection.prepare(forge.util.encodeUtf8('Hi server!'));
+    /* NOTE: experimental, start heartbeat retransmission timer
+    myHeartbeatTimer = setInterval(function() {
+      connection.prepareHeartbeatRequest(forge.util.createBuffer('1234'));
+    }, 5*60*1000);*/
+  },
+  /* provide a client-side cert if you want
+  getCertificate: function(connection, hint) {
+    return myClientCertificate;
+  },
+  /* the private key for the client-side cert if provided */
+  getPrivateKey: function(connection, cert) {
+    return myClientPrivateKey;
+  },
+  tlsDataReady: function(connection) {
+    // TLS data (encrypted) is ready to be sent to the server
+    sendToServerSomehow(connection.tlsData.getBytes());
+    // if you were communicating with the server below, you'd do:
+    // server.process(connection.tlsData.getBytes());
+  },
+  dataReady: function(connection) {
+    // clear data from the server is ready
+    console.log('the server sent: ' +
+      forge.util.decodeUtf8(connection.data.getBytes()));
+    // close connection
+    connection.close();
+  },
+  /* NOTE: experimental
+  heartbeatReceived: function(connection, payload) {
+    // restart retransmission timer, look at payload
+    clearInterval(myHeartbeatTimer);
+    myHeartbeatTimer = setInterval(function() {
+      connection.prepareHeartbeatRequest(forge.util.createBuffer('1234'));
+    }, 5*60*1000);
+    payload.getBytes();
+  },*/
+  closed: function(connection) {
+    console.log('disconnected');
+  },
+  error: function(connection, error) {
+    console.log('uh oh', error);
+  }
+});
+
+// start the handshake process
+client.handshake();
+
+// when encrypted TLS data is received from the server, process it
+client.process(encryptedBytesFromServer);
+
+// create TLS server
+var server = forge.tls.createConnection({
+  server: true,
+  caStore: /* Array of PEM-formatted certs or a CA store object */,
+  sessionCache: {},
+  // supported cipher suites in order of preference
+  cipherSuites: [
+    forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+    forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+  // require a client-side certificate if you want
+  verifyClient: true,
+  verify: function(connection, verified, depth, certs) {
+    if(depth === 0) {
+      var cn = certs[0].subject.getField('CN').value;
+      if(cn !== 'the-client') {
+        verified = {
+          alert: forge.tls.Alert.Description.bad_certificate,
+          message: 'Certificate common name does not match expected client.'
+        };
+      }
+    }
+    return verified;
+  },
+  connected: function(connection) {
+    console.log('connected');
+    // send message to client
+    connection.prepare(forge.util.encodeUtf8('Hi client!'));
+    /* NOTE: experimental, start heartbeat retransmission timer
+    myHeartbeatTimer = setInterval(function() {
+      connection.prepareHeartbeatRequest(forge.util.createBuffer('1234'));
+    }, 5*60*1000);*/
+  },
+  getCertificate: function(connection, hint) {
+    return myServerCertificate;
+  },
+  getPrivateKey: function(connection, cert) {
+    return myServerPrivateKey;
+  },
+  tlsDataReady: function(connection) {
+    // TLS data (encrypted) is ready to be sent to the client
+    sendToClientSomehow(connection.tlsData.getBytes());
+    // if you were communicating with the client above you'd do:
+    // client.process(connection.tlsData.getBytes());
+  },
+  dataReady: function(connection) {
+    // clear data from the client is ready
+    console.log('the client sent: ' +
+      forge.util.decodeUtf8(connection.data.getBytes()));
+    // close connection
+    connection.close();
+  },
+  /* NOTE: experimental
+  heartbeatReceived: function(connection, payload) {
+    // restart retransmission timer, look at payload
+    clearInterval(myHeartbeatTimer);
+    myHeartbeatTimer = setInterval(function() {
+      connection.prepareHeartbeatRequest(forge.util.createBuffer('1234'));
+    }, 5*60*1000);
+    payload.getBytes();
+  },*/
+  closed: function(connection) {
+    console.log('disconnected');
+  },
+  error: function(connection, error) {
+    console.log('uh oh', error);
+  }
+});
+
+// when encrypted TLS data is received from the client, process it
+server.process(encryptedBytesFromClient);
+```
+
+Connect to a TLS server using node's net.Socket:
+
+```js
+var socket = new net.Socket();
+
+var client = forge.tls.createConnection({
+  server: false,
+  verify: function(connection, verified, depth, certs) {
+    // skip verification for testing
+    console.log('[tls] server certificate verified');
+    return true;
+  },
+  connected: function(connection) {
+    console.log('[tls] connected');
+    // prepare some data to send (note that the string is interpreted as
+    // 'binary' encoded, which works for HTTP which only uses ASCII, use
+    // forge.util.encodeUtf8(str) otherwise
+    client.prepare('GET / HTTP/1.0\r\n\r\n');
+  },
+  tlsDataReady: function(connection) {
+    // encrypted data is ready to be sent to the server
+    var data = connection.tlsData.getBytes();
+    socket.write(data, 'binary'); // encoding should be 'binary'
+  },
+  dataReady: function(connection) {
+    // clear data from the server is ready
+    var data = connection.data.getBytes();
+    console.log('[tls] data received from the server: ' + data);
+  },
+  closed: function() {
+    console.log('[tls] disconnected');
+  },
+  error: function(connection, error) {
+    console.log('[tls] error', error);
+  }
+});
+
+socket.on('connect', function() {
+  console.log('[socket] connected');
+  client.handshake();
+});
+socket.on('data', function(data) {
+  client.process(data.toString('binary')); // encoding should be 'binary'
+});
+socket.on('end', function() {
+  console.log('[socket] disconnected');
+});
+
+// connect to google.com
+socket.connect(443, 'google.com');
+
+// or connect to gmail's imap server (but don't send the HTTP header above)
+//socket.connect(993, 'imap.gmail.com');
+```
+
+<a name="http" />
+### HTTP
+
+Provides a native [JavaScript][] mini-implementation of an http client that
+uses pooled sockets.
+
+__Examples__
+
+```js
+// create an HTTP GET request
+var request = forge.http.createRequest({method: 'GET', path: url.path});
+
+// send the request somewhere
+sendSomehow(request.toString());
+
+// receive response
+var buffer = forge.util.createBuffer();
+var response = forge.http.createResponse();
+var someAsyncDataHandler = function(bytes) {
+  if(!response.bodyReceived) {
+    buffer.putBytes(bytes);
+    if(!response.headerReceived) {
+      if(response.readHeader(buffer)) {
+        console.log('HTTP response header: ' + response.toString());
+      }
+    }
+    if(response.headerReceived && !response.bodyReceived) {
+      if(response.readBody(buffer)) {
+        console.log('HTTP response body: ' + response.body);
+      }
+    }
+  }
+};
+```
+
+<a name="ssh" />
+### SSH
+
+Provides some SSH utility functions.
+
+__Examples__
+
+```js
+// encodes (and optionally encrypts) a private RSA key as a Putty PPK file
+forge.ssh.privateKeyToPutty(privateKey, passphrase, comment);
+
+// encodes a public RSA key as an OpenSSH file
+forge.ssh.publicKeyToOpenSSH(key, comment);
+
+// encodes a private RSA key as an OpenSSH file
+forge.ssh.privateKeyToOpenSSH(privateKey, passphrase);
+
+// gets the SSH public key fingerprint in a byte buffer
+forge.ssh.getPublicKeyFingerprint(key);
+
+// gets a hex-encoded, colon-delimited SSH public key fingerprint
+forge.ssh.getPublicKeyFingerprint(key, {encoding: 'hex', delimiter: ':'});
+```
+
+<a name="xhr" />
+### XHR
+
+Provides an XmlHttpRequest implementation using forge.http as a backend.
+
+__Examples__
+
+```js
+```
+
+<a name="socket" />
+### Sockets
+
+Provides an interface to create and use raw sockets provided via Flash.
+
+__Examples__
+
+```js
+```
+
+---------------------------------------
+## Ciphers
+
+<a name="cipher" />
+### CIPHER
+
+Provides a basic API for block encryption and decryption. There is built-in
+support for the ciphers: [AES][], [3DES][], and [DES][], and for the modes
+of operation: [ECB][], [CBC][], [CFB][], [OFB][], [CTR][], and [GCM][].
+
+These algorithms are currently supported:
+
+* AES-CBC
+* AES-CFB
+* AES-OFB
+* AES-CTR
+* AES-GCM
+* 3DES-ECB
+* 3DES-CBC
+* DES-ECB
+* DES-CBC
+
+When using an [AES][] algorithm, the key size will determine whether
+AES-128, AES-192, or AES-256 is used (all are supported). When a [DES][]
+algorithm is used, the key size will determine whether [3DES][] or regular
+[DES][] is used. Use a [3DES][] algorithm to enforce Triple-DES.
+
+__Examples__
+
+```js
+// generate a random key and IV
+// Note: a key size of 16 bytes will use AES-128, 24 => AES-192, 32 => AES-256
+var key = forge.random.getBytesSync(16);
+var iv = forge.random.getBytesSync(16);
+
+/* alternatively, generate a password-based 16-byte key
+var salt = forge.random.getBytesSync(128);
+var key = forge.pkcs5.pbkdf2('password', salt, numIterations, 16);
+*/
+
+// encrypt some bytes using CBC mode
+// (other modes include: CFB, OFB, CTR, and GCM)
+var cipher = forge.cipher.createCipher('AES-CBC', key);
+cipher.start({iv: iv});
+cipher.update(forge.util.createBuffer(someBytes));
+cipher.finish();
+var encrypted = cipher.output;
+// outputs encrypted hex
+console.log(encrypted.toHex());
+
+// decrypt some bytes using CBC mode
+// (other modes include: CFB, OFB, CTR, and GCM)
+var decipher = forge.cipher.createDecipher('AES-CBC', key);
+decipher.start({iv: iv});
+decipher.update(encrypted);
+decipher.finish();
+// outputs decrypted hex
+console.log(decipher.output.toHex());
+
+// encrypt some bytes using GCM mode
+var cipher = forge.cipher.createCipher('AES-GCM', key);
+cipher.start({
+  iv: iv, // should be a 12-byte binary-encoded string or byte buffer
+  additionalData: 'binary-encoded string', // optional
+  tagLength: 128 // optional, defaults to 128 bits
+});
+cipher.update(forge.util.createBuffer(someBytes));
+cipher.finish();
+var encrypted = cipher.output;
+var tag = cipher.mode.tag;
+// outputs encrypted hex
+console.log(encrypted.toHex());
+// outputs authentication tag
+console.log(tag.toHex());
+
+// decrypt some bytes using GCM mode
+var decipher = forge.cipher.createDecipher('AES-GCM', key);
+decipher.start({
+  iv: iv,
+  additionalData: 'binary-encoded string', // optional
+  tagLength: 128, // optional, defaults to 128 bits
+  tag: tag // authentication tag from encryption
+});
+decipher.update(encrypted);
+var pass = decipher.finish();
+// pass is false if there was a failure (eg: authentication tag didn't match)
+if(pass) {
+  // outputs decrypted hex
+  console.log(decipher.output.toHex());
+}
+```
+
+Using forge in node.js to match openssl's "enc" command line tool (**Note**: OpenSSL "enc" uses a non-standard file format with a custom key derivation function and a fixed iteration count of 1, which some consider less secure than alternatives such as [OpenPGP](https://tools.ietf.org/html/rfc4880)/[GnuPG](https://www.gnupg.org/)):
+
+```js
+var forge = require('node-forge');
+var fs = require('fs');
+
+// openssl enc -des3 -in input.txt -out input.enc
+function encrypt(password) {
+  var input = fs.readFileSync('input.txt', {encoding: 'binary'});
+
+  // 3DES key and IV sizes
+  var keySize = 24;
+  var ivSize = 8;
+
+  // get derived bytes
+  // Notes:
+  // 1. If using an alternative hash (eg: "-md sha1") pass
+  //   "forge.md.sha1.create()" as the final parameter.
+  // 2. If using "-nosalt", set salt to null.
+  var salt = forge.random.getBytesSync(8);
+  // var md = forge.md.sha1.create(); // "-md sha1"
+  var derivedBytes = forge.pbe.opensslDeriveBytes(
+    password, salt, keySize + ivSize/*, md*/);
+  var buffer = forge.util.createBuffer(derivedBytes);
+  var key = buffer.getBytes(keySize);
+  var iv = buffer.getBytes(ivSize);
+
+  var cipher = forge.cipher.createCipher('3DES-CBC', key);
+  cipher.start({iv: iv});
+  cipher.update(forge.util.createBuffer(input, 'binary'));
+  cipher.finish();
+
+  var output = forge.util.createBuffer();
+
+  // if using a salt, prepend this to the output:
+  if(salt !== null) {
+    output.putBytes('Salted__'); // (add to match openssl tool output)
+    output.putBytes(salt);
+  }
+  output.putBuffer(cipher.output);
+
+  fs.writeFileSync('input.enc', output.getBytes(), {encoding: 'binary'});
+}
+
+// openssl enc -d -des3 -in input.enc -out input.dec.txt
+function decrypt(password) {
+  var input = fs.readFileSync('input.enc', {encoding: 'binary'});
+
+  // parse salt from input
+  input = forge.util.createBuffer(input, 'binary');
+  // skip "Salted__" (if known to be present)
+  input.getBytes('Salted__'.length);
+  // read 8-byte salt
+  var salt = input.getBytes(8);
+
+  // Note: if using "-nosalt", skip above parsing and use
+  // var salt = null;
+
+  // 3DES key and IV sizes
+  var keySize = 24;
+  var ivSize = 8;
+
+  var derivedBytes = forge.pbe.opensslDeriveBytes(
+    password, salt, keySize + ivSize);
+  var buffer = forge.util.createBuffer(derivedBytes);
+  var key = buffer.getBytes(keySize);
+  var iv = buffer.getBytes(ivSize);
+
+  var decipher = forge.cipher.createDecipher('3DES-CBC', key);
+  decipher.start({iv: iv});
+  decipher.update(input);
+  var result = decipher.finish(); // check 'result' for true/false
+
+  fs.writeFileSync(
+    'input.dec.txt', decipher.output.getBytes(), {encoding: 'binary'});
+}
+```
+
+<a name="aes" />
+### AES
+
+Provides [AES][] encryption and decryption in [CBC][], [CFB][], [OFB][],
+[CTR][], and [GCM][] modes. See [CIPHER](#cipher) for examples.
+
+<a name="des" />
+### DES
+
+Provides [3DES][] and [DES][] encryption and decryption in [ECB][] and
+[CBC][] modes. See [CIPHER](#cipher) for examples.
+
+<a name="rc2" />
+### RC2
+
+__Examples__
+
+```js
+// generate a random key and IV
+var key = forge.random.getBytesSync(16);
+var iv = forge.random.getBytesSync(8);
+
+// encrypt some bytes
+var cipher = forge.rc2.createEncryptionCipher(key);
+cipher.start(iv);
+cipher.update(forge.util.createBuffer(someBytes));
+cipher.finish();
+var encrypted = cipher.output;
+// outputs encrypted hex
+console.log(encrypted.toHex());
+
+// decrypt some bytes
+var cipher = forge.rc2.createDecryptionCipher(key);
+cipher.start(iv);
+cipher.update(encrypted);
+cipher.finish();
+// outputs decrypted hex
+console.log(cipher.output.toHex());
+```
+---------------------------------------
+## PKI
+
+Provides [X.509][] certificate and RSA public and private key encoding,
+decoding, encryption/decryption, and signing/verifying.
+
+<a name="rsa" />
+### RSA
+
+__Examples__
+
+```js
+var rsa = forge.pki.rsa;
+
+// generate an RSA key pair synchronously
+var keypair = rsa.generateKeyPair({bits: 2048, e: 0x10001});
+
+// generate an RSA key pair asynchronously (uses web workers if available)
+// use workers: -1 to run a fast core estimator to optimize # of workers
+rsa.generateKeyPair({bits: 2048, workers: 2}, function(err, keypair) {
+  // keypair.privateKey, keypair.publicKey
+});
+
+// generate an RSA key pair in steps that attempt to run for a specified period
+// of time on the main JS thread
+var state = rsa.createKeyPairGenerationState(2048, 0x10001);
+var step = function() {
+  // run for 100 ms
+  if(!rsa.stepKeyPairGenerationState(state, 100)) {
+    setTimeout(step, 1);
+  }
+  else {
+    // done, turn off progress indicator, use state.keys
+  }
+};
+// turn on progress indicator, schedule generation to run
+setTimeout(step);
+
+// sign data with a private key and output DigestInfo DER-encoded bytes
+// (defaults to RSASSA PKCS#1 v1.5)
+var md = forge.md.sha1.create();
+md.update('sign this', 'utf8');
+var signature = privateKey.sign(md);
+
+// verify data with a public key
+// (defaults to RSASSA PKCS#1 v1.5)
+var verified = publicKey.verify(md.digest().bytes(), signature);
+
+// sign data using RSASSA-PSS where PSS uses a SHA-1 hash, a SHA-1 based
+// masking function MGF1, and a 20 byte salt
+var md = forge.md.sha1.create();
+md.update('sign this', 'utf8');
+var pss = forge.pss.create({
+  md: forge.md.sha1.create(),
+  mgf: forge.mgf.mgf1.create(forge.md.sha1.create()),
+  saltLength: 20
+  // optionally pass 'prng' with a custom PRNG implementation
+  // optionalls pass 'salt' with a forge.util.ByteBuffer w/custom salt
+});
+var signature = privateKey.sign(md, pss);
+
+// verify RSASSA-PSS signature
+var pss = forge.pss.create({
+  md: forge.md.sha1.create(),
+  mgf: forge.mgf.mgf1.create(forge.md.sha1.create()),
+  saltLength: 20
+  // optionally pass 'prng' with a custom PRNG implementation
+});
+var md = forge.md.sha1.create();
+md.update('sign this', 'utf8');
+publicKey.verify(md.digest().getBytes(), signature, pss);
+
+// encrypt data with a public key (defaults to RSAES PKCS#1 v1.5)
+var encrypted = publicKey.encrypt(bytes);
+
+// decrypt data with a private key (defaults to RSAES PKCS#1 v1.5)
+var decrypted = privateKey.decrypt(encrypted);
+
+// encrypt data with a public key using RSAES PKCS#1 v1.5
+var encrypted = publicKey.encrypt(bytes, 'RSAES-PKCS1-V1_5');
+
+// decrypt data with a private key using RSAES PKCS#1 v1.5
+var decrypted = privateKey.decrypt(encrypted, 'RSAES-PKCS1-V1_5');
+
+// encrypt data with a public key using RSAES-OAEP
+var encrypted = publicKey.encrypt(bytes, 'RSA-OAEP');
+
+// decrypt data with a private key using RSAES-OAEP
+var decrypted = privateKey.decrypt(encrypted, 'RSA-OAEP');
+
+// encrypt data with a public key using RSAES-OAEP/SHA-256
+var encrypted = publicKey.encrypt(bytes, 'RSA-OAEP', {
+  md: forge.md.sha256.create()
+});
+
+// decrypt data with a private key using RSAES-OAEP/SHA-256
+var decrypted = privateKey.decrypt(encrypted, 'RSA-OAEP', {
+  md: forge.md.sha256.create()
+});
+
+// encrypt data with a public key using RSAES-OAEP/SHA-256/MGF1-SHA-1
+// compatible with Java's RSA/ECB/OAEPWithSHA-256AndMGF1Padding
+var encrypted = publicKey.encrypt(bytes, 'RSA-OAEP', {
+  md: forge.md.sha256.create(),
+  mgf1: {
+    md: forge.md.sha1.create()
+  }
+});
+
+// decrypt data with a private key using RSAES-OAEP/SHA-256/MGF1-SHA-1
+// compatible with Java's RSA/ECB/OAEPWithSHA-256AndMGF1Padding
+var decrypted = privateKey.decrypt(encrypted, 'RSA-OAEP', {
+  md: forge.md.sha256.create(),
+  mgf1: {
+    md: forge.md.sha1.create()
+  }
+});
+
+```
+
+<a name="rsakem" />
+### RSA-KEM
+
+__Examples__
+
+```js
+// generate an RSA key pair asynchronously (uses web workers if available)
+// use workers: -1 to run a fast core estimator to optimize # of workers
+forge.rsa.generateKeyPair({bits: 2048, workers: -1}, function(err, keypair) {
+  // keypair.privateKey, keypair.publicKey
+});
+
+// generate and encapsulate a 16-byte secret key
+var kdf1 = new forge.kem.kdf1(forge.md.sha1.create());
+var kem = forge.kem.rsa.create(kdf1);
+var result = kem.encrypt(keypair.publicKey, 16);
+// result has 'encapsulation' and 'key'
+
+// encrypt some bytes
+var iv = forge.random.getBytesSync(12);
+var someBytes = 'hello world!';
+var cipher = forge.cipher.createCipher('AES-GCM', result.key);
+cipher.start({iv: iv});
+cipher.update(forge.util.createBuffer(someBytes));
+cipher.finish();
+var encrypted = cipher.output.getBytes();
+var tag = cipher.mode.tag.getBytes();
+
+// send 'encrypted', 'iv', 'tag', and result.encapsulation to recipient
+
+// decrypt encapsulated 16-byte secret key
+var kdf1 = new forge.kem.kdf1(forge.md.sha1.create());
+var kem = forge.kem.rsa.create(kdf1);
+var key = kem.decrypt(keypair.privateKey, result.encapsulation, 16);
+
+// decrypt some bytes
+var decipher = forge.cipher.createDecipher('AES-GCM', key);
+decipher.start({iv: iv, tag: tag});
+decipher.update(forge.util.createBuffer(encrypted));
+var pass = decipher.finish();
+// pass is false if there was a failure (eg: authentication tag didn't match)
+if(pass) {
+  // outputs 'hello world!'
+  console.log(decipher.output.getBytes());
+}
+
+```
+
+<a name="x509" />
+### X.509
+
+__Examples__
+
+```js
+var pki = forge.pki;
+
+// convert a PEM-formatted public key to a Forge public key
+var publicKey = pki.publicKeyFromPem(pem);
+
+// convert a Forge public key to PEM-format
+var pem = pki.publicKeyToPem(publicKey);
+
+// convert an ASN.1 SubjectPublicKeyInfo to a Forge public key
+var publicKey = pki.publicKeyFromAsn1(subjectPublicKeyInfo);
+
+// convert a Forge public key to an ASN.1 SubjectPublicKeyInfo
+var subjectPublicKeyInfo = pki.publicKeyToAsn1(publicKey);
+
+// gets a SHA-1 RSAPublicKey fingerprint a byte buffer
+pki.getPublicKeyFingerprint(key);
+
+// gets a SHA-1 SubjectPublicKeyInfo fingerprint a byte buffer
+pki.getPublicKeyFingerprint(key, {type: 'SubjectPublicKeyInfo'});
+
+// gets a hex-encoded, colon-delimited SHA-1 RSAPublicKey public key fingerprint
+pki.getPublicKeyFingerprint(key, {encoding: 'hex', delimiter: ':'});
+
+// gets a hex-encoded, colon-delimited SHA-1 SubjectPublicKeyInfo public key fingerprint
+pki.getPublicKeyFingerprint(key, {
+  type: 'SubjectPublicKeyInfo',
+  encoding: 'hex',
+  delimiter: ':'
+});
+
+// gets a hex-encoded, colon-delimited MD5 RSAPublicKey public key fingerprint
+pki.getPublicKeyFingerprint(key, {
+  md: forge.md.md5.create(),
+  encoding: 'hex',
+  delimiter: ':'
+});
+
+// creates a CA store
+var caStore = pki.createCaStore([/* PEM-encoded cert */, ...]);
+
+// add a certificate to the CA store
+caStore.addCertificate(certObjectOrPemString);
+
+// gets the issuer (its certificate) for the given certificate
+var issuerCert = caStore.getIssuer(subjectCert);
+
+// verifies a certificate chain against a CA store
+pki.verifyCertificateChain(caStore, chain, customVerifyCallback);
+
+// signs a certificate using the given private key
+cert.sign(privateKey);
+
+// signs a certificate using SHA-256 instead of SHA-1
+cert.sign(privateKey, forge.md.sha256.create());
+
+// verifies an issued certificate using the certificates public key
+var verified = issuer.verify(issued);
+
+// generate a keypair and create an X.509v3 certificate
+var keys = pki.rsa.generateKeyPair(2048);
+var cert = pki.createCertificate();
+cert.publicKey = keys.publicKey;
+// alternatively set public key from a csr
+//cert.publicKey = csr.publicKey;
+cert.serialNumber = '01';
+cert.validity.notBefore = new Date();
+cert.validity.notAfter = new Date();
+cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1);
+var attrs = [{
+  name: 'commonName',
+  value: 'example.org'
+}, {
+  name: 'countryName',
+  value: 'US'
+}, {
+  shortName: 'ST',
+  value: 'Virginia'
+}, {
+  name: 'localityName',
+  value: 'Blacksburg'
+}, {
+  name: 'organizationName',
+  value: 'Test'
+}, {
+  shortName: 'OU',
+  value: 'Test'
+}];
+cert.setSubject(attrs);
+// alternatively set subject from a csr
+//cert.setSubject(csr.subject.attributes);
+cert.setIssuer(attrs);
+cert.setExtensions([{
+  name: 'basicConstraints',
+  cA: true
+}, {
+  name: 'keyUsage',
+  keyCertSign: true,
+  digitalSignature: true,
+  nonRepudiation: true,
+  keyEncipherment: true,
+  dataEncipherment: true
+}, {
+  name: 'extKeyUsage',
+  serverAuth: true,
+  clientAuth: true,
+  codeSigning: true,
+  emailProtection: true,
+  timeStamping: true
+}, {
+  name: 'nsCertType',
+  client: true,
+  server: true,
+  email: true,
+  objsign: true,
+  sslCA: true,
+  emailCA: true,
+  objCA: true
+}, {
+  name: 'subjectAltName',
+  altNames: [{
+    type: 6, // URI
+    value: 'http://example.org/webid#me'
+  }, {
+    type: 7, // IP
+    ip: '127.0.0.1'
+  }]
+}, {
+  name: 'subjectKeyIdentifier'
+}]);
+/* alternatively set extensions from a csr
+var extensions = csr.getAttribute({name: 'extensionRequest'}).extensions;
+// optionally add more extensions
+extensions.push.apply(extensions, [{
+  name: 'basicConstraints',
+  cA: true
+}, {
+  name: 'keyUsage',
+  keyCertSign: true,
+  digitalSignature: true,
+  nonRepudiation: true,
+  keyEncipherment: true,
+  dataEncipherment: true
+}]);
+cert.setExtensions(extensions);
+*/
+// self-sign certificate
+cert.sign(keys.privateKey);
+
+// convert a Forge certificate to PEM
+var pem = pki.certificateToPem(cert);
+
+// convert a Forge certificate from PEM
+var cert = pki.certificateFromPem(pem);
+
+// convert an ASN.1 X.509x3 object to a Forge certificate
+var cert = pki.certificateFromAsn1(obj);
+
+// convert a Forge certificate to an ASN.1 X.509v3 object
+var asn1Cert = pki.certificateToAsn1(cert);
+```
+
+<a name="pkcs5" />
+### PKCS#5
+
+Provides the password-based key-derivation function from [PKCS#5][].
+
+__Examples__
+
+```js
+// generate a password-based 16-byte key
+// note an optional message digest can be passed as the final parameter
+var salt = forge.random.getBytesSync(128);
+var derivedKey = forge.pkcs5.pbkdf2('password', salt, numIterations, 16);
+
+// generate key asynchronously
+// note an optional message digest can be passed before the callback
+forge.pkcs5.pbkdf2('password', salt, numIterations, 16, function(err, derivedKey) {
+  // do something w/derivedKey
+});
+```
+
+<a name="pkcs7" />
+### PKCS#7
+
+Provides cryptographically protected messages from [PKCS#7][].
+
+__Examples__
+
+```js
+// convert a message from PEM
+var p7 = forge.pkcs7.messageFromPem(pem);
+// look at p7.recipients
+
+// find a recipient by the issuer of a certificate
+var recipient = p7.findRecipient(cert);
+
+// decrypt
+p7.decrypt(p7.recipients[0], privateKey);
+
+// create a p7 enveloped message
+var p7 = forge.pkcs7.createEnvelopedData();
+
+// add a recipient
+var cert = forge.pki.certificateFromPem(certPem);
+p7.addRecipient(cert);
+
+// set content
+p7.content = forge.util.createBuffer('Hello');
+
+// encrypt
+p7.encrypt();
+
+// convert message to PEM
+var pem = forge.pkcs7.messageToPem(p7);
+
+// create a degenerate PKCS#7 certificate container
+// (CRLs not currently supported, only certificates)
+var p7 = forge.pkcs7.createSignedData();
+p7.addCertificate(certOrCertPem1);
+p7.addCertificate(certOrCertPem2);
+var pem = forge.pkcs7.messageToPem(p7);
+```
+
+<a name="pkcs8" />
+### PKCS#8
+
+__Examples__
+
+```js
+var pki = forge.pki;
+
+// convert a PEM-formatted private key to a Forge private key
+var privateKey = pki.privateKeyFromPem(pem);
+
+// convert a Forge private key to PEM-format
+var pem = pki.privateKeyToPem(privateKey);
+
+// convert an ASN.1 PrivateKeyInfo or RSAPrivateKey to a Forge private key
+var privateKey = pki.privateKeyFromAsn1(rsaPrivateKey);
+
+// convert a Forge private key to an ASN.1 RSAPrivateKey
+var rsaPrivateKey = pki.privateKeyToAsn1(privateKey);
+
+// wrap an RSAPrivateKey ASN.1 object in a PKCS#8 ASN.1 PrivateKeyInfo
+var privateKeyInfo = pki.wrapRsaPrivateKey(rsaPrivateKey);
+
+// convert a PKCS#8 ASN.1 PrivateKeyInfo to PEM
+var pem = pki.privateKeyInfoToPem(privateKeyInfo);
+
+// encrypts a PrivateKeyInfo and outputs an EncryptedPrivateKeyInfo
+var encryptedPrivateKeyInfo = pki.encryptPrivateKeyInfo(
+  privateKeyInfo, 'password', {
+    algorithm: 'aes256', // 'aes128', 'aes192', 'aes256', '3des'
+  });
+
+// decrypts an ASN.1 EncryptedPrivateKeyInfo
+var privateKeyInfo = pki.decryptPrivateKeyInfo(
+  encryptedPrivateKeyInfo, 'password');
+
+// converts an EncryptedPrivateKeyInfo to PEM
+var pem = pki.encryptedPrivateKeyToPem(encryptedPrivateKeyInfo);
+
+// converts a PEM-encoded EncryptedPrivateKeyInfo to ASN.1 format
+var encryptedPrivateKeyInfo = pki.encryptedPrivateKeyFromPem(pem);
+
+// wraps and encrypts a Forge private key and outputs it in PEM format
+var pem = pki.encryptRsaPrivateKey(privateKey, 'password');
+
+// encrypts a Forge private key and outputs it in PEM format using OpenSSL's
+// proprietary legacy format + encapsulated PEM headers (DEK-Info)
+var pem = pki.encryptRsaPrivateKey(privateKey, 'password', {legacy: true});
+
+// decrypts a PEM-formatted, encrypted private key
+var privateKey = pki.decryptRsaPrivateKey(pem, 'password');
+
+// sets an RSA public key from a private key
+var publicKey = pki.setRsaPublicKey(privateKey.n, privateKey.e);
+```
+
+<a name="pkcs10" />
+### PKCS#10
+
+Provides certification requests or certificate signing requests (CSR) from
+[PKCS#10][].
+
+__Examples__
+
+```js
+// generate a key pair
+var keys = forge.pki.rsa.generateKeyPair(1024);
+
+// create a certification request (CSR)
+var csr = forge.pki.createCertificationRequest();
+csr.publicKey = keys.publicKey;
+csr.setSubject([{
+  name: 'commonName',
+  value: 'example.org'
+}, {
+  name: 'countryName',
+  value: 'US'
+}, {
+  shortName: 'ST',
+  value: 'Virginia'
+}, {
+  name: 'localityName',
+  value: 'Blacksburg'
+}, {
+  name: 'organizationName',
+  value: 'Test'
+}, {
+  shortName: 'OU',
+  value: 'Test'
+}]);
+// set (optional) attributes
+csr.setAttributes([{
+  name: 'challengePassword',
+  value: 'password'
+}, {
+  name: 'unstructuredName',
+  value: 'My Company, Inc.'
+}, {
+  name: 'extensionRequest',
+  extensions: [{
+    name: 'subjectAltName',
+    altNames: [{
+      // 2 is DNS type
+      type: 2,
+      value: 'test.domain.com'
+    }, {
+      type: 2,
+      value: 'other.domain.com',
+    }, {
+      type: 2,
+      value: 'www.domain.net'
+    }]
+  }]
+}]);
+
+// sign certification request
+csr.sign(keys.privateKey);
+
+// verify certification request
+var verified = csr.verify();
+
+// convert certification request to PEM-format
+var pem = forge.pki.certificationRequestToPem(csr);
+
+// convert a Forge certification request from PEM-format
+var csr = forge.pki.certificationRequestFromPem(pem);
+
+// get an attribute
+csr.getAttribute({name: 'challengePassword'});
+
+// get extensions array
+csr.getAttribute({name: 'extensionRequest'}).extensions;
+
+```
+
+<a name="pkcs12" />
+### PKCS#12
+
+Provides the cryptographic archive file format from [PKCS#12][].
+
+__Examples__
+
+```js
+// decode p12 from base64
+var p12Der = forge.util.decode64(p12b64);
+// get p12 as ASN.1 object
+var p12Asn1 = forge.asn1.fromDer(p12Der);
+// decrypt p12 using the password 'password'
+var p12 = forge.pkcs12.pkcs12FromAsn1(p12Asn1, 'password');
+// decrypt p12 using non-strict parsing mode (resolves some ASN.1 parse errors)
+var p12 = forge.pkcs12.pkcs12FromAsn1(p12Asn1, false, 'password');
+// decrypt p12 using literally no password (eg: Mac OS X/apple push)
+var p12 = forge.pkcs12.pkcs12FromAsn1(p12Asn1);
+// decrypt p12 using an "empty" password (eg: OpenSSL with no password input)
+var p12 = forge.pkcs12.pkcs12FromAsn1(p12Asn1, '');
+// p12.safeContents is an array of safe contents, each of
+// which contains an array of safeBags
+
+// get bags by friendlyName
+var bags = p12.getBags({friendlyName: 'test'});
+// bags are key'd by attribute type (here "friendlyName")
+// and the key values are an array of matching objects
+var cert = bags.friendlyName[0];
+
+// get bags by localKeyId
+var bags = p12.getBags({localKeyId: buffer});
+// bags are key'd by attribute type (here "localKeyId")
+// and the key values are an array of matching objects
+var cert = bags.localKeyId[0];
+
+// get bags by localKeyId (input in hex)
+var bags = p12.getBags({localKeyIdHex: '7b59377ff142d0be4565e9ac3d396c01401cd879'});
+// bags are key'd by attribute type (here "localKeyId", *not* "localKeyIdHex")
+// and the key values are an array of matching objects
+var cert = bags.localKeyId[0];
+
+// get bags by type
+var bags = p12.getBags({bagType: forge.pki.oids.certBag});
+// bags are key'd by bagType and each bagType key's value
+// is an array of matches (in this case, certificate objects)
+var cert = bags[forge.pki.oids.certBag][0];
+
+// get bags by friendlyName and filter on bag type
+var bags = p12.getBags({
+  friendlyName: 'test',
+  bagType: forge.pki.oids.certBag
+});
+
+// generate a p12 using AES (default)
+var p12Asn1 = forge.pkcs12.toPkcs12Asn1(
+  privateKey, certificateChain, 'password');
+
+// generate a p12 that can be imported by Chrome/Firefox
+// (requires the use of Triple DES instead of AES)
+var p12Asn1 = forge.pkcs12.toPkcs12Asn1(
+  privateKey, certificateChain, 'password',
+  {algorithm: '3des'});
+
+// base64-encode p12
+var p12Der = forge.asn1.toDer(p12Asn1).getBytes();
+var p12b64 = forge.util.encode64(p12Der);
+
+// create download link for p12
+var a = document.createElement('a');
+a.download = 'example.p12';
+a.setAttribute('href', 'data:application/x-pkcs12;base64,' + p12b64);
+a.appendChild(document.createTextNode('Download'));
+```
+
+<a name="asn" />
+### ASN.1
+
+Provides [ASN.1][] DER encoding and decoding.
+
+__Examples__
+
+```js
+var asn1 = forge.asn1;
+
+// create a SubjectPublicKeyInfo
+var subjectPublicKeyInfo =
+  asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+    // AlgorithmIdentifier
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+      // algorithm
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+        asn1.oidToDer(pki.oids['rsaEncryption']).getBytes()),
+      // parameters (null)
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
+    ]),
+    // subjectPublicKey
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.BITSTRING, false, [
+      // RSAPublicKey
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+        // modulus (n)
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+          _bnToBytes(key.n)),
+        // publicExponent (e)
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+          _bnToBytes(key.e))
+      ])
+    ])
+  ]);
+
+// serialize an ASN.1 object to DER format
+var derBuffer = asn1.toDer(subjectPublicKeyInfo);
+
+// deserialize to an ASN.1 object from a byte buffer filled with DER data
+var object = asn1.fromDer(derBuffer);
+
+// convert an OID dot-separated string to a byte buffer
+var derOidBuffer = asn1.oidToDer('1.2.840.113549.1.1.5');
+
+// convert a byte buffer with a DER-encoded OID to a dot-separated string
+console.log(asn1.derToDer(derOidBuffer));
+// output: 1.2.840.113549.1.1.5
+
+// validates that an ASN.1 object matches a particular ASN.1 structure and
+// captures data of interest from that structure for easy access
+var publicKeyValidator = {
+  name: 'SubjectPublicKeyInfo',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  captureAsn1: 'subjectPublicKeyInfo',
+  value: [{
+    name: 'SubjectPublicKeyInfo.AlgorithmIdentifier',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true,
+    value: [{
+      name: 'AlgorithmIdentifier.algorithm',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.OID,
+      constructed: false,
+      capture: 'publicKeyOid'
+    }]
+  }, {
+    // subjectPublicKey
+    name: 'SubjectPublicKeyInfo.subjectPublicKey',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.BITSTRING,
+    constructed: false,
+    value: [{
+      // RSAPublicKey
+      name: 'SubjectPublicKeyInfo.subjectPublicKey.RSAPublicKey',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.SEQUENCE,
+      constructed: true,
+      optional: true,
+      captureAsn1: 'rsaPublicKey'
+    }]
+  }]
+};
+
+var capture = {};
+var errors = [];
+if(!asn1.validate(
+  publicKeyValidator, subjectPublicKeyInfo, validator, capture, errors)) {
+  throw 'ASN.1 object is not a SubjectPublicKeyInfo.';
+}
+// capture.subjectPublicKeyInfo contains the full ASN.1 object
+// capture.rsaPublicKey contains the full ASN.1 object for the RSA public key
+// capture.publicKeyOid only contains the value for the OID
+var oid = asn1.derToOid(capture.publicKeyOid);
+if(oid !== pki.oids['rsaEncryption']) {
+  throw 'Unsupported OID.';
+}
+
+// pretty print an ASN.1 object to a string for debugging purposes
+asn1.prettyPrint(object);
+```
+
+---------------------------------------
+## Message Digests
+
+<a name="sha1" />
+### SHA1
+
+Provides [SHA-1][] message digests.
+
+__Examples__
+
+```js
+var md = forge.md.sha1.create();
+md.update('The quick brown fox jumps over the lazy dog');
+console.log(md.digest().toHex());
+// output: 2fd4e1c67a2d28fced849ee1bb76e7391b93eb12
+```
+
+<a name="sha256" />
+### SHA256
+
+Provides [SHA-256][] message digests.
+
+__Examples__
+
+```js
+var md = forge.md.sha256.create();
+md.update('The quick brown fox jumps over the lazy dog');
+console.log(md.digest().toHex());
+// output: d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592
+```
+
+<a name="sha384" />
+### SHA384
+
+Provides [SHA-384][] message digests.
+
+__Examples__
+
+```js
+var md = forge.md.sha384.create();
+md.update('The quick brown fox jumps over the lazy dog');
+console.log(md.digest().toHex());
+// output: ca737f1014a48f4c0b6dd43cb177b0afd9e5169367544c494011e3317dbf9a509cb1e5dc1e85a941bbee3d7f2afbc9b1
+```
+
+<a name="sha512" />
+### SHA512
+
+Provides [SHA-512][] message digests.
+
+__Examples__
+
+```js
+// SHA-512
+var md = forge.md.sha512.create();
+md.update('The quick brown fox jumps over the lazy dog');
+console.log(md.digest().toHex());
+// output: 07e547d9586f6a73f73fbac0435ed76951218fb7d0c8d788a309d785436bbb642e93a252a954f23912547d1e8a3b5ed6e1bfd7097821233fa0538f3db854fee6
+
+// SHA-512/224
+var md = forge.md.sha512.sha224.create();
+md.update('The quick brown fox jumps over the lazy dog');
+console.log(md.digest().toHex());
+// output: 944cd2847fb54558d4775db0485a50003111c8e5daa63fe722c6aa37
+
+// SHA-512/256
+var md = forge.md.sha512.sha256.create();
+md.update('The quick brown fox jumps over the lazy dog');
+console.log(md.digest().toHex());
+// output: dd9d67b371519c339ed8dbd25af90e976a1eeefd4ad3d889005e532fc5bef04d
+```
+
+<a name="md5" />
+### MD5
+
+Provides [MD5][] message digests.
+
+__Examples__
+
+```js
+var md = forge.md.md5.create();
+md.update('The quick brown fox jumps over the lazy dog');
+console.log(md.digest().toHex());
+// output: 9e107d9d372bb6826bd81d3542a419d6
+```
+
+<a name="hmac" />
+### HMAC
+
+Provides [HMAC][] w/any supported message digest algorithm.
+
+__Examples__
+
+```js
+var hmac = forge.hmac.create();
+hmac.start('sha1', 'Jefe');
+hmac.update('what do ya want for nothing?');
+console.log(hmac.digest().toHex());
+// output: effcdf6ae5eb2fa2d27416d5f184df9c259a7c79
+```
+
+---------------------------------------
+## Utilities
+
+<a name="prime" />
+### Prime
+
+Provides an API for generating large, random, probable primes.
+
+__Examples__
+
+```js
+// generate a random prime on the main JS thread
+var bits = 1024;
+forge.prime.generateProbablePrime(bits, function(err, num) {
+  console.log('random prime', num.toString(16));
+});
+
+// generate a random prime using Web Workers (if available, otherwise
+// falls back to the main thread)
+var bits = 1024;
+var options = {
+  algorithm: {
+    name: 'PRIMEINC',
+    workers: -1 // auto-optimize # of workers
+  }
+};
+forge.prime.generateProbablePrime(bits, options, function(err, num) {
+  console.log('random prime', num.toString(16));
+});
+```
+
+<a name="prng" />
+### PRNG
+
+Provides a [Fortuna][]-based cryptographically-secure pseudo-random number
+generator, to be used with a cryptographic function backend, e.g. [AES][]. An
+implementation using [AES][] as a backend is provided. An API for collecting
+entropy is given, though if window.crypto.getRandomValues is available, it will
+be used automatically.
+
+__Examples__
+
+```js
+// get some random bytes synchronously
+var bytes = forge.random.getBytesSync(32);
+console.log(forge.util.bytesToHex(bytes));
+
+// get some random bytes asynchronously
+forge.random.getBytes(32, function(err, bytes) {
+  console.log(forge.util.bytesToHex(bytes));
+});
+
+// collect some entropy if you'd like
+forge.random.collect(someRandomBytes);
+jQuery().mousemove(function(e) {
+  forge.random.collectInt(e.clientX, 16);
+  forge.random.collectInt(e.clientY, 16);
+});
+
+// specify a seed file for use with the synchronous API if you'd like
+forge.random.seedFileSync = function(needed) {
+  // get 'needed' number of random bytes from somewhere
+  return fetchedRandomBytes;
+};
+
+// specify a seed file for use with the asynchronous API if you'd like
+forge.random.seedFile = function(needed, callback) {
+  // get the 'needed' number of random bytes from somewhere
+  callback(null, fetchedRandomBytes);
+});
+
+// register the main thread to send entropy or a Web Worker to receive
+// entropy on demand from the main thread
+forge.random.registerWorker(self);
+
+// generate a new instance of a PRNG with no collected entropy
+var myPrng = forge.random.createInstance();
+```
+
+<a name="task" />
+### Tasks
+
+Provides queuing and synchronizing tasks in a web application.
+
+__Examples__
+
+```js
+```
+
+<a name="util" />
+### Utilities
+
+Provides utility functions, including byte buffer support, base64,
+bytes to/from hex, zlib inflate/deflate, etc.
+
+__Examples__
+
+```js
+// encode/decode base64
+var encoded = forge.util.encode64(str);
+var str = forge.util.decode64(encoded);
+
+// encode/decode UTF-8
+var encoded = forge.util.encodeUtf8(str);
+var str = forge.util.decodeUtf8(encoded);
+
+// bytes to/from hex
+var bytes = forge.util.hexToBytes(hex);
+var hex = forge.util.bytesToHex(bytes);
+
+// create an empty byte buffer
+var buffer = forge.util.createBuffer();
+// create a byte buffer from raw binary bytes
+var buffer = forge.util.createBuffer(input, 'raw');
+// create a byte buffer from utf8 bytes
+var buffer = forge.util.createBuffer(input, 'utf8');
+
+// get the length of the buffer in bytes
+buffer.length();
+// put bytes into the buffer
+buffer.putBytes(bytes);
+// put a 32-bit integer into the buffer
+buffer.putInt32(10);
+// buffer to hex
+buffer.toHex();
+// get a copy of the bytes in the buffer
+bytes.bytes(/* count */);
+// empty this buffer and get its contents
+bytes.getBytes(/* count */);
+
+// convert a forge buffer into a node.js Buffer
+// make sure you specify the encoding as 'binary'
+var forgeBuffer = forge.util.createBuffer();
+var nodeBuffer = new Buffer(forgeBuffer.getBytes(), 'binary');
+
+// convert a node.js Buffer into a forge buffer
+// make sure you specify the encoding as 'binary'
+var nodeBuffer = new Buffer();
+var forgeBuffer = forge.util.createBuffer(nodeBuffer.toString('binary'));
+
+// parse a URL
+var parsed = forge.util.parseUrl('http://example.com/foo?bar=baz');
+// parsed.scheme, parsed.host, parsed.port, parsed.path, parsed.fullHost
+```
+
+<a name="log" />
+### Logging
+
+Provides logging to a javascript console using various categories and
+levels of verbosity.
+
+__Examples__
+
+```js
+```
+
+<a name="debug" />
+### Debugging
+
+Provides storage of debugging information normally inaccessible in
+closures for viewing/investigation.
+
+__Examples__
+
+```js
+```
+
+<a name="fsp" />
+### Flash Socket Policy Module
+
+Provides an [Apache][] module "mod_fsp" that can serve up a Flash Socket
+Policy. See `mod_fsp/README` for more details. This module makes it easy to
+modify an [Apache][] server to allow cross domain requests to be made to it.
+
+
+Library Details
+---------------
+
+* http://digitalbazaar.com/2010/07/20/javascript-tls-1/
+* http://digitalbazaar.com/2010/07/20/javascript-tls-2/
+
+Contact
+-------
+
+* Code: https://github.com/digitalbazaar/forge
+* Bugs: https://github.com/digitalbazaar/forge/issues
+* Email: support@digitalbazaar.com
+
+Donations welcome:
+
+* Donate: paypal@digitalbazaar.com
+
+[AES]: http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
+[ASN.1]: http://en.wikipedia.org/wiki/ASN.1
+[Apache]: http://httpd.apache.org/
+[CFB]: http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation
+[CBC]: http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation
+[CTR]: http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation
+[3DES]: http://en.wikipedia.org/wiki/Triple_DES
+[DES]: http://en.wikipedia.org/wiki/Data_Encryption_Standard
+[ECB]: http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation
+[Fortuna]: http://en.wikipedia.org/wiki/Fortuna_(PRNG)
+[GCM]: http://en.wikipedia.org/wiki/GCM_mode
+[HMAC]: http://en.wikipedia.org/wiki/HMAC
+[JavaScript]: http://en.wikipedia.org/wiki/JavaScript
+[MD5]: http://en.wikipedia.org/wiki/MD5
+[OFB]: http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation
+[PKCS#5]: http://en.wikipedia.org/wiki/PKCS
+[PKCS#7]: http://en.wikipedia.org/wiki/Cryptographic_Message_Syntax
+[PKCS#10]: http://en.wikipedia.org/wiki/Certificate_signing_request
+[PKCS#12]: http://en.wikipedia.org/wiki/PKCS_%E2%99%AF12
+[RC2]: http://en.wikipedia.org/wiki/RC2
+[SHA-1]: http://en.wikipedia.org/wiki/SHA-1
+[SHA-256]: http://en.wikipedia.org/wiki/SHA-256
+[SHA-384]: http://en.wikipedia.org/wiki/SHA-384
+[SHA-512]: http://en.wikipedia.org/wiki/SHA-512
+[TLS]: http://en.wikipedia.org/wiki/Transport_Layer_Security
+[X.509]: http://en.wikipedia.org/wiki/X.509
+[node.js]: http://nodejs.org/
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/bower.json b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/bower.json
new file mode 100644
index 0000000..c65a95e
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/bower.json
@@ -0,0 +1,15 @@
+{
+  "name": "forge",
+  "version": "0.6.21",
+  "description": "JavaScript implementations of network transports, cryptography, ciphers, PKI, message digests, and various utilities.",
+  "authors": [
+    "Digital Bazaar, Inc."
+  ],
+  "license": "BSD",
+  "main": ["js/forge.js"],
+  "dependencies": {},
+  "ignore": [
+    "node_modules",
+    "bower_components"
+  ]
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/build-flash.xml b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/build-flash.xml
new file mode 100644
index 0000000..8d8829c
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/build-flash.xml
@@ -0,0 +1,7 @@
+<flex-config>
+   <compiler>
+      <source-path>
+         <path-element>flash</path-element>
+      </source-path>
+   </compiler>
+</flex-config>
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/build-setup b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/build-setup
new file mode 100755
index 0000000..5c3866e
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/build-setup
@@ -0,0 +1,46 @@
+#!/bin/sh
+#
+# This shell script sets up the software to be built using 'make'. In 
+# order to perform a build from a fresh source tree, do the following:
+#
+# 1. ./build-setup
+# 2. make
+#
+# If you don't want ./configure to be run automatically, you can do
+# the following: ./build-setup -s
+
+# Process command line options
+SKIP_CONFIGURE=0
+for arg in "$*"
+do
+   case $arg in
+      "-s" | "--setup-only" ) SKIP_CONFIGURE=1 ;;
+   esac
+done
+
+# Check and add potential aclocal dirs
+MAYBE_AC_DIRS="
+   /usr/local/share/aclocal
+   /opt/local/share/aclocal
+   /sw/share/aclocal
+   "
+ACDIRS="-I m4"
+for dir in $MAYBE_AC_DIRS; do
+   if test -d $dir; then
+      ACDIRS="$ACDIRS -I $dir"
+   fi
+done
+
+# Run aclocal on the set of local ac scripts
+cd setup
+aclocal $ACDIRS
+# Generate the configure script
+autoconf && mv configure ..
+cd ..
+
+# Run the configure script if "-s" isn't a command line option
+if [ $SKIP_CONFIGURE -eq 0 ]; then
+   # Run the configure script in default development mode
+   ./configure $*
+fi
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/end.frag b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/end.frag
new file mode 100644
index 0000000..cbcf226
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/end.frag
@@ -0,0 +1,4 @@
+
+return require('js/forge');
+
+});
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/flash/PooledSocket.as b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/flash/PooledSocket.as
new file mode 100644
index 0000000..15e3ae4
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/flash/PooledSocket.as
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2009 Digital Bazaar, Inc. All rights reserved.
+ * 
+ * @author Dave Longley
+ */
+package
+{
+   import flash.net.Socket;
+   
+   /**
+    * A helper class that contains the ID for a Socket.
+    */
+   public class PooledSocket extends Socket
+   {
+      // the ID in the related socket pool
+      public var id:String;
+   }
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/flash/SocketEvent.as b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/flash/SocketEvent.as
new file mode 100644
index 0000000..56f5e7f
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/flash/SocketEvent.as
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2009 Digital Bazaar, Inc. All rights reserved.
+ * 
+ * @author Dave Longley
+ */
+package
+{
+   import flash.events.Event;
+   
+   /**
+    * A helper class that contains the ID for a Socket.
+    */
+   public class SocketEvent extends Event
+   {
+      // the associated socket
+      public var socket:PooledSocket;
+      // an associated message
+      public var message:String;
+      
+      /**
+       * Creates a new SocketEvent.
+       * 
+       * @param type the type of event.
+       * @param socket the associated PooledSocket.
+       * @param message an associated message.
+       */
+      public function SocketEvent(
+         type:String, socket:PooledSocket, message:String = null)
+      {
+         super(type, false, false);
+         this.socket = socket;
+         this.message = message;
+      }
+   }
+}
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/flash/SocketPool.as b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/flash/SocketPool.as
new file mode 100644
index 0000000..d99b3ec
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/flash/SocketPool.as
@@ -0,0 +1,754 @@
+/*
+ * Copyright (c) 2009-2010 Digital Bazaar, Inc. All rights reserved.
+ * 
+ * @author Dave Longley
+ */
+package
+{
+   import flash.display.Sprite;
+   
+   /**
+    * A SocketPool is a flash object that can be embedded in a web page to
+    * allow javascript access to pools of Sockets.
+    * 
+    * Javascript can create a pool and then as many Sockets as it desires. Each
+    * Socket will be assigned a unique ID that allows continued javascript
+    * access to it. There is no limit on the number of persistent socket
+    * connections.
+    */
+   public class SocketPool extends Sprite
+   {
+      import flash.events.Event;
+      import flash.events.EventDispatcher;
+      import flash.errors.IOError;
+      import flash.events.IOErrorEvent;
+      import flash.events.ProgressEvent;
+      import flash.events.SecurityErrorEvent;
+      import flash.events.TextEvent;
+      import flash.external.ExternalInterface;
+      import flash.net.SharedObject;
+      import flash.system.Security;
+      import flash.utils.ByteArray;
+      import mx.utils.Base64Decoder;
+      import mx.utils.Base64Encoder;
+      
+      // a map of ID to Socket
+      private var mSocketMap:Object;
+      
+      // a counter for Socket IDs (Note: assumes there will be no overflow)
+      private var mNextId:uint;
+      
+      // an event dispatcher for sending events to javascript
+      private var mEventDispatcher:EventDispatcher;
+      
+      /**
+       * Creates a new, unitialized SocketPool.
+       * 
+       * @throws Error - if no external interface is available to provide
+       *                 javascript access.
+       */
+      public function SocketPool()
+      {
+         if(!ExternalInterface.available)
+         {
+            trace("ExternalInterface is not available");
+            throw new Error(
+               "Flash's ExternalInterface is not available. This is a " +
+               "requirement of SocketPool and therefore, it will be " +
+               "unavailable.");
+         }
+         else
+         {
+            try
+            {
+               // set up javascript access:
+               
+               // initializes/cleans up the SocketPool
+               ExternalInterface.addCallback("init", init);
+               ExternalInterface.addCallback("cleanup", cleanup);
+               
+               // creates/destroys a socket
+               ExternalInterface.addCallback("create", create);
+               ExternalInterface.addCallback("destroy", destroy);
+               
+               // connects/closes a socket
+               ExternalInterface.addCallback("connect", connect);
+               ExternalInterface.addCallback("close", close);
+               
+               // checks for a connection
+               ExternalInterface.addCallback("isConnected", isConnected);
+               
+               // sends/receives data over the socket
+               ExternalInterface.addCallback("send", send);
+               ExternalInterface.addCallback("receive", receive);
+               
+               // gets the number of bytes available on a socket
+               ExternalInterface.addCallback(
+                  "getBytesAvailable", getBytesAvailable);
+               
+               // add a callback for subscribing to socket events
+               ExternalInterface.addCallback("subscribe", subscribe);
+               
+               // add callbacks for deflate/inflate
+               ExternalInterface.addCallback("deflate", deflate);
+               ExternalInterface.addCallback("inflate", inflate);
+               
+               // add callbacks for local disk storage
+               ExternalInterface.addCallback("setItem", setItem);
+               ExternalInterface.addCallback("getItem", getItem);
+               ExternalInterface.addCallback("removeItem", removeItem);
+               ExternalInterface.addCallback("clearItems", clearItems);
+               
+               // socket pool is now ready
+               ExternalInterface.call("window.forge.socketPool.ready");
+            }
+            catch(e:Error)
+            {
+               log("error=" + e.errorID + "," + e.name + "," + e.message);
+               throw e;
+            }
+            
+            log("ready");
+         }
+      }
+      
+      /**
+       * A debug logging function.
+       * 
+       * @param obj the string or error to log.
+       */
+      CONFIG::debugging
+      private function log(obj:Object):void
+      {
+         if(obj is String)
+         {
+            var str:String = obj as String;
+            ExternalInterface.call("console.log", "SocketPool", str);
+         }
+         else if(obj is Error)
+         {
+            var e:Error = obj as Error;
+            log("error=" + e.errorID + "," + e.name + "," + e.message);
+         }
+      }
+      
+      CONFIG::release
+      private function log(obj:Object):void
+      {
+         // log nothing in release mode
+      }
+      
+      /**
+       * Called by javascript to initialize this SocketPool.
+       * 
+       * @param options:
+       *        marshallExceptions: true to pass exceptions to and from
+       *           javascript.
+       */
+      private function init(... args):void
+      {
+         log("init()");
+         
+         // get options from first argument
+         var options:Object = args.length > 0 ? args[0] : null;
+         
+         // create socket map, set next ID, and create event dispatcher
+         mSocketMap = new Object();
+         mNextId = 1;
+         mEventDispatcher = new EventDispatcher();
+         
+         // enable marshalling exceptions if appropriate
+         if(options != null &&
+            "marshallExceptions" in options &&
+            options.marshallExceptions === true)
+         {
+            try
+            {
+               // Note: setting marshallExceptions in IE, even inside of a
+               // try-catch block will terminate flash. Don't set this on IE.
+               ExternalInterface.marshallExceptions = true;
+            }
+            catch(e:Error)
+            {
+               log(e);
+            }
+         }
+         
+         log("init() done");
+      }
+      
+      /**
+       * Called by javascript to clean up a SocketPool.
+       */
+      private function cleanup():void
+      {
+         log("cleanup()");
+         
+         mSocketMap = null;
+         mNextId = 1;
+         mEventDispatcher = null;
+         
+         log("cleanup() done");
+      }
+      
+      /**
+       * Handles events.
+       * 
+       * @param e the event to handle.
+       */
+      private function handleEvent(e:Event):void
+      {
+         // dispatch socket event
+         var message:String = (e is TextEvent) ? (e as TextEvent).text : null;
+         mEventDispatcher.dispatchEvent(
+            new SocketEvent(e.type, e.target as PooledSocket, message));
+      }
+      
+      /**
+       * Called by javascript to create a Socket.
+       * 
+       * @return the Socket ID.
+       */
+      private function create():String
+      {
+         log("create()");
+         
+         // create a Socket
+         var id:String = "" + mNextId++;
+         var s:PooledSocket = new PooledSocket();
+         s.id = id;
+         s.addEventListener(Event.CONNECT, handleEvent);
+         s.addEventListener(Event.CLOSE, handleEvent);
+         s.addEventListener(ProgressEvent.SOCKET_DATA, handleEvent);
+         s.addEventListener(IOErrorEvent.IO_ERROR, handleEvent);
+         s.addEventListener(SecurityErrorEvent.SECURITY_ERROR, handleEvent);
+         mSocketMap[id] = s;
+         
+         log("socket " + id + " created");
+         log("create() done");
+         
+         return id;
+      }
+      
+      /**
+       * Called by javascript to clean up a Socket.
+       * 
+       * @param id the ID of the Socket to clean up.
+       */
+      private function destroy(id:String):void
+      {
+         log("destroy(" + id + ")");
+         
+         if(id in mSocketMap)
+         {
+            // remove Socket
+            delete mSocketMap[id];
+            log("socket " + id + " destroyed");
+         }
+         
+         log("destroy(" + id + ") done");
+      }
+      
+      /**
+       * Connects the Socket with the given ID to the given host and port,
+       * using the given socket policy port.
+       *
+       * @param id the ID of the Socket.
+       * @param host the host to connect to.
+       * @param port the port to connect to.
+       * @param spPort the security policy port to use, 0 to use a url.
+       * @param spUrl the http URL to the policy file to use, null for default.
+       */
+      private function connect(
+         id:String, host:String, port:uint, spPort:uint,
+         spUrl:String = null):void
+      {
+         log("connect(" +
+            id + "," + host + "," + port + "," + spPort + "," + spUrl + ")");
+         
+         if(id in mSocketMap)
+         {
+            // get the Socket
+            var s:PooledSocket = mSocketMap[id];
+            
+            // load socket policy file
+            // (permits socket access to backend)
+            if(spPort !== 0)
+            {
+               spUrl = "xmlsocket://" + host + ":" + spPort;
+               log("using cross-domain url: " + spUrl);
+               Security.loadPolicyFile(spUrl);
+            }
+            else if(spUrl !== null && typeof(spUrl) !== undefined)
+            {
+               log("using cross-domain url: " + spUrl);
+               Security.loadPolicyFile(spUrl);
+            }
+            else
+            {
+               log("not loading any cross-domain url");
+            }
+            
+            // connect
+            s.connect(host, port);
+         }
+         else
+         {
+            // no such socket
+            log("socket " + id + " does not exist");
+         }
+         
+         log("connect(" + id + ") done");
+      }
+      
+      /**
+       * Closes the Socket with the given ID.
+       *
+       * @param id the ID of the Socket.
+       */
+      private function close(id:String):void
+      {
+         log("close(" + id + ")");
+         
+         if(id in mSocketMap)
+         {
+            // close the Socket
+            var s:PooledSocket = mSocketMap[id];
+            if(s.connected)
+            {
+               s.close();
+            }
+         }
+         else
+         {
+            // no such socket
+            log("socket " + id + " does not exist");
+         }
+         
+         log("close(" + id + ") done");
+      }
+      
+      /**
+       * Determines if the Socket with the given ID is connected or not.
+       *
+       * @param id the ID of the Socket.
+       *
+       * @return true if the socket is connected, false if not.
+       */
+      private function isConnected(id:String):Boolean
+      {
+         var rval:Boolean = false;
+         log("isConnected(" + id + ")");
+         
+         if(id in mSocketMap)
+         {
+            // check the Socket
+            var s:PooledSocket = mSocketMap[id];
+            rval = s.connected;
+         }
+         else
+         {
+            // no such socket
+            log("socket " + id + " does not exist");
+         }
+         
+         log("isConnected(" + id + ") done");
+         return rval;
+      }
+      
+      /**
+       * Writes bytes to a Socket.
+       *
+       * @param id the ID of the Socket.
+       * @param bytes the string of base64-encoded bytes to write.
+       *
+       * @return true on success, false on failure. 
+       */
+      private function send(id:String, bytes:String):Boolean
+      {
+         var rval:Boolean = false;
+         log("send(" + id + ")");
+         
+         if(id in mSocketMap)
+         {
+         	// write bytes to socket
+            var s:PooledSocket = mSocketMap[id];
+            try
+            {
+               var b64:Base64Decoder = new Base64Decoder();
+               b64.decode(bytes);
+               var b:ByteArray = b64.toByteArray();
+               s.writeBytes(b, 0, b.length);
+               s.flush();
+               rval = true;
+            }
+            catch(e:IOError)
+            {
+               log(e);
+               
+               // dispatch IO error event
+               mEventDispatcher.dispatchEvent(new SocketEvent(
+                  IOErrorEvent.IO_ERROR, s, e.message));
+               if(s.connected)
+               {
+                  s.close();
+               }
+            }
+         }
+         else
+         {
+            // no such socket
+            log("socket " + id + " does not exist");
+         }
+         
+         log("send(" + id + ") done");
+         return rval;
+      }
+      
+      /**
+       * Receives bytes from a Socket.
+       *
+       * @param id the ID of the Socket.
+       * @param count the maximum number of bytes to receive.
+       *
+       * @return an object with 'rval' set to the received bytes,
+       *         base64-encoded, or set to null on error.
+       */
+      private function receive(id:String, count:uint):Object
+      {
+      	 var rval:String = null;
+         log("receive(" + id + "," + count + ")");
+         
+         if(id in mSocketMap)
+         {
+         	// only read what is available
+            var s:PooledSocket = mSocketMap[id];
+            if(count > s.bytesAvailable)
+            {
+               count = s.bytesAvailable;
+            }
+            
+            try
+            {
+               // read bytes from socket
+               var b:ByteArray = new ByteArray();
+               s.readBytes(b, 0, count);
+               b.position = 0;
+               var b64:Base64Encoder = new Base64Encoder();
+               b64.insertNewLines = false;
+               b64.encodeBytes(b, 0, b.length);
+               rval = b64.toString();
+            }
+            catch(e:IOError)
+            {
+               log(e);
+               
+               // dispatch IO error event
+               mEventDispatcher.dispatchEvent(new SocketEvent(
+                  IOErrorEvent.IO_ERROR, s, e.message));
+               if(s.connected)
+               {
+                  s.close();
+               }
+            }
+         }
+         else
+         {
+            // no such socket
+            log("socket " + id + " does not exist");
+         }
+         
+         log("receive(" + id + "," + count + ") done");
+         return {rval: rval};
+      }
+      
+      /**
+       * Gets the number of bytes available from a Socket.
+       *
+       * @param id the ID of the Socket.
+       *
+       * @return the number of available bytes.
+       */
+      private function getBytesAvailable(id:String):uint
+      {
+         var rval:uint = 0;
+         log("getBytesAvailable(" + id + ")");
+         
+         if(id in mSocketMap)
+         {
+            var s:PooledSocket = mSocketMap[id];
+            rval = s.bytesAvailable;
+         }
+         else
+         {
+            // no such socket
+            log("socket " + id + " does not exist");
+         }
+         
+         log("getBytesAvailable(" + id +") done");
+         return rval;
+      }      
+      
+      /**
+       * Registers a javascript function as a callback for an event.
+       * 
+       * @param eventType the type of event (socket event types).
+       * @param callback the name of the callback function.
+       */
+      private function subscribe(eventType:String, callback:String):void
+      {
+         log("subscribe(" + eventType + "," + callback + ")");
+         
+         switch(eventType)
+         {
+            case Event.CONNECT:
+            case Event.CLOSE:
+            case IOErrorEvent.IO_ERROR:
+            case SecurityErrorEvent.SECURITY_ERROR:
+            case ProgressEvent.SOCKET_DATA:
+            {
+               log(eventType + " => " + callback);
+               mEventDispatcher.addEventListener(
+                  eventType, function(event:SocketEvent):void
+               {
+                  log("event dispatched: " + eventType);
+                  
+                  // build event for javascript
+                  var e:Object = new Object();
+                  e.id = event.socket ? event.socket.id : 0;
+                  e.type = eventType;
+                  if(event.socket && event.socket.connected)
+                  {
+                     e.bytesAvailable = event.socket.bytesAvailable;
+                  }
+                  else
+                  {
+                     e.bytesAvailable = 0;
+                  }
+                  if(event.message)
+                  {
+                     e.message = event.message;
+                  }
+                  
+                  // send event to javascript
+                  ExternalInterface.call(callback, e);
+               });
+               break;
+            }
+            default:
+               throw new ArgumentError(
+                  "Could not subscribe to event. " +
+                  "Invalid event type specified: " + eventType);
+         }
+         
+         log("subscribe(" + eventType + "," + callback + ") done");
+      }
+      
+      /**
+       * Deflates the given data.
+       * 
+       * @param data the base64-encoded data to deflate.
+       * 
+       * @return an object with 'rval' set to deflated data, base64-encoded.
+       */
+      private function deflate(data:String):Object
+      {
+         log("deflate");
+         
+         var b64d:Base64Decoder = new Base64Decoder();
+         b64d.decode(data);
+         var b:ByteArray = b64d.toByteArray();
+         b.compress();
+         b.position = 0;
+         var b64e:Base64Encoder = new Base64Encoder();
+         b64e.insertNewLines = false;
+         b64e.encodeBytes(b, 0, b.length);
+         
+         log("deflate done");
+         return {rval: b64e.toString()};
+      }
+      
+      /**
+       * Inflates the given data.
+       * 
+       * @param data the base64-encoded data to inflate.
+       * 
+       * @return an object with 'rval' set to the inflated data,
+       *         base64-encoded, null on error.
+       */
+      private function inflate(data:String):Object
+      {
+         log("inflate");
+         var rval:Object = {rval: null};
+      	 
+      	 try
+      	 {
+            var b64d:Base64Decoder = new Base64Decoder();
+            b64d.decode(data);
+            var b:ByteArray = b64d.toByteArray();
+            b.uncompress();
+            b.position = 0;
+            var b64e:Base64Encoder = new Base64Encoder();
+            b64e.insertNewLines = false;
+            b64e.encodeBytes(b, 0, b.length);
+            rval.rval = b64e.toString();
+         }
+         catch(e:Error)
+         {
+         	log(e);
+            rval.error = {
+                id: e.errorID,
+                name: e.name,
+                message: e.message
+            };
+         }
+         
+         log("inflate done");
+         return rval;
+      }
+      
+      /**
+       * Stores an item with a key and arbitrary base64-encoded data on local
+       * disk.
+       * 
+       * @param key the key for the item.
+       * @param data the base64-encoded item data.
+       * @param storeId the storage ID to use, defaults to "forge.storage".
+       * 
+       * @return an object with rval set to true on success, false on failure
+       *         with error included.
+       */
+      private function setItem(
+         key:String, data:String, storeId:String = "forge.storage"):Object
+      {
+         var rval:Object = {rval: false};
+         try
+         {
+            var store:SharedObject = SharedObject.getLocal(storeId);
+            if(!('keys' in store.data))
+            {
+               store.data.keys = {};
+            }
+            store.data.keys[key] = data;
+            store.flush();
+            rval.rval = true;
+         }
+         catch(e:Error)
+         {
+         	log(e);
+         	rval.error = {
+                id: e.errorID,
+                name: e.name,
+                message: e.message
+            };
+         }
+         return rval;
+      }
+      
+      /**
+       * Gets an item from the local disk.
+       * 
+       * @param key the key for the item.
+       * @param storeId the storage ID to use, defaults to "forge.storage".
+       * 
+       * @return an object with rval set to the item data (which may be null),
+       *         check for error object if null.
+       */
+      private function getItem(
+         key:String, storeId:String = "forge.storage"):Object
+      {
+         var rval:Object = {rval: null};
+         try
+         {
+            var store:SharedObject = SharedObject.getLocal(storeId);
+            if('keys' in store.data && key in store.data.keys)
+            {
+               rval.rval = store.data.keys[key];
+            }
+         }
+         catch(e:Error)
+         {
+         	log(e);
+            rval.error = {
+                id: e.errorID,
+                name: e.name,
+                message: e.message
+            };
+         }
+         return rval;
+      }
+      
+      /**
+       * Removes an item from the local disk.
+       * 
+       * @param key the key for the item.
+       * @param storeId the storage ID to use, defaults to "forge.storage".
+       * 
+       * @return an object with rval set to true if removed, false if not.
+       */
+      private function removeItem(
+         key:String, storeId:String = "forge.storage"):Object
+      {
+         var rval:Object = {rval: false};
+         try
+         {
+            var store:SharedObject = SharedObject.getLocal(storeId);
+            if('keys' in store.data && key in store.data.keys)
+            {
+               delete store.data.keys[key];
+               
+               // clean up storage entirely if empty
+               var empty:Boolean = true;
+               for(var prop:String in store.data.keys)
+               {
+                  empty = false;
+                  break;
+               }
+               if(empty)
+               {
+                  store.clear();
+               }
+               rval.rval = true;
+            }
+         }
+         catch(e:Error)
+         {
+            log(e);
+            rval.error = {
+                id: e.errorID,
+                name: e.name,
+                message: e.message
+            };
+         }
+         return rval;
+      }
+      
+      /**
+       * Clears an entire store of all of its items.
+       * 
+       * @param storeId the storage ID to use, defaults to "forge.storage".
+       * 
+       * @return an object with rval set to true if cleared, false if not.
+       */
+      private function clearItems(storeId:String = "forge.storage"):Object
+      {
+         var rval:Object = {rval: false};
+         try
+         {
+            var store:SharedObject = SharedObject.getLocal(storeId);
+            store.clear();
+            rval.rval = true;
+         }
+         catch(e:Error)
+         {
+            log(e);
+            rval.error = {
+                id: e.errorID,
+                name: e.name,
+                message: e.message
+            };
+         }
+         return rval;
+      }
+   }
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/aes.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/aes.js
new file mode 100644
index 0000000..d4b745b
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/aes.js
@@ -0,0 +1,1146 @@
+/**
+ * Advanced Encryption Standard (AES) implementation.
+ *
+ * This implementation is based on the public domain library 'jscrypto' which
+ * was written by:
+ *
+ * Emily Stark (estark@stanford.edu)
+ * Mike Hamburg (mhamburg@stanford.edu)
+ * Dan Boneh (dabo@cs.stanford.edu)
+ *
+ * Parts of this code are based on the OpenSSL implementation of AES:
+ * http://www.openssl.org
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+/* AES API */
+forge.aes = forge.aes || {};
+
+/**
+ * Deprecated. Instead, use:
+ *
+ * var cipher = forge.cipher.createCipher('AES-<mode>', key);
+ * cipher.start({iv: iv});
+ *
+ * Creates an AES cipher object to encrypt data using the given symmetric key.
+ * The output will be stored in the 'output' member of the returned cipher.
+ *
+ * The key and iv may be given as a string of bytes, an array of bytes,
+ * a byte buffer, or an array of 32-bit words.
+ *
+ * @param key the symmetric key to use.
+ * @param iv the initialization vector to use.
+ * @param output the buffer to write to, null to create one.
+ * @param mode the cipher mode to use (default: 'CBC').
+ *
+ * @return the cipher.
+ */
+forge.aes.startEncrypting = function(key, iv, output, mode) {
+  var cipher = _createCipher({
+    key: key,
+    output: output,
+    decrypt: false,
+    mode: mode
+  });
+  cipher.start(iv);
+  return cipher;
+};
+
+/**
+ * Deprecated. Instead, use:
+ *
+ * var cipher = forge.cipher.createCipher('AES-<mode>', key);
+ *
+ * Creates an AES cipher object to encrypt data using the given symmetric key.
+ *
+ * The key may be given as a string of bytes, an array of bytes, a
+ * byte buffer, or an array of 32-bit words.
+ *
+ * @param key the symmetric key to use.
+ * @param mode the cipher mode to use (default: 'CBC').
+ *
+ * @return the cipher.
+ */
+forge.aes.createEncryptionCipher = function(key, mode) {
+  return _createCipher({
+    key: key,
+    output: null,
+    decrypt: false,
+    mode: mode
+  });
+};
+
+/**
+ * Deprecated. Instead, use:
+ *
+ * var decipher = forge.cipher.createDecipher('AES-<mode>', key);
+ * decipher.start({iv: iv});
+ *
+ * Creates an AES cipher object to decrypt data using the given symmetric key.
+ * The output will be stored in the 'output' member of the returned cipher.
+ *
+ * The key and iv may be given as a string of bytes, an array of bytes,
+ * a byte buffer, or an array of 32-bit words.
+ *
+ * @param key the symmetric key to use.
+ * @param iv the initialization vector to use.
+ * @param output the buffer to write to, null to create one.
+ * @param mode the cipher mode to use (default: 'CBC').
+ *
+ * @return the cipher.
+ */
+forge.aes.startDecrypting = function(key, iv, output, mode) {
+  var cipher = _createCipher({
+    key: key,
+    output: output,
+    decrypt: true,
+    mode: mode
+  });
+  cipher.start(iv);
+  return cipher;
+};
+
+/**
+ * Deprecated. Instead, use:
+ *
+ * var decipher = forge.cipher.createDecipher('AES-<mode>', key);
+ *
+ * Creates an AES cipher object to decrypt data using the given symmetric key.
+ *
+ * The key may be given as a string of bytes, an array of bytes, a
+ * byte buffer, or an array of 32-bit words.
+ *
+ * @param key the symmetric key to use.
+ * @param mode the cipher mode to use (default: 'CBC').
+ *
+ * @return the cipher.
+ */
+forge.aes.createDecryptionCipher = function(key, mode) {
+  return _createCipher({
+    key: key,
+    output: null,
+    decrypt: true,
+    mode: mode
+  });
+};
+
+/**
+ * Creates a new AES cipher algorithm object.
+ *
+ * @param name the name of the algorithm.
+ * @param mode the mode factory function.
+ *
+ * @return the AES algorithm object.
+ */
+forge.aes.Algorithm = function(name, mode) {
+  if(!init) {
+    initialize();
+  }
+  var self = this;
+  self.name = name;
+  self.mode = new mode({
+    blockSize: 16,
+    cipher: {
+      encrypt: function(inBlock, outBlock) {
+        return _updateBlock(self._w, inBlock, outBlock, false);
+      },
+      decrypt: function(inBlock, outBlock) {
+        return _updateBlock(self._w, inBlock, outBlock, true);
+      }
+    }
+  });
+  self._init = false;
+};
+
+/**
+ * Initializes this AES algorithm by expanding its key.
+ *
+ * @param options the options to use.
+ *          key the key to use with this algorithm.
+ *          decrypt true if the algorithm should be initialized for decryption,
+ *            false for encryption.
+ */
+forge.aes.Algorithm.prototype.initialize = function(options) {
+  if(this._init) {
+    return;
+  }
+
+  var key = options.key;
+  var tmp;
+
+  /* Note: The key may be a string of bytes, an array of bytes, a byte
+    buffer, or an array of 32-bit integers. If the key is in bytes, then
+    it must be 16, 24, or 32 bytes in length. If it is in 32-bit
+    integers, it must be 4, 6, or 8 integers long. */
+
+  if(typeof key === 'string' &&
+    (key.length === 16 || key.length === 24 || key.length === 32)) {
+    // convert key string into byte buffer
+    key = forge.util.createBuffer(key);
+  } else if(forge.util.isArray(key) &&
+    (key.length === 16 || key.length === 24 || key.length === 32)) {
+    // convert key integer array into byte buffer
+    tmp = key;
+    key = forge.util.createBuffer();
+    for(var i = 0; i < tmp.length; ++i) {
+      key.putByte(tmp[i]);
+    }
+  }
+
+  // convert key byte buffer into 32-bit integer array
+  if(!forge.util.isArray(key)) {
+    tmp = key;
+    key = [];
+
+    // key lengths of 16, 24, 32 bytes allowed
+    var len = tmp.length();
+    if(len === 16 || len === 24 || len === 32) {
+      len = len >>> 2;
+      for(var i = 0; i < len; ++i) {
+        key.push(tmp.getInt32());
+      }
+    }
+  }
+
+  // key must be an array of 32-bit integers by now
+  if(!forge.util.isArray(key) ||
+    !(key.length === 4 || key.length === 6 || key.length === 8)) {
+    throw new Error('Invalid key parameter.');
+  }
+
+  // encryption operation is always used for these modes
+  var mode = this.mode.name;
+  var encryptOp = (['CFB', 'OFB', 'CTR', 'GCM'].indexOf(mode) !== -1);
+
+  // do key expansion
+  this._w = _expandKey(key, options.decrypt && !encryptOp);
+  this._init = true;
+};
+
+/**
+ * Expands a key. Typically only used for testing.
+ *
+ * @param key the symmetric key to expand, as an array of 32-bit words.
+ * @param decrypt true to expand for decryption, false for encryption.
+ *
+ * @return the expanded key.
+ */
+forge.aes._expandKey = function(key, decrypt) {
+  if(!init) {
+    initialize();
+  }
+  return _expandKey(key, decrypt);
+};
+
+/**
+ * Updates a single block. Typically only used for testing.
+ *
+ * @param w the expanded key to use.
+ * @param input an array of block-size 32-bit words.
+ * @param output an array of block-size 32-bit words.
+ * @param decrypt true to decrypt, false to encrypt.
+ */
+forge.aes._updateBlock = _updateBlock;
+
+
+/** Register AES algorithms **/
+
+registerAlgorithm('AES-CBC', forge.cipher.modes.cbc);
+registerAlgorithm('AES-CFB', forge.cipher.modes.cfb);
+registerAlgorithm('AES-OFB', forge.cipher.modes.ofb);
+registerAlgorithm('AES-CTR', forge.cipher.modes.ctr);
+registerAlgorithm('AES-GCM', forge.cipher.modes.gcm);
+
+function registerAlgorithm(name, mode) {
+  var factory = function() {
+    return new forge.aes.Algorithm(name, mode);
+  };
+  forge.cipher.registerAlgorithm(name, factory);
+}
+
+
+/** AES implementation **/
+
+var init = false; // not yet initialized
+var Nb = 4;       // number of words comprising the state (AES = 4)
+var sbox;         // non-linear substitution table used in key expansion
+var isbox;        // inversion of sbox
+var rcon;         // round constant word array
+var mix;          // mix-columns table
+var imix;         // inverse mix-columns table
+
+/**
+ * Performs initialization, ie: precomputes tables to optimize for speed.
+ *
+ * One way to understand how AES works is to imagine that 'addition' and
+ * 'multiplication' are interfaces that require certain mathematical
+ * properties to hold true (ie: they are associative) but they might have
+ * different implementations and produce different kinds of results ...
+ * provided that their mathematical properties remain true. AES defines
+ * its own methods of addition and multiplication but keeps some important
+ * properties the same, ie: associativity and distributivity. The
+ * explanation below tries to shed some light on how AES defines addition
+ * and multiplication of bytes and 32-bit words in order to perform its
+ * encryption and decryption algorithms.
+ *
+ * The basics:
+ *
+ * The AES algorithm views bytes as binary representations of polynomials
+ * that have either 1 or 0 as the coefficients. It defines the addition
+ * or subtraction of two bytes as the XOR operation. It also defines the
+ * multiplication of two bytes as a finite field referred to as GF(2^8)
+ * (Note: 'GF' means "Galois Field" which is a field that contains a finite
+ * number of elements so GF(2^8) has 256 elements).
+ *
+ * This means that any two bytes can be represented as binary polynomials;
+ * when they multiplied together and modularly reduced by an irreducible
+ * polynomial of the 8th degree, the results are the field GF(2^8). The
+ * specific irreducible polynomial that AES uses in hexadecimal is 0x11b.
+ * This multiplication is associative with 0x01 as the identity:
+ *
+ * (b * 0x01 = GF(b, 0x01) = b).
+ *
+ * The operation GF(b, 0x02) can be performed at the byte level by left
+ * shifting b once and then XOR'ing it (to perform the modular reduction)
+ * with 0x11b if b is >= 128. Repeated application of the multiplication
+ * of 0x02 can be used to implement the multiplication of any two bytes.
+ *
+ * For instance, multiplying 0x57 and 0x13, denoted as GF(0x57, 0x13), can
+ * be performed by factoring 0x13 into 0x01, 0x02, and 0x10. Then these
+ * factors can each be multiplied by 0x57 and then added together. To do
+ * the multiplication, values for 0x57 multiplied by each of these 3 factors
+ * can be precomputed and stored in a table. To add them, the values from
+ * the table are XOR'd together.
+ *
+ * AES also defines addition and multiplication of words, that is 4-byte
+ * numbers represented as polynomials of 3 degrees where the coefficients
+ * are the values of the bytes.
+ *
+ * The word [a0, a1, a2, a3] is a polynomial a3x^3 + a2x^2 + a1x + a0.
+ *
+ * Addition is performed by XOR'ing like powers of x. Multiplication
+ * is performed in two steps, the first is an algebriac expansion as
+ * you would do normally (where addition is XOR). But the result is
+ * a polynomial larger than 3 degrees and thus it cannot fit in a word. So
+ * next the result is modularly reduced by an AES-specific polynomial of
+ * degree 4 which will always produce a polynomial of less than 4 degrees
+ * such that it will fit in a word. In AES, this polynomial is x^4 + 1.
+ *
+ * The modular product of two polynomials 'a' and 'b' is thus:
+ *
+ * d(x) = d3x^3 + d2x^2 + d1x + d0
+ * with
+ * d0 = GF(a0, b0) ^ GF(a3, b1) ^ GF(a2, b2) ^ GF(a1, b3)
+ * d1 = GF(a1, b0) ^ GF(a0, b1) ^ GF(a3, b2) ^ GF(a2, b3)
+ * d2 = GF(a2, b0) ^ GF(a1, b1) ^ GF(a0, b2) ^ GF(a3, b3)
+ * d3 = GF(a3, b0) ^ GF(a2, b1) ^ GF(a1, b2) ^ GF(a0, b3)
+ *
+ * As a matrix:
+ *
+ * [d0] = [a0 a3 a2 a1][b0]
+ * [d1]   [a1 a0 a3 a2][b1]
+ * [d2]   [a2 a1 a0 a3][b2]
+ * [d3]   [a3 a2 a1 a0][b3]
+ *
+ * Special polynomials defined by AES (0x02 == {02}):
+ * a(x)    = {03}x^3 + {01}x^2 + {01}x + {02}
+ * a^-1(x) = {0b}x^3 + {0d}x^2 + {09}x + {0e}.
+ *
+ * These polynomials are used in the MixColumns() and InverseMixColumns()
+ * operations, respectively, to cause each element in the state to affect
+ * the output (referred to as diffusing).
+ *
+ * RotWord() uses: a0 = a1 = a2 = {00} and a3 = {01}, which is the
+ * polynomial x3.
+ *
+ * The ShiftRows() method modifies the last 3 rows in the state (where
+ * the state is 4 words with 4 bytes per word) by shifting bytes cyclically.
+ * The 1st byte in the second row is moved to the end of the row. The 1st
+ * and 2nd bytes in the third row are moved to the end of the row. The 1st,
+ * 2nd, and 3rd bytes are moved in the fourth row.
+ *
+ * More details on how AES arithmetic works:
+ *
+ * In the polynomial representation of binary numbers, XOR performs addition
+ * and subtraction and multiplication in GF(2^8) denoted as GF(a, b)
+ * corresponds with the multiplication of polynomials modulo an irreducible
+ * polynomial of degree 8. In other words, for AES, GF(a, b) will multiply
+ * polynomial 'a' with polynomial 'b' and then do a modular reduction by
+ * an AES-specific irreducible polynomial of degree 8.
+ *
+ * A polynomial is irreducible if its only divisors are one and itself. For
+ * the AES algorithm, this irreducible polynomial is:
+ *
+ * m(x) = x^8 + x^4 + x^3 + x + 1,
+ *
+ * or {01}{1b} in hexadecimal notation, where each coefficient is a bit:
+ * 100011011 = 283 = 0x11b.
+ *
+ * For example, GF(0x57, 0x83) = 0xc1 because
+ *
+ * 0x57 = 87  = 01010111 = x^6 + x^4 + x^2 + x + 1
+ * 0x85 = 131 = 10000101 = x^7 + x + 1
+ *
+ * (x^6 + x^4 + x^2 + x + 1) * (x^7 + x + 1)
+ * =  x^13 + x^11 + x^9 + x^8 + x^7 +
+ *    x^7 + x^5 + x^3 + x^2 + x +
+ *    x^6 + x^4 + x^2 + x + 1
+ * =  x^13 + x^11 + x^9 + x^8 + x^6 + x^5 + x^4 + x^3 + 1 = y
+ *    y modulo (x^8 + x^4 + x^3 + x + 1)
+ * =  x^7 + x^6 + 1.
+ *
+ * The modular reduction by m(x) guarantees the result will be a binary
+ * polynomial of less than degree 8, so that it can fit in a byte.
+ *
+ * The operation to multiply a binary polynomial b with x (the polynomial
+ * x in binary representation is 00000010) is:
+ *
+ * b_7x^8 + b_6x^7 + b_5x^6 + b_4x^5 + b_3x^4 + b_2x^3 + b_1x^2 + b_0x^1
+ *
+ * To get GF(b, x) we must reduce that by m(x). If b_7 is 0 (that is the
+ * most significant bit is 0 in b) then the result is already reduced. If
+ * it is 1, then we can reduce it by subtracting m(x) via an XOR.
+ *
+ * It follows that multiplication by x (00000010 or 0x02) can be implemented
+ * by performing a left shift followed by a conditional bitwise XOR with
+ * 0x1b. This operation on bytes is denoted by xtime(). Multiplication by
+ * higher powers of x can be implemented by repeated application of xtime().
+ *
+ * By adding intermediate results, multiplication by any constant can be
+ * implemented. For instance:
+ *
+ * GF(0x57, 0x13) = 0xfe because:
+ *
+ * xtime(b) = (b & 128) ? (b << 1 ^ 0x11b) : (b << 1)
+ *
+ * Note: We XOR with 0x11b instead of 0x1b because in javascript our
+ * datatype for b can be larger than 1 byte, so a left shift will not
+ * automatically eliminate bits that overflow a byte ... by XOR'ing the
+ * overflow bit with 1 (the extra one from 0x11b) we zero it out.
+ *
+ * GF(0x57, 0x02) = xtime(0x57) = 0xae
+ * GF(0x57, 0x04) = xtime(0xae) = 0x47
+ * GF(0x57, 0x08) = xtime(0x47) = 0x8e
+ * GF(0x57, 0x10) = xtime(0x8e) = 0x07
+ *
+ * GF(0x57, 0x13) = GF(0x57, (0x01 ^ 0x02 ^ 0x10))
+ *
+ * And by the distributive property (since XOR is addition and GF() is
+ * multiplication):
+ *
+ * = GF(0x57, 0x01) ^ GF(0x57, 0x02) ^ GF(0x57, 0x10)
+ * = 0x57 ^ 0xae ^ 0x07
+ * = 0xfe.
+ */
+function initialize() {
+  init = true;
+
+  /* Populate the Rcon table. These are the values given by
+    [x^(i-1),{00},{00},{00}] where x^(i-1) are powers of x (and x = 0x02)
+    in the field of GF(2^8), where i starts at 1.
+
+    rcon[0] = [0x00, 0x00, 0x00, 0x00]
+    rcon[1] = [0x01, 0x00, 0x00, 0x00] 2^(1-1) = 2^0 = 1
+    rcon[2] = [0x02, 0x00, 0x00, 0x00] 2^(2-1) = 2^1 = 2
+    ...
+    rcon[9]  = [0x1B, 0x00, 0x00, 0x00] 2^(9-1)  = 2^8 = 0x1B
+    rcon[10] = [0x36, 0x00, 0x00, 0x00] 2^(10-1) = 2^9 = 0x36
+
+    We only store the first byte because it is the only one used.
+  */
+  rcon = [0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36];
+
+  // compute xtime table which maps i onto GF(i, 0x02)
+  var xtime = new Array(256);
+  for(var i = 0; i < 128; ++i) {
+    xtime[i] = i << 1;
+    xtime[i + 128] = (i + 128) << 1 ^ 0x11B;
+  }
+
+  // compute all other tables
+  sbox = new Array(256);
+  isbox = new Array(256);
+  mix = new Array(4);
+  imix = new Array(4);
+  for(var i = 0; i < 4; ++i) {
+    mix[i] = new Array(256);
+    imix[i] = new Array(256);
+  }
+  var e = 0, ei = 0, e2, e4, e8, sx, sx2, me, ime;
+  for(var i = 0; i < 256; ++i) {
+    /* We need to generate the SubBytes() sbox and isbox tables so that
+      we can perform byte substitutions. This requires us to traverse
+      all of the elements in GF, find their multiplicative inverses,
+      and apply to each the following affine transformation:
+
+      bi' = bi ^ b(i + 4) mod 8 ^ b(i + 5) mod 8 ^ b(i + 6) mod 8 ^
+            b(i + 7) mod 8 ^ ci
+      for 0 <= i < 8, where bi is the ith bit of the byte, and ci is the
+      ith bit of a byte c with the value {63} or {01100011}.
+
+      It is possible to traverse every possible value in a Galois field
+      using what is referred to as a 'generator'. There are many
+      generators (128 out of 256): 3,5,6,9,11,82 to name a few. To fully
+      traverse GF we iterate 255 times, multiplying by our generator
+      each time.
+
+      On each iteration we can determine the multiplicative inverse for
+      the current element.
+
+      Suppose there is an element in GF 'e'. For a given generator 'g',
+      e = g^x. The multiplicative inverse of e is g^(255 - x). It turns
+      out that if use the inverse of a generator as another generator
+      it will produce all of the corresponding multiplicative inverses
+      at the same time. For this reason, we choose 5 as our inverse
+      generator because it only requires 2 multiplies and 1 add and its
+      inverse, 82, requires relatively few operations as well.
+
+      In order to apply the affine transformation, the multiplicative
+      inverse 'ei' of 'e' can be repeatedly XOR'd (4 times) with a
+      bit-cycling of 'ei'. To do this 'ei' is first stored in 's' and
+      'x'. Then 's' is left shifted and the high bit of 's' is made the
+      low bit. The resulting value is stored in 's'. Then 'x' is XOR'd
+      with 's' and stored in 'x'. On each subsequent iteration the same
+      operation is performed. When 4 iterations are complete, 'x' is
+      XOR'd with 'c' (0x63) and the transformed value is stored in 'x'.
+      For example:
+
+      s = 01000001
+      x = 01000001
+
+      iteration 1: s = 10000010, x ^= s
+      iteration 2: s = 00000101, x ^= s
+      iteration 3: s = 00001010, x ^= s
+      iteration 4: s = 00010100, x ^= s
+      x ^= 0x63
+
+      This can be done with a loop where s = (s << 1) | (s >> 7). However,
+      it can also be done by using a single 16-bit (in this case 32-bit)
+      number 'sx'. Since XOR is an associative operation, we can set 'sx'
+      to 'ei' and then XOR it with 'sx' left-shifted 1,2,3, and 4 times.
+      The most significant bits will flow into the high 8 bit positions
+      and be correctly XOR'd with one another. All that remains will be
+      to cycle the high 8 bits by XOR'ing them all with the lower 8 bits
+      afterwards.
+
+      At the same time we're populating sbox and isbox we can precompute
+      the multiplication we'll need to do to do MixColumns() later.
+    */
+
+    // apply affine transformation
+    sx = ei ^ (ei << 1) ^ (ei << 2) ^ (ei << 3) ^ (ei << 4);
+    sx = (sx >> 8) ^ (sx & 255) ^ 0x63;
+
+    // update tables
+    sbox[e] = sx;
+    isbox[sx] = e;
+
+    /* Mixing columns is done using matrix multiplication. The columns
+      that are to be mixed are each a single word in the current state.
+      The state has Nb columns (4 columns). Therefore each column is a
+      4 byte word. So to mix the columns in a single column 'c' where
+      its rows are r0, r1, r2, and r3, we use the following matrix
+      multiplication:
+
+      [2 3 1 1]*[r0,c]=[r'0,c]
+      [1 2 3 1] [r1,c] [r'1,c]
+      [1 1 2 3] [r2,c] [r'2,c]
+      [3 1 1 2] [r3,c] [r'3,c]
+
+      r0, r1, r2, and r3 are each 1 byte of one of the words in the
+      state (a column). To do matrix multiplication for each mixed
+      column c' we multiply the corresponding row from the left matrix
+      with the corresponding column from the right matrix. In total, we
+      get 4 equations:
+
+      r0,c' = 2*r0,c + 3*r1,c + 1*r2,c + 1*r3,c
+      r1,c' = 1*r0,c + 2*r1,c + 3*r2,c + 1*r3,c
+      r2,c' = 1*r0,c + 1*r1,c + 2*r2,c + 3*r3,c
+      r3,c' = 3*r0,c + 1*r1,c + 1*r2,c + 2*r3,c
+
+      As usual, the multiplication is as previously defined and the
+      addition is XOR. In order to optimize mixing columns we can store
+      the multiplication results in tables. If you think of the whole
+      column as a word (it might help to visualize by mentally rotating
+      the equations above by counterclockwise 90 degrees) then you can
+      see that it would be useful to map the multiplications performed on
+      each byte (r0, r1, r2, r3) onto a word as well. For instance, we
+      could map 2*r0,1*r0,1*r0,3*r0 onto a word by storing 2*r0 in the
+      highest 8 bits and 3*r0 in the lowest 8 bits (with the other two
+      respectively in the middle). This means that a table can be
+      constructed that uses r0 as an index to the word. We can do the
+      same with r1, r2, and r3, creating a total of 4 tables.
+
+      To construct a full c', we can just look up each byte of c in
+      their respective tables and XOR the results together.
+
+      Also, to build each table we only have to calculate the word
+      for 2,1,1,3 for every byte ... which we can do on each iteration
+      of this loop since we will iterate over every byte. After we have
+      calculated 2,1,1,3 we can get the results for the other tables
+      by cycling the byte at the end to the beginning. For instance
+      we can take the result of table 2,1,1,3 and produce table 3,2,1,1
+      by moving the right most byte to the left most position just like
+      how you can imagine the 3 moved out of 2,1,1,3 and to the front
+      to produce 3,2,1,1.
+
+      There is another optimization in that the same multiples of
+      the current element we need in order to advance our generator
+      to the next iteration can be reused in performing the 2,1,1,3
+      calculation. We also calculate the inverse mix column tables,
+      with e,9,d,b being the inverse of 2,1,1,3.
+
+      When we're done, and we need to actually mix columns, the first
+      byte of each state word should be put through mix[0] (2,1,1,3),
+      the second through mix[1] (3,2,1,1) and so forth. Then they should
+      be XOR'd together to produce the fully mixed column.
+    */
+
+    // calculate mix and imix table values
+    sx2 = xtime[sx];
+    e2 = xtime[e];
+    e4 = xtime[e2];
+    e8 = xtime[e4];
+    me =
+      (sx2 << 24) ^  // 2
+      (sx << 16) ^   // 1
+      (sx << 8) ^    // 1
+      (sx ^ sx2);    // 3
+    ime =
+      (e2 ^ e4 ^ e8) << 24 ^  // E (14)
+      (e ^ e8) << 16 ^        // 9
+      (e ^ e4 ^ e8) << 8 ^    // D (13)
+      (e ^ e2 ^ e8);          // B (11)
+    // produce each of the mix tables by rotating the 2,1,1,3 value
+    for(var n = 0; n < 4; ++n) {
+      mix[n][e] = me;
+      imix[n][sx] = ime;
+      // cycle the right most byte to the left most position
+      // ie: 2,1,1,3 becomes 3,2,1,1
+      me = me << 24 | me >>> 8;
+      ime = ime << 24 | ime >>> 8;
+    }
+
+    // get next element and inverse
+    if(e === 0) {
+      // 1 is the inverse of 1
+      e = ei = 1;
+    } else {
+      // e = 2e + 2*2*2*(10e)) = multiply e by 82 (chosen generator)
+      // ei = ei + 2*2*ei = multiply ei by 5 (inverse generator)
+      e = e2 ^ xtime[xtime[xtime[e2 ^ e8]]];
+      ei ^= xtime[xtime[ei]];
+    }
+  }
+}
+
+/**
+ * Generates a key schedule using the AES key expansion algorithm.
+ *
+ * The AES algorithm takes the Cipher Key, K, and performs a Key Expansion
+ * routine to generate a key schedule. The Key Expansion generates a total
+ * of Nb*(Nr + 1) words: the algorithm requires an initial set of Nb words,
+ * and each of the Nr rounds requires Nb words of key data. The resulting
+ * key schedule consists of a linear array of 4-byte words, denoted [wi ],
+ * with i in the range 0 ≤ i < Nb(Nr + 1).
+ *
+ * KeyExpansion(byte key[4*Nk], word w[Nb*(Nr+1)], Nk)
+ * AES-128 (Nb=4, Nk=4, Nr=10)
+ * AES-192 (Nb=4, Nk=6, Nr=12)
+ * AES-256 (Nb=4, Nk=8, Nr=14)
+ * Note: Nr=Nk+6.
+ *
+ * Nb is the number of columns (32-bit words) comprising the State (or
+ * number of bytes in a block). For AES, Nb=4.
+ *
+ * @param key the key to schedule (as an array of 32-bit words).
+ * @param decrypt true to modify the key schedule to decrypt, false not to.
+ *
+ * @return the generated key schedule.
+ */
+function _expandKey(key, decrypt) {
+  // copy the key's words to initialize the key schedule
+  var w = key.slice(0);
+
+  /* RotWord() will rotate a word, moving the first byte to the last
+    byte's position (shifting the other bytes left).
+
+    We will be getting the value of Rcon at i / Nk. 'i' will iterate
+    from Nk to (Nb * Nr+1). Nk = 4 (4 byte key), Nb = 4 (4 words in
+    a block), Nr = Nk + 6 (10). Therefore 'i' will iterate from
+    4 to 44 (exclusive). Each time we iterate 4 times, i / Nk will
+    increase by 1. We use a counter iNk to keep track of this.
+   */
+
+  // go through the rounds expanding the key
+  var temp, iNk = 1;
+  var Nk = w.length;
+  var Nr1 = Nk + 6 + 1;
+  var end = Nb * Nr1;
+  for(var i = Nk; i < end; ++i) {
+    temp = w[i - 1];
+    if(i % Nk === 0) {
+      // temp = SubWord(RotWord(temp)) ^ Rcon[i / Nk]
+      temp =
+        sbox[temp >>> 16 & 255] << 24 ^
+        sbox[temp >>> 8 & 255] << 16 ^
+        sbox[temp & 255] << 8 ^
+        sbox[temp >>> 24] ^ (rcon[iNk] << 24);
+      iNk++;
+    } else if(Nk > 6 && (i % Nk === 4)) {
+      // temp = SubWord(temp)
+      temp =
+        sbox[temp >>> 24] << 24 ^
+        sbox[temp >>> 16 & 255] << 16 ^
+        sbox[temp >>> 8 & 255] << 8 ^
+        sbox[temp & 255];
+    }
+    w[i] = w[i - Nk] ^ temp;
+  }
+
+   /* When we are updating a cipher block we always use the code path for
+     encryption whether we are decrypting or not (to shorten code and
+     simplify the generation of look up tables). However, because there
+     are differences in the decryption algorithm, other than just swapping
+     in different look up tables, we must transform our key schedule to
+     account for these changes:
+
+     1. The decryption algorithm gets its key rounds in reverse order.
+     2. The decryption algorithm adds the round key before mixing columns
+       instead of afterwards.
+
+     We don't need to modify our key schedule to handle the first case,
+     we can just traverse the key schedule in reverse order when decrypting.
+
+     The second case requires a little work.
+
+     The tables we built for performing rounds will take an input and then
+     perform SubBytes() and MixColumns() or, for the decrypt version,
+     InvSubBytes() and InvMixColumns(). But the decrypt algorithm requires
+     us to AddRoundKey() before InvMixColumns(). This means we'll need to
+     apply some transformations to the round key to inverse-mix its columns
+     so they'll be correct for moving AddRoundKey() to after the state has
+     had its columns inverse-mixed.
+
+     To inverse-mix the columns of the state when we're decrypting we use a
+     lookup table that will apply InvSubBytes() and InvMixColumns() at the
+     same time. However, the round key's bytes are not inverse-substituted
+     in the decryption algorithm. To get around this problem, we can first
+     substitute the bytes in the round key so that when we apply the
+     transformation via the InvSubBytes()+InvMixColumns() table, it will
+     undo our substitution leaving us with the original value that we
+     want -- and then inverse-mix that value.
+
+     This change will correctly alter our key schedule so that we can XOR
+     each round key with our already transformed decryption state. This
+     allows us to use the same code path as the encryption algorithm.
+
+     We make one more change to the decryption key. Since the decryption
+     algorithm runs in reverse from the encryption algorithm, we reverse
+     the order of the round keys to avoid having to iterate over the key
+     schedule backwards when running the encryption algorithm later in
+     decryption mode. In addition to reversing the order of the round keys,
+     we also swap each round key's 2nd and 4th rows. See the comments
+     section where rounds are performed for more details about why this is
+     done. These changes are done inline with the other substitution
+     described above.
+  */
+  if(decrypt) {
+    var tmp;
+    var m0 = imix[0];
+    var m1 = imix[1];
+    var m2 = imix[2];
+    var m3 = imix[3];
+    var wnew = w.slice(0);
+    end = w.length;
+    for(var i = 0, wi = end - Nb; i < end; i += Nb, wi -= Nb) {
+      // do not sub the first or last round key (round keys are Nb
+      // words) as no column mixing is performed before they are added,
+      // but do change the key order
+      if(i === 0 || i === (end - Nb)) {
+        wnew[i] = w[wi];
+        wnew[i + 1] = w[wi + 3];
+        wnew[i + 2] = w[wi + 2];
+        wnew[i + 3] = w[wi + 1];
+      } else {
+        // substitute each round key byte because the inverse-mix
+        // table will inverse-substitute it (effectively cancel the
+        // substitution because round key bytes aren't sub'd in
+        // decryption mode) and swap indexes 3 and 1
+        for(var n = 0; n < Nb; ++n) {
+          tmp = w[wi + n];
+          wnew[i + (3&-n)] =
+            m0[sbox[tmp >>> 24]] ^
+            m1[sbox[tmp >>> 16 & 255]] ^
+            m2[sbox[tmp >>> 8 & 255]] ^
+            m3[sbox[tmp & 255]];
+        }
+      }
+    }
+    w = wnew;
+  }
+
+  return w;
+}
+
+/**
+ * Updates a single block (16 bytes) using AES. The update will either
+ * encrypt or decrypt the block.
+ *
+ * @param w the key schedule.
+ * @param input the input block (an array of 32-bit words).
+ * @param output the updated output block.
+ * @param decrypt true to decrypt the block, false to encrypt it.
+ */
+function _updateBlock(w, input, output, decrypt) {
+  /*
+  Cipher(byte in[4*Nb], byte out[4*Nb], word w[Nb*(Nr+1)])
+  begin
+    byte state[4,Nb]
+    state = in
+    AddRoundKey(state, w[0, Nb-1])
+    for round = 1 step 1 to Nr–1
+      SubBytes(state)
+      ShiftRows(state)
+      MixColumns(state)
+      AddRoundKey(state, w[round*Nb, (round+1)*Nb-1])
+    end for
+    SubBytes(state)
+    ShiftRows(state)
+    AddRoundKey(state, w[Nr*Nb, (Nr+1)*Nb-1])
+    out = state
+  end
+
+  InvCipher(byte in[4*Nb], byte out[4*Nb], word w[Nb*(Nr+1)])
+  begin
+    byte state[4,Nb]
+    state = in
+    AddRoundKey(state, w[Nr*Nb, (Nr+1)*Nb-1])
+    for round = Nr-1 step -1 downto 1
+      InvShiftRows(state)
+      InvSubBytes(state)
+      AddRoundKey(state, w[round*Nb, (round+1)*Nb-1])
+      InvMixColumns(state)
+    end for
+    InvShiftRows(state)
+    InvSubBytes(state)
+    AddRoundKey(state, w[0, Nb-1])
+    out = state
+  end
+  */
+
+  // Encrypt: AddRoundKey(state, w[0, Nb-1])
+  // Decrypt: AddRoundKey(state, w[Nr*Nb, (Nr+1)*Nb-1])
+  var Nr = w.length / 4 - 1;
+  var m0, m1, m2, m3, sub;
+  if(decrypt) {
+    m0 = imix[0];
+    m1 = imix[1];
+    m2 = imix[2];
+    m3 = imix[3];
+    sub = isbox;
+  } else {
+    m0 = mix[0];
+    m1 = mix[1];
+    m2 = mix[2];
+    m3 = mix[3];
+    sub = sbox;
+  }
+  var a, b, c, d, a2, b2, c2;
+  a = input[0] ^ w[0];
+  b = input[decrypt ? 3 : 1] ^ w[1];
+  c = input[2] ^ w[2];
+  d = input[decrypt ? 1 : 3] ^ w[3];
+  var i = 3;
+
+  /* In order to share code we follow the encryption algorithm when both
+    encrypting and decrypting. To account for the changes required in the
+    decryption algorithm, we use different lookup tables when decrypting
+    and use a modified key schedule to account for the difference in the
+    order of transformations applied when performing rounds. We also get
+    key rounds in reverse order (relative to encryption). */
+  for(var round = 1; round < Nr; ++round) {
+    /* As described above, we'll be using table lookups to perform the
+      column mixing. Each column is stored as a word in the state (the
+      array 'input' has one column as a word at each index). In order to
+      mix a column, we perform these transformations on each row in c,
+      which is 1 byte in each word. The new column for c0 is c'0:
+
+               m0      m1      m2      m3
+      r0,c'0 = 2*r0,c0 + 3*r1,c0 + 1*r2,c0 + 1*r3,c0
+      r1,c'0 = 1*r0,c0 + 2*r1,c0 + 3*r2,c0 + 1*r3,c0
+      r2,c'0 = 1*r0,c0 + 1*r1,c0 + 2*r2,c0 + 3*r3,c0
+      r3,c'0 = 3*r0,c0 + 1*r1,c0 + 1*r2,c0 + 2*r3,c0
+
+      So using mix tables where c0 is a word with r0 being its upper
+      8 bits and r3 being its lower 8 bits:
+
+      m0[c0 >> 24] will yield this word: [2*r0,1*r0,1*r0,3*r0]
+      ...
+      m3[c0 & 255] will yield this word: [1*r3,1*r3,3*r3,2*r3]
+
+      Therefore to mix the columns in each word in the state we
+      do the following (& 255 omitted for brevity):
+      c'0,r0 = m0[c0 >> 24] ^ m1[c1 >> 16] ^ m2[c2 >> 8] ^ m3[c3]
+      c'0,r1 = m0[c0 >> 24] ^ m1[c1 >> 16] ^ m2[c2 >> 8] ^ m3[c3]
+      c'0,r2 = m0[c0 >> 24] ^ m1[c1 >> 16] ^ m2[c2 >> 8] ^ m3[c3]
+      c'0,r3 = m0[c0 >> 24] ^ m1[c1 >> 16] ^ m2[c2 >> 8] ^ m3[c3]
+
+      However, before mixing, the algorithm requires us to perform
+      ShiftRows(). The ShiftRows() transformation cyclically shifts the
+      last 3 rows of the state over different offsets. The first row
+      (r = 0) is not shifted.
+
+      s'_r,c = s_r,(c + shift(r, Nb) mod Nb
+      for 0 < r < 4 and 0 <= c < Nb and
+      shift(1, 4) = 1
+      shift(2, 4) = 2
+      shift(3, 4) = 3.
+
+      This causes the first byte in r = 1 to be moved to the end of
+      the row, the first 2 bytes in r = 2 to be moved to the end of
+      the row, the first 3 bytes in r = 3 to be moved to the end of
+      the row:
+
+      r1: [c0 c1 c2 c3] => [c1 c2 c3 c0]
+      r2: [c0 c1 c2 c3]    [c2 c3 c0 c1]
+      r3: [c0 c1 c2 c3]    [c3 c0 c1 c2]
+
+      We can make these substitutions inline with our column mixing to
+      generate an updated set of equations to produce each word in the
+      state (note the columns have changed positions):
+
+      c0 c1 c2 c3 => c0 c1 c2 c3
+      c0 c1 c2 c3    c1 c2 c3 c0  (cycled 1 byte)
+      c0 c1 c2 c3    c2 c3 c0 c1  (cycled 2 bytes)
+      c0 c1 c2 c3    c3 c0 c1 c2  (cycled 3 bytes)
+
+      Therefore:
+
+      c'0 = 2*r0,c0 + 3*r1,c1 + 1*r2,c2 + 1*r3,c3
+      c'0 = 1*r0,c0 + 2*r1,c1 + 3*r2,c2 + 1*r3,c3
+      c'0 = 1*r0,c0 + 1*r1,c1 + 2*r2,c2 + 3*r3,c3
+      c'0 = 3*r0,c0 + 1*r1,c1 + 1*r2,c2 + 2*r3,c3
+
+      c'1 = 2*r0,c1 + 3*r1,c2 + 1*r2,c3 + 1*r3,c0
+      c'1 = 1*r0,c1 + 2*r1,c2 + 3*r2,c3 + 1*r3,c0
+      c'1 = 1*r0,c1 + 1*r1,c2 + 2*r2,c3 + 3*r3,c0
+      c'1 = 3*r0,c1 + 1*r1,c2 + 1*r2,c3 + 2*r3,c0
+
+      ... and so forth for c'2 and c'3. The important distinction is
+      that the columns are cycling, with c0 being used with the m0
+      map when calculating c0, but c1 being used with the m0 map when
+      calculating c1 ... and so forth.
+
+      When performing the inverse we transform the mirror image and
+      skip the bottom row, instead of the top one, and move upwards:
+
+      c3 c2 c1 c0 => c0 c3 c2 c1  (cycled 3 bytes) *same as encryption
+      c3 c2 c1 c0    c1 c0 c3 c2  (cycled 2 bytes)
+      c3 c2 c1 c0    c2 c1 c0 c3  (cycled 1 byte)  *same as encryption
+      c3 c2 c1 c0    c3 c2 c1 c0
+
+      If you compare the resulting matrices for ShiftRows()+MixColumns()
+      and for InvShiftRows()+InvMixColumns() the 2nd and 4th columns are
+      different (in encrypt mode vs. decrypt mode). So in order to use
+      the same code to handle both encryption and decryption, we will
+      need to do some mapping.
+
+      If in encryption mode we let a=c0, b=c1, c=c2, d=c3, and r<N> be
+      a row number in the state, then the resulting matrix in encryption
+      mode for applying the above transformations would be:
+
+      r1: a b c d
+      r2: b c d a
+      r3: c d a b
+      r4: d a b c
+
+      If we did the same in decryption mode we would get:
+
+      r1: a d c b
+      r2: b a d c
+      r3: c b a d
+      r4: d c b a
+
+      If instead we swap d and b (set b=c3 and d=c1), then we get:
+
+      r1: a b c d
+      r2: d a b c
+      r3: c d a b
+      r4: b c d a
+
+      Now the 1st and 3rd rows are the same as the encryption matrix. All
+      we need to do then to make the mapping exactly the same is to swap
+      the 2nd and 4th rows when in decryption mode. To do this without
+      having to do it on each iteration, we swapped the 2nd and 4th rows
+      in the decryption key schedule. We also have to do the swap above
+      when we first pull in the input and when we set the final output. */
+    a2 =
+      m0[a >>> 24] ^
+      m1[b >>> 16 & 255] ^
+      m2[c >>> 8 & 255] ^
+      m3[d & 255] ^ w[++i];
+    b2 =
+      m0[b >>> 24] ^
+      m1[c >>> 16 & 255] ^
+      m2[d >>> 8 & 255] ^
+      m3[a & 255] ^ w[++i];
+    c2 =
+      m0[c >>> 24] ^
+      m1[d >>> 16 & 255] ^
+      m2[a >>> 8 & 255] ^
+      m3[b & 255] ^ w[++i];
+    d =
+      m0[d >>> 24] ^
+      m1[a >>> 16 & 255] ^
+      m2[b >>> 8 & 255] ^
+      m3[c & 255] ^ w[++i];
+    a = a2;
+    b = b2;
+    c = c2;
+  }
+
+  /*
+    Encrypt:
+    SubBytes(state)
+    ShiftRows(state)
+    AddRoundKey(state, w[Nr*Nb, (Nr+1)*Nb-1])
+
+    Decrypt:
+    InvShiftRows(state)
+    InvSubBytes(state)
+    AddRoundKey(state, w[0, Nb-1])
+   */
+   // Note: rows are shifted inline
+  output[0] =
+    (sub[a >>> 24] << 24) ^
+    (sub[b >>> 16 & 255] << 16) ^
+    (sub[c >>> 8 & 255] << 8) ^
+    (sub[d & 255]) ^ w[++i];
+  output[decrypt ? 3 : 1] =
+    (sub[b >>> 24] << 24) ^
+    (sub[c >>> 16 & 255] << 16) ^
+    (sub[d >>> 8 & 255] << 8) ^
+    (sub[a & 255]) ^ w[++i];
+  output[2] =
+    (sub[c >>> 24] << 24) ^
+    (sub[d >>> 16 & 255] << 16) ^
+    (sub[a >>> 8 & 255] << 8) ^
+    (sub[b & 255]) ^ w[++i];
+  output[decrypt ? 1 : 3] =
+    (sub[d >>> 24] << 24) ^
+    (sub[a >>> 16 & 255] << 16) ^
+    (sub[b >>> 8 & 255] << 8) ^
+    (sub[c & 255]) ^ w[++i];
+}
+
+/**
+ * Deprecated. Instead, use:
+ *
+ * forge.cipher.createCipher('AES-<mode>', key);
+ * forge.cipher.createDecipher('AES-<mode>', key);
+ *
+ * Creates a deprecated AES cipher object. This object's mode will default to
+ * CBC (cipher-block-chaining).
+ *
+ * The key and iv may be given as a string of bytes, an array of bytes, a
+ * byte buffer, or an array of 32-bit words.
+ *
+ * @param options the options to use.
+ *          key the symmetric key to use.
+ *          output the buffer to write to.
+ *          decrypt true for decryption, false for encryption.
+ *          mode the cipher mode to use (default: 'CBC').
+ *
+ * @return the cipher.
+ */
+function _createCipher(options) {
+  options = options || {};
+  var mode = (options.mode || 'CBC').toUpperCase();
+  var algorithm = 'AES-' + mode;
+
+  var cipher;
+  if(options.decrypt) {
+    cipher = forge.cipher.createDecipher(algorithm, options.key);
+  } else {
+    cipher = forge.cipher.createCipher(algorithm, options.key);
+  }
+
+  // backwards compatible start API
+  var start = cipher.start;
+  cipher.start = function(iv, options) {
+    // backwards compatibility: support second arg as output buffer
+    var output = null;
+    if(options instanceof forge.util.ByteBuffer) {
+      output = options;
+      options = {};
+    }
+    options = options || {};
+    options.output = output;
+    options.iv = iv;
+    start.call(cipher, options);
+  };
+
+  return cipher;
+}
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'aes';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(
+  ['require', 'module', './cipher', './cipherModes', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/aesCipherSuites.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/aesCipherSuites.js
new file mode 100644
index 0000000..b0b4b4f
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/aesCipherSuites.js
@@ -0,0 +1,312 @@
+/**
+ * A Javascript implementation of AES Cipher Suites for TLS.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2009-2014 Digital Bazaar, Inc.
+ *
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+var tls = forge.tls;
+
+/**
+ * Supported cipher suites.
+ */
+tls.CipherSuites['TLS_RSA_WITH_AES_128_CBC_SHA'] = {
+  id: [0x00,0x2f],
+  name: 'TLS_RSA_WITH_AES_128_CBC_SHA',
+  initSecurityParameters: function(sp) {
+    sp.bulk_cipher_algorithm = tls.BulkCipherAlgorithm.aes;
+    sp.cipher_type = tls.CipherType.block;
+    sp.enc_key_length = 16;
+    sp.block_length = 16;
+    sp.fixed_iv_length = 16;
+    sp.record_iv_length = 16;
+    sp.mac_algorithm = tls.MACAlgorithm.hmac_sha1;
+    sp.mac_length = 20;
+    sp.mac_key_length = 20;
+  },
+  initConnectionState: initConnectionState
+};
+tls.CipherSuites['TLS_RSA_WITH_AES_256_CBC_SHA'] = {
+  id: [0x00,0x35],
+  name: 'TLS_RSA_WITH_AES_256_CBC_SHA',
+  initSecurityParameters: function(sp) {
+    sp.bulk_cipher_algorithm = tls.BulkCipherAlgorithm.aes;
+    sp.cipher_type = tls.CipherType.block;
+    sp.enc_key_length = 32;
+    sp.block_length = 16;
+    sp.fixed_iv_length = 16;
+    sp.record_iv_length = 16;
+    sp.mac_algorithm = tls.MACAlgorithm.hmac_sha1;
+    sp.mac_length = 20;
+    sp.mac_key_length = 20;
+  },
+  initConnectionState: initConnectionState
+};
+
+function initConnectionState(state, c, sp) {
+  var client = (c.entity === forge.tls.ConnectionEnd.client);
+
+  // cipher setup
+  state.read.cipherState = {
+    init: false,
+    cipher: forge.cipher.createDecipher('AES-CBC', client ?
+      sp.keys.server_write_key : sp.keys.client_write_key),
+    iv: client ? sp.keys.server_write_IV : sp.keys.client_write_IV
+  };
+  state.write.cipherState = {
+    init: false,
+    cipher: forge.cipher.createCipher('AES-CBC', client ?
+      sp.keys.client_write_key : sp.keys.server_write_key),
+    iv: client ? sp.keys.client_write_IV : sp.keys.server_write_IV
+  };
+  state.read.cipherFunction = decrypt_aes_cbc_sha1;
+  state.write.cipherFunction = encrypt_aes_cbc_sha1;
+
+  // MAC setup
+  state.read.macLength = state.write.macLength = sp.mac_length;
+  state.read.macFunction = state.write.macFunction = tls.hmac_sha1;
+}
+
+/**
+ * Encrypts the TLSCompressed record into a TLSCipherText record using AES
+ * in CBC mode.
+ *
+ * @param record the TLSCompressed record to encrypt.
+ * @param s the ConnectionState to use.
+ *
+ * @return true on success, false on failure.
+ */
+function encrypt_aes_cbc_sha1(record, s) {
+  var rval = false;
+
+  // append MAC to fragment, update sequence number
+  var mac = s.macFunction(s.macKey, s.sequenceNumber, record);
+  record.fragment.putBytes(mac);
+  s.updateSequenceNumber();
+
+  // TLS 1.1+ use an explicit IV every time to protect against CBC attacks
+  var iv;
+  if(record.version.minor === tls.Versions.TLS_1_0.minor) {
+    // use the pre-generated IV when initializing for TLS 1.0, otherwise use
+    // the residue from the previous encryption
+    iv = s.cipherState.init ? null : s.cipherState.iv;
+  } else {
+    iv = forge.random.getBytesSync(16);
+  }
+
+  s.cipherState.init = true;
+
+  // start cipher
+  var cipher = s.cipherState.cipher;
+  cipher.start({iv: iv});
+
+  // TLS 1.1+ write IV into output
+  if(record.version.minor >= tls.Versions.TLS_1_1.minor) {
+    cipher.output.putBytes(iv);
+  }
+
+  // do encryption (default padding is appropriate)
+  cipher.update(record.fragment);
+  if(cipher.finish(encrypt_aes_cbc_sha1_padding)) {
+    // set record fragment to encrypted output
+    record.fragment = cipher.output;
+    record.length = record.fragment.length();
+    rval = true;
+  }
+
+  return rval;
+}
+
+/**
+ * Handles padding for aes_cbc_sha1 in encrypt mode.
+ *
+ * @param blockSize the block size.
+ * @param input the input buffer.
+ * @param decrypt true in decrypt mode, false in encrypt mode.
+ *
+ * @return true on success, false on failure.
+ */
+function encrypt_aes_cbc_sha1_padding(blockSize, input, decrypt) {
+  /* The encrypted data length (TLSCiphertext.length) is one more than the sum
+   of SecurityParameters.block_length, TLSCompressed.length,
+   SecurityParameters.mac_length, and padding_length.
+
+   The padding may be any length up to 255 bytes long, as long as it results in
+   the TLSCiphertext.length being an integral multiple of the block length.
+   Lengths longer than necessary might be desirable to frustrate attacks on a
+   protocol based on analysis of the lengths of exchanged messages. Each uint8
+   in the padding data vector must be filled with the padding length value.
+
+   The padding length should be such that the total size of the
+   GenericBlockCipher structure is a multiple of the cipher's block length.
+   Legal values range from zero to 255, inclusive. This length specifies the
+   length of the padding field exclusive of the padding_length field itself.
+
+   This is slightly different from PKCS#7 because the padding value is 1
+   less than the actual number of padding bytes if you include the
+   padding_length uint8 itself as a padding byte. */
+  if(!decrypt) {
+    // get the number of padding bytes required to reach the blockSize and
+    // subtract 1 for the padding value (to make room for the padding_length
+    // uint8)
+    var padding = blockSize - (input.length() % blockSize);
+    input.fillWithByte(padding - 1, padding);
+  }
+  return true;
+}
+
+/**
+ * Handles padding for aes_cbc_sha1 in decrypt mode.
+ *
+ * @param blockSize the block size.
+ * @param output the output buffer.
+ * @param decrypt true in decrypt mode, false in encrypt mode.
+ *
+ * @return true on success, false on failure.
+ */
+function decrypt_aes_cbc_sha1_padding(blockSize, output, decrypt) {
+  var rval = true;
+  if(decrypt) {
+    /* The last byte in the output specifies the number of padding bytes not
+      including itself. Each of the padding bytes has the same value as that
+      last byte (known as the padding_length). Here we check all padding
+      bytes to ensure they have the value of padding_length even if one of
+      them is bad in order to ward-off timing attacks. */
+    var len = output.length();
+    var paddingLength = output.last();
+    for(var i = len - 1 - paddingLength; i < len - 1; ++i) {
+      rval = rval && (output.at(i) == paddingLength);
+    }
+    if(rval) {
+      // trim off padding bytes and last padding length byte
+      output.truncate(paddingLength + 1);
+    }
+  }
+  return rval;
+}
+
+/**
+ * Decrypts a TLSCipherText record into a TLSCompressed record using
+ * AES in CBC mode.
+ *
+ * @param record the TLSCipherText record to decrypt.
+ * @param s the ConnectionState to use.
+ *
+ * @return true on success, false on failure.
+ */
+var count = 0;
+function decrypt_aes_cbc_sha1(record, s) {
+  var rval = false;
+  ++count;
+
+  var iv;
+  if(record.version.minor === tls.Versions.TLS_1_0.minor) {
+    // use pre-generated IV when initializing for TLS 1.0, otherwise use the
+    // residue from the previous decryption
+    iv = s.cipherState.init ? null : s.cipherState.iv;
+  } else {
+    // TLS 1.1+ use an explicit IV every time to protect against CBC attacks
+    // that is appended to the record fragment
+    iv = record.fragment.getBytes(16);
+  }
+
+  s.cipherState.init = true;
+
+  // start cipher
+  var cipher = s.cipherState.cipher;
+  cipher.start({iv: iv});
+
+  // do decryption
+  cipher.update(record.fragment);
+  rval = cipher.finish(decrypt_aes_cbc_sha1_padding);
+
+  // even if decryption fails, keep going to minimize timing attacks
+
+  // decrypted data:
+  // first (len - 20) bytes = application data
+  // last 20 bytes          = MAC
+  var macLen = s.macLength;
+
+  // create a zero'd out mac
+  var mac = '';
+  for(var i = 0; i < macLen; ++i) {
+    mac += String.fromCharCode(0);
+  }
+
+  // get fragment and mac
+  var len = cipher.output.length();
+  if(len >= macLen) {
+    record.fragment = cipher.output.getBytes(len - macLen);
+    mac = cipher.output.getBytes(macLen);
+  } else {
+    // bad data, but get bytes anyway to try to keep timing consistent
+    record.fragment = cipher.output.getBytes();
+  }
+  record.fragment = forge.util.createBuffer(record.fragment);
+  record.length = record.fragment.length();
+
+  // see if data integrity checks out, update sequence number
+  var mac2 = s.macFunction(s.macKey, s.sequenceNumber, record);
+  s.updateSequenceNumber();
+  rval = (mac2 === mac) && rval;
+  return rval;
+}
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'aesCipherSuites';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './aes', './tls'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/asn1.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/asn1.js
new file mode 100644
index 0000000..9ac7df4
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/asn1.js
@@ -0,0 +1,1114 @@
+/**
+ * Javascript implementation of Abstract Syntax Notation Number One.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2014 Digital Bazaar, Inc.
+ *
+ * An API for storing data using the Abstract Syntax Notation Number One
+ * format using DER (Distinguished Encoding Rules) encoding. This encoding is
+ * commonly used to store data for PKI, i.e. X.509 Certificates, and this
+ * implementation exists for that purpose.
+ *
+ * Abstract Syntax Notation Number One (ASN.1) is used to define the abstract
+ * syntax of information without restricting the way the information is encoded
+ * for transmission. It provides a standard that allows for open systems
+ * communication. ASN.1 defines the syntax of information data and a number of
+ * simple data types as well as a notation for describing them and specifying
+ * values for them.
+ *
+ * The RSA algorithm creates public and private keys that are often stored in
+ * X.509 or PKCS#X formats -- which use ASN.1 (encoded in DER format). This
+ * class provides the most basic functionality required to store and load DSA
+ * keys that are encoded according to ASN.1.
+ *
+ * The most common binary encodings for ASN.1 are BER (Basic Encoding Rules)
+ * and DER (Distinguished Encoding Rules). DER is just a subset of BER that
+ * has stricter requirements for how data must be encoded.
+ *
+ * Each ASN.1 structure has a tag (a byte identifying the ASN.1 structure type)
+ * and a byte array for the value of this ASN1 structure which may be data or a
+ * list of ASN.1 structures.
+ *
+ * Each ASN.1 structure using BER is (Tag-Length-Value):
+ *
+ * | byte 0 | bytes X | bytes Y |
+ * |--------|---------|----------
+ * |  tag   | length  |  value  |
+ *
+ * ASN.1 allows for tags to be of "High-tag-number form" which allows a tag to
+ * be two or more octets, but that is not supported by this class. A tag is
+ * only 1 byte. Bits 1-5 give the tag number (ie the data type within a
+ * particular 'class'), 6 indicates whether or not the ASN.1 value is
+ * constructed from other ASN.1 values, and bits 7 and 8 give the 'class'. If
+ * bits 7 and 8 are both zero, the class is UNIVERSAL. If only bit 7 is set,
+ * then the class is APPLICATION. If only bit 8 is set, then the class is
+ * CONTEXT_SPECIFIC. If both bits 7 and 8 are set, then the class is PRIVATE.
+ * The tag numbers for the data types for the class UNIVERSAL are listed below:
+ *
+ * UNIVERSAL 0 Reserved for use by the encoding rules
+ * UNIVERSAL 1 Boolean type
+ * UNIVERSAL 2 Integer type
+ * UNIVERSAL 3 Bitstring type
+ * UNIVERSAL 4 Octetstring type
+ * UNIVERSAL 5 Null type
+ * UNIVERSAL 6 Object identifier type
+ * UNIVERSAL 7 Object descriptor type
+ * UNIVERSAL 8 External type and Instance-of type
+ * UNIVERSAL 9 Real type
+ * UNIVERSAL 10 Enumerated type
+ * UNIVERSAL 11 Embedded-pdv type
+ * UNIVERSAL 12 UTF8String type
+ * UNIVERSAL 13 Relative object identifier type
+ * UNIVERSAL 14-15 Reserved for future editions
+ * UNIVERSAL 16 Sequence and Sequence-of types
+ * UNIVERSAL 17 Set and Set-of types
+ * UNIVERSAL 18-22, 25-30 Character string types
+ * UNIVERSAL 23-24 Time types
+ *
+ * The length of an ASN.1 structure is specified after the tag identifier.
+ * There is a definite form and an indefinite form. The indefinite form may
+ * be used if the encoding is constructed and not all immediately available.
+ * The indefinite form is encoded using a length byte with only the 8th bit
+ * set. The end of the constructed object is marked using end-of-contents
+ * octets (two zero bytes).
+ *
+ * The definite form looks like this:
+ *
+ * The length may take up 1 or more bytes, it depends on the length of the
+ * value of the ASN.1 structure. DER encoding requires that if the ASN.1
+ * structure has a value that has a length greater than 127, more than 1 byte
+ * will be used to store its length, otherwise just one byte will be used.
+ * This is strict.
+ *
+ * In the case that the length of the ASN.1 value is less than 127, 1 octet
+ * (byte) is used to store the "short form" length. The 8th bit has a value of
+ * 0 indicating the length is "short form" and not "long form" and bits 7-1
+ * give the length of the data. (The 8th bit is the left-most, most significant
+ * bit: also known as big endian or network format).
+ *
+ * In the case that the length of the ASN.1 value is greater than 127, 2 to
+ * 127 octets (bytes) are used to store the "long form" length. The first
+ * byte's 8th bit is set to 1 to indicate the length is "long form." Bits 7-1
+ * give the number of additional octets. All following octets are in base 256
+ * with the most significant digit first (typical big-endian binary unsigned
+ * integer storage). So, for instance, if the length of a value was 257, the
+ * first byte would be set to:
+ *
+ * 10000010 = 130 = 0x82.
+ *
+ * This indicates there are 2 octets (base 256) for the length. The second and
+ * third bytes (the octets just mentioned) would store the length in base 256:
+ *
+ * octet 2: 00000001 = 1 * 256^1 = 256
+ * octet 3: 00000001 = 1 * 256^0 = 1
+ * total = 257
+ *
+ * The algorithm for converting a js integer value of 257 to base-256 is:
+ *
+ * var value = 257;
+ * var bytes = [];
+ * bytes[0] = (value >>> 8) & 0xFF; // most significant byte first
+ * bytes[1] = value & 0xFF;        // least significant byte last
+ *
+ * On the ASN.1 UNIVERSAL Object Identifier (OID) type:
+ *
+ * An OID can be written like: "value1.value2.value3...valueN"
+ *
+ * The DER encoding rules:
+ *
+ * The first byte has the value 40 * value1 + value2.
+ * The following bytes, if any, encode the remaining values. Each value is
+ * encoded in base 128, most significant digit first (big endian), with as
+ * few digits as possible, and the most significant bit of each byte set
+ * to 1 except the last in each value's encoding. For example: Given the
+ * OID "1.2.840.113549", its DER encoding is (remember each byte except the
+ * last one in each encoding is OR'd with 0x80):
+ *
+ * byte 1: 40 * 1 + 2 = 42 = 0x2A.
+ * bytes 2-3: 128 * 6 + 72 = 840 = 6 72 = 6 72 = 0x0648 = 0x8648
+ * bytes 4-6: 16384 * 6 + 128 * 119 + 13 = 6 119 13 = 0x06770D = 0x86F70D
+ *
+ * The final value is: 0x2A864886F70D.
+ * The full OID (including ASN.1 tag and length of 6 bytes) is:
+ * 0x06062A864886F70D
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+/* ASN.1 API */
+var asn1 = forge.asn1 = forge.asn1 || {};
+
+/**
+ * ASN.1 classes.
+ */
+asn1.Class = {
+  UNIVERSAL:        0x00,
+  APPLICATION:      0x40,
+  CONTEXT_SPECIFIC: 0x80,
+  PRIVATE:          0xC0
+};
+
+/**
+ * ASN.1 types. Not all types are supported by this implementation, only
+ * those necessary to implement a simple PKI are implemented.
+ */
+asn1.Type = {
+  NONE:             0,
+  BOOLEAN:          1,
+  INTEGER:          2,
+  BITSTRING:        3,
+  OCTETSTRING:      4,
+  NULL:             5,
+  OID:              6,
+  ODESC:            7,
+  EXTERNAL:         8,
+  REAL:             9,
+  ENUMERATED:      10,
+  EMBEDDED:        11,
+  UTF8:            12,
+  ROID:            13,
+  SEQUENCE:        16,
+  SET:             17,
+  PRINTABLESTRING: 19,
+  IA5STRING:       22,
+  UTCTIME:         23,
+  GENERALIZEDTIME: 24,
+  BMPSTRING:       30
+};
+
+/**
+ * Creates a new asn1 object.
+ *
+ * @param tagClass the tag class for the object.
+ * @param type the data type (tag number) for the object.
+ * @param constructed true if the asn1 object is in constructed form.
+ * @param value the value for the object, if it is not constructed.
+ *
+ * @return the asn1 object.
+ */
+asn1.create = function(tagClass, type, constructed, value) {
+  /* An asn1 object has a tagClass, a type, a constructed flag, and a
+    value. The value's type depends on the constructed flag. If
+    constructed, it will contain a list of other asn1 objects. If not,
+    it will contain the ASN.1 value as an array of bytes formatted
+    according to the ASN.1 data type. */
+
+  // remove undefined values
+  if(forge.util.isArray(value)) {
+    var tmp = [];
+    for(var i = 0; i < value.length; ++i) {
+      if(value[i] !== undefined) {
+        tmp.push(value[i]);
+      }
+    }
+    value = tmp;
+  }
+
+  return {
+    tagClass: tagClass,
+    type: type,
+    constructed: constructed,
+    composed: constructed || forge.util.isArray(value),
+    value: value
+  };
+};
+
+/**
+ * Gets the length of an ASN.1 value.
+ *
+ * In case the length is not specified, undefined is returned.
+ *
+ * @param b the ASN.1 byte buffer.
+ *
+ * @return the length of the ASN.1 value.
+ */
+var _getValueLength = function(b) {
+  var b2 = b.getByte();
+  if(b2 === 0x80) {
+    return undefined;
+  }
+
+  // see if the length is "short form" or "long form" (bit 8 set)
+  var length;
+  var longForm = b2 & 0x80;
+  if(!longForm) {
+    // length is just the first byte
+    length = b2;
+  } else {
+    // the number of bytes the length is specified in bits 7 through 1
+    // and each length byte is in big-endian base-256
+    length = b.getInt((b2 & 0x7F) << 3);
+  }
+  return length;
+};
+
+/**
+ * Parses an asn1 object from a byte buffer in DER format.
+ *
+ * @param bytes the byte buffer to parse from.
+ * @param strict true to be strict when checking value lengths, false to
+ *          allow truncated values (default: true).
+ *
+ * @return the parsed asn1 object.
+ */
+asn1.fromDer = function(bytes, strict) {
+  if(strict === undefined) {
+    strict = true;
+  }
+
+  // wrap in buffer if needed
+  if(typeof bytes === 'string') {
+    bytes = forge.util.createBuffer(bytes);
+  }
+
+  // minimum length for ASN.1 DER structure is 2
+  if(bytes.length() < 2) {
+    var error = new Error('Too few bytes to parse DER.');
+    error.bytes = bytes.length();
+    throw error;
+  }
+
+  // get the first byte
+  var b1 = bytes.getByte();
+
+  // get the tag class
+  var tagClass = (b1 & 0xC0);
+
+  // get the type (bits 1-5)
+  var type = b1 & 0x1F;
+
+  // get the value length
+  var length = _getValueLength(bytes);
+
+  // ensure there are enough bytes to get the value
+  if(bytes.length() < length) {
+    if(strict) {
+      var error = new Error('Too few bytes to read ASN.1 value.');
+      error.detail = bytes.length() + ' < ' + length;
+      throw error;
+    }
+    // Note: be lenient with truncated values
+    length = bytes.length();
+  }
+
+  // prepare to get value
+  var value;
+
+  // constructed flag is bit 6 (32 = 0x20) of the first byte
+  var constructed = ((b1 & 0x20) === 0x20);
+
+  // determine if the value is composed of other ASN.1 objects (if its
+  // constructed it will be and if its a BITSTRING it may be)
+  var composed = constructed;
+  if(!composed && tagClass === asn1.Class.UNIVERSAL &&
+    type === asn1.Type.BITSTRING && length > 1) {
+    /* The first octet gives the number of bits by which the length of the
+      bit string is less than the next multiple of eight (this is called
+      the "number of unused bits").
+
+      The second and following octets give the value of the bit string
+      converted to an octet string. */
+    // if there are no unused bits, maybe the bitstring holds ASN.1 objs
+    var read = bytes.read;
+    var unused = bytes.getByte();
+    if(unused === 0) {
+      // if the first byte indicates UNIVERSAL or CONTEXT_SPECIFIC,
+      // and the length is valid, assume we've got an ASN.1 object
+      b1 = bytes.getByte();
+      var tc = (b1 & 0xC0);
+      if(tc === asn1.Class.UNIVERSAL || tc === asn1.Class.CONTEXT_SPECIFIC) {
+        try {
+          var len = _getValueLength(bytes);
+          composed = (len === length - (bytes.read - read));
+          if(composed) {
+            // adjust read/length to account for unused bits byte
+            ++read;
+            --length;
+          }
+        } catch(ex) {}
+      }
+    }
+    // restore read pointer
+    bytes.read = read;
+  }
+
+  if(composed) {
+    // parse child asn1 objects from the value
+    value = [];
+    if(length === undefined) {
+      // asn1 object of indefinite length, read until end tag
+      for(;;) {
+        if(bytes.bytes(2) === String.fromCharCode(0, 0)) {
+          bytes.getBytes(2);
+          break;
+        }
+        value.push(asn1.fromDer(bytes, strict));
+      }
+    } else {
+      // parsing asn1 object of definite length
+      var start = bytes.length();
+      while(length > 0) {
+        value.push(asn1.fromDer(bytes, strict));
+        length -= start - bytes.length();
+        start = bytes.length();
+      }
+    }
+  } else {
+    // asn1 not composed, get raw value
+    // TODO: do DER to OID conversion and vice-versa in .toDer?
+
+    if(length === undefined) {
+      if(strict) {
+        throw new Error('Non-constructed ASN.1 object of indefinite length.');
+      }
+      // be lenient and use remaining bytes
+      length = bytes.length();
+    }
+
+    if(type === asn1.Type.BMPSTRING) {
+      value = '';
+      for(var i = 0; i < length; i += 2) {
+        value += String.fromCharCode(bytes.getInt16());
+      }
+    } else {
+      value = bytes.getBytes(length);
+    }
+  }
+
+  // create and return asn1 object
+  return asn1.create(tagClass, type, constructed, value);
+};
+
+/**
+ * Converts the given asn1 object to a buffer of bytes in DER format.
+ *
+ * @param asn1 the asn1 object to convert to bytes.
+ *
+ * @return the buffer of bytes.
+ */
+asn1.toDer = function(obj) {
+  var bytes = forge.util.createBuffer();
+
+  // build the first byte
+  var b1 = obj.tagClass | obj.type;
+
+  // for storing the ASN.1 value
+  var value = forge.util.createBuffer();
+
+  // if composed, use each child asn1 object's DER bytes as value
+  if(obj.composed) {
+    // turn on 6th bit (0x20 = 32) to indicate asn1 is constructed
+    // from other asn1 objects
+    if(obj.constructed) {
+      b1 |= 0x20;
+    } else {
+      // type is a bit string, add unused bits of 0x00
+      value.putByte(0x00);
+    }
+
+    // add all of the child DER bytes together
+    for(var i = 0; i < obj.value.length; ++i) {
+      if(obj.value[i] !== undefined) {
+        value.putBuffer(asn1.toDer(obj.value[i]));
+      }
+    }
+  } else {
+    // use asn1.value directly
+    if(obj.type === asn1.Type.BMPSTRING) {
+      for(var i = 0; i < obj.value.length; ++i) {
+        value.putInt16(obj.value.charCodeAt(i));
+      }
+    } else {
+      value.putBytes(obj.value);
+    }
+  }
+
+  // add tag byte
+  bytes.putByte(b1);
+
+  // use "short form" encoding
+  if(value.length() <= 127) {
+    // one byte describes the length
+    // bit 8 = 0 and bits 7-1 = length
+    bytes.putByte(value.length() & 0x7F);
+  } else {
+    // use "long form" encoding
+    // 2 to 127 bytes describe the length
+    // first byte: bit 8 = 1 and bits 7-1 = # of additional bytes
+    // other bytes: length in base 256, big-endian
+    var len = value.length();
+    var lenBytes = '';
+    do {
+      lenBytes += String.fromCharCode(len & 0xFF);
+      len = len >>> 8;
+    } while(len > 0);
+
+    // set first byte to # bytes used to store the length and turn on
+    // bit 8 to indicate long-form length is used
+    bytes.putByte(lenBytes.length | 0x80);
+
+    // concatenate length bytes in reverse since they were generated
+    // little endian and we need big endian
+    for(var i = lenBytes.length - 1; i >= 0; --i) {
+      bytes.putByte(lenBytes.charCodeAt(i));
+    }
+  }
+
+  // concatenate value bytes
+  bytes.putBuffer(value);
+  return bytes;
+};
+
+/**
+ * Converts an OID dot-separated string to a byte buffer. The byte buffer
+ * contains only the DER-encoded value, not any tag or length bytes.
+ *
+ * @param oid the OID dot-separated string.
+ *
+ * @return the byte buffer.
+ */
+asn1.oidToDer = function(oid) {
+  // split OID into individual values
+  var values = oid.split('.');
+  var bytes = forge.util.createBuffer();
+
+  // first byte is 40 * value1 + value2
+  bytes.putByte(40 * parseInt(values[0], 10) + parseInt(values[1], 10));
+  // other bytes are each value in base 128 with 8th bit set except for
+  // the last byte for each value
+  var last, valueBytes, value, b;
+  for(var i = 2; i < values.length; ++i) {
+    // produce value bytes in reverse because we don't know how many
+    // bytes it will take to store the value
+    last = true;
+    valueBytes = [];
+    value = parseInt(values[i], 10);
+    do {
+      b = value & 0x7F;
+      value = value >>> 7;
+      // if value is not last, then turn on 8th bit
+      if(!last) {
+        b |= 0x80;
+      }
+      valueBytes.push(b);
+      last = false;
+    } while(value > 0);
+
+    // add value bytes in reverse (needs to be in big endian)
+    for(var n = valueBytes.length - 1; n >= 0; --n) {
+      bytes.putByte(valueBytes[n]);
+    }
+  }
+
+  return bytes;
+};
+
+/**
+ * Converts a DER-encoded byte buffer to an OID dot-separated string. The
+ * byte buffer should contain only the DER-encoded value, not any tag or
+ * length bytes.
+ *
+ * @param bytes the byte buffer.
+ *
+ * @return the OID dot-separated string.
+ */
+asn1.derToOid = function(bytes) {
+  var oid;
+
+  // wrap in buffer if needed
+  if(typeof bytes === 'string') {
+    bytes = forge.util.createBuffer(bytes);
+  }
+
+  // first byte is 40 * value1 + value2
+  var b = bytes.getByte();
+  oid = Math.floor(b / 40) + '.' + (b % 40);
+
+  // other bytes are each value in base 128 with 8th bit set except for
+  // the last byte for each value
+  var value = 0;
+  while(bytes.length() > 0) {
+    b = bytes.getByte();
+    value = value << 7;
+    // not the last byte for the value
+    if(b & 0x80) {
+      value += b & 0x7F;
+    } else {
+      // last byte
+      oid += '.' + (value + b);
+      value = 0;
+    }
+  }
+
+  return oid;
+};
+
+/**
+ * Converts a UTCTime value to a date.
+ *
+ * Note: GeneralizedTime has 4 digits for the year and is used for X.509
+ * dates passed 2049. Parsing that structure hasn't been implemented yet.
+ *
+ * @param utc the UTCTime value to convert.
+ *
+ * @return the date.
+ */
+asn1.utcTimeToDate = function(utc) {
+  /* The following formats can be used:
+
+    YYMMDDhhmmZ
+    YYMMDDhhmm+hh'mm'
+    YYMMDDhhmm-hh'mm'
+    YYMMDDhhmmssZ
+    YYMMDDhhmmss+hh'mm'
+    YYMMDDhhmmss-hh'mm'
+
+    Where:
+
+    YY is the least significant two digits of the year
+    MM is the month (01 to 12)
+    DD is the day (01 to 31)
+    hh is the hour (00 to 23)
+    mm are the minutes (00 to 59)
+    ss are the seconds (00 to 59)
+    Z indicates that local time is GMT, + indicates that local time is
+    later than GMT, and - indicates that local time is earlier than GMT
+    hh' is the absolute value of the offset from GMT in hours
+    mm' is the absolute value of the offset from GMT in minutes */
+  var date = new Date();
+
+  // if YY >= 50 use 19xx, if YY < 50 use 20xx
+  var year = parseInt(utc.substr(0, 2), 10);
+  year = (year >= 50) ? 1900 + year : 2000 + year;
+  var MM = parseInt(utc.substr(2, 2), 10) - 1; // use 0-11 for month
+  var DD = parseInt(utc.substr(4, 2), 10);
+  var hh = parseInt(utc.substr(6, 2), 10);
+  var mm = parseInt(utc.substr(8, 2), 10);
+  var ss = 0;
+
+  // not just YYMMDDhhmmZ
+  if(utc.length > 11) {
+    // get character after minutes
+    var c = utc.charAt(10);
+    var end = 10;
+
+    // see if seconds are present
+    if(c !== '+' && c !== '-') {
+      // get seconds
+      ss = parseInt(utc.substr(10, 2), 10);
+      end += 2;
+    }
+  }
+
+  // update date
+  date.setUTCFullYear(year, MM, DD);
+  date.setUTCHours(hh, mm, ss, 0);
+
+  if(end) {
+    // get +/- after end of time
+    c = utc.charAt(end);
+    if(c === '+' || c === '-') {
+      // get hours+minutes offset
+      var hhoffset = parseInt(utc.substr(end + 1, 2), 10);
+      var mmoffset = parseInt(utc.substr(end + 4, 2), 10);
+
+      // calculate offset in milliseconds
+      var offset = hhoffset * 60 + mmoffset;
+      offset *= 60000;
+
+      // apply offset
+      if(c === '+') {
+        date.setTime(+date - offset);
+      } else {
+        date.setTime(+date + offset);
+      }
+    }
+  }
+
+  return date;
+};
+
+/**
+ * Converts a GeneralizedTime value to a date.
+ *
+ * @param gentime the GeneralizedTime value to convert.
+ *
+ * @return the date.
+ */
+asn1.generalizedTimeToDate = function(gentime) {
+  /* The following formats can be used:
+
+    YYYYMMDDHHMMSS
+    YYYYMMDDHHMMSS.fff
+    YYYYMMDDHHMMSSZ
+    YYYYMMDDHHMMSS.fffZ
+    YYYYMMDDHHMMSS+hh'mm'
+    YYYYMMDDHHMMSS.fff+hh'mm'
+    YYYYMMDDHHMMSS-hh'mm'
+    YYYYMMDDHHMMSS.fff-hh'mm'
+
+    Where:
+
+    YYYY is the year
+    MM is the month (01 to 12)
+    DD is the day (01 to 31)
+    hh is the hour (00 to 23)
+    mm are the minutes (00 to 59)
+    ss are the seconds (00 to 59)
+    .fff is the second fraction, accurate to three decimal places
+    Z indicates that local time is GMT, + indicates that local time is
+    later than GMT, and - indicates that local time is earlier than GMT
+    hh' is the absolute value of the offset from GMT in hours
+    mm' is the absolute value of the offset from GMT in minutes */
+  var date = new Date();
+
+  var YYYY = parseInt(gentime.substr(0, 4), 10);
+  var MM = parseInt(gentime.substr(4, 2), 10) - 1; // use 0-11 for month
+  var DD = parseInt(gentime.substr(6, 2), 10);
+  var hh = parseInt(gentime.substr(8, 2), 10);
+  var mm = parseInt(gentime.substr(10, 2), 10);
+  var ss = parseInt(gentime.substr(12, 2), 10);
+  var fff = 0;
+  var offset = 0;
+  var isUTC = false;
+
+  if(gentime.charAt(gentime.length - 1) === 'Z') {
+    isUTC = true;
+  }
+
+  var end = gentime.length - 5, c = gentime.charAt(end);
+  if(c === '+' || c === '-') {
+    // get hours+minutes offset
+    var hhoffset = parseInt(gentime.substr(end + 1, 2), 10);
+    var mmoffset = parseInt(gentime.substr(end + 4, 2), 10);
+
+    // calculate offset in milliseconds
+    offset = hhoffset * 60 + mmoffset;
+    offset *= 60000;
+
+    // apply offset
+    if(c === '+') {
+      offset *= -1;
+    }
+
+    isUTC = true;
+  }
+
+  // check for second fraction
+  if(gentime.charAt(14) === '.') {
+    fff = parseFloat(gentime.substr(14), 10) * 1000;
+  }
+
+  if(isUTC) {
+    date.setUTCFullYear(YYYY, MM, DD);
+    date.setUTCHours(hh, mm, ss, fff);
+
+    // apply offset
+    date.setTime(+date + offset);
+  } else {
+    date.setFullYear(YYYY, MM, DD);
+    date.setHours(hh, mm, ss, fff);
+  }
+
+  return date;
+};
+
+
+/**
+ * Converts a date to a UTCTime value.
+ *
+ * Note: GeneralizedTime has 4 digits for the year and is used for X.509
+ * dates passed 2049. Converting to a GeneralizedTime hasn't been
+ * implemented yet.
+ *
+ * @param date the date to convert.
+ *
+ * @return the UTCTime value.
+ */
+asn1.dateToUtcTime = function(date) {
+  var rval = '';
+
+  // create format YYMMDDhhmmssZ
+  var format = [];
+  format.push(('' + date.getUTCFullYear()).substr(2));
+  format.push('' + (date.getUTCMonth() + 1));
+  format.push('' + date.getUTCDate());
+  format.push('' + date.getUTCHours());
+  format.push('' + date.getUTCMinutes());
+  format.push('' + date.getUTCSeconds());
+
+  // ensure 2 digits are used for each format entry
+  for(var i = 0; i < format.length; ++i) {
+    if(format[i].length < 2) {
+      rval += '0';
+    }
+    rval += format[i];
+  }
+  rval += 'Z';
+
+  return rval;
+};
+
+/**
+ * Converts a javascript integer to a DER-encoded byte buffer to be used
+ * as the value for an INTEGER type.
+ *
+ * @param x the integer.
+ *
+ * @return the byte buffer.
+ */
+asn1.integerToDer = function(x) {
+  var rval = forge.util.createBuffer();
+  if(x >= -0x80 && x < 0x80) {
+    return rval.putSignedInt(x, 8);
+  }
+  if(x >= -0x8000 && x < 0x8000) {
+    return rval.putSignedInt(x, 16);
+  }
+  if(x >= -0x800000 && x < 0x800000) {
+    return rval.putSignedInt(x, 24);
+  }
+  if(x >= -0x80000000 && x < 0x80000000) {
+    return rval.putSignedInt(x, 32);
+  }
+  var error = new Error('Integer too large; max is 32-bits.');
+  error.integer = x;
+  throw error;
+};
+
+/**
+ * Converts a DER-encoded byte buffer to a javascript integer. This is
+ * typically used to decode the value of an INTEGER type.
+ *
+ * @param bytes the byte buffer.
+ *
+ * @return the integer.
+ */
+asn1.derToInteger = function(bytes) {
+  // wrap in buffer if needed
+  if(typeof bytes === 'string') {
+    bytes = forge.util.createBuffer(bytes);
+  }
+
+  var n = bytes.length() * 8;
+  if(n > 32) {
+    throw new Error('Integer too large; max is 32-bits.');
+  }
+  return bytes.getSignedInt(n);
+};
+
+/**
+ * Validates the that given ASN.1 object is at least a super set of the
+ * given ASN.1 structure. Only tag classes and types are checked. An
+ * optional map may also be provided to capture ASN.1 values while the
+ * structure is checked.
+ *
+ * To capture an ASN.1 value, set an object in the validator's 'capture'
+ * parameter to the key to use in the capture map. To capture the full
+ * ASN.1 object, specify 'captureAsn1'.
+ *
+ * Objects in the validator may set a field 'optional' to true to indicate
+ * that it isn't necessary to pass validation.
+ *
+ * @param obj the ASN.1 object to validate.
+ * @param v the ASN.1 structure validator.
+ * @param capture an optional map to capture values in.
+ * @param errors an optional array for storing validation errors.
+ *
+ * @return true on success, false on failure.
+ */
+asn1.validate = function(obj, v, capture, errors) {
+  var rval = false;
+
+  // ensure tag class and type are the same if specified
+  if((obj.tagClass === v.tagClass || typeof(v.tagClass) === 'undefined') &&
+    (obj.type === v.type || typeof(v.type) === 'undefined')) {
+    // ensure constructed flag is the same if specified
+    if(obj.constructed === v.constructed ||
+      typeof(v.constructed) === 'undefined') {
+      rval = true;
+
+      // handle sub values
+      if(v.value && forge.util.isArray(v.value)) {
+        var j = 0;
+        for(var i = 0; rval && i < v.value.length; ++i) {
+          rval = v.value[i].optional || false;
+          if(obj.value[j]) {
+            rval = asn1.validate(obj.value[j], v.value[i], capture, errors);
+            if(rval) {
+              ++j;
+            } else if(v.value[i].optional) {
+              rval = true;
+            }
+          }
+          if(!rval && errors) {
+            errors.push(
+              '[' + v.name + '] ' +
+              'Tag class "' + v.tagClass + '", type "' +
+              v.type + '" expected value length "' +
+              v.value.length + '", got "' +
+              obj.value.length + '"');
+          }
+        }
+      }
+
+      if(rval && capture) {
+        if(v.capture) {
+          capture[v.capture] = obj.value;
+        }
+        if(v.captureAsn1) {
+          capture[v.captureAsn1] = obj;
+        }
+      }
+    } else if(errors) {
+      errors.push(
+        '[' + v.name + '] ' +
+        'Expected constructed "' + v.constructed + '", got "' +
+        obj.constructed + '"');
+    }
+  } else if(errors) {
+    if(obj.tagClass !== v.tagClass) {
+      errors.push(
+        '[' + v.name + '] ' +
+        'Expected tag class "' + v.tagClass + '", got "' +
+        obj.tagClass + '"');
+    }
+    if(obj.type !== v.type) {
+      errors.push(
+        '[' + v.name + '] ' +
+        'Expected type "' + v.type + '", got "' + obj.type + '"');
+    }
+  }
+  return rval;
+};
+
+// regex for testing for non-latin characters
+var _nonLatinRegex = /[^\\u0000-\\u00ff]/;
+
+/**
+ * Pretty prints an ASN.1 object to a string.
+ *
+ * @param obj the object to write out.
+ * @param level the level in the tree.
+ * @param indentation the indentation to use.
+ *
+ * @return the string.
+ */
+asn1.prettyPrint = function(obj, level, indentation) {
+  var rval = '';
+
+  // set default level and indentation
+  level = level || 0;
+  indentation = indentation || 2;
+
+  // start new line for deep levels
+  if(level > 0) {
+    rval += '\n';
+  }
+
+  // create indent
+  var indent = '';
+  for(var i = 0; i < level * indentation; ++i) {
+    indent += ' ';
+  }
+
+  // print class:type
+  rval += indent + 'Tag: ';
+  switch(obj.tagClass) {
+  case asn1.Class.UNIVERSAL:
+    rval += 'Universal:';
+    break;
+  case asn1.Class.APPLICATION:
+    rval += 'Application:';
+    break;
+  case asn1.Class.CONTEXT_SPECIFIC:
+    rval += 'Context-Specific:';
+    break;
+  case asn1.Class.PRIVATE:
+    rval += 'Private:';
+    break;
+  }
+
+  if(obj.tagClass === asn1.Class.UNIVERSAL) {
+    rval += obj.type;
+
+    // known types
+    switch(obj.type) {
+    case asn1.Type.NONE:
+      rval += ' (None)';
+      break;
+    case asn1.Type.BOOLEAN:
+      rval += ' (Boolean)';
+      break;
+    case asn1.Type.BITSTRING:
+      rval += ' (Bit string)';
+      break;
+    case asn1.Type.INTEGER:
+      rval += ' (Integer)';
+      break;
+    case asn1.Type.OCTETSTRING:
+      rval += ' (Octet string)';
+      break;
+    case asn1.Type.NULL:
+      rval += ' (Null)';
+      break;
+    case asn1.Type.OID:
+      rval += ' (Object Identifier)';
+      break;
+    case asn1.Type.ODESC:
+      rval += ' (Object Descriptor)';
+      break;
+    case asn1.Type.EXTERNAL:
+      rval += ' (External or Instance of)';
+      break;
+    case asn1.Type.REAL:
+      rval += ' (Real)';
+      break;
+    case asn1.Type.ENUMERATED:
+      rval += ' (Enumerated)';
+      break;
+    case asn1.Type.EMBEDDED:
+      rval += ' (Embedded PDV)';
+      break;
+    case asn1.Type.UTF8:
+      rval += ' (UTF8)';
+      break;
+    case asn1.Type.ROID:
+      rval += ' (Relative Object Identifier)';
+      break;
+    case asn1.Type.SEQUENCE:
+      rval += ' (Sequence)';
+      break;
+    case asn1.Type.SET:
+      rval += ' (Set)';
+      break;
+    case asn1.Type.PRINTABLESTRING:
+      rval += ' (Printable String)';
+      break;
+    case asn1.Type.IA5String:
+      rval += ' (IA5String (ASCII))';
+      break;
+    case asn1.Type.UTCTIME:
+      rval += ' (UTC time)';
+      break;
+    case asn1.Type.GENERALIZEDTIME:
+      rval += ' (Generalized time)';
+      break;
+    case asn1.Type.BMPSTRING:
+      rval += ' (BMP String)';
+      break;
+    }
+  } else {
+    rval += obj.type;
+  }
+
+  rval += '\n';
+  rval += indent + 'Constructed: ' + obj.constructed + '\n';
+
+  if(obj.composed) {
+    var subvalues = 0;
+    var sub = '';
+    for(var i = 0; i < obj.value.length; ++i) {
+      if(obj.value[i] !== undefined) {
+        subvalues += 1;
+        sub += asn1.prettyPrint(obj.value[i], level + 1, indentation);
+        if((i + 1) < obj.value.length) {
+          sub += ',';
+        }
+      }
+    }
+    rval += indent + 'Sub values: ' + subvalues + sub;
+  } else {
+    rval += indent + 'Value: ';
+    if(obj.type === asn1.Type.OID) {
+      var oid = asn1.derToOid(obj.value);
+      rval += oid;
+      if(forge.pki && forge.pki.oids) {
+        if(oid in forge.pki.oids) {
+          rval += ' (' + forge.pki.oids[oid] + ') ';
+        }
+      }
+    }
+    if(obj.type === asn1.Type.INTEGER) {
+      try {
+        rval += asn1.derToInteger(obj.value);
+      } catch(ex) {
+        rval += '0x' + forge.util.bytesToHex(obj.value);
+      }
+    } else if(obj.type === asn1.Type.OCTETSTRING) {
+      if(!_nonLatinRegex.test(obj.value)) {
+        rval += '(' + obj.value + ') ';
+      }
+      rval += '0x' + forge.util.bytesToHex(obj.value);
+    } else if(obj.type === asn1.Type.UTF8) {
+      rval += forge.util.decodeUtf8(obj.value);
+    } else if(obj.type === asn1.Type.PRINTABLESTRING ||
+      obj.type === asn1.Type.IA5String) {
+      rval += obj.value;
+    } else if(_nonLatinRegex.test(obj.value)) {
+      rval += '0x' + forge.util.bytesToHex(obj.value);
+    } else if(obj.value.length === 0) {
+      rval += '[null]';
+    } else {
+      rval += obj.value;
+    }
+  }
+
+  return rval;
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'asn1';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './util', './oids'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/cipher.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/cipher.js
new file mode 100644
index 0000000..58db4ce
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/cipher.js
@@ -0,0 +1,286 @@
+/**
+ * Cipher base API.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+forge.cipher = forge.cipher || {};
+
+// registered algorithms
+forge.cipher.algorithms = forge.cipher.algorithms || {};
+
+/**
+ * Creates a cipher object that can be used to encrypt data using the given
+ * algorithm and key. The algorithm may be provided as a string value for a
+ * previously registered algorithm or it may be given as a cipher algorithm
+ * API object.
+ *
+ * @param algorithm the algorithm to use, either a string or an algorithm API
+ *          object.
+ * @param key the key to use, as a binary-encoded string of bytes or a
+ *          byte buffer.
+ *
+ * @return the cipher.
+ */
+forge.cipher.createCipher = function(algorithm, key) {
+  var api = algorithm;
+  if(typeof api === 'string') {
+    api = forge.cipher.getAlgorithm(api);
+    if(api) {
+      api = api();
+    }
+  }
+  if(!api) {
+    throw new Error('Unsupported algorithm: ' + algorithm);
+  }
+
+  // assume block cipher
+  return new forge.cipher.BlockCipher({
+    algorithm: api,
+    key: key,
+    decrypt: false
+  });
+};
+
+/**
+ * Creates a decipher object that can be used to decrypt data using the given
+ * algorithm and key. The algorithm may be provided as a string value for a
+ * previously registered algorithm or it may be given as a cipher algorithm
+ * API object.
+ *
+ * @param algorithm the algorithm to use, either a string or an algorithm API
+ *          object.
+ * @param key the key to use, as a binary-encoded string of bytes or a
+ *          byte buffer.
+ *
+ * @return the cipher.
+ */
+forge.cipher.createDecipher = function(algorithm, key) {
+  var api = algorithm;
+  if(typeof api === 'string') {
+    api = forge.cipher.getAlgorithm(api);
+    if(api) {
+      api = api();
+    }
+  }
+  if(!api) {
+    throw new Error('Unsupported algorithm: ' + algorithm);
+  }
+
+  // assume block cipher
+  return new forge.cipher.BlockCipher({
+    algorithm: api,
+    key: key,
+    decrypt: true
+  });
+};
+
+/**
+ * Registers an algorithm by name. If the name was already registered, the
+ * algorithm API object will be overwritten.
+ *
+ * @param name the name of the algorithm.
+ * @param algorithm the algorithm API object.
+ */
+forge.cipher.registerAlgorithm = function(name, algorithm) {
+  name = name.toUpperCase();
+  forge.cipher.algorithms[name] = algorithm;
+};
+
+/**
+ * Gets a registered algorithm by name.
+ *
+ * @param name the name of the algorithm.
+ *
+ * @return the algorithm, if found, null if not.
+ */
+forge.cipher.getAlgorithm = function(name) {
+  name = name.toUpperCase();
+  if(name in forge.cipher.algorithms) {
+    return forge.cipher.algorithms[name];
+  }
+  return null;
+};
+
+var BlockCipher = forge.cipher.BlockCipher = function(options) {
+  this.algorithm = options.algorithm;
+  this.mode = this.algorithm.mode;
+  this.blockSize = this.mode.blockSize;
+  this._finish = false;
+  this._input = null;
+  this.output = null;
+  this._op = options.decrypt ? this.mode.decrypt : this.mode.encrypt;
+  this._decrypt = options.decrypt;
+  this.algorithm.initialize(options);
+};
+
+/**
+ * Starts or restarts the encryption or decryption process, whichever
+ * was previously configured.
+ *
+ * For non-GCM mode, the IV may be a binary-encoded string of bytes, an array
+ * of bytes, a byte buffer, or an array of 32-bit integers. If the IV is in
+ * bytes, then it must be Nb (16) bytes in length. If the IV is given in as
+ * 32-bit integers, then it must be 4 integers long.
+ *
+ * For GCM-mode, the IV must be given as a binary-encoded string of bytes or
+ * a byte buffer. The number of bytes should be 12 (96 bits) as recommended
+ * by NIST SP-800-38D but another length may be given.
+ *
+ * @param options the options to use:
+ *          iv the initialization vector to use as a binary-encoded string of
+ *            bytes, null to reuse the last ciphered block from a previous
+ *            update() (this "residue" method is for legacy support only).
+ *          additionalData additional authentication data as a binary-encoded
+ *            string of bytes, for 'GCM' mode, (default: none).
+ *          tagLength desired length of authentication tag, in bits, for
+ *            'GCM' mode (0-128, default: 128).
+ *          tag the authentication tag to check if decrypting, as a
+ *             binary-encoded string of bytes.
+ *          output the output the buffer to write to, null to create one.
+ */
+BlockCipher.prototype.start = function(options) {
+  options = options || {};
+  var opts = {};
+  for(var key in options) {
+    opts[key] = options[key];
+  }
+  opts.decrypt = this._decrypt;
+  this._finish = false;
+  this._input = forge.util.createBuffer();
+  this.output = options.output || forge.util.createBuffer();
+  this.mode.start(opts);
+};
+
+/**
+ * Updates the next block according to the cipher mode.
+ *
+ * @param input the buffer to read from.
+ */
+BlockCipher.prototype.update = function(input) {
+  if(!this._finish) {
+    // not finishing, so fill the input buffer with more input
+    this._input.putBuffer(input);
+  }
+
+  // do cipher operation while input contains full blocks or if finishing
+  while(this._input.length() >= this.blockSize ||
+    (this._input.length() > 0 && this._finish)) {
+    this._op.call(this.mode, this._input, this.output);
+  }
+
+  // free consumed memory from input buffer
+  this._input.compact();
+};
+
+/**
+ * Finishes encrypting or decrypting.
+ *
+ * @param pad a padding function to use in CBC mode, null for default,
+ *          signature(blockSize, buffer, decrypt).
+ *
+ * @return true if successful, false on error.
+ */
+BlockCipher.prototype.finish = function(pad) {
+  // backwards-compatibility w/deprecated padding API
+  // Note: will overwrite padding functions even after another start() call
+  if(pad && this.mode.name === 'CBC') {
+    this.mode.pad = function(input) {
+      return pad(this.blockSize, input, false);
+    };
+    this.mode.unpad = function(output) {
+      return pad(this.blockSize, output, true);
+    };
+  }
+
+  // build options for padding and afterFinish functions
+  var options = {};
+  options.decrypt = this._decrypt;
+
+  // get # of bytes that won't fill a block
+  options.overflow = this._input.length() % this.blockSize;
+
+  if(!this._decrypt && this.mode.pad) {
+    if(!this.mode.pad(this._input, options)) {
+      return false;
+    }
+  }
+
+  // do final update
+  this._finish = true;
+  this.update();
+
+  if(this._decrypt && this.mode.unpad) {
+    if(!this.mode.unpad(this.output, options)) {
+      return false;
+    }
+  }
+
+  if(this.mode.afterFinish) {
+    if(!this.mode.afterFinish(this.output, options)) {
+      return false;
+    }
+  }
+
+  return true;
+};
+
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'cipher';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/cipherModes.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/cipherModes.js
new file mode 100644
index 0000000..9ed318f
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/cipherModes.js
@@ -0,0 +1,817 @@
+/**
+ * Supported cipher modes.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+forge.cipher = forge.cipher || {};
+
+// supported cipher modes
+var modes = forge.cipher.modes = forge.cipher.modes || {};
+
+
+/** Electronic codebook (ECB) (Don't use this; it's not secure) **/
+
+modes.ecb = function(options) {
+  options = options || {};
+  this.name = 'ECB';
+  this.cipher = options.cipher;
+  this.blockSize = options.blockSize || 16;
+  this._blocks = this.blockSize / 4;
+  this._inBlock = new Array(this._blocks);
+  this._outBlock = new Array(this._blocks);
+};
+
+modes.ecb.prototype.start = function(options) {};
+
+modes.ecb.prototype.encrypt = function(input, output) {
+  // get next block
+  for(var i = 0; i < this._blocks; ++i) {
+    this._inBlock[i] = input.getInt32();
+  }
+
+  // encrypt block
+  this.cipher.encrypt(this._inBlock, this._outBlock);
+
+  // write output
+  for(var i = 0; i < this._blocks; ++i) {
+    output.putInt32(this._outBlock[i]);
+  }
+};
+
+modes.ecb.prototype.decrypt = function(input, output) {
+  // get next block
+  for(var i = 0; i < this._blocks; ++i) {
+    this._inBlock[i] = input.getInt32();
+  }
+
+  // decrypt block
+  this.cipher.decrypt(this._inBlock, this._outBlock);
+
+  // write output
+  for(var i = 0; i < this._blocks; ++i) {
+    output.putInt32(this._outBlock[i]);
+  }
+};
+
+modes.ecb.prototype.pad = function(input, options) {
+  // add PKCS#7 padding to block (each pad byte is the
+  // value of the number of pad bytes)
+  var padding = (input.length() === this.blockSize ?
+    this.blockSize : (this.blockSize - input.length()));
+  input.fillWithByte(padding, padding);
+  return true;
+};
+
+modes.ecb.prototype.unpad = function(output, options) {
+  // check for error: input data not a multiple of blockSize
+  if(options.overflow > 0) {
+    return false;
+  }
+
+  // ensure padding byte count is valid
+  var len = output.length();
+  var count = output.at(len - 1);
+  if(count > (this.blockSize << 2)) {
+    return false;
+  }
+
+  // trim off padding bytes
+  output.truncate(count);
+  return true;
+};
+
+
+/** Cipher-block Chaining (CBC) **/
+
+modes.cbc = function(options) {
+  options = options || {};
+  this.name = 'CBC';
+  this.cipher = options.cipher;
+  this.blockSize = options.blockSize || 16;
+  this._blocks = this.blockSize / 4;
+  this._inBlock = new Array(this._blocks);
+  this._outBlock = new Array(this._blocks);
+};
+
+modes.cbc.prototype.start = function(options) {
+  // Note: legacy support for using IV residue (has security flaws)
+  // if IV is null, reuse block from previous processing
+  if(options.iv === null) {
+    // must have a previous block
+    if(!this._prev) {
+      throw new Error('Invalid IV parameter.');
+    }
+    this._iv = this._prev.slice(0);
+  } else if(!('iv' in options)) {
+    throw new Error('Invalid IV parameter.');
+  } else {
+    // save IV as "previous" block
+    this._iv = transformIV(options.iv);
+    this._prev = this._iv.slice(0);
+  }
+};
+
+modes.cbc.prototype.encrypt = function(input, output) {
+  // get next block
+  // CBC XOR's IV (or previous block) with plaintext
+  for(var i = 0; i < this._blocks; ++i) {
+    this._inBlock[i] = this._prev[i] ^ input.getInt32();
+  }
+
+  // encrypt block
+  this.cipher.encrypt(this._inBlock, this._outBlock);
+
+  // write output, save previous block
+  for(var i = 0; i < this._blocks; ++i) {
+    output.putInt32(this._outBlock[i]);
+  }
+  this._prev = this._outBlock;
+};
+
+modes.cbc.prototype.decrypt = function(input, output) {
+  // get next block
+  for(var i = 0; i < this._blocks; ++i) {
+    this._inBlock[i] = input.getInt32();
+  }
+
+  // decrypt block
+  this.cipher.decrypt(this._inBlock, this._outBlock);
+
+  // write output, save previous ciphered block
+  // CBC XOR's IV (or previous block) with ciphertext
+  for(var i = 0; i < this._blocks; ++i) {
+    output.putInt32(this._prev[i] ^ this._outBlock[i]);
+  }
+  this._prev = this._inBlock.slice(0);
+};
+
+modes.cbc.prototype.pad = function(input, options) {
+  // add PKCS#7 padding to block (each pad byte is the
+  // value of the number of pad bytes)
+  var padding = (input.length() === this.blockSize ?
+    this.blockSize : (this.blockSize - input.length()));
+  input.fillWithByte(padding, padding);
+  return true;
+};
+
+modes.cbc.prototype.unpad = function(output, options) {
+  // check for error: input data not a multiple of blockSize
+  if(options.overflow > 0) {
+    return false;
+  }
+
+  // ensure padding byte count is valid
+  var len = output.length();
+  var count = output.at(len - 1);
+  if(count > (this.blockSize << 2)) {
+    return false;
+  }
+
+  // trim off padding bytes
+  output.truncate(count);
+  return true;
+};
+
+
+/** Cipher feedback (CFB) **/
+
+modes.cfb = function(options) {
+  options = options || {};
+  this.name = 'CFB';
+  this.cipher = options.cipher;
+  this.blockSize = options.blockSize || 16;
+  this._blocks = this.blockSize / 4;
+  this._inBlock = null;
+  this._outBlock = new Array(this._blocks);
+};
+
+modes.cfb.prototype.start = function(options) {
+  if(!('iv' in options)) {
+    throw new Error('Invalid IV parameter.');
+  }
+  // use IV as first input
+  this._iv = transformIV(options.iv);
+  this._inBlock = this._iv.slice(0);
+};
+
+modes.cfb.prototype.encrypt = function(input, output) {
+  // encrypt block
+  this.cipher.encrypt(this._inBlock, this._outBlock);
+
+  // XOR input with output, write input as output
+  for(var i = 0; i < this._blocks; ++i) {
+    this._inBlock[i] = input.getInt32() ^ this._outBlock[i];
+    output.putInt32(this._inBlock[i]);
+  }
+};
+
+modes.cfb.prototype.decrypt = function(input, output) {
+  // encrypt block (CFB always uses encryption mode)
+  this.cipher.encrypt(this._inBlock, this._outBlock);
+
+  // XOR input with output
+  for(var i = 0; i < this._blocks; ++i) {
+    this._inBlock[i] = input.getInt32();
+    output.putInt32(this._inBlock[i] ^ this._outBlock[i]);
+  }
+};
+
+modes.cfb.prototype.afterFinish = function(output, options) {
+  // handle stream mode truncation
+  if(options.overflow > 0) {
+    output.truncate(this.blockSize - options.overflow);
+  }
+  return true;
+};
+
+/** Output feedback (OFB) **/
+
+modes.ofb = function(options) {
+  options = options || {};
+  this.name = 'OFB';
+  this.cipher = options.cipher;
+  this.blockSize = options.blockSize || 16;
+  this._blocks = this.blockSize / 4;
+  this._inBlock = null;
+  this._outBlock = new Array(this._blocks);
+};
+
+modes.ofb.prototype.start = function(options) {
+  if(!('iv' in options)) {
+    throw new Error('Invalid IV parameter.');
+  }
+  // use IV as first input
+  this._iv = transformIV(options.iv);
+  this._inBlock = this._iv.slice(0);
+};
+
+modes.ofb.prototype.encrypt = function(input, output) {
+  // encrypt block (OFB always uses encryption mode)
+  this.cipher.encrypt(this._inBlock, this._outBlock);
+
+  // XOR input with output and update next input
+  for(var i = 0; i < this._blocks; ++i) {
+    output.putInt32(input.getInt32() ^ this._outBlock[i]);
+    this._inBlock[i] = this._outBlock[i];
+  }
+};
+
+modes.ofb.prototype.decrypt = modes.ofb.prototype.encrypt;
+
+modes.ofb.prototype.afterFinish = function(output, options) {
+  // handle stream mode truncation
+  if(options.overflow > 0) {
+    output.truncate(this.blockSize - options.overflow);
+  }
+  return true;
+};
+
+
+/** Counter (CTR) **/
+
+modes.ctr = function(options) {
+  options = options || {};
+  this.name = 'CTR';
+  this.cipher = options.cipher;
+  this.blockSize = options.blockSize || 16;
+  this._blocks = this.blockSize / 4;
+  this._inBlock = null;
+  this._outBlock = new Array(this._blocks);
+};
+
+modes.ctr.prototype.start = function(options) {
+  if(!('iv' in options)) {
+    throw new Error('Invalid IV parameter.');
+  }
+  // use IV as first input
+  this._iv = transformIV(options.iv);
+  this._inBlock = this._iv.slice(0);
+};
+
+modes.ctr.prototype.encrypt = function(input, output) {
+  // encrypt block (CTR always uses encryption mode)
+  this.cipher.encrypt(this._inBlock, this._outBlock);
+
+  // increment counter (input block)
+  inc32(this._inBlock);
+
+  // XOR input with output
+  for(var i = 0; i < this._blocks; ++i) {
+    output.putInt32(input.getInt32() ^ this._outBlock[i]);
+  }
+};
+
+modes.ctr.prototype.decrypt = modes.ctr.prototype.encrypt;
+
+modes.ctr.prototype.afterFinish = function(output, options) {
+  // handle stream mode truncation
+  if(options.overflow > 0) {
+    output.truncate(this.blockSize - options.overflow);
+  }
+  return true;
+};
+
+
+/** Galois/Counter Mode (GCM) **/
+
+modes.gcm = function(options) {
+  options = options || {};
+  this.name = 'GCM';
+  this.cipher = options.cipher;
+  this.blockSize = options.blockSize || 16;
+  this._blocks = this.blockSize / 4;
+  this._inBlock = new Array(this._blocks);
+  this._outBlock = new Array(this._blocks);
+
+  // R is actually this value concatenated with 120 more zero bits, but
+  // we only XOR against R so the other zeros have no effect -- we just
+  // apply this value to the first integer in a block
+  this._R = 0xE1000000;
+};
+
+modes.gcm.prototype.start = function(options) {
+  if(!('iv' in options)) {
+    throw new Error('Invalid IV parameter.');
+  }
+  // ensure IV is a byte buffer
+  var iv = forge.util.createBuffer(options.iv);
+
+  // no ciphered data processed yet
+  this._cipherLength = 0;
+
+  // default additional data is none
+  var additionalData;
+  if('additionalData' in options) {
+    additionalData = forge.util.createBuffer(options.additionalData);
+  } else {
+    additionalData = forge.util.createBuffer();
+  }
+
+  // default tag length is 128 bits
+  if('tagLength' in options) {
+    this._tagLength = options.tagLength;
+  } else {
+    this._tagLength = 128;
+  }
+
+  // if tag is given, ensure tag matches tag length
+  this._tag = null;
+  if(options.decrypt) {
+    // save tag to check later
+    this._tag = forge.util.createBuffer(options.tag).getBytes();
+    if(this._tag.length !== (this._tagLength / 8)) {
+      throw new Error('Authentication tag does not match tag length.');
+    }
+  }
+
+  // create tmp storage for hash calculation
+  this._hashBlock = new Array(this._blocks);
+
+  // no tag generated yet
+  this.tag = null;
+
+  // generate hash subkey
+  // (apply block cipher to "zero" block)
+  this._hashSubkey = new Array(this._blocks);
+  this.cipher.encrypt([0, 0, 0, 0], this._hashSubkey);
+
+  // generate table M
+  // use 4-bit tables (32 component decomposition of a 16 byte value)
+  // 8-bit tables take more space and are known to have security
+  // vulnerabilities (in native implementations)
+  this.componentBits = 4;
+  this._m = this.generateHashTable(this._hashSubkey, this.componentBits);
+
+  // Note: support IV length different from 96 bits? (only supporting
+  // 96 bits is recommended by NIST SP-800-38D)
+  // generate J_0
+  var ivLength = iv.length();
+  if(ivLength === 12) {
+    // 96-bit IV
+    this._j0 = [iv.getInt32(), iv.getInt32(), iv.getInt32(), 1];
+  } else {
+    // IV is NOT 96-bits
+    this._j0 = [0, 0, 0, 0];
+    while(iv.length() > 0) {
+      this._j0 = this.ghash(
+        this._hashSubkey, this._j0,
+        [iv.getInt32(), iv.getInt32(), iv.getInt32(), iv.getInt32()]);
+    }
+    this._j0 = this.ghash(
+      this._hashSubkey, this._j0, [0, 0].concat(from64To32(ivLength * 8)));
+  }
+
+  // generate ICB (initial counter block)
+  this._inBlock = this._j0.slice(0);
+  inc32(this._inBlock);
+
+  // consume authentication data
+  additionalData = forge.util.createBuffer(additionalData);
+  // save additional data length as a BE 64-bit number
+  this._aDataLength = from64To32(additionalData.length() * 8);
+  // pad additional data to 128 bit (16 byte) block size
+  var overflow = additionalData.length() % this.blockSize;
+  if(overflow) {
+    additionalData.fillWithByte(0, this.blockSize - overflow);
+  }
+  this._s = [0, 0, 0, 0];
+  while(additionalData.length() > 0) {
+    this._s = this.ghash(this._hashSubkey, this._s, [
+      additionalData.getInt32(),
+      additionalData.getInt32(),
+      additionalData.getInt32(),
+      additionalData.getInt32()
+    ]);
+  }
+};
+
+modes.gcm.prototype.encrypt = function(input, output) {
+  // encrypt block
+  this.cipher.encrypt(this._inBlock, this._outBlock);
+
+  // increment counter (input block)
+  inc32(this._inBlock);
+
+  // save input length
+  var inputLength = input.length();
+
+  // XOR input with output
+  for(var i = 0; i < this._blocks; ++i) {
+    this._outBlock[i] ^= input.getInt32();
+  }
+
+  // handle overflow prior to hashing
+  if(inputLength < this.blockSize) {
+    // get block overflow
+    var overflow = inputLength % this.blockSize;
+    this._cipherLength += overflow;
+
+    // truncate for hash function
+    var tmp = forge.util.createBuffer();
+    tmp.putInt32(this._outBlock[0]);
+    tmp.putInt32(this._outBlock[1]);
+    tmp.putInt32(this._outBlock[2]);
+    tmp.putInt32(this._outBlock[3]);
+    tmp.truncate(this.blockSize - overflow);
+    this._outBlock[0] = tmp.getInt32();
+    this._outBlock[1] = tmp.getInt32();
+    this._outBlock[2] = tmp.getInt32();
+    this._outBlock[3] = tmp.getInt32();
+  } else {
+    this._cipherLength += this.blockSize;
+  }
+
+  for(var i = 0; i < this._blocks; ++i) {
+    output.putInt32(this._outBlock[i]);
+  }
+
+  // update hash block S
+  this._s = this.ghash(this._hashSubkey, this._s, this._outBlock);
+};
+
+modes.gcm.prototype.decrypt = function(input, output) {
+  // encrypt block (GCM always uses encryption mode)
+  this.cipher.encrypt(this._inBlock, this._outBlock);
+
+  // increment counter (input block)
+  inc32(this._inBlock);
+
+  // save input length
+  var inputLength = input.length();
+
+  // update hash block S
+  this._hashBlock[0] = input.getInt32();
+  this._hashBlock[1] = input.getInt32();
+  this._hashBlock[2] = input.getInt32();
+  this._hashBlock[3] = input.getInt32();
+  this._s = this.ghash(this._hashSubkey, this._s, this._hashBlock);
+
+  // XOR hash input with output
+  for(var i = 0; i < this._blocks; ++i) {
+    output.putInt32(this._outBlock[i] ^ this._hashBlock[i]);
+  }
+
+  // increment cipher data length
+  if(inputLength < this.blockSize) {
+    this._cipherLength += inputLength % this.blockSize;
+  } else {
+    this._cipherLength += this.blockSize;
+  }
+};
+
+modes.gcm.prototype.afterFinish = function(output, options) {
+  var rval = true;
+
+  // handle overflow
+  if(options.overflow) {
+    output.truncate(this.blockSize - options.overflow);
+  }
+
+  // handle authentication tag
+  this.tag = forge.util.createBuffer();
+
+  // concatenate additional data length with cipher length
+  var lengths = this._aDataLength.concat(from64To32(this._cipherLength * 8));
+
+  // include lengths in hash
+  this._s = this.ghash(this._hashSubkey, this._s, lengths);
+
+  // do GCTR(J_0, S)
+  var tag = [];
+  this.cipher.encrypt(this._j0, tag);
+  for(var i = 0; i < this._blocks; ++i) {
+    this.tag.putInt32(this._s[i] ^ tag[i]);
+  }
+
+  // trim tag to length
+  this.tag.truncate(this.tag.length() % (this._tagLength / 8));
+
+  // check authentication tag
+  if(options.decrypt && this.tag.bytes() !== this._tag) {
+    rval = false;
+  }
+
+  return rval;
+};
+
+/**
+ * See NIST SP-800-38D 6.3 (Algorithm 1). This function performs Galois
+ * field multiplication. The field, GF(2^128), is defined by the polynomial:
+ *
+ * x^128 + x^7 + x^2 + x + 1
+ *
+ * Which is represented in little-endian binary form as: 11100001 (0xe1). When
+ * the value of a coefficient is 1, a bit is set. The value R, is the
+ * concatenation of this value and 120 zero bits, yielding a 128-bit value
+ * which matches the block size.
+ *
+ * This function will multiply two elements (vectors of bytes), X and Y, in
+ * the field GF(2^128). The result is initialized to zero. For each bit of
+ * X (out of 128), x_i, if x_i is set, then the result is multiplied (XOR'd)
+ * by the current value of Y. For each bit, the value of Y will be raised by
+ * a power of x (multiplied by the polynomial x). This can be achieved by
+ * shifting Y once to the right. If the current value of Y, prior to being
+ * multiplied by x, has 0 as its LSB, then it is a 127th degree polynomial.
+ * Otherwise, we must divide by R after shifting to find the remainder.
+ *
+ * @param x the first block to multiply by the second.
+ * @param y the second block to multiply by the first.
+ *
+ * @return the block result of the multiplication.
+ */
+modes.gcm.prototype.multiply = function(x, y) {
+  var z_i = [0, 0, 0, 0];
+  var v_i = y.slice(0);
+
+  // calculate Z_128 (block has 128 bits)
+  for(var i = 0; i < 128; ++i) {
+    // if x_i is 0, Z_{i+1} = Z_i (unchanged)
+    // else Z_{i+1} = Z_i ^ V_i
+    // get x_i by finding 32-bit int position, then left shift 1 by remainder
+    var x_i = x[(i / 32) | 0] & (1 << (31 - i % 32));
+    if(x_i) {
+      z_i[0] ^= v_i[0];
+      z_i[1] ^= v_i[1];
+      z_i[2] ^= v_i[2];
+      z_i[3] ^= v_i[3];
+    }
+
+    // if LSB(V_i) is 1, V_i = V_i >> 1
+    // else V_i = (V_i >> 1) ^ R
+    this.pow(v_i, v_i);
+  }
+
+  return z_i;
+};
+
+modes.gcm.prototype.pow = function(x, out) {
+  // if LSB(x) is 1, x = x >>> 1
+  // else x = (x >>> 1) ^ R
+  var lsb = x[3] & 1;
+
+  // always do x >>> 1:
+  // starting with the rightmost integer, shift each integer to the right
+  // one bit, pulling in the bit from the integer to the left as its top
+  // most bit (do this for the last 3 integers)
+  for(var i = 3; i > 0; --i) {
+    out[i] = (x[i] >>> 1) | ((x[i - 1] & 1) << 31);
+  }
+  // shift the first integer normally
+  out[0] = x[0] >>> 1;
+
+  // if lsb was not set, then polynomial had a degree of 127 and doesn't
+  // need to divided; otherwise, XOR with R to find the remainder; we only
+  // need to XOR the first integer since R technically ends w/120 zero bits
+  if(lsb) {
+    out[0] ^= this._R;
+  }
+};
+
+modes.gcm.prototype.tableMultiply = function(x) {
+  // assumes 4-bit tables are used
+  var z = [0, 0, 0, 0];
+  for(var i = 0; i < 32; ++i) {
+    var idx = (i / 8) | 0;
+    var x_i = (x[idx] >>> ((7 - (i % 8)) * 4)) & 0xF;
+    var ah = this._m[i][x_i];
+    z[0] ^= ah[0];
+    z[1] ^= ah[1];
+    z[2] ^= ah[2];
+    z[3] ^= ah[3];
+  }
+  return z;
+};
+
+/**
+ * A continuing version of the GHASH algorithm that operates on a single
+ * block. The hash block, last hash value (Ym) and the new block to hash
+ * are given.
+ *
+ * @param h the hash block.
+ * @param y the previous value for Ym, use [0, 0, 0, 0] for a new hash.
+ * @param x the block to hash.
+ *
+ * @return the hashed value (Ym).
+ */
+modes.gcm.prototype.ghash = function(h, y, x) {
+  y[0] ^= x[0];
+  y[1] ^= x[1];
+  y[2] ^= x[2];
+  y[3] ^= x[3];
+  return this.tableMultiply(y);
+  //return this.multiply(y, h);
+};
+
+/**
+ * Precomputes a table for multiplying against the hash subkey. This
+ * mechanism provides a substantial speed increase over multiplication
+ * performed without a table. The table-based multiplication this table is
+ * for solves X * H by multiplying each component of X by H and then
+ * composing the results together using XOR.
+ *
+ * This function can be used to generate tables with different bit sizes
+ * for the components, however, this implementation assumes there are
+ * 32 components of X (which is a 16 byte vector), therefore each component
+ * takes 4-bits (so the table is constructed with bits=4).
+ *
+ * @param h the hash subkey.
+ * @param bits the bit size for a component.
+ */
+modes.gcm.prototype.generateHashTable = function(h, bits) {
+  // TODO: There are further optimizations that would use only the
+  // first table M_0 (or some variant) along with a remainder table;
+  // this can be explored in the future
+  var multiplier = 8 / bits;
+  var perInt = 4 * multiplier;
+  var size = 16 * multiplier;
+  var m = new Array(size);
+  for(var i = 0; i < size; ++i) {
+    var tmp = [0, 0, 0, 0];
+    var idx = (i / perInt) | 0;
+    var shft = ((perInt - 1 - (i % perInt)) * bits);
+    tmp[idx] = (1 << (bits - 1)) << shft;
+    m[i] = this.generateSubHashTable(this.multiply(tmp, h), bits);
+  }
+  return m;
+};
+
+/**
+ * Generates a table for multiplying against the hash subkey for one
+ * particular component (out of all possible component values).
+ *
+ * @param mid the pre-multiplied value for the middle key of the table.
+ * @param bits the bit size for a component.
+ */
+modes.gcm.prototype.generateSubHashTable = function(mid, bits) {
+  // compute the table quickly by minimizing the number of
+  // POW operations -- they only need to be performed for powers of 2,
+  // all other entries can be composed from those powers using XOR
+  var size = 1 << bits;
+  var half = size >>> 1;
+  var m = new Array(size);
+  m[half] = mid.slice(0);
+  var i = half >>> 1;
+  while(i > 0) {
+    // raise m0[2 * i] and store in m0[i]
+    this.pow(m[2 * i], m[i] = []);
+    i >>= 1;
+  }
+  i = 2;
+  while(i < half) {
+    for(var j = 1; j < i; ++j) {
+      var m_i = m[i];
+      var m_j = m[j];
+      m[i + j] = [
+        m_i[0] ^ m_j[0],
+        m_i[1] ^ m_j[1],
+        m_i[2] ^ m_j[2],
+        m_i[3] ^ m_j[3]
+      ];
+    }
+    i *= 2;
+  }
+  m[0] = [0, 0, 0, 0];
+  /* Note: We could avoid storing these by doing composition during multiply
+  calculate top half using composition by speed is preferred. */
+  for(i = half + 1; i < size; ++i) {
+    var c = m[i ^ half];
+    m[i] = [mid[0] ^ c[0], mid[1] ^ c[1], mid[2] ^ c[2], mid[3] ^ c[3]];
+  }
+  return m;
+};
+
+
+/** Utility functions */
+
+function transformIV(iv) {
+  if(typeof iv === 'string') {
+    // convert iv string into byte buffer
+    iv = forge.util.createBuffer(iv);
+  }
+
+  if(forge.util.isArray(iv) && iv.length > 4) {
+    // convert iv byte array into byte buffer
+    var tmp = iv;
+    iv = forge.util.createBuffer();
+    for(var i = 0; i < tmp.length; ++i) {
+      iv.putByte(tmp[i]);
+    }
+  }
+  if(!forge.util.isArray(iv)) {
+    // convert iv byte buffer into 32-bit integer array
+    iv = [iv.getInt32(), iv.getInt32(), iv.getInt32(), iv.getInt32()];
+  }
+
+  return iv;
+}
+
+function inc32(block) {
+  // increment last 32 bits of block only
+  block[block.length - 1] = (block[block.length - 1] + 1) & 0xFFFFFFFF;
+}
+
+function from64To32(num) {
+  // convert 64-bit number to two BE Int32s
+  return [(num / 0x100000000) | 0, num & 0xFFFFFFFF];
+}
+
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'cipherModes';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/debug.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/debug.js
new file mode 100644
index 0000000..4f7c13d
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/debug.js
@@ -0,0 +1,134 @@
+/**
+ * Debugging support for web applications.
+ *
+ * @author David I. Lehn <dlehn@digitalbazaar.com>
+ *
+ * Copyright 2008-2013 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+/* DEBUG API */
+forge.debug = forge.debug || {};
+
+// Private storage for debugging.
+// Useful to expose data that is otherwise unviewable behind closures.
+// NOTE: remember that this can hold references to data and cause leaks!
+// format is "forge._debug.<modulename>.<dataname> = data"
+// Example:
+// (function() {
+//   var cat = 'forge.test.Test'; // debugging category
+//   var sState = {...}; // local state
+//   forge.debug.set(cat, 'sState', sState);
+// })();
+forge.debug.storage = {};
+
+/**
+ * Gets debug data. Omit name for all cat data  Omit name and cat for
+ * all data.
+ *
+ * @param cat name of debugging category.
+ * @param name name of data to get (optional).
+ * @return object with requested debug data or undefined.
+ */
+forge.debug.get = function(cat, name) {
+  var rval;
+  if(typeof(cat) === 'undefined') {
+    rval = forge.debug.storage;
+  } else if(cat in forge.debug.storage) {
+    if(typeof(name) === 'undefined') {
+      rval = forge.debug.storage[cat];
+    } else {
+      rval = forge.debug.storage[cat][name];
+    }
+  }
+  return rval;
+};
+
+/**
+ * Sets debug data.
+ *
+ * @param cat name of debugging category.
+ * @param name name of data to set.
+ * @param data data to set.
+ */
+forge.debug.set = function(cat, name, data) {
+  if(!(cat in forge.debug.storage)) {
+    forge.debug.storage[cat] = {};
+  }
+  forge.debug.storage[cat][name] = data;
+};
+
+/**
+ * Clears debug data. Omit name for all cat data. Omit name and cat for
+ * all data.
+ *
+ * @param cat name of debugging category.
+ * @param name name of data to clear or omit to clear entire category.
+ */
+forge.debug.clear = function(cat, name) {
+  if(typeof(cat) === 'undefined') {
+    forge.debug.storage = {};
+  } else if(cat in forge.debug.storage) {
+    if(typeof(name) === 'undefined') {
+      delete forge.debug.storage[cat];
+    } else {
+      delete forge.debug.storage[cat][name];
+    }
+  }
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'debug';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/des.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/des.js
new file mode 100644
index 0000000..bf6d477
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/des.js
@@ -0,0 +1,552 @@
+/**
+ * DES (Data Encryption Standard) implementation.
+ *
+ * This implementation supports DES as well as 3DES-EDE in ECB and CBC mode.
+ * It is based on the BSD-licensed implementation by Paul Tero:
+ *
+ * Paul Tero, July 2001
+ * http://www.tero.co.uk/des/
+ *
+ * Optimised for performance with large blocks by Michael Hayworth, November 2001
+ * http://www.netdealing.com
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @author Stefan Siegl
+ * @author Dave Longley
+ *
+ * Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
+ * Copyright (c) 2012-2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+/* DES API */
+forge.des = forge.des || {};
+
+/**
+ * Deprecated. Instead, use:
+ *
+ * var cipher = forge.cipher.createCipher('DES-<mode>', key);
+ * cipher.start({iv: iv});
+ *
+ * Creates an DES cipher object to encrypt data using the given symmetric key.
+ * The output will be stored in the 'output' member of the returned cipher.
+ *
+ * The key and iv may be given as binary-encoded strings of bytes or
+ * byte buffers.
+ *
+ * @param key the symmetric key to use (64 or 192 bits).
+ * @param iv the initialization vector to use.
+ * @param output the buffer to write to, null to create one.
+ * @param mode the cipher mode to use (default: 'CBC' if IV is
+ *          given, 'ECB' if null).
+ *
+ * @return the cipher.
+ */
+forge.des.startEncrypting = function(key, iv, output, mode) {
+  var cipher = _createCipher({
+    key: key,
+    output: output,
+    decrypt: false,
+    mode: mode || (iv === null ? 'ECB' : 'CBC')
+  });
+  cipher.start(iv);
+  return cipher;
+};
+
+/**
+ * Deprecated. Instead, use:
+ *
+ * var cipher = forge.cipher.createCipher('DES-<mode>', key);
+ *
+ * Creates an DES cipher object to encrypt data using the given symmetric key.
+ *
+ * The key may be given as a binary-encoded string of bytes or a byte buffer.
+ *
+ * @param key the symmetric key to use (64 or 192 bits).
+ * @param mode the cipher mode to use (default: 'CBC').
+ *
+ * @return the cipher.
+ */
+forge.des.createEncryptionCipher = function(key, mode) {
+  return _createCipher({
+    key: key,
+    output: null,
+    decrypt: false,
+    mode: mode
+  });
+};
+
+/**
+ * Deprecated. Instead, use:
+ *
+ * var decipher = forge.cipher.createDecipher('DES-<mode>', key);
+ * decipher.start({iv: iv});
+ *
+ * Creates an DES cipher object to decrypt data using the given symmetric key.
+ * The output will be stored in the 'output' member of the returned cipher.
+ *
+ * The key and iv may be given as binary-encoded strings of bytes or
+ * byte buffers.
+ *
+ * @param key the symmetric key to use (64 or 192 bits).
+ * @param iv the initialization vector to use.
+ * @param output the buffer to write to, null to create one.
+ * @param mode the cipher mode to use (default: 'CBC' if IV is
+ *          given, 'ECB' if null).
+ *
+ * @return the cipher.
+ */
+forge.des.startDecrypting = function(key, iv, output, mode) {
+  var cipher = _createCipher({
+    key: key,
+    output: output,
+    decrypt: true,
+    mode: mode || (iv === null ? 'ECB' : 'CBC')
+  });
+  cipher.start(iv);
+  return cipher;
+};
+
+/**
+ * Deprecated. Instead, use:
+ *
+ * var decipher = forge.cipher.createDecipher('DES-<mode>', key);
+ *
+ * Creates an DES cipher object to decrypt data using the given symmetric key.
+ *
+ * The key may be given as a binary-encoded string of bytes or a byte buffer.
+ *
+ * @param key the symmetric key to use (64 or 192 bits).
+ * @param mode the cipher mode to use (default: 'CBC').
+ *
+ * @return the cipher.
+ */
+forge.des.createDecryptionCipher = function(key, mode) {
+  return _createCipher({
+    key: key,
+    output: null,
+    decrypt: true,
+    mode: mode
+  });
+};
+
+/**
+ * Creates a new DES cipher algorithm object.
+ *
+ * @param name the name of the algorithm.
+ * @param mode the mode factory function.
+ *
+ * @return the DES algorithm object.
+ */
+forge.des.Algorithm = function(name, mode) {
+  var self = this;
+  self.name = name;
+  self.mode = new mode({
+    blockSize: 8,
+    cipher: {
+      encrypt: function(inBlock, outBlock) {
+        return _updateBlock(self._keys, inBlock, outBlock, false);
+      },
+      decrypt: function(inBlock, outBlock) {
+        return _updateBlock(self._keys, inBlock, outBlock, true);
+      }
+    }
+  });
+  self._init = false;
+};
+
+/**
+ * Initializes this DES algorithm by expanding its key.
+ *
+ * @param options the options to use.
+ *          key the key to use with this algorithm.
+ *          decrypt true if the algorithm should be initialized for decryption,
+ *            false for encryption.
+ */
+forge.des.Algorithm.prototype.initialize = function(options) {
+  if(this._init) {
+    return;
+  }
+
+  var key = forge.util.createBuffer(options.key);
+  if(this.name.indexOf('3DES') === 0) {
+    if(key.length() !== 24) {
+      throw new Error('Invalid Triple-DES key size: ' + key.length() * 8);
+    }
+  }
+
+  // do key expansion to 16 or 48 subkeys (single or triple DES)
+  this._keys = _createKeys(key);
+  this._init = true;
+};
+
+
+/** Register DES algorithms **/
+
+registerAlgorithm('DES-ECB', forge.cipher.modes.ecb);
+registerAlgorithm('DES-CBC', forge.cipher.modes.cbc);
+registerAlgorithm('DES-CFB', forge.cipher.modes.cfb);
+registerAlgorithm('DES-OFB', forge.cipher.modes.ofb);
+registerAlgorithm('DES-CTR', forge.cipher.modes.ctr);
+
+registerAlgorithm('3DES-ECB', forge.cipher.modes.ecb);
+registerAlgorithm('3DES-CBC', forge.cipher.modes.cbc);
+registerAlgorithm('3DES-CFB', forge.cipher.modes.cfb);
+registerAlgorithm('3DES-OFB', forge.cipher.modes.ofb);
+registerAlgorithm('3DES-CTR', forge.cipher.modes.ctr);
+
+function registerAlgorithm(name, mode) {
+  var factory = function() {
+    return new forge.des.Algorithm(name, mode);
+  };
+  forge.cipher.registerAlgorithm(name, factory);
+}
+
+
+/** DES implementation **/
+
+var spfunction1 = [0x1010400,0,0x10000,0x1010404,0x1010004,0x10404,0x4,0x10000,0x400,0x1010400,0x1010404,0x400,0x1000404,0x1010004,0x1000000,0x4,0x404,0x1000400,0x1000400,0x10400,0x10400,0x1010000,0x1010000,0x1000404,0x10004,0x1000004,0x1000004,0x10004,0,0x404,0x10404,0x1000000,0x10000,0x1010404,0x4,0x1010000,0x1010400,0x1000000,0x1000000,0x400,0x1010004,0x10000,0x10400,0x1000004,0x400,0x4,0x1000404,0x10404,0x1010404,0x10004,0x1010000,0x1000404,0x1000004,0x404,0x10404,0x1010400,0x404,0x1000400,0x1000400,0,0x10004,0x10400,0,0x1010004];
+var spfunction2 = [-0x7fef7fe0,-0x7fff8000,0x8000,0x108020,0x100000,0x20,-0x7fefffe0,-0x7fff7fe0,-0x7fffffe0,-0x7fef7fe0,-0x7fef8000,-0x80000000,-0x7fff8000,0x100000,0x20,-0x7fefffe0,0x108000,0x100020,-0x7fff7fe0,0,-0x80000000,0x8000,0x108020,-0x7ff00000,0x100020,-0x7fffffe0,0,0x108000,0x8020,-0x7fef8000,-0x7ff00000,0x8020,0,0x108020,-0x7fefffe0,0x100000,-0x7fff7fe0,-0x7ff00000,-0x7fef8000,0x8000,-0x7ff00000,-0x7fff8000,0x20,-0x7fef7fe0,0x108020,0x20,0x8000,-0x80000000,0x8020,-0x7fef8000,0x100000,-0x7fffffe0,0x100020,-0x7fff7fe0,-0x7fffffe0,0x100020,0x108000,0,-0x7fff8000,0x8020,-0x80000000,-0x7fefffe0,-0x7fef7fe0,0x108000];
+var spfunction3 = [0x208,0x8020200,0,0x8020008,0x8000200,0,0x20208,0x8000200,0x20008,0x8000008,0x8000008,0x20000,0x8020208,0x20008,0x8020000,0x208,0x8000000,0x8,0x8020200,0x200,0x20200,0x8020000,0x8020008,0x20208,0x8000208,0x20200,0x20000,0x8000208,0x8,0x8020208,0x200,0x8000000,0x8020200,0x8000000,0x20008,0x208,0x20000,0x8020200,0x8000200,0,0x200,0x20008,0x8020208,0x8000200,0x8000008,0x200,0,0x8020008,0x8000208,0x20000,0x8000000,0x8020208,0x8,0x20208,0x20200,0x8000008,0x8020000,0x8000208,0x208,0x8020000,0x20208,0x8,0x8020008,0x20200];
+var spfunction4 = [0x802001,0x2081,0x2081,0x80,0x802080,0x800081,0x800001,0x2001,0,0x802000,0x802000,0x802081,0x81,0,0x800080,0x800001,0x1,0x2000,0x800000,0x802001,0x80,0x800000,0x2001,0x2080,0x800081,0x1,0x2080,0x800080,0x2000,0x802080,0x802081,0x81,0x800080,0x800001,0x802000,0x802081,0x81,0,0,0x802000,0x2080,0x800080,0x800081,0x1,0x802001,0x2081,0x2081,0x80,0x802081,0x81,0x1,0x2000,0x800001,0x2001,0x802080,0x800081,0x2001,0x2080,0x800000,0x802001,0x80,0x800000,0x2000,0x802080];
+var spfunction5 = [0x100,0x2080100,0x2080000,0x42000100,0x80000,0x100,0x40000000,0x2080000,0x40080100,0x80000,0x2000100,0x40080100,0x42000100,0x42080000,0x80100,0x40000000,0x2000000,0x40080000,0x40080000,0,0x40000100,0x42080100,0x42080100,0x2000100,0x42080000,0x40000100,0,0x42000000,0x2080100,0x2000000,0x42000000,0x80100,0x80000,0x42000100,0x100,0x2000000,0x40000000,0x2080000,0x42000100,0x40080100,0x2000100,0x40000000,0x42080000,0x2080100,0x40080100,0x100,0x2000000,0x42080000,0x42080100,0x80100,0x42000000,0x42080100,0x2080000,0,0x40080000,0x42000000,0x80100,0x2000100,0x40000100,0x80000,0,0x40080000,0x2080100,0x40000100];
+var spfunction6 = [0x20000010,0x20400000,0x4000,0x20404010,0x20400000,0x10,0x20404010,0x400000,0x20004000,0x404010,0x400000,0x20000010,0x400010,0x20004000,0x20000000,0x4010,0,0x400010,0x20004010,0x4000,0x404000,0x20004010,0x10,0x20400010,0x20400010,0,0x404010,0x20404000,0x4010,0x404000,0x20404000,0x20000000,0x20004000,0x10,0x20400010,0x404000,0x20404010,0x400000,0x4010,0x20000010,0x400000,0x20004000,0x20000000,0x4010,0x20000010,0x20404010,0x404000,0x20400000,0x404010,0x20404000,0,0x20400010,0x10,0x4000,0x20400000,0x404010,0x4000,0x400010,0x20004010,0,0x20404000,0x20000000,0x400010,0x20004010];
+var spfunction7 = [0x200000,0x4200002,0x4000802,0,0x800,0x4000802,0x200802,0x4200800,0x4200802,0x200000,0,0x4000002,0x2,0x4000000,0x4200002,0x802,0x4000800,0x200802,0x200002,0x4000800,0x4000002,0x4200000,0x4200800,0x200002,0x4200000,0x800,0x802,0x4200802,0x200800,0x2,0x4000000,0x200800,0x4000000,0x200800,0x200000,0x4000802,0x4000802,0x4200002,0x4200002,0x2,0x200002,0x4000000,0x4000800,0x200000,0x4200800,0x802,0x200802,0x4200800,0x802,0x4000002,0x4200802,0x4200000,0x200800,0,0x2,0x4200802,0,0x200802,0x4200000,0x800,0x4000002,0x4000800,0x800,0x200002];
+var spfunction8 = [0x10001040,0x1000,0x40000,0x10041040,0x10000000,0x10001040,0x40,0x10000000,0x40040,0x10040000,0x10041040,0x41000,0x10041000,0x41040,0x1000,0x40,0x10040000,0x10000040,0x10001000,0x1040,0x41000,0x40040,0x10040040,0x10041000,0x1040,0,0,0x10040040,0x10000040,0x10001000,0x41040,0x40000,0x41040,0x40000,0x10041000,0x1000,0x40,0x10040040,0x1000,0x41040,0x10001000,0x40,0x10000040,0x10040000,0x10040040,0x10000000,0x40000,0x10001040,0,0x10041040,0x40040,0x10000040,0x10040000,0x10001000,0x10001040,0,0x10041040,0x41000,0x41000,0x1040,0x1040,0x40040,0x10000000,0x10041000];
+
+/**
+ * Create necessary sub keys.
+ *
+ * @param key the 64-bit or 192-bit key.
+ *
+ * @return the expanded keys.
+ */
+function _createKeys(key) {
+  var pc2bytes0  = [0,0x4,0x20000000,0x20000004,0x10000,0x10004,0x20010000,0x20010004,0x200,0x204,0x20000200,0x20000204,0x10200,0x10204,0x20010200,0x20010204],
+      pc2bytes1  = [0,0x1,0x100000,0x100001,0x4000000,0x4000001,0x4100000,0x4100001,0x100,0x101,0x100100,0x100101,0x4000100,0x4000101,0x4100100,0x4100101],
+      pc2bytes2  = [0,0x8,0x800,0x808,0x1000000,0x1000008,0x1000800,0x1000808,0,0x8,0x800,0x808,0x1000000,0x1000008,0x1000800,0x1000808],
+      pc2bytes3  = [0,0x200000,0x8000000,0x8200000,0x2000,0x202000,0x8002000,0x8202000,0x20000,0x220000,0x8020000,0x8220000,0x22000,0x222000,0x8022000,0x8222000],
+      pc2bytes4  = [0,0x40000,0x10,0x40010,0,0x40000,0x10,0x40010,0x1000,0x41000,0x1010,0x41010,0x1000,0x41000,0x1010,0x41010],
+      pc2bytes5  = [0,0x400,0x20,0x420,0,0x400,0x20,0x420,0x2000000,0x2000400,0x2000020,0x2000420,0x2000000,0x2000400,0x2000020,0x2000420],
+      pc2bytes6  = [0,0x10000000,0x80000,0x10080000,0x2,0x10000002,0x80002,0x10080002,0,0x10000000,0x80000,0x10080000,0x2,0x10000002,0x80002,0x10080002],
+      pc2bytes7  = [0,0x10000,0x800,0x10800,0x20000000,0x20010000,0x20000800,0x20010800,0x20000,0x30000,0x20800,0x30800,0x20020000,0x20030000,0x20020800,0x20030800],
+      pc2bytes8  = [0,0x40000,0,0x40000,0x2,0x40002,0x2,0x40002,0x2000000,0x2040000,0x2000000,0x2040000,0x2000002,0x2040002,0x2000002,0x2040002],
+      pc2bytes9  = [0,0x10000000,0x8,0x10000008,0,0x10000000,0x8,0x10000008,0x400,0x10000400,0x408,0x10000408,0x400,0x10000400,0x408,0x10000408],
+      pc2bytes10 = [0,0x20,0,0x20,0x100000,0x100020,0x100000,0x100020,0x2000,0x2020,0x2000,0x2020,0x102000,0x102020,0x102000,0x102020],
+      pc2bytes11 = [0,0x1000000,0x200,0x1000200,0x200000,0x1200000,0x200200,0x1200200,0x4000000,0x5000000,0x4000200,0x5000200,0x4200000,0x5200000,0x4200200,0x5200200],
+      pc2bytes12 = [0,0x1000,0x8000000,0x8001000,0x80000,0x81000,0x8080000,0x8081000,0x10,0x1010,0x8000010,0x8001010,0x80010,0x81010,0x8080010,0x8081010],
+      pc2bytes13 = [0,0x4,0x100,0x104,0,0x4,0x100,0x104,0x1,0x5,0x101,0x105,0x1,0x5,0x101,0x105];
+
+  // how many iterations (1 for des, 3 for triple des)
+  // changed by Paul 16/6/2007 to use Triple DES for 9+ byte keys
+  var iterations = key.length() > 8 ? 3 : 1;
+
+  // stores the return keys
+  var keys = [];
+
+  // now define the left shifts which need to be done
+  var shifts = [0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0];
+
+  var n = 0, tmp;
+  for(var j = 0; j < iterations; j ++) {
+    var left = key.getInt32();
+    var right = key.getInt32();
+
+    tmp = ((left >>> 4) ^ right) & 0x0f0f0f0f;
+    right ^= tmp;
+    left ^= (tmp << 4);
+
+    tmp = ((right >>> -16) ^ left) & 0x0000ffff;
+    left ^= tmp;
+    right ^= (tmp << -16);
+
+    tmp = ((left >>> 2) ^ right) & 0x33333333;
+    right ^= tmp;
+    left ^= (tmp << 2);
+
+    tmp = ((right >>> -16) ^ left) & 0x0000ffff;
+    left ^= tmp;
+    right ^= (tmp << -16);
+
+    tmp = ((left >>> 1) ^ right) & 0x55555555;
+    right ^= tmp;
+    left ^= (tmp << 1);
+
+    tmp = ((right >>> 8) ^ left) & 0x00ff00ff;
+    left ^= tmp;
+    right ^= (tmp << 8);
+
+    tmp = ((left >>> 1) ^ right) & 0x55555555;
+    right ^= tmp;
+    left ^= (tmp << 1);
+
+    // right needs to be shifted and OR'd with last four bits of left
+    tmp = (left << 8) | ((right >>> 20) & 0x000000f0);
+
+    // left needs to be put upside down
+    left = ((right << 24) | ((right << 8) & 0xff0000) |
+      ((right >>> 8) & 0xff00) | ((right >>> 24) & 0xf0));
+    right = tmp;
+
+    // now go through and perform these shifts on the left and right keys
+    for(var i = 0; i < shifts.length; ++i) {
+      //shift the keys either one or two bits to the left
+      if(shifts[i]) {
+        left = (left << 2) | (left >>> 26);
+        right = (right << 2) | (right >>> 26);
+      } else {
+        left = (left << 1) | (left >>> 27);
+        right = (right << 1) | (right >>> 27);
+      }
+      left &= -0xf;
+      right &= -0xf;
+
+      // now apply PC-2, in such a way that E is easier when encrypting or
+      // decrypting this conversion will look like PC-2 except only the last 6
+      // bits of each byte are used rather than 48 consecutive bits and the
+      // order of lines will be according to how the S selection functions will
+      // be applied: S2, S4, S6, S8, S1, S3, S5, S7
+      var lefttmp = (
+        pc2bytes0[left >>> 28] | pc2bytes1[(left >>> 24) & 0xf] |
+        pc2bytes2[(left >>> 20) & 0xf] | pc2bytes3[(left >>> 16) & 0xf] |
+        pc2bytes4[(left >>> 12) & 0xf] | pc2bytes5[(left >>> 8) & 0xf] |
+        pc2bytes6[(left >>> 4) & 0xf]);
+      var righttmp = (
+        pc2bytes7[right >>> 28] | pc2bytes8[(right >>> 24) & 0xf] |
+        pc2bytes9[(right >>> 20) & 0xf] | pc2bytes10[(right >>> 16) & 0xf] |
+        pc2bytes11[(right >>> 12) & 0xf] | pc2bytes12[(right >>> 8) & 0xf] |
+        pc2bytes13[(right >>> 4) & 0xf]);
+      tmp = ((righttmp >>> 16) ^ lefttmp) & 0x0000ffff;
+      keys[n++] = lefttmp ^ tmp;
+      keys[n++] = righttmp ^ (tmp << 16);
+    }
+  }
+
+  return keys;
+}
+
+/**
+ * Updates a single block (1 byte) using DES. The update will either
+ * encrypt or decrypt the block.
+ *
+ * @param keys the expanded keys.
+ * @param input the input block (an array of 32-bit words).
+ * @param output the updated output block.
+ * @param decrypt true to decrypt the block, false to encrypt it.
+ */
+function _updateBlock(keys, input, output, decrypt) {
+  // set up loops for single or triple DES
+  var iterations = keys.length === 32 ? 3 : 9;
+  var looping;
+  if(iterations === 3) {
+    looping = decrypt ? [30, -2, -2] : [0, 32, 2];
+  } else {
+    looping = (decrypt ?
+      [94, 62, -2, 32, 64, 2, 30, -2, -2] :
+      [0, 32, 2, 62, 30, -2, 64, 96, 2]);
+  }
+
+  var tmp;
+
+  var left = input[0];
+  var right = input[1];
+
+  // first each 64 bit chunk of the message must be permuted according to IP
+  tmp = ((left >>> 4) ^ right) & 0x0f0f0f0f;
+  right ^= tmp;
+  left ^= (tmp << 4);
+
+  tmp = ((left >>> 16) ^ right) & 0x0000ffff;
+  right ^= tmp;
+  left ^= (tmp << 16);
+
+  tmp = ((right >>> 2) ^ left) & 0x33333333;
+  left ^= tmp;
+  right ^= (tmp << 2);
+
+  tmp = ((right >>> 8) ^ left) & 0x00ff00ff;
+  left ^= tmp;
+  right ^= (tmp << 8);
+
+  tmp = ((left >>> 1) ^ right) & 0x55555555;
+  right ^= tmp;
+  left ^= (tmp << 1);
+
+  // rotate left 1 bit
+  left = ((left << 1) | (left >>> 31));
+  right = ((right << 1) | (right >>> 31));
+
+  for(var j = 0; j < iterations; j += 3) {
+    var endloop = looping[j + 1];
+    var loopinc = looping[j + 2];
+
+    // now go through and perform the encryption or decryption
+    for(var i = looping[j]; i != endloop; i += loopinc) {
+      var right1 = right ^ keys[i];
+      var right2 = ((right >>> 4) | (right << 28)) ^ keys[i + 1];
+
+      // passing these bytes through the S selection functions
+      tmp = left;
+      left = right;
+      right = tmp ^ (
+        spfunction2[(right1 >>> 24) & 0x3f] |
+        spfunction4[(right1 >>> 16) & 0x3f] |
+        spfunction6[(right1 >>>  8) & 0x3f] |
+        spfunction8[right1 & 0x3f] |
+        spfunction1[(right2 >>> 24) & 0x3f] |
+        spfunction3[(right2 >>> 16) & 0x3f] |
+        spfunction5[(right2 >>>  8) & 0x3f] |
+        spfunction7[right2 & 0x3f]);
+    }
+    // unreverse left and right
+    tmp = left;
+    left = right;
+    right = tmp;
+  }
+
+  // rotate right 1 bit
+  left = ((left >>> 1) | (left << 31));
+  right = ((right >>> 1) | (right << 31));
+
+  // now perform IP-1, which is IP in the opposite direction
+  tmp = ((left >>> 1) ^ right) & 0x55555555;
+  right ^= tmp;
+  left ^= (tmp << 1);
+
+  tmp = ((right >>> 8) ^ left) & 0x00ff00ff;
+  left ^= tmp;
+  right ^= (tmp << 8);
+
+  tmp = ((right >>> 2) ^ left) & 0x33333333;
+  left ^= tmp;
+  right ^= (tmp << 2);
+
+  tmp = ((left >>> 16) ^ right) & 0x0000ffff;
+  right ^= tmp;
+  left ^= (tmp << 16);
+
+  tmp = ((left >>> 4) ^ right) & 0x0f0f0f0f;
+  right ^= tmp;
+  left ^= (tmp << 4);
+
+  output[0] = left;
+  output[1] = right;
+}
+
+/**
+ * Deprecated. Instead, use:
+ *
+ * forge.cipher.createCipher('DES-<mode>', key);
+ * forge.cipher.createDecipher('DES-<mode>', key);
+ *
+ * Creates a deprecated DES cipher object. This object's mode will default to
+ * CBC (cipher-block-chaining).
+ *
+ * The key may be given as a binary-encoded string of bytes or a byte buffer.
+ *
+ * @param options the options to use.
+ *          key the symmetric key to use (64 or 192 bits).
+ *          output the buffer to write to.
+ *          decrypt true for decryption, false for encryption.
+ *          mode the cipher mode to use (default: 'CBC').
+ *
+ * @return the cipher.
+ */
+function _createCipher(options) {
+  options = options || {};
+  var mode = (options.mode || 'CBC').toUpperCase();
+  var algorithm = 'DES-' + mode;
+
+  var cipher;
+  if(options.decrypt) {
+    cipher = forge.cipher.createDecipher(algorithm, options.key);
+  } else {
+    cipher = forge.cipher.createCipher(algorithm, options.key);
+  }
+
+  // backwards compatible start API
+  var start = cipher.start;
+  cipher.start = function(iv, options) {
+    // backwards compatibility: support second arg as output buffer
+    var output = null;
+    if(options instanceof forge.util.ByteBuffer) {
+      output = options;
+      options = {};
+    }
+    options = options || {};
+    options.output = output;
+    options.iv = iv;
+    start.call(cipher, options);
+  };
+
+  return cipher;
+}
+
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'des';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(
+  ['require', 'module', './cipher', './cipherModes', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/forge.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/forge.js
new file mode 100644
index 0000000..b314e22
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/forge.js
@@ -0,0 +1,92 @@
+/**
+ * Node.js module for Forge.
+ *
+ * @author Dave Longley
+ *
+ * Copyright 2011-2014 Digital Bazaar, Inc.
+ */
+(function() {
+var name = 'forge';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      // set to true to disable native code if even it's available
+      forge = {disableNativeCode: false};
+    }
+    return;
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    });
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge;
+  };
+  // set to true to disable native code if even it's available
+  module.exports.disableNativeCode = false;
+  module.exports(module.exports);
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define([
+  'require',
+  'module',
+  './aes',
+  './aesCipherSuites',
+  './asn1',
+  './cipher',
+  './cipherModes',
+  './debug',
+  './des',
+  './hmac',
+  './kem',
+  './log',
+  './md',
+  './mgf1',
+  './pbkdf2',
+  './pem',
+  './pkcs7',
+  './pkcs1',
+  './pkcs12',
+  './pki',
+  './prime',
+  './prng',
+  './pss',
+  './random',
+  './rc2',
+  './ssh',
+  './task',
+  './tls',
+  './util'
+], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/form.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/form.js
new file mode 100644
index 0000000..62d4424
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/form.js
@@ -0,0 +1,157 @@
+/**
+ * Functions for manipulating web forms.
+ *
+ * @author David I. Lehn <dlehn@digitalbazaar.com>
+ * @author Dave Longley
+ * @author Mike Johnson
+ *
+ * Copyright (c) 2011-2014 Digital Bazaar, Inc. All rights reserved.
+ */
+(function($) {
+
+/**
+ * The form namespace.
+ */
+var form = {};
+
+/**
+ * Regex for parsing a single name property (handles array brackets).
+ */
+var _regex = /(.*?)\[(.*?)\]/g;
+
+/**
+ * Parses a single name property into an array with the name and any
+ * array indices.
+ *
+ * @param name the name to parse.
+ *
+ * @return the array of the name and its array indices in order.
+ */
+var _parseName = function(name) {
+  var rval = [];
+
+  var matches;
+  while(!!(matches = _regex.exec(name))) {
+    if(matches[1].length > 0) {
+      rval.push(matches[1]);
+    }
+    if(matches.length >= 2) {
+      rval.push(matches[2]);
+    }
+  }
+  if(rval.length === 0) {
+    rval.push(name);
+  }
+
+  return rval;
+};
+
+/**
+ * Adds a field from the given form to the given object.
+ *
+ * @param obj the object.
+ * @param names the field as an array of object property names.
+ * @param value the value of the field.
+ * @param dict a dictionary of names to replace.
+ */
+var _addField = function(obj, names, value, dict) {
+  // combine array names that fall within square brackets
+  var tmp = [];
+  for(var i = 0; i < names.length; ++i) {
+    // check name for starting square bracket but no ending one
+    var name = names[i];
+    if(name.indexOf('[') !== -1 && name.indexOf(']') === -1 &&
+      i < names.length - 1) {
+      do {
+        name += '.' + names[++i];
+      } while(i < names.length - 1 && names[i].indexOf(']') === -1);
+    }
+    tmp.push(name);
+  }
+  names = tmp;
+
+  // split out array indexes
+  var tmp = [];
+  $.each(names, function(n, name) {
+    tmp = tmp.concat(_parseName(name));
+  });
+  names = tmp;
+
+  // iterate over object property names until value is set
+  $.each(names, function(n, name) {
+    // do dictionary name replacement
+    if(dict && name.length !== 0 && name in dict) {
+       name = dict[name];
+    }
+
+    // blank name indicates appending to an array, set name to
+    // new last index of array
+    if(name.length === 0) {
+       name = obj.length;
+    }
+
+    // value already exists, append value
+    if(obj[name]) {
+      // last name in the field
+      if(n == names.length - 1) {
+        // more than one value, so convert into an array
+        if(!$.isArray(obj[name])) {
+          obj[name] = [obj[name]];
+        }
+        obj[name].push(value);
+      } else {
+        // not last name, go deeper into object
+        obj = obj[name];
+      }
+    } else if(n == names.length - 1) {
+      // new value, last name in the field, set value
+      obj[name] = value;
+    } else {
+      // new value, not last name, go deeper
+      // get next name
+      var next = names[n + 1];
+
+      // blank next value indicates array-appending, so create array
+      if(next.length === 0) {
+         obj[name] = [];
+      } else {
+        // if next name is a number create an array, otherwise a map
+        var isNum = ((next - 0) == next && next.length > 0);
+        obj[name] = isNum ? [] : {};
+      }
+      obj = obj[name];
+    }
+  });
+};
+
+/**
+ * Serializes a form to a JSON object. Object properties will be separated
+ * using the given separator (defaults to '.') and by square brackets.
+ *
+ * @param input the jquery form to serialize.
+ * @param sep the object-property separator (defaults to '.').
+ * @param dict a dictionary of names to replace (name=replace).
+ *
+ * @return the JSON-serialized form.
+ */
+form.serialize = function(input, sep, dict) {
+  var rval = {};
+
+  // add all fields in the form to the object
+  sep = sep || '.';
+  $.each(input.serializeArray(), function() {
+    _addField(rval, this.name.split(sep), this.value || '', dict);
+  });
+
+  return rval;
+};
+
+/**
+ * The forge namespace and form API.
+ */
+if(typeof forge === 'undefined') {
+  forge = {};
+}
+forge.form = form;
+
+})(jQuery);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/hmac.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/hmac.js
new file mode 100644
index 0000000..eee58bc
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/hmac.js
@@ -0,0 +1,200 @@
+/**
+ * Hash-based Message Authentication Code implementation. Requires a message
+ * digest object that can be obtained, for example, from forge.md.sha1 or
+ * forge.md.md5.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2012 Digital Bazaar, Inc. All rights reserved.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+/* HMAC API */
+var hmac = forge.hmac = forge.hmac || {};
+
+/**
+ * Creates an HMAC object that uses the given message digest object.
+ *
+ * @return an HMAC object.
+ */
+hmac.create = function() {
+  // the hmac key to use
+  var _key = null;
+
+  // the message digest to use
+  var _md = null;
+
+  // the inner padding
+  var _ipadding = null;
+
+  // the outer padding
+  var _opadding = null;
+
+  // hmac context
+  var ctx = {};
+
+  /**
+   * Starts or restarts the HMAC with the given key and message digest.
+   *
+   * @param md the message digest to use, null to reuse the previous one,
+   *           a string to use builtin 'sha1', 'md5', 'sha256'.
+   * @param key the key to use as a string, array of bytes, byte buffer,
+   *           or null to reuse the previous key.
+   */
+  ctx.start = function(md, key) {
+    if(md !== null) {
+      if(typeof md === 'string') {
+        // create builtin message digest
+        md = md.toLowerCase();
+        if(md in forge.md.algorithms) {
+          _md = forge.md.algorithms[md].create();
+        } else {
+          throw new Error('Unknown hash algorithm "' + md + '"');
+        }
+      } else {
+        // store message digest
+        _md = md;
+      }
+    }
+
+    if(key === null) {
+      // reuse previous key
+      key = _key;
+    } else {
+      if(typeof key === 'string') {
+        // convert string into byte buffer
+        key = forge.util.createBuffer(key);
+      } else if(forge.util.isArray(key)) {
+        // convert byte array into byte buffer
+        var tmp = key;
+        key = forge.util.createBuffer();
+        for(var i = 0; i < tmp.length; ++i) {
+          key.putByte(tmp[i]);
+        }
+      }
+
+      // if key is longer than blocksize, hash it
+      var keylen = key.length();
+      if(keylen > _md.blockLength) {
+        _md.start();
+        _md.update(key.bytes());
+        key = _md.digest();
+      }
+
+      // mix key into inner and outer padding
+      // ipadding = [0x36 * blocksize] ^ key
+      // opadding = [0x5C * blocksize] ^ key
+      _ipadding = forge.util.createBuffer();
+      _opadding = forge.util.createBuffer();
+      keylen = key.length();
+      for(var i = 0; i < keylen; ++i) {
+        var tmp = key.at(i);
+        _ipadding.putByte(0x36 ^ tmp);
+        _opadding.putByte(0x5C ^ tmp);
+      }
+
+      // if key is shorter than blocksize, add additional padding
+      if(keylen < _md.blockLength) {
+        var tmp = _md.blockLength - keylen;
+        for(var i = 0; i < tmp; ++i) {
+          _ipadding.putByte(0x36);
+          _opadding.putByte(0x5C);
+        }
+      }
+      _key = key;
+      _ipadding = _ipadding.bytes();
+      _opadding = _opadding.bytes();
+    }
+
+    // digest is done like so: hash(opadding | hash(ipadding | message))
+
+    // prepare to do inner hash
+    // hash(ipadding | message)
+    _md.start();
+    _md.update(_ipadding);
+  };
+
+  /**
+   * Updates the HMAC with the given message bytes.
+   *
+   * @param bytes the bytes to update with.
+   */
+  ctx.update = function(bytes) {
+    _md.update(bytes);
+  };
+
+  /**
+   * Produces the Message Authentication Code (MAC).
+   *
+   * @return a byte buffer containing the digest value.
+   */
+  ctx.getMac = function() {
+    // digest is done like so: hash(opadding | hash(ipadding | message))
+    // here we do the outer hashing
+    var inner = _md.digest().bytes();
+    _md.start();
+    _md.update(_opadding);
+    _md.update(inner);
+    return _md.digest();
+  };
+  // alias for getMac
+  ctx.digest = ctx.getMac;
+
+  return ctx;
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'hmac';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './md', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/http.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/http.js
new file mode 100644
index 0000000..fa01aed
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/http.js
@@ -0,0 +1,1369 @@
+/**
+ * HTTP client-side implementation that uses forge.net sockets.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2014 Digital Bazaar, Inc. All rights reserved.
+ */
+(function() {
+
+// define http namespace
+var http = {};
+
+// logging category
+var cat = 'forge.http';
+
+// add array of clients to debug storage
+if(forge.debug) {
+  forge.debug.set('forge.http', 'clients', []);
+}
+
+// normalizes an http header field name
+var _normalize = function(name) {
+  return name.toLowerCase().replace(/(^.)|(-.)/g,
+    function(a){return a.toUpperCase();});
+};
+
+/**
+ * Gets the local storage ID for the given client.
+ *
+ * @param client the client to get the local storage ID for.
+ *
+ * @return the local storage ID to use.
+ */
+var _getStorageId = function(client) {
+  // TODO: include browser in ID to avoid sharing cookies between
+  // browsers (if this is undesirable)
+  // navigator.userAgent
+  return 'forge.http.' +
+    client.url.scheme + '.' +
+    client.url.host + '.' +
+    client.url.port;
+};
+
+/**
+ * Loads persistent cookies from disk for the given client.
+ *
+ * @param client the client.
+ */
+var _loadCookies = function(client) {
+  if(client.persistCookies) {
+    try {
+      var cookies = forge.util.getItem(
+        client.socketPool.flashApi,
+        _getStorageId(client), 'cookies');
+      client.cookies = cookies || {};
+    } catch(ex) {
+      // no flash storage available, just silently fail
+      // TODO: i assume we want this logged somewhere or
+      // should it actually generate an error
+      //forge.log.error(cat, ex);
+    }
+  }
+};
+
+/**
+ * Saves persistent cookies on disk for the given client.
+ *
+ * @param client the client.
+ */
+var _saveCookies = function(client) {
+  if(client.persistCookies) {
+    try {
+      forge.util.setItem(
+        client.socketPool.flashApi,
+        _getStorageId(client), 'cookies', client.cookies);
+    } catch(ex) {
+      // no flash storage available, just silently fail
+      // TODO: i assume we want this logged somewhere or
+      // should it actually generate an error
+      //forge.log.error(cat, ex);
+    }
+  }
+
+  // FIXME: remove me
+  _loadCookies(client);
+};
+
+/**
+ * Clears persistent cookies on disk for the given client.
+ *
+ * @param client the client.
+ */
+var _clearCookies = function(client) {
+  if(client.persistCookies) {
+    try {
+      // only thing stored is 'cookies', so clear whole storage
+      forge.util.clearItems(
+        client.socketPool.flashApi,
+        _getStorageId(client));
+    } catch(ex) {
+      // no flash storage available, just silently fail
+      // TODO: i assume we want this logged somewhere or
+      // should it actually generate an error
+      //forge.log.error(cat, ex);
+    }
+  }
+};
+
+/**
+ * Connects and sends a request.
+ *
+ * @param client the http client.
+ * @param socket the socket to use.
+ */
+var _doRequest = function(client, socket) {
+  if(socket.isConnected()) {
+    // already connected
+    socket.options.request.connectTime = +new Date();
+    socket.connected({
+      type: 'connect',
+      id: socket.id
+    });
+  } else {
+    // connect
+    socket.options.request.connectTime = +new Date();
+    socket.connect({
+      host: client.url.host,
+      port: client.url.port,
+      policyPort: client.policyPort,
+      policyUrl: client.policyUrl
+    });
+  }
+};
+
+/**
+ * Handles the next request or marks a socket as idle.
+ *
+ * @param client the http client.
+ * @param socket the socket.
+ */
+var _handleNextRequest = function(client, socket) {
+  // clear buffer
+  socket.buffer.clear();
+
+  // get pending request
+  var pending = null;
+  while(pending === null && client.requests.length > 0) {
+    pending = client.requests.shift();
+    if(pending.request.aborted) {
+      pending = null;
+    }
+  }
+
+  // mark socket idle if no pending requests
+  if(pending === null) {
+    if(socket.options !== null) {
+      socket.options = null;
+    }
+    client.idle.push(socket);
+  } else {
+    // handle pending request, allow 1 retry
+    socket.retries = 1;
+    socket.options = pending;
+    _doRequest(client, socket);
+  }
+};
+
+/**
+ * Sets up a socket for use with an http client.
+ *
+ * @param client the parent http client.
+ * @param socket the socket to set up.
+ * @param tlsOptions if the socket must use TLS, the TLS options.
+ */
+var _initSocket = function(client, socket, tlsOptions) {
+  // no socket options yet
+  socket.options = null;
+
+  // set up handlers
+  socket.connected = function(e) {
+    // socket primed by caching TLS session, handle next request
+    if(socket.options === null) {
+      _handleNextRequest(client, socket);
+    } else {
+      // socket in use
+      var request = socket.options.request;
+      request.connectTime = +new Date() - request.connectTime;
+      e.socket = socket;
+      socket.options.connected(e);
+      if(request.aborted) {
+        socket.close();
+      } else {
+        var out = request.toString();
+        if(request.body) {
+          out += request.body;
+        }
+        request.time = +new Date();
+        socket.send(out);
+        request.time = +new Date() - request.time;
+        socket.options.response.time = +new Date();
+        socket.sending = true;
+      }
+    }
+  };
+  socket.closed = function(e) {
+    if(socket.sending) {
+      socket.sending = false;
+      if(socket.retries > 0) {
+        --socket.retries;
+        _doRequest(client, socket);
+      } else {
+        // error, closed during send
+        socket.error({
+          id: socket.id,
+          type: 'ioError',
+          message: 'Connection closed during send. Broken pipe.',
+          bytesAvailable: 0
+        });
+      }
+    } else {
+      // handle unspecified content-length transfer
+      var response = socket.options.response;
+      if(response.readBodyUntilClose) {
+        response.time = +new Date() - response.time;
+        response.bodyReceived = true;
+        socket.options.bodyReady({
+          request: socket.options.request,
+          response: response,
+          socket: socket
+        });
+      }
+      socket.options.closed(e);
+      _handleNextRequest(client, socket);
+    }
+  };
+  socket.data = function(e) {
+    socket.sending = false;
+    var request = socket.options.request;
+    if(request.aborted) {
+      socket.close();
+    } else {
+      // receive all bytes available
+      var response = socket.options.response;
+      var bytes = socket.receive(e.bytesAvailable);
+      if(bytes !== null) {
+        // receive header and then body
+        socket.buffer.putBytes(bytes);
+        if(!response.headerReceived) {
+          response.readHeader(socket.buffer);
+          if(response.headerReceived) {
+            socket.options.headerReady({
+              request: socket.options.request,
+              response: response,
+              socket: socket
+            });
+          }
+        }
+        if(response.headerReceived && !response.bodyReceived) {
+          response.readBody(socket.buffer);
+        }
+        if(response.bodyReceived) {
+          socket.options.bodyReady({
+            request: socket.options.request,
+            response: response,
+            socket: socket
+          });
+          // close connection if requested or by default on http/1.0
+          var value = response.getField('Connection') || '';
+          if(value.indexOf('close') != -1 ||
+            (response.version === 'HTTP/1.0' &&
+            response.getField('Keep-Alive') === null)) {
+            socket.close();
+          } else {
+            _handleNextRequest(client, socket);
+          }
+        }
+      }
+    }
+  };
+  socket.error = function(e) {
+    // do error callback, include request
+    socket.options.error({
+      type: e.type,
+      message: e.message,
+      request: socket.options.request,
+      response: socket.options.response,
+      socket: socket
+    });
+    socket.close();
+  };
+
+  // wrap socket for TLS
+  if(tlsOptions) {
+    socket = forge.tls.wrapSocket({
+      sessionId: null,
+      sessionCache: {},
+      caStore: tlsOptions.caStore,
+      cipherSuites: tlsOptions.cipherSuites,
+      socket: socket,
+      virtualHost: tlsOptions.virtualHost,
+      verify: tlsOptions.verify,
+      getCertificate: tlsOptions.getCertificate,
+      getPrivateKey: tlsOptions.getPrivateKey,
+      getSignature: tlsOptions.getSignature,
+      deflate: tlsOptions.deflate || null,
+      inflate: tlsOptions.inflate || null
+    });
+
+    socket.options = null;
+    socket.buffer = forge.util.createBuffer();
+    client.sockets.push(socket);
+    if(tlsOptions.prime) {
+      // prime socket by connecting and caching TLS session, will do
+      // next request from there
+      socket.connect({
+        host: client.url.host,
+        port: client.url.port,
+        policyPort: client.policyPort,
+        policyUrl: client.policyUrl
+      });
+    } else {
+      // do not prime socket, just add as idle
+      client.idle.push(socket);
+    }
+  } else {
+    // no need to prime non-TLS sockets
+    socket.buffer = forge.util.createBuffer();
+    client.sockets.push(socket);
+    client.idle.push(socket);
+  }
+};
+
+/**
+ * Checks to see if the given cookie has expired. If the cookie's max-age
+ * plus its created time is less than the time now, it has expired, unless
+ * its max-age is set to -1 which indicates it will never expire.
+ *
+ * @param cookie the cookie to check.
+ *
+ * @return true if it has expired, false if not.
+ */
+var _hasCookieExpired = function(cookie) {
+  var rval = false;
+
+  if(cookie.maxAge !== -1) {
+    var now = _getUtcTime(new Date());
+    var expires = cookie.created + cookie.maxAge;
+    if(expires <= now) {
+      rval = true;
+    }
+  }
+
+  return rval;
+};
+
+/**
+ * Adds cookies in the given client to the given request.
+ *
+ * @param client the client.
+ * @param request the request.
+ */
+var _writeCookies = function(client, request) {
+  var expired = [];
+  var url = client.url;
+  var cookies = client.cookies;
+  for(var name in cookies) {
+    // get cookie paths
+    var paths = cookies[name];
+    for(var p in paths) {
+      var cookie = paths[p];
+      if(_hasCookieExpired(cookie)) {
+        // store for clean up
+        expired.push(cookie);
+      } else if(request.path.indexOf(cookie.path) === 0) {
+        // path or path's ancestor must match cookie.path
+        request.addCookie(cookie);
+      }
+    }
+  }
+
+  // clean up expired cookies
+  for(var i = 0; i < expired.length; ++i) {
+    var cookie = expired[i];
+    client.removeCookie(cookie.name, cookie.path);
+  }
+};
+
+/**
+ * Gets cookies from the given response and adds the to the given client.
+ *
+ * @param client the client.
+ * @param response the response.
+ */
+var _readCookies = function(client, response) {
+  var cookies = response.getCookies();
+  for(var i = 0; i < cookies.length; ++i) {
+    try {
+      client.setCookie(cookies[i]);
+    } catch(ex) {
+      // ignore failure to add other-domain, etc. cookies
+    }
+  }
+};
+
+/**
+ * Creates an http client that uses forge.net sockets as a backend and
+ * forge.tls for security.
+ *
+ * @param options:
+ *   url: the url to connect to (scheme://host:port).
+ *     socketPool: the flash socket pool to use.
+ *   policyPort: the flash policy port to use (if other than the
+ *     socket pool default), use 0 for flash default.
+ *   policyUrl: the flash policy file URL to use (if provided will
+ *     be used instead of a policy port).
+ *   connections: number of connections to use to handle requests.
+ *   caCerts: an array of certificates to trust for TLS, certs may
+ *     be PEM-formatted or cert objects produced via forge.pki.
+ *   cipherSuites: an optional array of cipher suites to use,
+ *     see forge.tls.CipherSuites.
+ *   virtualHost: the virtual server name to use in a TLS SNI
+ *     extension, if not provided the url host will be used.
+ *   verify: a custom TLS certificate verify callback to use.
+ *   getCertificate: an optional callback used to get a client-side
+ *     certificate (see forge.tls for details).
+ *   getPrivateKey: an optional callback used to get a client-side
+ *     private key (see forge.tls for details).
+ *   getSignature: an optional callback used to get a client-side
+ *     signature (see forge.tls for details).
+ *   persistCookies: true to use persistent cookies via flash local
+ *     storage, false to only keep cookies in javascript.
+ *   primeTlsSockets: true to immediately connect TLS sockets on
+ *     their creation so that they will cache TLS sessions for reuse.
+ *
+ * @return the client.
+ */
+http.createClient = function(options) {
+  // create CA store to share with all TLS connections
+  var caStore = null;
+  if(options.caCerts) {
+    caStore = forge.pki.createCaStore(options.caCerts);
+  }
+
+  // get scheme, host, and port from url
+  options.url = (options.url ||
+    window.location.protocol + '//' + window.location.host);
+  var url = http.parseUrl(options.url);
+  if(!url) {
+    var error = new Error('Invalid url.');
+    error.details = {url: options.url};
+    throw error;
+  }
+
+  // default to 1 connection
+  options.connections = options.connections || 1;
+
+  // create client
+  var sp = options.socketPool;
+  var client = {
+    // url
+    url: url,
+    // socket pool
+    socketPool: sp,
+    // the policy port to use
+    policyPort: options.policyPort,
+    // policy url to use
+    policyUrl: options.policyUrl,
+    // queue of requests to service
+    requests: [],
+    // all sockets
+    sockets: [],
+    // idle sockets
+    idle: [],
+    // whether or not the connections are secure
+    secure: (url.scheme === 'https'),
+    // cookie jar (key'd off of name and then path, there is only 1 domain
+    // and one setting for secure per client so name+path is unique)
+    cookies: {},
+    // default to flash storage of cookies
+    persistCookies: (typeof(options.persistCookies) === 'undefined') ?
+      true : options.persistCookies
+  };
+
+  // add client to debug storage
+  if(forge.debug) {
+    forge.debug.get('forge.http', 'clients').push(client);
+  }
+
+  // load cookies from disk
+  _loadCookies(client);
+
+  /**
+   * A default certificate verify function that checks a certificate common
+   * name against the client's URL host.
+   *
+   * @param c the TLS connection.
+   * @param verified true if cert is verified, otherwise alert number.
+   * @param depth the chain depth.
+   * @param certs the cert chain.
+   *
+   * @return true if verified and the common name matches the host, error
+   *         otherwise.
+   */
+  var _defaultCertificateVerify = function(c, verified, depth, certs) {
+    if(depth === 0 && verified === true) {
+      // compare common name to url host
+      var cn = certs[depth].subject.getField('CN');
+      if(cn === null || client.url.host !== cn.value) {
+        verified = {
+          message: 'Certificate common name does not match url host.'
+        };
+      }
+    }
+    return verified;
+  };
+
+  // determine if TLS is used
+  var tlsOptions = null;
+  if(client.secure) {
+    tlsOptions = {
+      caStore: caStore,
+      cipherSuites: options.cipherSuites || null,
+      virtualHost: options.virtualHost || url.host,
+      verify: options.verify || _defaultCertificateVerify,
+      getCertificate: options.getCertificate || null,
+      getPrivateKey: options.getPrivateKey || null,
+      getSignature: options.getSignature || null,
+      prime: options.primeTlsSockets || false
+    };
+
+    // if socket pool uses a flash api, then add deflate support to TLS
+    if(sp.flashApi !== null) {
+      tlsOptions.deflate = function(bytes) {
+        // strip 2 byte zlib header and 4 byte trailer
+        return forge.util.deflate(sp.flashApi, bytes, true);
+      };
+      tlsOptions.inflate = function(bytes) {
+        return forge.util.inflate(sp.flashApi, bytes, true);
+      };
+    }
+  }
+
+  // create and initialize sockets
+  for(var i = 0; i < options.connections; ++i) {
+    _initSocket(client, sp.createSocket(), tlsOptions);
+  }
+
+  /**
+   * Sends a request. A method 'abort' will be set on the request that
+   * can be called to attempt to abort the request.
+   *
+   * @param options:
+   *          request: the request to send.
+   *          connected: a callback for when the connection is open.
+   *          closed: a callback for when the connection is closed.
+   *          headerReady: a callback for when the response header arrives.
+   *          bodyReady: a callback for when the response body arrives.
+   *          error: a callback for if an error occurs.
+   */
+  client.send = function(options) {
+    // add host header if not set
+    if(options.request.getField('Host') === null) {
+      options.request.setField('Host', client.url.fullHost);
+    }
+
+    // set default dummy handlers
+    var opts = {};
+    opts.request = options.request;
+    opts.connected = options.connected || function(){};
+    opts.closed = options.close || function(){};
+    opts.headerReady = function(e) {
+      // read cookies
+      _readCookies(client, e.response);
+      if(options.headerReady) {
+        options.headerReady(e);
+      }
+    };
+    opts.bodyReady = options.bodyReady || function(){};
+    opts.error = options.error || function(){};
+
+    // create response
+    opts.response = http.createResponse();
+    opts.response.time = 0;
+    opts.response.flashApi = client.socketPool.flashApi;
+    opts.request.flashApi = client.socketPool.flashApi;
+
+    // create abort function
+    opts.request.abort = function() {
+      // set aborted, clear handlers
+      opts.request.aborted = true;
+      opts.connected = function(){};
+      opts.closed = function(){};
+      opts.headerReady = function(){};
+      opts.bodyReady = function(){};
+      opts.error = function(){};
+    };
+
+    // add cookies to request
+    _writeCookies(client, opts.request);
+
+    // queue request options if there are no idle sockets
+    if(client.idle.length === 0) {
+      client.requests.push(opts);
+    } else {
+      // use an idle socket, prefer an idle *connected* socket first
+      var socket = null;
+      var len = client.idle.length;
+      for(var i = 0; socket === null && i < len; ++i) {
+        socket = client.idle[i];
+        if(socket.isConnected()) {
+          client.idle.splice(i, 1);
+        } else {
+          socket = null;
+        }
+      }
+      // no connected socket available, get unconnected socket
+      if(socket === null) {
+        socket = client.idle.pop();
+      }
+      socket.options = opts;
+      _doRequest(client, socket);
+    }
+  };
+
+  /**
+   * Destroys this client.
+   */
+  client.destroy = function() {
+    // clear pending requests, close and destroy sockets
+    client.requests = [];
+    for(var i = 0; i < client.sockets.length; ++i) {
+      client.sockets[i].close();
+      client.sockets[i].destroy();
+    }
+    client.socketPool = null;
+    client.sockets = [];
+    client.idle = [];
+  };
+
+  /**
+   * Sets a cookie for use with all connections made by this client. Any
+   * cookie with the same name will be replaced. If the cookie's value
+   * is undefined, null, or the blank string, the cookie will be removed.
+   *
+   * If the cookie's domain doesn't match this client's url host or the
+   * cookie's secure flag doesn't match this client's url scheme, then
+   * setting the cookie will fail with an exception.
+   *
+   * @param cookie the cookie with parameters:
+   *   name: the name of the cookie.
+   *   value: the value of the cookie.
+   *   comment: an optional comment string.
+   *   maxAge: the age of the cookie in seconds relative to created time.
+   *   secure: true if the cookie must be sent over a secure protocol.
+   *   httpOnly: true to restrict access to the cookie from javascript
+   *     (inaffective since the cookies are stored in javascript).
+   *   path: the path for the cookie.
+   *   domain: optional domain the cookie belongs to (must start with dot).
+   *   version: optional version of the cookie.
+   *   created: creation time, in UTC seconds, of the cookie.
+   */
+  client.setCookie = function(cookie) {
+    var rval;
+    if(typeof(cookie.name) !== 'undefined') {
+      if(cookie.value === null || typeof(cookie.value) === 'undefined' ||
+        cookie.value === '') {
+        // remove cookie
+        rval = client.removeCookie(cookie.name, cookie.path);
+      } else {
+        // set cookie defaults
+        cookie.comment = cookie.comment || '';
+        cookie.maxAge = cookie.maxAge || 0;
+        cookie.secure = (typeof(cookie.secure) === 'undefined') ?
+          true : cookie.secure;
+        cookie.httpOnly = cookie.httpOnly || true;
+        cookie.path = cookie.path || '/';
+        cookie.domain = cookie.domain || null;
+        cookie.version = cookie.version || null;
+        cookie.created = _getUtcTime(new Date());
+
+        // do secure check
+        if(cookie.secure !== client.secure) {
+          var error = new Error('Http client url scheme is incompatible ' +
+            'with cookie secure flag.');
+          error.url = client.url;
+          error.cookie = cookie;
+          throw error;
+        }
+        // make sure url host is within cookie.domain
+        if(!http.withinCookieDomain(client.url, cookie)) {
+          var error = new Error('Http client url scheme is incompatible ' +
+            'with cookie secure flag.');
+          error.url = client.url;
+          error.cookie = cookie;
+          throw error;
+        }
+
+        // add new cookie
+        if(!(cookie.name in client.cookies)) {
+          client.cookies[cookie.name] = {};
+        }
+        client.cookies[cookie.name][cookie.path] = cookie;
+        rval = true;
+
+        // save cookies
+        _saveCookies(client);
+      }
+    }
+
+    return rval;
+  };
+
+  /**
+   * Gets a cookie by its name.
+   *
+   * @param name the name of the cookie to retrieve.
+   * @param path an optional path for the cookie (if there are multiple
+   *          cookies with the same name but different paths).
+   *
+   * @return the cookie or null if not found.
+   */
+  client.getCookie = function(name, path) {
+    var rval = null;
+    if(name in client.cookies) {
+      var paths = client.cookies[name];
+
+      // get path-specific cookie
+      if(path) {
+        if(path in paths) {
+          rval = paths[path];
+        }
+      } else {
+        // get first cookie
+        for(var p in paths) {
+          rval = paths[p];
+          break;
+        }
+      }
+    }
+    return rval;
+  };
+
+  /**
+   * Removes a cookie.
+   *
+   * @param name the name of the cookie to remove.
+   * @param path an optional path for the cookie (if there are multiple
+   *          cookies with the same name but different paths).
+   *
+   * @return true if a cookie was removed, false if not.
+   */
+  client.removeCookie = function(name, path) {
+    var rval = false;
+    if(name in client.cookies) {
+      // delete the specific path
+      if(path) {
+        var paths = client.cookies[name];
+        if(path in paths) {
+          rval = true;
+          delete client.cookies[name][path];
+          // clean up entry if empty
+          var empty = true;
+          for(var i in client.cookies[name]) {
+            empty = false;
+            break;
+          }
+          if(empty) {
+            delete client.cookies[name];
+          }
+        }
+      } else {
+        // delete all cookies with the given name
+        rval = true;
+        delete client.cookies[name];
+      }
+    }
+    if(rval) {
+      // save cookies
+      _saveCookies(client);
+    }
+    return rval;
+  };
+
+  /**
+   * Clears all cookies stored in this client.
+   */
+  client.clearCookies = function() {
+    client.cookies = {};
+    _clearCookies(client);
+  };
+
+  if(forge.log) {
+    forge.log.debug('forge.http', 'created client', options);
+  }
+
+  return client;
+};
+
+/**
+ * Trims the whitespace off of the beginning and end of a string.
+ *
+ * @param str the string to trim.
+ *
+ * @return the trimmed string.
+ */
+var _trimString = function(str) {
+  return str.replace(/^\s*/, '').replace(/\s*$/, '');
+};
+
+/**
+ * Creates an http header object.
+ *
+ * @return the http header object.
+ */
+var _createHeader = function() {
+  var header = {
+    fields: {},
+    setField: function(name, value) {
+      // normalize field name, trim value
+      header.fields[_normalize(name)] = [_trimString('' + value)];
+    },
+    appendField: function(name, value) {
+      name = _normalize(name);
+      if(!(name in header.fields)) {
+        header.fields[name] = [];
+      }
+      header.fields[name].push(_trimString('' + value));
+    },
+    getField: function(name, index) {
+      var rval = null;
+      name = _normalize(name);
+      if(name in header.fields) {
+        index = index || 0;
+        rval = header.fields[name][index];
+      }
+      return rval;
+    }
+  };
+  return header;
+};
+
+/**
+ * Gets the time in utc seconds given a date.
+ *
+ * @param d the date to use.
+ *
+ * @return the time in utc seconds.
+ */
+var _getUtcTime = function(d) {
+  var utc = +d + d.getTimezoneOffset() * 60000;
+  return Math.floor(+new Date() / 1000);
+};
+
+/**
+ * Creates an http request.
+ *
+ * @param options:
+ *          version: the version.
+ *          method: the method.
+ *          path: the path.
+ *          body: the body.
+ *          headers: custom header fields to add,
+ *            eg: [{'Content-Length': 0}].
+ *
+ * @return the http request.
+ */
+http.createRequest = function(options) {
+  options = options || {};
+  var request = _createHeader();
+  request.version = options.version || 'HTTP/1.1';
+  request.method = options.method || null;
+  request.path = options.path || null;
+  request.body = options.body || null;
+  request.bodyDeflated = false;
+  request.flashApi = null;
+
+  // add custom headers
+  var headers = options.headers || [];
+  if(!forge.util.isArray(headers)) {
+    headers = [headers];
+  }
+  for(var i = 0; i < headers.length; ++i) {
+    for(var name in headers[i]) {
+      request.appendField(name, headers[i][name]);
+    }
+  }
+
+  /**
+   * Adds a cookie to the request 'Cookie' header.
+   *
+   * @param cookie a cookie to add.
+   */
+  request.addCookie = function(cookie) {
+    var value = '';
+    var field = request.getField('Cookie');
+    if(field !== null) {
+      // separate cookies by semi-colons
+      value = field + '; ';
+    }
+
+    // get current time in utc seconds
+    var now = _getUtcTime(new Date());
+
+    // output cookie name and value
+    value += cookie.name + '=' + cookie.value;
+    request.setField('Cookie', value);
+  };
+
+  /**
+   * Converts an http request into a string that can be sent as an
+   * HTTP request. Does not include any data.
+   *
+   * @return the string representation of the request.
+   */
+  request.toString = function() {
+    /* Sample request header:
+      GET /some/path/?query HTTP/1.1
+      Host: www.someurl.com
+      Connection: close
+      Accept-Encoding: deflate
+      Accept: image/gif, text/html
+      User-Agent: Mozilla 4.0
+     */
+
+    // set default headers
+    if(request.getField('User-Agent') === null) {
+      request.setField('User-Agent', 'forge.http 1.0');
+    }
+    if(request.getField('Accept') === null) {
+      request.setField('Accept', '*/*');
+    }
+    if(request.getField('Connection') === null) {
+      request.setField('Connection', 'keep-alive');
+      request.setField('Keep-Alive', '115');
+    }
+
+    // add Accept-Encoding if not specified
+    if(request.flashApi !== null &&
+      request.getField('Accept-Encoding') === null) {
+      request.setField('Accept-Encoding', 'deflate');
+    }
+
+    // if the body isn't null, deflate it if its larger than 100 bytes
+    if(request.flashApi !== null && request.body !== null &&
+      request.getField('Content-Encoding') === null &&
+      !request.bodyDeflated && request.body.length > 100) {
+      // use flash to compress data
+      request.body = forge.util.deflate(request.flashApi, request.body);
+      request.bodyDeflated = true;
+      request.setField('Content-Encoding', 'deflate');
+      request.setField('Content-Length', request.body.length);
+    } else if(request.body !== null) {
+      // set content length for body
+      request.setField('Content-Length', request.body.length);
+    }
+
+    // build start line
+    var rval =
+      request.method.toUpperCase() + ' ' + request.path + ' ' +
+      request.version + '\r\n';
+
+    // add each header
+    for(var name in request.fields) {
+      var fields = request.fields[name];
+      for(var i = 0; i < fields.length; ++i) {
+        rval += name + ': ' + fields[i] + '\r\n';
+      }
+    }
+    // final terminating CRLF
+    rval += '\r\n';
+
+    return rval;
+  };
+
+  return request;
+};
+
+/**
+ * Creates an empty http response header.
+ *
+ * @return the empty http response header.
+ */
+http.createResponse = function() {
+  // private vars
+  var _first = true;
+  var _chunkSize = 0;
+  var _chunksFinished = false;
+
+  // create response
+  var response = _createHeader();
+  response.version = null;
+  response.code = 0;
+  response.message = null;
+  response.body = null;
+  response.headerReceived = false;
+  response.bodyReceived = false;
+  response.flashApi = null;
+
+  /**
+   * Reads a line that ends in CRLF from a byte buffer.
+   *
+   * @param b the byte buffer.
+   *
+   * @return the line or null if none was found.
+   */
+  var _readCrlf = function(b) {
+    var line = null;
+    var i = b.data.indexOf('\r\n', b.read);
+    if(i != -1) {
+      // read line, skip CRLF
+      line = b.getBytes(i - b.read);
+      b.getBytes(2);
+    }
+    return line;
+  };
+
+  /**
+   * Parses a header field and appends it to the response.
+   *
+   * @param line the header field line.
+   */
+  var _parseHeader = function(line) {
+    var tmp = line.indexOf(':');
+    var name = line.substring(0, tmp++);
+    response.appendField(
+      name, (tmp < line.length) ? line.substring(tmp) : '');
+  };
+
+  /**
+   * Reads an http response header from a buffer of bytes.
+   *
+   * @param b the byte buffer to parse the header from.
+   *
+   * @return true if the whole header was read, false if not.
+   */
+  response.readHeader = function(b) {
+    // read header lines (each ends in CRLF)
+    var line = '';
+    while(!response.headerReceived && line !== null) {
+      line = _readCrlf(b);
+      if(line !== null) {
+        // parse first line
+        if(_first) {
+          _first = false;
+          var tmp = line.split(' ');
+          if(tmp.length >= 3) {
+            response.version = tmp[0];
+            response.code = parseInt(tmp[1], 10);
+            response.message = tmp.slice(2).join(' ');
+          } else {
+            // invalid header
+            var error = new Error('Invalid http response header.');
+            error.details = {'line': line};
+            throw error;
+          }
+        } else if(line.length === 0) {
+          // handle final line, end of header
+          response.headerReceived = true;
+        } else {
+          _parseHeader(line);
+        }
+      }
+    }
+
+    return response.headerReceived;
+  };
+
+  /**
+   * Reads some chunked http response entity-body from the given buffer of
+   * bytes.
+   *
+   * @param b the byte buffer to read from.
+   *
+   * @return true if the whole body was read, false if not.
+   */
+  var _readChunkedBody = function(b) {
+    /* Chunked transfer-encoding sends data in a series of chunks,
+      followed by a set of 0-N http trailers.
+      The format is as follows:
+
+      chunk-size (in hex) CRLF
+      chunk data (with "chunk-size" many bytes) CRLF
+      ... (N many chunks)
+      chunk-size (of 0 indicating the last chunk) CRLF
+      N many http trailers followed by CRLF
+      blank line + CRLF (terminates the trailers)
+
+      If there are no http trailers, then after the chunk-size of 0,
+      there is still a single CRLF (indicating the blank line + CRLF
+      that terminates the trailers). In other words, you always terminate
+      the trailers with blank line + CRLF, regardless of 0-N trailers. */
+
+      /* From RFC-2616, section 3.6.1, here is the pseudo-code for
+      implementing chunked transfer-encoding:
+
+      length := 0
+      read chunk-size, chunk-extension (if any) and CRLF
+      while (chunk-size > 0) {
+        read chunk-data and CRLF
+        append chunk-data to entity-body
+        length := length + chunk-size
+        read chunk-size and CRLF
+      }
+      read entity-header
+      while (entity-header not empty) {
+        append entity-header to existing header fields
+        read entity-header
+      }
+      Content-Length := length
+      Remove "chunked" from Transfer-Encoding
+    */
+
+    var line = '';
+    while(line !== null && b.length() > 0) {
+      // if in the process of reading a chunk
+      if(_chunkSize > 0) {
+        // if there are not enough bytes to read chunk and its
+        // trailing CRLF,  we must wait for more data to be received
+        if(_chunkSize + 2 > b.length()) {
+          break;
+        }
+
+        // read chunk data, skip CRLF
+        response.body += b.getBytes(_chunkSize);
+        b.getBytes(2);
+        _chunkSize = 0;
+      } else if(!_chunksFinished) {
+        // more chunks, read next chunk-size line
+        line = _readCrlf(b);
+        if(line !== null) {
+          // parse chunk-size (ignore any chunk extension)
+          _chunkSize = parseInt(line.split(';', 1)[0], 16);
+          _chunksFinished = (_chunkSize === 0);
+        }
+      } else {
+        // chunks finished, read next trailer
+        line = _readCrlf(b);
+        while(line !== null) {
+          if(line.length > 0) {
+            // parse trailer
+            _parseHeader(line);
+            // read next trailer
+            line = _readCrlf(b);
+          } else {
+            // body received
+            response.bodyReceived = true;
+            line = null;
+          }
+        }
+      }
+    }
+
+    return response.bodyReceived;
+  };
+
+  /**
+   * Reads an http response body from a buffer of bytes.
+   *
+   * @param b the byte buffer to read from.
+   *
+   * @return true if the whole body was read, false if not.
+   */
+  response.readBody = function(b) {
+    var contentLength = response.getField('Content-Length');
+    var transferEncoding = response.getField('Transfer-Encoding');
+    if(contentLength !== null) {
+      contentLength = parseInt(contentLength);
+    }
+
+    // read specified length
+    if(contentLength !== null && contentLength >= 0) {
+      response.body = response.body || '';
+      response.body += b.getBytes(contentLength);
+      response.bodyReceived = (response.body.length === contentLength);
+    } else if(transferEncoding !== null) {
+      // read chunked encoding
+      if(transferEncoding.indexOf('chunked') != -1) {
+        response.body = response.body || '';
+        _readChunkedBody(b);
+      } else {
+        var error = new Error('Unknown Transfer-Encoding.');
+        error.details = {'transferEncoding': transferEncoding};
+        throw error;
+      }
+    } else if((contentLength !== null && contentLength < 0) ||
+      (contentLength === null &&
+      response.getField('Content-Type') !== null)) {
+      // read all data in the buffer
+      response.body = response.body || '';
+      response.body += b.getBytes();
+      response.readBodyUntilClose = true;
+    } else {
+      // no body
+      response.body = null;
+      response.bodyReceived = true;
+    }
+
+    if(response.bodyReceived) {
+      response.time = +new Date() - response.time;
+    }
+
+    if(response.flashApi !== null &&
+      response.bodyReceived && response.body !== null &&
+      response.getField('Content-Encoding') === 'deflate') {
+      // inflate using flash api
+      response.body = forge.util.inflate(
+        response.flashApi, response.body);
+    }
+
+    return response.bodyReceived;
+  };
+
+   /**
+    * Parses an array of cookies from the 'Set-Cookie' field, if present.
+    *
+    * @return the array of cookies.
+    */
+   response.getCookies = function() {
+     var rval = [];
+
+     // get Set-Cookie field
+     if('Set-Cookie' in response.fields) {
+       var field = response.fields['Set-Cookie'];
+
+       // get current local time in seconds
+       var now = +new Date() / 1000;
+
+       // regex for parsing 'name1=value1; name2=value2; name3'
+       var regex = /\s*([^=]*)=?([^;]*)(;|$)/g;
+
+       // examples:
+       // Set-Cookie: cookie1_name=cookie1_value; max-age=0; path=/
+       // Set-Cookie: c2=v2; expires=Thu, 21-Aug-2008 23:47:25 GMT; path=/
+       for(var i = 0; i < field.length; ++i) {
+         var fv = field[i];
+         var m;
+         regex.lastIndex = 0;
+         var first = true;
+         var cookie = {};
+         do {
+           m = regex.exec(fv);
+           if(m !== null) {
+             var name = _trimString(m[1]);
+             var value = _trimString(m[2]);
+
+             // cookie_name=value
+             if(first) {
+               cookie.name = name;
+               cookie.value = value;
+               first = false;
+             } else {
+               // property_name=value
+               name = name.toLowerCase();
+               switch(name) {
+               case 'expires':
+                 // replace hyphens w/spaces so date will parse
+                 value = value.replace(/-/g, ' ');
+                 var secs = Date.parse(value) / 1000;
+                 cookie.maxAge = Math.max(0, secs - now);
+                 break;
+               case 'max-age':
+                 cookie.maxAge = parseInt(value, 10);
+                 break;
+               case 'secure':
+                 cookie.secure = true;
+                 break;
+               case 'httponly':
+                 cookie.httpOnly = true;
+                 break;
+               default:
+                 if(name !== '') {
+                   cookie[name] = value;
+                 }
+               }
+             }
+           }
+         } while(m !== null && m[0] !== '');
+         rval.push(cookie);
+       }
+     }
+
+     return rval;
+  };
+
+  /**
+   * Converts an http response into a string that can be sent as an
+   * HTTP response. Does not include any data.
+   *
+   * @return the string representation of the response.
+   */
+  response.toString = function() {
+    /* Sample response header:
+      HTTP/1.0 200 OK
+      Host: www.someurl.com
+      Connection: close
+     */
+
+    // build start line
+    var rval =
+      response.version + ' ' + response.code + ' ' + response.message + '\r\n';
+
+    // add each header
+    for(var name in response.fields) {
+      var fields = response.fields[name];
+      for(var i = 0; i < fields.length; ++i) {
+        rval += name + ': ' + fields[i] + '\r\n';
+      }
+    }
+    // final terminating CRLF
+    rval += '\r\n';
+
+    return rval;
+  };
+
+  return response;
+};
+
+/**
+ * Parses the scheme, host, and port from an http(s) url.
+ *
+ * @param str the url string.
+ *
+ * @return the parsed url object or null if the url is invalid.
+ */
+http.parseUrl = forge.util.parseUrl;
+
+/**
+ * Returns true if the given url is within the given cookie's domain.
+ *
+ * @param url the url to check.
+ * @param cookie the cookie or cookie domain to check.
+ */
+http.withinCookieDomain = function(url, cookie) {
+  var rval = false;
+
+  // cookie may be null, a cookie object, or a domain string
+  var domain = (cookie === null || typeof cookie === 'string') ?
+    cookie : cookie.domain;
+
+  // any domain will do
+  if(domain === null) {
+    rval = true;
+  } else if(domain.charAt(0) === '.') {
+    // ensure domain starts with a '.'
+    // parse URL as necessary
+    if(typeof url === 'string') {
+      url = http.parseUrl(url);
+    }
+
+    // add '.' to front of URL host to match against domain
+    var host = '.' + url.host;
+
+    // if the host ends with domain then it falls within it
+    var idx = host.lastIndexOf(domain);
+    if(idx !== -1 && (idx + domain.length === host.length)) {
+      rval = true;
+    }
+  }
+
+  return rval;
+};
+
+// public access to http namespace
+if(typeof forge === 'undefined') {
+  forge = {};
+}
+forge.http = http;
+
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/jsbn.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/jsbn.js
new file mode 100644
index 0000000..29a1509
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/jsbn.js
@@ -0,0 +1,1321 @@
+// Copyright (c) 2005  Tom Wu
+// All Rights Reserved.
+// See "LICENSE" for details.
+
+// Basic JavaScript BN library - subset useful for RSA encryption.
+
+/*
+Licensing (LICENSE)
+-------------------
+
+This software is covered under the following copyright:
+*/
+/*
+ * Copyright (c) 2003-2005  Tom Wu
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
+ * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
+ * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF
+ * THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * In addition, the following condition applies:
+ *
+ * All redistributions must retain an intact copy of this copyright notice
+ * and disclaimer.
+ */
+/*
+Address all questions regarding this license to:
+
+  Tom Wu
+  tjw@cs.Stanford.EDU
+*/
+
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+// Bits per digit
+var dbits;
+
+// JavaScript engine analysis
+var canary = 0xdeadbeefcafe;
+var j_lm = ((canary&0xffffff)==0xefcafe);
+
+// (public) Constructor
+function BigInteger(a,b,c) {
+  this.data = [];
+  if(a != null)
+    if("number" == typeof a) this.fromNumber(a,b,c);
+    else if(b == null && "string" != typeof a) this.fromString(a,256);
+    else this.fromString(a,b);
+}
+
+// return new, unset BigInteger
+function nbi() { return new BigInteger(null); }
+
+// am: Compute w_j += (x*this_i), propagate carries,
+// c is initial carry, returns final carry.
+// c < 3*dvalue, x < 2*dvalue, this_i < dvalue
+// We need to select the fastest one that works in this environment.
+
+// am1: use a single mult and divide to get the high bits,
+// max digit bits should be 26 because
+// max internal value = 2*dvalue^2-2*dvalue (< 2^53)
+function am1(i,x,w,j,c,n) {
+  while(--n >= 0) {
+    var v = x*this.data[i++]+w.data[j]+c;
+    c = Math.floor(v/0x4000000);
+    w.data[j++] = v&0x3ffffff;
+  }
+  return c;
+}
+// am2 avoids a big mult-and-extract completely.
+// Max digit bits should be <= 30 because we do bitwise ops
+// on values up to 2*hdvalue^2-hdvalue-1 (< 2^31)
+function am2(i,x,w,j,c,n) {
+  var xl = x&0x7fff, xh = x>>15;
+  while(--n >= 0) {
+    var l = this.data[i]&0x7fff;
+    var h = this.data[i++]>>15;
+    var m = xh*l+h*xl;
+    l = xl*l+((m&0x7fff)<<15)+w.data[j]+(c&0x3fffffff);
+    c = (l>>>30)+(m>>>15)+xh*h+(c>>>30);
+    w.data[j++] = l&0x3fffffff;
+  }
+  return c;
+}
+// Alternately, set max digit bits to 28 since some
+// browsers slow down when dealing with 32-bit numbers.
+function am3(i,x,w,j,c,n) {
+  var xl = x&0x3fff, xh = x>>14;
+  while(--n >= 0) {
+    var l = this.data[i]&0x3fff;
+    var h = this.data[i++]>>14;
+    var m = xh*l+h*xl;
+    l = xl*l+((m&0x3fff)<<14)+w.data[j]+c;
+    c = (l>>28)+(m>>14)+xh*h;
+    w.data[j++] = l&0xfffffff;
+  }
+  return c;
+}
+
+// node.js (no browser)
+if(typeof(navigator) === 'undefined')
+{
+   BigInteger.prototype.am = am3;
+   dbits = 28;
+} else if(j_lm && (navigator.appName == "Microsoft Internet Explorer")) {
+  BigInteger.prototype.am = am2;
+  dbits = 30;
+} else if(j_lm && (navigator.appName != "Netscape")) {
+  BigInteger.prototype.am = am1;
+  dbits = 26;
+} else { // Mozilla/Netscape seems to prefer am3
+  BigInteger.prototype.am = am3;
+  dbits = 28;
+}
+
+BigInteger.prototype.DB = dbits;
+BigInteger.prototype.DM = ((1<<dbits)-1);
+BigInteger.prototype.DV = (1<<dbits);
+
+var BI_FP = 52;
+BigInteger.prototype.FV = Math.pow(2,BI_FP);
+BigInteger.prototype.F1 = BI_FP-dbits;
+BigInteger.prototype.F2 = 2*dbits-BI_FP;
+
+// Digit conversions
+var BI_RM = "0123456789abcdefghijklmnopqrstuvwxyz";
+var BI_RC = new Array();
+var rr,vv;
+rr = "0".charCodeAt(0);
+for(vv = 0; vv <= 9; ++vv) BI_RC[rr++] = vv;
+rr = "a".charCodeAt(0);
+for(vv = 10; vv < 36; ++vv) BI_RC[rr++] = vv;
+rr = "A".charCodeAt(0);
+for(vv = 10; vv < 36; ++vv) BI_RC[rr++] = vv;
+
+function int2char(n) { return BI_RM.charAt(n); }
+function intAt(s,i) {
+  var c = BI_RC[s.charCodeAt(i)];
+  return (c==null)?-1:c;
+}
+
+// (protected) copy this to r
+function bnpCopyTo(r) {
+  for(var i = this.t-1; i >= 0; --i) r.data[i] = this.data[i];
+  r.t = this.t;
+  r.s = this.s;
+}
+
+// (protected) set from integer value x, -DV <= x < DV
+function bnpFromInt(x) {
+  this.t = 1;
+  this.s = (x<0)?-1:0;
+  if(x > 0) this.data[0] = x;
+  else if(x < -1) this.data[0] = x+this.DV;
+  else this.t = 0;
+}
+
+// return bigint initialized to value
+function nbv(i) { var r = nbi(); r.fromInt(i); return r; }
+
+// (protected) set from string and radix
+function bnpFromString(s,b) {
+  var k;
+  if(b == 16) k = 4;
+  else if(b == 8) k = 3;
+  else if(b == 256) k = 8; // byte array
+  else if(b == 2) k = 1;
+  else if(b == 32) k = 5;
+  else if(b == 4) k = 2;
+  else { this.fromRadix(s,b); return; }
+  this.t = 0;
+  this.s = 0;
+  var i = s.length, mi = false, sh = 0;
+  while(--i >= 0) {
+    var x = (k==8)?s[i]&0xff:intAt(s,i);
+    if(x < 0) {
+      if(s.charAt(i) == "-") mi = true;
+      continue;
+    }
+    mi = false;
+    if(sh == 0)
+      this.data[this.t++] = x;
+    else if(sh+k > this.DB) {
+      this.data[this.t-1] |= (x&((1<<(this.DB-sh))-1))<<sh;
+      this.data[this.t++] = (x>>(this.DB-sh));
+    } else
+      this.data[this.t-1] |= x<<sh;
+    sh += k;
+    if(sh >= this.DB) sh -= this.DB;
+  }
+  if(k == 8 && (s[0]&0x80) != 0) {
+    this.s = -1;
+    if(sh > 0) this.data[this.t-1] |= ((1<<(this.DB-sh))-1)<<sh;
+  }
+  this.clamp();
+  if(mi) BigInteger.ZERO.subTo(this,this);
+}
+
+// (protected) clamp off excess high words
+function bnpClamp() {
+  var c = this.s&this.DM;
+  while(this.t > 0 && this.data[this.t-1] == c) --this.t;
+}
+
+// (public) return string representation in given radix
+function bnToString(b) {
+  if(this.s < 0) return "-"+this.negate().toString(b);
+  var k;
+  if(b == 16) k = 4;
+  else if(b == 8) k = 3;
+  else if(b == 2) k = 1;
+  else if(b == 32) k = 5;
+  else if(b == 4) k = 2;
+  else return this.toRadix(b);
+  var km = (1<<k)-1, d, m = false, r = "", i = this.t;
+  var p = this.DB-(i*this.DB)%k;
+  if(i-- > 0) {
+    if(p < this.DB && (d = this.data[i]>>p) > 0) { m = true; r = int2char(d); }
+    while(i >= 0) {
+      if(p < k) {
+        d = (this.data[i]&((1<<p)-1))<<(k-p);
+        d |= this.data[--i]>>(p+=this.DB-k);
+      } else {
+        d = (this.data[i]>>(p-=k))&km;
+        if(p <= 0) { p += this.DB; --i; }
+      }
+      if(d > 0) m = true;
+      if(m) r += int2char(d);
+    }
+  }
+  return m?r:"0";
+}
+
+// (public) -this
+function bnNegate() { var r = nbi(); BigInteger.ZERO.subTo(this,r); return r; }
+
+// (public) |this|
+function bnAbs() { return (this.s<0)?this.negate():this; }
+
+// (public) return + if this > a, - if this < a, 0 if equal
+function bnCompareTo(a) {
+  var r = this.s-a.s;
+  if(r != 0) return r;
+  var i = this.t;
+  r = i-a.t;
+  if(r != 0) return (this.s<0)?-r:r;
+  while(--i >= 0) if((r=this.data[i]-a.data[i]) != 0) return r;
+  return 0;
+}
+
+// returns bit length of the integer x
+function nbits(x) {
+  var r = 1, t;
+  if((t=x>>>16) != 0) { x = t; r += 16; }
+  if((t=x>>8) != 0) { x = t; r += 8; }
+  if((t=x>>4) != 0) { x = t; r += 4; }
+  if((t=x>>2) != 0) { x = t; r += 2; }
+  if((t=x>>1) != 0) { x = t; r += 1; }
+  return r;
+}
+
+// (public) return the number of bits in "this"
+function bnBitLength() {
+  if(this.t <= 0) return 0;
+  return this.DB*(this.t-1)+nbits(this.data[this.t-1]^(this.s&this.DM));
+}
+
+// (protected) r = this << n*DB
+function bnpDLShiftTo(n,r) {
+  var i;
+  for(i = this.t-1; i >= 0; --i) r.data[i+n] = this.data[i];
+  for(i = n-1; i >= 0; --i) r.data[i] = 0;
+  r.t = this.t+n;
+  r.s = this.s;
+}
+
+// (protected) r = this >> n*DB
+function bnpDRShiftTo(n,r) {
+  for(var i = n; i < this.t; ++i) r.data[i-n] = this.data[i];
+  r.t = Math.max(this.t-n,0);
+  r.s = this.s;
+}
+
+// (protected) r = this << n
+function bnpLShiftTo(n,r) {
+  var bs = n%this.DB;
+  var cbs = this.DB-bs;
+  var bm = (1<<cbs)-1;
+  var ds = Math.floor(n/this.DB), c = (this.s<<bs)&this.DM, i;
+  for(i = this.t-1; i >= 0; --i) {
+    r.data[i+ds+1] = (this.data[i]>>cbs)|c;
+    c = (this.data[i]&bm)<<bs;
+  }
+  for(i = ds-1; i >= 0; --i) r.data[i] = 0;
+  r.data[ds] = c;
+  r.t = this.t+ds+1;
+  r.s = this.s;
+  r.clamp();
+}
+
+// (protected) r = this >> n
+function bnpRShiftTo(n,r) {
+  r.s = this.s;
+  var ds = Math.floor(n/this.DB);
+  if(ds >= this.t) { r.t = 0; return; }
+  var bs = n%this.DB;
+  var cbs = this.DB-bs;
+  var bm = (1<<bs)-1;
+  r.data[0] = this.data[ds]>>bs;
+  for(var i = ds+1; i < this.t; ++i) {
+    r.data[i-ds-1] |= (this.data[i]&bm)<<cbs;
+    r.data[i-ds] = this.data[i]>>bs;
+  }
+  if(bs > 0) r.data[this.t-ds-1] |= (this.s&bm)<<cbs;
+  r.t = this.t-ds;
+  r.clamp();
+}
+
+// (protected) r = this - a
+function bnpSubTo(a,r) {
+  var i = 0, c = 0, m = Math.min(a.t,this.t);
+  while(i < m) {
+    c += this.data[i]-a.data[i];
+    r.data[i++] = c&this.DM;
+    c >>= this.DB;
+  }
+  if(a.t < this.t) {
+    c -= a.s;
+    while(i < this.t) {
+      c += this.data[i];
+      r.data[i++] = c&this.DM;
+      c >>= this.DB;
+    }
+    c += this.s;
+  } else {
+    c += this.s;
+    while(i < a.t) {
+      c -= a.data[i];
+      r.data[i++] = c&this.DM;
+      c >>= this.DB;
+    }
+    c -= a.s;
+  }
+  r.s = (c<0)?-1:0;
+  if(c < -1) r.data[i++] = this.DV+c;
+  else if(c > 0) r.data[i++] = c;
+  r.t = i;
+  r.clamp();
+}
+
+// (protected) r = this * a, r != this,a (HAC 14.12)
+// "this" should be the larger one if appropriate.
+function bnpMultiplyTo(a,r) {
+  var x = this.abs(), y = a.abs();
+  var i = x.t;
+  r.t = i+y.t;
+  while(--i >= 0) r.data[i] = 0;
+  for(i = 0; i < y.t; ++i) r.data[i+x.t] = x.am(0,y.data[i],r,i,0,x.t);
+  r.s = 0;
+  r.clamp();
+  if(this.s != a.s) BigInteger.ZERO.subTo(r,r);
+}
+
+// (protected) r = this^2, r != this (HAC 14.16)
+function bnpSquareTo(r) {
+  var x = this.abs();
+  var i = r.t = 2*x.t;
+  while(--i >= 0) r.data[i] = 0;
+  for(i = 0; i < x.t-1; ++i) {
+    var c = x.am(i,x.data[i],r,2*i,0,1);
+    if((r.data[i+x.t]+=x.am(i+1,2*x.data[i],r,2*i+1,c,x.t-i-1)) >= x.DV) {
+      r.data[i+x.t] -= x.DV;
+      r.data[i+x.t+1] = 1;
+    }
+  }
+  if(r.t > 0) r.data[r.t-1] += x.am(i,x.data[i],r,2*i,0,1);
+  r.s = 0;
+  r.clamp();
+}
+
+// (protected) divide this by m, quotient and remainder to q, r (HAC 14.20)
+// r != q, this != m.  q or r may be null.
+function bnpDivRemTo(m,q,r) {
+  var pm = m.abs();
+  if(pm.t <= 0) return;
+  var pt = this.abs();
+  if(pt.t < pm.t) {
+    if(q != null) q.fromInt(0);
+    if(r != null) this.copyTo(r);
+    return;
+  }
+  if(r == null) r = nbi();
+  var y = nbi(), ts = this.s, ms = m.s;
+  var nsh = this.DB-nbits(pm.data[pm.t-1]);	// normalize modulus
+  if(nsh > 0) { pm.lShiftTo(nsh,y); pt.lShiftTo(nsh,r); } else { pm.copyTo(y); pt.copyTo(r); }
+  var ys = y.t;
+  var y0 = y.data[ys-1];
+  if(y0 == 0) return;
+  var yt = y0*(1<<this.F1)+((ys>1)?y.data[ys-2]>>this.F2:0);
+  var d1 = this.FV/yt, d2 = (1<<this.F1)/yt, e = 1<<this.F2;
+  var i = r.t, j = i-ys, t = (q==null)?nbi():q;
+  y.dlShiftTo(j,t);
+  if(r.compareTo(t) >= 0) {
+    r.data[r.t++] = 1;
+    r.subTo(t,r);
+  }
+  BigInteger.ONE.dlShiftTo(ys,t);
+  t.subTo(y,y);	// "negative" y so we can replace sub with am later
+  while(y.t < ys) y.data[y.t++] = 0;
+  while(--j >= 0) {
+    // Estimate quotient digit
+    var qd = (r.data[--i]==y0)?this.DM:Math.floor(r.data[i]*d1+(r.data[i-1]+e)*d2);
+    if((r.data[i]+=y.am(0,qd,r,j,0,ys)) < qd) {	// Try it out
+      y.dlShiftTo(j,t);
+      r.subTo(t,r);
+      while(r.data[i] < --qd) r.subTo(t,r);
+    }
+  }
+  if(q != null) {
+    r.drShiftTo(ys,q);
+    if(ts != ms) BigInteger.ZERO.subTo(q,q);
+  }
+  r.t = ys;
+  r.clamp();
+  if(nsh > 0) r.rShiftTo(nsh,r);	// Denormalize remainder
+  if(ts < 0) BigInteger.ZERO.subTo(r,r);
+}
+
+// (public) this mod a
+function bnMod(a) {
+  var r = nbi();
+  this.abs().divRemTo(a,null,r);
+  if(this.s < 0 && r.compareTo(BigInteger.ZERO) > 0) a.subTo(r,r);
+  return r;
+}
+
+// Modular reduction using "classic" algorithm
+function Classic(m) { this.m = m; }
+function cConvert(x) {
+  if(x.s < 0 || x.compareTo(this.m) >= 0) return x.mod(this.m);
+  else return x;
+}
+function cRevert(x) { return x; }
+function cReduce(x) { x.divRemTo(this.m,null,x); }
+function cMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); }
+function cSqrTo(x,r) { x.squareTo(r); this.reduce(r); }
+
+Classic.prototype.convert = cConvert;
+Classic.prototype.revert = cRevert;
+Classic.prototype.reduce = cReduce;
+Classic.prototype.mulTo = cMulTo;
+Classic.prototype.sqrTo = cSqrTo;
+
+// (protected) return "-1/this % 2^DB"; useful for Mont. reduction
+// justification:
+//         xy == 1 (mod m)
+//         xy =  1+km
+//   xy(2-xy) = (1+km)(1-km)
+// x[y(2-xy)] = 1-k^2m^2
+// x[y(2-xy)] == 1 (mod m^2)
+// if y is 1/x mod m, then y(2-xy) is 1/x mod m^2
+// should reduce x and y(2-xy) by m^2 at each step to keep size bounded.
+// JS multiply "overflows" differently from C/C++, so care is needed here.
+function bnpInvDigit() {
+  if(this.t < 1) return 0;
+  var x = this.data[0];
+  if((x&1) == 0) return 0;
+  var y = x&3;		// y == 1/x mod 2^2
+  y = (y*(2-(x&0xf)*y))&0xf;	// y == 1/x mod 2^4
+  y = (y*(2-(x&0xff)*y))&0xff;	// y == 1/x mod 2^8
+  y = (y*(2-(((x&0xffff)*y)&0xffff)))&0xffff;	// y == 1/x mod 2^16
+  // last step - calculate inverse mod DV directly;
+  // assumes 16 < DB <= 32 and assumes ability to handle 48-bit ints
+  y = (y*(2-x*y%this.DV))%this.DV;		// y == 1/x mod 2^dbits
+  // we really want the negative inverse, and -DV < y < DV
+  return (y>0)?this.DV-y:-y;
+}
+
+// Montgomery reduction
+function Montgomery(m) {
+  this.m = m;
+  this.mp = m.invDigit();
+  this.mpl = this.mp&0x7fff;
+  this.mph = this.mp>>15;
+  this.um = (1<<(m.DB-15))-1;
+  this.mt2 = 2*m.t;
+}
+
+// xR mod m
+function montConvert(x) {
+  var r = nbi();
+  x.abs().dlShiftTo(this.m.t,r);
+  r.divRemTo(this.m,null,r);
+  if(x.s < 0 && r.compareTo(BigInteger.ZERO) > 0) this.m.subTo(r,r);
+  return r;
+}
+
+// x/R mod m
+function montRevert(x) {
+  var r = nbi();
+  x.copyTo(r);
+  this.reduce(r);
+  return r;
+}
+
+// x = x/R mod m (HAC 14.32)
+function montReduce(x) {
+  while(x.t <= this.mt2)	// pad x so am has enough room later
+    x.data[x.t++] = 0;
+  for(var i = 0; i < this.m.t; ++i) {
+    // faster way of calculating u0 = x.data[i]*mp mod DV
+    var j = x.data[i]&0x7fff;
+    var u0 = (j*this.mpl+(((j*this.mph+(x.data[i]>>15)*this.mpl)&this.um)<<15))&x.DM;
+    // use am to combine the multiply-shift-add into one call
+    j = i+this.m.t;
+    x.data[j] += this.m.am(0,u0,x,i,0,this.m.t);
+    // propagate carry
+    while(x.data[j] >= x.DV) { x.data[j] -= x.DV; x.data[++j]++; }
+  }
+  x.clamp();
+  x.drShiftTo(this.m.t,x);
+  if(x.compareTo(this.m) >= 0) x.subTo(this.m,x);
+}
+
+// r = "x^2/R mod m"; x != r
+function montSqrTo(x,r) { x.squareTo(r); this.reduce(r); }
+
+// r = "xy/R mod m"; x,y != r
+function montMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); }
+
+Montgomery.prototype.convert = montConvert;
+Montgomery.prototype.revert = montRevert;
+Montgomery.prototype.reduce = montReduce;
+Montgomery.prototype.mulTo = montMulTo;
+Montgomery.prototype.sqrTo = montSqrTo;
+
+// (protected) true iff this is even
+function bnpIsEven() { return ((this.t>0)?(this.data[0]&1):this.s) == 0; }
+
+// (protected) this^e, e < 2^32, doing sqr and mul with "r" (HAC 14.79)
+function bnpExp(e,z) {
+  if(e > 0xffffffff || e < 1) return BigInteger.ONE;
+  var r = nbi(), r2 = nbi(), g = z.convert(this), i = nbits(e)-1;
+  g.copyTo(r);
+  while(--i >= 0) {
+    z.sqrTo(r,r2);
+    if((e&(1<<i)) > 0) z.mulTo(r2,g,r);
+    else { var t = r; r = r2; r2 = t; }
+  }
+  return z.revert(r);
+}
+
+// (public) this^e % m, 0 <= e < 2^32
+function bnModPowInt(e,m) {
+  var z;
+  if(e < 256 || m.isEven()) z = new Classic(m); else z = new Montgomery(m);
+  return this.exp(e,z);
+}
+
+// protected
+BigInteger.prototype.copyTo = bnpCopyTo;
+BigInteger.prototype.fromInt = bnpFromInt;
+BigInteger.prototype.fromString = bnpFromString;
+BigInteger.prototype.clamp = bnpClamp;
+BigInteger.prototype.dlShiftTo = bnpDLShiftTo;
+BigInteger.prototype.drShiftTo = bnpDRShiftTo;
+BigInteger.prototype.lShiftTo = bnpLShiftTo;
+BigInteger.prototype.rShiftTo = bnpRShiftTo;
+BigInteger.prototype.subTo = bnpSubTo;
+BigInteger.prototype.multiplyTo = bnpMultiplyTo;
+BigInteger.prototype.squareTo = bnpSquareTo;
+BigInteger.prototype.divRemTo = bnpDivRemTo;
+BigInteger.prototype.invDigit = bnpInvDigit;
+BigInteger.prototype.isEven = bnpIsEven;
+BigInteger.prototype.exp = bnpExp;
+
+// public
+BigInteger.prototype.toString = bnToString;
+BigInteger.prototype.negate = bnNegate;
+BigInteger.prototype.abs = bnAbs;
+BigInteger.prototype.compareTo = bnCompareTo;
+BigInteger.prototype.bitLength = bnBitLength;
+BigInteger.prototype.mod = bnMod;
+BigInteger.prototype.modPowInt = bnModPowInt;
+
+// "constants"
+BigInteger.ZERO = nbv(0);
+BigInteger.ONE = nbv(1);
+
+// jsbn2 lib
+
+//Copyright (c) 2005-2009  Tom Wu
+//All Rights Reserved.
+//See "LICENSE" for details (See jsbn.js for LICENSE).
+
+//Extended JavaScript BN functions, required for RSA private ops.
+
+//Version 1.1: new BigInteger("0", 10) returns "proper" zero
+
+//(public)
+function bnClone() { var r = nbi(); this.copyTo(r); return r; }
+
+//(public) return value as integer
+function bnIntValue() {
+if(this.s < 0) {
+ if(this.t == 1) return this.data[0]-this.DV;
+ else if(this.t == 0) return -1;
+} else if(this.t == 1) return this.data[0];
+else if(this.t == 0) return 0;
+// assumes 16 < DB < 32
+return ((this.data[1]&((1<<(32-this.DB))-1))<<this.DB)|this.data[0];
+}
+
+//(public) return value as byte
+function bnByteValue() { return (this.t==0)?this.s:(this.data[0]<<24)>>24; }
+
+//(public) return value as short (assumes DB>=16)
+function bnShortValue() { return (this.t==0)?this.s:(this.data[0]<<16)>>16; }
+
+//(protected) return x s.t. r^x < DV
+function bnpChunkSize(r) { return Math.floor(Math.LN2*this.DB/Math.log(r)); }
+
+//(public) 0 if this == 0, 1 if this > 0
+function bnSigNum() {
+if(this.s < 0) return -1;
+else if(this.t <= 0 || (this.t == 1 && this.data[0] <= 0)) return 0;
+else return 1;
+}
+
+//(protected) convert to radix string
+function bnpToRadix(b) {
+if(b == null) b = 10;
+if(this.signum() == 0 || b < 2 || b > 36) return "0";
+var cs = this.chunkSize(b);
+var a = Math.pow(b,cs);
+var d = nbv(a), y = nbi(), z = nbi(), r = "";
+this.divRemTo(d,y,z);
+while(y.signum() > 0) {
+ r = (a+z.intValue()).toString(b).substr(1) + r;
+ y.divRemTo(d,y,z);
+}
+return z.intValue().toString(b) + r;
+}
+
+//(protected) convert from radix string
+function bnpFromRadix(s,b) {
+this.fromInt(0);
+if(b == null) b = 10;
+var cs = this.chunkSize(b);
+var d = Math.pow(b,cs), mi = false, j = 0, w = 0;
+for(var i = 0; i < s.length; ++i) {
+ var x = intAt(s,i);
+ if(x < 0) {
+   if(s.charAt(i) == "-" && this.signum() == 0) mi = true;
+   continue;
+ }
+ w = b*w+x;
+ if(++j >= cs) {
+   this.dMultiply(d);
+   this.dAddOffset(w,0);
+   j = 0;
+   w = 0;
+ }
+}
+if(j > 0) {
+ this.dMultiply(Math.pow(b,j));
+ this.dAddOffset(w,0);
+}
+if(mi) BigInteger.ZERO.subTo(this,this);
+}
+
+//(protected) alternate constructor
+function bnpFromNumber(a,b,c) {
+if("number" == typeof b) {
+ // new BigInteger(int,int,RNG)
+ if(a < 2) this.fromInt(1);
+ else {
+   this.fromNumber(a,c);
+   if(!this.testBit(a-1))  // force MSB set
+     this.bitwiseTo(BigInteger.ONE.shiftLeft(a-1),op_or,this);
+   if(this.isEven()) this.dAddOffset(1,0); // force odd
+   while(!this.isProbablePrime(b)) {
+     this.dAddOffset(2,0);
+     if(this.bitLength() > a) this.subTo(BigInteger.ONE.shiftLeft(a-1),this);
+   }
+ }
+} else {
+ // new BigInteger(int,RNG)
+ var x = new Array(), t = a&7;
+ x.length = (a>>3)+1;
+ b.nextBytes(x);
+ if(t > 0) x[0] &= ((1<<t)-1); else x[0] = 0;
+ this.fromString(x,256);
+}
+}
+
+//(public) convert to bigendian byte array
+function bnToByteArray() {
+var i = this.t, r = new Array();
+r[0] = this.s;
+var p = this.DB-(i*this.DB)%8, d, k = 0;
+if(i-- > 0) {
+ if(p < this.DB && (d = this.data[i]>>p) != (this.s&this.DM)>>p)
+   r[k++] = d|(this.s<<(this.DB-p));
+ while(i >= 0) {
+   if(p < 8) {
+     d = (this.data[i]&((1<<p)-1))<<(8-p);
+     d |= this.data[--i]>>(p+=this.DB-8);
+   } else {
+     d = (this.data[i]>>(p-=8))&0xff;
+     if(p <= 0) { p += this.DB; --i; }
+   }
+   if((d&0x80) != 0) d |= -256;
+   if(k == 0 && (this.s&0x80) != (d&0x80)) ++k;
+   if(k > 0 || d != this.s) r[k++] = d;
+ }
+}
+return r;
+}
+
+function bnEquals(a) { return(this.compareTo(a)==0); }
+function bnMin(a) { return(this.compareTo(a)<0)?this:a; }
+function bnMax(a) { return(this.compareTo(a)>0)?this:a; }
+
+//(protected) r = this op a (bitwise)
+function bnpBitwiseTo(a,op,r) {
+var i, f, m = Math.min(a.t,this.t);
+for(i = 0; i < m; ++i) r.data[i] = op(this.data[i],a.data[i]);
+if(a.t < this.t) {
+ f = a.s&this.DM;
+ for(i = m; i < this.t; ++i) r.data[i] = op(this.data[i],f);
+ r.t = this.t;
+} else {
+ f = this.s&this.DM;
+ for(i = m; i < a.t; ++i) r.data[i] = op(f,a.data[i]);
+ r.t = a.t;
+}
+r.s = op(this.s,a.s);
+r.clamp();
+}
+
+//(public) this & a
+function op_and(x,y) { return x&y; }
+function bnAnd(a) { var r = nbi(); this.bitwiseTo(a,op_and,r); return r; }
+
+//(public) this | a
+function op_or(x,y) { return x|y; }
+function bnOr(a) { var r = nbi(); this.bitwiseTo(a,op_or,r); return r; }
+
+//(public) this ^ a
+function op_xor(x,y) { return x^y; }
+function bnXor(a) { var r = nbi(); this.bitwiseTo(a,op_xor,r); return r; }
+
+//(public) this & ~a
+function op_andnot(x,y) { return x&~y; }
+function bnAndNot(a) { var r = nbi(); this.bitwiseTo(a,op_andnot,r); return r; }
+
+//(public) ~this
+function bnNot() {
+var r = nbi();
+for(var i = 0; i < this.t; ++i) r.data[i] = this.DM&~this.data[i];
+r.t = this.t;
+r.s = ~this.s;
+return r;
+}
+
+//(public) this << n
+function bnShiftLeft(n) {
+var r = nbi();
+if(n < 0) this.rShiftTo(-n,r); else this.lShiftTo(n,r);
+return r;
+}
+
+//(public) this >> n
+function bnShiftRight(n) {
+var r = nbi();
+if(n < 0) this.lShiftTo(-n,r); else this.rShiftTo(n,r);
+return r;
+}
+
+//return index of lowest 1-bit in x, x < 2^31
+function lbit(x) {
+if(x == 0) return -1;
+var r = 0;
+if((x&0xffff) == 0) { x >>= 16; r += 16; }
+if((x&0xff) == 0) { x >>= 8; r += 8; }
+if((x&0xf) == 0) { x >>= 4; r += 4; }
+if((x&3) == 0) { x >>= 2; r += 2; }
+if((x&1) == 0) ++r;
+return r;
+}
+
+//(public) returns index of lowest 1-bit (or -1 if none)
+function bnGetLowestSetBit() {
+for(var i = 0; i < this.t; ++i)
+ if(this.data[i] != 0) return i*this.DB+lbit(this.data[i]);
+if(this.s < 0) return this.t*this.DB;
+return -1;
+}
+
+//return number of 1 bits in x
+function cbit(x) {
+var r = 0;
+while(x != 0) { x &= x-1; ++r; }
+return r;
+}
+
+//(public) return number of set bits
+function bnBitCount() {
+var r = 0, x = this.s&this.DM;
+for(var i = 0; i < this.t; ++i) r += cbit(this.data[i]^x);
+return r;
+}
+
+//(public) true iff nth bit is set
+function bnTestBit(n) {
+var j = Math.floor(n/this.DB);
+if(j >= this.t) return(this.s!=0);
+return((this.data[j]&(1<<(n%this.DB)))!=0);
+}
+
+//(protected) this op (1<<n)
+function bnpChangeBit(n,op) {
+var r = BigInteger.ONE.shiftLeft(n);
+this.bitwiseTo(r,op,r);
+return r;
+}
+
+//(public) this | (1<<n)
+function bnSetBit(n) { return this.changeBit(n,op_or); }
+
+//(public) this & ~(1<<n)
+function bnClearBit(n) { return this.changeBit(n,op_andnot); }
+
+//(public) this ^ (1<<n)
+function bnFlipBit(n) { return this.changeBit(n,op_xor); }
+
+//(protected) r = this + a
+function bnpAddTo(a,r) {
+var i = 0, c = 0, m = Math.min(a.t,this.t);
+while(i < m) {
+ c += this.data[i]+a.data[i];
+ r.data[i++] = c&this.DM;
+ c >>= this.DB;
+}
+if(a.t < this.t) {
+ c += a.s;
+ while(i < this.t) {
+   c += this.data[i];
+   r.data[i++] = c&this.DM;
+   c >>= this.DB;
+ }
+ c += this.s;
+} else {
+ c += this.s;
+ while(i < a.t) {
+   c += a.data[i];
+   r.data[i++] = c&this.DM;
+   c >>= this.DB;
+ }
+ c += a.s;
+}
+r.s = (c<0)?-1:0;
+if(c > 0) r.data[i++] = c;
+else if(c < -1) r.data[i++] = this.DV+c;
+r.t = i;
+r.clamp();
+}
+
+//(public) this + a
+function bnAdd(a) { var r = nbi(); this.addTo(a,r); return r; }
+
+//(public) this - a
+function bnSubtract(a) { var r = nbi(); this.subTo(a,r); return r; }
+
+//(public) this * a
+function bnMultiply(a) { var r = nbi(); this.multiplyTo(a,r); return r; }
+
+//(public) this / a
+function bnDivide(a) { var r = nbi(); this.divRemTo(a,r,null); return r; }
+
+//(public) this % a
+function bnRemainder(a) { var r = nbi(); this.divRemTo(a,null,r); return r; }
+
+//(public) [this/a,this%a]
+function bnDivideAndRemainder(a) {
+var q = nbi(), r = nbi();
+this.divRemTo(a,q,r);
+return new Array(q,r);
+}
+
+//(protected) this *= n, this >= 0, 1 < n < DV
+function bnpDMultiply(n) {
+this.data[this.t] = this.am(0,n-1,this,0,0,this.t);
+++this.t;
+this.clamp();
+}
+
+//(protected) this += n << w words, this >= 0
+function bnpDAddOffset(n,w) {
+if(n == 0) return;
+while(this.t <= w) this.data[this.t++] = 0;
+this.data[w] += n;
+while(this.data[w] >= this.DV) {
+ this.data[w] -= this.DV;
+ if(++w >= this.t) this.data[this.t++] = 0;
+ ++this.data[w];
+}
+}
+
+//A "null" reducer
+function NullExp() {}
+function nNop(x) { return x; }
+function nMulTo(x,y,r) { x.multiplyTo(y,r); }
+function nSqrTo(x,r) { x.squareTo(r); }
+
+NullExp.prototype.convert = nNop;
+NullExp.prototype.revert = nNop;
+NullExp.prototype.mulTo = nMulTo;
+NullExp.prototype.sqrTo = nSqrTo;
+
+//(public) this^e
+function bnPow(e) { return this.exp(e,new NullExp()); }
+
+//(protected) r = lower n words of "this * a", a.t <= n
+//"this" should be the larger one if appropriate.
+function bnpMultiplyLowerTo(a,n,r) {
+var i = Math.min(this.t+a.t,n);
+r.s = 0; // assumes a,this >= 0
+r.t = i;
+while(i > 0) r.data[--i] = 0;
+var j;
+for(j = r.t-this.t; i < j; ++i) r.data[i+this.t] = this.am(0,a.data[i],r,i,0,this.t);
+for(j = Math.min(a.t,n); i < j; ++i) this.am(0,a.data[i],r,i,0,n-i);
+r.clamp();
+}
+
+//(protected) r = "this * a" without lower n words, n > 0
+//"this" should be the larger one if appropriate.
+function bnpMultiplyUpperTo(a,n,r) {
+--n;
+var i = r.t = this.t+a.t-n;
+r.s = 0; // assumes a,this >= 0
+while(--i >= 0) r.data[i] = 0;
+for(i = Math.max(n-this.t,0); i < a.t; ++i)
+ r.data[this.t+i-n] = this.am(n-i,a.data[i],r,0,0,this.t+i-n);
+r.clamp();
+r.drShiftTo(1,r);
+}
+
+//Barrett modular reduction
+function Barrett(m) {
+// setup Barrett
+this.r2 = nbi();
+this.q3 = nbi();
+BigInteger.ONE.dlShiftTo(2*m.t,this.r2);
+this.mu = this.r2.divide(m);
+this.m = m;
+}
+
+function barrettConvert(x) {
+if(x.s < 0 || x.t > 2*this.m.t) return x.mod(this.m);
+else if(x.compareTo(this.m) < 0) return x;
+else { var r = nbi(); x.copyTo(r); this.reduce(r); return r; }
+}
+
+function barrettRevert(x) { return x; }
+
+//x = x mod m (HAC 14.42)
+function barrettReduce(x) {
+x.drShiftTo(this.m.t-1,this.r2);
+if(x.t > this.m.t+1) { x.t = this.m.t+1; x.clamp(); }
+this.mu.multiplyUpperTo(this.r2,this.m.t+1,this.q3);
+this.m.multiplyLowerTo(this.q3,this.m.t+1,this.r2);
+while(x.compareTo(this.r2) < 0) x.dAddOffset(1,this.m.t+1);
+x.subTo(this.r2,x);
+while(x.compareTo(this.m) >= 0) x.subTo(this.m,x);
+}
+
+//r = x^2 mod m; x != r
+function barrettSqrTo(x,r) { x.squareTo(r); this.reduce(r); }
+
+//r = x*y mod m; x,y != r
+function barrettMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); }
+
+Barrett.prototype.convert = barrettConvert;
+Barrett.prototype.revert = barrettRevert;
+Barrett.prototype.reduce = barrettReduce;
+Barrett.prototype.mulTo = barrettMulTo;
+Barrett.prototype.sqrTo = barrettSqrTo;
+
+//(public) this^e % m (HAC 14.85)
+function bnModPow(e,m) {
+var i = e.bitLength(), k, r = nbv(1), z;
+if(i <= 0) return r;
+else if(i < 18) k = 1;
+else if(i < 48) k = 3;
+else if(i < 144) k = 4;
+else if(i < 768) k = 5;
+else k = 6;
+if(i < 8)
+ z = new Classic(m);
+else if(m.isEven())
+ z = new Barrett(m);
+else
+ z = new Montgomery(m);
+
+// precomputation
+var g = new Array(), n = 3, k1 = k-1, km = (1<<k)-1;
+g[1] = z.convert(this);
+if(k > 1) {
+ var g2 = nbi();
+ z.sqrTo(g[1],g2);
+ while(n <= km) {
+   g[n] = nbi();
+   z.mulTo(g2,g[n-2],g[n]);
+   n += 2;
+ }
+}
+
+var j = e.t-1, w, is1 = true, r2 = nbi(), t;
+i = nbits(e.data[j])-1;
+while(j >= 0) {
+ if(i >= k1) w = (e.data[j]>>(i-k1))&km;
+ else {
+   w = (e.data[j]&((1<<(i+1))-1))<<(k1-i);
+   if(j > 0) w |= e.data[j-1]>>(this.DB+i-k1);
+ }
+
+ n = k;
+ while((w&1) == 0) { w >>= 1; --n; }
+ if((i -= n) < 0) { i += this.DB; --j; }
+ if(is1) {  // ret == 1, don't bother squaring or multiplying it
+   g[w].copyTo(r);
+   is1 = false;
+ } else {
+   while(n > 1) { z.sqrTo(r,r2); z.sqrTo(r2,r); n -= 2; }
+   if(n > 0) z.sqrTo(r,r2); else { t = r; r = r2; r2 = t; }
+   z.mulTo(r2,g[w],r);
+ }
+
+ while(j >= 0 && (e.data[j]&(1<<i)) == 0) {
+   z.sqrTo(r,r2); t = r; r = r2; r2 = t;
+   if(--i < 0) { i = this.DB-1; --j; }
+ }
+}
+return z.revert(r);
+}
+
+//(public) gcd(this,a) (HAC 14.54)
+function bnGCD(a) {
+var x = (this.s<0)?this.negate():this.clone();
+var y = (a.s<0)?a.negate():a.clone();
+if(x.compareTo(y) < 0) { var t = x; x = y; y = t; }
+var i = x.getLowestSetBit(), g = y.getLowestSetBit();
+if(g < 0) return x;
+if(i < g) g = i;
+if(g > 0) {
+ x.rShiftTo(g,x);
+ y.rShiftTo(g,y);
+}
+while(x.signum() > 0) {
+ if((i = x.getLowestSetBit()) > 0) x.rShiftTo(i,x);
+ if((i = y.getLowestSetBit()) > 0) y.rShiftTo(i,y);
+ if(x.compareTo(y) >= 0) {
+   x.subTo(y,x);
+   x.rShiftTo(1,x);
+ } else {
+   y.subTo(x,y);
+   y.rShiftTo(1,y);
+ }
+}
+if(g > 0) y.lShiftTo(g,y);
+return y;
+}
+
+//(protected) this % n, n < 2^26
+function bnpModInt(n) {
+if(n <= 0) return 0;
+var d = this.DV%n, r = (this.s<0)?n-1:0;
+if(this.t > 0)
+ if(d == 0) r = this.data[0]%n;
+ else for(var i = this.t-1; i >= 0; --i) r = (d*r+this.data[i])%n;
+return r;
+}
+
+//(public) 1/this % m (HAC 14.61)
+function bnModInverse(m) {
+var ac = m.isEven();
+if((this.isEven() && ac) || m.signum() == 0) return BigInteger.ZERO;
+var u = m.clone(), v = this.clone();
+var a = nbv(1), b = nbv(0), c = nbv(0), d = nbv(1);
+while(u.signum() != 0) {
+ while(u.isEven()) {
+   u.rShiftTo(1,u);
+   if(ac) {
+     if(!a.isEven() || !b.isEven()) { a.addTo(this,a); b.subTo(m,b); }
+     a.rShiftTo(1,a);
+   } else if(!b.isEven()) b.subTo(m,b);
+   b.rShiftTo(1,b);
+ }
+ while(v.isEven()) {
+   v.rShiftTo(1,v);
+   if(ac) {
+     if(!c.isEven() || !d.isEven()) { c.addTo(this,c); d.subTo(m,d); }
+     c.rShiftTo(1,c);
+   } else if(!d.isEven()) d.subTo(m,d);
+   d.rShiftTo(1,d);
+ }
+ if(u.compareTo(v) >= 0) {
+   u.subTo(v,u);
+   if(ac) a.subTo(c,a);
+   b.subTo(d,b);
+ } else {
+   v.subTo(u,v);
+   if(ac) c.subTo(a,c);
+   d.subTo(b,d);
+ }
+}
+if(v.compareTo(BigInteger.ONE) != 0) return BigInteger.ZERO;
+if(d.compareTo(m) >= 0) return d.subtract(m);
+if(d.signum() < 0) d.addTo(m,d); else return d;
+if(d.signum() < 0) return d.add(m); else return d;
+}
+
+var lowprimes = [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,503,509];
+var lplim = (1<<26)/lowprimes[lowprimes.length-1];
+
+//(public) test primality with certainty >= 1-.5^t
+function bnIsProbablePrime(t) {
+var i, x = this.abs();
+if(x.t == 1 && x.data[0] <= lowprimes[lowprimes.length-1]) {
+ for(i = 0; i < lowprimes.length; ++i)
+   if(x.data[0] == lowprimes[i]) return true;
+ return false;
+}
+if(x.isEven()) return false;
+i = 1;
+while(i < lowprimes.length) {
+ var m = lowprimes[i], j = i+1;
+ while(j < lowprimes.length && m < lplim) m *= lowprimes[j++];
+ m = x.modInt(m);
+ while(i < j) if(m%lowprimes[i++] == 0) return false;
+}
+return x.millerRabin(t);
+}
+
+//(protected) true if probably prime (HAC 4.24, Miller-Rabin)
+function bnpMillerRabin(t) {
+var n1 = this.subtract(BigInteger.ONE);
+var k = n1.getLowestSetBit();
+if(k <= 0) return false;
+var r = n1.shiftRight(k);
+var prng = bnGetPrng();
+var a;
+for(var i = 0; i < t; ++i) {
+ // select witness 'a' at random from between 1 and n1
+ do {
+   a = new BigInteger(this.bitLength(), prng);
+ }
+ while(a.compareTo(BigInteger.ONE) <= 0 || a.compareTo(n1) >= 0);
+ var y = a.modPow(r,this);
+ if(y.compareTo(BigInteger.ONE) != 0 && y.compareTo(n1) != 0) {
+   var j = 1;
+   while(j++ < k && y.compareTo(n1) != 0) {
+     y = y.modPowInt(2,this);
+     if(y.compareTo(BigInteger.ONE) == 0) return false;
+   }
+   if(y.compareTo(n1) != 0) return false;
+ }
+}
+return true;
+}
+
+// get pseudo random number generator
+function bnGetPrng() {
+  // create prng with api that matches BigInteger secure random
+  return {
+    // x is an array to fill with bytes
+    nextBytes: function(x) {
+      for(var i = 0; i < x.length; ++i) {
+        x[i] = Math.floor(Math.random() * 0xFF);
+      }
+    }
+  };
+}
+
+//protected
+BigInteger.prototype.chunkSize = bnpChunkSize;
+BigInteger.prototype.toRadix = bnpToRadix;
+BigInteger.prototype.fromRadix = bnpFromRadix;
+BigInteger.prototype.fromNumber = bnpFromNumber;
+BigInteger.prototype.bitwiseTo = bnpBitwiseTo;
+BigInteger.prototype.changeBit = bnpChangeBit;
+BigInteger.prototype.addTo = bnpAddTo;
+BigInteger.prototype.dMultiply = bnpDMultiply;
+BigInteger.prototype.dAddOffset = bnpDAddOffset;
+BigInteger.prototype.multiplyLowerTo = bnpMultiplyLowerTo;
+BigInteger.prototype.multiplyUpperTo = bnpMultiplyUpperTo;
+BigInteger.prototype.modInt = bnpModInt;
+BigInteger.prototype.millerRabin = bnpMillerRabin;
+
+//public
+BigInteger.prototype.clone = bnClone;
+BigInteger.prototype.intValue = bnIntValue;
+BigInteger.prototype.byteValue = bnByteValue;
+BigInteger.prototype.shortValue = bnShortValue;
+BigInteger.prototype.signum = bnSigNum;
+BigInteger.prototype.toByteArray = bnToByteArray;
+BigInteger.prototype.equals = bnEquals;
+BigInteger.prototype.min = bnMin;
+BigInteger.prototype.max = bnMax;
+BigInteger.prototype.and = bnAnd;
+BigInteger.prototype.or = bnOr;
+BigInteger.prototype.xor = bnXor;
+BigInteger.prototype.andNot = bnAndNot;
+BigInteger.prototype.not = bnNot;
+BigInteger.prototype.shiftLeft = bnShiftLeft;
+BigInteger.prototype.shiftRight = bnShiftRight;
+BigInteger.prototype.getLowestSetBit = bnGetLowestSetBit;
+BigInteger.prototype.bitCount = bnBitCount;
+BigInteger.prototype.testBit = bnTestBit;
+BigInteger.prototype.setBit = bnSetBit;
+BigInteger.prototype.clearBit = bnClearBit;
+BigInteger.prototype.flipBit = bnFlipBit;
+BigInteger.prototype.add = bnAdd;
+BigInteger.prototype.subtract = bnSubtract;
+BigInteger.prototype.multiply = bnMultiply;
+BigInteger.prototype.divide = bnDivide;
+BigInteger.prototype.remainder = bnRemainder;
+BigInteger.prototype.divideAndRemainder = bnDivideAndRemainder;
+BigInteger.prototype.modPow = bnModPow;
+BigInteger.prototype.modInverse = bnModInverse;
+BigInteger.prototype.pow = bnPow;
+BigInteger.prototype.gcd = bnGCD;
+BigInteger.prototype.isProbablePrime = bnIsProbablePrime;
+
+//BigInteger interfaces not implemented in jsbn:
+
+//BigInteger(int signum, byte[] magnitude)
+//double doubleValue()
+//float floatValue()
+//int hashCode()
+//long longValue()
+//static BigInteger valueOf(long val)
+
+forge.jsbn = forge.jsbn || {};
+forge.jsbn.BigInteger = BigInteger;
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'jsbn';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/kem.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/kem.js
new file mode 100644
index 0000000..7ac7851
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/kem.js
@@ -0,0 +1,221 @@
+/**
+ * Javascript implementation of RSA-KEM.
+ *
+ * @author Lautaro Cozzani Rodriguez
+ * @author Dave Longley
+ *
+ * Copyright (c) 2014 Lautaro Cozzani <lautaro.cozzani@scytl.com>
+ * Copyright (c) 2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+forge.kem = forge.kem || {};
+
+var BigInteger = forge.jsbn.BigInteger;
+
+/**
+ * The API for the RSA Key Encapsulation Mechanism (RSA-KEM) from ISO 18033-2.
+ */
+forge.kem.rsa = {};
+
+/**
+ * Creates an RSA KEM API object for generating a secret asymmetric key.
+ *
+ * The symmetric key may be generated via a call to 'encrypt', which will
+ * produce a ciphertext to be transmitted to the recipient and a key to be
+ * kept secret. The ciphertext is a parameter to be passed to 'decrypt' which
+ * will produce the same secret key for the recipient to use to decrypt a
+ * message that was encrypted with the secret key.
+ *
+ * @param kdf the KDF API to use (eg: new forge.kem.kdf1()).
+ * @param options the options to use.
+ *          [prng] a custom crypto-secure pseudo-random number generator to use,
+ *            that must define "getBytesSync".
+ */
+forge.kem.rsa.create = function(kdf, options) {
+  options = options || {};
+  var prng = options.prng || forge.random;
+
+  var kem = {};
+
+  /**
+   * Generates a secret key and its encapsulation.
+   *
+   * @param publicKey the RSA public key to encrypt with.
+   * @param keyLength the length, in bytes, of the secret key to generate.
+   *
+   * @return an object with:
+   *   encapsulation: the ciphertext for generating the secret key, as a
+   *     binary-encoded string of bytes.
+   *   key: the secret key to use for encrypting a message.
+   */
+  kem.encrypt = function(publicKey, keyLength) {
+    // generate a random r where 1 > r > n
+    var byteLength = Math.ceil(publicKey.n.bitLength() / 8);
+    var r;
+    do {
+      r = new BigInteger(
+        forge.util.bytesToHex(prng.getBytesSync(byteLength)),
+        16).mod(publicKey.n);
+    } while(r.equals(BigInteger.ZERO));
+
+    // prepend r with zeros
+    r = forge.util.hexToBytes(r.toString(16));
+    var zeros = byteLength - r.length;
+    if(zeros > 0) {
+      r = forge.util.fillString(String.fromCharCode(0), zeros) + r;
+    }
+
+    // encrypt the random
+    var encapsulation = publicKey.encrypt(r, 'NONE');
+
+    // generate the secret key
+    var key = kdf.generate(r, keyLength);
+
+    return {encapsulation: encapsulation, key: key};
+  };
+
+  /**
+   * Decrypts an encapsulated secret key.
+   *
+   * @param privateKey the RSA private key to decrypt with.
+   * @param encapsulation the ciphertext for generating the secret key, as
+   *          a binary-encoded string of bytes.
+   * @param keyLength the length, in bytes, of the secret key to generate.
+   *
+   * @return the secret key as a binary-encoded string of bytes.
+   */
+  kem.decrypt = function(privateKey, encapsulation, keyLength) {
+    // decrypt the encapsulation and generate the secret key
+    var r = privateKey.decrypt(encapsulation, 'NONE');
+    return kdf.generate(r, keyLength);
+  };
+
+  return kem;
+};
+
+// TODO: add forge.kem.kdf.create('KDF1', {md: ..., ...}) API?
+
+/**
+ * Creates a key derivation API object that implements KDF1 per ISO 18033-2.
+ *
+ * @param md the hash API to use.
+ * @param [digestLength] an optional digest length that must be positive and
+ *          less than or equal to md.digestLength.
+ *
+ * @return a KDF1 API object.
+ */
+forge.kem.kdf1 = function(md, digestLength) {
+  _createKDF(this, md, 0, digestLength || md.digestLength);
+};
+
+/**
+ * Creates a key derivation API object that implements KDF2 per ISO 18033-2.
+ *
+ * @param md the hash API to use.
+ * @param [digestLength] an optional digest length that must be positive and
+ *          less than or equal to md.digestLength.
+ *
+ * @return a KDF2 API object.
+ */
+forge.kem.kdf2 = function(md, digestLength) {
+  _createKDF(this, md, 1, digestLength || md.digestLength);
+};
+
+/**
+ * Creates a KDF1 or KDF2 API object.
+ *
+ * @param md the hash API to use.
+ * @param counterStart the starting index for the counter.
+ * @param digestLength the digest length to use.
+ *
+ * @return the KDF API object.
+ */
+function _createKDF(kdf, md, counterStart, digestLength) {
+  /**
+   * Generate a key of the specified length.
+   *
+   * @param x the binary-encoded byte string to generate a key from.
+   * @param length the number of bytes to generate (the size of the key).
+   *
+   * @return the key as a binary-encoded string.
+   */
+  kdf.generate = function(x, length) {
+    var key = new forge.util.ByteBuffer();
+
+    // run counter from counterStart to ceil(length / Hash.len)
+    var k = Math.ceil(length / digestLength) + counterStart;
+
+    var c = new forge.util.ByteBuffer();
+    for(var i = counterStart; i < k; ++i) {
+      // I2OSP(i, 4): convert counter to an octet string of 4 octets
+      c.putInt32(i);
+
+      // digest 'x' and the counter and add the result to the key
+      md.start();
+      md.update(x + c.getBytes());
+      var hash = md.digest();
+      key.putBytes(hash.getBytes(digestLength));
+    }
+
+    // truncate to the correct key length
+    key.truncate(key.length() - length);
+    return key.getBytes();
+  };
+}
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'kem';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './util','./random','./jsbn'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/log.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/log.js
new file mode 100644
index 0000000..c7931f5
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/log.js
@@ -0,0 +1,372 @@
+/**
+ * Cross-browser support for logging in a web application.
+ *
+ * @author David I. Lehn <dlehn@digitalbazaar.com>
+ *
+ * Copyright (c) 2008-2013 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+/* LOG API */
+forge.log = forge.log || {};
+
+/**
+ * Application logging system.
+ *
+ * Each logger level available as it's own function of the form:
+ *   forge.log.level(category, args...)
+ * The category is an arbitrary string, and the args are the same as
+ * Firebug's console.log API. By default the call will be output as:
+ *   'LEVEL [category] <args[0]>, args[1], ...'
+ * This enables proper % formatting via the first argument.
+ * Each category is enabled by default but can be enabled or disabled with
+ * the setCategoryEnabled() function.
+ */
+// list of known levels
+forge.log.levels = [
+  'none', 'error', 'warning', 'info', 'debug', 'verbose', 'max'];
+// info on the levels indexed by name:
+//   index: level index
+//   name: uppercased display name
+var sLevelInfo = {};
+// list of loggers
+var sLoggers = [];
+/**
+ * Standard console logger. If no console support is enabled this will
+ * remain null. Check before using.
+ */
+var sConsoleLogger = null;
+
+// logger flags
+/**
+ * Lock the level at the current value. Used in cases where user config may
+ * set the level such that only critical messages are seen but more verbose
+ * messages are needed for debugging or other purposes.
+ */
+forge.log.LEVEL_LOCKED = (1 << 1);
+/**
+ * Always call log function. By default, the logging system will check the
+ * message level against logger.level before calling the log function. This
+ * flag allows the function to do its own check.
+ */
+forge.log.NO_LEVEL_CHECK = (1 << 2);
+/**
+ * Perform message interpolation with the passed arguments. "%" style
+ * fields in log messages will be replaced by arguments as needed. Some
+ * loggers, such as Firebug, may do this automatically. The original log
+ * message will be available as 'message' and the interpolated version will
+ * be available as 'fullMessage'.
+ */
+forge.log.INTERPOLATE = (1 << 3);
+
+// setup each log level
+for(var i = 0; i < forge.log.levels.length; ++i) {
+  var level = forge.log.levels[i];
+  sLevelInfo[level] = {
+    index: i,
+    name: level.toUpperCase()
+  };
+}
+
+/**
+ * Message logger. Will dispatch a message to registered loggers as needed.
+ *
+ * @param message message object
+ */
+forge.log.logMessage = function(message) {
+  var messageLevelIndex = sLevelInfo[message.level].index;
+  for(var i = 0; i < sLoggers.length; ++i) {
+    var logger = sLoggers[i];
+    if(logger.flags & forge.log.NO_LEVEL_CHECK) {
+      logger.f(message);
+    } else {
+      // get logger level
+      var loggerLevelIndex = sLevelInfo[logger.level].index;
+      // check level
+      if(messageLevelIndex <= loggerLevelIndex) {
+        // message critical enough, call logger
+        logger.f(logger, message);
+      }
+    }
+  }
+};
+
+/**
+ * Sets the 'standard' key on a message object to:
+ * "LEVEL [category] " + message
+ *
+ * @param message a message log object
+ */
+forge.log.prepareStandard = function(message) {
+  if(!('standard' in message)) {
+    message.standard =
+      sLevelInfo[message.level].name +
+      //' ' + +message.timestamp +
+      ' [' + message.category + '] ' +
+      message.message;
+  }
+};
+
+/**
+ * Sets the 'full' key on a message object to the original message
+ * interpolated via % formatting with the message arguments.
+ *
+ * @param message a message log object.
+ */
+forge.log.prepareFull = function(message) {
+  if(!('full' in message)) {
+    // copy args and insert message at the front
+    var args = [message.message];
+    args = args.concat([] || message['arguments']);
+    // format the message
+    message.full = forge.util.format.apply(this, args);
+  }
+};
+
+/**
+ * Applies both preparseStandard() and prepareFull() to a message object and
+ * store result in 'standardFull'.
+ *
+ * @param message a message log object.
+ */
+forge.log.prepareStandardFull = function(message) {
+  if(!('standardFull' in message)) {
+    // FIXME implement 'standardFull' logging
+    forge.log.prepareStandard(message);
+    message.standardFull = message.standard;
+  }
+};
+
+// create log level functions
+if(true) {
+  // levels for which we want functions
+  var levels = ['error', 'warning', 'info', 'debug', 'verbose'];
+  for(var i = 0; i < levels.length; ++i) {
+    // wrap in a function to ensure proper level var is passed
+    (function(level) {
+      // create function for this level
+      forge.log[level] = function(category, message/*, args...*/) {
+        // convert arguments to real array, remove category and message
+        var args = Array.prototype.slice.call(arguments).slice(2);
+        // create message object
+        // Note: interpolation and standard formatting is done lazily
+        var msg = {
+          timestamp: new Date(),
+          level: level,
+          category: category,
+          message: message,
+          'arguments': args
+          /*standard*/
+          /*full*/
+          /*fullMessage*/
+        };
+        // process this message
+        forge.log.logMessage(msg);
+      };
+    })(levels[i]);
+  }
+}
+
+/**
+ * Creates a new logger with specified custom logging function.
+ *
+ * The logging function has a signature of:
+ *   function(logger, message)
+ * logger: current logger
+ * message: object:
+ *   level: level id
+ *   category: category
+ *   message: string message
+ *   arguments: Array of extra arguments
+ *   fullMessage: interpolated message and arguments if INTERPOLATE flag set
+ *
+ * @param logFunction a logging function which takes a log message object
+ *          as a parameter.
+ *
+ * @return a logger object.
+ */
+forge.log.makeLogger = function(logFunction) {
+  var logger = {
+    flags: 0,
+    f: logFunction
+  };
+  forge.log.setLevel(logger, 'none');
+  return logger;
+};
+
+/**
+ * Sets the current log level on a logger.
+ *
+ * @param logger the target logger.
+ * @param level the new maximum log level as a string.
+ *
+ * @return true if set, false if not.
+ */
+forge.log.setLevel = function(logger, level) {
+  var rval = false;
+  if(logger && !(logger.flags & forge.log.LEVEL_LOCKED)) {
+    for(var i = 0; i < forge.log.levels.length; ++i) {
+      var aValidLevel = forge.log.levels[i];
+      if(level == aValidLevel) {
+        // set level
+        logger.level = level;
+        rval = true;
+        break;
+      }
+    }
+  }
+
+  return rval;
+};
+
+/**
+ * Locks the log level at its current value.
+ *
+ * @param logger the target logger.
+ * @param lock boolean lock value, default to true.
+ */
+forge.log.lock = function(logger, lock) {
+  if(typeof lock === 'undefined' || lock) {
+    logger.flags |= forge.log.LEVEL_LOCKED;
+  } else {
+    logger.flags &= ~forge.log.LEVEL_LOCKED;
+  }
+};
+
+/**
+ * Adds a logger.
+ *
+ * @param logger the logger object.
+ */
+forge.log.addLogger = function(logger) {
+  sLoggers.push(logger);
+};
+
+// setup the console logger if possible, else create fake console.log
+if(typeof(console) !== 'undefined' && 'log' in console) {
+  var logger;
+  if(console.error && console.warn && console.info && console.debug) {
+    // looks like Firebug-style logging is available
+    // level handlers map
+    var levelHandlers = {
+      error: console.error,
+      warning: console.warn,
+      info: console.info,
+      debug: console.debug,
+      verbose: console.debug
+    };
+    var f = function(logger, message) {
+      forge.log.prepareStandard(message);
+      var handler = levelHandlers[message.level];
+      // prepend standard message and concat args
+      var args = [message.standard];
+      args = args.concat(message['arguments'].slice());
+      // apply to low-level console function
+      handler.apply(console, args);
+    };
+    logger = forge.log.makeLogger(f);
+  } else {
+    // only appear to have basic console.log
+    var f = function(logger, message) {
+      forge.log.prepareStandardFull(message);
+      console.log(message.standardFull);
+    };
+    logger = forge.log.makeLogger(f);
+  }
+  forge.log.setLevel(logger, 'debug');
+  forge.log.addLogger(logger);
+  sConsoleLogger = logger;
+} else {
+  // define fake console.log to avoid potential script errors on
+  // browsers that do not have console logging
+  console = {
+    log: function() {}
+  };
+}
+
+/*
+ * Check for logging control query vars.
+ *
+ * console.level=<level-name>
+ * Set's the console log level by name.  Useful to override defaults and
+ * allow more verbose logging before a user config is loaded.
+ *
+ * console.lock=<true|false>
+ * Lock the console log level at whatever level it is set at.  This is run
+ * after console.level is processed.  Useful to force a level of verbosity
+ * that could otherwise be limited by a user config.
+ */
+if(sConsoleLogger !== null) {
+  var query = forge.util.getQueryVariables();
+  if('console.level' in query) {
+    // set with last value
+    forge.log.setLevel(
+      sConsoleLogger, query['console.level'].slice(-1)[0]);
+  }
+  if('console.lock' in query) {
+    // set with last value
+    var lock = query['console.lock'].slice(-1)[0];
+    if(lock == 'true') {
+      forge.log.lock(sConsoleLogger);
+    }
+  }
+}
+
+// provide public access to console logger
+forge.log.consoleLogger = sConsoleLogger;
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'log';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/md.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/md.js
new file mode 100644
index 0000000..e980cfd
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/md.js
@@ -0,0 +1,75 @@
+/**
+ * Node.js module for Forge message digests.
+ *
+ * @author Dave Longley
+ *
+ * Copyright 2011-2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+forge.md = forge.md || {};
+forge.md.algorithms = {
+  md5: forge.md5,
+  sha1: forge.sha1,
+  sha256: forge.sha256
+};
+forge.md.md5 = forge.md5;
+forge.md.sha1 = forge.sha1;
+forge.md.sha256 = forge.sha256;
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'md';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(
+  ['require', 'module', './md5', './sha1', './sha256', './sha512'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/md5.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/md5.js
new file mode 100644
index 0000000..acf7d11
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/md5.js
@@ -0,0 +1,322 @@
+/**
+ * Message Digest Algorithm 5 with 128-bit digest (MD5) implementation.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+var md5 = forge.md5 = forge.md5 || {};
+forge.md = forge.md || {};
+forge.md.algorithms = forge.md.algorithms || {};
+forge.md.md5 = forge.md.algorithms.md5 = md5;
+
+/**
+ * Creates an MD5 message digest object.
+ *
+ * @return a message digest object.
+ */
+md5.create = function() {
+  // do initialization as necessary
+  if(!_initialized) {
+    _init();
+  }
+
+  // MD5 state contains four 32-bit integers
+  var _state = null;
+
+  // input buffer
+  var _input = forge.util.createBuffer();
+
+  // used for word storage
+  var _w = new Array(16);
+
+  // message digest object
+  var md = {
+    algorithm: 'md5',
+    blockLength: 64,
+    digestLength: 16,
+    // 56-bit length of message so far (does not including padding)
+    messageLength: 0,
+    // true 64-bit message length as two 32-bit ints
+    messageLength64: [0, 0]
+  };
+
+  /**
+   * Starts the digest.
+   *
+   * @return this digest object.
+   */
+  md.start = function() {
+    md.messageLength = 0;
+    md.messageLength64 = [0, 0];
+    _input = forge.util.createBuffer();
+    _state = {
+      h0: 0x67452301,
+      h1: 0xEFCDAB89,
+      h2: 0x98BADCFE,
+      h3: 0x10325476
+    };
+    return md;
+  };
+  // start digest automatically for first time
+  md.start();
+
+  /**
+   * Updates the digest with the given message input. The given input can
+   * treated as raw input (no encoding will be applied) or an encoding of
+   * 'utf8' maybe given to encode the input using UTF-8.
+   *
+   * @param msg the message input to update with.
+   * @param encoding the encoding to use (default: 'raw', other: 'utf8').
+   *
+   * @return this digest object.
+   */
+  md.update = function(msg, encoding) {
+    if(encoding === 'utf8') {
+      msg = forge.util.encodeUtf8(msg);
+    }
+
+    // update message length
+    md.messageLength += msg.length;
+    md.messageLength64[0] += (msg.length / 0x100000000) >>> 0;
+    md.messageLength64[1] += msg.length >>> 0;
+
+    // add bytes to input buffer
+    _input.putBytes(msg);
+
+    // process bytes
+    _update(_state, _w, _input);
+
+    // compact input buffer every 2K or if empty
+    if(_input.read > 2048 || _input.length() === 0) {
+      _input.compact();
+    }
+
+    return md;
+  };
+
+  /**
+   * Produces the digest.
+   *
+   * @return a byte buffer containing the digest value.
+   */
+  md.digest = function() {
+    /* Note: Here we copy the remaining bytes in the input buffer and
+    add the appropriate MD5 padding. Then we do the final update
+    on a copy of the state so that if the user wants to get
+    intermediate digests they can do so. */
+
+    /* Determine the number of bytes that must be added to the message
+    to ensure its length is congruent to 448 mod 512. In other words,
+    the data to be digested must be a multiple of 512 bits (or 128 bytes).
+    This data includes the message, some padding, and the length of the
+    message. Since the length of the message will be encoded as 8 bytes (64
+    bits), that means that the last segment of the data must have 56 bytes
+    (448 bits) of message and padding. Therefore, the length of the message
+    plus the padding must be congruent to 448 mod 512 because
+    512 - 128 = 448.
+
+    In order to fill up the message length it must be filled with
+    padding that begins with 1 bit followed by all 0 bits. Padding
+    must *always* be present, so if the message length is already
+    congruent to 448 mod 512, then 512 padding bits must be added. */
+
+    // 512 bits == 64 bytes, 448 bits == 56 bytes, 64 bits = 8 bytes
+    // _padding starts with 1 byte with first bit is set in it which
+    // is byte value 128, then there may be up to 63 other pad bytes
+    var padBytes = forge.util.createBuffer();
+    padBytes.putBytes(_input.bytes());
+    // 64 - (remaining msg + 8 bytes msg length) mod 64
+    padBytes.putBytes(
+      _padding.substr(0, 64 - ((md.messageLength64[1] + 8) & 0x3F)));
+
+    /* Now append length of the message. The length is appended in bits
+    as a 64-bit number in little-endian order. Since we store the length in
+    bytes, we must multiply the 64-bit length by 8 (or left shift by 3). */
+    padBytes.putInt32Le(md.messageLength64[1] << 3);
+    padBytes.putInt32Le(
+      (md.messageLength64[0] << 3) | (md.messageLength64[0] >>> 28));
+    var s2 = {
+      h0: _state.h0,
+      h1: _state.h1,
+      h2: _state.h2,
+      h3: _state.h3
+    };
+    _update(s2, _w, padBytes);
+    var rval = forge.util.createBuffer();
+    rval.putInt32Le(s2.h0);
+    rval.putInt32Le(s2.h1);
+    rval.putInt32Le(s2.h2);
+    rval.putInt32Le(s2.h3);
+    return rval;
+  };
+
+  return md;
+};
+
+// padding, constant tables for calculating md5
+var _padding = null;
+var _g = null;
+var _r = null;
+var _k = null;
+var _initialized = false;
+
+/**
+ * Initializes the constant tables.
+ */
+function _init() {
+  // create padding
+  _padding = String.fromCharCode(128);
+  _padding += forge.util.fillString(String.fromCharCode(0x00), 64);
+
+  // g values
+  _g = [
+    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+    1, 6, 11, 0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12,
+    5, 8, 11, 14, 1, 4, 7, 10, 13, 0, 3, 6, 9, 12, 15, 2,
+    0, 7, 14, 5, 12, 3, 10, 1, 8, 15, 6, 13, 4, 11, 2, 9];
+
+  // rounds table
+  _r = [
+    7, 12, 17, 22,  7, 12, 17, 22,  7, 12, 17, 22,  7, 12, 17, 22,
+    5,  9, 14, 20,  5,  9, 14, 20,  5,  9, 14, 20,  5,  9, 14, 20,
+    4, 11, 16, 23,  4, 11, 16, 23,  4, 11, 16, 23,  4, 11, 16, 23,
+    6, 10, 15, 21,  6, 10, 15, 21,  6, 10, 15, 21,  6, 10, 15, 21];
+
+  // get the result of abs(sin(i + 1)) as a 32-bit integer
+  _k = new Array(64);
+  for(var i = 0; i < 64; ++i) {
+    _k[i] = Math.floor(Math.abs(Math.sin(i + 1)) * 0x100000000);
+  }
+
+  // now initialized
+  _initialized = true;
+}
+
+/**
+ * Updates an MD5 state with the given byte buffer.
+ *
+ * @param s the MD5 state to update.
+ * @param w the array to use to store words.
+ * @param bytes the byte buffer to update with.
+ */
+function _update(s, w, bytes) {
+  // consume 512 bit (64 byte) chunks
+  var t, a, b, c, d, f, r, i;
+  var len = bytes.length();
+  while(len >= 64) {
+    // initialize hash value for this chunk
+    a = s.h0;
+    b = s.h1;
+    c = s.h2;
+    d = s.h3;
+
+    // round 1
+    for(i = 0; i < 16; ++i) {
+      w[i] = bytes.getInt32Le();
+      f = d ^ (b & (c ^ d));
+      t = (a + f + _k[i] + w[i]);
+      r = _r[i];
+      a = d;
+      d = c;
+      c = b;
+      b += (t << r) | (t >>> (32 - r));
+    }
+    // round 2
+    for(; i < 32; ++i) {
+      f = c ^ (d & (b ^ c));
+      t = (a + f + _k[i] + w[_g[i]]);
+      r = _r[i];
+      a = d;
+      d = c;
+      c = b;
+      b += (t << r) | (t >>> (32 - r));
+    }
+    // round 3
+    for(; i < 48; ++i) {
+      f = b ^ c ^ d;
+      t = (a + f + _k[i] + w[_g[i]]);
+      r = _r[i];
+      a = d;
+      d = c;
+      c = b;
+      b += (t << r) | (t >>> (32 - r));
+    }
+    // round 4
+    for(; i < 64; ++i) {
+      f = c ^ (b | ~d);
+      t = (a + f + _k[i] + w[_g[i]]);
+      r = _r[i];
+      a = d;
+      d = c;
+      c = b;
+      b += (t << r) | (t >>> (32 - r));
+    }
+
+    // update hash state
+    s.h0 = (s.h0 + a) | 0;
+    s.h1 = (s.h1 + b) | 0;
+    s.h2 = (s.h2 + c) | 0;
+    s.h3 = (s.h3 + d) | 0;
+
+    len -= 64;
+  }
+}
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'md5';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/mgf.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/mgf.js
new file mode 100644
index 0000000..927082a
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/mgf.js
@@ -0,0 +1,67 @@
+/**
+ * Node.js module for Forge mask generation functions.
+ *
+ * @author Stefan Siegl
+ *
+ * Copyright 2012 Stefan Siegl <stesie@brokenpipe.de>
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+forge.mgf = forge.mgf || {};
+forge.mgf.mgf1 = forge.mgf1;
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'mgf';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './mgf1'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/mgf1.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/mgf1.js
new file mode 100644
index 0000000..82d62cd
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/mgf1.js
@@ -0,0 +1,112 @@
+/**
+ * Javascript implementation of mask generation function MGF1.
+ *
+ * @author Stefan Siegl
+ * @author Dave Longley
+ *
+ * Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
+ * Copyright (c) 2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+forge.mgf = forge.mgf || {};
+var mgf1 = forge.mgf.mgf1 = forge.mgf1 = forge.mgf1 || {};
+
+/**
+ * Creates a MGF1 mask generation function object.
+ *
+ * @param md the message digest API to use (eg: forge.md.sha1.create()).
+ *
+ * @return a mask generation function object.
+ */
+mgf1.create = function(md) {
+  var mgf = {
+    /**
+     * Generate mask of specified length.
+     *
+     * @param {String} seed The seed for mask generation.
+     * @param maskLen Number of bytes to generate.
+     * @return {String} The generated mask.
+     */
+    generate: function(seed, maskLen) {
+      /* 2. Let T be the empty octet string. */
+      var t = new forge.util.ByteBuffer();
+
+      /* 3. For counter from 0 to ceil(maskLen / hLen), do the following: */
+      var len = Math.ceil(maskLen / md.digestLength);
+      for(var i = 0; i < len; i++) {
+        /* a. Convert counter to an octet string C of length 4 octets */
+        var c = new forge.util.ByteBuffer();
+        c.putInt32(i);
+
+        /* b. Concatenate the hash of the seed mgfSeed and C to the octet
+         * string T: */
+        md.start();
+        md.update(seed + c.getBytes());
+        t.putBuffer(md.digest());
+      }
+
+      /* Output the leading maskLen octets of T as the octet string mask. */
+      t.truncate(t.length() - maskLen);
+      return t.getBytes();
+    }
+  };
+
+  return mgf;
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'mgf1';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/oids.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/oids.js
new file mode 100644
index 0000000..ef3e67d
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/oids.js
@@ -0,0 +1,269 @@
+/**
+ * Object IDs for ASN.1.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2013 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+forge.pki = forge.pki || {};
+var oids = forge.pki.oids = forge.oids = forge.oids || {};
+
+// algorithm OIDs
+oids['1.2.840.113549.1.1.1'] = 'rsaEncryption';
+oids['rsaEncryption'] = '1.2.840.113549.1.1.1';
+// Note: md2 & md4 not implemented
+//oids['1.2.840.113549.1.1.2'] = 'md2WithRSAEncryption';
+//oids['md2WithRSAEncryption'] = '1.2.840.113549.1.1.2';
+//oids['1.2.840.113549.1.1.3'] = 'md4WithRSAEncryption';
+//oids['md4WithRSAEncryption'] = '1.2.840.113549.1.1.3';
+oids['1.2.840.113549.1.1.4'] = 'md5WithRSAEncryption';
+oids['md5WithRSAEncryption'] = '1.2.840.113549.1.1.4';
+oids['1.2.840.113549.1.1.5'] = 'sha1WithRSAEncryption';
+oids['sha1WithRSAEncryption'] = '1.2.840.113549.1.1.5';
+oids['1.2.840.113549.1.1.7'] = 'RSAES-OAEP';
+oids['RSAES-OAEP'] = '1.2.840.113549.1.1.7';
+oids['1.2.840.113549.1.1.8'] = 'mgf1';
+oids['mgf1'] = '1.2.840.113549.1.1.8';
+oids['1.2.840.113549.1.1.9'] = 'pSpecified';
+oids['pSpecified'] = '1.2.840.113549.1.1.9';
+oids['1.2.840.113549.1.1.10'] = 'RSASSA-PSS';
+oids['RSASSA-PSS'] = '1.2.840.113549.1.1.10';
+oids['1.2.840.113549.1.1.11'] = 'sha256WithRSAEncryption';
+oids['sha256WithRSAEncryption'] = '1.2.840.113549.1.1.11';
+oids['1.2.840.113549.1.1.12'] = 'sha384WithRSAEncryption';
+oids['sha384WithRSAEncryption'] = '1.2.840.113549.1.1.12';
+oids['1.2.840.113549.1.1.13'] = 'sha512WithRSAEncryption';
+oids['sha512WithRSAEncryption'] = '1.2.840.113549.1.1.13';
+
+oids['1.3.14.3.2.7'] = 'desCBC';
+oids['desCBC'] = '1.3.14.3.2.7';
+
+oids['1.3.14.3.2.26'] = 'sha1';
+oids['sha1'] = '1.3.14.3.2.26';
+oids['2.16.840.1.101.3.4.2.1'] = 'sha256';
+oids['sha256'] = '2.16.840.1.101.3.4.2.1';
+oids['2.16.840.1.101.3.4.2.2'] = 'sha384';
+oids['sha384'] = '2.16.840.1.101.3.4.2.2';
+oids['2.16.840.1.101.3.4.2.3'] = 'sha512';
+oids['sha512'] = '2.16.840.1.101.3.4.2.3';
+oids['1.2.840.113549.2.5'] = 'md5';
+oids['md5'] = '1.2.840.113549.2.5';
+
+// pkcs#7 content types
+oids['1.2.840.113549.1.7.1'] = 'data';
+oids['data'] = '1.2.840.113549.1.7.1';
+oids['1.2.840.113549.1.7.2'] = 'signedData';
+oids['signedData'] = '1.2.840.113549.1.7.2';
+oids['1.2.840.113549.1.7.3'] = 'envelopedData';
+oids['envelopedData'] = '1.2.840.113549.1.7.3';
+oids['1.2.840.113549.1.7.4'] = 'signedAndEnvelopedData';
+oids['signedAndEnvelopedData'] = '1.2.840.113549.1.7.4';
+oids['1.2.840.113549.1.7.5'] = 'digestedData';
+oids['digestedData'] = '1.2.840.113549.1.7.5';
+oids['1.2.840.113549.1.7.6'] = 'encryptedData';
+oids['encryptedData'] = '1.2.840.113549.1.7.6';
+
+// pkcs#9 oids
+oids['1.2.840.113549.1.9.1'] = 'emailAddress';
+oids['emailAddress'] = '1.2.840.113549.1.9.1';
+oids['1.2.840.113549.1.9.2'] = 'unstructuredName';
+oids['unstructuredName'] = '1.2.840.113549.1.9.2';
+oids['1.2.840.113549.1.9.3'] = 'contentType';
+oids['contentType'] = '1.2.840.113549.1.9.3';
+oids['1.2.840.113549.1.9.4'] = 'messageDigest';
+oids['messageDigest'] = '1.2.840.113549.1.9.4';
+oids['1.2.840.113549.1.9.5'] = 'signingTime';
+oids['signingTime'] = '1.2.840.113549.1.9.5';
+oids['1.2.840.113549.1.9.6'] = 'counterSignature';
+oids['counterSignature'] = '1.2.840.113549.1.9.6';
+oids['1.2.840.113549.1.9.7'] = 'challengePassword';
+oids['challengePassword'] = '1.2.840.113549.1.9.7';
+oids['1.2.840.113549.1.9.8'] = 'unstructuredAddress';
+oids['unstructuredAddress'] = '1.2.840.113549.1.9.8';
+oids['1.2.840.113549.1.9.14'] = 'extensionRequest';
+oids['extensionRequest'] = '1.2.840.113549.1.9.14';
+
+oids['1.2.840.113549.1.9.20'] = 'friendlyName';
+oids['friendlyName'] = '1.2.840.113549.1.9.20';
+oids['1.2.840.113549.1.9.21'] = 'localKeyId';
+oids['localKeyId'] = '1.2.840.113549.1.9.21';
+oids['1.2.840.113549.1.9.22.1'] = 'x509Certificate';
+oids['x509Certificate'] = '1.2.840.113549.1.9.22.1';
+
+// pkcs#12 safe bags
+oids['1.2.840.113549.1.12.10.1.1'] = 'keyBag';
+oids['keyBag'] = '1.2.840.113549.1.12.10.1.1';
+oids['1.2.840.113549.1.12.10.1.2'] = 'pkcs8ShroudedKeyBag';
+oids['pkcs8ShroudedKeyBag'] = '1.2.840.113549.1.12.10.1.2';
+oids['1.2.840.113549.1.12.10.1.3'] = 'certBag';
+oids['certBag'] = '1.2.840.113549.1.12.10.1.3';
+oids['1.2.840.113549.1.12.10.1.4'] = 'crlBag';
+oids['crlBag'] = '1.2.840.113549.1.12.10.1.4';
+oids['1.2.840.113549.1.12.10.1.5'] = 'secretBag';
+oids['secretBag'] = '1.2.840.113549.1.12.10.1.5';
+oids['1.2.840.113549.1.12.10.1.6'] = 'safeContentsBag';
+oids['safeContentsBag'] = '1.2.840.113549.1.12.10.1.6';
+
+// password-based-encryption for pkcs#12
+oids['1.2.840.113549.1.5.13'] = 'pkcs5PBES2';
+oids['pkcs5PBES2'] = '1.2.840.113549.1.5.13';
+oids['1.2.840.113549.1.5.12'] = 'pkcs5PBKDF2';
+oids['pkcs5PBKDF2'] = '1.2.840.113549.1.5.12';
+
+oids['1.2.840.113549.1.12.1.1'] = 'pbeWithSHAAnd128BitRC4';
+oids['pbeWithSHAAnd128BitRC4'] = '1.2.840.113549.1.12.1.1';
+oids['1.2.840.113549.1.12.1.2'] = 'pbeWithSHAAnd40BitRC4';
+oids['pbeWithSHAAnd40BitRC4'] = '1.2.840.113549.1.12.1.2';
+oids['1.2.840.113549.1.12.1.3'] = 'pbeWithSHAAnd3-KeyTripleDES-CBC';
+oids['pbeWithSHAAnd3-KeyTripleDES-CBC'] = '1.2.840.113549.1.12.1.3';
+oids['1.2.840.113549.1.12.1.4'] = 'pbeWithSHAAnd2-KeyTripleDES-CBC';
+oids['pbeWithSHAAnd2-KeyTripleDES-CBC'] = '1.2.840.113549.1.12.1.4';
+oids['1.2.840.113549.1.12.1.5'] = 'pbeWithSHAAnd128BitRC2-CBC';
+oids['pbeWithSHAAnd128BitRC2-CBC'] = '1.2.840.113549.1.12.1.5';
+oids['1.2.840.113549.1.12.1.6'] = 'pbewithSHAAnd40BitRC2-CBC';
+oids['pbewithSHAAnd40BitRC2-CBC'] = '1.2.840.113549.1.12.1.6';
+
+// symmetric key algorithm oids
+oids['1.2.840.113549.3.7'] = 'des-EDE3-CBC';
+oids['des-EDE3-CBC'] = '1.2.840.113549.3.7';
+oids['2.16.840.1.101.3.4.1.2'] = 'aes128-CBC';
+oids['aes128-CBC'] = '2.16.840.1.101.3.4.1.2';
+oids['2.16.840.1.101.3.4.1.22'] = 'aes192-CBC';
+oids['aes192-CBC'] = '2.16.840.1.101.3.4.1.22';
+oids['2.16.840.1.101.3.4.1.42'] = 'aes256-CBC';
+oids['aes256-CBC'] = '2.16.840.1.101.3.4.1.42';
+
+// certificate issuer/subject OIDs
+oids['2.5.4.3'] = 'commonName';
+oids['commonName'] = '2.5.4.3';
+oids['2.5.4.5'] = 'serialName';
+oids['serialName'] = '2.5.4.5';
+oids['2.5.4.6'] = 'countryName';
+oids['countryName'] = '2.5.4.6';
+oids['2.5.4.7'] = 'localityName';
+oids['localityName'] = '2.5.4.7';
+oids['2.5.4.8'] = 'stateOrProvinceName';
+oids['stateOrProvinceName'] = '2.5.4.8';
+oids['2.5.4.10'] = 'organizationName';
+oids['organizationName'] = '2.5.4.10';
+oids['2.5.4.11'] = 'organizationalUnitName';
+oids['organizationalUnitName'] = '2.5.4.11';
+
+// X.509 extension OIDs
+oids['2.16.840.1.113730.1.1'] = 'nsCertType';
+oids['nsCertType'] = '2.16.840.1.113730.1.1';
+oids['2.5.29.1'] = 'authorityKeyIdentifier'; // deprecated, use .35
+oids['2.5.29.2'] = 'keyAttributes'; // obsolete use .37 or .15
+oids['2.5.29.3'] = 'certificatePolicies'; // deprecated, use .32
+oids['2.5.29.4'] = 'keyUsageRestriction'; // obsolete use .37 or .15
+oids['2.5.29.5'] = 'policyMapping'; // deprecated use .33
+oids['2.5.29.6'] = 'subtreesConstraint'; // obsolete use .30
+oids['2.5.29.7'] = 'subjectAltName'; // deprecated use .17
+oids['2.5.29.8'] = 'issuerAltName'; // deprecated use .18
+oids['2.5.29.9'] = 'subjectDirectoryAttributes';
+oids['2.5.29.10'] = 'basicConstraints'; // deprecated use .19
+oids['2.5.29.11'] = 'nameConstraints'; // deprecated use .30
+oids['2.5.29.12'] = 'policyConstraints'; // deprecated use .36
+oids['2.5.29.13'] = 'basicConstraints'; // deprecated use .19
+oids['2.5.29.14'] = 'subjectKeyIdentifier';
+oids['subjectKeyIdentifier'] = '2.5.29.14';
+oids['2.5.29.15'] = 'keyUsage';
+oids['keyUsage'] = '2.5.29.15';
+oids['2.5.29.16'] = 'privateKeyUsagePeriod';
+oids['2.5.29.17'] = 'subjectAltName';
+oids['subjectAltName'] = '2.5.29.17';
+oids['2.5.29.18'] = 'issuerAltName';
+oids['issuerAltName'] = '2.5.29.18';
+oids['2.5.29.19'] = 'basicConstraints';
+oids['basicConstraints'] = '2.5.29.19';
+oids['2.5.29.20'] = 'cRLNumber';
+oids['2.5.29.21'] = 'cRLReason';
+oids['2.5.29.22'] = 'expirationDate';
+oids['2.5.29.23'] = 'instructionCode';
+oids['2.5.29.24'] = 'invalidityDate';
+oids['2.5.29.25'] = 'cRLDistributionPoints'; // deprecated use .31
+oids['2.5.29.26'] = 'issuingDistributionPoint'; // deprecated use .28
+oids['2.5.29.27'] = 'deltaCRLIndicator';
+oids['2.5.29.28'] = 'issuingDistributionPoint';
+oids['2.5.29.29'] = 'certificateIssuer';
+oids['2.5.29.30'] = 'nameConstraints';
+oids['2.5.29.31'] = 'cRLDistributionPoints';
+oids['2.5.29.32'] = 'certificatePolicies';
+oids['2.5.29.33'] = 'policyMappings';
+oids['2.5.29.34'] = 'policyConstraints'; // deprecated use .36
+oids['2.5.29.35'] = 'authorityKeyIdentifier';
+oids['2.5.29.36'] = 'policyConstraints';
+oids['2.5.29.37'] = 'extKeyUsage';
+oids['extKeyUsage'] = '2.5.29.37';
+oids['2.5.29.46'] = 'freshestCRL';
+oids['2.5.29.54'] = 'inhibitAnyPolicy';
+
+// extKeyUsage purposes
+oids['1.3.6.1.5.5.7.3.1'] = 'serverAuth';
+oids['serverAuth'] = '1.3.6.1.5.5.7.3.1';
+oids['1.3.6.1.5.5.7.3.2'] = 'clientAuth';
+oids['clientAuth'] = '1.3.6.1.5.5.7.3.2';
+oids['1.3.6.1.5.5.7.3.3'] = 'codeSigning';
+oids['codeSigning'] = '1.3.6.1.5.5.7.3.3';
+oids['1.3.6.1.5.5.7.3.4'] = 'emailProtection';
+oids['emailProtection'] = '1.3.6.1.5.5.7.3.4';
+oids['1.3.6.1.5.5.7.3.8'] = 'timeStamping';
+oids['timeStamping'] = '1.3.6.1.5.5.7.3.8';
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'oids';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/pbe.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/pbe.js
new file mode 100644
index 0000000..0b25758
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/pbe.js
@@ -0,0 +1,975 @@
+/**
+ * Password-based encryption functions.
+ *
+ * @author Dave Longley
+ * @author Stefan Siegl <stesie@brokenpipe.de>
+ *
+ * Copyright (c) 2010-2013 Digital Bazaar, Inc.
+ * Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
+ *
+ * An EncryptedPrivateKeyInfo:
+ *
+ * EncryptedPrivateKeyInfo ::= SEQUENCE {
+ *   encryptionAlgorithm  EncryptionAlgorithmIdentifier,
+ *   encryptedData        EncryptedData }
+ *
+ * EncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
+ *
+ * EncryptedData ::= OCTET STRING
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+if(typeof BigInteger === 'undefined') {
+  var BigInteger = forge.jsbn.BigInteger;
+}
+
+// shortcut for asn.1 API
+var asn1 = forge.asn1;
+
+/* Password-based encryption implementation. */
+var pki = forge.pki = forge.pki || {};
+pki.pbe = forge.pbe = forge.pbe || {};
+var oids = pki.oids;
+
+// validator for an EncryptedPrivateKeyInfo structure
+// Note: Currently only works w/algorithm params
+var encryptedPrivateKeyValidator = {
+  name: 'EncryptedPrivateKeyInfo',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'EncryptedPrivateKeyInfo.encryptionAlgorithm',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true,
+    value: [{
+      name: 'AlgorithmIdentifier.algorithm',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.OID,
+      constructed: false,
+      capture: 'encryptionOid'
+    }, {
+      name: 'AlgorithmIdentifier.parameters',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.SEQUENCE,
+      constructed: true,
+      captureAsn1: 'encryptionParams'
+    }]
+  }, {
+    // encryptedData
+    name: 'EncryptedPrivateKeyInfo.encryptedData',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.OCTETSTRING,
+    constructed: false,
+    capture: 'encryptedData'
+  }]
+};
+
+// validator for a PBES2Algorithms structure
+// Note: Currently only works w/PBKDF2 + AES encryption schemes
+var PBES2AlgorithmsValidator = {
+  name: 'PBES2Algorithms',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'PBES2Algorithms.keyDerivationFunc',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true,
+    value: [{
+      name: 'PBES2Algorithms.keyDerivationFunc.oid',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.OID,
+      constructed: false,
+      capture: 'kdfOid'
+    }, {
+      name: 'PBES2Algorithms.params',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.SEQUENCE,
+      constructed: true,
+      value: [{
+        name: 'PBES2Algorithms.params.salt',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.OCTETSTRING,
+        constructed: false,
+        capture: 'kdfSalt'
+      }, {
+        name: 'PBES2Algorithms.params.iterationCount',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.INTEGER,
+        onstructed: true,
+        capture: 'kdfIterationCount'
+      }]
+    }]
+  }, {
+    name: 'PBES2Algorithms.encryptionScheme',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true,
+    value: [{
+      name: 'PBES2Algorithms.encryptionScheme.oid',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.OID,
+      constructed: false,
+      capture: 'encOid'
+    }, {
+      name: 'PBES2Algorithms.encryptionScheme.iv',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.OCTETSTRING,
+      constructed: false,
+      capture: 'encIv'
+    }]
+  }]
+};
+
+var pkcs12PbeParamsValidator = {
+  name: 'pkcs-12PbeParams',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'pkcs-12PbeParams.salt',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.OCTETSTRING,
+    constructed: false,
+    capture: 'salt'
+  }, {
+    name: 'pkcs-12PbeParams.iterations',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'iterations'
+  }]
+};
+
+/**
+ * Encrypts a ASN.1 PrivateKeyInfo object, producing an EncryptedPrivateKeyInfo.
+ *
+ * PBES2Algorithms ALGORITHM-IDENTIFIER ::=
+ *   { {PBES2-params IDENTIFIED BY id-PBES2}, ...}
+ *
+ * id-PBES2 OBJECT IDENTIFIER ::= {pkcs-5 13}
+ *
+ * PBES2-params ::= SEQUENCE {
+ *   keyDerivationFunc AlgorithmIdentifier {{PBES2-KDFs}},
+ *   encryptionScheme AlgorithmIdentifier {{PBES2-Encs}}
+ * }
+ *
+ * PBES2-KDFs ALGORITHM-IDENTIFIER ::=
+ *   { {PBKDF2-params IDENTIFIED BY id-PBKDF2}, ... }
+ *
+ * PBES2-Encs ALGORITHM-IDENTIFIER ::= { ... }
+ *
+ * PBKDF2-params ::= SEQUENCE {
+ *   salt CHOICE {
+ *     specified OCTET STRING,
+ *     otherSource AlgorithmIdentifier {{PBKDF2-SaltSources}}
+ *   },
+ *   iterationCount INTEGER (1..MAX),
+ *   keyLength INTEGER (1..MAX) OPTIONAL,
+ *   prf AlgorithmIdentifier {{PBKDF2-PRFs}} DEFAULT algid-hmacWithSHA1
+ * }
+ *
+ * @param obj the ASN.1 PrivateKeyInfo object.
+ * @param password the password to encrypt with.
+ * @param options:
+ *          algorithm the encryption algorithm to use
+ *            ('aes128', 'aes192', 'aes256', '3des'), defaults to 'aes128'.
+ *          count the iteration count to use.
+ *          saltSize the salt size to use.
+ *
+ * @return the ASN.1 EncryptedPrivateKeyInfo.
+ */
+pki.encryptPrivateKeyInfo = function(obj, password, options) {
+  // set default options
+  options = options || {};
+  options.saltSize = options.saltSize || 8;
+  options.count = options.count || 2048;
+  options.algorithm = options.algorithm || 'aes128';
+
+  // generate PBE params
+  var salt = forge.random.getBytesSync(options.saltSize);
+  var count = options.count;
+  var countBytes = asn1.integerToDer(count);
+  var dkLen;
+  var encryptionAlgorithm;
+  var encryptedData;
+  if(options.algorithm.indexOf('aes') === 0 || options.algorithm === 'des') {
+    // Do PBES2
+    var ivLen, encOid, cipherFn;
+    switch(options.algorithm) {
+    case 'aes128':
+      dkLen = 16;
+      ivLen = 16;
+      encOid = oids['aes128-CBC'];
+      cipherFn = forge.aes.createEncryptionCipher;
+      break;
+    case 'aes192':
+      dkLen = 24;
+      ivLen = 16;
+      encOid = oids['aes192-CBC'];
+      cipherFn = forge.aes.createEncryptionCipher;
+      break;
+    case 'aes256':
+      dkLen = 32;
+      ivLen = 16;
+      encOid = oids['aes256-CBC'];
+      cipherFn = forge.aes.createEncryptionCipher;
+      break;
+    case 'des':
+      dkLen = 8;
+      ivLen = 8;
+      encOid = oids['desCBC'];
+      cipherFn = forge.des.createEncryptionCipher;
+      break;
+    default:
+      var error = new Error('Cannot encrypt private key. Unknown encryption algorithm.');
+      error.algorithm = options.algorithm;
+      throw error;
+    }
+
+    // encrypt private key using pbe SHA-1 and AES/DES
+    var dk = forge.pkcs5.pbkdf2(password, salt, count, dkLen);
+    var iv = forge.random.getBytesSync(ivLen);
+    var cipher = cipherFn(dk);
+    cipher.start(iv);
+    cipher.update(asn1.toDer(obj));
+    cipher.finish();
+    encryptedData = cipher.output.getBytes();
+
+    encryptionAlgorithm = asn1.create(
+      asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+        asn1.oidToDer(oids['pkcs5PBES2']).getBytes()),
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+        // keyDerivationFunc
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+            asn1.oidToDer(oids['pkcs5PBKDF2']).getBytes()),
+          // PBKDF2-params
+          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+            // salt
+            asn1.create(
+              asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, salt),
+            // iteration count
+            asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+              countBytes.getBytes())
+          ])
+        ]),
+        // encryptionScheme
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+            asn1.oidToDer(encOid).getBytes()),
+          // iv
+          asn1.create(
+            asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, iv)
+        ])
+      ])
+    ]);
+  } else if(options.algorithm === '3des') {
+    // Do PKCS12 PBE
+    dkLen = 24;
+
+    var saltBytes = new forge.util.ByteBuffer(salt);
+    var dk = pki.pbe.generatePkcs12Key(password, saltBytes, 1, count, dkLen);
+    var iv = pki.pbe.generatePkcs12Key(password, saltBytes, 2, count, dkLen);
+    var cipher = forge.des.createEncryptionCipher(dk);
+    cipher.start(iv);
+    cipher.update(asn1.toDer(obj));
+    cipher.finish();
+    encryptedData = cipher.output.getBytes();
+
+    encryptionAlgorithm = asn1.create(
+      asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+        asn1.oidToDer(oids['pbeWithSHAAnd3-KeyTripleDES-CBC']).getBytes()),
+      // pkcs-12PbeParams
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+        // salt
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, salt),
+        // iteration count
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+          countBytes.getBytes())
+      ])
+    ]);
+  } else {
+    var error = new Error('Cannot encrypt private key. Unknown encryption algorithm.');
+    error.algorithm = options.algorithm;
+    throw error;
+  }
+
+  // EncryptedPrivateKeyInfo
+  var rval = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+    // encryptionAlgorithm
+    encryptionAlgorithm,
+    // encryptedData
+    asn1.create(
+      asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, encryptedData)
+  ]);
+  return rval;
+};
+
+/**
+ * Decrypts a ASN.1 PrivateKeyInfo object.
+ *
+ * @param obj the ASN.1 EncryptedPrivateKeyInfo object.
+ * @param password the password to decrypt with.
+ *
+ * @return the ASN.1 PrivateKeyInfo on success, null on failure.
+ */
+pki.decryptPrivateKeyInfo = function(obj, password) {
+  var rval = null;
+
+  // get PBE params
+  var capture = {};
+  var errors = [];
+  if(!asn1.validate(obj, encryptedPrivateKeyValidator, capture, errors)) {
+    var error = new Error('Cannot read encrypted private key. ' +
+      'ASN.1 object is not a supported EncryptedPrivateKeyInfo.');
+    error.errors = errors;
+    throw error;
+  }
+
+  // get cipher
+  var oid = asn1.derToOid(capture.encryptionOid);
+  var cipher = pki.pbe.getCipher(oid, capture.encryptionParams, password);
+
+  // get encrypted data
+  var encrypted = forge.util.createBuffer(capture.encryptedData);
+
+  cipher.update(encrypted);
+  if(cipher.finish()) {
+    rval = asn1.fromDer(cipher.output);
+  }
+
+  return rval;
+};
+
+/**
+ * Converts a EncryptedPrivateKeyInfo to PEM format.
+ *
+ * @param epki the EncryptedPrivateKeyInfo.
+ * @param maxline the maximum characters per line, defaults to 64.
+ *
+ * @return the PEM-formatted encrypted private key.
+ */
+pki.encryptedPrivateKeyToPem = function(epki, maxline) {
+  // convert to DER, then PEM-encode
+  var msg = {
+    type: 'ENCRYPTED PRIVATE KEY',
+    body: asn1.toDer(epki).getBytes()
+  };
+  return forge.pem.encode(msg, {maxline: maxline});
+};
+
+/**
+ * Converts a PEM-encoded EncryptedPrivateKeyInfo to ASN.1 format. Decryption
+ * is not performed.
+ *
+ * @param pem the EncryptedPrivateKeyInfo in PEM-format.
+ *
+ * @return the ASN.1 EncryptedPrivateKeyInfo.
+ */
+pki.encryptedPrivateKeyFromPem = function(pem) {
+  var msg = forge.pem.decode(pem)[0];
+
+  if(msg.type !== 'ENCRYPTED PRIVATE KEY') {
+    var error = new Error('Could not convert encrypted private key from PEM; ' +
+      'PEM header type is "ENCRYPTED PRIVATE KEY".');
+    error.headerType = msg.type;
+    throw error;
+  }
+  if(msg.procType && msg.procType.type === 'ENCRYPTED') {
+    throw new Error('Could not convert encrypted private key from PEM; ' +
+      'PEM is encrypted.');
+  }
+
+  // convert DER to ASN.1 object
+  return asn1.fromDer(msg.body);
+};
+
+/**
+ * Encrypts an RSA private key. By default, the key will be wrapped in
+ * a PrivateKeyInfo and encrypted to produce a PKCS#8 EncryptedPrivateKeyInfo.
+ * This is the standard, preferred way to encrypt a private key.
+ *
+ * To produce a non-standard PEM-encrypted private key that uses encapsulated
+ * headers to indicate the encryption algorithm (old-style non-PKCS#8 OpenSSL
+ * private key encryption), set the 'legacy' option to true. Note: Using this
+ * option will cause the iteration count to be forced to 1.
+ *
+ * Note: The 'des' algorithm is supported, but it is not considered to be
+ * secure because it only uses a single 56-bit key. If possible, it is highly
+ * recommended that a different algorithm be used.
+ *
+ * @param rsaKey the RSA key to encrypt.
+ * @param password the password to use.
+ * @param options:
+ *          algorithm: the encryption algorithm to use
+ *            ('aes128', 'aes192', 'aes256', '3des', 'des').
+ *          count: the iteration count to use.
+ *          saltSize: the salt size to use.
+ *          legacy: output an old non-PKCS#8 PEM-encrypted+encapsulated
+ *            headers (DEK-Info) private key.
+ *
+ * @return the PEM-encoded ASN.1 EncryptedPrivateKeyInfo.
+ */
+pki.encryptRsaPrivateKey = function(rsaKey, password, options) {
+  // standard PKCS#8
+  options = options || {};
+  if(!options.legacy) {
+    // encrypt PrivateKeyInfo
+    var rval = pki.wrapRsaPrivateKey(pki.privateKeyToAsn1(rsaKey));
+    rval = pki.encryptPrivateKeyInfo(rval, password, options);
+    return pki.encryptedPrivateKeyToPem(rval);
+  }
+
+  // legacy non-PKCS#8
+  var algorithm;
+  var iv;
+  var dkLen;
+  var cipherFn;
+  switch(options.algorithm) {
+  case 'aes128':
+    algorithm = 'AES-128-CBC';
+    dkLen = 16;
+    iv = forge.random.getBytesSync(16);
+    cipherFn = forge.aes.createEncryptionCipher;
+    break;
+  case 'aes192':
+    algorithm = 'AES-192-CBC';
+    dkLen = 24;
+    iv = forge.random.getBytesSync(16);
+    cipherFn = forge.aes.createEncryptionCipher;
+    break;
+  case 'aes256':
+    algorithm = 'AES-256-CBC';
+    dkLen = 32;
+    iv = forge.random.getBytesSync(16);
+    cipherFn = forge.aes.createEncryptionCipher;
+    break;
+  case '3des':
+    algorithm = 'DES-EDE3-CBC';
+    dkLen = 24;
+    iv = forge.random.getBytesSync(8);
+    cipherFn = forge.des.createEncryptionCipher;
+    break;
+  case 'des':
+    algorithm = 'DES-CBC';
+    dkLen = 8;
+    iv = forge.random.getBytesSync(8);
+    cipherFn = forge.des.createEncryptionCipher;
+    break;
+  default:
+    var error = new Error('Could not encrypt RSA private key; unsupported ' +
+      'encryption algorithm "' + options.algorithm + '".');
+    error.algorithm = options.algorithm;
+    throw error;
+  }
+
+  // encrypt private key using OpenSSL legacy key derivation
+  var dk = forge.pbe.opensslDeriveBytes(password, iv.substr(0, 8), dkLen);
+  var cipher = cipherFn(dk);
+  cipher.start(iv);
+  cipher.update(asn1.toDer(pki.privateKeyToAsn1(rsaKey)));
+  cipher.finish();
+
+  var msg = {
+    type: 'RSA PRIVATE KEY',
+    procType: {
+      version: '4',
+      type: 'ENCRYPTED'
+    },
+    dekInfo: {
+      algorithm: algorithm,
+      parameters: forge.util.bytesToHex(iv).toUpperCase()
+    },
+    body: cipher.output.getBytes()
+  };
+  return forge.pem.encode(msg);
+};
+
+/**
+ * Decrypts an RSA private key.
+ *
+ * @param pem the PEM-formatted EncryptedPrivateKeyInfo to decrypt.
+ * @param password the password to use.
+ *
+ * @return the RSA key on success, null on failure.
+ */
+pki.decryptRsaPrivateKey = function(pem, password) {
+  var rval = null;
+
+  var msg = forge.pem.decode(pem)[0];
+
+  if(msg.type !== 'ENCRYPTED PRIVATE KEY' &&
+    msg.type !== 'PRIVATE KEY' &&
+    msg.type !== 'RSA PRIVATE KEY') {
+    var error = new Error('Could not convert private key from PEM; PEM header type ' +
+      'is not "ENCRYPTED PRIVATE KEY", "PRIVATE KEY", or "RSA PRIVATE KEY".');
+    error.headerType = error;
+    throw error;
+  }
+
+  if(msg.procType && msg.procType.type === 'ENCRYPTED') {
+    var dkLen;
+    var cipherFn;
+    switch(msg.dekInfo.algorithm) {
+    case 'DES-CBC':
+      dkLen = 8;
+      cipherFn = forge.des.createDecryptionCipher;
+      break;
+    case 'DES-EDE3-CBC':
+      dkLen = 24;
+      cipherFn = forge.des.createDecryptionCipher;
+      break;
+    case 'AES-128-CBC':
+      dkLen = 16;
+      cipherFn = forge.aes.createDecryptionCipher;
+      break;
+    case 'AES-192-CBC':
+      dkLen = 24;
+      cipherFn = forge.aes.createDecryptionCipher;
+      break;
+    case 'AES-256-CBC':
+      dkLen = 32;
+      cipherFn = forge.aes.createDecryptionCipher;
+      break;
+    case 'RC2-40-CBC':
+      dkLen = 5;
+      cipherFn = function(key) {
+        return forge.rc2.createDecryptionCipher(key, 40);
+      };
+      break;
+    case 'RC2-64-CBC':
+      dkLen = 8;
+      cipherFn = function(key) {
+        return forge.rc2.createDecryptionCipher(key, 64);
+      };
+      break;
+    case 'RC2-128-CBC':
+      dkLen = 16;
+      cipherFn = function(key) {
+        return forge.rc2.createDecryptionCipher(key, 128);
+      };
+      break;
+    default:
+      var error = new Error('Could not decrypt private key; unsupported ' +
+        'encryption algorithm "' + msg.dekInfo.algorithm + '".');
+      error.algorithm = msg.dekInfo.algorithm;
+      throw error;
+    }
+
+    // use OpenSSL legacy key derivation
+    var iv = forge.util.hexToBytes(msg.dekInfo.parameters);
+    var dk = forge.pbe.opensslDeriveBytes(password, iv.substr(0, 8), dkLen);
+    var cipher = cipherFn(dk);
+    cipher.start(iv);
+    cipher.update(forge.util.createBuffer(msg.body));
+    if(cipher.finish()) {
+      rval = cipher.output.getBytes();
+    } else {
+      return rval;
+    }
+  } else {
+    rval = msg.body;
+  }
+
+  if(msg.type === 'ENCRYPTED PRIVATE KEY') {
+    rval = pki.decryptPrivateKeyInfo(asn1.fromDer(rval), password);
+  } else {
+    // decryption already performed above
+    rval = asn1.fromDer(rval);
+  }
+
+  if(rval !== null) {
+    rval = pki.privateKeyFromAsn1(rval);
+  }
+
+  return rval;
+};
+
+/**
+ * Derives a PKCS#12 key.
+ *
+ * @param password the password to derive the key material from, null or
+ *          undefined for none.
+ * @param salt the salt, as a ByteBuffer, to use.
+ * @param id the PKCS#12 ID byte (1 = key material, 2 = IV, 3 = MAC).
+ * @param iter the iteration count.
+ * @param n the number of bytes to derive from the password.
+ * @param md the message digest to use, defaults to SHA-1.
+ *
+ * @return a ByteBuffer with the bytes derived from the password.
+ */
+pki.pbe.generatePkcs12Key = function(password, salt, id, iter, n, md) {
+  var j, l;
+
+  if(typeof md === 'undefined' || md === null) {
+    md = forge.md.sha1.create();
+  }
+
+  var u = md.digestLength;
+  var v = md.blockLength;
+  var result = new forge.util.ByteBuffer();
+
+  /* Convert password to Unicode byte buffer + trailing 0-byte. */
+  var passBuf = new forge.util.ByteBuffer();
+  if(password !== null && password !== undefined) {
+    for(l = 0; l < password.length; l++) {
+      passBuf.putInt16(password.charCodeAt(l));
+    }
+    passBuf.putInt16(0);
+  }
+
+  /* Length of salt and password in BYTES. */
+  var p = passBuf.length();
+  var s = salt.length();
+
+  /* 1. Construct a string, D (the "diversifier"), by concatenating
+        v copies of ID. */
+  var D = new forge.util.ByteBuffer();
+  D.fillWithByte(id, v);
+
+  /* 2. Concatenate copies of the salt together to create a string S of length
+        v * ceil(s / v) bytes (the final copy of the salt may be trunacted
+        to create S).
+        Note that if the salt is the empty string, then so is S. */
+  var Slen = v * Math.ceil(s / v);
+  var S = new forge.util.ByteBuffer();
+  for(l = 0; l < Slen; l ++) {
+    S.putByte(salt.at(l % s));
+  }
+
+  /* 3. Concatenate copies of the password together to create a string P of
+        length v * ceil(p / v) bytes (the final copy of the password may be
+        truncated to create P).
+        Note that if the password is the empty string, then so is P. */
+  var Plen = v * Math.ceil(p / v);
+  var P = new forge.util.ByteBuffer();
+  for(l = 0; l < Plen; l ++) {
+    P.putByte(passBuf.at(l % p));
+  }
+
+  /* 4. Set I=S||P to be the concatenation of S and P. */
+  var I = S;
+  I.putBuffer(P);
+
+  /* 5. Set c=ceil(n / u). */
+  var c = Math.ceil(n / u);
+
+  /* 6. For i=1, 2, ..., c, do the following: */
+  for(var i = 1; i <= c; i ++) {
+    /* a) Set Ai=H^r(D||I). (l.e. the rth hash of D||I, H(H(H(...H(D||I)))) */
+    var buf = new forge.util.ByteBuffer();
+    buf.putBytes(D.bytes());
+    buf.putBytes(I.bytes());
+    for(var round = 0; round < iter; round ++) {
+      md.start();
+      md.update(buf.getBytes());
+      buf = md.digest();
+    }
+
+    /* b) Concatenate copies of Ai to create a string B of length v bytes (the
+          final copy of Ai may be truncated to create B). */
+    var B = new forge.util.ByteBuffer();
+    for(l = 0; l < v; l ++) {
+      B.putByte(buf.at(l % u));
+    }
+
+    /* c) Treating I as a concatenation I0, I1, ..., Ik-1 of v-byte blocks,
+          where k=ceil(s / v) + ceil(p / v), modify I by setting
+          Ij=(Ij+B+1) mod 2v for each j.  */
+    var k = Math.ceil(s / v) + Math.ceil(p / v);
+    var Inew = new forge.util.ByteBuffer();
+    for(j = 0; j < k; j ++) {
+      var chunk = new forge.util.ByteBuffer(I.getBytes(v));
+      var x = 0x1ff;
+      for(l = B.length() - 1; l >= 0; l --) {
+        x = x >> 8;
+        x += B.at(l) + chunk.at(l);
+        chunk.setAt(l, x & 0xff);
+      }
+      Inew.putBuffer(chunk);
+    }
+    I = Inew;
+
+    /* Add Ai to A. */
+    result.putBuffer(buf);
+  }
+
+  result.truncate(result.length() - n);
+  return result;
+};
+
+/**
+ * Get new Forge cipher object instance.
+ *
+ * @param oid the OID (in string notation).
+ * @param params the ASN.1 params object.
+ * @param password the password to decrypt with.
+ *
+ * @return new cipher object instance.
+ */
+pki.pbe.getCipher = function(oid, params, password) {
+  switch(oid) {
+  case pki.oids['pkcs5PBES2']:
+    return pki.pbe.getCipherForPBES2(oid, params, password);
+
+  case pki.oids['pbeWithSHAAnd3-KeyTripleDES-CBC']:
+  case pki.oids['pbewithSHAAnd40BitRC2-CBC']:
+    return pki.pbe.getCipherForPKCS12PBE(oid, params, password);
+
+  default:
+    var error = new Error('Cannot read encrypted PBE data block. Unsupported OID.');
+    error.oid = oid;
+    error.supportedOids = [
+      'pkcs5PBES2',
+      'pbeWithSHAAnd3-KeyTripleDES-CBC',
+      'pbewithSHAAnd40BitRC2-CBC'
+    ];
+    throw error;
+  }
+};
+
+/**
+ * Get new Forge cipher object instance according to PBES2 params block.
+ *
+ * The returned cipher instance is already started using the IV
+ * from PBES2 parameter block.
+ *
+ * @param oid the PKCS#5 PBKDF2 OID (in string notation).
+ * @param params the ASN.1 PBES2-params object.
+ * @param password the password to decrypt with.
+ *
+ * @return new cipher object instance.
+ */
+pki.pbe.getCipherForPBES2 = function(oid, params, password) {
+  // get PBE params
+  var capture = {};
+  var errors = [];
+  if(!asn1.validate(params, PBES2AlgorithmsValidator, capture, errors)) {
+    var error = new Error('Cannot read password-based-encryption algorithm ' +
+      'parameters. ASN.1 object is not a supported EncryptedPrivateKeyInfo.');
+    error.errors = errors;
+    throw error;
+  }
+
+  // check oids
+  oid = asn1.derToOid(capture.kdfOid);
+  if(oid !== pki.oids['pkcs5PBKDF2']) {
+    var error = new Error('Cannot read encrypted private key. ' +
+      'Unsupported key derivation function OID.');
+    error.oid = oid;
+    error.supportedOids = ['pkcs5PBKDF2'];
+    throw error;
+  }
+  oid = asn1.derToOid(capture.encOid);
+  if(oid !== pki.oids['aes128-CBC'] &&
+    oid !== pki.oids['aes192-CBC'] &&
+    oid !== pki.oids['aes256-CBC'] &&
+    oid !== pki.oids['des-EDE3-CBC'] &&
+    oid !== pki.oids['desCBC']) {
+    var error = new Error('Cannot read encrypted private key. ' +
+      'Unsupported encryption scheme OID.');
+    error.oid = oid;
+    error.supportedOids = [
+      'aes128-CBC', 'aes192-CBC', 'aes256-CBC', 'des-EDE3-CBC', 'desCBC'];
+    throw error;
+  }
+
+  // set PBE params
+  var salt = capture.kdfSalt;
+  var count = forge.util.createBuffer(capture.kdfIterationCount);
+  count = count.getInt(count.length() << 3);
+  var dkLen;
+  var cipherFn;
+  switch(pki.oids[oid]) {
+  case 'aes128-CBC':
+    dkLen = 16;
+    cipherFn = forge.aes.createDecryptionCipher;
+    break;
+  case 'aes192-CBC':
+    dkLen = 24;
+    cipherFn = forge.aes.createDecryptionCipher;
+    break;
+  case 'aes256-CBC':
+    dkLen = 32;
+    cipherFn = forge.aes.createDecryptionCipher;
+    break;
+  case 'des-EDE3-CBC':
+    dkLen = 24;
+    cipherFn = forge.des.createDecryptionCipher;
+    break;
+  case 'desCBC':
+    dkLen = 8;
+    cipherFn = forge.des.createDecryptionCipher;
+    break;
+  }
+
+  // decrypt private key using pbe SHA-1 and AES/DES
+  var dk = forge.pkcs5.pbkdf2(password, salt, count, dkLen);
+  var iv = capture.encIv;
+  var cipher = cipherFn(dk);
+  cipher.start(iv);
+
+  return cipher;
+};
+
+/**
+ * Get new Forge cipher object instance for PKCS#12 PBE.
+ *
+ * The returned cipher instance is already started using the key & IV
+ * derived from the provided password and PKCS#12 PBE salt.
+ *
+ * @param oid The PKCS#12 PBE OID (in string notation).
+ * @param params The ASN.1 PKCS#12 PBE-params object.
+ * @param password The password to decrypt with.
+ *
+ * @return the new cipher object instance.
+ */
+pki.pbe.getCipherForPKCS12PBE = function(oid, params, password) {
+  // get PBE params
+  var capture = {};
+  var errors = [];
+  if(!asn1.validate(params, pkcs12PbeParamsValidator, capture, errors)) {
+    var error = new Error('Cannot read password-based-encryption algorithm ' +
+      'parameters. ASN.1 object is not a supported EncryptedPrivateKeyInfo.');
+    error.errors = errors;
+    throw error;
+  }
+
+  var salt = forge.util.createBuffer(capture.salt);
+  var count = forge.util.createBuffer(capture.iterations);
+  count = count.getInt(count.length() << 3);
+
+  var dkLen, dIvLen, cipherFn;
+  switch(oid) {
+    case pki.oids['pbeWithSHAAnd3-KeyTripleDES-CBC']:
+      dkLen = 24;
+      dIvLen = 8;
+      cipherFn = forge.des.startDecrypting;
+      break;
+
+    case pki.oids['pbewithSHAAnd40BitRC2-CBC']:
+      dkLen = 5;
+      dIvLen = 8;
+      cipherFn = function(key, iv) {
+        var cipher = forge.rc2.createDecryptionCipher(key, 40);
+        cipher.start(iv, null);
+        return cipher;
+      };
+      break;
+
+    default:
+      var error = new Error('Cannot read PKCS #12 PBE data block. Unsupported OID.');
+      error.oid = oid;
+      throw error;
+  }
+
+  var key = pki.pbe.generatePkcs12Key(password, salt, 1, count, dkLen);
+  var iv = pki.pbe.generatePkcs12Key(password, salt, 2, count, dIvLen);
+
+  return cipherFn(key, iv);
+};
+
+/**
+ * OpenSSL's legacy key derivation function.
+ *
+ * See: http://www.openssl.org/docs/crypto/EVP_BytesToKey.html
+ *
+ * @param password the password to derive the key from.
+ * @param salt the salt to use, null for none.
+ * @param dkLen the number of bytes needed for the derived key.
+ * @param [options] the options to use:
+ *          [md] an optional message digest object to use.
+ */
+pki.pbe.opensslDeriveBytes = function(password, salt, dkLen, md) {
+  if(typeof md === 'undefined' || md === null) {
+    md = forge.md.md5.create();
+  }
+  if(salt === null) {
+    salt = '';
+  }
+  var digests = [hash(md, password + salt)];
+  for(var length = 16, i = 1; length < dkLen; ++i, length += 16) {
+    digests.push(hash(md, digests[i - 1] + password + salt));
+  }
+  return digests.join('').substr(0, dkLen);
+};
+
+function hash(md, bytes) {
+  return md.start().update(bytes).digest().getBytes();
+}
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'pbe';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define([
+  'require',
+  'module',
+  './aes',
+  './asn1',
+  './des',
+  './md',
+  './oids',
+  './pem',
+  './pbkdf2',
+  './random',
+  './rc2',
+  './rsa',
+  './util'
+], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/pbkdf2.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/pbkdf2.js
new file mode 100644
index 0000000..d983610
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/pbkdf2.js
@@ -0,0 +1,214 @@
+/**
+ * Password-Based Key-Derivation Function #2 implementation.
+ *
+ * See RFC 2898 for details.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2013 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+var pkcs5 = forge.pkcs5 = forge.pkcs5 || {};
+
+/**
+ * Derives a key from a password.
+ *
+ * @param p the password as a string of bytes.
+ * @param s the salt as a string of bytes.
+ * @param c the iteration count, a positive integer.
+ * @param dkLen the intended length, in bytes, of the derived key,
+ *          (max: 2^32 - 1) * hash length of the PRF.
+ * @param md the message digest to use in the PRF, defaults to SHA-1.
+ *
+ * @return the derived key, as a string of bytes.
+ */
+forge.pbkdf2 = pkcs5.pbkdf2 = function(p, s, c, dkLen, md, callback) {
+  if(typeof md === 'function') {
+    callback = md;
+    md = null;
+  }
+  // default prf to SHA-1
+  if(typeof md === 'undefined' || md === null) {
+    md = forge.md.sha1.create();
+  }
+
+  var hLen = md.digestLength;
+
+  /* 1. If dkLen > (2^32 - 1) * hLen, output "derived key too long" and
+    stop. */
+  if(dkLen > (0xFFFFFFFF * hLen)) {
+    var err = new Error('Derived key is too long.');
+    if(callback) {
+      return callback(err);
+    }
+    throw err;
+  }
+
+  /* 2. Let len be the number of hLen-octet blocks in the derived key,
+    rounding up, and let r be the number of octets in the last
+    block:
+
+    len = CEIL(dkLen / hLen),
+    r = dkLen - (len - 1) * hLen. */
+  var len = Math.ceil(dkLen / hLen);
+  var r = dkLen - (len - 1) * hLen;
+
+  /* 3. For each block of the derived key apply the function F defined
+    below to the password P, the salt S, the iteration count c, and
+    the block index to compute the block:
+
+    T_1 = F(P, S, c, 1),
+    T_2 = F(P, S, c, 2),
+    ...
+    T_len = F(P, S, c, len),
+
+    where the function F is defined as the exclusive-or sum of the
+    first c iterates of the underlying pseudorandom function PRF
+    applied to the password P and the concatenation of the salt S
+    and the block index i:
+
+    F(P, S, c, i) = u_1 XOR u_2 XOR ... XOR u_c
+
+    where
+
+    u_1 = PRF(P, S || INT(i)),
+    u_2 = PRF(P, u_1),
+    ...
+    u_c = PRF(P, u_{c-1}).
+
+    Here, INT(i) is a four-octet encoding of the integer i, most
+    significant octet first. */
+  var prf = forge.hmac.create();
+  prf.start(md, p);
+  var dk = '';
+  var xor, u_c, u_c1;
+
+  // sync version
+  if(!callback) {
+    for(var i = 1; i <= len; ++i) {
+      // PRF(P, S || INT(i)) (first iteration)
+      prf.start(null, null);
+      prf.update(s);
+      prf.update(forge.util.int32ToBytes(i));
+      xor = u_c1 = prf.digest().getBytes();
+
+      // PRF(P, u_{c-1}) (other iterations)
+      for(var j = 2; j <= c; ++j) {
+        prf.start(null, null);
+        prf.update(u_c1);
+        u_c = prf.digest().getBytes();
+        // F(p, s, c, i)
+        xor = forge.util.xorBytes(xor, u_c, hLen);
+        u_c1 = u_c;
+      }
+
+      /* 4. Concatenate the blocks and extract the first dkLen octets to
+        produce a derived key DK:
+
+        DK = T_1 || T_2 ||  ...  || T_len<0..r-1> */
+      dk += (i < len) ? xor : xor.substr(0, r);
+    }
+    /* 5. Output the derived key DK. */
+    return dk;
+  }
+
+  // async version
+  var i = 1, j;
+  function outer() {
+    if(i > len) {
+      // done
+      return callback(null, dk);
+    }
+
+    // PRF(P, S || INT(i)) (first iteration)
+    prf.start(null, null);
+    prf.update(s);
+    prf.update(forge.util.int32ToBytes(i));
+    xor = u_c1 = prf.digest().getBytes();
+
+    // PRF(P, u_{c-1}) (other iterations)
+    j = 2;
+    inner();
+  }
+
+  function inner() {
+    if(j <= c) {
+      prf.start(null, null);
+      prf.update(u_c1);
+      u_c = prf.digest().getBytes();
+      // F(p, s, c, i)
+      xor = forge.util.xorBytes(xor, u_c, hLen);
+      u_c1 = u_c;
+      ++j;
+      return forge.util.setImmediate(inner);
+    }
+
+    /* 4. Concatenate the blocks and extract the first dkLen octets to
+      produce a derived key DK:
+
+      DK = T_1 || T_2 ||  ...  || T_len<0..r-1> */
+    dk += (i < len) ? xor : xor.substr(0, r);
+
+    ++i;
+    outer();
+  }
+
+  outer();
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'pbkdf2';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './hmac', './md', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/pem.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/pem.js
new file mode 100644
index 0000000..e3085dc
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/pem.js
@@ -0,0 +1,285 @@
+/**
+ * Javascript implementation of basic PEM (Privacy Enhanced Mail) algorithms.
+ *
+ * See: RFC 1421.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2013-2014 Digital Bazaar, Inc.
+ *
+ * A Forge PEM object has the following fields:
+ *
+ * type: identifies the type of message (eg: "RSA PRIVATE KEY").
+ *
+ * procType: identifies the type of processing performed on the message,
+ *   it has two subfields: version and type, eg: 4,ENCRYPTED.
+ *
+ * contentDomain: identifies the type of content in the message, typically
+ *   only uses the value: "RFC822".
+ *
+ * dekInfo: identifies the message encryption algorithm and mode and includes
+ *   any parameters for the algorithm, it has two subfields: algorithm and
+ *   parameters, eg: DES-CBC,F8143EDE5960C597.
+ *
+ * headers: contains all other PEM encapsulated headers -- where order is
+ *   significant (for pairing data like recipient ID + key info).
+ *
+ * body: the binary-encoded body.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+// shortcut for pem API
+var pem = forge.pem = forge.pem || {};
+
+/**
+ * Encodes (serializes) the given PEM object.
+ *
+ * @param msg the PEM message object to encode.
+ * @param options the options to use:
+ *          maxline the maximum characters per line for the body, (default: 64).
+ *
+ * @return the PEM-formatted string.
+ */
+pem.encode = function(msg, options) {
+  options = options || {};
+  var rval = '-----BEGIN ' + msg.type + '-----\r\n';
+
+  // encode special headers
+  var header;
+  if(msg.procType) {
+    header = {
+      name: 'Proc-Type',
+      values: [String(msg.procType.version), msg.procType.type]
+    };
+    rval += foldHeader(header);
+  }
+  if(msg.contentDomain) {
+    header = {name: 'Content-Domain', values: [msg.contentDomain]};
+    rval += foldHeader(header);
+  }
+  if(msg.dekInfo) {
+    header = {name: 'DEK-Info', values: [msg.dekInfo.algorithm]};
+    if(msg.dekInfo.parameters) {
+      header.values.push(msg.dekInfo.parameters);
+    }
+    rval += foldHeader(header);
+  }
+
+  if(msg.headers) {
+    // encode all other headers
+    for(var i = 0; i < msg.headers.length; ++i) {
+      rval += foldHeader(msg.headers[i]);
+    }
+  }
+
+  // terminate header
+  if(msg.procType) {
+    rval += '\r\n';
+  }
+
+  // add body
+  rval += forge.util.encode64(msg.body, options.maxline || 64) + '\r\n';
+
+  rval += '-----END ' + msg.type + '-----\r\n';
+  return rval;
+};
+
+/**
+ * Decodes (deserializes) all PEM messages found in the given string.
+ *
+ * @param str the PEM-formatted string to decode.
+ *
+ * @return the PEM message objects in an array.
+ */
+pem.decode = function(str) {
+  var rval = [];
+
+  // split string into PEM messages (be lenient w/EOF on BEGIN line)
+  var rMessage = /\s*-----BEGIN ([A-Z0-9- ]+)-----\r?\n?([\x21-\x7e\s]+?(?:\r?\n\r?\n))?([:A-Za-z0-9+\/=\s]+?)-----END \1-----/g;
+  var rHeader = /([\x21-\x7e]+):\s*([\x21-\x7e\s^:]+)/;
+  var rCRLF = /\r?\n/;
+  var match;
+  while(true) {
+    match = rMessage.exec(str);
+    if(!match) {
+      break;
+    }
+
+    var msg = {
+      type: match[1],
+      procType: null,
+      contentDomain: null,
+      dekInfo: null,
+      headers: [],
+      body: forge.util.decode64(match[3])
+    };
+    rval.push(msg);
+
+    // no headers
+    if(!match[2]) {
+      continue;
+    }
+
+    // parse headers
+    var lines = match[2].split(rCRLF);
+    var li = 0;
+    while(match && li < lines.length) {
+      // get line, trim any rhs whitespace
+      var line = lines[li].replace(/\s+$/, '');
+
+      // RFC2822 unfold any following folded lines
+      for(var nl = li + 1; nl < lines.length; ++nl) {
+        var next = lines[nl];
+        if(!/\s/.test(next[0])) {
+          break;
+        }
+        line += next;
+        li = nl;
+      }
+
+      // parse header
+      match = line.match(rHeader);
+      if(match) {
+        var header = {name: match[1], values: []};
+        var values = match[2].split(',');
+        for(var vi = 0; vi < values.length; ++vi) {
+          header.values.push(ltrim(values[vi]));
+        }
+
+        // Proc-Type must be the first header
+        if(!msg.procType) {
+          if(header.name !== 'Proc-Type') {
+            throw new Error('Invalid PEM formatted message. The first ' +
+              'encapsulated header must be "Proc-Type".');
+          } else if(header.values.length !== 2) {
+            throw new Error('Invalid PEM formatted message. The "Proc-Type" ' +
+              'header must have two subfields.');
+          }
+          msg.procType = {version: values[0], type: values[1]};
+        } else if(!msg.contentDomain && header.name === 'Content-Domain') {
+          // special-case Content-Domain
+          msg.contentDomain = values[0] || '';
+        } else if(!msg.dekInfo && header.name === 'DEK-Info') {
+          // special-case DEK-Info
+          if(header.values.length === 0) {
+            throw new Error('Invalid PEM formatted message. The "DEK-Info" ' +
+              'header must have at least one subfield.');
+          }
+          msg.dekInfo = {algorithm: values[0], parameters: values[1] || null};
+        } else {
+          msg.headers.push(header);
+        }
+      }
+
+      ++li;
+    }
+
+    if(msg.procType === 'ENCRYPTED' && !msg.dekInfo) {
+      throw new Error('Invalid PEM formatted message. The "DEK-Info" ' +
+        'header must be present if "Proc-Type" is "ENCRYPTED".');
+    }
+  }
+
+  if(rval.length === 0) {
+    throw new Error('Invalid PEM formatted message.');
+  }
+
+  return rval;
+};
+
+function foldHeader(header) {
+  var rval = header.name + ': ';
+
+  // ensure values with CRLF are folded
+  var values = [];
+  var insertSpace = function(match, $1) {
+    return ' ' + $1;
+  };
+  for(var i = 0; i < header.values.length; ++i) {
+    values.push(header.values[i].replace(/^(\S+\r\n)/, insertSpace));
+  }
+  rval += values.join(',') + '\r\n';
+
+  // do folding
+  var length = 0;
+  var candidate = -1;
+  for(var i = 0; i < rval.length; ++i, ++length) {
+    if(length > 65 && candidate !== -1) {
+      var insert = rval[candidate];
+      if(insert === ',') {
+        ++candidate;
+        rval = rval.substr(0, candidate) + '\r\n ' + rval.substr(candidate);
+      } else {
+        rval = rval.substr(0, candidate) +
+          '\r\n' + insert + rval.substr(candidate + 1);
+      }
+      length = (i - candidate - 1);
+      candidate = -1;
+      ++i;
+    } else if(rval[i] === ' ' || rval[i] === '\t' || rval[i] === ',') {
+      candidate = i;
+    }
+  }
+
+  return rval;
+}
+
+function ltrim(str) {
+  return str.replace(/^\s+/, '');
+}
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'pem';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/pkcs1.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/pkcs1.js
new file mode 100644
index 0000000..7bf734c
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/pkcs1.js
@@ -0,0 +1,329 @@
+/**
+ * Partial implementation of PKCS#1 v2.2: RSA-OEAP
+ *
+ * Modified but based on the following MIT and BSD licensed code:
+ *
+ * https://github.com/kjur/jsjws/blob/master/rsa.js:
+ *
+ * The 'jsjws'(JSON Web Signature JavaScript Library) License
+ *
+ * Copyright (c) 2012 Kenji Urushima
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * http://webrsa.cvs.sourceforge.net/viewvc/webrsa/Client/RSAES-OAEP.js?content-type=text%2Fplain:
+ *
+ * RSAES-OAEP.js
+ * $Id: RSAES-OAEP.js,v 1.1.1.1 2003/03/19 15:37:20 ellispritchard Exp $
+ * JavaScript Implementation of PKCS #1 v2.1 RSA CRYPTOGRAPHY STANDARD (RSA Laboratories, June 14, 2002)
+ * Copyright (C) Ellis Pritchard, Guardian Unlimited 2003.
+ * Contact: ellis@nukinetics.com
+ * Distributed under the BSD License.
+ *
+ * Official documentation: http://www.rsa.com/rsalabs/node.asp?id=2125
+ *
+ * @author Evan Jones (http://evanjones.ca/)
+ * @author Dave Longley
+ *
+ * Copyright (c) 2013-2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+// shortcut for PKCS#1 API
+var pkcs1 = forge.pkcs1 = forge.pkcs1 || {};
+
+/**
+ * Encode the given RSAES-OAEP message (M) using key, with optional label (L)
+ * and seed.
+ *
+ * This method does not perform RSA encryption, it only encodes the message
+ * using RSAES-OAEP.
+ *
+ * @param key the RSA key to use.
+ * @param message the message to encode.
+ * @param options the options to use:
+ *          label an optional label to use.
+ *          seed the seed to use.
+ *          md the message digest object to use, undefined for SHA-1.
+ *          mgf1 optional mgf1 parameters:
+ *            md the message digest object to use for MGF1.
+ *
+ * @return the encoded message bytes.
+ */
+pkcs1.encode_rsa_oaep = function(key, message, options) {
+  // parse arguments
+  var label;
+  var seed;
+  var md;
+  var mgf1Md;
+  // legacy args (label, seed, md)
+  if(typeof options === 'string') {
+    label = options;
+    seed = arguments[3] || undefined;
+    md = arguments[4] || undefined;
+  } else if(options) {
+    label = options.label || undefined;
+    seed = options.seed || undefined;
+    md = options.md || undefined;
+    if(options.mgf1 && options.mgf1.md) {
+      mgf1Md = options.mgf1.md;
+    }
+  }
+
+  // default OAEP to SHA-1 message digest
+  if(!md) {
+    md = forge.md.sha1.create();
+  } else {
+    md.start();
+  }
+
+  // default MGF-1 to same as OAEP
+  if(!mgf1Md) {
+    mgf1Md = md;
+  }
+
+  // compute length in bytes and check output
+  var keyLength = Math.ceil(key.n.bitLength() / 8);
+  var maxLength = keyLength - 2 * md.digestLength - 2;
+  if(message.length > maxLength) {
+    var error = new Error('RSAES-OAEP input message length is too long.');
+    error.length = message.length;
+    error.maxLength = maxLength;
+    throw error;
+  }
+
+  if(!label) {
+    label = '';
+  }
+  md.update(label, 'raw');
+  var lHash = md.digest();
+
+  var PS = '';
+  var PS_length = maxLength - message.length;
+  for (var i = 0; i < PS_length; i++) {
+    PS += '\x00';
+  }
+
+  var DB = lHash.getBytes() + PS + '\x01' + message;
+
+  if(!seed) {
+    seed = forge.random.getBytes(md.digestLength);
+  } else if(seed.length !== md.digestLength) {
+    var error = new Error('Invalid RSAES-OAEP seed. The seed length must ' +
+      'match the digest length.')
+    error.seedLength = seed.length;
+    error.digestLength = md.digestLength;
+    throw error;
+  }
+
+  var dbMask = rsa_mgf1(seed, keyLength - md.digestLength - 1, mgf1Md);
+  var maskedDB = forge.util.xorBytes(DB, dbMask, DB.length);
+
+  var seedMask = rsa_mgf1(maskedDB, md.digestLength, mgf1Md);
+  var maskedSeed = forge.util.xorBytes(seed, seedMask, seed.length);
+
+  // return encoded message
+  return '\x00' + maskedSeed + maskedDB;
+};
+
+/**
+ * Decode the given RSAES-OAEP encoded message (EM) using key, with optional
+ * label (L).
+ *
+ * This method does not perform RSA decryption, it only decodes the message
+ * using RSAES-OAEP.
+ *
+ * @param key the RSA key to use.
+ * @param em the encoded message to decode.
+ * @param options the options to use:
+ *          label an optional label to use.
+ *          md the message digest object to use for OAEP, undefined for SHA-1.
+ *          mgf1 optional mgf1 parameters:
+ *            md the message digest object to use for MGF1.
+ *
+ * @return the decoded message bytes.
+ */
+pkcs1.decode_rsa_oaep = function(key, em, options) {
+  // parse args
+  var label;
+  var md;
+  var mgf1Md;
+  // legacy args
+  if(typeof options === 'string') {
+    label = options;
+    md = arguments[3] || undefined;
+  } else if(options) {
+    label = options.label || undefined;
+    md = options.md || undefined;
+    if(options.mgf1 && options.mgf1.md) {
+      mgf1Md = options.mgf1.md;
+    }
+  }
+
+  // compute length in bytes
+  var keyLength = Math.ceil(key.n.bitLength() / 8);
+
+  if(em.length !== keyLength) {
+    var error = new Error('RSAES-OAEP encoded message length is invalid.');
+    error.length = em.length;
+    error.expectedLength = keyLength;
+    throw error;
+  }
+
+  // default OAEP to SHA-1 message digest
+  if(md === undefined) {
+    md = forge.md.sha1.create();
+  } else {
+    md.start();
+  }
+
+  // default MGF-1 to same as OAEP
+  if(!mgf1Md) {
+    mgf1Md = md;
+  }
+
+  if(keyLength < 2 * md.digestLength + 2) {
+    throw new Error('RSAES-OAEP key is too short for the hash function.');
+  }
+
+  if(!label) {
+    label = '';
+  }
+  md.update(label, 'raw');
+  var lHash = md.digest().getBytes();
+
+  // split the message into its parts
+  var y = em.charAt(0);
+  var maskedSeed = em.substring(1, md.digestLength + 1);
+  var maskedDB = em.substring(1 + md.digestLength);
+
+  var seedMask = rsa_mgf1(maskedDB, md.digestLength, mgf1Md);
+  var seed = forge.util.xorBytes(maskedSeed, seedMask, maskedSeed.length);
+
+  var dbMask = rsa_mgf1(seed, keyLength - md.digestLength - 1, mgf1Md);
+  var db = forge.util.xorBytes(maskedDB, dbMask, maskedDB.length);
+
+  var lHashPrime = db.substring(0, md.digestLength);
+
+  // constant time check that all values match what is expected
+  var error = (y !== '\x00');
+
+  // constant time check lHash vs lHashPrime
+  for(var i = 0; i < md.digestLength; ++i) {
+    error |= (lHash.charAt(i) !== lHashPrime.charAt(i));
+  }
+
+  // "constant time" find the 0x1 byte separating the padding (zeros) from the
+  // message
+  // TODO: It must be possible to do this in a better/smarter way?
+  var in_ps = 1;
+  var index = md.digestLength;
+  for(var j = md.digestLength; j < db.length; j++) {
+    var code = db.charCodeAt(j);
+
+    var is_0 = (code & 0x1) ^ 0x1;
+
+    // non-zero if not 0 or 1 in the ps section
+    var error_mask = in_ps ? 0xfffe : 0x0000;
+    error |= (code & error_mask);
+
+    // latch in_ps to zero after we find 0x1
+    in_ps = in_ps & is_0;
+    index += in_ps;
+  }
+
+  if(error || db.charCodeAt(index) !== 0x1) {
+    throw new Error('Invalid RSAES-OAEP padding.');
+  }
+
+  return db.substring(index + 1);
+};
+
+function rsa_mgf1(seed, maskLength, hash) {
+  // default to SHA-1 message digest
+  if(!hash) {
+    hash = forge.md.sha1.create();
+  }
+  var t = '';
+  var count = Math.ceil(maskLength / hash.digestLength);
+  for(var i = 0; i < count; ++i) {
+    var c = String.fromCharCode(
+      (i >> 24) & 0xFF, (i >> 16) & 0xFF, (i >> 8) & 0xFF, i & 0xFF);
+    hash.start();
+    hash.update(seed + c);
+    t += hash.digest().getBytes();
+  }
+  return t.substring(0, maskLength);
+}
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'pkcs1';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './util', './random', './sha1'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/pkcs12.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/pkcs12.js
new file mode 100644
index 0000000..19f1c55
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/pkcs12.js
@@ -0,0 +1,1121 @@
+/**
+ * Javascript implementation of PKCS#12.
+ *
+ * @author Dave Longley
+ * @author Stefan Siegl <stesie@brokenpipe.de>
+ *
+ * Copyright (c) 2010-2014 Digital Bazaar, Inc.
+ * Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
+ *
+ * The ASN.1 representation of PKCS#12 is as follows
+ * (see ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-12/pkcs-12-tc1.pdf for details)
+ *
+ * PFX ::= SEQUENCE {
+ *   version  INTEGER {v3(3)}(v3,...),
+ *   authSafe ContentInfo,
+ *   macData  MacData OPTIONAL
+ * }
+ *
+ * MacData ::= SEQUENCE {
+ *   mac DigestInfo,
+ *   macSalt OCTET STRING,
+ *   iterations INTEGER DEFAULT 1
+ * }
+ * Note: The iterations default is for historical reasons and its use is
+ * deprecated. A higher value, like 1024, is recommended.
+ *
+ * DigestInfo is defined in PKCS#7 as follows:
+ *
+ * DigestInfo ::= SEQUENCE {
+ *   digestAlgorithm DigestAlgorithmIdentifier,
+ *   digest Digest
+ * }
+ *
+ * DigestAlgorithmIdentifier ::= AlgorithmIdentifier
+ *
+ * The AlgorithmIdentifier contains an Object Identifier (OID) and parameters
+ * for the algorithm, if any. In the case of SHA1 there is none.
+ *
+ * AlgorithmIdentifer ::= SEQUENCE {
+ *    algorithm OBJECT IDENTIFIER,
+ *    parameters ANY DEFINED BY algorithm OPTIONAL
+ * }
+ *
+ * Digest ::= OCTET STRING
+ *
+ *
+ * ContentInfo ::= SEQUENCE {
+ *   contentType ContentType,
+ *   content     [0] EXPLICIT ANY DEFINED BY contentType OPTIONAL
+ * }
+ *
+ * ContentType ::= OBJECT IDENTIFIER
+ *
+ * AuthenticatedSafe ::= SEQUENCE OF ContentInfo
+ * -- Data if unencrypted
+ * -- EncryptedData if password-encrypted
+ * -- EnvelopedData if public key-encrypted
+ *
+ *
+ * SafeContents ::= SEQUENCE OF SafeBag
+ *
+ * SafeBag ::= SEQUENCE {
+ *   bagId     BAG-TYPE.&id ({PKCS12BagSet})
+ *   bagValue  [0] EXPLICIT BAG-TYPE.&Type({PKCS12BagSet}{@bagId}),
+ *   bagAttributes SET OF PKCS12Attribute OPTIONAL
+ * }
+ *
+ * PKCS12Attribute ::= SEQUENCE {
+ *   attrId ATTRIBUTE.&id ({PKCS12AttrSet}),
+ *   attrValues SET OF ATTRIBUTE.&Type ({PKCS12AttrSet}{@attrId})
+ * } -- This type is compatible with the X.500 type ’Attribute’
+ *
+ * PKCS12AttrSet ATTRIBUTE ::= {
+ *   friendlyName | -- from PKCS #9
+ *   localKeyId, -- from PKCS #9
+ *   ... -- Other attributes are allowed
+ * }
+ *
+ * CertBag ::= SEQUENCE {
+ *   certId    BAG-TYPE.&id   ({CertTypes}),
+ *   certValue [0] EXPLICIT BAG-TYPE.&Type ({CertTypes}{@certId})
+ * }
+ *
+ * x509Certificate BAG-TYPE ::= {OCTET STRING IDENTIFIED BY {certTypes 1}}
+ *   -- DER-encoded X.509 certificate stored in OCTET STRING
+ *
+ * sdsiCertificate BAG-TYPE ::= {IA5String IDENTIFIED BY {certTypes 2}}
+ * -- Base64-encoded SDSI certificate stored in IA5String
+ *
+ * CertTypes BAG-TYPE ::= {
+ *   x509Certificate |
+ *   sdsiCertificate,
+ *   ... -- For future extensions
+ * }
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+// shortcut for asn.1 & PKI API
+var asn1 = forge.asn1;
+var pki = forge.pki;
+
+// shortcut for PKCS#12 API
+var p12 = forge.pkcs12 = forge.pkcs12 || {};
+
+var contentInfoValidator = {
+  name: 'ContentInfo',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,  // a ContentInfo
+  constructed: true,
+  value: [{
+    name: 'ContentInfo.contentType',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.OID,
+    constructed: false,
+    capture: 'contentType'
+  }, {
+    name: 'ContentInfo.content',
+    tagClass: asn1.Class.CONTEXT_SPECIFIC,
+    constructed: true,
+    captureAsn1: 'content'
+  }]
+};
+
+var pfxValidator = {
+  name: 'PFX',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'PFX.version',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'version'
+  },
+  contentInfoValidator, {
+    name: 'PFX.macData',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true,
+    optional: true,
+    captureAsn1: 'mac',
+    value: [{
+      name: 'PFX.macData.mac',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.SEQUENCE,  // DigestInfo
+      constructed: true,
+      value: [{
+        name: 'PFX.macData.mac.digestAlgorithm',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.SEQUENCE,  // DigestAlgorithmIdentifier
+        constructed: true,
+        value: [{
+          name: 'PFX.macData.mac.digestAlgorithm.algorithm',
+          tagClass: asn1.Class.UNIVERSAL,
+          type: asn1.Type.OID,
+          constructed: false,
+          capture: 'macAlgorithm'
+        }, {
+          name: 'PFX.macData.mac.digestAlgorithm.parameters',
+          tagClass: asn1.Class.UNIVERSAL,
+          captureAsn1: 'macAlgorithmParameters'
+        }]
+      }, {
+        name: 'PFX.macData.mac.digest',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.OCTETSTRING,
+        constructed: false,
+        capture: 'macDigest'
+      }]
+    }, {
+      name: 'PFX.macData.macSalt',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.OCTETSTRING,
+      constructed: false,
+      capture: 'macSalt'
+    }, {
+      name: 'PFX.macData.iterations',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.INTEGER,
+      constructed: false,
+      optional: true,
+      capture: 'macIterations'
+    }]
+  }]
+};
+
+var safeBagValidator = {
+  name: 'SafeBag',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'SafeBag.bagId',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.OID,
+    constructed: false,
+    capture: 'bagId'
+  }, {
+    name: 'SafeBag.bagValue',
+    tagClass: asn1.Class.CONTEXT_SPECIFIC,
+    constructed: true,
+    captureAsn1: 'bagValue'
+  }, {
+    name: 'SafeBag.bagAttributes',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SET,
+    constructed: true,
+    optional: true,
+    capture: 'bagAttributes'
+  }]
+};
+
+var attributeValidator = {
+  name: 'Attribute',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'Attribute.attrId',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.OID,
+    constructed: false,
+    capture: 'oid'
+  }, {
+    name: 'Attribute.attrValues',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SET,
+    constructed: true,
+    capture: 'values'
+  }]
+};
+
+var certBagValidator = {
+  name: 'CertBag',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'CertBag.certId',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.OID,
+    constructed: false,
+    capture: 'certId'
+  }, {
+    name: 'CertBag.certValue',
+    tagClass: asn1.Class.CONTEXT_SPECIFIC,
+    constructed: true,
+    /* So far we only support X.509 certificates (which are wrapped in
+       an OCTET STRING, hence hard code that here). */
+    value: [{
+      name: 'CertBag.certValue[0]',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Class.OCTETSTRING,
+      constructed: false,
+      capture: 'cert'
+    }]
+  }]
+};
+
+/**
+ * Search SafeContents structure for bags with matching attributes.
+ *
+ * The search can optionally be narrowed by a certain bag type.
+ *
+ * @param safeContents the SafeContents structure to search in.
+ * @param attrName the name of the attribute to compare against.
+ * @param attrValue the attribute value to search for.
+ * @param [bagType] bag type to narrow search by.
+ *
+ * @return an array of matching bags.
+ */
+function _getBagsByAttribute(safeContents, attrName, attrValue, bagType) {
+  var result = [];
+
+  for(var i = 0; i < safeContents.length; i ++) {
+    for(var j = 0; j < safeContents[i].safeBags.length; j ++) {
+      var bag = safeContents[i].safeBags[j];
+      if(bagType !== undefined && bag.type !== bagType) {
+        continue;
+      }
+      // only filter by bag type, no attribute specified
+      if(attrName === null) {
+        result.push(bag);
+        continue;
+      }
+      if(bag.attributes[attrName] !== undefined &&
+        bag.attributes[attrName].indexOf(attrValue) >= 0) {
+        result.push(bag);
+      }
+    }
+  }
+
+  return result;
+}
+
+/**
+ * Converts a PKCS#12 PFX in ASN.1 notation into a PFX object.
+ *
+ * @param obj The PKCS#12 PFX in ASN.1 notation.
+ * @param strict true to use strict DER decoding, false not to (default: true).
+ * @param {String} password Password to decrypt with (optional).
+ *
+ * @return PKCS#12 PFX object.
+ */
+p12.pkcs12FromAsn1 = function(obj, strict, password) {
+  // handle args
+  if(typeof strict === 'string') {
+    password = strict;
+    strict = true;
+  } else if(strict === undefined) {
+    strict = true;
+  }
+
+  // validate PFX and capture data
+  var capture = {};
+  var errors = [];
+  if(!asn1.validate(obj, pfxValidator, capture, errors)) {
+    var error = new Error('Cannot read PKCS#12 PFX. ' +
+      'ASN.1 object is not an PKCS#12 PFX.');
+    error.errors = error;
+    throw error;
+  }
+
+  var pfx = {
+    version: capture.version.charCodeAt(0),
+    safeContents: [],
+
+    /**
+     * Gets bags with matching attributes.
+     *
+     * @param filter the attributes to filter by:
+     *          [localKeyId] the localKeyId to search for.
+     *          [localKeyIdHex] the localKeyId in hex to search for.
+     *          [friendlyName] the friendly name to search for.
+     *          [bagType] bag type to narrow each attribute search by.
+     *
+     * @return a map of attribute type to an array of matching bags or, if no
+     *           attribute was given but a bag type, the map key will be the
+     *           bag type.
+     */
+    getBags: function(filter) {
+      var rval = {};
+
+      var localKeyId;
+      if('localKeyId' in filter) {
+        localKeyId = filter.localKeyId;
+      } else if('localKeyIdHex' in filter) {
+        localKeyId = forge.util.hexToBytes(filter.localKeyIdHex);
+      }
+
+      // filter on bagType only
+      if(localKeyId === undefined && !('friendlyName' in filter) &&
+        'bagType' in filter) {
+        rval[filter.bagType] = _getBagsByAttribute(
+          pfx.safeContents, null, null, filter.bagType);
+      }
+
+      if(localKeyId !== undefined) {
+        rval.localKeyId = _getBagsByAttribute(
+          pfx.safeContents, 'localKeyId',
+          localKeyId, filter.bagType);
+      }
+      if('friendlyName' in filter) {
+        rval.friendlyName = _getBagsByAttribute(
+          pfx.safeContents, 'friendlyName',
+          filter.friendlyName, filter.bagType);
+      }
+
+      return rval;
+    },
+
+    /**
+     * DEPRECATED: use getBags() instead.
+     *
+     * Get bags with matching friendlyName attribute.
+     *
+     * @param friendlyName the friendly name to search for.
+     * @param [bagType] bag type to narrow search by.
+     *
+     * @return an array of bags with matching friendlyName attribute.
+     */
+    getBagsByFriendlyName: function(friendlyName, bagType) {
+      return _getBagsByAttribute(
+        pfx.safeContents, 'friendlyName', friendlyName, bagType);
+    },
+
+    /**
+     * DEPRECATED: use getBags() instead.
+     *
+     * Get bags with matching localKeyId attribute.
+     *
+     * @param localKeyId the localKeyId to search for.
+     * @param [bagType] bag type to narrow search by.
+     *
+     * @return an array of bags with matching localKeyId attribute.
+     */
+    getBagsByLocalKeyId: function(localKeyId, bagType) {
+      return _getBagsByAttribute(
+        pfx.safeContents, 'localKeyId', localKeyId, bagType);
+    }
+  };
+
+  if(capture.version.charCodeAt(0) !== 3) {
+    var error = new Error('PKCS#12 PFX of version other than 3 not supported.');
+    error.version = capture.version.charCodeAt(0);
+    throw error;
+  }
+
+  if(asn1.derToOid(capture.contentType) !== pki.oids.data) {
+    var error = new Error('Only PKCS#12 PFX in password integrity mode supported.');
+    error.oid = asn1.derToOid(capture.contentType);
+    throw error;
+  }
+
+  var data = capture.content.value[0];
+  if(data.tagClass !== asn1.Class.UNIVERSAL ||
+     data.type !== asn1.Type.OCTETSTRING) {
+    throw new Error('PKCS#12 authSafe content data is not an OCTET STRING.');
+  }
+  data = _decodePkcs7Data(data);
+
+  // check for MAC
+  if(capture.mac) {
+    var md = null;
+    var macKeyBytes = 0;
+    var macAlgorithm = asn1.derToOid(capture.macAlgorithm);
+    switch(macAlgorithm) {
+    case pki.oids.sha1:
+      md = forge.md.sha1.create();
+      macKeyBytes = 20;
+      break;
+    case pki.oids.sha256:
+      md = forge.md.sha256.create();
+      macKeyBytes = 32;
+      break;
+    case pki.oids.sha384:
+      md = forge.md.sha384.create();
+      macKeyBytes = 48;
+      break;
+    case pki.oids.sha512:
+      md = forge.md.sha512.create();
+      macKeyBytes = 64;
+      break;
+    case pki.oids.md5:
+      md = forge.md.md5.create();
+      macKeyBytes = 16;
+      break;
+    }
+    if(md === null) {
+      throw new Error('PKCS#12 uses unsupported MAC algorithm: ' + macAlgorithm);
+    }
+
+    // verify MAC (iterations default to 1)
+    var macSalt = new forge.util.ByteBuffer(capture.macSalt);
+    var macIterations = (('macIterations' in capture) ?
+      parseInt(forge.util.bytesToHex(capture.macIterations), 16) : 1);
+    var macKey = p12.generateKey(
+      password, macSalt, 3, macIterations, macKeyBytes, md);
+    var mac = forge.hmac.create();
+    mac.start(md, macKey);
+    mac.update(data.value);
+    var macValue = mac.getMac();
+    if(macValue.getBytes() !== capture.macDigest) {
+      throw new Error('PKCS#12 MAC could not be verified. Invalid password?');
+    }
+  }
+
+  _decodeAuthenticatedSafe(pfx, data.value, strict, password);
+  return pfx;
+};
+
+/**
+ * Decodes PKCS#7 Data. PKCS#7 (RFC 2315) defines "Data" as an OCTET STRING,
+ * but it is sometimes an OCTET STRING that is composed/constructed of chunks,
+ * each its own OCTET STRING. This is BER-encoding vs. DER-encoding. This
+ * function transforms this corner-case into the usual simple,
+ * non-composed/constructed OCTET STRING.
+ *
+ * This function may be moved to ASN.1 at some point to better deal with
+ * more BER-encoding issues, should they arise.
+ *
+ * @param data the ASN.1 Data object to transform.
+ */
+function _decodePkcs7Data(data) {
+  // handle special case of "chunked" data content: an octet string composed
+  // of other octet strings
+  if(data.composed || data.constructed) {
+    var value = forge.util.createBuffer();
+    for(var i = 0; i < data.value.length; ++i) {
+      value.putBytes(data.value[i].value);
+    }
+    data.composed = data.constructed = false;
+    data.value = value.getBytes();
+  }
+  return data;
+}
+
+/**
+ * Decode PKCS#12 AuthenticatedSafe (BER encoded) into PFX object.
+ *
+ * The AuthenticatedSafe is a BER-encoded SEQUENCE OF ContentInfo.
+ *
+ * @param pfx The PKCS#12 PFX object to fill.
+ * @param {String} authSafe BER-encoded AuthenticatedSafe.
+ * @param strict true to use strict DER decoding, false not to.
+ * @param {String} password Password to decrypt with (optional).
+ */
+function _decodeAuthenticatedSafe(pfx, authSafe, strict, password) {
+  authSafe = asn1.fromDer(authSafe, strict);  /* actually it's BER encoded */
+
+  if(authSafe.tagClass !== asn1.Class.UNIVERSAL ||
+     authSafe.type !== asn1.Type.SEQUENCE ||
+     authSafe.constructed !== true) {
+    throw new Error('PKCS#12 AuthenticatedSafe expected to be a ' +
+      'SEQUENCE OF ContentInfo');
+  }
+
+  for(var i = 0; i < authSafe.value.length; i ++) {
+    var contentInfo = authSafe.value[i];
+
+    // validate contentInfo and capture data
+    var capture = {};
+    var errors = [];
+    if(!asn1.validate(contentInfo, contentInfoValidator, capture, errors)) {
+      var error = new Error('Cannot read ContentInfo.');
+      error.errors = errors;
+      throw error;
+    }
+
+    var obj = {
+      encrypted: false
+    };
+    var safeContents = null;
+    var data = capture.content.value[0];
+    switch(asn1.derToOid(capture.contentType)) {
+    case pki.oids.data:
+      if(data.tagClass !== asn1.Class.UNIVERSAL ||
+         data.type !== asn1.Type.OCTETSTRING) {
+        throw new Error('PKCS#12 SafeContents Data is not an OCTET STRING.');
+      }
+      safeContents = _decodePkcs7Data(data).value;
+      break;
+    case pki.oids.encryptedData:
+      safeContents = _decryptSafeContents(data, password);
+      obj.encrypted = true;
+      break;
+    default:
+      var error = new Error('Unsupported PKCS#12 contentType.');
+      error.contentType = asn1.derToOid(capture.contentType);
+      throw error;
+    }
+
+    obj.safeBags = _decodeSafeContents(safeContents, strict, password);
+    pfx.safeContents.push(obj);
+  }
+}
+
+/**
+ * Decrypt PKCS#7 EncryptedData structure.
+ *
+ * @param data ASN.1 encoded EncryptedContentInfo object.
+ * @param password The user-provided password.
+ *
+ * @return The decrypted SafeContents (ASN.1 object).
+ */
+function _decryptSafeContents(data, password) {
+  var capture = {};
+  var errors = [];
+  if(!asn1.validate(
+    data, forge.pkcs7.asn1.encryptedDataValidator, capture, errors)) {
+    var error = new Error('Cannot read EncryptedContentInfo.');
+    error.errors = errors;
+    throw error;
+  }
+
+  var oid = asn1.derToOid(capture.contentType);
+  if(oid !== pki.oids.data) {
+    var error = new Error(
+      'PKCS#12 EncryptedContentInfo ContentType is not Data.');
+    error.oid = oid;
+    throw error;
+  }
+
+  // get cipher
+  oid = asn1.derToOid(capture.encAlgorithm);
+  var cipher = pki.pbe.getCipher(oid, capture.encParameter, password);
+
+  // get encrypted data
+  var encryptedContentAsn1 = _decodePkcs7Data(capture.encryptedContentAsn1);
+  var encrypted = forge.util.createBuffer(encryptedContentAsn1.value);
+
+  cipher.update(encrypted);
+  if(!cipher.finish()) {
+    throw new Error('Failed to decrypt PKCS#12 SafeContents.');
+  }
+
+  return cipher.output.getBytes();
+}
+
+/**
+ * Decode PKCS#12 SafeContents (BER-encoded) into array of Bag objects.
+ *
+ * The safeContents is a BER-encoded SEQUENCE OF SafeBag.
+ *
+ * @param {String} safeContents BER-encoded safeContents.
+ * @param strict true to use strict DER decoding, false not to.
+ * @param {String} password Password to decrypt with (optional).
+ *
+ * @return {Array} Array of Bag objects.
+ */
+function _decodeSafeContents(safeContents, strict, password) {
+  // if strict and no safe contents, return empty safes
+  if(!strict && safeContents.length === 0) {
+    return [];
+  }
+
+  // actually it's BER-encoded
+  safeContents = asn1.fromDer(safeContents, strict);
+
+  if(safeContents.tagClass !== asn1.Class.UNIVERSAL ||
+    safeContents.type !== asn1.Type.SEQUENCE ||
+    safeContents.constructed !== true) {
+    throw new Error(
+      'PKCS#12 SafeContents expected to be a SEQUENCE OF SafeBag.');
+  }
+
+  var res = [];
+  for(var i = 0; i < safeContents.value.length; i++) {
+    var safeBag = safeContents.value[i];
+
+    // validate SafeBag and capture data
+    var capture = {};
+    var errors = [];
+    if(!asn1.validate(safeBag, safeBagValidator, capture, errors)) {
+      var error = new Error('Cannot read SafeBag.');
+      error.errors = errors;
+      throw error;
+    }
+
+    /* Create bag object and push to result array. */
+    var bag = {
+      type: asn1.derToOid(capture.bagId),
+      attributes: _decodeBagAttributes(capture.bagAttributes)
+    };
+    res.push(bag);
+
+    var validator, decoder;
+    var bagAsn1 = capture.bagValue.value[0];
+    switch(bag.type) {
+      case pki.oids.pkcs8ShroudedKeyBag:
+        /* bagAsn1 has a EncryptedPrivateKeyInfo, which we need to decrypt.
+           Afterwards we can handle it like a keyBag,
+           which is a PrivateKeyInfo. */
+        bagAsn1 = pki.decryptPrivateKeyInfo(bagAsn1, password);
+        if(bagAsn1 === null) {
+          throw new Error(
+            'Unable to decrypt PKCS#8 ShroudedKeyBag, wrong password?');
+        }
+
+        /* fall through */
+      case pki.oids.keyBag:
+        /* A PKCS#12 keyBag is a simple PrivateKeyInfo as understood by our
+           PKI module, hence we don't have to do validation/capturing here,
+           just pass what we already got. */
+        bag.key = pki.privateKeyFromAsn1(bagAsn1);
+        continue;  /* Nothing more to do. */
+
+      case pki.oids.certBag:
+        /* A PKCS#12 certBag can wrap both X.509 and sdsi certificates.
+           Therefore put the SafeBag content through another validator to
+           capture the fields.  Afterwards check & store the results. */
+        validator = certBagValidator;
+        decoder = function() {
+          if(asn1.derToOid(capture.certId) !== pki.oids.x509Certificate) {
+            var error = new Error(
+              'Unsupported certificate type, only X.509 supported.');
+            error.oid = asn1.derToOid(capture.certId);
+            throw error;
+          }
+
+          // true=produce cert hash
+          bag.cert = pki.certificateFromAsn1(
+            asn1.fromDer(capture.cert, strict), true);
+        };
+        break;
+
+      default:
+        var error = new Error('Unsupported PKCS#12 SafeBag type.');
+        error.oid = bag.type;
+        throw error;
+    }
+
+    /* Validate SafeBag value (i.e. CertBag, etc.) and capture data if needed. */
+    if(validator !== undefined &&
+       !asn1.validate(bagAsn1, validator, capture, errors)) {
+      var error = new Error('Cannot read PKCS#12 ' + validator.name);
+      error.errors = errors;
+      throw error;
+    }
+
+    /* Call decoder function from above to store the results. */
+    decoder();
+  }
+
+  return res;
+}
+
+/**
+ * Decode PKCS#12 SET OF PKCS12Attribute into JavaScript object.
+ *
+ * @param attributes SET OF PKCS12Attribute (ASN.1 object).
+ *
+ * @return the decoded attributes.
+ */
+function _decodeBagAttributes(attributes) {
+  var decodedAttrs = {};
+
+  if(attributes !== undefined) {
+    for(var i = 0; i < attributes.length; ++i) {
+      var capture = {};
+      var errors = [];
+      if(!asn1.validate(attributes[i], attributeValidator, capture, errors)) {
+        var error = new Error('Cannot read PKCS#12 BagAttribute.');
+        error.errors = errors;
+        throw error;
+      }
+
+      var oid = asn1.derToOid(capture.oid);
+      if(pki.oids[oid] === undefined) {
+        // unsupported attribute type, ignore.
+        continue;
+      }
+
+      decodedAttrs[pki.oids[oid]] = [];
+      for(var j = 0; j < capture.values.length; ++j) {
+        decodedAttrs[pki.oids[oid]].push(capture.values[j].value);
+      }
+    }
+  }
+
+  return decodedAttrs;
+}
+
+/**
+ * Wraps a private key and certificate in a PKCS#12 PFX wrapper. If a
+ * password is provided then the private key will be encrypted.
+ *
+ * An entire certificate chain may also be included. To do this, pass
+ * an array for the "cert" parameter where the first certificate is
+ * the one that is paired with the private key and each subsequent one
+ * verifies the previous one. The certificates may be in PEM format or
+ * have been already parsed by Forge.
+ *
+ * @todo implement password-based-encryption for the whole package
+ *
+ * @param key the private key.
+ * @param cert the certificate (may be an array of certificates in order
+ *          to specify a certificate chain).
+ * @param password the password to use, null for none.
+ * @param options:
+ *          algorithm the encryption algorithm to use
+ *            ('aes128', 'aes192', 'aes256', '3des'), defaults to 'aes128'.
+ *          count the iteration count to use.
+ *          saltSize the salt size to use.
+ *          useMac true to include a MAC, false not to, defaults to true.
+ *          localKeyId the local key ID to use, in hex.
+ *          friendlyName the friendly name to use.
+ *          generateLocalKeyId true to generate a random local key ID,
+ *            false not to, defaults to true.
+ *
+ * @return the PKCS#12 PFX ASN.1 object.
+ */
+p12.toPkcs12Asn1 = function(key, cert, password, options) {
+  // set default options
+  options = options || {};
+  options.saltSize = options.saltSize || 8;
+  options.count = options.count || 2048;
+  options.algorithm = options.algorithm || options.encAlgorithm || 'aes128';
+  if(!('useMac' in options)) {
+    options.useMac = true;
+  }
+  if(!('localKeyId' in options)) {
+    options.localKeyId = null;
+  }
+  if(!('generateLocalKeyId' in options)) {
+    options.generateLocalKeyId = true;
+  }
+
+  var localKeyId = options.localKeyId;
+  var bagAttrs;
+  if(localKeyId !== null) {
+    localKeyId = forge.util.hexToBytes(localKeyId);
+  } else if(options.generateLocalKeyId) {
+    // use SHA-1 of paired cert, if available
+    if(cert) {
+      var pairedCert = forge.util.isArray(cert) ? cert[0] : cert;
+      if(typeof pairedCert === 'string') {
+        pairedCert = pki.certificateFromPem(pairedCert);
+      }
+      var sha1 = forge.md.sha1.create();
+      sha1.update(asn1.toDer(pki.certificateToAsn1(pairedCert)).getBytes());
+      localKeyId = sha1.digest().getBytes();
+    } else {
+      // FIXME: consider using SHA-1 of public key (which can be generated
+      // from private key components), see: cert.generateSubjectKeyIdentifier
+      // generate random bytes
+      localKeyId = forge.random.getBytes(20);
+    }
+  }
+
+  var attrs = [];
+  if(localKeyId !== null) {
+    attrs.push(
+      // localKeyID
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+        // attrId
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+          asn1.oidToDer(pki.oids.localKeyId).getBytes()),
+        // attrValues
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true, [
+          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
+            localKeyId)
+        ])
+      ]));
+  }
+  if('friendlyName' in options) {
+    attrs.push(
+      // friendlyName
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+        // attrId
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+          asn1.oidToDer(pki.oids.friendlyName).getBytes()),
+        // attrValues
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true, [
+          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.BMPSTRING, false,
+            options.friendlyName)
+        ])
+      ]));
+  }
+
+  if(attrs.length > 0) {
+    bagAttrs = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true, attrs);
+  }
+
+  // collect contents for AuthenticatedSafe
+  var contents = [];
+
+  // create safe bag(s) for certificate chain
+  var chain = [];
+  if(cert !== null) {
+    if(forge.util.isArray(cert)) {
+      chain = cert;
+    } else {
+      chain = [cert];
+    }
+  }
+
+  var certSafeBags = [];
+  for(var i = 0; i < chain.length; ++i) {
+    // convert cert from PEM as necessary
+    cert = chain[i];
+    if(typeof cert === 'string') {
+      cert = pki.certificateFromPem(cert);
+    }
+
+    // SafeBag
+    var certBagAttrs = (i === 0) ? bagAttrs : undefined;
+    var certAsn1 = pki.certificateToAsn1(cert);
+    var certSafeBag =
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+        // bagId
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+          asn1.oidToDer(pki.oids.certBag).getBytes()),
+        // bagValue
+        asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
+          // CertBag
+          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+            // certId
+            asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+              asn1.oidToDer(pki.oids.x509Certificate).getBytes()),
+            // certValue (x509Certificate)
+            asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
+              asn1.create(
+                asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
+                asn1.toDer(certAsn1).getBytes())
+            ])])]),
+        // bagAttributes (OPTIONAL)
+        certBagAttrs
+      ]);
+    certSafeBags.push(certSafeBag);
+  }
+
+  if(certSafeBags.length > 0) {
+    // SafeContents
+    var certSafeContents = asn1.create(
+      asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, certSafeBags);
+
+    // ContentInfo
+    var certCI =
+      // PKCS#7 ContentInfo
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+        // contentType
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+          // OID for the content type is 'data'
+          asn1.oidToDer(pki.oids.data).getBytes()),
+        // content
+        asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
+          asn1.create(
+            asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
+            asn1.toDer(certSafeContents).getBytes())
+        ])
+      ]);
+    contents.push(certCI);
+  }
+
+  // create safe contents for private key
+  var keyBag = null;
+  if(key !== null) {
+    // SafeBag
+    var pkAsn1 = pki.wrapRsaPrivateKey(pki.privateKeyToAsn1(key));
+    if(password === null) {
+      // no encryption
+      keyBag = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+        // bagId
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+          asn1.oidToDer(pki.oids.keyBag).getBytes()),
+        // bagValue
+        asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
+          // PrivateKeyInfo
+          pkAsn1
+        ]),
+        // bagAttributes (OPTIONAL)
+        bagAttrs
+      ]);
+    } else {
+      // encrypted PrivateKeyInfo
+      keyBag = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+        // bagId
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+          asn1.oidToDer(pki.oids.pkcs8ShroudedKeyBag).getBytes()),
+        // bagValue
+        asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
+          // EncryptedPrivateKeyInfo
+          pki.encryptPrivateKeyInfo(pkAsn1, password, options)
+        ]),
+        // bagAttributes (OPTIONAL)
+        bagAttrs
+      ]);
+    }
+
+    // SafeContents
+    var keySafeContents =
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [keyBag]);
+
+    // ContentInfo
+    var keyCI =
+      // PKCS#7 ContentInfo
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+        // contentType
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+          // OID for the content type is 'data'
+          asn1.oidToDer(pki.oids.data).getBytes()),
+        // content
+        asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
+          asn1.create(
+            asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
+            asn1.toDer(keySafeContents).getBytes())
+        ])
+      ]);
+    contents.push(keyCI);
+  }
+
+  // create AuthenticatedSafe by stringing together the contents
+  var safe = asn1.create(
+    asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, contents);
+
+  var macData;
+  if(options.useMac) {
+    // MacData
+    var sha1 = forge.md.sha1.create();
+    var macSalt = new forge.util.ByteBuffer(
+      forge.random.getBytes(options.saltSize));
+    var count = options.count;
+    // 160-bit key
+    var key = p12.generateKey(password, macSalt, 3, count, 20);
+    var mac = forge.hmac.create();
+    mac.start(sha1, key);
+    mac.update(asn1.toDer(safe).getBytes());
+    var macValue = mac.getMac();
+    macData = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+      // mac DigestInfo
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+        // digestAlgorithm
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+          // algorithm = SHA-1
+          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+            asn1.oidToDer(pki.oids.sha1).getBytes()),
+          // parameters = Null
+          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
+        ]),
+        // digest
+        asn1.create(
+          asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING,
+          false, macValue.getBytes())
+      ]),
+      // macSalt OCTET STRING
+      asn1.create(
+        asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, macSalt.getBytes()),
+      // iterations INTEGER (XXX: Only support count < 65536)
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+        asn1.integerToDer(count).getBytes()
+      )
+    ]);
+  }
+
+  // PFX
+  return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+    // version (3)
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      asn1.integerToDer(3).getBytes()),
+    // PKCS#7 ContentInfo
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+      // contentType
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+        // OID for the content type is 'data'
+        asn1.oidToDer(pki.oids.data).getBytes()),
+      // content
+      asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
+        asn1.create(
+          asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
+          asn1.toDer(safe).getBytes())
+      ])
+    ]),
+    macData
+  ]);
+};
+
+/**
+ * Derives a PKCS#12 key.
+ *
+ * @param password the password to derive the key material from, null or
+ *          undefined for none.
+ * @param salt the salt, as a ByteBuffer, to use.
+ * @param id the PKCS#12 ID byte (1 = key material, 2 = IV, 3 = MAC).
+ * @param iter the iteration count.
+ * @param n the number of bytes to derive from the password.
+ * @param md the message digest to use, defaults to SHA-1.
+ *
+ * @return a ByteBuffer with the bytes derived from the password.
+ */
+p12.generateKey = forge.pbe.generatePkcs12Key;
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'pkcs12';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define([
+  'require',
+  'module',
+  './asn1',
+  './hmac',
+  './oids',
+  './pkcs7asn1',
+  './pbe',
+  './random',
+  './rsa',
+  './sha1',
+  './util',
+  './x509'
+], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/pkcs7.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/pkcs7.js
new file mode 100644
index 0000000..ffa7413
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/pkcs7.js
@@ -0,0 +1,842 @@
+/**
+ * Javascript implementation of PKCS#7 v1.5. Currently only certain parts of
+ * PKCS#7 are implemented, especially the enveloped-data content type.
+ *
+ * @author Stefan Siegl
+ *
+ * Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
+ *
+ * Currently this implementation only supports ContentType of either
+ * EnvelopedData or EncryptedData on root level.  The top level elements may
+ * contain only a ContentInfo of ContentType Data, i.e. plain data.  Further
+ * nesting is not (yet) supported.
+ *
+ * The Forge validators for PKCS #7's ASN.1 structures are available from
+ * a seperate file pkcs7asn1.js, since those are referenced from other
+ * PKCS standards like PKCS #12.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+// shortcut for ASN.1 API
+var asn1 = forge.asn1;
+
+// shortcut for PKCS#7 API
+var p7 = forge.pkcs7 = forge.pkcs7 || {};
+
+/**
+ * Converts a PKCS#7 message from PEM format.
+ *
+ * @param pem the PEM-formatted PKCS#7 message.
+ *
+ * @return the PKCS#7 message.
+ */
+p7.messageFromPem = function(pem) {
+  var msg = forge.pem.decode(pem)[0];
+
+  if(msg.type !== 'PKCS7') {
+    var error = new Error('Could not convert PKCS#7 message from PEM; PEM ' +
+      'header type is not "PKCS#7".');
+    error.headerType = msg.type;
+    throw error;
+  }
+  if(msg.procType && msg.procType.type === 'ENCRYPTED') {
+    throw new Error('Could not convert PKCS#7 message from PEM; PEM is encrypted.');
+  }
+
+  // convert DER to ASN.1 object
+  var obj = asn1.fromDer(msg.body);
+
+  return p7.messageFromAsn1(obj);
+};
+
+/**
+ * Converts a PKCS#7 message to PEM format.
+ *
+ * @param msg The PKCS#7 message object
+ * @param maxline The maximum characters per line, defaults to 64.
+ *
+ * @return The PEM-formatted PKCS#7 message.
+ */
+p7.messageToPem = function(msg, maxline) {
+  // convert to ASN.1, then DER, then PEM-encode
+  var pemObj = {
+    type: 'PKCS7',
+    body: asn1.toDer(msg.toAsn1()).getBytes()
+  };
+  return forge.pem.encode(pemObj, {maxline: maxline});
+};
+
+/**
+ * Converts a PKCS#7 message from an ASN.1 object.
+ *
+ * @param obj the ASN.1 representation of a ContentInfo.
+ *
+ * @return the PKCS#7 message.
+ */
+p7.messageFromAsn1 = function(obj) {
+  // validate root level ContentInfo and capture data
+  var capture = {};
+  var errors = [];
+  if(!asn1.validate(obj, p7.asn1.contentInfoValidator, capture, errors))
+  {
+    var error = new Error('Cannot read PKCS#7 message. ' +
+      'ASN.1 object is not an PKCS#7 ContentInfo.');
+    error.errors = errors;
+    throw error;
+  }
+
+  var contentType = asn1.derToOid(capture.contentType);
+  var msg;
+
+  switch(contentType) {
+    case forge.pki.oids.envelopedData:
+      msg = p7.createEnvelopedData();
+      break;
+
+    case forge.pki.oids.encryptedData:
+      msg = p7.createEncryptedData();
+      break;
+
+    case forge.pki.oids.signedData:
+      msg = p7.createSignedData();
+      break;
+
+    default:
+      throw new Error('Cannot read PKCS#7 message. ContentType with OID ' +
+        contentType + ' is not (yet) supported.');
+  }
+
+  msg.fromAsn1(capture.content.value[0]);
+  return msg;
+};
+
+/**
+ * Converts a single RecipientInfo from an ASN.1 object.
+ *
+ * @param obj The ASN.1 representation of a RecipientInfo.
+ *
+ * @return The recipientInfo object.
+ */
+var _recipientInfoFromAsn1 = function(obj) {
+  // Validate EnvelopedData content block and capture data.
+  var capture = {};
+  var errors = [];
+  if(!asn1.validate(obj, p7.asn1.recipientInfoValidator, capture, errors))
+  {
+    var error = new Error('Cannot read PKCS#7 message. ' +
+      'ASN.1 object is not an PKCS#7 EnvelopedData.');
+    error.errors = errors;
+    throw error;
+  }
+
+  return {
+    version: capture.version.charCodeAt(0),
+    issuer: forge.pki.RDNAttributesAsArray(capture.issuer),
+    serialNumber: forge.util.createBuffer(capture.serial).toHex(),
+    encryptedContent: {
+      algorithm: asn1.derToOid(capture.encAlgorithm),
+      parameter: capture.encParameter.value,
+      content: capture.encKey
+    }
+  };
+};
+
+/**
+ * Converts a single recipientInfo object to an ASN.1 object.
+ *
+ * @param obj The recipientInfo object.
+ *
+ * @return The ASN.1 representation of a RecipientInfo.
+ */
+var _recipientInfoToAsn1 = function(obj) {
+  return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+    // Version
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      asn1.integerToDer(obj.version).getBytes()),
+    // IssuerAndSerialNumber
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+      // Name
+      forge.pki.distinguishedNameToAsn1({attributes: obj.issuer}),
+      // Serial
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+        forge.util.hexToBytes(obj.serialNumber))
+    ]),
+    // KeyEncryptionAlgorithmIdentifier
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+      // Algorithm
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+        asn1.oidToDer(obj.encryptedContent.algorithm).getBytes()),
+      // Parameter, force NULL, only RSA supported for now.
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
+    ]),
+    // EncryptedKey
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
+      obj.encryptedContent.content)
+  ]);
+};
+
+/**
+ * Map a set of RecipientInfo ASN.1 objects to recipientInfo objects.
+ *
+ * @param objArr Array of ASN.1 representations RecipientInfo (i.e. SET OF).
+ *
+ * @return array of recipientInfo objects.
+ */
+var _recipientInfosFromAsn1 = function(objArr) {
+  var ret = [];
+  for(var i = 0; i < objArr.length; i ++) {
+    ret.push(_recipientInfoFromAsn1(objArr[i]));
+  }
+  return ret;
+};
+
+/**
+ * Map an array of recipientInfo objects to ASN.1 objects.
+ *
+ * @param recipientsArr Array of recipientInfo objects.
+ *
+ * @return Array of ASN.1 representations RecipientInfo.
+ */
+var _recipientInfosToAsn1 = function(recipientsArr) {
+  var ret = [];
+  for(var i = 0; i < recipientsArr.length; i ++) {
+    ret.push(_recipientInfoToAsn1(recipientsArr[i]));
+  }
+  return ret;
+};
+
+/**
+ * Map messages encrypted content to ASN.1 objects.
+ *
+ * @param ec The encryptedContent object of the message.
+ *
+ * @return ASN.1 representation of the encryptedContent object (SEQUENCE).
+ */
+var _encryptedContentToAsn1 = function(ec) {
+  return [
+    // ContentType, always Data for the moment
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+      asn1.oidToDer(forge.pki.oids.data).getBytes()),
+    // ContentEncryptionAlgorithmIdentifier
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+      // Algorithm
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+        asn1.oidToDer(ec.algorithm).getBytes()),
+      // Parameters (IV)
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
+        ec.parameter.getBytes())
+    ]),
+    // [0] EncryptedContent
+    asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
+        ec.content.getBytes())
+    ])
+  ];
+};
+
+/**
+ * Reads the "common part" of an PKCS#7 content block (in ASN.1 format)
+ *
+ * This function reads the "common part" of the PKCS#7 content blocks
+ * EncryptedData and EnvelopedData, i.e. version number and symmetrically
+ * encrypted content block.
+ *
+ * The result of the ASN.1 validate and capture process is returned
+ * to allow the caller to extract further data, e.g. the list of recipients
+ * in case of a EnvelopedData object.
+ *
+ * @param msg the PKCS#7 object to read the data to.
+ * @param obj the ASN.1 representation of the content block.
+ * @param validator the ASN.1 structure validator object to use.
+ *
+ * @return the value map captured by validator object.
+ */
+var _fromAsn1 = function(msg, obj, validator) {
+  var capture = {};
+  var errors = [];
+  if(!asn1.validate(obj, validator, capture, errors)) {
+    var error = new Error('Cannot read PKCS#7 message. ' +
+      'ASN.1 object is not a supported PKCS#7 message.');
+    error.errors = error;
+    throw error;
+  }
+
+  // Check contentType, so far we only support (raw) Data.
+  var contentType = asn1.derToOid(capture.contentType);
+  if(contentType !== forge.pki.oids.data) {
+    throw new Error('Unsupported PKCS#7 message. ' +
+      'Only wrapped ContentType Data supported.');
+  }
+
+  if(capture.encryptedContent) {
+    var content = '';
+    if(forge.util.isArray(capture.encryptedContent)) {
+      for(var i = 0; i < capture.encryptedContent.length; ++i) {
+        if(capture.encryptedContent[i].type !== asn1.Type.OCTETSTRING) {
+          throw new Error('Malformed PKCS#7 message, expecting encrypted ' +
+            'content constructed of only OCTET STRING objects.');
+        }
+        content += capture.encryptedContent[i].value;
+      }
+    } else {
+      content = capture.encryptedContent;
+    }
+    msg.encryptedContent = {
+      algorithm: asn1.derToOid(capture.encAlgorithm),
+      parameter: forge.util.createBuffer(capture.encParameter.value),
+      content: forge.util.createBuffer(content)
+    };
+  }
+
+  if(capture.content) {
+    var content = '';
+    if(forge.util.isArray(capture.content)) {
+      for(var i = 0; i < capture.content.length; ++i) {
+        if(capture.content[i].type !== asn1.Type.OCTETSTRING) {
+          throw new Error('Malformed PKCS#7 message, expecting ' +
+            'content constructed of only OCTET STRING objects.');
+        }
+        content += capture.content[i].value;
+      }
+    } else {
+      content = capture.content;
+    }
+    msg.content = forge.util.createBuffer(content);
+  }
+
+  msg.version = capture.version.charCodeAt(0);
+  msg.rawCapture = capture;
+
+  return capture;
+};
+
+/**
+ * Decrypt the symmetrically encrypted content block of the PKCS#7 message.
+ *
+ * Decryption is skipped in case the PKCS#7 message object already has a
+ * (decrypted) content attribute.  The algorithm, key and cipher parameters
+ * (probably the iv) are taken from the encryptedContent attribute of the
+ * message object.
+ *
+ * @param The PKCS#7 message object.
+ */
+var _decryptContent = function (msg) {
+  if(msg.encryptedContent.key === undefined) {
+    throw new Error('Symmetric key not available.');
+  }
+
+  if(msg.content === undefined) {
+    var ciph;
+
+    switch(msg.encryptedContent.algorithm) {
+      case forge.pki.oids['aes128-CBC']:
+      case forge.pki.oids['aes192-CBC']:
+      case forge.pki.oids['aes256-CBC']:
+        ciph = forge.aes.createDecryptionCipher(msg.encryptedContent.key);
+        break;
+
+      case forge.pki.oids['desCBC']:
+      case forge.pki.oids['des-EDE3-CBC']:
+        ciph = forge.des.createDecryptionCipher(msg.encryptedContent.key);
+        break;
+
+      default:
+        throw new Error('Unsupported symmetric cipher, OID ' +
+          msg.encryptedContent.algorithm);
+    }
+    ciph.start(msg.encryptedContent.parameter);
+    ciph.update(msg.encryptedContent.content);
+
+    if(!ciph.finish()) {
+      throw new Error('Symmetric decryption failed.');
+    }
+
+    msg.content = ciph.output;
+  }
+};
+
+p7.createSignedData = function() {
+  var msg = null;
+  msg = {
+    type: forge.pki.oids.signedData,
+    version: 1,
+    certificates: [],
+    crls: [],
+    // populated during sign()
+    digestAlgorithmIdentifiers: [],
+    contentInfo: null,
+    signerInfos: [],
+
+    fromAsn1: function(obj) {
+      // validate SignedData content block and capture data.
+      _fromAsn1(msg, obj, p7.asn1.signedDataValidator);
+      msg.certificates = [];
+      msg.crls = [];
+      msg.digestAlgorithmIdentifiers = [];
+      msg.contentInfo = null;
+      msg.signerInfos = [];
+
+      var certs = msg.rawCapture.certificates.value;
+      for(var i = 0; i < certs.length; ++i) {
+        msg.certificates.push(forge.pki.certificateFromAsn1(certs[i]));
+      }
+
+      // TODO: parse crls
+    },
+
+    toAsn1: function() {
+      // TODO: add support for more data types here
+      if('content' in msg) {
+        throw new Error('Signing PKCS#7 content not yet implemented.');
+      }
+
+      // degenerate case with no content
+      if(!msg.contentInfo) {
+        msg.sign();
+      }
+
+      var certs = [];
+      for(var i = 0; i < msg.certificates.length; ++i) {
+        certs.push(forge.pki.certificateToAsn1(msg.certificates[0]));
+      }
+
+      var crls = [];
+      // TODO: implement CRLs
+
+      // ContentInfo
+      return asn1.create(
+        asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+          // ContentType
+          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+            asn1.oidToDer(msg.type).getBytes()),
+          // [0] SignedData
+          asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
+            asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+              // Version
+              asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+                asn1.integerToDer(msg.version).getBytes()),
+              // DigestAlgorithmIdentifiers
+              asn1.create(
+                asn1.Class.UNIVERSAL, asn1.Type.SET, true,
+                msg.digestAlgorithmIdentifiers),
+              // ContentInfo
+              msg.contentInfo,
+              // [0] IMPLICIT ExtendedCertificatesAndCertificates
+              asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, certs),
+              // [1] IMPLICIT CertificateRevocationLists
+              asn1.create(asn1.Class.CONTEXT_SPECIFIC, 1, true, crls),
+              // SignerInfos
+              asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true,
+                msg.signerInfos)
+            ])
+          ])
+      ]);
+    },
+
+    /**
+     * Signs the content.
+     *
+     * @param signer the signer (or array of signers) to sign as, for each:
+     *          key the private key to sign with.
+     *          [md] the message digest to use, defaults to sha-1.
+     */
+    sign: function(signer) {
+      if('content' in msg) {
+        throw new Error('PKCS#7 signing not yet implemented.');
+      }
+
+      if(typeof msg.content !== 'object') {
+        // use Data ContentInfo
+        msg.contentInfo = asn1.create(
+          asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+            // ContentType
+            asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+              asn1.oidToDer(forge.pki.oids.data).getBytes())
+          ]);
+
+        // add actual content, if present
+        if('content' in msg) {
+          msg.contentInfo.value.push(
+            // [0] EXPLICIT content
+            asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
+              asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
+                msg.content)
+            ]));
+        }
+      }
+
+      // TODO: generate digest algorithm identifiers
+
+      // TODO: generate signerInfos
+    },
+
+    verify: function() {
+      throw new Error('PKCS#7 signature verification not yet implemented.');
+    },
+
+    /**
+     * Add a certificate.
+     *
+     * @param cert the certificate to add.
+     */
+    addCertificate: function(cert) {
+      // convert from PEM
+      if(typeof cert === 'string') {
+        cert = forge.pki.certificateFromPem(cert);
+      }
+      msg.certificates.push(cert);
+    },
+
+    /**
+     * Add a certificate revokation list.
+     *
+     * @param crl the certificate revokation list to add.
+     */
+    addCertificateRevokationList: function(crl) {
+      throw new Error('PKCS#7 CRL support not yet implemented.');
+    }
+  };
+  return msg;
+};
+
+/**
+ * Creates an empty PKCS#7 message of type EncryptedData.
+ *
+ * @return the message.
+ */
+p7.createEncryptedData = function() {
+  var msg = null;
+  msg = {
+    type: forge.pki.oids.encryptedData,
+    version: 0,
+    encryptedContent: {
+      algorithm: forge.pki.oids['aes256-CBC']
+    },
+
+    /**
+     * Reads an EncryptedData content block (in ASN.1 format)
+     *
+     * @param obj The ASN.1 representation of the EncryptedData content block
+     */
+    fromAsn1: function(obj) {
+      // Validate EncryptedData content block and capture data.
+      _fromAsn1(msg, obj, p7.asn1.encryptedDataValidator);
+    },
+
+    /**
+     * Decrypt encrypted content
+     *
+     * @param key The (symmetric) key as a byte buffer
+     */
+    decrypt: function(key) {
+      if(key !== undefined) {
+        msg.encryptedContent.key = key;
+      }
+      _decryptContent(msg);
+    }
+  };
+  return msg;
+};
+
+/**
+ * Creates an empty PKCS#7 message of type EnvelopedData.
+ *
+ * @return the message.
+ */
+p7.createEnvelopedData = function() {
+  var msg = null;
+  msg = {
+    type: forge.pki.oids.envelopedData,
+    version: 0,
+    recipients: [],
+    encryptedContent: {
+      algorithm: forge.pki.oids['aes256-CBC']
+    },
+
+    /**
+     * Reads an EnvelopedData content block (in ASN.1 format)
+     *
+     * @param obj the ASN.1 representation of the EnvelopedData content block.
+     */
+    fromAsn1: function(obj) {
+      // validate EnvelopedData content block and capture data
+      var capture = _fromAsn1(msg, obj, p7.asn1.envelopedDataValidator);
+      msg.recipients = _recipientInfosFromAsn1(capture.recipientInfos.value);
+    },
+
+    toAsn1: function() {
+      // ContentInfo
+      return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+        // ContentType
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+          asn1.oidToDer(msg.type).getBytes()),
+        // [0] EnvelopedData
+        asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
+          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+            // Version
+            asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+              asn1.integerToDer(msg.version).getBytes()),
+            // RecipientInfos
+            asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true,
+              _recipientInfosToAsn1(msg.recipients)),
+            // EncryptedContentInfo
+            asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true,
+              _encryptedContentToAsn1(msg.encryptedContent))
+          ])
+        ])
+      ]);
+    },
+
+    /**
+     * Find recipient by X.509 certificate's issuer.
+     *
+     * @param cert the certificate with the issuer to look for.
+     *
+     * @return the recipient object.
+     */
+    findRecipient: function(cert) {
+      var sAttr = cert.issuer.attributes;
+
+      for(var i = 0; i < msg.recipients.length; ++i) {
+        var r = msg.recipients[i];
+        var rAttr = r.issuer;
+
+        if(r.serialNumber !== cert.serialNumber) {
+          continue;
+        }
+
+        if(rAttr.length !== sAttr.length) {
+          continue;
+        }
+
+        var match = true;
+        for(var j = 0; j < sAttr.length; ++j) {
+          if(rAttr[j].type !== sAttr[j].type ||
+            rAttr[j].value !== sAttr[j].value) {
+            match = false;
+            break;
+          }
+        }
+
+        if(match) {
+          return r;
+        }
+      }
+
+      return null;
+    },
+
+    /**
+     * Decrypt enveloped content
+     *
+     * @param recipient The recipient object related to the private key
+     * @param privKey The (RSA) private key object
+     */
+    decrypt: function(recipient, privKey) {
+      if(msg.encryptedContent.key === undefined && recipient !== undefined &&
+        privKey !== undefined) {
+        switch(recipient.encryptedContent.algorithm) {
+          case forge.pki.oids.rsaEncryption:
+          case forge.pki.oids.desCBC:
+            var key = privKey.decrypt(recipient.encryptedContent.content);
+            msg.encryptedContent.key = forge.util.createBuffer(key);
+            break;
+
+          default:
+            throw new Error('Unsupported asymmetric cipher, ' +
+              'OID ' + recipient.encryptedContent.algorithm);
+        }
+      }
+
+      _decryptContent(msg);
+    },
+
+    /**
+     * Add (another) entity to list of recipients.
+     *
+     * @param cert The certificate of the entity to add.
+     */
+    addRecipient: function(cert) {
+      msg.recipients.push({
+        version: 0,
+        issuer: cert.issuer.attributes,
+        serialNumber: cert.serialNumber,
+        encryptedContent: {
+          // We simply assume rsaEncryption here, since forge.pki only
+          // supports RSA so far.  If the PKI module supports other
+          // ciphers one day, we need to modify this one as well.
+          algorithm: forge.pki.oids.rsaEncryption,
+          key: cert.publicKey
+        }
+      });
+    },
+
+    /**
+     * Encrypt enveloped content.
+     *
+     * This function supports two optional arguments, cipher and key, which
+     * can be used to influence symmetric encryption.  Unless cipher is
+     * provided, the cipher specified in encryptedContent.algorithm is used
+     * (defaults to AES-256-CBC).  If no key is provided, encryptedContent.key
+     * is (re-)used.  If that one's not set, a random key will be generated
+     * automatically.
+     *
+     * @param [key] The key to be used for symmetric encryption.
+     * @param [cipher] The OID of the symmetric cipher to use.
+     */
+    encrypt: function(key, cipher) {
+      // Part 1: Symmetric encryption
+      if(msg.encryptedContent.content === undefined) {
+        cipher = cipher || msg.encryptedContent.algorithm;
+        key = key || msg.encryptedContent.key;
+
+        var keyLen, ivLen, ciphFn;
+        switch(cipher) {
+          case forge.pki.oids['aes128-CBC']:
+            keyLen = 16;
+            ivLen = 16;
+            ciphFn = forge.aes.createEncryptionCipher;
+            break;
+
+          case forge.pki.oids['aes192-CBC']:
+            keyLen = 24;
+            ivLen = 16;
+            ciphFn = forge.aes.createEncryptionCipher;
+            break;
+
+          case forge.pki.oids['aes256-CBC']:
+            keyLen = 32;
+            ivLen = 16;
+            ciphFn = forge.aes.createEncryptionCipher;
+            break;
+
+          case forge.pki.oids['des-EDE3-CBC']:
+            keyLen = 24;
+            ivLen = 8;
+            ciphFn = forge.des.createEncryptionCipher;
+            break;
+
+          default:
+            throw new Error('Unsupported symmetric cipher, OID ' + cipher);
+        }
+
+        if(key === undefined) {
+          key = forge.util.createBuffer(forge.random.getBytes(keyLen));
+        } else if(key.length() != keyLen) {
+          throw new Error('Symmetric key has wrong length; ' +
+            'got ' + key.length() + ' bytes, expected ' + keyLen + '.');
+        }
+
+        // Keep a copy of the key & IV in the object, so the caller can
+        // use it for whatever reason.
+        msg.encryptedContent.algorithm = cipher;
+        msg.encryptedContent.key = key;
+        msg.encryptedContent.parameter = forge.util.createBuffer(
+          forge.random.getBytes(ivLen));
+
+        var ciph = ciphFn(key);
+        ciph.start(msg.encryptedContent.parameter.copy());
+        ciph.update(msg.content);
+
+        // The finish function does PKCS#7 padding by default, therefore
+        // no action required by us.
+        if(!ciph.finish()) {
+          throw new Error('Symmetric encryption failed.');
+        }
+
+        msg.encryptedContent.content = ciph.output;
+      }
+
+      // Part 2: asymmetric encryption for each recipient
+      for(var i = 0; i < msg.recipients.length; i ++) {
+        var recipient = msg.recipients[i];
+
+        // Nothing to do, encryption already done.
+        if(recipient.encryptedContent.content !== undefined) {
+          continue;
+        }
+
+        switch(recipient.encryptedContent.algorithm) {
+          case forge.pki.oids.rsaEncryption:
+            recipient.encryptedContent.content =
+              recipient.encryptedContent.key.encrypt(
+                msg.encryptedContent.key.data);
+            break;
+
+          default:
+            throw new Error('Unsupported asymmetric cipher, OID ' +
+              recipient.encryptedContent.algorithm);
+        }
+      }
+    }
+  };
+  return msg;
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'pkcs7';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define([
+  'require',
+  'module',
+  './aes',
+  './asn1',
+  './des',
+  './oids',
+  './pem',
+  './pkcs7asn1',
+  './random',
+  './util',
+  './x509'
+], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/pkcs7asn1.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/pkcs7asn1.js
new file mode 100644
index 0000000..f7c4df6
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/pkcs7asn1.js
@@ -0,0 +1,399 @@
+/**
+ * Javascript implementation of PKCS#7 v1.5.  Currently only certain parts of
+ * PKCS#7 are implemented, especially the enveloped-data content type.
+ *
+ * @author Stefan Siegl
+ *
+ * Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
+ *
+ * The ASN.1 representation of PKCS#7 is as follows
+ * (see RFC #2315 for details, http://www.ietf.org/rfc/rfc2315.txt):
+ *
+ * A PKCS#7 message consists of a ContentInfo on root level, which may
+ * contain any number of further ContentInfo nested into it.
+ *
+ * ContentInfo ::= SEQUENCE {
+ *    contentType                ContentType,
+ *    content               [0]  EXPLICIT ANY DEFINED BY contentType OPTIONAL
+ * }
+ *
+ * ContentType ::= OBJECT IDENTIFIER
+ *
+ * EnvelopedData ::= SEQUENCE {
+ *    version                    Version,
+ *    recipientInfos             RecipientInfos,
+ *    encryptedContentInfo       EncryptedContentInfo
+ * }
+ *
+ * EncryptedData ::= SEQUENCE {
+ *    version                    Version,
+ *    encryptedContentInfo       EncryptedContentInfo
+ * }
+ *
+ * Version ::= INTEGER
+ *
+ * RecipientInfos ::= SET OF RecipientInfo
+ *
+ * EncryptedContentInfo ::= SEQUENCE {
+ *   contentType                 ContentType,
+ *   contentEncryptionAlgorithm  ContentEncryptionAlgorithmIdentifier,
+ *   encryptedContent       [0]  IMPLICIT EncryptedContent OPTIONAL
+ * }
+ *
+ * ContentEncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
+ *
+ * The AlgorithmIdentifier contains an Object Identifier (OID) and parameters
+ * for the algorithm, if any. In the case of AES and DES3, there is only one,
+ * the IV.
+ *
+ * AlgorithmIdentifer ::= SEQUENCE {
+ *    algorithm OBJECT IDENTIFIER,
+ *    parameters ANY DEFINED BY algorithm OPTIONAL
+ * }
+ *
+ * EncryptedContent ::= OCTET STRING
+ *
+ * RecipientInfo ::= SEQUENCE {
+ *   version                     Version,
+ *   issuerAndSerialNumber       IssuerAndSerialNumber,
+ *   keyEncryptionAlgorithm      KeyEncryptionAlgorithmIdentifier,
+ *   encryptedKey                EncryptedKey
+ * }
+ *
+ * IssuerAndSerialNumber ::= SEQUENCE {
+ *   issuer                      Name,
+ *   serialNumber                CertificateSerialNumber
+ * }
+ *
+ * CertificateSerialNumber ::= INTEGER
+ *
+ * KeyEncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
+ *
+ * EncryptedKey ::= OCTET STRING
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+// shortcut for ASN.1 API
+var asn1 = forge.asn1;
+
+// shortcut for PKCS#7 API
+var p7v = forge.pkcs7asn1 = forge.pkcs7asn1 || {};
+forge.pkcs7 = forge.pkcs7 || {};
+forge.pkcs7.asn1 = p7v;
+
+var contentInfoValidator = {
+  name: 'ContentInfo',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'ContentInfo.ContentType',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.OID,
+    constructed: false,
+    capture: 'contentType'
+  }, {
+    name: 'ContentInfo.content',
+    tagClass: asn1.Class.CONTEXT_SPECIFIC,
+    type: 0,
+    constructed: true,
+    optional: true,
+    captureAsn1: 'content'
+  }]
+};
+p7v.contentInfoValidator = contentInfoValidator;
+
+var encryptedContentInfoValidator = {
+  name: 'EncryptedContentInfo',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'EncryptedContentInfo.contentType',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.OID,
+    constructed: false,
+    capture: 'contentType'
+  }, {
+    name: 'EncryptedContentInfo.contentEncryptionAlgorithm',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true,
+    value: [{
+      name: 'EncryptedContentInfo.contentEncryptionAlgorithm.algorithm',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.OID,
+      constructed: false,
+      capture: 'encAlgorithm'
+    }, {
+      name: 'EncryptedContentInfo.contentEncryptionAlgorithm.parameter',
+      tagClass: asn1.Class.UNIVERSAL,
+      captureAsn1: 'encParameter'
+    }]
+  }, {
+    name: 'EncryptedContentInfo.encryptedContent',
+    tagClass: asn1.Class.CONTEXT_SPECIFIC,
+    type: 0,
+    /* The PKCS#7 structure output by OpenSSL somewhat differs from what
+     * other implementations do generate.
+     *
+     * OpenSSL generates a structure like this:
+     * SEQUENCE {
+     *    ...
+     *    [0]
+     *       26 DA 67 D2 17 9C 45 3C B1 2A A8 59 2F 29 33 38
+     *       C3 C3 DF 86 71 74 7A 19 9F 40 D0 29 BE 85 90 45
+     *       ...
+     * }
+     *
+     * Whereas other implementations (and this PKCS#7 module) generate:
+     * SEQUENCE {
+     *    ...
+     *    [0] {
+     *       OCTET STRING
+     *          26 DA 67 D2 17 9C 45 3C B1 2A A8 59 2F 29 33 38
+     *          C3 C3 DF 86 71 74 7A 19 9F 40 D0 29 BE 85 90 45
+     *          ...
+     *    }
+     * }
+     *
+     * In order to support both, we just capture the context specific
+     * field here.  The OCTET STRING bit is removed below.
+     */
+    capture: 'encryptedContent',
+    captureAsn1: 'encryptedContentAsn1'
+  }]
+};
+
+p7v.envelopedDataValidator = {
+  name: 'EnvelopedData',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'EnvelopedData.Version',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'version'
+  }, {
+    name: 'EnvelopedData.RecipientInfos',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SET,
+    constructed: true,
+    captureAsn1: 'recipientInfos'
+  }].concat(encryptedContentInfoValidator)
+};
+
+p7v.encryptedDataValidator = {
+  name: 'EncryptedData',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'EncryptedData.Version',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'version'
+  }].concat(encryptedContentInfoValidator)
+};
+
+var signerValidator = {
+  name: 'SignerInfo',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'SignerInfo.Version',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false
+  }, {
+    name: 'SignerInfo.IssuerAndSerialNumber',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true
+  }, {
+    name: 'SignerInfo.DigestAlgorithm',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true
+  }, {
+    name: 'SignerInfo.AuthenticatedAttributes',
+    tagClass: asn1.Class.CONTEXT_SPECIFIC,
+    type: 0,
+    constructed: true,
+    optional: true,
+    capture: 'authenticatedAttributes'
+  }, {
+    name: 'SignerInfo.DigestEncryptionAlgorithm',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true
+  }, {
+    name: 'SignerInfo.EncryptedDigest',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.OCTETSTRING,
+    constructed: false,
+    capture: 'signature'
+  }, {
+    name: 'SignerInfo.UnauthenticatedAttributes',
+    tagClass: asn1.Class.CONTEXT_SPECIFIC,
+    type: 1,
+    constructed: true,
+    optional: true
+  }]
+};
+
+p7v.signedDataValidator = {
+  name: 'SignedData',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'SignedData.Version',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'version'
+  }, {
+    name: 'SignedData.DigestAlgorithms',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SET,
+    constructed: true,
+    captureAsn1: 'digestAlgorithms'
+  },
+  contentInfoValidator,
+  {
+    name: 'SignedData.Certificates',
+    tagClass: asn1.Class.CONTEXT_SPECIFIC,
+    type: 0,
+    optional: true,
+    captureAsn1: 'certificates'
+  }, {
+    name: 'SignedData.CertificateRevocationLists',
+    tagClass: asn1.Class.CONTEXT_SPECIFIC,
+    type: 1,
+    optional: true,
+    captureAsn1: 'crls'
+  }, {
+    name: 'SignedData.SignerInfos',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SET,
+    capture: 'signerInfos',
+    optional: true,
+    value: [signerValidator]
+  }]
+};
+
+p7v.recipientInfoValidator = {
+  name: 'RecipientInfo',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'RecipientInfo.version',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'version'
+  }, {
+    name: 'RecipientInfo.issuerAndSerial',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true,
+    value: [{
+      name: 'RecipientInfo.issuerAndSerial.issuer',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.SEQUENCE,
+      constructed: true,
+      captureAsn1: 'issuer'
+    }, {
+      name: 'RecipientInfo.issuerAndSerial.serialNumber',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.INTEGER,
+      constructed: false,
+      capture: 'serial'
+    }]
+  }, {
+    name: 'RecipientInfo.keyEncryptionAlgorithm',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true,
+    value: [{
+      name: 'RecipientInfo.keyEncryptionAlgorithm.algorithm',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.OID,
+      constructed: false,
+      capture: 'encAlgorithm'
+    }, {
+      name: 'RecipientInfo.keyEncryptionAlgorithm.parameter',
+      tagClass: asn1.Class.UNIVERSAL,
+      constructed: false,
+      captureAsn1: 'encParameter'
+    }]
+  }, {
+    name: 'RecipientInfo.encryptedKey',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.OCTETSTRING,
+    constructed: false,
+    capture: 'encKey'
+  }]
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'pkcs7asn1';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './asn1', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/pki.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/pki.js
new file mode 100644
index 0000000..3df7805
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/pki.js
@@ -0,0 +1,161 @@
+/**
+ * Javascript implementation of a basic Public Key Infrastructure, including
+ * support for RSA public and private keys.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2013 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+// shortcut for asn.1 API
+var asn1 = forge.asn1;
+
+/* Public Key Infrastructure (PKI) implementation. */
+var pki = forge.pki = forge.pki || {};
+
+/**
+ * NOTE: THIS METHOD IS DEPRECATED. Use pem.decode() instead.
+ *
+ * Converts PEM-formatted data to DER.
+ *
+ * @param pem the PEM-formatted data.
+ *
+ * @return the DER-formatted data.
+ */
+pki.pemToDer = function(pem) {
+  var msg = forge.pem.decode(pem)[0];
+  if(msg.procType && msg.procType.type === 'ENCRYPTED') {
+    throw new Error('Could not convert PEM to DER; PEM is encrypted.');
+  }
+  return forge.util.createBuffer(msg.body);
+};
+
+/**
+ * Converts an RSA private key from PEM format.
+ *
+ * @param pem the PEM-formatted private key.
+ *
+ * @return the private key.
+ */
+pki.privateKeyFromPem = function(pem) {
+  var msg = forge.pem.decode(pem)[0];
+
+  if(msg.type !== 'PRIVATE KEY' && msg.type !== 'RSA PRIVATE KEY') {
+    var error = new Error('Could not convert private key from PEM; PEM ' +
+      'header type is not "PRIVATE KEY" or "RSA PRIVATE KEY".');
+    error.headerType = msg.type;
+    throw error;
+  }
+  if(msg.procType && msg.procType.type === 'ENCRYPTED') {
+    throw new Error('Could not convert private key from PEM; PEM is encrypted.');
+  }
+
+  // convert DER to ASN.1 object
+  var obj = asn1.fromDer(msg.body);
+
+  return pki.privateKeyFromAsn1(obj);
+};
+
+/**
+ * Converts an RSA private key to PEM format.
+ *
+ * @param key the private key.
+ * @param maxline the maximum characters per line, defaults to 64.
+ *
+ * @return the PEM-formatted private key.
+ */
+pki.privateKeyToPem = function(key, maxline) {
+  // convert to ASN.1, then DER, then PEM-encode
+  var msg = {
+    type: 'RSA PRIVATE KEY',
+    body: asn1.toDer(pki.privateKeyToAsn1(key)).getBytes()
+  };
+  return forge.pem.encode(msg, {maxline: maxline});
+};
+
+/**
+ * Converts a PrivateKeyInfo to PEM format.
+ *
+ * @param pki the PrivateKeyInfo.
+ * @param maxline the maximum characters per line, defaults to 64.
+ *
+ * @return the PEM-formatted private key.
+ */
+pki.privateKeyInfoToPem = function(pki, maxline) {
+  // convert to DER, then PEM-encode
+  var msg = {
+    type: 'PRIVATE KEY',
+    body: asn1.toDer(pki).getBytes()
+  };
+  return forge.pem.encode(msg, {maxline: maxline});
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'pki';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define([
+  'require',
+  'module',
+  './asn1',
+  './oids',
+  './pbe',
+  './pem',
+  './pbkdf2',
+  './pkcs12',
+  './pss',
+  './rsa',
+  './util',
+  './x509'
+], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/prime.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/prime.js
new file mode 100644
index 0000000..2857c36
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/prime.js
@@ -0,0 +1,337 @@
+/**
+ * Prime number generation API.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+// forge.prime already defined
+if(forge.prime) {
+  return;
+}
+
+/* PRIME API */
+var prime = forge.prime = forge.prime || {};
+
+var BigInteger = forge.jsbn.BigInteger;
+
+// primes are 30k+i for i = 1, 7, 11, 13, 17, 19, 23, 29
+var GCD_30_DELTA = [6, 4, 2, 4, 2, 4, 6, 2];
+var THIRTY = new BigInteger(null);
+THIRTY.fromInt(30);
+var op_or = function(x, y) {return x|y;};
+
+/**
+ * Generates a random probable prime with the given number of bits.
+ *
+ * Alternative algorithms can be specified by name as a string or as an
+ * object with custom options like so:
+ *
+ * {
+ *   name: 'PRIMEINC',
+ *   options: {
+ *     maxBlockTime: <the maximum amount of time to block the main
+ *       thread before allowing I/O other JS to run>,
+ *     millerRabinTests: <the number of miller-rabin tests to run>,
+ *     workerScript: <the worker script URL>,
+ *     workers: <the number of web workers (if supported) to use,
+ *       -1 to use estimated cores minus one>.
+ *     workLoad: the size of the work load, ie: number of possible prime
+ *       numbers for each web worker to check per work assignment,
+ *       (default: 100).
+ *   }
+ * }
+ *
+ * @param bits the number of bits for the prime number.
+ * @param options the options to use.
+ *          [algorithm] the algorithm to use (default: 'PRIMEINC').
+ *          [prng] a custom crypto-secure pseudo-random number generator to use,
+ *            that must define "getBytesSync".
+ *
+ * @return callback(err, num) called once the operation completes.
+ */
+prime.generateProbablePrime = function(bits, options, callback) {
+  if(typeof options === 'function') {
+    callback = options;
+    options = {};
+  }
+  options = options || {};
+
+  // default to PRIMEINC algorithm
+  var algorithm = options.algorithm || 'PRIMEINC';
+  if(typeof algorithm === 'string') {
+    algorithm = {name: algorithm};
+  }
+  algorithm.options = algorithm.options || {};
+
+  // create prng with api that matches BigInteger secure random
+  var prng = options.prng || forge.random;
+  var rng = {
+    // x is an array to fill with bytes
+    nextBytes: function(x) {
+      var b = prng.getBytesSync(x.length);
+      for(var i = 0; i < x.length; ++i) {
+        x[i] = b.charCodeAt(i);
+      }
+    }
+  };
+
+  if(algorithm.name === 'PRIMEINC') {
+    return primeincFindPrime(bits, rng, algorithm.options, callback);
+  }
+
+  throw new Error('Invalid prime generation algorithm: ' + algorithm.name);
+};
+
+function primeincFindPrime(bits, rng, options, callback) {
+  if('workers' in options) {
+    return primeincFindPrimeWithWorkers(bits, rng, options, callback);
+  }
+  return primeincFindPrimeWithoutWorkers(bits, rng, options, callback);
+}
+
+function primeincFindPrimeWithoutWorkers(bits, rng, options, callback) {
+  // initialize random number
+  var num = generateRandom(bits, rng);
+
+  /* Note: All primes are of the form 30k+i for i < 30 and gcd(30, i)=1. The
+  number we are given is always aligned at 30k + 1. Each time the number is
+  determined not to be prime we add to get to the next 'i', eg: if the number
+  was at 30k + 1 we add 6. */
+  var deltaIdx = 0;
+
+  // get required number of MR tests
+  var mrTests = getMillerRabinTests(num.bitLength());
+  if('millerRabinTests' in options) {
+    mrTests = options.millerRabinTests;
+  }
+
+  // find prime nearest to 'num' for maxBlockTime ms
+  // 10 ms gives 5ms of leeway for other calculations before dropping
+  // below 60fps (1000/60 == 16.67), but in reality, the number will
+  // likely be higher due to an 'atomic' big int modPow
+  var maxBlockTime = 10;
+  if('maxBlockTime' in options) {
+    maxBlockTime = options.maxBlockTime;
+  }
+  var start = +new Date();
+  do {
+    // overflow, regenerate random number
+    if(num.bitLength() > bits) {
+      num = generateRandom(bits, rng);
+    }
+    // do primality test
+    if(num.isProbablePrime(mrTests)) {
+      return callback(null, num);
+    }
+    // get next potential prime
+    num.dAddOffset(GCD_30_DELTA[deltaIdx++ % 8], 0);
+  } while(maxBlockTime < 0 || (+new Date() - start < maxBlockTime));
+
+  // keep trying (setImmediate would be better here)
+  forge.util.setImmediate(function() {
+    primeincFindPrimeWithoutWorkers(bits, rng, options, callback);
+  });
+}
+
+function primeincFindPrimeWithWorkers(bits, rng, options, callback) {
+  // web workers unavailable
+  if(typeof Worker === 'undefined') {
+    return primeincFindPrimeWithoutWorkers(bits, rng, options, callback);
+  }
+
+  // initialize random number
+  var num = generateRandom(bits, rng);
+
+  // use web workers to generate keys
+  var numWorkers = options.workers;
+  var workLoad = options.workLoad || 100;
+  var range = workLoad * 30 / 8;
+  var workerScript = options.workerScript || 'forge/prime.worker.js';
+  if(numWorkers === -1) {
+    return forge.util.estimateCores(function(err, cores) {
+      if(err) {
+        // default to 2
+        cores = 2;
+      }
+      numWorkers = cores - 1;
+      generate();
+    });
+  }
+  generate();
+
+  function generate() {
+    // require at least 1 worker
+    numWorkers = Math.max(1, numWorkers);
+
+    // TODO: consider optimizing by starting workers outside getPrime() ...
+    // note that in order to clean up they will have to be made internally
+    // asynchronous which may actually be slower
+
+    // start workers immediately
+    var workers = [];
+    for(var i = 0; i < numWorkers; ++i) {
+      // FIXME: fix path or use blob URLs
+      workers[i] = new Worker(workerScript);
+    }
+    var running = numWorkers;
+
+    // listen for requests from workers and assign ranges to find prime
+    for(var i = 0; i < numWorkers; ++i) {
+      workers[i].addEventListener('message', workerMessage);
+    }
+
+    /* Note: The distribution of random numbers is unknown. Therefore, each
+    web worker is continuously allocated a range of numbers to check for a
+    random number until one is found.
+
+    Every 30 numbers will be checked just 8 times, because prime numbers
+    have the form:
+
+    30k+i, for i < 30 and gcd(30, i)=1 (there are 8 values of i for this)
+
+    Therefore, if we want a web worker to run N checks before asking for
+    a new range of numbers, each range must contain N*30/8 numbers.
+
+    For 100 checks (workLoad), this is a range of 375. */
+
+    var found = false;
+    function workerMessage(e) {
+      // ignore message, prime already found
+      if(found) {
+        return;
+      }
+
+      --running;
+      var data = e.data;
+      if(data.found) {
+        // terminate all workers
+        for(var i = 0; i < workers.length; ++i) {
+          workers[i].terminate();
+        }
+        found = true;
+        return callback(null, new BigInteger(data.prime, 16));
+      }
+
+      // overflow, regenerate random number
+      if(num.bitLength() > bits) {
+        num = generateRandom(bits, rng);
+      }
+
+      // assign new range to check
+      var hex = num.toString(16);
+
+      // start prime search
+      e.target.postMessage({
+        hex: hex,
+        workLoad: workLoad
+      });
+
+      num.dAddOffset(range, 0);
+    }
+  }
+}
+
+/**
+ * Generates a random number using the given number of bits and RNG.
+ *
+ * @param bits the number of bits for the number.
+ * @param rng the random number generator to use.
+ *
+ * @return the random number.
+ */
+function generateRandom(bits, rng) {
+  var num = new BigInteger(bits, rng);
+  // force MSB set
+  var bits1 = bits - 1;
+  if(!num.testBit(bits1)) {
+    num.bitwiseTo(BigInteger.ONE.shiftLeft(bits1), op_or, num);
+  }
+  // align number on 30k+1 boundary
+  num.dAddOffset(31 - num.mod(THIRTY).byteValue(), 0);
+  return num;
+}
+
+/**
+ * Returns the required number of Miller-Rabin tests to generate a
+ * prime with an error probability of (1/2)^80.
+ *
+ * See Handbook of Applied Cryptography Chapter 4, Table 4.4.
+ *
+ * @param bits the bit size.
+ *
+ * @return the required number of iterations.
+ */
+function getMillerRabinTests(bits) {
+  if(bits <= 100) return 27;
+  if(bits <= 150) return 18;
+  if(bits <= 200) return 15;
+  if(bits <= 250) return 12;
+  if(bits <= 300) return 9;
+  if(bits <= 350) return 8;
+  if(bits <= 400) return 7;
+  if(bits <= 500) return 6;
+  if(bits <= 600) return 5;
+  if(bits <= 800) return 4;
+  if(bits <= 1250) return 3;
+  return 2;
+}
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'prime';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './util', './jsbn', './random'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/prime.worker.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/prime.worker.js
new file mode 100644
index 0000000..5fdaa7f
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/prime.worker.js
@@ -0,0 +1,165 @@
+/**
+ * RSA Key Generation Worker.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2013 Digital Bazaar, Inc.
+ */
+importScripts('jsbn.js');
+
+// prime constants
+var LOW_PRIMES = [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,587,593,599,601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691,701,709,719,727,733,739,743,751,757,761,769,773,787,797,809,811,821,823,827,829,839,853,857,859,863,877,881,883,887,907,911,919,929,937,941,947,953,967,971,977,983,991,997];
+var LP_LIMIT = (1 << 26) / LOW_PRIMES[LOW_PRIMES.length - 1];
+
+var BigInteger = forge.jsbn.BigInteger;
+var BIG_TWO = new BigInteger(null);
+BIG_TWO.fromInt(2);
+
+self.addEventListener('message', function(e) {
+  var result = findPrime(e.data);
+  self.postMessage(result);
+});
+
+// start receiving ranges to check
+self.postMessage({found: false});
+
+// primes are 30k+i for i = 1, 7, 11, 13, 17, 19, 23, 29
+var GCD_30_DELTA = [6, 4, 2, 4, 2, 4, 6, 2];
+
+function findPrime(data) {
+  // TODO: abstract based on data.algorithm (PRIMEINC vs. others)
+
+  // create BigInteger from given random bytes
+  var num = new BigInteger(data.hex, 16);
+
+  /* Note: All primes are of the form 30k+i for i < 30 and gcd(30, i)=1. The
+    number we are given is always aligned at 30k + 1. Each time the number is
+    determined not to be prime we add to get to the next 'i', eg: if the number
+    was at 30k + 1 we add 6. */
+  var deltaIdx = 0;
+
+  // find nearest prime
+  var workLoad = data.workLoad;
+  for(var i = 0; i < workLoad; ++i) {
+    // do primality test
+    if(isProbablePrime(num)) {
+      return {found: true, prime: num.toString(16)};
+    }
+    // get next potential prime
+    num.dAddOffset(GCD_30_DELTA[deltaIdx++ % 8], 0);
+  }
+
+  return {found: false};
+}
+
+function isProbablePrime(n) {
+  // divide by low primes, ignore even checks, etc (n alread aligned properly)
+  var i = 1;
+  while(i < LOW_PRIMES.length) {
+    var m = LOW_PRIMES[i];
+    var j = i + 1;
+    while(j < LOW_PRIMES.length && m < LP_LIMIT) {
+      m *= LOW_PRIMES[j++];
+    }
+    m = n.modInt(m);
+    while(i < j) {
+      if(m % LOW_PRIMES[i++] === 0) {
+        return false;
+      }
+    }
+  }
+  return runMillerRabin(n);
+}
+
+// HAC 4.24, Miller-Rabin
+function runMillerRabin(n) {
+  // n1 = n - 1
+  var n1 = n.subtract(BigInteger.ONE);
+
+  // get s and d such that n1 = 2^s * d
+  var s = n1.getLowestSetBit();
+  if(s <= 0) {
+    return false;
+  }
+  var d = n1.shiftRight(s);
+
+  var k = _getMillerRabinTests(n.bitLength());
+  var prng = getPrng();
+  var a;
+  for(var i = 0; i < k; ++i) {
+    // select witness 'a' at random from between 1 and n - 1
+    do {
+      a = new BigInteger(n.bitLength(), prng);
+    } while(a.compareTo(BigInteger.ONE) <= 0 || a.compareTo(n1) >= 0);
+
+    /* See if 'a' is a composite witness. */
+
+    // x = a^d mod n
+    var x = a.modPow(d, n);
+
+    // probably prime
+    if(x.compareTo(BigInteger.ONE) === 0 || x.compareTo(n1) === 0) {
+      continue;
+    }
+
+    var j = s;
+    while(--j) {
+      // x = x^2 mod a
+      x = x.modPowInt(2, n);
+
+      // 'n' is composite because no previous x == -1 mod n
+      if(x.compareTo(BigInteger.ONE) === 0) {
+        return false;
+      }
+      // x == -1 mod n, so probably prime
+      if(x.compareTo(n1) === 0) {
+        break;
+      }
+    }
+
+    // 'x' is first_x^(n1/2) and is not +/- 1, so 'n' is not prime
+    if(j === 0) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+// get pseudo random number generator
+function getPrng() {
+  // create prng with api that matches BigInteger secure random
+  return {
+    // x is an array to fill with bytes
+    nextBytes: function(x) {
+      for(var i = 0; i < x.length; ++i) {
+        x[i] = Math.floor(Math.random() * 0xFF);
+      }
+    }
+  };
+}
+
+/**
+ * Returns the required number of Miller-Rabin tests to generate a
+ * prime with an error probability of (1/2)^80.
+ *
+ * See Handbook of Applied Cryptography Chapter 4, Table 4.4.
+ *
+ * @param bits the bit size.
+ *
+ * @return the required number of iterations.
+ */
+function _getMillerRabinTests(bits) {
+  if(bits <= 100) return 27;
+  if(bits <= 150) return 18;
+  if(bits <= 200) return 15;
+  if(bits <= 250) return 12;
+  if(bits <= 300) return 9;
+  if(bits <= 350) return 8;
+  if(bits <= 400) return 7;
+  if(bits <= 500) return 6;
+  if(bits <= 600) return 5;
+  if(bits <= 800) return 4;
+  if(bits <= 1250) return 3;
+  return 2;
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/prng.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/prng.js
new file mode 100644
index 0000000..72b4594
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/prng.js
@@ -0,0 +1,458 @@
+/**
+ * A javascript implementation of a cryptographically-secure
+ * Pseudo Random Number Generator (PRNG). The Fortuna algorithm is followed
+ * here though the use of SHA-256 is not enforced; when generating an
+ * a PRNG context, the hashing algorithm and block cipher used for
+ * the generator are specified via a plugin.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+var _nodejs = (
+  typeof process !== 'undefined' && process.versions && process.versions.node);
+var _crypto = null;
+if(!forge.disableNativeCode && _nodejs && !process.versions['node-webkit']) {
+  _crypto = require('crypto');
+}
+
+/* PRNG API */
+var prng = forge.prng = forge.prng || {};
+
+/**
+ * Creates a new PRNG context.
+ *
+ * A PRNG plugin must be passed in that will provide:
+ *
+ * 1. A function that initializes the key and seed of a PRNG context. It
+ *   will be given a 16 byte key and a 16 byte seed. Any key expansion
+ *   or transformation of the seed from a byte string into an array of
+ *   integers (or similar) should be performed.
+ * 2. The cryptographic function used by the generator. It takes a key and
+ *   a seed.
+ * 3. A seed increment function. It takes the seed and returns seed + 1.
+ * 4. An api to create a message digest.
+ *
+ * For an example, see random.js.
+ *
+ * @param plugin the PRNG plugin to use.
+ */
+prng.create = function(plugin) {
+  var ctx = {
+    plugin: plugin,
+    key: null,
+    seed: null,
+    time: null,
+    // number of reseeds so far
+    reseeds: 0,
+    // amount of data generated so far
+    generated: 0
+  };
+
+  // create 32 entropy pools (each is a message digest)
+  var md = plugin.md;
+  var pools = new Array(32);
+  for(var i = 0; i < 32; ++i) {
+    pools[i] = md.create();
+  }
+  ctx.pools = pools;
+
+  // entropy pools are written to cyclically, starting at index 0
+  ctx.pool = 0;
+
+  /**
+   * Generates random bytes. The bytes may be generated synchronously or
+   * asynchronously. Web workers must use the asynchronous interface or
+   * else the behavior is undefined.
+   *
+   * @param count the number of random bytes to generate.
+   * @param [callback(err, bytes)] called once the operation completes.
+   *
+   * @return count random bytes as a string.
+   */
+  ctx.generate = function(count, callback) {
+    // do synchronously
+    if(!callback) {
+      return ctx.generateSync(count);
+    }
+
+    // simple generator using counter-based CBC
+    var cipher = ctx.plugin.cipher;
+    var increment = ctx.plugin.increment;
+    var formatKey = ctx.plugin.formatKey;
+    var formatSeed = ctx.plugin.formatSeed;
+    var b = forge.util.createBuffer();
+
+    // reset key for every request
+    ctx.key = null;
+
+    generate();
+
+    function generate(err) {
+      if(err) {
+        return callback(err);
+      }
+
+      // sufficient bytes generated
+      if(b.length() >= count) {
+        return callback(null, b.getBytes(count));
+      }
+
+      // if amount of data generated is greater than 1 MiB, trigger reseed
+      if(ctx.generated > 0xfffff) {
+        ctx.key = null;
+      }
+
+      if(ctx.key === null) {
+        // prevent stack overflow
+        return forge.util.nextTick(function() {
+          _reseed(generate);
+        });
+      }
+
+      // generate the random bytes
+      var bytes = cipher(ctx.key, ctx.seed);
+      ctx.generated += bytes.length;
+      b.putBytes(bytes);
+
+      // generate bytes for a new key and seed
+      ctx.key = formatKey(cipher(ctx.key, increment(ctx.seed)));
+      ctx.seed = formatSeed(cipher(ctx.key, ctx.seed));
+
+      forge.util.setImmediate(generate);
+    }
+  };
+
+  /**
+   * Generates random bytes synchronously.
+   *
+   * @param count the number of random bytes to generate.
+   *
+   * @return count random bytes as a string.
+   */
+  ctx.generateSync = function(count) {
+    // simple generator using counter-based CBC
+    var cipher = ctx.plugin.cipher;
+    var increment = ctx.plugin.increment;
+    var formatKey = ctx.plugin.formatKey;
+    var formatSeed = ctx.plugin.formatSeed;
+
+    // reset key for every request
+    ctx.key = null;
+
+    var b = forge.util.createBuffer();
+    while(b.length() < count) {
+      // if amount of data generated is greater than 1 MiB, trigger reseed
+      if(ctx.generated > 0xfffff) {
+        ctx.key = null;
+      }
+
+      if(ctx.key === null) {
+        _reseedSync();
+      }
+
+      // generate the random bytes
+      var bytes = cipher(ctx.key, ctx.seed);
+      ctx.generated += bytes.length;
+      b.putBytes(bytes);
+
+      // generate bytes for a new key and seed
+      ctx.key = formatKey(cipher(ctx.key, increment(ctx.seed)));
+      ctx.seed = formatSeed(cipher(ctx.key, ctx.seed));
+    }
+
+    return b.getBytes(count);
+  };
+
+  /**
+   * Private function that asynchronously reseeds a generator.
+   *
+   * @param callback(err) called once the operation completes.
+   */
+  function _reseed(callback) {
+    if(ctx.pools[0].messageLength >= 32) {
+      _seed();
+      return callback();
+    }
+    // not enough seed data...
+    var needed = (32 - ctx.pools[0].messageLength) << 5;
+    ctx.seedFile(needed, function(err, bytes) {
+      if(err) {
+        return callback(err);
+      }
+      ctx.collect(bytes);
+      _seed();
+      callback();
+    });
+  }
+
+  /**
+   * Private function that synchronously reseeds a generator.
+   */
+  function _reseedSync() {
+    if(ctx.pools[0].messageLength >= 32) {
+      return _seed();
+    }
+    // not enough seed data...
+    var needed = (32 - ctx.pools[0].messageLength) << 5;
+    ctx.collect(ctx.seedFileSync(needed));
+    _seed();
+  }
+
+  /**
+   * Private function that seeds a generator once enough bytes are available.
+   */
+  function _seed() {
+    // create a plugin-based message digest
+    var md = ctx.plugin.md.create();
+
+    // digest pool 0's entropy and restart it
+    md.update(ctx.pools[0].digest().getBytes());
+    ctx.pools[0].start();
+
+    // digest the entropy of other pools whose index k meet the
+    // condition '2^k mod n == 0' where n is the number of reseeds
+    var k = 1;
+    for(var i = 1; i < 32; ++i) {
+      // prevent signed numbers from being used
+      k = (k === 31) ? 0x80000000 : (k << 2);
+      if(k % ctx.reseeds === 0) {
+        md.update(ctx.pools[i].digest().getBytes());
+        ctx.pools[i].start();
+      }
+    }
+
+    // get digest for key bytes and iterate again for seed bytes
+    var keyBytes = md.digest().getBytes();
+    md.start();
+    md.update(keyBytes);
+    var seedBytes = md.digest().getBytes();
+
+    // update
+    ctx.key = ctx.plugin.formatKey(keyBytes);
+    ctx.seed = ctx.plugin.formatSeed(seedBytes);
+    ctx.reseeds = (ctx.reseeds === 0xffffffff) ? 0 : ctx.reseeds + 1;
+    ctx.generated = 0;
+  }
+
+  /**
+   * The built-in default seedFile. This seedFile is used when entropy
+   * is needed immediately.
+   *
+   * @param needed the number of bytes that are needed.
+   *
+   * @return the random bytes.
+   */
+  function defaultSeedFile(needed) {
+    // use window.crypto.getRandomValues strong source of entropy if available
+    var getRandomValues = null;
+    if(typeof window !== 'undefined') {
+      var _crypto = window.crypto || window.msCrypto;
+      if(_crypto && _crypto.getRandomValues) {
+        getRandomValues = function(arr) {
+          return _crypto.getRandomValues(arr);
+        };
+      }
+    }
+
+    var b = forge.util.createBuffer();
+    if(getRandomValues) {
+      while(b.length() < needed) {
+        // max byte length is 65536 before QuotaExceededError is thrown
+        // http://www.w3.org/TR/WebCryptoAPI/#RandomSource-method-getRandomValues
+        var count = Math.max(1, Math.min(needed - b.length(), 65536) / 4);
+        var entropy = new Uint32Array(Math.floor(count));
+        try {
+          getRandomValues(entropy);
+          for(var i = 0; i < entropy.length; ++i) {
+            b.putInt32(entropy[i]);
+          }
+        } catch(e) {
+          /* only ignore QuotaExceededError */
+          if(!(typeof QuotaExceededError !== 'undefined' &&
+            e instanceof QuotaExceededError)) {
+            throw e;
+          }
+        }
+      }
+    }
+
+    // be sad and add some weak random data
+    if(b.length() < needed) {
+      /* Draws from Park-Miller "minimal standard" 31 bit PRNG,
+      implemented with David G. Carta's optimization: with 32 bit math
+      and without division (Public Domain). */
+      var hi, lo, next;
+      var seed = Math.floor(Math.random() * 0x010000);
+      while(b.length() < needed) {
+        lo = 16807 * (seed & 0xFFFF);
+        hi = 16807 * (seed >> 16);
+        lo += (hi & 0x7FFF) << 16;
+        lo += hi >> 15;
+        lo = (lo & 0x7FFFFFFF) + (lo >> 31);
+        seed = lo & 0xFFFFFFFF;
+
+        // consume lower 3 bytes of seed
+        for(var i = 0; i < 3; ++i) {
+          // throw in more pseudo random
+          next = seed >>> (i << 3);
+          next ^= Math.floor(Math.random() * 0x0100);
+          b.putByte(String.fromCharCode(next & 0xFF));
+        }
+      }
+    }
+
+    return b.getBytes(needed);
+  }
+  // initialize seed file APIs
+  if(_crypto) {
+    // use nodejs async API
+    ctx.seedFile = function(needed, callback) {
+      _crypto.randomBytes(needed, function(err, bytes) {
+        if(err) {
+          return callback(err);
+        }
+        callback(null, bytes.toString());
+      });
+    };
+    // use nodejs sync API
+    ctx.seedFileSync = function(needed) {
+      return _crypto.randomBytes(needed).toString();
+    };
+  } else {
+    ctx.seedFile = function(needed, callback) {
+      try {
+        callback(null, defaultSeedFile(needed));
+      } catch(e) {
+        callback(e);
+      }
+    };
+    ctx.seedFileSync = defaultSeedFile;
+  }
+
+  /**
+   * Adds entropy to a prng ctx's accumulator.
+   *
+   * @param bytes the bytes of entropy as a string.
+   */
+  ctx.collect = function(bytes) {
+    // iterate over pools distributing entropy cyclically
+    var count = bytes.length;
+    for(var i = 0; i < count; ++i) {
+      ctx.pools[ctx.pool].update(bytes.substr(i, 1));
+      ctx.pool = (ctx.pool === 31) ? 0 : ctx.pool + 1;
+    }
+  };
+
+  /**
+   * Collects an integer of n bits.
+   *
+   * @param i the integer entropy.
+   * @param n the number of bits in the integer.
+   */
+  ctx.collectInt = function(i, n) {
+    var bytes = '';
+    for(var x = 0; x < n; x += 8) {
+      bytes += String.fromCharCode((i >> x) & 0xFF);
+    }
+    ctx.collect(bytes);
+  };
+
+  /**
+   * Registers a Web Worker to receive immediate entropy from the main thread.
+   * This method is required until Web Workers can access the native crypto
+   * API. This method should be called twice for each created worker, once in
+   * the main thread, and once in the worker itself.
+   *
+   * @param worker the worker to register.
+   */
+  ctx.registerWorker = function(worker) {
+    // worker receives random bytes
+    if(worker === self) {
+      ctx.seedFile = function(needed, callback) {
+        function listener(e) {
+          var data = e.data;
+          if(data.forge && data.forge.prng) {
+            self.removeEventListener('message', listener);
+            callback(data.forge.prng.err, data.forge.prng.bytes);
+          }
+        }
+        self.addEventListener('message', listener);
+        self.postMessage({forge: {prng: {needed: needed}}});
+      };
+    } else {
+      // main thread sends random bytes upon request
+      var listener = function(e) {
+        var data = e.data;
+        if(data.forge && data.forge.prng) {
+          ctx.seedFile(data.forge.prng.needed, function(err, bytes) {
+            worker.postMessage({forge: {prng: {err: err, bytes: bytes}}});
+          });
+        }
+      };
+      // TODO: do we need to remove the event listener when the worker dies?
+      worker.addEventListener('message', listener);
+    }
+  };
+
+  return ctx;
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'prng';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './md', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/pss.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/pss.js
new file mode 100644
index 0000000..1b284fc
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/pss.js
@@ -0,0 +1,295 @@
+/**
+ * Javascript implementation of PKCS#1 PSS signature padding.
+ *
+ * @author Stefan Siegl
+ *
+ * Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+// shortcut for PSS API
+var pss = forge.pss = forge.pss || {};
+
+/**
+ * Creates a PSS signature scheme object.
+ *
+ * There are several ways to provide a salt for encoding:
+ *
+ * 1. Specify the saltLength only and the built-in PRNG will generate it.
+ * 2. Specify the saltLength and a custom PRNG with 'getBytesSync' defined that
+ *   will be used.
+ * 3. Specify the salt itself as a forge.util.ByteBuffer.
+ *
+ * @param options the options to use:
+ *          md the message digest object to use, a forge md instance.
+ *          mgf the mask generation function to use, a forge mgf instance.
+ *          [saltLength] the length of the salt in octets.
+ *          [prng] the pseudo-random number generator to use to produce a salt.
+ *          [salt] the salt to use when encoding.
+ *
+ * @return a signature scheme object.
+ */
+pss.create = function(options) {
+  // backwards compatibility w/legacy args: hash, mgf, sLen
+  if(arguments.length === 3) {
+    options = {
+      md: arguments[0],
+      mgf: arguments[1],
+      saltLength: arguments[2]
+    };
+  }
+
+  var hash = options.md;
+  var mgf = options.mgf;
+  var hLen = hash.digestLength;
+
+  var salt_ = options.salt || null;
+  if(typeof salt_ === 'string') {
+    // assume binary-encoded string
+    salt_ = forge.util.createBuffer(salt_);
+  }
+
+  var sLen;
+  if('saltLength' in options) {
+    sLen = options.saltLength;
+  } else if(salt_ !== null) {
+    sLen = salt_.length();
+  } else {
+    throw new Error('Salt length not specified or specific salt not given.');
+  }
+
+  if(salt_ !== null && salt_.length() !== sLen) {
+    throw new Error('Given salt length does not match length of given salt.');
+  }
+
+  var prng = options.prng || forge.random;
+
+  var pssobj = {};
+
+  /**
+   * Encodes a PSS signature.
+   *
+   * This function implements EMSA-PSS-ENCODE as per RFC 3447, section 9.1.1.
+   *
+   * @param md the message digest object with the hash to sign.
+   * @param modsBits the length of the RSA modulus in bits.
+   *
+   * @return the encoded message as a binary-encoded string of length
+   *           ceil((modBits - 1) / 8).
+   */
+  pssobj.encode = function(md, modBits) {
+    var i;
+    var emBits = modBits - 1;
+    var emLen = Math.ceil(emBits / 8);
+
+    /* 2. Let mHash = Hash(M), an octet string of length hLen. */
+    var mHash = md.digest().getBytes();
+
+    /* 3. If emLen < hLen + sLen + 2, output "encoding error" and stop. */
+    if(emLen < hLen + sLen + 2) {
+      throw new Error('Message is too long to encrypt.');
+    }
+
+    /* 4. Generate a random octet string salt of length sLen; if sLen = 0,
+     *    then salt is the empty string. */
+    var salt;
+    if(salt_ === null) {
+      salt = prng.getBytesSync(sLen);
+    } else {
+      salt = salt_.bytes();
+    }
+
+    /* 5. Let M' = (0x)00 00 00 00 00 00 00 00 || mHash || salt; */
+    var m_ = new forge.util.ByteBuffer();
+    m_.fillWithByte(0, 8);
+    m_.putBytes(mHash);
+    m_.putBytes(salt);
+
+    /* 6. Let H = Hash(M'), an octet string of length hLen. */
+    hash.start();
+    hash.update(m_.getBytes());
+    var h = hash.digest().getBytes();
+
+    /* 7. Generate an octet string PS consisting of emLen - sLen - hLen - 2
+     *    zero octets.  The length of PS may be 0. */
+    var ps = new forge.util.ByteBuffer();
+    ps.fillWithByte(0, emLen - sLen - hLen - 2);
+
+    /* 8. Let DB = PS || 0x01 || salt; DB is an octet string of length
+     *    emLen - hLen - 1. */
+    ps.putByte(0x01);
+    ps.putBytes(salt);
+    var db = ps.getBytes();
+
+    /* 9. Let dbMask = MGF(H, emLen - hLen - 1). */
+    var maskLen = emLen - hLen - 1;
+    var dbMask = mgf.generate(h, maskLen);
+
+    /* 10. Let maskedDB = DB \xor dbMask. */
+    var maskedDB = '';
+    for(i = 0; i < maskLen; i ++) {
+      maskedDB += String.fromCharCode(db.charCodeAt(i) ^ dbMask.charCodeAt(i));
+    }
+
+    /* 11. Set the leftmost 8emLen - emBits bits of the leftmost octet in
+     *     maskedDB to zero. */
+    var mask = (0xFF00 >> (8 * emLen - emBits)) & 0xFF;
+    maskedDB = String.fromCharCode(maskedDB.charCodeAt(0) & ~mask) +
+      maskedDB.substr(1);
+
+    /* 12. Let EM = maskedDB || H || 0xbc.
+     * 13. Output EM. */
+    return maskedDB + h + String.fromCharCode(0xbc);
+  };
+
+  /**
+   * Verifies a PSS signature.
+   *
+   * This function implements EMSA-PSS-VERIFY as per RFC 3447, section 9.1.2.
+   *
+   * @param mHash the message digest hash, as a binary-encoded string, to
+   *         compare against the signature.
+   * @param em the encoded message, as a binary-encoded string
+   *          (RSA decryption result).
+   * @param modsBits the length of the RSA modulus in bits.
+   *
+   * @return true if the signature was verified, false if not.
+   */
+  pssobj.verify = function(mHash, em, modBits) {
+    var i;
+    var emBits = modBits - 1;
+    var emLen = Math.ceil(emBits / 8);
+
+    /* c. Convert the message representative m to an encoded message EM
+     *    of length emLen = ceil((modBits - 1) / 8) octets, where modBits
+     *    is the length in bits of the RSA modulus n */
+    em = em.substr(-emLen);
+
+    /* 3. If emLen < hLen + sLen + 2, output "inconsistent" and stop. */
+    if(emLen < hLen + sLen + 2) {
+      throw new Error('Inconsistent parameters to PSS signature verification.');
+    }
+
+    /* 4. If the rightmost octet of EM does not have hexadecimal value
+     *    0xbc, output "inconsistent" and stop. */
+    if(em.charCodeAt(emLen - 1) !== 0xbc) {
+      throw new Error('Encoded message does not end in 0xBC.');
+    }
+
+    /* 5. Let maskedDB be the leftmost emLen - hLen - 1 octets of EM, and
+     *    let H be the next hLen octets. */
+    var maskLen = emLen - hLen - 1;
+    var maskedDB = em.substr(0, maskLen);
+    var h = em.substr(maskLen, hLen);
+
+    /* 6. If the leftmost 8emLen - emBits bits of the leftmost octet in
+     *    maskedDB are not all equal to zero, output "inconsistent" and stop. */
+    var mask = (0xFF00 >> (8 * emLen - emBits)) & 0xFF;
+    if((maskedDB.charCodeAt(0) & mask) !== 0) {
+      throw new Error('Bits beyond keysize not zero as expected.');
+    }
+
+    /* 7. Let dbMask = MGF(H, emLen - hLen - 1). */
+    var dbMask = mgf.generate(h, maskLen);
+
+    /* 8. Let DB = maskedDB \xor dbMask. */
+    var db = '';
+    for(i = 0; i < maskLen; i ++) {
+      db += String.fromCharCode(maskedDB.charCodeAt(i) ^ dbMask.charCodeAt(i));
+    }
+
+    /* 9. Set the leftmost 8emLen - emBits bits of the leftmost octet
+     * in DB to zero. */
+    db = String.fromCharCode(db.charCodeAt(0) & ~mask) + db.substr(1);
+
+    /* 10. If the emLen - hLen - sLen - 2 leftmost octets of DB are not zero
+     * or if the octet at position emLen - hLen - sLen - 1 (the leftmost
+     * position is "position 1") does not have hexadecimal value 0x01,
+     * output "inconsistent" and stop. */
+    var checkLen = emLen - hLen - sLen - 2;
+    for(i = 0; i < checkLen; i ++) {
+      if(db.charCodeAt(i) !== 0x00) {
+        throw new Error('Leftmost octets not zero as expected');
+      }
+    }
+
+    if(db.charCodeAt(checkLen) !== 0x01) {
+      throw new Error('Inconsistent PSS signature, 0x01 marker not found');
+    }
+
+    /* 11. Let salt be the last sLen octets of DB. */
+    var salt = db.substr(-sLen);
+
+    /* 12.  Let M' = (0x)00 00 00 00 00 00 00 00 || mHash || salt */
+    var m_ = new forge.util.ByteBuffer();
+    m_.fillWithByte(0, 8);
+    m_.putBytes(mHash);
+    m_.putBytes(salt);
+
+    /* 13. Let H' = Hash(M'), an octet string of length hLen. */
+    hash.start();
+    hash.update(m_.getBytes());
+    var h_ = hash.digest().getBytes();
+
+    /* 14. If H = H', output "consistent." Otherwise, output "inconsistent." */
+    return h === h_;
+  };
+
+  return pssobj;
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'pss';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './random', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/random.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/random.js
new file mode 100644
index 0000000..febc1fd
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/random.js
@@ -0,0 +1,237 @@
+/**
+ * An API for getting cryptographically-secure random bytes. The bytes are
+ * generated using the Fortuna algorithm devised by Bruce Schneier and
+ * Niels Ferguson.
+ *
+ * Getting strong random bytes is not yet easy to do in javascript. The only
+ * truish random entropy that can be collected is from the mouse, keyboard, or
+ * from timing with respect to page loads, etc. This generator makes a poor
+ * attempt at providing random bytes when those sources haven't yet provided
+ * enough entropy to initially seed or to reseed the PRNG.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2009-2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+// forge.random already defined
+if(forge.random && forge.random.getBytes) {
+  return;
+}
+
+(function(jQuery) {
+
+// the default prng plugin, uses AES-128
+var prng_aes = {};
+var _prng_aes_output = new Array(4);
+var _prng_aes_buffer = forge.util.createBuffer();
+prng_aes.formatKey = function(key) {
+  // convert the key into 32-bit integers
+  var tmp = forge.util.createBuffer(key);
+  key = new Array(4);
+  key[0] = tmp.getInt32();
+  key[1] = tmp.getInt32();
+  key[2] = tmp.getInt32();
+  key[3] = tmp.getInt32();
+
+  // return the expanded key
+  return forge.aes._expandKey(key, false);
+};
+prng_aes.formatSeed = function(seed) {
+  // convert seed into 32-bit integers
+  var tmp = forge.util.createBuffer(seed);
+  seed = new Array(4);
+  seed[0] = tmp.getInt32();
+  seed[1] = tmp.getInt32();
+  seed[2] = tmp.getInt32();
+  seed[3] = tmp.getInt32();
+  return seed;
+};
+prng_aes.cipher = function(key, seed) {
+  forge.aes._updateBlock(key, seed, _prng_aes_output, false);
+  _prng_aes_buffer.putInt32(_prng_aes_output[0]);
+  _prng_aes_buffer.putInt32(_prng_aes_output[1]);
+  _prng_aes_buffer.putInt32(_prng_aes_output[2]);
+  _prng_aes_buffer.putInt32(_prng_aes_output[3]);
+  return _prng_aes_buffer.getBytes();
+};
+prng_aes.increment = function(seed) {
+  // FIXME: do we care about carry or signed issues?
+  ++seed[3];
+  return seed;
+};
+prng_aes.md = forge.md.sha256;
+
+/**
+ * Creates a new PRNG.
+ */
+function spawnPrng() {
+  var ctx = forge.prng.create(prng_aes);
+
+  /**
+   * Gets random bytes. If a native secure crypto API is unavailable, this
+   * method tries to make the bytes more unpredictable by drawing from data that
+   * can be collected from the user of the browser, eg: mouse movement.
+   *
+   * If a callback is given, this method will be called asynchronously.
+   *
+   * @param count the number of random bytes to get.
+   * @param [callback(err, bytes)] called once the operation completes.
+   *
+   * @return the random bytes in a string.
+   */
+  ctx.getBytes = function(count, callback) {
+    return ctx.generate(count, callback);
+  };
+
+  /**
+   * Gets random bytes asynchronously. If a native secure crypto API is
+   * unavailable, this method tries to make the bytes more unpredictable by
+   * drawing from data that can be collected from the user of the browser,
+   * eg: mouse movement.
+   *
+   * @param count the number of random bytes to get.
+   *
+   * @return the random bytes in a string.
+   */
+  ctx.getBytesSync = function(count) {
+    return ctx.generate(count);
+  };
+
+  return ctx;
+}
+
+// create default prng context
+var _ctx = spawnPrng();
+
+// add other sources of entropy only if window.crypto.getRandomValues is not
+// available -- otherwise this source will be automatically used by the prng
+var _nodejs = (
+  typeof process !== 'undefined' && process.versions && process.versions.node);
+var getRandomValues = null;
+if(typeof window !== 'undefined') {
+  var _crypto = window.crypto || window.msCrypto;
+  if(_crypto && _crypto.getRandomValues) {
+    getRandomValues = function(arr) {
+      return _crypto.getRandomValues(arr);
+    };
+  }
+}
+if(forge.disableNativeCode || (!_nodejs && !getRandomValues)) {
+  // if this is a web worker, do not use weak entropy, instead register to
+  // receive strong entropy asynchronously from the main thread
+  if(typeof window === 'undefined' || window.document === undefined) {
+    // FIXME:
+  }
+
+  // get load time entropy
+  _ctx.collectInt(+new Date(), 32);
+
+  // add some entropy from navigator object
+  if(typeof(navigator) !== 'undefined') {
+    var _navBytes = '';
+    for(var key in navigator) {
+      try {
+        if(typeof(navigator[key]) == 'string') {
+          _navBytes += navigator[key];
+        }
+      } catch(e) {
+        /* Some navigator keys might not be accessible, e.g. the geolocation
+          attribute throws an exception if touched in Mozilla chrome://
+          context.
+
+          Silently ignore this and just don't use this as a source of
+          entropy. */
+      }
+    }
+    _ctx.collect(_navBytes);
+    _navBytes = null;
+  }
+
+  // add mouse and keyboard collectors if jquery is available
+  if(jQuery) {
+    // set up mouse entropy capture
+    jQuery().mousemove(function(e) {
+      // add mouse coords
+      _ctx.collectInt(e.clientX, 16);
+      _ctx.collectInt(e.clientY, 16);
+    });
+
+    // set up keyboard entropy capture
+    jQuery().keypress(function(e) {
+      _ctx.collectInt(e.charCode, 8);
+    });
+  }
+}
+
+/* Random API */
+if(!forge.random) {
+  forge.random = _ctx;
+} else {
+  // extend forge.random with _ctx
+  for(var key in _ctx) {
+    forge.random[key] = _ctx[key];
+  }
+}
+
+// expose spawn PRNG
+forge.random.createInstance = spawnPrng;
+
+})(typeof(jQuery) !== 'undefined' ? jQuery : null);
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'random';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './aes', './md', './prng', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/rc2.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/rc2.js
new file mode 100644
index 0000000..0a67011
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/rc2.js
@@ -0,0 +1,470 @@
+/**
+ * RC2 implementation.
+ *
+ * @author Stefan Siegl
+ *
+ * Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
+ *
+ * Information on the RC2 cipher is available from RFC #2268,
+ * http://www.ietf.org/rfc/rfc2268.txt
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+var piTable = [
+  0xd9, 0x78, 0xf9, 0xc4, 0x19, 0xdd, 0xb5, 0xed, 0x28, 0xe9, 0xfd, 0x79, 0x4a, 0xa0, 0xd8, 0x9d,
+  0xc6, 0x7e, 0x37, 0x83, 0x2b, 0x76, 0x53, 0x8e, 0x62, 0x4c, 0x64, 0x88, 0x44, 0x8b, 0xfb, 0xa2,
+  0x17, 0x9a, 0x59, 0xf5, 0x87, 0xb3, 0x4f, 0x13, 0x61, 0x45, 0x6d, 0x8d, 0x09, 0x81, 0x7d, 0x32,
+  0xbd, 0x8f, 0x40, 0xeb, 0x86, 0xb7, 0x7b, 0x0b, 0xf0, 0x95, 0x21, 0x22, 0x5c, 0x6b, 0x4e, 0x82,
+  0x54, 0xd6, 0x65, 0x93, 0xce, 0x60, 0xb2, 0x1c, 0x73, 0x56, 0xc0, 0x14, 0xa7, 0x8c, 0xf1, 0xdc,
+  0x12, 0x75, 0xca, 0x1f, 0x3b, 0xbe, 0xe4, 0xd1, 0x42, 0x3d, 0xd4, 0x30, 0xa3, 0x3c, 0xb6, 0x26,
+  0x6f, 0xbf, 0x0e, 0xda, 0x46, 0x69, 0x07, 0x57, 0x27, 0xf2, 0x1d, 0x9b, 0xbc, 0x94, 0x43, 0x03,
+  0xf8, 0x11, 0xc7, 0xf6, 0x90, 0xef, 0x3e, 0xe7, 0x06, 0xc3, 0xd5, 0x2f, 0xc8, 0x66, 0x1e, 0xd7,
+  0x08, 0xe8, 0xea, 0xde, 0x80, 0x52, 0xee, 0xf7, 0x84, 0xaa, 0x72, 0xac, 0x35, 0x4d, 0x6a, 0x2a,
+  0x96, 0x1a, 0xd2, 0x71, 0x5a, 0x15, 0x49, 0x74, 0x4b, 0x9f, 0xd0, 0x5e, 0x04, 0x18, 0xa4, 0xec,
+  0xc2, 0xe0, 0x41, 0x6e, 0x0f, 0x51, 0xcb, 0xcc, 0x24, 0x91, 0xaf, 0x50, 0xa1, 0xf4, 0x70, 0x39,
+  0x99, 0x7c, 0x3a, 0x85, 0x23, 0xb8, 0xb4, 0x7a, 0xfc, 0x02, 0x36, 0x5b, 0x25, 0x55, 0x97, 0x31,
+  0x2d, 0x5d, 0xfa, 0x98, 0xe3, 0x8a, 0x92, 0xae, 0x05, 0xdf, 0x29, 0x10, 0x67, 0x6c, 0xba, 0xc9,
+  0xd3, 0x00, 0xe6, 0xcf, 0xe1, 0x9e, 0xa8, 0x2c, 0x63, 0x16, 0x01, 0x3f, 0x58, 0xe2, 0x89, 0xa9,
+  0x0d, 0x38, 0x34, 0x1b, 0xab, 0x33, 0xff, 0xb0, 0xbb, 0x48, 0x0c, 0x5f, 0xb9, 0xb1, 0xcd, 0x2e,
+  0xc5, 0xf3, 0xdb, 0x47, 0xe5, 0xa5, 0x9c, 0x77, 0x0a, 0xa6, 0x20, 0x68, 0xfe, 0x7f, 0xc1, 0xad
+];
+
+var s = [1, 2, 3, 5];
+
+
+/**
+ * Rotate a word left by given number of bits.
+ *
+ * Bits that are shifted out on the left are put back in on the right
+ * hand side.
+ *
+ * @param word The word to shift left.
+ * @param bits The number of bits to shift by.
+ * @return The rotated word.
+ */
+var rol = function(word, bits) {
+  return ((word << bits) & 0xffff) | ((word & 0xffff) >> (16 - bits));
+};
+
+/**
+ * Rotate a word right by given number of bits.
+ *
+ * Bits that are shifted out on the right are put back in on the left
+ * hand side.
+ *
+ * @param word The word to shift right.
+ * @param bits The number of bits to shift by.
+ * @return The rotated word.
+ */
+var ror = function(word, bits) {
+  return ((word & 0xffff) >> bits) | ((word << (16 - bits)) & 0xffff);
+};
+
+
+/* RC2 API */
+forge.rc2 = forge.rc2 || {};
+
+/**
+ * Perform RC2 key expansion as per RFC #2268, section 2.
+ *
+ * @param key variable-length user key (between 1 and 128 bytes)
+ * @param effKeyBits number of effective key bits (default: 128)
+ * @return the expanded RC2 key (ByteBuffer of 128 bytes)
+ */
+forge.rc2.expandKey = function(key, effKeyBits) {
+  if(typeof key === 'string') {
+    key = forge.util.createBuffer(key);
+  }
+  effKeyBits = effKeyBits || 128;
+
+  /* introduce variables that match the names used in RFC #2268 */
+  var L = key;
+  var T = key.length();
+  var T1 = effKeyBits;
+  var T8 = Math.ceil(T1 / 8);
+  var TM = 0xff >> (T1 & 0x07);
+  var i;
+
+  for(i = T; i < 128; i ++) {
+    L.putByte(piTable[(L.at(i - 1) + L.at(i - T)) & 0xff]);
+  }
+
+  L.setAt(128 - T8, piTable[L.at(128 - T8) & TM]);
+
+  for(i = 127 - T8; i >= 0; i --) {
+    L.setAt(i, piTable[L.at(i + 1) ^ L.at(i + T8)]);
+  }
+
+  return L;
+};
+
+
+/**
+ * Creates a RC2 cipher object.
+ *
+ * @param key the symmetric key to use (as base for key generation).
+ * @param bits the number of effective key bits.
+ * @param encrypt false for decryption, true for encryption.
+ *
+ * @return the cipher.
+ */
+var createCipher = function(key, bits, encrypt) {
+  var _finish = false, _input = null, _output = null, _iv = null;
+  var mixRound, mashRound;
+  var i, j, K = [];
+
+  /* Expand key and fill into K[] Array */
+  key = forge.rc2.expandKey(key, bits);
+  for(i = 0; i < 64; i ++) {
+    K.push(key.getInt16Le());
+  }
+
+  if(encrypt) {
+    /**
+     * Perform one mixing round "in place".
+     *
+     * @param R Array of four words to perform mixing on.
+     */
+    mixRound = function(R) {
+      for(i = 0; i < 4; i++) {
+        R[i] += K[j] + (R[(i + 3) % 4] & R[(i + 2) % 4]) +
+          ((~R[(i + 3) % 4]) & R[(i + 1) % 4]);
+        R[i] = rol(R[i], s[i]);
+        j ++;
+      }
+    };
+
+    /**
+     * Perform one mashing round "in place".
+     *
+     * @param R Array of four words to perform mashing on.
+     */
+    mashRound = function(R) {
+      for(i = 0; i < 4; i ++) {
+        R[i] += K[R[(i + 3) % 4] & 63];
+      }
+    };
+  } else {
+    /**
+     * Perform one r-mixing round "in place".
+     *
+     * @param R Array of four words to perform mixing on.
+     */
+    mixRound = function(R) {
+      for(i = 3; i >= 0; i--) {
+        R[i] = ror(R[i], s[i]);
+        R[i] -= K[j] + (R[(i + 3) % 4] & R[(i + 2) % 4]) +
+          ((~R[(i + 3) % 4]) & R[(i + 1) % 4]);
+        j --;
+      }
+    };
+
+    /**
+     * Perform one r-mashing round "in place".
+     *
+     * @param R Array of four words to perform mashing on.
+     */
+    mashRound = function(R) {
+      for(i = 3; i >= 0; i--) {
+        R[i] -= K[R[(i + 3) % 4] & 63];
+      }
+    };
+  }
+
+  /**
+   * Run the specified cipher execution plan.
+   *
+   * This function takes four words from the input buffer, applies the IV on
+   * it (if requested) and runs the provided execution plan.
+   *
+   * The plan must be put together in form of a array of arrays.  Where the
+   * outer one is simply a list of steps to perform and the inner one needs
+   * to have two elements: the first one telling how many rounds to perform,
+   * the second one telling what to do (i.e. the function to call).
+   *
+   * @param {Array} plan The plan to execute.
+   */
+  var runPlan = function(plan) {
+    var R = [];
+
+    /* Get data from input buffer and fill the four words into R */
+    for(i = 0; i < 4; i ++) {
+      var val = _input.getInt16Le();
+
+      if(_iv !== null) {
+        if(encrypt) {
+          /* We're encrypting, apply the IV first. */
+          val ^= _iv.getInt16Le();
+        } else {
+          /* We're decryption, keep cipher text for next block. */
+          _iv.putInt16Le(val);
+        }
+      }
+
+      R.push(val & 0xffff);
+    }
+
+    /* Reset global "j" variable as per spec. */
+    j = encrypt ? 0 : 63;
+
+    /* Run execution plan. */
+    for(var ptr = 0; ptr < plan.length; ptr ++) {
+      for(var ctr = 0; ctr < plan[ptr][0]; ctr ++) {
+        plan[ptr][1](R);
+      }
+    }
+
+    /* Write back result to output buffer. */
+    for(i = 0; i < 4; i ++) {
+      if(_iv !== null) {
+        if(encrypt) {
+          /* We're encrypting in CBC-mode, feed back encrypted bytes into
+             IV buffer to carry it forward to next block. */
+          _iv.putInt16Le(R[i]);
+        } else {
+          R[i] ^= _iv.getInt16Le();
+        }
+      }
+
+      _output.putInt16Le(R[i]);
+    }
+  };
+
+
+  /* Create cipher object */
+  var cipher = null;
+  cipher = {
+    /**
+     * Starts or restarts the encryption or decryption process, whichever
+     * was previously configured.
+     *
+     * To use the cipher in CBC mode, iv may be given either as a string
+     * of bytes, or as a byte buffer.  For ECB mode, give null as iv.
+     *
+     * @param iv the initialization vector to use, null for ECB mode.
+     * @param output the output the buffer to write to, null to create one.
+     */
+    start: function(iv, output) {
+      if(iv) {
+        /* CBC mode */
+        if(typeof iv === 'string') {
+          iv = forge.util.createBuffer(iv);
+        }
+      }
+
+      _finish = false;
+      _input = forge.util.createBuffer();
+      _output = output || new forge.util.createBuffer();
+      _iv = iv;
+
+      cipher.output = _output;
+    },
+
+    /**
+     * Updates the next block.
+     *
+     * @param input the buffer to read from.
+     */
+    update: function(input) {
+      if(!_finish) {
+        // not finishing, so fill the input buffer with more input
+        _input.putBuffer(input);
+      }
+
+      while(_input.length() >= 8) {
+        runPlan([
+            [ 5, mixRound ],
+            [ 1, mashRound ],
+            [ 6, mixRound ],
+            [ 1, mashRound ],
+            [ 5, mixRound ]
+          ]);
+      }
+    },
+
+    /**
+     * Finishes encrypting or decrypting.
+     *
+     * @param pad a padding function to use, null for PKCS#7 padding,
+     *           signature(blockSize, buffer, decrypt).
+     *
+     * @return true if successful, false on error.
+     */
+    finish: function(pad) {
+      var rval = true;
+
+      if(encrypt) {
+        if(pad) {
+          rval = pad(8, _input, !encrypt);
+        } else {
+          // add PKCS#7 padding to block (each pad byte is the
+          // value of the number of pad bytes)
+          var padding = (_input.length() === 8) ? 8 : (8 - _input.length());
+          _input.fillWithByte(padding, padding);
+        }
+      }
+
+      if(rval) {
+        // do final update
+        _finish = true;
+        cipher.update();
+      }
+
+      if(!encrypt) {
+        // check for error: input data not a multiple of block size
+        rval = (_input.length() === 0);
+        if(rval) {
+          if(pad) {
+            rval = pad(8, _output, !encrypt);
+          } else {
+            // ensure padding byte count is valid
+            var len = _output.length();
+            var count = _output.at(len - 1);
+
+            if(count > len) {
+              rval = false;
+            } else {
+              // trim off padding bytes
+              _output.truncate(count);
+            }
+          }
+        }
+      }
+
+      return rval;
+    }
+  };
+
+  return cipher;
+};
+
+
+/**
+ * Creates an RC2 cipher object to encrypt data in ECB or CBC mode using the
+ * given symmetric key. The output will be stored in the 'output' member
+ * of the returned cipher.
+ *
+ * The key and iv may be given as a string of bytes or a byte buffer.
+ * The cipher is initialized to use 128 effective key bits.
+ *
+ * @param key the symmetric key to use.
+ * @param iv the initialization vector to use.
+ * @param output the buffer to write to, null to create one.
+ *
+ * @return the cipher.
+ */
+forge.rc2.startEncrypting = function(key, iv, output) {
+  var cipher = forge.rc2.createEncryptionCipher(key, 128);
+  cipher.start(iv, output);
+  return cipher;
+};
+
+/**
+ * Creates an RC2 cipher object to encrypt data in ECB or CBC mode using the
+ * given symmetric key.
+ *
+ * The key may be given as a string of bytes or a byte buffer.
+ *
+ * To start encrypting call start() on the cipher with an iv and optional
+ * output buffer.
+ *
+ * @param key the symmetric key to use.
+ *
+ * @return the cipher.
+ */
+forge.rc2.createEncryptionCipher = function(key, bits) {
+  return createCipher(key, bits, true);
+};
+
+/**
+ * Creates an RC2 cipher object to decrypt data in ECB or CBC mode using the
+ * given symmetric key. The output will be stored in the 'output' member
+ * of the returned cipher.
+ *
+ * The key and iv may be given as a string of bytes or a byte buffer.
+ * The cipher is initialized to use 128 effective key bits.
+ *
+ * @param key the symmetric key to use.
+ * @param iv the initialization vector to use.
+ * @param output the buffer to write to, null to create one.
+ *
+ * @return the cipher.
+ */
+forge.rc2.startDecrypting = function(key, iv, output) {
+  var cipher = forge.rc2.createDecryptionCipher(key, 128);
+  cipher.start(iv, output);
+  return cipher;
+};
+
+/**
+ * Creates an RC2 cipher object to decrypt data in ECB or CBC mode using the
+ * given symmetric key.
+ *
+ * The key may be given as a string of bytes or a byte buffer.
+ *
+ * To start decrypting call start() on the cipher with an iv and optional
+ * output buffer.
+ *
+ * @param key the symmetric key to use.
+ *
+ * @return the cipher.
+ */
+forge.rc2.createDecryptionCipher = function(key, bits) {
+  return createCipher(key, bits, false);
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'rc2';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/rsa.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/rsa.js
new file mode 100644
index 0000000..90f8c0a
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/rsa.js
@@ -0,0 +1,1712 @@
+/**
+ * Javascript implementation of basic RSA algorithms.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2014 Digital Bazaar, Inc.
+ *
+ * The only algorithm currently supported for PKI is RSA.
+ *
+ * An RSA key is often stored in ASN.1 DER format. The SubjectPublicKeyInfo
+ * ASN.1 structure is composed of an algorithm of type AlgorithmIdentifier
+ * and a subjectPublicKey of type bit string.
+ *
+ * The AlgorithmIdentifier contains an Object Identifier (OID) and parameters
+ * for the algorithm, if any. In the case of RSA, there aren't any.
+ *
+ * SubjectPublicKeyInfo ::= SEQUENCE {
+ *   algorithm AlgorithmIdentifier,
+ *   subjectPublicKey BIT STRING
+ * }
+ *
+ * AlgorithmIdentifer ::= SEQUENCE {
+ *   algorithm OBJECT IDENTIFIER,
+ *   parameters ANY DEFINED BY algorithm OPTIONAL
+ * }
+ *
+ * For an RSA public key, the subjectPublicKey is:
+ *
+ * RSAPublicKey ::= SEQUENCE {
+ *   modulus            INTEGER,    -- n
+ *   publicExponent     INTEGER     -- e
+ * }
+ *
+ * PrivateKeyInfo ::= SEQUENCE {
+ *   version                   Version,
+ *   privateKeyAlgorithm       PrivateKeyAlgorithmIdentifier,
+ *   privateKey                PrivateKey,
+ *   attributes           [0]  IMPLICIT Attributes OPTIONAL
+ * }
+ *
+ * Version ::= INTEGER
+ * PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
+ * PrivateKey ::= OCTET STRING
+ * Attributes ::= SET OF Attribute
+ *
+ * An RSA private key as the following structure:
+ *
+ * RSAPrivateKey ::= SEQUENCE {
+ *   version Version,
+ *   modulus INTEGER, -- n
+ *   publicExponent INTEGER, -- e
+ *   privateExponent INTEGER, -- d
+ *   prime1 INTEGER, -- p
+ *   prime2 INTEGER, -- q
+ *   exponent1 INTEGER, -- d mod (p-1)
+ *   exponent2 INTEGER, -- d mod (q-1)
+ *   coefficient INTEGER -- (inverse of q) mod p
+ * }
+ *
+ * Version ::= INTEGER
+ *
+ * The OID for the RSA key algorithm is: 1.2.840.113549.1.1.1
+ */
+(function() {
+function initModule(forge) {
+/* ########## Begin module implementation ########## */
+
+if(typeof BigInteger === 'undefined') {
+  var BigInteger = forge.jsbn.BigInteger;
+}
+
+// shortcut for asn.1 API
+var asn1 = forge.asn1;
+
+/*
+ * RSA encryption and decryption, see RFC 2313.
+ */
+forge.pki = forge.pki || {};
+forge.pki.rsa = forge.rsa = forge.rsa || {};
+var pki = forge.pki;
+
+// for finding primes, which are 30k+i for i = 1, 7, 11, 13, 17, 19, 23, 29
+var GCD_30_DELTA = [6, 4, 2, 4, 2, 4, 6, 2];
+
+// validator for a PrivateKeyInfo structure
+var privateKeyValidator = {
+  // PrivateKeyInfo
+  name: 'PrivateKeyInfo',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    // Version (INTEGER)
+    name: 'PrivateKeyInfo.version',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'privateKeyVersion'
+  }, {
+    // privateKeyAlgorithm
+    name: 'PrivateKeyInfo.privateKeyAlgorithm',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true,
+    value: [{
+      name: 'AlgorithmIdentifier.algorithm',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.OID,
+      constructed: false,
+      capture: 'privateKeyOid'
+    }]
+  }, {
+    // PrivateKey
+    name: 'PrivateKeyInfo',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.OCTETSTRING,
+    constructed: false,
+    capture: 'privateKey'
+  }]
+};
+
+// validator for an RSA private key
+var rsaPrivateKeyValidator = {
+  // RSAPrivateKey
+  name: 'RSAPrivateKey',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    // Version (INTEGER)
+    name: 'RSAPrivateKey.version',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'privateKeyVersion'
+  }, {
+    // modulus (n)
+    name: 'RSAPrivateKey.modulus',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'privateKeyModulus'
+  }, {
+    // publicExponent (e)
+    name: 'RSAPrivateKey.publicExponent',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'privateKeyPublicExponent'
+  }, {
+    // privateExponent (d)
+    name: 'RSAPrivateKey.privateExponent',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'privateKeyPrivateExponent'
+  }, {
+    // prime1 (p)
+    name: 'RSAPrivateKey.prime1',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'privateKeyPrime1'
+  }, {
+    // prime2 (q)
+    name: 'RSAPrivateKey.prime2',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'privateKeyPrime2'
+  }, {
+    // exponent1 (d mod (p-1))
+    name: 'RSAPrivateKey.exponent1',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'privateKeyExponent1'
+  }, {
+    // exponent2 (d mod (q-1))
+    name: 'RSAPrivateKey.exponent2',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'privateKeyExponent2'
+  }, {
+    // coefficient ((inverse of q) mod p)
+    name: 'RSAPrivateKey.coefficient',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'privateKeyCoefficient'
+  }]
+};
+
+// validator for an RSA public key
+var rsaPublicKeyValidator = {
+  // RSAPublicKey
+  name: 'RSAPublicKey',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    // modulus (n)
+    name: 'RSAPublicKey.modulus',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'publicKeyModulus'
+  }, {
+    // publicExponent (e)
+    name: 'RSAPublicKey.exponent',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'publicKeyExponent'
+  }]
+};
+
+// validator for an SubjectPublicKeyInfo structure
+// Note: Currently only works with an RSA public key
+var publicKeyValidator = forge.pki.rsa.publicKeyValidator = {
+  name: 'SubjectPublicKeyInfo',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  captureAsn1: 'subjectPublicKeyInfo',
+  value: [{
+    name: 'SubjectPublicKeyInfo.AlgorithmIdentifier',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true,
+    value: [{
+      name: 'AlgorithmIdentifier.algorithm',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.OID,
+      constructed: false,
+      capture: 'publicKeyOid'
+    }]
+  }, {
+    // subjectPublicKey
+    name: 'SubjectPublicKeyInfo.subjectPublicKey',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.BITSTRING,
+    constructed: false,
+    value: [{
+      // RSAPublicKey
+      name: 'SubjectPublicKeyInfo.subjectPublicKey.RSAPublicKey',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.SEQUENCE,
+      constructed: true,
+      optional: true,
+      captureAsn1: 'rsaPublicKey'
+    }]
+  }]
+};
+
+/**
+ * Wrap digest in DigestInfo object.
+ *
+ * This function implements EMSA-PKCS1-v1_5-ENCODE as per RFC 3447.
+ *
+ * DigestInfo ::= SEQUENCE {
+ *   digestAlgorithm DigestAlgorithmIdentifier,
+ *   digest Digest
+ * }
+ *
+ * DigestAlgorithmIdentifier ::= AlgorithmIdentifier
+ * Digest ::= OCTET STRING
+ *
+ * @param md the message digest object with the hash to sign.
+ *
+ * @return the encoded message (ready for RSA encrytion)
+ */
+var emsaPkcs1v15encode = function(md) {
+  // get the oid for the algorithm
+  var oid;
+  if(md.algorithm in pki.oids) {
+    oid = pki.oids[md.algorithm];
+  } else {
+    var error = new Error('Unknown message digest algorithm.');
+    error.algorithm = md.algorithm;
+    throw error;
+  }
+  var oidBytes = asn1.oidToDer(oid).getBytes();
+
+  // create the digest info
+  var digestInfo = asn1.create(
+    asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []);
+  var digestAlgorithm = asn1.create(
+    asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []);
+  digestAlgorithm.value.push(asn1.create(
+    asn1.Class.UNIVERSAL, asn1.Type.OID, false, oidBytes));
+  digestAlgorithm.value.push(asn1.create(
+    asn1.Class.UNIVERSAL, asn1.Type.NULL, false, ''));
+  var digest = asn1.create(
+    asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING,
+    false, md.digest().getBytes());
+  digestInfo.value.push(digestAlgorithm);
+  digestInfo.value.push(digest);
+
+  // encode digest info
+  return asn1.toDer(digestInfo).getBytes();
+};
+
+/**
+ * Performs x^c mod n (RSA encryption or decryption operation).
+ *
+ * @param x the number to raise and mod.
+ * @param key the key to use.
+ * @param pub true if the key is public, false if private.
+ *
+ * @return the result of x^c mod n.
+ */
+var _modPow = function(x, key, pub) {
+  if(pub) {
+    return x.modPow(key.e, key.n);
+  }
+
+  if(!key.p || !key.q) {
+    // allow calculation without CRT params (slow)
+    return x.modPow(key.d, key.n);
+  }
+
+  // pre-compute dP, dQ, and qInv if necessary
+  if(!key.dP) {
+    key.dP = key.d.mod(key.p.subtract(BigInteger.ONE));
+  }
+  if(!key.dQ) {
+    key.dQ = key.d.mod(key.q.subtract(BigInteger.ONE));
+  }
+  if(!key.qInv) {
+    key.qInv = key.q.modInverse(key.p);
+  }
+
+  /* Chinese remainder theorem (CRT) states:
+
+    Suppose n1, n2, ..., nk are positive integers which are pairwise
+    coprime (n1 and n2 have no common factors other than 1). For any
+    integers x1, x2, ..., xk there exists an integer x solving the
+    system of simultaneous congruences (where ~= means modularly
+    congruent so a ~= b mod n means a mod n = b mod n):
+
+    x ~= x1 mod n1
+    x ~= x2 mod n2
+    ...
+    x ~= xk mod nk
+
+    This system of congruences has a single simultaneous solution x
+    between 0 and n - 1. Furthermore, each xk solution and x itself
+    is congruent modulo the product n = n1*n2*...*nk.
+    So x1 mod n = x2 mod n = xk mod n = x mod n.
+
+    The single simultaneous solution x can be solved with the following
+    equation:
+
+    x = sum(xi*ri*si) mod n where ri = n/ni and si = ri^-1 mod ni.
+
+    Where x is less than n, xi = x mod ni.
+
+    For RSA we are only concerned with k = 2. The modulus n = pq, where
+    p and q are coprime. The RSA decryption algorithm is:
+
+    y = x^d mod n
+
+    Given the above:
+
+    x1 = x^d mod p
+    r1 = n/p = q
+    s1 = q^-1 mod p
+    x2 = x^d mod q
+    r2 = n/q = p
+    s2 = p^-1 mod q
+
+    So y = (x1r1s1 + x2r2s2) mod n
+         = ((x^d mod p)q(q^-1 mod p) + (x^d mod q)p(p^-1 mod q)) mod n
+
+    According to Fermat's Little Theorem, if the modulus P is prime,
+    for any integer A not evenly divisible by P, A^(P-1) ~= 1 mod P.
+    Since A is not divisible by P it follows that if:
+    N ~= M mod (P - 1), then A^N mod P = A^M mod P. Therefore:
+
+    A^N mod P = A^(M mod (P - 1)) mod P. (The latter takes less effort
+    to calculate). In order to calculate x^d mod p more quickly the
+    exponent d mod (p - 1) is stored in the RSA private key (the same
+    is done for x^d mod q). These values are referred to as dP and dQ
+    respectively. Therefore we now have:
+
+    y = ((x^dP mod p)q(q^-1 mod p) + (x^dQ mod q)p(p^-1 mod q)) mod n
+
+    Since we'll be reducing x^dP by modulo p (same for q) we can also
+    reduce x by p (and q respectively) before hand. Therefore, let
+
+    xp = ((x mod p)^dP mod p), and
+    xq = ((x mod q)^dQ mod q), yielding:
+
+    y = (xp*q*(q^-1 mod p) + xq*p*(p^-1 mod q)) mod n
+
+    This can be further reduced to a simple algorithm that only
+    requires 1 inverse (the q inverse is used) to be used and stored.
+    The algorithm is called Garner's algorithm. If qInv is the
+    inverse of q, we simply calculate:
+
+    y = (qInv*(xp - xq) mod p) * q + xq
+
+    However, there are two further complications. First, we need to
+    ensure that xp > xq to prevent signed BigIntegers from being used
+    so we add p until this is true (since we will be mod'ing with
+    p anyway). Then, there is a known timing attack on algorithms
+    using the CRT. To mitigate this risk, "cryptographic blinding"
+    should be used. This requires simply generating a random number r between
+    0 and n-1 and its inverse and multiplying x by r^e before calculating y
+    and then multiplying y by r^-1 afterwards.
+  */
+
+  // cryptographic blinding
+  var r;
+  do {
+    r = new BigInteger(
+      forge.util.bytesToHex(forge.random.getBytes(key.n.bitLength() / 8)),
+      16).mod(key.n);
+  } while(r.equals(BigInteger.ZERO));
+  x = x.multiply(r.modPow(key.e, key.n)).mod(key.n);
+
+  // calculate xp and xq
+  var xp = x.mod(key.p).modPow(key.dP, key.p);
+  var xq = x.mod(key.q).modPow(key.dQ, key.q);
+
+  // xp must be larger than xq to avoid signed bit usage
+  while(xp.compareTo(xq) < 0) {
+    xp = xp.add(key.p);
+  }
+
+  // do last step
+  var y = xp.subtract(xq)
+    .multiply(key.qInv).mod(key.p)
+    .multiply(key.q).add(xq);
+
+  // remove effect of random for cryptographic blinding
+  y = y.multiply(r.modInverse(key.n)).mod(key.n);
+
+  return y;
+};
+
+/**
+ * NOTE: THIS METHOD IS DEPRECATED, use 'sign' on a private key object or
+ * 'encrypt' on a public key object instead.
+ *
+ * Performs RSA encryption.
+ *
+ * The parameter bt controls whether to put padding bytes before the
+ * message passed in. Set bt to either true or false to disable padding
+ * completely (in order to handle e.g. EMSA-PSS encoding seperately before),
+ * signaling whether the encryption operation is a public key operation
+ * (i.e. encrypting data) or not, i.e. private key operation (data signing).
+ *
+ * For PKCS#1 v1.5 padding pass in the block type to use, i.e. either 0x01
+ * (for signing) or 0x02 (for encryption). The key operation mode (private
+ * or public) is derived from this flag in that case).
+ *
+ * @param m the message to encrypt as a byte string.
+ * @param key the RSA key to use.
+ * @param bt for PKCS#1 v1.5 padding, the block type to use
+ *   (0x01 for private key, 0x02 for public),
+ *   to disable padding: true = public key, false = private key.
+ *
+ * @return the encrypted bytes as a string.
+ */
+pki.rsa.encrypt = function(m, key, bt) {
+  var pub = bt;
+  var eb;
+
+  // get the length of the modulus in bytes
+  var k = Math.ceil(key.n.bitLength() / 8);
+
+  if(bt !== false && bt !== true) {
+    // legacy, default to PKCS#1 v1.5 padding
+    pub = (bt === 0x02);
+    eb = _encodePkcs1_v1_5(m, key, bt);
+  } else {
+    eb = forge.util.createBuffer();
+    eb.putBytes(m);
+  }
+
+  // load encryption block as big integer 'x'
+  // FIXME: hex conversion inefficient, get BigInteger w/byte strings
+  var x = new BigInteger(eb.toHex(), 16);
+
+  // do RSA encryption
+  var y = _modPow(x, key, pub);
+
+  // convert y into the encrypted data byte string, if y is shorter in
+  // bytes than k, then prepend zero bytes to fill up ed
+  // FIXME: hex conversion inefficient, get BigInteger w/byte strings
+  var yhex = y.toString(16);
+  var ed = forge.util.createBuffer();
+  var zeros = k - Math.ceil(yhex.length / 2);
+  while(zeros > 0) {
+    ed.putByte(0x00);
+    --zeros;
+  }
+  ed.putBytes(forge.util.hexToBytes(yhex));
+  return ed.getBytes();
+};
+
+/**
+ * NOTE: THIS METHOD IS DEPRECATED, use 'decrypt' on a private key object or
+ * 'verify' on a public key object instead.
+ *
+ * Performs RSA decryption.
+ *
+ * The parameter ml controls whether to apply PKCS#1 v1.5 padding
+ * or not.  Set ml = false to disable padding removal completely
+ * (in order to handle e.g. EMSA-PSS later on) and simply pass back
+ * the RSA encryption block.
+ *
+ * @param ed the encrypted data to decrypt in as a byte string.
+ * @param key the RSA key to use.
+ * @param pub true for a public key operation, false for private.
+ * @param ml the message length, if known, false to disable padding.
+ *
+ * @return the decrypted message as a byte string.
+ */
+pki.rsa.decrypt = function(ed, key, pub, ml) {
+  // get the length of the modulus in bytes
+  var k = Math.ceil(key.n.bitLength() / 8);
+
+  // error if the length of the encrypted data ED is not k
+  if(ed.length !== k) {
+    var error = new Error('Encrypted message length is invalid.');
+    error.length = ed.length;
+    error.expected = k;
+    throw error;
+  }
+
+  // convert encrypted data into a big integer
+  // FIXME: hex conversion inefficient, get BigInteger w/byte strings
+  var y = new BigInteger(forge.util.createBuffer(ed).toHex(), 16);
+
+  // y must be less than the modulus or it wasn't the result of
+  // a previous mod operation (encryption) using that modulus
+  if(y.compareTo(key.n) >= 0) {
+    throw new Error('Encrypted message is invalid.');
+  }
+
+  // do RSA decryption
+  var x = _modPow(y, key, pub);
+
+  // create the encryption block, if x is shorter in bytes than k, then
+  // prepend zero bytes to fill up eb
+  // FIXME: hex conversion inefficient, get BigInteger w/byte strings
+  var xhex = x.toString(16);
+  var eb = forge.util.createBuffer();
+  var zeros = k - Math.ceil(xhex.length / 2);
+  while(zeros > 0) {
+    eb.putByte(0x00);
+    --zeros;
+  }
+  eb.putBytes(forge.util.hexToBytes(xhex));
+
+  if(ml !== false) {
+    // legacy, default to PKCS#1 v1.5 padding
+    return _decodePkcs1_v1_5(eb.getBytes(), key, pub);
+  }
+
+  // return message
+  return eb.getBytes();
+};
+
+/**
+ * Creates an RSA key-pair generation state object. It is used to allow
+ * key-generation to be performed in steps. It also allows for a UI to
+ * display progress updates.
+ *
+ * @param bits the size for the private key in bits, defaults to 2048.
+ * @param e the public exponent to use, defaults to 65537 (0x10001).
+ * @param [options] the options to use.
+ *          prng a custom crypto-secure pseudo-random number generator to use,
+ *            that must define "getBytesSync".
+ *          algorithm the algorithm to use (default: 'PRIMEINC').
+ *
+ * @return the state object to use to generate the key-pair.
+ */
+pki.rsa.createKeyPairGenerationState = function(bits, e, options) {
+  // TODO: migrate step-based prime generation code to forge.prime
+
+  // set default bits
+  if(typeof(bits) === 'string') {
+    bits = parseInt(bits, 10);
+  }
+  bits = bits || 2048;
+
+  // create prng with api that matches BigInteger secure random
+  options = options || {};
+  var prng = options.prng || forge.random;
+  var rng = {
+    // x is an array to fill with bytes
+    nextBytes: function(x) {
+      var b = prng.getBytesSync(x.length);
+      for(var i = 0; i < x.length; ++i) {
+        x[i] = b.charCodeAt(i);
+      }
+    }
+  };
+
+  var algorithm = options.algorithm || 'PRIMEINC';
+
+  // create PRIMEINC algorithm state
+  var rval;
+  if(algorithm === 'PRIMEINC') {
+    rval = {
+      algorithm: algorithm,
+      state: 0,
+      bits: bits,
+      rng: rng,
+      eInt: e || 65537,
+      e: new BigInteger(null),
+      p: null,
+      q: null,
+      qBits: bits >> 1,
+      pBits: bits - (bits >> 1),
+      pqState: 0,
+      num: null,
+      keys: null
+    };
+    rval.e.fromInt(rval.eInt);
+  } else {
+    throw new Error('Invalid key generation algorithm: ' + algorithm);
+  }
+
+  return rval;
+};
+
+/**
+ * Attempts to runs the key-generation algorithm for at most n seconds
+ * (approximately) using the given state. When key-generation has completed,
+ * the keys will be stored in state.keys.
+ *
+ * To use this function to update a UI while generating a key or to prevent
+ * causing browser lockups/warnings, set "n" to a value other than 0. A
+ * simple pattern for generating a key and showing a progress indicator is:
+ *
+ * var state = pki.rsa.createKeyPairGenerationState(2048);
+ * var step = function() {
+ *   // step key-generation, run algorithm for 100 ms, repeat
+ *   if(!forge.pki.rsa.stepKeyPairGenerationState(state, 100)) {
+ *     setTimeout(step, 1);
+ *   } else {
+ *     // key-generation complete
+ *     // TODO: turn off progress indicator here
+ *     // TODO: use the generated key-pair in "state.keys"
+ *   }
+ * };
+ * // TODO: turn on progress indicator here
+ * setTimeout(step, 0);
+ *
+ * @param state the state to use.
+ * @param n the maximum number of milliseconds to run the algorithm for, 0
+ *          to run the algorithm to completion.
+ *
+ * @return true if the key-generation completed, false if not.
+ */
+pki.rsa.stepKeyPairGenerationState = function(state, n) {
+  // set default algorithm if not set
+  if(!('algorithm' in state)) {
+    state.algorithm = 'PRIMEINC';
+  }
+
+  // TODO: migrate step-based prime generation code to forge.prime
+  // TODO: abstract as PRIMEINC algorithm
+
+  // do key generation (based on Tom Wu's rsa.js, see jsbn.js license)
+  // with some minor optimizations and designed to run in steps
+
+  // local state vars
+  var THIRTY = new BigInteger(null);
+  THIRTY.fromInt(30);
+  var deltaIdx = 0;
+  var op_or = function(x,y) { return x|y; };
+
+  // keep stepping until time limit is reached or done
+  var t1 = +new Date();
+  var t2;
+  var total = 0;
+  while(state.keys === null && (n <= 0 || total < n)) {
+    // generate p or q
+    if(state.state === 0) {
+      /* Note: All primes are of the form:
+
+        30k+i, for i < 30 and gcd(30, i)=1, where there are 8 values for i
+
+        When we generate a random number, we always align it at 30k + 1. Each
+        time the number is determined not to be prime we add to get to the
+        next 'i', eg: if the number was at 30k + 1 we add 6. */
+      var bits = (state.p === null) ? state.pBits : state.qBits;
+      var bits1 = bits - 1;
+
+      // get a random number
+      if(state.pqState === 0) {
+        state.num = new BigInteger(bits, state.rng);
+        // force MSB set
+        if(!state.num.testBit(bits1)) {
+          state.num.bitwiseTo(
+            BigInteger.ONE.shiftLeft(bits1), op_or, state.num);
+        }
+        // align number on 30k+1 boundary
+        state.num.dAddOffset(31 - state.num.mod(THIRTY).byteValue(), 0);
+        deltaIdx = 0;
+
+        ++state.pqState;
+      } else if(state.pqState === 1) {
+        // try to make the number a prime
+        if(state.num.bitLength() > bits) {
+          // overflow, try again
+          state.pqState = 0;
+          // do primality test
+        } else if(state.num.isProbablePrime(
+          _getMillerRabinTests(state.num.bitLength()))) {
+          ++state.pqState;
+        } else {
+          // get next potential prime
+          state.num.dAddOffset(GCD_30_DELTA[deltaIdx++ % 8], 0);
+        }
+      } else if(state.pqState === 2) {
+        // ensure number is coprime with e
+        state.pqState =
+          (state.num.subtract(BigInteger.ONE).gcd(state.e)
+          .compareTo(BigInteger.ONE) === 0) ? 3 : 0;
+      } else if(state.pqState === 3) {
+        // store p or q
+        state.pqState = 0;
+        if(state.p === null) {
+          state.p = state.num;
+        } else {
+          state.q = state.num;
+        }
+
+        // advance state if both p and q are ready
+        if(state.p !== null && state.q !== null) {
+          ++state.state;
+        }
+        state.num = null;
+      }
+    } else if(state.state === 1) {
+      // ensure p is larger than q (swap them if not)
+      if(state.p.compareTo(state.q) < 0) {
+        state.num = state.p;
+        state.p = state.q;
+        state.q = state.num;
+      }
+      ++state.state;
+    } else if(state.state === 2) {
+      // compute phi: (p - 1)(q - 1) (Euler's totient function)
+      state.p1 = state.p.subtract(BigInteger.ONE);
+      state.q1 = state.q.subtract(BigInteger.ONE);
+      state.phi = state.p1.multiply(state.q1);
+      ++state.state;
+    } else if(state.state === 3) {
+      // ensure e and phi are coprime
+      if(state.phi.gcd(state.e).compareTo(BigInteger.ONE) === 0) {
+        // phi and e are coprime, advance
+        ++state.state;
+      } else {
+        // phi and e aren't coprime, so generate a new p and q
+        state.p = null;
+        state.q = null;
+        state.state = 0;
+      }
+    } else if(state.state === 4) {
+      // create n, ensure n is has the right number of bits
+      state.n = state.p.multiply(state.q);
+
+      // ensure n is right number of bits
+      if(state.n.bitLength() === state.bits) {
+        // success, advance
+        ++state.state;
+      } else {
+        // failed, get new q
+        state.q = null;
+        state.state = 0;
+      }
+    } else if(state.state === 5) {
+      // set keys
+      var d = state.e.modInverse(state.phi);
+      state.keys = {
+        privateKey: pki.rsa.setPrivateKey(
+          state.n, state.e, d, state.p, state.q,
+          d.mod(state.p1), d.mod(state.q1),
+          state.q.modInverse(state.p)),
+        publicKey: pki.rsa.setPublicKey(state.n, state.e)
+      };
+    }
+
+    // update timing
+    t2 = +new Date();
+    total += t2 - t1;
+    t1 = t2;
+  }
+
+  return state.keys !== null;
+};
+
+/**
+ * Generates an RSA public-private key pair in a single call.
+ *
+ * To generate a key-pair in steps (to allow for progress updates and to
+ * prevent blocking or warnings in slow browsers) then use the key-pair
+ * generation state functions.
+ *
+ * To generate a key-pair asynchronously (either through web-workers, if
+ * available, or by breaking up the work on the main thread), pass a
+ * callback function.
+ *
+ * @param [bits] the size for the private key in bits, defaults to 2048.
+ * @param [e] the public exponent to use, defaults to 65537.
+ * @param [options] options for key-pair generation, if given then 'bits'
+ *          and 'e' must *not* be given:
+ *          bits the size for the private key in bits, (default: 2048).
+ *          e the public exponent to use, (default: 65537 (0x10001)).
+ *          workerScript the worker script URL.
+ *          workers the number of web workers (if supported) to use,
+ *            (default: 2).
+ *          workLoad the size of the work load, ie: number of possible prime
+ *            numbers for each web worker to check per work assignment,
+ *            (default: 100).
+ *          e the public exponent to use, defaults to 65537.
+ *          prng a custom crypto-secure pseudo-random number generator to use,
+ *            that must define "getBytesSync".
+ *          algorithm the algorithm to use (default: 'PRIMEINC').
+ * @param [callback(err, keypair)] called once the operation completes.
+ *
+ * @return an object with privateKey and publicKey properties.
+ */
+pki.rsa.generateKeyPair = function(bits, e, options, callback) {
+  // (bits), (options), (callback)
+  if(arguments.length === 1) {
+    if(typeof bits === 'object') {
+      options = bits;
+      bits = undefined;
+    } else if(typeof bits === 'function') {
+      callback = bits;
+      bits = undefined;
+    }
+  } else if(arguments.length === 2) {
+    // (bits, e), (bits, options), (bits, callback), (options, callback)
+    if(typeof bits === 'number') {
+      if(typeof e === 'function') {
+        callback = e;
+        e = undefined;
+      } else if(typeof e !== 'number') {
+        options = e;
+        e = undefined;
+      }
+    } else {
+      options = bits;
+      callback = e;
+      bits = undefined;
+      e = undefined;
+    }
+  } else if(arguments.length === 3) {
+    // (bits, e, options), (bits, e, callback), (bits, options, callback)
+    if(typeof e === 'number') {
+      if(typeof options === 'function') {
+        callback = options;
+        options = undefined;
+      }
+    } else {
+      callback = options;
+      options = e;
+      e = undefined;
+    }
+  }
+  options = options || {};
+  if(bits === undefined) {
+    bits = options.bits || 2048;
+  }
+  if(e === undefined) {
+    e = options.e || 0x10001;
+  }
+  var state = pki.rsa.createKeyPairGenerationState(bits, e, options);
+  if(!callback) {
+    pki.rsa.stepKeyPairGenerationState(state, 0);
+    return state.keys;
+  }
+  _generateKeyPair(state, options, callback);
+};
+
+/**
+ * Sets an RSA public key from BigIntegers modulus and exponent.
+ *
+ * @param n the modulus.
+ * @param e the exponent.
+ *
+ * @return the public key.
+ */
+pki.setRsaPublicKey = pki.rsa.setPublicKey = function(n, e) {
+  var key = {
+    n: n,
+    e: e
+  };
+
+  /**
+   * Encrypts the given data with this public key. Newer applications
+   * should use the 'RSA-OAEP' decryption scheme, 'RSAES-PKCS1-V1_5' is for
+   * legacy applications.
+   *
+   * @param data the byte string to encrypt.
+   * @param scheme the encryption scheme to use:
+   *          'RSAES-PKCS1-V1_5' (default),
+   *          'RSA-OAEP',
+   *          'RAW', 'NONE', or null to perform raw RSA encryption,
+   *          an object with an 'encode' property set to a function
+   *          with the signature 'function(data, key)' that returns
+   *          a binary-encoded string representing the encoded data.
+   * @param schemeOptions any scheme-specific options.
+   *
+   * @return the encrypted byte string.
+   */
+  key.encrypt = function(data, scheme, schemeOptions) {
+    if(typeof scheme === 'string') {
+      scheme = scheme.toUpperCase();
+    } else if(scheme === undefined) {
+      scheme = 'RSAES-PKCS1-V1_5';
+    }
+
+    if(scheme === 'RSAES-PKCS1-V1_5') {
+      scheme = {
+        encode: function(m, key, pub) {
+          return _encodePkcs1_v1_5(m, key, 0x02).getBytes();
+        }
+      };
+    } else if(scheme === 'RSA-OAEP' || scheme === 'RSAES-OAEP') {
+      scheme = {
+        encode: function(m, key) {
+          return forge.pkcs1.encode_rsa_oaep(key, m, schemeOptions);
+        }
+      };
+    } else if(['RAW', 'NONE', 'NULL', null].indexOf(scheme) !== -1) {
+      scheme = { encode: function(e) { return e; } };
+    } else if(typeof scheme === 'string') {
+      throw new Error('Unsupported encryption scheme: "' + scheme + '".');
+    }
+
+    // do scheme-based encoding then rsa encryption
+    var e = scheme.encode(data, key, true);
+    return pki.rsa.encrypt(e, key, true);
+  };
+
+  /**
+   * Verifies the given signature against the given digest.
+   *
+   * PKCS#1 supports multiple (currently two) signature schemes:
+   * RSASSA-PKCS1-V1_5 and RSASSA-PSS.
+   *
+   * By default this implementation uses the "old scheme", i.e.
+   * RSASSA-PKCS1-V1_5, in which case once RSA-decrypted, the
+   * signature is an OCTET STRING that holds a DigestInfo.
+   *
+   * DigestInfo ::= SEQUENCE {
+   *   digestAlgorithm DigestAlgorithmIdentifier,
+   *   digest Digest
+   * }
+   * DigestAlgorithmIdentifier ::= AlgorithmIdentifier
+   * Digest ::= OCTET STRING
+   *
+   * To perform PSS signature verification, provide an instance
+   * of Forge PSS object as the scheme parameter.
+   *
+   * @param digest the message digest hash to compare against the signature,
+   *          as a binary-encoded string.
+   * @param signature the signature to verify, as a binary-encoded string.
+   * @param scheme signature verification scheme to use:
+   *          'RSASSA-PKCS1-V1_5' or undefined for RSASSA PKCS#1 v1.5,
+   *          a Forge PSS object for RSASSA-PSS,
+   *          'NONE' or null for none, DigestInfo will not be expected, but
+   *            PKCS#1 v1.5 padding will still be used.
+   *
+   * @return true if the signature was verified, false if not.
+   */
+   key.verify = function(digest, signature, scheme) {
+     if(typeof scheme === 'string') {
+       scheme = scheme.toUpperCase();
+     } else if(scheme === undefined) {
+       scheme = 'RSASSA-PKCS1-V1_5';
+     }
+
+     if(scheme === 'RSASSA-PKCS1-V1_5') {
+       scheme = {
+         verify: function(digest, d) {
+           // remove padding
+           d = _decodePkcs1_v1_5(d, key, true);
+           // d is ASN.1 BER-encoded DigestInfo
+           var obj = asn1.fromDer(d);
+           // compare the given digest to the decrypted one
+           return digest === obj.value[1].value;
+         }
+       };
+     } else if(scheme === 'NONE' || scheme === 'NULL' || scheme === null) {
+       scheme = {
+         verify: function(digest, d) {
+           // remove padding
+           d = _decodePkcs1_v1_5(d, key, true);
+           return digest === d;
+         }
+       };
+     }
+
+     // do rsa decryption w/o any decoding, then verify -- which does decoding
+     var d = pki.rsa.decrypt(signature, key, true, false);
+     return scheme.verify(digest, d, key.n.bitLength());
+  };
+
+  return key;
+};
+
+/**
+ * Sets an RSA private key from BigIntegers modulus, exponent, primes,
+ * prime exponents, and modular multiplicative inverse.
+ *
+ * @param n the modulus.
+ * @param e the public exponent.
+ * @param d the private exponent ((inverse of e) mod n).
+ * @param p the first prime.
+ * @param q the second prime.
+ * @param dP exponent1 (d mod (p-1)).
+ * @param dQ exponent2 (d mod (q-1)).
+ * @param qInv ((inverse of q) mod p)
+ *
+ * @return the private key.
+ */
+pki.setRsaPrivateKey = pki.rsa.setPrivateKey = function(
+  n, e, d, p, q, dP, dQ, qInv) {
+  var key = {
+    n: n,
+    e: e,
+    d: d,
+    p: p,
+    q: q,
+    dP: dP,
+    dQ: dQ,
+    qInv: qInv
+  };
+
+  /**
+   * Decrypts the given data with this private key. The decryption scheme
+   * must match the one used to encrypt the data.
+   *
+   * @param data the byte string to decrypt.
+   * @param scheme the decryption scheme to use:
+   *          'RSAES-PKCS1-V1_5' (default),
+   *          'RSA-OAEP',
+   *          'RAW', 'NONE', or null to perform raw RSA decryption.
+   * @param schemeOptions any scheme-specific options.
+   *
+   * @return the decrypted byte string.
+   */
+  key.decrypt = function(data, scheme, schemeOptions) {
+    if(typeof scheme === 'string') {
+      scheme = scheme.toUpperCase();
+    } else if(scheme === undefined) {
+      scheme = 'RSAES-PKCS1-V1_5';
+    }
+
+    // do rsa decryption w/o any decoding
+    var d = pki.rsa.decrypt(data, key, false, false);
+
+    if(scheme === 'RSAES-PKCS1-V1_5') {
+      scheme = { decode: _decodePkcs1_v1_5 };
+    } else if(scheme === 'RSA-OAEP' || scheme === 'RSAES-OAEP') {
+      scheme = {
+        decode: function(d, key) {
+          return forge.pkcs1.decode_rsa_oaep(key, d, schemeOptions);
+        }
+      };
+    } else if(['RAW', 'NONE', 'NULL', null].indexOf(scheme) !== -1) {
+      scheme = { decode: function(d) { return d; } };
+    } else {
+      throw new Error('Unsupported encryption scheme: "' + scheme + '".');
+    }
+
+    // decode according to scheme
+    return scheme.decode(d, key, false);
+  };
+
+  /**
+   * Signs the given digest, producing a signature.
+   *
+   * PKCS#1 supports multiple (currently two) signature schemes:
+   * RSASSA-PKCS1-V1_5 and RSASSA-PSS.
+   *
+   * By default this implementation uses the "old scheme", i.e.
+   * RSASSA-PKCS1-V1_5. In order to generate a PSS signature, provide
+   * an instance of Forge PSS object as the scheme parameter.
+   *
+   * @param md the message digest object with the hash to sign.
+   * @param scheme the signature scheme to use:
+   *          'RSASSA-PKCS1-V1_5' or undefined for RSASSA PKCS#1 v1.5,
+   *          a Forge PSS object for RSASSA-PSS,
+   *          'NONE' or null for none, DigestInfo will not be used but
+   *            PKCS#1 v1.5 padding will still be used.
+   *
+   * @return the signature as a byte string.
+   */
+  key.sign = function(md, scheme) {
+    /* Note: The internal implementation of RSA operations is being
+      transitioned away from a PKCS#1 v1.5 hard-coded scheme. Some legacy
+      code like the use of an encoding block identifier 'bt' will eventually
+      be removed. */
+
+    // private key operation
+    var bt = false;
+
+    if(typeof scheme === 'string') {
+      scheme = scheme.toUpperCase();
+    }
+
+    if(scheme === undefined || scheme === 'RSASSA-PKCS1-V1_5') {
+      scheme = { encode: emsaPkcs1v15encode };
+      bt = 0x01;
+    } else if(scheme === 'NONE' || scheme === 'NULL' || scheme === null) {
+      scheme = { encode: function() { return md; } };
+      bt = 0x01;
+    }
+
+    // encode and then encrypt
+    var d = scheme.encode(md, key.n.bitLength());
+    return pki.rsa.encrypt(d, key, bt);
+  };
+
+  return key;
+};
+
+/**
+ * Wraps an RSAPrivateKey ASN.1 object in an ASN.1 PrivateKeyInfo object.
+ *
+ * @param rsaKey the ASN.1 RSAPrivateKey.
+ *
+ * @return the ASN.1 PrivateKeyInfo.
+ */
+pki.wrapRsaPrivateKey = function(rsaKey) {
+  // PrivateKeyInfo
+  return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+    // version (0)
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      asn1.integerToDer(0).getBytes()),
+    // privateKeyAlgorithm
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+      asn1.create(
+        asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+        asn1.oidToDer(pki.oids.rsaEncryption).getBytes()),
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
+    ]),
+    // PrivateKey
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
+      asn1.toDer(rsaKey).getBytes())
+    ]);
+};
+
+/**
+ * Converts a private key from an ASN.1 object.
+ *
+ * @param obj the ASN.1 representation of a PrivateKeyInfo containing an
+ *          RSAPrivateKey or an RSAPrivateKey.
+ *
+ * @return the private key.
+ */
+pki.privateKeyFromAsn1 = function(obj) {
+  // get PrivateKeyInfo
+  var capture = {};
+  var errors = [];
+  if(asn1.validate(obj, privateKeyValidator, capture, errors)) {
+    obj = asn1.fromDer(forge.util.createBuffer(capture.privateKey));
+  }
+
+  // get RSAPrivateKey
+  capture = {};
+  errors = [];
+  if(!asn1.validate(obj, rsaPrivateKeyValidator, capture, errors)) {
+    var error = new Error('Cannot read private key. ' +
+      'ASN.1 object does not contain an RSAPrivateKey.');
+    error.errors = errors;
+    throw error;
+  }
+
+  // Note: Version is currently ignored.
+  // capture.privateKeyVersion
+  // FIXME: inefficient, get a BigInteger that uses byte strings
+  var n, e, d, p, q, dP, dQ, qInv;
+  n = forge.util.createBuffer(capture.privateKeyModulus).toHex();
+  e = forge.util.createBuffer(capture.privateKeyPublicExponent).toHex();
+  d = forge.util.createBuffer(capture.privateKeyPrivateExponent).toHex();
+  p = forge.util.createBuffer(capture.privateKeyPrime1).toHex();
+  q = forge.util.createBuffer(capture.privateKeyPrime2).toHex();
+  dP = forge.util.createBuffer(capture.privateKeyExponent1).toHex();
+  dQ = forge.util.createBuffer(capture.privateKeyExponent2).toHex();
+  qInv = forge.util.createBuffer(capture.privateKeyCoefficient).toHex();
+
+  // set private key
+  return pki.setRsaPrivateKey(
+    new BigInteger(n, 16),
+    new BigInteger(e, 16),
+    new BigInteger(d, 16),
+    new BigInteger(p, 16),
+    new BigInteger(q, 16),
+    new BigInteger(dP, 16),
+    new BigInteger(dQ, 16),
+    new BigInteger(qInv, 16));
+};
+
+/**
+ * Converts a private key to an ASN.1 RSAPrivateKey.
+ *
+ * @param key the private key.
+ *
+ * @return the ASN.1 representation of an RSAPrivateKey.
+ */
+pki.privateKeyToAsn1 = pki.privateKeyToRSAPrivateKey = function(key) {
+  // RSAPrivateKey
+  return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+    // version (0 = only 2 primes, 1 multiple primes)
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      asn1.integerToDer(0).getBytes()),
+    // modulus (n)
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      _bnToBytes(key.n)),
+    // publicExponent (e)
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      _bnToBytes(key.e)),
+    // privateExponent (d)
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      _bnToBytes(key.d)),
+    // privateKeyPrime1 (p)
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      _bnToBytes(key.p)),
+    // privateKeyPrime2 (q)
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      _bnToBytes(key.q)),
+    // privateKeyExponent1 (dP)
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      _bnToBytes(key.dP)),
+    // privateKeyExponent2 (dQ)
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      _bnToBytes(key.dQ)),
+    // coefficient (qInv)
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      _bnToBytes(key.qInv))
+  ]);
+};
+
+/**
+ * Converts a public key from an ASN.1 SubjectPublicKeyInfo or RSAPublicKey.
+ *
+ * @param obj the asn1 representation of a SubjectPublicKeyInfo or RSAPublicKey.
+ *
+ * @return the public key.
+ */
+pki.publicKeyFromAsn1 = function(obj) {
+  // get SubjectPublicKeyInfo
+  var capture = {};
+  var errors = [];
+  if(asn1.validate(obj, publicKeyValidator, capture, errors)) {
+    // get oid
+    var oid = asn1.derToOid(capture.publicKeyOid);
+    if(oid !== pki.oids.rsaEncryption) {
+      var error = new Error('Cannot read public key. Unknown OID.');
+      error.oid = oid;
+      throw error;
+    }
+    obj = capture.rsaPublicKey;
+  }
+
+  // get RSA params
+  errors = [];
+  if(!asn1.validate(obj, rsaPublicKeyValidator, capture, errors)) {
+    var error = new Error('Cannot read public key. ' +
+      'ASN.1 object does not contain an RSAPublicKey.');
+    error.errors = errors;
+    throw error;
+  }
+
+  // FIXME: inefficient, get a BigInteger that uses byte strings
+  var n = forge.util.createBuffer(capture.publicKeyModulus).toHex();
+  var e = forge.util.createBuffer(capture.publicKeyExponent).toHex();
+
+  // set public key
+  return pki.setRsaPublicKey(
+    new BigInteger(n, 16),
+    new BigInteger(e, 16));
+};
+
+/**
+ * Converts a public key to an ASN.1 SubjectPublicKeyInfo.
+ *
+ * @param key the public key.
+ *
+ * @return the asn1 representation of a SubjectPublicKeyInfo.
+ */
+pki.publicKeyToAsn1 = pki.publicKeyToSubjectPublicKeyInfo = function(key) {
+  // SubjectPublicKeyInfo
+  return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+    // AlgorithmIdentifier
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+      // algorithm
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+        asn1.oidToDer(pki.oids.rsaEncryption).getBytes()),
+      // parameters (null)
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
+    ]),
+    // subjectPublicKey
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.BITSTRING, false, [
+      pki.publicKeyToRSAPublicKey(key)
+    ])
+  ]);
+};
+
+/**
+ * Converts a public key to an ASN.1 RSAPublicKey.
+ *
+ * @param key the public key.
+ *
+ * @return the asn1 representation of a RSAPublicKey.
+ */
+pki.publicKeyToRSAPublicKey = function(key) {
+  // RSAPublicKey
+  return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+    // modulus (n)
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      _bnToBytes(key.n)),
+    // publicExponent (e)
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      _bnToBytes(key.e))
+  ]);
+};
+
+/**
+ * Encodes a message using PKCS#1 v1.5 padding.
+ *
+ * @param m the message to encode.
+ * @param key the RSA key to use.
+ * @param bt the block type to use, i.e. either 0x01 (for signing) or 0x02
+ *          (for encryption).
+ *
+ * @return the padded byte buffer.
+ */
+function _encodePkcs1_v1_5(m, key, bt) {
+  var eb = forge.util.createBuffer();
+
+  // get the length of the modulus in bytes
+  var k = Math.ceil(key.n.bitLength() / 8);
+
+  /* use PKCS#1 v1.5 padding */
+  if(m.length > (k - 11)) {
+    var error = new Error('Message is too long for PKCS#1 v1.5 padding.');
+    error.length = m.length;
+    error.max = k - 11;
+    throw error;
+  }
+
+  /* A block type BT, a padding string PS, and the data D shall be
+    formatted into an octet string EB, the encryption block:
+
+    EB = 00 || BT || PS || 00 || D
+
+    The block type BT shall be a single octet indicating the structure of
+    the encryption block. For this version of the document it shall have
+    value 00, 01, or 02. For a private-key operation, the block type
+    shall be 00 or 01. For a public-key operation, it shall be 02.
+
+    The padding string PS shall consist of k-3-||D|| octets. For block
+    type 00, the octets shall have value 00; for block type 01, they
+    shall have value FF; and for block type 02, they shall be
+    pseudorandomly generated and nonzero. This makes the length of the
+    encryption block EB equal to k. */
+
+  // build the encryption block
+  eb.putByte(0x00);
+  eb.putByte(bt);
+
+  // create the padding
+  var padNum = k - 3 - m.length;
+  var padByte;
+  // private key op
+  if(bt === 0x00 || bt === 0x01) {
+    padByte = (bt === 0x00) ? 0x00 : 0xFF;
+    for(var i = 0; i < padNum; ++i) {
+      eb.putByte(padByte);
+    }
+  } else {
+    // public key op
+    // pad with random non-zero values
+    while(padNum > 0) {
+      var numZeros = 0;
+      var padBytes = forge.random.getBytes(padNum);
+      for(var i = 0; i < padNum; ++i) {
+        padByte = padBytes.charCodeAt(i);
+        if(padByte === 0) {
+          ++numZeros;
+        } else {
+          eb.putByte(padByte);
+        }
+      }
+      padNum = numZeros;
+    }
+  }
+
+  // zero followed by message
+  eb.putByte(0x00);
+  eb.putBytes(m);
+
+  return eb;
+}
+
+/**
+ * Decodes a message using PKCS#1 v1.5 padding.
+ *
+ * @param em the message to decode.
+ * @param key the RSA key to use.
+ * @param pub true if the key is a public key, false if it is private.
+ * @param ml the message length, if specified.
+ *
+ * @return the decoded bytes.
+ */
+function _decodePkcs1_v1_5(em, key, pub, ml) {
+  // get the length of the modulus in bytes
+  var k = Math.ceil(key.n.bitLength() / 8);
+
+  /* It is an error if any of the following conditions occurs:
+
+    1. The encryption block EB cannot be parsed unambiguously.
+    2. The padding string PS consists of fewer than eight octets
+      or is inconsisent with the block type BT.
+    3. The decryption process is a public-key operation and the block
+      type BT is not 00 or 01, or the decryption process is a
+      private-key operation and the block type is not 02.
+   */
+
+  // parse the encryption block
+  var eb = forge.util.createBuffer(em);
+  var first = eb.getByte();
+  var bt = eb.getByte();
+  if(first !== 0x00 ||
+    (pub && bt !== 0x00 && bt !== 0x01) ||
+    (!pub && bt != 0x02) ||
+    (pub && bt === 0x00 && typeof(ml) === 'undefined')) {
+    throw new Error('Encryption block is invalid.');
+  }
+
+  var padNum = 0;
+  if(bt === 0x00) {
+    // check all padding bytes for 0x00
+    padNum = k - 3 - ml;
+    for(var i = 0; i < padNum; ++i) {
+      if(eb.getByte() !== 0x00) {
+        throw new Error('Encryption block is invalid.');
+      }
+    }
+  } else if(bt === 0x01) {
+    // find the first byte that isn't 0xFF, should be after all padding
+    padNum = 0;
+    while(eb.length() > 1) {
+      if(eb.getByte() !== 0xFF) {
+        --eb.read;
+        break;
+      }
+      ++padNum;
+    }
+  } else if(bt === 0x02) {
+    // look for 0x00 byte
+    padNum = 0;
+    while(eb.length() > 1) {
+      if(eb.getByte() === 0x00) {
+        --eb.read;
+        break;
+      }
+      ++padNum;
+    }
+  }
+
+  // zero must be 0x00 and padNum must be (k - 3 - message length)
+  var zero = eb.getByte();
+  if(zero !== 0x00 || padNum !== (k - 3 - eb.length())) {
+    throw new Error('Encryption block is invalid.');
+  }
+
+  return eb.getBytes();
+}
+
+/**
+ * Runs the key-generation algorithm asynchronously, either in the background
+ * via Web Workers, or using the main thread and setImmediate.
+ *
+ * @param state the key-pair generation state.
+ * @param [options] options for key-pair generation:
+ *          workerScript the worker script URL.
+ *          workers the number of web workers (if supported) to use,
+ *            (default: 2, -1 to use estimated cores minus one).
+ *          workLoad the size of the work load, ie: number of possible prime
+ *            numbers for each web worker to check per work assignment,
+ *            (default: 100).
+ * @param callback(err, keypair) called once the operation completes.
+ */
+function _generateKeyPair(state, options, callback) {
+  if(typeof options === 'function') {
+    callback = options;
+    options = {};
+  }
+  options = options || {};
+
+  var opts = {
+    algorithm: {
+      name: options.algorithm || 'PRIMEINC',
+      options: {
+        workers: options.workers || 2,
+        workLoad: options.workLoad || 100,
+        workerScript: options.workerScript
+      }
+    }
+  };
+  if('prng' in options) {
+    opts.prng = options.prng;
+  }
+
+  generate();
+
+  function generate() {
+    // find p and then q (done in series to simplify)
+    getPrime(state.pBits, function(err, num) {
+      if(err) {
+        return callback(err);
+      }
+      state.p = num;
+      if(state.q !== null) {
+        return finish(err, state.q);
+      }
+      getPrime(state.qBits, finish);
+    });
+  }
+
+  function getPrime(bits, callback) {
+    forge.prime.generateProbablePrime(bits, opts, callback);
+  }
+
+  function finish(err, num) {
+    if(err) {
+      return callback(err);
+    }
+
+    // set q
+    state.q = num;
+
+    // ensure p is larger than q (swap them if not)
+    if(state.p.compareTo(state.q) < 0) {
+      var tmp = state.p;
+      state.p = state.q;
+      state.q = tmp;
+    }
+
+    // ensure p is coprime with e
+    if(state.p.subtract(BigInteger.ONE).gcd(state.e)
+      .compareTo(BigInteger.ONE) !== 0) {
+      state.p = null;
+      generate();
+      return;
+    }
+
+    // ensure q is coprime with e
+    if(state.q.subtract(BigInteger.ONE).gcd(state.e)
+      .compareTo(BigInteger.ONE) !== 0) {
+      state.q = null;
+      getPrime(state.qBits, finish);
+      return;
+    }
+
+    // compute phi: (p - 1)(q - 1) (Euler's totient function)
+    state.p1 = state.p.subtract(BigInteger.ONE);
+    state.q1 = state.q.subtract(BigInteger.ONE);
+    state.phi = state.p1.multiply(state.q1);
+
+    // ensure e and phi are coprime
+    if(state.phi.gcd(state.e).compareTo(BigInteger.ONE) !== 0) {
+      // phi and e aren't coprime, so generate a new p and q
+      state.p = state.q = null;
+      generate();
+      return;
+    }
+
+    // create n, ensure n is has the right number of bits
+    state.n = state.p.multiply(state.q);
+    if(state.n.bitLength() !== state.bits) {
+      // failed, get new q
+      state.q = null;
+      getPrime(state.qBits, finish);
+      return;
+    }
+
+    // set keys
+    var d = state.e.modInverse(state.phi);
+    state.keys = {
+      privateKey: pki.rsa.setPrivateKey(
+        state.n, state.e, d, state.p, state.q,
+        d.mod(state.p1), d.mod(state.q1),
+        state.q.modInverse(state.p)),
+      publicKey: pki.rsa.setPublicKey(state.n, state.e)
+    };
+
+    callback(null, state.keys);
+  }
+}
+
+/**
+ * Converts a positive BigInteger into 2's-complement big-endian bytes.
+ *
+ * @param b the big integer to convert.
+ *
+ * @return the bytes.
+ */
+function _bnToBytes(b) {
+  // prepend 0x00 if first byte >= 0x80
+  var hex = b.toString(16);
+  if(hex[0] >= '8') {
+    hex = '00' + hex;
+  }
+  return forge.util.hexToBytes(hex);
+}
+
+/**
+ * Returns the required number of Miller-Rabin tests to generate a
+ * prime with an error probability of (1/2)^80.
+ *
+ * See Handbook of Applied Cryptography Chapter 4, Table 4.4.
+ *
+ * @param bits the bit size.
+ *
+ * @return the required number of iterations.
+ */
+function _getMillerRabinTests(bits) {
+  if(bits <= 100) return 27;
+  if(bits <= 150) return 18;
+  if(bits <= 200) return 15;
+  if(bits <= 250) return 12;
+  if(bits <= 300) return 9;
+  if(bits <= 350) return 8;
+  if(bits <= 400) return 7;
+  if(bits <= 500) return 6;
+  if(bits <= 600) return 5;
+  if(bits <= 800) return 4;
+  if(bits <= 1250) return 3;
+  return 2;
+}
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'rsa';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define([
+  'require',
+  'module',
+  './asn1',
+  './jsbn',
+  './oids',
+  './pkcs1',
+  './prime',
+  './random',
+  './util'
+], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/sha1.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/sha1.js
new file mode 100644
index 0000000..53f65d2
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/sha1.js
@@ -0,0 +1,342 @@
+/**
+ * Secure Hash Algorithm with 160-bit digest (SHA-1) implementation.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+var sha1 = forge.sha1 = forge.sha1 || {};
+forge.md = forge.md || {};
+forge.md.algorithms = forge.md.algorithms || {};
+forge.md.sha1 = forge.md.algorithms.sha1 = sha1;
+
+/**
+ * Creates a SHA-1 message digest object.
+ *
+ * @return a message digest object.
+ */
+sha1.create = function() {
+  // do initialization as necessary
+  if(!_initialized) {
+    _init();
+  }
+
+  // SHA-1 state contains five 32-bit integers
+  var _state = null;
+
+  // input buffer
+  var _input = forge.util.createBuffer();
+
+  // used for word storage
+  var _w = new Array(80);
+
+  // message digest object
+  var md = {
+    algorithm: 'sha1',
+    blockLength: 64,
+    digestLength: 20,
+    // 56-bit length of message so far (does not including padding)
+    messageLength: 0,
+    // true 64-bit message length as two 32-bit ints
+    messageLength64: [0, 0]
+  };
+
+  /**
+   * Starts the digest.
+   *
+   * @return this digest object.
+   */
+  md.start = function() {
+    md.messageLength = 0;
+    md.messageLength64 = [0, 0];
+    _input = forge.util.createBuffer();
+    _state = {
+      h0: 0x67452301,
+      h1: 0xEFCDAB89,
+      h2: 0x98BADCFE,
+      h3: 0x10325476,
+      h4: 0xC3D2E1F0
+    };
+    return md;
+  };
+  // start digest automatically for first time
+  md.start();
+
+  /**
+   * Updates the digest with the given message input. The given input can
+   * treated as raw input (no encoding will be applied) or an encoding of
+   * 'utf8' maybe given to encode the input using UTF-8.
+   *
+   * @param msg the message input to update with.
+   * @param encoding the encoding to use (default: 'raw', other: 'utf8').
+   *
+   * @return this digest object.
+   */
+  md.update = function(msg, encoding) {
+    if(encoding === 'utf8') {
+      msg = forge.util.encodeUtf8(msg);
+    }
+
+    // update message length
+    md.messageLength += msg.length;
+    md.messageLength64[0] += (msg.length / 0x100000000) >>> 0;
+    md.messageLength64[1] += msg.length >>> 0;
+
+    // add bytes to input buffer
+    _input.putBytes(msg);
+
+    // process bytes
+    _update(_state, _w, _input);
+
+    // compact input buffer every 2K or if empty
+    if(_input.read > 2048 || _input.length() === 0) {
+      _input.compact();
+    }
+
+    return md;
+  };
+
+   /**
+    * Produces the digest.
+    *
+    * @return a byte buffer containing the digest value.
+    */
+   md.digest = function() {
+    /* Note: Here we copy the remaining bytes in the input buffer and
+    add the appropriate SHA-1 padding. Then we do the final update
+    on a copy of the state so that if the user wants to get
+    intermediate digests they can do so. */
+
+    /* Determine the number of bytes that must be added to the message
+    to ensure its length is congruent to 448 mod 512. In other words,
+    the data to be digested must be a multiple of 512 bits (or 128 bytes).
+    This data includes the message, some padding, and the length of the
+    message. Since the length of the message will be encoded as 8 bytes (64
+    bits), that means that the last segment of the data must have 56 bytes
+    (448 bits) of message and padding. Therefore, the length of the message
+    plus the padding must be congruent to 448 mod 512 because
+    512 - 128 = 448.
+
+    In order to fill up the message length it must be filled with
+    padding that begins with 1 bit followed by all 0 bits. Padding
+    must *always* be present, so if the message length is already
+    congruent to 448 mod 512, then 512 padding bits must be added. */
+
+    // 512 bits == 64 bytes, 448 bits == 56 bytes, 64 bits = 8 bytes
+    // _padding starts with 1 byte with first bit is set in it which
+    // is byte value 128, then there may be up to 63 other pad bytes
+    var padBytes = forge.util.createBuffer();
+    padBytes.putBytes(_input.bytes());
+    // 64 - (remaining msg + 8 bytes msg length) mod 64
+    padBytes.putBytes(
+      _padding.substr(0, 64 - ((md.messageLength64[1] + 8) & 0x3F)));
+
+    /* Now append length of the message. The length is appended in bits
+    as a 64-bit number in big-endian order. Since we store the length in
+    bytes, we must multiply the 64-bit length by 8 (or left shift by 3). */
+    padBytes.putInt32(
+      (md.messageLength64[0] << 3) | (md.messageLength64[0] >>> 28));
+    padBytes.putInt32(md.messageLength64[1] << 3);
+    var s2 = {
+      h0: _state.h0,
+      h1: _state.h1,
+      h2: _state.h2,
+      h3: _state.h3,
+      h4: _state.h4
+    };
+    _update(s2, _w, padBytes);
+    var rval = forge.util.createBuffer();
+    rval.putInt32(s2.h0);
+    rval.putInt32(s2.h1);
+    rval.putInt32(s2.h2);
+    rval.putInt32(s2.h3);
+    rval.putInt32(s2.h4);
+    return rval;
+  };
+
+  return md;
+};
+
+// sha-1 padding bytes not initialized yet
+var _padding = null;
+var _initialized = false;
+
+/**
+ * Initializes the constant tables.
+ */
+function _init() {
+  // create padding
+  _padding = String.fromCharCode(128);
+  _padding += forge.util.fillString(String.fromCharCode(0x00), 64);
+
+  // now initialized
+  _initialized = true;
+}
+
+/**
+ * Updates a SHA-1 state with the given byte buffer.
+ *
+ * @param s the SHA-1 state to update.
+ * @param w the array to use to store words.
+ * @param bytes the byte buffer to update with.
+ */
+function _update(s, w, bytes) {
+  // consume 512 bit (64 byte) chunks
+  var t, a, b, c, d, e, f, i;
+  var len = bytes.length();
+  while(len >= 64) {
+    // the w array will be populated with sixteen 32-bit big-endian words
+    // and then extended into 80 32-bit words according to SHA-1 algorithm
+    // and for 32-79 using Max Locktyukhin's optimization
+
+    // initialize hash value for this chunk
+    a = s.h0;
+    b = s.h1;
+    c = s.h2;
+    d = s.h3;
+    e = s.h4;
+
+    // round 1
+    for(i = 0; i < 16; ++i) {
+      t = bytes.getInt32();
+      w[i] = t;
+      f = d ^ (b & (c ^ d));
+      t = ((a << 5) | (a >>> 27)) + f + e + 0x5A827999 + t;
+      e = d;
+      d = c;
+      c = (b << 30) | (b >>> 2);
+      b = a;
+      a = t;
+    }
+    for(; i < 20; ++i) {
+      t = (w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]);
+      t = (t << 1) | (t >>> 31);
+      w[i] = t;
+      f = d ^ (b & (c ^ d));
+      t = ((a << 5) | (a >>> 27)) + f + e + 0x5A827999 + t;
+      e = d;
+      d = c;
+      c = (b << 30) | (b >>> 2);
+      b = a;
+      a = t;
+    }
+    // round 2
+    for(; i < 32; ++i) {
+      t = (w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]);
+      t = (t << 1) | (t >>> 31);
+      w[i] = t;
+      f = b ^ c ^ d;
+      t = ((a << 5) | (a >>> 27)) + f + e + 0x6ED9EBA1 + t;
+      e = d;
+      d = c;
+      c = (b << 30) | (b >>> 2);
+      b = a;
+      a = t;
+    }
+    for(; i < 40; ++i) {
+      t = (w[i - 6] ^ w[i - 16] ^ w[i - 28] ^ w[i - 32]);
+      t = (t << 2) | (t >>> 30);
+      w[i] = t;
+      f = b ^ c ^ d;
+      t = ((a << 5) | (a >>> 27)) + f + e + 0x6ED9EBA1 + t;
+      e = d;
+      d = c;
+      c = (b << 30) | (b >>> 2);
+      b = a;
+      a = t;
+    }
+    // round 3
+    for(; i < 60; ++i) {
+      t = (w[i - 6] ^ w[i - 16] ^ w[i - 28] ^ w[i - 32]);
+      t = (t << 2) | (t >>> 30);
+      w[i] = t;
+      f = (b & c) | (d & (b ^ c));
+      t = ((a << 5) | (a >>> 27)) + f + e + 0x8F1BBCDC + t;
+      e = d;
+      d = c;
+      c = (b << 30) | (b >>> 2);
+      b = a;
+      a = t;
+    }
+    // round 4
+    for(; i < 80; ++i) {
+      t = (w[i - 6] ^ w[i - 16] ^ w[i - 28] ^ w[i - 32]);
+      t = (t << 2) | (t >>> 30);
+      w[i] = t;
+      f = b ^ c ^ d;
+      t = ((a << 5) | (a >>> 27)) + f + e + 0xCA62C1D6 + t;
+      e = d;
+      d = c;
+      c = (b << 30) | (b >>> 2);
+      b = a;
+      a = t;
+    }
+
+    // update hash state
+    s.h0 = (s.h0 + a) | 0;
+    s.h1 = (s.h1 + b) | 0;
+    s.h2 = (s.h2 + c) | 0;
+    s.h3 = (s.h3 + d) | 0;
+    s.h4 = (s.h4 + e) | 0;
+
+    len -= 64;
+  }
+}
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'sha1';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/sha256.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/sha256.js
new file mode 100644
index 0000000..fdbc4fc
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/sha256.js
@@ -0,0 +1,352 @@
+/**
+ * Secure Hash Algorithm with 256-bit digest (SHA-256) implementation.
+ *
+ * See FIPS 180-2 for details.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+var sha256 = forge.sha256 = forge.sha256 || {};
+forge.md = forge.md || {};
+forge.md.algorithms = forge.md.algorithms || {};
+forge.md.sha256 = forge.md.algorithms.sha256 = sha256;
+
+/**
+ * Creates a SHA-256 message digest object.
+ *
+ * @return a message digest object.
+ */
+sha256.create = function() {
+  // do initialization as necessary
+  if(!_initialized) {
+    _init();
+  }
+
+  // SHA-256 state contains eight 32-bit integers
+  var _state = null;
+
+  // input buffer
+  var _input = forge.util.createBuffer();
+
+  // used for word storage
+  var _w = new Array(64);
+
+  // message digest object
+  var md = {
+    algorithm: 'sha256',
+    blockLength: 64,
+    digestLength: 32,
+    // 56-bit length of message so far (does not including padding)
+    messageLength: 0,
+    // true 64-bit message length as two 32-bit ints
+    messageLength64: [0, 0]
+  };
+
+  /**
+   * Starts the digest.
+   *
+   * @return this digest object.
+   */
+  md.start = function() {
+    md.messageLength = 0;
+    md.messageLength64 = [0, 0];
+    _input = forge.util.createBuffer();
+    _state = {
+      h0: 0x6A09E667,
+      h1: 0xBB67AE85,
+      h2: 0x3C6EF372,
+      h3: 0xA54FF53A,
+      h4: 0x510E527F,
+      h5: 0x9B05688C,
+      h6: 0x1F83D9AB,
+      h7: 0x5BE0CD19
+    };
+    return md;
+  };
+  // start digest automatically for first time
+  md.start();
+
+  /**
+   * Updates the digest with the given message input. The given input can
+   * treated as raw input (no encoding will be applied) or an encoding of
+   * 'utf8' maybe given to encode the input using UTF-8.
+   *
+   * @param msg the message input to update with.
+   * @param encoding the encoding to use (default: 'raw', other: 'utf8').
+   *
+   * @return this digest object.
+   */
+  md.update = function(msg, encoding) {
+    if(encoding === 'utf8') {
+      msg = forge.util.encodeUtf8(msg);
+    }
+
+    // update message length
+    md.messageLength += msg.length;
+    md.messageLength64[0] += (msg.length / 0x100000000) >>> 0;
+    md.messageLength64[1] += msg.length >>> 0;
+
+    // add bytes to input buffer
+    _input.putBytes(msg);
+
+    // process bytes
+    _update(_state, _w, _input);
+
+    // compact input buffer every 2K or if empty
+    if(_input.read > 2048 || _input.length() === 0) {
+      _input.compact();
+    }
+
+    return md;
+  };
+
+  /**
+   * Produces the digest.
+   *
+   * @return a byte buffer containing the digest value.
+   */
+  md.digest = function() {
+    /* Note: Here we copy the remaining bytes in the input buffer and
+    add the appropriate SHA-256 padding. Then we do the final update
+    on a copy of the state so that if the user wants to get
+    intermediate digests they can do so. */
+
+    /* Determine the number of bytes that must be added to the message
+    to ensure its length is congruent to 448 mod 512. In other words,
+    the data to be digested must be a multiple of 512 bits (or 128 bytes).
+    This data includes the message, some padding, and the length of the
+    message. Since the length of the message will be encoded as 8 bytes (64
+    bits), that means that the last segment of the data must have 56 bytes
+    (448 bits) of message and padding. Therefore, the length of the message
+    plus the padding must be congruent to 448 mod 512 because
+    512 - 128 = 448.
+
+    In order to fill up the message length it must be filled with
+    padding that begins with 1 bit followed by all 0 bits. Padding
+    must *always* be present, so if the message length is already
+    congruent to 448 mod 512, then 512 padding bits must be added. */
+
+    // 512 bits == 64 bytes, 448 bits == 56 bytes, 64 bits = 8 bytes
+    // _padding starts with 1 byte with first bit is set in it which
+    // is byte value 128, then there may be up to 63 other pad bytes
+    var padBytes = forge.util.createBuffer();
+    padBytes.putBytes(_input.bytes());
+    // 64 - (remaining msg + 8 bytes msg length) mod 64
+    padBytes.putBytes(
+      _padding.substr(0, 64 - ((md.messageLength64[1] + 8) & 0x3F)));
+
+    /* Now append length of the message. The length is appended in bits
+    as a 64-bit number in big-endian order. Since we store the length in
+    bytes, we must multiply the 64-bit length by 8 (or left shift by 3). */
+    padBytes.putInt32(
+      (md.messageLength64[0] << 3) | (md.messageLength64[0] >>> 28));
+    padBytes.putInt32(md.messageLength64[1] << 3);
+    var s2 = {
+      h0: _state.h0,
+      h1: _state.h1,
+      h2: _state.h2,
+      h3: _state.h3,
+      h4: _state.h4,
+      h5: _state.h5,
+      h6: _state.h6,
+      h7: _state.h7
+    };
+    _update(s2, _w, padBytes);
+    var rval = forge.util.createBuffer();
+    rval.putInt32(s2.h0);
+    rval.putInt32(s2.h1);
+    rval.putInt32(s2.h2);
+    rval.putInt32(s2.h3);
+    rval.putInt32(s2.h4);
+    rval.putInt32(s2.h5);
+    rval.putInt32(s2.h6);
+    rval.putInt32(s2.h7);
+    return rval;
+  };
+
+  return md;
+};
+
+// sha-256 padding bytes not initialized yet
+var _padding = null;
+var _initialized = false;
+
+// table of constants
+var _k = null;
+
+/**
+ * Initializes the constant tables.
+ */
+function _init() {
+  // create padding
+  _padding = String.fromCharCode(128);
+  _padding += forge.util.fillString(String.fromCharCode(0x00), 64);
+
+  // create K table for SHA-256
+  _k = [
+    0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
+    0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+    0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
+    0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
+    0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
+    0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+    0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
+    0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
+    0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
+    0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+    0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
+    0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+    0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
+    0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
+    0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
+    0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2];
+
+  // now initialized
+  _initialized = true;
+}
+
+/**
+ * Updates a SHA-256 state with the given byte buffer.
+ *
+ * @param s the SHA-256 state to update.
+ * @param w the array to use to store words.
+ * @param bytes the byte buffer to update with.
+ */
+function _update(s, w, bytes) {
+  // consume 512 bit (64 byte) chunks
+  var t1, t2, s0, s1, ch, maj, i, a, b, c, d, e, f, g, h;
+  var len = bytes.length();
+  while(len >= 64) {
+    // the w array will be populated with sixteen 32-bit big-endian words
+    // and then extended into 64 32-bit words according to SHA-256
+    for(i = 0; i < 16; ++i) {
+      w[i] = bytes.getInt32();
+    }
+    for(; i < 64; ++i) {
+      // XOR word 2 words ago rot right 17, rot right 19, shft right 10
+      t1 = w[i - 2];
+      t1 =
+        ((t1 >>> 17) | (t1 << 15)) ^
+        ((t1 >>> 19) | (t1 << 13)) ^
+        (t1 >>> 10);
+      // XOR word 15 words ago rot right 7, rot right 18, shft right 3
+      t2 = w[i - 15];
+      t2 =
+        ((t2 >>> 7) | (t2 << 25)) ^
+        ((t2 >>> 18) | (t2 << 14)) ^
+        (t2 >>> 3);
+      // sum(t1, word 7 ago, t2, word 16 ago) modulo 2^32
+      w[i] = (t1 + w[i - 7] + t2 + w[i - 16]) | 0;
+    }
+
+    // initialize hash value for this chunk
+    a = s.h0;
+    b = s.h1;
+    c = s.h2;
+    d = s.h3;
+    e = s.h4;
+    f = s.h5;
+    g = s.h6;
+    h = s.h7;
+
+    // round function
+    for(i = 0; i < 64; ++i) {
+      // Sum1(e)
+      s1 =
+        ((e >>> 6) | (e << 26)) ^
+        ((e >>> 11) | (e << 21)) ^
+        ((e >>> 25) | (e << 7));
+      // Ch(e, f, g) (optimized the same way as SHA-1)
+      ch = g ^ (e & (f ^ g));
+      // Sum0(a)
+      s0 =
+        ((a >>> 2) | (a << 30)) ^
+        ((a >>> 13) | (a << 19)) ^
+        ((a >>> 22) | (a << 10));
+      // Maj(a, b, c) (optimized the same way as SHA-1)
+      maj = (a & b) | (c & (a ^ b));
+
+      // main algorithm
+      t1 = h + s1 + ch + _k[i] + w[i];
+      t2 = s0 + maj;
+      h = g;
+      g = f;
+      f = e;
+      e = (d + t1) | 0;
+      d = c;
+      c = b;
+      b = a;
+      a = (t1 + t2) | 0;
+    }
+
+    // update hash state
+    s.h0 = (s.h0 + a) | 0;
+    s.h1 = (s.h1 + b) | 0;
+    s.h2 = (s.h2 + c) | 0;
+    s.h3 = (s.h3 + d) | 0;
+    s.h4 = (s.h4 + e) | 0;
+    s.h5 = (s.h5 + f) | 0;
+    s.h6 = (s.h6 + g) | 0;
+    s.h7 = (s.h7 + h) | 0;
+    len -= 64;
+  }
+}
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'sha256';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/sha512.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/sha512.js
new file mode 100644
index 0000000..12a9d94
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/sha512.js
@@ -0,0 +1,590 @@
+/**
+ * Secure Hash Algorithm with a 1024-bit block size implementation.
+ *
+ * This includes: SHA-512, SHA-384, SHA-512/224, and SHA-512/256. For
+ * SHA-256 (block size 512 bits), see sha256.js.
+ *
+ * See FIPS 180-4 for details.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+var sha512 = forge.sha512 = forge.sha512 || {};
+forge.md = forge.md || {};
+forge.md.algorithms = forge.md.algorithms || {};
+
+// SHA-512
+forge.md.sha512 = forge.md.algorithms.sha512 = sha512;
+
+// SHA-384
+var sha384 = forge.sha384 = forge.sha512.sha384 = forge.sha512.sha384 || {};
+sha384.create = function() {
+  return sha512.create('SHA-384');
+};
+forge.md.sha384 = forge.md.algorithms.sha384 = sha384;
+
+// SHA-512/256
+forge.sha512.sha256 = forge.sha512.sha256 || {
+  create: function() {
+    return sha512.create('SHA-512/256');
+  }
+};
+forge.md['sha512/256'] = forge.md.algorithms['sha512/256'] =
+  forge.sha512.sha256;
+
+// SHA-512/224
+forge.sha512.sha224 = forge.sha512.sha224 || {
+  create: function() {
+    return sha512.create('SHA-512/224');
+  }
+};
+forge.md['sha512/224'] = forge.md.algorithms['sha512/224'] =
+  forge.sha512.sha224;
+
+/**
+ * Creates a SHA-2 message digest object.
+ *
+ * @param algorithm the algorithm to use (SHA-512, SHA-384, SHA-512/224,
+ *          SHA-512/256).
+ *
+ * @return a message digest object.
+ */
+sha512.create = function(algorithm) {
+  // do initialization as necessary
+  if(!_initialized) {
+    _init();
+  }
+
+  if(typeof algorithm === 'undefined') {
+    algorithm = 'SHA-512';
+  }
+
+  if(!(algorithm in _states)) {
+    throw new Error('Invalid SHA-512 algorithm: ' + algorithm);
+  }
+
+  // SHA-512 state contains eight 64-bit integers (each as two 32-bit ints)
+  var _state = _states[algorithm];
+  var _h = null;
+
+  // input buffer
+  var _input = forge.util.createBuffer();
+
+  // used for 64-bit word storage
+  var _w = new Array(80);
+  for(var wi = 0; wi < 80; ++wi) {
+    _w[wi] = new Array(2);
+  }
+
+  // message digest object
+  var md = {
+    // SHA-512 => sha512
+    algorithm: algorithm.replace('-', '').toLowerCase(),
+    blockLength: 128,
+    digestLength: 64,
+    // 56-bit length of message so far (does not including padding)
+    messageLength: 0,
+    // true 128-bit message length as four 32-bit ints
+    messageLength128: [0, 0, 0, 0]
+  };
+
+  /**
+   * Starts the digest.
+   *
+   * @return this digest object.
+   */
+  md.start = function() {
+    md.messageLength = 0;
+    md.messageLength128 = [0, 0, 0, 0];
+    _input = forge.util.createBuffer();
+    _h = new Array(_state.length);
+    for(var i = 0; i < _state.length; ++i) {
+      _h[i] = _state[i].slice(0);
+    }
+    return md;
+  };
+  // start digest automatically for first time
+  md.start();
+
+  /**
+   * Updates the digest with the given message input. The given input can
+   * treated as raw input (no encoding will be applied) or an encoding of
+   * 'utf8' maybe given to encode the input using UTF-8.
+   *
+   * @param msg the message input to update with.
+   * @param encoding the encoding to use (default: 'raw', other: 'utf8').
+   *
+   * @return this digest object.
+   */
+  md.update = function(msg, encoding) {
+    if(encoding === 'utf8') {
+      msg = forge.util.encodeUtf8(msg);
+    }
+
+    // update message length
+    md.messageLength += msg.length;
+    var len = msg.length;
+    len = [(len / 0x100000000) >>> 0, len >>> 0];
+    for(var i = 3; i >= 0; --i) {
+      md.messageLength128[i] += len[1];
+      len[1] = len[0] + ((md.messageLength128[i] / 0x100000000) >>> 0);
+      md.messageLength128[i] = md.messageLength128[i] >>> 0;
+      len[0] = ((len[1] / 0x100000000) >>> 0);
+    }
+
+    // add bytes to input buffer
+    _input.putBytes(msg);
+
+    // process bytes
+    _update(_h, _w, _input);
+
+    // compact input buffer every 2K or if empty
+    if(_input.read > 2048 || _input.length() === 0) {
+      _input.compact();
+    }
+
+    return md;
+  };
+
+  /**
+   * Produces the digest.
+   *
+   * @return a byte buffer containing the digest value.
+   */
+  md.digest = function() {
+    /* Note: Here we copy the remaining bytes in the input buffer and
+    add the appropriate SHA-512 padding. Then we do the final update
+    on a copy of the state so that if the user wants to get
+    intermediate digests they can do so. */
+
+    /* Determine the number of bytes that must be added to the message
+    to ensure its length is congruent to 896 mod 1024. In other words,
+    the data to be digested must be a multiple of 1024 bits (or 128 bytes).
+    This data includes the message, some padding, and the length of the
+    message. Since the length of the message will be encoded as 16 bytes (128
+    bits), that means that the last segment of the data must have 112 bytes
+    (896 bits) of message and padding. Therefore, the length of the message
+    plus the padding must be congruent to 896 mod 1024 because
+    1024 - 128 = 896.
+
+    In order to fill up the message length it must be filled with
+    padding that begins with 1 bit followed by all 0 bits. Padding
+    must *always* be present, so if the message length is already
+    congruent to 896 mod 1024, then 1024 padding bits must be added. */
+
+    // 1024 bits == 128 bytes, 896 bits == 112 bytes, 128 bits = 16 bytes
+    // _padding starts with 1 byte with first bit is set in it which
+    // is byte value 128, then there may be up to 127 other pad bytes
+    var padBytes = forge.util.createBuffer();
+    padBytes.putBytes(_input.bytes());
+    // 128 - (remaining msg + 16 bytes msg length) mod 128
+    padBytes.putBytes(
+      _padding.substr(0, 128 - ((md.messageLength128[3] + 16) & 0x7F)));
+
+    /* Now append length of the message. The length is appended in bits
+    as a 128-bit number in big-endian order. Since we store the length in
+    bytes, we must multiply the 128-bit length by 8 (or left shift by 3). */
+    var bitLength = [];
+    for(var i = 0; i < 3; ++i) {
+      bitLength[i] = ((md.messageLength128[i] << 3) |
+        (md.messageLength128[i - 1] >>> 28));
+    }
+    // shift the last integer normally
+    bitLength[3] = md.messageLength128[3] << 3;
+    padBytes.putInt32(bitLength[0]);
+    padBytes.putInt32(bitLength[1]);
+    padBytes.putInt32(bitLength[2]);
+    padBytes.putInt32(bitLength[3]);
+    var h = new Array(_h.length);
+    for(var i = 0; i < _h.length; ++i) {
+      h[i] = _h[i].slice(0);
+    }
+    _update(h, _w, padBytes);
+    var rval = forge.util.createBuffer();
+    var hlen;
+    if(algorithm === 'SHA-512') {
+      hlen = h.length;
+    } else if(algorithm === 'SHA-384') {
+      hlen = h.length - 2;
+    } else {
+      hlen = h.length - 4;
+    }
+    for(var i = 0; i < hlen; ++i) {
+      rval.putInt32(h[i][0]);
+      if(i !== hlen - 1 || algorithm !== 'SHA-512/224') {
+        rval.putInt32(h[i][1]);
+      }
+    }
+    return rval;
+  };
+
+  return md;
+};
+
+// sha-512 padding bytes not initialized yet
+var _padding = null;
+var _initialized = false;
+
+// table of constants
+var _k = null;
+
+// initial hash states
+var _states = null;
+
+/**
+ * Initializes the constant tables.
+ */
+function _init() {
+  // create padding
+  _padding = String.fromCharCode(128);
+  _padding += forge.util.fillString(String.fromCharCode(0x00), 128);
+
+  // create K table for SHA-512
+  _k = [
+    [0x428a2f98, 0xd728ae22], [0x71374491, 0x23ef65cd],
+    [0xb5c0fbcf, 0xec4d3b2f], [0xe9b5dba5, 0x8189dbbc],
+    [0x3956c25b, 0xf348b538], [0x59f111f1, 0xb605d019],
+    [0x923f82a4, 0xaf194f9b], [0xab1c5ed5, 0xda6d8118],
+    [0xd807aa98, 0xa3030242], [0x12835b01, 0x45706fbe],
+    [0x243185be, 0x4ee4b28c], [0x550c7dc3, 0xd5ffb4e2],
+    [0x72be5d74, 0xf27b896f], [0x80deb1fe, 0x3b1696b1],
+    [0x9bdc06a7, 0x25c71235], [0xc19bf174, 0xcf692694],
+    [0xe49b69c1, 0x9ef14ad2], [0xefbe4786, 0x384f25e3],
+    [0x0fc19dc6, 0x8b8cd5b5], [0x240ca1cc, 0x77ac9c65],
+    [0x2de92c6f, 0x592b0275], [0x4a7484aa, 0x6ea6e483],
+    [0x5cb0a9dc, 0xbd41fbd4], [0x76f988da, 0x831153b5],
+    [0x983e5152, 0xee66dfab], [0xa831c66d, 0x2db43210],
+    [0xb00327c8, 0x98fb213f], [0xbf597fc7, 0xbeef0ee4],
+    [0xc6e00bf3, 0x3da88fc2], [0xd5a79147, 0x930aa725],
+    [0x06ca6351, 0xe003826f], [0x14292967, 0x0a0e6e70],
+    [0x27b70a85, 0x46d22ffc], [0x2e1b2138, 0x5c26c926],
+    [0x4d2c6dfc, 0x5ac42aed], [0x53380d13, 0x9d95b3df],
+    [0x650a7354, 0x8baf63de], [0x766a0abb, 0x3c77b2a8],
+    [0x81c2c92e, 0x47edaee6], [0x92722c85, 0x1482353b],
+    [0xa2bfe8a1, 0x4cf10364], [0xa81a664b, 0xbc423001],
+    [0xc24b8b70, 0xd0f89791], [0xc76c51a3, 0x0654be30],
+    [0xd192e819, 0xd6ef5218], [0xd6990624, 0x5565a910],
+    [0xf40e3585, 0x5771202a], [0x106aa070, 0x32bbd1b8],
+    [0x19a4c116, 0xb8d2d0c8], [0x1e376c08, 0x5141ab53],
+    [0x2748774c, 0xdf8eeb99], [0x34b0bcb5, 0xe19b48a8],
+    [0x391c0cb3, 0xc5c95a63], [0x4ed8aa4a, 0xe3418acb],
+    [0x5b9cca4f, 0x7763e373], [0x682e6ff3, 0xd6b2b8a3],
+    [0x748f82ee, 0x5defb2fc], [0x78a5636f, 0x43172f60],
+    [0x84c87814, 0xa1f0ab72], [0x8cc70208, 0x1a6439ec],
+    [0x90befffa, 0x23631e28], [0xa4506ceb, 0xde82bde9],
+    [0xbef9a3f7, 0xb2c67915], [0xc67178f2, 0xe372532b],
+    [0xca273ece, 0xea26619c], [0xd186b8c7, 0x21c0c207],
+    [0xeada7dd6, 0xcde0eb1e], [0xf57d4f7f, 0xee6ed178],
+    [0x06f067aa, 0x72176fba], [0x0a637dc5, 0xa2c898a6],
+    [0x113f9804, 0xbef90dae], [0x1b710b35, 0x131c471b],
+    [0x28db77f5, 0x23047d84], [0x32caab7b, 0x40c72493],
+    [0x3c9ebe0a, 0x15c9bebc], [0x431d67c4, 0x9c100d4c],
+    [0x4cc5d4be, 0xcb3e42b6], [0x597f299c, 0xfc657e2a],
+    [0x5fcb6fab, 0x3ad6faec], [0x6c44198c, 0x4a475817]
+  ];
+
+  // initial hash states
+  _states = {};
+  _states['SHA-512'] = [
+    [0x6a09e667, 0xf3bcc908],
+    [0xbb67ae85, 0x84caa73b],
+    [0x3c6ef372, 0xfe94f82b],
+    [0xa54ff53a, 0x5f1d36f1],
+    [0x510e527f, 0xade682d1],
+    [0x9b05688c, 0x2b3e6c1f],
+    [0x1f83d9ab, 0xfb41bd6b],
+    [0x5be0cd19, 0x137e2179]
+  ];
+  _states['SHA-384'] = [
+    [0xcbbb9d5d, 0xc1059ed8],
+    [0x629a292a, 0x367cd507],
+    [0x9159015a, 0x3070dd17],
+    [0x152fecd8, 0xf70e5939],
+    [0x67332667, 0xffc00b31],
+    [0x8eb44a87, 0x68581511],
+    [0xdb0c2e0d, 0x64f98fa7],
+    [0x47b5481d, 0xbefa4fa4]
+  ];
+  _states['SHA-512/256'] = [
+    [0x22312194, 0xFC2BF72C],
+    [0x9F555FA3, 0xC84C64C2],
+    [0x2393B86B, 0x6F53B151],
+    [0x96387719, 0x5940EABD],
+    [0x96283EE2, 0xA88EFFE3],
+    [0xBE5E1E25, 0x53863992],
+    [0x2B0199FC, 0x2C85B8AA],
+    [0x0EB72DDC, 0x81C52CA2]
+  ];
+  _states['SHA-512/224'] = [
+    [0x8C3D37C8, 0x19544DA2],
+    [0x73E19966, 0x89DCD4D6],
+    [0x1DFAB7AE, 0x32FF9C82],
+    [0x679DD514, 0x582F9FCF],
+    [0x0F6D2B69, 0x7BD44DA8],
+    [0x77E36F73, 0x04C48942],
+    [0x3F9D85A8, 0x6A1D36C8],
+    [0x1112E6AD, 0x91D692A1]
+  ];
+
+  // now initialized
+  _initialized = true;
+}
+
+/**
+ * Updates a SHA-512 state with the given byte buffer.
+ *
+ * @param s the SHA-512 state to update.
+ * @param w the array to use to store words.
+ * @param bytes the byte buffer to update with.
+ */
+function _update(s, w, bytes) {
+  // consume 512 bit (128 byte) chunks
+  var t1_hi, t1_lo;
+  var t2_hi, t2_lo;
+  var s0_hi, s0_lo;
+  var s1_hi, s1_lo;
+  var ch_hi, ch_lo;
+  var maj_hi, maj_lo;
+  var a_hi, a_lo;
+  var b_hi, b_lo;
+  var c_hi, c_lo;
+  var d_hi, d_lo;
+  var e_hi, e_lo;
+  var f_hi, f_lo;
+  var g_hi, g_lo;
+  var h_hi, h_lo;
+  var i, hi, lo, w2, w7, w15, w16;
+  var len = bytes.length();
+  while(len >= 128) {
+    // the w array will be populated with sixteen 64-bit big-endian words
+    // and then extended into 64 64-bit words according to SHA-512
+    for(i = 0; i < 16; ++i) {
+      w[i][0] = bytes.getInt32() >>> 0;
+      w[i][1] = bytes.getInt32() >>> 0;
+    }
+    for(; i < 80; ++i) {
+      // for word 2 words ago: ROTR 19(x) ^ ROTR 61(x) ^ SHR 6(x)
+      w2 = w[i - 2];
+      hi = w2[0];
+      lo = w2[1];
+
+      // high bits
+      t1_hi = (
+        ((hi >>> 19) | (lo << 13)) ^ // ROTR 19
+        ((lo >>> 29) | (hi << 3)) ^ // ROTR 61/(swap + ROTR 29)
+        (hi >>> 6)) >>> 0; // SHR 6
+      // low bits
+      t1_lo = (
+        ((hi << 13) | (lo >>> 19)) ^ // ROTR 19
+        ((lo << 3) | (hi >>> 29)) ^ // ROTR 61/(swap + ROTR 29)
+        ((hi << 26) | (lo >>> 6))) >>> 0; // SHR 6
+
+      // for word 15 words ago: ROTR 1(x) ^ ROTR 8(x) ^ SHR 7(x)
+      w15 = w[i - 15];
+      hi = w15[0];
+      lo = w15[1];
+
+      // high bits
+      t2_hi = (
+        ((hi >>> 1) | (lo << 31)) ^ // ROTR 1
+        ((hi >>> 8) | (lo << 24)) ^ // ROTR 8
+        (hi >>> 7)) >>> 0; // SHR 7
+      // low bits
+      t2_lo = (
+        ((hi << 31) | (lo >>> 1)) ^ // ROTR 1
+        ((hi << 24) | (lo >>> 8)) ^ // ROTR 8
+        ((hi << 25) | (lo >>> 7))) >>> 0; // SHR 7
+
+      // sum(t1, word 7 ago, t2, word 16 ago) modulo 2^64 (carry lo overflow)
+      w7 = w[i - 7];
+      w16 = w[i - 16];
+      lo = (t1_lo + w7[1] + t2_lo + w16[1]);
+      w[i][0] = (t1_hi + w7[0] + t2_hi + w16[0] +
+        ((lo / 0x100000000) >>> 0)) >>> 0;
+      w[i][1] = lo >>> 0;
+    }
+
+    // initialize hash value for this chunk
+    a_hi = s[0][0];
+    a_lo = s[0][1];
+    b_hi = s[1][0];
+    b_lo = s[1][1];
+    c_hi = s[2][0];
+    c_lo = s[2][1];
+    d_hi = s[3][0];
+    d_lo = s[3][1];
+    e_hi = s[4][0];
+    e_lo = s[4][1];
+    f_hi = s[5][0];
+    f_lo = s[5][1];
+    g_hi = s[6][0];
+    g_lo = s[6][1];
+    h_hi = s[7][0];
+    h_lo = s[7][1];
+
+    // round function
+    for(i = 0; i < 80; ++i) {
+      // Sum1(e) = ROTR 14(e) ^ ROTR 18(e) ^ ROTR 41(e)
+      s1_hi = (
+        ((e_hi >>> 14) | (e_lo << 18)) ^ // ROTR 14
+        ((e_hi >>> 18) | (e_lo << 14)) ^ // ROTR 18
+        ((e_lo >>> 9) | (e_hi << 23))) >>> 0; // ROTR 41/(swap + ROTR 9)
+      s1_lo = (
+        ((e_hi << 18) | (e_lo >>> 14)) ^ // ROTR 14
+        ((e_hi << 14) | (e_lo >>> 18)) ^ // ROTR 18
+        ((e_lo << 23) | (e_hi >>> 9))) >>> 0; // ROTR 41/(swap + ROTR 9)
+
+      // Ch(e, f, g) (optimized the same way as SHA-1)
+      ch_hi = (g_hi ^ (e_hi & (f_hi ^ g_hi))) >>> 0;
+      ch_lo = (g_lo ^ (e_lo & (f_lo ^ g_lo))) >>> 0;
+
+      // Sum0(a) = ROTR 28(a) ^ ROTR 34(a) ^ ROTR 39(a)
+      s0_hi = (
+        ((a_hi >>> 28) | (a_lo << 4)) ^ // ROTR 28
+        ((a_lo >>> 2) | (a_hi << 30)) ^ // ROTR 34/(swap + ROTR 2)
+        ((a_lo >>> 7) | (a_hi << 25))) >>> 0; // ROTR 39/(swap + ROTR 7)
+      s0_lo = (
+        ((a_hi << 4) | (a_lo >>> 28)) ^ // ROTR 28
+        ((a_lo << 30) | (a_hi >>> 2)) ^ // ROTR 34/(swap + ROTR 2)
+        ((a_lo << 25) | (a_hi >>> 7))) >>> 0; // ROTR 39/(swap + ROTR 7)
+
+      // Maj(a, b, c) (optimized the same way as SHA-1)
+      maj_hi = ((a_hi & b_hi) | (c_hi & (a_hi ^ b_hi))) >>> 0;
+      maj_lo = ((a_lo & b_lo) | (c_lo & (a_lo ^ b_lo))) >>> 0;
+
+      // main algorithm
+      // t1 = (h + s1 + ch + _k[i] + _w[i]) modulo 2^64 (carry lo overflow)
+      lo = (h_lo + s1_lo + ch_lo + _k[i][1] + w[i][1]);
+      t1_hi = (h_hi + s1_hi + ch_hi + _k[i][0] + w[i][0] +
+        ((lo / 0x100000000) >>> 0)) >>> 0;
+      t1_lo = lo >>> 0;
+
+      // t2 = s0 + maj modulo 2^64 (carry lo overflow)
+      lo = s0_lo + maj_lo;
+      t2_hi = (s0_hi + maj_hi + ((lo / 0x100000000) >>> 0)) >>> 0;
+      t2_lo = lo >>> 0;
+
+      h_hi = g_hi;
+      h_lo = g_lo;
+
+      g_hi = f_hi;
+      g_lo = f_lo;
+
+      f_hi = e_hi;
+      f_lo = e_lo;
+
+      // e = (d + t1) modulo 2^64 (carry lo overflow)
+      lo = d_lo + t1_lo;
+      e_hi = (d_hi + t1_hi + ((lo / 0x100000000) >>> 0)) >>> 0;
+      e_lo = lo >>> 0;
+
+      d_hi = c_hi;
+      d_lo = c_lo;
+
+      c_hi = b_hi;
+      c_lo = b_lo;
+
+      b_hi = a_hi;
+      b_lo = a_lo;
+
+      // a = (t1 + t2) modulo 2^64 (carry lo overflow)
+      lo = t1_lo + t2_lo;
+      a_hi = (t1_hi + t2_hi + ((lo / 0x100000000) >>> 0)) >>> 0;
+      a_lo = lo >>> 0;
+    }
+
+    // update hash state (additional modulo 2^64)
+    lo = s[0][1] + a_lo;
+    s[0][0] = (s[0][0] + a_hi + ((lo / 0x100000000) >>> 0)) >>> 0;
+    s[0][1] = lo >>> 0;
+
+    lo = s[1][1] + b_lo;
+    s[1][0] = (s[1][0] + b_hi + ((lo / 0x100000000) >>> 0)) >>> 0;
+    s[1][1] = lo >>> 0;
+
+    lo = s[2][1] + c_lo;
+    s[2][0] = (s[2][0] + c_hi + ((lo / 0x100000000) >>> 0)) >>> 0;
+    s[2][1] = lo >>> 0;
+
+    lo = s[3][1] + d_lo;
+    s[3][0] = (s[3][0] + d_hi + ((lo / 0x100000000) >>> 0)) >>> 0;
+    s[3][1] = lo >>> 0;
+
+    lo = s[4][1] + e_lo;
+    s[4][0] = (s[4][0] + e_hi + ((lo / 0x100000000) >>> 0)) >>> 0;
+    s[4][1] = lo >>> 0;
+
+    lo = s[5][1] + f_lo;
+    s[5][0] = (s[5][0] + f_hi + ((lo / 0x100000000) >>> 0)) >>> 0;
+    s[5][1] = lo >>> 0;
+
+    lo = s[6][1] + g_lo;
+    s[6][0] = (s[6][0] + g_hi + ((lo / 0x100000000) >>> 0)) >>> 0;
+    s[6][1] = lo >>> 0;
+
+    lo = s[7][1] + h_lo;
+    s[7][0] = (s[7][0] + h_hi + ((lo / 0x100000000) >>> 0)) >>> 0;
+    s[7][1] = lo >>> 0;
+
+    len -= 128;
+  }
+}
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'sha512';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/socket.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/socket.js
new file mode 100644
index 0000000..e50e1aa
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/socket.js
@@ -0,0 +1,342 @@
+/**
+ * Socket implementation that uses flash SocketPool class as a backend.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2013 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+// define net namespace
+var net = forge.net = forge.net || {};
+
+// map of flash ID to socket pool
+net.socketPools = {};
+
+/**
+ * Creates a flash socket pool.
+ *
+ * @param options:
+ *          flashId: the dom ID for the flash object element.
+ *          policyPort: the default policy port for sockets, 0 to use the
+ *            flash default.
+ *          policyUrl: the default policy file URL for sockets (if provided
+ *            used instead of a policy port).
+ *          msie: true if the browser is msie, false if not.
+ *
+ * @return the created socket pool.
+ */
+net.createSocketPool = function(options) {
+  // set default
+  options.msie = options.msie || false;
+
+  // initialize the flash interface
+  var spId = options.flashId;
+  var api = document.getElementById(spId);
+  api.init({marshallExceptions: !options.msie});
+
+  // create socket pool entry
+  var sp = {
+    // ID of the socket pool
+    id: spId,
+    // flash interface
+    flashApi: api,
+    // map of socket ID to sockets
+    sockets: {},
+    // default policy port
+    policyPort: options.policyPort || 0,
+    // default policy URL
+    policyUrl: options.policyUrl || null
+  };
+  net.socketPools[spId] = sp;
+
+  // create event handler, subscribe to flash events
+  if(options.msie === true) {
+    sp.handler = function(e) {
+      if(e.id in sp.sockets) {
+        // get handler function
+        var f;
+        switch(e.type) {
+        case 'connect':
+          f = 'connected';
+          break;
+        case 'close':
+          f = 'closed';
+          break;
+        case 'socketData':
+          f = 'data';
+          break;
+        default:
+          f = 'error';
+          break;
+        }
+        /* IE calls javascript on the thread of the external object
+          that triggered the event (in this case flash) ... which will
+          either run concurrently with other javascript or pre-empt any
+          running javascript in the middle of its execution (BAD!) ...
+          calling setTimeout() will schedule the javascript to run on
+          the javascript thread and solve this EVIL problem. */
+        setTimeout(function(){sp.sockets[e.id][f](e);}, 0);
+      }
+    };
+  } else {
+    sp.handler = function(e) {
+      if(e.id in sp.sockets) {
+        // get handler function
+        var f;
+        switch(e.type) {
+        case 'connect':
+          f = 'connected';
+          break;
+        case 'close':
+          f = 'closed';
+          break;
+        case 'socketData':
+          f = 'data';
+          break;
+        default:
+          f = 'error';
+          break;
+        }
+        sp.sockets[e.id][f](e);
+      }
+    };
+  }
+  var handler = 'forge.net.socketPools[\'' + spId + '\'].handler';
+  api.subscribe('connect', handler);
+  api.subscribe('close', handler);
+  api.subscribe('socketData', handler);
+  api.subscribe('ioError', handler);
+  api.subscribe('securityError', handler);
+
+  /**
+   * Destroys a socket pool. The socket pool still needs to be cleaned
+   * up via net.cleanup().
+   */
+  sp.destroy = function() {
+    delete net.socketPools[options.flashId];
+    for(var id in sp.sockets) {
+      sp.sockets[id].destroy();
+    }
+    sp.sockets = {};
+    api.cleanup();
+  };
+
+  /**
+   * Creates a new socket.
+   *
+   * @param options:
+   *          connected: function(event) called when the socket connects.
+   *          closed: function(event) called when the socket closes.
+   *          data: function(event) called when socket data has arrived,
+   *            it can be read from the socket using receive().
+   *          error: function(event) called when a socket error occurs.
+   */
+   sp.createSocket = function(options) {
+     // default to empty options
+     options = options || {};
+
+     // create flash socket
+     var id = api.create();
+
+     // create javascript socket wrapper
+     var socket = {
+       id: id,
+       // set handlers
+       connected: options.connected || function(e){},
+       closed: options.closed || function(e){},
+       data: options.data || function(e){},
+       error: options.error || function(e){}
+     };
+
+     /**
+      * Destroys this socket.
+      */
+     socket.destroy = function() {
+       api.destroy(id);
+       delete sp.sockets[id];
+     };
+
+     /**
+      * Connects this socket.
+      *
+      * @param options:
+      *          host: the host to connect to.
+      *          port: the port to connect to.
+      *          policyPort: the policy port to use (if non-default), 0 to
+      *            use the flash default.
+      *          policyUrl: the policy file URL to use (instead of port).
+      */
+     socket.connect = function(options) {
+       // give precedence to policy URL over policy port
+       // if no policy URL and passed port isn't 0, use default port,
+       // otherwise use 0 for the port
+       var policyUrl = options.policyUrl || null;
+       var policyPort = 0;
+       if(policyUrl === null && options.policyPort !== 0) {
+         policyPort = options.policyPort || sp.policyPort;
+       }
+       api.connect(id, options.host, options.port, policyPort, policyUrl);
+     };
+
+     /**
+      * Closes this socket.
+      */
+     socket.close = function() {
+       api.close(id);
+       socket.closed({
+         id: socket.id,
+         type: 'close',
+         bytesAvailable: 0
+       });
+     };
+
+     /**
+      * Determines if the socket is connected or not.
+      *
+      * @return true if connected, false if not.
+      */
+     socket.isConnected = function() {
+       return api.isConnected(id);
+     };
+
+     /**
+      * Writes bytes to this socket.
+      *
+      * @param bytes the bytes (as a string) to write.
+      *
+      * @return true on success, false on failure.
+      */
+     socket.send = function(bytes) {
+       return api.send(id, forge.util.encode64(bytes));
+     };
+
+     /**
+      * Reads bytes from this socket (non-blocking). Fewer than the number
+      * of bytes requested may be read if enough bytes are not available.
+      *
+      * This method should be called from the data handler if there are
+      * enough bytes available. To see how many bytes are available, check
+      * the 'bytesAvailable' property on the event in the data handler or
+      * call the bytesAvailable() function on the socket. If the browser is
+      * msie, then the bytesAvailable() function should be used to avoid
+      * race conditions. Otherwise, using the property on the data handler's
+      * event may be quicker.
+      *
+      * @param count the maximum number of bytes to read.
+      *
+      * @return the bytes read (as a string) or null on error.
+      */
+     socket.receive = function(count) {
+       var rval = api.receive(id, count).rval;
+       return (rval === null) ? null : forge.util.decode64(rval);
+     };
+
+     /**
+      * Gets the number of bytes available for receiving on the socket.
+      *
+      * @return the number of bytes available for receiving.
+      */
+     socket.bytesAvailable = function() {
+       return api.getBytesAvailable(id);
+     };
+
+     // store and return socket
+     sp.sockets[id] = socket;
+     return socket;
+  };
+
+  return sp;
+};
+
+/**
+ * Destroys a flash socket pool.
+ *
+ * @param options:
+ *          flashId: the dom ID for the flash object element.
+ */
+net.destroySocketPool = function(options) {
+  if(options.flashId in net.socketPools) {
+    var sp = net.socketPools[options.flashId];
+    sp.destroy();
+  }
+};
+
+/**
+ * Creates a new socket.
+ *
+ * @param options:
+ *          flashId: the dom ID for the flash object element.
+ *          connected: function(event) called when the socket connects.
+ *          closed: function(event) called when the socket closes.
+ *          data: function(event) called when socket data has arrived, it
+ *            can be read from the socket using receive().
+ *          error: function(event) called when a socket error occurs.
+ *
+ * @return the created socket.
+ */
+net.createSocket = function(options) {
+  var socket = null;
+  if(options.flashId in net.socketPools) {
+    // get related socket pool
+    var sp = net.socketPools[options.flashId];
+    socket = sp.createSocket(options);
+  }
+  return socket;
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'net';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/ssh.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/ssh.js
new file mode 100644
index 0000000..ef76c82
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/ssh.js
@@ -0,0 +1,295 @@
+/**
+ * Functions to output keys in SSH-friendly formats.
+ *
+ * This is part of the Forge project which may be used under the terms of
+ * either the BSD License or the GNU General Public License (GPL) Version 2.
+ *
+ * See: https://github.com/digitalbazaar/forge/blob/cbebca3780658703d925b61b2caffb1d263a6c1d/LICENSE
+ *
+ * @author https://github.com/shellac
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+var ssh = forge.ssh = forge.ssh || {};
+
+/**
+ * Encodes (and optionally encrypts) a private RSA key as a Putty PPK file.
+ *
+ * @param privateKey the key.
+ * @param passphrase a passphrase to protect the key (falsy for no encryption).
+ * @param comment a comment to include in the key file.
+ *
+ * @return the PPK file as a string.
+ */
+ssh.privateKeyToPutty = function(privateKey, passphrase, comment) {
+  comment = comment || '';
+  passphrase = passphrase || '';
+  var algorithm = 'ssh-rsa';
+  var encryptionAlgorithm = (passphrase === '') ? 'none' : 'aes256-cbc';
+
+  var ppk = 'PuTTY-User-Key-File-2: ' + algorithm + '\r\n';
+  ppk += 'Encryption: ' + encryptionAlgorithm + '\r\n';
+  ppk += 'Comment: ' + comment + '\r\n';
+
+  // public key into buffer for ppk
+  var pubbuffer = forge.util.createBuffer();
+  _addStringToBuffer(pubbuffer, algorithm);
+  _addBigIntegerToBuffer(pubbuffer, privateKey.e);
+  _addBigIntegerToBuffer(pubbuffer, privateKey.n);
+
+  // write public key
+  var pub = forge.util.encode64(pubbuffer.bytes(), 64);
+  var length = Math.floor(pub.length / 66) + 1; // 66 = 64 + \r\n
+  ppk += 'Public-Lines: ' + length + '\r\n';
+  ppk += pub;
+
+  // private key into a buffer
+  var privbuffer = forge.util.createBuffer();
+  _addBigIntegerToBuffer(privbuffer, privateKey.d);
+  _addBigIntegerToBuffer(privbuffer, privateKey.p);
+  _addBigIntegerToBuffer(privbuffer, privateKey.q);
+  _addBigIntegerToBuffer(privbuffer, privateKey.qInv);
+
+  // optionally encrypt the private key
+  var priv;
+  if(!passphrase) {
+    // use the unencrypted buffer
+    priv = forge.util.encode64(privbuffer.bytes(), 64);
+  } else {
+    // encrypt RSA key using passphrase
+    var encLen = privbuffer.length() + 16 - 1;
+    encLen -= encLen % 16;
+
+    // pad private key with sha1-d data -- needs to be a multiple of 16
+    var padding = _sha1(privbuffer.bytes());
+
+    padding.truncate(padding.length() - encLen + privbuffer.length());
+    privbuffer.putBuffer(padding);
+
+    var aeskey = forge.util.createBuffer();
+    aeskey.putBuffer(_sha1('\x00\x00\x00\x00', passphrase));
+    aeskey.putBuffer(_sha1('\x00\x00\x00\x01', passphrase));
+
+    // encrypt some bytes using CBC mode
+    // key is 40 bytes, so truncate *by* 8 bytes
+    var cipher = forge.aes.createEncryptionCipher(aeskey.truncate(8), 'CBC');
+    cipher.start(forge.util.createBuffer().fillWithByte(0, 16));
+    cipher.update(privbuffer.copy());
+    cipher.finish();
+    var encrypted = cipher.output;
+
+    // Note: this appears to differ from Putty -- is forge wrong, or putty?
+    // due to padding we finish as an exact multiple of 16
+    encrypted.truncate(16); // all padding
+
+    priv = forge.util.encode64(encrypted.bytes(), 64);
+  }
+
+  // output private key
+  length = Math.floor(priv.length / 66) + 1; // 64 + \r\n
+  ppk += '\r\nPrivate-Lines: ' + length + '\r\n';
+  ppk += priv;
+
+  // MAC
+  var mackey = _sha1('putty-private-key-file-mac-key', passphrase);
+
+  var macbuffer = forge.util.createBuffer();
+  _addStringToBuffer(macbuffer, algorithm);
+  _addStringToBuffer(macbuffer, encryptionAlgorithm);
+  _addStringToBuffer(macbuffer, comment);
+  macbuffer.putInt32(pubbuffer.length());
+  macbuffer.putBuffer(pubbuffer);
+  macbuffer.putInt32(privbuffer.length());
+  macbuffer.putBuffer(privbuffer);
+
+  var hmac = forge.hmac.create();
+  hmac.start('sha1', mackey);
+  hmac.update(macbuffer.bytes());
+
+  ppk += '\r\nPrivate-MAC: ' + hmac.digest().toHex() + '\r\n';
+
+  return ppk;
+};
+
+/**
+ * Encodes a public RSA key as an OpenSSH file.
+ *
+ * @param key the key.
+ * @param comment a comment.
+ *
+ * @return the public key in OpenSSH format.
+ */
+ssh.publicKeyToOpenSSH = function(key, comment) {
+  var type = 'ssh-rsa';
+  comment = comment || '';
+
+  var buffer = forge.util.createBuffer();
+  _addStringToBuffer(buffer, type);
+  _addBigIntegerToBuffer(buffer, key.e);
+  _addBigIntegerToBuffer(buffer, key.n);
+
+  return type + ' ' + forge.util.encode64(buffer.bytes()) + ' ' + comment;
+};
+
+/**
+ * Encodes a private RSA key as an OpenSSH file.
+ *
+ * @param key the key.
+ * @param passphrase a passphrase to protect the key (falsy for no encryption).
+ *
+ * @return the public key in OpenSSH format.
+ */
+ssh.privateKeyToOpenSSH = function(privateKey, passphrase) {
+  if(!passphrase) {
+    return forge.pki.privateKeyToPem(privateKey);
+  }
+  // OpenSSH private key is just a legacy format, it seems
+  return forge.pki.encryptRsaPrivateKey(privateKey, passphrase,
+    {legacy: true, algorithm: 'aes128'});
+};
+
+/**
+ * Gets the SSH fingerprint for the given public key.
+ *
+ * @param options the options to use.
+ *          [md] the message digest object to use (defaults to forge.md.md5).
+ *          [encoding] an alternative output encoding, such as 'hex'
+ *            (defaults to none, outputs a byte buffer).
+ *          [delimiter] the delimiter to use between bytes for 'hex' encoded
+ *            output, eg: ':' (defaults to none).
+ *
+ * @return the fingerprint as a byte buffer or other encoding based on options.
+ */
+ssh.getPublicKeyFingerprint = function(key, options) {
+  options = options || {};
+  var md = options.md || forge.md.md5.create();
+
+  var type = 'ssh-rsa';
+  var buffer = forge.util.createBuffer();
+  _addStringToBuffer(buffer, type);
+  _addBigIntegerToBuffer(buffer, key.e);
+  _addBigIntegerToBuffer(buffer, key.n);
+
+  // hash public key bytes
+  md.start();
+  md.update(buffer.getBytes());
+  var digest = md.digest();
+  if(options.encoding === 'hex') {
+    var hex = digest.toHex();
+    if(options.delimiter) {
+      return hex.match(/.{2}/g).join(options.delimiter);
+    }
+    return hex;
+  } else if(options.encoding === 'binary') {
+    return digest.getBytes();
+  } else if(options.encoding) {
+    throw new Error('Unknown encoding "' + options.encoding + '".');
+  }
+  return digest;
+};
+
+/**
+ * Adds len(val) then val to a buffer.
+ *
+ * @param buffer the buffer to add to.
+ * @param val a big integer.
+ */
+function _addBigIntegerToBuffer(buffer, val) {
+  var hexVal = val.toString(16);
+  // ensure 2s complement +ve
+  if(hexVal[0] >= '8') {
+    hexVal = '00' + hexVal;
+  }
+  var bytes = forge.util.hexToBytes(hexVal);
+  buffer.putInt32(bytes.length);
+  buffer.putBytes(bytes);
+}
+
+/**
+ * Adds len(val) then val to a buffer.
+ *
+ * @param buffer the buffer to add to.
+ * @param val a string.
+ */
+function _addStringToBuffer(buffer, val) {
+  buffer.putInt32(val.length);
+  buffer.putString(val);
+}
+
+/**
+ * Hashes the arguments into one value using SHA-1.
+ *
+ * @return the sha1 hash of the provided arguments.
+ */
+function _sha1() {
+  var sha = forge.md.sha1.create();
+  var num = arguments.length;
+  for (var i = 0; i < num; ++i) {
+    sha.update(arguments[i]);
+  }
+  return sha.digest();
+}
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'ssh';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define([
+  'require',
+  'module',
+  './aes',
+  './hmac',
+  './md5',
+  './sha1',
+  './util'
+], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/task.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/task.js
new file mode 100644
index 0000000..f49bbf7
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/task.js
@@ -0,0 +1,778 @@
+/**
+ * Support for concurrent task management and synchronization in web
+ * applications.
+ *
+ * @author Dave Longley
+ * @author David I. Lehn <dlehn@digitalbazaar.com>
+ *
+ * Copyright (c) 2009-2013 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+// logging category
+var cat = 'forge.task';
+
+// verbose level
+// 0: off, 1: a little, 2: a whole lot
+// Verbose debug logging is surrounded by a level check to avoid the
+// performance issues with even calling the logging code regardless if it
+// is actually logged.  For performance reasons this should not be set to 2
+// for production use.
+// ex: if(sVL >= 2) forge.log.verbose(....)
+var sVL = 0;
+
+// track tasks for debugging
+var sTasks = {};
+var sNextTaskId = 0;
+// debug access
+forge.debug.set(cat, 'tasks', sTasks);
+
+// a map of task type to task queue
+var sTaskQueues = {};
+// debug access
+forge.debug.set(cat, 'queues', sTaskQueues);
+
+// name for unnamed tasks
+var sNoTaskName = '?';
+
+// maximum number of doNext() recursions before a context swap occurs
+// FIXME: might need to tweak this based on the browser
+var sMaxRecursions = 30;
+
+// time slice for doing tasks before a context swap occurs
+// FIXME: might need to tweak this based on the browser
+var sTimeSlice = 20;
+
+/**
+ * Task states.
+ *
+ * READY: ready to start processing
+ * RUNNING: task or a subtask is running
+ * BLOCKED: task is waiting to acquire N permits to continue
+ * SLEEPING: task is sleeping for a period of time
+ * DONE: task is done
+ * ERROR: task has an error
+ */
+var READY = 'ready';
+var RUNNING = 'running';
+var BLOCKED = 'blocked';
+var SLEEPING = 'sleeping';
+var DONE = 'done';
+var ERROR = 'error';
+
+/**
+ * Task actions.  Used to control state transitions.
+ *
+ * STOP: stop processing
+ * START: start processing tasks
+ * BLOCK: block task from continuing until 1 or more permits are released
+ * UNBLOCK: release one or more permits
+ * SLEEP: sleep for a period of time
+ * WAKEUP: wakeup early from SLEEPING state
+ * CANCEL: cancel further tasks
+ * FAIL: a failure occured
+ */
+var STOP = 'stop';
+var START = 'start';
+var BLOCK = 'block';
+var UNBLOCK = 'unblock';
+var SLEEP = 'sleep';
+var WAKEUP = 'wakeup';
+var CANCEL = 'cancel';
+var FAIL = 'fail';
+
+/**
+ * State transition table.
+ *
+ * nextState = sStateTable[currentState][action]
+ */
+var sStateTable = {};
+
+sStateTable[READY] = {};
+sStateTable[READY][STOP] = READY;
+sStateTable[READY][START] = RUNNING;
+sStateTable[READY][CANCEL] = DONE;
+sStateTable[READY][FAIL] = ERROR;
+
+sStateTable[RUNNING] = {};
+sStateTable[RUNNING][STOP] = READY;
+sStateTable[RUNNING][START] = RUNNING;
+sStateTable[RUNNING][BLOCK] = BLOCKED;
+sStateTable[RUNNING][UNBLOCK] = RUNNING;
+sStateTable[RUNNING][SLEEP] = SLEEPING;
+sStateTable[RUNNING][WAKEUP] = RUNNING;
+sStateTable[RUNNING][CANCEL] = DONE;
+sStateTable[RUNNING][FAIL] = ERROR;
+
+sStateTable[BLOCKED] = {};
+sStateTable[BLOCKED][STOP] = BLOCKED;
+sStateTable[BLOCKED][START] = BLOCKED;
+sStateTable[BLOCKED][BLOCK] = BLOCKED;
+sStateTable[BLOCKED][UNBLOCK] = BLOCKED;
+sStateTable[BLOCKED][SLEEP] = BLOCKED;
+sStateTable[BLOCKED][WAKEUP] = BLOCKED;
+sStateTable[BLOCKED][CANCEL] = DONE;
+sStateTable[BLOCKED][FAIL] = ERROR;
+
+sStateTable[SLEEPING] = {};
+sStateTable[SLEEPING][STOP] = SLEEPING;
+sStateTable[SLEEPING][START] = SLEEPING;
+sStateTable[SLEEPING][BLOCK] = SLEEPING;
+sStateTable[SLEEPING][UNBLOCK] = SLEEPING;
+sStateTable[SLEEPING][SLEEP] = SLEEPING;
+sStateTable[SLEEPING][WAKEUP] = SLEEPING;
+sStateTable[SLEEPING][CANCEL] = DONE;
+sStateTable[SLEEPING][FAIL] = ERROR;
+
+sStateTable[DONE] = {};
+sStateTable[DONE][STOP] = DONE;
+sStateTable[DONE][START] = DONE;
+sStateTable[DONE][BLOCK] = DONE;
+sStateTable[DONE][UNBLOCK] = DONE;
+sStateTable[DONE][SLEEP] = DONE;
+sStateTable[DONE][WAKEUP] = DONE;
+sStateTable[DONE][CANCEL] = DONE;
+sStateTable[DONE][FAIL] = ERROR;
+
+sStateTable[ERROR] = {};
+sStateTable[ERROR][STOP] = ERROR;
+sStateTable[ERROR][START] = ERROR;
+sStateTable[ERROR][BLOCK] = ERROR;
+sStateTable[ERROR][UNBLOCK] = ERROR;
+sStateTable[ERROR][SLEEP] = ERROR;
+sStateTable[ERROR][WAKEUP] = ERROR;
+sStateTable[ERROR][CANCEL] = ERROR;
+sStateTable[ERROR][FAIL] = ERROR;
+
+/**
+ * Creates a new task.
+ *
+ * @param options options for this task
+ *   run: the run function for the task (required)
+ *   name: the run function for the task (optional)
+ *   parent: parent of this task (optional)
+ *
+ * @return the empty task.
+ */
+var Task = function(options) {
+  // task id
+  this.id = -1;
+
+  // task name
+  this.name = options.name || sNoTaskName;
+
+  // task has no parent
+  this.parent = options.parent || null;
+
+  // save run function
+  this.run = options.run;
+
+  // create a queue of subtasks to run
+  this.subtasks = [];
+
+  // error flag
+  this.error = false;
+
+  // state of the task
+  this.state = READY;
+
+  // number of times the task has been blocked (also the number
+  // of permits needed to be released to continue running)
+  this.blocks = 0;
+
+  // timeout id when sleeping
+  this.timeoutId = null;
+
+  // no swap time yet
+  this.swapTime = null;
+
+  // no user data
+  this.userData = null;
+
+  // initialize task
+  // FIXME: deal with overflow
+  this.id = sNextTaskId++;
+  sTasks[this.id] = this;
+  if(sVL >= 1) {
+    forge.log.verbose(cat, '[%s][%s] init', this.id, this.name, this);
+  }
+};
+
+/**
+ * Logs debug information on this task and the system state.
+ */
+Task.prototype.debug = function(msg) {
+  msg = msg || '';
+  forge.log.debug(cat, msg,
+    '[%s][%s] task:', this.id, this.name, this,
+    'subtasks:', this.subtasks.length,
+    'queue:', sTaskQueues);
+};
+
+/**
+ * Adds a subtask to run after task.doNext() or task.fail() is called.
+ *
+ * @param name human readable name for this task (optional).
+ * @param subrun a function to run that takes the current task as
+ *          its first parameter.
+ *
+ * @return the current task (useful for chaining next() calls).
+ */
+Task.prototype.next = function(name, subrun) {
+  // juggle parameters if it looks like no name is given
+  if(typeof(name) === 'function') {
+    subrun = name;
+
+    // inherit parent's name
+    name = this.name;
+  }
+  // create subtask, set parent to this task, propagate callbacks
+  var subtask = new Task({
+    run: subrun,
+    name: name,
+    parent: this
+  });
+  // start subtasks running
+  subtask.state = RUNNING;
+  subtask.type = this.type;
+  subtask.successCallback = this.successCallback || null;
+  subtask.failureCallback = this.failureCallback || null;
+
+  // queue a new subtask
+  this.subtasks.push(subtask);
+
+  return this;
+};
+
+/**
+ * Adds subtasks to run in parallel after task.doNext() or task.fail()
+ * is called.
+ *
+ * @param name human readable name for this task (optional).
+ * @param subrun functions to run that take the current task as
+ *          their first parameter.
+ *
+ * @return the current task (useful for chaining next() calls).
+ */
+Task.prototype.parallel = function(name, subrun) {
+  // juggle parameters if it looks like no name is given
+  if(forge.util.isArray(name)) {
+    subrun = name;
+
+    // inherit parent's name
+    name = this.name;
+  }
+  // Wrap parallel tasks in a regular task so they are started at the
+  // proper time.
+  return this.next(name, function(task) {
+    // block waiting for subtasks
+    var ptask = task;
+    ptask.block(subrun.length);
+
+    // we pass the iterator from the loop below as a parameter
+    // to a function because it is otherwise included in the
+    // closure and changes as the loop changes -- causing i
+    // to always be set to its highest value
+    var startParallelTask = function(pname, pi) {
+      forge.task.start({
+        type: pname,
+        run: function(task) {
+           subrun[pi](task);
+        },
+        success: function(task) {
+           ptask.unblock();
+        },
+        failure: function(task) {
+           ptask.unblock();
+        }
+      });
+    };
+
+    for(var i = 0; i < subrun.length; i++) {
+      // Type must be unique so task starts in parallel:
+      //    name + private string + task id + sub-task index
+      // start tasks in parallel and unblock when the finish
+      var pname = name + '__parallel-' + task.id + '-' + i;
+      var pi = i;
+      startParallelTask(pname, pi);
+    }
+  });
+};
+
+/**
+ * Stops a running task.
+ */
+Task.prototype.stop = function() {
+  this.state = sStateTable[this.state][STOP];
+};
+
+/**
+ * Starts running a task.
+ */
+Task.prototype.start = function() {
+  this.error = false;
+  this.state = sStateTable[this.state][START];
+
+  // try to restart
+  if(this.state === RUNNING) {
+    this.start = new Date();
+    this.run(this);
+    runNext(this, 0);
+  }
+};
+
+/**
+ * Blocks a task until it one or more permits have been released. The
+ * task will not resume until the requested number of permits have
+ * been released with call(s) to unblock().
+ *
+ * @param n number of permits to wait for(default: 1).
+ */
+Task.prototype.block = function(n) {
+  n = typeof(n) === 'undefined' ? 1 : n;
+  this.blocks += n;
+  if(this.blocks > 0) {
+    this.state = sStateTable[this.state][BLOCK];
+  }
+};
+
+/**
+ * Releases a permit to unblock a task. If a task was blocked by
+ * requesting N permits via block(), then it will only continue
+ * running once enough permits have been released via unblock() calls.
+ *
+ * If multiple processes need to synchronize with a single task then
+ * use a condition variable (see forge.task.createCondition). It is
+ * an error to unblock a task more times than it has been blocked.
+ *
+ * @param n number of permits to release (default: 1).
+ *
+ * @return the current block count (task is unblocked when count is 0)
+ */
+Task.prototype.unblock = function(n) {
+  n = typeof(n) === 'undefined' ? 1 : n;
+  this.blocks -= n;
+  if(this.blocks === 0 && this.state !== DONE) {
+    this.state = RUNNING;
+    runNext(this, 0);
+  }
+  return this.blocks;
+};
+
+/**
+ * Sleep for a period of time before resuming tasks.
+ *
+ * @param n number of milliseconds to sleep (default: 0).
+ */
+Task.prototype.sleep = function(n) {
+  n = typeof(n) === 'undefined' ? 0 : n;
+  this.state = sStateTable[this.state][SLEEP];
+  var self = this;
+  this.timeoutId = setTimeout(function() {
+    self.timeoutId = null;
+    self.state = RUNNING;
+    runNext(self, 0);
+  }, n);
+};
+
+/**
+ * Waits on a condition variable until notified. The next task will
+ * not be scheduled until notification. A condition variable can be
+ * created with forge.task.createCondition().
+ *
+ * Once cond.notify() is called, the task will continue.
+ *
+ * @param cond the condition variable to wait on.
+ */
+Task.prototype.wait = function(cond) {
+  cond.wait(this);
+};
+
+/**
+ * If sleeping, wakeup and continue running tasks.
+ */
+Task.prototype.wakeup = function() {
+  if(this.state === SLEEPING) {
+    cancelTimeout(this.timeoutId);
+    this.timeoutId = null;
+    this.state = RUNNING;
+    runNext(this, 0);
+  }
+};
+
+/**
+ * Cancel all remaining subtasks of this task.
+ */
+Task.prototype.cancel = function() {
+  this.state = sStateTable[this.state][CANCEL];
+  // remove permits needed
+  this.permitsNeeded = 0;
+  // cancel timeouts
+  if(this.timeoutId !== null) {
+    cancelTimeout(this.timeoutId);
+    this.timeoutId = null;
+  }
+  // remove subtasks
+  this.subtasks = [];
+};
+
+/**
+ * Finishes this task with failure and sets error flag. The entire
+ * task will be aborted unless the next task that should execute
+ * is passed as a parameter. This allows levels of subtasks to be
+ * skipped. For instance, to abort only this tasks's subtasks, then
+ * call fail(task.parent). To abort this task's subtasks and its
+ * parent's subtasks, call fail(task.parent.parent). To abort
+ * all tasks and simply call the task callback, call fail() or
+ * fail(null).
+ *
+ * The task callback (success or failure) will always, eventually, be
+ * called.
+ *
+ * @param next the task to continue at, or null to abort entirely.
+ */
+Task.prototype.fail = function(next) {
+  // set error flag
+  this.error = true;
+
+  // finish task
+  finish(this, true);
+
+  if(next) {
+    // propagate task info
+    next.error = this.error;
+    next.swapTime = this.swapTime;
+    next.userData = this.userData;
+
+    // do next task as specified
+    runNext(next, 0);
+  } else {
+    if(this.parent !== null) {
+      // finish root task (ensures it is removed from task queue)
+      var parent = this.parent;
+      while(parent.parent !== null) {
+        // propagate task info
+        parent.error = this.error;
+        parent.swapTime = this.swapTime;
+        parent.userData = this.userData;
+        parent = parent.parent;
+      }
+      finish(parent, true);
+    }
+
+    // call failure callback if one exists
+    if(this.failureCallback) {
+      this.failureCallback(this);
+    }
+  }
+};
+
+/**
+ * Asynchronously start a task.
+ *
+ * @param task the task to start.
+ */
+var start = function(task) {
+  task.error = false;
+  task.state = sStateTable[task.state][START];
+  setTimeout(function() {
+    if(task.state === RUNNING) {
+      task.swapTime = +new Date();
+      task.run(task);
+      runNext(task, 0);
+    }
+  }, 0);
+};
+
+/**
+ * Run the next subtask or finish this task.
+ *
+ * @param task the task to process.
+ * @param recurse the recursion count.
+ */
+var runNext = function(task, recurse) {
+  // get time since last context swap (ms), if enough time has passed set
+  // swap to true to indicate that doNext was performed asynchronously
+  // also, if recurse is too high do asynchronously
+  var swap =
+    (recurse > sMaxRecursions) ||
+    (+new Date() - task.swapTime) > sTimeSlice;
+
+  var doNext = function(recurse) {
+    recurse++;
+    if(task.state === RUNNING) {
+      if(swap) {
+        // update swap time
+        task.swapTime = +new Date();
+      }
+
+      if(task.subtasks.length > 0) {
+        // run next subtask
+        var subtask = task.subtasks.shift();
+        subtask.error = task.error;
+        subtask.swapTime = task.swapTime;
+        subtask.userData = task.userData;
+        subtask.run(subtask);
+        if(!subtask.error) {
+           runNext(subtask, recurse);
+        }
+      } else {
+        finish(task);
+
+        if(!task.error) {
+          // chain back up and run parent
+          if(task.parent !== null) {
+            // propagate task info
+            task.parent.error = task.error;
+            task.parent.swapTime = task.swapTime;
+            task.parent.userData = task.userData;
+
+            // no subtasks left, call run next subtask on parent
+            runNext(task.parent, recurse);
+          }
+        }
+      }
+    }
+  };
+
+  if(swap) {
+    // we're swapping, so run asynchronously
+    setTimeout(doNext, 0);
+  } else {
+    // not swapping, so run synchronously
+    doNext(recurse);
+  }
+};
+
+/**
+ * Finishes a task and looks for the next task in the queue to start.
+ *
+ * @param task the task to finish.
+ * @param suppressCallbacks true to suppress callbacks.
+ */
+var finish = function(task, suppressCallbacks) {
+  // subtask is now done
+  task.state = DONE;
+
+  delete sTasks[task.id];
+  if(sVL >= 1) {
+    forge.log.verbose(cat, '[%s][%s] finish',
+      task.id, task.name, task);
+  }
+
+  // only do queue processing for root tasks
+  if(task.parent === null) {
+    // report error if queue is missing
+    if(!(task.type in sTaskQueues)) {
+      forge.log.error(cat,
+        '[%s][%s] task queue missing [%s]',
+        task.id, task.name, task.type);
+    } else if(sTaskQueues[task.type].length === 0) {
+      // report error if queue is empty
+      forge.log.error(cat,
+        '[%s][%s] task queue empty [%s]',
+        task.id, task.name, task.type);
+    } else if(sTaskQueues[task.type][0] !== task) {
+      // report error if this task isn't the first in the queue
+      forge.log.error(cat,
+        '[%s][%s] task not first in queue [%s]',
+        task.id, task.name, task.type);
+    } else {
+      // remove ourselves from the queue
+      sTaskQueues[task.type].shift();
+      // clean up queue if it is empty
+      if(sTaskQueues[task.type].length === 0) {
+        if(sVL >= 1) {
+          forge.log.verbose(cat, '[%s][%s] delete queue [%s]',
+            task.id, task.name, task.type);
+        }
+        /* Note: Only a task can delete a queue of its own type. This
+         is used as a way to synchronize tasks. If a queue for a certain
+         task type exists, then a task of that type is running.
+         */
+        delete sTaskQueues[task.type];
+      } else {
+        // dequeue the next task and start it
+        if(sVL >= 1) {
+          forge.log.verbose(cat,
+            '[%s][%s] queue start next [%s] remain:%s',
+            task.id, task.name, task.type,
+            sTaskQueues[task.type].length);
+        }
+        sTaskQueues[task.type][0].start();
+      }
+    }
+
+    if(!suppressCallbacks) {
+      // call final callback if one exists
+      if(task.error && task.failureCallback) {
+        task.failureCallback(task);
+      } else if(!task.error && task.successCallback) {
+        task.successCallback(task);
+      }
+    }
+  }
+};
+
+/* Tasks API */
+forge.task = forge.task || {};
+
+/**
+ * Starts a new task that will run the passed function asynchronously.
+ *
+ * In order to finish the task, either task.doNext() or task.fail()
+ * *must* be called.
+ *
+ * The task must have a type (a string identifier) that can be used to
+ * synchronize it with other tasks of the same type. That type can also
+ * be used to cancel tasks that haven't started yet.
+ *
+ * To start a task, the following object must be provided as a parameter
+ * (each function takes a task object as its first parameter):
+ *
+ * {
+ *   type: the type of task.
+ *   run: the function to run to execute the task.
+ *   success: a callback to call when the task succeeds (optional).
+ *   failure: a callback to call when the task fails (optional).
+ * }
+ *
+ * @param options the object as described above.
+ */
+forge.task.start = function(options) {
+  // create a new task
+  var task = new Task({
+    run: options.run,
+    name: options.name || sNoTaskName
+  });
+  task.type = options.type;
+  task.successCallback = options.success || null;
+  task.failureCallback = options.failure || null;
+
+  // append the task onto the appropriate queue
+  if(!(task.type in sTaskQueues)) {
+    if(sVL >= 1) {
+      forge.log.verbose(cat, '[%s][%s] create queue [%s]',
+        task.id, task.name, task.type);
+    }
+    // create the queue with the new task
+    sTaskQueues[task.type] = [task];
+    start(task);
+  } else {
+    // push the task onto the queue, it will be run after a task
+    // with the same type completes
+    sTaskQueues[options.type].push(task);
+  }
+};
+
+/**
+ * Cancels all tasks of the given type that haven't started yet.
+ *
+ * @param type the type of task to cancel.
+ */
+forge.task.cancel = function(type) {
+  // find the task queue
+  if(type in sTaskQueues) {
+    // empty all but the current task from the queue
+    sTaskQueues[type] = [sTaskQueues[type][0]];
+  }
+};
+
+/**
+ * Creates a condition variable to synchronize tasks. To make a task wait
+ * on the condition variable, call task.wait(condition). To notify all
+ * tasks that are waiting, call condition.notify().
+ *
+ * @return the condition variable.
+ */
+forge.task.createCondition = function() {
+  var cond = {
+    // all tasks that are blocked
+    tasks: {}
+  };
+
+  /**
+   * Causes the given task to block until notify is called. If the task
+   * is already waiting on this condition then this is a no-op.
+   *
+   * @param task the task to cause to wait.
+   */
+  cond.wait = function(task) {
+    // only block once
+    if(!(task.id in cond.tasks)) {
+       task.block();
+       cond.tasks[task.id] = task;
+    }
+  };
+
+  /**
+   * Notifies all waiting tasks to wake up.
+   */
+  cond.notify = function() {
+    // since unblock() will run the next task from here, make sure to
+    // clear the condition's blocked task list before unblocking
+    var tmp = cond.tasks;
+    cond.tasks = {};
+    for(var id in tmp) {
+      tmp[id].unblock();
+    }
+  };
+
+  return cond;
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'task';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './debug', './log', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/tls.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/tls.js
new file mode 100644
index 0000000..b3bb2e8
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/tls.js
@@ -0,0 +1,4316 @@
+/**
+ * A Javascript implementation of Transport Layer Security (TLS).
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2009-2014 Digital Bazaar, Inc.
+ *
+ * The TLS Handshake Protocol involves the following steps:
+ *
+ * - Exchange hello messages to agree on algorithms, exchange random values,
+ * and check for session resumption.
+ *
+ * - Exchange the necessary cryptographic parameters to allow the client and
+ * server to agree on a premaster secret.
+ *
+ * - Exchange certificates and cryptographic information to allow the client
+ * and server to authenticate themselves.
+ *
+ * - Generate a master secret from the premaster secret and exchanged random
+ * values.
+ *
+ * - Provide security parameters to the record layer.
+ *
+ * - Allow the client and server to verify that their peer has calculated the
+ * same security parameters and that the handshake occurred without tampering
+ * by an attacker.
+ *
+ * Up to 4 different messages may be sent during a key exchange. The server
+ * certificate, the server key exchange, the client certificate, and the
+ * client key exchange.
+ *
+ * A typical handshake (from the client's perspective).
+ *
+ * 1. Client sends ClientHello.
+ * 2. Client receives ServerHello.
+ * 3. Client receives optional Certificate.
+ * 4. Client receives optional ServerKeyExchange.
+ * 5. Client receives ServerHelloDone.
+ * 6. Client sends optional Certificate.
+ * 7. Client sends ClientKeyExchange.
+ * 8. Client sends optional CertificateVerify.
+ * 9. Client sends ChangeCipherSpec.
+ * 10. Client sends Finished.
+ * 11. Client receives ChangeCipherSpec.
+ * 12. Client receives Finished.
+ * 13. Client sends/receives application data.
+ *
+ * To reuse an existing session:
+ *
+ * 1. Client sends ClientHello with session ID for reuse.
+ * 2. Client receives ServerHello with same session ID if reusing.
+ * 3. Client receives ChangeCipherSpec message if reusing.
+ * 4. Client receives Finished.
+ * 5. Client sends ChangeCipherSpec.
+ * 6. Client sends Finished.
+ *
+ * Note: Client ignores HelloRequest if in the middle of a handshake.
+ *
+ * Record Layer:
+ *
+ * The record layer fragments information blocks into TLSPlaintext records
+ * carrying data in chunks of 2^14 bytes or less. Client message boundaries are
+ * not preserved in the record layer (i.e., multiple client messages of the
+ * same ContentType MAY be coalesced into a single TLSPlaintext record, or a
+ * single message MAY be fragmented across several records).
+ *
+ * struct {
+ *   uint8 major;
+ *   uint8 minor;
+ * } ProtocolVersion;
+ *
+ * struct {
+ *   ContentType type;
+ *   ProtocolVersion version;
+ *   uint16 length;
+ *   opaque fragment[TLSPlaintext.length];
+ * } TLSPlaintext;
+ *
+ * type:
+ *   The higher-level protocol used to process the enclosed fragment.
+ *
+ * version:
+ *   The version of the protocol being employed. TLS Version 1.2 uses version
+ *   {3, 3}. TLS Version 1.0 uses version {3, 1}. Note that a client that
+ *   supports multiple versions of TLS may not know what version will be
+ *   employed before it receives the ServerHello.
+ *
+ * length:
+ *   The length (in bytes) of the following TLSPlaintext.fragment. The length
+ *   MUST NOT exceed 2^14 = 16384 bytes.
+ *
+ * fragment:
+ *   The application data. This data is transparent and treated as an
+ *   independent block to be dealt with by the higher-level protocol specified
+ *   by the type field.
+ *
+ * Implementations MUST NOT send zero-length fragments of Handshake, Alert, or
+ * ChangeCipherSpec content types. Zero-length fragments of Application data
+ * MAY be sent as they are potentially useful as a traffic analysis
+ * countermeasure.
+ *
+ * Note: Data of different TLS record layer content types MAY be interleaved.
+ * Application data is generally of lower precedence for transmission than
+ * other content types. However, records MUST be delivered to the network in
+ * the same order as they are protected by the record layer. Recipients MUST
+ * receive and process interleaved application layer traffic during handshakes
+ * subsequent to the first one on a connection.
+ *
+ * struct {
+ *   ContentType type;       // same as TLSPlaintext.type
+ *   ProtocolVersion version;// same as TLSPlaintext.version
+ *   uint16 length;
+ *   opaque fragment[TLSCompressed.length];
+ * } TLSCompressed;
+ *
+ * length:
+ *   The length (in bytes) of the following TLSCompressed.fragment.
+ *   The length MUST NOT exceed 2^14 + 1024.
+ *
+ * fragment:
+ *   The compressed form of TLSPlaintext.fragment.
+ *
+ * Note: A CompressionMethod.null operation is an identity operation; no fields
+ * are altered. In this implementation, since no compression is supported,
+ * uncompressed records are always the same as compressed records.
+ *
+ * Encryption Information:
+ *
+ * The encryption and MAC functions translate a TLSCompressed structure into a
+ * TLSCiphertext. The decryption functions reverse the process. The MAC of the
+ * record also includes a sequence number so that missing, extra, or repeated
+ * messages are detectable.
+ *
+ * struct {
+ *   ContentType type;
+ *   ProtocolVersion version;
+ *   uint16 length;
+ *   select (SecurityParameters.cipher_type) {
+ *     case stream: GenericStreamCipher;
+ *     case block:  GenericBlockCipher;
+ *     case aead:   GenericAEADCipher;
+ *   } fragment;
+ * } TLSCiphertext;
+ *
+ * type:
+ *   The type field is identical to TLSCompressed.type.
+ *
+ * version:
+ *   The version field is identical to TLSCompressed.version.
+ *
+ * length:
+ *   The length (in bytes) of the following TLSCiphertext.fragment.
+ *   The length MUST NOT exceed 2^14 + 2048.
+ *
+ * fragment:
+ *   The encrypted form of TLSCompressed.fragment, with the MAC.
+ *
+ * Note: Only CBC Block Ciphers are supported by this implementation.
+ *
+ * The TLSCompressed.fragment structures are converted to/from block
+ * TLSCiphertext.fragment structures.
+ *
+ * struct {
+ *   opaque IV[SecurityParameters.record_iv_length];
+ *   block-ciphered struct {
+ *     opaque content[TLSCompressed.length];
+ *     opaque MAC[SecurityParameters.mac_length];
+ *     uint8 padding[GenericBlockCipher.padding_length];
+ *     uint8 padding_length;
+ *   };
+ * } GenericBlockCipher;
+ *
+ * The MAC is generated as described in Section 6.2.3.1.
+ *
+ * IV:
+ *   The Initialization Vector (IV) SHOULD be chosen at random, and MUST be
+ *   unpredictable. Note that in versions of TLS prior to 1.1, there was no
+ *   IV field, and the last ciphertext block of the previous record (the "CBC
+ *   residue") was used as the IV. This was changed to prevent the attacks
+ *   described in [CBCATT]. For block ciphers, the IV length is of length
+ *   SecurityParameters.record_iv_length, which is equal to the
+ *   SecurityParameters.block_size.
+ *
+ * padding:
+ *   Padding that is added to force the length of the plaintext to be an
+ *   integral multiple of the block cipher's block length. The padding MAY be
+ *   any length up to 255 bytes, as long as it results in the
+ *   TLSCiphertext.length being an integral multiple of the block length.
+ *   Lengths longer than necessary might be desirable to frustrate attacks on
+ *   a protocol that are based on analysis of the lengths of exchanged
+ *   messages. Each uint8 in the padding data vector MUST be filled with the
+ *   padding length value. The receiver MUST check this padding and MUST use
+ *   the bad_record_mac alert to indicate padding errors.
+ *
+ * padding_length:
+ *   The padding length MUST be such that the total size of the
+ *   GenericBlockCipher structure is a multiple of the cipher's block length.
+ *   Legal values range from zero to 255, inclusive. This length specifies the
+ *   length of the padding field exclusive of the padding_length field itself.
+ *
+ * The encrypted data length (TLSCiphertext.length) is one more than the sum of
+ * SecurityParameters.block_length, TLSCompressed.length,
+ * SecurityParameters.mac_length, and padding_length.
+ *
+ * Example: If the block length is 8 bytes, the content length
+ * (TLSCompressed.length) is 61 bytes, and the MAC length is 20 bytes, then the
+ * length before padding is 82 bytes (this does not include the IV. Thus, the
+ * padding length modulo 8 must be equal to 6 in order to make the total length
+ * an even multiple of 8 bytes (the block length). The padding length can be
+ * 6, 14, 22, and so on, through 254. If the padding length were the minimum
+ * necessary, 6, the padding would be 6 bytes, each containing the value 6.
+ * Thus, the last 8 octets of the GenericBlockCipher before block encryption
+ * would be xx 06 06 06 06 06 06 06, where xx is the last octet of the MAC.
+ *
+ * Note: With block ciphers in CBC mode (Cipher Block Chaining), it is critical
+ * that the entire plaintext of the record be known before any ciphertext is
+ * transmitted. Otherwise, it is possible for the attacker to mount the attack
+ * described in [CBCATT].
+ *
+ * Implementation note: Canvel et al. [CBCTIME] have demonstrated a timing
+ * attack on CBC padding based on the time required to compute the MAC. In
+ * order to defend against this attack, implementations MUST ensure that
+ * record processing time is essentially the same whether or not the padding
+ * is correct. In general, the best way to do this is to compute the MAC even
+ * if the padding is incorrect, and only then reject the packet. For instance,
+ * if the pad appears to be incorrect, the implementation might assume a
+ * zero-length pad and then compute the MAC. This leaves a small timing
+ * channel, since MAC performance depends, to some extent, on the size of the
+ * data fragment, but it is not believed to be large enough to be exploitable,
+ * due to the large block size of existing MACs and the small size of the
+ * timing signal.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+/**
+ * Generates pseudo random bytes by mixing the result of two hash functions,
+ * MD5 and SHA-1.
+ *
+ * prf_TLS1(secret, label, seed) =
+ *   P_MD5(S1, label + seed) XOR P_SHA-1(S2, label + seed);
+ *
+ * Each P_hash function functions as follows:
+ *
+ * P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) +
+ *                        HMAC_hash(secret, A(2) + seed) +
+ *                        HMAC_hash(secret, A(3) + seed) + ...
+ * A() is defined as:
+ *   A(0) = seed
+ *   A(i) = HMAC_hash(secret, A(i-1))
+ *
+ * The '+' operator denotes concatenation.
+ *
+ * As many iterations A(N) as are needed are performed to generate enough
+ * pseudo random byte output. If an iteration creates more data than is
+ * necessary, then it is truncated.
+ *
+ * Therefore:
+ * A(1) = HMAC_hash(secret, A(0))
+ *      = HMAC_hash(secret, seed)
+ * A(2) = HMAC_hash(secret, A(1))
+ *      = HMAC_hash(secret, HMAC_hash(secret, seed))
+ *
+ * Therefore:
+ * P_hash(secret, seed) =
+ *   HMAC_hash(secret, HMAC_hash(secret, A(0)) + seed) +
+ *   HMAC_hash(secret, HMAC_hash(secret, A(1)) + seed) +
+ *   ...
+ *
+ * Therefore:
+ * P_hash(secret, seed) =
+ *   HMAC_hash(secret, HMAC_hash(secret, seed) + seed) +
+ *   HMAC_hash(secret, HMAC_hash(secret, HMAC_hash(secret, seed)) + seed) +
+ *   ...
+ *
+ * @param secret the secret to use.
+ * @param label the label to use.
+ * @param seed the seed value to use.
+ * @param length the number of bytes to generate.
+ *
+ * @return the pseudo random bytes in a byte buffer.
+ */
+var prf_TLS1 = function(secret, label, seed, length) {
+  var rval = forge.util.createBuffer();
+
+  /* For TLS 1.0, the secret is split in half, into two secrets of equal
+    length. If the secret has an odd length then the last byte of the first
+    half will be the same as the first byte of the second. The length of the
+    two secrets is half of the secret rounded up. */
+  var idx = (secret.length >> 1);
+  var slen = idx + (secret.length & 1);
+  var s1 = secret.substr(0, slen);
+  var s2 = secret.substr(idx, slen);
+  var ai = forge.util.createBuffer();
+  var hmac = forge.hmac.create();
+  seed = label + seed;
+
+  // determine the number of iterations that must be performed to generate
+  // enough output bytes, md5 creates 16 byte hashes, sha1 creates 20
+  var md5itr = Math.ceil(length / 16);
+  var sha1itr = Math.ceil(length / 20);
+
+  // do md5 iterations
+  hmac.start('MD5', s1);
+  var md5bytes = forge.util.createBuffer();
+  ai.putBytes(seed);
+  for(var i = 0; i < md5itr; ++i) {
+    // HMAC_hash(secret, A(i-1))
+    hmac.start(null, null);
+    hmac.update(ai.getBytes());
+    ai.putBuffer(hmac.digest());
+
+    // HMAC_hash(secret, A(i) + seed)
+    hmac.start(null, null);
+    hmac.update(ai.bytes() + seed);
+    md5bytes.putBuffer(hmac.digest());
+  }
+
+  // do sha1 iterations
+  hmac.start('SHA1', s2);
+  var sha1bytes = forge.util.createBuffer();
+  ai.clear();
+  ai.putBytes(seed);
+  for(var i = 0; i < sha1itr; ++i) {
+    // HMAC_hash(secret, A(i-1))
+    hmac.start(null, null);
+    hmac.update(ai.getBytes());
+    ai.putBuffer(hmac.digest());
+
+    // HMAC_hash(secret, A(i) + seed)
+    hmac.start(null, null);
+    hmac.update(ai.bytes() + seed);
+    sha1bytes.putBuffer(hmac.digest());
+  }
+
+  // XOR the md5 bytes with the sha1 bytes
+  rval.putBytes(forge.util.xorBytes(
+    md5bytes.getBytes(), sha1bytes.getBytes(), length));
+
+  return rval;
+};
+
+/**
+ * Generates pseudo random bytes using a SHA256 algorithm. For TLS 1.2.
+ *
+ * @param secret the secret to use.
+ * @param label the label to use.
+ * @param seed the seed value to use.
+ * @param length the number of bytes to generate.
+ *
+ * @return the pseudo random bytes in a byte buffer.
+ */
+var prf_sha256 = function(secret, label, seed, length) {
+   // FIXME: implement me for TLS 1.2
+};
+
+/**
+ * Gets a MAC for a record using the SHA-1 hash algorithm.
+ *
+ * @param key the mac key.
+ * @param state the sequence number (array of two 32-bit integers).
+ * @param record the record.
+ *
+ * @return the sha-1 hash (20 bytes) for the given record.
+ */
+var hmac_sha1 = function(key, seqNum, record) {
+  /* MAC is computed like so:
+  HMAC_hash(
+    key, seqNum +
+      TLSCompressed.type +
+      TLSCompressed.version +
+      TLSCompressed.length +
+      TLSCompressed.fragment)
+  */
+  var hmac = forge.hmac.create();
+  hmac.start('SHA1', key);
+  var b = forge.util.createBuffer();
+  b.putInt32(seqNum[0]);
+  b.putInt32(seqNum[1]);
+  b.putByte(record.type);
+  b.putByte(record.version.major);
+  b.putByte(record.version.minor);
+  b.putInt16(record.length);
+  b.putBytes(record.fragment.bytes());
+  hmac.update(b.getBytes());
+  return hmac.digest().getBytes();
+};
+
+/**
+ * Compresses the TLSPlaintext record into a TLSCompressed record using the
+ * deflate algorithm.
+ *
+ * @param c the TLS connection.
+ * @param record the TLSPlaintext record to compress.
+ * @param s the ConnectionState to use.
+ *
+ * @return true on success, false on failure.
+ */
+var deflate = function(c, record, s) {
+  var rval = false;
+
+  try {
+    var bytes = c.deflate(record.fragment.getBytes());
+    record.fragment = forge.util.createBuffer(bytes);
+    record.length = bytes.length;
+    rval = true;
+  } catch(ex) {
+    // deflate error, fail out
+  }
+
+  return rval;
+};
+
+/**
+ * Decompresses the TLSCompressed record into a TLSPlaintext record using the
+ * deflate algorithm.
+ *
+ * @param c the TLS connection.
+ * @param record the TLSCompressed record to decompress.
+ * @param s the ConnectionState to use.
+ *
+ * @return true on success, false on failure.
+ */
+var inflate = function(c, record, s) {
+  var rval = false;
+
+  try {
+    var bytes = c.inflate(record.fragment.getBytes());
+    record.fragment = forge.util.createBuffer(bytes);
+    record.length = bytes.length;
+    rval = true;
+  } catch(ex) {
+    // inflate error, fail out
+  }
+
+  return rval;
+};
+
+/**
+ * Reads a TLS variable-length vector from a byte buffer.
+ *
+ * Variable-length vectors are defined by specifying a subrange of legal
+ * lengths, inclusively, using the notation <floor..ceiling>. When these are
+ * encoded, the actual length precedes the vector's contents in the byte
+ * stream. The length will be in the form of a number consuming as many bytes
+ * as required to hold the vector's specified maximum (ceiling) length. A
+ * variable-length vector with an actual length field of zero is referred to
+ * as an empty vector.
+ *
+ * @param b the byte buffer.
+ * @param lenBytes the number of bytes required to store the length.
+ *
+ * @return the resulting byte buffer.
+ */
+var readVector = function(b, lenBytes) {
+  var len = 0;
+  switch(lenBytes) {
+  case 1:
+    len = b.getByte();
+    break;
+  case 2:
+    len = b.getInt16();
+    break;
+  case 3:
+    len = b.getInt24();
+    break;
+  case 4:
+    len = b.getInt32();
+    break;
+  }
+
+  // read vector bytes into a new buffer
+  return forge.util.createBuffer(b.getBytes(len));
+};
+
+/**
+ * Writes a TLS variable-length vector to a byte buffer.
+ *
+ * @param b the byte buffer.
+ * @param lenBytes the number of bytes required to store the length.
+ * @param v the byte buffer vector.
+ */
+var writeVector = function(b, lenBytes, v) {
+  // encode length at the start of the vector, where the number of bytes for
+  // the length is the maximum number of bytes it would take to encode the
+  // vector's ceiling
+  b.putInt(v.length(), lenBytes << 3);
+  b.putBuffer(v);
+};
+
+/**
+ * The tls implementation.
+ */
+var tls = {};
+
+/**
+ * Version: TLS 1.2 = 3.3, TLS 1.1 = 3.2, TLS 1.0 = 3.1. Both TLS 1.1 and
+ * TLS 1.2 were still too new (ie: openSSL didn't implement them) at the time
+ * of this implementation so TLS 1.0 was implemented instead.
+ */
+tls.Versions = {
+  TLS_1_0: {major: 3, minor: 1},
+  TLS_1_1: {major: 3, minor: 2},
+  TLS_1_2: {major: 3, minor: 3}
+};
+tls.SupportedVersions = [
+  tls.Versions.TLS_1_1,
+  tls.Versions.TLS_1_0
+];
+tls.Version = tls.SupportedVersions[0];
+
+/**
+ * Maximum fragment size. True maximum is 16384, but we fragment before that
+ * to allow for unusual small increases during compression.
+ */
+tls.MaxFragment = 16384 - 1024;
+
+/**
+ * Whether this entity is considered the "client" or "server".
+ * enum { server, client } ConnectionEnd;
+ */
+tls.ConnectionEnd = {
+  server: 0,
+  client: 1
+};
+
+/**
+ * Pseudo-random function algorithm used to generate keys from the master
+ * secret.
+ * enum { tls_prf_sha256 } PRFAlgorithm;
+ */
+tls.PRFAlgorithm = {
+  tls_prf_sha256: 0
+};
+
+/**
+ * Bulk encryption algorithms.
+ * enum { null, rc4, des3, aes } BulkCipherAlgorithm;
+ */
+tls.BulkCipherAlgorithm = {
+  none: null,
+  rc4: 0,
+  des3: 1,
+  aes: 2
+};
+
+/**
+ * Cipher types.
+ * enum { stream, block, aead } CipherType;
+ */
+tls.CipherType = {
+  stream: 0,
+  block: 1,
+  aead: 2
+};
+
+/**
+ * MAC (Message Authentication Code) algorithms.
+ * enum { null, hmac_md5, hmac_sha1, hmac_sha256,
+ *   hmac_sha384, hmac_sha512} MACAlgorithm;
+ */
+tls.MACAlgorithm = {
+  none: null,
+  hmac_md5: 0,
+  hmac_sha1: 1,
+  hmac_sha256: 2,
+  hmac_sha384: 3,
+  hmac_sha512: 4
+};
+
+/**
+ * Compression algorithms.
+ * enum { null(0), deflate(1), (255) } CompressionMethod;
+ */
+tls.CompressionMethod = {
+  none: 0,
+  deflate: 1
+};
+
+/**
+ * TLS record content types.
+ * enum {
+ *   change_cipher_spec(20), alert(21), handshake(22),
+ *   application_data(23), (255)
+ * } ContentType;
+ */
+tls.ContentType = {
+  change_cipher_spec: 20,
+  alert: 21,
+  handshake: 22,
+  application_data: 23,
+  heartbeat: 24
+};
+
+/**
+ * TLS handshake types.
+ * enum {
+ *   hello_request(0), client_hello(1), server_hello(2),
+ *   certificate(11), server_key_exchange (12),
+ *   certificate_request(13), server_hello_done(14),
+ *   certificate_verify(15), client_key_exchange(16),
+ *   finished(20), (255)
+ * } HandshakeType;
+ */
+tls.HandshakeType = {
+  hello_request: 0,
+  client_hello: 1,
+  server_hello: 2,
+  certificate: 11,
+  server_key_exchange: 12,
+  certificate_request: 13,
+  server_hello_done: 14,
+  certificate_verify: 15,
+  client_key_exchange: 16,
+  finished: 20
+};
+
+/**
+ * TLS Alert Protocol.
+ *
+ * enum { warning(1), fatal(2), (255) } AlertLevel;
+ *
+ * enum {
+ *   close_notify(0),
+ *   unexpected_message(10),
+ *   bad_record_mac(20),
+ *   decryption_failed(21),
+ *   record_overflow(22),
+ *   decompression_failure(30),
+ *   handshake_failure(40),
+ *   bad_certificate(42),
+ *   unsupported_certificate(43),
+ *   certificate_revoked(44),
+ *   certificate_expired(45),
+ *   certificate_unknown(46),
+ *   illegal_parameter(47),
+ *   unknown_ca(48),
+ *   access_denied(49),
+ *   decode_error(50),
+ *   decrypt_error(51),
+ *   export_restriction(60),
+ *   protocol_version(70),
+ *   insufficient_security(71),
+ *   internal_error(80),
+ *   user_canceled(90),
+ *   no_renegotiation(100),
+ *   (255)
+ * } AlertDescription;
+ *
+ * struct {
+ *   AlertLevel level;
+ *   AlertDescription description;
+ * } Alert;
+ */
+tls.Alert = {};
+tls.Alert.Level = {
+  warning: 1,
+  fatal: 2
+};
+tls.Alert.Description = {
+  close_notify: 0,
+  unexpected_message: 10,
+  bad_record_mac: 20,
+  decryption_failed: 21,
+  record_overflow: 22,
+  decompression_failure: 30,
+  handshake_failure: 40,
+  bad_certificate: 42,
+  unsupported_certificate: 43,
+  certificate_revoked: 44,
+  certificate_expired: 45,
+  certificate_unknown: 46,
+  illegal_parameter: 47,
+  unknown_ca: 48,
+  access_denied: 49,
+  decode_error: 50,
+  decrypt_error: 51,
+  export_restriction: 60,
+  protocol_version: 70,
+  insufficient_security: 71,
+  internal_error: 80,
+  user_canceled: 90,
+  no_renegotiation: 100
+};
+
+/**
+ * TLS Heartbeat Message types.
+ * enum {
+ *   heartbeat_request(1),
+ *   heartbeat_response(2),
+ *   (255)
+ * } HeartbeatMessageType;
+ */
+tls.HeartbeatMessageType = {
+  heartbeat_request: 1,
+  heartbeat_response: 2
+};
+
+/**
+ * Supported cipher suites.
+ */
+tls.CipherSuites = {};
+
+/**
+ * Gets a supported cipher suite from its 2 byte ID.
+ *
+ * @param twoBytes two bytes in a string.
+ *
+ * @return the matching supported cipher suite or null.
+ */
+tls.getCipherSuite = function(twoBytes) {
+  var rval = null;
+  for(var key in tls.CipherSuites) {
+    var cs = tls.CipherSuites[key];
+    if(cs.id[0] === twoBytes.charCodeAt(0) &&
+      cs.id[1] === twoBytes.charCodeAt(1)) {
+      rval = cs;
+      break;
+    }
+  }
+  return rval;
+};
+
+/**
+ * Called when an unexpected record is encountered.
+ *
+ * @param c the connection.
+ * @param record the record.
+ */
+tls.handleUnexpected = function(c, record) {
+  // if connection is client and closed, ignore unexpected messages
+  var ignore = (!c.open && c.entity === tls.ConnectionEnd.client);
+  if(!ignore) {
+    c.error(c, {
+      message: 'Unexpected message. Received TLS record out of order.',
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.unexpected_message
+      }
+    });
+  }
+};
+
+/**
+ * Called when a client receives a HelloRequest record.
+ *
+ * @param c the connection.
+ * @param record the record.
+ * @param length the length of the handshake message.
+ */
+tls.handleHelloRequest = function(c, record, length) {
+  // ignore renegotiation requests from the server during a handshake, but
+  // if handshaking, send a warning alert that renegotation is denied
+  if(!c.handshaking && c.handshakes > 0) {
+    // send alert warning
+    tls.queue(c, tls.createAlert(c, {
+       level: tls.Alert.Level.warning,
+       description: tls.Alert.Description.no_renegotiation
+    }));
+    tls.flush(c);
+  }
+
+  // continue
+  c.process();
+};
+
+/**
+ * Parses a hello message from a ClientHello or ServerHello record.
+ *
+ * @param record the record to parse.
+ *
+ * @return the parsed message.
+ */
+tls.parseHelloMessage = function(c, record, length) {
+  var msg = null;
+
+  var client = (c.entity === tls.ConnectionEnd.client);
+
+  // minimum of 38 bytes in message
+  if(length < 38) {
+    c.error(c, {
+      message: client ?
+        'Invalid ServerHello message. Message too short.' :
+        'Invalid ClientHello message. Message too short.',
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.illegal_parameter
+      }
+    });
+  } else {
+    // use 'remaining' to calculate # of remaining bytes in the message
+    var b = record.fragment;
+    var remaining = b.length();
+    msg = {
+      version: {
+        major: b.getByte(),
+        minor: b.getByte()
+      },
+      random: forge.util.createBuffer(b.getBytes(32)),
+      session_id: readVector(b, 1),
+      extensions: []
+    };
+    if(client) {
+      msg.cipher_suite = b.getBytes(2);
+      msg.compression_method = b.getByte();
+    } else {
+      msg.cipher_suites = readVector(b, 2);
+      msg.compression_methods = readVector(b, 1);
+    }
+
+    // read extensions if there are any bytes left in the message
+    remaining = length - (remaining - b.length());
+    if(remaining > 0) {
+      // parse extensions
+      var exts = readVector(b, 2);
+      while(exts.length() > 0) {
+        msg.extensions.push({
+          type: [exts.getByte(), exts.getByte()],
+          data: readVector(exts, 2)
+        });
+      }
+
+      // TODO: make extension support modular
+      if(!client) {
+        for(var i = 0; i < msg.extensions.length; ++i) {
+          var ext = msg.extensions[i];
+
+          // support SNI extension
+          if(ext.type[0] === 0x00 && ext.type[1] === 0x00) {
+            // get server name list
+            var snl = readVector(ext.data, 2);
+            while(snl.length() > 0) {
+              // read server name type
+              var snType = snl.getByte();
+
+              // only HostName type (0x00) is known, break out if
+              // another type is detected
+              if(snType !== 0x00) {
+                break;
+              }
+
+              // add host name to server name list
+              c.session.extensions.server_name.serverNameList.push(
+                readVector(snl, 2).getBytes());
+            }
+          }
+        }
+      }
+    }
+
+    // version already set, do not allow version change
+    if(c.session.version) {
+      if(msg.version.major !== c.session.version.major ||
+        msg.version.minor !== c.session.version.minor) {
+        return c.error(c, {
+          message: 'TLS version change is disallowed during renegotiation.',
+          send: true,
+          alert: {
+            level: tls.Alert.Level.fatal,
+            description: tls.Alert.Description.protocol_version
+          }
+        });
+      }
+    }
+
+    // get the chosen (ServerHello) cipher suite
+    if(client) {
+      // FIXME: should be checking configured acceptable cipher suites
+      c.session.cipherSuite = tls.getCipherSuite(msg.cipher_suite);
+    } else {
+      // get a supported preferred (ClientHello) cipher suite
+      // choose the first supported cipher suite
+      var tmp = forge.util.createBuffer(msg.cipher_suites.bytes());
+      while(tmp.length() > 0) {
+        // FIXME: should be checking configured acceptable suites
+        // cipher suites take up 2 bytes
+        c.session.cipherSuite = tls.getCipherSuite(tmp.getBytes(2));
+        if(c.session.cipherSuite !== null) {
+          break;
+        }
+      }
+    }
+
+    // cipher suite not supported
+    if(c.session.cipherSuite === null) {
+      return c.error(c, {
+        message: 'No cipher suites in common.',
+        send: true,
+        alert: {
+          level: tls.Alert.Level.fatal,
+          description: tls.Alert.Description.handshake_failure
+        },
+        cipherSuite: forge.util.bytesToHex(msg.cipher_suite)
+      });
+    }
+
+    // TODO: handle compression methods
+    if(client) {
+      c.session.compressionMethod = msg.compression_method;
+    } else {
+      // no compression
+      c.session.compressionMethod = tls.CompressionMethod.none;
+    }
+  }
+
+  return msg;
+};
+
+/**
+ * Creates security parameters for the given connection based on the given
+ * hello message.
+ *
+ * @param c the TLS connection.
+ * @param msg the hello message.
+ */
+tls.createSecurityParameters = function(c, msg) {
+  /* Note: security params are from TLS 1.2, some values like prf_algorithm
+  are ignored for TLS 1.0/1.1 and the builtin as specified in the spec is
+  used. */
+
+  // TODO: handle other options from server when more supported
+
+  // get client and server randoms
+  var client = (c.entity === tls.ConnectionEnd.client);
+  var msgRandom = msg.random.bytes();
+  var cRandom = client ? c.session.sp.client_random : msgRandom;
+  var sRandom = client ? msgRandom : tls.createRandom().getBytes();
+
+  // create new security parameters
+  c.session.sp = {
+    entity: c.entity,
+    prf_algorithm: tls.PRFAlgorithm.tls_prf_sha256,
+    bulk_cipher_algorithm: null,
+    cipher_type: null,
+    enc_key_length: null,
+    block_length: null,
+    fixed_iv_length: null,
+    record_iv_length: null,
+    mac_algorithm: null,
+    mac_length: null,
+    mac_key_length: null,
+    compression_algorithm: c.session.compressionMethod,
+    pre_master_secret: null,
+    master_secret: null,
+    client_random: cRandom,
+    server_random: sRandom
+  };
+};
+
+/**
+ * Called when a client receives a ServerHello record.
+ *
+ * When a ServerHello message will be sent:
+ *   The server will send this message in response to a client hello message
+ *   when it was able to find an acceptable set of algorithms. If it cannot
+ *   find such a match, it will respond with a handshake failure alert.
+ *
+ * uint24 length;
+ * struct {
+ *   ProtocolVersion server_version;
+ *   Random random;
+ *   SessionID session_id;
+ *   CipherSuite cipher_suite;
+ *   CompressionMethod compression_method;
+ *   select(extensions_present) {
+ *     case false:
+ *       struct {};
+ *     case true:
+ *       Extension extensions<0..2^16-1>;
+ *   };
+ * } ServerHello;
+ *
+ * @param c the connection.
+ * @param record the record.
+ * @param length the length of the handshake message.
+ */
+tls.handleServerHello = function(c, record, length) {
+  var msg = tls.parseHelloMessage(c, record, length);
+  if(c.fail) {
+    return;
+  }
+
+  // ensure server version is compatible
+  if(msg.version.minor <= c.version.minor) {
+    c.version.minor = msg.version.minor;
+  } else {
+    return c.error(c, {
+      message: 'Incompatible TLS version.',
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.protocol_version
+      }
+    });
+  }
+
+  // indicate session version has been set
+  c.session.version = c.version;
+
+  // get the session ID from the message
+  var sessionId = msg.session_id.bytes();
+
+  // if the session ID is not blank and matches the cached one, resume
+  // the session
+  if(sessionId.length > 0 && sessionId === c.session.id) {
+    // resuming session, expect a ChangeCipherSpec next
+    c.expect = SCC;
+    c.session.resuming = true;
+
+    // get new server random
+    c.session.sp.server_random = msg.random.bytes();
+  } else {
+    // not resuming, expect a server Certificate message next
+    c.expect = SCE;
+    c.session.resuming = false;
+
+    // create new security parameters
+    tls.createSecurityParameters(c, msg);
+  }
+
+  // set new session ID
+  c.session.id = sessionId;
+
+  // continue
+  c.process();
+};
+
+/**
+ * Called when a server receives a ClientHello record.
+ *
+ * When a ClientHello message will be sent:
+ *   When a client first connects to a server it is required to send the
+ *   client hello as its first message. The client can also send a client
+ *   hello in response to a hello request or on its own initiative in order
+ *   to renegotiate the security parameters in an existing connection.
+ *
+ * @param c the connection.
+ * @param record the record.
+ * @param length the length of the handshake message.
+ */
+tls.handleClientHello = function(c, record, length) {
+  var msg = tls.parseHelloMessage(c, record, length);
+  if(c.fail) {
+    return;
+  }
+
+  // get the session ID from the message
+  var sessionId = msg.session_id.bytes();
+
+  // see if the given session ID is in the cache
+  var session = null;
+  if(c.sessionCache) {
+    session = c.sessionCache.getSession(sessionId);
+    if(session === null) {
+      // session ID not found
+      sessionId = '';
+    } else if(session.version.major !== msg.version.major ||
+      session.version.minor > msg.version.minor) {
+      // if session version is incompatible with client version, do not resume
+      session = null;
+      sessionId = '';
+    }
+  }
+
+  // no session found to resume, generate a new session ID
+  if(sessionId.length === 0) {
+    sessionId = forge.random.getBytes(32);
+  }
+
+  // update session
+  c.session.id = sessionId;
+  c.session.clientHelloVersion = msg.version;
+  c.session.sp = {};
+  if(session) {
+    // use version and security parameters from resumed session
+    c.version = c.session.version = session.version;
+    c.session.sp = session.sp;
+  } else {
+    // use highest compatible minor version
+    var version;
+    for(var i = 1; i < tls.SupportedVersions.length; ++i) {
+      version = tls.SupportedVersions[i];
+      if(version.minor <= msg.version.minor) {
+        break;
+      }
+    }
+    c.version = {major: version.major, minor: version.minor};
+    c.session.version = c.version;
+  }
+
+  // if a session is set, resume it
+  if(session !== null) {
+    // resuming session, expect a ChangeCipherSpec next
+    c.expect = CCC;
+    c.session.resuming = true;
+
+    // get new client random
+    c.session.sp.client_random = msg.random.bytes();
+  } else {
+    // not resuming, expect a Certificate or ClientKeyExchange
+    c.expect = (c.verifyClient !== false) ? CCE : CKE;
+    c.session.resuming = false;
+
+    // create new security parameters
+    tls.createSecurityParameters(c, msg);
+  }
+
+  // connection now open
+  c.open = true;
+
+  // queue server hello
+  tls.queue(c, tls.createRecord(c, {
+    type: tls.ContentType.handshake,
+    data: tls.createServerHello(c)
+  }));
+
+  if(c.session.resuming) {
+    // queue change cipher spec message
+    tls.queue(c, tls.createRecord(c, {
+      type: tls.ContentType.change_cipher_spec,
+      data: tls.createChangeCipherSpec()
+    }));
+
+    // create pending state
+    c.state.pending = tls.createConnectionState(c);
+
+    // change current write state to pending write state
+    c.state.current.write = c.state.pending.write;
+
+    // queue finished
+    tls.queue(c, tls.createRecord(c, {
+      type: tls.ContentType.handshake,
+      data: tls.createFinished(c)
+    }));
+  } else {
+    // queue server certificate
+    tls.queue(c, tls.createRecord(c, {
+      type: tls.ContentType.handshake,
+      data: tls.createCertificate(c)
+    }));
+
+    if(!c.fail) {
+      // queue server key exchange
+      tls.queue(c, tls.createRecord(c, {
+        type: tls.ContentType.handshake,
+        data: tls.createServerKeyExchange(c)
+      }));
+
+      // request client certificate if set
+      if(c.verifyClient !== false) {
+        // queue certificate request
+        tls.queue(c, tls.createRecord(c, {
+          type: tls.ContentType.handshake,
+          data: tls.createCertificateRequest(c)
+        }));
+      }
+
+      // queue server hello done
+      tls.queue(c, tls.createRecord(c, {
+        type: tls.ContentType.handshake,
+        data: tls.createServerHelloDone(c)
+      }));
+    }
+  }
+
+  // send records
+  tls.flush(c);
+
+  // continue
+  c.process();
+};
+
+/**
+ * Called when a client receives a Certificate record.
+ *
+ * When this message will be sent:
+ *   The server must send a certificate whenever the agreed-upon key exchange
+ *   method is not an anonymous one. This message will always immediately
+ *   follow the server hello message.
+ *
+ * Meaning of this message:
+ *   The certificate type must be appropriate for the selected cipher suite's
+ *   key exchange algorithm, and is generally an X.509v3 certificate. It must
+ *   contain a key which matches the key exchange method, as follows. Unless
+ *   otherwise specified, the signing algorithm for the certificate must be
+ *   the same as the algorithm for the certificate key. Unless otherwise
+ *   specified, the public key may be of any length.
+ *
+ * opaque ASN.1Cert<1..2^24-1>;
+ * struct {
+ *   ASN.1Cert certificate_list<1..2^24-1>;
+ * } Certificate;
+ *
+ * @param c the connection.
+ * @param record the record.
+ * @param length the length of the handshake message.
+ */
+tls.handleCertificate = function(c, record, length) {
+  // minimum of 3 bytes in message
+  if(length < 3) {
+    return c.error(c, {
+      message: 'Invalid Certificate message. Message too short.',
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.illegal_parameter
+      }
+    });
+  }
+
+  var b = record.fragment;
+  var msg = {
+    certificate_list: readVector(b, 3)
+  };
+
+  /* The sender's certificate will be first in the list (chain), each
+    subsequent one that follows will certify the previous one, but root
+    certificates (self-signed) that specify the certificate authority may
+    be omitted under the assumption that clients must already possess it. */
+  var cert, asn1;
+  var certs = [];
+  try {
+    while(msg.certificate_list.length() > 0) {
+      // each entry in msg.certificate_list is a vector with 3 len bytes
+      cert = readVector(msg.certificate_list, 3);
+      asn1 = forge.asn1.fromDer(cert);
+      cert = forge.pki.certificateFromAsn1(asn1, true);
+      certs.push(cert);
+    }
+  } catch(ex) {
+    return c.error(c, {
+      message: 'Could not parse certificate list.',
+      cause: ex,
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.bad_certificate
+      }
+    });
+  }
+
+  // ensure at least 1 certificate was provided if in client-mode
+  // or if verifyClient was set to true to require a certificate
+  // (as opposed to 'optional')
+  var client = (c.entity === tls.ConnectionEnd.client);
+  if((client || c.verifyClient === true) && certs.length === 0) {
+    // error, no certificate
+    c.error(c, {
+      message: client ?
+        'No server certificate provided.' :
+        'No client certificate provided.',
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.illegal_parameter
+      }
+    });
+  } else if(certs.length === 0) {
+    // no certs to verify
+    // expect a ServerKeyExchange or ClientKeyExchange message next
+    c.expect = client ? SKE : CKE;
+  } else {
+    // save certificate in session
+    if(client) {
+      c.session.serverCertificate = certs[0];
+    } else {
+      c.session.clientCertificate = certs[0];
+    }
+
+    if(tls.verifyCertificateChain(c, certs)) {
+      // expect a ServerKeyExchange or ClientKeyExchange message next
+      c.expect = client ? SKE : CKE;
+    }
+  }
+
+  // continue
+  c.process();
+};
+
+/**
+ * Called when a client receives a ServerKeyExchange record.
+ *
+ * When this message will be sent:
+ *   This message will be sent immediately after the server certificate
+ *   message (or the server hello message, if this is an anonymous
+ *   negotiation).
+ *
+ *   The server key exchange message is sent by the server only when the
+ *   server certificate message (if sent) does not contain enough data to
+ *   allow the client to exchange a premaster secret.
+ *
+ * Meaning of this message:
+ *   This message conveys cryptographic information to allow the client to
+ *   communicate the premaster secret: either an RSA public key to encrypt
+ *   the premaster secret with, or a Diffie-Hellman public key with which the
+ *   client can complete a key exchange (with the result being the premaster
+ *   secret.)
+ *
+ * enum {
+ *   dhe_dss, dhe_rsa, dh_anon, rsa, dh_dss, dh_rsa
+ * } KeyExchangeAlgorithm;
+ *
+ * struct {
+ *   opaque dh_p<1..2^16-1>;
+ *   opaque dh_g<1..2^16-1>;
+ *   opaque dh_Ys<1..2^16-1>;
+ * } ServerDHParams;
+ *
+ * struct {
+ *   select(KeyExchangeAlgorithm) {
+ *     case dh_anon:
+ *       ServerDHParams params;
+ *     case dhe_dss:
+ *     case dhe_rsa:
+ *       ServerDHParams params;
+ *       digitally-signed struct {
+ *         opaque client_random[32];
+ *         opaque server_random[32];
+ *         ServerDHParams params;
+ *       } signed_params;
+ *     case rsa:
+ *     case dh_dss:
+ *     case dh_rsa:
+ *       struct {};
+ *   };
+ * } ServerKeyExchange;
+ *
+ * @param c the connection.
+ * @param record the record.
+ * @param length the length of the handshake message.
+ */
+tls.handleServerKeyExchange = function(c, record, length) {
+  // this implementation only supports RSA, no Diffie-Hellman support
+  // so any length > 0 is invalid
+  if(length > 0) {
+    return c.error(c, {
+      message: 'Invalid key parameters. Only RSA is supported.',
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.unsupported_certificate
+      }
+    });
+  }
+
+  // expect an optional CertificateRequest message next
+  c.expect = SCR;
+
+  // continue
+  c.process();
+};
+
+/**
+ * Called when a client receives a ClientKeyExchange record.
+ *
+ * @param c the connection.
+ * @param record the record.
+ * @param length the length of the handshake message.
+ */
+tls.handleClientKeyExchange = function(c, record, length) {
+  // this implementation only supports RSA, no Diffie-Hellman support
+  // so any length < 48 is invalid
+  if(length < 48) {
+    return c.error(c, {
+      message: 'Invalid key parameters. Only RSA is supported.',
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.unsupported_certificate
+      }
+    });
+  }
+
+  var b = record.fragment;
+  var msg = {
+    enc_pre_master_secret: readVector(b, 2).getBytes()
+  };
+
+  // do rsa decryption
+  var privateKey = null;
+  if(c.getPrivateKey) {
+    try {
+      privateKey = c.getPrivateKey(c, c.session.serverCertificate);
+      privateKey = forge.pki.privateKeyFromPem(privateKey);
+    } catch(ex) {
+      c.error(c, {
+        message: 'Could not get private key.',
+        cause: ex,
+        send: true,
+        alert: {
+          level: tls.Alert.Level.fatal,
+          description: tls.Alert.Description.internal_error
+        }
+      });
+    }
+  }
+
+  if(privateKey === null) {
+    return c.error(c, {
+      message: 'No private key set.',
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.internal_error
+      }
+    });
+  }
+
+  try {
+    // decrypt 48-byte pre-master secret
+    var sp = c.session.sp;
+    sp.pre_master_secret = privateKey.decrypt(msg.enc_pre_master_secret);
+
+    // ensure client hello version matches first 2 bytes
+    var version = c.session.clientHelloVersion;
+    if(version.major !== sp.pre_master_secret.charCodeAt(0) ||
+      version.minor !== sp.pre_master_secret.charCodeAt(1)) {
+      // error, do not send alert (see BLEI attack below)
+      throw new Error('TLS version rollback attack detected.');
+    }
+  } catch(ex) {
+    /* Note: Daniel Bleichenbacher [BLEI] can be used to attack a
+      TLS server which is using PKCS#1 encoded RSA, so instead of
+      failing here, we generate 48 random bytes and use that as
+      the pre-master secret. */
+    sp.pre_master_secret = forge.random.getBytes(48);
+  }
+
+  // expect a CertificateVerify message if a Certificate was received that
+  // does not have fixed Diffie-Hellman params, otherwise expect
+  // ChangeCipherSpec
+  c.expect = CCC;
+  if(c.session.clientCertificate !== null) {
+    // only RSA support, so expect CertificateVerify
+    // TODO: support Diffie-Hellman
+    c.expect = CCV;
+  }
+
+  // continue
+  c.process();
+};
+
+/**
+ * Called when a client receives a CertificateRequest record.
+ *
+ * When this message will be sent:
+ *   A non-anonymous server can optionally request a certificate from the
+ *   client, if appropriate for the selected cipher suite. This message, if
+ *   sent, will immediately follow the Server Key Exchange message (if it is
+ *   sent; otherwise, the Server Certificate message).
+ *
+ * enum {
+ *   rsa_sign(1), dss_sign(2), rsa_fixed_dh(3), dss_fixed_dh(4),
+ *   rsa_ephemeral_dh_RESERVED(5), dss_ephemeral_dh_RESERVED(6),
+ *   fortezza_dms_RESERVED(20), (255)
+ * } ClientCertificateType;
+ *
+ * opaque DistinguishedName<1..2^16-1>;
+ *
+ * struct {
+ *   ClientCertificateType certificate_types<1..2^8-1>;
+ *   SignatureAndHashAlgorithm supported_signature_algorithms<2^16-1>;
+ *   DistinguishedName certificate_authorities<0..2^16-1>;
+ * } CertificateRequest;
+ *
+ * @param c the connection.
+ * @param record the record.
+ * @param length the length of the handshake message.
+ */
+tls.handleCertificateRequest = function(c, record, length) {
+  // minimum of 3 bytes in message
+  if(length < 3) {
+    return c.error(c, {
+      message: 'Invalid CertificateRequest. Message too short.',
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.illegal_parameter
+      }
+    });
+  }
+
+  // TODO: TLS 1.2+ has different format including
+  // SignatureAndHashAlgorithm after cert types
+  var b = record.fragment;
+  var msg = {
+    certificate_types: readVector(b, 1),
+    certificate_authorities: readVector(b, 2)
+  };
+
+  // save certificate request in session
+  c.session.certificateRequest = msg;
+
+  // expect a ServerHelloDone message next
+  c.expect = SHD;
+
+  // continue
+  c.process();
+};
+
+/**
+ * Called when a server receives a CertificateVerify record.
+ *
+ * @param c the connection.
+ * @param record the record.
+ * @param length the length of the handshake message.
+ */
+tls.handleCertificateVerify = function(c, record, length) {
+  if(length < 2) {
+    return c.error(c, {
+      message: 'Invalid CertificateVerify. Message too short.',
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.illegal_parameter
+      }
+    });
+  }
+
+  // rewind to get full bytes for message so it can be manually
+  // digested below (special case for CertificateVerify messages because
+  // they must be digested *after* handling as opposed to all others)
+  var b = record.fragment;
+  b.read -= 4;
+  var msgBytes = b.bytes();
+  b.read += 4;
+
+  var msg = {
+    signature: readVector(b, 2).getBytes()
+  };
+
+  // TODO: add support for DSA
+
+  // generate data to verify
+  var verify = forge.util.createBuffer();
+  verify.putBuffer(c.session.md5.digest());
+  verify.putBuffer(c.session.sha1.digest());
+  verify = verify.getBytes();
+
+  try {
+    var cert = c.session.clientCertificate;
+    /*b = forge.pki.rsa.decrypt(
+      msg.signature, cert.publicKey, true, verify.length);
+    if(b !== verify) {*/
+    if(!cert.publicKey.verify(verify, msg.signature, 'NONE')) {
+      throw new Error('CertificateVerify signature does not match.');
+    }
+
+    // digest message now that it has been handled
+    c.session.md5.update(msgBytes);
+    c.session.sha1.update(msgBytes);
+  } catch(ex) {
+    return c.error(c, {
+      message: 'Bad signature in CertificateVerify.',
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.handshake_failure
+      }
+    });
+  }
+
+  // expect ChangeCipherSpec
+  c.expect = CCC;
+
+  // continue
+  c.process();
+};
+
+/**
+ * Called when a client receives a ServerHelloDone record.
+ *
+ * When this message will be sent:
+ *   The server hello done message is sent by the server to indicate the end
+ *   of the server hello and associated messages. After sending this message
+ *   the server will wait for a client response.
+ *
+ * Meaning of this message:
+ *   This message means that the server is done sending messages to support
+ *   the key exchange, and the client can proceed with its phase of the key
+ *   exchange.
+ *
+ *   Upon receipt of the server hello done message the client should verify
+ *   that the server provided a valid certificate if required and check that
+ *   the server hello parameters are acceptable.
+ *
+ * struct {} ServerHelloDone;
+ *
+ * @param c the connection.
+ * @param record the record.
+ * @param length the length of the handshake message.
+ */
+tls.handleServerHelloDone = function(c, record, length) {
+  // len must be 0 bytes
+  if(length > 0) {
+    return c.error(c, {
+      message: 'Invalid ServerHelloDone message. Invalid length.',
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.record_overflow
+      }
+    });
+  }
+
+  if(c.serverCertificate === null) {
+    // no server certificate was provided
+    var error = {
+      message: 'No server certificate provided. Not enough security.',
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.insufficient_security
+      }
+    };
+
+    // call application callback
+    var depth = 0;
+    var ret = c.verify(c, error.alert.description, depth, []);
+    if(ret !== true) {
+      // check for custom alert info
+      if(ret || ret === 0) {
+        // set custom message and alert description
+        if(typeof ret === 'object' && !forge.util.isArray(ret)) {
+          if(ret.message) {
+            error.message = ret.message;
+          }
+          if(ret.alert) {
+            error.alert.description = ret.alert;
+          }
+        } else if(typeof ret === 'number') {
+          // set custom alert description
+          error.alert.description = ret;
+        }
+      }
+
+      // send error
+      return c.error(c, error);
+    }
+  }
+
+  // create client certificate message if requested
+  if(c.session.certificateRequest !== null) {
+    record = tls.createRecord(c, {
+      type: tls.ContentType.handshake,
+      data: tls.createCertificate(c)
+    });
+    tls.queue(c, record);
+  }
+
+  // create client key exchange message
+  record = tls.createRecord(c, {
+     type: tls.ContentType.handshake,
+     data: tls.createClientKeyExchange(c)
+  });
+  tls.queue(c, record);
+
+  // expect no messages until the following callback has been called
+  c.expect = SER;
+
+  // create callback to handle client signature (for client-certs)
+  var callback = function(c, signature) {
+    if(c.session.certificateRequest !== null &&
+      c.session.clientCertificate !== null) {
+      // create certificate verify message
+      tls.queue(c, tls.createRecord(c, {
+        type: tls.ContentType.handshake,
+        data: tls.createCertificateVerify(c, signature)
+      }));
+    }
+
+    // create change cipher spec message
+    tls.queue(c, tls.createRecord(c, {
+      type: tls.ContentType.change_cipher_spec,
+      data: tls.createChangeCipherSpec()
+    }));
+
+    // create pending state
+    c.state.pending = tls.createConnectionState(c);
+
+    // change current write state to pending write state
+    c.state.current.write = c.state.pending.write;
+
+    // create finished message
+    tls.queue(c, tls.createRecord(c, {
+      type: tls.ContentType.handshake,
+      data: tls.createFinished(c)
+    }));
+
+    // expect a server ChangeCipherSpec message next
+    c.expect = SCC;
+
+    // send records
+    tls.flush(c);
+
+    // continue
+    c.process();
+  };
+
+  // if there is no certificate request or no client certificate, do
+  // callback immediately
+  if(c.session.certificateRequest === null ||
+    c.session.clientCertificate === null) {
+    return callback(c, null);
+  }
+
+  // otherwise get the client signature
+  tls.getClientSignature(c, callback);
+};
+
+/**
+ * Called when a ChangeCipherSpec record is received.
+ *
+ * @param c the connection.
+ * @param record the record.
+ */
+tls.handleChangeCipherSpec = function(c, record) {
+  if(record.fragment.getByte() !== 0x01) {
+    return c.error(c, {
+      message: 'Invalid ChangeCipherSpec message received.',
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.illegal_parameter
+      }
+    });
+  }
+
+  // create pending state if:
+  // 1. Resuming session in client mode OR
+  // 2. NOT resuming session in server mode
+  var client = (c.entity === tls.ConnectionEnd.client);
+  if((c.session.resuming && client) || (!c.session.resuming && !client)) {
+    c.state.pending = tls.createConnectionState(c);
+  }
+
+  // change current read state to pending read state
+  c.state.current.read = c.state.pending.read;
+
+  // clear pending state if:
+  // 1. NOT resuming session in client mode OR
+  // 2. resuming a session in server mode
+  if((!c.session.resuming && client) || (c.session.resuming && !client)) {
+    c.state.pending = null;
+  }
+
+  // expect a Finished record next
+  c.expect = client ? SFI : CFI;
+
+  // continue
+  c.process();
+};
+
+/**
+ * Called when a Finished record is received.
+ *
+ * When this message will be sent:
+ *   A finished message is always sent immediately after a change
+ *   cipher spec message to verify that the key exchange and
+ *   authentication processes were successful. It is essential that a
+ *   change cipher spec message be received between the other
+ *   handshake messages and the Finished message.
+ *
+ * Meaning of this message:
+ *   The finished message is the first protected with the just-
+ *   negotiated algorithms, keys, and secrets. Recipients of finished
+ *   messages must verify that the contents are correct.  Once a side
+ *   has sent its Finished message and received and validated the
+ *   Finished message from its peer, it may begin to send and receive
+ *   application data over the connection.
+ *
+ * struct {
+ *   opaque verify_data[verify_data_length];
+ * } Finished;
+ *
+ * verify_data
+ *   PRF(master_secret, finished_label, Hash(handshake_messages))
+ *     [0..verify_data_length-1];
+ *
+ * finished_label
+ *   For Finished messages sent by the client, the string
+ *   "client finished". For Finished messages sent by the server, the
+ *   string "server finished".
+ *
+ * verify_data_length depends on the cipher suite. If it is not specified
+ * by the cipher suite, then it is 12. Versions of TLS < 1.2 always used
+ * 12 bytes.
+ *
+ * @param c the connection.
+ * @param record the record.
+ * @param length the length of the handshake message.
+ */
+tls.handleFinished = function(c, record, length) {
+  // rewind to get full bytes for message so it can be manually
+  // digested below (special case for Finished messages because they
+  // must be digested *after* handling as opposed to all others)
+  var b = record.fragment;
+  b.read -= 4;
+  var msgBytes = b.bytes();
+  b.read += 4;
+
+  // message contains only verify_data
+  var vd = record.fragment.getBytes();
+
+  // ensure verify data is correct
+  b = forge.util.createBuffer();
+  b.putBuffer(c.session.md5.digest());
+  b.putBuffer(c.session.sha1.digest());
+
+  // set label based on entity type
+  var client = (c.entity === tls.ConnectionEnd.client);
+  var label = client ? 'server finished' : 'client finished';
+
+  // TODO: determine prf function and verify length for TLS 1.2
+  var sp = c.session.sp;
+  var vdl = 12;
+  var prf = prf_TLS1;
+  b = prf(sp.master_secret, label, b.getBytes(), vdl);
+  if(b.getBytes() !== vd) {
+    return c.error(c, {
+      message: 'Invalid verify_data in Finished message.',
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.decrypt_error
+      }
+    });
+  }
+
+  // digest finished message now that it has been handled
+  c.session.md5.update(msgBytes);
+  c.session.sha1.update(msgBytes);
+
+  // resuming session as client or NOT resuming session as server
+  if((c.session.resuming && client) || (!c.session.resuming && !client)) {
+    // create change cipher spec message
+    tls.queue(c, tls.createRecord(c, {
+      type: tls.ContentType.change_cipher_spec,
+      data: tls.createChangeCipherSpec()
+    }));
+
+    // change current write state to pending write state, clear pending
+    c.state.current.write = c.state.pending.write;
+    c.state.pending = null;
+
+    // create finished message
+    tls.queue(c, tls.createRecord(c, {
+      type: tls.ContentType.handshake,
+      data: tls.createFinished(c)
+    }));
+  }
+
+  // expect application data next
+  c.expect = client ? SAD : CAD;
+
+  // handshake complete
+  c.handshaking = false;
+  ++c.handshakes;
+
+  // save access to peer certificate
+  c.peerCertificate = client ?
+    c.session.serverCertificate : c.session.clientCertificate;
+
+  // send records
+  tls.flush(c);
+
+  // now connected
+  c.isConnected = true;
+  c.connected(c);
+
+  // continue
+  c.process();
+};
+
+/**
+ * Called when an Alert record is received.
+ *
+ * @param c the connection.
+ * @param record the record.
+ */
+tls.handleAlert = function(c, record) {
+  // read alert
+  var b = record.fragment;
+  var alert = {
+    level: b.getByte(),
+    description: b.getByte()
+  };
+
+  // TODO: consider using a table?
+  // get appropriate message
+  var msg;
+  switch(alert.description) {
+  case tls.Alert.Description.close_notify:
+    msg = 'Connection closed.';
+    break;
+  case tls.Alert.Description.unexpected_message:
+    msg = 'Unexpected message.';
+    break;
+  case tls.Alert.Description.bad_record_mac:
+    msg = 'Bad record MAC.';
+    break;
+  case tls.Alert.Description.decryption_failed:
+    msg = 'Decryption failed.';
+    break;
+  case tls.Alert.Description.record_overflow:
+    msg = 'Record overflow.';
+    break;
+  case tls.Alert.Description.decompression_failure:
+    msg = 'Decompression failed.';
+    break;
+  case tls.Alert.Description.handshake_failure:
+    msg = 'Handshake failure.';
+    break;
+  case tls.Alert.Description.bad_certificate:
+    msg = 'Bad certificate.';
+    break;
+  case tls.Alert.Description.unsupported_certificate:
+    msg = 'Unsupported certificate.';
+    break;
+  case tls.Alert.Description.certificate_revoked:
+    msg = 'Certificate revoked.';
+    break;
+  case tls.Alert.Description.certificate_expired:
+    msg = 'Certificate expired.';
+    break;
+  case tls.Alert.Description.certificate_unknown:
+    msg = 'Certificate unknown.';
+    break;
+  case tls.Alert.Description.illegal_parameter:
+    msg = 'Illegal parameter.';
+    break;
+  case tls.Alert.Description.unknown_ca:
+    msg = 'Unknown certificate authority.';
+    break;
+  case tls.Alert.Description.access_denied:
+    msg = 'Access denied.';
+    break;
+  case tls.Alert.Description.decode_error:
+    msg = 'Decode error.';
+    break;
+  case tls.Alert.Description.decrypt_error:
+    msg = 'Decrypt error.';
+    break;
+  case tls.Alert.Description.export_restriction:
+    msg = 'Export restriction.';
+    break;
+  case tls.Alert.Description.protocol_version:
+    msg = 'Unsupported protocol version.';
+    break;
+  case tls.Alert.Description.insufficient_security:
+    msg = 'Insufficient security.';
+    break;
+  case tls.Alert.Description.internal_error:
+    msg = 'Internal error.';
+    break;
+  case tls.Alert.Description.user_canceled:
+    msg = 'User canceled.';
+    break;
+  case tls.Alert.Description.no_renegotiation:
+    msg = 'Renegotiation not supported.';
+    break;
+  default:
+    msg = 'Unknown error.';
+    break;
+  }
+
+  // close connection on close_notify, not an error
+  if(alert.description === tls.Alert.Description.close_notify) {
+    return c.close();
+  }
+
+  // call error handler
+  c.error(c, {
+    message: msg,
+    send: false,
+    // origin is the opposite end
+    origin: (c.entity === tls.ConnectionEnd.client) ? 'server' : 'client',
+    alert: alert
+  });
+
+  // continue
+  c.process();
+};
+
+/**
+ * Called when a Handshake record is received.
+ *
+ * @param c the connection.
+ * @param record the record.
+ */
+tls.handleHandshake = function(c, record) {
+  // get the handshake type and message length
+  var b = record.fragment;
+  var type = b.getByte();
+  var length = b.getInt24();
+
+  // see if the record fragment doesn't yet contain the full message
+  if(length > b.length()) {
+    // cache the record, clear its fragment, and reset the buffer read
+    // pointer before the type and length were read
+    c.fragmented = record;
+    record.fragment = forge.util.createBuffer();
+    b.read -= 4;
+
+    // continue
+    return c.process();
+  }
+
+  // full message now available, clear cache, reset read pointer to
+  // before type and length
+  c.fragmented = null;
+  b.read -= 4;
+
+  // save the handshake bytes for digestion after handler is found
+  // (include type and length of handshake msg)
+  var bytes = b.bytes(length + 4);
+
+  // restore read pointer
+  b.read += 4;
+
+  // handle expected message
+  if(type in hsTable[c.entity][c.expect]) {
+    // initialize server session
+    if(c.entity === tls.ConnectionEnd.server && !c.open && !c.fail) {
+      c.handshaking = true;
+      c.session = {
+        version: null,
+        extensions: {
+          server_name: {
+            serverNameList: []
+          }
+        },
+        cipherSuite: null,
+        compressionMethod: null,
+        serverCertificate: null,
+        clientCertificate: null,
+        md5: forge.md.md5.create(),
+        sha1: forge.md.sha1.create()
+      };
+    }
+
+    /* Update handshake messages digest. Finished and CertificateVerify
+      messages are not digested here. They can't be digested as part of
+      the verify_data that they contain. These messages are manually
+      digested in their handlers. HelloRequest messages are simply never
+      included in the handshake message digest according to spec. */
+    if(type !== tls.HandshakeType.hello_request &&
+      type !== tls.HandshakeType.certificate_verify &&
+      type !== tls.HandshakeType.finished) {
+      c.session.md5.update(bytes);
+      c.session.sha1.update(bytes);
+    }
+
+    // handle specific handshake type record
+    hsTable[c.entity][c.expect][type](c, record, length);
+  } else {
+    // unexpected record
+    tls.handleUnexpected(c, record);
+  }
+};
+
+/**
+ * Called when an ApplicationData record is received.
+ *
+ * @param c the connection.
+ * @param record the record.
+ */
+tls.handleApplicationData = function(c, record) {
+  // buffer data, notify that its ready
+  c.data.putBuffer(record.fragment);
+  c.dataReady(c);
+
+  // continue
+  c.process();
+};
+
+/**
+ * Called when a Heartbeat record is received.
+ *
+ * @param c the connection.
+ * @param record the record.
+ */
+tls.handleHeartbeat = function(c, record) {
+  // get the heartbeat type and payload
+  var b = record.fragment;
+  var type = b.getByte();
+  var length = b.getInt16();
+  var payload = b.getBytes(length);
+
+  if(type === tls.HeartbeatMessageType.heartbeat_request) {
+    // discard request during handshake or if length is too large
+    if(c.handshaking || length > payload.length) {
+      // continue
+      return c.process();
+    }
+    // retransmit payload
+    tls.queue(c, tls.createRecord(c, {
+      type: tls.ContentType.heartbeat,
+      data: tls.createHeartbeat(
+        tls.HeartbeatMessageType.heartbeat_response, payload)
+    }));
+    tls.flush(c);
+  } else if(type === tls.HeartbeatMessageType.heartbeat_response) {
+    // check payload against expected payload, discard heartbeat if no match
+    if(payload !== c.expectedHeartbeatPayload) {
+      // continue
+      return c.process();
+    }
+
+    // notify that a valid heartbeat was received
+    if(c.heartbeatReceived) {
+      c.heartbeatReceived(c, forge.util.createBuffer(payload));
+    }
+  }
+
+  // continue
+  c.process();
+};
+
+/**
+ * The transistional state tables for receiving TLS records. It maps the
+ * current TLS engine state and a received record to a function to handle the
+ * record and update the state.
+ *
+ * For instance, if the current state is SHE, then the TLS engine is expecting
+ * a ServerHello record. Once a record is received, the handler function is
+ * looked up using the state SHE and the record's content type.
+ *
+ * The resulting function will either be an error handler or a record handler.
+ * The function will take whatever action is appropriate and update the state
+ * for the next record.
+ *
+ * The states are all based on possible server record types. Note that the
+ * client will never specifically expect to receive a HelloRequest or an alert
+ * from the server so there is no state that reflects this. These messages may
+ * occur at any time.
+ *
+ * There are two tables for mapping states because there is a second tier of
+ * types for handshake messages. Once a record with a content type of handshake
+ * is received, the handshake record handler will look up the handshake type in
+ * the secondary map to get its appropriate handler.
+ *
+ * Valid message orders are as follows:
+ *
+ * =======================FULL HANDSHAKE======================
+ * Client                                               Server
+ *
+ * ClientHello                  -------->
+ *                                                 ServerHello
+ *                                                Certificate*
+ *                                          ServerKeyExchange*
+ *                                         CertificateRequest*
+ *                              <--------      ServerHelloDone
+ * Certificate*
+ * ClientKeyExchange
+ * CertificateVerify*
+ * [ChangeCipherSpec]
+ * Finished                     -------->
+ *                                          [ChangeCipherSpec]
+ *                              <--------             Finished
+ * Application Data             <------->     Application Data
+ *
+ * =====================SESSION RESUMPTION=====================
+ * Client                                                Server
+ *
+ * ClientHello                   -------->
+ *                                                  ServerHello
+ *                                           [ChangeCipherSpec]
+ *                               <--------             Finished
+ * [ChangeCipherSpec]
+ * Finished                      -------->
+ * Application Data              <------->     Application Data
+ */
+// client expect states (indicate which records are expected to be received)
+var SHE = 0; // rcv server hello
+var SCE = 1; // rcv server certificate
+var SKE = 2; // rcv server key exchange
+var SCR = 3; // rcv certificate request
+var SHD = 4; // rcv server hello done
+var SCC = 5; // rcv change cipher spec
+var SFI = 6; // rcv finished
+var SAD = 7; // rcv application data
+var SER = 8; // not expecting any messages at this point
+
+// server expect states
+var CHE = 0; // rcv client hello
+var CCE = 1; // rcv client certificate
+var CKE = 2; // rcv client key exchange
+var CCV = 3; // rcv certificate verify
+var CCC = 4; // rcv change cipher spec
+var CFI = 5; // rcv finished
+var CAD = 6; // rcv application data
+var CER = 7; // not expecting any messages at this point
+
+// map client current expect state and content type to function
+var __ = tls.handleUnexpected;
+var R0 = tls.handleChangeCipherSpec;
+var R1 = tls.handleAlert;
+var R2 = tls.handleHandshake;
+var R3 = tls.handleApplicationData;
+var R4 = tls.handleHeartbeat;
+var ctTable = [];
+ctTable[tls.ConnectionEnd.client] = [
+//      CC,AL,HS,AD,HB
+/*SHE*/[__,R1,R2,__,R4],
+/*SCE*/[__,R1,R2,__,R4],
+/*SKE*/[__,R1,R2,__,R4],
+/*SCR*/[__,R1,R2,__,R4],
+/*SHD*/[__,R1,R2,__,R4],
+/*SCC*/[R0,R1,__,__,R4],
+/*SFI*/[__,R1,R2,__,R4],
+/*SAD*/[__,R1,R2,R3,R4],
+/*SER*/[__,R1,R2,__,R4]
+];
+
+// map server current expect state and content type to function
+ctTable[tls.ConnectionEnd.server] = [
+//      CC,AL,HS,AD
+/*CHE*/[__,R1,R2,__,R4],
+/*CCE*/[__,R1,R2,__,R4],
+/*CKE*/[__,R1,R2,__,R4],
+/*CCV*/[__,R1,R2,__,R4],
+/*CCC*/[R0,R1,__,__,R4],
+/*CFI*/[__,R1,R2,__,R4],
+/*CAD*/[__,R1,R2,R3,R4],
+/*CER*/[__,R1,R2,__,R4]
+];
+
+// map client current expect state and handshake type to function
+var H0 = tls.handleHelloRequest;
+var H1 = tls.handleServerHello;
+var H2 = tls.handleCertificate;
+var H3 = tls.handleServerKeyExchange;
+var H4 = tls.handleCertificateRequest;
+var H5 = tls.handleServerHelloDone;
+var H6 = tls.handleFinished;
+var hsTable = [];
+hsTable[tls.ConnectionEnd.client] = [
+//      HR,01,SH,03,04,05,06,07,08,09,10,SC,SK,CR,HD,15,CK,17,18,19,FI
+/*SHE*/[__,__,H1,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__],
+/*SCE*/[H0,__,__,__,__,__,__,__,__,__,__,H2,H3,H4,H5,__,__,__,__,__,__],
+/*SKE*/[H0,__,__,__,__,__,__,__,__,__,__,__,H3,H4,H5,__,__,__,__,__,__],
+/*SCR*/[H0,__,__,__,__,__,__,__,__,__,__,__,__,H4,H5,__,__,__,__,__,__],
+/*SHD*/[H0,__,__,__,__,__,__,__,__,__,__,__,__,__,H5,__,__,__,__,__,__],
+/*SCC*/[H0,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__],
+/*SFI*/[H0,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,H6],
+/*SAD*/[H0,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__],
+/*SER*/[H0,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__]
+];
+
+// map server current expect state and handshake type to function
+// Note: CAD[CH] does not map to FB because renegotation is prohibited
+var H7 = tls.handleClientHello;
+var H8 = tls.handleClientKeyExchange;
+var H9 = tls.handleCertificateVerify;
+hsTable[tls.ConnectionEnd.server] = [
+//      01,CH,02,03,04,05,06,07,08,09,10,CC,12,13,14,CV,CK,17,18,19,FI
+/*CHE*/[__,H7,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__],
+/*CCE*/[__,__,__,__,__,__,__,__,__,__,__,H2,__,__,__,__,__,__,__,__,__],
+/*CKE*/[__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,H8,__,__,__,__],
+/*CCV*/[__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,H9,__,__,__,__,__],
+/*CCC*/[__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__],
+/*CFI*/[__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,H6],
+/*CAD*/[__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__],
+/*CER*/[__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__]
+];
+
+/**
+ * Generates the master_secret and keys using the given security parameters.
+ *
+ * The security parameters for a TLS connection state are defined as such:
+ *
+ * struct {
+ *   ConnectionEnd          entity;
+ *   PRFAlgorithm           prf_algorithm;
+ *   BulkCipherAlgorithm    bulk_cipher_algorithm;
+ *   CipherType             cipher_type;
+ *   uint8                  enc_key_length;
+ *   uint8                  block_length;
+ *   uint8                  fixed_iv_length;
+ *   uint8                  record_iv_length;
+ *   MACAlgorithm           mac_algorithm;
+ *   uint8                  mac_length;
+ *   uint8                  mac_key_length;
+ *   CompressionMethod      compression_algorithm;
+ *   opaque                 master_secret[48];
+ *   opaque                 client_random[32];
+ *   opaque                 server_random[32];
+ * } SecurityParameters;
+ *
+ * Note that this definition is from TLS 1.2. In TLS 1.0 some of these
+ * parameters are ignored because, for instance, the PRFAlgorithm is a
+ * builtin-fixed algorithm combining iterations of MD5 and SHA-1 in TLS 1.0.
+ *
+ * The Record Protocol requires an algorithm to generate keys required by the
+ * current connection state.
+ *
+ * The master secret is expanded into a sequence of secure bytes, which is then
+ * split to a client write MAC key, a server write MAC key, a client write
+ * encryption key, and a server write encryption key. In TLS 1.0 a client write
+ * IV and server write IV are also generated. Each of these is generated from
+ * the byte sequence in that order. Unused values are empty. In TLS 1.2, some
+ * AEAD ciphers may additionally require a client write IV and a server write
+ * IV (see Section 6.2.3.3).
+ *
+ * When keys, MAC keys, and IVs are generated, the master secret is used as an
+ * entropy source.
+ *
+ * To generate the key material, compute:
+ *
+ * master_secret = PRF(pre_master_secret, "master secret",
+ *                     ClientHello.random + ServerHello.random)
+ *
+ * key_block = PRF(SecurityParameters.master_secret,
+ *                 "key expansion",
+ *                 SecurityParameters.server_random +
+ *                 SecurityParameters.client_random);
+ *
+ * until enough output has been generated. Then, the key_block is
+ * partitioned as follows:
+ *
+ * client_write_MAC_key[SecurityParameters.mac_key_length]
+ * server_write_MAC_key[SecurityParameters.mac_key_length]
+ * client_write_key[SecurityParameters.enc_key_length]
+ * server_write_key[SecurityParameters.enc_key_length]
+ * client_write_IV[SecurityParameters.fixed_iv_length]
+ * server_write_IV[SecurityParameters.fixed_iv_length]
+ *
+ * In TLS 1.2, the client_write_IV and server_write_IV are only generated for
+ * implicit nonce techniques as described in Section 3.2.1 of [AEAD]. This
+ * implementation uses TLS 1.0 so IVs are generated.
+ *
+ * Implementation note: The currently defined cipher suite which requires the
+ * most material is AES_256_CBC_SHA256. It requires 2 x 32 byte keys and 2 x 32
+ * byte MAC keys, for a total 128 bytes of key material. In TLS 1.0 it also
+ * requires 2 x 16 byte IVs, so it actually takes 160 bytes of key material.
+ *
+ * @param c the connection.
+ * @param sp the security parameters to use.
+ *
+ * @return the security keys.
+ */
+tls.generateKeys = function(c, sp) {
+  // TLS_RSA_WITH_AES_128_CBC_SHA (required to be compliant with TLS 1.2) &
+  // TLS_RSA_WITH_AES_256_CBC_SHA are the only cipher suites implemented
+  // at present
+
+  // TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA is required to be compliant with
+  // TLS 1.0 but we don't care right now because AES is better and we have
+  // an implementation for it
+
+  // TODO: TLS 1.2 implementation
+  /*
+  // determine the PRF
+  var prf;
+  switch(sp.prf_algorithm) {
+  case tls.PRFAlgorithm.tls_prf_sha256:
+    prf = prf_sha256;
+    break;
+  default:
+    // should never happen
+    throw new Error('Invalid PRF');
+  }
+  */
+
+  // TLS 1.0/1.1 implementation
+  var prf = prf_TLS1;
+
+  // concatenate server and client random
+  var random = sp.client_random + sp.server_random;
+
+  // only create master secret if session is new
+  if(!c.session.resuming) {
+    // create master secret, clean up pre-master secret
+    sp.master_secret = prf(
+      sp.pre_master_secret, 'master secret', random, 48).bytes();
+    sp.pre_master_secret = null;
+  }
+
+  // generate the amount of key material needed
+  random = sp.server_random + sp.client_random;
+  var length = 2 * sp.mac_key_length + 2 * sp.enc_key_length;
+
+  // include IV for TLS/1.0
+  var tls10 = (c.version.major === tls.Versions.TLS_1_0.major &&
+    c.version.minor === tls.Versions.TLS_1_0.minor);
+  if(tls10) {
+    length += 2 * sp.fixed_iv_length;
+  }
+  var km = prf(sp.master_secret, 'key expansion', random, length);
+
+  // split the key material into the MAC and encryption keys
+  var rval = {
+    client_write_MAC_key: km.getBytes(sp.mac_key_length),
+    server_write_MAC_key: km.getBytes(sp.mac_key_length),
+    client_write_key: km.getBytes(sp.enc_key_length),
+    server_write_key: km.getBytes(sp.enc_key_length)
+  };
+
+  // include TLS 1.0 IVs
+  if(tls10) {
+    rval.client_write_IV = km.getBytes(sp.fixed_iv_length);
+    rval.server_write_IV = km.getBytes(sp.fixed_iv_length);
+  }
+
+  return rval;
+};
+
+/**
+ * Creates a new initialized TLS connection state. A connection state has
+ * a read mode and a write mode.
+ *
+ * compression state:
+ *   The current state of the compression algorithm.
+ *
+ * cipher state:
+ *   The current state of the encryption algorithm. This will consist of the
+ *   scheduled key for that connection. For stream ciphers, this will also
+ *   contain whatever state information is necessary to allow the stream to
+ *   continue to encrypt or decrypt data.
+ *
+ * MAC key:
+ *   The MAC key for the connection.
+ *
+ * sequence number:
+ *   Each connection state contains a sequence number, which is maintained
+ *   separately for read and write states. The sequence number MUST be set to
+ *   zero whenever a connection state is made the active state. Sequence
+ *   numbers are of type uint64 and may not exceed 2^64-1. Sequence numbers do
+ *   not wrap. If a TLS implementation would need to wrap a sequence number,
+ *   it must renegotiate instead. A sequence number is incremented after each
+ *   record: specifically, the first record transmitted under a particular
+ *   connection state MUST use sequence number 0.
+ *
+ * @param c the connection.
+ *
+ * @return the new initialized TLS connection state.
+ */
+tls.createConnectionState = function(c) {
+  var client = (c.entity === tls.ConnectionEnd.client);
+
+  var createMode = function() {
+    var mode = {
+      // two 32-bit numbers, first is most significant
+      sequenceNumber: [0, 0],
+      macKey: null,
+      macLength: 0,
+      macFunction: null,
+      cipherState: null,
+      cipherFunction: function(record) {return true;},
+      compressionState: null,
+      compressFunction: function(record) {return true;},
+      updateSequenceNumber: function() {
+        if(mode.sequenceNumber[1] === 0xFFFFFFFF) {
+          mode.sequenceNumber[1] = 0;
+          ++mode.sequenceNumber[0];
+        } else {
+          ++mode.sequenceNumber[1];
+        }
+      }
+    };
+    return mode;
+  };
+  var state = {
+    read: createMode(),
+    write: createMode()
+  };
+
+  // update function in read mode will decrypt then decompress a record
+  state.read.update = function(c, record) {
+    if(!state.read.cipherFunction(record, state.read)) {
+      c.error(c, {
+        message: 'Could not decrypt record or bad MAC.',
+        send: true,
+        alert: {
+          level: tls.Alert.Level.fatal,
+          // doesn't matter if decryption failed or MAC was
+          // invalid, return the same error so as not to reveal
+          // which one occurred
+          description: tls.Alert.Description.bad_record_mac
+        }
+      });
+    } else if(!state.read.compressFunction(c, record, state.read)) {
+      c.error(c, {
+        message: 'Could not decompress record.',
+        send: true,
+        alert: {
+          level: tls.Alert.Level.fatal,
+          description: tls.Alert.Description.decompression_failure
+        }
+      });
+    }
+    return !c.fail;
+  };
+
+  // update function in write mode will compress then encrypt a record
+  state.write.update = function(c, record) {
+    if(!state.write.compressFunction(c, record, state.write)) {
+      // error, but do not send alert since it would require
+      // compression as well
+      c.error(c, {
+        message: 'Could not compress record.',
+        send: false,
+        alert: {
+          level: tls.Alert.Level.fatal,
+          description: tls.Alert.Description.internal_error
+        }
+      });
+    } else if(!state.write.cipherFunction(record, state.write)) {
+      // error, but do not send alert since it would require
+      // encryption as well
+      c.error(c, {
+        message: 'Could not encrypt record.',
+        send: false,
+        alert: {
+          level: tls.Alert.Level.fatal,
+          description: tls.Alert.Description.internal_error
+        }
+      });
+    }
+    return !c.fail;
+  };
+
+  // handle security parameters
+  if(c.session) {
+    var sp = c.session.sp;
+    c.session.cipherSuite.initSecurityParameters(sp);
+
+    // generate keys
+    sp.keys = tls.generateKeys(c, sp);
+    state.read.macKey = client ?
+      sp.keys.server_write_MAC_key : sp.keys.client_write_MAC_key;
+    state.write.macKey = client ?
+      sp.keys.client_write_MAC_key : sp.keys.server_write_MAC_key;
+
+    // cipher suite setup
+    c.session.cipherSuite.initConnectionState(state, c, sp);
+
+    // compression setup
+    switch(sp.compression_algorithm) {
+    case tls.CompressionMethod.none:
+      break;
+    case tls.CompressionMethod.deflate:
+      state.read.compressFunction = inflate;
+      state.write.compressFunction = deflate;
+      break;
+    default:
+      throw new Error('Unsupported compression algorithm.');
+    }
+  }
+
+  return state;
+};
+
+/**
+ * Creates a Random structure.
+ *
+ * struct {
+ *   uint32 gmt_unix_time;
+ *   opaque random_bytes[28];
+ * } Random;
+ *
+ * gmt_unix_time:
+ *   The current time and date in standard UNIX 32-bit format (seconds since
+ *   the midnight starting Jan 1, 1970, UTC, ignoring leap seconds) according
+ *   to the sender's internal clock. Clocks are not required to be set
+ *   correctly by the basic TLS protocol; higher-level or application
+ *   protocols may define additional requirements. Note that, for historical
+ *   reasons, the data element is named using GMT, the predecessor of the
+ *   current worldwide time base, UTC.
+ * random_bytes:
+ *   28 bytes generated by a secure random number generator.
+ *
+ * @return the Random structure as a byte array.
+ */
+tls.createRandom = function() {
+  // get UTC milliseconds
+  var d = new Date();
+  var utc = +d + d.getTimezoneOffset() * 60000;
+  var rval = forge.util.createBuffer();
+  rval.putInt32(utc);
+  rval.putBytes(forge.random.getBytes(28));
+  return rval;
+};
+
+/**
+ * Creates a TLS record with the given type and data.
+ *
+ * @param c the connection.
+ * @param options:
+ *   type: the record type.
+ *   data: the plain text data in a byte buffer.
+ *
+ * @return the created record.
+ */
+tls.createRecord = function(c, options) {
+  if(!options.data) {
+    return null;
+  }
+  var record = {
+    type: options.type,
+    version: {
+      major: c.version.major,
+      minor: c.version.minor
+    },
+    length: options.data.length(),
+    fragment: options.data
+  };
+  return record;
+};
+
+/**
+ * Creates a TLS alert record.
+ *
+ * @param c the connection.
+ * @param alert:
+ *   level: the TLS alert level.
+ *   description: the TLS alert description.
+ *
+ * @return the created alert record.
+ */
+tls.createAlert = function(c, alert) {
+  var b = forge.util.createBuffer();
+  b.putByte(alert.level);
+  b.putByte(alert.description);
+  return tls.createRecord(c, {
+    type: tls.ContentType.alert,
+    data: b
+  });
+};
+
+/* The structure of a TLS handshake message.
+ *
+ * struct {
+ *    HandshakeType msg_type;    // handshake type
+ *    uint24 length;             // bytes in message
+ *    select(HandshakeType) {
+ *       case hello_request:       HelloRequest;
+ *       case client_hello:        ClientHello;
+ *       case server_hello:        ServerHello;
+ *       case certificate:         Certificate;
+ *       case server_key_exchange: ServerKeyExchange;
+ *       case certificate_request: CertificateRequest;
+ *       case server_hello_done:   ServerHelloDone;
+ *       case certificate_verify:  CertificateVerify;
+ *       case client_key_exchange: ClientKeyExchange;
+ *       case finished:            Finished;
+ *    } body;
+ * } Handshake;
+ */
+
+/**
+ * Creates a ClientHello message.
+ *
+ * opaque SessionID<0..32>;
+ * enum { null(0), deflate(1), (255) } CompressionMethod;
+ * uint8 CipherSuite[2];
+ *
+ * struct {
+ *   ProtocolVersion client_version;
+ *   Random random;
+ *   SessionID session_id;
+ *   CipherSuite cipher_suites<2..2^16-2>;
+ *   CompressionMethod compression_methods<1..2^8-1>;
+ *   select(extensions_present) {
+ *     case false:
+ *       struct {};
+ *     case true:
+ *       Extension extensions<0..2^16-1>;
+ *   };
+ * } ClientHello;
+ *
+ * The extension format for extended client hellos and server hellos is:
+ *
+ * struct {
+ *   ExtensionType extension_type;
+ *   opaque extension_data<0..2^16-1>;
+ * } Extension;
+ *
+ * Here:
+ *
+ * - "extension_type" identifies the particular extension type.
+ * - "extension_data" contains information specific to the particular
+ * extension type.
+ *
+ * The extension types defined in this document are:
+ *
+ * enum {
+ *   server_name(0), max_fragment_length(1),
+ *   client_certificate_url(2), trusted_ca_keys(3),
+ *   truncated_hmac(4), status_request(5), (65535)
+ * } ExtensionType;
+ *
+ * @param c the connection.
+ *
+ * @return the ClientHello byte buffer.
+ */
+tls.createClientHello = function(c) {
+  // save hello version
+  c.session.clientHelloVersion = {
+    major: c.version.major,
+    minor: c.version.minor
+  };
+
+  // create supported cipher suites
+  var cipherSuites = forge.util.createBuffer();
+  for(var i = 0; i < c.cipherSuites.length; ++i) {
+    var cs = c.cipherSuites[i];
+    cipherSuites.putByte(cs.id[0]);
+    cipherSuites.putByte(cs.id[1]);
+  }
+  var cSuites = cipherSuites.length();
+
+  // create supported compression methods, null always supported, but
+  // also support deflate if connection has inflate and deflate methods
+  var compressionMethods = forge.util.createBuffer();
+  compressionMethods.putByte(tls.CompressionMethod.none);
+  // FIXME: deflate support disabled until issues with raw deflate data
+  // without zlib headers are resolved
+  /*
+  if(c.inflate !== null && c.deflate !== null) {
+    compressionMethods.putByte(tls.CompressionMethod.deflate);
+  }
+  */
+  var cMethods = compressionMethods.length();
+
+  // create TLS SNI (server name indication) extension if virtual host
+  // has been specified, see RFC 3546
+  var extensions = forge.util.createBuffer();
+  if(c.virtualHost) {
+    // create extension struct
+    var ext = forge.util.createBuffer();
+    ext.putByte(0x00); // type server_name (ExtensionType is 2 bytes)
+    ext.putByte(0x00);
+
+    /* In order to provide the server name, clients MAY include an
+     * extension of type "server_name" in the (extended) client hello.
+     * The "extension_data" field of this extension SHALL contain
+     * "ServerNameList" where:
+     *
+     * struct {
+     *   NameType name_type;
+     *   select(name_type) {
+     *     case host_name: HostName;
+     *   } name;
+     * } ServerName;
+     *
+     * enum {
+     *   host_name(0), (255)
+     * } NameType;
+     *
+     * opaque HostName<1..2^16-1>;
+     *
+     * struct {
+     *   ServerName server_name_list<1..2^16-1>
+     * } ServerNameList;
+     */
+    var serverName = forge.util.createBuffer();
+    serverName.putByte(0x00); // type host_name
+    writeVector(serverName, 2, forge.util.createBuffer(c.virtualHost));
+
+    // ServerNameList is in extension_data
+    var snList = forge.util.createBuffer();
+    writeVector(snList, 2, serverName);
+    writeVector(ext, 2, snList);
+    extensions.putBuffer(ext);
+  }
+  var extLength = extensions.length();
+  if(extLength > 0) {
+    // add extension vector length
+    extLength += 2;
+  }
+
+  // determine length of the handshake message
+  // cipher suites and compression methods size will need to be
+  // updated if more get added to the list
+  var sessionId = c.session.id;
+  var length =
+    sessionId.length + 1 + // session ID vector
+    2 +                    // version (major + minor)
+    4 + 28 +               // random time and random bytes
+    2 + cSuites +          // cipher suites vector
+    1 + cMethods +         // compression methods vector
+    extLength;             // extensions vector
+
+  // build record fragment
+  var rval = forge.util.createBuffer();
+  rval.putByte(tls.HandshakeType.client_hello);
+  rval.putInt24(length);                     // handshake length
+  rval.putByte(c.version.major);             // major version
+  rval.putByte(c.version.minor);             // minor version
+  rval.putBytes(c.session.sp.client_random); // random time + bytes
+  writeVector(rval, 1, forge.util.createBuffer(sessionId));
+  writeVector(rval, 2, cipherSuites);
+  writeVector(rval, 1, compressionMethods);
+  if(extLength > 0) {
+    writeVector(rval, 2, extensions);
+  }
+  return rval;
+};
+
+/**
+ * Creates a ServerHello message.
+ *
+ * @param c the connection.
+ *
+ * @return the ServerHello byte buffer.
+ */
+tls.createServerHello = function(c) {
+  // determine length of the handshake message
+  var sessionId = c.session.id;
+  var length =
+    sessionId.length + 1 + // session ID vector
+    2 +                    // version (major + minor)
+    4 + 28 +               // random time and random bytes
+    2 +                    // chosen cipher suite
+    1;                     // chosen compression method
+
+  // build record fragment
+  var rval = forge.util.createBuffer();
+  rval.putByte(tls.HandshakeType.server_hello);
+  rval.putInt24(length);                     // handshake length
+  rval.putByte(c.version.major);             // major version
+  rval.putByte(c.version.minor);             // minor version
+  rval.putBytes(c.session.sp.server_random); // random time + bytes
+  writeVector(rval, 1, forge.util.createBuffer(sessionId));
+  rval.putByte(c.session.cipherSuite.id[0]);
+  rval.putByte(c.session.cipherSuite.id[1]);
+  rval.putByte(c.session.compressionMethod);
+  return rval;
+};
+
+/**
+ * Creates a Certificate message.
+ *
+ * When this message will be sent:
+ *   This is the first message the client can send after receiving a server
+ *   hello done message and the first message the server can send after
+ *   sending a ServerHello. This client message is only sent if the server
+ *   requests a certificate. If no suitable certificate is available, the
+ *   client should send a certificate message containing no certificates. If
+ *   client authentication is required by the server for the handshake to
+ *   continue, it may respond with a fatal handshake failure alert.
+ *
+ * opaque ASN.1Cert<1..2^24-1>;
+ *
+ * struct {
+ *   ASN.1Cert certificate_list<0..2^24-1>;
+ * } Certificate;
+ *
+ * @param c the connection.
+ *
+ * @return the Certificate byte buffer.
+ */
+tls.createCertificate = function(c) {
+  // TODO: check certificate request to ensure types are supported
+
+  // get a certificate (a certificate as a PEM string)
+  var client = (c.entity === tls.ConnectionEnd.client);
+  var cert = null;
+  if(c.getCertificate) {
+    var hint;
+    if(client) {
+      hint = c.session.certificateRequest;
+    } else {
+      hint = c.session.extensions.server_name.serverNameList;
+    }
+    cert = c.getCertificate(c, hint);
+  }
+
+  // buffer to hold certificate list
+  var certList = forge.util.createBuffer();
+  if(cert !== null) {
+    try {
+      // normalize cert to a chain of certificates
+      if(!forge.util.isArray(cert)) {
+        cert = [cert];
+      }
+      var asn1 = null;
+      for(var i = 0; i < cert.length; ++i) {
+        var msg = forge.pem.decode(cert[i])[0];
+        if(msg.type !== 'CERTIFICATE' &&
+          msg.type !== 'X509 CERTIFICATE' &&
+          msg.type !== 'TRUSTED CERTIFICATE') {
+          var error = new Error('Could not convert certificate from PEM; PEM ' +
+            'header type is not "CERTIFICATE", "X509 CERTIFICATE", or ' +
+            '"TRUSTED CERTIFICATE".');
+          error.headerType = msg.type;
+          throw error;
+        }
+        if(msg.procType && msg.procType.type === 'ENCRYPTED') {
+          throw new Error('Could not convert certificate from PEM; PEM is encrypted.');
+        }
+
+        var der = forge.util.createBuffer(msg.body);
+        if(asn1 === null) {
+          asn1 = forge.asn1.fromDer(der.bytes(), false);
+        }
+
+        // certificate entry is itself a vector with 3 length bytes
+        var certBuffer = forge.util.createBuffer();
+        writeVector(certBuffer, 3, der);
+
+        // add cert vector to cert list vector
+        certList.putBuffer(certBuffer);
+      }
+
+      // save certificate
+      cert = forge.pki.certificateFromAsn1(asn1);
+      if(client) {
+        c.session.clientCertificate = cert;
+      } else {
+        c.session.serverCertificate = cert;
+      }
+    } catch(ex) {
+      return c.error(c, {
+        message: 'Could not send certificate list.',
+        cause: ex,
+        send: true,
+        alert: {
+          level: tls.Alert.Level.fatal,
+          description: tls.Alert.Description.bad_certificate
+        }
+      });
+    }
+  }
+
+  // determine length of the handshake message
+  var length = 3 + certList.length(); // cert list vector
+
+  // build record fragment
+  var rval = forge.util.createBuffer();
+  rval.putByte(tls.HandshakeType.certificate);
+  rval.putInt24(length);
+  writeVector(rval, 3, certList);
+  return rval;
+};
+
+/**
+ * Creates a ClientKeyExchange message.
+ *
+ * When this message will be sent:
+ *   This message is always sent by the client. It will immediately follow the
+ *   client certificate message, if it is sent. Otherwise it will be the first
+ *   message sent by the client after it receives the server hello done
+ *   message.
+ *
+ * Meaning of this message:
+ *   With this message, the premaster secret is set, either though direct
+ *   transmission of the RSA-encrypted secret, or by the transmission of
+ *   Diffie-Hellman parameters which will allow each side to agree upon the
+ *   same premaster secret. When the key exchange method is DH_RSA or DH_DSS,
+ *   client certification has been requested, and the client was able to
+ *   respond with a certificate which contained a Diffie-Hellman public key
+ *   whose parameters (group and generator) matched those specified by the
+ *   server in its certificate, this message will not contain any data.
+ *
+ * Meaning of this message:
+ *   If RSA is being used for key agreement and authentication, the client
+ *   generates a 48-byte premaster secret, encrypts it using the public key
+ *   from the server's certificate or the temporary RSA key provided in a
+ *   server key exchange message, and sends the result in an encrypted
+ *   premaster secret message. This structure is a variant of the client
+ *   key exchange message, not a message in itself.
+ *
+ * struct {
+ *   select(KeyExchangeAlgorithm) {
+ *     case rsa: EncryptedPreMasterSecret;
+ *     case diffie_hellman: ClientDiffieHellmanPublic;
+ *   } exchange_keys;
+ * } ClientKeyExchange;
+ *
+ * struct {
+ *   ProtocolVersion client_version;
+ *   opaque random[46];
+ * } PreMasterSecret;
+ *
+ * struct {
+ *   public-key-encrypted PreMasterSecret pre_master_secret;
+ * } EncryptedPreMasterSecret;
+ *
+ * A public-key-encrypted element is encoded as a vector <0..2^16-1>.
+ *
+ * @param c the connection.
+ *
+ * @return the ClientKeyExchange byte buffer.
+ */
+tls.createClientKeyExchange = function(c) {
+  // create buffer to encrypt
+  var b = forge.util.createBuffer();
+
+  // add highest client-supported protocol to help server avoid version
+  // rollback attacks
+  b.putByte(c.session.clientHelloVersion.major);
+  b.putByte(c.session.clientHelloVersion.minor);
+
+  // generate and add 46 random bytes
+  b.putBytes(forge.random.getBytes(46));
+
+  // save pre-master secret
+  var sp = c.session.sp;
+  sp.pre_master_secret = b.getBytes();
+
+  // RSA-encrypt the pre-master secret
+  var key = c.session.serverCertificate.publicKey;
+  b = key.encrypt(sp.pre_master_secret);
+
+  /* Note: The encrypted pre-master secret will be stored in a
+    public-key-encrypted opaque vector that has the length prefixed using
+    2 bytes, so include those 2 bytes in the handshake message length. This
+    is done as a minor optimization instead of calling writeVector(). */
+
+  // determine length of the handshake message
+  var length = b.length + 2;
+
+  // build record fragment
+  var rval = forge.util.createBuffer();
+  rval.putByte(tls.HandshakeType.client_key_exchange);
+  rval.putInt24(length);
+  // add vector length bytes
+  rval.putInt16(b.length);
+  rval.putBytes(b);
+  return rval;
+};
+
+/**
+ * Creates a ServerKeyExchange message.
+ *
+ * @param c the connection.
+ *
+ * @return the ServerKeyExchange byte buffer.
+ */
+tls.createServerKeyExchange = function(c) {
+  // this implementation only supports RSA, no Diffie-Hellman support,
+  // so this record is empty
+
+  // determine length of the handshake message
+  var length = 0;
+
+  // build record fragment
+  var rval = forge.util.createBuffer();
+  if(length > 0) {
+    rval.putByte(tls.HandshakeType.server_key_exchange);
+    rval.putInt24(length);
+  }
+  return rval;
+};
+
+/**
+ * Gets the signed data used to verify a client-side certificate. See
+ * tls.createCertificateVerify() for details.
+ *
+ * @param c the connection.
+ * @param callback the callback to call once the signed data is ready.
+ */
+tls.getClientSignature = function(c, callback) {
+  // generate data to RSA encrypt
+  var b = forge.util.createBuffer();
+  b.putBuffer(c.session.md5.digest());
+  b.putBuffer(c.session.sha1.digest());
+  b = b.getBytes();
+
+  // create default signing function as necessary
+  c.getSignature = c.getSignature || function(c, b, callback) {
+    // do rsa encryption, call callback
+    var privateKey = null;
+    if(c.getPrivateKey) {
+      try {
+        privateKey = c.getPrivateKey(c, c.session.clientCertificate);
+        privateKey = forge.pki.privateKeyFromPem(privateKey);
+      } catch(ex) {
+        c.error(c, {
+          message: 'Could not get private key.',
+          cause: ex,
+          send: true,
+          alert: {
+            level: tls.Alert.Level.fatal,
+            description: tls.Alert.Description.internal_error
+          }
+        });
+      }
+    }
+    if(privateKey === null) {
+      c.error(c, {
+        message: 'No private key set.',
+        send: true,
+        alert: {
+          level: tls.Alert.Level.fatal,
+          description: tls.Alert.Description.internal_error
+        }
+      });
+    } else {
+      b = privateKey.sign(b, null);
+    }
+    callback(c, b);
+  };
+
+  // get client signature
+  c.getSignature(c, b, callback);
+};
+
+/**
+ * Creates a CertificateVerify message.
+ *
+ * Meaning of this message:
+ *   This structure conveys the client's Diffie-Hellman public value
+ *   (Yc) if it was not already included in the client's certificate.
+ *   The encoding used for Yc is determined by the enumerated
+ *   PublicValueEncoding. This structure is a variant of the client
+ *   key exchange message, not a message in itself.
+ *
+ * When this message will be sent:
+ *   This message is used to provide explicit verification of a client
+ *   certificate. This message is only sent following a client
+ *   certificate that has signing capability (i.e. all certificates
+ *   except those containing fixed Diffie-Hellman parameters). When
+ *   sent, it will immediately follow the client key exchange message.
+ *
+ * struct {
+ *   Signature signature;
+ * } CertificateVerify;
+ *
+ * CertificateVerify.signature.md5_hash
+ *   MD5(handshake_messages);
+ *
+ * Certificate.signature.sha_hash
+ *   SHA(handshake_messages);
+ *
+ * Here handshake_messages refers to all handshake messages sent or
+ * received starting at client hello up to but not including this
+ * message, including the type and length fields of the handshake
+ * messages.
+ *
+ * select(SignatureAlgorithm) {
+ *   case anonymous: struct { };
+ *   case rsa:
+ *     digitally-signed struct {
+ *       opaque md5_hash[16];
+ *       opaque sha_hash[20];
+ *     };
+ *   case dsa:
+ *     digitally-signed struct {
+ *       opaque sha_hash[20];
+ *     };
+ * } Signature;
+ *
+ * In digital signing, one-way hash functions are used as input for a
+ * signing algorithm. A digitally-signed element is encoded as an opaque
+ * vector <0..2^16-1>, where the length is specified by the signing
+ * algorithm and key.
+ *
+ * In RSA signing, a 36-byte structure of two hashes (one SHA and one
+ * MD5) is signed (encrypted with the private key). It is encoded with
+ * PKCS #1 block type 0 or type 1 as described in [PKCS1].
+ *
+ * In DSS, the 20 bytes of the SHA hash are run directly through the
+ * Digital Signing Algorithm with no additional hashing.
+ *
+ * @param c the connection.
+ * @param signature the signature to include in the message.
+ *
+ * @return the CertificateVerify byte buffer.
+ */
+tls.createCertificateVerify = function(c, signature) {
+  /* Note: The signature will be stored in a "digitally-signed" opaque
+    vector that has the length prefixed using 2 bytes, so include those
+    2 bytes in the handshake message length. This is done as a minor
+    optimization instead of calling writeVector(). */
+
+  // determine length of the handshake message
+  var length = signature.length + 2;
+
+  // build record fragment
+  var rval = forge.util.createBuffer();
+  rval.putByte(tls.HandshakeType.certificate_verify);
+  rval.putInt24(length);
+  // add vector length bytes
+  rval.putInt16(signature.length);
+  rval.putBytes(signature);
+  return rval;
+};
+
+/**
+ * Creates a CertificateRequest message.
+ *
+ * @param c the connection.
+ *
+ * @return the CertificateRequest byte buffer.
+ */
+tls.createCertificateRequest = function(c) {
+  // TODO: support other certificate types
+  var certTypes = forge.util.createBuffer();
+
+  // common RSA certificate type
+  certTypes.putByte(0x01);
+
+  // TODO: verify that this data format is correct
+  // add distinguished names from CA store
+  var cAs = forge.util.createBuffer();
+  for(var key in c.caStore.certs) {
+    var cert = c.caStore.certs[key];
+    var dn = forge.pki.distinguishedNameToAsn1(cert.subject);
+    cAs.putBuffer(forge.asn1.toDer(dn));
+  }
+
+  // TODO: TLS 1.2+ has a different format
+
+  // determine length of the handshake message
+  var length =
+    1 + certTypes.length() +
+    2 + cAs.length();
+
+  // build record fragment
+  var rval = forge.util.createBuffer();
+  rval.putByte(tls.HandshakeType.certificate_request);
+  rval.putInt24(length);
+  writeVector(rval, 1, certTypes);
+  writeVector(rval, 2, cAs);
+  return rval;
+};
+
+/**
+ * Creates a ServerHelloDone message.
+ *
+ * @param c the connection.
+ *
+ * @return the ServerHelloDone byte buffer.
+ */
+tls.createServerHelloDone = function(c) {
+  // build record fragment
+  var rval = forge.util.createBuffer();
+  rval.putByte(tls.HandshakeType.server_hello_done);
+  rval.putInt24(0);
+  return rval;
+};
+
+/**
+ * Creates a ChangeCipherSpec message.
+ *
+ * The change cipher spec protocol exists to signal transitions in
+ * ciphering strategies. The protocol consists of a single message,
+ * which is encrypted and compressed under the current (not the pending)
+ * connection state. The message consists of a single byte of value 1.
+ *
+ * struct {
+ *   enum { change_cipher_spec(1), (255) } type;
+ * } ChangeCipherSpec;
+ *
+ * @return the ChangeCipherSpec byte buffer.
+ */
+tls.createChangeCipherSpec = function() {
+  var rval = forge.util.createBuffer();
+  rval.putByte(0x01);
+  return rval;
+};
+
+/**
+ * Creates a Finished message.
+ *
+ * struct {
+ *   opaque verify_data[12];
+ * } Finished;
+ *
+ * verify_data
+ *   PRF(master_secret, finished_label, MD5(handshake_messages) +
+ *   SHA-1(handshake_messages)) [0..11];
+ *
+ * finished_label
+ *   For Finished messages sent by the client, the string "client
+ *   finished". For Finished messages sent by the server, the
+ *   string "server finished".
+ *
+ * handshake_messages
+ *   All of the data from all handshake messages up to but not
+ *   including this message. This is only data visible at the
+ *   handshake layer and does not include record layer headers.
+ *   This is the concatenation of all the Handshake structures as
+ *   defined in 7.4 exchanged thus far.
+ *
+ * @param c the connection.
+ *
+ * @return the Finished byte buffer.
+ */
+tls.createFinished = function(c) {
+  // generate verify_data
+  var b = forge.util.createBuffer();
+  b.putBuffer(c.session.md5.digest());
+  b.putBuffer(c.session.sha1.digest());
+
+  // TODO: determine prf function and verify length for TLS 1.2
+  var client = (c.entity === tls.ConnectionEnd.client);
+  var sp = c.session.sp;
+  var vdl = 12;
+  var prf = prf_TLS1;
+  var label = client ? 'client finished' : 'server finished';
+  b = prf(sp.master_secret, label, b.getBytes(), vdl);
+
+  // build record fragment
+  var rval = forge.util.createBuffer();
+  rval.putByte(tls.HandshakeType.finished);
+  rval.putInt24(b.length());
+  rval.putBuffer(b);
+  return rval;
+};
+
+/**
+ * Creates a HeartbeatMessage (See RFC 6520).
+ *
+ * struct {
+ *   HeartbeatMessageType type;
+ *   uint16 payload_length;
+ *   opaque payload[HeartbeatMessage.payload_length];
+ *   opaque padding[padding_length];
+ * } HeartbeatMessage;
+ *
+ * The total length of a HeartbeatMessage MUST NOT exceed 2^14 or
+ * max_fragment_length when negotiated as defined in [RFC6066].
+ *
+ * type: The message type, either heartbeat_request or heartbeat_response.
+ *
+ * payload_length: The length of the payload.
+ *
+ * payload: The payload consists of arbitrary content.
+ *
+ * padding: The padding is random content that MUST be ignored by the
+ *   receiver. The length of a HeartbeatMessage is TLSPlaintext.length
+ *   for TLS and DTLSPlaintext.length for DTLS. Furthermore, the
+ *   length of the type field is 1 byte, and the length of the
+ *   payload_length is 2. Therefore, the padding_length is
+ *   TLSPlaintext.length - payload_length - 3 for TLS and
+ *   DTLSPlaintext.length - payload_length - 3 for DTLS. The
+ *   padding_length MUST be at least 16.
+ *
+ * The sender of a HeartbeatMessage MUST use a random padding of at
+ * least 16 bytes. The padding of a received HeartbeatMessage message
+ * MUST be ignored.
+ *
+ * If the payload_length of a received HeartbeatMessage is too large,
+ * the received HeartbeatMessage MUST be discarded silently.
+ *
+ * @param c the connection.
+ * @param type the tls.HeartbeatMessageType.
+ * @param payload the heartbeat data to send as the payload.
+ * @param [payloadLength] the payload length to use, defaults to the
+ *          actual payload length.
+ *
+ * @return the HeartbeatRequest byte buffer.
+ */
+tls.createHeartbeat = function(type, payload, payloadLength) {
+  if(typeof payloadLength === 'undefined') {
+    payloadLength = payload.length;
+  }
+  // build record fragment
+  var rval = forge.util.createBuffer();
+  rval.putByte(type);               // heartbeat message type
+  rval.putInt16(payloadLength);     // payload length
+  rval.putBytes(payload);           // payload
+  // padding
+  var plaintextLength = rval.length();
+  var paddingLength = Math.max(16, plaintextLength - payloadLength - 3);
+  rval.putBytes(forge.random.getBytes(paddingLength));
+  return rval;
+};
+
+/**
+ * Fragments, compresses, encrypts, and queues a record for delivery.
+ *
+ * @param c the connection.
+ * @param record the record to queue.
+ */
+tls.queue = function(c, record) {
+  // error during record creation
+  if(!record) {
+    return;
+  }
+
+  // if the record is a handshake record, update handshake hashes
+  if(record.type === tls.ContentType.handshake) {
+    var bytes = record.fragment.bytes();
+    c.session.md5.update(bytes);
+    c.session.sha1.update(bytes);
+    bytes = null;
+  }
+
+  // handle record fragmentation
+  var records;
+  if(record.fragment.length() <= tls.MaxFragment) {
+    records = [record];
+  } else {
+    // fragment data as long as it is too long
+    records = [];
+    var data = record.fragment.bytes();
+    while(data.length > tls.MaxFragment) {
+      records.push(tls.createRecord(c, {
+        type: record.type,
+        data: forge.util.createBuffer(data.slice(0, tls.MaxFragment))
+      }));
+      data = data.slice(tls.MaxFragment);
+    }
+    // add last record
+    if(data.length > 0) {
+      records.push(tls.createRecord(c, {
+        type: record.type,
+        data: forge.util.createBuffer(data)
+      }));
+    }
+  }
+
+  // compress and encrypt all fragmented records
+  for(var i = 0; i < records.length && !c.fail; ++i) {
+    // update the record using current write state
+    var rec = records[i];
+    var s = c.state.current.write;
+    if(s.update(c, rec)) {
+      // store record
+      c.records.push(rec);
+    }
+  }
+};
+
+/**
+ * Flushes all queued records to the output buffer and calls the
+ * tlsDataReady() handler on the given connection.
+ *
+ * @param c the connection.
+ *
+ * @return true on success, false on failure.
+ */
+tls.flush = function(c) {
+  for(var i = 0; i < c.records.length; ++i) {
+    var record = c.records[i];
+
+    // add record header and fragment
+    c.tlsData.putByte(record.type);
+    c.tlsData.putByte(record.version.major);
+    c.tlsData.putByte(record.version.minor);
+    c.tlsData.putInt16(record.fragment.length());
+    c.tlsData.putBuffer(c.records[i].fragment);
+  }
+  c.records = [];
+  return c.tlsDataReady(c);
+};
+
+/**
+ * Maps a pki.certificateError to a tls.Alert.Description.
+ *
+ * @param error the error to map.
+ *
+ * @return the alert description.
+ */
+var _certErrorToAlertDesc = function(error) {
+  switch(error) {
+  case true:
+    return true;
+  case forge.pki.certificateError.bad_certificate:
+    return tls.Alert.Description.bad_certificate;
+  case forge.pki.certificateError.unsupported_certificate:
+    return tls.Alert.Description.unsupported_certificate;
+  case forge.pki.certificateError.certificate_revoked:
+    return tls.Alert.Description.certificate_revoked;
+  case forge.pki.certificateError.certificate_expired:
+    return tls.Alert.Description.certificate_expired;
+  case forge.pki.certificateError.certificate_unknown:
+    return tls.Alert.Description.certificate_unknown;
+  case forge.pki.certificateError.unknown_ca:
+    return tls.Alert.Description.unknown_ca;
+  default:
+    return tls.Alert.Description.bad_certificate;
+  }
+};
+
+/**
+ * Maps a tls.Alert.Description to a pki.certificateError.
+ *
+ * @param desc the alert description.
+ *
+ * @return the certificate error.
+ */
+var _alertDescToCertError = function(desc) {
+  switch(desc) {
+  case true:
+    return true;
+  case tls.Alert.Description.bad_certificate:
+    return forge.pki.certificateError.bad_certificate;
+  case tls.Alert.Description.unsupported_certificate:
+    return forge.pki.certificateError.unsupported_certificate;
+  case tls.Alert.Description.certificate_revoked:
+    return forge.pki.certificateError.certificate_revoked;
+  case tls.Alert.Description.certificate_expired:
+    return forge.pki.certificateError.certificate_expired;
+  case tls.Alert.Description.certificate_unknown:
+    return forge.pki.certificateError.certificate_unknown;
+  case tls.Alert.Description.unknown_ca:
+    return forge.pki.certificateError.unknown_ca;
+  default:
+    return forge.pki.certificateError.bad_certificate;
+  }
+};
+
+/**
+ * Verifies a certificate chain against the given connection's
+ * Certificate Authority store.
+ *
+ * @param c the TLS connection.
+ * @param chain the certificate chain to verify, with the root or highest
+ *          authority at the end.
+ *
+ * @return true if successful, false if not.
+ */
+tls.verifyCertificateChain = function(c, chain) {
+  try {
+    // verify chain
+    forge.pki.verifyCertificateChain(c.caStore, chain,
+      function verify(vfd, depth, chain) {
+        // convert pki.certificateError to tls alert description
+        var desc = _certErrorToAlertDesc(vfd);
+
+        // call application callback
+        var ret = c.verify(c, vfd, depth, chain);
+        if(ret !== true) {
+          if(typeof ret === 'object' && !forge.util.isArray(ret)) {
+            // throw custom error
+            var error = new Error('The application rejected the certificate.');
+            error.send = true;
+            error.alert = {
+              level: tls.Alert.Level.fatal,
+              description: tls.Alert.Description.bad_certificate
+            };
+            if(ret.message) {
+              error.message = ret.message;
+            }
+            if(ret.alert) {
+              error.alert.description = ret.alert;
+            }
+            throw error;
+          }
+
+          // convert tls alert description to pki.certificateError
+          if(ret !== vfd) {
+            ret = _alertDescToCertError(ret);
+          }
+        }
+
+        return ret;
+      });
+  } catch(ex) {
+    // build tls error if not already customized
+    var err = ex;
+    if(typeof err !== 'object' || forge.util.isArray(err)) {
+      err = {
+        send: true,
+        alert: {
+          level: tls.Alert.Level.fatal,
+          description: _certErrorToAlertDesc(ex)
+        }
+      };
+    }
+    if(!('send' in err)) {
+      err.send = true;
+    }
+    if(!('alert' in err)) {
+      err.alert = {
+        level: tls.Alert.Level.fatal,
+        description: _certErrorToAlertDesc(err.error)
+      };
+    }
+
+    // send error
+    c.error(c, err);
+  }
+
+  return !c.fail;
+};
+
+/**
+ * Creates a new TLS session cache.
+ *
+ * @param cache optional map of session ID to cached session.
+ * @param capacity the maximum size for the cache (default: 100).
+ *
+ * @return the new TLS session cache.
+ */
+tls.createSessionCache = function(cache, capacity) {
+  var rval = null;
+
+  // assume input is already a session cache object
+  if(cache && cache.getSession && cache.setSession && cache.order) {
+    rval = cache;
+  } else {
+    // create cache
+    rval = {};
+    rval.cache = cache || {};
+    rval.capacity = Math.max(capacity || 100, 1);
+    rval.order = [];
+
+    // store order for sessions, delete session overflow
+    for(var key in cache) {
+      if(rval.order.length <= capacity) {
+        rval.order.push(key);
+      } else {
+        delete cache[key];
+      }
+    }
+
+    // get a session from a session ID (or get any session)
+    rval.getSession = function(sessionId) {
+      var session = null;
+      var key = null;
+
+      // if session ID provided, use it
+      if(sessionId) {
+        key = forge.util.bytesToHex(sessionId);
+      } else if(rval.order.length > 0) {
+        // get first session from cache
+        key = rval.order[0];
+      }
+
+      if(key !== null && key in rval.cache) {
+        // get cached session and remove from cache
+        session = rval.cache[key];
+        delete rval.cache[key];
+        for(var i in rval.order) {
+          if(rval.order[i] === key) {
+            rval.order.splice(i, 1);
+            break;
+          }
+        }
+      }
+
+      return session;
+    };
+
+    // set a session in the cache
+    rval.setSession = function(sessionId, session) {
+      // remove session from cache if at capacity
+      if(rval.order.length === rval.capacity) {
+        var key = rval.order.shift();
+        delete rval.cache[key];
+      }
+      // add session to cache
+      var key = forge.util.bytesToHex(sessionId);
+      rval.order.push(key);
+      rval.cache[key] = session;
+    };
+  }
+
+  return rval;
+};
+
+/**
+ * Creates a new TLS connection.
+ *
+ * See public createConnection() docs for more details.
+ *
+ * @param options the options for this connection.
+ *
+ * @return the new TLS connection.
+ */
+tls.createConnection = function(options) {
+  var caStore = null;
+  if(options.caStore) {
+    // if CA store is an array, convert it to a CA store object
+    if(forge.util.isArray(options.caStore)) {
+      caStore = forge.pki.createCaStore(options.caStore);
+    } else {
+      caStore = options.caStore;
+    }
+  } else {
+    // create empty CA store
+    caStore = forge.pki.createCaStore();
+  }
+
+  // setup default cipher suites
+  var cipherSuites = options.cipherSuites || null;
+  if(cipherSuites === null) {
+    cipherSuites = [];
+    for(var key in tls.CipherSuites) {
+      cipherSuites.push(tls.CipherSuites[key]);
+    }
+  }
+
+  // set default entity
+  var entity = (options.server || false) ?
+    tls.ConnectionEnd.server : tls.ConnectionEnd.client;
+
+  // create session cache if requested
+  var sessionCache = options.sessionCache ?
+    tls.createSessionCache(options.sessionCache) : null;
+
+  // create TLS connection
+  var c = {
+    version: {major: tls.Version.major, minor: tls.Version.minor},
+    entity: entity,
+    sessionId: options.sessionId,
+    caStore: caStore,
+    sessionCache: sessionCache,
+    cipherSuites: cipherSuites,
+    connected: options.connected,
+    virtualHost: options.virtualHost || null,
+    verifyClient: options.verifyClient || false,
+    verify: options.verify || function(cn, vfd, dpth, cts) {return vfd;},
+    getCertificate: options.getCertificate || null,
+    getPrivateKey: options.getPrivateKey || null,
+    getSignature: options.getSignature || null,
+    input: forge.util.createBuffer(),
+    tlsData: forge.util.createBuffer(),
+    data: forge.util.createBuffer(),
+    tlsDataReady: options.tlsDataReady,
+    dataReady: options.dataReady,
+    heartbeatReceived: options.heartbeatReceived,
+    closed: options.closed,
+    error: function(c, ex) {
+      // set origin if not set
+      ex.origin = ex.origin ||
+        ((c.entity === tls.ConnectionEnd.client) ? 'client' : 'server');
+
+      // send TLS alert
+      if(ex.send) {
+        tls.queue(c, tls.createAlert(c, ex.alert));
+        tls.flush(c);
+      }
+
+      // error is fatal by default
+      var fatal = (ex.fatal !== false);
+      if(fatal) {
+        // set fail flag
+        c.fail = true;
+      }
+
+      // call error handler first
+      options.error(c, ex);
+
+      if(fatal) {
+        // fatal error, close connection, do not clear fail
+        c.close(false);
+      }
+    },
+    deflate: options.deflate || null,
+    inflate: options.inflate || null
+  };
+
+  /**
+   * Resets a closed TLS connection for reuse. Called in c.close().
+   *
+   * @param clearFail true to clear the fail flag (default: true).
+   */
+  c.reset = function(clearFail) {
+    c.version = {major: tls.Version.major, minor: tls.Version.minor};
+    c.record = null;
+    c.session = null;
+    c.peerCertificate = null;
+    c.state = {
+      pending: null,
+      current: null
+    };
+    c.expect = (c.entity === tls.ConnectionEnd.client) ? SHE : CHE;
+    c.fragmented = null;
+    c.records = [];
+    c.open = false;
+    c.handshakes = 0;
+    c.handshaking = false;
+    c.isConnected = false;
+    c.fail = !(clearFail || typeof(clearFail) === 'undefined');
+    c.input.clear();
+    c.tlsData.clear();
+    c.data.clear();
+    c.state.current = tls.createConnectionState(c);
+  };
+
+  // do initial reset of connection
+  c.reset();
+
+  /**
+   * Updates the current TLS engine state based on the given record.
+   *
+   * @param c the TLS connection.
+   * @param record the TLS record to act on.
+   */
+  var _update = function(c, record) {
+    // get record handler (align type in table by subtracting lowest)
+    var aligned = record.type - tls.ContentType.change_cipher_spec;
+    var handlers = ctTable[c.entity][c.expect];
+    if(aligned in handlers) {
+      handlers[aligned](c, record);
+    } else {
+      // unexpected record
+      tls.handleUnexpected(c, record);
+    }
+  };
+
+  /**
+   * Reads the record header and initializes the next record on the given
+   * connection.
+   *
+   * @param c the TLS connection with the next record.
+   *
+   * @return 0 if the input data could be processed, otherwise the
+   *         number of bytes required for data to be processed.
+   */
+  var _readRecordHeader = function(c) {
+    var rval = 0;
+
+    // get input buffer and its length
+    var b = c.input;
+    var len = b.length();
+
+    // need at least 5 bytes to initialize a record
+    if(len < 5) {
+      rval = 5 - len;
+    } else {
+      // enough bytes for header
+      // initialize record
+      c.record = {
+        type: b.getByte(),
+        version: {
+          major: b.getByte(),
+          minor: b.getByte()
+        },
+        length: b.getInt16(),
+        fragment: forge.util.createBuffer(),
+        ready: false
+      };
+
+      // check record version
+      var compatibleVersion = (c.record.version.major === c.version.major);
+      if(compatibleVersion && c.session && c.session.version) {
+        // session version already set, require same minor version
+        compatibleVersion = (c.record.version.minor === c.version.minor);
+      }
+      if(!compatibleVersion) {
+        c.error(c, {
+          message: 'Incompatible TLS version.',
+          send: true,
+          alert: {
+            level: tls.Alert.Level.fatal,
+            description: tls.Alert.Description.protocol_version
+          }
+        });
+      }
+    }
+
+    return rval;
+  };
+
+  /**
+   * Reads the next record's contents and appends its message to any
+   * previously fragmented message.
+   *
+   * @param c the TLS connection with the next record.
+   *
+   * @return 0 if the input data could be processed, otherwise the
+   *         number of bytes required for data to be processed.
+   */
+  var _readRecord = function(c) {
+    var rval = 0;
+
+    // ensure there is enough input data to get the entire record
+    var b = c.input;
+    var len = b.length();
+    if(len < c.record.length) {
+      // not enough data yet, return how much is required
+      rval = c.record.length - len;
+    } else {
+      // there is enough data to parse the pending record
+      // fill record fragment and compact input buffer
+      c.record.fragment.putBytes(b.getBytes(c.record.length));
+      b.compact();
+
+      // update record using current read state
+      var s = c.state.current.read;
+      if(s.update(c, c.record)) {
+        // see if there is a previously fragmented message that the
+        // new record's message fragment should be appended to
+        if(c.fragmented !== null) {
+          // if the record type matches a previously fragmented
+          // record, append the record fragment to it
+          if(c.fragmented.type === c.record.type) {
+            // concatenate record fragments
+            c.fragmented.fragment.putBuffer(c.record.fragment);
+            c.record = c.fragmented;
+          } else {
+            // error, invalid fragmented record
+            c.error(c, {
+              message: 'Invalid fragmented record.',
+              send: true,
+              alert: {
+                level: tls.Alert.Level.fatal,
+                description:
+                  tls.Alert.Description.unexpected_message
+              }
+            });
+          }
+        }
+
+        // record is now ready
+        c.record.ready = true;
+      }
+    }
+
+    return rval;
+  };
+
+  /**
+   * Performs a handshake using the TLS Handshake Protocol, as a client.
+   *
+   * This method should only be called if the connection is in client mode.
+   *
+   * @param sessionId the session ID to use, null to start a new one.
+   */
+  c.handshake = function(sessionId) {
+    // error to call this in non-client mode
+    if(c.entity !== tls.ConnectionEnd.client) {
+      // not fatal error
+      c.error(c, {
+        message: 'Cannot initiate handshake as a server.',
+        fatal: false
+      });
+    } else if(c.handshaking) {
+      // handshake is already in progress, fail but not fatal error
+      c.error(c, {
+        message: 'Handshake already in progress.',
+        fatal: false
+      });
+    } else {
+      // clear fail flag on reuse
+      if(c.fail && !c.open && c.handshakes === 0) {
+        c.fail = false;
+      }
+
+      // now handshaking
+      c.handshaking = true;
+
+      // default to blank (new session)
+      sessionId = sessionId || '';
+
+      // if a session ID was specified, try to find it in the cache
+      var session = null;
+      if(sessionId.length > 0) {
+        if(c.sessionCache) {
+          session = c.sessionCache.getSession(sessionId);
+        }
+
+        // matching session not found in cache, clear session ID
+        if(session === null) {
+          sessionId = '';
+        }
+      }
+
+      // no session given, grab a session from the cache, if available
+      if(sessionId.length === 0 && c.sessionCache) {
+        session = c.sessionCache.getSession();
+        if(session !== null) {
+          sessionId = session.id;
+        }
+      }
+
+      // set up session
+      c.session = {
+        id: sessionId,
+        version: null,
+        cipherSuite: null,
+        compressionMethod: null,
+        serverCertificate: null,
+        certificateRequest: null,
+        clientCertificate: null,
+        sp: {},
+        md5: forge.md.md5.create(),
+        sha1: forge.md.sha1.create()
+      };
+
+      // use existing session information
+      if(session) {
+        // only update version on connection, session version not yet set
+        c.version = session.version;
+        c.session.sp = session.sp;
+      }
+
+      // generate new client random
+      c.session.sp.client_random = tls.createRandom().getBytes();
+
+      // connection now open
+      c.open = true;
+
+      // send hello
+      tls.queue(c, tls.createRecord(c, {
+        type: tls.ContentType.handshake,
+        data: tls.createClientHello(c)
+      }));
+      tls.flush(c);
+    }
+  };
+
+  /**
+   * Called when TLS protocol data has been received from somewhere and should
+   * be processed by the TLS engine.
+   *
+   * @param data the TLS protocol data, as a string, to process.
+   *
+   * @return 0 if the data could be processed, otherwise the number of bytes
+   *         required for data to be processed.
+   */
+  c.process = function(data) {
+    var rval = 0;
+
+    // buffer input data
+    if(data) {
+      c.input.putBytes(data);
+    }
+
+    // process next record if no failure, process will be called after
+    // each record is handled (since handling can be asynchronous)
+    if(!c.fail) {
+      // reset record if ready and now empty
+      if(c.record !== null &&
+        c.record.ready && c.record.fragment.isEmpty()) {
+        c.record = null;
+      }
+
+      // if there is no pending record, try to read record header
+      if(c.record === null) {
+        rval = _readRecordHeader(c);
+      }
+
+      // read the next record (if record not yet ready)
+      if(!c.fail && c.record !== null && !c.record.ready) {
+        rval = _readRecord(c);
+      }
+
+      // record ready to be handled, update engine state
+      if(!c.fail && c.record !== null && c.record.ready) {
+        _update(c, c.record);
+      }
+    }
+
+    return rval;
+  };
+
+  /**
+   * Requests that application data be packaged into a TLS record. The
+   * tlsDataReady handler will be called when the TLS record(s) have been
+   * prepared.
+   *
+   * @param data the application data, as a raw 'binary' encoded string, to
+   *          be sent; to send utf-16/utf-8 string data, use the return value
+   *          of util.encodeUtf8(str).
+   *
+   * @return true on success, false on failure.
+   */
+  c.prepare = function(data) {
+    tls.queue(c, tls.createRecord(c, {
+      type: tls.ContentType.application_data,
+      data: forge.util.createBuffer(data)
+    }));
+    return tls.flush(c);
+  };
+
+  /**
+   * Requests that a heartbeat request be packaged into a TLS record for
+   * transmission. The tlsDataReady handler will be called when TLS record(s)
+   * have been prepared.
+   *
+   * When a heartbeat response has been received, the heartbeatReceived
+   * handler will be called with the matching payload. This handler can
+   * be used to clear a retransmission timer, etc.
+   *
+   * @param payload the heartbeat data to send as the payload in the message.
+   * @param [payloadLength] the payload length to use, defaults to the
+   *          actual payload length.
+   *
+   * @return true on success, false on failure.
+   */
+  c.prepareHeartbeatRequest = function(payload, payloadLength) {
+    if(payload instanceof forge.util.ByteBuffer) {
+      payload = payload.bytes();
+    }
+    if(typeof payloadLength === 'undefined') {
+      payloadLength = payload.length;
+    }
+    c.expectedHeartbeatPayload = payload;
+    tls.queue(c, tls.createRecord(c, {
+      type: tls.ContentType.heartbeat,
+      data: tls.createHeartbeat(
+        tls.HeartbeatMessageType.heartbeat_request, payload, payloadLength)
+    }));
+    return tls.flush(c);
+  };
+
+  /**
+   * Closes the connection (sends a close_notify alert).
+   *
+   * @param clearFail true to clear the fail flag (default: true).
+   */
+  c.close = function(clearFail) {
+    // save session if connection didn't fail
+    if(!c.fail && c.sessionCache && c.session) {
+      // only need to preserve session ID, version, and security params
+      var session = {
+        id: c.session.id,
+        version: c.session.version,
+        sp: c.session.sp
+      };
+      session.sp.keys = null;
+      c.sessionCache.setSession(session.id, session);
+    }
+
+    if(c.open) {
+      // connection no longer open, clear input
+      c.open = false;
+      c.input.clear();
+
+      // if connected or handshaking, send an alert
+      if(c.isConnected || c.handshaking) {
+        c.isConnected = c.handshaking = false;
+
+        // send close_notify alert
+        tls.queue(c, tls.createAlert(c, {
+          level: tls.Alert.Level.warning,
+          description: tls.Alert.Description.close_notify
+        }));
+        tls.flush(c);
+      }
+
+      // call handler
+      c.closed(c);
+    }
+
+    // reset TLS connection, do not clear fail flag
+    c.reset(clearFail);
+  };
+
+  return c;
+};
+
+/* TLS API */
+forge.tls = forge.tls || {};
+
+// expose non-functions
+for(var key in tls) {
+  if(typeof tls[key] !== 'function') {
+    forge.tls[key] = tls[key];
+  }
+}
+
+// expose prf_tls1 for testing
+forge.tls.prf_tls1 = prf_TLS1;
+
+// expose sha1 hmac method
+forge.tls.hmac_sha1 = hmac_sha1;
+
+// expose session cache creation
+forge.tls.createSessionCache = tls.createSessionCache;
+
+/**
+ * Creates a new TLS connection. This does not make any assumptions about the
+ * transport layer that TLS is working on top of, ie: it does not assume there
+ * is a TCP/IP connection or establish one. A TLS connection is totally
+ * abstracted away from the layer is runs on top of, it merely establishes a
+ * secure channel between a client" and a "server".
+ *
+ * A TLS connection contains 4 connection states: pending read and write, and
+ * current read and write.
+ *
+ * At initialization, the current read and write states will be null. Only once
+ * the security parameters have been set and the keys have been generated can
+ * the pending states be converted into current states. Current states will be
+ * updated for each record processed.
+ *
+ * A custom certificate verify callback may be provided to check information
+ * like the common name on the server's certificate. It will be called for
+ * every certificate in the chain. It has the following signature:
+ *
+ * variable func(c, certs, index, preVerify)
+ * Where:
+ * c         The TLS connection
+ * verified  Set to true if certificate was verified, otherwise the alert
+ *           tls.Alert.Description for why the certificate failed.
+ * depth     The current index in the chain, where 0 is the server's cert.
+ * certs     The certificate chain, *NOTE* if the server was anonymous then
+ *           the chain will be empty.
+ *
+ * The function returns true on success and on failure either the appropriate
+ * tls.Alert.Description or an object with 'alert' set to the appropriate
+ * tls.Alert.Description and 'message' set to a custom error message. If true
+ * is not returned then the connection will abort using, in order of
+ * availability, first the returned alert description, second the preVerify
+ * alert description, and lastly the default 'bad_certificate'.
+ *
+ * There are three callbacks that can be used to make use of client-side
+ * certificates where each takes the TLS connection as the first parameter:
+ *
+ * getCertificate(conn, hint)
+ *   The second parameter is a hint as to which certificate should be
+ *   returned. If the connection entity is a client, then the hint will be
+ *   the CertificateRequest message from the server that is part of the
+ *   TLS protocol. If the connection entity is a server, then it will be
+ *   the servername list provided via an SNI extension the ClientHello, if
+ *   one was provided (empty array if not). The hint can be examined to
+ *   determine which certificate to use (advanced). Most implementations
+ *   will just return a certificate. The return value must be a
+ *   PEM-formatted certificate or an array of PEM-formatted certificates
+ *   that constitute a certificate chain, with the first in the array/chain
+ *   being the client's certificate.
+ * getPrivateKey(conn, certificate)
+ *   The second parameter is an forge.pki X.509 certificate object that
+ *   is associated with the requested private key. The return value must
+ *   be a PEM-formatted private key.
+ * getSignature(conn, bytes, callback)
+ *   This callback can be used instead of getPrivateKey if the private key
+ *   is not directly accessible in javascript or should not be. For
+ *   instance, a secure external web service could provide the signature
+ *   in exchange for appropriate credentials. The second parameter is a
+ *   string of bytes to be signed that are part of the TLS protocol. These
+ *   bytes are used to verify that the private key for the previously
+ *   provided client-side certificate is accessible to the client. The
+ *   callback is a function that takes 2 parameters, the TLS connection
+ *   and the RSA encrypted (signed) bytes as a string. This callback must
+ *   be called once the signature is ready.
+ *
+ * @param options the options for this connection:
+ *   server: true if the connection is server-side, false for client.
+ *   sessionId: a session ID to reuse, null for a new connection.
+ *   caStore: an array of certificates to trust.
+ *   sessionCache: a session cache to use.
+ *   cipherSuites: an optional array of cipher suites to use,
+ *     see tls.CipherSuites.
+ *   connected: function(conn) called when the first handshake completes.
+ *   virtualHost: the virtual server name to use in a TLS SNI extension.
+ *   verifyClient: true to require a client certificate in server mode,
+ *     'optional' to request one, false not to (default: false).
+ *   verify: a handler used to custom verify certificates in the chain.
+ *   getCertificate: an optional callback used to get a certificate or
+ *     a chain of certificates (as an array).
+ *   getPrivateKey: an optional callback used to get a private key.
+ *   getSignature: an optional callback used to get a signature.
+ *   tlsDataReady: function(conn) called when TLS protocol data has been
+ *     prepared and is ready to be used (typically sent over a socket
+ *     connection to its destination), read from conn.tlsData buffer.
+ *   dataReady: function(conn) called when application data has
+ *     been parsed from a TLS record and should be consumed by the
+ *     application, read from conn.data buffer.
+ *   closed: function(conn) called when the connection has been closed.
+ *   error: function(conn, error) called when there was an error.
+ *   deflate: function(inBytes) if provided, will deflate TLS records using
+ *     the deflate algorithm if the server supports it.
+ *   inflate: function(inBytes) if provided, will inflate TLS records using
+ *     the deflate algorithm if the server supports it.
+ *
+ * @return the new TLS connection.
+ */
+forge.tls.createConnection = tls.createConnection;
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'tls';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define([
+  'require',
+  'module',
+  './asn1',
+  './hmac',
+  './md',
+  './pem',
+  './pki',
+  './random',
+  './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/tlssocket.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/tlssocket.js
new file mode 100644
index 0000000..9a00ea2
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/tlssocket.js
@@ -0,0 +1,304 @@
+/**
+ * Socket wrapping functions for TLS.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2009-2012 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+/**
+ * Wraps a forge.net socket with a TLS layer.
+ *
+ * @param options:
+ *   sessionId: a session ID to reuse, null for a new connection if no session
+ *     cache is provided or it is empty.
+ *   caStore: an array of certificates to trust.
+ *   sessionCache: a session cache to use.
+ *   cipherSuites: an optional array of cipher suites to use, see
+ *     tls.CipherSuites.
+ *   socket: the socket to wrap.
+ *   virtualHost: the virtual server name to use in a TLS SNI extension.
+ *   verify: a handler used to custom verify certificates in the chain.
+ *   getCertificate: an optional callback used to get a certificate.
+ *   getPrivateKey: an optional callback used to get a private key.
+ *   getSignature: an optional callback used to get a signature.
+ *   deflate: function(inBytes) if provided, will deflate TLS records using
+ *     the deflate algorithm if the server supports it.
+ *   inflate: function(inBytes) if provided, will inflate TLS records using
+ *     the deflate algorithm if the server supports it.
+ *
+ * @return the TLS-wrapped socket.
+ */
+forge.tls.wrapSocket = function(options) {
+  // get raw socket
+  var socket = options.socket;
+
+  // create TLS socket
+  var tlsSocket = {
+    id: socket.id,
+    // set handlers
+    connected: socket.connected || function(e){},
+    closed: socket.closed || function(e){},
+    data: socket.data || function(e){},
+    error: socket.error || function(e){}
+  };
+
+  // create TLS connection
+  var c = forge.tls.createConnection({
+    server: false,
+    sessionId: options.sessionId || null,
+    caStore: options.caStore || [],
+    sessionCache: options.sessionCache || null,
+    cipherSuites: options.cipherSuites || null,
+    virtualHost: options.virtualHost,
+    verify: options.verify,
+    getCertificate: options.getCertificate,
+    getPrivateKey: options.getPrivateKey,
+    getSignature: options.getSignature,
+    deflate: options.deflate,
+    inflate: options.inflate,
+    connected: function(c) {
+      // first handshake complete, call handler
+      if(c.handshakes === 1) {
+        tlsSocket.connected({
+          id: socket.id,
+          type: 'connect',
+          bytesAvailable: c.data.length()
+        });
+      }
+    },
+    tlsDataReady: function(c) {
+      // send TLS data over socket
+      return socket.send(c.tlsData.getBytes());
+    },
+    dataReady: function(c) {
+      // indicate application data is ready
+      tlsSocket.data({
+        id: socket.id,
+        type: 'socketData',
+        bytesAvailable: c.data.length()
+      });
+    },
+    closed: function(c) {
+      // close socket
+      socket.close();
+    },
+    error: function(c, e) {
+      // send error, close socket
+      tlsSocket.error({
+        id: socket.id,
+        type: 'tlsError',
+        message: e.message,
+        bytesAvailable: 0,
+        error: e
+      });
+      socket.close();
+    }
+  });
+
+  // handle doing handshake after connecting
+  socket.connected = function(e) {
+    c.handshake(options.sessionId);
+  };
+
+  // handle closing TLS connection
+  socket.closed = function(e) {
+    if(c.open && c.handshaking) {
+      // error
+      tlsSocket.error({
+        id: socket.id,
+        type: 'ioError',
+        message: 'Connection closed during handshake.',
+        bytesAvailable: 0
+      });
+    }
+    c.close();
+
+    // call socket handler
+    tlsSocket.closed({
+      id: socket.id,
+      type: 'close',
+      bytesAvailable: 0
+    });
+  };
+
+  // handle error on socket
+  socket.error = function(e) {
+    // error
+    tlsSocket.error({
+      id: socket.id,
+      type: e.type,
+      message: e.message,
+      bytesAvailable: 0
+    });
+    c.close();
+  };
+
+  // handle receiving raw TLS data from socket
+  var _requiredBytes = 0;
+  socket.data = function(e) {
+    // drop data if connection not open
+    if(!c.open) {
+      socket.receive(e.bytesAvailable);
+    } else {
+      // only receive if there are enough bytes available to
+      // process a record
+      if(e.bytesAvailable >= _requiredBytes) {
+        var count = Math.max(e.bytesAvailable, _requiredBytes);
+        var data = socket.receive(count);
+        if(data !== null) {
+          _requiredBytes = c.process(data);
+        }
+      }
+    }
+  };
+
+  /**
+   * Destroys this socket.
+   */
+  tlsSocket.destroy = function() {
+    socket.destroy();
+  };
+
+  /**
+   * Sets this socket's TLS session cache. This should be called before
+   * the socket is connected or after it is closed.
+   *
+   * The cache is an object mapping session IDs to internal opaque state.
+   * An application might need to change the cache used by a particular
+   * tlsSocket between connections if it accesses multiple TLS hosts.
+   *
+   * @param cache the session cache to use.
+   */
+  tlsSocket.setSessionCache = function(cache) {
+    c.sessionCache = tls.createSessionCache(cache);
+  };
+
+  /**
+   * Connects this socket.
+   *
+   * @param options:
+   *           host: the host to connect to.
+   *           port: the port to connect to.
+   *           policyPort: the policy port to use (if non-default), 0 to
+   *              use the flash default.
+   *           policyUrl: the policy file URL to use (instead of port).
+   */
+  tlsSocket.connect = function(options) {
+    socket.connect(options);
+  };
+
+  /**
+   * Closes this socket.
+   */
+  tlsSocket.close = function() {
+    c.close();
+  };
+
+  /**
+   * Determines if the socket is connected or not.
+   *
+   * @return true if connected, false if not.
+   */
+  tlsSocket.isConnected = function() {
+    return c.isConnected && socket.isConnected();
+  };
+
+  /**
+   * Writes bytes to this socket.
+   *
+   * @param bytes the bytes (as a string) to write.
+   *
+   * @return true on success, false on failure.
+   */
+  tlsSocket.send = function(bytes) {
+    return c.prepare(bytes);
+  };
+
+  /**
+   * Reads bytes from this socket (non-blocking). Fewer than the number of
+   * bytes requested may be read if enough bytes are not available.
+   *
+   * This method should be called from the data handler if there are enough
+   * bytes available. To see how many bytes are available, check the
+   * 'bytesAvailable' property on the event in the data handler or call the
+   * bytesAvailable() function on the socket. If the browser is msie, then the
+   * bytesAvailable() function should be used to avoid race conditions.
+   * Otherwise, using the property on the data handler's event may be quicker.
+   *
+   * @param count the maximum number of bytes to read.
+   *
+   * @return the bytes read (as a string) or null on error.
+   */
+  tlsSocket.receive = function(count) {
+    return c.data.getBytes(count);
+  };
+
+  /**
+   * Gets the number of bytes available for receiving on the socket.
+   *
+   * @return the number of bytes available for receiving.
+   */
+  tlsSocket.bytesAvailable = function() {
+    return c.data.length();
+  };
+
+  return tlsSocket;
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'tlssocket';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './tls'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/util.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/util.js
new file mode 100644
index 0000000..0290842
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/util.js
@@ -0,0 +1,2953 @@
+/**
+ * Utility functions for web applications.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+/* Utilities API */
+var util = forge.util = forge.util || {};
+
+// define setImmediate and nextTick
+if(typeof process === 'undefined' || !process.nextTick) {
+  if(typeof setImmediate === 'function') {
+    util.setImmediate = setImmediate;
+    util.nextTick = function(callback) {
+      return setImmediate(callback);
+    };
+  } else {
+    util.setImmediate = function(callback) {
+      setTimeout(callback, 0);
+    };
+    util.nextTick = util.setImmediate;
+  }
+} else {
+  util.nextTick = process.nextTick;
+  if(typeof setImmediate === 'function') {
+    util.setImmediate = setImmediate;
+  } else {
+    util.setImmediate = util.nextTick;
+  }
+}
+
+// define isArray
+util.isArray = Array.isArray || function(x) {
+  return Object.prototype.toString.call(x) === '[object Array]';
+};
+
+// define isArrayBuffer
+util.isArrayBuffer = function(x) {
+  return typeof ArrayBuffer !== 'undefined' && x instanceof ArrayBuffer;
+};
+
+// define isArrayBufferView
+var _arrayBufferViews = [];
+if(typeof DataView !== 'undefined') {
+  _arrayBufferViews.push(DataView);
+}
+if(typeof Int8Array !== 'undefined') {
+  _arrayBufferViews.push(Int8Array);
+}
+if(typeof Uint8Array !== 'undefined') {
+  _arrayBufferViews.push(Uint8Array);
+}
+if(typeof Uint8ClampedArray !== 'undefined') {
+  _arrayBufferViews.push(Uint8ClampedArray);
+}
+if(typeof Int16Array !== 'undefined') {
+  _arrayBufferViews.push(Int16Array);
+}
+if(typeof Uint16Array !== 'undefined') {
+  _arrayBufferViews.push(Uint16Array);
+}
+if(typeof Int32Array !== 'undefined') {
+  _arrayBufferViews.push(Int32Array);
+}
+if(typeof Uint32Array !== 'undefined') {
+  _arrayBufferViews.push(Uint32Array);
+}
+if(typeof Float32Array !== 'undefined') {
+  _arrayBufferViews.push(Float32Array);
+}
+if(typeof Float64Array !== 'undefined') {
+  _arrayBufferViews.push(Float64Array);
+}
+util.isArrayBufferView = function(x) {
+  for(var i = 0; i < _arrayBufferViews.length; ++i) {
+    if(x instanceof _arrayBufferViews[i]) {
+      return true;
+    }
+  }
+  return false;
+};
+
+// TODO: set ByteBuffer to best available backing
+util.ByteBuffer = ByteStringBuffer;
+
+/** Buffer w/BinaryString backing */
+
+/**
+ * Constructor for a binary string backed byte buffer.
+ *
+ * @param [b] the bytes to wrap (either encoded as string, one byte per
+ *          character, or as an ArrayBuffer or Typed Array).
+ */
+function ByteStringBuffer(b) {
+  // TODO: update to match DataBuffer API
+
+  // the data in this buffer
+  this.data = '';
+  // the pointer for reading from this buffer
+  this.read = 0;
+
+  if(typeof b === 'string') {
+    this.data = b;
+  } else if(util.isArrayBuffer(b) || util.isArrayBufferView(b)) {
+    // convert native buffer to forge buffer
+    // FIXME: support native buffers internally instead
+    var arr = new Uint8Array(b);
+    try {
+      this.data = String.fromCharCode.apply(null, arr);
+    } catch(e) {
+      for(var i = 0; i < arr.length; ++i) {
+        this.putByte(arr[i]);
+      }
+    }
+  } else if(b instanceof ByteStringBuffer ||
+    (typeof b === 'object' && typeof b.data === 'string' &&
+    typeof b.read === 'number')) {
+    // copy existing buffer
+    this.data = b.data;
+    this.read = b.read;
+  }
+
+  // used for v8 optimization
+  this._constructedStringLength = 0;
+}
+util.ByteStringBuffer = ByteStringBuffer;
+
+/* Note: This is an optimization for V8-based browsers. When V8 concatenates
+  a string, the strings are only joined logically using a "cons string" or
+  "constructed/concatenated string". These containers keep references to one
+  another and can result in very large memory usage. For example, if a 2MB
+  string is constructed by concatenating 4 bytes together at a time, the
+  memory usage will be ~44MB; so ~22x increase. The strings are only joined
+  together when an operation requiring their joining takes place, such as
+  substr(). This function is called when adding data to this buffer to ensure
+  these types of strings are periodically joined to reduce the memory
+  footprint. */
+var _MAX_CONSTRUCTED_STRING_LENGTH = 4096;
+util.ByteStringBuffer.prototype._optimizeConstructedString = function(x) {
+  this._constructedStringLength += x;
+  if(this._constructedStringLength > _MAX_CONSTRUCTED_STRING_LENGTH) {
+    // this substr() should cause the constructed string to join
+    this.data.substr(0, 1);
+    this._constructedStringLength = 0;
+  }
+};
+
+/**
+ * Gets the number of bytes in this buffer.
+ *
+ * @return the number of bytes in this buffer.
+ */
+util.ByteStringBuffer.prototype.length = function() {
+  return this.data.length - this.read;
+};
+
+/**
+ * Gets whether or not this buffer is empty.
+ *
+ * @return true if this buffer is empty, false if not.
+ */
+util.ByteStringBuffer.prototype.isEmpty = function() {
+  return this.length() <= 0;
+};
+
+/**
+ * Puts a byte in this buffer.
+ *
+ * @param b the byte to put.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.putByte = function(b) {
+  return this.putBytes(String.fromCharCode(b));
+};
+
+/**
+ * Puts a byte in this buffer N times.
+ *
+ * @param b the byte to put.
+ * @param n the number of bytes of value b to put.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.fillWithByte = function(b, n) {
+  b = String.fromCharCode(b);
+  var d = this.data;
+  while(n > 0) {
+    if(n & 1) {
+      d += b;
+    }
+    n >>>= 1;
+    if(n > 0) {
+      b += b;
+    }
+  }
+  this.data = d;
+  this._optimizeConstructedString(n);
+  return this;
+};
+
+/**
+ * Puts bytes in this buffer.
+ *
+ * @param bytes the bytes (as a UTF-8 encoded string) to put.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.putBytes = function(bytes) {
+  this.data += bytes;
+  this._optimizeConstructedString(bytes.length);
+  return this;
+};
+
+/**
+ * Puts a UTF-16 encoded string into this buffer.
+ *
+ * @param str the string to put.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.putString = function(str) {
+  return this.putBytes(util.encodeUtf8(str));
+};
+
+/**
+ * Puts a 16-bit integer in this buffer in big-endian order.
+ *
+ * @param i the 16-bit integer.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.putInt16 = function(i) {
+  return this.putBytes(
+    String.fromCharCode(i >> 8 & 0xFF) +
+    String.fromCharCode(i & 0xFF));
+};
+
+/**
+ * Puts a 24-bit integer in this buffer in big-endian order.
+ *
+ * @param i the 24-bit integer.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.putInt24 = function(i) {
+  return this.putBytes(
+    String.fromCharCode(i >> 16 & 0xFF) +
+    String.fromCharCode(i >> 8 & 0xFF) +
+    String.fromCharCode(i & 0xFF));
+};
+
+/**
+ * Puts a 32-bit integer in this buffer in big-endian order.
+ *
+ * @param i the 32-bit integer.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.putInt32 = function(i) {
+  return this.putBytes(
+    String.fromCharCode(i >> 24 & 0xFF) +
+    String.fromCharCode(i >> 16 & 0xFF) +
+    String.fromCharCode(i >> 8 & 0xFF) +
+    String.fromCharCode(i & 0xFF));
+};
+
+/**
+ * Puts a 16-bit integer in this buffer in little-endian order.
+ *
+ * @param i the 16-bit integer.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.putInt16Le = function(i) {
+  return this.putBytes(
+    String.fromCharCode(i & 0xFF) +
+    String.fromCharCode(i >> 8 & 0xFF));
+};
+
+/**
+ * Puts a 24-bit integer in this buffer in little-endian order.
+ *
+ * @param i the 24-bit integer.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.putInt24Le = function(i) {
+  return this.putBytes(
+    String.fromCharCode(i & 0xFF) +
+    String.fromCharCode(i >> 8 & 0xFF) +
+    String.fromCharCode(i >> 16 & 0xFF));
+};
+
+/**
+ * Puts a 32-bit integer in this buffer in little-endian order.
+ *
+ * @param i the 32-bit integer.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.putInt32Le = function(i) {
+  return this.putBytes(
+    String.fromCharCode(i & 0xFF) +
+    String.fromCharCode(i >> 8 & 0xFF) +
+    String.fromCharCode(i >> 16 & 0xFF) +
+    String.fromCharCode(i >> 24 & 0xFF));
+};
+
+/**
+ * Puts an n-bit integer in this buffer in big-endian order.
+ *
+ * @param i the n-bit integer.
+ * @param n the number of bits in the integer.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.putInt = function(i, n) {
+  var bytes = '';
+  do {
+    n -= 8;
+    bytes += String.fromCharCode((i >> n) & 0xFF);
+  } while(n > 0);
+  return this.putBytes(bytes);
+};
+
+/**
+ * Puts a signed n-bit integer in this buffer in big-endian order. Two's
+ * complement representation is used.
+ *
+ * @param i the n-bit integer.
+ * @param n the number of bits in the integer.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.putSignedInt = function(i, n) {
+  if(i < 0) {
+    i += 2 << (n - 1);
+  }
+  return this.putInt(i, n);
+};
+
+/**
+ * Puts the given buffer into this buffer.
+ *
+ * @param buffer the buffer to put into this one.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.putBuffer = function(buffer) {
+  return this.putBytes(buffer.getBytes());
+};
+
+/**
+ * Gets a byte from this buffer and advances the read pointer by 1.
+ *
+ * @return the byte.
+ */
+util.ByteStringBuffer.prototype.getByte = function() {
+  return this.data.charCodeAt(this.read++);
+};
+
+/**
+ * Gets a uint16 from this buffer in big-endian order and advances the read
+ * pointer by 2.
+ *
+ * @return the uint16.
+ */
+util.ByteStringBuffer.prototype.getInt16 = function() {
+  var rval = (
+    this.data.charCodeAt(this.read) << 8 ^
+    this.data.charCodeAt(this.read + 1));
+  this.read += 2;
+  return rval;
+};
+
+/**
+ * Gets a uint24 from this buffer in big-endian order and advances the read
+ * pointer by 3.
+ *
+ * @return the uint24.
+ */
+util.ByteStringBuffer.prototype.getInt24 = function() {
+  var rval = (
+    this.data.charCodeAt(this.read) << 16 ^
+    this.data.charCodeAt(this.read + 1) << 8 ^
+    this.data.charCodeAt(this.read + 2));
+  this.read += 3;
+  return rval;
+};
+
+/**
+ * Gets a uint32 from this buffer in big-endian order and advances the read
+ * pointer by 4.
+ *
+ * @return the word.
+ */
+util.ByteStringBuffer.prototype.getInt32 = function() {
+  var rval = (
+    this.data.charCodeAt(this.read) << 24 ^
+    this.data.charCodeAt(this.read + 1) << 16 ^
+    this.data.charCodeAt(this.read + 2) << 8 ^
+    this.data.charCodeAt(this.read + 3));
+  this.read += 4;
+  return rval;
+};
+
+/**
+ * Gets a uint16 from this buffer in little-endian order and advances the read
+ * pointer by 2.
+ *
+ * @return the uint16.
+ */
+util.ByteStringBuffer.prototype.getInt16Le = function() {
+  var rval = (
+    this.data.charCodeAt(this.read) ^
+    this.data.charCodeAt(this.read + 1) << 8);
+  this.read += 2;
+  return rval;
+};
+
+/**
+ * Gets a uint24 from this buffer in little-endian order and advances the read
+ * pointer by 3.
+ *
+ * @return the uint24.
+ */
+util.ByteStringBuffer.prototype.getInt24Le = function() {
+  var rval = (
+    this.data.charCodeAt(this.read) ^
+    this.data.charCodeAt(this.read + 1) << 8 ^
+    this.data.charCodeAt(this.read + 2) << 16);
+  this.read += 3;
+  return rval;
+};
+
+/**
+ * Gets a uint32 from this buffer in little-endian order and advances the read
+ * pointer by 4.
+ *
+ * @return the word.
+ */
+util.ByteStringBuffer.prototype.getInt32Le = function() {
+  var rval = (
+    this.data.charCodeAt(this.read) ^
+    this.data.charCodeAt(this.read + 1) << 8 ^
+    this.data.charCodeAt(this.read + 2) << 16 ^
+    this.data.charCodeAt(this.read + 3) << 24);
+  this.read += 4;
+  return rval;
+};
+
+/**
+ * Gets an n-bit integer from this buffer in big-endian order and advances the
+ * read pointer by n/8.
+ *
+ * @param n the number of bits in the integer.
+ *
+ * @return the integer.
+ */
+util.ByteStringBuffer.prototype.getInt = function(n) {
+  var rval = 0;
+  do {
+    rval = (rval << 8) + this.data.charCodeAt(this.read++);
+    n -= 8;
+  } while(n > 0);
+  return rval;
+};
+
+/**
+ * Gets a signed n-bit integer from this buffer in big-endian order, using
+ * two's complement, and advances the read pointer by n/8.
+ *
+ * @param n the number of bits in the integer.
+ *
+ * @return the integer.
+ */
+util.ByteStringBuffer.prototype.getSignedInt = function(n) {
+  var x = this.getInt(n);
+  var max = 2 << (n - 2);
+  if(x >= max) {
+    x -= max << 1;
+  }
+  return x;
+};
+
+/**
+ * Reads bytes out into a UTF-8 string and clears them from the buffer.
+ *
+ * @param count the number of bytes to read, undefined or null for all.
+ *
+ * @return a UTF-8 string of bytes.
+ */
+util.ByteStringBuffer.prototype.getBytes = function(count) {
+  var rval;
+  if(count) {
+    // read count bytes
+    count = Math.min(this.length(), count);
+    rval = this.data.slice(this.read, this.read + count);
+    this.read += count;
+  } else if(count === 0) {
+    rval = '';
+  } else {
+    // read all bytes, optimize to only copy when needed
+    rval = (this.read === 0) ? this.data : this.data.slice(this.read);
+    this.clear();
+  }
+  return rval;
+};
+
+/**
+ * Gets a UTF-8 encoded string of the bytes from this buffer without modifying
+ * the read pointer.
+ *
+ * @param count the number of bytes to get, omit to get all.
+ *
+ * @return a string full of UTF-8 encoded characters.
+ */
+util.ByteStringBuffer.prototype.bytes = function(count) {
+  return (typeof(count) === 'undefined' ?
+    this.data.slice(this.read) :
+    this.data.slice(this.read, this.read + count));
+};
+
+/**
+ * Gets a byte at the given index without modifying the read pointer.
+ *
+ * @param i the byte index.
+ *
+ * @return the byte.
+ */
+util.ByteStringBuffer.prototype.at = function(i) {
+  return this.data.charCodeAt(this.read + i);
+};
+
+/**
+ * Puts a byte at the given index without modifying the read pointer.
+ *
+ * @param i the byte index.
+ * @param b the byte to put.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.setAt = function(i, b) {
+  this.data = this.data.substr(0, this.read + i) +
+    String.fromCharCode(b) +
+    this.data.substr(this.read + i + 1);
+  return this;
+};
+
+/**
+ * Gets the last byte without modifying the read pointer.
+ *
+ * @return the last byte.
+ */
+util.ByteStringBuffer.prototype.last = function() {
+  return this.data.charCodeAt(this.data.length - 1);
+};
+
+/**
+ * Creates a copy of this buffer.
+ *
+ * @return the copy.
+ */
+util.ByteStringBuffer.prototype.copy = function() {
+  var c = util.createBuffer(this.data);
+  c.read = this.read;
+  return c;
+};
+
+/**
+ * Compacts this buffer.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.compact = function() {
+  if(this.read > 0) {
+    this.data = this.data.slice(this.read);
+    this.read = 0;
+  }
+  return this;
+};
+
+/**
+ * Clears this buffer.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.clear = function() {
+  this.data = '';
+  this.read = 0;
+  return this;
+};
+
+/**
+ * Shortens this buffer by triming bytes off of the end of this buffer.
+ *
+ * @param count the number of bytes to trim off.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.truncate = function(count) {
+  var len = Math.max(0, this.length() - count);
+  this.data = this.data.substr(this.read, len);
+  this.read = 0;
+  return this;
+};
+
+/**
+ * Converts this buffer to a hexadecimal string.
+ *
+ * @return a hexadecimal string.
+ */
+util.ByteStringBuffer.prototype.toHex = function() {
+  var rval = '';
+  for(var i = this.read; i < this.data.length; ++i) {
+    var b = this.data.charCodeAt(i);
+    if(b < 16) {
+      rval += '0';
+    }
+    rval += b.toString(16);
+  }
+  return rval;
+};
+
+/**
+ * Converts this buffer to a UTF-16 string (standard JavaScript string).
+ *
+ * @return a UTF-16 string.
+ */
+util.ByteStringBuffer.prototype.toString = function() {
+  return util.decodeUtf8(this.bytes());
+};
+
+/** End Buffer w/BinaryString backing */
+
+
+/** Buffer w/UInt8Array backing */
+
+/**
+ * FIXME: Experimental. Do not use yet.
+ *
+ * Constructor for an ArrayBuffer-backed byte buffer.
+ *
+ * The buffer may be constructed from a string, an ArrayBuffer, DataView, or a
+ * TypedArray.
+ *
+ * If a string is given, its encoding should be provided as an option,
+ * otherwise it will default to 'binary'. A 'binary' string is encoded such
+ * that each character is one byte in length and size.
+ *
+ * If an ArrayBuffer, DataView, or TypedArray is given, it will be used
+ * *directly* without any copying. Note that, if a write to the buffer requires
+ * more space, the buffer will allocate a new backing ArrayBuffer to
+ * accommodate. The starting read and write offsets for the buffer may be
+ * given as options.
+ *
+ * @param [b] the initial bytes for this buffer.
+ * @param options the options to use:
+ *          [readOffset] the starting read offset to use (default: 0).
+ *          [writeOffset] the starting write offset to use (default: the
+ *            length of the first parameter).
+ *          [growSize] the minimum amount, in bytes, to grow the buffer by to
+ *            accommodate writes (default: 1024).
+ *          [encoding] the encoding ('binary', 'utf8', 'utf16', 'hex') for the
+ *            first parameter, if it is a string (default: 'binary').
+ */
+function DataBuffer(b, options) {
+  // default options
+  options = options || {};
+
+  // pointers for read from/write to buffer
+  this.read = options.readOffset || 0;
+  this.growSize = options.growSize || 1024;
+
+  var isArrayBuffer = util.isArrayBuffer(b);
+  var isArrayBufferView = util.isArrayBufferView(b);
+  if(isArrayBuffer || isArrayBufferView) {
+    // use ArrayBuffer directly
+    if(isArrayBuffer) {
+      this.data = new DataView(b);
+    } else {
+      // TODO: adjust read/write offset based on the type of view
+      // or specify that this must be done in the options ... that the
+      // offsets are byte-based
+      this.data = new DataView(b.buffer, b.byteOffset, b.byteLength);
+    }
+    this.write = ('writeOffset' in options ?
+      options.writeOffset : this.data.byteLength);
+    return;
+  }
+
+  // initialize to empty array buffer and add any given bytes using putBytes
+  this.data = new DataView(new ArrayBuffer(0));
+  this.write = 0;
+
+  if(b !== null && b !== undefined) {
+    this.putBytes(b);
+  }
+
+  if('writeOffset' in options) {
+    this.write = options.writeOffset;
+  }
+}
+util.DataBuffer = DataBuffer;
+
+/**
+ * Gets the number of bytes in this buffer.
+ *
+ * @return the number of bytes in this buffer.
+ */
+util.DataBuffer.prototype.length = function() {
+  return this.write - this.read;
+};
+
+/**
+ * Gets whether or not this buffer is empty.
+ *
+ * @return true if this buffer is empty, false if not.
+ */
+util.DataBuffer.prototype.isEmpty = function() {
+  return this.length() <= 0;
+};
+
+/**
+ * Ensures this buffer has enough empty space to accommodate the given number
+ * of bytes. An optional parameter may be given that indicates a minimum
+ * amount to grow the buffer if necessary. If the parameter is not given,
+ * the buffer will be grown by some previously-specified default amount
+ * or heuristic.
+ *
+ * @param amount the number of bytes to accommodate.
+ * @param [growSize] the minimum amount, in bytes, to grow the buffer by if
+ *          necessary.
+ */
+util.DataBuffer.prototype.accommodate = function(amount, growSize) {
+  if(this.length() >= amount) {
+    return this;
+  }
+  growSize = Math.max(growSize || this.growSize, amount);
+
+  // grow buffer
+  var src = new Uint8Array(
+    this.data.buffer, this.data.byteOffset, this.data.byteLength);
+  var dst = new Uint8Array(this.length() + growSize);
+  dst.set(src);
+  this.data = new DataView(dst.buffer);
+
+  return this;
+};
+
+/**
+ * Puts a byte in this buffer.
+ *
+ * @param b the byte to put.
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.putByte = function(b) {
+  this.accommodate(1);
+  this.data.setUint8(this.write++, b);
+  return this;
+};
+
+/**
+ * Puts a byte in this buffer N times.
+ *
+ * @param b the byte to put.
+ * @param n the number of bytes of value b to put.
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.fillWithByte = function(b, n) {
+  this.accommodate(n);
+  for(var i = 0; i < n; ++i) {
+    this.data.setUint8(b);
+  }
+  return this;
+};
+
+/**
+ * Puts bytes in this buffer. The bytes may be given as a string, an
+ * ArrayBuffer, a DataView, or a TypedArray.
+ *
+ * @param bytes the bytes to put.
+ * @param [encoding] the encoding for the first parameter ('binary', 'utf8',
+ *          'utf16', 'hex'), if it is a string (default: 'binary').
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.putBytes = function(bytes, encoding) {
+  if(util.isArrayBufferView(bytes)) {
+    var src = new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength);
+    var len = src.byteLength - src.byteOffset;
+    this.accommodate(len);
+    var dst = new Uint8Array(this.data.buffer, this.write);
+    dst.set(src);
+    this.write += len;
+    return this;
+  }
+
+  if(util.isArrayBuffer(bytes)) {
+    var src = new Uint8Array(bytes);
+    this.accommodate(src.byteLength);
+    var dst = new Uint8Array(this.data.buffer);
+    dst.set(src, this.write);
+    this.write += src.byteLength;
+    return this;
+  }
+
+  // bytes is a util.DataBuffer or equivalent
+  if(bytes instanceof util.DataBuffer ||
+    (typeof bytes === 'object' &&
+    typeof bytes.read === 'number' && typeof bytes.write === 'number' &&
+    util.isArrayBufferView(bytes.data))) {
+    var src = new Uint8Array(bytes.data.byteLength, bytes.read, bytes.length());
+    this.accommodate(src.byteLength);
+    var dst = new Uint8Array(bytes.data.byteLength, this.write);
+    dst.set(src);
+    this.write += src.byteLength;
+    return this;
+  }
+
+  if(bytes instanceof util.ByteStringBuffer) {
+    // copy binary string and process as the same as a string parameter below
+    bytes = bytes.data;
+    encoding = 'binary';
+  }
+
+  // string conversion
+  encoding = encoding || 'binary';
+  if(typeof bytes === 'string') {
+    var view;
+
+    // decode from string
+    if(encoding === 'hex') {
+      this.accommodate(Math.ceil(bytes.length / 2));
+      view = new Uint8Array(this.data.buffer, this.write);
+      this.write += util.binary.hex.decode(bytes, view, this.write);
+      return this;
+    }
+    if(encoding === 'base64') {
+      this.accommodate(Math.ceil(bytes.length / 4) * 3);
+      view = new Uint8Array(this.data.buffer, this.write);
+      this.write += util.binary.base64.decode(bytes, view, this.write);
+      return this;
+    }
+
+    // encode text as UTF-8 bytes
+    if(encoding === 'utf8') {
+      // encode as UTF-8 then decode string as raw binary
+      bytes = util.encodeUtf8(bytes);
+      encoding = 'binary';
+    }
+
+    // decode string as raw binary
+    if(encoding === 'binary' || encoding === 'raw') {
+      // one byte per character
+      this.accommodate(bytes.length);
+      view = new Uint8Array(this.data.buffer, this.write);
+      this.write += util.binary.raw.decode(view);
+      return this;
+    }
+
+    // encode text as UTF-16 bytes
+    if(encoding === 'utf16') {
+      // two bytes per character
+      this.accommodate(bytes.length * 2);
+      view = new Uint16Array(this.data.buffer, this.write);
+      this.write += util.text.utf16.encode(view);
+      return this;
+    }
+
+    throw new Error('Invalid encoding: ' + encoding);
+  }
+
+  throw Error('Invalid parameter: ' + bytes);
+};
+
+/**
+ * Puts the given buffer into this buffer.
+ *
+ * @param buffer the buffer to put into this one.
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.putBuffer = function(buffer) {
+  this.putBytes(buffer);
+  buffer.clear();
+  return this;
+};
+
+/**
+ * Puts a string into this buffer.
+ *
+ * @param str the string to put.
+ * @param [encoding] the encoding for the string (default: 'utf16').
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.putString = function(str) {
+  return this.putBytes(str, 'utf16');
+};
+
+/**
+ * Puts a 16-bit integer in this buffer in big-endian order.
+ *
+ * @param i the 16-bit integer.
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.putInt16 = function(i) {
+  this.accommodate(2);
+  this.data.setInt16(this.write, i);
+  this.write += 2;
+  return this;
+};
+
+/**
+ * Puts a 24-bit integer in this buffer in big-endian order.
+ *
+ * @param i the 24-bit integer.
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.putInt24 = function(i) {
+  this.accommodate(3);
+  this.data.setInt16(this.write, i >> 8 & 0xFFFF);
+  this.data.setInt8(this.write, i >> 16 & 0xFF);
+  this.write += 3;
+  return this;
+};
+
+/**
+ * Puts a 32-bit integer in this buffer in big-endian order.
+ *
+ * @param i the 32-bit integer.
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.putInt32 = function(i) {
+  this.accommodate(4);
+  this.data.setInt32(this.write, i);
+  this.write += 4;
+  return this;
+};
+
+/**
+ * Puts a 16-bit integer in this buffer in little-endian order.
+ *
+ * @param i the 16-bit integer.
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.putInt16Le = function(i) {
+  this.accommodate(2);
+  this.data.setInt16(this.write, i, true);
+  this.write += 2;
+  return this;
+};
+
+/**
+ * Puts a 24-bit integer in this buffer in little-endian order.
+ *
+ * @param i the 24-bit integer.
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.putInt24Le = function(i) {
+  this.accommodate(3);
+  this.data.setInt8(this.write, i >> 16 & 0xFF);
+  this.data.setInt16(this.write, i >> 8 & 0xFFFF, true);
+  this.write += 3;
+  return this;
+};
+
+/**
+ * Puts a 32-bit integer in this buffer in little-endian order.
+ *
+ * @param i the 32-bit integer.
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.putInt32Le = function(i) {
+  this.accommodate(4);
+  this.data.setInt32(this.write, i, true);
+  this.write += 4;
+  return this;
+};
+
+/**
+ * Puts an n-bit integer in this buffer in big-endian order.
+ *
+ * @param i the n-bit integer.
+ * @param n the number of bits in the integer.
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.putInt = function(i, n) {
+  this.accommodate(n / 8);
+  do {
+    n -= 8;
+    this.data.setInt8(this.write++, (i >> n) & 0xFF);
+  } while(n > 0);
+  return this;
+};
+
+/**
+ * Puts a signed n-bit integer in this buffer in big-endian order. Two's
+ * complement representation is used.
+ *
+ * @param i the n-bit integer.
+ * @param n the number of bits in the integer.
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.putSignedInt = function(i, n) {
+  this.accommodate(n / 8);
+  if(i < 0) {
+    i += 2 << (n - 1);
+  }
+  return this.putInt(i, n);
+};
+
+/**
+ * Gets a byte from this buffer and advances the read pointer by 1.
+ *
+ * @return the byte.
+ */
+util.DataBuffer.prototype.getByte = function() {
+  return this.data.getInt8(this.read++);
+};
+
+/**
+ * Gets a uint16 from this buffer in big-endian order and advances the read
+ * pointer by 2.
+ *
+ * @return the uint16.
+ */
+util.DataBuffer.prototype.getInt16 = function() {
+  var rval = this.data.getInt16(this.read);
+  this.read += 2;
+  return rval;
+};
+
+/**
+ * Gets a uint24 from this buffer in big-endian order and advances the read
+ * pointer by 3.
+ *
+ * @return the uint24.
+ */
+util.DataBuffer.prototype.getInt24 = function() {
+  var rval = (
+    this.data.getInt16(this.read) << 8 ^
+    this.data.getInt8(this.read + 2));
+  this.read += 3;
+  return rval;
+};
+
+/**
+ * Gets a uint32 from this buffer in big-endian order and advances the read
+ * pointer by 4.
+ *
+ * @return the word.
+ */
+util.DataBuffer.prototype.getInt32 = function() {
+  var rval = this.data.getInt32(this.read);
+  this.read += 4;
+  return rval;
+};
+
+/**
+ * Gets a uint16 from this buffer in little-endian order and advances the read
+ * pointer by 2.
+ *
+ * @return the uint16.
+ */
+util.DataBuffer.prototype.getInt16Le = function() {
+  var rval = this.data.getInt16(this.read, true);
+  this.read += 2;
+  return rval;
+};
+
+/**
+ * Gets a uint24 from this buffer in little-endian order and advances the read
+ * pointer by 3.
+ *
+ * @return the uint24.
+ */
+util.DataBuffer.prototype.getInt24Le = function() {
+  var rval = (
+    this.data.getInt8(this.read) ^
+    this.data.getInt16(this.read + 1, true) << 8);
+  this.read += 3;
+  return rval;
+};
+
+/**
+ * Gets a uint32 from this buffer in little-endian order and advances the read
+ * pointer by 4.
+ *
+ * @return the word.
+ */
+util.DataBuffer.prototype.getInt32Le = function() {
+  var rval = this.data.getInt32(this.read, true);
+  this.read += 4;
+  return rval;
+};
+
+/**
+ * Gets an n-bit integer from this buffer in big-endian order and advances the
+ * read pointer by n/8.
+ *
+ * @param n the number of bits in the integer.
+ *
+ * @return the integer.
+ */
+util.DataBuffer.prototype.getInt = function(n) {
+  var rval = 0;
+  do {
+    rval = (rval << 8) + this.data.getInt8(this.read++);
+    n -= 8;
+  } while(n > 0);
+  return rval;
+};
+
+/**
+ * Gets a signed n-bit integer from this buffer in big-endian order, using
+ * two's complement, and advances the read pointer by n/8.
+ *
+ * @param n the number of bits in the integer.
+ *
+ * @return the integer.
+ */
+util.DataBuffer.prototype.getSignedInt = function(n) {
+  var x = this.getInt(n);
+  var max = 2 << (n - 2);
+  if(x >= max) {
+    x -= max << 1;
+  }
+  return x;
+};
+
+/**
+ * Reads bytes out into a UTF-8 string and clears them from the buffer.
+ *
+ * @param count the number of bytes to read, undefined or null for all.
+ *
+ * @return a UTF-8 string of bytes.
+ */
+util.DataBuffer.prototype.getBytes = function(count) {
+  // TODO: deprecate this method, it is poorly named and
+  // this.toString('binary') replaces it
+  // add a toTypedArray()/toArrayBuffer() function
+  var rval;
+  if(count) {
+    // read count bytes
+    count = Math.min(this.length(), count);
+    rval = this.data.slice(this.read, this.read + count);
+    this.read += count;
+  } else if(count === 0) {
+    rval = '';
+  } else {
+    // read all bytes, optimize to only copy when needed
+    rval = (this.read === 0) ? this.data : this.data.slice(this.read);
+    this.clear();
+  }
+  return rval;
+};
+
+/**
+ * Gets a UTF-8 encoded string of the bytes from this buffer without modifying
+ * the read pointer.
+ *
+ * @param count the number of bytes to get, omit to get all.
+ *
+ * @return a string full of UTF-8 encoded characters.
+ */
+util.DataBuffer.prototype.bytes = function(count) {
+  // TODO: deprecate this method, it is poorly named, add "getString()"
+  return (typeof(count) === 'undefined' ?
+    this.data.slice(this.read) :
+    this.data.slice(this.read, this.read + count));
+};
+
+/**
+ * Gets a byte at the given index without modifying the read pointer.
+ *
+ * @param i the byte index.
+ *
+ * @return the byte.
+ */
+util.DataBuffer.prototype.at = function(i) {
+  return this.data.getUint8(this.read + i);
+};
+
+/**
+ * Puts a byte at the given index without modifying the read pointer.
+ *
+ * @param i the byte index.
+ * @param b the byte to put.
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.setAt = function(i, b) {
+  this.data.setUint8(i, b);
+  return this;
+};
+
+/**
+ * Gets the last byte without modifying the read pointer.
+ *
+ * @return the last byte.
+ */
+util.DataBuffer.prototype.last = function() {
+  return this.data.getUint8(this.write - 1);
+};
+
+/**
+ * Creates a copy of this buffer.
+ *
+ * @return the copy.
+ */
+util.DataBuffer.prototype.copy = function() {
+  return new util.DataBuffer(this);
+};
+
+/**
+ * Compacts this buffer.
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.compact = function() {
+  if(this.read > 0) {
+    var src = new Uint8Array(this.data.buffer, this.read);
+    var dst = new Uint8Array(src.byteLength);
+    dst.set(src);
+    this.data = new DataView(dst);
+    this.write -= this.read;
+    this.read = 0;
+  }
+  return this;
+};
+
+/**
+ * Clears this buffer.
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.clear = function() {
+  this.data = new DataView(new ArrayBuffer(0));
+  this.read = this.write = 0;
+  return this;
+};
+
+/**
+ * Shortens this buffer by triming bytes off of the end of this buffer.
+ *
+ * @param count the number of bytes to trim off.
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.truncate = function(count) {
+  this.write = Math.max(0, this.length() - count);
+  this.read = Math.min(this.read, this.write);
+  return this;
+};
+
+/**
+ * Converts this buffer to a hexadecimal string.
+ *
+ * @return a hexadecimal string.
+ */
+util.DataBuffer.prototype.toHex = function() {
+  var rval = '';
+  for(var i = this.read; i < this.data.byteLength; ++i) {
+    var b = this.data.getUint8(i);
+    if(b < 16) {
+      rval += '0';
+    }
+    rval += b.toString(16);
+  }
+  return rval;
+};
+
+/**
+ * Converts this buffer to a string, using the given encoding. If no
+ * encoding is given, 'utf8' (UTF-8) is used.
+ *
+ * @param [encoding] the encoding to use: 'binary', 'utf8', 'utf16', 'hex',
+ *          'base64' (default: 'utf8').
+ *
+ * @return a string representation of the bytes in this buffer.
+ */
+util.DataBuffer.prototype.toString = function(encoding) {
+  var view = new Uint8Array(this.data, this.read, this.length());
+  encoding = encoding || 'utf8';
+
+  // encode to string
+  if(encoding === 'binary' || encoding === 'raw') {
+    return util.binary.raw.encode(view);
+  }
+  if(encoding === 'hex') {
+    return util.binary.hex.encode(view);
+  }
+  if(encoding === 'base64') {
+    return util.binary.base64.encode(view);
+  }
+
+  // decode to text
+  if(encoding === 'utf8') {
+    return util.text.utf8.decode(view);
+  }
+  if(encoding === 'utf16') {
+    return util.text.utf16.decode(view);
+  }
+
+  throw new Error('Invalid encoding: ' + encoding);
+};
+
+/** End Buffer w/UInt8Array backing */
+
+
+/**
+ * Creates a buffer that stores bytes. A value may be given to put into the
+ * buffer that is either a string of bytes or a UTF-16 string that will
+ * be encoded using UTF-8 (to do the latter, specify 'utf8' as the encoding).
+ *
+ * @param [input] the bytes to wrap (as a string) or a UTF-16 string to encode
+ *          as UTF-8.
+ * @param [encoding] (default: 'raw', other: 'utf8').
+ */
+util.createBuffer = function(input, encoding) {
+  // TODO: deprecate, use new ByteBuffer() instead
+  encoding = encoding || 'raw';
+  if(input !== undefined && encoding === 'utf8') {
+    input = util.encodeUtf8(input);
+  }
+  return new util.ByteBuffer(input);
+};
+
+/**
+ * Fills a string with a particular value. If you want the string to be a byte
+ * string, pass in String.fromCharCode(theByte).
+ *
+ * @param c the character to fill the string with, use String.fromCharCode
+ *          to fill the string with a byte value.
+ * @param n the number of characters of value c to fill with.
+ *
+ * @return the filled string.
+ */
+util.fillString = function(c, n) {
+  var s = '';
+  while(n > 0) {
+    if(n & 1) {
+      s += c;
+    }
+    n >>>= 1;
+    if(n > 0) {
+      c += c;
+    }
+  }
+  return s;
+};
+
+/**
+ * Performs a per byte XOR between two byte strings and returns the result as a
+ * string of bytes.
+ *
+ * @param s1 first string of bytes.
+ * @param s2 second string of bytes.
+ * @param n the number of bytes to XOR.
+ *
+ * @return the XOR'd result.
+ */
+util.xorBytes = function(s1, s2, n) {
+  var s3 = '';
+  var b = '';
+  var t = '';
+  var i = 0;
+  var c = 0;
+  for(; n > 0; --n, ++i) {
+    b = s1.charCodeAt(i) ^ s2.charCodeAt(i);
+    if(c >= 10) {
+      s3 += t;
+      t = '';
+      c = 0;
+    }
+    t += String.fromCharCode(b);
+    ++c;
+  }
+  s3 += t;
+  return s3;
+};
+
+/**
+ * Converts a hex string into a 'binary' encoded string of bytes.
+ *
+ * @param hex the hexadecimal string to convert.
+ *
+ * @return the binary-encoded string of bytes.
+ */
+util.hexToBytes = function(hex) {
+  // TODO: deprecate: "Deprecated. Use util.binary.hex.decode instead."
+  var rval = '';
+  var i = 0;
+  if(hex.length & 1 == 1) {
+    // odd number of characters, convert first character alone
+    i = 1;
+    rval += String.fromCharCode(parseInt(hex[0], 16));
+  }
+  // convert 2 characters (1 byte) at a time
+  for(; i < hex.length; i += 2) {
+    rval += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
+  }
+  return rval;
+};
+
+/**
+ * Converts a 'binary' encoded string of bytes to hex.
+ *
+ * @param bytes the byte string to convert.
+ *
+ * @return the string of hexadecimal characters.
+ */
+util.bytesToHex = function(bytes) {
+  // TODO: deprecate: "Deprecated. Use util.binary.hex.encode instead."
+  return util.createBuffer(bytes).toHex();
+};
+
+/**
+ * Converts an 32-bit integer to 4-big-endian byte string.
+ *
+ * @param i the integer.
+ *
+ * @return the byte string.
+ */
+util.int32ToBytes = function(i) {
+  return (
+    String.fromCharCode(i >> 24 & 0xFF) +
+    String.fromCharCode(i >> 16 & 0xFF) +
+    String.fromCharCode(i >> 8 & 0xFF) +
+    String.fromCharCode(i & 0xFF));
+};
+
+// base64 characters, reverse mapping
+var _base64 =
+  'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
+var _base64Idx = [
+/*43 -43 = 0*/
+/*'+',  1,  2,  3,'/' */
+   62, -1, -1, -1, 63,
+
+/*'0','1','2','3','4','5','6','7','8','9' */
+   52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
+
+/*15, 16, 17,'=', 19, 20, 21 */
+  -1, -1, -1, 64, -1, -1, -1,
+
+/*65 - 43 = 22*/
+/*'A','B','C','D','E','F','G','H','I','J','K','L','M', */
+   0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12,
+
+/*'N','O','P','Q','R','S','T','U','V','W','X','Y','Z' */
+   13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
+
+/*91 - 43 = 48 */
+/*48, 49, 50, 51, 52, 53 */
+  -1, -1, -1, -1, -1, -1,
+
+/*97 - 43 = 54*/
+/*'a','b','c','d','e','f','g','h','i','j','k','l','m' */
+   26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
+
+/*'n','o','p','q','r','s','t','u','v','w','x','y','z' */
+   39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
+];
+
+/**
+ * Base64 encodes a 'binary' encoded string of bytes.
+ *
+ * @param input the binary encoded string of bytes to base64-encode.
+ * @param maxline the maximum number of encoded characters per line to use,
+ *          defaults to none.
+ *
+ * @return the base64-encoded output.
+ */
+util.encode64 = function(input, maxline) {
+  // TODO: deprecate: "Deprecated. Use util.binary.base64.encode instead."
+  var line = '';
+  var output = '';
+  var chr1, chr2, chr3;
+  var i = 0;
+  while(i < input.length) {
+    chr1 = input.charCodeAt(i++);
+    chr2 = input.charCodeAt(i++);
+    chr3 = input.charCodeAt(i++);
+
+    // encode 4 character group
+    line += _base64.charAt(chr1 >> 2);
+    line += _base64.charAt(((chr1 & 3) << 4) | (chr2 >> 4));
+    if(isNaN(chr2)) {
+      line += '==';
+    } else {
+      line += _base64.charAt(((chr2 & 15) << 2) | (chr3 >> 6));
+      line += isNaN(chr3) ? '=' : _base64.charAt(chr3 & 63);
+    }
+
+    if(maxline && line.length > maxline) {
+      output += line.substr(0, maxline) + '\r\n';
+      line = line.substr(maxline);
+    }
+  }
+  output += line;
+  return output;
+};
+
+/**
+ * Base64 decodes a string into a 'binary' encoded string of bytes.
+ *
+ * @param input the base64-encoded input.
+ *
+ * @return the binary encoded string.
+ */
+util.decode64 = function(input) {
+  // TODO: deprecate: "Deprecated. Use util.binary.base64.decode instead."
+
+  // remove all non-base64 characters
+  input = input.replace(/[^A-Za-z0-9\+\/\=]/g, '');
+
+  var output = '';
+  var enc1, enc2, enc3, enc4;
+  var i = 0;
+
+  while(i < input.length) {
+    enc1 = _base64Idx[input.charCodeAt(i++) - 43];
+    enc2 = _base64Idx[input.charCodeAt(i++) - 43];
+    enc3 = _base64Idx[input.charCodeAt(i++) - 43];
+    enc4 = _base64Idx[input.charCodeAt(i++) - 43];
+
+    output += String.fromCharCode((enc1 << 2) | (enc2 >> 4));
+    if(enc3 !== 64) {
+      // decoded at least 2 bytes
+      output += String.fromCharCode(((enc2 & 15) << 4) | (enc3 >> 2));
+      if(enc4 !== 64) {
+        // decoded 3 bytes
+        output += String.fromCharCode(((enc3 & 3) << 6) | enc4);
+      }
+    }
+  }
+
+  return output;
+};
+
+/**
+ * UTF-8 encodes the given UTF-16 encoded string (a standard JavaScript
+ * string). Non-ASCII characters will be encoded as multiple bytes according
+ * to UTF-8.
+ *
+ * @param str the string to encode.
+ *
+ * @return the UTF-8 encoded string.
+ */
+util.encodeUtf8 = function(str) {
+  return unescape(encodeURIComponent(str));
+};
+
+/**
+ * Decodes a UTF-8 encoded string into a UTF-16 string.
+ *
+ * @param str the string to decode.
+ *
+ * @return the UTF-16 encoded string (standard JavaScript string).
+ */
+util.decodeUtf8 = function(str) {
+  return decodeURIComponent(escape(str));
+};
+
+// binary encoding/decoding tools
+// FIXME: Experimental. Do not use yet.
+util.binary = {
+  raw: {},
+  hex: {},
+  base64: {}
+};
+
+/**
+ * Encodes a Uint8Array as a binary-encoded string. This encoding uses
+ * a value between 0 and 255 for each character.
+ *
+ * @param bytes the Uint8Array to encode.
+ *
+ * @return the binary-encoded string.
+ */
+util.binary.raw.encode = function(bytes) {
+  return String.fromCharCode.apply(null, bytes);
+};
+
+/**
+ * Decodes a binary-encoded string to a Uint8Array. This encoding uses
+ * a value between 0 and 255 for each character.
+ *
+ * @param str the binary-encoded string to decode.
+ * @param [output] an optional Uint8Array to write the output to; if it
+ *          is too small, an exception will be thrown.
+ * @param [offset] the start offset for writing to the output (default: 0).
+ *
+ * @return the Uint8Array or the number of bytes written if output was given.
+ */
+util.binary.raw.decode = function(str, output, offset) {
+  var out = output;
+  if(!out) {
+    out = new Uint8Array(str.length);
+  }
+  offset = offset || 0;
+  var j = offset;
+  for(var i = 0; i < str.length; ++i) {
+    out[j++] = str.charCodeAt(i);
+  }
+  return output ? (j - offset) : out;
+};
+
+/**
+ * Encodes a 'binary' string, ArrayBuffer, DataView, TypedArray, or
+ * ByteBuffer as a string of hexadecimal characters.
+ *
+ * @param bytes the bytes to convert.
+ *
+ * @return the string of hexadecimal characters.
+ */
+util.binary.hex.encode = util.bytesToHex;
+
+/**
+ * Decodes a hex-encoded string to a Uint8Array.
+ *
+ * @param hex the hexadecimal string to convert.
+ * @param [output] an optional Uint8Array to write the output to; if it
+ *          is too small, an exception will be thrown.
+ * @param [offset] the start offset for writing to the output (default: 0).
+ *
+ * @return the Uint8Array or the number of bytes written if output was given.
+ */
+util.binary.hex.decode = function(hex, output, offset) {
+  var out = output;
+  if(!out) {
+    out = new Uint8Array(Math.ceil(hex.length / 2));
+  }
+  offset = offset || 0;
+  var i = 0, j = offset;
+  if(hex.length & 1) {
+    // odd number of characters, convert first character alone
+    i = 1;
+    out[j++] = parseInt(hex[0], 16);
+  }
+  // convert 2 characters (1 byte) at a time
+  for(; i < hex.length; i += 2) {
+    out[j++] = parseInt(hex.substr(i, 2), 16);
+  }
+  return output ? (j - offset) : out;
+};
+
+/**
+ * Base64-encodes a Uint8Array.
+ *
+ * @param input the Uint8Array to encode.
+ * @param maxline the maximum number of encoded characters per line to use,
+ *          defaults to none.
+ *
+ * @return the base64-encoded output string.
+ */
+util.binary.base64.encode = function(input, maxline) {
+  var line = '';
+  var output = '';
+  var chr1, chr2, chr3;
+  var i = 0;
+  while(i < input.byteLength) {
+    chr1 = input[i++];
+    chr2 = input[i++];
+    chr3 = input[i++];
+
+    // encode 4 character group
+    line += _base64.charAt(chr1 >> 2);
+    line += _base64.charAt(((chr1 & 3) << 4) | (chr2 >> 4));
+    if(isNaN(chr2)) {
+      line += '==';
+    } else {
+      line += _base64.charAt(((chr2 & 15) << 2) | (chr3 >> 6));
+      line += isNaN(chr3) ? '=' : _base64.charAt(chr3 & 63);
+    }
+
+    if(maxline && line.length > maxline) {
+      output += line.substr(0, maxline) + '\r\n';
+      line = line.substr(maxline);
+    }
+  }
+  output += line;
+  return output;
+};
+
+/**
+ * Decodes a base64-encoded string to a Uint8Array.
+ *
+ * @param input the base64-encoded input string.
+ * @param [output] an optional Uint8Array to write the output to; if it
+ *          is too small, an exception will be thrown.
+ * @param [offset] the start offset for writing to the output (default: 0).
+ *
+ * @return the Uint8Array or the number of bytes written if output was given.
+ */
+util.binary.base64.decode = function(input, output, offset) {
+  var out = output;
+  if(!out) {
+    out = new Uint8Array(Math.ceil(input.length / 4) * 3);
+  }
+
+  // remove all non-base64 characters
+  input = input.replace(/[^A-Za-z0-9\+\/\=]/g, '');
+
+  offset = offset || 0;
+  var enc1, enc2, enc3, enc4;
+  var i = 0, j = offset;
+
+  while(i < input.length) {
+    enc1 = _base64Idx[input.charCodeAt(i++) - 43];
+    enc2 = _base64Idx[input.charCodeAt(i++) - 43];
+    enc3 = _base64Idx[input.charCodeAt(i++) - 43];
+    enc4 = _base64Idx[input.charCodeAt(i++) - 43];
+
+    out[j++] = (enc1 << 2) | (enc2 >> 4);
+    if(enc3 !== 64) {
+      // decoded at least 2 bytes
+      out[j++] = ((enc2 & 15) << 4) | (enc3 >> 2);
+      if(enc4 !== 64) {
+        // decoded 3 bytes
+        out[j++] = ((enc3 & 3) << 6) | enc4;
+      }
+    }
+  }
+  
+  // make sure result is the exact decoded length
+  return output ?
+         (j - offset) :
+         out.subarray(0, j);
+};
+
+// text encoding/decoding tools
+// FIXME: Experimental. Do not use yet.
+util.text = {
+  utf8: {},
+  utf16: {}
+};
+
+/**
+ * Encodes the given string as UTF-8 in a Uint8Array.
+ *
+ * @param str the string to encode.
+ * @param [output] an optional Uint8Array to write the output to; if it
+ *          is too small, an exception will be thrown.
+ * @param [offset] the start offset for writing to the output (default: 0).
+ *
+ * @return the Uint8Array or the number of bytes written if output was given.
+ */
+util.text.utf8.encode = function(str, output, offset) {
+  str = util.encodeUtf8(str);
+  var out = output;
+  if(!out) {
+    out = new Uint8Array(str.length);
+  }
+  offset = offset || 0;
+  var j = offset;
+  for(var i = 0; i < str.length; ++i) {
+    out[j++] = str.charCodeAt(i);
+  }
+  return output ? (j - offset) : out;
+};
+
+/**
+ * Decodes the UTF-8 contents from a Uint8Array.
+ *
+ * @param bytes the Uint8Array to decode.
+ *
+ * @return the resulting string.
+ */
+util.text.utf8.decode = function(bytes) {
+  return util.decodeUtf8(String.fromCharCode.apply(null, bytes));
+};
+
+/**
+ * Encodes the given string as UTF-16 in a Uint8Array.
+ *
+ * @param str the string to encode.
+ * @param [output] an optional Uint8Array to write the output to; if it
+ *          is too small, an exception will be thrown.
+ * @param [offset] the start offset for writing to the output (default: 0).
+ *
+ * @return the Uint8Array or the number of bytes written if output was given.
+ */
+util.text.utf16.encode = function(str, output, offset) {
+  var out = output;
+  if(!out) {
+    out = new Uint8Array(str.length);
+  }
+  var view = new Uint16Array(out);
+  offset = offset || 0;
+  var j = offset;
+  var k = offset;
+  for(var i = 0; i < str.length; ++i) {
+    view[k++] = str.charCodeAt(i);
+    j += 2;
+  }
+  return output ? (j - offset) : out;
+};
+
+/**
+ * Decodes the UTF-16 contents from a Uint8Array.
+ *
+ * @param bytes the Uint8Array to decode.
+ *
+ * @return the resulting string.
+ */
+util.text.utf16.decode = function(bytes) {
+  return String.fromCharCode.apply(null, new Uint16Array(bytes));
+};
+
+/**
+ * Deflates the given data using a flash interface.
+ *
+ * @param api the flash interface.
+ * @param bytes the data.
+ * @param raw true to return only raw deflate data, false to include zlib
+ *          header and trailer.
+ *
+ * @return the deflated data as a string.
+ */
+util.deflate = function(api, bytes, raw) {
+  bytes = util.decode64(api.deflate(util.encode64(bytes)).rval);
+
+  // strip zlib header and trailer if necessary
+  if(raw) {
+    // zlib header is 2 bytes (CMF,FLG) where FLG indicates that
+    // there is a 4-byte DICT (alder-32) block before the data if
+    // its 5th bit is set
+    var start = 2;
+    var flg = bytes.charCodeAt(1);
+    if(flg & 0x20) {
+      start = 6;
+    }
+    // zlib trailer is 4 bytes of adler-32
+    bytes = bytes.substring(start, bytes.length - 4);
+  }
+
+  return bytes;
+};
+
+/**
+ * Inflates the given data using a flash interface.
+ *
+ * @param api the flash interface.
+ * @param bytes the data.
+ * @param raw true if the incoming data has no zlib header or trailer and is
+ *          raw DEFLATE data.
+ *
+ * @return the inflated data as a string, null on error.
+ */
+util.inflate = function(api, bytes, raw) {
+  // TODO: add zlib header and trailer if necessary/possible
+  var rval = api.inflate(util.encode64(bytes)).rval;
+  return (rval === null) ? null : util.decode64(rval);
+};
+
+/**
+ * Sets a storage object.
+ *
+ * @param api the storage interface.
+ * @param id the storage ID to use.
+ * @param obj the storage object, null to remove.
+ */
+var _setStorageObject = function(api, id, obj) {
+  if(!api) {
+    throw new Error('WebStorage not available.');
+  }
+
+  var rval;
+  if(obj === null) {
+    rval = api.removeItem(id);
+  } else {
+    // json-encode and base64-encode object
+    obj = util.encode64(JSON.stringify(obj));
+    rval = api.setItem(id, obj);
+  }
+
+  // handle potential flash error
+  if(typeof(rval) !== 'undefined' && rval.rval !== true) {
+    var error = new Error(rval.error.message);
+    error.id = rval.error.id;
+    error.name = rval.error.name;
+    throw error;
+  }
+};
+
+/**
+ * Gets a storage object.
+ *
+ * @param api the storage interface.
+ * @param id the storage ID to use.
+ *
+ * @return the storage object entry or null if none exists.
+ */
+var _getStorageObject = function(api, id) {
+  if(!api) {
+    throw new Error('WebStorage not available.');
+  }
+
+  // get the existing entry
+  var rval = api.getItem(id);
+
+  /* Note: We check api.init because we can't do (api == localStorage)
+    on IE because of "Class doesn't support Automation" exception. Only
+    the flash api has an init method so this works too, but we need a
+    better solution in the future. */
+
+  // flash returns item wrapped in an object, handle special case
+  if(api.init) {
+    if(rval.rval === null) {
+      if(rval.error) {
+        var error = new Error(rval.error.message);
+        error.id = rval.error.id;
+        error.name = rval.error.name;
+        throw error;
+      }
+      // no error, but also no item
+      rval = null;
+    } else {
+      rval = rval.rval;
+    }
+  }
+
+  // handle decoding
+  if(rval !== null) {
+    // base64-decode and json-decode data
+    rval = JSON.parse(util.decode64(rval));
+  }
+
+  return rval;
+};
+
+/**
+ * Stores an item in local storage.
+ *
+ * @param api the storage interface.
+ * @param id the storage ID to use.
+ * @param key the key for the item.
+ * @param data the data for the item (any javascript object/primitive).
+ */
+var _setItem = function(api, id, key, data) {
+  // get storage object
+  var obj = _getStorageObject(api, id);
+  if(obj === null) {
+    // create a new storage object
+    obj = {};
+  }
+  // update key
+  obj[key] = data;
+
+  // set storage object
+  _setStorageObject(api, id, obj);
+};
+
+/**
+ * Gets an item from local storage.
+ *
+ * @param api the storage interface.
+ * @param id the storage ID to use.
+ * @param key the key for the item.
+ *
+ * @return the item.
+ */
+var _getItem = function(api, id, key) {
+  // get storage object
+  var rval = _getStorageObject(api, id);
+  if(rval !== null) {
+    // return data at key
+    rval = (key in rval) ? rval[key] : null;
+  }
+
+  return rval;
+};
+
+/**
+ * Removes an item from local storage.
+ *
+ * @param api the storage interface.
+ * @param id the storage ID to use.
+ * @param key the key for the item.
+ */
+var _removeItem = function(api, id, key) {
+  // get storage object
+  var obj = _getStorageObject(api, id);
+  if(obj !== null && key in obj) {
+    // remove key
+    delete obj[key];
+
+    // see if entry has no keys remaining
+    var empty = true;
+    for(var prop in obj) {
+      empty = false;
+      break;
+    }
+    if(empty) {
+      // remove entry entirely if no keys are left
+      obj = null;
+    }
+
+    // set storage object
+    _setStorageObject(api, id, obj);
+  }
+};
+
+/**
+ * Clears the local disk storage identified by the given ID.
+ *
+ * @param api the storage interface.
+ * @param id the storage ID to use.
+ */
+var _clearItems = function(api, id) {
+  _setStorageObject(api, id, null);
+};
+
+/**
+ * Calls a storage function.
+ *
+ * @param func the function to call.
+ * @param args the arguments for the function.
+ * @param location the location argument.
+ *
+ * @return the return value from the function.
+ */
+var _callStorageFunction = function(func, args, location) {
+  var rval = null;
+
+  // default storage types
+  if(typeof(location) === 'undefined') {
+    location = ['web', 'flash'];
+  }
+
+  // apply storage types in order of preference
+  var type;
+  var done = false;
+  var exception = null;
+  for(var idx in location) {
+    type = location[idx];
+    try {
+      if(type === 'flash' || type === 'both') {
+        if(args[0] === null) {
+          throw new Error('Flash local storage not available.');
+        }
+        rval = func.apply(this, args);
+        done = (type === 'flash');
+      }
+      if(type === 'web' || type === 'both') {
+        args[0] = localStorage;
+        rval = func.apply(this, args);
+        done = true;
+      }
+    } catch(ex) {
+      exception = ex;
+    }
+    if(done) {
+      break;
+    }
+  }
+
+  if(!done) {
+    throw exception;
+  }
+
+  return rval;
+};
+
+/**
+ * Stores an item on local disk.
+ *
+ * The available types of local storage include 'flash', 'web', and 'both'.
+ *
+ * The type 'flash' refers to flash local storage (SharedObject). In order
+ * to use flash local storage, the 'api' parameter must be valid. The type
+ * 'web' refers to WebStorage, if supported by the browser. The type 'both'
+ * refers to storing using both 'flash' and 'web', not just one or the
+ * other.
+ *
+ * The location array should list the storage types to use in order of
+ * preference:
+ *
+ * ['flash']: flash only storage
+ * ['web']: web only storage
+ * ['both']: try to store in both
+ * ['flash','web']: store in flash first, but if not available, 'web'
+ * ['web','flash']: store in web first, but if not available, 'flash'
+ *
+ * The location array defaults to: ['web', 'flash']
+ *
+ * @param api the flash interface, null to use only WebStorage.
+ * @param id the storage ID to use.
+ * @param key the key for the item.
+ * @param data the data for the item (any javascript object/primitive).
+ * @param location an array with the preferred types of storage to use.
+ */
+util.setItem = function(api, id, key, data, location) {
+  _callStorageFunction(_setItem, arguments, location);
+};
+
+/**
+ * Gets an item on local disk.
+ *
+ * Set setItem() for details on storage types.
+ *
+ * @param api the flash interface, null to use only WebStorage.
+ * @param id the storage ID to use.
+ * @param key the key for the item.
+ * @param location an array with the preferred types of storage to use.
+ *
+ * @return the item.
+ */
+util.getItem = function(api, id, key, location) {
+  return _callStorageFunction(_getItem, arguments, location);
+};
+
+/**
+ * Removes an item on local disk.
+ *
+ * Set setItem() for details on storage types.
+ *
+ * @param api the flash interface.
+ * @param id the storage ID to use.
+ * @param key the key for the item.
+ * @param location an array with the preferred types of storage to use.
+ */
+util.removeItem = function(api, id, key, location) {
+  _callStorageFunction(_removeItem, arguments, location);
+};
+
+/**
+ * Clears the local disk storage identified by the given ID.
+ *
+ * Set setItem() for details on storage types.
+ *
+ * @param api the flash interface if flash is available.
+ * @param id the storage ID to use.
+ * @param location an array with the preferred types of storage to use.
+ */
+util.clearItems = function(api, id, location) {
+  _callStorageFunction(_clearItems, arguments, location);
+};
+
+/**
+ * Parses the scheme, host, and port from an http(s) url.
+ *
+ * @param str the url string.
+ *
+ * @return the parsed url object or null if the url is invalid.
+ */
+util.parseUrl = function(str) {
+  // FIXME: this regex looks a bit broken
+  var regex = /^(https?):\/\/([^:&^\/]*):?(\d*)(.*)$/g;
+  regex.lastIndex = 0;
+  var m = regex.exec(str);
+  var url = (m === null) ? null : {
+    full: str,
+    scheme: m[1],
+    host: m[2],
+    port: m[3],
+    path: m[4]
+  };
+  if(url) {
+    url.fullHost = url.host;
+    if(url.port) {
+      if(url.port !== 80 && url.scheme === 'http') {
+        url.fullHost += ':' + url.port;
+      } else if(url.port !== 443 && url.scheme === 'https') {
+        url.fullHost += ':' + url.port;
+      }
+    } else if(url.scheme === 'http') {
+      url.port = 80;
+    } else if(url.scheme === 'https') {
+      url.port = 443;
+    }
+    url.full = url.scheme + '://' + url.fullHost;
+  }
+  return url;
+};
+
+/* Storage for query variables */
+var _queryVariables = null;
+
+/**
+ * Returns the window location query variables. Query is parsed on the first
+ * call and the same object is returned on subsequent calls. The mapping
+ * is from keys to an array of values. Parameters without values will have
+ * an object key set but no value added to the value array. Values are
+ * unescaped.
+ *
+ * ...?k1=v1&k2=v2:
+ * {
+ *   "k1": ["v1"],
+ *   "k2": ["v2"]
+ * }
+ *
+ * ...?k1=v1&k1=v2:
+ * {
+ *   "k1": ["v1", "v2"]
+ * }
+ *
+ * ...?k1=v1&k2:
+ * {
+ *   "k1": ["v1"],
+ *   "k2": []
+ * }
+ *
+ * ...?k1=v1&k1:
+ * {
+ *   "k1": ["v1"]
+ * }
+ *
+ * ...?k1&k1:
+ * {
+ *   "k1": []
+ * }
+ *
+ * @param query the query string to parse (optional, default to cached
+ *          results from parsing window location search query).
+ *
+ * @return object mapping keys to variables.
+ */
+util.getQueryVariables = function(query) {
+  var parse = function(q) {
+    var rval = {};
+    var kvpairs = q.split('&');
+    for(var i = 0; i < kvpairs.length; i++) {
+      var pos = kvpairs[i].indexOf('=');
+      var key;
+      var val;
+      if(pos > 0) {
+        key = kvpairs[i].substring(0, pos);
+        val = kvpairs[i].substring(pos + 1);
+      } else {
+        key = kvpairs[i];
+        val = null;
+      }
+      if(!(key in rval)) {
+        rval[key] = [];
+      }
+      // disallow overriding object prototype keys
+      if(!(key in Object.prototype) && val !== null) {
+        rval[key].push(unescape(val));
+      }
+    }
+    return rval;
+  };
+
+   var rval;
+   if(typeof(query) === 'undefined') {
+     // set cached variables if needed
+     if(_queryVariables === null) {
+       if(typeof(window) === 'undefined') {
+          // no query variables available
+          _queryVariables = {};
+       } else {
+          // parse window search query
+          _queryVariables = parse(window.location.search.substring(1));
+       }
+     }
+     rval = _queryVariables;
+   } else {
+     // parse given query
+     rval = parse(query);
+   }
+   return rval;
+};
+
+/**
+ * Parses a fragment into a path and query. This method will take a URI
+ * fragment and break it up as if it were the main URI. For example:
+ *    /bar/baz?a=1&b=2
+ * results in:
+ *    {
+ *       path: ["bar", "baz"],
+ *       query: {"k1": ["v1"], "k2": ["v2"]}
+ *    }
+ *
+ * @return object with a path array and query object.
+ */
+util.parseFragment = function(fragment) {
+  // default to whole fragment
+  var fp = fragment;
+  var fq = '';
+  // split into path and query if possible at the first '?'
+  var pos = fragment.indexOf('?');
+  if(pos > 0) {
+    fp = fragment.substring(0, pos);
+    fq = fragment.substring(pos + 1);
+  }
+  // split path based on '/' and ignore first element if empty
+  var path = fp.split('/');
+  if(path.length > 0 && path[0] === '') {
+    path.shift();
+  }
+  // convert query into object
+  var query = (fq === '') ? {} : util.getQueryVariables(fq);
+
+  return {
+    pathString: fp,
+    queryString: fq,
+    path: path,
+    query: query
+  };
+};
+
+/**
+ * Makes a request out of a URI-like request string. This is intended to
+ * be used where a fragment id (after a URI '#') is parsed as a URI with
+ * path and query parts. The string should have a path beginning and
+ * delimited by '/' and optional query parameters following a '?'. The
+ * query should be a standard URL set of key value pairs delimited by
+ * '&'. For backwards compatibility the initial '/' on the path is not
+ * required. The request object has the following API, (fully described
+ * in the method code):
+ *    {
+ *       path: <the path string part>.
+ *       query: <the query string part>,
+ *       getPath(i): get part or all of the split path array,
+ *       getQuery(k, i): get part or all of a query key array,
+ *       getQueryLast(k, _default): get last element of a query key array.
+ *    }
+ *
+ * @return object with request parameters.
+ */
+util.makeRequest = function(reqString) {
+  var frag = util.parseFragment(reqString);
+  var req = {
+    // full path string
+    path: frag.pathString,
+    // full query string
+    query: frag.queryString,
+    /**
+     * Get path or element in path.
+     *
+     * @param i optional path index.
+     *
+     * @return path or part of path if i provided.
+     */
+    getPath: function(i) {
+      return (typeof(i) === 'undefined') ? frag.path : frag.path[i];
+    },
+    /**
+     * Get query, values for a key, or value for a key index.
+     *
+     * @param k optional query key.
+     * @param i optional query key index.
+     *
+     * @return query, values for a key, or value for a key index.
+     */
+    getQuery: function(k, i) {
+      var rval;
+      if(typeof(k) === 'undefined') {
+        rval = frag.query;
+      } else {
+        rval = frag.query[k];
+        if(rval && typeof(i) !== 'undefined') {
+           rval = rval[i];
+        }
+      }
+      return rval;
+    },
+    getQueryLast: function(k, _default) {
+      var rval;
+      var vals = req.getQuery(k);
+      if(vals) {
+        rval = vals[vals.length - 1];
+      } else {
+        rval = _default;
+      }
+      return rval;
+    }
+  };
+  return req;
+};
+
+/**
+ * Makes a URI out of a path, an object with query parameters, and a
+ * fragment. Uses jQuery.param() internally for query string creation.
+ * If the path is an array, it will be joined with '/'.
+ *
+ * @param path string path or array of strings.
+ * @param query object with query parameters. (optional)
+ * @param fragment fragment string. (optional)
+ *
+ * @return string object with request parameters.
+ */
+util.makeLink = function(path, query, fragment) {
+  // join path parts if needed
+  path = jQuery.isArray(path) ? path.join('/') : path;
+
+  var qstr = jQuery.param(query || {});
+  fragment = fragment || '';
+  return path +
+    ((qstr.length > 0) ? ('?' + qstr) : '') +
+    ((fragment.length > 0) ? ('#' + fragment) : '');
+};
+
+/**
+ * Follows a path of keys deep into an object hierarchy and set a value.
+ * If a key does not exist or it's value is not an object, create an
+ * object in it's place. This can be destructive to a object tree if
+ * leaf nodes are given as non-final path keys.
+ * Used to avoid exceptions from missing parts of the path.
+ *
+ * @param object the starting object.
+ * @param keys an array of string keys.
+ * @param value the value to set.
+ */
+util.setPath = function(object, keys, value) {
+  // need to start at an object
+  if(typeof(object) === 'object' && object !== null) {
+    var i = 0;
+    var len = keys.length;
+    while(i < len) {
+      var next = keys[i++];
+      if(i == len) {
+        // last
+        object[next] = value;
+      } else {
+        // more
+        var hasNext = (next in object);
+        if(!hasNext ||
+          (hasNext && typeof(object[next]) !== 'object') ||
+          (hasNext && object[next] === null)) {
+          object[next] = {};
+        }
+        object = object[next];
+      }
+    }
+  }
+};
+
+/**
+ * Follows a path of keys deep into an object hierarchy and return a value.
+ * If a key does not exist, create an object in it's place.
+ * Used to avoid exceptions from missing parts of the path.
+ *
+ * @param object the starting object.
+ * @param keys an array of string keys.
+ * @param _default value to return if path not found.
+ *
+ * @return the value at the path if found, else default if given, else
+ *         undefined.
+ */
+util.getPath = function(object, keys, _default) {
+  var i = 0;
+  var len = keys.length;
+  var hasNext = true;
+  while(hasNext && i < len &&
+    typeof(object) === 'object' && object !== null) {
+    var next = keys[i++];
+    hasNext = next in object;
+    if(hasNext) {
+      object = object[next];
+    }
+  }
+  return (hasNext ? object : _default);
+};
+
+/**
+ * Follow a path of keys deep into an object hierarchy and delete the
+ * last one. If a key does not exist, do nothing.
+ * Used to avoid exceptions from missing parts of the path.
+ *
+ * @param object the starting object.
+ * @param keys an array of string keys.
+ */
+util.deletePath = function(object, keys) {
+  // need to start at an object
+  if(typeof(object) === 'object' && object !== null) {
+    var i = 0;
+    var len = keys.length;
+    while(i < len) {
+      var next = keys[i++];
+      if(i == len) {
+        // last
+        delete object[next];
+      } else {
+        // more
+        if(!(next in object) ||
+          (typeof(object[next]) !== 'object') ||
+          (object[next] === null)) {
+           break;
+        }
+        object = object[next];
+      }
+    }
+  }
+};
+
+/**
+ * Check if an object is empty.
+ *
+ * Taken from:
+ * http://stackoverflow.com/questions/679915/how-do-i-test-for-an-empty-javascript-object-from-json/679937#679937
+ *
+ * @param object the object to check.
+ */
+util.isEmpty = function(obj) {
+  for(var prop in obj) {
+    if(obj.hasOwnProperty(prop)) {
+      return false;
+    }
+  }
+  return true;
+};
+
+/**
+ * Format with simple printf-style interpolation.
+ *
+ * %%: literal '%'
+ * %s,%o: convert next argument into a string.
+ *
+ * @param format the string to format.
+ * @param ... arguments to interpolate into the format string.
+ */
+util.format = function(format) {
+  var re = /%./g;
+  // current match
+  var match;
+  // current part
+  var part;
+  // current arg index
+  var argi = 0;
+  // collected parts to recombine later
+  var parts = [];
+  // last index found
+  var last = 0;
+  // loop while matches remain
+  while((match = re.exec(format))) {
+    part = format.substring(last, re.lastIndex - 2);
+    // don't add empty strings (ie, parts between %s%s)
+    if(part.length > 0) {
+      parts.push(part);
+    }
+    last = re.lastIndex;
+    // switch on % code
+    var code = match[0][1];
+    switch(code) {
+    case 's':
+    case 'o':
+      // check if enough arguments were given
+      if(argi < arguments.length) {
+        parts.push(arguments[argi++ + 1]);
+      } else {
+        parts.push('<?>');
+      }
+      break;
+    // FIXME: do proper formating for numbers, etc
+    //case 'f':
+    //case 'd':
+    case '%':
+      parts.push('%');
+      break;
+    default:
+      parts.push('<%' + code + '?>');
+    }
+  }
+  // add trailing part of format string
+  parts.push(format.substring(last));
+  return parts.join('');
+};
+
+/**
+ * Formats a number.
+ *
+ * http://snipplr.com/view/5945/javascript-numberformat--ported-from-php/
+ */
+util.formatNumber = function(number, decimals, dec_point, thousands_sep) {
+  // http://kevin.vanzonneveld.net
+  // +   original by: Jonas Raoni Soares Silva (http://www.jsfromhell.com)
+  // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
+  // +     bugfix by: Michael White (http://crestidg.com)
+  // +     bugfix by: Benjamin Lupton
+  // +     bugfix by: Allan Jensen (http://www.winternet.no)
+  // +    revised by: Jonas Raoni Soares Silva (http://www.jsfromhell.com)
+  // *     example 1: number_format(1234.5678, 2, '.', '');
+  // *     returns 1: 1234.57
+
+  var n = number, c = isNaN(decimals = Math.abs(decimals)) ? 2 : decimals;
+  var d = dec_point === undefined ? ',' : dec_point;
+  var t = thousands_sep === undefined ?
+   '.' : thousands_sep, s = n < 0 ? '-' : '';
+  var i = parseInt((n = Math.abs(+n || 0).toFixed(c)), 10) + '';
+  var j = (i.length > 3) ? i.length % 3 : 0;
+  return s + (j ? i.substr(0, j) + t : '') +
+    i.substr(j).replace(/(\d{3})(?=\d)/g, '$1' + t) +
+    (c ? d + Math.abs(n - i).toFixed(c).slice(2) : '');
+};
+
+/**
+ * Formats a byte size.
+ *
+ * http://snipplr.com/view/5949/format-humanize-file-byte-size-presentation-in-javascript/
+ */
+util.formatSize = function(size) {
+  if(size >= 1073741824) {
+    size = util.formatNumber(size / 1073741824, 2, '.', '') + ' GiB';
+  } else if(size >= 1048576) {
+    size = util.formatNumber(size / 1048576, 2, '.', '') + ' MiB';
+  } else if(size >= 1024) {
+    size = util.formatNumber(size / 1024, 0) + ' KiB';
+  } else {
+    size = util.formatNumber(size, 0) + ' bytes';
+  }
+  return size;
+};
+
+/**
+ * Converts an IPv4 or IPv6 string representation into bytes (in network order).
+ *
+ * @param ip the IPv4 or IPv6 address to convert.
+ *
+ * @return the 4-byte IPv6 or 16-byte IPv6 address or null if the address can't
+ *         be parsed.
+ */
+util.bytesFromIP = function(ip) {
+  if(ip.indexOf('.') !== -1) {
+    return util.bytesFromIPv4(ip);
+  }
+  if(ip.indexOf(':') !== -1) {
+    return util.bytesFromIPv6(ip);
+  }
+  return null;
+};
+
+/**
+ * Converts an IPv4 string representation into bytes (in network order).
+ *
+ * @param ip the IPv4 address to convert.
+ *
+ * @return the 4-byte address or null if the address can't be parsed.
+ */
+util.bytesFromIPv4 = function(ip) {
+  ip = ip.split('.');
+  if(ip.length !== 4) {
+    return null;
+  }
+  var b = util.createBuffer();
+  for(var i = 0; i < ip.length; ++i) {
+    var num = parseInt(ip[i], 10);
+    if(isNaN(num)) {
+      return null;
+    }
+    b.putByte(num);
+  }
+  return b.getBytes();
+};
+
+/**
+ * Converts an IPv6 string representation into bytes (in network order).
+ *
+ * @param ip the IPv6 address to convert.
+ *
+ * @return the 16-byte address or null if the address can't be parsed.
+ */
+util.bytesFromIPv6 = function(ip) {
+  var blanks = 0;
+  ip = ip.split(':').filter(function(e) {
+    if(e.length === 0) ++blanks;
+    return true;
+  });
+  var zeros = (8 - ip.length + blanks) * 2;
+  var b = util.createBuffer();
+  for(var i = 0; i < 8; ++i) {
+    if(!ip[i] || ip[i].length === 0) {
+      b.fillWithByte(0, zeros);
+      zeros = 0;
+      continue;
+    }
+    var bytes = util.hexToBytes(ip[i]);
+    if(bytes.length < 2) {
+      b.putByte(0);
+    }
+    b.putBytes(bytes);
+  }
+  return b.getBytes();
+};
+
+/**
+ * Converts 4-bytes into an IPv4 string representation or 16-bytes into
+ * an IPv6 string representation. The bytes must be in network order.
+ *
+ * @param bytes the bytes to convert.
+ *
+ * @return the IPv4 or IPv6 string representation if 4 or 16 bytes,
+ *         respectively, are given, otherwise null.
+ */
+util.bytesToIP = function(bytes) {
+  if(bytes.length === 4) {
+    return util.bytesToIPv4(bytes);
+  }
+  if(bytes.length === 16) {
+    return util.bytesToIPv6(bytes);
+  }
+  return null;
+};
+
+/**
+ * Converts 4-bytes into an IPv4 string representation. The bytes must be
+ * in network order.
+ *
+ * @param bytes the bytes to convert.
+ *
+ * @return the IPv4 string representation or null for an invalid # of bytes.
+ */
+util.bytesToIPv4 = function(bytes) {
+  if(bytes.length !== 4) {
+    return null;
+  }
+  var ip = [];
+  for(var i = 0; i < bytes.length; ++i) {
+    ip.push(bytes.charCodeAt(i));
+  }
+  return ip.join('.');
+};
+
+/**
+ * Converts 16-bytes into an IPv16 string representation. The bytes must be
+ * in network order.
+ *
+ * @param bytes the bytes to convert.
+ *
+ * @return the IPv16 string representation or null for an invalid # of bytes.
+ */
+util.bytesToIPv6 = function(bytes) {
+  if(bytes.length !== 16) {
+    return null;
+  }
+  var ip = [];
+  var zeroGroups = [];
+  var zeroMaxGroup = 0;
+  for(var i = 0; i < bytes.length; i += 2) {
+    var hex = util.bytesToHex(bytes[i] + bytes[i + 1]);
+    // canonicalize zero representation
+    while(hex[0] === '0' && hex !== '0') {
+      hex = hex.substr(1);
+    }
+    if(hex === '0') {
+      var last = zeroGroups[zeroGroups.length - 1];
+      var idx = ip.length;
+      if(!last || idx !== last.end + 1) {
+        zeroGroups.push({start: idx, end: idx});
+      } else {
+        last.end = idx;
+        if((last.end - last.start) >
+          (zeroGroups[zeroMaxGroup].end - zeroGroups[zeroMaxGroup].start)) {
+          zeroMaxGroup = zeroGroups.length - 1;
+        }
+      }
+    }
+    ip.push(hex);
+  }
+  if(zeroGroups.length > 0) {
+    var group = zeroGroups[zeroMaxGroup];
+    // only shorten group of length > 0
+    if(group.end - group.start > 0) {
+      ip.splice(group.start, group.end - group.start + 1, '');
+      if(group.start === 0) {
+        ip.unshift('');
+      }
+      if(group.end === 7) {
+        ip.push('');
+      }
+    }
+  }
+  return ip.join(':');
+};
+
+/**
+ * Estimates the number of processes that can be run concurrently. If
+ * creating Web Workers, keep in mind that the main JavaScript process needs
+ * its own core.
+ *
+ * @param options the options to use:
+ *          update true to force an update (not use the cached value).
+ * @param callback(err, max) called once the operation completes.
+ */
+util.estimateCores = function(options, callback) {
+  if(typeof options === 'function') {
+    callback = options;
+    options = {};
+  }
+  options = options || {};
+  if('cores' in util && !options.update) {
+    return callback(null, util.cores);
+  }
+  if(typeof navigator !== 'undefined' &&
+    'hardwareConcurrency' in navigator &&
+    navigator.hardwareConcurrency > 0) {
+    util.cores = navigator.hardwareConcurrency;
+    return callback(null, util.cores);
+  }
+  if(typeof Worker === 'undefined') {
+    // workers not available
+    util.cores = 1;
+    return callback(null, util.cores);
+  }
+  if(typeof Blob === 'undefined') {
+    // can't estimate, default to 2
+    util.cores = 2;
+    return callback(null, util.cores);
+  }
+
+  // create worker concurrency estimation code as blob
+  var blobUrl = URL.createObjectURL(new Blob(['(',
+    function() {
+      self.addEventListener('message', function(e) {
+        // run worker for 4 ms
+        var st = Date.now();
+        var et = st + 4;
+        while(Date.now() < et);
+        self.postMessage({st: st, et: et});
+      });
+    }.toString(),
+  ')()'], {type: 'application/javascript'}));
+
+  // take 5 samples using 16 workers
+  sample([], 5, 16);
+
+  function sample(max, samples, numWorkers) {
+    if(samples === 0) {
+      // get overlap average
+      var avg = Math.floor(max.reduce(function(avg, x) {
+        return avg + x;
+      }, 0) / max.length);
+      util.cores = Math.max(1, avg);
+      URL.revokeObjectURL(blobUrl);
+      return callback(null, util.cores);
+    }
+    map(numWorkers, function(err, results) {
+      max.push(reduce(numWorkers, results));
+      sample(max, samples - 1, numWorkers);
+    });
+  }
+
+  function map(numWorkers, callback) {
+    var workers = [];
+    var results = [];
+    for(var i = 0; i < numWorkers; ++i) {
+      var worker = new Worker(blobUrl);
+      worker.addEventListener('message', function(e) {
+        results.push(e.data);
+        if(results.length === numWorkers) {
+          for(var i = 0; i < numWorkers; ++i) {
+            workers[i].terminate();
+          }
+          callback(null, results);
+        }
+      });
+      workers.push(worker);
+    }
+    for(var i = 0; i < numWorkers; ++i) {
+      workers[i].postMessage(i);
+    }
+  }
+
+  function reduce(numWorkers, results) {
+    // find overlapping time windows
+    var overlaps = [];
+    for(var n = 0; n < numWorkers; ++n) {
+      var r1 = results[n];
+      var overlap = overlaps[n] = [];
+      for(var i = 0; i < numWorkers; ++i) {
+        if(n === i) {
+          continue;
+        }
+        var r2 = results[i];
+        if((r1.st > r2.st && r1.st < r2.et) ||
+          (r2.st > r1.st && r2.st < r1.et)) {
+          overlap.push(i);
+        }
+      }
+    }
+    // get maximum overlaps ... don't include overlapping worker itself
+    // as the main JS process was also being scheduled during the work and
+    // would have to be subtracted from the estimate anyway
+    return overlaps.reduce(function(max, overlap) {
+      return Math.max(max, overlap.length);
+    }, 0);
+  }
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'util';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/x509.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/x509.js
new file mode 100644
index 0000000..6cd8585
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/x509.js
@@ -0,0 +1,3178 @@
+/**
+ * Javascript implementation of X.509 and related components (such as
+ * Certification Signing Requests) of a Public Key Infrastructure.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2014 Digital Bazaar, Inc.
+ *
+ * The ASN.1 representation of an X.509v3 certificate is as follows
+ * (see RFC 2459):
+ *
+ * Certificate ::= SEQUENCE {
+ *   tbsCertificate       TBSCertificate,
+ *   signatureAlgorithm   AlgorithmIdentifier,
+ *   signatureValue       BIT STRING
+ * }
+ *
+ * TBSCertificate ::= SEQUENCE {
+ *   version         [0]  EXPLICIT Version DEFAULT v1,
+ *   serialNumber         CertificateSerialNumber,
+ *   signature            AlgorithmIdentifier,
+ *   issuer               Name,
+ *   validity             Validity,
+ *   subject              Name,
+ *   subjectPublicKeyInfo SubjectPublicKeyInfo,
+ *   issuerUniqueID  [1]  IMPLICIT UniqueIdentifier OPTIONAL,
+ *                        -- If present, version shall be v2 or v3
+ *   subjectUniqueID [2]  IMPLICIT UniqueIdentifier OPTIONAL,
+ *                        -- If present, version shall be v2 or v3
+ *   extensions      [3]  EXPLICIT Extensions OPTIONAL
+ *                        -- If present, version shall be v3
+ * }
+ *
+ * Version ::= INTEGER  { v1(0), v2(1), v3(2) }
+ *
+ * CertificateSerialNumber ::= INTEGER
+ *
+ * Name ::= CHOICE {
+ *   // only one possible choice for now
+ *   RDNSequence
+ * }
+ *
+ * RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
+ *
+ * RelativeDistinguishedName ::= SET OF AttributeTypeAndValue
+ *
+ * AttributeTypeAndValue ::= SEQUENCE {
+ *   type     AttributeType,
+ *   value    AttributeValue
+ * }
+ * AttributeType ::= OBJECT IDENTIFIER
+ * AttributeValue ::= ANY DEFINED BY AttributeType
+ *
+ * Validity ::= SEQUENCE {
+ *   notBefore      Time,
+ *   notAfter       Time
+ * }
+ *
+ * Time ::= CHOICE {
+ *   utcTime        UTCTime,
+ *   generalTime    GeneralizedTime
+ * }
+ *
+ * UniqueIdentifier ::= BIT STRING
+ *
+ * SubjectPublicKeyInfo ::= SEQUENCE {
+ *   algorithm            AlgorithmIdentifier,
+ *   subjectPublicKey     BIT STRING
+ * }
+ *
+ * Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension
+ *
+ * Extension ::= SEQUENCE {
+ *   extnID      OBJECT IDENTIFIER,
+ *   critical    BOOLEAN DEFAULT FALSE,
+ *   extnValue   OCTET STRING
+ * }
+ *
+ * The only key algorithm currently supported for PKI is RSA.
+ *
+ * RSASSA-PSS signatures are described in RFC 3447 and RFC 4055.
+ *
+ * PKCS#10 v1.7 describes certificate signing requests:
+ *
+ * CertificationRequestInfo:
+ *
+ * CertificationRequestInfo ::= SEQUENCE {
+ *   version       INTEGER { v1(0) } (v1,...),
+ *   subject       Name,
+ *   subjectPKInfo SubjectPublicKeyInfo{{ PKInfoAlgorithms }},
+ *   attributes    [0] Attributes{{ CRIAttributes }}
+ * }
+ *
+ * Attributes { ATTRIBUTE:IOSet } ::= SET OF Attribute{{ IOSet }}
+ *
+ * CRIAttributes  ATTRIBUTE  ::= {
+ *   ... -- add any locally defined attributes here -- }
+ *
+ * Attribute { ATTRIBUTE:IOSet } ::= SEQUENCE {
+ *   type   ATTRIBUTE.&id({IOSet}),
+ *   values SET SIZE(1..MAX) OF ATTRIBUTE.&Type({IOSet}{@type})
+ * }
+ *
+ * CertificationRequest ::= SEQUENCE {
+ *   certificationRequestInfo CertificationRequestInfo,
+ *   signatureAlgorithm AlgorithmIdentifier{{ SignatureAlgorithms }},
+ *   signature          BIT STRING
+ * }
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+// shortcut for asn.1 API
+var asn1 = forge.asn1;
+
+/* Public Key Infrastructure (PKI) implementation. */
+var pki = forge.pki = forge.pki || {};
+var oids = pki.oids;
+
+// short name OID mappings
+var _shortNames = {};
+_shortNames['CN'] = oids['commonName'];
+_shortNames['commonName'] = 'CN';
+_shortNames['C'] = oids['countryName'];
+_shortNames['countryName'] = 'C';
+_shortNames['L'] = oids['localityName'];
+_shortNames['localityName'] = 'L';
+_shortNames['ST'] = oids['stateOrProvinceName'];
+_shortNames['stateOrProvinceName'] = 'ST';
+_shortNames['O'] = oids['organizationName'];
+_shortNames['organizationName'] = 'O';
+_shortNames['OU'] = oids['organizationalUnitName'];
+_shortNames['organizationalUnitName'] = 'OU';
+_shortNames['E'] = oids['emailAddress'];
+_shortNames['emailAddress'] = 'E';
+
+// validator for an SubjectPublicKeyInfo structure
+// Note: Currently only works with an RSA public key
+var publicKeyValidator = forge.pki.rsa.publicKeyValidator;
+
+// validator for an X.509v3 certificate
+var x509CertificateValidator = {
+  name: 'Certificate',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'Certificate.TBSCertificate',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true,
+    captureAsn1: 'tbsCertificate',
+    value: [{
+      name: 'Certificate.TBSCertificate.version',
+      tagClass: asn1.Class.CONTEXT_SPECIFIC,
+      type: 0,
+      constructed: true,
+      optional: true,
+      value: [{
+        name: 'Certificate.TBSCertificate.version.integer',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.INTEGER,
+        constructed: false,
+        capture: 'certVersion'
+      }]
+    }, {
+      name: 'Certificate.TBSCertificate.serialNumber',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.INTEGER,
+      constructed: false,
+      capture: 'certSerialNumber'
+    }, {
+      name: 'Certificate.TBSCertificate.signature',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.SEQUENCE,
+      constructed: true,
+      value: [{
+        name: 'Certificate.TBSCertificate.signature.algorithm',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.OID,
+        constructed: false,
+        capture: 'certinfoSignatureOid'
+      }, {
+        name: 'Certificate.TBSCertificate.signature.parameters',
+        tagClass: asn1.Class.UNIVERSAL,
+        optional: true,
+        captureAsn1: 'certinfoSignatureParams'
+      }]
+    }, {
+      name: 'Certificate.TBSCertificate.issuer',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.SEQUENCE,
+      constructed: true,
+      captureAsn1: 'certIssuer'
+    }, {
+      name: 'Certificate.TBSCertificate.validity',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.SEQUENCE,
+      constructed: true,
+      // Note: UTC and generalized times may both appear so the capture
+      // names are based on their detected order, the names used below
+      // are only for the common case, which validity time really means
+      // "notBefore" and which means "notAfter" will be determined by order
+      value: [{
+        // notBefore (Time) (UTC time case)
+        name: 'Certificate.TBSCertificate.validity.notBefore (utc)',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.UTCTIME,
+        constructed: false,
+        optional: true,
+        capture: 'certValidity1UTCTime'
+      }, {
+        // notBefore (Time) (generalized time case)
+        name: 'Certificate.TBSCertificate.validity.notBefore (generalized)',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.GENERALIZEDTIME,
+        constructed: false,
+        optional: true,
+        capture: 'certValidity2GeneralizedTime'
+      }, {
+        // notAfter (Time) (only UTC time is supported)
+        name: 'Certificate.TBSCertificate.validity.notAfter (utc)',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.UTCTIME,
+        constructed: false,
+        optional: true,
+        capture: 'certValidity3UTCTime'
+      }, {
+        // notAfter (Time) (only UTC time is supported)
+        name: 'Certificate.TBSCertificate.validity.notAfter (generalized)',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.GENERALIZEDTIME,
+        constructed: false,
+        optional: true,
+        capture: 'certValidity4GeneralizedTime'
+      }]
+    }, {
+      // Name (subject) (RDNSequence)
+      name: 'Certificate.TBSCertificate.subject',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.SEQUENCE,
+      constructed: true,
+      captureAsn1: 'certSubject'
+    },
+      // SubjectPublicKeyInfo
+      publicKeyValidator,
+    {
+      // issuerUniqueID (optional)
+      name: 'Certificate.TBSCertificate.issuerUniqueID',
+      tagClass: asn1.Class.CONTEXT_SPECIFIC,
+      type: 1,
+      constructed: true,
+      optional: true,
+      value: [{
+        name: 'Certificate.TBSCertificate.issuerUniqueID.id',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.BITSTRING,
+        constructed: false,
+        capture: 'certIssuerUniqueId'
+      }]
+    }, {
+      // subjectUniqueID (optional)
+      name: 'Certificate.TBSCertificate.subjectUniqueID',
+      tagClass: asn1.Class.CONTEXT_SPECIFIC,
+      type: 2,
+      constructed: true,
+      optional: true,
+      value: [{
+        name: 'Certificate.TBSCertificate.subjectUniqueID.id',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.BITSTRING,
+        constructed: false,
+        capture: 'certSubjectUniqueId'
+      }]
+    }, {
+      // Extensions (optional)
+      name: 'Certificate.TBSCertificate.extensions',
+      tagClass: asn1.Class.CONTEXT_SPECIFIC,
+      type: 3,
+      constructed: true,
+      captureAsn1: 'certExtensions',
+      optional: true
+    }]
+  }, {
+    // AlgorithmIdentifier (signature algorithm)
+    name: 'Certificate.signatureAlgorithm',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true,
+    value: [{
+      // algorithm
+      name: 'Certificate.signatureAlgorithm.algorithm',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.OID,
+      constructed: false,
+      capture: 'certSignatureOid'
+    }, {
+      name: 'Certificate.TBSCertificate.signature.parameters',
+      tagClass: asn1.Class.UNIVERSAL,
+      optional: true,
+      captureAsn1: 'certSignatureParams'
+    }]
+  }, {
+    // SignatureValue
+    name: 'Certificate.signatureValue',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.BITSTRING,
+    constructed: false,
+    capture: 'certSignature'
+  }]
+};
+
+var rsassaPssParameterValidator = {
+  name: 'rsapss',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'rsapss.hashAlgorithm',
+    tagClass: asn1.Class.CONTEXT_SPECIFIC,
+    type: 0,
+    constructed: true,
+    value: [{
+      name: 'rsapss.hashAlgorithm.AlgorithmIdentifier',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Class.SEQUENCE,
+      constructed: true,
+      optional: true,
+      value: [{
+        name: 'rsapss.hashAlgorithm.AlgorithmIdentifier.algorithm',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.OID,
+        constructed: false,
+        capture: 'hashOid'
+        /* parameter block omitted, for SHA1 NULL anyhow. */
+      }]
+    }]
+  }, {
+    name: 'rsapss.maskGenAlgorithm',
+    tagClass: asn1.Class.CONTEXT_SPECIFIC,
+    type: 1,
+    constructed: true,
+    value: [{
+      name: 'rsapss.maskGenAlgorithm.AlgorithmIdentifier',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Class.SEQUENCE,
+      constructed: true,
+      optional: true,
+      value: [{
+        name: 'rsapss.maskGenAlgorithm.AlgorithmIdentifier.algorithm',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.OID,
+        constructed: false,
+        capture: 'maskGenOid'
+      }, {
+        name: 'rsapss.maskGenAlgorithm.AlgorithmIdentifier.params',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.SEQUENCE,
+        constructed: true,
+        value: [{
+          name: 'rsapss.maskGenAlgorithm.AlgorithmIdentifier.params.algorithm',
+          tagClass: asn1.Class.UNIVERSAL,
+          type: asn1.Type.OID,
+          constructed: false,
+          capture: 'maskGenHashOid'
+          /* parameter block omitted, for SHA1 NULL anyhow. */
+        }]
+      }]
+    }]
+  }, {
+    name: 'rsapss.saltLength',
+    tagClass: asn1.Class.CONTEXT_SPECIFIC,
+    type: 2,
+    optional: true,
+    value: [{
+      name: 'rsapss.saltLength.saltLength',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Class.INTEGER,
+      constructed: false,
+      capture: 'saltLength'
+    }]
+  }, {
+    name: 'rsapss.trailerField',
+    tagClass: asn1.Class.CONTEXT_SPECIFIC,
+    type: 3,
+    optional: true,
+    value: [{
+      name: 'rsapss.trailer.trailer',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Class.INTEGER,
+      constructed: false,
+      capture: 'trailer'
+    }]
+  }]
+};
+
+// validator for a CertificationRequestInfo structure
+var certificationRequestInfoValidator = {
+  name: 'CertificationRequestInfo',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  captureAsn1: 'certificationRequestInfo',
+  value: [{
+    name: 'CertificationRequestInfo.integer',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'certificationRequestInfoVersion'
+  }, {
+    // Name (subject) (RDNSequence)
+    name: 'CertificationRequestInfo.subject',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true,
+    captureAsn1: 'certificationRequestInfoSubject'
+  },
+  // SubjectPublicKeyInfo
+  publicKeyValidator,
+  {
+    name: 'CertificationRequestInfo.attributes',
+    tagClass: asn1.Class.CONTEXT_SPECIFIC,
+    type: 0,
+    constructed: true,
+    optional: true,
+    capture: 'certificationRequestInfoAttributes',
+    value: [{
+      name: 'CertificationRequestInfo.attributes',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.SEQUENCE,
+      constructed: true,
+      value: [{
+        name: 'CertificationRequestInfo.attributes.type',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.OID,
+        constructed: false
+      }, {
+        name: 'CertificationRequestInfo.attributes.value',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.SET,
+        constructed: true
+      }]
+    }]
+  }]
+};
+
+// validator for a CertificationRequest structure
+var certificationRequestValidator = {
+  name: 'CertificationRequest',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  captureAsn1: 'csr',
+  value: [
+    certificationRequestInfoValidator, {
+    // AlgorithmIdentifier (signature algorithm)
+    name: 'CertificationRequest.signatureAlgorithm',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true,
+    value: [{
+      // algorithm
+      name: 'CertificationRequest.signatureAlgorithm.algorithm',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.OID,
+      constructed: false,
+      capture: 'csrSignatureOid'
+    }, {
+      name: 'CertificationRequest.signatureAlgorithm.parameters',
+      tagClass: asn1.Class.UNIVERSAL,
+      optional: true,
+      captureAsn1: 'csrSignatureParams'
+    }]
+  }, {
+    // signature
+    name: 'CertificationRequest.signature',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.BITSTRING,
+    constructed: false,
+    capture: 'csrSignature'
+  }]
+};
+
+/**
+ * Converts an RDNSequence of ASN.1 DER-encoded RelativeDistinguishedName
+ * sets into an array with objects that have type and value properties.
+ *
+ * @param rdn the RDNSequence to convert.
+ * @param md a message digest to append type and value to if provided.
+ */
+pki.RDNAttributesAsArray = function(rdn, md) {
+  var rval = [];
+
+  // each value in 'rdn' in is a SET of RelativeDistinguishedName
+  var set, attr, obj;
+  for(var si = 0; si < rdn.value.length; ++si) {
+    // get the RelativeDistinguishedName set
+    set = rdn.value[si];
+
+    // each value in the SET is an AttributeTypeAndValue sequence
+    // containing first a type (an OID) and second a value (defined by
+    // the OID)
+    for(var i = 0; i < set.value.length; ++i) {
+      obj = {};
+      attr = set.value[i];
+      obj.type = asn1.derToOid(attr.value[0].value);
+      obj.value = attr.value[1].value;
+      obj.valueTagClass = attr.value[1].type;
+      // if the OID is known, get its name and short name
+      if(obj.type in oids) {
+        obj.name = oids[obj.type];
+        if(obj.name in _shortNames) {
+          obj.shortName = _shortNames[obj.name];
+        }
+      }
+      if(md) {
+        md.update(obj.type);
+        md.update(obj.value);
+      }
+      rval.push(obj);
+    }
+  }
+
+  return rval;
+};
+
+/**
+ * Converts ASN.1 CRIAttributes into an array with objects that have type and
+ * value properties.
+ *
+ * @param attributes the CRIAttributes to convert.
+ */
+pki.CRIAttributesAsArray = function(attributes) {
+  var rval = [];
+
+  // each value in 'attributes' in is a SEQUENCE with an OID and a SET
+  for(var si = 0; si < attributes.length; ++si) {
+    // get the attribute sequence
+    var seq = attributes[si];
+
+    // each value in the SEQUENCE containing first a type (an OID) and
+    // second a set of values (defined by the OID)
+    var type = asn1.derToOid(seq.value[0].value);
+    var values = seq.value[1].value;
+    for(var vi = 0; vi < values.length; ++vi) {
+      var obj = {};
+      obj.type = type;
+      obj.value = values[vi].value;
+      obj.valueTagClass = values[vi].type;
+      // if the OID is known, get its name and short name
+      if(obj.type in oids) {
+        obj.name = oids[obj.type];
+        if(obj.name in _shortNames) {
+          obj.shortName = _shortNames[obj.name];
+        }
+      }
+      // parse extensions
+      if(obj.type === oids.extensionRequest) {
+        obj.extensions = [];
+        for(var ei = 0; ei < obj.value.length; ++ei) {
+          obj.extensions.push(pki.certificateExtensionFromAsn1(obj.value[ei]));
+        }
+      }
+      rval.push(obj);
+    }
+  }
+
+  return rval;
+};
+
+/**
+ * Gets an issuer or subject attribute from its name, type, or short name.
+ *
+ * @param obj the issuer or subject object.
+ * @param options a short name string or an object with:
+ *          shortName the short name for the attribute.
+ *          name the name for the attribute.
+ *          type the type for the attribute.
+ *
+ * @return the attribute.
+ */
+function _getAttribute(obj, options) {
+  if(typeof options === 'string') {
+    options = {shortName: options};
+  }
+
+  var rval = null;
+  var attr;
+  for(var i = 0; rval === null && i < obj.attributes.length; ++i) {
+    attr = obj.attributes[i];
+    if(options.type && options.type === attr.type) {
+      rval = attr;
+    } else if(options.name && options.name === attr.name) {
+      rval = attr;
+    } else if(options.shortName && options.shortName === attr.shortName) {
+      rval = attr;
+    }
+  }
+  return rval;
+}
+
+/**
+ * Converts signature parameters from ASN.1 structure.
+ *
+ * Currently only RSASSA-PSS supported.  The PKCS#1 v1.5 signature scheme had
+ * no parameters.
+ *
+ * RSASSA-PSS-params  ::=  SEQUENCE  {
+ *   hashAlgorithm      [0] HashAlgorithm DEFAULT
+ *                             sha1Identifier,
+ *   maskGenAlgorithm   [1] MaskGenAlgorithm DEFAULT
+ *                             mgf1SHA1Identifier,
+ *   saltLength         [2] INTEGER DEFAULT 20,
+ *   trailerField       [3] INTEGER DEFAULT 1
+ * }
+ *
+ * HashAlgorithm  ::=  AlgorithmIdentifier
+ *
+ * MaskGenAlgorithm  ::=  AlgorithmIdentifier
+ *
+ * AlgorithmIdentifer ::= SEQUENCE {
+ *   algorithm OBJECT IDENTIFIER,
+ *   parameters ANY DEFINED BY algorithm OPTIONAL
+ * }
+ *
+ * @param oid The OID specifying the signature algorithm
+ * @param obj The ASN.1 structure holding the parameters
+ * @param fillDefaults Whether to use return default values where omitted
+ * @return signature parameter object
+ */
+var _readSignatureParameters = function(oid, obj, fillDefaults) {
+  var params = {};
+
+  if(oid !== oids['RSASSA-PSS']) {
+    return params;
+  }
+
+  if(fillDefaults) {
+    params = {
+      hash: {
+        algorithmOid: oids['sha1']
+      },
+      mgf: {
+        algorithmOid: oids['mgf1'],
+        hash: {
+          algorithmOid: oids['sha1']
+        }
+      },
+      saltLength: 20
+    };
+  }
+
+  var capture = {};
+  var errors = [];
+  if(!asn1.validate(obj, rsassaPssParameterValidator, capture, errors)) {
+    var error = new Error('Cannot read RSASSA-PSS parameter block.');
+    error.errors = errors;
+    throw error;
+  }
+
+  if(capture.hashOid !== undefined) {
+    params.hash = params.hash || {};
+    params.hash.algorithmOid = asn1.derToOid(capture.hashOid);
+  }
+
+  if(capture.maskGenOid !== undefined) {
+    params.mgf = params.mgf || {};
+    params.mgf.algorithmOid = asn1.derToOid(capture.maskGenOid);
+    params.mgf.hash = params.mgf.hash || {};
+    params.mgf.hash.algorithmOid = asn1.derToOid(capture.maskGenHashOid);
+  }
+
+  if(capture.saltLength !== undefined) {
+    params.saltLength = capture.saltLength.charCodeAt(0);
+  }
+
+  return params;
+};
+
+/**
+ * Converts an X.509 certificate from PEM format.
+ *
+ * Note: If the certificate is to be verified then compute hash should
+ * be set to true. This will scan the TBSCertificate part of the ASN.1
+ * object while it is converted so it doesn't need to be converted back
+ * to ASN.1-DER-encoding later.
+ *
+ * @param pem the PEM-formatted certificate.
+ * @param computeHash true to compute the hash for verification.
+ * @param strict true to be strict when checking ASN.1 value lengths, false to
+ *          allow truncated values (default: true).
+ *
+ * @return the certificate.
+ */
+pki.certificateFromPem = function(pem, computeHash, strict) {
+  var msg = forge.pem.decode(pem)[0];
+
+  if(msg.type !== 'CERTIFICATE' &&
+    msg.type !== 'X509 CERTIFICATE' &&
+    msg.type !== 'TRUSTED CERTIFICATE') {
+    var error = new Error('Could not convert certificate from PEM; PEM header type ' +
+      'is not "CERTIFICATE", "X509 CERTIFICATE", or "TRUSTED CERTIFICATE".');
+    error.headerType = msg.type;
+    throw error;
+  }
+  if(msg.procType && msg.procType.type === 'ENCRYPTED') {
+    throw new Error('Could not convert certificate from PEM; PEM is encrypted.');
+  }
+
+  // convert DER to ASN.1 object
+  var obj = asn1.fromDer(msg.body, strict);
+
+  return pki.certificateFromAsn1(obj, computeHash);
+};
+
+/**
+ * Converts an X.509 certificate to PEM format.
+ *
+ * @param cert the certificate.
+ * @param maxline the maximum characters per line, defaults to 64.
+ *
+ * @return the PEM-formatted certificate.
+ */
+pki.certificateToPem = function(cert, maxline) {
+  // convert to ASN.1, then DER, then PEM-encode
+  var msg = {
+    type: 'CERTIFICATE',
+    body: asn1.toDer(pki.certificateToAsn1(cert)).getBytes()
+  };
+  return forge.pem.encode(msg, {maxline: maxline});
+};
+
+/**
+ * Converts an RSA public key from PEM format.
+ *
+ * @param pem the PEM-formatted public key.
+ *
+ * @return the public key.
+ */
+pki.publicKeyFromPem = function(pem) {
+  var msg = forge.pem.decode(pem)[0];
+
+  if(msg.type !== 'PUBLIC KEY' && msg.type !== 'RSA PUBLIC KEY') {
+    var error = new Error('Could not convert public key from PEM; PEM header ' +
+      'type is not "PUBLIC KEY" or "RSA PUBLIC KEY".');
+    error.headerType = msg.type;
+    throw error;
+  }
+  if(msg.procType && msg.procType.type === 'ENCRYPTED') {
+    throw new Error('Could not convert public key from PEM; PEM is encrypted.');
+  }
+
+  // convert DER to ASN.1 object
+  var obj = asn1.fromDer(msg.body);
+
+  return pki.publicKeyFromAsn1(obj);
+};
+
+/**
+ * Converts an RSA public key to PEM format (using a SubjectPublicKeyInfo).
+ *
+ * @param key the public key.
+ * @param maxline the maximum characters per line, defaults to 64.
+ *
+ * @return the PEM-formatted public key.
+ */
+pki.publicKeyToPem = function(key, maxline) {
+  // convert to ASN.1, then DER, then PEM-encode
+  var msg = {
+    type: 'PUBLIC KEY',
+    body: asn1.toDer(pki.publicKeyToAsn1(key)).getBytes()
+  };
+  return forge.pem.encode(msg, {maxline: maxline});
+};
+
+/**
+ * Converts an RSA public key to PEM format (using an RSAPublicKey).
+ *
+ * @param key the public key.
+ * @param maxline the maximum characters per line, defaults to 64.
+ *
+ * @return the PEM-formatted public key.
+ */
+pki.publicKeyToRSAPublicKeyPem = function(key, maxline) {
+  // convert to ASN.1, then DER, then PEM-encode
+  var msg = {
+    type: 'RSA PUBLIC KEY',
+    body: asn1.toDer(pki.publicKeyToRSAPublicKey(key)).getBytes()
+  };
+  return forge.pem.encode(msg, {maxline: maxline});
+};
+
+/**
+ * Gets a fingerprint for the given public key.
+ *
+ * @param options the options to use.
+ *          [md] the message digest object to use (defaults to forge.md.sha1).
+ *          [type] the type of fingerprint, such as 'RSAPublicKey',
+ *            'SubjectPublicKeyInfo' (defaults to 'RSAPublicKey').
+ *          [encoding] an alternative output encoding, such as 'hex'
+ *            (defaults to none, outputs a byte buffer).
+ *          [delimiter] the delimiter to use between bytes for 'hex' encoded
+ *            output, eg: ':' (defaults to none).
+ *
+ * @return the fingerprint as a byte buffer or other encoding based on options.
+ */
+pki.getPublicKeyFingerprint = function(key, options) {
+  options = options || {};
+  var md = options.md || forge.md.sha1.create();
+  var type = options.type || 'RSAPublicKey';
+
+  var bytes;
+  switch(type) {
+  case 'RSAPublicKey':
+    bytes = asn1.toDer(pki.publicKeyToRSAPublicKey(key)).getBytes();
+    break;
+  case 'SubjectPublicKeyInfo':
+    bytes = asn1.toDer(pki.publicKeyToAsn1(key)).getBytes();
+    break;
+  default:
+    throw new Error('Unknown fingerprint type "' + options.type + '".');
+  }
+
+  // hash public key bytes
+  md.start();
+  md.update(bytes);
+  var digest = md.digest();
+  if(options.encoding === 'hex') {
+    var hex = digest.toHex();
+    if(options.delimiter) {
+      return hex.match(/.{2}/g).join(options.delimiter);
+    }
+    return hex;
+  } else if(options.encoding === 'binary') {
+    return digest.getBytes();
+  } else if(options.encoding) {
+    throw new Error('Unknown encoding "' + options.encoding + '".');
+  }
+  return digest;
+};
+
+/**
+ * Converts a PKCS#10 certification request (CSR) from PEM format.
+ *
+ * Note: If the certification request is to be verified then compute hash
+ * should be set to true. This will scan the CertificationRequestInfo part of
+ * the ASN.1 object while it is converted so it doesn't need to be converted
+ * back to ASN.1-DER-encoding later.
+ *
+ * @param pem the PEM-formatted certificate.
+ * @param computeHash true to compute the hash for verification.
+ * @param strict true to be strict when checking ASN.1 value lengths, false to
+ *          allow truncated values (default: true).
+ *
+ * @return the certification request (CSR).
+ */
+pki.certificationRequestFromPem = function(pem, computeHash, strict) {
+  var msg = forge.pem.decode(pem)[0];
+
+  if(msg.type !== 'CERTIFICATE REQUEST') {
+    var error = new Error('Could not convert certification request from PEM; ' +
+      'PEM header type is not "CERTIFICATE REQUEST".');
+    error.headerType = msg.type;
+    throw error;
+  }
+  if(msg.procType && msg.procType.type === 'ENCRYPTED') {
+    throw new Error('Could not convert certification request from PEM; ' +
+      'PEM is encrypted.');
+  }
+
+  // convert DER to ASN.1 object
+  var obj = asn1.fromDer(msg.body, strict);
+
+  return pki.certificationRequestFromAsn1(obj, computeHash);
+};
+
+/**
+ * Converts a PKCS#10 certification request (CSR) to PEM format.
+ *
+ * @param csr the certification request.
+ * @param maxline the maximum characters per line, defaults to 64.
+ *
+ * @return the PEM-formatted certification request.
+ */
+pki.certificationRequestToPem = function(csr, maxline) {
+  // convert to ASN.1, then DER, then PEM-encode
+  var msg = {
+    type: 'CERTIFICATE REQUEST',
+    body: asn1.toDer(pki.certificationRequestToAsn1(csr)).getBytes()
+  };
+  return forge.pem.encode(msg, {maxline: maxline});
+};
+
+/**
+ * Creates an empty X.509v3 RSA certificate.
+ *
+ * @return the certificate.
+ */
+pki.createCertificate = function() {
+  var cert = {};
+  cert.version = 0x02;
+  cert.serialNumber = '00';
+  cert.signatureOid = null;
+  cert.signature = null;
+  cert.siginfo = {};
+  cert.siginfo.algorithmOid = null;
+  cert.validity = {};
+  cert.validity.notBefore = new Date();
+  cert.validity.notAfter = new Date();
+
+  cert.issuer = {};
+  cert.issuer.getField = function(sn) {
+    return _getAttribute(cert.issuer, sn);
+  };
+  cert.issuer.addField = function(attr) {
+    _fillMissingFields([attr]);
+    cert.issuer.attributes.push(attr);
+  };
+  cert.issuer.attributes = [];
+  cert.issuer.hash = null;
+
+  cert.subject = {};
+  cert.subject.getField = function(sn) {
+    return _getAttribute(cert.subject, sn);
+  };
+  cert.subject.addField = function(attr) {
+    _fillMissingFields([attr]);
+    cert.subject.attributes.push(attr);
+  };
+  cert.subject.attributes = [];
+  cert.subject.hash = null;
+
+  cert.extensions = [];
+  cert.publicKey = null;
+  cert.md = null;
+
+  /**
+   * Sets the subject of this certificate.
+   *
+   * @param attrs the array of subject attributes to use.
+   * @param uniqueId an optional a unique ID to use.
+   */
+  cert.setSubject = function(attrs, uniqueId) {
+    // set new attributes, clear hash
+    _fillMissingFields(attrs);
+    cert.subject.attributes = attrs;
+    delete cert.subject.uniqueId;
+    if(uniqueId) {
+      cert.subject.uniqueId = uniqueId;
+    }
+    cert.subject.hash = null;
+  };
+
+  /**
+   * Sets the issuer of this certificate.
+   *
+   * @param attrs the array of issuer attributes to use.
+   * @param uniqueId an optional a unique ID to use.
+   */
+  cert.setIssuer = function(attrs, uniqueId) {
+    // set new attributes, clear hash
+    _fillMissingFields(attrs);
+    cert.issuer.attributes = attrs;
+    delete cert.issuer.uniqueId;
+    if(uniqueId) {
+      cert.issuer.uniqueId = uniqueId;
+    }
+    cert.issuer.hash = null;
+  };
+
+  /**
+   * Sets the extensions of this certificate.
+   *
+   * @param exts the array of extensions to use.
+   */
+  cert.setExtensions = function(exts) {
+    for(var i = 0; i < exts.length; ++i) {
+      _fillMissingExtensionFields(exts[i], {cert: cert});
+    }
+    // set new extensions
+    cert.extensions = exts;
+  };
+
+  /**
+   * Gets an extension by its name or id.
+   *
+   * @param options the name to use or an object with:
+   *          name the name to use.
+   *          id the id to use.
+   *
+   * @return the extension or null if not found.
+   */
+  cert.getExtension = function(options) {
+    if(typeof options === 'string') {
+      options = {name: options};
+    }
+
+    var rval = null;
+    var ext;
+    for(var i = 0; rval === null && i < cert.extensions.length; ++i) {
+      ext = cert.extensions[i];
+      if(options.id && ext.id === options.id) {
+        rval = ext;
+      } else if(options.name && ext.name === options.name) {
+        rval = ext;
+      }
+    }
+    return rval;
+  };
+
+  /**
+   * Signs this certificate using the given private key.
+   *
+   * @param key the private key to sign with.
+   * @param md the message digest object to use (defaults to forge.md.sha1).
+   */
+  cert.sign = function(key, md) {
+    // TODO: get signature OID from private key
+    cert.md = md || forge.md.sha1.create();
+    var algorithmOid = oids[cert.md.algorithm + 'WithRSAEncryption'];
+    if(!algorithmOid) {
+      var error = new Error('Could not compute certificate digest. ' +
+        'Unknown message digest algorithm OID.');
+      error.algorithm = cert.md.algorithm;
+      throw error;
+    }
+    cert.signatureOid = cert.siginfo.algorithmOid = algorithmOid;
+
+    // get TBSCertificate, convert to DER
+    cert.tbsCertificate = pki.getTBSCertificate(cert);
+    var bytes = asn1.toDer(cert.tbsCertificate);
+
+    // digest and sign
+    cert.md.update(bytes.getBytes());
+    cert.signature = key.sign(cert.md);
+  };
+
+  /**
+   * Attempts verify the signature on the passed certificate using this
+   * certificate's public key.
+   *
+   * @param child the certificate to verify.
+   *
+   * @return true if verified, false if not.
+   */
+  cert.verify = function(child) {
+    var rval = false;
+
+    if(!cert.issued(child)) {
+      var issuer = child.issuer;
+      var subject = cert.subject;
+      var error = new Error('The parent certificate did not issue the given child ' +
+        'certificate; the child certificate\'s issuer does not match the ' +
+        'parent\'s subject.');
+      error.expectedIssuer = issuer.attributes;
+      error.actualIssuer = subject.attributes;
+      throw error;
+    }
+
+    var md = child.md;
+    if(md === null) {
+      // check signature OID for supported signature types
+      if(child.signatureOid in oids) {
+        var oid = oids[child.signatureOid];
+        switch(oid) {
+        case 'sha1WithRSAEncryption':
+          md = forge.md.sha1.create();
+          break;
+        case 'md5WithRSAEncryption':
+          md = forge.md.md5.create();
+          break;
+        case 'sha256WithRSAEncryption':
+          md = forge.md.sha256.create();
+          break;
+        case 'RSASSA-PSS':
+          md = forge.md.sha256.create();
+          break;
+        }
+      }
+      if(md === null) {
+        var error = new Error('Could not compute certificate digest. ' +
+          'Unknown signature OID.');
+        error.signatureOid = child.signatureOid;
+        throw error;
+      }
+
+      // produce DER formatted TBSCertificate and digest it
+      var tbsCertificate = child.tbsCertificate || pki.getTBSCertificate(child);
+      var bytes = asn1.toDer(tbsCertificate);
+      md.update(bytes.getBytes());
+    }
+
+    if(md !== null) {
+      var scheme;
+
+      switch(child.signatureOid) {
+      case oids.sha1WithRSAEncryption:
+        scheme = undefined;  /* use PKCS#1 v1.5 padding scheme */
+        break;
+      case oids['RSASSA-PSS']:
+        var hash, mgf;
+
+        /* initialize mgf */
+        hash = oids[child.signatureParameters.mgf.hash.algorithmOid];
+        if(hash === undefined || forge.md[hash] === undefined) {
+          var error = new Error('Unsupported MGF hash function.');
+          error.oid = child.signatureParameters.mgf.hash.algorithmOid;
+          error.name = hash;
+          throw error;
+        }
+
+        mgf = oids[child.signatureParameters.mgf.algorithmOid];
+        if(mgf === undefined || forge.mgf[mgf] === undefined) {
+          var error = new Error('Unsupported MGF function.');
+          error.oid = child.signatureParameters.mgf.algorithmOid;
+          error.name = mgf;
+          throw error;
+        }
+
+        mgf = forge.mgf[mgf].create(forge.md[hash].create());
+
+        /* initialize hash function */
+        hash = oids[child.signatureParameters.hash.algorithmOid];
+        if(hash === undefined || forge.md[hash] === undefined) {
+          throw {
+            message: 'Unsupported RSASSA-PSS hash function.',
+            oid: child.signatureParameters.hash.algorithmOid,
+            name: hash
+          };
+        }
+
+        scheme = forge.pss.create(forge.md[hash].create(), mgf,
+          child.signatureParameters.saltLength);
+        break;
+      }
+
+      // verify signature on cert using public key
+      rval = cert.publicKey.verify(
+        md.digest().getBytes(), child.signature, scheme);
+    }
+
+    return rval;
+  };
+
+  /**
+   * Returns true if this certificate's issuer matches the passed
+   * certificate's subject. Note that no signature check is performed.
+   *
+   * @param parent the certificate to check.
+   *
+   * @return true if this certificate's issuer matches the passed certificate's
+   *         subject.
+   */
+  cert.isIssuer = function(parent) {
+    var rval = false;
+
+    var i = cert.issuer;
+    var s = parent.subject;
+
+    // compare hashes if present
+    if(i.hash && s.hash) {
+      rval = (i.hash === s.hash);
+    } else if(i.attributes.length === s.attributes.length) {
+      // all attributes are the same so issuer matches subject
+      rval = true;
+      var iattr, sattr;
+      for(var n = 0; rval && n < i.attributes.length; ++n) {
+        iattr = i.attributes[n];
+        sattr = s.attributes[n];
+        if(iattr.type !== sattr.type || iattr.value !== sattr.value) {
+          // attribute mismatch
+          rval = false;
+        }
+      }
+    }
+
+    return rval;
+  };
+
+  /**
+   * Returns true if this certificate's subject matches the issuer of the
+   * given certificate). Note that not signature check is performed.
+   *
+   * @param child the certificate to check.
+   *
+   * @return true if this certificate's subject matches the passed
+   *         certificate's issuer.
+   */
+  cert.issued = function(child) {
+    return child.isIssuer(cert);
+  };
+
+  /**
+   * Generates the subjectKeyIdentifier for this certificate as byte buffer.
+   *
+   * @return the subjectKeyIdentifier for this certificate as byte buffer.
+   */
+  cert.generateSubjectKeyIdentifier = function() {
+    /* See: 4.2.1.2 section of the the RFC3280, keyIdentifier is either:
+
+      (1) The keyIdentifier is composed of the 160-bit SHA-1 hash of the
+        value of the BIT STRING subjectPublicKey (excluding the tag,
+        length, and number of unused bits).
+
+      (2) The keyIdentifier is composed of a four bit type field with
+        the value 0100 followed by the least significant 60 bits of the
+        SHA-1 hash of the value of the BIT STRING subjectPublicKey
+        (excluding the tag, length, and number of unused bit string bits).
+    */
+
+    // skipping the tag, length, and number of unused bits is the same
+    // as just using the RSAPublicKey (for RSA keys, which are the
+    // only ones supported)
+    return pki.getPublicKeyFingerprint(cert.publicKey, {type: 'RSAPublicKey'});
+  };
+
+  /**
+   * Verifies the subjectKeyIdentifier extension value for this certificate
+   * against its public key. If no extension is found, false will be
+   * returned.
+   *
+   * @return true if verified, false if not.
+   */
+  cert.verifySubjectKeyIdentifier = function() {
+    var oid = oids['subjectKeyIdentifier'];
+    for(var i = 0; i < cert.extensions.length; ++i) {
+      var ext = cert.extensions[i];
+      if(ext.id === oid) {
+        var ski = cert.generateSubjectKeyIdentifier().getBytes();
+        return (forge.util.hexToBytes(ext.subjectKeyIdentifier) === ski);
+      }
+    }
+    return false;
+  };
+
+  return cert;
+};
+
+/**
+ * Converts an X.509v3 RSA certificate from an ASN.1 object.
+ *
+ * Note: If the certificate is to be verified then compute hash should
+ * be set to true. There is currently no implementation for converting
+ * a certificate back to ASN.1 so the TBSCertificate part of the ASN.1
+ * object needs to be scanned before the cert object is created.
+ *
+ * @param obj the asn1 representation of an X.509v3 RSA certificate.
+ * @param computeHash true to compute the hash for verification.
+ *
+ * @return the certificate.
+ */
+pki.certificateFromAsn1 = function(obj, computeHash) {
+  // validate certificate and capture data
+  var capture = {};
+  var errors = [];
+  if(!asn1.validate(obj, x509CertificateValidator, capture, errors)) {
+    var error = new Error('Cannot read X.509 certificate. ' +
+      'ASN.1 object is not an X509v3 Certificate.');
+    error.errors = errors;
+    throw error;
+  }
+
+  // ensure signature is not interpreted as an embedded ASN.1 object
+  if(typeof capture.certSignature !== 'string') {
+    var certSignature = '\x00';
+    for(var i = 0; i < capture.certSignature.length; ++i) {
+      certSignature += asn1.toDer(capture.certSignature[i]).getBytes();
+    }
+    capture.certSignature = certSignature;
+  }
+
+  // get oid
+  var oid = asn1.derToOid(capture.publicKeyOid);
+  if(oid !== pki.oids['rsaEncryption']) {
+    throw new Error('Cannot read public key. OID is not RSA.');
+  }
+
+  // create certificate
+  var cert = pki.createCertificate();
+  cert.version = capture.certVersion ?
+    capture.certVersion.charCodeAt(0) : 0;
+  var serial = forge.util.createBuffer(capture.certSerialNumber);
+  cert.serialNumber = serial.toHex();
+  cert.signatureOid = forge.asn1.derToOid(capture.certSignatureOid);
+  cert.signatureParameters = _readSignatureParameters(
+    cert.signatureOid, capture.certSignatureParams, true);
+  cert.siginfo.algorithmOid = forge.asn1.derToOid(capture.certinfoSignatureOid);
+  cert.siginfo.parameters = _readSignatureParameters(cert.siginfo.algorithmOid,
+    capture.certinfoSignatureParams, false);
+  // skip "unused bits" in signature value BITSTRING
+  var signature = forge.util.createBuffer(capture.certSignature);
+  ++signature.read;
+  cert.signature = signature.getBytes();
+
+  var validity = [];
+  if(capture.certValidity1UTCTime !== undefined) {
+    validity.push(asn1.utcTimeToDate(capture.certValidity1UTCTime));
+  }
+  if(capture.certValidity2GeneralizedTime !== undefined) {
+    validity.push(asn1.generalizedTimeToDate(
+      capture.certValidity2GeneralizedTime));
+  }
+  if(capture.certValidity3UTCTime !== undefined) {
+    validity.push(asn1.utcTimeToDate(capture.certValidity3UTCTime));
+  }
+  if(capture.certValidity4GeneralizedTime !== undefined) {
+    validity.push(asn1.generalizedTimeToDate(
+      capture.certValidity4GeneralizedTime));
+  }
+  if(validity.length > 2) {
+    throw new Error('Cannot read notBefore/notAfter validity times; more ' +
+      'than two times were provided in the certificate.');
+  }
+  if(validity.length < 2) {
+    throw new Error('Cannot read notBefore/notAfter validity times; they ' +
+      'were not provided as either UTCTime or GeneralizedTime.');
+  }
+  cert.validity.notBefore = validity[0];
+  cert.validity.notAfter = validity[1];
+
+  // keep TBSCertificate to preserve signature when exporting
+  cert.tbsCertificate = capture.tbsCertificate;
+
+  if(computeHash) {
+    // check signature OID for supported signature types
+    cert.md = null;
+    if(cert.signatureOid in oids) {
+      var oid = oids[cert.signatureOid];
+      switch(oid) {
+      case 'sha1WithRSAEncryption':
+        cert.md = forge.md.sha1.create();
+        break;
+      case 'md5WithRSAEncryption':
+        cert.md = forge.md.md5.create();
+        break;
+      case 'sha256WithRSAEncryption':
+        cert.md = forge.md.sha256.create();
+        break;
+      case 'RSASSA-PSS':
+        cert.md = forge.md.sha256.create();
+        break;
+      }
+    }
+    if(cert.md === null) {
+      var error = new Error('Could not compute certificate digest. ' +
+        'Unknown signature OID.');
+      error.signatureOid = cert.signatureOid;
+      throw error;
+    }
+
+    // produce DER formatted TBSCertificate and digest it
+    var bytes = asn1.toDer(cert.tbsCertificate);
+    cert.md.update(bytes.getBytes());
+  }
+
+  // handle issuer, build issuer message digest
+  var imd = forge.md.sha1.create();
+  cert.issuer.getField = function(sn) {
+    return _getAttribute(cert.issuer, sn);
+  };
+  cert.issuer.addField = function(attr) {
+    _fillMissingFields([attr]);
+    cert.issuer.attributes.push(attr);
+  };
+  cert.issuer.attributes = pki.RDNAttributesAsArray(capture.certIssuer, imd);
+  if(capture.certIssuerUniqueId) {
+    cert.issuer.uniqueId = capture.certIssuerUniqueId;
+  }
+  cert.issuer.hash = imd.digest().toHex();
+
+  // handle subject, build subject message digest
+  var smd = forge.md.sha1.create();
+  cert.subject.getField = function(sn) {
+    return _getAttribute(cert.subject, sn);
+  };
+  cert.subject.addField = function(attr) {
+    _fillMissingFields([attr]);
+    cert.subject.attributes.push(attr);
+  };
+  cert.subject.attributes = pki.RDNAttributesAsArray(capture.certSubject, smd);
+  if(capture.certSubjectUniqueId) {
+    cert.subject.uniqueId = capture.certSubjectUniqueId;
+  }
+  cert.subject.hash = smd.digest().toHex();
+
+  // handle extensions
+  if(capture.certExtensions) {
+    cert.extensions = pki.certificateExtensionsFromAsn1(capture.certExtensions);
+  } else {
+    cert.extensions = [];
+  }
+
+  // convert RSA public key from ASN.1
+  cert.publicKey = pki.publicKeyFromAsn1(capture.subjectPublicKeyInfo);
+
+  return cert;
+};
+
+/**
+ * Converts an ASN.1 extensions object (with extension sequences as its
+ * values) into an array of extension objects with types and values.
+ *
+ * Supported extensions:
+ *
+ * id-ce-keyUsage OBJECT IDENTIFIER ::=  { id-ce 15 }
+ * KeyUsage ::= BIT STRING {
+ *   digitalSignature        (0),
+ *   nonRepudiation          (1),
+ *   keyEncipherment         (2),
+ *   dataEncipherment        (3),
+ *   keyAgreement            (4),
+ *   keyCertSign             (5),
+ *   cRLSign                 (6),
+ *   encipherOnly            (7),
+ *   decipherOnly            (8)
+ * }
+ *
+ * id-ce-basicConstraints OBJECT IDENTIFIER ::=  { id-ce 19 }
+ * BasicConstraints ::= SEQUENCE {
+ *   cA                      BOOLEAN DEFAULT FALSE,
+ *   pathLenConstraint       INTEGER (0..MAX) OPTIONAL
+ * }
+ *
+ * subjectAltName EXTENSION ::= {
+ *   SYNTAX GeneralNames
+ *   IDENTIFIED BY id-ce-subjectAltName
+ * }
+ *
+ * GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
+ *
+ * GeneralName ::= CHOICE {
+ *   otherName      [0] INSTANCE OF OTHER-NAME,
+ *   rfc822Name     [1] IA5String,
+ *   dNSName        [2] IA5String,
+ *   x400Address    [3] ORAddress,
+ *   directoryName  [4] Name,
+ *   ediPartyName   [5] EDIPartyName,
+ *   uniformResourceIdentifier [6] IA5String,
+ *   IPAddress      [7] OCTET STRING,
+ *   registeredID   [8] OBJECT IDENTIFIER
+ * }
+ *
+ * OTHER-NAME ::= TYPE-IDENTIFIER
+ *
+ * EDIPartyName ::= SEQUENCE {
+ *   nameAssigner [0] DirectoryString {ub-name} OPTIONAL,
+ *   partyName    [1] DirectoryString {ub-name}
+ * }
+ *
+ * @param exts the extensions ASN.1 with extension sequences to parse.
+ *
+ * @return the array.
+ */
+pki.certificateExtensionsFromAsn1 = function(exts) {
+  var rval = [];
+  for(var i = 0; i < exts.value.length; ++i) {
+    // get extension sequence
+    var extseq = exts.value[i];
+    for(var ei = 0; ei < extseq.value.length; ++ei) {
+      rval.push(pki.certificateExtensionFromAsn1(extseq.value[ei]));
+    }
+  }
+
+  return rval;
+};
+
+/**
+ * Parses a single certificate extension from ASN.1.
+ *
+ * @param ext the extension in ASN.1 format.
+ *
+ * @return the parsed extension as an object.
+ */
+pki.certificateExtensionFromAsn1 = function(ext) {
+  // an extension has:
+  // [0] extnID      OBJECT IDENTIFIER
+  // [1] critical    BOOLEAN DEFAULT FALSE
+  // [2] extnValue   OCTET STRING
+  var e = {};
+  e.id = asn1.derToOid(ext.value[0].value);
+  e.critical = false;
+  if(ext.value[1].type === asn1.Type.BOOLEAN) {
+    e.critical = (ext.value[1].value.charCodeAt(0) !== 0x00);
+    e.value = ext.value[2].value;
+  } else {
+    e.value = ext.value[1].value;
+  }
+  // if the oid is known, get its name
+  if(e.id in oids) {
+    e.name = oids[e.id];
+
+    // handle key usage
+    if(e.name === 'keyUsage') {
+      // get value as BIT STRING
+      var ev = asn1.fromDer(e.value);
+      var b2 = 0x00;
+      var b3 = 0x00;
+      if(ev.value.length > 1) {
+        // skip first byte, just indicates unused bits which
+        // will be padded with 0s anyway
+        // get bytes with flag bits
+        b2 = ev.value.charCodeAt(1);
+        b3 = ev.value.length > 2 ? ev.value.charCodeAt(2) : 0;
+      }
+      // set flags
+      e.digitalSignature = (b2 & 0x80) === 0x80;
+      e.nonRepudiation = (b2 & 0x40) === 0x40;
+      e.keyEncipherment = (b2 & 0x20) === 0x20;
+      e.dataEncipherment = (b2 & 0x10) === 0x10;
+      e.keyAgreement = (b2 & 0x08) === 0x08;
+      e.keyCertSign = (b2 & 0x04) === 0x04;
+      e.cRLSign = (b2 & 0x02) === 0x02;
+      e.encipherOnly = (b2 & 0x01) === 0x01;
+      e.decipherOnly = (b3 & 0x80) === 0x80;
+    } else if(e.name === 'basicConstraints') {
+      // handle basic constraints
+      // get value as SEQUENCE
+      var ev = asn1.fromDer(e.value);
+      // get cA BOOLEAN flag (defaults to false)
+      if(ev.value.length > 0 && ev.value[0].type === asn1.Type.BOOLEAN) {
+        e.cA = (ev.value[0].value.charCodeAt(0) !== 0x00);
+      } else {
+        e.cA = false;
+      }
+      // get path length constraint
+      var value = null;
+      if(ev.value.length > 0 && ev.value[0].type === asn1.Type.INTEGER) {
+        value = ev.value[0].value;
+      } else if(ev.value.length > 1) {
+        value = ev.value[1].value;
+      }
+      if(value !== null) {
+        e.pathLenConstraint = asn1.derToInteger(value);
+      }
+    } else if(e.name === 'extKeyUsage') {
+      // handle extKeyUsage
+      // value is a SEQUENCE of OIDs
+      var ev = asn1.fromDer(e.value);
+      for(var vi = 0; vi < ev.value.length; ++vi) {
+        var oid = asn1.derToOid(ev.value[vi].value);
+        if(oid in oids) {
+          e[oids[oid]] = true;
+        } else {
+          e[oid] = true;
+        }
+      }
+    } else if(e.name === 'nsCertType') {
+      // handle nsCertType
+      // get value as BIT STRING
+      var ev = asn1.fromDer(e.value);
+      var b2 = 0x00;
+      if(ev.value.length > 1) {
+        // skip first byte, just indicates unused bits which
+        // will be padded with 0s anyway
+        // get bytes with flag bits
+        b2 = ev.value.charCodeAt(1);
+      }
+      // set flags
+      e.client = (b2 & 0x80) === 0x80;
+      e.server = (b2 & 0x40) === 0x40;
+      e.email = (b2 & 0x20) === 0x20;
+      e.objsign = (b2 & 0x10) === 0x10;
+      e.reserved = (b2 & 0x08) === 0x08;
+      e.sslCA = (b2 & 0x04) === 0x04;
+      e.emailCA = (b2 & 0x02) === 0x02;
+      e.objCA = (b2 & 0x01) === 0x01;
+    } else if(
+      e.name === 'subjectAltName' ||
+      e.name === 'issuerAltName') {
+      // handle subjectAltName/issuerAltName
+      e.altNames = [];
+
+      // ev is a SYNTAX SEQUENCE
+      var gn;
+      var ev = asn1.fromDer(e.value);
+      for(var n = 0; n < ev.value.length; ++n) {
+        // get GeneralName
+        gn = ev.value[n];
+
+        var altName = {
+          type: gn.type,
+          value: gn.value
+        };
+        e.altNames.push(altName);
+
+        // Note: Support for types 1,2,6,7,8
+        switch(gn.type) {
+        // rfc822Name
+        case 1:
+        // dNSName
+        case 2:
+        // uniformResourceIdentifier (URI)
+        case 6:
+          break;
+        // IPAddress
+        case 7:
+          // convert to IPv4/IPv6 string representation
+          altName.ip = forge.util.bytesToIP(gn.value);
+          break;
+        // registeredID
+        case 8:
+          altName.oid = asn1.derToOid(gn.value);
+          break;
+        default:
+          // unsupported
+        }
+      }
+    } else if(e.name === 'subjectKeyIdentifier') {
+      // value is an OCTETSTRING w/the hash of the key-type specific
+      // public key structure (eg: RSAPublicKey)
+      var ev = asn1.fromDer(e.value);
+      e.subjectKeyIdentifier = forge.util.bytesToHex(ev.value);
+    }
+  }
+  return e;
+};
+
+/**
+ * Converts a PKCS#10 certification request (CSR) from an ASN.1 object.
+ *
+ * Note: If the certification request is to be verified then compute hash
+ * should be set to true. There is currently no implementation for converting
+ * a certificate back to ASN.1 so the CertificationRequestInfo part of the
+ * ASN.1 object needs to be scanned before the csr object is created.
+ *
+ * @param obj the asn1 representation of a PKCS#10 certification request (CSR).
+ * @param computeHash true to compute the hash for verification.
+ *
+ * @return the certification request (CSR).
+ */
+pki.certificationRequestFromAsn1 = function(obj, computeHash) {
+  // validate certification request and capture data
+  var capture = {};
+  var errors = [];
+  if(!asn1.validate(obj, certificationRequestValidator, capture, errors)) {
+    var error = new Error('Cannot read PKCS#10 certificate request. ' +
+      'ASN.1 object is not a PKCS#10 CertificationRequest.');
+    error.errors = errors;
+    throw error;
+  }
+
+  // ensure signature is not interpreted as an embedded ASN.1 object
+  if(typeof capture.csrSignature !== 'string') {
+    var csrSignature = '\x00';
+    for(var i = 0; i < capture.csrSignature.length; ++i) {
+      csrSignature += asn1.toDer(capture.csrSignature[i]).getBytes();
+    }
+    capture.csrSignature = csrSignature;
+  }
+
+  // get oid
+  var oid = asn1.derToOid(capture.publicKeyOid);
+  if(oid !== pki.oids.rsaEncryption) {
+    throw new Error('Cannot read public key. OID is not RSA.');
+  }
+
+  // create certification request
+  var csr = pki.createCertificationRequest();
+  csr.version = capture.csrVersion ? capture.csrVersion.charCodeAt(0) : 0;
+  csr.signatureOid = forge.asn1.derToOid(capture.csrSignatureOid);
+  csr.signatureParameters = _readSignatureParameters(
+    csr.signatureOid, capture.csrSignatureParams, true);
+  csr.siginfo.algorithmOid = forge.asn1.derToOid(capture.csrSignatureOid);
+  csr.siginfo.parameters = _readSignatureParameters(
+    csr.siginfo.algorithmOid, capture.csrSignatureParams, false);
+  // skip "unused bits" in signature value BITSTRING
+  var signature = forge.util.createBuffer(capture.csrSignature);
+  ++signature.read;
+  csr.signature = signature.getBytes();
+
+  // keep CertificationRequestInfo to preserve signature when exporting
+  csr.certificationRequestInfo = capture.certificationRequestInfo;
+
+  if(computeHash) {
+    // check signature OID for supported signature types
+    csr.md = null;
+    if(csr.signatureOid in oids) {
+      var oid = oids[csr.signatureOid];
+      switch(oid) {
+      case 'sha1WithRSAEncryption':
+        csr.md = forge.md.sha1.create();
+        break;
+      case 'md5WithRSAEncryption':
+        csr.md = forge.md.md5.create();
+        break;
+      case 'sha256WithRSAEncryption':
+        csr.md = forge.md.sha256.create();
+        break;
+      case 'RSASSA-PSS':
+        csr.md = forge.md.sha256.create();
+        break;
+      }
+    }
+    if(csr.md === null) {
+      var error = new Error('Could not compute certification request digest. ' +
+        'Unknown signature OID.');
+      error.signatureOid = csr.signatureOid;
+      throw error;
+    }
+
+    // produce DER formatted CertificationRequestInfo and digest it
+    var bytes = asn1.toDer(csr.certificationRequestInfo);
+    csr.md.update(bytes.getBytes());
+  }
+
+  // handle subject, build subject message digest
+  var smd = forge.md.sha1.create();
+  csr.subject.getField = function(sn) {
+    return _getAttribute(csr.subject, sn);
+  };
+  csr.subject.addField = function(attr) {
+    _fillMissingFields([attr]);
+    csr.subject.attributes.push(attr);
+  };
+  csr.subject.attributes = pki.RDNAttributesAsArray(
+    capture.certificationRequestInfoSubject, smd);
+  csr.subject.hash = smd.digest().toHex();
+
+  // convert RSA public key from ASN.1
+  csr.publicKey = pki.publicKeyFromAsn1(capture.subjectPublicKeyInfo);
+
+  // convert attributes from ASN.1
+  csr.getAttribute = function(sn) {
+    return _getAttribute(csr, sn);
+  };
+  csr.addAttribute = function(attr) {
+    _fillMissingFields([attr]);
+    csr.attributes.push(attr);
+  };
+  csr.attributes = pki.CRIAttributesAsArray(
+    capture.certificationRequestInfoAttributes || []);
+
+  return csr;
+};
+
+/**
+ * Creates an empty certification request (a CSR or certificate signing
+ * request). Once created, its public key and attributes can be set and then
+ * it can be signed.
+ *
+ * @return the empty certification request.
+ */
+pki.createCertificationRequest = function() {
+  var csr = {};
+  csr.version = 0x00;
+  csr.signatureOid = null;
+  csr.signature = null;
+  csr.siginfo = {};
+  csr.siginfo.algorithmOid = null;
+
+  csr.subject = {};
+  csr.subject.getField = function(sn) {
+    return _getAttribute(csr.subject, sn);
+  };
+  csr.subject.addField = function(attr) {
+    _fillMissingFields([attr]);
+    csr.subject.attributes.push(attr);
+  };
+  csr.subject.attributes = [];
+  csr.subject.hash = null;
+
+  csr.publicKey = null;
+  csr.attributes = [];
+  csr.getAttribute = function(sn) {
+    return _getAttribute(csr, sn);
+  };
+  csr.addAttribute = function(attr) {
+    _fillMissingFields([attr]);
+    csr.attributes.push(attr);
+  };
+  csr.md = null;
+
+  /**
+   * Sets the subject of this certification request.
+   *
+   * @param attrs the array of subject attributes to use.
+   */
+  csr.setSubject = function(attrs) {
+    // set new attributes
+    _fillMissingFields(attrs);
+    csr.subject.attributes = attrs;
+    csr.subject.hash = null;
+  };
+
+  /**
+   * Sets the attributes of this certification request.
+   *
+   * @param attrs the array of attributes to use.
+   */
+  csr.setAttributes = function(attrs) {
+    // set new attributes
+    _fillMissingFields(attrs);
+    csr.attributes = attrs;
+  };
+
+  /**
+   * Signs this certification request using the given private key.
+   *
+   * @param key the private key to sign with.
+   * @param md the message digest object to use (defaults to forge.md.sha1).
+   */
+  csr.sign = function(key, md) {
+    // TODO: get signature OID from private key
+    csr.md = md || forge.md.sha1.create();
+    var algorithmOid = oids[csr.md.algorithm + 'WithRSAEncryption'];
+    if(!algorithmOid) {
+      var error = new Error('Could not compute certification request digest. ' +
+        'Unknown message digest algorithm OID.');
+      error.algorithm = csr.md.algorithm;
+      throw error;
+    }
+    csr.signatureOid = csr.siginfo.algorithmOid = algorithmOid;
+
+    // get CertificationRequestInfo, convert to DER
+    csr.certificationRequestInfo = pki.getCertificationRequestInfo(csr);
+    var bytes = asn1.toDer(csr.certificationRequestInfo);
+
+    // digest and sign
+    csr.md.update(bytes.getBytes());
+    csr.signature = key.sign(csr.md);
+  };
+
+  /**
+   * Attempts verify the signature on the passed certification request using
+   * its public key.
+   *
+   * A CSR that has been exported to a file in PEM format can be verified using
+   * OpenSSL using this command:
+   *
+   * openssl req -in <the-csr-pem-file> -verify -noout -text
+   *
+   * @return true if verified, false if not.
+   */
+  csr.verify = function() {
+    var rval = false;
+
+    var md = csr.md;
+    if(md === null) {
+      // check signature OID for supported signature types
+      if(csr.signatureOid in oids) {
+        var oid = oids[csr.signatureOid];
+        switch(oid) {
+        case 'sha1WithRSAEncryption':
+          md = forge.md.sha1.create();
+          break;
+        case 'md5WithRSAEncryption':
+          md = forge.md.md5.create();
+          break;
+        case 'sha256WithRSAEncryption':
+          md = forge.md.sha256.create();
+          break;
+        case 'RSASSA-PSS':
+          md = forge.md.sha256.create();
+          break;
+        }
+      }
+      if(md === null) {
+        var error = new Error('Could not compute certification request digest. ' +
+          'Unknown signature OID.');
+        error.signatureOid = csr.signatureOid;
+        throw error;
+      }
+
+      // produce DER formatted CertificationRequestInfo and digest it
+      var cri = csr.certificationRequestInfo ||
+        pki.getCertificationRequestInfo(csr);
+      var bytes = asn1.toDer(cri);
+      md.update(bytes.getBytes());
+    }
+
+    if(md !== null) {
+      var scheme;
+
+      switch(csr.signatureOid) {
+      case oids.sha1WithRSAEncryption:
+        /* use PKCS#1 v1.5 padding scheme */
+        break;
+      case oids['RSASSA-PSS']:
+        var hash, mgf;
+
+        /* initialize mgf */
+        hash = oids[csr.signatureParameters.mgf.hash.algorithmOid];
+        if(hash === undefined || forge.md[hash] === undefined) {
+          var error = new Error('Unsupported MGF hash function.');
+          error.oid = csr.signatureParameters.mgf.hash.algorithmOid;
+          error.name = hash;
+          throw error;
+        }
+
+        mgf = oids[csr.signatureParameters.mgf.algorithmOid];
+        if(mgf === undefined || forge.mgf[mgf] === undefined) {
+          var error = new Error('Unsupported MGF function.');
+          error.oid = csr.signatureParameters.mgf.algorithmOid;
+          error.name = mgf;
+          throw error;
+        }
+
+        mgf = forge.mgf[mgf].create(forge.md[hash].create());
+
+        /* initialize hash function */
+        hash = oids[csr.signatureParameters.hash.algorithmOid];
+        if(hash === undefined || forge.md[hash] === undefined) {
+          var error = new Error('Unsupported RSASSA-PSS hash function.');
+          error.oid = csr.signatureParameters.hash.algorithmOid;
+          error.name = hash;
+          throw error;
+        }
+
+        scheme = forge.pss.create(forge.md[hash].create(), mgf,
+          csr.signatureParameters.saltLength);
+        break;
+      }
+
+      // verify signature on csr using its public key
+      rval = csr.publicKey.verify(
+        md.digest().getBytes(), csr.signature, scheme);
+    }
+
+    return rval;
+  };
+
+  return csr;
+};
+
+/**
+ * Converts an X.509 subject or issuer to an ASN.1 RDNSequence.
+ *
+ * @param obj the subject or issuer (distinguished name).
+ *
+ * @return the ASN.1 RDNSequence.
+ */
+function _dnToAsn1(obj) {
+  // create an empty RDNSequence
+  var rval = asn1.create(
+    asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []);
+
+  // iterate over attributes
+  var attr, set;
+  var attrs = obj.attributes;
+  for(var i = 0; i < attrs.length; ++i) {
+    attr = attrs[i];
+    var value = attr.value;
+
+    // reuse tag class for attribute value if available
+    var valueTagClass = asn1.Type.PRINTABLESTRING;
+    if('valueTagClass' in attr) {
+      valueTagClass = attr.valueTagClass;
+
+      if(valueTagClass === asn1.Type.UTF8) {
+        value = forge.util.encodeUtf8(value);
+      }
+      // FIXME: handle more encodings
+    }
+
+    // create a RelativeDistinguishedName set
+    // each value in the set is an AttributeTypeAndValue first
+    // containing the type (an OID) and second the value
+    set = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true, [
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+        // AttributeType
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+          asn1.oidToDer(attr.type).getBytes()),
+        // AttributeValue
+        asn1.create(asn1.Class.UNIVERSAL, valueTagClass, false, value)
+      ])
+    ]);
+    rval.value.push(set);
+  }
+
+  return rval;
+}
+
+/**
+ * Gets all printable attributes (typically of an issuer or subject) in a
+ * simplified JSON format for display.
+ *
+ * @param attrs the attributes.
+ *
+ * @return the JSON for display.
+ */
+function _getAttributesAsJson(attrs) {
+  var rval = {};
+  for(var i = 0; i < attrs.length; ++i) {
+    var attr = attrs[i];
+    if(attr.shortName && (
+      attr.valueTagClass === asn1.Type.UTF8 ||
+      attr.valueTagClass === asn1.Type.PRINTABLESTRING ||
+      attr.valueTagClass === asn1.Type.IA5STRING)) {
+      var value = attr.value;
+      if(attr.valueTagClass === asn1.Type.UTF8) {
+        value = forge.util.encodeUtf8(attr.value);
+      }
+      if(!(attr.shortName in rval)) {
+        rval[attr.shortName] = value;
+      } else if(forge.util.isArray(rval[attr.shortName])) {
+        rval[attr.shortName].push(value);
+      } else {
+        rval[attr.shortName] = [rval[attr.shortName], value];
+      }
+    }
+  }
+  return rval;
+}
+
+/**
+ * Fills in missing fields in attributes.
+ *
+ * @param attrs the attributes to fill missing fields in.
+ */
+function _fillMissingFields(attrs) {
+  var attr;
+  for(var i = 0; i < attrs.length; ++i) {
+    attr = attrs[i];
+
+    // populate missing name
+    if(typeof attr.name === 'undefined') {
+      if(attr.type && attr.type in pki.oids) {
+        attr.name = pki.oids[attr.type];
+      } else if(attr.shortName && attr.shortName in _shortNames) {
+        attr.name = pki.oids[_shortNames[attr.shortName]];
+      }
+    }
+
+    // populate missing type (OID)
+    if(typeof attr.type === 'undefined') {
+      if(attr.name && attr.name in pki.oids) {
+        attr.type = pki.oids[attr.name];
+      } else {
+        var error = new Error('Attribute type not specified.');
+        error.attribute = attr;
+        throw error;
+      }
+    }
+
+    // populate missing shortname
+    if(typeof attr.shortName === 'undefined') {
+      if(attr.name && attr.name in _shortNames) {
+        attr.shortName = _shortNames[attr.name];
+      }
+    }
+
+    // convert extensions to value
+    if(attr.type === oids.extensionRequest) {
+      attr.valueConstructed = true;
+      attr.valueTagClass = asn1.Type.SEQUENCE;
+      if(!attr.value && attr.extensions) {
+        attr.value = [];
+        for(var ei = 0; ei < attr.extensions.length; ++ei) {
+          attr.value.push(pki.certificateExtensionToAsn1(
+            _fillMissingExtensionFields(attr.extensions[ei])));
+        }
+      }
+    }
+
+    if(typeof attr.value === 'undefined') {
+      var error = new Error('Attribute value not specified.');
+      error.attribute = attr;
+      throw error;
+    }
+  }
+}
+
+/**
+ * Fills in missing fields in certificate extensions.
+ *
+ * @param e the extension.
+ * @param [options] the options to use.
+ *          [cert] the certificate the extensions are for.
+ *
+ * @return the extension.
+ */
+function _fillMissingExtensionFields(e, options) {
+  options = options || {};
+
+  // populate missing name
+  if(typeof e.name === 'undefined') {
+    if(e.id && e.id in pki.oids) {
+      e.name = pki.oids[e.id];
+    }
+  }
+
+  // populate missing id
+  if(typeof e.id === 'undefined') {
+    if(e.name && e.name in pki.oids) {
+      e.id = pki.oids[e.name];
+    } else {
+      var error = new Error('Extension ID not specified.');
+      error.extension = e;
+      throw error;
+    }
+  }
+
+  if(typeof e.value !== 'undefined') {
+    return;
+  }
+
+  // handle missing value:
+
+  // value is a BIT STRING
+  if(e.name === 'keyUsage') {
+    // build flags
+    var unused = 0;
+    var b2 = 0x00;
+    var b3 = 0x00;
+    if(e.digitalSignature) {
+      b2 |= 0x80;
+      unused = 7;
+    }
+    if(e.nonRepudiation) {
+      b2 |= 0x40;
+      unused = 6;
+    }
+    if(e.keyEncipherment) {
+      b2 |= 0x20;
+      unused = 5;
+    }
+    if(e.dataEncipherment) {
+      b2 |= 0x10;
+      unused = 4;
+    }
+    if(e.keyAgreement) {
+      b2 |= 0x08;
+      unused = 3;
+    }
+    if(e.keyCertSign) {
+      b2 |= 0x04;
+      unused = 2;
+    }
+    if(e.cRLSign) {
+      b2 |= 0x02;
+      unused = 1;
+    }
+    if(e.encipherOnly) {
+      b2 |= 0x01;
+      unused = 0;
+    }
+    if(e.decipherOnly) {
+      b3 |= 0x80;
+      unused = 7;
+    }
+
+    // create bit string
+    var value = String.fromCharCode(unused);
+    if(b3 !== 0) {
+      value += String.fromCharCode(b2) + String.fromCharCode(b3);
+    } else if(b2 !== 0) {
+      value += String.fromCharCode(b2);
+    }
+    e.value = asn1.create(
+      asn1.Class.UNIVERSAL, asn1.Type.BITSTRING, false, value);
+  } else if(e.name === 'basicConstraints') {
+    // basicConstraints is a SEQUENCE
+    e.value = asn1.create(
+      asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []);
+    // cA BOOLEAN flag defaults to false
+    if(e.cA) {
+      e.value.value.push(asn1.create(
+        asn1.Class.UNIVERSAL, asn1.Type.BOOLEAN, false,
+        String.fromCharCode(0xFF)));
+    }
+    if('pathLenConstraint' in e) {
+      e.value.value.push(asn1.create(
+        asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+        asn1.integerToDer(e.pathLenConstraint).getBytes()));
+    }
+  } else if(e.name === 'extKeyUsage') {
+    // extKeyUsage is a SEQUENCE of OIDs
+    e.value = asn1.create(
+      asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []);
+    var seq = e.value.value;
+    for(var key in e) {
+      if(e[key] !== true) {
+        continue;
+      }
+      // key is name in OID map
+      if(key in oids) {
+        seq.push(asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID,
+          false, asn1.oidToDer(oids[key]).getBytes()));
+      } else if(key.indexOf('.') !== -1) {
+        // assume key is an OID
+        seq.push(asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID,
+          false, asn1.oidToDer(key).getBytes()));
+      }
+    }
+  } else if(e.name === 'nsCertType') {
+    // nsCertType is a BIT STRING
+    // build flags
+    var unused = 0;
+    var b2 = 0x00;
+
+    if(e.client) {
+      b2 |= 0x80;
+      unused = 7;
+    }
+    if(e.server) {
+      b2 |= 0x40;
+      unused = 6;
+    }
+    if(e.email) {
+      b2 |= 0x20;
+      unused = 5;
+    }
+    if(e.objsign) {
+      b2 |= 0x10;
+      unused = 4;
+    }
+    if(e.reserved) {
+      b2 |= 0x08;
+      unused = 3;
+    }
+    if(e.sslCA) {
+      b2 |= 0x04;
+      unused = 2;
+    }
+    if(e.emailCA) {
+      b2 |= 0x02;
+      unused = 1;
+    }
+    if(e.objCA) {
+      b2 |= 0x01;
+      unused = 0;
+    }
+
+    // create bit string
+    var value = String.fromCharCode(unused);
+    if(b2 !== 0) {
+      value += String.fromCharCode(b2);
+    }
+    e.value = asn1.create(
+      asn1.Class.UNIVERSAL, asn1.Type.BITSTRING, false, value);
+  } else if(e.name === 'subjectAltName' || e.name === 'issuerAltName') {
+    // SYNTAX SEQUENCE
+    e.value = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []);
+
+    var altName;
+    for(var n = 0; n < e.altNames.length; ++n) {
+      altName = e.altNames[n];
+      var value = altName.value;
+      // handle IP
+      if(altName.type === 7 && altName.ip) {
+        value = forge.util.bytesFromIP(altName.ip);
+        if(value === null) {
+          var error = new Error(
+            'Extension "ip" value is not a valid IPv4 or IPv6 address.');
+          error.extension = e;
+          throw error;
+        }
+      } else if(altName.type === 8) {
+        // handle OID
+        if(altName.oid) {
+          value = asn1.oidToDer(asn1.oidToDer(altName.oid));
+        } else {
+          // deprecated ... convert value to OID
+          value = asn1.oidToDer(value);
+        }
+      }
+      e.value.value.push(asn1.create(
+        asn1.Class.CONTEXT_SPECIFIC, altName.type, false,
+        value));
+    }
+  } else if(e.name === 'subjectKeyIdentifier' && options.cert) {
+    var ski = options.cert.generateSubjectKeyIdentifier();
+    e.subjectKeyIdentifier = ski.toHex();
+    // OCTETSTRING w/digest
+    e.value = asn1.create(
+      asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, ski.getBytes());
+  }
+
+  // ensure value has been defined by now
+  if(typeof e.value === 'undefined') {
+    var error = new Error('Extension value not specified.');
+    error.extension = e;
+    throw error;
+  }
+
+  return e;
+}
+
+/**
+ * Convert signature parameters object to ASN.1
+ *
+ * @param {String} oid Signature algorithm OID
+ * @param params The signature parametrs object
+ * @return ASN.1 object representing signature parameters
+ */
+function _signatureParametersToAsn1(oid, params) {
+  switch(oid) {
+  case oids['RSASSA-PSS']:
+    var parts = [];
+
+    if(params.hash.algorithmOid !== undefined) {
+      parts.push(asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+            asn1.oidToDer(params.hash.algorithmOid).getBytes()),
+          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
+        ])
+      ]));
+    }
+
+    if(params.mgf.algorithmOid !== undefined) {
+      parts.push(asn1.create(asn1.Class.CONTEXT_SPECIFIC, 1, true, [
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+            asn1.oidToDer(params.mgf.algorithmOid).getBytes()),
+          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+            asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+              asn1.oidToDer(params.mgf.hash.algorithmOid).getBytes()),
+            asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
+          ])
+        ])
+      ]));
+    }
+
+    if(params.saltLength !== undefined) {
+      parts.push(asn1.create(asn1.Class.CONTEXT_SPECIFIC, 2, true, [
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+          asn1.integerToDer(params.saltLength).getBytes())
+      ]));
+    }
+
+    return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, parts);
+
+  default:
+    return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '');
+  }
+}
+
+/**
+ * Converts a certification request's attributes to an ASN.1 set of
+ * CRIAttributes.
+ *
+ * @param csr certification request.
+ *
+ * @return the ASN.1 set of CRIAttributes.
+ */
+function _CRIAttributesToAsn1(csr) {
+  // create an empty context-specific container
+  var rval = asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, []);
+
+  // no attributes, return empty container
+  if(csr.attributes.length === 0) {
+    return rval;
+  }
+
+  // each attribute has a sequence with a type and a set of values
+  var attrs = csr.attributes;
+  for(var i = 0; i < attrs.length; ++i) {
+    var attr = attrs[i];
+    var value = attr.value;
+
+    // reuse tag class for attribute value if available
+    var valueTagClass = asn1.Type.UTF8;
+    if('valueTagClass' in attr) {
+      valueTagClass = attr.valueTagClass;
+    }
+    if(valueTagClass === asn1.Type.UTF8) {
+      value = forge.util.encodeUtf8(value);
+    }
+    var valueConstructed = false;
+    if('valueConstructed' in attr) {
+      valueConstructed = attr.valueConstructed;
+    }
+    // FIXME: handle more encodings
+
+    // create a RelativeDistinguishedName set
+    // each value in the set is an AttributeTypeAndValue first
+    // containing the type (an OID) and second the value
+    var seq = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+      // AttributeType
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+        asn1.oidToDer(attr.type).getBytes()),
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true, [
+        // AttributeValue
+        asn1.create(
+          asn1.Class.UNIVERSAL, valueTagClass, valueConstructed, value)
+      ])
+    ]);
+    rval.value.push(seq);
+  }
+
+  return rval;
+}
+
+/**
+ * Gets the ASN.1 TBSCertificate part of an X.509v3 certificate.
+ *
+ * @param cert the certificate.
+ *
+ * @return the asn1 TBSCertificate.
+ */
+pki.getTBSCertificate = function(cert) {
+  // TBSCertificate
+  var tbs = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+    // version
+    asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
+      // integer
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+        asn1.integerToDer(cert.version).getBytes())
+    ]),
+    // serialNumber
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      forge.util.hexToBytes(cert.serialNumber)),
+    // signature
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+      // algorithm
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+        asn1.oidToDer(cert.siginfo.algorithmOid).getBytes()),
+      // parameters
+      _signatureParametersToAsn1(
+        cert.siginfo.algorithmOid, cert.siginfo.parameters)
+    ]),
+    // issuer
+    _dnToAsn1(cert.issuer),
+    // validity
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+      // notBefore
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.UTCTIME, false,
+        asn1.dateToUtcTime(cert.validity.notBefore)),
+      // notAfter
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.UTCTIME, false,
+        asn1.dateToUtcTime(cert.validity.notAfter))
+    ]),
+    // subject
+    _dnToAsn1(cert.subject),
+    // SubjectPublicKeyInfo
+    pki.publicKeyToAsn1(cert.publicKey)
+  ]);
+
+  if(cert.issuer.uniqueId) {
+    // issuerUniqueID (optional)
+    tbs.value.push(
+      asn1.create(asn1.Class.CONTEXT_SPECIFIC, 1, true, [
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.BITSTRING, false,
+          String.fromCharCode(0x00) +
+          cert.issuer.uniqueId
+        )
+      ])
+    );
+  }
+  if(cert.subject.uniqueId) {
+    // subjectUniqueID (optional)
+    tbs.value.push(
+      asn1.create(asn1.Class.CONTEXT_SPECIFIC, 2, true, [
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.BITSTRING, false,
+          String.fromCharCode(0x00) +
+          cert.subject.uniqueId
+        )
+      ])
+    );
+  }
+
+  if(cert.extensions.length > 0) {
+    // extensions (optional)
+    tbs.value.push(pki.certificateExtensionsToAsn1(cert.extensions));
+  }
+
+  return tbs;
+};
+
+/**
+ * Gets the ASN.1 CertificationRequestInfo part of a
+ * PKCS#10 CertificationRequest.
+ *
+ * @param csr the certification request.
+ *
+ * @return the asn1 CertificationRequestInfo.
+ */
+pki.getCertificationRequestInfo = function(csr) {
+  // CertificationRequestInfo
+  var cri = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+    // version
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      asn1.integerToDer(csr.version).getBytes()),
+    // subject
+    _dnToAsn1(csr.subject),
+    // SubjectPublicKeyInfo
+    pki.publicKeyToAsn1(csr.publicKey),
+    // attributes
+    _CRIAttributesToAsn1(csr)
+  ]);
+
+  return cri;
+};
+
+/**
+ * Converts a DistinguishedName (subject or issuer) to an ASN.1 object.
+ *
+ * @param dn the DistinguishedName.
+ *
+ * @return the asn1 representation of a DistinguishedName.
+ */
+pki.distinguishedNameToAsn1 = function(dn) {
+  return _dnToAsn1(dn);
+};
+
+/**
+ * Converts an X.509v3 RSA certificate to an ASN.1 object.
+ *
+ * @param cert the certificate.
+ *
+ * @return the asn1 representation of an X.509v3 RSA certificate.
+ */
+pki.certificateToAsn1 = function(cert) {
+  // prefer cached TBSCertificate over generating one
+  var tbsCertificate = cert.tbsCertificate || pki.getTBSCertificate(cert);
+
+  // Certificate
+  return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+    // TBSCertificate
+    tbsCertificate,
+    // AlgorithmIdentifier (signature algorithm)
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+      // algorithm
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+        asn1.oidToDer(cert.signatureOid).getBytes()),
+      // parameters
+      _signatureParametersToAsn1(cert.signatureOid, cert.signatureParameters)
+    ]),
+    // SignatureValue
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.BITSTRING, false,
+      String.fromCharCode(0x00) + cert.signature)
+  ]);
+};
+
+/**
+ * Converts X.509v3 certificate extensions to ASN.1.
+ *
+ * @param exts the extensions to convert.
+ *
+ * @return the extensions in ASN.1 format.
+ */
+pki.certificateExtensionsToAsn1 = function(exts) {
+  // create top-level extension container
+  var rval = asn1.create(asn1.Class.CONTEXT_SPECIFIC, 3, true, []);
+
+  // create extension sequence (stores a sequence for each extension)
+  var seq = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []);
+  rval.value.push(seq);
+
+  for(var i = 0; i < exts.length; ++i) {
+    seq.value.push(pki.certificateExtensionToAsn1(exts[i]));
+  }
+
+  return rval;
+};
+
+/**
+ * Converts a single certificate extension to ASN.1.
+ *
+ * @param ext the extension to convert.
+ *
+ * @return the extension in ASN.1 format.
+ */
+pki.certificateExtensionToAsn1 = function(ext) {
+  // create a sequence for each extension
+  var extseq = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []);
+
+  // extnID (OID)
+  extseq.value.push(asn1.create(
+    asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+    asn1.oidToDer(ext.id).getBytes()));
+
+  // critical defaults to false
+  if(ext.critical) {
+    // critical BOOLEAN DEFAULT FALSE
+    extseq.value.push(asn1.create(
+      asn1.Class.UNIVERSAL, asn1.Type.BOOLEAN, false,
+      String.fromCharCode(0xFF)));
+  }
+
+  var value = ext.value;
+  if(typeof ext.value !== 'string') {
+    // value is asn.1
+    value = asn1.toDer(value).getBytes();
+  }
+
+  // extnValue (OCTET STRING)
+  extseq.value.push(asn1.create(
+    asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, value));
+
+  return extseq;
+};
+
+/**
+ * Converts a PKCS#10 certification request to an ASN.1 object.
+ *
+ * @param csr the certification request.
+ *
+ * @return the asn1 representation of a certification request.
+ */
+pki.certificationRequestToAsn1 = function(csr) {
+  // prefer cached CertificationRequestInfo over generating one
+  var cri = csr.certificationRequestInfo ||
+    pki.getCertificationRequestInfo(csr);
+
+  // Certificate
+  return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+    // CertificationRequestInfo
+    cri,
+    // AlgorithmIdentifier (signature algorithm)
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+      // algorithm
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+        asn1.oidToDer(csr.signatureOid).getBytes()),
+      // parameters
+      _signatureParametersToAsn1(csr.signatureOid, csr.signatureParameters)
+    ]),
+    // signature
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.BITSTRING, false,
+      String.fromCharCode(0x00) + csr.signature)
+  ]);
+};
+
+/**
+ * Creates a CA store.
+ *
+ * @param certs an optional array of certificate objects or PEM-formatted
+ *          certificate strings to add to the CA store.
+ *
+ * @return the CA store.
+ */
+pki.createCaStore = function(certs) {
+  // create CA store
+  var caStore = {
+    // stored certificates
+    certs: {}
+  };
+
+  /**
+   * Gets the certificate that issued the passed certificate or its
+   * 'parent'.
+   *
+   * @param cert the certificate to get the parent for.
+   *
+   * @return the parent certificate or null if none was found.
+   */
+  caStore.getIssuer = function(cert) {
+    var rval = getBySubject(cert.issuer);
+
+    // see if there are multiple matches
+    /*if(forge.util.isArray(rval)) {
+      // TODO: resolve multiple matches by checking
+      // authorityKey/subjectKey/issuerUniqueID/other identifiers, etc.
+      // FIXME: or alternatively do authority key mapping
+      // if possible (X.509v1 certs can't work?)
+      throw new Error('Resolving multiple issuer matches not implemented yet.');
+    }*/
+
+    return rval;
+  };
+
+  /**
+   * Adds a trusted certificate to the store.
+   *
+   * @param cert the certificate to add as a trusted certificate (either a
+   *          pki.certificate object or a PEM-formatted certificate).
+   */
+  caStore.addCertificate = function(cert) {
+    // convert from pem if necessary
+    if(typeof cert === 'string') {
+      cert = forge.pki.certificateFromPem(cert);
+    }
+
+    // produce subject hash if it doesn't exist
+    if(!cert.subject.hash) {
+      var md = forge.md.sha1.create();
+      cert.subject.attributes =  pki.RDNAttributesAsArray(
+        _dnToAsn1(cert.subject), md);
+      cert.subject.hash = md.digest().toHex();
+    }
+
+    if(cert.subject.hash in caStore.certs) {
+      // subject hash already exists, append to array
+      var tmp = caStore.certs[cert.subject.hash];
+      if(!forge.util.isArray(tmp)) {
+        tmp = [tmp];
+      }
+      tmp.push(cert);
+    } else {
+      caStore.certs[cert.subject.hash] = cert;
+    }
+  };
+
+  /**
+   * Checks to see if the given certificate is in the store.
+   *
+   * @param cert the certificate to check.
+   *
+   * @return true if the certificate is in the store, false if not.
+   */
+  caStore.hasCertificate = function(cert) {
+    var match = getBySubject(cert.subject);
+    if(!match) {
+      return false;
+    }
+    if(!forge.util.isArray(match)) {
+      match = [match];
+    }
+    // compare DER-encoding of certificates
+    var der1 = asn1.toDer(pki.certificateToAsn1(cert)).getBytes();
+    for(var i = 0; i < match.length; ++i) {
+      var der2 = asn1.toDer(pki.certificateToAsn1(match[i])).getBytes();
+      if(der1 === der2) {
+        return true;
+      }
+    }
+    return false;
+  };
+
+  function getBySubject(subject) {
+    // produce subject hash if it doesn't exist
+    if(!subject.hash) {
+      var md = forge.md.sha1.create();
+      subject.attributes =  pki.RDNAttributesAsArray(_dnToAsn1(subject), md);
+      subject.hash = md.digest().toHex();
+    }
+    return caStore.certs[subject.hash] || null;
+  }
+
+  // auto-add passed in certs
+  if(certs) {
+    // parse PEM-formatted certificates as necessary
+    for(var i = 0; i < certs.length; ++i) {
+      var cert = certs[i];
+      caStore.addCertificate(cert);
+    }
+  }
+
+  return caStore;
+};
+
+/**
+ * Certificate verification errors, based on TLS.
+ */
+pki.certificateError = {
+  bad_certificate: 'forge.pki.BadCertificate',
+  unsupported_certificate: 'forge.pki.UnsupportedCertificate',
+  certificate_revoked: 'forge.pki.CertificateRevoked',
+  certificate_expired: 'forge.pki.CertificateExpired',
+  certificate_unknown: 'forge.pki.CertificateUnknown',
+  unknown_ca: 'forge.pki.UnknownCertificateAuthority'
+};
+
+/**
+ * Verifies a certificate chain against the given Certificate Authority store
+ * with an optional custom verify callback.
+ *
+ * @param caStore a certificate store to verify against.
+ * @param chain the certificate chain to verify, with the root or highest
+ *          authority at the end (an array of certificates).
+ * @param verify called for every certificate in the chain.
+ *
+ * The verify callback has the following signature:
+ *
+ * verified - Set to true if certificate was verified, otherwise the
+ *   pki.certificateError for why the certificate failed.
+ * depth - The current index in the chain, where 0 is the end point's cert.
+ * certs - The certificate chain, *NOTE* an empty chain indicates an anonymous
+ *   end point.
+ *
+ * The function returns true on success and on failure either the appropriate
+ * pki.certificateError or an object with 'error' set to the appropriate
+ * pki.certificateError and 'message' set to a custom error message.
+ *
+ * @return true if successful, error thrown if not.
+ */
+pki.verifyCertificateChain = function(caStore, chain, verify) {
+  /* From: RFC3280 - Internet X.509 Public Key Infrastructure Certificate
+    Section 6: Certification Path Validation
+    See inline parentheticals related to this particular implementation.
+
+    The primary goal of path validation is to verify the binding between
+    a subject distinguished name or a subject alternative name and subject
+    public key, as represented in the end entity certificate, based on the
+    public key of the trust anchor. This requires obtaining a sequence of
+    certificates that support that binding. That sequence should be provided
+    in the passed 'chain'. The trust anchor should be in the given CA
+    store. The 'end entity' certificate is the certificate provided by the
+    end point (typically a server) and is the first in the chain.
+
+    To meet this goal, the path validation process verifies, among other
+    things, that a prospective certification path (a sequence of n
+    certificates or a 'chain') satisfies the following conditions:
+
+    (a) for all x in {1, ..., n-1}, the subject of certificate x is
+          the issuer of certificate x+1;
+
+    (b) certificate 1 is issued by the trust anchor;
+
+    (c) certificate n is the certificate to be validated; and
+
+    (d) for all x in {1, ..., n}, the certificate was valid at the
+          time in question.
+
+    Note that here 'n' is index 0 in the chain and 1 is the last certificate
+    in the chain and it must be signed by a certificate in the connection's
+    CA store.
+
+    The path validation process also determines the set of certificate
+    policies that are valid for this path, based on the certificate policies
+    extension, policy mapping extension, policy constraints extension, and
+    inhibit any-policy extension.
+
+    Note: Policy mapping extension not supported (Not Required).
+
+    Note: If the certificate has an unsupported critical extension, then it
+    must be rejected.
+
+    Note: A certificate is self-issued if the DNs that appear in the subject
+    and issuer fields are identical and are not empty.
+
+    The path validation algorithm assumes the following seven inputs are
+    provided to the path processing logic. What this specific implementation
+    will use is provided parenthetically:
+
+    (a) a prospective certification path of length n (the 'chain')
+    (b) the current date/time: ('now').
+    (c) user-initial-policy-set: A set of certificate policy identifiers
+          naming the policies that are acceptable to the certificate user.
+          The user-initial-policy-set contains the special value any-policy
+          if the user is not concerned about certificate policy
+          (Not implemented. Any policy is accepted).
+    (d) trust anchor information, describing a CA that serves as a trust
+          anchor for the certification path. The trust anchor information
+          includes:
+
+      (1)  the trusted issuer name,
+      (2)  the trusted public key algorithm,
+      (3)  the trusted public key, and
+      (4)  optionally, the trusted public key parameters associated
+             with the public key.
+
+      (Trust anchors are provided via certificates in the CA store).
+
+      The trust anchor information may be provided to the path processing
+      procedure in the form of a self-signed certificate. The trusted anchor
+      information is trusted because it was delivered to the path processing
+      procedure by some trustworthy out-of-band procedure. If the trusted
+      public key algorithm requires parameters, then the parameters are
+      provided along with the trusted public key (No parameters used in this
+      implementation).
+
+    (e) initial-policy-mapping-inhibit, which indicates if policy mapping is
+          allowed in the certification path.
+          (Not implemented, no policy checking)
+
+    (f) initial-explicit-policy, which indicates if the path must be valid
+          for at least one of the certificate policies in the user-initial-
+          policy-set.
+          (Not implemented, no policy checking)
+
+    (g) initial-any-policy-inhibit, which indicates whether the
+          anyPolicy OID should be processed if it is included in a
+          certificate.
+          (Not implemented, so any policy is valid provided that it is
+          not marked as critical) */
+
+  /* Basic Path Processing:
+
+    For each certificate in the 'chain', the following is checked:
+
+    1. The certificate validity period includes the current time.
+    2. The certificate was signed by its parent (where the parent is either
+       the next in the chain or from the CA store). Allow processing to
+       continue to the next step if no parent is found but the certificate is
+       in the CA store.
+    3. TODO: The certificate has not been revoked.
+    4. The certificate issuer name matches the parent's subject name.
+    5. TODO: If the certificate is self-issued and not the final certificate
+       in the chain, skip this step, otherwise verify that the subject name
+       is within one of the permitted subtrees of X.500 distinguished names
+       and that each of the alternative names in the subjectAltName extension
+       (critical or non-critical) is within one of the permitted subtrees for
+       that name type.
+    6. TODO: If the certificate is self-issued and not the final certificate
+       in the chain, skip this step, otherwise verify that the subject name
+       is not within one of the excluded subtrees for X.500 distinguished
+       names and none of the subjectAltName extension names are excluded for
+       that name type.
+    7. The other steps in the algorithm for basic path processing involve
+       handling the policy extension which is not presently supported in this
+       implementation. Instead, if a critical policy extension is found, the
+       certificate is rejected as not supported.
+    8. If the certificate is not the first or if its the only certificate in
+       the chain (having no parent from the CA store or is self-signed) and it
+       has a critical key usage extension, verify that the keyCertSign bit is
+       set. If the key usage extension exists, verify that the basic
+       constraints extension exists. If the basic constraints extension exists,
+       verify that the cA flag is set. If pathLenConstraint is set, ensure that
+       the number of certificates that precede in the chain (come earlier
+       in the chain as implemented below), excluding the very first in the
+       chain (typically the end-entity one), isn't greater than the
+       pathLenConstraint. This constraint limits the number of intermediate
+       CAs that may appear below a CA before only end-entity certificates
+       may be issued. */
+
+  // copy cert chain references to another array to protect against changes
+  // in verify callback
+  chain = chain.slice(0);
+  var certs = chain.slice(0);
+
+  // get current date
+  var now = new Date();
+
+  // verify each cert in the chain using its parent, where the parent
+  // is either the next in the chain or from the CA store
+  var first = true;
+  var error = null;
+  var depth = 0;
+  do {
+    var cert = chain.shift();
+    var parent = null;
+    var selfSigned = false;
+
+    // 1. check valid time
+    if(now < cert.validity.notBefore || now > cert.validity.notAfter) {
+      error = {
+        message: 'Certificate is not valid yet or has expired.',
+        error: pki.certificateError.certificate_expired,
+        notBefore: cert.validity.notBefore,
+        notAfter: cert.validity.notAfter,
+        now: now
+      };
+    }
+
+    // 2. verify with parent from chain or CA store
+    if(error === null) {
+      parent = chain[0] || caStore.getIssuer(cert);
+      if(parent === null) {
+        // check for self-signed cert
+        if(cert.isIssuer(cert)) {
+          selfSigned = true;
+          parent = cert;
+        }
+      }
+
+      if(parent) {
+        // FIXME: current CA store implementation might have multiple
+        // certificates where the issuer can't be determined from the
+        // certificate (happens rarely with, eg: old certificates) so normalize
+        // by always putting parents into an array
+        // TODO: there's may be an extreme degenerate case currently uncovered
+        // where an old intermediate certificate seems to have a matching parent
+        // but none of the parents actually verify ... but the intermediate
+        // is in the CA and it should pass this check; needs investigation
+        var parents = parent;
+        if(!forge.util.isArray(parents)) {
+          parents = [parents];
+        }
+
+        // try to verify with each possible parent (typically only one)
+        var verified = false;
+        while(!verified && parents.length > 0) {
+          parent = parents.shift();
+          try {
+            verified = parent.verify(cert);
+          } catch(ex) {
+            // failure to verify, don't care why, try next one
+          }
+        }
+
+        if(!verified) {
+          error = {
+            message: 'Certificate signature is invalid.',
+            error: pki.certificateError.bad_certificate
+          };
+        }
+      }
+
+      if(error === null && (!parent || selfSigned) &&
+        !caStore.hasCertificate(cert)) {
+        // no parent issuer and certificate itself is not trusted
+        error = {
+          message: 'Certificate is not trusted.',
+          error: pki.certificateError.unknown_ca
+        };
+      }
+    }
+
+    // TODO: 3. check revoked
+
+    // 4. check for matching issuer/subject
+    if(error === null && parent && !cert.isIssuer(parent)) {
+      // parent is not issuer
+      error = {
+        message: 'Certificate issuer is invalid.',
+        error: pki.certificateError.bad_certificate
+      };
+    }
+
+    // 5. TODO: check names with permitted names tree
+
+    // 6. TODO: check names against excluded names tree
+
+    // 7. check for unsupported critical extensions
+    if(error === null) {
+      // supported extensions
+      var se = {
+        keyUsage: true,
+        basicConstraints: true
+      };
+      for(var i = 0; error === null && i < cert.extensions.length; ++i) {
+        var ext = cert.extensions[i];
+        if(ext.critical && !(ext.name in se)) {
+          error = {
+            message:
+              'Certificate has an unsupported critical extension.',
+            error: pki.certificateError.unsupported_certificate
+          };
+        }
+      }
+    }
+
+    // 8. check for CA if cert is not first or is the only certificate
+    // remaining in chain with no parent or is self-signed
+    if(error === null &&
+      (!first || (chain.length === 0 && (!parent || selfSigned)))) {
+      // first check keyUsage extension and then basic constraints
+      var bcExt = cert.getExtension('basicConstraints');
+      var keyUsageExt = cert.getExtension('keyUsage');
+      if(keyUsageExt !== null) {
+        // keyCertSign must be true and there must be a basic
+        // constraints extension
+        if(!keyUsageExt.keyCertSign || bcExt === null) {
+          // bad certificate
+          error = {
+            message:
+              'Certificate keyUsage or basicConstraints conflict ' +
+              'or indicate that the certificate is not a CA. ' +
+              'If the certificate is the only one in the chain or ' +
+              'isn\'t the first then the certificate must be a ' +
+              'valid CA.',
+            error: pki.certificateError.bad_certificate
+          };
+        }
+      }
+      // basic constraints cA flag must be set
+      if(error === null && bcExt !== null && !bcExt.cA) {
+        // bad certificate
+        error = {
+          message:
+            'Certificate basicConstraints indicates the certificate ' +
+            'is not a CA.',
+          error: pki.certificateError.bad_certificate
+        };
+      }
+      // if error is not null and keyUsage is available, then we know it
+      // has keyCertSign and there is a basic constraints extension too,
+      // which means we can check pathLenConstraint (if it exists)
+      if(error === null && keyUsageExt !== null &&
+        'pathLenConstraint' in bcExt) {
+        // pathLen is the maximum # of intermediate CA certs that can be
+        // found between the current certificate and the end-entity (depth 0)
+        // certificate; this number does not include the end-entity (depth 0,
+        // last in the chain) even if it happens to be a CA certificate itself
+        var pathLen = depth - 1;
+        if(pathLen > bcExt.pathLenConstraint) {
+          // pathLenConstraint violated, bad certificate
+          error = {
+            message:
+              'Certificate basicConstraints pathLenConstraint violated.',
+            error: pki.certificateError.bad_certificate
+          };
+        }
+      }
+    }
+
+    // call application callback
+    var vfd = (error === null) ? true : error.error;
+    var ret = verify ? verify(vfd, depth, certs) : vfd;
+    if(ret === true) {
+      // clear any set error
+      error = null;
+    } else {
+      // if passed basic tests, set default message and alert
+      if(vfd === true) {
+        error = {
+          message: 'The application rejected the certificate.',
+          error: pki.certificateError.bad_certificate
+        };
+      }
+
+      // check for custom error info
+      if(ret || ret === 0) {
+        // set custom message and error
+        if(typeof ret === 'object' && !forge.util.isArray(ret)) {
+          if(ret.message) {
+             error.message = ret.message;
+          }
+          if(ret.error) {
+            error.error = ret.error;
+          }
+        } else if(typeof ret === 'string') {
+          // set custom error
+          error.error = ret;
+        }
+      }
+
+      // throw error
+      throw error;
+    }
+
+    // no longer first cert in chain
+    first = false;
+    ++depth;
+  } while(chain.length > 0);
+
+  return true;
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'x509';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge.pki;
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define([
+  'require',
+  'module',
+  './aes',
+  './asn1',
+  './des',
+  './md',
+  './mgf',
+  './oids',
+  './pem',
+  './pss',
+  './rsa',
+  './util'
+], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/xhr.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/xhr.js
new file mode 100644
index 0000000..96082ad
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/js/xhr.js
@@ -0,0 +1,739 @@
+/**
+ * XmlHttpRequest implementation that uses TLS and flash SocketPool.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2013 Digital Bazaar, Inc.
+ */
+(function($) {
+
+// logging category
+var cat = 'forge.xhr';
+
+/*
+XMLHttpRequest interface definition from:
+http://www.w3.org/TR/XMLHttpRequest
+
+interface XMLHttpRequest {
+  // event handler
+  attribute EventListener onreadystatechange;
+
+  // state
+  const unsigned short UNSENT = 0;
+  const unsigned short OPENED = 1;
+  const unsigned short HEADERS_RECEIVED = 2;
+  const unsigned short LOADING = 3;
+  const unsigned short DONE = 4;
+  readonly attribute unsigned short readyState;
+
+  // request
+  void open(in DOMString method, in DOMString url);
+  void open(in DOMString method, in DOMString url, in boolean async);
+  void open(in DOMString method, in DOMString url,
+            in boolean async, in DOMString user);
+  void open(in DOMString method, in DOMString url,
+            in boolean async, in DOMString user, in DOMString password);
+  void setRequestHeader(in DOMString header, in DOMString value);
+  void send();
+  void send(in DOMString data);
+  void send(in Document data);
+  void abort();
+
+  // response
+  DOMString getAllResponseHeaders();
+  DOMString getResponseHeader(in DOMString header);
+  readonly attribute DOMString responseText;
+  readonly attribute Document responseXML;
+  readonly attribute unsigned short status;
+  readonly attribute DOMString statusText;
+};
+*/
+
+// readyStates
+var UNSENT = 0;
+var OPENED = 1;
+var HEADERS_RECEIVED = 2;
+var LOADING = 3;
+var DONE = 4;
+
+// exceptions
+var INVALID_STATE_ERR = 11;
+var SYNTAX_ERR = 12;
+var SECURITY_ERR = 18;
+var NETWORK_ERR = 19;
+var ABORT_ERR = 20;
+
+// private flash socket pool vars
+var _sp = null;
+var _policyPort = 0;
+var _policyUrl = null;
+
+// default client (used if no special URL provided when creating an XHR)
+var _client = null;
+
+// all clients including the default, key'd by full base url
+// (multiple cross-domain http clients are permitted so there may be more
+// than one client in this map)
+// TODO: provide optional clean up API for non-default clients
+var _clients = {};
+
+// the default maximum number of concurrents connections per client
+var _maxConnections = 10;
+
+// local aliases
+if(typeof forge === 'undefined') {
+  forge = {};
+}
+var net = forge.net;
+var http = forge.http;
+
+// define the xhr interface
+var xhrApi = {};
+
+/**
+ * Initializes flash XHR support.
+ *
+ * @param options:
+ *   url: the default base URL to connect to if xhr URLs are relative,
+ *     ie: https://myserver.com.
+ *   flashId: the dom ID of the flash SocketPool.
+ *   policyPort: the port that provides the server's flash policy, 0 to use
+ *     the flash default.
+ *   policyUrl: the policy file URL to use instead of a policy port.
+ *   msie: true if browser is internet explorer, false if not.
+ *   connections: the maximum number of concurrent connections.
+ *   caCerts: a list of PEM-formatted certificates to trust.
+ *   cipherSuites: an optional array of cipher suites to use,
+ *     see forge.tls.CipherSuites.
+ *   verify: optional TLS certificate verify callback to use (see forge.tls
+ *     for details).
+ *   getCertificate: an optional callback used to get a client-side
+ *     certificate (see forge.tls for details).
+ *   getPrivateKey: an optional callback used to get a client-side private
+ *     key (see forge.tls for details).
+ *   getSignature: an optional callback used to get a client-side signature
+ *     (see forge.tls for details).
+ *   persistCookies: true to use persistent cookies via flash local storage,
+ *     false to only keep cookies in javascript.
+ *   primeTlsSockets: true to immediately connect TLS sockets on their
+ *     creation so that they will cache TLS sessions for reuse.
+ */
+xhrApi.init = function(options) {
+  forge.log.debug(cat, 'initializing', options);
+
+  // update default policy port and max connections
+  _policyPort = options.policyPort || _policyPort;
+  _policyUrl = options.policyUrl || _policyUrl;
+  _maxConnections = options.connections || _maxConnections;
+
+  // create the flash socket pool
+  _sp = net.createSocketPool({
+    flashId: options.flashId,
+    policyPort: _policyPort,
+    policyUrl: _policyUrl,
+    msie: options.msie || false
+  });
+
+  // create default http client
+  _client = http.createClient({
+    url: options.url || (
+      window.location.protocol + '//' + window.location.host),
+    socketPool: _sp,
+    policyPort: _policyPort,
+    policyUrl: _policyUrl,
+    connections: options.connections || _maxConnections,
+    caCerts: options.caCerts,
+    cipherSuites: options.cipherSuites,
+    persistCookies: options.persistCookies || true,
+    primeTlsSockets: options.primeTlsSockets || false,
+    verify: options.verify,
+    getCertificate: options.getCertificate,
+    getPrivateKey: options.getPrivateKey,
+    getSignature: options.getSignature
+  });
+  _clients[_client.url.full] = _client;
+
+  forge.log.debug(cat, 'ready');
+};
+
+/**
+ * Called to clean up the clients and socket pool.
+ */
+xhrApi.cleanup = function() {
+  // destroy all clients
+  for(var key in _clients) {
+    _clients[key].destroy();
+  }
+  _clients = {};
+  _client = null;
+
+  // destroy socket pool
+  _sp.destroy();
+  _sp = null;
+};
+
+/**
+ * Sets a cookie.
+ *
+ * @param cookie the cookie with parameters:
+ *   name: the name of the cookie.
+ *   value: the value of the cookie.
+ *   comment: an optional comment string.
+ *   maxAge: the age of the cookie in seconds relative to created time.
+ *   secure: true if the cookie must be sent over a secure protocol.
+ *   httpOnly: true to restrict access to the cookie from javascript
+ *     (inaffective since the cookies are stored in javascript).
+ *   path: the path for the cookie.
+ *   domain: optional domain the cookie belongs to (must start with dot).
+ *   version: optional version of the cookie.
+ *   created: creation time, in UTC seconds, of the cookie.
+ */
+xhrApi.setCookie = function(cookie) {
+  // default cookie expiration to never
+  cookie.maxAge = cookie.maxAge || -1;
+
+  // if the cookie's domain is set, use the appropriate client
+  if(cookie.domain) {
+    // add the cookies to the applicable domains
+    for(var key in _clients) {
+      var client = _clients[key];
+      if(http.withinCookieDomain(client.url, cookie) &&
+        client.secure === cookie.secure) {
+        client.setCookie(cookie);
+      }
+    }
+  } else {
+    // use the default domain
+    // FIXME: should a null domain cookie be added to all clients? should
+    // this be an option?
+    _client.setCookie(cookie);
+  }
+};
+
+/**
+ * Gets a cookie.
+ *
+ * @param name the name of the cookie.
+ * @param path an optional path for the cookie (if there are multiple cookies
+ *          with the same name but different paths).
+ * @param domain an optional domain for the cookie (if not using the default
+ *          domain).
+ *
+ * @return the cookie, cookies (if multiple matches), or null if not found.
+ */
+xhrApi.getCookie = function(name, path, domain) {
+  var rval = null;
+
+  if(domain) {
+    // get the cookies from the applicable domains
+    for(var key in _clients) {
+      var client = _clients[key];
+      if(http.withinCookieDomain(client.url, domain)) {
+        var cookie = client.getCookie(name, path);
+        if(cookie !== null) {
+          if(rval === null) {
+            rval = cookie;
+          } else if(!forge.util.isArray(rval)) {
+            rval = [rval, cookie];
+          } else {
+            rval.push(cookie);
+          }
+        }
+      }
+    }
+  } else {
+    // get cookie from default domain
+    rval = _client.getCookie(name, path);
+  }
+
+  return rval;
+};
+
+/**
+ * Removes a cookie.
+ *
+ * @param name the name of the cookie.
+ * @param path an optional path for the cookie (if there are multiple cookies
+ *          with the same name but different paths).
+ * @param domain an optional domain for the cookie (if not using the default
+ *          domain).
+ *
+ * @return true if a cookie was removed, false if not.
+ */
+xhrApi.removeCookie = function(name, path, domain) {
+  var rval = false;
+
+  if(domain) {
+    // remove the cookies from the applicable domains
+    for(var key in _clients) {
+      var client = _clients[key];
+      if(http.withinCookieDomain(client.url, domain)) {
+        if(client.removeCookie(name, path)) {
+           rval = true;
+        }
+      }
+    }
+  } else {
+    // remove cookie from default domain
+    rval = _client.removeCookie(name, path);
+  }
+
+  return rval;
+};
+
+/**
+ * Creates a new XmlHttpRequest. By default the base URL, flash policy port,
+ * etc, will be used. However, an XHR can be created to point at another
+ * cross-domain URL.
+ *
+ * @param options:
+ *   logWarningOnError: If true and an HTTP error status code is received then
+ *     log a warning, otherwise log a verbose message.
+ *   verbose: If true be very verbose in the output including the response
+ *     event and response body, otherwise only include status, timing, and
+ *     data size.
+ *   logError: a multi-var log function for warnings that takes the log
+ *     category as the first var.
+ *   logWarning: a multi-var log function for warnings that takes the log
+ *     category as the first var.
+ *   logDebug: a multi-var log function for warnings that takes the log
+ *     category as the first var.
+ *   logVerbose: a multi-var log function for warnings that takes the log
+ *     category as the first var.
+ *   url: the default base URL to connect to if xhr URLs are relative,
+ *     eg: https://myserver.com, and note that the following options will be
+ *     ignored if the URL is absent or the same as the default base URL.
+ *   policyPort: the port that provides the server's flash policy, 0 to use
+ *     the flash default.
+ *   policyUrl: the policy file URL to use instead of a policy port.
+ *   connections: the maximum number of concurrent connections.
+ *   caCerts: a list of PEM-formatted certificates to trust.
+ *   cipherSuites: an optional array of cipher suites to use, see
+ *     forge.tls.CipherSuites.
+ *   verify: optional TLS certificate verify callback to use (see forge.tls
+ *     for details).
+ *   getCertificate: an optional callback used to get a client-side
+ *     certificate.
+ *   getPrivateKey: an optional callback used to get a client-side private key.
+ *   getSignature: an optional callback used to get a client-side signature.
+ *   persistCookies: true to use persistent cookies via flash local storage,
+ *     false to only keep cookies in javascript.
+ *   primeTlsSockets: true to immediately connect TLS sockets on their
+ *     creation so that they will cache TLS sessions for reuse.
+ *
+ * @return the XmlHttpRequest.
+ */
+xhrApi.create = function(options) {
+  // set option defaults
+  options = $.extend({
+    logWarningOnError: true,
+    verbose: false,
+    logError: function(){},
+    logWarning: function(){},
+    logDebug: function(){},
+    logVerbose: function(){},
+    url: null
+  }, options || {});
+
+  // private xhr state
+  var _state = {
+    // the http client to use
+    client: null,
+    // request storage
+    request: null,
+    // response storage
+    response: null,
+    // asynchronous, true if doing asynchronous communication
+    asynchronous: true,
+    // sendFlag, true if send has been called
+    sendFlag: false,
+    // errorFlag, true if a network error occurred
+    errorFlag: false
+  };
+
+  // private log functions
+  var _log = {
+    error: options.logError || forge.log.error,
+    warning: options.logWarning || forge.log.warning,
+    debug: options.logDebug || forge.log.debug,
+    verbose: options.logVerbose || forge.log.verbose
+  };
+
+  // create public xhr interface
+  var xhr = {
+    // an EventListener
+    onreadystatechange: null,
+    // readonly, the current readyState
+    readyState: UNSENT,
+    // a string with the response entity-body
+    responseText: '',
+    // a Document for response entity-bodies that are XML
+    responseXML: null,
+    // readonly, returns the HTTP status code (i.e. 404)
+    status: 0,
+    // readonly, returns the HTTP status message (i.e. 'Not Found')
+    statusText: ''
+  };
+
+  // determine which http client to use
+  if(options.url === null) {
+    // use default
+    _state.client = _client;
+  } else {
+    var url = http.parseUrl(options.url);
+    if(!url) {
+      var error = new Error('Invalid url.');
+      error.details = {
+        url: options.url
+      };
+    }
+
+    // find client
+    if(url.full in _clients) {
+      // client found
+      _state.client = _clients[url.full];
+    } else {
+      // create client
+      _state.client = http.createClient({
+        url: options.url,
+        socketPool: _sp,
+        policyPort: options.policyPort || _policyPort,
+        policyUrl: options.policyUrl || _policyUrl,
+        connections: options.connections || _maxConnections,
+        caCerts: options.caCerts,
+        cipherSuites: options.cipherSuites,
+        persistCookies: options.persistCookies || true,
+        primeTlsSockets: options.primeTlsSockets || false,
+        verify: options.verify,
+        getCertificate: options.getCertificate,
+        getPrivateKey: options.getPrivateKey,
+        getSignature: options.getSignature
+      });
+      _clients[url.full] = _state.client;
+    }
+  }
+
+  /**
+   * Opens the request. This method will create the HTTP request to send.
+   *
+   * @param method the HTTP method (i.e. 'GET').
+   * @param url the relative url (the HTTP request path).
+   * @param async always true, ignored.
+   * @param user always null, ignored.
+   * @param password always null, ignored.
+   */
+  xhr.open = function(method, url, async, user, password) {
+    // 1. validate Document if one is associated
+    // TODO: not implemented (not used yet)
+
+    // 2. validate method token
+    // 3. change method to uppercase if it matches a known
+    // method (here we just require it to be uppercase, and
+    // we do not allow the standard methods)
+    // 4. disallow CONNECT, TRACE, or TRACK with a security error
+    switch(method) {
+    case 'DELETE':
+    case 'GET':
+    case 'HEAD':
+    case 'OPTIONS':
+    case 'PATCH':
+    case 'POST':
+    case 'PUT':
+      // valid method
+      break;
+    case 'CONNECT':
+    case 'TRACE':
+    case 'TRACK':
+      throw new Error('CONNECT, TRACE and TRACK methods are disallowed');
+    default:
+      throw new Error('Invalid method: ' + method);;
+    }
+
+    // TODO: other validation steps in algorithm are not implemented
+
+    // 19. set send flag to false
+    // set response body to null
+    // empty list of request headers
+    // set request method to given method
+    // set request URL
+    // set username, password
+    // set asychronous flag
+    _state.sendFlag = false;
+    xhr.responseText = '';
+    xhr.responseXML = null;
+
+    // custom: reset status and statusText
+    xhr.status = 0;
+    xhr.statusText = '';
+
+    // create the HTTP request
+    _state.request = http.createRequest({
+      method: method,
+      path: url
+    });
+
+    // 20. set state to OPENED
+    xhr.readyState = OPENED;
+
+    // 21. dispatch onreadystatechange
+    if(xhr.onreadystatechange) {
+       xhr.onreadystatechange();
+    }
+  };
+
+  /**
+   * Adds an HTTP header field to the request.
+   *
+   * @param header the name of the header field.
+   * @param value the value of the header field.
+   */
+  xhr.setRequestHeader = function(header, value) {
+    // 1. if state is not OPENED or send flag is true, raise exception
+    if(xhr.readyState != OPENED || _state.sendFlag) {
+      throw new Error('XHR not open or sending');
+    }
+
+    // TODO: other validation steps in spec aren't implemented
+
+    // set header
+    _state.request.setField(header, value);
+  };
+
+  /**
+   * Sends the request and any associated data.
+   *
+   * @param data a string or Document object to send, null to send no data.
+   */
+  xhr.send = function(data) {
+    // 1. if state is not OPENED or 2. send flag is true, raise
+    // an invalid state exception
+    if(xhr.readyState != OPENED || _state.sendFlag) {
+      throw new Error('XHR not open or sending');
+    }
+
+    // 3. ignore data if method is GET or HEAD
+    if(data &&
+      _state.request.method !== 'GET' &&
+      _state.request.method !== 'HEAD') {
+      // handle non-IE case
+      if(typeof(XMLSerializer) !== 'undefined') {
+        if(data instanceof Document) {
+          var xs = new XMLSerializer();
+          _state.request.body = xs.serializeToString(data);
+        } else {
+          _state.request.body = data;
+        }
+      } else {
+        // poorly implemented IE case
+        if(typeof(data.xml) !== 'undefined') {
+          _state.request.body = data.xml;
+        } else {
+          _state.request.body = data;
+        }
+      }
+    }
+
+    // 4. release storage mutex (not used)
+
+    // 5. set error flag to false
+    _state.errorFlag = false;
+
+    // 6. if asynchronous is true (must be in this implementation)
+
+    // 6.1 set send flag to true
+    _state.sendFlag = true;
+
+    // 6.2 dispatch onreadystatechange
+    if(xhr.onreadystatechange) {
+      xhr.onreadystatechange();
+    }
+
+    // create send options
+    var options = {};
+    options.request = _state.request;
+    options.headerReady = function(e) {
+      // make cookies available for ease of use/iteration
+      xhr.cookies = _state.client.cookies;
+
+      // TODO: update document.cookie with any cookies where the
+      // script's domain matches
+
+      // headers received
+      xhr.readyState = HEADERS_RECEIVED;
+      xhr.status = e.response.code;
+      xhr.statusText = e.response.message;
+      _state.response = e.response;
+      if(xhr.onreadystatechange) {
+        xhr.onreadystatechange();
+      }
+      if(!_state.response.aborted) {
+        // now loading body
+        xhr.readyState = LOADING;
+        if(xhr.onreadystatechange) {
+           xhr.onreadystatechange();
+        }
+      }
+    };
+    options.bodyReady = function(e) {
+      xhr.readyState = DONE;
+      var ct = e.response.getField('Content-Type');
+      // Note: this null/undefined check is done outside because IE
+      // dies otherwise on a "'null' is null" error
+      if(ct) {
+        if(ct.indexOf('text/xml') === 0 ||
+          ct.indexOf('application/xml') === 0 ||
+          ct.indexOf('+xml') !== -1) {
+          try {
+            var doc = new ActiveXObject('MicrosoftXMLDOM');
+            doc.async = false;
+            doc.loadXML(e.response.body);
+            xhr.responseXML = doc;
+          } catch(ex) {
+            var parser = new DOMParser();
+            xhr.responseXML = parser.parseFromString(ex.body, 'text/xml');
+          }
+        }
+      }
+
+      var length = 0;
+      if(e.response.body !== null) {
+        xhr.responseText = e.response.body;
+        length = e.response.body.length;
+      }
+      // build logging output
+      var req = _state.request;
+      var output =
+        req.method + ' ' + req.path + ' ' +
+        xhr.status + ' ' + xhr.statusText + ' ' +
+        length + 'B ' +
+        (e.request.connectTime + e.request.time + e.response.time) +
+        'ms';
+      var lFunc;
+      if(options.verbose) {
+        lFunc = (xhr.status >= 400 && options.logWarningOnError) ?
+          _log.warning : _log.verbose;
+        lFunc(cat, output,
+          e, e.response.body ? '\n' + e.response.body : '\nNo content');
+      } else {
+        lFunc = (xhr.status >= 400 && options.logWarningOnError) ?
+          _log.warning : _log.debug;
+        lFunc(cat, output);
+      }
+      if(xhr.onreadystatechange) {
+        xhr.onreadystatechange();
+      }
+    };
+    options.error = function(e) {
+      var req = _state.request;
+      _log.error(cat, req.method + ' ' + req.path, e);
+
+      // 1. set response body to null
+      xhr.responseText = '';
+      xhr.responseXML = null;
+
+      // 2. set error flag to true (and reset status)
+      _state.errorFlag = true;
+      xhr.status = 0;
+      xhr.statusText = '';
+
+      // 3. set state to done
+      xhr.readyState = DONE;
+
+      // 4. asyc flag is always true, so dispatch onreadystatechange
+      if(xhr.onreadystatechange) {
+        xhr.onreadystatechange();
+      }
+    };
+
+    // 7. send request
+    _state.client.send(options);
+  };
+
+  /**
+   * Aborts the request.
+   */
+  xhr.abort = function() {
+    // 1. abort send
+    // 2. stop network activity
+    _state.request.abort();
+
+    // 3. set response to null
+    xhr.responseText = '';
+    xhr.responseXML = null;
+
+    // 4. set error flag to true (and reset status)
+    _state.errorFlag = true;
+    xhr.status = 0;
+    xhr.statusText = '';
+
+    // 5. clear user headers
+    _state.request = null;
+    _state.response = null;
+
+    // 6. if state is DONE or UNSENT, or if OPENED and send flag is false
+    if(xhr.readyState === DONE || xhr.readyState === UNSENT ||
+     (xhr.readyState === OPENED && !_state.sendFlag)) {
+      // 7. set ready state to unsent
+      xhr.readyState = UNSENT;
+    } else {
+      // 6.1 set state to DONE
+      xhr.readyState = DONE;
+
+      // 6.2 set send flag to false
+      _state.sendFlag = false;
+
+      // 6.3 dispatch onreadystatechange
+      if(xhr.onreadystatechange) {
+        xhr.onreadystatechange();
+      }
+
+      // 7. set state to UNSENT
+      xhr.readyState = UNSENT;
+    }
+  };
+
+  /**
+   * Gets all response headers as a string.
+   *
+   * @return the HTTP-encoded response header fields.
+   */
+  xhr.getAllResponseHeaders = function() {
+    var rval = '';
+    if(_state.response !== null) {
+      var fields = _state.response.fields;
+      $.each(fields, function(name, array) {
+        $.each(array, function(i, value) {
+          rval += name + ': ' + value + '\r\n';
+        });
+      });
+    }
+    return rval;
+  };
+
+  /**
+   * Gets a single header field value or, if there are multiple
+   * fields with the same name, a comma-separated list of header
+   * values.
+   *
+   * @return the header field value(s) or null.
+   */
+  xhr.getResponseHeader = function(header) {
+    var rval = null;
+    if(_state.response !== null) {
+      if(header in _state.response.fields) {
+        rval = _state.response.fields[header];
+        if(forge.util.isArray(rval)) {
+          rval = rval.join();
+        }
+      }
+    }
+    return rval;
+  };
+
+  return xhr;
+};
+
+// expose public api
+forge.xhr = xhrApi;
+
+})(jQuery);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/minify.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/minify.js
new file mode 100644
index 0000000..36430a2
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/minify.js
@@ -0,0 +1,10 @@
+({
+  name: 'node_modules/almond/almond',
+  include: ['js/forge'],
+  out: 'js/forge.min.js',
+  wrap: {
+    startFile: 'start.frag',
+    endFile: 'end.frag'
+  },
+  preserveLicenseComments: false
+})
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/mod_fsp/README b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/mod_fsp/README
new file mode 100644
index 0000000..0331e01
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/mod_fsp/README
@@ -0,0 +1,135 @@
+=======
+mod_fsp
+=======
+
+mod_fsp is an Apache2 module for providing a Flash Socket Policy on the
+same port that HTTP is served. The cross-domain policy that is served is
+specified via a configuration option 'FSPPolicyFile'.
+
+If a flash application sends a policy file request to an Apache server
+that has enabled and configured the mod_fsp module over its HTTP port,
+then the configured cross-domain policy will be returned as the response.
+
+========
+Building
+========
+
+To build the mod_fsp source code you can use Apache2's module
+build and installation tool: 'apxs2' which is, at the time of
+this writing, available on debian in the package:
+
+apache2-threaded-dev
+
+To compile mod_fsp you would run the following command:
+
+apxs2 -c mod_fsp.c
+
+============
+Installation
+============
+
+To install mod_fsp you the following command as root:
+
+apxs2 -c -i -a mod_fsp.c
+
+You must then restart your apache2 process, typically like so:
+
+/etc/init.d/apache2 restart
+
+===================
+Manual Installation
+===================
+
+To manually enable mod_dsp on your Apache2 server, you must copy the
+module file to the appropriate directory and create a load file.
+
+The module file:
+
+fsp.so (The library extension may vary if you are not using linux).
+
+Must be copied to Apache's module installation directory which is
+typically located (on a debian system):
+
+/usr/lib/apache2/modules
+
+The load file:
+
+fsp.load
+
+Must be created in Apache2's 'mods-available' directory, typically
+located (on a debian system):
+
+/etc/apache2/mods-available
+
+The load file should contain:
+
+LoadModule fsp_module         /usr/lib/apache2/modules/mod_fsp.so
+
+If your Apache module installation directory is different from
+the one listed above, you will need to set the correct one in the
+fsp.load file.
+
+To actually enable the module you must create a symbolic link in
+Apache's 'mods-enabled' directory, typically located (on debian):
+
+/etc/apache2/mods-enabled
+
+By typing (from that directory):
+
+ln -s ../mods-available/fsp.load fsp.load
+
+=============
+Configuration
+=============
+
+Once mod_fsp is installed, it must be configured. There is currently
+only one configuration option for mod_fsp: 'FSPPolicyFile'. This
+configuration option will set the file that mod_fsp will look in
+on apache startup for the cross-domain policy to serve. This option
+can be provided on a per-port basis. Each port can use a different
+one, but VirtualServers on a single port will use the same one. This
+is a limitation of the design by Adobe.
+
+Note: The cross-domain policy may fail to be served if the configuration
+option isn't added in the first VirtualHost entry (for a given port) read
+by Apache.
+
+An example of this configuration in use:
+
+<VirtualHost *:80>
+   ServerName example.com
+   DocumentRoot /var/www/example.com
+   ErrorLog /var/log/apache2/example.com-error.log
+   CustomLog /var/log/apache2/example.com-access.log vhost_combined
+
+   # mod_fsp config option
+   FSPPolicyFile /etc/apache2/crossdomain/crossdomain.xml
+
+   <Directory /var/www/example.com>
+      Options Indexes FollowSymLinks MultiViews
+      AllowOverride All
+      Order allow,deny
+      allow from all
+   </Directory>
+
+</VirtualHost>
+
+And example of the most permissive cross-domain policy file for flash:
+
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE cross-domain-policy SYSTEM
+"http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">
+<cross-domain-policy>
+<site-control permitted-cross-domain-policies="all"/>
+<allow-access-from domain="*" to-ports="*"/>
+<allow-http-request-headers-from domain="*" headers="*"/>
+</cross-domain-policy>
+
+==================
+Note about SSL/TLS
+==================
+
+Flash currently has no built-in SSL/TLS support so there is no
+reason to specify an 'FSPPolicyFile' option for SSL servers. The
+Flash player cannot directly communicate with them when doing
+internal look ups of policy files.
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/mod_fsp/mod_fsp.c b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/mod_fsp/mod_fsp.c
new file mode 100644
index 0000000..8beb824
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/mod_fsp/mod_fsp.c
@@ -0,0 +1,415 @@
+/**
+ * Flash Socket Policy Apache Module.
+ *
+ * This module provides a flash socket policy file on the same port that
+ * serves HTTP on Apache. This can help simplify setting up a server that
+ * supports cross-domain communication with flash.
+ *
+ * Quick note about Apache memory handling: Data is allocated from pools and
+ * is not manually returned to those pools. The pools are typically considered
+ * short-lived and will be cleaned up automatically by Apache.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010 Digital Bazaar, Inc. All rights reserved.
+ */
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+
+#include "ap_compat.h"
+
+#include <string.h>
+
+// length of a policy file request
+#define PFR_LENGTH 23
+
+// declare main module
+module AP_MODULE_DECLARE_DATA fsp_module;
+
+// configuration for the module
+typedef struct fsp_config
+{
+   // the cross-domain policy to serve
+   char* policy;
+   apr_size_t policy_length;
+} fsp_config;
+
+// filter state for keeping track of detected policy file requests
+typedef struct filter_state
+{
+   fsp_config* cfg;
+   int checked;
+   int found;
+} filter_state;
+
+// for registering hooks, filters, etc.
+static void fsp_register_hooks(apr_pool_t *p);
+static int fsp_pre_connection(conn_rec *c, void *csd);
+
+// filter handler declarations
+static apr_status_t fsp_input_filter(
+   ap_filter_t* f, apr_bucket_brigade* bb,
+	ap_input_mode_t mode, apr_read_type_e block, apr_off_t nbytes);
+static int fsp_output_filter(ap_filter_t* f, apr_bucket_brigade* bb);
+
+/**
+ * Registers the hooks for this module.
+ *
+ * @param p the pool to allocate from, if necessary.
+ */
+static void fsp_register_hooks(apr_pool_t* p)
+{
+   // registers the pre-connection hook to handle adding filters
+   ap_hook_pre_connection(
+      fsp_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
+
+   // will parse a policy file request, to be added in pre_connection
+   ap_register_input_filter(
+      "fsp_request", fsp_input_filter,
+      NULL, AP_FTYPE_CONNECTION);
+
+   // will emit a cross-domain policy response to be added in pre_connection
+   ap_register_output_filter(
+      "fsp_response", fsp_output_filter,
+      NULL, AP_FTYPE_CONNECTION);
+}
+
+/**
+ * A hook that is called before a connection is handled. This function will
+ * get the module configuration and add the flash socket policy filters if
+ * a cross-domain policy has been specified in the configuration.
+ *
+ * @param c the connection.
+ * @param csd the connection socket descriptor.
+ *
+ * @return OK on success.
+ */
+static int fsp_pre_connection(conn_rec* c, void* csd)
+{
+   // only install filters if a policy was specified in the module config
+   fsp_config* cfg = ap_get_module_config(
+      c->base_server->module_config, &fsp_module);
+   if(cfg->policy != NULL)
+   {
+      // allocate filter state
+      filter_state* state = apr_palloc(c->pool, sizeof(filter_state));
+      if(state != NULL)
+      {
+         // initialize state
+         state->cfg = cfg;
+         state->checked = state->found = 0;
+
+         // add filters
+         ap_add_input_filter("fsp_request", state, NULL, c);
+         ap_add_output_filter("fsp_response", state, NULL, c);
+      }
+   }
+
+   return OK;
+}
+
+/**
+ * Searches the input request for a flash socket policy request. This request,
+ * unfortunately, does not follow the HTTP protocol and cannot be handled
+ * via a special HTTP handler. Instead, it is a short xml string followed by
+ * a null character:
+ *
+ * '<policy-file-request/>\0'
+ *
+ * A peek into the incoming data checks the first character of the stream to
+ * see if it is '<' (as opposed to typically something else for HTTP). If it
+ * is not, then this function returns and HTTP input is read normally. If it
+ * is, then the remaining bytes in the policy-file-request are read and
+ * checked. If a match is found, then the filter state will be updated to
+ * inform the output filter to send a cross-domain policy as a response. If
+ * no match is found, HTTP traffic will proceed as usual.
+ *
+ * @param f the input filter.
+ * @param state the filter state.
+ *
+ * @return APR_SUCCESS on success, some other status on failure.
+ */
+static apr_status_t find_policy_file_request(
+   ap_filter_t* f, filter_state* state)
+{
+   apr_status_t rval = APR_SUCCESS;
+
+   // create a temp buffer for speculative reads
+   apr_bucket_brigade* tmp = apr_brigade_create(f->c->pool, f->c->bucket_alloc);
+
+   // FIXME: not sure how blocking mode works ... can it return fewer than
+   // the number of specified bytes?
+
+   // peek at the first PFR_LENGTH bytes
+   rval = ap_get_brigade(
+      f->next, tmp, AP_MODE_SPECULATIVE, APR_BLOCK_READ, PFR_LENGTH);
+   if(rval == APR_SUCCESS)
+   {
+      // quickly check the first bucket for the beginning of a pfr
+      const char* data;
+      apr_size_t length;
+      apr_bucket* b = APR_BRIGADE_FIRST(tmp);
+      rval = apr_bucket_read(b, &data, &length, APR_BLOCK_READ);
+      if(rval == APR_SUCCESS && length > 0 && data[0] == '<')
+      {
+         // possible policy file request, fill local buffer
+         char pfr[PFR_LENGTH];
+         char* ptr = pfr;
+         memcpy(ptr, data, length);
+         ptr += length;
+         memset(ptr, '\0', PFR_LENGTH - length);
+         b = APR_BUCKET_NEXT(b);
+         while(rval == APR_SUCCESS && b != APR_BRIGADE_SENTINEL(tmp))
+         {
+            rval = apr_bucket_read(b, &data, &length, APR_BLOCK_READ);
+            if(rval == APR_SUCCESS)
+            {
+               memcpy(ptr, data, length);
+               ptr += length;
+               b = APR_BUCKET_NEXT(b);
+            }
+         }
+
+         if(rval == APR_SUCCESS)
+         {
+            // see if pfr is a policy file request: '<policy-file-request/>\0'
+            if((ptr - pfr == PFR_LENGTH) && (pfr[PFR_LENGTH - 1] == '\0') &&
+               (strncmp(pfr, "<policy-file-request/>", PFR_LENGTH -1) == 0))
+            {
+               // pfr found
+               state->found = 1;
+            }
+         }
+      }
+   }
+
+   return rval;
+}
+
+/**
+ * Handles incoming data. If an attempt has not yet been made to look for
+ * a policy request (it is the beginning of the connection), then one is
+ * made. Otherwise this filter does nothing.
+ *
+ * If an attempt is made to find a policy request and one is not found, then
+ * reads proceed as normal. If one is found, then the filter state is modified
+ * to inform the output filter to send a policy request and the return value
+ * of this filter is EOF indicating that the connection should close after
+ * sending the cross-domain policy.
+ *
+ * @param f the input filter.
+ * @param bb the brigate to fill with input from the next filters in the chain
+ *           and then process (look for a policy file request).
+ * @param mode the type of read requested (ie: AP_MODE_GETLINE means read until
+ *           a CRLF is found, AP_MODE_GETBYTES means 'nbytes' of data, etc).
+ * @param block APR_BLOCK_READ or APR_NONBLOCK_READ, indicates the type of
+ *           blocking to do when trying to read.
+ * @param nbytes used if the read mode is appropriate to specify the number of
+ *           bytes to read (set to 0 for AP_MODE_GETLINE).
+ *
+ * @return the status of the input (ie: APR_SUCCESS for read success, APR_EOF
+ *         for end of stream, APR_EAGAIN to read again when non-blocking).
+ */
+static apr_status_t fsp_input_filter(
+   ap_filter_t* f, apr_bucket_brigade* bb,
+	ap_input_mode_t mode, apr_read_type_e block, apr_off_t nbytes)
+{
+   apr_status_t rval = APR_SUCCESS;
+
+   filter_state* state = f->ctx;
+   if(state->checked == 1)
+   {
+      // already checked for policy file request, just read from other filters
+      rval = ap_get_brigade(f->next, bb, mode, block, nbytes);
+   }
+   else
+   {
+      // try to find a policy file request
+      rval = find_policy_file_request(f, state);
+      state->checked = 1;
+
+      if(rval == APR_SUCCESS)
+      {
+         if(state->found)
+         {
+            // do read of PFR_LENGTH bytes, consider end of stream
+            rval = ap_get_brigade(
+               f->next, bb, AP_MODE_READBYTES, APR_BLOCK_READ, PFR_LENGTH);
+            rval = APR_EOF;
+         }
+         else
+         {
+            // do normal read
+            rval = ap_get_brigade(f->next, bb, mode, block, nbytes);
+         }
+      }
+   }
+
+   return rval;
+}
+
+/**
+ * Handles outgoing data. If the filter state indicates that a cross-domain
+ * policy should be sent then it is added to the outgoing brigade of data. If
+ * a policy request was not detected, then this filter makes no changes to
+ * the outgoing data.
+ *
+ * @param f the output filter.
+ * @param bb the outgoing brigade of data.
+ *
+ * @return APR_SUCCESS on success, some other status on error.
+ */
+static int fsp_output_filter(ap_filter_t* f, apr_bucket_brigade* bb)
+{
+   apr_status_t rval = APR_SUCCESS;
+
+   filter_state* state = f->ctx;
+   if(state->found)
+   {
+      // found policy-file-request, add response bucket
+      // bucket is immortal because the data is stored in the configuration
+      // and doesn't need to be copied
+      apr_bucket* head = apr_bucket_immortal_create(
+         state->cfg->policy, state->cfg->policy_length, bb->bucket_alloc);
+      APR_BRIGADE_INSERT_HEAD(bb, head);
+   }
+
+   if(rval == APR_SUCCESS)
+   {
+      // pass brigade to next filter
+      rval = ap_pass_brigade(f->next, bb);
+   }
+
+   return rval;
+}
+
+/**
+ * Creates the configuration for this module.
+ *
+ * @param p the pool to allocate from.
+ * @param s the server the configuration is for.
+ *
+ * @return the configuration data.
+ */
+static void* fsp_create_config(apr_pool_t* p, server_rec* s)
+{
+   // allocate config
+   fsp_config* cfg = apr_palloc(p, sizeof(fsp_config));
+
+   // no default policy
+   cfg->policy = NULL;
+   cfg->policy_length = 0;
+   return cfg;
+}
+
+/**
+ * Sets the policy file to use from the configuration.
+ *
+ * @param parms the command directive parameters.
+ * @param userdata NULL, not used.
+ * @param arg the string argument to the command directive (the file with
+ *           the cross-domain policy to serve as content).
+ *
+ * @return NULL on success, otherwise an error string to display.
+ */
+static const char* fsp_set_policy_file(
+   cmd_parms* parms, void* userdata, const char* arg)
+{
+   const char* rval = NULL;
+
+   apr_pool_t* pool = (apr_pool_t*)parms->pool;
+   fsp_config* cfg = ap_get_module_config(
+      parms->server->module_config, &fsp_module);
+
+   // ensure command is in the correct context
+   rval = ap_check_cmd_context(parms, NOT_IN_DIR_LOC_FILE|NOT_IN_LIMIT);
+   if(rval == NULL)
+   {
+      // get canonical file name
+      char* fname = ap_server_root_relative(pool, arg);
+      if(fname == NULL)
+      {
+         rval = (const char*)apr_psprintf(
+            pool, "%s: Invalid policy file '%s'",
+            parms->cmd->name, arg);
+      }
+      else
+      {
+         // try to open the file
+         apr_status_t rv;
+         apr_file_t* fd;
+         apr_finfo_t finfo;
+         rv = apr_file_open(&fd, fname, APR_READ, APR_OS_DEFAULT, pool);
+         if(rv == APR_SUCCESS)
+         {
+            // stat file
+            rv = apr_file_info_get(&finfo, APR_FINFO_NORM, fd);
+            if(rv == APR_SUCCESS)
+            {
+               // ensure file is not empty
+               apr_size_t length = (apr_size_t)finfo.size;
+               if(length <= 0)
+               {
+                  rval = (const char*)apr_psprintf(
+                     pool, "%s: policy file '%s' is empty",
+                     parms->cmd->name, fname);
+               }
+               // read file
+               else
+               {
+                  char* buf = (char*)apr_palloc(pool, length + 1);
+                  buf[length] = '\0';
+                  rv = apr_file_read_full(fd, buf, length, NULL);
+                  if(rv == APR_SUCCESS)
+                  {
+                     // TODO: validate file
+                     // save policy string
+                     cfg->policy = buf;
+                     cfg->policy_length = length + 1;
+                  }
+               }
+
+               // close the file
+               apr_file_close(fd);
+            }
+         }
+
+         // handle error case
+         if(rv != APR_SUCCESS)
+         {
+            char errmsg[120];
+            rval = (const char*)apr_psprintf(
+               pool, "%s: Invalid policy file '%s' (%s)",
+               parms->cmd->name, fname,
+               apr_strerror(rv, errmsg, sizeof(errmsg)));
+         }
+      }
+   }
+
+   return rval;
+}
+
+// table of configuration directives
+static const command_rec fsp_cmds[] =
+{
+   AP_INIT_TAKE1(
+      "FSPPolicyFile", /* the directive */
+      fsp_set_policy_file, /* function to call when directive is found */
+      NULL, /* user data to pass to function, not used */
+      RSRC_CONF, /* indicates the directive appears outside of <Location> */
+      "FSPPolicyFile (string) The cross-domain policy file to use"), /* docs */
+   {NULL}
+};
+
+// module setup
+module AP_MODULE_DECLARE_DATA fsp_module =
+{
+    STANDARD20_MODULE_STUFF,    /* stuff declared in every 2.0 mod       */
+    NULL,                       /* create per-directory config structure */
+    NULL,                       /* merge per-directory config structures */
+    fsp_create_config,          /* create per-server config structure    */
+    NULL,                       /* merge per-server config structures    */
+    fsp_cmds,                   /* command apr_table_t                   */
+    fsp_register_hooks          /* register hooks                        */
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/README.md b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/README.md
new file mode 100644
index 0000000..1be00fa
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/README.md
@@ -0,0 +1,34 @@
+Running all the tests (headless-browser and node.js)
+====================================================
+
+    npm install
+    npm test
+
+Running the browser-based tests
+===============================
+
+    npm install
+    node server.js
+
+Then go to http://localhost:8083/.
+
+Testing Require.js optimised version of the JavaScript
+------------------------------------------------------
+
+    npm install -g requirejs
+    r.js -o build.js
+
+You will now have a single optimised JS file at ui/test.min.js, containing the
+tests and all the forge dependencies.
+
+Now edit ui/index.html and change `data-main="test"` to `data-main="test.min"`,
+then reload http://localhost:8083/.
+
+Building a minimized single file for all forge modules
+------------------------------------------------------
+
+    npm install -g requirejs
+    r.js -o minify.js
+
+You will now have forge.min.js, in the 'js' directory, which will contain all
+forge modules.
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/build.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/build.js
new file mode 100644
index 0000000..30ba7b5
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/build.js
@@ -0,0 +1,8 @@
+({
+    paths: {
+        forge: '../js'
+    },
+    name: 'ui/test.js',
+    out: 'ui/test.min.js',
+    preserveLicenseComments: false
+})
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/minify.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/minify.js
new file mode 100644
index 0000000..69d96a9
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/minify.js
@@ -0,0 +1,8 @@
+({
+    paths: {
+        forge: '../js'
+    },
+    name: '../js/forge',
+    out: '../js/forge.min.js',
+    preserveLicenseComments: false
+})
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/package.json b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/package.json
new file mode 100644
index 0000000..f60f52a
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/package.json
@@ -0,0 +1,17 @@
+{
+    "name": "forge-nodejs-example",
+    "version": "0.1.0",
+    "private": true,
+    "main": "server.js",
+    "dependencies": {
+        "express": "~3.1.0",
+        "mocha": "~1.8.2",
+        "chai": "~1.5.0",
+        "grunt": "~0.4.1",
+        "grunt-mocha": "~0.3.1"
+    },
+    "scripts": {
+        "test": "mocha -t 20000 -R spec test/*.js",
+        "run": "node server"
+    }
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/server.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/server.js
new file mode 100644
index 0000000..175bd56
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/server.js
@@ -0,0 +1,46 @@
+var PATH = require('path');
+var express = require('express');
+var PORT = 8083;
+
+exports.main = function(callback) {
+  try {
+    var app = express();
+
+    mountStaticDir(app, /^\/forge\/(.*)$/, PATH.join(__dirname, '../js'));
+    mountStaticDir(app, /^\/test\/(.*)$/, PATH.join(__dirname, 'test'));
+    mountStaticDir(app, /^\/mocha\/(.*)$/, PATH.join(__dirname, 'node_modules/mocha'));
+    mountStaticDir(app, /^\/chai\/(.*)$/, PATH.join(__dirname, 'node_modules/chai'));
+    app.get(/^\//, express.static(PATH.join(__dirname, 'ui')));
+
+    var server = app.listen(PORT);
+
+    console.log('open http://localhost:' + PORT + '/');
+
+    return callback(null, {
+      server: server,
+      port: PORT
+    });
+  } catch(err) {
+    return callback(err);
+  }
+};
+
+function mountStaticDir(app, route, path) {
+  app.get(route, function(req, res, next) {
+    var originalUrl = req.url;
+    req.url = req.params[0];
+    express.static(path)(req, res, function() {
+      req.url = originalUrl;
+      return next.apply(null, arguments);
+    });
+  });
+}
+
+if(require.main === module) {
+  exports.main(function(err) {
+    if(err) {
+      console.error(err.stack);
+      process.exit(1);
+    }
+  });
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/aes.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/aes.js
new file mode 100644
index 0000000..ddd91a4
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/aes.js
@@ -0,0 +1,1213 @@
+(function() {
+
+function Tests(ASSERT, CIPHER, AES, UTIL) {
+  describe('aes', function() {
+    it('should encrypt a single block with a 128-bit key', function() {
+      var key = [0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f];
+      var block = [0x00112233, 0x44556677, 0x8899aabb, 0xccddeeff];
+
+      var output = [];
+      var w = AES._expandKey(key, false);
+      AES._updateBlock(w, block, output, false);
+
+      var out = UTIL.createBuffer();
+      out.putInt32(output[0]);
+      out.putInt32(output[1]);
+      out.putInt32(output[2]);
+      out.putInt32(output[3]);
+
+      ASSERT.equal(out.toHex(), '69c4e0d86a7b0430d8cdb78070b4c55a');
+    });
+
+    it('should decrypt a single block with a 128-bit key', function() {
+      var key = [0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f];
+      var block = [0x69c4e0d8, 0x6a7b0430, 0xd8cdb780, 0x70b4c55a];
+
+      var output = [];
+      var w = AES._expandKey(key, true);
+      AES._updateBlock(w, block, output, true);
+
+      var out = UTIL.createBuffer();
+      out.putInt32(output[0]);
+      out.putInt32(output[1]);
+      out.putInt32(output[2]);
+      out.putInt32(output[3]);
+
+      ASSERT.equal(out.toHex(), '00112233445566778899aabbccddeeff');
+    });
+
+    it('should encrypt a single block with a 192-bit key', function() {
+        var key = [
+          0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f,
+          0x10111213, 0x14151617];
+        var block = [0x00112233, 0x44556677, 0x8899aabb, 0xccddeeff];
+
+        var output = [];
+        var w = AES._expandKey(key, false);
+        AES._updateBlock(w, block, output, false);
+
+        var out = UTIL.createBuffer();
+        out.putInt32(output[0]);
+        out.putInt32(output[1]);
+        out.putInt32(output[2]);
+        out.putInt32(output[3]);
+
+        ASSERT.equal(out.toHex(), 'dda97ca4864cdfe06eaf70a0ec0d7191');
+    });
+
+    it('should decrypt a single block with a 192-bit key', function() {
+        var key = [
+          0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f,
+          0x10111213, 0x14151617];
+        var block = [0xdda97ca4, 0x864cdfe0, 0x6eaf70a0, 0xec0d7191];
+
+        var output = [];
+        var w = AES._expandKey(key, true);
+        AES._updateBlock(w, block, output, true);
+
+        var out = UTIL.createBuffer();
+        out.putInt32(output[0]);
+        out.putInt32(output[1]);
+        out.putInt32(output[2]);
+        out.putInt32(output[3]);
+
+        ASSERT.equal(out.toHex(), '00112233445566778899aabbccddeeff');
+    });
+
+    it('should encrypt a single block with a 256-bit key', function() {
+        var key = [
+          0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f,
+          0x10111213, 0x14151617, 0x18191a1b, 0x1c1d1e1f];
+        var block = [0x00112233, 0x44556677, 0x8899aabb, 0xccddeeff];
+
+        var output = [];
+        var w = AES._expandKey(key, false);
+        AES._updateBlock(w, block, output, false);
+
+        var out = UTIL.createBuffer();
+        out.putInt32(output[0]);
+        out.putInt32(output[1]);
+        out.putInt32(output[2]);
+        out.putInt32(output[3]);
+
+        ASSERT.equal(out.toHex(), '8ea2b7ca516745bfeafc49904b496089');
+    });
+
+    it('should decrypt a single block with a 256-bit key', function() {
+        var key = [
+          0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f,
+          0x10111213, 0x14151617, 0x18191a1b, 0x1c1d1e1f];
+        var block = [0x8ea2b7ca, 0x516745bf, 0xeafc4990, 0x4b496089];
+
+        var output = [];
+        var w = AES._expandKey(key, true);
+        AES._updateBlock(w, block, output, true);
+
+        var out = UTIL.createBuffer();
+        out.putInt32(output[0]);
+        out.putInt32(output[1]);
+        out.putInt32(output[2]);
+        out.putInt32(output[3]);
+
+        ASSERT.equal(out.toHex(), '00112233445566778899aabbccddeeff');
+    });
+
+    // AES-128-CBC
+    (function() {
+      var keys = [
+        '06a9214036b8a15b512e03d534120006',
+        'c286696d887c9aa0611bbb3e2025a45a',
+        '6c3ea0477630ce21a2ce334aa746c2cd',
+        '56e47a38c5598974bc46903dba290349'
+      ];
+
+      var ivs = [
+        '3dafba429d9eb430b422da802c9fac41',
+        '562e17996d093d28ddb3ba695a2e6f58',
+        'c782dc4c098c66cbd9cd27d825682c81',
+        '8ce82eefbea0da3c44699ed7db51b7d9'
+      ];
+
+      var inputs = [
+        'Single block msg',
+        '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f',
+        'This is a 48-byte message (exactly 3 AES blocks)',
+        'a0a1a2a3a4a5a6a7a8a9aaabacadaeaf' +
+          'b0b1b2b3b4b5b6b7b8b9babbbcbdbebf' +
+          'c0c1c2c3c4c5c6c7c8c9cacbcccdcecf' +
+          'd0d1d2d3d4d5d6d7d8d9dadbdcdddedf'
+      ];
+
+      var outputs = [
+        'e353779c1079aeb82708942dbe77181a',
+        'd296cd94c2cccf8a3a863028b5e1dc0a7586602d253cfff91b8266bea6d61ab1',
+        'd0a02b3836451753d493665d33f0e886' +
+          '2dea54cdb293abc7506939276772f8d5' +
+          '021c19216bad525c8579695d83ba2684',
+        'c30e32ffedc0774e6aff6af0869f71aa' +
+          '0f3af07a9a31a9c684db207eb0ef8e4e' +
+          '35907aa632c3ffdf868bb7b29d3d46ad' +
+          '83ce9f9a102ee99d49a53e87f4c3da55'
+      ];
+
+      for(var i = 0; i < keys.length; ++i) {
+        (function(i) {
+          var key = UTIL.hexToBytes(keys[i]);
+          var iv = UTIL.hexToBytes(ivs[i]);
+          var input = (i & 1) ? UTIL.hexToBytes(inputs[i]) : inputs[i];
+          var output = UTIL.hexToBytes(outputs[i]);
+
+          it('should aes-128-cbc encrypt: ' + inputs[i], function() {
+            // encrypt w/no padding
+            var cipher = CIPHER.createCipher('AES-CBC', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(input));
+            cipher.finish(function(){return true;});
+            ASSERT.equal(cipher.output.toHex(), outputs[i]);
+          });
+
+          it('should aes-128-cbc decrypt: ' + outputs[i], function() {
+            // decrypt w/no padding
+            var cipher = CIPHER.createDecipher('AES-CBC', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(output));
+            cipher.finish(function(){return true;});
+            var out = (i & 1) ? cipher.output.toHex() : cipher.output.bytes();
+            ASSERT.equal(out, inputs[i]);
+          });
+        })(i);
+      }
+    })();
+
+    // AES-192-CBC
+    (function() {
+      var keys = [
+        '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b',
+        '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b',
+        '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b',
+        '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b'
+      ];
+
+      var ivs = [
+        '000102030405060708090A0B0C0D0E0F',
+        '4F021DB243BC633D7178183A9FA071E8',
+        'B4D9ADA9AD7DEDF4E5E738763F69145A',
+        '571B242012FB7AE07FA9BAAC3DF102E0'
+      ];
+
+      var inputs = [
+        '6bc1bee22e409f96e93d7e117393172a',
+        'ae2d8a571e03ac9c9eb76fac45af8e51',
+        '30c81c46a35ce411e5fbc1191a0a52ef',
+        'f69f2445df4f9b17ad2b417be66c3710'
+      ];
+
+      var outputs = [
+        '4f021db243bc633d7178183a9fa071e8',
+        'b4d9ada9ad7dedf4e5e738763f69145a',
+        '571b242012fb7ae07fa9baac3df102e0',
+        '08b0e27988598881d920a9e64f5615cd'
+      ];
+
+      for(var i = 0; i < keys.length; ++i) {
+        (function(i) {
+          var key = UTIL.hexToBytes(keys[i]);
+          var iv = UTIL.hexToBytes(ivs[i]);
+          var input = UTIL.hexToBytes(inputs[i]);
+          var output = UTIL.hexToBytes(outputs[i]);
+
+          it('should aes-192-cbc encrypt: ' + inputs[i], function() {
+            // encrypt w/no padding
+            var cipher = CIPHER.createCipher('AES-CBC', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(input));
+            cipher.finish(function(){return true;});
+            ASSERT.equal(cipher.output.toHex(), outputs[i]);
+          });
+
+          it('should aes-192-cbc decrypt: ' + outputs[i], function() {
+            // decrypt w/no padding
+            var cipher = CIPHER.createDecipher('AES-CBC', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(output));
+            cipher.finish(function(){return true;});
+            var out = cipher.output.toHex();
+            ASSERT.equal(out, inputs[i]);
+          });
+        })(i);
+      }
+    })();
+
+    // AES-256-CBC
+    (function() {
+      var keys = [
+        '603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4',
+        '603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4',
+        '603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4',
+        '603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4'
+      ];
+
+      var ivs = [
+        '000102030405060708090A0B0C0D0E0F',
+        'F58C4C04D6E5F1BA779EABFB5F7BFBD6',
+        '9CFC4E967EDB808D679F777BC6702C7D',
+        '39F23369A9D9BACFA530E26304231461'
+      ];
+
+      var inputs = [
+        '6bc1bee22e409f96e93d7e117393172a',
+        'ae2d8a571e03ac9c9eb76fac45af8e51',
+        '30c81c46a35ce411e5fbc1191a0a52ef',
+        'f69f2445df4f9b17ad2b417be66c3710'
+      ];
+
+      var outputs = [
+        'f58c4c04d6e5f1ba779eabfb5f7bfbd6',
+        '9cfc4e967edb808d679f777bc6702c7d',
+        '39f23369a9d9bacfa530e26304231461',
+        'b2eb05e2c39be9fcda6c19078c6a9d1b'
+      ];
+
+      for(var i = 0; i < keys.length; ++i) {
+        (function(i) {
+          var key = UTIL.hexToBytes(keys[i]);
+          var iv = UTIL.hexToBytes(ivs[i]);
+          var input = UTIL.hexToBytes(inputs[i]);
+          var output = UTIL.hexToBytes(outputs[i]);
+
+          it('should aes-256-cbc encrypt: ' + inputs[i], function() {
+            // encrypt w/no padding
+            var cipher = CIPHER.createCipher('AES-CBC', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(input));
+            cipher.finish(function(){return true;});
+            ASSERT.equal(cipher.output.toHex(), outputs[i]);
+          });
+
+          it('should aes-256-cbc decrypt: ' + outputs[i], function() {
+            // decrypt w/no padding
+            var cipher = CIPHER.createDecipher('AES-CBC', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(output));
+            cipher.finish(function(){return true;});
+            var out = cipher.output.toHex();
+            ASSERT.equal(out, inputs[i]);
+          });
+        })(i);
+      }
+    })();
+
+    // AES-128-CFB
+    (function() {
+      var keys = [
+        '00000000000000000000000000000000',
+        '2b7e151628aed2a6abf7158809cf4f3c',
+        '2b7e151628aed2a6abf7158809cf4f3c',
+        '2b7e151628aed2a6abf7158809cf4f3c',
+        '2b7e151628aed2a6abf7158809cf4f3c',
+        '00000000000000000000000000000000'
+      ];
+
+      var ivs = [
+        '80000000000000000000000000000000',
+        '000102030405060708090a0b0c0d0e0f',
+        '3B3FD92EB72DAD20333449F8E83CFB4A',
+        'C8A64537A0B3A93FCDE3CDAD9F1CE58B',
+        '26751F67A3CBB140B1808CF187A4F4DF',
+        '60f9ff04fac1a25657bf5b36b5efaf75'
+      ];
+
+      var inputs = [
+        '00000000000000000000000000000000',
+        '6bc1bee22e409f96e93d7e117393172a',
+        'ae2d8a571e03ac9c9eb76fac45af8e51',
+        '30c81c46a35ce411e5fbc1191a0a52ef',
+        'f69f2445df4f9b17ad2b417be66c3710',
+        'This is a 48-byte message (exactly 3 AES blocks)'
+      ];
+
+      var outputs = [
+        '3ad78e726c1ec02b7ebfe92b23d9ec34',
+        '3b3fd92eb72dad20333449f8e83cfb4a',
+        'c8a64537a0b3a93fcde3cdad9f1ce58b',
+        '26751f67a3cbb140b1808cf187a4f4df',
+        'c04b05357c5d1c0eeac4c66f9ff7f2e6',
+        '52396a2ba1ba420c5e5b699a814944d8' +
+          'f4e7fbf984a038319fbc0b4ee45cfa6f' +
+          '07b2564beab5b5e92dbd44cb345f49b4'
+      ];
+
+      for(var i = 0; i < keys.length; ++i) {
+        (function(i) {
+          var key = UTIL.hexToBytes(keys[i]);
+          var iv = UTIL.hexToBytes(ivs[i]);
+          var input = (i !== 5) ? UTIL.hexToBytes(inputs[i]) : inputs[i];
+          var output = UTIL.hexToBytes(outputs[i]);
+
+          it('should aes-128-cfb encrypt: ' + inputs[i], function() {
+            // encrypt
+            var cipher = CIPHER.createCipher('AES-CFB', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(input));
+            cipher.finish();
+            ASSERT.equal(cipher.output.toHex(), outputs[i]);
+          });
+
+          it('should aes-128-cfb decrypt: ' + outputs[i], function() {
+            // decrypt
+            var cipher = CIPHER.createDecipher('AES-CFB', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(output));
+            cipher.finish();
+            var out = (i !== 5) ?
+              cipher.output.toHex() : cipher.output.getBytes();
+            ASSERT.equal(out, inputs[i]);
+          });
+        })(i);
+      }
+    })();
+
+    // AES-192-CFB
+    (function() {
+      var keys = [
+        '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b',
+        '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b',
+        '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b',
+        '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b'
+      ];
+
+      var ivs = [
+        '000102030405060708090A0B0C0D0E0F',
+        'CDC80D6FDDF18CAB34C25909C99A4174',
+        '67CE7F7F81173621961A2B70171D3D7A',
+        '2E1E8A1DD59B88B1C8E60FED1EFAC4C9'
+      ];
+
+      var inputs = [
+        '6bc1bee22e409f96e93d7e117393172a',
+        'ae2d8a571e03ac9c9eb76fac45af8e51',
+        '30c81c46a35ce411e5fbc1191a0a52ef',
+        'f69f2445df4f9b17ad2b417be66c3710'
+      ];
+
+      var outputs = [
+        'cdc80d6fddf18cab34c25909c99a4174',
+        '67ce7f7f81173621961a2b70171d3d7a',
+        '2e1e8a1dd59b88b1c8e60fed1efac4c9',
+        'c05f9f9ca9834fa042ae8fba584b09ff'
+      ];
+
+      for(var i = 0; i < keys.length; ++i) {
+        (function(i) {
+          var key = UTIL.hexToBytes(keys[i]);
+          var iv = UTIL.hexToBytes(ivs[i]);
+          var input = UTIL.hexToBytes(inputs[i]);
+          var output = UTIL.hexToBytes(outputs[i]);
+
+          it('should aes-192-cfb encrypt: ' + inputs[i], function() {
+            // encrypt
+            var cipher = CIPHER.createCipher('AES-CFB', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(input));
+            cipher.finish();
+            ASSERT.equal(cipher.output.toHex(), outputs[i]);
+          });
+
+          it('should aes-192-cfb decrypt: ' + outputs[i], function() {
+            // decrypt
+            var cipher = CIPHER.createDecipher('AES-CFB', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(output));
+            cipher.finish();
+            var out = cipher.output.toHex();
+            ASSERT.equal(out, inputs[i]);
+          });
+        })(i);
+      }
+    })();
+
+    // AES-256-CFB
+    (function() {
+      var keys = [
+        '861009ec4d599fab1f40abc76e6f89880cff5833c79c548c99f9045f191cd90b'
+      ];
+
+      var ivs = [
+        'd927ad81199aa7dcadfdb4e47b6dc694'
+      ];
+
+      var inputs = [
+        'MY-DATA-AND-HERE-IS-MORE-DATA'
+      ];
+
+      var outputs = [
+        '80eb666a9fc9e263faf71e87ffc94451d7d8df7cfcf2606470351dd5ac'
+      ];
+
+      for(var i = 0; i < keys.length; ++i) {
+        (function(i) {
+          var key = UTIL.hexToBytes(keys[i]);
+          var iv = UTIL.hexToBytes(ivs[i]);
+          var input = inputs[i];
+          var output = UTIL.hexToBytes(outputs[i]);
+
+          it('should aes-256-cfb encrypt: ' + inputs[i], function() {
+            // encrypt
+            var cipher = CIPHER.createCipher('AES-CFB', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(input));
+            cipher.finish();
+            ASSERT.equal(cipher.output.toHex(), outputs[i]);
+          });
+
+          it('should aes-256-cfb decrypt: ' + outputs[i], function() {
+            // decrypt
+            var cipher = CIPHER.createDecipher('AES-CFB', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(output));
+            cipher.finish();
+            var out = cipher.output.getBytes();
+            ASSERT.equal(out, inputs[i]);
+          });
+        })(i);
+      }
+    })();
+
+    // AES-128-OFB
+    (function() {
+      var keys = [
+        '00000000000000000000000000000000',
+        '00000000000000000000000000000000'
+      ];
+
+      var ivs = [
+        '80000000000000000000000000000000',
+        'c8ca0d6a35dbeac776e911ee16bea7d3'
+      ];
+
+      var inputs = [
+        '00000000000000000000000000000000',
+        'This is a 48-byte message (exactly 3 AES blocks)'
+      ];
+
+      var outputs = [
+        '3ad78e726c1ec02b7ebfe92b23d9ec34',
+        '39c0190727a76b2a90963426f63689cf' +
+          'cdb8a2be8e20c5e877a81a724e3611f6' +
+          '2ecc386f2e941b2441c838906002be19'
+      ];
+
+      for(var i = 0; i < keys.length; ++i) {
+        (function(i) {
+          var key = UTIL.hexToBytes(keys[i]);
+          var iv = UTIL.hexToBytes(ivs[i]);
+          var input = (i !== 1) ? UTIL.hexToBytes(inputs[i]) : inputs[i];
+          var output = UTIL.hexToBytes(outputs[i]);
+
+          it('should aes-128-ofb encrypt: ' + inputs[i], function() {
+            // encrypt
+            var cipher = CIPHER.createCipher('AES-OFB', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(input));
+            cipher.finish();
+            ASSERT.equal(cipher.output.toHex(), outputs[i]);
+          });
+
+          it('should aes-128-ofb decrypt: ' + outputs[i], function() {
+            // decrypt
+            var cipher = CIPHER.createDecipher('AES-OFB', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(output));
+            cipher.finish();
+            var out = (i !== 1) ?
+              cipher.output.toHex() : cipher.output.getBytes();
+            ASSERT.equal(out, inputs[i]);
+          });
+        })(i);
+      }
+    })();
+
+    // AES-192-OFB
+    (function() {
+      var keys = [
+        '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b',
+        '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b',
+        '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b',
+        '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b'
+      ];
+
+      var ivs = [
+        '000102030405060708090A0B0C0D0E0F',
+        'A609B38DF3B1133DDDFF2718BA09565E',
+        '52EF01DA52602FE0975F78AC84BF8A50',
+        'BD5286AC63AABD7EB067AC54B553F71D'
+      ];
+
+      var inputs = [
+        '6bc1bee22e409f96e93d7e117393172a',
+        'ae2d8a571e03ac9c9eb76fac45af8e51',
+        '30c81c46a35ce411e5fbc1191a0a52ef',
+        'f69f2445df4f9b17ad2b417be66c3710'
+      ];
+
+      var outputs = [
+        'cdc80d6fddf18cab34c25909c99a4174',
+        'fcc28b8d4c63837c09e81700c1100401',
+        '8d9a9aeac0f6596f559c6d4daf59a5f2',
+        '6d9f200857ca6c3e9cac524bd9acc92a'
+      ];
+
+      for(var i = 0; i < keys.length; ++i) {
+        (function(i) {
+          var key = UTIL.hexToBytes(keys[i]);
+          var iv = UTIL.hexToBytes(ivs[i]);
+          var input = UTIL.hexToBytes(inputs[i]);
+          var output = UTIL.hexToBytes(outputs[i]);
+
+          it('should aes-192-ofb encrypt: ' + inputs[i], function() {
+            // encrypt
+            var cipher = CIPHER.createCipher('AES-OFB', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(input));
+            cipher.finish();
+            ASSERT.equal(cipher.output.toHex(), outputs[i]);
+          });
+
+          it('should aes-192-ofb decrypt: ' + outputs[i], function() {
+            // decrypt
+            var cipher = CIPHER.createDecipher('AES-OFB', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(output));
+            cipher.finish();
+            var out = cipher.output.toHex();
+            ASSERT.equal(out, inputs[i]);
+          });
+        })(i);
+      }
+    })();
+
+    // AES-256-OFB
+    (function() {
+      var keys = [
+        '603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4',
+        '603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4',
+        '603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4',
+        '603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4'
+      ];
+
+      var ivs = [
+        '000102030405060708090A0B0C0D0E0F',
+        'B7BF3A5DF43989DD97F0FA97EBCE2F4A',
+        'E1C656305ED1A7A6563805746FE03EDC',
+        '41635BE625B48AFC1666DD42A09D96E7'
+      ];
+
+      var inputs = [
+        '6bc1bee22e409f96e93d7e117393172a',
+        'ae2d8a571e03ac9c9eb76fac45af8e51',
+        '30c81c46a35ce411e5fbc1191a0a52ef',
+        'f69f2445df4f9b17ad2b417be66c3710'
+      ];
+
+      var outputs = [
+        'dc7e84bfda79164b7ecd8486985d3860',
+        '4febdc6740d20b3ac88f6ad82a4fb08d',
+        '71ab47a086e86eedf39d1c5bba97c408',
+        '0126141d67f37be8538f5a8be740e484'
+      ];
+
+      for(var i = 0; i < keys.length; ++i) {
+        (function(i) {
+          var key = UTIL.hexToBytes(keys[i]);
+          var iv = UTIL.hexToBytes(ivs[i]);
+          var input = UTIL.hexToBytes(inputs[i]);
+          var output = UTIL.hexToBytes(outputs[i]);
+
+          it('should aes-256-ofb encrypt: ' + inputs[i], function() {
+            // encrypt
+            var cipher = CIPHER.createCipher('AES-OFB', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(input));
+            cipher.finish();
+            ASSERT.equal(cipher.output.toHex(), outputs[i]);
+          });
+
+          it('should aes-256-ofb decrypt: ' + outputs[i], function() {
+            // decrypt
+            var cipher = CIPHER.createDecipher('AES-OFB', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(output));
+            cipher.finish();
+            var out = cipher.output.toHex();
+            ASSERT.equal(out, inputs[i]);
+          });
+        })(i);
+      }
+    })();
+
+    // AES-128-CTR
+    (function() {
+      var keys = [
+        '2b7e151628aed2a6abf7158809cf4f3c',
+        '00000000000000000000000000000000'
+      ];
+
+      var ivs = [
+        'f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff',
+        '650cdb80ff9fc758342d2bd99ee2abcf'
+      ];
+
+      var inputs = [
+        '6bc1bee22e409f96e93d7e117393172a',
+        'This is a 48-byte message (exactly 3 AES blocks)'
+      ];
+
+      var outputs = [
+        '874d6191b620e3261bef6864990db6ce',
+        '5ede11d00e9a76ec1d5e7e811ea3dd1c' +
+          'e09ee941210f825d35718d3282796f1c' +
+          '07c3f1cb424f2b365766ab5229f5b5a4'
+      ];
+
+      for(var i = 0; i < keys.length; ++i) {
+        (function(i) {
+          var key = UTIL.hexToBytes(keys[i]);
+          var iv = UTIL.hexToBytes(ivs[i]);
+          var input = (i !== 1) ? UTIL.hexToBytes(inputs[i]) : inputs[i];
+          var output = UTIL.hexToBytes(outputs[i]);
+
+          it('should aes-128-ctr encrypt: ' + inputs[i], function() {
+            // encrypt
+            var cipher = CIPHER.createCipher('AES-CTR', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(input));
+            cipher.finish();
+            ASSERT.equal(cipher.output.toHex(), outputs[i]);
+          });
+
+          it('should aes-128-ctr decrypt: ' + outputs[i], function() {
+            // decrypt
+            var cipher = CIPHER.createDecipher('AES-CTR', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(output));
+            cipher.finish();
+            var out = (i !== 1) ?
+              cipher.output.toHex() : cipher.output.getBytes();
+            ASSERT.equal(out, inputs[i]);
+          });
+        })(i);
+      }
+    })();
+
+    // AES-192-CTR
+    (function() {
+      var keys = [
+        '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b'
+      ];
+
+      var ivs = [
+        'f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff'
+      ];
+
+      var inputs = [
+        '6bc1bee22e409f96e93d7e117393172a' +
+          'ae2d8a571e03ac9c9eb76fac45af8e51' +
+          '30c81c46a35ce411e5fbc1191a0a52ef' +
+          'f69f2445df4f9b17ad2b417be66c3710'
+      ];
+
+      var outputs = [
+        '1abc932417521ca24f2b0459fe7e6e0b' +
+          '090339ec0aa6faefd5ccc2c6f4ce8e94' +
+          '1e36b26bd1ebc670d1bd1d665620abf7' +
+          '4f78a7f6d29809585a97daec58c6b050'
+      ];
+
+      for(var i = 0; i < keys.length; ++i) {
+        (function(i) {
+          var key = UTIL.hexToBytes(keys[i]);
+          var iv = UTIL.hexToBytes(ivs[i]);
+          var input = UTIL.hexToBytes(inputs[i]);
+          var output = UTIL.hexToBytes(outputs[i]);
+
+          it('should aes-192-ctr encrypt: ' + inputs[i], function() {
+            // encrypt
+            var cipher = CIPHER.createCipher('AES-CTR', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(input));
+            cipher.finish();
+            ASSERT.equal(cipher.output.toHex(), outputs[i]);
+          });
+
+          it('should aes-192-ctr decrypt: ' + outputs[i], function() {
+            // decrypt
+            var cipher = CIPHER.createDecipher('AES-CTR', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(output));
+            cipher.finish();
+            var out = cipher.output.toHex();
+            ASSERT.equal(out, inputs[i]);
+          });
+        })(i);
+      }
+    })();
+
+    // AES-256-CTR
+    (function() {
+      var keys = [
+        '603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4'
+      ];
+
+      var ivs = [
+        'f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff'
+      ];
+
+      var inputs = [
+        '6bc1bee22e409f96e93d7e117393172a' +
+          'ae2d8a571e03ac9c9eb76fac45af8e51' +
+          '30c81c46a35ce411e5fbc1191a0a52ef' +
+          'f69f2445df4f9b17ad2b417be66c3710'
+      ];
+
+      var outputs = [
+        '601ec313775789a5b7a7f504bbf3d228' +
+          'f443e3ca4d62b59aca84e990cacaf5c5' +
+          '2b0930daa23de94ce87017ba2d84988d' +
+          'dfc9c58db67aada613c2dd08457941a6'
+      ];
+
+      for(var i = 0; i < keys.length; ++i) {
+        (function(i) {
+          var key = UTIL.hexToBytes(keys[i]);
+          var iv = UTIL.hexToBytes(ivs[i]);
+          var input = UTIL.hexToBytes(inputs[i]);
+          var output = UTIL.hexToBytes(outputs[i]);
+
+          it('should aes-256-ctr encrypt: ' + inputs[i], function() {
+            // encrypt
+            var cipher = CIPHER.createCipher('AES-CTR', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(input));
+            cipher.finish();
+            ASSERT.equal(cipher.output.toHex(), outputs[i]);
+          });
+
+          it('should aes-256-ctr decrypt: ' + outputs[i], function() {
+            // decrypt
+            var cipher = CIPHER.createDecipher('AES-CTR', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(output));
+            cipher.finish();
+            var out = cipher.output.toHex();
+            ASSERT.equal(out, inputs[i]);
+          });
+        })(i);
+      }
+    })();
+
+    // AES-128-GCM
+    (function() {
+      var keys = [
+        '00000000000000000000000000000000',
+        '00000000000000000000000000000000',
+        'feffe9928665731c6d6a8f9467308308',
+        'feffe9928665731c6d6a8f9467308308',
+        'feffe9928665731c6d6a8f9467308308',
+        'feffe9928665731c6d6a8f9467308308'
+      ];
+
+      var ivs = [
+        '000000000000000000000000',
+        '000000000000000000000000',
+        'cafebabefacedbaddecaf888',
+        'cafebabefacedbaddecaf888',
+        'cafebabefacedbad',
+        '9313225df88406e555909c5aff5269aa' +
+          '6a7a9538534f7da1e4c303d2a318a728' +
+          'c3c0c95156809539fcf0e2429a6b5254' +
+          '16aedbf5a0de6a57a637b39b'
+      ];
+
+      var adatas = [
+        '',
+        '',
+        '',
+        'feedfacedeadbeeffeedfacedeadbeef' +
+          'abaddad2',
+        'feedfacedeadbeeffeedfacedeadbeef' +
+          'abaddad2',
+        'feedfacedeadbeeffeedfacedeadbeef' +
+          'abaddad2'
+      ];
+
+      var inputs = [
+        '',
+        '00000000000000000000000000000000',
+        'd9313225f88406e5a55909c5aff5269a' +
+          '86a7a9531534f7da2e4c303d8a318a72' +
+          '1c3c0c95956809532fcf0e2449a6b525' +
+          'b16aedf5aa0de657ba637b391aafd255',
+        'd9313225f88406e5a55909c5aff5269a' +
+          '86a7a9531534f7da2e4c303d8a318a72' +
+          '1c3c0c95956809532fcf0e2449a6b525' +
+          'b16aedf5aa0de657ba637b39',
+        'd9313225f88406e5a55909c5aff5269a' +
+          '86a7a9531534f7da2e4c303d8a318a72' +
+          '1c3c0c95956809532fcf0e2449a6b525' +
+          'b16aedf5aa0de657ba637b39',
+        'd9313225f88406e5a55909c5aff5269a' +
+          '86a7a9531534f7da2e4c303d8a318a72' +
+          '1c3c0c95956809532fcf0e2449a6b525' +
+          'b16aedf5aa0de657ba637b39'
+      ];
+
+      var outputs = [
+        '',
+        '0388dace60b6a392f328c2b971b2fe78',
+        '42831ec2217774244b7221b784d0d49c' +
+          'e3aa212f2c02a4e035c17e2329aca12e' +
+          '21d514b25466931c7d8f6a5aac84aa05' +
+          '1ba30b396a0aac973d58e091473f5985',
+        '42831ec2217774244b7221b784d0d49c' +
+          'e3aa212f2c02a4e035c17e2329aca12e' +
+          '21d514b25466931c7d8f6a5aac84aa05' +
+          '1ba30b396a0aac973d58e091',
+        '61353b4c2806934a777ff51fa22a4755' +
+          '699b2a714fcdc6f83766e5f97b6c7423' +
+          '73806900e49f24b22b097544d4896b42' +
+          '4989b5e1ebac0f07c23f4598',
+        '8ce24998625615b603a033aca13fb894' +
+          'be9112a5c3a211a8ba262a3cca7e2ca7' +
+          '01e4a9a4fba43c90ccdcb281d48c7c6f' +
+          'd62875d2aca417034c34aee5'
+      ];
+
+      var tags = [
+        '58e2fccefa7e3061367f1d57a4e7455a',
+        'ab6e47d42cec13bdf53a67b21257bddf',
+        '4d5c2af327cd64a62cf35abd2ba6fab4',
+        '5bc94fbc3221a5db94fae95ae7121a47',
+        '3612d2e79e3b0785561be14aaca2fccb',
+        '619cc5aefffe0bfa462af43c1699d050'
+      ];
+
+      for(var i = 0; i < keys.length; ++i) {
+        (function(i) {
+          var key = UTIL.hexToBytes(keys[i]);
+          var iv = UTIL.hexToBytes(ivs[i]);
+          var adata = UTIL.hexToBytes(adatas[i]);
+          var input = UTIL.hexToBytes(inputs[i]);
+          var output = UTIL.hexToBytes(outputs[i]);
+
+          it('should aes-128-gcm encrypt: ' + inputs[i], function() {
+            // encrypt
+            var cipher = CIPHER.createCipher('AES-GCM', key);
+            cipher.start({iv: iv, additionalData: adata});
+            cipher.update(UTIL.createBuffer(input));
+            cipher.finish();
+            ASSERT.equal(cipher.output.toHex(), outputs[i]);
+            ASSERT.equal(cipher.mode.tag.toHex(), tags[i]);
+          });
+
+          it('should aes-128-gcm decrypt: ' + outputs[i], function() {
+            // decrypt
+            var cipher = CIPHER.createDecipher('AES-GCM', key);
+            cipher.start({
+              iv: iv,
+              additionalData: adata,
+              tag: UTIL.hexToBytes(tags[i])
+            });
+            cipher.update(UTIL.createBuffer(output));
+            var pass = cipher.finish();
+            ASSERT.equal(cipher.mode.tag.toHex(), tags[i]);
+            ASSERT.equal(pass, true);
+            ASSERT.equal(cipher.output.toHex(), inputs[i]);
+          });
+        })(i);
+      }
+    })();
+
+    // AES-192-GCM
+    (function() {
+      var keys = [
+        '00000000000000000000000000000000' +
+          '0000000000000000',
+        '00000000000000000000000000000000' +
+          '0000000000000000',
+        'feffe9928665731c6d6a8f9467308308' +
+          'feffe9928665731c',
+        'feffe9928665731c6d6a8f9467308308' +
+          'feffe9928665731c',
+        'feffe9928665731c6d6a8f9467308308' +
+          'feffe9928665731c',
+        'feffe9928665731c6d6a8f9467308308' +
+          'feffe9928665731c'
+      ];
+
+      var ivs = [
+        '000000000000000000000000',
+        '000000000000000000000000',
+        'cafebabefacedbaddecaf888',
+        'cafebabefacedbaddecaf888',
+        'cafebabefacedbad',
+        '9313225df88406e555909c5aff5269aa' +
+          '6a7a9538534f7da1e4c303d2a318a728' +
+          'c3c0c95156809539fcf0e2429a6b5254' +
+          '16aedbf5a0de6a57a637b39b'
+      ];
+
+      var adatas = [
+        '',
+        '',
+        '',
+        'feedfacedeadbeeffeedfacedeadbeef' +
+          'abaddad2',
+        'feedfacedeadbeeffeedfacedeadbeef' +
+          'abaddad2',
+        'feedfacedeadbeeffeedfacedeadbeef' +
+          'abaddad2'
+      ];
+
+      var inputs = [
+        '',
+        '00000000000000000000000000000000',
+        'd9313225f88406e5a55909c5aff5269a' +
+          '86a7a9531534f7da2e4c303d8a318a72' +
+          '1c3c0c95956809532fcf0e2449a6b525' +
+          'b16aedf5aa0de657ba637b391aafd255',
+        'd9313225f88406e5a55909c5aff5269a' +
+          '86a7a9531534f7da2e4c303d8a318a72' +
+          '1c3c0c95956809532fcf0e2449a6b525' +
+          'b16aedf5aa0de657ba637b39',
+        'd9313225f88406e5a55909c5aff5269a' +
+          '86a7a9531534f7da2e4c303d8a318a72' +
+          '1c3c0c95956809532fcf0e2449a6b525' +
+          'b16aedf5aa0de657ba637b39',
+        'd9313225f88406e5a55909c5aff5269a' +
+          '86a7a9531534f7da2e4c303d8a318a72' +
+          '1c3c0c95956809532fcf0e2449a6b525' +
+          'b16aedf5aa0de657ba637b39'
+      ];
+
+      var outputs = [
+        '',
+        '98e7247c07f0fe411c267e4384b0f600',
+        '3980ca0b3c00e841eb06fac4872a2757' +
+          '859e1ceaa6efd984628593b40ca1e19c' +
+          '7d773d00c144c525ac619d18c84a3f47' +
+          '18e2448b2fe324d9ccda2710acade256',
+        '3980ca0b3c00e841eb06fac4872a2757' +
+          '859e1ceaa6efd984628593b40ca1e19c' +
+          '7d773d00c144c525ac619d18c84a3f47' +
+          '18e2448b2fe324d9ccda2710',
+        '0f10f599ae14a154ed24b36e25324db8' +
+          'c566632ef2bbb34f8347280fc4507057' +
+          'fddc29df9a471f75c66541d4d4dad1c9' +
+          'e93a19a58e8b473fa0f062f7',
+        'd27e88681ce3243c4830165a8fdcf9ff' +
+          '1de9a1d8e6b447ef6ef7b79828666e45' +
+          '81e79012af34ddd9e2f037589b292db3' +
+          'e67c036745fa22e7e9b7373b'
+      ];
+
+      var tags = [
+        'cd33b28ac773f74ba00ed1f312572435',
+        '2ff58d80033927ab8ef4d4587514f0fb',
+        '9924a7c8587336bfb118024db8674a14',
+        '2519498e80f1478f37ba55bd6d27618c',
+        '65dcc57fcf623a24094fcca40d3533f8',
+        'dcf566ff291c25bbb8568fc3d376a6d9'
+      ];
+
+      for(var i = 0; i < keys.length; ++i) {
+        (function(i) {
+          var key = UTIL.hexToBytes(keys[i]);
+          var iv = UTIL.hexToBytes(ivs[i]);
+          var adata = UTIL.hexToBytes(adatas[i]);
+          var input = UTIL.hexToBytes(inputs[i]);
+          var output = UTIL.hexToBytes(outputs[i]);
+
+          it('should aes-128-gcm encrypt: ' + inputs[i], function() {
+            // encrypt
+            var cipher = CIPHER.createCipher('AES-GCM', key);
+            cipher.start({iv: iv, additionalData: adata});
+            cipher.update(UTIL.createBuffer(input));
+            cipher.finish();
+            ASSERT.equal(cipher.output.toHex(), outputs[i]);
+            ASSERT.equal(cipher.mode.tag.toHex(), tags[i]);
+          });
+
+          it('should aes-128-gcm decrypt: ' + outputs[i], function() {
+            // decrypt
+            var cipher = CIPHER.createDecipher('AES-GCM', key);
+            cipher.start({
+              iv: iv,
+              additionalData: adata,
+              tag: UTIL.hexToBytes(tags[i])
+            });
+            cipher.update(UTIL.createBuffer(output));
+            var pass = cipher.finish();
+            ASSERT.equal(cipher.mode.tag.toHex(), tags[i]);
+            ASSERT.equal(pass, true);
+            ASSERT.equal(cipher.output.toHex(), inputs[i]);
+          });
+        })(i);
+      }
+    })();
+
+    // AES-256-GCM
+    (function() {
+      var keys = [
+        '00000000000000000000000000000000' +
+          '00000000000000000000000000000000',
+        '00000000000000000000000000000000' +
+          '00000000000000000000000000000000',
+        'feffe9928665731c6d6a8f9467308308' +
+          'feffe9928665731c6d6a8f9467308308',
+        'feffe9928665731c6d6a8f9467308308' +
+          'feffe9928665731c6d6a8f9467308308',
+        'feffe9928665731c6d6a8f9467308308' +
+          'feffe9928665731c6d6a8f9467308308',
+        'feffe9928665731c6d6a8f9467308308' +
+          'feffe9928665731c6d6a8f9467308308'
+      ];
+
+      var ivs = [
+        '000000000000000000000000',
+        '000000000000000000000000',
+        'cafebabefacedbaddecaf888',
+        'cafebabefacedbaddecaf888',
+        'cafebabefacedbad',
+        '9313225df88406e555909c5aff5269aa' +
+          '6a7a9538534f7da1e4c303d2a318a728' +
+          'c3c0c95156809539fcf0e2429a6b5254' +
+          '16aedbf5a0de6a57a637b39b'
+      ];
+
+      var adatas = [
+        '',
+        '',
+        '',
+        'feedfacedeadbeeffeedfacedeadbeef' +
+          'abaddad2',
+        'feedfacedeadbeeffeedfacedeadbeef' +
+          'abaddad2',
+        'feedfacedeadbeeffeedfacedeadbeef' +
+          'abaddad2'
+      ];
+
+      var inputs = [
+        '',
+        '00000000000000000000000000000000',
+        'd9313225f88406e5a55909c5aff5269a' +
+          '86a7a9531534f7da2e4c303d8a318a72' +
+          '1c3c0c95956809532fcf0e2449a6b525' +
+          'b16aedf5aa0de657ba637b391aafd255',
+        'd9313225f88406e5a55909c5aff5269a' +
+          '86a7a9531534f7da2e4c303d8a318a72' +
+          '1c3c0c95956809532fcf0e2449a6b525' +
+          'b16aedf5aa0de657ba637b39',
+        'd9313225f88406e5a55909c5aff5269a' +
+          '86a7a9531534f7da2e4c303d8a318a72' +
+          '1c3c0c95956809532fcf0e2449a6b525' +
+          'b16aedf5aa0de657ba637b39',
+        'd9313225f88406e5a55909c5aff5269a' +
+          '86a7a9531534f7da2e4c303d8a318a72' +
+          '1c3c0c95956809532fcf0e2449a6b525' +
+          'b16aedf5aa0de657ba637b39'
+      ];
+
+      var outputs = [
+        '',
+        'cea7403d4d606b6e074ec5d3baf39d18',
+        '522dc1f099567d07f47f37a32a84427d' +
+          '643a8cdcbfe5c0c97598a2bd2555d1aa' +
+          '8cb08e48590dbb3da7b08b1056828838' +
+          'c5f61e6393ba7a0abcc9f662898015ad',
+        '522dc1f099567d07f47f37a32a84427d' +
+          '643a8cdcbfe5c0c97598a2bd2555d1aa' +
+          '8cb08e48590dbb3da7b08b1056828838' +
+          'c5f61e6393ba7a0abcc9f662',
+        'c3762df1ca787d32ae47c13bf19844cb' +
+          'af1ae14d0b976afac52ff7d79bba9de0' +
+          'feb582d33934a4f0954cc2363bc73f78' +
+          '62ac430e64abe499f47c9b1f',
+        '5a8def2f0c9e53f1f75d7853659e2a20' +
+          'eeb2b22aafde6419a058ab4f6f746bf4' +
+          '0fc0c3b780f244452da3ebf1c5d82cde' +
+          'a2418997200ef82e44ae7e3f'
+      ];
+
+      var tags = [
+        '530f8afbc74536b9a963b4f1c4cb738b',
+        'd0d1c8a799996bf0265b98b5d48ab919',
+        'b094dac5d93471bdec1a502270e3cc6c',
+        '76fc6ece0f4e1768cddf8853bb2d551b',
+        '3a337dbf46a792c45e454913fe2ea8f2',
+        'a44a8266ee1c8eb0c8b5d4cf5ae9f19a'
+      ];
+
+      for(var i = 0; i < keys.length; ++i) {
+        (function(i) {
+          var key = UTIL.hexToBytes(keys[i]);
+          var iv = UTIL.hexToBytes(ivs[i]);
+          var adata = UTIL.hexToBytes(adatas[i]);
+          var input = UTIL.hexToBytes(inputs[i]);
+          var output = UTIL.hexToBytes(outputs[i]);
+
+          it('should aes-128-gcm encrypt: ' + inputs[i], function() {
+            // encrypt
+            var cipher = CIPHER.createCipher('AES-GCM', key);
+            cipher.start({iv: iv, additionalData: adata});
+            cipher.update(UTIL.createBuffer(input));
+            cipher.finish();
+            ASSERT.equal(cipher.output.toHex(), outputs[i]);
+            ASSERT.equal(cipher.mode.tag.toHex(), tags[i]);
+          });
+
+          it('should aes-128-gcm decrypt: ' + outputs[i], function() {
+            // decrypt
+            var cipher = CIPHER.createDecipher('AES-GCM', key);
+            cipher.start({
+              iv: iv,
+              additionalData: adata,
+              tag: UTIL.hexToBytes(tags[i])
+            });
+            cipher.update(UTIL.createBuffer(output));
+            var pass = cipher.finish();
+            ASSERT.equal(cipher.mode.tag.toHex(), tags[i]);
+            ASSERT.equal(pass, true);
+            ASSERT.equal(cipher.output.toHex(), inputs[i]);
+          });
+        })(i);
+      }
+    })();
+  });
+}
+
+// check for AMD
+var forge = {};
+if(typeof define === 'function') {
+  define([
+    'forge/cipher',
+    'forge/aes',
+    'forge/util'
+  ], function(CIPHER, AES, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      CIPHER(forge),
+      AES(forge),
+      UTIL(forge)
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/cipher')(forge),
+    require('../../js/aes')(forge),
+    require('../../js/util')(forge));
+}
+
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/asn1.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/asn1.js
new file mode 100644
index 0000000..7d0880e
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/asn1.js
@@ -0,0 +1,262 @@
+(function() {
+
+function Tests(ASSERT, ASN1, UTIL) {
+  describe('asn1', function() {
+    // TODO: add more ASN.1 coverage
+
+    it('should convert an OID to DER', function() {
+      ASSERT.equal(ASN1.oidToDer('1.2.840.113549').toHex(), '2a864886f70d');
+    });
+
+    it('should convert an OID from DER', function() {
+      var der = UTIL.hexToBytes('2a864886f70d');
+      ASSERT.equal(ASN1.derToOid(der), '1.2.840.113549');
+    });
+
+    it('should convert INTEGER 0 to DER', function() {
+      ASSERT.equal(ASN1.integerToDer(0).toHex(), '00');
+    });
+
+    it('should convert INTEGER 1 to DER', function() {
+      ASSERT.equal(ASN1.integerToDer(1).toHex(), '01');
+    });
+
+    it('should convert INTEGER 127 to DER', function() {
+      ASSERT.equal(ASN1.integerToDer(127).toHex(), '7f');
+    });
+
+    it('should convert INTEGER 128 to DER', function() {
+      ASSERT.equal(ASN1.integerToDer(128).toHex(), '0080');
+    });
+
+    it('should convert INTEGER 256 to DER', function() {
+      ASSERT.equal(ASN1.integerToDer(256).toHex(), '0100');
+    });
+
+    it('should convert INTEGER -128 to DER', function() {
+      ASSERT.equal(ASN1.integerToDer(-128).toHex(), '80');
+    });
+
+    it('should convert INTEGER -129 to DER', function() {
+      ASSERT.equal(ASN1.integerToDer(-129).toHex(), 'ff7f');
+    });
+
+    it('should convert INTEGER 32768 to DER', function() {
+      ASSERT.equal(ASN1.integerToDer(32768).toHex(), '008000');
+    });
+
+    it('should convert INTEGER -32768 to DER', function() {
+      ASSERT.equal(ASN1.integerToDer(-32768).toHex(), '8000');
+    });
+
+    it('should convert INTEGER -32769 to DER', function() {
+      ASSERT.equal(ASN1.integerToDer(-32769).toHex(), 'ff7fff');
+    });
+
+    it('should convert INTEGER 8388608 to DER', function() {
+      ASSERT.equal(ASN1.integerToDer(8388608).toHex(), '00800000');
+    });
+
+    it('should convert INTEGER -8388608 to DER', function() {
+      ASSERT.equal(ASN1.integerToDer(-8388608).toHex(), '800000');
+    });
+
+    it('should convert INTEGER -8388609 to DER', function() {
+      ASSERT.equal(ASN1.integerToDer(-8388609).toHex(), 'ff7fffff');
+    });
+
+    it('should convert INTEGER 2147483647 to DER', function() {
+      ASSERT.equal(ASN1.integerToDer(2147483647).toHex(), '7fffffff');
+    });
+
+    it('should convert INTEGER -2147483648 to DER', function() {
+      ASSERT.equal(ASN1.integerToDer(-2147483648).toHex(), '80000000');
+    });
+
+    it('should convert INTEGER 0 from DER', function() {
+      var der = UTIL.hexToBytes('00');
+      ASSERT.equal(ASN1.derToInteger(der), 0);
+    });
+
+    it('should convert INTEGER 1 from DER', function() {
+      var der = UTIL.hexToBytes('01');
+      ASSERT.equal(ASN1.derToInteger(der), 1);
+    });
+
+    it('should convert INTEGER 127 from DER', function() {
+      var der = UTIL.hexToBytes('7f');
+      ASSERT.equal(ASN1.derToInteger(der), 127);
+    });
+
+    it('should convert INTEGER 128 from DER', function() {
+      var der = UTIL.hexToBytes('0080');
+      ASSERT.equal(ASN1.derToInteger(der), 128);
+    });
+
+    it('should convert INTEGER 256 from DER', function() {
+      var der = UTIL.hexToBytes('0100');
+      ASSERT.equal(ASN1.derToInteger(der), 256);
+    });
+
+    it('should convert INTEGER -128 from DER', function() {
+      var der = UTIL.hexToBytes('80');
+      ASSERT.equal(ASN1.derToInteger(der), -128);
+    });
+
+    it('should convert INTEGER -129 from DER', function() {
+      var der = UTIL.hexToBytes('ff7f');
+      ASSERT.equal(ASN1.derToInteger(der), -129);
+    });
+
+    it('should convert INTEGER 32768 from DER', function() {
+      var der = UTIL.hexToBytes('008000');
+      ASSERT.equal(ASN1.derToInteger(der), 32768);
+    });
+
+    it('should convert INTEGER -32768 from DER', function() {
+      var der = UTIL.hexToBytes('8000');
+      ASSERT.equal(ASN1.derToInteger(der), -32768);
+    });
+
+    it('should convert INTEGER -32769 from DER', function() {
+      var der = UTIL.hexToBytes('ff7fff');
+      ASSERT.equal(ASN1.derToInteger(der), -32769);
+    });
+
+    it('should convert INTEGER 8388608 from DER', function() {
+      var der = UTIL.hexToBytes('00800000');
+      ASSERT.equal(ASN1.derToInteger(der), 8388608);
+    });
+
+    it('should convert INTEGER -8388608 from DER', function() {
+      var der = UTIL.hexToBytes('800000');
+      ASSERT.equal(ASN1.derToInteger(der), -8388608);
+    });
+
+    it('should convert INTEGER -8388609 from DER', function() {
+      var der = UTIL.hexToBytes('ff7fffff');
+      ASSERT.equal(ASN1.derToInteger(der), -8388609);
+    });
+
+    it('should convert INTEGER 2147483647 from DER', function() {
+      var der = UTIL.hexToBytes('7fffffff');
+      ASSERT.equal(ASN1.derToInteger(der), 2147483647);
+    });
+
+    it('should convert INTEGER -2147483648 from DER', function() {
+      var der = UTIL.hexToBytes('80000000');
+      ASSERT.equal(ASN1.derToInteger(der), -2147483648);
+    });
+
+    (function() {
+      var tests = [{
+        in: '20110223123400',
+        out: 1298464440000
+      }, {
+        in: '20110223123400.1',
+        out: 1298464440100
+      }, {
+        in: '20110223123400.123',
+        out: 1298464440123
+      }];
+      for(var i = 0; i < tests.length; ++i) {
+        var test = tests[i];
+        it('should convert local generalized time "' + test.in + '" to a Date', function() {
+          var d = ASN1.generalizedTimeToDate(test.in);
+          var localOffset = d.getTimezoneOffset() * 60000;
+          ASSERT.equal(d.getTime(), test.out + localOffset);
+        });
+      }
+    })();
+
+    (function() {
+      var tests = [{
+        in: '20110223123400Z', // Wed Feb 23 12:34:00.000 UTC 2011
+        out: 1298464440000
+      }, {
+        in: '20110223123400.1Z', // Wed Feb 23 12:34:00.100 UTC 2011
+        out: 1298464440100
+      }, {
+        in: '20110223123400.123Z', // Wed Feb 23 12:34:00.123 UTC 2011
+        out: 1298464440123
+      }, {
+        in: '20110223123400+0200', // Wed Feb 23 10:34:00.000 UTC 2011
+        out: 1298457240000
+      }, {
+        in: '20110223123400.1+0200', // Wed Feb 23 10:34:00.100 UTC 2011
+        out: 1298457240100
+      }, {
+        in: '20110223123400.123+0200', // Wed Feb 23 10:34:00.123 UTC 2011
+        out: 1298457240123
+      }, {
+        in: '20110223123400-0200', // Wed Feb 23 14:34:00.000 UTC 2011
+        out: 1298471640000
+      }, {
+        in: '20110223123400.1-0200', // Wed Feb 23 14:34:00.100 UTC 2011
+        out: 1298471640100
+      }, {
+        in: '20110223123400.123-0200', // Wed Feb 23 14:34:00.123 UTC 2011
+        out: 1298471640123
+      }];
+      for(var i = 0; i < tests.length; ++i) {
+        var test = tests[i];
+        it('should convert utc generalized time "' + test.in + '" to a Date', function() {
+          var d = ASN1.generalizedTimeToDate(test.in);
+          ASSERT.equal(d.getTime(), test.out);
+        });
+      }
+    })();
+
+    (function() {
+      var tests = [{
+        in: '1102231234Z', // Wed Feb 23 12:34:00 UTC 2011
+        out: 1298464440000
+      }, {
+        in: '1102231234+0200', // Wed Feb 23 10:34:00 UTC 2011
+        out: 1298457240000
+      }, {
+        in: '1102231234-0200', // Wed Feb 23 14:34:00 UTC 2011
+        out: 1298471640000
+      }, {
+        in: '110223123456Z', // Wed Feb 23 12:34:56 UTC 2011
+        out: 1298464496000
+      }, {
+        in: '110223123456+0200', // Wed Feb 23 10:34:56 UTC 2011
+        out: 1298457296000
+      }, {
+        in: '110223123456-0200', // Wed Feb 23 14:34:56 UTC 2011
+        out: 1298471696000
+      }];
+      for(var i = 0; i < tests.length; ++i) {
+        var test = tests[i];
+        it('should convert utc time "' + test.in + '" to a Date', function() {
+          var d = ASN1.utcTimeToDate(test.in);
+          ASSERT.equal(d.getTime(), test.out);
+        });
+      }
+    })();
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/asn1',
+    'forge/util'
+  ], function(ASN1, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      ASN1(),
+      UTIL()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/asn1')(),
+    require('../../js/util')());
+}
+
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/browser.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/browser.js
new file mode 100644
index 0000000..a96b2d6
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/browser.js
@@ -0,0 +1,41 @@
+var server = require('../server');
+var grunt = require('grunt');
+
+describe('browser', function() {
+  it('should run tests', function(done) {
+    this.timeout(60 * 1000 * 5);
+
+    return server.main(function(err, info) {
+      if(err) {
+        return done(err);
+      }
+
+      grunt.initConfig({
+        mocha: {
+          all: {
+            options: {
+              reporter: 'List',
+              urls: ['http://localhost:' + info.port + '/index.html']
+            }
+          }
+        }
+      });
+
+      grunt.loadNpmTasks('grunt-mocha');
+
+      grunt.registerInitTask('default', function() {
+        grunt.task.run(['mocha']);
+      });
+      grunt.tasks(['default'], {
+        //debug: true
+      }, function() {
+        if(err) {
+          return done(err);
+        }
+        // finish immediately
+        done(null);
+        return info.server.close();
+      });
+    });
+  });
+});
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/csr.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/csr.js
new file mode 100644
index 0000000..340c09f
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/csr.js
@@ -0,0 +1,148 @@
+(function() {
+
+function Tests(ASSERT, PKI) {
+  var _pem = {
+    privateKey: '-----BEGIN RSA PRIVATE KEY-----\r\n' +
+      'MIICXQIBAAKBgQDL0EugUiNGMWscLAVM0VoMdhDZEJOqdsUMpx9U0YZI7szokJqQ\r\n' +
+      'NIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMGTkP3VF29vXBo+dLq5e+8VyAy\r\n' +
+      'Q3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGtvnM+z0MYDdKo80efzwIDAQAB\r\n' +
+      'AoGAIzkGONi5G+JifmXlLJdplom486p3upf4Ce2/7mqfaG9MnkyPSairKD/JXvfh\r\n' +
+      'NNWkkN8DKKDKBcVVElPgORYT0qwrWc7ueLBMUCbRXb1ZyfEulimG0R3kjUh7NYau\r\n' +
+      'DaIkVgfykXGSQMZx8FoaT6L080zd+0emKDDYRrb+/kgJNJECQQDoUZoiC2K/DWNY\r\n' +
+      'h3/ppZ0ane2y4SBmJUHJVMPQ2CEgxsrJTxet668ckNCKaOP/3VFPoWC41f17DvKq\r\n' +
+      'noYINNntAkEA4JbZBZBVUrQFhHlrpXT4jzqtO2RlKZzEq8qmFZfEErxOT1WMyyCi\r\n' +
+      'lAQ5gUKardo1Kf0omC8Xq/uO9ZYdED55KwJBALs6cJ65UFaq4oLJiQPzLd7yokuE\r\n' +
+      'dcj8g71PLBTW6jPxIiMFNA89nz3FU9wIVp+xbMNhSoMMKqIPVPC+m0Rn260CQQDA\r\n' +
+      'I83fWK/mZWUjBM33a68KumRiH238v8XyQxj7+C8i6D8G2GXvkigFAehAkb7LZZd+\r\n' +
+      'KLuGFyPlWv3fVWHf99KpAkBQFKk3MRMl6IGJZUEFQe4l5whm8LkGU4acSqv9B3xt\r\n' +
+      'qROkCrsFrMPqjuuzEmyHoQZ64r2PLJg7FOuyhBnQUOt4\r\n' +
+      '-----END RSA PRIVATE KEY-----\r\n',
+    publicKey: '-----BEGIN PUBLIC KEY-----\r\n' +
+      'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDL0EugUiNGMWscLAVM0VoMdhDZ\r\n' +
+      'EJOqdsUMpx9U0YZI7szokJqQNIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMG\r\n' +
+      'TkP3VF29vXBo+dLq5e+8VyAyQ3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGt\r\n' +
+      'vnM+z0MYDdKo80efzwIDAQAB\r\n' +
+      '-----END PUBLIC KEY-----\r\n'
+  };
+
+  describe('csr', function() {
+    it('should generate a certification request', function() {
+      var keys = {
+        privateKey: PKI.privateKeyFromPem(_pem.privateKey),
+        publicKey: PKI.publicKeyFromPem(_pem.publicKey)
+      };
+      var csr = PKI.createCertificationRequest();
+      csr.publicKey = keys.publicKey;
+      csr.setSubject([{
+        name: 'commonName',
+        value: 'example.org'
+      }, {
+        name: 'countryName',
+        value: 'US'
+      }, {
+        shortName: 'ST',
+        value: 'Virginia'
+      }, {
+        name: 'localityName',
+        value: 'Blacksburg'
+      }, {
+        name: 'organizationName',
+        value: 'Test'
+      }, {
+        shortName: 'OU',
+        value: 'Test'
+      }]);
+      // add optional attributes
+      csr.setAttributes([{
+        name: 'challengePassword',
+        value: 'password'
+      }, {
+        name: 'unstructuredName',
+        value: 'My company'
+      }, {
+        name: 'extensionRequest',
+        extensions: [{
+          name: 'subjectAltName',
+          altNames: [{
+            // type 2 is DNS
+            type: 2,
+            value: 'test.domain.com'
+          }, {
+            type: 2,
+            value: 'other.domain.com'
+          }, {
+            type: 2,
+            value: 'www.domain.net'
+          }]
+        }]
+      }]);
+
+      // sign certification request
+      csr.sign(keys.privateKey);
+
+      var pem = PKI.certificationRequestToPem(csr);
+      csr = PKI.certificationRequestFromPem(pem);
+      ASSERT.ok(csr.getAttribute({name: 'extensionRequest'}));
+      ASSERT.equal(csr.getAttribute({name: 'extensionRequest'}).extensions[0].name, 'subjectAltName');
+      ASSERT.deepEqual(csr.getAttribute({name: 'extensionRequest'}).extensions[0].altNames, [{
+        // type 2 is DNS
+        type: 2,
+        value: 'test.domain.com'
+      }, {
+        type: 2,
+        value: 'other.domain.com'
+      }, {
+        type: 2,
+        value: 'www.domain.net'
+      }]);
+      ASSERT.ok(csr.verify());
+    });
+
+    it('should load an OpenSSL-generated certification request', function() {
+      var pem = '-----BEGIN CERTIFICATE REQUEST-----\r\n' +
+        'MIICdTCCAV0CAQAwMDEVMBMGA1UEAwwMTXlDb21tb25OYW1lMRcwFQYDVQQKDA5N\r\n' +
+        'eU9yZ2FuaXphdGlvbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKRU\r\n' +
+        'zrAMbiiSjAYYl3PWsOrNwY0VtemgRZc0t7+3FlWp1e8uIA3KxZFZY875wo0QOvD+\r\n' +
+        'AdNv5+YnokgzOi83F3T4yewBSR0TiO3Pa4tL4C7CzWnhYliC/owk5bHCV0HLkYUW\r\n' +
+        'F6z7Lx3HyhoxlKmrHySSPPZRLKp7KcwxbjFc2EfhQV21I73Z1mCG6MEp7cN2qBbQ\r\n' +
+        'PyOMNjAUibOWs4JJEdUjWhm86EZm9+qfgpL5tlpZCe+kXySrKTp56mMsfSOQvlol\r\n' +
+        'pRO8pP9AUjaEqRikCZ745I/9W7dHNPUoyxkWV5jRDwcT7s652+L6oxtoqVOXpg28\r\n' +
+        'uAL0kUZQMa8wkYUKZiMCAwEAAaAAMA0GCSqGSIb3DQEBBQUAA4IBAQCXQH+ut6tr\r\n' +
+        'Z/FdIDOljrc7uh8XpFRKS3GqC/PJsEwrV7d3CX5HuWPTuPc9FU5FQ88w6evXEA0o\r\n' +
+        'ijxHuydeXmdjpy433vXWo1TaRSXh1WaBMG5pW/SlGZK9/Hr1P0v7KN/KCY5nXxoQ\r\n' +
+        'k3Ndg9HzGrYnRoJVXzvdQeBGwCoJFk4FH+Rxa/F03VTUU5nwx66TsL9JUp9pnbI7\r\n' +
+        'MR6DIA97LnTmut8Xp0Uurw+zsS5rif9iv0BKHd7eGpNNGl0RXu8E5dbT0zD90TSa\r\n' +
+        'P5WjxjvY+Udg8XZU+UwT3kcyTEFpiQdkzTIKXg0dFurfUE9XG/9aic9oMZ/IBZz9\r\n' +
+        'a535a7e9RkbJ\r\n' +
+        '-----END CERTIFICATE REQUEST-----\r\n';
+
+      var csr = PKI.certificationRequestFromPem(pem);
+      ASSERT.equal(csr.subject.getField('CN').value, 'MyCommonName');
+      ASSERT.equal(csr.subject.getField('O').value, 'MyOrganization');
+      ASSERT.equal(csr.signatureOid, PKI.oids.sha1WithRSAEncryption);
+      ASSERT.equal(csr.publicKey.e.toString(16), '10001');
+      ASSERT.equal(csr.publicKey.n.toString(16).toUpperCase(), 'A454CEB00C6E28928C06189773D6B0EACDC18D15B5E9A0459734B7BFB71655A9D5EF2E200DCAC5915963CEF9C28D103AF0FE01D36FE7E627A248333A2F371774F8C9EC01491D1388EDCF6B8B4BE02EC2CD69E1625882FE8C24E5B1C25741CB91851617ACFB2F1DC7CA1A3194A9AB1F24923CF6512CAA7B29CC316E315CD847E1415DB523BDD9D66086E8C129EDC376A816D03F238C36301489B396B3824911D5235A19BCE84666F7EA9F8292F9B65A5909EFA45F24AB293A79EA632C7D2390BE5A25A513BCA4FF40523684A918A4099EF8E48FFD5BB74734F528CB19165798D10F0713EECEB9DBE2FAA31B68A95397A60DBCB802F491465031AF3091850A6623');
+      ASSERT.ok(csr.verify());
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/pki'
+  ], function(PKI) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      PKI()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/pki')());
+}
+
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/des.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/des.js
new file mode 100644
index 0000000..8be2c68
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/des.js
@@ -0,0 +1,155 @@
+(function() {
+
+function Tests(ASSERT, CIPHER, DES, UTIL) {
+  describe('des', function() {
+    // OpenSSL equivalent:
+    // openssl enc -des-ecb -K a1c06b381adf3651 -nosalt
+    it('should des-ecb encrypt: foobar', function() {
+      var key = new UTIL.createBuffer(
+        UTIL.hexToBytes('a1c06b381adf3651'));
+
+      var cipher = CIPHER.createCipher('DES-ECB', key);
+      cipher.start();
+      cipher.update(UTIL.createBuffer('foobar'));
+      cipher.finish();
+      ASSERT.equal(cipher.output.toHex(), 'b705ffcf3dff06b3');
+    });
+
+    // OpenSSL equivalent:
+    // openssl enc -d -des-ecb -K a1c06b381adf3651 -nosalt
+    it('should des-ecb decrypt: b705ffcf3dff06b3', function() {
+      var key = new UTIL.createBuffer(
+        UTIL.hexToBytes('a1c06b381adf3651'));
+
+      var decipher = CIPHER.createDecipher('DES-ECB', key);
+      decipher.start();
+      decipher.update(UTIL.createBuffer(UTIL.hexToBytes('b705ffcf3dff06b3')));
+      decipher.finish();
+      ASSERT.equal(decipher.output.getBytes(), 'foobar');
+    });
+
+    // OpenSSL equivalent:
+    // openssl enc -des -K a1c06b381adf3651 -iv 818bcf76efc59662 -nosalt
+    it('should des-cbc encrypt: foobar', function() {
+      var key = new UTIL.createBuffer(
+        UTIL.hexToBytes('a1c06b381adf3651'));
+      var iv = new UTIL.createBuffer(
+        UTIL.hexToBytes('818bcf76efc59662'));
+
+      var cipher = CIPHER.createCipher('DES-CBC', key);
+      cipher.start({iv: iv});
+      cipher.update(UTIL.createBuffer('foobar'));
+      cipher.finish();
+      ASSERT.equal(cipher.output.toHex(), '3261e5839a990454');
+    });
+
+    // OpenSSL equivalent:
+    // openssl enc -d -des -K a1c06b381adf3651 -iv 818bcf76efc59662 -nosalt
+    it('should des-cbc decrypt: 3261e5839a990454', function() {
+      var key = new UTIL.createBuffer(
+        UTIL.hexToBytes('a1c06b381adf3651'));
+      var iv = new UTIL.createBuffer(
+        UTIL.hexToBytes('818bcf76efc59662'));
+
+      var decipher = CIPHER.createDecipher('DES-CBC', key);
+      decipher.start({iv: iv});
+      decipher.update(UTIL.createBuffer(UTIL.hexToBytes('3261e5839a990454')));
+      decipher.finish();
+      ASSERT.equal(decipher.output.getBytes(), 'foobar');
+    });
+
+    // OpenSSL equivalent:
+    // openssl enc -des-ede3 -K a1c06b381adf36517e84575552777779da5e3d9f994b05b5 -nosalt
+    it('should 3des-ecb encrypt: foobar', function() {
+      var key = new UTIL.createBuffer(
+        UTIL.hexToBytes('a1c06b381adf36517e84575552777779da5e3d9f994b05b5'));
+
+      var cipher = CIPHER.createCipher('3DES-ECB', key);
+      cipher.start();
+      cipher.update(UTIL.createBuffer('foobar'));
+      cipher.finish();
+      ASSERT.equal(cipher.output.toHex(), 'fce8b1ee8c6440d1');
+    });
+
+    // OpenSSL equivalent:
+    // openssl enc -d -des-ede3 -K a1c06b381adf36517e84575552777779da5e3d9f994b05b5 -nosalt
+    it('should 3des-ecb decrypt: fce8b1ee8c6440d1', function() {
+      var key = new UTIL.createBuffer(
+        UTIL.hexToBytes('a1c06b381adf36517e84575552777779da5e3d9f994b05b5'));
+
+      var decipher = CIPHER.createDecipher('3DES-ECB', key);
+      decipher.start();
+      decipher.update(UTIL.createBuffer(UTIL.hexToBytes('fce8b1ee8c6440d1')));
+      decipher.finish();
+      ASSERT.equal(decipher.output.getBytes(), 'foobar');
+    });
+
+    // OpenSSL equivalent:
+    // openssl enc -des3 -K a1c06b381adf36517e84575552777779da5e3d9f994b05b5 -iv 818bcf76efc59662 -nosalt
+    it('should 3des-cbc encrypt "foobar", restart, and encrypt "foobar,,"', function() {
+      var key = new UTIL.createBuffer(
+        UTIL.hexToBytes('a1c06b381adf36517e84575552777779da5e3d9f994b05b5'));
+      var iv = new UTIL.createBuffer(
+        UTIL.hexToBytes('818bcf76efc59662'));
+
+      var cipher = CIPHER.createCipher('3DES-CBC', key);
+      cipher.start({iv: iv.copy()});
+      cipher.update(UTIL.createBuffer('foobar'));
+      cipher.finish();
+      ASSERT.equal(cipher.output.toHex(), '209225f7687ca0b2');
+
+      cipher.start({iv: iv.copy()});
+      cipher.update(UTIL.createBuffer('foobar,,'));
+      cipher.finish();
+      ASSERT.equal(cipher.output.toHex(), '57156174c48dfc37293831bf192a6742');
+    });
+
+    // OpenSSL equivalent:
+    // openssl enc -d -des3 -K a1c06b381adf36517e84575552777779da5e3d9f994b05b5 -iv 818bcf76efc59662 -nosalt
+    it('should 3des-cbc decrypt "209225f7687ca0b2", restart, and decrypt "57156174c48dfc37293831bf192a6742,,"', function() {
+      var key = new UTIL.createBuffer(
+        UTIL.hexToBytes('a1c06b381adf36517e84575552777779da5e3d9f994b05b5'));
+      var iv = new UTIL.createBuffer(
+        UTIL.hexToBytes('818bcf76efc59662'));
+
+      var decipher = CIPHER.createDecipher('3DES-CBC', key);
+      decipher.start({iv: iv.copy()});
+      decipher.update(UTIL.createBuffer(UTIL.hexToBytes('209225f7687ca0b2')));
+      decipher.finish();
+      ASSERT.equal(decipher.output.getBytes(), 'foobar');
+
+      decipher.start({iv: iv.copy()});
+      decipher.update(
+        UTIL.createBuffer(UTIL.hexToBytes('57156174c48dfc37293831bf192a6742')));
+      decipher.finish();
+      ASSERT.equal(decipher.output.getBytes(), 'foobar,,');
+    });
+  });
+}
+
+// check for AMD
+var forge = {};
+if(typeof define === 'function') {
+  define([
+    'forge/cipher',
+    'forge/des',
+    'forge/util'
+  ], function(CIPHER, DES, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      CIPHER(forge),
+      DES(forge),
+      UTIL(forge)
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/cipher')(forge),
+    require('../../js/des')(forge),
+    require('../../js/util')(forge));
+}
+
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/hmac.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/hmac.js
new file mode 100644
index 0000000..404b36b
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/hmac.js
@@ -0,0 +1,85 @@
+(function() {
+
+function Tests(ASSERT, HMAC, UTIL) {
+  describe('hmac', function() {
+    it('should md5 hash "Hi There", 16-byte key', function() {
+      var key = UTIL.hexToBytes('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b');
+      var hmac = HMAC.create();
+      hmac.start('MD5', key);
+      hmac.update('Hi There');
+      ASSERT.equal(hmac.digest().toHex(), '9294727a3638bb1c13f48ef8158bfc9d');
+    });
+
+    it('should md5 hash "what do ya want for nothing?", "Jefe" key', function() {
+      var hmac = HMAC.create();
+      hmac.start('MD5', 'Jefe');
+      hmac.update('what do ya want for nothing?');
+      ASSERT.equal(hmac.digest().toHex(), '750c783e6ab0b503eaa86e310a5db738');
+    });
+
+    it('should md5 hash "Test Using Larger Than Block-Size Key - Hash Key First", 80-byte key', function() {
+      var key = UTIL.hexToBytes(
+        'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
+        'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
+        'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
+        'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
+      var hmac = HMAC.create();
+      hmac.start('MD5', key);
+      hmac.update('Test Using Larger Than Block-Size Key - Hash Key First');
+      ASSERT.equal(hmac.digest().toHex(), '6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd');
+    });
+
+    it('should sha1 hash "Hi There", 20-byte key', function() {
+      var key = UTIL.hexToBytes('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b');
+      var hmac = HMAC.create();
+      hmac.start('SHA1', key);
+      hmac.update('Hi There');
+      ASSERT.equal(
+        hmac.digest().toHex(), 'b617318655057264e28bc0b6fb378c8ef146be00');
+    });
+
+    it('should sha1 hash "what do ya want for nothing?", "Jefe" key', function() {
+      var hmac = HMAC.create();
+      hmac.start('SHA1', 'Jefe');
+      hmac.update('what do ya want for nothing?');
+      ASSERT.equal(
+        hmac.digest().toHex(), 'effcdf6ae5eb2fa2d27416d5f184df9c259a7c79');
+    });
+
+    it('should sha1 hash "Test Using Larger Than Block-Size Key - Hash Key First", 80-byte key', function() {
+      var key = UTIL.hexToBytes(
+        'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
+        'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
+        'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
+        'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
+      var hmac = HMAC.create();
+      hmac.start('SHA1', key);
+      hmac.update('Test Using Larger Than Block-Size Key - Hash Key First');
+      ASSERT.equal(
+        hmac.digest().toHex(), 'aa4ae5e15272d00e95705637ce8a3b55ed402112');
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/hmac',
+    'forge/util'
+  ], function(HMAC, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      HMAC(),
+      UTIL()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/hmac')(),
+    require('../../js/util')());
+}
+
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/kem.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/kem.js
new file mode 100644
index 0000000..0415abe
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/kem.js
@@ -0,0 +1,198 @@
+(function() {
+
+function Tests(ASSERT, KEM, MD, RSA, UTIL, JSBN, RANDOM) {
+
+  function FixedSecureRandom(str) {
+    var bytes = UTIL.hexToBytes(str);
+    this.getBytesSync = function(count) {
+      // prepend zeros
+      return UTIL.fillString(String.fromCharCode(0), bytes.length - count) +
+        bytes;
+    };
+  }
+
+  describe('kem', function() {
+    it('should generate and encrypt a symmetric key and decrypt it 10x', function() {
+      for(var i = 0; i < 10; ++i) {
+        var kdf = new KEM.kdf1(MD.sha256.create());
+        var kem = KEM.rsa.create(kdf);
+
+        var pair = RSA.generateKeyPair(512);
+
+        var result = kem.encrypt(pair.publicKey, 256);
+        var key1 = result.key;
+        var key2 = kem.decrypt(pair.privateKey, result.encapsulation, 256);
+
+        ASSERT.equal(UTIL.bytesToHex(key1), UTIL.bytesToHex(key2));
+      }
+    });
+  });
+
+  /**
+   * According to section "C.6 Test vectors for RSA-KEM" from ISO-18033-2 final
+   * draft.
+   */
+  describe('C.6 Test vectors for RSA-KEM from ISO-18033-2 final', function() {
+    it('should pass test vector C.6.1', function() {
+      var n = '5888113332502691251761936431009284884966640757179802337490546478326238537107326596800820237597139824869184990638749556269785797065508097452399642780486933';
+      var e = '65537';
+      var d = '3202313555859948186315374524474173995679783580392140237044349728046479396037520308981353808895461806395564474639124525446044708705259675840210989546479265';
+
+      var C0 = '4603e5324cab9cef8365c817052d954d44447b1667099edc69942d32cd594e4ffcf268ae3836e2c35744aaa53ae201fe499806b67dedaa26bf72ecbd117a6fc0';
+      var K = '5f8de105b5e96b2e490ddecbd147dd1def7e3b8e0e6a26eb7b956ccb8b3bdc1ca975bc57c3989e8fbad31a224655d800c46954840ff32052cdf0d640562bdfadfa263cfccf3c52b29f2af4a1869959bc77f854cf15bd7a25192985a842dbff8e13efee5b7e7e55bbe4d389647c686a9a9ab3fb889b2d7767d3837eea4e0a2f04';
+
+      var kdf = new KEM.kdf1(MD.sha1.create());
+      var rnd = new FixedSecureRandom('032e45326fa859a72ec235acff929b15d1372e30b207255f0611b8f785d764374152e0ac009e509e7ba30cd2f1778e113b64e135cf4e2292c75efe5288edfda4');
+      var kem = KEM.rsa.create(kdf, {prng: rnd});
+
+      var rsaPublicKey = RSA.setPublicKey(
+        new JSBN.BigInteger(n), new JSBN.BigInteger(e));
+      var rsaPrivateKey = RSA.setPrivateKey(
+        new JSBN.BigInteger(n), null, new JSBN.BigInteger(d));
+
+      var result = kem.encrypt(rsaPublicKey, 128);
+      ASSERT.equal(UTIL.bytesToHex(result.encapsulation), C0);
+      ASSERT.equal(UTIL.bytesToHex(result.key), K);
+
+      var decryptedKey = kem.decrypt(rsaPrivateKey, result.encapsulation, 128);
+      ASSERT.equal(UTIL.bytesToHex(decryptedKey), K);
+    });
+
+    it('should pass test vector C.6.2', function() {
+      var n = '5888113332502691251761936431009284884966640757179802337490546478326238537107326596800820237597139824869184990638749556269785797065508097452399642780486933';
+      var e = '65537';
+      var d = '3202313555859948186315374524474173995679783580392140237044349728046479396037520308981353808895461806395564474639124525446044708705259675840210989546479265';
+
+      var C0 = '4603e5324cab9cef8365c817052d954d44447b1667099edc69942d32cd594e4ffcf268ae3836e2c35744aaa53ae201fe499806b67dedaa26bf72ecbd117a6fc0';
+      var K = '0e6a26eb7b956ccb8b3bdc1ca975bc57c3989e8fbad31a224655d800c46954840ff32052cdf0d640562bdfadfa263cfccf3c52b29f2af4a1869959bc77f854cf15bd7a25192985a842dbff8e13efee5b7e7e55bbe4d389647c686a9a9ab3fb889b2d7767d3837eea4e0a2f04b53ca8f50fb31225c1be2d0126c8c7a4753b0807';
+
+      var kdf = new KEM.kdf2(MD.sha1.create());
+      var rnd = new FixedSecureRandom('032e45326fa859a72ec235acff929b15d1372e30b207255f0611b8f785d764374152e0ac009e509e7ba30cd2f1778e113b64e135cf4e2292c75efe5288edfda4');
+      var kem = KEM.rsa.create(kdf, {prng: rnd});
+
+      var rsaPublicKey = RSA.setPublicKey(
+        new JSBN.BigInteger(n), new JSBN.BigInteger(e));
+      var rsaPrivateKey = RSA.setPrivateKey(
+        new JSBN.BigInteger(n), null, new JSBN.BigInteger(d));
+
+      var result = kem.encrypt(rsaPublicKey, 128);
+      ASSERT.equal(UTIL.bytesToHex(result.encapsulation), C0);
+      ASSERT.equal(UTIL.bytesToHex(result.key), K);
+
+      var decryptedKey = kem.decrypt(rsaPrivateKey, result.encapsulation, 128);
+      ASSERT.equal(UTIL.bytesToHex(decryptedKey), K);
+    });
+
+    it('should pass test vector C.6.3', function() {
+      var n = '5888113332502691251761936431009284884966640757179802337490546478326238537107326596800820237597139824869184990638749556269785797065508097452399642780486933';
+      var e = '65537';
+      var d = '3202313555859948186315374524474173995679783580392140237044349728046479396037520308981353808895461806395564474639124525446044708705259675840210989546479265';
+
+      var C0 = '4603e5324cab9cef8365c817052d954d44447b1667099edc69942d32cd594e4ffcf268ae3836e2c35744aaa53ae201fe499806b67dedaa26bf72ecbd117a6fc0';
+      var K = '09e2decf2a6e1666c2f6071ff4298305e2643fd510a2403db42a8743cb989de86e668d168cbe604611ac179f819a3d18412e9eb45668f2923c087c12fee0c5a0d2a8aa70185401fbbd99379ec76c663e875a60b4aacb1319fa11c3365a8b79a44669f26fb555c80391847b05eca1cb5cf8c2d531448d33fbaca19f6410ee1fcb';
+
+      var kdf = new KEM.kdf1(MD.sha256.create(), 20);
+      var rnd = new FixedSecureRandom('032e45326fa859a72ec235acff929b15d1372e30b207255f0611b8f785d764374152e0ac009e509e7ba30cd2f1778e113b64e135cf4e2292c75efe5288edfda4');
+      var kem = KEM.rsa.create(kdf, {prng: rnd});
+
+      var rsaPublicKey = RSA.setPublicKey(
+        new JSBN.BigInteger(n), new JSBN.BigInteger(e));
+      var rsaPrivateKey = RSA.setPrivateKey(
+        new JSBN.BigInteger(n), null, new JSBN.BigInteger(d));
+
+      var result = kem.encrypt(rsaPublicKey, 128);
+      ASSERT.equal(UTIL.bytesToHex(result.encapsulation), C0);
+      ASSERT.equal(UTIL.bytesToHex(result.key), K);
+
+      var decryptedKey = kem.decrypt(rsaPrivateKey, result.encapsulation, 128);
+      ASSERT.equal(UTIL.bytesToHex(decryptedKey), K);
+    });
+
+    it('should pass test vector C.6.4', function() {
+      var n = '5888113332502691251761936431009284884966640757179802337490546478326238537107326596800820237597139824869184990638749556269785797065508097452399642780486933';
+      var e = '65537';
+      var d = '3202313555859948186315374524474173995679783580392140237044349728046479396037520308981353808895461806395564474639124525446044708705259675840210989546479265';
+
+      var C0 = '4603e5324cab9cef8365c817052d954d44447b1667099edc69942d32cd594e4ffcf268ae3836e2c35744aaa53ae201fe499806b67dedaa26bf72ecbd117a6fc0';
+      var K = '10a2403db42a8743cb989de86e668d168cbe604611ac179f819a3d18412e9eb45668f2923c087c12fee0c5a0d2a8aa70185401fbbd99379ec76c663e875a60b4aacb1319fa11c3365a8b79a44669f26fb555c80391847b05eca1cb5cf8c2d531448d33fbaca19f6410ee1fcb260892670e0814c348664f6a7248aaf998a3acc6';
+
+      var kdf = new KEM.kdf2(MD.sha256.create(), 20);
+      var rnd = new FixedSecureRandom('00032e45326fa859a72ec235acff929b15d1372e30b207255f0611b8f785d764374152e0ac009e509e7ba30cd2f1778e113b64e135cf4e2292c75efe5288edfda4');
+      var kem = KEM.rsa.create(kdf, {prng: rnd});
+
+      var rsaPublicKey = RSA.setPublicKey(
+        new JSBN.BigInteger(n), new JSBN.BigInteger(e));
+      var rsaPrivateKey = RSA.setPrivateKey(
+        new JSBN.BigInteger(n), null, new JSBN.BigInteger(d));
+
+      var result = kem.encrypt(rsaPublicKey, 128);
+      ASSERT.equal(UTIL.bytesToHex(result.encapsulation), C0);
+      ASSERT.equal(UTIL.bytesToHex(result.key), K);
+
+      var decryptedKey = kem.decrypt(rsaPrivateKey, result.encapsulation, 128);
+      ASSERT.equal(UTIL.bytesToHex(decryptedKey), K);
+    });
+  });
+
+  describe('prepended zeros test', function() {
+    it('should pass when random has leading zeros', function() {
+      var n = '5888113332502691251761936431009284884966640757179802337490546478326238537107326596800820237597139824869184990638749556269785797065508097452399642780486933';
+      var e = '65537';
+      var d = '3202313555859948186315374524474173995679783580392140237044349728046479396037520308981353808895461806395564474639124525446044708705259675840210989546479265';
+
+      var C0 = '5f268a76c1aed04bc195a143d7ee768bee0aad308d16196274a02d9c1a72bbe10cbf718de323fc0135c5f8129f96ac8f504d9623960dc54cd87bddee94f5a0b2';
+      var K = '8bf41e59dc1b83142ee32569a347a94539e48c98347c685a29e3aa8b7a3ea714d68c1a43c4a760c9d4a45149b0ce8b681e98076bdd4393394c7832a7fa71848257772ac38a4e7fbe96e8bb383becbb7242841946e82e35d9ef1667245fc82601e7edf53b897f5ce2b6bce8e1e3212abd5a8a99a0c9b99472e22a313dac396383';
+
+      var kdf = new KEM.kdf1(MD.sha1.create());
+      var rnd = new FixedSecureRandom('000e45326fa859a72ec235acff929b15d1372e30b207255f0611b8f785d764374152e0ac009e509e7ba30cd2f1778e113b64e135cf4e2292c75efe5288edfda4');
+      var kem = KEM.rsa.create(kdf, {prng: rnd});
+
+      var rsaPublicKey = RSA.setPublicKey(
+        new JSBN.BigInteger(n), new JSBN.BigInteger(e));
+      var rsaPrivateKey = RSA.setPrivateKey(
+        new JSBN.BigInteger(n), null, new JSBN.BigInteger(d));
+
+      var result = kem.encrypt(rsaPublicKey, 128);
+      ASSERT.equal(UTIL.bytesToHex(result.encapsulation), C0);
+      ASSERT.equal(UTIL.bytesToHex(result.key), K);
+
+      var decryptedKey = kem.decrypt(rsaPrivateKey, result.encapsulation, 128);
+      ASSERT.equal(UTIL.bytesToHex(decryptedKey), K);
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/kem',
+    'forge/md',
+    'forge/rsa',
+    'forge/util',
+    'forge/jsbn',
+    'forge/random'
+  ], function(KEM, MD, RSA, UTIL, JSBN, RANDOM) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      KEM(),
+      MD(),
+      RSA(),
+      UTIL(),
+      JSBN(),
+      RANDOM()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/kem')(),
+    require('../../js/md')(),
+    require('../../js/rsa')(),
+    require('../../js/util')(),
+    require('../../js/jsbn')(),
+    require('../../js/random')());
+}
+
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/md5.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/md5.js
new file mode 100644
index 0000000..5ab3d58
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/md5.js
@@ -0,0 +1,117 @@
+(function() {
+
+function Tests(ASSERT, MD5, UTIL) {
+  describe('md5', function() {
+    it('should digest the empty string', function() {
+      var md = MD5.create();
+      ASSERT.equal(md.digest().toHex(), 'd41d8cd98f00b204e9800998ecf8427e');
+    });
+
+    it('should digest "abc"', function() {
+      var md = MD5.create();
+      md.update('abc');
+      ASSERT.equal(md.digest().toHex(), '900150983cd24fb0d6963f7d28e17f72');
+    });
+
+    it('should digest "The quick brown fox jumps over the lazy dog"', function() {
+      var md = MD5.create();
+      md.update('The quick brown fox jumps over the lazy dog');
+      ASSERT.equal(md.digest().toHex(), '9e107d9d372bb6826bd81d3542a419d6');
+    });
+
+    it('should digest "c\'\u00e8"', function() {
+      var md = MD5.create();
+      md.update("c\'\u00e8", 'utf8');
+      ASSERT.equal(md.digest().toHex(), '8ef7c2941d78fe89f31e614437c9db59');
+    });
+
+    it('should digest "THIS IS A MESSAGE"', function() {
+      var md = MD5.create();
+      md.start();
+      md.update('THIS IS ');
+      md.update('A MESSAGE');
+      // do twice to check continuing digest
+      ASSERT.equal(md.digest().toHex(), '78eebfd9d42958e3f31244f116ab7bbe');
+      ASSERT.equal(md.digest().toHex(), '78eebfd9d42958e3f31244f116ab7bbe');
+    });
+
+    it('should digest a long message', function() {
+      var input = UTIL.hexToBytes(
+        '0100002903018d32e9c6dc423774c4c39a5a1b78f44cc2cab5f676d39' +
+        'f703d29bfa27dfeb870000002002f01000200004603014c2c1e835d39' +
+        'da71bc0857eb04c2b50fe90dbb2a8477fe7364598d6f0575999c20a6c' +
+        '7248c5174da6d03ac711888f762fc4ed54f7254b32273690de849c843' +
+        '073d002f000b0003d20003cf0003cc308203c8308202b0a0030201020' +
+        '20100300d06092a864886f70d0101050500308186310b300906035504' +
+        '0613025553311d301b060355040a13144469676974616c2042617a616' +
+        '1722c20496e632e31443042060355040b133b4269746d756e6b206c6f' +
+        '63616c686f73742d6f6e6c7920436572746966696361746573202d204' +
+        '17574686f72697a6174696f6e20766961204254503112301006035504' +
+        '0313096c6f63616c686f7374301e170d3130303231343137303931395' +
+        'a170d3230303231333137303931395a308186310b3009060355040613' +
+        '025553311d301b060355040a13144469676974616c2042617a6161722' +
+        'c20496e632e31443042060355040b133b4269746d756e6b206c6f6361' +
+        '6c686f73742d6f6e6c7920436572746966696361746573202d2041757' +
+        '4686f72697a6174696f6e207669612042545031123010060355040313' +
+        '096c6f63616c686f737430820122300d06092a864886f70d010101050' +
+        '00382010f003082010a0282010100dc436f17d6909d8a9d6186ea218e' +
+        'b5c86b848bae02219bd56a71203daf07e81bc19e7e98134136bcb0128' +
+        '81864bf03b3774652ad5eab85dba411a5114ffeac09babce75f313143' +
+        '45512cd87c91318b2e77433270a52185fc16f428c3ca412ad6e9484bc' +
+        '2fb87abb4e8fb71bf0f619e31a42340b35967f06c24a741a31c979c0b' +
+        'b8921a90a47025fbeb8adca576979e70a56830c61170c9647c18c0794' +
+        'd68c0df38f3aac5fc3b530e016ea5659715339f3f3c209cdee9dbe794' +
+        'b5af92530c5754c1d874b78974bfad994e0dfc582275e79feb522f6e4' +
+        'bcc2b2945baedfb0dbdaebb605f9483ff0bea29ecd5f4d6f2769965d1' +
+        'b3e04f8422716042680011ff676f0203010001a33f303d300c0603551' +
+        'd130101ff04023000300e0603551d0f0101ff0404030204f0301d0603' +
+        '551d250416301406082b0601050507030106082b06010505070302300' +
+        'd06092a864886f70d010105050003820101009c4562be3f2d8d8e3880' +
+        '85a697f2f106eaeff4992a43f198fe3dcf15c8229cf1043f061a38204' +
+        'f73d86f4fb6348048cc5279ed719873aa10e3773d92b629c2c3fcce04' +
+        '012c81ba3b4ec451e9644ec5191078402d845e05d02c7b4d974b45882' +
+        '76e5037aba7ef26a8bddeb21e10698c82f425e767dc401adf722fa73a' +
+        'b78cfa069bd69052d7ca6a75cc9225550e315d71c5f8764362ea4dbc6' +
+        'ecb837a8471043c5a7f826a71af145a053090bd4bccca6a2c552841cd' +
+        'b1908a8352f49283d2e641acdef667c7543af441a16f8294251e2ac37' +
+        '6fa507b53ae418dd038cd20cef1e7bfbf5ae03a7c88d93d843abaabbd' +
+        'c5f3431132f3e559d2dd414c3eda38a210b80e0000001000010201002' +
+        '6a220b7be857402819b78d81080d01a682599bbd00902985cc64edf8e' +
+        '520e4111eb0e1729a14ffa3498ca259cc9ad6fc78fa130d968ebdb78d' +
+        'c0b950c0aa44355f13ba678419185d7e4608fe178ca6b2cef33e41937' +
+        '78d1a70fe4d0dfcb110be4bbb4dbaa712177655728f914ab4c0f6c4ae' +
+        'f79a46b3d996c82b2ebe9ed1748eb5cace7dc44fb67e73f452a047f2e' +
+        'd199b3d50d5db960acf03244dc8efa4fc129faf8b65f9e52e62b55447' +
+        '22bd17d2358e817a777618a4265a3db277fc04851a82a91fe6cdcb812' +
+        '7f156e0b4a5d1f54ce2742eb70c895f5f8b85f5febe69bc73e891f928' +
+        '0826860a0c2ef94c7935e6215c3c4cd6b0e43e80cca396d913d36be');
+
+      var md = MD5.create();
+      md.update(input);
+      ASSERT.equal(md.digest().toHex(), 'd15a2da0e92c3da55dc573f885b6e653');
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/md5',
+    'forge/util'
+  ], function(MD5, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      MD5(),
+      UTIL()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/md5')(),
+    require('../../js/util')());
+}
+
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/mgf1.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/mgf1.js
new file mode 100644
index 0000000..6c54ff1
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/mgf1.js
@@ -0,0 +1,39 @@
+(function() {
+
+function Tests(ASSERT, MGF, MD, UTIL) {
+  describe('mgf1', function() {
+    it('should digest the empty string', function() {
+      var seed = UTIL.hexToBytes('032e45326fa859a72ec235acff929b15d1372e30b207255f0611b8f785d764374152e0ac009e509e7ba30cd2f1778e113b64e135cf4e2292c75efe5288edfda4');
+      var expect = UTIL.hexToBytes('5f8de105b5e96b2e490ddecbd147dd1def7e3b8e0e6a26eb7b956ccb8b3bdc1ca975bc57c3989e8fbad31a224655d800c46954840ff32052cdf0d640562bdfadfa263cfccf3c52b29f2af4a1869959bc77f854cf15bd7a25192985a842dbff8e13efee5b7e7e55bbe4d389647c686a9a9ab3fb889b2d7767d3837eea4e0a2f04');
+      var mgf = MGF.mgf1.create(MD.sha1.create());
+      var result = mgf.generate(seed, expect.length);
+      ASSERT.equal(result, expect);
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/mgf',
+    'forge/md',
+    'forge/util'
+  ], function(MGF, MD, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      MGF(),
+      MD(),
+      UTIL()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/mgf')(),
+    require('../../js/md')(),
+    require('../../js/util')());
+}
+
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/pbkdf2.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/pbkdf2.js
new file mode 100644
index 0000000..0b53e27
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/pbkdf2.js
@@ -0,0 +1,123 @@
+(function() {
+
+function Tests(ASSERT, PBKDF2, MD, UTIL) {
+  describe('pbkdf2', function() {
+    it('should derive a password with hmac-sha-1 c=1', function() {
+      var dkHex = UTIL.bytesToHex(PBKDF2('password', 'salt', 1, 20));
+      ASSERT.equal(dkHex, '0c60c80f961f0e71f3a9b524af6012062fe037a6');
+    });
+
+    it('should derive a password with hmac-sha-1 c=2', function() {
+      var dkHex = UTIL.bytesToHex(PBKDF2('password', 'salt', 2, 20));
+      ASSERT.equal(dkHex, 'ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957');
+    });
+
+    it('should derive a password with hmac-sha-1 c=5 keylen=8', function() {
+      var salt = UTIL.hexToBytes('1234567878563412');
+      var dkHex = UTIL.bytesToHex(PBKDF2('password', salt, 5, 8));
+      ASSERT.equal(dkHex, 'd1daa78615f287e6');
+    });
+
+    it('should derive a password with hmac-sha-1 c=4096', function() {
+      // Note: might be too slow on old browsers
+      var dkHex = UTIL.bytesToHex(PBKDF2('password', 'salt', 4096, 20));
+      ASSERT.equal(dkHex, '4b007901b765489abead49d926f721d065a429c1');
+    });
+
+    /*
+    it('should derive a password with hmac-sha-1 c=16777216', function() {
+      // Note: too slow
+      var dkHex = UTIL.bytesToHex(PBKDF2('password', 'salt', 16777216, 20));
+      ASSERT.equal(dkHex, 'eefe3d61cd4da4e4e9945b3d6ba2158c2634e984');
+    });*/
+
+    it('should derive a password with hmac-sha-256 c=1000', function() {
+      // Note: might be too slow on old browsers
+      var salt = '4bcda0d1c689fe465c5b8a817f0ddf3d';
+      var md = MD.sha256.create();
+      var dkHex = UTIL.bytesToHex(PBKDF2('password', salt, 1000, 48, md));
+      ASSERT.equal(dkHex, '9da8a5f4ae605f35e82e5beac5f362df15c4255d88f738d641466a4107f9970238e768e72af29ac89a1b16ff277b31d2');
+    });
+
+    it('should asynchronously derive a password with hmac-sha-1 c=1', function(done) {
+      PBKDF2('password', 'salt', 1, 20, function(err, dk) {
+        var dkHex = UTIL.bytesToHex(dk);
+        ASSERT.equal(dkHex, '0c60c80f961f0e71f3a9b524af6012062fe037a6');
+        done();
+      });
+    });
+
+    it('should asynchronously derive a password with hmac-sha-1 c=2', function(done) {
+      PBKDF2('password', 'salt', 2, 20, function(err, dk) {
+        var dkHex = UTIL.bytesToHex(dk);
+        ASSERT.equal(dkHex, 'ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957');
+        done();
+      });
+    });
+
+    it('should asynchronously derive a password with hmac-sha-1 c=5 keylen=8', function(done) {
+      var salt = UTIL.hexToBytes('1234567878563412');
+      PBKDF2('password', salt, 5, 8, function(err, dk) {
+        var dkHex = UTIL.bytesToHex(dk);
+        ASSERT.equal(dkHex, 'd1daa78615f287e6');
+        done();
+      });
+    });
+
+    it('should asynchronously derive a password with hmac-sha-1 c=4096', function(done) {
+      // Note: might be too slow on old browsers
+      PBKDF2('password', 'salt', 4096, 20, function(err, dk) {
+        var dkHex = UTIL.bytesToHex(dk);
+        ASSERT.equal(dkHex, '4b007901b765489abead49d926f721d065a429c1');
+        done();
+      });
+    });
+
+    /*
+    it('should asynchronously derive a password with hmac-sha-1 c=16777216', function(done) {
+      // Note: too slow
+      PBKDF2('password', 'salt', 16777216, 20, function(err, dk) {
+        var dkHex = UTIL.bytesToHex(dk);
+        ASSERT.equal(dkHex, 'eefe3d61cd4da4e4e9945b3d6ba2158c2634e984');
+        done();
+      });
+    });*/
+
+    it('should asynchronously derive a password with hmac-sha-256 c=1000', function(done) {
+      // Note: might be too slow on old browsers
+      var salt = '4bcda0d1c689fe465c5b8a817f0ddf3d';
+      var md = MD.sha256.create();
+      PBKDF2('password', salt, 1000, 48, md, function(err, dk) {
+        var dkHex = UTIL.bytesToHex(dk);
+        ASSERT.equal(dkHex, '9da8a5f4ae605f35e82e5beac5f362df15c4255d88f738d641466a4107f9970238e768e72af29ac89a1b16ff277b31d2');
+        done();
+      });
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/pbkdf2',
+    'forge/md',
+    'forge/util'
+  ], function(PBKDF2, MD, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      PBKDF2(),
+      MD(),
+      UTIL()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/pbkdf2')(),
+    require('../../js/md')(),
+    require('../../js/util')());
+}
+
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/pem.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/pem.js
new file mode 100644
index 0000000..6b405cb
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/pem.js
@@ -0,0 +1,104 @@
+(function() {
+
+function Tests(ASSERT, PEM) {
+  var _input = '-----BEGIN PRIVACY-ENHANCED MESSAGE-----\r\n' +
+    'Proc-Type: 4,ENCRYPTED\r\n' +
+    'Content-Domain: RFC822\r\n' +
+    'DEK-Info: DES-CBC,F8143EDE5960C597\r\n' +
+    'Originator-ID-Symmetric: linn@zendia.enet.dec.com,,\r\n' +
+    'Recipient-ID-Symmetric: linn@zendia.enet.dec.com,ptf-kmc,3\r\n' +
+    'Key-Info: DES-ECB,RSA-MD2,9FD3AAD2F2691B9A,\r\n' +
+    ' B70665BB9BF7CBCDA60195DB94F727D3\r\n' +
+    'Recipient-ID-Symmetric: pem-dev@tis.com,ptf-kmc,4\r\n' +
+    'Key-Info: DES-ECB,RSA-MD2,161A3F75DC82EF26,\r\n' +
+    ' E2EF532C65CBCFF79F83A2658132DB47\r\n' +
+    '\r\n' +
+    'LLrHB0eJzyhP+/fSStdW8okeEnv47jxe7SJ/iN72ohNcUk2jHEUSoH1nvNSIWL9M\r\n' +
+    '8tEjmF/zxB+bATMtPjCUWbz8Lr9wloXIkjHUlBLpvXR0UrUzYbkNpk0agV2IzUpk\r\n' +
+    'J6UiRRGcDSvzrsoK+oNvqu6z7Xs5Xfz5rDqUcMlK1Z6720dcBWGGsDLpTpSCnpot\r\n' +
+    'dXd/H5LMDWnonNvPCwQUHg==\r\n' +
+    '-----END PRIVACY-ENHANCED MESSAGE-----\r\n' +
+    '-----BEGIN PRIVACY-ENHANCED MESSAGE-----\r\n' +
+    'Proc-Type: 4,ENCRYPTED\r\n' +
+    'Content-Domain: RFC822\r\n' +
+    'DEK-Info: DES-CBC,BFF968AA74691AC1\r\n' +
+    'Originator-Certificate:\r\n' +
+    ' MIIBlTCCAScCAWUwDQYJKoZIhvcNAQECBQAwUTELMAkGA1UEBhMCVVMxIDAeBgNV\r\n' +
+    ' BAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMQ8wDQYDVQQLEwZCZXRhIDExDzAN\r\n' +
+    ' BgNVBAsTBk5PVEFSWTAeFw05MTA5MDQxODM4MTdaFw05MzA5MDMxODM4MTZaMEUx\r\n' +
+    ' CzAJBgNVBAYTAlVTMSAwHgYDVQQKExdSU0EgRGF0YSBTZWN1cml0eSwgSW5jLjEU\r\n' +
+    ' MBIGA1UEAxMLVGVzdCBVc2VyIDEwWTAKBgRVCAEBAgICAANLADBIAkEAwHZHl7i+\r\n' +
+    ' yJcqDtjJCowzTdBJrdAiLAnSC+CnnjOJELyuQiBgkGrgIh3j8/x0fM+YrsyF1u3F\r\n' +
+    ' LZPVtzlndhYFJQIDAQABMA0GCSqGSIb3DQEBAgUAA1kACKr0PqphJYw1j+YPtcIq\r\n' +
+    ' iWlFPuN5jJ79Khfg7ASFxskYkEMjRNZV/HZDZQEhtVaU7Jxfzs2wfX5byMp2X3U/\r\n' +
+    ' 5XUXGx7qusDgHQGs7Jk9W8CW1fuSWUgN4w==\r\n' +
+    'Key-Info: RSA,\r\n' +
+    ' I3rRIGXUGWAF8js5wCzRTkdhO34PTHdRZY9Tuvm03M+NM7fx6qc5udixps2Lng0+\r\n' +
+    ' wGrtiUm/ovtKdinz6ZQ/aQ==\r\n' +
+    'Issuer-Certificate:\r\n' +
+    ' MIIB3DCCAUgCAQowDQYJKoZIhvcNAQECBQAwTzELMAkGA1UEBhMCVVMxIDAeBgNV\r\n' +
+    ' BAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMQ8wDQYDVQQLEwZCZXRhIDExDTAL\r\n' +
+    ' BgNVBAsTBFRMQ0EwHhcNOTEwOTAxMDgwMDAwWhcNOTIwOTAxMDc1OTU5WjBRMQsw\r\n' +
+    ' CQYDVQQGEwJVUzEgMB4GA1UEChMXUlNBIERhdGEgU2VjdXJpdHksIEluYy4xDzAN\r\n' +
+    ' BgNVBAsTBkJldGEgMTEPMA0GA1UECxMGTk9UQVJZMHAwCgYEVQgBAQICArwDYgAw\r\n' +
+    ' XwJYCsnp6lQCxYykNlODwutF/jMJ3kL+3PjYyHOwk+/9rLg6X65B/LD4bJHtO5XW\r\n' +
+    ' cqAz/7R7XhjYCm0PcqbdzoACZtIlETrKrcJiDYoP+DkZ8k1gCk7hQHpbIwIDAQAB\r\n' +
+    ' MA0GCSqGSIb3DQEBAgUAA38AAICPv4f9Gx/tY4+p+4DB7MV+tKZnvBoy8zgoMGOx\r\n' +
+    ' dD2jMZ/3HsyWKWgSF0eH/AJB3qr9zosG47pyMnTf3aSy2nBO7CMxpUWRBcXUpE+x\r\n' +
+    ' EREZd9++32ofGBIXaialnOgVUn0OzSYgugiQ077nJLDUj0hQehCizEs5wUJ35a5h\r\n' +
+    'MIC-Info: RSA-MD5,RSA,\r\n' +
+    ' UdFJR8u/TIGhfH65ieewe2lOW4tooa3vZCvVNGBZirf/7nrgzWDABz8w9NsXSexv\r\n' +
+    ' AjRFbHoNPzBuxwmOAFeA0HJszL4yBvhG\r\n' +
+    'Recipient-ID-Asymmetric:\r\n' +
+    ' MFExCzAJBgNVBAYTAlVTMSAwHgYDVQQKExdSU0EgRGF0YSBTZWN1cml0eSwgSW5j\r\n' +
+    ' LjEPMA0GA1UECxMGQmV0YSAxMQ8wDQYDVQQLEwZOT1RBUlk=,66\r\n' +
+    'Key-Info: RSA,\r\n' +
+    ' O6BS1ww9CTyHPtS3bMLD+L0hejdvX6Qv1HK2ds2sQPEaXhX8EhvVphHYTjwekdWv\r\n' +
+    ' 7x0Z3Jx2vTAhOYHMcqqCjA==\r\n' +
+    '\r\n' +
+    'qeWlj/YJ2Uf5ng9yznPbtD0mYloSwIuV9FRYx+gzY+8iXd/NQrXHfi6/MhPfPF3d\r\n' +
+    'jIqCJAxvld2xgqQimUzoS1a4r7kQQ5c/Iua4LqKeq3ciFzEv/MbZhA==\r\n' +
+    '-----END PRIVACY-ENHANCED MESSAGE-----\r\n' +
+    '-----BEGIN RSA PRIVATE KEY-----\r\n' +
+    'MIIBPAIBAAJBALjXU+IdHkSkdBscgXf+EBoa55ruAIsU50uDFjFBkp+rWFt5AOGF\r\n' +
+    '9xL1/HNIby5M64BCw021nJTZKEOmXKdmzYsCAwEAAQJBAApyYRNOgf9vLAC8Q7T8\r\n' +
+    'bvyKuLxQ50b1D319EywFgLv1Yn0s/F9F+Rew6c04Q0pIqmuOGUM7z94ul/y5OlNJ\r\n' +
+    '2cECIQDveEW1ib2+787l7Y0tMeDzf/HQl4MAWdcxXWOeUFK+7QIhAMWZsukutEn9\r\n' +
+    '9/yqFMt8bL/dclfNn1IAgUL4+dMJ7zdXAiEAhaxGhVKxN28XuCOFhe/s2R/XdQ/O\r\n' +
+    'UZjU1bqCzDGcLvUCIGYmxu71Tg7SVFkyM/3eHPozKOFrU2m5CRnuTHhlMl2RAiEA\r\n' +
+    '0vhM5TEmmNWz0anPVabqDj9TA0z5MsDJQcn5NmO9xnw=\r\n' +
+    '-----END RSA PRIVATE KEY-----\r\n';
+
+  describe('pem', function() {
+    it('should decode and re-encode PEM messages', function() {
+      var msgs = PEM.decode(_input);
+
+      var output = '';
+      for(var i = 0; i < msgs.length; ++i) {
+        output += PEM.encode(msgs[i]);
+      }
+
+      ASSERT.equal(output, _input);
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/pem'
+  ], function(PEM) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      PEM()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/pem')());
+}
+
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/pkcs1.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/pkcs1.js
new file mode 100644
index 0000000..889eb6d
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/pkcs1.js
@@ -0,0 +1,1105 @@
+(function() {
+
+function Tests(ASSERT, PKI, PKCS1, MD, JSBN, UTIL) {
+  var BigInteger = JSBN.BigInteger;
+
+  // RSA's test vectors for Forge's RSA-OAEP implementation:
+  // http://www.rsa.com/rsalabs/node.asp?id=2125
+  describe('pkcs1', function() {
+    it('should detect invalid RSAES-OAEP padding', function() {
+      var keys = makeKey();
+
+      // provide the seed to test the same input each time
+      var seed = UTIL.decode64('JRTfRpV1WmeyiOr0kFw27sZv0v0=');
+
+      // test decrypting corrupted data: flip every bit (skip first byte to
+      // avoid triggering other invalid encryption error) in the message this
+      // tests the padding error handling
+      var encoded = PKCS1.encode_rsa_oaep(
+        keys.publicKey, 'datadatadatadata', {seed: seed});
+      var encrypted = keys.publicKey.encrypt(encoded, null);
+      var bitLength = encrypted.length * 8;
+      // FIXME: test it too slow to run all the time -- temporary
+      // change only does partial checks, need a longer term fix
+      bitLength /= 8;
+      for(var bit = 8; bit < bitLength; ++bit) {
+        var byteIndex = bit / 8;
+        var bitInByte = bit % 8;
+
+        var out = encrypted.substring(0, byteIndex);
+        var mask = 0x1 << bitInByte;
+        out += String.fromCharCode(encrypted.charCodeAt(byteIndex) ^ mask);
+        out += encrypted.substring(byteIndex + 1);
+
+        try {
+          var decrypted = keys.privateKey.decrypt(out, null);
+          PKCS1.decode_rsa_oaep(keys.privateKey, decrypted);
+          throw {
+            message: 'Expected an exception.'
+          };
+        } catch(e) {
+          ASSERT.equal(e.message, 'Invalid RSAES-OAEP padding.');
+        }
+      }
+    });
+
+    it('should detect leading zero bytes', function() {
+      var keys = makeKey();
+      var message = UTIL.fillString('\x00', 80);
+      var encoded = PKCS1.encode_rsa_oaep(keys.publicKey, message);
+      var ciphertext = keys.publicKey.encrypt(encoded, null);
+      var decrypted = keys.privateKey.decrypt(ciphertext, null);
+      var decoded = PKCS1.decode_rsa_oaep(keys.privateKey, decrypted);
+      ASSERT.equal(message, decoded);
+    });
+
+    testOAEP();
+    testOAEPSHA256();
+
+    function testOAEP() {
+      var modulus, exponent, d, p, q, dP, dQ, qInv, pubkey, privateKey;
+      var examples;
+
+      // Example 1: A 1024-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'qLOyhK+OtQs4cDSoYPFGxJGfMYdjzWxVmMiuSBGh4KvEx+CwgtaTpef87Wdc9GaFEncsDLxkp0LGxjD1M8jMcvYq6DPEC/JYQumEu3i9v5fAEH1VvbZi9cTg+rmEXLUUjvc5LdOq/5OuHmtme7PUJHYW1PW6ENTP0ibeiNOfFvs=';
+      exponent = 'AQAB';
+      d = 'UzOc/befyEZqZVxzFqyoXFX9j23YmP2vEZUX709S6P2OJY35P+4YD6DkqylpPNg7FSpVPUrE0YEri5+lrw5/Vf5zBN9BVwkm8zEfFcTWWnMsSDEW7j09LQrzVJrZv3y/t4rYhPhNW+sEck3HNpsx3vN9DPU56c/N095lNynq1dE=';
+      p = '0yc35yZ//hNBstXA0VCoG1hvsxMr7S+NUmKGSpy58wrzi+RIWY1BOhcu+4AsIazxwRxSDC8mpHHcrSEurHyjnQ==';
+      q = 'zIhT0dVNpjD6wAT0cfKBx7iYLYIkpJDtvrM9Pj1cyTxHZXA9HdeRZC8fEWoN2FK+JBmyr3K/6aAw6GCwKItddw==';
+      dP = 'DhK/FxjpzvVZm6HDiC/oBGqQh07vzo8szCDk8nQfsKM6OEiuyckwX77L0tdoGZZ9RnGsxkMeQDeWjbN4eOaVwQ==';
+      dQ = 'lSl7D5Wi+mfQBwfWCd/U/AXIna/C721upVvsdx6jM3NNklHnkILs2oZu/vE8RZ4aYxOGt+NUyJn18RLKhdcVgw==';
+      qInv = 'T0VsUCSTvcDtKrdWo6btTWc1Kml9QhbpMhKxJ6Y9VBHOb6mNXb79cyY+NygUJ0OBgWbtfdY2h90qjKHS9PvY4Q==';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 1.1',
+        message: 'ZigZThIHPbA7qUzanvlTI5fVDbp5uYcASv7+NA==',
+        seed: 'GLd26iEGnWl3ajPpa61I4d2gpe8=',
+        encrypted: 'NU/me0oSbV01/jbHd3kaP3uhPe9ITi05CK/3IvrUaPshaW3pXQvpEcLTF0+K/MIBA197bY5pQC3lRRYYwhpTX6nXv8W43Z/CQ/jPkn2zEyLW6IHqqRqZYXDmV6BaJmQm2YyIAD+Ed8EicJSg2foejEAkMJzh7My1IQA11HrHLoo='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 1.2',
+        message: 'dQxAR/VH6OQUEYVlIymKybriRe+vE5f75W+d1Q==',
+        seed: 'DMdCzkqbfzL5UbyyUe/ZJf5P418=',
+        encrypted: 'ZA2xrMWOBWj+VAfl+bcB3/jDyR5xbFNvx/zsbLW3HBFlmI1KJ54Vd9cw/Hopky4/AMgVFSNtjY4xAXp6Cd9DUtkEzet5qlg63MMeppikwFKD2rqQib5UkfZ8Gk7kjcdLu+ZkOu+EZnm0yzlaNS1e0RWRLfaW/+BwKTKUbXFJK0Q='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 1.3',
+        message: '2Urggy5kRc5CMxywbVMagrHbS6rTD3RtyRbfJNTjwkUf/1mmQj6w4dAtT+ZGz2md/YGMbpewUQ==',
+        seed: 'JRTfRpV1WmeyiOr0kFw27sZv0v0=',
+        encrypted: 'Qjc27QNfYCavJ2w1wLN0GzZeX3bKCRtOjCni8L7+5gNZWqgyLWAtLmJeleuBsvHJck6CLsp224YYzwnFNDUDpDYINbWQO8Y344efsF4O8yaF1a7FBnzXzJb+SyZwturDBmsfz1aGtoWJqvt9YpsC2PhiXKODNiTUgA+wgbHPlOs='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 1.4',
+        message: 'UuZQ2Y5/KgSLT4aFIVO5fgHdMW80ahn2eoU=',
+        seed: 'xENaPhoYpotoIENikKN877hds/s=',
+        encrypted: 'RerUylUeZiyYAPGsqCg7BSXmq64wvktKunYvpA/T044iq+/Gl5T267vAXduxEhYkfS9BL9D7qHxuOs2IiBNkb9DkjnhSBPnD9z1tgjlWJyLd3Ydx/sSLg6Me5vWSxM/UvIgXTzsToRKq47n3uA4PxvclW6iA3H2AIeIq1qhfB1U='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 1.5',
+        message: 'jaif2eX5dKKf7/tGK0kYD2z56AI=',
+        seed: 'sxjELfO+D4P+qCP1p7R+1eQlo7U=',
+        encrypted: 'NvbjTZSo002qy6M6ITnQCthak0WoYFHnMHFiAFa5IOIZAFhVohOg8jiXzc1zG0UlfHd/6QggK+/dC1g4axJE6gz1OaBdXRAynaROEwMP12Dc1kTP7yCU0ZENP0M+HHxt0YvB8t9/ZD1mL7ndN+rZBZGQ9PpmyjnoacTrRJy9xDk='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 1.6',
+        message: 'JlIQUIRCcQ==',
+        seed: '5OwJgsIzbzpnf2o1YXTrDOiHq8I=',
+        encrypted: 'Qs7iYXsezqTbP0gpOG+9Ydr78DjhgNg3yWNm3yTAl7SrD6xr31kNghyfEGQuaBrQW414s3jA9Gzi+tY/dOCtPfBrB11+tfVjb41AO5BZynYbXGK7UqpFAC6nC6rOCN7SQ7nYy9YqaK3iZYMrVlZOQ6b6Qu0ZmgmXaXQt8VOeglU='
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha1', examples);
+
+      // Example 2: A 1025-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'AZR8f86QQl9HJ55whR8l1eYjFv6KHfGTcePmKOJgVD5JAe9ggfaMC4FBGQ0q6Nq6fRJQ7G22NulE7Dcih3x8HQpn8UsWlMXwN5RRpD5Joy3eg2cLc9qRocmbwjtDamAFXGEPC6+ZwaB5VluVo/FSZjLR1Npg8g7aJeZTxPACdm9F';
+      exponent = 'AQAB';
+      d = 'CCPyD6212okIip0AiT4h+kobEfvJPGSjvguq6pf7O5PD/3E3BMGcljwdEHqumQVHOfeeAuGG3ob4em3e/qbYzNHTyBpHv6clW+IGAaSksvCKFnteJ51xWxtFW91+qyRZQdl2i5rO+zzNpZUto87nJSW0UBZjqO4VyemS2SRi/jk=';
+      p = 'AVnb3gSjPvBvtgi4CxkPTT4ivME6yOSggQM6v6QW7bCzOKoItXMJ6lpSQOfcblQ3jGlBTDHZfdsfQG2zdpzEGkM=';
+      q = 'AStlLzBAOzi0CZX9b/QaGsyK2nA3Mja3IC05su4wz7RtsJUR9vMHzGHMIWBsGKdbimL4It8DG6DfDa/VUG9Wi9c=';
+      dP = 'Q271CN5zZRnC2kxYDZjILLdFKj+1763Ducd4mhvGWE95Wt270yQ5x0aGVS7LbCwwek069/U57sFXJIx7MfGiVQ==';
+      dQ = 'ASsVqJ89+ys5Bz5z8CvdDBp7N53UNfBc3eLv+eRilIt87GLukFDV4IFuB4WoVrSRCNy3XzaDh00cpjKaGQEwZv8=';
+      qInv = 'AnDbF9WRSwGNdhGLJDiac1Dsg2sAY6IXISNv2O222JtR5+64e2EbcTLLfqc1bCMVHB53UVB8eG2e4XlBcKjI6A==';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 2.1',
+        message: 'j/AMqmBccCgwY02abD1CxlK1jPHZL+xXC+7n',
+        seed: 'jEB7XsKJnlCZxT6M55O/lOcbF4I=',
+        encrypted: 'AYGviSK5/LTXnZLr4ZgVmS/AwUOdi81JE5ig9K06Mppb2ThVYNtTJoPIt9oE5LEq7Wqs30ccNMnNqJGt3MLfNFZlOqY4Lprlm1RFUlfrCZ1WK74QRT8rbRPFnALhDx+Ku12g0FcJMtrPLQkB23KdD+/MBU5wlo6lQMgbBLyu/nIO'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 2.2',
+        message: 'LQ==',
+        seed: 'tgDPPC5QbX8Wd4yRDTqLAD7uYdU=',
+        encrypted: 'AYdZ/x32OyeSQQViMUQWqK6vKsY0tG+UCrgtZNvxZe7jMBHadJ1Lq24vzRgSnJ5JJ32EUxErQpoiKoRxsHCZOZjnWIYcTT9tdJ2RxCkNMyx6SrP36jX/OgfUl8lV/w/8lQBrYsbSloENm/qwJBlseTQBLC35eO8pmrojmUDLoQJF'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 2.3',
+        message: 'dPyIxRvJD3evnV6aSnATPUtOCzTaPDfH744=',
+        seed: 'pzdoruqpH52MHtb50rY0Z/B8yuM=',
+        encrypted: 'AYgCurBMYDJegcSWIxHyvnwq3OkwQaAHGciPlXV18sefG3vIztEVxwazEcCKLZhso7apM2sUfCnG8ilAnd7GUb0f3VoLf2EMmTf9tKOnYjZLizIGtOpIX9CY0I9j1KqLsml9Ant1DDLX906vUYDS6bZrF8svpVUjvCgNoQ0UviBT'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 2.4',
+        message: 'p+sqUDaTHSfU6JEybZlpL/rdqb9+/T405iLErcCF9yHf6IUHLHiiA7FRc5vlQPqMFToQ8Ao=',
+        seed: 'mns7DnCL2W+BkOyrT7mys4BagVY=',
+        encrypted: 'AKRXjLwXYximOPun0B3xV0avRNT2zZbX58SVy/QlsJxknTK/iG2kj7r5iaIRcYfK+x+1gDF2kOPM1EaSC3r4KzHbWATYfQFRSsv6kVbngvhn9r7ZRJ4OmiwJvOzGqgh2NpZeNLPsdm8v4uQwGKL93rFAYWoOnYLlMxAk7gZS/HZB'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 2.5',
+        message: 'LvKwZvhUwz873LtZlKQ15z1sbA==',
+        seed: '6zzrvErcFrtI6IyK7A40r39Cf9M=',
+        encrypted: 'AOvF9f2nfP2tPINkGpAl531y2Kb7M6gQ9ZUPjXTHPo2THoY02GqxJGJWrge2AFtxt/L7mDUSGDMc5puP+9ydoIu8nHBPh23rnfn8LsBlyth/kJCweswXqn+ZeyespIgG6Jf3cdlRQf5FJtilMBtnhifvq3B/1A++vW55KiVhPnrs'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 2.6',
+        message: 'in+zRMi2yyzy7x9kP5oyGPbhm7qJwA==',
+        seed: 'TEXPTVfJjj1tIJWtxRxInrUN/4Q=',
+        encrypted: 'AQg57CDCe5BS5Vvvubd+b8JukHXXpUN4xkar31HkRb1XFd6BeJ9W8YA9kXB2Sp6Ty3h5hpQCPuc5POBLxdj4xaUsFx1Dg346ymL2CesKpf+wlg7wQZjddU9X9/vmq/dlzxGLTKRDsjtaqyZvlSMmrEWBEAZEMl+LchrNXQT/FO86'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha1', examples);
+
+      // Example 3: A 1026-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'ArWP7AOahgcApNe2Ri+T5s3UkRYd3XT06BC0DjwWUgBqXCd7J3TBEwWky6taeO+lfheobfej+jb8Sx0iSfIux8LdakYyMqzOqQbWbr6AtXBLEHKdpvgzI0q7Xv3UopLL+tM7TTP6ehS4w5e1bjrNISA0KLd836M6bacGs9iw/EPp';
+      exponent = 'AQAB';
+      d = 'FbSKW1aDqUZw4jtXGPgU+g4T+FA49QcRGCy6YVEFgfPSLH4jLvk34i5VHWi4bi+MsarYvi5Ij13379J54/Vo1Orzb4DPcUGs5g/MkRP7bEqEH9ULvHxRL/y+/yFIeqgR6zyoxiAFNGqG3oa/odipSP0/NIwi6q3zM8PObOEyCP0=';
+      p = 'Ab8B0hbXNZXPAnDCvreNQKDYRH0x2pGamD9+6ngbd9hf43Gz6Tc+e2khfTFQoC2JWN5/rZ1VUWCVi0RUEn4Ofq8=';
+      q = 'AY0zmWWBZts4KYFteylUFnWenJGYf1stiuzWOwS0i9ey/PIpu3+KbciLoT3S45rVW20aBhYHCPlwC+gLj9N0TOc=';
+      dP = 'BsCiSdIKby7nXIi0lNU/aq6ZqkJ8iMKLFjp2lEXl85DPQMJ0/W6mMppc58fOA6IVg5buKnhFeG4J4ohalyjk5Q==';
+      dQ = '0dJ8Kf7dkthsNI7dDMv6wU90bgUc4dGBHfNdYfLuHJfUvygEgC9kJxh7qOkKivRCQ7QHmwNEXmAuKfpRk+ZP6Q==';
+      qInv = 'jLL3Vr2JQbHTt3DlrTHuNzsorNpp/5tvQP5Xi58a+4WDb5Yn03rP9zwneeY0uyYBHCyPfzNhriqepl7WieNjmg==';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 3.1',
+        message: 'CHggtWno+o0=',
+        seed: 'jO1rGWKQgFeQ6QkHQBXmogsMSJQ=',
+        encrypted: 'AmoEhdlq69lrQ4IIUJm5Yuaivew9kMjbYl4UNy3oXi1be6q2XI+vkbtVBPtJWvzlyYiz9qUuIOHWy9NWbFzR8rgxi7VCzA6iXEqrmTKvogdg6t3seEOWoH6g7yTU5vTTflBSp6MeFGqkgKERu+kmQBMH4A9BADOEK22C/lzk366A'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 3.2',
+        message: 'RlOsrxcZYLAfUqe+Y6OrIdw2jsQ7UNguw3geBA==',
+        seed: 'tCkdZWdVCEjMFWlnyAm6q2ylB/A=',
+        encrypted: 'Ak24nHgCmJvgeDhHhjCElBvyCddhmH44+Xy19vG8iNpypQtz668RyHnE+V3ze4ULj2XXYi4lsbiJ6A/oC6yiBp1uDh2CmVP8RZBp3pjql5i0UeVX6Zq/j+PZzPkJbrvz5SVdO04cbS7K3wZ6NZ7qhkBazUfV4WVRfMr9R9bb7kv1'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 3.3',
+        message: '2UzQ4I+kBO2J',
+        seed: 'zoko9gWVWCVACLrdl5T63NL9H2U=',
+        encrypted: 'Ajm85oEDJEFSiHfW0ci7KKo7yX8d9YRWNhiZV5doOETKhmZHMvS+16CqsIOqq/tyOPWC4wlYwgJOROVwQ7l5UP1UPal3yQzd5TN9YYRC+Z5g13g6tZzm3Z1pxHrR6WK+wi0FiVz/jT9k7VJh2SsmeFEDk0hJkLo/fwaBiub/zoo6'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 3.4',
+        message: 'bMZBtrYeb5Y5dNrSOpATKE7x',
+        seed: 'bil59S1oFKV9g7CQBUiI8RmluaM=',
+        encrypted: 'AplMYq/Xb0mLof0s9kKFf8qB9Dc8sI8cuu5vAlw7UStCw+h3kRNHZkgDnb4Ek/kkYpL6wolQYA58DzLt+cgbnexFw73gzI2IR1kBaZB7fcWZHOspuwcU1hPZbfDxLsXY01B8jueueN2D8hb6Yd4QA2OspIp+kUrp9C3fvpQ7Cdmg'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 3.5',
+        message: '31FRgyth9PJYkftBcvMo0u3fg3H/z9vpl5OSlfMOymkYAXz9oRU796avh1kyIw==',
+        seed: 'LXYL/jjFneNM3IuMeKOOZihKLSc=',
+        encrypted: 'AWIEL/aWlZKmFnAxgRojmDTOY4q/VP7IuZR4Eir+LuZ/jFsYsDOYBb/bxaTmcgs3xZz7qUJGTFl/9TKhGYIVRf0uWbEU5h2vcYIFKfUCnPUklUMnw07F5vW6fvzE3pQ6uK1O14exRUMp9w23mKOo9NkvgnTispSK3mJ86O4z5Dxg'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 3.6',
+        message: 'PDutiTxUSm1SCrAiMZGIyNUEt6eIuFCQO4WXLqoYVS4RNKetYJiCYlT/erZys9jrMVj6xtTLrvE=',
+        seed: '8XR3nF/Tz+AHuty3o2ybVb/Pvw4=',
+        encrypted: 'ABEgUeddBklDvER4B15DSC/VnO4Ged5ok+7DqUPapJC5aRyT38BGS2YjufPb0+cAgyZPA0s3T3QWThoAdjcl5XR0S6C524NDTzHflvbiom9tjro0i9RobCI4rAfDeqw3hdHH7qL4Gf2RSReY7Y6c715Dt4Gw4CduN8Q/+UktAFcw'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha1', examples);
+
+      // Example 4: A 1027-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'BRJAtswABPpI0BNGccB4x8jew7Pi8lvCVkRnM52ziFPQa4XupbLeNTv/QqwuRryX+uaslhjalTelyPVTweNXYlmR1hCNzXiF+zolQT9T78rZSMs1zZua6cHGdibRE9V93kxb6na7W7felsANBzculoWm11z50jn6FI1wkxtfP7A5';
+      exponent = 'AQAB';
+      d = 'BBH/yjt8penpvn/jioUQXjU4ltsFxXlq7NKnJRYes2UchimpuGK5BNewx7N/jLWhwrVAAQGKAKHrLK/k7k6UksNIvCvtq0ueu/Bk6O/zIrkAn47sZTkF9A34ijzcSdRWf3VifUGspiQSm0agt8aY5eZfK3uhAsdJoQE1tlQNBAE=';
+      p = 'AnRYwZ7BY2kZ5zbJryXWCaUbj1YdGca/aUPdHuGriko/IyEAvUC4jezGuiNVSLbveSoRyd6CPQp5IscJW266VwE=';
+      q = 'AhDumzOrYXFuJ9JRvUZfSzWhojLi2gCQHClL8iNQzkkNCZ9kK1N1YS22O6HyA4ZJK/BNNLPCK865CdE0QbU7UTk=';
+      dP = 'OfoCi4JuiMESG3UKiyQvqaNcW2a9/R+mN9PMSKhKT0V6GU53J+Sfe8xuWlpBJlf8RwxzIuvDdBbvRYwweowJAQ==';
+      dQ = 'AV2ZqEGVlDl5+p4b4sPBtp9DL0b9A+R9W++7v9ax0Tcdg++zMKPgIJQrL+0RXl0CviT9kskBnRzs1t1M8eVMyJk=';
+      qInv = 'AfC3AVFws/XkIiO6MDAcQabYfLtw4wy308Z9JUc9sfbL8D4/kSbj6XloJ5qGWywrQmUkz8UqaD0x7TDrmEvkEro=';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 4.1',
+        message: 'SoZglTTuQ0psvKP36WLnbUVeMmTBn2Bfbl/2E3xlxW1/s0TNUryTN089FmyfDG+cUGutGTMJctI=',
+        seed: 'HKwZzpk971X5ggP2hSiWyVzMofM=',
+        encrypted: 'BMzhlhSEXglBUqP+GOVOMzDETl77xkrhaIbLGGkBTMV4Gx+PngRThNARKhNcoNEunIio5AY0Ft6q44RPYNbpb+FVFF9FJbmjRDHKN2YYD3DhWl5djosaUW/4cGCfE/iWk1ztGIJ5pY7RPQcRQnfXXGVoYH4KsJL9gDoiPkqO4LGo'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 4.2',
+        message: 'sK3E8/4R2lnOmSdz2QWZQ8AwRkl+6dn5oG3xFm20bZj1jSfsB0wC7ubL4kSci5/FCAxcP0QzCSUS7EaqeTdDyA==',
+        seed: '9UXViXWF49txqgy42nbFHQMq6WM=',
+        encrypted: 'AJe2mMYWVkWzA0hvv1oqRHnA7oWIm1QabwuFjWtll7E7hU60+DmvAzmagNeb2mV4yEH5DWRXFbKA03FDmS3RhsgLlJt3XK6XNw5OyXRDE2xtpITpcP/bEyOiCEeCHTsYOB3hO7SarqZlMMSkuCcfPq4XLNNm4H5mNvEBnSoortFe'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 4.3',
+        message: 'v21C5wFwex0CBrDItFoccmQf8SiJIZqCveqWW155qWsNAWPtnVeOya2iDy+88eo8QInYNBm6gbDGDzYG2pk=',
+        seed: 'rZl/7vcw1up75g0NxS5y6sv90nU=',
+        encrypted: 'AwH5NenEery0isu+CYldn1lxrxSDnaT/lUF+5FPR/XcxkHK7cpfhtV11Yc2dG7JMGpo3xhmGQwgkKASHnYbr0AHc5Rg5deFQaYm3DlqDQ0FU1cv9aiR4fmDrDGWNKsGTMC0RksbmItShKtS1OSO8okbfMcY5XjdwLGp4rggfudBl'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 4.4',
+        message: '+y7xEvXnZuuUAZKXk0eU974vb8HFjg==',
+        seed: 'E2RU31cw9zyAen5A2MGjEqxbndM=',
+        encrypted: 'AtEQrTCvtye+tpHdDPF9CvGh5/oMwEDsGkuiakLFnQp5ai4iyPNXzMmLZRms62gulF5iy3NGFKUpQHzUUr7j5E/s6EI8wZ5VVIuLmUuEnH7N5JM+dgN+HQzkQnWwhxDGjkMBMLkpcw7XfgmwFWQsVZPwTk/7lBB5gQKo6W/9/hHk'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 4.5',
+        message: 'KMzUR7uehRZtq7nlt9GtrcS5058gTpbV5EDOmtkovBwihA==',
+        seed: 'vKgFf4JLLqJX8oYUB+72PTMghoE=',
+        encrypted: 'ANu4p0OdkO/ZGaN3xU+uj+EexYw7hYNi4jrRuKRDEHmQZrmTR6pSVpHSrcWNmwbjTyiMFwOQxfDhHAqjZFlZ8Y7nno8r6NesXCPQYfGN10uMXypY/LXrDFT5nwGoMkdWgpJTZYM0CUjXqMl8Ss0emNHincMg6XomBTKoqnp1ih7C'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 4.6',
+        message: '8iJCdR7GsQ==',
+        seed: 'Ln4eF/ZHtd3QM+FUcvkPaBLzrE4=',
+        encrypted: 'AKX/pHaMi77K7i23fo8u7JlZWTNUVSCDXlun25ST0+F83e/mpfVnYkRxkI204tg6D77mBgj8hASVA7IjSgfcg7J7IoR62JIP9C9nTvebdigLACM9K1G4yycDqdQr+8glDJbsMsBR5X8bS6Uo24nDfkxU4n5uZKxpY1roh9lUFhmp'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha1', examples);
+
+      // Example 5: A 1028-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'Cq3z+cEl5diR8xrESOmT3v5YD4ArRfnX8iulAh6cR1drWh5oAxup205tq+TZah1vPSZyaM/0CABfEY78rbmYiNHCNEZxZrKiuEmgWoicBgrA2gxfrotV8wm6YucDdC+gMm8tELARAhSJ/0l3cBkNiV/Tn1IpPDnv1zppi9q58Q7Z';
+      exponent = 'AQAB';
+      d = 'AlbrTLpwZ/LSvlQNzf9FgqNrfTHRyQmbshS3mEhGaiaPgPWKSawEwONkiTSgIGwEU3wZsjZkOmCCcyFE33X6IXWI95RoK+iRaCdtxybFwMvbhNMbvybQpDr0lXF/fVKKz+40FWH2/zyuBcV4+EcNloL5wNBy+fYGi1bViA9oK+LF';
+      p = 'A7DTli9tF1Scv8oRKUNI3PDn45+MK8aCTyFktgbWh4YNrh5jI5PP7fUTIoIpBp4vYOSs1+YzpDYGP4I4X0iZNwc=';
+      q = 'AuTDLi9Rcmm3ByMJ8AwOMTZffOKLI2uCkS3yOavzlXLPDtYEsCmC5TVkxS1qBTl95cBSov3cFB73GJg2NGrrMx8=';
+      dP = 'AehLEZ0lFh+mewAlalvZtkXSsjLssFsBUYACmohiKtw/CbOurN5hYat83iLCrSbneX31TgcsvTsmc4ALPkM429U=';
+      dQ = '65CqGkATW0zqBxl87ciBm+Hny/8lR2YhFvRlpKn0h6sS87pP7xOCImWmUpfZi3ve2TcuP/6Bo4s+lgD+0FV1Tw==';
+      qInv = 'AS9/gTj5QEBi64WkKSRSCzj1u4hqAZb0i7jc6mD9kswCfxjngVijSlxdX4YKD2wEBxp9ATEsBlBi8etIt50cg8s=';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 5.1',
+        message: 'r3GpAeOmHTEy8Pwf20dPnqZXklf/wk0WQXAUWz296A==',
+        seed: 'RMkuKD93uUmcYD2WNmDIfS+TlGE=',
+        encrypted: 'A2BGpKR9ntO6mokTnBBQOOt0krBaXWi/1TrM/0WX96aGUbR7SkYn2Sfkhe7XtFZkIOi0CYeeXWBuriUdIqXfeZ95IL/BF7mSVypTsSYxRrzqAzhcxehTyaEByMPhvaMaUZgHSWxsteXvtAiCOjUrj6BmH7Zk763Vk965n/9e0ADl'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 5.2',
+        message: 'o7hEoII5qKxBYFrxemz9pNNQE2WFkDpBenkmh2BRmktKwzA+xz8Ph8+zI5k=',
+        seed: 'yyj1hgZZ/O7knD7q/OYlpwgDvTI=',
+        encrypted: 'A9brZU7c5hW8WfRVJl7U5aGCI8u5vk5AabRzgE1d6W9U3KqmA9BJxdlKoUcN/NIlQGa3x7Yf8fb2dw4yFcUTmf1ONOxQgrxI8ImECtBDVK5m3A8b0Y5GGjPMEli0Q6KDem3yZ1mqIwIzSYb4c4DJzJ1Tvp+ZYF0smpfaewkVpKet'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 5.3',
+        message: 'MIsOy9LHbLd/xvcMXt0jP9LyCSnWKfAmlTu2Ko9KOjFL3hld6FtfgW2iqrB00my2rN3zI647nGeKw88S+93n',
+        seed: 'IoX0DXcEgvmp76LHLLOsVXFtwMo=',
+        encrypted: 'B3CVIYFkn5+fB/9ib/OiLDXEYkQ9kF1Fap/Qv/Q8rCynqfVU6UeLmsw6yDiwIED/0+GEfeLkJTkp+d2e5ARDJamwXKu4CLLuhA004V0QWj8feydpWhoHotc/4I7KqjycnU1aif+JDVRyfXrkDA7BqN2GFl2O4sY2gUEBaki1W2ln'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 5.4',
+        message: 'FcW57hGF',
+        seed: 'SfpF06eN0Q39V3OZ0esAr37tVRM=',
+        encrypted: 'CBK3Z2jry2QtBAJY5fREGgGFIb2WaH5sXomfzWwXWI/1moLMiuA6S0WzEpmvF4jDKffc0oX4z0ztgmBrl2EmcaRb7coTNEIUTRYX0RT4AoV/D51zl1HFej+e5ACRLGHi5pkr4DGkPdSPproU7vfEIrXtxOevoE/dOPQC0ci7cZq/'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 5.5',
+        message: 'IQJuaADH+nKPyqug0ZauKNeirE/9irznlPCYX2DIpnNydzZdP+oR24kjogKa',
+        seed: '8Ch0EyNMxQNHJKCUxFhrh6/xM/w=',
+        encrypted: 'B7YOFOyVS/0p5g0AR+eJ9R1XGGxjWJkDMGeTztP2gkHHQ1KaumpjdPkuGeAWPvozaX4Zb3Zh36qkeqxr3l5R3rUHxyxYmiyhaT2WsUYDgSSbLNuerER2nySJxdPS+Z8O48fuW/ZKWsecQr1DPxSb6MtZVINhZAWVUTyXr3vCUJcj'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 5.6',
+        message: 'VB43totsiHK4TAI=',
+        seed: '2fukXJbyHm4m0p6yzctlhb6cs0E=',
+        encrypted: 'CMNtTdozQjsu1oMNhfZBG6Hc9HCh+uDr7+58CJ8lbO90y5bqacOPYPOavuRBKby0yS3n95diOyAHTj2cKJlwHtkHHh76C92E1MPlEwMC2PAkC6ukuEpxzAMvIjWl/w+uJ3w+j5ESvvRMmuINF1/JpAWL/JMLoxsC4uT0REg3EPJK'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha1', examples);
+
+      // Example 6: A 1029-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'ErF/ba0uzRn/RtwT94YPCeDgz7Z3s4pSWSMFzq8CLBZtuQ0ErCnjP33RLZ+vZuCBa7Y+rSZ8x9RsF8N74hS8oqItcjpk5EQHQ2tvyWVymu/CVU83bNXc6mgpN4CmK/OdAClIWhYLu55dwJctIaUE9S5e4CiqQWMy9RCy6c/19yKv';
+      exponent = 'AQAB';
+      d = 'ApXso1YGGDaVWc7NMDqpz9r8HZ8GlZ33X/75KaqJaWG80ZDcaZftp/WWPnJNB7TcEfMGXlrpfZaDURIoC5CEuxTyoh69ToidQbnEEy7BlW/KuLsv7QV1iEk2Uixf99MyYZBIJOfK3uTguzctJFfPeOK9EoYij/g/EHMc5jyQz/P5';
+      p = 'BKbOi3NY36ab3PdCYXAFr7U4X186WKJO90oiqMBct8w469TMnZqdeJpizQ9g8MuUHTQjyWku+k/jrf8pDEdJo4s=';
+      q = 'BATJqAM3H+20xb4588ALAJ5eCKY74eQANc2spQEcxwHPfuvLmfD/4Xz9Ckv3vv0t1TaslG23l/28Sr6PKTSbke0=';
+      dP = 'A5Ycj3YKor1RVMeq/XciWzus0BOa57WUjqMxH8zYb7lcda+nZyhLmy3lWVcvFdjQRMfrg6G+X63yzDd8DYR1KUs=';
+      dQ = 'AiGX4GZ0IZaqvAP6L+605wsVy3h9YXrNMbt1x7wjStcG98SNIYLR8P+cIo3PQZZ7bAum0sCtEQobhXgx7CReLLE=';
+      qInv = 'BAHEwMU9RdvbXp2W0P7PQnXfCXS8Sgc2tKdMMmkFPvtoas4kBuIsngWN20rlQGJ64v2wgmHo5+S8vJlNqvowXEU=';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 6.1',
+        message: 'QEbKi6ozR8on9J4NgfnMHXG+m6UX1A==',
+        seed: '3Q9s/kFeiOWkaaUfu6bf1ArbQ4Q=',
+        encrypted: 'BjDuvNKFbCT3mIBuQfnmc0Xtqc7aOGrMn6yuoe7tBqzlg3CXGNnRafrfQU1cdvkploM+8wW3Wx5LlfZiog+u3DuuDEgnqL+KiO29V+wgOieoQfAuQ6YVurGoysBwHeNN6972KgiAibVew26nUi/T7I0GtqBz5t+DMVO8Cu/ZO9Gj'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 6.2',
+        message: 'XMcsYCMd8Ds9QPm1eTG8MRCflyUn8osZ50gMcojLPJKyJRIhTkvmyRR5Ldq99X+qiqc=',
+        seed: 'jRS9lGoTURSPXK4u2aDGU+hevYU=',
+        encrypted: 'Drw3N2FzpP0vicxVwspismsR1Rw8fOSeiEX3TnYHMXxDa8jSO5Zn3+udCHI0tHvGg3F1rlwFWfa4HX0iQW0+UPSsUz2PCBLy2555H+nHdayLatD1Na2c6yOkoCAUxYqz+NMWFJmiYPOTSOcUriodNEMgj9i3Isz9+zk+mAEfmeY/'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 6.3',
+        message: 'sg5lEwMJL0vMtDBwwPhtIwSTYu2WZC/FYywn20pS49gx8qsGiyOxSYecAC9r8/7ul1kRElYs',
+        seed: 'bAdbxFUg8WXAv16kxd8ZG8nvDkQ=',
+        encrypted: 'Cpi/EJNhk5RDbPaNjzji8Vj96OpU80NfI5uNBrgyGEQgJHau7ZYAlJJIDOOo1wVJjEyMaPAVAdyB22CPYAhzUMjDsL0unvaoFFi3yAG4ny5P6Z1JALpqS15althl3Gdsd1WSh5QTDWKAqBYKGQ8t8+p8+aoCcdiOnmkF7PHFFS1l'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 6.4',
+        message: 'aE4wOMXAQfc=',
+        seed: 'O7w71mN9/hKEaQECm/WwwHEDQ5w=',
+        encrypted: 'AI56Z8rPtcTiS+x97hSRF/GVmM6MRYCP74jGCP+c1uaVJjuaPArUuLpMlSOOlqhCK4U1YpyNU4I3RHmtE/o5l0skL5p1nur5yDrVqMoYlAoBYrp1WHbfJj9L1QxlJcVgkCZ8Hw4JzgiZoM81nogSCr2b+JNEWzyud9Ngc1mumlL4'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 6.5',
+        message: 'MkiMsmLQQdbk3TX5h788ppbbHwasKaRGkw==',
+        seed: 'tGtBiT6L7zJvZ1k4OoMHHa5/yrw=',
+        encrypted: 'AAA0dEFse2i9+WHDhXN5RNfx9AyzlTQ8aTzAtP5jsx/t8erurJzMBnizHcMuCXdIlRTE8JCF9imKllPwGupARf9YLuiHviauV1tz7vfzd0kh43Wj0ZrdoMoxqhhJiHwfQsrJZ396L06SP25ahos4wITvGHWU3J9/BI/qLgKVU4Sr'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 6.6',
+        message: 'ULoUvoRicgJ5wwa6',
+        seed: 'CiQDMSpB49UvBg+8E6Z95c92Cac=',
+        encrypted: 'CgJt2l/IeF972b91Mntj6F4sD97l2ttl69ysmuHelcksZyq0M6p6jmnOam2Il/rErEpU3oQa5eW7znaHh515Y0zqejBoQGXHFNUkCbkoJWu/U+q81SMetyWVBFNzmb0pFktybTOkbacBNgpBaKCRzKty1Epi/tJGwP/qWxNIq1Rw'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha1', examples);
+
+      // Example 7: A 1030-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'MRF58Lz8m508oxXQDvMNe906LPrpkRv+3LlIs6R4LQcytqtEqkvwN0GmRNwBvsPmmwGgM+Z12KzXxJJcaxrsMRkFHf2Jdi0hXUVHX/y1n5CBSGI/NxdxVvauht16fF9D3B4fkIJUBYooSl8GwAIXk6h/GsX+/33K7mnF5Ro3ieNz';
+      exponent = 'AQAB';
+      d = 'Bwz8/y/rgnbidDLEXf7kj0m3kX1lMOHwyjRg8y4CdhdEh8VuIqRdJQDXd1SVIZ19Flqc872Swyr5qY2NycwpaACtyUoKVPtA80KRv4TujqErbxCTWcbTVCpQ+cdn9c//BaaBwuZW+3fKqttL6UaNirzU35j1jobSBT+hNJ90jiGx';
+      p = 'B0kmLBEc1HDsJWbms3MvwJMpRpqhkHHTucAZBlFMbx0muqFL6rCXHIt+YRpPeQCdb+p3aSjKJShbDeNkPRo/jHE=';
+      q = 'BrweUOlsAr9jbp7qi4mbvr92Ud533UdMPpvCO62BgrYZBMfZffvr+x4AEIh4tuZ+QVOR1nlCwrK/m0Q1+IsMsCM=';
+      dP = 'A7x+p/CqsUOrxs6LlxGGNqMBcuTP4CyPoN2jt7qvkPgJKYKYVSX0iL38tL1ybiJjmsZKMJKrf/y/HVM0z6ULW/E=';
+      dQ = 'AmKmqinCo8Z9xTRsBjga/Zh6o8yTz7/s9U/dn514fX9ZpSPTmJedoTei9jgf6UgB98lNohUY3DTLQIcMRpeZStk=';
+      qInv = 'ZJ1MF7buFyHnctA4mlWcPTzflVDUV8RrA3t0ZBsdUhZq+KITyDliBs37pEIvGNb2Hby10hTJcb9IKuuXanNwwg==';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 7.1',
+        message: 'R6rpCQ==',
+        seed: 'Q90JoH/0ysccqkYy7l4cHa7kzY8=',
+        encrypted: 'FojkzneUu6bLcBQWns1VnO3iowtWpSto2f4Yzxlz75eyoDFTlRx1X2KUqkmtvbVYRatodfs5hsk+z5J5YoQNKC+eVM6LaQ98DLi71zRA2VcdGxbNkmD56rR4PMSC5SI9xglzhxeD7Cewrg/UdzLLwoahc/ySsA+0umgkZHzZPIXB'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 7.2',
+        message: 'HZsuIiPZvBO/ufFiznNdtIunxo9oIqChp7auFlg05w==',
+        seed: 'Opw87HuE+b063svGc+yZ1UsivJs=',
+        encrypted: 'EFLtOXsuAeHQ7hxQvyQ2P5XlBPSgNDSgj9giV07WuXNu27XzkNsQMhR5qKE5NQ4r1Jd8N3jvMx8+eK4RiyaEUfIKLwHUcfXVPFZpNxcbLbwtS95FmleZ8DctZXQjmyMj0kXQu4HChrY8iaNhAXM35JAviKRn9MfyRL/Vq0ZDf/O2'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 7.3',
+        message: '2Xb8',
+        seed: 'dqdeW2FXpVbPiIS7LkXCk91UXPU=',
+        encrypted: 'IVXNhD/ySk7outt2lCYAKKSQgTuos2mky/EG7BSOUphwf1llvn0QHBBJ6oWEwkzWNFWtnBBNaGKC0/uAOkwRwcLpuRxxeIAdG2ZA8AP1co3wB7ikzMkrzgXkGicnjXyFAYxSQUMTpQd3iQAdTwGRC3Kq0F0iCqFKWHM6dIm8VFVr'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 7.4',
+        message: '1HOGI98iOqQ4Q9+EZ1NMQdAT4MgDxiTiY2ZrI5veQKXymuuN5549qmHdA3D0m9SwE4NLmCEq72scXuNzs8s=',
+        seed: 'eGYxSmrW8rJQo1lB2yj1hktYWFk=',
+        encrypted: 'CrFMNzrrfUMo0KqtjAlNiLnrCYuV8hBUopCCUivnwnoxKHi2N5F+PYGebDxWjbXYQ4ArBtUdnpiivgv0DAMUI7AO37/4Mg77kXG9IERlOky5xRIvbGXoPNouw8EmAnqcGla6h00P6iPzgLgs8kC4z1QABHWMTHfZNBV6dPP8Er+s'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 7.5',
+        message: 'u0cjHKXqHTrUbJk0XZqKYQ==',
+        seed: 'shZu1HLVjbEMqyxrAAzM8Qp9xQk=',
+        encrypted: 'AoOHoxgndDR5i02X9GAGjfUpj6ulBBuhF2Ghy3MWskGEEU7FACV+JYntO2B6HrvpemzC4CvxtoH0IxKjO3p32OeFXEpt4D48BGQ/eGuRomSg1oBeLOqR5oF363pk2SVeTyfnE7fM7ADcIA69IcLqK7iQ/q5JQt+UHcP5eJDtNHR4'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 7.6',
+        message: 'IYSCcJXTXD+G9gDo5ZdUATKW',
+        seed: 'Umc73iyhZsKqRhMawdyAjWfX07E=',
+        encrypted: 'FMZ4qUrWBSXvOelZsvO6XAl6lP+RK2fbrOgFNcGHq9R9B1QgsYchUrugj3/DHzE7v5JzyRL8TAFJqbDPt5gH40brMyBpYRvsD/m80Wjx98M+dzE86kVLlOJUnuzwAuKs9/by0oRdT+CqsuWpLd9oxICuESR5NdH2JXSEIhauZ0EV'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha1', examples);
+
+      // Example 8: A 1031-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'W98OMNMh3aUUf4gkCPppGVSA34+A0/bov1gYUE82QnypsfVUC5xlqPaXTPhEeiRNkoAgG7Sfy75jeNGUTNIn4jD5bj0Q+Bnc7ydsZKALKktnAefQHeX6veOx6aDfgvRjE1nNImaWR/uxcXJGE07XtJfP/73EK1nHOpbtkBZiEt/3';
+      exponent = 'AQAB';
+      d = 'D30enlqqJf0T5KBmOuFE4NFfXNGLzbCd8sx+ZOPF6RWtYmRTBBYdCYxxW7eri9AdB+rz/tfH7QivKopi70SrFrMg4Ur3Kkj5av4mKgrkz2XmNekQeQzU7lzqdopLJjn35vZ3s/C7a+MrdXR9iQkDbwJk9Y1AHNuhMXFhV6dez2Mx';
+      p = 'CgLvhEjZ+ti70NAEyMKql1HvlyHBsNAyNqVLDflHy67VolXuno4g1JHqFyP+CUcEqXYuiK/RbrtZlEEsqWbcT58=';
+      q = 'CS02Ln7ToL/Z6f0ObAMBtt8pFZz1DMg7mwz01u6nGmHgArRuCuny3mLSW110UtSYuByaxvxYWT1MP7T11y37sKk=';
+      dP = 'B8cUEK8QOWLbNnQE43roULqk6cKd2SFFgVKUpnx9HG3tJjqgMKm2M65QMD4UA10a8BQSPrpoeCAwjY68hbaVfX0=';
+      dQ = 'rix1OAwCwBatBYkbMwHeiB8orhFxGCtrLIO+p8UV7KnKKYx7HKtYF6WXBo/IUGDeTaigFjeKrkPH+We8w3kEuQ==';
+      qInv = 'BZjRBZ462k9jIHUsCdgF/30fGuDQF67u6c76DX3X/3deRLV4Mi9kBdYhHaGVGWZqqH/cTNjIj2tuPWfpYdy7o9A=';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 8.1',
+        message: 'BQt1Xl5ogPe56daSp0w3quRJsxv+pt7/g3R6iX9sLIJbsa2/hQo8lplLXeWzPLx9SheROnln',
+        seed: 'dwb/yh7PsevuKlXlxuJM0nl6QSU=',
+        encrypted: 'CbNoPYousPspW2LtH7kpC3FEV7eCUxn0ZHhyr4ibMECUcgIK0SkSvxmxHUgZ9JYUgk/9hNCcChfn0XMJ0SkZeQQQqimVaZ9qhtvjJCtazCOvRWkQgNaxroEPs+MFcIfwlwCSzgC+lWL/QFO2Jizgyqk+E3I9LjpboHXUXw1htUth'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 8.2',
+        message: 'TraNzZPKmxnfERvUNgj1VwJv5KodXPrCJ6PrWrlUjBigbd7SP4GCWYay/NcRCezvfv+Ihz8HXCqgxGn2nJK8',
+        seed: 'o3F9oUO03P+8dCZlqPqVBYVUg0M=',
+        encrypted: 'Ls8VyXxaFbFHaumGs3G1eiQoT0oWKo0MgYLnkF55IlbxgSul+D8fehMOQtzAIjKETtwUoxpo7peuVko4OjQRZWQkxfYt22Rgk8Nnvh/NpCbPAKBtist+V3dvu9hVrD31BvwWsdfD8hEPPYBo6R4YY2ODHIQJaA2NqezYzx+iDuOd'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 8.3',
+        message: 'hgSsVjKMGrWtkXhh',
+        seed: '7gYgkHPMoCa7Jk5Rhb+MaLdzn4Y=',
+        encrypted: 'S8iRMKWy2rt8L8+Q610Or55oG3FGo48xc6PZz+xS6p4KQZMuZIqdaTRMUNp2P1GgPJV2ITHoBSJU3NIkjLpA/TFmd4bOBaK3tTGsnaye1YSlm2d8GortjF0V1owFVp4r54C/fbY4/Sv9KoWrJ2hg83dzOPypif/XQ9E+4I4MqYk/'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 8.4',
+        message: '/dpfv27DYanZpKxoryFqBob0OLHg5cNrlV904QfznA3dzA==',
+        seed: 'mQrVc9xIqXMjW22CVDYY8ulVEF0=',
+        encrypted: 'LkVoR9j8Nv8BR9aZNZS5OXIn1Xd1LHnQ+QT8sDnU2BL+pgWntXTdgsp4b5N1I0hDjun1tUVJhdXw4WmePnrRdaMuFfA96wQquf4d2dsbuG+MCJzLRefvDF7nyptykMprFb7UcDl4ioqT/4Pg6NYkTHEAY2Le72m29Bb7PGhDg/vQ'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 8.5',
+        message: 'Sl9JFL7iXePGk0HeBw==',
+        seed: '7MY7KPB1byL1Ksjm7BJRpuwwRxg=',
+        encrypted: 'H7k1b9XEsXltsuv30NOTzIEK32FF3vwvznFPedk4ANXirCEeqLvsyktlS5TDsYsw3Vds403JVDbvV6CUFWRZIzWaXXtBce8iwkZw8bIp02A+kfdmcbffl+cxfJdzRHbV89F9Ic+Ctbqfg98uWI02mE/RtYRGi9I7LodfMvaJU/ey'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 8.6',
+        message: 'jgfWb3uICnJWOrzT81CSvDNAn7f4jyRyvg==',
+        seed: 'OSXHGzYtQKCm3kIUVXm6Hn3UWfw=',
+        encrypted: 'Ov2cZgAUeyF5jYGMZVoPTJIS2ybQsN/cKnWUzLPSL1vx18PhEs1z/H1QnHqLr908J00TmQCflgnsS+ZHfkU/B1qjPbOChwwcNAmu85LXOGrjppa5mpS02gWJRH6VXRbJixdgKlm9c2J5/Nj7KAxEYtWQv6m/E/7VcOr96XMwosIQ'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha1', examples);
+
+      // Example 9: A 1536-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'zyzUHjTKOnKOpcuK/2TDbSe971Nk4zb9aNMSPFoZaowocBPoU9UVbVjRUZVFIPtPbXsXq7aBd2WQnFdhGWWdkCsZBu2KKxDBVcJNEkUo2rnurjeb6sZuSkEXhty4/QBi68Aw3hIZoEwqjBt90xMeTWtsruLjGl7UGsFQmy7x7iqxg2S+VoypQcJezIT/nWQ7XsGqrhAqINc/R5t4D9bakQdSEtnqwDoGdNiZ66LkMfTES2Fba6IjK9SzO67XPWJd';
+      exponent = 'AQAB';
+      d = 'GYwUHiNxWpK8z2oRmlvBE4lGjSgR9UjXJ+F7SrDrmG1vIR77U7cffMvqh+5px17mFQCMUzLetSvzkKvfv+N9cgU2gVmyY4wd4ybiHSIlHw+1hIs78VAF0qdDMPCv6RbuYszBNE0dg6cJ5gZ2JzhA9/N3QkpeCk2nXwGzH/doGc+cv90hUkPDkXwD7zgZkxLlZ7O/eu06tFfzce+KFCP0W2jG4oLsERu6KDO5h/1p+tg7wbjGE8Xh6hbBHtEl6n7B';
+      p = '/I1sBL7E65qBksp5AMvlNuLotRnezzOyRZeYxpCd9PF2230jGQ/HK4hlpxiviV8bzZFFKYAnQjtgXnCkfPWDkKjD6I/IxI6LMuPaIQ374+iB6lZ0tqNIwh6T+eVepl79';
+      q = '0gDUXniKrOpgakAdBGD4fdXBAn4S3BoNdYbok52c94m0D1GsBEKWHefSHMIeBcgxVcHyqpGTOHz9+VbLSNFTuicEBvm7ulN9SYfZ4vmULXoUy//+p0/s3ako0j4ln17h';
+      dP = '2xaAL3mi8NRfNY1p/TPkS4H66ChiLpOlQlPpl9AbB0N1naDoErSqTmyL6rIyjVQxlVpBimf/JqjFyAel2jVOBe8xzIz3WPRjcylQsD4mVyb7lOOdalcqJiRKsI23V1Kt';
+      dQ = 'oKMXz+ffFCP4em3uhFH04rSmflSX8ptPHk6DC5+t2UARZwJvVZblo5yXgX4PXxbifhnsmQLgHX6m+5qjx2Cv7h44G2neasnAdYWgatnEugC/dcitL6iYpHnoCuKU/tKh';
+      qInv = 'CyHzNcNTNC60TDqiREV4DC1lW5QBdMrjjHyKTmSTwLqf0wN0gmewg7mnpsth5C2zYrjJiW23Bk4CrVrmFYfaFbRknJBZSQn+s328tlS+tyaOyAHlqLSqORG+vYhULwW+';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 9.1',
+        message: '9zX9VbqSWSw7Urj5xPaaqhy++P6IrdCVWVQSRn+c9OwLiWxZ7aFiEOdUnIq7EM28IaEuyba1uP0vEDmetg==',
+        seed: 'jsll8TSj7Jkx6SocoNyBadXqcFw=',
+        encrypted: 'JnvNEYrKsfyLqByF1zADy4YQ+lXB2X2o1Ip8fwaJak23UaooQlW502rWXzdlPYKfGzf5e4ABlCVFsvwsVac3bKehvksXYMjgWjPlqiUmuNmOMXCI54NMdVsqWbEmMaGCwF1dQ6sXeSZPhFb1Fc5X399RLVST2re3M43Et9eNucCRrDuvU3pp/H9UnZefDv+alP2kFpvU0dGaacmeM8O1VJDVAbObHtrhGP9nk6FTJhWE06Xzn25oLj0XyM0SYfpy'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 9.2',
+        message: 'gbkGYFAVpjqr5C3fEeGXiRL1QEx0dLJtzj7Ugr+WHsyBi/QgxUZZ',
+        seed: '7LG4sl+lDNqwjlYEKGf0r1gm0Ww=',
+        encrypted: 'k6yfBnHsKay7RE7/waV0E1HWD9sOOT+/dUrPDeSXYaFIQd93cum8gnc5ZqFYTE1yuuoAEY+D81zKblN8vU2BH1WDspeD2KbZTNMb5w1vUmwQ/wnG+nzgaXlaP80FEf1fy1ZLzIDqnHjzi4ABJTnYpN32/oHpzdt/UNu7vMfl2GCXzPTsSRifuL8xi+bVoHFdUWtJrxkSWM0y3IM85utGc8A6Gbus6IzFSJX2NswMHsiQltEc4jWiZcoXZCMqaJro'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 9.3',
+        message: '/TJkKd+biQ4JtUsYuPNPHiQ=',
+        seed: '6JuwMsbOYiy9tTvJRmAU6nf3d8A=',
+        encrypted: 'gevdlQVLDIIu+a12k/Woet+0tMTOcN8t+E7UnATaWLpfwgoZ4abot6OQCyJ5bcToae5rQnktFajs61bAnGmRToE86o9pMeS47W9CGvKY1ZXJf0eJx8qmEsfvNgmEwhuT7cVAEGi1r0x4qHcbmE1TuOqK3y9qfUoLp2x14d2fZY8g3tSkYHHUbXeRtWgD2P6n8LD45Brj8JODpvlYX+d1Pqr/0r+UVjEIvuzCB7u1NfX8xwXw3en3CMYvSanJA3HT'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 9.4',
+        message: '8UWbXwyS8BoPcjouVmJITY+MCiD8KdrWrNQ7tfPv/fThtj4H/f5mKNDXTKGb8taeSgq/htKTklp5Z3L4CI4=',
+        seed: 'YG87mcC5zNdx6qKeoOTIhPMYnMw=',
+        encrypted: 'vMNflM3mbLETZiXWJblEMqNbIvPS+hGmE/8PylvVf4e5AszcHNCuvLBxXuhp0dH+OV9nkwA/XspGUFnIhmDURv9fCBhVICJVfjjAimfq2ZEmIlTxBoKXXsVjl3aFN/SXevbV9qrOt/sl3sWTcjAjH9iXivSRGaKfKeQkq4JytHVieS1clPd0uIKdCw2fGoye3fN1dNX6JI7vqcUnH8XsJXnIG91htBD6Yf425CQiHBE63bJ1ZkyAHTTKjGNR5KhY'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 9.5',
+        message: 'U+boxynW+cMZ3TF+dLDbjkzMol88gwV0bhN6xjpj7zc557WVq7lujVXlT3vUGrQzN4/7kR0=',
+        seed: '/LxCFALp7KvGCCr6QLpfJlIshA4=',
+        encrypted: 'Iyr7ySf6CML2onuH1KXLCcB9wm+uc9c6kFWIOfT9ZtKBuH7HNLziN7oWZpjtgpEGp95pQs1s3OeP7Y0uTYFCjmZJDQNiZM75KvlB0+NQVf45geFNKcu5pPZ0cwY7rseaEXn1oXycGDLyg4/X1eWbuWWdVtzooBnt7xuzrMxpfMbMenePYKBkx/b11SnGIQJi4APeWD6B4xZ7iZcfuMDhXUT//vibU9jWTdeX0Vm1bSsI6lMH6hLCQb1Y1O4nih8u'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 9.6',
+        message: 'trKOohmNDBAIvGQ=',
+        seed: 'I6reDh4Iu5uaeNIwKlL5whsuG6I=',
+        encrypted: 'Q4zH3AimjaJJ5CUF+Fc7pg4sJ3PVspD0z53/cY6EIIHDg+ZwJKDylZTqmHudJeS3OPKFlw0ZWrs6jIBU49eda5yagye6WW8SWeJxJmdHZpB9jVgv86hHYVSSmtsebRI1ssy07I9mO6nMZwqSvr2FPI2/acZDbQFvYa3YNulHMkUENCB/n9TEPewqEqlY76Ae/iZpiZteYEwlXFX7cWbeVYnjaVl7sJFowG3V2xd+BqF0DrLVyC+uym2S/O6ZMbqf'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha1', examples);
+
+      // Example 10: A 2048-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'rkXtVgHOxrjMBfgDk1xnTdvg11xMCf15UfxrDK7DE6jfOZcMUYv/ul7Wjz8NfyKkAp1BPxrgfk6+nkF3ziPn9UBLVp5O4b3PPB+wPvETgC1PhV65tRNLWnyAha3K5vovoUF+w3Y74XGwxit2Dt4jwSrZK5gIhMZB9aj6wmva1KAzgaIv4bdUiFCUyCUG1AGaU1ooav6ycbubpZLeGNz2AMKu6uVuAvfPefwUzzvcfNhP67v5UMqQMEsiGaeqBjrvosPBmA5WDNZK/neVhbYQdle5V4V+/eYBCYirfeQX/IjY84TE5ucsP5Q+DDHAxKXMNvh52KOsnX1Zhg6q2muDuw==';
+      exponent = 'AQAB';
+      d = 'BWsEIW/l81SsdyUKS2sMhSWoXFmwvYDFZFCiLV9DjllqMzqodeKR3UP0jLiLnV/A1Jn5/NHDl/mvwHDNnjmMjRnmHbfHQQprJnXfv100W4BNIBrdUC1c4t/LCRzpmXu+vlcwbzg+TViBA/A29+hdGTTRUqMj5KjbRR1vSlsbDxAswVDgL+7iuI3qStTBusyyTYQHLRTh0kpncfdAjuMFZPuG1Dk6NLzwt4hQHRkzA/E6IoSwAfD2Ser3kyjUrFxDCrRBSSCpRg7Rt7xA7GU+h20Jq8UJrkW1JRkBFqDCYQGEgphQnBw786SD5ydAVOFelwdQNumJ9gkygHtSV3UeeQ==';
+      p = '7PWuzR5VFf/6y9daKBbG6/SQGM37RjjhhdZqc5a2+AkPgBjH/ZXMNLhX3BfwzGUWuxNGq01YLK2te0EDNSOHtwM40IQEfJ2VObZJYgSz3W6kQkmSB77AH5ZCh/9jNsOYRlgzaEb1bkaGGIHBAjPSF2vxWl6W3ceAvIaKp30852k=';
+      q = 'vEbEZPxqxMp4Ow6wijyEG3cvfpsvKLq9WIroheGgxh5IWKD7JawpmZDzW+hRZMJZuhF1zdcZJwcTUYSZK2wpt0bdDSyr4UKDX30UjMFhUktKCZRtSLgoRz8c52tstohsNFwD4F9B1RtcOpCj8kBzx9dKT+JdnPIcdZYPP8OGMYM=';
+      dP = 'xzVkVx0A+xXQij3plXpQkV1xJulELaz0K8guhi5Wc/9qAI7U0uN0YX34nxehYLQ7f9qctra3QhhgmBX31FyiY8FZqjLSctEn+vS8jKLXc3jorrGbCtfaPLPeCucxSYD2K21LCoddHfA8G645zNgz72zX4tlSi/CE0flp55Tp9sE=';
+      dQ = 'Jlizf235wQML4dtoEX+p2H456itpO35tOi9wlHQT7sYULhj7jfy2rFRdfIagrUj4RXFw8O+ya8SBJsU+/R0WkgGY3CoRB9woLbaoDNMGI2C6P6E/cOQxL/GmzWuPxM2cXD2xfG1qVyEvc64p9hkye61ZsVOFhYW6Tii2CmKkXkk=';
+      qInv = 'bzhSazklCFU07z5BWoNu3ouGFYosfL/sywvYNDBP7Gg7qNT0ecQz1DQW5jJpYjzqEAd22Fr/QB0//2EO5lQRzjsTY9Y6lwnu3kJkfOpWFJPVRXCoecGGgs2XcQuWIF7DERfXO182Ij+t1ui6kN18DuYdROFjJR4gx/ZuswURfLg=';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 10.1',
+        message: 'i7pr+CpsD4bV8XVul5VocLCJU7BrTrIFvBaU7g==',
+        seed: 'R+GrcRn+5WyV7l6q2G9A0KpjvTM=',
+        encrypted: 'U+pdwIzSYPs7hYVnKH+pFVLDCy/r+6IT8K6HcC0GjRm6sH/ldFI9+0ITnWjDxa/u4L/ky3lpy/OCuATW5hOWFE4tDmB0H4mTwwFLWLmxlXqLq80jr4VPTDVvsWYqpyv8x+WGVZ3EKA0WDBJnhacj6+6+/3HxFZRECq74fRB5Ood0ojnUoEyH/hRnudr4UgjsbHJVeUqWzCkUL5qL1Bjjwf1nNEsM0IKd87K+xgJTGWKTxrNNP3XTLyE91Fxic9UFrfTM7RBXy3WPwmru+kQSVe1OZMGZ7gdefxZkYYL9tGRzm2irXa/w5j6VUgFoJPBUv008jJCpe7a2VTKE60KfzA=='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 10.2',
+        message: '5q0YHwU7WKkE8kV1EDc+Vw==',
+        seed: 'bRf1tMH/rDUdGVv3sJ0J8JpAec8=',
+        encrypted: 'orGkMKnWV+L6HCu17UP/slwFowj+kJPAEDF5X1h0QAEQgorlj7m1gc6d3dPlSa4EoJhUWb3mxiZZTnsF3EJ4sqFGXBNoQIgjyF6W3GbDowmDxjlmT8RWmjf+IeWhlbV3bu0t+NjTYa9obnUCKbvWY/FhhopQYV4MM3vsDKNf7AuxnDbrLgu8wFgvodk6rNsGEGP1nyzh7kNgXl2J7KGD0qzf6fgQEQIq07Q6PdQX2slLThHqgbGSlm6WaxgggucZZGB7T4AC82KZhEoR8q4PrqwurnD49PmAiKzc0KxVbp/MxRFSGQj60m8ExkIBRQMFd4dYsFOL+LW7FEqCjmKXlQ=='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 10.3',
+        message: 'UQos9g6Gb6I0BVPJTqOfvCVjEeg+lEVLQSQ=',
+        seed: 'OFOHUU3szHx0DdjN+druSaHL/VQ=',
+        encrypted: 'mIbD5nZKi5qE6EFI69jDsaqAUDgaePZocUwW2c/Spu3FaXnFNdne47RLhcGL6JKJkjcXEUciFtld2pjS7oNHybFN/9/4SqSNJawG99fmU5islnsc6Qkl9n3OBJt/gS2wdCmXp01E/oHb4Oej/q8uXECviI1VDdu+O8IGV6KVQ/j8KRO5vRphsqsiVuxAm719wNF3F+olxD9C7Sffhzi/SvxnZv96/whZVV7ig5IPTIpjxKc0DLr93DOezbSwUVAC+WyTK1t5Fnr2mcCtP8z98PROhacCYr8uGP40uFBYmXXoZ/+WnUjqvyEicVRs3AWmnstSblKHDINvMHvXmHgO3g=='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 10.4',
+        message: 'vN0ZDaO30wDfmgbiLKrip18QyR/2Z7fBa96LUwZKJkmpQEXJ',
+        seed: 'XKymoPdkFhqWhPhdkrbg7zfKi2U=',
+        encrypted: 'Yxjp+1wNBeUwfhaDQ26QMpOsRkI1iqoiPXFjATq6h+Lf2o5gxoYOKaHpJoYWPqC5F18ynKOxMaHt06d3Wai5e61qT49DlvKM9vOcpYES5IFg1uID2qWFbzrKX/7Vd69JlAjj39Iz4+YE2+NKnEyQgt5lUnysYzHSncgOBQig+nEi5/Mp9sylz6NNTR2kF4BUV+AIvsVJ5Hj/nhKnY8R30Vu7ePW2m9V4MPwsTtaG15vHKpXYX4gTTGsK/laozPvIVYKLszm9F5Cc8dcN4zNa4HA5CT5gbWVTZd5lULhyzW3h1EDuAxthlF9imtijU7DUCTnpajxFDSqNXu6fZ4CTyA=='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 10.5',
+        message: 'p91sfcJLRvndXx6RraTDs9+UfodyMqk=',
+        seed: 'lbyp44WYlLPdhp+n7NW7xkAb8+Q=',
+        encrypted: 'dSkIcsz9SkUFZg1lH1babaoJyhMB2JBjL2qZLz1WXO5GSv3tQO07W+k1ZxTqWqdlX0oTZsLxfHKPbyxaXR+OKEKbxOb48s/42o3A4KmAjkX9CeovpAyyts5v//XA4VnRG2jZCoX3uE4QOwnmgmZkgMZXUFwJKSWUaKMUeG106rExVzzyNL9X232eZsxnSBkuAC3A3uqTBYXwgx/c2bwz1R957S/8Frz01ZgS/OvKo/kGmw5EVobWRMJcz2O0Vu5fpv/pbxnN91H+2erzWVd1Tb9L/qUhaqGETcUHyy0IDnIuuhUDCMK1/xGTYg8XZuz0SBuvuUO9KSh38hNspJSroA=='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 10.6',
+        message: '6vGnOhsMRglTfeac2SKLvPuajKjGw++vBW/kp/RjTtALfDnsaSLXuOosBOus',
+        seed: 'n0fd9C6X7qhWqb28cU6zrCL26zI=',
+        encrypted: 'LSB6c0Mqj7TAMFGz9zsophdkCY36NMR6IJlfgRWqaBZnm1V+gtvuWEkIxuaXgtfes029Za8GPVf8p2pf0GlJL9YGjZmE0gk1BWWmLlx38jA4wSyxDGY0cJtUfEb2tKcJvYXKEi10Rl75d2LCl2Pgbbx6nnOMeL/KAQLcXnnWW5c/KCQMqrLhYaeLV9JiRX7YGV1T48eunaAhiDxtt8JK/dIyLqyXKtPDVMX87x4UbDoCkPtnrfAHBm4AQo0s7BjOWPkyhpje/vSy617HaRj94cGYy7OLevxnYmqa7+xDIr/ZDSVjSByaIh94yCcsgtG2KrkU4cafavbvMMpSYNtKRg=='
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha1', examples);
+    }
+
+    function testOAEPSHA256() {
+      var modulus, exponent, d, p, q, dP, dQ, qInv, pubkey, privateKey;
+      var examples;
+
+      // Example 1: A 1024-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'qLOyhK+OtQs4cDSoYPFGxJGfMYdjzWxVmMiuSBGh4KvEx+CwgtaTpef87Wdc9GaFEncsDLxkp0LGxjD1M8jMcvYq6DPEC/JYQumEu3i9v5fAEH1VvbZi9cTg+rmEXLUUjvc5LdOq/5OuHmtme7PUJHYW1PW6ENTP0ibeiNOfFvs=';
+      exponent = 'AQAB';
+      d = 'UzOc/befyEZqZVxzFqyoXFX9j23YmP2vEZUX709S6P2OJY35P+4YD6DkqylpPNg7FSpVPUrE0YEri5+lrw5/Vf5zBN9BVwkm8zEfFcTWWnMsSDEW7j09LQrzVJrZv3y/t4rYhPhNW+sEck3HNpsx3vN9DPU56c/N095lNynq1dE=';
+      p = '0yc35yZ//hNBstXA0VCoG1hvsxMr7S+NUmKGSpy58wrzi+RIWY1BOhcu+4AsIazxwRxSDC8mpHHcrSEurHyjnQ==';
+      q = 'zIhT0dVNpjD6wAT0cfKBx7iYLYIkpJDtvrM9Pj1cyTxHZXA9HdeRZC8fEWoN2FK+JBmyr3K/6aAw6GCwKItddw==';
+      dP = 'DhK/FxjpzvVZm6HDiC/oBGqQh07vzo8szCDk8nQfsKM6OEiuyckwX77L0tdoGZZ9RnGsxkMeQDeWjbN4eOaVwQ==';
+      dQ = 'lSl7D5Wi+mfQBwfWCd/U/AXIna/C721upVvsdx6jM3NNklHnkILs2oZu/vE8RZ4aYxOGt+NUyJn18RLKhdcVgw==';
+      qInv = 'T0VsUCSTvcDtKrdWo6btTWc1Kml9QhbpMhKxJ6Y9VBHOb6mNXb79cyY+NygUJ0OBgWbtfdY2h90qjKHS9PvY4Q==';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 1.1',
+        message: 'ZigZThIHPbA7qUzanvlTI5fVDbp5uYcASv7+NA==',
+        seed: 'GLd26iEGnWl3ajPpa61I4d2gpe8Yt3bqIQadaXdqM+k=',
+        encrypted: 'W1QN+A1CKWotV6aZW7NYnUy7SmZd34SiX0jiPiLj9+8sZW6O/L7793+IFFSO3VKbPWhrjJPyR3ZmZ+yHDCzTDkRth+s5FN3nuFtlD3XQmmh0+x60PvAUiXJnAMcwxV96wHKjsUNPSnE1fsrCPBpIO5ZRaJ1pIF6R25IeuMwDujo='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 1.2',
+        message: 'dQxAR/VH6OQUEYVlIymKybriRe+vE5f75W+d1Q==',
+        seed: 'DMdCzkqbfzL5UbyyUe/ZJf5P418Mx0LOSpt/MvlRvLI=',
+        encrypted: 'jsKSyOW1BkucnZpnt9fS72P/lamWQqexXEDPVs8uzGlFj24Rj+cqGYVlt7i9nTmOGj2YrvM8swUTJQCYIF+QBiKbkcA7WBTBXfiUlkHvpWQD0bLwOkp1CmwfpF4sq2gTsCuSaGzZAc50ZAIOvpldizU7uOCwNNGOlERcFkvhfEE='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 1.3',
+        message: '2Urggy5kRc5CMxywbVMagrHbS6rTD3RtyRbfJNTjwkUf/1mmQj6w4dAtT+ZGz2md/YGMbpewUQ==',
+        seed: 'JRTfRpV1WmeyiOr0kFw27sZv0v0lFN9GlXVaZ7KI6vQ=',
+        encrypted: 'LcQ1BhOH4Vs0XX8/QJ6q/L0vSs9BUXfA20lQ6mwAt/gvUaUOvKJWBujoxt1QgpRnU6WuH7cSCFWXuKNnrhofpFF3CBTLIUbHZFoou0A4Roi4vFGFvYYu96Boy+oWivwB9/BKs1QMQeHADgNwUgqVD15+q27yHdfIH7kGp+DiGas='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 1.4',
+        message: 'UuZQ2Y5/KgSLT4aFIVO5fgHdMW80ahn2eoU=',
+        seed: 'xENaPhoYpotoIENikKN877hds/vEQ1o+Ghimi2ggQ2I=',
+        encrypted: 'ZMkqw9CM3SuY2zPBr8/9QbgXaVon4O4AKIufl3i7RVPD07fiTOnXF0aSWKUcdXNhE6ZcXc0Ha97/S5aw6mQKYfbmjaSq/H45s2nfZYTNIa74OgsV1DTDDLSF6/3J2UKhsG0LGIFaV9cNjfucDA5KbfQbzTq8u/+WN06J6nbInrI='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 1.5',
+        message: 'jaif2eX5dKKf7/tGK0kYD2z56AI=',
+        seed: 'sxjELfO+D4P+qCP1p7R+1eQlo7WzGMQt874Pg/6oI/U=',
+        encrypted: 'NzKEr8KhWRbX/VHniUE8ap0HzdDEWOyfl7dfNHXjL4h/320dmK633rGUvlA7sE4z9yuMj/xF++9ZeBzN6oSPLhVJV/aivUfcC8J99lwwp49W7phnvkUA4WUSmUeX+XRhwj8cR27mf5lu/6kKKbgasdt4BHqXcc5jOZICnld6vdE='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 1.6',
+        message: 'JlIQUIRCcQ==',
+        seed: '5OwJgsIzbzpnf2o1YXTrDOiHq8Lk7AmCwjNvOmd/ajU=',
+        encrypted: 'nfQEzsDY2gS9UYXF85t+u0Tm7HrOmmf+LqxCD+6N4XD36NoQ96PE9Squ83PvxKy8Bj8Q0N2L8E5Z5/9AWxLPCBqOkqkqIqO7ZDQMmpHml3H1yz82rpAzAQi6acZDSFQAW8NKhg4nEEwfwKdaGQcI0JZm6FrTQUuXskOqFUT0NJc='
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha256', examples);
+
+      // Example 2: A 1025-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'AZR8f86QQl9HJ55whR8l1eYjFv6KHfGTcePmKOJgVD5JAe9ggfaMC4FBGQ0q6Nq6fRJQ7G22NulE7Dcih3x8HQpn8UsWlMXwN5RRpD5Joy3eg2cLc9qRocmbwjtDamAFXGEPC6+ZwaB5VluVo/FSZjLR1Npg8g7aJeZTxPACdm9F';
+      exponent = 'AQAB';
+      d = 'CCPyD6212okIip0AiT4h+kobEfvJPGSjvguq6pf7O5PD/3E3BMGcljwdEHqumQVHOfeeAuGG3ob4em3e/qbYzNHTyBpHv6clW+IGAaSksvCKFnteJ51xWxtFW91+qyRZQdl2i5rO+zzNpZUto87nJSW0UBZjqO4VyemS2SRi/jk=';
+      p = 'AVnb3gSjPvBvtgi4CxkPTT4ivME6yOSggQM6v6QW7bCzOKoItXMJ6lpSQOfcblQ3jGlBTDHZfdsfQG2zdpzEGkM=';
+      q = 'AStlLzBAOzi0CZX9b/QaGsyK2nA3Mja3IC05su4wz7RtsJUR9vMHzGHMIWBsGKdbimL4It8DG6DfDa/VUG9Wi9c=';
+      dP = 'Q271CN5zZRnC2kxYDZjILLdFKj+1763Ducd4mhvGWE95Wt270yQ5x0aGVS7LbCwwek069/U57sFXJIx7MfGiVQ==';
+      dQ = 'ASsVqJ89+ys5Bz5z8CvdDBp7N53UNfBc3eLv+eRilIt87GLukFDV4IFuB4WoVrSRCNy3XzaDh00cpjKaGQEwZv8=';
+      qInv = 'AnDbF9WRSwGNdhGLJDiac1Dsg2sAY6IXISNv2O222JtR5+64e2EbcTLLfqc1bCMVHB53UVB8eG2e4XlBcKjI6A==';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 2.1',
+        message: 'j/AMqmBccCgwY02abD1CxlK1jPHZL+xXC+7n',
+        seed: 'jEB7XsKJnlCZxT6M55O/lOcbF4KMQHtewomeUJnFPow=',
+        encrypted: 'AR3o2JwhHLKUfOLZ26KXD9INUK1/fWJzdZix7E545qladDYdpHRaE5zBP9nf6IPmZvBUPq75n1E4suxm+Bom7crf9be1HXCFZnmR/wo92CKg4D1zRlBwr/3Gitr3h9rU6N+tid2x9yOYj955rf3Bq4j6wmjYQpWphbhBIBMoliyJ'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 2.2',
+        message: 'LQ==',
+        seed: 'tgDPPC5QbX8Wd4yRDTqLAD7uYdW2AM88LlBtfxZ3jJE=',
+        encrypted: 'AIeYuAD2aYZYnEu1YK+INur95FfP2pTz8/k4r3xwL4bVMufgvzWFLdVK24fP96jTteLkrX6HjmebBVeUhSWG3ahebh3LH5yVS9yx+xHzM1Jxc8X1rS+kYgdCGWFbszMF/vP0ogisy5XthHqcoHNEM4Rzln7ugrXuS+dNuuPEjIAf'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 2.3',
+        message: 'dPyIxRvJD3evnV6aSnATPUtOCzTaPDfH744=',
+        seed: 'pzdoruqpH52MHtb50rY0Z/B8yuOnN2iu6qkfnYwe1vk=',
+        encrypted: 'AMkW9IJHAFs0JbfwRZhrRITtj1bQVDLcjFCwYxHMDBlSHIqpDzSAL8aMxiUq41Feo9S2O/1ZTXIiK8baJpWs9y+BPqgi1lABB6JJIvU2QZYMzWK0XgjkWk12g6HSPFhuK4yf+LQ1UYpbKVquUdZ9POOCR8S7yS+tdful6qP8Wpkm'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 2.4',
+        message: 'p+sqUDaTHSfU6JEybZlpL/rdqb9+/T405iLErcCF9yHf6IUHLHiiA7FRc5vlQPqMFToQ8Ao=',
+        seed: 'mns7DnCL2W+BkOyrT7mys4BagVaaezsOcIvZb4GQ7Ks=',
+        encrypted: 'AJ6YQ3DNjd7YXZzjHASKxPmwFbHKwoEpof+P+Li3+o6Xa95C21XyWZF0iCXc5USp5jwLt66T6G3aYQkEpoyFGvSPA3NV6tOUabopdmslYCkOwuOIsFLiuzkJc4Hu6nWXeJtTVtHn7FmzQgzQOMjuty1YConfe78YuQvyE3IAKkr2'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 2.5',
+        message: 'LvKwZvhUwz873LtZlKQ15z1sbA==',
+        seed: '6zzrvErcFrtI6IyK7A40r39Cf9PrPOu8StwWu0jojIo=',
+        encrypted: 'AMv457W0EOt8RH+LAEoMQ7dKjZamzOdwTHJepDkaGGoQHi2z8coCiVemL5XYZ+ctjPBdw3y3nlMn1sif9i3WCzY26ram8PL5eVYk7Bm3XBjv9wuhw1RZmLFzKfJS+3vi+RTFhwjyyeaJrc07f5E7Cu7CVWNh3Oe3lvSF3TB2HUI8'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 2.6',
+        message: 'in+zRMi2yyzy7x9kP5oyGPbhm7qJwA==',
+        seed: 'TEXPTVfJjj1tIJWtxRxInrUN/4RMRc9NV8mOPW0gla0=',
+        encrypted: 'AJ5iMVr3Q6ZZlqLj/x8wWewQBcUMnRoaS2lrejzqRk12Bw120fXolT6pgo20OtM6/ZpZSN7vCpmPOYgCf93MOqKpN1pqumUH33+iP1a+tos5351SidwwNb2hLy3JfhkapvjB+c9JvbIolIgr+xeWhWPmMDam/Du/y+EsBOdZrbYc'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha256', examples);
+
+      // Example 3: A 1026-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'ArWP7AOahgcApNe2Ri+T5s3UkRYd3XT06BC0DjwWUgBqXCd7J3TBEwWky6taeO+lfheobfej+jb8Sx0iSfIux8LdakYyMqzOqQbWbr6AtXBLEHKdpvgzI0q7Xv3UopLL+tM7TTP6ehS4w5e1bjrNISA0KLd836M6bacGs9iw/EPp';
+      exponent = 'AQAB';
+      d = 'FbSKW1aDqUZw4jtXGPgU+g4T+FA49QcRGCy6YVEFgfPSLH4jLvk34i5VHWi4bi+MsarYvi5Ij13379J54/Vo1Orzb4DPcUGs5g/MkRP7bEqEH9ULvHxRL/y+/yFIeqgR6zyoxiAFNGqG3oa/odipSP0/NIwi6q3zM8PObOEyCP0=';
+      p = 'Ab8B0hbXNZXPAnDCvreNQKDYRH0x2pGamD9+6ngbd9hf43Gz6Tc+e2khfTFQoC2JWN5/rZ1VUWCVi0RUEn4Ofq8=';
+      q = 'AY0zmWWBZts4KYFteylUFnWenJGYf1stiuzWOwS0i9ey/PIpu3+KbciLoT3S45rVW20aBhYHCPlwC+gLj9N0TOc=';
+      dP = 'BsCiSdIKby7nXIi0lNU/aq6ZqkJ8iMKLFjp2lEXl85DPQMJ0/W6mMppc58fOA6IVg5buKnhFeG4J4ohalyjk5Q==';
+      dQ = '0dJ8Kf7dkthsNI7dDMv6wU90bgUc4dGBHfNdYfLuHJfUvygEgC9kJxh7qOkKivRCQ7QHmwNEXmAuKfpRk+ZP6Q==';
+      qInv = 'jLL3Vr2JQbHTt3DlrTHuNzsorNpp/5tvQP5Xi58a+4WDb5Yn03rP9zwneeY0uyYBHCyPfzNhriqepl7WieNjmg==';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 3.1',
+        message: 'CHggtWno+o0=',
+        seed: 'jO1rGWKQgFeQ6QkHQBXmogsMSJSM7WsZYpCAV5DpCQc=',
+        encrypted: 'AJqBCgTJGSHjv2OR0lObiDY2gZmWdutHfVeadCdFr2W4mS3ZHwet283wbtY/bsM8w0rVxNAPh3NZNrcRt56NhoT0NzD2IK3WNy39Im/CfbicvC6Vq2PyXUh1iza+90PUM3jECPP5NsOx658MzEnYyFZFb9izZIna6YLsXwkWoHVO'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 3.2',
+        message: 'RlOsrxcZYLAfUqe+Y6OrIdw2jsQ7UNguw3geBA==',
+        seed: 'tCkdZWdVCEjMFWlnyAm6q2ylB/C0KR1lZ1UISMwVaWc=',
+        encrypted: 'ARCj8j/hSsscyXtuINlyU0HuC+d7wZc7bSekF60BJFWKeKa1p28d4KsJXmdqI22sxha7PgkI9bgpfgdBd8KHp12g5y68uXiwRyPOvv8s6YDKmJFhbW13LHbE3iZHch2YG1eHi/20M/IrsAqCuk/W5Q/dP5eSVM1hLT9LBVsX3rIH'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 3.3',
+        message: '2UzQ4I+kBO2J',
+        seed: 'zoko9gWVWCVACLrdl5T63NL9H2XOiSj2BZVYJUAIut0=',
+        encrypted: 'Anfa/o/QML7UxLCHcSUWFPUWhcp955u97b5wLqXuLnWqoeQ3POhwasFh3/ow2lkzjjIdU47jkYJEk6A0dNgYiBuDg57/KN5yS2Px/QOSV+2nYEzPgSUHGyZacrHVkj/ZVyZ+ni7Iyf/QkNTfvPGxqmZtX6cq095jgdG1ELgYsTdr'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 3.4',
+        message: 'bMZBtrYeb5Y5dNrSOpATKE7x',
+        seed: 'bil59S1oFKV9g7CQBUiI8RmluaNuKXn1LWgUpX2DsJA=',
+        encrypted: 'AalUnNYX91mP0FrqphpfhU22832WgnjDNRU1pkpSrd5eD7t7Q1YhYE+pKds6glA8i1AE/li216hJs2IbCJMddyaXrDzT8V9/UfIUaSkLfcRYBrTn9DEDOTjY1Xnn38poLOFykpZbAz5hdbOh0qG39qFgl5QZG0+aTBd1tmlMZBfO'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 3.5',
+        message: '31FRgyth9PJYkftBcvMo0u3fg3H/z9vpl5OSlfMOymkYAXz9oRU796avh1kyIw==',
+        seed: 'LXYL/jjFneNM3IuMeKOOZihKLSctdgv+OMWd40zci4w=',
+        encrypted: 'AGgQQYTuy9dW6e3SwV5UFYbEtqQD7TDtxcrMYOmYlTPgTwIFpo4GbQbtgD9BMFAW7a1lIzLxKEld49jH6m95Xgtq/BAVFl/gXin5MMbiZfRTOl38miBTg5a6IS9w6tcrWIBeY5Z5n4iCuUqF9r/m9TqvxWF0aMP2VGVKZn+LHMVj'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 3.6',
+        message: 'PDutiTxUSm1SCrAiMZGIyNUEt6eIuFCQO4WXLqoYVS4RNKetYJiCYlT/erZys9jrMVj6xtTLrvE=',
+        seed: '8XR3nF/Tz+AHuty3o2ybVb/Pvw7xdHecX9PP4Ae63Lc=',
+        encrypted: 'Aps8BQrRkPPwpNIjHw3NBznsDvp1hIHmlbG5wRERr9+Ar4ervO2GA/MMUVNijdZEtFnCGjbLwpM6RKzCk96jJX1bIgzq7hnmIzwKmq2Ue4qqO29rQL39jpCS87BBo/YKMbkYsPc2yYSDMBMOe9VDG63pvDgFGrlk/3Yfz1km3+/Y'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha256', examples);
+
+      // Example 4: A 1027-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'BRJAtswABPpI0BNGccB4x8jew7Pi8lvCVkRnM52ziFPQa4XupbLeNTv/QqwuRryX+uaslhjalTelyPVTweNXYlmR1hCNzXiF+zolQT9T78rZSMs1zZua6cHGdibRE9V93kxb6na7W7felsANBzculoWm11z50jn6FI1wkxtfP7A5';
+      exponent = 'AQAB';
+      d = 'BBH/yjt8penpvn/jioUQXjU4ltsFxXlq7NKnJRYes2UchimpuGK5BNewx7N/jLWhwrVAAQGKAKHrLK/k7k6UksNIvCvtq0ueu/Bk6O/zIrkAn47sZTkF9A34ijzcSdRWf3VifUGspiQSm0agt8aY5eZfK3uhAsdJoQE1tlQNBAE=';
+      p = 'AnRYwZ7BY2kZ5zbJryXWCaUbj1YdGca/aUPdHuGriko/IyEAvUC4jezGuiNVSLbveSoRyd6CPQp5IscJW266VwE=';
+      q = 'AhDumzOrYXFuJ9JRvUZfSzWhojLi2gCQHClL8iNQzkkNCZ9kK1N1YS22O6HyA4ZJK/BNNLPCK865CdE0QbU7UTk=';
+      dP = 'OfoCi4JuiMESG3UKiyQvqaNcW2a9/R+mN9PMSKhKT0V6GU53J+Sfe8xuWlpBJlf8RwxzIuvDdBbvRYwweowJAQ==';
+      dQ = 'AV2ZqEGVlDl5+p4b4sPBtp9DL0b9A+R9W++7v9ax0Tcdg++zMKPgIJQrL+0RXl0CviT9kskBnRzs1t1M8eVMyJk=';
+      qInv = 'AfC3AVFws/XkIiO6MDAcQabYfLtw4wy308Z9JUc9sfbL8D4/kSbj6XloJ5qGWywrQmUkz8UqaD0x7TDrmEvkEro=';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 4.1',
+        message: 'SoZglTTuQ0psvKP36WLnbUVeMmTBn2Bfbl/2E3xlxW1/s0TNUryTN089FmyfDG+cUGutGTMJctI=',
+        seed: 'HKwZzpk971X5ggP2hSiWyVzMofMcrBnOmT3vVfmCA/Y=',
+        encrypted: 'AooWJVOiXRikAgxb8XW7nkDMKIcrCgZNTV0sY352+QatjTq4go6/DtieHvIgUgb/QYBYlOPOZkdiMWXtOFdapIMRFraGeq4mKhEVmSM8G5mpVgc62nVR0jX49AXeuw7kMGxnKTV4whJanPYYQRoOb0L4Mf+8uJ5QdqBE03Ohupsp'
+    /* FIXME: could not convert 4.2', to SHA-256, message too long
+      }, {
+        title: 'RSAES-OAEP Encryption Example 4.2',
+        message: 'sK3E8/4R2lnOmSdz2QWZQ8AwRkl+6dn5oG3xFm20bZj1jSfsB0wC7ubL4kSci5/FCAxcP0QzCSUS7EaqeTdDyA==',
+        seed: '9UXViXWF49txqgy42nbFHQMq6WM=',
+        encrypted: 'AJe2mMYWVkWzA0hvv1oqRHnA7oWIm1QabwuFjWtll7E7hU60+DmvAzmagNeb2mV4yEH5DWRXFbKA03FDmS3RhsgLlJt3XK6XNw5OyXRDE2xtpITpcP/bEyOiCEeCHTsYOB3hO7SarqZlMMSkuCcfPq4XLNNm4H5mNvEBnSoortFe'
+    */
+      }, {
+        title: 'RSAES-OAEP Encryption Example 4.3',
+        message: 'v21C5wFwex0CBrDItFoccmQf8SiJIZqCveqWW155qWsNAWPtnVeOya2iDy+88eo8QInYNBm6gbDGDzYG2pk=',
+        seed: 'rZl/7vcw1up75g0NxS5y6sv90nWtmX/u9zDW6nvmDQ0=',
+        encrypted: 'AtYYko32Vmzn3ZtrsDQH9Mw/cSQk9pePdwQZJ6my7gYXWYpBdhbEN/fH7LMmvjtHnKLLTDazfF1HT0tTG6E+TY002cy+fMUvdRn0rfmFkNeHeqVOABP2EmI4eXFCBbbIlpshLxbA3vDTzPPZZqwMN+KPG4O11wmS9DcyHYtpsIOU'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 4.4',
+        message: '+y7xEvXnZuuUAZKXk0eU974vb8HFjg==',
+        seed: 'E2RU31cw9zyAen5A2MGjEqxbndMTZFTfVzD3PIB6fkA=',
+        encrypted: 'AZX8z/njjTP/ApNNF+BNGUjlczSK/7iKULnZhiAKo4LJ0XaTzTtvL9jacr+OkRxSPUCpQeK36wXdi9qjsl3SO9D7APyzN1nNE5Nu5YstiAfEMVNpdRYGdgpUasEZ4jshBRGXYW28uTMcFWRtzrlol9Lc7IhIkldTXZsR9zg11KFn'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 4.5',
+        message: 'KMzUR7uehRZtq7nlt9GtrcS5058gTpbV5EDOmtkovBwihA==',
+        seed: 'vKgFf4JLLqJX8oYUB+72PTMghoG8qAV/gksuolfyhhQ=',
+        encrypted: 'A8GIo5X2qOS6MdKjYJg+h3hi2endxxeb3F5A8v+MbC7/8WbBJnzOvKLb6YMukOfAqutJiGGzdPQM9fopdhbRwS/Ovw4ksvmNBVM+Q26CFPqvdhV8P0WxmeYTxGFGrLgma+fwxpe7L6mj300Jq6Y/5kfTEJSXNdKuLRn0JsIg8LSD'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 4.6',
+        message: '8iJCdR7GsQ==',
+        seed: 'Ln4eF/ZHtd3QM+FUcvkPaBLzrE4ufh4X9ke13dAz4VQ=',
+        encrypted: 'AM9cnO14EVBadGQYTnkkbm/vYwqmnYvnAutrc4bZR3XC0DNhhuUlzFosUSmaC1LrjKcFfcqZOwzlev5uZycR7tUlLihC6lf4o0khAjUb03Dj+ubNsDKNCOA6vP63N3p6jSVIak7EPu0KtBLuNpIyW5mdkMuwssV7CNLOQ6qG7zxZ'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha256', examples);
+
+      // Example 5: A 1028-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'Cq3z+cEl5diR8xrESOmT3v5YD4ArRfnX8iulAh6cR1drWh5oAxup205tq+TZah1vPSZyaM/0CABfEY78rbmYiNHCNEZxZrKiuEmgWoicBgrA2gxfrotV8wm6YucDdC+gMm8tELARAhSJ/0l3cBkNiV/Tn1IpPDnv1zppi9q58Q7Z';
+      exponent = 'AQAB';
+      d = 'AlbrTLpwZ/LSvlQNzf9FgqNrfTHRyQmbshS3mEhGaiaPgPWKSawEwONkiTSgIGwEU3wZsjZkOmCCcyFE33X6IXWI95RoK+iRaCdtxybFwMvbhNMbvybQpDr0lXF/fVKKz+40FWH2/zyuBcV4+EcNloL5wNBy+fYGi1bViA9oK+LF';
+      p = 'A7DTli9tF1Scv8oRKUNI3PDn45+MK8aCTyFktgbWh4YNrh5jI5PP7fUTIoIpBp4vYOSs1+YzpDYGP4I4X0iZNwc=';
+      q = 'AuTDLi9Rcmm3ByMJ8AwOMTZffOKLI2uCkS3yOavzlXLPDtYEsCmC5TVkxS1qBTl95cBSov3cFB73GJg2NGrrMx8=';
+      dP = 'AehLEZ0lFh+mewAlalvZtkXSsjLssFsBUYACmohiKtw/CbOurN5hYat83iLCrSbneX31TgcsvTsmc4ALPkM429U=';
+      dQ = '65CqGkATW0zqBxl87ciBm+Hny/8lR2YhFvRlpKn0h6sS87pP7xOCImWmUpfZi3ve2TcuP/6Bo4s+lgD+0FV1Tw==';
+      qInv = 'AS9/gTj5QEBi64WkKSRSCzj1u4hqAZb0i7jc6mD9kswCfxjngVijSlxdX4YKD2wEBxp9ATEsBlBi8etIt50cg8s=';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 5.1',
+        message: 'r3GpAeOmHTEy8Pwf20dPnqZXklf/wk0WQXAUWz296A==',
+        seed: 'RMkuKD93uUmcYD2WNmDIfS+TlGFEyS4oP3e5SZxgPZY=',
+        encrypted: 'BOGyBDRo+2G7OC79yDEzJwwLMPuhIduDVaaBdb5svHj/ZAkVlyGVnH0j+ECliT42Nhvp4kZts+9cJ0W+ui7Q9KXbjmX033MpxrvSV1Ik//kHhX6xTn51UGpaOTiqofjM3QTTi9DVzRtAarWd/c8oAldrGok1vs+tJEDbcA5KvZz7'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 5.2',
+        message: 'o7hEoII5qKxBYFrxemz9pNNQE2WFkDpBenkmh2BRmktKwzA+xz8Ph8+zI5k=',
+        seed: 'yyj1hgZZ/O7knD7q/OYlpwgDvTLLKPWGBln87uScPuo=',
+        encrypted: 'AeleoSbRCOqBTpyWGLCrZ2G3mfjCYzMvupBy+q+sOKAfBzaLCjCehEUmIZFhe+CvtmVvyKGFqOwHUWgvks9vFU7Gfi580aZm7d4FtaGGVHBO6Q32/6IS7a+KSk7L6rPWwBTI+kyxb5SW12HTEowheKkFTda06tU0l4Ji45xP2tyh'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 5.3',
+        message: 'MIsOy9LHbLd/xvcMXt0jP9LyCSnWKfAmlTu2Ko9KOjFL3hld6FtfgW2iqrB00my2rN3zI647nGeKw88S+93n',
+        seed: 'IoX0DXcEgvmp76LHLLOsVXFtwMoihfQNdwSC+anvosc=',
+        encrypted: 'Ci3TBMV4P0o59ap6Wztb9LQHfJzYSOAaYaiXjk85Q9FYhAREaeS5YXhegKbbphMIS5i1SYJShmwpYu/t8SGHiX/72v6NnRgafDKzttROuF/HJoFkTBKH6C9NKke+mxoDy/YVZ9qYzFY6PwzB4pTDwku9s5Ha4DmRBlFdA/z713a4'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 5.4',
+        message: 'FcW57hGF',
+        seed: 'SfpF06eN0Q39V3OZ0esAr37tVRNJ+kXTp43RDf1Xc5k=',
+        encrypted: 'AcMQiclY0MMdT9K4kPqZ7JYHTaSolc8B3huHcQ4U5mG11/9XjzWjTLha8Liy0w909aaPbaB7+ZQTebg7x3F4yeWFRmnAJMaIFGBW/oA952mEaJ+FR2HO0xfRPzCRCaaU7cyOxy0gnR8d9FMunt9fhbffM9TvOfR6YDE5Duz6Jg0W'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 5.5',
+        message: 'IQJuaADH+nKPyqug0ZauKNeirE/9irznlPCYX2DIpnNydzZdP+oR24kjogKa',
+        seed: '8Ch0EyNMxQNHJKCUxFhrh6/xM/zwKHQTI0zFA0ckoJQ=',
+        encrypted: 'A/gvyZ/MNHUG3JGcisvAw/h1bhviZsqIsEM5+gBpLfj7d8iq28yZa6z5cnS4cJWywHxyQMgt9BAd37im/f5WcIcB+TZS9uegFSdgaetpYf4ft/1wMlcdc1ReCrTrCKPFHLLczeifyrnJSVvQD84kQY21b4fW9uLbSiGO0Ly94il1'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 5.6',
+        message: 'VB43totsiHK4TAI=',
+        seed: '2fukXJbyHm4m0p6yzctlhb6cs0HZ+6RclvIebibSnrI=',
+        encrypted: 'AWmTYpBHOqRBGy1h5mF88hMmBVNLN++kXAqQr4PKszqorigNQZbvwbOdWYNLsXydyvKi55ds8tTvXf4rRBswyuNmbtT0t2FVCTnTjNzA1cMSahInbdKfL/1wib3CjyQmC0TbbIa3kkAdXkiYytSafDxwNyan1OWtJcMLkJ6l8WRm'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha256', examples);
+
+      // Example 6: A 1029-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'ErF/ba0uzRn/RtwT94YPCeDgz7Z3s4pSWSMFzq8CLBZtuQ0ErCnjP33RLZ+vZuCBa7Y+rSZ8x9RsF8N74hS8oqItcjpk5EQHQ2tvyWVymu/CVU83bNXc6mgpN4CmK/OdAClIWhYLu55dwJctIaUE9S5e4CiqQWMy9RCy6c/19yKv';
+      exponent = 'AQAB';
+      d = 'ApXso1YGGDaVWc7NMDqpz9r8HZ8GlZ33X/75KaqJaWG80ZDcaZftp/WWPnJNB7TcEfMGXlrpfZaDURIoC5CEuxTyoh69ToidQbnEEy7BlW/KuLsv7QV1iEk2Uixf99MyYZBIJOfK3uTguzctJFfPeOK9EoYij/g/EHMc5jyQz/P5';
+      p = 'BKbOi3NY36ab3PdCYXAFr7U4X186WKJO90oiqMBct8w469TMnZqdeJpizQ9g8MuUHTQjyWku+k/jrf8pDEdJo4s=';
+      q = 'BATJqAM3H+20xb4588ALAJ5eCKY74eQANc2spQEcxwHPfuvLmfD/4Xz9Ckv3vv0t1TaslG23l/28Sr6PKTSbke0=';
+      dP = 'A5Ycj3YKor1RVMeq/XciWzus0BOa57WUjqMxH8zYb7lcda+nZyhLmy3lWVcvFdjQRMfrg6G+X63yzDd8DYR1KUs=';
+      dQ = 'AiGX4GZ0IZaqvAP6L+605wsVy3h9YXrNMbt1x7wjStcG98SNIYLR8P+cIo3PQZZ7bAum0sCtEQobhXgx7CReLLE=';
+      qInv = 'BAHEwMU9RdvbXp2W0P7PQnXfCXS8Sgc2tKdMMmkFPvtoas4kBuIsngWN20rlQGJ64v2wgmHo5+S8vJlNqvowXEU=';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 6.1',
+        message: 'QEbKi6ozR8on9J4NgfnMHXG+m6UX1A==',
+        seed: '3Q9s/kFeiOWkaaUfu6bf1ArbQ4TdD2z+QV6I5aRppR8=',
+        encrypted: 'C3d3hdR81ybq+Wuf6QUfy2KHVuQjrsvVH2HuE2LbJT2o2ZPdrDHIoGphdGj+GWNTrcV/d8iPLJlZ0CR3O2e2b7wLUVPMorv1HidYA8B8eJxkg5FIsPuK836LchnGqQlE7ObiWOjSuIw4lZ/ULCfOYejelr6PJXSxWgQUlV78sbvP'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 6.2',
+        message: 'XMcsYCMd8Ds9QPm1eTG8MRCflyUn8osZ50gMcojLPJKyJRIhTkvmyRR5Ldq99X+qiqc=',
+        seed: 'jRS9lGoTURSPXK4u2aDGU+hevYWNFL2UahNRFI9cri4=',
+        encrypted: 'DXAHBh/uWFjxl/kIwrzm0MXeHNH5MSmoPc0mjn00UcCUFmOwTQipPmLmephH+rNOOfCQVvwP5wysU3/w2hjmk/rl6Jb4qNc+KqDiij7fKSKhPGTvY3aiXZ2LflnJ3yv4LdT9KvvWsZrHEWfsEG+fQZW4c1OMEpOMC4N44nc88yRm'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 6.3',
+        message: 'sg5lEwMJL0vMtDBwwPhtIwSTYu2WZC/FYywn20pS49gx8qsGiyOxSYecAC9r8/7ul1kRElYs',
+        seed: 'bAdbxFUg8WXAv16kxd8ZG8nvDkRsB1vEVSDxZcC/XqQ=',
+        encrypted: 'AMe1ZPYbk4lABLKDLhwJMM4AfK46Jyilp/vQ9M921AamJzanoNGdlj6ZEFkbIO68hc/Wp4Qr43iWtjcasgpLw2NS0vroRi91VI5k9BZgXtgNG7Z9FBOtPjM61Um2PWSFpAyfaZS7zoJlfRKciEa+XUKa4VGly4fYSXXAbUJV2YHc'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 6.4',
+        message: 'aE4wOMXAQfc=',
+        seed: 'O7w71mN9/hKEaQECm/WwwHEDQ5w7vDvWY33+EoRpAQI=',
+        encrypted: 'AJS/vpVJKJuLwnnzENVQChT5MCBa0mLxw/a9nt+6Zj4FL8nucIl7scjXSOkwBDPcyCWr7gqdtjjZ9z6RCQv0HfjmVKI2M6AxI2MYuzwftIQldbhCRqo8AlyK3XKjfcK+Rzvii53W8Xw4Obbsv9OCLnCrrbK8aO3XKgrHPmDthH7x'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 6.5',
+        message: 'MkiMsmLQQdbk3TX5h788ppbbHwasKaRGkw==',
+        seed: 'tGtBiT6L7zJvZ1k4OoMHHa5/yry0a0GJPovvMm9nWTg=',
+        encrypted: 'CmNUKnNQco5hWHxCISdwN5M7LbL7YJ0u7bfH82LulE32VdATd3vcJmRiFtcczNNudMlHVhl6/ZsDVY1zymLrK2kLIYWeG9Iag3rQ5xhjLAdpMYBBuwjrJ8Oqc4+2qH57bBveynuE5xRpd9p+CkkiRP7x7g4B/iAwrmFxPtrxV/q/'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 6.6',
+        message: 'ULoUvoRicgJ5wwa6',
+        seed: 'CiQDMSpB49UvBg+8E6Z95c92CacKJAMxKkHj1S8GD7w=',
+        encrypted: 'DpQAu4uQ4zbkpP/f698+a5f3MhAXCi3QTcP7vXmQVlkH0CFlCnDESNG36Jk2ybe3VmzE2deBHBKI9a5cHUzM9Lsa/AoxnbD5qd2fJt9k19dSRtDWZUR/Bn/AdVHwstzsX/vRLe6qOk9Kf01OZcvKrmlWh2IBLs8/6sEJhBWXNAKj'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha256', examples);
+
+      // Example 7: A 1030-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'MRF58Lz8m508oxXQDvMNe906LPrpkRv+3LlIs6R4LQcytqtEqkvwN0GmRNwBvsPmmwGgM+Z12KzXxJJcaxrsMRkFHf2Jdi0hXUVHX/y1n5CBSGI/NxdxVvauht16fF9D3B4fkIJUBYooSl8GwAIXk6h/GsX+/33K7mnF5Ro3ieNz';
+      exponent = 'AQAB';
+      d = 'Bwz8/y/rgnbidDLEXf7kj0m3kX1lMOHwyjRg8y4CdhdEh8VuIqRdJQDXd1SVIZ19Flqc872Swyr5qY2NycwpaACtyUoKVPtA80KRv4TujqErbxCTWcbTVCpQ+cdn9c//BaaBwuZW+3fKqttL6UaNirzU35j1jobSBT+hNJ90jiGx';
+      p = 'B0kmLBEc1HDsJWbms3MvwJMpRpqhkHHTucAZBlFMbx0muqFL6rCXHIt+YRpPeQCdb+p3aSjKJShbDeNkPRo/jHE=';
+      q = 'BrweUOlsAr9jbp7qi4mbvr92Ud533UdMPpvCO62BgrYZBMfZffvr+x4AEIh4tuZ+QVOR1nlCwrK/m0Q1+IsMsCM=';
+      dP = 'A7x+p/CqsUOrxs6LlxGGNqMBcuTP4CyPoN2jt7qvkPgJKYKYVSX0iL38tL1ybiJjmsZKMJKrf/y/HVM0z6ULW/E=';
+      dQ = 'AmKmqinCo8Z9xTRsBjga/Zh6o8yTz7/s9U/dn514fX9ZpSPTmJedoTei9jgf6UgB98lNohUY3DTLQIcMRpeZStk=';
+      qInv = 'ZJ1MF7buFyHnctA4mlWcPTzflVDUV8RrA3t0ZBsdUhZq+KITyDliBs37pEIvGNb2Hby10hTJcb9IKuuXanNwwg==';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 7.1',
+        message: 'R6rpCQ==',
+        seed: 'Q90JoH/0ysccqkYy7l4cHa7kzY9D3Qmgf/TKxxyqRjI=',
+        encrypted: 'CdXefX8LEW8SqnT1ly7/dvScQdke1rrSIBF4NcFO/G+jg0u7yjsqLLfTa8voI44Ue3W6lVuj5SkVYaP9i7VPmCWA4nFfeleuy23gbHylm4gokcCmzcAm2RLfPQYPnxIb3hoQ2C3wXo/aWoLIMFPYzI19g5uY90XMEchAci3FVC/a'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 7.2',
+        message: 'HZsuIiPZvBO/ufFiznNdtIunxo9oIqChp7auFlg05w==',
+        seed: 'Opw87HuE+b063svGc+yZ1UsivJs6nDzse4T5vTrey8Y=',
+        encrypted: 'DDar5/aikhAVropPgT3SVzSMRtdS9sEmVBEqg9my/3na0Okz51EcAy436TOZVsM0exezvKYsVbDQhtOM0Mn9r6oyBsqzUR4lx6Gt2rYDYC4X1aMsJSVcQs9pDqeAWfIAmDIIQH/3IN2uJ6u4Xl2+gFCpp8RP0F//Rj2llnEsnRsl'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 7.3',
+        message: '2Xb8',
+        seed: 'dqdeW2FXpVbPiIS7LkXCk91UXPV2p15bYVelVs+IhLs=',
+        encrypted: 'GpTkYrRFNyD9Jw1Pc1TSPSfc9Yb8k4Fw1l4kCwqPodlAboKMJe+yuXoGgVeB7Jb7JTQklGpQc1keZUzUUVZO0Q4qYUelFFe5lWM2uhq21VCbvcchrMTP6Wjts05hVgJHklLKF5cOtBGpQC0FlkwCYoXUAk//wejNycM/ZXw+ozkB'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 7.4',
+        message: '1HOGI98iOqQ4Q9+EZ1NMQdAT4MgDxiTiY2ZrI5veQKXymuuN5549qmHdA3D0m9SwE4NLmCEq72scXuNzs8s=',
+        seed: 'eGYxSmrW8rJQo1lB2yj1hktYWFl4ZjFKatbyslCjWUE=',
+        encrypted: 'G5GJEPP4ifUCg+3OHEq41DFvaucXwgdSGyuDX6/yQ1+e30d0OIjIFv4JTUXv6Oi8/uADg+EN5Ug+lEyf0RNSS4wfKgRfAaXK6x1U8dh48g/bED27ZCZ+MjhAkUcjMO0h4m3nNfLxAju7nxO2cJzNI9n1TBCMngJBco0zzhOvMZaN'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 7.5',
+        message: 'u0cjHKXqHTrUbJk0XZqKYQ==',
+        seed: 'shZu1HLVjbEMqyxrAAzM8Qp9xQmyFm7UctWNsQyrLGs=',
+        encrypted: 'HebBc/6i18c2FbG7ibWuxyQgtiN1uhtxyNsXw1Kuz8zo7RkBkt5JZEwucKyXFSwI6drZlK6QaqCRZwPQsdc2wnZlQzbkilVf1TiACqzDdpKX5i+SbCTUsOyGETV3vtxFe7/SatEKseFSLEWkIfZxAFcisIs5hWmLJdqfWQeYuMrK'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 7.6',
+        message: 'IYSCcJXTXD+G9gDo5ZdUATKW',
+        seed: 'Umc73iyhZsKqRhMawdyAjWfX07FSZzveLKFmwqpGExo=',
+        encrypted: 'DX+W3vsdnJfe63BVUFYgCAG1VmTqG/DbQ4nZgWTUGHhuijUshLtz07dHar21GJ9Ory8QQPX67PgKGnBMp0fJBnqKO3boMOEcc52HEnQxOWIW2h2rgmDtnaYtvK85pddXzhbuXkXg4DDnoMy+4XzgbLfArK12deGB0wruBbQyv3Ar'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha256', examples);
+
+      // Example 8: A 1031-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'W98OMNMh3aUUf4gkCPppGVSA34+A0/bov1gYUE82QnypsfVUC5xlqPaXTPhEeiRNkoAgG7Sfy75jeNGUTNIn4jD5bj0Q+Bnc7ydsZKALKktnAefQHeX6veOx6aDfgvRjE1nNImaWR/uxcXJGE07XtJfP/73EK1nHOpbtkBZiEt/3';
+      exponent = 'AQAB';
+      d = 'D30enlqqJf0T5KBmOuFE4NFfXNGLzbCd8sx+ZOPF6RWtYmRTBBYdCYxxW7eri9AdB+rz/tfH7QivKopi70SrFrMg4Ur3Kkj5av4mKgrkz2XmNekQeQzU7lzqdopLJjn35vZ3s/C7a+MrdXR9iQkDbwJk9Y1AHNuhMXFhV6dez2Mx';
+      p = 'CgLvhEjZ+ti70NAEyMKql1HvlyHBsNAyNqVLDflHy67VolXuno4g1JHqFyP+CUcEqXYuiK/RbrtZlEEsqWbcT58=';
+      q = 'CS02Ln7ToL/Z6f0ObAMBtt8pFZz1DMg7mwz01u6nGmHgArRuCuny3mLSW110UtSYuByaxvxYWT1MP7T11y37sKk=';
+      dP = 'B8cUEK8QOWLbNnQE43roULqk6cKd2SFFgVKUpnx9HG3tJjqgMKm2M65QMD4UA10a8BQSPrpoeCAwjY68hbaVfX0=';
+      dQ = 'rix1OAwCwBatBYkbMwHeiB8orhFxGCtrLIO+p8UV7KnKKYx7HKtYF6WXBo/IUGDeTaigFjeKrkPH+We8w3kEuQ==';
+      qInv = 'BZjRBZ462k9jIHUsCdgF/30fGuDQF67u6c76DX3X/3deRLV4Mi9kBdYhHaGVGWZqqH/cTNjIj2tuPWfpYdy7o9A=';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 8.1',
+        message: 'BQt1Xl5ogPe56daSp0w3quRJsxv+pt7/g3R6iX9sLIJbsa2/hQo8lplLXeWzPLx9SheROnln',
+        seed: 'dwb/yh7PsevuKlXlxuJM0nl6QSV3Bv/KHs+x6+4qVeU=',
+        encrypted: 'DZZvGJ61GU6OOkaPl2t8iLNAB1VwLjl3RKd/tcu19Vz9j68fjCFBQvASq9FK6Sul/N6sXIVsi4ypx/1m77bErYJqiGwkE8sQz/g4ViwQmeCvpfbCoq00B5LxklezvhnM5OeSxFtO/8AtYimLrJ3sUmDYk7xkDI20/Lb8/pyOFsjH'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 8.2',
+        message: 'TraNzZPKmxnfERvUNgj1VwJv5KodXPrCJ6PrWrlUjBigbd7SP4GCWYay/NcRCezvfv+Ihz8HXCqgxGn2nJK8',
+        seed: 'o3F9oUO03P+8dCZlqPqVBYVUg0OjcX2hQ7Tc/7x0JmU=',
+        encrypted: 'DwsnkHG2jNLgSU4LEvbkOuSaQ9+br9t3dwen8KDGmLkKVJgWGu+TNxyyo2gsBCw7S4eqEFrl49ENEhMehdjrHCBLrEBrhbHxgncKrwIXmcjX1DOCrQXEfbT4keig8TaXkasow5qby9Ded6MWcLu1dZnXPfiuiXaOYajMGJ1D3/y7'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 8.3',
+        message: 'hgSsVjKMGrWtkXhh',
+        seed: '7gYgkHPMoCa7Jk5Rhb+MaLdzn4buBiCQc8ygJrsmTlE=',
+        encrypted: 'PAKF3K/lSKcZKWQDr56LmmVqSltcaKEfS7G6+rwG239qszt8eKG6fMYJsP4h7ZfXyV1zuIZXTVhXgiRQbA9os0AhkWiMJJouhsAn60R20BOLQOtQxlXxVOvUMPGuG5EP2O+nTI0VCXky5kHNJdfJolSW+pJLVdSu4mX9Ooga1CSx'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 8.4',
+        message: '/dpfv27DYanZpKxoryFqBob0OLHg5cNrlV904QfznA3dzA==',
+        seed: 'mQrVc9xIqXMjW22CVDYY8ulVEF2ZCtVz3EipcyNbbYI=',
+        encrypted: 'LcCLDDj2S5ZOKwpvobOk6rfBWMBbxs3eWR+Edk3lKaoEAFFD5sQv0AaIs3r7yI8sOir9HvS6GKf+jc9t31zIDCIJc3sKVyrNZfEeUFSvihjbPZDo6IaZ8Jau8woE2p1z7n9rG+cbMwKuILRPSEN4hE0QSA/qz0wcye6bjb6NbK20'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 8.5',
+        message: 'Sl9JFL7iXePGk0HeBw==',
+        seed: '7MY7KPB1byL1Ksjm7BJRpuwwRxjsxjso8HVvIvUqyOY=',
+        encrypted: 'U+PBTMw6peNHnFQ3pwix/KvVeWu1nSKQGcr9QPpYNgHhuFZK6ARR7XhKxFtDoUMP+iVTXprcow4lr1Uaw4PnEx+cPe0Khl15R8GLiuh5Vm9p3lTPW1u58iP2Oa81pQZTB5AuFiD0fnFZsl+BYfjDVah3cMIu83KOBRPOdLY0j8iq'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 8.6',
+        message: 'jgfWb3uICnJWOrzT81CSvDNAn7f4jyRyvg==',
+        seed: 'OSXHGzYtQKCm3kIUVXm6Hn3UWfw5JccbNi1AoKbeQhQ=',
+        encrypted: 'WK9hbyje7E0PLeXtWaJxqD4cFkdL5x4vawlKJSOO1OKyZ6uaY8vMYBhBO47xRIqtab5Ul5UGCwdvwPR69PpARsiiSfJHVavkihXixHGgZViGTMU/7J7ftSiNT9hAwrj4JL4f1+8RhTp6WKRzsXAEKpvLK0TrzQL3ioF3QtTsiu/a'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha256', examples);
+
+      // Example 9: A 1536-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'zyzUHjTKOnKOpcuK/2TDbSe971Nk4zb9aNMSPFoZaowocBPoU9UVbVjRUZVFIPtPbXsXq7aBd2WQnFdhGWWdkCsZBu2KKxDBVcJNEkUo2rnurjeb6sZuSkEXhty4/QBi68Aw3hIZoEwqjBt90xMeTWtsruLjGl7UGsFQmy7x7iqxg2S+VoypQcJezIT/nWQ7XsGqrhAqINc/R5t4D9bakQdSEtnqwDoGdNiZ66LkMfTES2Fba6IjK9SzO67XPWJd';
+      exponent = 'AQAB';
+      d = 'GYwUHiNxWpK8z2oRmlvBE4lGjSgR9UjXJ+F7SrDrmG1vIR77U7cffMvqh+5px17mFQCMUzLetSvzkKvfv+N9cgU2gVmyY4wd4ybiHSIlHw+1hIs78VAF0qdDMPCv6RbuYszBNE0dg6cJ5gZ2JzhA9/N3QkpeCk2nXwGzH/doGc+cv90hUkPDkXwD7zgZkxLlZ7O/eu06tFfzce+KFCP0W2jG4oLsERu6KDO5h/1p+tg7wbjGE8Xh6hbBHtEl6n7B';
+      p = '/I1sBL7E65qBksp5AMvlNuLotRnezzOyRZeYxpCd9PF2230jGQ/HK4hlpxiviV8bzZFFKYAnQjtgXnCkfPWDkKjD6I/IxI6LMuPaIQ374+iB6lZ0tqNIwh6T+eVepl79';
+      q = '0gDUXniKrOpgakAdBGD4fdXBAn4S3BoNdYbok52c94m0D1GsBEKWHefSHMIeBcgxVcHyqpGTOHz9+VbLSNFTuicEBvm7ulN9SYfZ4vmULXoUy//+p0/s3ako0j4ln17h';
+      dP = '2xaAL3mi8NRfNY1p/TPkS4H66ChiLpOlQlPpl9AbB0N1naDoErSqTmyL6rIyjVQxlVpBimf/JqjFyAel2jVOBe8xzIz3WPRjcylQsD4mVyb7lOOdalcqJiRKsI23V1Kt';
+      dQ = 'oKMXz+ffFCP4em3uhFH04rSmflSX8ptPHk6DC5+t2UARZwJvVZblo5yXgX4PXxbifhnsmQLgHX6m+5qjx2Cv7h44G2neasnAdYWgatnEugC/dcitL6iYpHnoCuKU/tKh';
+      qInv = 'CyHzNcNTNC60TDqiREV4DC1lW5QBdMrjjHyKTmSTwLqf0wN0gmewg7mnpsth5C2zYrjJiW23Bk4CrVrmFYfaFbRknJBZSQn+s328tlS+tyaOyAHlqLSqORG+vYhULwW+';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 9.1',
+        message: '9zX9VbqSWSw7Urj5xPaaqhy++P6IrdCVWVQSRn+c9OwLiWxZ7aFiEOdUnIq7EM28IaEuyba1uP0vEDmetg==',
+        seed: 'jsll8TSj7Jkx6SocoNyBadXqcFyOyWXxNKPsmTHpKhw=',
+        encrypted: 'kuBqApUSIPP0yfNvL0I57K2hReD8CcPhiYZFlPPmdM0cVFQHvdPMjQ2GcEekoBMk2+JR2H3IY6QF0JcANECuoepAuEvks/XolStfJNyUVUO3vLbWGlA1JOOSPiWElIdM0hmLN5In0DizqQit7R0mzH3Y1vUGPBhzOnNgNVQgrWVvqJugjG1SPY7LaZxIjhnz3O/8EkCpxLWcyWkFLX+ujnxIKxAqjmvEztiwLLlcWbJKILOy7KE1lctyh58wYP6e'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 9.2',
+        message: 'gbkGYFAVpjqr5C3fEeGXiRL1QEx0dLJtzj7Ugr+WHsyBi/QgxUZZ',
+        seed: '7LG4sl+lDNqwjlYEKGf0r1gm0WzssbiyX6UM2rCOVgQ=',
+        encrypted: 'iNEGT/cssEWuXX1C+SWyMK1hhUjRdNz9l8FeCBhftw8I5JUY1EDXPi2hUpESGxbJOjbyricua+QVvfP/UeoPfOxCcpRSoA3DlviB0ExCJTpCb2NSv9qXtw6Z7qEqk2YyQD7mAsGb2/Y3ug3KkKrF68MAxsWFV3tmL2NV2h+yfW6qz1dVAbAIUZeRuLIbaLdY9F7O4yPC68zkaX9NcTg0tOtnAJth9fMAOFX8sVbvKBgeOuVHV1A8HcAbmqkLMIyp'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 9.3',
+        message: '/TJkKd+biQ4JtUsYuPNPHiQ=',
+        seed: '6JuwMsbOYiy9tTvJRmAU6nf3d8Dom7Ayxs5iLL21O8k=',
+        encrypted: 'JiwfWprF58xVjVRR9B9r0mhomwU5IzkxXCZDgYJwYUcacmrz+KRLKMmtCMN7DLA2lOsfK+72mU+RLmhwfAAhBYmLGR8dLLstazb5xzU9wIM9u3jAl5iyyMLSo6wk/3SH0f7vC2bnFtMkhoHsd3VSTpzl5Q+SqX/4Q1JAMGWMMiHdyjCH+WaXNdTrboPEnPVtTcBGthkkYu8r/G0IkBR6OMPCZFgl/J4uiRTGCRbZx7UC02g6+qNMQY+ksygV6R8w'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 9.4',
+        message: '8UWbXwyS8BoPcjouVmJITY+MCiD8KdrWrNQ7tfPv/fThtj4H/f5mKNDXTKGb8taeSgq/htKTklp5Z3L4CI4=',
+        seed: 'YG87mcC5zNdx6qKeoOTIhPMYnMxgbzuZwLnM13Hqop4=',
+        encrypted: 'YXo+2y1QMWzjHkLtCW6DjdJ6fS5qdm+VHALYLFhG/dI1GmOwGOiOrFqesc5KPtWE73N5nJ680e6iYQYdFIsny6a4VH9mq/2Lr6qasMgM27znPzK8l6uQ1pTcDu1fJ4gCJABshzVEXzeTWx6OyKZsOFL8eXiNCwQpyfP9eH0tRjc+F75H3dnzX6AEVff4t0yKjDqp7aRMxFZGidcMJ6KetiNXUg1dhs/lHzItdQ7oMSUAgMnHYAvJDGqy5L4F8XXM'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 9.5',
+        message: 'U+boxynW+cMZ3TF+dLDbjkzMol88gwV0bhN6xjpj7zc557WVq7lujVXlT3vUGrQzN4/7kR0=',
+        seed: '/LxCFALp7KvGCCr6QLpfJlIshA78vEIUAunsq8YIKvo=',
+        encrypted: 'fwY+yhF2kyhotPKPlHEXcTOqVRG8Kg9bDJE/cSPUOoyVoiV0j57o9xpEYtZBuM5RanPUsTDcYNvorKqP5mbN81JV3SmEkIRTL7JoHGpJAMDHFjXBfpAwgUCPhfJ2+CUCIyOoPZqlt4w+K9l+WeFZYDatr0HC1NO+stbvWq358HRdX27TexTocG5OEB4l9gqhnUYD2JHNlGidsm0vzFQJoIMaH26x9Kgosg6tZQ0t3jdoeLbTCSxOMM9dDQjjK447'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 9.6',
+        message: 'trKOohmNDBAIvGQ=',
+        seed: 'I6reDh4Iu5uaeNIwKlL5whsuG6Ijqt4OHgi7m5p40jA=',
+        encrypted: 'PISd/61VECapJ7gfG4J2OroSl69kvIZD2uuqmiro3E4pmXfpdOW/q+1WCr574Pjsj/xrIUdgmNMAl8QjciO/nArYi0IFco1tCRLNZqMDGjzZifHIcDNCsvnKg/VRmkPrjXbndebLqMtw7taeVztYq1HKVAoGsdIvLkuhmsK0Iaesp+/8xka40c9hWwcXHsG+I7pevwFarxQQbuUjXSkZ2ObWgzgGSiGCw9QNUGpO0usATLSd0AFkeE+IM/KAwJCy'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha256', examples);
+
+      // Example 10: A 2048-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'rkXtVgHOxrjMBfgDk1xnTdvg11xMCf15UfxrDK7DE6jfOZcMUYv/ul7Wjz8NfyKkAp1BPxrgfk6+nkF3ziPn9UBLVp5O4b3PPB+wPvETgC1PhV65tRNLWnyAha3K5vovoUF+w3Y74XGwxit2Dt4jwSrZK5gIhMZB9aj6wmva1KAzgaIv4bdUiFCUyCUG1AGaU1ooav6ycbubpZLeGNz2AMKu6uVuAvfPefwUzzvcfNhP67v5UMqQMEsiGaeqBjrvosPBmA5WDNZK/neVhbYQdle5V4V+/eYBCYirfeQX/IjY84TE5ucsP5Q+DDHAxKXMNvh52KOsnX1Zhg6q2muDuw==';
+      exponent = 'AQAB';
+      d = 'BWsEIW/l81SsdyUKS2sMhSWoXFmwvYDFZFCiLV9DjllqMzqodeKR3UP0jLiLnV/A1Jn5/NHDl/mvwHDNnjmMjRnmHbfHQQprJnXfv100W4BNIBrdUC1c4t/LCRzpmXu+vlcwbzg+TViBA/A29+hdGTTRUqMj5KjbRR1vSlsbDxAswVDgL+7iuI3qStTBusyyTYQHLRTh0kpncfdAjuMFZPuG1Dk6NLzwt4hQHRkzA/E6IoSwAfD2Ser3kyjUrFxDCrRBSSCpRg7Rt7xA7GU+h20Jq8UJrkW1JRkBFqDCYQGEgphQnBw786SD5ydAVOFelwdQNumJ9gkygHtSV3UeeQ==';
+      p = '7PWuzR5VFf/6y9daKBbG6/SQGM37RjjhhdZqc5a2+AkPgBjH/ZXMNLhX3BfwzGUWuxNGq01YLK2te0EDNSOHtwM40IQEfJ2VObZJYgSz3W6kQkmSB77AH5ZCh/9jNsOYRlgzaEb1bkaGGIHBAjPSF2vxWl6W3ceAvIaKp30852k=';
+      q = 'vEbEZPxqxMp4Ow6wijyEG3cvfpsvKLq9WIroheGgxh5IWKD7JawpmZDzW+hRZMJZuhF1zdcZJwcTUYSZK2wpt0bdDSyr4UKDX30UjMFhUktKCZRtSLgoRz8c52tstohsNFwD4F9B1RtcOpCj8kBzx9dKT+JdnPIcdZYPP8OGMYM=';
+      dP = 'xzVkVx0A+xXQij3plXpQkV1xJulELaz0K8guhi5Wc/9qAI7U0uN0YX34nxehYLQ7f9qctra3QhhgmBX31FyiY8FZqjLSctEn+vS8jKLXc3jorrGbCtfaPLPeCucxSYD2K21LCoddHfA8G645zNgz72zX4tlSi/CE0flp55Tp9sE=';
+      dQ = 'Jlizf235wQML4dtoEX+p2H456itpO35tOi9wlHQT7sYULhj7jfy2rFRdfIagrUj4RXFw8O+ya8SBJsU+/R0WkgGY3CoRB9woLbaoDNMGI2C6P6E/cOQxL/GmzWuPxM2cXD2xfG1qVyEvc64p9hkye61ZsVOFhYW6Tii2CmKkXkk=';
+      qInv = 'bzhSazklCFU07z5BWoNu3ouGFYosfL/sywvYNDBP7Gg7qNT0ecQz1DQW5jJpYjzqEAd22Fr/QB0//2EO5lQRzjsTY9Y6lwnu3kJkfOpWFJPVRXCoecGGgs2XcQuWIF7DERfXO182Ij+t1ui6kN18DuYdROFjJR4gx/ZuswURfLg=';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 10.1',
+        message: 'i7pr+CpsD4bV8XVul5VocLCJU7BrTrIFvBaU7g==',
+        seed: 'R+GrcRn+5WyV7l6q2G9A0KpjvTNH4atxGf7lbJXuXqo=',
+        encrypted: 'iXCnHvFRO1zd7U4HnjDMCLRvnKZj6OVRMZv8VZCAyxdA1T4AUORzxWzAtAAA541iVjEs1n5MIrDkymBDk3cM1oha9XCGsXeazZpW2z2+4aeaM3mv/oz3QYfEGiet415sHNnikAQ9ZmYg2uzBNUOS90h0qRAWFdUV5Tyxo1HZ0slg37Ikvyu2d6tgWRAAjgiAGK7IzlU4muAfQ4GiLpvElfm+0vch7lhlrk7t5TErhEF7RWQe16lVBva7azIxlGyyrqOhYrmQ+6JQpmPnsmEKYpSxTUP2tLzoSH5e+Y0CSaD7ZB20PWILB+7PKRueJ23hlMYmnAgQBePWSUdsljXAgA=='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 10.2',
+        message: '5q0YHwU7WKkE8kV1EDc+Vw==',
+        seed: 'bRf1tMH/rDUdGVv3sJ0J8JpAec9tF/W0wf+sNR0ZW/c=',
+        encrypted: 'I3uBbIiYuvEYFA5OtRycm8zxMuuEoZMNRsPspeKZIGhcnQkqH8XEM8iJMeL6ZKA0hJm3jj4z1Xz7ra3tqMyTiw3vGKocjsYdXchK+ar3Atj/jXkdJLeIiqfTBA+orCKoPbrBXLllt4dqkhc3lbq0Z5lTBeh6caklDnmJGIMnxkiG3vON/uVpIR6LMBg+IudMCMOv2f++RpBhhrI8iJOsPbnebdMIrxviVaVxT22GUNehadT8WrHI/qKv+p1rCpD3AAyXAhJy7KKp1l+nPCy1IY1prey+YgBxCAnlHuHv2V7q1FZTRXMJe3iKubLeiX6SfAKU1sivpoqk5ntMSMgAfw=='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 10.3',
+        message: 'UQos9g6Gb6I0BVPJTqOfvCVjEeg+lEVLQSQ=',
+        seed: 'OFOHUU3szHx0DdjN+druSaHL/VQ4U4dRTezMfHQN2M0=',
+        encrypted: 'n3scq/IYyBWbaN4Xd+mKJ0bZQR10yiSYzdjV1D1K3xiH11Tvhbj59PdRQXflSxE1QMhxN0jp9/tsErIlXqSnBH2XsTX6glPwJmdgXj7gZ1Aj+wsl15PctCcZv0I/4imvBBOSEd5TRmag3oU7gmbpKQCSHi6Hp2z5H/xEHekrRZemX7Dwl6A8tzVhCBpPweKNpe34OLMrHcdyb0k/uyabEHtzoZLpiOgHRjqi7SHr2ene9PPOswH7hc87xkiKtiFOpCeCScF6asFeiUTn5sf5tuPHVGqjKskwxcm/ToW3hm7ChnQdYcPRlnHrMeLBJt6o6xdrg6+SvsnNzctLxes0gA=='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 10.4',
+        message: 'vN0ZDaO30wDfmgbiLKrip18QyR/2Z7fBa96LUwZKJkmpQEXJ',
+        seed: 'XKymoPdkFhqWhPhdkrbg7zfKi2VcrKag92QWGpaE+F0=',
+        encrypted: 'KWbozLkoxbGfY0Dixr8GE/JD+MDAXIUFzm7K5AYscTvyAh9EDkLfDc/i8Y9Cjz/GXWsrRAlzO9PmLj4rECjbaNdkyzgYUiXSVV0SWmEF62nhZcScf+5QWHgsv6syu2VXdkz9nW4O3LWir2M/HqJ6kmpKVm5o7TqeYZ7GrY25FUnFDM8DpXOZqOImHVAoh8Tim9d2V9lk2D2Av6Tdsa4SIyBDj5VcX3OVoTbqdkKj5It9ANHjXaqGwqEyj7j1cQrRzrbGVbib3qzvoFvGWoo5yzr3D8J8z/UXJ4sBkumcjrphFTDe9qQcJ5FI82ZZsChJssRcZl4ApFosoljixk0WkA=='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 10.5',
+        message: 'p91sfcJLRvndXx6RraTDs9+UfodyMqk=',
+        seed: 'lbyp44WYlLPdhp+n7NW7xkAb8+SVvKnjhZiUs92Gn6c=',
+        encrypted: 'D7UPhV1nPwixcgg47HSlk/8yDLEDSXxoyo6H7MMopUTYwCmAjtnpWp4oWGg0sACoUlzKpR3PN21a4xru1txalcOkceylsQI9AIFvLhZyS20HbvQeExT9zQGyJaDhygC/6gPifgELk7x5QUqsd+TL/MQdgBZqbLO0skLOqNG3KrTMmN0oeWgxgjMmWnyBH0qkUpV5SlRN2P3nIHd/DZrkDn/qJG0MpXh6AeNHhvSgv8gyDG2Vzdf04OgvZLJTJaTdqHuXz93t7+PQ+QfKOG0wCEf5gOaYpkFarorv9XPLhzuOauN+Dd2IPzgKH5+wjbTZlZzEn+xRyDXK7s6GL/XOZw=='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 10.6',
+        message: '6vGnOhsMRglTfeac2SKLvPuajKjGw++vBW/kp/RjTtALfDnsaSLXuOosBOus',
+        seed: 'n0fd9C6X7qhWqb28cU6zrCL26zKfR930LpfuqFapvbw=',
+        encrypted: 'FO6Mv81w3SW/oVGIgdfAbIOW1eK8/UFdwryWg3ek0URFK09jNQtAaxT+66Yn5EJrTWh8fgRn1spnAOUsY5eq7iGpRsPGE86MLNonOvrBIht4Z+IDum55EgmwCrlfyiGe2fX4Xv1ifCQMSHd3OJTujAosVI3vPJaSsbTW6FqOFkM5m9uPqrdd+yhQ942wN4m4d4TG/YPx5gf62fbCRHOfvA5qSpO0XGQ45u+sWBAtOfzxmaYtf7WRAlu+JvIjTp8I2lAfVEuuW9+TJattx9RXN8jaWOBsceLIOfE6bkgad50UX5PyEtapnJOG1j0bh5PZ//oKtIASarB3PwdWM1EQTQ=='
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha256', examples);
+    }
+
+    function _bytesToBigInteger(bytes) {
+      var buffer = UTIL.createBuffer(bytes);
+      var hex = buffer.toHex();
+      return new BigInteger(hex, 16);
+    }
+
+    function _base64ToBn(s) {
+      var decoded = UTIL.decode64(s);
+      return _bytesToBigInteger(decoded);
+    }
+
+    function checkOAEPEncryptExamples(publicKey, privateKey, md, examples) {
+      if(md === 'sha1') {
+        md = MD.sha1.create();
+      } else if(md === 'sha256') {
+        md = MD.sha256.create();
+      }
+
+      for(var i = 0; i < examples.length; ++i) {
+        var ex = examples[i];
+        it('should test ' + ex.title, function() {
+          checkOAEPEncrypt(
+            publicKey, privateKey, md, ex.message, ex.seed, ex.encrypted);
+        });
+      }
+    }
+
+    function checkOAEPEncrypt(
+      publicKey, privateKey, md, message, seed, expected) {
+      var message = UTIL.decode64(message);
+      var seed = UTIL.decode64(seed);
+      var encoded = PKCS1.encode_rsa_oaep(
+        publicKey, message, {seed: seed, md: md});
+      var ciphertext = publicKey.encrypt(encoded, null);
+      ASSERT.equal(expected, UTIL.encode64(ciphertext));
+
+      var decrypted = privateKey.decrypt(ciphertext, null);
+      var decoded = PKCS1.decode_rsa_oaep(privateKey, decrypted, {md: md});
+      ASSERT.equal(message, decoded);
+
+      // test with higher-level API, default label, and generating a seed
+      ciphertext = publicKey.encrypt(message, 'RSA-OAEP', {md: md});
+      decoded = privateKey.decrypt(ciphertext, 'RSA-OAEP', {md: md});
+      ASSERT.equal(message, decoded);
+    }
+
+    function decodeBase64PublicKey(modulus, exponent) {
+      modulus = _base64ToBn(modulus);
+      exponent = _base64ToBn(exponent);
+      return PKI.setRsaPublicKey(modulus, exponent);
+    }
+
+    function decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv) {
+      modulus = _base64ToBn(modulus);
+      exponent = _base64ToBn(exponent);
+      d = _base64ToBn(d);
+      p = _base64ToBn(p);
+      q = _base64ToBn(q);
+      dP = _base64ToBn(dP);
+      dQ = _base64ToBn(dQ);
+      qInv = _base64ToBn(qInv);
+      return PKI.setRsaPrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+    }
+
+    function makeKey() {
+      var modulus, exponent, d, p, q, dP, dQ, qInv, pubkey, privateKey;
+
+      // Example 1: A 1024-bit RSA Key Pair
+      modulus = 'qLOyhK+OtQs4cDSoYPFGxJGfMYdjzWxVmMiuSBGh4KvEx+CwgtaTpef87Wdc9GaFEncsDLxkp0LGxjD1M8jMcvYq6DPEC/JYQumEu3i9v5fAEH1VvbZi9cTg+rmEXLUUjvc5LdOq/5OuHmtme7PUJHYW1PW6ENTP0ibeiNOfFvs=';
+      exponent = 'AQAB';
+      d = 'UzOc/befyEZqZVxzFqyoXFX9j23YmP2vEZUX709S6P2OJY35P+4YD6DkqylpPNg7FSpVPUrE0YEri5+lrw5/Vf5zBN9BVwkm8zEfFcTWWnMsSDEW7j09LQrzVJrZv3y/t4rYhPhNW+sEck3HNpsx3vN9DPU56c/N095lNynq1dE=';
+      p = '0yc35yZ//hNBstXA0VCoG1hvsxMr7S+NUmKGSpy58wrzi+RIWY1BOhcu+4AsIazxwRxSDC8mpHHcrSEurHyjnQ==';
+      q = 'zIhT0dVNpjD6wAT0cfKBx7iYLYIkpJDtvrM9Pj1cyTxHZXA9HdeRZC8fEWoN2FK+JBmyr3K/6aAw6GCwKItddw==';
+      dP = 'DhK/FxjpzvVZm6HDiC/oBGqQh07vzo8szCDk8nQfsKM6OEiuyckwX77L0tdoGZZ9RnGsxkMeQDeWjbN4eOaVwQ==';
+      dQ = 'lSl7D5Wi+mfQBwfWCd/U/AXIna/C721upVvsdx6jM3NNklHnkILs2oZu/vE8RZ4aYxOGt+NUyJn18RLKhdcVgw==';
+      qInv = 'T0VsUCSTvcDtKrdWo6btTWc1Kml9QhbpMhKxJ6Y9VBHOb6mNXb79cyY+NygUJ0OBgWbtfdY2h90qjKHS9PvY4Q==';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      return {publicKey: pubkey, privateKey: privateKey};
+    }
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/pki',
+    'forge/pkcs1',
+    'forge/md',
+    'forge/jsbn',
+    'forge/util'
+  ], function(PKI, PKCS1, MD, JSBN, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      PKI(),
+      PKCS1(),
+      MD(),
+      JSBN(),
+      UTIL()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/pki')(),
+    require('../../js/pkcs1')(),
+    require('../../js/md')(),
+    require('../../js/jsbn')(),
+    require('../../js/util')());
+}
+
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/pkcs12.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/pkcs12.js
new file mode 100644
index 0000000..bbf1eea
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/pkcs12.js
@@ -0,0 +1,650 @@
+(function() {
+
+function Tests(ASSERT, FORGE) {
+  var forge = FORGE();
+  var PKCS12 = forge.pkcs12;
+  var ASN1 = forge.asn1;
+  var PKI = forge.pki;
+  var UTIL = forge.util;
+
+  var _data;
+  describe('pkcs12', function() {
+    it('should create certificate-only p12', function() {
+      var p12Asn = PKCS12.toPkcs12Asn1(null, _data.certificate, null, {
+        useMac: false,
+        generateLocalKeyId: false
+      });
+      var p12Der = ASN1.toDer(p12Asn).getBytes();
+
+      /* The generated PKCS#12 file lacks a MAC, therefore pass -nomacver to
+        OpenSSL: openssl pkcs12 -nomacver -nodes -in pkcs12_certonly.p12 */
+      ASSERT.equal(p12Der, UTIL.decode64(_data.p12certonly));
+    });
+
+    it('should create key-only p12', function() {
+      var privateKey = PKI.privateKeyFromPem(_data.privateKey);
+      var p12Asn = PKCS12.toPkcs12Asn1(privateKey, null, null, {
+        useMac: false,
+        generateLocalKeyId: false
+      });
+      var p12Der = ASN1.toDer(p12Asn).getBytes();
+
+      /* The generated PKCS#12 file lacks a MAC, therefore pass -nomacver to
+        OpenSSL: openssl pkcs12 -nomacver -nodes -in pkcs12_keyonly.p12 */
+      ASSERT.equal(p12Der, UTIL.decode64(_data.p12keyonly));
+    });
+
+    it('should create encrypted-key-only p12', function() {
+      /* Note we need to mock the PRNG, since the PKCS#12 file uses encryption
+        which otherwise would differ each time due to the randomized IV. */
+      var oldRandomGenerate = forge.random.generate;
+      forge.random.generate = function(num) {
+        return UTIL.createBuffer().fillWithByte(0, num).getBytes();
+      };
+
+      var privateKey = PKI.privateKeyFromPem(_data.privateKey);
+      var p12Asn = PKCS12.toPkcs12Asn1(privateKey, null, 'nopass', {
+        useMac: false,
+        generateLocalKeyId: false
+      });
+      var p12Der = ASN1.toDer(p12Asn).getBytes();
+
+      // restore old random function
+      forge.random.generate = oldRandomGenerate;
+
+      /* The generated PKCS#12 file lacks a MAC, therefore pass -nomacver to
+        OpenSSL: openssl pkcs12 -nomacver -in pkcs12_enckeyonly.p12 */
+      ASSERT.equal(p12Der, UTIL.decode64(_data.p12enckeyonly));
+    });
+
+    it('should import certificate-only p12', function() {
+      var p12Der = UTIL.decode64(_data.p12certonly);
+      var p12Asn1 = ASN1.fromDer(p12Der);
+      var p12 = PKCS12.pkcs12FromAsn1(p12Asn1);
+      ASSERT.equal(p12.version, 3);
+
+      // PKCS#12 PFX has exactly one SafeContents; it is not encrypted
+      ASSERT.equal(p12.safeContents.length, 1);
+      ASSERT.equal(p12.safeContents[0].encrypted, false);
+
+      // SafeContents has one SafeBag which has one CertBag with the cert
+      ASSERT.equal(p12.safeContents[0].safeBags.length, 1);
+      ASSERT.equal(p12.safeContents[0].safeBags[0].type, PKI.oids.certBag);
+
+      // check cert's serial number
+      ASSERT.equal(
+        p12.safeContents[0].safeBags[0].cert.serialNumber,
+        '00d4541c40d835e2f3');
+    });
+
+    it('should import key-only p12', function() {
+      var p12Der = UTIL.decode64(_data.p12keyonly);
+      var p12Asn1 = ASN1.fromDer(p12Der);
+      var p12 = PKCS12.pkcs12FromAsn1(p12Asn1);
+      ASSERT.equal(p12.version, 3);
+
+      // PKCS#12 PFX has exactly one SafeContents; it is not encrypted
+      ASSERT.equal(p12.safeContents.length, 1);
+      ASSERT.equal(p12.safeContents[0].encrypted, false);
+
+      // SafeContents has one SafeBag which has one KeyBag with the key
+      ASSERT.equal(p12.safeContents[0].safeBags.length, 1);
+      ASSERT.equal(p12.safeContents[0].safeBags[0].type, PKI.oids.keyBag);
+
+      // compare the key from the PFX by comparing both primes
+      var expected = PKI.privateKeyFromPem(_data.privateKey);
+      ASSERT.deepEqual(p12.safeContents[0].safeBags[0].key.p, expected.p);
+      ASSERT.deepEqual(p12.safeContents[0].safeBags[0].key.q, expected.q);
+    });
+
+    it('should import encrypted-key-only p12', function() {
+      var p12Der = UTIL.decode64(_data.p12enckeyonly);
+      var p12Asn1 = ASN1.fromDer(p12Der);
+      var p12 = PKCS12.pkcs12FromAsn1(p12Asn1, 'nopass');
+      ASSERT.equal(p12.version, 3);
+
+      // PKCS#12 PFX has exactly one SafeContents; it is *not* encrypted,
+      // only the key itself is encrypted (shrouded)
+      ASSERT.equal(p12.safeContents.length, 1);
+      ASSERT.equal(p12.safeContents[0].encrypted, false);
+
+      // SafeContents has one SafeBag which has one shrouded KeyBag with the key
+      ASSERT.equal(p12.safeContents[0].safeBags.length, 1);
+      ASSERT.equal(
+        p12.safeContents[0].safeBags[0].type, PKI.oids.pkcs8ShroudedKeyBag);
+
+      // compare the key from the PFX by comparing both primes
+      var expected = PKI.privateKeyFromPem(_data.privateKey);
+      ASSERT.deepEqual(p12.safeContents[0].safeBags[0].key.p, expected.p);
+      ASSERT.deepEqual(p12.safeContents[0].safeBags[0].key.q, expected.q);
+    });
+
+    it('should import an encrypted-key-only p12', function() {
+      var p12Der = UTIL.decode64(_data.p12enckeyonly);
+      var p12Asn1 = ASN1.fromDer(p12Der);
+      var p12 = PKCS12.pkcs12FromAsn1(p12Asn1, 'nopass');
+      ASSERT.equal(p12.version, 3);
+
+      // PKCS#12 PFX has exactly one SafeContents; it is *not* encrypted,
+      // only the key itself is encrypted (shrouded)
+      ASSERT.equal(p12.safeContents.length, 1);
+      ASSERT.equal(p12.safeContents[0].encrypted, false);
+
+      // SafeContents has one SafeBag which has one shrouded KeyBag with the key
+      ASSERT.equal(p12.safeContents[0].safeBags.length, 1);
+      ASSERT.equal(
+        p12.safeContents[0].safeBags[0].type, PKI.oids.pkcs8ShroudedKeyBag);
+
+      // compare the key from the PFX by comparing both primes
+      var expected = PKI.privateKeyFromPem(_data.privateKey);
+      ASSERT.deepEqual(p12.safeContents[0].safeBags[0].key.p, expected.p);
+      ASSERT.deepEqual(p12.safeContents[0].safeBags[0].key.q, expected.q);
+    });
+
+    it('should import an encrypted p12 with keys and certificates', function() {
+      var p12Der = UTIL.decode64(_data.p12encmixed);
+      var p12Asn1 = ASN1.fromDer(p12Der);
+      var p12 = PKCS12.pkcs12FromAsn1(p12Asn1, '123456');
+      ASSERT.equal(p12.version, 3);
+
+      // PKCS#12 PFX has two SafeContents; first is *not* encrypted but
+      // contains two shrouded keys, second is encrypted and has six
+      // certificates
+      ASSERT.equal(p12.safeContents.length, 2);
+
+      ASSERT.equal(p12.safeContents[0].encrypted, false);
+      ASSERT.equal(p12.safeContents[0].safeBags.length, 2);
+      ASSERT.equal(p12.safeContents[0].safeBags[0].type, PKI.oids.pkcs8ShroudedKeyBag);
+      ASSERT.equal(p12.safeContents[0].safeBags[0].attributes.friendlyName.length, 1);
+      ASSERT.equal(p12.safeContents[0].safeBags[0].attributes.friendlyName[0], 'encryptionkey');
+      ASSERT.equal(p12.safeContents[0].safeBags[0].attributes.localKeyId.length, 1);
+      ASSERT.equal(p12.safeContents[0].safeBags[0].attributes.localKeyId[0], 'Time 1311855238964');
+
+      ASSERT.equal(p12.safeContents[0].safeBags[1].type, PKI.oids.pkcs8ShroudedKeyBag);
+      ASSERT.equal(p12.safeContents[0].safeBags[1].attributes.friendlyName.length, 1);
+      ASSERT.equal(p12.safeContents[0].safeBags[1].attributes.friendlyName[0], 'signaturekey');
+      ASSERT.equal(p12.safeContents[0].safeBags[1].attributes.localKeyId.length, 1);
+      ASSERT.equal(p12.safeContents[0].safeBags[1].attributes.localKeyId[0], 'Time 1311855238863');
+
+      ASSERT.equal(p12.safeContents[1].encrypted, true);
+      ASSERT.equal(p12.safeContents[1].safeBags.length, 6);
+
+      ASSERT.equal(p12.safeContents[1].safeBags[0].type, PKI.oids.certBag);
+      ASSERT.equal(p12.safeContents[1].safeBags[0].attributes.friendlyName.length, 1);
+      ASSERT.equal(p12.safeContents[1].safeBags[0].attributes.friendlyName[0], 'CN=1002753325,2.5.4.5=#130b3130303237353333323543');
+      ASSERT.equal(p12.safeContents[1].safeBags[0].attributes.localKeyId.length, 1);
+      ASSERT.equal(p12.safeContents[1].safeBags[0].attributes.localKeyId[0], 'Time 1311855238964');
+
+      ASSERT.equal(p12.safeContents[1].safeBags[1].type, PKI.oids.certBag);
+      ASSERT.equal(p12.safeContents[1].safeBags[1].attributes.friendlyName.length, 1);
+      ASSERT.equal(p12.safeContents[1].safeBags[1].attributes.friendlyName[0], 'CN=ElsterSoftTestCA,OU=CA,O=Elster,C=DE');
+
+      ASSERT.equal(p12.safeContents[1].safeBags[2].type, PKI.oids.certBag);
+      ASSERT.equal(p12.safeContents[1].safeBags[2].attributes.friendlyName.length, 1);
+      ASSERT.equal(p12.safeContents[1].safeBags[2].attributes.friendlyName[0], 'CN=ElsterRootCA,OU=RootCA,O=Elster,C=DE');
+
+      ASSERT.equal(p12.safeContents[1].safeBags[3].type, PKI.oids.certBag);
+      ASSERT.equal(p12.safeContents[1].safeBags[3].attributes.friendlyName.length, 1);
+      ASSERT.equal(p12.safeContents[1].safeBags[3].attributes.friendlyName[0], 'CN=1002753325,2.5.4.5=#130b3130303237353333323541');
+      ASSERT.equal(p12.safeContents[1].safeBags[3].attributes.localKeyId.length, 1);
+      ASSERT.equal(p12.safeContents[1].safeBags[3].attributes.localKeyId[0], 'Time 1311855238863');
+
+      ASSERT.equal(p12.safeContents[1].safeBags[4].type, PKI.oids.certBag);
+      ASSERT.equal(p12.safeContents[1].safeBags[4].attributes.friendlyName.length, 1);
+      ASSERT.equal(p12.safeContents[1].safeBags[4].attributes.friendlyName[0], 'CN=ElsterSoftTestCA,OU=CA,O=Elster,C=DE');
+
+      ASSERT.equal(p12.safeContents[1].safeBags[5].type, PKI.oids.certBag);
+      ASSERT.equal(p12.safeContents[1].safeBags[5].attributes.friendlyName.length, 1);
+      ASSERT.equal(p12.safeContents[1].safeBags[5].attributes.friendlyName[0], 'CN=ElsterRootCA,OU=RootCA,O=Elster,C=DE');
+    });
+
+    it('should get bags by friendly name', function() {
+      var p12Der = UTIL.decode64(_data.p12encmixed);
+      var p12Asn1 = ASN1.fromDer(p12Der);
+      var p12 = PKCS12.pkcs12FromAsn1(p12Asn1, '123456');
+      var bags = p12.getBags({friendlyName: 'signaturekey'});
+
+      ASSERT.equal(bags.friendlyName.length, 1);
+      ASSERT.equal(bags.friendlyName[0].attributes.friendlyName[0], 'signaturekey');
+    });
+
+    it('should get cert bags by friendly name', function() {
+      var p12Der = UTIL.decode64(_data.p12encmixed);
+      var p12Asn1 = ASN1.fromDer(p12Der);
+      var p12 = PKCS12.pkcs12FromAsn1(p12Asn1, '123456');
+      var bags = p12.getBags({
+        friendlyName: 'CN=1002753325,2.5.4.5=#130b3130303237353333323543',
+        bagType: PKI.oids.certBag
+      });
+
+      ASSERT.equal(bags.friendlyName.length, 1);
+      ASSERT.equal(bags.friendlyName[0].attributes.friendlyName[0], 'CN=1002753325,2.5.4.5=#130b3130303237353333323543');
+    });
+
+    it('should get all cert bags', function() {
+      var p12Der = UTIL.decode64(_data.p12encmixed);
+      var p12Asn1 = ASN1.fromDer(p12Der);
+      var p12 = PKCS12.pkcs12FromAsn1(p12Asn1, '123456');
+      var bags = p12.getBags({
+        bagType: PKI.oids.certBag
+      });
+
+      ASSERT.equal(bags[PKI.oids.certBag].length, 6);
+      for(var i = 0; i < bags[PKI.oids.certBag].length; ++i) {
+        ASSERT.equal(bags[PKI.oids.certBag][i].type, PKI.oids.certBag);
+      }
+    });
+
+    it('should get bags by local key ID', function() {
+      var p12Der = UTIL.decode64(_data.p12encmixed);
+      var p12Asn1 = ASN1.fromDer(p12Der);
+      var p12 = PKCS12.pkcs12FromAsn1(p12Asn1, '123456');
+      var bags = p12.getBags({localKeyId: 'Time 1311855238863'});
+
+      ASSERT.equal(bags.localKeyId.length, 2);
+      ASSERT.equal(bags.localKeyId[0].attributes.localKeyId[0], 'Time 1311855238863');
+      ASSERT.equal(bags.localKeyId[1].attributes.localKeyId[0], 'Time 1311855238863');
+    });
+
+    it('should get cert bags by local key ID', function() {
+      var p12Der = UTIL.decode64(_data.p12encmixed);
+      var p12Asn1 = ASN1.fromDer(p12Der);
+      var p12 = PKCS12.pkcs12FromAsn1(p12Asn1, '123456');
+      var bags = p12.getBags({
+        localKeyId: 'Time 1311855238863',
+        bagType: PKI.oids.certBag
+      });
+
+      ASSERT.equal(bags.localKeyId.length, 1);
+      ASSERT.equal(bags.localKeyId[0].attributes.localKeyId[0], 'Time 1311855238863');
+      ASSERT.equal(bags.localKeyId[0].type, PKI.oids.certBag);
+    });
+
+    it('should generate a PKCS#12 mac key', function() {
+      var salt = 'A15D6AA8F8DAFC352F9EE1C192F09966EB85D17B';
+      salt = UTIL.createBuffer(UTIL.hexToBytes(salt));
+      var expected = '03e46727268575c6ebd6bff828d0d09b0c914201263ca543';
+      var key = PKCS12.generateKey('123456', salt, 1, 1024, 24);
+      ASSERT.equal(key.toHex(), expected);
+    });
+  });
+
+  _data = {
+    certificate: '-----BEGIN CERTIFICATE-----\r\n' +
+      'MIIDtDCCApwCCQDUVBxA2DXi8zANBgkqhkiG9w0BAQUFADCBmzELMAkGA1UEBhMC\r\n' +
+      'REUxEjAQBgNVBAgMCUZyYW5jb25pYTEQMA4GA1UEBwwHQW5zYmFjaDEVMBMGA1UE\r\n' +
+      'CgwMU3RlZmFuIFNpZWdsMRIwEAYDVQQLDAlHZWllcmxlaW4xFjAUBgNVBAMMDUdl\r\n' +
+      'aWVybGVpbiBERVYxIzAhBgkqhkiG9w0BCQEWFHN0ZXNpZUBicm9rZW5waXBlLmRl\r\n' +
+      'MB4XDTEyMDMxODIyNTc0M1oXDTEzMDMxODIyNTc0M1owgZsxCzAJBgNVBAYTAkRF\r\n' +
+      'MRIwEAYDVQQIDAlGcmFuY29uaWExEDAOBgNVBAcMB0Fuc2JhY2gxFTATBgNVBAoM\r\n' +
+      'DFN0ZWZhbiBTaWVnbDESMBAGA1UECwwJR2VpZXJsZWluMRYwFAYDVQQDDA1HZWll\r\n' +
+      'cmxlaW4gREVWMSMwIQYJKoZIhvcNAQkBFhRzdGVzaWVAYnJva2VucGlwZS5kZTCC\r\n' +
+      'ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMsAbQ4fWevHqP1K1y/ewpMS\r\n' +
+      '3vYovBto7IsKBq0v3NmC2kPf3NhyaSKfjOOS5uAPONLffLck+iGdOLLFia6OSpM6\r\n' +
+      '0tyQIV9lHoRh7fOEYORab0Z+aBUZcEGT9yotBOraX1YbKc5f9XO+80eG4XYvb5ua\r\n' +
+      '1NHrxWqe4w2p3zGJCKO+wHpvGkbKz0nfu36jwWz5aihfHi9hp/xs8mfH86mIKiD7\r\n' +
+      'f2X2KeZ1PK9RvppA0X3lLb2VLOqMt+FHWicyZ/wjhQZ4oW55ln2yYJUQ+adlgaYn\r\n' +
+      'PrtnsxmbTxM+99oF0F2/HmGrNs8nLZSva1Vy+hmjmWz6/O8ZxhiIj7oBRqYcAocC\r\n' +
+      'AwEAATANBgkqhkiG9w0BAQUFAAOCAQEAvfvtu31GFBO5+mFjPAoR4BlzKq/H3EPO\r\n' +
+      'qS8cm/TjHgDRALwSnwKYCFs/bXqE4iOTD6otV4TusX3EPbqL2vzZQEcZn6paU/oZ\r\n' +
+      'ZVXwQqMqY5tf2teQiNxqxNmSIEPRHOr2QVBVIx2YF4Po89KGUqJ9u/3/10lDqRwp\r\n' +
+      'sReijr5UKv5aygEcnwcW8+Ne4rTx934UDsutKG20dr5trZfWQRVS9fS9CFwJehEX\r\n' +
+      'HAMUc/0++80NhfQthmWZWlWM1R3dr4TrIPtWdn5z0MtGeDvqBk7HjGrhcVS6kAsy\r\n' +
+      'Z9y/lfLPjBuxlQAHztEJCWgI4TW3/RLhgfg2gI1noM2n84Cdmisfkg==\r\n' +
+      '-----END CERTIFICATE-----\r\n',
+    privateKey: '-----BEGIN RSA PRIVATE KEY-----\r\n' +
+      'MIIEowIBAAKCAQEAywBtDh9Z68eo/UrXL97CkxLe9ii8G2jsiwoGrS/c2YLaQ9/c\r\n' +
+      '2HJpIp+M45Lm4A840t98tyT6IZ04ssWJro5KkzrS3JAhX2UehGHt84Rg5FpvRn5o\r\n' +
+      'FRlwQZP3Ki0E6tpfVhspzl/1c77zR4bhdi9vm5rU0evFap7jDanfMYkIo77Aem8a\r\n' +
+      'RsrPSd+7fqPBbPlqKF8eL2Gn/GzyZ8fzqYgqIPt/ZfYp5nU8r1G+mkDRfeUtvZUs\r\n' +
+      '6oy34UdaJzJn/COFBnihbnmWfbJglRD5p2WBpic+u2ezGZtPEz732gXQXb8eYas2\r\n' +
+      'zyctlK9rVXL6GaOZbPr87xnGGIiPugFGphwChwIDAQABAoIBAAjMA+3QvfzRsikH\r\n' +
+      'zTtt09C7yJ2yNjSZ32ZHEPMAV/m1CfBXCyL2EkhF0b0q6IZdIoFA3g6xs4UxYvuc\r\n' +
+      'Q9Mkp2ap7elQ9aFEqIXkGIOtAOXkZV4QrEH90DeHSfax7LygqfD5TF59Gg3iAHjh\r\n' +
+      'B3Qvqg58LyzJosx0BjLZYaqr3Yv67GkqyflpF/roPGdClHpahAi5PBkHiNhNTAUU\r\n' +
+      'LJRGvMegXGZkUKgGMAiGCk0N96OZwrinMKO6YKGdtgwVWC2wbJY0trElaiwXozSt\r\n' +
+      'NmP6KTQp94C7rcVO6v1lZiOfhBe5Kc8QHUU+GYydgdjqm6Rdow/yLHOALAVtXSeb\r\n' +
+      'U+tPfcECgYEA6Qi+qF+gtPincEDBxRtoKwAlRkALt8kly8bYiGcUmd116k/5bmPw\r\n' +
+      'd0tBUOQbqRa1obYC88goOVzp9LInAcBSSrexhVaPAF4nrkwYXMOq+76MiH17WUfQ\r\n' +
+      'MgVM2IB48PBjNk1s3Crj6j1cxxkctqmCnVaI9HlU2PPZ3xjaklfv/NsCgYEA3wH8\r\n' +
+      'mehUhiAp7vuhd+hfomFw74cqgHC9v0saiYGckpMafh9MJGc4U5GrN1kYeb/CFkSx\r\n' +
+      '1hOytD3YBKoaKKoYagaMQcjxf6HnEF0f/5OiQkUQpWmgC9lNnE4XTWjnwqaTS5L9\r\n' +
+      'D+H50SiI3VjHymGXTRJeKpAIwV74AxxrnVofqsUCgYAwmL1B2adm9g/c7fQ6yatg\r\n' +
+      'hEhBrSuEaTMzmsUfNPfr2m4zrffjWH4WMqBtYRSPn4fDMHTPJ+eThtfXSqutxtCi\r\n' +
+      'ekpP9ywdNIVr6LyP49Ita6Bc+mYVyU8Wj1pmL+yIumjGM0FHbL5Y4/EMKCV/xjvR\r\n' +
+      '2fD3orHaCIhf6QvzxtjqTwKBgFm6UemXKlMhI94tTsWRMNGEBU3LA9XUBvSuAkpr\r\n' +
+      'ZRUwrQssCpXnFinBxbMqXQe3mR8emrM5D8En1P/jdU0BS3t1kP9zG4AwI2lZHuPV\r\n' +
+      'ggbKBS2Y9zVtRKXsYcHawM13+nIA/WNjmAGJHrB45UJPy/HNvye+9lbfoEiYKdCR\r\n' +
+      'D4bFAoGBAIm9jcZkIwLa9kLAWH995YYYSGRY4KC29XZr2io2mog+BAjhFt1sqebt\r\n' +
+      'R8sRHNiIP2mcUECMOcaS+tcayi+8KTHWxIEed9qDmFu6XBbePfe/L6yxPSagcixH\r\n' +
+      'BK0KuK/fgTPvZCmIs8hUIC+AxhXKnqn4fIWoO54xLsALc0gEjs2d\r\n' +
+      '-----END RSA PRIVATE KEY-----\r\n',
+    p12certonly:
+      'MIIEHgIBAzCCBBcGCSqGSIb3DQEHAaCCBAgEggQEMIIEADCCA/wGCSqGSIb3DQEH\r\n' +
+      'AaCCA+0EggPpMIID5TCCA+EGCyqGSIb3DQEMCgEDoIID0DCCA8wGCiqGSIb3DQEJ\r\n' +
+      'FgGgggO8BIIDuDCCA7QwggKcAgkA1FQcQNg14vMwDQYJKoZIhvcNAQEFBQAwgZsx\r\n' +
+      'CzAJBgNVBAYTAkRFMRIwEAYDVQQIDAlGcmFuY29uaWExEDAOBgNVBAcMB0Fuc2Jh\r\n' +
+      'Y2gxFTATBgNVBAoMDFN0ZWZhbiBTaWVnbDESMBAGA1UECwwJR2VpZXJsZWluMRYw\r\n' +
+      'FAYDVQQDDA1HZWllcmxlaW4gREVWMSMwIQYJKoZIhvcNAQkBFhRzdGVzaWVAYnJv\r\n' +
+      'a2VucGlwZS5kZTAeFw0xMjAzMTgyMjU3NDNaFw0xMzAzMTgyMjU3NDNaMIGbMQsw\r\n' +
+      'CQYDVQQGEwJERTESMBAGA1UECAwJRnJhbmNvbmlhMRAwDgYDVQQHDAdBbnNiYWNo\r\n' +
+      'MRUwEwYDVQQKDAxTdGVmYW4gU2llZ2wxEjAQBgNVBAsMCUdlaWVybGVpbjEWMBQG\r\n' +
+      'A1UEAwwNR2VpZXJsZWluIERFVjEjMCEGCSqGSIb3DQEJARYUc3Rlc2llQGJyb2tl\r\n' +
+      'bnBpcGUuZGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDLAG0OH1nr\r\n' +
+      'x6j9Stcv3sKTEt72KLwbaOyLCgatL9zZgtpD39zYcmkin4zjkubgDzjS33y3JPoh\r\n' +
+      'nTiyxYmujkqTOtLckCFfZR6EYe3zhGDkWm9GfmgVGXBBk/cqLQTq2l9WGynOX/Vz\r\n' +
+      'vvNHhuF2L2+bmtTR68VqnuMNqd8xiQijvsB6bxpGys9J37t+o8Fs+WooXx4vYaf8\r\n' +
+      'bPJnx/OpiCog+39l9inmdTyvUb6aQNF95S29lSzqjLfhR1onMmf8I4UGeKFueZZ9\r\n' +
+      'smCVEPmnZYGmJz67Z7MZm08TPvfaBdBdvx5hqzbPJy2Ur2tVcvoZo5ls+vzvGcYY\r\n' +
+      'iI+6AUamHAKHAgMBAAEwDQYJKoZIhvcNAQEFBQADggEBAL377bt9RhQTufphYzwK\r\n' +
+      'EeAZcyqvx9xDzqkvHJv04x4A0QC8Ep8CmAhbP216hOIjkw+qLVeE7rF9xD26i9r8\r\n' +
+      '2UBHGZ+qWlP6GWVV8EKjKmObX9rXkIjcasTZkiBD0Rzq9kFQVSMdmBeD6PPShlKi\r\n' +
+      'fbv9/9dJQ6kcKbEXoo6+VCr+WsoBHJ8HFvPjXuK08fd+FA7LrShttHa+ba2X1kEV\r\n' +
+      'UvX0vQhcCXoRFxwDFHP9PvvNDYX0LYZlmVpVjNUd3a+E6yD7VnZ+c9DLRng76gZO\r\n' +
+      'x4xq4XFUupALMmfcv5Xyz4wbsZUAB87RCQloCOE1t/0S4YH4NoCNZ6DNp/OAnZor\r\n' +
+      'H5I=',
+    p12enckeyonly:
+      'MIIFcQIBAzCCBWoGCSqGSIb3DQEHAaCCBVsEggVXMIIFUzCCBU8GCSqGSIb3DQEH\r\n' +
+      'AaCCBUAEggU8MIIFODCCBTQGCyqGSIb3DQEMCgECoIIFIzCCBR8wSQYJKoZIhvcN\r\n' +
+      'AQUNMDwwGwYJKoZIhvcNAQUMMA4ECAAAAAAAAAAAAgIIADAdBglghkgBZQMEAQIE\r\n' +
+      'EAAAAAAAAAAAAAAAAAAAAAAEggTQQHIbPs0naCmJGgmtvFNmUlv9sHkm2A/vWHjY\r\n' +
+      'B8UavyYUz3IMtDCWZBoWHWp/izLDroCSxkabxyzqlSbYdGug1QY9y9RP6TjP6uaw\r\n' +
+      'SFurDe7beTRB3d8Oe2AMEmUQtaPE/zQI52aWse8RNh5P1I1wQEzVvk8/hf2eLdLQ\r\n' +
+      'cxUb0flz65Nkr4tVPsAmXfbepiyPm+lISi7syNfO6d7424CsGYXD3VCtDxbS5r0m\r\n' +
+      'L7OIkMfr7JhkvlrcdgrBY5r8/67MtfaJrMe0FR90UJd6ST++2FyhbilSz2BI6Twu\r\n' +
+      'wNICvkbThwY/LLxOCPKm4AgEj/81pYy6z2eWG59pD8fju4IOJUt3AGoPZoCQrbmD\r\n' +
+      'MDahpYgey6bo8ti9H08HhvP9keOgI2HUCQoObL0c2GF+fv6m/EJ59hpH9qeryfT4\r\n' +
+      'HAzSfd4h0YszF32a23+dGEgXAA492m00lZ/uM5nTF0RIQsqj5BJSxEEBpYequF4A\r\n' +
+      'MNCsjKl90HPSIndNSUfgN0us8FtmrzBNbmIATFE9juKHWag3p751ertsGv6e/Ofm\r\n' +
+      'xAhgF21j8ZhwXKjsVY4uYVFYLWkCLSD4gF8/ijWg873XZKzjPuy8w3SAAcya8vaQ\r\n' +
+      'buzzk5zgN0g5T+JxCAdP50FH68rVG2dhfY1BDFe8xY6mxSfs/wUj5EVD9jdqlYpu\r\n' +
+      '/o3IFtdksSra8eOwO2F/kw69x11wZaYpZaRzbIM2x1pDARkAtnbdvdSEXMOT7htA\r\n' +
+      'OYAJiZuhW0czOgumGGhIO8TBxwMh3/WjkUdlJ1JNhl6M+JHlmIKerCuP6kCLJsAp\r\n' +
+      '+RyRRp6pKa4t09G5rjAjCUiPqkCKRSf40ijhn+lLhj8ZHKLDyw4FCMo6NvQvFsDp\r\n' +
+      'dbCbbHzWGZXEspT56jGbuju1DQCiy+uVKYhigU1PvRXrxpCHKcv65iqnwPRgGE6X\r\n' +
+      'dPSGfjsLDbATvSrVv1DvJNTH9wyCSQt/eqBXFWkQeFEqKXij0atkdHL6GXRo57PX\r\n' +
+      'KZgeul2Xxd2J5SYNCUJf8IL4UOfHRMB4GaGGt9LTpPq2bI9fju1vVE4BjL1gSYIi\r\n' +
+      'cvynjH7mpzVwq+Cxj4uCo8aZQKWB0BI7c4cDaFmKPIFD47QFZUNgYCv9WfNljOe/\r\n' +
+      '+CqRbxNFUsXCR4hEoYmdn0EEI2b1F6Hkz/dDrLH33My4Gp14N8MVkASWtecxvbfa\r\n' +
+      'xkj5SiC5NZQ2TZtt3DX508BPFSqJRjb83I7qhNjWxqFUxS1ma9PF/AQzUgNLw+Gz\r\n' +
+      't5fpB3hD+33fWE8y4RbiUsBU+O56qaN9luOZLa/eVwo+I9F1LgXsS29iv6LvHO5m\r\n' +
+      '+IfzHM+FROS1XhzM+t8rxTK7VmBHqmPrKcjtnYXZh0eA9YIhTEeRdlEO8q4dsKFv\r\n' +
+      'sbQZ3+65DW6pbDbe/3CGqf43w5vbTvhsRSYqC9ojKjnUtoJ8gY+b7GPNUVsgxQCh\r\n' +
+      'jfqqZoVmhBihTO5hgeHJf+ilCbw5cPCEXobAxMfdPaasBV5xDBcvDDl7Sv16feYk\r\n' +
+      'OZJ6bm9wRkqbQUsWYMgYLCfs/FDe1kfkSeS8JYlmFIkHZL6K3LqkULnqPfQdnlMp\r\n' +
+      '1PYGlPTdp+6XcqNBVORyXkOXF7PyrOw7vRefEuGcBvZ4TT0jmHE3KxKEvJwbVsne\r\n' +
+      'H4/s3xo=',
+    p12keyonly:
+      'MIIFEAIBAzCCBQkGCSqGSIb3DQEHAaCCBPoEggT2MIIE8jCCBO4GCSqGSIb3DQEH\r\n' +
+      'AaCCBN8EggTbMIIE1zCCBNMGCyqGSIb3DQEMCgEBoIIEwjCCBL4CAQAwDQYJKoZI\r\n' +
+      'hvcNAQEBBQAEggSoMIIEpAIBAAKCAQEAywBtDh9Z68eo/UrXL97CkxLe9ii8G2js\r\n' +
+      'iwoGrS/c2YLaQ9/c2HJpIp+M45Lm4A840t98tyT6IZ04ssWJro5KkzrS3JAhX2Ue\r\n' +
+      'hGHt84Rg5FpvRn5oFRlwQZP3Ki0E6tpfVhspzl/1c77zR4bhdi9vm5rU0evFap7j\r\n' +
+      'DanfMYkIo77Aem8aRsrPSd+7fqPBbPlqKF8eL2Gn/GzyZ8fzqYgqIPt/ZfYp5nU8\r\n' +
+      'r1G+mkDRfeUtvZUs6oy34UdaJzJn/COFBnihbnmWfbJglRD5p2WBpic+u2ezGZtP\r\n' +
+      'Ez732gXQXb8eYas2zyctlK9rVXL6GaOZbPr87xnGGIiPugFGphwChwIDAQABAoIB\r\n' +
+      'AQAIzAPt0L380bIpB807bdPQu8idsjY0md9mRxDzAFf5tQnwVwsi9hJIRdG9KuiG\r\n' +
+      'XSKBQN4OsbOFMWL7nEPTJKdmqe3pUPWhRKiF5BiDrQDl5GVeEKxB/dA3h0n2sey8\r\n' +
+      'oKnw+UxefRoN4gB44Qd0L6oOfC8syaLMdAYy2WGqq92L+uxpKsn5aRf66DxnQpR6\r\n' +
+      'WoQIuTwZB4jYTUwFFCyURrzHoFxmZFCoBjAIhgpNDfejmcK4pzCjumChnbYMFVgt\r\n' +
+      'sGyWNLaxJWosF6M0rTZj+ik0KfeAu63FTur9ZWYjn4QXuSnPEB1FPhmMnYHY6puk\r\n' +
+      'XaMP8ixzgCwFbV0nm1PrT33BAoGBAOkIvqhfoLT4p3BAwcUbaCsAJUZAC7fJJcvG\r\n' +
+      '2IhnFJnddepP+W5j8HdLQVDkG6kWtaG2AvPIKDlc6fSyJwHAUkq3sYVWjwBeJ65M\r\n' +
+      'GFzDqvu+jIh9e1lH0DIFTNiAePDwYzZNbNwq4+o9XMcZHLapgp1WiPR5VNjz2d8Y\r\n' +
+      '2pJX7/zbAoGBAN8B/JnoVIYgKe77oXfoX6JhcO+HKoBwvb9LGomBnJKTGn4fTCRn\r\n' +
+      'OFORqzdZGHm/whZEsdYTsrQ92ASqGiiqGGoGjEHI8X+h5xBdH/+TokJFEKVpoAvZ\r\n' +
+      'TZxOF01o58Kmk0uS/Q/h+dEoiN1Yx8phl00SXiqQCMFe+AMca51aH6rFAoGAMJi9\r\n' +
+      'QdmnZvYP3O30OsmrYIRIQa0rhGkzM5rFHzT369puM63341h+FjKgbWEUj5+HwzB0\r\n' +
+      'zyfnk4bX10qrrcbQonpKT/csHTSFa+i8j+PSLWugXPpmFclPFo9aZi/siLpoxjNB\r\n' +
+      'R2y+WOPxDCglf8Y70dnw96Kx2giIX+kL88bY6k8CgYBZulHplypTISPeLU7FkTDR\r\n' +
+      'hAVNywPV1Ab0rgJKa2UVMK0LLAqV5xYpwcWzKl0Ht5kfHpqzOQ/BJ9T/43VNAUt7\r\n' +
+      'dZD/cxuAMCNpWR7j1YIGygUtmPc1bUSl7GHB2sDNd/pyAP1jY5gBiR6weOVCT8vx\r\n' +
+      'zb8nvvZW36BImCnQkQ+GxQKBgQCJvY3GZCMC2vZCwFh/feWGGEhkWOCgtvV2a9oq\r\n' +
+      'NpqIPgQI4RbdbKnm7UfLERzYiD9pnFBAjDnGkvrXGsovvCkx1sSBHnfag5hbulwW\r\n' +
+      '3j33vy+ssT0moHIsRwStCriv34Ez72QpiLPIVCAvgMYVyp6p+HyFqDueMS7AC3NI\r\n' +
+      'BI7NnQ==',
+    p12encmixed:
+      'MIIpiwIBAzCCKUUGCSqGSIb3DQEHAaCCKTYEgikyMIIpLjCCCtMGCSqGSIb3DQEH\r\n' +
+      'AaCCCsQEggrAMIIKvDCCBVsGCyqGSIb3DQEMCgECoIIE+jCCBPYwKAYKKoZIhvcN\r\n' +
+      'AQwBAzAaBBShXWqo+Nr8NS+e4cGS8Jlm64XRewICBAAEggTIMtqzJpmSCGAYKTj8\r\n' +
+      '1t3U0mGNAErZt0UA2EP9dcGvyG+0W+PMorwwduveGz5ymdqh+8mdbGOTTGKqLVmB\r\n' +
+      '9vR2826foDSgjB+x+fSX9UtSvf9xwF0J6VGPt64RP4J3c+5ntd/gleJCpeBW/div\r\n' +
+      'ieeSRAJ0JX/JthDvO1VyzBOb8w5lakK/mCvLpcbUMIMnF6M/TT1rreqtl8GSx9+n\r\n' +
+      '+ne4tBWCUYAZfYHuKd2tCpT+lpP8pmZ7BYEvgyWPmYkNTkbPMAct1nJcN3L89Rt0\r\n' +
+      'Yw2fg58pvzY0WlHK3H5rB4J7835jTnZLAYz2sjnlDXndwXtiH9AU3X3KQpSDHrkd\r\n' +
+      'ypBQfPHVwB7f+UiKYx5KYNjT1ph2y4DoBV6e4tnQs4KjixtKnom/9ipqOwjP2o6+\r\n' +
+      '4rkZf3I+g8ZrTQygbmQBCfdduNwnhImf7XJezK2RoW9/b9WoSPGSuzsVmt7h+hYs\r\n' +
+      'hGGx5mdk+iJTmst5MrdNx4byKLW+ThrFJ+eqUwm/d+ODQolu+gckOTyaUvNfjYy7\r\n' +
+      'JYi7wnzKAiOyNHwoP+Efpwb+eDffDyg0u/SuEc3qulWr61hfvjihK7w9Vo21kW3r\r\n' +
+      'a6vSL6mS9XBJjvJFdf5sEMiNTUxR7Zw4AsKUysgyiXRFM2hkxuwImFozyK+Er4OJ\r\n' +
+      'ydi3NpzcAL2+a8JzB35QztRxnypn/v/bWIyt89fW1mrstkCwvNRBaYnI4AGDN0C7\r\n' +
+      'jw5aYbOcdg3PbV69+5i4RCRkN2GU6LTovSaBvfBWxrJHav8pfinLhduOckwrWckx\r\n' +
+      'O38vc0fSuTUQaXgL8fXofX6L0139l9fN2MfndI8+39JOlzXNCsldpX+Nt2PI2Awm\r\n' +
+      'AgKEpLA3jbjazqvOmZUBxh0RVozzVu+JTbGWvkalEcmyncCuKSFZkMlP3SNrn4PS\r\n' +
+      'tpHlohTPBPHpxgJce0O6ylxgUZkUsSDatE0joWW/YJ+us0bqeGej5OLvmI9/L9iH\r\n' +
+      '2jCFIN79WVG06GsNuiKON12KPL4J/B4rv9bguQHdcPGJcVXtKv1Uzscpy1uQcqZc\r\n' +
+      'qVzl+Om4fbb0mg+vRXi9FQu//U35yK95NjF6KyniVF0riZWA6bb8dO4YdO4q9IYZ\r\n' +
+      'OFeoLQ/Zl4Zg58ytsUsqoFW6yK7itGUAV1y4BPME4pqkQAI5EVgaFnng9Gdcq9hN\r\n' +
+      '3VHHJLUiCjMLCmWrzt5dTgNCLrvF60bDnM5w9VAkR1xvNzYL/ui0j5A5fbpFc7jz\r\n' +
+      '1JcwFilP9qD94MPBOoPRNJNRxDl1bigdBtR50VTo7tNt48sSZHdVWPGMaqjDndRg\r\n' +
+      'ur3EJeQVMUvVf/5L9hDaZdqxJ9x6Va+5f4a4Or3ttOCb1qCawqutx6IcOc26EAVy\r\n' +
+      'nQ47UXQ2j/AjDoG+T8u34+TQsiVyC5X96TezAfPk5Vp8KUBjhBy15Z0YlnxXw4Up\r\n' +
+      'KzFPMfWOLTiElbJGaLtD7MXrXMQcdK9S2d/MR01zM8QuLwDH4OJfSJ53mlgsFmRG\r\n' +
+      'x7L+nZS7961GpoGHIZRRWvi7yejNpzxBUN7rIERgUqVQeh3lLDeDz8XKT83Hzd5R\r\n' +
+      '4AufZHsVg4K1xHxczD1NVoc2O/GM40vyPiK2ms1mQPiTckyF1jrsfKYDwbkzE3yi\r\n' +
+      'tJXp7Wlc5IHVQqULMU4wKQYJKoZIhvcNAQkUMRweGgBlAG4AYwByAHkAcAB0AGkA\r\n' +
+      'bwBuAGsAZQB5MCEGCSqGSIb3DQEJFTEUBBJUaW1lIDEzMTE4NTUyMzg5NjQwggVZ\r\n' +
+      'BgsqhkiG9w0BDAoBAqCCBPowggT2MCgGCiqGSIb3DQEMAQMwGgQUVHez67zL2YSj\r\n' +
+      'TqMZjS54S+FO+3wCAgQABIIEyDFgx9KJvdvBoednovcwUJeTWhvvMl6owrJ2FhVY\r\n' +
+      'FjahfYv7vLAKUeQiqnepRcATUzSHJgDDKlnW+0UDSGUqUoabbJhAqtHqrHFevGS2\r\n' +
+      'YpPNCfi7C2XTm4+F1MNmlmZhsM8gIY+2lmVpjRm+DvymKBzRuEw81xcF+RFDdOTX\r\n' +
+      '/ka6l5leRoFWTbfTnpIxA5QBVvEH52UkDw3hcrmVIct0v60IseiOqiL/4IpbpjmF\r\n' +
+      '3/rQdinE2sckujcEJD8edu1zbZzZ7KIbklWpPvcMRqCQSgrTuW1/lBuyVH3cvoFp\r\n' +
+      'FtaAw60f6X1ezKmiwA0nYIwahGVmyG4iektxO76zzBPkhL5HPD8BuJX+TRE9hYrZ\r\n' +
+      'tk161/hKFznWpPEC5ferEEEQi0vB2In1uz7L9LkpmC/to1k8MI1A/0yhY5xXUh4H\r\n' +
+      'hmp50OEsBnaXjDqPgtZeukqObjKbOSS4zL1WZ5vohJWQdF+C04d93MZoieUSz0yr\r\n' +
+      '1vSQ/JIr51NRKdffCgoZSFayv5vzFyTu9FKlUBgGEUMEzBdZ200v5ho7fHXWp1gW\r\n' +
+      'TyZK1pdVAC6tnKIgfSdkG7txHUDru120G7AdFXoFeRo7zalxGiGx5RIn3b/qfmyO\r\n' +
+      'MxcJX9wpFck9IcnN8L/S7hbxt9yAINshOyEM0rUXz0fjVZfcckKLso87YXCGJ7+X\r\n' +
+      '6HYe8bs7/uID7Yz7svD1iwnBlEPQInENZBEPuj6dtMYhMXXMHrY5nwNkXBGQioET\r\n' +
+      'O6xLjigPX7AUSuCCIRuoHGfo54HxV5uCon2/ibDuhTr46FrTKxQl2xv3M6VoWF/+\r\n' +
+      '0vLiCGKDa/aT5dZhdZ9OqC56mr6dFf8fSntMBBBxtUmcLVySa33G5UCInSrnTgu0\r\n' +
+      'fY8XGgud/V++xs5lr6jxFQjTdc0ec4muRBOflAvxGq/KKmhbO6h2sa9Ldmr9EABY\r\n' +
+      'jRaMz63WvObklE1m1IajjiceVXNLwJCwf37J7HKp1836WiWl/psIRSpsV0gdeb7y\r\n' +
+      'kEx54sEkbwtu8TNga6PbWUzwVEamFSGkAIxAnCCBj7W0okoLw+z1+FAby2lnMSSP\r\n' +
+      'F9g6aEEACt0h7fgOb6AEi9NCqfrpiZADwW1E0FRYOf8sIy/z6NPQGft3aIlUG9DA\r\n' +
+      'EZAm5IdZ0umQLMqeG30ZkC88W+ERhdIpVpwuHevdRyDwwN6SZ2+AZd5it1EOCLrC\r\n' +
+      '8CSWXyCNaSkPyoPzE0CpeayyhxYkQNg2KiLEOsOOOmSFpQx+R4QQjJL+chuX8T/D\r\n' +
+      'rxrgUgnPWPTDRI5iTexcCBlPdMbeyxfpwIWU0ZZsQxK1eBdizIzw/2JTSyHYVZuq\r\n' +
+      'nhznMaQHH0oA2PGqZw0y7Vu9iRzGU3RrEBBdGnZIwdz9agBc6BxqtLQ5tLKNLCBS\r\n' +
+      'BZjrCbWe9yBarQOFOpVPiczt/oJju/d5jC9Sj1QDppjLTiajZlcoY+mHGqcbzoe4\r\n' +
+      'wVN9+ZetkrGk4zDc8MPYMbHIxLR58P3noVZ6s84h1rhA8lKCg9vvG0jffcuveIRu\r\n' +
+      'AosyBT0v0qVRUWMIXJKpJSivKPykbQm6J+bAoK8+l3yCJ0AWpDcw5Wo5XqV/t4px\r\n' +
+      'xr95ikcr1+ANBRxa/TAl4oYuoqhlkx7Q8i/XCSudpXrswWcfR5ipc0tBzDFMMCcG\r\n' +
+      'CSqGSIb3DQEJFDEaHhgAcwBpAGcAbgBhAHQAdQByAGUAawBlAHkwIQYJKoZIhvcN\r\n' +
+      'AQkVMRQEElRpbWUgMTMxMTg1NTIzODg2MzCCHlMGCSqGSIb3DQEHBqCCHkQwgh5A\r\n' +
+      'AgEAMIIeOQYJKoZIhvcNAQcBMCgGCiqGSIb3DQEMAQYwGgQUQmWgPDEzodyfX1/t\r\n' +
+      '0lON23fzMeUCAgQAgIIeAAxfoaegDbtpKNtbR/bKgGGecS1491HJMR22X5mHI5EV\r\n' +
+      'UxPyuyM2bHky/U1eGix06P0ExQMV5kh/Eb+6vRLn+l0pTci53Ps2ITKFXvmqZ5Zx\r\n' +
+      'yjFtU3LCzN/qh5rFsLpPLdFn4oNrBveXWNPJrIj3Sf93To2GkLFQQ2aINNHe76k3\r\n' +
+      'O4jp6Kz4DKFrnyrY/fDDhHuGnkvHXBXPO+413PIV4Jgmv1zULkB94WpcJ35gsBGV\r\n' +
+      '3Njt7F0X10ZE3VN/tXlEPjaSv5k4zpG5Pe18Q4LnrPSN+XLfFLRnnYJiDlQkvO91\r\n' +
+      'AOxqlAkUq4jAGbTBUeSt+8P5HaliAPDJA43/tEWrb7fX68VpIblm4Y38AIoZOL8u\r\n' +
+      'uafg3WctcD9cy2rP6e0kblkcG4DLrwp/EezeXDxbOsdViiLU5SL1/RhO/0cqB2zw\r\n' +
+      '2scYLc6nJkxzC3iyzhukyn4834SAj+reJMzyiy9VviGQlDz4HFC+P9kYKOqbdW9N\r\n' +
+      'YYLYluHWjnNzE1enaYSPNPuR1U9UhSN/wKVwmdXsLRK0+ee7xpryxpTeUNAwacGR\r\n' +
+      'R7uWiXVBj+xQ2AG5qmW4fe1wxrZIL5bD1Z98ss3YLyUESUIv3K6MxkXZdp+gXv97\r\n' +
+      'jN6j2r536fGpA6jWehjsjk03tL5Zjv4i0PZLUFj16T1uXMzmaKplVd1HYY6bhBl6\r\n' +
+      '7lJerTtrGnPpybAeVn5KFsct0HchWIOkAeqOy3tIqi3R1msIrtR5FviFCgFYS5sV\r\n' +
+      'ly+T+rNdjQM4FrRk6y/IcCqoQTE6By8cYafRH58X07ix1+k5IFlrTbPrA8w1qQ6T\r\n' +
+      'wI5ww0hf4aE3W7brXMlmLYBfwfkTWLH/yDQsXBLsma0y1G7Ixn0BLuo6FBm3ayC2\r\n' +
+      'LEkN+iB+zEeC54oHE450Bhv1TOS80PrLzDW7wL789adWKXHgMmug9sT67gBbaFeU\r\n' +
+      'u3Z8VTISGxmbrEJQAMEoLuQiujHSfpPb5zK02+A363r+bLt1VbIs5jrYMvaB7qrk\r\n' +
+      '7nVJbVYlPscGwUQUEq4YbCjlg77rhY6d61LIcguG5snF2JTnR74Gu72JpqkrrtA9\r\n' +
+      'QHQ/njBnmIenXkqCzwcjyqiKUmPXazC/un7Hl3ZUp7BbbvfCJ1VNqtQJTdyS6kZ0\r\n' +
+      'ZIURy6R4uenoOw9BJfTzLEi+on3af1XXavb8apGlTVCxs8hL4F2IR1A3bkB8fMHv\r\n' +
+      '5oie2te80GKp+B+r0VrEdOLy6BkLgEfuwrpcsIjz+6z83UhfMhgKAcXYJVUC/mmf\r\n' +
+      'xO4hZ3AHKLCgVql8D4NoYPanQYEKx2sEoTDsCzsoh+E6OYhe4CiSBYwB4s5fKX1w\r\n' +
+      '5LVz7crf8Pg+WfffeP8Y/tDOiShz4luB7YVzw+sAy9Xil5+KmPO11yeDwIe3bdvu\r\n' +
+      'SeTAgzZ4lx7aZUpQ2cGaswo5Ix3Q7z2WkooYxCi4+XVw+BhRO0pVuyQB04v5rr1W\r\n' +
+      'EFlDAsC+RVcUw8gyM+tSxm5vqP7H6oEJT00tBYNAX/9ztDpoX4i2276s+6Iszz8B\r\n' +
+      'kqqTfasb41xzUdFf1PpSzqVGKDi4lAftfedn4JFuQHhcI4MhtxwwecKUL3uHXWiW\r\n' +
+      '3o++dAjO7ybfBm3j0WIKGVwxfON5KnVetSOofc3ZahSklUqEuyaQ/X93FT4amYMJ\r\n' +
+      'U7NwbLmrCuYe19/+0lt0ySSSBPNNJcV8r+/P0m4gR7athre9aSn/gU2rRrpYfXUS\r\n' +
+      'SIskLLPn26naLqLW5eEqF9KBg77pGXoXA4guavjUtxEeDCL0ncqAPlhNlRc7NTw5\r\n' +
+      'MGy65ozntamlGrAWK96fMesmF81ZFHyBH4XImDkIvEr62hmJUJuTh3lBhIGAmqwo\r\n' +
+      'jcYdAkibrZh3RmhYNzuSAPoBOd959fOwb9SVltDea49XAopKTodL6FPX4UQbCuES\r\n' +
+      'oml4ZBvRs+ykU+q1to+0QdoY8x0vzkEL1cOOEcbAQebK3kw3GmNZSi6+dzh+gC3C\r\n' +
+      'xrt53S6VrPlO5kpvlMjUjd5LDTIa05Kz+pIXVXUJSY5zNEWtQ54ne3TIHoqpT8oB\r\n' +
+      '8aQ+AnUKznf3Q5S3hQSA0P/zeIcbLwUvDGwk5GI+X38vNm6zbg+fhLwKi0E87fGE\r\n' +
+      '4ZM1w+D5Cfzl6AOP8QTnM9Az/g7z+nlslfh1uS4O87WNnETXyFqOKuMK5MsAYBmg\r\n' +
+      'mctsteL7lHbOcmATAX0MjGfewqvh3uVm18xg3S8RHbsQ42IC6NGDS7YfYI/ePrGu\r\n' +
+      'hdaTeUJuQVm8vSseL5vTeINLbWG7znV4avDgFDx6V+lL77relJ6dQQkRoCf/SNc4\r\n' +
+      'r3v2I1Dd7I77+AT/uBZ3laKsqQcUhcjhEb2iLzjWpZNnO54VhqILwVD8AU8QMQpu\r\n' +
+      'bjMxDXNKY9nhDWZtCoSEcbmvReo5dYXLCAjUokd2utwT8xTj+D7MADWKLTIfUO4H\r\n' +
+      '/OKq26mKCZq/6xgoLzXpiQeDxBfojJA4HWvZTmPlqH2VzIihKNFgP3QD1TH/nPCp\r\n' +
+      'LP0SULTuszYNMTbOOmPj8sYK57dVJUJc2/TiUr1rbxSEEnBL/y4BBUmWxESzNJuO\r\n' +
+      'ohJcR8rnyeklB5tnB5KzYuJqb5Do8PX7h7sGKZWOX0JAQkyq6QNSpJPR3PQ4tTSo\r\n' +
+      'vt2pn/+3Uj+9uEvMpYroJEaOMKW3kGL+PUxLg5xMmOWR86jGqHmfY/zx/sx0eiYL\r\n' +
+      'xXlD7KzaNuBLKGnTv/7fK7OzNc9bmS+pIeru8jtPIm6i6u+mQ/byIehjIPYxR1m/\r\n' +
+      'WBu7LJv4LALDHUh93Fnr92sdWkiV9yU5m5dMIgWzcT2Qis148p+y+w1teqJEnYsN\r\n' +
+      '7Ea1cRRbG/HXB4EmOuwI9oDU5so4gYqQKwv0YvL1P93AzxN0v5iA8g9JIcWD8jun\r\n' +
+      'CeyV90HiPa/shqk/xMbwQTypfFK0kGSPPeCJNBeAmLlM4RoTdGHY7pSoYyuRaRZj\r\n' +
+      'TgBfHT4WxQA5Wttp/rLlbJbyt0vabH15gyjE0WpYOItPh11ONchShJXh5/6zEyDS\r\n' +
+      'Nyn6TjFLmoDqHRSIxNraYQd2q7e11v9CJX0eoqljjst0LAWPFZ7X4m+kSQtoTdzt\r\n' +
+      'tuiPqkBY8wFokG/Mo0mDKwfTT1ZYSdygJZr8ZrNF+hXmBJN9mm4/0S+hN4Vtx/wm\r\n' +
+      'KKWeUOysbqOl3r0EHhh0Mugo2ApTABBDwzoLy7UDXfmJT8T7M0hh+ZT1Pja+G1YL\r\n' +
+      '/HrGHx8eAQQj0c4hyqePC86jgSh3iIkaBJFgEpytfJAnRZ/qr50YK5G7J6R2EjrL\r\n' +
+      '8IGcABSimIidvn0gQ0fBB//LR3con+KjugsA8cTC/cTwfSoyWr9K9FhjpmQ0rxUZ\r\n' +
+      'uE12auhTB2dNdCoOwai97jREVngGaL5GTqUqowNeUUXbedhQI5sRKphrRoinYjJ1\r\n' +
+      'uPuJDLVEsur2pkenLZLZn4l0Srgq1KAOBuZzqqDT6adxfQn3eKN6H0XHja9HMYU5\r\n' +
+      'eXNDEZyT+W6Xg4HcHtiH751LF62SR74GR1HiU3B1XXryXpxyBMGbzdypIDRR6PZb\r\n' +
+      '4o6na3Kx8nyYztI6KZ1Y4PukFqsYuCjFqjJDf9KtFM9eJyedSlsYGd2XDVMUpIlC\r\n' +
+      'NQ9VbRk+hDoH+J74upvX3cbASGkjmuu6sIKdt+quV2vdbyCKukayeWBXVP8bCW4Z\r\n' +
+      'ft0UIf8QIPmkP6GQ3F2qn/SjJI7WhrdGh04tpC0QuMdRLzJnd+R/tjA/QisCWxAW\r\n' +
+      '3WETPDphJMTqKHAUx/86VDSGV013uCrOkTXvuGJLOTl3mdbEj2+0+DLOE2APBsJc\r\n' +
+      'O0Lt5P0Oouigbj+wSVz20Fg7QhXO8Wep7S0krHAXJv3FdV0Cdn6MXaxeCBOfY4Rf\r\n' +
+      'MDUmN/xaiMk2mz7dfDRhg8OADNacg60RylM9jEQ1UclXNlzEBUseY7x3R7qqyeXz\r\n' +
+      '8zDQeCXj+PHFBm48fEvKhP5sqHNNZsB5cy53y6nVwM2Nb9XBOmVajX2kUSgQE3GQ\r\n' +
+      'HdCZE45Gx9FNP+tG6fYRnOx33ABnJdYwcN4s7xNwBXlTFp2t4CLWPDjwXUSBPudh\r\n' +
+      '2Hy/IzXic86kMmpl09WvPY89eFQ9o1laR4y7M5vnx+GMpCGkxmYZBbOZIGESVoy0\r\n' +
+      '70R7mkVJDFpPQg8FONTNzJki4ggZ2osWBy9fHbE1DvM+MqZe+4zpikjeMwoqmsK4\r\n' +
+      'flvcaautoiwLChpiG0/tjMw13JLPMW3ZMwQDfZXYta2ngT35X2iKcgZTykNSeVJe\r\n' +
+      'bB+ABC1Q9+R9/xlmlrBIUzzZHjNWr2FqDfDvbIIhURYmOqojtncOCttvEg2BUKSU\r\n' +
+      'DdHwTay9R34YmeM6GjzjAcJWY5PJUy+kYnD5Drkp0CNL3LSxoCuQEMqudFz/fMU/\r\n' +
+      'C3PogT6Ncnkr1FVu4uvs3ujG2ufu2YaGrLcYw2/N1yOZJWnnz07goD94VtyojMNc\r\n' +
+      'WTmKni3mHDobNYwHWiRW+g1vxptOH+u5efUlDuz9nnn6cOnqR73Xuht3wDOpyn/N\r\n' +
+      'VfvhZLRa3xeTTVHFqQFU+oqPjTV9H6y58zWpGhu8HOvsBcMmU/FQS6mfK7ebPGGi\r\n' +
+      'jboKbwLpHYFewi01tYgfqwn6hMMrzYPjJY1tsYJ8NIAsmRHkG70t70PVeTQ8cJoC\r\n' +
+      'Fm2IFDsZV/iTDdrBqvRcyBo0XmONAQQKr7rk/90eM4fd8nxGm/cAp/67NotSWQHa\r\n' +
+      'ZFdxhOPSBr6VBiS8SAfF1rIra8funxtQ5Yk04FPjsVotkm2nkMt4gntoM2b3w23Q\r\n' +
+      'GBaNcyPikhkQ8UC80Fbz6UzyLBKbZqCDI/GSa1A4BSvp0vy1pndHzrynyytF4t80\r\n' +
+      'r3I7e0M0SEHWYJFGmQ9szh3cXePvk0p5KJIu1BzPH6AoIK0dNRXQXAINnsxmpkeJ\r\n' +
+      '7pAkz0rIVxZ4SyH4TrZcXxnVJ0Gte9kd/95XSEZDyvT9Arhs/0jHzotxaua6wpK3\r\n' +
+      'JFF4BEmRPE7U3PsPJQN1fm6mqOdmaCE0UrnLhaMf8uMzYOoXVV8A5eRIDtgJ3X8V\r\n' +
+      'k6UkNbDt8mVlctLdkNM9tKkClaF4JnvyYYX16HS5sAJiZnM8vW46nh4KsYIVRqAf\r\n' +
+      'DaAeJzxRTSInaW52tuDqrBPVnl5XiAKbrved1fOUSSorI+SptHzaHcIH20h2DuSJ\r\n' +
+      'ryQnLseZ+F3sb7wdAUtQb6eMNvu7L3s1vBxKqKKlwAVuZEqQI/GT/5WAB34iul3U\r\n' +
+      'hAZZX0xKfweRp27xLRyUiqGFAsOaoDIwRiDhVKJZVCwIa3dSKCW8jvmC+EaeSyKG\r\n' +
+      'Wx7gGnJm9XovdI1hi/zHM60ABejiMnDeAACcvsCJqKXE/9YDFQF+yW30OSZ2AOUL\r\n' +
+      'UWnyD493R347W2oPzV1HbYLd//2gIQFFeMDv0AWfJGv4K0JkZ/pGpaPAoee6Pd1C\r\n' +
+      'OjcxbWhvbEwXDtVRFztCjgNd/rp4t+YQ9QbMczK3HplpMyYjIs0WdTU3gNWqmTEf\r\n' +
+      'guOokh7tqlOHQso0gg3ax65vc2k9V2yLJz2CDkVkATKpJOjV4sNWGPnB4129xact\r\n' +
+      'p9JfGDAWniAE4cYW/etNTXhNWJTzkSlb5Ad5JPPQ4p/lB97Xr/Krwjp1o3h2JTgC\r\n' +
+      'IBfqb9g7WQ/B8EL0AwnoHxPDTdXAHOCiUr0y1M1w36thr56AVR97/R02k2XI3dxv\r\n' +
+      'oS/bCgNtFFSao2O7uANqtU/SMHMl0BrR8dk+4924Wu0m06iNDZB8NU0jU5bqxcW6\r\n' +
+      'wzf/rjqwIndehfpH7MkeCk6rM0JiVku/EKoCfg9DOAA2rLIiyWO2+mm5UWiT60a0\r\n' +
+      'kmGwwrAxduugMnfVdb5fI8F+IyXYCH8Iwi6qpFvSLm4F/++0WP6pD1Xov6cRu9Eq\r\n' +
+      'nQ4FcCFQJ62ymKlZ0+qZ1ywftKTRwNNlPfZezkqJm17sDI02AUAjGotxrSdDfca5\r\n' +
+      'ViRxq+HJiQGVCUo4fEl4iMzSWaBLeQr9nSijB76dyq1e89NMXS0L3Uo6B7gbKm2i\r\n' +
+      'AjRTUEN2LIGM7TiRC4kZRRMrgVcBBDAtuyY/sMDZ6bUageLXlAPSGZ+VY/a+usok\r\n' +
+      'pxP+U88X7mkxuvvPIG7yKaxymdB993pRaVvLuPVcZRDmXIFrTSP5wxejRQpIvwNR\r\n' +
+      'UeYwGQs1gAuM3l6N7trX99j6WBzZr09YRVPgehh5N3s/omrEMDMcExlmAdVOYNij\r\n' +
+      'UN5NOZgPZrHTev4BtZa53FKttvGT9Ly9iLtle218tQyJRK7UQ/APZJzidpcy3p/x\r\n' +
+      'U9AgXG9+horGLG4/HAmpZh4VH+8wXpiUxsC2rXLb0cAoFg03gStLvqXU93UU6KSn\r\n' +
+      'xC0FYZZAqeFDdKbk4IMirklafEu+j45I+57RiCr7mpOyDI4o5FItWMzSxFo06ciw\r\n' +
+      'aUT4eQf+pUFrBz0yUvgJArh3+VZdRhd8vycuxrYgfp9q4H1n2hOEOi/eeQCuJH36\r\n' +
+      'RnAkToyRYwCepD3di2tf5FL2cW2DPMj69o7dIUHEn76SKVtgwmv5Q86rBWTecAn1\r\n' +
+      'qkUXMst6qxyZCqHMsrQ0Bf7lcB9nSTvPXHzbJjLg0QRYi4qZzU46Vmo5bLw0l8R/\r\n' +
+      '66Wyv+OIastQdCB6S1JtRnE2zvR7nRA/TgfmbJBklgEUY9KeyRzh1Vkp7aykuMXV\r\n' +
+      '9bsND+1swzKgqTGxCyMMqIP6OQsr9AVlO4MsR8XCTOY4F/dTaCRHWXC/uvtuar/y\r\n' +
+      '8vFQeaUPSR10+XGxYb7tnaaBqdVy9MMuwz7Y3jYgvbfxku6aXJMyWFBRqCRskOZa\r\n' +
+      'GQOMmb0j9QH/bl6goHBfCJjSSU+vkVytQf7ZtWyD+k4+R3X+nQEex0Eb+2nfzh3i\r\n' +
+      'ZHSO7cqRz12/B8CmQ75L8suRcRrqINMdAZfmARp5s0UtmHYKbOcrxd4l625rUwTJ\r\n' +
+      't0vih8+BK6k1F6oT1kCR6ZyfIHhh8dn22SYJAQFW3+WZsaPjLgkh0ihcyfhLfKMC\r\n' +
+      'K3YvF/dt9rQDorwNwp5+xiuGUrwk7SLbc7wmNCFiD5nER3AhUSuGzQLfZzjeqYgK\r\n' +
+      'Wge2QCPwtwzaHNp51c5QMvKqQfsg12P81qs3Jl/j+xKpzLh2vLYlnq8OuFd3lR6x\r\n' +
+      'q0Rya6j4o+AqW/v1CJBRhS0qXTW/bHvPm8uU16Uw9W4AGPnISbLQh5sfOKkKgNN/\r\n' +
+      'jTogehgId2rZ1VfhW7n9xvPkk2NEt+YmXHn7EuPri6GXPIDhaLWLaSpa8PYW+jxx\r\n' +
+      'T0CDjYQkT/Q/TfuX3yzGHXKhMInKxjqihd1RQ2OIBLBF8/1UFNLM82XntXt2TJXK\r\n' +
+      'kUQYAIJxH23h9ZBH2K3T2gNjOqLmiqE0C4QEW8xNO75TWiYm8j+sX2LmdYmXZP8C\r\n' +
+      'iMlyE2shMVriN3t457D8S5a1aEvATDFxM4YL5k5OsZ6HrQ6PrnzZfrWXh5OxoxAU\r\n' +
+      '+FCXxpRi6lwY3yNi3kUteexRLZGrEz2FRPemDLsevShRqnsy/0OA/05TA6JxLVpd\r\n' +
+      'Dd7ZWUBcIJZ7lQKMzfCAdWR20J7ngEuiUksQDo5h9/727aO/fbVh+aLVYY1EF+0p\r\n' +
+      '8gbM3/hyoGd8pujWqU1U7jLQACAp5zsy7xvnbiXYl42SaF1PFUP5aZrAPBcj0Fru\r\n' +
+      't8SnPjys2JE172lCkQQOBglanklkpRiBDWYxG8josUyASo7EzddOneLNoMNl8+ZO\r\n' +
+      'ZZYN6BRIioChYDsrrPZiootTU5DYC8a0/AcDsV6PQ48SlInCKtuAOi8nHJDVUzBI\r\n' +
+      'QkDd13kAeIFEMOJUV17xh7eLpbe10bv1B8zUiMbvBTzWPXZHEbuNlWiGy960J4t3\r\n' +
+      'x6NGEAfIjYg9+aMCf7uiEWd48s+nrKWymn7Ewg7llyMfK2Vsa9PVMilopGx42y51\r\n' +
+      'HMIzSV4TjOxSAJmXFZs55w57Rqjx3+LP9P7Ilpde4Lh35hD6yX5hZW+gnQs+B/j8\r\n' +
+      'DkBDeIYtMSz4tHqiK6rBUD/KnNUYYmOOGUi/bPyS4TH0ycbSFp1xx+rS/86Uh8YK\r\n' +
+      'wSOVkKvL2VhGE5G0RSSvYLUkEPcjA8K+EaHf8aCWpnGmpr3rT7F00JFhmH/kDXcU\r\n' +
+      'rtatu8Lniqm0sIV84nVEqHF9Vgz1D2d2/VYfLWlMDM5Mb8IWVUi8fjNFQf32bTCZ\r\n' +
+      'ZYTNUSushCwwpo2R8akkURsev+zstIzw73MGldj2AJ6y/0h51Z4dpQuJbwsKIw4g\r\n' +
+      '5MH42cM4PwiQ7hpqDeGLoyfeAMRFnme/HZCsgBCv247KXdpuYolORXBwjiqhlXYl\r\n' +
+      '6W5aUXp7H+Idz+ahq+nEdsGR57lX1dCC731i8x7/0fl7LEAPGCgr3A0UqTesBKqV\r\n' +
+      '5iq03xmxLhXEyv5QJVPCmG2067Wuoi9hMbXWb/IuX6TV2GACuZ54x9ftWtrPtZ7J\r\n' +
+      'bJEst/IK1SvODlNpk3Z8jcx8YFS7RzjrI3CuVrn45HXF5yHlzwiyBnaFiuBXaDFk\r\n' +
+      'kFGnTIxDrDfBsxCN7v3snuf+eW41SaXv8BHAvi4A+cv5vpSduEGY+aZWdgLDsnxw\r\n' +
+      '+zU5GUhNuT28YKEYzyTnMTdo/QL1KZkFqRDqANeRK3V24OaxHt6sbxYuRLGphytc\r\n' +
+      'uUnB6ICpHgwASejiJY/hWhm5PLI3jxdXAa7XOg7asESz1yo7FrJIwW7UlnNBOneA\r\n' +
+      'yuYFdB0usNx+E63hsw+TJ9Sg0+t+mG2+Fr1hE2qEahF2BrrB9LW0xuTXmAeW2qHp\r\n' +
+      'cOVLJigo9QsEy3Y/sPuDJC0z9MnsKefglpSZyGBxkpKtVN7ePHl/hmMBRD6W1aZ0\r\n' +
+      '8bdl0Ljj6SoT9DB8qqyUX3Km/5xSWguvp2hMa1s/J+dJAzOOGx9P94QOgggrImOR\r\n' +
+      'yhMa/3i5qA9QPzT0ivMtQwS5HaGL6Hjv6jkmK1FzfCoOE8d6+9AuhvvbfZs3c0Wf\r\n' +
+      '31F5e09s6fPqXTk3Dw6TsiED+NjtTTywPEaNgjldpPjZPBpAl6pNx/i9KghBmaCG\r\n' +
+      'LDsvFJ/BqZf1qYFKE47Ozf8jQ4b+ZgU37awZAKERnoEvPdJ3gv5H+pyjbYbacLG4\r\n' +
+      '2jF/pRzhiF0eRBuqY/5DrgMe1dkI9TNvBFzsX4YFOxZWca/kc26JhCajuH8MaTyW\r\n' +
+      'LzOeIg6QKane6HBBxRvoOBMIa40oBhffbOi5FKukKUFS3xlPL3EwdS/aZK61vCR2\r\n' +
+      'NPS7Y/d2vk80aNVRZAm2FBcmBWF6q7A825S7HqwM1izmlmqC6yWYXGofP8PuYfww\r\n' +
+      'eWW5rm+3URjcRM54K5Ob7rfKu3q7zUwUAB6R7YM9pgeDbaARyE7mB0MmpB+3UqO8\r\n' +
+      'F5heKtELqIskZGAiCKxGPKERoHPItKTV77ZCZ+ql0FjlJSrXVZ1P/9i/BiwdYmij\r\n' +
+      'vhjCEtDcHWPXXIra6Hf5hTIUJ7conZ9ldGhHliV6Rso7ST1FGIsqrgYDyt1/+Vo4\r\n' +
+      'hNBaUhWOHh65EKRblCW04v71KyaL8ms7Pevgcz4NZFtUwv3v2qI+OqdWBFFbc9Lr\r\n' +
+      'cfiyt5XbZeUD4GiI5/wDVk0b07ev7xyoedeB7GvXgmb13D1vCtHYubeDyI+V7zlM\r\n' +
+      'GXPvCkIPhj34fK6vhtHJIfHL3+8vf6emd7h4Ziakod9G0HYJTKbugpCmi6ejW8G9\r\n' +
+      'X5Kzrn9c8HD7bNCUtwNFV0unoZUN3ReVAOLNn2N0LUfHBrlq/XwseHovUbzSomYT\r\n' +
+      'Xtr/w+tiLSMSRLsJzAu0LJHgNtYPsPIavpim0OLTPg7JBmnzWoyEFCXcLvjNry6c\r\n' +
+      'yCgA4RgfmBcJzXS1Uyf/TUM9IFoeTbGo9dIziygUdWXxrUzx2Uyak53xZXEX82cB\r\n' +
+      'kC/v1+VCq668xgthc9pEEHIsqxKuRCUXj53xYThI5gSJke3XYrCdk3R8rh8FdkkQ\r\n' +
+      'E/4WFpZ8kqraFXSYlfYvGHYd31cbJoSxjTIISd5US85KaOH2n3HN0d017xfwaSqS\r\n' +
+      'I1l1iutPvcc+wxydp7On+uQAP4GiV1uPmuN0s0lu81j7ye9nS+fjxlXiukHQu1mF\r\n' +
+      'c5IdEASgborfk+mrVpl/hpeLJH4LZIGPaZgr3KDBZPDMgqDCXBphL+GjJYPXyW7I\r\n' +
+      't3QRCKMTNHCO7E3e7eet7k2ADSjrN1eZuzo7FxCU6cv+oCQUWPzaRYWb6gzr2QV4\r\n' +
+      'snvwM2sGc0Mkg1QnJAzT6zrtfVZ2uh2VwkN93u8KxwiiCRn53rHn46uW1djNHmIe\r\n' +
+      '4E2vS4IWoCmy59lGxV6UEfsjEGxC+pDv33xX69aDf8vN6VON8B4ooHwdg+GMe2Us\r\n' +
+      'N7sQkhf1ykdR0tmJnG8yr0DfGfxbcJArEv8wcZh89M0oOY7iKx/hq4n4DSVHLmDg\r\n' +
+      'obV4S2+c5aRrVFWQiw+/OjA9MCEwCQYFKw4DAhoFAAQUXolDwewLkmOH6dGcPdhJ\r\n' +
+      'JeUrAz0EFHRZbCAQ2bUo5B8DAFM8VJLi/+A2AgIEAA=='
+  };
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/forge'
+  ], function(FORGE) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      FORGE
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/forge'));
+}
+
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/pkcs7.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/pkcs7.js
new file mode 100644
index 0000000..2c4e793
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/pkcs7.js
@@ -0,0 +1,350 @@
+(function() {
+
+function Tests(ASSERT, PKCS7, PKI, AES, DES, UTIL) {
+  var _pem = {
+    p7: '-----BEGIN PKCS7-----\r\n' +
+      'MIICTgYJKoZIhvcNAQcDoIICPzCCAjsCAQAxggHGMIIBwgIBADCBqTCBmzELMAkG\r\n' +
+      'A1UEBhMCREUxEjAQBgNVBAgMCUZyYW5jb25pYTEQMA4GA1UEBwwHQW5zYmFjaDEV\r\n' +
+      'MBMGA1UECgwMU3RlZmFuIFNpZWdsMRIwEAYDVQQLDAlHZWllcmxlaW4xFjAUBgNV\r\n' +
+      'BAMMDUdlaWVybGVpbiBERVYxIzAhBgkqhkiG9w0BCQEWFHN0ZXNpZUBicm9rZW5w\r\n' +
+      'aXBlLmRlAgkA1FQcQNg14vMwDQYJKoZIhvcNAQEBBQAEggEAJhWQz5SniCd1w3A8\r\n' +
+      'uKVZEfc8Tp21I7FMfFqou+UOVsZCq7kcEa9uv2DIj3o7zD8wbLK1fuyFi4SJxTwx\r\n' +
+      'kR0a6V4bbonIpXPPJ1f615dc4LydAi2tv5w14LJ1Js5XCgGVnkAmQHDaW3EHXB7X\r\n' +
+      'T4w9PR3+tcS/5YAnWaM6Es38zCKHd7TnHpuakplIkwSK9rBFAyA1g/IyTPI+ktrE\r\n' +
+      'EHcVuJcz/7eTlF6wJEa2HL8F1TVWuL0p/0GsJP/8y0MYGdCdtr+TIVo//3YGhoBl\r\n' +
+      'N4tnheFT/jRAzfCZtflDdgAukW24CekrJ1sG2M42p5cKQ5rGFQtzNy/n8EjtUutO\r\n' +
+      'HD5YITBsBgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBBmlpfy3WrYj3uWW7+xNEiH\r\n' +
+      'gEAm2mfSF5xFPLEqqFkvKTM4w8PfhnF0ehmfQNApvoWQRQanNWLCT+Q9GHx6DCFj\r\n' +
+      'TUHl+53x88BrCl1E7FhYPs92\r\n' +
+      '-----END PKCS7-----\r\n',
+    certificate: '-----BEGIN CERTIFICATE-----\r\n' +
+      'MIIDtDCCApwCCQDUVBxA2DXi8zANBgkqhkiG9w0BAQUFADCBmzELMAkGA1UEBhMC\r\n' +
+      'REUxEjAQBgNVBAgMCUZyYW5jb25pYTEQMA4GA1UEBwwHQW5zYmFjaDEVMBMGA1UE\r\n' +
+      'CgwMU3RlZmFuIFNpZWdsMRIwEAYDVQQLDAlHZWllcmxlaW4xFjAUBgNVBAMMDUdl\r\n' +
+      'aWVybGVpbiBERVYxIzAhBgkqhkiG9w0BCQEWFHN0ZXNpZUBicm9rZW5waXBlLmRl\r\n' +
+      'MB4XDTEyMDMxODIyNTc0M1oXDTEzMDMxODIyNTc0M1owgZsxCzAJBgNVBAYTAkRF\r\n' +
+      'MRIwEAYDVQQIDAlGcmFuY29uaWExEDAOBgNVBAcMB0Fuc2JhY2gxFTATBgNVBAoM\r\n' +
+      'DFN0ZWZhbiBTaWVnbDESMBAGA1UECwwJR2VpZXJsZWluMRYwFAYDVQQDDA1HZWll\r\n' +
+      'cmxlaW4gREVWMSMwIQYJKoZIhvcNAQkBFhRzdGVzaWVAYnJva2VucGlwZS5kZTCC\r\n' +
+      'ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMsAbQ4fWevHqP1K1y/ewpMS\r\n' +
+      '3vYovBto7IsKBq0v3NmC2kPf3NhyaSKfjOOS5uAPONLffLck+iGdOLLFia6OSpM6\r\n' +
+      '0tyQIV9lHoRh7fOEYORab0Z+aBUZcEGT9yotBOraX1YbKc5f9XO+80eG4XYvb5ua\r\n' +
+      '1NHrxWqe4w2p3zGJCKO+wHpvGkbKz0nfu36jwWz5aihfHi9hp/xs8mfH86mIKiD7\r\n' +
+      'f2X2KeZ1PK9RvppA0X3lLb2VLOqMt+FHWicyZ/wjhQZ4oW55ln2yYJUQ+adlgaYn\r\n' +
+      'PrtnsxmbTxM+99oF0F2/HmGrNs8nLZSva1Vy+hmjmWz6/O8ZxhiIj7oBRqYcAocC\r\n' +
+      'AwEAATANBgkqhkiG9w0BAQUFAAOCAQEAvfvtu31GFBO5+mFjPAoR4BlzKq/H3EPO\r\n' +
+      'qS8cm/TjHgDRALwSnwKYCFs/bXqE4iOTD6otV4TusX3EPbqL2vzZQEcZn6paU/oZ\r\n' +
+      'ZVXwQqMqY5tf2teQiNxqxNmSIEPRHOr2QVBVIx2YF4Po89KGUqJ9u/3/10lDqRwp\r\n' +
+      'sReijr5UKv5aygEcnwcW8+Ne4rTx934UDsutKG20dr5trZfWQRVS9fS9CFwJehEX\r\n' +
+      'HAMUc/0++80NhfQthmWZWlWM1R3dr4TrIPtWdn5z0MtGeDvqBk7HjGrhcVS6kAsy\r\n' +
+      'Z9y/lfLPjBuxlQAHztEJCWgI4TW3/RLhgfg2gI1noM2n84Cdmisfkg==\r\n' +
+      '-----END CERTIFICATE-----\r\n',
+    privateKey: '-----BEGIN RSA PRIVATE KEY-----\r\n' +
+      'MIIEowIBAAKCAQEAywBtDh9Z68eo/UrXL97CkxLe9ii8G2jsiwoGrS/c2YLaQ9/c\r\n' +
+      '2HJpIp+M45Lm4A840t98tyT6IZ04ssWJro5KkzrS3JAhX2UehGHt84Rg5FpvRn5o\r\n' +
+      'FRlwQZP3Ki0E6tpfVhspzl/1c77zR4bhdi9vm5rU0evFap7jDanfMYkIo77Aem8a\r\n' +
+      'RsrPSd+7fqPBbPlqKF8eL2Gn/GzyZ8fzqYgqIPt/ZfYp5nU8r1G+mkDRfeUtvZUs\r\n' +
+      '6oy34UdaJzJn/COFBnihbnmWfbJglRD5p2WBpic+u2ezGZtPEz732gXQXb8eYas2\r\n' +
+      'zyctlK9rVXL6GaOZbPr87xnGGIiPugFGphwChwIDAQABAoIBAAjMA+3QvfzRsikH\r\n' +
+      'zTtt09C7yJ2yNjSZ32ZHEPMAV/m1CfBXCyL2EkhF0b0q6IZdIoFA3g6xs4UxYvuc\r\n' +
+      'Q9Mkp2ap7elQ9aFEqIXkGIOtAOXkZV4QrEH90DeHSfax7LygqfD5TF59Gg3iAHjh\r\n' +
+      'B3Qvqg58LyzJosx0BjLZYaqr3Yv67GkqyflpF/roPGdClHpahAi5PBkHiNhNTAUU\r\n' +
+      'LJRGvMegXGZkUKgGMAiGCk0N96OZwrinMKO6YKGdtgwVWC2wbJY0trElaiwXozSt\r\n' +
+      'NmP6KTQp94C7rcVO6v1lZiOfhBe5Kc8QHUU+GYydgdjqm6Rdow/yLHOALAVtXSeb\r\n' +
+      'U+tPfcECgYEA6Qi+qF+gtPincEDBxRtoKwAlRkALt8kly8bYiGcUmd116k/5bmPw\r\n' +
+      'd0tBUOQbqRa1obYC88goOVzp9LInAcBSSrexhVaPAF4nrkwYXMOq+76MiH17WUfQ\r\n' +
+      'MgVM2IB48PBjNk1s3Crj6j1cxxkctqmCnVaI9HlU2PPZ3xjaklfv/NsCgYEA3wH8\r\n' +
+      'mehUhiAp7vuhd+hfomFw74cqgHC9v0saiYGckpMafh9MJGc4U5GrN1kYeb/CFkSx\r\n' +
+      '1hOytD3YBKoaKKoYagaMQcjxf6HnEF0f/5OiQkUQpWmgC9lNnE4XTWjnwqaTS5L9\r\n' +
+      'D+H50SiI3VjHymGXTRJeKpAIwV74AxxrnVofqsUCgYAwmL1B2adm9g/c7fQ6yatg\r\n' +
+      'hEhBrSuEaTMzmsUfNPfr2m4zrffjWH4WMqBtYRSPn4fDMHTPJ+eThtfXSqutxtCi\r\n' +
+      'ekpP9ywdNIVr6LyP49Ita6Bc+mYVyU8Wj1pmL+yIumjGM0FHbL5Y4/EMKCV/xjvR\r\n' +
+      '2fD3orHaCIhf6QvzxtjqTwKBgFm6UemXKlMhI94tTsWRMNGEBU3LA9XUBvSuAkpr\r\n' +
+      'ZRUwrQssCpXnFinBxbMqXQe3mR8emrM5D8En1P/jdU0BS3t1kP9zG4AwI2lZHuPV\r\n' +
+      'ggbKBS2Y9zVtRKXsYcHawM13+nIA/WNjmAGJHrB45UJPy/HNvye+9lbfoEiYKdCR\r\n' +
+      'D4bFAoGBAIm9jcZkIwLa9kLAWH995YYYSGRY4KC29XZr2io2mog+BAjhFt1sqebt\r\n' +
+      'R8sRHNiIP2mcUECMOcaS+tcayi+8KTHWxIEed9qDmFu6XBbePfe/L6yxPSagcixH\r\n' +
+      'BK0KuK/fgTPvZCmIs8hUIC+AxhXKnqn4fIWoO54xLsALc0gEjs2d\r\n' +
+      '-----END RSA PRIVATE KEY-----\r\n',
+    encryptedData: '-----BEGIN PKCS7-----\r\n' +
+      'MIGHBgkqhkiG9w0BBwagejB4AgEAMHMGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQI\r\n' +
+      'upMFou5X3DWAUAqObuHSlewM0ZtHzWk9MAmtYb7MSb//OBMKVfLCdbmrS5BpKm9J\r\n' +
+      'gzwiDR5Od7xgfkqasLS2lOdKAvJ5jZjjTpAyrjBKpShqK9gtXDuO0zH+\r\n' +
+      '-----END PKCS7-----\r\n',
+    p7IndefiniteLength: '-----BEGIN PKCS7-----\r\n' +
+      'MIAGCSqGSIb3DQEHA6CAMIACAQAxggHGMIIBwgIBADCBqTCBmzELMAkGA1UEBhMC\r\n' +
+      'REUxEjAQBgNVBAgMCUZyYW5jb25pYTEQMA4GA1UEBwwHQW5zYmFjaDEVMBMGA1UE\r\n' +
+      'CgwMU3RlZmFuIFNpZWdsMRIwEAYDVQQLDAlHZWllcmxlaW4xFjAUBgNVBAMMDUdl\r\n' +
+      'aWVybGVpbiBERVYxIzAhBgkqhkiG9w0BCQEWFHN0ZXNpZUBicm9rZW5waXBlLmRl\r\n' +
+      'AgkA1FQcQNg14vMwDQYJKoZIhvcNAQEBBQAEggEAlWCH+E25c4jfff+m0eAxxMmE\r\n' +
+      'WWaftdsk4ZpAVAr7HsvxJ35bj1mhwTh7rBTg929JBKt6ZaQ4I800jCNxD2O40V6z\r\n' +
+      'lB7JNRqzgBwfeuU2nV6FB7v1984NBi1jQx6EfxOcusE6RL/63HqJdFbmq3Tl55gF\r\n' +
+      'dm3JdjmHbCXqwPhuwOXU4yhkpV1RJcrYhPLe3OrLAH7ZfoE0nPJPOX9HPTZ6ReES\r\n' +
+      'NToS7I9D9k7rCa8fAP7pgjO96GJGBtCHG1VXB9NX4w+xRDbgVPOeHXqqxwZhqpW2\r\n' +
+      'usBU4+B+MnFLjquOPoySXFfdJFwTP61TPClUdyIne5FFP6EYf98mdtnkjxHo1TCA\r\n' +
+      'BgkqhkiG9w0BBwEwFAYIKoZIhvcNAwcECFNtpqBmU3M9oIAESM+yyQLkreETS0Kc\r\n' +
+      'o01yl6dqqNBczH5FNTK88ypz38/jzjo47+DURlvGzjHJibiDsCz9KyiVmgbRrtvH\r\n' +
+      '08rfnMbrU+grCkkx9wQI1GnLrYhr87oAAAAAAAAAAAAA\r\n' +
+      '-----END PKCS7-----\r\n',
+    p73des: '-----BEGIN PKCS7-----\r\n' +
+      'MIICTQYJKoZIhvcNAQcDoIICPjCCAjoCAQAxggHGMIIBwgIBADCBqTCBmzELMAkG\r\n' +
+      'A1UEBhMCREUxEjAQBgNVBAgMCUZyYW5jb25pYTEQMA4GA1UEBwwHQW5zYmFjaDEV\r\n' +
+      'MBMGA1UECgwMU3RlZmFuIFNpZWdsMRIwEAYDVQQLDAlHZWllcmxlaW4xFjAUBgNV\r\n' +
+      'BAMMDUdlaWVybGVpbiBERVYxIzAhBgkqhkiG9w0BCQEWFHN0ZXNpZUBicm9rZW5w\r\n' +
+      'aXBlLmRlAgkA1FQcQNg14vMwDQYJKoZIhvcNAQEBBQAEggEAS6K+sQvdKcK6YafJ\r\n' +
+      'maDPjBzyjf5jtBgVrFgBXTCRIp/Z2zAXa70skfxhbwTgmilYTacA7jPGRrnLmvBc\r\n' +
+      'BjhyCKM3dRUyYgh1K1ka0w1prvLmRk6Onf5df1ZQn3AJMIujJZcCOhbV1ByLInve\r\n' +
+      'xn02KNHstGmdHM/JGyPCp+iYGprhUozVSpNCKS+R33EbsT0sAxamfqdAblT9+5Qj\r\n' +
+      '4CABvW11a1clPV7STwBbAKbZaLs8mDeoWP0yHvBtJ7qzZdSgJJA2oU7SDv4icwEe\r\n' +
+      'Ahccbe2HWkLRw8G5YG9XcWx5PnQQhhnXMxkLoSMIYxItyL/cRORbpDohd+otAo66\r\n' +
+      'WLH1ODBrBgkqhkiG9w0BBwEwFAYIKoZIhvcNAwcECD5EWJMv1fd7gEj1w3WM1KsM\r\n' +
+      'L8GDk9JoqA8t9v3oXCT0nAMXoNpHZMnv+0UHHVljlSXBTQxwUP5VMY/ddquJ5O3N\r\n' +
+      'rDEqqJuHB+KPIsW1kxrdplU=\r\n' +
+      '-----END PKCS7-----\r\n'
+  };
+
+  describe('pkcs7', function() {
+    it('should import message from PEM', function() {
+      var p7 = PKCS7.messageFromPem(_pem.p7);
+
+      ASSERT.equal(p7.type, PKI.oids.envelopedData);
+      ASSERT.equal(p7.version, 0);
+
+      ASSERT.equal(p7.recipients.length, 1);
+      ASSERT.equal(p7.recipients[0].version, 0);
+      ASSERT.equal(p7.recipients[0].serialNumber, '00d4541c40d835e2f3');
+
+      // Test converted RDN, which is constructed of seven parts.
+      ASSERT.equal(p7.recipients[0].issuer.length, 7);
+      ASSERT.equal(p7.recipients[0].issuer[0].type, '2.5.4.6');
+      ASSERT.equal(p7.recipients[0].issuer[0].value, 'DE');
+      ASSERT.equal(p7.recipients[0].issuer[1].type, '2.5.4.8');
+      ASSERT.equal(p7.recipients[0].issuer[1].value, 'Franconia');
+      ASSERT.equal(p7.recipients[0].issuer[2].type, '2.5.4.7');
+      ASSERT.equal(p7.recipients[0].issuer[2].value, 'Ansbach');
+      ASSERT.equal(p7.recipients[0].issuer[3].type, '2.5.4.10');
+      ASSERT.equal(p7.recipients[0].issuer[3].value, 'Stefan Siegl');
+      ASSERT.equal(p7.recipients[0].issuer[4].type, '2.5.4.11');
+      ASSERT.equal(p7.recipients[0].issuer[4].value, 'Geierlein');
+      ASSERT.equal(p7.recipients[0].issuer[5].type, '2.5.4.3');
+      ASSERT.equal(p7.recipients[0].issuer[5].value, 'Geierlein DEV');
+      ASSERT.equal(p7.recipients[0].issuer[6].type, '1.2.840.113549.1.9.1');
+      ASSERT.equal(p7.recipients[0].issuer[6].value, 'stesie@brokenpipe.de');
+
+      ASSERT.equal(p7.recipients[0].encryptedContent.algorithm, PKI.oids.rsaEncryption);
+      ASSERT.equal(p7.recipients[0].encryptedContent.content.length, 256);
+
+      ASSERT.equal(p7.encryptedContent.algorithm, PKI.oids['aes256-CBC']);
+      ASSERT.equal(p7.encryptedContent.parameter.data.length, 16);  // IV
+    });
+
+    it('should import indefinite length message from PEM', function() {
+      ASSERT.doesNotThrow(function() {
+        var p7 = PKCS7.messageFromPem(_pem.p7IndefiniteLength);
+        ASSERT.equal(p7.type, PKI.oids.envelopedData);
+        ASSERT.equal(p7.encryptedContent.parameter.toHex(), '536da6a06653733d');
+        ASSERT.equal(p7.encryptedContent.content.length(), 80);
+      });
+    });
+
+    it('should find recipient by serial number', function() {
+      var p7 = PKCS7.messageFromPem(_pem.p7);
+      var cert = PKI.certificateFromPem(_pem.certificate);
+
+      var ri = p7.findRecipient(cert);
+      ASSERT.equal(ri.serialNumber, '00d4541c40d835e2f3');
+
+      // modify certificate so it doesn't match recipient any more
+      cert.serialNumber = '1234567890abcdef42';
+      ri = p7.findRecipient(cert);
+      ASSERT.equal(ri, null);
+    });
+
+    it('should aes-decrypt message', function() {
+      var p7 = PKCS7.messageFromPem(_pem.p7);
+      var privateKey = PKI.privateKeyFromPem(_pem.privateKey);
+      p7.decrypt(p7.recipients[0], privateKey);
+
+      // symmetric key must be 32 bytes long (AES 256 key)
+      ASSERT.equal(p7.encryptedContent.key.data.length, 32);
+      ASSERT.equal(
+        p7.content,
+        'Today is Boomtime, the 9th day of Discord in the YOLD 3178\r\n');
+    });
+
+    it('should 3des-decrypt message', function() {
+      var p7 = PKCS7.messageFromPem(_pem.p73des);
+      var privateKey = PKI.privateKeyFromPem(_pem.privateKey);
+      p7.decrypt(p7.recipients[0], privateKey);
+
+      // symmetric key must be 24 bytes long (DES3 key)
+      ASSERT.equal(p7.encryptedContent.key.data.length, 24);
+      ASSERT.equal(
+        p7.content,
+        'Today is Prickle-Prickle, ' +
+        'the 16th day of Discord in the YOLD 3178\r\n');
+    });
+
+    it('should add a recipient', function() {
+      var p7 = PKCS7.createEnvelopedData();
+
+      // initially there should be no recipients
+      ASSERT.equal(p7.recipients.length, 0);
+
+      var cert = PKI.certificateFromPem(_pem.certificate);
+      p7.addRecipient(cert);
+
+      ASSERT.equal(p7.recipients.length, 1);
+      ASSERT.deepEqual(p7.recipients[0].serialNumber, cert.serialNumber);
+      ASSERT.deepEqual(p7.recipients[0].issuer, cert.subject.attributes);
+      ASSERT.deepEqual(p7.recipients[0].encryptedContent.key, cert.publicKey);
+    });
+
+    it('should aes-encrypt a message', function() {
+      var p7 = PKCS7.createEnvelopedData();
+      var cert = PKI.certificateFromPem(_pem.certificate);
+      var privateKey = PKI.privateKeyFromPem(_pem.privateKey);
+
+      p7.addRecipient(cert);
+      p7.content = UTIL.createBuffer('Just a little test');
+
+      // pre-condition, PKCS#7 module should default to AES-256-CBC
+      ASSERT.equal(p7.encryptedContent.algorithm, PKI.oids['aes256-CBC']);
+      p7.encrypt();
+
+      // since we did not provide a key, a random key should have been created
+      // automatically, AES256 requires 32 bytes of key material
+      ASSERT.equal(p7.encryptedContent.key.data.length, 32);
+
+      // furthermore an IV must be generated, AES256 has 16 byte IV
+      ASSERT.equal(p7.encryptedContent.parameter.data.length, 16);
+
+      // content is 18 bytes long, AES has 16 byte blocksize,
+      // with padding that makes 32 bytes
+      ASSERT.equal(p7.encryptedContent.content.data.length, 32);
+
+      // RSA encryption should yield 256 bytes
+      ASSERT.equal(p7.recipients[0].encryptedContent.content.length, 256);
+
+      // rewind Key & IV
+      p7.encryptedContent.key.read = 0;
+      p7.encryptedContent.parameter.read = 0;
+
+      // decryption of the asym. encrypted data should reveal the symmetric key
+      var decryptedKey = privateKey.decrypt(
+        p7.recipients[0].encryptedContent.content);
+      ASSERT.equal(decryptedKey, p7.encryptedContent.key.data);
+
+      // decryption of sym. encrypted data should reveal the content
+      var ciph = AES.createDecryptionCipher(decryptedKey);
+      ciph.start(p7.encryptedContent.parameter);
+      ciph.update(p7.encryptedContent.content);
+      ciph.finish();
+      ASSERT.equal(ciph.output, 'Just a little test');
+    });
+
+    it('should 3des-ede-encrypt a message', function() {
+      var p7 = PKCS7.createEnvelopedData();
+      var cert = PKI.certificateFromPem(_pem.certificate);
+      var privateKey = PKI.privateKeyFromPem(_pem.privateKey);
+
+      p7.addRecipient(cert);
+      p7.content = UTIL.createBuffer('Just a little test');
+      p7.encryptedContent.algorithm = PKI.oids['des-EDE3-CBC'];
+      p7.encrypt();
+
+      // since we did not provide a key, a random key should have been created
+      // automatically, 3DES-EDE requires 24 bytes of key material
+      ASSERT.equal(p7.encryptedContent.key.data.length, 24);
+
+      // furthermore an IV must be generated, DES3 has 8 byte IV
+      ASSERT.equal(p7.encryptedContent.parameter.data.length, 8);
+
+      // content is 18 bytes long, DES has 8 byte blocksize,
+      // with padding that makes 24 bytes
+      ASSERT.equal(p7.encryptedContent.content.data.length, 24);
+
+      // RSA encryption should yield 256 bytes
+      ASSERT.equal(p7.recipients[0].encryptedContent.content.length, 256);
+
+      // rewind Key & IV
+      p7.encryptedContent.key.read = 0;
+      p7.encryptedContent.parameter.read = 0;
+
+      // decryption of the asym. encrypted data should reveal the symmetric key
+      var decryptedKey = privateKey.decrypt(
+        p7.recipients[0].encryptedContent.content);
+      ASSERT.equal(decryptedKey, p7.encryptedContent.key.data);
+
+      // decryption of sym. encrypted data should reveal the content
+      var ciph = DES.createDecryptionCipher(decryptedKey);
+      ciph.start(p7.encryptedContent.parameter);
+      ciph.update(p7.encryptedContent.content);
+      ciph.finish();
+      ASSERT.equal(ciph.output, 'Just a little test');
+    });
+
+    it('should export message to PEM', function() {
+      var p7 = PKCS7.createEnvelopedData();
+      p7.addRecipient(PKI.certificateFromPem(_pem.certificate));
+      p7.content = UTIL.createBuffer('Just a little test');
+      p7.encrypt();
+
+      var pem = PKCS7.messageToPem(p7);
+
+      // convert back from PEM to new PKCS#7 object, decrypt, and test
+      p7 = PKCS7.messageFromPem(pem);
+      p7.decrypt(p7.recipients[0], PKI.privateKeyFromPem(_pem.privateKey));
+      ASSERT.equal(p7.content, 'Just a little test');
+    });
+
+    it('should decrypt encrypted data from PEM', function() {
+      var result = '1f8b08000000000000000b2e494d4bcc5308ce4c4dcfd15130b0b430d4b7343732b03437d05170cc2b4e4a4cced051b034343532d25170492d2d294ecec849cc4b0100bf52f02437000000';
+      var key = 'b96e4a4c0a3555d31e1b295647cc5cfe74081918cb7f797b';
+      key = UTIL.createBuffer(UTIL.hexToBytes(key));
+
+      ASSERT.doesNotThrow(function() {
+        var p7 = PKCS7.messageFromPem(_pem.encryptedData);
+        ASSERT.equal(p7.type, PKI.oids.encryptedData);
+        ASSERT.equal(p7.encryptedContent.algorithm, PKI.oids['des-EDE3-CBC']);
+        ASSERT.equal(p7.encryptedContent.parameter.toHex(), 'ba9305a2ee57dc35');
+        ASSERT.equal(p7.encryptedContent.content.length(), 80);
+
+        p7.decrypt(key);
+        ASSERT.equal(p7.content.toHex(), result);
+      });
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/pkcs7',
+    'forge/pki',
+    'forge/aes',
+    'forge/des',
+    'forge/util'
+  ], function(PKCS7, PKI, AES, DES, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      PKCS7(),
+      PKI(),
+      AES(),
+      DES(),
+      UTIL()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/pkcs7')(),
+    require('../../js/pki')(),
+    require('../../js/aes')(),
+    require('../../js/des')(),
+    require('../../js/util')());
+}
+
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/random.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/random.js
new file mode 100644
index 0000000..efeec2b
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/random.js
@@ -0,0 +1,70 @@
+(function() {
+
+function Tests(ASSERT, RANDOM, UTIL) {
+  var random = RANDOM();
+
+  describe('random', function() {
+    it('should generate 10 random bytes', function() {
+      random.getBytes(16);
+      random.getBytes(24);
+      random.getBytes(32);
+
+      var b = random.getBytes(10);
+      ASSERT.equal(b.length, 10);
+    });
+
+    it('should use a synchronous seed file', function() {
+      var rand = RANDOM();
+      rand.seedFileSync = function(needed) {
+        return UTIL.fillString('a', needed);
+      };
+      var b = rand.getBytes(10);
+      ASSERT.equal(UTIL.bytesToHex(b), '80a7901a239c3e606319');
+    });
+
+    it('should use an asynchronous seed file', function(done) {
+      var rand = RANDOM();
+      rand.seedFile = function(needed, callback) {
+        callback(null, UTIL.fillString('a', needed));
+      };
+      rand.getBytes(10, function(err, b) {
+        ASSERT.equal(err, null);
+        ASSERT.equal(UTIL.bytesToHex(b), '80a7901a239c3e606319');
+        done();
+      });
+    });
+
+    it('should collect some random bytes', function() {
+      var rand = RANDOM();
+      rand.seedFileSync = function(needed) {
+        return UTIL.fillString('a', needed);
+      };
+      rand.collect('bbb');
+      var b = rand.getBytes(10);
+      ASSERT.equal(UTIL.bytesToHex(b), 'ff8d213516047c94ca46');
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/random',
+    'forge/util'
+  ], function(RANDOM, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      RANDOM,
+      UTIL()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/random'),
+    require('../../js/util')());
+}
+
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/rc2.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/rc2.js
new file mode 100644
index 0000000..2acbe7b
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/rc2.js
@@ -0,0 +1,109 @@
+(function() {
+
+function Tests(ASSERT, RC2, UTIL) {
+  describe('rc2', function() {
+    it('should expand a 128-bit key', function() {
+      var key = UTIL.hexToBytes('88bca90e90875a7f0f79c384627bafb2');
+      var expect = '71ab26462f0b9333609d4476e48ab72438c2194b70a47085d84b6af1dc72119023b94fe80aee2b6b45f27f923d9be1570da3ce8b16ad7f78db166ffbc28a836a4392cf0b748085dae4b69bdc2a4679cdfc09d84317016987e0c5b765c91dc612b1f44d7921b3e2c46447508bd2ac02e119e0f42a89c719675da320cf3e8958cd';
+      ASSERT.equal(RC2.expandKey(key).toHex(), expect);
+    });
+
+    it('should expand a 40-bit key', function() {
+      var key = UTIL.hexToBytes('88bca90e90');
+      var expect = 'af136d2243b94a0878d7a604f8d6d9fd64a698fd6ebc613e641f0d1612055ef6cb55966db8f32bfd9246dae99880be8a91433adf54ea546d9daad62db7a55f6c7790aa87ba67de0e9ea9128dfc7ccdddd7c47c33d2bb7f823729977f083b5dc1f5bb09000b98e12cdaaf22f80dcc88c37d2c2fd80402f8a30a9e41d356669471';
+      ASSERT.equal(RC2.expandKey(key, 40).toHex(), expect);
+    });
+
+    it('should rc2-ecb encrypt zeros', function() {
+      var key = UTIL.hexToBytes('88bca90e90875a7f0f79c384627bafb2');
+      var input = new UTIL.createBuffer().fillWithByte(0, 8);
+      var cipher = RC2.startEncrypting(key, null, null);
+      cipher.update(input);
+      cipher.finish();
+      ASSERT.equal(cipher.output.toHex(), '2269552ab0f85ca6e35b3b2ce4e02191');
+    });
+
+    it('should rc2-ecb encrypt: vegan', function() {
+      var key = UTIL.hexToBytes('88bca90e90875a7f0f79c384627bafb2');
+      var input = new UTIL.createBuffer('vegan');
+      var cipher = RC2.startEncrypting(key, null, null);
+      cipher.update(input);
+      cipher.finish();
+      ASSERT.equal(cipher.output.toHex(), '2194adaf4d517e3a');
+    });
+
+    it('should rc2-ecb decrypt: 2194adaf4d517e3a', function() {
+      var key = UTIL.hexToBytes('88bca90e90875a7f0f79c384627bafb2');
+      var input = new UTIL.createBuffer(UTIL.hexToBytes('2194adaf4d517e3a'));
+      var cipher = RC2.startDecrypting(key, null, null);
+      cipher.update(input);
+      cipher.finish();
+      ASSERT.equal(cipher.output.getBytes(), 'vegan');
+    });
+
+    it('should rc2-cbc encrypt: revolution', function() {
+      var key = UTIL.hexToBytes('88bca90e90875a7f0f79c384627bafb2');
+      var iv = new UTIL.createBuffer(UTIL.hexToBytes('0123456789abcdef'));
+      var input = new UTIL.createBuffer('revolution');
+      var cipher = RC2.startEncrypting(key, iv, null);
+      cipher.update(input);
+      cipher.finish();
+      ASSERT.equal(cipher.output.toHex(), '50cfd16e0fd7f20b17a622eb2a469b7e');
+    });
+
+    it('should rc2-cbc decrypt: 50cfd16e0fd7f20b17a622eb2a469b7e', function() {
+      var key = UTIL.hexToBytes('88bca90e90875a7f0f79c384627bafb2');
+      var iv = new UTIL.createBuffer(UTIL.hexToBytes('0123456789abcdef'));
+      var input = new UTIL.createBuffer(
+        UTIL.hexToBytes('50cfd16e0fd7f20b17a622eb2a469b7e'));
+      var cipher = RC2.startDecrypting(key, iv, null);
+      cipher.update(input);
+      cipher.finish();
+      ASSERT.equal(cipher.output, 'revolution');
+    });
+
+    it('should rc2-cbc encrypt w/binary string iv: revolution', function() {
+      var key = UTIL.hexToBytes('88bca90e90875a7f0f79c384627bafb2');
+      var iv = UTIL.hexToBytes('0123456789abcdef');
+      var input = new UTIL.createBuffer('revolution');
+      var cipher = RC2.startEncrypting(key, iv, null);
+      cipher.update(input);
+      cipher.finish();
+      ASSERT.equal(cipher.output.toHex(), '50cfd16e0fd7f20b17a622eb2a469b7e');
+    });
+
+    it('should rc2-cbc decrypt w/binary string iv: 50cfd16e0fd7f20b17a622eb2a469b7e', function() {
+      var key = UTIL.hexToBytes('88bca90e90875a7f0f79c384627bafb2');
+      var iv = UTIL.hexToBytes('0123456789abcdef');
+      var input = new UTIL.createBuffer(
+        UTIL.hexToBytes('50cfd16e0fd7f20b17a622eb2a469b7e'));
+      var cipher = RC2.startDecrypting(key, iv, null);
+      cipher.update(input);
+      cipher.finish();
+      ASSERT.equal(cipher.output, 'revolution');
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/rc2',
+    'forge/util'
+  ], function(RC2, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      RC2(),
+      UTIL()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/rc2')(),
+    require('../../js/util')());
+}
+
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/rsa.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/rsa.js
new file mode 100644
index 0000000..434d7a3
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/rsa.js
@@ -0,0 +1,602 @@
+(function() {
+
+function Tests(ASSERT, PKI, RSA, MD, MGF, PSS, RANDOM, UTIL) {
+  var _pem = {
+    privateKey: '-----BEGIN RSA PRIVATE KEY-----\r\n' +
+      'MIICXQIBAAKBgQDL0EugUiNGMWscLAVM0VoMdhDZEJOqdsUMpx9U0YZI7szokJqQ\r\n' +
+      'NIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMGTkP3VF29vXBo+dLq5e+8VyAy\r\n' +
+      'Q3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGtvnM+z0MYDdKo80efzwIDAQAB\r\n' +
+      'AoGAIzkGONi5G+JifmXlLJdplom486p3upf4Ce2/7mqfaG9MnkyPSairKD/JXvfh\r\n' +
+      'NNWkkN8DKKDKBcVVElPgORYT0qwrWc7ueLBMUCbRXb1ZyfEulimG0R3kjUh7NYau\r\n' +
+      'DaIkVgfykXGSQMZx8FoaT6L080zd+0emKDDYRrb+/kgJNJECQQDoUZoiC2K/DWNY\r\n' +
+      'h3/ppZ0ane2y4SBmJUHJVMPQ2CEgxsrJTxet668ckNCKaOP/3VFPoWC41f17DvKq\r\n' +
+      'noYINNntAkEA4JbZBZBVUrQFhHlrpXT4jzqtO2RlKZzEq8qmFZfEErxOT1WMyyCi\r\n' +
+      'lAQ5gUKardo1Kf0omC8Xq/uO9ZYdED55KwJBALs6cJ65UFaq4oLJiQPzLd7yokuE\r\n' +
+      'dcj8g71PLBTW6jPxIiMFNA89nz3FU9wIVp+xbMNhSoMMKqIPVPC+m0Rn260CQQDA\r\n' +
+      'I83fWK/mZWUjBM33a68KumRiH238v8XyQxj7+C8i6D8G2GXvkigFAehAkb7LZZd+\r\n' +
+      'KLuGFyPlWv3fVWHf99KpAkBQFKk3MRMl6IGJZUEFQe4l5whm8LkGU4acSqv9B3xt\r\n' +
+      'qROkCrsFrMPqjuuzEmyHoQZ64r2PLJg7FOuyhBnQUOt4\r\n' +
+      '-----END RSA PRIVATE KEY-----\r\n',
+    privateKeyInfo: '-----BEGIN PRIVATE KEY-----\r\n' +
+      'MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMvQS6BSI0Yxaxws\r\n' +
+      'BUzRWgx2ENkQk6p2xQynH1TRhkjuzOiQmpA0jCiSJDoSic2dZIyUi/LjoCGeVFif\r\n' +
+      '57N5N5Tt4wZOQ/dUXb29cGj50url77xXIDJDcXMzXAji2ziFEAIXzDqarKBdDuL9\r\n' +
+      'IO7z+tepEa2+cz7PQxgN0qjzR5/PAgMBAAECgYAjOQY42Lkb4mJ+ZeUsl2mWibjz\r\n' +
+      'qne6l/gJ7b/uap9ob0yeTI9JqKsoP8le9+E01aSQ3wMooMoFxVUSU+A5FhPSrCtZ\r\n' +
+      'zu54sExQJtFdvVnJ8S6WKYbRHeSNSHs1hq4NoiRWB/KRcZJAxnHwWhpPovTzTN37\r\n' +
+      'R6YoMNhGtv7+SAk0kQJBAOhRmiILYr8NY1iHf+mlnRqd7bLhIGYlQclUw9DYISDG\r\n' +
+      'yslPF63rrxyQ0Ipo4//dUU+hYLjV/XsO8qqehgg02e0CQQDgltkFkFVStAWEeWul\r\n' +
+      'dPiPOq07ZGUpnMSryqYVl8QSvE5PVYzLIKKUBDmBQpqt2jUp/SiYLxer+471lh0Q\r\n' +
+      'PnkrAkEAuzpwnrlQVqrigsmJA/Mt3vKiS4R1yPyDvU8sFNbqM/EiIwU0Dz2fPcVT\r\n' +
+      '3AhWn7Fsw2FKgwwqog9U8L6bRGfbrQJBAMAjzd9Yr+ZlZSMEzfdrrwq6ZGIfbfy/\r\n' +
+      'xfJDGPv4LyLoPwbYZe+SKAUB6ECRvstll34ou4YXI+Va/d9VYd/30qkCQFAUqTcx\r\n' +
+      'EyXogYllQQVB7iXnCGbwuQZThpxKq/0HfG2pE6QKuwWsw+qO67MSbIehBnrivY8s\r\n' +
+      'mDsU67KEGdBQ63g=\r\n' +
+      '-----END PRIVATE KEY-----\r\n',
+    publicKey: '-----BEGIN PUBLIC KEY-----\r\n' +
+      'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDL0EugUiNGMWscLAVM0VoMdhDZ\r\n' +
+      'EJOqdsUMpx9U0YZI7szokJqQNIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMG\r\n' +
+      'TkP3VF29vXBo+dLq5e+8VyAyQ3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGt\r\n' +
+      'vnM+z0MYDdKo80efzwIDAQAB\r\n' +
+      '-----END PUBLIC KEY-----\r\n'
+  };
+  var _signature =
+    '9200ece65cdaed36bcc20b94c65af852e4f88f0b4fe5b249d54665f815992ac4' +
+    '3a1399e65d938c6a7f16dd39d971a53ca66523209dbbfbcb67afa579dbb0c220' +
+    '672813d9e6f4818f29b9becbb29da2032c5e422da97e0c39bfb7a2e7d568615a' +
+    '5073af0337ff215a8e1b2332d668691f4fb731440055420c24ac451dd3c913f4';
+
+  describe('rsa', function() {
+    it('should generate 512 bit key pair', function() {
+      var pair = RSA.generateKeyPair(512);
+      ASSERT.equal(PKI.privateKeyToPem(pair.privateKey).indexOf('-----BEGIN RSA PRIVATE KEY-----'), 0);
+      ASSERT.equal(PKI.publicKeyToPem(pair.publicKey).indexOf('-----BEGIN PUBLIC KEY-----'), 0);
+
+      // sign and verify
+      var md = MD.sha1.create();
+      md.update('0123456789abcdef');
+      var signature = pair.privateKey.sign(md);
+      ASSERT.ok(pair.publicKey.verify(md.digest().getBytes(), signature));
+    });
+
+    it('should generate the same 512 bit key pair', function() {
+      var prng = RANDOM.createInstance();
+      prng.seedFileSync = function(needed) {
+        return UTIL.fillString('a', needed);
+      };
+      var pair = RSA.generateKeyPair(512, {prng: prng});
+      var pem = {
+        privateKey: PKI.privateKeyToPem(pair.privateKey),
+        publicKey: PKI.publicKeyToPem(pair.publicKey)
+      };
+      ASSERT.equal(pem.privateKey.indexOf('-----BEGIN RSA PRIVATE KEY-----'), 0);
+      ASSERT.equal(pem.publicKey.indexOf('-----BEGIN PUBLIC KEY-----'), 0);
+
+      // sign and verify
+      var md = MD.sha1.create();
+      md.update('0123456789abcdef');
+      var signature = pair.privateKey.sign(md);
+      ASSERT.ok(pair.publicKey.verify(md.digest().getBytes(), signature));
+
+      // create same key pair by using same PRNG
+      prng = RANDOM.createInstance();
+      prng.seedFileSync = function(needed) {
+        return UTIL.fillString('a', needed);
+      };
+      var pair2 = RSA.generateKeyPair(512, {prng: prng});
+      var pem2 = {
+        privateKey: PKI.privateKeyToPem(pair2.privateKey),
+        publicKey: PKI.publicKeyToPem(pair2.publicKey)
+      };
+      ASSERT.equal(pem.privateKey, pem2.privateKey);
+      ASSERT.equal(pem.publicKey, pem2.publicKey);
+    });
+
+    it('should convert private key to/from PEM', function() {
+      var privateKey = PKI.privateKeyFromPem(_pem.privateKey);
+      ASSERT.equal(PKI.privateKeyToPem(privateKey), _pem.privateKey);
+    });
+
+    it('should convert public key to/from PEM', function() {
+      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
+      ASSERT.equal(PKI.publicKeyToPem(publicKey), _pem.publicKey);
+    });
+
+    it('should convert a PKCS#8 PrivateKeyInfo to/from PEM', function() {
+      var privateKey = PKI.privateKeyFromPem(_pem.privateKeyInfo);
+      var rsaPrivateKey = PKI.privateKeyToAsn1(privateKey);
+      var pki = PKI.wrapRsaPrivateKey(rsaPrivateKey);
+      ASSERT.equal(PKI.privateKeyInfoToPem(pki), _pem.privateKeyInfo);
+    });
+
+    (function() {
+      var algorithms = ['aes128', 'aes192', 'aes256', '3des', 'des'];
+      for(var i = 0; i < algorithms.length; ++i) {
+        var algorithm = algorithms[i];
+        it('should PKCS#8 encrypt and decrypt private key with ' + algorithm, function() {
+          var privateKey = PKI.privateKeyFromPem(_pem.privateKey);
+          var encryptedPem = PKI.encryptRsaPrivateKey(
+             privateKey, 'password', {algorithm: algorithm});
+          privateKey = PKI.decryptRsaPrivateKey(encryptedPem, 'password');
+          ASSERT.equal(PKI.privateKeyToPem(privateKey), _pem.privateKey);
+        });
+      }
+    })();
+
+    (function() {
+      var algorithms = ['aes128', 'aes192', 'aes256', '3des', 'des'];
+      for(var i = 0; i < algorithms.length; ++i) {
+        var algorithm = algorithms[i];
+        it('should legacy (OpenSSL style) encrypt and decrypt private key with ' + algorithm, function() {
+          var privateKey = PKI.privateKeyFromPem(_pem.privateKey);
+          var encryptedPem = PKI.encryptRsaPrivateKey(
+             privateKey, 'password', {algorithm: algorithm, legacy: true});
+          privateKey = PKI.decryptRsaPrivateKey(encryptedPem, 'password');
+          ASSERT.equal(PKI.privateKeyToPem(privateKey), _pem.privateKey);
+        });
+      }
+    })();
+
+    it('should verify signature', function() {
+      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
+      var md = MD.sha1.create();
+      md.update('0123456789abcdef');
+      var signature = UTIL.hexToBytes(_signature);
+      ASSERT.ok(publicKey.verify(md.digest().getBytes(), signature));
+    });
+
+    it('should sign and verify', function() {
+      var privateKey = PKI.privateKeyFromPem(_pem.privateKey);
+      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
+      var md = MD.sha1.create();
+      md.update('0123456789abcdef');
+      var signature = privateKey.sign(md);
+      ASSERT.ok(publicKey.verify(md.digest().getBytes(), signature));
+    });
+
+    it('should generate missing CRT parameters, sign, and verify', function() {
+      var privateKey = PKI.privateKeyFromPem(_pem.privateKey);
+
+      // remove dQ, dP, and qInv
+      privateKey = RSA.setPrivateKey(
+        privateKey.n, privateKey.e, privateKey.d,
+        privateKey.p, privateKey.q);
+
+      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
+      var md = MD.sha1.create();
+      md.update('0123456789abcdef');
+      var signature = privateKey.sign(md);
+      ASSERT.ok(publicKey.verify(md.digest().getBytes(), signature));
+    });
+
+    it('should sign and verify with a private key containing only e, n, and d parameters', function() {
+      var privateKey = PKI.privateKeyFromPem(_pem.privateKey);
+
+      // remove all CRT parameters from private key, so that it consists
+      // only of e, n and d (which make a perfectly valid private key, but its
+      // operations are slower)
+      privateKey = RSA.setPrivateKey(
+        privateKey.n, privateKey.e, privateKey.d);
+
+      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
+      var md = MD.sha1.create();
+      md.update('0123456789abcdef');
+      var signature = privateKey.sign(md);
+      ASSERT.ok(publicKey.verify(md.digest().getBytes(), signature));
+    });
+
+    (function() {
+      var tests = [{
+        keySize: 1024,
+        privateKeyPem: '-----BEGIN RSA PRIVATE KEY-----\r\n' +
+          'MIICWwIBAAKBgQDCjvkkLWNTeYXqEsqGiVCW/pDt3/qAodNMHcU9gOU2rxeWwiRu\r\n' +
+          'OhhLqmMxXHLi0oP5Xmg0m7zdOiLMEyzzyRzdp21aqp3k5qtuSDkZcf1prsp1jpYm\r\n' +
+          '6z9EGpaSHb64BCuUsQGmUPKutd5RERKHGZXtiRuvvIyue7ETq6VjXrOUHQIDAQAB\r\n' +
+          'AoGAOKeBjTNaVRhyEnNeXkbmHNIMSfiK7aIx8VxJ71r1ZDMgX1oxWZe5M29uaxVM\r\n' +
+          'rxg2Lgt7tLYVDSa8s0hyMptBuBdy3TJUWruDx85uwCrWnMerCt/iKVBS22fv5vm0\r\n' +
+          'LEq/4gjgIVTZwgqbVxGsBlKcY2VzxAfYqYzU8EOZBeNhZdECQQDy+PJAPcUN2xOs\r\n' +
+          '6qy66S91x6y3vMjs900OeX4+bgT4VSVKmLpqRTPizzcL07tT4+Y+pAAOX6VstZvZ\r\n' +
+          '6iFDL5rPAkEAzP1+gaRczboKoJWKJt0uEMUmztcY9NXJFDmjVLqzKwKjcAoGgIal\r\n' +
+          'h+uBFT9VJ16QajC7KxTRLlarzmMvspItUwJAeUMNhEpPwm6ID1DADDi82wdgiALM\r\n' +
+          'NJfn+UVhYD8Ac//qsKQwxUDseFH6owh1AZVIIBMxg/rwUKUCt2tGVoW3uQJAIt6M\r\n' +
+          'Aml/D8+xtxc45NuC1n9y1oRoTl1/Ut1rFyKbD5nnS0upR3uf9LruvjqDtaq0Thvz\r\n' +
+          '+qQT4RoFJ5pfprSO2QJAdMkfNWRqECfAhZyQuUrapeWU3eQ0wjvktIynCIwiBDd2\r\n' +
+          'MfjmVXzBJhMk6dtINt+vBEITVQEOdtyTgDt0y3n2Lw==\r\n' +
+          '-----END RSA PRIVATE KEY-----\r\n',
+        publicKeyPem: '-----BEGIN PUBLIC KEY-----\r\n' +
+          'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCjvkkLWNTeYXqEsqGiVCW/pDt\r\n' +
+          '3/qAodNMHcU9gOU2rxeWwiRuOhhLqmMxXHLi0oP5Xmg0m7zdOiLMEyzzyRzdp21a\r\n' +
+          'qp3k5qtuSDkZcf1prsp1jpYm6z9EGpaSHb64BCuUsQGmUPKutd5RERKHGZXtiRuv\r\n' +
+          'vIyue7ETq6VjXrOUHQIDAQAB\r\n' +
+          '-----END PUBLIC KEY-----\r\n',
+        encrypted: 'jsej3OoacmJ1VjWrlw68F+drnQORAuKAqVu6RMbz1xSXjzA355vctrJZXolRU0mvzuu/6VuNynkKGGyRJ6DHt85CvwTMChw4tOMV4Dy6bgnUt3j+DZA2sWTwFhOlpzvNQMK70QpuqrXtOZmAO59EwoDeJkW/iH6t4YzNOVYo9Jg=',
+        signature: 'GT0/3EV2zrXxPd1ydijJq3R7lkI4c0GtcprgpG04dSECv/xyXtikuzivxv7XzUdHpu6QiYmM0xE4D4i7LK3Mzy+f7aB4o/dg8XXO3htLiBzVI+ZJCRh06RdYctPtclAWmyZikZ8Etw3NnA/ldKuG4jApbwRb21UFm5gYLrJ4SP4=',
+        signaturePss: 'F4xffaANDBjhFxeSJx8ANuBbdhaWZjUHRQh4ueYQMPPCaR2mpwdqxE04sbgNgIiZzBuLIAI4HpTMMoDk3Rruhjefx3+9UhzTxgB0hRI+KzRChRs+ToltWWDZdYzt9T8hfTlELeqT4V8HgjDuteO/IAvIVlRIBwMNv53Iebu1FY4=',
+        signatureWithAbcSalt: 'GYA/Zp8G+jqG2Fu7Um+XP7Cr/yaVdzJN8lyt57Lw6gFflia2CPbOVMLyqLzD7fKoE8UD0Rc6DF8k04xhEu60sudw2nxGHeDvpL4M9du0uYra/WSr9kv7xNjAW62NyNerDngHD2J7O8gQ07TZiTXkrfS724vQab5xZL/+FhvisMY=',
+        signatureWithCustomPrng: 'LzWcUpUYK+URDp72hJbz1GVEp0rG0LHjd+Pdh2w5rfQFbUThbmXDl3X6DUT5UZr5RjUSHtc2usvH+w49XskyIJJO929sUk9EkMJMK/6QAnYYEp5BA+48pdGNNMZyjIbhyl9Y4lInzFPX8XYMM8o+tdSK+hj+dW5OPdnwWbDtR7U='
+      }, {
+        keySize: 1025,
+        privateKeyPem: '-----BEGIN RSA PRIVATE KEY-----\r\n' +
+          'MIICXgIBAAKBgQGIkej4PDlAigUh5fbbHp1WXuTHhOdQfAke+LoH0TM4uzn0QmgK\r\n' +
+          'SJqxzB1COJ5o0DwZw/NR+CNy7NUrly+vmh2YPwsaqN+AsYBF9qsF93oN8/TBtaL/\r\n' +
+          'GRoRGpDcCglkj1kZnDaWR79NsG8mC0TrvQCkcCLOP0c2Ux1hRbntOetGXwIDAQAB\r\n' +
+          'AoGBAIaJWsoX+ZcAthmT8jHOICXFh6pJBe0zVPzkSPz82Q0MPSRUzcsYbsuYJD7Z\r\n' +
+          'oJBTLQW3feANpjhwqe2ydok7y//ONm3Th53Bcu8jLfoatg4KYxNFIwXEO10mPOld\r\n' +
+          'VuDIGrBkTABe6q2P5PeUKGCKLT6i/u/2OTXTrQiJbQ0gU8thAkEBjqcFivWMXo34\r\n' +
+          'Cb9/EgfWCCtv9edRMexgvcFMysRsbHJHDK9JjRLobZltwtAv3cY7F3a/Cu1afg+g\r\n' +
+          'jAzm5E3gowJBAPwYFHTLzaZToxFKNQztWrPsXF6YfqHpPUUIpT4UzL6DhGG0M00U\r\n' +
+          'qMyhkYRRqmGOSrSovjg2hjM2643MUUWxUxUCQDPkk/khu5L3YglKzyy2rmrD1MAq\r\n' +
+          'y0v3XCR3TBq89Ows+AizrJxbkLvrk/kfBowU6M5GG9o9SWFNgXWZnFittocCQQDT\r\n' +
+          'e1P1419DUFi1UX6NuLTlybx3sxBQvf0jY6xUF1jn3ib5XBXJbTJqcIRF78iyjI9J\r\n' +
+          'XWIugDc20bTsQOJRSAA9AkEBU8kpueHBaiXTikqqlK9wvc2Lp476hgyKVmVyBGye\r\n' +
+          '9TLTWkTCzDPtManLy47YtXkXnmyazS+DlKFU61XAGEnZfg==\r\n' +
+          '-----END RSA PRIVATE KEY-----\r\n',
+        publicKeyPem: '-----BEGIN PUBLIC KEY-----\r\n' +
+          'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQGIkej4PDlAigUh5fbbHp1WXuTH\r\n' +
+          'hOdQfAke+LoH0TM4uzn0QmgKSJqxzB1COJ5o0DwZw/NR+CNy7NUrly+vmh2YPwsa\r\n' +
+          'qN+AsYBF9qsF93oN8/TBtaL/GRoRGpDcCglkj1kZnDaWR79NsG8mC0TrvQCkcCLO\r\n' +
+          'P0c2Ux1hRbntOetGXwIDAQAB\r\n' +
+          '-----END PUBLIC KEY-----\r\n',
+        encrypted: 'AOVeCUN8BOVkZvt4mxyNn/yCYE1MZ40A3e/osh6EvCBcJ09hyYbx7bzKSrdkhRnDyW0pGtgP352CollasllQZ9HlfI2Wy9zKM0aYZZn8OHBA+60Tc3xHHDGznLZqggUKuhoNpj+faVZ1uzb285eTpQQa+4mLUue2svJD4ViM8+ng',
+        signature: 'AFSx0axDYXlF2rO3ofgUhYSI8ZlIWtJUUZ62PhgdBp9O5zFqMX3DXoiov1e7NenSOz1khvTSMctFWzKP3GU3F0yewe+Yd3UAZE0dM8vAxigSSfAchUkBDmp9OFuszUie63zwWwpG+gXtvyfueZs1RniBvW1ZmXJvS+HFgX4ouzwd',
+        signaturePss: 'AQvBdhAXDpu+7RpcybMgwuTUk6w+qa08Lcq3G1xHY4kC7ZUzauZd/Jn9e0ePKApDqs7eDNAOV+dQkU2wiH/uBg6VGelzb0hFwcpSLyBW92Vw0q3GlzY7myWn8qnNzasrt110zFflWQa1GiuzH/C8f+Z82/MzlWDxloJIYbq2PRC8',
+        signatureWithAbcSalt: 'AW4bKnG/0TGvAZgqX5Dk+fXpUNgX7INFelE46d3m+spaMTG5XalY0xP1sxWfaE/+Zl3FmZcfTNtfOCo0eNRO1h1+GZZfp32ZQZmZvkdUG+dUQp318LNzgygrVf/5iIX+QKV5/soSDuAHBzS7yDfMgzJfnXNpFE/zPLOgZIoOIuLq',
+        signatureWithCustomPrng: 'AVxfCyGC/7Y3kz//eYFEuWQijjR7eR05AM36CwDlLsVkDRtXoeVzz2yTFBdP+i+QgQ73C/I3lLtvXTwfleorvIX9YncVBeGDQXssmULxzqsM3izaLfJXCRAGx9ErL1Az10+fAqPZpq954OVSDqrR/61Q7CsMY7CiQO3nfIIaxgVL'
+      }, {
+        keySize: 1031,
+        privateKeyPem: '-----BEGIN RSA PRIVATE KEY-----\r\n' +
+          'MIICXwIBAAKBgWyeKqA2oA4klYrKT9hjjutYQksJNN0cxwaQwIm9AYiLxOsYtT/C\r\n' +
+          'ovJx5Oy1EvkbYQbfvYsGISUx9bW8yasZkTHR55IbW3+UptvQjTDtdxBQTgQOpsAh\r\n' +
+          'BJtZYY3OmyH9Sj3F3oB//oyriNoj0QYyfsvlO8UsMmLzpnf6qfZBDHA/9QIDAQAB\r\n' +
+          'AoGBBj/3ne5muUmbnTfU7lOUNrCGaADonMx6G0ObAJHyk6PPOePbEgcmDyNEk+Y7\r\n' +
+          'aEAODjIzmttIbvZ39/Qb+o9nDmCSZC9VxiYPP+rjOzPglCDT5ks2Xcjwzd3If6Ya\r\n' +
+          'Uw6P31Y760OCYeTb4Ib+8zz5q51CkjkdX5Hq/Yu+lZn0Vx7BAkENo83VfL+bwxTm\r\n' +
+          'V7vR6gXqTD5IuuIGHL3uTmMNNURAP6FQDHu//duipys83iMChcOeXtboE16qYrO0\r\n' +
+          '9KC0cqL4JQJBB/aYo/auVUGZA6f50YBp0b2slGMk9TBQG0iQefuuSyH4kzKnt2e3\r\n' +
+          'Q40SBmprcM+DfttWJ11bouec++goXjz+95ECQQyiTWYRxulgKVuyqCYnvpLnTEnR\r\n' +
+          '0MoYlVTHBriVPkLErYaYCYgse+SNM1+N4p/Thv6KmkUcq/Lmuc5DSRfbl1iBAkEE\r\n' +
+          '7GKtJQvd7EO1bfpXnARQx+tWhwHHkgpFBBVHReMZ0rQEFhJ5o2c8HZEiZFNvGO2c\r\n' +
+          '1fErP14zlu2JFZ03vpCI8QJBCQz9HL28VNjafSAF2mon/SNjKablRjoGGKSoSdyA\r\n' +
+          'DHDZ/LeRsTp2dg8+bSiG1R+vPqw0f/BT+ux295Sy9ocGEM8=\r\n' +
+          '-----END RSA PRIVATE KEY-----\r\n',
+        publicKeyPem: '-----BEGIN PUBLIC KEY-----\r\n' +
+          'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgWyeKqA2oA4klYrKT9hjjutYQksJ\r\n' +
+          'NN0cxwaQwIm9AYiLxOsYtT/CovJx5Oy1EvkbYQbfvYsGISUx9bW8yasZkTHR55Ib\r\n' +
+          'W3+UptvQjTDtdxBQTgQOpsAhBJtZYY3OmyH9Sj3F3oB//oyriNoj0QYyfsvlO8Us\r\n' +
+          'MmLzpnf6qfZBDHA/9QIDAQAB\r\n' +
+          '-----END PUBLIC KEY-----\r\n',
+        encrypted: 'ShSS4/fEAkuS6XiQakhOpWp82IXaaCaDNtsndU4uokvriqgCGZyqc+IkIk3eVmZ8bn4vVIRR43ydFuvGgsptVjizOdLGZudph3TJ1clcYEMcCXk4z5HaEu0bx5SW9jmzHhE/z+WV8PB48q7y7C2qtmPmfttG2NMsNLBvkiaDopRO',
+        signature: 'Z3vYgRdezrWmdA3NC1Uz2CcHRTcE+/C2idGZA1FjUGqFztAHQ31k0QW/F5zuJdKvg8LQU45S3KxW+OQpbGPL98QbzJLhml88mFGe6OinLXJbi7UQWrtXwamc2jMdiXwovSLbXaXy6PX2QW089iC8XuAZftVi3T/IKV0458FQQprg',
+        signaturePss: 'R6QsK6b3QinIPZPamm/dP0Zndqti1TzAkFTRSZJaRSa1u2zuvZC5QHF4flDjEtHosWeDyxrBE7PHGQZ0b1bHv9qgHGsJCMwaQPj3AWj9fjYmx7b86KM2vHr8q/vqDaa9pTvVRSSwvD6fwoZPc9twQEfdjdDBAiy23yLDzk/zZiwM',
+        signatureWithAbcSalt: 'Ep9qx4/FPNcWTixWhvL2IAyJR69o5I4MIJi3cMAhDmpuTvAaL/ThQwFWkBPPOPT4Jbumnu6ELjPNjo72wa00e5k64qnZgy1pauBPMlXRlKehRc9UJZ6+xot642z8Qs+rt89OgbYTsvlyr8lzXooUHz/lPpfawYCqd7maRMs8YlYM',
+        signatureWithCustomPrng: 'NHAwyn2MdM5ez/WbDNbu2A2JNS+cRiWk/zBoh0lg3aq/RsBS0nrYr4AGiC5jt6KWVcN4AIVOomYtX2k+MhLoemN2t2rDj/+LXOeU7kgCAz0q0ED2NFQz7919JU+PuYXMy03qTMfl5jbvStdi/00eQHjJKGEH+xAgrDcED2lrhtCu'
+      }, {
+        keySize: 1032,
+        privateKeyPem: '-----BEGIN RSA PRIVATE KEY-----\r\n' +
+          'MIICYQIBAAKBggDPhzn5I3GecxWt5DKbP+VhM2AFNSOL0+VbYEOR1hnlZdLbxGK4\r\n' +
+          'cPQzMr2qT6dyttJcsgWr3xKobPkz7vsTZzQATSiekm5Js5dGpaj5lrq/x2+WTZvn\r\n' +
+          '55x9M5Y5dlpusDMKcC3KaIX/axc+MbvPFzo6Eli7JLCWdBg01eKo30knil0CAwEA\r\n' +
+          'AQKBggCNl/sjFF7SOD1jbt5kdL0hi7cI9o+xOLs1lEGmAEmc7dNnZN/ibhb/06/6\r\n' +
+          'wuxB5aEz47bg5IvLZMbG+1hNjc26D0J6Y3Ltwrg8f4ZMdDrh4v0DZ8hy/HbEpMrJ\r\n' +
+          'Td5dk3mtw9FLow10MB5udPLTDKhfDpTcWiObKm2STtFeBk3xeEECQQ6Cx6bZxQJ1\r\n' +
+          'zCxflV5Xi8BgAQaUKMqygugte+HpOLflL0j1fuZ0rPosUyDOEFkTzOsPxBYYOU8i\r\n' +
+          'Gzan1GvW3WwRAkEOTTRt849wpgC9xx2pF0IrYEVmv5gEMy3IiRfCNgEoBwpTWVf4\r\n' +
+          'QFpN3V/9GFz0WQEEYo6OTmkNcC3Of5zbHhu1jQJBBGxXAYQ2KnbP4uLL/DMBdYWO\r\n' +
+          'Knw1JvxdLPrYXVejI2MoE7xJj2QXajbirAhEMXL4rtpicj22EmoaE4H7HVgkrJEC\r\n' +
+          'QQq2V5w4AGwvW4TLHXNnYX/eB33z6ujScOuxjGNDUlBqHZja5iKkCUAjnl+UnSPF\r\n' +
+          'exaOwBrlrpiLOzRer94MylKNAkEBmI58bqfkI5OCGDArAsJ0Ih58V0l1UW35C1SX\r\n' +
+          '4yDoXSM5A/xQu2BJbXO4jPe3PnDvCVCEyKpbCK6bWbe26Y7zuw==\r\n' +
+          '-----END RSA PRIVATE KEY-----\r\n',
+        publicKeyPem: '-----BEGIN PUBLIC KEY-----\r\n' +
+          'MIGgMA0GCSqGSIb3DQEBAQUAA4GOADCBigKBggDPhzn5I3GecxWt5DKbP+VhM2AF\r\n' +
+          'NSOL0+VbYEOR1hnlZdLbxGK4cPQzMr2qT6dyttJcsgWr3xKobPkz7vsTZzQATSie\r\n' +
+          'km5Js5dGpaj5lrq/x2+WTZvn55x9M5Y5dlpusDMKcC3KaIX/axc+MbvPFzo6Eli7\r\n' +
+          'JLCWdBg01eKo30knil0CAwEAAQ==\r\n' +
+          '-----END PUBLIC KEY-----\r\n',
+        encrypted: 'pKTbv+xgXPDc+wbjsANFu1/WTcmy4aZFKXKnxddHbU5S0Dpdj2OqCACiBwu1oENPMgPAJ27XRbFtKG+eS8tX47mKP2Fo0Bi+BPFtzuQ1bj3zUzTwzjemT+PU+a4Tho/eKjPhm6xrwGAoQH2VEDEpvcYf+SRmGFJpJ/zPUrSxgffj',
+        signature: 'R9WBFprCfcIC4zY9SmBpEM0E+cr5j4gMn3Ido5mktoR9VBoJqC6eR6lubIPvZZUz9e4yUSYX0squ56Q9Y0yZFQjTHgsrlmhB2YW8kpv4h8P32Oz2TLcMJK9R2tIh9vvyxwBkd/Ml1qG60GnOFUFzxUad9VIlzaF1PFR6EfnkgBUW',
+        signaturePss: 'v9UBd4XzBxSRz8yhWKjUkFpBX4Fr2G+ImjqbePL4sAZvYw1tWL+aUQpzG8eOyMxxE703VDh9nIZULYI/uIb9HYHQoGYQ3WoUaWqtZg1x8pZP+Ad7ilUWk5ImRl57fTznNQiVdwlkS5Wgheh1yJCES570a4eujiK9OyB0ba4rKIcM',
+        signatureWithAbcSalt: 'HCm0FI1jE6wQgwwi0ZwPTkGjssxAPtRh6tWXhNd2J2IoJYj9oQMMjCEElnvQFBa/l00sIsw2YV1tKyoTABaSTGV4vlJcDF+K0g/wiAf30TRUZo72DZKDNdyffDlH0wBDkNVW+F6uqdciJqBC6zz+unNh7x+FRwYaY8xhudIPXdyP',
+        signatureWithCustomPrng: 'AGyN8xu+0yfCR1tyB9mCXcTGb2vdLnsX9ro2Qy5KV6Hw5YMVNltAt65dKR4Y8pfu6D4WUyyJRUtJ8td2ZHYzIVtWY6bG1xFt5rkjTVg4v1tzQgUQq8AHvRE2qLzwDXhazJ1e6Id2Nuxb1uInFyRC6/gLmiPga1WRDEVvFenuIA48'
+      }];
+      for(var i = 0; i < tests.length; ++i) {
+        createTests(tests[i]);
+      }
+
+      it('should ensure maximum message length for a 1024-bit key is exceeded', function() {
+        /* For PKCS#1 v1.5, the message must be padded with at least eight bytes,
+          two zero bytes and one byte telling what the block type is. This is 11
+          extra bytes are added to the message. The test uses a message of 118
+          bytes.Together with the 11 extra bytes the encryption block needs to be
+          at least 129 bytes long. This requires a key of 1025-bits. */
+        var key = PKI.publicKeyFromPem(tests[0].publicKeyPem);
+        var message = UTIL.createBuffer().fillWithByte(0, 118);
+        ASSERT.throws(function() {
+          key.encrypt(message.getBytes());
+        });
+      });
+
+      it('should ensure maximum message length for a 1025-bit key is not exceeded', function() {
+        var key = PKI.publicKeyFromPem(tests[1].publicKeyPem);
+        var message = UTIL.createBuffer().fillWithByte(0, 118);
+        ASSERT.doesNotThrow(function() {
+          key.encrypt(message.getBytes());
+        });
+      });
+
+      /**
+       * Creates RSA encryption & decryption tests.
+       *
+       * Uses different key sizes (1024, 1025, 1031, 1032). The test functions are
+       * generated from "templates" below, one for each key size to provide sensible
+       * output.
+       *
+       * Key material in was created with OpenSSL using these commands:
+       *
+       * openssl genrsa -out rsa_1024_private.pem 1024
+       * openssl rsa -in rsa_1024_private.pem -out rsa_1024_public.pem \
+       *   -outform PEM -pubout
+       * echo 'too many secrets' | openssl rsautl -encrypt \
+       *   -inkey rsa_1024_public.pem -pubin -out rsa_1024_encrypted.bin
+       *
+       * echo -n 'just testing' | openssl dgst -sha1 -binary > tosign.sha1
+       * openssl pkeyutl -sign -in tosign.sha1 -inkey rsa_1024_private.pem \
+       *   -out rsa_1024_sig.bin -pkeyopt digest:sha1
+       * openssl pkeyutl -sign -in tosign.sha1 -inkey rsa_1024_private.pem \
+       *   -out rsa_1024_sigpss.bin -pkeyopt digest:sha1 \
+       *   -pkeyopt rsa_padding_mode:pss -pkeyopt rsa_pss_saltlen:20
+       *
+       * OpenSSL commands for signature verification:
+       *
+       * openssl pkeyutl -verify -in tosign.sha1 -sigfile rsa_1024_sig.bin \
+       *   -pubin -inkey rsa_1024_public.pem -pkeyopt digest:sha1
+       * openssl pkeyutl -verify -in tosign.sha1 -sigfile rsa_1025_sigpss.bin \
+       *   -pubin -inkey rsa_1025_public.pem -pkeyopt digest:sha1 \
+       *   -pkeyopt rsa_padding_mode:pss -pkeyopt rsa_pss_saltlen:20
+       */
+      function createTests(params) {
+        var keySize = params.keySize;
+
+        it('should rsa encrypt using a ' + keySize + '-bit key', function() {
+          var message = "it need's to be about 20% cooler"; // it need's better grammar too
+
+          /* First step, do public key encryption */
+          var key = PKI.publicKeyFromPem(params.publicKeyPem);
+          var data = key.encrypt(message);
+
+          /* Second step, use private key decryption to verify successful
+            encryption. The encrypted message differs every time, since it is
+            padded with random data. Therefore just rely on the decryption
+            routine to work, which is tested seperately against an externally
+            provided encrypted message. */
+          key = PKI.privateKeyFromPem(params.privateKeyPem);
+          ASSERT.equal(key.decrypt(data), message);
+        });
+
+        it('should rsa decrypt using a ' + keySize + '-bit key', function() {
+          var data = UTIL.decode64(params.encrypted);
+          var key = PKI.privateKeyFromPem(params.privateKeyPem);
+          ASSERT.equal(key.decrypt(data), 'too many secrets\n');
+        });
+
+        it('should rsa sign using a ' + keySize + '-bit key and PKCS#1 v1.5 padding', function() {
+          var key = PKI.privateKeyFromPem(params.privateKeyPem);
+
+          var md = MD.sha1.create();
+          md.start();
+          md.update('just testing');
+
+          var signature = UTIL.decode64(params.signature);
+          ASSERT.equal(key.sign(md), signature);
+        });
+
+        it('should verify an rsa signature using a ' + keySize + '-bit key and PKCS#1 v1.5 padding', function() {
+          var signature = UTIL.decode64(params.signature);
+          var key = PKI.publicKeyFromPem(params.publicKeyPem);
+
+          var md = MD.sha1.create();
+          md.start();
+          md.update('just testing');
+
+          ASSERT.equal(key.verify(md.digest().getBytes(), signature), true);
+        });
+
+        /* Note: signatures are *not* deterministic (the point of RSASSA-PSS),
+          so they can't be compared easily -- instead they are just verified
+          using the verify() function which is tested against OpenSSL-generated
+          signatures. */
+        it('should rsa sign using a ' + keySize + '-bit key and PSS padding', function() {
+          var privateKey = PKI.privateKeyFromPem(params.privateKeyPem);
+          var publicKey = PKI.publicKeyFromPem(params.publicKeyPem);
+
+          var md = MD.sha1.create();
+          md.start();
+          md.update('just testing');
+
+          // create signature
+          var pss = PSS.create(
+            MD.sha1.create(), MGF.mgf1.create(MD.sha1.create()), 20);
+          var signature = privateKey.sign(md, pss);
+
+          // verify signature
+          md.start();
+          md.update('just testing');
+          ASSERT.equal(
+            publicKey.verify(md.digest().getBytes(), signature, pss), true);
+        });
+
+        it('should verify an rsa signature using a ' + keySize + '-bit key and PSS padding', function() {
+          var signature = UTIL.decode64(params.signaturePss);
+          var key = PKI.publicKeyFromPem(params.publicKeyPem);
+
+          var md = MD.sha1.create();
+          md.start();
+          md.update('just testing');
+
+          var pss = PSS.create(
+            MD.sha1.create(), MGF.mgf1.create(MD.sha1.create()), 20);
+          ASSERT.equal(
+            key.verify(md.digest().getBytes(), signature, pss), true);
+        });
+
+        it('should rsa sign using a ' + keySize + '-bit key and PSS padding using pss named-param API', function() {
+          var privateKey = PKI.privateKeyFromPem(params.privateKeyPem);
+          var publicKey = PKI.publicKeyFromPem(params.publicKeyPem);
+
+          var md = MD.sha1.create();
+          md.start();
+          md.update('just testing');
+
+          // create signature
+          var pss = PSS.create({
+            md: MD.sha1.create(),
+            mgf: MGF.mgf1.create(MD.sha1.create()),
+            saltLength: 20
+          });
+          var signature = privateKey.sign(md, pss);
+
+          // verify signature
+          md.start();
+          md.update('just testing');
+          ASSERT.equal(
+            publicKey.verify(md.digest().getBytes(), signature, pss), true);
+        });
+
+        it('should verify an rsa signature using a ' + keySize + '-bit key and PSS padding using pss named-param API', function() {
+          var signature = UTIL.decode64(params.signaturePss);
+          var key = PKI.publicKeyFromPem(params.publicKeyPem);
+
+          var md = MD.sha1.create();
+          md.start();
+          md.update('just testing');
+
+          var pss = PSS.create({
+            md: MD.sha1.create(),
+            mgf: MGF.mgf1.create(MD.sha1.create()),
+            saltLength: 20
+          });
+          ASSERT.equal(
+            key.verify(md.digest().getBytes(), signature, pss), true);
+        });
+
+        it('should rsa sign using a ' + keySize + '-bit key and PSS padding using salt "abc"', function() {
+          var privateKey = PKI.privateKeyFromPem(params.privateKeyPem);
+
+          var md = MD.sha1.create();
+          md.start();
+          md.update('just testing');
+
+          // create signature
+          var pss = PSS.create({
+            md: MD.sha1.create(),
+            mgf: MGF.mgf1.create(MD.sha1.create()),
+            salt: UTIL.createBuffer('abc')
+          });
+          var signature = privateKey.sign(md, pss);
+          var b64 = UTIL.encode64(signature);
+          ASSERT.equal(b64, params.signatureWithAbcSalt);
+        });
+
+        it('should verify an rsa signature using a ' + keySize + '-bit key and PSS padding using salt "abc"', function() {
+          var signature = UTIL.decode64(params.signatureWithAbcSalt);
+          var key = PKI.publicKeyFromPem(params.publicKeyPem);
+
+          var md = MD.sha1.create();
+          md.start();
+          md.update('just testing');
+
+          var pss = PSS.create({
+            md: MD.sha1.create(),
+            mgf: MGF.mgf1.create(MD.sha1.create()),
+            saltLength: 3
+          });
+          ASSERT.equal(
+            key.verify(md.digest().getBytes(), signature, pss), true);
+        });
+
+        it('should rsa sign using a ' + keySize + '-bit key and PSS padding using custom PRNG', function() {
+          var prng = RANDOM.createInstance();
+          prng.seedFileSync = function(needed) {
+            return UTIL.fillString('a', needed);
+          };
+          var privateKey = PKI.privateKeyFromPem(params.privateKeyPem);
+
+          var md = MD.sha1.create();
+          md.start();
+          md.update('just testing');
+
+          // create signature
+          var pss = PSS.create({
+            md: MD.sha1.create(),
+            mgf: MGF.mgf1.create(MD.sha1.create()),
+            saltLength: 20,
+            prng: prng
+          });
+          var signature = privateKey.sign(md, pss);
+          var b64 = UTIL.encode64(signature);
+          ASSERT.equal(b64, params.signatureWithCustomPrng);
+        });
+
+        it('should verify an rsa signature using a ' + keySize + '-bit key and PSS padding using custom PRNG', function() {
+          var prng = RANDOM.createInstance();
+          prng.seedFileSync = function(needed) {
+            return UTIL.fillString('a', needed);
+          };
+          var signature = UTIL.decode64(params.signatureWithCustomPrng);
+          var key = PKI.publicKeyFromPem(params.publicKeyPem);
+
+          var md = MD.sha1.create();
+          md.start();
+          md.update('just testing');
+
+          var pss = PSS.create({
+            md: MD.sha1.create(),
+            mgf: MGF.mgf1.create(MD.sha1.create()),
+            saltLength: 20,
+            prng: prng
+          });
+          ASSERT.equal(
+            key.verify(md.digest().getBytes(), signature, pss), true);
+        });
+      }
+    })();
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/pki',
+    'forge/rsa',
+    'forge/md',
+    'forge/mgf',
+    'forge/pss',
+    'forge/random',
+    'forge/util'
+  ], function(PKI, RSA, MD, MGF, PSS, RANDOM, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      PKI(),
+      RSA(),
+      MD(),
+      MGF(),
+      PSS(),
+      RANDOM(),
+      UTIL()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/pki')(),
+    require('../../js/rsa')(),
+    require('../../js/md')(),
+    require('../../js/mgf')(),
+    require('../../js/pss')(),
+    require('../../js/random')(),
+    require('../../js/util')());
+}
+
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/sha1.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/sha1.js
new file mode 100644
index 0000000..3ffd985
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/sha1.js
@@ -0,0 +1,75 @@
+(function() {
+
+function Tests(ASSERT, SHA1, UTIL) {
+  describe('sha1', function() {
+    it('should digest the empty string', function() {
+      var md = SHA1.create();
+      ASSERT.equal(
+        md.digest().toHex(), 'da39a3ee5e6b4b0d3255bfef95601890afd80709');
+    });
+
+    it('should digest "abc"', function() {
+      var md = SHA1.create();
+      md.update('abc');
+      ASSERT.equal(
+        md.digest().toHex(), 'a9993e364706816aba3e25717850c26c9cd0d89d');
+    });
+
+    it('should digest "The quick brown fox jumps over the lazy dog"', function() {
+      var md = SHA1.create();
+      md.update('The quick brown fox jumps over the lazy dog');
+      ASSERT.equal(
+        md.digest().toHex(), '2fd4e1c67a2d28fced849ee1bb76e7391b93eb12');
+    });
+
+    it('should digest "c\'\u00e8"', function() {
+      var md = SHA1.create();
+      md.update("c\'\u00e8", 'utf8');
+      ASSERT.equal(
+        md.digest().toHex(), '98c9a3f804daa73b68a5660d032499a447350c0d');
+    });
+
+    it('should digest "THIS IS A MESSAGE"', function() {
+      var md = SHA1.create();
+      md.start();
+      md.update('THIS IS ');
+      md.update('A MESSAGE');
+      // do twice to check continuing digest
+      ASSERT.equal(
+        md.digest().toHex(), '5f24f4d6499fd2d44df6c6e94be8b14a796c071d');
+      ASSERT.equal(
+        md.digest().toHex(), '5f24f4d6499fd2d44df6c6e94be8b14a796c071d');
+    });
+
+    it('should digest a long message', function() {
+      // Note: might be too slow on old browsers
+      var md = SHA1.create();
+      md.update(UTIL.fillString('a', 1000000));
+      ASSERT.equal(
+        md.digest().toHex(), '34aa973cd4c4daa4f61eeb2bdbad27316534016f');
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/sha1',
+    'forge/util'
+  ], function(SHA1, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      SHA1(),
+      UTIL()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/sha1')(),
+    require('../../js/util')());
+}
+
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/sha256.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/sha256.js
new file mode 100644
index 0000000..2d5eb8a
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/sha256.js
@@ -0,0 +1,81 @@
+(function() {
+
+function Tests(ASSERT, SHA256, UTIL) {
+  describe('sha256', function() {
+    it('should digest the empty string', function() {
+      var md = SHA256.create();
+      ASSERT.equal(
+        md.digest().toHex(),
+        'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855');
+    });
+
+    it('should digest "abc"', function() {
+      var md = SHA256.create();
+      md.update('abc');
+      ASSERT.equal(
+        md.digest().toHex(),
+        'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad');
+    });
+
+    it('should digest "The quick brown fox jumps over the lazy dog"', function() {
+      var md = SHA256.create();
+      md.update('The quick brown fox jumps over the lazy dog');
+      ASSERT.equal(
+        md.digest().toHex(),
+        'd7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592');
+    });
+
+    it('should digest "c\'\u00e8"', function() {
+      var md = SHA256.create();
+      md.update("c\'\u00e8", 'utf8');
+      ASSERT.equal(
+        md.digest().toHex(),
+        '1aa15c717afffd312acce2217ce1c2e5dabca53c92165999132ec9ca5decdaca');
+    });
+
+    it('should digest "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"', function() {
+      var md = SHA256.create();
+      md.start();
+      md.update('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq');
+      // do twice to check continuing digest
+      ASSERT.equal(
+        md.digest().toHex(),
+        '248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1');
+      ASSERT.equal(
+        md.digest().toHex(),
+        '248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1');
+    });
+
+    it('should digest a long message', function() {
+      // Note: might be too slow on old browsers
+      var md = SHA256.create();
+      md.update(UTIL.fillString('a', 1000000));
+      ASSERT.equal(
+        md.digest().toHex(),
+        'cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0');
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/sha256',
+    'forge/util'
+  ], function(SHA256, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      SHA256(),
+      UTIL()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/sha256')(),
+    require('../../js/util')());
+}
+
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/sha512.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/sha512.js
new file mode 100644
index 0000000..3cbc4dc
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/sha512.js
@@ -0,0 +1,174 @@
+(function() {
+
+function Tests(ASSERT, SHA512, UTIL) {
+  describe('sha512', function() {
+    it('should digest the empty string', function() {
+      var md = SHA512.create();
+      ASSERT.equal(
+        md.digest().toHex(),
+        'cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e');
+    });
+
+    it('should digest "abc"', function() {
+      var md = SHA512.create();
+      md.update('abc');
+      ASSERT.equal(
+        md.digest().toHex(),
+        'ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f');
+    });
+
+    it('should digest "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"', function() {
+      var md = SHA512.create();
+      md.update('abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu');
+      ASSERT.equal(
+        md.digest().toHex(),
+        '8e959b75dae313da8cf4f72814fc143f8f7779c6eb9f7fa17299aeadb6889018501d289e4900f7e4331b99dec4b5433ac7d329eeb6dd26545e96e55b874be909');
+    });
+
+    it('should digest "The quick brown fox jumps over the lazy dog"', function() {
+      var md = SHA512.create();
+      md.update('The quick brown fox jumps over the lazy dog');
+      ASSERT.equal(
+        md.digest().toHex(),
+        '07e547d9586f6a73f73fbac0435ed76951218fb7d0c8d788a309d785436bbb642e93a252a954f23912547d1e8a3b5ed6e1bfd7097821233fa0538f3db854fee6');
+    });
+
+    it('should digest "c\'\u00e8"', function() {
+      var md = SHA512.create();
+      md.update("c\'\u00e8", 'utf8');
+      ASSERT.equal(
+        md.digest().toHex(),
+        '9afdc0390dd91e81c63f858d1c6fcd9f949f3fc89dbdaed9e4211505bad63d8e8787797e2e9ea651285eb6954e51c4f0299837c3108cb40f1420bca1d237355c');
+    });
+
+    it('should digest "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"', function() {
+      var md = SHA512.create();
+      md.start();
+      md.update('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq');
+      // do twice to check continuing digest
+      ASSERT.equal(
+        md.digest().toHex(),
+        '204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c33596fd15c13b1b07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445');
+      ASSERT.equal(
+        md.digest().toHex(),
+        '204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c33596fd15c13b1b07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445');
+    });
+  });
+
+  SHA384 = SHA512.sha384;
+
+  describe('sha384', function() {
+    it('should digest the empty string', function() {
+      var md = SHA384.create();
+      ASSERT.equal(
+        md.digest().toHex(),
+        '38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b');
+    });
+
+    it('should digest "abc"', function() {
+      var md = SHA384.create();
+      md.update('abc');
+      ASSERT.equal(
+        md.digest().toHex(),
+        'cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7');
+    });
+
+    it('should digest "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"', function() {
+      var md = SHA384.create();
+      md.update('abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu');
+      ASSERT.equal(
+        md.digest().toHex(),
+        '09330c33f71147e83d192fc782cd1b4753111b173b3b05d22fa08086e3b0f712fcc7c71a557e2db966c3e9fa91746039');
+    });
+
+    it('should digest "The quick brown fox jumps over the lazy dog"', function() {
+      var md = SHA384.create();
+      md.update('The quick brown fox jumps over the lazy dog');
+      ASSERT.equal(
+        md.digest().toHex(),
+        'ca737f1014a48f4c0b6dd43cb177b0afd9e5169367544c494011e3317dbf9a509cb1e5dc1e85a941bbee3d7f2afbc9b1');
+    });
+
+    it('should digest "c\'\u00e8"', function() {
+      var md = SHA384.create();
+      md.update("c\'\u00e8", 'utf8');
+      ASSERT.equal(
+        md.digest().toHex(),
+        '382ec8a92d50abf57f7d0f934ff3969d6d354d30c96f1616678a920677867aba49521d2d535c0f285a3c2961c2034ea3');
+    });
+
+    it('should digest "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"', function() {
+      var md = SHA384.create();
+      md.start();
+      md.update('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq');
+      // do twice to check continuing digest
+      ASSERT.equal(
+        md.digest().toHex(),
+        '3391fdddfc8dc7393707a65b1b4709397cf8b1d162af05abfe8f450de5f36bc6b0455a8520bc4e6f5fe95b1fe3c8452b');
+      ASSERT.equal(
+        md.digest().toHex(),
+        '3391fdddfc8dc7393707a65b1b4709397cf8b1d162af05abfe8f450de5f36bc6b0455a8520bc4e6f5fe95b1fe3c8452b');
+    });
+  });
+
+  SHA256 = SHA512.sha256;
+
+  describe('sha512/256', function() {
+    it('should digest the empty string', function() {
+      var md = SHA256.create();
+      ASSERT.equal(
+        md.digest().toHex(),
+        'c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a');
+    });
+
+    it('should digest "The quick brown fox jumps over the lazy dog"', function() {
+      var md = SHA256.create();
+      md.update('The quick brown fox jumps over the lazy dog');
+      ASSERT.equal(
+        md.digest().toHex(),
+        'dd9d67b371519c339ed8dbd25af90e976a1eeefd4ad3d889005e532fc5bef04d');
+    });
+  });
+
+  SHA224 = SHA512.sha224;
+
+  describe('sha512/224', function() {
+    it('should digest the empty string', function() {
+      var md = SHA224.create();
+      ASSERT.equal(
+        md.digest().toHex(),
+        '6ed0dd02806fa89e25de060c19d3ac86cabb87d6a0ddd05c333b84f4');
+    });
+
+    it('should digest "The quick brown fox jumps over the lazy dog"', function() {
+      var md = SHA224.create();
+      md.update('The quick brown fox jumps over the lazy dog');
+      ASSERT.equal(
+        md.digest().toHex(),
+        '944cd2847fb54558d4775db0485a50003111c8e5daa63fe722c6aa37');
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/sha512',
+    'forge/util'
+  ], function(SHA512, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      SHA512(),
+      UTIL()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/sha512')(),
+    require('../../js/util')());
+}
+
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/ssh.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/ssh.js
new file mode 100644
index 0000000..c90eb26
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/ssh.js
@@ -0,0 +1,193 @@
+(function() {
+
+function Tests(ASSERT, forge) {
+
+  // Original RSA key generated by openssh
+  var keystr =
+    '-----BEGIN RSA PRIVATE KEY-----\r\n' +
+    'MIIEogIBAAKCAQEA301O+LBnd6ngw9i0Pb5iTwlQ1s37ay3DNpisjh+jPDDvaIZq\r\n' +
+    'PC+44YV7RaR1ib2wEoQfg4DwqVw5wlA2x97Txngb+sJe0yUfmM9vMTxhXWuURVR6\r\n' +
+    '6DGd8t2eOBN8qDoYlpvdVf8Go5Btrb6NCexUbZtFS1AmumK2zQKxIizgVLK8dx4G\r\n' +
+    'NYbqxibUxgYHbwRpu8ROrogVrZbMW1cOb4JE+DzG1FvfHSdzkxb9e5ARcsuLxCdP\r\n' +
+    'ItUMVY8jjgER6b5lK+Nzr57hFdr08RjWGBldNGrCFqDdm+1WkGAHTRrg/i/MD8BU\r\n' +
+    '8NCFUBpQTSrhALkGqBdGZPN/PrXonXjhcd11awIDAQABAoIBAHGMESUaJnLN2jIc\r\n' +
+    'RoLDBaBk/0tLEJaOfZ6Mgen/InUf+Q0wlGKobZ2Xz3g5SV9SKm8v6gpnjXjBIcmy\r\n' +
+    'GjkGEK/yMWAQaEF7thZxHHxv1J65bnrWm2zolgWCNcsT9aZhbFFhTmpFNO4FKhBY\r\n' +
+    'PcWW+9OESfci+Z57RbL3tHTJVwUZrthtagpFEgVceq18GLSS9N4TWJ8HX1CDxf9R\r\n' +
+    '4gzEy3+8oC0QONx+bgsWUmzNk9QNNW6yPV5NYZ3SwNLVgw3+6m8WDaqo5FWK7y2f\r\n' +
+    'pa63DUzXlg5uSYnk4OFVxI6TLpmNQcjigpXnsd1PwcynW0AM83jzIYu0F31ppU61\r\n' +
+    '8flrqfECgYEA8j/joUve1F4lTt2U34RF+59giZ1r0ME4LafNEgN/ZLMB8ghToSqr\r\n' +
+    'qzNnJnSppkAKTa2NVZGgJkvqn+qysM1HNm/3ksdUcE5yijgc3E17PNdBJwwNLZfP\r\n' +
+    'p9jB/ZPaNMduGqltYamOVkbg/qS7O4rcUHstrGGGtJ9fveH+cu87emMCgYEA6/oX\r\n' +
+    '6A2fW88hw4hZwV2pCk6gumi89vXbKbhnD6j907TW583xIqXYsQBb7x/KGyPf65+k\r\n' +
+    'Sou9MRyLhRu7qcc8INpSnFsra8ZQosP+ao8tUTq7p7N0H27qG5liTeAAksvk/xnq\r\n' +
+    '2VdL1YDRpo4tmRD7TAj8uc1sgXqdsBCPrqq4Q1kCgYAHcNjwEmGEymOA+aNh/jEc\r\n' +
+    'Gngfofs2zUiJdncBD6RxFmJ/6auP7ryZJJoNf1XaqmrmmecWcsOliX1qbg4RCi0e\r\n' +
+    'ye+jzYWVcYNpJXIVfjfD1aTFq0QYW2pgcHL88/am2l1SalPWxRt/IOw2Rh8OJCTC\r\n' +
+    'QBZWDiTSFXceYPus0hZUmwKBgCc2FYbfzJ0q3Adrvs5cy9wEmLyg7tVyoQpbs/Rs\r\n' +
+    'NlFZeWRnWixRtqIi1yPy+lhsK6cxjdE9SyDAB4cExrg9fQZQgO2uUJbGC1wgiUQX\r\n' +
+    'qoYW5lvFfARFH+2aHTWnhTDfZJvnKJkY4mcF0tCES5tlsPw/eg89zUvunglFlzqE\r\n' +
+    '771xAoGAdYRG1PIkAzjuh785rc35885dsaXChZx1+7rfZ+AclyisRsmES9UfqL6M\r\n' +
+    '+SuluaBSWJQUBS7iaHazUeXmy5t+HBjSSLuAOHoZUckwDWD3EM7GHjFlWJOCArI3\r\n' +
+    'hOYlsXSyl07rApzg/t+HxXcNpLZGJTgRwrRGF2OipUL0VPlTdRc=\r\n' +
+    '-----END RSA PRIVATE KEY-----\r\n';
+
+  var key = forge.pki.privateKeyFromPem(keystr);
+
+  describe('ssh', function() {
+    it('should convert keys to openssh public keys', function() {
+      var expect =
+        'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDfTU74sGd3qeDD2LQ9vmJPCVD' +
+        'WzftrLcM2mKyOH6M8MO9ohmo8L7jhhXtFpHWJvbAShB+DgPCpXDnCUDbH3tPGeB' +
+        'v6wl7TJR+Yz28xPGFda5RFVHroMZ3y3Z44E3yoOhiWm91V/wajkG2tvo0J7FRtm' +
+        '0VLUCa6YrbNArEiLOBUsrx3HgY1hurGJtTGBgdvBGm7xE6uiBWtlsxbVw5vgkT4' +
+        'PMbUW98dJ3OTFv17kBFyy4vEJ08i1QxVjyOOARHpvmUr43OvnuEV2vTxGNYYGV0' +
+        '0asIWoN2b7VaQYAdNGuD+L8wPwFTw0IVQGlBNKuEAuQaoF0Zk838+teideOFx3X' +
+        'Vr A comment';
+
+      ASSERT.equal(forge.ssh.publicKeyToOpenSSH(key, 'A comment'), expect);
+    });
+
+    it('should convert keys to putty unencrypted keys', function() {
+      var expect =
+        'PuTTY-User-Key-File-2: ssh-rsa\r\n' +
+        'Encryption: none\r\n' +
+        'Comment: imported-openssh-key\r\n' +
+        'Public-Lines: 6\r\n' +
+        'AAAAB3NzaC1yc2EAAAADAQABAAABAQDfTU74sGd3qeDD2LQ9vmJPCVDWzftrLcM2\r\n' +
+        'mKyOH6M8MO9ohmo8L7jhhXtFpHWJvbAShB+DgPCpXDnCUDbH3tPGeBv6wl7TJR+Y\r\n' +
+        'z28xPGFda5RFVHroMZ3y3Z44E3yoOhiWm91V/wajkG2tvo0J7FRtm0VLUCa6YrbN\r\n' +
+        'ArEiLOBUsrx3HgY1hurGJtTGBgdvBGm7xE6uiBWtlsxbVw5vgkT4PMbUW98dJ3OT\r\n' +
+        'Fv17kBFyy4vEJ08i1QxVjyOOARHpvmUr43OvnuEV2vTxGNYYGV00asIWoN2b7VaQ\r\n' +
+        'YAdNGuD+L8wPwFTw0IVQGlBNKuEAuQaoF0Zk838+teideOFx3XVr\r\n' +
+        'Private-Lines: 14\r\n' +
+        'AAABAHGMESUaJnLN2jIcRoLDBaBk/0tLEJaOfZ6Mgen/InUf+Q0wlGKobZ2Xz3g5\r\n' +
+        'SV9SKm8v6gpnjXjBIcmyGjkGEK/yMWAQaEF7thZxHHxv1J65bnrWm2zolgWCNcsT\r\n' +
+        '9aZhbFFhTmpFNO4FKhBYPcWW+9OESfci+Z57RbL3tHTJVwUZrthtagpFEgVceq18\r\n' +
+        'GLSS9N4TWJ8HX1CDxf9R4gzEy3+8oC0QONx+bgsWUmzNk9QNNW6yPV5NYZ3SwNLV\r\n' +
+        'gw3+6m8WDaqo5FWK7y2fpa63DUzXlg5uSYnk4OFVxI6TLpmNQcjigpXnsd1Pwcyn\r\n' +
+        'W0AM83jzIYu0F31ppU618flrqfEAAACBAPI/46FL3tReJU7dlN+ERfufYImda9DB\r\n' +
+        'OC2nzRIDf2SzAfIIU6Eqq6szZyZ0qaZACk2tjVWRoCZL6p/qsrDNRzZv95LHVHBO\r\n' +
+        'coo4HNxNezzXQScMDS2Xz6fYwf2T2jTHbhqpbWGpjlZG4P6kuzuK3FB7LaxhhrSf\r\n' +
+        'X73h/nLvO3pjAAAAgQDr+hfoDZ9bzyHDiFnBXakKTqC6aLz29dspuGcPqP3TtNbn\r\n' +
+        'zfEipdixAFvvH8obI9/rn6RKi70xHIuFG7upxzwg2lKcWytrxlCiw/5qjy1ROrun\r\n' +
+        's3QfbuobmWJN4ACSy+T/GerZV0vVgNGmji2ZEPtMCPy5zWyBep2wEI+uqrhDWQAA\r\n' +
+        'AIB1hEbU8iQDOO6Hvzmtzfnzzl2xpcKFnHX7ut9n4ByXKKxGyYRL1R+ovoz5K6W5\r\n' +
+        'oFJYlBQFLuJodrNR5ebLm34cGNJIu4A4ehlRyTANYPcQzsYeMWVYk4ICsjeE5iWx\r\n' +
+        'dLKXTusCnOD+34fFdw2ktkYlOBHCtEYXY6KlQvRU+VN1Fw==\r\n' +
+        'Private-MAC: 87fa1011848453317d8e41b00c927e9d17dc334e\r\n';
+
+      ASSERT.equal(forge.ssh.privateKeyToPutty(key, '', 'imported-openssh-key'), expect);
+    });
+
+    it('should convert keys to putty encrypted keys', function() {
+      var expect =
+        'PuTTY-User-Key-File-2: ssh-rsa\r\n' +
+        'Encryption: aes256-cbc\r\n' +
+        'Comment: imported-openssh-key\r\n' +
+        'Public-Lines: 6\r\n' +
+        'AAAAB3NzaC1yc2EAAAADAQABAAABAQDfTU74sGd3qeDD2LQ9vmJPCVDWzftrLcM2\r\n' +
+        'mKyOH6M8MO9ohmo8L7jhhXtFpHWJvbAShB+DgPCpXDnCUDbH3tPGeBv6wl7TJR+Y\r\n' +
+        'z28xPGFda5RFVHroMZ3y3Z44E3yoOhiWm91V/wajkG2tvo0J7FRtm0VLUCa6YrbN\r\n' +
+        'ArEiLOBUsrx3HgY1hurGJtTGBgdvBGm7xE6uiBWtlsxbVw5vgkT4PMbUW98dJ3OT\r\n' +
+        'Fv17kBFyy4vEJ08i1QxVjyOOARHpvmUr43OvnuEV2vTxGNYYGV00asIWoN2b7VaQ\r\n' +
+        'YAdNGuD+L8wPwFTw0IVQGlBNKuEAuQaoF0Zk838+teideOFx3XVr\r\n' +
+        'Private-Lines: 14\r\n' +
+        'EiVwpacmA7mhmGBTPXeIZZPkeRDtb4LOWzI+68cA5oM7UJTpBxh9zsnpAdWg2knP\r\n' +
+        'snA5gpTSq0CJV9HWb8yAY3R5izflQ493fCbJzuPFTW6RJ2y/D5dUmDWccfMczPNT\r\n' +
+        'GEDOfslCPe+Yz1h0y0y5NI5WwJowxv3nL9FTlQ9KDVrdoSizkVbTh4CJTrvqIc7c\r\n' +
+        'sI/HU25OHS8kcIZPiPL8m40ot254rkbPCWrskm9H4n+EC+BwJNtEic9ZQfGPWOVl\r\n' +
+        't8JxY35mHqGIlfhyVkdts/Rx7kwOHY+GPXDVRnHQZQOkFtVqCFGx8mL83CspIEq0\r\n' +
+        'V8LaHuvxiadA4OEeR0azuDhfVJXvrUpHd4CPjAzJu4doHm98GJAyrz3mtCyxIguH\r\n' +
+        'k8zKVJzbNawy5T43l5x9cR6VKzcl6d4V14vphiovxc8DG/J7RHBd4d1y6wxo5ZMY\r\n' +
+        'qqQ0E6VHtq6auBZjnGzx0P/1lGjpZdxUf4OVTaZ+stCxX5wAH9exF+gdZAlk20Gp\r\n' +
+        'Atg60boQuolHBiH66dQynyHp+6yuLPLKXy74EO+AEB3HvNK7aIQV8rCD7I7HEa12\r\n' +
+        'xxqIH4l0FWQKHXNIrK45uo6Hdg1siYp9zU4FFgDcNGOZJsT6+etPp1sgAeBuBR4F\r\n' +
+        'pnuX1KZzRTbG1kcRrTOjsMh0bKfZAn0+uwyuPBtwEnLziGoCXU+9+XO85zcPF2j1\r\n' +
+        'Ip5AWAOBI82SKMLu51Dpdz1rwSZOPFWnHxRxfnUiQa9Kc7qBGrMxy1UNECAwMGzp\r\n' +
+        'ljKesqZvoANo0voiodALXGs7kSpmXjcbHnUUt0SI/QHyajXabIiLHGf6lfvStYLP\r\n' +
+        'L5bLfswsMqG/2lonYWrPl9YC0WzvfRpbHgpI9G1eLjFFRlaqJ3EpZf5Dy26Z0eh0\r\n' +
+        'Private-MAC: 23aa5b6e2a411107c59e1e6c3bca06247e3c9627\r\n';
+
+      ASSERT.equal(forge.ssh.privateKeyToPutty(key, 'passphrase', 'imported-openssh-key'), expect);
+    });
+
+    it('should convert keys to openssh encrypted private keys', function() {
+      var expect =
+        '-----BEGIN RSA PRIVATE KEY-----\n' +
+        'Proc-Type: 4,ENCRYPTED\n' +
+        'DEK-Info: AES-128-CBC,2616162F269429AA628E42C3BD5A0027\n' +
+        '\n' +
+        'p8+mGWeQxZrRg6OeeFqgEX8sXGGUqWJuK4XhtgRpxAQaSg8bK6m/ahArEonjzgrO\n' +
+        'XMLow7N0aXqGJzL+n4c4EzL7e4SquzeYZLq0UCs8vbWE5GdTT6BxisWIJqzOaQW3\n' +
+        'd3OqS2lM5o47cuADMIMp015b0dJn5nwJall20GSI1XnpTUHIJ1oFv7fW/s5g39VD\n' +
+        'DSVmPzJEMhcTa8BskHrKITV6l+TuivGqrHH0LCYCfQ3IBLiRZrPINQLLkaHR6kis\n' +
+        '4qvFEMhQGAz0GrifwEob9+FPzDAHHnYTS0kG1jhZ3p92vaUi8sPxyv5ndRXOSZZg\n' +
+        'vh6Cdrk62myG/rHbsBRrrpa+Ka+BX4ofedwP3SBHPwqBpksYhEF7MxsWKhmHY+d0\n' +
+        'YINHrj0w+yfw4H3n1+0w4wajlHVUncp7RP8KKMtG3vvvfF1loWpLbyF0s6fgq7p4\n' +
+        '7kt1LcnRKB3U2IZYfMHuv94+5q0BKfGF6NmRpmgdJojyS2IXZyFaJRqrCa0mKcP9\n' +
+        'PtDZFPTEy0VeNTL8KqjweEmjK3/JtZPVgXXkPWlFMw3Hc/dtP/l6VssGLk8P/DHD\n' +
+        'yknagPsfa70iydTvDO+nrtS57DkoUqhMsU3BhCjMzZg+/bo1JgmxQUWT//PoQLSB\n' +
+        'Z7/F59AfHzZIkWgqfvGRzX3y+G1M1l11dX658iWmN0kZ5MyHU0qwU9hVI6P/fcfk\n' +
+        '6MQKc/MzPnkjw/knREtYMtHrVsKrDVDizfguGFKFC8FVhhrDOFZUnza0nh6nt1HZ\n' +
+        'Xk156MhATenWdHBW4Rn3ec1aMOD16k2SRIHd+nyJzx51H3PUdTtXBWqFNGggzoJG\n' +
+        '99ax3jD6pTLQY3BG146QKQ0csItMTIdwZPAidkzv8VVXC7HaqXk1K1pgfJT6mD4P\n' +
+        'LaNbuA9r7mNiNoPzwzk0h3BomBTMXZpAyL9Jlre9jTu6lpyN/TkOzHhs/I1/lvKQ\n' +
+        'Uki7BXv65Jq6RqkTbNc5plxBYggdzLGurr0ZIBDsoN6uXkzaM+fCMlJU8+MgAcBb\n' +
+        'x88bj8h3t4akPd/WaSsWKeOzB3Uaw3ztYCpwSVv1F+N0u6C6zGo+9VFAQZh1uKvC\n' +
+        'G9U5hvAG7WEoQ801/fvKj93lqLDhOarPJC8CcfMLwoIqj7zah7TtBYq8VsuF7Fsx\n' +
+        'dEWNFiXhtFqUhgBMt1tcOXGiXA79NucSVzUBNzrRZKlLDYm2TKKyOqEbG9vF/YxL\n' +
+        'ZG3TwmcFj0sko2K61ya7FGpKmfPQCJJoi0tIfnW4eIVcyeRpXYi5B2xqvSXcTdUx\n' +
+        '5y5Vpuu1CzrMZr50b3sSOFjcOXE5211RS8SHpOMWY+JDDB4vF4Dv94fqEIgnFtrR\n' +
+        'oQgk3DueWb1x09NcJtEZsW6lT3Jw19ursb++XSejFZ9Xu5ED8fbewgGo2w/N5j1H\n' +
+        'vQEnFkGcL1jLlLqp9PlvPIE4a///wy1y0XbnKMJs+dKxiesKVx1zZ1WDcK2Qgv4r\n' +
+        'G+RsZzHZuCjUyty1+SMVOYM6+3zW6bjXN58xI3XeSxgE/JaJKjLWBZWx5+eU7b6a\n' +
+        '04mJDMhnpdLHG97m9p90L1yuudiJfq6ngha41xxv9xLmNatfrtStCrq/DR0KHm0K\n' +
+        '-----END RSA PRIVATE KEY-----\n';
+
+      // Unable to test -- uses a random IV that I can't control
+      //ASSERT.equal(forge.ssh.rsaPrivateKeyAsOpenSSH(key, 'passphrase'), expect);
+    });
+
+    it('should convert keys to openssh unencrypted private keys', function() {
+      var expect = keystr;
+      ASSERT.equal(forge.ssh.privateKeyToOpenSSH(key, ''), expect);
+    });
+
+    it('should get an MD5 SSH fingerprint', function() {
+      var fp = forge.ssh.getPublicKeyFingerprint(key);
+      ASSERT.equal(fp.toHex(), '46549abeb89422a0955d4041ae7322ec');
+    });
+
+    it('should get a hex MD5 SSH fingerprint', function() {
+      var fp = forge.ssh.getPublicKeyFingerprint(key, {encoding: 'hex'});
+      ASSERT.equal(fp, '46549abeb89422a0955d4041ae7322ec');
+    });
+
+    it('should get a hex, colon-delimited MD5 SSH fingerprint', function() {
+      var fp = forge.ssh.getPublicKeyFingerprint(
+        key, {encoding: 'hex', delimiter: ':'});
+      ASSERT.equal(fp, '46:54:9a:be:b8:94:22:a0:95:5d:40:41:ae:73:22:ec');
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/forge'
+  ], function(forge) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      forge
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/forge'));
+}
+
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/tls.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/tls.js
new file mode 100644
index 0000000..d9ce944
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/tls.js
@@ -0,0 +1,191 @@
+(function() {
+
+function Tests(ASSERT, forge) {
+  describe('tls', function() {
+    it('should test TLS 1.0 PRF', function() {
+      // Note: This test vector is originally from:
+      // http://www.imc.org/ietf-tls/mail-archive/msg01589.html
+      // But that link is now dead.
+      var secret = forge.util.createBuffer().fillWithByte(0xAB, 48).getBytes();
+      var seed = forge.util.createBuffer().fillWithByte(0xCD, 64).getBytes();
+      var bytes = forge.tls.prf_tls1(secret, 'PRF Testvector',  seed, 104);
+      var expect =
+        'd3d4d1e349b5d515044666d51de32bab258cb521' +
+        'b6b053463e354832fd976754443bcf9a296519bc' +
+        '289abcbc1187e4ebd31e602353776c408aafb74c' +
+        'bc85eff69255f9788faa184cbb957a9819d84a5d' +
+        '7eb006eb459d3ae8de9810454b8b2d8f1afbc655' +
+        'a8c9a013';
+      ASSERT.equal(bytes.toHex(), expect);
+    });
+
+    it('should establish a TLS connection and transfer data', function(done) {
+      var end = {};
+      var data = {};
+
+      createCertificate('server', data);
+      createCertificate('client', data);
+      data.client.connection = {};
+      data.server.connection = {};
+
+      end.client = forge.tls.createConnection({
+        server: false,
+        caStore: [data.server.cert],
+        sessionCache: {},
+        cipherSuites: [
+          forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+          forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+        virtualHost: 'server',
+        verify: function(c, verified, depth, certs) {
+          data.client.connection.commonName =
+            certs[0].subject.getField('CN').value;
+          data.client.connection.certVerified = verified;
+          return true;
+        },
+        connected: function(c) {
+          c.prepare('Hello Server');
+        },
+        getCertificate: function(c, hint) {
+          return data.client.cert;
+        },
+        getPrivateKey: function(c, cert) {
+          return data.client.privateKey;
+        },
+        tlsDataReady: function(c) {
+          end.server.process(c.tlsData.getBytes());
+        },
+        dataReady: function(c) {
+          data.client.connection.data = c.data.getBytes();
+          c.close();
+        },
+        closed: function(c) {
+          ASSERT.equal(data.client.connection.commonName, 'server');
+          ASSERT.equal(data.client.connection.certVerified, true);
+          ASSERT.equal(data.client.connection.data, 'Hello Client');
+          done();
+        },
+        error: function(c, error) {
+          ASSERT.equal(error.message, undefined);
+        }
+      });
+
+      end.server = forge.tls.createConnection({
+        server: true,
+        caStore: [data.client.cert],
+        sessionCache: {},
+        cipherSuites: [
+          forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+          forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+        connected: function(c) {
+        },
+        verifyClient: true,
+        verify: function(c, verified, depth, certs) {
+          data.server.connection.commonName =
+            certs[0].subject.getField('CN').value;
+          data.server.connection.certVerified = verified;
+          return true;
+        },
+        getCertificate: function(c, hint) {
+          data.server.connection.certHint = hint[0];
+          return data.server.cert;
+        },
+        getPrivateKey: function(c, cert) {
+          return data.server.privateKey;
+        },
+        tlsDataReady: function(c) {
+          end.client.process(c.tlsData.getBytes());
+        },
+        dataReady: function(c) {
+          data.server.connection.data = c.data.getBytes();
+          c.prepare('Hello Client');
+          c.close();
+        },
+        closed: function(c) {
+          ASSERT.equal(data.server.connection.certHint, 'server');
+          ASSERT.equal(data.server.connection.commonName, 'client');
+          ASSERT.equal(data.server.connection.certVerified, true);
+          ASSERT.equal(data.server.connection.data, 'Hello Server');
+        },
+        error: function(c, error) {
+          ASSERT.equal(error.message, undefined);
+        }
+      });
+
+      end.client.handshake();
+
+      function createCertificate(cn, data) {
+        var keys = forge.pki.rsa.generateKeyPair(512);
+        var cert = forge.pki.createCertificate();
+        cert.publicKey = keys.publicKey;
+        cert.serialNumber = '01';
+        cert.validity.notBefore = new Date();
+        cert.validity.notAfter = new Date();
+        cert.validity.notAfter.setFullYear(
+          cert.validity.notBefore.getFullYear() + 1);
+        var attrs = [{
+          name: 'commonName',
+          value: cn
+        }, {
+          name: 'countryName',
+          value: 'US'
+        }, {
+          shortName: 'ST',
+          value: 'Virginia'
+        }, {
+          name: 'localityName',
+          value: 'Blacksburg'
+        }, {
+          name: 'organizationName',
+          value: 'Test'
+        }, {
+          shortName: 'OU',
+          value: 'Test'
+        }];
+        cert.setSubject(attrs);
+        cert.setIssuer(attrs);
+        cert.setExtensions([{
+          name: 'basicConstraints',
+          cA: true
+        }, {
+          name: 'keyUsage',
+          keyCertSign: true,
+          digitalSignature: true,
+          nonRepudiation: true,
+          keyEncipherment: true,
+          dataEncipherment: true
+        }, {
+          name: 'subjectAltName',
+          altNames: [{
+            type: 6, // URI
+            value: 'https://myuri.com/webid#me'
+          }]
+        }]);
+        cert.sign(keys.privateKey);
+        data[cn] = {
+          cert: forge.pki.certificateToPem(cert),
+          privateKey: forge.pki.privateKeyToPem(keys.privateKey)
+        };
+      }
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/forge'
+  ], function(forge) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      forge
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/forge'));
+}
+
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/util.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/util.js
new file mode 100644
index 0000000..57104a1
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/util.js
@@ -0,0 +1,406 @@
+(function() {
+
+function Tests(ASSERT, UTIL) {
+  // custom assertion to test array-like objects
+  function assertArrayEqual(actual, expected) {
+    ASSERT.equal(actual.length, expected.length);
+    for (var idx = 0; idx < expected.length; idx++) {
+      ASSERT.equal(actual[idx], expected[idx]);
+    }
+  }
+
+  describe('util', function() {
+    it('should put bytes into a buffer', function() {
+      var b = UTIL.createBuffer();
+      b.putByte(1);
+      b.putByte(2);
+      b.putByte(3);
+      b.putByte(4);
+      b.putInt32(4);
+      b.putByte(1);
+      b.putByte(2);
+      b.putByte(3);
+      b.putInt32(4294967295);
+      var hex = b.toHex();
+      ASSERT.equal(hex, '0102030400000004010203ffffffff');
+
+      var bytes = [];
+      while(b.length() > 0) {
+        bytes.push(b.getByte());
+      }
+      ASSERT.deepEqual(
+        bytes, [1, 2, 3, 4, 0, 0, 0, 4, 1, 2, 3, 255, 255, 255, 255]);
+    });
+
+    it('should put bytes from an Uint8Array into a buffer', function() {
+      if(typeof Uint8Array === 'undefined') {
+        return;
+      }
+      var data = [1, 2, 3, 4, 0, 0, 0, 4, 1, 2, 3, 255, 255, 255, 255];
+      var ab = new Uint8Array(data);
+      var b = UTIL.createBuffer(ab);
+      var hex = b.toHex();
+      ASSERT.equal(hex, '0102030400000004010203ffffffff');
+
+      var bytes = [];
+      while(b.length() > 0) {
+        bytes.push(b.getByte());
+      }
+      ASSERT.deepEqual(bytes, data);
+    });
+
+    it('should convert bytes from hex', function() {
+      var hex = '0102030400000004010203ffffffff';
+      var b = UTIL.createBuffer();
+      b.putBytes(UTIL.hexToBytes(hex));
+      ASSERT.equal(b.toHex(), hex);
+    });
+
+    it('should put 0 into a buffer using two\'s complement', function() {
+      var b = UTIL.createBuffer();
+      b.putSignedInt(0, 8);
+      ASSERT.equal(b.toHex(), '00');
+    });
+
+    it('should put 0 into a buffer using two\'s complement w/2 bytes', function() {
+      var b = UTIL.createBuffer();
+      b.putSignedInt(0, 16);
+      ASSERT.equal(b.toHex(), '0000');
+    });
+
+    it('should put 127 into a buffer using two\'s complement', function() {
+      var b = UTIL.createBuffer();
+      b.putSignedInt(127, 8);
+      ASSERT.equal(b.toHex(), '7f');
+    });
+
+    it('should put 127 into a buffer using two\'s complement w/2 bytes', function() {
+      var b = UTIL.createBuffer();
+      b.putSignedInt(127, 16);
+      ASSERT.equal(b.toHex(), '007f');
+    });
+
+    it('should put 128 into a buffer using two\'s complement', function() {
+      var b = UTIL.createBuffer();
+      b.putSignedInt(128, 16);
+      ASSERT.equal(b.toHex(), '0080');
+    });
+
+    it('should put 256 into a buffer using two\'s complement', function() {
+      var b = UTIL.createBuffer();
+      b.putSignedInt(256, 16);
+      ASSERT.equal(b.toHex(), '0100');
+    });
+
+    it('should put -128 into a buffer using two\'s complement', function() {
+      var b = UTIL.createBuffer();
+      b.putSignedInt(-128, 8);
+      ASSERT.equal(b.toHex(), '80');
+    });
+
+    it('should put -129 into a buffer using two\'s complement', function() {
+      var b = UTIL.createBuffer();
+      b.putSignedInt(-129, 16);
+      ASSERT.equal(b.toHex(), 'ff7f');
+    });
+
+    it('should get 0 from a buffer using two\'s complement', function() {
+      var x = 0;
+      var n = 8;
+      var b = UTIL.createBuffer();
+      b.putSignedInt(x, n);
+      ASSERT.equal(b.getSignedInt(x, n), x);
+    });
+
+    it('should get 127 from a buffer using two\'s complement', function() {
+      var x = 127;
+      var n = 8;
+      var b = UTIL.createBuffer();
+      b.putSignedInt(x, n);
+      ASSERT.equal(b.getSignedInt(n), x);
+    });
+
+    it('should get 128 from a buffer using two\'s complement', function() {
+      var x = 128;
+      var n = 16;
+      var b = UTIL.createBuffer();
+      b.putSignedInt(x, n);
+      ASSERT.equal(b.getSignedInt(n), x);
+    });
+
+    it('should get 256 from a buffer using two\'s complement', function() {
+      var x = 256;
+      var n = 16;
+      var b = UTIL.createBuffer();
+      b.putSignedInt(x, n);
+      ASSERT.equal(b.getSignedInt(n), x);
+    });
+
+    it('should get -128 from a buffer using two\'s complement', function() {
+      var x = -128;
+      var n = 8;
+      var b = UTIL.createBuffer();
+      b.putSignedInt(x, n);
+      ASSERT.equal(b.getSignedInt(n), x);
+    });
+
+    it('should get -129 from a buffer using two\'s complement', function() {
+      var x = -129;
+      var n = 16;
+      var b = UTIL.createBuffer();
+      b.putSignedInt(x, n);
+      ASSERT.equal(b.getSignedInt(n), x);
+    });
+
+    it('should base64 encode some bytes', function() {
+      var s1 = '00010203050607080A0B0C0D0F1011121415161719';
+      var s2 = 'MDAwMTAyMDMwNTA2MDcwODBBMEIwQzBEMEYxMDExMTIxNDE1MTYxNzE5';
+      ASSERT.equal(UTIL.encode64(s1), s2);
+    });
+
+    it('should base64 decode some bytes', function() {
+      var s1 = '00010203050607080A0B0C0D0F1011121415161719';
+      var s2 = 'MDAwMTAyMDMwNTA2MDcwODBBMEIwQzBEMEYxMDExMTIxNDE1MTYxNzE5';
+      ASSERT.equal(UTIL.decode64(s2), s1);
+    });
+    
+    it('should base64 encode some bytes using util.binary.base64', function() {
+      var s1 = new Uint8Array([
+        0x30, 0x30, 0x30, 0x31, 0x30, 0x32, 0x30, 0x33, 0x30,
+        0x35, 0x30, 0x36, 0x30, 0x37, 0x30, 0x38, 0x30, 0x41,
+        0x30, 0x42, 0x30, 0x43, 0x30, 0x44, 0x30, 0x46, 0x31,
+        0x30, 0x31, 0x31, 0x31, 0x32, 0x31, 0x34, 0x31, 0x35,
+        0x31, 0x36, 0x31, 0x37, 0x31, 0x39]);
+      var s2 = 'MDAwMTAyMDMwNTA2MDcwODBBMEIwQzBEMEYxMDExMTIxNDE1MTYxNzE5';
+      ASSERT.equal(UTIL.binary.base64.encode(s1), s2);
+    });
+
+    it('should base64 encode some odd-length bytes using util.binary.base64', function() {
+      var s1 = new Uint8Array([
+        0x30, 0x30, 0x30, 0x31, 0x30, 0x32, 0x30, 0x33, 0x30,
+        0x35, 0x30, 0x36, 0x30, 0x37, 0x30, 0x38, 0x30, 0x41,
+        0x30, 0x42, 0x30, 0x43, 0x30, 0x44, 0x30, 0x46, 0x31,
+        0x30, 0x31, 0x31, 0x31, 0x32, 0x31, 0x34, 0x31, 0x35,
+        0x31, 0x36, 0x31, 0x37, 0x31, 0x39, 0x31, 0x41, 0x31,
+        0x42]);
+      var s2 = 'MDAwMTAyMDMwNTA2MDcwODBBMEIwQzBEMEYxMDExMTIxNDE1MTYxNzE5MUExQg==';
+      ASSERT.equal(UTIL.binary.base64.encode(s1), s2);
+    });
+
+    it('should base64 decode some bytes using util.binary.base64', function() {
+      var s1 = new Uint8Array([
+        0x30, 0x30, 0x30, 0x31, 0x30, 0x32, 0x30, 0x33, 0x30,
+        0x35, 0x30, 0x36, 0x30, 0x37, 0x30, 0x38, 0x30, 0x41,
+        0x30, 0x42, 0x30, 0x43, 0x30, 0x44, 0x30, 0x46, 0x31,
+        0x30, 0x31, 0x31, 0x31, 0x32, 0x31, 0x34, 0x31, 0x35,
+        0x31, 0x36, 0x31, 0x37, 0x31, 0x39]);
+      var s2 = 'MDAwMTAyMDMwNTA2MDcwODBBMEIwQzBEMEYxMDExMTIxNDE1MTYxNzE5';
+      ASSERT.deepEqual(UTIL.binary.base64.decode(s2), s1);
+    });
+      
+    it('should base64 decode some odd-length bytes using util.binary.base64', function() {
+      var s1 = new Uint8Array([
+        0x30, 0x30, 0x30, 0x31, 0x30, 0x32, 0x30, 0x33, 0x30,
+        0x35, 0x30, 0x36, 0x30, 0x37, 0x30, 0x38, 0x30, 0x41,
+        0x30, 0x42, 0x30, 0x43, 0x30, 0x44, 0x30, 0x46, 0x31,
+        0x30, 0x31, 0x31, 0x31, 0x32, 0x31, 0x34, 0x31, 0x35,
+        0x31, 0x36, 0x31, 0x37, 0x31, 0x39, 0x31, 0x41, 0x31,
+        0x42]);
+      var s2 = 'MDAwMTAyMDMwNTA2MDcwODBBMEIwQzBEMEYxMDExMTIxNDE1MTYxNzE5MUExQg==';
+      assertArrayEqual(UTIL.binary.base64.decode(s2), s1);
+    });
+      
+    it('should convert IPv4 0.0.0.0 textual address to 4-byte address', function() {
+      var bytes = UTIL.bytesFromIP('0.0.0.0');
+      var b = UTIL.createBuffer().fillWithByte(0, 4);
+      ASSERT.equal(bytes, b.getBytes());
+    });
+
+    it('should convert IPv4 127.0.0.1 textual address to 4-byte address', function() {
+      var bytes = UTIL.bytesFromIP('127.0.0.1');
+      var b = UTIL.createBuffer();
+      b.putByte(127);
+      b.putByte(0);
+      b.putByte(0);
+      b.putByte(1);
+      ASSERT.equal(bytes, b.getBytes());
+    });
+
+    it('should convert IPv6 :: textual address to 16-byte address', function() {
+      var bytes = UTIL.bytesFromIP('::');
+      var b = UTIL.createBuffer().fillWithByte(0, 16);
+      ASSERT.equal(bytes, b.getBytes());
+    });
+
+    it('should convert IPv6 ::0 textual address to 16-byte address', function() {
+      var bytes = UTIL.bytesFromIP('::0');
+      var b = UTIL.createBuffer().fillWithByte(0, 16);
+      ASSERT.equal(bytes, b.getBytes());
+    });
+
+    it('should convert IPv6 0:: textual address to 16-byte address', function() {
+      var bytes = UTIL.bytesFromIP('0::');
+      var b = UTIL.createBuffer().fillWithByte(0, 16);
+      ASSERT.equal(bytes, b.getBytes());
+    });
+
+    it('should convert IPv6 ::1 textual address to 16-byte address', function() {
+      var bytes = UTIL.bytesFromIP('::1');
+      var b = UTIL.createBuffer().fillWithByte(0, 14);
+      b.putBytes(UTIL.hexToBytes('0001'));
+      ASSERT.equal(bytes, b.getBytes());
+    });
+
+    it('should convert IPv6 1:: textual address to 16-byte address', function() {
+      var bytes = UTIL.bytesFromIP('1::');
+      var b = UTIL.createBuffer();
+      b.putBytes(UTIL.hexToBytes('0001'));
+      b.fillWithByte(0, 14);
+      ASSERT.equal(bytes, b.getBytes());
+    });
+
+    it('should convert IPv6 1::1 textual address to 16-byte address', function() {
+      var bytes = UTIL.bytesFromIP('1::1');
+      var b = UTIL.createBuffer();
+      b.putBytes(UTIL.hexToBytes('0001'));
+      b.fillWithByte(0, 12);
+      b.putBytes(UTIL.hexToBytes('0001'));
+      ASSERT.equal(bytes, b.getBytes());
+    });
+
+    it('should convert IPv6 1::1:0 textual address to 16-byte address', function() {
+      var bytes = UTIL.bytesFromIP('1::1:0');
+      var b = UTIL.createBuffer();
+      b.putBytes(UTIL.hexToBytes('0001'));
+      b.fillWithByte(0, 10);
+      b.putBytes(UTIL.hexToBytes('0001'));
+      b.putBytes(UTIL.hexToBytes('0000'));
+      ASSERT.equal(bytes, b.getBytes());
+    });
+
+    it('should convert IPv6 2001:db8:0:1:1:1:1:1 textual address to 16-byte address', function() {
+      var bytes = UTIL.bytesFromIP('2001:db8:0:1:1:1:1:1');
+      var b = UTIL.createBuffer();
+      b.putBytes(UTIL.hexToBytes('2001'));
+      b.putBytes(UTIL.hexToBytes('0db8'));
+      b.putBytes(UTIL.hexToBytes('0000'));
+      b.putBytes(UTIL.hexToBytes('0001'));
+      b.putBytes(UTIL.hexToBytes('0001'));
+      b.putBytes(UTIL.hexToBytes('0001'));
+      b.putBytes(UTIL.hexToBytes('0001'));
+      b.putBytes(UTIL.hexToBytes('0001'));
+      ASSERT.equal(bytes, b.getBytes());
+    });
+
+    it('should convert IPv4 0.0.0.0 byte address to textual representation', function() {
+      var addr = '0.0.0.0';
+      var bytes = UTIL.createBuffer().fillWithByte(0, 4).getBytes();
+      var addr = UTIL.bytesToIP(bytes);
+      ASSERT.equal(addr, '0.0.0.0');
+    });
+
+    it('should convert IPv4 0.0.0.0 byte address to textual representation', function() {
+      var addr = '127.0.0.1';
+      var bytes = UTIL.bytesFromIP(addr);
+      var addr = UTIL.bytesToIP(bytes);
+      ASSERT.equal(addr, '127.0.0.1');
+    });
+
+    it('should convert IPv6 :: byte address to canonical textual representation (RFC 5952)', function() {
+      var addr = '::';
+      var bytes = UTIL.createBuffer().fillWithByte(0, 16).getBytes();
+      var addr = UTIL.bytesToIP(bytes);
+      ASSERT.equal(addr, '::');
+    });
+
+    it('should convert IPv6 ::1 byte address to canonical textual representation (RFC 5952)', function() {
+      var addr = '::1';
+      var bytes = UTIL.bytesFromIP(addr);
+      var addr = UTIL.bytesToIP(bytes);
+      ASSERT.equal(addr, '::1');
+    });
+
+    it('should convert IPv6 1:: byte address to canonical textual representation (RFC 5952)', function() {
+      var addr = '1::';
+      var bytes = UTIL.bytesFromIP(addr);
+      var addr = UTIL.bytesToIP(bytes);
+      ASSERT.equal(addr, '1::');
+    });
+
+    it('should convert IPv6 0:0:0:0:0:0:0:1 byte address to canonical textual representation (RFC 5952)', function() {
+      var addr = '0:0:0:0:0:0:0:1';
+      var bytes = UTIL.bytesFromIP(addr);
+      var addr = UTIL.bytesToIP(bytes);
+      ASSERT.equal(addr, '::1');
+    });
+
+    it('should convert IPv6 1:0:0:0:0:0:0:0 byte address to canonical textual representation (RFC 5952)', function() {
+      var addr = '1:0:0:0:0:0:0:0';
+      var bytes = UTIL.bytesFromIP(addr);
+      var addr = UTIL.bytesToIP(bytes);
+      ASSERT.equal(addr, '1::');
+    });
+
+    it('should convert IPv6 1::1 byte address to canonical textual representation (RFC 5952)', function() {
+      var addr = '1::1';
+      var bytes = UTIL.bytesFromIP(addr);
+      var addr = UTIL.bytesToIP(bytes);
+      ASSERT.equal(addr, '1::1');
+    });
+
+    it('should convert IPv6 1:0:0:0:0:0:0:1 byte address to canonical textual representation (RFC 5952)', function() {
+      var addr = '1:0:0:0:0:0:0:1';
+      var bytes = UTIL.bytesFromIP(addr);
+      var addr = UTIL.bytesToIP(bytes);
+      ASSERT.equal(addr, '1::1');
+    });
+
+    it('should convert IPv6 1:0000:0000:0000:0000:0000:0000:1 byte address to canonical textual representation (RFC 5952)', function() {
+      var addr = '1:0000:0000:0000:0000:0000:0000:1';
+      var bytes = UTIL.bytesFromIP(addr);
+      var addr = UTIL.bytesToIP(bytes);
+      ASSERT.equal(addr, '1::1');
+    });
+
+    it('should convert IPv6 1:0:0:1:1:1:0:1 byte address to canonical textual representation (RFC 5952)', function() {
+      var addr = '1:0:0:1:1:1:0:1';
+      var bytes = UTIL.bytesFromIP(addr);
+      var addr = UTIL.bytesToIP(bytes);
+      ASSERT.equal(addr, '1::1:1:1:0:1');
+    });
+
+    it('should convert IPv6 1:0:1:1:1:0:0:1 byte address to canonical textual representation (RFC 5952)', function() {
+      var addr = '1:0:1:1:1:0:0:1';
+      var bytes = UTIL.bytesFromIP(addr);
+      var addr = UTIL.bytesToIP(bytes);
+      ASSERT.equal(addr, '1:0:1:1:1::1');
+    });
+
+    it('should convert IPv6 2001:db8:0:1:1:1:1:1 byte address to canonical textual representation (RFC 5952)', function() {
+      var addr = '2001:db8:0:1:1:1:1:1';
+      var bytes = UTIL.bytesFromIP(addr);
+      var addr = UTIL.bytesToIP(bytes);
+      ASSERT.equal(addr, '2001:db8:0:1:1:1:1:1');
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/util'
+  ], function(UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      UTIL()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/util')());
+}
+
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/x509.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/x509.js
new file mode 100644
index 0000000..47a9e7f
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/x509.js
@@ -0,0 +1,734 @@
+(function() {
+
+function Tests(ASSERT, PKI, MD, UTIL) {
+  var _pem = {
+    privateKey: '-----BEGIN RSA PRIVATE KEY-----\r\n' +
+      'MIICXQIBAAKBgQDL0EugUiNGMWscLAVM0VoMdhDZEJOqdsUMpx9U0YZI7szokJqQ\r\n' +
+      'NIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMGTkP3VF29vXBo+dLq5e+8VyAy\r\n' +
+      'Q3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGtvnM+z0MYDdKo80efzwIDAQAB\r\n' +
+      'AoGAIzkGONi5G+JifmXlLJdplom486p3upf4Ce2/7mqfaG9MnkyPSairKD/JXvfh\r\n' +
+      'NNWkkN8DKKDKBcVVElPgORYT0qwrWc7ueLBMUCbRXb1ZyfEulimG0R3kjUh7NYau\r\n' +
+      'DaIkVgfykXGSQMZx8FoaT6L080zd+0emKDDYRrb+/kgJNJECQQDoUZoiC2K/DWNY\r\n' +
+      'h3/ppZ0ane2y4SBmJUHJVMPQ2CEgxsrJTxet668ckNCKaOP/3VFPoWC41f17DvKq\r\n' +
+      'noYINNntAkEA4JbZBZBVUrQFhHlrpXT4jzqtO2RlKZzEq8qmFZfEErxOT1WMyyCi\r\n' +
+      'lAQ5gUKardo1Kf0omC8Xq/uO9ZYdED55KwJBALs6cJ65UFaq4oLJiQPzLd7yokuE\r\n' +
+      'dcj8g71PLBTW6jPxIiMFNA89nz3FU9wIVp+xbMNhSoMMKqIPVPC+m0Rn260CQQDA\r\n' +
+      'I83fWK/mZWUjBM33a68KumRiH238v8XyQxj7+C8i6D8G2GXvkigFAehAkb7LZZd+\r\n' +
+      'KLuGFyPlWv3fVWHf99KpAkBQFKk3MRMl6IGJZUEFQe4l5whm8LkGU4acSqv9B3xt\r\n' +
+      'qROkCrsFrMPqjuuzEmyHoQZ64r2PLJg7FOuyhBnQUOt4\r\n' +
+      '-----END RSA PRIVATE KEY-----\r\n',
+    publicKey: '-----BEGIN PUBLIC KEY-----\r\n' +
+      'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDL0EugUiNGMWscLAVM0VoMdhDZ\r\n' +
+      'EJOqdsUMpx9U0YZI7szokJqQNIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMG\r\n' +
+      'TkP3VF29vXBo+dLq5e+8VyAyQ3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGt\r\n' +
+      'vnM+z0MYDdKo80efzwIDAQAB\r\n' +
+      '-----END PUBLIC KEY-----\r\n',
+    certificate: '-----BEGIN CERTIFICATE-----\r\n' +
+      'MIIDIjCCAougAwIBAgIJANE2aHSbwpaRMA0GCSqGSIb3DQEBBQUAMGoxCzAJBgNV\r\n' +
+      'BAYTAlVTMREwDwYDVQQIEwhWaXJnaW5pYTETMBEGA1UEBxMKQmxhY2tzYnVyZzEN\r\n' +
+      'MAsGA1UEChMEVGVzdDENMAsGA1UECxMEVGVzdDEVMBMGA1UEAxMMbXlzZXJ2ZXIu\r\n' +
+      'Y29tMB4XDTEwMDYxOTE3MzYyOFoXDTExMDYxOTE3MzYyOFowajELMAkGA1UEBhMC\r\n' +
+      'VVMxETAPBgNVBAgTCFZpcmdpbmlhMRMwEQYDVQQHEwpCbGFja3NidXJnMQ0wCwYD\r\n' +
+      'VQQKEwRUZXN0MQ0wCwYDVQQLEwRUZXN0MRUwEwYDVQQDEwxteXNlcnZlci5jb20w\r\n' +
+      'gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMvQS6BSI0YxaxwsBUzRWgx2ENkQ\r\n' +
+      'k6p2xQynH1TRhkjuzOiQmpA0jCiSJDoSic2dZIyUi/LjoCGeVFif57N5N5Tt4wZO\r\n' +
+      'Q/dUXb29cGj50url77xXIDJDcXMzXAji2ziFEAIXzDqarKBdDuL9IO7z+tepEa2+\r\n' +
+      'cz7PQxgN0qjzR5/PAgMBAAGjgc8wgcwwHQYDVR0OBBYEFPV1Y+DHXW6bA/r9sv1y\r\n' +
+      'NJ8jAwMAMIGcBgNVHSMEgZQwgZGAFPV1Y+DHXW6bA/r9sv1yNJ8jAwMAoW6kbDBq\r\n' +
+      'MQswCQYDVQQGEwJVUzERMA8GA1UECBMIVmlyZ2luaWExEzARBgNVBAcTCkJsYWNr\r\n' +
+      'c2J1cmcxDTALBgNVBAoTBFRlc3QxDTALBgNVBAsTBFRlc3QxFTATBgNVBAMTDG15\r\n' +
+      'c2VydmVyLmNvbYIJANE2aHSbwpaRMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEF\r\n' +
+      'BQADgYEARdH2KOlJWTC1CS2y/PAvg4uiM31PXMC1hqSdJlnLM1MY4hRfuf9VyTeX\r\n' +
+      'Y6FdybcyDLSxKn9id+g9229ci9/s9PI+QmD5vXd8yZyScLc2JkYB4GC6+9D1+/+x\r\n' +
+      's2hzMxuK6kzZlP+0l9LGcraMQPGRydjCARZZm4Uegln9rh85XFQ=\r\n' +
+      '-----END CERTIFICATE-----\r\n'
+  };
+
+  describe('x509', function() {
+    it('should convert certificate to/from PEM', function() {
+      var certificate = PKI.certificateFromPem(_pem.certificate);
+      ASSERT.equal(PKI.certificateToPem(certificate), _pem.certificate);
+    });
+
+    it('should verify self-signed certificate', function() {
+      var certificate = PKI.certificateFromPem(_pem.certificate);
+      ASSERT.ok(certificate.verify(certificate));
+    });
+
+    it('should generate and verify a self-signed certificate', function() {
+      var keys = {
+        privateKey: PKI.privateKeyFromPem(_pem.privateKey),
+        publicKey: PKI.publicKeyFromPem(_pem.publicKey)
+      };
+      var attrs = [{
+        name: 'commonName',
+        value: 'example.org'
+      }, {
+        name: 'countryName',
+        value: 'US'
+      }, {
+        shortName: 'ST',
+        value: 'Virginia'
+      }, {
+        name: 'localityName',
+        value: 'Blacksburg'
+      }, {
+        name: 'organizationName',
+        value: 'Test'
+      }, {
+        shortName: 'OU',
+        value: 'Test'
+      }];
+      var cert = createCertificate({
+        publicKey: keys.publicKey,
+        signingKey: keys.privateKey,
+        serialNumber: '01',
+        subject: attrs,
+        issuer: attrs,
+        isCA: true
+      });
+
+      var pem = PKI.certificateToPem(cert);
+      cert = PKI.certificateFromPem(pem);
+
+      // verify certificate chain
+      var caStore = PKI.createCaStore();
+      caStore.addCertificate(cert);
+      PKI.verifyCertificateChain(caStore, [cert], function(vfd, depth, chain) {
+        ASSERT.equal(vfd, true);
+        ASSERT.ok(cert.verifySubjectKeyIdentifier());
+        return true;
+      });
+    });
+
+    it('should generate and fail to verify a self-signed certificate that is not in the CA store', function() {
+      var keys = {
+        privateKey: PKI.privateKeyFromPem(_pem.privateKey),
+        publicKey: PKI.publicKeyFromPem(_pem.publicKey)
+      };
+      var attrs = [{
+        name: 'commonName',
+        value: 'example.org'
+      }, {
+        name: 'countryName',
+        value: 'US'
+      }, {
+        shortName: 'ST',
+        value: 'Virginia'
+      }, {
+        name: 'localityName',
+        value: 'Blacksburg'
+      }, {
+        name: 'organizationName',
+        value: 'Test'
+      }, {
+        shortName: 'OU',
+        value: 'Test'
+      }];
+      var cert = createCertificate({
+        publicKey: keys.publicKey,
+        signingKey: keys.privateKey,
+        serialNumber: '01',
+        subject: attrs,
+        issuer: attrs,
+        isCA: true
+      });
+
+      var pem = PKI.certificateToPem(cert);
+      cert = PKI.certificateFromPem(pem);
+
+      // verify certificate chain
+      var caStore = PKI.createCaStore();
+      PKI.verifyCertificateChain(caStore, [cert], function(vfd, depth, chain) {
+        ASSERT.equal(vfd, PKI.certificateError.unknown_ca);
+        return true;
+      });
+    });
+
+    it('should verify certificate chain ending with intermediate certificate from CA store', function() {
+      var keys = {
+        privateKey: PKI.privateKeyFromPem(_pem.privateKey),
+        publicKey: PKI.publicKeyFromPem(_pem.publicKey)
+      };
+      var entity = [{
+        name: 'commonName',
+        value: 'example.org'
+      }, {
+        name: 'countryName',
+        value: 'US'
+      }, {
+        shortName: 'ST',
+        value: 'Virginia'
+      }, {
+        name: 'localityName',
+        value: 'Blacksburg'
+      }, {
+        name: 'organizationName',
+        value: 'Test'
+      }, {
+        shortName: 'OU',
+        value: 'Test'
+      }];
+      var intermediate = [{
+        name: 'commonName',
+        value: 'intermediate'
+      }, {
+        name: 'countryName',
+        value: 'US'
+      }, {
+        shortName: 'ST',
+        value: 'Virginia'
+      }, {
+        name: 'localityName',
+        value: 'Blacksburg'
+      }, {
+        name: 'organizationName',
+        value: 'Test'
+      }, {
+        shortName: 'OU',
+        value: 'Test'
+      }];
+      var root = [{
+        name: 'commonName',
+        value: 'root'
+      }, {
+        name: 'countryName',
+        value: 'US'
+      }, {
+        shortName: 'ST',
+        value: 'Virginia'
+      }, {
+        name: 'localityName',
+        value: 'Blacksburg'
+      }, {
+        name: 'organizationName',
+        value: 'Test'
+      }, {
+        shortName: 'OU',
+        value: 'Test'
+      }];
+
+      var intermediateCert = createCertificate({
+        publicKey: keys.publicKey,
+        signingKey: keys.privateKey,
+        serialNumber: '01',
+        subject: intermediate,
+        issuer: root,
+        isCA: true
+      });
+
+      var entityCert = createCertificate({
+        publicKey: keys.publicKey,
+        signingKey: keys.privateKey,
+        serialNumber: '01',
+        subject: entity,
+        issuer: intermediate,
+        isCA: false
+      });
+
+      // verify certificate chain
+      var caStore = PKI.createCaStore();
+      caStore.addCertificate(intermediateCert);
+      var chain = [entityCert, intermediateCert];
+      PKI.verifyCertificateChain(caStore, chain, function(vfd, depth, chain) {
+        ASSERT.equal(vfd, true);
+        return true;
+      });
+    });
+
+    it('should fail to verify certificate chain ending with non-CA intermediate certificate from CA store', function() {
+      var keys = {
+        privateKey: PKI.privateKeyFromPem(_pem.privateKey),
+        publicKey: PKI.publicKeyFromPem(_pem.publicKey)
+      };
+      var entity = [{
+        name: 'commonName',
+        value: 'example.org'
+      }, {
+        name: 'countryName',
+        value: 'US'
+      }, {
+        shortName: 'ST',
+        value: 'Virginia'
+      }, {
+        name: 'localityName',
+        value: 'Blacksburg'
+      }, {
+        name: 'organizationName',
+        value: 'Test'
+      }, {
+        shortName: 'OU',
+        value: 'Test'
+      }];
+      var intermediate = [{
+        name: 'commonName',
+        value: 'intermediate'
+      }, {
+        name: 'countryName',
+        value: 'US'
+      }, {
+        shortName: 'ST',
+        value: 'Virginia'
+      }, {
+        name: 'localityName',
+        value: 'Blacksburg'
+      }, {
+        name: 'organizationName',
+        value: 'Test'
+      }, {
+        shortName: 'OU',
+        value: 'Test'
+      }];
+      var root = [{
+        name: 'commonName',
+        value: 'root'
+      }, {
+        name: 'countryName',
+        value: 'US'
+      }, {
+        shortName: 'ST',
+        value: 'Virginia'
+      }, {
+        name: 'localityName',
+        value: 'Blacksburg'
+      }, {
+        name: 'organizationName',
+        value: 'Test'
+      }, {
+        shortName: 'OU',
+        value: 'Test'
+      }];
+
+      var intermediateCert = createCertificate({
+        publicKey: keys.publicKey,
+        signingKey: keys.privateKey,
+        serialNumber: '01',
+        subject: intermediate,
+        issuer: root,
+        isCA: false
+      });
+
+      var entityCert = createCertificate({
+        publicKey: keys.publicKey,
+        signingKey: keys.privateKey,
+        serialNumber: '01',
+        subject: entity,
+        issuer: intermediate,
+        isCA: false
+      });
+
+      // verify certificate chain
+      var caStore = PKI.createCaStore();
+      caStore.addCertificate(intermediateCert);
+      var chain = [entityCert, intermediateCert];
+      PKI.verifyCertificateChain(caStore, chain, function(vfd, depth, chain) {
+        if(depth === 0) {
+          ASSERT.equal(vfd, true);
+        } else {
+          ASSERT.equal(vfd, PKI.certificateError.bad_certificate);
+        }
+        return true;
+      });
+    });
+
+    it('should verify certificate with sha1WithRSAEncryption signature', function() {
+      var certPem = '-----BEGIN CERTIFICATE-----\r\n' +
+        'MIIDZDCCAs2gAwIBAgIKQ8fjjgAAAABh3jANBgkqhkiG9w0BAQUFADBGMQswCQYD\r\n' +
+        'VQQGEwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzEiMCAGA1UEAxMZR29vZ2xlIElu\r\n' +
+        'dGVybmV0IEF1dGhvcml0eTAeFw0xMjA2MjcxMzU5MTZaFw0xMzA2MDcxOTQzMjda\r\n' +
+        'MGcxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1N\r\n' +
+        'b3VudGFpbiBWaWV3MRMwEQYDVQQKEwpHb29nbGUgSW5jMRYwFAYDVQQDEw13d3cu\r\n' +
+        'Z29vZ2xlLmRlMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCw2Hw3vNy5QMSd\r\n' +
+        '0/iMCS8lwZk9lnEk2NmrJt6vGJfRGlBprtHp5lpMFMoi+x8m8EwGVxXHGp7hLyN/\r\n' +
+        'gXuUjL7/DY9fxxx9l77D+sDZz7jfUfWmhS03Ra1FbT6myF8miVZFChJ8XgWzioJY\r\n' +
+        'gyNdRUC9149yrXdPWrSmSVaT0+tUCwIDAQABo4IBNjCCATIwHQYDVR0lBBYwFAYI\r\n' +
+        'KwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBTiQGhrO3785rMPIKZ/zQEl5RyS\r\n' +
+        '0TAfBgNVHSMEGDAWgBS/wDDr9UMRPme6npH7/Gra42sSJDBbBgNVHR8EVDBSMFCg\r\n' +
+        'TqBMhkpodHRwOi8vd3d3LmdzdGF0aWMuY29tL0dvb2dsZUludGVybmV0QXV0aG9y\r\n' +
+        'aXR5L0dvb2dsZUludGVybmV0QXV0aG9yaXR5LmNybDBmBggrBgEFBQcBAQRaMFgw\r\n' +
+        'VgYIKwYBBQUHMAKGSmh0dHA6Ly93d3cuZ3N0YXRpYy5jb20vR29vZ2xlSW50ZXJu\r\n' +
+        'ZXRBdXRob3JpdHkvR29vZ2xlSW50ZXJuZXRBdXRob3JpdHkuY3J0MAwGA1UdEwEB\r\n' +
+        '/wQCMAAwDQYJKoZIhvcNAQEFBQADgYEAVJ0qt/MBvHEPuWHeH51756qy+lBNygLA\r\n' +
+        'Xp5Gq+xHUTOzRty61BR05zv142hYAGWvpvnEOJ/DI7V3QlXK8a6dQ+du97obQJJx\r\n' +
+        '7ekqtfxVzmlSb23halYSoXmWgP8Tq0VUDsgsSLE7fS8JuO1soXUVKj1/6w189HL6\r\n' +
+        'LsngXwZSuL0=\r\n' +
+        '-----END CERTIFICATE-----\r\n';
+      var issuerPem = '-----BEGIN CERTIFICATE-----\r\n' +
+        'MIICsDCCAhmgAwIBAgIDC2dxMA0GCSqGSIb3DQEBBQUAME4xCzAJBgNVBAYTAlVT\r\n' +
+        'MRAwDgYDVQQKEwdFcXVpZmF4MS0wKwYDVQQLEyRFcXVpZmF4IFNlY3VyZSBDZXJ0\r\n' +
+        'aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDkwNjA4MjA0MzI3WhcNMTMwNjA3MTk0MzI3\r\n' +
+        'WjBGMQswCQYDVQQGEwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzEiMCAGA1UEAxMZ\r\n' +
+        'R29vZ2xlIEludGVybmV0IEF1dGhvcml0eTCBnzANBgkqhkiG9w0BAQEFAAOBjQAw\r\n' +
+        'gYkCgYEAye23pIucV+eEPkB9hPSP0XFjU5nneXQUr0SZMyCSjXvlKAy6rWxJfoNf\r\n' +
+        'NFlOCnowzdDXxFdF7dWq1nMmzq0yE7jXDx07393cCDaob1FEm8rWIFJztyaHNWrb\r\n' +
+        'qeXUWaUr/GcZOfqTGBhs3t0lig4zFEfC7wFQeeT9adGnwKziV28CAwEAAaOBozCB\r\n' +
+        'oDAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFL/AMOv1QxE+Z7qekfv8atrjaxIk\r\n' +
+        'MB8GA1UdIwQYMBaAFEjmaPkr0rKV10fYIyAQTzOYkJ/UMBIGA1UdEwEB/wQIMAYB\r\n' +
+        'Af8CAQAwOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2NybC5nZW90cnVzdC5jb20v\r\n' +
+        'Y3Jscy9zZWN1cmVjYS5jcmwwDQYJKoZIhvcNAQEFBQADgYEAuIojxkiWsRF8YHde\r\n' +
+        'BZqrocb6ghwYB8TrgbCoZutJqOkM0ymt9e8kTP3kS8p/XmOrmSfLnzYhLLkQYGfN\r\n' +
+        '0rTw8Ktx5YtaiScRhKqOv5nwnQkhClIZmloJ0pC3+gz4fniisIWvXEyZ2VxVKfml\r\n' +
+        'UUIuOss4jHg7y/j7lYe8vJD5UDI=\r\n' +
+        '-----END CERTIFICATE-----\r\n';
+      var cert = PKI.certificateFromPem(certPem, true);
+      var issuer = PKI.certificateFromPem(issuerPem);
+      ASSERT.ok(issuer.verify(cert));
+    });
+
+    it('should verify certificate with sha256WithRSAEncryption signature', function() {
+      var certPem = '-----BEGIN CERTIFICATE-----\r\n' +
+        'MIIDuzCCAqOgAwIBAgIEO5vZjDANBgkqhkiG9w0BAQsFADBGMQswCQYDVQQGEwJE\r\n' +
+        'RTEPMA0GA1UEChMGRWxzdGVyMQswCQYDVQQLEwJDQTEZMBcGA1UEAxMQRWxzdGVy\r\n' +
+        'U29mdFRlc3RDQTAeFw0xMDA5MTUwNTM4MjRaFw0xMzA5MTUwNTM4MjRaMCsxFDAS\r\n' +
+        'BgNVBAUTCzEwMDIzMTQ5OTRDMRMwEQYDVQQDEwoxMDAyMzE0OTk0MIGfMA0GCSqG\r\n' +
+        'SIb3DQEBAQUAA4GNADCBiQKBgQCLPqjbwjsugzw6+qwwm/pdzDwk7ASIsBYJ17GT\r\n' +
+        'qyT0zCnYmdDDGWsYc+xxFVVIi8xBt6Mlq8Rwj+02UJhY9qm6zRA9MqFZC3ih+HoW\r\n' +
+        'xq7H8N2d10N0rX6h5PSjkF5fU5ugncZmppsRGJ9DNXgwjpf/CsH2rqThUzK4xfrq\r\n' +
+        'jpDS/wIDAQABo4IBTjCCAUowDgYDVR0PAQH/BAQDAgUgMAwGA1UdEwEB/wQCMAAw\r\n' +
+        'HQYDVR0OBBYEFF1h7H37OQivS57GD8+nK6VsgMPTMIGXBgNVHR8EgY8wgYwwgYmg\r\n' +
+        'gYaggYOGgYBsZGFwOi8vMTkyLjE2OC42LjI0OjM4OS9sJTNkQ0ElMjBaZXJ0aWZp\r\n' +
+        'a2F0ZSxvdSUzZENBLGNuJTNkRWxzdGVyU29mdFRlc3RDQSxkYyUzZHdpZXNlbCxk\r\n' +
+        'YyUzZGVsc3RlcixkYyUzZGRlPz9iYXNlPyhvYmplY3RDbGFzcz0qKTBxBgNVHSME\r\n' +
+        'ajBogBRBILMYmlZu//pj3wjDe2UPkq7jk6FKpEgwRjELMAkGA1UEBhMCREUxDzAN\r\n' +
+        'BgNVBAoTBkVsc3RlcjEPMA0GA1UECxMGUm9vdENBMRUwEwYDVQQDEwxFbHN0ZXJS\r\n' +
+        'b290Q0GCBDuayikwDQYJKoZIhvcNAQELBQADggEBAK8Z1+/VNyU5w/EiyhFH5aRE\r\n' +
+        'Mzxo0DahqKEm4pW5haBgKubJwZGs+CrBZR70TPbZGgJd36eyMgeXb/06lBnxewii\r\n' +
+        'I/aY6wMTviQTpqFnz5m0Le8UhH+hY1bqNG/vf6J+1gbOSrZyhAUV+MDJbL/OkzX4\r\n' +
+        'voVAfUBqSODod0f5wCW2RhvBmB9E62baP6qizdxyPA4iV16H4C0etd/7coLX6NZC\r\n' +
+        'oz3Yu0IRTQCH+YrpfIbxGb0grNhtCTfFpa287fuzu8mIEvLNr8GibhBXmQg7iJ+y\r\n' +
+        'q0VIyZLY8k6jEPrUB5Iv5ouSR19Dda/2+xJPlT/bosuNcErEuk/yKAHWAzwm1wQ=\r\n' +
+        '-----END CERTIFICATE-----\r\n';
+      var issuerPem = '-----BEGIN CERTIFICATE-----\r\n' +
+        'MIIESjCCAzKgAwIBAgIEO5rKKTANBgkqhkiG9w0BAQsFADBGMQswCQYDVQQGEwJE\r\n' +
+        'RTEPMA0GA1UEChMGRWxzdGVyMQ8wDQYDVQQLEwZSb290Q0ExFTATBgNVBAMTDEVs\r\n' +
+        'c3RlclJvb3RDQTAeFw0wOTA3MjgwODE5MTFaFw0xNDA3MjgwODE5MTFaMEYxCzAJ\r\n' +
+        'BgNVBAYTAkRFMQ8wDQYDVQQKEwZFbHN0ZXIxCzAJBgNVBAsTAkNBMRkwFwYDVQQD\r\n' +
+        'ExBFbHN0ZXJTb2Z0VGVzdENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC\r\n' +
+        'AQEAv5uoKLnxXQe75iqwqgyt3H6MDAx/wvUVs26+2+yHpEUb/2gA3L8E+NChSb9E\r\n' +
+        'aNgxxoac3Yhvxzq2mPpih3vkY7Xw512Tm8l/OPbT8kbmBJmYZneFALXHytAIZiEf\r\n' +
+        'e0ZYNKAlClFIgNP5bE9UjTqVEEoSiUhpTubM6c5xEYVznnwPBoYQ0ari7RTDYnME\r\n' +
+        'HK4vMfoeBeWHYPiEygNHnGUG8d3merRC/lQASUtL6ikmLWKCKHfyit5ACzPNKAtw\r\n' +
+        'IzHAzD5ek0BpcUTci8hUsKz2ZvmoZcjPyj63veQuMYS5cTMgr3bfz9uz1xuoEDsb\r\n' +
+        'Sv9rQX9Iw3N7yMpxTDJTqGKhYwIDAQABo4IBPjCCATowDgYDVR0PAQH/BAQDAgEG\r\n' +
+        'MBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFEEgsxiaVm7/+mPfCMN7ZQ+S\r\n' +
+        'ruOTMIGXBgNVHR8EgY8wgYwwgYmggYaggYOGgYBsZGFwOi8vMTkyLjE2OC42LjI0\r\n' +
+        'OjM4OS9sJTNkQ0ElMjBaZXJ0aWZpa2F0ZSxvdSUzZFJvb3RDQSxjbiUzZEVsc3Rl\r\n' +
+        'clJvb3RDQSxkYyUzZHdpZXNlbCxkYyUzZGVsc3RlcixkYyUzZGRlPz9iYXNlPyhv\r\n' +
+        'YmplY3RDbGFzcz0qKTBbBgNVHSMEVDBSoUqkSDBGMQswCQYDVQQGEwJERTEPMA0G\r\n' +
+        'A1UEChMGRWxzdGVyMQ8wDQYDVQQLEwZSb290Q0ExFTATBgNVBAMTDEVsc3RlclJv\r\n' +
+        'b3RDQYIEO5rKADANBgkqhkiG9w0BAQsFAAOCAQEAFauDnfHSbgRmbFkpQUXM5wKi\r\n' +
+        'K5STAaVps201iAjacX5EsOs5L37VUMoT9G2DAE8Z6B1pIiR3zcd3UpiHnFlUTC0f\r\n' +
+        'ZdOCXzDkOfziKY/RzuUsLNFUhBizCIA0+XcKgm3dSA5ex8fChLJddSYheSLuPua7\r\n' +
+        'iNMuzaU2YnevbMwpdEsl55Qr/uzcc0YM/mCuM4vsNFyFml91SQyPPmdR3VvGOoGl\r\n' +
+        'qS1R0HSoPJUvr0N0kARwD7kO3ikcJ6FxBCsgVLZHGJC+q8PQNZmVMbfgjH4nAyP8\r\n' +
+        'u7Qe03v2WLW0UgKu2g0UcQXWXbovktpZoK0fUOwv3bqsZ0K1IjVvMKG8OysUvA==\r\n' +
+        '-----END CERTIFICATE-----\r\n';
+      var cert = PKI.certificateFromPem(certPem, true);
+      var issuer = PKI.certificateFromPem(issuerPem);
+      ASSERT.ok(issuer.verify(cert));
+    });
+
+    it('should import certificate with sha256 RSASSA-PSS signature', function() {
+      var certPem = '-----BEGIN CERTIFICATE-----\r\n' +
+        'MIIERzCCAvugAwIBAgIEO50CcjBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQC\r\n' +
+        'AQUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCASAwRjELMAkGA1UE\r\n' +
+        'BhMCREUxDzANBgNVBAoTBkVsc3RlcjELMAkGA1UECxMCQ0ExGTAXBgNVBAMTEEVs\r\n' +
+        'c3RlclNvZnRUZXN0Q0EwHhcNMTEwNzI4MTIxMzU3WhcNMTQwNzI4MTIxMzU3WjAr\r\n' +
+        'MRQwEgYDVQQFEwsxMDAyNzUzMzI1QzETMBEGA1UEAxMKMTAwMjc1MzMyNTCCASIw\r\n' +
+        'DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALHCogo7LVUkxWsMIc/0jgH2PCLt\r\n' +
+        'ukbATPehxWBG1XUPrz53lWgFJzlZaKLlLVVnXrfULaifuOKlZP6SM1JQlL1JuYgY\r\n' +
+        'AdgZyHjderNIk5NsSTmefwonSn/ukri5IRTH420oHtSjxk6+/DXlWnQy5OzTN6Bq\r\n' +
+        'jVJo8L+TTmf2jWuEam5cWa+YVP2k3tIqX5yMUDFjKO4znHdtIkHnBE0Kx03rWQRB\r\n' +
+        'TSYWDgDm2gttdOs9JVeuW0nnwQj27uo9gOR0iyaUjVrKLZ95p6zpXhM4uMSVRNeo\r\n' +
+        'LqkdqP2n+4pDXZVqLNgjkHQUS/xq9Q/kYgT2J7wkGfYxP9to7TG7vra1eOECAwEA\r\n' +
+        'AaOB7zCB7DAOBgNVHQ8BAf8EBAMCBSAwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU\r\n' +
+        'NDJ2BZIk6ukLqkdmttH12bu2leswOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2Ny\r\n' +
+        'bC5lbHN0ZXIuZGUvRWxzdGVyU29mdFRlc3RDQS5jcmwwcQYDVR0jBGowaIAU1R9A\r\n' +
+        'HmpdzaxK3v+ihQsEpAFgzOKhSqRIMEYxCzAJBgNVBAYTAkRFMQ8wDQYDVQQKEwZF\r\n' +
+        'bHN0ZXIxDzANBgNVBAsTBlJvb3RDQTEVMBMGA1UEAxMMRWxzdGVyUm9vdENBggQ7\r\n' +
+        'msqPMEEGCSqGSIb3DQEBCjA0oA8wDQYJYIZIAWUDBAIBBQChHDAaBgkqhkiG9w0B\r\n' +
+        'AQgwDQYJYIZIAWUDBAIBBQCiAwIBIAOCAQEAJBYNRZpe+z3yPPLV539Yci6OfjVg\r\n' +
+        'vs1e3rvSvcpFaqRJ8vZ8WNx3uuRQZ6B4Z3YEc00UJAOl3wU6KhamyryK2YvCrPg+\r\n' +
+        'TS5QDXNaO2z/rAnY1wWSlwBPlhqpMRrNv9cRXBcgK5YxprjytCVYN0UHdintgYxG\r\n' +
+        'fg7QYiFb00UXxAza1AFrpG+RqySFfO1scmu4kgpeb6A3USnQ0r6rZz6dt6NqgZZ6\r\n' +
+        'oUpDOCvnS9XSOWuvJirV8hIU0KAagguTbwfTqt9nt0wDlwZpemsJZ4Vvnvy8d9Jf\r\n' +
+        'zA68EWHbZLr2QP9hb3sHCWJgplMsTJnUwRfi2hf5KNtP8Xg5DSLMfTEbhw==\r\n' +
+        '-----END CERTIFICATE-----\r\n';
+      var cert = PKI.certificateFromPem(certPem, true);
+
+      ASSERT.equal(cert.signatureOid, PKI.oids['RSASSA-PSS']);
+      ASSERT.equal(cert.signatureParameters.hash.algorithmOid, PKI.oids['sha256']);
+      ASSERT.equal(cert.signatureParameters.mgf.algorithmOid, PKI.oids['mgf1']);
+      ASSERT.equal(cert.signatureParameters.mgf.hash.algorithmOid, PKI.oids['sha256']);
+      ASSERT.equal(cert.siginfo.algorithmOid, PKI.oids['RSASSA-PSS']);
+      ASSERT.equal(cert.siginfo.parameters.hash.algorithmOid, PKI.oids['sha256']);
+      ASSERT.equal(cert.siginfo.parameters.mgf.algorithmOid, PKI.oids['mgf1']);
+      ASSERT.equal(cert.siginfo.parameters.mgf.hash.algorithmOid, PKI.oids['sha256']);
+    });
+
+    it('should export certificate with sha256 RSASSA-PSS signature', function() {
+      var certPem = '-----BEGIN CERTIFICATE-----\r\n' +
+        'MIIERzCCAvugAwIBAgIEO50CcjBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQC\r\n' +
+        'AQUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCASAwRjELMAkGA1UE\r\n' +
+        'BhMCREUxDzANBgNVBAoTBkVsc3RlcjELMAkGA1UECxMCQ0ExGTAXBgNVBAMTEEVs\r\n' +
+        'c3RlclNvZnRUZXN0Q0EwHhcNMTEwNzI4MTIxMzU3WhcNMTQwNzI4MTIxMzU3WjAr\r\n' +
+        'MRQwEgYDVQQFEwsxMDAyNzUzMzI1QzETMBEGA1UEAxMKMTAwMjc1MzMyNTCCASIw\r\n' +
+        'DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALHCogo7LVUkxWsMIc/0jgH2PCLt\r\n' +
+        'ukbATPehxWBG1XUPrz53lWgFJzlZaKLlLVVnXrfULaifuOKlZP6SM1JQlL1JuYgY\r\n' +
+        'AdgZyHjderNIk5NsSTmefwonSn/ukri5IRTH420oHtSjxk6+/DXlWnQy5OzTN6Bq\r\n' +
+        'jVJo8L+TTmf2jWuEam5cWa+YVP2k3tIqX5yMUDFjKO4znHdtIkHnBE0Kx03rWQRB\r\n' +
+        'TSYWDgDm2gttdOs9JVeuW0nnwQj27uo9gOR0iyaUjVrKLZ95p6zpXhM4uMSVRNeo\r\n' +
+        'LqkdqP2n+4pDXZVqLNgjkHQUS/xq9Q/kYgT2J7wkGfYxP9to7TG7vra1eOECAwEA\r\n' +
+        'AaOB7zCB7DAOBgNVHQ8BAf8EBAMCBSAwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU\r\n' +
+        'NDJ2BZIk6ukLqkdmttH12bu2leswOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2Ny\r\n' +
+        'bC5lbHN0ZXIuZGUvRWxzdGVyU29mdFRlc3RDQS5jcmwwcQYDVR0jBGowaIAU1R9A\r\n' +
+        'HmpdzaxK3v+ihQsEpAFgzOKhSqRIMEYxCzAJBgNVBAYTAkRFMQ8wDQYDVQQKEwZF\r\n' +
+        'bHN0ZXIxDzANBgNVBAsTBlJvb3RDQTEVMBMGA1UEAxMMRWxzdGVyUm9vdENBggQ7\r\n' +
+        'msqPMEEGCSqGSIb3DQEBCjA0oA8wDQYJYIZIAWUDBAIBBQChHDAaBgkqhkiG9w0B\r\n' +
+        'AQgwDQYJYIZIAWUDBAIBBQCiAwIBIAOCAQEAJBYNRZpe+z3yPPLV539Yci6OfjVg\r\n' +
+        'vs1e3rvSvcpFaqRJ8vZ8WNx3uuRQZ6B4Z3YEc00UJAOl3wU6KhamyryK2YvCrPg+\r\n' +
+        'TS5QDXNaO2z/rAnY1wWSlwBPlhqpMRrNv9cRXBcgK5YxprjytCVYN0UHdintgYxG\r\n' +
+        'fg7QYiFb00UXxAza1AFrpG+RqySFfO1scmu4kgpeb6A3USnQ0r6rZz6dt6NqgZZ6\r\n' +
+        'oUpDOCvnS9XSOWuvJirV8hIU0KAagguTbwfTqt9nt0wDlwZpemsJZ4Vvnvy8d9Jf\r\n' +
+        'zA68EWHbZLr2QP9hb3sHCWJgplMsTJnUwRfi2hf5KNtP8Xg5DSLMfTEbhw==\r\n' +
+        '-----END CERTIFICATE-----\r\n';
+      var cert = PKI.certificateFromPem(certPem, true);
+      ASSERT.equal(PKI.certificateToPem(cert), certPem);
+    });
+
+    it('should verify certificate with sha256 RSASSA-PSS signature', function() {
+      var certPem = '-----BEGIN CERTIFICATE-----\r\n' +
+        'MIIERzCCAvugAwIBAgIEO50CcjBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQC\r\n' +
+        'AQUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCASAwRjELMAkGA1UE\r\n' +
+        'BhMCREUxDzANBgNVBAoTBkVsc3RlcjELMAkGA1UECxMCQ0ExGTAXBgNVBAMTEEVs\r\n' +
+        'c3RlclNvZnRUZXN0Q0EwHhcNMTEwNzI4MTIxMzU3WhcNMTQwNzI4MTIxMzU3WjAr\r\n' +
+        'MRQwEgYDVQQFEwsxMDAyNzUzMzI1QzETMBEGA1UEAxMKMTAwMjc1MzMyNTCCASIw\r\n' +
+        'DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALHCogo7LVUkxWsMIc/0jgH2PCLt\r\n' +
+        'ukbATPehxWBG1XUPrz53lWgFJzlZaKLlLVVnXrfULaifuOKlZP6SM1JQlL1JuYgY\r\n' +
+        'AdgZyHjderNIk5NsSTmefwonSn/ukri5IRTH420oHtSjxk6+/DXlWnQy5OzTN6Bq\r\n' +
+        'jVJo8L+TTmf2jWuEam5cWa+YVP2k3tIqX5yMUDFjKO4znHdtIkHnBE0Kx03rWQRB\r\n' +
+        'TSYWDgDm2gttdOs9JVeuW0nnwQj27uo9gOR0iyaUjVrKLZ95p6zpXhM4uMSVRNeo\r\n' +
+        'LqkdqP2n+4pDXZVqLNgjkHQUS/xq9Q/kYgT2J7wkGfYxP9to7TG7vra1eOECAwEA\r\n' +
+        'AaOB7zCB7DAOBgNVHQ8BAf8EBAMCBSAwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU\r\n' +
+        'NDJ2BZIk6ukLqkdmttH12bu2leswOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2Ny\r\n' +
+        'bC5lbHN0ZXIuZGUvRWxzdGVyU29mdFRlc3RDQS5jcmwwcQYDVR0jBGowaIAU1R9A\r\n' +
+        'HmpdzaxK3v+ihQsEpAFgzOKhSqRIMEYxCzAJBgNVBAYTAkRFMQ8wDQYDVQQKEwZF\r\n' +
+        'bHN0ZXIxDzANBgNVBAsTBlJvb3RDQTEVMBMGA1UEAxMMRWxzdGVyUm9vdENBggQ7\r\n' +
+        'msqPMEEGCSqGSIb3DQEBCjA0oA8wDQYJYIZIAWUDBAIBBQChHDAaBgkqhkiG9w0B\r\n' +
+        'AQgwDQYJYIZIAWUDBAIBBQCiAwIBIAOCAQEAJBYNRZpe+z3yPPLV539Yci6OfjVg\r\n' +
+        'vs1e3rvSvcpFaqRJ8vZ8WNx3uuRQZ6B4Z3YEc00UJAOl3wU6KhamyryK2YvCrPg+\r\n' +
+        'TS5QDXNaO2z/rAnY1wWSlwBPlhqpMRrNv9cRXBcgK5YxprjytCVYN0UHdintgYxG\r\n' +
+        'fg7QYiFb00UXxAza1AFrpG+RqySFfO1scmu4kgpeb6A3USnQ0r6rZz6dt6NqgZZ6\r\n' +
+        'oUpDOCvnS9XSOWuvJirV8hIU0KAagguTbwfTqt9nt0wDlwZpemsJZ4Vvnvy8d9Jf\r\n' +
+        'zA68EWHbZLr2QP9hb3sHCWJgplMsTJnUwRfi2hf5KNtP8Xg5DSLMfTEbhw==\r\n' +
+        '-----END CERTIFICATE-----\r\n';
+      var issuerPem = '-----BEGIN CERTIFICATE-----\r\n' +
+        'MIIEZDCCAxigAwIBAgIEO5rKjzBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQC\r\n' +
+        'AQUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCASAwRjELMAkGA1UE\r\n' +
+        'BhMCREUxDzANBgNVBAoTBkVsc3RlcjEPMA0GA1UECxMGUm9vdENBMRUwEwYDVQQD\r\n' +
+        'EwxFbHN0ZXJSb290Q0EwHhcNMTEwNzI4MTExNzI4WhcNMTYwNzI4MTExNzI4WjBG\r\n' +
+        'MQswCQYDVQQGEwJERTEPMA0GA1UEChMGRWxzdGVyMQswCQYDVQQLEwJDQTEZMBcG\r\n' +
+        'A1UEAxMQRWxzdGVyU29mdFRlc3RDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\r\n' +
+        'AQoCggEBAMFpz3sXnXq4ZUBdYdpG5DJVfITLXYwXPfEJzr1pLRfJ2eMPn7k3B/2g\r\n' +
+        'bvuH30zKmDRjlfV51sFw4l1l+cQugzy5FEOrfE6g7IvhpBUf9SQujVWtE3BoSRR5\r\n' +
+        'pSGbIWC7sm2SG0drpoCRpL0xmWZlAUS5mz7hBecJC/kKkKeOxUg5h492XQgWd0ow\r\n' +
+        '6GlyQBxJCmxgQBMnLS0stecs234hR5gvTHic50Ey+gRMPsTyco2Fm0FqvXtBuOsj\r\n' +
+        'zAW7Nk2hnM6awFHVMDBLm+ClE1ww0dLW0ujkdoGsTEbvmM/w8MBI6WAiWaanjK/x\r\n' +
+        'lStmQeUVXKd+AfuzT/FIPrwANcC1qGUCAwEAAaOB8TCB7jAOBgNVHQ8BAf8EBAMC\r\n' +
+        'AQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU1R9AHmpdzaxK3v+ihQsE\r\n' +
+        'pAFgzOIwNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5lbHN0ZXIuZGUvRWxz\r\n' +
+        'dGVyUm9vdENBLmNybDBxBgNVHSMEajBogBS3zfTokckL2H/fTojW02K+metEcaFK\r\n' +
+        'pEgwRjELMAkGA1UEBhMCREUxDzANBgNVBAoTBkVsc3RlcjEPMA0GA1UECxMGUm9v\r\n' +
+        'dENBMRUwEwYDVQQDEwxFbHN0ZXJSb290Q0GCBDuaylowQQYJKoZIhvcNAQEKMDSg\r\n' +
+        'DzANBglghkgBZQMEAgEFAKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgEFAKID\r\n' +
+        'AgEgA4IBAQBjT107fBmSfQNUYCpXIlaS/pogPoCahuC1g8QDtq6IEVXoEgXCzfVN\r\n' +
+        'JYHyYOQOraP4O2LEcXpo/oc0MMpVF06QLKG/KieUE0mrZnsCJ3GLSJkM8tI8bRHj\r\n' +
+        '8tAhlViMacUqnKKufCs/rIN25JB57+sCyFqjtRbSt88e+xqFJ5I79Btp/bNtoj2G\r\n' +
+        'OJGl997EPua9/yJuvdA9W67WO/KwEh+FqylB1pAapccOHqttwu4QJIc/XJfG5rrf\r\n' +
+        '8QZz8HIuOcroA4zIrprQ1nJRCuMII04ueDtBVz1eIEmqNEUkr09JdK8M0LKH0lMK\r\n' +
+        'Ysgjai/P2mPVVwhVw6dHzUVRLXh3xIQr\r\n' +
+        '-----END CERTIFICATE-----\r\n';
+      var cert = PKI.certificateFromPem(certPem, true);
+      var issuer = PKI.certificateFromPem(issuerPem);
+      ASSERT.ok(issuer.verify(cert));
+    });
+  });
+
+  describe('public key fingerprints', function() {
+    it('should get a SHA-1 RSAPublicKey fingerprint', function() {
+      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
+      var fp = PKI.getPublicKeyFingerprint(publicKey, {type: 'RSAPublicKey'});
+      ASSERT.equal(fp.toHex(), 'f57563e0c75d6e9b03fafdb2fd72349f23030300');
+    });
+
+    it('should get a SHA-1 SubjectPublicKeyInfo fingerprint', function() {
+      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
+      var fp = PKI.getPublicKeyFingerprint(
+        publicKey, {type: 'SubjectPublicKeyInfo'});
+      ASSERT.equal(fp.toHex(), '984724bc548bbc2c8acbac044bd8d518abd26dd8');
+    });
+
+    it('should get a hex SHA-1 RSAPublicKey fingerprint', function() {
+      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
+      var fp = PKI.getPublicKeyFingerprint(
+        publicKey, {type: 'RSAPublicKey', encoding: 'hex'});
+      ASSERT.equal(fp, 'f57563e0c75d6e9b03fafdb2fd72349f23030300');
+    });
+
+    it('should get a hex, colon-delimited SHA-1 RSAPublicKey fingerprint', function() {
+      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
+      var fp = PKI.getPublicKeyFingerprint(
+        publicKey, {type: 'RSAPublicKey', encoding: 'hex', delimiter: ':'});
+      ASSERT.equal(
+        fp, 'f5:75:63:e0:c7:5d:6e:9b:03:fa:fd:b2:fd:72:34:9f:23:03:03:00');
+    });
+
+    it('should get a hex, colon-delimited SHA-1 SubjectPublicKeyInfo fingerprint', function() {
+      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
+      var fp = PKI.getPublicKeyFingerprint(
+        publicKey, {
+          type: 'SubjectPublicKeyInfo',
+          encoding: 'hex',
+          delimiter: ':'
+        });
+      ASSERT.equal(
+        fp, '98:47:24:bc:54:8b:bc:2c:8a:cb:ac:04:4b:d8:d5:18:ab:d2:6d:d8');
+    });
+
+    it('should get an MD5 RSAPublicKey fingerprint', function() {
+      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
+      var fp = PKI.getPublicKeyFingerprint(
+        publicKey, {md: MD.md5.create(), type: 'RSAPublicKey'});
+      ASSERT.equal(fp.toHex(), 'c7da180cc48d31a071d31a78bc43d9d7');
+    });
+
+    it('should get an MD5 SubjectPublicKeyInfo fingerprint', function() {
+      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
+      var fp = PKI.getPublicKeyFingerprint(
+        publicKey, {md: MD.md5.create(), type: 'SubjectPublicKeyInfo'});
+      ASSERT.equal(fp.toHex(), 'e5c5ba577fe24fb8a678d8d58f539cd7');
+    });
+
+    it('should get a hex MD5 RSAPublicKey fingerprint', function() {
+      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
+      var fp = PKI.getPublicKeyFingerprint(
+        publicKey,
+        {md: MD.md5.create(), type: 'RSAPublicKey', encoding: 'hex'});
+      ASSERT.equal(fp, 'c7da180cc48d31a071d31a78bc43d9d7');
+    });
+
+    it('should get a hex, colon-delimited MD5 RSAPublicKey fingerprint', function() {
+      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
+      var fp = PKI.getPublicKeyFingerprint(
+        publicKey, {
+          md: MD.md5.create(),
+          type: 'RSAPublicKey',
+          encoding: 'hex',
+          delimiter: ':'
+        });
+      ASSERT.equal(fp, 'c7:da:18:0c:c4:8d:31:a0:71:d3:1a:78:bc:43:d9:d7');
+    });
+
+    it('should get a hex, colon-delimited MD5 SubjectPublicKeyInfo fingerprint', function() {
+      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
+      var fp = PKI.getPublicKeyFingerprint(
+        publicKey, {
+          md: MD.md5.create(),
+          type: 'SubjectPublicKeyInfo',
+          encoding: 'hex',
+          delimiter: ':'
+        });
+      ASSERT.equal(fp, 'e5:c5:ba:57:7f:e2:4f:b8:a6:78:d8:d5:8f:53:9c:d7');
+    });
+  });
+
+  function createCertificate(options) {
+    var publicKey = options.publicKey;
+    var signingKey = options.signingKey;
+    var subject = options.subject;
+    var issuer = options.issuer;
+    var isCA = options.isCA;
+    var serialNumber = options.serialNumber || '01';
+
+    var cert = PKI.createCertificate();
+    cert.publicKey = publicKey;
+    cert.serialNumber = serialNumber;
+    cert.validity.notBefore = new Date();
+    cert.validity.notAfter = new Date();
+    cert.validity.notAfter.setFullYear(
+      cert.validity.notBefore.getFullYear() + 1);
+    cert.setSubject(subject);
+    cert.setIssuer(issuer);
+    var extensions = [];
+    if(isCA) {
+      extensions.push({
+        name: 'basicConstraints',
+        cA: true
+      });
+    }
+    extensions.push({
+      name: 'keyUsage',
+      keyCertSign: true,
+      digitalSignature: true,
+      nonRepudiation: true,
+      keyEncipherment: true,
+      dataEncipherment: true
+    }, {
+      name: 'extKeyUsage',
+      serverAuth: true,
+      clientAuth: true,
+      codeSigning: true,
+      emailProtection: true,
+      timeStamping: true
+    }, {
+      name: 'nsCertType',
+      client: true,
+      server: true,
+      email: true,
+      objsign: true,
+      sslCA: true,
+      emailCA: true,
+      objCA: true
+    }, {
+      name: 'subjectAltName',
+      altNames: [{
+        type: 6, // URI
+        value: 'http://example.org/webid#me'
+      }]
+    }, {
+      name: 'subjectKeyIdentifier'
+    });
+    // FIXME: add authorityKeyIdentifier extension
+    cert.setExtensions(extensions);
+
+    cert.sign(signingKey);
+
+    return cert;
+  }
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/pki',
+    'forge/md',
+    'forge/util'
+  ], function(PKI, MD, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      PKI(),
+      MD(),
+      UTIL()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/pki')(),
+    require('../../js/md')(),
+    require('../../js/util')());
+}
+
+})();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/ui/index.html b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/ui/index.html
new file mode 100644
index 0000000..25e81b4
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/ui/index.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <link rel="stylesheet" href="mocha/mocha.css">
+    <script src="mocha/mocha.js" type="text/javascript" charset="utf-8"></script>
+    <script src="chai/chai.js" type="text/javascript" charset="utf-8"></script>
+    <script src="require.js" data-main="test" type="text/javascript" charset="utf-8"></script>
+</head>
+<body>
+    <div id="mocha"></div>
+</body>
+</html>
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/ui/require.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/ui/require.js
new file mode 100644
index 0000000..7df5d90
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/ui/require.js
@@ -0,0 +1,35 @@
+/*
+ RequireJS 2.1.5 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.
+ Available via the MIT or new BSD license.
+ see: http://github.com/jrburke/requirejs for details
+*/
+var requirejs,require,define;
+(function(aa){function I(b){return"[object Function]"===L.call(b)}function J(b){return"[object Array]"===L.call(b)}function y(b,c){if(b){var d;for(d=0;d<b.length&&(!b[d]||!c(b[d],d,b));d+=1);}}function M(b,c){if(b){var d;for(d=b.length-1;-1<d&&(!b[d]||!c(b[d],d,b));d-=1);}}function s(b,c){return ga.call(b,c)}function m(b,c){return s(b,c)&&b[c]}function G(b,c){for(var d in b)if(s(b,d)&&c(b[d],d))break}function R(b,c,d,m){c&&G(c,function(c,j){if(d||!s(b,j))m&&"string"!==typeof c?(b[j]||(b[j]={}),R(b[j],
+c,d,m)):b[j]=c});return b}function u(b,c){return function(){return c.apply(b,arguments)}}function ba(b){if(!b)return b;var c=aa;y(b.split("."),function(b){c=c[b]});return c}function B(b,c,d,m){c=Error(c+"\nhttp://requirejs.org/docs/errors.html#"+b);c.requireType=b;c.requireModules=m;d&&(c.originalError=d);return c}function ha(b){function c(a,f,b){var e,n,c,g,d,S,i,h=f&&f.split("/");e=h;var j=k.map,l=j&&j["*"];if(a&&"."===a.charAt(0))if(f){e=m(k.pkgs,f)?h=[f]:h.slice(0,h.length-1);f=a=e.concat(a.split("/"));
+for(e=0;f[e];e+=1)if(n=f[e],"."===n)f.splice(e,1),e-=1;else if(".."===n)if(1===e&&(".."===f[2]||".."===f[0]))break;else 0<e&&(f.splice(e-1,2),e-=2);e=m(k.pkgs,f=a[0]);a=a.join("/");e&&a===f+"/"+e.main&&(a=f)}else 0===a.indexOf("./")&&(a=a.substring(2));if(b&&j&&(h||l)){f=a.split("/");for(e=f.length;0<e;e-=1){c=f.slice(0,e).join("/");if(h)for(n=h.length;0<n;n-=1)if(b=m(j,h.slice(0,n).join("/")))if(b=m(b,c)){g=b;d=e;break}if(g)break;!S&&(l&&m(l,c))&&(S=m(l,c),i=e)}!g&&S&&(g=S,d=i);g&&(f.splice(0,d,
+g),a=f.join("/"))}return a}function d(a){A&&y(document.getElementsByTagName("script"),function(f){if(f.getAttribute("data-requiremodule")===a&&f.getAttribute("data-requirecontext")===i.contextName)return f.parentNode.removeChild(f),!0})}function z(a){var f=m(k.paths,a);if(f&&J(f)&&1<f.length)return d(a),f.shift(),i.require.undef(a),i.require([a]),!0}function h(a){var f,b=a?a.indexOf("!"):-1;-1<b&&(f=a.substring(0,b),a=a.substring(b+1,a.length));return[f,a]}function j(a,f,b,e){var n,C,g=null,d=f?f.name:
+null,j=a,l=!0,k="";a||(l=!1,a="_@r"+(M+=1));a=h(a);g=a[0];a=a[1];g&&(g=c(g,d,e),C=m(q,g));a&&(g?k=C&&C.normalize?C.normalize(a,function(a){return c(a,d,e)}):c(a,d,e):(k=c(a,d,e),a=h(k),g=a[0],k=a[1],b=!0,n=i.nameToUrl(k)));b=g&&!C&&!b?"_unnormalized"+(Q+=1):"";return{prefix:g,name:k,parentMap:f,unnormalized:!!b,url:n,originalName:j,isDefine:l,id:(g?g+"!"+k:k)+b}}function r(a){var f=a.id,b=m(p,f);b||(b=p[f]=new i.Module(a));return b}function t(a,f,b){var e=a.id,n=m(p,e);if(s(q,e)&&(!n||n.defineEmitComplete))"defined"===
+f&&b(q[e]);else r(a).on(f,b)}function v(a,f){var b=a.requireModules,e=!1;if(f)f(a);else if(y(b,function(f){if(f=m(p,f))f.error=a,f.events.error&&(e=!0,f.emit("error",a))}),!e)l.onError(a)}function w(){T.length&&(ia.apply(H,[H.length-1,0].concat(T)),T=[])}function x(a){delete p[a];delete V[a]}function F(a,f,b){var e=a.map.id;a.error?a.emit("error",a.error):(f[e]=!0,y(a.depMaps,function(e,c){var g=e.id,d=m(p,g);d&&(!a.depMatched[c]&&!b[g])&&(m(f,g)?(a.defineDep(c,q[g]),a.check()):F(d,f,b))}),b[e]=!0)}
+function D(){var a,f,b,e,n=(b=1E3*k.waitSeconds)&&i.startTime+b<(new Date).getTime(),c=[],g=[],h=!1,j=!0;if(!W){W=!0;G(V,function(b){a=b.map;f=a.id;if(b.enabled&&(a.isDefine||g.push(b),!b.error))if(!b.inited&&n)z(f)?h=e=!0:(c.push(f),d(f));else if(!b.inited&&(b.fetched&&a.isDefine)&&(h=!0,!a.prefix))return j=!1});if(n&&c.length)return b=B("timeout","Load timeout for modules: "+c,null,c),b.contextName=i.contextName,v(b);j&&y(g,function(a){F(a,{},{})});if((!n||e)&&h)if((A||da)&&!X)X=setTimeout(function(){X=
+0;D()},50);W=!1}}function E(a){s(q,a[0])||r(j(a[0],null,!0)).init(a[1],a[2])}function K(a){var a=a.currentTarget||a.srcElement,b=i.onScriptLoad;a.detachEvent&&!Y?a.detachEvent("onreadystatechange",b):a.removeEventListener("load",b,!1);b=i.onScriptError;(!a.detachEvent||Y)&&a.removeEventListener("error",b,!1);return{node:a,id:a&&a.getAttribute("data-requiremodule")}}function L(){var a;for(w();H.length;){a=H.shift();if(null===a[0])return v(B("mismatch","Mismatched anonymous define() module: "+a[a.length-
+1]));E(a)}}var W,Z,i,N,X,k={waitSeconds:7,baseUrl:"./",paths:{},pkgs:{},shim:{},config:{}},p={},V={},$={},H=[],q={},U={},M=1,Q=1;N={require:function(a){return a.require?a.require:a.require=i.makeRequire(a.map)},exports:function(a){a.usingExports=!0;if(a.map.isDefine)return a.exports?a.exports:a.exports=q[a.map.id]={}},module:function(a){return a.module?a.module:a.module={id:a.map.id,uri:a.map.url,config:function(){return k.config&&m(k.config,a.map.id)||{}},exports:q[a.map.id]}}};Z=function(a){this.events=
+m($,a.id)||{};this.map=a;this.shim=m(k.shim,a.id);this.depExports=[];this.depMaps=[];this.depMatched=[];this.pluginMaps={};this.depCount=0};Z.prototype={init:function(a,b,c,e){e=e||{};if(!this.inited){this.factory=b;if(c)this.on("error",c);else this.events.error&&(c=u(this,function(a){this.emit("error",a)}));this.depMaps=a&&a.slice(0);this.errback=c;this.inited=!0;this.ignore=e.ignore;e.enabled||this.enabled?this.enable():this.check()}},defineDep:function(a,b){this.depMatched[a]||(this.depMatched[a]=
+!0,this.depCount-=1,this.depExports[a]=b)},fetch:function(){if(!this.fetched){this.fetched=!0;i.startTime=(new Date).getTime();var a=this.map;if(this.shim)i.makeRequire(this.map,{enableBuildCallback:!0})(this.shim.deps||[],u(this,function(){return a.prefix?this.callPlugin():this.load()}));else return a.prefix?this.callPlugin():this.load()}},load:function(){var a=this.map.url;U[a]||(U[a]=!0,i.load(this.map.id,a))},check:function(){if(this.enabled&&!this.enabling){var a,b,c=this.map.id;b=this.depExports;
+var e=this.exports,n=this.factory;if(this.inited)if(this.error)this.emit("error",this.error);else{if(!this.defining){this.defining=!0;if(1>this.depCount&&!this.defined){if(I(n)){if(this.events.error)try{e=i.execCb(c,n,b,e)}catch(d){a=d}else e=i.execCb(c,n,b,e);this.map.isDefine&&((b=this.module)&&void 0!==b.exports&&b.exports!==this.exports?e=b.exports:void 0===e&&this.usingExports&&(e=this.exports));if(a)return a.requireMap=this.map,a.requireModules=[this.map.id],a.requireType="define",v(this.error=
+a)}else e=n;this.exports=e;if(this.map.isDefine&&!this.ignore&&(q[c]=e,l.onResourceLoad))l.onResourceLoad(i,this.map,this.depMaps);x(c);this.defined=!0}this.defining=!1;this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=!0)}}else this.fetch()}},callPlugin:function(){var a=this.map,b=a.id,d=j(a.prefix);this.depMaps.push(d);t(d,"defined",u(this,function(e){var n,d;d=this.map.name;var g=this.map.parentMap?this.map.parentMap.name:null,h=
+i.makeRequire(a.parentMap,{enableBuildCallback:!0});if(this.map.unnormalized){if(e.normalize&&(d=e.normalize(d,function(a){return c(a,g,!0)})||""),e=j(a.prefix+"!"+d,this.map.parentMap),t(e,"defined",u(this,function(a){this.init([],function(){return a},null,{enabled:!0,ignore:!0})})),d=m(p,e.id)){this.depMaps.push(e);if(this.events.error)d.on("error",u(this,function(a){this.emit("error",a)}));d.enable()}}else n=u(this,function(a){this.init([],function(){return a},null,{enabled:!0})}),n.error=u(this,
+function(a){this.inited=!0;this.error=a;a.requireModules=[b];G(p,function(a){0===a.map.id.indexOf(b+"_unnormalized")&&x(a.map.id)});v(a)}),n.fromText=u(this,function(e,c){var d=a.name,g=j(d),C=O;c&&(e=c);C&&(O=!1);r(g);s(k.config,b)&&(k.config[d]=k.config[b]);try{l.exec(e)}catch(ca){return v(B("fromtexteval","fromText eval for "+b+" failed: "+ca,ca,[b]))}C&&(O=!0);this.depMaps.push(g);i.completeLoad(d);h([d],n)}),e.load(a.name,h,n,k)}));i.enable(d,this);this.pluginMaps[d.id]=d},enable:function(){V[this.map.id]=
+this;this.enabling=this.enabled=!0;y(this.depMaps,u(this,function(a,b){var c,e;if("string"===typeof a){a=j(a,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap);this.depMaps[b]=a;if(c=m(N,a.id)){this.depExports[b]=c(this);return}this.depCount+=1;t(a,"defined",u(this,function(a){this.defineDep(b,a);this.check()}));this.errback&&t(a,"error",this.errback)}c=a.id;e=p[c];!s(N,c)&&(e&&!e.enabled)&&i.enable(a,this)}));G(this.pluginMaps,u(this,function(a){var b=m(p,a.id);b&&!b.enabled&&i.enable(a,
+this)}));this.enabling=!1;this.check()},on:function(a,b){var c=this.events[a];c||(c=this.events[a]=[]);c.push(b)},emit:function(a,b){y(this.events[a],function(a){a(b)});"error"===a&&delete this.events[a]}};i={config:k,contextName:b,registry:p,defined:q,urlFetched:U,defQueue:H,Module:Z,makeModuleMap:j,nextTick:l.nextTick,onError:v,configure:function(a){a.baseUrl&&"/"!==a.baseUrl.charAt(a.baseUrl.length-1)&&(a.baseUrl+="/");var b=k.pkgs,c=k.shim,e={paths:!0,config:!0,map:!0};G(a,function(a,b){e[b]?
+"map"===b?(k.map||(k.map={}),R(k[b],a,!0,!0)):R(k[b],a,!0):k[b]=a});a.shim&&(G(a.shim,function(a,b){J(a)&&(a={deps:a});if((a.exports||a.init)&&!a.exportsFn)a.exportsFn=i.makeShimExports(a);c[b]=a}),k.shim=c);a.packages&&(y(a.packages,function(a){a="string"===typeof a?{name:a}:a;b[a.name]={name:a.name,location:a.location||a.name,main:(a.main||"main").replace(ja,"").replace(ea,"")}}),k.pkgs=b);G(p,function(a,b){!a.inited&&!a.map.unnormalized&&(a.map=j(b))});if(a.deps||a.callback)i.require(a.deps||[],
+a.callback)},makeShimExports:function(a){return function(){var b;a.init&&(b=a.init.apply(aa,arguments));return b||a.exports&&ba(a.exports)}},makeRequire:function(a,f){function d(e,c,h){var g,k;f.enableBuildCallback&&(c&&I(c))&&(c.__requireJsBuild=!0);if("string"===typeof e){if(I(c))return v(B("requireargs","Invalid require call"),h);if(a&&s(N,e))return N[e](p[a.id]);if(l.get)return l.get(i,e,a,d);g=j(e,a,!1,!0);g=g.id;return!s(q,g)?v(B("notloaded",'Module name "'+g+'" has not been loaded yet for context: '+
+b+(a?"":". Use require([])"))):q[g]}L();i.nextTick(function(){L();k=r(j(null,a));k.skipMap=f.skipMap;k.init(e,c,h,{enabled:!0});D()});return d}f=f||{};R(d,{isBrowser:A,toUrl:function(b){var d,f=b.lastIndexOf("."),g=b.split("/")[0];if(-1!==f&&(!("."===g||".."===g)||1<f))d=b.substring(f,b.length),b=b.substring(0,f);return i.nameToUrl(c(b,a&&a.id,!0),d,!0)},defined:function(b){return s(q,j(b,a,!1,!0).id)},specified:function(b){b=j(b,a,!1,!0).id;return s(q,b)||s(p,b)}});a||(d.undef=function(b){w();var c=
+j(b,a,!0),d=m(p,b);delete q[b];delete U[c.url];delete $[b];d&&(d.events.defined&&($[b]=d.events),x(b))});return d},enable:function(a){m(p,a.id)&&r(a).enable()},completeLoad:function(a){var b,c,e=m(k.shim,a)||{},d=e.exports;for(w();H.length;){c=H.shift();if(null===c[0]){c[0]=a;if(b)break;b=!0}else c[0]===a&&(b=!0);E(c)}c=m(p,a);if(!b&&!s(q,a)&&c&&!c.inited){if(k.enforceDefine&&(!d||!ba(d)))return z(a)?void 0:v(B("nodefine","No define call for "+a,null,[a]));E([a,e.deps||[],e.exportsFn])}D()},nameToUrl:function(a,
+b,c){var e,d,h,g,j,i;if(l.jsExtRegExp.test(a))g=a+(b||"");else{e=k.paths;d=k.pkgs;g=a.split("/");for(j=g.length;0<j;j-=1)if(i=g.slice(0,j).join("/"),h=m(d,i),i=m(e,i)){J(i)&&(i=i[0]);g.splice(0,j,i);break}else if(h){a=a===h.name?h.location+"/"+h.main:h.location;g.splice(0,j,a);break}g=g.join("/");g+=b||(/\?/.test(g)||c?"":".js");g=("/"===g.charAt(0)||g.match(/^[\w\+\.\-]+:/)?"":k.baseUrl)+g}return k.urlArgs?g+((-1===g.indexOf("?")?"?":"&")+k.urlArgs):g},load:function(a,b){l.load(i,a,b)},execCb:function(a,
+b,c,d){return b.apply(d,c)},onScriptLoad:function(a){if("load"===a.type||ka.test((a.currentTarget||a.srcElement).readyState))P=null,a=K(a),i.completeLoad(a.id)},onScriptError:function(a){var b=K(a);if(!z(b.id))return v(B("scripterror","Script error",a,[b.id]))}};i.require=i.makeRequire();return i}var l,w,x,D,t,E,P,K,Q,fa,la=/(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg,ma=/[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,ea=/\.js$/,ja=/^\.\//;w=Object.prototype;var L=w.toString,ga=w.hasOwnProperty,ia=
+Array.prototype.splice,A=!!("undefined"!==typeof window&&navigator&&document),da=!A&&"undefined"!==typeof importScripts,ka=A&&"PLAYSTATION 3"===navigator.platform?/^complete$/:/^(complete|loaded)$/,Y="undefined"!==typeof opera&&"[object Opera]"===opera.toString(),F={},r={},T=[],O=!1;if("undefined"===typeof define){if("undefined"!==typeof requirejs){if(I(requirejs))return;r=requirejs;requirejs=void 0}"undefined"!==typeof require&&!I(require)&&(r=require,require=void 0);l=requirejs=function(b,c,d,z){var h,
+j="_";!J(b)&&"string"!==typeof b&&(h=b,J(c)?(b=c,c=d,d=z):b=[]);h&&h.context&&(j=h.context);(z=m(F,j))||(z=F[j]=l.s.newContext(j));h&&z.configure(h);return z.require(b,c,d)};l.config=function(b){return l(b)};l.nextTick="undefined"!==typeof setTimeout?function(b){setTimeout(b,4)}:function(b){b()};require||(require=l);l.version="2.1.5";l.jsExtRegExp=/^\/|:|\?|\.js$/;l.isBrowser=A;w=l.s={contexts:F,newContext:ha};l({});y(["toUrl","undef","defined","specified"],function(b){l[b]=function(){var c=F._;return c.require[b].apply(c,
+arguments)}});if(A&&(x=w.head=document.getElementsByTagName("head")[0],D=document.getElementsByTagName("base")[0]))x=w.head=D.parentNode;l.onError=function(b){throw b;};l.load=function(b,c,d){var l=b&&b.config||{},h;if(A)return h=l.xhtml?document.createElementNS("http://www.w3.org/1999/xhtml","html:script"):document.createElement("script"),h.type=l.scriptType||"text/javascript",h.charset="utf-8",h.async=!0,h.setAttribute("data-requirecontext",b.contextName),h.setAttribute("data-requiremodule",c),
+h.attachEvent&&!(h.attachEvent.toString&&0>h.attachEvent.toString().indexOf("[native code"))&&!Y?(O=!0,h.attachEvent("onreadystatechange",b.onScriptLoad)):(h.addEventListener("load",b.onScriptLoad,!1),h.addEventListener("error",b.onScriptError,!1)),h.src=d,K=h,D?x.insertBefore(h,D):x.appendChild(h),K=null,h;if(da)try{importScripts(d),b.completeLoad(c)}catch(j){b.onError(B("importscripts","importScripts failed for "+c+" at "+d,j,[c]))}};A&&M(document.getElementsByTagName("script"),function(b){x||(x=
+b.parentNode);if(t=b.getAttribute("data-main"))return r.baseUrl||(E=t.split("/"),Q=E.pop(),fa=E.length?E.join("/")+"/":"./",r.baseUrl=fa,t=Q),t=t.replace(ea,""),r.deps=r.deps?r.deps.concat(t):[t],!0});define=function(b,c,d){var l,h;"string"!==typeof b&&(d=c,c=b,b=null);J(c)||(d=c,c=[]);!c.length&&I(d)&&d.length&&(d.toString().replace(la,"").replace(ma,function(b,d){c.push(d)}),c=(1===d.length?["require"]:["require","exports","module"]).concat(c));if(O){if(!(l=K))P&&"interactive"===P.readyState||M(document.getElementsByTagName("script"),
+function(b){if("interactive"===b.readyState)return P=b}),l=P;l&&(b||(b=l.getAttribute("data-requiremodule")),h=F[l.getAttribute("data-requirecontext")])}(h?h.defQueue:T).push([b,c,d])};define.amd={jQuery:!0};l.exec=function(b){return eval(b)};l(r)}})(this);
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/ui/test.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/ui/test.js
new file mode 100644
index 0000000..e4d15d7
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/ui/test.js
@@ -0,0 +1,36 @@
+var ASSERT = chai.assert;
+mocha.setup({
+    ui: 'bdd',
+    timeout: 20000
+});
+requirejs.config({
+    paths: {
+        forge: 'forge',
+        test: 'test'
+    }
+});
+requirejs([
+    'test/util',
+    'test/md5',
+    'test/sha1',
+    'test/sha256',
+    'test/hmac',
+    'test/pbkdf2',
+    'test/mgf1',
+    'test/random',
+    'test/asn1',
+    'test/pem',
+    'test/rsa',
+    'test/kem',
+    'test/pkcs1',
+    'test/x509',
+    'test/csr',
+    'test/aes',
+    'test/rc2',
+    'test/des',
+    'test/pkcs7',
+    'test/pkcs12',
+    'test/tls'
+], function() {
+    mocha.run();
+});
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/ui/test.min.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/ui/test.min.js
new file mode 100644
index 0000000..e94e9e0
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/nodejs/ui/test.min.js
@@ -0,0 +1 @@
+(function(){function e(e){var t=e.util=e.util||{};typeof process=="undefined"||!process.nextTick?typeof setImmediate=="function"?(t.setImmediate=setImmediate,t.nextTick=function(e){return setImmediate(e)}):(t.setImmediate=function(e){setTimeout(e,0)},t.nextTick=t.setImmediate):(t.nextTick=process.nextTick,typeof setImmediate=="function"?t.setImmediate=setImmediate:t.setImmediate=t.nextTick),t.isArray=Array.isArray||function(e){return Object.prototype.toString.call(e)==="[object Array]"},t.ByteBuffer=function(e){this.data=e||"",this.read=0},t.ByteBuffer.prototype.length=function(){return this.data.length-this.read},t.ByteBuffer.prototype.isEmpty=function(){return this.length()<=0},t.ByteBuffer.prototype.putByte=function(e){return this.data+=String.fromCharCode(e),this},t.ByteBuffer.prototype.fillWithByte=function(e,t){e=String.fromCharCode(e);var n=this.data;while(t>0)t&1&&(n+=e),t>>>=1,t>0&&(e+=e);return this.data=n,this},t.ByteBuffer.prototype.putBytes=function(e){return this.data+=e,this},t.ByteBuffer.prototype.putString=function(e){return this.data+=t.encodeUtf8(e),this},t.ByteBuffer.prototype.putInt16=function(e){return this.data+=String.fromCharCode(e>>8&255)+String.fromCharCode(e&255),this},t.ByteBuffer.prototype.putInt24=function(e){return this.data+=String.fromCharCode(e>>16&255)+String.fromCharCode(e>>8&255)+String.fromCharCode(e&255),this},t.ByteBuffer.prototype.putInt32=function(e){return this.data+=String.fromCharCode(e>>24&255)+String.fromCharCode(e>>16&255)+String.fromCharCode(e>>8&255)+String.fromCharCode(e&255),this},t.ByteBuffer.prototype.putInt16Le=function(e){return this.data+=String.fromCharCode(e&255)+String.fromCharCode(e>>8&255),this},t.ByteBuffer.prototype.putInt24Le=function(e){return this.data+=String.fromCharCode(e&255)+String.fromCharCode(e>>8&255)+String.fromCharCode(e>>16&255),this},t.ByteBuffer.prototype.putInt32Le=function(e){return this.data+=String.fromCharCode(e&255)+String.fromCharCode(e>>8&255)+String.fromCharCode(e>>16&255)+String.fromCharCode(e>>24&255),this},t.ByteBuffer.prototype.putInt=function(e,t){do t-=8,this.data+=String.fromCharCode(e>>t&255);while(t>0);return this},t.ByteBuffer.prototype.putBuffer=function(e){return this.data+=e.getBytes(),this},t.ByteBuffer.prototype.getByte=function(){return this.data.charCodeAt(this.read++)},t.ByteBuffer.prototype.getInt16=function(){var e=this.data.charCodeAt(this.read)<<8^this.data.charCodeAt(this.read+1);return this.read+=2,e},t.ByteBuffer.prototype.getInt24=function(){var e=this.data.charCodeAt(this.read)<<16^this.data.charCodeAt(this.read+1)<<8^this.data.charCodeAt(this.read+2);return this.read+=3,e},t.ByteBuffer.prototype.getInt32=function(){var e=this.data.charCodeAt(this.read)<<24^this.data.charCodeAt(this.read+1)<<16^this.data.charCodeAt(this.read+2)<<8^this.data.charCodeAt(this.read+3);return this.read+=4,e},t.ByteBuffer.prototype.getInt16Le=function(){var e=this.data.charCodeAt(this.read)^this.data.charCodeAt(this.read+1)<<8;return this.read+=2,e},t.ByteBuffer.prototype.getInt24Le=function(){var e=this.data.charCodeAt(this.read)^this.data.charCodeAt(this.read+1)<<8^this.data.charCodeAt(this.read+2)<<16;return this.read+=3,e},t.ByteBuffer.prototype.getInt32Le=function(){var e=this.data.charCodeAt(this.read)^this.data.charCodeAt(this.read+1)<<8^this.data.charCodeAt(this.read+2)<<16^this.data.charCodeAt(this.read+3)<<24;return this.read+=4,e},t.ByteBuffer.prototype.getInt=function(e){var t=0;do t=(t<<8)+this.data.charCodeAt(this.read++),e-=8;while(e>0);return t},t.ByteBuffer.prototype.getBytes=function(e){var t;return e?(e=Math.min(this.length(),e),t=this.data.slice(this.read,this.read+e),this.read+=e):e===0?t="":(t=this.read===0?this.data:this.data.slice(this.read),this.clear()),t},t.ByteBuffer.prototype.bytes=function(e){return typeof e=="undefined"?this.data.slice(this.read):this.data.slice(this.read,this.read+e)},t.ByteBuffer.prototype.at=function(e){return this.data.charCodeAt(this.read+e)},t.ByteBuffer.prototype.setAt=function(e,t){return this.data=this.data.substr(0,this.read+e)+String.fromCharCode(t)+this.data.substr(this.read+e+1),this},t.ByteBuffer.prototype.last=function(){return this.data.charCodeAt(this.data.length-1)},t.ByteBuffer.prototype.copy=function(){var e=t.createBuffer(this.data);return e.read=this.read,e},t.ByteBuffer.prototype.compact=function(){return this.read>0&&(this.data=this.data.slice(this.read),this.read=0),this},t.ByteBuffer.prototype.clear=function(){return this.data="",this.read=0,this},t.ByteBuffer.prototype.truncate=function(e){var t=Math.max(0,this.length()-e);return this.data=this.data.substr(this.read,t),this.read=0,this},t.ByteBuffer.prototype.toHex=function(){var e="";for(var t=this.read;t<this.data.length;++t){var n=this.data.charCodeAt(t);n<16&&(e+="0"),e+=n.toString(16)}return e},t.ByteBuffer.prototype.toString=function(){return t.decodeUtf8(this.bytes())},t.createBuffer=function(e,n){return n=n||"raw",e!==undefined&&n==="utf8"&&(e=t.encodeUtf8(e)),new t.ByteBuffer(e)},t.fillString=function(e,t){var n="";while(t>0)t&1&&(n+=e),t>>>=1,t>0&&(e+=e);return n},t.xorBytes=function(e,t,n){var r="",i="",s="",o=0,u=0;for(;n>0;--n,++o)i=e.charCodeAt(o)^t.charCodeAt(o),u>=10&&(r+=s,s="",u=0),s+=String.fromCharCode(i),++u;return r+=s,r},t.hexToBytes=function(e){var t="",n=0;e.length&!0&&(n=1,t+=String.fromCharCode(parseInt(e[0],16)));for(;n<e.length;n+=2)t+=String.fromCharCode(parseInt(e.substr(n,2),16));return t},t.bytesToHex=function(e){return t.createBuffer(e).toHex()},t.int32ToBytes=function(e){return String.fromCharCode(e>>24&255)+String.fromCharCode(e>>16&255)+String.fromCharCode(e>>8&255)+String.fromCharCode(e&255)};var n="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",r=[62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,64,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51];t.encode64=function(e,t){var r="",i="",s,o,u,a=0;while(a<e.length)s=e.charCodeAt(a++),o=e.charCodeAt(a++),u=e.charCodeAt(a++),r+=n.charAt(s>>2),r+=n.charAt((s&3)<<4|o>>4),isNaN(o)?r+="==":(r+=n.charAt((o&15)<<2|u>>6),r+=isNaN(u)?"=":n.charAt(u&63)),t&&r.length>t&&(i+=r.substr(0,t)+"\r\n",r=r.substr(t));return i+=r,i},t.decode64=function(e){e=e.replace(/[^A-Za-z0-9\+\/\=]/g,"");var t="",n,i,s,o,u=0;while(u<e.length)n=r[e.charCodeAt(u++)-43],i=r[e.charCodeAt(u++)-43],s=r[e.charCodeAt(u++)-43],o=r[e.charCodeAt(u++)-43],t+=String.fromCharCode(n<<2|i>>4),s!==64&&(t+=String.fromCharCode((i&15)<<4|s>>2),o!==64&&(t+=String.fromCharCode((s&3)<<6|o)));return t},t.encodeUtf8=function(e){return unescape(encodeURIComponent(e))},t.decodeUtf8=function(e){return decodeURIComponent(escape(e))},t.deflate=function(e,n,r){n=t.decode64(e.deflate(t.encode64(n)).rval);if(r){var i=2,s=n.charCodeAt(1);s&32&&(i=6),n=n.substring(i,n.length-4)}return n},t.inflate=function(e,n,r){var i=e.inflate(t.encode64(n)).rval;return i===null?null:t.decode64(i)};var i=function(e,n,r){if(!e)throw{message:"WebStorage not available."};var i;r===null?i=e.removeItem(n):(r=t.encode64(JSON.stringify(r)),i=e.setItem(n,r));if(typeof i!="undefined"&&i.rval!==!0)throw i.error},s=function(e,n){if(!e)throw{message:"WebStorage not available."};var r=e.getItem(n);if(e.init)if(r.rval===null){if(r.error)throw r.error;r=null}else r=r.rval;return r!==null&&(r=JSON.parse(t.decode64(r))),r},o=function(e,t,n,r){var o=s(e,t);o===null&&(o={}),o[n]=r,i(e,t,o)},u=function(e,t,n){var r=s(e,t);return r!==null&&(r=n in r?r[n]:null),r},a=function(e,t,n){var r=s(e,t);if(r!==null&&n in r){delete r[n];var o=!0;for(var u in r){o=!1;break}o&&(r=null),i(e,t,r)}},f=function(e,t){i(e,t,null)},l=function(e,t,n){var r=null;typeof n=="undefined"&&(n=["web","flash"]);var i,s=!1,o=null;for(var u in n){i=n[u];try{if(i==="flash"||i==="both"){if(t[0]===null)throw{message:"Flash local storage not available."};r=e.apply(this,t),s=i==="flash"}if(i==="web"||i==="both")t[0]=localStorage,r=e.apply(this,t),s=!0}catch(a){o=a}if(s)break}if(!s)throw o;return r};t.setItem=function(e,t,n,r,i){l(o,arguments,i)},t.getItem=function(e,t,n,r){return l(u,arguments,r)},t.removeItem=function(e,t,n,r){l(a,arguments,r)},t.clearItems=function(e,t,n){l(f,arguments,n)},t.parseUrl=function(e){var t=/^(https?):\/\/([^:&^\/]*):?(\d*)(.*)$/g;t.lastIndex=0;var n=t.exec(e),r=n===null?null:{full:e,scheme:n[1],host:n[2],port:n[3],path:n[4]};return r&&(r.fullHost=r.host,r.port?r.port!==80&&r.scheme==="http"?r.fullHost+=":"+r.port:r.port!==443&&r.scheme==="https"&&(r.fullHost+=":"+r.port):r.scheme==="http"?r.port=80:r.scheme==="https"&&(r.port=443),r.full=r.scheme+"://"+r.fullHost),r};var c=null;t.getQueryVariables=function(e){var t=function(e){var t={},n=e.split("&");for(var r=0;r<n.length;r++){var i=n[r].indexOf("="),s,o;i>0?(s=n[r].substring(0,i),o=n[r].substring(i+1)):(s=n[r],o=null),s in t||(t[s]=[]),!(s in Object.prototype)&&o!==null&&t[s].push(unescape(o))}return t},n;return typeof e=="undefined"?(c===null&&(typeof window=="undefined"?c={}:c=t(window.location.search.substring(1))),n=c):n=t(e),n},t.parseFragment=function(e){var n=e,r="",i=e.indexOf("?");i>0&&(n=e.substring(0,i),r=e.substring(i+1));var s=n.split("/");s.length>0&&s[0]===""&&s.shift();var o=r===""?{}:t.getQueryVariables(r);return{pathString:n,queryString:r,path:s,query:o}},t.makeRequest=function(e){var n=t.parseFragment(e),r={path:n.pathString,query:n.queryString,getPath:function(e){return typeof e=="undefined"?n.path:n.path[e]},getQuery:function(e,t){var r;return typeof e=="undefined"?r=n.query:(r=n.query[e],r&&typeof t!="undefined"&&(r=r[t])),r},getQueryLast:function(e,t){var n,i=r.getQuery(e);return i?n=i[i.length-1]:n=t,n}};return r},t.makeLink=function(e,t,n){e=jQuery.isArray(e)?e.join("/"):e;var r=jQuery.param(t||{});return n=n||"",e+(r.length>0?"?"+r:"")+(n.length>0?"#"+n:"")},t.setPath=function(e,t,n){if(typeof e=="object"&&e!==null){var r=0,i=t.length;while(r<i){var s=t[r++];if(r==i)e[s]=n;else{var o=s in e;if(!o||o&&typeof e[s]!="object"||o&&e[s]===null)e[s]={};e=e[s]}}}},t.getPath=function(e,t,n){var r=0,i=t.length,s=!0;while(s&&r<i&&typeof e=="object"&&e!==null){var o=t[r++];s=o in e,s&&(e=e[o])}return s?e:n},t.deletePath=function(e,t){if(typeof e=="object"&&e!==null){var n=0,r=t.length;while(n<r){var i=t[n++];if(n==r)delete e[i];else{if(!(i in e&&typeof e[i]=="object"&&e[i]!==null))break;e=e[i]}}}},t.isEmpty=function(e){for(var t in e)if(e.hasOwnProperty(t))return!1;return!0},t.format=function(e){var t=/%./g,n,r,i=0,s=[],o=0;while(n=t.exec(e)){r=e.substring(o,t.lastIndex-2),r.length>0&&s.push(r),o=t.lastIndex;var u=n[0][1];switch(u){case"s":case"o":i<arguments.length?s.push(arguments[i++ +1]):s.push("<?>");break;case"%":s.push("%");break;default:s.push("<%"+u+"?>")}}return s.push(e.substring(o)),s.join("")},t.formatNumber=function(e,t,n,r){var i=e,s=isNaN(t=Math.abs(t))?2:t,o=n===undefined?",":n,u=r===undefined?".":r,a=i<0?"-":"",f=parseInt(i=Math.abs(+i||0).toFixed(s),10)+"",l=f.length>3?f.length%3:0;return a+(l?f.substr(0,l)+u:"")+f.substr(l).replace(/(\d{3})(?=\d)/g,"$1"+u)+(s?o+Math.abs(i-f).toFixed(s).slice(2):"")},t.formatSize=function(e){return e>=1073741824?e=t.formatNumber(e/1073741824,2,".","")+" GiB":e>=1048576?e=t.formatNumber(e/1048576,2,".","")+" MiB":e>=1024?e=t.formatNumber(e/1024,0)+" KiB":e=t.formatNumber(e,0)+" bytes",e}}var t="util";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/util",["require","module"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})})(),function(){function e(e,t){describe("util",function(){it("should put bytes into a buffer",function(){var n=t.createBuffer();n.putByte(1),n.putByte(2),n.putByte(3),n.putByte(4),n.putInt32(4),n.putByte(1),n.putByte(2),n.putByte(3),n.putInt32(4294967295);var r=n.toHex();e.equal(r,"0102030400000004010203ffffffff");var i=[];while(n.length()>0)i.push(n.getByte());e.deepEqual(i,[1,2,3,4,0,0,0,4,1,2,3,255,255,255,255])}),it("should convert bytes from hex",function(){var n="0102030400000004010203ffffffff",r=t.createBuffer();r.putBytes(t.hexToBytes(n)),e.equal(r.toHex(),n)}),it("should base64 encode some bytes",function(){var n="00010203050607080A0B0C0D0F1011121415161719",r="MDAwMTAyMDMwNTA2MDcwODBBMEIwQzBEMEYxMDExMTIxNDE1MTYxNzE5";e.equal(t.encode64(n),r)}),it("should base64 decode some bytes",function(){var n="00010203050607080A0B0C0D0F1011121415161719",r="MDAwMTAyMDMwNTA2MDcwODBBMEIwQzBEMEYxMDExMTIxNDE1MTYxNzE5";e.equal(t.decode64(r),n)})})}typeof define=="function"?define("test/util",["forge/util"],function(t){e(ASSERT,t())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/util")())}(),function(){function e(e){var t=e.md5=e.md5||{};e.md=e.md||{},e.md.algorithms=e.md.algorithms||{},e.md.md5=e.md.algorithms.md5=t;var n=null,r=null,i=null,s=null,o=!1,u=function(){n=String.fromCharCode(128),n+=e.util.fillString(String.fromCharCode(0),64),r=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,1,6,11,0,5,10,15,4,9,14,3,8,13,2,7,12,5,8,11,14,1,4,7,10,13,0,3,6,9,12,15,2,0,7,14,5,12,3,10,1,8,15,6,13,4,11,2,9],i=[7,12,17,22,7,12,17,22,7,12,17,22,7,12,17,22,5,9,14,20,5,9,14,20,5,9,14,20,5,9,14,20,4,11,16,23,4,11,16,23,4,11,16,23,4,11,16,23,6,10,15,21,6,10,15,21,6,10,15,21,6,10,15,21],s=new Array(64);for(var t=0;t<64;++t)s[t]=Math.floor(Math.abs(Math.sin(t+1))*4294967296);o=!0},a=function(e,t,n){var o,u,a,f,l,c,h,p,d=n.length();while(d>=64){u=e.h0,a=e.h1,f=e.h2,l=e.h3;for(p=0;p<16;++p)t[p]=n.getInt32Le(),c=l^a&(f^l),o=u+c+s[p]+t[p],h=i[p],u=l,l=f,f=a,a+=o<<h|o>>>32-h;for(;p<32;++p)c=f^l&(a^f),o=u+c+s[p]+t[r[p]],h=i[p],u=l,l=f,f=a,a+=o<<h|o>>>32-h;for(;p<48;++p)c=a^f^l,o=u+c+s[p]+t[r[p]],h=i[p],u=l,l=f,f=a,a+=o<<h|o>>>32-h;for(;p<64;++p)c=f^(a|~l),o=u+c+s[p]+t[r[p]],h=i[p],u=l,l=f,f=a,a+=o<<h|o>>>32-h;e.h0=e.h0+u&4294967295,e.h1=e.h1+a&4294967295,e.h2=e.h2+f&4294967295,e.h3=e.h3+l&4294967295,d-=64}};t.create=function(){o||u();var t=null,r=e.util.createBuffer(),i=new Array(16),s={algorithm:"md5",blockLength:64,digestLength:16,messageLength:0};return s.start=function(){return s.messageLength=0,r=e.util.createBuffer(),t={h0:1732584193,h1:4023233417,h2:2562383102,h3:271733878},s},s.start(),s.update=function(n,o){return o==="utf8"&&(n=e.util.encodeUtf8(n)),s.messageLength+=n.length,r.putBytes(n),a(t,i,r),(r.read>2048||r.length()===0)&&r.compact(),s},s.digest=function(){var o=s.messageLength,u=e.util.createBuffer();u.putBytes(r.bytes()),u.putBytes(n.substr(0,64-(o+8)%64)),u.putInt32Le(o<<3&4294967295),u.putInt32Le(o>>>29&255);var f={h0:t.h0,h1:t.h1,h2:t.h2,h3:t.h3};a(f,i,u);var l=e.util.createBuffer();return l.putInt32Le(f.h0),l.putInt32Le(f.h1),l.putInt32Le(f.h2),l.putInt32Le(f.h3),l},s}}var t="md5";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/md5",["require","module","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e,t,n){describe("md5",function(){it("should digest the empty string",function(){var n=t.create();e.equal(n.digest().toHex(),"d41d8cd98f00b204e9800998ecf8427e")}),it('should digest "abc"',function(){var n=t.create();n.update("abc"),e.equal(n.digest().toHex(),"900150983cd24fb0d6963f7d28e17f72")}),it('should digest "The quick brown fox jumps over the lazy dog"',function(){var n=t.create();n.update("The quick brown fox jumps over the lazy dog"),e.equal(n.digest().toHex(),"9e107d9d372bb6826bd81d3542a419d6")}),it('should digest "c\'è"',function(){var n=t.create();n.update("c'è","utf8"),e.equal(n.digest().toHex(),"8ef7c2941d78fe89f31e614437c9db59")}),it('should digest "THIS IS A MESSAGE"',function(){var n=t.create();n.start(),n.update("THIS IS "),n.update("A MESSAGE"),e.equal(n.digest().toHex(),"78eebfd9d42958e3f31244f116ab7bbe"),e.equal(n.digest().toHex(),"78eebfd9d42958e3f31244f116ab7bbe")}),it("should digest a long message",function(){var r=n.hexToBytes("0100002903018d32e9c6dc423774c4c39a5a1b78f44cc2cab5f676d39f703d29bfa27dfeb870000002002f01000200004603014c2c1e835d39da71bc0857eb04c2b50fe90dbb2a8477fe7364598d6f0575999c20a6c7248c5174da6d03ac711888f762fc4ed54f7254b32273690de849c843073d002f000b0003d20003cf0003cc308203c8308202b0a003020102020100300d06092a864886f70d0101050500308186310b3009060355040613025553311d301b060355040a13144469676974616c2042617a6161722c20496e632e31443042060355040b133b4269746d756e6b206c6f63616c686f73742d6f6e6c7920436572746966696361746573202d20417574686f72697a6174696f6e207669612042545031123010060355040313096c6f63616c686f7374301e170d3130303231343137303931395a170d3230303231333137303931395a308186310b3009060355040613025553311d301b060355040a13144469676974616c2042617a6161722c20496e632e31443042060355040b133b4269746d756e6b206c6f63616c686f73742d6f6e6c7920436572746966696361746573202d20417574686f72697a6174696f6e207669612042545031123010060355040313096c6f63616c686f737430820122300d06092a864886f70d01010105000382010f003082010a0282010100dc436f17d6909d8a9d6186ea218eb5c86b848bae02219bd56a71203daf07e81bc19e7e98134136bcb012881864bf03b3774652ad5eab85dba411a5114ffeac09babce75f31314345512cd87c91318b2e77433270a52185fc16f428c3ca412ad6e9484bc2fb87abb4e8fb71bf0f619e31a42340b35967f06c24a741a31c979c0bb8921a90a47025fbeb8adca576979e70a56830c61170c9647c18c0794d68c0df38f3aac5fc3b530e016ea5659715339f3f3c209cdee9dbe794b5af92530c5754c1d874b78974bfad994e0dfc582275e79feb522f6e4bcc2b2945baedfb0dbdaebb605f9483ff0bea29ecd5f4d6f2769965d1b3e04f8422716042680011ff676f0203010001a33f303d300c0603551d130101ff04023000300e0603551d0f0101ff0404030204f0301d0603551d250416301406082b0601050507030106082b06010505070302300d06092a864886f70d010105050003820101009c4562be3f2d8d8e388085a697f2f106eaeff4992a43f198fe3dcf15c8229cf1043f061a38204f73d86f4fb6348048cc5279ed719873aa10e3773d92b629c2c3fcce04012c81ba3b4ec451e9644ec5191078402d845e05d02c7b4d974b4588276e5037aba7ef26a8bddeb21e10698c82f425e767dc401adf722fa73ab78cfa069bd69052d7ca6a75cc9225550e315d71c5f8764362ea4dbc6ecb837a8471043c5a7f826a71af145a053090bd4bccca6a2c552841cdb1908a8352f49283d2e641acdef667c7543af441a16f8294251e2ac376fa507b53ae418dd038cd20cef1e7bfbf5ae03a7c88d93d843abaabbdc5f3431132f3e559d2dd414c3eda38a210b80e00000010000102010026a220b7be857402819b78d81080d01a682599bbd00902985cc64edf8e520e4111eb0e1729a14ffa3498ca259cc9ad6fc78fa130d968ebdb78dc0b950c0aa44355f13ba678419185d7e4608fe178ca6b2cef33e4193778d1a70fe4d0dfcb110be4bbb4dbaa712177655728f914ab4c0f6c4aef79a46b3d996c82b2ebe9ed1748eb5cace7dc44fb67e73f452a047f2ed199b3d50d5db960acf03244dc8efa4fc129faf8b65f9e52e62b5544722bd17d2358e817a777618a4265a3db277fc04851a82a91fe6cdcb8127f156e0b4a5d1f54ce2742eb70c895f5f8b85f5febe69bc73e891f9280826860a0c2ef94c7935e6215c3c4cd6b0e43e80cca396d913d36be"),i=t.create();i.update(r),e.equal(i.digest().toHex(),"d15a2da0e92c3da55dc573f885b6e653")})})}typeof define=="function"?define("test/md5",["forge/md5","forge/util"],function(t,n){e(ASSERT,t(),n())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/md5")(),require("../../js/util")())}(),function(){function e(e){var t=e.sha1=e.sha1||{};e.md=e.md||{},e.md.algorithms=e.md.algorithms||{},e.md.sha1=e.md.algorithms.sha1=t;var n=null,r=!1,i=function(){n=String.fromCharCode(128),n+=e.util.fillString(String.fromCharCode(0),64),r=!0},s=function(e,t,n){var r,i,s,o,u,a,f,l,c=n.length();while(c>=64){i=e.h0,s=e.h1,o=e.h2,u=e.h3,a=e.h4;for(l=0;l<16;++l)r=n.getInt32(),t[l]=r,f=u^s&(o^u),r=(i<<5|i>>>27)+f+a+1518500249+r,a=u,u=o,o=s<<30|s>>>2,s=i,i=r;for(;l<20;++l)r=t[l-3]^t[l-8]^t[l-14]^t[l-16],r=r<<1|r>>>31,t[l]=r,f=u^s&(o^u),r=(i<<5|i>>>27)+f+a+1518500249+r,a=u,u=o,o=s<<30|s>>>2,s=i,i=r;for(;l<32;++l)r=t[l-3]^t[l-8]^t[l-14]^t[l-16],r=r<<1|r>>>31,t[l]=r,f=s^o^u,r=(i<<5|i>>>27)+f+a+1859775393+r,a=u,u=o,o=s<<30|s>>>2,s=i,i=r;for(;l<40;++l)r=t[l-6]^t[l-16]^t[l-28]^t[l-32],r=r<<2|r>>>30,t[l]=r,f=s^o^u,r=(i<<5|i>>>27)+f+a+1859775393+r,a=u,u=o,o=s<<30|s>>>2,s=i,i=r;for(;l<60;++l)r=t[l-6]^t[l-16]^t[l-28]^t[l-32],r=r<<2|r>>>30,t[l]=r,f=s&o|u&(s^o),r=(i<<5|i>>>27)+f+a+2400959708+r,a=u,u=o,o=s<<30|s>>>2,s=i,i=r;for(;l<80;++l)r=t[l-6]^t[l-16]^t[l-28]^t[l-32],r=r<<2|r>>>30,t[l]=r,f=s^o^u,r=(i<<5|i>>>27)+f+a+3395469782+r,a=u,u=o,o=s<<30|s>>>2,s=i,i=r;e.h0+=i,e.h1+=s,e.h2+=o,e.h3+=u,e.h4+=a,c-=64}};t.create=function(){r||i();var t=null,o=e.util.createBuffer(),u=new Array(80),a={algorithm:"sha1",blockLength:64,digestLength:20,messageLength:0};return a.start=function(){return a.messageLength=0,o=e.util.createBuffer(),t={h0:1732584193,h1:4023233417,h2:2562383102,h3:271733878,h4:3285377520},a},a.start(),a.update=function(n,r){return r==="utf8"&&(n=e.util.encodeUtf8(n)),a.messageLength+=n.length,o.putBytes(n),s(t,u,o),(o.read>2048||o.length()===0)&&o.compact(),a},a.digest=function(){var r=a.messageLength,i=e.util.createBuffer();i.putBytes(o.bytes()),i.putBytes(n.substr(0,64-(r+8)%64)),i.putInt32(r>>>29&255),i.putInt32(r<<3&4294967295);var f={h0:t.h0,h1:t.h1,h2:t.h2,h3:t.h3,h4:t.h4};s(f,u,i);var l=e.util.createBuffer();return l.putInt32(f.h0),l.putInt32(f.h1),l.putInt32(f.h2),l.putInt32(f.h3),l.putInt32(f.h4),l},a}}var t="sha1";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/sha1",["require","module","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e,t,n){describe("sha1",function(){it("should digest the empty string",function(){var n=t.create();e.equal(n.digest().toHex(),"da39a3ee5e6b4b0d3255bfef95601890afd80709")}),it('should digest "abc"',function(){var n=t.create();n.update("abc"),e.equal(n.digest().toHex(),"a9993e364706816aba3e25717850c26c9cd0d89d")}),it('should digest "The quick brown fox jumps over the lazy dog"',function(){var n=t.create();n.update("The quick brown fox jumps over the lazy dog"),e.equal(n.digest().toHex(),"2fd4e1c67a2d28fced849ee1bb76e7391b93eb12")}),it('should digest "c\'è"',function(){var n=t.create();n.update("c'è","utf8"),e.equal(n.digest().toHex(),"98c9a3f804daa73b68a5660d032499a447350c0d")}),it('should digest "THIS IS A MESSAGE"',function(){var n=t.create();n.start(),n.update("THIS IS "),n.update("A MESSAGE"),e.equal(n.digest().toHex(),"5f24f4d6499fd2d44df6c6e94be8b14a796c071d"),e.equal(n.digest().toHex(),"5f24f4d6499fd2d44df6c6e94be8b14a796c071d")}),it("should digest a long message",function(){var r=t.create();r.update(n.fillString("a",1e6)),e.equal(r.digest().toHex(),"34aa973cd4c4daa4f61eeb2bdbad27316534016f")})})}typeof define=="function"?define("test/sha1",["forge/sha1","forge/util"],function(t,n){e(ASSERT,t(),n())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/sha1")(),require("../../js/util")())}(),function(){function e(e){var t=e.sha256=e.sha256||{};e.md=e.md||{},e.md.algorithms=e.md.algorithms||{},e.md.sha256=e.md.algorithms.sha256=t;var n=null,r=!1,i=null,s=function(){n=String.fromCharCode(128),n+=e.util.fillString(String.fromCharCode(0),64),i=[1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298],r=!0},o=function(e,t,n){var r,s,o,u,a,f,l,c,h,p,d,v,m,g,y,b=n.length();while(b>=64){for(l=0;l<16;++l)t[l]=n.getInt32();for(;l<64;++l)r=t[l-2],r=(r>>>17|r<<15)^(r>>>19|r<<13)^r>>>10,s=t[l-15],s=(s>>>7|s<<25)^(s>>>18|s<<14)^s>>>3,t[l]=r+t[l-7]+s+t[l-16]&4294967295;c=e.h0,h=e.h1,p=e.h2,d=e.h3,v=e.h4,m=e.h5,g=e.h6,y=e.h7;for(l=0;l<64;++l)u=(v>>>6|v<<26)^(v>>>11|v<<21)^(v>>>25|v<<7),a=g^v&(m^g),o=(c>>>2|c<<30)^(c>>>13|c<<19)^(c>>>22|c<<10),f=c&h|p&(c^h),r=y+u+a+i[l]+t[l],s=o+f,y=g,g=m,m=v,v=d+r&4294967295,d=p,p=h,h=c,c=r+s&4294967295;e.h0=e.h0+c&4294967295,e.h1=e.h1+h&4294967295,e.h2=e.h2+p&4294967295,e.h3=e.h3+d&4294967295,e.h4=e.h4+v&4294967295,e.h5=e.h5+m&4294967295,e.h6=e.h6+g&4294967295,e.h7=e.h7+y&4294967295,b-=64}};t.create=function(){r||s();var t=null,i=e.util.createBuffer(),u=new Array(64),a={algorithm:"sha256",blockLength:64,digestLength:32,messageLength:0};return a.start=function(){return a.messageLength=0,i=e.util.createBuffer(),t={h0:1779033703,h1:3144134277,h2:1013904242,h3:2773480762,h4:1359893119,h5:2600822924,h6:528734635,h7:1541459225},a},a.start(),a.update=function(n,r){return r==="utf8"&&(n=e.util.encodeUtf8(n)),a.messageLength+=n.length,i.putBytes(n),o(t,u,i),(i.read>2048||i.length()===0)&&i.compact(),a},a.digest=function(){var r=a.messageLength,s=e.util.createBuffer();s.putBytes(i.bytes()),s.putBytes(n.substr(0,64-(r+8)%64)),s.putInt32(r>>>29&255),s.putInt32(r<<3&4294967295);var f={h0:t.h0,h1:t.h1,h2:t.h2,h3:t.h3,h4:t.h4,h5:t.h5,h6:t.h6,h7:t.h7};o(f,u,s);var l=e.util.createBuffer();return l.putInt32(f.h0),l.putInt32(f.h1),l.putInt32(f.h2),l.putInt32(f.h3),l.putInt32(f.h4),l.putInt32(f.h5),l.putInt32(f.h6),l.putInt32(f.h7),l},a}}var t="sha256";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/sha256",["require","module","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e,t,n){describe("sha256",function(){it("should digest the empty string",function(){var n=t.create();e.equal(n.digest().toHex(),"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")}),it('should digest "abc"',function(){var n=t.create();n.update("abc"),e.equal(n.digest().toHex(),"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad")}),it('should digest "The quick brown fox jumps over the lazy dog"',function(){var n=t.create();n.update("The quick brown fox jumps over the lazy dog"),e.equal(n.digest().toHex(),"d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592")}),it('should digest "c\'è"',function(){var n=t.create();n.update("c'è","utf8"),e.equal(n.digest().toHex(),"1aa15c717afffd312acce2217ce1c2e5dabca53c92165999132ec9ca5decdaca")}),it('should digest "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"',function(){var n=t.create();n.start(),n.update("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"),e.equal(n.digest().toHex(),"248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1"),e.equal(n.digest().toHex(),"248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1")}),it("should digest a long message",function(){var r=t.create();r.update(n.fillString("a",1e6)),e.equal(r.digest().toHex(),"cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0")})})}typeof define=="function"?define("test/sha256",["forge/sha256","forge/util"],function(t,n){e(ASSERT,t(),n())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/sha256")(),require("../../js/util")())}(),function(){function e(e){e.md=e.md||{},e.md.algorithms={md5:e.md5,sha1:e.sha1,sha256:e.sha256},e.md.md5=e.md5,e.md.sha1=e.sha1,e.md.sha256=e.sha256}var t="md";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/md",["require","module","./md5","./sha1","./sha256"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){var t=e.hmac=e.hmac||{};t.create=function(){var t=null,n=null,r=null,i=null,s={};return s.start=function(s,o){if(s!==null)if(typeof s=="string"){s=s.toLowerCase();if(!(s in e.md.algorithms))throw'Unknown hash algorithm "'+s+'"';n=e.md.algorithms[s].create()}else n=s;if(o===null)o=t;else{if(typeof o=="string")o=e.util.createBuffer(o);else if(e.util.isArray(o)){var u=o;o=e.util.createBuffer();for(var a=0;a<u.length;++a)o.putByte(u[a])}var f=o.length();f>n.blockLength&&(n.start(),n.update(o.bytes()),o=n.digest()),r=e.util.createBuffer(),i=e.util.createBuffer(),f=o.length();for(var a=0;a<f;++a){var u=o.at(a);r.putByte(54^u),i.putByte(92^u)}if(f<n.blockLength){var u=n.blockLength-f;for(var a=0;a<u;++a)r.putByte(54),i.putByte(92)}t=o,r=r.bytes(),i=i.bytes()}n.start(),n.update(r)},s.update=function(e){n.update(e)},s.getMac=function(){var e=n.digest().bytes();return n.start(),n.update(i),n.update(e),n.digest()},s.digest=s.getMac,s}}var t="hmac";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/hmac",["require","module","./md","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e,t,n){describe("hmac",function(){it('should md5 hash "Hi There", 16-byte key',function(){var r=n.hexToBytes("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"),i=t.create();i.start("MD5",r),i.update("Hi There"),e.equal(i.digest().toHex(),"9294727a3638bb1c13f48ef8158bfc9d")}),it('should md5 hash "what do ya want for nothing?", "Jefe" key',function(){var n=t.create();n.start("MD5","Jefe"),n.update("what do ya want for nothing?"),e.equal(n.digest().toHex(),"750c783e6ab0b503eaa86e310a5db738")}),it('should md5 hash "Test Using Larger Than Block-Size Key - Hash Key First", 80-byte key',function(){var r=n.hexToBytes("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),i=t.create();i.start("MD5",r),i.update("Test Using Larger Than Block-Size Key - Hash Key First"),e.equal(i.digest().toHex(),"6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd")}),it('should sha1 hash "Hi There", 20-byte key',function(){var r=n.hexToBytes("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"),i=t.create();i.start("SHA1",r),i.update("Hi There"),e.equal(i.digest().toHex(),"b617318655057264e28bc0b6fb378c8ef146be00")}),it('should sha1 hash "what do ya want for nothing?", "Jefe" key',function(){var n=t.create();n.start("SHA1","Jefe"),n.update("what do ya want for nothing?"),e.equal(n.digest().toHex(),"effcdf6ae5eb2fa2d27416d5f184df9c259a7c79")}),it('should sha1 hash "Test Using Larger Than Block-Size Key - Hash Key First", 80-byte key',function(){var r=n.hexToBytes("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),i=t.create();i.start("SHA1",r),i.update("Test Using Larger Than Block-Size Key - Hash Key First"),e.equal(i.digest().toHex(),"aa4ae5e15272d00e95705637ce8a3b55ed402112")})})}typeof define=="function"?define("test/hmac",["forge/hmac","forge/util"],function(t,n){e(ASSERT,t(),n())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/hmac")(),require("../../js/util")())}(),function(){function e(e){var t=e.pkcs5=e.pkcs5||{};e.pbkdf2=t.pbkdf2=function(t,n,r,i,s){if(typeof s=="undefined"||s===null)s=e.md.sha1.create();var o=s.digestLength;if(i>4294967295*o)throw{message:"Derived key is too long."};var u=Math.ceil(i/o),a=i-(u-1)*o,f=e.hmac.create();f.start(s,t);var l="",c,h,p;for(var d=1;d<=u;++d){f.update(n),f.update(e.util.int32ToBytes(d)),c=p=f.digest().getBytes();for(var v=2;v<=r;++v)f.start(null,null),f.update(p),h=f.digest().getBytes(),c=e.util.xorBytes(c,h,o),p=h;l+=d<u?c:c.substr(0,a)}return l}}var t="pbkdf2";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/pbkdf2",["require","module","./hmac","./md","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e,t,n){describe("pbkdf2",function(){it("should derive a password with hmac-sha-1 c=1",function(){var r=n.bytesToHex(t("password","salt",1,20));e.equal(r,"0c60c80f961f0e71f3a9b524af6012062fe037a6")}),it("should derive a password with hmac-sha-1 c=2",function(){var r=n.bytesToHex(t("password","salt",2,20));e.equal(r,"ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957")}),it("should derive a password with hmac-sha-1 c=5 keylen=8",function(){var r=n.hexToBytes("1234567878563412"),i=n.bytesToHex(t("password",r,5,8));e.equal(i,"d1daa78615f287e6")}),it("should derive a password with hmac-sha-1 c=4096",function(){var r=n.bytesToHex(t("password","salt",4096,20));e.equal(r,"4b007901b765489abead49d926f721d065a429c1")})})}typeof define=="function"?define("test/pbkdf2",["forge/pbkdf2","forge/util"],function(t,n){e(ASSERT,t(),n())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/pbkdf2")(),require("../../js/util")())}(),function(){function e(e){e.mgf=e.mgf||{};var t=e.mgf.mgf1=e.mgf1=e.mgf1||{};t.create=function(t){var n={generate:function(n,r){var i=new e.util.ByteBuffer,s=Math.ceil(r/t.digestLength);for(var o=0;o<s;o++){var u=new e.util.ByteBuffer;u.putInt32(o),t.start(),t.update(n+u.getBytes()),i.putBuffer(t.digest())}return i.truncate(i.length()-r),i.getBytes()}};return n}}var t="mgf1";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/mgf1",["require","module","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){e.mgf=e.mgf||{},e.mgf.mgf1=e.mgf1}var t="mgf";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/mgf",["require","module","./mgf1"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e,t,n,r){describe("mgf1",function(){it("should digest the empty string",function(){var i=r.hexToBytes("032e45326fa859a72ec235acff929b15d1372e30b207255f0611b8f785d764374152e0ac009e509e7ba30cd2f1778e113b64e135cf4e2292c75efe5288edfda4"),s=r.hexToBytes("5f8de105b5e96b2e490ddecbd147dd1def7e3b8e0e6a26eb7b956ccb8b3bdc1ca975bc57c3989e8fbad31a224655d800c46954840ff32052cdf0d640562bdfadfa263cfccf3c52b29f2af4a1869959bc77f854cf15bd7a25192985a842dbff8e13efee5b7e7e55bbe4d389647c686a9a9ab3fb889b2d7767d3837eea4e0a2f04"),o=t.mgf1.create(n.sha1.create()),u=o.generate(i,s.length);e.equal(u,s)})})}typeof define=="function"?define("test/mgf1",["forge/mgf","forge/md","forge/util"],function(t,n,r){e(ASSERT,t(),n(),r())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/mgf")(),require("../../js/md")(),require("../../js/util")())}(),function(){function e(e){var t=!1,n=4,r,i,s,o,u,a=function(){t=!0,s=[0,1,2,4,8,16,32,64,128,27,54];var e=new Array(256);for(var n=0;n<128;++n)e[n]=n<<1,e[n+128]=n+128<<1^283;r=new Array(256),i=new Array(256),o=new Array(4),u=new Array(4);for(var n=0;n<4;++n)o[n]=new Array(256),u[n]=new Array(256);var a=0,f=0,l,c,h,p,d,v,m;for(var n=0;n<256;++n){p=f^f<<1^f<<2^f<<3^f<<4,p=p>>8^p&255^99,r[a]=p,i[p]=a,d=e[p],l=e[a],c=e[l],h=e[c],v=d<<24^p<<16^p<<8^(p^d),m=(l^c^h)<<24^(a^h)<<16^(a^c^h)<<8^(a^l^h);for(var g=0;g<4;++g)o[g][a]=v,u[g][p]=m,v=v<<24|v>>>8,m=m<<24|m>>>8;a===0?a=f=1:(a=l^e[e[e[l^h]]],f^=e[e[f]])}},f=function(e,t){var i=e.slice(0),o,a=1,f=i.length,l=f+6+1,c=n*l;for(var h=f;h<c;++h)o=i[h-1],h%f===0?(o=r[o>>>16&255]<<24^r[o>>>8&255]<<16^r[o&255]<<8^r[o>>>24]^s[a]<<24,a++):f>6&&h%f===4&&(o=r[o>>>24]<<24^r[o>>>16&255]<<16^r[o>>>8&255]<<8^r[o&255]),i[h]=i[h-f]^o;if(t){var p,d=u[0],v=u[1],m=u[2],g=u[3],y=i.slice(0),c=i.length;for(var h=0,b=c-n;h<c;h+=n,b-=n)if(h===0||h===c-n)y[h]=i[b],y[h+1]=i[b+3],y[h+2]=i[b+2],y[h+3]=i[b+1];else for(var w=0;w<n;++w)p=i[b+w],y[h+(3&-w)]=d[r[p>>>24]]^v[r[p>>>16&255]]^m[r[p>>>8&255]]^g[r[p&255]];i=y}return i},l=function(e,t,n,s){var a=e.length/4-1,f,l,c,h,p;s?(f=u[0],l=u[1],c=u[2],h=u[3],p=i):(f=o[0],l=o[1],c=o[2],h=o[3],p=r);var d,v,m,g,y,b,w;d=t[0]^e[0],v=t[s?3:1]^e[1],m=t[2]^e[2],g=t[s?1:3]^e[3];var E=3;for(var S=1;S<a;++S)y=f[d>>>24]^l[v>>>16&255]^c[m>>>8&255]^h[g&255]^e[++E],b=f[v>>>24]^l[m>>>16&255]^c[g>>>8&255]^h[d&255]^e[++E],w=f[m>>>24]^l[g>>>16&255]^c[d>>>8&255]^h[v&255]^e[++E],g=f[g>>>24]^l[d>>>16&255]^c[v>>>8&255]^h[m&255]^e[++E],d=y,v=b,m=w;n[0]=p[d>>>24]<<24^p[v>>>16&255]<<16^p[m>>>8&255]<<8^p[g&255]^e[++E],n[s?3:1]=p[v>>>24]<<24^p[m>>>16&255]<<16^p[g>>>8&255]<<8^p[d&255]^e[++E],n[2]=p[m>>>24]<<24^p[g>>>16&255]<<16^p[d>>>8&255]<<8^p[v&255]^e[++E],n[s?1:3]=p[g>>>24]<<24^p[d>>>16&255]<<16^p[v>>>8&255]<<8^p[m&255]^e[++E]},c=function(r,i,s,o,u){function C(){if(o)for(var e=0;e<n;++e)E[e]=b.getInt32();else for(var e=0;e<n;++e)E[e]=x[e]^b.getInt32();l(g,E,S,o);if(o){for(var e=0;e<n;++e)w.putInt32(x[e]^S[e]);x=E.slice(0)}else{for(var e=0;e<n;++e)w.putInt32(S[e]);x=S}}function k(){l(g,E,S,!1);for(var e=0;e<n;++e)E[e]=b.getInt32();for(var e=0;e<n;++e){var t=E[e]^S[e];o||(E[e]=t),w.putInt32(t)}}function L(){l(g,E,S,!1);for(var e=0;e<n;++e)E[e]=b.getInt32();for(var e=0;e<n;++e)w.putInt32(E[e]^S[e]),E[e]=S[e]}function A(){l(g,E,S,!1);for(var e=n-1;e>=0;--e){if(E[e]!==4294967295){++E[e];break}E[e]=0}for(var e=0;e<n;++e)w.putInt32(b.getInt32()^S[e])}var c=null;t||a(),u=(u||"CBC").toUpperCase();if(typeof r!="string"||r.length!==16&&r.length!==24&&r.length!==32){if(e.util.isArray(r)&&(r.length===16||r.length===24||r.length===32)){var h=r,r=e.util.createBuffer();for(var p=0;p<h.length;++p)r.putByte(h[p])}}else r=e.util.createBuffer(r);if(!e.util.isArray(r)){var h=r;r=[];var d=h.length();if(d===16||d===24||d===32){d>>>=2;for(var p=0;p<d;++p)r.push(h.getInt32())}}if(!e.util.isArray(r)||r.length!==4&&r.length!==6&&r.length!==8)return c;var v=["CFB","OFB","CTR"].indexOf(u)!==-1,m=u==="CBC",g=f(r,o&&!v),y=n<<2,b,w,E,S,x,T,N;c={output:null};if(u==="CBC")N=C;else if(u==="CFB")N=k;else if(u==="OFB")N=L;else{if(u!=="CTR")throw{message:'Unsupported block cipher mode of operation: "'+u+'"'};N=A}return c.update=function(e){T||b.putBuffer(e);while(b.length()>=y||b.length()>0&&T)N()},c.finish=function(e){var t=!0,r=b.length()%y;if(!o)if(e)t=e(y,b,o);else if(m){var i=b.length()===y?y:y-b.length();b.fillWithByte(i,i)}t&&(T=!0,c.update());if(o){m&&(t=r===0);if(t)if(e)t=e(y,w,o);else if(m){var s=w.length(),u=w.at(s-1);u>n<<2?t=!1:w.truncate(u)}}return!m&&!e&&r>0&&w.truncate(y-r),t},c.start=function(t,r){t===null&&(t=x.slice(0));if(typeof t=="string"&&t.length===16)t=e.util.createBuffer(t);else if(e.util.isArray(t)&&t.length===16){var i=t,t=e.util.createBuffer();for(var s=0;s<16;++s)t.putByte(i[s])}if(!e.util.isArray(t)){var i=t;t=new Array(4),t[0]=i.getInt32(),t[1]=i.getInt32(),t[2]=i.getInt32(),t[3]=i.getInt32()}b=e.util.createBuffer(),w=r||e.util.createBuffer(),x=t.slice(0),E=new Array(n),S=new Array(n),T=!1,c.output=w;if(["CFB","OFB","CTR"].indexOf(u)!==-1){for(var s=0;s<n;++s)E[s]=x[s];x=null}},i!==null&&c.start(i,s),c};e.aes=e.aes||{},e.aes.startEncrypting=function(e,t,n,r){return c(e,t,n,!1,r)},e.aes.createEncryptionCipher=function(e,t){return c(e,null,null,!1,t)},e.aes.startDecrypting=function(e,t,n,r){return c(e,t,n,!0,r)},e.aes.createDecryptionCipher=function(e,t){return c(e,null,null,!0,t)},e.aes._expandKey=function(e,n){return t||a(),f(e,n)},e.aes._updateBlock=l}var t="aes";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/aes",["require","module","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){var t=typeof process!="undefined"&&process.versions&&process.versions.node,n=null;!e.disableNativeCode&&t&&(n=require("crypto"));var r=e.prng=e.prng||{};r.create=function(t){function u(e){if(r.pools[0].messageLength>=32)return f(),e();var t=32-r.pools[0].messageLength<<5;r.seedFile(t,function(t,n){if(t)return e(t);r.collect(n),f(),e()})}function a(){if(r.pools[0].messageLength>=32)return f();var e=32-r.pools[0].messageLength<<5;r.collect(r.seedFileSync(e)),f()}function f(){var t=e.md.sha1.create();t.update(r.pools[0].digest().getBytes()),r.pools[0].start();var n=1;for(var i=1;i<32;++i)n=n===31?2147483648:n<<2,n%r.reseeds===0&&(t.update(r.pools[i].digest().getBytes()),r.pools[i].start());var s=t.digest().getBytes();t.start(),t.update(s);var o=t.digest().getBytes();r.key=r.plugin.formatKey(s),r.seed=r.plugin.formatSeed(o),++r.reseeds,r.generated=0,r.time=+(new Date)}function l(t){var n=e.util.createBuffer();if(typeof window!="undefined"&&window.crypto&&window.crypto.getRandomValues){var r=new Uint32Array(t/4);try{window.crypto.getRandomValues(r);for(var i=0;i<r.length;++i)n.putInt32(r[i])}catch(s){}}if(n.length()<t){var o,u,a,f=Math.floor(Math.random()*65535);while(n.length()<t){u=16807*(f&65535),o=16807*(f>>16),u+=(o&32767)<<16,u+=o>>15,u=(u&2147483647)+(u>>31),f=u&4294967295;for(var i=0;i<3;++i)a=f>>>(i<<3),a^=Math.floor(Math.random()*255),n.putByte(String.fromCharCode(a&255))}}return n.getBytes()}var r={plugin:t,key:null,seed:null,time:null,reseeds:0,generated:0},i=t.md,s=new Array(32);for(var o=0;o<32;++o)s[o]=i.create();return r.pools=s,r.pool=0,r.generate=function(t,n){function l(c){if(c)return n(c);if(f.length()>=t)return n(null,f.getBytes(t));if(r.generated>=1048576){var h=+(new Date);if(r.time===null||h-r.time>100)r.key=null}if(r.key===null)return u(l);var p=i(r.key,r.seed);r.generated+=p.length,f.putBytes(p),r.key=o(i(r.key,s(r.seed))),r.seed=a(i(r.key,r.seed)),e.util.setImmediate(l)}if(!n)return r.generateSync(t);var i=r.plugin.cipher,s=r.plugin.increment,o=r.plugin.formatKey,a=r.plugin.formatSeed,f=e.util.createBuffer();l()},r.generateSync=function(t){var n=r.plugin.cipher,i=r.plugin.increment,s=r.plugin.formatKey,o=r.plugin.formatSeed,u=e.util.createBuffer();while(u.length()<t){if(r.generated>=1048576){var f=+(new Date);if(r.time===null||f-r.time>100)r.key=null}r.key===null&&a();var l=n(r.key,r.seed);r.generated+=l.length,u.putBytes(l),r.key=s(n(r.key,i(r.seed))),r.seed=o(n(r.key,r.seed))}return u.getBytes(t)},n?(r.seedFile=function(e,t){n.randomBytes(e,function(e,n){if(e)return t(e);t(null,n.toString())})},r.seedFileSync=function(e){return n.randomBytes(e).toString()}):(r.seedFile=function(e,t){try{t(null,l(e))}catch(n){t(n)}},r.seedFileSync=l),r.collect=function(e){var t=e.length;for(var n=0;n<t;++n)r.pools[r.pool].update(e.substr(n,1)),r.pool=r.pool===31?0:r.pool+1},r.collectInt=function(e,t){var n="";for(var i=0;i<t;i+=8)n+=String.fromCharCode(e>>i&255);r.collect(n)},r.registerWorker=function(e){if(e===self)r.seedFile=function(e,t){function n(e){var r=e.data;r.forge&&r.forge.prng&&(self.removeEventListener("message",n),t(r.forge.prng.err,r.forge.prng.bytes))}self.addEventListener("message",n),self.postMessage({forge:{prng:{needed:e}}})};else{function t(t){var n=t.data;n.forge&&n.forge.prng&&r.seedFile(n.forge.prng.needed,function(t,n){e.postMessage({forge:{prng:{err:t,bytes:n}}})})}e.addEventListener("message",t)}},r}}var t="prng";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/prng",["require","module","./md","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){if(e.random&&e.random.getBytes)return;(function(t){var n={},r=new Array(4),i=e.util.createBuffer();n.formatKey=function(t){var n=e.util.createBuffer(t);return t=new Array(4),t[0]=n.getInt32(),t[1]=n.getInt32(),t[2]=n.getInt32(),t[3]=n.getInt32(),e.aes._expandKey(t,!1)},n.formatSeed=function(t){var n=e.util.createBuffer(t);return t=new Array(4),t[0]=n.getInt32(),t[1]=n.getInt32(),t[2]=n.getInt32(),t[3]=n.getInt32(),t},n.cipher=function(t,n){return e.aes._updateBlock(t,n,r,!1),i.putInt32(r[0]),i.putInt32(r[1]),i.putInt32(r[2]),i.putInt32(r[3]),i.getBytes()},n.increment=function(e){return++e[3],e},n.md=e.md.sha1;var s=e.prng.create(n),o=typeof process!="undefined"&&process.versions&&process.versions.node;if(e.disableNativeCode||!o&&(typeof window=="undefined"||!window.crypto||!window.crypto.getRandomValues)){typeof window=="undefined"||window.document===undefined,s.collectInt(+(new Date),32);if(typeof navigator!="undefined"){var u="";for(var a in navigator)try{typeof navigator[a]=="string"&&(u+=navigator[a])}catch(f){}s.collect(u),u=null}t&&(t().mousemove(function(e){s.collectInt(e.clientX,16),s.collectInt(e.clientY,16)}),t().keypress(function(e){s.collectInt(e.charCode,8)}))}if(!e.random)e.random=s;else for(var a in s)e.random[a]=s[a];e.random.getBytes=function(t,n){return e.random.generate(t,n)},e.random.getBytesSync=function(t){return e.random.generate(t)}})(typeof jQuery!="undefined"?jQuery:null)}var t="random";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/random",["require","module","./aes","./md","./prng","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e,t,n){var r=t();describe("random",function(){it("should generate 10 random bytes",function(){r.getBytes(16),r.getBytes(24),r.getBytes(32);var t=r.getBytes(10);e.equal(t.length,10)}),it("should use a synchronous seed file",function(){var r=t();r.seedFileSync=function(e){return n.fillString("a",e)};var i=r.getBytes(10);e.equal(n.bytesToHex(i),"a44857544b3df0fcac84")}),it("should use an asynchronous seed file",function(r){var i=t();i.seedFile=function(e,t){t(null,n.fillString("a",e))},i.getBytes(10,function(t,i){e.equal(t,null),e.equal(n.bytesToHex(i),"a44857544b3df0fcac84"),r()})}),it("should collect some random bytes",function(){var r=t();r.seedFileSync=function(e){return n.fillString("a",e)},r.collect("bbb");var i=r.getBytes(10);e.equal(n.bytesToHex(i),"8274fa6e0a192d670ddb")})})}typeof define=="function"?define("test/random",["forge/random","forge/util"],function(t,n){e(ASSERT,t,n())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/random"),require("../../js/util")())}(),function(){function e(e){e.pki=e.pki||{};var t=e.pki.oids=e.oids=e.oids||{};t["1.2.840.113549.1.1.1"]="rsaEncryption",t.rsaEncryption="1.2.840.113549.1.1.1",t["1.2.840.113549.1.1.4"]="md5WithRSAEncryption",t.md5WithRSAEncryption="1.2.840.113549.1.1.4",t["1.2.840.113549.1.1.5"]="sha1WithRSAEncryption",t.sha1WithRSAEncryption="1.2.840.113549.1.1.5",t["1.2.840.113549.1.1.7"]="RSAES-OAEP",t["RSAES-OAEP"]="1.2.840.113549.1.1.7",t["1.2.840.113549.1.1.8"]="mgf1",t.mgf1="1.2.840.113549.1.1.8",t["1.2.840.113549.1.1.9"]="pSpecified",t.pSpecified="1.2.840.113549.1.1.9",t["1.2.840.113549.1.1.10"]="RSASSA-PSS",t["RSASSA-PSS"]="1.2.840.113549.1.1.10",t["1.2.840.113549.1.1.11"]="sha256WithRSAEncryption",t.sha256WithRSAEncryption="1.2.840.113549.1.1.11",t["1.2.840.113549.1.1.12"]="sha384WithRSAEncryption",t.sha384WithRSAEncryption="1.2.840.113549.1.1.12",t["1.2.840.113549.1.1.13"]="sha512WithRSAEncryption",t.sha512WithRSAEncryption="1.2.840.113549.1.1.13",t["1.3.14.3.2.26"]="sha1",t.sha1="1.3.14.3.2.26",t["2.16.840.1.101.3.4.2.1"]="sha256",t.sha256="2.16.840.1.101.3.4.2.1",t["2.16.840.1.101.3.4.2.2"]="sha384",t.sha384="2.16.840.1.101.3.4.2.2",t["2.16.840.1.101.3.4.2.3"]="sha512",t.sha512="2.16.840.1.101.3.4.2.3",t["1.2.840.113549.2.5"]="md5",t.md5="1.2.840.113549.2.5",t["1.2.840.113549.1.7.1"]="data",t.data="1.2.840.113549.1.7.1",t["1.2.840.113549.1.7.2"]="signedData",t.signedData="1.2.840.113549.1.7.2",t["1.2.840.113549.1.7.3"]="envelopedData",t.envelopedData="1.2.840.113549.1.7.3",t["1.2.840.113549.1.7.4"]="signedAndEnvelopedData",t.signedAndEnvelopedData="1.2.840.113549.1.7.4",t["1.2.840.113549.1.7.5"]="digestedData",t.digestedData="1.2.840.113549.1.7.5",t["1.2.840.113549.1.7.6"]="encryptedData",t.encryptedData="1.2.840.113549.1.7.6",t["1.2.840.113549.1.9.1"]="emailAddress",t.emailAddress="1.2.840.113549.1.9.1",t["1.2.840.113549.1.9.2"]="unstructuredName",t.unstructuredName="1.2.840.113549.1.9.2",t["1.2.840.113549.1.9.3"]="contentType",t.contentType="1.2.840.113549.1.9.3",t["1.2.840.113549.1.9.4"]="messageDigest",t.messageDigest="1.2.840.113549.1.9.4",t["1.2.840.113549.1.9.5"]="signingTime",t.signingTime="1.2.840.113549.1.9.5",t["1.2.840.113549.1.9.6"]="counterSignature",t.counterSignature="1.2.840.113549.1.9.6",t["1.2.840.113549.1.9.7"]="challengePassword",t.challengePassword="1.2.840.113549.1.9.7",t["1.2.840.113549.1.9.8"]="unstructuredAddress",t.unstructuredAddress="1.2.840.113549.1.9.8",t["1.2.840.113549.1.9.20"]="friendlyName",t.friendlyName="1.2.840.113549.1.9.20",t["1.2.840.113549.1.9.21"]="localKeyId",t.localKeyId="1.2.840.113549.1.9.21",t["1.2.840.113549.1.9.22.1"]="x509Certificate",t.x509Certificate="1.2.840.113549.1.9.22.1",t["1.2.840.113549.1.12.10.1.1"]="keyBag",t.keyBag="1.2.840.113549.1.12.10.1.1",t["1.2.840.113549.1.12.10.1.2"]="pkcs8ShroudedKeyBag",t.pkcs8ShroudedKeyBag="1.2.840.113549.1.12.10.1.2",t["1.2.840.113549.1.12.10.1.3"]="certBag",t.certBag="1.2.840.113549.1.12.10.1.3",t["1.2.840.113549.1.12.10.1.4"]="crlBag",t.crlBag="1.2.840.113549.1.12.10.1.4",t["1.2.840.113549.1.12.10.1.5"]="secretBag",t.secretBag="1.2.840.113549.1.12.10.1.5",t["1.2.840.113549.1.12.10.1.6"]="safeContentsBag",t.safeContentsBag="1.2.840.113549.1.12.10.1.6",t["1.2.840.113549.1.5.13"]="pkcs5PBES2",t.pkcs5PBES2="1.2.840.113549.1.5.13",t["1.2.840.113549.1.5.12"]="pkcs5PBKDF2",t.pkcs5PBKDF2="1.2.840.113549.1.5.12",t["1.2.840.113549.1.12.1.1"]="pbeWithSHAAnd128BitRC4",t.pbeWithSHAAnd128BitRC4="1.2.840.113549.1.12.1.1",t["1.2.840.113549.1.12.1.2"]="pbeWithSHAAnd40BitRC4",t.pbeWithSHAAnd40BitRC4="1.2.840.113549.1.12.1.2",t["1.2.840.113549.1.12.1.3"]="pbeWithSHAAnd3-KeyTripleDES-CBC",t["pbeWithSHAAnd3-KeyTripleDES-CBC"]="1.2.840.113549.1.12.1.3",t["1.2.840.113549.1.12.1.4"]="pbeWithSHAAnd2-KeyTripleDES-CBC",t["pbeWithSHAAnd2-KeyTripleDES-CBC"]="1.2.840.113549.1.12.1.4",t["1.2.840.113549.1.12.1.5"]="pbeWithSHAAnd128BitRC2-CBC",t["pbeWithSHAAnd128BitRC2-CBC"]="1.2.840.113549.1.12.1.5",t["1.2.840.113549.1.12.1.6"]="pbewithSHAAnd40BitRC2-CBC",t["pbewithSHAAnd40BitRC2-CBC"]="1.2.840.113549.1.12.1.6",t["1.2.840.113549.3.7"]="des-EDE3-CBC",t["des-EDE3-CBC"]="1.2.840.113549.3.7",t["2.16.840.1.101.3.4.1.2"]="aes128-CBC",t["aes128-CBC"]="2.16.840.1.101.3.4.1.2",t["2.16.840.1.101.3.4.1.22"]="aes192-CBC",t["aes192-CBC"]="2.16.840.1.101.3.4.1.22",t["2.16.840.1.101.3.4.1.42"]="aes256-CBC",t["aes256-CBC"]="2.16.840.1.101.3.4.1.42",t["2.5.4.3"]="commonName",t.commonName="2.5.4.3",t["2.5.4.5"]="serialName",t.serialName="2.5.4.5",t["2.5.4.6"]="countryName",t.countryName="2.5.4.6",t["2.5.4.7"]="localityName",t.localityName="2.5.4.7",t["2.5.4.8"]="stateOrProvinceName",t.stateOrProvinceName="2.5.4.8",t["2.5.4.10"]="organizationName",t.organizationName="2.5.4.10",t["2.5.4.11"]="organizationalUnitName",t.organizationalUnitName="2.5.4.11",t["2.16.840.1.113730.1.1"]="nsCertType",t.nsCertType="2.16.840.1.113730.1.1",t["2.5.29.1"]="authorityKeyIdentifier",t["2.5.29.2"]="keyAttributes",t["2.5.29.3"]="certificatePolicies",t["2.5.29.4"]="keyUsageRestriction",t["2.5.29.5"]="policyMapping",t["2.5.29.6"]="subtreesConstraint",t["2.5.29.7"]="subjectAltName",t["2.5.29.8"]="issuerAltName",t["2.5.29.9"]="subjectDirectoryAttributes",t["2.5.29.10"]="basicConstraints",t["2.5.29.11"]="nameConstraints",t["2.5.29.12"]="policyConstraints",t["2.5.29.13"]="basicConstraints",t["2.5.29.14"]="subjectKeyIdentifier",t.subjectKeyIdentifier="2.5.29.14",t["2.5.29.15"]="keyUsage",t.keyUsage="2.5.29.15",t["2.5.29.16"]="privateKeyUsagePeriod",t["2.5.29.17"]="subjectAltName",t.subjectAltName="2.5.29.17",t["2.5.29.18"]="issuerAltName",t.issuerAltName="2.5.29.18",t["2.5.29.19"]="basicConstraints",t.basicConstraints="2.5.29.19",t["2.5.29.20"]="cRLNumber",t["2.5.29.21"]="cRLReason",t["2.5.29.22"]="expirationDate",t["2.5.29.23"]="instructionCode",t["2.5.29.24"]="invalidityDate",t["2.5.29.25"]="cRLDistributionPoints",t["2.5.29.26"]="issuingDistributionPoint",t["2.5.29.27"]="deltaCRLIndicator",t["2.5.29.28"]="issuingDistributionPoint",t["2.5.29.29"]="certificateIssuer",t["2.5.29.30"]="nameConstraints",t["2.5.29.31"]="cRLDistributionPoints",t["2.5.29.32"]="certificatePolicies",t["2.5.29.33"]="policyMappings",t["2.5.29.34"]="policyConstraints",t["2.5.29.35"]="authorityKeyIdentifier",t["2.5.29.36"]="policyConstraints",t["2.5.29.37"]="extKeyUsage",t.extKeyUsage="2.5.29.37",t["2.5.29.46"]="freshestCRL",t["2.5.29.54"]="inhibitAnyPolicy",t["1.3.6.1.5.5.7.3.1"]="serverAuth",t.serverAuth="1.3.6.1.5.5.7.3.1",t["1.3.6.1.5.5.7.3.2"]="clientAuth",t.clientAuth="1.3.6.1.5.5.7.3.2",t["1.3.6.1.5.5.7.3.3"]="codeSigning",t.codeSigning="1.3.6.1.5.5.7.3.3",t["1.3.6.1.5.5.7.3.4"]="emailProtection",t.emailProtection="1.3.6.1.5.5.7.3.4",t["1.3.6.1.5.5.7.3.8"]="timeStamping",t.timeStamping="1.3.6.1.5.5.7.3.8"}var t="oids";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/oids",["require","module"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){var t=e.asn1=e.asn1||{};t.Class={UNIVERSAL:0,APPLICATION:64,CONTEXT_SPECIFIC:128,PRIVATE:192},t.Type={NONE:0,BOOLEAN:1,INTEGER:2,BITSTRING:3,OCTETSTRING:4,NULL:5,OID:6,ODESC:7,EXTERNAL:8,REAL:9,ENUMERATED:10,EMBEDDED:11,UTF8:12,ROID:13,SEQUENCE:16,SET:17,PRINTABLESTRING:19,IA5STRING:22,UTCTIME:23,GENERALIZEDTIME:24,BMPSTRING:30},t.create=function(t,n,r,i){if(e.util.isArray(i)){var s=[];for(var o=0;o<i.length;++o)i[o]!==undefined&&s.push(i[o]);i=s}return{tagClass:t,type:n,constructed:r,composed:r||e.util.isArray(i),value:i}};var n=function(e){var t=e.getByte();if(t===128)return undefined;var n,r=t&128;return r?n=e.getInt((t&127)<<3):n=t,n};t.fromDer=function(r,i){i===undefined&&(i=!0),typeof r=="string"&&(r=e.util.createBuffer(r));if(r.length()<2)throw{message:"Too few bytes to parse DER.",bytes:r.length()};var s=r.getByte(),o=s&192,u=s&31,a=n(r);if(r.length()<a){if(i)throw{message:"Too few bytes to read ASN.1 value.",detail:r.length()+" < "+a};a=r.length()}var f,l=(s&32)===32,c=l;if(!c&&o===t.Class.UNIVERSAL&&u===t.Type.BITSTRING&&a>1){var h=r.read,p=r.getByte();if(p===0){s=r.getByte();var d=s&192;if(d===t.Class.UNIVERSAL||d===t.Class.CONTEXT_SPECIFIC)try{var v=n(r);c=v===a-(r.read-h),c&&(++h,--a)}catch(m){}}r.read=h}if(c){f=[];if(a===undefined)for(;;){if(r.bytes(2)===String.fromCharCode(0,0)){r.getBytes(2);break}f.push(t.fromDer(r,i))}else{var g=r.length();while(a>0)f.push(t.fromDer(r,i)),a-=g-r.length(),g=r.length()}}else{if(a===undefined)throw{message:"Non-constructed ASN.1 object of indefinite length."};if(u===t.Type.BMPSTRING){f="";for(var y=0;y<a;y+=2)f+=String.fromCharCode(r.getInt16())}else f=r.getBytes(a)}return t.create(o,u,l,f)},t.toDer=function(n){var r=e.util.createBuffer(),i=n.tagClass|n.type,s=e.util.createBuffer();if(n.composed){n.constructed?i|=32:s.putByte(0);for(var o=0;o<n.value.length;++o)n.value[o]!==undefined&&s.putBuffer(t.toDer(n.value[o]))}else if(n.type===t.Type.BMPSTRING)for(var o=0;o<n.value.length;++o)s.putInt16(n.value.charCodeAt(o));else s.putBytes(n.value);r.putByte(i);if(s.length()<=127)r.putByte(s.length()&127);else{var u=s.length(),a="";do a+=String.fromCharCode(u&255),u>>>=8;while(u>0);r.putByte(a.length|128);for(var o=a.length-1;o>=0;--o)r.putByte(a.charCodeAt(o))}return r.putBuffer(s),r},t.oidToDer=function(t){var n=t.split("."),r=e.util.createBuffer();r.putByte(40*parseInt(n[0],10)+parseInt(n[1],10));var i,s,o,u;for(var a=2;a<n.length;++a){i=!0,s=[],o=parseInt(n[a],10);do u=o&127,o>>>=7,i||(u|=128),s.push(u),i=!1;while(o>0);for(var f=s.length-1;f>=0;--f)r.putByte(s[f])}return r},t.derToOid=function(t){var n;typeof t=="string"&&(t=e.util.createBuffer(t));var r=t.getByte();n=Math.floor(r/40)+"."+r%40;var i=0;while(t.length()>0)r=t.getByte(),i<<=7,r&128?i+=r&127:(n+="."+(i+r),i=0);return n},t.utcTimeToDate=function(e){var t=new Date,n=parseInt(e.substr(0,2),10);n=n>=50?1900+n:2e3+n;var r=parseInt(e.substr(2,2),10)-1,i=parseInt(e.substr(4,2),10),s=parseInt(e.substr(6,2),10),o=parseInt(e.substr(8,2),10),u=0;if(e.length>11){var a=e.charAt(10),f=10;a!=="+"&&a!=="-"&&(u=parseInt(e.substr(10,2),10),f+=2)}t.setUTCFullYear(n,r,i),t.setUTCHours(s,o,u,0);if(f){a=e.charAt(f);if(a==="+"||a==="-"){var l=parseInt(e.substr(f+1,2),10),c=parseInt(e.substr(f+4,2),10),h=l*60+c;h*=6e4,a==="+"?t.setTime(+t-h):t.setTime(+t+h)}}return t},t.generalizedTimeToDate=function(e){var t=new Date,n=parseInt(e.substr(0,4),10),r=parseInt(e.substr(4,2),10)-1,i=parseInt(e.substr(6,2),10),s=parseInt(e.substr(8,2),10),o=parseInt(e.substr(10,2),10),u=parseInt(e.substr(12,2),10),a=0,f=0,l=!1;e.charAt(e.length-1)==="Z"&&(l=!0);var c=e.length-5,h=e.charAt(c);if(h==="+"||h==="-"){var p=parseInt(e.substr(c+1,2),10),d=parseInt(e.substr(c+4,2),10);f=p*60+d,f*=6e4,h==="+"&&(f*=-1),l=!0}return e.charAt(14)==="."&&(a=parseFloat(e.substr(14),10)*1e3),l?(t.setUTCFullYear(n,r,i),t.setUTCHours(s,o,u,a),t.setTime(+t+f)):(t.setFullYear(n,r,i),t.setHours(s,o,u,a)),t},t.dateToUtcTime=function(e){var t="",n=[];n.push((""+e.getUTCFullYear()).substr(2)),n.push(""+(e.getUTCMonth()+1)),n.push(""+e.getUTCDate()),n.push(""+e.getUTCHours()),n.push(""+e.getUTCMinutes()),n.push(""+e.getUTCSeconds());for(var r=0;r<n.length;++r)n[r].length<2&&(t+="0"),t+=n[r];return t+="Z",t},t.validate=function(n,r,i,s){var o=!1;if(n.tagClass!==r.tagClass&&typeof r.tagClass!="undefined"||n.type!==r.type&&typeof r.type!="undefined")s&&(n.tagClass!==r.tagClass&&s.push("["+r.name+"] "+'Expected tag class "'+r.tagClass+'", got "'+n.tagClass+'"'),n.type!==r.type&&s.push("["+r.name+"] "+'Expected type "'+r.type+'", got "'+n.type+'"'));else if(n.constructed===r.constructed||typeof r.constructed=="undefined"){o=!0;if(r.value&&e.util.isArray(r.value)){var u=0;for(var a=0;o&&a<r.value.length;++a)o=r.value[a].optional||!1,n.value[u]&&(o=t.validate(n.value[u],r.value[a],i,s),o?++u:r.value[a].optional&&(o=!0)),!o&&s&&s.push("["+r.name+"] "+'Tag class "'+r.tagClass+'", type "'+r.type+'" expected value length "'+r.value.length+'", got "'+n.value.length+'"')}o&&i&&(r.capture&&(i[r.capture]=n.value),r.captureAsn1&&(i[r.captureAsn1]=n))}else s&&s.push("["+r.name+"] "+'Expected constructed "'+r.constructed+'", got "'+n.constructed+'"');return o};var r=/[^\\u0000-\\u00ff]/;t.prettyPrint=function(n,i,s){var o="";i=i||0,s=s||2,i>0&&(o+="\n");var u="";for(var a=0;a<i*s;++a)u+=" ";o+=u+"Tag: ";switch(n.tagClass){case t.Class.UNIVERSAL:o+="Universal:";break;case t.Class.APPLICATION:o+="Application:";break;case t.Class.CONTEXT_SPECIFIC:o+="Context-Specific:";break;case t.Class.PRIVATE:o+="Private:"}if(n.tagClass===t.Class.UNIVERSAL){o+=n.type;switch(n.type){case t.Type.NONE:o+=" (None)";break;case t.Type.BOOLEAN:o+=" (Boolean)";break;case t.Type.BITSTRING:o+=" (Bit string)";break;case t.Type.INTEGER:o+=" (Integer)";break;case t.Type.OCTETSTRING:o+=" (Octet string)";break;case t.Type.NULL:o+=" (Null)";break;case t.Type.OID:o+=" (Object Identifier)";break;case t.Type.ODESC:o+=" (Object Descriptor)";break;case t.Type.EXTERNAL:o+=" (External or Instance of)";break;case t.Type.REAL:o+=" (Real)";break;case t.Type.ENUMERATED:o+=" (Enumerated)";break;case t.Type.EMBEDDED:o+=" (Embedded PDV)";break;case t.Type.UTF8:o+=" (UTF8)";break;case t.Type.ROID:o+=" (Relative Object Identifier)";break;case t.Type.SEQUENCE:o+=" (Sequence)";break;case t.Type.SET:o+=" (Set)";break;case t.Type.PRINTABLESTRING:o+=" (Printable String)";break;case t.Type.IA5String:o+=" (IA5String (ASCII))";break;case t.Type.UTCTIME:o+=" (UTC time)";break;case t.Type.GENERALIZEDTIME:o+=" (Generalized time)";break;case t.Type.BMPSTRING:o+=" (BMP String)"}}else o+=n.type;o+="\n",o+=u+"Constructed: "+n.constructed+"\n";if(n.composed){var f=0,l="";for(var a=0;a<n.value.length;++a)n.value[a]!==undefined&&(f+=1,l+=t.prettyPrint(n.value[a],i+1,s),a+1<n.value.length&&(l+=","));o+=u+"Sub values: "+f+l}else{o+=u+"Value: ";if(n.type===t.Type.OID){var c=t.derToOid(n.value);o+=c,e.pki&&e.pki.oids&&c in e.pki.oids&&(o+=" ("+e.pki.oids[c]+")")}else r.test(n.value)?o+="0x"+e.util.createBuffer(n.value,"utf8").toHex():n.value.length===0?o+="[null]":o+=n.value}return o}}var t="asn1";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/asn1",["require","module","./util","./oids"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e,t,n){describe("asn1",function(){it("should convert an OID to DER",function(){e.equal(t.oidToDer("1.2.840.113549").toHex(),"2a864886f70d")}),it("should convert an OID from DER",function(){var r=n.hexToBytes("2a864886f70d");e.equal(t.derToOid(r),"1.2.840.113549")}),function(){var n=[{"in":"20110223123400",out:129846444e4},{"in":"20110223123400.1",out:1298464440100},{"in":"20110223123400.123",out:1298464440123}];for(var r=0;r<n.length;++r){var i=n[r];it('should convert local generalized time "'+i.in+'" to a Date',function(){var n=t.generalizedTimeToDate(i.in),r=n.getTimezoneOffset()*6e4;e.equal(n.getTime(),i.out+r)})}}(),function(){var n=[{"in":"20110223123400Z",out:129846444e4},{"in":"20110223123400.1Z",out:1298464440100},{"in":"20110223123400.123Z",out:1298464440123},{"in":"20110223123400+0200",out:129845724e4},{"in":"20110223123400.1+0200",out:1298457240100},{"in":"20110223123400.123+0200",out:1298457240123},{"in":"20110223123400-0200",out:129847164e4},{"in":"20110223123400.1-0200",out:1298471640100},{"in":"20110223123400.123-0200",out:1298471640123}];for(var r=0;r<n.length;++r){var i=n[r];it('should convert utc generalized time "'+i.in+'" to a Date',function(){var n=t.generalizedTimeToDate(i.in);e.equal(n.getTime(),i.out)})}}(),function(){var n=[{"in":"1102231234Z",out:129846444e4},{"in":"1102231234+0200",out:129845724e4},{"in":"1102231234-0200",out:129847164e4},{"in":"110223123456Z",out:1298464496e3},{"in":"110223123456+0200",out:1298457296e3},{"in":"110223123456-0200",out:1298471696e3}];for(var r=0;r<n.length;++r){var i=n[r];it('should convert utc time "'+i.in+'" to a Date',function(){var n=t.utcTimeToDate(i.in);e.equal(n.getTime(),i.out)})}}()})}typeof define=="function"?define("test/asn1",["forge/asn1","forge/util"],function(t,n){e(ASSERT,t(),n())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/asn1")(),require("../../js/util")())}(),function(){function e(e){function n(e){var t=e.name+": ",n=[];for(var r=0;r<e.values.length;++r)n.push(e.values[r].replace(/^(\S+\r\n)/,function(e,t){return" "+t}));t+=n.join(",")+"\r\n";var i=0,s=-1;for(var r=0;r<t.length;++r,++i)if(i>65&&s!==-1){var o=t[s];o===","?(++s,t=t.substr(0,s)+"\r\n "+t.substr(s)):t=t.substr(0,s)+"\r\n"+o+t.substr(s+1),i=r-s-1,s=-1,++r}else if(t[r]===" "||t[r]==="	"||t[r]===",")s=r;return t}function r(e){return e.replace(/^\s+/,"")}var t=e.pem=e.pem||{};t.encode=function(t,r){r=r||{};var i="-----BEGIN "+t.type+"-----\r\n",s;t.procType&&(s={name:"Proc-Type",values:[String(t.procType.version),t.procType.type]},i+=n(s)),t.contentDomain&&(s={name:"Content-Domain",values:[t.contentDomain]},i+=n(s)),t.dekInfo&&(s={name:"DEK-Info",values:[t.dekInfo.algorithm]},t.dekInfo.parameters&&s.values.push(t.dekInfo.parameters),i+=n(s));if(t.headers)for(var o=0;o<t.headers.length;++o)i+=n(t.headers[o]);return t.procType&&(i+="\r\n"),i+=e.util.encode64(t.body,r.maxline||64)+"\r\n",i+="-----END "+t.type+"-----\r\n",i},t.decode=function(t){var n=[],i=/\s*-----BEGIN ([A-Z0-9- ]+)-----\r?\n([\x21-\x7e\s]+?(?:\r?\n\r?\n))?([:A-Za-z0-9+\/=\s]+?)-----END \1-----/g,s=/([\x21-\x7e]+):\s*([\x21-\x7e\s^:]+)/,o=/\r?\n/,u;for(;;){u=i.exec(t);if(!u)break;var a={type:u[1],procType:null,contentDomain:null,dekInfo:null,headers:[],body:e.util.decode64(u[3])};n.push(a);if(!u[2])continue;var f=u[2].split(o),l=0;while(u&&l<f.length){var c=f[l].replace(/\s+$/,"");for(var h=l+1;h<f.length;++h){var p=f[h];if(!/\s/.test(p[0]))break;c+=p,l=h}u=c.match(s);if(u){var d={name:u[1],values:[]},v=u[2].split(",");for(var m=0;m<v.length;++m)d.values.push(r(v[m]));if(!a.procType){if(d.name!=="Proc-Type")throw{message:'Invalid PEM formatted message. The first encapsulated header must be "Proc-Type".'};if(d.values.length!==2)throw{message:'Invalid PEM formatted message. The "Proc-Type" header must have two subfields.'};a.procType={version:v[0],type:v[1]}}else if(!a.contentDomain&&d.name==="Content-Domain")a.contentDomain=v[0]||"";else if(!a.dekInfo&&d.name==="DEK-Info"){if(d.values.length===0)throw{message:'Invalid PEM formatted message. The "DEK-Info" header must have at least one subfield.'};a.dekInfo={algorithm:v[0],parameters:v[1]||null}}else a.headers.push(d)}++l}if(a.procType==="ENCRYPTED"&&!a.dekInfo)throw{message:'Invalid PEM formatted message. The "DEK-Info" header must be present if "Proc-Type" is "ENCRYPTED".'}}if(n.length===0)throw{message:"Invalid PEM formatted message."};return n}}var t="pem";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/pem",["require","module","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e,t){var n="-----BEGIN PRIVACY-ENHANCED MESSAGE-----\r\nProc-Type: 4,ENCRYPTED\r\nContent-Domain: RFC822\r\nDEK-Info: DES-CBC,F8143EDE5960C597\r\nOriginator-ID-Symmetric: linn@zendia.enet.dec.com,,\r\nRecipient-ID-Symmetric: linn@zendia.enet.dec.com,ptf-kmc,3\r\nKey-Info: DES-ECB,RSA-MD2,9FD3AAD2F2691B9A,\r\n B70665BB9BF7CBCDA60195DB94F727D3\r\nRecipient-ID-Symmetric: pem-dev@tis.com,ptf-kmc,4\r\nKey-Info: DES-ECB,RSA-MD2,161A3F75DC82EF26,\r\n E2EF532C65CBCFF79F83A2658132DB47\r\n\r\nLLrHB0eJzyhP+/fSStdW8okeEnv47jxe7SJ/iN72ohNcUk2jHEUSoH1nvNSIWL9M\r\n8tEjmF/zxB+bATMtPjCUWbz8Lr9wloXIkjHUlBLpvXR0UrUzYbkNpk0agV2IzUpk\r\nJ6UiRRGcDSvzrsoK+oNvqu6z7Xs5Xfz5rDqUcMlK1Z6720dcBWGGsDLpTpSCnpot\r\ndXd/H5LMDWnonNvPCwQUHg==\r\n-----END PRIVACY-ENHANCED MESSAGE-----\r\n-----BEGIN PRIVACY-ENHANCED MESSAGE-----\r\nProc-Type: 4,ENCRYPTED\r\nContent-Domain: RFC822\r\nDEK-Info: DES-CBC,BFF968AA74691AC1\r\nOriginator-Certificate:\r\n MIIBlTCCAScCAWUwDQYJKoZIhvcNAQECBQAwUTELMAkGA1UEBhMCVVMxIDAeBgNV\r\n BAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMQ8wDQYDVQQLEwZCZXRhIDExDzAN\r\n BgNVBAsTBk5PVEFSWTAeFw05MTA5MDQxODM4MTdaFw05MzA5MDMxODM4MTZaMEUx\r\n CzAJBgNVBAYTAlVTMSAwHgYDVQQKExdSU0EgRGF0YSBTZWN1cml0eSwgSW5jLjEU\r\n MBIGA1UEAxMLVGVzdCBVc2VyIDEwWTAKBgRVCAEBAgICAANLADBIAkEAwHZHl7i+\r\n yJcqDtjJCowzTdBJrdAiLAnSC+CnnjOJELyuQiBgkGrgIh3j8/x0fM+YrsyF1u3F\r\n LZPVtzlndhYFJQIDAQABMA0GCSqGSIb3DQEBAgUAA1kACKr0PqphJYw1j+YPtcIq\r\n iWlFPuN5jJ79Khfg7ASFxskYkEMjRNZV/HZDZQEhtVaU7Jxfzs2wfX5byMp2X3U/\r\n 5XUXGx7qusDgHQGs7Jk9W8CW1fuSWUgN4w==\r\nKey-Info: RSA,\r\n I3rRIGXUGWAF8js5wCzRTkdhO34PTHdRZY9Tuvm03M+NM7fx6qc5udixps2Lng0+\r\n wGrtiUm/ovtKdinz6ZQ/aQ==\r\nIssuer-Certificate:\r\n MIIB3DCCAUgCAQowDQYJKoZIhvcNAQECBQAwTzELMAkGA1UEBhMCVVMxIDAeBgNV\r\n BAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMQ8wDQYDVQQLEwZCZXRhIDExDTAL\r\n BgNVBAsTBFRMQ0EwHhcNOTEwOTAxMDgwMDAwWhcNOTIwOTAxMDc1OTU5WjBRMQsw\r\n CQYDVQQGEwJVUzEgMB4GA1UEChMXUlNBIERhdGEgU2VjdXJpdHksIEluYy4xDzAN\r\n BgNVBAsTBkJldGEgMTEPMA0GA1UECxMGTk9UQVJZMHAwCgYEVQgBAQICArwDYgAw\r\n XwJYCsnp6lQCxYykNlODwutF/jMJ3kL+3PjYyHOwk+/9rLg6X65B/LD4bJHtO5XW\r\n cqAz/7R7XhjYCm0PcqbdzoACZtIlETrKrcJiDYoP+DkZ8k1gCk7hQHpbIwIDAQAB\r\n MA0GCSqGSIb3DQEBAgUAA38AAICPv4f9Gx/tY4+p+4DB7MV+tKZnvBoy8zgoMGOx\r\n dD2jMZ/3HsyWKWgSF0eH/AJB3qr9zosG47pyMnTf3aSy2nBO7CMxpUWRBcXUpE+x\r\n EREZd9++32ofGBIXaialnOgVUn0OzSYgugiQ077nJLDUj0hQehCizEs5wUJ35a5h\r\nMIC-Info: RSA-MD5,RSA,\r\n UdFJR8u/TIGhfH65ieewe2lOW4tooa3vZCvVNGBZirf/7nrgzWDABz8w9NsXSexv\r\n AjRFbHoNPzBuxwmOAFeA0HJszL4yBvhG\r\nRecipient-ID-Asymmetric:\r\n MFExCzAJBgNVBAYTAlVTMSAwHgYDVQQKExdSU0EgRGF0YSBTZWN1cml0eSwgSW5j\r\n LjEPMA0GA1UECxMGQmV0YSAxMQ8wDQYDVQQLEwZOT1RBUlk=,66\r\nKey-Info: RSA,\r\n O6BS1ww9CTyHPtS3bMLD+L0hejdvX6Qv1HK2ds2sQPEaXhX8EhvVphHYTjwekdWv\r\n 7x0Z3Jx2vTAhOYHMcqqCjA==\r\n\r\nqeWlj/YJ2Uf5ng9yznPbtD0mYloSwIuV9FRYx+gzY+8iXd/NQrXHfi6/MhPfPF3d\r\njIqCJAxvld2xgqQimUzoS1a4r7kQQ5c/Iua4LqKeq3ciFzEv/MbZhA==\r\n-----END PRIVACY-ENHANCED MESSAGE-----\r\n-----BEGIN RSA PRIVATE KEY-----\r\nMIIBPAIBAAJBALjXU+IdHkSkdBscgXf+EBoa55ruAIsU50uDFjFBkp+rWFt5AOGF\r\n9xL1/HNIby5M64BCw021nJTZKEOmXKdmzYsCAwEAAQJBAApyYRNOgf9vLAC8Q7T8\r\nbvyKuLxQ50b1D319EywFgLv1Yn0s/F9F+Rew6c04Q0pIqmuOGUM7z94ul/y5OlNJ\r\n2cECIQDveEW1ib2+787l7Y0tMeDzf/HQl4MAWdcxXWOeUFK+7QIhAMWZsukutEn9\r\n9/yqFMt8bL/dclfNn1IAgUL4+dMJ7zdXAiEAhaxGhVKxN28XuCOFhe/s2R/XdQ/O\r\nUZjU1bqCzDGcLvUCIGYmxu71Tg7SVFkyM/3eHPozKOFrU2m5CRnuTHhlMl2RAiEA\r\n0vhM5TEmmNWz0anPVabqDj9TA0z5MsDJQcn5NmO9xnw=\r\n-----END RSA PRIVATE KEY-----\r\n";describe("pem",function(){it("should decode and re-encode PEM messages",function(){var r=t.decode(n),i="";for(var s=0;s<r.length;++s)i+=t.encode(r[s]);e.equal(i,n)})})}typeof define=="function"?define("test/pem",["forge/pem"],function(t){e(ASSERT,t())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/pem")())}(),function(){function e(e){function f(e){var t=[0,4,536870912,536870916,65536,65540,536936448,536936452,512,516,536871424,536871428,66048,66052,536936960,536936964],n=[0,1,1048576,1048577,67108864,67108865,68157440,68157441,256,257,1048832,1048833,67109120,67109121,68157696,68157697],r=[0,8,2048,2056,16777216,16777224,16779264,16779272,0,8,2048,2056,16777216,16777224,16779264,16779272],i=[0,2097152,134217728,136314880,8192,2105344,134225920,136323072,131072,2228224,134348800,136445952,139264,2236416,134356992,136454144],s=[0,262144,16,262160,0,262144,16,262160,4096,266240,4112,266256,4096,266240,4112,266256],o=[0,1024,32,1056,0,1024,32,1056,33554432,33555456,33554464,33555488,33554432,33555456,33554464,33555488],u=[0,268435456,524288,268959744,2,268435458,524290,268959746,0,268435456,524288,268959744,2,268435458,524290,268959746],a=[0,65536,2048,67584,536870912,536936448,536872960,536938496,131072,196608,133120,198656,537001984,537067520,537004032,537069568],f=[0,262144,0,262144,2,262146,2,262146,33554432,33816576,33554432,33816576,33554434,33816578,33554434,33816578],l=[0,268435456,8,268435464,0,268435456,8,268435464,1024,268436480,1032,268436488,1024,268436480,1032,268436488],c=[0,32,0,32,1048576,1048608,1048576,1048608,8192,8224,8192,8224,1056768,1056800,1056768,1056800],h=[0,16777216,512,16777728,2097152,18874368,2097664,18874880,67108864,83886080,67109376,83886592,69206016,85983232,69206528,85983744],p=[0,4096,134217728,134221824,524288,528384,134742016,134746112,16,4112,134217744,134221840,524304,528400,134742032,134746128],d=[0,4,256,260,0,4,256,260,1,5,257,261,1,5,257,261],v=e.length()>8?3:1,m=[],g=[0,0,1,1,1,1,1,1,0,1,1,1,1,1,1,0],y=0,b;for(var w=0;w<v;w++){var E=e.getInt32(),S=e.getInt32();b=(E>>>4^S)&252645135,S^=b,E^=b<<4,b=(S>>>-16^E)&65535,E^=b,S^=b<<-16,b=(E>>>2^S)&858993459,S^=b,E^=b<<2,b=(S>>>-16^E)&65535,E^=b,S^=b<<-16,b=(E>>>1^S)&1431655765,S^=b,E^=b<<1,b=(S>>>8^E)&16711935,E^=b,S^=b<<8,b=(E>>>1^S)&1431655765,S^=b,E^=b<<1,b=E<<8|S>>>20&240,E=S<<24|S<<8&16711680|S>>>8&65280|S>>>24&240,S=b;for(var x=0;x<g.length;x++){g[x]?(E=E<<2|E>>>26,S=S<<2|S>>>26):(E=E<<1|E>>>27,S=S<<1|S>>>27),E&=-15,S&=-15;var T=t[E>>>28]|n[E>>>24&15]|r[E>>>20&15]|i[E>>>16&15]|s[E>>>12&15]|o[E>>>8&15]|u[E>>>4&15],N=a[S>>>28]|f[S>>>24&15]|l[S>>>20&15]|c[S>>>16&15]|h[S>>>12&15]|p[S>>>8&15]|d[S>>>4&15];b=(N>>>16^T)&65535,m[y++]=T^b,m[y++]=N^b<<16}}return m}var t=[16843776,0,65536,16843780,16842756,66564,4,65536,1024,16843776,16843780,1024,16778244,16842756,16777216,4,1028,16778240,16778240,66560,66560,16842752,16842752,16778244,65540,16777220,16777220,65540,0,1028,66564,16777216,65536,16843780,4,16842752,16843776,16777216,16777216,1024,16842756,65536,66560,16777220,1024,4,16778244,66564,16843780,65540,16842752,16778244,16777220,1028,66564,16843776,1028,16778240,16778240,0,65540,66560,0,16842756],n=[-2146402272,-2147450880,32768,1081376,1048576,32,-2146435040,-2147450848,-2147483616,-2146402272,-2146402304,-2147483648,-2147450880,1048576,32,-2146435040,1081344,1048608,-2147450848,0,-2147483648,32768,1081376,-2146435072,1048608,-2147483616,0,1081344,32800,-2146402304,-2146435072,32800,0,1081376,-2146435040,1048576,-2147450848,-2146435072,-2146402304,32768,-2146435072,-2147450880,32,-2146402272,1081376,32,32768,-2147483648,32800,-2146402304,1048576,-2147483616,1048608,-2147450848,-2147483616,1048608,1081344,0,-2147450880,32800,-2147483648,-2146435040,-2146402272,1081344],r=[520,134349312,0,134348808,134218240,0,131592,134218240,131080,134217736,134217736,131072,134349320,131080,134348800,520,134217728,8,134349312,512,131584,134348800,134348808,131592,134218248,131584,131072,134218248,8,134349320,512,134217728,134349312,134217728,131080,520,131072,134349312,134218240,0,512,131080,134349320,134218240,134217736,512,0,134348808,134218248,131072,134217728,134349320,8,131592,131584,134217736,134348800,134218248,520,134348800,131592,8,134348808,131584],i=[8396801,8321,8321,128,8396928,8388737,8388609,8193,0,8396800,8396800,8396929,129,0,8388736,8388609,1,8192,8388608,8396801,128,8388608,8193,8320,8388737,1,8320,8388736,8192,8396928,8396929,129,8388736,8388609,8396800,8396929,129,0,0,8396800,8320,8388736,8388737,1,8396801,8321,8321,128,8396929,129,1,8192,8388609,8193,8396928,8388737,8193,8320,8388608,8396801,128,8388608,8192,8396928],s=[256,34078976,34078720,1107296512,524288,256,1073741824,34078720,1074266368,524288,33554688,1074266368,1107296512,1107820544,524544,1073741824,33554432,1074266112,1074266112,0,1073742080,1107820800,1107820800,33554688,1107820544,1073742080,0,1107296256,34078976,33554432,1107296256,524544,524288,1107296512,256,33554432,1073741824,34078720,1107296512,1074266368,33554688,1073741824,1107820544,34078976,1074266368,256,33554432,1107820544,1107820800,524544,1107296256,1107820800,34078720,0,1074266112,1107296256,524544,33554688,1073742080,524288,0,1074266112,34078976,1073742080],o=[536870928,541065216,16384,541081616,541065216,16,541081616,4194304,536887296,4210704,4194304,536870928,4194320,536887296,536870912,16400,0,4194320,536887312,16384,4210688,536887312,16,541065232,541065232,0,4210704,541081600,16400,4210688,541081600,536870912,536887296,16,541065232,4210688,541081616,4194304,16400,536870928,4194304,536887296,536870912,16400,536870928,541081616,4210688,541065216,4210704,541081600,0,541065232,16,16384,541065216,4210704,16384,4194320,536887312,0,541081600,536870912,4194320,536887312],u=[2097152,69206018,67110914,0,2048,67110914,2099202,69208064,69208066,2097152,0,67108866,2,67108864,69206018,2050,67110912,2099202,2097154,67110912,67108866,69206016,69208064,2097154,69206016,2048,2050,69208066,2099200,2,67108864,2099200,67108864,2099200,2097152,67110914,67110914,69206018,69206018,2,2097154,67108864,67110912,2097152,69208064,2050,2099202,69208064,2050,67108866,69208066,69206016,2099200,0,2,69208066,0,2099202,69206016,2048,67108866,67110912,2048,2097154],a=[268439616,4096,262144,268701760,268435456,268439616,64,268435456,262208,268697600,268701760,266240,268701696,266304,4096,64,268697600,268435520,268439552,4160,266240,262208,268697664,268701696,4160,0,0,268697664,268435520,268439552,266304,262144,266304,262144,268701696,4096,64,268697664,4096,266304,268439552,64,268435520,268697600,268697664,268435456,262144,268439616,0,268701760,262208,268435520,268697600,268439552,268439616,0,268701760,266240,266240,4160,4160,262208,268435456,268701696],l=function(l,c){typeof l=="string"&&(l.length===8||l.length===24)&&(l=e.util.createBuffer(l));var h=f(l),p=1,d=0,v=0,m=0,g=0,y=!1,b=null,w=null,E=h.length===32?3:9,S;E===3?S=c?[0,32,2]:[30,-2,-2]:S=c?[0,32,2,62,30,-2,64,96,2]:[94,62,-2,32,64,2,30,-2,-2];var x=null;return x={start:function(t,n){t?(typeof t=="string"&&t.length===8&&(t=e.util.createBuffer(t)),p=1,d=t.getInt32(),m=t.getInt32()):p=0,y=!1,b=e.util.createBuffer(),w=n||e.util.createBuffer(),x.output=w},update:function(e){y||b.putBuffer(e);while(b.length()>=8){var f,l=b.getInt32(),x=b.getInt32();p===1&&(c?(l^=d,x^=m):(v=d,g=m,d=l,m=x)),f=(l>>>4^x)&252645135,x^=f,l^=f<<4,f=(l>>>16^x)&65535,x^=f,l^=f<<16,f=(x>>>2^l)&858993459,l^=f,x^=f<<2,f=(x>>>8^l)&16711935,l^=f,x^=f<<8,f=(l>>>1^x)&1431655765,x^=f,l^=f<<1,l=l<<1|l>>>31,x=x<<1|x>>>31;for(var T=0;T<E;T+=3){var N=S[T+1],C=S[T+2];for(var k=S[T];k!=N;k+=C){var L=x^h[k],A=(x>>>4|x<<28)^h[k+1];f=l,l=x,x=f^(n[L>>>24&63]|i[L>>>16&63]|o[L>>>8&63]|a[L&63]|t[A>>>24&63]|r[A>>>16&63]|s[A>>>8&63]|u[A&63])}f=l,l=x,x=f}l=l>>>1|l<<31,x=x>>>1|x<<31,f=(l>>>1^x)&1431655765,x^=f,l^=f<<1,f=(x>>>8^l)&16711935,l^=f,x^=f<<8,f=(x>>>2^l)&858993459,l^=f,x^=f<<2,f=(l>>>16^x)&65535,x^=f,l^=f<<16,f=(l>>>4^x)&252645135,x^=f,l^=f<<4,p===1&&(c?(d=l,m=x):(l^=v,x^=g)),w.putInt32(l),w.putInt32(x)}},finish:function(e){var t=!0;if(c)if(e)t=e(8,b,!c);else{var n=b.length()===8?8:8-b.length();b.fillWithByte(n,n)}t&&(y=!0,x.update());if(!c){t=b.length()===0;if(t)if(e)t=e(8,w,!c);else{var r=w.length(),i=w.at(r-1);i>r?t=!1:w.truncate(i)}}return t}},x};e.des=e.des||{},e.des.startEncrypting=function(e,t,n){var r=l(e,!0);return r.start(t,n),r},e.des.createEncryptionCipher=function(e){return l(e,!0)},e.des.startDecrypting=function(e,t,n){var r=l(e,!1);return r.start(t,n),r},e.des.createDecryptionCipher=function(e){return l(e,!1)}}var t="des";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/des",["require","module","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){function i(e,t,n){this.data=[],e!=null&&("number"==typeof e?this.fromNumber(e,t,n):t==null&&"string"!=typeof e?this.fromString(e,256):this.fromString(e,t))}function s(){return new i(null)}function o(e,t,n,r,i,s){while(--s>=0){var o=t*this.data[e++]+n.data[r]+i;i=Math.floor(o/67108864),n.data[r++]=o&67108863}return i}function u(e,t,n,r,i,s){var o=t&32767,u=t>>15;while(--s>=0){var a=this.data[e]&32767,f=this.data[e++]>>15,l=u*a+f*o;a=o*a+((l&32767)<<15)+n.data[r]+(i&1073741823),i=(a>>>30)+(l>>>15)+u*f+(i>>>30),n.data[r++]=a&1073741823}return i}function a(e,t,n,r,i,s){var o=t&16383,u=t>>14;while(--s>=0){var a=this.data[e]&16383,f=this.data[e++]>>14,l=u*a+f*o;a=o*a+((l&16383)<<14)+n.data[r]+i,i=(a>>28)+(l>>14)+u*f,n.data[r++]=a&268435455}return i}function d(e){return l.charAt(e)}function v(e,t){var n=c[e.charCodeAt(t)];return n==null?-1:n}function m(e){for(var t=this.t-1;t>=0;--t)e.data[t]=this.data[t];e.t=this.t,e.s=this.s}function g(e){this.t=1,this.s=e<0?-1:0,e>0?this.data[0]=e:e<-1?this.data[0]=e+DV:this.t=0}function y(e){var t=s();return t.fromInt(e),t}function b(e,t){var n;if(t==16)n=4;else if(t==8)n=3;else if(t==256)n=8;else if(t==2)n=1;else if(t==32)n=5;else{if(t!=4){this.fromRadix(e,t);return}n=2}this.t=0,this.s=0;var r=e.length,s=!1,o=0;while(--r>=0){var u=n==8?e[r]&255:v(e,r);if(u<0){e.charAt(r)=="-"&&(s=!0);continue}s=!1,o==0?this.data[this.t++]=u:o+n>this.DB?(this.data[this.t-1]|=(u&(1<<this.DB-o)-1)<<o,this.data[this.t++]=u>>this.DB-o):this.data[this.t-1]|=u<<o,o+=n,o>=this.DB&&(o-=this.DB)}n==8&&(e[0]&128)!=0&&(this.s=-1,o>0&&(this.data[this.t-1]|=(1<<this.DB-o)-1<<o)),this.clamp(),s&&i.ZERO.subTo(this,this)}function w(){var e=this.s&this.DM;while(this.t>0&&this.data[this.t-1]==e)--this.t}function E(e){if(this.s<0)return"-"+this.negate().toString(e);var t;if(e==16)t=4;else if(e==8)t=3;else if(e==2)t=1;else if(e==32)t=5;else{if(e!=4)return this.toRadix(e);t=2}var n=(1<<t)-1,r,i=!1,s="",o=this.t,u=this.DB-o*this.DB%t;if(o-->0){u<this.DB&&(r=this.data[o]>>u)>0&&(i=!0,s=d(r));while(o>=0)u<t?(r=(this.data[o]&(1<<u)-1)<<t-u,r|=this.data[--o]>>(u+=this.DB-t)):(r=this.data[o]>>(u-=t)&n,u<=0&&(u+=this.DB,--o)),r>0&&(i=!0),i&&(s+=d(r))}return i?s:"0"}function S(){var e=s();return i.ZERO.subTo(this,e),e}function x(){return this.s<0?this.negate():this}function T(e){var t=this.s-e.s;if(t!=0)return t;var n=this.t;t=n-e.t;if(t!=0)return this.s<0?-t:t;while(--n>=0)if((t=this.data[n]-e.data[n])!=0)return t;return 0}function N(e){var t=1,n;return(n=e>>>16)!=0&&(e=n,t+=16),(n=e>>8)!=0&&(e=n,t+=8),(n=e>>4)!=0&&(e=n,t+=4),(n=e>>2)!=0&&(e=n,t+=2),(n=e>>1)!=0&&(e=n,t+=1),t}function C(){return this.t<=0?0:this.DB*(this.t-1)+N(this.data[this.t-1]^this.s&this.DM)}function k(e,t){var n;for(n=this.t-1;n>=0;--n)t.data[n+e]=this.data[n];for(n=e-1;n>=0;--n)t.data[n]=0;t.t=this.t+e,t.s=this.s}function L(e,t){for(var n=e;n<this.t;++n)t.data[n-e]=this.data[n];t.t=Math.max(this.t-e,0),t.s=this.s}function A(e,t){var n=e%this.DB,r=this.DB-n,i=(1<<r)-1,s=Math.floor(e/this.DB),o=this.s<<n&this.DM,u;for(u=this.t-1;u>=0;--u)t.data[u+s+1]=this.data[u]>>r|o,o=(this.data[u]&i)<<n;for(u=s-1;u>=0;--u)t.data[u]=0;t.data[s]=o,t.t=this.t+s+1,t.s=this.s,t.clamp()}function O(e,t){t.s=this.s;var n=Math.floor(e/this.DB);if(n>=this.t){t.t=0;return}var r=e%this.DB,i=this.DB-r,s=(1<<r)-1;t.data[0]=this.data[n]>>r;for(var o=n+1;o<this.t;++o)t.data[o-n-1]|=(this.data[o]&s)<<i,t.data[o-n]=this.data[o]>>r;r>0&&(t.data[this.t-n-1]|=(this.s&s)<<i),t.t=this.t-n,t.clamp()}function M(e,t){var n=0,r=0,i=Math.min(e.t,this.t);while(n<i)r+=this.data[n]-e.data[n],t.data[n++]=r&this.DM,r>>=this.DB;if(e.t<this.t){r-=e.s;while(n<this.t)r+=this.data[n],t.data[n++]=r&this.DM,r>>=this.DB;r+=this.s}else{r+=this.s;while(n<e.t)r-=e.data[n],t.data[n++]=r&this.DM,r>>=this.DB;r-=e.s}t.s=r<0?-1:0,r<-1?t.data[n++]=this.DV+r:r>0&&(t.data[n++]=r),t.t=n,t.clamp()}function _(e,t){var n=this.abs(),r=e.abs(),s=n.t;t.t=s+r.t;while(--s>=0)t.data[s]=0;for(s=0;s<r.t;++s)t.data[s+n.t]=n.am(0,r.data[s],t,s,0,n.t);t.s=0,t.clamp(),this.s!=e.s&&i.ZERO.subTo(t,t)}function D(e){var t=this.abs(),n=e.t=2*t.t;while(--n>=0)e.data[n]=0;for(n=0;n<t.t-1;++n){var r=t.am(n,t.data[n],e,2*n,0,1);(e.data[n+t.t]+=t.am(n+1,2*t.data[n],e,2*n+1,r,t.t-n-1))>=t.DV&&(e.data[n+t.t]-=t.DV,e.data[n+t.t+1]=1)}e.t>0&&(e.data[e.t-1]+=t.am(n,t.data[n],e,2*n,0,1)),e.s=0,e.clamp()}function P(e,t,n){var r=e.abs();if(r.t<=0)return;var o=this.abs();if(o.t<r.t){t!=null&&t.fromInt(0),n!=null&&this.copyTo(n);return}n==null&&(n=s());var u=s(),a=this.s,f=e.s,l=this.DB-N(r.data[r.t-1]);l>0?(r.lShiftTo(l,u),o.lShiftTo(l,n)):(r.copyTo(u),o.copyTo(n));var c=u.t,h=u.data[c-1];if(h==0)return;var p=h*(1<<this.F1)+(c>1?u.data[c-2]>>this.F2:0),d=this.FV/p,v=(1<<this.F1)/p,m=1<<this.F2,g=n.t,y=g-c,b=t==null?s():t;u.dlShiftTo(y,b),n.compareTo(b)>=0&&(n.data[n.t++]=1,n.subTo(b,n)),i.ONE.dlShiftTo(c,b),b.subTo(u,u);while(u.t<c)u.data[u.t++]=0;while(--y>=0){var w=n.data[--g]==h?this.DM:Math.floor(n.data[g]*d+(n.data[g-1]+m)*v);if((n.data[g]+=u.am(0,w,n,y,0,c))<w){u.dlShiftTo(y,b),n.subTo(b,n);while(n.data[g]<--w)n.subTo(b,n)}}t!=null&&(n.drShiftTo(c,t),a!=f&&i.ZERO.subTo(t,t)),n.t=c,n.clamp(),l>0&&n.rShiftTo(l,n),a<0&&i.ZERO.subTo(n,n)}function H(e){var t=s();return this.abs().divRemTo(e,null,t),this.s<0&&t.compareTo(i.ZERO)>0&&e.subTo(t,t),t}function B(e){this.m=e}function j(e){return e.s<0||e.compareTo(this.m)>=0?e.mod(this.m):e}function F(e){return e}function I(e){e.divRemTo(this.m,null,e)}function q(e,t,n){e.multiplyTo(t,n),this.reduce(n)}function R(e,t){e.squareTo(t),this.reduce(t)}function U(){if(this.t<1)return 0;var e=this.data[0];if((e&1)==0)return 0;var t=e&3;return t=t*(2-(e&15)*t)&15,t=t*(2-(e&255)*t)&255,t=t*(2-((e&65535)*t&65535))&65535,t=t*(2-e*t%this.DV)%this.DV,t>0?this.DV-t:-t}function z(e){this.m=e,this.mp=e.invDigit(),this.mpl=this.mp&32767,this.mph=this.mp>>15,this.um=(1<<e.DB-15)-1,this.mt2=2*e.t}function W(e){var t=s();return e.abs().dlShiftTo(this.m.t,t),t.divRemTo(this.m,null,t),e.s<0&&t.compareTo(i.ZERO)>0&&this.m.subTo(t,t),t}function X(e){var t=s();return e.copyTo(t),this.reduce(t),t}function V(e){while(e.t<=this.mt2)e.data[e.t++]=0;for(var t=0;t<this.m.t;++t){var n=e.data[t]&32767,r=n*this.mpl+((n*this.mph+(e.data[t]>>15)*this.mpl&this.um)<<15)&e.DM;n=t+this.m.t,e.data[n]+=this.m.am(0,r,e,t,0,this.m.t);while(e.data[n]>=e.DV)e.data[n]-=e.DV,e.data[++n]++}e.clamp(),e.drShiftTo(this.m.t,e),e.compareTo(this.m)>=0&&e.subTo(this.m,e)}function $(e,t){e.squareTo(t),this.reduce(t)}function J(e,t,n){e.multiplyTo(t,n),this.reduce(n)}function K(){return(this.t>0?this.data[0]&1:this.s)==0}function Q(e,t){if(e>4294967295||e<1)return i.ONE;var n=s(),r=s(),o=t.convert(this),u=N(e)-1;o.copyTo(n);while(--u>=0){t.sqrTo(n,r);if((e&1<<u)>0)t.mulTo(r,o,n);else{var a=n;n=r,r=a}}return t.revert(n)}function G(e,t){var n;return e<256||t.isEven()?n=new B(t):n=new z(t),this.exp(e,n)}function Y(){var e=s();return this.copyTo(e),e}function Z(){if(this.s<0){if(this.t==1)return this.data[0]-this.DV;if(this.t==0)return-1}else{if(this.t==1)return this.data[0];if(this.t==0)return 0}return(this.data[1]&(1<<32-this.DB)-1)<<this.DB|this.data[0]}function et(){return this.t==0?this.s:this.data[0]<<24>>24}function tt(){return this.t==0?this.s:this.data[0]<<16>>16}function nt(e){return Math.floor(Math.LN2*this.DB/Math.log(e))}function rt(){return this.s<0?-1:this.t<=0||this.t==1&&this.data[0]<=0?0:1}function it(e){e==null&&(e=10);if(this.signum()==0||e<2||e>36)return"0";var t=this.chunkSize(e),n=Math.pow(e,t),r=y(n),i=s(),o=s(),u="";this.divRemTo(r,i,o);while(i.signum()>0)u=(n+o.intValue()).toString(e).substr(1)+u,i.divRemTo(r,i,o);return o.intValue().toString(e)+u}function st(e,t){this.fromInt(0),t==null&&(t=10);var n=this.chunkSize(t),r=Math.pow(t,n),s=!1,o=0,u=0;for(var a=0;a<e.length;++a){var f=v(e,a);if(f<0){e.charAt(a)=="-"&&this.signum()==0&&(s=!0);continue}u=t*u+f,++o>=n&&(this.dMultiply(r),this.dAddOffset(u,0),o=0,u=0)}o>0&&(this.dMultiply(Math.pow(t,o)),this.dAddOffset(u,0)),s&&i.ZERO.subTo(this,this)}function ot(e,t,n){if("number"==typeof t)if(e<2)this.fromInt(1);else{this.fromNumber(e,n),this.testBit(e-1)||this.bitwiseTo(i.ONE.shiftLeft(e-1),dt,this),this.isEven()&&this.dAddOffset(1,0);while(!this.isProbablePrime(t))this.dAddOffset(2,0),this.bitLength()>e&&this.subTo(i.ONE.shiftLeft(e-1),this)}else{var r=new Array,s=e&7;r.length=(e>>3)+1,t.nextBytes(r),s>0?r[0]&=(1<<s)-1:r[0]=0,this.fromString(r,256)}}function ut(){var e=this.t,t=new Array;t[0]=this.s;var n=this.DB-e*this.DB%8,r,i=0;if(e-->0){n<this.DB&&(r=this.data[e]>>n)!=(this.s&this.DM)>>n&&(t[i++]=r|this.s<<this.DB-n);while(e>=0){n<8?(r=(this.data[e]&(1<<n)-1)<<8-n,r|=this.data[--e]>>(n+=this.DB-8)):(r=this.data[e]>>(n-=8)&255,n<=0&&(n+=this.DB,--e)),(r&128)!=0&&(r|=-256),i==0&&(this.s&128)!=(r&128)&&++i;if(i>0||r!=this.s)t[i++]=r}}return t}function at(e){return this.compareTo(e)==0}function ft(e){return this.compareTo(e)<0?this:e}function lt(e){return this.compareTo(e)>0?this:e}function ct(e,t,n){var r,i,s=Math.min(e.t,this.t);for(r=0;r<s;++r)n.data[r]=t(this.data[r],e.data[r]);if(e.t<this.t){i=e.s&this.DM;for(r=s;r<this.t;++r)n.data[r]=t(this.data[r],i);n.t=this.t}else{i=this.s&this.DM;for(r=s;r<e.t;++r)n.data[r]=t(i,e.data[r]);n.t=e.t}n.s=t(this.s,e.s),n.clamp()}function ht(e,t){return e&t}function pt(e){var t=s();return this.bitwiseTo(e,ht,t),t}function dt(e,t){return e|t}function vt(e){var t=s();return this.bitwiseTo(e,dt,t),t}function mt(e,t){return e^t}function gt(e){var t=s();return this.bitwiseTo(e,mt,t),t}function yt(e,t){return e&~t}function bt(e){var t=s();return this.bitwiseTo(e,yt,t),t}function wt(){var e=s();for(var t=0;t<this.t;++t)e.data[t]=this.DM&~this.data[t];return e.t=this.t,e.s=~this.s,e}function Et(e){var t=s();return e<0?this.rShiftTo(-e,t):this.lShiftTo(e,t),t}function St(e){var t=s();return e<0?this.lShiftTo(-e,t):this.rShiftTo(e,t),t}function xt(e){if(e==0)return-1;var t=0;return(e&65535)==0&&(e>>=16,t+=16),(e&255)==0&&(e>>=8,t+=8),(e&15)==0&&(e>>=4,t+=4),(e&3)==0&&(e>>=2,t+=2),(e&1)==0&&++t,t}function Tt(){for(var e=0;e<this.t;++e)if(this.data[e]!=0)return e*this.DB+xt(this.data[e]);return this.s<0?this.t*this.DB:-1}function Nt(e){var t=0;while(e!=0)e&=e-1,++t;return t}function Ct(){var e=0,t=this.s&this.DM;for(var n=0;n<this.t;++n)e+=Nt(this.data[n]^t);return e}function kt(e){var t=Math.floor(e/this.DB);return t>=this.t?this.s!=0:(this.data[t]&1<<e%this.DB)!=0}function Lt(e,t){var n=i.ONE.shiftLeft(e);return this.bitwiseTo(n,t,n),n}function At(e){return this.changeBit(e,dt)}function Ot(e){return this.changeBit(e,yt)}function Mt(e){return this.changeBit(e,mt)}function _t(e,t){var n=0,r=0,i=Math.min(e.t,this.t);while(n<i)r+=this.data[n]+e.data[n],t.data[n++]=r&this.DM,r>>=this.DB;if(e.t<this.t){r+=e.s;while(n<this.t)r+=this.data[n],t.data[n++]=r&this.DM,r>>=this.DB;r+=this.s}else{r+=this.s;while(n<e.t)r+=e.data[n],t.data[n++]=r&this.DM,r>>=this.DB;r+=e.s}t.s=r<0?-1:0,r>0?t.data[n++]=r:r<-1&&(t.data[n++]=this.DV+r),t.t=n,t.clamp()}function Dt(e){var t=s();return this.addTo(e,t),t}function Pt(e){var t=s();return this.subTo(e,t),t}function Ht(e){var t=s();return this.multiplyTo(e,t),t}function Bt(e){var t=s();return this.divRemTo(e,t,null),t}function jt(e){var t=s();return this.divRemTo(e,null,t),t}function Ft(e){var t=s(),n=s();return this.divRemTo(e,t,n),new Array(t,n)}function It(e){this.data[this.t]=this.am(0,e-1,this,0,0,this.t),++this.t,this.clamp()}function qt(e,t){if(e==0)return;while(this.t<=t)this.data[this.t++]=0;this.data[t]+=e;while(this.data[t]>=this.DV)this.data[t]-=this.DV,++t>=this.t&&(this.data[this.t++]=0),++this.data[t]}function Rt(){}function Ut(e){return e}function zt(e,t,n){e.multiplyTo(t,n)}function Wt(e,t){e.squareTo(t)}function Xt(e){return this.exp(e,new Rt)}function Vt(e,t,n){var r=Math.min(this.t+e.t,t);n.s=0,n.t=r;while(r>0)n.data[--r]=0;var i;for(i=n.t-this.t;r<i;++r)n.data[r+this.t]=this.am(0,e.data[r],n,r,0,this.t);for(i=Math.min(e.t,t);r<i;++r)this.am(0,e.data[r],n,r,0,t-r);n.clamp()}function $t(e,t,n){--t;var r=n.t=this.t+e.t-t;n.s=0;while(--r>=0)n.data[r]=0;for(r=Math.max(t-this.t,0);r<e.t;++r)n.data[this.t+r-t]=this.am(t-r,e.data[r],n,0,0,this.t+r-t);n.clamp(),n.drShiftTo(1,n)}function Jt(e){this.r2=s(),this.q3=s(),i.ONE.dlShiftTo(2*e.t,this.r2),this.mu=this.r2.divide(e),this.m=e}function Kt(e){if(e.s<0||e.t>2*this.m.t)return e.mod(this.m);if(e.compareTo(this.m)<0)return e;var t=s();return e.copyTo(t),this.reduce(t),t}function Qt(e){return e}function Gt(e){e.drShiftTo(this.m.t-1,this.r2),e.t>this.m.t+1&&(e.t=this.m.t+1,e.clamp()),this.mu.multiplyUpperTo(this.r2,this.m.t+1,this.q3),this.m.multiplyLowerTo(this.q3,this.m.t+1,this.r2);while(e.compareTo(this.r2)<0)e.dAddOffset(1,this.m.t+1);e.subTo(this.r2,e);while(e.compareTo(this.m)>=0)e.subTo(this.m,e)}function Yt(e,t){e.squareTo(t),this.reduce(t)}function Zt(e,t,n){e.multiplyTo(t,n),this.reduce(n)}function en(e,t){var n=e.bitLength(),r,i=y(1),o;if(n<=0)return i;n<18?r=1:n<48?r=3:n<144?r=4:n<768?r=5:r=6,n<8?o=new B(t):t.isEven()?o=new Jt(t):o=new z(t);var u=new Array,a=3,f=r-1,l=(1<<r)-1;u[1]=o.convert(this);if(r>1){var c=s();o.sqrTo(u[1],c);while(a<=l)u[a]=s(),o.mulTo(c,u[a-2],u[a]),a+=2}var h=e.t-1,p,d=!0,v=s(),m;n=N(e.data[h])-1;while(h>=0){n>=f?p=e.data[h]>>n-f&l:(p=(e.data[h]&(1<<n+1)-1)<<f-n,h>0&&(p|=e.data[h-1]>>this.DB+n-f)),a=r;while((p&1)==0)p>>=1,--a;(n-=a)<0&&(n+=this.DB,--h);if(d)u[p].copyTo(i),d=!1;else{while(a>1)o.sqrTo(i,v),o.sqrTo(v,i),a-=2;a>0?o.sqrTo(i,v):(m=i,i=v,v=m),o.mulTo(v,u[p],i)}while(h>=0&&(e.data[h]&1<<n)==0)o.sqrTo(i,v),m=i,i=v,v=m,--n<0&&(n=this.DB-1,--h)}return o.revert(i)}function tn(e){var t=this.s<0?this.negate():this.clone(),n=e.s<0?e.negate():e.clone();if(t.compareTo(n)<0){var r=t;t=n,n=r}var i=t.getLowestSetBit(),s=n.getLowestSetBit();if(s<0)return t;i<s&&(s=i),s>0&&(t.rShiftTo(s,t),n.rShiftTo(s,n));while(t.signum()>0)(i=t.getLowestSetBit())>0&&t.rShiftTo(i,t),(i=n.getLowestSetBit())>0&&n.rShiftTo(i,n),t.compareTo(n)>=0?(t.subTo(n,t),t.rShiftTo(1,t)):(n.subTo(t,n),n.rShiftTo(1,n));return s>0&&n.lShiftTo(s,n),n}function nn(e){if(e<=0)return 0;var t=this.DV%e,n=this.s<0?e-1:0;if(this.t>0)if(t==0)n=this.data[0]%e;else for(var r=this.t-1;r>=0;--r)n=(t*n+this.data[r])%e;return n}function rn(e){var t=e.isEven();if(this.isEven()&&t||e.signum()==0)return i.ZERO;var n=e.clone(),r=this.clone(),s=y(1),o=y(0),u=y(0),a=y(1);while(n.signum()!=0){while(n.isEven()){n.rShiftTo(1,n);if(t){if(!s.isEven()||!o.isEven())s.addTo(this,s),o.subTo(e,o);s.rShiftTo(1,s)}else o.isEven()||o.subTo(e,o);o.rShiftTo(1,o)}while(r.isEven()){r.rShiftTo(1,r);if(t){if(!u.isEven()||!a.isEven())u.addTo(this,u),a.subTo(e,a);u.rShiftTo(1,u)}else a.isEven()||a.subTo(e,a);a.rShiftTo(1,a)}n.compareTo(r)>=0?(n.subTo(r,n),t&&s.subTo(u,s),o.subTo(a,o)):(r.subTo(n,r),t&&u.subTo(s,u),a.subTo(o,a))}return r.compareTo(i.ONE)!=0?i.ZERO:a.compareTo(e)>=0?a.subtract(e):a.signum()<0?(a.addTo(e,a),a.signum()<0?a.add(e):a):a}function un(e){var t,n=this.abs();if(n.t==1&&n.data[0]<=sn[sn.length-1]){for(t=0;t<sn.length;++t)if(n.data[0]==sn[t])return!0;return!1}if(n.isEven())return!1;t=1;while(t<sn.length){var r=sn[t],i=t+1;while(i<sn.length&&r<on)r*=sn[i++];r=n.modInt(r);while(t<i)if(r%sn[t++]==0)return!1}return n.millerRabin(e)}function an(e){var t=this.subtract(i.ONE),n=t.getLowestSetBit();if(n<=0)return!1;var r=t.shiftRight(n);e=e+1>>1,e>sn.length&&(e=sn.length);var o=s();for(var u=0;u<e;++u){o.fromInt(sn[u]);var a=o.modPow(r,this);if(a.compareTo(i.ONE)!=0&&a.compareTo(t)!=0){var f=1;while(f++<n&&a.compareTo(t)!=0){a=a.modPowInt(2,this);if(a.compareTo(i.ONE)==0)return!1}if(a.compareTo(t)!=0)return!1}}return!0}var t,n=0xdeadbeefcafe,r=(n&16777215)==15715070;typeof navigator=="undefined"?(i.prototype.am=a,t=28):r&&navigator.appName=="Microsoft Internet Explorer"?(i.prototype.am=u,t=30):r&&navigator.appName!="Netscape"?(i.prototype.am=o,t=26):(i.prototype.am=a,t=28),i.prototype.DB=t,i.prototype.DM=(1<<t)-1,i.prototype.DV=1<<t;var f=52;i.prototype.FV=Math.pow(2,f),i.prototype.F1=f-t,i.prototype.F2=2*t-f;var l="0123456789abcdefghijklmnopqrstuvwxyz",c=new Array,h,p;h="0".charCodeAt(0);for(p=0;p<=9;++p)c[h++]=p;h="a".charCodeAt(0);for(p=10;p<36;++p)c[h++]=p;h="A".charCodeAt(0);for(p=10;p<36;++p)c[h++]=p;B.prototype.convert=j,B.prototype.revert=F,B.prototype.reduce=I,B.prototype.mulTo=q,B.prototype.sqrTo=R,z.prototype.convert=W,z.prototype.revert=X,z.prototype.reduce=V,z.prototype.mulTo=J,z.prototype.sqrTo=$,i.prototype.copyTo=m,i.prototype.fromInt=g,i.prototype.fromString=b,i.prototype.clamp=w,i.prototype.dlShiftTo=k,i.prototype.drShiftTo=L,i.prototype.lShiftTo=A,i.prototype.rShiftTo=O,i.prototype.subTo=M,i.prototype.multiplyTo=_,i.prototype.squareTo=D,i.prototype.divRemTo=P,i.prototype.invDigit=U,i.prototype.isEven=K,i.prototype.exp=Q,i.prototype.toString=E,i.prototype.negate=S,i.prototype.abs=x,i.prototype.compareTo=T,i.prototype.bitLength=C,i.prototype.mod=H,i.prototype.modPowInt=G,i.ZERO=y(0),i.ONE=y(1),Rt.prototype.convert=Ut,Rt.prototype.revert=Ut,Rt.prototype.mulTo=zt,Rt.prototype.sqrTo=Wt,Jt.prototype.convert=Kt,Jt.prototype.revert=Qt,Jt.prototype.reduce=Gt,Jt.prototype.mulTo=Zt,Jt.prototype.sqrTo=Yt;var sn=[2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,503,509],on=(1<<26)/sn[sn.length-1];i.prototype.chunkSize=nt,i.prototype.toRadix=it,i.prototype.fromRadix=st,i.prototype.fromNumber=ot,i.prototype.bitwiseTo=ct,i.prototype.changeBit=Lt,i.prototype.addTo=_t,i.prototype.dMultiply=It,i.prototype.dAddOffset=qt,i.prototype.multiplyLowerTo=Vt,i.prototype.multiplyUpperTo=$t,i.prototype.modInt=nn,i.prototype.millerRabin=an,i.prototype.clone=Y,i.prototype.intValue=Z,i.prototype.byteValue=et,i.prototype.shortValue=tt,i.prototype.signum=rt,i.prototype.toByteArray=ut,i.prototype.equals=at,i.prototype.min=ft,i.prototype.max=lt,i.prototype.and=pt,i.prototype.or=vt,i.prototype.xor=gt,i.prototype.andNot=bt,i.prototype.not=wt,i.prototype.shiftLeft=Et,i.prototype.shiftRight=St,i.prototype.getLowestSetBit=Tt,i.prototype.bitCount=Ct,i.prototype.testBit=kt,i.prototype.setBit=At,i.prototype.clearBit=Ot,i.prototype.flipBit=Mt,i.prototype.add=Dt,i.prototype.subtract=Pt,i.prototype.multiply=Ht,i.prototype.divide=Bt,i.prototype.remainder=jt,i.prototype.divideAndRemainder=Ft,i.prototype.modPow=en,i.prototype.modInverse=rn,i.prototype.pow=Xt,i.prototype.gcd=tn,i.prototype.isProbablePrime=un,e.jsbn=e.jsbn||{},e.jsbn.BigInteger=i}var t="jsbn";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/jsbn",["require","module"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){var t=e.asn1,n=e.pkcs7asn1=e.pkcs7asn1||{};e.pkcs7=e.pkcs7||{},e.pkcs7.asn1=n;var r={name:"ContentInfo",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"ContentInfo.ContentType",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"contentType"},{name:"ContentInfo.content",tagClass:t.Class.CONTEXT_SPECIFIC,type:0,constructed:!0,optional:!0,captureAsn1:"content"}]};n.contentInfoValidator=r;var i={name:"EncryptedContentInfo",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"EncryptedContentInfo.contentType",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"contentType"},{name:"EncryptedContentInfo.contentEncryptionAlgorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"EncryptedContentInfo.contentEncryptionAlgorithm.algorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"encAlgorithm"},{name:"EncryptedContentInfo.contentEncryptionAlgorithm.parameter",tagClass:t.Class.UNIVERSAL,captureAsn1:"encParameter"}]},{name:"EncryptedContentInfo.encryptedContent",tagClass:t.Class.CONTEXT_SPECIFIC,type:0,capture:"encContent"}]};n.envelopedDataValidator={name:"EnvelopedData",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"EnvelopedData.Version",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"version"},{name:"EnvelopedData.RecipientInfos",tagClass:t.Class.UNIVERSAL,type:t.Type.SET,constructed:!0,captureAsn1:"recipientInfos"}].concat(i)},n.encryptedDataValidator={name:"EncryptedData",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"EncryptedData.Version",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"version"}].concat(i)};var s={name:"SignerInfo",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"SignerInfo.Version",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1},{name:"SignerInfo.IssuerAndSerialNumber",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0},{name:"SignerInfo.DigestAlgorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0},{name:"SignerInfo.AuthenticatedAttributes",tagClass:t.Class.CONTEXT_SPECIFIC,type:0,constructed:!0,optional:!0,capture:"authenticatedAttributes"},{name:"SignerInfo.DigestEncryptionAlgorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0},{name:"SignerInfo.EncryptedDigest",tagClass:t.Class.UNIVERSAL,type:t.Type.OCTETSTRING,constructed:!1,capture:"signature"},{name:"SignerInfo.UnauthenticatedAttributes",tagClass:t.Class.CONTEXT_SPECIFIC,type:1,constructed:!0,optional:!0}]};n.signedDataValidator={name:"SignedData",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"SignedData.Version",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"version"},{name:"SignedData.DigestAlgorithms",tagClass:t.Class.UNIVERSAL,type:t.Type.SET,constructed:!0,captureAsn1:"digestAlgorithms"},r,{name:"SignedData.Certificates",tagClass:t.Class.CONTEXT_SPECIFIC,type:0,optional:!0,captureAsn1:"certificates"},{name:"SignedData.CertificateRevocationLists",tagClass:t.Class.CONTEXT_SPECIFIC,type:1,optional:!0,captureAsn1:"crls"},{name:"SignedData.SignerInfos",tagClass:t.Class.UNIVERSAL,type:t.Type.SET,capture:"signerInfos",value:[s]}]},n.recipientInfoValidator={name:"RecipientInfo",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"RecipientInfo.version",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"version"},{name:"RecipientInfo.issuerAndSerial",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"RecipientInfo.issuerAndSerial.issuer",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,captureAsn1:"issuer"},{name:"RecipientInfo.issuerAndSerial.serialNumber",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"serial"}]},{name:"RecipientInfo.keyEncryptionAlgorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"RecipientInfo.keyEncryptionAlgorithm.algorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"encAlgorithm"},{name:"RecipientInfo.keyEncryptionAlgorithm.parameter",tagClass:t.Class.UNIVERSAL,constructed:!1,captureAsn1:"encParameter"}]},{name:"RecipientInfo.encryptedKey",tagClass:t.Class.UNIVERSAL,type:t.Type.OCTETSTRING,constructed:!1,capture:"encKey"}]}}var t="pkcs7asn1";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/pkcs7asn1",["require","module","./asn1","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){function f(e,t,n,r){var i=[];for(var s=0;s<e.length;s++)for(var o=0;o<e[s].safeBags.length;o++){var u=e[s].safeBags[o];if(r!==undefined&&u.type!==r)continue;u.attributes[t]!==undefined&&u.attributes[t].indexOf(n)>=0&&i.push(u)}return i}function l(e,r,s,o){r=t.fromDer(r,s);if(r.tagClass!==t.Class.UNIVERSAL||r.type!==t.Type.SEQUENCE||r.constructed!==!0)throw{message:"PKCS#12 AuthenticatedSafe expected to be a SEQUENCE OF ContentInfo"};for(var u=0;u<r.value.length;u++){var a=r.value[u],f={},l=[];if(!t.validate(a,i,f,l))throw{message:"Cannot read ContentInfo.",errors:l};var p={encrypted:!1},d=null,v=f.content.value[0];switch(t.derToOid(f.contentType)){case n.oids.data:if(v.tagClass!==t.Class.UNIVERSAL||v.type!==t.Type.OCTETSTRING)throw{message:"PKCS#12 SafeContents Data is not an OCTET STRING."};d=v.value;break;case n.oids.encryptedData:if(o===undefined)throw{message:"Found PKCS#12 Encrypted SafeContents Data but no password available."};d=c(v,o),p.encrypted=!0;break;default:throw{message:"Unsupported PKCS#12 contentType.",contentType:t.derToOid(f.contentType)}}p.safeBags=h(d,s,o),e.safeContents.push(p)}}function c(r,i){var s={},o=[];if(!t.validate(r,e.pkcs7.asn1.encryptedDataValidator,s,o))throw{message:"Cannot read EncryptedContentInfo. ",errors:o};var u=t.derToOid(s.contentType);if(u!==n.oids.data)throw{message:"PKCS#12 EncryptedContentInfo ContentType is not Data.",oid:u};u=t.derToOid(s.encAlgorithm);var a=n.pbe.getCipher(u,s.encParameter,i),f=e.util.createBuffer(s.encContent);a.update(f);if(!a.finish())throw{message:"Failed to decrypt PKCS#12 SafeContents."};return a.output.getBytes()}function h(e,r,i){e=t.fromDer(e,r);if(e.tagClass!==t.Class.UNIVERSAL||e.type!==t.Type.SEQUENCE||e.constructed!==!0)throw{message:"PKCS#12 SafeContents expected to be a SEQUENCE OF SafeBag"};var s=[];for(var u=0;u<e.value.length;u++){var f=e.value[u],l={},c=[];if(!t.validate(f,o,l,c))throw{message:"Cannot read SafeBag.",errors:c};var h={type:t.derToOid(l.bagId),attributes:p(l.bagAttributes)};s.push(h);var d,v,m=l.bagValue.value[0];switch(h.type){case n.oids.pkcs8ShroudedKeyBag:if(i===undefined)throw{message:"Found PKCS#8 ShroudedKeyBag but no password available."};m=n.decryptPrivateKeyInfo(m,i);if(m===null)throw{message:"Unable to decrypt PKCS#8 ShroudedKeyBag, wrong password?"};case n.oids.keyBag:h.key=n.privateKeyFromAsn1(m);continue;case n.oids.certBag:d=a,v=function(){if(t.derToOid(l.certId)!==n.oids.x509Certificate)throw{message:"Unsupported certificate type, only X.509 supported.",oid:t.derToOid(l.certId)};h.cert=n.certificateFromAsn1(t.fromDer(l.cert,r),!0)};break;default:throw{message:"Unsupported PKCS#12 SafeBag type.",oid:h.type}}if(d!==undefined&&!t.validate(m,d,l,c))throw{message:"Cannot read PKCS#12 "+d.name,errors:c};v()}return s}function p(e){var r={};if(e!==undefined)for(var i=0;i<e.length;++i){var s={},o=[];if(!t.validate(e[i],u,s,o))throw{message:"Cannot read PKCS#12 BagAttribute.",errors:o};var a=t.derToOid(s.oid);if(n.oids[a]===undefined)continue;r[n.oids[a]]=[];for(var f=0;f<s.values.length;++f)r[n.oids[a]].push(s.values[f].value)}return r}var t=e.asn1,n=e.pki,r=e.pkcs12=e.pkcs12||{},i={name:"ContentInfo",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"ContentInfo.contentType",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"contentType"},{name:"ContentInfo.content",tagClass:t.Class.CONTEXT_SPECIFIC,constructed:!0,captureAsn1:"content"}]},s={name:"PFX",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"PFX.version",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"version"},i,{name:"PFX.macData",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,optional:!0,captureAsn1:"mac",value:[{name:"PFX.macData.mac",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"PFX.macData.mac.digestAlgorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"PFX.macData.mac.digestAlgorithm.algorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"macAlgorithm"},{name:"PFX.macData.mac.digestAlgorithm.parameters",tagClass:t.Class.UNIVERSAL,captureAsn1:"macAlgorithmParameters"}]},{name:"PFX.macData.mac.digest",tagClass:t.Class.UNIVERSAL,type:t.Type.OCTETSTRING,constructed:!1,capture:"macDigest"}]},{name:"PFX.macData.macSalt",tagClass:t.Class.UNIVERSAL,type:t.Type.OCTETSTRING,constructed:!1,capture:"macSalt"},{name:"PFX.macData.iterations",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,optional:!0,capture:"macIterations"}]}]},o={name:"SafeBag",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"SafeBag.bagId",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"bagId"},{name:"SafeBag.bagValue",tagClass:t.Class.CONTEXT_SPECIFIC,constructed:!0,captureAsn1:"bagValue"},{name:"SafeBag.bagAttributes",tagClass:t.Class.UNIVERSAL,type:t.Type.SET,constructed:!0,optional:!0,capture:"bagAttributes"}]},u={name:"Attribute",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"Attribute.attrId",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"oid"},{name:"Attribute.attrValues",tagClass:t.Class.UNIVERSAL,type:t.Type.SET,constructed:!0,capture:"values"}]},a={name:"CertBag",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"CertBag.certId",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"certId"},{name:"CertBag.certValue",tagClass:t.Class.CONTEXT_SPECIFIC,constructed:!0,value:[{name:"CertBag.certValue[0]",tagClass:t.Class.UNIVERSAL,type:t.Class.OCTETSTRING,constructed:!1,capture:"cert"}]}]};r.pkcs12FromAsn1=function(i,o,u){typeof o=="string"?(u=o,o=!0):o===undefined&&(o=!0);var a={},c=[];if(!t.validate(i,s,a,c))throw{message:"Cannot read PKCS#12 PFX. ASN.1 object is not an PKCS#12 PFX.",errors:c};var h={version:a.version.charCodeAt(0),safeContents:[],getBags:function(t){var n={},r;return"localKeyId"in t?r=t.localKeyId:"localKeyIdHex"in t&&(r=e.util.hexToBytes(t.localKeyIdHex)),r!==undefined&&(n.localKeyId=f(h.safeContents,"localKeyId",r,t.bagType)),"friendlyName"in t&&(n.friendlyName=f(h.safeContents,"friendlyName",t.friendlyName,t.bagType)),n},getBagsByFriendlyName:function(e,t){return f(h.safeContents,"friendlyName",e,t)},getBagsByLocalKeyId:function(e,t){return f(h.safeContents,"localKeyId",e,t)}};if(a.version.charCodeAt(0)!==3)throw{message:"PKCS#12 PFX of version other than 3 not supported.",version:a.version.charCodeAt(0)};if(t.derToOid(a.contentType)!==n.oids.data)throw{message:"Only PKCS#12 PFX in password integrity mode supported.",oid:t.derToOid(a.contentType)};var p=a.content.value[0];if(p.tagClass!==t.Class.UNIVERSAL||p.type!==t.Type.OCTETSTRING)throw{message:"PKCS#12 authSafe content data is not an OCTET STRING."};if(a.mac){var d=null,v=0,m=t.derToOid(a.macAlgorithm);switch(m){case n.oids.sha1:d=e.md.sha1.create(),v=20;break;case n.oids.sha256:d=e.md.sha256.create(),v=32;break;case n.oids.sha384:d=e.md.sha384.create(),v=48;break;case n.oids.sha512:d=e.md.sha512.create(),v=64;break;case n.oids.md5:d=e.md.md5.create(),v=16}if(d===null)throw{message:"PKCS#12 uses unsupported MAC algorithm: "+m};var g=new e.util.ByteBuffer(a.macSalt),y="macIterations"in a?parseInt(e.util.bytesToHex(a.macIterations),16):1,b=r.generateKey(u||"",g,3,y,v,d),w=e.hmac.create();w.start(d,b),w.update(p.value);var E=w.getMac();if(E.getBytes()!==a.macDigest)throw{message:"PKCS#12 MAC could not be verified. Invalid password?"}}return l(h,p.value,o,u),h},r.toPkcs12Asn1=function(i,s,o,u){u=u||{},u.saltSize=u.saltSize||8,u.count=u.count||2048,u.algorithm=u.algorithm||u.encAlgorithm||"aes128","useMac"in u||(u.useMac=!0),"localKeyId"in u||(u.localKeyId=null),"generateLocalKeyId"in u||(u.generateLocalKeyId=!0);var a=u.localKeyId,f=undefined;if(a!==null)a=e.util.hexToBytes(a);else if(u.generateLocalKeyId)if(s){var l=e.util.isArray(s)?s[0]:s;typeof l=="string"&&(l=n.certificateFromPem(l));var c=e.md.sha1.create();c.update(t.toDer(n.certificateToAsn1(l)).getBytes()),a=c.digest().getBytes()}else a=e.random.getBytes(20);var h=[];a!==null&&h.push(t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.oids.localKeyId).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.SET,!0,[t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,a)])])),"friendlyName"in u&&h.push(t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.oids.friendlyName).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.SET,!0,[t.create(t.Class.UNIVERSAL,t.Type.BMPSTRING,!1,u.friendlyName)])])),h.length>0&&(f=t.create(t.Class.UNIVERSAL,t.Type.SET,!0,h));var p=[],d=[];s!==null&&(e.util.isArray(s)?d=s:d=[s]);var v=[];for(var m=0;m<d.length;++m){s=d[m],typeof s=="string"&&(s=n.certificateFromPem(s));var g=m===0?f:undefined,y=n.certificateToAsn1(s),b=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.oids.certBag).getBytes()),t.create(t.Class.CONTEXT_SPECIFIC,0,!0,[t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.oids.x509Certificate).getBytes()),t.create(t.Class.CONTEXT_SPECIFIC,0,!0,[t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,t.toDer(y).getBytes())])])]),g]);v.push(b)}if(v.length>0){var w=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,v),E=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.oids.data).getBytes()),t.create(t.Class.CONTEXT_SPECIFIC,0,!0,[t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,t.toDer(w).getBytes())])]);p.push(E)}var S=null;if(i!==null){var x=n.wrapRsaPrivateKey(n.privateKeyToAsn1(i));o===null?S=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.oids.keyBag).getBytes()),t.create(t.Class.CONTEXT_SPECIFIC,0,!0,[x]),f]):S=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.oids.pkcs8ShroudedKeyBag).getBytes()),t.create(t.Class.CONTEXT_SPECIFIC,0,!0,[n.encryptPrivateKeyInfo(x,o,u)]),f]);var T=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[S]),N=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.oids.data).getBytes()),t.create(t.Class.CONTEXT_SPECIFIC,0,!0,[t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,t.toDer(T).getBytes())])]);p.push(N)}var C=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,p),k=undefined;if(u.useMac){var c=e.md.sha1.create(),L=new e.util.ByteBuffer(e.random.getBytes(u.saltSize)),A=u.count,i=r.generateKey(o||"",L,3,A,20),O=e.hmac.create();O.start(c,i),O.update(t.toDer(C).getBytes());var M=O.getMac();k=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.oids.sha1).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.NULL,!1,"")]),t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,M.getBytes())]),t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,L.getBytes()),t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,e.util.hexToBytes(A.toString(16)))])}return t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,String.fromCharCode(3)),t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.oids.data).getBytes()),t.create(t.Class.CONTEXT_SPECIFIC,0,!0,[t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,t.toDer(C).getBytes())])]),k])},r.generateKey=function(t,n,r,i,s,o){var u,a;if(typeof o=="undefined"||o===null)o=e.md.sha1.create();var f=o.digestLength,l=o.blockLength,c=new e.util.ByteBuffer,h=new e.util.ByteBuffer;for(a=0;a<t.length;a++)h.putInt16(t.charCodeAt(a));h.putInt16(0);var p=h.length(),d=n.length(),v=new e.util.ByteBuffer;v.fillWithByte(r,l);var m=l*Math.ceil(d/l),g=new e.util.ByteBuffer;for(a=0;a<m;a++)g.putByte(n.at(a%d));var y=l*Math.ceil(p/l),b=new e.util.ByteBuffer;for(a=0;a<y;a++)b.putByte(h.at(a%p));var w=g;w.putBuffer(b);var E=Math.ceil(s/f);for(var S=1;S<=E;S++){var x=new e.util.ByteBuffer;x.putBytes(v.bytes()),x.putBytes(w.bytes());for(var T=0;T<i;T++)o.start(),o.update(x.getBytes()),x=o.digest();var N=new e.util.ByteBuffer;for(a=0;a<l;a++)N.putByte(x.at(a%f));var C=Math.ceil(d/l)+Math.ceil(p/l),k=new e.util.ByteBuffer;for(u=0;u<C;u++){var L=new e.util.ByteBuffer(w.getBytes(l)),A=511;for(a=N.length()-1;a>=0;a--)A>>=8,A+=N.at(a)+L.at(a),L.setAt(a,A&255);k.putBuffer(L)}w=k,c.putBuffer(x)}return c.truncate(c.length()-s),c}}var t="pkcs12";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/pkcs12",["require","module","./asn1","./sha1","./pkcs7asn1","./pki","./util","./random","./hmac"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){var t=e.pss=e.pss||{};t.create=function(t,n,r){var i=t.digestLength,s={};return s.verify=function(s,o,u){var a,f=u-1,l=Math.ceil(f/8);o=o.substr(-l);if(l<i+r+2)throw{message:"Inconsistent parameters to PSS signature verification."};if(o.charCodeAt(l-1)!==188)throw{message:"Encoded message does not end in 0xBC."};var c=l-i-1,h=o.substr(0,c),p=o.substr(c,i),d=65280>>8*l-f&255;if((h.charCodeAt(0)&d)!==0)throw{message:"Bits beyond keysize not zero as expected."};var v=n.generate(p,c),m="";for(a=0;a<c;a++)m+=String.fromCharCode(h.charCodeAt(a)^v.charCodeAt(a));m=String.fromCharCode(m.charCodeAt(0)&~d)+m.substr(1);var g=l-i-r-2;for(a=0;a<g;a++)if(m.charCodeAt(a)!==0)throw{message:"Leftmost octets not zero as expected"};if(m.charCodeAt(g)!==1)throw{message:"Inconsistent PSS signature, 0x01 marker not found"};var y=m.substr(-r),b=new e.util.ByteBuffer;b.fillWithByte(0,8),b.putBytes(s),b.putBytes(y),t.start(),t.update(b.getBytes());var w=t.digest().getBytes();return p===w},s.encode=function(s,o){var u,a=o-1,f=Math.ceil(a/8),l=s.digest().getBytes();if(f<i+r+2)throw{message:"Message is too long to encrypt"};var c=e.random.getBytes(r),h=new e.util.ByteBuffer;h.fillWithByte(0,8),h.putBytes(l),h.putBytes(c),t.start(),t.update(h.getBytes());var p=t.digest().getBytes(),d=new e.util.ByteBuffer;d.fillWithByte(0,f-r-i-2),d.putByte(1),d.putBytes(c);var v=d.getBytes(),m=f-i-1,g=n.generate(p,m),y="";for(u=0;u<m;u++)y+=String.fromCharCode(v.charCodeAt(u)^g.charCodeAt(u));var b=65280>>8*f-a&255;return y=String.fromCharCode(y.charCodeAt(0)&~b)+y.substr(1),y+p+String.fromCharCode(188)},s}}var t="pss";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/pss",["require","module","./random","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){var t=[217,120,249,196,25,221,181,237,40,233,253,121,74,160,216,157,198,126,55,131,43,118,83,142,98,76,100,136,68,139,251,162,23,154,89,245,135,179,79,19,97,69,109,141,9,129,125,50,189,143,64,235,134,183,123,11,240,149,33,34,92,107,78,130,84,214,101,147,206,96,178,28,115,86,192,20,167,140,241,220,18,117,202,31,59,190,228,209,66,61,212,48,163,60,182,38,111,191,14,218,70,105,7,87,39,242,29,155,188,148,67,3,248,17,199,246,144,239,62,231,6,195,213,47,200,102,30,215,8,232,234,222,128,82,238,247,132,170,114,172,53,77,106,42,150,26,210,113,90,21,73,116,75,159,208,94,4,24,164,236,194,224,65,110,15,81,203,204,36,145,175,80,161,244,112,57,153,124,58,133,35,184,180,122,252,2,54,91,37,85,151,49,45,93,250,152,227,138,146,174,5,223,41,16,103,108,186,201,211,0,230,207,225,158,168,44,99,22,1,63,88,226,137,169,13,56,52,27,171,51,255,176,187,72,12,95,185,177,205,46,197,243,219,71,229,165,156,119,10,166,32,104,254,127,193,173],n=[1,2,3,5],r=function(e,t){return e<<t&65535|(e&65535)>>16-t},i=function(e,t){return(e&65535)>>t|e<<16-t&65535};e.rc2=e.rc2||{},e.rc2.expandKey=function(n,r){typeof n=="string"&&(n=e.util.createBuffer(n)),r=r||128;var i=n,s=n.length(),o=r,u=Math.ceil(o/8),a=255>>(o&7),f;for(f=s;f<128;f++)i.putByte(t[i.at(f-1)+i.at(f-s)&255]);i.setAt(128-u,t[i.at(128-u)&a]);for(f=127-u;f>=0;f--)i.setAt(f,t[i.at(f+1)^i.at(f+u)]);return i};var s=function(t,s,o){var u=!1,a=null,f=null,l=null,c,h,p,d,v=[];t=e.rc2.expandKey(t,s);for(p=0;p<64;p++)v.push(t.getInt16Le());o?(c=function(e){for(p=0;p<4;p++)e[p]+=v[d]+(e[(p+3)%4]&e[(p+2)%4])+(~e[(p+3)%4]&e[(p+1)%4]),e[p]=r(e[p],n[p]),d++},h=function(e){for(p=0;p<4;p++)e[p]+=v[e[(p+3)%4]&63]}):(c=function(e){for(p=3;p>=0;p--)e[p]=i(e[p],n[p]),e[p]-=v[d]+(e[(p+3)%4]&e[(p+2)%4])+(~e[(p+3)%4]&e[(p+1)%4]),d--},h=function(e){for(p=3;p>=0;p--)e[p]-=v[e[(p+3)%4]&63]});var m=function(e){var t=[];for(p=0;p<4;p++){var n=a.getInt16Le();l!==null&&(o?n^=l.getInt16Le():l.putInt16Le(n)),t.push(n&65535)}d=o?0:63;for(var r=0;r<e.length;r++)for(var i=0;i<e[r][0];i++)e[r][1](t);for(p=0;p<4;p++)l!==null&&(o?l.putInt16Le(t[p]):t[p]^=l.getInt16Le()),f.putInt16Le(t[p])},g=null;return g={start:function(n,r){n&&typeof t=="string"&&n.length===8&&(n=e.util.createBuffer(n)),u=!1,a=e.util.createBuffer(),f=r||new e.util.createBuffer,l=n,g.output=f},update:function(e){u||a.putBuffer(e);while(a.length()>=8)m([[5,c],[1,h],[6,c],[1,h],[5,c]])},finish:function(e){var t=!0;if(o)if(e)t=e(8,a,!o);else{var n=a.length()===8?8:8-a.length();a.fillWithByte(n,n)}t&&(u=!0,g.update());if(!o){t=a.length()===0;if(t)if(e)t=e(8,f,!o);else{var r=f.length(),i=f.at(r-1);i>r?t=!1:f.truncate(i)}}return t}},g};e.rc2.startEncrypting=function(t,n,r){var i=e.rc2.createEncryptionCipher(t,128);return i.start(n,r),i},e.rc2.createEncryptionCipher=function(e,t){return s(e,t,!0)},e.rc2.startDecrypting=function(t,n,r){var i=e.rc2.createDecryptionCipher(t,128);return i.start(n,r),i},e.rc2.createDecryptionCipher=function(e,t){return s(e,t,!1)}}var t="rc2";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/rc2",["require","module","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){function n(e,t,n){var r="",i=Math.ceil(t/n.digestLength);for(var s=0;s<i;++s){var o=String.fromCharCode(s>>24&255,s>>16&255,s>>8&255,s&255);n.start(),n.update(e+o),r+=n.digest().getBytes()}return r.substring(0,t)}var t=e.pkcs1=e.pkcs1||{};t.encode_rsa_oaep=function(t,r,i){var s=undefined,o=undefined,u=undefined;typeof i=="string"?(s=i,o=arguments[3]||undefined,u=arguments[4]||undefined):i&&(s=i.label||undefined,o=i.seed||undefined,u=i.md||undefined),u?u.start():u=e.md.sha1.create();var a=Math.ceil(t.n.bitLength()/8),f=a-2*u.digestLength-2;if(r.length>f)throw{message:"RSAES-OAEP input message length is too long.",length:r.length,maxLength:f};s||(s=""),u.update(s,"raw");var l=u.digest(),c="",h=f-r.length;for(var p=0;p<h;p++)c+="\0";var d=l.getBytes()+c+""+r;if(!o)o=e.random.getBytes(u.digestLength);else if(o.length!==u.digestLength)throw{message:"Invalid RSAES-OAEP seed. The seed length must match the digest length.",seedLength:o.length,digestLength:u.digestLength};var v=n(o,a-u.digestLength-1,u),m=e.util.xorBytes(d,v,d.length),g=n(m,u.digestLength,u),y=e.util.xorBytes(o,g,o.length);return"\0"+y+m},t.decode_rsa_oaep=function(t,r,i){var s=undefined,o=undefined;typeof i=="string"?(s=i,o=arguments[3]||undefined):i&&(s=i.label||undefined,o=i.md||undefined);var u=Math.ceil(t.n.bitLength()/8);if(r.length!==u)throw{message:"RSAES-OAEP encoded message length is invalid.",length:r.length,expectedLength:u};o===undefined?o=e.md.sha1.create():o.start();if(u<2*o.digestLength+2)throw{message:"RSAES-OAEP key is too short for the hash function."};s||(s=""),o.update(s,"raw");var a=o.digest().getBytes(),f=r.charAt(0),l=r.substring(1,o.digestLength+1),c=r.substring(1+o.digestLength),h=n(c,o.digestLength,o),p=e.util.xorBytes(l,h,l.length),d=n(p,u-o.digestLength-1,o),v=e.util.xorBytes(c,d,c.length),m=v.substring(0,o.digestLength),g=f!=="\0";for(var y=0;y<o.digestLength;++y)g|=a.charAt(y)!==m.charAt(y);var b=1,w=o.digestLength;for(var E=o.digestLength;E<v.length;E++){var S=v.charCodeAt(E),x=S&1^1,T=b?65534:0;g|=S&T,b&=x,w+=b}if(g||v.charCodeAt(w)!==1)throw{message:"Invalid RSAES-OAEP padding."};return v.substring(w+1)}}var t="pkcs1";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/pkcs1",["require","module","./util","./random","./sha1"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){function o(t,n,r){var i=e.util.createBuffer(),s=Math.ceil(n.n.bitLength()/8);if(t.length>s-11)throw{message:"Message is too long for PKCS#1 v1.5 padding.",length:t.length,max:s-11};i.putByte(0),i.putByte(r);var o=s-3-t.length,u;if(r===0||r===1){u=r===0?0:255;for(var a=0;a<o;++a)i.putByte(u)}else for(var a=0;a<o;++a)u=Math.floor(Math.random()*255)+1,i.putByte(u);return i.putByte(0),i.putBytes(t),i}function u(t,n,r,i){var s=Math.ceil(n.n.bitLength()/8),o=e.util.createBuffer(t),u=o.getByte(),a=o.getByte();if(u!==0||r&&a!==0&&a!==1||!r&&a!=2||r&&a===0&&typeof i=="undefined")throw{message:"Encryption block is invalid."};var f=0;if(a===0){f=s-3-i;for(var l=0;l<f;++l)if(o.getByte()!==0)throw{message:"Encryption block is invalid."}}else if(a===1){f=0;while(o.length()>1){if(o.getByte()!==255){--o.read;break}++f}}else if(a===2){f=0;while(o.length()>1){if(o.getByte()===0){--o.read;break}++f}}var c=o.getByte();if(c!==0||f!==s-3-o.length())throw{message:"Encryption block is invalid."};return o.getBytes()}function a(t,n,r){function c(){h(t.pBits,function(e,n){if(e)return r(e);t.p=n,h(t.qBits,p)})}function h(e,n){function p(){var n=e-1,r=new BigInteger(e,t.rng);return r.testBit(n)||r.bitwiseTo(BigInteger.ONE.shiftLeft(n),l,r),r.dAddOffset(31-r.mod(f).byteValue(),0),r}function v(i){if(d)return;--c;var s=i.data;if(s.found){for(var a=0;a<r.length;++a)r[a].terminate();return d=!0,n(null,new BigInteger(s.prime,16))}h.bitLength()>e&&(h=p());var f=h.toString(16);i.target.postMessage({e:t.eInt,hex:f,workLoad:o}),h.dAddOffset(u,0)}var r=[];for(var i=0;i<s;++i)r[i]=new Worker(a);var c=s,h=p();for(var i=0;i<s;++i)r[i].addEventListener("message",v);var d=!1}function p(n,i){t.q=i;if(t.p.compareTo(t.q)<0){var s=t.p;t.p=t.q,t.q=s}t.p1=t.p.subtract(BigInteger.ONE),t.q1=t.q.subtract(BigInteger.ONE),t.phi=t.p1.multiply(t.q1);if(t.phi.gcd(t.e).compareTo(BigInteger.ONE)!==0){t.p=t.q=null,c();return}t.n=t.p.multiply(t.q);if(t.n.bitLength()!==t.bits){t.q=null,h(t.qBits,p);return}var o=t.e.modInverse(t.phi);t.keys={privateKey:e.pki.rsa.setPrivateKey(t.n,t.e,o,t.p,t.q,o.mod(t.p1),o.mod(t.q1),t.q.modInverse(t.p)),publicKey:e.pki.rsa.setPublicKey(t.n,t.e)},r(null,t.keys)}typeof n=="function"&&(r=n,n={});if(typeof Worker=="undefined"){function i(){if(e.pki.rsa.stepKeyPairGenerationState(t,10))return r(null,t.keys);e.util.setImmediate(i)}return i()}var s=n.workers||2,o=n.workLoad||100,u=o*30/8,a=n.workerScript||"forge/prime.worker.js",f=new BigInteger(null);f.fromInt(30);var l=function(e,t){return e|t};c()}typeof BigInteger=="undefined"&&(BigInteger=e.jsbn.BigInteger);var t=e.asn1;e.pki=e.pki||{},e.pki.rsa=e.rsa=e.rsa||{};var n=e.pki,r=[6,4,2,4,2,4,6,2],i=function(n){var r;if(n.algorithm in e.pki.oids){r=e.pki.oids[n.algorithm];var i=t.oidToDer(r).getBytes(),s=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[]),o=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[]);o.value.push(t.create(t.Class.UNIVERSAL,t.Type.OID,!1,i)),o.value.push(t.create(t.Class.UNIVERSAL,t.Type.NULL,!1,""));var u=t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,n.digest().getBytes());return s.value.push(o),s.value.push(u),t.toDer(s).getBytes()}throw{message:"Unknown message digest algorithm.",algorithm:n.algorithm}},s=function(e,t,n){var r;if(n)r=e.modPow(t.e,t.n);else{t.dP||(t.dP=t.d.mod(t.p.subtract(BigInteger.ONE))),t.dQ||(t.dQ=t.d.mod(t.q.subtract(BigInteger.ONE))),t.qInv||(t.qInv=t.q.modInverse(t.p));var i=e.mod(t.p).modPow(t.dP,t.p),s=e.mod(t.q).modPow(t.dQ,t.q);while(i.compareTo(s)<0)i=i.add(t.p);r=i.subtract(s).multiply(t.qInv).mod(t.p).multiply(t.q).add(s)}return r};n.rsa.encrypt=function(t,n,r){var i=r,u,a=Math.ceil(n.n.bitLength()/8);r!==!1&&r!==!0?(i=r===2,u=o(t,n,r)):(u=e.util.createBuffer(),u.putBytes(t));var f=new BigInteger(u.toHex(),16),l=s(f,n,i),c=l.toString(16),h=e.util.createBuffer(),p=a-Math.ceil(c.length/2);while(p>0)h.putByte(0),--p;return h.putBytes(e.util.hexToBytes(c)),h.getBytes()},n.rsa.decrypt=function(t,n,r,i){var o=Math.ceil(n.n.bitLength()/8);if(t.length!==o)throw{message:"Encrypted message length is invalid.",length:t.length,expected:o};var a=new BigInteger(e.util.createBuffer(t).toHex(),16);if(a.compareTo(n.n)>=0)throw{message:"Encrypted message is invalid."};var f=s(a,n,r),l=f.toString(16),c=e.util.createBuffer(),h=o-Math.ceil(l.length/2);while(h>0)c.putByte(0),--h;return c.putBytes(e.util.hexToBytes(l)),i!==!1?u(c.getBytes(),n,r):c.getBytes()},n.rsa.createKeyPairGenerationState=function(t,n){typeof t=="string"&&(t=parseInt(t,10)),t=t||1024;var r={nextBytes:function(t){var n=e.random.getBytes(t.length);for(var r=0;r<t.length;++r)t[r]=n.charCodeAt(r)}},i={state:0,bits:t,rng:r,eInt:n||65537,e:new BigInteger(null),p:null,q:null,qBits:t>>1,pBits:t-(t>>1),pqState:0,num:null,keys:null};return i.e.fromInt(i.eInt),i},n.rsa.stepKeyPairGenerationState=function(t,n){var i=new BigInteger(null);i.fromInt(30);var s=0,o=function(e,t){return e|t},u=+(new Date),a,f=0;while(t.keys===null&&(n<=0||f<n)){if(t.state===0){var l=t.p===null?t.pBits:t.qBits,c=l-1;t.pqState===0?(t.num=new BigInteger(l,t.rng),t.num.testBit(c)||t.num.bitwiseTo(BigInteger.ONE.shiftLeft(c),o,t.num),t.num.dAddOffset(31-t.num.mod(i).byteValue(),0),s=0,++t.pqState):t.pqState===1?t.num.bitLength()>l?t.pqState=0:t.num.isProbablePrime(1)?++t.pqState:t.num.dAddOffset(r[s++%8],0):t.pqState===2?t.pqState=t.num.subtract(BigInteger.ONE).gcd(t.e).compareTo(BigInteger.ONE)===0?3:0:t.pqState===3&&(t.pqState=0,t.num.isProbablePrime(10)&&(t.p===null?t.p=t.num:t.q=t.num,t.p!==null&&t.q!==null&&++t.state),t.num=null)}else if(t.state===1)t.p.compareTo(t.q)<0&&(t.num=t.p,t.p=t.q,t.q=t.num),++t.state;else if(t.state===2)t.p1=t.p.subtract(BigInteger.ONE),t.q1=t.q.subtract(BigInteger.ONE),t.phi=t.p1.multiply(t.q1),++t.state;else if(t.state===3)t.phi.gcd(t.e).compareTo(BigInteger.ONE)===0?++t.state:(t.p=null,t.q=null,t.state=0);else if(t.state===4)t.n=t.p.multiply(t.q),t.n.bitLength()===t.bits?++t.state:(t.q=null,t.state=0);else if(t.state===5){var h=t.e.modInverse(t.phi);t.keys={privateKey:e.pki.rsa.setPrivateKey(t.n,t.e,h,t.p,t.q,h.mod(t.p1),h.mod(t.q1),t.q.modInverse(t.p)),publicKey:e.pki.rsa.setPublicKey(t.n,t.e)}}a=+(new Date),f+=a-u,u=a}return t.keys!==null},n.rsa.generateKeyPair=function(e,t,r,i){arguments.length===1?typeof e=="object"?(r=e,e=undefined):typeof e=="function"&&(i=e,e=undefined):arguments.length===2?(typeof e=="number"?typeof t=="function"?i=t:r=t:(r=e,i=t,e=undefined),t=undefined):arguments.length===3&&(typeof t=="number"?typeof r=="function"&&(i=r,r=undefined):(i=r,r=t,t=undefined)),r=r||{},e===undefined&&(e=r.bits||1024),t===undefined&&(t=r.e||65537);var s=n.rsa.createKeyPairGenerationState(e,t);if(!i)return n.rsa.stepKeyPairGenerationState(s,0),s.keys;a(s,r,i)},n.rsa.setPublicKey=function(r,i){var s={n:r,e:i};return s.encrypt=function(t,r,i){typeof r=="string"?r=r.toUpperCase():r===undefined&&(r="RSAES-PKCS1-V1_5");if(r==="RSAES-PKCS1-V1_5")r={encode:function(e,t,n){return o(e,t,2).getBytes()}};else if(r==="RSA-OAEP"||r==="RSAES-OAEP")r={encode:function(t,n){return e.pkcs1.encode_rsa_oaep(n,t,i)}};else{if(["RAW","NONE","NULL",null].indexOf(r)===-1)throw{message:'Unsupported encryption scheme: "'+r+'".'};r={encode:function(e){return e}}}var u=r.encode(t,s,!0);return n.rsa.encrypt(u,s,!0)},s.verify=function(e,r,i){typeof i=="string"?i=i.toUpperCase():i===undefined&&(i="RSASSA-PKCS1-V1_5");if(i==="RSASSA-PKCS1-V1_5")i={verify:function(e,n){n=u(n,s,!0);var r=t.fromDer(n);return e===r.value[1].value}};else if(i==="NONE"||i==="NULL"||i===null)i={verify:function(e,t){return t=u(t,s,!0),e===t}};var o=n.rsa.decrypt(r,s,!0,!1);return i.verify(e,o,s.n.bitLength())},s},n.rsa.setPrivateKey=function(t,r,s,o,a,f,l,c){var h={n:t,e:r,d:s,p:o,q:a,dP:f,dQ:l,qInv:c};return h.decrypt=function(t,r,i){typeof r=="string"?r=r.toUpperCase():r===undefined&&(r="RSAES-PKCS1-V1_5");var s=n.rsa.decrypt(t,h,!1,!1);if(r==="RSAES-PKCS1-V1_5")r={decode:u};else if(r==="RSA-OAEP"||r==="RSAES-OAEP")r={decode:function(t,n){return e.pkcs1.decode_rsa_oaep(n,t,i)}};else{if(["RAW","NONE","NULL",null].indexOf(r)===-1)throw{message:'Unsupported encryption scheme: "'+r+'".'};r={decode:function(e){return e}}}return r.decode(s,h,!1)},h.sign=function(e,t){var r=!1;typeof t=="string"&&(t=t.toUpperCase());if(t===undefined||t==="RSASSA-PKCS1-V1_5")t={encode:i},r=1;else if(t==="NONE"||t==="NULL"||t===null)t={encode:function(){return e}},r=1;var s=t.encode(e,h.n.bitLength());return n.rsa.encrypt(s,h,r)},h}}var t="rsa";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/rsa",["require","module","./asn1","./oids","./random","./util","./jsbn","./pkcs1"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){function w(n){var r=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[]),i,s,o=n.attributes;for(var u=0;u<o.length;++u){i=o[u];var a=i.value,f=t.Type.PRINTABLESTRING;"valueTagClass"in i&&(f=i.valueTagClass,f===t.Type.UTF8&&(a=e.util.encodeUtf8(a))),s=t.create(t.Class.UNIVERSAL,t.Type.SET,!0,[t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(i.type).getBytes()),t.create(t.Class.UNIVERSAL,f,!1,a)])]),r.value.push(s)}return r}function E(e){var n=t.create(t.Class.CONTEXT_SPECIFIC,3,!0,[]),r=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[]);n.value.push(r);var i,s;for(var o=0;o<e.length;++o){i=e[o],s=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[]),r.value.push(s),s.value.push(t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(i.id).getBytes())),i.critical&&s.value.push(t.create(t.Class.UNIVERSAL,t.Type.BOOLEAN,!1,String.fromCharCode(255)));var u=i.value;typeof i.value!="string"&&(u=t.toDer(u).getBytes()),s.value.push(t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,u))}return n}function S(e,n){switch(e){case r["RSASSA-PSS"]:var i=[];return n.hash.algorithmOid!==undefined&&i.push(t.create(t.Class.CONTEXT_SPECIFIC,0,!0,[t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.hash.algorithmOid).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.NULL,!1,"")])])),n.mgf.algorithmOid!==undefined&&i.push(t.create(t.Class.CONTEXT_SPECIFIC,1,!0,[t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.mgf.algorithmOid).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.mgf.hash.algorithmOid).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.NULL,!1,"")])])])),n.saltLength!==undefined&&i.push(t.create(t.Class.CONTEXT_SPECIFIC,2,!0,[t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,String.fromCharCode(n.saltLength))])),t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,i);default:return t.create(t.Class.UNIVERSAL,t.Type.NULL,!1,"")}}function x(n){var r=t.create(t.Class.CONTEXT_SPECIFIC,0,!0,[]);if(n.attributes.length===0)return r;var i=n.attributes;for(var s=0;s<i.length;++s){var o=i[s],u=o.value,a=t.Type.UTF8;"valueTagClass"in o&&(a=o.valueTagClass),a===t.Type.UTF8&&(u=e.util.encodeUtf8(u));var f=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(o.type).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.SET,!0,[t.create(t.Class.UNIVERSAL,a,!1,u)])]);r.value.push(f)}return r}function T(e,t,n){var r=[N(e+t)];for(var i=16,s=1;i<n;++s,i+=16)r.push(N(r[s-1]+e+t));return r.join("").substr(0,n)}function N(t){return e.md.md5.create().update(t).digest().getBytes()}typeof BigInteger=="undefined"&&(BigInteger=e.jsbn.BigInteger);var t=e.asn1,n=e.pki=e.pki||{},r=n.oids;n.pbe={};var i={};i.CN=r.commonName,i.commonName="CN",i.C=r.countryName,i.countryName="C",i.L=r.localityName,i.localityName="L",i.ST=r.stateOrProvinceName,i.stateOrProvinceName="ST",i.O=r.organizationName,i.organizationName="O",i.OU=r.organizationalUnitName,i.organizationalUnitName="OU",i.E=r.emailAddress,i.emailAddress="E";var s={name:"SubjectPublicKeyInfo",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,captureAsn1:"subjectPublicKeyInfo",value:[{name:"SubjectPublicKeyInfo.AlgorithmIdentifier",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"AlgorithmIdentifier.algorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"publicKeyOid"}]},{name:"SubjectPublicKeyInfo.subjectPublicKey",tagClass:t.Class.UNIVERSAL,type:t.Type.BITSTRING,constructed:!1,value:[{name:"SubjectPublicKeyInfo.subjectPublicKey.RSAPublicKey",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,optional:!0,captureAsn1:"rsaPublicKey"}]}]},o={name:"RSAPublicKey",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"RSAPublicKey.modulus",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"publicKeyModulus"},{name:"RSAPublicKey.exponent",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"publicKeyExponent"}]},u={name:"Certificate",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"Certificate.TBSCertificate",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,captureAsn1:"tbsCertificate",value:[{name:"Certificate.TBSCertificate.version",tagClass:t.Class.CONTEXT_SPECIFIC,type:0,constructed:!0,optional:!0,value:[{name:"Certificate.TBSCertificate.version.integer",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"certVersion"}]},{name:"Certificate.TBSCertificate.serialNumber",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"certSerialNumber"},{name:"Certificate.TBSCertificate.signature",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"Certificate.TBSCertificate.signature.algorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"certinfoSignatureOid"},{name:"Certificate.TBSCertificate.signature.parameters",tagClass:t.Class.UNIVERSAL,optional:!0,captureAsn1:"certinfoSignatureParams"}]},{name:"Certificate.TBSCertificate.issuer",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,captureAsn1:"certIssuer"},{name:"Certificate.TBSCertificate.validity",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"Certificate.TBSCertificate.validity.notBefore (utc)",tagClass:t.Class.UNIVERSAL,type:t.Type.UTCTIME,constructed:!1,optional:!0,capture:"certValidity1UTCTime"},{name:"Certificate.TBSCertificate.validity.notBefore (generalized)",tagClass:t.Class.UNIVERSAL,type:t.Type.GENERALIZEDTIME,constructed:!1,optional:!0,capture:"certValidity2GeneralizedTime"},{name:"Certificate.TBSCertificate.validity.notAfter (utc)",tagClass:t.Class.UNIVERSAL,type:t.Type.UTCTIME,constructed:!1,optional:!0,capture:"certValidity3UTCTime"},{name:"Certificate.TBSCertificate.validity.notAfter (generalized)",tagClass:t.Class.UNIVERSAL,type:t.Type.GENERALIZEDTIME,constructed:!1,optional:!0,capture:"certValidity4GeneralizedTime"}]},{name:"Certificate.TBSCertificate.subject",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,captureAsn1:"certSubject"},s,{name:"Certificate.TBSCertificate.issuerUniqueID",tagClass:t.Class.CONTEXT_SPECIFIC,type:1,constructed:!0,optional:!0,value:[{name:"Certificate.TBSCertificate.issuerUniqueID.id",tagClass:t.Class.UNIVERSAL,type:t.Type.BITSTRING,constructed:!1,capture:"certIssuerUniqueId"}]},{name:"Certificate.TBSCertificate.subjectUniqueID",tagClass:t.Class.CONTEXT_SPECIFIC,type:2,constructed:!0,optional:!0,value:[{name:"Certificate.TBSCertificate.subjectUniqueID.id",tagClass:t.Class.UNIVERSAL,type:t.Type.BITSTRING,constructed:!1,capture:"certSubjectUniqueId"}]},{name:"Certificate.TBSCertificate.extensions",tagClass:t.Class.CONTEXT_SPECIFIC,type:3,constructed:!0,captureAsn1:"certExtensions",optional:!0}]},{name:"Certificate.signatureAlgorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"Certificate.signatureAlgorithm.algorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"certSignatureOid"},{name:"Certificate.TBSCertificate.signature.parameters",tagClass:t.Class.UNIVERSAL,optional:!0,captureAsn1:"certSignatureParams"}]},{name:"Certificate.signatureValue",tagClass:t.Class.UNIVERSAL,type:t.Type.BITSTRING,constructed:!1,capture:"certSignature"}]},a={name:"PrivateKeyInfo",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"PrivateKeyInfo.version",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"privateKeyVersion"},{name:"PrivateKeyInfo.privateKeyAlgorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"AlgorithmIdentifier.algorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"privateKeyOid"}]},{name:"PrivateKeyInfo",tagClass:t.Class.UNIVERSAL,type:t.Type.OCTETSTRING,constructed:!1,capture:"privateKey"}]},f={name:"RSAPrivateKey",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"RSAPrivateKey.version",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"privateKeyVersion"},{name:"RSAPrivateKey.modulus",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"privateKeyModulus"},{name:"RSAPrivateKey.publicExponent",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"privateKeyPublicExponent"},{name:"RSAPrivateKey.privateExponent",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"privateKeyPrivateExponent"},{name:"RSAPrivateKey.prime1",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"privateKeyPrime1"},{name:"RSAPrivateKey.prime2",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"privateKeyPrime2"},{name:"RSAPrivateKey.exponent1",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"privateKeyExponent1"},{name:"RSAPrivateKey.exponent2",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"privateKeyExponent2"},{name:"RSAPrivateKey.coefficient",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"privateKeyCoefficient"}]},l={name:"EncryptedPrivateKeyInfo",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"EncryptedPrivateKeyInfo.encryptionAlgorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"AlgorithmIdentifier.algorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"encryptionOid"},{name:"AlgorithmIdentifier.parameters",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,captureAsn1:"encryptionParams"}]},{name:"EncryptedPrivateKeyInfo.encryptedData",tagClass:t.Class.UNIVERSAL,type:t.Type.OCTETSTRING,constructed:!1,capture:"encryptedData"}]},c={name:"PBES2Algorithms",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"PBES2Algorithms.keyDerivationFunc",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"PBES2Algorithms.keyDerivationFunc.oid",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"kdfOid"},{name:"PBES2Algorithms.params",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"PBES2Algorithms.params.salt",tagClass:t.Class.UNIVERSAL,type:t.Type.OCTETSTRING,constructed:!1,capture:"kdfSalt"},{name:"PBES2Algorithms.params.iterationCount",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,onstructed:!0,capture:"kdfIterationCount"}]}]},{name:"PBES2Algorithms.encryptionScheme",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"PBES2Algorithms.encryptionScheme.oid",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"encOid"},{name:"PBES2Algorithms.encryptionScheme.iv",tagClass:t.Class.UNIVERSAL,type:t.Type.OCTETSTRING,constructed:!1,capture:"encIv"}]}]},h={name:"pkcs-12PbeParams",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"pkcs-12PbeParams.salt",tagClass:t.Class.UNIVERSAL,type:t.Type.OCTETSTRING,constructed:!1,capture:"salt"},{name:"pkcs-12PbeParams.iterations",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"iterations"}]},p={name:"rsapss",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"rsapss.hashAlgorithm",tagClass:t.Class.CONTEXT_SPECIFIC,type:0,constructed:!0,value:[{name:"rsapss.hashAlgorithm.AlgorithmIdentifier",tagClass:t.Class.UNIVERSAL,type:t.Class.SEQUENCE,constructed:!0,optional:!0,value:[{name:"rsapss.hashAlgorithm.AlgorithmIdentifier.algorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"hashOid"}]}]},{name:"rsapss.maskGenAlgorithm",tagClass:t.Class.CONTEXT_SPECIFIC,type:1,constructed:!0,value:[{name:"rsapss.maskGenAlgorithm.AlgorithmIdentifier",tagClass:t.Class.UNIVERSAL,type:t.Class.SEQUENCE,constructed:!0,optional:!0,value:[{name:"rsapss.maskGenAlgorithm.AlgorithmIdentifier.algorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"maskGenOid"},{name:"rsapss.maskGenAlgorithm.AlgorithmIdentifier.params",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"rsapss.maskGenAlgorithm.AlgorithmIdentifier.params.algorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"maskGenHashOid"}]}]}]},{name:"rsapss.saltLength",tagClass:t.Class.CONTEXT_SPECIFIC,type:2,optional:!0,value:[{name:"rsapss.saltLength.saltLength",tagClass:t.Class.UNIVERSAL,type:t.Class.INTEGER,constructed:!1,capture:"saltLength"}]},{name:"rsapss.trailerField",tagClass:t.Class.CONTEXT_SPECIFIC,type:3,optional:!0,value:[{name:"rsapss.trailer.trailer",tagClass:t.Class.UNIVERSAL,type:t.Class.INTEGER,constructed:!1,capture:"trailer"}]}]},d={name:"CertificationRequestInfo",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,captureAsn1:"certificationRequestInfo",value:[{name:"CertificationRequestInfo.integer",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"certificationRequestInfoVersion"},{name:"CertificationRequestInfo.subject",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,captureAsn1:"certificationRequestInfoSubject"},s,{name:"CertificationRequestInfo.attributes",tagClass:t.Class.CONTEXT_SPECIFIC,type:0,constructed:!0,optional:!0,capture:"certificationRequestInfoAttributes",value:[{name:"CertificationRequestInfo.attributes",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"CertificationRequestInfo.attributes.type",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1},{name:"CertificationRequestInfo.attributes.value",tagClass:t.Class.UNIVERSAL,type:t.Type.SET,constructed:!0}]}]}]},v={name:"CertificationRequest",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,captureAsn1:"csr",value:[d,{name:"CertificationRequest.signatureAlgorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"CertificationRequest.signatureAlgorithm.algorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"csrSignatureOid"},{name:"CertificationRequest.signatureAlgorithm.parameters",tagClass:t.Class.UNIVERSAL,optional:!0,captureAsn1:"csrSignatureParams"}]},{name:"CertificationRequest.signature",tagClass:t.Class.UNIVERSAL,type:t.Type.BITSTRING,constructed:!1,capture:"csrSignature"}]};n.RDNAttributesAsArray=function(e,n){var s=[],o,u,a;for(var f=0;f<e.value.length;++f){o=e.value[f];for(var l=0;l<o.value.length;++l)a={},u=o.value[l],a.type=t.derToOid(u.value[0].value),a.value=u.value[1].value,a.valueTagClass=u.value[1].type,a.type in r&&(a.name=r[a.type],a.name in i&&(a.shortName=i[a.name])),n&&(n.update(a.type),n.update(a.value)),s.push(a)}return s},n.CRIAttributesAsArray=function(e){var n=[];for(var s=0;s<e.length;++s){var o=e[s],u=t.derToOid(o.value[0].value),a=o.value[1].value;for(var f=0;f<a.length;++f){var l={};l.type=u,l.value=a[f].value,l.valueTagClass=a[f].type,l.type in r&&(l.name=r[l.type],l.name in i&&(l.shortName=i[l.name])),n.push(l)}}return n};var m=function(e,t){typeof t=="string"&&(t={shortName:t});var n=null,r;for(var i=0;n===null&&i<e.attributes.length;++i)r=e.attributes[i],t.type&&t.type===r.type?n=r:t.name&&t.name===r.name?n=r:t.shortName&&t.shortName===r.shortName&&(n=r);return n},g=function(n){var i=[],s,o,u;for(var a=0;a<n.value.length;++a){u=n.value[a];for(var f=0;f<u.value.length;++f){o=u.value[f],s={},s.id=t.derToOid(o.value[0].value),s.critical=!1,o.value[1].type===t.Type.BOOLEAN?(s.critical=o.value[1].value.charCodeAt(0)!==0,s.value=o.value[2].value):s.value=o.value[1].value;if(s.id in r){s.name=r[s.id];if(s.name==="keyUsage"){var l=t.fromDer(s.value),c=0,h=0;l.value.length>1&&(c=l.value.charCodeAt(1),h=l.value.length>2?l.value.charCodeAt(2):0),s.digitalSignature=(c&128)===128,s.nonRepudiation=(c&64)===64,s.keyEncipherment=(c&32)===32,s.dataEncipherment=(c&16)===16,s.keyAgreement=(c&8)===8,s.keyCertSign=(c&4)===4,s.cRLSign=(c&2)===2,s.encipherOnly=(c&1)===1,s.decipherOnly=(h&128)===128}else if(s.name==="basicConstraints"){var l=t.fromDer(s.value);l.value.length>0?s.cA=l.value[0].value.charCodeAt(0)!==0:s.cA=!1;if(l.value.length>1){var p=e.util.createBuffer(l.value[1].value);s.pathLenConstraint=p.getInt(p.length()<<3)}}else if(s.name==="extKeyUsage"){var l=t.fromDer(s.value);for(var d=0;d<l.value.length;++d){var v=t.derToOid(l.value[d].value);v in r?s[r[v]]=!0:s[v]=!0}}else if(s.name==="nsCertType"){var l=t.fromDer(s.value),c=0;l.value.length>1&&(c=l.value.charCodeAt(1)),s.client=(c&128)===128,s.server=(c&64)===64,s.email=(c&32)===32,s.objsign=(c&16)===16,s.reserved=(c&8)===8,s.sslCA=(c&4)===4,s.emailCA=(c&2)===2,s.objCA=(c&1)===1}else if(s.name==="subjectAltName"||s.name==="issuerAltName"){s.altNames=[];var m,l=t.fromDer(s.value);for(var g=0;g<l.value.length;++g){m=l.value[g];var y={type:m.type,value:m.value};s.altNames.push(y);switch(m.type){case 1:case 2:case 6:break;case 7:break;case 8:y.oid=t.derToOid(m.value);break;default:}}}else if(s.name==="subjectKeyIdentifier"){var l=t.fromDer(s.value);s.subjectKeyIdentifier=e.util.bytesToHex(l.value)}}i.push(s)}}return i};n.pemToDer=function(t){var n=e.pem.decode(t)[0];if(n.procType&&n.procType.type==="ENCRYPTED")throw{message:"Could not convert PEM to DER; PEM is encrypted."};return e.util.createBuffer(n.body)};var y=function(t){var n=t.toString(16);return n[0]>="8"&&(n="00"+n),e.util.hexToBytes(n)},b=function(e,n,i){var s={};if(e!==r["RSASSA-PSS"])return s;i&&(s={hash:{algorithmOid:r.sha1},mgf:{algorithmOid:r.mgf1,hash:{algorithmOid:r.sha1}},saltLength:20});var o={},u=[];if(!t.validate(n,p,o,u))throw{message:"Cannot read RSASSA-PSS parameter block.",errors:u};return o.hashOid!==undefined&&(s.hash=s.hash||{},s.hash.algorithmOid=t.derToOid(o.hashOid)),o.maskGenOid!==undefined&&(s.mgf=s.mgf||{},s.mgf.algorithmOid=t.derToOid(o.maskGenOid),s.mgf.hash=s.mgf.hash||{},s.mgf.hash.algorithmOid=t.derToOid(o.maskGenHashOid)),o.saltLength!==undefined&&(s.saltLength=o.saltLength.charCodeAt(0)),s};n.certificateFromPem=function(r,i,s){var o=e.pem.decode(r)[0];if(o.type!=="CERTIFICATE"&&o.type!=="X509 CERTIFICATE"&&o.type!=="TRUSTED CERTIFICATE")throw{message:'Could not convert certificate from PEM; PEM header type is not "CERTIFICATE", "X509 CERTIFICATE", or "TRUSTED CERTIFICATE".',headerType:o.type};if(o.procType&&o.procType.type==="ENCRYPTED")throw{message:"Could not convert certificate from PEM; PEM is encrypted."};var u=t.fromDer(o.body,s);return n.certificateFromAsn1(u,i)},n.certificateToPem=function(r,i){var s={type:"CERTIFICATE",body:t.toDer(n.certificateToAsn1(r)).getBytes()};return e.pem.encode(s,{maxline:i})},n.publicKeyFromPem=function(r){var i=e.pem.decode(r)[0];if(i.type!=="PUBLIC KEY"&&i.type!=="RSA PUBLIC KEY")throw{message:'Could not convert public key from PEM; PEM header type is not "PUBLIC KEY" or "RSA PUBLIC KEY".',headerType:i.type};if(i.procType&&i.procType.type==="ENCRYPTED")throw{message:"Could not convert public key from PEM; PEM is encrypted."};var s=t.fromDer(i.body);return n.publicKeyFromAsn1(s)},n.publicKeyToPem=function(r,i){var s={type:"PUBLIC KEY",body:t.toDer(n.publicKeyToAsn1(r)).getBytes()};return e.pem.encode(s,{maxline:i})},n.publicKeyToRSAPublicKeyPem=function(r,i){var s={type:"RSA PUBLIC KEY",body:t.toDer(n.publicKeyToRSAPublicKey(r)).getBytes()};return e.pem.encode(s,{maxline:i})},n.privateKeyFromPem=function(r){var i=e.pem.decode(r)[0];if(i.type!=="PRIVATE KEY"&&i.type!=="RSA PRIVATE KEY")throw{message:'Could not convert private key from PEM; PEM header type is not "PRIVATE KEY" or "RSA PRIVATE KEY".',headerType:i.type};if(i.procType&&i.procType.type==="ENCRYPTED")throw{message:"Could not convert private key from PEM; PEM is encrypted."};var s=t.fromDer(i.body);return n.privateKeyFromAsn1(s)},n.privateKeyToPem=function(r,i){var s={type:"RSA PRIVATE KEY",body:t.toDer(n.privateKeyToAsn1(r)).getBytes()};return e.pem.encode(s,{maxline:i})},n.certificationRequestFromPem=function(r,i,s){var o=e.pem.decode(r)[0];if(o.type!=="CERTIFICATE REQUEST")throw{message:'Could not convert certification request from PEM; PEM header type is not "CERTIFICATE REQUEST".',headerType:o.type};if(o.procType&&o.procType.type==="ENCRYPTED")throw{message:"Could not convert certification request from PEM; PEM is encrypted."};var u=t.fromDer(o.body,s);return n.certificationRequestFromAsn1(u,i)},n.certificationRequestToPem=function(r,i){var s={type:"CERTIFICATE REQUEST",body:t.toDer(n.certificationRequestToAsn1(r)).getBytes()};return e.pem.encode(s,{maxline:i})},n.createCertificate=function(){var s={};s.version=2,s.serialNumber="00",s.signatureOid=null,s.signature=null,s.siginfo={},s.siginfo.algorithmOid=null,s.validity={},s.validity.notBefore=new Date,s.validity.notAfter=new Date,s.issuer={},s.issuer.getField=function(e){return m(s.issuer,e)},s.issuer.addField=function(e){o([e]),s.issuer.attributes.push(e)},s.issuer.attributes=[],s.issuer.hash=null,s.subject={},s.subject.getField=function(e){return m(s.subject,e)},s.subject.addField=function(e){o([e]),s.subject.attributes.push(e)},s.subject.attributes=[],s.subject.hash=null,s.extensions=[],s.publicKey=null,s.md=null;var o=function(e){var t;for(var r=0;r<e.length;++r){t=e[r],typeof t.name=="undefined"&&(t.type&&t.type in n.oids?t.name=n.oids[t.type]:t.shortName&&t.shortName in i&&(t.name=n.oids[i[t.shortName]]));if(typeof t.type=="undefined"){if(!(t.name&&t.name in n.oids))throw{message:"Attribute type not specified.",attribute:t};t.type=n.oids[t.name]}typeof t.shortName=="undefined"&&t.name&&t.name in i&&(t.shortName=i[t.name]);if(typeof t.value=="undefined")throw{message:"Attribute value not specified.",attribute:t}}};return s.setSubject=function(e,t){o(e),s.subject.attributes=e,delete s.subject.uniqueId,t&&(s.subject.uniqueId=t),s.subject.hash=null},s.setIssuer=function(e,t){o(e),s.issuer.attributes=e,delete s.issuer.uniqueId,t&&(s.issuer.uniqueId=t),s.issuer.hash=null},s.setExtensions=function(i){var o;for(var u=0;u<i.length;++u){o=i[u],typeof o.name=="undefined"&&o.id&&o.id in n.oids&&(o.name=n.oids[o.id]);if(typeof o.id=="undefined"){if(!(o.name&&o.name in n.oids))throw{message:"Extension ID not specified.",extension:o};o.id=n.oids[o.name]}if(typeof o.value=="undefined"){if(o.name==="keyUsage"){var a=0,f=0,l=0;o.digitalSignature&&(f|=128,a=7),o.nonRepudiation&&(f|=64,a=6),o.keyEncipherment&&(f|=32,a=5),o.dataEncipherment&&(f|=16,a=4),o.keyAgreement&&(f|=8,a=3),o.keyCertSign&&(f|=4,a=2),o.cRLSign&&(f|=2,a=1),o.encipherOnly&&(f|=1,a=0),o.decipherOnly&&(l|=128,a=7);var c=String.fromCharCode(a);l!==0?c+=String.fromCharCode(f)+String.fromCharCode(l):f!==0&&(c+=String.fromCharCode(f)),o.value=t.create(t.Class.UNIVERSAL,t.Type.BITSTRING,!1,c)}else if(o.name==="basicConstraints"){o.value=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[]),o.cA&&o.value.value.push(t.create(t.Class.UNIVERSAL,t.Type.BOOLEAN,!1,String.fromCharCode(255)));if(o.pathLenConstraint){var h=o.pathLenConstraint,p=e.util.createBuffer();p.putInt(h,h.toString(2).length),o.value.value.push(t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,p.getBytes()))}}else if(o.name==="extKeyUsage"){o.value=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[]);var d=o.value.value;for(var v in o){if(o[v]!==!0)continue;v in r?d.push(t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(r[v]).getBytes())):v.indexOf(".")!==-1&&d.push(t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(v).getBytes()))}}else if(o.name==="nsCertType"){var a=0,f=0;o.client&&(f|=128,a=7),o.server&&(f|=64,a=6),o.email&&(f|=32,a=5),o.objsign&&(f|=16,a=4),o.reserved&&(f|=8,a=3),o.sslCA&&(f|=4,a=2),o.emailCA&&(f|=2,a=1),o.objCA&&(f|=1,a=0);var c=String.fromCharCode(a);f!==0&&(c+=String.fromCharCode(f)),o.value=t.create(t.Class.UNIVERSAL,t.Type.BITSTRING,!1,c)}else if(o.name==="subjectAltName"||o.name==="issuerAltName"){o.value=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[]);var m;for(var g=0;g<o.altNames.length;++g){m=o.altNames[g];var c=m.value;m.type===8&&(c=t.oidToDer(c)),o.value.value.push(t.create(t.Class.CONTEXT_SPECIFIC,m.type,!1,c))}}else if(o.name==="subjectKeyIdentifier"){var y=s.generateSubjectKeyIdentifier();o.subjectKeyIdentifier=y.toHex(),o.value=t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,y.getBytes())}if(typeof o.value=="undefined")throw{message:"Extension value not specified.",extension:o}}}s.extensions=i},s.getExtension=function(e){typeof e=="string"&&(e={name:e});var t=null,n;for(var r=0;t===null&&r<s.extensions.length;++r)n=s.extensions[r],e.id&&n.id===e.id?t=n:e.name&&n.name===e.name&&(t=n);return t},s.sign=function(i,o){s.md=o||e.md.sha1.create();var u=r[s.md.algorithm+"WithRSAEncryption"];if(!u)throw{message:"Could not compute certificate digest. Unknown message digest algorithm OID.",algorithm:s.md.algorithm};s.signatureOid=s.siginfo.algorithmOid=u,s.tbsCertificate=n.getTBSCertificate(s);var a=t.toDer(s.tbsCertificate);s.md.update(a.getBytes()),s.signature=i.sign(s.md)},s.verify=function(i){var o=!1,u=i.md;if(u===null){if(i.signatureOid in r){var a=r[i.signatureOid];switch(a){case"sha1WithRSAEncryption":u=e.md.sha1.create();break;case"md5WithRSAEncryption":u=e.md.md5.create();break;case"sha256WithRSAEncryption":u=e.md.sha256.create();break;case"RSASSA-PSS":u=e.md.sha256.create()}}if(u===null)throw{message:"Could not compute certificate digest. Unknown signature OID.",signatureOid:i.signatureOid};var f=i.tbsCertificate||n.getTBSCertificate(i),l=t.toDer(f);u.update(l.getBytes())}if(u!==null){var c=undefined;switch(i.signatureOid){case r.sha1WithRSAEncryption:c=undefined;break;case r["RSASSA-PSS"]:var h,p;h=r[i.signatureParameters.mgf.hash.algorithmOid];if(h===undefined||e.md[h]===undefined)throw{message:"Unsupported MGF hash function.",oid:i.signatureParameters.mgf.hash.algorithmOid,name:h};p=r[i.signatureParameters.mgf.algorithmOid];if(p===undefined||e.mgf[p]===undefined)throw{message:"Unsupported MGF function.",oid:i.signatureParameters.mgf.algorithmOid,name:p};p=e.mgf[p].create(e.md[h].create()),h=r[i.signatureParameters.hash.algorithmOid];if(h===undefined||e.md[h]===undefined)throw{message:"Unsupported RSASSA-PSS hash function.",oid:i.signatureParameters.hash.algorithmOid,name:h};c=e.pss.create(e.md[h].create(),p,i.signatureParameters.saltLength)}o=s.publicKey.verify(u.digest().getBytes(),i.signature,c)}return o},s.isIssuer=function(e){var t=!1,n=s.issuer,r=e.subject;if(n.hash&&r.hash)t=n.hash===r.hash;else if(n.attributes.length===r.attributes.length){t=!0;var i,o;for(var u=0;t&&u<n.attributes.length;++u){i=n.attributes[u],o=r.attributes[u];if(i.type!==o.type||i.value!==o.value)t=!1}}return t},s.generateSubjectKeyIdentifier=function(){var r=t.toDer(n.publicKeyToRSAPublicKey(s.publicKey)),i=e.md.sha1.create();return i.update(r.getBytes()),i.digest()},s.verifySubjectKeyIdentifier=function(){var t=!1,n=r.subjectKeyIdentifier;for(var i=0;i<s.extensions.length;++i){var o=s.extensions[i];if(o.id===n){var u=s.generateSubjectKeyIdentifier().getBytes();return e.util.hexToBytes(o.subjectKeyIdentifier)===u}}return!1},s},n.certificateFromAsn1=function(i,s){var o={},a=[];if(!t.validate(i,u,o,a))throw{message:"Cannot read X.509 certificate. ASN.1 object is not an X509v3 Certificate.",errors:a};if(typeof o.certSignature!="string"){var f="\0";for(var l=0;l<o.certSignature.length;++l)f+=t.toDer(o.certSignature[l]).getBytes();o.certSignature=f}var c=t.derToOid(o.publicKeyOid);if(c!==n.oids.rsaEncryption)throw{message:"Cannot read public key. OID is not RSA."};var h=n.createCertificate();h.version=o.certVersion?o.certVersion.charCodeAt(0):0;var p=e.util.createBuffer(o.certSerialNumber);h.serialNumber=p.toHex(),h.signatureOid=e.asn1.derToOid(o.certSignatureOid),h.signatureParameters=b(h.signatureOid,o.certSignatureParams,!0),h.siginfo.algorithmOid=e.asn1.derToOid(o.certinfoSignatureOid),h.siginfo.parameters=b(h.siginfo.algorithmOid,o.certinfoSignatureParams,!1);var d=e.util.createBuffer(o.certSignature);++d.read,h.signature=d.getBytes();var v=[];o.certValidity1UTCTime!==undefined&&v.push(t.utcTimeToDate(o.certValidity1UTCTime)),o.certValidity2GeneralizedTime!==undefined&&v.push(t.generalizedTimeToDate(o.certValidity2GeneralizedTime)),o.certValidity3UTCTime!==undefined&&v.push(t.utcTimeToDate(o.certValidity3UTCTime)),o.certValidity4GeneralizedTime!==undefined&&v.push(t.generalizedTimeToDate(o.certValidity4GeneralizedTime));if(v.length>2)throw{message:"Cannot read notBefore/notAfter validity times; more than two times were provided in the certificate."};if(v.length<2)throw{message:"Cannot read notBefore/notAfter validity times; they were not provided as either UTCTime or GeneralizedTime."};h.validity.notBefore=v[0],h.validity.notAfter=v[1],h.tbsCertificate=o.tbsCertificate;if(s){h.md=null;if(h.signatureOid in r){var c=r[h.signatureOid];switch(c){case"sha1WithRSAEncryption":h.md=e.md.sha1.create();break;case"md5WithRSAEncryption":h.md=e.md.md5.create();break;case"sha256WithRSAEncryption":h.md=e.md.sha256.create();break;case"RSASSA-PSS":h.md=e.md.sha256.create()}}if(h.md===null)throw{message:"Could not compute certificate digest. Unknown signature OID.",signatureOid:h.signatureOid};var y=t.toDer(h.tbsCertificate);h.md.update(y.getBytes())}var w=e.md.sha1.create();h.issuer.getField=function(e){return m(h.issuer,e)},h.issuer.addField=function(e){_fillMissingFields([e]),h.issuer.attributes.push(e)},h.issuer.attributes=n.RDNAttributesAsArray(o.certIssuer,w),o.certIssuerUniqueId&&(h.issuer.uniqueId=o.certIssuerUniqueId),h.issuer.hash=w.digest().toHex();var E=e.md.sha1.create();return h.subject.getField=function(e){return m(h.subject,e)},h.subject.addField=function(e){_fillMissingFields([e]),h.subject.attributes.push(e)},h.subject.attributes=n.RDNAttributesAsArray(o.certSubject,E),o.certSubjectUniqueId&&(h.subject.uniqueId=o.certSubjectUniqueId),h.subject.hash=E.digest().toHex(),o.certExtensions?h.extensions=g(o.certExtensions):h.extensions=[],h.publicKey=n.publicKeyFromAsn1(o.subjectPublicKeyInfo),h},n.certificationRequestFromAsn1=function(i,s){var o={},u=[];if(!t.validate(i,v,o,u))throw{message:"Cannot read PKCS#10 certificate request. ASN.1 object is not a PKCS#10 CertificationRequest.",errors:u};if(typeof o.csrSignature!="string"){var a="\0";for(var f=0;f<o.csrSignature.length;++f)a+=t.toDer(o.csrSignature[f]).getBytes();o.csrSignature=a}var l=t.derToOid(o.publicKeyOid);if(l!==n.oids.rsaEncryption)throw{message:"Cannot read public key. OID is not RSA."};var c=n.createCertificationRequest();c.version=o.csrVersion?o.csrVersion.charCodeAt(0):0,c.signatureOid=e.asn1.derToOid(o.csrSignatureOid),c.signatureParameters=b(c.signatureOid,o.csrSignatureParams,!0),c.siginfo.algorithmOid=e.asn1.derToOid(o.csrSignatureOid),c.siginfo.parameters=b(c.siginfo.algorithmOid,o.csrSignatureParams,!1);var h=e.util.createBuffer(o.csrSignature);++h.read,c.signature=h.getBytes(),c.certificationRequestInfo=o.certificationRequestInfo;if(s){c.md=null;if(c.signatureOid in r){var l=r[c.signatureOid];switch(l){case"sha1WithRSAEncryption":c.md=e.md.sha1.create();break;case"md5WithRSAEncryption":c.md=e.md.md5.create();break;case"sha256WithRSAEncryption":c.md=e.md.sha256.create();break;case"RSASSA-PSS":c.md=e.md.sha256.create()}}if(c.md===null)throw{message:"Could not compute certification request digest. Unknown signature OID.",signatureOid:c.signatureOid};var p=t.toDer(c.certificationRequestInfo);c.md.update(p.getBytes())}var d=e.md.sha1.create();return c.subject.getField=function(e){return m(c.subject,e)},c.subject.addField=function(e){_fillMissingFields([e]),c.subject.attributes.push(e)},c.subject.attributes=n.RDNAttributesAsArray(o.certificationRequestInfoSubject,d),c.subject.hash=d.digest().toHex(),c.publicKey=n.publicKeyFromAsn1(o.subjectPublicKeyInfo),c.getAttribute=function(e){return m(c.attributes,e)},c.addAttribute=function(e){_fillMissingFields([e]),c.attributes.push(e)},c.attributes=n.CRIAttributesAsArray(o.certificationRequestInfoAttributes),c},n.createCertificationRequest=function(){var s={};s.version=0,s.signatureOid=null,s.signature=null,s.siginfo={},s.siginfo.algorithmOid=null,s.subject={},s.subject.getField=function(e){return m(s.subject,e)},s.subject.addField=function(e){o([e]),s.subject.attributes.push(e)},s.subject.attributes=[],s.subject.hash=null,s.publicKey=null,s.attributes=[],s.getAttribute=function(e){return m(s.attributes,e)},s.addAttribute=function(e){o([e]),s.attributes.push(e)},s.md=null;var o=function(e){var t;for(var r=0;r<e.length;++r){t=e[r],typeof t.name=="undefined"&&(t.type&&t.type in n.oids?t.name=n.oids[t.type]:t.shortName&&t.shortName in i&&(t.name=n.oids[i[t.shortName]]));if(typeof t.type=="undefined"){if(!(t.name&&t.name in n.oids))throw{message:"Attribute type not specified.",attribute:t};t.type=n.oids[t.name]}typeof t.shortName=="undefined"&&t.name&&t.name in i&&(t.shortName=i[t.name]);if(typeof t.value=="undefined")throw{message:"Attribute value not specified.",attribute:t}}};return s.setSubject=function(e){o(e),s.subject.attributes=e,s.subject.hash=null},s.setAttributes=function(e){o(e),s.attributes=e},s.sign=function(i,o){s.md=o||e.md.sha1.create();var u=r[s.md.algorithm+"WithRSAEncryption"];if(!u)throw{message:"Could not compute certification request digest. Unknown message digest algorithm OID.",algorithm:s.md.algorithm};s.signatureOid=s.siginfo.algorithmOid=u,s.certificationRequestInfo=n.getCertificationRequestInfo(s);var a=t.toDer(s.certificationRequestInfo);s.md.update(a.getBytes()),s.signature=i.sign(s.md)},s.verify=function(){var i=!1,o=s.md;if(o===null){if(s.signatureOid in r){var u=r[s.signatureOid];switch(u){case"sha1WithRSAEncryption":o=e.md.sha1.create();break;case"md5WithRSAEncryption":o=e.md.md5.create();break;case"sha256WithRSAEncryption":o=e.md.sha256.create();break;case"RSASSA-PSS":o=e.md.sha256.create()}}if(o===null)throw{message:"Could not compute certification request digest. Unknown signature OID.",signatureOid:s.signatureOid};var a=s.certificationRequestInfo||n.getCertificationRequestInfo(s),f=t.toDer(a);o.update(f.getBytes())}if(o!==null){var l=undefined;switch(s.signatureOid){case r.sha1WithRSAEncryption:l=undefined;break;case r["RSASSA-PSS"]:var c,h;c=r[s.signatureParameters.mgf.hash.algorithmOid];if(c===undefined||e.md[c]===undefined)throw{message:"Unsupported MGF hash function.",oid:s.signatureParameters.mgf.hash.algorithmOid,name:c};h=r[s.signatureParameters.mgf.algorithmOid];if(h===undefined||e.mgf[h]===undefined)throw{message:"Unsupported MGF function.",oid:s.signatureParameters.mgf.algorithmOid,name:h};h=e.mgf[h].create(e.md[c].create()),c=r[s.signatureParameters.hash.algorithmOid];if(c===undefined||e.md[c]===undefined)throw{message:"Unsupported RSASSA-PSS hash function.",oid:s.signatureParameters.hash.algorithmOid,name:c};l=e.pss.create(e.md[c].create(),h,s.signatureParameters.saltLength)}i=s.publicKey.verify(o.digest().getBytes(),s.signature,l)}return i},s},n.getTBSCertificate=function(r){var i=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.CONTEXT_SPECIFIC,0,!0,[t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,String.fromCharCode(r.version))]),t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,e.util.hexToBytes(r.serialNumber)),t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(r.siginfo.algorithmOid).getBytes()),S(r.siginfo.algorithmOid,r.siginfo.parameters)]),w(r.issuer),t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.UTCTIME,!1,t.dateToUtcTime(r.validity.notBefore)),t.create(t.Class.UNIVERSAL,t.Type.UTCTIME,!1,t.dateToUtcTime(r.validity.notAfter))]),w(r.subject),n.publicKeyToAsn1(r.publicKey)]);return r.issuer.uniqueId&&i.value.push(t.create(t.Class.CONTEXT_SPECIFIC,1,!0,[t.create(t.Class.UNIVERSAL,t.Type.BITSTRING,!1,String.fromCharCode(0)+r.issuer.uniqueId)])),r.subject.uniqueId&&i.value.push(t.create(t.Class.CONTEXT_SPECIFIC,2,!0,[t.create(t.Class.UNIVERSAL,t.Type.BITSTRING,!1,String.fromCharCode(0)+r.subject.uniqueId)])),r.extensions.length>0&&i.value.push(E(r.extensions)),i},n.getCertificationRequestInfo=function(e){var r=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,String.fromCharCode(e.version)),w(e.subject),n.publicKeyToAsn1(e.publicKey),x(e)]);return r},n.distinguishedNameToAsn1=function(e){return w(e)},n.certificateToAsn1=function(e){var r=e.tbsCertificate||n.getTBSCertificate(e);return t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[r,t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(e.signatureOid).getBytes()),S(e.signatureOid,e.signatureParameters)]),t.create(t.Class.UNIVERSAL,t.Type.BITSTRING,!1,String.fromCharCode(0)+e.signature)])},n.certificationRequestToAsn1=function(e){var r=e.certificationRequestInfo||n.getCertificationRequestInfo(e);return t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[r,t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(e.signatureOid).getBytes()),S(e.signatureOid,e.signatureParameters)]),t.create(t.Class.UNIVERSAL,t.Type.BITSTRING,!1,String.fromCharCode(0)+e.signature)])},n.createCaStore=function(t){var r={certs:{}};r.getIssuer=function(t){var i=null;if(!t.issuer.hash){var s=e.md.sha1.create();t.issuer.attributes=n.RDNAttributesAsArray(w(t.issuer),s),t.issuer.hash=s.digest().toHex()}if(t.issuer.hash in r.certs){i=r.certs[t.issuer.hash];if(e.util.isArray(i))throw{message:"Resolving multiple issuer matches not implemented yet."}}return i},r.addCertificate=function(t){typeof t=="string"&&(t=e.pki.certificateFromPem(t));if(!t.subject.hash){var i=e.md.sha1.create();t.subject.attributes=n.RDNAttributesAsArray(w(t.subject),i),t.subject.hash=i.digest().toHex()}if(t.subject.hash in r.certs){var s=r.certs[t.subject.hash];e.util.isArray(s)||(s=[s]),s.push(t)}else r.certs[t.subject.hash]=t};if(t)for(var i=0;i<t.length;++i){var s=t[i];r.addCertificate(s)}return r},n.certificateError={bad_certificate:"forge.pki.BadCertificate",unsupported_certificate:"forge.pki.UnsupportedCertificate",certificate_revoked:"forge.pki.CertificateRevoked",certificate_expired:"forge.pki.CertificateExpired",certificate_unknown:"forge.pki.CertificateUnknown",unknown_ca:"forge.pki.UnknownCertificateAuthority"},n.verifyCertificateChain=function(t,r,i){r=r.slice(0);var s=r.slice(0),o=new Date,u=!0,a=null,f=0,l=null;do{var c=r.shift();if(o<c.validity.notBefore||o>c.validity.notAfter)a={message:"Certificate is not valid yet or has expired.",error:n.certificateError.certificate_expired,notBefore:c.validity.notBefore,notAfter:c.validity.notAfter,now:o};else{var h=!1;if(r.length>0){l=r[0];try{h=l.verify(c)}catch(p){}}else{var d=t.getIssuer(c);if(d===null)a={message:"Certificate is not trusted.",error:n.certificateError.unknown_ca};else{e.util.isArray(d)||(d=[d]);while(!h&&d.length>0){l=d.shift();try{h=l.verify(c)}catch(p){}}}}a===null&&!h&&(a={message:"Certificate signature is invalid.",error:n.certificateError.bad_certificate})}a===null&&!c.isIssuer(l)&&(a={message:"Certificate issuer is invalid.",error:n.certificateError.bad_certificate});if(a===null){var v={keyUsage:!0,basicConstraints:!0};for(var m=0;a===null&&m<c.extensions.length;++m){var g=c.extensions[m];g.critical&&!(g.name in v)&&(a={message:"Certificate has an unsupported critical extension.",error:n.certificateError.unsupported_certificate})}}if(!u||r.length===0&&!l){var y=c.getExtension("basicConstraints"),b=c.getExtension("keyUsage");b!==null&&(!b.keyCertSign||y===null)&&(a={message:"Certificate keyUsage or basicConstraints conflict or indicate that the certificate is not a CA. If the certificate is the only one in the chain or isn't the first then the certificate must be a valid CA.",error:n.certificateError.bad_certificate}),a===null&&y!==null&&!y.cA&&(a={message:"Certificate basicConstraints indicates the certificate is not a CA.",error:n.certificateError.bad_certificate})}var w=a===null?!0:a.error,E=i?i(w,f,s):w;if(E!==!0){w===!0&&(a={message:"The application rejected the certificate.",error:n.certificateError.bad_certificate});if(E||E===0)typeof E=="object"&&!e.util.isArray(E)?(E.message&&(a.message=E.message),E.error&&(a.error=E.error)):typeof E=="string"&&(a.error=E);throw a}a=null,u=!1,++f}while(r.length>0);return!0},n.publicKeyFromAsn1=function(r){var i={},u=[];if(t.validate(r,s,i,u)){var a=t.derToOid(i.publicKeyOid);if(a!==n.oids.rsaEncryption)throw{message:"Cannot read public key. Unknown OID.",oid:a};r=i.rsaPublicKey}u=[];if(!t.validate(r,o,i,u))throw{message:"Cannot read public key. ASN.1 object does not contain an RSAPublicKey.",errors:u};var f=e.util.createBuffer(i.publicKeyModulus).toHex(),l=e.util.createBuffer(i.publicKeyExponent).toHex();return n.setRsaPublicKey(new BigInteger(f,16),new BigInteger(l,16))},n.publicKeyToAsn1=n.publicKeyToSubjectPublicKeyInfo=function(e){return t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.oids.rsaEncryption).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.NULL,!1,"")]),t.create(t.Class.UNIVERSAL,t.Type.BITSTRING,!1,[n.publicKeyToRSAPublicKey(e)])])},n.publicKeyToRSAPublicKey=function(e){return t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,y(e.n)),t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,y(e.e))])},n.privateKeyFromAsn1=function(r){var i={},s=[];t.validate(r,a,i,s)&&(r=t.fromDer(e.util.createBuffer(i.privateKey))),i={},s=[];if(!t.validate(r,f,i,s))throw{message:"Cannot read private key. ASN.1 object does not contain an RSAPrivateKey.",errors:s};var o,u,l,c,h,p,d,v;return o=e.util.createBuffer(i.privateKeyModulus).toHex(),u=e.util.createBuffer(i.privateKeyPublicExponent).toHex(),l=e.util.createBuffer(i.privateKeyPrivateExponent).toHex(),c=e.util.createBuffer(i.privateKeyPrime1).toHex(),h=e.util.createBuffer(i.privateKeyPrime2).toHex(),p=e.util.createBuffer(i.privateKeyExponent1).toHex(),d=e.util.createBuffer(i.privateKeyExponent2).toHex(),v=e.util.createBuffer(i.privateKeyCoefficient).toHex(),n.setRsaPrivateKey(new BigInteger(o,16),new BigInteger(u,16),new BigInteger(l,16),new BigInteger(c,16),new BigInteger(h,16),new BigInteger(p,16),new BigInteger(d,16),new BigInteger(v,16))},n.privateKeyToAsn1=n.privateKeyToRSAPrivateKey=function(e){return t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,String.fromCharCode(0)),t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,y(e.n)),t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,y(e.e)),t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,y(e.d)),t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,y(e.p)),t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,y(e.q)),t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,y(e.dP)),t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,y(e.dQ)),t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,y(e.qInv))])},n.wrapRsaPrivateKey=function(e){return t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,"\0"),t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(r.rsaEncryption).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.NULL,!1,"")]),t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,t.toDer(e).getBytes())])},n.encryptPrivateKeyInfo=function(n,i,s){s=s||{},s.saltSize=s.saltSize||8,s.count=s.count||2048,s.algorithm=s.algorithm||"aes128";var o=e.random.getBytes(s.saltSize),u=s.count,a=e.util.createBuffer();a.putInt16(u);var f,l,c;if(s.algorithm.indexOf("aes")===0){var h;if(s.algorithm==="aes128")f=16,h=r["aes128-CBC"];else if(s.algorithm==="aes192")f=24,h=r["aes192-CBC"];else{if(s.algorithm!=="aes256")throw{message:"Cannot encrypt private key. Unknown encryption algorithm.",algorithm:s.algorithm};f=32,h=r["aes256-CBC"]}var p=e.pkcs5.pbkdf2(i,o,u,f),d=e.random.getBytes(16),v=e.aes.createEncryptionCipher(p);v.start(d),v.update(t.toDer(n)),v.finish(),c=v.output.getBytes(),l=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(r.pkcs5PBES2).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(r.pkcs5PBKDF2).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,o),t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,a.getBytes())])]),t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(h).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,d)])])])}else{if(s.algorithm!=="3des")throw{message:"Cannot encrypt private key. Unknown encryption algorithm.",algorithm:s.algorithm};f=24;var m=new e.util.ByteBuffer(o),p=e.pkcs12.generateKey(i,m,1,u,f),d=e.pkcs12.generateKey(i,m,2,u,f),v=e.des.createEncryptionCipher(p);v.start(d),v.update(t.toDer(n)),v.finish(),c=v.output.getBytes(),l=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(r["pbeWithSHAAnd3-KeyTripleDES-CBC"]).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,o),t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,a.getBytes())])])}var g=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[l,t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,c)]);return g},n.pbe.getCipherForPBES2=function(r,i,s){var o={},u=[];if(!t.validate(i,c,o,u))throw{message:"Cannot read password-based-encryption algorithm parameters. ASN.1 object is not a supported EncryptedPrivateKeyInfo.",errors:u};r=t.derToOid(o.kdfOid);if(r!==n.oids.pkcs5PBKDF2)throw{message:"Cannot read encrypted private key. Unsupported key derivation function OID.",oid:r,supportedOids:["pkcs5PBKDF2"]};r=t.derToOid(o.encOid);if(r!==n.oids["aes128-CBC"]&&r!==n.oids["aes192-CBC"]&&r!==n.oids["aes256-CBC"])throw{message:"Cannot read encrypted private key. Unsupported encryption scheme OID.",oid:r,supportedOids:["aes128-CBC","aes192-CBC","aes256-CBC"]};var a=o.kdfSalt,f=e.util.createBuffer(o.kdfIterationCount);f=f.getInt(f.length()<<3);var l;r===n.oids["aes128-CBC"]?l=16:r===n.oids["aes192-CBC"]?l=24:r===n.oids["aes256-CBC"]&&(l=32);var h=e.pkcs5.pbkdf2(s,a,f,l),p=o.encIv,d=e.aes.createDecryptionCipher(h);return d.start(p),d},n.pbe.getCipherForPKCS12PBE=function(r,i,s){var o={},u=[];if(!t.validate(i,h,o,u))throw{message:"Cannot read password-based-encryption algorithm parameters. ASN.1 object is not a supported EncryptedPrivateKeyInfo.",errors:u};var a=e.util.createBuffer(o.salt),f=e.util.createBuffer(o.iterations);f=f.getInt(f.length()<<3);var l,c,p;switch(r){case n.oids["pbeWithSHAAnd3-KeyTripleDES-CBC"]:l=24,c=8,p=e.des.startDecrypting;break;case n.oids["pbewithSHAAnd40BitRC2-CBC"]:l=5,c=8,p=function(t,n){var r=e.rc2.createDecryptionCipher(t,40);return r.start(n,null),r};break;default:throw{message:"Cannot read PKCS #12 PBE data block. Unsupported OID.",oid:r}}var d=e.pkcs12.generateKey(s,a,1,f,l),v=e.pkcs12.generateKey(s,a,2,f,c);return p(d,v)},n.pbe.getCipher=function(e,t,r){switch(e){case n.oids.pkcs5PBES2:return n.pbe.getCipherForPBES2(e,t,r);case n.oids["pbeWithSHAAnd3-KeyTripleDES-CBC"]:case n.oids["pbewithSHAAnd40BitRC2-CBC"]:return n.pbe.getCipherForPKCS12PBE(e,t,r);default:throw{message:"Cannot read encrypted PBE data block. Unsupported OID.",oid:e,supportedOids:["pkcs5PBES2","pbeWithSHAAnd3-KeyTripleDES-CBC","pbewithSHAAnd40BitRC2-CBC"]}}},n.decryptPrivateKeyInfo=function(r,i){var s=null,o={},u=[];if(!t.validate(r,l,o,u))throw{message:"Cannot read encrypted private key. ASN.1 object is not a supported EncryptedPrivateKeyInfo.",errors:u};var a=t.derToOid(o.encryptionOid),f=n.pbe.getCipher(a,o.encryptionParams,i),c=e.util.createBuffer(o.encryptedData);return f.update(c),f.finish()&&(s=t.fromDer(f.output)),s},n.encryptedPrivateKeyToPem=function(n,r){var i={type:"ENCRYPTED PRIVATE KEY",body:t.toDer(n).getBytes()};return e.pem.encode(i,{maxline:r})},n.encryptedPrivateKeyFromPem=function(n){var r=e.pem.decode(n)[0];if(r.type!=="ENCRYPTED PRIVATE KEY")throw{message:'Could not convert encrypted private key from PEM; PEM header type is "ENCRYPTED PRIVATE KEY".',headerType:r.type};if(r.procType&&r.procType.type==="ENCRYPTED")throw{message:"Could not convert encrypted private key from PEM; PEM is encrypted."};return t.fromDer(r.body)},n.encryptRsaPrivateKey=function(r,i,s){s=s||{};if(!s.legacy){var o=n.wrapRsaPrivateKey(n.privateKeyToAsn1(r));return o=n.encryptPrivateKeyInfo(o,i,s),n.encryptedPrivateKeyToPem(o)}var u,a,f,l;switch(s.algorithm){case"aes128":u="AES-128-CBC",f=16,a=e.random.getBytes(16),l=e.aes.createEncryptionCipher;break;case"aes192":u="AES-192-CBC",f=24,a=e.random.getBytes(16),l=e.aes.createEncryptionCipher;break;case"aes256":u="AES-256-CBC",f=32,a=e.random.getBytes(16),l=e.aes.createEncryptionCipher;break;case"3des":u="DES-EDE3-CBC",f=24,a=e.random.getBytes(8),l=e.des.createEncryptionCipher;break;default:throw{message:'Could not encrypt RSA private key; unsupported encryption algorithm "'+s.algorithm+'".',algorithm:s.algorithm}}var c=T(i,a.substr(0,8),f),h=l(c);h.start(a),h.update(t.toDer(n.privateKeyToAsn1(r))),h.finish();var p={type:"RSA PRIVATE KEY",procType:{version:"4",type:"ENCRYPTED"},dekInfo:{algorithm:u,parameters:e.util.bytesToHex(a).toUpperCase()},body:h.output.getBytes()};return e.pem.encode(p)},n.decryptRsaPrivateKey=function(r,i){var s=null,o=e.pem.decode(r)[0];if(o.type!=="ENCRYPTED PRIVATE KEY"&&o.type!=="PRIVATE KEY"&&o.type!=="RSA PRIVATE KEY")throw{message:'Could not convert private key from PEM; PEM header type is not "ENCRYPTED PRIVATE KEY", "PRIVATE KEY", or "RSA PRIVATE KEY".',headerType:o.type};if(o.procType&&o.procType.type==="ENCRYPTED"){var u,a;switch(o.dekInfo.algorithm){case"DES-EDE3-CBC":u=24,a=e.des.createDecryptionCipher;break;case"AES-128-CBC":u=16,a=e.aes.createDecryptionCipher;break;case"AES-192-CBC":u=24,a=e.aes.createDecryptionCipher;break;case"AES-256-CBC":u=32,a=e.aes.createDecryptionCipher;break;case"RC2-40-CBC":u=5,a=function(t){return e.rc2.createDecryptionCipher(t,40)};break;case"RC2-64-CBC":u=8,a=function(t){return e.rc2.createDecryptionCipher(t,64)};break;case"RC2-128-CBC":u=16,a=function(t){return e.rc2.createDecryptionCipher(t,128)};break;default:throw{message:'Could not decrypt private key; unsupported encryption algorithm "'+o.dekInfo.algorithm+'".',algorithm:o.dekInfo.algorithm}}var f=e.util.hexToBytes(o.dekInfo.parameters),l=T(i,f.substr(0,8),u),c=a(l);c.start(f),c.update(e.util.createBuffer(o.body));if(!c.finish())return s;s=c.output.getBytes()}else s=o.body;return o.type==="ENCRYPTED PRIVATE KEY"?s=n.decryptPrivateKeyInfo(t.fromDer(s),i):s=t.fromDer(s),s!==null&&(s=n.privateKeyFromAsn1(s)),s},n.setRsaPublicKey=n.rsa.setPublicKey,n.setRsaPrivateKey=n.rsa.setPrivateKey}var t="pki";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/pki",["require","module","./aes","./asn1","./des","./jsbn","./md","./mgf","./oids","./pem","./pbkdf2","./pkcs12","./pss","./random","./rc2","./rsa","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e,t,n,r,i,s,o){var u={privateKey:"-----BEGIN RSA PRIVATE KEY-----\r\nMIICXQIBAAKBgQDL0EugUiNGMWscLAVM0VoMdhDZEJOqdsUMpx9U0YZI7szokJqQ\r\nNIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMGTkP3VF29vXBo+dLq5e+8VyAy\r\nQ3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGtvnM+z0MYDdKo80efzwIDAQAB\r\nAoGAIzkGONi5G+JifmXlLJdplom486p3upf4Ce2/7mqfaG9MnkyPSairKD/JXvfh\r\nNNWkkN8DKKDKBcVVElPgORYT0qwrWc7ueLBMUCbRXb1ZyfEulimG0R3kjUh7NYau\r\nDaIkVgfykXGSQMZx8FoaT6L080zd+0emKDDYRrb+/kgJNJECQQDoUZoiC2K/DWNY\r\nh3/ppZ0ane2y4SBmJUHJVMPQ2CEgxsrJTxet668ckNCKaOP/3VFPoWC41f17DvKq\r\nnoYINNntAkEA4JbZBZBVUrQFhHlrpXT4jzqtO2RlKZzEq8qmFZfEErxOT1WMyyCi\r\nlAQ5gUKardo1Kf0omC8Xq/uO9ZYdED55KwJBALs6cJ65UFaq4oLJiQPzLd7yokuE\r\ndcj8g71PLBTW6jPxIiMFNA89nz3FU9wIVp+xbMNhSoMMKqIPVPC+m0Rn260CQQDA\r\nI83fWK/mZWUjBM33a68KumRiH238v8XyQxj7+C8i6D8G2GXvkigFAehAkb7LZZd+\r\nKLuGFyPlWv3fVWHf99KpAkBQFKk3MRMl6IGJZUEFQe4l5whm8LkGU4acSqv9B3xt\r\nqROkCrsFrMPqjuuzEmyHoQZ64r2PLJg7FOuyhBnQUOt4\r\n-----END RSA PRIVATE KEY-----\r\n",publicKey:"-----BEGIN PUBLIC KEY-----\r\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDL0EugUiNGMWscLAVM0VoMdhDZ\r\nEJOqdsUMpx9U0YZI7szokJqQNIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMG\r\nTkP3VF29vXBo+dLq5e+8VyAyQ3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGt\r\nvnM+z0MYDdKo80efzwIDAQAB\r\n-----END PUBLIC KEY-----\r\n"},a="9200ece65cdaed36bcc20b94c65af852e4f88f0b4fe5b249d54665f815992ac43a1399e65d938c6a7f16dd39d971a53ca66523209dbbfbcb67afa579dbb0c220672813d9e6f4818f29b9becbb29da2032c5e422da97e0c39bfb7a2e7d568615a5073af0337ff215a8e1b2332d668691f4fb731440055420c24ac451dd3c913f4";describe("rsa",function(){it("should generate 512 bit key pair",function(){var i=n.generateKeyPair(512);e.equal(t.privateKeyToPem(i.privateKey).indexOf("-----BEGIN RSA PRIVATE KEY-----"),0),e.equal(t.publicKeyToPem(i.publicKey).indexOf("-----BEGIN PUBLIC KEY-----"),0);var s=r.sha1.create();s.update("0123456789abcdef");var o=i.privateKey.sign(s);e.ok(i.publicKey.verify(s.digest().getBytes(),o))}),it("should convert private key to/from PEM",function(){var n=t.privateKeyFromPem(u.privateKey);e.equal(t.privateKeyToPem(n),u.privateKey)}),it("should convert public key to/from PEM",function(){var n=t.publicKeyFromPem(u.publicKey);e.equal(t.publicKeyToPem(n),u.publicKey)}),function(){var n=["aes128","aes192","aes256","3des"];for(var r=0;r<n.length;++r){var i=n[r];it("should PKCS#8 encrypt and decrypt private key with "+i,function(){var n=t.privateKeyFromPem(u.privateKey),r=t.encryptRsaPrivateKey(n,"password",{algorithm:i}),n=t.decryptRsaPrivateKey(r,"password");e.equal(t.privateKeyToPem(n),u.privateKey)})}}(),function(){var n=["aes128","aes192","aes256","3des"];for(var r=0;r<n.length;++r){var i=n[r];it("should legacy (OpenSSL style) encrypt and decrypt private key with "+i,function(){var n=t.privateKeyFromPem(u.privateKey),r=t.encryptRsaPrivateKey(n,"password",{algorithm:i,legacy:!0}),n=t.decryptRsaPrivateKey(r,"password");e.equal(t.privateKeyToPem(n),u.privateKey)})}}(),it("should verify signature",function(){var n=t.publicKeyFromPem(u.publicKey),i=r.sha1.create();i.update("0123456789abcdef");var s=o.hexToBytes(a);e.ok(n.verify(i.digest().getBytes(),s))}),it("should sign and verify",function(){var n=t.privateKeyFromPem(u.privateKey),i=t.publicKeyFromPem(u.publicKey),s=r.sha1.create();s.update("0123456789abcdef");var o=n.sign(s);e.ok(i.verify(s.digest().getBytes(),o))}),function(){function a(n){var u=n.keySize;it("should rsa encrypt using a "+u+"-bit key",function(){var r="it need's to be about 20% cooler",i=t.publicKeyFromPem(n.publicKeyPem),s=i.encrypt(r);i=t.privateKeyFromPem(n.privateKeyPem),e.equal(i.decrypt(s),r)}),it("should rsa decrypt using a "+u+"-bit key",function(){var r=o.decode64(n.encrypted),i=t.privateKeyFromPem(n.privateKeyPem);e.equal(i.decrypt(r),"too many secrets\n")}),it("should rsa sign using a "+u+"-bit key and PKCS#1 v1.5 padding",function(){var i=t.privateKeyFromPem(n.privateKeyPem),s=r.sha1.create();s.start(),s.update("just testing");var u=o.decode64(n.signature);e.equal(i.sign(s),u)}),it("should verify an rsa signature using a "+u+"-bit key and PKCS#1 v1.5 padding",function(){var i=o.decode64(n.signature),s=t.publicKeyFromPem(n.publicKeyPem),u=r.sha1.create();u.start(),u.update("just testing"),e.equal(s.verify(u.digest().getBytes(),i),!0)}),it("should rsa sign using a "+u+"-bit key and PSS padding",function(){var o=t.privateKeyFromPem(n.privateKeyPem),u=t.publicKeyFromPem(n.publicKeyPem),a=r.sha1.create();a.start(),a.update("just testing");var f=s.create(r.sha1.create(),i.mgf1.create(r.sha1.create()),20),l=o.sign(a,f);a.start(),a.update("just testing"),e.equal(u.verify(a.digest().getBytes(),l,f),!0)}),it("should verify an rsa signature using a "+u+"-bit key and PSS padding",function(){var u=o.decode64(n.signaturePss),a=t.publicKeyFromPem(n.publicKeyPem),f=r.sha1.create();f.start(),f.update("just testing");var l=s.create(r.sha1.create(),i.mgf1.create(r.sha1.create()),20);e.equal(a.verify(f.digest().getBytes(),u,l),!0)})}var n=[{keySize:1024,privateKeyPem:"-----BEGIN RSA PRIVATE KEY-----\r\nMIICWwIBAAKBgQDCjvkkLWNTeYXqEsqGiVCW/pDt3/qAodNMHcU9gOU2rxeWwiRu\r\nOhhLqmMxXHLi0oP5Xmg0m7zdOiLMEyzzyRzdp21aqp3k5qtuSDkZcf1prsp1jpYm\r\n6z9EGpaSHb64BCuUsQGmUPKutd5RERKHGZXtiRuvvIyue7ETq6VjXrOUHQIDAQAB\r\nAoGAOKeBjTNaVRhyEnNeXkbmHNIMSfiK7aIx8VxJ71r1ZDMgX1oxWZe5M29uaxVM\r\nrxg2Lgt7tLYVDSa8s0hyMptBuBdy3TJUWruDx85uwCrWnMerCt/iKVBS22fv5vm0\r\nLEq/4gjgIVTZwgqbVxGsBlKcY2VzxAfYqYzU8EOZBeNhZdECQQDy+PJAPcUN2xOs\r\n6qy66S91x6y3vMjs900OeX4+bgT4VSVKmLpqRTPizzcL07tT4+Y+pAAOX6VstZvZ\r\n6iFDL5rPAkEAzP1+gaRczboKoJWKJt0uEMUmztcY9NXJFDmjVLqzKwKjcAoGgIal\r\nh+uBFT9VJ16QajC7KxTRLlarzmMvspItUwJAeUMNhEpPwm6ID1DADDi82wdgiALM\r\nNJfn+UVhYD8Ac//qsKQwxUDseFH6owh1AZVIIBMxg/rwUKUCt2tGVoW3uQJAIt6M\r\nAml/D8+xtxc45NuC1n9y1oRoTl1/Ut1rFyKbD5nnS0upR3uf9LruvjqDtaq0Thvz\r\n+qQT4RoFJ5pfprSO2QJAdMkfNWRqECfAhZyQuUrapeWU3eQ0wjvktIynCIwiBDd2\r\nMfjmVXzBJhMk6dtINt+vBEITVQEOdtyTgDt0y3n2Lw==\r\n-----END RSA PRIVATE KEY-----\r\n",publicKeyPem:"-----BEGIN PUBLIC KEY-----\r\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCjvkkLWNTeYXqEsqGiVCW/pDt\r\n3/qAodNMHcU9gOU2rxeWwiRuOhhLqmMxXHLi0oP5Xmg0m7zdOiLMEyzzyRzdp21a\r\nqp3k5qtuSDkZcf1prsp1jpYm6z9EGpaSHb64BCuUsQGmUPKutd5RERKHGZXtiRuv\r\nvIyue7ETq6VjXrOUHQIDAQAB\r\n-----END PUBLIC KEY-----\r\n",encrypted:"jsej3OoacmJ1VjWrlw68F+drnQORAuKAqVu6RMbz1xSXjzA355vctrJZXolRU0mvzuu/6VuNynkKGGyRJ6DHt85CvwTMChw4tOMV4Dy6bgnUt3j+DZA2sWTwFhOlpzvNQMK70QpuqrXtOZmAO59EwoDeJkW/iH6t4YzNOVYo9Jg=",signature:"GT0/3EV2zrXxPd1ydijJq3R7lkI4c0GtcprgpG04dSECv/xyXtikuzivxv7XzUdHpu6QiYmM0xE4D4i7LK3Mzy+f7aB4o/dg8XXO3htLiBzVI+ZJCRh06RdYctPtclAWmyZikZ8Etw3NnA/ldKuG4jApbwRb21UFm5gYLrJ4SP4=",signaturePss:"F4xffaANDBjhFxeSJx8ANuBbdhaWZjUHRQh4ueYQMPPCaR2mpwdqxE04sbgNgIiZzBuLIAI4HpTMMoDk3Rruhjefx3+9UhzTxgB0hRI+KzRChRs+ToltWWDZdYzt9T8hfTlELeqT4V8HgjDuteO/IAvIVlRIBwMNv53Iebu1FY4="},{keySize:1025,privateKeyPem:"-----BEGIN RSA PRIVATE KEY-----\r\nMIICXgIBAAKBgQGIkej4PDlAigUh5fbbHp1WXuTHhOdQfAke+LoH0TM4uzn0QmgK\r\nSJqxzB1COJ5o0DwZw/NR+CNy7NUrly+vmh2YPwsaqN+AsYBF9qsF93oN8/TBtaL/\r\nGRoRGpDcCglkj1kZnDaWR79NsG8mC0TrvQCkcCLOP0c2Ux1hRbntOetGXwIDAQAB\r\nAoGBAIaJWsoX+ZcAthmT8jHOICXFh6pJBe0zVPzkSPz82Q0MPSRUzcsYbsuYJD7Z\r\noJBTLQW3feANpjhwqe2ydok7y//ONm3Th53Bcu8jLfoatg4KYxNFIwXEO10mPOld\r\nVuDIGrBkTABe6q2P5PeUKGCKLT6i/u/2OTXTrQiJbQ0gU8thAkEBjqcFivWMXo34\r\nCb9/EgfWCCtv9edRMexgvcFMysRsbHJHDK9JjRLobZltwtAv3cY7F3a/Cu1afg+g\r\njAzm5E3gowJBAPwYFHTLzaZToxFKNQztWrPsXF6YfqHpPUUIpT4UzL6DhGG0M00U\r\nqMyhkYRRqmGOSrSovjg2hjM2643MUUWxUxUCQDPkk/khu5L3YglKzyy2rmrD1MAq\r\ny0v3XCR3TBq89Ows+AizrJxbkLvrk/kfBowU6M5GG9o9SWFNgXWZnFittocCQQDT\r\ne1P1419DUFi1UX6NuLTlybx3sxBQvf0jY6xUF1jn3ib5XBXJbTJqcIRF78iyjI9J\r\nXWIugDc20bTsQOJRSAA9AkEBU8kpueHBaiXTikqqlK9wvc2Lp476hgyKVmVyBGye\r\n9TLTWkTCzDPtManLy47YtXkXnmyazS+DlKFU61XAGEnZfg==\r\n-----END RSA PRIVATE KEY-----\r\n",publicKeyPem:"-----BEGIN PUBLIC KEY-----\r\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQGIkej4PDlAigUh5fbbHp1WXuTH\r\nhOdQfAke+LoH0TM4uzn0QmgKSJqxzB1COJ5o0DwZw/NR+CNy7NUrly+vmh2YPwsa\r\nqN+AsYBF9qsF93oN8/TBtaL/GRoRGpDcCglkj1kZnDaWR79NsG8mC0TrvQCkcCLO\r\nP0c2Ux1hRbntOetGXwIDAQAB\r\n-----END PUBLIC KEY-----\r\n",encrypted:"AOVeCUN8BOVkZvt4mxyNn/yCYE1MZ40A3e/osh6EvCBcJ09hyYbx7bzKSrdkhRnDyW0pGtgP352CollasllQZ9HlfI2Wy9zKM0aYZZn8OHBA+60Tc3xHHDGznLZqggUKuhoNpj+faVZ1uzb285eTpQQa+4mLUue2svJD4ViM8+ng",signature:"AFSx0axDYXlF2rO3ofgUhYSI8ZlIWtJUUZ62PhgdBp9O5zFqMX3DXoiov1e7NenSOz1khvTSMctFWzKP3GU3F0yewe+Yd3UAZE0dM8vAxigSSfAchUkBDmp9OFuszUie63zwWwpG+gXtvyfueZs1RniBvW1ZmXJvS+HFgX4ouzwd",signaturePss:"AQvBdhAXDpu+7RpcybMgwuTUk6w+qa08Lcq3G1xHY4kC7ZUzauZd/Jn9e0ePKApDqs7eDNAOV+dQkU2wiH/uBg6VGelzb0hFwcpSLyBW92Vw0q3GlzY7myWn8qnNzasrt110zFflWQa1GiuzH/C8f+Z82/MzlWDxloJIYbq2PRC8"},{keySize:1031,privateKeyPem:"-----BEGIN RSA PRIVATE KEY-----\r\nMIICXwIBAAKBgWyeKqA2oA4klYrKT9hjjutYQksJNN0cxwaQwIm9AYiLxOsYtT/C\r\novJx5Oy1EvkbYQbfvYsGISUx9bW8yasZkTHR55IbW3+UptvQjTDtdxBQTgQOpsAh\r\nBJtZYY3OmyH9Sj3F3oB//oyriNoj0QYyfsvlO8UsMmLzpnf6qfZBDHA/9QIDAQAB\r\nAoGBBj/3ne5muUmbnTfU7lOUNrCGaADonMx6G0ObAJHyk6PPOePbEgcmDyNEk+Y7\r\naEAODjIzmttIbvZ39/Qb+o9nDmCSZC9VxiYPP+rjOzPglCDT5ks2Xcjwzd3If6Ya\r\nUw6P31Y760OCYeTb4Ib+8zz5q51CkjkdX5Hq/Yu+lZn0Vx7BAkENo83VfL+bwxTm\r\nV7vR6gXqTD5IuuIGHL3uTmMNNURAP6FQDHu//duipys83iMChcOeXtboE16qYrO0\r\n9KC0cqL4JQJBB/aYo/auVUGZA6f50YBp0b2slGMk9TBQG0iQefuuSyH4kzKnt2e3\r\nQ40SBmprcM+DfttWJ11bouec++goXjz+95ECQQyiTWYRxulgKVuyqCYnvpLnTEnR\r\n0MoYlVTHBriVPkLErYaYCYgse+SNM1+N4p/Thv6KmkUcq/Lmuc5DSRfbl1iBAkEE\r\n7GKtJQvd7EO1bfpXnARQx+tWhwHHkgpFBBVHReMZ0rQEFhJ5o2c8HZEiZFNvGO2c\r\n1fErP14zlu2JFZ03vpCI8QJBCQz9HL28VNjafSAF2mon/SNjKablRjoGGKSoSdyA\r\nDHDZ/LeRsTp2dg8+bSiG1R+vPqw0f/BT+ux295Sy9ocGEM8=\r\n-----END RSA PRIVATE KEY-----\r\n",publicKeyPem:"-----BEGIN PUBLIC KEY-----\r\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgWyeKqA2oA4klYrKT9hjjutYQksJ\r\nNN0cxwaQwIm9AYiLxOsYtT/CovJx5Oy1EvkbYQbfvYsGISUx9bW8yasZkTHR55Ib\r\nW3+UptvQjTDtdxBQTgQOpsAhBJtZYY3OmyH9Sj3F3oB//oyriNoj0QYyfsvlO8Us\r\nMmLzpnf6qfZBDHA/9QIDAQAB\r\n-----END PUBLIC KEY-----\r\n",encrypted:"ShSS4/fEAkuS6XiQakhOpWp82IXaaCaDNtsndU4uokvriqgCGZyqc+IkIk3eVmZ8bn4vVIRR43ydFuvGgsptVjizOdLGZudph3TJ1clcYEMcCXk4z5HaEu0bx5SW9jmzHhE/z+WV8PB48q7y7C2qtmPmfttG2NMsNLBvkiaDopRO",signature:"Z3vYgRdezrWmdA3NC1Uz2CcHRTcE+/C2idGZA1FjUGqFztAHQ31k0QW/F5zuJdKvg8LQU45S3KxW+OQpbGPL98QbzJLhml88mFGe6OinLXJbi7UQWrtXwamc2jMdiXwovSLbXaXy6PX2QW089iC8XuAZftVi3T/IKV0458FQQprg",signaturePss:"R6QsK6b3QinIPZPamm/dP0Zndqti1TzAkFTRSZJaRSa1u2zuvZC5QHF4flDjEtHosWeDyxrBE7PHGQZ0b1bHv9qgHGsJCMwaQPj3AWj9fjYmx7b86KM2vHr8q/vqDaa9pTvVRSSwvD6fwoZPc9twQEfdjdDBAiy23yLDzk/zZiwM"},{keySize:1032,privateKeyPem:"-----BEGIN RSA PRIVATE KEY-----\r\nMIICYQIBAAKBggDPhzn5I3GecxWt5DKbP+VhM2AFNSOL0+VbYEOR1hnlZdLbxGK4\r\ncPQzMr2qT6dyttJcsgWr3xKobPkz7vsTZzQATSiekm5Js5dGpaj5lrq/x2+WTZvn\r\n55x9M5Y5dlpusDMKcC3KaIX/axc+MbvPFzo6Eli7JLCWdBg01eKo30knil0CAwEA\r\nAQKBggCNl/sjFF7SOD1jbt5kdL0hi7cI9o+xOLs1lEGmAEmc7dNnZN/ibhb/06/6\r\nwuxB5aEz47bg5IvLZMbG+1hNjc26D0J6Y3Ltwrg8f4ZMdDrh4v0DZ8hy/HbEpMrJ\r\nTd5dk3mtw9FLow10MB5udPLTDKhfDpTcWiObKm2STtFeBk3xeEECQQ6Cx6bZxQJ1\r\nzCxflV5Xi8BgAQaUKMqygugte+HpOLflL0j1fuZ0rPosUyDOEFkTzOsPxBYYOU8i\r\nGzan1GvW3WwRAkEOTTRt849wpgC9xx2pF0IrYEVmv5gEMy3IiRfCNgEoBwpTWVf4\r\nQFpN3V/9GFz0WQEEYo6OTmkNcC3Of5zbHhu1jQJBBGxXAYQ2KnbP4uLL/DMBdYWO\r\nKnw1JvxdLPrYXVejI2MoE7xJj2QXajbirAhEMXL4rtpicj22EmoaE4H7HVgkrJEC\r\nQQq2V5w4AGwvW4TLHXNnYX/eB33z6ujScOuxjGNDUlBqHZja5iKkCUAjnl+UnSPF\r\nexaOwBrlrpiLOzRer94MylKNAkEBmI58bqfkI5OCGDArAsJ0Ih58V0l1UW35C1SX\r\n4yDoXSM5A/xQu2BJbXO4jPe3PnDvCVCEyKpbCK6bWbe26Y7zuw==\r\n-----END RSA PRIVATE KEY-----\r\n",publicKeyPem:"-----BEGIN PUBLIC KEY-----\r\nMIGgMA0GCSqGSIb3DQEBAQUAA4GOADCBigKBggDPhzn5I3GecxWt5DKbP+VhM2AF\r\nNSOL0+VbYEOR1hnlZdLbxGK4cPQzMr2qT6dyttJcsgWr3xKobPkz7vsTZzQATSie\r\nkm5Js5dGpaj5lrq/x2+WTZvn55x9M5Y5dlpusDMKcC3KaIX/axc+MbvPFzo6Eli7\r\nJLCWdBg01eKo30knil0CAwEAAQ==\r\n-----END PUBLIC KEY-----\r\n",encrypted:"pKTbv+xgXPDc+wbjsANFu1/WTcmy4aZFKXKnxddHbU5S0Dpdj2OqCACiBwu1oENPMgPAJ27XRbFtKG+eS8tX47mKP2Fo0Bi+BPFtzuQ1bj3zUzTwzjemT+PU+a4Tho/eKjPhm6xrwGAoQH2VEDEpvcYf+SRmGFJpJ/zPUrSxgffj",signature:"R9WBFprCfcIC4zY9SmBpEM0E+cr5j4gMn3Ido5mktoR9VBoJqC6eR6lubIPvZZUz9e4yUSYX0squ56Q9Y0yZFQjTHgsrlmhB2YW8kpv4h8P32Oz2TLcMJK9R2tIh9vvyxwBkd/Ml1qG60GnOFUFzxUad9VIlzaF1PFR6EfnkgBUW",signaturePss:"v9UBd4XzBxSRz8yhWKjUkFpBX4Fr2G+ImjqbePL4sAZvYw1tWL+aUQpzG8eOyMxxE703VDh9nIZULYI/uIb9HYHQoGYQ3WoUaWqtZg1x8pZP+Ad7ilUWk5ImRl57fTznNQiVdwlkS5Wgheh1yJCES570a4eujiK9OyB0ba4rKIcM"}];for(var u=0;u<n.length;++u)a(n[u]);it("should ensure maximum message length for a 1024-bit key is exceeded",function(){var r=t.publicKeyFromPem(n[0].publicKeyPem),i=o.createBuffer().fillWithByte(0,118);e.throws(function(){r.encrypt(i.getBytes())})}),it("should ensure maximum message length for a 1025-bit key is not exceeded",function(){var r=t.publicKeyFromPem(n[1].publicKeyPem),i=o.createBuffer().fillWithByte(0,118);e.doesNotThrow(function(){r.encrypt(i.getBytes())})})}()})}typeof define=="function"?define("test/rsa",["forge/pki","forge/rsa","forge/md","forge/mgf","forge/pss","forge/util"],function(t,n,r,i,s,o){e(ASSERT,t(),n(),r(),i(),s(),o())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/pki")(),require("../../js/rsa")(),require("../../js/md")(),require("../../js/mgf")(),require("../../js/pss")(),require("../../js/util")())}(),function(){function e(e,t,n,r,i){describe("pkcs1",function(){function s(){var e,t,n,r,i,s,o,u,a,l,p;e="qLOyhK+OtQs4cDSoYPFGxJGfMYdjzWxVmMiuSBGh4KvEx+CwgtaTpef87Wdc9GaFEncsDLxkp0LGxjD1M8jMcvYq6DPEC/JYQumEu3i9v5fAEH1VvbZi9cTg+rmEXLUUjvc5LdOq/5OuHmtme7PUJHYW1PW6ENTP0ibeiNOfFvs=",t="AQAB",n="UzOc/befyEZqZVxzFqyoXFX9j23YmP2vEZUX709S6P2OJY35P+4YD6DkqylpPNg7FSpVPUrE0YEri5+lrw5/Vf5zBN9BVwkm8zEfFcTWWnMsSDEW7j09LQrzVJrZv3y/t4rYhPhNW+sEck3HNpsx3vN9DPU56c/N095lNynq1dE=",r="0yc35yZ//hNBstXA0VCoG1hvsxMr7S+NUmKGSpy58wrzi+RIWY1BOhcu+4AsIazxwRxSDC8mpHHcrSEurHyjnQ==",i="zIhT0dVNpjD6wAT0cfKBx7iYLYIkpJDtvrM9Pj1cyTxHZXA9HdeRZC8fEWoN2FK+JBmyr3K/6aAw6GCwKItddw==",s="DhK/FxjpzvVZm6HDiC/oBGqQh07vzo8szCDk8nQfsKM6OEiuyckwX77L0tdoGZZ9RnGsxkMeQDeWjbN4eOaVwQ==",o="lSl7D5Wi+mfQBwfWCd/U/AXIna/C721upVvsdx6jM3NNklHnkILs2oZu/vE8RZ4aYxOGt+NUyJn18RLKhdcVgw==",u="T0VsUCSTvcDtKrdWo6btTWc1Kml9QhbpMhKxJ6Y9VBHOb6mNXb79cyY+NygUJ0OBgWbtfdY2h90qjKHS9PvY4Q==",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 1.1",message:"ZigZThIHPbA7qUzanvlTI5fVDbp5uYcASv7+NA==",seed:"GLd26iEGnWl3ajPpa61I4d2gpe8=",encrypted:"NU/me0oSbV01/jbHd3kaP3uhPe9ITi05CK/3IvrUaPshaW3pXQvpEcLTF0+K/MIBA197bY5pQC3lRRYYwhpTX6nXv8W43Z/CQ/jPkn2zEyLW6IHqqRqZYXDmV6BaJmQm2YyIAD+Ed8EicJSg2foejEAkMJzh7My1IQA11HrHLoo="},{title:"RSAES-OAEP Encryption Example 1.2",message:"dQxAR/VH6OQUEYVlIymKybriRe+vE5f75W+d1Q==",seed:"DMdCzkqbfzL5UbyyUe/ZJf5P418=",encrypted:"ZA2xrMWOBWj+VAfl+bcB3/jDyR5xbFNvx/zsbLW3HBFlmI1KJ54Vd9cw/Hopky4/AMgVFSNtjY4xAXp6Cd9DUtkEzet5qlg63MMeppikwFKD2rqQib5UkfZ8Gk7kjcdLu+ZkOu+EZnm0yzlaNS1e0RWRLfaW/+BwKTKUbXFJK0Q="},{title:"RSAES-OAEP Encryption Example 1.3",message:"2Urggy5kRc5CMxywbVMagrHbS6rTD3RtyRbfJNTjwkUf/1mmQj6w4dAtT+ZGz2md/YGMbpewUQ==",seed:"JRTfRpV1WmeyiOr0kFw27sZv0v0=",encrypted:"Qjc27QNfYCavJ2w1wLN0GzZeX3bKCRtOjCni8L7+5gNZWqgyLWAtLmJeleuBsvHJck6CLsp224YYzwnFNDUDpDYINbWQO8Y344efsF4O8yaF1a7FBnzXzJb+SyZwturDBmsfz1aGtoWJqvt9YpsC2PhiXKODNiTUgA+wgbHPlOs="},{title:"RSAES-OAEP Encryption Example 1.4",message:"UuZQ2Y5/KgSLT4aFIVO5fgHdMW80ahn2eoU=",seed:"xENaPhoYpotoIENikKN877hds/s=",encrypted:"RerUylUeZiyYAPGsqCg7BSXmq64wvktKunYvpA/T044iq+/Gl5T267vAXduxEhYkfS9BL9D7qHxuOs2IiBNkb9DkjnhSBPnD9z1tgjlWJyLd3Ydx/sSLg6Me5vWSxM/UvIgXTzsToRKq47n3uA4PxvclW6iA3H2AIeIq1qhfB1U="},{title:"RSAES-OAEP Encryption Example 1.5",message:"jaif2eX5dKKf7/tGK0kYD2z56AI=",seed:"sxjELfO+D4P+qCP1p7R+1eQlo7U=",encrypted:"NvbjTZSo002qy6M6ITnQCthak0WoYFHnMHFiAFa5IOIZAFhVohOg8jiXzc1zG0UlfHd/6QggK+/dC1g4axJE6gz1OaBdXRAynaROEwMP12Dc1kTP7yCU0ZENP0M+HHxt0YvB8t9/ZD1mL7ndN+rZBZGQ9PpmyjnoacTrRJy9xDk="},{title:"RSAES-OAEP Encryption Example 1.6",message:"JlIQUIRCcQ==",seed:"5OwJgsIzbzpnf2o1YXTrDOiHq8I=",encrypted:"Qs7iYXsezqTbP0gpOG+9Ydr78DjhgNg3yWNm3yTAl7SrD6xr31kNghyfEGQuaBrQW414s3jA9Gzi+tY/dOCtPfBrB11+tfVjb41AO5BZynYbXGK7UqpFAC6nC6rOCN7SQ7nYy9YqaK3iZYMrVlZOQ6b6Qu0ZmgmXaXQt8VOeglU="}],f(a,l,"sha1",p),e="AZR8f86QQl9HJ55whR8l1eYjFv6KHfGTcePmKOJgVD5JAe9ggfaMC4FBGQ0q6Nq6fRJQ7G22NulE7Dcih3x8HQpn8UsWlMXwN5RRpD5Joy3eg2cLc9qRocmbwjtDamAFXGEPC6+ZwaB5VluVo/FSZjLR1Npg8g7aJeZTxPACdm9F",t="AQAB",n="CCPyD6212okIip0AiT4h+kobEfvJPGSjvguq6pf7O5PD/3E3BMGcljwdEHqumQVHOfeeAuGG3ob4em3e/qbYzNHTyBpHv6clW+IGAaSksvCKFnteJ51xWxtFW91+qyRZQdl2i5rO+zzNpZUto87nJSW0UBZjqO4VyemS2SRi/jk=",r="AVnb3gSjPvBvtgi4CxkPTT4ivME6yOSggQM6v6QW7bCzOKoItXMJ6lpSQOfcblQ3jGlBTDHZfdsfQG2zdpzEGkM=",i="AStlLzBAOzi0CZX9b/QaGsyK2nA3Mja3IC05su4wz7RtsJUR9vMHzGHMIWBsGKdbimL4It8DG6DfDa/VUG9Wi9c=",s="Q271CN5zZRnC2kxYDZjILLdFKj+1763Ducd4mhvGWE95Wt270yQ5x0aGVS7LbCwwek069/U57sFXJIx7MfGiVQ==",o="ASsVqJ89+ys5Bz5z8CvdDBp7N53UNfBc3eLv+eRilIt87GLukFDV4IFuB4WoVrSRCNy3XzaDh00cpjKaGQEwZv8=",u="AnDbF9WRSwGNdhGLJDiac1Dsg2sAY6IXISNv2O222JtR5+64e2EbcTLLfqc1bCMVHB53UVB8eG2e4XlBcKjI6A==",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 2.1",message:"j/AMqmBccCgwY02abD1CxlK1jPHZL+xXC+7n",seed:"jEB7XsKJnlCZxT6M55O/lOcbF4I=",encrypted:"AYGviSK5/LTXnZLr4ZgVmS/AwUOdi81JE5ig9K06Mppb2ThVYNtTJoPIt9oE5LEq7Wqs30ccNMnNqJGt3MLfNFZlOqY4Lprlm1RFUlfrCZ1WK74QRT8rbRPFnALhDx+Ku12g0FcJMtrPLQkB23KdD+/MBU5wlo6lQMgbBLyu/nIO"},{title:"RSAES-OAEP Encryption Example 2.2",message:"LQ==",seed:"tgDPPC5QbX8Wd4yRDTqLAD7uYdU=",encrypted:"AYdZ/x32OyeSQQViMUQWqK6vKsY0tG+UCrgtZNvxZe7jMBHadJ1Lq24vzRgSnJ5JJ32EUxErQpoiKoRxsHCZOZjnWIYcTT9tdJ2RxCkNMyx6SrP36jX/OgfUl8lV/w/8lQBrYsbSloENm/qwJBlseTQBLC35eO8pmrojmUDLoQJF"},{title:"RSAES-OAEP Encryption Example 2.3",message:"dPyIxRvJD3evnV6aSnATPUtOCzTaPDfH744=",seed:"pzdoruqpH52MHtb50rY0Z/B8yuM=",encrypted:"AYgCurBMYDJegcSWIxHyvnwq3OkwQaAHGciPlXV18sefG3vIztEVxwazEcCKLZhso7apM2sUfCnG8ilAnd7GUb0f3VoLf2EMmTf9tKOnYjZLizIGtOpIX9CY0I9j1KqLsml9Ant1DDLX906vUYDS6bZrF8svpVUjvCgNoQ0UviBT"},{title:"RSAES-OAEP Encryption Example 2.4",message:"p+sqUDaTHSfU6JEybZlpL/rdqb9+/T405iLErcCF9yHf6IUHLHiiA7FRc5vlQPqMFToQ8Ao=",seed:"mns7DnCL2W+BkOyrT7mys4BagVY=",encrypted:"AKRXjLwXYximOPun0B3xV0avRNT2zZbX58SVy/QlsJxknTK/iG2kj7r5iaIRcYfK+x+1gDF2kOPM1EaSC3r4KzHbWATYfQFRSsv6kVbngvhn9r7ZRJ4OmiwJvOzGqgh2NpZeNLPsdm8v4uQwGKL93rFAYWoOnYLlMxAk7gZS/HZB"},{title:"RSAES-OAEP Encryption Example 2.5",message:"LvKwZvhUwz873LtZlKQ15z1sbA==",seed:"6zzrvErcFrtI6IyK7A40r39Cf9M=",encrypted:"AOvF9f2nfP2tPINkGpAl531y2Kb7M6gQ9ZUPjXTHPo2THoY02GqxJGJWrge2AFtxt/L7mDUSGDMc5puP+9ydoIu8nHBPh23rnfn8LsBlyth/kJCweswXqn+ZeyespIgG6Jf3cdlRQf5FJtilMBtnhifvq3B/1A++vW55KiVhPnrs"},{title:"RSAES-OAEP Encryption Example 2.6",message:"in+zRMi2yyzy7x9kP5oyGPbhm7qJwA==",seed:"TEXPTVfJjj1tIJWtxRxInrUN/4Q=",encrypted:"AQg57CDCe5BS5Vvvubd+b8JukHXXpUN4xkar31HkRb1XFd6BeJ9W8YA9kXB2Sp6Ty3h5hpQCPuc5POBLxdj4xaUsFx1Dg346ymL2CesKpf+wlg7wQZjddU9X9/vmq/dlzxGLTKRDsjtaqyZvlSMmrEWBEAZEMl+LchrNXQT/FO86"}],f(a,l,"sha1",p),e="ArWP7AOahgcApNe2Ri+T5s3UkRYd3XT06BC0DjwWUgBqXCd7J3TBEwWky6taeO+lfheobfej+jb8Sx0iSfIux8LdakYyMqzOqQbWbr6AtXBLEHKdpvgzI0q7Xv3UopLL+tM7TTP6ehS4w5e1bjrNISA0KLd836M6bacGs9iw/EPp",t="AQAB",n="FbSKW1aDqUZw4jtXGPgU+g4T+FA49QcRGCy6YVEFgfPSLH4jLvk34i5VHWi4bi+MsarYvi5Ij13379J54/Vo1Orzb4DPcUGs5g/MkRP7bEqEH9ULvHxRL/y+/yFIeqgR6zyoxiAFNGqG3oa/odipSP0/NIwi6q3zM8PObOEyCP0=",r="Ab8B0hbXNZXPAnDCvreNQKDYRH0x2pGamD9+6ngbd9hf43Gz6Tc+e2khfTFQoC2JWN5/rZ1VUWCVi0RUEn4Ofq8=",i="AY0zmWWBZts4KYFteylUFnWenJGYf1stiuzWOwS0i9ey/PIpu3+KbciLoT3S45rVW20aBhYHCPlwC+gLj9N0TOc=",s="BsCiSdIKby7nXIi0lNU/aq6ZqkJ8iMKLFjp2lEXl85DPQMJ0/W6mMppc58fOA6IVg5buKnhFeG4J4ohalyjk5Q==",o="0dJ8Kf7dkthsNI7dDMv6wU90bgUc4dGBHfNdYfLuHJfUvygEgC9kJxh7qOkKivRCQ7QHmwNEXmAuKfpRk+ZP6Q==",u="jLL3Vr2JQbHTt3DlrTHuNzsorNpp/5tvQP5Xi58a+4WDb5Yn03rP9zwneeY0uyYBHCyPfzNhriqepl7WieNjmg==",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 3.1",message:"CHggtWno+o0=",seed:"jO1rGWKQgFeQ6QkHQBXmogsMSJQ=",encrypted:"AmoEhdlq69lrQ4IIUJm5Yuaivew9kMjbYl4UNy3oXi1be6q2XI+vkbtVBPtJWvzlyYiz9qUuIOHWy9NWbFzR8rgxi7VCzA6iXEqrmTKvogdg6t3seEOWoH6g7yTU5vTTflBSp6MeFGqkgKERu+kmQBMH4A9BADOEK22C/lzk366A"},{title:"RSAES-OAEP Encryption Example 3.2",message:"RlOsrxcZYLAfUqe+Y6OrIdw2jsQ7UNguw3geBA==",seed:"tCkdZWdVCEjMFWlnyAm6q2ylB/A=",encrypted:"Ak24nHgCmJvgeDhHhjCElBvyCddhmH44+Xy19vG8iNpypQtz668RyHnE+V3ze4ULj2XXYi4lsbiJ6A/oC6yiBp1uDh2CmVP8RZBp3pjql5i0UeVX6Zq/j+PZzPkJbrvz5SVdO04cbS7K3wZ6NZ7qhkBazUfV4WVRfMr9R9bb7kv1"},{title:"RSAES-OAEP Encryption Example 3.3",message:"2UzQ4I+kBO2J",seed:"zoko9gWVWCVACLrdl5T63NL9H2U=",encrypted:"Ajm85oEDJEFSiHfW0ci7KKo7yX8d9YRWNhiZV5doOETKhmZHMvS+16CqsIOqq/tyOPWC4wlYwgJOROVwQ7l5UP1UPal3yQzd5TN9YYRC+Z5g13g6tZzm3Z1pxHrR6WK+wi0FiVz/jT9k7VJh2SsmeFEDk0hJkLo/fwaBiub/zoo6"},{title:"RSAES-OAEP Encryption Example 3.4",message:"bMZBtrYeb5Y5dNrSOpATKE7x",seed:"bil59S1oFKV9g7CQBUiI8RmluaM=",encrypted:"AplMYq/Xb0mLof0s9kKFf8qB9Dc8sI8cuu5vAlw7UStCw+h3kRNHZkgDnb4Ek/kkYpL6wolQYA58DzLt+cgbnexFw73gzI2IR1kBaZB7fcWZHOspuwcU1hPZbfDxLsXY01B8jueueN2D8hb6Yd4QA2OspIp+kUrp9C3fvpQ7Cdmg"},{title:"RSAES-OAEP Encryption Example 3.5",message:"31FRgyth9PJYkftBcvMo0u3fg3H/z9vpl5OSlfMOymkYAXz9oRU796avh1kyIw==",seed:"LXYL/jjFneNM3IuMeKOOZihKLSc=",encrypted:"AWIEL/aWlZKmFnAxgRojmDTOY4q/VP7IuZR4Eir+LuZ/jFsYsDOYBb/bxaTmcgs3xZz7qUJGTFl/9TKhGYIVRf0uWbEU5h2vcYIFKfUCnPUklUMnw07F5vW6fvzE3pQ6uK1O14exRUMp9w23mKOo9NkvgnTispSK3mJ86O4z5Dxg"},{title:"RSAES-OAEP Encryption Example 3.6",message:"PDutiTxUSm1SCrAiMZGIyNUEt6eIuFCQO4WXLqoYVS4RNKetYJiCYlT/erZys9jrMVj6xtTLrvE=",seed:"8XR3nF/Tz+AHuty3o2ybVb/Pvw4=",encrypted:"ABEgUeddBklDvER4B15DSC/VnO4Ged5ok+7DqUPapJC5aRyT38BGS2YjufPb0+cAgyZPA0s3T3QWThoAdjcl5XR0S6C524NDTzHflvbiom9tjro0i9RobCI4rAfDeqw3hdHH7qL4Gf2RSReY7Y6c715Dt4Gw4CduN8Q/+UktAFcw"}],f(a,l,"sha1",p),e="BRJAtswABPpI0BNGccB4x8jew7Pi8lvCVkRnM52ziFPQa4XupbLeNTv/QqwuRryX+uaslhjalTelyPVTweNXYlmR1hCNzXiF+zolQT9T78rZSMs1zZua6cHGdibRE9V93kxb6na7W7felsANBzculoWm11z50jn6FI1wkxtfP7A5",t="AQAB",n="BBH/yjt8penpvn/jioUQXjU4ltsFxXlq7NKnJRYes2UchimpuGK5BNewx7N/jLWhwrVAAQGKAKHrLK/k7k6UksNIvCvtq0ueu/Bk6O/zIrkAn47sZTkF9A34ijzcSdRWf3VifUGspiQSm0agt8aY5eZfK3uhAsdJoQE1tlQNBAE=",r="AnRYwZ7BY2kZ5zbJryXWCaUbj1YdGca/aUPdHuGriko/IyEAvUC4jezGuiNVSLbveSoRyd6CPQp5IscJW266VwE=",i="AhDumzOrYXFuJ9JRvUZfSzWhojLi2gCQHClL8iNQzkkNCZ9kK1N1YS22O6HyA4ZJK/BNNLPCK865CdE0QbU7UTk=",s="OfoCi4JuiMESG3UKiyQvqaNcW2a9/R+mN9PMSKhKT0V6GU53J+Sfe8xuWlpBJlf8RwxzIuvDdBbvRYwweowJAQ==",o="AV2ZqEGVlDl5+p4b4sPBtp9DL0b9A+R9W++7v9ax0Tcdg++zMKPgIJQrL+0RXl0CviT9kskBnRzs1t1M8eVMyJk=",u="AfC3AVFws/XkIiO6MDAcQabYfLtw4wy308Z9JUc9sfbL8D4/kSbj6XloJ5qGWywrQmUkz8UqaD0x7TDrmEvkEro=",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 4.1",message:"SoZglTTuQ0psvKP36WLnbUVeMmTBn2Bfbl/2E3xlxW1/s0TNUryTN089FmyfDG+cUGutGTMJctI=",seed:"HKwZzpk971X5ggP2hSiWyVzMofM=",encrypted:"BMzhlhSEXglBUqP+GOVOMzDETl77xkrhaIbLGGkBTMV4Gx+PngRThNARKhNcoNEunIio5AY0Ft6q44RPYNbpb+FVFF9FJbmjRDHKN2YYD3DhWl5djosaUW/4cGCfE/iWk1ztGIJ5pY7RPQcRQnfXXGVoYH4KsJL9gDoiPkqO4LGo"},{title:"RSAES-OAEP Encryption Example 4.2",message:"sK3E8/4R2lnOmSdz2QWZQ8AwRkl+6dn5oG3xFm20bZj1jSfsB0wC7ubL4kSci5/FCAxcP0QzCSUS7EaqeTdDyA==",seed:"9UXViXWF49txqgy42nbFHQMq6WM=",encrypted:"AJe2mMYWVkWzA0hvv1oqRHnA7oWIm1QabwuFjWtll7E7hU60+DmvAzmagNeb2mV4yEH5DWRXFbKA03FDmS3RhsgLlJt3XK6XNw5OyXRDE2xtpITpcP/bEyOiCEeCHTsYOB3hO7SarqZlMMSkuCcfPq4XLNNm4H5mNvEBnSoortFe"},{title:"RSAES-OAEP Encryption Example 4.3",message:"v21C5wFwex0CBrDItFoccmQf8SiJIZqCveqWW155qWsNAWPtnVeOya2iDy+88eo8QInYNBm6gbDGDzYG2pk=",seed:"rZl/7vcw1up75g0NxS5y6sv90nU=",encrypted:"AwH5NenEery0isu+CYldn1lxrxSDnaT/lUF+5FPR/XcxkHK7cpfhtV11Yc2dG7JMGpo3xhmGQwgkKASHnYbr0AHc5Rg5deFQaYm3DlqDQ0FU1cv9aiR4fmDrDGWNKsGTMC0RksbmItShKtS1OSO8okbfMcY5XjdwLGp4rggfudBl"},{title:"RSAES-OAEP Encryption Example 4.4",message:"+y7xEvXnZuuUAZKXk0eU974vb8HFjg==",seed:"E2RU31cw9zyAen5A2MGjEqxbndM=",encrypted:"AtEQrTCvtye+tpHdDPF9CvGh5/oMwEDsGkuiakLFnQp5ai4iyPNXzMmLZRms62gulF5iy3NGFKUpQHzUUr7j5E/s6EI8wZ5VVIuLmUuEnH7N5JM+dgN+HQzkQnWwhxDGjkMBMLkpcw7XfgmwFWQsVZPwTk/7lBB5gQKo6W/9/hHk"},{title:"RSAES-OAEP Encryption Example 4.5",message:"KMzUR7uehRZtq7nlt9GtrcS5058gTpbV5EDOmtkovBwihA==",seed:"vKgFf4JLLqJX8oYUB+72PTMghoE=",encrypted:"ANu4p0OdkO/ZGaN3xU+uj+EexYw7hYNi4jrRuKRDEHmQZrmTR6pSVpHSrcWNmwbjTyiMFwOQxfDhHAqjZFlZ8Y7nno8r6NesXCPQYfGN10uMXypY/LXrDFT5nwGoMkdWgpJTZYM0CUjXqMl8Ss0emNHincMg6XomBTKoqnp1ih7C"},{title:"RSAES-OAEP Encryption Example 4.6",message:"8iJCdR7GsQ==",seed:"Ln4eF/ZHtd3QM+FUcvkPaBLzrE4=",encrypted:"AKX/pHaMi77K7i23fo8u7JlZWTNUVSCDXlun25ST0+F83e/mpfVnYkRxkI204tg6D77mBgj8hASVA7IjSgfcg7J7IoR62JIP9C9nTvebdigLACM9K1G4yycDqdQr+8glDJbsMsBR5X8bS6Uo24nDfkxU4n5uZKxpY1roh9lUFhmp"}],f(a,l,"sha1",p),e="Cq3z+cEl5diR8xrESOmT3v5YD4ArRfnX8iulAh6cR1drWh5oAxup205tq+TZah1vPSZyaM/0CABfEY78rbmYiNHCNEZxZrKiuEmgWoicBgrA2gxfrotV8wm6YucDdC+gMm8tELARAhSJ/0l3cBkNiV/Tn1IpPDnv1zppi9q58Q7Z",t="AQAB",n="AlbrTLpwZ/LSvlQNzf9FgqNrfTHRyQmbshS3mEhGaiaPgPWKSawEwONkiTSgIGwEU3wZsjZkOmCCcyFE33X6IXWI95RoK+iRaCdtxybFwMvbhNMbvybQpDr0lXF/fVKKz+40FWH2/zyuBcV4+EcNloL5wNBy+fYGi1bViA9oK+LF",r="A7DTli9tF1Scv8oRKUNI3PDn45+MK8aCTyFktgbWh4YNrh5jI5PP7fUTIoIpBp4vYOSs1+YzpDYGP4I4X0iZNwc=",i="AuTDLi9Rcmm3ByMJ8AwOMTZffOKLI2uCkS3yOavzlXLPDtYEsCmC5TVkxS1qBTl95cBSov3cFB73GJg2NGrrMx8=",s="AehLEZ0lFh+mewAlalvZtkXSsjLssFsBUYACmohiKtw/CbOurN5hYat83iLCrSbneX31TgcsvTsmc4ALPkM429U=",o="65CqGkATW0zqBxl87ciBm+Hny/8lR2YhFvRlpKn0h6sS87pP7xOCImWmUpfZi3ve2TcuP/6Bo4s+lgD+0FV1Tw==",u="AS9/gTj5QEBi64WkKSRSCzj1u4hqAZb0i7jc6mD9kswCfxjngVijSlxdX4YKD2wEBxp9ATEsBlBi8etIt50cg8s=",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 5.1",message:"r3GpAeOmHTEy8Pwf20dPnqZXklf/wk0WQXAUWz296A==",seed:"RMkuKD93uUmcYD2WNmDIfS+TlGE=",encrypted:"A2BGpKR9ntO6mokTnBBQOOt0krBaXWi/1TrM/0WX96aGUbR7SkYn2Sfkhe7XtFZkIOi0CYeeXWBuriUdIqXfeZ95IL/BF7mSVypTsSYxRrzqAzhcxehTyaEByMPhvaMaUZgHSWxsteXvtAiCOjUrj6BmH7Zk763Vk965n/9e0ADl"},{title:"RSAES-OAEP Encryption Example 5.2",message:"o7hEoII5qKxBYFrxemz9pNNQE2WFkDpBenkmh2BRmktKwzA+xz8Ph8+zI5k=",seed:"yyj1hgZZ/O7knD7q/OYlpwgDvTI=",encrypted:"A9brZU7c5hW8WfRVJl7U5aGCI8u5vk5AabRzgE1d6W9U3KqmA9BJxdlKoUcN/NIlQGa3x7Yf8fb2dw4yFcUTmf1ONOxQgrxI8ImECtBDVK5m3A8b0Y5GGjPMEli0Q6KDem3yZ1mqIwIzSYb4c4DJzJ1Tvp+ZYF0smpfaewkVpKet"},{title:"RSAES-OAEP Encryption Example 5.3",message:"MIsOy9LHbLd/xvcMXt0jP9LyCSnWKfAmlTu2Ko9KOjFL3hld6FtfgW2iqrB00my2rN3zI647nGeKw88S+93n",seed:"IoX0DXcEgvmp76LHLLOsVXFtwMo=",encrypted:"B3CVIYFkn5+fB/9ib/OiLDXEYkQ9kF1Fap/Qv/Q8rCynqfVU6UeLmsw6yDiwIED/0+GEfeLkJTkp+d2e5ARDJamwXKu4CLLuhA004V0QWj8feydpWhoHotc/4I7KqjycnU1aif+JDVRyfXrkDA7BqN2GFl2O4sY2gUEBaki1W2ln"},{title:"RSAES-OAEP Encryption Example 5.4",message:"FcW57hGF",seed:"SfpF06eN0Q39V3OZ0esAr37tVRM=",encrypted:"CBK3Z2jry2QtBAJY5fREGgGFIb2WaH5sXomfzWwXWI/1moLMiuA6S0WzEpmvF4jDKffc0oX4z0ztgmBrl2EmcaRb7coTNEIUTRYX0RT4AoV/D51zl1HFej+e5ACRLGHi5pkr4DGkPdSPproU7vfEIrXtxOevoE/dOPQC0ci7cZq/"},{title:"RSAES-OAEP Encryption Example 5.5",message:"IQJuaADH+nKPyqug0ZauKNeirE/9irznlPCYX2DIpnNydzZdP+oR24kjogKa",seed:"8Ch0EyNMxQNHJKCUxFhrh6/xM/w=",encrypted:"B7YOFOyVS/0p5g0AR+eJ9R1XGGxjWJkDMGeTztP2gkHHQ1KaumpjdPkuGeAWPvozaX4Zb3Zh36qkeqxr3l5R3rUHxyxYmiyhaT2WsUYDgSSbLNuerER2nySJxdPS+Z8O48fuW/ZKWsecQr1DPxSb6MtZVINhZAWVUTyXr3vCUJcj"},{title:"RSAES-OAEP Encryption Example 5.6",message:"VB43totsiHK4TAI=",seed:"2fukXJbyHm4m0p6yzctlhb6cs0E=",encrypted:"CMNtTdozQjsu1oMNhfZBG6Hc9HCh+uDr7+58CJ8lbO90y5bqacOPYPOavuRBKby0yS3n95diOyAHTj2cKJlwHtkHHh76C92E1MPlEwMC2PAkC6ukuEpxzAMvIjWl/w+uJ3w+j5ESvvRMmuINF1/JpAWL/JMLoxsC4uT0REg3EPJK"}],f(a,l,"sha1",p),e="ErF/ba0uzRn/RtwT94YPCeDgz7Z3s4pSWSMFzq8CLBZtuQ0ErCnjP33RLZ+vZuCBa7Y+rSZ8x9RsF8N74hS8oqItcjpk5EQHQ2tvyWVymu/CVU83bNXc6mgpN4CmK/OdAClIWhYLu55dwJctIaUE9S5e4CiqQWMy9RCy6c/19yKv",t="AQAB",n="ApXso1YGGDaVWc7NMDqpz9r8HZ8GlZ33X/75KaqJaWG80ZDcaZftp/WWPnJNB7TcEfMGXlrpfZaDURIoC5CEuxTyoh69ToidQbnEEy7BlW/KuLsv7QV1iEk2Uixf99MyYZBIJOfK3uTguzctJFfPeOK9EoYij/g/EHMc5jyQz/P5",r="BKbOi3NY36ab3PdCYXAFr7U4X186WKJO90oiqMBct8w469TMnZqdeJpizQ9g8MuUHTQjyWku+k/jrf8pDEdJo4s=",i="BATJqAM3H+20xb4588ALAJ5eCKY74eQANc2spQEcxwHPfuvLmfD/4Xz9Ckv3vv0t1TaslG23l/28Sr6PKTSbke0=",s="A5Ycj3YKor1RVMeq/XciWzus0BOa57WUjqMxH8zYb7lcda+nZyhLmy3lWVcvFdjQRMfrg6G+X63yzDd8DYR1KUs=",o="AiGX4GZ0IZaqvAP6L+605wsVy3h9YXrNMbt1x7wjStcG98SNIYLR8P+cIo3PQZZ7bAum0sCtEQobhXgx7CReLLE=",u="BAHEwMU9RdvbXp2W0P7PQnXfCXS8Sgc2tKdMMmkFPvtoas4kBuIsngWN20rlQGJ64v2wgmHo5+S8vJlNqvowXEU=",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 6.1",message:"QEbKi6ozR8on9J4NgfnMHXG+m6UX1A==",seed:"3Q9s/kFeiOWkaaUfu6bf1ArbQ4Q=",encrypted:"BjDuvNKFbCT3mIBuQfnmc0Xtqc7aOGrMn6yuoe7tBqzlg3CXGNnRafrfQU1cdvkploM+8wW3Wx5LlfZiog+u3DuuDEgnqL+KiO29V+wgOieoQfAuQ6YVurGoysBwHeNN6972KgiAibVew26nUi/T7I0GtqBz5t+DMVO8Cu/ZO9Gj"},{title:"RSAES-OAEP Encryption Example 6.2",message:"XMcsYCMd8Ds9QPm1eTG8MRCflyUn8osZ50gMcojLPJKyJRIhTkvmyRR5Ldq99X+qiqc=",seed:"jRS9lGoTURSPXK4u2aDGU+hevYU=",encrypted:"Drw3N2FzpP0vicxVwspismsR1Rw8fOSeiEX3TnYHMXxDa8jSO5Zn3+udCHI0tHvGg3F1rlwFWfa4HX0iQW0+UPSsUz2PCBLy2555H+nHdayLatD1Na2c6yOkoCAUxYqz+NMWFJmiYPOTSOcUriodNEMgj9i3Isz9+zk+mAEfmeY/"},{title:"RSAES-OAEP Encryption Example 6.3",message:"sg5lEwMJL0vMtDBwwPhtIwSTYu2WZC/FYywn20pS49gx8qsGiyOxSYecAC9r8/7ul1kRElYs",seed:"bAdbxFUg8WXAv16kxd8ZG8nvDkQ=",encrypted:"Cpi/EJNhk5RDbPaNjzji8Vj96OpU80NfI5uNBrgyGEQgJHau7ZYAlJJIDOOo1wVJjEyMaPAVAdyB22CPYAhzUMjDsL0unvaoFFi3yAG4ny5P6Z1JALpqS15althl3Gdsd1WSh5QTDWKAqBYKGQ8t8+p8+aoCcdiOnmkF7PHFFS1l"},{title:"RSAES-OAEP Encryption Example 6.4",message:"aE4wOMXAQfc=",seed:"O7w71mN9/hKEaQECm/WwwHEDQ5w=",encrypted:"AI56Z8rPtcTiS+x97hSRF/GVmM6MRYCP74jGCP+c1uaVJjuaPArUuLpMlSOOlqhCK4U1YpyNU4I3RHmtE/o5l0skL5p1nur5yDrVqMoYlAoBYrp1WHbfJj9L1QxlJcVgkCZ8Hw4JzgiZoM81nogSCr2b+JNEWzyud9Ngc1mumlL4"},{title:"RSAES-OAEP Encryption Example 6.5",message:"MkiMsmLQQdbk3TX5h788ppbbHwasKaRGkw==",seed:"tGtBiT6L7zJvZ1k4OoMHHa5/yrw=",encrypted:"AAA0dEFse2i9+WHDhXN5RNfx9AyzlTQ8aTzAtP5jsx/t8erurJzMBnizHcMuCXdIlRTE8JCF9imKllPwGupARf9YLuiHviauV1tz7vfzd0kh43Wj0ZrdoMoxqhhJiHwfQsrJZ396L06SP25ahos4wITvGHWU3J9/BI/qLgKVU4Sr"},{title:"RSAES-OAEP Encryption Example 6.6",message:"ULoUvoRicgJ5wwa6",seed:"CiQDMSpB49UvBg+8E6Z95c92Cac=",encrypted:"CgJt2l/IeF972b91Mntj6F4sD97l2ttl69ysmuHelcksZyq0M6p6jmnOam2Il/rErEpU3oQa5eW7znaHh515Y0zqejBoQGXHFNUkCbkoJWu/U+q81SMetyWVBFNzmb0pFktybTOkbacBNgpBaKCRzKty1Epi/tJGwP/qWxNIq1Rw"}],f(a,l,"sha1",p),e="MRF58Lz8m508oxXQDvMNe906LPrpkRv+3LlIs6R4LQcytqtEqkvwN0GmRNwBvsPmmwGgM+Z12KzXxJJcaxrsMRkFHf2Jdi0hXUVHX/y1n5CBSGI/NxdxVvauht16fF9D3B4fkIJUBYooSl8GwAIXk6h/GsX+/33K7mnF5Ro3ieNz",t="AQAB",n="Bwz8/y/rgnbidDLEXf7kj0m3kX1lMOHwyjRg8y4CdhdEh8VuIqRdJQDXd1SVIZ19Flqc872Swyr5qY2NycwpaACtyUoKVPtA80KRv4TujqErbxCTWcbTVCpQ+cdn9c//BaaBwuZW+3fKqttL6UaNirzU35j1jobSBT+hNJ90jiGx",r="B0kmLBEc1HDsJWbms3MvwJMpRpqhkHHTucAZBlFMbx0muqFL6rCXHIt+YRpPeQCdb+p3aSjKJShbDeNkPRo/jHE=",i="BrweUOlsAr9jbp7qi4mbvr92Ud533UdMPpvCO62BgrYZBMfZffvr+x4AEIh4tuZ+QVOR1nlCwrK/m0Q1+IsMsCM=",s="A7x+p/CqsUOrxs6LlxGGNqMBcuTP4CyPoN2jt7qvkPgJKYKYVSX0iL38tL1ybiJjmsZKMJKrf/y/HVM0z6ULW/E=",o="AmKmqinCo8Z9xTRsBjga/Zh6o8yTz7/s9U/dn514fX9ZpSPTmJedoTei9jgf6UgB98lNohUY3DTLQIcMRpeZStk=",u="ZJ1MF7buFyHnctA4mlWcPTzflVDUV8RrA3t0ZBsdUhZq+KITyDliBs37pEIvGNb2Hby10hTJcb9IKuuXanNwwg==",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 7.1",message:"R6rpCQ==",seed:"Q90JoH/0ysccqkYy7l4cHa7kzY8=",encrypted:"FojkzneUu6bLcBQWns1VnO3iowtWpSto2f4Yzxlz75eyoDFTlRx1X2KUqkmtvbVYRatodfs5hsk+z5J5YoQNKC+eVM6LaQ98DLi71zRA2VcdGxbNkmD56rR4PMSC5SI9xglzhxeD7Cewrg/UdzLLwoahc/ySsA+0umgkZHzZPIXB"},{title:"RSAES-OAEP Encryption Example 7.2",message:"HZsuIiPZvBO/ufFiznNdtIunxo9oIqChp7auFlg05w==",seed:"Opw87HuE+b063svGc+yZ1UsivJs=",encrypted:"EFLtOXsuAeHQ7hxQvyQ2P5XlBPSgNDSgj9giV07WuXNu27XzkNsQMhR5qKE5NQ4r1Jd8N3jvMx8+eK4RiyaEUfIKLwHUcfXVPFZpNxcbLbwtS95FmleZ8DctZXQjmyMj0kXQu4HChrY8iaNhAXM35JAviKRn9MfyRL/Vq0ZDf/O2"},{title:"RSAES-OAEP Encryption Example 7.3",message:"2Xb8",seed:"dqdeW2FXpVbPiIS7LkXCk91UXPU=",encrypted:"IVXNhD/ySk7outt2lCYAKKSQgTuos2mky/EG7BSOUphwf1llvn0QHBBJ6oWEwkzWNFWtnBBNaGKC0/uAOkwRwcLpuRxxeIAdG2ZA8AP1co3wB7ikzMkrzgXkGicnjXyFAYxSQUMTpQd3iQAdTwGRC3Kq0F0iCqFKWHM6dIm8VFVr"},{title:"RSAES-OAEP Encryption Example 7.4",message:"1HOGI98iOqQ4Q9+EZ1NMQdAT4MgDxiTiY2ZrI5veQKXymuuN5549qmHdA3D0m9SwE4NLmCEq72scXuNzs8s=",seed:"eGYxSmrW8rJQo1lB2yj1hktYWFk=",encrypted:"CrFMNzrrfUMo0KqtjAlNiLnrCYuV8hBUopCCUivnwnoxKHi2N5F+PYGebDxWjbXYQ4ArBtUdnpiivgv0DAMUI7AO37/4Mg77kXG9IERlOky5xRIvbGXoPNouw8EmAnqcGla6h00P6iPzgLgs8kC4z1QABHWMTHfZNBV6dPP8Er+s"},{title:"RSAES-OAEP Encryption Example 7.5",message:"u0cjHKXqHTrUbJk0XZqKYQ==",seed:"shZu1HLVjbEMqyxrAAzM8Qp9xQk=",encrypted:"AoOHoxgndDR5i02X9GAGjfUpj6ulBBuhF2Ghy3MWskGEEU7FACV+JYntO2B6HrvpemzC4CvxtoH0IxKjO3p32OeFXEpt4D48BGQ/eGuRomSg1oBeLOqR5oF363pk2SVeTyfnE7fM7ADcIA69IcLqK7iQ/q5JQt+UHcP5eJDtNHR4"},{title:"RSAES-OAEP Encryption Example 7.6",message:"IYSCcJXTXD+G9gDo5ZdUATKW",seed:"Umc73iyhZsKqRhMawdyAjWfX07E=",encrypted:"FMZ4qUrWBSXvOelZsvO6XAl6lP+RK2fbrOgFNcGHq9R9B1QgsYchUrugj3/DHzE7v5JzyRL8TAFJqbDPt5gH40brMyBpYRvsD/m80Wjx98M+dzE86kVLlOJUnuzwAuKs9/by0oRdT+CqsuWpLd9oxICuESR5NdH2JXSEIhauZ0EV"}],f(a,l,"sha1",p),e="W98OMNMh3aUUf4gkCPppGVSA34+A0/bov1gYUE82QnypsfVUC5xlqPaXTPhEeiRNkoAgG7Sfy75jeNGUTNIn4jD5bj0Q+Bnc7ydsZKALKktnAefQHeX6veOx6aDfgvRjE1nNImaWR/uxcXJGE07XtJfP/73EK1nHOpbtkBZiEt/3",t="AQAB",n="D30enlqqJf0T5KBmOuFE4NFfXNGLzbCd8sx+ZOPF6RWtYmRTBBYdCYxxW7eri9AdB+rz/tfH7QivKopi70SrFrMg4Ur3Kkj5av4mKgrkz2XmNekQeQzU7lzqdopLJjn35vZ3s/C7a+MrdXR9iQkDbwJk9Y1AHNuhMXFhV6dez2Mx",r="CgLvhEjZ+ti70NAEyMKql1HvlyHBsNAyNqVLDflHy67VolXuno4g1JHqFyP+CUcEqXYuiK/RbrtZlEEsqWbcT58=",i="CS02Ln7ToL/Z6f0ObAMBtt8pFZz1DMg7mwz01u6nGmHgArRuCuny3mLSW110UtSYuByaxvxYWT1MP7T11y37sKk=",s="B8cUEK8QOWLbNnQE43roULqk6cKd2SFFgVKUpnx9HG3tJjqgMKm2M65QMD4UA10a8BQSPrpoeCAwjY68hbaVfX0=",o="rix1OAwCwBatBYkbMwHeiB8orhFxGCtrLIO+p8UV7KnKKYx7HKtYF6WXBo/IUGDeTaigFjeKrkPH+We8w3kEuQ==",u="BZjRBZ462k9jIHUsCdgF/30fGuDQF67u6c76DX3X/3deRLV4Mi9kBdYhHaGVGWZqqH/cTNjIj2tuPWfpYdy7o9A=",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 8.1",message:"BQt1Xl5ogPe56daSp0w3quRJsxv+pt7/g3R6iX9sLIJbsa2/hQo8lplLXeWzPLx9SheROnln",seed:"dwb/yh7PsevuKlXlxuJM0nl6QSU=",encrypted:"CbNoPYousPspW2LtH7kpC3FEV7eCUxn0ZHhyr4ibMECUcgIK0SkSvxmxHUgZ9JYUgk/9hNCcChfn0XMJ0SkZeQQQqimVaZ9qhtvjJCtazCOvRWkQgNaxroEPs+MFcIfwlwCSzgC+lWL/QFO2Jizgyqk+E3I9LjpboHXUXw1htUth"},{title:"RSAES-OAEP Encryption Example 8.2",message:"TraNzZPKmxnfERvUNgj1VwJv5KodXPrCJ6PrWrlUjBigbd7SP4GCWYay/NcRCezvfv+Ihz8HXCqgxGn2nJK8",seed:"o3F9oUO03P+8dCZlqPqVBYVUg0M=",encrypted:"Ls8VyXxaFbFHaumGs3G1eiQoT0oWKo0MgYLnkF55IlbxgSul+D8fehMOQtzAIjKETtwUoxpo7peuVko4OjQRZWQkxfYt22Rgk8Nnvh/NpCbPAKBtist+V3dvu9hVrD31BvwWsdfD8hEPPYBo6R4YY2ODHIQJaA2NqezYzx+iDuOd"},{title:"RSAES-OAEP Encryption Example 8.3",message:"hgSsVjKMGrWtkXhh",seed:"7gYgkHPMoCa7Jk5Rhb+MaLdzn4Y=",encrypted:"S8iRMKWy2rt8L8+Q610Or55oG3FGo48xc6PZz+xS6p4KQZMuZIqdaTRMUNp2P1GgPJV2ITHoBSJU3NIkjLpA/TFmd4bOBaK3tTGsnaye1YSlm2d8GortjF0V1owFVp4r54C/fbY4/Sv9KoWrJ2hg83dzOPypif/XQ9E+4I4MqYk/"},{title:"RSAES-OAEP Encryption Example 8.4",message:"/dpfv27DYanZpKxoryFqBob0OLHg5cNrlV904QfznA3dzA==",seed:"mQrVc9xIqXMjW22CVDYY8ulVEF0=",encrypted:"LkVoR9j8Nv8BR9aZNZS5OXIn1Xd1LHnQ+QT8sDnU2BL+pgWntXTdgsp4b5N1I0hDjun1tUVJhdXw4WmePnrRdaMuFfA96wQquf4d2dsbuG+MCJzLRefvDF7nyptykMprFb7UcDl4ioqT/4Pg6NYkTHEAY2Le72m29Bb7PGhDg/vQ"},{title:"RSAES-OAEP Encryption Example 8.5",message:"Sl9JFL7iXePGk0HeBw==",seed:"7MY7KPB1byL1Ksjm7BJRpuwwRxg=",encrypted:"H7k1b9XEsXltsuv30NOTzIEK32FF3vwvznFPedk4ANXirCEeqLvsyktlS5TDsYsw3Vds403JVDbvV6CUFWRZIzWaXXtBce8iwkZw8bIp02A+kfdmcbffl+cxfJdzRHbV89F9Ic+Ctbqfg98uWI02mE/RtYRGi9I7LodfMvaJU/ey"},{title:"RSAES-OAEP Encryption Example 8.6",message:"jgfWb3uICnJWOrzT81CSvDNAn7f4jyRyvg==",seed:"OSXHGzYtQKCm3kIUVXm6Hn3UWfw=",encrypted:"Ov2cZgAUeyF5jYGMZVoPTJIS2ybQsN/cKnWUzLPSL1vx18PhEs1z/H1QnHqLr908J00TmQCflgnsS+ZHfkU/B1qjPbOChwwcNAmu85LXOGrjppa5mpS02gWJRH6VXRbJixdgKlm9c2J5/Nj7KAxEYtWQv6m/E/7VcOr96XMwosIQ"}],f(a,l,"sha1",p),e="zyzUHjTKOnKOpcuK/2TDbSe971Nk4zb9aNMSPFoZaowocBPoU9UVbVjRUZVFIPtPbXsXq7aBd2WQnFdhGWWdkCsZBu2KKxDBVcJNEkUo2rnurjeb6sZuSkEXhty4/QBi68Aw3hIZoEwqjBt90xMeTWtsruLjGl7UGsFQmy7x7iqxg2S+VoypQcJezIT/nWQ7XsGqrhAqINc/R5t4D9bakQdSEtnqwDoGdNiZ66LkMfTES2Fba6IjK9SzO67XPWJd",t="AQAB",n="GYwUHiNxWpK8z2oRmlvBE4lGjSgR9UjXJ+F7SrDrmG1vIR77U7cffMvqh+5px17mFQCMUzLetSvzkKvfv+N9cgU2gVmyY4wd4ybiHSIlHw+1hIs78VAF0qdDMPCv6RbuYszBNE0dg6cJ5gZ2JzhA9/N3QkpeCk2nXwGzH/doGc+cv90hUkPDkXwD7zgZkxLlZ7O/eu06tFfzce+KFCP0W2jG4oLsERu6KDO5h/1p+tg7wbjGE8Xh6hbBHtEl6n7B",r="/I1sBL7E65qBksp5AMvlNuLotRnezzOyRZeYxpCd9PF2230jGQ/HK4hlpxiviV8bzZFFKYAnQjtgXnCkfPWDkKjD6I/IxI6LMuPaIQ374+iB6lZ0tqNIwh6T+eVepl79",i="0gDUXniKrOpgakAdBGD4fdXBAn4S3BoNdYbok52c94m0D1GsBEKWHefSHMIeBcgxVcHyqpGTOHz9+VbLSNFTuicEBvm7ulN9SYfZ4vmULXoUy//+p0/s3ako0j4ln17h",s="2xaAL3mi8NRfNY1p/TPkS4H66ChiLpOlQlPpl9AbB0N1naDoErSqTmyL6rIyjVQxlVpBimf/JqjFyAel2jVOBe8xzIz3WPRjcylQsD4mVyb7lOOdalcqJiRKsI23V1Kt",o="oKMXz+ffFCP4em3uhFH04rSmflSX8ptPHk6DC5+t2UARZwJvVZblo5yXgX4PXxbifhnsmQLgHX6m+5qjx2Cv7h44G2neasnAdYWgatnEugC/dcitL6iYpHnoCuKU/tKh",u="CyHzNcNTNC60TDqiREV4DC1lW5QBdMrjjHyKTmSTwLqf0wN0gmewg7mnpsth5C2zYrjJiW23Bk4CrVrmFYfaFbRknJBZSQn+s328tlS+tyaOyAHlqLSqORG+vYhULwW+",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 9.1",message:"9zX9VbqSWSw7Urj5xPaaqhy++P6IrdCVWVQSRn+c9OwLiWxZ7aFiEOdUnIq7EM28IaEuyba1uP0vEDmetg==",seed:"jsll8TSj7Jkx6SocoNyBadXqcFw=",encrypted:"JnvNEYrKsfyLqByF1zADy4YQ+lXB2X2o1Ip8fwaJak23UaooQlW502rWXzdlPYKfGzf5e4ABlCVFsvwsVac3bKehvksXYMjgWjPlqiUmuNmOMXCI54NMdVsqWbEmMaGCwF1dQ6sXeSZPhFb1Fc5X399RLVST2re3M43Et9eNucCRrDuvU3pp/H9UnZefDv+alP2kFpvU0dGaacmeM8O1VJDVAbObHtrhGP9nk6FTJhWE06Xzn25oLj0XyM0SYfpy"},{title:"RSAES-OAEP Encryption Example 9.2",message:"gbkGYFAVpjqr5C3fEeGXiRL1QEx0dLJtzj7Ugr+WHsyBi/QgxUZZ",seed:"7LG4sl+lDNqwjlYEKGf0r1gm0Ww=",encrypted:"k6yfBnHsKay7RE7/waV0E1HWD9sOOT+/dUrPDeSXYaFIQd93cum8gnc5ZqFYTE1yuuoAEY+D81zKblN8vU2BH1WDspeD2KbZTNMb5w1vUmwQ/wnG+nzgaXlaP80FEf1fy1ZLzIDqnHjzi4ABJTnYpN32/oHpzdt/UNu7vMfl2GCXzPTsSRifuL8xi+bVoHFdUWtJrxkSWM0y3IM85utGc8A6Gbus6IzFSJX2NswMHsiQltEc4jWiZcoXZCMqaJro"},{title:"RSAES-OAEP Encryption Example 9.3",message:"/TJkKd+biQ4JtUsYuPNPHiQ=",seed:"6JuwMsbOYiy9tTvJRmAU6nf3d8A=",encrypted:"gevdlQVLDIIu+a12k/Woet+0tMTOcN8t+E7UnATaWLpfwgoZ4abot6OQCyJ5bcToae5rQnktFajs61bAnGmRToE86o9pMeS47W9CGvKY1ZXJf0eJx8qmEsfvNgmEwhuT7cVAEGi1r0x4qHcbmE1TuOqK3y9qfUoLp2x14d2fZY8g3tSkYHHUbXeRtWgD2P6n8LD45Brj8JODpvlYX+d1Pqr/0r+UVjEIvuzCB7u1NfX8xwXw3en3CMYvSanJA3HT"},{title:"RSAES-OAEP Encryption Example 9.4",message:"8UWbXwyS8BoPcjouVmJITY+MCiD8KdrWrNQ7tfPv/fThtj4H/f5mKNDXTKGb8taeSgq/htKTklp5Z3L4CI4=",seed:"YG87mcC5zNdx6qKeoOTIhPMYnMw=",encrypted:"vMNflM3mbLETZiXWJblEMqNbIvPS+hGmE/8PylvVf4e5AszcHNCuvLBxXuhp0dH+OV9nkwA/XspGUFnIhmDURv9fCBhVICJVfjjAimfq2ZEmIlTxBoKXXsVjl3aFN/SXevbV9qrOt/sl3sWTcjAjH9iXivSRGaKfKeQkq4JytHVieS1clPd0uIKdCw2fGoye3fN1dNX6JI7vqcUnH8XsJXnIG91htBD6Yf425CQiHBE63bJ1ZkyAHTTKjGNR5KhY"},{title:"RSAES-OAEP Encryption Example 9.5",message:"U+boxynW+cMZ3TF+dLDbjkzMol88gwV0bhN6xjpj7zc557WVq7lujVXlT3vUGrQzN4/7kR0=",seed:"/LxCFALp7KvGCCr6QLpfJlIshA4=",encrypted:"Iyr7ySf6CML2onuH1KXLCcB9wm+uc9c6kFWIOfT9ZtKBuH7HNLziN7oWZpjtgpEGp95pQs1s3OeP7Y0uTYFCjmZJDQNiZM75KvlB0+NQVf45geFNKcu5pPZ0cwY7rseaEXn1oXycGDLyg4/X1eWbuWWdVtzooBnt7xuzrMxpfMbMenePYKBkx/b11SnGIQJi4APeWD6B4xZ7iZcfuMDhXUT//vibU9jWTdeX0Vm1bSsI6lMH6hLCQb1Y1O4nih8u"},{title:"RSAES-OAEP Encryption Example 9.6",message:"trKOohmNDBAIvGQ=",seed:"I6reDh4Iu5uaeNIwKlL5whsuG6I=",encrypted:"Q4zH3AimjaJJ5CUF+Fc7pg4sJ3PVspD0z53/cY6EIIHDg+ZwJKDylZTqmHudJeS3OPKFlw0ZWrs6jIBU49eda5yagye6WW8SWeJxJmdHZpB9jVgv86hHYVSSmtsebRI1ssy07I9mO6nMZwqSvr2FPI2/acZDbQFvYa3YNulHMkUENCB/n9TEPewqEqlY76Ae/iZpiZteYEwlXFX7cWbeVYnjaVl7sJFowG3V2xd+BqF0DrLVyC+uym2S/O6ZMbqf"}],f(a,l,"sha1",p),e="rkXtVgHOxrjMBfgDk1xnTdvg11xMCf15UfxrDK7DE6jfOZcMUYv/ul7Wjz8NfyKkAp1BPxrgfk6+nkF3ziPn9UBLVp5O4b3PPB+wPvETgC1PhV65tRNLWnyAha3K5vovoUF+w3Y74XGwxit2Dt4jwSrZK5gIhMZB9aj6wmva1KAzgaIv4bdUiFCUyCUG1AGaU1ooav6ycbubpZLeGNz2AMKu6uVuAvfPefwUzzvcfNhP67v5UMqQMEsiGaeqBjrvosPBmA5WDNZK/neVhbYQdle5V4V+/eYBCYirfeQX/IjY84TE5ucsP5Q+DDHAxKXMNvh52KOsnX1Zhg6q2muDuw==",t="AQAB",n="BWsEIW/l81SsdyUKS2sMhSWoXFmwvYDFZFCiLV9DjllqMzqodeKR3UP0jLiLnV/A1Jn5/NHDl/mvwHDNnjmMjRnmHbfHQQprJnXfv100W4BNIBrdUC1c4t/LCRzpmXu+vlcwbzg+TViBA/A29+hdGTTRUqMj5KjbRR1vSlsbDxAswVDgL+7iuI3qStTBusyyTYQHLRTh0kpncfdAjuMFZPuG1Dk6NLzwt4hQHRkzA/E6IoSwAfD2Ser3kyjUrFxDCrRBSSCpRg7Rt7xA7GU+h20Jq8UJrkW1JRkBFqDCYQGEgphQnBw786SD5ydAVOFelwdQNumJ9gkygHtSV3UeeQ==",r="7PWuzR5VFf/6y9daKBbG6/SQGM37RjjhhdZqc5a2+AkPgBjH/ZXMNLhX3BfwzGUWuxNGq01YLK2te0EDNSOHtwM40IQEfJ2VObZJYgSz3W6kQkmSB77AH5ZCh/9jNsOYRlgzaEb1bkaGGIHBAjPSF2vxWl6W3ceAvIaKp30852k=",i="vEbEZPxqxMp4Ow6wijyEG3cvfpsvKLq9WIroheGgxh5IWKD7JawpmZDzW+hRZMJZuhF1zdcZJwcTUYSZK2wpt0bdDSyr4UKDX30UjMFhUktKCZRtSLgoRz8c52tstohsNFwD4F9B1RtcOpCj8kBzx9dKT+JdnPIcdZYPP8OGMYM=",s="xzVkVx0A+xXQij3plXpQkV1xJulELaz0K8guhi5Wc/9qAI7U0uN0YX34nxehYLQ7f9qctra3QhhgmBX31FyiY8FZqjLSctEn+vS8jKLXc3jorrGbCtfaPLPeCucxSYD2K21LCoddHfA8G645zNgz72zX4tlSi/CE0flp55Tp9sE=",o="Jlizf235wQML4dtoEX+p2H456itpO35tOi9wlHQT7sYULhj7jfy2rFRdfIagrUj4RXFw8O+ya8SBJsU+/R0WkgGY3CoRB9woLbaoDNMGI2C6P6E/cOQxL/GmzWuPxM2cXD2xfG1qVyEvc64p9hkye61ZsVOFhYW6Tii2CmKkXkk=",u="bzhSazklCFU07z5BWoNu3ouGFYosfL/sywvYNDBP7Gg7qNT0ecQz1DQW5jJpYjzqEAd22Fr/QB0//2EO5lQRzjsTY9Y6lwnu3kJkfOpWFJPVRXCoecGGgs2XcQuWIF7DERfXO182Ij+t1ui6kN18DuYdROFjJR4gx/ZuswURfLg=",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 10.1",message:"i7pr+CpsD4bV8XVul5VocLCJU7BrTrIFvBaU7g==",seed:"R+GrcRn+5WyV7l6q2G9A0KpjvTM=",encrypted:"U+pdwIzSYPs7hYVnKH+pFVLDCy/r+6IT8K6HcC0GjRm6sH/ldFI9+0ITnWjDxa/u4L/ky3lpy/OCuATW5hOWFE4tDmB0H4mTwwFLWLmxlXqLq80jr4VPTDVvsWYqpyv8x+WGVZ3EKA0WDBJnhacj6+6+/3HxFZRECq74fRB5Ood0ojnUoEyH/hRnudr4UgjsbHJVeUqWzCkUL5qL1Bjjwf1nNEsM0IKd87K+xgJTGWKTxrNNP3XTLyE91Fxic9UFrfTM7RBXy3WPwmru+kQSVe1OZMGZ7gdefxZkYYL9tGRzm2irXa/w5j6VUgFoJPBUv008jJCpe7a2VTKE60KfzA=="},{title:"RSAES-OAEP Encryption Example 10.2",message:"5q0YHwU7WKkE8kV1EDc+Vw==",seed:"bRf1tMH/rDUdGVv3sJ0J8JpAec8=",encrypted:"orGkMKnWV+L6HCu17UP/slwFowj+kJPAEDF5X1h0QAEQgorlj7m1gc6d3dPlSa4EoJhUWb3mxiZZTnsF3EJ4sqFGXBNoQIgjyF6W3GbDowmDxjlmT8RWmjf+IeWhlbV3bu0t+NjTYa9obnUCKbvWY/FhhopQYV4MM3vsDKNf7AuxnDbrLgu8wFgvodk6rNsGEGP1nyzh7kNgXl2J7KGD0qzf6fgQEQIq07Q6PdQX2slLThHqgbGSlm6WaxgggucZZGB7T4AC82KZhEoR8q4PrqwurnD49PmAiKzc0KxVbp/MxRFSGQj60m8ExkIBRQMFd4dYsFOL+LW7FEqCjmKXlQ=="},{title:"RSAES-OAEP Encryption Example 10.3",message:"UQos9g6Gb6I0BVPJTqOfvCVjEeg+lEVLQSQ=",seed:"OFOHUU3szHx0DdjN+druSaHL/VQ=",encrypted:"mIbD5nZKi5qE6EFI69jDsaqAUDgaePZocUwW2c/Spu3FaXnFNdne47RLhcGL6JKJkjcXEUciFtld2pjS7oNHybFN/9/4SqSNJawG99fmU5islnsc6Qkl9n3OBJt/gS2wdCmXp01E/oHb4Oej/q8uXECviI1VDdu+O8IGV6KVQ/j8KRO5vRphsqsiVuxAm719wNF3F+olxD9C7Sffhzi/SvxnZv96/whZVV7ig5IPTIpjxKc0DLr93DOezbSwUVAC+WyTK1t5Fnr2mcCtP8z98PROhacCYr8uGP40uFBYmXXoZ/+WnUjqvyEicVRs3AWmnstSblKHDINvMHvXmHgO3g=="},{title:"RSAES-OAEP Encryption Example 10.4",message:"vN0ZDaO30wDfmgbiLKrip18QyR/2Z7fBa96LUwZKJkmpQEXJ",seed:"XKymoPdkFhqWhPhdkrbg7zfKi2U=",encrypted:"Yxjp+1wNBeUwfhaDQ26QMpOsRkI1iqoiPXFjATq6h+Lf2o5gxoYOKaHpJoYWPqC5F18ynKOxMaHt06d3Wai5e61qT49DlvKM9vOcpYES5IFg1uID2qWFbzrKX/7Vd69JlAjj39Iz4+YE2+NKnEyQgt5lUnysYzHSncgOBQig+nEi5/Mp9sylz6NNTR2kF4BUV+AIvsVJ5Hj/nhKnY8R30Vu7ePW2m9V4MPwsTtaG15vHKpXYX4gTTGsK/laozPvIVYKLszm9F5Cc8dcN4zNa4HA5CT5gbWVTZd5lULhyzW3h1EDuAxthlF9imtijU7DUCTnpajxFDSqNXu6fZ4CTyA=="},{title:"RSAES-OAEP Encryption Example 10.5",message:"p91sfcJLRvndXx6RraTDs9+UfodyMqk=",seed:"lbyp44WYlLPdhp+n7NW7xkAb8+Q=",encrypted:"dSkIcsz9SkUFZg1lH1babaoJyhMB2JBjL2qZLz1WXO5GSv3tQO07W+k1ZxTqWqdlX0oTZsLxfHKPbyxaXR+OKEKbxOb48s/42o3A4KmAjkX9CeovpAyyts5v//XA4VnRG2jZCoX3uE4QOwnmgmZkgMZXUFwJKSWUaKMUeG106rExVzzyNL9X232eZsxnSBkuAC3A3uqTBYXwgx/c2bwz1R957S/8Frz01ZgS/OvKo/kGmw5EVobWRMJcz2O0Vu5fpv/pbxnN91H+2erzWVd1Tb9L/qUhaqGETcUHyy0IDnIuuhUDCMK1/xGTYg8XZuz0SBuvuUO9KSh38hNspJSroA=="},{title:"RSAES-OAEP Encryption Example 10.6",message:"6vGnOhsMRglTfeac2SKLvPuajKjGw++vBW/kp/RjTtALfDnsaSLXuOosBOus",seed:"n0fd9C6X7qhWqb28cU6zrCL26zI=",encrypted:"LSB6c0Mqj7TAMFGz9zsophdkCY36NMR6IJlfgRWqaBZnm1V+gtvuWEkIxuaXgtfes029Za8GPVf8p2pf0GlJL9YGjZmE0gk1BWWmLlx38jA4wSyxDGY0cJtUfEb2tKcJvYXKEi10Rl75d2LCl2Pgbbx6nnOMeL/KAQLcXnnWW5c/KCQMqrLhYaeLV9JiRX7YGV1T48eunaAhiDxtt8JK/dIyLqyXKtPDVMX87x4UbDoCkPtnrfAHBm4AQo0s7BjOWPkyhpje/vSy617HaRj94cGYy7OLevxnYmqa7+xDIr/ZDSVjSByaIh94yCcsgtG2KrkU4cafavbvMMpSYNtKRg=="}],f(a,l,"sha1",p)}function o(){var e,t,n,r,i,s,o,u,a,l,p;e="qLOyhK+OtQs4cDSoYPFGxJGfMYdjzWxVmMiuSBGh4KvEx+CwgtaTpef87Wdc9GaFEncsDLxkp0LGxjD1M8jMcvYq6DPEC/JYQumEu3i9v5fAEH1VvbZi9cTg+rmEXLUUjvc5LdOq/5OuHmtme7PUJHYW1PW6ENTP0ibeiNOfFvs=",t="AQAB",n="UzOc/befyEZqZVxzFqyoXFX9j23YmP2vEZUX709S6P2OJY35P+4YD6DkqylpPNg7FSpVPUrE0YEri5+lrw5/Vf5zBN9BVwkm8zEfFcTWWnMsSDEW7j09LQrzVJrZv3y/t4rYhPhNW+sEck3HNpsx3vN9DPU56c/N095lNynq1dE=",r="0yc35yZ//hNBstXA0VCoG1hvsxMr7S+NUmKGSpy58wrzi+RIWY1BOhcu+4AsIazxwRxSDC8mpHHcrSEurHyjnQ==",i="zIhT0dVNpjD6wAT0cfKBx7iYLYIkpJDtvrM9Pj1cyTxHZXA9HdeRZC8fEWoN2FK+JBmyr3K/6aAw6GCwKItddw==",s="DhK/FxjpzvVZm6HDiC/oBGqQh07vzo8szCDk8nQfsKM6OEiuyckwX77L0tdoGZZ9RnGsxkMeQDeWjbN4eOaVwQ==",o="lSl7D5Wi+mfQBwfWCd/U/AXIna/C721upVvsdx6jM3NNklHnkILs2oZu/vE8RZ4aYxOGt+NUyJn18RLKhdcVgw==",u="T0VsUCSTvcDtKrdWo6btTWc1Kml9QhbpMhKxJ6Y9VBHOb6mNXb79cyY+NygUJ0OBgWbtfdY2h90qjKHS9PvY4Q==",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 1.1",message:"ZigZThIHPbA7qUzanvlTI5fVDbp5uYcASv7+NA==",seed:"GLd26iEGnWl3ajPpa61I4d2gpe8Yt3bqIQadaXdqM+k=",encrypted:"W1QN+A1CKWotV6aZW7NYnUy7SmZd34SiX0jiPiLj9+8sZW6O/L7793+IFFSO3VKbPWhrjJPyR3ZmZ+yHDCzTDkRth+s5FN3nuFtlD3XQmmh0+x60PvAUiXJnAMcwxV96wHKjsUNPSnE1fsrCPBpIO5ZRaJ1pIF6R25IeuMwDujo="},{title:"RSAES-OAEP Encryption Example 1.2",message:"dQxAR/VH6OQUEYVlIymKybriRe+vE5f75W+d1Q==",seed:"DMdCzkqbfzL5UbyyUe/ZJf5P418Mx0LOSpt/MvlRvLI=",encrypted:"jsKSyOW1BkucnZpnt9fS72P/lamWQqexXEDPVs8uzGlFj24Rj+cqGYVlt7i9nTmOGj2YrvM8swUTJQCYIF+QBiKbkcA7WBTBXfiUlkHvpWQD0bLwOkp1CmwfpF4sq2gTsCuSaGzZAc50ZAIOvpldizU7uOCwNNGOlERcFkvhfEE="},{title:"RSAES-OAEP Encryption Example 1.3",message:"2Urggy5kRc5CMxywbVMagrHbS6rTD3RtyRbfJNTjwkUf/1mmQj6w4dAtT+ZGz2md/YGMbpewUQ==",seed:"JRTfRpV1WmeyiOr0kFw27sZv0v0lFN9GlXVaZ7KI6vQ=",encrypted:"LcQ1BhOH4Vs0XX8/QJ6q/L0vSs9BUXfA20lQ6mwAt/gvUaUOvKJWBujoxt1QgpRnU6WuH7cSCFWXuKNnrhofpFF3CBTLIUbHZFoou0A4Roi4vFGFvYYu96Boy+oWivwB9/BKs1QMQeHADgNwUgqVD15+q27yHdfIH7kGp+DiGas="},{title:"RSAES-OAEP Encryption Example 1.4",message:"UuZQ2Y5/KgSLT4aFIVO5fgHdMW80ahn2eoU=",seed:"xENaPhoYpotoIENikKN877hds/vEQ1o+Ghimi2ggQ2I=",encrypted:"ZMkqw9CM3SuY2zPBr8/9QbgXaVon4O4AKIufl3i7RVPD07fiTOnXF0aSWKUcdXNhE6ZcXc0Ha97/S5aw6mQKYfbmjaSq/H45s2nfZYTNIa74OgsV1DTDDLSF6/3J2UKhsG0LGIFaV9cNjfucDA5KbfQbzTq8u/+WN06J6nbInrI="},{title:"RSAES-OAEP Encryption Example 1.5",message:"jaif2eX5dKKf7/tGK0kYD2z56AI=",seed:"sxjELfO+D4P+qCP1p7R+1eQlo7WzGMQt874Pg/6oI/U=",encrypted:"NzKEr8KhWRbX/VHniUE8ap0HzdDEWOyfl7dfNHXjL4h/320dmK633rGUvlA7sE4z9yuMj/xF++9ZeBzN6oSPLhVJV/aivUfcC8J99lwwp49W7phnvkUA4WUSmUeX+XRhwj8cR27mf5lu/6kKKbgasdt4BHqXcc5jOZICnld6vdE="},{title:"RSAES-OAEP Encryption Example 1.6",message:"JlIQUIRCcQ==",seed:"5OwJgsIzbzpnf2o1YXTrDOiHq8Lk7AmCwjNvOmd/ajU=",encrypted:"nfQEzsDY2gS9UYXF85t+u0Tm7HrOmmf+LqxCD+6N4XD36NoQ96PE9Squ83PvxKy8Bj8Q0N2L8E5Z5/9AWxLPCBqOkqkqIqO7ZDQMmpHml3H1yz82rpAzAQi6acZDSFQAW8NKhg4nEEwfwKdaGQcI0JZm6FrTQUuXskOqFUT0NJc="}],f(a,l,"sha256",p),e="AZR8f86QQl9HJ55whR8l1eYjFv6KHfGTcePmKOJgVD5JAe9ggfaMC4FBGQ0q6Nq6fRJQ7G22NulE7Dcih3x8HQpn8UsWlMXwN5RRpD5Joy3eg2cLc9qRocmbwjtDamAFXGEPC6+ZwaB5VluVo/FSZjLR1Npg8g7aJeZTxPACdm9F",t="AQAB",n="CCPyD6212okIip0AiT4h+kobEfvJPGSjvguq6pf7O5PD/3E3BMGcljwdEHqumQVHOfeeAuGG3ob4em3e/qbYzNHTyBpHv6clW+IGAaSksvCKFnteJ51xWxtFW91+qyRZQdl2i5rO+zzNpZUto87nJSW0UBZjqO4VyemS2SRi/jk=",r="AVnb3gSjPvBvtgi4CxkPTT4ivME6yOSggQM6v6QW7bCzOKoItXMJ6lpSQOfcblQ3jGlBTDHZfdsfQG2zdpzEGkM=",i="AStlLzBAOzi0CZX9b/QaGsyK2nA3Mja3IC05su4wz7RtsJUR9vMHzGHMIWBsGKdbimL4It8DG6DfDa/VUG9Wi9c=",s="Q271CN5zZRnC2kxYDZjILLdFKj+1763Ducd4mhvGWE95Wt270yQ5x0aGVS7LbCwwek069/U57sFXJIx7MfGiVQ==",o="ASsVqJ89+ys5Bz5z8CvdDBp7N53UNfBc3eLv+eRilIt87GLukFDV4IFuB4WoVrSRCNy3XzaDh00cpjKaGQEwZv8=",u="AnDbF9WRSwGNdhGLJDiac1Dsg2sAY6IXISNv2O222JtR5+64e2EbcTLLfqc1bCMVHB53UVB8eG2e4XlBcKjI6A==",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 2.1",message:"j/AMqmBccCgwY02abD1CxlK1jPHZL+xXC+7n",seed:"jEB7XsKJnlCZxT6M55O/lOcbF4KMQHtewomeUJnFPow=",encrypted:"AR3o2JwhHLKUfOLZ26KXD9INUK1/fWJzdZix7E545qladDYdpHRaE5zBP9nf6IPmZvBUPq75n1E4suxm+Bom7crf9be1HXCFZnmR/wo92CKg4D1zRlBwr/3Gitr3h9rU6N+tid2x9yOYj955rf3Bq4j6wmjYQpWphbhBIBMoliyJ"},{title:"RSAES-OAEP Encryption Example 2.2",message:"LQ==",seed:"tgDPPC5QbX8Wd4yRDTqLAD7uYdW2AM88LlBtfxZ3jJE=",encrypted:"AIeYuAD2aYZYnEu1YK+INur95FfP2pTz8/k4r3xwL4bVMufgvzWFLdVK24fP96jTteLkrX6HjmebBVeUhSWG3ahebh3LH5yVS9yx+xHzM1Jxc8X1rS+kYgdCGWFbszMF/vP0ogisy5XthHqcoHNEM4Rzln7ugrXuS+dNuuPEjIAf"},{title:"RSAES-OAEP Encryption Example 2.3",message:"dPyIxRvJD3evnV6aSnATPUtOCzTaPDfH744=",seed:"pzdoruqpH52MHtb50rY0Z/B8yuOnN2iu6qkfnYwe1vk=",encrypted:"AMkW9IJHAFs0JbfwRZhrRITtj1bQVDLcjFCwYxHMDBlSHIqpDzSAL8aMxiUq41Feo9S2O/1ZTXIiK8baJpWs9y+BPqgi1lABB6JJIvU2QZYMzWK0XgjkWk12g6HSPFhuK4yf+LQ1UYpbKVquUdZ9POOCR8S7yS+tdful6qP8Wpkm"},{title:"RSAES-OAEP Encryption Example 2.4",message:"p+sqUDaTHSfU6JEybZlpL/rdqb9+/T405iLErcCF9yHf6IUHLHiiA7FRc5vlQPqMFToQ8Ao=",seed:"mns7DnCL2W+BkOyrT7mys4BagVaaezsOcIvZb4GQ7Ks=",encrypted:"AJ6YQ3DNjd7YXZzjHASKxPmwFbHKwoEpof+P+Li3+o6Xa95C21XyWZF0iCXc5USp5jwLt66T6G3aYQkEpoyFGvSPA3NV6tOUabopdmslYCkOwuOIsFLiuzkJc4Hu6nWXeJtTVtHn7FmzQgzQOMjuty1YConfe78YuQvyE3IAKkr2"},{title:"RSAES-OAEP Encryption Example 2.5",message:"LvKwZvhUwz873LtZlKQ15z1sbA==",seed:"6zzrvErcFrtI6IyK7A40r39Cf9PrPOu8StwWu0jojIo=",encrypted:"AMv457W0EOt8RH+LAEoMQ7dKjZamzOdwTHJepDkaGGoQHi2z8coCiVemL5XYZ+ctjPBdw3y3nlMn1sif9i3WCzY26ram8PL5eVYk7Bm3XBjv9wuhw1RZmLFzKfJS+3vi+RTFhwjyyeaJrc07f5E7Cu7CVWNh3Oe3lvSF3TB2HUI8"},{title:"RSAES-OAEP Encryption Example 2.6",message:"in+zRMi2yyzy7x9kP5oyGPbhm7qJwA==",seed:"TEXPTVfJjj1tIJWtxRxInrUN/4RMRc9NV8mOPW0gla0=",encrypted:"AJ5iMVr3Q6ZZlqLj/x8wWewQBcUMnRoaS2lrejzqRk12Bw120fXolT6pgo20OtM6/ZpZSN7vCpmPOYgCf93MOqKpN1pqumUH33+iP1a+tos5351SidwwNb2hLy3JfhkapvjB+c9JvbIolIgr+xeWhWPmMDam/Du/y+EsBOdZrbYc"}],f(a,l,"sha256",p),e="ArWP7AOahgcApNe2Ri+T5s3UkRYd3XT06BC0DjwWUgBqXCd7J3TBEwWky6taeO+lfheobfej+jb8Sx0iSfIux8LdakYyMqzOqQbWbr6AtXBLEHKdpvgzI0q7Xv3UopLL+tM7TTP6ehS4w5e1bjrNISA0KLd836M6bacGs9iw/EPp",t="AQAB",n="FbSKW1aDqUZw4jtXGPgU+g4T+FA49QcRGCy6YVEFgfPSLH4jLvk34i5VHWi4bi+MsarYvi5Ij13379J54/Vo1Orzb4DPcUGs5g/MkRP7bEqEH9ULvHxRL/y+/yFIeqgR6zyoxiAFNGqG3oa/odipSP0/NIwi6q3zM8PObOEyCP0=",r="Ab8B0hbXNZXPAnDCvreNQKDYRH0x2pGamD9+6ngbd9hf43Gz6Tc+e2khfTFQoC2JWN5/rZ1VUWCVi0RUEn4Ofq8=",i="AY0zmWWBZts4KYFteylUFnWenJGYf1stiuzWOwS0i9ey/PIpu3+KbciLoT3S45rVW20aBhYHCPlwC+gLj9N0TOc=",s="BsCiSdIKby7nXIi0lNU/aq6ZqkJ8iMKLFjp2lEXl85DPQMJ0/W6mMppc58fOA6IVg5buKnhFeG4J4ohalyjk5Q==",o="0dJ8Kf7dkthsNI7dDMv6wU90bgUc4dGBHfNdYfLuHJfUvygEgC9kJxh7qOkKivRCQ7QHmwNEXmAuKfpRk+ZP6Q==",u="jLL3Vr2JQbHTt3DlrTHuNzsorNpp/5tvQP5Xi58a+4WDb5Yn03rP9zwneeY0uyYBHCyPfzNhriqepl7WieNjmg==",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 3.1",message:"CHggtWno+o0=",seed:"jO1rGWKQgFeQ6QkHQBXmogsMSJSM7WsZYpCAV5DpCQc=",encrypted:"AJqBCgTJGSHjv2OR0lObiDY2gZmWdutHfVeadCdFr2W4mS3ZHwet283wbtY/bsM8w0rVxNAPh3NZNrcRt56NhoT0NzD2IK3WNy39Im/CfbicvC6Vq2PyXUh1iza+90PUM3jECPP5NsOx658MzEnYyFZFb9izZIna6YLsXwkWoHVO"},{title:"RSAES-OAEP Encryption Example 3.2",message:"RlOsrxcZYLAfUqe+Y6OrIdw2jsQ7UNguw3geBA==",seed:"tCkdZWdVCEjMFWlnyAm6q2ylB/C0KR1lZ1UISMwVaWc=",encrypted:"ARCj8j/hSsscyXtuINlyU0HuC+d7wZc7bSekF60BJFWKeKa1p28d4KsJXmdqI22sxha7PgkI9bgpfgdBd8KHp12g5y68uXiwRyPOvv8s6YDKmJFhbW13LHbE3iZHch2YG1eHi/20M/IrsAqCuk/W5Q/dP5eSVM1hLT9LBVsX3rIH"},{title:"RSAES-OAEP Encryption Example 3.3",message:"2UzQ4I+kBO2J",seed:"zoko9gWVWCVACLrdl5T63NL9H2XOiSj2BZVYJUAIut0=",encrypted:"Anfa/o/QML7UxLCHcSUWFPUWhcp955u97b5wLqXuLnWqoeQ3POhwasFh3/ow2lkzjjIdU47jkYJEk6A0dNgYiBuDg57/KN5yS2Px/QOSV+2nYEzPgSUHGyZacrHVkj/ZVyZ+ni7Iyf/QkNTfvPGxqmZtX6cq095jgdG1ELgYsTdr"},{title:"RSAES-OAEP Encryption Example 3.4",message:"bMZBtrYeb5Y5dNrSOpATKE7x",seed:"bil59S1oFKV9g7CQBUiI8RmluaNuKXn1LWgUpX2DsJA=",encrypted:"AalUnNYX91mP0FrqphpfhU22832WgnjDNRU1pkpSrd5eD7t7Q1YhYE+pKds6glA8i1AE/li216hJs2IbCJMddyaXrDzT8V9/UfIUaSkLfcRYBrTn9DEDOTjY1Xnn38poLOFykpZbAz5hdbOh0qG39qFgl5QZG0+aTBd1tmlMZBfO"},{title:"RSAES-OAEP Encryption Example 3.5",message:"31FRgyth9PJYkftBcvMo0u3fg3H/z9vpl5OSlfMOymkYAXz9oRU796avh1kyIw==",seed:"LXYL/jjFneNM3IuMeKOOZihKLSctdgv+OMWd40zci4w=",encrypted:"AGgQQYTuy9dW6e3SwV5UFYbEtqQD7TDtxcrMYOmYlTPgTwIFpo4GbQbtgD9BMFAW7a1lIzLxKEld49jH6m95Xgtq/BAVFl/gXin5MMbiZfRTOl38miBTg5a6IS9w6tcrWIBeY5Z5n4iCuUqF9r/m9TqvxWF0aMP2VGVKZn+LHMVj"},{title:"RSAES-OAEP Encryption Example 3.6",message:"PDutiTxUSm1SCrAiMZGIyNUEt6eIuFCQO4WXLqoYVS4RNKetYJiCYlT/erZys9jrMVj6xtTLrvE=",seed:"8XR3nF/Tz+AHuty3o2ybVb/Pvw7xdHecX9PP4Ae63Lc=",encrypted:"Aps8BQrRkPPwpNIjHw3NBznsDvp1hIHmlbG5wRERr9+Ar4ervO2GA/MMUVNijdZEtFnCGjbLwpM6RKzCk96jJX1bIgzq7hnmIzwKmq2Ue4qqO29rQL39jpCS87BBo/YKMbkYsPc2yYSDMBMOe9VDG63pvDgFGrlk/3Yfz1km3+/Y"}],f(a,l,"sha256",p),e="BRJAtswABPpI0BNGccB4x8jew7Pi8lvCVkRnM52ziFPQa4XupbLeNTv/QqwuRryX+uaslhjalTelyPVTweNXYlmR1hCNzXiF+zolQT9T78rZSMs1zZua6cHGdibRE9V93kxb6na7W7felsANBzculoWm11z50jn6FI1wkxtfP7A5",t="AQAB",n="BBH/yjt8penpvn/jioUQXjU4ltsFxXlq7NKnJRYes2UchimpuGK5BNewx7N/jLWhwrVAAQGKAKHrLK/k7k6UksNIvCvtq0ueu/Bk6O/zIrkAn47sZTkF9A34ijzcSdRWf3VifUGspiQSm0agt8aY5eZfK3uhAsdJoQE1tlQNBAE=",r="AnRYwZ7BY2kZ5zbJryXWCaUbj1YdGca/aUPdHuGriko/IyEAvUC4jezGuiNVSLbveSoRyd6CPQp5IscJW266VwE=",i="AhDumzOrYXFuJ9JRvUZfSzWhojLi2gCQHClL8iNQzkkNCZ9kK1N1YS22O6HyA4ZJK/BNNLPCK865CdE0QbU7UTk=",s="OfoCi4JuiMESG3UKiyQvqaNcW2a9/R+mN9PMSKhKT0V6GU53J+Sfe8xuWlpBJlf8RwxzIuvDdBbvRYwweowJAQ==",o="AV2ZqEGVlDl5+p4b4sPBtp9DL0b9A+R9W++7v9ax0Tcdg++zMKPgIJQrL+0RXl0CviT9kskBnRzs1t1M8eVMyJk=",u="AfC3AVFws/XkIiO6MDAcQabYfLtw4wy308Z9JUc9sfbL8D4/kSbj6XloJ5qGWywrQmUkz8UqaD0x7TDrmEvkEro=",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 4.1",message:"SoZglTTuQ0psvKP36WLnbUVeMmTBn2Bfbl/2E3xlxW1/s0TNUryTN089FmyfDG+cUGutGTMJctI=",seed:"HKwZzpk971X5ggP2hSiWyVzMofMcrBnOmT3vVfmCA/Y=",encrypted:"AooWJVOiXRikAgxb8XW7nkDMKIcrCgZNTV0sY352+QatjTq4go6/DtieHvIgUgb/QYBYlOPOZkdiMWXtOFdapIMRFraGeq4mKhEVmSM8G5mpVgc62nVR0jX49AXeuw7kMGxnKTV4whJanPYYQRoOb0L4Mf+8uJ5QdqBE03Ohupsp"},{title:"RSAES-OAEP Encryption Example 4.3",message:"v21C5wFwex0CBrDItFoccmQf8SiJIZqCveqWW155qWsNAWPtnVeOya2iDy+88eo8QInYNBm6gbDGDzYG2pk=",seed:"rZl/7vcw1up75g0NxS5y6sv90nWtmX/u9zDW6nvmDQ0=",encrypted:"AtYYko32Vmzn3ZtrsDQH9Mw/cSQk9pePdwQZJ6my7gYXWYpBdhbEN/fH7LMmvjtHnKLLTDazfF1HT0tTG6E+TY002cy+fMUvdRn0rfmFkNeHeqVOABP2EmI4eXFCBbbIlpshLxbA3vDTzPPZZqwMN+KPG4O11wmS9DcyHYtpsIOU"},{title:"RSAES-OAEP Encryption Example 4.4",message:"+y7xEvXnZuuUAZKXk0eU974vb8HFjg==",seed:"E2RU31cw9zyAen5A2MGjEqxbndMTZFTfVzD3PIB6fkA=",encrypted:"AZX8z/njjTP/ApNNF+BNGUjlczSK/7iKULnZhiAKo4LJ0XaTzTtvL9jacr+OkRxSPUCpQeK36wXdi9qjsl3SO9D7APyzN1nNE5Nu5YstiAfEMVNpdRYGdgpUasEZ4jshBRGXYW28uTMcFWRtzrlol9Lc7IhIkldTXZsR9zg11KFn"},{title:"RSAES-OAEP Encryption Example 4.5",message:"KMzUR7uehRZtq7nlt9GtrcS5058gTpbV5EDOmtkovBwihA==",seed:"vKgFf4JLLqJX8oYUB+72PTMghoG8qAV/gksuolfyhhQ=",encrypted:"A8GIo5X2qOS6MdKjYJg+h3hi2endxxeb3F5A8v+MbC7/8WbBJnzOvKLb6YMukOfAqutJiGGzdPQM9fopdhbRwS/Ovw4ksvmNBVM+Q26CFPqvdhV8P0WxmeYTxGFGrLgma+fwxpe7L6mj300Jq6Y/5kfTEJSXNdKuLRn0JsIg8LSD"},{title:"RSAES-OAEP Encryption Example 4.6",message:"8iJCdR7GsQ==",seed:"Ln4eF/ZHtd3QM+FUcvkPaBLzrE4ufh4X9ke13dAz4VQ=",encrypted:"AM9cnO14EVBadGQYTnkkbm/vYwqmnYvnAutrc4bZR3XC0DNhhuUlzFosUSmaC1LrjKcFfcqZOwzlev5uZycR7tUlLihC6lf4o0khAjUb03Dj+ubNsDKNCOA6vP63N3p6jSVIak7EPu0KtBLuNpIyW5mdkMuwssV7CNLOQ6qG7zxZ"}],f(a,l,"sha256",p),e="Cq3z+cEl5diR8xrESOmT3v5YD4ArRfnX8iulAh6cR1drWh5oAxup205tq+TZah1vPSZyaM/0CABfEY78rbmYiNHCNEZxZrKiuEmgWoicBgrA2gxfrotV8wm6YucDdC+gMm8tELARAhSJ/0l3cBkNiV/Tn1IpPDnv1zppi9q58Q7Z",t="AQAB",n="AlbrTLpwZ/LSvlQNzf9FgqNrfTHRyQmbshS3mEhGaiaPgPWKSawEwONkiTSgIGwEU3wZsjZkOmCCcyFE33X6IXWI95RoK+iRaCdtxybFwMvbhNMbvybQpDr0lXF/fVKKz+40FWH2/zyuBcV4+EcNloL5wNBy+fYGi1bViA9oK+LF",r="A7DTli9tF1Scv8oRKUNI3PDn45+MK8aCTyFktgbWh4YNrh5jI5PP7fUTIoIpBp4vYOSs1+YzpDYGP4I4X0iZNwc=",i="AuTDLi9Rcmm3ByMJ8AwOMTZffOKLI2uCkS3yOavzlXLPDtYEsCmC5TVkxS1qBTl95cBSov3cFB73GJg2NGrrMx8=",s="AehLEZ0lFh+mewAlalvZtkXSsjLssFsBUYACmohiKtw/CbOurN5hYat83iLCrSbneX31TgcsvTsmc4ALPkM429U=",o="65CqGkATW0zqBxl87ciBm+Hny/8lR2YhFvRlpKn0h6sS87pP7xOCImWmUpfZi3ve2TcuP/6Bo4s+lgD+0FV1Tw==",u="AS9/gTj5QEBi64WkKSRSCzj1u4hqAZb0i7jc6mD9kswCfxjngVijSlxdX4YKD2wEBxp9ATEsBlBi8etIt50cg8s=",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 5.1",message:"r3GpAeOmHTEy8Pwf20dPnqZXklf/wk0WQXAUWz296A==",seed:"RMkuKD93uUmcYD2WNmDIfS+TlGFEyS4oP3e5SZxgPZY=",encrypted:"BOGyBDRo+2G7OC79yDEzJwwLMPuhIduDVaaBdb5svHj/ZAkVlyGVnH0j+ECliT42Nhvp4kZts+9cJ0W+ui7Q9KXbjmX033MpxrvSV1Ik//kHhX6xTn51UGpaOTiqofjM3QTTi9DVzRtAarWd/c8oAldrGok1vs+tJEDbcA5KvZz7"},{title:"RSAES-OAEP Encryption Example 5.2",message:"o7hEoII5qKxBYFrxemz9pNNQE2WFkDpBenkmh2BRmktKwzA+xz8Ph8+zI5k=",seed:"yyj1hgZZ/O7knD7q/OYlpwgDvTLLKPWGBln87uScPuo=",encrypted:"AeleoSbRCOqBTpyWGLCrZ2G3mfjCYzMvupBy+q+sOKAfBzaLCjCehEUmIZFhe+CvtmVvyKGFqOwHUWgvks9vFU7Gfi580aZm7d4FtaGGVHBO6Q32/6IS7a+KSk7L6rPWwBTI+kyxb5SW12HTEowheKkFTda06tU0l4Ji45xP2tyh"},{title:"RSAES-OAEP Encryption Example 5.3",message:"MIsOy9LHbLd/xvcMXt0jP9LyCSnWKfAmlTu2Ko9KOjFL3hld6FtfgW2iqrB00my2rN3zI647nGeKw88S+93n",seed:"IoX0DXcEgvmp76LHLLOsVXFtwMoihfQNdwSC+anvosc=",encrypted:"Ci3TBMV4P0o59ap6Wztb9LQHfJzYSOAaYaiXjk85Q9FYhAREaeS5YXhegKbbphMIS5i1SYJShmwpYu/t8SGHiX/72v6NnRgafDKzttROuF/HJoFkTBKH6C9NKke+mxoDy/YVZ9qYzFY6PwzB4pTDwku9s5Ha4DmRBlFdA/z713a4"},{title:"RSAES-OAEP Encryption Example 5.4",message:"FcW57hGF",seed:"SfpF06eN0Q39V3OZ0esAr37tVRNJ+kXTp43RDf1Xc5k=",encrypted:"AcMQiclY0MMdT9K4kPqZ7JYHTaSolc8B3huHcQ4U5mG11/9XjzWjTLha8Liy0w909aaPbaB7+ZQTebg7x3F4yeWFRmnAJMaIFGBW/oA952mEaJ+FR2HO0xfRPzCRCaaU7cyOxy0gnR8d9FMunt9fhbffM9TvOfR6YDE5Duz6Jg0W"},{title:"RSAES-OAEP Encryption Example 5.5",message:"IQJuaADH+nKPyqug0ZauKNeirE/9irznlPCYX2DIpnNydzZdP+oR24kjogKa",seed:"8Ch0EyNMxQNHJKCUxFhrh6/xM/zwKHQTI0zFA0ckoJQ=",encrypted:"A/gvyZ/MNHUG3JGcisvAw/h1bhviZsqIsEM5+gBpLfj7d8iq28yZa6z5cnS4cJWywHxyQMgt9BAd37im/f5WcIcB+TZS9uegFSdgaetpYf4ft/1wMlcdc1ReCrTrCKPFHLLczeifyrnJSVvQD84kQY21b4fW9uLbSiGO0Ly94il1"},{title:"RSAES-OAEP Encryption Example 5.6",message:"VB43totsiHK4TAI=",seed:"2fukXJbyHm4m0p6yzctlhb6cs0HZ+6RclvIebibSnrI=",encrypted:"AWmTYpBHOqRBGy1h5mF88hMmBVNLN++kXAqQr4PKszqorigNQZbvwbOdWYNLsXydyvKi55ds8tTvXf4rRBswyuNmbtT0t2FVCTnTjNzA1cMSahInbdKfL/1wib3CjyQmC0TbbIa3kkAdXkiYytSafDxwNyan1OWtJcMLkJ6l8WRm"}],f(a,l,"sha256",p),e="ErF/ba0uzRn/RtwT94YPCeDgz7Z3s4pSWSMFzq8CLBZtuQ0ErCnjP33RLZ+vZuCBa7Y+rSZ8x9RsF8N74hS8oqItcjpk5EQHQ2tvyWVymu/CVU83bNXc6mgpN4CmK/OdAClIWhYLu55dwJctIaUE9S5e4CiqQWMy9RCy6c/19yKv",t="AQAB",n="ApXso1YGGDaVWc7NMDqpz9r8HZ8GlZ33X/75KaqJaWG80ZDcaZftp/WWPnJNB7TcEfMGXlrpfZaDURIoC5CEuxTyoh69ToidQbnEEy7BlW/KuLsv7QV1iEk2Uixf99MyYZBIJOfK3uTguzctJFfPeOK9EoYij/g/EHMc5jyQz/P5",r="BKbOi3NY36ab3PdCYXAFr7U4X186WKJO90oiqMBct8w469TMnZqdeJpizQ9g8MuUHTQjyWku+k/jrf8pDEdJo4s=",i="BATJqAM3H+20xb4588ALAJ5eCKY74eQANc2spQEcxwHPfuvLmfD/4Xz9Ckv3vv0t1TaslG23l/28Sr6PKTSbke0=",s="A5Ycj3YKor1RVMeq/XciWzus0BOa57WUjqMxH8zYb7lcda+nZyhLmy3lWVcvFdjQRMfrg6G+X63yzDd8DYR1KUs=",o="AiGX4GZ0IZaqvAP6L+605wsVy3h9YXrNMbt1x7wjStcG98SNIYLR8P+cIo3PQZZ7bAum0sCtEQobhXgx7CReLLE=",u="BAHEwMU9RdvbXp2W0P7PQnXfCXS8Sgc2tKdMMmkFPvtoas4kBuIsngWN20rlQGJ64v2wgmHo5+S8vJlNqvowXEU=",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 6.1",message:"QEbKi6ozR8on9J4NgfnMHXG+m6UX1A==",seed:"3Q9s/kFeiOWkaaUfu6bf1ArbQ4TdD2z+QV6I5aRppR8=",encrypted:"C3d3hdR81ybq+Wuf6QUfy2KHVuQjrsvVH2HuE2LbJT2o2ZPdrDHIoGphdGj+GWNTrcV/d8iPLJlZ0CR3O2e2b7wLUVPMorv1HidYA8B8eJxkg5FIsPuK836LchnGqQlE7ObiWOjSuIw4lZ/ULCfOYejelr6PJXSxWgQUlV78sbvP"},{title:"RSAES-OAEP Encryption Example 6.2",message:"XMcsYCMd8Ds9QPm1eTG8MRCflyUn8osZ50gMcojLPJKyJRIhTkvmyRR5Ldq99X+qiqc=",seed:"jRS9lGoTURSPXK4u2aDGU+hevYWNFL2UahNRFI9cri4=",encrypted:"DXAHBh/uWFjxl/kIwrzm0MXeHNH5MSmoPc0mjn00UcCUFmOwTQipPmLmephH+rNOOfCQVvwP5wysU3/w2hjmk/rl6Jb4qNc+KqDiij7fKSKhPGTvY3aiXZ2LflnJ3yv4LdT9KvvWsZrHEWfsEG+fQZW4c1OMEpOMC4N44nc88yRm"},{title:"RSAES-OAEP Encryption Example 6.3",message:"sg5lEwMJL0vMtDBwwPhtIwSTYu2WZC/FYywn20pS49gx8qsGiyOxSYecAC9r8/7ul1kRElYs",seed:"bAdbxFUg8WXAv16kxd8ZG8nvDkRsB1vEVSDxZcC/XqQ=",encrypted:"AMe1ZPYbk4lABLKDLhwJMM4AfK46Jyilp/vQ9M921AamJzanoNGdlj6ZEFkbIO68hc/Wp4Qr43iWtjcasgpLw2NS0vroRi91VI5k9BZgXtgNG7Z9FBOtPjM61Um2PWSFpAyfaZS7zoJlfRKciEa+XUKa4VGly4fYSXXAbUJV2YHc"},{title:"RSAES-OAEP Encryption Example 6.4",message:"aE4wOMXAQfc=",seed:"O7w71mN9/hKEaQECm/WwwHEDQ5w7vDvWY33+EoRpAQI=",encrypted:"AJS/vpVJKJuLwnnzENVQChT5MCBa0mLxw/a9nt+6Zj4FL8nucIl7scjXSOkwBDPcyCWr7gqdtjjZ9z6RCQv0HfjmVKI2M6AxI2MYuzwftIQldbhCRqo8AlyK3XKjfcK+Rzvii53W8Xw4Obbsv9OCLnCrrbK8aO3XKgrHPmDthH7x"},{title:"RSAES-OAEP Encryption Example 6.5",message:"MkiMsmLQQdbk3TX5h788ppbbHwasKaRGkw==",seed:"tGtBiT6L7zJvZ1k4OoMHHa5/yry0a0GJPovvMm9nWTg=",encrypted:"CmNUKnNQco5hWHxCISdwN5M7LbL7YJ0u7bfH82LulE32VdATd3vcJmRiFtcczNNudMlHVhl6/ZsDVY1zymLrK2kLIYWeG9Iag3rQ5xhjLAdpMYBBuwjrJ8Oqc4+2qH57bBveynuE5xRpd9p+CkkiRP7x7g4B/iAwrmFxPtrxV/q/"},{title:"RSAES-OAEP Encryption Example 6.6",message:"ULoUvoRicgJ5wwa6",seed:"CiQDMSpB49UvBg+8E6Z95c92CacKJAMxKkHj1S8GD7w=",encrypted:"DpQAu4uQ4zbkpP/f698+a5f3MhAXCi3QTcP7vXmQVlkH0CFlCnDESNG36Jk2ybe3VmzE2deBHBKI9a5cHUzM9Lsa/AoxnbD5qd2fJt9k19dSRtDWZUR/Bn/AdVHwstzsX/vRLe6qOk9Kf01OZcvKrmlWh2IBLs8/6sEJhBWXNAKj"}],f(a,l,"sha256",p),e="MRF58Lz8m508oxXQDvMNe906LPrpkRv+3LlIs6R4LQcytqtEqkvwN0GmRNwBvsPmmwGgM+Z12KzXxJJcaxrsMRkFHf2Jdi0hXUVHX/y1n5CBSGI/NxdxVvauht16fF9D3B4fkIJUBYooSl8GwAIXk6h/GsX+/33K7mnF5Ro3ieNz",t="AQAB",n="Bwz8/y/rgnbidDLEXf7kj0m3kX1lMOHwyjRg8y4CdhdEh8VuIqRdJQDXd1SVIZ19Flqc872Swyr5qY2NycwpaACtyUoKVPtA80KRv4TujqErbxCTWcbTVCpQ+cdn9c//BaaBwuZW+3fKqttL6UaNirzU35j1jobSBT+hNJ90jiGx",r="B0kmLBEc1HDsJWbms3MvwJMpRpqhkHHTucAZBlFMbx0muqFL6rCXHIt+YRpPeQCdb+p3aSjKJShbDeNkPRo/jHE=",i="BrweUOlsAr9jbp7qi4mbvr92Ud533UdMPpvCO62BgrYZBMfZffvr+x4AEIh4tuZ+QVOR1nlCwrK/m0Q1+IsMsCM=",s="A7x+p/CqsUOrxs6LlxGGNqMBcuTP4CyPoN2jt7qvkPgJKYKYVSX0iL38tL1ybiJjmsZKMJKrf/y/HVM0z6ULW/E=",o="AmKmqinCo8Z9xTRsBjga/Zh6o8yTz7/s9U/dn514fX9ZpSPTmJedoTei9jgf6UgB98lNohUY3DTLQIcMRpeZStk=",u="ZJ1MF7buFyHnctA4mlWcPTzflVDUV8RrA3t0ZBsdUhZq+KITyDliBs37pEIvGNb2Hby10hTJcb9IKuuXanNwwg==",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 7.1",message:"R6rpCQ==",seed:"Q90JoH/0ysccqkYy7l4cHa7kzY9D3Qmgf/TKxxyqRjI=",encrypted:"CdXefX8LEW8SqnT1ly7/dvScQdke1rrSIBF4NcFO/G+jg0u7yjsqLLfTa8voI44Ue3W6lVuj5SkVYaP9i7VPmCWA4nFfeleuy23gbHylm4gokcCmzcAm2RLfPQYPnxIb3hoQ2C3wXo/aWoLIMFPYzI19g5uY90XMEchAci3FVC/a"},{title:"RSAES-OAEP Encryption Example 7.2",message:"HZsuIiPZvBO/ufFiznNdtIunxo9oIqChp7auFlg05w==",seed:"Opw87HuE+b063svGc+yZ1UsivJs6nDzse4T5vTrey8Y=",encrypted:"DDar5/aikhAVropPgT3SVzSMRtdS9sEmVBEqg9my/3na0Okz51EcAy436TOZVsM0exezvKYsVbDQhtOM0Mn9r6oyBsqzUR4lx6Gt2rYDYC4X1aMsJSVcQs9pDqeAWfIAmDIIQH/3IN2uJ6u4Xl2+gFCpp8RP0F//Rj2llnEsnRsl"},{title:"RSAES-OAEP Encryption Example 7.3",message:"2Xb8",seed:"dqdeW2FXpVbPiIS7LkXCk91UXPV2p15bYVelVs+IhLs=",encrypted:"GpTkYrRFNyD9Jw1Pc1TSPSfc9Yb8k4Fw1l4kCwqPodlAboKMJe+yuXoGgVeB7Jb7JTQklGpQc1keZUzUUVZO0Q4qYUelFFe5lWM2uhq21VCbvcchrMTP6Wjts05hVgJHklLKF5cOtBGpQC0FlkwCYoXUAk//wejNycM/ZXw+ozkB"},{title:"RSAES-OAEP Encryption Example 7.4",message:"1HOGI98iOqQ4Q9+EZ1NMQdAT4MgDxiTiY2ZrI5veQKXymuuN5549qmHdA3D0m9SwE4NLmCEq72scXuNzs8s=",seed:"eGYxSmrW8rJQo1lB2yj1hktYWFl4ZjFKatbyslCjWUE=",encrypted:"G5GJEPP4ifUCg+3OHEq41DFvaucXwgdSGyuDX6/yQ1+e30d0OIjIFv4JTUXv6Oi8/uADg+EN5Ug+lEyf0RNSS4wfKgRfAaXK6x1U8dh48g/bED27ZCZ+MjhAkUcjMO0h4m3nNfLxAju7nxO2cJzNI9n1TBCMngJBco0zzhOvMZaN"},{title:"RSAES-OAEP Encryption Example 7.5",message:"u0cjHKXqHTrUbJk0XZqKYQ==",seed:"shZu1HLVjbEMqyxrAAzM8Qp9xQmyFm7UctWNsQyrLGs=",encrypted:"HebBc/6i18c2FbG7ibWuxyQgtiN1uhtxyNsXw1Kuz8zo7RkBkt5JZEwucKyXFSwI6drZlK6QaqCRZwPQsdc2wnZlQzbkilVf1TiACqzDdpKX5i+SbCTUsOyGETV3vtxFe7/SatEKseFSLEWkIfZxAFcisIs5hWmLJdqfWQeYuMrK"},{title:"RSAES-OAEP Encryption Example 7.6",message:"IYSCcJXTXD+G9gDo5ZdUATKW",seed:"Umc73iyhZsKqRhMawdyAjWfX07FSZzveLKFmwqpGExo=",encrypted:"DX+W3vsdnJfe63BVUFYgCAG1VmTqG/DbQ4nZgWTUGHhuijUshLtz07dHar21GJ9Ory8QQPX67PgKGnBMp0fJBnqKO3boMOEcc52HEnQxOWIW2h2rgmDtnaYtvK85pddXzhbuXkXg4DDnoMy+4XzgbLfArK12deGB0wruBbQyv3Ar"}],f(a,l,"sha256",p),e="W98OMNMh3aUUf4gkCPppGVSA34+A0/bov1gYUE82QnypsfVUC5xlqPaXTPhEeiRNkoAgG7Sfy75jeNGUTNIn4jD5bj0Q+Bnc7ydsZKALKktnAefQHeX6veOx6aDfgvRjE1nNImaWR/uxcXJGE07XtJfP/73EK1nHOpbtkBZiEt/3",t="AQAB",n="D30enlqqJf0T5KBmOuFE4NFfXNGLzbCd8sx+ZOPF6RWtYmRTBBYdCYxxW7eri9AdB+rz/tfH7QivKopi70SrFrMg4Ur3Kkj5av4mKgrkz2XmNekQeQzU7lzqdopLJjn35vZ3s/C7a+MrdXR9iQkDbwJk9Y1AHNuhMXFhV6dez2Mx",r="CgLvhEjZ+ti70NAEyMKql1HvlyHBsNAyNqVLDflHy67VolXuno4g1JHqFyP+CUcEqXYuiK/RbrtZlEEsqWbcT58=",i="CS02Ln7ToL/Z6f0ObAMBtt8pFZz1DMg7mwz01u6nGmHgArRuCuny3mLSW110UtSYuByaxvxYWT1MP7T11y37sKk=",s="B8cUEK8QOWLbNnQE43roULqk6cKd2SFFgVKUpnx9HG3tJjqgMKm2M65QMD4UA10a8BQSPrpoeCAwjY68hbaVfX0=",o="rix1OAwCwBatBYkbMwHeiB8orhFxGCtrLIO+p8UV7KnKKYx7HKtYF6WXBo/IUGDeTaigFjeKrkPH+We8w3kEuQ==",u="BZjRBZ462k9jIHUsCdgF/30fGuDQF67u6c76DX3X/3deRLV4Mi9kBdYhHaGVGWZqqH/cTNjIj2tuPWfpYdy7o9A=",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 8.1",message:"BQt1Xl5ogPe56daSp0w3quRJsxv+pt7/g3R6iX9sLIJbsa2/hQo8lplLXeWzPLx9SheROnln",seed:"dwb/yh7PsevuKlXlxuJM0nl6QSV3Bv/KHs+x6+4qVeU=",encrypted:"DZZvGJ61GU6OOkaPl2t8iLNAB1VwLjl3RKd/tcu19Vz9j68fjCFBQvASq9FK6Sul/N6sXIVsi4ypx/1m77bErYJqiGwkE8sQz/g4ViwQmeCvpfbCoq00B5LxklezvhnM5OeSxFtO/8AtYimLrJ3sUmDYk7xkDI20/Lb8/pyOFsjH"},{title:"RSAES-OAEP Encryption Example 8.2",message:"TraNzZPKmxnfERvUNgj1VwJv5KodXPrCJ6PrWrlUjBigbd7SP4GCWYay/NcRCezvfv+Ihz8HXCqgxGn2nJK8",seed:"o3F9oUO03P+8dCZlqPqVBYVUg0OjcX2hQ7Tc/7x0JmU=",encrypted:"DwsnkHG2jNLgSU4LEvbkOuSaQ9+br9t3dwen8KDGmLkKVJgWGu+TNxyyo2gsBCw7S4eqEFrl49ENEhMehdjrHCBLrEBrhbHxgncKrwIXmcjX1DOCrQXEfbT4keig8TaXkasow5qby9Ded6MWcLu1dZnXPfiuiXaOYajMGJ1D3/y7"},{title:"RSAES-OAEP Encryption Example 8.3",message:"hgSsVjKMGrWtkXhh",seed:"7gYgkHPMoCa7Jk5Rhb+MaLdzn4buBiCQc8ygJrsmTlE=",encrypted:"PAKF3K/lSKcZKWQDr56LmmVqSltcaKEfS7G6+rwG239qszt8eKG6fMYJsP4h7ZfXyV1zuIZXTVhXgiRQbA9os0AhkWiMJJouhsAn60R20BOLQOtQxlXxVOvUMPGuG5EP2O+nTI0VCXky5kHNJdfJolSW+pJLVdSu4mX9Ooga1CSx"},{title:"RSAES-OAEP Encryption Example 8.4",message:"/dpfv27DYanZpKxoryFqBob0OLHg5cNrlV904QfznA3dzA==",seed:"mQrVc9xIqXMjW22CVDYY8ulVEF2ZCtVz3EipcyNbbYI=",encrypted:"LcCLDDj2S5ZOKwpvobOk6rfBWMBbxs3eWR+Edk3lKaoEAFFD5sQv0AaIs3r7yI8sOir9HvS6GKf+jc9t31zIDCIJc3sKVyrNZfEeUFSvihjbPZDo6IaZ8Jau8woE2p1z7n9rG+cbMwKuILRPSEN4hE0QSA/qz0wcye6bjb6NbK20"},{title:"RSAES-OAEP Encryption Example 8.5",message:"Sl9JFL7iXePGk0HeBw==",seed:"7MY7KPB1byL1Ksjm7BJRpuwwRxjsxjso8HVvIvUqyOY=",encrypted:"U+PBTMw6peNHnFQ3pwix/KvVeWu1nSKQGcr9QPpYNgHhuFZK6ARR7XhKxFtDoUMP+iVTXprcow4lr1Uaw4PnEx+cPe0Khl15R8GLiuh5Vm9p3lTPW1u58iP2Oa81pQZTB5AuFiD0fnFZsl+BYfjDVah3cMIu83KOBRPOdLY0j8iq"},{title:"RSAES-OAEP Encryption Example 8.6",message:"jgfWb3uICnJWOrzT81CSvDNAn7f4jyRyvg==",seed:"OSXHGzYtQKCm3kIUVXm6Hn3UWfw5JccbNi1AoKbeQhQ=",encrypted:"WK9hbyje7E0PLeXtWaJxqD4cFkdL5x4vawlKJSOO1OKyZ6uaY8vMYBhBO47xRIqtab5Ul5UGCwdvwPR69PpARsiiSfJHVavkihXixHGgZViGTMU/7J7ftSiNT9hAwrj4JL4f1+8RhTp6WKRzsXAEKpvLK0TrzQL3ioF3QtTsiu/a"}],f(a,l,"sha256",p),e="zyzUHjTKOnKOpcuK/2TDbSe971Nk4zb9aNMSPFoZaowocBPoU9UVbVjRUZVFIPtPbXsXq7aBd2WQnFdhGWWdkCsZBu2KKxDBVcJNEkUo2rnurjeb6sZuSkEXhty4/QBi68Aw3hIZoEwqjBt90xMeTWtsruLjGl7UGsFQmy7x7iqxg2S+VoypQcJezIT/nWQ7XsGqrhAqINc/R5t4D9bakQdSEtnqwDoGdNiZ66LkMfTES2Fba6IjK9SzO67XPWJd",t="AQAB",n="GYwUHiNxWpK8z2oRmlvBE4lGjSgR9UjXJ+F7SrDrmG1vIR77U7cffMvqh+5px17mFQCMUzLetSvzkKvfv+N9cgU2gVmyY4wd4ybiHSIlHw+1hIs78VAF0qdDMPCv6RbuYszBNE0dg6cJ5gZ2JzhA9/N3QkpeCk2nXwGzH/doGc+cv90hUkPDkXwD7zgZkxLlZ7O/eu06tFfzce+KFCP0W2jG4oLsERu6KDO5h/1p+tg7wbjGE8Xh6hbBHtEl6n7B",r="/I1sBL7E65qBksp5AMvlNuLotRnezzOyRZeYxpCd9PF2230jGQ/HK4hlpxiviV8bzZFFKYAnQjtgXnCkfPWDkKjD6I/IxI6LMuPaIQ374+iB6lZ0tqNIwh6T+eVepl79",i="0gDUXniKrOpgakAdBGD4fdXBAn4S3BoNdYbok52c94m0D1GsBEKWHefSHMIeBcgxVcHyqpGTOHz9+VbLSNFTuicEBvm7ulN9SYfZ4vmULXoUy//+p0/s3ako0j4ln17h",s="2xaAL3mi8NRfNY1p/TPkS4H66ChiLpOlQlPpl9AbB0N1naDoErSqTmyL6rIyjVQxlVpBimf/JqjFyAel2jVOBe8xzIz3WPRjcylQsD4mVyb7lOOdalcqJiRKsI23V1Kt",o="oKMXz+ffFCP4em3uhFH04rSmflSX8ptPHk6DC5+t2UARZwJvVZblo5yXgX4PXxbifhnsmQLgHX6m+5qjx2Cv7h44G2neasnAdYWgatnEugC/dcitL6iYpHnoCuKU/tKh",u="CyHzNcNTNC60TDqiREV4DC1lW5QBdMrjjHyKTmSTwLqf0wN0gmewg7mnpsth5C2zYrjJiW23Bk4CrVrmFYfaFbRknJBZSQn+s328tlS+tyaOyAHlqLSqORG+vYhULwW+",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 9.1",message:"9zX9VbqSWSw7Urj5xPaaqhy++P6IrdCVWVQSRn+c9OwLiWxZ7aFiEOdUnIq7EM28IaEuyba1uP0vEDmetg==",seed:"jsll8TSj7Jkx6SocoNyBadXqcFyOyWXxNKPsmTHpKhw=",encrypted:"kuBqApUSIPP0yfNvL0I57K2hReD8CcPhiYZFlPPmdM0cVFQHvdPMjQ2GcEekoBMk2+JR2H3IY6QF0JcANECuoepAuEvks/XolStfJNyUVUO3vLbWGlA1JOOSPiWElIdM0hmLN5In0DizqQit7R0mzH3Y1vUGPBhzOnNgNVQgrWVvqJugjG1SPY7LaZxIjhnz3O/8EkCpxLWcyWkFLX+ujnxIKxAqjmvEztiwLLlcWbJKILOy7KE1lctyh58wYP6e"},{title:"RSAES-OAEP Encryption Example 9.2",message:"gbkGYFAVpjqr5C3fEeGXiRL1QEx0dLJtzj7Ugr+WHsyBi/QgxUZZ",seed:"7LG4sl+lDNqwjlYEKGf0r1gm0WzssbiyX6UM2rCOVgQ=",encrypted:"iNEGT/cssEWuXX1C+SWyMK1hhUjRdNz9l8FeCBhftw8I5JUY1EDXPi2hUpESGxbJOjbyricua+QVvfP/UeoPfOxCcpRSoA3DlviB0ExCJTpCb2NSv9qXtw6Z7qEqk2YyQD7mAsGb2/Y3ug3KkKrF68MAxsWFV3tmL2NV2h+yfW6qz1dVAbAIUZeRuLIbaLdY9F7O4yPC68zkaX9NcTg0tOtnAJth9fMAOFX8sVbvKBgeOuVHV1A8HcAbmqkLMIyp"},{title:"RSAES-OAEP Encryption Example 9.3",message:"/TJkKd+biQ4JtUsYuPNPHiQ=",seed:"6JuwMsbOYiy9tTvJRmAU6nf3d8Dom7Ayxs5iLL21O8k=",encrypted:"JiwfWprF58xVjVRR9B9r0mhomwU5IzkxXCZDgYJwYUcacmrz+KRLKMmtCMN7DLA2lOsfK+72mU+RLmhwfAAhBYmLGR8dLLstazb5xzU9wIM9u3jAl5iyyMLSo6wk/3SH0f7vC2bnFtMkhoHsd3VSTpzl5Q+SqX/4Q1JAMGWMMiHdyjCH+WaXNdTrboPEnPVtTcBGthkkYu8r/G0IkBR6OMPCZFgl/J4uiRTGCRbZx7UC02g6+qNMQY+ksygV6R8w"},{title:"RSAES-OAEP Encryption Example 9.4",message:"8UWbXwyS8BoPcjouVmJITY+MCiD8KdrWrNQ7tfPv/fThtj4H/f5mKNDXTKGb8taeSgq/htKTklp5Z3L4CI4=",seed:"YG87mcC5zNdx6qKeoOTIhPMYnMxgbzuZwLnM13Hqop4=",encrypted:"YXo+2y1QMWzjHkLtCW6DjdJ6fS5qdm+VHALYLFhG/dI1GmOwGOiOrFqesc5KPtWE73N5nJ680e6iYQYdFIsny6a4VH9mq/2Lr6qasMgM27znPzK8l6uQ1pTcDu1fJ4gCJABshzVEXzeTWx6OyKZsOFL8eXiNCwQpyfP9eH0tRjc+F75H3dnzX6AEVff4t0yKjDqp7aRMxFZGidcMJ6KetiNXUg1dhs/lHzItdQ7oMSUAgMnHYAvJDGqy5L4F8XXM"},{title:"RSAES-OAEP Encryption Example 9.5",message:"U+boxynW+cMZ3TF+dLDbjkzMol88gwV0bhN6xjpj7zc557WVq7lujVXlT3vUGrQzN4/7kR0=",seed:"/LxCFALp7KvGCCr6QLpfJlIshA78vEIUAunsq8YIKvo=",encrypted:"fwY+yhF2kyhotPKPlHEXcTOqVRG8Kg9bDJE/cSPUOoyVoiV0j57o9xpEYtZBuM5RanPUsTDcYNvorKqP5mbN81JV3SmEkIRTL7JoHGpJAMDHFjXBfpAwgUCPhfJ2+CUCIyOoPZqlt4w+K9l+WeFZYDatr0HC1NO+stbvWq358HRdX27TexTocG5OEB4l9gqhnUYD2JHNlGidsm0vzFQJoIMaH26x9Kgosg6tZQ0t3jdoeLbTCSxOMM9dDQjjK447"},{title:"RSAES-OAEP Encryption Example 9.6",message:"trKOohmNDBAIvGQ=",seed:"I6reDh4Iu5uaeNIwKlL5whsuG6Ijqt4OHgi7m5p40jA=",encrypted:"PISd/61VECapJ7gfG4J2OroSl69kvIZD2uuqmiro3E4pmXfpdOW/q+1WCr574Pjsj/xrIUdgmNMAl8QjciO/nArYi0IFco1tCRLNZqMDGjzZifHIcDNCsvnKg/VRmkPrjXbndebLqMtw7taeVztYq1HKVAoGsdIvLkuhmsK0Iaesp+/8xka40c9hWwcXHsG+I7pevwFarxQQbuUjXSkZ2ObWgzgGSiGCw9QNUGpO0usATLSd0AFkeE+IM/KAwJCy"}],f(a,l,"sha256",p),e="rkXtVgHOxrjMBfgDk1xnTdvg11xMCf15UfxrDK7DE6jfOZcMUYv/ul7Wjz8NfyKkAp1BPxrgfk6+nkF3ziPn9UBLVp5O4b3PPB+wPvETgC1PhV65tRNLWnyAha3K5vovoUF+w3Y74XGwxit2Dt4jwSrZK5gIhMZB9aj6wmva1KAzgaIv4bdUiFCUyCUG1AGaU1ooav6ycbubpZLeGNz2AMKu6uVuAvfPefwUzzvcfNhP67v5UMqQMEsiGaeqBjrvosPBmA5WDNZK/neVhbYQdle5V4V+/eYBCYirfeQX/IjY84TE5ucsP5Q+DDHAxKXMNvh52KOsnX1Zhg6q2muDuw==",t="AQAB",n="BWsEIW/l81SsdyUKS2sMhSWoXFmwvYDFZFCiLV9DjllqMzqodeKR3UP0jLiLnV/A1Jn5/NHDl/mvwHDNnjmMjRnmHbfHQQprJnXfv100W4BNIBrdUC1c4t/LCRzpmXu+vlcwbzg+TViBA/A29+hdGTTRUqMj5KjbRR1vSlsbDxAswVDgL+7iuI3qStTBusyyTYQHLRTh0kpncfdAjuMFZPuG1Dk6NLzwt4hQHRkzA/E6IoSwAfD2Ser3kyjUrFxDCrRBSSCpRg7Rt7xA7GU+h20Jq8UJrkW1JRkBFqDCYQGEgphQnBw786SD5ydAVOFelwdQNumJ9gkygHtSV3UeeQ==",r="7PWuzR5VFf/6y9daKBbG6/SQGM37RjjhhdZqc5a2+AkPgBjH/ZXMNLhX3BfwzGUWuxNGq01YLK2te0EDNSOHtwM40IQEfJ2VObZJYgSz3W6kQkmSB77AH5ZCh/9jNsOYRlgzaEb1bkaGGIHBAjPSF2vxWl6W3ceAvIaKp30852k=",i="vEbEZPxqxMp4Ow6wijyEG3cvfpsvKLq9WIroheGgxh5IWKD7JawpmZDzW+hRZMJZuhF1zdcZJwcTUYSZK2wpt0bdDSyr4UKDX30UjMFhUktKCZRtSLgoRz8c52tstohsNFwD4F9B1RtcOpCj8kBzx9dKT+JdnPIcdZYPP8OGMYM=",s="xzVkVx0A+xXQij3plXpQkV1xJulELaz0K8guhi5Wc/9qAI7U0uN0YX34nxehYLQ7f9qctra3QhhgmBX31FyiY8FZqjLSctEn+vS8jKLXc3jorrGbCtfaPLPeCucxSYD2K21LCoddHfA8G645zNgz72zX4tlSi/CE0flp55Tp9sE=",o="Jlizf235wQML4dtoEX+p2H456itpO35tOi9wlHQT7sYULhj7jfy2rFRdfIagrUj4RXFw8O+ya8SBJsU+/R0WkgGY3CoRB9woLbaoDNMGI2C6P6E/cOQxL/GmzWuPxM2cXD2xfG1qVyEvc64p9hkye61ZsVOFhYW6Tii2CmKkXkk=",u="bzhSazklCFU07z5BWoNu3ouGFYosfL/sywvYNDBP7Gg7qNT0ecQz1DQW5jJpYjzqEAd22Fr/QB0//2EO5lQRzjsTY9Y6lwnu3kJkfOpWFJPVRXCoecGGgs2XcQuWIF7DERfXO182Ij+t1ui6kN18DuYdROFjJR4gx/ZuswURfLg=",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 10.1",message:"i7pr+CpsD4bV8XVul5VocLCJU7BrTrIFvBaU7g==",seed:"R+GrcRn+5WyV7l6q2G9A0KpjvTNH4atxGf7lbJXuXqo=",encrypted:"iXCnHvFRO1zd7U4HnjDMCLRvnKZj6OVRMZv8VZCAyxdA1T4AUORzxWzAtAAA541iVjEs1n5MIrDkymBDk3cM1oha9XCGsXeazZpW2z2+4aeaM3mv/oz3QYfEGiet415sHNnikAQ9ZmYg2uzBNUOS90h0qRAWFdUV5Tyxo1HZ0slg37Ikvyu2d6tgWRAAjgiAGK7IzlU4muAfQ4GiLpvElfm+0vch7lhlrk7t5TErhEF7RWQe16lVBva7azIxlGyyrqOhYrmQ+6JQpmPnsmEKYpSxTUP2tLzoSH5e+Y0CSaD7ZB20PWILB+7PKRueJ23hlMYmnAgQBePWSUdsljXAgA=="},{title:"RSAES-OAEP Encryption Example 10.2",message:"5q0YHwU7WKkE8kV1EDc+Vw==",seed:"bRf1tMH/rDUdGVv3sJ0J8JpAec9tF/W0wf+sNR0ZW/c=",encrypted:"I3uBbIiYuvEYFA5OtRycm8zxMuuEoZMNRsPspeKZIGhcnQkqH8XEM8iJMeL6ZKA0hJm3jj4z1Xz7ra3tqMyTiw3vGKocjsYdXchK+ar3Atj/jXkdJLeIiqfTBA+orCKoPbrBXLllt4dqkhc3lbq0Z5lTBeh6caklDnmJGIMnxkiG3vON/uVpIR6LMBg+IudMCMOv2f++RpBhhrI8iJOsPbnebdMIrxviVaVxT22GUNehadT8WrHI/qKv+p1rCpD3AAyXAhJy7KKp1l+nPCy1IY1prey+YgBxCAnlHuHv2V7q1FZTRXMJe3iKubLeiX6SfAKU1sivpoqk5ntMSMgAfw=="},{title:"RSAES-OAEP Encryption Example 10.3",message:"UQos9g6Gb6I0BVPJTqOfvCVjEeg+lEVLQSQ=",seed:"OFOHUU3szHx0DdjN+druSaHL/VQ4U4dRTezMfHQN2M0=",encrypted:"n3scq/IYyBWbaN4Xd+mKJ0bZQR10yiSYzdjV1D1K3xiH11Tvhbj59PdRQXflSxE1QMhxN0jp9/tsErIlXqSnBH2XsTX6glPwJmdgXj7gZ1Aj+wsl15PctCcZv0I/4imvBBOSEd5TRmag3oU7gmbpKQCSHi6Hp2z5H/xEHekrRZemX7Dwl6A8tzVhCBpPweKNpe34OLMrHcdyb0k/uyabEHtzoZLpiOgHRjqi7SHr2ene9PPOswH7hc87xkiKtiFOpCeCScF6asFeiUTn5sf5tuPHVGqjKskwxcm/ToW3hm7ChnQdYcPRlnHrMeLBJt6o6xdrg6+SvsnNzctLxes0gA=="},{title:"RSAES-OAEP Encryption Example 10.4",message:"vN0ZDaO30wDfmgbiLKrip18QyR/2Z7fBa96LUwZKJkmpQEXJ",seed:"XKymoPdkFhqWhPhdkrbg7zfKi2VcrKag92QWGpaE+F0=",encrypted:"KWbozLkoxbGfY0Dixr8GE/JD+MDAXIUFzm7K5AYscTvyAh9EDkLfDc/i8Y9Cjz/GXWsrRAlzO9PmLj4rECjbaNdkyzgYUiXSVV0SWmEF62nhZcScf+5QWHgsv6syu2VXdkz9nW4O3LWir2M/HqJ6kmpKVm5o7TqeYZ7GrY25FUnFDM8DpXOZqOImHVAoh8Tim9d2V9lk2D2Av6Tdsa4SIyBDj5VcX3OVoTbqdkKj5It9ANHjXaqGwqEyj7j1cQrRzrbGVbib3qzvoFvGWoo5yzr3D8J8z/UXJ4sBkumcjrphFTDe9qQcJ5FI82ZZsChJssRcZl4ApFosoljixk0WkA=="},{title:"RSAES-OAEP Encryption Example 10.5",message:"p91sfcJLRvndXx6RraTDs9+UfodyMqk=",seed:"lbyp44WYlLPdhp+n7NW7xkAb8+SVvKnjhZiUs92Gn6c=",encrypted:"D7UPhV1nPwixcgg47HSlk/8yDLEDSXxoyo6H7MMopUTYwCmAjtnpWp4oWGg0sACoUlzKpR3PN21a4xru1txalcOkceylsQI9AIFvLhZyS20HbvQeExT9zQGyJaDhygC/6gPifgELk7x5QUqsd+TL/MQdgBZqbLO0skLOqNG3KrTMmN0oeWgxgjMmWnyBH0qkUpV5SlRN2P3nIHd/DZrkDn/qJG0MpXh6AeNHhvSgv8gyDG2Vzdf04OgvZLJTJaTdqHuXz93t7+PQ+QfKOG0wCEf5gOaYpkFarorv9XPLhzuOauN+Dd2IPzgKH5+wjbTZlZzEn+xRyDXK7s6GL/XOZw=="},{title:"RSAES-OAEP Encryption Example 10.6",message:"6vGnOhsMRglTfeac2SKLvPuajKjGw++vBW/kp/RjTtALfDnsaSLXuOosBOus",seed:"n0fd9C6X7qhWqb28cU6zrCL26zKfR930LpfuqFapvbw=",encrypted:"FO6Mv81w3SW/oVGIgdfAbIOW1eK8/UFdwryWg3ek0URFK09jNQtAaxT+66Yn5EJrTWh8fgRn1spnAOUsY5eq7iGpRsPGE86MLNonOvrBIht4Z+IDum55EgmwCrlfyiGe2fX4Xv1ifCQMSHd3OJTujAosVI3vPJaSsbTW6FqOFkM5m9uPqrdd+yhQ942wN4m4d4TG/YPx5gf62fbCRHOfvA5qSpO0XGQ45u+sWBAtOfzxmaYtf7WRAlu+JvIjTp8I2lAfVEuuW9+TJattx9RXN8jaWOBsceLIOfE6bkgad50UX5PyEtapnJOG1j0bh5PZ//oKtIASarB3PwdWM1EQTQ=="}],f(a,l,"sha256",p)}function u(e){var t=i.createBuffer(e),n=t.toHex();return new BigInteger(n,16)}function a(e){var t=i.decode64(e);return u(t)}function f(e,t,n,i){n==="sha1"?n=r.sha1.create():n==="sha256"&&(n=r.sha256.create());for(var s=0;s<i.length;++s){var o=i[s];it("should test "+o.title,function(){l(e,t,n,o.message,o.seed,o.encrypted)})}}function l(t,r,s,o,u,a){var o=i.decode64(o),u=i.decode64(u),f=n.encode_rsa_oaep(t,o,{seed:u,md:s}),l=t.encrypt(f,null);e.equal(a,i.encode64(l));var c=r.decrypt(l,null),h=n.decode_rsa_oaep(r,c,{md:s});e.equal(o,h),l=t.encrypt(o,"RSA-OAEP",{md:s}),h=r.decrypt(l,"RSA-OAEP",{md:s}),e.equal(o,h)}function c(e,n){return e=a(e),n=a(n),t.setRsaPublicKey(e,n)}function h(e,n,r,i,s,o,u,f){return e=a(e),n=a(n),r=a(r),i=a(i),s=a(s),o=a(o),u=a(u),f=a(f),t.setRsaPrivateKey(e,n,r,i,s,o,u,f)}function p(){var e,t,n,r,i,s,o,u,a,f;return e="qLOyhK+OtQs4cDSoYPFGxJGfMYdjzWxVmMiuSBGh4KvEx+CwgtaTpef87Wdc9GaFEncsDLxkp0LGxjD1M8jMcvYq6DPEC/JYQumEu3i9v5fAEH1VvbZi9cTg+rmEXLUUjvc5LdOq/5OuHmtme7PUJHYW1PW6ENTP0ibeiNOfFvs=",t="AQAB",n="UzOc/befyEZqZVxzFqyoXFX9j23YmP2vEZUX709S6P2OJY35P+4YD6DkqylpPNg7FSpVPUrE0YEri5+lrw5/Vf5zBN9BVwkm8zEfFcTWWnMsSDEW7j09LQrzVJrZv3y/t4rYhPhNW+sEck3HNpsx3vN9DPU56c/N095lNynq1dE=",r="0yc35yZ//hNBstXA0VCoG1hvsxMr7S+NUmKGSpy58wrzi+RIWY1BOhcu+4AsIazxwRxSDC8mpHHcrSEurHyjnQ==",i="zIhT0dVNpjD6wAT0cfKBx7iYLYIkpJDtvrM9Pj1cyTxHZXA9HdeRZC8fEWoN2FK+JBmyr3K/6aAw6GCwKItddw==",s="DhK/FxjpzvVZm6HDiC/oBGqQh07vzo8szCDk8nQfsKM6OEiuyckwX77L0tdoGZZ9RnGsxkMeQDeWjbN4eOaVwQ==",o="lSl7D5Wi+mfQBwfWCd/U/AXIna/C721upVvsdx6jM3NNklHnkILs2oZu/vE8RZ4aYxOGt+NUyJn18RLKhdcVgw==",u="T0VsUCSTvcDtKrdWo6btTWc1Kml9QhbpMhKxJ6Y9VBHOb6mNXb79cyY+NygUJ0OBgWbtfdY2h90qjKHS9PvY4Q==",a=c(e,t),f=h(e,t,n,r,i,s,o,u),{publicKey:a,privateKey:f}}it("should detect invalid RSAES-OAEP padding",function(){var t=p(),r=i.decode64("JRTfRpV1WmeyiOr0kFw27sZv0v0="),s=n.encode_rsa_oaep(t.publicKey,"datadatadatadata",{seed:r}),o=t.publicKey.encrypt(s,null),u=o.length*8;u/=8;for(var a=8;a<u;++a){var f=a/8,l=a%8,c=o.substring(0,f),h=1<<l;c+=String.fromCharCode(o.charCodeAt(f)^h),c+=o.substring(f+1);try{var d=t.privateKey.decrypt(c,null);throw n.decode_rsa_oaep(t.privateKey,d),{message:"Expected an exception."}}catch(v){e.equal(v.message,"Invalid RSAES-OAEP padding.")}}}),it("should detect leading zero bytes",function(){var t=p(),r=i.fillString("\0",80),s=n.encode_rsa_oaep(t.publicKey,r),o=t.publicKey.encrypt(s,null),u=t.privateKey.decrypt(o,null),a=n.decode_rsa_oaep(t.privateKey,u);e.equal(r,a)}),s(),o()})}typeof define=="function"?define("test/pkcs1",["forge/pki","forge/pkcs1","forge/md","forge/util"],function(t,n,r,i){e(ASSERT,t(),n(),r(),i())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/pki")(),require("../../js/pkcs1")(),require("../../js/md")(),require("../../js/util")())}(),function(){function e(e,t,n,r){var i={privateKey:"-----BEGIN RSA PRIVATE KEY-----\r\nMIICXQIBAAKBgQDL0EugUiNGMWscLAVM0VoMdhDZEJOqdsUMpx9U0YZI7szokJqQ\r\nNIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMGTkP3VF29vXBo+dLq5e+8VyAy\r\nQ3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGtvnM+z0MYDdKo80efzwIDAQAB\r\nAoGAIzkGONi5G+JifmXlLJdplom486p3upf4Ce2/7mqfaG9MnkyPSairKD/JXvfh\r\nNNWkkN8DKKDKBcVVElPgORYT0qwrWc7ueLBMUCbRXb1ZyfEulimG0R3kjUh7NYau\r\nDaIkVgfykXGSQMZx8FoaT6L080zd+0emKDDYRrb+/kgJNJECQQDoUZoiC2K/DWNY\r\nh3/ppZ0ane2y4SBmJUHJVMPQ2CEgxsrJTxet668ckNCKaOP/3VFPoWC41f17DvKq\r\nnoYINNntAkEA4JbZBZBVUrQFhHlrpXT4jzqtO2RlKZzEq8qmFZfEErxOT1WMyyCi\r\nlAQ5gUKardo1Kf0omC8Xq/uO9ZYdED55KwJBALs6cJ65UFaq4oLJiQPzLd7yokuE\r\ndcj8g71PLBTW6jPxIiMFNA89nz3FU9wIVp+xbMNhSoMMKqIPVPC+m0Rn260CQQDA\r\nI83fWK/mZWUjBM33a68KumRiH238v8XyQxj7+C8i6D8G2GXvkigFAehAkb7LZZd+\r\nKLuGFyPlWv3fVWHf99KpAkBQFKk3MRMl6IGJZUEFQe4l5whm8LkGU4acSqv9B3xt\r\nqROkCrsFrMPqjuuzEmyHoQZ64r2PLJg7FOuyhBnQUOt4\r\n-----END RSA PRIVATE KEY-----\r\n",publicKey:"-----BEGIN PUBLIC KEY-----\r\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDL0EugUiNGMWscLAVM0VoMdhDZ\r\nEJOqdsUMpx9U0YZI7szokJqQNIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMG\r\nTkP3VF29vXBo+dLq5e+8VyAyQ3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGt\r\nvnM+z0MYDdKo80efzwIDAQAB\r\n-----END PUBLIC KEY-----\r\n",certificate:"-----BEGIN CERTIFICATE-----\r\nMIIDIjCCAougAwIBAgIJANE2aHSbwpaRMA0GCSqGSIb3DQEBBQUAMGoxCzAJBgNV\r\nBAYTAlVTMREwDwYDVQQIEwhWaXJnaW5pYTETMBEGA1UEBxMKQmxhY2tzYnVyZzEN\r\nMAsGA1UEChMEVGVzdDENMAsGA1UECxMEVGVzdDEVMBMGA1UEAxMMbXlzZXJ2ZXIu\r\nY29tMB4XDTEwMDYxOTE3MzYyOFoXDTExMDYxOTE3MzYyOFowajELMAkGA1UEBhMC\r\nVVMxETAPBgNVBAgTCFZpcmdpbmlhMRMwEQYDVQQHEwpCbGFja3NidXJnMQ0wCwYD\r\nVQQKEwRUZXN0MQ0wCwYDVQQLEwRUZXN0MRUwEwYDVQQDEwxteXNlcnZlci5jb20w\r\ngZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMvQS6BSI0YxaxwsBUzRWgx2ENkQ\r\nk6p2xQynH1TRhkjuzOiQmpA0jCiSJDoSic2dZIyUi/LjoCGeVFif57N5N5Tt4wZO\r\nQ/dUXb29cGj50url77xXIDJDcXMzXAji2ziFEAIXzDqarKBdDuL9IO7z+tepEa2+\r\ncz7PQxgN0qjzR5/PAgMBAAGjgc8wgcwwHQYDVR0OBBYEFPV1Y+DHXW6bA/r9sv1y\r\nNJ8jAwMAMIGcBgNVHSMEgZQwgZGAFPV1Y+DHXW6bA/r9sv1yNJ8jAwMAoW6kbDBq\r\nMQswCQYDVQQGEwJVUzERMA8GA1UECBMIVmlyZ2luaWExEzARBgNVBAcTCkJsYWNr\r\nc2J1cmcxDTALBgNVBAoTBFRlc3QxDTALBgNVBAsTBFRlc3QxFTATBgNVBAMTDG15\r\nc2VydmVyLmNvbYIJANE2aHSbwpaRMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEF\r\nBQADgYEARdH2KOlJWTC1CS2y/PAvg4uiM31PXMC1hqSdJlnLM1MY4hRfuf9VyTeX\r\nY6FdybcyDLSxKn9id+g9229ci9/s9PI+QmD5vXd8yZyScLc2JkYB4GC6+9D1+/+x\r\ns2hzMxuK6kzZlP+0l9LGcraMQPGRydjCARZZm4Uegln9rh85XFQ=\r\n-----END CERTIFICATE-----\r\n"};describe("x509",function(){it("should convert certificate to/from PEM",function(){var n=t.certificateFromPem(i.certificate);e.equal(t.certificateToPem(n),i.certificate)}),it("should verify self-signed certificate",function(){var n=t.certificateFromPem(i.certificate);e.ok(n.verify(n))}),it("should generate a self-signed certificate",function(){var n={privateKey:t.privateKeyFromPem(i.privateKey),publicKey:t.publicKeyFromPem(i.publicKey)},r=t.createCertificate();r.publicKey=n.publicKey,r.serialNumber="01",r.validity.notBefore=new Date,r.validity.notAfter=new Date,r.validity.notAfter.setFullYear(r.validity.notBefore.getFullYear()+1);var s=[{name:"commonName",value:"example.org"},{name:"countryName",value:"US"},{shortName:"ST",value:"Virginia"},{name:"localityName",value:"Blacksburg"},{name:"organizationName",value:"Test"},{shortName:"OU",value:"Test"}];r.setSubject(s),r.setIssuer(s),r.setExtensions([{name:"basicConstraints",cA:!0},{name:"keyUsage",keyCertSign:!0,digitalSignature:!0,nonRepudiation:!0,keyEncipherment:!0,dataEncipherment:!0},{name:"extKeyUsage",serverAuth:!0,clientAuth:!0,codeSigning:!0,emailProtection:!0,timeStamping:!0},{name:"nsCertType",client:!0,server:!0,email:!0,objsign:!0,sslCA:!0,emailCA:!0,objCA:!0},{name:"subjectAltName",altNames:[{type:6,value:"http://example.org/webid#me"}]},{name:"subjectKeyIdentifier"}]),r.sign(n.privateKey);var o=t.certificateToPem(r);r=t.certificateFromPem(o);var u=t.createCaStore();u.addCertificate(r),t.verifyCertificateChain(u,[r],function(t,n,i){return e.equal(t,!0),e.ok(r.verifySubjectKeyIdentifier()),!0})}),it("should verify certificate with sha1WithRSAEncryption signature",function(){var n="-----BEGIN CERTIFICATE-----\r\nMIIDZDCCAs2gAwIBAgIKQ8fjjgAAAABh3jANBgkqhkiG9w0BAQUFADBGMQswCQYD\r\nVQQGEwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzEiMCAGA1UEAxMZR29vZ2xlIElu\r\ndGVybmV0IEF1dGhvcml0eTAeFw0xMjA2MjcxMzU5MTZaFw0xMzA2MDcxOTQzMjda\r\nMGcxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1N\r\nb3VudGFpbiBWaWV3MRMwEQYDVQQKEwpHb29nbGUgSW5jMRYwFAYDVQQDEw13d3cu\r\nZ29vZ2xlLmRlMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCw2Hw3vNy5QMSd\r\n0/iMCS8lwZk9lnEk2NmrJt6vGJfRGlBprtHp5lpMFMoi+x8m8EwGVxXHGp7hLyN/\r\ngXuUjL7/DY9fxxx9l77D+sDZz7jfUfWmhS03Ra1FbT6myF8miVZFChJ8XgWzioJY\r\ngyNdRUC9149yrXdPWrSmSVaT0+tUCwIDAQABo4IBNjCCATIwHQYDVR0lBBYwFAYI\r\nKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBTiQGhrO3785rMPIKZ/zQEl5RyS\r\n0TAfBgNVHSMEGDAWgBS/wDDr9UMRPme6npH7/Gra42sSJDBbBgNVHR8EVDBSMFCg\r\nTqBMhkpodHRwOi8vd3d3LmdzdGF0aWMuY29tL0dvb2dsZUludGVybmV0QXV0aG9y\r\naXR5L0dvb2dsZUludGVybmV0QXV0aG9yaXR5LmNybDBmBggrBgEFBQcBAQRaMFgw\r\nVgYIKwYBBQUHMAKGSmh0dHA6Ly93d3cuZ3N0YXRpYy5jb20vR29vZ2xlSW50ZXJu\r\nZXRBdXRob3JpdHkvR29vZ2xlSW50ZXJuZXRBdXRob3JpdHkuY3J0MAwGA1UdEwEB\r\n/wQCMAAwDQYJKoZIhvcNAQEFBQADgYEAVJ0qt/MBvHEPuWHeH51756qy+lBNygLA\r\nXp5Gq+xHUTOzRty61BR05zv142hYAGWvpvnEOJ/DI7V3QlXK8a6dQ+du97obQJJx\r\n7ekqtfxVzmlSb23halYSoXmWgP8Tq0VUDsgsSLE7fS8JuO1soXUVKj1/6w189HL6\r\nLsngXwZSuL0=\r\n-----END CERTIFICATE-----\r\n",r="-----BEGIN CERTIFICATE-----\r\nMIICsDCCAhmgAwIBAgIDC2dxMA0GCSqGSIb3DQEBBQUAME4xCzAJBgNVBAYTAlVT\r\nMRAwDgYDVQQKEwdFcXVpZmF4MS0wKwYDVQQLEyRFcXVpZmF4IFNlY3VyZSBDZXJ0\r\naWZpY2F0ZSBBdXRob3JpdHkwHhcNMDkwNjA4MjA0MzI3WhcNMTMwNjA3MTk0MzI3\r\nWjBGMQswCQYDVQQGEwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzEiMCAGA1UEAxMZ\r\nR29vZ2xlIEludGVybmV0IEF1dGhvcml0eTCBnzANBgkqhkiG9w0BAQEFAAOBjQAw\r\ngYkCgYEAye23pIucV+eEPkB9hPSP0XFjU5nneXQUr0SZMyCSjXvlKAy6rWxJfoNf\r\nNFlOCnowzdDXxFdF7dWq1nMmzq0yE7jXDx07393cCDaob1FEm8rWIFJztyaHNWrb\r\nqeXUWaUr/GcZOfqTGBhs3t0lig4zFEfC7wFQeeT9adGnwKziV28CAwEAAaOBozCB\r\noDAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFL/AMOv1QxE+Z7qekfv8atrjaxIk\r\nMB8GA1UdIwQYMBaAFEjmaPkr0rKV10fYIyAQTzOYkJ/UMBIGA1UdEwEB/wQIMAYB\r\nAf8CAQAwOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2NybC5nZW90cnVzdC5jb20v\r\nY3Jscy9zZWN1cmVjYS5jcmwwDQYJKoZIhvcNAQEFBQADgYEAuIojxkiWsRF8YHde\r\nBZqrocb6ghwYB8TrgbCoZutJqOkM0ymt9e8kTP3kS8p/XmOrmSfLnzYhLLkQYGfN\r\n0rTw8Ktx5YtaiScRhKqOv5nwnQkhClIZmloJ0pC3+gz4fniisIWvXEyZ2VxVKfml\r\nUUIuOss4jHg7y/j7lYe8vJD5UDI=\r\n-----END CERTIFICATE-----\r\n",i=t.certificateFromPem(n,!0),s=t.certificateFromPem(r);e.ok(s.verify(i))}),it("should verify certificate with sha256WithRSAEncryption signature",function(){var n="-----BEGIN CERTIFICATE-----\r\nMIIDuzCCAqOgAwIBAgIEO5vZjDANBgkqhkiG9w0BAQsFADBGMQswCQYDVQQGEwJE\r\nRTEPMA0GA1UEChMGRWxzdGVyMQswCQYDVQQLEwJDQTEZMBcGA1UEAxMQRWxzdGVy\r\nU29mdFRlc3RDQTAeFw0xMDA5MTUwNTM4MjRaFw0xMzA5MTUwNTM4MjRaMCsxFDAS\r\nBgNVBAUTCzEwMDIzMTQ5OTRDMRMwEQYDVQQDEwoxMDAyMzE0OTk0MIGfMA0GCSqG\r\nSIb3DQEBAQUAA4GNADCBiQKBgQCLPqjbwjsugzw6+qwwm/pdzDwk7ASIsBYJ17GT\r\nqyT0zCnYmdDDGWsYc+xxFVVIi8xBt6Mlq8Rwj+02UJhY9qm6zRA9MqFZC3ih+HoW\r\nxq7H8N2d10N0rX6h5PSjkF5fU5ugncZmppsRGJ9DNXgwjpf/CsH2rqThUzK4xfrq\r\njpDS/wIDAQABo4IBTjCCAUowDgYDVR0PAQH/BAQDAgUgMAwGA1UdEwEB/wQCMAAw\r\nHQYDVR0OBBYEFF1h7H37OQivS57GD8+nK6VsgMPTMIGXBgNVHR8EgY8wgYwwgYmg\r\ngYaggYOGgYBsZGFwOi8vMTkyLjE2OC42LjI0OjM4OS9sJTNkQ0ElMjBaZXJ0aWZp\r\na2F0ZSxvdSUzZENBLGNuJTNkRWxzdGVyU29mdFRlc3RDQSxkYyUzZHdpZXNlbCxk\r\nYyUzZGVsc3RlcixkYyUzZGRlPz9iYXNlPyhvYmplY3RDbGFzcz0qKTBxBgNVHSME\r\najBogBRBILMYmlZu//pj3wjDe2UPkq7jk6FKpEgwRjELMAkGA1UEBhMCREUxDzAN\r\nBgNVBAoTBkVsc3RlcjEPMA0GA1UECxMGUm9vdENBMRUwEwYDVQQDEwxFbHN0ZXJS\r\nb290Q0GCBDuayikwDQYJKoZIhvcNAQELBQADggEBAK8Z1+/VNyU5w/EiyhFH5aRE\r\nMzxo0DahqKEm4pW5haBgKubJwZGs+CrBZR70TPbZGgJd36eyMgeXb/06lBnxewii\r\nI/aY6wMTviQTpqFnz5m0Le8UhH+hY1bqNG/vf6J+1gbOSrZyhAUV+MDJbL/OkzX4\r\nvoVAfUBqSODod0f5wCW2RhvBmB9E62baP6qizdxyPA4iV16H4C0etd/7coLX6NZC\r\noz3Yu0IRTQCH+YrpfIbxGb0grNhtCTfFpa287fuzu8mIEvLNr8GibhBXmQg7iJ+y\r\nq0VIyZLY8k6jEPrUB5Iv5ouSR19Dda/2+xJPlT/bosuNcErEuk/yKAHWAzwm1wQ=\r\n-----END CERTIFICATE-----\r\n",r="-----BEGIN CERTIFICATE-----\r\nMIIESjCCAzKgAwIBAgIEO5rKKTANBgkqhkiG9w0BAQsFADBGMQswCQYDVQQGEwJE\r\nRTEPMA0GA1UEChMGRWxzdGVyMQ8wDQYDVQQLEwZSb290Q0ExFTATBgNVBAMTDEVs\r\nc3RlclJvb3RDQTAeFw0wOTA3MjgwODE5MTFaFw0xNDA3MjgwODE5MTFaMEYxCzAJ\r\nBgNVBAYTAkRFMQ8wDQYDVQQKEwZFbHN0ZXIxCzAJBgNVBAsTAkNBMRkwFwYDVQQD\r\nExBFbHN0ZXJTb2Z0VGVzdENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC\r\nAQEAv5uoKLnxXQe75iqwqgyt3H6MDAx/wvUVs26+2+yHpEUb/2gA3L8E+NChSb9E\r\naNgxxoac3Yhvxzq2mPpih3vkY7Xw512Tm8l/OPbT8kbmBJmYZneFALXHytAIZiEf\r\ne0ZYNKAlClFIgNP5bE9UjTqVEEoSiUhpTubM6c5xEYVznnwPBoYQ0ari7RTDYnME\r\nHK4vMfoeBeWHYPiEygNHnGUG8d3merRC/lQASUtL6ikmLWKCKHfyit5ACzPNKAtw\r\nIzHAzD5ek0BpcUTci8hUsKz2ZvmoZcjPyj63veQuMYS5cTMgr3bfz9uz1xuoEDsb\r\nSv9rQX9Iw3N7yMpxTDJTqGKhYwIDAQABo4IBPjCCATowDgYDVR0PAQH/BAQDAgEG\r\nMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFEEgsxiaVm7/+mPfCMN7ZQ+S\r\nruOTMIGXBgNVHR8EgY8wgYwwgYmggYaggYOGgYBsZGFwOi8vMTkyLjE2OC42LjI0\r\nOjM4OS9sJTNkQ0ElMjBaZXJ0aWZpa2F0ZSxvdSUzZFJvb3RDQSxjbiUzZEVsc3Rl\r\nclJvb3RDQSxkYyUzZHdpZXNlbCxkYyUzZGVsc3RlcixkYyUzZGRlPz9iYXNlPyhv\r\nYmplY3RDbGFzcz0qKTBbBgNVHSMEVDBSoUqkSDBGMQswCQYDVQQGEwJERTEPMA0G\r\nA1UEChMGRWxzdGVyMQ8wDQYDVQQLEwZSb290Q0ExFTATBgNVBAMTDEVsc3RlclJv\r\nb3RDQYIEO5rKADANBgkqhkiG9w0BAQsFAAOCAQEAFauDnfHSbgRmbFkpQUXM5wKi\r\nK5STAaVps201iAjacX5EsOs5L37VUMoT9G2DAE8Z6B1pIiR3zcd3UpiHnFlUTC0f\r\nZdOCXzDkOfziKY/RzuUsLNFUhBizCIA0+XcKgm3dSA5ex8fChLJddSYheSLuPua7\r\niNMuzaU2YnevbMwpdEsl55Qr/uzcc0YM/mCuM4vsNFyFml91SQyPPmdR3VvGOoGl\r\nqS1R0HSoPJUvr0N0kARwD7kO3ikcJ6FxBCsgVLZHGJC+q8PQNZmVMbfgjH4nAyP8\r\nu7Qe03v2WLW0UgKu2g0UcQXWXbovktpZoK0fUOwv3bqsZ0K1IjVvMKG8OysUvA==\r\n-----END CERTIFICATE-----\r\n",i=t.certificateFromPem(n,!0),s=t.certificateFromPem(r);e.ok(s.verify(i))}),it("should import certificate with sha256 RSASSA-PSS signature",function(){var n="-----BEGIN CERTIFICATE-----\r\nMIIERzCCAvugAwIBAgIEO50CcjBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQC\r\nAQUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCASAwRjELMAkGA1UE\r\nBhMCREUxDzANBgNVBAoTBkVsc3RlcjELMAkGA1UECxMCQ0ExGTAXBgNVBAMTEEVs\r\nc3RlclNvZnRUZXN0Q0EwHhcNMTEwNzI4MTIxMzU3WhcNMTQwNzI4MTIxMzU3WjAr\r\nMRQwEgYDVQQFEwsxMDAyNzUzMzI1QzETMBEGA1UEAxMKMTAwMjc1MzMyNTCCASIw\r\nDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALHCogo7LVUkxWsMIc/0jgH2PCLt\r\nukbATPehxWBG1XUPrz53lWgFJzlZaKLlLVVnXrfULaifuOKlZP6SM1JQlL1JuYgY\r\nAdgZyHjderNIk5NsSTmefwonSn/ukri5IRTH420oHtSjxk6+/DXlWnQy5OzTN6Bq\r\njVJo8L+TTmf2jWuEam5cWa+YVP2k3tIqX5yMUDFjKO4znHdtIkHnBE0Kx03rWQRB\r\nTSYWDgDm2gttdOs9JVeuW0nnwQj27uo9gOR0iyaUjVrKLZ95p6zpXhM4uMSVRNeo\r\nLqkdqP2n+4pDXZVqLNgjkHQUS/xq9Q/kYgT2J7wkGfYxP9to7TG7vra1eOECAwEA\r\nAaOB7zCB7DAOBgNVHQ8BAf8EBAMCBSAwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU\r\nNDJ2BZIk6ukLqkdmttH12bu2leswOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2Ny\r\nbC5lbHN0ZXIuZGUvRWxzdGVyU29mdFRlc3RDQS5jcmwwcQYDVR0jBGowaIAU1R9A\r\nHmpdzaxK3v+ihQsEpAFgzOKhSqRIMEYxCzAJBgNVBAYTAkRFMQ8wDQYDVQQKEwZF\r\nbHN0ZXIxDzANBgNVBAsTBlJvb3RDQTEVMBMGA1UEAxMMRWxzdGVyUm9vdENBggQ7\r\nmsqPMEEGCSqGSIb3DQEBCjA0oA8wDQYJYIZIAWUDBAIBBQChHDAaBgkqhkiG9w0B\r\nAQgwDQYJYIZIAWUDBAIBBQCiAwIBIAOCAQEAJBYNRZpe+z3yPPLV539Yci6OfjVg\r\nvs1e3rvSvcpFaqRJ8vZ8WNx3uuRQZ6B4Z3YEc00UJAOl3wU6KhamyryK2YvCrPg+\r\nTS5QDXNaO2z/rAnY1wWSlwBPlhqpMRrNv9cRXBcgK5YxprjytCVYN0UHdintgYxG\r\nfg7QYiFb00UXxAza1AFrpG+RqySFfO1scmu4kgpeb6A3USnQ0r6rZz6dt6NqgZZ6\r\noUpDOCvnS9XSOWuvJirV8hIU0KAagguTbwfTqt9nt0wDlwZpemsJZ4Vvnvy8d9Jf\r\nzA68EWHbZLr2QP9hb3sHCWJgplMsTJnUwRfi2hf5KNtP8Xg5DSLMfTEbhw==\r\n-----END CERTIFICATE-----\r\n",r=t.certificateFromPem(n,!0);e.equal(r.signatureOid,t.oids["RSASSA-PSS"]),e.equal(r.signatureParameters.hash.algorithmOid,t.oids.sha256),e.equal(r.signatureParameters.mgf.algorithmOid,t.oids.mgf1),e.equal(r.signatureParameters.mgf.hash.algorithmOid,t.oids.sha256),e.equal(r.siginfo.algorithmOid,t.oids["RSASSA-PSS"]),e.equal(r.siginfo.parameters.hash.algorithmOid,t.oids.sha256),e.equal(r.siginfo.parameters.mgf.algorithmOid,t.oids.mgf1),e.equal(r.siginfo.parameters.mgf.hash.algorithmOid,t.oids.sha256)}),it("should export certificate with sha256 RSASSA-PSS signature",function(){var n="-----BEGIN CERTIFICATE-----\r\nMIIERzCCAvugAwIBAgIEO50CcjBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQC\r\nAQUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCASAwRjELMAkGA1UE\r\nBhMCREUxDzANBgNVBAoTBkVsc3RlcjELMAkGA1UECxMCQ0ExGTAXBgNVBAMTEEVs\r\nc3RlclNvZnRUZXN0Q0EwHhcNMTEwNzI4MTIxMzU3WhcNMTQwNzI4MTIxMzU3WjAr\r\nMRQwEgYDVQQFEwsxMDAyNzUzMzI1QzETMBEGA1UEAxMKMTAwMjc1MzMyNTCCASIw\r\nDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALHCogo7LVUkxWsMIc/0jgH2PCLt\r\nukbATPehxWBG1XUPrz53lWgFJzlZaKLlLVVnXrfULaifuOKlZP6SM1JQlL1JuYgY\r\nAdgZyHjderNIk5NsSTmefwonSn/ukri5IRTH420oHtSjxk6+/DXlWnQy5OzTN6Bq\r\njVJo8L+TTmf2jWuEam5cWa+YVP2k3tIqX5yMUDFjKO4znHdtIkHnBE0Kx03rWQRB\r\nTSYWDgDm2gttdOs9JVeuW0nnwQj27uo9gOR0iyaUjVrKLZ95p6zpXhM4uMSVRNeo\r\nLqkdqP2n+4pDXZVqLNgjkHQUS/xq9Q/kYgT2J7wkGfYxP9to7TG7vra1eOECAwEA\r\nAaOB7zCB7DAOBgNVHQ8BAf8EBAMCBSAwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU\r\nNDJ2BZIk6ukLqkdmttH12bu2leswOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2Ny\r\nbC5lbHN0ZXIuZGUvRWxzdGVyU29mdFRlc3RDQS5jcmwwcQYDVR0jBGowaIAU1R9A\r\nHmpdzaxK3v+ihQsEpAFgzOKhSqRIMEYxCzAJBgNVBAYTAkRFMQ8wDQYDVQQKEwZF\r\nbHN0ZXIxDzANBgNVBAsTBlJvb3RDQTEVMBMGA1UEAxMMRWxzdGVyUm9vdENBggQ7\r\nmsqPMEEGCSqGSIb3DQEBCjA0oA8wDQYJYIZIAWUDBAIBBQChHDAaBgkqhkiG9w0B\r\nAQgwDQYJYIZIAWUDBAIBBQCiAwIBIAOCAQEAJBYNRZpe+z3yPPLV539Yci6OfjVg\r\nvs1e3rvSvcpFaqRJ8vZ8WNx3uuRQZ6B4Z3YEc00UJAOl3wU6KhamyryK2YvCrPg+\r\nTS5QDXNaO2z/rAnY1wWSlwBPlhqpMRrNv9cRXBcgK5YxprjytCVYN0UHdintgYxG\r\nfg7QYiFb00UXxAza1AFrpG+RqySFfO1scmu4kgpeb6A3USnQ0r6rZz6dt6NqgZZ6\r\noUpDOCvnS9XSOWuvJirV8hIU0KAagguTbwfTqt9nt0wDlwZpemsJZ4Vvnvy8d9Jf\r\nzA68EWHbZLr2QP9hb3sHCWJgplMsTJnUwRfi2hf5KNtP8Xg5DSLMfTEbhw==\r\n-----END CERTIFICATE-----\r\n",r=t.certificateFromPem(n,!0);e.equal(t.certificateToPem(r),n)}),it("should verify certificate with sha256 RSASSA-PSS signature",function(){var n="-----BEGIN CERTIFICATE-----\r\nMIIERzCCAvugAwIBAgIEO50CcjBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQC\r\nAQUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCASAwRjELMAkGA1UE\r\nBhMCREUxDzANBgNVBAoTBkVsc3RlcjELMAkGA1UECxMCQ0ExGTAXBgNVBAMTEEVs\r\nc3RlclNvZnRUZXN0Q0EwHhcNMTEwNzI4MTIxMzU3WhcNMTQwNzI4MTIxMzU3WjAr\r\nMRQwEgYDVQQFEwsxMDAyNzUzMzI1QzETMBEGA1UEAxMKMTAwMjc1MzMyNTCCASIw\r\nDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALHCogo7LVUkxWsMIc/0jgH2PCLt\r\nukbATPehxWBG1XUPrz53lWgFJzlZaKLlLVVnXrfULaifuOKlZP6SM1JQlL1JuYgY\r\nAdgZyHjderNIk5NsSTmefwonSn/ukri5IRTH420oHtSjxk6+/DXlWnQy5OzTN6Bq\r\njVJo8L+TTmf2jWuEam5cWa+YVP2k3tIqX5yMUDFjKO4znHdtIkHnBE0Kx03rWQRB\r\nTSYWDgDm2gttdOs9JVeuW0nnwQj27uo9gOR0iyaUjVrKLZ95p6zpXhM4uMSVRNeo\r\nLqkdqP2n+4pDXZVqLNgjkHQUS/xq9Q/kYgT2J7wkGfYxP9to7TG7vra1eOECAwEA\r\nAaOB7zCB7DAOBgNVHQ8BAf8EBAMCBSAwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU\r\nNDJ2BZIk6ukLqkdmttH12bu2leswOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2Ny\r\nbC5lbHN0ZXIuZGUvRWxzdGVyU29mdFRlc3RDQS5jcmwwcQYDVR0jBGowaIAU1R9A\r\nHmpdzaxK3v+ihQsEpAFgzOKhSqRIMEYxCzAJBgNVBAYTAkRFMQ8wDQYDVQQKEwZF\r\nbHN0ZXIxDzANBgNVBAsTBlJvb3RDQTEVMBMGA1UEAxMMRWxzdGVyUm9vdENBggQ7\r\nmsqPMEEGCSqGSIb3DQEBCjA0oA8wDQYJYIZIAWUDBAIBBQChHDAaBgkqhkiG9w0B\r\nAQgwDQYJYIZIAWUDBAIBBQCiAwIBIAOCAQEAJBYNRZpe+z3yPPLV539Yci6OfjVg\r\nvs1e3rvSvcpFaqRJ8vZ8WNx3uuRQZ6B4Z3YEc00UJAOl3wU6KhamyryK2YvCrPg+\r\nTS5QDXNaO2z/rAnY1wWSlwBPlhqpMRrNv9cRXBcgK5YxprjytCVYN0UHdintgYxG\r\nfg7QYiFb00UXxAza1AFrpG+RqySFfO1scmu4kgpeb6A3USnQ0r6rZz6dt6NqgZZ6\r\noUpDOCvnS9XSOWuvJirV8hIU0KAagguTbwfTqt9nt0wDlwZpemsJZ4Vvnvy8d9Jf\r\nzA68EWHbZLr2QP9hb3sHCWJgplMsTJnUwRfi2hf5KNtP8Xg5DSLMfTEbhw==\r\n-----END CERTIFICATE-----\r\n",r="-----BEGIN CERTIFICATE-----\r\nMIIEZDCCAxigAwIBAgIEO5rKjzBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQC\r\nAQUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCASAwRjELMAkGA1UE\r\nBhMCREUxDzANBgNVBAoTBkVsc3RlcjEPMA0GA1UECxMGUm9vdENBMRUwEwYDVQQD\r\nEwxFbHN0ZXJSb290Q0EwHhcNMTEwNzI4MTExNzI4WhcNMTYwNzI4MTExNzI4WjBG\r\nMQswCQYDVQQGEwJERTEPMA0GA1UEChMGRWxzdGVyMQswCQYDVQQLEwJDQTEZMBcG\r\nA1UEAxMQRWxzdGVyU29mdFRlc3RDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\r\nAQoCggEBAMFpz3sXnXq4ZUBdYdpG5DJVfITLXYwXPfEJzr1pLRfJ2eMPn7k3B/2g\r\nbvuH30zKmDRjlfV51sFw4l1l+cQugzy5FEOrfE6g7IvhpBUf9SQujVWtE3BoSRR5\r\npSGbIWC7sm2SG0drpoCRpL0xmWZlAUS5mz7hBecJC/kKkKeOxUg5h492XQgWd0ow\r\n6GlyQBxJCmxgQBMnLS0stecs234hR5gvTHic50Ey+gRMPsTyco2Fm0FqvXtBuOsj\r\nzAW7Nk2hnM6awFHVMDBLm+ClE1ww0dLW0ujkdoGsTEbvmM/w8MBI6WAiWaanjK/x\r\nlStmQeUVXKd+AfuzT/FIPrwANcC1qGUCAwEAAaOB8TCB7jAOBgNVHQ8BAf8EBAMC\r\nAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU1R9AHmpdzaxK3v+ihQsE\r\npAFgzOIwNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5lbHN0ZXIuZGUvRWxz\r\ndGVyUm9vdENBLmNybDBxBgNVHSMEajBogBS3zfTokckL2H/fTojW02K+metEcaFK\r\npEgwRjELMAkGA1UEBhMCREUxDzANBgNVBAoTBkVsc3RlcjEPMA0GA1UECxMGUm9v\r\ndENBMRUwEwYDVQQDEwxFbHN0ZXJSb290Q0GCBDuaylowQQYJKoZIhvcNAQEKMDSg\r\nDzANBglghkgBZQMEAgEFAKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgEFAKID\r\nAgEgA4IBAQBjT107fBmSfQNUYCpXIlaS/pogPoCahuC1g8QDtq6IEVXoEgXCzfVN\r\nJYHyYOQOraP4O2LEcXpo/oc0MMpVF06QLKG/KieUE0mrZnsCJ3GLSJkM8tI8bRHj\r\n8tAhlViMacUqnKKufCs/rIN25JB57+sCyFqjtRbSt88e+xqFJ5I79Btp/bNtoj2G\r\nOJGl997EPua9/yJuvdA9W67WO/KwEh+FqylB1pAapccOHqttwu4QJIc/XJfG5rrf\r\n8QZz8HIuOcroA4zIrprQ1nJRCuMII04ueDtBVz1eIEmqNEUkr09JdK8M0LKH0lMK\r\nYsgjai/P2mPVVwhVw6dHzUVRLXh3xIQr\r\n-----END CERTIFICATE-----\r\n",i=t.certificateFromPem(n,!0),s=t.certificateFromPem(r);e.ok(s.verify(i))})})}typeof define=="function"?define("test/x509",["forge/pki","forge/md","forge/util"],function(t,n,r){e(ASSERT,t(),n(),r())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/pki")(),require("../../js/md")(),require("../../js/util")())}(),function(){function e(e,t){var n={privateKey:"-----BEGIN RSA PRIVATE KEY-----\r\nMIICXQIBAAKBgQDL0EugUiNGMWscLAVM0VoMdhDZEJOqdsUMpx9U0YZI7szokJqQ\r\nNIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMGTkP3VF29vXBo+dLq5e+8VyAy\r\nQ3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGtvnM+z0MYDdKo80efzwIDAQAB\r\nAoGAIzkGONi5G+JifmXlLJdplom486p3upf4Ce2/7mqfaG9MnkyPSairKD/JXvfh\r\nNNWkkN8DKKDKBcVVElPgORYT0qwrWc7ueLBMUCbRXb1ZyfEulimG0R3kjUh7NYau\r\nDaIkVgfykXGSQMZx8FoaT6L080zd+0emKDDYRrb+/kgJNJECQQDoUZoiC2K/DWNY\r\nh3/ppZ0ane2y4SBmJUHJVMPQ2CEgxsrJTxet668ckNCKaOP/3VFPoWC41f17DvKq\r\nnoYINNntAkEA4JbZBZBVUrQFhHlrpXT4jzqtO2RlKZzEq8qmFZfEErxOT1WMyyCi\r\nlAQ5gUKardo1Kf0omC8Xq/uO9ZYdED55KwJBALs6cJ65UFaq4oLJiQPzLd7yokuE\r\ndcj8g71PLBTW6jPxIiMFNA89nz3FU9wIVp+xbMNhSoMMKqIPVPC+m0Rn260CQQDA\r\nI83fWK/mZWUjBM33a68KumRiH238v8XyQxj7+C8i6D8G2GXvkigFAehAkb7LZZd+\r\nKLuGFyPlWv3fVWHf99KpAkBQFKk3MRMl6IGJZUEFQe4l5whm8LkGU4acSqv9B3xt\r\nqROkCrsFrMPqjuuzEmyHoQZ64r2PLJg7FOuyhBnQUOt4\r\n-----END RSA PRIVATE KEY-----\r\n",publicKey:"-----BEGIN PUBLIC KEY-----\r\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDL0EugUiNGMWscLAVM0VoMdhDZ\r\nEJOqdsUMpx9U0YZI7szokJqQNIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMG\r\nTkP3VF29vXBo+dLq5e+8VyAyQ3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGt\r\nvnM+z0MYDdKo80efzwIDAQAB\r\n-----END PUBLIC KEY-----\r\n"};describe("csr",function(){it("should generate a certification request",function(){var r={privateKey:t.privateKeyFromPem(n.privateKey),publicKey:t.publicKeyFromPem(n.publicKey)},i=t.createCertificationRequest();i.publicKey=r.publicKey,i.setSubject([{name:"commonName",value:"example.org"},{name:"countryName",value:"US"},{shortName:"ST",value:"Virginia"},{name:"localityName",value:"Blacksburg"},{name:"organizationName",value:"Test"},{shortName:"OU",value:"Test"}]),i.setAttributes([{name:"challengePassword",value:"password"},{name:"unstructuredName",value:"My company"}]),i.sign(r.privateKey);var s=t.certificationRequestToPem(i);i=t.certificationRequestFromPem(s),e.ok(i.verify())})})}typeof define=="function"?define("test/csr",["forge/pki"],function(t){e(ASSERT,t())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/pki")())}(),function(){function e(e,t,n){describe("aes",function(){it("should encrypt a single block with a 128-bit key",function(){var r=[66051,67438087,134810123,202182159],i=[1122867,1146447479,2291772091,3437096703],s=[],o=t._expandKey(r,!1);t._updateBlock(o,i,s,!1);var u=n.createBuffer();u.putInt32(s[0]),u.putInt32(s[1]),u.putInt32(s[2]),u.putInt32(s[3]),e.equal(u.toHex(),"69c4e0d86a7b0430d8cdb78070b4c55a")}),it("should decrypt a single block with a 128-bit key",function(){var r=[66051,67438087,134810123,202182159],i=[1774510296,1786446896,3637360512,1890895194],s=[],o=t._expandKey(r,!0);t._updateBlock(o,i,s,!0);var u=n.createBuffer();u.putInt32(s[0]),u.putInt32(s[1]),u.putInt32(s[2]),u.putInt32(s[3]),e.equal(u.toHex(),"00112233445566778899aabbccddeeff")}),it("should encrypt a single block with a 192-bit key",function(){var r=[66051,67438087,134810123,202182159,269554195,336926231],i=[1122867,1146447479,2291772091,3437096703],s=[],o=t._expandKey(r,!1);t._updateBlock(o,i,s,!1);var u=n.createBuffer();u.putInt32(s[0]),u.putInt32(s[1]),u.putInt32(s[2]),u.putInt32(s[3]),e.equal(u.toHex(),"dda97ca4864cdfe06eaf70a0ec0d7191")}),it("should decrypt a single block with a 192-bit key",function(){var r=[66051,67438087,134810123,202182159,269554195,336926231],i=[3718872228,2253184992,1856991392,3960304017],s=[],o=t._expandKey(r,!0);t._updateBlock(o,i,s,!0);var u=n.createBuffer();u.putInt32(s[0]),u.putInt32(s[1]),u.putInt32(s[2]),u.putInt32(s[3]),e.equal(u.toHex(),"00112233445566778899aabbccddeeff")}),it("should encrypt a single block with a 256-bit key",function(){var r=[66051,67438087,134810123,202182159,269554195,336926231,404298267,471670303],i=[1122867,1146447479,2291772091,3437096703],s=[],o=t._expandKey(r,!1);t._updateBlock(o,i,s,!1);var u=n.createBuffer();u.putInt32(s[0]),u.putInt32(s[1]),u.putInt32(s[2]),u.putInt32(s[3]),e.equal(u.toHex(),"8ea2b7ca516745bfeafc49904b496089")}),it("should decrypt a single block with a 256-bit key",function(){var r=[66051,67438087,134810123,202182159,269554195,336926231,404298267,471670303],i=[2393028554,1365722559,3942402448,1263100041],s=[],o=t._expandKey(r,!0);t._updateBlock(o,i,s,!0);var u=n.createBuffer();u.putInt32(s[0]),u.putInt32(s[1]),u.putInt32(s[2]),u.putInt32(s[3]),e.equal(u.toHex(),"00112233445566778899aabbccddeeff")}),function(){var r=["06a9214036b8a15b512e03d534120006","c286696d887c9aa0611bbb3e2025a45a","6c3ea0477630ce21a2ce334aa746c2cd","56e47a38c5598974bc46903dba290349"],i=["3dafba429d9eb430b422da802c9fac41","562e17996d093d28ddb3ba695a2e6f58","c782dc4c098c66cbd9cd27d825682c81","8ce82eefbea0da3c44699ed7db51b7d9"],s=["Single block msg","000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","This is a 48-byte message (exactly 3 AES blocks)","a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedf"],o=["e353779c1079aeb82708942dbe77181a","d296cd94c2cccf8a3a863028b5e1dc0a7586602d253cfff91b8266bea6d61ab1","d0a02b3836451753d493665d33f0e8862dea54cdb293abc7506939276772f8d5021c19216bad525c8579695d83ba2684","c30e32ffedc0774e6aff6af0869f71aa0f3af07a9a31a9c684db207eb0ef8e4e35907aa632c3ffdf868bb7b29d3d46ad83ce9f9a102ee99d49a53e87f4c3da55"];for(var u=0;u<r.length;++u)(function(u){var a=n.hexToBytes(r[u]),f=n.hexToBytes(i[u]),l=u&1?n.hexToBytes(s[u]):s[u],c=n.hexToBytes(o[u]);it("should aes-128-cbc encrypt: "+s[u],function(){var r=t.createEncryptionCipher(a,"CBC");r.start(f),r.update(n.createBuffer(l)),r.finish(function(){return!0}),e.equal(r.output.toHex(),o[u])}),it("should aes-128-cbc decrypt: "+o[u],function(){var r=t.createDecryptionCipher(a,"CBC");r.start(f),r.update(n.createBuffer(c)),r.finish(function(){return!0});var i=u&1?r.output.toHex():r.output.bytes();e.equal(i,s[u])})})(u)}(),function(){var r=["00000000000000000000000000000000","2b7e151628aed2a6abf7158809cf4f3c","2b7e151628aed2a6abf7158809cf4f3c","2b7e151628aed2a6abf7158809cf4f3c","2b7e151628aed2a6abf7158809cf4f3c","00000000000000000000000000000000"],i=["80000000000000000000000000000000","000102030405060708090a0b0c0d0e0f","3B3FD92EB72DAD20333449F8E83CFB4A","C8A64537A0B3A93FCDE3CDAD9F1CE58B","26751F67A3CBB140B1808CF187A4F4DF","60f9ff04fac1a25657bf5b36b5efaf75"],s=["00000000000000000000000000000000","6bc1bee22e409f96e93d7e117393172a","ae2d8a571e03ac9c9eb76fac45af8e51","30c81c46a35ce411e5fbc1191a0a52ef","f69f2445df4f9b17ad2b417be66c3710","This is a 48-byte message (exactly 3 AES blocks)"],o=["3ad78e726c1ec02b7ebfe92b23d9ec34","3b3fd92eb72dad20333449f8e83cfb4a","c8a64537a0b3a93fcde3cdad9f1ce58b","26751f67a3cbb140b1808cf187a4f4df","c04b05357c5d1c0eeac4c66f9ff7f2e6","52396a2ba1ba420c5e5b699a814944d8f4e7fbf984a038319fbc0b4ee45cfa6f07b2564beab5b5e92dbd44cb345f49b4"];for(var u=0;u<r.length;++u)(function(u){var a=n.hexToBytes(r[u]),f=n.hexToBytes(i[u]),l=u!==5?n.hexToBytes(s[u]):s[u],c=n.hexToBytes(o[u]);it("should aes-128-cfb encrypt: "+s[u],function(){var r=t.createEncryptionCipher(a,"CFB");r.start(f),r.update(n.createBuffer(l)),r.finish(),e.equal(r.output.toHex(),o[u])}),it("should aes-128-cfb decrypt: "+o[u],function(){var r=t.createDecryptionCipher(a,"CFB");r.start(f),r.update(n.createBuffer(c)),r.finish();var i=u!==5?r.output.toHex():r.output.getBytes();e.equal(i,s[u])})})(u)}(),function(){var r=["00000000000000000000000000000000","00000000000000000000000000000000"],i=["80000000000000000000000000000000","c8ca0d6a35dbeac776e911ee16bea7d3"],s=["00000000000000000000000000000000","This is a 48-byte message (exactly 3 AES blocks)"],o=["3ad78e726c1ec02b7ebfe92b23d9ec34","39c0190727a76b2a90963426f63689cfcdb8a2be8e20c5e877a81a724e3611f62ecc386f2e941b2441c838906002be19"];for(var u=0;u<r.length;++u)(function(u){var a=n.hexToBytes(r[u]),f=n.hexToBytes(i[u]),l=u!==1?n.hexToBytes(s[u]):s[u],c=n.hexToBytes(o[u]);it("should aes-128-ofb encrypt: "+s[u],function(){var r=t.createEncryptionCipher(a,"OFB");r.start(f),r.update(n.createBuffer(l)),r.finish(),e.equal(r.output.toHex(),o[u])}),it("should aes-128-ofb decrypt: "+o[u],function(){var r=t.createDecryptionCipher(a,"OFB");r.start(f),r.update(n.createBuffer(c)),r.finish();var i=u!==1?r.output.toHex():r.output.getBytes();e.equal(i,s[u])})})(u)}(),function(){var r=["2b7e151628aed2a6abf7158809cf4f3c","00000000000000000000000000000000"],i=["f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff","650cdb80ff9fc758342d2bd99ee2abcf"],s=["6bc1bee22e409f96e93d7e117393172a","This is a 48-byte message (exactly 3 AES blocks)"],o=["874d6191b620e3261bef6864990db6ce","5ede11d00e9a76ec1d5e7e811ea3dd1ce09ee941210f825d35718d3282796f1c07c3f1cb424f2b365766ab5229f5b5a4"];for(var u=0;u<r.length;++u)(function(u){var a=n.hexToBytes(r[u]),f=n.hexToBytes(i[u]),l=u!==1?n.hexToBytes(s[u]):s[u],c=n.hexToBytes(o[u]);it("should aes-128-ctr encrypt: "+s[u],function(){var r=t.createEncryptionCipher(a,"CTR");r.start(f),r.update(n.createBuffer(l)),r.finish(),e.equal(r.output.toHex(),o[u])}),it("should aes-128-ctr decrypt: "+o[u],function(){var r=t.createDecryptionCipher(a,"CTR");r.start(f),r.update(n.createBuffer(c)),r.finish();var i=u!==1?r.output.toHex():r.output.getBytes();e.equal(i,s[u])})})(u)}(),function(){var r=["861009ec4d599fab1f40abc76e6f89880cff5833c79c548c99f9045f191cd90b"],i=["d927ad81199aa7dcadfdb4e47b6dc694"],s=["MY-DATA-AND-HERE-IS-MORE-DATA"],o=["80eb666a9fc9e263faf71e87ffc94451d7d8df7cfcf2606470351dd5ac"];for(var u=0;u<r.length;++u)(function(u){var a=n.hexToBytes(r[u]),f=n.hexToBytes(i[u]),l=s[u],c=n.hexToBytes(o[u]);it("should aes-256-cfb encrypt: "+s[u],function(){var r=t.createEncryptionCipher(a,"CFB");r.start(f),r.update(n.createBuffer(l)),r.finish(),e.equal(r.output.toHex(),o[u])}),it("should aes-256-cfb decrypt: "+o[u],function(){var r=t.createDecryptionCipher(a,"CFB");r.start(f),r.update(n.createBuffer(c)),r.finish();var i=r.output.getBytes();e.equal(i,s[u])})})(u)}()})}typeof define=="function"?define("test/aes",["forge/aes","forge/util"],function(t,n){e(ASSERT,t(),n())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/aes")(),require("../../js/util")())}(),function(){function e(e,t,n){describe("rc2",function(){it("should expand a 128-bit key",function(){var r=n.hexToBytes("88bca90e90875a7f0f79c384627bafb2"),i="71ab26462f0b9333609d4476e48ab72438c2194b70a47085d84b6af1dc72119023b94fe80aee2b6b45f27f923d9be1570da3ce8b16ad7f78db166ffbc28a836a4392cf0b748085dae4b69bdc2a4679cdfc09d84317016987e0c5b765c91dc612b1f44d7921b3e2c46447508bd2ac02e119e0f42a89c719675da320cf3e8958cd";e.equal(t.expandKey(r).toHex(),i)}),it("should expand a 40-bit key",function(){var r=n.hexToBytes("88bca90e90"),i="af136d2243b94a0878d7a604f8d6d9fd64a698fd6ebc613e641f0d1612055ef6cb55966db8f32bfd9246dae99880be8a91433adf54ea546d9daad62db7a55f6c7790aa87ba67de0e9ea9128dfc7ccdddd7c47c33d2bb7f823729977f083b5dc1f5bb09000b98e12cdaaf22f80dcc88c37d2c2fd80402f8a30a9e41d356669471";e.equal(t.expandKey(r,40).toHex(),i)}),it("should rc2-ecb encrypt zeros",function(){var r=n.hexToBytes("88bca90e90875a7f0f79c384627bafb2"),i=(new n.createBuffer).fillWithByte(0,8),s=t.startEncrypting(r,null,null);s.update(i),s.finish(),e.equal(s.output.toHex(),"2269552ab0f85ca6e35b3b2ce4e02191")}),it("should rc2-ecb encrypt: vegan",function(){var r=n.hexToBytes("88bca90e90875a7f0f79c384627bafb2"),i=new n.createBuffer("vegan"),s=t.startEncrypting(r,null,null);s.update(i),s.finish(),e.equal(s.output.toHex(),"2194adaf4d517e3a")}),it("should rc2-ecb decrypt: 2194adaf4d517e3a",function(){var r=n.hexToBytes("88bca90e90875a7f0f79c384627bafb2"),i=new n.createBuffer(n.hexToBytes("2194adaf4d517e3a")),s=t.startDecrypting(r,null,null);s.update(i),s.finish(),e.equal(s.output.getBytes(),"vegan")}),it("should rc2-cbc encrypt: revolution",function(){var r=n.hexToBytes("88bca90e90875a7f0f79c384627bafb2"),i=new n.createBuffer(n.hexToBytes("0123456789abcdef")),s=new n.createBuffer("revolution"),o=t.startEncrypting(r,i,null);o.update(s),o.finish(),e.equal(o.output.toHex(),"50cfd16e0fd7f20b17a622eb2a469b7e")}),it("should rc2-cbc decrypt: 50cfd16e0fd7f20b17a622eb2a469b7e",function(){var r=n.hexToBytes("88bca90e90875a7f0f79c384627bafb2"),i=new n.createBuffer(n.hexToBytes("0123456789abcdef")),s=new n.createBuffer(n.hexToBytes("50cfd16e0fd7f20b17a622eb2a469b7e")),o=t.startDecrypting(r,i,null);o.update(s),o.finish(),e.equal(o.output,"revolution")})})}typeof define=="function"?define("test/rc2",["forge/rc2","forge/util"],function(t,n){e(ASSERT,t(),n())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/rc2")(),require("../../js/util")())}(),function(){function e(e,t,n){describe("des",function(){it("should des-ecb encrypt: foobar",function(){var r=new n.createBuffer(n.hexToBytes("a1c06b381adf3651")),i=t.startEncrypting(r,null,null);i.update(n.createBuffer("foobar")),i.finish(),e.equal(i.output.toHex(),"b705ffcf3dff06b3")}),it("should des-ecb decrypt: b705ffcf3dff06b3",function(){var r=new n.createBuffer(n.hexToBytes("a1c06b381adf3651")),i=t.startDecrypting(r,null,null);i.update(n.createBuffer(n.hexToBytes("b705ffcf3dff06b3"))),i.finish(),e.equal(i.output.getBytes(),"foobar")}),it("should des-cbc encrypt: foobar",function(){var r=new n.createBuffer(n.hexToBytes("a1c06b381adf3651")),i=new n.createBuffer(n.hexToBytes("818bcf76efc59662")),s=t.startEncrypting(r,i,null);s.update(n.createBuffer("foobar")),s.finish(),e.equal(s.output.toHex(),"3261e5839a990454")}),it("should des-cbc decrypt: 3261e5839a990454",function(){var r=new n.createBuffer(n.hexToBytes("a1c06b381adf3651")),i=new n.createBuffer(n.hexToBytes("818bcf76efc59662")),s=t.startDecrypting(r,i,null);s.update(n.createBuffer(n.hexToBytes("3261e5839a990454"))),s.finish(),e.equal(s.output.getBytes(),"foobar")}),it("should 3des-ecb encrypt: foobar",function(){var r=new n.createBuffer(n.hexToBytes("a1c06b381adf36517e84575552777779da5e3d9f994b05b5")),i=t.startEncrypting(r,null,null);i.update(n.createBuffer("foobar")),i.finish(),e.equal(i.output.toHex(),"fce8b1ee8c6440d1")}),it("should 3des-ecb decrypt: fce8b1ee8c6440d1",function(){var r=new n.createBuffer(n.hexToBytes("a1c06b381adf36517e84575552777779da5e3d9f994b05b5")),i=t.startDecrypting(r,null,null);i.update(n.createBuffer(n.hexToBytes("fce8b1ee8c6440d1"))),i.finish(),e.equal(i.output.getBytes(),"foobar")}),it('should 3des-cbc encrypt "foobar", restart, and encrypt "foobar,,"',function(){var r=new n.createBuffer(n.hexToBytes("a1c06b381adf36517e84575552777779da5e3d9f994b05b5")),i=new n.createBuffer(n.hexToBytes("818bcf76efc59662")),s=t.startEncrypting(r,i.copy(),null);s.update(n.createBuffer("foobar")),s.finish(),e.equal(s.output.toHex(),"209225f7687ca0b2"),s.start(i.copy()),s.update(n.createBuffer("foobar,,")),s.finish(),e.equal(s.output.toHex(),"57156174c48dfc37293831bf192a6742")}),it('should 3des-cbc decrypt "209225f7687ca0b2", restart, and decrypt "57156174c48dfc37293831bf192a6742,,"',function(){var r=new n.createBuffer(n.hexToBytes("a1c06b381adf36517e84575552777779da5e3d9f994b05b5")),i=new n.createBuffer(n.hexToBytes("818bcf76efc59662")),s=t.startDecrypting(r,i.copy(),null);s.update(n.createBuffer(n.hexToBytes("209225f7687ca0b2"))),s.finish(),e.equal(s.output.getBytes(),"foobar"),s.start(i.copy()),s.update(n.createBuffer(n.hexToBytes("57156174c48dfc37293831bf192a6742"))),s.finish(),e.equal(s.output.getBytes(),"foobar,,")})})}typeof define=="function"?define("test/des",["forge/des","forge/util"],function(t,n){e(ASSERT,t(),n())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/des")(),require("../../js/util")())}(),function(){function e(e){var t=e.asn1,n=e.pkcs7=e.pkcs7||{};n.messageFromPem=function(r){var i=e.pem.decode(r)[0];if(i.type!=="PKCS7")throw{message:'Could not convert PKCS#7 message from PEM; PEM header type is not "PKCS#7".',headerType:i.type};if(i.procType&&i.procType.type==="ENCRYPTED")throw{message:"Could not convert PKCS#7 message from PEM; PEM is encrypted."};var s=t.fromDer(i.body);return n.messageFromAsn1(s)},n.messageToPem=function(n,r){var i={type:"PKCS7",body:t.toDer(n.toAsn1()).getBytes()};return e.pem.encode(i,{maxline:r})},n.messageFromAsn1=function(r){var i={},s=[];if(!t.validate(r,n.asn1.contentInfoValidator,i,s))throw{message:"Cannot read PKCS#7 message. ASN.1 object is not an PKCS#7 ContentInfo.",errors:s};var o=t.derToOid(i.contentType),u;switch(o){case e.pki.oids.envelopedData:u=n.createEnvelopedData();break;case e.pki.oids.encryptedData:u=n.createEncryptedData();break;case e.pki.oids.signedData:u=n.createSignedData();break;default:throw{message:"Cannot read PKCS#7 message. ContentType with OID "+o+" is not (yet) supported."}}return u.fromAsn1(i.content.value[0]),u};var r=function(r){var i={},s=[];if(!t.validate(r,n.asn1.recipientInfoValidator,i,s))throw{message:"Cannot read PKCS#7 message. ASN.1 object is not an PKCS#7 EnvelopedData.",errors:s};return{version:i.version.charCodeAt(0),issuer:e.pki.RDNAttributesAsArray(i.issuer),serialNumber:e.util.createBuffer(i.serial).toHex(),encContent:{algorithm:t.derToOid(i.encAlgorithm),parameter:i.encParameter.value,content:i.encKey}}},i=function(n){return t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,String.fromCharCode(n.version)),t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[e.pki.distinguishedNameToAsn1({attributes:n.issuer}),t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,e.util.hexToBytes(n.serialNumber))]),t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.encContent.algorithm).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.NULL,!1,"")]),t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,n.encContent.content)])},s=function(e){var t=[];for(var n=0;n<e.length;n++)t.push(r(e[n]));return t},o=function(e){var t=[];for(var n=0;n<e.length;n++)t.push(i(e[n]));return t},u=function(n){return[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(e.pki.oids.data).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.algorithm).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,n.parameter.getBytes())]),t.create(t.Class.CONTEXT_SPECIFIC,0,!0,[t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,n.content.getBytes())])]},a=function(n,r,i){var s={},o=[];if(!t.validate(r,i,s,o))throw{message:"Cannot read PKCS#7 message. ASN.1 object is not an PKCS#7 EnvelopedData.",errors:o};var u=t.derToOid(s.contentType);if(u!==e.pki.oids.data)throw{message:"Unsupported PKCS#7 message. Only contentType Data supported within EnvelopedData."};if(s.encContent){var a="";if(e.util.isArray(s.encContent))for(var f=0;f<s.encContent.length;++f){if(s.encContent[f].type!==t.Type.OCTETSTRING)throw{message:"Malformed PKCS#7 message, expecting encrypted content constructed of only OCTET STRING objects."};a+=s.encContent[f].value}else a=s.encContent;n.encContent={algorithm:t.derToOid(s.encAlgorithm),parameter:e.util.createBuffer(s.encParameter.value),content:e.util.createBuffer(a)}}if(s.content){var a="";if(e.util.isArray(s.content))for(var f=0;f<s.content.length;++f){if(s.content[f].type!==t.Type.OCTETSTRING)throw{message:"Malformed PKCS#7 message, expecting content constructed of only OCTET STRING objects."};a+=s.content[f].value}else a=s.content;n.content=e.util.createBuffer(a)}return n.version=s.version.charCodeAt(0),n.rawCapture=s,s},f=function(t){if(t.encContent.key===undefined)throw{message:"Symmetric key not available."};if(t.content===undefined){var n;switch(t.encContent.algorithm){case e.pki.oids["aes128-CBC"]:case e.pki.oids["aes192-CBC"]:case e.pki.oids["aes256-CBC"]:n=e.aes.createDecryptionCipher(t.encContent.key);break;case e.pki.oids["des-EDE3-CBC"]:n=e.des.createDecryptionCipher(t.encContent.key);break;default:throw{message:"Unsupported symmetric cipher, OID "+t.encContent.algorithm}}n.start(t.encContent.parameter),n.update(t.encContent.content);if(!n.finish())throw{message:"Symmetric decryption failed."};t.content=n.output}};n.createSignedData=function(){var t=null;return t={type:e.pki.oids.signedData,version:1,fromAsn1:function(e){a(t,e,n.asn1.signedDataValidator)}},t},n.createEncryptedData=function(){var t=null;return t={type:e.pki.oids.encryptedData,version:0,encContent:{algorithm:e.pki.oids["aes256-CBC"]},fromAsn1:function(e){a(t,e,n.asn1.encryptedDataValidator)},decrypt:function(e){e!==undefined&&(t.encContent.key=e),f(t)}},t},n.createEnvelopedData=function(){var r=null;return r={type:e.pki.oids.envelopedData,version:0,recipients:[],encContent:{algorithm:e.pki.oids["aes256-CBC"]},fromAsn1:function(e){var t=a(r,e,n.asn1.envelopedDataValidator);r.recipients=s(t.recipientInfos.value)},toAsn1:function(){return t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(r.type).getBytes()),t.create(t.Class.CONTEXT_SPECIFIC,0,!0,[t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,String.fromCharCode(r.version)),t.create(t.Class.UNIVERSAL,t.Type.SET,!0,o(r.recipients)),t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,u(r.encContent))])])])},findRecipient:function(e){var t=e.issuer.attributes;for(var n=0;n<r.recipients.length;++n){var i=r.recipients[n],s=i.issuer;if(i.serialNumber!==e.serialNumber)continue;if(s.length!==t.length)continue;var o=!0;for(var u=0;u<t.length;++u)if(s[u].type!==t[u].type||s[u].value!==t[u].value){o=!1;break}if(o)return i}return null},decrypt:function(t,n){if(r.encContent.key===undefined&&t!==undefined&&n!==undefined)switch(t.encContent.algorithm){case e.pki.oids.rsaEncryption:var i=n.decrypt(t.encContent.content);r.encContent.key=e.util.createBuffer(i);break;default:throw{message:"Unsupported asymmetric cipher, OID "+t.encContent.algorithm}}f(r)},addRecipient:function(t){r.recipients.push({version:0,issuer:t.subject.attributes,serialNumber:t.serialNumber,encContent:{algorithm:e.pki.oids.rsaEncryption,key:t.publicKey}})},encrypt:function(t,n){if(r.encContent.content===undefined){n=n||r.encContent.algorithm,t=t||r.encContent.key;var i,s,o;switch(n){case e.pki.oids["aes128-CBC"]:i=16,s=16,o=e.aes.createEncryptionCipher;break;case e.pki.oids["aes192-CBC"]:i=24,s=16,o=e.aes.createEncryptionCipher;break;case e.pki.oids["aes256-CBC"]:i=32,s=16,o=e.aes.createEncryptionCipher;break;case e.pki.oids["des-EDE3-CBC"]:i=24,s=8,o=e.des.createEncryptionCipher;break;default:throw{message:"Unsupported symmetric cipher, OID "+n}}if(t===undefined)t=e.util.createBuffer(e.random.getBytes(i));else if(t.length()!=i)throw{message:"Symmetric key has wrong length, got "+t.length()+" bytes, expected "+i};r.encContent.algorithm=n,r.encContent.key=t,r.encContent.parameter=e.util.createBuffer(e.random.getBytes(s));var u=o(t);u.start(r.encContent.parameter.copy()),u.update(r.content);if(!u.finish())throw{message:"Symmetric encryption failed."};r.encContent.content=u.output}for(var a=0;a<r.recipients.length;a++){var f=r.recipients[a];if(f.encContent.content!==undefined)continue;switch(f.encContent.algorithm){case e.pki.oids.rsaEncryption:f.encContent.content=f.encContent.key.encrypt(r.encContent.key.data);break;default:throw{message:"Unsupported asymmetric cipher, OID "+f.encContent.algorithm}}}}},r}}var t="pkcs7";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/pkcs7",["require","module","./aes","./asn1","./des","./pem","./pkcs7asn1","./pki","./random","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e,t,n,r,i,s){var o={p7:"-----BEGIN PKCS7-----\r\nMIICTgYJKoZIhvcNAQcDoIICPzCCAjsCAQAxggHGMIIBwgIBADCBqTCBmzELMAkG\r\nA1UEBhMCREUxEjAQBgNVBAgMCUZyYW5jb25pYTEQMA4GA1UEBwwHQW5zYmFjaDEV\r\nMBMGA1UECgwMU3RlZmFuIFNpZWdsMRIwEAYDVQQLDAlHZWllcmxlaW4xFjAUBgNV\r\nBAMMDUdlaWVybGVpbiBERVYxIzAhBgkqhkiG9w0BCQEWFHN0ZXNpZUBicm9rZW5w\r\naXBlLmRlAgkA1FQcQNg14vMwDQYJKoZIhvcNAQEBBQAEggEAJhWQz5SniCd1w3A8\r\nuKVZEfc8Tp21I7FMfFqou+UOVsZCq7kcEa9uv2DIj3o7zD8wbLK1fuyFi4SJxTwx\r\nkR0a6V4bbonIpXPPJ1f615dc4LydAi2tv5w14LJ1Js5XCgGVnkAmQHDaW3EHXB7X\r\nT4w9PR3+tcS/5YAnWaM6Es38zCKHd7TnHpuakplIkwSK9rBFAyA1g/IyTPI+ktrE\r\nEHcVuJcz/7eTlF6wJEa2HL8F1TVWuL0p/0GsJP/8y0MYGdCdtr+TIVo//3YGhoBl\r\nN4tnheFT/jRAzfCZtflDdgAukW24CekrJ1sG2M42p5cKQ5rGFQtzNy/n8EjtUutO\r\nHD5YITBsBgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBBmlpfy3WrYj3uWW7+xNEiH\r\ngEAm2mfSF5xFPLEqqFkvKTM4w8PfhnF0ehmfQNApvoWQRQanNWLCT+Q9GHx6DCFj\r\nTUHl+53x88BrCl1E7FhYPs92\r\n-----END PKCS7-----\r\n",certificate:"-----BEGIN CERTIFICATE-----\r\nMIIDtDCCApwCCQDUVBxA2DXi8zANBgkqhkiG9w0BAQUFADCBmzELMAkGA1UEBhMC\r\nREUxEjAQBgNVBAgMCUZyYW5jb25pYTEQMA4GA1UEBwwHQW5zYmFjaDEVMBMGA1UE\r\nCgwMU3RlZmFuIFNpZWdsMRIwEAYDVQQLDAlHZWllcmxlaW4xFjAUBgNVBAMMDUdl\r\naWVybGVpbiBERVYxIzAhBgkqhkiG9w0BCQEWFHN0ZXNpZUBicm9rZW5waXBlLmRl\r\nMB4XDTEyMDMxODIyNTc0M1oXDTEzMDMxODIyNTc0M1owgZsxCzAJBgNVBAYTAkRF\r\nMRIwEAYDVQQIDAlGcmFuY29uaWExEDAOBgNVBAcMB0Fuc2JhY2gxFTATBgNVBAoM\r\nDFN0ZWZhbiBTaWVnbDESMBAGA1UECwwJR2VpZXJsZWluMRYwFAYDVQQDDA1HZWll\r\ncmxlaW4gREVWMSMwIQYJKoZIhvcNAQkBFhRzdGVzaWVAYnJva2VucGlwZS5kZTCC\r\nASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMsAbQ4fWevHqP1K1y/ewpMS\r\n3vYovBto7IsKBq0v3NmC2kPf3NhyaSKfjOOS5uAPONLffLck+iGdOLLFia6OSpM6\r\n0tyQIV9lHoRh7fOEYORab0Z+aBUZcEGT9yotBOraX1YbKc5f9XO+80eG4XYvb5ua\r\n1NHrxWqe4w2p3zGJCKO+wHpvGkbKz0nfu36jwWz5aihfHi9hp/xs8mfH86mIKiD7\r\nf2X2KeZ1PK9RvppA0X3lLb2VLOqMt+FHWicyZ/wjhQZ4oW55ln2yYJUQ+adlgaYn\r\nPrtnsxmbTxM+99oF0F2/HmGrNs8nLZSva1Vy+hmjmWz6/O8ZxhiIj7oBRqYcAocC\r\nAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAvfvtu31GFBO5+mFjPAoR4BlzKq/H3EPO\r\nqS8cm/TjHgDRALwSnwKYCFs/bXqE4iOTD6otV4TusX3EPbqL2vzZQEcZn6paU/oZ\r\nZVXwQqMqY5tf2teQiNxqxNmSIEPRHOr2QVBVIx2YF4Po89KGUqJ9u/3/10lDqRwp\r\nsReijr5UKv5aygEcnwcW8+Ne4rTx934UDsutKG20dr5trZfWQRVS9fS9CFwJehEX\r\nHAMUc/0++80NhfQthmWZWlWM1R3dr4TrIPtWdn5z0MtGeDvqBk7HjGrhcVS6kAsy\r\nZ9y/lfLPjBuxlQAHztEJCWgI4TW3/RLhgfg2gI1noM2n84Cdmisfkg==\r\n-----END CERTIFICATE-----\r\n",privateKey:"-----BEGIN RSA PRIVATE KEY-----\r\nMIIEowIBAAKCAQEAywBtDh9Z68eo/UrXL97CkxLe9ii8G2jsiwoGrS/c2YLaQ9/c\r\n2HJpIp+M45Lm4A840t98tyT6IZ04ssWJro5KkzrS3JAhX2UehGHt84Rg5FpvRn5o\r\nFRlwQZP3Ki0E6tpfVhspzl/1c77zR4bhdi9vm5rU0evFap7jDanfMYkIo77Aem8a\r\nRsrPSd+7fqPBbPlqKF8eL2Gn/GzyZ8fzqYgqIPt/ZfYp5nU8r1G+mkDRfeUtvZUs\r\n6oy34UdaJzJn/COFBnihbnmWfbJglRD5p2WBpic+u2ezGZtPEz732gXQXb8eYas2\r\nzyctlK9rVXL6GaOZbPr87xnGGIiPugFGphwChwIDAQABAoIBAAjMA+3QvfzRsikH\r\nzTtt09C7yJ2yNjSZ32ZHEPMAV/m1CfBXCyL2EkhF0b0q6IZdIoFA3g6xs4UxYvuc\r\nQ9Mkp2ap7elQ9aFEqIXkGIOtAOXkZV4QrEH90DeHSfax7LygqfD5TF59Gg3iAHjh\r\nB3Qvqg58LyzJosx0BjLZYaqr3Yv67GkqyflpF/roPGdClHpahAi5PBkHiNhNTAUU\r\nLJRGvMegXGZkUKgGMAiGCk0N96OZwrinMKO6YKGdtgwVWC2wbJY0trElaiwXozSt\r\nNmP6KTQp94C7rcVO6v1lZiOfhBe5Kc8QHUU+GYydgdjqm6Rdow/yLHOALAVtXSeb\r\nU+tPfcECgYEA6Qi+qF+gtPincEDBxRtoKwAlRkALt8kly8bYiGcUmd116k/5bmPw\r\nd0tBUOQbqRa1obYC88goOVzp9LInAcBSSrexhVaPAF4nrkwYXMOq+76MiH17WUfQ\r\nMgVM2IB48PBjNk1s3Crj6j1cxxkctqmCnVaI9HlU2PPZ3xjaklfv/NsCgYEA3wH8\r\nmehUhiAp7vuhd+hfomFw74cqgHC9v0saiYGckpMafh9MJGc4U5GrN1kYeb/CFkSx\r\n1hOytD3YBKoaKKoYagaMQcjxf6HnEF0f/5OiQkUQpWmgC9lNnE4XTWjnwqaTS5L9\r\nD+H50SiI3VjHymGXTRJeKpAIwV74AxxrnVofqsUCgYAwmL1B2adm9g/c7fQ6yatg\r\nhEhBrSuEaTMzmsUfNPfr2m4zrffjWH4WMqBtYRSPn4fDMHTPJ+eThtfXSqutxtCi\r\nekpP9ywdNIVr6LyP49Ita6Bc+mYVyU8Wj1pmL+yIumjGM0FHbL5Y4/EMKCV/xjvR\r\n2fD3orHaCIhf6QvzxtjqTwKBgFm6UemXKlMhI94tTsWRMNGEBU3LA9XUBvSuAkpr\r\nZRUwrQssCpXnFinBxbMqXQe3mR8emrM5D8En1P/jdU0BS3t1kP9zG4AwI2lZHuPV\r\nggbKBS2Y9zVtRKXsYcHawM13+nIA/WNjmAGJHrB45UJPy/HNvye+9lbfoEiYKdCR\r\nD4bFAoGBAIm9jcZkIwLa9kLAWH995YYYSGRY4KC29XZr2io2mog+BAjhFt1sqebt\r\nR8sRHNiIP2mcUECMOcaS+tcayi+8KTHWxIEed9qDmFu6XBbePfe/L6yxPSagcixH\r\nBK0KuK/fgTPvZCmIs8hUIC+AxhXKnqn4fIWoO54xLsALc0gEjs2d\r\n-----END RSA PRIVATE KEY-----\r\n",encryptedData:"-----BEGIN PKCS7-----\r\nMIGHBgkqhkiG9w0BBwagejB4AgEAMHMGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQI\r\nupMFou5X3DWAUAqObuHSlewM0ZtHzWk9MAmtYb7MSb//OBMKVfLCdbmrS5BpKm9J\r\ngzwiDR5Od7xgfkqasLS2lOdKAvJ5jZjjTpAyrjBKpShqK9gtXDuO0zH+\r\n-----END PKCS7-----\r\n",p7IndefiniteLength:"-----BEGIN PKCS7-----\r\nMIAGCSqGSIb3DQEHA6CAMIACAQAxggHGMIIBwgIBADCBqTCBmzELMAkGA1UEBhMC\r\nREUxEjAQBgNVBAgMCUZyYW5jb25pYTEQMA4GA1UEBwwHQW5zYmFjaDEVMBMGA1UE\r\nCgwMU3RlZmFuIFNpZWdsMRIwEAYDVQQLDAlHZWllcmxlaW4xFjAUBgNVBAMMDUdl\r\naWVybGVpbiBERVYxIzAhBgkqhkiG9w0BCQEWFHN0ZXNpZUBicm9rZW5waXBlLmRl\r\nAgkA1FQcQNg14vMwDQYJKoZIhvcNAQEBBQAEggEAlWCH+E25c4jfff+m0eAxxMmE\r\nWWaftdsk4ZpAVAr7HsvxJ35bj1mhwTh7rBTg929JBKt6ZaQ4I800jCNxD2O40V6z\r\nlB7JNRqzgBwfeuU2nV6FB7v1984NBi1jQx6EfxOcusE6RL/63HqJdFbmq3Tl55gF\r\ndm3JdjmHbCXqwPhuwOXU4yhkpV1RJcrYhPLe3OrLAH7ZfoE0nPJPOX9HPTZ6ReES\r\nNToS7I9D9k7rCa8fAP7pgjO96GJGBtCHG1VXB9NX4w+xRDbgVPOeHXqqxwZhqpW2\r\nusBU4+B+MnFLjquOPoySXFfdJFwTP61TPClUdyIne5FFP6EYf98mdtnkjxHo1TCA\r\nBgkqhkiG9w0BBwEwFAYIKoZIhvcNAwcECFNtpqBmU3M9oIAESM+yyQLkreETS0Kc\r\no01yl6dqqNBczH5FNTK88ypz38/jzjo47+DURlvGzjHJibiDsCz9KyiVmgbRrtvH\r\n08rfnMbrU+grCkkx9wQI1GnLrYhr87oAAAAAAAAAAAAA\r\n-----END PKCS7-----\r\n",p73des:"-----BEGIN PKCS7-----\r\nMIICTQYJKoZIhvcNAQcDoIICPjCCAjoCAQAxggHGMIIBwgIBADCBqTCBmzELMAkG\r\nA1UEBhMCREUxEjAQBgNVBAgMCUZyYW5jb25pYTEQMA4GA1UEBwwHQW5zYmFjaDEV\r\nMBMGA1UECgwMU3RlZmFuIFNpZWdsMRIwEAYDVQQLDAlHZWllcmxlaW4xFjAUBgNV\r\nBAMMDUdlaWVybGVpbiBERVYxIzAhBgkqhkiG9w0BCQEWFHN0ZXNpZUBicm9rZW5w\r\naXBlLmRlAgkA1FQcQNg14vMwDQYJKoZIhvcNAQEBBQAEggEAS6K+sQvdKcK6YafJ\r\nmaDPjBzyjf5jtBgVrFgBXTCRIp/Z2zAXa70skfxhbwTgmilYTacA7jPGRrnLmvBc\r\nBjhyCKM3dRUyYgh1K1ka0w1prvLmRk6Onf5df1ZQn3AJMIujJZcCOhbV1ByLInve\r\nxn02KNHstGmdHM/JGyPCp+iYGprhUozVSpNCKS+R33EbsT0sAxamfqdAblT9+5Qj\r\n4CABvW11a1clPV7STwBbAKbZaLs8mDeoWP0yHvBtJ7qzZdSgJJA2oU7SDv4icwEe\r\nAhccbe2HWkLRw8G5YG9XcWx5PnQQhhnXMxkLoSMIYxItyL/cRORbpDohd+otAo66\r\nWLH1ODBrBgkqhkiG9w0BBwEwFAYIKoZIhvcNAwcECD5EWJMv1fd7gEj1w3WM1KsM\r\nL8GDk9JoqA8t9v3oXCT0nAMXoNpHZMnv+0UHHVljlSXBTQxwUP5VMY/ddquJ5O3N\r\nrDEqqJuHB+KPIsW1kxrdplU=\r\n-----END PKCS7-----\r\n"};describe("pkcs7",function(){it("should import message from PEM",function(){var r=t.messageFromPem(o.p7);e.equal(r.type,n.oids.envelopedData),e.equal(r.version,0),e.equal(r.recipients.length,1),e.equal(r.recipients[0].version,0),e.equal(r.recipients[0].serialNumber,"00d4541c40d835e2f3"),e.equal(r.recipients[0].issuer.length,7),e.equal(r.recipients[0].issuer[0].type,"2.5.4.6"),e.equal(r.recipients[0].issuer[0].value,"DE"),e.equal(r.recipients[0].issuer[1].type,"2.5.4.8"),e.equal(r.recipients[0].issuer[1].value,"Franconia"),e.equal(r.recipients[0].issuer[2].type,"2.5.4.7"),e.equal(r.recipients[0].issuer[2].value,"Ansbach"),e.equal(r.recipients[0].issuer[3].type,"2.5.4.10"),e.equal(r.recipients[0].issuer[3].value,"Stefan Siegl"),e.equal(r.recipients[0].issuer[4].type,"2.5.4.11"),e.equal(r.recipients[0].issuer[4].value,"Geierlein"),e.equal(r.recipients[0].issuer[5].type,"2.5.4.3"),e.equal(r.recipients[0].issuer[5].value,"Geierlein DEV"),e.equal(r.recipients[0].issuer[6].type,"1.2.840.113549.1.9.1"),e.equal(r.recipients[0].issuer[6].value,"stesie@brokenpipe.de"),e.equal(r.recipients[0].encContent.algorithm,n.oids.rsaEncryption),e.equal(r.recipients[0].encContent.content.length,256),e.equal(r.encContent.algorithm,n.oids["aes256-CBC"]),e.equal(r.encContent.parameter.data.length,16)}),it("should import indefinite length message from PEM",function(){e.doesNotThrow(function(){var r=t.messageFromPem(o.p7IndefiniteLength);e.equal(r.type,n.oids.envelopedData),e.equal(r.encContent.parameter.toHex(),"536da6a06653733d"),e.equal(r.encContent.content.length(),80)})}),it("should find recipient by serial number",function(){var r=t.messageFromPem(o.p7),i=n.certificateFromPem(o.certificate),s=r.findRecipient(i);e.equal(s.serialNumber,"00d4541c40d835e2f3"),i.serialNumber="1234567890abcdef42",s=r.findRecipient(i),e.equal(s,null)}),it("should aes-decrypt message",function(){var r=t.messageFromPem(o.p7),i=n.privateKeyFromPem(o.privateKey);r.decrypt(r.recipients[0],i),e.equal(r.encContent.key.data.length,32),e.equal(r.content,"Today is Boomtime, the 9th day of Discord in the YOLD 3178\r\n")}),it("should 3des-decrypt message",function(){var r=t.messageFromPem(o.p73des),i=n.privateKeyFromPem(o.privateKey);r.decrypt(r.recipients[0],i),e.equal(r.encContent.key.data.length,24),e.equal(r.content,"Today is Prickle-Prickle, the 16th day of Discord in the YOLD 3178\r\n")}),it("should add a recipient",function(){var r=t.createEnvelopedData();e.equal(r.recipients.length,0);var i=n.certificateFromPem(o.certificate);r.addRecipient(i),e.equal(r.recipients.length,1),e.deepEqual(r.recipients[0].serialNumber,i.serialNumber),e.deepEqual(r.recipients[0].issuer,i.subject.attributes),e.deepEqual(r.recipients[0].encContent.key,i.publicKey)}),it("should aes-encrypt a message",function(){var i=t.createEnvelopedData(),u=n.certificateFromPem(o.certificate),a=n.privateKeyFromPem(o.privateKey);i.addRecipient(u),i.content=s.createBuffer("Just a little test"),e.equal(i.encContent.algorithm,n.oids["aes256-CBC"]),i.encrypt(),e.equal(i.encContent.key.data.length,32),e.equal(i.encContent.parameter.data.length,16),e.equal(i.encContent.content.data.length,32),e.equal(i.recipients[0].encContent.content.length,256),i.encContent.key.read=0,i.encContent.parameter.read=0;var f=a.decrypt(i.recipients[0].encContent.content);e.equal(f,i.encContent.key.data);var l=r.createDecryptionCipher(f);l.start(i.encContent.parameter),l.update(i.encContent.content),l.finish(),e.equal(l.output,"Just a little test")}),it("should 3des-ede-encrypt a message",function(){var r=t.createEnvelopedData(),u=n.certificateFromPem(o.certificate),a=n.privateKeyFromPem(o.privateKey);r.addRecipient(u),r.content=s.createBuffer("Just a little test"),r.encContent.algorithm=n.oids["des-EDE3-CBC"],r.encrypt(),e.equal(r.encContent.key.data.length,24),e.equal(r.encContent.parameter.data.length,8),e.equal(r.encContent.content.data.length,24),e.equal(r.recipients[0].encContent.content.length,256),r.encContent.key.read=0,r.encContent.parameter.read=0;var f=a.decrypt(r.recipients[0].encContent.content);e.equal(f,r.encContent.key.data);var l=i.createDecryptionCipher(f);l.start(r.encContent.parameter),l.update(r.encContent.content),l.finish(),e.equal(l.output,"Just a little test")}),it("should export message to PEM",function(){var r=t.createEnvelopedData();r.addRecipient(n.certificateFromPem(o.certificate)),r.content=s.createBuffer("Just a little test"),r.encrypt();var i=t.messageToPem(r);r=t.messageFromPem(i),r.decrypt(r.recipients[0],n.privateKeyFromPem(o.privateKey)),e.equal(r.content,"Just a little test")}),it("should decrypt encrypted data from PEM",function(){var r="1f8b08000000000000000b2e494d4bcc5308ce4c4dcfd15130b0b430d4b7343732b03437d05170cc2b4e4a4cced051b034343532d25170492d2d294ecec849cc4b0100bf52f02437000000",i="b96e4a4c0a3555d31e1b295647cc5cfe74081918cb7f797b";i=s.createBuffer(s.hexToBytes(i)),e.doesNotThrow(function(){var u=t.messageFromPem(o.encryptedData);e.equal(u.type,n.oids.encryptedData),e.equal(u.encContent.algorithm,n.oids["des-EDE3-CBC"]),e.equal(u.encContent.parameter.toHex(),"ba9305a2ee57dc35"),e.equal(u.encContent.content.length(),80),u.decrypt(i),e.equal(u.content.getBytes(),s.hexToBytes(r))})})})}typeof define=="function"?define("test/pkcs7",["forge/pkcs7","forge/pki","forge/aes","forge/des","forge/util"],function(t,n,r,i,s){e(ASSERT,t(),n(),r(),i(),s())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/pkcs7")(),require("../../js/pki")(),require("../../js/aes")(),require("../../js/des")(),require("../../js/util")())}(),function(){function e(e){var t=function(t,n,r,i){var s=e.util.createBuffer(),o=t.length>>1,u=o+(t.length&1),a=t.substr(0,u),f=t.substr(o,u),l=e.util.createBuffer(),c=e.hmac.create();r=n+r;var h=Math.ceil(i/16),p=Math.ceil(i/20);c.start("MD5",a);var d=e.util.createBuffer();l.putBytes(r);for(var v=0;v<h;++v)c.start(null,null),c.update(l.getBytes()),l.putBuffer(c.digest()),c.start(null,null),c.update(l.bytes()+r),d.putBuffer(c.digest());c.start("SHA1",f);var m=e.util.createBuffer();l.clear(),l.putBytes(r);for(var v=0;v<p;++v)c.start(null,null),c.update(l.getBytes()),l.putBuffer(c.digest()),c.start(null,null),c.update(l.bytes()+r),m.putBuffer(c.digest());return s.putBytes(e.util.xorBytes(d.getBytes(),m.getBytes(),i)),s},n=function(e,t,n,r){},r=function(t,n,r){var i=e.hmac.create();i.start("SHA1",t);var s=e.util.createBuffer();return s.putInt32(n[0]),s.putInt32(n[1]),s.putByte(r.type),s.putByte(r.version.major),s.putByte(r.version.minor),s.putInt16(r.length),s.putBytes(r.fragment.bytes()),i.update(s.getBytes()),i.digest().getBytes()},i=function(t,n,r){var i=!1;try{var s=t.deflate(n.fragment.getBytes());n.fragment=e.util.createBuffer(s),n.length=s.length,i=!0}catch(o){}return i},s=function(t,n,r){var i=!1;try{var s=t.inflate(n.fragment.getBytes());n.fragment=e.util.createBuffer(s),n.length=s.length,i=!0}catch(o){}return i},o=function(t,n){var r=0;switch(n){case 1:r=t.getByte();break;case 2:r=t.getInt16();break;case 3:r=t.getInt24();break;case 4:r=t.getInt32()}return e.util.createBuffer(t.getBytes(r))},u=function(e,t,n){e.putInt(n.length(),t<<3),e.putBuffer(n)},a={};a.Version={major:3,minor:1},a.MaxFragment=15360,a.ConnectionEnd={server:0,client:1},a.PRFAlgorithm={tls_prf_sha256:0},a.BulkCipherAlgorithm={none:null,rc4:0,des3:1,aes:2},a.CipherType={stream:0,block:1,aead:2},a.MACAlgorithm={none:null,hmac_md5:0,hmac_sha1:1,hmac_sha256:2,hmac_sha384:3,hmac_sha512:4},a.CompressionMethod={none:0,deflate:1},a.ContentType={change_cipher_spec:20,alert:21,handshake:22,application_data:23},a.HandshakeType={hello_request:0,client_hello:1,server_hello:2,certificate:11,server_key_exchange:12,certificate_request:13,server_hello_done:14,certificate_verify:15,client_key_exchange:16,finished:20},a.Alert={},a.Alert.Level={warning:1,fatal:2},a.Alert.Description={close_notify:0,unexpected_message:10,bad_record_mac:20,decryption_failed:21,record_overflow:22,decompression_failure:30,handshake_failure:40,bad_certificate:42,unsupported_certificate:43,certificate_revoked:44,certificate_expired:45,certificate_unknown:46,illegal_parameter:47,unknown_ca:48,access_denied:49,decode_error:50,decrypt_error:51,export_restriction:60,protocol_version:70,insufficient_security:71,internal_error:80,user_canceled:90,no_renegotiation:100},a.CipherSuites={},a.getCipherSuite=function(e){var t=null;for(var n in a.CipherSuites){var r=a.CipherSuites[n];if(r.id[0]===e.charCodeAt(0)&&r.id[1]===e.charCodeAt(1)){t=r;break}}return t},a.handleUnexpected=function(e,t){var n=!e.open&&e.entity===a.ConnectionEnd.client;n||e.error(e,{message:"Unexpected message. Received TLS record out of order.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.unexpected_message}})},a.handleHelloRequest=function(e,t,n){!e.handshaking&&e.handshakes>0&&(a.queue(e,a.createAlert({level:a.Alert.Level.warning,description:a.Alert.Description.no_renegotiation})),a.flush(e)),e.process()},a.parseHelloMessage=function(t,n,r){var i=null,s=t.entity===a.ConnectionEnd.client;if(r<38)t.error(t,{message:s?"Invalid ServerHello message. Message too short.":"Invalid ClientHello message. Message too short.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.illegal_parameter}});else{var u=n.fragment,f=u.length();i={version:{major:u.getByte(),minor:u.getByte()},random:e.util.createBuffer(u.getBytes(32)),session_id:o(u,1),extensions:[]},s?(i.cipher_suite=u.getBytes(2),i.compression_method=u.getByte()):(i.cipher_suites=o(u,2),i.compression_methods=o(u,1)),f=r-(f-u.length());if(f>0){var l=o(u,2);while(l.length()>0)i.extensions.push({type:[l.getByte(),l.getByte()],data:o(l,2)});if(!s)for(var c=0;c<i.extensions.length;++c){var h=i.extensions[c];if(h.type[0]===0&&h.type[1]===0){var p=o(h.data,2);while(p.length()>0){var d=p.getByte();if(d!==0)break;t.session.serverNameList.push(o(p,2).getBytes())}}}}(i.version.major!==a.Version.major||i.version.minor!==a.Version.minor)&&t.error(t,{message:"Incompatible TLS version.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.protocol_version}});if(s)t.session.cipherSuite=a.getCipherSuite(i.cipher_suite);else{var v=e.util.createBuffer(i.cipher_suites.bytes());while(v.length()>0){t.session.cipherSuite=a.getCipherSuite(v.getBytes(2));if(t.session.cipherSuite!==null)break}}if(t.session.cipherSuite===null)return t.error(t,{message:"No cipher suites in common.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.handshake_failure},cipherSuite:e.util.bytesToHex(i.cipher_suite)});s?t.session.compressionMethod=i.compression_method:t.session.compressionMethod=a.CompressionMethod.none}return i},a.createSecurityParameters=function(e,t){var n=e.entity===a.ConnectionEnd.client,r=t.random.bytes(),i=n?e.session.sp.client_random:r,s=n?r:a.createRandom().getBytes();e.session.sp={entity:e.entity,prf_algorithm:a.PRFAlgorithm.tls_prf_sha256,bulk_cipher_algorithm:null,cipher_type:null,enc_key_length:null,block_length:null,fixed_iv_length:null,record_iv_length:null,mac_algorithm:null,mac_length:null,mac_key_length:null,compression_algorithm:e.session.compressionMethod,pre_master_secret:null,master_secret:null,client_random:i,server_random:s}},a.handleServerHello=function(e,t,n){var r=a.parseHelloMessage(e,t,n);if(!e.fail){var i=r.session_id.bytes();i===e.session.id?(e.expect=d,e.session.resuming=!0,e.session.sp.server_random=r.random.bytes()):(e.expect=l,e.session.resuming=!1,a.createSecurityParameters(e,r)),e.session.id=i,e.process()}},a.handleClientHello=function(t,n,r){var i=a.parseHelloMessage(t,n,r);if(!t.fail){var s=i.session_id.bytes(),o=null;t.sessionCache&&(o=t.sessionCache.getSession(s),o===null&&(s="")),s.length===0&&(s=e.random.getBytes(32)),t.session.id=s,t.session.clientHelloVersion=i.version,t.session.sp=o?o.sp:{},o!==null?(t.expect=S,t.session.resuming=!0,t.session.sp.client_random=i.random.bytes()):(t.expect=t.verifyClient!==!1?b:w,t.session.resuming=!1,a.createSecurityParameters(t,i)),t.open=!0,a.queue(t,a.createRecord({type:a.ContentType.handshake,data:a.createServerHello(t)})),t.session.resuming?(a.queue(t,a.createRecord({type:a.ContentType.change_cipher_spec,data:a.createChangeCipherSpec()})),t.state.pending=a.createConnectionState(t),t.state.current.write=t.state.pending.write,a.queue(t,a.createRecord({type:a.ContentType.handshake,data:a.createFinished(t)}))):(a.queue(t,a.createRecord({type:a.ContentType.handshake,data:a.createCertificate(t)})),t.fail||(a.queue(t,a.createRecord({type:a.ContentType.handshake,data:a.createServerKeyExchange(t)})),t.verifyClient!==!1&&a.queue(t,a.createRecord({type:a.ContentType.handshake,data:a.createCertificateRequest(t)})),a.queue(t,a.createRecord({type:a.ContentType.handshake,data:a.createServerHelloDone(t)})))),a.flush(t),t.process()}},a.handleCertificate=function(t,n,r){if(r<3)t.error(t,{message:"Invalid Certificate message. Message too short.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.illegal_parameter}});else{var i=n.fragment,s={certificate_list:o(i,3)},u,f,l=[];try{while(s.certificate_list.length()>0)u=o(s.certificate_list,3),f=e.asn1.fromDer(u),u=e.pki.certificateFromAsn1(f,!0),l.push(u)}catch(h){t.error(t,{message:"Could not parse certificate list.",cause:h,send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.bad_certificate}})}if(!t.fail){var p=t.entity===a.ConnectionEnd.client;!p&&t.verifyClient!==!0||l.length!==0?l.length===0?t.expect=p?c:w:(p?t.session.serverCertificate=l[0]:t.session.clientCertificate=l[0],a.verifyCertificateChain(t,l)&&(t.expect=p?c:w)):t.error(t,{message:p?"No server certificate provided.":"No client certificate provided.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.illegal_parameter}}),t.process()}}},a.handleServerKeyExchange=function(e,t,n){n>0?e.error(e,{message:"Invalid key parameters. Only RSA is supported.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.unsupported_certificate}}):(e.expect=h,e.process())},a.handleClientKeyExchange=function(t,n,r){if(r<48)t.error(t,{message:"Invalid key parameters. Only RSA is supported.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.unsupported_certificate}});else{var i=n.fragment,s={enc_pre_master_secret:o(i,2).getBytes()},u=null;if(t.getPrivateKey)try{u=t.getPrivateKey(t,t.session.serverCertificate),u=e.pki.privateKeyFromPem(u)}catch(f){t.error(t,{message:"Could not get private key.",cause:f,send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.internal_error}})}if(u===null)t.error(t,{message:"No private key set.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.internal_error}});else try{var l=t.session.sp;l.pre_master_secret=u.decrypt(s.enc_pre_master_secret);var c=t.session.clientHelloVersion;if(c.major!==l.pre_master_secret.charCodeAt(0)||c.minor!==l.pre_master_secret.charCodeAt(1))throw{message:"TLS version rollback attack detected."}}catch(f){l.pre_master_secret=e.random.getBytes(48)}}t.fail||(t.expect=S,t.session.clientCertificate!==null&&(t.expect=E),t.process())},a.handleCertificateRequest=function(e,t,n){if(n<3)e.error(e,{message:"Invalid CertificateRequest. Message too short.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.illegal_parameter}});else{var r=t.fragment,i={certificate_types:o(r,1),certificate_authorities:o(r,2)};e.session.certificateRequest=i,e.expect=p,e.process()}},a.handleCertificateVerify=function(t,n,r){if(r<2)t.error(t,{message:"Invalid CertificateVerify. Message too short.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.illegal_parameter}});else{var i=n.fragment;i.read-=4;var s=i.bytes();i.read+=4;var u={signature:o(i,2).getBytes()},f=e.util.createBuffer();f.putBuffer(t.session.md5.digest()),f.putBuffer(t.session.sha1.digest()),f=f.getBytes();try{var l=t.session.clientCertificate;if(!l.publicKey.verify(f,u.signature,"NONE"))throw{message:"CertificateVerify signature does not match."};t.session.md5.update(s),t.session.sha1.update(s)}catch(c){t.error(t,{message:"Bad signature in CertificateVerify.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.handshake_failure}})}t.fail||(t.expect=S,t.process())}},a.handleServerHelloDone=function(t,n,r){if(r>0)t.error(t,{message:"Invalid ServerHelloDone message. Invalid length.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.record_overflow}});else if(t.serverCertificate===null){var i={message:"No server certificate provided. Not enough security.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.insufficient_security}},s=t.verify(t,i.alert.description,depth,[]);if(s===!0)i=null;else{if(s||s===0)typeof s=="object"&&!e.util.isArray(s)?(s.message&&(i.message=s.message),s.alert&&(i.alert.description=s.alert)):typeof s=="number"&&(i.alert.description=s);t.error(t,i)}}!t.fail&&t.session.certificateRequest!==null&&(n=a.createRecord({type:a.ContentType.handshake,data:a.createCertificate(t)}),a.queue(t,n));if(!t.fail){n=a.createRecord({type:a.ContentType.handshake,data:a.createClientKeyExchange(t)}),a.queue(t,n),t.expect=g;var o=function(e,t){e.session.certificateRequest!==null&&e.session.clientCertificate!==null&&a.queue(e,a.createRecord({type:a.ContentType.handshake,data:a.createCertificateVerify(e,t)})),a.queue(e,a.createRecord({type:a.ContentType.change_cipher_spec,data:a.createChangeCipherSpec()})),e.state.pending=a.createConnectionState(e),e.state.current.write=e.state.pending.write,a.queue(e,a.createRecord({type:a.ContentType.handshake,data:a.createFinished(e)})),e.expect=d,a.flush(e),e.process()};t.session.certificateRequest===null||t.session.clientCertificate===null?o(t,null):a.getClientSignature(t,o)}},a.handleChangeCipherSpec=function(e,t){if(t.fragment.getByte()!==1)e.error(e,{message:"Invalid ChangeCipherSpec message received.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.illegal_parameter}});else{var n=e.entity===a.ConnectionEnd.client;if(e.session.resuming&&n||!e.session.resuming&&!n)e.state.pending=a.createConnectionState(e);e.state.current.read=e.state.pending.read;if(!e.session.resuming&&n||e.session.resuming&&!n)e.state.pending=null;e.expect=n?v:x,e.process()}},a.handleFinished=function(n,r,i){var s=r.fragment;s.read-=4;var o=s.bytes();s.read+=4;var u=r.fragment.getBytes();s=e.util.createBuffer(),s.putBuffer(n.session.md5.digest()),s.putBuffer(n.session.sha1.digest());var f=n.entity===a.ConnectionEnd.client,l=f?"server finished":"client finished",c=n.session.sp,h=12,p=t;s=p(c.master_secret,l,s.getBytes(),h);if(s.getBytes()!==u)n.error(n,{message:"Invalid verify_data in Finished message.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.decrypt_error}});else{n.session.md5.update(o),n.session.sha1.update(o);if(n.session.resuming&&f||!n.session.resuming&&!f)a.queue(n,a.createRecord({type:a.ContentType.change_cipher_spec,data:a.createChangeCipherSpec()})),n.state.current.write=n.state.pending.write,n.state.pending=null,a.queue(n,a.createRecord({type:a.ContentType.handshake,data:a.createFinished(n)}));n.expect=f?m:T,n.handshaking=!1,++n.handshakes,n.peerCertificate=f?n.session.serverCertificate:n.session.clientCertificate,n.sessionCache?(n.session={id:n.session.id,sp:n.session.sp},n.session.sp.keys=null):n.session=null,a.flush(n),n.isConnected=!0,n.connected(n),n.process()}},a.handleAlert=function(e,t){var n=t.fragment,r={level:n.getByte(),description:n.getByte()},i;switch(r.description){case a.Alert.Description.close_notify:i="Connection closed.";break;case a.Alert.Description.unexpected_message:i="Unexpected message.";break;case a.Alert.Description.bad_record_mac:i="Bad record MAC.";break;case a.Alert.Description.decryption_failed:i="Decryption failed.";break;case a.Alert.Description.record_overflow:i="Record overflow.";break;case a.Alert.Description.decompression_failure:i="Decompression failed.";break;case a.Alert.Description.handshake_failure:i="Handshake failure.";break;case a.Alert.Description.bad_certificate:i="Bad certificate.";break;case a.Alert.Description.unsupported_certificate:i="Unsupported certificate.";break;case a.Alert.Description.certificate_revoked:i="Certificate revoked.";break;case a.Alert.Description.certificate_expired:i="Certificate expired.";break;case a.Alert.Description.certificate_unknown:i="Certificate unknown.";break;case a.Alert.Description.illegal_parameter:i="Illegal parameter.";break;case a.Alert.Description.unknown_ca:i="Unknown certificate authority.";break;case a.Alert.Description.access_denied:i="Access denied.";break;case a.Alert.Description.decode_error:i="Decode error.";break;case a.Alert.Description.decrypt_error:i="Decrypt error.";break;case a.Alert.Description.export_restriction:i="Export restriction.";break;case a.Alert.Description.protocol_version:i="Unsupported protocol version.";break;case a.Alert.Description.insufficient_security:i="Insufficient security.";break;case a.Alert.Description.internal_error:i="Internal error.";break;case a.Alert.Description.user_canceled:i="User canceled.";break;case a.Alert.Description.no_renegotiation:i="Renegotiation not supported.";break;default:i="Unknown error."}r.description===a.Alert.Description.close_notify?e.close():(e.error(e,{message:i,send:!1,origin:e.entity===a.ConnectionEnd.client?"server":"client",alert:r}),e.process())},a.handleHandshake=function(t,n){var r=n.fragment,i=r.getByte(),s=r.getInt24();if(s>r.length())t.fragmented=n,n.fragment=e.util.createBuffer(),r.read-=4,t.process();else{t.fragmented=null,r.read-=4;var o=r.bytes(s+4);r.read+=4,i in I[t.entity][t.expect]?(t.entity===a.ConnectionEnd.server&&!t.open&&!t.fail&&(t.handshaking=!0,t.session={serverNameList:[],cipherSuite:null,compressionMethod:null,serverCertificate:null,clientCertificate:null,md5:e.md.md5.create(),sha1:e.md.sha1.create()}),i!==a.HandshakeType.hello_request&&i!==a.HandshakeType.certificate_verify&&i!==a.HandshakeType.finished&&(t.session.md5.update(o),t.session.sha1.update(o)),I[t.entity][t.expect][i](t,n,s)):a.handleUnexpected(t,n)}},a.handleApplicationData=function(e,t){e.data.putBuffer(t.fragment),e.dataReady(e),e.process()};var f=0,l=1,c=2,h=3,p=4,d=5,v=6,m=7,g=8,y=0,b=1,w=2,E=3,S=4,x=5,T=6,N=7,C=a.handleUnexpected,k=a.handleChangeCipherSpec,L=a.handleAlert,A=a.handleHandshake,O=a.handleApplicationData,M=[];M[a.ConnectionEnd.client]=[[C,L,A,C],[C,L,A,C],[C,L,A,C],[C,L,A,C],[C,L,A,C],[k,L,C,C],[C,L,A,C],[C,L,A,O],[C,L,A,C]],M[a.ConnectionEnd.server]=[[C,L,A,C],[C,L,A,C],[C,L,A,C],[C,L,A,C],[k,L,C,C],[C,L,A,C],[C,L,A,O],[C,L,A,C]];var _=a.handleHelloRequest,D=a.handleServerHello,P=a.handleCertificate,H=a.handleServerKeyExchange,B=a.handleCertificateRequest,j=a.handleServerHelloDone,F=a.handleFinished,I=[];I[a.ConnectionEnd.client]=[[C,C,D,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C],[_,C,C,C,C,C,C,C,C,C,C,P,H,B,j,C,C,C,C,C,C],[_,C,C,C,C,C,C,C,C,C,C,C,H,B,j,C,C,C,C,C,C],[_,C,C,C,C,C,C,C,C,C,C,C,C,B,j,C,C,C,C,C,C],[_,C,C,C,C,C,C,C,C,C,C,C,C,C,j,C,C,C,C,C,C],[_,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C],[_,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,F],[_,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C],[_,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C]];var q=a.handleClientHello,R=a.handleClientKeyExchange,U=a.handleCertificateVerify;I[a.ConnectionEnd.server]=[[C,q,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C],[C,C,C,C,C,C,C,C,C,C,C,P,C,C,C,C,C,C,C,C,C],[C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,R,C,C,C,C],[C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,U,C,C,C,C,C],[C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C],[C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,F],[C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C],[C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C]],a.generateKeys=function(e,n){var r=t,i=n.client_random+n.server_random;e.session.resuming||(n.master_secret=r(n.pre_master_secret,"master secret",i,48).bytes(),n.pre_master_secret=null),i=n.server_random+n.client_random;var s=2*n.mac_key_length+2*n.enc_key_length+2*n.fixed_iv_length,o=r(n.master_secret,"key expansion",i,s);return{client_write_MAC_key:o.getBytes(n.mac_key_length),server_write_MAC_key:o.getBytes(n.mac_key_length),client_write_key:o.getBytes(n.enc_key_length),server_write_key:o.getBytes(n.enc_key_length),client_write_IV:o.getBytes(n.fixed_iv_length),server_write_IV:o.getBytes(n.fixed_iv_length)}},a.createConnectionState=function(e){var t=e.entity===a.ConnectionEnd.client,n=function(){var e={sequenceNumber:[0,0],macKey:null,macLength:0,macFunction:null,cipherState:null,cipherFunction:function(e){return!0},compressionState:null,compressFunction:function(e){return!0},updateSequenceNumber:function(){e.sequenceNumber[1]===4294967295?(e.sequenceNumber[1]=0,++e.sequenceNumber[0]):++e.sequenceNumber[1]}};return e},r={read:n(),write:n()};r.read.update=function(e,t){return r.read.cipherFunction(t,r.read)?r.read.compressFunction(e,t,r.read)||e.error(e,{message:"Could not decompress record.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.decompression_failure}}):e.error(e,{message:"Could not decrypt record or bad MAC.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.bad_record_mac}}),!e.fail},r.write.update=function(e,t){return r.write.compressFunction(e,t,r.write)?r.write.cipherFunction(t,r.write)||e.error(e,{message:"Could not encrypt record.",send:!1,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.internal_error}}):e.error(e,{message:"Could not compress record.",send:!1,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.internal_error}}),!e.fail};if(e.session){var o=e.session.sp;e.session.cipherSuite.initSecurityParameters(o),o.keys=a.generateKeys(e,o),r.read.macKey=t?o.keys.server_write_MAC_key:o.keys.client_write_MAC_key,r.write.macKey=t?o.keys.client_write_MAC_key:o.keys.server_write_MAC_key,e.session.cipherSuite.initConnectionState(r,e,o);switch(o.compression_algorithm){case a.CompressionMethod.none:break;case a.CompressionMethod.deflate:r.read.compressFunction=s,r.write.compressFunction=i;break;default:throw{message:"Unsupported compression algorithm."}}}return r},a.createRandom=function(){var t=new Date,n=+t+t.getTimezoneOffset()*6e4,r=e.util.createBuffer();return r.putInt32(n),r.putBytes(e.random.getBytes(28)),r},a.createRecord=function(e){if(!e.data)return null;var t={type:e.type,version:{major:a.Version.major,minor:a.Version.minor},length:e.data.length(),fragment:e.data};return t},a.createAlert=function(t){var n=e.util.createBuffer();return n.putByte(t.level),n.putByte(t.description),a.createRecord({type:a.ContentType.alert,data:n})},a.createClientHello=function(t){var n=e.util.createBuffer();for(var r=0;r<t.cipherSuites.length;++r){var i=t.cipherSuites[r];n.putByte(i.id[0]),n.putByte(i.id[1])}var s=n.length(),o=e.util.createBuffer();o.putByte(a.CompressionMethod.none);var f=o.length(),l=e.util.createBuffer();if(t.virtualHost){var c=e.util.createBuffer();c.putByte(0),c.putByte(0);var h=e.util.createBuffer();h.putByte(0),u(h,2,e.util.createBuffer(t.virtualHost));var p=e.util.createBuffer();u(p,2,h),u(c,2,p),l.putBuffer(c)}var d=l.length();d>0&&(d+=2);var v=t.session.id,m=v.length+1+2+4+28+2+s+1+f+d,g=e.util.createBuffer();return g.putByte(a.HandshakeType.client_hello),g.putInt24(m),g.putByte(a.Version.major),g.putByte(a.Version.minor),g.putBytes(t.session.sp.client_random),u(g,1,e.util.createBuffer(v)),u(g,2,n),u(g,1,o),d>0&&u(g,2,l),g},a.createServerHello=function(t){var n=t.session.id,r=n.length+1+2+4+28+2+1,i=e.util.createBuffer();return i.putByte(a.HandshakeType.server_hello),i.putInt24(r),i.putByte(a.Version.major),i.putByte(a.Version.minor),i.putBytes(t.session.sp.server_random),u(i,1,e.util.createBuffer(n)),i.putByte(t.session.cipherSuite.id[0]),i.putByte(t.session.cipherSuite.id[1]),i.putByte(t.session.compressionMethod),i},a.createCertificate=function(t){var n=t.entity===a.ConnectionEnd.client,r=null;t.getCertificate&&(r=t.getCertificate(t,n?t.session.certificateRequest:t.session.serverNameList));var i=e.util.createBuffer();if(r!==null)try{e.util.isArray(r)||(r=[r]);var s=null;for(var o=0;o<r.length;++o){var f=e.pem.decode(r[o])[0];if(f.type!=="CERTIFICATE"&&f.type!=="X509 CERTIFICATE"&&f.type!=="TRUSTED CERTIFICATE")throw{message:'Could not convert certificate from PEM; PEM header type is not "CERTIFICATE", "X509 CERTIFICATE", or "TRUSTED CERTIFICATE".',headerType:f.type};if(f.procType&&f.procType.type==="ENCRYPTED")throw{message:"Could not convert certificate from PEM; PEM is encrypted."};var l=e.util.createBuffer(f.body);s===null&&(s=e.asn1.fromDer(l.bytes(),!1));var c=e.util.createBuffer();u(c,3,l),i.putBuffer(c)}r=e.pki.certificateFromAsn1(s),n?t.session.clientCertificate=r:t.session.serverCertificate=r}catch(h){return t.error(t,{message:"Could not send certificate list.",cause:h,send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.bad_certificate}})}var p=3+i.length(),d=e.util.createBuffer();return d.putByte(a.HandshakeType.certificate),d.putInt24(p),u(d,3,i),d},a.createClientKeyExchange=function(t){var n=e.util.createBuffer();n.putByte(a.Version.major),n.putByte(a.Version.minor),n.putBytes(e.random.getBytes(46));var r=t.session.sp;r.pre_master_secret=n.getBytes();var i=t.session.serverCertificate.publicKey;n=i.encrypt(r.pre_master_secret);var s=n.length+2,o=e.util.createBuffer();return o.putByte(a.HandshakeType.client_key_exchange),o.putInt24(s),o.putInt16(n.length),o.putBytes(n),o},a.createServerKeyExchange=function(t){var n=0,r=e.util.createBuffer();return n>0&&(r.putByte(a.HandshakeType.server_key_exchange),r.putInt24(n)),r},a.getClientSignature=function(t,n){var r=e.util.createBuffer();r.putBuffer(t.session.md5.digest()),r.putBuffer(t.session.sha1.digest()),r=r.getBytes(),t.getSignature=t.getSignature||function(t,n,r){var i=null;if(t.getPrivateKey)try{i=t.getPrivateKey(t,t.session.clientCertificate),i=e.pki.privateKeyFromPem(i)}catch(s){t.error(t,{message:"Could not get private key.",cause:s,send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.internal_error}})}i===null?t.error(t,{message:"No private key set.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.internal_error}}):n=i.sign(n,null),r(t,n)},t.getSignature(t,r,n)},a.createCertificateVerify=function(t,n){var r=n.length+2,i=e.util.createBuffer();return i.putByte(a.HandshakeType.certificate_verify),i.putInt24(r),i.putInt16(n.length),i.putBytes(n),i},a.createCertificateRequest=function(t){var n=e.util.createBuffer();n.putByte(1);var r=e.util.createBuffer();for(var i in t.caStore.certs){var s=t.caStore.certs[i],o=e.pki.distinguishedNameToAsn1(s.subject);r.putBuffer(e.asn1.toDer(o))}var f=1+n.length()+2+r.length(),l=e.util.createBuffer();return l.putByte(a.HandshakeType.certificate_request),l.putInt24(f),u(l,1,n),u(l,2,r),l},a.createServerHelloDone=function(t){var n=e.util.createBuffer();return n.putByte(a.HandshakeType.server_hello_done),n.putInt24(0),n},a.createChangeCipherSpec=function(){var t=e.util.createBuffer();return t.putByte(1),t},a.createFinished=function(n){var r=e.util.createBuffer();r.putBuffer(n.session.md5.digest()),r.putBuffer(n.session.sha1.digest());var i=n.entity===a.ConnectionEnd.client,s=n.session.sp,o=12,u=t,f=i?"client finished":"server finished";r=u(s.master_secret,f,r.getBytes(),o);var l=e.util.createBuffer();return l.putByte(a.HandshakeType.finished),l.putInt24(r.length()),l.putBuffer(r),l},a.queue=function(t,n){if(!n)return;if(n.type===a.ContentType.handshake){var r=n.fragment.bytes();t.session.md5.update(r),t.session.sha1.update(r),r=null}var i;if(n.fragment.length()<=a.MaxFragment)i=[n];else{i=[];var s=n.fragment.bytes();while(s.length>a.MaxFragment)i.push(a.createRecord({type:n.type,data:e.util.createBuffer(s.slice(0,a.MaxFragment))})),s=s.slice(a.MaxFragment);s.length>0&&i.push(a.createRecord({type:n.type,data:e.util.createBuffer(s)}))}for(var o=0;o<i.length&&!t.fail;++o){var u=i[o],f=t.state.current.write;f.update(t,u)&&t.records.push(u)}},a.flush=function(e){for(var t=0;t<e.records.length;++t){var n=e.records[t];e.tlsData.putByte(n.type),e.tlsData.putByte(n.version.major),e.tlsData.putByte(n.version.minor),e.tlsData.putInt16(n.fragment.length()),e.tlsData.putBuffer(e.records[t].fragment)}return e.records=[],e.tlsDataReady(e)};var z=function(t){switch(t){case!0:return!0;case e.pki.certificateError.bad_certificate:return a.Alert.Description.bad_certificate;case e.pki.certificateError.unsupported_certificate:return a.Alert.Description.unsupported_certificate;case e.pki.certificateError.certificate_revoked:return a.Alert.Description.certificate_revoked;case e.pki.certificateError.certificate_expired:return a.Alert.Description.certificate_expired;case e.pki.certificateError.certificate_unknown:return a.Alert.Description.certificate_unknown;case e.pki.certificateError.unknown_ca:return a.Alert.Description.unknown_ca;default:return a.Alert.Description.bad_certificate}},W=function(t){switch(t){case!0:return!0;case a.Alert.Description.bad_certificate:return e.pki.certificateError.bad_certificate;case a.Alert.Description.unsupported_certificate:return e.pki.certificateError.unsupported_certificate;case a.Alert.Description.certificate_revoked:return e.pki.certificateError.certificate_revoked;case a.Alert.Description.certificate_expired:return e.pki.certificateError.certificate_expired;case a.Alert.Description.certificate_unknown:return e.pki.certificateError.certificate_unknown;case a.Alert.Description.unknown_ca:return e.pki.certificateError.unknown_ca;default:return e.pki.certificateError.bad_certificate}};a.verifyCertificateChain=function(t,n){try{e.pki.verifyCertificateChain(t.caStore,n,function(r,i,s){var o=z(r),u=t.verify(t,r,i,s);if(u!==!0){if(typeof u=="object"&&!e.util.isArray(u)){var f={message:"The application rejected the certificate.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.bad_certificate}};throw u.message&&(f.message=u.message),u.alert&&(f.alert.description=u.alert),f}u!==r&&(u=W(u))}return u})}catch(r){if(typeof r!="object"||e.util.isArray(r))r={send:!0,alert:{level:a.Alert.Level.fatal,description:z(r)}};"send"in r||(r.send=!0),"alert"in r||(r.alert={level:a.Alert.Level.fatal,description:z(r.error)}),t.error(t,r)}return!t.fail},a.createSessionCache=function(t,n){var r=null;if(t&&t.getSession&&t.setSession&&t.order)r=t;else{r={},r.cache=t||{},r.capacity=Math.max(n||100,1),r.order=[];for(var i in t)r.order.length<=n?r.order.push(i):delete t[i];r.getSession=function(t){var n=null,i=null;t?i=e.util.bytesToHex(t):r.order.length>0&&(i=r.order[0]);if(i!==null&&i in r.cache){n=r.cache[i],delete r.cache[i];for(var s in r.order)if(r.order[s]===i){r.order.splice(s,1);break}}return n},r.setSession=function(t,n){if(r.order.length===r.capacity){var i=r.order.shift();delete r.cache[i]}var i=e.util.bytesToHex(t);r.order.push(i),r.cache[i]=n}}return r},a.createConnection=function(t){var n=null;t.caStore?e.util.isArray(t.caStore)?n=e.pki.createCaStore(t.caStore):n=t.caStore:n=e.pki.createCaStore();var r=t.cipherSuites||null;if(r===null){r=[];for(var i in a.CipherSuites)r.push(a.CipherSuites[i])}var s=t.server||!1?a.ConnectionEnd.server:a.ConnectionEnd.client,o=t.sessionCache?a.createSessionCache(t.sessionCache):null,u={entity:s,sessionId:t.sessionId,caStore:n,sessionCache:o,cipherSuites:r,connected:t.connected,virtualHost:t.virtualHost||null,verifyClient:t.verifyClient||!1,verify:t.verify||function(e,t,n,r){return t},getCertificate:t.getCertificate||null,getPrivateKey:t.getPrivateKey||null,getSignature:t.getSignature||null,input:e.util.createBuffer(),tlsData:e.util.createBuffer(),data:e.util.createBuffer(),tlsDataReady:t.tlsDataReady,dataReady:t.dataReady,closed:t.closed,error:function(e,n){n.origin=n.origin||(e.entity===a.ConnectionEnd.client?"client":"server"),n.send&&(a.queue(e,a.createAlert(n.alert)),a.flush(e));var r=n.fatal!==!1;r&&(e.fail=!0),t.error(e,n),r&&e.close(!1)},deflate:t.deflate||null,inflate:t.inflate||null};u.reset=function(e){u.record=null,u.session=null,u.peerCertificate=null,u.state={pending:null,current:null},u.expect=u.entity===a.ConnectionEnd.client?f:y,u.fragmented=null,u.records=[],u.open=!1,u.handshakes=0,u.handshaking=!1,u.isConnected=!1,u.fail=!e&&typeof e!="undefined",u.input.clear(),u.tlsData.clear(),u.data.clear(),u.state.current=a.createConnectionState(u)},u.reset();var l=function(e,t){var n=t.type-a.ContentType.change_cipher_spec,r=M[e.entity][e.expect];n in r?r[n](e,t):a.handleUnexpected(e,t)},c=function(t){var n=0,r=t.input,i=r.length();return i<5?n=5-i:(t.record={type:r.getByte(),version:{major:r.getByte(),minor:r.getByte()},length:r.getInt16(),fragment:e.util.createBuffer(),ready:!1},(t.record.version.major!==a.Version.major||t.record.version.minor!==a.Version.minor)&&t.error(t,{message:"Incompatible TLS version.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.protocol_version}})),n},h=function(e){var t=0,n=e.input,r=n.length();if(r<e.record.length)t=e.record.length-r;else{e.record.fragment.putBytes(n.getBytes(e.record.length));var i=e.state.current.read;i.update(e,e.record)&&(e.fragmented!==null&&(e.fragmented.type===e.record.type?(e.fragmented.fragment.putBuffer(e.record.fragment),e.record=e.fragmented):e.error(e,{message:"Invalid fragmented record.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.unexpected_message}})),e.record.ready=!0)}return t};return u.handshake=function(t){if(u.entity!==a.ConnectionEnd.client)u.error(u,{message:"Cannot initiate handshake as a server.",fatal:!1});else if(u.handshaking)u.error(u,{message:"Handshake already in progress.",fatal:!1});else{u.fail&&!u.open&&u.handshakes===0&&(u.fail=!1),u.handshaking=!0,t=t||"";var n=null;t.length>0&&(u.sessionCache&&(n=u.sessionCache.getSession(t)),n===null&&(t="")),t.length===0&&u.sessionCache&&(n=u.sessionCache.getSession(),n!==null&&(t=n.id)),u.session={id:t,cipherSuite:null,compressionMethod:null,serverCertificate:null,certificateRequest:null,clientCertificate:null,sp:n?n.sp:{},md5:e.md.md5.create(),sha1:e.md.sha1.create()},u.session.sp.client_random=a.createRandom().getBytes(),u.open=!0,a.queue(u,a.createRecord({type:a.ContentType.handshake,data:a.createClientHello(u)})),a.flush(u)}},u.process=function(e){var t=0;return e&&u.input.putBytes(e),u.fail||(u.record!==null&&u.record.ready&&u.record.fragment.isEmpty()&&(u.record=null),u.record===null&&(t=c(u)),!u.fail&&u.record!==null&&!u.record.ready&&(t=h(u)),!u.fail&&u.record!==null&&u.record.ready&&l(u,u.record)),t},u.prepare=function(t){return a.queue(u,a.createRecord({type:a.ContentType.application_data,data:e.util.createBuffer(t)})),a.flush(u)},u.close=function(e){!u.fail&&u.sessionCache&&u.session&&u.sessionCache.setSession(u.session.id,u.session);if(u.open){u.open=!1,u.input.clear();if(u.isConnected||u.handshaking)u.isConnected=u.handshaking=!1,a.queue(u,a.createAlert({level:a.Alert.Level.warning,description:a.Alert.Description.close_notify})),a.flush(u);u.closed(u)}u.reset(e)},u},e.tls=e.tls||{};for(var X in a)typeof a[X]!="function"&&(e.tls[X]=a[X]);e.tls.prf_tls1=t,e.tls.hmac_sha1=r,e.tls.createSessionCache=a.createSessionCache,e.tls.createConnection=a.createConnection}var t="tls";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/tls",["require","module","./asn1","./hmac","./md","./pem","./pki","./random","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){function n(n,i,s){var u=i.entity===e.tls.ConnectionEnd.client;n.read.cipherState={init:!1,cipher:e.aes.createDecryptionCipher(u?s.keys.server_write_key:s.keys.client_write_key),iv:u?s.keys.server_write_IV:s.keys.client_write_IV},n.write.cipherState={init:!1,cipher:e.aes.createEncryptionCipher(u?s.keys.client_write_key:s.keys.server_write_key),iv:u?s.keys.client_write_IV:s.keys.server_write_IV},n.read.cipherFunction=o,n.write.cipherFunction=r,n.read.macLength=n.write.macLength=s.mac_length,n.read.macFunction=n.write.macFunction=t.hmac_sha1}function r(t,n){var r=!1,s=n.macFunction(n.macKey,n.sequenceNumber,t);t.fragment.putBytes(s),n.updateSequenceNumber();var o;t.version.minor>1?o=e.random.getBytes(16):o=n.cipherState.init?null:n.cipherState.iv,n.cipherState.init=!0;var u=n.cipherState.cipher;return u.start(o),t.version.minor>1&&u.output.putBytes(o),u.update(t.fragment),u.finish(i)&&(t.fragment=u.output,t.length=t.fragment.length(),r=!0),r}function i(e,t,n){if(!n){var r=e-t.length()%e;t.fillWithByte(r-1,r)}return!0}function s(e,t,n){var r=!0;if(n){var i=t.length(),s=t.last();for(var o=i-1-s;o<i-1;++o)r=r&&t.at(o)==s;r&&t.truncate(s+1)}return r}function o(t,n){var r=!1,i=n.cipherState.init?null:n.cipherState.iv;n.cipherState.init=!0;var o=n.cipherState.cipher;o.start(i),o.update(t.fragment),r=o.finish(s);var u=n.macLength,a="";for(var f=0;f<u;++f)a+=String.fromCharCode(0);var l=o.output.length();l>=u?(t.fragment=o.output.getBytes(l-u),a=o.output.getBytes(u)):t.fragment=o.output.getBytes(),t.fragment=e.util.createBuffer(t.fragment),t.length=t.fragment.length();var c=n.macFunction(n.macKey,n.sequenceNumber,t);return n.updateSequenceNumber(),r=c===a&&r,r}var t=e.tls;t.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA={id:[0,47],name:"TLS_RSA_WITH_AES_128_CBC_SHA",initSecurityParameters:function(e){e.bulk_cipher_algorithm=t.BulkCipherAlgorithm.aes,e.cipher_type=t.CipherType.block,e.enc_key_length=16,e.block_length=16,e.fixed_iv_length=16,e.record_iv_length=16,e.mac_algorithm=t.MACAlgorithm.hmac_sha1,e.mac_length=20,e.mac_key_length=20},initConnectionState:n},t.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA={id:[0,53],name:"TLS_RSA_WITH_AES_256_CBC_SHA",initSecurityParameters:function(e){e.bulk_cipher_algorithm=t.BulkCipherAlgorithm.aes,e.cipher_type=t.CipherType.block,e.enc_key_length=32,e.block_length=16,e.fixed_iv_length=16,e.record_iv_length=16,e.mac_algorithm=t.MACAlgorithm.hmac_sha1,e.mac_length=20,e.mac_key_length=20},initConnectionState:n}}var t="aesCipherSuites";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/aesCipherSuites",["require","module","./aes","./tls"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){e.debug=e.debug||{},e.debug.storage={},e.debug.get=function(t,n){var r;return typeof t=="undefined"?r=e.debug.storage:t in e.debug.storage&&(typeof n=="undefined"?r=e.debug.storage[t]:r=e.debug.storage[t][n]),r},e.debug.set=function(t,n,r){t in e.debug.storage||(e.debug.storage[t]={}),e.debug.storage[t][n]=r},e.debug.clear=function(t,n){typeof t=="undefined"?e.debug.storage={}:t in e.debug.storage&&(typeof n=="undefined"?delete e.debug.storage[t]:delete e.debug.storage[t][n])}}var t="debug";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/debug",["require","module"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){e.log=e.log||{},e.log.levels=["none","error","warning","info","debug","verbose","max"];var t={},n=[],r=null;e.log.LEVEL_LOCKED=2,e.log.NO_LEVEL_CHECK=4,e.log.INTERPOLATE=8;for(var i=0;i<e.log.levels.length;++i){var s=e.log.levels[i];t[s]={index:i,name:s.toUpperCase()}}e.log.logMessage=function(r){var i=t[r.level].index;for(var s=0;s<n.length;++s){var o=n[s];if(o.flags&e.log.NO_LEVEL_CHECK)o.f(r);else{var u=t[o.level].index;i<=u&&o.f(o,r)}}},e.log.prepareStandard=function(e){"standard"in e||(e.standard=t[e.level].name+" ["+e.category+"] "+e.message)},e.log.prepareFull=function(t){if(!("full"in t)){var n=[t.message];n=n.concat([]||t.arguments),t.full=e.util.format.apply(this,n)}},e.log.prepareStandardFull=function(t){"standardFull"in t||(e.log.prepareStandard(t),t.standardFull=t.standard)};var o=["error","warning","info","debug","verbose"];for(var i=0;i<o.length;++i)(function(t){e.log[t]=function(n,r){var i=Array.prototype.slice.call(arguments).slice(2),s={timestamp:new Date,level:t,category:n,message:r,arguments:i};e.log.logMessage(s)}})(o[i]);e.log.makeLogger=function(t){var n={flags:0,f:t};return e.log.setLevel(n,"none"),n},e.log.setLevel=function(t,n){var r=!1;if(t&&!(t.flags&e.log.LEVEL_LOCKED))for(var i=0;i<e.log.levels.length;++i){var s=e.log.levels[i];if(n==s){t.level=n,r=!0;break}}return r},e.log.lock=function(t,n){typeof n=="undefined"||n?t.flags|=e.log.LEVEL_LOCKED:t.flags&=~e.log.LEVEL_LOCKED},e.log.addLogger=function(e){n.push(e)};if(typeof console!="undefined"&&"log"in console){var u;if(console.error&&console.warn&&console.info&&console.debug){var a={error:console.error,warning:console.warn,info:console.info,debug:console.debug,verbose:console.debug},f=function(t,n){e.log.prepareStandard(n);var r=a[n.level],i=[n.standard];i=i.concat(n.arguments.slice()),r.apply(console,i)};u=e.log.makeLogger(f)}else{var f=function(t,n){e.log.prepareStandardFull(n),console.log(n.standardFull)};u=e.log.makeLogger(f)}e.log.setLevel(u,"debug"),e.log.addLogger(u),r=u}else console={log:function(){}};if(r!==null){var l=e.util.getQueryVariables();"console.level"in l&&e.log.setLevel(r,l["console.level"].slice(-1)[0]);if("console.lock"in l){var c=l["console.lock"].slice(-1)[0];c=="true"&&e.log.lock(r)}}e.log.consoleLogger=r}var t="log";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/log",["require","module","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){var t="forge.task",n=0,r={},i=0;e.debug.set(t,"tasks",r);var s={};e.debug.set(t,"queues",s);var o="?",u=30,a=20,f="ready",l="running",c="blocked",h="sleeping",p="done",d="error",v="stop",m="start",g="block",y="unblock",b="sleep",w="wakeup",E="cancel",S="fail",x={};x[f]={},x[f][v]=f,x[f][m]=l,x[f][E]=p,x[f][S]=d,x[l]={},x[l][v]=f,x[l][m]=l,x[l][g]=c,x[l][y]=l,x[l][b]=h,x[l][w]=l,x[l][E]=p,x[l][S]=d,x[c]={},x[c][v]=c,x[c][m]=c,x[c][g]=c,x[c][y]=c,x[c][b]=c,x[c][w]=c,x[c][E]=p,x[c][S]=d,x[h]={},x[h][v]=h,x[h][m]=h,x[h][g]=h,x[h][y]=h,x[h][b]=h,x[h][w]=h,x[h][E]=p,x[h][S]=d,x[p]={},x[p][v]=p,x[p][m]=p,x[p][g]=p,x[p][y]=p,x[p][b]=p,x[p][w]=p,x[p][E]=p,x[p][S]=d,x[d]={},x[d][v]=d,x[d][m]=d,x[d][g]=d,x[d][y]=d,x[d][b]=d,x[d][w]=d,x[d][E]=d,x[d][S]=d;var T=function(s){this.id=-1,this.name=s.name||o,this.parent=s.parent||null,this.run=s.run,this.subtasks=[],this.error=!1,this.state=f,this.blocks=0,this.timeoutId=null,this.swapTime=null,this.userData=null,this.id=i++,r[this.id]=this,n>=1&&e.log.verbose(t,"[%s][%s] init",this.id,this.name,this)};T.prototype.debug=function(n){n=n||"",e.log.debug(t,n,"[%s][%s] task:",this.id,this.name,this,"subtasks:",this.subtasks.length,"queue:",s)},T.prototype.next=function(e,t){typeof e=="function"&&(t=e,e=this.name);var n=new T({run:t,name:e,parent:this});return n.state=l,n.type=this.type,n.successCallback=this.successCallback||null,n.failureCallback=this.failureCallback||null,this.subtasks.push(n),this},T.prototype.parallel=function(t,n){return e.util.isArray(t)&&(n=t,t=this.name),this.next(t,function(r){var i=r;i.block(n.length);var s=function(t,r){e.task.start({type:t,run:function(e){n[r](e)},success:function(e){i.unblock()},failure:function(e){i.unblock()}})};for(var o=0;o<n.length;o++){var u=t+"__parallel-"+r.id+"-"+o,a=o;s(u,a)}})},T.prototype.stop=function(){this.state=x[this.state][v]},T.prototype.start=function(){this.error=!1,this.state=x[this.state][m],this.state===l&&(this.start=new Date,this.run(this),C(this,0))},T.prototype.block=function(e){e=typeof e=="undefined"?1:e,this.blocks+=e,this.blocks>0&&(this.state=x[this.state][g])},T.prototype.unblock=function(e){return e=typeof e=="undefined"?1:e,this.blocks-=e,this.blocks===0&&this.state!==p&&(this.state=l,C(this,0)),this.blocks},T.prototype.sleep=function(e){e=typeof e=="undefined"?0:e,this.state=x[this.state][b];var t=this;this.timeoutId=setTimeout(function(){t.timeoutId=null,t.state=l,C(t,0)},e)},T.prototype.wait=function(e){e.wait(this)},T.prototype.wakeup=function(){this.state===h&&(cancelTimeout(this.timeoutId),this.timeoutId=null,this.state=l,C(this,0))},T.prototype.cancel=function(){this.state=x[this.state][E],this.permitsNeeded=0,this.timeoutId!==null&&(cancelTimeout(this.timeoutId),this.timeoutId=null),this.subtasks=[]},T.prototype.fail=function(e){this.error=!0,k(this,!0);if(e)e.error=this.error,e.swapTime=this.swapTime,e.userData=this.userData,C(e,0);else{if(this.parent!==null){var t=this.parent;while(t.parent!==null)t.error=this.error,t.swapTime=this.swapTime,t.userData=this.userData,t=t.parent;k(t,!0)}this.failureCallback&&this.failureCallback(this)}};var N=function(e){e.error=!1,e.state=x[e.state][m],setTimeout(function(){e.state===l&&(e.swapTime=+(new Date),e.run(e),C(e,0))},0)},C=function(e,t){var n=t>u||+(new Date)-e.swapTime>a,r=function(t){t++;if(e.state===l){n&&(e.swapTime=+(new Date));if(e.subtasks.length>0){var r=e.subtasks.shift();r.error=e.error,r.swapTime=e.swapTime,r.userData=e.userData,r.run(r),r.error||C(r,t)}else k(e),e.error||e.parent!==null&&(e.parent.error=e.error,e.parent.swapTime=e.swapTime,e.parent.userData=e.userData,C(e.parent,t))}};n?setTimeout(r,0):r(t)},k=function(i,o){i.state=p,delete r[i.id],n>=1&&e.log.verbose(t,"[%s][%s] finish",i.id,i.name,i),i.parent===null&&(i.type in s?s[i.type].length===0?e.log.error(t,"[%s][%s] task queue empty [%s]",i.id,i.name,i.type):s[i.type][0]!==i?e.log.error(t,"[%s][%s] task not first in queue [%s]",i.id,i.name,i.type):(s[i.type].shift(),s[i.type].length===0?(n>=1&&e.log.verbose(t,"[%s][%s] delete queue [%s]",i.id,i.name,i.type),delete s[i.type]):(n>=1&&e.log.verbose(t,"[%s][%s] queue start next [%s] remain:%s",i.id,i.name,i.type,s[i.type].length),s[i.type][0].start())):e.log.error(t,"[%s][%s] task queue missing [%s]",i.id,i.name,i.type),o||(i.error&&i.failureCallback?i.failureCallback(i):!i.error&&i.successCallback&&i.successCallback(i)))};e.task=e.task||{},e.task.start=function(r){var i=new T({run:r.run,name:r.name||o});i.type=r.type,i.successCallback=r.success||null,i.failureCallback=r.failure||null,i.type in s?s[r.type].push(i):(n>=1&&e.log.verbose(t,"[%s][%s] create queue [%s]",i.id,i.name,i.type),s[i.type]=[i],N(i))},e.task.cancel=function(e){e in s&&(s[e]=[s[e][0]])},e.task.createCondition=function(){var e={tasks:{}};return e.wait=function(t){t.id in e.tasks||(t.block(),e.tasks[t.id]=t)},e.notify=function(){var t=e.tasks;e.tasks={};for(var n in t)t[n].unblock()},e}}var t="task";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/task",["require","module","./debug","./log","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){var e="forge";if(typeof define!="function"){if(typeof module!="object"||!module.exports){typeof forge=="undefined"&&(forge={disableNativeCode:!1});return}var t=!0;define=function(e,t){t(require,module)}}var n,r=function(t,r){r.exports=function(r){var i=n.map(function(e){return t(e)});r=r||{},r.defined=r.defined||{};if(r.defined[e])return r[e];r.defined[e]=!0;for(var s=0;s<i.length;++s)i[s](r);return r},r.exports.disableNativeCode=!1,r.exports(r.exports)},i=define;define=function(e,r){return n=typeof e=="string"?r.slice(2):e.slice(2),t?(delete define,i.apply(null,Array.prototype.slice.call(arguments,0))):(define=i,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/forge",["require","module","./aes","./aesCipherSuites","./asn1","./debug","./des","./hmac","./log","./pbkdf2","./pem","./pkcs7","./pkcs1","./pkcs12","./pki","./prng","./pss","./random","./rc2","./task","./tls","./util","./md","./mgf1"],function(){r.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e,t){describe("tls",function(){it("should test TLS 1.0 PRF",function(){var n=t.util.createBuffer().fillWithByte(171,48).getBytes(),r=t.util.createBuffer().fillWithByte(205,64).getBytes(),i=t.tls.prf_tls1(n,"PRF Testvector",r,104),s="d3d4d1e349b5d515044666d51de32bab258cb521b6b053463e354832fd976754443bcf9a296519bc289abcbc1187e4ebd31e602353776c408aafb74cbc85eff69255f9788faa184cbb957a9819d84a5d7eb006eb459d3ae8de9810454b8b2d8f1afbc655a8c9a013";e.equal(i.toHex(),s)}),it("should establish a TLS connection and transfer data",function(n){function s(e,n){var r=t.pki.rsa.generateKeyPair(512),i=t.pki.createCertificate();i.publicKey=r.publicKey,i.serialNumber="01",i.validity.notBefore=new Date,i.validity.notAfter=new Date,i.validity.notAfter.setFullYear(i.validity.notBefore.getFullYear()+1);var s=[{name:"commonName",value:e},{name:"countryName",value:"US"},{shortName:"ST",value:"Virginia"},{name:"localityName",value:"Blacksburg"},{name:"organizationName",value:"Test"},{shortName:"OU",value:"Test"}];i.setSubject(s),i.setIssuer(s),i.setExtensions([{name:"basicConstraints",cA:!0},{name:"keyUsage",keyCertSign:!0,digitalSignature:!0,nonRepudiation:!0,keyEncipherment:!0,dataEncipherment:!0},{name:"subjectAltName",altNames:[{type:6,value:"https://myuri.com/webid#me"}]}]),i.sign(r.privateKey),n[e]={cert:t.pki.certificateToPem(i),privateKey:t.pki.privateKeyToPem(r.privateKey)}}var r={},i={};s("server",i),s("client",i),i.client.connection={},i.server.connection={},r.client=t.tls.createConnection({server:!1,caStore:[i.server.cert],sessionCache:{},cipherSuites:[t.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,t.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],virtualHost:"server",verify:function(e,t,n,r){return i.client.connection.commonName=r[0].subject.getField("CN").value,i.client.connection.certVerified=t,!0},connected:function(e){e.prepare("Hello Server")},getCertificate:function(e,t){return i.client.cert},getPrivateKey:function(e,t){return i.client.privateKey},tlsDataReady:function(e){r.server.process(e.tlsData.getBytes())},dataReady:function(e){i.client.connection.data=e.data.getBytes(),e.close()},closed:function(t){e.equal(i.client.connection.commonName,"server"),e.equal(i.client.connection.certVerified,!0),e.equal(i.client.connection.data,"Hello Client"),n()},error:function(t,n){e.equal(n.message,undefined)}}),r.server=t.tls.createConnection({server:!0,caStore:[i.client.cert],sessionCache:{},cipherSuites:[t.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,t.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],connected:function(e){},verifyClient:!0,verify:function(e,t,n,r){return i.server.connection.commonName=r[0].subject.getField("CN").value,i.server.connection.certVerified=t,!0},getCertificate:function(e,t){return i.server.connection.certHint=t[0],i.server.cert},getPrivateKey:function(e,t){return i.server.privateKey},tlsDataReady:function(e){r.client.process(e.tlsData.getBytes())},dataReady:function(e){i.server.connection.data=e.data.getBytes(),e.prepare("Hello Client"),e.close()},closed:function(t){e.equal(i.server.connection.certHint,"server"),e.equal(i.server.connection.commonName,"client"),e.equal(i.server.connection.certVerified,!0),e.equal(i.server.connection.data,"Hello Server")},error:function(t,n){e.equal(n.message,undefined)}}),r.client.handshake()})})}typeof define=="function"?define("test/tls",["forge/forge"],function(t){e(ASSERT,t)}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/forge"))}();var ASSERT=chai.assert;mocha.setup({ui:"bdd"}),requirejs.config({paths:{forge:"forge",test:"test"}}),requirejs(["test/util","test/md5","test/sha1","test/sha256","test/hmac","test/pbkdf2","test/mgf1","test/random","test/asn1","test/pem","test/rsa","test/pkcs1","test/x509","test/csr","test/aes","test/rc2","test/des","test/pkcs7","test/tls"],function(){mocha.run()}),define("ui/test.js",function(){});
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/package.json b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/package.json
new file mode 100644
index 0000000..9de0a11
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/package.json
@@ -0,0 +1,113 @@
+{
+  "name": "node-forge",
+  "version": "0.6.21",
+  "description": "JavaScript implementations of network transports, cryptography, ciphers, PKI, message digests, and various utilities.",
+  "homepage": "http://github.com/digitalbazaar/forge",
+  "author": {
+    "name": "Digital Bazaar, Inc.",
+    "email": "support@digitalbazaar.com",
+    "url": "http://digitalbazaar.com/"
+  },
+  "contributors": [
+    {
+      "name": "Dave Longley",
+      "email": "dlongley@digitalbazaar.com"
+    },
+    {
+      "name": "Stefan Siegl",
+      "email": "stesie@brokenpipe.de"
+    },
+    {
+      "name": "Christoph Dorn",
+      "email": "christoph@christophdorn.com"
+    }
+  ],
+  "devDependencies": {
+    "almond": "~0.2.6",
+    "jscs": "^1.8.1",
+    "requirejs": "~2.1.8"
+  },
+  "repository": {
+    "type": "git",
+    "url": "http://github.com/digitalbazaar/forge"
+  },
+  "bugs": {
+    "url": "https://github.com/digitalbazaar/forge/issues",
+    "email": "support@digitalbazaar.com"
+  },
+  "licenses": [
+    {
+      "type": "BSD",
+      "url": "https://github.com/digitalbazaar/forge/raw/master/LICENSE"
+    }
+  ],
+  "main": "js/forge.js",
+  "engines": {
+    "node": "*"
+  },
+  "keywords": [
+    "aes",
+    "asn",
+    "asn.1",
+    "cbc",
+    "crypto",
+    "cryptography",
+    "csr",
+    "des",
+    "gcm",
+    "hmac",
+    "http",
+    "https",
+    "md5",
+    "network",
+    "pkcs",
+    "pki",
+    "prng",
+    "rc2",
+    "rsa",
+    "sha1",
+    "sha256",
+    "sha384",
+    "sha512",
+    "ssh",
+    "tls",
+    "x.509",
+    "x509"
+  ],
+  "scripts": {
+    "bundle": "r.js -o minify.js optimize=none out=js/forge.bundle.js",
+    "minify": "r.js -o minify.js",
+    "jscs": "jscs *.js js/*.js minify.js nodejs/*.js nodejs/test/*.js nodejs/ui/*.js tests/*.js",
+    "jshint": "jshint *.js js/*.js minify.js nodejs/*.js nodejs/test/*.js nodejs/ui/*.js tests/*.js"
+  },
+  "gitHead": "875d46d0ef05f1b2a8a93b533a882c3a862f7fcf",
+  "_id": "node-forge@0.6.21",
+  "_shasum": "7dadde911be009c7aae9150e780aea21d4f8bd09",
+  "_from": "node-forge@^0.6.12",
+  "_npmVersion": "1.4.28",
+  "_npmUser": {
+    "name": "dlongley",
+    "email": "dlongley@digitalbazaar.com"
+  },
+  "maintainers": [
+    {
+      "name": "davidlehn",
+      "email": "dil@lehn.org"
+    },
+    {
+      "name": "dlongley",
+      "email": "dlongley@digitalbazaar.com"
+    },
+    {
+      "name": "msporny",
+      "email": "msporny@digitalbazaar.com"
+    }
+  ],
+  "dist": {
+    "shasum": "7dadde911be009c7aae9150e780aea21d4f8bd09",
+    "tarball": "http://registry.npmjs.org/node-forge/-/node-forge-0.6.21.tgz"
+  },
+  "directories": {},
+  "_resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.6.21.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/setup/configure.ac b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/setup/configure.ac
new file mode 100644
index 0000000..0d94441
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/setup/configure.ac
@@ -0,0 +1,202 @@
+# Configure script for Digital Bazaar Bitmunk product line
+# Usage: Run ./configure once 
+# Author: Manu Sporny
+
+AC_PREREQ([2.60])
+AC_INIT([forge],[0.1.0],[support@digitalbazaar.com])
+AC_CONFIG_AUX_DIR(setup)
+
+# Setup standard build environment variables
+# FIXME: allow changing via configure option
+FORGE_DIR=`(cd ${srcdir} && pwd)`
+AC_SUBST(FORGE_DIR)
+DATE_YMD=`date +%Y%m%d`
+PACKAGE_DATE_VERSION=${PACKAGE_VERSION}-${DATE_YMD}
+AC_SUBST(DATE_RFC_2822)
+AC_SUBST(PACKAGE_DATE_VERSION)
+
+dnl ----------------- docs -----------------
+
+AC_ARG_ENABLE([docs],
+   AS_HELP_STRING([--enable-docs], [build documentation [no]]),
+   [ 
+      case "${enableval}" in
+         yes) BUILD_DOCS=yes ;;
+         no) BUILD_DOCS=no ;;
+         *) AC_MSG_ERROR(bad value ${enableval} for --enable-docs) ;;
+      esac
+   ], [BUILD_DOCS=no]) dnl Default value
+
+AC_SUBST(BUILD_DOCS)
+
+dnl ----------------- tests -----------------
+
+AC_ARG_ENABLE([tests],
+   AC_HELP_STRING([--disable-tests], [disable building test apps [no]]),
+   [
+   case "${enableval}" in
+      yes) BUILD_TESTS=yes ;;
+      no)  BUILD_TESTS=no ;;
+      *)   AC_MSG_ERROR(bad value ${enableval} for --disable-tests) ;;
+   esac
+   ], [BUILD_TESTS=no]) dnl Default value
+
+AC_SUBST(BUILD_TESTS)
+
+dnl ----------------- build flash -----------------
+
+AC_ARG_ENABLE([flash],
+   AC_HELP_STRING([--disable-flash], [disable building Flash [no]]),
+   [
+   case "${enableval}" in
+      yes) BUILD_FLASH=yes ;;
+      no)  BUILD_FLASH=no ;;
+      *)   AC_MSG_ERROR(bad value ${enableval} for --disable-flash) ;;
+   esac
+   ], [BUILD_FLASH=yes]) dnl Default value
+
+AC_ARG_ENABLE([pre-built-flash],
+   AC_HELP_STRING([--disable-pre-built-flash],
+      [disable use of pre-built Flash [no]]),
+   [
+   case "${enableval}" in
+      yes) USE_PRE_BUILT_FLASH=yes ;;
+      no)  USE_PRE_BUILT_FLASH=no ;;
+      *)   AC_MSG_ERROR(bad value ${enableval} for --disable-flash) ;;
+   esac
+   ], [USE_PRE_BUILT_FLASH=yes]) dnl Default value
+
+AC_SUBST(BUILD_FLASH)
+AC_SUBST(USE_PRE_BUILT_FLASH)
+
+dnl ----------------- mxmlc -----------------
+
+AC_ARG_WITH([mxmlc],
+   AC_HELP_STRING([--with-mxmlc=PATH],
+      [use PATH for mxmlc]),
+   [
+      case "${withval}" in
+         yes|no) AC_MSG_ERROR(bad value ${withval} for --with-mxmlc) ;;
+         *)      MXMLC="${withval}" ;;
+      esac
+      if test "x$MXMLC" = x -o ! -x "$MXMLC"; then
+         AC_MSG_ERROR([mxmlc not found at "$MXMLC"])
+      fi
+   ])
+
+if test "$BUILD_FLASH" = "yes" ; then
+   dnl Need to try to find mxmlc
+   if test "x$MXMLC" = x; then
+      AC_CHECK_PROGS(MXMLC, mxmlc /usr/lib/flex3/bin/mxmlc,, $PATH /)
+   fi
+   dnl Check that mxmlc was found
+   if test "x$MXMLC" = x; then
+      if test "$USE_PRE_BUILT_FLASH" = "yes"; then
+         dnl Check pre-built SWF is present
+         if test -r "$FORGE_DIR/swf/SocketPool.swf"; then
+            AC_MSG_NOTICE([Using pre-built Flash])
+            BUILD_FLASH=no
+         else
+            AC_MSG_ERROR([mxmlc and pre-built Flash not found])
+         fi
+      else
+         AC_MSG_ERROR([mxmlc not found, try --with-mxmlc])
+      fi
+   fi
+fi
+
+AC_SUBST(MXMLC)
+
+dnl ----------------- mxmlc debug -----------------
+
+AC_ARG_ENABLE([mxmlc-debug],
+   AC_HELP_STRING([--enable-mxmlc-debug], [enable mxmlc debug mode [no]]),
+   [
+   case "${enableval}" in
+      yes) MXMLC_DEBUG_MODE=yes ;;
+      no)  MXMLC_DEBUG_MODE=no ;;
+      *)   AC_MSG_ERROR(bad value ${enableval} for --enable-mxmlc-debug) ;;
+   esac
+   ], [MXMLC_DEBUG_MODE=no]) dnl Default value
+
+AC_SUBST(MXMLC_DEBUG_MODE)
+
+dnl ----------------- end of options -----------------
+
+echo -e "\n--------- Configuring Build Environment -----------"
+
+PKG_PROG_PKG_CONFIG
+
+# Checking for standard build tools
+AC_PROG_CPP
+AC_PROG_INSTALL
+AS_PATH_PYTHON([2.7])
+
+if test "x$PYTHON" != x; then
+   save_CPPFLAGS="$CPPFLAGS"
+   AC_CHECK_PROGS([PYTHON_CONFIG], [python-config]) 
+   if test "x$PYTHON_CONFIG" != x; then
+      CPPFLAGS="$CPPFLAGS `$PYTHON_CONFIG --cflags`"
+   fi
+   AC_CHECK_HEADERS([Python.h],
+      [BUILD_PYTHON_MODULES=yes],
+      [BUILD_PYTHON_MODULES=no
+      AC_MSG_WARN([Python.h not found, SSL bindings will not be build.])])
+   CPPFLAGS="$save_CPPFLAGS"
+else
+   AC_MSG_WARN([Need at least Python 2.7 to build SSL bindings.])
+fi
+
+AC_SUBST(BUILD_PYTHON_MODULES)
+
+dnl ----------------------------------
+
+dnl NOTE:
+dnl This code was used previously to autogenerate the .gitignore file but due
+dnl to the current more common use of just the js files, it's likely people
+dnl who checkout the code will never run the build scripts. The files are now
+dnl just hardcoded into .gitignore and should be updated by hand as needed.
+dnl
+dnl # Generating files
+dnl AC_CONFIG_FILES([
+dnl    .gitignore
+dnl    Makefile
+dnl ])
+dnl
+dnl # add newlines to internal output file list
+dnl CONFIGURE_GENERATED_FILES="`echo $ac_config_files | tr ' ' '\n'`"
+dnl AC_SUBST(CONFIGURE_GENERATED_FILES)
+
+AC_OUTPUT
+
+# Dump the build configuration
+
+echo -e "\n--------- Forge Build Environment -----------"
+echo "Forge Version     : $PACKAGE_NAME $PACKAGE_DATE_VERSION"
+
+if test "x$BUILD_FLASH" = "xyes" ; then
+   echo "Adobe Flash       : Flash building enabled"
+   echo "MXMLC             : $MXMLC"
+   echo "MXMLC Debug flags : $MXMLC_DEBUG_MODE"
+else
+   echo "Adobe Flash       : using pre-built Flash"
+fi
+
+if test "x$BUILD_PYTHON_MODULES" = "xyes" ; then
+   echo "Python            : $PYTHON (version $PYTHON_VERSION)"
+else
+   echo "Python            : development environment not found"
+fi
+
+if test "x$BUILD_DOCS" = "xyes"; then
+   echo "Documentation     : enabled"
+else
+   echo "Documentation     : disabled (use --enable-docs to enable)"
+fi
+
+if test "x$BUILD_TESTS" = "xyes"; then
+   echo "Tests             : enabled"
+else
+   echo "Tests             : disabled (use --enable-tests to enable)"
+fi
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/setup/install-sh b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/setup/install-sh
new file mode 100755
index 0000000..d4744f0
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/setup/install-sh
@@ -0,0 +1,269 @@
+#!/bin/sh
+#
+# install - install a program, script, or datafile
+#
+# This originates from X11R5 (mit/util/scripts/install.sh), which was
+# later released in X11R6 (xc/config/util/install.sh) with the
+# following copyright and license.
+#
+# Copyright (C) 1994 X Consortium
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC-
+# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+# Except as contained in this notice, the name of the X Consortium shall not
+# be used in advertising or otherwise to promote the sale, use or other deal-
+# ings in this Software without prior written authorization from the X Consor-
+# tium.
+#
+#
+# FSF changes to this file are in the public domain.
+#
+# Calling this script install-sh is preferred over install.sh, to prevent
+# `make' implicit rules from creating a file called install from it
+# when there is no Makefile.
+#
+# This script is compatible with the BSD install script, but was written
+# from scratch.  It can only install one file at a time, a restriction
+# shared with many OS's install programs.
+
+
+# set DOITPROG to echo to test this script
+
+# Don't use :- since 4.3BSD and earlier shells don't like it.
+doit="${DOITPROG-}"
+
+
+# put in absolute paths if you don't have them in your path; or use env. vars.
+
+mvprog="${MVPROG-mv}"
+cpprog="${CPPROG-cp}"
+chmodprog="${CHMODPROG-chmod}"
+chownprog="${CHOWNPROG-chown}"
+chgrpprog="${CHGRPPROG-chgrp}"
+stripprog="${STRIPPROG-strip}"
+rmprog="${RMPROG-rm}"
+mkdirprog="${MKDIRPROG-mkdir}"
+
+transformbasename=""
+transform_arg=""
+instcmd="$mvprog"
+chmodcmd="$chmodprog 0755"
+chowncmd=""
+chgrpcmd=""
+stripcmd=""
+rmcmd="$rmprog -f"
+mvcmd="$mvprog"
+src=""
+dst=""
+dir_arg=""
+
+while [ x"$1" != x ]; do
+    case $1 in
+	-c) instcmd="$cpprog"
+	    shift
+	    continue;;
+
+	-d) dir_arg=true
+	    shift
+	    continue;;
+
+	-m) chmodcmd="$chmodprog $2"
+	    shift
+	    shift
+	    continue;;
+
+	-o) chowncmd="$chownprog $2"
+	    shift
+	    shift
+	    continue;;
+
+	-g) chgrpcmd="$chgrpprog $2"
+	    shift
+	    shift
+	    continue;;
+
+	-s) stripcmd="$stripprog"
+	    shift
+	    continue;;
+
+	-t=*) transformarg=`echo $1 | sed 's/-t=//'`
+	    shift
+	    continue;;
+
+	-b=*) transformbasename=`echo $1 | sed 's/-b=//'`
+	    shift
+	    continue;;
+
+	*)  if [ x"$src" = x ]
+	    then
+		src=$1
+	    else
+		# this colon is to work around a 386BSD /bin/sh bug
+		:
+		dst=$1
+	    fi
+	    shift
+	    continue;;
+    esac
+done
+
+if [ x"$src" = x ]
+then
+	echo "install:	no input file specified"
+	exit 1
+else
+	true
+fi
+
+if [ x"$dir_arg" != x ]; then
+	dst=$src
+	src=""
+	
+	if [ -d $dst ]; then
+		instcmd=:
+		chmodcmd=""
+	else
+		instcmd=mkdir
+	fi
+else
+
+# Waiting for this to be detected by the "$instcmd $src $dsttmp" command
+# might cause directories to be created, which would be especially bad 
+# if $src (and thus $dsttmp) contains '*'.
+
+	if [ -f $src -o -d $src ]
+	then
+		true
+	else
+		echo "install:  $src does not exist"
+		exit 1
+	fi
+	
+	if [ x"$dst" = x ]
+	then
+		echo "install:	no destination specified"
+		exit 1
+	else
+		true
+	fi
+
+# If destination is a directory, append the input filename; if your system
+# does not like double slashes in filenames, you may need to add some logic
+
+	if [ -d $dst ]
+	then
+		dst="$dst"/`basename $src`
+	else
+		true
+	fi
+fi
+
+## this sed command emulates the dirname command
+dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'`
+
+# Make sure that the destination directory exists.
+#  this part is taken from Noah Friedman's mkinstalldirs script
+
+# Skip lots of stat calls in the usual case.
+if [ ! -d "$dstdir" ]; then
+defaultIFS=' 	
+'
+IFS="${IFS-${defaultIFS}}"
+
+oIFS="${IFS}"
+# Some sh's can't handle IFS=/ for some reason.
+IFS='%'
+set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'`
+IFS="${oIFS}"
+
+pathcomp=''
+
+while [ $# -ne 0 ] ; do
+	pathcomp="${pathcomp}${1}"
+	shift
+
+	if [ ! -d "${pathcomp}" ] ;
+        then
+		$mkdirprog "${pathcomp}"
+	else
+		true
+	fi
+
+	pathcomp="${pathcomp}/"
+done
+fi
+
+if [ x"$dir_arg" != x ]
+then
+	$doit $instcmd $dst &&
+
+	if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else true ; fi &&
+	if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else true ; fi &&
+	if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else true ; fi &&
+	if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else true ; fi
+else
+
+# If we're going to rename the final executable, determine the name now.
+
+	if [ x"$transformarg" = x ] 
+	then
+		dstfile=`basename $dst`
+	else
+		dstfile=`basename $dst $transformbasename | 
+			sed $transformarg`$transformbasename
+	fi
+
+# don't allow the sed command to completely eliminate the filename
+
+	if [ x"$dstfile" = x ] 
+	then
+		dstfile=`basename $dst`
+	else
+		true
+	fi
+
+# Make a temp file name in the proper directory.
+
+	dsttmp=$dstdir/#inst.$$#
+
+# Move or copy the file name to the temp name
+
+	$doit $instcmd $src $dsttmp &&
+
+	trap "rm -f ${dsttmp}" 0 &&
+
+# and set any options; do chmod last to preserve setuid bits
+
+# If any of these fail, we abort the whole thing.  If we want to
+# ignore errors from any of these, just make sure not to ignore
+# errors from the above "$doit $instcmd $src $dsttmp" command.
+
+	if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else true;fi &&
+	if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else true;fi &&
+	if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else true;fi &&
+	if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else true;fi &&
+
+# Now rename the file to the real destination.
+
+	$doit $rmcmd -f $dstdir/$dstfile &&
+	$doit $mvcmd $dsttmp $dstdir/$dstfile 
+
+fi &&
+
+
+exit 0
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/setup/m4/as-python.m4 b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/setup/m4/as-python.m4
new file mode 100644
index 0000000..84d4e36
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/setup/m4/as-python.m4
@@ -0,0 +1,156 @@
+## ------------------------
+## Python file handling
+## From Andrew Dalke
+## Updated by James Henstridge
+## Updated by Andy Wingo to loop through possible pythons
+## ------------------------
+
+# AS_PATH_PYTHON([MINIMUM-VERSION])
+
+# Adds support for distributing Python modules and packages.  To
+# install modules, copy them to $(pythondir), using the python_PYTHON
+# automake variable.  To install a package with the same name as the
+# automake package, install to $(pkgpythondir), or use the
+# pkgpython_PYTHON automake variable.
+
+# The variables $(pyexecdir) and $(pkgpyexecdir) are provided as
+# locations to install python extension modules (shared libraries).
+# Another macro is required to find the appropriate flags to compile
+# extension modules.
+
+# If your package is configured with a different prefix to python,
+# users will have to add the install directory to the PYTHONPATH
+# environment variable, or create a .pth file (see the python
+# documentation for details).
+
+# If the MINIMUM-VERSION argument is passed, AS_PATH_PYTHON will
+# cause an error if the version of python installed on the system
+# doesn't meet the requirement.  MINIMUM-VERSION should consist of
+# numbers and dots only.
+
+# Updated to loop over all possible python binaries by Andy Wingo
+# <wingo@pobox.com>
+# Updated to only warn and unset PYTHON if no good one is found
+
+AC_DEFUN([AS_PATH_PYTHON],
+ [
+  dnl Find a version of Python.  I could check for python versions 1.4
+  dnl or earlier, but the default installation locations changed from
+  dnl $prefix/lib/site-python in 1.4 to $prefix/lib/python1.5/site-packages
+  dnl in 1.5, and I don't want to maintain that logic.
+
+  dnl should we do the version check?
+  PYTHON_CANDIDATES="$PYTHON python python2 \
+                     python2.7 python2.6 pyton2.5 python2.4 python2.3 \
+                     python2.2 python2.1 python2.0 \
+                     python1.6 python1.5"
+  dnl Declare PYTHON as a special var
+  AC_ARG_VAR([PYTHON], [path to Python interpreter])
+  ifelse([$1],[],
+         [AC_PATH_PROG(PYTHON, $PYTHON_CANDIDATES)],
+         [
+     AC_MSG_NOTICE(Looking for Python version >= $1)
+    changequote(<<, >>)dnl
+    prog="
+import sys, string
+minver = '$1'
+# split string by '.' and convert to numeric
+minver_info = map(string.atoi, string.split(minver, '.'))
+# we can now do comparisons on the two lists:
+if sys.version_info >= tuple(minver_info):
+    sys.exit(0)
+else:
+    sys.exit(1)"
+    changequote([, ])dnl
+
+    python_good=false
+    for python_candidate in $PYTHON_CANDIDATES; do
+      unset PYTHON
+      AC_PATH_PROG(PYTHON, $python_candidate) 1> /dev/null 2> /dev/null
+
+      if test "x$PYTHON" = "x"; then continue; fi
+
+      if $PYTHON -c "$prog" 1>&AC_FD_CC 2>&AC_FD_CC; then
+        AC_MSG_CHECKING(["$PYTHON":])
+        AC_MSG_RESULT([okay])
+        python_good=true
+        break;
+      else
+        dnl clear the cache val
+        unset ac_cv_path_PYTHON
+      fi
+    done
+  ])
+
+  if test "$python_good" != "true"; then
+    AC_MSG_WARN([No suitable version of python found])
+    PYTHON=
+  else
+
+  AC_MSG_CHECKING([local Python configuration])
+
+  dnl Query Python for its version number.  Getting [:3] seems to be
+  dnl the best way to do this; it's what "site.py" does in the standard
+  dnl library.  Need to change quote character because of [:3]
+
+  AC_SUBST(PYTHON_VERSION)
+  changequote(<<, >>)dnl
+  PYTHON_VERSION=`$PYTHON -c "import sys; print sys.version[:3]"`
+  changequote([, ])dnl
+
+
+  dnl Use the values of $prefix and $exec_prefix for the corresponding
+  dnl values of PYTHON_PREFIX and PYTHON_EXEC_PREFIX.  These are made
+  dnl distinct variables so they can be overridden if need be.  However,
+  dnl general consensus is that you shouldn't need this ability.
+
+  AC_SUBST(PYTHON_PREFIX)
+  PYTHON_PREFIX='${prefix}'
+
+  AC_SUBST(PYTHON_EXEC_PREFIX)
+  PYTHON_EXEC_PREFIX='${exec_prefix}'
+
+  dnl At times (like when building shared libraries) you may want
+  dnl to know which OS platform Python thinks this is.
+
+  AC_SUBST(PYTHON_PLATFORM)
+  PYTHON_PLATFORM=`$PYTHON -c "import sys; print sys.platform"`
+
+
+  dnl Set up 4 directories:
+
+  dnl pythondir -- where to install python scripts.  This is the
+  dnl   site-packages directory, not the python standard library
+  dnl   directory like in previous automake betas.  This behaviour
+  dnl   is more consistent with lispdir.m4 for example.
+  dnl
+  dnl Also, if the package prefix isn't the same as python's prefix,
+  dnl then the old $(pythondir) was pretty useless.
+
+  AC_SUBST(pythondir)
+  pythondir=$PYTHON_PREFIX"/lib/python"$PYTHON_VERSION/site-packages
+
+  dnl pkgpythondir -- $PACKAGE directory under pythondir.  Was
+  dnl   PYTHON_SITE_PACKAGE in previous betas, but this naming is
+  dnl   more consistent with the rest of automake.
+  dnl   Maybe this should be put in python.am?
+
+  AC_SUBST(pkgpythondir)
+  pkgpythondir=\${pythondir}/$PACKAGE
+
+  dnl pyexecdir -- directory for installing python extension modules
+  dnl   (shared libraries)  Was PYTHON_SITE_EXEC in previous betas.
+
+  AC_SUBST(pyexecdir)
+  pyexecdir=$PYTHON_EXEC_PREFIX"/lib/python"$PYTHON_VERSION/site-packages
+
+  dnl pkgpyexecdir -- $(pyexecdir)/$(PACKAGE)
+  dnl   Maybe this should be put in python.am?
+
+  AC_SUBST(pkgpyexecdir)
+  pkgpyexecdir=\${pyexecdir}/$PACKAGE
+
+  AC_MSG_RESULT([looks good])
+
+  fi
+])
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/start.frag b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/start.frag
new file mode 100644
index 0000000..dad9d0f
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/start.frag
@@ -0,0 +1,7 @@
+(function(root, factory) {
+  if(typeof define === 'function' && define.amd) {
+    define([], factory);
+  } else {
+    root.forge = factory();
+  }
+})(this, function() {
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/aes-speed.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/aes-speed.js
new file mode 100644
index 0000000..6c9922a
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/aes-speed.js
@@ -0,0 +1,54 @@
+var forge = require('../js/forge');
+
+aes_128('GCM');
+
+function aes_128(mode) {
+  var size = 4096;
+  var key = forge.random.getBytes(16);
+  var iv = forge.random.getBytes(mode === 'GCM' ? 12 : 16);
+  var plain = forge.util.createBuffer().fillWithByte(0, size);
+
+  // run for 5 seconds
+  var start = Date.now();
+
+  var now;
+  var totalEncrypt = 0;
+  var totalDecrypt = 0;
+  var count = 0;
+  var passed = 0;
+  while(passed < 5000) {
+    var input = forge.util.createBuffer(plain);
+
+    // encrypt, only measuring update() and finish()
+    var cipher = forge.aes.createEncryptionCipher(key, mode);
+    cipher.start(iv);
+    now = Date.now();
+    cipher.update(input);
+    cipher.finish();
+    totalEncrypt += Date.now() - now;
+
+    var ciphertext = cipher.output;
+    var tag = cipher.tag;
+
+    // decrypt, only measuring update() and finish()
+    cipher = forge.aes.createDecryptionCipher(key, mode);
+    cipher.start(iv, {tag: tag});
+    now = Date.now();
+    cipher.update(ciphertext);
+    if(!cipher.finish()) {
+      throw new Error('Decryption error.');
+    }
+    totalDecrypt += Date.now() - now;
+
+    ++count;
+    passed = Date.now() - start;
+  }
+
+  count = count * size / 1000;
+  totalEncrypt /= 1000;
+  totalDecrypt /= 1000;
+
+  console.log('times in 1000s of bytes/sec processed.');
+  console.log('encrypt: ' + (count / totalEncrypt) + ' k/sec');
+  console.log('decrypt: ' + (count / totalDecrypt) + ' k/sec');
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/common.html b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/common.html
new file mode 100644
index 0000000..0fb4705
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/common.html
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+   <head>
+      <title>Forge Common Test</title>
+      <link type="text/css" rel="stylesheet" media="all" href="screen.css" />
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
+      <script type="text/javascript" src="forge/debug.js"></script>
+      <script type="text/javascript" src="forge/util.js"></script>
+      <script type="text/javascript" src="forge/log.js"></script>
+      <script type="text/javascript" src="forge/task.js"></script>
+      <script type="text/javascript" src="forge/md5.js"></script>
+      <script type="text/javascript" src="forge/sha1.js"></script>
+      <script type="text/javascript" src="forge/sha256.js"></script>
+      <script type="text/javascript" src="forge/hmac.js"></script>
+      <script type="text/javascript" src="forge/pbkdf2.js"></script>
+      <script type="text/javascript" src="forge/aes.js"></script>
+      <script type="text/javascript" src="forge/pem.js"></script>
+      <script type="text/javascript" src="forge/asn1.js"></script>
+      <script type="text/javascript" src="forge/jsbn.js"></script>
+      <script type="text/javascript" src="forge/prng.js"></script>
+      <script type="text/javascript" src="forge/random.js"></script>
+      <script type="text/javascript" src="forge/oids.js"></script>
+      <script type="text/javascript" src="forge/rsa.js"></script>
+      <script type="text/javascript" src="forge/pbe.js"></script>
+      <script type="text/javascript" src="forge/x509.js"></script>
+      <script type="text/javascript" src="forge/pki.js"></script>
+      <script type="text/javascript" src="forge/tls.js"></script>
+      <script type="text/javascript" src="forge/aesCipherSuites.js"></script>
+      <script type="text/javascript" src="common.js"></script>
+
+      <link type="text/css" rel="stylesheet" media="all" href="screen.css" />
+      <style type="text/css">
+         .ready { color: inherit; background: inherit; }
+         .testing { color: black; background: yellow; }
+         .pass{ color: white; background: green; }
+         .fail{ color: white; background: red; }
+      </style>
+   </head>
+<body>
+<div class="nav"><a href="index.html">Forge Tests</a> / Common</div>
+
+<div class="header">
+   <h1>Common Tests</h1>
+</div>
+
+<div class="content">
+
+<fieldset class="section">
+   <ul>
+      <li>Test various Forge components.</li>
+      <li>See JavaScript console for more detailed output.</li>
+   </ul>
+</fieldset>
+
+<fieldset class="section">
+<legend>Control</legend>
+   <button id="start">Start</button>
+   <button id="reset">Reset</button>
+   <br/>
+   <input id="scroll" type="checkbox" />Scroll Tests
+   <br/>
+   <button id="keygen">Generate RSA key pair</button>
+   <button id="certgen">Generate RSA certificate</button>
+   <input id="bits" value="1024"/>bits
+</fieldset>
+
+<fieldset class="section">
+<legend>Progress</legend>
+Status: <span id="status">?</span><br/>
+Pass: <span id="pass">?</span>/<span id="total">?</span><br/>
+Fail: <span id="fail">?</span>
+</fieldset>
+
+<fieldset class="section">
+<legend>Tests</legend>
+<div id="tests"></div>
+</fieldset>
+
+</div>
+
+</body>
+</html>
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/common.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/common.js
new file mode 100644
index 0000000..57dfbc4
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/common.js
@@ -0,0 +1,2199 @@
+/**
+ * Forge Common Tests
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2009-2012 Digital Bazaar, Inc. All rights reserved.
+ */
+jQuery(function($)
+{
+   // logging category
+   var cat = 'forge.tests.common';
+
+   // local alias
+   var forge = window.forge;
+
+   var tests = [];
+   var passed = 0;
+   var failed = 0;
+
+   var init = function()
+   {
+      passed = failed = 0;
+      $('.ready,.testing,.pass,.fail')
+         .removeClass('ready testing pass fail');
+      $('#status')
+         .text('Ready.')
+         .addClass('ready');
+      $('#total').text(tests.length);
+      $('#pass').text(passed);
+      $('#fail').text(failed);
+      $('.expect').empty();
+      $('.result').empty();
+      $('.time').empty();
+      $('.timePer').empty();
+      $('#start').attr('disabled', '');
+   };
+
+   var start = function()
+   {
+      $('#start').attr('disabled', 'true');
+      // meta! use tasks to run the task tests
+      forge.task.start({
+         type: 'test',
+         run: function(task) {
+            task.next('starting', function(task) {
+               forge.log.debug(cat, 'start');
+               $('#status')
+                  .text('Testing...')
+                  .addClass('testing')
+                  .removeClass('idle');
+            });
+            $.each(tests, function(i, test) {
+               task.next('test', function(task) {
+                  var title = $('li:first', test.container);
+                  if($('#scroll:checked').length === 1)
+                  {
+                     $('html,body').animate({scrollTop: title.offset().top});
+                  }
+                  title.addClass('testing');
+                  test.run(task, test);
+               });
+               task.next('test', function(task) {
+                  $('li:first', test.container).removeClass('testing');
+               });
+            });
+            task.next('success', function(task) {
+               forge.log.debug(cat, 'done');
+               if(failed === 0) {
+                  $('#status')
+                     .text('PASS')
+                     .addClass('pass')
+                     .removeClass('testing');
+               } else {
+                  // FIXME: should just be hitting failure() below
+                  $('#status')
+                     .text('FAIL')
+                     .addClass('fail')
+                     .removeClass('testing');
+               }
+            });
+         },
+         failure: function() {
+            $('#status')
+               .text('FAIL')
+               .addClass('fail')
+               .removeClass('testing');
+         }
+      });
+   };
+
+   $('#start').click(function() {
+      start();
+   });
+
+   $('#reset').click(function() {
+      init();
+   });
+
+   $('#keygen').click(function() {
+      var bits = $('#bits')[0].value;
+      var keys = forge.pki.rsa.generateKeyPair(bits);
+      forge.log.debug(cat, 'generating ' + bits + '-bit RSA key-pair...');
+      setTimeout(function()
+      {
+         forge.log.debug(cat, 'private key:', keys.privateKey);
+         forge.log.debug(cat, forge.pki.privateKeyToPem(keys.privateKey));
+         forge.log.debug(cat, 'public key:', keys.publicKey);
+         forge.log.debug(cat, forge.pki.publicKeyToPem(keys.publicKey));
+
+         forge.log.debug(cat, 'testing sign/verify...');
+         setTimeout(function()
+         {
+            // do sign/verify test
+            try
+            {
+               var md = forge.md.sha1.create();
+               md.update('foo');
+               var signature = keys.privateKey.sign(md);
+               keys.publicKey.verify(md.digest().getBytes(), signature);
+               forge.log.debug(cat, 'sign/verify success');
+            }
+            catch(ex)
+            {
+               forge.log.error(cat, 'sign/verify failure', ex);
+            }
+         }, 0);
+      }, 0);
+   });
+
+   $('#certgen').click(function() {
+      var bits = $('#bits')[0].value;
+      forge.log.debug(cat, 'generating ' + bits +
+         '-bit RSA key-pair and certificate...');
+      setTimeout(function()
+      {
+         try
+         {
+            var keys = forge.pki.rsa.generateKeyPair(bits);
+            var cert = forge.pki.createCertificate();
+            cert.serialNumber = '01';
+            cert.validity.notBefore = new Date();
+            cert.validity.notAfter = new Date();
+            cert.validity.notAfter.setFullYear(
+               cert.validity.notBefore.getFullYear() + 1);
+            var attrs = [{
+               name: 'commonName',
+               value: 'mycert'
+            }, {
+               name: 'countryName',
+               value: 'US'
+            }, {
+               shortName: 'ST',
+               value: 'Virginia'
+            }, {
+               name: 'localityName',
+               value: 'Blacksburg'
+            }, {
+               name: 'organizationName',
+               value: 'Test'
+            }, {
+               shortName: 'OU',
+               value: 'Test'
+            }];
+            cert.setSubject(attrs);
+            cert.setIssuer(attrs);
+            cert.setExtensions([{
+               name: 'basicConstraints',
+               cA: true
+            }, {
+               name: 'keyUsage',
+               keyCertSign: true
+            }, {
+               name: 'subjectAltName',
+               altNames: [{
+                  type: 6, // URI
+                  value: 'http://localhost/dataspace/person/myname#this'
+               }]
+            }]);
+            // FIXME: add subjectKeyIdentifier extension
+            // FIXME: add authorityKeyIdentifier extension
+            cert.publicKey = keys.publicKey;
+
+            // self-sign certificate
+            cert.sign(keys.privateKey);
+
+            forge.log.debug(cat, 'certificate:', cert);
+            //forge.log.debug(cat,
+            //   forge.asn1.prettyPrint(forge.pki.certificateToAsn1(cert)));
+            forge.log.debug(cat, forge.pki.certificateToPem(cert));
+
+            // verify certificate
+            forge.log.debug(cat, 'verified', cert.verify(cert));
+         }
+         catch(ex)
+         {
+            forge.log.error(cat, ex, ex.message ? ex.message : '');
+         }
+      }, 0);
+   });
+
+   var addTest = function(name, run)
+   {
+      var container = $('<ul><li>Test ' + name + '</li><ul/></ul>');
+      var expect = $('<li>Expect: <span class="expect"/></li>');
+      var result = $('<li>Result: <span class="result"/></li>');
+      var time = $('<li>Time: <span class="time"/></li>');
+      var timePer = $('<li>Time Per Iteration: <span class="timePer"/></li>');
+      $('ul', container)
+         .append(expect)
+         .append(result)
+         .append(time)
+         .append(timePer);
+      $('#tests').append(container);
+      var test = {
+         container: container,
+         startTime: null,
+         run: function(task, test) {
+            test.startTime = new Date();
+            run(task, test);
+         },
+         expect: $('span', expect),
+         result: $('span', result),
+         check: function() {
+            var e = test.expect.text();
+            var r = test.result.text();
+            (e == r) ? test.pass() : test.fail();
+         },
+         pass: function(iterations) {
+            var dt = new Date() - test.startTime;
+            if(!iterations)
+            {
+               iterations = 1;
+            }
+            var dti = (dt / iterations);
+            passed += 1;
+            $('#pass').text(passed);
+            $('li:first', container).addClass('pass');
+            $('span.time', container).html(dt + 'ms');
+            $('span.timePer', container).html(dti + 'ms');
+         },
+         fail: function(iterations) {
+            var dt = new Date() - test.startTime;
+            if(!iterations)
+            {
+               iterations = 1;
+            }
+            var dti = (dt / iterations);
+            failed += 1;
+            $('#fail').text(failed);
+            $('li:first', container).addClass('fail');
+            $('span.time', container).html(dt + 'ms');
+            $('span.timePer', container).html(dti + 'ms');
+         }
+      };
+      tests.push(test);
+   };
+
+   addTest('buffer put bytes', function(task, test)
+   {
+      ba = forge.util.createBuffer();
+      ba.putByte(1);
+      ba.putByte(2);
+      ba.putByte(3);
+      ba.putByte(4);
+      ba.putInt32(4);
+      ba.putByte(1);
+      ba.putByte(2);
+      ba.putByte(3);
+      ba.putInt32(4294967295);
+      var hex = ba.toHex();
+      var bytes = [];
+      while(ba.length() > 0)
+      {
+         bytes.push(ba.getByte());
+      }
+      var expect = [1, 2, 3, 4, 0, 0, 0, 4, 1, 2, 3, 255, 255, 255, 255];
+      var exHex = '0102030400000004010203ffffffff';
+      test.expect.html(exHex);
+      test.result.html(hex);
+
+      test.check();
+   });
+
+   addTest('buffer from hex', function(task, test)
+   {
+      var exHex = '0102030400000004010203ffffffff';
+      test.expect.html(exHex);
+
+      var buf = forge.util.createBuffer();
+      buf.putBytes(forge.util.hexToBytes(exHex));
+      test.result.html(buf.toHex());
+
+      test.check();
+   });
+
+   addTest('base64 encode', function(task, test)
+   {
+      var s1 = '00010203050607080A0B0C0D0F1011121415161719';
+      var s2 = 'MDAwMTAyMDMwNTA2MDcwODBBMEIwQzBEMEYxMDExMTIxNDE1MTYxNzE5';
+      test.expect.html(s2);
+
+      var out = forge.util.encode64(s1);
+      test.result.html(out);
+
+      test.check();
+   });
+
+   addTest('base64 decode', function(task, test)
+   {
+      var s1 = '00010203050607080A0B0C0D0F1011121415161719';
+      var s2 = 'MDAwMTAyMDMwNTA2MDcwODBBMEIwQzBEMEYxMDExMTIxNDE1MTYxNzE5';
+      test.expect.html(s1);
+
+      var out = forge.util.decode64(s2);
+      test.result.html(out);
+
+      test.check();
+   });
+
+   addTest('md5 empty', function(task, test)
+   {
+      var expect = 'd41d8cd98f00b204e9800998ecf8427e';
+      test.expect.html(expect);
+      var md = forge.md.md5.create();
+      test.result.html(md.digest().toHex());
+      test.check();
+   });
+
+   addTest('md5 "abc"', function(task, test)
+   {
+      var expect = '900150983cd24fb0d6963f7d28e17f72';
+      test.expect.html(expect);
+      var md = forge.md.md5.create();
+      md.update('abc');
+      test.result.html(md.digest().toHex());
+      test.check();
+   });
+
+   addTest('md5 "The quick brown fox jumps over the lazy dog"',
+      function(task, test)
+   {
+      var expect = '9e107d9d372bb6826bd81d3542a419d6';
+      test.expect.html(expect);
+      var md = forge.md.md5.create();
+      md.start();
+      md.update('The quick brown fox jumps over the lazy dog');
+      test.result.html(md.digest().toHex());
+      test.check();
+   });
+
+   // c'è
+   addTest('md5 "c\'\u00e8"', function(task, test)
+   {
+      var expect = '8ef7c2941d78fe89f31e614437c9db59';
+      test.expect.html(expect);
+      var md = forge.md.md5.create();
+      md.update("c'\u00e8", 'utf8');
+      test.result.html(md.digest().toHex());
+      test.check();
+   });
+
+   addTest('md5 "THIS IS A MESSAGE"',
+   function(task, test)
+   {
+      var expect = '78eebfd9d42958e3f31244f116ab7bbe';
+      test.expect.html(expect);
+      var md = forge.md.md5.create();
+      md.start();
+      md.update('THIS IS ');
+      md.update('A MESSAGE');
+      // do twice to check continuing digest
+      test.result.html(md.digest().toHex());
+      test.result.html(md.digest().toHex());
+      test.check();
+   });
+
+   addTest('md5 long message',
+   function(task, test)
+   {
+      var input = forge.util.createBuffer();
+      input.putBytes(forge.util.hexToBytes(
+         '0100002903018d32e9c6dc423774c4c39a5a1b78f44cc2cab5f676d39' +
+         'f703d29bfa27dfeb870000002002f0100'));
+      input.putBytes(forge.util.hexToBytes(
+         '0200004603014c2c1e835d39da71bc0857eb04c2b50fe90dbb2a8477f' +
+         'e7364598d6f0575999c20a6c7248c5174da6d03ac711888f762fc4ed5' +
+         '4f7254b32273690de849c843073d002f00'));
+      input.putBytes(forge.util.hexToBytes(
+         '0b0003d20003cf0003cc308203c8308202b0a003020102020100300d0' +
+         '6092a864886f70d0101050500308186310b3009060355040613025553' +
+         '311d301b060355040a13144469676974616c2042617a6161722c20496' +
+         'e632e31443042060355040b133b4269746d756e6b206c6f63616c686f' +
+         '73742d6f6e6c7920436572746966696361746573202d20417574686f7' +
+         '2697a6174696f6e207669612042545031123010060355040313096c6f' +
+         '63616c686f7374301e170d3130303231343137303931395a170d32303' +
+         '03231333137303931395a308186310b3009060355040613025553311d' +
+         '301b060355040a13144469676974616c2042617a6161722c20496e632' +
+         'e31443042060355040b133b4269746d756e6b206c6f63616c686f7374' +
+         '2d6f6e6c7920436572746966696361746573202d20417574686f72697' +
+         'a6174696f6e207669612042545031123010060355040313096c6f6361' +
+         '6c686f737430820122300d06092a864886f70d01010105000382010f0' +
+         '03082010a0282010100dc436f17d6909d8a9d6186ea218eb5c86b848b' +
+         'ae02219bd56a71203daf07e81bc19e7e98134136bcb012881864bf03b' +
+         '3774652ad5eab85dba411a5114ffeac09babce75f31314345512cd87c' +
+         '91318b2e77433270a52185fc16f428c3ca412ad6e9484bc2fb87abb4e' +
+         '8fb71bf0f619e31a42340b35967f06c24a741a31c979c0bb8921a90a4' +
+         '7025fbeb8adca576979e70a56830c61170c9647c18c0794d68c0df38f' +
+         '3aac5fc3b530e016ea5659715339f3f3c209cdee9dbe794b5af92530c' +
+         '5754c1d874b78974bfad994e0dfc582275e79feb522f6e4bcc2b2945b' +
+         'aedfb0dbdaebb605f9483ff0bea29ecd5f4d6f2769965d1b3e04f8422' +
+         '716042680011ff676f0203010001a33f303d300c0603551d130101ff0' +
+         '4023000300e0603551d0f0101ff0404030204f0301d0603551d250416' +
+         '301406082b0601050507030106082b06010505070302300d06092a864' +
+         '886f70d010105050003820101009c4562be3f2d8d8e388085a697f2f1' +
+         '06eaeff4992a43f198fe3dcf15c8229cf1043f061a38204f73d86f4fb' +
+         '6348048cc5279ed719873aa10e3773d92b629c2c3fcce04012c81ba3b' +
+         '4ec451e9644ec5191078402d845e05d02c7b4d974b4588276e5037aba' +
+         '7ef26a8bddeb21e10698c82f425e767dc401adf722fa73ab78cfa069b' +
+         'd69052d7ca6a75cc9225550e315d71c5f8764362ea4dbc6ecb837a847' +
+         '1043c5a7f826a71af145a053090bd4bccca6a2c552841cdb1908a8352' +
+         'f49283d2e641acdef667c7543af441a16f8294251e2ac376fa507b53a' +
+         'e418dd038cd20cef1e7bfbf5ae03a7c88d93d843abaabbdc5f3431132' +
+         'f3e559d2dd414c3eda38a210b8'));
+      input.putBytes(forge.util.hexToBytes('0e000000'));
+      input.putBytes(forge.util.hexToBytes(
+         '10000102010026a220b7be857402819b78d81080d01a682599bbd0090' +
+         '2985cc64edf8e520e4111eb0e1729a14ffa3498ca259cc9ad6fc78fa1' +
+         '30d968ebdb78dc0b950c0aa44355f13ba678419185d7e4608fe178ca6' +
+         'b2cef33e4193778d1a70fe4d0dfcb110be4bbb4dbaa712177655728f9' +
+         '14ab4c0f6c4aef79a46b3d996c82b2ebe9ed1748eb5cace7dc44fb67e' +
+         '73f452a047f2ed199b3d50d5db960acf03244dc8efa4fc129faf8b65f' +
+         '9e52e62b5544722bd17d2358e817a777618a4265a3db277fc04851a82' +
+         'a91fe6cdcb8127f156e0b4a5d1f54ce2742eb70c895f5f8b85f5febe6' +
+         '9bc73e891f9280826860a0c2ef94c7935e6215c3c4cd6b0e43e80cca3' +
+         '96d913d36be'));
+
+      var expect = 'd15a2da0e92c3da55dc573f885b6e653';
+      test.expect.html(expect);
+
+      var md = forge.md.md5.create();
+      md.start();
+      md.update(input.getBytes());
+      test.result.html(md.digest().toHex());
+
+      test.check();
+   });
+
+   addTest('sha-1 empty', function(task, test)
+   {
+      var expect = 'da39a3ee5e6b4b0d3255bfef95601890afd80709';
+      test.expect.html(expect);
+      var md = forge.md.sha1.create();
+      test.result.html(md.digest().toHex());
+      test.check();
+   });
+
+   addTest('sha-1 "abc"', function(task, test)
+   {
+      var expect = 'a9993e364706816aba3e25717850c26c9cd0d89d';
+      test.expect.html(expect);
+      var md = forge.md.sha1.create();
+      md.update('abc');
+      test.result.html(md.digest().toHex());
+      test.check();
+   });
+
+   addTest('sha-1 "The quick brown fox jumps over the lazy dog"',
+      function(task, test)
+   {
+      var expect = '2fd4e1c67a2d28fced849ee1bb76e7391b93eb12';
+      test.expect.html(expect);
+      var md = forge.md.sha1.create();
+      md.start();
+      md.update('The quick brown fox jumps over the lazy dog');
+      test.result.html(md.digest().toHex());
+      test.check();
+   });
+
+   // c'è
+   addTest('sha-1 "c\'\u00e8"', function(task, test)
+   {
+      var expect = '98c9a3f804daa73b68a5660d032499a447350c0d';
+      test.expect.html(expect);
+      var md = forge.md.sha1.create();
+      md.update("c'\u00e8", 'utf8');
+      test.result.html(md.digest().toHex());
+      test.check();
+   });
+
+   addTest('sha-1 "THIS IS A MESSAGE"',
+   function(task, test)
+   {
+      var expect = '5f24f4d6499fd2d44df6c6e94be8b14a796c071d';
+      test.expect.html(expect);
+      var md = forge.md.sha1.create();
+      md.start();
+      md.update('THIS IS ');
+      md.update('A MESSAGE');
+      // do twice to check continuing digest
+      test.result.html(md.digest().toHex());
+      test.result.html(md.digest().toHex());
+      test.check();
+   });
+
+   // other browsers too slow for this test
+   if($.browser.webkit)
+   {
+      addTest('sha-1 long message',
+      function(task, test)
+      {
+         var expect = '34aa973cd4c4daa4f61eeb2bdbad27316534016f';
+         test.expect.html(expect);
+         var md = forge.md.sha1.create();
+         md.start();
+         md.update(forge.util.fillString('a', 1000000));
+         // do twice to check continuing digest
+         test.result.html(md.digest().toHex());
+         test.result.html(md.digest().toHex());
+         test.check();
+      });
+   }
+
+   addTest('sha-256 "abc"', function(task, test)
+   {
+      var expect =
+         'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad';
+      test.expect.html(expect);
+      var md = forge.md.sha256.create();
+      md.update('abc');
+      test.result.html(md.digest().toHex());
+      test.check();
+   });
+
+   // c'è
+   addTest('sha-256 "c\'\u00e8"', function(task, test)
+   {
+      var expect =
+         '1aa15c717afffd312acce2217ce1c2e5dabca53c92165999132ec9ca5decdaca';
+      test.expect.html(expect);
+      var md = forge.md.sha256.create();
+      md.update("c'\u00e8", 'utf8');
+      test.result.html(md.digest().toHex());
+      test.check();
+   });
+
+   addTest('sha-256 "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"',
+   function(task, test)
+   {
+      var expect =
+         '248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1';
+      test.expect.html(expect);
+      var md = forge.md.sha256.create();
+      md.start();
+      md.update('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq');
+      test.result.html(md.digest().toHex());
+      test.check();
+   });
+
+   // other browsers too slow for this test
+   if($.browser.webkit)
+   {
+      addTest('sha-256 long message',
+      function(task, test)
+      {
+         var expect =
+            'cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0';
+         test.expect.html(expect);
+         var md = forge.md.sha256.create();
+         md.start();
+         md.update(forge.util.fillString('a', 1000000));
+         // do twice to check continuing digest
+         test.result.html(md.digest().toHex());
+         test.result.html(md.digest().toHex());
+         test.check();
+      });
+   }
+
+   addTest('hmac md5 "Hi There", 16-byte key', function(task, test)
+   {
+      var expect = '9294727a3638bb1c13f48ef8158bfc9d';
+      test.expect.html(expect);
+      var key = forge.util.hexToBytes(
+         '0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b');
+      var hmac = forge.hmac.create();
+      hmac.start('MD5', key);
+      hmac.update('Hi There');
+      test.result.html(hmac.digest().toHex());
+      test.check();
+   });
+
+   addTest('hmac md5 "what do ya want for nothing?", "Jefe" key',
+      function(task, test)
+   {
+      var expect = '750c783e6ab0b503eaa86e310a5db738';
+      test.expect.html(expect);
+      var hmac = forge.hmac.create();
+      hmac.start('MD5', 'Jefe');
+      hmac.update('what do ya want for nothing?');
+      test.result.html(hmac.digest().toHex());
+      test.check();
+   });
+
+   addTest('hmac md5 "Test Using Larger Than Block-Size Key - ' +
+      'Hash Key First", 80-byte key', function(task, test)
+   {
+      var expect = '6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd';
+      test.expect.html(expect);
+      var key = forge.util.hexToBytes(
+         'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
+         'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
+         'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
+         'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
+      var hmac = forge.hmac.create();
+      hmac.start('MD5', key);
+      hmac.update('Test Using Larger Than Block-Size Key - Hash Key First');
+      test.result.html(hmac.digest().toHex());
+      test.check();
+   });
+
+   addTest('hmac sha-1 "Hi There", 20-byte key', function(task, test)
+   {
+      var expect = 'b617318655057264e28bc0b6fb378c8ef146be00';
+      test.expect.html(expect);
+      var key = forge.util.hexToBytes(
+         '0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b');
+      var hmac = forge.hmac.create();
+      hmac.start('SHA1', key);
+      hmac.update('Hi There');
+      test.result.html(hmac.digest().toHex());
+      test.check();
+   });
+
+   addTest('hmac sha-1 "what do ya want for nothing?", "Jefe" key',
+      function(task, test)
+   {
+      var expect = 'effcdf6ae5eb2fa2d27416d5f184df9c259a7c79';
+      test.expect.html(expect);
+      var hmac = forge.hmac.create();
+      hmac.start('SHA1', 'Jefe');
+      hmac.update('what do ya want for nothing?');
+      test.result.html(hmac.digest().toHex());
+      test.check();
+   });
+
+   addTest('hmac sha-1 "Test Using Larger Than Block-Size Key - ' +
+      'Hash Key First", 80-byte key', function(task, test)
+   {
+      var expect = 'aa4ae5e15272d00e95705637ce8a3b55ed402112';
+      test.expect.html(expect);
+      var key = forge.util.hexToBytes(
+         'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
+         'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
+         'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
+         'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
+      var hmac = forge.hmac.create();
+      hmac.start('SHA1', key);
+      hmac.update('Test Using Larger Than Block-Size Key - Hash Key First');
+      test.result.html(hmac.digest().toHex());
+      test.check();
+   });
+
+   addTest('pbkdf2 hmac-sha-1 c=1', function(task, test)
+   {
+      var expect = '0c60c80f961f0e71f3a9b524af6012062fe037a6';
+      var dk = forge.pkcs5.pbkdf2('password', 'salt', 1, 20);
+      test.expect.html(expect);
+      test.result.html(forge.util.bytesToHex(dk));
+      test.check();
+   });
+
+   addTest('pbkdf2 hmac-sha-1 c=2', function(task, test)
+   {
+      var expect = 'ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957';
+      var dk = forge.pkcs5.pbkdf2('password', 'salt', 2, 20);
+      test.expect.html(expect);
+      test.result.html(forge.util.bytesToHex(dk));
+      test.check();
+   });
+
+   addTest('pbkdf2 hmac-sha-1 c=2', function(task, test)
+   {
+      var expect = 'ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957';
+      var dk = forge.pkcs5.pbkdf2('password', 'salt', 2, 20);
+      test.expect.html(expect);
+      test.result.html(forge.util.bytesToHex(dk));
+      test.check();
+   });
+
+   addTest('pbkdf2 hmac-sha-1 c=5 keylen=8', function(task, test)
+   {
+      var expect = 'd1daa78615f287e6';
+      var salt = forge.util.hexToBytes('1234567878563412');
+      var dk = forge.pkcs5.pbkdf2('password', salt, 5, 8);
+      test.expect.html(expect);
+      test.result.html(forge.util.bytesToHex(dk));
+      test.check();
+   });
+
+   // other browsers too slow for this test
+   if($.browser.webkit)
+   {
+      addTest('pbkdf2 hmac-sha-1 c=4096', function(task, test)
+      {
+         var expect = '4b007901b765489abead49d926f721d065a429c1';
+         var dk = forge.pkcs5.pbkdf2('password', 'salt', 4096, 20);
+         test.expect.html(expect);
+         test.result.html(forge.util.bytesToHex(dk));
+         test.check();
+      });
+   }
+
+   /* too slow for javascript
+   addTest('pbkdf2 hmac-sha-1 c=16777216', function(task, test)
+   {
+      var expect = 'eefe3d61cd4da4e4e9945b3d6ba2158c2634e984';
+      var dk = forge.pkcs5.pbkdf2('password', 'salt', 16777216, 20);
+      test.expect.html(expect);
+      test.result.html(forge.util.bytesToHex(dk));
+      test.check();
+   });*/
+
+   addTest('aes-128 encrypt', function(task, test)
+   {
+      var block = [];
+      block.push(0x00112233);
+      block.push(0x44556677);
+      block.push(0x8899aabb);
+      block.push(0xccddeeff);
+      var plain = block;
+
+      var key = [];
+      key.push(0x00010203);
+      key.push(0x04050607);
+      key.push(0x08090a0b);
+      key.push(0x0c0d0e0f);
+
+      var expect = [];
+      expect.push(0x69c4e0d8);
+      expect.push(0x6a7b0430);
+      expect.push(0xd8cdb780);
+      expect.push(0x70b4c55a);
+
+      test.expect.html('69c4e0d86a7b0430d8cdb78070b4c55a');
+
+      var output = [];
+      var w = forge.aes._expandKey(key, false);
+      forge.aes._updateBlock(w, block, output, false);
+
+      var out = forge.util.createBuffer();
+      out.putInt32(output[0]);
+      out.putInt32(output[1]);
+      out.putInt32(output[2]);
+      out.putInt32(output[3]);
+      test.result.html(out.toHex());
+
+      test.check();
+   });
+
+   addTest('aes-128 decrypt', function(task, test)
+   {
+      var block = [];
+      block.push(0x69c4e0d8);
+      block.push(0x6a7b0430);
+      block.push(0xd8cdb780);
+      block.push(0x70b4c55a);
+
+      var key = [];
+      key.push(0x00010203);
+      key.push(0x04050607);
+      key.push(0x08090a0b);
+      key.push(0x0c0d0e0f);
+
+      var expect = [];
+      expect.push(0x00112233);
+      expect.push(0x44556677);
+      expect.push(0x8899aabb);
+      expect.push(0xccddeeff);
+
+      test.expect.html('00112233445566778899aabbccddeeff');
+
+      var output = [];
+      w = forge.aes._expandKey(key, true);
+      forge.aes._updateBlock(w, block, output, true);
+
+      var out = forge.util.createBuffer();
+      out.putInt32(output[0]);
+      out.putInt32(output[1]);
+      out.putInt32(output[2]);
+      out.putInt32(output[3]);
+      test.result.html(out.toHex());
+
+      test.check();
+   });
+
+   addTest('aes-192 encrypt', function(task, test)
+   {
+      var block = [];
+      block.push(0x00112233);
+      block.push(0x44556677);
+      block.push(0x8899aabb);
+      block.push(0xccddeeff);
+      var plain = block;
+
+      var key = [];
+      key.push(0x00010203);
+      key.push(0x04050607);
+      key.push(0x08090a0b);
+      key.push(0x0c0d0e0f);
+      key.push(0x10111213);
+      key.push(0x14151617);
+
+      var expect = [];
+      expect.push(0xdda97ca4);
+      expect.push(0x864cdfe0);
+      expect.push(0x6eaf70a0);
+      expect.push(0xec0d7191);
+
+      test.expect.html('dda97ca4864cdfe06eaf70a0ec0d7191');
+
+      var output = [];
+      var w = forge.aes._expandKey(key, false);
+      forge.aes._updateBlock(w, block, output, false);
+
+      var out = forge.util.createBuffer();
+      out.putInt32(output[0]);
+      out.putInt32(output[1]);
+      out.putInt32(output[2]);
+      out.putInt32(output[3]);
+      test.result.html(out.toHex());
+
+      test.check();
+   });
+
+   addTest('aes-192 decrypt', function(task, test)
+   {
+      var block = [];
+      block.push(0xdda97ca4);
+      block.push(0x864cdfe0);
+      block.push(0x6eaf70a0);
+      block.push(0xec0d7191);
+
+      var key = [];
+      key.push(0x00010203);
+      key.push(0x04050607);
+      key.push(0x08090a0b);
+      key.push(0x0c0d0e0f);
+      key.push(0x10111213);
+      key.push(0x14151617);
+
+      var expect = [];
+      expect.push(0x00112233);
+      expect.push(0x44556677);
+      expect.push(0x8899aabb);
+      expect.push(0xccddeeff);
+
+      test.expect.html('00112233445566778899aabbccddeeff');
+
+      var output = [];
+      w = forge.aes._expandKey(key, true);
+      forge.aes._updateBlock(w, block, output, true);
+
+      var out = forge.util.createBuffer();
+      out.putInt32(output[0]);
+      out.putInt32(output[1]);
+      out.putInt32(output[2]);
+      out.putInt32(output[3]);
+      test.result.html(out.toHex());
+
+      test.check();
+   });
+
+   addTest('aes-256 encrypt', function(task, test)
+   {
+      var block = [];
+      block.push(0x00112233);
+      block.push(0x44556677);
+      block.push(0x8899aabb);
+      block.push(0xccddeeff);
+      var plain = block;
+
+      var key = [];
+      key.push(0x00010203);
+      key.push(0x04050607);
+      key.push(0x08090a0b);
+      key.push(0x0c0d0e0f);
+      key.push(0x10111213);
+      key.push(0x14151617);
+      key.push(0x18191a1b);
+      key.push(0x1c1d1e1f);
+
+      var expect = [];
+      expect.push(0x8ea2b7ca);
+      expect.push(0x516745bf);
+      expect.push(0xeafc4990);
+      expect.push(0x4b496089);
+
+      test.expect.html('8ea2b7ca516745bfeafc49904b496089');
+
+      var output = [];
+      var w = forge.aes._expandKey(key, false);
+      forge.aes._updateBlock(w, block, output, false);
+
+      var out = forge.util.createBuffer();
+      out.putInt32(output[0]);
+      out.putInt32(output[1]);
+      out.putInt32(output[2]);
+      out.putInt32(output[3]);
+      test.result.html(out.toHex());
+
+      test.check();
+   });
+
+   addTest('aes-256 decrypt', function(task, test)
+   {
+      var block = [];
+      block.push(0x8ea2b7ca);
+      block.push(0x516745bf);
+      block.push(0xeafc4990);
+      block.push(0x4b496089);
+
+      var key = [];
+      key.push(0x00010203);
+      key.push(0x04050607);
+      key.push(0x08090a0b);
+      key.push(0x0c0d0e0f);
+      key.push(0x10111213);
+      key.push(0x14151617);
+      key.push(0x18191a1b);
+      key.push(0x1c1d1e1f);
+
+      var expect = [];
+      expect.push(0x00112233);
+      expect.push(0x44556677);
+      expect.push(0x8899aabb);
+      expect.push(0xccddeeff);
+
+      test.expect.html('00112233445566778899aabbccddeeff');
+
+      var output = [];
+      w = forge.aes._expandKey(key, true);
+      forge.aes._updateBlock(w, block, output, true);
+
+      var out = forge.util.createBuffer();
+      out.putInt32(output[0]);
+      out.putInt32(output[1]);
+      out.putInt32(output[2]);
+      out.putInt32(output[3]);
+      test.result.html(out.toHex());
+
+      test.check();
+   });
+
+   (function()
+   {
+      var keys = [
+         '06a9214036b8a15b512e03d534120006',
+         'c286696d887c9aa0611bbb3e2025a45a',
+         '6c3ea0477630ce21a2ce334aa746c2cd',
+         '56e47a38c5598974bc46903dba290349'
+      ];
+
+      var ivs = [
+         '3dafba429d9eb430b422da802c9fac41',
+         '562e17996d093d28ddb3ba695a2e6f58',
+         'c782dc4c098c66cbd9cd27d825682c81',
+         '8ce82eefbea0da3c44699ed7db51b7d9'
+      ];
+
+      var inputs = [
+         'Single block msg',
+         '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f',
+         'This is a 48-byte message (exactly 3 AES blocks)',
+         'a0a1a2a3a4a5a6a7a8a9aaabacadaeaf' +
+            'b0b1b2b3b4b5b6b7b8b9babbbcbdbebf' +
+            'c0c1c2c3c4c5c6c7c8c9cacbcccdcecf' +
+            'd0d1d2d3d4d5d6d7d8d9dadbdcdddedf'
+      ];
+
+      var outputs = [
+         'e353779c1079aeb82708942dbe77181a',
+         'd296cd94c2cccf8a3a863028b5e1dc0a7586602d253cfff91b8266bea6d61ab1',
+         'd0a02b3836451753d493665d33f0e886' +
+            '2dea54cdb293abc7506939276772f8d5' +
+            '021c19216bad525c8579695d83ba2684',
+         'c30e32ffedc0774e6aff6af0869f71aa' +
+            '0f3af07a9a31a9c684db207eb0ef8e4e' +
+            '35907aa632c3ffdf868bb7b29d3d46ad' +
+            '83ce9f9a102ee99d49a53e87f4c3da55'
+      ];
+
+      for(var i = 0; i < keys.length; ++i)
+      {
+         (function(i)
+         {
+            var key = forge.util.hexToBytes(keys[i]);
+            var iv = forge.util.hexToBytes(ivs[i]);
+            var input = (i & 1) ? forge.util.hexToBytes(inputs[i]) : inputs[i];
+            var output = forge.util.hexToBytes(outputs[i]);
+
+            addTest('aes-128 cbc encrypt', function(task, test)
+            {
+               // encrypt w/no padding
+               test.expect.html(outputs[i]);
+               var cipher = forge.aes.createEncryptionCipher(key);
+               cipher.start(iv);
+               cipher.update(forge.util.createBuffer(input));
+               cipher.finish(function(){return true;});
+               test.result.html(cipher.output.toHex());
+               test.check();
+            });
+
+            addTest('aes-128 cbc decrypt', function(task, test)
+            {
+               // decrypt w/no padding
+               test.expect.html(inputs[i]);
+               var cipher = forge.aes.createDecryptionCipher(key);
+               cipher.start(iv);
+               cipher.update(forge.util.createBuffer(output));
+               cipher.finish(function(){return true;});
+               var out = (i & 1) ?
+                  cipher.output.toHex() : cipher.output.bytes();
+               test.result.html(out);
+               test.check();
+            });
+         })(i);
+      }
+   })();
+
+   (function()
+   {
+      var keys = [
+         '00000000000000000000000000000000',
+         '2b7e151628aed2a6abf7158809cf4f3c',
+         '2b7e151628aed2a6abf7158809cf4f3c',
+         '2b7e151628aed2a6abf7158809cf4f3c',
+         '2b7e151628aed2a6abf7158809cf4f3c',
+         '00000000000000000000000000000000'
+      ];
+
+      var ivs = [
+         '80000000000000000000000000000000',
+         '000102030405060708090a0b0c0d0e0f',
+         '3B3FD92EB72DAD20333449F8E83CFB4A',
+         'C8A64537A0B3A93FCDE3CDAD9F1CE58B',
+         '26751F67A3CBB140B1808CF187A4F4DF',
+         '60f9ff04fac1a25657bf5b36b5efaf75'
+      ];
+
+      var inputs = [
+         '00000000000000000000000000000000',
+         '6bc1bee22e409f96e93d7e117393172a',
+         'ae2d8a571e03ac9c9eb76fac45af8e51',
+         '30c81c46a35ce411e5fbc1191a0a52ef',
+         'f69f2445df4f9b17ad2b417be66c3710',
+         'This is a 48-byte message (exactly 3 AES blocks)'
+      ];
+
+      var outputs = [
+         '3ad78e726c1ec02b7ebfe92b23d9ec34',
+         '3b3fd92eb72dad20333449f8e83cfb4a',
+         'c8a64537a0b3a93fcde3cdad9f1ce58b',
+         '26751f67a3cbb140b1808cf187a4f4df',
+         'c04b05357c5d1c0eeac4c66f9ff7f2e6',
+         '52396a2ba1ba420c5e5b699a814944d8' +
+           'f4e7fbf984a038319fbc0b4ee45cfa6f' +
+           '07b2564beab5b5e92dbd44cb345f49b4'
+      ];
+
+      for(var i = 0; i < keys.length; ++i)
+      {
+         (function(i)
+         {
+            var key = forge.util.hexToBytes(keys[i]);
+            var iv = forge.util.hexToBytes(ivs[i]);
+            var input = (i !== 5) ?
+               forge.util.hexToBytes(inputs[i]) : inputs[i];
+            var output = forge.util.hexToBytes(outputs[i]);
+
+            addTest('aes-128 cfb encrypt', function(task, test)
+            {
+               // encrypt
+               test.expect.html(outputs[i]);
+               var cipher = forge.aes.createEncryptionCipher(key, 'CFB');
+               cipher.start(iv);
+               cipher.update(forge.util.createBuffer(input));
+               cipher.finish();
+               test.result.html(cipher.output.toHex());
+               test.check();
+            });
+
+            addTest('aes-128 cfb decrypt', function(task, test)
+            {
+               // decrypt
+               test.expect.html(inputs[i]);
+               var cipher = forge.aes.createDecryptionCipher(key, 'CFB');
+               cipher.start(iv);
+               cipher.update(forge.util.createBuffer(output));
+               cipher.finish();
+               var out = (i !== 5) ?
+                 cipher.output.toHex() : cipher.output.getBytes();
+               test.result.html(out);
+               test.check();
+            });
+         })(i);
+      }
+   })();
+
+   (function()
+   {
+      var keys = [
+         '861009ec4d599fab1f40abc76e6f89880cff5833c79c548c99f9045f191cd90b'
+      ];
+
+      var ivs = [
+         'd927ad81199aa7dcadfdb4e47b6dc694'
+      ];
+
+      var inputs = [
+         'MY-DATA-AND-HERE-IS-MORE-DATA'
+      ];
+
+      var outputs = [
+         '80eb666a9fc9e263faf71e87ffc94451d7d8df7cfcf2606470351dd5ac'
+      ];
+
+      for(var i = 0; i < keys.length; ++i)
+      {
+         (function(i)
+         {
+            var key = forge.util.hexToBytes(keys[i]);
+            var iv = forge.util.hexToBytes(ivs[i]);
+            var input = inputs[i];
+            var output = forge.util.hexToBytes(outputs[i]);
+
+            addTest('aes-256 cfb encrypt', function(task, test)
+            {
+               // encrypt
+               test.expect.html(outputs[i]);
+               var cipher = forge.aes.createEncryptionCipher(key, 'CFB');
+               cipher.start(iv);
+               cipher.update(forge.util.createBuffer(input));
+               cipher.finish();
+               test.result.html(cipher.output.toHex());
+               test.check();
+            });
+
+            addTest('aes-256 cfb decrypt', function(task, test)
+            {
+               // decrypt
+               test.expect.html(inputs[i]);
+               var cipher = forge.aes.createDecryptionCipher(key, 'CFB');
+               cipher.start(iv);
+               cipher.update(forge.util.createBuffer(output));
+               cipher.finish();
+               var out = cipher.output.getBytes();
+               test.result.html(out);
+               test.check();
+            });
+         })(i);
+      }
+   })();
+
+   (function()
+   {
+      var keys = [
+         '00000000000000000000000000000000',
+         '00000000000000000000000000000000'
+      ];
+
+      var ivs = [
+         '80000000000000000000000000000000',
+         'c8ca0d6a35dbeac776e911ee16bea7d3'
+      ];
+
+      var inputs = [
+         '00000000000000000000000000000000',
+         'This is a 48-byte message (exactly 3 AES blocks)'
+      ];
+
+      var outputs = [
+         '3ad78e726c1ec02b7ebfe92b23d9ec34',
+         '39c0190727a76b2a90963426f63689cf' +
+           'cdb8a2be8e20c5e877a81a724e3611f6' +
+           '2ecc386f2e941b2441c838906002be19'
+      ];
+
+      for(var i = 0; i < keys.length; ++i)
+      {
+         (function(i)
+         {
+            var key = forge.util.hexToBytes(keys[i]);
+            var iv = forge.util.hexToBytes(ivs[i]);
+            var input = (i !== 1) ?
+               forge.util.hexToBytes(inputs[i]) : inputs[i];
+            var output = forge.util.hexToBytes(outputs[i]);
+
+            addTest('aes-128 ofb encrypt', function(task, test)
+            {
+               // encrypt w/no padding
+               test.expect.html(outputs[i]);
+               var cipher = forge.aes.createEncryptionCipher(key, 'OFB');
+               cipher.start(iv);
+               cipher.update(forge.util.createBuffer(input));
+               cipher.finish(function(){return true;});
+               test.result.html(cipher.output.toHex());
+               test.check();
+            });
+
+            addTest('aes-128 ofb decrypt', function(task, test)
+            {
+               // decrypt w/no padding
+               test.expect.html(inputs[i]);
+               var cipher = forge.aes.createDecryptionCipher(key, 'OFB');
+               cipher.start(iv);
+               cipher.update(forge.util.createBuffer(output));
+               cipher.finish(function(){return true;});
+               var out = (i !== 1) ?
+                 cipher.output.toHex() : cipher.output.getBytes();
+               test.result.html(out);
+               test.check();
+            });
+         })(i);
+      }
+   })();
+
+   (function()
+   {
+      var keys = [
+         '00000000000000000000000000000000',
+         '2b7e151628aed2a6abf7158809cf4f3c'
+      ];
+
+      var ivs = [
+         '650cdb80ff9fc758342d2bd99ee2abcf',
+         'f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff'
+      ];
+
+      var inputs = [
+         'This is a 48-byte message (exactly 3 AES blocks)',
+         '6bc1bee22e409f96e93d7e117393172a'
+      ];
+
+      var outputs = [
+         '5ede11d00e9a76ec1d5e7e811ea3dd1c' +
+           'e09ee941210f825d35718d3282796f1c' +
+           '07c3f1cb424f2b365766ab5229f5b5a4',
+         '874d6191b620e3261bef6864990db6ce'
+      ];
+
+      for(var i = 0; i < keys.length; ++i)
+      {
+         (function(i)
+         {
+            var key = forge.util.hexToBytes(keys[i]);
+            var iv = forge.util.hexToBytes(ivs[i]);
+            var input = (i !== 0) ?
+               forge.util.hexToBytes(inputs[i]) : inputs[i];
+            var output = forge.util.hexToBytes(outputs[i]);
+
+            addTest('aes-128 ctr encrypt', function(task, test)
+            {
+               // encrypt w/no padding
+               test.expect.html(outputs[i]);
+               var cipher = forge.aes.createEncryptionCipher(key, 'CTR');
+               cipher.start(iv);
+               cipher.update(forge.util.createBuffer(input));
+               cipher.finish(function(){return true;});
+               test.result.html(cipher.output.toHex());
+               test.check();
+            });
+
+            addTest('aes-128 ctr decrypt', function(task, test)
+            {
+               // decrypt w/no padding
+               test.expect.html(inputs[i]);
+               var cipher = forge.aes.createDecryptionCipher(key, 'CTR');
+               cipher.start(iv);
+               cipher.update(forge.util.createBuffer(output));
+               cipher.finish(function(){return true;});
+               var out = (i !== 0) ?
+                 cipher.output.toHex() : cipher.output.getBytes();
+               test.result.html(out);
+               test.check();
+            });
+         })(i);
+      }
+   })();
+
+   addTest('private key encryption', function(task, test)
+   {
+      var _privateKey =
+         '-----BEGIN RSA PRIVATE KEY-----\r\n' +
+         'MIICXQIBAAKBgQDL0EugUiNGMWscLAVM0VoMdhDZEJOqdsUMpx9U0YZI7szokJqQ\r\n' +
+         'NIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMGTkP3VF29vXBo+dLq5e+8VyAy\r\n' +
+         'Q3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGtvnM+z0MYDdKo80efzwIDAQAB\r\n' +
+         'AoGAIzkGONi5G+JifmXlLJdplom486p3upf4Ce2/7mqfaG9MnkyPSairKD/JXvfh\r\n' +
+         'NNWkkN8DKKDKBcVVElPgORYT0qwrWc7ueLBMUCbRXb1ZyfEulimG0R3kjUh7NYau\r\n' +
+         'DaIkVgfykXGSQMZx8FoaT6L080zd+0emKDDYRrb+/kgJNJECQQDoUZoiC2K/DWNY\r\n' +
+         'h3/ppZ0ane2y4SBmJUHJVMPQ2CEgxsrJTxet668ckNCKaOP/3VFPoWC41f17DvKq\r\n' +
+         'noYINNntAkEA4JbZBZBVUrQFhHlrpXT4jzqtO2RlKZzEq8qmFZfEErxOT1WMyyCi\r\n' +
+         'lAQ5gUKardo1Kf0omC8Xq/uO9ZYdED55KwJBALs6cJ65UFaq4oLJiQPzLd7yokuE\r\n' +
+         'dcj8g71PLBTW6jPxIiMFNA89nz3FU9wIVp+xbMNhSoMMKqIPVPC+m0Rn260CQQDA\r\n' +
+         'I83fWK/mZWUjBM33a68KumRiH238v8XyQxj7+C8i6D8G2GXvkigFAehAkb7LZZd+\r\n' +
+         'KLuGFyPlWv3fVWHf99KpAkBQFKk3MRMl6IGJZUEFQe4l5whm8LkGU4acSqv9B3xt\r\n' +
+         'qROkCrsFrMPqjuuzEmyHoQZ64r2PLJg7FOuyhBnQUOt4\r\n' +
+         '-----END RSA PRIVATE KEY-----';
+      var pk = forge.pki.privateKeyFromPem(_privateKey);
+      var pem1 = forge.pki.privateKeyToPem(pk);
+      var pem2 = forge.pki.encryptRsaPrivateKey(
+         pk, 'password', {'encAlg': 'aes128'});
+      var privateKey = forge.pki.decryptRsaPrivateKey(pem2, 'password');
+      var pem3 = forge.pki.privateKeyToPem(privateKey);
+      if(pem1 === pem3)
+      {
+         test.pass();
+      }
+      else
+      {
+         test.fail();
+      }
+   });
+
+   addTest('random', function(task, test)
+   {
+     forge.random.getBytes(16);
+     forge.random.getBytes(24);
+     forge.random.getBytes(32);
+
+      var b = forge.random.getBytes(10);
+      test.result.html(forge.util.bytesToHex(b));
+      if(b.length === 10)
+      {
+         test.pass();
+      }
+      else
+      {
+         test.fail();
+      }
+   });
+
+   addTest('asn.1 oid => der', function(task, test)
+   {
+      test.expect.html('2a864886f70d');
+      test.result.html(forge.asn1.oidToDer('1.2.840.113549').toHex());
+      test.check();
+   });
+
+   addTest('asn.1 der => oid', function(task, test)
+   {
+      var der = '2a864886f70d';
+      test.expect.html('1.2.840.113549');
+      test.result.html(forge.asn1.derToOid(forge.util.hexToBytes(der)));
+      test.check();
+   });
+
+   (function()
+   {
+      var _privateKey =
+      '-----BEGIN RSA PRIVATE KEY-----\r\n' +
+      'MIICXQIBAAKBgQDL0EugUiNGMWscLAVM0VoMdhDZEJOqdsUMpx9U0YZI7szokJqQ\r\n' +
+      'NIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMGTkP3VF29vXBo+dLq5e+8VyAy\r\n' +
+      'Q3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGtvnM+z0MYDdKo80efzwIDAQAB\r\n' +
+      'AoGAIzkGONi5G+JifmXlLJdplom486p3upf4Ce2/7mqfaG9MnkyPSairKD/JXvfh\r\n' +
+      'NNWkkN8DKKDKBcVVElPgORYT0qwrWc7ueLBMUCbRXb1ZyfEulimG0R3kjUh7NYau\r\n' +
+      'DaIkVgfykXGSQMZx8FoaT6L080zd+0emKDDYRrb+/kgJNJECQQDoUZoiC2K/DWNY\r\n' +
+      'h3/ppZ0ane2y4SBmJUHJVMPQ2CEgxsrJTxet668ckNCKaOP/3VFPoWC41f17DvKq\r\n' +
+      'noYINNntAkEA4JbZBZBVUrQFhHlrpXT4jzqtO2RlKZzEq8qmFZfEErxOT1WMyyCi\r\n' +
+      'lAQ5gUKardo1Kf0omC8Xq/uO9ZYdED55KwJBALs6cJ65UFaq4oLJiQPzLd7yokuE\r\n' +
+      'dcj8g71PLBTW6jPxIiMFNA89nz3FU9wIVp+xbMNhSoMMKqIPVPC+m0Rn260CQQDA\r\n' +
+      'I83fWK/mZWUjBM33a68KumRiH238v8XyQxj7+C8i6D8G2GXvkigFAehAkb7LZZd+\r\n' +
+      'KLuGFyPlWv3fVWHf99KpAkBQFKk3MRMl6IGJZUEFQe4l5whm8LkGU4acSqv9B3xt\r\n' +
+      'qROkCrsFrMPqjuuzEmyHoQZ64r2PLJg7FOuyhBnQUOt4\r\n' +
+      '-----END RSA PRIVATE KEY-----\r\n';
+
+      var _publicKey =
+      '-----BEGIN PUBLIC KEY-----\r\n' +
+      'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDL0EugUiNGMWscLAVM0VoMdhDZ\r\n' +
+      'EJOqdsUMpx9U0YZI7szokJqQNIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMG\r\n' +
+      'TkP3VF29vXBo+dLq5e+8VyAyQ3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGt\r\n' +
+      'vnM+z0MYDdKo80efzwIDAQAB\r\n' +
+      '-----END PUBLIC KEY-----\r\n';
+
+      var _certificate =
+      '-----BEGIN CERTIFICATE-----\r\n' +
+      'MIIDIjCCAougAwIBAgIJANE2aHSbwpaRMA0GCSqGSIb3DQEBBQUAMGoxCzAJBgNV\r\n' +
+      'BAYTAlVTMREwDwYDVQQIEwhWaXJnaW5pYTETMBEGA1UEBxMKQmxhY2tzYnVyZzEN\r\n' +
+      'MAsGA1UEChMEVGVzdDENMAsGA1UECxMEVGVzdDEVMBMGA1UEAxMMbXlzZXJ2ZXIu\r\n' +
+      'Y29tMB4XDTEwMDYxOTE3MzYyOFoXDTExMDYxOTE3MzYyOFowajELMAkGA1UEBhMC\r\n' +
+      'VVMxETAPBgNVBAgTCFZpcmdpbmlhMRMwEQYDVQQHEwpCbGFja3NidXJnMQ0wCwYD\r\n' +
+      'VQQKEwRUZXN0MQ0wCwYDVQQLEwRUZXN0MRUwEwYDVQQDEwxteXNlcnZlci5jb20w\r\n' +
+      'gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMvQS6BSI0YxaxwsBUzRWgx2ENkQ\r\n' +
+      'k6p2xQynH1TRhkjuzOiQmpA0jCiSJDoSic2dZIyUi/LjoCGeVFif57N5N5Tt4wZO\r\n' +
+      'Q/dUXb29cGj50url77xXIDJDcXMzXAji2ziFEAIXzDqarKBdDuL9IO7z+tepEa2+\r\n' +
+      'cz7PQxgN0qjzR5/PAgMBAAGjgc8wgcwwHQYDVR0OBBYEFPV1Y+DHXW6bA/r9sv1y\r\n' +
+      'NJ8jAwMAMIGcBgNVHSMEgZQwgZGAFPV1Y+DHXW6bA/r9sv1yNJ8jAwMAoW6kbDBq\r\n' +
+      'MQswCQYDVQQGEwJVUzERMA8GA1UECBMIVmlyZ2luaWExEzARBgNVBAcTCkJsYWNr\r\n' +
+      'c2J1cmcxDTALBgNVBAoTBFRlc3QxDTALBgNVBAsTBFRlc3QxFTATBgNVBAMTDG15\r\n' +
+      'c2VydmVyLmNvbYIJANE2aHSbwpaRMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEF\r\n' +
+      'BQADgYEARdH2KOlJWTC1CS2y/PAvg4uiM31PXMC1hqSdJlnLM1MY4hRfuf9VyTeX\r\n' +
+      'Y6FdybcyDLSxKn9id+g9229ci9/s9PI+QmD5vXd8yZyScLc2JkYB4GC6+9D1+/+x\r\n' +
+      's2hzMxuK6kzZlP+0l9LGcraMQPGRydjCARZZm4Uegln9rh85XFQ=\r\n' +
+      '-----END CERTIFICATE-----\r\n';
+
+      var _signature =
+         '9200ece65cdaed36bcc20b94c65af852e4f88f0b4fe5b249d54665f815992ac4' +
+         '3a1399e65d938c6a7f16dd39d971a53ca66523209dbbfbcb67afa579dbb0c220' +
+         '672813d9e6f4818f29b9becbb29da2032c5e422da97e0c39bfb7a2e7d568615a' +
+         '5073af0337ff215a8e1b2332d668691f4fb731440055420c24ac451dd3c913f4';
+
+      addTest('private key from pem/to pem', function(task, test)
+      {
+         try
+         {
+            // convert from pem
+            var key = forge.pki.privateKeyFromPem(_privateKey);
+            //forge.log.debug(cat, 'privateKey', key);
+
+            // convert back to pem
+            var pem = forge.pki.privateKeyToPem(key);
+            test.expect.html(_privateKey);
+            test.result.html(pem);
+            test.check();
+         }
+         catch(ex)
+         {
+            forge.log.error('test', ex);
+            test.fail();
+         }
+      });
+
+      addTest('public key from pem/to pem', function(task, test)
+      {
+         try
+         {
+            // convert from pem
+            var key = forge.pki.publicKeyFromPem(_publicKey);
+            //forge.log.debug(cat, 'publicKey', key);
+
+            // convert back to pem
+            var pem = forge.pki.publicKeyToPem(key);
+            test.expect.html(_publicKey);
+            test.result.html(pem);
+            test.check();
+         }
+         catch(ex)
+         {
+            forge.log.error('test', ex);
+            test.fail();
+         }
+      });
+
+      addTest('certificate key from pem/to pem', function(task, test)
+      {
+         try
+         {
+            var cert = forge.pki.certificateFromPem(_certificate);
+            /*
+            forge.log.debug(cat, 'cert', cert);
+            forge.log.debug(cat, 'CN', cert.subject.getField('CN').value);
+            forge.log.debug(cat, 'C',
+               cert.subject.getField({shortName: 'C'}).value);
+            forge.log.debug(cat, 'stateOrProvinceName',
+               cert.subject.getField({name: 'stateOrProvinceName'}).value);
+            forge.log.debug(cat, '2.5.4.7',
+               cert.subject.getField({type: '2.5.4.7'}).value);
+            */
+            // convert back to pem
+            var pem = forge.pki.certificateToPem(cert);
+            test.expect.html(_certificate);
+            test.result.html(pem);
+            test.check();
+         }
+         catch(ex)
+         {
+            forge.log.error('test', ex);
+            test.fail();
+         }
+      });
+
+      addTest('verify signature', function(task, test)
+      {
+         try
+         {
+            var key = forge.pki.publicKeyFromPem(_publicKey);
+            var md = forge.md.sha1.create();
+            md.update('0123456789abcdef');
+            var signature = forge.util.hexToBytes(_signature);
+            var success = key.verify(md.digest().getBytes(), signature);
+            if(success)
+            {
+               test.pass();
+            }
+            else
+            {
+               test.fail();
+            }
+         }
+         catch(ex)
+         {
+            forge.log.error('test', ex);
+            test.fail();
+         }
+      });
+
+      addTest('sign and verify', function(task, test)
+      {
+         try
+         {
+            var privateKey = forge.pki.privateKeyFromPem(_privateKey);
+            var publicKey = forge.pki.publicKeyFromPem(_publicKey);
+
+            // do sign
+            var md = forge.md.sha1.create();
+            md.update('0123456789abcdef');
+            var st = +new Date();
+            var signature = privateKey.sign(md);
+            var et = +new Date();
+            //forge.log.debug(cat, 'sign time', (et - st) + 'ms');
+
+            // do verify
+            st = +new Date();
+            var success = publicKey.verify(md.digest().getBytes(), signature);
+            et = +new Date();
+            //forge.log.debug(cat, 'verify time', (et - st) + 'ms');
+            if(success)
+            {
+               test.pass();
+            }
+            else
+            {
+               test.fail();
+            }
+         }
+         catch(ex)
+         {
+            forge.log.error('test', ex);
+            test.fail();
+         }
+      });
+
+      addTest('certificate verify', function(task, test)
+      {
+         try
+         {
+            var cert = forge.pki.certificateFromPem(_certificate, true);
+            //forge.log.debug(cat, 'cert', cert);
+            var success = cert.verify(cert);
+            if(success)
+            {
+               test.pass();
+            }
+            else
+            {
+               test.fail();
+            }
+         }
+         catch(ex)
+         {
+            forge.log.error('test', ex);
+            test.fail();
+         }
+      });
+   })();
+
+   addTest('TLS prf', function(task, test)
+   {
+      // Note: This test vector is originally from:
+      // http://www.imc.org/ietf-tls/mail-archive/msg01589.html
+      // But that link is now dead.
+      var secret = forge.util.createBuffer();
+      for(var i = 0; i < 48; ++i)
+      {
+         secret.putByte(0xAB);
+      }
+      secret = secret.getBytes();
+      var seed = forge.util.createBuffer();
+      for(var i = 0; i < 64; ++i)
+      {
+         seed.putByte(0xCD);
+      }
+      seed = seed.getBytes();
+
+      var bytes = forge.tls.prf_tls1(secret, 'PRF Testvector',  seed, 104);
+      var expect =
+         'd3d4d1e349b5d515044666d51de32bab258cb521' +
+         'b6b053463e354832fd976754443bcf9a296519bc' +
+         '289abcbc1187e4ebd31e602353776c408aafb74c' +
+         'bc85eff69255f9788faa184cbb957a9819d84a5d' +
+         '7eb006eb459d3ae8de9810454b8b2d8f1afbc655' +
+         'a8c9a013';
+      test.expect.html(expect);
+      test.result.html(bytes.toHex());
+      test.check();
+   });
+
+   // function to create certificate
+   var createCert = function(keys, cn, data)
+   {
+      var cert = forge.pki.createCertificate();
+      cert.serialNumber = '01';
+      cert.validity.notBefore = new Date();
+      cert.validity.notAfter = new Date();
+      cert.validity.notAfter.setFullYear(
+         cert.validity.notBefore.getFullYear() + 1);
+      var attrs = [{
+         name: 'commonName',
+         value: cn
+      }, {
+         name: 'countryName',
+         value: 'US'
+      }, {
+         shortName: 'ST',
+         value: 'Virginia'
+      }, {
+         name: 'localityName',
+         value: 'Blacksburg'
+      }, {
+         name: 'organizationName',
+         value: 'Test'
+      }, {
+         shortName: 'OU',
+         value: 'Test'
+      }];
+      cert.setSubject(attrs);
+      cert.setIssuer(attrs);
+      cert.setExtensions([{
+         name: 'basicConstraints',
+         cA: true
+      }, {
+         name: 'keyUsage',
+         keyCertSign: true,
+         digitalSignature: true,
+         nonRepudiation: true,
+         keyEncipherment: true,
+         dataEncipherment: true
+      }, {
+         name: 'subjectAltName',
+         altNames: [{
+            type: 6, // URI
+            value: 'http://myuri.com/webid#me'
+         }]
+      }]);
+      // FIXME: add subjectKeyIdentifier extension
+      // FIXME: add authorityKeyIdentifier extension
+      cert.publicKey = keys.publicKey;
+
+      // self-sign certificate
+      cert.sign(keys.privateKey);
+
+      // save data
+      data[cn] = {
+         cert: forge.pki.certificateToPem(cert),
+         privateKey: forge.pki.privateKeyToPem(keys.privateKey)
+      };
+   };
+
+   var generateCert = function(task, test, cn, data)
+   {
+      task.block();
+
+      // create key-generation state and function to step algorithm
+      test.result.html(
+         'Generating 512-bit key-pair and certificate for \"' + cn + '\".');
+      var state = forge.pki.rsa.createKeyPairGenerationState(512);
+      var kgTime = +new Date();
+      var step = function()
+      {
+         // step key-generation
+         if(!forge.pki.rsa.stepKeyPairGenerationState(state, 1000))
+         {
+            test.result.html(test.result.html() + '.');
+            setTimeout(step, 1);
+         }
+         // key-generation complete
+         else
+         {
+            kgTime = +new Date() - kgTime;
+            forge.log.debug(cat, 'Total key-gen time', kgTime + 'ms');
+            try
+            {
+               createCert(state.keys, cn, data);
+               test.result.html(
+                  test.result.html() + 'done. Time=' + kgTime + 'ms. ');
+               task.unblock();
+            }
+            catch(ex)
+            {
+               forge.log.error(cat, ex, ex.message ? ex.message : '');
+               test.result.html(ex.message);
+               test.fail();
+               task.fail();
+            }
+         }
+      };
+
+      // run key-gen algorithm
+      setTimeout(step, 0);
+   };
+
+   var clientSessionCache1 = forge.tls.createSessionCache();
+   var serverSessionCache1 = forge.tls.createSessionCache();
+   addTest('TLS connection, w/o client-certificate', function(task, test)
+   {
+      var data = {};
+
+      task.next('generate server certifcate', function(task)
+      {
+         generateCert(task, test, 'server', data);
+      });
+
+      task.next('starttls', function(task)
+      {
+         test.result.html(test.result.html() + 'Starting TLS...');
+
+         var end =
+         {
+            client: null,
+            server: null
+         };
+         var success = false;
+
+         // create client
+         end.client = forge.tls.createConnection(
+         {
+            server: false,
+            caStore: [data.server.cert],
+            sessionCache: clientSessionCache1,
+            // optional cipher suites in order of preference
+            cipherSuites: [
+               forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+               forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+            virtualHost: 'server',
+            verify: function(c, verified, depth, certs)
+            {
+               test.result.html(test.result.html() +
+                  'Client verifying certificate w/CN: \"' +
+                  certs[0].subject.getField('CN').value +
+                  '\", verified: ' + verified + '...');
+               if(verified !== true)
+               {
+                  test.fail();
+                  task.fail();
+               }
+               return verified;
+            },
+            connected: function(c)
+            {
+               test.result.html(test.result.html() + 'Client connected...');
+
+               // send message to server
+               setTimeout(function()
+               {
+                  c.prepare('Hello Server');
+               }, 1);
+            },
+            tlsDataReady: function(c)
+            {
+               // send TLS data to server
+               end.server.process(c.tlsData.getBytes());
+            },
+            dataReady: function(c)
+            {
+               var response = c.data.getBytes();
+               test.result.html(test.result.html() +
+                  'Client received \"' + response + '\"');
+               success = (response === 'Hello Client');
+               c.close();
+            },
+            closed: function(c)
+            {
+               test.result.html(test.result.html() + 'Client disconnected.');
+               test.result.html(success ? 'Success' : 'Failure');
+               if(success)
+               {
+                  test.expect.html('Success');
+                  task.unblock();
+                  test.pass();
+               }
+               else
+               {
+                  console.log('closed fail');
+                  test.fail();
+                  task.fail();
+               }
+            },
+            error: function(c, error)
+            {
+               test.result.html(test.result.html() + 'Error: ' + error.message);
+               test.fail();
+               task.fail();
+            }
+         });
+
+         // create server
+         end.server = forge.tls.createConnection(
+         {
+            server: true,
+            sessionCache: serverSessionCache1,
+            // optional cipher suites in order of preference
+            cipherSuites: [
+               forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+               forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+            connected: function(c)
+            {
+               test.result.html(test.result.html() + 'Server connected...');
+            },
+            getCertificate: function(c, hint)
+            {
+               test.result.html(test.result.html() +
+                  'Server getting certificate for \"' + hint[0] + '\"...');
+               return data.server.cert;
+            },
+            getPrivateKey: function(c, cert)
+            {
+               return data.server.privateKey;
+            },
+            tlsDataReady: function(c)
+            {
+               // send TLS data to client
+               end.client.process(c.tlsData.getBytes());
+            },
+            dataReady: function(c)
+            {
+               test.result.html(test.result.html() +
+                  'Server received \"' + c.data.getBytes() + '\"');
+
+               // send response
+               c.prepare('Hello Client');
+               c.close();
+            },
+            closed: function(c)
+            {
+               test.result.html(test.result.html() + 'Server disconnected.');
+            },
+            error: function(c, error)
+            {
+               test.result.html(test.result.html() + 'Error: ' + error.message);
+               test.fail();
+               task.fail();
+            }
+         });
+
+         // start handshake
+         task.block();
+         end.client.handshake();
+      });
+   });
+
+   var clientSessionCache2 = forge.tls.createSessionCache();
+   var serverSessionCache2 = forge.tls.createSessionCache();
+   addTest('TLS connection, w/optional client-certificate', function(task, test)
+   {
+      var data = {};
+
+      task.next('generate server certifcate', function(task)
+      {
+         generateCert(task, test, 'server', data);
+      });
+
+      // client-cert generated but not sent in this test
+      task.next('generate client certifcate', function(task)
+      {
+         generateCert(task, test, 'client', data);
+      });
+
+      task.next('starttls', function(task)
+      {
+         test.result.html(test.result.html() + 'Starting TLS...');
+
+         var end =
+         {
+            client: null,
+            server: null
+         };
+         var success = false;
+
+         // create client
+         end.client = forge.tls.createConnection(
+         {
+            server: false,
+            caStore: [data.server.cert],
+            sessionCache: clientSessionCache2,
+            // supported cipher suites in order of preference
+            cipherSuites: [
+               forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+               forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+            virtualHost: 'server',
+            verify: function(c, verified, depth, certs)
+            {
+               test.result.html(test.result.html() +
+                  'Client verifying certificate w/CN: \"' +
+                  certs[0].subject.getField('CN').value +
+                  '\", verified: ' + verified + '...');
+               if(verified !== true)
+               {
+                  test.fail();
+                  task.fail();
+               }
+               return verified;
+            },
+            connected: function(c)
+            {
+               test.result.html(test.result.html() + 'Client connected...');
+
+               // send message to server
+               setTimeout(function()
+               {
+                  c.prepare('Hello Server');
+               }, 1);
+            },
+            tlsDataReady: function(c)
+            {
+               // send TLS data to server
+               end.server.process(c.tlsData.getBytes());
+            },
+            dataReady: function(c)
+            {
+               var response = c.data.getBytes();
+               test.result.html(test.result.html() +
+                  'Client received \"' + response + '\"');
+               success = (response === 'Hello Client');
+               c.close();
+            },
+            closed: function(c)
+            {
+               test.result.html(test.result.html() + 'Client disconnected.');
+               test.result.html(success ? 'Success' : 'Failure');
+               if(success)
+               {
+                  test.expect.html('Success');
+                  task.unblock();
+                  test.pass();
+               }
+               else
+               {
+                  console.log('closed fail');
+                  test.fail();
+                  task.fail();
+               }
+            },
+            error: function(c, error)
+            {
+               test.result.html(test.result.html() + 'Error: ' + error.message);
+               test.fail();
+               task.fail();
+            }
+         });
+
+         // create server
+         end.server = forge.tls.createConnection(
+         {
+            server: true,
+            caStore: [data.client.cert],
+            sessionCache: serverSessionCache2,
+            // supported cipher suites in order of preference
+            cipherSuites: [
+               forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+               forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+            connected: function(c)
+            {
+               test.result.html(test.result.html() + 'Server connected...');
+            },
+            verifyClient: 'optional',
+            verify: function(c, verified, depth, certs)
+            {
+               test.result.html(test.result.html() +
+                  'Server verifying certificate w/CN: \"' +
+                  certs[0].subject.getField('CN').value +
+                  '\", verified: ' + verified + '...');
+               if(verified !== true)
+               {
+                  test.fail();
+                  task.fail();
+               }
+               return verified;
+            },
+            getCertificate: function(c, hint)
+            {
+               test.result.html(test.result.html() +
+                  'Server getting certificate for \"' + hint[0] + '\"...');
+               return data.server.cert;
+            },
+            getPrivateKey: function(c, cert)
+            {
+               return data.server.privateKey;
+            },
+            tlsDataReady: function(c)
+            {
+               // send TLS data to client
+               end.client.process(c.tlsData.getBytes());
+            },
+            dataReady: function(c)
+            {
+               test.result.html(test.result.html() +
+                  'Server received \"' + c.data.getBytes() + '\"');
+
+               // send response
+               c.prepare('Hello Client');
+               c.close();
+            },
+            closed: function(c)
+            {
+               test.result.html(test.result.html() + 'Server disconnected.');
+            },
+            error: function(c, error)
+            {
+               test.result.html(test.result.html() + 'Error: ' + error.message);
+               test.fail();
+               task.fail();
+            }
+         });
+
+         // start handshake
+         task.block();
+         end.client.handshake();
+      });
+   });
+
+   var clientSessionCache3 = forge.tls.createSessionCache();
+   var serverSessionCache3 = forge.tls.createSessionCache();
+   addTest('TLS connection, w/client-certificate', function(task, test)
+   {
+      var data = {};
+
+      task.next('generate server certifcate', function(task)
+      {
+         generateCert(task, test, 'server', data);
+      });
+
+      task.next('generate client certifcate', function(task)
+      {
+         generateCert(task, test, 'client', data);
+      });
+
+      task.next('starttls', function(task)
+      {
+         test.result.html(test.result.html() + 'Starting TLS...');
+
+         var end =
+         {
+            client: null,
+            server: null
+         };
+         var success = false;
+
+         // create client
+         end.client = forge.tls.createConnection(
+         {
+            server: false,
+            caStore: [data.server.cert],
+            sessionCache: clientSessionCache3,
+            // supported cipher suites in order of preference
+            cipherSuites: [
+               forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+               forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+            virtualHost: 'server',
+            verify: function(c, verified, depth, certs)
+            {
+               test.result.html(test.result.html() +
+                  'Client verifying certificate w/CN: \"' +
+                  certs[0].subject.getField('CN').value +
+                  '\", verified: ' + verified + '...');
+               if(verified !== true)
+               {
+                  test.fail();
+                  task.fail();
+               }
+               return verified;
+            },
+            connected: function(c)
+            {
+               test.result.html(test.result.html() + 'Client connected...');
+
+               // send message to server
+               setTimeout(function()
+               {
+                  c.prepare('Hello Server');
+               }, 1);
+            },
+            getCertificate: function(c, hint)
+            {
+               test.result.html(test.result.html() +
+                  'Client getting certificate ...');
+               return data.client.cert;
+            },
+            getPrivateKey: function(c, cert)
+            {
+               return data.client.privateKey;
+            },
+            tlsDataReady: function(c)
+            {
+               // send TLS data to server
+               end.server.process(c.tlsData.getBytes());
+            },
+            dataReady: function(c)
+            {
+               var response = c.data.getBytes();
+               test.result.html(test.result.html() +
+                  'Client received \"' + response + '\"');
+               success = (response === 'Hello Client');
+               c.close();
+            },
+            closed: function(c)
+            {
+               test.result.html(test.result.html() + 'Client disconnected.');
+               test.result.html(success ? 'Success' : 'Failure');
+               if(success)
+               {
+                  test.expect.html('Success');
+                  task.unblock();
+                  test.pass();
+               }
+               else
+               {
+                  console.log('closed fail');
+                  test.fail();
+                  task.fail();
+               }
+            },
+            error: function(c, error)
+            {
+               test.result.html(test.result.html() + 'Error: ' + error.message);
+               test.fail();
+               task.fail();
+            }
+         });
+
+         // create server
+         end.server = forge.tls.createConnection(
+         {
+            server: true,
+            caStore: [data.client.cert],
+            sessionCache: serverSessionCache3,
+            // supported cipher suites in order of preference
+            cipherSuites: [
+               forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+               forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+            connected: function(c)
+            {
+               test.result.html(test.result.html() + 'Server connected...');
+            },
+            verifyClient: true, // use 'optional' to request but not require
+            verify: function(c, verified, depth, certs)
+            {
+               test.result.html(test.result.html() +
+                  'Server verifying certificate w/CN: \"' +
+                  certs[0].subject.getField('CN').value +
+                  '\", verified: ' + verified + '...');
+               if(verified !== true)
+               {
+                  test.fail();
+                  task.fail();
+               }
+               return verified;
+            },
+            getCertificate: function(c, hint)
+            {
+               test.result.html(test.result.html() +
+                  'Server getting certificate for \"' + hint[0] + '\"...');
+               return data.server.cert;
+            },
+            getPrivateKey: function(c, cert)
+            {
+               return data.server.privateKey;
+            },
+            tlsDataReady: function(c)
+            {
+               // send TLS data to client
+               end.client.process(c.tlsData.getBytes());
+            },
+            dataReady: function(c)
+            {
+               test.result.html(test.result.html() +
+                  'Server received \"' + c.data.getBytes() + '\"');
+
+               // send response
+               c.prepare('Hello Client');
+               c.close();
+            },
+            closed: function(c)
+            {
+               test.result.html(test.result.html() + 'Server disconnected.');
+            },
+            error: function(c, error)
+            {
+               test.result.html(test.result.html() + 'Error: ' + error.message);
+               test.fail();
+               task.fail();
+            }
+         });
+
+         // start handshake
+         task.block();
+         end.client.handshake();
+      });
+   });
+
+   init();
+});
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/favicon.ico b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/favicon.ico
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/favicon.ico
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/flash/Test.as b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/flash/Test.as
new file mode 100644
index 0000000..7c03727
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/flash/Test.as
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2010 Digital Bazaar, Inc. All rights reserved.
+ * 
+ * @author Dave Longley
+ */
+package
+{
+   import flash.display.Sprite;
+   
+   public class Test extends Sprite
+   {
+      import flash.events.*;
+      import flash.net.*;
+      
+      import flash.external.ExternalInterface;
+      import flash.system.Security;
+      
+      public function Test()
+      {
+         try
+         {
+            // FIXME: replace 'localhost' with cross-domain host to hit
+            var xhost:String = "localhost";
+            Security.loadPolicyFile("xmlsocket://" + xhost + ":80");
+            
+            var loader:URLLoader = new URLLoader();
+            loader.addEventListener(
+               Event.COMPLETE, completeHandler);
+            loader.addEventListener(
+               Event.OPEN, openHandler);
+            loader.addEventListener(
+               ProgressEvent.PROGRESS, progressHandler);
+            loader.addEventListener(
+               SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
+            loader.addEventListener(
+               HTTPStatusEvent.HTTP_STATUS, httpStatusHandler);
+            loader.addEventListener(
+               IOErrorEvent.IO_ERROR, ioErrorHandler);
+            
+            var request:URLRequest = new URLRequest(
+               "http://" + xhost + "/index.html");
+            loader.load(request);
+         }
+         catch(e:Error)
+         {
+            log("error=" + e.errorID + "," + e.name + "," + e.message);
+            throw e;
+         }
+      }
+      
+      private function log(obj:Object):void
+      {
+         if(obj is String)
+         {
+            var str:String = obj as String;
+            ExternalInterface.call("console.log", "Test", str);
+         }
+         else if(obj is Error)
+         {
+            var e:Error = obj as Error;
+            log("error=" + e.errorID + "," + e.name + "," + e.message);
+         }
+      }
+      
+      private function completeHandler(event:Event):void
+      {
+         var loader:URLLoader = URLLoader(event.target);
+         log("complete: " + loader.data);
+      }
+
+      private function openHandler(event:Event):void
+      {
+         log("open: " + event);
+      }
+
+      private function progressHandler(event:ProgressEvent):void
+      {
+         log("progress:" + event.bytesLoaded + " total: " + event.bytesTotal);
+      }
+
+      private function securityErrorHandler(event:SecurityErrorEvent):void
+      {
+         log("securityError: " + event);
+      }
+
+      private function httpStatusHandler(event:HTTPStatusEvent):void
+      {
+         log("httpStatus: " + event);
+      }
+
+      private function ioErrorHandler(event:IOErrorEvent):void
+      {
+         log("ioError: " + event);
+      }
+   }
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/flash/build-flash.xml b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/flash/build-flash.xml
new file mode 100644
index 0000000..f037c58
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/flash/build-flash.xml
@@ -0,0 +1,7 @@
+<flex-config>
+   <compiler>
+      <source-path>
+         <path-element>.</path-element>
+      </source-path>
+   </compiler>
+</flex-config>
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/flash/index.html b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/flash/index.html
new file mode 100644
index 0000000..26a10b8
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/flash/index.html
@@ -0,0 +1,27 @@
+<html>
+   <head>
+      <link type="text/css" rel="stylesheet" media="all" href="screen.css" />
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js"></script>
+      
+      <script type="text/javascript">
+      //<![CDATA[
+      swfobject.embedSWF(
+         'Test.swf', 'test', '0', '0', '9.0.0',
+         false, {}, {allowscriptaccess: 'always'}, {});
+      //]]>
+      </script>
+   </head>
+   <body>
+      <div class="header">
+         <h1>Flash Cross-Domain URLLoader Test</h1>
+      </div>
+
+      <div class="content">
+
+      <div id="test">
+         <p>Could not load the flash test.</p>
+      </div>
+
+      </div>
+   </body>
+</html>
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/forge_ssl/forge/__init__.py b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/forge_ssl/forge/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/forge_ssl/forge/__init__.py
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/forge_ssl/forge/_ssl.c b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/forge_ssl/forge/_ssl.c
new file mode 100644
index 0000000..bdef8c9
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/forge_ssl/forge/_ssl.c
@@ -0,0 +1,1770 @@
+/* SSL socket module
+
+   SSL support based on patches by Brian E Gallew and Laszlo Kovacs.
+   Re-worked a bit by Bill Janssen to add server-side support and
+   certificate decoding.  Chris Stawarz contributed some non-blocking
+   patches.
+
+   This module is imported by ssl.py. It should *not* be used
+   directly.
+
+   XXX should partial writes be enabled, SSL_MODE_ENABLE_PARTIAL_WRITE?
+
+   XXX integrate several "shutdown modes" as suggested in
+       http://bugs.python.org/issue8108#msg102867 ?
+*/
+
+#include "Python.h"
+
+#ifdef WITH_THREAD
+#include "pythread.h"
+#define PySSL_BEGIN_ALLOW_THREADS { \
+            PyThreadState *_save = NULL;  \
+            if (_ssl_locks_count>0) {_save = PyEval_SaveThread();}
+#define PySSL_BLOCK_THREADS     if (_ssl_locks_count>0){PyEval_RestoreThread(_save)};
+#define PySSL_UNBLOCK_THREADS   if (_ssl_locks_count>0){_save = PyEval_SaveThread()};
+#define PySSL_END_ALLOW_THREADS if (_ssl_locks_count>0){PyEval_RestoreThread(_save);} \
+         }
+
+#else   /* no WITH_THREAD */
+
+#define PySSL_BEGIN_ALLOW_THREADS
+#define PySSL_BLOCK_THREADS
+#define PySSL_UNBLOCK_THREADS
+#define PySSL_END_ALLOW_THREADS
+
+#endif
+
+enum py_ssl_error {
+    /* these mirror ssl.h */
+    PY_SSL_ERROR_NONE,
+    PY_SSL_ERROR_SSL,
+    PY_SSL_ERROR_WANT_READ,
+    PY_SSL_ERROR_WANT_WRITE,
+    PY_SSL_ERROR_WANT_X509_LOOKUP,
+    PY_SSL_ERROR_SYSCALL,     /* look at error stack/return value/errno */
+    PY_SSL_ERROR_ZERO_RETURN,
+    PY_SSL_ERROR_WANT_CONNECT,
+    /* start of non ssl.h errorcodes */
+    PY_SSL_ERROR_EOF,         /* special case of SSL_ERROR_SYSCALL */
+    PY_SSL_ERROR_INVALID_ERROR_CODE
+};
+
+enum py_ssl_server_or_client {
+    PY_SSL_CLIENT,
+    PY_SSL_SERVER
+};
+
+enum py_ssl_cert_requirements {
+    PY_SSL_CERT_NONE,
+    PY_SSL_CERT_OPTIONAL,
+    PY_SSL_CERT_REQUIRED
+};
+
+enum py_ssl_version {
+    PY_SSL_VERSION_SSL2,
+    PY_SSL_VERSION_SSL3,
+    PY_SSL_VERSION_SSL23,
+    PY_SSL_VERSION_TLS1
+};
+
+enum py_ssl_sess_cache_mode {
+    PY_SSL_SESS_CACHE_OFF,
+    PY_SSL_SESS_CACHE_CLIENT,
+    PY_SSL_SESS_CACHE_SERVER,
+    PY_SSL_SESS_CACHE_BOTH
+};
+
+/* Include symbols from _socket module */
+#include "socketmodule.h"
+
+#if defined(HAVE_POLL_H)
+#include <poll.h>
+#elif defined(HAVE_SYS_POLL_H)
+#include <sys/poll.h>
+#endif
+
+/* Include OpenSSL header files */
+#include "openssl/rsa.h"
+#include "openssl/crypto.h"
+#include "openssl/x509.h"
+#include "openssl/x509v3.h"
+#include "openssl/pem.h"
+#include "openssl/ssl.h"
+#include "openssl/err.h"
+#include "openssl/rand.h"
+
+/* SSL error object */
+static PyObject *PySSLErrorObject;
+
+#ifdef WITH_THREAD
+
+/* serves as a flag to see whether we've initialized the SSL thread support. */
+/* 0 means no, greater than 0 means yes */
+
+static unsigned int _ssl_locks_count = 0;
+
+#endif /* def WITH_THREAD */
+
+/* SSL socket object */
+
+#define X509_NAME_MAXLEN 256
+
+/* RAND_* APIs got added to OpenSSL in 0.9.5 */
+#if OPENSSL_VERSION_NUMBER >= 0x0090500fL
+# define HAVE_OPENSSL_RAND 1
+#else
+# undef HAVE_OPENSSL_RAND
+#endif
+
+typedef struct {
+    PyObject_HEAD
+    PySocketSockObject *Socket;         /* Socket on which we're layered */
+    int                 inherited;
+    SSL_CTX*            ctx;
+    SSL*                ssl;
+    X509*               peer_cert;
+    char                server[X509_NAME_MAXLEN];
+    char                issuer[X509_NAME_MAXLEN];
+    int                 shutdown_seen_zero;
+
+} PySSLObject;
+
+static PyTypeObject PySSL_Type;
+static PyObject *PySSL_SSLwrite(PySSLObject *self, PyObject *args);
+static PyObject *PySSL_SSLread(PySSLObject *self, PyObject *args);
+static int check_socket_and_wait_for_timeout(PySocketSockObject *s,
+                                             int writing);
+static PyObject *PySSL_peercert(PySSLObject *self, PyObject *args);
+static PyObject *PySSL_cipher(PySSLObject *self);
+
+#define PySSLObject_Check(v)    (Py_TYPE(v) == &PySSL_Type)
+
+typedef enum {
+    SOCKET_IS_NONBLOCKING,
+    SOCKET_IS_BLOCKING,
+    SOCKET_HAS_TIMED_OUT,
+    SOCKET_HAS_BEEN_CLOSED,
+    SOCKET_TOO_LARGE_FOR_SELECT,
+    SOCKET_OPERATION_OK
+} timeout_state;
+
+/* Wrap error strings with filename and line # */
+#define STRINGIFY1(x) #x
+#define STRINGIFY2(x) STRINGIFY1(x)
+#define ERRSTR1(x,y,z) (x ":" y ": " z)
+#define ERRSTR(x) ERRSTR1("_ssl.c", STRINGIFY2(__LINE__), x)
+
+/* XXX It might be helpful to augment the error message generated
+   below with the name of the SSL function that generated the error.
+   I expect it's obvious most of the time.
+*/
+
+static PyObject *
+PySSL_SetError(PySSLObject *obj, int ret, char *filename, int lineno)
+{
+    PyObject *v;
+    char buf[2048];
+    char *errstr;
+    int err;
+    enum py_ssl_error p = PY_SSL_ERROR_NONE;
+
+    assert(ret <= 0);
+
+    if (obj->ssl != NULL) {
+        err = SSL_get_error(obj->ssl, ret);
+
+        switch (err) {
+        case SSL_ERROR_ZERO_RETURN:
+            errstr = "TLS/SSL connection has been closed";
+            p = PY_SSL_ERROR_ZERO_RETURN;
+            break;
+        case SSL_ERROR_WANT_READ:
+            errstr = "The operation did not complete (read)";
+            p = PY_SSL_ERROR_WANT_READ;
+            break;
+        case SSL_ERROR_WANT_WRITE:
+            p = PY_SSL_ERROR_WANT_WRITE;
+            errstr = "The operation did not complete (write)";
+            break;
+        case SSL_ERROR_WANT_X509_LOOKUP:
+            p = PY_SSL_ERROR_WANT_X509_LOOKUP;
+            errstr = "The operation did not complete (X509 lookup)";
+            break;
+        case SSL_ERROR_WANT_CONNECT:
+            p = PY_SSL_ERROR_WANT_CONNECT;
+            errstr = "The operation did not complete (connect)";
+            break;
+        case SSL_ERROR_SYSCALL:
+        {
+            unsigned long e = ERR_get_error();
+            if (e == 0) {
+                if (ret == 0 || !obj->Socket) {
+                    p = PY_SSL_ERROR_EOF;
+                    errstr = "EOF occurred in violation of protocol";
+                } else if (ret == -1) {
+                    /* underlying BIO reported an I/O error */
+                    ERR_clear_error();
+                    return obj->Socket->errorhandler();
+                } else { /* possible? */
+                    p = PY_SSL_ERROR_SYSCALL;
+                    errstr = "Some I/O error occurred";
+                }
+            } else {
+                p = PY_SSL_ERROR_SYSCALL;
+                /* XXX Protected by global interpreter lock */
+                errstr = ERR_error_string(e, NULL);
+            }
+            break;
+        }
+        case SSL_ERROR_SSL:
+        {
+            unsigned long e = ERR_get_error();
+            p = PY_SSL_ERROR_SSL;
+            if (e != 0)
+                /* XXX Protected by global interpreter lock */
+                errstr = ERR_error_string(e, NULL);
+            else {              /* possible? */
+                errstr = "A failure in the SSL library occurred";
+            }
+            break;
+        }
+        default:
+            p = PY_SSL_ERROR_INVALID_ERROR_CODE;
+            errstr = "Invalid error code";
+        }
+    } else {
+        errstr = ERR_error_string(ERR_peek_last_error(), NULL);
+    }
+    PyOS_snprintf(buf, sizeof(buf), "_ssl.c:%d: %s", lineno, errstr);
+    ERR_clear_error();
+    v = Py_BuildValue("(is)", p, buf);
+    if (v != NULL) {
+        PyErr_SetObject(PySSLErrorObject, v);
+        Py_DECREF(v);
+    }
+    return NULL;
+}
+
+static PyObject *
+_setSSLError (char *errstr, int errcode, char *filename, int lineno) {
+
+    char buf[2048];
+    PyObject *v;
+
+    if (errstr == NULL) {
+        errcode = ERR_peek_last_error();
+        errstr = ERR_error_string(errcode, NULL);
+    }
+    PyOS_snprintf(buf, sizeof(buf), "_ssl.c:%d: %s", lineno, errstr);
+    ERR_clear_error();
+    v = Py_BuildValue("(is)", errcode, buf);
+    if (v != NULL) {
+        PyErr_SetObject(PySSLErrorObject, v);
+        Py_DECREF(v);
+    }
+    return NULL;
+}
+
+static PySSLObject *
+newPySSLObject(PySSLObject *ssl_object, PySocketSockObject *Sock,
+               char *key_file, char *cert_file,
+               enum py_ssl_server_or_client socket_type,
+               enum py_ssl_cert_requirements certreq,
+               enum py_ssl_version proto_version,
+               enum py_ssl_sess_cache_mode cache_mode,
+               char *sess_id_ctx,
+               char *cacerts_file)
+{
+    PySSLObject *self;
+    char *errstr = NULL;
+    int ret;
+    int verification_mode;
+    int sess_cache_mode;
+
+    self = PyObject_New(PySSLObject, &PySSL_Type); /* Create new object */
+    if (self == NULL)
+        return NULL;
+    memset(self->server, '\0', sizeof(char) * X509_NAME_MAXLEN);
+    memset(self->issuer, '\0', sizeof(char) * X509_NAME_MAXLEN);
+    self->peer_cert = NULL;
+    self->inherited = 0;
+    self->ssl = NULL;
+    self->ctx = NULL;
+    self->Socket = NULL;
+
+    /* Make sure the SSL error state is initialized */
+    (void) ERR_get_state();
+    ERR_clear_error();
+
+    if ((key_file && !cert_file) || (!key_file && cert_file)) {
+        errstr = ERRSTR("Both the key & certificate files "
+                        "must be specified");
+        goto fail;
+    }
+
+    if ((socket_type == PY_SSL_SERVER) && (ssl_object == NULL) &&
+        ((key_file == NULL) || (cert_file == NULL))) {
+        errstr = ERRSTR("Both the key & certificate files "
+                        "must be specified for server-side operation");
+        goto fail;
+    }
+
+    if (ssl_object != NULL) {
+        self->inherited = 1;
+        self->ctx = ssl_object->ctx;
+    } else {
+        self->inherited = 0;
+
+        PySSL_BEGIN_ALLOW_THREADS
+        if (proto_version == PY_SSL_VERSION_TLS1)
+            self->ctx = SSL_CTX_new(TLSv1_method()); /* Set up context */
+        else if (proto_version == PY_SSL_VERSION_SSL3)
+            self->ctx = SSL_CTX_new(SSLv3_method()); /* Set up context */
+        else if (proto_version == PY_SSL_VERSION_SSL2)
+            self->ctx = SSL_CTX_new(SSLv2_method()); /* Set up context */
+        else if (proto_version == PY_SSL_VERSION_SSL23)
+            self->ctx = SSL_CTX_new(SSLv23_method()); /* Set up context */
+        PySSL_END_ALLOW_THREADS
+    }
+
+    if (self->ctx == NULL) {
+        errstr = ERRSTR("Invalid SSL protocol variant specified.");
+        goto fail;
+    }
+
+    if (self->inherited == 0 && certreq != PY_SSL_CERT_NONE) {
+        if (cacerts_file == NULL) {
+            errstr = ERRSTR("No root certificates specified for "
+                            "verification of other-side certificates.");
+            goto fail;
+        } else {
+            PySSL_BEGIN_ALLOW_THREADS
+            ret = SSL_CTX_load_verify_locations(self->ctx,
+                                                cacerts_file,
+                                                NULL);
+            PySSL_END_ALLOW_THREADS
+            if (ret != 1) {
+                _setSSLError(NULL, 0, __FILE__, __LINE__);
+                goto fail;
+            }
+        }
+    }
+    if (self->inherited == 0 && key_file) {
+        PySSL_BEGIN_ALLOW_THREADS
+        ret = SSL_CTX_use_PrivateKey_file(self->ctx, key_file,
+                                          SSL_FILETYPE_PEM);
+        PySSL_END_ALLOW_THREADS
+        if (ret != 1) {
+            _setSSLError(NULL, ret, __FILE__, __LINE__);
+            goto fail;
+        }
+
+        PySSL_BEGIN_ALLOW_THREADS
+        ret = SSL_CTX_use_certificate_chain_file(self->ctx,
+                                                 cert_file);
+        PySSL_END_ALLOW_THREADS
+        if (ret != 1) {
+            /*
+            fprintf(stderr, "ret is %d, errcode is %lu, %lu, with file \"%s\"\n",
+                ret, ERR_peek_error(), ERR_peek_last_error(), cert_file);
+                */
+            if (ERR_peek_last_error() != 0) {
+                _setSSLError(NULL, ret, __FILE__, __LINE__);
+                goto fail;
+            }
+        }
+    }
+
+    if (self->inherited == 0) {
+        /* ssl compatibility */
+        SSL_CTX_set_options(self->ctx, SSL_OP_ALL);
+
+        /* session cache mode */
+        PySSL_BEGIN_ALLOW_THREADS
+        sess_cache_mode = SSL_SESS_CACHE_SERVER;
+        if (cache_mode == PY_SSL_SESS_CACHE_OFF)
+            sess_cache_mode = SSL_SESS_CACHE_OFF;
+        else if (cache_mode == PY_SSL_SESS_CACHE_CLIENT)
+            sess_cache_mode = SSL_SESS_CACHE_CLIENT;
+        else if (cache_mode == PY_SSL_SESS_CACHE_SERVER)
+            sess_cache_mode = SSL_SESS_CACHE_SERVER;
+        else if (cache_mode == PY_SSL_SESS_CACHE_BOTH)
+            sess_cache_mode = SSL_SESS_CACHE_BOTH;
+        SSL_CTX_set_session_cache_mode(self->ctx, sess_cache_mode);
+
+        /* session id context */
+        if (sess_id_ctx != NULL)
+           SSL_CTX_set_session_id_context(self->ctx,
+                                          (const unsigned char*)sess_id_ctx,
+                                          strlen(sess_id_ctx));
+        PySSL_END_ALLOW_THREADS
+
+        verification_mode = SSL_VERIFY_NONE;
+        if (certreq == PY_SSL_CERT_OPTIONAL)
+            verification_mode = SSL_VERIFY_PEER;
+        else if (certreq == PY_SSL_CERT_REQUIRED)
+            verification_mode = (SSL_VERIFY_PEER |
+                                 SSL_VERIFY_FAIL_IF_NO_PEER_CERT);
+        SSL_CTX_set_verify(self->ctx, verification_mode,
+                           NULL); /* set verify lvl */
+    }
+
+    self->ssl = SSL_new(self->ctx); /* New ssl struct */
+    SSL_set_fd(self->ssl, Sock->sock_fd);       /* Set the socket for SSL */
+#ifdef SSL_MODE_AUTO_RETRY
+    SSL_set_mode(self->ssl, SSL_MODE_AUTO_RETRY);
+#endif
+
+    /* If the socket is in non-blocking mode or timeout mode, set the BIO
+     * to non-blocking mode (blocking is the default)
+     */
+    if (Sock->sock_timeout >= 0.0) {
+        /* Set both the read and write BIO's to non-blocking mode */
+        BIO_set_nbio(SSL_get_rbio(self->ssl), 1);
+        BIO_set_nbio(SSL_get_wbio(self->ssl), 1);
+    }
+
+    PySSL_BEGIN_ALLOW_THREADS
+    if (socket_type == PY_SSL_CLIENT)
+        SSL_set_connect_state(self->ssl);
+    else
+        SSL_set_accept_state(self->ssl);
+    PySSL_END_ALLOW_THREADS
+
+    self->Socket = Sock;
+    Py_INCREF(self->Socket);
+    return self;
+ fail:
+    if (errstr)
+        PyErr_SetString(PySSLErrorObject, errstr);
+    Py_DECREF(self);
+    return NULL;
+}
+
+static PyObject *
+PySSL_sslwrap(PyObject *self, PyObject *args)
+{
+    PySocketSockObject *Sock;
+    int server_side = 0;
+    int verification_mode = PY_SSL_CERT_NONE;
+    int protocol = PY_SSL_VERSION_SSL23;
+    int sess_cache_mode = PY_SSL_SESS_CACHE_SERVER;
+    char *sess_id_ctx = NULL;
+    char *key_file = NULL;
+    char *cert_file = NULL;
+    char *cacerts_file = NULL;
+
+    if (!PyArg_ParseTuple(args, "O!i|zziiizz:sslwrap",
+                          PySocketModule.Sock_Type,
+                          &Sock,
+                          &server_side,
+                          &key_file, &cert_file,
+                          &verification_mode, &protocol,
+                          &sess_cache_mode, &sess_id_ctx,
+                          &cacerts_file))
+        return NULL;
+
+    /*
+    fprintf(stderr,
+        "server_side is %d, keyfile %p, certfile %p, verify_mode %d, "
+        "protocol %d, sess_cache_mode %d, sess_id_ctx %p, certs %p\n",
+        server_side, key_file, cert_file, verification_mode,
+        protocol, sess_cache_mode, sess_id_ctx, cacerts_file);
+     */
+
+    return (PyObject *) newPySSLObject(NULL, Sock, key_file, cert_file,
+                                       server_side, verification_mode,
+                                       protocol, sess_cache_mode, sess_id_ctx,
+                                       cacerts_file);
+}
+
+PyDoc_STRVAR(sslwrap_doc,
+"sslwrap(socket, server_side, [keyfile, certfile, certs_mode, protocol,\n"
+"                              sess_cache_mode, sess_id_ctx, cacertsfile]) -> sslobject");
+
+/* SSL object methods */
+
+static PyObject *
+PySSL_SSLwrap_accepted(PySSLObject *self, PyObject *args)
+{
+    PySocketSockObject *Sock;
+
+    if (!PyArg_ParseTuple(args, "O!:sslwrap",
+                          PySocketModule.Sock_Type,
+                          &Sock))
+        return NULL;
+
+    return (PyObject *) newPySSLObject(self, Sock, NULL, NULL,
+                                       PY_SSL_SERVER, 0, 0, 0, NULL, NULL);
+}
+
+PyDoc_STRVAR(PySSL_SSLwrap_accepted_doc,
+"wrap_accepted(socket) -> sslobject");
+
+static PyObject *PySSL_SSLdo_handshake(PySSLObject *self)
+{
+    int ret;
+    int err;
+    int sockstate, nonblocking;
+
+    /* just in case the blocking state of the socket has been changed */
+    nonblocking = (self->Socket->sock_timeout >= 0.0);
+    BIO_set_nbio(SSL_get_rbio(self->ssl), nonblocking);
+    BIO_set_nbio(SSL_get_wbio(self->ssl), nonblocking);
+
+    /* Actually negotiate SSL connection */
+    /* XXX If SSL_do_handshake() returns 0, it's also a failure. */
+    sockstate = 0;
+    do {
+        PySSL_BEGIN_ALLOW_THREADS
+        ret = SSL_do_handshake(self->ssl);
+        err = SSL_get_error(self->ssl, ret);
+        PySSL_END_ALLOW_THREADS
+        if(PyErr_CheckSignals()) {
+            return NULL;
+        }
+        if (err == SSL_ERROR_WANT_READ) {
+            sockstate = check_socket_and_wait_for_timeout(self->Socket, 0);
+        } else if (err == SSL_ERROR_WANT_WRITE) {
+            sockstate = check_socket_and_wait_for_timeout(self->Socket, 1);
+        } else {
+            sockstate = SOCKET_OPERATION_OK;
+        }
+        if (sockstate == SOCKET_HAS_TIMED_OUT) {
+            PyErr_SetString(PySSLErrorObject,
+                            ERRSTR("The handshake operation timed out"));
+            return NULL;
+        } else if (sockstate == SOCKET_HAS_BEEN_CLOSED) {
+            PyErr_SetString(PySSLErrorObject,
+                            ERRSTR("Underlying socket has been closed."));
+            return NULL;
+        } else if (sockstate == SOCKET_TOO_LARGE_FOR_SELECT) {
+            PyErr_SetString(PySSLErrorObject,
+                            ERRSTR("Underlying socket too large for select()."));
+            return NULL;
+        } else if (sockstate == SOCKET_IS_NONBLOCKING) {
+            break;
+        }
+    } while (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE);
+    if (ret < 1)
+        return PySSL_SetError(self, ret, __FILE__, __LINE__);
+
+    if (self->peer_cert)
+        X509_free (self->peer_cert);
+    PySSL_BEGIN_ALLOW_THREADS
+    if ((self->peer_cert = SSL_get_peer_certificate(self->ssl))) {
+        X509_NAME_oneline(X509_get_subject_name(self->peer_cert),
+                          self->server, X509_NAME_MAXLEN);
+        X509_NAME_oneline(X509_get_issuer_name(self->peer_cert),
+                          self->issuer, X509_NAME_MAXLEN);
+    }
+    PySSL_END_ALLOW_THREADS
+
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+static PyObject *
+PySSL_server(PySSLObject *self)
+{
+    return PyString_FromString(self->server);
+}
+
+static PyObject *
+PySSL_issuer(PySSLObject *self)
+{
+    return PyString_FromString(self->issuer);
+}
+
+static PyObject *
+_create_tuple_for_attribute (ASN1_OBJECT *name, ASN1_STRING *value) {
+
+    char namebuf[X509_NAME_MAXLEN];
+    int buflen;
+    PyObject *name_obj;
+    PyObject *value_obj;
+    PyObject *attr;
+    unsigned char *valuebuf = NULL;
+
+    buflen = OBJ_obj2txt(namebuf, sizeof(namebuf), name, 0);
+    if (buflen < 0) {
+        _setSSLError(NULL, 0, __FILE__, __LINE__);
+        goto fail;
+    }
+    name_obj = PyString_FromStringAndSize(namebuf, buflen);
+    if (name_obj == NULL)
+        goto fail;
+
+    buflen = ASN1_STRING_to_UTF8(&valuebuf, value);
+    if (buflen < 0) {
+        _setSSLError(NULL, 0, __FILE__, __LINE__);
+        Py_DECREF(name_obj);
+        goto fail;
+    }
+    value_obj = PyUnicode_DecodeUTF8((char *) valuebuf,
+                                     buflen, "strict");
+    OPENSSL_free(valuebuf);
+    if (value_obj == NULL) {
+        Py_DECREF(name_obj);
+        goto fail;
+    }
+    attr = PyTuple_New(2);
+    if (attr == NULL) {
+        Py_DECREF(name_obj);
+        Py_DECREF(value_obj);
+        goto fail;
+    }
+    PyTuple_SET_ITEM(attr, 0, name_obj);
+    PyTuple_SET_ITEM(attr, 1, value_obj);
+    return attr;
+
+  fail:
+    return NULL;
+}
+
+static PyObject *
+_create_tuple_for_X509_NAME (X509_NAME *xname)
+{
+    PyObject *dn = NULL;    /* tuple which represents the "distinguished name" */
+    PyObject *rdn = NULL;   /* tuple to hold a "relative distinguished name" */
+    PyObject *rdnt;
+    PyObject *attr = NULL;   /* tuple to hold an attribute */
+    int entry_count = X509_NAME_entry_count(xname);
+    X509_NAME_ENTRY *entry;
+    ASN1_OBJECT *name;
+    ASN1_STRING *value;
+    int index_counter;
+    int rdn_level = -1;
+    int retcode;
+
+    dn = PyList_New(0);
+    if (dn == NULL)
+        return NULL;
+    /* now create another tuple to hold the top-level RDN */
+    rdn = PyList_New(0);
+    if (rdn == NULL)
+        goto fail0;
+
+    for (index_counter = 0;
+         index_counter < entry_count;
+         index_counter++)
+    {
+        entry = X509_NAME_get_entry(xname, index_counter);
+
+        /* check to see if we've gotten to a new RDN */
+        if (rdn_level >= 0) {
+            if (rdn_level != entry->set) {
+                /* yes, new RDN */
+                /* add old RDN to DN */
+                rdnt = PyList_AsTuple(rdn);
+                Py_DECREF(rdn);
+                if (rdnt == NULL)
+                    goto fail0;
+                retcode = PyList_Append(dn, rdnt);
+                Py_DECREF(rdnt);
+                if (retcode < 0)
+                    goto fail0;
+                /* create new RDN */
+                rdn = PyList_New(0);
+                if (rdn == NULL)
+                    goto fail0;
+            }
+        }
+        rdn_level = entry->set;
+
+        /* now add this attribute to the current RDN */
+        name = X509_NAME_ENTRY_get_object(entry);
+        value = X509_NAME_ENTRY_get_data(entry);
+        attr = _create_tuple_for_attribute(name, value);
+        /*
+        fprintf(stderr, "RDN level %d, attribute %s: %s\n",
+            entry->set,
+            PyString_AS_STRING(PyTuple_GET_ITEM(attr, 0)),
+            PyString_AS_STRING(PyTuple_GET_ITEM(attr, 1)));
+        */
+        if (attr == NULL)
+            goto fail1;
+        retcode = PyList_Append(rdn, attr);
+        Py_DECREF(attr);
+        if (retcode < 0)
+            goto fail1;
+    }
+    /* now, there's typically a dangling RDN */
+    if ((rdn != NULL) && (PyList_Size(rdn) > 0)) {
+        rdnt = PyList_AsTuple(rdn);
+        Py_DECREF(rdn);
+        if (rdnt == NULL)
+            goto fail0;
+        retcode = PyList_Append(dn, rdnt);
+        Py_DECREF(rdnt);
+        if (retcode < 0)
+            goto fail0;
+    }
+
+    /* convert list to tuple */
+    rdnt = PyList_AsTuple(dn);
+    Py_DECREF(dn);
+    if (rdnt == NULL)
+        return NULL;
+    return rdnt;
+
+  fail1:
+    Py_XDECREF(rdn);
+
+  fail0:
+    Py_XDECREF(dn);
+    return NULL;
+}
+
+static PyObject *
+_get_peer_alt_names (X509 *certificate) {
+
+    /* this code follows the procedure outlined in
+       OpenSSL's crypto/x509v3/v3_prn.c:X509v3_EXT_print()
+       function to extract the STACK_OF(GENERAL_NAME),
+       then iterates through the stack to add the
+       names. */
+
+    int i, j;
+    PyObject *peer_alt_names = Py_None;
+    PyObject *v, *t;
+    X509_EXTENSION *ext = NULL;
+    GENERAL_NAMES *names = NULL;
+    GENERAL_NAME *name;
+    X509V3_EXT_METHOD *method;
+    BIO *biobuf = NULL;
+    char buf[2048];
+    char *vptr;
+    int len;
+    const unsigned char *p;
+
+    if (certificate == NULL)
+        return peer_alt_names;
+
+    /* get a memory buffer */
+    biobuf = BIO_new(BIO_s_mem());
+
+    i = 0;
+    while ((i = X509_get_ext_by_NID(
+                    certificate, NID_subject_alt_name, i)) >= 0) {
+
+        if (peer_alt_names == Py_None) {
+            peer_alt_names = PyList_New(0);
+            if (peer_alt_names == NULL)
+                goto fail;
+        }
+
+        /* now decode the altName */
+        ext = X509_get_ext(certificate, i);
+        if(!(method = X509V3_EXT_get(ext))) {
+            PyErr_SetString(PySSLErrorObject,
+                            ERRSTR("No method for internalizing subjectAltName!"));
+            goto fail;
+        }
+
+        p = ext->value->data;
+        if (method->it)
+            names = (GENERAL_NAMES*) (ASN1_item_d2i(NULL,
+                                                    &p,
+                                                    ext->value->length,
+                                                    ASN1_ITEM_ptr(method->it)));
+        else
+            names = (GENERAL_NAMES*) (method->d2i(NULL,
+                                                  &p,
+                                                  ext->value->length));
+
+        for(j = 0; j < sk_GENERAL_NAME_num(names); j++) {
+
+            /* get a rendering of each name in the set of names */
+
+            name = sk_GENERAL_NAME_value(names, j);
+            if (name->type == GEN_DIRNAME) {
+
+                /* we special-case DirName as a tuple of tuples of attributes */
+
+                t = PyTuple_New(2);
+                if (t == NULL) {
+                    goto fail;
+                }
+
+                v = PyString_FromString("DirName");
+                if (v == NULL) {
+                    Py_DECREF(t);
+                    goto fail;
+                }
+                PyTuple_SET_ITEM(t, 0, v);
+
+                v = _create_tuple_for_X509_NAME (name->d.dirn);
+                if (v == NULL) {
+                    Py_DECREF(t);
+                    goto fail;
+                }
+                PyTuple_SET_ITEM(t, 1, v);
+
+            } else {
+
+                /* for everything else, we use the OpenSSL print form */
+
+                (void) BIO_reset(biobuf);
+                GENERAL_NAME_print(biobuf, name);
+                len = BIO_gets(biobuf, buf, sizeof(buf)-1);
+                if (len < 0) {
+                    _setSSLError(NULL, 0, __FILE__, __LINE__);
+                    goto fail;
+                }
+                vptr = strchr(buf, ':');
+                if (vptr == NULL)
+                    goto fail;
+                t = PyTuple_New(2);
+                if (t == NULL)
+                    goto fail;
+                v = PyString_FromStringAndSize(buf, (vptr - buf));
+                if (v == NULL) {
+                    Py_DECREF(t);
+                    goto fail;
+                }
+                PyTuple_SET_ITEM(t, 0, v);
+                v = PyString_FromStringAndSize((vptr + 1), (len - (vptr - buf + 1)));
+                if (v == NULL) {
+                    Py_DECREF(t);
+                    goto fail;
+                }
+                PyTuple_SET_ITEM(t, 1, v);
+            }
+
+            /* and add that rendering to the list */
+
+            if (PyList_Append(peer_alt_names, t) < 0) {
+                Py_DECREF(t);
+                goto fail;
+            }
+            Py_DECREF(t);
+        }
+    }
+    BIO_free(biobuf);
+    if (peer_alt_names != Py_None) {
+        v = PyList_AsTuple(peer_alt_names);
+        Py_DECREF(peer_alt_names);
+        return v;
+    } else {
+        return peer_alt_names;
+    }
+
+
+  fail:
+    if (biobuf != NULL)
+        BIO_free(biobuf);
+
+    if (peer_alt_names != Py_None) {
+        Py_XDECREF(peer_alt_names);
+    }
+
+    return NULL;
+}
+
+static PyObject *
+_decode_certificate (X509 *certificate, int verbose) {
+
+    PyObject *retval = NULL;
+    BIO *biobuf = NULL;
+    PyObject *peer;
+    PyObject *peer_alt_names = NULL;
+    PyObject *issuer;
+    PyObject *version;
+    PyObject *sn_obj;
+    ASN1_INTEGER *serialNumber;
+    char buf[2048];
+    int len;
+    ASN1_TIME *notBefore, *notAfter;
+    PyObject *pnotBefore, *pnotAfter;
+
+    retval = PyDict_New();
+    if (retval == NULL)
+        return NULL;
+
+    peer = _create_tuple_for_X509_NAME(
+        X509_get_subject_name(certificate));
+    if (peer == NULL)
+        goto fail0;
+    if (PyDict_SetItemString(retval, (const char *) "subject", peer) < 0) {
+        Py_DECREF(peer);
+        goto fail0;
+    }
+    Py_DECREF(peer);
+
+    if (verbose) {
+        issuer = _create_tuple_for_X509_NAME(
+            X509_get_issuer_name(certificate));
+        if (issuer == NULL)
+            goto fail0;
+        if (PyDict_SetItemString(retval, (const char *)"issuer", issuer) < 0) {
+            Py_DECREF(issuer);
+            goto fail0;
+        }
+        Py_DECREF(issuer);
+
+        version = PyInt_FromLong(X509_get_version(certificate) + 1);
+        if (PyDict_SetItemString(retval, "version", version) < 0) {
+            Py_DECREF(version);
+            goto fail0;
+        }
+        Py_DECREF(version);
+    }
+
+    /* get a memory buffer */
+    biobuf = BIO_new(BIO_s_mem());
+
+    if (verbose) {
+
+        (void) BIO_reset(biobuf);
+        serialNumber = X509_get_serialNumber(certificate);
+        /* should not exceed 20 octets, 160 bits, so buf is big enough */
+        i2a_ASN1_INTEGER(biobuf, serialNumber);
+        len = BIO_gets(biobuf, buf, sizeof(buf)-1);
+        if (len < 0) {
+            _setSSLError(NULL, 0, __FILE__, __LINE__);
+            goto fail1;
+        }
+        sn_obj = PyString_FromStringAndSize(buf, len);
+        if (sn_obj == NULL)
+            goto fail1;
+        if (PyDict_SetItemString(retval, "serialNumber", sn_obj) < 0) {
+            Py_DECREF(sn_obj);
+            goto fail1;
+        }
+        Py_DECREF(sn_obj);
+
+        (void) BIO_reset(biobuf);
+        notBefore = X509_get_notBefore(certificate);
+        ASN1_TIME_print(biobuf, notBefore);
+        len = BIO_gets(biobuf, buf, sizeof(buf)-1);
+        if (len < 0) {
+            _setSSLError(NULL, 0, __FILE__, __LINE__);
+            goto fail1;
+        }
+        pnotBefore = PyString_FromStringAndSize(buf, len);
+        if (pnotBefore == NULL)
+            goto fail1;
+        if (PyDict_SetItemString(retval, "notBefore", pnotBefore) < 0) {
+            Py_DECREF(pnotBefore);
+            goto fail1;
+        }
+        Py_DECREF(pnotBefore);
+    }
+
+    (void) BIO_reset(biobuf);
+    notAfter = X509_get_notAfter(certificate);
+    ASN1_TIME_print(biobuf, notAfter);
+    len = BIO_gets(biobuf, buf, sizeof(buf)-1);
+    if (len < 0) {
+        _setSSLError(NULL, 0, __FILE__, __LINE__);
+        goto fail1;
+    }
+    pnotAfter = PyString_FromStringAndSize(buf, len);
+    if (pnotAfter == NULL)
+        goto fail1;
+    if (PyDict_SetItemString(retval, "notAfter", pnotAfter) < 0) {
+        Py_DECREF(pnotAfter);
+        goto fail1;
+    }
+    Py_DECREF(pnotAfter);
+
+    /* Now look for subjectAltName */
+
+    peer_alt_names = _get_peer_alt_names(certificate);
+    if (peer_alt_names == NULL)
+        goto fail1;
+    else if (peer_alt_names != Py_None) {
+        if (PyDict_SetItemString(retval, "subjectAltName",
+                                 peer_alt_names) < 0) {
+            Py_DECREF(peer_alt_names);
+            goto fail1;
+        }
+        Py_DECREF(peer_alt_names);
+    }
+
+    BIO_free(biobuf);
+    return retval;
+
+  fail1:
+    if (biobuf != NULL)
+        BIO_free(biobuf);
+  fail0:
+    Py_XDECREF(retval);
+    return NULL;
+}
+
+
+static PyObject *
+PySSL_test_decode_certificate (PyObject *mod, PyObject *args) {
+
+    PyObject *retval = NULL;
+    char *filename = NULL;
+    X509 *x=NULL;
+    BIO *cert;
+    int verbose = 1;
+
+    if (!PyArg_ParseTuple(args, "s|i:test_decode_certificate", &filename, &verbose))
+        return NULL;
+
+    if ((cert=BIO_new(BIO_s_file())) == NULL) {
+        PyErr_SetString(PySSLErrorObject, "Can't malloc memory to read file");
+        goto fail0;
+    }
+
+    if (BIO_read_filename(cert,filename) <= 0) {
+        PyErr_SetString(PySSLErrorObject, "Can't open file");
+        goto fail0;
+    }
+
+    x = PEM_read_bio_X509_AUX(cert,NULL, NULL, NULL);
+    if (x == NULL) {
+        PyErr_SetString(PySSLErrorObject, "Error decoding PEM-encoded file");
+        goto fail0;
+    }
+
+    retval = _decode_certificate(x, verbose);
+
+  fail0:
+
+    if (cert != NULL) BIO_free(cert);
+    return retval;
+}
+
+
+static PyObject *
+PySSL_peercert(PySSLObject *self, PyObject *args)
+{
+    PyObject *retval = NULL;
+    int len;
+    int verification;
+    PyObject *binary_mode = Py_None;
+
+    if (!PyArg_ParseTuple(args, "|O:peer_certificate", &binary_mode))
+        return NULL;
+
+    if (!self->peer_cert)
+        Py_RETURN_NONE;
+
+    if (PyObject_IsTrue(binary_mode)) {
+        /* return cert in DER-encoded format */
+
+        unsigned char *bytes_buf = NULL;
+
+        bytes_buf = NULL;
+        len = i2d_X509(self->peer_cert, &bytes_buf);
+        if (len < 0) {
+            PySSL_SetError(self, len, __FILE__, __LINE__);
+            return NULL;
+        }
+        retval = PyString_FromStringAndSize((const char *) bytes_buf, len);
+        OPENSSL_free(bytes_buf);
+        return retval;
+
+    } else {
+
+        verification = SSL_CTX_get_verify_mode(self->ctx);
+        if ((verification & SSL_VERIFY_PEER) == 0)
+            return PyDict_New();
+        else
+            return _decode_certificate (self->peer_cert, 0);
+    }
+}
+
+PyDoc_STRVAR(PySSL_peercert_doc,
+"peer_certificate([der=False]) -> certificate\n\
+\n\
+Returns the certificate for the peer.  If no certificate was provided,\n\
+returns None.  If a certificate was provided, but not validated, returns\n\
+an empty dictionary.  Otherwise returns a dict containing information\n\
+about the peer certificate.\n\
+\n\
+If the optional argument is True, returns a DER-encoded copy of the\n\
+peer certificate, or None if no certificate was provided.  This will\n\
+return the certificate even if it wasn't validated.");
+
+static PyObject *PySSL_cipher (PySSLObject *self) {
+
+    PyObject *retval, *v;
+    SSL_CIPHER *current;
+    char *cipher_name;
+    char *cipher_protocol;
+
+    if (self->ssl == NULL)
+        return Py_None;
+    current = SSL_get_current_cipher(self->ssl);
+    if (current == NULL)
+        return Py_None;
+
+    retval = PyTuple_New(3);
+    if (retval == NULL)
+        return NULL;
+
+    cipher_name = (char *) SSL_CIPHER_get_name(current);
+    if (cipher_name == NULL) {
+        PyTuple_SET_ITEM(retval, 0, Py_None);
+    } else {
+        v = PyString_FromString(cipher_name);
+        if (v == NULL)
+            goto fail0;
+        PyTuple_SET_ITEM(retval, 0, v);
+    }
+    cipher_protocol = SSL_CIPHER_get_version(current);
+    if (cipher_protocol == NULL) {
+        PyTuple_SET_ITEM(retval, 1, Py_None);
+    } else {
+        v = PyString_FromString(cipher_protocol);
+        if (v == NULL)
+            goto fail0;
+        PyTuple_SET_ITEM(retval, 1, v);
+    }
+    v = PyInt_FromLong(SSL_CIPHER_get_bits(current, NULL));
+    if (v == NULL)
+        goto fail0;
+    PyTuple_SET_ITEM(retval, 2, v);
+    return retval;
+
+  fail0:
+    Py_DECREF(retval);
+    return NULL;
+}
+
+static void PySSL_dealloc(PySSLObject *self)
+{
+    if (self->peer_cert)        /* Possible not to have one? */
+        X509_free (self->peer_cert);
+    if (self->ssl)
+        SSL_free(self->ssl);
+    if (self->ctx && self->inherited == 0)
+        SSL_CTX_free(self->ctx);
+    Py_XDECREF(self->Socket);
+    PyObject_Del(self);
+}
+
+/* If the socket has a timeout, do a select()/poll() on the socket.
+   The argument writing indicates the direction.
+   Returns one of the possibilities in the timeout_state enum (above).
+ */
+
+static int
+check_socket_and_wait_for_timeout(PySocketSockObject *s, int writing)
+{
+    fd_set fds;
+    struct timeval tv;
+    int rc;
+
+    /* Nothing to do unless we're in timeout mode (not non-blocking) */
+    if (s->sock_timeout < 0.0)
+        return SOCKET_IS_BLOCKING;
+    else if (s->sock_timeout == 0.0)
+        return SOCKET_IS_NONBLOCKING;
+
+    /* Guard against closed socket */
+    if (s->sock_fd < 0)
+        return SOCKET_HAS_BEEN_CLOSED;
+
+    /* Prefer poll, if available, since you can poll() any fd
+     * which can't be done with select(). */
+#ifdef HAVE_POLL
+    {
+        struct pollfd pollfd;
+        int timeout;
+
+        pollfd.fd = s->sock_fd;
+        pollfd.events = writing ? POLLOUT : POLLIN;
+
+        /* s->sock_timeout is in seconds, timeout in ms */
+        timeout = (int)(s->sock_timeout * 1000 + 0.5);
+        PySSL_BEGIN_ALLOW_THREADS
+        rc = poll(&pollfd, 1, timeout);
+        PySSL_END_ALLOW_THREADS
+
+        goto normal_return;
+    }
+#endif
+
+    /* Guard against socket too large for select*/
+#ifndef Py_SOCKET_FD_CAN_BE_GE_FD_SETSIZE
+    if (s->sock_fd >= FD_SETSIZE)
+        return SOCKET_TOO_LARGE_FOR_SELECT;
+#endif
+
+    /* Construct the arguments to select */
+    tv.tv_sec = (int)s->sock_timeout;
+    tv.tv_usec = (int)((s->sock_timeout - tv.tv_sec) * 1e6);
+    FD_ZERO(&fds);
+    FD_SET(s->sock_fd, &fds);
+
+    /* See if the socket is ready */
+    PySSL_BEGIN_ALLOW_THREADS
+    if (writing)
+        rc = select(s->sock_fd+1, NULL, &fds, NULL, &tv);
+    else
+        rc = select(s->sock_fd+1, &fds, NULL, NULL, &tv);
+    PySSL_END_ALLOW_THREADS
+
+#ifdef HAVE_POLL
+normal_return:
+#endif
+    /* Return SOCKET_TIMED_OUT on timeout, SOCKET_OPERATION_OK otherwise
+       (when we are able to write or when there's something to read) */
+    return rc == 0 ? SOCKET_HAS_TIMED_OUT : SOCKET_OPERATION_OK;
+}
+
+static PyObject *PySSL_SSLwrite(PySSLObject *self, PyObject *args)
+{
+    char *data;
+    int len;
+    int count;
+    int sockstate;
+    int err;
+    int nonblocking;
+
+    if (!PyArg_ParseTuple(args, "s#:write", &data, &count))
+        return NULL;
+
+    /* just in case the blocking state of the socket has been changed */
+    nonblocking = (self->Socket->sock_timeout >= 0.0);
+    BIO_set_nbio(SSL_get_rbio(self->ssl), nonblocking);
+    BIO_set_nbio(SSL_get_wbio(self->ssl), nonblocking);
+
+    sockstate = check_socket_and_wait_for_timeout(self->Socket, 1);
+    if (sockstate == SOCKET_HAS_TIMED_OUT) {
+        PyErr_SetString(PySSLErrorObject,
+                        "The write operation timed out");
+        return NULL;
+    } else if (sockstate == SOCKET_HAS_BEEN_CLOSED) {
+        PyErr_SetString(PySSLErrorObject,
+                        "Underlying socket has been closed.");
+        return NULL;
+    } else if (sockstate == SOCKET_TOO_LARGE_FOR_SELECT) {
+        PyErr_SetString(PySSLErrorObject,
+                        "Underlying socket too large for select().");
+        return NULL;
+    }
+    do {
+        err = 0;
+        PySSL_BEGIN_ALLOW_THREADS
+        len = SSL_write(self->ssl, data, count);
+        err = SSL_get_error(self->ssl, len);
+        PySSL_END_ALLOW_THREADS
+        if(PyErr_CheckSignals()) {
+            return NULL;
+        }
+        if (err == SSL_ERROR_WANT_READ) {
+            sockstate = check_socket_and_wait_for_timeout(self->Socket, 0);
+        } else if (err == SSL_ERROR_WANT_WRITE) {
+            sockstate = check_socket_and_wait_for_timeout(self->Socket, 1);
+        } else {
+            sockstate = SOCKET_OPERATION_OK;
+        }
+        if (sockstate == SOCKET_HAS_TIMED_OUT) {
+            PyErr_SetString(PySSLErrorObject,
+                            "The write operation timed out");
+            return NULL;
+        } else if (sockstate == SOCKET_HAS_BEEN_CLOSED) {
+            PyErr_SetString(PySSLErrorObject,
+                            "Underlying socket has been closed.");
+            return NULL;
+        } else if (sockstate == SOCKET_IS_NONBLOCKING) {
+            break;
+        }
+    } while (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE);
+    if (len > 0)
+        return PyInt_FromLong(len);
+    else
+        return PySSL_SetError(self, len, __FILE__, __LINE__);
+}
+
+PyDoc_STRVAR(PySSL_SSLwrite_doc,
+"write(s) -> len\n\
+\n\
+Writes the string s into the SSL object.  Returns the number\n\
+of bytes written.");
+
+static PyObject *PySSL_SSLpending(PySSLObject *self)
+{
+    int count = 0;
+
+    PySSL_BEGIN_ALLOW_THREADS
+    count = SSL_pending(self->ssl);
+    PySSL_END_ALLOW_THREADS
+    if (count < 0)
+        return PySSL_SetError(self, count, __FILE__, __LINE__);
+    else
+        return PyInt_FromLong(count);
+}
+
+PyDoc_STRVAR(PySSL_SSLpending_doc,
+"pending() -> count\n\
+\n\
+Returns the number of already decrypted bytes available for read,\n\
+pending on the connection.\n");
+
+static PyObject *PySSL_SSLread(PySSLObject *self, PyObject *args)
+{
+    PyObject *buf;
+    int count = 0;
+    int len = 1024;
+    int sockstate;
+    int err;
+    int nonblocking;
+
+    if (!PyArg_ParseTuple(args, "|i:read", &len))
+        return NULL;
+
+    if (!(buf = PyString_FromStringAndSize((char *) 0, len)))
+        return NULL;
+
+    /* just in case the blocking state of the socket has been changed */
+    nonblocking = (self->Socket->sock_timeout >= 0.0);
+    BIO_set_nbio(SSL_get_rbio(self->ssl), nonblocking);
+    BIO_set_nbio(SSL_get_wbio(self->ssl), nonblocking);
+
+    /* first check if there are bytes ready to be read */
+    PySSL_BEGIN_ALLOW_THREADS
+    count = SSL_pending(self->ssl);
+    PySSL_END_ALLOW_THREADS
+
+    if (!count) {
+        sockstate = check_socket_and_wait_for_timeout(self->Socket, 0);
+        if (sockstate == SOCKET_HAS_TIMED_OUT) {
+            PyErr_SetString(PySSLErrorObject,
+                            "The read operation timed out");
+            Py_DECREF(buf);
+            return NULL;
+        } else if (sockstate == SOCKET_TOO_LARGE_FOR_SELECT) {
+            PyErr_SetString(PySSLErrorObject,
+                            "Underlying socket too large for select().");
+            Py_DECREF(buf);
+            return NULL;
+        } else if (sockstate == SOCKET_HAS_BEEN_CLOSED) {
+            if (SSL_get_shutdown(self->ssl) !=
+                SSL_RECEIVED_SHUTDOWN)
+            {
+                Py_DECREF(buf);
+                PyErr_SetString(PySSLErrorObject,
+                                "Socket closed without SSL shutdown handshake");
+                return NULL;
+            } else {
+                /* should contain a zero-length string */
+                _PyString_Resize(&buf, 0);
+                return buf;
+            }
+        }
+    }
+    do {
+        err = 0;
+        PySSL_BEGIN_ALLOW_THREADS
+        count = SSL_read(self->ssl, PyString_AsString(buf), len);
+        err = SSL_get_error(self->ssl, count);
+        PySSL_END_ALLOW_THREADS
+        if(PyErr_CheckSignals()) {
+            Py_DECREF(buf);
+            return NULL;
+        }
+        if (err == SSL_ERROR_WANT_READ) {
+            sockstate = check_socket_and_wait_for_timeout(self->Socket, 0);
+        } else if (err == SSL_ERROR_WANT_WRITE) {
+            sockstate = check_socket_and_wait_for_timeout(self->Socket, 1);
+        } else if ((err == SSL_ERROR_ZERO_RETURN) &&
+                   (SSL_get_shutdown(self->ssl) ==
+                    SSL_RECEIVED_SHUTDOWN))
+        {
+            _PyString_Resize(&buf, 0);
+            return buf;
+        } else {
+            sockstate = SOCKET_OPERATION_OK;
+        }
+        if (sockstate == SOCKET_HAS_TIMED_OUT) {
+            PyErr_SetString(PySSLErrorObject,
+                            "The read operation timed out");
+            Py_DECREF(buf);
+            return NULL;
+        } else if (sockstate == SOCKET_IS_NONBLOCKING) {
+            break;
+        }
+    } while (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE);
+    if (count <= 0) {
+        Py_DECREF(buf);
+        return PySSL_SetError(self, count, __FILE__, __LINE__);
+    }
+    if (count != len)
+        _PyString_Resize(&buf, count);
+    return buf;
+}
+
+PyDoc_STRVAR(PySSL_SSLread_doc,
+"read([len]) -> string\n\
+\n\
+Read up to len bytes from the SSL socket.");
+
+static PyObject *PySSL_SSLshutdown(PySSLObject *self)
+{
+    int err, ssl_err, sockstate, nonblocking;
+    int zeros = 0;
+
+    /* Guard against closed socket */
+    if (self->Socket->sock_fd < 0) {
+        PyErr_SetString(PySSLErrorObject,
+                        "Underlying socket has been closed.");
+        return NULL;
+    }
+
+    /* Just in case the blocking state of the socket has been changed */
+    nonblocking = (self->Socket->sock_timeout >= 0.0);
+    BIO_set_nbio(SSL_get_rbio(self->ssl), nonblocking);
+    BIO_set_nbio(SSL_get_wbio(self->ssl), nonblocking);
+
+    while (1) {
+        PySSL_BEGIN_ALLOW_THREADS
+        /* Disable read-ahead so that unwrap can work correctly.
+         * Otherwise OpenSSL might read in too much data,
+         * eating clear text data that happens to be
+         * transmitted after the SSL shutdown.
+         * Should be safe to call repeatedly everytime this
+         * function is used and the shutdown_seen_zero != 0
+         * condition is met.
+         */
+        if (self->shutdown_seen_zero)
+            SSL_set_read_ahead(self->ssl, 0);
+        err = SSL_shutdown(self->ssl);
+        PySSL_END_ALLOW_THREADS
+        /* If err == 1, a secure shutdown with SSL_shutdown() is complete */
+        if (err > 0)
+            break;
+        if (err == 0) {
+            /* Don't loop endlessly; instead preserve legacy
+               behaviour of trying SSL_shutdown() only twice.
+               This looks necessary for OpenSSL < 0.9.8m */
+            if (++zeros > 1)
+                break;
+            /* Shutdown was sent, now try receiving */
+            self->shutdown_seen_zero = 1;
+            continue;
+        }
+
+        /* Possibly retry shutdown until timeout or failure */
+        ssl_err = SSL_get_error(self->ssl, err);
+        if (ssl_err == SSL_ERROR_WANT_READ)
+            sockstate = check_socket_and_wait_for_timeout(self->Socket, 0);
+        else if (ssl_err == SSL_ERROR_WANT_WRITE)
+            sockstate = check_socket_and_wait_for_timeout(self->Socket, 1);
+        else
+            break;
+        if (sockstate == SOCKET_HAS_TIMED_OUT) {
+            if (ssl_err == SSL_ERROR_WANT_READ)
+                PyErr_SetString(PySSLErrorObject,
+                                "The read operation timed out");
+            else
+                PyErr_SetString(PySSLErrorObject,
+                                "The write operation timed out");
+            return NULL;
+        }
+        else if (sockstate == SOCKET_TOO_LARGE_FOR_SELECT) {
+            PyErr_SetString(PySSLErrorObject,
+                            "Underlying socket too large for select().");
+            return NULL;
+        }
+        else if (sockstate != SOCKET_OPERATION_OK)
+            /* Retain the SSL error code */
+            break;
+    }
+
+    if (err < 0)
+        return PySSL_SetError(self, err, __FILE__, __LINE__);
+    else {
+        Py_INCREF(self->Socket);
+        return (PyObject *) (self->Socket);
+    }
+}
+
+PyDoc_STRVAR(PySSL_SSLshutdown_doc,
+"shutdown(s) -> socket\n\
+\n\
+Does the SSL shutdown handshake with the remote end, and returns\n\
+the underlying socket object.");
+
+static PyMethodDef PySSLMethods[] = {
+    {"wrap_accepted", (PyCFunction)PySSL_SSLwrap_accepted, METH_VARARGS,
+     PySSL_SSLwrap_accepted_doc},
+    {"do_handshake", (PyCFunction)PySSL_SSLdo_handshake, METH_NOARGS},
+    {"write", (PyCFunction)PySSL_SSLwrite, METH_VARARGS,
+     PySSL_SSLwrite_doc},
+    {"read", (PyCFunction)PySSL_SSLread, METH_VARARGS,
+     PySSL_SSLread_doc},
+    {"pending", (PyCFunction)PySSL_SSLpending, METH_NOARGS,
+     PySSL_SSLpending_doc},
+    {"server", (PyCFunction)PySSL_server, METH_NOARGS},
+    {"issuer", (PyCFunction)PySSL_issuer, METH_NOARGS},
+    {"peer_certificate", (PyCFunction)PySSL_peercert, METH_VARARGS,
+     PySSL_peercert_doc},
+    {"cipher", (PyCFunction)PySSL_cipher, METH_NOARGS},
+    {"shutdown", (PyCFunction)PySSL_SSLshutdown, METH_NOARGS,
+     PySSL_SSLshutdown_doc},
+    {NULL, NULL}
+};
+
+static PyObject *PySSL_getattr(PySSLObject *self, char *name)
+{
+    return Py_FindMethod(PySSLMethods, (PyObject *)self, name);
+}
+
+static PyTypeObject PySSL_Type = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "ssl.SSLContext",                   /*tp_name*/
+    sizeof(PySSLObject),                /*tp_basicsize*/
+    0,                                  /*tp_itemsize*/
+    /* methods */
+    (destructor)PySSL_dealloc,          /*tp_dealloc*/
+    0,                                  /*tp_print*/
+    (getattrfunc)PySSL_getattr,         /*tp_getattr*/
+    0,                                  /*tp_setattr*/
+    0,                                  /*tp_compare*/
+    0,                                  /*tp_repr*/
+    0,                                  /*tp_as_number*/
+    0,                                  /*tp_as_sequence*/
+    0,                                  /*tp_as_mapping*/
+    0,                                  /*tp_hash*/
+};
+
+#ifdef HAVE_OPENSSL_RAND
+
+/* helper routines for seeding the SSL PRNG */
+static PyObject *
+PySSL_RAND_add(PyObject *self, PyObject *args)
+{
+    char *buf;
+    int len;
+    double entropy;
+
+    if (!PyArg_ParseTuple(args, "s#d:RAND_add", &buf, &len, &entropy))
+        return NULL;
+    RAND_add(buf, len, entropy);
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+PyDoc_STRVAR(PySSL_RAND_add_doc,
+"RAND_add(string, entropy)\n\
+\n\
+Mix string into the OpenSSL PRNG state.  entropy (a float) is a lower\n\
+bound on the entropy contained in string.  See RFC 1750.");
+
+static PyObject *
+PySSL_RAND_status(PyObject *self)
+{
+    return PyInt_FromLong(RAND_status());
+}
+
+PyDoc_STRVAR(PySSL_RAND_status_doc,
+"RAND_status() -> 0 or 1\n\
+\n\
+Returns 1 if the OpenSSL PRNG has been seeded with enough data and 0 if not.\n\
+It is necessary to seed the PRNG with RAND_add() on some platforms before\n\
+using the ssl() function.");
+
+static PyObject *
+PySSL_RAND_egd(PyObject *self, PyObject *arg)
+{
+    int bytes;
+
+    if (!PyString_Check(arg))
+        return PyErr_Format(PyExc_TypeError,
+                            "RAND_egd() expected string, found %s",
+                            Py_TYPE(arg)->tp_name);
+    bytes = RAND_egd(PyString_AS_STRING(arg));
+    if (bytes == -1) {
+        PyErr_SetString(PySSLErrorObject,
+                        "EGD connection failed or EGD did not return "
+                        "enough data to seed the PRNG");
+        return NULL;
+    }
+    return PyInt_FromLong(bytes);
+}
+
+PyDoc_STRVAR(PySSL_RAND_egd_doc,
+"RAND_egd(path) -> bytes\n\
+\n\
+Queries the entropy gather daemon (EGD) on the socket named by 'path'.\n\
+Returns number of bytes read.  Raises SSLError if connection to EGD\n\
+fails or if it does provide enough data to seed PRNG.");
+
+#endif
+
+/* List of functions exported by this module. */
+
+static PyMethodDef PySSL_methods[] = {
+    {"sslwrap",             PySSL_sslwrap,
+     METH_VARARGS, sslwrap_doc},
+    {"_test_decode_cert",   PySSL_test_decode_certificate,
+     METH_VARARGS},
+#ifdef HAVE_OPENSSL_RAND
+    {"RAND_add",            PySSL_RAND_add, METH_VARARGS,
+     PySSL_RAND_add_doc},
+    {"RAND_egd",            PySSL_RAND_egd, METH_O,
+     PySSL_RAND_egd_doc},
+    {"RAND_status",         (PyCFunction)PySSL_RAND_status, METH_NOARGS,
+     PySSL_RAND_status_doc},
+#endif
+    {NULL,                  NULL}            /* Sentinel */
+};
+
+
+#ifdef WITH_THREAD
+
+/* an implementation of OpenSSL threading operations in terms
+   of the Python C thread library */
+
+static PyThread_type_lock *_ssl_locks = NULL;
+
+static unsigned long _ssl_thread_id_function (void) {
+    return PyThread_get_thread_ident();
+}
+
+static void _ssl_thread_locking_function (int mode, int n, const char *file, int line) {
+    /* this function is needed to perform locking on shared data
+       structures. (Note that OpenSSL uses a number of global data
+       structures that will be implicitly shared whenever multiple threads
+       use OpenSSL.) Multi-threaded applications will crash at random if
+       it is not set.
+
+       locking_function() must be able to handle up to CRYPTO_num_locks()
+       different mutex locks. It sets the n-th lock if mode & CRYPTO_LOCK, and
+       releases it otherwise.
+
+       file and line are the file number of the function setting the
+       lock. They can be useful for debugging.
+    */
+
+    if ((_ssl_locks == NULL) ||
+        (n < 0) || ((unsigned)n >= _ssl_locks_count))
+        return;
+
+    if (mode & CRYPTO_LOCK) {
+        PyThread_acquire_lock(_ssl_locks[n], 1);
+    } else {
+        PyThread_release_lock(_ssl_locks[n]);
+    }
+}
+
+static int _setup_ssl_threads(void) {
+
+    unsigned int i;
+
+    if (_ssl_locks == NULL) {
+        _ssl_locks_count = CRYPTO_num_locks();
+        _ssl_locks = (PyThread_type_lock *)
+            malloc(sizeof(PyThread_type_lock) * _ssl_locks_count);
+        if (_ssl_locks == NULL)
+            return 0;
+        memset(_ssl_locks, 0, sizeof(PyThread_type_lock) * _ssl_locks_count);
+        for (i = 0;  i < _ssl_locks_count;  i++) {
+            _ssl_locks[i] = PyThread_allocate_lock();
+            if (_ssl_locks[i] == NULL) {
+                unsigned int j;
+                for (j = 0;  j < i;  j++) {
+                    PyThread_free_lock(_ssl_locks[j]);
+                }
+                free(_ssl_locks);
+                return 0;
+            }
+        }
+        CRYPTO_set_locking_callback(_ssl_thread_locking_function);
+        CRYPTO_set_id_callback(_ssl_thread_id_function);
+    }
+    return 1;
+}
+
+#endif  /* def HAVE_THREAD */
+
+PyDoc_STRVAR(module_doc,
+"Implementation module for SSL socket operations.  See the socket module\n\
+for documentation.");
+
+PyMODINIT_FUNC
+init_forge_ssl(void)
+{
+    PyObject *m, *d;
+
+    Py_TYPE(&PySSL_Type) = &PyType_Type;
+
+    m = Py_InitModule3("_forge_ssl", PySSL_methods, module_doc);
+    if (m == NULL)
+        return;
+    d = PyModule_GetDict(m);
+
+    /* Load _socket module and its C API */
+    if (PySocketModule_ImportModuleAndAPI())
+        return;
+
+    /* Init OpenSSL */
+    SSL_load_error_strings();
+    SSL_library_init();
+#ifdef WITH_THREAD
+    /* note that this will start threading if not already started */
+    if (!_setup_ssl_threads()) {
+        return;
+    }
+#endif
+    OpenSSL_add_all_algorithms();
+
+    /* Add symbols to module dict */
+    PySSLErrorObject = PyErr_NewException("ssl.SSLError",
+                                          PySocketModule.error,
+                                          NULL);
+    if (PySSLErrorObject == NULL)
+        return;
+    if (PyDict_SetItemString(d, "SSLError", PySSLErrorObject) != 0)
+        return;
+    if (PyDict_SetItemString(d, "SSLType",
+                             (PyObject *)&PySSL_Type) != 0)
+        return;
+    PyModule_AddIntConstant(m, "SSL_ERROR_ZERO_RETURN",
+                            PY_SSL_ERROR_ZERO_RETURN);
+    PyModule_AddIntConstant(m, "SSL_ERROR_WANT_READ",
+                            PY_SSL_ERROR_WANT_READ);
+    PyModule_AddIntConstant(m, "SSL_ERROR_WANT_WRITE",
+                            PY_SSL_ERROR_WANT_WRITE);
+    PyModule_AddIntConstant(m, "SSL_ERROR_WANT_X509_LOOKUP",
+                            PY_SSL_ERROR_WANT_X509_LOOKUP);
+    PyModule_AddIntConstant(m, "SSL_ERROR_SYSCALL",
+                            PY_SSL_ERROR_SYSCALL);
+    PyModule_AddIntConstant(m, "SSL_ERROR_SSL",
+                            PY_SSL_ERROR_SSL);
+    PyModule_AddIntConstant(m, "SSL_ERROR_WANT_CONNECT",
+                            PY_SSL_ERROR_WANT_CONNECT);
+    /* non ssl.h errorcodes */
+    PyModule_AddIntConstant(m, "SSL_ERROR_EOF",
+                            PY_SSL_ERROR_EOF);
+    PyModule_AddIntConstant(m, "SSL_ERROR_INVALID_ERROR_CODE",
+                            PY_SSL_ERROR_INVALID_ERROR_CODE);
+    /* cert requirements */
+    PyModule_AddIntConstant(m, "CERT_NONE",
+                            PY_SSL_CERT_NONE);
+    PyModule_AddIntConstant(m, "CERT_OPTIONAL",
+                            PY_SSL_CERT_OPTIONAL);
+    PyModule_AddIntConstant(m, "CERT_REQUIRED",
+                            PY_SSL_CERT_REQUIRED);
+
+    /* protocol versions */
+    PyModule_AddIntConstant(m, "PROTOCOL_SSLv2",
+                            PY_SSL_VERSION_SSL2);
+    PyModule_AddIntConstant(m, "PROTOCOL_SSLv3",
+                            PY_SSL_VERSION_SSL3);
+    PyModule_AddIntConstant(m, "PROTOCOL_SSLv23",
+                            PY_SSL_VERSION_SSL23);
+    PyModule_AddIntConstant(m, "PROTOCOL_TLSv1",
+                            PY_SSL_VERSION_TLS1);
+
+    /* session cache modes */
+    PyModule_AddIntConstant(m, "SESS_CACHE_OFF",
+                            PY_SSL_SESS_CACHE_OFF);
+    PyModule_AddIntConstant(m, "SESS_CACHE_CLIENT",
+                            PY_SSL_SESS_CACHE_CLIENT);
+    PyModule_AddIntConstant(m, "SESS_CACHE_SERVER",
+                            PY_SSL_SESS_CACHE_SERVER);
+    PyModule_AddIntConstant(m, "SESS_CACHE_BOTH",
+                            PY_SSL_SESS_CACHE_BOTH);
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/forge_ssl/forge/socketmodule.h b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/forge_ssl/forge/socketmodule.h
new file mode 100644
index 0000000..a4415b5
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/forge_ssl/forge/socketmodule.h
@@ -0,0 +1,268 @@
+/* Socket module header file */
+
+/* Includes needed for the sockaddr_* symbols below */
+#ifndef MS_WINDOWS
+#ifdef __VMS
+#   include <socket.h>
+# else
+#   include <sys/socket.h>
+# endif
+# include <netinet/in.h>
+# if !(defined(__BEOS__) || defined(__CYGWIN__) || (defined(PYOS_OS2) && defined(PYCC_VACPP)))
+#  include <netinet/tcp.h>
+# endif
+
+#else /* MS_WINDOWS */
+# include <winsock2.h>
+# include <ws2tcpip.h>
+/* VC6 is shipped with old platform headers, and does not have MSTcpIP.h
+ * Separate SDKs have all the functions we want, but older ones don't have
+ * any version information.
+ * I use SIO_GET_MULTICAST_FILTER to detect a decent SDK.
+ */
+# ifdef SIO_GET_MULTICAST_FILTER
+#  include <MSTcpIP.h> /* for SIO_RCVALL */
+#  define HAVE_ADDRINFO
+#  define HAVE_SOCKADDR_STORAGE
+#  define HAVE_GETADDRINFO
+#  define HAVE_GETNAMEINFO
+#  define ENABLE_IPV6
+# else
+typedef int socklen_t;
+# endif /* IPPROTO_IPV6 */
+#endif /* MS_WINDOWS */
+
+#ifdef HAVE_SYS_UN_H
+# include <sys/un.h>
+#else
+# undef AF_UNIX
+#endif
+
+#ifdef HAVE_LINUX_NETLINK_H
+# ifdef HAVE_ASM_TYPES_H
+#  include <asm/types.h>
+# endif
+# include <linux/netlink.h>
+#else
+#  undef AF_NETLINK
+#endif
+
+#ifdef HAVE_BLUETOOTH_BLUETOOTH_H
+/*
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/rfcomm.h>
+#include <bluetooth/l2cap.h>
+#include <bluetooth/sco.h>
+#include <bluetooth/hci.h>
+*/
+#endif
+
+#ifdef HAVE_BLUETOOTH_H
+#include <bluetooth.h>
+#endif
+
+#ifdef HAVE_NETPACKET_PACKET_H
+# include <sys/ioctl.h>
+# include <net/if.h>
+# include <netpacket/packet.h>
+#endif
+
+#ifdef HAVE_LINUX_TIPC_H
+# include <linux/tipc.h>
+#endif
+
+#ifndef Py__SOCKET_H
+#define Py__SOCKET_H
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Python module and C API name */
+#define PySocket_MODULE_NAME    "_socket"
+#define PySocket_CAPI_NAME      "CAPI"
+
+/* Abstract the socket file descriptor type */
+#ifdef MS_WINDOWS
+typedef SOCKET SOCKET_T;
+#       ifdef MS_WIN64
+#               define SIZEOF_SOCKET_T 8
+#       else
+#               define SIZEOF_SOCKET_T 4
+#       endif
+#else
+typedef int SOCKET_T;
+#       define SIZEOF_SOCKET_T SIZEOF_INT
+#endif
+
+/* Socket address */
+typedef union sock_addr {
+    struct sockaddr_in in;
+#ifdef AF_UNIX
+    struct sockaddr_un un;
+#endif
+#ifdef AF_NETLINK
+    struct sockaddr_nl nl;
+#endif
+#ifdef ENABLE_IPV6
+    struct sockaddr_in6 in6;
+    struct sockaddr_storage storage;
+#endif
+#ifdef HAVE_BLUETOOTH_BLUETOOTH_H
+/*
+    struct sockaddr_l2 bt_l2;
+    struct sockaddr_rc bt_rc;
+    struct sockaddr_sco bt_sco;
+    struct sockaddr_hci bt_hci;
+*/
+#endif
+#ifdef HAVE_NETPACKET_PACKET_H
+    struct sockaddr_ll ll;
+#endif
+} sock_addr_t;
+
+/* The object holding a socket.  It holds some extra information,
+   like the address family, which is used to decode socket address
+   arguments properly. */
+
+typedef struct {
+    PyObject_HEAD
+    SOCKET_T sock_fd;           /* Socket file descriptor */
+    int sock_family;            /* Address family, e.g., AF_INET */
+    int sock_type;              /* Socket type, e.g., SOCK_STREAM */
+    int sock_proto;             /* Protocol type, usually 0 */
+    PyObject *(*errorhandler)(void); /* Error handler; checks
+                                        errno, returns NULL and
+                                        sets a Python exception */
+    double sock_timeout;                 /* Operation timeout in seconds;
+                                        0.0 means non-blocking */
+} PySocketSockObject;
+
+/* --- C API ----------------------------------------------------*/
+
+/* Short explanation of what this C API export mechanism does
+   and how it works:
+
+    The _ssl module needs access to the type object defined in
+    the _socket module. Since cross-DLL linking introduces a lot of
+    problems on many platforms, the "trick" is to wrap the
+    C API of a module in a struct which then gets exported to
+    other modules via a PyCObject.
+
+    The code in socketmodule.c defines this struct (which currently
+    only contains the type object reference, but could very
+    well also include other C APIs needed by other modules)
+    and exports it as PyCObject via the module dictionary
+    under the name "CAPI".
+
+    Other modules can now include the socketmodule.h file
+    which defines the needed C APIs to import and set up
+    a static copy of this struct in the importing module.
+
+    After initialization, the importing module can then
+    access the C APIs from the _socket module by simply
+    referring to the static struct, e.g.
+
+    Load _socket module and its C API; this sets up the global
+    PySocketModule:
+
+    if (PySocketModule_ImportModuleAndAPI())
+        return;
+
+
+    Now use the C API as if it were defined in the using
+    module:
+
+    if (!PyArg_ParseTuple(args, "O!|zz:ssl",
+
+                          PySocketModule.Sock_Type,
+
+                          (PyObject*)&Sock,
+                          &key_file, &cert_file))
+        return NULL;
+
+    Support could easily be extended to export more C APIs/symbols
+    this way. Currently, only the type object is exported,
+    other candidates would be socket constructors and socket
+    access functions.
+
+*/
+
+/* C API for usage by other Python modules */
+typedef struct {
+    PyTypeObject *Sock_Type;
+    PyObject *error;
+} PySocketModule_APIObject;
+
+/* XXX The net effect of the following appears to be to define a function
+   XXX named PySocketModule_APIObject in _ssl.c.  It's unclear why it isn't
+   XXX defined there directly.
+
+   >>> It's defined here because other modules might also want to use
+   >>> the C API.
+
+*/
+#ifndef PySocket_BUILDING_SOCKET
+
+/* --- C API ----------------------------------------------------*/
+
+/* Interfacestructure to C API for other modules.
+   Call PySocketModule_ImportModuleAndAPI() to initialize this
+   structure. After that usage is simple:
+
+   if (!PyArg_ParseTuple(args, "O!|zz:ssl",
+                         &PySocketModule.Sock_Type, (PyObject*)&Sock,
+                         &key_file, &cert_file))
+     return NULL;
+   ...
+*/
+
+static
+PySocketModule_APIObject PySocketModule;
+
+/* You *must* call this before using any of the functions in
+   PySocketModule and check its outcome; otherwise all accesses will
+   result in a segfault. Returns 0 on success. */
+
+#ifndef DPRINTF
+# define DPRINTF if (0) printf
+#endif
+
+static
+int PySocketModule_ImportModuleAndAPI(void)
+{
+    PyObject *mod = 0, *v = 0;
+    char *apimodule = PySocket_MODULE_NAME;
+    char *apiname = PySocket_CAPI_NAME;
+    void *api;
+
+    DPRINTF("Importing the %s C API...\n", apimodule);
+    mod = PyImport_ImportModuleNoBlock(apimodule);
+    if (mod == NULL)
+        goto onError;
+    DPRINTF(" %s package found\n", apimodule);
+    v = PyObject_GetAttrString(mod, apiname);
+    if (v == NULL)
+        goto onError;
+    Py_DECREF(mod);
+    DPRINTF(" API object %s found\n", apiname);
+    api = PyCObject_AsVoidPtr(v);
+    if (api == NULL)
+        goto onError;
+    Py_DECREF(v);
+    memcpy(&PySocketModule, api, sizeof(PySocketModule));
+    DPRINTF(" API object loaded and initialized.\n");
+    return 0;
+
+ onError:
+    DPRINTF(" not found.\n");
+    Py_XDECREF(mod);
+    Py_XDECREF(v);
+    return -1;
+}
+
+#endif /* !PySocket_BUILDING_SOCKET */
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* !Py__SOCKET_H */
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/forge_ssl/forge/ssl.py b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/forge_ssl/forge/ssl.py
new file mode 100644
index 0000000..aa9fc14
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/forge_ssl/forge/ssl.py
@@ -0,0 +1,486 @@
+# Wrapper module for _ssl, providing some additional facilities
+# implemented in Python.  Written by Bill Janssen.
+
+"""\
+This module provides some more Pythonic support for SSL.
+
+Object types:
+
+  SSLSocket -- subtype of socket.socket which does SSL over the socket
+
+Exceptions:
+
+  SSLError -- exception raised for I/O errors
+
+Functions:
+
+  cert_time_to_seconds -- convert time string used for certificate
+                          notBefore and notAfter functions to integer
+                          seconds past the Epoch (the time values
+                          returned from time.time())
+
+  fetch_server_certificate (HOST, PORT) -- fetch the certificate provided
+                          by the server running on HOST at port PORT.  No
+                          validation of the certificate is performed.
+
+Integer constants:
+
+SSL_ERROR_ZERO_RETURN
+SSL_ERROR_WANT_READ
+SSL_ERROR_WANT_WRITE
+SSL_ERROR_WANT_X509_LOOKUP
+SSL_ERROR_SYSCALL
+SSL_ERROR_SSL
+SSL_ERROR_WANT_CONNECT
+
+SSL_ERROR_EOF
+SSL_ERROR_INVALID_ERROR_CODE
+
+The following group define certificate requirements that one side is
+allowing/requiring from the other side:
+
+CERT_NONE - no certificates from the other side are required (or will
+            be looked at if provided)
+CERT_OPTIONAL - certificates are not required, but if provided will be
+                validated, and if validation fails, the connection will
+                also fail
+CERT_REQUIRED - certificates are required, and will be validated, and
+                if validation fails, the connection will also fail
+
+The following constants identify various SSL protocol variants:
+
+PROTOCOL_SSLv2
+PROTOCOL_SSLv3
+PROTOCOL_SSLv23
+PROTOCOL_TLSv1
+
+The following constants identify various SSL session caching modes:
+
+SESS_CACHE_OFF
+SESS_CACHE_CLIENT
+SESS_CACHE_SERVER
+SESS_CACHE_BOTH
+"""
+
+import textwrap
+
+import _forge_ssl             # if we can't import it, let the error propagate
+
+from _forge_ssl import SSLError
+from _forge_ssl import CERT_NONE, CERT_OPTIONAL, CERT_REQUIRED
+from _forge_ssl import PROTOCOL_SSLv2, PROTOCOL_SSLv3, PROTOCOL_SSLv23, PROTOCOL_TLSv1
+from _forge_ssl import SESS_CACHE_OFF, SESS_CACHE_CLIENT, SESS_CACHE_SERVER, SESS_CACHE_BOTH
+from _forge_ssl import RAND_status, RAND_egd, RAND_add
+from _forge_ssl import \
+     SSL_ERROR_ZERO_RETURN, \
+     SSL_ERROR_WANT_READ, \
+     SSL_ERROR_WANT_WRITE, \
+     SSL_ERROR_WANT_X509_LOOKUP, \
+     SSL_ERROR_SYSCALL, \
+     SSL_ERROR_SSL, \
+     SSL_ERROR_WANT_CONNECT, \
+     SSL_ERROR_EOF, \
+     SSL_ERROR_INVALID_ERROR_CODE
+
+from socket import socket, _fileobject, _delegate_methods
+from socket import error as socket_error
+from socket import getnameinfo as _getnameinfo
+import base64        # for DER-to-PEM translation
+import errno
+
+class SSLSocket(socket):
+
+    """This class implements a subtype of socket.socket that wraps
+    the underlying OS socket in an SSL context when necessary, and
+    provides read and write methods over that channel."""
+
+    def __init__(self, parent_socket, sock, keyfile=None, certfile=None,
+                 server_side=False, cert_reqs=CERT_NONE,
+                 ssl_version=PROTOCOL_SSLv23,
+                 sess_cache_mode=SESS_CACHE_SERVER,
+                 sess_id_ctx=None,
+                 ca_certs=None,
+                 do_handshake_on_connect=True,
+                 suppress_ragged_eofs=True):
+        socket.__init__(self, _sock=sock._sock)
+        # The initializer for socket overrides the methods send(), recv(), etc.
+        # in the instancce, which we don't need -- but we want to provide the
+        # methods defined in SSLSocket.
+        for attr in _delegate_methods:
+            try:
+                delattr(self, attr)
+            except AttributeError:
+                pass
+
+        if certfile and not keyfile:
+            keyfile = certfile
+
+        create = True
+        connected = False
+        if not server_side:
+            # see if it's connected
+            try:
+                socket.getpeername(self)
+                connected = True
+            except socket_error, e:
+                if e.errno != errno.ENOTCONN:
+                    raise
+                # no, no connection yet
+                self._sslobj = None
+                create = False
+        if create:
+            # yes, create the SSL object
+            if parent_socket == None:
+                self._sslobj = _forge_ssl.sslwrap(
+                                                 self._sock,
+                                                 server_side,
+                                                 keyfile, certfile,
+                                                 cert_reqs, ssl_version,
+                                                 sess_cache_mode, sess_id_ctx,
+                                                 ca_certs)
+            else:
+                self._sslobj = parent_socket._sslobj.wrap_accepted(self._sock)
+
+        if connected and do_handshake_on_connect:
+            self.do_handshake()
+        self.keyfile = keyfile
+        self.certfile = certfile
+        self.cert_reqs = cert_reqs
+        self.ssl_version = ssl_version
+        self.sess_cache_mode = sess_cache_mode
+        self.sess_id_ctx = sess_id_ctx
+        self.ca_certs = ca_certs
+        self.do_handshake_on_connect = do_handshake_on_connect
+        self.suppress_ragged_eofs = suppress_ragged_eofs
+        self._makefile_refs = 0
+
+    def read(self, len=1024):
+
+        """Read up to LEN bytes and return them.
+        Return zero-length string on EOF."""
+
+        try:
+            return self._sslobj.read(len)
+        except SSLError, x:
+            if x.args[0] == SSL_ERROR_EOF and self.suppress_ragged_eofs:
+                return ''
+            else:
+                raise
+
+    def write(self, data):
+
+        """Write DATA to the underlying SSL channel.  Returns
+        number of bytes of DATA actually transmitted."""
+
+        return self._sslobj.write(data)
+
+    def getpeercert(self, binary_form=False):
+
+        """Returns a formatted version of the data in the
+        certificate provided by the other end of the SSL channel.
+        Return None if no certificate was provided, {} if a
+        certificate was provided, but not validated."""
+
+        return self._sslobj.peer_certificate(binary_form)
+
+    def cipher(self):
+
+        if not self._sslobj:
+            return None
+        else:
+            return self._sslobj.cipher()
+
+    def send(self, data, flags=0):
+        if self._sslobj:
+            if flags != 0:
+                raise ValueError(
+                    "non-zero flags not allowed in calls to send() on %s" %
+                    self.__class__)
+            while True:
+                try:
+                    v = self._sslobj.write(data)
+                except SSLError, x:
+                    if x.args[0] == SSL_ERROR_WANT_READ:
+                        return 0
+                    elif x.args[0] == SSL_ERROR_WANT_WRITE:
+                        return 0
+                    else:
+                        raise
+                else:
+                    return v
+        else:
+            return socket.send(self, data, flags)
+
+    def sendto(self, data, addr, flags=0):
+        if self._sslobj:
+            raise ValueError("sendto not allowed on instances of %s" %
+                             self.__class__)
+        else:
+            return socket.sendto(self, data, addr, flags)
+
+    def sendall(self, data, flags=0):
+        if self._sslobj:
+            if flags != 0:
+                raise ValueError(
+                    "non-zero flags not allowed in calls to sendall() on %s" %
+                    self.__class__)
+            amount = len(data)
+            count = 0
+            while (count < amount):
+                v = self.send(data[count:])
+                count += v
+            return amount
+        else:
+            return socket.sendall(self, data, flags)
+
+    def recv(self, buflen=1024, flags=0):
+        if self._sslobj:
+            if flags != 0:
+                raise ValueError(
+                    "non-zero flags not allowed in calls to recv() on %s" %
+                    self.__class__)
+            return self.read(buflen)
+        else:
+            return socket.recv(self, buflen, flags)
+
+    def recv_into(self, buffer, nbytes=None, flags=0):
+        if buffer and (nbytes is None):
+            nbytes = len(buffer)
+        elif nbytes is None:
+            nbytes = 1024
+        if self._sslobj:
+            if flags != 0:
+                raise ValueError(
+                  "non-zero flags not allowed in calls to recv_into() on %s" %
+                  self.__class__)
+            tmp_buffer = self.read(nbytes)
+            v = len(tmp_buffer)
+            buffer[:v] = tmp_buffer
+            return v
+        else:
+            return socket.recv_into(self, buffer, nbytes, flags)
+
+    def recvfrom(self, addr, buflen=1024, flags=0):
+        if self._sslobj:
+            raise ValueError("recvfrom not allowed on instances of %s" %
+                             self.__class__)
+        else:
+            return socket.recvfrom(self, addr, buflen, flags)
+
+    def recvfrom_into(self, buffer, nbytes=None, flags=0):
+        if self._sslobj:
+            raise ValueError("recvfrom_into not allowed on instances of %s" %
+                             self.__class__)
+        else:
+            return socket.recvfrom_into(self, buffer, nbytes, flags)
+
+    def pending(self):
+        if self._sslobj:
+            return self._sslobj.pending()
+        else:
+            return 0
+
+    def unwrap(self):
+        if self._sslobj:
+            try:
+                # if connected then shutdown
+                self.getpeername()
+                s = self._sslobj.shutdown()
+            except:
+                s = self._sock
+            self._sslobj = None
+            return s
+        else:
+            raise ValueError("No SSL wrapper around " + str(self))
+
+    def shutdown(self, how):
+        self._sslobj = None
+        socket.shutdown(self, how)
+
+    def close(self):
+        if self._makefile_refs < 1:
+            if self._sslobj:
+                self.unwrap()
+            socket.close(self)
+        else:
+            self._makefile_refs -= 1
+
+    def do_handshake(self):
+
+        """Perform a TLS/SSL handshake."""
+
+        self._sslobj.do_handshake()
+
+    def connect(self, addr):
+
+        """Connects to remote ADDR, and then wraps the connection in
+        an SSL channel."""
+
+        # Here we assume that the socket is client-side, and not
+        # connected at the time of the call.  We connect it, then wrap it.
+        if self._sslobj:
+            raise ValueError("attempt to connect already-connected SSLSocket!")
+        socket.connect(self, addr)
+        self._sslobj = _forge_ssl.sslwrap(self._sock, False,
+                                          self.keyfile, self.certfile,
+                                          self.cert_reqs, self.ssl_version,
+                                          self.sess_cache_mode,
+                                          self.sess_id_ctx,
+                                          self.ca_certs)
+        if self.do_handshake_on_connect:
+            self.do_handshake()
+
+    def accept(self):
+
+        """Accepts a new connection from a remote client, and returns
+        a tuple containing that new connection wrapped with a server-side
+        SSL channel, and the address of the remote client."""
+
+        newsock, addr = socket.accept(self)
+        return (SSLSocket(self,
+                          newsock,
+                          keyfile=self.keyfile,
+                          certfile=self.certfile,
+                          server_side=True,
+                          cert_reqs=self.cert_reqs,
+                          ssl_version=self.ssl_version,
+                          sess_cache_mode=self.sess_cache_mode,
+                          sess_id_ctx=self.sess_id_ctx,
+                          ca_certs=self.ca_certs,
+                          do_handshake_on_connect=self.do_handshake_on_connect,
+                          suppress_ragged_eofs=self.suppress_ragged_eofs),
+                addr)
+
+    def makefile(self, mode='r', bufsize=-1):
+
+        """Make and return a file-like object that
+        works with the SSL connection.  Just use the code
+        from the socket module."""
+
+        self._makefile_refs += 1
+        # close=True so as to decrement the reference count when done with
+        # the file-like object.
+        return _fileobject(self, mode, bufsize, close=True)
+
+
+
+def wrap_socket(sock, parent_socket=None, keyfile=None, certfile=None,
+                server_side=False, cert_reqs=CERT_NONE,
+                ssl_version=PROTOCOL_SSLv23,
+                sess_cache_mode=SESS_CACHE_SERVER,
+                sess_id_ctx=None,
+                ca_certs=None,
+                do_handshake_on_connect=True,
+                suppress_ragged_eofs=True):
+
+    return SSLSocket(parent_socket,
+                     sock, keyfile=keyfile, certfile=certfile,
+                     server_side=server_side, cert_reqs=cert_reqs,
+                     ssl_version=ssl_version,
+                     sess_cache_mode=sess_cache_mode,
+                     sess_id_ctx=sess_id_ctx,
+                     ca_certs=ca_certs,
+                     do_handshake_on_connect=do_handshake_on_connect,
+                     suppress_ragged_eofs=suppress_ragged_eofs)
+
+
+# some utility functions
+
+def cert_time_to_seconds(cert_time):
+
+    """Takes a date-time string in standard ASN1_print form
+    ("MON DAY 24HOUR:MINUTE:SEC YEAR TIMEZONE") and return
+    a Python time value in seconds past the epoch."""
+
+    import time
+    return time.mktime(time.strptime(cert_time, "%b %d %H:%M:%S %Y GMT"))
+
+PEM_HEADER = "-----BEGIN CERTIFICATE-----"
+PEM_FOOTER = "-----END CERTIFICATE-----"
+
+def DER_cert_to_PEM_cert(der_cert_bytes):
+
+    """Takes a certificate in binary DER format and returns the
+    PEM version of it as a string."""
+
+    if hasattr(base64, 'standard_b64encode'):
+        # preferred because older API gets line-length wrong
+        f = base64.standard_b64encode(der_cert_bytes)
+        return (PEM_HEADER + '\n' +
+                textwrap.fill(f, 64) + '\n' +
+                PEM_FOOTER + '\n')
+    else:
+        return (PEM_HEADER + '\n' +
+                base64.encodestring(der_cert_bytes) +
+                PEM_FOOTER + '\n')
+
+def PEM_cert_to_DER_cert(pem_cert_string):
+
+    """Takes a certificate in ASCII PEM format and returns the
+    DER-encoded version of it as a byte sequence"""
+
+    if not pem_cert_string.startswith(PEM_HEADER):
+        raise ValueError("Invalid PEM encoding; must start with %s"
+                         % PEM_HEADER)
+    if not pem_cert_string.strip().endswith(PEM_FOOTER):
+        raise ValueError("Invalid PEM encoding; must end with %s"
+                         % PEM_FOOTER)
+    d = pem_cert_string.strip()[len(PEM_HEADER):-len(PEM_FOOTER)]
+    return base64.decodestring(d)
+
+def get_server_certificate(addr, ssl_version=PROTOCOL_SSLv3, ca_certs=None):
+
+    """Retrieve the certificate from the server at the specified address,
+    and return it as a PEM-encoded string.
+    If 'ca_certs' is specified, validate the server cert against it.
+    If 'ssl_version' is specified, use it in the connection attempt."""
+
+    host, port = addr
+    if (ca_certs is not None):
+        cert_reqs = CERT_REQUIRED
+    else:
+        cert_reqs = CERT_NONE
+    s = wrap_socket(socket(), ssl_version=ssl_version,
+                    cert_reqs=cert_reqs, ca_certs=ca_certs)
+    s.connect(addr)
+    dercert = s.getpeercert(True)
+    s.close()
+    return DER_cert_to_PEM_cert(dercert)
+
+def get_protocol_name(protocol_code):
+    if protocol_code == PROTOCOL_TLSv1:
+        return "TLSv1"
+    elif protocol_code == PROTOCOL_SSLv23:
+        return "SSLv23"
+    elif protocol_code == PROTOCOL_SSLv2:
+        return "SSLv2"
+    elif protocol_code == PROTOCOL_SSLv3:
+        return "SSLv3"
+    else:
+        return "<unknown>"
+
+
+# a replacement for the old socket.ssl function
+
+def sslwrap_simple(sock, keyfile=None, certfile=None):
+
+    """A replacement for the old socket.ssl function.  Designed
+    for compability with Python 2.5 and earlier.  Will disappear in
+    Python 3.0."""
+
+    if hasattr(sock, "_sock"):
+        sock = sock._sock
+
+    ssl_sock = _forge_ssl.sslwrap(sock, 0, keyfile, certfile,
+                                  CERT_NONE, PROTOCOL_SSLv23,
+                                  SESS_CACHE_SERVER, None, None)
+    try:
+        sock.getpeername()
+    except:
+        # no, no connection yet
+        pass
+    else:
+        # yes, do the handshake
+        ssl_sock.do_handshake()
+
+    return ssl_sock
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/forge_ssl/setup.py b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/forge_ssl/setup.py
new file mode 100644
index 0000000..350ae37
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/forge_ssl/setup.py
@@ -0,0 +1,12 @@
+#!/usr/bin/env python
+
+from distutils.core import setup, Extension
+
+ssl = Extension('_forge_ssl',
+        sources = ['forge/_ssl.c'])
+
+setup (name = 'Forge SSL',
+        version = '1.0',
+        description = 'Python SSL with session cache support.',
+        ext_modules = [ssl],
+        py_modules = ['forge.ssl'])
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/form.html b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/form.html
new file mode 100644
index 0000000..cfe9f94
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/form.html
@@ -0,0 +1,150 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+   <head>
+      <title>Forge Form Test</title>
+      <link type="text/css" rel="stylesheet" media="all" href="screen.css" />
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
+      <script type="text/javascript" src="forge/debug.js"></script>
+      <script type="text/javascript" src="forge/util.js"></script>
+      <script type="text/javascript" src="forge/log.js"></script>
+      <script type="text/javascript" src="forge/form.js"></script>
+      <script type="text/javascript" src="form.js"></script>
+
+      <link type="text/css" rel="stylesheet" media="all" href="screen.css" />
+   </head>
+<body>
+<div class="nav"><a href="index.html">Forge Tests</a> / Form</div>
+
+<div class="header">
+   <h1>Form Tests</h1>
+</div>
+
+<div class="content">
+
+<form id="form-1" class="ajax standard" method="post" action="">
+   <fieldset>
+      <legend>Text</legend>
+
+      <p>
+         <input name="text1" type="text" value="value1" />
+         <input name="text2.sub1" type="text" value="value2" />
+         <input name="text2.sub2[]" type="text" value="value3" />
+         <input name="text2.sub2[]" type="text" value="value4" />
+         <input name="text2.sub3[0]" type="text" value="value5" />
+         <input name="text2.sub4[0][0]" type="text" value="value6" />
+         <input name="text2.sub4[0][]" type="text" value="value7" />
+         <input name="text2.sub5[foo][]" type="text" value="value8" />
+         <input name="text2.sub5[dotted.name]" type="text" value="value9" />
+         <input name="text2.sub6[]" type="text" value="value10" />
+         <input name="text2.sub7[].@" type="text" value="value11" />
+         <input name="text2.sub7[].@" type="text" value="value12" />
+         <input name="text2.sub8[][].@" type="text" value="value13" />
+      </p>
+
+      <p>
+         <label>username <input name="username" type="text" value="username" /></label>
+         <label>password <input name="password" type="password" value="password" /></label>
+      </p>
+
+      <p>
+         <label>password1.1 <input name="password1" type="password" value="password" /></label>
+         <label>password1.2 <input name="password1" type="password" value="password" /></label>
+      </p>
+   </fieldset>
+
+   <fieldset>
+      <legend>Checkbox</legend>
+
+      <p>
+         <label><input name="checkbox1" type="checkbox" value="c1" /> C1</label>
+         <label><input name="checkbox1" type="checkbox" value="c2" /> C1</label>
+         <label><input name="checkbox1" type="checkbox" value="c3" /> C1</label>
+      </p>
+
+      <p>
+         <label><input name="checkbox2[]" type="checkbox" value="c1" /> C2[]</label>
+         <label><input name="checkbox2[]" type="checkbox" value="c2" /> C2[]</label>
+         <label><input name="checkbox2[3]" type="checkbox" value="c3" /> C2[3]</label>
+         <label><input name="checkbox2[]" type="checkbox" value="c4" /> C2[]</label>
+         <label><input name="checkbox2.sub1" type="checkbox" value="c4" /> C2.sub1</label>
+      </p>
+
+      <p>
+         <label><input name="checkbox3.sub1" type="checkbox" value="c1" /> C3.s1</label>
+         <label><input name="checkbox3.sub2" type="checkbox" value="c2" /> C3.s2</label>
+         <label><input name="checkbox3.sub2" type="checkbox" value="c3" /> C3.s2</label>
+         <label><input name="checkbox3[]" type="checkbox" value="c4" /> C3[]</label>
+      </p>
+   </fieldset>
+
+   <fieldset>
+      <legend>Radio</legend>
+
+      <p>
+         <label><input name="radio1" type="radio" value="r1" /> R1</label>
+         <label><input name="radio1" type="radio" value="r2" /> R1</label>
+         <label><input name="radio1" type="radio" value="r3" /> R1</label>
+         <label><input name="radio1" type="radio" value="r4" /> R1</label>
+      </p>
+
+      <p>
+         <label><input name="radio2.sub1" type="radio" value="r1" /> R2.s1</label>
+         <label><input name="radio2.sub1" type="radio" value="r2" /> R2.s1</label>
+         <label><input name="radio2.sub2" type="radio" value="r3" /> R2.s2</label>
+         <label><input name="radio2.sub2" type="radio" value="r4" /> R2.s2</label>
+      </p>
+   </fieldset>
+
+   <fieldset>
+      <legend>Select</legend>
+      <p>
+         <select name="select1">
+            <option value="select1">Select 1</option>
+            <option value="select2">Select 2</option>
+            <option value="select3">Select 3</option>
+         </select>
+      </p>
+
+      <p>
+         <select name="select2" multiple="multiple">
+            <option value="select1">Select 1</option>
+            <option value="select2">Select 2</option>
+            <option value="select3">Select 3</option>
+         </select>
+      </p>
+   </fieldset>
+
+   <fieldset>
+      <legend>Text Area</legend>
+
+      <textarea name="textarea">Test text.</textarea>
+   </fieldset>
+
+   <fieldset>
+      <legend>Buttons</legend>
+
+      <p>
+         <button name="button1" type="submit" value="submit">Submit Form</button>
+         <button name="button2" type="reset" value="reset">Reset Form</button>
+      </p>
+   </fieldset>
+
+   <fieldset>
+      <legend>Input Buttons</legend>
+      
+      <p>
+         <input name="submit" type="submit" value="Submit Form" />
+         <input name="reset" type="reset" value="Reset Form" />
+      </p>
+   </fieldset>
+</form>
+
+<p>Result:</p>
+<div id="result"></div>
+
+</div>
+
+</body>
+</html>
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/form.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/form.js
new file mode 100644
index 0000000..abfbab0
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/form.js
@@ -0,0 +1,40 @@
+/**
+ * Forge Form Tests
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2011 Digital Bazaar, Inc. All rights reserved.
+ */
+(function($) {
+$(document).ready(function()
+{
+   // logging category
+   var cat = 'forge.tests.form';
+   
+   // local alias
+   var forge = window.forge;
+   
+   $('form.ajax').each(function(i, form)
+   {
+      $(form).submit(function()
+      {
+         try
+         {
+            var f = forge.form.serialize($(this));
+            forge.log.debug(cat, 'result:', JSON.stringify(f));
+            $('#result').html(JSON.stringify(f));
+            
+            /* dictionary test
+            var f = forge.form.serialize($(this), '.', {'username':'user'});
+            forge.log.debug(cat, 'result:', JSON.stringify(f));
+            */
+         }
+         catch(e)
+         {
+            console.log('exception', e.stack);
+         }
+         return false;
+      });
+   });
+});
+})(jQuery);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/heartbleed.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/heartbleed.js
new file mode 100644
index 0000000..b38869a
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/heartbleed.js
@@ -0,0 +1,55 @@
+var forge = require('../js/forge');
+var net = require('net');
+
+var socket = new net.Socket();
+
+var client = forge.tls.createConnection({
+  server: false,
+  verify: function(connection, verified, depth, certs) {
+    // skip verification for testing
+    return true;
+  },
+  connected: function(connection) {
+    // heartbleeds 2k
+    console.log('[tls] connected');
+    connection.prepareHeartbeatRequest('', 2048);
+    setTimeout(function() {
+      client.close();
+    }, 1000);
+  },
+  tlsDataReady: function(connection) {
+    // encrypted data is ready to be sent to the server
+    var data = connection.tlsData.getBytes();
+    socket.write(data, 'binary');
+  },
+  dataReady: function(connection) {
+    // clear data from the server is ready
+    var data = connection.data.getBytes();
+    console.log('[tls] received from the server: ' + data);
+  },
+  heartbeatReceived: function(c, payload) {
+    console.log('Heartbleed:\n' + payload.toHex());
+    client.close();
+  },
+  closed: function() {
+    console.log('[tls] disconnected');
+    socket.end();
+  },
+  error: function(connection, error) {
+    console.log('[tls] error', error);
+  }
+});
+
+socket.on('connect', function() {
+  console.log('[socket] connected');
+  client.handshake();
+});
+socket.on('data', function(data) {
+  client.process(data.toString('binary'));
+});
+socket.on('end', function() {
+  console.log('[socket] disconnected');
+});
+
+// connect
+socket.connect(443, 'yahoo.com');
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/http.html b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/http.html
new file mode 100644
index 0000000..3bdf941
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/http.html
@@ -0,0 +1,229 @@
+<html>
+   <head>
+      <link type="text/css" rel="stylesheet" media="all" href="screen.css" />
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js"></script>
+      <script type="text/javascript" src="forge/util.js"></script>
+      <script type="text/javascript" src="forge/socket.js"></script>
+      <script type="text/javascript" src="forge/http.js"></script>
+      <script type="text/javascript" src="forge/log.js"></script>
+      
+      <script type="text/javascript">
+      //<![CDATA[
+      // logging category
+      var cat = 'forge.tests.http';
+
+      window.forge.socketPool =
+      {
+         ready: function()
+         {
+            forge.log.debug(cat, 'SocketPool ready.');
+         }
+      };
+
+      swfobject.embedSWF(
+         'forge/SocketPool.swf', 'socketPool', '0', '0', '9.0.0',
+         false, {}, {allowscriptaccess: 'always'}, {});
+      
+      // local aliases
+      var net = window.forge.net;
+      var http = window.forge.http;
+      var util = window.forge.util;
+
+      var client;
+      
+      function client_init()
+      {
+         try
+         {
+            var sp = net.createSocketPool({
+               flashId: 'socketPool',
+               policyPort: 19945,
+               msie: false
+            });
+            client = http.createClient({
+               //url: 'http://' + window.location.host,
+               socketPool: sp,
+               connections: 10
+            });
+            
+            document.getElementById('feedback').innerHTML =
+               'HTTP client created';
+         }
+         catch(ex)
+         {
+            forge.log.error(cat, ex);
+         }
+         return false;
+      }
+      
+      function client_cleanup()
+      {
+         var sp = client.socketPool;
+         client.destroy();
+         sp.destroy();
+         document.getElementById('feedback').innerHTML =
+            'HTTP client cleaned up';
+         return false;
+      }
+
+      function client_send()
+      {
+         var request = http.createRequest({
+            method: 'GET',
+            path: '/'
+            //body: 'echo=foo',
+            //headers: [{'Content-Type': 'application/x-www-form-urlencoded'}]
+         });
+         
+         client.send({
+            request: request,
+            connected: function(e)
+            {
+               forge.log.debug(cat, 'connected', e);
+            },
+            headerReady: function(e)
+            {
+               forge.log.debug(cat, 'header ready', e);
+            },
+            bodyReady: function(e)
+            {
+               forge.log.debug(cat, 'body ready', e);
+            },
+            error: function(e)
+            {
+               forge.log.error(cat, 'error', e);
+            }
+         });
+         document.getElementById('feedback').innerHTML =
+            'HTTP request sent';
+         return false;
+      }
+
+      function client_send_10()
+      {
+         for(var i = 0; i < 10; ++i)
+         {
+            client_send();
+         }
+         return false;
+      }
+
+      function client_stress()
+      {
+         for(var i = 0; i < 10; ++i)
+         {
+            setTimeout(function()
+            {
+               for(var i = 0; i < 10; ++i)
+               {
+                  client_send();
+               }
+            }, 0);
+         }
+         return false;
+      }
+      
+      function client_cookies()
+      {
+         var cookie =
+         {
+            name: 'test-cookie',
+            value: 'test-value',
+            maxAge: -1,
+            secure: false,
+            path: '/'
+         };
+         client.setCookie(cookie);
+         forge.log.debug(cat, 'cookie', client.getCookie('test-cookie'));
+      }
+
+      function client_clear_cookies()
+      {
+         client.clearCookies();
+      }
+
+      function request_add_cookies()
+      {
+         var cookie1 =
+         {
+            name: 'test-cookie1',
+            value: 'test-value1',
+            maxAge: -1,
+            secure: false,
+            path: '/'
+         };
+         var cookie2 =
+         {
+            name: 'test-cookie2',
+            value: 'test-value2',
+            maxAge: -1,
+            secure: false,
+            path: '/'
+         };
+         var request = http.createRequest({
+            method: 'GET',
+            path: '/'
+         });
+         request.addCookie(cookie1);
+         request.addCookie(cookie2);
+         forge.log.debug(cat, 'request', request.toString());
+      }
+
+      function response_get_cookies()
+      {
+         var response = http.createResponse();
+         response.appendField('Set-Cookie',
+            'test-cookie1=test-value1; max-age=0; path=/; secure');
+         response.appendField('Set-Cookie',
+            'test-cookie2=test-value2; ' +
+            'expires=Thu, 21-Aug-2008 23:47:25 GMT; path=/');
+         var cookies = response.getCookies();
+         forge.log.debug(cat, 'cookies', cookies);
+      }
+      
+      //]]>
+      </script>
+   </head>
+   <body>
+      <div class="nav"><a href="index.html">Forge Tests</a> / HTTP</div>
+
+      <div class="header">
+         <h1>HTTP Test</h1>
+      </div>
+
+      <div class="content">
+
+      <div id="socketPool">
+         <p>Could not load the flash SocketPool.</p>
+      </div>
+
+      <fieldset class="section">
+         <ul>
+            <li>Use the controls below to test the HTTP client.</li>
+            <li>You currently need a JavaScript console to view the output.</li>
+         </ul>
+      </fieldset>
+
+      <fieldset class="section">
+      <legend>Controls</legend>
+         <button id="init" onclick="javascript:return client_init();">init</button>
+         <button id="cleanup" onclick="javascript:return client_cleanup();">cleanup</button>
+         <button id="send" onclick="javascript:return client_send();">send</button>
+         <button id="send10" onclick="javascript:return client_send_10();">send 10</button>
+         <button id="stress" onclick="javascript:return client_stress();">stress</button>
+         <button id="client_cookies" onclick="javascript:return client_cookies();">cookies</button>
+         <button id="clear_cookies" onclick="javascript:return client_clear_cookies();">clear cookies</button>
+         <button id="add_cookies" onclick="javascript:return request_add_cookies();">add cookies</button>
+         <button id="get_cookies" onclick="javascript:return response_get_cookies();">get cookies</button>
+      </fieldset>
+
+      <fieldset class="section">
+      <legend>Feedback</legend>
+      <p>Feedback from the flash SocketPool:</p>
+      <div id="feedback">
+      None
+      </div>
+
+      </div>
+   </body>
+</html>
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/index.html b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/index.html
new file mode 100644
index 0000000..2e27c8a
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/index.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+   <meta name="author" content="Digital Bazaar, Inc.; http://digitalbazaar.com/" />
+   <meta name="description" content="Forge Tests" />
+   <link type="text/css" rel="stylesheet" media="all" href="screen.css" />
+   <title>Forge Tests</title>
+</head>
+
+<body>
+  <div class="header">
+     <h1>Forge Tests</h1>
+  </div>
+  <div class="content">
+     <fieldset class="section">
+       <ul>
+         <li>These are various tests of the Forge JavaScript libraries.</li>
+         <li>Please see the code and documentation for details.</li>
+       </ul>
+     </fieldset>
+     <fieldset class="section">
+        <legend>Tests</legend>
+        <ul>
+           <li><a href="common.html">Common</a>
+              <sup>1,2</sup>
+           </li>
+           <li><a href="form.html">Form</a>
+              <sup>1,2</sup>
+           </li>
+           <li><a href="performance.html">Performance</a>
+              <sup>1,2</sup>
+           </li>
+           <li><a href="tasks.html">Tasks</a>
+              <sup>1,2</sup>
+           </li>
+           <li><a href="socketPool.html">SocketPool</a>
+              <sup>1</sup>
+           </li>
+           <li><a href="http.html">HTTP</a>
+              <sup>1</sup>
+           </li>
+           <li><a href="tls.html">TLS</a>
+              <sup>2</sup>
+           </li>
+           <li><a href="xhr.html">XHR</a>
+              <sup>1,2</sup>
+           </li>
+           <li><a href="webid.html">Web ID</a>
+              <sup>1,2</sup>
+           </li>
+        </ul>
+        <div>
+           <span><sup>1</sup>: Test works over HTTP</span><br/>
+           <span><sup>2</sup>: Test works over HTTPS</span>
+        </div>
+     </fieldset>
+  </div>
+  <div class="footer">
+     Copyright &copy; 2010 <a href="http://digitalbazaar.com/">Digital Bazaar, Inc.</a>
+  </div>
+</body>
+</html>
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/keygen.html b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/keygen.html
new file mode 100644
index 0000000..22e2432
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/keygen.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="utf-8" />
+  <script type="text/javascript" src="forge/util.js"></script>
+  <script type="text/javascript" src="forge/sha256.js"></script>
+  <script type="text/javascript" src="forge/cipher.js"></script>
+  <script type="text/javascript" src="forge/cipherModes.js"></script>
+  <script type="text/javascript" src="forge/aes.js"></script>
+  <script type="text/javascript" src="forge/prng.js"></script>
+  <script type="text/javascript" src="forge/random.js"></script>
+  <script type="text/javascript" src="forge/jsbn.js"></script>
+  <script type="text/javascript" src="forge/asn1.js"></script>
+  <script type="text/javascript" src="forge/pem.js"></script>
+  <script type="text/javascript" src="forge/rsa.js"></script>
+</head>
+
+<body>
+
+<script type="text/javascript">
+
+function async() {
+  var bits = 2048;
+  console.log('Generating ' + bits + '-bit key-pair...');
+  var st = +new Date();
+  forge.pki.rsa.generateKeyPair({
+    bits: bits,
+    workers: -1,
+    /*workLoad: 100,*/
+    workerScript: 'forge/prime.worker.js'
+  }, function(err, keypair) {
+    var et = +new Date();
+    console.log('Key-pair created in ' + (et - st) + 'ms.');
+    //console.log('private', forge.pki.privateKeyToPem(keypair.privateKey));
+    //console.log('public', forge.pki.publicKeyToPem(keypair.publicKey));
+  });
+}
+
+function sync() {
+  var bits = 2048;
+  console.log('Generating ' + bits + '-bit key-pair...');
+  var st = +new Date();
+  var keypair = forge.pki.rsa.generateKeyPair(bits);
+  var et = +new Date();
+  console.log('Key-pair created in ' + (et - st) + 'ms.');
+  //console.log('private', forge.pki.privateKeyToPem(keypair.privateKey));
+  //console.log('public', forge.pki.publicKeyToPem(keypair.publicKey));
+}
+
+async();
+//sync();
+
+</script>
+
+</body>
+</html>
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/login.css b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/login.css
new file mode 100644
index 0000000..3a1cb05
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/login.css
@@ -0,0 +1,26 @@
+/* WebID CSS */
+
+* {
+margin: 0;
+padding: 0;
+}
+
+p {
+margin: 10px 0;
+}
+
+body {
+margin: 10px;
+font-family: helvetica,arial,sans-serif;
+font-size: 14px;
+}
+
+#header {
+padding: 5px;
+background-color: #ddf;
+border: 2px solid #000;
+}
+
+#header h1 {
+font-size: 20px;
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/loginDemo.html b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/loginDemo.html
new file mode 100644
index 0000000..a1aecd4
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/loginDemo.html
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+   <head>
+      <title>Web ID Login</title>
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js"></script>
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
+      <script type="text/javascript" src="forge/debug.js"></script>
+      <script type="text/javascript" src="forge/util.js"></script>
+      <script type="text/javascript" src="forge/log.js"></script>
+      <script type="text/javascript" src="forge/task.js"></script>
+      <script type="text/javascript" src="forge/socket.js"></script>
+      <script type="text/javascript" src="forge/md5.js"></script>
+      <script type="text/javascript" src="forge/sha1.js"></script>
+      <script type="text/javascript" src="forge/hmac.js"></script>
+      <script type="text/javascript" src="forge/aes.js"></script>
+      <script type="text/javascript" src="forge/pem.js"></script>
+      <script type="text/javascript" src="forge/asn1.js"></script>
+      <script type="text/javascript" src="forge/jsbn.js"></script>
+      <script type="text/javascript" src="forge/prng.js"></script>
+      <script type="text/javascript" src="forge/random.js"></script>
+      <script type="text/javascript" src="forge/oids.js"></script>
+      <script type="text/javascript" src="forge/rsa.js"></script>
+      <script type="text/javascript" src="forge/pbe.js"></script>
+      <script type="text/javascript" src="forge/x509.js"></script>
+      <script type="text/javascript" src="forge/pki.js"></script>
+      <script type="text/javascript" src="forge/tls.js"></script>
+      <script type="text/javascript" src="forge/aesCipherSuites.js"></script>
+      <script type="text/javascript" src="forge/http.js"></script>
+      <script type="text/javascript" src="forge/xhr.js"></script>
+      <script type="text/javascript" src="loginDemo.js"></script>
+      
+      <link type="text/css" rel="stylesheet" media="all" href="login.css" />
+   </head>
+<body>
+
+<div id="header">
+   <h1>Web ID Login</h1>
+</div>
+
+<div id="content">
+   <p>Please select a Web ID to log in to <span id="domain">this website</span>.</p>
+
+   <div id="webids"></div>
+
+   <p>Do not select an identity if you do not trust this website.</p>
+
+   <!-- div used to hold the flash socket pool implementation -->
+   <div id="socketPool">
+      <p>Could not load the flash SocketPool.</p>
+   </div>
+</div>
+
+</body>
+</html>
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/loginDemo.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/loginDemo.js
new file mode 100644
index 0000000..859e1f0
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/loginDemo.js
@@ -0,0 +1,149 @@
+/**
+ * Forge Web ID Tests
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010 Digital Bazaar, Inc. All rights reserved.
+ */
+(function($)
+{
+   // load flash socket pool
+   window.forge.socketPool = {};
+   window.forge.socketPool.ready = function()
+   {
+      // init page
+      init($);
+   };
+   swfobject.embedSWF(
+      'forge/SocketPool.swf', 'socketPool', '0', '0', '9.0.0',
+      false, {}, {allowscriptaccess: 'always'}, {});
+})(jQuery);
+
+var init = function($)
+{
+   // logging category
+   var cat = 'forge.tests.loginDemo';
+   
+   // local alias
+   var forge = window.forge;
+   try
+   {
+      // get query variables
+      var query = forge.util.getQueryVariables();
+      var domain = query.domain || '';
+      var auth = query.auth || '';
+      var redirect = query.redirect || '';
+      var pport = query.pport || 843;
+      redirect = 'https://' + domain + '/' + redirect;
+      if(domain)
+      {
+         $('#domain').html('`' + domain + '`');
+      } 
+      
+      // for chosen webid
+      var chosen = null;
+      
+      // init forge xhr
+      forge.xhr.init({
+         flashId: 'socketPool',
+         msie: $.browser.msie,
+         url: 'https://' + domain,
+         policyPort: pport,
+         connections: 1,
+         caCerts: [],
+         verify: function(c, verified, depth, certs)
+         {
+            // don't care about cert verification for test
+            return true;
+         },
+         getCertificate: function(c)
+         {
+            forge.log.debug(cat, 'using cert', chosen.certificate);
+            return chosen.certificate;
+         },
+         getPrivateKey: function(c)
+         {
+            //forge.log.debug(cat, 'using private key', chosen.privateKey);
+            return chosen.privateKey;
+         }
+      });
+      
+      // get flash API
+      var flashApi = document.getElementById('socketPool');
+      
+      // get web ids collection
+      var webids = forge.util.getItem(
+         flashApi, 'forge.test.webid', 'webids');
+      webids = webids || {};
+      
+      var id = 0;
+      var list = $('<ul/>');
+      for(var key in webids)
+      {
+         (function(webid)
+         {
+            var cert = forge.pki.certificateFromPem(webid.certificate);
+            var item = $('<li/>');
+            var button = $('<button>');
+            button.attr('id', '' + (webid + id++));
+            button.html('Choose');
+            button.click(function()
+            {
+               button.attr('disabled', 'disabled');
+               
+               // set chosen webid
+               chosen = webid;
+               
+               // do webid call
+               $.ajax(
+               {
+                  type: 'GET',
+                  url: '/' + auth,
+                  success: function(data, textStatus, xhr)
+                  {
+                     if(data !== '')
+                     {
+                        forge.log.debug(cat, 'authentication completed');
+                        forge.log.debug(cat, data);
+                        window.name = data;
+                     }
+                     else
+                     {
+                        forge.log.debug(cat, 'authentication failed');
+                        window.name = '';
+                     }
+                     window.location = redirect;
+                  },
+                  error: function(xhr, textStatus, errorThrown)
+                  {
+                     forge.log.error(cat, 'authentication failed');
+                  },
+                  xhr: forge.xhr.create
+               });
+            });
+            item.append(button);
+            item.append(' ' + key + '<br/>');
+            
+            // display certificate attributes
+            var attr;
+            for(var n = 0; n < cert.subject.attributes.length; ++n)
+            {
+               attr = cert.subject.attributes[n];
+               item.append(attr.name + ': ' + attr.value + '<br/>');
+            }
+            
+            list.append(item);
+         })(webids[key]);
+      }
+      if(list.html() === '<ul/>')
+      {
+         list.append('None');
+      }
+      
+      $('#webids').append(list);
+   }
+   catch(ex)
+   {
+      forge.log.error(cat, ex);
+   }
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/nodejs-create-cert.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/nodejs-create-cert.js
new file mode 100644
index 0000000..d1666eb
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/nodejs-create-cert.js
@@ -0,0 +1,110 @@
+var forge = require('../js/forge');
+
+console.log('Generating 1024-bit key-pair...');
+var keys = forge.pki.rsa.generateKeyPair(1024);
+console.log('Key-pair created.');
+
+console.log('Creating self-signed certificate...');
+var cert = forge.pki.createCertificate();
+cert.publicKey = keys.publicKey;
+cert.serialNumber = '01';
+cert.validity.notBefore = new Date();
+cert.validity.notAfter = new Date();
+cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1);
+var attrs = [{
+  name: 'commonName',
+  value: 'example.org'
+}, {
+  name: 'countryName',
+  value: 'US'
+}, {
+  shortName: 'ST',
+  value: 'Virginia'
+}, {
+  name: 'localityName',
+  value: 'Blacksburg'
+}, {
+  name: 'organizationName',
+  value: 'Test'
+}, {
+  shortName: 'OU',
+  value: 'Test'
+}];
+cert.setSubject(attrs);
+cert.setIssuer(attrs);
+cert.setExtensions([{
+  name: 'basicConstraints',
+  cA: true/*,
+  pathLenConstraint: 4*/
+}, {
+  name: 'keyUsage',
+  keyCertSign: true,
+  digitalSignature: true,
+  nonRepudiation: true,
+  keyEncipherment: true,
+  dataEncipherment: true
+}, {
+  name: 'extKeyUsage',
+  serverAuth: true,
+  clientAuth: true,
+  codeSigning: true,
+  emailProtection: true,
+  timeStamping: true
+}, {
+  name: 'nsCertType',
+  client: true,
+  server: true,
+  email: true,
+  objsign: true,
+  sslCA: true,
+  emailCA: true,
+  objCA: true
+}, {
+  name: 'subjectAltName',
+  altNames: [{
+    type: 6, // URI
+    value: 'http://example.org/webid#me'
+  }, {
+    type: 7, // IP
+    ip: '127.0.0.1'
+  }]
+}, {
+  name: 'subjectKeyIdentifier'
+}]);
+// FIXME: add authorityKeyIdentifier extension
+
+// self-sign certificate
+cert.sign(keys.privateKey/*, forge.md.sha256.create()*/);
+console.log('Certificate created.');
+
+// PEM-format keys and cert
+var pem = {
+  privateKey: forge.pki.privateKeyToPem(keys.privateKey),
+  publicKey: forge.pki.publicKeyToPem(keys.publicKey),
+  certificate: forge.pki.certificateToPem(cert)
+};
+
+console.log('\nKey-Pair:');
+console.log(pem.privateKey);
+console.log(pem.publicKey);
+
+console.log('\nCertificate:');
+console.log(pem.certificate);
+
+// verify certificate
+var caStore = forge.pki.createCaStore();
+caStore.addCertificate(cert);
+try {
+  forge.pki.verifyCertificateChain(caStore, [cert],
+    function(vfd, depth, chain) {
+      if(vfd === true) {
+        console.log('SubjectKeyIdentifier verified: ' +
+          cert.verifySubjectKeyIdentifier());
+        console.log('Certificate verified.');
+      }
+      return true;
+  });
+} catch(ex) {
+  console.log('Certificate verification failure: ' +
+    JSON.stringify(ex, null, 2));
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/nodejs-create-csr.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/nodejs-create-csr.js
new file mode 100644
index 0000000..1cb335f
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/nodejs-create-csr.js
@@ -0,0 +1,66 @@
+var forge = require('../js/forge');
+
+console.log('Generating 1024-bit key-pair...');
+var keys = forge.pki.rsa.generateKeyPair(1024);
+console.log('Key-pair created.');
+
+console.log('Creating certification request (CSR) ...');
+var csr = forge.pki.createCertificationRequest();
+csr.publicKey = keys.publicKey;
+csr.setSubject([{
+  name: 'commonName',
+  value: 'example.org'
+}, {
+  name: 'countryName',
+  value: 'US'
+}, {
+  shortName: 'ST',
+  value: 'Virginia'
+}, {
+  name: 'localityName',
+  value: 'Blacksburg'
+}, {
+  name: 'organizationName',
+  value: 'Test'
+}, {
+  shortName: 'OU',
+  value: 'Test'
+}]);
+// add optional attributes
+csr.setAttributes([{
+  name: 'challengePassword',
+  value: 'password'
+}, {
+  name: 'unstructuredName',
+  value: 'My company'
+}]);
+
+// sign certification request
+csr.sign(keys.privateKey/*, forge.md.sha256.create()*/);
+console.log('Certification request (CSR) created.');
+
+// PEM-format keys and csr
+var pem = {
+  privateKey: forge.pki.privateKeyToPem(keys.privateKey),
+  publicKey: forge.pki.publicKeyToPem(keys.publicKey),
+  csr: forge.pki.certificationRequestToPem(csr)
+};
+
+console.log('\nKey-Pair:');
+console.log(pem.privateKey);
+console.log(pem.publicKey);
+
+console.log('\nCertification Request (CSR):');
+console.log(pem.csr);
+
+// verify certification request
+try {
+  if(csr.verify()) {
+    console.log('Certification request (CSR) verified.');
+  } else {
+    throw new Error('Signature not verified.');
+  }
+} catch(err) {
+  console.log('Certification request (CSR) verification failure: ' +
+    JSON.stringify(err, null, 2));
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/nodejs-create-pkcs12.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/nodejs-create-pkcs12.js
new file mode 100644
index 0000000..e52eefa
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/nodejs-create-pkcs12.js
@@ -0,0 +1,160 @@
+var forge = require('../js/forge');
+
+try {
+  // generate a keypair
+  console.log('Generating 1024-bit key-pair...');
+  var keys = forge.pki.rsa.generateKeyPair(1024);
+  console.log('Key-pair created.');
+
+  // create a certificate
+  console.log('Creating self-signed certificate...');
+  var cert = forge.pki.createCertificate();
+  cert.publicKey = keys.publicKey;
+  cert.serialNumber = '01';
+  cert.validity.notBefore = new Date();
+  cert.validity.notAfter = new Date();
+  cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1);
+  var attrs = [{
+    name: 'commonName',
+    value: 'example.org'
+  }, {
+    name: 'countryName',
+    value: 'US'
+  }, {
+    shortName: 'ST',
+    value: 'Virginia'
+  }, {
+    name: 'localityName',
+    value: 'Blacksburg'
+  }, {
+    name: 'organizationName',
+    value: 'Test'
+  }, {
+    shortName: 'OU',
+    value: 'Test'
+  }];
+  cert.setSubject(attrs);
+  cert.setIssuer(attrs);
+  cert.setExtensions([{
+    name: 'basicConstraints',
+    cA: true
+  }, {
+    name: 'keyUsage',
+    keyCertSign: true,
+    digitalSignature: true,
+    nonRepudiation: true,
+    keyEncipherment: true,
+    dataEncipherment: true
+  }, {
+    name: 'subjectAltName',
+    altNames: [{
+      type: 6, // URI
+      value: 'http://example.org/webid#me'
+    }]
+  }]);
+
+  // self-sign certificate
+  cert.sign(keys.privateKey);
+  console.log('Certificate created.');
+
+  // create PKCS12
+  console.log('\nCreating PKCS#12...');
+  var password = 'password';
+  var newPkcs12Asn1 = forge.pkcs12.toPkcs12Asn1(
+    keys.privateKey, [cert], password,
+    {generateLocalKeyId: true, friendlyName: 'test'});
+  var newPkcs12Der = forge.asn1.toDer(newPkcs12Asn1).getBytes();
+
+  console.log('\nBase64-encoded new PKCS#12:');
+  console.log(forge.util.encode64(newPkcs12Der));
+
+  // create CA store (w/own certificate in this example)
+  var caStore = forge.pki.createCaStore([cert]);
+
+  console.log('\nLoading new PKCS#12 to confirm...');
+  loadPkcs12(newPkcs12Der, password, caStore);
+} catch(ex) {
+  if(ex.stack) {
+    console.log(ex.stack);
+  } else {
+    console.log('Error', ex);
+  }
+}
+
+function loadPkcs12(pkcs12Der, password, caStore) {
+  var pkcs12Asn1 = forge.asn1.fromDer(pkcs12Der);
+  var pkcs12 = forge.pkcs12.pkcs12FromAsn1(pkcs12Asn1, false, password);
+
+  // load keypair and cert chain from safe content(s) and map to key ID
+  var map = {};
+  for(var sci = 0; sci < pkcs12.safeContents.length; ++sci) {
+    var safeContents = pkcs12.safeContents[sci];
+    console.log('safeContents ' + (sci + 1));
+
+    for(var sbi = 0; sbi < safeContents.safeBags.length; ++sbi) {
+      var safeBag = safeContents.safeBags[sbi];
+      console.log('safeBag.type: ' + safeBag.type);
+
+      var localKeyId = null;
+      if(safeBag.attributes.localKeyId) {
+        localKeyId = forge.util.bytesToHex(
+          safeBag.attributes.localKeyId[0]);
+        console.log('localKeyId: ' + localKeyId);
+        if(!(localKeyId in map)) {
+          map[localKeyId] = {
+            privateKey: null,
+            certChain: []
+          };
+        }
+      } else {
+        // no local key ID, skip bag
+        continue;
+      }
+
+      // this bag has a private key
+      if(safeBag.type === forge.pki.oids.pkcs8ShroudedKeyBag) {
+        console.log('found private key');
+        map[localKeyId].privateKey = safeBag.key;
+      } else if(safeBag.type === forge.pki.oids.certBag) {
+        // this bag has a certificate
+        console.log('found certificate');
+        map[localKeyId].certChain.push(safeBag.cert);
+      }
+    }
+  }
+
+  console.log('\nPKCS#12 Info:');
+
+  for(var localKeyId in map) {
+    var entry = map[localKeyId];
+    console.log('\nLocal Key ID: ' + localKeyId);
+    if(entry.privateKey) {
+      var privateKeyP12Pem = forge.pki.privateKeyToPem(entry.privateKey);
+      var encryptedPrivateKeyP12Pem = forge.pki.encryptRsaPrivateKey(
+        entry.privateKey, password);
+
+      console.log('\nPrivate Key:');
+      console.log(privateKeyP12Pem);
+      console.log('Encrypted Private Key (password: "' + password + '"):');
+      console.log(encryptedPrivateKeyP12Pem);
+    } else {
+      console.log('');
+    }
+    if(entry.certChain.length > 0) {
+      console.log('Certificate chain:');
+      var certChain = entry.certChain;
+      for(var i = 0; i < certChain.length; ++i) {
+        var certP12Pem = forge.pki.certificateToPem(certChain[i]);
+        console.log(certP12Pem);
+      }
+
+      var chainVerified = false;
+      try {
+        chainVerified = forge.pki.verifyCertificateChain(caStore, certChain);
+      } catch(ex) {
+        chainVerified = ex;
+      }
+      console.log('Certificate chain verified: ', chainVerified);
+    }
+  }
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/nodejs-imap.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/nodejs-imap.js
new file mode 100644
index 0000000..ba024ef
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/nodejs-imap.js
@@ -0,0 +1,46 @@
+var forge = require('../js/forge');
+var net = require('net');
+
+var socket = new net.Socket();
+
+var client = forge.tls.createConnection({
+  server: false,
+  verify: function(connection, verified, depth, certs) {
+    // skip verification for testing
+    return true;
+  },
+  connected: function(connection) {
+    console.log('[tls] connected');
+  },
+  tlsDataReady: function(connection) {
+    // encrypted data is ready to be sent to the server
+    var data = connection.tlsData.getBytes();
+    socket.write(data, 'binary');
+  },
+  dataReady: function(connection) {
+    // clear data from the server is ready
+    var data = connection.data.getBytes();
+    console.log('[tls] received from the server: ' + data);
+    client.close();
+  },
+  closed: function() {
+    console.log('[tls] disconnected');
+  },
+  error: function(connection, error) {
+    console.log('[tls] error', error);
+  }
+});
+
+socket.on('connect', function() {
+  console.log('[socket] connected');
+  client.handshake();
+});
+socket.on('data', function(data) {
+  client.process(data.toString('binary'));
+});
+socket.on('end', function() {
+  console.log('[socket] disconnected');
+});
+
+// connect to gmail's imap server
+socket.connect(993, 'imap.gmail.com');
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/nodejs-tls.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/nodejs-tls.js
new file mode 100644
index 0000000..5be6acd
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/nodejs-tls.js
@@ -0,0 +1,189 @@
+var forge = require('../js/forge');
+
+// function to create certificate
+var createCert = function(cn, data) {
+  console.log(
+    'Generating 512-bit key-pair and certificate for \"' + cn + '\".');
+  var keys = forge.pki.rsa.generateKeyPair(512);
+  console.log('key-pair created.');
+
+  var cert = forge.pki.createCertificate();
+  cert.serialNumber = '01';
+  cert.validity.notBefore = new Date();
+  cert.validity.notAfter = new Date();
+  cert.validity.notAfter.setFullYear(
+    cert.validity.notBefore.getFullYear() + 1);
+  var attrs = [{
+    name: 'commonName',
+    value: cn
+  }, {
+    name: 'countryName',
+    value: 'US'
+  }, {
+    shortName: 'ST',
+    value: 'Virginia'
+  }, {
+    name: 'localityName',
+    value: 'Blacksburg'
+  }, {
+    name: 'organizationName',
+    value: 'Test'
+  }, {
+    shortName: 'OU',
+    value: 'Test'
+  }];
+  cert.setSubject(attrs);
+  cert.setIssuer(attrs);
+  cert.setExtensions([{
+    name: 'basicConstraints',
+    cA: true
+  }, {
+    name: 'keyUsage',
+    keyCertSign: true,
+    digitalSignature: true,
+    nonRepudiation: true,
+    keyEncipherment: true,
+    dataEncipherment: true
+  }, {
+    name: 'subjectAltName',
+    altNames: [{
+      type: 6, // URI
+      value: 'http://myuri.com/webid#me'
+    }]
+  }]);
+  // FIXME: add subjectKeyIdentifier extension
+  // FIXME: add authorityKeyIdentifier extension
+  cert.publicKey = keys.publicKey;
+
+  // self-sign certificate
+  cert.sign(keys.privateKey);
+
+  // save data
+  data[cn] = {
+    cert: forge.pki.certificateToPem(cert),
+    privateKey: forge.pki.privateKeyToPem(keys.privateKey)
+  };
+
+  console.log('certificate created for \"' + cn + '\": \n' + data[cn].cert);
+};
+
+var end = {};
+var data = {};
+
+// create certificate for server and client
+createCert('server', data);
+createCert('client', data);
+
+var success = false;
+
+// create TLS client
+end.client = forge.tls.createConnection({
+  server: false,
+  caStore: [data.server.cert],
+  sessionCache: {},
+  // supported cipher suites in order of preference
+  cipherSuites: [
+    forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+    forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+  virtualHost: 'server',
+  verify: function(c, verified, depth, certs) {
+    console.log(
+      'TLS Client verifying certificate w/CN: \"' +
+      certs[0].subject.getField('CN').value +
+      '\", verified: ' + verified + '...');
+    return verified;
+  },
+  connected: function(c) {
+    console.log('Client connected...');
+
+    // send message to server
+    setTimeout(function() {
+      c.prepareHeartbeatRequest('heartbeat');
+      c.prepare('Hello Server');
+    }, 1);
+  },
+  getCertificate: function(c, hint) {
+    console.log('Client getting certificate ...');
+    return data.client.cert;
+  },
+  getPrivateKey: function(c, cert) {
+    return data.client.privateKey;
+  },
+  tlsDataReady: function(c) {
+    // send TLS data to server
+    end.server.process(c.tlsData.getBytes());
+  },
+  dataReady: function(c) {
+    var response = c.data.getBytes();
+    console.log('Client received \"' + response + '\"');
+    success = (response === 'Hello Client');
+    c.close();
+  },
+  heartbeatReceived: function(c, payload) {
+    console.log('Client received heartbeat: ' + payload.getBytes());
+  },
+  closed: function(c) {
+    console.log('Client disconnected.');
+    if(success) {
+      console.log('PASS');
+    } else {
+      console.log('FAIL');
+    }
+  },
+  error: function(c, error) {
+    console.log('Client error: ' + error.message);
+  }
+});
+
+// create TLS server
+end.server = forge.tls.createConnection({
+  server: true,
+  caStore: [data.client.cert],
+  sessionCache: {},
+  // supported cipher suites in order of preference
+  cipherSuites: [
+    forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+    forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+  connected: function(c) {
+    console.log('Server connected');
+    c.prepareHeartbeatRequest('heartbeat');
+  },
+  verifyClient: true,
+  verify: function(c, verified, depth, certs) {
+    console.log(
+      'Server verifying certificate w/CN: \"' +
+      certs[0].subject.getField('CN').value +
+      '\", verified: ' + verified + '...');
+    return verified;
+  },
+  getCertificate: function(c, hint) {
+    console.log('Server getting certificate for \"' + hint[0] + '\"...');
+    return data.server.cert;
+  },
+  getPrivateKey: function(c, cert) {
+    return data.server.privateKey;
+  },
+  tlsDataReady: function(c) {
+    // send TLS data to client
+    end.client.process(c.tlsData.getBytes());
+  },
+  dataReady: function(c) {
+    console.log('Server received \"' + c.data.getBytes() + '\"');
+
+    // send response
+    c.prepare('Hello Client');
+    c.close();
+  },
+  heartbeatReceived: function(c, payload) {
+    console.log('Server received heartbeat: ' + payload.getBytes());
+  },
+  closed: function(c) {
+    console.log('Server disconnected.');
+  },
+  error: function(c, error) {
+    console.log('Server error: ' + error.message);
+  }
+});
+
+console.log('created TLS client and server, doing handshake...');
+end.client.handshake();
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/nodejs-ws-webid.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/nodejs-ws-webid.js
new file mode 100644
index 0000000..fae0b82
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/nodejs-ws-webid.js
@@ -0,0 +1,491 @@
+var forge = require('../js/forge');
+var fs = require('fs');
+var http = require('http');
+//var rdf = require('./rdflib');
+var sys = require('sys');
+var urllib = require('url');
+var ws = require('./ws');
+
+// remove xmlns from input
+var normalizeNs = function(input, ns) {
+  var rval = null;
+
+  // primitive
+  if(typeof input === 'string' ||
+    typeof input === 'number' ||
+    typeof input === 'boolean') {
+    rval = input;
+  }
+  // array
+  else if(forge.util.isArray(input)) {
+    rval = [];
+    for(var i = 0; i < input.length; ++i) {
+      rval.push(normalizeNs(input[i], ns));
+    }
+  }
+  // object
+  else {
+    if('@' in input) {
+      // copy namespace map
+      var newNs = {};
+      for(var key in ns) {
+        newNs[key] = ns[key];
+      }
+      ns = newNs;
+
+      // update namespace map
+      for(var key in input['@']) {
+        if(key.indexOf('xmlns:') === 0) {
+          ns[key.substr(6)] = input['@'][key];
+        }
+      }
+    }
+
+    rval = {};
+    for(var key in input) {
+      if(key.indexOf('xmlns:') !== 0) {
+        var value = input[key];
+        var colon = key.indexOf(':');
+        if(colon !== -1) {
+          var prefix = key.substr(0, colon);
+          if(prefix in ns) {
+            key = ns[prefix] + key.substr(colon + 1);
+          }
+        }
+        rval[key] = normalizeNs(value, ns);
+      }
+    }
+  }
+
+  return rval;
+};
+
+// gets public key from WebID rdf
+var getPublicKey = function(data, uri, callback) {
+  // FIXME: use RDF library to simplify code below
+  //var kb = new rdf.RDFParser(rdf.IndexedFormula(), uri).loadBuf(data);
+  //var CERT = rdf.Namespace('http://www.w3.org/ns/auth/cert#');
+  //var RSA  = rdf.Namespace('http://www.w3.org/ns/auth/rsa#');
+  var RDF = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
+  var CERT = 'http://www.w3.org/ns/auth/cert#';
+  var RSA  = 'http://www.w3.org/ns/auth/rsa#';
+  var desc = RDF + 'Description';
+  var about = RDF + 'about';
+  var type = RDF + 'type';
+  var resource = RDF + 'resource';
+  var publicKey = RSA + 'RSAPublicKey';
+  var modulus = RSA + 'modulus';
+  var exponent = RSA + 'public_exponent';
+  var identity = CERT + 'identity';
+  var hex = CERT + 'hex';
+  var decimal = CERT + 'decimal';
+
+  // gets a resource identifer from a node
+  var getResource = function(node, key) {
+    var rval = null;
+
+    // special case 'about'
+    if(key === about) {
+      if('@' in node && about in node['@']) {
+        rval = node['@'][about];
+      }
+    }
+    // any other resource
+    else if(
+       key in node &&
+       typeof node[key] === 'object' && !forge.util.isArray(node[key]) &&
+       '@' in node[key] && resource in node[key]['@']) {
+      rval = node[key]['@'][resource];
+    }
+
+    return rval;
+  };
+
+  // parse XML
+  uri = urllib.parse(uri);
+  var xml2js = require('./xml2js');
+  var parser = new xml2js.Parser();
+  parser.addListener('end', function(result) {
+    // normalize namespaces
+    result = normalizeNs(result, {});
+
+    // find grab all public keys whose identity matches hash from uri
+    var keys = [];
+    if(desc in result) {
+      // normalize RDF descriptions to array
+      if(!forge.util.isArray(result[desc])) {
+        desc = [result[desc]];
+      }
+      else {
+        desc = result[desc];
+      }
+
+      // collect properties for all resources
+      var graph = {};
+      for(var i = 0; i < desc.length; ++i) {
+        var node = desc[i];
+        var res = {};
+        for(var key in node) {
+          var obj = getResource(node, key);
+          res[key] = (obj === null) ? node[key] : obj;
+        }
+        graph[getResource(node, about) || ''] = res;
+      }
+
+      // for every public key w/identity that matches the uri hash
+      // save the public key modulus and exponent
+      for(var r in graph) {
+        var props = graph[r];
+        if(identity in props &&
+          type in props &&
+          props[type] === publicKey &&
+          props[identity] === uri.hash &&
+          modulus in props &&
+          exponent in props &&
+          props[modulus] in graph &&
+          props[exponent] in graph &&
+          hex in graph[props[modulus]] &&
+          decimal in graph[props[exponent]]) {
+          keys.push({
+            modulus: graph[props[modulus]][hex],
+            exponent: graph[props[exponent]][decimal]
+          });
+        }
+      }
+    }
+
+    sys.log('Public keys from RDF: ' + JSON.stringify(keys));
+    callback(keys);
+  });
+  parser.parseString(data);
+};
+
+// compares two public keys for equality
+var comparePublicKeys = function(key1, key2) {
+  return key1.modulus === key2.modulus && key1.exponent === key2.exponent;
+};
+
+// gets the RDF data from a URL
+var fetchUrl = function(url, callback, redirects) {
+  // allow 3 redirects by default
+  if(typeof(redirects) === 'undefined') {
+    redirects = 3;
+  }
+
+  sys.log('Fetching URL: \"' + url + '\"');
+
+  // parse URL
+  url = forge.util.parseUrl(url);
+  var client = http.createClient(
+    url.port, url.fullHost, url.scheme === 'https');
+  var request = client.request('GET', url.path, {
+    'Host': url.host,
+    'Accept': 'application/rdf+xml'
+  });
+  request.addListener('response', function(response) {
+    var body = '';
+
+    // error, return empty body
+    if(response.statusCode >= 400) {
+       callback(body);
+    }
+    // follow redirect
+    else if(response.statusCode === 302) {
+      if(redirects > 0) {
+        // follow redirect
+        fetchUrl(response.headers.location, callback, --redirects);
+      }
+      else {
+        // return empty body
+        callback(body);
+      }
+    }
+    // handle data
+    else {
+      response.setEncoding('utf8');
+      response.addListener('data', function(chunk) {
+        body += chunk;
+      });
+      response.addListener('end', function() {
+        callback(body);
+      });
+    }
+  });
+  request.end();
+};
+
+// does WebID authentication
+var authenticateWebId = function(c, state) {
+  var auth = false;
+
+  // get client-certificate
+  var cert = c.peerCertificate;
+
+  // get public key from certificate
+  var publicKey = {
+    modulus: cert.publicKey.n.toString(16).toLowerCase(),
+    exponent: cert.publicKey.e.toString(10)
+  };
+
+  sys.log(
+    'Server verifying certificate w/CN: \"' +
+    cert.subject.getField('CN').value + '\"\n' +
+    'Public Key: ' + JSON.stringify(publicKey));
+
+  // build queue of subject alternative names to authenticate with
+  var altNames = [];
+  var ext = cert.getExtension({name: 'subjectAltName'});
+  if(ext !== null && ext.altNames) {
+    for(var i = 0; i < ext.altNames.length; ++i) {
+      var altName = ext.altNames[i];
+      if(altName.type === 6) {
+        altNames.push(altName.value);
+      }
+    }
+  }
+
+  // create authentication processor
+  var authNext = function() {
+    if(!auth) {
+      // no more alt names, auth failed
+      if(altNames.length === 0) {
+        sys.log('WebID authentication FAILED.');
+        c.prepare(JSON.stringify({
+          success: false,
+          error: 'Not Authenticated'
+        }));
+        c.close();
+      }
+      // try next alt name
+      else {
+        // fetch URL
+        var url = altNames.shift();
+        fetchUrl(url, function(body) {
+          // get public key
+          getPublicKey(body, url, function(keys) {
+            // compare public keys from RDF until one matches
+            for(var i = 0; !auth && i < keys.length; ++i) {
+              auth = comparePublicKeys(keys[i], publicKey);
+            }
+            if(auth) {
+              // send authenticated notice to client
+              sys.log('WebID authentication PASSED.');
+              state.authenticated = true;
+              c.prepare(JSON.stringify({
+                success: true,
+                cert: forge.pki.certificateToPem(cert),
+                webID: url,
+                rdf: forge.util.encode64(body)
+              }));
+            }
+            else {
+              // try next alt name
+              authNext();
+            }
+          });
+        });
+      }
+    }
+  };
+
+  // do auth
+  authNext();
+};
+
+// creates credentials (private key + certificate)
+var createCredentials = function(cn, credentials) {
+  sys.log('Generating 512-bit key-pair and certificate for \"' + cn + '\".');
+  var keys = forge.pki.rsa.generateKeyPair(512);
+  sys.log('key-pair created.');
+
+  var cert = forge.pki.createCertificate();
+  cert.serialNumber = '01';
+  cert.validity.notBefore = new Date();
+  cert.validity.notAfter = new Date();
+  cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1);
+  var attrs = [{
+    name: 'commonName',
+    value: cn
+  }, {
+    name: 'countryName',
+    value: 'US'
+  }, {
+    shortName: 'ST',
+    value: 'Virginia'
+  }, {
+    name: 'localityName',
+    value: 'Blacksburg'
+  }, {
+    name: 'organizationName',
+    value: 'Test'
+  }, {
+    shortName: 'OU',
+    value: 'Test'
+  }];
+  cert.setSubject(attrs);
+  cert.setIssuer(attrs);
+  cert.setExtensions([{
+    name: 'basicConstraints',
+    cA: true
+  }, {
+    name: 'keyUsage',
+    keyCertSign: true,
+    digitalSignature: true,
+    nonRepudiation: true,
+    keyEncipherment: true,
+    dataEncipherment: true
+  }, {
+    name: 'subjectAltName',
+    altNames: [{
+      type: 6, // URI
+      value: 'http://myuri.com/webid#me'
+    }]
+  }]);
+  // FIXME: add subjectKeyIdentifier extension
+  // FIXME: add authorityKeyIdentifier extension
+  cert.publicKey = keys.publicKey;
+
+  // self-sign certificate
+  cert.sign(keys.privateKey);
+
+  // save credentials
+  credentials.key = forge.pki.privateKeyToPem(keys.privateKey);
+  credentials.cert = forge.pki.certificateToPem(cert);
+
+  sys.log('Certificate created for \"' + cn + '\": \n' + credentials.cert);
+};
+
+// initialize credentials
+var credentials = {
+  key: null,
+  cert: null
+};
+
+// read private key from file
+var readPrivateKey = function(filename) {
+  credentials.key = fs.readFileSync(filename);
+  // try to parse from PEM as test
+  forge.pki.privateKeyFromPem(credentials.key);
+};
+
+// read certificate from file
+var readCertificate = function(filename) {
+  credentials.cert = fs.readFileSync(filename);
+  // try to parse from PEM as test
+  forge.pki.certificateFromPem(credentials.cert);
+};
+
+// parse command line options
+var opts = require('opts');
+var options = [
+{ short       : 'v'
+, long        : 'version'
+, description : 'Show version and exit'
+, callback    : function() { console.log('v1.0'); process.exit(1); }
+},
+{ short       : 'p'
+, long        : 'port'
+, description : 'The port to listen for WebSocket connections on'
+, value       : true
+},
+{ long        : 'key'
+, description : 'The server private key file to use in PEM format'
+, value       : true
+, callback    : readPrivateKey
+},
+{ long        : 'cert'
+, description : 'The server certificate file to use in PEM format'
+, value       : true
+, callback    : readCertificate
+}
+];
+opts.parse(options, true);
+
+// create credentials for server
+if(credentials.key === null || credentials.cert === null) {
+  createCredentials('server', credentials);
+}
+
+// function to create TLS server connection
+var createTls = function(websocket) {
+  var state = {
+    authenticated: false
+  };
+  return forge.tls.createConnection({
+    server: true,
+    caStore: [],
+    sessionCache: {},
+    // supported cipher suites in order of preference
+    cipherSuites: [
+       forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+       forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+    connected: function(c) {
+      sys.log('Server connected');
+
+      // do WebID authentication
+      try {
+        authenticateWebId(c, state);
+      }
+      catch(ex) {
+        c.close();
+      }
+    },
+    verifyClient: true,
+    verify: function(c, verified, depth, certs) {
+      // accept certs w/unknown-CA (48)
+      if(verified === 48) {
+        verified = true;
+      }
+      return verified;
+    },
+    getCertificate: function(c, hint) {
+       sys.log('Server using certificate for \"' + hint[0] + '\"');
+       return credentials.cert;
+    },
+    getPrivateKey: function(c, cert) {
+       return credentials.key;
+    },
+    tlsDataReady: function(c) {
+       // send base64-encoded TLS data over websocket
+       websocket.write(forge.util.encode64(c.tlsData.getBytes()));
+    },
+    dataReady: function(c) {
+      // ignore any data until connection is authenticated
+      if(state.authenticated) {
+        sys.log('Server received \"' + c.data.getBytes() + '\"');
+      }
+    },
+    closed: function(c) {
+      sys.log('Server disconnected');
+      websocket.end();
+    },
+    error: function(c, error) {
+      sys.log('Server error: ' + error.message);
+    }
+  });
+};
+
+// create websocket server
+var port = opts.get('port') || 8080;
+ws.createServer(function(websocket) {
+  // create TLS server connection
+  var tls = createTls(websocket);
+
+  websocket.addListener('connect', function(resource) {
+    sys.log('WebSocket connected: ' + resource);
+
+    // close connection after 30 second timeout
+    setTimeout(websocket.end, 30 * 1000);
+  });
+
+  websocket.addListener('data', function(data) {
+    // base64-decode data and process it
+    tls.process(forge.util.decode64(data));
+  });
+
+  websocket.addListener('close', function() {
+    sys.log('WebSocket closed');
+  });
+}).listen(port);
+
+sys.log('WebSocket WebID server running on port ' + port);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/nodejs-ws.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/nodejs-ws.js
new file mode 100644
index 0000000..164962d
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/nodejs-ws.js
@@ -0,0 +1,166 @@
+var sys = require('sys');
+var ws = require('./ws');
+var forge = require('../js/forge');
+
+// function to create certificate
+var createCert = function(cn, data)
+{
+   sys.puts(
+      'Generating 512-bit key-pair and certificate for \"' + cn + '\".');
+   var keys = forge.pki.rsa.generateKeyPair(512);
+   sys.puts('key-pair created.');
+
+   var cert = forge.pki.createCertificate();
+   cert.serialNumber = '01';
+   cert.validity.notBefore = new Date();
+   cert.validity.notAfter = new Date();
+   cert.validity.notAfter.setFullYear(
+      cert.validity.notBefore.getFullYear() + 1);
+   var attrs = [{
+      name: 'commonName',
+      value: cn
+   }, {
+      name: 'countryName',
+      value: 'US'
+   }, {
+      shortName: 'ST',
+      value: 'Virginia'
+   }, {
+      name: 'localityName',
+      value: 'Blacksburg'
+   }, {
+      name: 'organizationName',
+      value: 'Test'
+   }, {
+      shortName: 'OU',
+      value: 'Test'
+   }];
+   cert.setSubject(attrs);
+   cert.setIssuer(attrs);
+   cert.setExtensions([{
+      name: 'basicConstraints',
+      cA: true
+   }, {
+      name: 'keyUsage',
+      keyCertSign: true,
+      digitalSignature: true,
+      nonRepudiation: true,
+      keyEncipherment: true,
+      dataEncipherment: true
+   }, {
+      name: 'subjectAltName',
+      altNames: [{
+         type: 6, // URI
+         value: 'http://myuri.com/webid#me'
+      }]
+   }]);
+   // FIXME: add subjectKeyIdentifier extension
+   // FIXME: add authorityKeyIdentifier extension
+   cert.publicKey = keys.publicKey;
+   
+   // self-sign certificate
+   cert.sign(keys.privateKey);
+   
+   // save data
+   data[cn] = {
+      cert: forge.pki.certificateToPem(cert),
+      privateKey: forge.pki.privateKeyToPem(keys.privateKey)
+   };
+   
+   sys.puts('certificate created for \"' + cn + '\": \n' + data[cn].cert);
+};
+
+var data = {};
+
+// create certificate for server
+createCert('server', data);
+
+// function to create TLS server connection
+var createTls = function(websocket)
+{
+   return forge.tls.createConnection(
+   {
+      server: true,
+      caStore: [],
+      sessionCache: {},
+      // supported cipher suites in order of preference
+      cipherSuites: [
+         forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+         forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+      connected: function(c)
+      {
+         sys.puts('Server connected');
+      },
+      verifyClient: true,
+      verify: function(c, verified, depth, certs)
+      {
+         sys.puts(
+            'Server verifying certificate w/CN: \"' +
+            certs[0].subject.getField('CN').value +
+            '\", verified: ' + verified + '...');
+         
+         // accept any certificate (could actually do WebID authorization from
+         // here within the protocol)
+         return true;
+      },
+      getCertificate: function(c, hint)
+      {
+         sys.puts('Server getting certificate for \"' + hint[0] + '\"...');
+         return data.server.cert;
+      },
+      getPrivateKey: function(c, cert)
+      {
+         return data.server.privateKey;
+      },
+      tlsDataReady: function(c)
+      {
+         // send base64-encoded TLS data over websocket
+         websocket.write(forge.util.encode64(c.tlsData.getBytes()));
+      },
+      dataReady: function(c)
+      {
+         sys.puts('Server received \"' + c.data.getBytes() + '\"');
+         
+         // send response
+         c.prepare('Hello Client');
+      },
+      closed: function(c)
+      {
+         sys.puts('Server disconnected.');
+         websocket.end();
+      },
+      error: function(c, error)
+      {
+         sys.puts('Server error: ' + error.message);
+      }
+   });
+};
+
+// create websocket server
+var port = 8080;
+ws.createServer(function(websocket)
+{
+   // create TLS server connection
+   var tls = createTls(websocket);
+   
+   websocket.addListener('connect', function(resource)
+   {
+      sys.puts('connected: ' + resource);
+      
+      // close connection after 10 seconds
+      setTimeout(websocket.end, 10 * 1000);
+   });
+   
+   websocket.addListener('data', function(data)
+   {
+      // base64-decode data and process it
+      tls.process(forge.util.decode64(data));
+   });
+   
+   websocket.addListener('close', function()
+   {
+      sys.puts('closed');
+   });
+}).listen(port);
+
+sys.puts('server running on port ' + port);
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/performance.html b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/performance.html
new file mode 100644
index 0000000..9acbcc5
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/performance.html
@@ -0,0 +1,550 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+   <head>
+      <title>Forge Performance Tests</title>
+      <link type="text/css" rel="stylesheet" media="all" href="screen.css" />
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
+      <script type="text/javascript" src="forge/debug.js"></script>
+      <script type="text/javascript" src="forge/util.js"></script>
+      <script type="text/javascript" src="forge/log.js"></script>
+      <script type="text/javascript" src="forge/task.js"></script>
+      <script type="text/javascript" src="forge/md5.js"></script>
+      <script type="text/javascript" src="forge/sha1.js"></script>
+      <script type="text/javascript" src="forge/sha256.js"></script>
+      <script type="text/javascript" src="forge/hmac.js"></script>
+      <script type="text/javascript" src="forge/aes.js"></script>
+      <script type="text/javascript" src="forge/pem.js"></script>
+      <script type="text/javascript" src="forge/asn1.js"></script>
+      <script type="text/javascript" src="forge/jsbn.js"></script>
+      <script type="text/javascript" src="forge/prng.js"></script>
+      <script type="text/javascript" src="forge/random.js"></script>
+      <script type="text/javascript" src="forge/oids.js"></script>
+      <script type="text/javascript" src="forge/rsa.js"></script>
+      <script type="text/javascript" src="forge/pbe.js"></script>
+      <script type="text/javascript" src="forge/x509.js"></script>
+      <script type="text/javascript" src="forge/pki.js"></script>
+      <script type="text/javascript" src="forge/tls.js"></script>
+      <script type="text/javascript" src="forge/aesCipherSuites.js"></script>
+
+      <script type="text/javascript">
+      //<![CDATA[
+      // logging category
+      var cat = 'forge.tests.performance';
+
+      var test_random = function()
+      {
+         forge.log.debug(cat, 'painting canvas...');
+         setTimeout(function()
+         {
+	         var canvas = document.getElementById("canvas");
+	         var ctx = canvas.getContext("2d");
+	         var imgData = ctx.createImageData(canvas.width, canvas.height);
+
+	         // generate random bytes
+	         var bytes = forge.random.getBytes(canvas.width * canvas.height * 3);
+	         var n = 0;
+	         for(var x = 0; x < imgData.width; x++)
+	         {
+	            for(var y = 0; y < imgData.height; y++)
+	            {
+	               // index of the pixel in the array
+	               var idx = (x + y * imgData.width) * 4;
+
+	               // set values
+	               imgData.data[idx + 0] = bytes.charCodeAt(n++); // Red
+	               imgData.data[idx + 1] = bytes.charCodeAt(n++); // Green
+	               imgData.data[idx + 2] = bytes.charCodeAt(n++); // Blue
+	               imgData.data[idx + 3] = 255;                   // Alpha
+	            }
+	         }
+
+	         ctx.putImageData(imgData, 0, 0);
+	         forge.log.debug(cat, 'done');
+         }, 0);
+      };
+
+      var canvas_clear = function()
+      {
+         var canvas = document.getElementById("canvas");
+         var ctx = canvas.getContext("2d");
+         ctx.clearRect(0, 0, canvas.width, canvas.height);
+      };
+
+      var test_buffer_fill = function()
+      {
+         forge.log.debug(cat,
+            'buffer fill optimized vs. slow running for about 5 seconds...');
+
+         setTimeout(function()
+         {
+	         // run slow fill for 2.5 seconds
+	         var st, et;
+	         var b = '';
+	         var passed = 0;
+	         while(passed < 2500)
+	         {
+	            st = +new Date();
+	            b += 'b';
+	            et = +new Date();
+	            passed += (et - st);
+	         }
+
+	         // do optimized fill
+	         var buf = forge.util.createBuffer();
+	         st = +new Date();
+	         buf.fillWithByte('b'.charCodeAt(0), b.length);
+	         et = +new Date();
+
+	         forge.log.debug(cat, 'fill times', (et - st) + ' < ' + passed);
+         });
+      };
+
+      var test_buffer_xor = function()
+      {
+         forge.log.debug(cat,
+            'buffer xor optimized vs. slow running for about 5 seconds...');
+
+         setTimeout(function()
+         {
+	         // run slow xor for 2.5 seconds
+	         var st, et;
+	         var output = forge.util.createBuffer();
+	         var passed = 0;
+	         while(passed < 2500)
+	         {
+	            st = +new Date();
+	            output.putByte(0x01 ^ 0x02);
+	            et = +new Date();
+	            passed += (et - st);
+	         }
+
+	         // do optimized xor
+	         var count = output.length();
+	         var b1 = forge.util.createBuffer();
+	         b1.fillWithByte(0x01, count);
+	         var b2 = forge.util.createBuffer();
+	         b2.fillWithByte(0x02, count);
+
+	         st = +new Date();
+	         output = forge.util.xorBytes(b1.getBytes(), b2.getBytes(), count);
+	         et = +new Date();
+
+	         forge.log.debug(cat, 'xor times', (et - st) + ' < ' + passed);
+         });
+      };
+
+      // TODO: follow the same format as the hash tests
+      var test_base64_encode = function()
+      {
+         forge.log.debug(cat, 'base64 encode running for about 5 seconds...');
+
+         setTimeout(function()
+         {
+	         // get starting time to make test run for only 5 seconds
+	         var start = +new Date();
+
+	         // build data to encode
+	         var str = '';
+	         for(var i = 0; i < 100; i++)
+	         {
+	            str += '00010203050607080A0B0C0D0F1011121415161719';
+	         }
+
+	         // keep encoding for 5 seconds, keep track of total and count
+	         var total = 0, count = 0, st, et;
+	         var passed = 0;
+	         while(passed < 5000)
+	         {
+	            st = +new Date();
+	            forge.util.encode64(str);
+	            et = +new Date();
+	            ++count;
+	            total += (et - st);
+	            passed = +new Date() - start;
+	         }
+
+	         total /= 1000;
+	         var kb = 4200/1024;
+	         forge.log.debug(cat,
+	            'encode:', (count*kb/total) + ' KiB/s');
+         });
+      };
+
+      // TODO: follow the same format as the hash tests
+      var test_base64_decode = function()
+      {
+         forge.log.debug(cat, 'base64 decode running for about 5 seconds...');
+
+         setTimeout(function()
+         {
+	         // get starting time to make test run for only 5 seconds
+	         var start = +new Date();
+
+	         // build data to decode
+	         var str = '';
+	         for(var i = 0; i < 100; i++)
+	         {
+	            str +=
+		            'MDAwMTAyMDMwNTA2MDcwODBBMEIwQzBEMEYxMDExMTIxNDE1MTYxNzE5';
+	         }
+
+	         // keep encoding for 5 seconds, keep track of total and count
+	         var total = 0, count = 0, st, et;
+	         var passed = 0;
+	         while(passed < 5000)
+	         {
+	            st = +new Date();
+	            forge.util.decode64(str);
+	            et = +new Date();
+	            ++count;
+	            total += (et - st);
+	            passed = +new Date() - start;
+	         }
+
+	         total /= 1000;
+	         var kb = 5600/1024;
+	         forge.log.debug(cat,
+	            'decode:', (count*kb/total) + ' KiB/s');
+         });
+      };
+
+      var test_md5 = function()
+      {
+         // create input data
+         var input = ['0123456789abcdef', '', '', '', ''];
+         for(var i = 0; i < 4; ++i)
+         {
+            input[1] += input[0];
+         }
+         for(var i = 0; i < 4; ++i)
+         {
+            input[2] += input[1];
+         }
+         for(var i = 0; i < 4; ++i)
+         {
+            input[3] += input[2];
+         }
+         for(var i = 0; i < 8; ++i)
+         {
+            input[4] += input[3];
+         }
+
+         var md = forge.md.md5.create();
+
+         forge.log.debug(cat, 'md5 times in 1000s of bytes/sec processed:');
+
+         var st, et;
+         var output =
+            ['  16 bytes: ',
+             '  64 bytes: ',
+             ' 256 bytes: ',
+             '1024 bytes: ',
+             '8192 bytes: '];
+         var s = [16, 64, 256, 1024, 8192];
+         var t = [0, 0, 0, 0, 0];
+         for(var n = 0; n < 5; ++n)
+         {
+            var f = function(n)
+            {
+               setTimeout(function()
+               {
+                  // run for 2 seconds each
+                  var count = 0;
+                  while(t[n] < 2000)
+                  {
+                     md.start();
+                     st = +new Date();
+                     md.update(input[n]);
+                     md.digest();
+                     et = +new Date();
+                     t[n] = t[n] + (et - st);
+                     ++count;
+                  }
+                  t[n] /= 1000;
+                  forge.log.debug(cat,
+                     output[n], (count*s[n]/t[n]/1000) + 'k/sec');
+               }, 0);
+            }(n);
+         }
+      };
+
+      var test_sha1 = function()
+      {
+         // create input data
+         var input = ['0123456789abcdef', '', '', '', ''];
+         for(var i = 0; i < 4; ++i)
+         {
+            input[1] += input[0];
+         }
+         for(var i = 0; i < 4; ++i)
+         {
+            input[2] += input[1];
+         }
+         for(var i = 0; i < 4; ++i)
+         {
+            input[3] += input[2];
+         }
+         for(var i = 0; i < 8; ++i)
+         {
+            input[4] += input[3];
+         }
+
+         var md = forge.md.sha1.create();
+
+         forge.log.debug(cat, 'sha-1 times in 1000s of bytes/sec processed:');
+
+         var st, et;
+         var output =
+            ['  16 bytes: ',
+             '  64 bytes: ',
+             ' 256 bytes: ',
+             '1024 bytes: ',
+             '8192 bytes: '];
+         var s = [16, 64, 256, 1024, 8192];
+         var t = [0, 0, 0, 0, 0];
+         for(var n = 0; n < 5; ++n)
+         {
+            var f = function(n)
+            {
+               setTimeout(function()
+               {
+                  // run for 2 seconds each
+                  var count = 0;
+                  while(t[n] < 2000)
+                  {
+                     md.start();
+                     st = +new Date();
+                     md.update(input[n]);
+                     md.digest();
+                     et = +new Date();
+                     t[n] = t[n] + (et - st);
+                     ++count;
+                  }
+                  t[n] /= 1000;
+                  forge.log.debug(cat,
+                     output[n], (count*s[n]/t[n]/1000) + 'k/sec');
+               }, 0);
+            }(n);
+         }
+      };
+
+      var test_sha256 = function()
+      {
+         // create input data
+         var input = ['0123456789abcdef', '', '', '', ''];
+         for(var i = 0; i < 4; ++i)
+         {
+            input[1] += input[0];
+         }
+         for(var i = 0; i < 4; ++i)
+         {
+            input[2] += input[1];
+         }
+         for(var i = 0; i < 4; ++i)
+         {
+            input[3] += input[2];
+         }
+         for(var i = 0; i < 8; ++i)
+         {
+            input[4] += input[3];
+         }
+
+         var md = forge.md.sha256.create();
+
+         forge.log.debug(cat, 'sha-256 times in 1000s of bytes/sec processed:');
+
+         var st, et;
+         var output =
+            ['  16 bytes: ',
+             '  64 bytes: ',
+             ' 256 bytes: ',
+             '1024 bytes: ',
+             '8192 bytes: '];
+         var s = [16, 64, 256, 1024, 8192];
+         var t = [0, 0, 0, 0, 0];
+         for(var n = 0; n < 5; ++n)
+         {
+            var f = function(n)
+            {
+               setTimeout(function()
+               {
+                  // run for 2 seconds each
+                  var count = 0;
+                  while(t[n] < 2000)
+                  {
+                     md.start();
+                     st = +new Date();
+                     md.update(input[n]);
+                     md.digest();
+                     et = +new Date();
+                     t[n] = t[n] + (et - st);
+                     ++count;
+                  }
+                  t[n] /= 1000;
+                  forge.log.debug(cat,
+                     output[n], (count*s[n]/t[n]/1000) + 'k/sec');
+               }, 0);
+            }(n);
+         }
+      };
+
+      // TODO: follow the same format as the hash tests
+      var aes_128 = function()
+      {
+         forge.log.debug(cat, 'running AES-128 for 5 seconds...');
+
+         var block = [];
+         block.push(0x00112233);
+         block.push(0x44556677);
+         block.push(0x8899aabb);
+         block.push(0xccddeeff);
+
+         var key = [];
+         key.push(0x00010203);
+         key.push(0x04050607);
+         key.push(0x08090a0b);
+         key.push(0x0c0d0e0f);
+
+         setTimeout(function()
+         {
+            // run for 5 seconds
+            var start = +new Date();
+	         var now;
+	         var totalEncrypt = 0;
+	         var totalDecrypt = 0;
+	         var count = 0;
+            var passed = 0;
+	         while(passed < 5000)
+	         {
+	            var output = [];
+	            var w = forge.aes._expandKey(key, false);
+	            now = +new Date();
+	            forge.aes._updateBlock(w, block, output, false);
+	            totalEncrypt += +new Date() - now;
+
+	            block = output;
+	            output = [];
+	            w = forge.aes._expandKey(key, true);
+	            now = +new Date();
+	            forge.aes._updateBlock(w, block, output, true);
+	            totalDecrypt += +new Date() - now;
+
+               ++count;
+	            passed = +new Date() - start;
+	         }
+
+	         count = count * 16 / 1000;
+	         totalEncrypt /= 1000;
+	         totalDecrypt /= 1000;
+	         forge.log.debug(cat, 'times in 1000s of bytes/sec processed.');
+	         forge.log.debug(cat,
+	   	      'encrypt: ' + (count*16 / totalEncrypt) + ' k/sec');
+	         forge.log.debug(cat,
+	   	      'decrypt: ' + (count*16 / totalDecrypt) + ' k/sec');
+         }, 0);
+      };
+
+      // TODO: follow the same format as the hash tests
+      var aes_128_cbc = function()
+      {
+         forge.log.debug(cat, 'running AES-128 CBC for 5 seconds...');
+
+         var key = forge.random.getBytes(16);
+         var iv = forge.random.getBytes(16);
+         var plain = forge.random.getBytes(16);
+
+         setTimeout(function()
+         {
+            // run for 5 seconds
+            var start = +new Date();
+	         var now;
+	         var totalEncrypt = 0;
+	         var totalDecrypt = 0;
+	         var cipher;
+	         var count = 0;
+	         var passed = 0;
+	         while(passed < 5000)
+	         {
+               var input = forge.util.createBuffer(plain);
+
+               // encrypt, only measuring update() and finish()
+               cipher = forge.aes.startEncrypting(key, iv);
+               now = +new Date();
+               cipher.update(input);
+               cipher.finish();
+               totalEncrypt += +new Date() - now;
+
+               // decrypt, only measuring update() and finish()
+               var ct = cipher.output;
+               cipher = forge.aes.startDecrypting(key, iv);
+               now = +new Date();
+               cipher.update(ct);
+               cipher.finish();
+               totalDecrypt += +new Date() - now;
+
+               ++count;
+               passed = +new Date() - start;
+	         }
+
+	         // 32 bytes encrypted because of 16 bytes of padding
+	         count = count * 32 / 1000;
+            totalEncrypt /= 1000;
+            totalDecrypt /= 1000;
+            forge.log.debug(cat, 'times in 1000s of bytes/sec processed.');
+            forge.log.debug(cat,
+               'encrypt: ' + (count / totalEncrypt) + ' k/sec');
+            forge.log.debug(cat,
+               'decrypt: ' + (count / totalDecrypt) + ' k/sec');
+         }, 0);
+      };
+
+      //]]>
+      </script>
+   </head>
+   <body>
+      <div class="nav"><a href="index.html">Forge Tests</a> / Performance</div>
+
+      <div class="header">
+         <h1>Performance Tests</h1>
+      </div>
+
+      <div class="content">
+
+         <fieldset class="section">
+            <ul>
+               <li>Use the controls below to run Forge performance tests.</li>
+               <li>You currently need a JavaScript console to view the output.</li>
+            </ul>
+         </fieldset>
+
+         <fieldset class="section">
+            <legend>Tests</legend>
+
+         <div id="random_controls">
+            <button id="random" onclick="javascript:return test_random();">paint random</button>
+            <button id="clear" onclick="javascript:return canvas_clear();">clear</button>
+         </div>
+         <canvas id="canvas" width="100" height="100"></canvas>
+         <div id="buffer_controls">
+            <button id="buffer_fill" onclick="javascript:return test_buffer_fill();">buffer fill</button>
+            <button id="buffer_xor" onclick="javascript:return test_buffer_xor();">buffer xor</button>
+         </div>
+         <div id="base64_controls">
+            <button id="base64_encode" onclick="javascript:return test_base64_encode();">base64 encode</button>
+            <button id="base64_decode" onclick="javascript:return test_base64_decode();">base64 decode</button>
+         </div>
+         <div id="hash_controls">
+            <button id="md5" onclick="javascript:return test_md5();">md5</button>
+            <button id="sha1" onclick="javascript:return test_sha1();">sha1</button>
+            <button id="sha256" onclick="javascript:return test_sha256();">sha256</button>
+         </div>
+         <div id="aes_controls">
+            <button id="aes_128" onclick="javascript:return aes_128();">aes-128</button>
+            <button id="aes_128_cbc" onclick="javascript:return aes_128_cbc();">aes-128 cbc</button>
+         </div>
+         </fieldset>
+      </div>
+   </body>
+</html>
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/policyserver.py b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/policyserver.py
new file mode 100755
index 0000000..bda8afe
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/policyserver.py
@@ -0,0 +1,112 @@
+#!/usr/bin/env python
+
+"""
+Flash Socket Policy Server.
+
+- Starts Flash socket policy file server.
+- Defaults to port 843.
+- NOTE: Most operating systems require administrative privileges to use
+  ports under 1024.
+
+  $ ./policyserver.py [options]
+"""
+
+"""
+Also consider Adobe's solutions:
+http://www.adobe.com/devnet/flashplayer/articles/socket_policy_files.html
+"""
+
+from multiprocessing import Process
+from optparse import OptionParser
+import SocketServer
+import logging
+
+# Set address reuse for all TCPServers
+SocketServer.TCPServer.allow_reuse_address = True
+
+# Static socket policy file string.
+# NOTE: This format is very strict. Edit with care.
+socket_policy_file = """\
+<?xml version="1.0"?>\
+<!DOCTYPE cross-domain-policy\
+ SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">\
+<cross-domain-policy>\
+<allow-access-from domain="*" to-ports="*"/>\
+</cross-domain-policy>\0"""
+
+
+class PolicyHandler(SocketServer.BaseRequestHandler):
+    """
+    The RequestHandler class for our server.
+
+    Returns a policy file when requested.
+    """
+
+    def handle(self):
+        """Send policy string if proper request string is received."""
+        # get some data
+        # TODO: make this more robust (while loop, etc)
+        self.data = self.request.recv(1024).rstrip('\0')
+        logging.debug("%s wrote:%s" % (self.client_address[0], repr(self.data)))
+        # if policy file request, send the file.
+        if self.data == "<policy-file-request/>":
+            logging.info("Policy server request from %s." % (self.client_address[0]))
+            self.request.send(socket_policy_file)
+        else:
+            logging.info("Policy server received junk from %s: \"%s\"" % \
+                    (self.client_address[0], repr(self.data)))
+
+
+class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
+    def serve_forever(self):
+        """Handle one request at a time until shutdown or keyboard interrupt."""
+        try:
+           SocketServer.BaseServer.serve_forever(self)
+        except KeyboardInterrupt:
+           return
+
+
+def main():
+    """Run socket policy file servers."""
+    usage = "Usage: %prog [options]"
+    parser = OptionParser(usage=usage)
+    parser.add_option("", "--host", dest="host", metavar="HOST",
+            default="localhost", help="bind to HOST")
+    parser.add_option("-p", "--port", dest="port", metavar="PORT",
+            default=843, type="int", help="serve on PORT")
+    parser.add_option("-d", "--debug", dest="debug", action="store_true",
+            default=False, help="debugging output")
+    parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
+            default=False, help="verbose output")
+    (options, args) = parser.parse_args()
+
+    # setup logging
+    if options.debug:
+        lvl = logging.DEBUG
+    elif options.verbose:
+        lvl = logging.INFO
+    else:
+        lvl = logging.WARNING
+    logging.basicConfig(level=lvl, format="%(levelname)-8s %(message)s")
+
+    # log basic info
+    logging.info("Flash Socket Policy Server. Use ctrl-c to exit.")
+    
+    # create policy server
+    logging.info("Socket policy serving on %s:%d." % (options.host, options.port))
+    policyd = ThreadedTCPServer((options.host, options.port), PolicyHandler)
+
+    # start server
+    policy_p = Process(target=policyd.serve_forever)
+    policy_p.start()
+
+    while policy_p.is_alive():
+        try:
+            policy_p.join(1)
+        except KeyboardInterrupt:
+            logging.info("Stopping test server...")
+
+
+if __name__ == "__main__":
+    main()
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/result.txt b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/result.txt
new file mode 100644
index 0000000..7cb007c
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/result.txt
@@ -0,0 +1 @@
+expected result
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/screen.css b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/screen.css
new file mode 100644
index 0000000..365a39f
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/screen.css
@@ -0,0 +1,61 @@
+/* CSS for Forge tests */
+body {
+   background: white;
+   color: black;
+   margin: 0;
+   padding: 0;
+}
+.warning {
+   border: thin solid red;
+   background: #7FF;
+}
+div.nav {
+   background: white;
+   border-bottom: thin solid black;
+   padding: .5em;
+   padding-top: .2em;
+   padding-bottom: .2em;
+}
+div.header {
+   border-bottom: thin solid black;
+   padding: .5em;
+}
+div.content {
+   padding: .5em;
+   background: #DDD;
+}
+div.footer {
+   border-top: thin solid black;
+   font-size: 80%;
+   padding: .5em;
+}
+canvas {
+   background: black;
+}
+table, td, th {
+   border: thin solid black;
+   border-collapse: collapse;
+}
+td, th {
+   padding: .2em;
+}
+span.sp-state-on {
+   font-weight: bold;
+}
+table#feedback {
+   width: 100%;
+}
+table#feedback th, table#feedback td {
+   width: 33%;
+}
+fieldset.section {
+   margin: .5em;
+   border: 2px solid black;
+   background: #FFF;
+}
+fieldset.section legend {
+   padding: .2em;
+   border: 2px solid black;
+   background: #DDF;
+   font-weight: bold;
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/server.crt b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/server.crt
new file mode 100644
index 0000000..6952426
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/server.crt
@@ -0,0 +1,26 @@
+-----BEGIN CERTIFICATE-----
+MIIEaDCCA1CgAwIBAgIJAJuj0AjEWncuMA0GCSqGSIb3DQEBBQUAMH8xCzAJBgNV
+BAYTAlVTMREwDwYDVQQIEwhWaXJnaW5pYTETMBEGA1UEBxMKQmxhY2tzYnVyZzEd
+MBsGA1UEChMURGlnaXRhbCBCYXphYXIsIEluYy4xGjAYBgNVBAsTEUZvcmdlIFRl
+c3QgU2VydmVyMQ0wCwYDVQQDEwR0ZXN0MB4XDTEwMDcxMzE3MjAzN1oXDTMwMDcw
+ODE3MjAzN1owfzELMAkGA1UEBhMCVVMxETAPBgNVBAgTCFZpcmdpbmlhMRMwEQYD
+VQQHEwpCbGFja3NidXJnMR0wGwYDVQQKExREaWdpdGFsIEJhemFhciwgSW5jLjEa
+MBgGA1UECxMRRm9yZ2UgVGVzdCBTZXJ2ZXIxDTALBgNVBAMTBHRlc3QwggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCm/FobjqK8CVP/Xbnpyhf1tpoyaiFf
+ShUOmlWqL5rLe0Q0dDR/Zur+sLMUv/1T4wOfFkjjxvZ0Sk5NIjK3Wy2UA41a+M3J
+RTbCFrg4ujsovFaD4CDmV7Rek0qJB3m5Gp7hgu5vfL/v+WrwxnQObNq+IrTMSA15
+cO4LzNIPj9K1LN2dB+ucT7xTQFHAfvLLgLlCLiberoabF4rEhgTMTbmMtFVKSt+P
+xgQIYPnhw1WuAvE9hFesRQFdfARLqIZk92FeHkgtHv9BAunktJemcidbowTCTBaM
+/njcgi1Tei/LFkph/FCVyGER0pekJNHX626bAQSLo/srsWfmcll9rK6bAgMBAAGj
+geYwgeMwHQYDVR0OBBYEFCau5k6jxezjULlLuo/liswJlBF8MIGzBgNVHSMEgasw
+gaiAFCau5k6jxezjULlLuo/liswJlBF8oYGEpIGBMH8xCzAJBgNVBAYTAlVTMREw
+DwYDVQQIEwhWaXJnaW5pYTETMBEGA1UEBxMKQmxhY2tzYnVyZzEdMBsGA1UEChMU
+RGlnaXRhbCBCYXphYXIsIEluYy4xGjAYBgNVBAsTEUZvcmdlIFRlc3QgU2VydmVy
+MQ0wCwYDVQQDEwR0ZXN0ggkAm6PQCMRady4wDAYDVR0TBAUwAwEB/zANBgkqhkiG
+9w0BAQUFAAOCAQEAnP/2mzFWaoGx6+KAfY8pcgnF48IoyKPx5cAQyzpMo+uRwrln
+INcDGwNx6p6rkjFbK27TME9ReCk+xQuVGaKOtqErKECXWDtD+0M35noyaOwWIFu2
+7gPZ0uGJ1n9ZMe/S9yZmmusaIrc66rX4o+fslUlH0g3SrH7yf83M8aOC2pEyCsG0
+mNNfwSFWfmu+1GMRHXJQ/qT8qBX8ZPhzRY2BAS6vr+eh3gwXR6yXLA8Xm1+e+iDU
+gGTQoYkixDIL2nhvd4AFFlE977BiE+0sMS1eJKUUbQ36MLAWb5oOZKHrphEvqMKA
+eGDO3qoDqB5TkZC3x38DXBDvAZ01d9s0fvveag==
+-----END CERTIFICATE-----
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/server.key b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/server.key
new file mode 100644
index 0000000..4024097
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/server.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEApvxaG46ivAlT/1256coX9baaMmohX0oVDppVqi+ay3tENHQ0
+f2bq/rCzFL/9U+MDnxZI48b2dEpOTSIyt1stlAONWvjNyUU2wha4OLo7KLxWg+Ag
+5le0XpNKiQd5uRqe4YLub3y/7/lq8MZ0DmzaviK0zEgNeXDuC8zSD4/StSzdnQfr
+nE+8U0BRwH7yy4C5Qi4m3q6GmxeKxIYEzE25jLRVSkrfj8YECGD54cNVrgLxPYRX
+rEUBXXwES6iGZPdhXh5ILR7/QQLp5LSXpnInW6MEwkwWjP543IItU3ovyxZKYfxQ
+lchhEdKXpCTR1+tumwEEi6P7K7Fn5nJZfayumwIDAQABAoIBAFGPbEuNbXq+a6KN
+GuNP7Ef9em8pW0d5nbNWOoU3XzoH6RZds86ObDUeBTobVBaHCRvI/K0UXwgJyxjt
+nSvlguuKmJ5Ya9rkzYwbILvEamTJKNCcxjT7nYOcGYm4dwGsOPIYy3D006LYhh04
+MTNig6zessQcZUhtmjd1QRyMuPP4PaWVO79ic01jxZR/ip6tN/FjCYclPRi/FRi8
+bQVuGEVLW2qHgQbDKPpcXFyFjIqt7c9dL97/3eeIDp+SgdQ6bPi80J7v9p2MRyBP
+7OPhX8ZDsAiZr4G4N0EbEzmWWpVSjAI3Nlmk8SLT4lu42KKyoZLtuKPjEOVI3/TR
+0ktsc/ECgYEA27AHLnsv07Yqe7Z2bmv+GP8PKlwrPSHwqU/3Z5/1V590N+jo15N4
+lb7gvBUwwvXIxQQQVYJqRimqNQYVfT6+xRtQdtdqInxv2hvhc/cKPEiIHNpRh7OI
+w7I59yNMlCnqLeRBiCOmd7ruCWoMGw+VLhsyArwUTXuqUK2oYN9qWm8CgYEAwpZF
+XNm8xCFa+YeqP+WQzwK/0yUxHmYZs7ofh9ZIgHtqwHNKO/OA8+nGsZBaIl5xiyT4
+uZ/qZi2EkYzOmx0iSathiWQpSyc9kB+nOTdMHyhBOj8CgbTRRXIMjDQ6bz78Z09F
+Nxenhwk2gSVr3oB2FG/BWc1rlmVlmGJIIX3QtJUCgYBfLhLOdpywExqw4srI6Iz8
+c3U0mx44rD3CfVzpTopTXkhR+Nz4mXIDHuHrWxr3PNmxUiNpiMlWgLK3ql0hGFA6
+wazI8GeRbWxgiPfS8FNE7v/Z0FTGgGhesRcgFfEVuFs3as9hlmCHOzvqZEG+b6/o
+e+vc93OsZknSDosG/YTsjQKBgHrb7HGinLftI4a3rLvpU1QRNVK4gdnit0muM6hN
+mLtesVlPschGh935ddW5Ad//Z4tmTZDOMm5PQQuxLuXrMDH5fn0D+7qSzSEJi0jp
+7Csj/IMtM4T3yMYjK17+vwJsb2s/NsGBMupk28ARA5mZ3HQs15S+ybZM0Se0rjxP
+Nw49AoGBAKrLTOtZta0DSGt7tURwQK1mERuGM8ZZdXjvIVTJIIknohD2u3/T+O4+
+ekFTUd6GQKOFd/hZ52t4wcRs7c7KE1Xen7vRHc8c6c3TkF9ldpLmK2AWT8WifQO6
+9Fjx2Wf8HM+CbrokQYH/OHSV9Xft8BRTOPHGUJlp1UsYikSwp4fW
+-----END RSA PRIVATE KEY-----
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/server.py b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/server.py
new file mode 100755
index 0000000..b5a5f06
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/server.py
@@ -0,0 +1,184 @@
+#!/usr/bin/env python
+
+"""
+SSL server for Forge tests.
+
+- The server changes to the directory of the server script.
+- SSL uses "server.key" and "server.crt".
+- Sever performs basic static file serving.
+- Starts Flash cross domain policy file server.
+- Defaults to HTTP/HTTPS port 19400.
+- Defaults to Flash socket policy port 19945.
+
+  $ ./server.py [options]
+
+If you just need a simple HTTP server, also consider:
+  $ python -m SimpleHTTPServer 19400
+"""
+
+from multiprocessing import Process
+from optparse import OptionParser
+import SimpleHTTPServer
+import SocketServer
+import os
+import sys
+import time
+
+# Try to import special Forge SSL module with session cache support
+# Using the built directory directly
+python_version = "python" + sys.version[:3]
+sys.path.insert(0, os.path.join(
+    os.path.dirname(os.path.realpath(__file__)),
+    "..", "dist", "forge_ssl", "lib", python_version, "site-packages"))
+try:
+    from forge import ssl
+    have_ssl_sessions = True
+    have_ssl = True
+except ImportError:
+    have_ssl_sessions = False
+    try:
+        import ssl
+        have_ssl = True
+    except ImportError:
+        have_ssl = False
+
+# Set address reuse for all TCPServers
+SocketServer.TCPServer.allow_reuse_address = True
+
+# The policy file
+# NOTE: This format is very strict. Edit with care.
+policy_file = """\
+<?xml version="1.0"?>\
+<!DOCTYPE cross-domain-policy\
+ SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">\
+<cross-domain-policy>\
+<allow-access-from domain="*" to-ports="*"/>\
+</cross-domain-policy>\0"""
+
+
+class PolicyHandler(SocketServer.BaseRequestHandler):
+    """
+    The RequestHandler class for our server.
+
+    Returns a policy file when requested.
+    """
+
+    def handle(self):
+        # get some data
+        # TODO: make this more robust (while loop, etc)
+        self.data = self.request.recv(1024).rstrip('\0')
+        #print "%s wrote:" % self.client_address[0]
+        #print repr(self.data)
+        # if policy file request, send the file.
+        if self.data == "<policy-file-request/>":
+            print "Policy server request from %s." % (self.client_address[0])
+            self.request.send(policy_file)
+        else:
+            print "Policy server received junk from %s: \"%s\"" % \
+                    (self.client_address[0], repr(self.data))
+
+
+def create_policy_server(options):
+    """Start a policy server"""
+    print "Policy serving from %d." % (options.policy_port)
+    policyd = SocketServer.TCPServer((options.host, options.policy_port), PolicyHandler)
+    return policyd
+
+
+class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
+    pass
+
+
+def create_http_server(options, script_dir):
+    """Start a static file server"""
+    # use UTF-8 encoding for javascript files
+    m = SimpleHTTPServer.SimpleHTTPRequestHandler.extensions_map
+    m['.js'] = 'application/javascript;charset=UTF-8'
+
+    Handler = SimpleHTTPServer.SimpleHTTPRequestHandler
+#    httpd = SocketServer.TCPServer((options.host, options.port), Handler)
+    httpd = ThreadedTCPServer((options.host, options.port), Handler)
+    if options.tls:
+        if not have_ssl:
+            raise Exception("SSL support from Python 2.7 or later is required.")
+
+        # setup session args if we session support
+        sess_args = {}
+        if have_ssl_sessions:
+            sess_args["sess_id_ctx"] = "forgetest"
+        else:
+            print "Forge SSL with session cache not available, using standard version."
+
+        httpd.socket = ssl.wrap_socket(
+            httpd.socket,
+            keyfile="server.key",
+            certfile="server.crt",
+            server_side=True,
+            **sess_args)
+
+    print "Serving from \"%s\"." % (script_dir)
+    print "%s://%s:%d/" % \
+            (("https" if options.tls else "http"),
+            httpd.server_address[0],
+            options.port)
+    return httpd
+
+
+def serve_forever(server):
+    """Serve until shutdown or keyboard interrupt."""
+    try:
+       server.serve_forever()
+    except KeyboardInterrupt:
+       return
+
+
+def main():
+    """Start static file and policy servers"""
+    usage = "Usage: %prog [options]"
+    parser = OptionParser(usage=usage)
+    parser.add_option("", "--host", dest="host", metavar="HOST",
+            default="localhost", help="bind to HOST")
+    parser.add_option("-p", "--port", dest="port", type="int",
+            help="serve on PORT", metavar="PORT", default=19400)
+    parser.add_option("-P", "--policy-port", dest="policy_port", type="int",
+            help="serve policy file on PORT", metavar="PORT", default=19945)
+    parser.add_option("", "--tls", dest="tls", action="store_true",
+            help="serve HTTPS", default=False)
+    (options, args) = parser.parse_args()
+
+    # Change to script dir so SSL and test files are in current dir.
+    script_dir = os.path.dirname(os.path.realpath(__file__))
+    os.chdir(script_dir)
+
+    print "Forge Test Server. Use ctrl-c to exit."
+    
+    # create policy and http servers
+    httpd = create_http_server(options, script_dir)
+    policyd = create_policy_server(options)
+
+    # start servers
+    server_p = Process(target=serve_forever, args=(httpd,))
+    policy_p = Process(target=serve_forever, args=(policyd,))
+    server_p.start()
+    policy_p.start()
+
+    processes = [server_p, policy_p]
+
+    while len(processes) > 0:
+        try:
+            for p in processes:
+               if p.is_alive():
+                  p.join(1)
+               else:
+                  processes.remove(p)
+        except KeyboardInterrupt:
+            print "\nStopping test server..."
+            # processes each receive interrupt
+            # so no need to shutdown
+            #httpd.shutdown();
+            #policyd.shutdown();
+
+
+if __name__ == "__main__":
+    main()
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/socketPool.html b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/socketPool.html
new file mode 100644
index 0000000..33a095f
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/socketPool.html
@@ -0,0 +1,299 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+   <head>
+      <link type="text/css" rel="stylesheet" media="all" href="screen.css" />
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js"></script>
+      <script type="text/javascript" src="forge/util.js"></script>
+      <script type="text/javascript" src="forge/socket.js"></script>
+      <script type="text/javascript" src="forge/log.js"></script>
+      
+      <script type="text/javascript">
+      //<![CDATA[
+      // logging category
+      var cat = 'forge.tests.socketPool';
+
+      // feedback types
+      var ERROR = '';
+      var USER = 'user';
+      var SOCKETPOOL = 'socketpool'
+      var SOCKET = 'socket'
+
+      function addFeedback(type, text)
+      {
+          var row = $('<tr/>')
+             .append($('<td/>').html('&nbsp;'))
+             .append($('<td/>').html('&nbsp;'))
+             .append($('<td/>').html('&nbsp;'));
+          switch(type)
+          {
+             case USER:
+                row.children().eq(0).html(text);
+                break;
+             case SOCKETPOOL:
+                row.children().eq(1).html(text);
+                break;
+             case SOCKET:
+                row.children().eq(2).html(text);
+                break;
+             default:
+                var msg = 'ERROR: bad feedback type:' + type;
+                row.children().eq(1).html(msg);
+                forge.log.error(cat, msg);
+          }
+          $('#feedback').append(row);
+          forge.log.debug(cat, '[' + type + ']', text);
+      };
+
+      function _setState(stateSel)
+      {
+         $('.sp-control').filter(stateSel).removeAttr('disabled');
+         $('.sp-control').filter(':not(' + stateSel + ')').attr('disabled', 'disabled');
+         $('.sp-state').filter(stateSel).addClass('sp-state-on');
+         $('.sp-state').filter(':not(' + stateSel + ')').removeClass('sp-state-on');
+      };
+
+      function setState(state)
+      {
+         switch(state)
+         {
+            case 'ready':
+               _setState('.sp-ready');
+               break;
+            case 'initialized':
+               _setState('.sp-ready,.sp-initialized');
+               break;
+            case 'created':
+               _setState('.sp-ready,.sp-initialized,.sp-created');
+               break;
+            case 'connected':
+               _setState('.sp-ready,.sp-initialized,.sp-created,.sp-connected');
+               break;
+            default:
+               addFeedback(ERROR, 'ERROR: bad state: ' + state);
+         };
+      };
+
+      window.forge.socketPool =
+      {
+         ready: function()
+         {
+            $(document).ready(function() {
+               addFeedback(SOCKETPOOL, 'Ready');
+               setState('ready');
+            });
+         }
+      };
+      
+      swfobject.embedSWF(
+         "forge/SocketPool.swf", "socketPool", "0", "0", "9.0.0",
+         false, {}, {allowscriptaccess: 'always'}, {});
+      
+      // local alias
+      var net = window.forge.net;
+
+      // socket to use
+      var socket;
+
+      $(document).ready(function() {
+         addFeedback(USER, 'Ready');
+         $('#host').val(window.location.hostname);
+         $('#port').val(window.location.port);
+      });
+
+      function sp_init()
+      {
+         net.createSocketPool({
+            flashId: 'socketPool',
+            policyPort: parseInt($('#policyPort').val()),
+            msie: false
+         });
+         addFeedback(SOCKETPOOL, 'Initialized');
+         setState('initialized');
+         return false;
+      };
+      
+      function sp_cleanup()
+      {
+         net.destroySocketPool({flashId: 'socketPool'});
+         addFeedback(SOCKETPOOL, 'Cleaned up');
+         setState('ready');
+         return false;
+      };
+
+      function sp_create()
+      {
+         socket = net.createSocket({
+            flashId: 'socketPool',
+            connected: function(e)
+            {
+               forge.log.debug(cat, 'connected', e);
+               addFeedback(SOCKET, 'Connected');
+            },
+            closed: function(e)
+            {
+               forge.log.debug(cat, 'closed', e);
+               addFeedback(SOCKET, 'Closed. Type: ' + e.type);
+               setState('created');
+            },
+            data: function(e)
+            {
+               forge.log.debug(cat, 'data received', e);
+               forge.log.debug(cat, 'bytes available', socket.bytesAvailable());
+               addFeedback(SOCKET,
+                  'Data available: ' +
+                  socket.bytesAvailable() +' bytes');
+               var bytes = socket.receive(e.bytesAvailable);
+               forge.log.debug(cat, 'bytes received', bytes);
+            },
+            error: function(e)
+            {
+               forge.log.error(cat, 'error', e);
+               addFeedback(SOCKET, 'Error: ' + e);
+            }
+         });
+         addFeedback(SOCKETPOOL, 'Created socket');
+         setState('created');
+         return false;
+      };
+      
+      function sp_destroy()
+      {
+         socket.destroy();
+         addFeedback(USER, 'Request socket destroy');
+         setState('initialized');
+         return false;
+      };
+
+      function sp_connect()
+      {
+         socket.connect({
+            host: $('#host').val(),
+            port: parseInt($('#port').val()),
+            policyPort: parseInt($('#policyPort').val())
+         });
+         addFeedback(USER, 'Request socket connect');
+         setState('connected');
+      };
+
+      function sp_isConnected()
+      {
+         var connected = socket.isConnected();
+         addFeedback(USER, 'Socket connected check: ' + connected);
+      };
+
+      function sp_send()
+      {
+         socket.send('GET ' + $('#path').val() + ' HTTP/1.0\r\n\r\n');
+         addFeedback(USER, 'Send GET request');
+      };
+
+      function sp_close()
+      {
+         socket.close();
+         addFeedback(USER, 'Requst socket close');
+         setState('created');
+      };
+      //]]>
+      </script>
+   </head>
+   <body>
+      <div class="nav"><a href="index.html">Forge Tests</a> / SocketPool</div>
+
+      <div class="header">
+         <h1>SocketPool Test</h1>
+      </div>
+
+      <div class="content">
+         <!-- div used to hold the flash socket pool implemenation -->
+         <div id="socketPool">
+            <p>Could not load the flash SocketPool.</p>
+         </div>
+   
+         <fieldset class="section">
+            <ul>
+               <li>This page tests a single socket connection to the local test server.</li>
+               <li>Note that the selected server must serve a Flash cross-domain policy file on the selected policy port.</li>
+               <li>Additional output available in the JavaScript console.</li>
+            </ul>
+         </fieldset>
+   
+         <fieldset class="section">
+            <legend>State</legend>
+            <p>State:
+               <span class="sp-state sp-ready">Ready</span> &raquo;
+               <span class="sp-state sp-initialized">Initialized</span> &raquo;
+               <span class="sp-state sp-created">Created</span> &raquo;
+               <span class="sp-state sp-connected">Connected</span>
+            </p>
+         </fieldset>
+   
+         <fieldset class="section">
+            <legend>Controls</legend>
+            <div id="controls">
+               <table>
+                  <tr><th>Action</th><th>Description</th></tr>
+                  <tr>
+                     <td><button id="init" disabled="disabled" class="sp-control sp-ready"
+                        onclick="javascript:return sp_init();">init</button></td>
+                     <td>Initialize SocketPool system.</td>
+                  </tr>
+                  <tr>
+                     <td><button id="cleanup" disabled="disabled" class="sp-control sp-initialized"
+                        onclick="javascript:return sp_cleanup();">cleanup</button></td>
+                     <td>Cleanup SocketPool system.</td>
+                  </tr>
+                  <tr>
+                     <td><button id="create" disabled="disabled" class="sp-control sp-initialized"
+                        onclick="javascript:return sp_create();">create socket</button></td>
+                     <td>Create a new test socket.</td>
+                  </tr>
+                  <tr>
+                     <td><button id="destroy" disabled="disabled" class="sp-control sp-created"
+                        onclick="javascript:return sp_destroy();">destroy socket</button></td>
+                     <td>Destroy the test socket.</td>
+                  </tr>
+                  <tr>
+                     <td><button id="connect" disabled="disabled" class="sp-control sp-created"
+                        onclick="javascript:return sp_connect();">connect</button></td>
+                     <td>Connect the socket to
+                        host: <input id="host"/>
+                        port: <input id="port"/>
+                        policy port: <input id="policyPort" value="19945"/>
+                     </td>
+                  </tr>
+                  <tr>
+                     <td><button id="isConnected" disabled="disabled" class="sp-control sp-created"
+                        onclick="javascript:return sp_isConnected();">is connected</button></td>
+                     <td>Check if socket is connected.</td>
+                  </tr>
+                  <tr>
+                     <td><button id="send" disabled="disabled" class="sp-control sp-connected"
+                        onclick="javascript:return sp_send();">send</button></td>
+                     <td>Send a GET request for
+                        path: <input id="path" value="/"/>
+                     </td>
+                  </tr>
+                  <tr>
+                     <td><button id="close" disabled="disabled" class="sp-control sp-connected"
+                        onclick="javascript:return sp_close();">close</button></td>
+                     <td>Close the test socket.</td>
+                  </tr>
+               </table>
+            </div>
+         </fieldset>
+   
+         <fieldset class="section">
+            <legend>Feedback</legend>
+            <table id="feedback">
+               <tr>
+                  <th>User</th>
+                  <th>SocketPool</th>
+                  <th>Socket</th>
+               </tr>
+            </table>
+         </fieldset>
+      </div>
+   </body>
+</html>
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/tasks.html b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/tasks.html
new file mode 100644
index 0000000..eba0173
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/tasks.html
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+   <head>
+      <title>Forge Tasks Test</title>
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
+      <script type="text/javascript" src="forge/debug.js"></script>
+      <script type="text/javascript" src="forge/util.js"></script>
+      <script type="text/javascript" src="forge/log.js"></script>
+      <script type="text/javascript" src="forge/task.js"></script>
+      <script type="text/javascript" src="tasks.js"></script>
+      <link type="text/css" rel="stylesheet" media="all" href="screen.css" />
+      <style type="text/css">
+         .ready { color: inherit; background: inherit; }
+         .testing { color: black; background: yellow; }
+         .pass{ color: white; background: green; }
+         .fail{ color: white; background: red; }
+      </style>
+   </head>
+<body>
+<div class="nav"><a href="index.html">Forge Tests</a> / Tasks</div>
+
+<div class="header">
+   <h1>Task Tests</h1>
+</div>
+
+<div class="content">
+
+<fieldset class="section">
+<legend>Control</legend>
+   <button id="start">Start</button>
+   <button id="reset">Reset</button>
+   <br/>
+   <input id="scroll" type="checkbox" checked="checked" />Scroll Tests
+</fieldset>
+
+<fieldset class="section">
+<legend>Progress</legend>
+Status: <span id="status">?</span><br/>
+Pass: <span id="pass">?</span>/<span id="total">?</span><br/>
+Fail: <span id="fail">?</span>
+</fieldset>
+
+<fieldset class="section">
+<legend>Tests</legend>
+<div id="tests"></div>
+</fieldset>
+
+</div>
+
+</body>
+</html>
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/tasks.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/tasks.js
new file mode 100644
index 0000000..dd3ffde
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/tasks.js
@@ -0,0 +1,378 @@
+/**
+ * Forge Tasks Test
+ *
+ * @author David I. Lehn <dlehn@digitalbazaar.com>
+ *
+ * Copyright (c) 2009-2010 Digital Bazaar, Inc. All rights reserved.
+ */
+jQuery(function($)
+{
+   var cat = 'forge.tests.tasks';
+
+   var tests = [];
+   var passed = 0;
+   var failed = 0;
+   
+   var init = function() {
+      passed = failed = 0;
+      $('.ready,.testing,.pass,.fail')
+         .removeClass('ready testing pass fail');
+      $('#status')
+         .text('Ready.')
+         .addClass('ready');
+      $('#total').text(tests.length);
+      $('#pass').text(passed);
+      $('#fail').text(failed);
+      $('.expect').empty();
+      $('.result').empty();
+      $('.time').empty();
+      $('#start').removeAttr('disabled');
+   };
+
+   var start = function()
+   {
+      $('#start').attr('disabled', 'disabled');
+      // meta! use tasks to run the task tests
+      forge.task.start({
+         type: 'test',
+         run: function(task) {
+            task.next('starting', function(task) {
+               forge.log.debug(cat, 'start');
+               $('#status')
+                  .text('Testing...')
+                  .addClass('testing')
+                  .removeClass('idle');
+            });
+            $.each(tests, function(i, test) {
+               task.next('test', function(task) {
+                  var title = $('li:first', test.container);
+                  if($('#scroll:checked').length === 1)
+                  {
+                     $('html,body').animate({scrollTop: title.offset().top});
+                  }
+                  title.addClass('testing');
+                  test.run(task, test);
+               });
+               task.next('test', function(task) {
+                  $('li:first', test.container).removeClass('testing');
+               });
+            });
+            task.next('success', function(task) {
+               forge.log.debug(cat, 'done');
+               if(failed === 0) {
+                  $('#status')
+                     .text('PASS')
+                     .addClass('pass')
+                     .removeClass('testing');
+               } else {
+                  // FIXME: should just be hitting failure() below
+                  $('#status')
+                     .text('FAIL')
+                     .addClass('fail')
+                     .removeClass('testing');
+               }
+            });
+         },
+         failure: function() {
+            $('#status')
+               .text('FAIL')
+               .addClass('fail')
+               .removeClass('testing');
+         }
+      });
+   };
+
+   $('#start').click(function() {
+      start();
+   });
+   
+   $('#reset').click(function() {
+      init();
+   });
+   
+   var addTest = function(name, run)
+   {
+      var container = $('<ul><li>Test ' + name + '</li><ul/></ul>');
+      var expect = $('<li>Expect: <span class="expect"/></li>');
+      var result = $('<li>Result: <span class="result"/></li>');
+      var time = $('<li>Time: <span class="time"/></li>');
+      $('ul', container).append(expect).append(result).append(time);
+      $('#tests').append(container);
+      var test = {
+         container: container,
+         startTime: null,
+         run: function(task, test) {
+            test.startTime = new Date();
+            run(task, test);
+         },
+         expect: $('span', expect),
+         result: $('span', result),
+         check: function() {
+            var e = test.expect.text();
+            var r = test.result.text();
+            (e == r) ? test.pass() : test.fail();
+         },
+         pass: function() {
+            passed += 1;
+            $('#pass').text(passed);
+            $('li:first', container).addClass('pass');
+            var dt = new Date() - test.startTime;
+            $('span.time', container).html(dt);
+         },
+         fail: function() {
+            failed += 1;
+            $('#fail').text(failed);
+            $('li:first', container).addClass('fail');
+            var dt = new Date() - test.startTime;
+            $('span.time', container).html(dt);
+         }
+      };
+      tests.push(test);
+   };
+
+   addTest('pass', function(task, test) {
+      test.pass();
+   });
+
+   addTest('check', function(task, test) {
+      test.check();
+   });
+
+   addTest('task 1', function(task, test) {
+      task.next(function(task) {
+         test.pass();
+      });
+   });
+
+   addTest('task check()', function(task, test) {
+      test.expect.append('check');
+      task.next(function(task) {
+         test.result.append('check');
+      });
+      task.next(function(task) {
+         test.check();
+      });
+   });
+
+   addTest('serial 20', function(task, test) {
+      // total
+      var n = 20;
+      // counter used in the tasks
+      var taskn = 0;
+      for(var i = 0; i < n; ++i) {
+         test.expect.append(i + ' ');
+         task.next(function(task) {
+            test.result.append(taskn++ + ' ');
+         });
+      }
+      task.next(function(task) {
+         test.check();
+      });
+   });
+
+   addTest('ajax block', function(task, test) {
+      test.expect.append('.');
+      task.next(function(task) {
+         task.parent.block();
+         $.ajax({
+            type: 'GET',
+            url: 'tasks.html',
+            success: function() {
+               test.result.append('.');
+               task.parent.unblock();
+            }
+         });
+      });
+      task.next(function(task) {
+         test.check();
+      });
+   });
+
+   addTest('serial ajax', function(task, test) {
+      var n = 10;
+      for(var i = 0; i < n; ++i)
+      {
+         test.expect.append(i + ' ');
+      }
+      task.next(function(task) {
+         // create parallel functions
+         task.parent.block(n);
+         for(var i = 0; i < n; ++i)
+         {
+            // pass value into closure
+            (function(i)
+            {
+               // serial tasks
+               task.next(function(ajaxTask)
+               {
+                  $.ajax({
+                     type: 'GET',
+                     url: 'tasks.html',
+                     success: function() {
+                        // results use top level task
+                        test.result.append(i + ' ');
+                        task.parent.unblock();
+                     }
+                  });
+               });
+            })(i);
+         }
+      });
+      task.next(function(task) {
+         test.check();
+      });
+   });
+
+   addTest('parallel ajax', function(task, test) {
+      task.next(function(task) {
+         var n = 10;
+         // create parallel functions
+         var tasks = [];
+         for(var i = 0; i < n; ++i)
+         {
+            // pass value into closure
+            (function(i)
+            {
+               tasks.push(function(ajaxTask)
+               {
+                  $.ajax({
+                     type: 'GET',
+                     url: 'tasks.html',
+                     success: function() {
+                        // results use top level task
+                        test.result.append(i + ' ');
+                     }
+                  });
+               });
+            })(i);
+         }
+         // launch in parallel
+         task.parallel(tasks);
+      });
+      task.next(function(task) {
+         test.pass();
+      });
+   });
+
+   addTest('linear empty tasks rate', function(task, test) {
+      test.expect.append('-');
+      // total
+      var n = 100;
+      var start = new Date();
+      for(var i = 0; i < n; ++i) {
+         // empty task
+         task.next(function(task) {});
+      }
+      task.next(function(task) {
+         var dt = (new Date() - start) / 1000;
+         var res = $('<ul/>')
+            .append('<li>Tasks: ' + n + '</li>')
+            .append('<li>Time: ' + dt + 's</li>')
+            .append('<li>Rate: ' + n/dt + ' tasks/s</li>')
+            .append('<li>Task Time: ' + 1000*dt/n + ' ms/tasks</li>');
+         test.result.html(res);
+         test.pass();
+      });
+   });
+
+   addTest('sleep', function(task, test) {
+      test.expect.append('-');
+      var st = 1000;
+      var start = new Date();
+      task.next(function(task) {
+         task.sleep(st);
+      });
+      task.next(function(task) {
+         var dt = new Date() - start;
+         var res = $('<ul/>')
+            .append('<li>Sleep Time : ' + st + 'ms</li>')
+            .append('<li>Real Time: ' + dt + 'ms</li>')
+            .append('<li>Diff: ' + (dt-st) + 'ms</li>');
+         test.result.html(res);
+         test.pass();
+      });
+   });
+   
+   addTest('serial 20 + sleep', function(task, test) {
+      // total
+      var n = 20;
+      // counter used in the tasks
+      var taskn = 0;
+      for(var i = 0; i < n; ++i) {
+         test.expect.append(i + ' ');
+         task.next(function(task) {
+            task.sleep(20);
+            test.result.append(taskn++ + ' ');
+         });
+      }
+      task.next(function(task) {
+         test.check();
+      });
+   });
+   
+   addTest('concurrent tasks', function(task, test)
+   {
+      var colors = [
+         'red',
+         'green',
+         'blue',
+         'black',
+         'purple',
+         'goldenrod',
+         'maroon',
+         'gray',
+         'teal',
+         'magenta'
+      ];
+      var count = colors.length;
+      task.next(function(task)
+      {
+         var main = task;
+         task.block(count);
+         
+         var tasks = [];
+         for(var i = 0; i < count; ++i)
+         {
+            var makefunction = function(index)
+            {
+               return function(task)
+               {
+                  // total
+                  var n = 20;
+                  // counter used in the tasks
+                  var taskn = 0;
+                  for(var j = 0; j < n; j++)
+                  {
+                     task.next(function(task)
+                     {
+                        test.result.append(
+                           '<span style=\"color:' + colors[index] + ';\">' +
+                           taskn++ + '</span> ');
+                     });
+                  }
+                  task.next(function(task)
+                  {
+                     main.unblock();
+                  });
+               };
+            };
+            tasks.push(
+            {
+               type: 'concurrent' + i,
+               run: makefunction(i)
+            });
+         }
+         
+         for(var i = 0; i < count; ++i)
+         {
+            forge.task.start(tasks[i]);
+         }
+      });
+      
+      task.next(function(task) {
+         test.pass();
+      });
+   });
+
+   init();
+});
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/tls.html b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/tls.html
new file mode 100644
index 0000000..92501b8
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/tls.html
@@ -0,0 +1,426 @@
+<html>
+   <head>
+      <link type="text/css" rel="stylesheet" media="all" href="screen.css" />
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js"></script>
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
+      <script type="text/javascript" src="forge/debug.js"></script>
+      <script type="text/javascript" src="forge/util.js"></script>
+      <script type="text/javascript" src="forge/log.js"></script>
+      <script type="text/javascript" src="forge/socket.js"></script>
+      <script type="text/javascript" src="forge/md5.js"></script>
+      <script type="text/javascript" src="forge/sha1.js"></script>
+      <script type="text/javascript" src="forge/hmac.js"></script>
+      <script type="text/javascript" src="forge/aes.js"></script>
+      <script type="text/javascript" src="forge/pem.js"></script>
+      <script type="text/javascript" src="forge/asn1.js"></script>
+      <script type="text/javascript" src="forge/jsbn.js"></script>
+      <script type="text/javascript" src="forge/prng.js"></script>
+      <script type="text/javascript" src="forge/random.js"></script>
+      <script type="text/javascript" src="forge/oids.js"></script>
+      <script type="text/javascript" src="forge/rsa.js"></script>
+      <script type="text/javascript" src="forge/pbe.js"></script>
+      <script type="text/javascript" src="forge/x509.js"></script>
+      <script type="text/javascript" src="forge/pki.js"></script>
+      <script type="text/javascript" src="forge/tls.js"></script>
+      <script type="text/javascript" src="forge/aesCipherSuites.js"></script>
+      <script type="text/javascript" src="forge/tlssocket.js"></script>
+      <script type="text/javascript" src="forge/http.js"></script>
+      <script type="text/javascript" src="ws-webid.js"></script>
+      
+      <script type="text/javascript">
+      //<![CDATA[
+      // logging category
+      var cat = 'forge.tests.tls';
+
+      swfobject.embedSWF(
+         'forge/SocketPool.swf', 'socketPool', '0', '0', '9.0.0',
+         false, {}, {allowscriptaccess: 'always'}, {});
+      
+      // CA certificate for test server
+      var certificatePem =
+         '-----BEGIN CERTIFICATE-----\r\n' +
+         'MIIEaDCCA1CgAwIBAgIJAJuj0AjEWncuMA0GCSqGSIb3DQEBBQUAMH8xCzAJBgNV\r\n' +
+         'BAYTAlVTMREwDwYDVQQIEwhWaXJnaW5pYTETMBEGA1UEBxMKQmxhY2tzYnVyZzEd\r\n' +
+         'MBsGA1UEChMURGlnaXRhbCBCYXphYXIsIEluYy4xGjAYBgNVBAsTEUZvcmdlIFRl\r\n' +
+         'c3QgU2VydmVyMQ0wCwYDVQQDEwR0ZXN0MB4XDTEwMDcxMzE3MjAzN1oXDTMwMDcw\r\n' +
+         'ODE3MjAzN1owfzELMAkGA1UEBhMCVVMxETAPBgNVBAgTCFZpcmdpbmlhMRMwEQYD\r\n' +
+         'VQQHEwpCbGFja3NidXJnMR0wGwYDVQQKExREaWdpdGFsIEJhemFhciwgSW5jLjEa\r\n' +
+         'MBgGA1UECxMRRm9yZ2UgVGVzdCBTZXJ2ZXIxDTALBgNVBAMTBHRlc3QwggEiMA0G\r\n' +
+         'CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCm/FobjqK8CVP/Xbnpyhf1tpoyaiFf\r\n' +
+         'ShUOmlWqL5rLe0Q0dDR/Zur+sLMUv/1T4wOfFkjjxvZ0Sk5NIjK3Wy2UA41a+M3J\r\n' +
+         'RTbCFrg4ujsovFaD4CDmV7Rek0qJB3m5Gp7hgu5vfL/v+WrwxnQObNq+IrTMSA15\r\n' +
+         'cO4LzNIPj9K1LN2dB+ucT7xTQFHAfvLLgLlCLiberoabF4rEhgTMTbmMtFVKSt+P\r\n' +
+         'xgQIYPnhw1WuAvE9hFesRQFdfARLqIZk92FeHkgtHv9BAunktJemcidbowTCTBaM\r\n' +
+         '/njcgi1Tei/LFkph/FCVyGER0pekJNHX626bAQSLo/srsWfmcll9rK6bAgMBAAGj\r\n' +
+         'geYwgeMwHQYDVR0OBBYEFCau5k6jxezjULlLuo/liswJlBF8MIGzBgNVHSMEgasw\r\n' +
+         'gaiAFCau5k6jxezjULlLuo/liswJlBF8oYGEpIGBMH8xCzAJBgNVBAYTAlVTMREw\r\n' +
+         'DwYDVQQIEwhWaXJnaW5pYTETMBEGA1UEBxMKQmxhY2tzYnVyZzEdMBsGA1UEChMU\r\n' +
+         'RGlnaXRhbCBCYXphYXIsIEluYy4xGjAYBgNVBAsTEUZvcmdlIFRlc3QgU2VydmVy\r\n' +
+         'MQ0wCwYDVQQDEwR0ZXN0ggkAm6PQCMRady4wDAYDVR0TBAUwAwEB/zANBgkqhkiG\r\n' +
+         '9w0BAQUFAAOCAQEAnP/2mzFWaoGx6+KAfY8pcgnF48IoyKPx5cAQyzpMo+uRwrln\r\n' +
+         'INcDGwNx6p6rkjFbK27TME9ReCk+xQuVGaKOtqErKECXWDtD+0M35noyaOwWIFu2\r\n' +
+         '7gPZ0uGJ1n9ZMe/S9yZmmusaIrc66rX4o+fslUlH0g3SrH7yf83M8aOC2pEyCsG0\r\n' +
+         'mNNfwSFWfmu+1GMRHXJQ/qT8qBX8ZPhzRY2BAS6vr+eh3gwXR6yXLA8Xm1+e+iDU\r\n' +
+         'gGTQoYkixDIL2nhvd4AFFlE977BiE+0sMS1eJKUUbQ36MLAWb5oOZKHrphEvqMKA\r\n' +
+         'eGDO3qoDqB5TkZC3x38DXBDvAZ01d9s0fvveag==\r\n' +
+         '-----END CERTIFICATE-----';
+      
+      // local aliases
+      var net = window.forge.net;
+      var tls = window.forge.tls;
+      var http = window.forge.http;
+      var util = window.forge.util;
+
+      var client;
+      
+      function client_init(primed)
+      {
+         try
+         {
+            var sp = net.createSocketPool({
+               flashId: 'socketPool',
+               policyPort: 19945,
+               msie: false
+            });
+            client = http.createClient({
+               //url: 'https://localhost:4433',
+               url: 'https://' + window.location.host,
+               socketPool: sp,
+               connections: 10,
+               caCerts: [certificatePem],
+               // optional cipher suites in order of preference
+               cipherSuites: [
+                  tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+                  tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+               verify: function(c, verified, depth, certs)
+               {
+                  forge.log.debug(cat,
+                     'TLS certificate ' + depth + ' verified', verified);
+                  // Note: change to always true to test verifying without cert
+                  //return verified;
+                  // FIXME: temporarily accept any cert to allow hitting any bpe
+                  if(verified !== true)
+                  {
+                     forge.log.warning(cat, 
+                        'Certificate NOT verified. Ignored for test.');
+                  }
+                  return true;
+               },
+               primeTlsSockets: primed
+            });
+            document.getElementById('feedback').innerHTML =
+               'http client created';
+         }
+         catch(ex)
+         {
+            forge.log.error(cat, ex);
+         }
+         
+         return false;
+      }
+      
+      function client_cleanup()
+      {
+         var sp = client.socketPool;
+         client.destroy();
+         sp.destroy();
+         document.getElementById('feedback').innerHTML =
+            'http client cleaned up';
+         return false;
+      }
+
+      function client_send()
+      {
+         /*
+         var request = http.createRequest({
+            method: 'POST',
+            path: '/',
+            body: 'echo=foo',
+            headers: [{'Content-Type': 'application/x-www-form-urlencoded'}]
+         });
+         */
+         var request = http.createRequest({
+            method: 'GET',
+            path: '/'
+         });
+         
+         client.send({
+            request: request,
+            connected: function(e)
+            {
+               forge.log.debug(cat, 'connected', e);
+            },
+            headerReady: function(e)
+            {
+               forge.log.debug(cat, 'header ready', e);
+            },
+            bodyReady: function(e)
+            {
+               forge.log.debug(cat, 'body ready', e);
+
+               // FIXME: current test server doesn't seem to handle keep-alive
+               // correctly, so close connection 
+               e.socket.close();
+            },
+            error: function(e)
+            {
+               forge.log.error(cat, 'error', e);
+            }
+         });
+         document.getElementById('feedback').innerHTML =
+            'http request sent';
+         return false;
+      }
+
+      function client_send_10()
+      {
+         for(var i = 0; i < 10; ++i)
+         {
+            client_send();
+         }
+         return false;
+      }
+
+      function client_stress()
+      {
+         for(var i = 0; i < 10; ++i)
+         {
+            setTimeout(function()
+            {
+               for(var i = 0; i < 10; ++i)
+               {
+                  client_send();
+               }
+            }, 0);
+         }
+         return false;
+      }
+
+      function client_cookies()
+      {
+         var cookie =
+         {
+            name: 'test-cookie',
+            value: 'test-value',
+            maxAge: -1,
+            secure: true,
+            path: '/'
+         };
+         client.setCookie(cookie);
+         forge.log.debug(cat, 'cookie', client.getCookie('test-cookie'));
+      }
+
+      function client_clear_cookies()
+      {
+         client.clearCookies();
+      }
+      
+      function websocket_test()
+      {
+         // create certificate
+         var cn = 'client';
+         console.log(
+            'Generating 512-bit key-pair and certificate for \"' + cn + '\".');
+         var keys = forge.pki.rsa.generateKeyPair(512);
+         console.log('key-pair created.');
+
+         var cert = forge.pki.createCertificate();
+         cert.serialNumber = '01';
+         cert.validity.notBefore = new Date();
+         cert.validity.notAfter = new Date();
+         cert.validity.notAfter.setFullYear(
+            cert.validity.notBefore.getFullYear() + 1);
+         var attrs = [{
+            name: 'commonName',
+            value: cn
+         }, {
+            name: 'countryName',
+            value: 'US'
+         }, {
+            shortName: 'ST',
+            value: 'Virginia'
+         }, {
+            name: 'localityName',
+            value: 'Blacksburg'
+         }, {
+            name: 'organizationName',
+            value: 'Test'
+         }, {
+            shortName: 'OU',
+            value: 'Test'
+         }];
+         cert.setSubject(attrs);
+         cert.setIssuer(attrs);
+         cert.setExtensions([{
+            name: 'basicConstraints',
+            cA: true
+         }, {
+            name: 'keyUsage',
+            keyCertSign: true,
+            digitalSignature: true,
+            nonRepudiation: true,
+            keyEncipherment: true,
+            dataEncipherment: true
+         }, {
+            name: 'subjectAltName',
+            altNames: [{
+               type: 6, // URI
+               value: 'http://myuri.com/webid#me'
+            }]
+         }]);
+         // FIXME: add subjectKeyIdentifier extension
+         // FIXME: add authorityKeyIdentifier extension
+         cert.publicKey = keys.publicKey;
+         
+         // self-sign certificate
+         cert.sign(keys.privateKey);
+         
+         // save cert and private key in PEM format
+         cert = forge.pki.certificateToPem(cert);
+         privateKey = forge.pki.privateKeyToPem(keys.privateKey);
+         console.log('certificate created for \"' + cn + '\": \n' + cert);
+
+         // create websocket
+         var ws = new WebSocket('ws://localhost:8080');
+         console.log('created websocket', ws);
+
+         // create TLS client
+         var success = false;
+         var tls = forge.tls.createConnection(
+         {
+            server: false,
+            caStore: [],
+            sessionCache: {},
+            // supported cipher suites in order of preference
+            cipherSuites: [
+               forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+               forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+            virtualHost: 'server',
+            verify: function(c, verified, depth, certs)
+            {
+               console.log(
+                  'TLS Client verifying certificate w/CN: \"' +
+                  certs[0].subject.getField('CN').value +
+                  '\", verified: ' + verified + '...');
+               // accept any certificate from the server for this test
+               return true;
+            },
+            connected: function(c)
+            {
+               console.log('Client connected...');
+               
+               // send message to server
+               setTimeout(function()
+               {
+                  c.prepare('Hello Server');
+               }, 1);
+            },
+            getCertificate: function(c, hint)
+            {
+               console.log('Client getting certificate ...');
+               return cert;
+            },
+            getPrivateKey: function(c, cert)
+            {
+               return privateKey;
+            },
+            tlsDataReady: function(c)
+            {
+               // send base64-encoded TLS data to server
+               ws.send(forge.util.encode64(c.tlsData.getBytes()));
+            },
+            dataReady: function(c)
+            {
+               var response = c.data.getBytes();
+               console.log('Client received \"' + response + '\"');
+               success = (response === 'Hello Client');
+               c.close();
+            },
+            closed: function(c)
+            {
+               console.log('Client disconnected.');
+               if(success)
+               {
+                  console.log('PASS');
+               }
+               else
+               {
+                  console.log('FAIL');
+               }
+            },
+            error: function(c, error)
+            {
+               console.log('Client error: ' + error.message);
+            }
+         });
+
+         ws.onopen = function(evt)
+         {
+            console.log('websocket connected');
+
+            // do TLS handshake
+            tls.handshake();
+         };
+         ws.onmessage = function(evt)
+         {
+            // base64-decode data and process it
+            tls.process(forge.util.decode64(evt.data));
+         };
+         ws.onclose = function(evt)
+         {
+            console.log('websocket closed');
+         };
+      }
+      
+      //]]>
+      </script>
+   </head>
+   <body>
+      <div class="nav"><a href="index.html">Forge Tests</a> / TLS</div>
+
+      <div class="header">
+         <h1>TLS Test</h1>
+      </div>
+
+      <div class="content">
+
+      <!-- div used to hold the flash socket pool implemenation -->
+      <div id="socketPool">
+         <p>Could not load the flash SocketPool.</p>
+      </div>
+
+      <fieldset class="section">
+         <ul>
+           <li>Use the controls below to test the HTTP client over TLS.</li>
+           <li>You currently need a JavaScript console to view the output.</li>
+           <li>This test connects to a TLS server so you must have one running. The easiest way to run this test is to start the test server with --tls and load this page over HTTPS.</li>
+         </ul>
+      </fieldset>
+
+      <fieldset class="section">
+      <legend>Controls</legend>
+      <div id="controls">
+         <button id="init" onclick="javascript:return client_init(false);">init</button>
+         <button id="init_primed" onclick="javascript:return client_init(true);">init primed</button>
+         <button id="cleanup" onclick="javascript:return client_cleanup();">cleanup</button>
+         <button id="send" onclick="javascript:return client_send();">send</button>
+         <button id="send10" onclick="javascript:return client_send_10();">send 10</button>
+         <button id="stress" onclick="javascript:return client_stress();">stress</button>
+         <button id="client_cookies" onclick="javascript:return client_cookies();">cookies</button>
+         <button id="clear_cookies" onclick="javascript:return client_clear_cookies();">clear cookies</button>
+         <button id="websocket" onclick="javascript:return websocket_test();">websocket test</button>
+         <button id="websocket-webid" onclick="javascript:return websocket_webid('localhost', 8080);">websocket webid test</button>
+      </div>
+      </fieldset>
+
+      <fieldset class="section">
+      <legend>Feedback</legend>
+      <p>Feedback from the flash SocketPool:</p>
+      <div id="feedback">
+      None
+      </div>
+      </fieldset>
+
+      </div>
+   </body>
+</html>
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/webid.html b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/webid.html
new file mode 100644
index 0000000..8c8d795
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/webid.html
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+   <head>
+      <title>Web ID Test</title>
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js"></script>
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
+      <script type="text/javascript" src="forge/debug.js"></script>
+      <script type="text/javascript" src="forge/util.js"></script>
+      <script type="text/javascript" src="forge/log.js"></script>
+      <script type="text/javascript" src="forge/task.js"></script>
+      <script type="text/javascript" src="forge/socket.js"></script>
+      <script type="text/javascript" src="forge/md5.js"></script>
+      <script type="text/javascript" src="forge/sha1.js"></script>
+      <script type="text/javascript" src="forge/hmac.js"></script>
+      <script type="text/javascript" src="forge/aes.js"></script>
+      <script type="text/javascript" src="forge/pem.js"></script>
+      <script type="text/javascript" src="forge/asn1.js"></script>
+      <script type="text/javascript" src="forge/jsbn.js"></script>
+      <script type="text/javascript" src="forge/prng.js"></script>
+      <script type="text/javascript" src="forge/random.js"></script>
+      <script type="text/javascript" src="forge/oids.js"></script>
+      <script type="text/javascript" src="forge/rsa.js"></script>
+      <script type="text/javascript" src="forge/pbe.js"></script>
+      <script type="text/javascript" src="forge/x509.js"></script>
+      <script type="text/javascript" src="forge/pki.js"></script>
+      <script type="text/javascript" src="forge/tls.js"></script>
+      <script type="text/javascript" src="forge/aesCipherSuites.js"></script>
+      <script type="text/javascript" src="forge/tlssocket.js"></script>
+      <script type="text/javascript" src="forge/http.js"></script>
+      <script type="text/javascript" src="forge/xhr.js"></script>
+      <script type="text/javascript" src="webid.js"></script>
+
+      <link type="text/css" rel="stylesheet" media="all" href="screen.css" />
+      <style type="text/css">
+         .ready { color: inherit; background: inherit; }
+         .testing { color: black; background: yellow; }
+         .pass{ color: white; background: green; }
+         .fail{ color: white; background: red; }
+      </style>
+   </head>
+<body>
+<div class="nav"><a href="index.html">Forge Tests</a> / Web ID</div>
+
+<div class="header">
+   <h1>Web ID Tests</h1>
+</div>
+
+<div class="content">
+
+<!-- div used to hold the flash socket pool implementation -->
+<div id="socketPool">
+   <p>Could not load the flash SocketPool.</p>
+</div>
+
+<fieldset class="section">
+   <ul>
+     <li>Use the controls below to test Web ID.</li>
+     <li>Use 512 bits or less on slower JavaScript implementations.</li>
+   </ul>
+</fieldset>
+
+<fieldset class="section">
+<legend>Control</legend>
+   <table>
+      <tr>
+         <td rowspan="3"><button id="create">Create Web ID</button></td>
+         <td>Bits</td>
+         <td><input id="bits" size=8 value="1024"/></td>
+      </tr>
+      <tr>
+         <td>URI</td>
+         <td><input id="uri" size=60 value="http://localhost/dataspace/person/myname#this"/></td>
+      </tr>
+      <tr>
+         <td>Common Name</td>
+         <td><input id="commonName" size=20 value="mycert"/></td>
+      </tr>
+      <tr>
+         <td><button id="show">Show Web IDs</button></td>
+         <td>&nbsp;</td>
+         <td>&nbsp;</td>
+      </tr>
+      <tr>
+         <td><button id="clear">Delete Web IDs</button></td>
+         <td>&nbsp;</td>
+         <td>&nbsp;</td>
+      </tr>
+      <tr>
+         <td><button id="authenticate">Authenticate using Web ID</button></td>
+         <td>URI</td>
+         <td><input id="webid" size=60 value="http://localhost/dataspace/person/myname#this"/></td>
+      </tr>
+   </table>
+</fieldset>
+
+<fieldset class="section">
+<legend>Progress</legend>
+   <div id="progress"></div>
+</fieldset>
+
+<fieldset class="section">
+<legend>Available Web IDs</legend>
+<div id="webids"></div>
+</fieldset>
+
+</div>
+</body>
+</html>
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/webid.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/webid.js
new file mode 100644
index 0000000..7c07ab9
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/webid.js
@@ -0,0 +1,313 @@
+/**
+ * Forge Web ID Tests
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010 Digital Bazaar, Inc. All rights reserved.
+ */
+(function($)
+{
+   // load flash socket pool
+   window.forge.socketPool = {};
+   window.forge.socketPool.ready = function()
+   {
+      // init forge xhr
+      forge.xhr.init({
+         flashId: 'socketPool',
+         policyPort: 19945,
+         msie: $.browser.msie,
+         connections: 10,
+         caCerts: [],
+         verify: function(c, verified, depth, certs)
+         {
+            // don't care about cert verification for test
+            return true;
+         }
+      });
+   };
+   swfobject.embedSWF(
+      'forge/SocketPool.swf', 'socketPool', '0', '0', '9.0.0',
+      false, {}, {allowscriptaccess: 'always'}, {});
+})(jQuery);
+
+jQuery(function($)
+{
+   var cat = 'forge.tests.webid';
+   
+   // local alias
+   var forge = window.forge;
+
+   $('#create').click(function()
+   {
+      var bits = $('#bits')[0].value;
+      var uri = $('#uri')[0].value;
+      var commonName = $('#commonName')[0].value;
+      forge.log.debug(cat, 'generating ' + bits +
+         '-bit RSA key-pair and certificate...');
+      
+      // function to create cert
+      var createCert = function(keys)
+      {
+         try
+         {
+            var cert = forge.pki.createCertificate();
+            cert.serialNumber = '01';
+            cert.validity.notBefore = new Date();
+            cert.validity.notAfter = new Date();
+            cert.validity.notAfter.setFullYear(
+               cert.validity.notBefore.getFullYear() + 1);
+            var attrs = [{
+               name: 'commonName',
+               value: commonName
+            }, {
+               name: 'countryName',
+               value: 'US'
+            }, {
+               shortName: 'ST',
+               value: 'Virginia'
+            }, {
+               name: 'localityName',
+               value: 'Blacksburg'
+            }, {
+               name: 'organizationName',
+               value: 'Test'
+            }, {
+               shortName: 'OU',
+               value: 'Test'
+            }];
+            cert.setSubject(attrs);
+            cert.setIssuer(attrs);
+            cert.setExtensions([{
+               name: 'basicConstraints',
+               cA: true
+            }, {
+               name: 'keyUsage',
+               keyCertSign: true,
+               digitalSignature: true,
+               nonRepudiation: true,
+               keyEncipherment: true,
+               dataEncipherment: true
+            }, {
+               name: 'subjectAltName',
+               altNames: [{
+                  type: 6, // URI
+                  value: uri
+               }]
+            }]);
+            // FIXME: add subjectKeyIdentifier extension
+            // FIXME: add authorityKeyIdentifier extension
+            cert.publicKey = keys.publicKey;
+            
+            // self-sign certificate
+            cert.sign(keys.privateKey);
+            
+            // verify certificate
+            forge.log.debug('verified', cert.verify(cert));
+            
+            forge.log.debug(cat, 'certificate:', cert);
+            //forge.log.debug(cat, 
+            //   forge.asn1.prettyPrint(forge.pki.certificateToAsn1(cert)));
+            var keyPem = forge.pki.privateKeyToPem(keys.privateKey);
+            var certPem = forge.pki.certificateToPem(cert);
+            forge.log.debug(cat, keyPem);
+            forge.log.debug(cat, certPem);
+            
+            forge.log.debug(cat, 'storing certificate and private key...');
+            try
+            {
+               // get flash API
+               var flashApi = document.getElementById('socketPool');
+               
+               // get web ids collection
+               var webids = forge.util.getItem(
+                  flashApi, 'forge.test.webid', 'webids');
+               webids = webids || {};
+               
+               // add web id
+               webids[uri] = {
+                  certificate: certPem,
+                  privateKey: keyPem
+               };
+               
+               // update web ids collection
+               forge.util.setItem(
+                  flashApi, 'forge.test.webid', 'webids', webids);
+               
+               forge.log.debug(cat, 'certificate and private key stored');
+               $('#show').click();
+            }
+            catch(ex)
+            {
+               forge.log.error(cat, ex);
+            }
+         }
+         catch(ex)
+         {
+            forge.log.error(cat, ex, ex.message ? ex.message : '');
+         }
+      };
+      
+      // create key-generation state and function to step algorithm
+      var progress = $('#progress');
+      progress.html('Generating ' + bits + '-bit key-pair.');
+      var state = forge.pki.rsa.createKeyPairGenerationState(bits);
+      var kgTime = +new Date();
+      var step = function()
+      {
+         // step key-generation
+         if(!forge.pki.rsa.stepKeyPairGenerationState(state, 1000))
+         {
+            progress.html(progress.html() + '.');
+            setTimeout(step, 1);
+         }
+         // key-generation complete
+         else
+         {
+            kgTime = +new Date() - kgTime;
+            forge.log.debug(cat, 'Total key-gen time', kgTime + 'ms');
+            createCert(state.keys);
+            progress.html(progress.html() + 'done. Time=' + kgTime + 'ms');
+         }
+      };
+      
+      // run key-gen algorithm
+      setTimeout(step, 0);
+   });
+
+   $('#show').click(function()
+   {  
+      forge.log.debug(cat, 'get stored web IDs...');
+      try
+      {
+         // get flash API
+         var flashApi = document.getElementById('socketPool');
+         
+         // get web ids collection
+         var webids = forge.util.getItem(
+            flashApi, 'forge.test.webid', 'webids');
+         webids = webids || {};
+         
+         var html = '<ul>';
+         var webid, cert;
+         for(var key in webids)
+         {
+            webid = webids[key];
+            cert = forge.pki.certificateFromPem(webid.certificate);
+            html += '<li><p>' + key + '</p>';
+            
+            var attr;
+            for(var n = 0; n < cert.subject.attributes.length; ++n)
+            {
+               attr = cert.subject.attributes[n];
+               html += attr.name + ': ' + attr.value + '<br/>';
+            }
+            
+            //html += '<p>' + webid.certificate + '</p></li>';
+            html += '</li>';
+         }
+         if(html === '<ul>')
+         {
+            html = 'None';
+         }
+         else
+         {
+            html += '</ul>';
+         }
+         
+         $('#webids').html(html);
+         
+         forge.log.debug(cat, 'Web IDs retrieved');
+      }
+      catch(ex)
+      {
+         forge.log.error(cat, ex);
+      }
+   });
+   
+   $('#clear').click(function()
+   {  
+      forge.log.debug(cat, 'clearing all web IDs...');
+      try
+      {
+         // get flash API
+         var flashApi = document.getElementById('socketPool');
+         forge.util.clearItems(flashApi, 'forge.test.webid');
+         $('#webids').html('None');
+         forge.log.debug(cat, 'Web IDs cleared');
+      }
+      catch(ex)
+      {
+         forge.log.error(cat, ex);
+      }
+   });
+   
+   $('#authenticate').click(function()
+   {
+      forge.log.debug(cat, 'doing Web ID authentication...');
+      
+      try
+      {
+         // get flash API
+         var flashApi = document.getElementById('socketPool');
+         
+         // get web ids collection
+         var webids = forge.util.getItem(
+            flashApi, 'forge.test.webid', 'webids');
+         webids = webids || {};
+         
+         var uri = $('#webid')[0].value;
+         var webid = webids[uri];
+         
+         $.ajax(
+         {
+            type: 'GET',
+            url: '/',
+            success: function(data, textStatus, xhr)
+            {
+               if(data !== '')
+               {
+                  forge.log.debug(cat, 'authentication completed');
+                  forge.log.debug(cat, data);
+               }
+               else
+               {
+                  forge.log.error(cat, 'authentication failed');
+               }
+            },
+            error: function(xhr, textStatus, errorThrown)
+            {
+               forge.log.error(cat, 'authentication failed');
+            },
+            xhr: function()
+            {
+               return forge.xhr.create({
+                  // FIXME: change URL
+                  url: 'https://localhost:4433',
+                  connections: 10,
+                  caCerts: [],
+                  verify: function(c, verified, depth, certs)
+                  {
+                     // don't care about cert verification for test
+                     return true;
+                  },
+                  getCertificate: function(c)
+                  {
+                     //forge.log.debug(cat, 'using cert', webid.certificate);
+                     return webid.certificate;
+                  },
+                  getPrivateKey: function(c)
+                  {
+                     //forge.log.debug(cat,
+                     //   'using private key', webid.privateKey);
+                     return webid.privateKey;
+                  }
+               });
+            }
+         });      
+      }
+      catch(ex)
+      {
+         forge.log.error(cat, ex);
+      }
+   });
+});
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/ws-webid.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/ws-webid.js
new file mode 100644
index 0000000..2ce5816
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/ws-webid.js
@@ -0,0 +1,132 @@
+var websocket_webid = function(host, port)
+{
+   var cat = 'ws';
+   
+   // TODO: get private key and certificate from local storage
+   var privateKey =
+   '-----BEGIN RSA PRIVATE KEY-----\r\n' +
+'MIICXAIBAAKBgQCTmE8QLARsC57Z1OrOaLM6AS3fn70N7BvlU7z7yw8UpcJA/jOl\r\n' +
+'NWu7eS9uzuckdVZ9FE0+x3DRvhtDI6K+18dcrUWtl5ADWXcs1QS3/7bGh7IybFyY\r\n' +
+'0xT4VzLHcx6K4PNmfkjAQdyOz/EsuRqZ/ngIQ2tdHdkkzdQPECbTvFeG2wIDAQAB\r\n' +
+'AoGAds3l7l2QHaxo7GzfqNBMXEdwto2tLxS8C6eQ+pkkBXm72HcF+Vj75AcTMD2p\r\n' +
+'fwZYXQxHdV4yqRI+fZeku7uTA/3yBAAvNobbEN5jtHnq0ZTO/HO8HuHkKrCvD8c3\r\n' +
+'0rJV6lNIuaARI9jZFf6HVchW3PMjKUpYhTs/sFhRxmsMpTkCQQDu8TPzXRmN1aw8\r\n' +
+'tSI2Nyn8QUy9bw/12tlVaZIhrcVCiJl7JHGqSCowTqZlwmJIjd4W0zWjTvS7tEeO\r\n' +
+'FaZHtP8lAkEAniGvm8S9zyzmhWRRIuU6EE2dtTbeAa5aSOK3nBaaNu2cHUxWle+J\r\n' +
+'8lE4uequ9wqDG1AfOLobPmHReccmOI6N/wJAIP/I1/RkohT/a4bsiaZGsyLlkUf0\r\n' +
+'YVTvLP+ege44zv6Ei+A1nnnG8dL64hTdc/27zVUwFDTEUeQM+c99nmudzQJBAApY\r\n' +
+'qeTHOqQTjAGuTqC53tKyQV9Z96yke8PJEbpkwDJX2Z8RH5kv0xbHua5wbII9bdab\r\n' +
+'p29OvfmW7N3K6fVJXoECQHK8FDC0i8v1Ui8FoBmt+Z1c1+/9TCEE0abUQ6rfOUbm\r\n' +
+'XHMMac/n4qDs0OoCjR4u46dpoK+WN7zcg56tToFPVow=\r\n' +
+'-----END RSA PRIVATE KEY-----';
+  var certificate =
+  '-----BEGIN CERTIFICATE-----\r\n' +
+'MIICgDCCAemgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMRMwEQYDVQQDEwpKb2hu\r\n' +
+'IFNtaXRoMRMwEQYDVQQHEwpCbGFja3NidXJnMREwDwYDVQQIEwhWaXJnaW5pYTEL\r\n' +
+'MAkGA1UEBhMCVVMxDDAKBgNVBAoTA0ZvbzAeFw0xMDExMjYxNzUxMzJaFw0xMTEx\r\n' +
+'MjYxNzUxMzJaMFgxEzARBgNVBAMTCkpvaG4gU21pdGgxEzARBgNVBAcTCkJsYWNr\r\n' +
+'c2J1cmcxETAPBgNVBAgTCFZpcmdpbmlhMQswCQYDVQQGEwJVUzEMMAoGA1UEChMD\r\n' +
+'Rm9vMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCTmE8QLARsC57Z1OrOaLM6\r\n' +
+'AS3fn70N7BvlU7z7yw8UpcJA/jOlNWu7eS9uzuckdVZ9FE0+x3DRvhtDI6K+18dc\r\n' +
+'rUWtl5ADWXcs1QS3/7bGh7IybFyY0xT4VzLHcx6K4PNmfkjAQdyOz/EsuRqZ/ngI\r\n' +
+'Q2tdHdkkzdQPECbTvFeG2wIDAQABo1owWDAMBgNVHRMEBTADAQH/MAsGA1UdDwQE\r\n' +
+'AwIC9DA7BgNVHREENDAyhjBodHRwOi8vd2ViaWQuZGlnaXRhbGJhemFhci5jb20v\r\n' +
+'aWRzLzE1MzQ1NzI2NDcjbWUwDQYJKoZIhvcNAQEFBQADgYEAPNm8albI4w6anynw\r\n' +
+'XE/+00sCVks9BbgTcIpRqZPGqSuTRwoYW35isNLDqFqIUdVREMvFrEn3nOlOyKi0\r\n' +
+'29G8JtLHFSXZsqf38Zou/bGAhtEH1AVEbM2bRtEnG8IW24jL8hiciz4htxmjnkHN\r\n' +
+'JnQ8SQtUSWplGnz0vMFEOv6JbnI=\r\n' +
+'-----END CERTIFICATE-----';
+
+   // create websocket
+   var ws = new WebSocket('ws://' + host + ':' + port);
+   forge.log.debug(cat, 'Created WebSocket', ws);
+
+   // create TLS client
+   var success = false;
+   var tls = forge.tls.createConnection(
+   {
+      server: false,
+      caStore: [],
+      sessionCache: {},
+      // supported cipher suites in order of preference
+      cipherSuites: [
+         forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+         forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+      virtualHost: host,
+      verify: function(c, verified, depth, certs)
+      {
+         forge.log.debug(cat, 
+            'TLS Client verifying certificate w/CN: \"' +
+            certs[0].subject.getField('CN').value + '\"');
+         // accept any certificate from the server for this test
+         return true;
+      },
+      connected: function(c)
+      {
+         forge.log.debug(cat, 'Client connected');
+      },
+      getCertificate: function(c, hint)
+      {
+         forge.log.debug(cat, 'Client using client-certificate');
+         return certificate;
+      },
+      getPrivateKey: function(c, cert)
+      {
+         return privateKey;
+      },
+      tlsDataReady: function(c)
+      {
+         // send base64-encoded TLS data to server
+         ws.send(forge.util.encode64(c.tlsData.getBytes()));
+      },
+      dataReady: function(c)
+      {
+         var response = c.data.getBytes();
+         forge.log.debug(cat, 'Client received \"' + response + '\"');
+         try
+         {
+            response = JSON.parse(response);
+            success = response.success;
+            
+            // TODO: call window.authenticate on response json, just like
+            // w/flash version
+         }
+         catch(ex) {}
+         c.close();
+      },
+      closed: function(c)
+      {
+         forge.log.debug(cat, 'Client disconnected');
+         if(success)
+         {
+            forge.log.debug(cat, 'PASS');
+         }
+         else
+         {
+            forge.log.debug(cat, 'FAIL');
+         }
+      },
+      error: function(c, error)
+      {
+         forge.log.debug(cat, 'Client error: ' + error.message);
+      }
+   });
+
+   ws.onopen = function(evt)
+   {
+      forge.log.debug(cat, 'WebSocket connected');
+
+      // do TLS handshake
+      tls.handshake();
+   };
+   ws.onmessage = function(evt)
+   {
+      // base64-decode data and process it
+      tls.process(forge.util.decode64(evt.data));
+   };
+   ws.onclose = function(evt)
+   {
+      forge.log.debug(cat, 'WebSocket closed');
+   };
+};
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/ws.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/ws.js
new file mode 100644
index 0000000..ba0b39d
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/ws.js
@@ -0,0 +1,237 @@
+// Github: http://github.com/ncr/node.ws.js
+// Compatible with node v0.1.91
+// Author: Jacek Becela
+// Contributors:
+//   Michael Stillwell  http://github.com/ithinkihaveacat
+//   Nick Chapman       http://github.com/nchapman
+//   Dmitriy Shalashov  http://github.com/skaurus
+//   Johan Dahlberg
+//   Andreas Kompanez
+//   Samuel Cyprian		http://github.com/samcyp
+// License: MIT
+// Based on: http://github.com/Guille/node.websocket.js
+
+function nano(template, data) {
+  return template.replace(/\{([\w\.]*)}/g, function (str, key) {
+    var keys = key.split("."), value = data[keys.shift()];
+    keys.forEach(function (key) { value = value[key];});
+    return value;
+  });
+}
+
+function pack(num) {
+  var result = '';
+  result += String.fromCharCode(num >> 24 & 0xFF);
+  result += String.fromCharCode(num >> 16 & 0xFF);
+  result += String.fromCharCode(num >> 8 & 0xFF);
+  result += String.fromCharCode(num & 0xFF);
+  return result;
+}
+
+var sys  = require("sys"),
+  net    = require("net"),
+  crypto = require("crypto"),
+  requiredHeaders = {
+    'get': /^GET (\/[^\s]*)/,
+    'upgrade': /^WebSocket$/,
+    'connection': /^Upgrade$/,
+    'host': /^(.+)$/,
+    'origin': /^(.+)$/
+  },
+  handshakeTemplate75 = [
+    'HTTP/1.1 101 Web Socket Protocol Handshake', 
+    'Upgrade: WebSocket', 
+    'Connection: Upgrade',
+    'WebSocket-Origin: {origin}',
+    'WebSocket-Location: {protocol}://{host}{resource}',
+    '',
+    ''
+  ].join("\r\n"),
+  handshakeTemplate76 = [
+    'HTTP/1.1 101 WebSocket Protocol Handshake', // note a diff here
+    'Upgrade: WebSocket',
+    'Connection: Upgrade',
+    'Sec-WebSocket-Origin: {origin}',
+    'Sec-WebSocket-Location: {protocol}://{host}{resource}',
+    '',
+    '{data}'
+  ].join("\r\n"),
+  flashPolicy = '<cross-domain-policy><allow-access-from domain="*" to-ports="*" /></cross-domain-policy>';
+
+
+
+exports.createSecureServer = function (websocketListener, credentials, options) {
+	if (!options) options = {};
+	options.secure = credentials;
+	return this.createServer(websocketListener, options);
+};
+
+exports.createServer = function (websocketListener, options) {
+  if (!options) options = {};
+  if (!options.flashPolicy) options.flashPolicy = flashPolicy;
+  // The value should be a crypto credentials
+  if (!options.secure) options.secure = null;
+
+  return net.createServer(function (socket) {
+	//Secure WebSockets
+	var wsProtocol = 'ws';
+	if(options.secure) {
+	  wsProtocol = 'wss';
+	  socket.setSecure(options.secure);
+	}
+    socket.setTimeout(0);
+    socket.setNoDelay(true);
+    socket.setKeepAlive(true, 0);
+
+    var emitter = new process.EventEmitter(),
+      handshaked = false,
+      buffer = "";
+      
+    function handle(data) {
+      buffer += data;
+      
+      var chunks = buffer.split("\ufffd"),
+        count = chunks.length - 1; // last is "" or a partial packet
+        
+      for(var i = 0; i < count; i++) {
+        var chunk = chunks[i];
+        if(chunk[0] == "\u0000") {
+          emitter.emit("data", chunk.slice(1));
+        } else {
+          socket.end();
+          return;
+        }
+      }
+      
+      buffer = chunks[count];
+    }
+
+    function handshake(data) {
+      var _headers = data.split("\r\n");
+
+      if ( /<policy-file-request.*>/.exec(_headers[0]) ) {
+        socket.write( options.flashPolicy );
+        socket.end();
+        return;
+      }
+
+      // go to more convenient hash form
+      var headers = {}, upgradeHead, len = _headers.length;
+      if ( _headers[0].match(/^GET /) ) {
+        headers["get"] = _headers[0];
+      } else {
+        socket.end();
+        return;
+      }
+      if ( _headers[ _headers.length - 1 ] ) {
+        upgradeHead = _headers[ _headers.length - 1 ];
+        len--;
+      }
+      while (--len) { // _headers[0] will be skipped
+        var header = _headers[len];
+        if (!header) continue;
+
+        var split = header.split(": ", 2); // second parameter actually seems to not work in node
+        headers[ split[0].toLowerCase() ] = split[1];
+      }
+
+      // check if we have all needed headers and fetch data from them
+      var data = {}, match;
+      for (var header in requiredHeaders) {
+        //           regexp                          actual header value
+        if ( match = requiredHeaders[ header ].exec( headers[header] ) ) {
+          data[header] = match;
+        } else {
+          socket.end();
+          return;
+        }
+      }
+
+      // draft auto-sensing
+      if ( headers["sec-websocket-key1"] && headers["sec-websocket-key2"] && upgradeHead ) { // 76
+        var strkey1 = headers["sec-websocket-key1"]
+          , strkey2 = headers["sec-websocket-key2"]
+
+          , numkey1 = parseInt(strkey1.replace(/[^\d]/g, ""), 10)
+          , numkey2 = parseInt(strkey2.replace(/[^\d]/g, ""), 10)
+
+          , spaces1 = strkey1.replace(/[^\ ]/g, "").length
+          , spaces2 = strkey2.replace(/[^\ ]/g, "").length;
+
+        if (spaces1 == 0 || spaces2 == 0 || numkey1 % spaces1 != 0 || numkey2 % spaces2 != 0) {
+          socket.end();
+          return;
+        }
+
+        var hash = crypto.createHash("md5")
+        , key1 = pack(parseInt(numkey1/spaces1))
+        , key2 = pack(parseInt(numkey2/spaces2));
+        
+        hash.update(key1);
+        hash.update(key2);
+        hash.update(upgradeHead);
+
+        socket.write(nano(handshakeTemplate76, {
+          protocol: wsProtocol,
+          resource: data.get[1],
+          host:     data.host[1],
+          origin:   data.origin[1],
+          data:     hash.digest("binary")
+        }), "binary");
+
+      } else { // 75
+        socket.write(nano(handshakeTemplate75, {
+          protocol: wsProtocol,
+          resource: data.get[1],
+          host:     data.host[1],
+          origin:   data.origin[1]
+        }));
+
+      }
+
+      handshaked = true;
+      emitter.emit("connect", data.get[1]);
+    }
+
+    socket.addListener("data", function (data) {
+      if(handshaked) {
+        handle(data.toString("utf8"));
+      } else {
+        handshake(data.toString("binary")); // because of draft76 handshakes
+      }
+    }).addListener("end", function () {
+      socket.end();
+    }).addListener("close", function () {
+      if (handshaked) { // don't emit close from policy-requests
+        emitter.emit("close");
+      }
+    }).addListener("error", function (exception) {
+      if (emitter.listeners("error").length > 0) {
+        emitter.emit("error", exception);
+      } else {
+        throw exception;
+      }
+    });
+
+    emitter.remoteAddress = socket.remoteAddress;
+    
+    emitter.write = function (data) {
+      try {
+        socket.write('\u0000', 'binary');
+        socket.write(data, 'utf8');
+        socket.write('\uffff', 'binary');
+      } catch(e) { 
+        // Socket not open for writing, 
+        // should get "close" event just before.
+        socket.end();
+      }
+    };
+    
+    emitter.end = function () {
+      socket.end();
+    };
+    
+    websocketListener(emitter); // emits: "connect", "data", "close", provides: write(data), end()
+  });
+};
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/xhr.html b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/xhr.html
new file mode 100644
index 0000000..aaa721c
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/xhr.html
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+   <head>
+      <title>Forge XmlHttpRequest Test</title>
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js"></script>
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
+      <script type="text/javascript" src="forge/debug.js"></script>
+      <script type="text/javascript" src="forge/util.js"></script>
+      <script type="text/javascript" src="forge/log.js"></script>
+      <script type="text/javascript" src="forge/task.js"></script>
+      <script type="text/javascript" src="forge/socket.js"></script>
+      <script type="text/javascript" src="forge/md5.js"></script>
+      <script type="text/javascript" src="forge/sha1.js"></script>
+      <script type="text/javascript" src="forge/hmac.js"></script>
+      <script type="text/javascript" src="forge/aes.js"></script>
+      <script type="text/javascript" src="forge/pem.js"></script>
+      <script type="text/javascript" src="forge/asn1.js"></script>
+      <script type="text/javascript" src="forge/jsbn.js"></script>
+      <script type="text/javascript" src="forge/prng.js"></script>
+      <script type="text/javascript" src="forge/random.js"></script>
+      <script type="text/javascript" src="forge/oids.js"></script>
+      <script type="text/javascript" src="forge/rsa.js"></script>
+      <script type="text/javascript" src="forge/pbe.js"></script>
+      <script type="text/javascript" src="forge/x509.js"></script>
+      <script type="text/javascript" src="forge/pki.js"></script>
+      <script type="text/javascript" src="forge/tls.js"></script>
+      <script type="text/javascript" src="forge/aesCipherSuites.js"></script>
+      <script type="text/javascript" src="forge/tlssocket.js"></script>
+      <script type="text/javascript" src="forge/http.js"></script>
+      <script type="text/javascript" src="forge/xhr.js"></script>
+      <script type="text/javascript" src="xhr.js"></script>
+      
+      <link type="text/css" rel="stylesheet" media="all" href="screen.css" />
+      <style type="text/css">
+         .ready { color: inherit; background: inherit; }
+         .testing { color: black; background: yellow; }
+         .pass{ color: white; background: green; }
+         .fail{ color: white; background: red; }
+      </style>
+   </head>
+<body>
+<div class="nav"><a href="index.html">Forge Tests</a> / XHR</div>
+
+<div class="header">
+   <h1>XmlHttpRequest Tests</h1>
+</div>
+
+<div class="content">
+
+<!-- div used to hold the flash socket pool implemenation -->
+<div id="socketPool">
+   <p>Could not load the flash SocketPool.</p>
+</div>
+
+<fieldset class="section">
+   <ul>
+     <li>Use the controls below to test the XHR wrapper.</li>
+   </ul>
+</fieldset>
+
+<fieldset class="section">
+<legend>Control</legend>
+   <button id="start">Start</button>
+   <button id="reset">Reset</button>
+   <br/>
+   <input id="scroll" type="checkbox" checked="checked" />Scroll Tests
+   <br/>
+   <button id="stress">Stress</button>
+</fieldset>
+
+<fieldset class="section">
+<legend>Progress</legend>
+Status: <span id="status">?</span><br/>
+Pass: <span id="pass">?</span>/<span id="total">?</span><br/>
+Fail: <span id="fail">?</span>
+</fieldset>
+
+<fieldset class="section">
+<legend>Tests</legend>
+<div id="tests"></div>
+</fieldset>
+
+</div>
+</body>
+</html>
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/xhr.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/xhr.js
new file mode 100644
index 0000000..78f91ad
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/node-forge/tests/xhr.js
@@ -0,0 +1,590 @@
+/**
+ * Forge XmlHttpRequest Test
+ *
+ * @author Dave Longley
+ * @author David I. Lehn <dlehn@digitalbazaar.com>
+ *
+ * Copyright (c) 2009-2010 Digital Bazaar, Inc. All rights reserved.
+ */
+(function($)
+{
+   // load flash socket pool
+   window.forge.socketPool = {};
+   window.forge.socketPool.ready = function()
+   {
+      // init forge xhr
+      forge.xhr.init({
+         flashId: 'socketPool',
+         policyPort: 19945,
+         msie: $.browser.msie,
+         connections: 10,
+         caCerts: [],
+         verify: function(c, verified, depth, certs)
+         {
+            // don't care about cert verification for test
+            return true;
+         }
+      });
+   };
+   swfobject.embedSWF(
+      'forge/SocketPool.swf', 'socketPool', '0', '0', '9.0.0',
+      false, {}, {allowscriptaccess: 'always'}, {});
+})(jQuery);
+
+jQuery(function($)
+{
+   var cat = 'forge.tests.xhr';
+
+   var tests = [];
+   var passed = 0;
+   var failed = 0;
+
+   var init = function() {
+      passed = failed = 0;
+      $('.ready,.testing,.pass,.fail')
+         .removeClass('ready testing pass fail');
+      $('#status')
+         .text('Ready.')
+         .addClass('ready');
+      $('#total').text(tests.length);
+      $('#pass').text(passed);
+      $('#fail').text(failed);
+      $('.expect').empty();
+      $('.result').empty();
+      $('.time').empty();
+      $('.timePer').empty();
+      $('#start').removeAttr('disabled');
+   };
+
+   var start = function()
+   {
+      $('#start').attr('disabled', 'disabled');
+      // meta! use tasks to run the task tests
+      forge.task.start({
+         type: 'test',
+         run: function(task) {
+            task.next('starting', function(task) {
+               forge.log.debug(cat, 'start');
+               $('#status')
+                  .text('Testing...')
+                  .addClass('testing')
+                  .removeClass('idle');
+            });
+            $.each(tests, function(i, test) {
+               task.next('test', function(task) {
+                  var title = $('li:first', test.container);
+                  if($('#scroll:checked').length === 1)
+                  {
+                     $('html,body').animate({scrollTop: title.offset().top});
+                  }
+                  title.addClass('testing');
+                  test.run(task, test);
+               });
+               task.next('test', function(task) {
+                  $('li:first', test.container).removeClass('testing');
+               });
+            });
+            task.next('success', function(task) {
+               forge.log.debug(cat, 'done');
+               if(failed === 0) {
+                  $('#status')
+                     .text('PASS')
+                     .addClass('pass')
+                     .removeClass('testing');
+               } else {
+                  // FIXME: should just be hitting failure() below
+                  $('#status')
+                     .text('FAIL')
+                     .addClass('fail')
+                     .removeClass('testing');
+               }
+            });
+         },
+         failure: function() {
+            $('#status')
+               .text('FAIL')
+               .addClass('fail')
+               .removeClass('testing');
+         }
+      });
+   };
+
+   $('#start').click(function() {
+      start();
+   });
+
+   $('#reset').click(function() {
+      init();
+   });
+
+   var stressStats =
+   {
+      sent: 0,
+      success: 0,
+      error: 0
+   };
+   var stressStatsMessage = function()
+   {
+      return 'received:' + (stressStats.success + stressStats.error) + '/' +
+         stressStats.sent + ' errors:' + stressStats.error;
+   };
+
+   $('#stress').click(function() {
+      for(var i = 1; i <= 100; ++i)
+      {
+         (function(seqnum)
+         {
+            setTimeout(function()
+            {
+               $.ajax(
+               {
+                  type: 'GET',
+                  url: '/result.txt?seq=' + seqnum,
+                  beforeSend: function(xhr)
+                  {
+                     ++stressStats.sent;
+                     xhr.setRequestHeader('Connection', 'close');
+                  },
+                  success: function(data, textStatus, xhr)
+                  {
+                     ++stressStats.success;
+                     forge.log.debug(cat, 'xhr connection completed' +
+                        ' seq:' + seqnum +
+                        ' datalen:' + data.length + ' ' +
+                        stressStatsMessage());
+                  },
+                  error: function(xhr, textStatus, errorThrown)
+                  {
+                     ++stressStats.error;
+                     forge.log.error(cat, 'xhr connection failed' +
+                        ' seq:' + seqnum + ' ' +
+                        stressStatsMessage(), arguments);
+                  },
+                  xhr: forge.xhr.create
+               });
+            }, 0);
+         })(i);
+      }
+      return false;
+   });
+
+   /**
+    * Creates a simple XMLHttpRequest wrapper. For testing.
+    */
+   var createWrapper = function()
+   {
+      var UNSENT = 0;
+      var OPENED = 1;
+      var HEADERS_RECEIVED = 2;
+      var LOADING = 3;
+      var DONE = 4;
+
+      var toWrap = new XMLHttpRequest();
+
+      // create xhr wrapper object
+      var xhr =
+      {
+         // FIXME: an EventListener
+         onreadystatechange: null,
+         // FIXME: readonly
+         readyState: UNSENT,
+         // FIXME: a string
+         responseText: null,
+         // FIXME: a document
+         responseXML: null,
+         // FIXME: readonly, returns the HTTP status code
+         status: 0,
+         // FIXME: readonly, returns the HTTP status message
+         statusText: null,
+
+         // FIXME: async, user, and password are optional
+         open: function(method, url, async, user, password)
+         {
+            toWrap.open(method, url, async, user, password);
+         },
+
+         setRequestHeader: function(header, value)
+         {
+            toWrap.setRequestHeader(header, value);
+         },
+
+         // FIXME: data can be a string or a document
+         send: function(data)
+         {
+            toWrap.send(data);
+         },
+
+         abort: function()
+         {
+            toWrap.abort();
+            toWrap.onreadystatechange = null;
+            toWrap = null;
+         },
+
+         // FIXME: return all response headers as a string
+         getAllResponseHeaders: function()
+         {
+            return toWrap.getAllResponseHeaders();
+         },
+
+         // FIXME: return header field value
+         getResponseHeader: function(header)
+         {
+            return toWrap.getResponseHeader(header);
+         }
+      };
+
+      toWrap.onreadystatechange = function()
+      {
+         // copy attributes
+         xhr.readyState = toWrap.readyState;
+         xhr.responseText = toWrap.responseText;
+         xhr.responseXML = toWrap.responseXML;
+
+         if(toWrap.readyState == HEADERS_RECEIVED)
+         {
+            xhr.status = toWrap.status;
+            xhr.statusText = toWrap.statusText;
+         }
+
+         if(xhr.onreadystatechange)
+         {
+            //forge.log.debug(cat, 'wrapper orsc', toWrap);
+            xhr.onreadystatechange();
+         }
+      };
+
+      return xhr;
+   };
+
+   var addTest = function(name, run)
+   {
+      var container = $('<ul><li>Test ' + name + '</li><ul/></ul>');
+      var expect = $('<li>Expect: <span class="expect"/></li>');
+      var result = $('<li>Result: <span class="result"/></li>');
+      var time = $('<li>Time: <span class="time"/></li>');
+      var timePer = $('<li>Time Per Iteration: <span class="timePer"/></li>');
+      $('ul', container)
+         .append(expect)
+         .append(result)
+         .append(time)
+         .append(timePer);
+      $('#tests').append(container);
+      var test = {
+         container: container,
+         startTime: null,
+         run: function(task, test) {
+            test.startTime = new Date();
+            run(task, test);
+         },
+         expect: $('span', expect),
+         result: $('span', result),
+         check: function() {
+            var e = test.expect.text();
+            var r = test.result.text();
+            (e == r) ? test.pass() : test.fail();
+         },
+         pass: function(iterations) {
+            var dt = new Date() - test.startTime;
+            if(!iterations)
+            {
+               iterations = 1;
+            }
+            var dti = (dt / iterations);
+            passed += 1;
+            $('#pass').text(passed);
+            $('li:first', container).addClass('pass');
+            $('span.time', container).html(dt + 'ms');
+            $('span.timePer', container).html(dti + 'ms');
+         },
+         fail: function(iterations) {
+            var dt = new Date() - test.startTime;
+            if(!iterations)
+            {
+               iterations = 1;
+            }
+            var dti = (dt / iterations);
+            failed += 1;
+            $('#fail').text(failed);
+            $('li:first', container).addClass('fail');
+            $('span.time', container).html(dt + 'ms');
+            $('span.timePer', container).html(dti + 'ms');
+         }
+      };
+      tests.push(test);
+   };
+
+   addTest('builtin xhr', function(task, test)
+   {
+      task.block();
+
+      $.ajax(
+      {
+         type: 'GET',
+         url: '/result.txt',
+         success: function(data)
+         {
+            test.expect.html('expected result');
+            test.result.html(data);
+            task.unblock();
+         },
+         error: function()
+         {
+            task.fail();
+         }
+      });
+
+      task.next(function(task)
+      {
+         test.pass();
+      });
+   });
+
+   addTest('builtin xhr (10 serial)', function(task, test)
+   {
+      var N = 10;
+      for(var i = 0; i < N; i++)
+      {
+         task.next(function(task)
+         {
+            task.parent.block();
+
+            $.ajax(
+            {
+               type: 'GET',
+               url: '/result.txt',
+               success: function(data, textStatus)
+               {
+                  test.result.append('.');
+                  task.parent.unblock();
+               },
+               error: function(xhr, textStatus, errorThrown)
+               {
+                  task.fail(N);
+               }
+            });
+         });
+      }
+
+      task.next(function(task)
+      {
+         test.pass(N);
+      });
+   });
+
+   addTest('builtin xhr (10 parallel)', function(task, test)
+   {
+      var N = 10;
+      task.block(N);
+      for(var i = 0; i < N; i++)
+      {
+         $.ajax(
+         {
+            type: 'GET',
+            url: '/result.txt',
+            success: function(data, textStatus)
+            {
+               test.result.append('.');
+               task.unblock();
+            },
+            error: function(xhr, textStatus, errorThrown)
+            {
+               task.fail(N);
+            }
+         });
+      }
+
+      task.next(function(task)
+      {
+         test.pass(N);
+      });
+   });
+
+   // test only works with non-IE
+   if(!$.browser.msie)
+   {
+      addTest('generic wrapper xhr', function(task, test)
+      {
+         task.block();
+
+         $.ajax(
+         {
+            type: 'GET',
+            url: '/result.txt',
+            success: function(data)
+            {
+               test.expect.html('expected result');
+               test.result.html(data);
+               task.unblock();
+            },
+            error: function()
+            {
+               task.fail();
+            },
+            xhr: createWrapper
+         });
+
+         task.next(function(task)
+         {
+            test.pass();
+         });
+      });
+
+      addTest('generic wrapper xhr (10 serial)', function(task, test)
+      {
+         var N = 10;
+         for(var i = 0; i < N; i++)
+         {
+            task.next(function(task)
+            {
+               task.parent.block();
+
+               $.ajax(
+               {
+                  type: 'GET',
+                  url: '/result.txt',
+                  success: function(data, textStatus)
+                  {
+                     test.result.append('.');
+                     task.parent.unblock();
+                  },
+                  error: function(xhr, textStatus, errorThrown)
+                  {
+                     task.fail(N);
+                  },
+                  xhr: createWrapper
+               });
+            });
+         }
+
+         task.next(function(task)
+         {
+            test.pass(N);
+         });
+      });
+
+      addTest('generic wrapper xhr (10 parallel)', function(task, test)
+      {
+         var N = 10;
+         task.block(N);
+         for(var i = 0; i < N; i++)
+         {
+            $.ajax(
+            {
+               type: 'GET',
+               url: '/result.txt',
+               success: function(data, textStatus)
+               {
+                  test.result.append('.');
+                  task.unblock();
+               },
+               error: function(xhr, textStatus, errorThrown)
+               {
+                  task.fail(N);
+               },
+               xhr: createWrapper
+            });
+         }
+
+         task.next(function(task)
+         {
+            test.pass(N);
+         });
+      });
+   }
+
+   for(var i = 0; i < 3; i++) {
+   addTest('TLS xhr ' + i, function(task, test)
+   {
+      task.block();
+
+      $.ajax(
+      {
+         type: 'GET',
+         url: '/result.txt',
+         success: function(data, textStatus, xhr)
+         {
+            test.expect.html('expected result');
+            test.result.html(data);
+            task.unblock();
+         },
+         error: function(xhr, textStatus, errorThrown)
+         {
+            task.fail();
+         },
+         xhr: forge.xhr.create
+      });
+
+      task.next(function(task)
+      {
+         test.pass();
+      });
+   });
+   }
+
+   addTest('TLS xhr (10 serial)', function(task, test)
+   {
+      var N = 10;
+      for(var i = 0; i < N; i++)
+      {
+         task.next(function(task)
+         {
+            task.parent.block();
+
+            $.ajax(
+            {
+               type: 'GET',
+               url: '/result.txt',
+               success: function(data, textStatus, xhr)
+               {
+                  test.result.append('.');
+                  task.parent.unblock();
+               },
+               error: function(xhr, textStatus, errorThrown)
+               {
+                  task.fail(N);
+               },
+               xhr: forge.xhr.create
+            });
+         });
+      }
+
+      task.next(function(task)
+      {
+         test.pass(N);
+      });
+   });
+
+   addTest('TLS xhr (10 parallel) ' +
+      '(hit "Reset" then "Start" to speed up - uses SSL session cache)',
+      function(task, test)
+   {
+      var N = 10;
+      task.block(N);
+      for(var i = 0; i < N; i++)
+      {
+         $.ajax(
+         {
+            type: 'GET',
+            url: '/result.txt',
+            success: function(data, textStatus, xhr)
+            {
+               test.result.append('.');
+               task.unblock();
+            },
+            error: function(xhr, textStatus, errorThrown)
+            {
+               task.fail(N);
+            },
+            xhr: forge.xhr.create
+         });
+      }
+
+      task.next(function(task)
+      {
+         test.pass(N);
+      });
+   });
+
+   init();
+});
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/.npmignore b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/.npmignore
new file mode 100644
index 0000000..13abef4
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/.npmignore
@@ -0,0 +1,3 @@
+node_modules
+node_modules/*
+npm_debug.log
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/.travis.yml b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/.travis.yml
new file mode 100644
index 0000000..dad2273
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/.travis.yml
@@ -0,0 +1,4 @@
+language: node_js
+node_js:
+  - 0.8
+  - "0.10"
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/LICENCE b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/LICENCE
new file mode 100644
index 0000000..171dd97
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/LICENCE
@@ -0,0 +1,22 @@
+Copyright (c) 2011 Dominic Tarr
+
+Permission is hereby granted, free of charge, 
+to any person obtaining a copy of this software and 
+associated documentation files (the "Software"), to 
+deal in the Software without restriction, including 
+without limitation the rights to use, copy, modify, 
+merge, publish, distribute, sublicense, and/or sell 
+copies of the Software, and to permit persons to whom 
+the Software is furnished to do so, 
+subject to the following conditions:
+
+The above copyright notice and this permission notice 
+shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 
+ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/examples/pretty.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/examples/pretty.js
new file mode 100644
index 0000000..2e89131
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/examples/pretty.js
@@ -0,0 +1,26 @@
+
+var inspect = require('util').inspect
+var es      = require('event-stream')     //load event-stream
+var split   = require('../')
+
+if(!module.parent) {
+  es.pipe(                            //pipe joins streams together
+    process.openStdin(),              //open stdin
+    split(),                       //split stream to break on newlines
+    es.map(function (data, callback) {//turn this async function into a stream
+      var j 
+      try {
+        j = JSON.parse(data)          //try to parse input into json
+      } catch (err) {
+        return callback(null, data)   //if it fails just pass it anyway
+      }
+      callback(null, inspect(j))      //render it nicely
+    }),
+    process.stdout                    // pipe it to stdout !
+    )
+  }
+  
+// run this
+// 
+// curl -sS registry.npmjs.org/event-stream | node pretty.js 
+//
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/index.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/index.js
new file mode 100644
index 0000000..ca57e0f
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/index.js
@@ -0,0 +1,59 @@
+//filter will reemit the data if cb(err,pass) pass is truthy
+
+// reduce is more tricky
+// maybe we want to group the reductions or emit progress updates occasionally
+// the most basic reduce just emits one 'data' event after it has recieved 'end'
+
+
+var through = require('through')
+var Decoder = require('string_decoder').StringDecoder
+
+module.exports = split
+
+//TODO pass in a function to map across the lines.
+
+function split (matcher, mapper) {
+  var decoder = new Decoder()
+  var soFar = ''
+  if('function' === typeof matcher)
+    mapper = matcher, matcher = null
+  if (!matcher)
+    matcher = /\r?\n/
+
+  function emit(stream, piece) {
+    if(mapper) {
+      try {
+        piece = mapper(piece)
+      }
+      catch (err) {
+        return stream.emit('error', err)
+      }
+      if('undefined' !== typeof piece)
+        stream.queue(piece)
+    }
+    else
+      stream.queue(piece)
+  }
+
+  function next (stream, buffer) { 
+    var pieces = (soFar + buffer).split(matcher)
+    soFar = pieces.pop()
+
+    for (var i = 0; i < pieces.length; i++) {
+      var piece = pieces[i]
+      emit(stream, piece)
+    }
+  }
+
+  return through(function (b) {
+    next(this, decoder.write(b))
+  },
+  function () {
+    if(decoder.end) 
+      next(this, decoder.end())
+    if(soFar != null)
+      emit(this, soFar)
+    this.queue(null)
+  })
+}
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/node_modules/through/.travis.yml b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/node_modules/through/.travis.yml
new file mode 100644
index 0000000..c693a93
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/node_modules/through/.travis.yml
@@ -0,0 +1,5 @@
+language: node_js
+node_js:
+  - 0.6
+  - 0.8
+  - "0.10"
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/node_modules/through/LICENSE.APACHE2 b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/node_modules/through/LICENSE.APACHE2
new file mode 100644
index 0000000..6366c04
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/node_modules/through/LICENSE.APACHE2
@@ -0,0 +1,15 @@
+Apache License, Version 2.0
+
+Copyright (c) 2011 Dominic Tarr
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/node_modules/through/LICENSE.MIT b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/node_modules/through/LICENSE.MIT
new file mode 100644
index 0000000..6eafbd7
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/node_modules/through/LICENSE.MIT
@@ -0,0 +1,24 @@
+The MIT License
+
+Copyright (c) 2011 Dominic Tarr
+
+Permission is hereby granted, free of charge, 
+to any person obtaining a copy of this software and 
+associated documentation files (the "Software"), to 
+deal in the Software without restriction, including 
+without limitation the rights to use, copy, modify, 
+merge, publish, distribute, sublicense, and/or sell 
+copies of the Software, and to permit persons to whom 
+the Software is furnished to do so, 
+subject to the following conditions:
+
+The above copyright notice and this permission notice 
+shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 
+ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/node_modules/through/index.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/node_modules/through/index.js
new file mode 100644
index 0000000..7b935bf
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/node_modules/through/index.js
@@ -0,0 +1,108 @@
+var Stream = require('stream')
+
+// through
+//
+// a stream that does nothing but re-emit the input.
+// useful for aggregating a series of changing but not ending streams into one stream)
+
+exports = module.exports = through
+through.through = through
+
+//create a readable writable stream.
+
+function through (write, end, opts) {
+  write = write || function (data) { this.queue(data) }
+  end = end || function () { this.queue(null) }
+
+  var ended = false, destroyed = false, buffer = [], _ended = false
+  var stream = new Stream()
+  stream.readable = stream.writable = true
+  stream.paused = false
+
+//  stream.autoPause   = !(opts && opts.autoPause   === false)
+  stream.autoDestroy = !(opts && opts.autoDestroy === false)
+
+  stream.write = function (data) {
+    write.call(this, data)
+    return !stream.paused
+  }
+
+  function drain() {
+    while(buffer.length && !stream.paused) {
+      var data = buffer.shift()
+      if(null === data)
+        return stream.emit('end')
+      else
+        stream.emit('data', data)
+    }
+  }
+
+  stream.queue = stream.push = function (data) {
+//    console.error(ended)
+    if(_ended) return stream
+    if(data == null) _ended = true
+    buffer.push(data)
+    drain()
+    return stream
+  }
+
+  //this will be registered as the first 'end' listener
+  //must call destroy next tick, to make sure we're after any
+  //stream piped from here.
+  //this is only a problem if end is not emitted synchronously.
+  //a nicer way to do this is to make sure this is the last listener for 'end'
+
+  stream.on('end', function () {
+    stream.readable = false
+    if(!stream.writable && stream.autoDestroy)
+      process.nextTick(function () {
+        stream.destroy()
+      })
+  })
+
+  function _end () {
+    stream.writable = false
+    end.call(stream)
+    if(!stream.readable && stream.autoDestroy)
+      stream.destroy()
+  }
+
+  stream.end = function (data) {
+    if(ended) return
+    ended = true
+    if(arguments.length) stream.write(data)
+    _end() // will emit or queue
+    return stream
+  }
+
+  stream.destroy = function () {
+    if(destroyed) return
+    destroyed = true
+    ended = true
+    buffer.length = 0
+    stream.writable = stream.readable = false
+    stream.emit('close')
+    return stream
+  }
+
+  stream.pause = function () {
+    if(stream.paused) return
+    stream.paused = true
+    return stream
+  }
+
+  stream.resume = function () {
+    if(stream.paused) {
+      stream.paused = false
+      stream.emit('resume')
+    }
+    drain()
+    //may have become paused again,
+    //as drain emits 'data'.
+    if(!stream.paused)
+      stream.emit('drain')
+    return stream
+  }
+  return stream
+}
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/node_modules/through/package.json b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/node_modules/through/package.json
new file mode 100644
index 0000000..fe9fbdb
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/node_modules/through/package.json
@@ -0,0 +1,65 @@
+{
+  "name": "through",
+  "version": "2.3.6",
+  "description": "simplified stream construction",
+  "main": "index.js",
+  "scripts": {
+    "test": "set -e; for t in test/*.js; do node $t; done"
+  },
+  "devDependencies": {
+    "stream-spec": "~0.3.5",
+    "tape": "~2.3.2",
+    "from": "~0.1.3"
+  },
+  "keywords": [
+    "stream",
+    "streams",
+    "user-streams",
+    "pipe"
+  ],
+  "author": {
+    "name": "Dominic Tarr",
+    "email": "dominic.tarr@gmail.com",
+    "url": "dominictarr.com"
+  },
+  "license": "MIT",
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/dominictarr/through.git"
+  },
+  "homepage": "http://github.com/dominictarr/through",
+  "testling": {
+    "browsers": [
+      "ie/8..latest",
+      "ff/15..latest",
+      "chrome/20..latest",
+      "safari/5.1..latest"
+    ],
+    "files": "test/*.js"
+  },
+  "gitHead": "19ed9b7e84efe7c3e3c8be80f29390b1620e13c0",
+  "bugs": {
+    "url": "https://github.com/dominictarr/through/issues"
+  },
+  "_id": "through@2.3.6",
+  "_shasum": "26681c0f524671021d4e29df7c36bce2d0ecf2e8",
+  "_from": "through@2",
+  "_npmVersion": "1.4.26",
+  "_npmUser": {
+    "name": "dominictarr",
+    "email": "dominic.tarr@gmail.com"
+  },
+  "maintainers": [
+    {
+      "name": "dominictarr",
+      "email": "dominic.tarr@gmail.com"
+    }
+  ],
+  "dist": {
+    "shasum": "26681c0f524671021d4e29df7c36bce2d0ecf2e8",
+    "tarball": "http://registry.npmjs.org/through/-/through-2.3.6.tgz"
+  },
+  "directories": {},
+  "_resolved": "https://registry.npmjs.org/through/-/through-2.3.6.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/node_modules/through/readme.markdown b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/node_modules/through/readme.markdown
new file mode 100644
index 0000000..cb34c81
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/node_modules/through/readme.markdown
@@ -0,0 +1,64 @@
+#through
+
+[![build status](https://secure.travis-ci.org/dominictarr/through.png)](http://travis-ci.org/dominictarr/through)
+[![testling badge](https://ci.testling.com/dominictarr/through.png)](https://ci.testling.com/dominictarr/through)
+
+Easy way to create a `Stream` that is both `readable` and `writable`. 
+
+* Pass in optional `write` and `end` methods.
+* `through` takes care of pause/resume logic if you use `this.queue(data)` instead of `this.emit('data', data)`.
+* Use `this.pause()` and `this.resume()` to manage flow.
+* Check `this.paused` to see current flow state. (`write` always returns `!this.paused`).
+
+This function is the basis for most of the synchronous streams in 
+[event-stream](http://github.com/dominictarr/event-stream).
+
+``` js
+var through = require('through')
+
+through(function write(data) {
+    this.queue(data) //data *must* not be null
+  },
+  function end () { //optional
+    this.queue(null)
+  })
+```
+
+Or, can also be used _without_ buffering on pause, use `this.emit('data', data)`,
+and this.emit('end')
+
+``` js
+var through = require('through')
+
+through(function write(data) {
+    this.emit('data', data)
+    //this.pause() 
+  },
+  function end () { //optional
+    this.emit('end')
+  })
+```
+
+## Extended Options
+
+You will probably not need these 99% of the time.
+
+### autoDestroy=false
+
+By default, `through` emits close when the writable
+and readable side of the stream has ended.
+If that is not desired, set `autoDestroy=false`.
+
+``` js
+var through = require('through')
+
+//like this
+var ts = through(write, end, {autoDestroy: false})
+//or like this
+var ts = through(write, end)
+ts.autoDestroy = false
+```
+
+## License
+
+MIT / Apache2
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/node_modules/through/test/async.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/node_modules/through/test/async.js
new file mode 100644
index 0000000..46bdbae
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/node_modules/through/test/async.js
@@ -0,0 +1,28 @@
+var from = require('from')
+var through = require('../')
+
+var tape = require('tape')
+
+tape('simple async example', function (t) {
+ 
+  var n = 0, expected = [1,2,3,4,5], actual = []
+  from(expected)
+  .pipe(through(function(data) {
+    this.pause()
+    n ++
+    setTimeout(function(){
+      console.log('pushing data', data)
+      this.push(data)
+      this.resume()
+    }.bind(this), 300)
+  })).pipe(through(function(data) {
+    console.log('pushing data second time', data);
+    this.push(data)
+  })).on('data', function (d) {
+    actual.push(d)
+  }).on('end', function() {
+    t.deepEqual(actual, expected)
+    t.end()
+  })
+
+})
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/node_modules/through/test/auto-destroy.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/node_modules/through/test/auto-destroy.js
new file mode 100644
index 0000000..9a8fd00
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/node_modules/through/test/auto-destroy.js
@@ -0,0 +1,30 @@
+var test = require('tape')
+var through = require('../')
+
+// must emit end before close.
+
+test('end before close', function (assert) {
+  var ts = through()
+  ts.autoDestroy = false
+  var ended = false, closed = false
+
+  ts.on('end', function () {
+    assert.ok(!closed)
+    ended = true
+  })
+  ts.on('close', function () {
+    assert.ok(ended)
+    closed = true
+  })
+
+  ts.write(1)
+  ts.write(2)
+  ts.write(3)
+  ts.end()
+  assert.ok(ended)
+  assert.notOk(closed)
+  ts.destroy()
+  assert.ok(closed)
+  assert.end()
+})
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/node_modules/through/test/buffering.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/node_modules/through/test/buffering.js
new file mode 100644
index 0000000..b0084bf
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/node_modules/through/test/buffering.js
@@ -0,0 +1,71 @@
+var test = require('tape')
+var through = require('../')
+
+// must emit end before close.
+
+test('buffering', function(assert) {
+  var ts = through(function (data) {
+    this.queue(data)
+  }, function () {
+    this.queue(null)
+  })
+
+  var ended = false,  actual = []
+
+  ts.on('data', actual.push.bind(actual))
+  ts.on('end', function () {
+    ended = true
+  })
+
+  ts.write(1)
+  ts.write(2)
+  ts.write(3)
+  assert.deepEqual(actual, [1, 2, 3])
+  ts.pause()
+  ts.write(4)
+  ts.write(5)
+  ts.write(6)
+  assert.deepEqual(actual, [1, 2, 3])
+  ts.resume()
+  assert.deepEqual(actual, [1, 2, 3, 4, 5, 6])
+  ts.pause()
+  ts.end()
+  assert.ok(!ended)
+  ts.resume()
+  assert.ok(ended)
+  assert.end()
+})
+
+test('buffering has data in queue, when ends', function (assert) {
+
+  /*
+   * If stream ends while paused with data in the queue,
+   * stream should still emit end after all data is written
+   * on resume.
+   */
+
+  var ts = through(function (data) {
+    this.queue(data)
+  }, function () {
+    this.queue(null)
+  })
+
+  var ended = false,  actual = []
+
+  ts.on('data', actual.push.bind(actual))
+  ts.on('end', function () {
+    ended = true
+  })
+
+  ts.pause()
+  ts.write(1)
+  ts.write(2)
+  ts.write(3)
+  ts.end()
+  assert.deepEqual(actual, [], 'no data written yet, still paused')
+  assert.ok(!ended, 'end not emitted yet, still paused')
+  ts.resume()
+  assert.deepEqual(actual, [1, 2, 3], 'resumed, all data should be delivered')
+  assert.ok(ended, 'end should be emitted once all data was delivered')
+  assert.end();
+})
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/node_modules/through/test/end.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/node_modules/through/test/end.js
new file mode 100644
index 0000000..fa113f5
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/node_modules/through/test/end.js
@@ -0,0 +1,45 @@
+var test = require('tape')
+var through = require('../')
+
+// must emit end before close.
+
+test('end before close', function (assert) {
+  var ts = through()
+  var ended = false, closed = false
+
+  ts.on('end', function () {
+    assert.ok(!closed)
+    ended = true
+  })
+  ts.on('close', function () {
+    assert.ok(ended)
+    closed = true
+  })
+
+  ts.write(1)
+  ts.write(2)
+  ts.write(3)
+  ts.end()
+  assert.ok(ended)
+  assert.ok(closed)
+  assert.end()
+})
+
+test('end only once', function (t) {
+
+  var ts = through()
+  var ended = false, closed = false
+
+  ts.on('end', function () {
+    t.equal(ended, false)
+    ended = true
+  })
+
+  ts.queue(null)
+  ts.queue(null)
+  ts.queue(null)
+
+  ts.resume()
+
+  t.end()
+})
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/node_modules/through/test/index.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/node_modules/through/test/index.js
new file mode 100644
index 0000000..8d1517e
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/node_modules/through/test/index.js
@@ -0,0 +1,117 @@
+
+var test = require('tape')
+var spec = require('stream-spec')
+var through = require('../')
+
+/*
+  I'm using these two functions, and not streams and pipe
+  so there is less to break. if this test fails it must be
+  the implementation of _through_
+*/
+
+function write(array, stream) {
+  array = array.slice()
+  function next() {
+    while(array.length)
+      if(stream.write(array.shift()) === false)
+        return stream.once('drain', next)
+    
+    stream.end()
+  }
+
+  next()
+}
+
+function read(stream, callback) {
+  var actual = []
+  stream.on('data', function (data) {
+    actual.push(data)
+  })
+  stream.once('end', function () {
+    callback(null, actual)
+  })
+  stream.once('error', function (err) {
+    callback(err)
+  })
+}
+
+test('simple defaults', function(assert) {
+
+  var l = 1000
+    , expected = []
+
+  while(l--) expected.push(l * Math.random())
+
+  var t = through()
+  var s = spec(t).through().pausable()
+
+  read(t, function (err, actual) {
+    assert.ifError(err)
+    assert.deepEqual(actual, expected)
+    assert.end()
+  })
+
+  t.on('close', s.validate)
+
+  write(expected, t)
+});
+
+test('simple functions', function(assert) {
+
+  var l = 1000
+    , expected = [] 
+
+  while(l--) expected.push(l * Math.random())
+
+  var t = through(function (data) {
+      this.emit('data', data*2)
+    }) 
+  var s = spec(t).through().pausable()
+      
+
+  read(t, function (err, actual) {
+    assert.ifError(err)
+    assert.deepEqual(actual, expected.map(function (data) {
+      return data*2
+    }))
+    assert.end()
+  })
+
+  t.on('close', s.validate)
+
+  write(expected, t)
+})
+
+test('pauses', function(assert) {
+
+  var l = 1000
+    , expected = [] 
+
+  while(l--) expected.push(l) //Math.random())
+
+  var t = through()    
+ 
+  var s = spec(t)
+      .through()
+      .pausable()
+
+  t.on('data', function () {
+    if(Math.random() > 0.1) return
+    t.pause()
+    process.nextTick(function () {
+      t.resume()
+    })
+  })
+
+  read(t, function (err, actual) {
+    assert.ifError(err)
+    assert.deepEqual(actual, expected)
+  })
+
+  t.on('close', function () {
+    s.validate()
+    assert.end()
+  })
+
+  write(expected, t)
+})
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/package.json b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/package.json
new file mode 100644
index 0000000..c726916
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/package.json
@@ -0,0 +1,39 @@
+{
+  "name": "split",
+  "version": "0.2.10",
+  "description": "split a Text Stream into a Line Stream",
+  "homepage": "http://github.com/dominictarr/split",
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/dominictarr/split.git"
+  },
+  "dependencies": {
+    "through": "2"
+  },
+  "devDependencies": {
+    "asynct": "*",
+    "it-is": "1",
+    "ubelt": "~2.9",
+    "stream-spec": "~0.2",
+    "event-stream": "~3.0.2"
+  },
+  "scripts": {
+    "test": "asynct test/"
+  },
+  "author": {
+    "name": "Dominic Tarr",
+    "email": "dominic.tarr@gmail.com",
+    "url": "http://bit.ly/dominictarr"
+  },
+  "optionalDependencies": {},
+  "engines": {
+    "node": "*"
+  },
+  "readme": "# Split (matcher)\n\n[![build status](https://secure.travis-ci.org/dominictarr/split.png)](http://travis-ci.org/dominictarr/split)\n\nBreak up a stream and reassemble it so that each line is a chunk. matcher may be a `String`, or a `RegExp` \n\nExample, read every line in a file ...\n\n``` js\n  fs.createReadStream(file)\n    .pipe(split())\n    .on('data', function (line) {\n      //each chunk now is a seperate line!\n    })\n\n```\n\n`split` takes the same arguments as `string.split` except it defaults to '/\\r?\\n/' instead of ',', and the optional `limit` paremeter is ignored.\n[String#split](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/split)\n\n# NDJ - Newline Delimited Json\n\n`split` accepts a function which transforms each line.\n\n``` js\nfs.createReadStream(file)\n  .pipe(split(JSON.parse))\n  .on('data', function (obj) {\n    //each chunk now is a a js object\n  })\n  .on('error', function (err) {\n    //syntax errors will land here\n    //note, this ends the stream.\n  })\n```\n\n# License\n\nMIT\n",
+  "readmeFilename": "readme.markdown",
+  "bugs": {
+    "url": "https://github.com/dominictarr/split/issues"
+  },
+  "_id": "split@0.2.10",
+  "_from": "split@~0.2.10"
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/readme.markdown b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/readme.markdown
new file mode 100644
index 0000000..55b8ba8
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/readme.markdown
@@ -0,0 +1,39 @@
+# Split (matcher)
+
+[![build status](https://secure.travis-ci.org/dominictarr/split.png)](http://travis-ci.org/dominictarr/split)
+
+Break up a stream and reassemble it so that each line is a chunk. matcher may be a `String`, or a `RegExp` 
+
+Example, read every line in a file ...
+
+``` js
+  fs.createReadStream(file)
+    .pipe(split())
+    .on('data', function (line) {
+      //each chunk now is a seperate line!
+    })
+
+```
+
+`split` takes the same arguments as `string.split` except it defaults to '/\r?\n/' instead of ',', and the optional `limit` paremeter is ignored.
+[String#split](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/split)
+
+# NDJ - Newline Delimited Json
+
+`split` accepts a function which transforms each line.
+
+``` js
+fs.createReadStream(file)
+  .pipe(split(JSON.parse))
+  .on('data', function (obj) {
+    //each chunk now is a a js object
+  })
+  .on('error', function (err) {
+    //syntax errors will land here
+    //note, this ends the stream.
+  })
+```
+
+# License
+
+MIT
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/test/partitioned_unicode.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/test/partitioned_unicode.js
new file mode 100644
index 0000000..aff3d5d
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/test/partitioned_unicode.js
@@ -0,0 +1,34 @@
+var it = require('it-is').style('colour')
+  , split = require('..')
+
+exports ['split data with partitioned unicode character'] = function (test) {
+  var s = split(/,/g)
+    , caughtError = false
+    , rows = []
+
+  s.on('error', function (err) {
+    caughtError = true
+  })
+ 
+  s.on('data', function (row) { rows.push(row) })
+
+  var x = 'テスト試験今日とても,よい天気で'
+  unicodeData = new Buffer(x);
+
+  // partition of 日
+  piece1 = unicodeData.slice(0, 20);
+  piece2 = unicodeData.slice(20, unicodeData.length);
+
+  s.write(piece1);
+  s.write(piece2);
+
+  s.end()
+
+  it(caughtError).equal(false)
+
+  it(rows).deepEqual(['テスト試験今日とても', 'よい天気で']);
+
+  it(rows).deepEqual(x.split(','))
+
+  test.done()
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/test/split.asynct.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/test/split.asynct.js
new file mode 100644
index 0000000..fb15b28
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/test/split.asynct.js
@@ -0,0 +1,85 @@
+var es = require('event-stream')
+  , it = require('it-is').style('colour')
+  , d = require('ubelt')
+  , split = require('..')
+  , join = require('path').join
+  , fs = require('fs')
+  , Stream = require('stream').Stream
+  , spec = require('stream-spec')
+
+exports ['split() works like String#split'] = function (test) {
+  var readme = join(__filename)
+    , expected = fs.readFileSync(readme, 'utf-8').split('\n')
+    , cs = split()
+    , actual = []
+    , ended = false
+    , x = spec(cs).through()
+
+  var a = new Stream ()
+  
+  a.write = function (l) {
+    actual.push(l.trim())
+  }
+  a.end = function () {
+
+      ended = true
+      expected.forEach(function (v,k) {
+        //String.split will append an empty string ''
+        //if the string ends in a split pattern.
+        //es.split doesn't which was breaking this test.
+        //clearly, appending the empty string is correct.
+        //tests are passing though. which is the current job.
+        if(v)
+          it(actual[k]).like(v)
+      })
+      //give the stream time to close
+      process.nextTick(function () {
+        test.done()
+        x.validate()
+      })
+  }
+  a.writable = true
+  
+  fs.createReadStream(readme, {flags: 'r'}).pipe(cs)
+  cs.pipe(a) 
+  
+}
+
+exports ['split() takes mapper function'] = function (test) {
+  var readme = join(__filename)
+    , expected = fs.readFileSync(readme, 'utf-8').split('\n')
+    , cs = split(function (line) { return line.toUpperCase() })
+    , actual = []
+    , ended = false
+    , x = spec(cs).through()
+
+  var a = new Stream ()
+  
+  a.write = function (l) {
+    actual.push(l.trim())
+  }
+  a.end = function () {
+
+      ended = true
+      expected.forEach(function (v,k) {
+        //String.split will append an empty string ''
+        //if the string ends in a split pattern.
+        //es.split doesn't which was breaking this test.
+        //clearly, appending the empty string is correct.
+        //tests are passing though. which is the current job.
+        if(v)
+          it(actual[k]).equal(v.trim().toUpperCase())
+      })
+      //give the stream time to close
+      process.nextTick(function () {
+        test.done()
+        x.validate()
+      })
+  }
+  a.writable = true
+  
+  fs.createReadStream(readme, {flags: 'r'}).pipe(cs)
+  cs.pipe(a) 
+  
+}
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/test/try_catch.asynct.js b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/test/try_catch.asynct.js
new file mode 100644
index 0000000..39e49f7
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/node_modules/split/test/try_catch.asynct.js
@@ -0,0 +1,51 @@
+var it = require('it-is').style('colour')
+  , split = require('..')
+
+exports ['emit mapper exceptions as error events'] = function (test) {
+  var s = split(JSON.parse)
+    , caughtError = false
+    , rows = []
+ 
+  s.on('error', function (err) {
+    caughtError = true
+  })
+ 
+  s.on('data', function (row) { rows.push(row) })
+
+  s.write('{"a":1}\n{"')
+  it(caughtError).equal(false)
+  it(rows).deepEqual([ { a: 1 } ])
+
+  s.write('b":2}\n{"c":}\n')
+  it(caughtError).equal(true)
+  it(rows).deepEqual([ { a: 1 }, { b: 2 } ])
+
+  s.end()
+  test.done()
+}
+
+exports ['mapper error events on trailing chunks'] = function (test) {
+  var s = split(JSON.parse)
+    , caughtError = false
+    , rows = []
+ 
+  s.on('error', function (err) {
+    caughtError = true
+  })
+ 
+  s.on('data', function (row) { rows.push(row) })
+
+  s.write('{"a":1}\n{"')
+  it(caughtError).equal(false)
+  it(rows).deepEqual([ { a: 1 } ])
+
+  s.write('b":2}\n{"c":}')
+  it(caughtError).equal(false)
+  it(rows).deepEqual([ { a: 1 }, { b: 2 } ])
+
+  s.end()
+  it(caughtError).equal(true)
+  it(rows).deepEqual([ { a: 1 }, { b: 2 } ])
+
+  test.done()
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/adbkit/package.json b/node_modules/node-firefox-find-ports/node_modules/adbkit/package.json
new file mode 100644
index 0000000..275ef4e
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/adbkit/package.json
@@ -0,0 +1,88 @@
+{
+  "name": "adbkit",
+  "version": "2.1.6",
+  "description": "A pure Node.js client for the Android Debug Bridge.",
+  "keywords": [
+    "adb",
+    "adbkit",
+    "android",
+    "logcat",
+    "monkey"
+  ],
+  "bugs": {
+    "url": "https://github.com/CyberAgent/adbkit/issues"
+  },
+  "license": "Apache-2.0",
+  "author": {
+    "name": "CyberAgent, Inc.",
+    "email": "npm@cyberagent.co.jp",
+    "url": "http://www.cyberagent.co.jp/"
+  },
+  "main": "./index",
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/CyberAgent/adbkit.git"
+  },
+  "scripts": {
+    "postpublish": "grunt clean",
+    "prepublish": "grunt test coffee",
+    "test": "grunt test"
+  },
+  "dependencies": {
+    "adbkit-logcat": "~1.0.3",
+    "adbkit-monkey": "~1.0.1",
+    "bluebird": "~1.1.0",
+    "commander": "^2.3.0",
+    "debug": "~0.7.4",
+    "node-forge": "^0.6.12",
+    "split": "~0.2.10"
+  },
+  "devDependencies": {
+    "bench": "~0.3.5",
+    "chai": "~1.9.0",
+    "coffee-script": "~1.7.1",
+    "grunt": "~0.4.2",
+    "grunt-cli": "~0.1.13",
+    "grunt-coffeelint": "0.0.8",
+    "grunt-contrib-clean": "~0.5.0",
+    "grunt-contrib-coffee": "~0.10.0",
+    "grunt-contrib-watch": "~0.5.3",
+    "grunt-exec": "~0.4.3",
+    "grunt-jsonlint": "~1.0.4",
+    "grunt-notify": "~0.2.16",
+    "mocha": "~1.17.1",
+    "sinon": "~1.8.1",
+    "sinon-chai": "~2.5.0",
+    "coffeelint": "~1.0.8"
+  },
+  "engines": {
+    "node": ">= 0.10.4"
+  },
+  "gitHead": "c74e6a5617fb046cfa93708e5b2be0289cde20e6",
+  "homepage": "https://github.com/CyberAgent/adbkit",
+  "_id": "adbkit@2.1.6",
+  "_shasum": "10f016de49483c255b549a1d28aec79882178f2a",
+  "_from": "adbkit@^2.1.6",
+  "_npmVersion": "1.4.28",
+  "_npmUser": {
+    "name": "sorccu",
+    "email": "simo@shoqolate.com"
+  },
+  "maintainers": [
+    {
+      "name": "sorccu",
+      "email": "simo@shoqolate.com"
+    },
+    {
+      "name": "cyberagent",
+      "email": "npm@cyberagent.co.jp"
+    }
+  ],
+  "dist": {
+    "shasum": "10f016de49483c255b549a1d28aec79882178f2a",
+    "tarball": "http://registry.npmjs.org/adbkit/-/adbkit-2.1.6.tgz"
+  },
+  "directories": {},
+  "_resolved": "https://registry.npmjs.org/adbkit/-/adbkit-2.1.6.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/README.md b/node_modules/node-firefox-find-ports/node_modules/firefox-client/README.md
new file mode 100644
index 0000000..c6aefea
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/README.md
@@ -0,0 +1,157 @@
+# firefox-client
+`firefox-client` is a [node](nodejs.org) library for remote debugging Firefox. You can use it to make things like [fxconsole](https://github.com/harthur/fxconsole), a remote JavaScript REPL.
+
+```javascript
+var FirefoxClient = require("firefox-client");
+
+var client = new FirefoxClient();
+
+client.connect(6000, function() {
+  client.listTabs(function(err, tabs) {
+    console.log("first tab:", tabs[0].url);
+  });
+});
+```
+
+## Install
+With [node.js](http://nodejs.org/) npm package manager:
+
+```bash
+npm install firefox-client
+```
+
+## Connecting
+
+### Desktop Firefox
+1. Enable remote debugging (You'll only have to do this once)
+ 1. Open the DevTools. **Web Developer** > **Toggle Tools**
+ 2. Visit the settings panel (gear icon)
+ 3. Check "Enable remote debugging" under Advanced Settings
+
+2. Listen for a connection
+ 1. Open the Firefox command line with **Tools** > **Web Developer** > **Developer Toolbar**.
+ 2. Start a server by entering this command: `listen 6000` (where `6000` is the port number)
+
+### Firefox for Android
+Follow the instructions in [this Hacks video](https://www.youtube.com/watch?v=Znj_8IFeTVs)
+
+### Firefox OS 1.1 Simulator
+A limited set of the API (`Console`, `StyleSheets`) is compatible with the [Simulator 4.0](https://addons.mozilla.org/en-US/firefox/addon/firefox-os-simulator/). See the [wiki instructions](https://github.com/harthur/firefox-client/wiki/Firefox-OS-Simulator-Instructions) for connecting.
+
+`client.listTabs()` will list the currently open apps in the Simulator.
+
+### Firefox OS 1.2+ Simulator and devices
+
+`client.getWebapps()` will expose the webapps in the Simulator, where each app implements the `Tab` API.
+
+```
+client.getWebapps(function(err, webapps) {
+  webapps.getApp("app://homescreen.gaiamobile.org/manifest.webapp", function (err, app) {
+    console.log("homescreen:", actor.url);
+    app.Console.evaluateJS("alert('foo')", function(err, resp) {
+      console.log("alert dismissed");
+    });
+  });
+});
+```
+
+## Compatibility
+
+This latest version of the library will stay compatible with [Firefox Nightly](http://nightly.mozilla.org/). Almost all of it will be compatible with [Firefox Aurora](http://www.mozilla.org/en-US/firefox/aurora/) as well.
+
+## API
+
+A `FirefoxClient` is the entry point to the API. After connecting, get a `Tab` object with `listTabs()` or `selectedTab()`. Once you have a `Tab`, you can call methods and listen to events from the tab's modules, `Console` or `Network`. There are also experimental `DOM` and `StyleSheets` tab modules, and an upcoming `Debugger` module.
+
+#### Methods
+Almost all API calls take a callback that will get called with an error as the first argument (or `null` if there is no error), and a return value as the second:
+
+```javascript
+tab.Console.evaluateJS("6 + 7", function(err, resp) {
+  if (err) throw err;
+
+  console.log(resp.result);
+});
+```
+
+#### Events
+
+The modules are `EventEmitter`s, listen for events with `on` or `once`, and stop listening with `off`:
+
+```javascript
+tab.Console.on("page-error", function(event) {
+  console.log("new error from tab:", event.errorMessage);
+});
+```
+
+Summary of the offerings of the modules and objects:
+
+#### [FirefoxClient](http://github.com/harthur/firefox-client/wiki/FirefoxClient)
+Methods: `connect()`, `disconnect()`, `listTabs()`, `selectedTab()`, `getWebapps()`, `getRoot()`
+
+Events: `"error"`, `"timeout"`, `"end"`
+
+#### [Tab](https://github.com/harthur/firefox-client/wiki/Tab)
+Properties: `url`, `title`
+
+Methods: `reload()`, `navigateTo()`, `attach()`, `detach()`
+
+Events: `"navigate"`, `"before-navigate"`
+
+#### [Tab.Console](https://github.com/harthur/firefox-client/wiki/Console)
+Methods: `evaluateJS()`, `startListening()`, `stopListening()`, `getCachedLogs()`
+
+Events: `"page-error"`, `"console-api-call"`
+
+#### [JSObject](https://github.com/harthur/firefox-client/wiki/JSObject)
+Properties: `class`, `name`, `displayName`
+
+Methods: `ownPropertyNames()`, `ownPropertyDescriptor()`, `ownProperties()`, `prototype()`
+
+#### [Tab.Network](https://github.com/harthur/firefox-client/wiki/Network)
+Methods: `startLogging()`, `stopLogging()`, `sendHTTPRequest()`
+
+Events: `"network-event"`
+
+#### [NetworkEvent](https://github.com/harthur/firefox-client/wiki/NetworkEvent)
+Properties: `url`, `method`, `isXHR`
+
+Methods: `getRequestHeaders()`, `getRequestCookies()`, `getRequestPostData()`, `getResponseHeaders()`, `getResponseCookies()`, `getResponseContent()`, `getEventTimings()`
+
+Events: `"request-headers"`, `"request-cookies"`, `"request-postdata"`, `"response-start"`, `"response-headers"`, `"response-cookies"`, `"event-timings"`
+
+#### [Tab.DOM](https://github.com/harthur/firefox-client/wiki/DOM)
+Methods: `document()`, `documentElement()`, `querySelector()`, `querySelectorAll()`
+
+#### [DOMNode](https://github.com/harthur/firefox-client/wiki/DOMNode)
+Properties: `nodeValue`, `nodeName`, `namespaceURI`
+
+Methods: `parentNode()`, `parents()`, `siblings()`, `nextSibling()`, `previousSibling()`, `querySelector()`, `querySelectorAll()`, `innerHTML()`, `outerHTML()`, `setAttribute()`, `remove()`, `release()`
+
+#### [Tab.StyleSheets](https://github.com/harthur/firefox-client/wiki/StyleSheets)
+Methods: `getStyleSheets()`, `addStyleSheet()`
+
+#### [StyleSheet](https://github.com/harthur/firefox-client/wiki/StyleSheet)
+Properties: `href`, `disabled`, `ruleCount`
+
+Methods: `getText()`, `update()`, `toggleDisabled()`, `getOriginalSources()`
+
+Events: `"disabled-changed"`, `"ruleCount-changed"`
+
+#### Tab.Memory
+Methods: `measure()`
+
+#### Webapps
+Methods: `listRunningApps()`, `getInstalledApps()`, `watchApps()`, `unwatchApps()`, `launch()`, `close()`, `getApp()`, `installHosted()`, `installPackaged()`, `installPackagedWithADB()`, `uninstall()`
+
+Events: `"appOpen"`, `"appClose"`, `"appInstall"`, `"appUninstall"`
+
+## Examples
+
+[fxconsole](https://github.com/harthur/fxconsole) - a remote JavaScript console for Firefox
+
+[webapps test script](https://pastebin.mozilla.org/5094843) - a sample usage of all webapps features
+
+## Feedback
+
+What do you need from the API? [File an issue](https://github.com/harthur/firefox-client/issues/new).
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/index.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/index.js
new file mode 100644
index 0000000..cbf7a39
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/index.js
@@ -0,0 +1 @@
+module.exports = require("./lib/browser");
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/lib/browser.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/lib/browser.js
new file mode 100644
index 0000000..2e52220
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/lib/browser.js
@@ -0,0 +1,123 @@
+var extend = require("./extend"),
+    ClientMethods = require("./client-methods"),
+    Client = require("./client"),
+    Tab = require("./tab"),
+    Webapps = require("./webapps"),
+    Device = require("./device"),
+    SimulatorApps = require("./simulator");
+
+const DEFAULT_PORT = 6000;
+const DEFAULT_HOST = "localhost";
+
+module.exports = FirefoxClient;
+
+function FirefoxClient(options) {
+  var client = new Client(options);
+  var actor = 'root';
+
+  client.on("error", this.onError.bind(this));
+  client.on("end", this.onEnd.bind(this));
+  client.on("timeout", this.onTimeout.bind(this));
+
+  this.initialize(client, actor);
+}
+
+FirefoxClient.prototype = extend(ClientMethods, {
+  connect: function(port, host, cb) {
+    if (typeof port == "function") {
+      // (cb)
+      cb = port;
+      port = DEFAULT_PORT;
+      host = DEFAULT_HOST;
+
+    }
+    if (typeof host == "function") {
+      // (port, cb)
+      cb = host;
+      host = DEFAULT_HOST;
+    }
+    // (port, host, cb)
+
+    this.client.connect(port, host, cb);
+
+    this.client.expectReply(this.actor, function(packet) {
+      // root message
+    });
+  },
+
+  disconnect: function() {
+    this.client.disconnect();
+  },
+
+  onError: function(error) {
+    this.emit("error", error);
+  },
+
+  onEnd: function() {
+    this.emit("end");
+  },
+
+  onTimeout: function() {
+    this.emit("timeout");
+  },
+
+  selectedTab: function(cb) {
+    this.request("listTabs", function(resp) {
+      var tab = resp.tabs[resp.selected];
+      return new Tab(this.client, tab);
+    }.bind(this), cb);
+  },
+
+  listTabs: function(cb) {
+    this.request("listTabs", function(err, resp) {
+      if (err) {
+        return cb(err);
+      }
+
+      if (resp.simulatorWebappsActor) {
+        // the server is the Firefox OS Simulator, return apps as "tabs"
+        var apps = new SimulatorApps(this.client, resp.simulatorWebappsActor);
+        apps.listApps(cb);
+      }
+      else {
+        var tabs = resp.tabs.map(function(tab) {
+          return new Tab(this.client, tab);
+        }.bind(this));
+        cb(null, tabs);
+      }
+    }.bind(this));
+  },
+
+  getWebapps: function(cb) {
+    this.request("listTabs", (function(err, resp) {
+      if (err) {
+        return cb(err);
+      }
+      var webapps = new Webapps(this.client, resp);
+      cb(null, webapps);
+    }).bind(this));
+  },
+
+  getDevice: function(cb) {
+    this.request("listTabs", (function(err, resp) {
+      if (err) {
+        return cb(err);
+      }
+      var device = new Device(this.client, resp);
+      cb(null, device);
+    }).bind(this));
+  },
+
+  getRoot: function(cb) {
+    this.request("listTabs", (function(err, resp) {
+      if (err) {
+        return cb(err);
+      }
+      if (!resp.consoleActor) {
+        return cb("No root actor being available.");
+      }
+      var root = new Tab(this.client, resp);
+      cb(null, root);
+    }).bind(this));
+  }
+})
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/lib/client-methods.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/lib/client-methods.js
new file mode 100644
index 0000000..c92c44c
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/lib/client-methods.js
@@ -0,0 +1,115 @@
+var events = require("events"),
+    extend = require("./extend");
+
+// to be instantiated later - to avoid circular dep resolution
+var JSObject;
+
+var ClientMethods = extend(events.EventEmitter.prototype, {
+  /**
+   * Intialize this client object.
+   *
+   * @param  {object} client
+   *         Client to send requests on.
+   * @param  {string} actor
+   *         Actor id to set as 'from' field on requests
+   */
+  initialize: function(client, actor) {
+    this.client = client;
+    this.actor = actor;
+
+    this.client.on('message', function(message) {
+      if (message.from == this.actor) {
+        this.emit(message.type, message);
+      }
+    }.bind(this));
+  },
+
+  /**
+   * Make request to our actor on the server.
+   *
+   * @param  {string}   type
+   *         Method name of the request
+   * @param  {object}   message
+   *         Optional extra properties (arguments to method)
+   * @param  {Function}   transform
+   *         Optional tranform for response object. Takes response object
+   *         and returns object to send on.
+   * @param  {Function} callback
+   *         Callback to call with (maybe transformed) response
+   */
+  request: function(type, message, transform, callback) {
+    if (typeof message == "function") {
+      if (typeof transform == "function") {
+        // (type, trans, cb)
+        callback = transform;
+        transform = message;
+      }
+      else {
+        // (type, cb)
+        callback = message;
+      }
+      message = {};
+    }
+    else if (!callback) {
+      if (!message) {
+        // (type)
+        message = {};
+      }
+      // (type, message, cb)
+      callback = transform;
+      transform = null;
+    }
+
+    message.to = this.actor;
+    message.type = type;
+
+    this.client.makeRequest(message, function(resp) {
+      delete resp.from;
+
+      if (resp.error) {
+        var err = new Error(resp.message);
+        err.name = resp.error;
+
+        callback(err);
+        return;
+      }
+
+      if (transform) {
+        resp = transform(resp);
+      }
+
+      if (callback) {
+        callback(null, resp);
+      }
+    });
+  },
+
+  /*
+   * Transform obj response into a JSObject
+   */
+  createJSObject: function(obj) {
+    if (obj == null) {
+      return;
+    }
+    if (!JSObject) {
+      // circular dependencies
+      JSObject = require("./jsobject");
+    }
+    if (obj.type == "object") {
+      return new JSObject(this.client, obj);
+    }
+    return obj;
+  },
+
+  /**
+   * Create function that plucks out only one value from an object.
+   * Used as the transform function for some responses.
+   */
+  pluck: function(prop) {
+    return function(obj) {
+      return obj[prop];
+    }
+  }
+})
+
+module.exports = ClientMethods;
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/lib/client.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/lib/client.js
new file mode 100644
index 0000000..22b3179
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/lib/client.js
@@ -0,0 +1,246 @@
+var net = require("net"),
+    events = require("events"),
+    extend = require("./extend");
+
+var colors = require("colors");
+
+module.exports = Client;
+
+// this is very unfortunate! and temporary. we can't
+// rely on 'type' property to signify an event, and we
+// need to write clients for each actor to handle differences
+// in actor protocols
+var unsolicitedEvents = {
+  "tabNavigated": "tabNavigated",
+  "styleApplied": "styleApplied",
+  "propertyChange": "propertyChange",
+  "networkEventUpdate": "networkEventUpdate",
+  "networkEvent": "networkEvent",
+  "propertyChange": "propertyChange",
+  "newMutations": "newMutations",
+  "appOpen": "appOpen",
+  "appClose": "appClose",
+  "appInstall": "appInstall",
+  "appUninstall": "appUninstall",
+  "frameUpdate": "frameUpdate"
+};
+
+/**
+ * a Client object handles connecting with a Firefox remote debugging
+ * server instance (e.g. a Firefox instance), plus sending and receiving
+ * packets on that conection using the Firefox remote debugging protocol.
+ *
+ * Important methods:
+ * connect - Create the connection to the server.
+ * makeRequest - Make a request to the server with a JSON message,
+ *   and a callback to call with the response.
+ *
+ * Important events:
+ * 'message' - An unsolicited (e.g. not a response to a prior request)
+ *    packet has been received. These packets usually describe events.
+ */
+function Client(options) {
+  this.options = options || {};
+
+  this.incoming = new Buffer("");
+
+  this._pendingRequests = [];
+  this._activeRequests = {};
+}
+
+Client.prototype = extend(events.EventEmitter.prototype, {
+  connect: function(port, host, cb) {
+    this.client = net.createConnection({
+      port: port,
+      host: host
+    });
+
+    this.client.on("connect", cb);
+    this.client.on("data", this.onData.bind(this));
+    this.client.on("error", this.onError.bind(this));
+    this.client.on("end", this.onEnd.bind(this));
+    this.client.on("timeout", this.onTimeout.bind(this));
+  },
+
+  disconnect: function() {
+    if (this.client) {
+      this.client.end();
+    }
+  },
+
+  /**
+   * Set a request to be sent to an actor on the server. If the actor
+   * is already handling a request, queue this request until the actor
+   * has responded to the previous request.
+   *
+   * @param {object} request
+   *        Message to be JSON-ified and sent to server.
+   * @param {function} callback
+   *        Function that's called with the response from the server.
+   */
+  makeRequest: function(request, callback) {
+    this.log("request: " + JSON.stringify(request).green);
+
+    if (!request.to) {
+      var type = request.type || "";
+      throw new Error(type + " request packet has no destination.");
+    }
+    this._pendingRequests.push({ to: request.to,
+                                 message: request,
+                                 callback: callback });
+    this._flushRequests();
+  },
+
+  /**
+   * Activate (send) any pending requests to actors that don't have an
+   * active request.
+   */
+  _flushRequests: function() {
+    this._pendingRequests = this._pendingRequests.filter(function(request) {
+      // only one active request per actor at a time
+      if (this._activeRequests[request.to]) {
+        return true;
+      }
+
+      // no active requests for this actor, so activate this one
+      this.sendMessage(request.message);
+      this.expectReply(request.to, request.callback);
+
+      // remove from pending requests
+      return false;
+    }.bind(this));
+  },
+
+  /**
+   * Send a JSON message over the connection to the server.
+   */
+  sendMessage: function(message) {
+    if (!message.to) {
+      throw new Error("No actor specified in request");
+    }
+    if (!this.client) {
+      throw new Error("Not connected, connect() before sending requests");
+    }
+    var str = JSON.stringify(message);
+
+    // message is preceded by byteLength(message):
+    str = (new Buffer(str).length) + ":" + str;
+
+    this.client.write(str);
+  },
+
+  /**
+   * Arrange to hand the next reply from |actor| to |handler|.
+   */
+  expectReply: function(actor, handler) {
+    if (this._activeRequests[actor]) {
+      throw Error("clashing handlers for next reply from " + uneval(actor));
+    }
+    this._activeRequests[actor] = handler;
+  },
+
+  /**
+   * Handler for a new message coming in. It's either an unsolicited event
+   * from the server, or a response to a previous request from the client.
+   */
+  handleMessage: function(message) {
+    if (!message.from) {
+      if (message.error) {
+        throw new Error(message.message);
+      }
+      throw new Error("Server didn't specify an actor: " + JSON.stringify(message));
+    }
+
+    if (!(message.type in unsolicitedEvents)
+        && this._activeRequests[message.from]) {
+      this.log("response: " + JSON.stringify(message).yellow);
+
+      var callback = this._activeRequests[message.from];
+      delete this._activeRequests[message.from];
+
+      callback(message);
+
+      this._flushRequests();
+    }
+    else if (message.type) {
+      // this is an unsolicited event from the server
+      this.log("unsolicited event: ".grey + JSON.stringify(message).grey);
+
+      this.emit('message', message);
+      return;
+    }
+    else {
+      throw new Error("Unexpected packet from actor " +  message.from
+      +  JSON.stringify(message));
+    }
+  },
+
+  /**
+   * Called when a new data chunk is received on the connection.
+   * Parse data into message(s) and call message handler for any full
+   * messages that are read in.
+   */
+  onData: function(data) {
+    this.incoming = Buffer.concat([this.incoming, data]);
+
+    while(this.readMessage()) {};
+  },
+
+  /**
+   * Parse out and process the next message from the data read from
+   * the connection. Returns true if a full meassage was parsed, false
+   * otherwise.
+   */
+  readMessage: function() {
+    var sep = this.incoming.toString().indexOf(':');
+    if (sep < 0) {
+      return false;
+    }
+
+    // beginning of a message is preceded by byteLength(message) + ":"
+    var count = parseInt(this.incoming.slice(0, sep));
+
+    if (this.incoming.length - (sep + 1) < count) {
+      this.log("no complete response yet".grey);
+      return false;
+    }
+    this.incoming = this.incoming.slice(sep + 1);
+
+    var packet = this.incoming.slice(0, count);
+
+    this.incoming = this.incoming.slice(count);
+
+    var message;
+    try {
+      message = JSON.parse(packet.toString());
+    } catch(e) {
+      throw new Error("Couldn't parse packet from server as JSON " + e
+        + ", message:\n" + packet);
+    }
+    this.handleMessage(message);
+
+    return true;
+  },
+
+  onError: function(error) {
+    var code = error.code ? error.code : error;
+    this.log("connection error: ".red + code.red);
+    this.emit("error", error);
+  },
+
+  onEnd: function() {
+    this.log("connection closed by server".red);
+    this.emit("end");
+  },
+
+  onTimeout: function() {
+    this.log("connection timeout".red);
+    this.emit("timeout");
+  },
+
+  log: function(str) {
+    if (this.options.log) {
+      console.log(str);
+    }
+  }
+})
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/lib/console.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/lib/console.js
new file mode 100644
index 0000000..3f3dcff
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/lib/console.js
@@ -0,0 +1,107 @@
+var select = require("js-select"),
+    extend = require("./extend"),
+    ClientMethods = require("./client-methods"),
+    JSObject = require("./jsobject");
+
+module.exports = Console;
+
+function Console(client, actor) {
+  this.initialize(client, actor);
+
+  this.on("consoleAPICall", this.onConsoleAPI.bind(this));
+  this.on("pageError", this.onPageError.bind(this));
+}
+
+Console.prototype = extend(ClientMethods, {
+  types: ["PageError", "ConsoleAPI"],
+
+  /**
+   * Response object:
+   *   -empty-
+   */
+  startListening: function(cb) {
+    this.request('startListeners', { listeners: this.types }, cb);
+  },
+
+  /**
+   * Response object:
+   *   -empty-
+   */
+  stopListening: function(cb) {
+    this.request('stopListeners', { listeners: this.types }, cb);
+  },
+
+  /**
+   * Event object:
+   *   level - "log", etc.
+   *   filename - file with call
+   *   lineNumber - line number of call
+   *   functionName - function log called from
+   *   timeStamp - ms timestamp of call
+   *   arguments - array of the arguments to log call
+   *   private -
+   */
+  onConsoleAPI: function(event) {
+    var message = this.transformConsoleCall(event.message);
+
+    this.emit("console-api-call", message);
+  },
+
+  /**
+   * Event object:
+   *   errorMessage - string error message
+   *   sourceName - file error
+   *   lineText
+   *   lineNumber - line number of error
+   *   columnNumber - column number of error
+   *   category - usually "content javascript",
+   *   timeStamp - time in ms of error occurance
+   *   warning - whether it's a warning
+   *   error - whether it's an error
+   *   exception - whether it's an exception
+   *   strict -
+   *   private -
+   */
+  onPageError: function(event) {
+    this.emit("page-error", event.pageError);
+  },
+
+  /**
+   * Response object: array of page error or console call objects.
+   */
+  getCachedLogs: function(cb) {
+    var message = {
+      messageTypes: this.types
+    };
+    this.request('getCachedMessages', message, function(resp) {
+      select(resp, ".messages > *").update(this.transformConsoleCall.bind(this));
+      return resp.messages;
+    }.bind(this), cb);
+  },
+
+  /**
+   * Response object:
+   *   -empty-
+   */
+  clearCachedLogs: function(cb) {
+    this.request('clearMessagesCache', cb);
+  },
+
+  /**
+   * Response object:
+   *   input - original input
+   *   result - result of the evaluation, a value or JSObject
+   *   timestamp - timestamp in ms of the evaluation
+   *   exception - any exception as a result of the evaluation
+   */
+  evaluateJS: function(text, cb) {
+    this.request('evaluateJS', { text: text }, function(resp) {
+      return select(resp, ".result, .exception")
+             .update(this.createJSObject.bind(this));
+    }.bind(this), cb)
+  },
+
+  transformConsoleCall: function(message) {
+    return select(message, ".arguments > *").update(this.createJSObject.bind(this));
+  }
+})
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/lib/device.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/lib/device.js
new file mode 100644
index 0000000..6c5da84
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/lib/device.js
@@ -0,0 +1,29 @@
+var extend = require("./extend"),
+    ClientMethods = require("./client-methods");
+
+module.exports = Device;
+
+function Device(client, tab) {
+  this.initialize(client, tab.deviceActor);
+}
+
+Device.prototype = extend(ClientMethods, {
+  getDescription: function(cb) {
+    this.request("getDescription", function(err, resp) {
+      if (err) {
+        return cb(err);
+      }
+
+      cb(null, resp.value);
+    });
+  },
+  getRawPermissionsTable: function(cb) {
+    this.request("getRawPermissionsTable", function(err, resp) {
+      if (err) {
+        return cb(err);
+      }
+
+      cb(null, resp.value.rawPermissionsTable);
+    });
+  }
+})
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/lib/dom.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/lib/dom.js
new file mode 100644
index 0000000..a00018c
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/lib/dom.js
@@ -0,0 +1,68 @@
+var extend = require("./extend"),
+    ClientMethods = require("./client-methods"),
+    Node = require("./domnode");
+
+module.exports = DOM;
+
+function DOM(client, actor) {
+  this.initialize(client, actor);
+  this.walker = null;
+}
+
+DOM.prototype = extend(ClientMethods, {
+  document: function(cb) {
+    this.walkerRequest("document", function(err, resp) {
+      if (err) return cb(err);
+
+      var node = new Node(this.client, this.walker, resp.node);
+      cb(null, node);
+    }.bind(this))
+  },
+
+  documentElement: function(cb) {
+    this.walkerRequest("documentElement", function(err, resp) {
+      var node = new Node(this.client, this.walker, resp.node);
+      cb(err, node);
+    }.bind(this))
+  },
+
+  querySelector: function(selector, cb) {
+    this.document(function(err, node) {
+      if (err) return cb(err);
+
+      node.querySelector(selector, cb);
+    })
+  },
+
+  querySelectorAll: function(selector, cb) {
+    this.document(function(err, node) {
+      if (err) return cb(err);
+
+      node.querySelectorAll(selector, cb);
+    })
+  },
+
+  walkerRequest: function(type, message, cb) {
+    this.getWalker(function(err, walker) {
+      walker.request(type, message, cb);
+    });
+  },
+
+  getWalker: function(cb) {
+    if (this.walker) {
+      return cb(null, this.walker);
+    }
+    this.request('getWalker', function(err, resp) {
+      this.walker = new Walker(this.client, resp.walker);
+      cb(err, this.walker);
+    }.bind(this))
+  }
+})
+
+function Walker(client, walker) {
+  this.initialize(client, walker.actor);
+
+  this.root = new Node(client, this, walker.root);
+}
+
+Walker.prototype = extend(ClientMethods, {});
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/lib/domnode.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/lib/domnode.js
new file mode 100644
index 0000000..e0a55b8
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/lib/domnode.js
@@ -0,0 +1,169 @@
+var extend = require("./extend"),
+    ClientMethods = require("./client-methods");
+
+module.exports = Node;
+
+function Node(client, walker, node) {
+  this.initialize(client, node.actor);
+  this.walker = walker;
+
+  this.getNode = this.getNode.bind(this);
+  this.getNodeArray = this.getNodeArray.bind(this);
+  this.getNodeList = this.getNodeList.bind(this);
+
+  walker.on('newMutations', function(event) {
+    //console.log("on new mutations! ", JSON.stringify(event));
+  });
+
+  ['nodeType', 'nodeName', 'namespaceURI', 'attrs']
+  .forEach(function(attr) {
+    this[attr] = node[attr];
+  }.bind(this));
+}
+
+Node.prototype = extend(ClientMethods, {
+  getAttribute: function(name) {
+    for (var i in this.attrs) {
+      var attr = this.attrs[i];
+      if (attr.name == name) {
+        return attr.value;
+      }
+    }
+  },
+
+  setAttribute: function(name, value, cb) {
+    var mods = [{
+      attributeName: name,
+      newValue: value
+    }];
+    this.request('modifyAttributes', { modifications: mods }, cb);
+  },
+
+  parentNode: function(cb) {
+    this.parents(function(err, nodes) {
+      if (err) {
+        return cb(err);
+      }
+      var node = null;
+      if (nodes.length) {
+        node = nodes[0];
+      }
+      cb(null, node);
+    })
+  },
+
+  parents: function(cb) {
+    this.nodeRequest('parents', this.getNodeArray, cb);
+  },
+
+  children: function(cb) {
+    this.nodeRequest('children', this.getNodeArray, cb);
+  },
+
+  siblings: function(cb) {
+    this.nodeRequest('siblings', this.getNodeArray, cb);
+  },
+
+  nextSibling: function(cb) {
+    this.nodeRequest('nextSibling', this.getNode, cb);
+  },
+
+  previousSibling: function(cb) {
+    this.nodeRequest('previousSibling', this.getNode, cb);
+  },
+
+  querySelector: function(selector, cb) {
+    this.nodeRequest('querySelector', { selector: selector },
+                     this.getNode, cb);
+  },
+
+  querySelectorAll: function(selector, cb) {
+    this.nodeRequest('querySelectorAll', { selector: selector },
+                     this.getNodeList, cb);
+  },
+
+  innerHTML: function(cb) {
+    this.nodeRequest('innerHTML', function(resp) {
+      return resp.value;
+    }, cb)
+  },
+
+  outerHTML: function(cb) {
+    this.nodeRequest('outerHTML', function(resp) {
+      return resp.value;
+    }, cb)
+  },
+
+  remove: function(cb) {
+    this.nodeRequest('removeNode', function(resp) {
+      return new Node(this.client, this.walker, resp.nextSibling);
+    }.bind(this), cb);
+  },
+
+  highlight: function(cb) {
+    this.nodeRequest('highlight', cb);
+  },
+
+  release: function(cb) {
+    this.nodeRequest('releaseNode', cb);
+  },
+
+  getNode: function(resp) {
+    if (resp.node) {
+      return new Node(this.client, this.walker, resp.node);
+    }
+    return null;
+  },
+
+  getNodeArray: function(resp) {
+    return resp.nodes.map(function(form) {
+      return new Node(this.client, this.walker, form);
+    }.bind(this));
+  },
+
+  getNodeList: function(resp) {
+    return new NodeList(this.client, this.walker, resp.list);
+  },
+
+  nodeRequest: function(type, message, transform, cb) {
+    if (!cb) {
+      cb = transform;
+      transform = message;
+      message = {};
+    }
+    message.node = this.actor;
+
+    this.walker.request(type, message, transform, cb);
+  }
+});
+
+function NodeList(client, walker, list) {
+  this.client = client;
+  this.walker = walker;
+  this.actor = list.actor;
+
+  this.length = list.length;
+}
+
+NodeList.prototype = extend(ClientMethods, {
+  items: function(start, end, cb) {
+    if (typeof start == "function") {
+      cb = start;
+      start = 0;
+      end = this.length;
+    }
+    else if (typeof end == "function") {
+      cb = end;
+      end = this.length;
+    }
+    this.request('items', { start: start, end: end },
+      this.getNodeArray.bind(this), cb);
+  },
+
+  // TODO: add this function to ClientMethods
+  getNodeArray: function(resp) {
+    return resp.nodes.map(function(form) {
+      return new Node(this.client, this.walker, form);
+    }.bind(this));
+  }
+});
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/lib/extend.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/lib/extend.js
new file mode 100644
index 0000000..8624103
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/lib/extend.js
@@ -0,0 +1,12 @@
+module.exports = function extend(prototype, properties) {
+  return Object.create(prototype, getOwnPropertyDescriptors(properties));
+}
+
+function getOwnPropertyDescriptors(object) {
+  var names = Object.getOwnPropertyNames(object);
+
+  return names.reduce(function(descriptor, name) {
+    descriptor[name] = Object.getOwnPropertyDescriptor(object, name);
+    return descriptor;
+  }, {});
+}
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/lib/jsobject.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/lib/jsobject.js
new file mode 100644
index 0000000..907999f
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/lib/jsobject.js
@@ -0,0 +1,91 @@
+var select = require("js-select"),
+    extend = require("./extend"),
+    ClientMethods = require("./client-methods");
+
+module.exports = JSObject;
+
+function JSObject(client, obj) {
+  this.initialize(client, obj.actor);
+  this.obj = obj;
+}
+
+JSObject.prototype = extend(ClientMethods, {
+  type: "object",
+
+  get class() {
+    return this.obj.class;
+  },
+
+  get name() {
+    return this.obj.name;
+  },
+
+  get displayName() {
+    return this.obj.displayName;
+  },
+
+  ownPropertyNames: function(cb) {
+    this.request('ownPropertyNames', function(resp) {
+      return resp.ownPropertyNames;
+    }, cb);
+  },
+
+  ownPropertyDescriptor: function(name, cb) {
+    this.request('property', { name: name }, function(resp) {
+      return this.transformDescriptor(resp.descriptor);
+    }.bind(this), cb);
+  },
+
+  ownProperties: function(cb) {
+    this.request('prototypeAndProperties', function(resp) {
+      return this.transformProperties(resp.ownProperties);
+    }.bind(this), cb);
+  },
+
+  prototype: function(cb) {
+    this.request('prototype', function(resp) {
+      return this.createJSObject(resp.prototype);
+    }.bind(this), cb);
+  },
+
+  ownPropertiesAndPrototype: function(cb) {
+    this.request('prototypeAndProperties', function(resp) {
+      resp.ownProperties = this.transformProperties(resp.ownProperties);
+      resp.safeGetterValues = this.transformGetters(resp.safeGetterValues);
+      resp.prototype = this.createJSObject(resp.prototype);
+
+      return resp;
+    }.bind(this), cb);
+  },
+
+  /* helpers */
+  transformProperties: function(props) {
+    var transformed = {};
+    for (var prop in props) {
+      transformed[prop] = this.transformDescriptor(props[prop]);
+    }
+    return transformed;
+  },
+
+  transformGetters: function(getters) {
+    var transformed = {};
+    for (var prop in getters) {
+      transformed[prop] = this.transformGetter(getters[prop]);
+    }
+    return transformed;
+  },
+
+  transformDescriptor: function(descriptor) {
+    descriptor.value = this.createJSObject(descriptor.value);
+    return descriptor;
+  },
+
+  transformGetter: function(getter) {
+    return {
+      value: this.createJSObject(getter.getterValue),
+      prototypeLevel: getter.getterPrototypeLevel,
+      enumerable: getter.enumerable,
+      writable: getter.writable
+    }
+  }
+})
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/lib/memory.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/lib/memory.js
new file mode 100644
index 0000000..8468539
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/lib/memory.js
@@ -0,0 +1,16 @@
+var extend = require("./extend"),
+    ClientMethods = require("./client-methods");
+
+module.exports = Memory;
+
+function Memory(client, actor) {
+  this.initialize(client, actor);
+}
+
+Memory.prototype = extend(ClientMethods, {
+  measure: function(cb) {
+    this.request('measure', function (err, resp) {
+      cb(err, resp);
+    });
+  }
+})
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/lib/network.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/lib/network.js
new file mode 100644
index 0000000..5c1afdb
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/lib/network.js
@@ -0,0 +1,105 @@
+var extend = require("./extend");
+var ClientMethods = require("./client-methods");
+
+module.exports = Network;
+
+function Network(client, actor) {
+  this.initialize(client, actor);
+
+  this.on("networkEvent", this.onNetworkEvent.bind(this));
+}
+
+Network.prototype = extend(ClientMethods, {
+  types: ["NetworkActivity"],
+
+  startLogging: function(cb) {
+    this.request('startListeners', { listeners: this.types }, cb);
+  },
+
+  stopLogging: function(cb) {
+    this.request('stopListeners', { listeners: this.types }, cb);
+  },
+
+  onNetworkEvent: function(event) {
+    var networkEvent = new NetworkEvent(this.client, event.eventActor);
+
+    this.emit("network-event", networkEvent);
+  },
+
+  sendHTTPRequest: function(request, cb) {
+    this.request('sendHTTPRequest', { request: request }, function(resp) {
+      return new NetworkEvent(this.client, resp.eventActor);
+    }.bind(this), cb);
+  }
+})
+
+function NetworkEvent(client, event) {
+  this.initialize(client, event.actor);
+  this.event = event;
+
+  this.on("networkEventUpdate", this.onUpdate.bind(this));
+}
+
+NetworkEvent.prototype = extend(ClientMethods, {
+  get url() {
+   return this.event.url;
+  },
+
+  get method() {
+    return this.event.method;
+  },
+
+  get isXHR() {
+    return this.event.isXHR;
+  },
+
+  getRequestHeaders: function(cb) {
+    this.request('getRequestHeaders', cb);
+  },
+
+  getRequestCookies: function(cb) {
+    this.request('getRequestCookies', this.pluck('cookies'), cb);
+  },
+
+  getRequestPostData: function(cb) {
+    this.request('getRequestPostData', cb);
+  },
+
+  getResponseHeaders: function(cb) {
+    this.request('getResponseHeaders', cb);
+  },
+
+  getResponseCookies: function(cb) {
+    this.request('getResponseCookies', this.pluck('cookies'), cb);
+  },
+
+  getResponseContent: function(cb) {
+    this.request('getResponseContent', cb);
+  },
+
+  getEventTimings: function(cb) {
+    this.request('getEventTimings', cb);
+  },
+
+  onUpdate: function(event) {
+    var types = {
+      "requestHeaders": "request-headers",
+      "requestCookies": "request-cookies",
+      "requestPostData": "request-postdata",
+      "responseStart": "response-start",
+      "responseHeaders": "response-headers",
+      "responseCookies": "response-cookies",
+      "responseContent": "response-content",
+      "eventTimings": "event-timings"
+    }
+
+    var type = types[event.updateType];
+    delete event.updateType;
+
+    this.emit(type, event);
+  }
+})
+
+
+
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/lib/simulator.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/lib/simulator.js
new file mode 100644
index 0000000..4df6a2b
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/lib/simulator.js
@@ -0,0 +1,22 @@
+var extend = require("./extend"),
+    ClientMethods = require("./client-methods"),
+    Tab = require("./tab");
+
+module.exports = SimulatorApps;
+
+function SimulatorApps(client, actor) {
+  this.initialize(client, actor);
+}
+
+SimulatorApps.prototype = extend(ClientMethods, {
+  listApps: function(cb) {
+    this.request('listApps', function(resp) {
+      var apps = [];
+      for (var url in resp.apps) {
+        var app = resp.apps[url];
+        apps.push(new Tab(this.client, app));
+      }
+      return apps;
+    }.bind(this), cb);
+  }
+})
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/lib/stylesheets.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/lib/stylesheets.js
new file mode 100644
index 0000000..0677c02
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/lib/stylesheets.js
@@ -0,0 +1,96 @@
+var extend = require("./extend");
+var ClientMethods = require("./client-methods");
+
+module.exports = StyleSheets;
+
+function StyleSheets(client, actor) {
+  this.initialize(client, actor);
+}
+
+StyleSheets.prototype = extend(ClientMethods, {
+  getStyleSheets: function(cb) {
+    this.request('getStyleSheets', function(resp) {
+      return resp.styleSheets.map(function(sheet) {
+        return new StyleSheet(this.client, sheet);
+      }.bind(this));
+    }.bind(this), cb);
+  },
+
+  addStyleSheet: function(text, cb) {
+    this.request('addStyleSheet', { text: text }, function(resp) {
+      return new StyleSheet(this.client, resp.styleSheet);
+    }.bind(this), cb);
+  }
+})
+
+function StyleSheet(client, sheet) {
+  this.initialize(client, sheet.actor);
+  this.sheet = sheet;
+
+  this.on("propertyChange", this.onPropertyChange.bind(this));
+}
+
+StyleSheet.prototype = extend(ClientMethods, {
+  get href() {
+    return this.sheet.href;
+  },
+
+  get disabled() {
+    return this.sheet.disabled;
+  },
+
+  get ruleCount() {
+    return this.sheet.ruleCount;
+  },
+
+  onPropertyChange: function(event) {
+    this.sheet[event.property] = event.value;
+    this.emit(event.property + "-changed", event.value);
+  },
+
+  toggleDisabled: function(cb) {
+    this.request('toggleDisabled', function(err, resp) {
+      if (err) return cb(err);
+
+      this.sheet.disabled = resp.disabled;
+      cb(null, resp.disabled);
+    }.bind(this));
+  },
+
+  getOriginalSources: function(cb) {
+    this.request('getOriginalSources', function(resp) {
+      if (resp.originalSources === null) {
+        return [];
+      }
+      return resp.originalSources.map(function(form) {
+        return new OriginalSource(this.client, form);
+      }.bind(this));
+    }.bind(this), cb);
+  },
+
+  update: function(text, cb) {
+    this.request('update', { text: text, transition: true }, cb);
+  },
+
+  getText: function(cb) {
+    this.request('getText', this.pluck('text'), cb);
+  }
+});
+
+
+function OriginalSource(client, source) {
+  console.log("source", source);
+  this.initialize(client, source.actor);
+
+  this.source = source;
+}
+
+OriginalSource.prototype = extend(ClientMethods, {
+  get url()  {
+    return this.source.url
+  },
+
+  getText: function(cb) {
+    this.request('getText', this.pluck('text'), cb);
+  }
+});
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/lib/tab.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/lib/tab.js
new file mode 100644
index 0000000..5e19e1c
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/lib/tab.js
@@ -0,0 +1,87 @@
+var extend = require("./extend"),
+    ClientMethods = require("./client-methods"),
+    Console = require("./console"),
+    Memory = require("./memory"),
+    DOM = require("./dom"),
+    Network = require("./network"),
+    StyleSheets = require("./stylesheets");
+
+module.exports = Tab;
+
+function Tab(client, tab) {
+  this.initialize(client, tab.actor);
+
+  this.tab = tab;
+  this.updateInfo(tab);
+
+  this.on("tabNavigated", this.onTabNavigated.bind(this));
+}
+
+Tab.prototype = extend(ClientMethods, {
+  updateInfo: function(form) {
+    this.url = form.url;
+    this.title = form.title;
+  },
+
+  get StyleSheets() {
+    if (!this._StyleSheets) {
+      this._StyleSheets = new StyleSheets(this.client, this.tab.styleSheetsActor);
+    }
+    return this._StyleSheets;
+  },
+
+  get DOM() {
+    if (!this._DOM) {
+      this._DOM = new DOM(this.client, this.tab.inspectorActor);
+    }
+    return this._DOM;
+  },
+
+  get Network() {
+    if (!this._Network) {
+      this._Network = new Network(this.client, this.tab.consoleActor);
+    }
+    return this._Network;
+  },
+
+  get Console() {
+    if (!this._Console) {
+      this._Console = new Console(this.client, this.tab.consoleActor);
+    }
+    return this._Console;
+  },
+
+  get Memory() {
+    if (!this._Memory) {
+      this._Memory = new Memory(this.client, this.tab.memoryActor);
+    }
+    return this._Memory;
+  },
+
+  onTabNavigated: function(event) {
+    if (event.state == "start") {
+      this.emit("before-navigate", { url: event.url });
+    }
+    else if (event.state == "stop") {
+      this.updateInfo(event);
+
+      this.emit("navigate", { url: event.url, title: event.title });
+    }
+  },
+
+  attach: function(cb) {
+    this.request("attach", cb);
+  },
+
+  detach: function(cb) {
+    this.request("detach", cb);
+  },
+
+  reload: function(cb) {
+    this.request("reload", cb);
+  },
+
+  navigateTo: function(url, cb) {
+    this.request("navigateTo", { url: url }, cb);
+  }
+})
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/lib/webapps.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/lib/webapps.js
new file mode 100644
index 0000000..5ede808
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/lib/webapps.js
@@ -0,0 +1,170 @@
+var extend = require("./extend"),
+    ClientMethods = require("./client-methods"),
+    Tab = require("./tab"),
+    fs = require("fs"),
+    spawn = require("child_process").spawn;
+
+module.exports = Webapps;
+
+var CHUNK_SIZE = 20480;
+
+// Also dispatch appOpen/appClose, appInstall/appUninstall events
+function Webapps(client, tab) {
+  this.initialize(client, tab.webappsActor);
+}
+
+Webapps.prototype = extend(ClientMethods, {
+  watchApps: function(cb) {
+    this.request("watchApps", cb);
+  },
+  unwatchApps: function(cb) {
+    this.request("unwatchApps", cb);
+  },
+  launch: function(manifestURL, cb) {
+    this.request("launch", {manifestURL: manifestURL}, cb);
+  },
+  close: function(manifestURL, cb) {
+    this.request("close", {manifestURL: manifestURL}, cb);
+  },
+  getInstalledApps: function(cb) {
+    this.request("getAll", function (err, resp) {
+      if (err) {
+        cb(err);
+        return;
+      }
+      cb(null, resp.apps);
+    });
+  },
+  listRunningApps: function(cb) {
+    this.request("listRunningApps", function (err, resp) {
+      if (err) {
+        cb(err);
+        return;
+      }
+      cb(null, resp.apps);
+    });
+  },
+  getApp: function(manifestURL, cb) {
+    this.request("getAppActor", {manifestURL: manifestURL}, (function (err, resp) {
+      if (err) {
+        cb(err);
+        return;
+      }
+      var actor = new Tab(this.client, resp.actor);
+      cb(null, actor);
+    }).bind(this));
+  },
+  installHosted: function(options, cb) {
+    this.request(
+      "install",
+      { appId: options.appId,
+        metadata: options.metadata,
+        manifest: options.manifest },
+      function (err, resp) {
+        if (err || resp.error) {
+          cb(err || resp.error);
+          return;
+        }
+        cb(null, resp.appId);
+      });
+  },
+  _upload: function (path, cb) {
+    // First create an upload actor
+    this.request("uploadPackage", function (err, resp) {
+      var actor = resp.actor;
+      fs.readFile(path, function(err, data) {
+        chunk(actor, data);
+      });
+    });
+    // Send push the file chunk by chunk
+    var self = this;
+    var step = 0;
+    function chunk(actor, data) {
+      var i = step++ * CHUNK_SIZE;
+      var m = Math.min(i + CHUNK_SIZE, data.length);
+      var c = "";
+      for(; i < m; i++)
+        c += String.fromCharCode(data[i]);
+      var message = {
+        to: actor,
+        type: "chunk",
+        chunk: c
+      };
+      self.client.makeRequest(message, function(resp) {
+        if (resp.error) {
+          cb(resp);
+          return;
+        }
+        if (i < data.length) {
+          setTimeout(chunk, 0, actor, data);
+        } else {
+          done(actor);
+        }
+      });
+    }
+    // Finally close the upload
+    function done(actor) {
+      var message = {
+        to: actor,
+        type: "done"
+      };
+      self.client.makeRequest(message, function(resp) {
+        if (resp.error) {
+          cb(resp);
+        } else {
+          cb(null, actor, cleanup.bind(null, actor));
+        }
+      });
+    }
+
+    // Remove the temporary uploaded file from the server:
+    function cleanup(actor) {
+      var message = {
+        to: actor,
+        type: "remove"
+      };
+      self.client.makeRequest(message, function () {});
+    }
+  },
+  installPackaged: function(path, appId, cb) {
+    this._upload(path, (function (err, actor, cleanup) {
+      this.request("install", {appId: appId, upload: actor},
+        function (err, resp) {
+          if (err) {
+            cb(err);
+            return;
+          }
+          cb(null, resp.appId);
+          cleanup();
+        });
+    }).bind(this));
+  },
+  installPackagedWithADB: function(path, appId, cb) {
+    var self = this;
+    // First ensure the temporary folder exists
+    function createTemporaryFolder() {
+      var c = spawn("adb", ["shell", "mkdir -p /data/local/tmp/b2g/" + appId], {stdio:"inherit"});
+      c.on("close", uploadPackage);
+    }
+    // then upload the package to the temporary directory
+    function uploadPackage() {
+      var child = spawn("adb", ["push", path, "/data/local/tmp/b2g/" + appId + "/application.zip"], {stdio:"inherit"});
+      child.on("close", installApp);
+    }
+    // finally order the webapps actor to install the app
+    function installApp() {
+      self.request("install", {appId: appId},
+        function (err, resp) {
+          if (err) {
+            cb(err);
+            return;
+          }
+          cb(null, resp.appId);
+        });
+    }
+    createTemporaryFolder();
+  },
+  uninstall: function(manifestURL, cb) {
+    this.request("uninstall", {manifestURL: manifestURL}, cb);
+  }
+})
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/colors/MIT-LICENSE.txt b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/colors/MIT-LICENSE.txt
new file mode 100644
index 0000000..7df0d5e
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/colors/MIT-LICENSE.txt
@@ -0,0 +1,19 @@
+Copyright (c) 2010 Alexis Sellier (cloudhead) , Marak Squires
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/colors/ReadMe.md b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/colors/ReadMe.md
new file mode 100644
index 0000000..74acead
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/colors/ReadMe.md
@@ -0,0 +1,30 @@
+<h1>colors.js - get color and style in your node.js console like what</h1>
+
+<img src="http://i.imgur.com/goJdO.png" border = "0"/>
+
+       var sys = require('sys');
+       var colors = require('./colors');
+
+       sys.puts('hello'.green); // outputs green text
+       sys.puts('i like cake and pies'.underline.red) // outputs red underlined text
+       sys.puts('inverse the color'.inverse); // inverses the color
+       sys.puts('OMG Rainbows!'.rainbow); // rainbow (ignores spaces)
+       
+<h2>colors and styles!</h2>
+- bold
+- italic
+- underline
+- inverse
+- yellow
+- cyan
+- white
+- magenta
+- green
+- red
+- grey
+- blue
+
+
+### Authors 
+
+#### Alexis Sellier (cloudhead) , Marak Squires , Justin Campbell, Dustin Diaz (@ded)
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/colors/colors.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/colors/colors.js
new file mode 100644
index 0000000..8c1d706
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/colors/colors.js
@@ -0,0 +1,230 @@
+/*
+colors.js
+
+Copyright (c) 2010 Alexis Sellier (cloudhead) , Marak Squires
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+*/
+
+exports.mode = "console";
+
+// prototypes the string object to have additional method calls that add terminal colors
+
+var addProperty = function (color, func) {
+  exports[color] = function(str) {
+    return func.apply(str);
+  };
+  String.prototype.__defineGetter__(color, func);
+}
+
+var isHeadless = (typeof module !== 'undefined');
+['bold', 'underline', 'italic', 'inverse', 'grey', 'black', 'yellow', 'red', 'green', 'blue', 'white', 'cyan', 'magenta'].forEach(function (style) {
+
+  // __defineGetter__ at the least works in more browsers
+  // http://robertnyman.com/javascript/javascript-getters-setters.html
+  // Object.defineProperty only works in Chrome
+  addProperty(style, function () {
+    return isHeadless ?
+             stylize(this, style) : // for those running in node (headless environments)
+             this.replace(/( )/, '$1'); // and for those running in browsers:
+             // re: ^ you'd think 'return this' works (but doesn't) so replace coerces the string to be a real string
+  });
+});
+
+// prototypes string with method "rainbow"
+// rainbow will apply a the color spectrum to a string, changing colors every letter
+addProperty('rainbow', function () {
+  if (!isHeadless) {
+    return this.replace(/( )/, '$1');
+  }
+  var rainbowcolors = ['red','yellow','green','blue','magenta']; //RoY G BiV
+  var exploded = this.split("");
+  var i=0;
+  exploded = exploded.map(function(letter) {
+    if (letter==" ") {
+      return letter;
+    }
+    else {
+      return stylize(letter,rainbowcolors[i++ % rainbowcolors.length]);
+    }
+  });
+  return exploded.join("");
+});
+
+function stylize(str, style) {
+  if (exports.mode == 'console') {
+    var styles = {
+      //styles
+      'bold'      : ['\033[1m',  '\033[22m'],
+      'italic'    : ['\033[3m',  '\033[23m'],
+      'underline' : ['\033[4m',  '\033[24m'],
+      'inverse'   : ['\033[7m',  '\033[27m'],
+      //grayscale
+      'white'     : ['\033[37m', '\033[39m'],
+      'grey'      : ['\033[90m', '\033[39m'],
+      'black'     : ['\033[30m', '\033[39m'],
+      //colors
+      'blue'      : ['\033[34m', '\033[39m'],
+      'cyan'      : ['\033[36m', '\033[39m'],
+      'green'     : ['\033[32m', '\033[39m'],
+      'magenta'   : ['\033[35m', '\033[39m'],
+      'red'       : ['\033[31m', '\033[39m'],
+      'yellow'    : ['\033[33m', '\033[39m']
+    };
+  } else if (exports.mode == 'browser') {
+    var styles = {
+      //styles
+      'bold'      : ['<b>',  '</b>'],
+      'italic'    : ['<i>',  '</i>'],
+      'underline' : ['<u>',  '</u>'],
+      'inverse'   : ['<span style="background-color:black;color:white;">',  '</span>'],
+      //grayscale
+      'white'     : ['<span style="color:white;">',   '</span>'],
+      'grey'      : ['<span style="color:grey;">',    '</span>'],
+      'black'     : ['<span style="color:black;">',   '</span>'],
+      //colors
+      'blue'      : ['<span style="color:blue;">',    '</span>'],
+      'cyan'      : ['<span style="color:cyan;">',    '</span>'],
+      'green'     : ['<span style="color:green;">',   '</span>'],
+      'magenta'   : ['<span style="color:magenta;">', '</span>'],
+      'red'       : ['<span style="color:red;">',     '</span>'],
+      'yellow'    : ['<span style="color:yellow;">',  '</span>']
+    };
+  } else if (exports.mode == 'none') {
+      return str;
+  } else {
+    console.log('unsupported mode, try "browser", "console" or "none"');
+  }
+
+  return styles[style][0] + str + styles[style][1];
+};
+
+// don't summon zalgo
+addProperty('zalgo', function () {
+  return zalgo(this);
+});
+
+// please no
+function zalgo(text, options) {
+  var soul = {
+    "up" : [
+      '̍','̎','̄','̅',
+      '̿','̑','̆','̐',
+      '͒','͗','͑','̇',
+      '̈','̊','͂','̓',
+      '̈','͊','͋','͌',
+      '̃','̂','̌','͐',
+      '̀','́','̋','̏',
+      '̒','̓','̔','̽',
+      '̉','ͣ','ͤ','ͥ',
+      'ͦ','ͧ','ͨ','ͩ',
+      'ͪ','ͫ','ͬ','ͭ',
+      'ͮ','ͯ','̾','͛',
+      '͆','̚'
+      ],
+    "down" : [
+      '̖','̗','̘','̙',
+      '̜','̝','̞','̟',
+      '̠','̤','̥','̦',
+      '̩','̪','̫','̬',
+      '̭','̮','̯','̰',
+      '̱','̲','̳','̹',
+      '̺','̻','̼','ͅ',
+      '͇','͈','͉','͍',
+      '͎','͓','͔','͕',
+      '͖','͙','͚','̣'
+      ],
+    "mid" : [
+      '̕','̛','̀','́',
+      '͘','̡','̢','̧',
+      '̨','̴','̵','̶',
+      '͜','͝','͞',
+      '͟','͠','͢','̸',
+      '̷','͡',' ҉'
+      ]
+  },
+  all = [].concat(soul.up, soul.down, soul.mid),
+  zalgo = {};
+
+  function randomNumber(range) {
+    r = Math.floor(Math.random()*range);
+    return r;
+  };
+
+  function is_char(character) {
+    var bool = false;
+    all.filter(function(i){
+     bool = (i == character);
+    });
+    return bool;
+  }
+
+  function heComes(text, options){
+      result = '';
+      options = options || {};
+      options["up"] = options["up"] || true;
+      options["mid"] = options["mid"] || true;
+      options["down"] = options["down"] || true;
+      options["size"] = options["size"] || "maxi";
+      var counts;
+      text = text.split('');
+       for(var l in text){
+         if(is_char(l)) { continue; }
+         result = result + text[l];
+
+        counts = {"up" : 0, "down" : 0, "mid" : 0};
+
+        switch(options.size) {
+          case 'mini':
+            counts.up = randomNumber(8);
+            counts.min= randomNumber(2);
+            counts.down = randomNumber(8);
+          break;
+          case 'maxi':
+            counts.up = randomNumber(16) + 3;
+            counts.min = randomNumber(4) + 1;
+            counts.down = randomNumber(64) + 3;
+          break;
+          default:
+            counts.up = randomNumber(8) + 1;
+            counts.mid = randomNumber(6) / 2;
+            counts.down= randomNumber(8) + 1;
+          break;
+        }
+
+        var arr = ["up", "mid", "down"];
+        for(var d in arr){
+          var index = arr[d];
+          for (var i = 0 ; i <= counts[index]; i++)
+          {
+            if(options[index]) {
+                result = result + soul[index][randomNumber(soul[index].length)];
+              }
+            }
+          }
+        }
+      return result;
+  };
+  return heComes(text);
+}
+
+addProperty('stripColors', function() {
+  return ("" + this).replace(/\u001b\[\d+m/g,'');
+});
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/colors/example.html b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/colors/example.html
new file mode 100644
index 0000000..c9cd68c
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/colors/example.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html lang="en-us">
+  <head>
+    <meta http-equiv="Content-type" content="text/html; charset=utf-8">
+    <title>Colors Example</title>
+    <script src="colors.js"></script>
+    <script type="text/javascript">
+      console.log('Rainbows are fun!'.rainbow);
+      console.log('So '.italic + 'are'.underline + ' styles! '.bold + 'inverse'.inverse);
+      console.log('Chains are also cool.'.bold.italic.underline.red);
+    </script>
+  </head>
+  <body>
+    <script>
+      document.write('Rainbows are fun!'.rainbow + '<br>');
+      document.write('So '.italic + 'are'.underline + ' styles! '.bold + 'inverse'.inverse + '<br>');
+      document.write('Chains are also cool.'.bold.italic.underline.red);
+    </script>
+  </body>
+</html>
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/colors/example.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/colors/example.js
new file mode 100644
index 0000000..12d8b1a
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/colors/example.js
@@ -0,0 +1,20 @@
+var util = require('util');
+var colors = require('./colors');
+
+//colors.mode = "browser";
+
+var test = colors.red("hopefully colorless output");
+util.puts('Rainbows are fun!'.rainbow);
+util.puts('So '.italic + 'are'.underline + ' styles! '.bold + 'inverse'.inverse); // styles not widely supported
+util.puts('Chains are also cool.'.bold.italic.underline.red); // styles not widely supported
+//util.puts('zalgo time!'.zalgo);
+util.puts(test.stripColors);
+util.puts("a".grey + " b".black);
+
+util.puts(colors.rainbow('Rainbows are fun!'));
+util.puts(colors.italic('So ') + colors.underline('are') + colors.bold(' styles! ') + colors.inverse('inverse')); // styles not widely supported
+util.puts(colors.bold(colors.italic(colors.underline(colors.red('Chains are also cool.'))))); // styles not widely supported
+//util.puts(colors.zalgo('zalgo time!'));
+util.puts(colors.stripColors(test));
+util.puts(colors.grey("a") + colors.black(" b"));
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/colors/package.json b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/colors/package.json
new file mode 100644
index 0000000..b0b4f79
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/colors/package.json
@@ -0,0 +1,49 @@
+{
+  "name": "colors",
+  "description": "get colors in your node.js console like what",
+  "version": "0.5.1",
+  "author": {
+    "name": "Marak Squires"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/Marak/colors.js.git"
+  },
+  "engines": {
+    "node": ">=0.1.90"
+  },
+  "main": "colors",
+  "_npmJsonOpts": {
+    "file": "/Users/maraksquires/.npm/colors/0.5.1/package/package.json",
+    "wscript": false,
+    "contributors": false,
+    "serverjs": false
+  },
+  "_id": "colors@0.5.1",
+  "dependencies": {},
+  "devDependencies": {},
+  "_engineSupported": true,
+  "_npmVersion": "1.0.30",
+  "_nodeVersion": "v0.4.10",
+  "_defaultsLoaded": true,
+  "dist": {
+    "shasum": "7d0023eaeb154e8ee9fce75dcb923d0ed1667774",
+    "tarball": "http://registry.npmjs.org/colors/-/colors-0.5.1.tgz"
+  },
+  "maintainers": [
+    {
+      "name": "marak",
+      "email": "marak.squires@gmail.com"
+    }
+  ],
+  "directories": {},
+  "_shasum": "7d0023eaeb154e8ee9fce75dcb923d0ed1667774",
+  "_from": "colors@0.5.x",
+  "_resolved": "https://registry.npmjs.org/colors/-/colors-0.5.1.tgz",
+  "bugs": {
+    "url": "https://github.com/Marak/colors.js/issues"
+  },
+  "readme": "ERROR: No README data found!",
+  "homepage": "https://github.com/Marak/colors.js",
+  "scripts": {}
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/LICENSE b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/LICENSE
new file mode 100644
index 0000000..2983774
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/LICENSE
@@ -0,0 +1,21 @@
+Copyright (c) 2011 Heather Arthur
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/README.md b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/README.md
new file mode 100644
index 0000000..6c218a4
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/README.md
@@ -0,0 +1,118 @@
+# js-select
+
+js-select uses [js-traverse](https://github.com/substack/js-traverse) to traverse and modify JavaScript object nodes that match [JSONSelect](http://jsonselect.org/) selectors.
+
+```javascript
+var people = {
+   george: {
+      age : 35,
+      movie: "Repo Man"
+   },
+   mary: {
+      age: 15,
+      movie: "Twilight"
+   }
+};
+```
+
+### .forEach(fn)
+
+Iterates over all matching nodes in the object. The callback gets a special `this` context. See [js-traverse](https://github.com/substack/js-traverse) for all the things you can do to modify and inspect the node with this context. In addition, js-select adds a `this.matches()` which will test if the node matches a selector:
+
+```javascript
+select(people).forEach(function(node) {
+   if (this.matches(".mary > .movie")) {
+      this.remove();
+   }
+});
+```
+
+### .nodes()
+
+Returns all matching nodes from the object.
+
+```javascript
+select(people, ".age").nodes(); // [35, 15]
+```
+
+### .remove()
+
+Removes matching elements from the original object.
+
+```javascript
+select(people, ".age").remove();
+```
+
+### .update(fn)
+
+Updates all matching nodes using the given callback.
+
+```javascript
+select(people, ".age").update(function(age) {
+   return age - 5;
+});
+```
+
+### .condense()
+
+Reduces the original object down to only the matching elements (the hierarchy is maintained).
+
+```javascript
+select(people, ".age").condense();
+```
+
+```javascript
+{
+    george: { age: 35 },
+    mary: { age: 15 }
+}
+```
+
+## Selectors
+
+js-select supports the following [JSONSelect](http://jsonselect.org/) selectors:
+
+```
+*
+type
+.key
+ancestor selector
+parent > selector
+sibling ~ selector
+selector1, selector2
+:root
+:nth-child(n)
+:nth-child(even)
+:nth-child(odd)
+:nth-last-child(n)
+:first-child
+:last-child
+:only-child
+:has(selector)
+:val("string")
+:contains("substring")
+```
+
+See [details](http://jsonselect.org/#docs/overview) on each selector, and [try them](http://jsonselect.org/#tryit) out on the JSONSelect website.
+
+## Install
+
+For [node](http://nodejs.org), install with [npm](http://npmjs.org):
+
+```bash
+npm install js-select
+```
+
+For the browser, download the select.js file or fetch the latest version from [npm](http://npmjs.org) and build a browser file using [browserify](https://github.com/substack/node-browserify):
+
+```bash
+npm install browserify -g
+npm install js-select
+
+browserify --require js-select --outfile select.js
+```
+this will build a browser file with `require('js-select')` available.
+
+## Propers
+
+Huge thanks to [@substack](http://github.com/substack) for the ingenious [js-traverse](https://github.com/substack/js-traverse) and [@lloyd](https://github.com/lloyd) for the ingenious [JSONSelect spec](http://http://jsonselect.org/) and [selector parser](http://search.npmjs.org/#/JSONSelect).
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/index.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/index.js
new file mode 100644
index 0000000..1489b8e
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/index.js
@@ -0,0 +1,197 @@
+var traverse = require("traverse"),
+    JSONSelect = require("JSONSelect");
+
+module.exports = function(obj, string) {
+   var sels = parseSelectors(string);
+
+   return {
+      nodes: function() {
+         var nodes = [];
+         this.forEach(function(node) {
+            nodes.push(node);
+         });
+         return nodes;
+      },
+
+      update: function(cb) {
+         this.forEach(function(node) {
+            this.update(typeof cb == "function" ? cb(node) : cb);
+         });
+         return obj;
+      },
+
+      remove: function() {
+         this.forEach(function(node) {
+            this.remove();
+         })
+         return obj;
+      },
+
+      condense: function() {
+         traverse(obj).forEach(function(node) {
+            if (!this.parent) return;
+
+            if (this.parent.keep) {
+               this.keep = true;
+            } else {
+               var match = matchesAny(sels, this);
+               this.keep = match;
+               if (!match) {
+                  if (this.isLeaf) {
+                     this.remove();
+                  } else {
+                     this.after(function() {
+                        if (this.keep_child) {
+                           this.parent.keep_child = true;
+                        }
+                        if (!this.keep && !this.keep_child) {
+                           this.remove();
+                        }
+                     });
+                  }
+               } else {
+                  this.parent.keep_child = true;
+               }
+            }
+         });
+         return obj;
+      },
+
+      forEach: function(cb) {
+         traverse(obj).forEach(function(node) {
+            if (matchesAny(sels, this)) {
+               this.matches = function(string) {
+                  return matchesAny(parseSelectors(string), this);
+               };
+               // inherit context from js-traverse
+               cb.call(this, node);
+            }
+         });
+         return obj;
+      }
+   };
+}
+
+function parseSelectors(string) {
+   var parsed = JSONSelect._parse(string || "*")[1];
+   return getSelectors(parsed);
+}
+
+function getSelectors(parsed) {
+   if (parsed[0] == ",") {  // "selector1, selector2"
+      return parsed.slice(1);
+   }
+   return [parsed];
+}
+
+function matchesAny(sels, context) {
+   for (var i = 0; i < sels.length; i++) {
+      if (matches(sels[i], context)) {
+         return true;
+      }
+   }
+   return false;
+}
+
+function matches(sel, context) {
+   var path = context.parents.concat([context]),
+       i = path.length - 1,
+       j = sel.length - 1;
+
+   // walk up the ancestors
+   var must = true;
+   while(j >= 0 && i >= 0) {
+      var part = sel[j],
+          context = path[i];
+
+      if (part == ">") {
+         j--;
+         must = true;
+         continue;
+      }
+
+      if (matchesKey(part, context)) {
+         j--;
+      }
+      else if (must) {
+         return false;
+      }
+
+      i--;
+      must = false;
+   }
+   return j == -1;
+}
+
+function matchesKey(part, context) {
+   var key = context.key,
+       node = context.node,
+       parent = context.parent;
+
+   if (part.id && key != part.id) {
+      return false;
+   }
+   if (part.type) {
+      var type = part.type;
+
+      if (type == "null" && node !== null) {
+         return false;
+      }
+      else if (type == "array" && !isArray(node)) {
+         return false;
+      }
+      else if (type == "object" && (typeof node != "object"
+                 || node === null || isArray(node))) {
+         return false;
+      }
+      else if ((type == "boolean" || type == "string" || type == "number")
+               && type != typeof node) {
+         return false;
+      }
+   }
+   if (part.pf == ":nth-child") {
+      var index = parseInt(key) + 1;
+      if ((part.a == 0 && index !== part.b)         // :nth-child(i)
+        || (part.a == 1 && !(index >= -part.b))     // :nth-child(n)
+        || (part.a == -1 && !(index <= part.b))     // :nth-child(-n + 1)
+        || (part.a == 2 && index % 2 != part.b)) {  // :nth-child(even)
+         return false;
+      }
+   }
+   if (part.pf == ":nth-last-child"
+      && (!parent || key != parent.node.length - part.b)) {
+         return false;
+   }
+   if (part.pc == ":only-child"
+      && (!parent || parent.node.length != 1)) {
+         return false;
+   }
+   if (part.pc == ":root" && key !== undefined) {
+      return false;
+   }
+   if (part.has) {
+      var sels = getSelectors(part.has[0]),
+          match = false;
+      traverse(node).forEach(function(child) {
+         if (matchesAny(sels, this)) {
+            match = true;
+         }
+      });
+      if (!match) {
+         return false;
+      }
+   }
+   if (part.expr) {
+      var expr = part.expr, lhs = expr[0], op = expr[1], rhs = expr[2];
+      if (typeof node != "string"
+          || (!lhs && op == "=" && node != rhs)   // :val("str")
+          || (!lhs && op == "*=" && node.indexOf(rhs) == -1)) { // :contains("substr")
+         return false;
+      }
+   }
+   return true;
+}
+
+var isArray = Array.isArray || function(obj) {
+    return toString.call(obj) === '[object Array]';
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/.gitmodules b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/.gitmodules
new file mode 100644
index 0000000..15339d4
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "src/test/tests"]
+	path = src/test/tests
+	url = git://github.com/lloyd/JSONSelectTests
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/.npmignore b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/.npmignore
new file mode 100644
index 0000000..0e2021f
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/.npmignore
@@ -0,0 +1,5 @@
+*~
+\#*\#
+.DS_Store
+/src/dist
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/JSONSelect.md b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/JSONSelect.md
new file mode 100644
index 0000000..bdd8580
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/JSONSelect.md
@@ -0,0 +1,214 @@
+**WARNING**: This document is a work in progress, just like JSONSelect itself.
+View or contribute to the latest version [on github](http://github.com/lloyd/JSONSelect/blob/master/JSONSelect.md)
+
+# JSONSelect
+
+  1. [introduction](#introduction)
+  1. [levels](#levels)
+  1. [language overview](#overview)
+  1. [grouping](#grouping)
+  1. [selectors](#selectors)
+  1. [pseudo classes](#pseudo)
+  1. [expressions](#expressions)
+  1. [combinators](#combinators)
+  1. [grammar](#grammar)
+  1. [conformance tests](#tests)
+  1. [references](#references)
+
+## Introduction<a name="introduction"></a>
+
+JSONSelect defines a language very similar in syntax and structure to
+[CSS3 Selectors](http://www.w3.org/TR/css3-selectors/).  JSONSelect
+expressions are patterns which can be matched against JSON documents.
+
+Potential applications of JSONSelect include:
+
+  * Simplified programmatic matching of nodes within JSON documents.
+  * Stream filtering, allowing efficient and incremental matching of documents.
+  * As a query language for a document database.
+
+## Levels<a name="levels"></a>
+
+The specification of JSONSelect is broken into three levels.  Higher
+levels include more powerful constructs, and are likewise more
+complicated to implement and use.
+
+**JSONSelect Level 1** is a small subset of CSS3.  Every feature is
+derived from a CSS construct that directly maps to JSON.  A level 1
+implementation is not particularly complicated while providing basic
+querying features.
+
+**JSONSelect Level 2** builds upon Level 1 adapting more complex CSS
+constructs which allow expressions to include constraints such as
+patterns that match against values, and those which consider a node's
+siblings.  Level 2 is still a direct adaptation of CSS, but includes
+constructs whose semantic meaning is significantly changed.
+
+**JSONSelect Level 3** adds constructs which do not necessarily have a
+direct analog in CSS, and are added to increase the power and convenience
+of the selector language.  These include aliases, wholly new pseudo
+class functions, and more blue sky dreaming.
+
+## Language Overview<a name="overview"></a>
+
+<table>
+<tr><th>pattern</th><th>meaning</th><th>level</th></tr>
+<tr><td>*</td><td>Any node</td><td>1</td></tr>
+<tr><td>T</td><td>A node of type T, where T is one string, number, object, array, boolean, or null</td><td>1</td></tr>
+<tr><td>T.key</td><td>A node of type T which is the child of an object and is the value its parents key property</td><td>1</td></tr>
+<tr><td>T."complex key"</td><td>Same as previous, but with property name specified as a JSON string</td><td>1</td></tr>
+<tr><td>T:root</td><td>A node of type T which is the root of the JSON document</td><td>1</td></tr>
+<tr><td>T:nth-child(n)</td><td>A node of type T which is the nth child of an array parent</td><td>1</td></tr>
+<tr><td>T:nth-last-child(n)</td><td>A node of type T which is the nth child of an array parent counting from the end</td><td>2</td></tr>
+<tr><td>T:first-child</td><td>A node of type T which is the first child of an array parent (equivalent to T:nth-child(1)</td><td>1</td></tr>
+<tr><td>T:last-child</td><td>A node of type T which is the last child of an array parent (equivalent to T:nth-last-child(1)</td><td>2</td></tr>
+<tr><td>T:only-child</td><td>A node of type T which is the only child of an array parent</td><td>2</td></tr>
+<tr><td>T:empty</td><td>A node of type T which is an array or object with no child</td><td>2</td></tr>
+<tr><td>T U</td><td>A node of type U with an ancestor of type T</td><td>1</td></tr>
+<tr><td>T > U</td><td>A node of type U with a parent of type T</td><td>1</td></tr>
+<tr><td>T ~ U</td><td>A node of type U with a sibling of type T</td><td>2</td></tr>
+<tr><td>S1, S2</td><td>Any node which matches either selector S1 or S2</td><td>1</td></tr>
+<tr><td>T:has(S)</td><td>A node of type T which has a child node satisfying the selector S</td><td>3</td></tr>
+<tr><td>T:expr(E)</td><td>A node of type T with a value that satisfies the expression E</td><td>3</td></tr>
+<tr><td>T:val(V)</td><td>A node of type T with a value that is equal to V</td><td>3</td></tr>
+<tr><td>T:contains(S)</td><td>A node of type T with a string value contains the substring S</td><td>3</td></tr>
+</table>
+
+## Grouping<a name="grouping"></a>
+
+## Selectors<a name="selectors"></a>
+
+## Pseudo Classes<a name="pseudo"></a>
+
+## Expressions<a name="expressions"></a>
+
+## Combinators<a name="combinators"></a>
+
+## Grammar<a name="grammar"></a>
+
+(Adapted from [CSS3](http://www.w3.org/TR/css3-selectors/#descendant-combinators) and
+ [json.org](http://json.org/))
+
+    selectors_group
+      : selector [ `,` selector ]*
+      ;
+
+    selector
+      : simple_selector_sequence [ combinator simple_selector_sequence ]*
+      ;
+
+    combinator
+      : `>` | \s+
+      ;
+
+    simple_selector_sequence
+      /* why allow multiple HASH entities in the grammar? */
+      : [ type_selector | universal ]
+        [ class | pseudo ]*
+      | [ class | pseudo ]+
+      ;
+
+    type_selector
+      : `object` | `array` | `number` | `string` | `boolean` | `null`
+      ;
+
+    universal
+      : '*'
+      ;
+
+    class
+      : `.` name
+      | `.` json_string
+      ;
+
+    pseudo
+      /* Note that pseudo-elements are restricted to one per selector and */
+      /* occur only in the last simple_selector_sequence. */
+      : `:` pseudo_class_name
+      | `:` nth_function_name `(` nth_expression `)`
+      | `:has` `(`  selectors_group `)`
+      | `:expr` `(`  expr `)`
+      | `:contains` `(`  json_string `)`
+      | `:val` `(` val `)`
+      ;
+
+    pseudo_class_name
+      : `root` | `first-child` | `last-child` | `only-child`
+
+    nth_function_name
+      : `nth-child` | `nth-last-child`
+
+    nth_expression
+      /* expression is and of the form "an+b" */
+      : TODO
+      ;
+
+    expr
+      : expr binop expr
+      | '(' expr ')'
+      | val
+      ;
+
+    binop
+      : '*' | '/' | '%' | '+' | '-' | '<=' | '>=' | '$='
+      | '^=' | '*=' | '>' | '<' | '=' | '!=' | '&&' | '||'
+      ;
+
+    val
+      : json_number | json_string | 'true' | 'false' | 'null' | 'x'
+      ;
+
+    json_string
+      : `"` json_chars* `"`
+      ;
+
+    json_chars
+      : any-Unicode-character-except-"-or-\-or-control-character
+      |  `\"`
+      |  `\\`
+      |  `\/`
+      |  `\b`
+      |  `\f`
+      |  `\n`
+      |  `\r`
+      |  `\t`
+      |   \u four-hex-digits
+      ;
+
+    name
+      : nmstart nmchar*
+      ;
+
+    nmstart
+      : escape | [_a-zA-Z] | nonascii
+      ;
+
+    nmchar
+      : [_a-zA-Z0-9-]
+      | escape
+      | nonascii
+      ;
+
+    escape
+      : \\[^\r\n\f0-9a-fA-F]
+      ;
+
+    nonascii
+      : [^\0-0177]
+      ;
+
+## Conformance Tests<a name="tests"></a>
+
+See [https://github.com/lloyd/JSONSelectTests](https://github.com/lloyd/JSONSelectTests)
+
+## References<a name="references"></a>
+
+In no particular order.
+
+  * [http://json.org/](http://json.org/)
+  * [http://www.w3.org/TR/css3-selectors/](http://www.w3.org/TR/css3-selectors/)
+  * [http://ejohn.org/blog/selectors-that-people-actually-use/](http://ejohn.org/blog/selectors-that-people-actually-use/)
+  * [http://shauninman.com/archive/2008/05/05/css\_qualified\_selectors](  * http://shauninman.com/archive/2008/05/05/css_qualified_selectors)
+  * [http://snook.ca/archives/html\_and\_css/css-parent-selectors](http://snook.ca/archives/html_and_css/css-parent-selectors)
+  * [http://remysharp.com/2010/10/11/css-parent-selector/](http://remysharp.com/2010/10/11/css-parent-selector/)
+  * [https://github.com/jquery/sizzle/wiki/Sizzle-Home](https://github.com/jquery/sizzle/wiki/Sizzle-Home)
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/README.md b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/README.md
new file mode 100644
index 0000000..00a573e
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/README.md
@@ -0,0 +1,33 @@
+JSONSelect is *EXPERIMENTAL*, *ALPHA*, etc.
+
+JSONSelect defines a selector language similar to CSS intended for
+JSON documents.  For an introduction to the project see
+[jsonselect.org](http://jsonselect.org) or the [documentation](https://github.com/lloyd/JSONSelect/blob/master/JSONSelect.md).
+
+## Project Overview
+
+JSONSelect is an attempt to create a selector language similar to
+CSS for JSON objects.  A couple key goals of the project's include:
+
+  * **intuitive** - JSONSelect is meant to *feel like* CSS, meaning a developers with an understanding of CSS can probably guess most of the syntax.
+  * **expressive** - As JSONSelect evolves, it will include more of the most popular constructs from the CSS spec and popular implementations (like [sizzle](http://sizzlejs.com/)).  A successful result will be a good balance of simplicity and power.
+  * **language independence** - The project will avoid features which are unnecessarily tied to a particular implementation language.
+  * **incremental adoption** - JSONSelect features are broken in to conformance levels, to make it easier to build basic support and to allow incremental stabilization of the language.
+  * **efficient** - As many constructs of the language as possible will be able to be evaluated in a single document traversal.  This allows for efficient stream filtering.
+
+JSONSelect should make common operations easy, complex operations possible,
+but haughtily ignore weird shit.
+
+## What's Here
+
+This repository is the home to many things related to JSONSelect:
+
+  * [Documentation](https://github.com/lloyd/JSONSelect/blob/master/JSONSelect.md) which describes the language
+  * The [jsonselect.org](http://jsonselect.org) [site source](https://github.com/lloyd/JSONSelect/blob/master/site/)
+  * A [reference implementation](https://github.com/lloyd/JSONSelect/blob/master/src/jsonselect.js) in JavaScript
+
+## Related projects
+
+Conformance tests are broken out into a [separate
+repository](https://github.com/lloyd/JSONSelectTests) and may be used
+by other implementations.
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/package.json b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/package.json
new file mode 100644
index 0000000..fae9d34
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/package.json
@@ -0,0 +1,49 @@
+{
+  "author": {
+    "name": "Lloyd Hilaiel",
+    "email": "lloyd@hilaiel.com",
+    "url": "http://trickyco.de"
+  },
+  "name": "JSONSelect",
+  "description": "CSS-like selectors for JSON",
+  "version": "0.2.1",
+  "homepage": "http://jsonselect.org",
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/lloyd/JSONSelect.git"
+  },
+  "main": "src/jsonselect",
+  "engines": {
+    "node": ">=0.4.7"
+  },
+  "dependencies": {},
+  "devDependencies": {},
+  "files": [
+    "src/jsonselect.js",
+    "src/test/run.js",
+    "src/test/tests",
+    "tests",
+    "README.md",
+    "JSONSelect.md",
+    "package.json",
+    "LICENSE"
+  ],
+  "_id": "JSONSelect@0.2.1",
+  "_engineSupported": true,
+  "_npmVersion": "1.0.6",
+  "_nodeVersion": "v0.4.7",
+  "_defaultsLoaded": true,
+  "dist": {
+    "shasum": "415418a526d33fe31d74b4defa3c836d485ec203",
+    "tarball": "http://registry.npmjs.org/JSONSelect/-/JSONSelect-0.2.1.tgz"
+  },
+  "scripts": {},
+  "directories": {},
+  "_shasum": "415418a526d33fe31d74b4defa3c836d485ec203",
+  "_from": "JSONSelect@0.2.1",
+  "_resolved": "https://registry.npmjs.org/JSONSelect/-/JSONSelect-0.2.1.tgz",
+  "bugs": {
+    "url": "https://github.com/lloyd/JSONSelect/issues"
+  },
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/css/droid_sans.css b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/css/droid_sans.css
new file mode 100644
index 0000000..0826445
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/css/droid_sans.css
@@ -0,0 +1,6 @@
+@font-face {
+  font-family: 'Droid Sans';
+  font-style: normal;
+  font-weight: normal;
+  src: local('Droid Sans'), local('DroidSans'), url('droid_sans.tt') format('truetype');
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/css/droid_sans.tt b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/css/droid_sans.tt
new file mode 100644
index 0000000..efd1f8b
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/css/droid_sans.tt
Binary files differ
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/css/style.css b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/css/style.css
new file mode 100644
index 0000000..b4fbaa6
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/css/style.css
@@ -0,0 +1,209 @@
+body {
+    background-color: #191919;
+    color: #a1a1a1;
+    font-family: 'Droid Sans', arial, serif;
+    font-size: 14px;
+}
+
+#header {
+    position: fixed;
+    background-color: #191919;
+    opacity: .7;
+    color: #ccc;
+    width: 100%;
+    top: 0px;
+    left: 0px;
+    margin: 0px;
+    padding: 4px 10px 4px 10px;
+}
+
+#main {
+    margin-top: 100px;
+}
+
+#header b {
+    font-weight: bold;
+    color: #fff;
+}
+
+a {
+    color: #999;
+    text-decoration: underline;
+}
+
+a:hover {
+    color: #fff;
+}
+
+#header .title {
+    font-size: 400%;
+}
+
+#header .subtitle {
+    font-size: 150%;
+    margin-left: 1em;
+    font-style: italic;
+}
+
+#header .nav {
+    float: right;
+    margin-right: 100px;
+}
+
+div.content {
+    max-width: 850px;
+    min-width: 675px;
+    margin: auto;
+}
+
+div#tryit {
+    min-width: 825px;
+}
+
+#splash {
+    max-width: 700px;
+}
+
+.json {
+    background-color: #333;
+    padding: .5em 1em .5em 1em;
+    border: 3px solid #444;
+    border-radius: 1em;
+    -moz-border-radius: 1em;
+    -webkit-border-radius: 1em;
+}
+
+#splash .sample {
+    width: 250px;
+    float: right;
+    margin-left: 1em;
+    margin-top: 3em;
+    font-size: 90%;
+}
+
+#doc {
+    margin-top: 140px;
+}
+
+#doc table {
+    width: 100%;
+}
+
+#doc table th {
+    background-color: #aaa;
+    color: #333;
+    padding: 10px;
+}
+
+#doc table tr td:first-child {
+    font-family: "andale mono",monospace;
+}
+
+#doc table td {
+    padding-top: 3px;
+}
+
+#splash .sample tt {
+    font-size: 90%;
+    padding: 0 .2em 0 .2em;
+}
+
+#splash .sample pre {
+    border-top: 1px dashed #aaa;
+    padding-top: 1em;
+}
+
+#splash .pitch {
+    padding-top: 2em;
+    font-size: 170%;
+}
+
+.selected {
+    opacity: .7;
+    padding: 8px;
+    margin: -10px;
+    background-color: #fff475;
+    color: #000;
+    border: 2px solid white;
+    border-radius: 4px;
+    -moz-border-radius: 4px;
+    -webkit-border-radius: 4px;
+}
+
+div.current input, div.selector, pre, code, tt {
+    font-family: "andale mono",monospace;
+    font-size: .9em;
+}
+
+div.current {
+    width: 100%;
+    padding: 1em;
+}
+
+div.current input {
+    color: #fff;
+    font-size: 110%;
+    background-color: #333;
+    border: 1px solid #444;
+    padding: 8px;
+    width: 400px;
+    margin-left: 1em;
+    margin-right: 1em;
+    border-radius: 4px;
+    -moz-border-radius: 4px;
+    -webkit-border-radius: 4px;
+}
+
+pre.doc {
+    float: right;
+    padding: 1em 3em 1em 3em;
+    padding-right: 160px;
+    font-size: 90%;
+}
+
+.selectors {
+    margin: 1em;
+    background-color: #444;
+    width: 300px;
+    padding: 8px;
+    border-radius: 4px;
+    -moz-border-radius: 4px;
+    -webkit-border-radius: 4px;
+
+}
+.selectors .selector {
+    background-color: #333;
+    padding: .4em;
+    margin: 1px;
+    cursor: pointer;
+}
+
+.selectors .selector.inuse {
+    border: 1px solid #9f9;
+    margin: -1px;
+    margin-left: 0px;
+}
+
+.results.error {
+    color: #f99;
+}
+
+.results {
+    color: #9f9;
+    font-size: 90%;
+    width: 300px;
+}
+
+#cred, #code {
+    padding-top: 2em;
+}
+
+p.psst {
+    margin-top: 4em;
+    font-size: .9em;
+}
+
+p.item {
+    margin-top: 2em;
+    margin-left: 1em;
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/index.html b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/index.html
new file mode 100644
index 0000000..984e184
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/index.html
@@ -0,0 +1,139 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title> JSONSelect </title>
+    <link href='css/droid_sans.css' rel='stylesheet' type='text/css'>
+    <link href='css/style.css' type='text/css' rel='stylesheet'>
+  </head>
+  <body>
+    <div id="header">
+      <div class="content">
+        <div class="title">json<b>:</b>select<b>()</b></div>
+        <div class="nav"> <a href="#overview">Overview</a> | <a href="#tryit">Try It!</a> | <a href="#docs">Docs</a> | <a href="#code">Code</a> | <a href="mailto:jsonselect@librelist.com">Contact</a> | <a href="#cred">Credit</a> </div>
+        <div class="subtitle">CSS-like selectors for JSON.</div>
+      </div>
+    </div>
+    <div id="main">
+      <div style="display: none" id="splash" class="content">
+        <div class="sample json">
+          <tt>".author .drinkPref&nbsp;:first-child"</tt>
+          <pre>{
+  "author": {
+    "name": {
+      "first": "Lloyd",
+      "last": "Hilaiel"
+    },
+    "drinkPref": [
+      <span class="selected">"whiskey"</span>,
+      "beer",
+      "wine"
+    ],
+  },
+  "thing": "JSONSelect site",
+  "license": "<a href="http://creativecommons.org/licenses/by-sa/3.0/us/">(cc) BY-SA</a>"
+}</pre></div>
+        <div class="pitch">
+        <p>JSONSelect is an <i>experimental</i> selector language for JSON.</p>
+        <p>It makes it easy to access data  in complex JSON documents.</p>
+        <p>It <i>feels like</i> CSS.</p>
+        <p>Why not <a href="#tryit">give it a try</a>?
+        </div>
+      </div>
+      <div id="tryit" style="display: none" class="content">
+        <div class="current"> Current Selector (click to edit): <input type="text"></input><span class="results"></span></div>
+        <pre class="doc json">
+{
+    "name": {
+        "first": "Lloyd",
+        "last": "Hilaiel"
+    },
+    "favoriteColor": "yellow",
+    "languagesSpoken": [
+        {
+            "lang": "Bulgarian",
+            "level": "advanced"
+        },
+        {
+            "lang": "English",
+            "level": "native",
+            "preferred": true
+        },
+        {
+            "lang": "Spanish",
+            "level": "beginner"
+        }
+    ],
+    "seatingPreference": [
+        "window",
+        "aisle"
+    ],
+    "drinkPreference": [
+        "whiskey",
+        "beer",
+        "wine"
+    ],
+    "weight": 172
+}
+        </pre>
+        <div class="selectors">
+          <div class="title"> Choose A Selector... </div>
+          <div class="selector">.languagesSpoken .lang</div>
+          <div class="selector">.drinkPreference :first-child</div>
+          <div class="selector">."weight"</div>
+          <div class="selector">.lang</div>
+          <div class="selector">.favoriteColor</div>
+          <div class="selector">string.favoriteColor</div>
+          <div class="selector">string:last-child</div>
+          <div class="selector">string:nth-child(-n+2)</div>
+          <div class="selector">string:nth-child(odd)</div>
+          <div class="selector">string:nth-last-child(1)</div>
+          <div class="selector">:root</div>
+          <div class="selector">number</div>
+          <div class="selector">:has(:root > .preferred)</div>
+          <div class="selector">.preferred ~ .lang</div>
+          <div class="selector">:has(.lang:val("Spanish")) > .level</div>
+          <div class="selector">.lang:val("Bulgarian") ~ .level</div>
+          <div class="selector">.seatingPreference :nth-child(1)</div>
+          <div class="selector">.weight:expr(x<180) ~ .name .first</div>
+        </div>
+      </div>
+      <div style="display: none" id="cred" class="content">
+        <p>JSONSelect is dedicated to sad code everywhere that looks like this:</p>
+        <pre class="json">if (foo && foo.bar && foo.bar.baz && foo.bar.baz.length > 2)
+    return foo.bar.baz[2];
+return undefined;</pre>
+        <p><a href="http://trickyco.de/">Lloyd Hilaiel</a> started it, and is surrounded by many <a href="https://github.com/lloyd/JSONSelect/contributors">awesome contributors</a>.</p>
+        <p><a href="http://blog.mozilla.com/dherman/">Dave Herman</a> provided the name, and lots of encouragement.</p>
+        <p><a href="http://open-mike.org/">Mike Hanson</a> gave deep feedback and ideas.</p>
+        <p><a href="http://ejohn.org/">John Resig</a> unwittingly contributed his <a href="http://ejohn.org/blog/selectors-that-people-actually-use/">design thoughts</a>.</p>
+        <p>The jsonselect.org site design was inspired by <a href="http://www.stephenwildish.co.uk/">Stephen Wildish</a>.</p>
+        <p><a href="http://json.org/">JSON</a> and <a href="http://www.w3.org/TR/css3-selectors/">CSS3 Selectors</a> are the prerequisites to JSONSelect's existence, so thanks to you guys too.</p>
+        <p></p>
+      </div>
+      <div style="display: none" id="doc" class="content">
+        Loading documentation...
+      </div>
+      <div style="display: none" id="code" class="content">
+        <p>Several different implementations of JSONSelect are available:</p>
+        <p class="item"><b>JavaScript:</b> Get it <a href="js/jsonselect.js">documented</a>, or <a href="js/jsonselect.min.js">minified</a> (<i>2.9k</i> minified and gzipped).
+           The code is <a href="https://github.com/lloyd/JSONSelect">on github</a>.</p>
+        <p class="item"><b>node.js:</b> <tt>npm install JSONSelect</tt></p>
+        <p class="item"><b>ruby:</b> A gem by <a href="https://github.com/fd">Simon Menke</a>: <a href="https://github.com/fd/json_select">https://github.com/fd/json_select</a></p>
+        <p class="psst">
+          Something missing from this list?  Please, <a href="https://github.com/lloyd/JSONSelect/issues">tell me about it</a>.
+        </p>
+      </div>
+
+    </div>
+
+
+<a href="https://github.com/lloyd/JSONSelect"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://d3nwyuy0nl342s.cloudfront.net/img/4c7dc970b89fd04b81c8e221ba88ff99a06c6b61/687474703a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f77686974655f6666666666662e706e67" alt="Fork me on GitHub"></a>
+  </body>
+  <script src="js/json2.js"></script>
+  <script src="js/showdown.js"></script>
+  <script src="js/jquery-1.6.1.min.js"></script>
+  <script src="js/jquery.ba-hashchange.min.js"></script>
+  <script src="js/jsonselect.js"></script>
+  <script src="js/nav.js"></script>
+  <script src="js/demo.js"></script>
+</html>
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/demo.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/demo.js
new file mode 100644
index 0000000..7462cf9
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/demo.js
@@ -0,0 +1,64 @@
+window.jsel = JSONSelect;
+
+$(document).ready(function() {
+    var theDoc = JSON.parse($("pre.doc").text());
+
+    function highlightMatches(ar) {
+        // first calculate match offsets
+        var wrk = [];
+        var html = $.trim(JSON.stringify(theDoc, undefined, 4));
+        var ss = "<span class=\"selected\">";
+        var es = "</span>";
+        for (var i = 0; i < ar.length; i++) {
+            var found = $.trim(JSON.stringify(ar[i], undefined, 4));
+            // turn the string into a regex to handle indentation
+            found = found.replace(/[-[\]{}()*+?.,\\^$|#]/g, "\\$&").replace(/\s+/gm, "\\s*");
+            var re = new RegExp(found, "m");
+            var m = re.exec(html);
+            if (!m) continue;
+            wrk.push({ off: m.index, typ: "s" });
+            wrk.push({ off: m[0].length+m.index, typ: "e" });
+        }
+        // sort by offset
+        wrk = wrk.sort(function(a,b) { return a.off - b.off; });
+
+        // now start injecting spans into the text
+        var cur = 0;
+        var cons = 0;
+        for (var i = 0; i < wrk.length; i++) {
+            var diff = wrk[i].off - cons;
+            cons = wrk[i].off;
+            var tag = (wrk[i].typ == 's' ? ss : es);
+            cur += diff;
+            html = html.substr(0, cur) + tag + html.substr(cur);
+            cur += tag.length;
+        }
+        return html;
+    }
+
+    // when a selector is chosen, update the text box
+    $(".selectors .selector").click(function() {
+        $(".current input").val($(this).text()).keyup();
+    });
+
+    var lastSel;
+    $(".current input").keyup(function () {
+        try {
+            var sel = $(".current input").val()
+            if (lastSel === $.trim(sel)) return;
+            lastSel = $.trim(sel);
+            var ar = jsel.match(sel, theDoc);
+            $(".current .results").text(ar.length + " match" + (ar.length == 1 ? "" : "es"))
+                .removeClass("error");
+            $("pre.doc").html(highlightMatches(ar));
+            $("pre.doc .selected").hide().fadeIn(700);
+        } catch(e) {
+            $(".current .results").text(e.toString()).addClass("error");
+            $("pre.doc").text($.trim(JSON.stringify(theDoc, undefined, 4)));
+        }
+        $(".selectors .selector").removeClass("inuse");
+        $(".selectors div.selector").each(function() {
+            if ($(this).text() === sel) $(this).addClass("inuse");
+        });
+    });
+});
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/jquery-1.6.1.min.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/jquery-1.6.1.min.js
new file mode 100644
index 0000000..b2ac174
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/jquery-1.6.1.min.js
@@ -0,0 +1,18 @@
+/*!
+ * jQuery JavaScript Library v1.6.1
+ * http://jquery.com/
+ *
+ * Copyright 2011, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ * Copyright 2011, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ *
+ * Date: Thu May 12 15:04:36 2011 -0400
+ */
+(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cv(a){if(!cj[a]){var b=f("<"+a+">").appendTo("body"),d=b.css("display");b.remove();if(d==="none"||d===""){ck||(ck=c.createElement("iframe"),ck.frameBorder=ck.width=ck.height=0),c.body.appendChild(ck);if(!cl||!ck.createElement)cl=(ck.contentWindow||ck.contentDocument).document,cl.write("<!doctype><html><body></body></html>");b=cl.createElement(a),cl.body.appendChild(b),d=f.css(b,"display"),c.body.removeChild(ck)}cj[a]=d}return cj[a]}function cu(a,b){var c={};f.each(cp.concat.apply([],cp.slice(0,b)),function(){c[this]=a});return c}function ct(){cq=b}function cs(){setTimeout(ct,0);return cq=f.now()}function ci(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ch(){try{return new a.XMLHttpRequest}catch(b){}}function cb(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g<i;g++){if(g===1)for(h in a.converters)typeof h=="string"&&(e[h.toLowerCase()]=a.converters[h]);l=k,k=d[g];if(k==="*")k=l;else if(l!=="*"&&l!==k){m=l+" "+k,n=e[m]||e["* "+k];if(!n){p=b;for(o in e){j=o.split(" ");if(j[0]===l||j[0]==="*"){p=e[j[1]+" "+k];if(p){o=e[o],o===!0?n=p:p===!0&&(n=o);break}}}}!n&&!p&&f.error("No conversion from "+m.replace(" "," to ")),n!==!0&&(c=n?n(c):p(o(c)))}}return c}function ca(a,c,d){var e=a.contents,f=a.dataTypes,g=a.responseFields,h,i,j,k;for(i in g)i in d&&(c[g[i]]=d[i]);while(f[0]==="*")f.shift(),h===b&&(h=a.mimeType||c.getResponseHeader("content-type"));if(h)for(i in e)if(e[i]&&e[i].test(h)){f.unshift(i);break}if(f[0]in d)j=f[0];else{for(i in d){if(!f[0]||a.converters[i+" "+f[0]]){j=i;break}k||(k=i)}j=j||k}if(j){j!==f[0]&&f.unshift(j);return d[j]}}function b_(a,b,c,d){if(f.isArray(b))f.each(b,function(b,e){c||bF.test(a)?d(a,e):b_(a+"["+(typeof e=="object"||f.isArray(e)?b:"")+"]",e,c,d)});else if(!c&&b!=null&&typeof b=="object")for(var e in b)b_(a+"["+e+"]",b[e],c,d);else d(a,b)}function b$(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h=a[f],i=0,j=h?h.length:0,k=a===bU,l;for(;i<j&&(k||!l);i++)l=h[i](c,d,e),typeof l=="string"&&(!k||g[l]?l=b:(c.dataTypes.unshift(l),l=b$(a,c,d,e,l,g)));(k||!l)&&!g["*"]&&(l=b$(a,c,d,e,"*",g));return l}function bZ(a){return function(b,c){typeof b!="string"&&(c=b,b="*");if(f.isFunction(c)){var d=b.toLowerCase().split(bQ),e=0,g=d.length,h,i,j;for(;e<g;e++)h=d[e],j=/^\+/.test(h),j&&(h=h.substr(1)||"*"),i=a[h]=a[h]||[],i[j?"unshift":"push"](c)}}}function bD(a,b,c){var d=b==="width"?bx:by,e=b==="width"?a.offsetWidth:a.offsetHeight;if(c==="border")return e;f.each(d,function(){c||(e-=parseFloat(f.css(a,"padding"+this))||0),c==="margin"?e+=parseFloat(f.css(a,"margin"+this))||0:e-=parseFloat(f.css(a,"border"+this+"Width"))||0});return e}function bn(a,b){b.src?f.ajax({url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(bf,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)}function bm(a){f.nodeName(a,"input")?bl(a):a.getElementsByTagName&&f.grep(a.getElementsByTagName("input"),bl)}function bl(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bk(a){return"getElementsByTagName"in a?a.getElementsByTagName("*"):"querySelectorAll"in a?a.querySelectorAll("*"):[]}function bj(a,b){var c;if(b.nodeType===1){b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase();if(c==="object")b.outerHTML=a.outerHTML;else if(c!=="input"||a.type!=="checkbox"&&a.type!=="radio"){if(c==="option")b.selected=a.defaultSelected;else if(c==="input"||c==="textarea")b.defaultValue=a.defaultValue}else a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value);b.removeAttribute(f.expando)}}function bi(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c=f.expando,d=f.data(a),e=f.data(b,d);if(d=d[c]){var g=d.events;e=e[c]=f.extend({},d);if(g){delete e.handle,e.events={};for(var h in g)for(var i=0,j=g[h].length;i<j;i++)f.event.add(b,h+(g[h][i].namespace?".":"")+g[h][i].namespace,g[h][i],g[h][i].data)}}}}function bh(a,b){return f.nodeName(a,"table")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function X(a,b,c){b=b||0;if(f.isFunction(b))return f.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return f.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=f.grep(a,function(a){return a.nodeType===1});if(S.test(b))return f.filter(b,d,!c);b=f.filter(b,d)}return f.grep(a,function(a,d){return f.inArray(a,b)>=0===c})}function W(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function O(a,b){return(a&&a!=="*"?a+".":"")+b.replace(A,"`").replace(B,"&")}function N(a){var b,c,d,e,g,h,i,j,k,l,m,n,o,p=[],q=[],r=f._data(this,"events");if(!(a.liveFired===this||!r||!r.live||a.target.disabled||a.button&&a.type==="click")){a.namespace&&(n=new RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)")),a.liveFired=this;var s=r.live.slice(0);for(i=0;i<s.length;i++)g=s[i],g.origType.replace(y,"")===a.type?q.push(g.selector):s.splice(i--,1);e=f(a.target).closest(q,a.currentTarget);for(j=0,k=e.length;j<k;j++){m=e[j];for(i=0;i<s.length;i++){g=s[i];if(m.selector===g.selector&&(!n||n.test(g.namespace))&&!m.elem.disabled){h=m.elem,d=null;if(g.preType==="mouseenter"||g.preType==="mouseleave")a.type=g.preType,d=f(a.relatedTarget).closest(g.selector)[0],d&&f.contains(h,d)&&(d=h);(!d||d!==h)&&p.push({elem:h,handleObj:g,level:m.level})}}}for(j=0,k=p.length;j<k;j++){e=p[j];if(c&&e.level>c)break;a.currentTarget=e.elem,a.data=e.handleObj.data,a.handleObj=e.handleObj,o=e.handleObj.origHandler.apply(e.elem,arguments);if(o===!1||a.isPropagationStopped()){c=e.level,o===!1&&(b=!1);if(a.isImmediatePropagationStopped())break}}return b}}function L(a,c,d){var e=f.extend({},d[0]);e.type=a,e.originalEvent={},e.liveFired=b,f.event.handle.call(c,e),e.isDefaultPrevented()&&d[0].preventDefault()}function F(){return!0}function E(){return!1}function m(a,c,d){var e=c+"defer",g=c+"queue",h=c+"mark",i=f.data(a,e,b,!0);i&&(d==="queue"||!f.data(a,g,b,!0))&&(d==="mark"||!f.data(a,h,b,!0))&&setTimeout(function(){!f.data(a,g,b,!0)&&!f.data(a,h,b,!0)&&(f.removeData(a,e,!0),i.resolve())},0)}function l(a){for(var b in a)if(b!=="toJSON")return!1;return!0}function k(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(j,"$1-$2").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNaN(d)?i.test(d)?f.parseJSON(d):d:parseFloat(d)}catch(g){}f.data(a,c,d)}else d=b}return d}var c=a.document,d=a.navigator,e=a.location,f=function(){function H(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(H,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/\d/,n=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,o=/^[\],:{}\s]*$/,p=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,q=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,r=/(?:^|:|,)(?:\s*\[)+/g,s=/(webkit)[ \/]([\w.]+)/,t=/(opera)(?:.*version)?[ \/]([\w.]+)/,u=/(msie) ([\w.]+)/,v=/(mozilla)(?:.*? rv:([\w.]+))?/,w=d.userAgent,x,y,z,A=Object.prototype.toString,B=Object.prototype.hasOwnProperty,C=Array.prototype.push,D=Array.prototype.slice,E=String.prototype.trim,F=Array.prototype.indexOf,G={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=n.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.6.1",length:0,size:function(){return this.length},toArray:function(){return D.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?C.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),y.done(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(D.apply(this,arguments),"slice",D.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:C,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j<k;j++)if((a=arguments[j])!=null)for(c in a){d=i[c],f=a[c];if(i===f)continue;l&&f&&(e.isPlainObject(f)||(g=e.isArray(f)))?(g?(g=!1,h=d&&e.isArray(d)?d:[]):h=d&&e.isPlainObject(d)?d:{},i[c]=e.extend(l,h,f)):f!==b&&(i[c]=f)}return i},e.extend({noConflict:function(b){a.$===e&&(a.$=g),b&&a.jQuery===e&&(a.jQuery=f);return e},isReady:!1,readyWait:1,holdReady:function(a){a?e.readyWait++:e.ready(!0)},ready:function(a){if(a===!0&&!--e.readyWait||a!==!0&&!e.isReady){if(!c.body)return setTimeout(e.ready,1);e.isReady=!0;if(a!==!0&&--e.readyWait>0)return;y.resolveWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").unbind("ready")}},bindReady:function(){if(!y){y=e._Deferred();if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",z,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",z),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&H()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNaN:function(a){return a==null||!m.test(a)||isNaN(a)},type:function(a){return a==null?String(a):G[A.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;if(a.constructor&&!B.call(a,"constructor")&&!B.call(a.constructor.prototype,"isPrototypeOf"))return!1;var c;for(c in a);return c===b||B.call(a,c)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw a},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(o.test(b.replace(p,"@").replace(q,"]").replace(r,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(b,c,d){a.DOMParser?(d=new DOMParser,c=d.parseFromString(b,"text/xml")):(c=new ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(b)),d=c.documentElement,(!d||!d.nodeName||d.nodeName==="parsererror")&&e.error("Invalid XML: "+b);return c},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g<h;)if(c.apply(a[g++],d)===!1)break}else if(i){for(f in a)if(c.call(a[f],f,a[f])===!1)break}else for(;g<h;)if(c.call(a[g],g,a[g++])===!1)break;return a},trim:E?function(a){return a==null?"":E.call(a)}:function(a){return a==null?"":(a+"").replace(k,"").replace(l,"")},makeArray:function(a,b){var c=b||[];if(a!=null){var d=e.type(a);a.length==null||d==="string"||d==="function"||d==="regexp"||e.isWindow(a)?C.call(c,a):e.merge(c,a)}return c},inArray:function(a,b){if(F)return F.call(b,a);for(var c=0,d=b.length;c<d;c++)if(b[c]===a)return c;return-1},merge:function(a,c){var d=a.length,e=0;if(typeof c.length=="number")for(var f=c.length;e<f;e++)a[d++]=c[e];else while(c[e]!==b)a[d++]=c[e++];a.length=d;return a},grep:function(a,b,c){var d=[],e;c=!!c;for(var f=0,g=a.length;f<g;f++)e=!!b(a[f],f),c!==e&&d.push(a[f]);return d},map:function(a,c,d){var f,g,h=[],i=0,j=a.length,k=a instanceof e||j!==b&&typeof j=="number"&&(j>0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i<j;i++)f=c(a[i],i,d),f!=null&&(h[h.length]=f);else for(g in a)f=c(a[g],g,d),f!=null&&(h[h.length]=f);return h.concat.apply([],h)},guid:1,proxy:function(a,c){if(typeof c=="string"){var d=a[c];c=a,a=d}if(!e.isFunction(a))return b;var f=D.call(arguments,2),g=function(){return a.apply(c,f.concat(D.call(arguments)))};g.guid=a.guid=a.guid||g.guid||e.guid++;return g},access:function(a,c,d,f,g,h){var i=a.length;if(typeof c=="object"){for(var j in c)e.access(a,j,c[j],f,g,d);return a}if(d!==b){f=!h&&f&&e.isFunction(d);for(var k=0;k<i;k++)g(a[k],c,f?d.call(a[k],k,g(a[k],c)):d,h);return a}return i?g(a[0],c):b},now:function(){return(new Date).getTime()},uaMatch:function(a){a=a.toLowerCase();var b=s.exec(a)||t.exec(a)||u.exec(a)||a.indexOf("compatible")<0&&v.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},sub:function(){function a(b,c){return new a.fn.init(b,c)}e.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function(d,f){f&&f instanceof e&&!(f instanceof a)&&(f=a(f));return e.fn.init.call(this,d,f,b)},a.fn.init.prototype=a.fn;var b=a(c);return a},browser:{}}),e.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){G["[object "+b+"]"]=b.toLowerCase()}),x=e.uaMatch(w),x.browser&&(e.browser[x.browser]=!0,e.browser.version=x.version),e.browser.webkit&&(e.browser.safari=!0),j.test(" ")&&(k=/^[\s\xA0]+/,l=/[\s\xA0]+$/),h=e(c),c.addEventListener?z=function(){c.removeEventListener("DOMContentLoaded",z,!1),e.ready()}:c.attachEvent&&(z=function(){c.readyState==="complete"&&(c.detachEvent("onreadystatechange",z),e.ready())});return e}(),g="done fail isResolved isRejected promise then always pipe".split(" "),h=[].slice;f.extend({_Deferred:function(){var a=[],b,c,d,e={done:function(){if(!d){var c=arguments,g,h,i,j,k;b&&(k=b,b=0);for(g=0,h=c.length;g<h;g++)i=c[g],j=f.type(i),j==="array"?e.done.apply(e,i):j==="function"&&a.push(i);k&&e.resolveWith(k[0],k[1])}return this},resolveWith:function(e,f){if(!d&&!b&&!c){f=f||[],c=1;try{while(a[0])a.shift().apply(e,f)}finally{b=[e,f],c=0}}return this},resolve:function(){e.resolveWith(this,arguments);return this},isResolved:function(){return!!c||!!b},cancel:function(){d=1,a=[];return this}};return e},Deferred:function(a){var b=f._Deferred(),c=f._Deferred(),d;f.extend(b,{then:function(a,c){b.done(a).fail(c);return this},always:function(){return b.done.apply(b,arguments).fail.apply(this,arguments)},fail:c.done,rejectWith:c.resolveWith,reject:c.resolve,isRejected:c.isResolved,pipe:function(a,c){return f.Deferred(function(d){f.each({done:[a,"resolve"],fail:[c,"reject"]},function(a,c){var e=c[0],g=c[1],h;f.isFunction(e)?b[a](function(){h=e.apply(this,arguments),h&&f.isFunction(h.promise)?h.promise().then(d.resolve,d.reject):d[g](h)}):b[a](d[g])})}).promise()},promise:function(a){if(a==null){if(d)return d;d=a={}}var c=g.length;while(c--)a[g[c]]=b[g[c]];return a}}),b.done(c.cancel).fail(b.cancel),delete b.cancel,a&&a.call(b,b);return b},when:function(a){function i(a){return function(c){b[a]=arguments.length>1?h.call(arguments,0):c,--e||g.resolveWith(g,h.call(b,0))}}var b=arguments,c=0,d=b.length,e=d,g=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred();if(d>1){for(;c<d;c++)b[c]&&f.isFunction(b[c].promise)?b[c].promise().then(i(c),g.reject):--e;e||g.resolveWith(g,b)}else g!==a&&g.resolveWith(g,d?[a]:[]);return g.promise()}}),f.support=function(){var a=c.createElement("div"),b=c.documentElement,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r;a.setAttribute("className","t"),a.innerHTML="   <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>",d=a.getElementsByTagName("*"),e=a.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};f=c.createElement("select"),g=f.appendChild(c.createElement("option")),h=a.getElementsByTagName("input")[0],j={leadingWhitespace:a.firstChild.nodeType===3,tbody:!a.getElementsByTagName("tbody").length,htmlSerialize:!!a.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55$/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:h.value==="on",optSelected:g.selected,getSetAttribute:a.className!=="t",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},h.checked=!0,j.noCloneChecked=h.cloneNode(!0).checked,f.disabled=!0,j.optDisabled=!g.disabled;try{delete a.test}catch(s){j.deleteExpando=!1}!a.addEventListener&&a.attachEvent&&a.fireEvent&&(a.attachEvent("onclick",function b(){j.noCloneEvent=!1,a.detachEvent("onclick",b)}),a.cloneNode(!0).fireEvent("onclick")),h=c.createElement("input"),h.value="t",h.setAttribute("type","radio"),j.radioValue=h.value==="t",h.setAttribute("checked","checked"),a.appendChild(h),k=c.createDocumentFragment(),k.appendChild(a.firstChild),j.checkClone=k.cloneNode(!0).cloneNode(!0).lastChild.checked,a.innerHTML="",a.style.width=a.style.paddingLeft="1px",l=c.createElement("body"),m={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"};for(q in m)l.style[q]=m[q];l.appendChild(a),b.insertBefore(l,b.firstChild),j.appendChecked=h.checked,j.boxModel=a.offsetWidth===2,"zoom"in a.style&&(a.style.display="inline",a.style.zoom=1,j.inlineBlockNeedsLayout=a.offsetWidth===2,a.style.display="",a.innerHTML="<div style='width:4px;'></div>",j.shrinkWrapBlocks=a.offsetWidth!==2),a.innerHTML="<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>",n=a.getElementsByTagName("td"),r=n[0].offsetHeight===0,n[0].style.display="",n[1].style.display="none",j.reliableHiddenOffsets=r&&n[0].offsetHeight===0,a.innerHTML="",c.defaultView&&c.defaultView.getComputedStyle&&(i=c.createElement("div"),i.style.width="0",i.style.marginRight="0",a.appendChild(i),j.reliableMarginRight=(parseInt((c.defaultView.getComputedStyle(i,null)||{marginRight:0}).marginRight,10)||0)===0),l.innerHTML="",b.removeChild(l);if(a.attachEvent)for(q in{submit:1,change:1,focusin:1})p="on"+q,r=p in a,r||(a.setAttribute(p,"return;"),r=typeof a[p]=="function"),j[q+"Bubbles"]=r;return j}(),f.boxModel=f.support.boxModel;var i=/^(?:\{.*\}|\[.*\])$/,j=/([a-z])([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!l(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g=f.expando,h=typeof c=="string",i,j=a.nodeType,k=j?f.cache:a,l=j?a[f.expando]:a[f.expando]&&f.expando;if((!l||e&&l&&!k[l][g])&&h&&d===b)return;l||(j?a[f.expando]=l=++f.uuid:l=f.expando),k[l]||(k[l]={},j||(k[l].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?k[l][g]=f.extend(k[l][g],c):k[l]=f.extend(k[l],c);i=k[l],e&&(i[g]||(i[g]={}),i=i[g]),d!==b&&(i[f.camelCase(c)]=d);if(c==="events"&&!i[c])return i[g]&&i[g].events;return h?i[f.camelCase(c)]:i}},removeData:function(b,c,d){if(!!f.acceptData(b)){var e=f.expando,g=b.nodeType,h=g?f.cache:b,i=g?b[f.expando]:f.expando;if(!h[i])return;if(c){var j=d?h[i][e]:h[i];if(j){delete j[c];if(!l(j))return}}if(d){delete h[i][e];if(!l(h[i]))return}var k=h[i][e];f.support.deleteExpando||h!=a?delete h[i]:h[i]=null,k?(h[i]={},g||(h[i].toJSON=f.noop),h[i][e]=k):g&&(f.support.deleteExpando?delete b[f.expando]:b.removeAttribute?b.removeAttribute(f.expando):b[f.expando]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d=null;if(typeof a=="undefined"){if(this.length){d=f.data(this[0]);if(this[0].nodeType===1){var e=this[0].attributes,g;for(var h=0,i=e.length;h<i;h++)g=e[h].name,g.indexOf("data-")===0&&(g=f.camelCase(g.substring(5)),k(this[0],g,d[g]))}}return d}if(typeof a=="object")return this.each(function(){f.data(this,a)});var j=a.split(".");j[1]=j[1]?"."+j[1]:"";if(c===b){d=this.triggerHandler("getData"+j[1]+"!",[j[0]]),d===b&&this.length&&(d=f.data(this[0],a),d=k(this[0],a,d));return d===b&&j[1]?this.data(j[0]):d}return this.each(function(){var b=f(this),d=[j[0],c];b.triggerHandler("setData"+j[1]+"!",d),f.data(this,a,c),b.triggerHandler("changeData"+j[1]+"!",d)})},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,c){a&&(c=(c||"fx")+"mark",f.data(a,c,(f.data(a,c,b,!0)||0)+1,!0))},_unmark:function(a,c,d){a!==!0&&(d=c,c=a,a=!1);if(c){d=d||"fx";var e=d+"mark",g=a?0:(f.data(c,e,b,!0)||1)-1;g?f.data(c,e,g,!0):(f.removeData(c,e,!0),m(c,d,"mark"))}},queue:function(a,c,d){if(a){c=(c||"fx")+"queue";var e=f.data(a,c,b,!0);d&&(!e||f.isArray(d)?e=f.data(a,c,f.makeArray(d),!0):e.push(d));return e||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e;d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),d.call(a,function(){f.dequeue(a,b)})),c.length||(f.removeData(a,b+"queue",!0),m(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){typeof a!="string"&&(c=a,a="fx");if(c===b)return f.queue(this[0],a);return this.each(function(){var b=f.queue(this,a,c);a==="fx"&&b[0]!=="inprogress"&&f.dequeue(this,a)})},dequeue:function(a){return this.each(function(){f.dequeue(this,a)})},delay:function(a,b){a=f.fx?f.fx.speeds[a]||a:a,b=b||"fx";return this.queue(b,function(){var c=this;setTimeout(function(){f.dequeue(c,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){function m(){--h||d.resolveWith(e,[e])}typeof a!="string"&&(c=a,a=b),a=a||"fx";var d=f.Deferred(),e=this,g=e.length,h=1,i=a+"defer",j=a+"queue",k=a+"mark",l;while(g--)if(l=f.data(e[g],i,b,!0)||(f.data(e[g],j,b,!0)||f.data(e[g],k,b,!0))&&f.data(e[g],i,f._Deferred(),!0))h++,l.done(m);m();return d.promise()}});var n=/[\n\t\r]/g,o=/\s+/,p=/\r/g,q=/^(?:button|input)$/i,r=/^(?:button|input|object|select|textarea)$/i,s=/^a(?:rea)?$/i,t=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,u=/\:/,v,w;f.fn.extend({attr:function(a,b){return f.access(this,a,b,!0,f.attr)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,a,b,!0,f.prop)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.addClass(a.call(this,b,c.attr("class")||""))});if(a&&typeof a=="string"){var b=(a||"").split(o);for(var c=0,d=this.length;c<d;c++){var e=this[c];if(e.nodeType===1)if(!e.className)e.className=a;else{var g=" "+e.className+" ",h=e.className;for(var i=0,j=b.length;i<j;i++)g.indexOf(" "+b[i]+" ")<0&&(h+=" "+b[i]);e.className=f.trim(h)}}}return this},removeClass:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.removeClass(a.call(this,b,c.attr("class")))});if(a&&typeof a=="string"||a===b){var c=(a||"").split(o);for(var d=0,e=this.length;d<e;d++){var g=this[d];if(g.nodeType===1&&g.className)if(a){var h=(" "+g.className+" ").replace(n," ");for(var i=0,j=c.length;i<j;i++)h=h.replace(" "+c[i]+" "," ");g.className=f.trim(h)}else g.className=""}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";if(f.isFunction(a))return this.each(function(c){var d=f(this);d.toggleClass(a.call(this,c,d.attr("class"),b),b)});return this.each(function(){if(c==="string"){var e,g=0,h=f(this),i=b,j=a.split(o);while(e=j[g++])i=d?i:!h.hasClass(e),h[i?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&f._data(this,"__className__",this.className),this.className=this.className||a===!1?"":f._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ";for(var c=0,d=this.length;c<d;c++)if((" "+this[c].className+" ").replace(n," ").indexOf(b)>-1)return!0;return!1},val:function(a){var c,d,e=this[0];if(!arguments.length){if(e){c=f.valHooks[e.nodeName.toLowerCase()]||f.valHooks[e.type];if(c&&"get"in c&&(d=c.get(e,"value"))!==b)return d;return(e.value||"").replace(p,"")}return b}var g=f.isFunction(a);return this.each(function(d){var e=f(this),h;if(this.nodeType===1){g?h=a.call(this,d,e.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c=a.selectedIndex,d=[],e=a.options,g=a.type==="select-one";if(c<0)return null;for(var h=g?c:0,i=g?c+1:e.length;h<i;h++){var j=e[h];if(j.selected&&(f.support.optDisabled?!j.disabled:j.getAttribute("disabled")===null)&&(!j.parentNode.disabled||!f.nodeName(j.parentNode,"optgroup"))){b=f(j).val();if(g)return b;d.push(b)}}if(g&&!d.length&&e.length)return f(e[c]).val();return d},set:function(a,b){var c=f.makeArray(b);f(a).find("option").each(function(){this.selected=f.inArray(f(this).val(),c)>=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attrFix:{tabindex:"tabIndex"},attr:function(a,c,d,e){var g=a.nodeType;if(!a||g===3||g===8||g===2)return b;if(e&&c in f.attrFn)return f(a)[c](d);if(!("getAttribute"in a))return f.prop(a,c,d);var h,i,j=g!==1||!f.isXMLDoc(a);c=j&&f.attrFix[c]||c,i=f.attrHooks[c],i||(!t.test(c)||typeof d!="boolean"&&d!==b&&d.toLowerCase()!==c.toLowerCase()?v&&(f.nodeName(a,"form")||u.test(c))&&(i=v):i=w);if(d!==b){if(d===null){f.removeAttr(a,c);return b}if(i&&"set"in i&&j&&(h=i.set(a,d,c))!==b)return h;a.setAttribute(c,""+d);return d}if(i&&"get"in i&&j)return i.get(a,c);h=a.getAttribute(c);return h===null?b:h},removeAttr:function(a,b){var c;a.nodeType===1&&(b=f.attrFix[b]||b,f.support.getSetAttribute?a.removeAttribute(b):(f.attr(a,b,""),a.removeAttributeNode(a.getAttributeNode(b))),t.test(b)&&(c=f.propFix[b]||b)in a&&(a[c]=!1))},attrHooks:{type:{set:function(a,b){if(q.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.value;a.setAttribute("type",b),c&&(a.value=c);return b}}},tabIndex:{get:function(a){var c=a.getAttributeNode("tabIndex");return c&&c.specified?parseInt(c.value,10):r.test(a.nodeName)||s.test(a.nodeName)&&a.href?0:b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e=a.nodeType;if(!a||e===3||e===8||e===2)return b;var g,h,i=e!==1||!f.isXMLDoc(a);c=i&&f.propFix[c]||c,h=f.propHooks[c];return d!==b?h&&"set"in h&&(g=h.set(a,d,c))!==b?g:a[c]=d:h&&"get"in h&&(g=h.get(a,c))!==b?g:a[c]},propHooks:{}}),w={get:function(a,c){return a[f.propFix[c]||c]?c.toLowerCase():b},set:function(a,b,c){var d;b===!1?f.removeAttr(a,c):(d=f.propFix[c]||c,d in a&&(a[d]=b),a.setAttribute(c,c.toLowerCase()));return c}},f.attrHooks.value={get:function(a,b){if(v&&f.nodeName(a,"button"))return v.get(a,b);return a.value},set:function(a,b,c){if(v&&f.nodeName(a,"button"))return v.set(a,b,c);a.value=b}},f.support.getSetAttribute||(f.attrFix=f.propFix,v=f.attrHooks.name=f.valHooks.button={get:function(a,c){var d;d=a.getAttributeNode(c);return d&&d.nodeValue!==""?d.nodeValue:b},set:function(a,b,c){var d=a.getAttributeNode(c);if(d){d.nodeValue=b;return b}}},f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})})),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}})),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var x=Object.prototype.hasOwnProperty,y=/\.(.*)$/,z=/^(?:textarea|input|select)$/i,A=/\./g,B=/ /g,C=/[^\w\s.|`]/g,D=function(a){return a.replace(C,"\\$&")};f.event={add:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){if(d===!1)d=E;else if(!d)return;var g,h;d.handler&&(g=d,d=g.handler),d.guid||(d.guid=f.guid++);var i=f._data(a);if(!i)return;var j=i.events,k=i.handle;j||(i.events=j={}),k||(i.handle=k=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.handle.apply(k.elem,arguments):b}),k.elem=a,c=c.split(" ");var l,m=0,n;while(l=c[m++]){h=g?f.extend({},g):{handler:d,data:e},l.indexOf(".")>-1?(n=l.split("."),l=n.shift(),h.namespace=n.slice(0).sort().join(".")):(n=[],h.namespace=""),h.type=l,h.guid||(h.guid=d.guid);var o=j[l],p=f.event.special[l]||{};if(!o){o=j[l]=[];if(!p.setup||p.setup.call(a,e,n,k)===!1)a.addEventListener?a.addEventListener(l,k,!1):a.attachEvent&&a.attachEvent("on"+l,k)}p.add&&(p.add.call(a,h),h.handler.guid||(h.handler.guid=d.guid)),o.push(h),f.event.global[l]=!0}a=null}},global:{},remove:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){d===!1&&(d=E);var g,h,i,j,k=0,l,m,n,o,p,q,r,s=f.hasData(a)&&f._data(a),t=s&&s.events;if(!s||!t)return;c&&c.type&&(d=c.handler,c=c.type);if(!c||typeof c=="string"&&c.charAt(0)==="."){c=c||"";for(h in t)f.event.remove(a,h+c);return}c=c.split(" ");while(h=c[k++]){r=h,q=null,l=h.indexOf(".")<0,m=[],l||(m=h.split("."),h=m.shift(),n=new RegExp("(^|\\.)"+f.map(m.slice(0).sort(),D).join("\\.(?:.*\\.)?")+"(\\.|$)")),p=t[h];if(!p)continue;if(!d){for(j=0;j<p.length;j++){q=p[j];if(l||n.test(q.namespace))f.event.remove(a,r,q.handler,j),p.splice(j--,1)}continue}o=f.event.special[h]||{};for(j=e||0;j<p.length;j++){q=p[j];if(d.guid===q.guid){if(l||n.test(q.namespace))e==null&&p.splice(j--,1),o.remove&&o.remove.call(a,q);if(e!=null)break}}if(p.length===0||e!=null&&p.length===1)(!o.teardown||o.teardown.call(a,m)===!1)&&f.removeEvent(a,h,s.handle),g=null,delete t[h]}if(f.isEmptyObject(t)){var u=s.handle;u&&(u.elem=null),delete s.events,delete s.handle,f.isEmptyObject(s)&&f.removeData(a,b,!0)}}},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,e,g){var h=c.type||c,i=[],j;h.indexOf("!")>=0&&(h=h.slice(0,-1),j=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if(!!e&&!f.event.customEvent[h]||!!f.event.global[h]){c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.exclusive=j,c.namespace=i.join("."),c.namespace_re=new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)");if(g||!e)c.preventDefault(),c.stopPropagation();if(!e){f.each(f.cache,function(){var a=f.expando,b=this[a];b&&b.events&&b.events[h]&&f.event.trigger(c,d,b.handle.elem
+)});return}if(e.nodeType===3||e.nodeType===8)return;c.result=b,c.target=e,d=d?f.makeArray(d):[],d.unshift(c);var k=e,l=h.indexOf(":")<0?"on"+h:"";do{var m=f._data(k,"handle");c.currentTarget=k,m&&m.apply(k,d),l&&f.acceptData(k)&&k[l]&&k[l].apply(k,d)===!1&&(c.result=!1,c.preventDefault()),k=k.parentNode||k.ownerDocument||k===c.target.ownerDocument&&a}while(k&&!c.isPropagationStopped());if(!c.isDefaultPrevented()){var n,o=f.event.special[h]||{};if((!o._default||o._default.call(e.ownerDocument,c)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)){try{l&&e[h]&&(n=e[l],n&&(e[l]=null),f.event.triggered=h,e[h]())}catch(p){}n&&(e[l]=n),f.event.triggered=b}}return c.result}},handle:function(c){c=f.event.fix(c||a.event);var d=((f._data(this,"events")||{})[c.type]||[]).slice(0),e=!c.exclusive&&!c.namespace,g=Array.prototype.slice.call(arguments,0);g[0]=c,c.currentTarget=this;for(var h=0,i=d.length;h<i;h++){var j=d[h];if(e||c.namespace_re.test(j.namespace)){c.handler=j.handler,c.data=j.data,c.handleObj=j;var k=j.handler.apply(this,g);k!==b&&(c.result=k,k===!1&&(c.preventDefault(),c.stopPropagation()));if(c.isImmediatePropagationStopped())break}}return c.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(a){if(a[f.expando])return a;var d=a;a=f.Event(d);for(var e=this.props.length,g;e;)g=this.props[--e],a[g]=d[g];a.target||(a.target=a.srcElement||c),a.target.nodeType===3&&(a.target=a.target.parentNode),!a.relatedTarget&&a.fromElement&&(a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement);if(a.pageX==null&&a.clientX!=null){var h=a.target.ownerDocument||c,i=h.documentElement,j=h.body;a.pageX=a.clientX+(i&&i.scrollLeft||j&&j.scrollLeft||0)-(i&&i.clientLeft||j&&j.clientLeft||0),a.pageY=a.clientY+(i&&i.scrollTop||j&&j.scrollTop||0)-(i&&i.clientTop||j&&j.clientTop||0)}a.which==null&&(a.charCode!=null||a.keyCode!=null)&&(a.which=a.charCode!=null?a.charCode:a.keyCode),!a.metaKey&&a.ctrlKey&&(a.metaKey=a.ctrlKey),!a.which&&a.button!==b&&(a.which=a.button&1?1:a.button&2?3:a.button&4?2:0);return a},guid:1e8,proxy:f.proxy,special:{ready:{setup:f.bindReady,teardown:f.noop},live:{add:function(a){f.event.add(this,O(a.origType,a.selector),f.extend({},a,{handler:N,guid:a.handler.guid}))},remove:function(a){f.event.remove(this,O(a.origType,a.selector),a)}},beforeunload:{setup:function(a,b,c){f.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}}},f.removeEvent=c.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){a.detachEvent&&a.detachEvent("on"+b,c)},f.Event=function(a,b){if(!this.preventDefault)return new f.Event(a,b);a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?F:E):this.type=a,b&&f.extend(this,b),this.timeStamp=f.now(),this[f.expando]=!0},f.Event.prototype={preventDefault:function(){this.isDefaultPrevented=F;var a=this.originalEvent;!a||(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){this.isPropagationStopped=F;var a=this.originalEvent;!a||(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=F,this.stopPropagation()},isDefaultPrevented:E,isPropagationStopped:E,isImmediatePropagationStopped:E};var G=function(a){var b=a.relatedTarget;a.type=a.data;try{if(b&&b!==c&&!b.parentNode)return;while(b&&b!==this)b=b.parentNode;b!==this&&f.event.handle.apply(this,arguments)}catch(d){}},H=function(a){a.type=a.data,f.event.handle.apply(this,arguments)};f.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){f.event.special[a]={setup:function(c){f.event.add(this,b,c&&c.selector?H:G,a)},teardown:function(a){f.event.remove(this,b,a&&a.selector?H:G)}}}),f.support.submitBubbles||(f.event.special.submit={setup:function(a,b){if(!f.nodeName(this,"form"))f.event.add(this,"click.specialSubmit",function(a){var b=a.target,c=b.type;(c==="submit"||c==="image")&&f(b).closest("form").length&&L("submit",this,arguments)}),f.event.add(this,"keypress.specialSubmit",function(a){var b=a.target,c=b.type;(c==="text"||c==="password")&&f(b).closest("form").length&&a.keyCode===13&&L("submit",this,arguments)});else return!1},teardown:function(a){f.event.remove(this,".specialSubmit")}});if(!f.support.changeBubbles){var I,J=function(a){var b=a.type,c=a.value;b==="radio"||b==="checkbox"?c=a.checked:b==="select-multiple"?c=a.selectedIndex>-1?f.map(a.options,function(a){return a.selected}).join("-"):"":f.nodeName(a,"select")&&(c=a.selectedIndex);return c},K=function(c){var d=c.target,e,g;if(!!z.test(d.nodeName)&&!d.readOnly){e=f._data(d,"_change_data"),g=J(d),(c.type!=="focusout"||d.type!=="radio")&&f._data(d,"_change_data",g);if(e===b||g===e)return;if(e!=null||g)c.type="change",c.liveFired=b,f.event.trigger(c,arguments[1],d)}};f.event.special.change={filters:{focusout:K,beforedeactivate:K,click:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(c==="radio"||c==="checkbox"||f.nodeName(b,"select"))&&K.call(this,a)},keydown:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(a.keyCode===13&&!f.nodeName(b,"textarea")||a.keyCode===32&&(c==="checkbox"||c==="radio")||c==="select-multiple")&&K.call(this,a)},beforeactivate:function(a){var b=a.target;f._data(b,"_change_data",J(b))}},setup:function(a,b){if(this.type==="file")return!1;for(var c in I)f.event.add(this,c+".specialChange",I[c]);return z.test(this.nodeName)},teardown:function(a){f.event.remove(this,".specialChange");return z.test(this.nodeName)}},I=f.event.special.change.filters,I.focus=I.beforeactivate}f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){function e(a){var c=f.event.fix(a);c.type=b,c.originalEvent={},f.event.trigger(c,null,c.target),c.isDefaultPrevented()&&a.preventDefault()}var d=0;f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.each(["bind","one"],function(a,c){f.fn[c]=function(a,d,e){var g;if(typeof a=="object"){for(var h in a)this[c](h,d,a[h],e);return this}if(arguments.length===2||d===!1)e=d,d=b;c==="one"?(g=function(a){f(this).unbind(a,g);return e.apply(this,arguments)},g.guid=e.guid||f.guid++):g=e;if(a==="unload"&&c!=="one")this.one(a,d,e);else for(var i=0,j=this.length;i<j;i++)f.event.add(this[i],a,g,d);return this}}),f.fn.extend({unbind:function(a,b){if(typeof a=="object"&&!a.preventDefault)for(var c in a)this.unbind(c,a[c]);else for(var d=0,e=this.length;d<e;d++)f.event.remove(this[d],a,b);return this},delegate:function(a,b,c,d){return this.live(b,c,d,a)},undelegate:function(a,b,c){return arguments.length===0?this.unbind("live"):this.die(b,null,c,a)},trigger:function(a,b){return this.each(function(){f.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return f.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=arguments,c=a.guid||f.guid++,d=0,e=function(c){var e=(f.data(this,"lastToggle"+a.guid)||0)%d;f.data(this,"lastToggle"+a.guid,e+1),c.preventDefault();return b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});var M={focus:"focusin",blur:"focusout",mouseenter:"mouseover",mouseleave:"mouseout"};f.each(["live","die"],function(a,c){f.fn[c]=function(a,d,e,g){var h,i=0,j,k,l,m=g||this.selector,n=g?this:f(this.context);if(typeof a=="object"&&!a.preventDefault){for(var o in a)n[c](o,d,a[o],m);return this}if(c==="die"&&!a&&g&&g.charAt(0)==="."){n.unbind(g);return this}if(d===!1||f.isFunction(d))e=d||E,d=b;a=(a||"").split(" ");while((h=a[i++])!=null){j=y.exec(h),k="",j&&(k=j[0],h=h.replace(y,""));if(h==="hover"){a.push("mouseenter"+k,"mouseleave"+k);continue}l=h,M[h]?(a.push(M[h]+k),h=h+k):h=(M[h]||h)+k;if(c==="live")for(var p=0,q=n.length;p<q;p++)f.event.add(n[p],"live."+O(h,m),{data:d,selector:m,handler:e,origType:h,origHandler:e,preType:l});else n.unbind("live."+O(h,m),e)}return this}}),f.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error".split(" "),function(a,b){f.fn[b]=function(a,c){c==null&&(c=a,a=null);return arguments.length>0?this.bind(b,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0)}),function(){function u(a,b,c,d,e,f){for(var g=0,h=d.length;g<h;g++){var i=d[g];if(i){var j=!1;i=i[a];while(i){if(i.sizcache===c){j=d[i.sizset];break}if(i.nodeType===1){f||(i.sizcache=c,i.sizset=g);if(typeof b!="string"){if(i===b){j=!0;break}}else if(k.filter(b,[i]).length>0){j=i;break}}i=i[a]}d[g]=j}}}function t(a,b,c,d,e,f){for(var g=0,h=d.length;g<h;g++){var i=d[g];if(i){var j=!1;i=i[a];while(i){if(i.sizcache===c){j=d[i.sizset];break}i.nodeType===1&&!f&&(i.sizcache=c,i.sizset=g);if(i.nodeName.toLowerCase()===b){j=i;break}i=i[a]}d[g]=j}}}var a=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d=0,e=Object.prototype.toString,g=!1,h=!0,i=/\\/g,j=/\W/;[0,0].sort(function(){h=!1;return 0});var k=function(b,d,f,g){f=f||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return f;var i,j,n,o,q,r,s,t,u=!0,w=k.isXML(d),x=[],y=b;do{a.exec(""),i=a.exec(y);if(i){y=i[3],x.push(i[1]);if(i[2]){o=i[3];break}}}while(i);if(x.length>1&&m.exec(b))if(x.length===2&&l.relative[x[0]])j=v(x[0]+x[1],d);else{j=l.relative[x[0]]?[d]:k(x.shift(),d);while(x.length)b=x.shift(),l.relative[b]&&(b+=x.shift()),j=v(b,j)}else{!g&&x.length>1&&d.nodeType===9&&!w&&l.match.ID.test(x[0])&&!l.match.ID.test(x[x.length-1])&&(q=k.find(x.shift(),d,w),d=q.expr?k.filter(q.expr,q.set)[0]:q.set[0]);if(d){q=g?{expr:x.pop(),set:p(g)}:k.find(x.pop(),x.length===1&&(x[0]==="~"||x[0]==="+")&&d.parentNode?d.parentNode:d,w),j=q.expr?k.filter(q.expr,q.set):q.set,x.length>0?n=p(j):u=!1;while(x.length)r=x.pop(),s=r,l.relative[r]?s=x.pop():r="",s==null&&(s=d),l.relative[r](n,s,w)}else n=x=[]}n||(n=j),n||k.error(r||b);if(e.call(n)==="[object Array]")if(!u)f.push.apply(f,n);else if(d&&d.nodeType===1)for(t=0;n[t]!=null;t++)n[t]&&(n[t]===!0||n[t].nodeType===1&&k.contains(d,n[t]))&&f.push(j[t]);else for(t=0;n[t]!=null;t++)n[t]&&n[t].nodeType===1&&f.push(j[t]);else p(n,f);o&&(k(o,h,f,g),k.uniqueSort(f));return f};k.uniqueSort=function(a){if(r){g=h,a.sort(r);if(g)for(var b=1;b<a.length;b++)a[b]===a[b-1]&&a.splice(b--,1)}return a},k.matches=function(a,b){return k(a,null,null,b)},k.matchesSelector=function(a,b){return k(b,null,null,[a]).length>0},k.find=function(a,b,c){var d;if(!a)return[];for(var e=0,f=l.order.length;e<f;e++){var g,h=l.order[e];if(g=l.leftMatch[h].exec(a)){var j=g[1];g.splice(1,1);if(j.substr(j.length-1)!=="\\"){g[1]=(g[1]||"").replace(i,""),d=l.find[h](g,b,c);if(d!=null){a=a.replace(l.match[h],"");break}}}}d||(d=typeof b.getElementsByTagName!="undefined"?b.getElementsByTagName("*"):[]);return{set:d,expr:a}},k.filter=function(a,c,d,e){var f,g,h=a,i=[],j=c,m=c&&c[0]&&k.isXML(c[0]);while(a&&c.length){for(var n in l.filter)if((f=l.leftMatch[n].exec(a))!=null&&f[2]){var o,p,q=l.filter[n],r=f[1];g=!1,f.splice(1,1);if(r.substr(r.length-1)==="\\")continue;j===i&&(i=[]);if(l.preFilter[n]){f=l.preFilter[n](f,j,d,i,e,m);if(!f)g=o=!0;else if(f===!0)continue}if(f)for(var s=0;(p=j[s])!=null;s++)if(p){o=q(p,f,s,j);var t=e^!!o;d&&o!=null?t?g=!0:j[s]=!1:t&&(i.push(p),g=!0)}if(o!==b){d||(j=i),a=a.replace(l.match[n],"");if(!g)return[];break}}if(a===h)if(g==null)k.error(a);else break;h=a}return j},k.error=function(a){throw"Syntax error, unrecognized expression: "+a};var l=k.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(a){return a.getAttribute("href")},type:function(a){return a.getAttribute("type")}},relative:{"+":function(a,b){var c=typeof b=="string",d=c&&!j.test(b),e=c&&!d;d&&(b=b.toLowerCase());for(var f=0,g=a.length,h;f<g;f++)if(h=a[f]){while((h=h.previousSibling)&&h.nodeType!==1);a[f]=e||h&&h.nodeName.toLowerCase()===b?h||!1:h===b}e&&k.filter(b,a,!0)},">":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!j.test(b)){b=b.toLowerCase();for(;e<f;e++){c=a[e];if(c){var g=c.parentNode;a[e]=g.nodeName.toLowerCase()===b?g:!1}}}else{for(;e<f;e++)c=a[e],c&&(a[e]=d?c.parentNode:c.parentNode===b);d&&k.filter(b,a,!0)}},"":function(a,b,c){var e,f=d++,g=u;typeof b=="string"&&!j.test(b)&&(b=b.toLowerCase(),e=b,g=t),g("parentNode",b,f,a,e,c)},"~":function(a,b,c){var e,f=d++,g=u;typeof b=="string"&&!j.test(b)&&(b=b.toLowerCase(),e=b,g=t),g("previousSibling",b,f,a,e,c)}},find:{ID:function(a,b,c){if(typeof b.getElementById!="undefined"&&!c){var d=b.getElementById(a[1]);return d&&d.parentNode?[d]:[]}},NAME:function(a,b){if(typeof b.getElementsByName!="undefined"){var c=[],d=b.getElementsByName(a[1]);for(var e=0,f=d.length;e<f;e++)d[e].getAttribute("name")===a[1]&&c.push(d[e]);return c.length===0?null:c}},TAG:function(a,b){if(typeof b.getElementsByTagName!="undefined")return b.getElementsByTagName(a[1])}},preFilter:{CLASS:function(a,b,c,d,e,f){a=" "+a[1].replace(i,"")+" ";if(f)return a;for(var g=0,h;(h=b[g])!=null;g++)h&&(e^(h.className&&(" "+h.className+" ").replace(/[\t\n\r]/g," ").indexOf(a)>=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(i,"")},TAG:function(a,b){return a[1].replace(i,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||k.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&k.error(a[0]);a[0]=d++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(i,"");!f&&l.attrMap[g]&&(a[1]=l.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(i,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=k(b[3],null,null,c);else{var g=k.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(l.match.POS.test(b[0])||l.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!k(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return b<c[3]-0},gt:function(a,b,c){return b>c[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=l.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||k.getText([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h<i;h++)if(g[h]===a)return!1;return!0}k.error(e)},CHILD:function(a,b){var c=b[1],d=a;switch(c){case"only":case"first":while(d=d.previousSibling)if(d.nodeType===1)return!1;if(c==="first")return!0;d=a;case"last":while(d=d.nextSibling)if(d.nodeType===1)return!1;return!0;case"nth":var e=b[2],f=b[3];if(e===1&&f===0)return!0;var g=b[0],h=a.parentNode;if(h&&(h.sizcache!==g||!a.nodeIndex)){var i=0;for(d=h.firstChild;d;d=d.nextSibling)d.nodeType===1&&(d.nodeIndex=++i);h.sizcache=g}var j=a.nodeIndex-f;return e===0?j===0:j%e===0&&j/e>=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=l.attrHandle[c]?l.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=l.setFilters[e];if(f)return f(a,c,b,d)}}},m=l.match.POS,n=function(a,b){return"\\"+(b-0+1)};for(var o in l.match)l.match[o]=new RegExp(l.match[o].source+/(?![^\[]*\])(?![^\(]*\))/.source),l.leftMatch[o]=new RegExp(/(^(?:.|\r|\n)*?)/.source+l.match[o].source.replace(/\\(\d+)/g,n));var p=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(q){p=function(a,b){var c=0,d=b||[];if(e.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var f=a.length;c<f;c++)d.push(a[c]);else for(;a[c];c++)d.push(a[c]);return d}}var r,s;c.documentElement.compareDocumentPosition?r=function(a,b){if(a===b){g=!0;return 0}if(!a.compareDocumentPosition||!b.compareDocumentPosition)return a.compareDocumentPosition?-1:1;return a.compareDocumentPosition(b)&4?-1:1}:(r=function(a,b){if(a===b){g=!0;return 0}if(a.sourceIndex&&b.sourceIndex)return a.sourceIndex-b.sourceIndex;var c,d,e=[],f=[],h=a.parentNode,i=b.parentNode,j=h;if(h===i)return s(a,b);if(!h)return-1;if(!i)return 1;while(j)e.unshift(j),j=j.parentNode;j=i;while(j)f.unshift(j),j=j.parentNode;c=e.length,d=f.length;for(var k=0;k<c&&k<d;k++)if(e[k]!==f[k])return s(e[k],f[k]);return k===c?s(a,f[k],-1):s(e[k],b,1)},s=function(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}),k.getText=function(a){var b="",c;for(var d=0;a[d];d++)c=a[d],c.nodeType===3||c.nodeType===4?b+=c.nodeValue:c.nodeType!==8&&(b+=k.getText(c.childNodes));return b},function(){var a=c.createElement("div"),d="script"+(new Date).getTime(),e=c.documentElement;a.innerHTML="<a name='"+d+"'/>",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(l.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},l.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(l.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(l.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=k,b=c.createElement("div"),d="__sizzle__";b.innerHTML="<p class='TEST'></p>";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){k=function(b,e,f,g){e=e||c;if(!g&&!k.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return p(e.getElementsByTagName(b),f);if(h[2]&&l.find.CLASS&&e.getElementsByClassName)return p(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return p([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return p([],f);if(i.id===h[3])return p([i],f)}try{return p(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var m=e,n=e.getAttribute("id"),o=n||d,q=e.parentNode,r=/^\s*[+~]/.test(b);n?o=o.replace(/'/g,"\\$&"):e.setAttribute("id",o),r&&q&&(e=e.parentNode);try{if(!r||q)return p(e.querySelectorAll("[id='"+o+"'] "+b),f)}catch(s){}finally{n||m.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)k[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}k.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!k.isXML(a))try{if(e||!l.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return k(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="<div class='test e'></div><div class='test'></div>";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;l.order.splice(1,0,"CLASS"),l.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?k.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?k.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:k.contains=function(){return!1},k.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var v=function(a,b){var c,d=[],e="",f=b.nodeType?[b]:b;while(c=l.match.PSEUDO.exec(a))e+=c[0],a=a.replace(l.match.PSEUDO,"");a=l.relative[a]?a+"*":a;for(var g=0,h=f.length;g<h;g++)k(a,f[g],d);return k.filter(e,d)};f.find=k,f.expr=k.selectors,f.expr[":"]=f.expr.filters,f.unique=k.uniqueSort,f.text=k.getText,f.isXMLDoc=k.isXML,f.contains=k.contains}();var P=/Until$/,Q=/^(?:parents|prevUntil|prevAll)/,R=/,/,S=/^.[^:#\[\.,]*$/,T=Array.prototype.slice,U=f.expr.match.POS,V={children:!0,contents:!0,next:!0,prev:!0};f.fn.extend({find:function(a){var b=this,c,d;if(typeof a!="string")return f(a).filter(function(){for(c=0,d=b.length;c<d;c++)if(f.contains(b[c],this))return!0});var e=this.pushStack("","find",a),g,h,i;for(c=0,d=this.length;c<d;c++){g=e.length,f.find(a,this[c],e);if(c>0)for(h=g;h<e.length;h++)for(i=0;i<g;i++)if(e[i]===e[h]){e.splice(h--,1);break}}return e},has:function(a){var b=f(a);return this.filter(function(){for(var a=0,c=b.length;a<c;a++)if(f.contains(this,b[a]))return!0})},not:function(a){return this.pushStack(X(this,a,!1),"not",a)},filter:function(a){return this.pushStack(X(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h,i,j={},k=1;if(g&&a.length){for(d=0,e=a.length;d<e;d++)i=a[d],j[i]||(j[i]=U.test(i)?f(i,b||this.context):i);while(g&&g.ownerDocument&&g!==b){for(i in j)h=j[i],(h.jquery?h.index(g)>-1:f(g).is(h))&&c.push({selector:i,elem:g,level:k});g=g.parentNode,k++}}return c}var l=U.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d<e;d++){g=this[d];while(g){if(l?l.index(g)>-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a||typeof a=="string")return f.inArray(this[0],a?f(a):this.parent().children());return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(W(c[0])||W(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c),g=T.call(arguments);P.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!V[a]?f.unique(e):e,(this.length>1||R.test(d))&&Q.test(a)&&(e=e.reverse());return this.pushStack(e,a,g.join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var Y=/ jQuery\d+="(?:\d+|null)"/g,Z=/^\s+/,$=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,_=/<([\w:]+)/,ba=/<tbody/i,bb=/<|&#?\w+;/,bc=/<(?:script|object|embed|option|style)/i,bd=/checked\s*(?:[^=]|=\s*.checked.)/i,be=/\/(java|ecma)script/i,bf=/^\s*<!(?:\[CDATA\[|\-\-)/,bg={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]};bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div<div>","</div>"]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){f(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f(arguments[0]).toArray());return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Y,""):null;if(typeof a=="string"&&!bc.test(a)&&(f.support.leadingWhitespace||!Z.test(a))&&!bg[(_.exec(a)||["",""])[1].toLowerCase()]){a=a.replace($,"<$1></$2>");try{for(var c=0,d=this.length;c<d;c++)this[c].nodeType===1&&(f.cleanData(this[c].getElementsByTagName("*")),this[c].innerHTML=a)}catch(e){this.empty().append(a)}}else f.isFunction(a)?this.each(function(b){var c=f(this);c.html(a.call(this,b,c.html()))}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&this[0].parentNode){if(f.isFunction(a))return this.each(function(b){var c=f(this),d=c.html();c.replaceWith(a.call(this,b,d))});typeof a!="string"&&(a=f(a).detach());return this.each(function(){var b=this.nextSibling,c=this.parentNode;f(this).remove(),b?f(b).before(a):f(c).append(a)})}return this.length?this.pushStack(f(f.isFunction(a)?a():a),"replaceWith",a):this},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){var e,g,h,i,j=a[0],k=[];if(!f.support.checkClone&&arguments.length===3&&typeof j=="string"&&bd.test(j))return this.each(function(){f(this).domManip(a,c,d,!0)});if(f.isFunction(j))return this.each(function(e){var g=f(this);a[0]=j.call(this,e,c?g.html():b),g.domManip(a,c,d)});if(this[0]){i=j&&j.parentNode,f.support.parentNode&&i&&i.nodeType===11&&i.childNodes.length===this.length?e={fragment:i}:e=f.buildFragment(a,this,k),h=e.fragment,h.childNodes.length===1?g=h=h.firstChild:g=h.firstChild;if(g){c=c&&f.nodeName(g,"tr");for(var l=0,m=this.length,n=m-1;l<m;l++)d.call(c?bh(this[l],g):this[l],e.cacheable||m>1&&l<n?f.clone(h,!0,!0):h)}k.length&&f.each(k,bn)}return this}}),f.buildFragment=function(a,b,d){var e,g,h,i=b&&b[0]?b[0].ownerDocument||b[0]:c;a.length===1&&typeof a[0]=="string"&&a[0].length<512&&i===c&&a[0].charAt(0)==="<"&&!bc.test(a[0])&&(f.support.checkClone||!bd.test(a[0]))&&(g=!0,h=f.fragments[a[0]],h&&h!==1&&(e=h)),e||(e=i.createDocumentFragment(),f.clean(a,i,e,d)),g&&(f.fragments[a[0]]=h?e:1);return{fragment:e,cacheable:g}},f.fragments={},f.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){f.fn[a]=function(c){var d=[],e=f(c),g=this.length===1&&this[0].parentNode;if(g&&g.nodeType===11&&g.childNodes.length===1&&e.length===1){e[b](this[0]);return this}for(var h=0,i=e.length;h<i;h++){var j=(h>0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d=a.cloneNode(!0),e,g,h;if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bj(a,d),e=bk(a),g=bk(d);for(h=0;e[h];++h)bj(e[h],g[h])}if(b){bi(a,d);if(c){e=bk(a),g=bk(d);for(h=0;e[h];++h)bi(e[h],g[h])}}return d},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||
+b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!bb.test(k))k=b.createTextNode(k);else{k=k.replace($,"<$1></$2>");var l=(_.exec(k)||["",""])[1].toLowerCase(),m=bg[l]||bg._default,n=m[0],o=b.createElement("div");o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=ba.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]==="<table>"&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&Z.test(k)&&o.insertBefore(b.createTextNode(Z.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i<r;i++)bm(k[i]);else bm(k);k.nodeType?h.push(k):h=f.merge(h,k)}if(d){g=function(a){return!a.type||be.test(a.type)};for(j=0;h[j];j++)if(e&&f.nodeName(h[j],"script")&&(!h[j].type||h[j].type.toLowerCase()==="text/javascript"))e.push(h[j].parentNode?h[j].parentNode.removeChild(h[j]):h[j]);else{if(h[j].nodeType===1){var s=f.grep(h[j].getElementsByTagName("script"),g);h.splice.apply(h,[j+1,0].concat(s))}d.appendChild(h[j])}}return h},cleanData:function(a){var b,c,d=f.cache,e=f.expando,g=f.event.special,h=f.support.deleteExpando;for(var i=0,j;(j=a[i])!=null;i++){if(j.nodeName&&f.noData[j.nodeName.toLowerCase()])continue;c=j[f.expando];if(c){b=d[c]&&d[c][e];if(b&&b.events){for(var k in b.events)g[k]?f.event.remove(j,k):f.removeEvent(j,k,b.handle);b.handle&&(b.handle.elem=null)}h?delete j[f.expando]:j.removeAttribute&&j.removeAttribute(f.expando),delete d[c]}}}});var bo=/alpha\([^)]*\)/i,bp=/opacity=([^)]*)/,bq=/-([a-z])/ig,br=/([A-Z]|^ms)/g,bs=/^-?\d+(?:px)?$/i,bt=/^-?\d/,bu=/^[+\-]=/,bv=/[^+\-\.\de]+/g,bw={position:"absolute",visibility:"hidden",display:"block"},bx=["Left","Right"],by=["Top","Bottom"],bz,bA,bB,bC=function(a,b){return b.toUpperCase()};f.fn.css=function(a,c){if(arguments.length===2&&c===b)return this;return f.access(this,a,c,!0,function(a,c,d){return d!==b?f.style(a,c,d):f.css(a,c)})},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bz(a,"opacity","opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{zIndex:!0,fontWeight:!0,opacity:!0,zoom:!0,lineHeight:!0,widows:!0,orphans:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d;if(h==="number"&&isNaN(d)||d==null)return;h==="string"&&bu.test(d)&&(d=+d.replace(bv,"")+parseFloat(f.css(a,c))),h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(bz)return bz(a,c)},swap:function(a,b,c){var d={};for(var e in b)d[e]=a.style[e],a.style[e]=b[e];c.call(a);for(e in b)a.style[e]=d[e]},camelCase:function(a){return a.replace(bq,bC)}}),f.curCSS=f.css,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){var e;if(c){a.offsetWidth!==0?e=bD(a,b,d):f.swap(a,bw,function(){e=bD(a,b,d)});if(e<=0){e=bz(a,b,b),e==="0px"&&bB&&(e=bB(a,b,b));if(e!=null)return e===""||e==="auto"?"0px":e}if(e<0||e==null){e=a.style[b];return e===""||e==="auto"?"0px":e}return typeof e=="string"?e:e+"px"}},set:function(a,b){if(!bs.test(b))return b;b=parseFloat(b);if(b>=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bp.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle;c.zoom=1;var e=f.isNaN(b)?"":"alpha(opacity="+b*100+")",g=d&&d.filter||c.filter||"";c.filter=bo.test(g)?g.replace(bo,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bz(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(bA=function(a,c){var d,e,g;c=c.replace(br,"-$1").toLowerCase();if(!(e=a.ownerDocument.defaultView))return b;if(g=e.getComputedStyle(a,null))d=g.getPropertyValue(c),d===""&&!f.contains(a.ownerDocument.documentElement,a)&&(d=f.style(a,c));return d}),c.documentElement.currentStyle&&(bB=function(a,b){var c,d=a.currentStyle&&a.currentStyle[b],e=a.runtimeStyle&&a.runtimeStyle[b],f=a.style;!bs.test(d)&&bt.test(d)&&(c=f.left,e&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":d||0,d=f.pixelLeft+"px",f.left=c,e&&(a.runtimeStyle.left=e));return d===""?"auto":d}),bz=bA||bB,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bE=/%20/g,bF=/\[\]$/,bG=/\r?\n/g,bH=/#.*$/,bI=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bJ=/^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bK=/^(?:about|app|app\-storage|.+\-extension|file|widget):$/,bL=/^(?:GET|HEAD)$/,bM=/^\/\//,bN=/\?/,bO=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,bP=/^(?:select|textarea)/i,bQ=/\s+/,bR=/([?&])_=[^&]*/,bS=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bT=f.fn.load,bU={},bV={},bW,bX;try{bW=e.href}catch(bY){bW=c.createElement("a"),bW.href="",bW=bW.href}bX=bS.exec(bW.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bT)return bT.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("<div>").append(c.replace(bO,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bP.test(this.nodeName)||bJ.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bG,"\r\n")}}):{name:b.name,value:c.replace(bG,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.bind(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?f.extend(!0,a,f.ajaxSettings,b):(b=a,a=f.extend(!0,f.ajaxSettings,b));for(var c in{context:1,url:1})c in b?a[c]=b[c]:c in f.ajaxSettings&&(a[c]=f.ajaxSettings[c]);return a},ajaxSettings:{url:bW,isLocal:bK.test(bX[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":"*/*"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML}},ajaxPrefilter:bZ(bU),ajaxTransport:bZ(bV),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a?4:0;var o,r,u,w=l?ca(d,v,l):b,x,y;if(a>=200&&a<300||a===304){if(d.ifModified){if(x=v.getResponseHeader("Last-Modified"))f.lastModified[k]=x;if(y=v.getResponseHeader("Etag"))f.etag[k]=y}if(a===304)c="notmodified",o=!0;else try{r=cb(d,w),c="success",o=!0}catch(z){c="parsererror",u=z}}else{u=c;if(!c||a)c="error",a<0&&(a=0)}v.status=a,v.statusText=c,o?h.resolveWith(e,[r,c,v]):h.rejectWith(e,[v,c,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.resolveWith(e,[v,c]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f._Deferred(),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bI.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.done,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bH,"").replace(bM,bX[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bQ),d.crossDomain==null&&(r=bS.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bX[1]&&r[2]==bX[2]&&(r[3]||(r[1]==="http:"?80:443))==(bX[3]||(bX[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),b$(bU,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bL.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bN.test(d.url)?"&":"?")+d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bR,"$1_="+x);d.url=y+(y===d.url?(bN.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", */*; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=b$(bV,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){status<2?w(-1,z):f.error(z)}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)b_(g,a[g],c,e);return d.join("&").replace(bE,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cc=f.now(),cd=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cc++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(cd.test(b.url)||e&&cd.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(cd,l),b.url===j&&(e&&(k=k.replace(cd,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var ce=a.ActiveXObject?function(){for(var a in cg)cg[a](0,1)}:!1,cf=0,cg;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ch()||ci()}:ch,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,ce&&delete cg[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cf,ce&&(cg||(cg={},f(a).unload(ce)),cg[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cj={},ck,cl,cm=/^(?:toggle|show|hide)$/,cn=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,co,cp=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cq,cr=a.webkitRequestAnimationFrame||a.mozRequestAnimationFrame||a.oRequestAnimationFrame;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cu("show",3),a,b,c);for(var g=0,h=this.length;g<h;g++)d=this[g],d.style&&(e=d.style.display,!f._data(d,"olddisplay")&&e==="none"&&(e=d.style.display=""),e===""&&f.css(d,"display")==="none"&&f._data(d,"olddisplay",cv(d.nodeName)));for(g=0;g<h;g++){d=this[g];if(d.style){e=d.style.display;if(e===""||e==="none")d.style.display=f._data(d,"olddisplay")||""}}return this},hide:function(a,b,c){if(a||a===0)return this.animate(cu("hide",3),a,b,c);for(var d=0,e=this.length;d<e;d++)if(this[d].style){var g=f.css(this[d],"display");g!=="none"&&!f._data(this[d],"olddisplay")&&f._data(this[d],"olddisplay",g)}for(d=0;d<e;d++)this[d].style&&(this[d].style.display="none");return this},_toggle:f.fn.toggle,toggle:function(a,b,c){var d=typeof a=="boolean";f.isFunction(a)&&f.isFunction(b)?this._toggle.apply(this,arguments):a==null||d?this.each(function(){var b=d?a:f(this).is(":hidden");f(this)[b?"show":"hide"]()}):this.animate(cu("toggle",3),a,b,c);return this},fadeTo:function(a,b,c,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=f.speed(b,c,d);if(f.isEmptyObject(a))return this.each(e.complete,[!1]);a=f.extend({},a);return this[e.queue===!1?"each":"queue"](function(){e.queue===!1&&f._mark(this);var b=f.extend({},e),c=this.nodeType===1,d=c&&f(this).is(":hidden"),g,h,i,j,k,l,m,n,o;b.animatedProperties={};for(i in a){g=f.camelCase(i),i!==g&&(a[g]=a[i],delete a[i]),h=a[g],f.isArray(h)?(b.animatedProperties[g]=h[1],h=a[g]=h[0]):b.animatedProperties[g]=b.specialEasing&&b.specialEasing[g]||b.easing||"swing";if(h==="hide"&&d||h==="show"&&!d)return b.complete.call(this);c&&(g==="height"||g==="width")&&(b.overflow=[this.style.overflow,this.style.overflowX,this.style.overflowY],f.css(this,"display")==="inline"&&f.css(this,"float")==="none"&&(f.support.inlineBlockNeedsLayout?(j=cv(this.nodeName),j==="inline"?this.style.display="inline-block":(this.style.display="inline",this.style.zoom=1)):this.style.display="inline-block"))}b.overflow!=null&&(this.style.overflow="hidden");for(i in a)k=new f.fx(this,b,i),h=a[i],cm.test(h)?k[h==="toggle"?d?"show":"hide":h]():(l=cn.exec(h),m=k.cur(),l?(n=parseFloat(l[2]),o=l[3]||(f.cssNumber[i]?"":"px"),o!=="px"&&(f.style(this,i,(n||1)+o),m=(n||1)/k.cur()*m,f.style(this,i,m+o)),l[1]&&(n=(l[1]==="-="?-1:1)*n+m),k.custom(m,n,o)):k.custom(m,h,""));return!0})},stop:function(a,b){a&&this.queue([]),this.each(function(){var a=f.timers,c=a.length;b||f._unmark(!0,this);while(c--)a[c].elem===this&&(b&&a[c](!0),a.splice(c,1))}),b||this.dequeue();return this}}),f.each({slideDown:cu("show",1),slideUp:cu("hide",1),slideToggle:cu("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){f.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),f.extend({speed:function(a,b,c){var d=a&&typeof a=="object"?f.extend({},a):{complete:c||!c&&b||f.isFunction(a)&&a,duration:a,easing:c&&b||b&&!f.isFunction(b)&&b};d.duration=f.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in f.fx.speeds?f.fx.speeds[d.duration]:f.fx.speeds._default,d.old=d.complete,d.complete=function(a){d.queue!==!1?f.dequeue(this):a!==!1&&f._unmark(this),f.isFunction(d.old)&&d.old.call(this)};return d},easing:{linear:function(a,b,c,d){return c+d*a},swing:function(a,b,c,d){return(-Math.cos(a*Math.PI)/2+.5)*d+c}},timers:[],fx:function(a,b,c){this.options=b,this.elem=a,this.prop=c,b.orig=b.orig||{}}}),f.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this),(f.fx.step[this.prop]||f.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a,b=f.css(this.elem,this.prop);return isNaN(a=parseFloat(b))?!b||b==="auto"?0:b:a},custom:function(a,b,c){function h(a){return d.step(a)}var d=this,e=f.fx,g;this.startTime=cq||cs(),this.start=a,this.end=b,this.unit=c||this.unit||(f.cssNumber[this.prop]?"":"px"),this.now=this.start,this.pos=this.state=0,h.elem=this.elem,h()&&f.timers.push(h)&&!co&&(cr?(co=1,g=function(){co&&(cr(g),e.tick())},cr(g)):co=setInterval(e.tick,e.interval))},show:function(){this.options.orig[this.prop]=f.style(this.elem,this.prop),this.options.show=!0,this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur()),f(this.elem).show()},hide:function(){this.options.orig[this.prop]=f.style(this.elem,this.prop),this.options.hide=!0,this.custom(this.cur(),0)},step:function(a){var b=cq||cs(),c=!0,d=this.elem,e=this.options,g,h;if(a||b>=e.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),e.animatedProperties[this.prop]=!0;for(g in e.animatedProperties)e.animatedProperties[g]!==!0&&(c=!1);if(c){e.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){d.style["overflow"+b]=e.overflow[a]}),e.hide&&f(d).hide();if(e.hide||e.show)for(var i in e.animatedProperties)f.style(d,i,e.orig[i]);e.complete.call(d)}return!1}e.duration==Infinity?this.now=b:(h=b-this.startTime,this.state=h/e.duration,this.pos=f.easing[e.animatedProperties[this.prop]](this.state,h,0,1,e.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){for(var a=f.timers,b=0;b<a.length;++b)a[b]()||a.splice(b--,1);a.length||f.fx.stop()},interval:13,stop:function(){clearInterval(co),co=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){f.style(a.elem,"opacity",a.now)},_default:function(a){a.elem.style&&a.elem.style[a.prop]!=null?a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit:a.elem[a.prop]=a.now}}}),f.expr&&f.expr.filters&&(f.expr.filters.animated=function(a){return f.grep(f.timers,function(b){return a===b.elem}).length});var cw=/^t(?:able|d|h)$/i,cx=/^(?:body|html)$/i;"getBoundingClientRect"in c.documentElement?f.fn.offset=function(a){var b=this[0],c;if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);try{c=b.getBoundingClientRect()}catch(d){}var e=b.ownerDocument,g=e.documentElement;if(!c||!f.contains(g,b))return c?{top:c.top,left:c.left}:{top:0,left:0};var h=e.body,i=cy(e),j=g.clientTop||h.clientTop||0,k=g.clientLeft||h.clientLeft||0,l=i.pageYOffset||f.support.boxModel&&g.scrollTop||h.scrollTop,m=i.pageXOffset||f.support.boxModel&&g.scrollLeft||h.scrollLeft,n=c.top+l-j,o=c.left+m-k;return{top:n,left:o}}:f.fn.offset=function(a){var b=this[0];if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);f.offset.initialize();var c,d=b.offsetParent,e=b,g=b.ownerDocument,h=g.documentElement,i=g.body,j=g.defaultView,k=j?j.getComputedStyle(b,null):b.currentStyle,l=b.offsetTop,m=b.offsetLeft;while((b=b.parentNode)&&b!==i&&b!==h){if(f.offset.supportsFixedPosition&&k.position==="fixed")break;c=j?j.getComputedStyle(b,null):b.currentStyle,l-=b.scrollTop,m-=b.scrollLeft,b===d&&(l+=b.offsetTop,m+=b.offsetLeft,f.offset.doesNotAddBorder&&(!f.offset.doesAddBorderForTableAndCells||!cw.test(b.nodeName))&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),e=d,d=b.offsetParent),f.offset.subtractsBorderForOverflowNotVisible&&c.overflow!=="visible"&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),k=c}if(k.position==="relative"||k.position==="static")l+=i.offsetTop,m+=i.offsetLeft;f.offset.supportsFixedPosition&&k.position==="fixed"&&(l+=Math.max(h.scrollTop,i.scrollTop),m+=Math.max(h.scrollLeft,i.scrollLeft));return{top:l,left:m}},f.offset={initialize:function(){var a=c.body,b=c.createElement("div"),d,e,g,h,i=parseFloat(f.css(a,"marginTop"))||0,j="<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";f.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"}),b.innerHTML=j,a.insertBefore(b,a.firstChild),d=b.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,this.doesNotAddBorder=e.offsetTop!==5,this.doesAddBorderForTableAndCells=h.offsetTop===5,e.style.position="fixed",e.style.top="20px",this.supportsFixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",this.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==i,a.removeChild(b),f.offset.initialize=f.noop},bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.offset.initialize(),f.offset.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cy(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cy(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){return this[0]?parseFloat(f.css(this[0],d,"padding")):null},f.fn["outer"+c]=function(a){return this[0]?parseFloat(f.css(this[0],d,a?"margin":"border")):null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c];return e.document.compatMode==="CSS1Compat"&&g||e.document.body["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var h=f.css(e,d),i=parseFloat(h);return f.isNaN(i)?h:i}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f})(window);
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/jquery.ba-hashchange.min.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/jquery.ba-hashchange.min.js
new file mode 100644
index 0000000..3c607ba
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/jquery.ba-hashchange.min.js
@@ -0,0 +1,9 @@
+/*
+ * jQuery hashchange event - v1.3 - 7/21/2010
+ * http://benalman.com/projects/jquery-hashchange-plugin/
+ * 
+ * Copyright (c) 2010 "Cowboy" Ben Alman
+ * Dual licensed under the MIT and GPL licenses.
+ * http://benalman.com/about/license/
+ */
+(function($,e,b){var c="hashchange",h=document,f,g=$.event.special,i=h.documentMode,d="on"+c in e&&(i===b||i>7);function a(j){j=j||location.href;return"#"+j.replace(/^[^#]*#?(.*)$/,"$1")}$.fn[c]=function(j){return j?this.bind(c,j):this.trigger(c)};$.fn[c].delay=50;g[c]=$.extend(g[c],{setup:function(){if(d){return false}$(f.start)},teardown:function(){if(d){return false}$(f.stop)}});f=(function(){var j={},p,m=a(),k=function(q){return q},l=k,o=k;j.start=function(){p||n()};j.stop=function(){p&&clearTimeout(p);p=b};function n(){var r=a(),q=o(m);if(r!==m){l(m=r,q);$(e).trigger(c)}else{if(q!==m){location.href=location.href.replace(/#.*/,"")+q}}p=setTimeout(n,$.fn[c].delay)}$.browser.msie&&!d&&(function(){var q,r;j.start=function(){if(!q){r=$.fn[c].src;r=r&&r+a();q=$('<iframe tabindex="-1" title="empty"/>').hide().one("load",function(){r||l(a());n()}).attr("src",r||"javascript:0").insertAfter("body")[0].contentWindow;h.onpropertychange=function(){try{if(event.propertyName==="title"){q.document.title=h.title}}catch(s){}}}};j.stop=k;o=function(){return a(q.location.href)};l=function(v,s){var u=q.document,t=$.fn[c].domain;if(v!==s){u.title=h.title;u.open();t&&u.write('<script>document.domain="'+t+'"<\/script>');u.close();q.location.hash=v}}})();return j})()})(jQuery,this);
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/json2.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/json2.js
new file mode 100644
index 0000000..11e1f0a
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/json2.js
@@ -0,0 +1,480 @@
+/*

+    http://www.JSON.org/json2.js

+    2011-02-23

+

+    Public Domain.

+

+    NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.

+

+    See http://www.JSON.org/js.html

+

+

+    This code should be minified before deployment.

+    See http://javascript.crockford.com/jsmin.html

+

+    USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO

+    NOT CONTROL.

+

+

+    This file creates a global JSON object containing two methods: stringify

+    and parse.

+

+        JSON.stringify(value, replacer, space)

+            value       any JavaScript value, usually an object or array.

+

+            replacer    an optional parameter that determines how object

+                        values are stringified for objects. It can be a

+                        function or an array of strings.

+

+            space       an optional parameter that specifies the indentation

+                        of nested structures. If it is omitted, the text will

+                        be packed without extra whitespace. If it is a number,

+                        it will specify the number of spaces to indent at each

+                        level. If it is a string (such as '\t' or '&nbsp;'),

+                        it contains the characters used to indent at each level.

+

+            This method produces a JSON text from a JavaScript value.

+

+            When an object value is found, if the object contains a toJSON

+            method, its toJSON method will be called and the result will be

+            stringified. A toJSON method does not serialize: it returns the

+            value represented by the name/value pair that should be serialized,

+            or undefined if nothing should be serialized. The toJSON method

+            will be passed the key associated with the value, and this will be

+            bound to the value

+

+            For example, this would serialize Dates as ISO strings.

+

+                Date.prototype.toJSON = function (key) {

+                    function f(n) {

+                        // Format integers to have at least two digits.

+                        return n < 10 ? '0' + n : n;

+                    }

+

+                    return this.getUTCFullYear()   + '-' +

+                         f(this.getUTCMonth() + 1) + '-' +

+                         f(this.getUTCDate())      + 'T' +

+                         f(this.getUTCHours())     + ':' +

+                         f(this.getUTCMinutes())   + ':' +

+                         f(this.getUTCSeconds())   + 'Z';

+                };

+

+            You can provide an optional replacer method. It will be passed the

+            key and value of each member, with this bound to the containing

+            object. The value that is returned from your method will be

+            serialized. If your method returns undefined, then the member will

+            be excluded from the serialization.

+

+            If the replacer parameter is an array of strings, then it will be

+            used to select the members to be serialized. It filters the results

+            such that only members with keys listed in the replacer array are

+            stringified.

+

+            Values that do not have JSON representations, such as undefined or

+            functions, will not be serialized. Such values in objects will be

+            dropped; in arrays they will be replaced with null. You can use

+            a replacer function to replace those with JSON values.

+            JSON.stringify(undefined) returns undefined.

+

+            The optional space parameter produces a stringification of the

+            value that is filled with line breaks and indentation to make it

+            easier to read.

+

+            If the space parameter is a non-empty string, then that string will

+            be used for indentation. If the space parameter is a number, then

+            the indentation will be that many spaces.

+

+            Example:

+

+            text = JSON.stringify(['e', {pluribus: 'unum'}]);

+            // text is '["e",{"pluribus":"unum"}]'

+

+

+            text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');

+            // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'

+

+            text = JSON.stringify([new Date()], function (key, value) {

+                return this[key] instanceof Date ?

+                    'Date(' + this[key] + ')' : value;

+            });

+            // text is '["Date(---current time---)"]'

+

+

+        JSON.parse(text, reviver)

+            This method parses a JSON text to produce an object or array.

+            It can throw a SyntaxError exception.

+

+            The optional reviver parameter is a function that can filter and

+            transform the results. It receives each of the keys and values,

+            and its return value is used instead of the original value.

+            If it returns what it received, then the structure is not modified.

+            If it returns undefined then the member is deleted.

+

+            Example:

+

+            // Parse the text. Values that look like ISO date strings will

+            // be converted to Date objects.

+

+            myData = JSON.parse(text, function (key, value) {

+                var a;

+                if (typeof value === 'string') {

+                    a =

+/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);

+                    if (a) {

+                        return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],

+                            +a[5], +a[6]));

+                    }

+                }

+                return value;

+            });

+

+            myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {

+                var d;

+                if (typeof value === 'string' &&

+                        value.slice(0, 5) === 'Date(' &&

+                        value.slice(-1) === ')') {

+                    d = new Date(value.slice(5, -1));

+                    if (d) {

+                        return d;

+                    }

+                }

+                return value;

+            });

+

+

+    This is a reference implementation. You are free to copy, modify, or

+    redistribute.

+*/

+

+/*jslint evil: true, strict: false, regexp: false */

+

+/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,

+    call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,

+    getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,

+    lastIndex, length, parse, prototype, push, replace, slice, stringify,

+    test, toJSON, toString, valueOf

+*/

+

+

+// Create a JSON object only if one does not already exist. We create the

+// methods in a closure to avoid creating global variables.

+

+var JSON;

+if (!JSON) {

+    JSON = {};

+}

+

+(function () {

+    "use strict";

+

+    function f(n) {

+        // Format integers to have at least two digits.

+        return n < 10 ? '0' + n : n;

+    }

+

+    if (typeof Date.prototype.toJSON !== 'function') {

+

+        Date.prototype.toJSON = function (key) {

+

+            return isFinite(this.valueOf()) ?

+                this.getUTCFullYear()     + '-' +

+                f(this.getUTCMonth() + 1) + '-' +

+                f(this.getUTCDate())      + 'T' +

+                f(this.getUTCHours())     + ':' +

+                f(this.getUTCMinutes())   + ':' +

+                f(this.getUTCSeconds())   + 'Z' : null;

+        };

+

+        String.prototype.toJSON      =

+            Number.prototype.toJSON  =

+            Boolean.prototype.toJSON = function (key) {

+                return this.valueOf();

+            };

+    }

+

+    var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,

+        escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,

+        gap,

+        indent,

+        meta = {    // table of character substitutions

+            '\b': '\\b',

+            '\t': '\\t',

+            '\n': '\\n',

+            '\f': '\\f',

+            '\r': '\\r',

+            '"' : '\\"',

+            '\\': '\\\\'

+        },

+        rep;

+

+

+    function quote(string) {

+

+// If the string contains no control characters, no quote characters, and no

+// backslash characters, then we can safely slap some quotes around it.

+// Otherwise we must also replace the offending characters with safe escape

+// sequences.

+

+        escapable.lastIndex = 0;

+        return escapable.test(string) ? '"' + string.replace(escapable, function (a) {

+            var c = meta[a];

+            return typeof c === 'string' ? c :

+                '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);

+        }) + '"' : '"' + string + '"';

+    }

+

+

+    function str(key, holder) {

+

+// Produce a string from holder[key].

+

+        var i,          // The loop counter.

+            k,          // The member key.

+            v,          // The member value.

+            length,

+            mind = gap,

+            partial,

+            value = holder[key];

+

+// If the value has a toJSON method, call it to obtain a replacement value.

+

+        if (value && typeof value === 'object' &&

+                typeof value.toJSON === 'function') {

+            value = value.toJSON(key);

+        }

+

+// If we were called with a replacer function, then call the replacer to

+// obtain a replacement value.

+

+        if (typeof rep === 'function') {

+            value = rep.call(holder, key, value);

+        }

+

+// What happens next depends on the value's type.

+

+        switch (typeof value) {

+        case 'string':

+            return quote(value);

+

+        case 'number':

+

+// JSON numbers must be finite. Encode non-finite numbers as null.

+

+            return isFinite(value) ? String(value) : 'null';

+

+        case 'boolean':

+        case 'null':

+

+// If the value is a boolean or null, convert it to a string. Note:

+// typeof null does not produce 'null'. The case is included here in

+// the remote chance that this gets fixed someday.

+

+            return String(value);

+

+// If the type is 'object', we might be dealing with an object or an array or

+// null.

+

+        case 'object':

+

+// Due to a specification blunder in ECMAScript, typeof null is 'object',

+// so watch out for that case.

+

+            if (!value) {

+                return 'null';

+            }

+

+// Make an array to hold the partial results of stringifying this object value.

+

+            gap += indent;

+            partial = [];

+

+// Is the value an array?

+

+            if (Object.prototype.toString.apply(value) === '[object Array]') {

+

+// The value is an array. Stringify every element. Use null as a placeholder

+// for non-JSON values.

+

+                length = value.length;

+                for (i = 0; i < length; i += 1) {

+                    partial[i] = str(i, value) || 'null';

+                }

+

+// Join all of the elements together, separated with commas, and wrap them in

+// brackets.

+

+                v = partial.length === 0 ? '[]' : gap ?

+                    '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' :

+                    '[' + partial.join(',') + ']';

+                gap = mind;

+                return v;

+            }

+

+// If the replacer is an array, use it to select the members to be stringified.

+

+            if (rep && typeof rep === 'object') {

+                length = rep.length;

+                for (i = 0; i < length; i += 1) {

+                    if (typeof rep[i] === 'string') {

+                        k = rep[i];

+                        v = str(k, value);

+                        if (v) {

+                            partial.push(quote(k) + (gap ? ': ' : ':') + v);

+                        }

+                    }

+                }

+            } else {

+

+// Otherwise, iterate through all of the keys in the object.

+

+                for (k in value) {

+                    if (Object.prototype.hasOwnProperty.call(value, k)) {

+                        v = str(k, value);

+                        if (v) {

+                            partial.push(quote(k) + (gap ? ': ' : ':') + v);

+                        }

+                    }

+                }

+            }

+

+// Join all of the member texts together, separated with commas,

+// and wrap them in braces.

+

+            v = partial.length === 0 ? '{}' : gap ?

+                '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' :

+                '{' + partial.join(',') + '}';

+            gap = mind;

+            return v;

+        }

+    }

+

+// If the JSON object does not yet have a stringify method, give it one.

+

+    if (typeof JSON.stringify !== 'function') {

+        JSON.stringify = function (value, replacer, space) {

+

+// The stringify method takes a value and an optional replacer, and an optional

+// space parameter, and returns a JSON text. The replacer can be a function

+// that can replace values, or an array of strings that will select the keys.

+// A default replacer method can be provided. Use of the space parameter can

+// produce text that is more easily readable.

+

+            var i;

+            gap = '';

+            indent = '';

+

+// If the space parameter is a number, make an indent string containing that

+// many spaces.

+

+            if (typeof space === 'number') {

+                for (i = 0; i < space; i += 1) {

+                    indent += ' ';

+                }

+

+// If the space parameter is a string, it will be used as the indent string.

+

+            } else if (typeof space === 'string') {

+                indent = space;

+            }

+

+// If there is a replacer, it must be a function or an array.

+// Otherwise, throw an error.

+

+            rep = replacer;

+            if (replacer && typeof replacer !== 'function' &&

+                    (typeof replacer !== 'object' ||

+                    typeof replacer.length !== 'number')) {

+                throw new Error('JSON.stringify');

+            }

+

+// Make a fake root object containing our value under the key of ''.

+// Return the result of stringifying the value.

+

+            return str('', {'': value});

+        };

+    }

+

+

+// If the JSON object does not yet have a parse method, give it one.

+

+    if (typeof JSON.parse !== 'function') {

+        JSON.parse = function (text, reviver) {

+

+// The parse method takes a text and an optional reviver function, and returns

+// a JavaScript value if the text is a valid JSON text.

+

+            var j;

+

+            function walk(holder, key) {

+

+// The walk method is used to recursively walk the resulting structure so

+// that modifications can be made.

+

+                var k, v, value = holder[key];

+                if (value && typeof value === 'object') {

+                    for (k in value) {

+                        if (Object.prototype.hasOwnProperty.call(value, k)) {

+                            v = walk(value, k);

+                            if (v !== undefined) {

+                                value[k] = v;

+                            } else {

+                                delete value[k];

+                            }

+                        }

+                    }

+                }

+                return reviver.call(holder, key, value);

+            }

+

+

+// Parsing happens in four stages. In the first stage, we replace certain

+// Unicode characters with escape sequences. JavaScript handles many characters

+// incorrectly, either silently deleting them, or treating them as line endings.

+

+            text = String(text);

+            cx.lastIndex = 0;

+            if (cx.test(text)) {

+                text = text.replace(cx, function (a) {

+                    return '\\u' +

+                        ('0000' + a.charCodeAt(0).toString(16)).slice(-4);

+                });

+            }

+

+// In the second stage, we run the text against regular expressions that look

+// for non-JSON patterns. We are especially concerned with '()' and 'new'

+// because they can cause invocation, and '=' because it can cause mutation.

+// But just to be safe, we want to reject all unexpected forms.

+

+// We split the second stage into 4 regexp operations in order to work around

+// crippling inefficiencies in IE's and Safari's regexp engines. First we

+// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we

+// replace all simple value tokens with ']' characters. Third, we delete all

+// open brackets that follow a colon or comma or that begin the text. Finally,

+// we look to see that the remaining characters are only whitespace or ']' or

+// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.

+

+            if (/^[\],:{}\s]*$/

+                    .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')

+                        .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')

+                        .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {

+

+// In the third stage we use the eval function to compile the text into a

+// JavaScript structure. The '{' operator is subject to a syntactic ambiguity

+// in JavaScript: it can begin a block or an object literal. We wrap the text

+// in parens to eliminate the ambiguity.

+

+                j = eval('(' + text + ')');

+

+// In the optional fourth stage, we recursively walk the new structure, passing

+// each name/value pair to a reviver function for possible transformation.

+

+                return typeof reviver === 'function' ?

+                    walk({'': j}, '') : j;

+            }

+

+// If the text is not JSON parseable, then a SyntaxError is thrown.

+

+            throw new SyntaxError('JSON.parse');

+        };

+    }

+}());

diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/main.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/main.js
new file mode 100644
index 0000000..4a9025a
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/main.js
@@ -0,0 +1,3 @@
+$(document).ready(function() {
+
+});
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/nav.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/nav.js
new file mode 100644
index 0000000..7736b4a
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/nav.js
@@ -0,0 +1,64 @@
+$(document).ready(function() {
+    var docsLoaded = false;
+
+    $(window).hashchange(function(e){
+        e.preventDefault();
+        e.stopPropagation();
+
+        if (location.hash === "#tryit") {
+            $("#main > .content").hide();
+            $("#tryit input").val("").keyup();
+            $("#tryit").fadeIn(400, function() {
+                $("#tryit input").val(".languagesSpoken .lang").keyup();
+            });
+        } else if (location.hash === "#cred") {
+            $("#main > .content").hide();
+            $("#cred").fadeIn(400);
+        } else if (location.hash === '#overview' || location.hash === '') {
+            $("#main > .content").hide();
+            $("#splash").fadeIn(400);
+        } else if (location.hash === '#code' || location.hash === '') {
+            $("#main > .content").hide();
+            $("#code").fadeIn(400);
+        } else if (location.hash.substr(0,5) === "#docs") {
+            function showIt() {
+                var where = window.location.hash.substr(6);
+                if (!where) {
+                    $("#doc").fadeIn(400);
+                } else {
+                    $("#doc").show();
+                    var dst = $("a[name='" + where + "']");
+                    if (dst.length) {
+                        $('html, body').animate({scrollTop:dst.offset().top - 100}, 500);
+                    }
+                }
+            }
+            $("#main > .content").hide();
+            if (!docsLoaded) {
+                $.get("JSONSelect.md").success(function(data) {
+                    var converter = new Showdown.converter();
+                    $("#doc").html(converter.makeHtml(data));
+                    $("#doc a").each(function() {
+                        var n = $(this).attr('href');
+                        if (typeof n === 'string' && n.substr(0,1) === '#') {
+                            $(this).attr('href', "#docs/" + n.substr(1));
+                        }
+                    });
+                    docsLoaded = true;
+                    showIt();
+                }).error(function() {
+                    $("#doc").text("Darnit, error fetching docs...").fadeIn(400);
+                });
+            } else {
+                showIt();
+            }
+        } else {
+        }
+        return false;
+    });
+
+    // Trigger the event (useful on page load).
+    if (window.location.hash === "")
+        window.location.hash = "#overview";
+    $(window).hashchange();
+});
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/showdown.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/showdown.js
new file mode 100644
index 0000000..43920d9
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/showdown.js
@@ -0,0 +1,1302 @@
+//

+// showdown.js -- A javascript port of Markdown.

+//

+// Copyright (c) 2007 John Fraser.

+//

+// Original Markdown Copyright (c) 2004-2005 John Gruber

+//   <http://daringfireball.net/projects/markdown/>

+//

+// Redistributable under a BSD-style open source license.

+// See license.txt for more information.

+//

+// The full source distribution is at:

+//

+//				A A L

+//				T C A

+//				T K B

+//

+//   <http://www.attacklab.net/>

+//

+

+//

+// Wherever possible, Showdown is a straight, line-by-line port

+// of the Perl version of Markdown.

+//

+// This is not a normal parser design; it's basically just a

+// series of string substitutions.  It's hard to read and

+// maintain this way,  but keeping Showdown close to the original

+// design makes it easier to port new features.

+//

+// More importantly, Showdown behaves like markdown.pl in most

+// edge cases.  So web applications can do client-side preview

+// in Javascript, and then build identical HTML on the server.

+//

+// This port needs the new RegExp functionality of ECMA 262,

+// 3rd Edition (i.e. Javascript 1.5).  Most modern web browsers

+// should do fine.  Even with the new regular expression features,

+// We do a lot of work to emulate Perl's regex functionality.

+// The tricky changes in this file mostly have the "attacklab:"

+// label.  Major or self-explanatory changes don't.

+//

+// Smart diff tools like Araxis Merge will be able to match up

+// this file with markdown.pl in a useful way.  A little tweaking

+// helps: in a copy of markdown.pl, replace "#" with "//" and

+// replace "$text" with "text".  Be sure to ignore whitespace

+// and line endings.

+//

+

+

+//

+// Showdown usage:

+//

+//   var text = "Markdown *rocks*.";

+//

+//   var converter = new Showdown.converter();

+//   var html = converter.makeHtml(text);

+//

+//   alert(html);

+//

+// Note: move the sample code to the bottom of this

+// file before uncommenting it.

+//

+

+

+//

+// Showdown namespace

+//

+var Showdown = {};

+

+//

+// converter

+//

+// Wraps all "globals" so that the only thing

+// exposed is makeHtml().

+//

+Showdown.converter = function() {

+

+//

+// Globals:

+//

+

+// Global hashes, used by various utility routines

+var g_urls;

+var g_titles;

+var g_html_blocks;

+

+// Used to track when we're inside an ordered or unordered list

+// (see _ProcessListItems() for details):

+var g_list_level = 0;

+

+

+this.makeHtml = function(text) {

+//

+// Main function. The order in which other subs are called here is

+// essential. Link and image substitutions need to happen before

+// _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the <a>

+// and <img> tags get encoded.

+//

+

+	// Clear the global hashes. If we don't clear these, you get conflicts

+	// from other articles when generating a page which contains more than

+	// one article (e.g. an index page that shows the N most recent

+	// articles):

+	g_urls = new Array();

+	g_titles = new Array();

+	g_html_blocks = new Array();

+

+	// attacklab: Replace ~ with ~T

+	// This lets us use tilde as an escape char to avoid md5 hashes

+	// The choice of character is arbitray; anything that isn't

+    // magic in Markdown will work.

+	text = text.replace(/~/g,"~T");

+

+	// attacklab: Replace $ with ~D

+	// RegExp interprets $ as a special character

+	// when it's in a replacement string

+	text = text.replace(/\$/g,"~D");

+

+	// Standardize line endings

+	text = text.replace(/\r\n/g,"\n"); // DOS to Unix

+	text = text.replace(/\r/g,"\n"); // Mac to Unix

+

+	// Make sure text begins and ends with a couple of newlines:

+	text = "\n\n" + text + "\n\n";

+

+	// Convert all tabs to spaces.

+	text = _Detab(text);

+

+	// Strip any lines consisting only of spaces and tabs.

+	// This makes subsequent regexen easier to write, because we can

+	// match consecutive blank lines with /\n+/ instead of something

+	// contorted like /[ \t]*\n+/ .

+	text = text.replace(/^[ \t]+$/mg,"");

+

+	// Turn block-level HTML blocks into hash entries

+	text = _HashHTMLBlocks(text);

+

+	// Strip link definitions, store in hashes.

+	text = _StripLinkDefinitions(text);

+

+	text = _RunBlockGamut(text);

+

+	text = _UnescapeSpecialChars(text);

+

+	// attacklab: Restore dollar signs

+	text = text.replace(/~D/g,"$$");

+

+	// attacklab: Restore tildes

+	text = text.replace(/~T/g,"~");

+

+	return text;

+}

+

+

+var _StripLinkDefinitions = function(text) {

+//

+// Strips link definitions from text, stores the URLs and titles in

+// hash references.

+//

+

+	// Link defs are in the form: ^[id]: url "optional title"

+

+	/*

+		var text = text.replace(/

+				^[ ]{0,3}\[(.+)\]:  // id = $1  attacklab: g_tab_width - 1

+				  [ \t]*

+				  \n?				// maybe *one* newline

+				  [ \t]*

+				<?(\S+?)>?			// url = $2

+				  [ \t]*

+				  \n?				// maybe one newline

+				  [ \t]*

+				(?:

+				  (\n*)				// any lines skipped = $3 attacklab: lookbehind removed

+				  ["(]

+				  (.+?)				// title = $4

+				  [")]

+				  [ \t]*

+				)?					// title is optional

+				(?:\n+|$)

+			  /gm,

+			  function(){...});

+	*/

+	var text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|\Z)/gm,

+		function (wholeMatch,m1,m2,m3,m4) {

+			m1 = m1.toLowerCase();

+			g_urls[m1] = _EncodeAmpsAndAngles(m2);  // Link IDs are case-insensitive

+			if (m3) {

+				// Oops, found blank lines, so it's not a title.

+				// Put back the parenthetical statement we stole.

+				return m3+m4;

+			} else if (m4) {

+				g_titles[m1] = m4.replace(/"/g,"&quot;");

+			}

+			

+			// Completely remove the definition from the text

+			return "";

+		}

+	);

+

+	return text;

+}

+

+

+var _HashHTMLBlocks = function(text) {

+	// attacklab: Double up blank lines to reduce lookaround

+	text = text.replace(/\n/g,"\n\n");

+

+	// Hashify HTML blocks:

+	// We only want to do this for block-level HTML tags, such as headers,

+	// lists, and tables. That's because we still want to wrap <p>s around

+	// "paragraphs" that are wrapped in non-block-level tags, such as anchors,

+	// phrase emphasis, and spans. The list of tags we're looking for is

+	// hard-coded:

+	var block_tags_a = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del"

+	var block_tags_b = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math"

+

+	// First, look for nested blocks, e.g.:

+	//   <div>

+	//     <div>

+	//     tags for inner block must be indented.

+	//     </div>

+	//   </div>

+	//

+	// The outermost tags must start at the left margin for this to match, and

+	// the inner nested divs must be indented.

+	// We need to do this before the next, more liberal match, because the next

+	// match will start at the first `<div>` and stop at the first `</div>`.

+

+	// attacklab: This regex can be expensive when it fails.

+	/*

+		var text = text.replace(/

+		(						// save in $1

+			^					// start of line  (with /m)

+			<($block_tags_a)	// start tag = $2

+			\b					// word break

+								// attacklab: hack around khtml/pcre bug...

+			[^\r]*?\n			// any number of lines, minimally matching

+			</\2>				// the matching end tag

+			[ \t]*				// trailing spaces/tabs

+			(?=\n+)				// followed by a newline

+		)						// attacklab: there are sentinel newlines at end of document

+		/gm,function(){...}};

+	*/

+	text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)\b[^\r]*?\n<\/\2>[ \t]*(?=\n+))/gm,hashElement);

+

+	//

+	// Now match more liberally, simply from `\n<tag>` to `</tag>\n`

+	//

+

+	/*

+		var text = text.replace(/

+		(						// save in $1

+			^					// start of line  (with /m)

+			<($block_tags_b)	// start tag = $2

+			\b					// word break

+								// attacklab: hack around khtml/pcre bug...

+			[^\r]*?				// any number of lines, minimally matching

+			.*</\2>				// the matching end tag

+			[ \t]*				// trailing spaces/tabs

+			(?=\n+)				// followed by a newline

+		)						// attacklab: there are sentinel newlines at end of document

+		/gm,function(){...}};

+	*/

+	text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math)\b[^\r]*?.*<\/\2>[ \t]*(?=\n+)\n)/gm,hashElement);

+

+	// Special case just for <hr />. It was easier to make a special case than

+	// to make the other regex more complicated.  

+

+	/*

+		text = text.replace(/

+		(						// save in $1

+			\n\n				// Starting after a blank line

+			[ ]{0,3}

+			(<(hr)				// start tag = $2

+			\b					// word break

+			([^<>])*?			// 

+			\/?>)				// the matching end tag

+			[ \t]*

+			(?=\n{2,})			// followed by a blank line

+		)

+		/g,hashElement);

+	*/

+	text = text.replace(/(\n[ ]{0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,hashElement);

+

+	// Special case for standalone HTML comments:

+

+	/*

+		text = text.replace(/

+		(						// save in $1

+			\n\n				// Starting after a blank line

+			[ ]{0,3}			// attacklab: g_tab_width - 1

+			<!

+			(--[^\r]*?--\s*)+

+			>

+			[ \t]*

+			(?=\n{2,})			// followed by a blank line

+		)

+		/g,hashElement);

+	*/

+	text = text.replace(/(\n\n[ ]{0,3}<!(--[^\r]*?--\s*)+>[ \t]*(?=\n{2,}))/g,hashElement);

+

+	// PHP and ASP-style processor instructions (<?...?> and <%...%>)

+

+	/*

+		text = text.replace(/

+		(?:

+			\n\n				// Starting after a blank line

+		)

+		(						// save in $1

+			[ ]{0,3}			// attacklab: g_tab_width - 1

+			(?:

+				<([?%])			// $2

+				[^\r]*?

+				\2>

+			)

+			[ \t]*

+			(?=\n{2,})			// followed by a blank line

+		)

+		/g,hashElement);

+	*/

+	text = text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,hashElement);

+

+	// attacklab: Undo double lines (see comment at top of this function)

+	text = text.replace(/\n\n/g,"\n");

+	return text;

+}

+

+var hashElement = function(wholeMatch,m1) {

+	var blockText = m1;

+

+	// Undo double lines

+	blockText = blockText.replace(/\n\n/g,"\n");

+	blockText = blockText.replace(/^\n/,"");

+	

+	// strip trailing blank lines

+	blockText = blockText.replace(/\n+$/g,"");

+	

+	// Replace the element text with a marker ("~KxK" where x is its key)

+	blockText = "\n\n~K" + (g_html_blocks.push(blockText)-1) + "K\n\n";

+	

+	return blockText;

+};

+

+var _RunBlockGamut = function(text) {

+//

+// These are all the transformations that form block-level

+// tags like paragraphs, headers, and list items.

+//

+	text = _DoHeaders(text);

+

+	// Do Horizontal Rules:

+	var key = hashBlock("<hr />");

+	text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm,key);

+	text = text.replace(/^[ ]{0,2}([ ]?\-[ ]?){3,}[ \t]*$/gm,key);

+	text = text.replace(/^[ ]{0,2}([ ]?\_[ ]?){3,}[ \t]*$/gm,key);

+

+	text = _DoLists(text);

+	text = _DoCodeBlocks(text);

+	text = _DoBlockQuotes(text);

+

+	// We already ran _HashHTMLBlocks() before, in Markdown(), but that

+	// was to escape raw HTML in the original Markdown source. This time,

+	// we're escaping the markup we've just created, so that we don't wrap

+	// <p> tags around block-level tags.

+	text = _HashHTMLBlocks(text);

+	text = _FormParagraphs(text);

+

+	return text;

+}

+

+

+var _RunSpanGamut = function(text) {

+//

+// These are all the transformations that occur *within* block-level

+// tags like paragraphs, headers, and list items.

+//

+

+	text = _DoCodeSpans(text);

+	text = _EscapeSpecialCharsWithinTagAttributes(text);

+	text = _EncodeBackslashEscapes(text);

+

+	// Process anchor and image tags. Images must come first,

+	// because ![foo][f] looks like an anchor.

+	text = _DoImages(text);

+	text = _DoAnchors(text);

+

+	// Make links out of things like `<http://example.com/>`

+	// Must come after _DoAnchors(), because you can use < and >

+	// delimiters in inline links like [this](<url>).

+	text = _DoAutoLinks(text);

+	text = _EncodeAmpsAndAngles(text);

+	text = _DoItalicsAndBold(text);

+

+	// Do hard breaks:

+	text = text.replace(/  +\n/g," <br />\n");

+

+	return text;

+}

+

+var _EscapeSpecialCharsWithinTagAttributes = function(text) {

+//

+// Within tags -- meaning between < and > -- encode [\ ` * _] so they

+// don't conflict with their use in Markdown for code, italics and strong.

+//

+

+	// Build a regex to find HTML tags and comments.  See Friedl's 

+	// "Mastering Regular Expressions", 2nd Ed., pp. 200-201.

+	var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|<!(--.*?--\s*)+>)/gi;

+

+	text = text.replace(regex, function(wholeMatch) {

+		var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g,"$1`");

+		tag = escapeCharacters(tag,"\\`*_");

+		return tag;

+	});

+

+	return text;

+}

+

+var _DoAnchors = function(text) {

+//

+// Turn Markdown link shortcuts into XHTML <a> tags.

+//

+	//

+	// First, handle reference-style links: [link text] [id]

+	//

+

+	/*

+		text = text.replace(/

+		(							// wrap whole match in $1

+			\[

+			(

+				(?:

+					\[[^\]]*\]		// allow brackets nested one level

+					|

+					[^\[]			// or anything else

+				)*

+			)

+			\]

+

+			[ ]?					// one optional space

+			(?:\n[ ]*)?				// one optional newline followed by spaces

+

+			\[

+			(.*?)					// id = $3

+			\]

+		)()()()()					// pad remaining backreferences

+		/g,_DoAnchors_callback);

+	*/

+	text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeAnchorTag);

+

+	//

+	// Next, inline-style links: [link text](url "optional title")

+	//

+

+	/*

+		text = text.replace(/

+			(						// wrap whole match in $1

+				\[

+				(

+					(?:

+						\[[^\]]*\]	// allow brackets nested one level

+					|

+					[^\[\]]			// or anything else

+				)

+			)

+			\]

+			\(						// literal paren

+			[ \t]*

+			()						// no id, so leave $3 empty

+			<?(.*?)>?				// href = $4

+			[ \t]*

+			(						// $5

+				(['"])				// quote char = $6

+				(.*?)				// Title = $7

+				\6					// matching quote

+				[ \t]*				// ignore any spaces/tabs between closing quote and )

+			)?						// title is optional

+			\)

+		)

+		/g,writeAnchorTag);

+	*/

+	text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()<?(.*?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeAnchorTag);

+

+	//

+	// Last, handle reference-style shortcuts: [link text]

+	// These must come last in case you've also got [link test][1]

+	// or [link test](/foo)

+	//

+

+	/*

+		text = text.replace(/

+		(		 					// wrap whole match in $1

+			\[

+			([^\[\]]+)				// link text = $2; can't contain '[' or ']'

+			\]

+		)()()()()()					// pad rest of backreferences

+		/g, writeAnchorTag);

+	*/

+	text = text.replace(/(\[([^\[\]]+)\])()()()()()/g, writeAnchorTag);

+

+	return text;

+}

+

+var writeAnchorTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) {

+	if (m7 == undefined) m7 = "";

+	var whole_match = m1;

+	var link_text   = m2;

+	var link_id	 = m3.toLowerCase();

+	var url		= m4;

+	var title	= m7;

+	

+	if (url == "") {

+		if (link_id == "") {

+			// lower-case and turn embedded newlines into spaces

+			link_id = link_text.toLowerCase().replace(/ ?\n/g," ");

+		}

+		url = "#"+link_id;

+		

+		if (g_urls[link_id] != undefined) {

+			url = g_urls[link_id];

+			if (g_titles[link_id] != undefined) {

+				title = g_titles[link_id];

+			}

+		}

+		else {

+			if (whole_match.search(/\(\s*\)$/m)>-1) {

+				// Special case for explicit empty url

+				url = "";

+			} else {

+				return whole_match;

+			}

+		}

+	}	

+	

+	url = escapeCharacters(url,"*_");

+	var result = "<a href=\"" + url + "\"";

+	

+	if (title != "") {

+		title = title.replace(/"/g,"&quot;");

+		title = escapeCharacters(title,"*_");

+		result +=  " title=\"" + title + "\"";

+	}

+	

+	result += ">" + link_text + "</a>";

+	

+	return result;

+}

+

+

+var _DoImages = function(text) {

+//

+// Turn Markdown image shortcuts into <img> tags.

+//

+

+	//

+	// First, handle reference-style labeled images: ![alt text][id]

+	//

+

+	/*

+		text = text.replace(/

+		(						// wrap whole match in $1

+			!\[

+			(.*?)				// alt text = $2

+			\]

+

+			[ ]?				// one optional space

+			(?:\n[ ]*)?			// one optional newline followed by spaces

+

+			\[

+			(.*?)				// id = $3

+			\]

+		)()()()()				// pad rest of backreferences

+		/g,writeImageTag);

+	*/

+	text = text.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeImageTag);

+

+	//

+	// Next, handle inline images:  ![alt text](url "optional title")

+	// Don't forget: encode * and _

+

+	/*

+		text = text.replace(/

+		(						// wrap whole match in $1

+			!\[

+			(.*?)				// alt text = $2

+			\]

+			\s?					// One optional whitespace character

+			\(					// literal paren

+			[ \t]*

+			()					// no id, so leave $3 empty

+			<?(\S+?)>?			// src url = $4

+			[ \t]*

+			(					// $5

+				(['"])			// quote char = $6

+				(.*?)			// title = $7

+				\6				// matching quote

+				[ \t]*

+			)?					// title is optional

+		\)

+		)

+		/g,writeImageTag);

+	*/

+	text = text.replace(/(!\[(.*?)\]\s?\([ \t]*()<?(\S+?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeImageTag);

+

+	return text;

+}

+

+var writeImageTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) {

+	var whole_match = m1;

+	var alt_text   = m2;

+	var link_id	 = m3.toLowerCase();

+	var url		= m4;

+	var title	= m7;

+

+	if (!title) title = "";

+	

+	if (url == "") {

+		if (link_id == "") {

+			// lower-case and turn embedded newlines into spaces

+			link_id = alt_text.toLowerCase().replace(/ ?\n/g," ");

+		}

+		url = "#"+link_id;

+		

+		if (g_urls[link_id] != undefined) {

+			url = g_urls[link_id];

+			if (g_titles[link_id] != undefined) {

+				title = g_titles[link_id];

+			}

+		}

+		else {

+			return whole_match;

+		}

+	}	

+	

+	alt_text = alt_text.replace(/"/g,"&quot;");

+	url = escapeCharacters(url,"*_");

+	var result = "<img src=\"" + url + "\" alt=\"" + alt_text + "\"";

+

+	// attacklab: Markdown.pl adds empty title attributes to images.

+	// Replicate this bug.

+

+	//if (title != "") {

+		title = title.replace(/"/g,"&quot;");

+		title = escapeCharacters(title,"*_");

+		result +=  " title=\"" + title + "\"";

+	//}

+	

+	result += " />";

+	

+	return result;

+}

+

+

+var _DoHeaders = function(text) {

+

+	// Setext-style headers:

+	//	Header 1

+	//	========

+	//  

+	//	Header 2

+	//	--------

+	//

+	text = text.replace(/^(.+)[ \t]*\n=+[ \t]*\n+/gm,

+		function(wholeMatch,m1){return hashBlock('<h1 id="' + headerId(m1) + '">' + _RunSpanGamut(m1) + "</h1>");});

+

+	text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm,

+		function(matchFound,m1){return hashBlock('<h2 id="' + headerId(m1) + '">' + _RunSpanGamut(m1) + "</h2>");});

+

+	// atx-style headers:

+	//  # Header 1

+	//  ## Header 2

+	//  ## Header 2 with closing hashes ##

+	//  ...

+	//  ###### Header 6

+	//

+

+	/*

+		text = text.replace(/

+			^(\#{1,6})				// $1 = string of #'s

+			[ \t]*

+			(.+?)					// $2 = Header text

+			[ \t]*

+			\#*						// optional closing #'s (not counted)

+			\n+

+		/gm, function() {...});

+	*/

+

+	text = text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm,

+		function(wholeMatch,m1,m2) {

+			var h_level = m1.length;

+			return hashBlock("<h" + h_level + ' id="' + headerId(m2) + '">' + _RunSpanGamut(m2) + "</h" + h_level + ">");

+		});

+

+	function headerId(m) {

+		return m.replace(/[^\w]/g, '').toLowerCase();

+	}

+	return text;

+}

+

+// This declaration keeps Dojo compressor from outputting garbage:

+var _ProcessListItems;

+

+var _DoLists = function(text) {

+//

+// Form HTML ordered (numbered) and unordered (bulleted) lists.

+//

+

+	// attacklab: add sentinel to hack around khtml/safari bug:

+	// http://bugs.webkit.org/show_bug.cgi?id=11231

+	text += "~0";

+

+	// Re-usable pattern to match any entirel ul or ol list:

+

+	/*

+		var whole_list = /

+		(									// $1 = whole list

+			(								// $2

+				[ ]{0,3}					// attacklab: g_tab_width - 1

+				([*+-]|\d+[.])				// $3 = first list item marker

+				[ \t]+

+			)

+			[^\r]+?

+			(								// $4

+				~0							// sentinel for workaround; should be $

+			|

+				\n{2,}

+				(?=\S)

+				(?!							// Negative lookahead for another list item marker

+					[ \t]*

+					(?:[*+-]|\d+[.])[ \t]+

+				)

+			)

+		)/g

+	*/

+	var whole_list = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;

+

+	if (g_list_level) {

+		text = text.replace(whole_list,function(wholeMatch,m1,m2) {

+			var list = m1;

+			var list_type = (m2.search(/[*+-]/g)>-1) ? "ul" : "ol";

+

+			// Turn double returns into triple returns, so that we can make a

+			// paragraph for the last item in a list, if necessary:

+			list = list.replace(/\n{2,}/g,"\n\n\n");;

+			var result = _ProcessListItems(list);

+	

+			// Trim any trailing whitespace, to put the closing `</$list_type>`

+			// up on the preceding line, to get it past the current stupid

+			// HTML block parser. This is a hack to work around the terrible

+			// hack that is the HTML block parser.

+			result = result.replace(/\s+$/,"");

+			result = "<"+list_type+">" + result + "</"+list_type+">\n";

+			return result;

+		});

+	} else {

+		whole_list = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g;

+		text = text.replace(whole_list,function(wholeMatch,m1,m2,m3) {

+			var runup = m1;

+			var list = m2;

+

+			var list_type = (m3.search(/[*+-]/g)>-1) ? "ul" : "ol";

+			// Turn double returns into triple returns, so that we can make a

+			// paragraph for the last item in a list, if necessary:

+			var list = list.replace(/\n{2,}/g,"\n\n\n");;

+			var result = _ProcessListItems(list);

+			result = runup + "<"+list_type+">\n" + result + "</"+list_type+">\n";	

+			return result;

+		});

+	}

+

+	// attacklab: strip sentinel

+	text = text.replace(/~0/,"");

+

+	return text;

+}

+

+_ProcessListItems = function(list_str) {

+//

+//  Process the contents of a single ordered or unordered list, splitting it

+//  into individual list items.

+//

+	// The $g_list_level global keeps track of when we're inside a list.

+	// Each time we enter a list, we increment it; when we leave a list,

+	// we decrement. If it's zero, we're not in a list anymore.

+	//

+	// We do this because when we're not inside a list, we want to treat

+	// something like this:

+	//

+	//    I recommend upgrading to version

+	//    8. Oops, now this line is treated

+	//    as a sub-list.

+	//

+	// As a single paragraph, despite the fact that the second line starts

+	// with a digit-period-space sequence.

+	//

+	// Whereas when we're inside a list (or sub-list), that line will be

+	// treated as the start of a sub-list. What a kludge, huh? This is

+	// an aspect of Markdown's syntax that's hard to parse perfectly

+	// without resorting to mind-reading. Perhaps the solution is to

+	// change the syntax rules such that sub-lists must start with a

+	// starting cardinal number; e.g. "1." or "a.".

+

+	g_list_level++;

+

+	// trim trailing blank lines:

+	list_str = list_str.replace(/\n{2,}$/,"\n");

+

+	// attacklab: add sentinel to emulate \z

+	list_str += "~0";

+

+	/*

+		list_str = list_str.replace(/

+			(\n)?							// leading line = $1

+			(^[ \t]*)						// leading whitespace = $2

+			([*+-]|\d+[.]) [ \t]+			// list marker = $3

+			([^\r]+?						// list item text   = $4

+			(\n{1,2}))

+			(?= \n* (~0 | \2 ([*+-]|\d+[.]) [ \t]+))

+		/gm, function(){...});

+	*/

+	list_str = list_str.replace(/(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+([^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm,

+		function(wholeMatch,m1,m2,m3,m4){

+			var item = m4;

+			var leading_line = m1;

+			var leading_space = m2;

+

+			if (leading_line || (item.search(/\n{2,}/)>-1)) {

+				item = _RunBlockGamut(_Outdent(item));

+			}

+			else {

+				// Recursion for sub-lists:

+				item = _DoLists(_Outdent(item));

+				item = item.replace(/\n$/,""); // chomp(item)

+				item = _RunSpanGamut(item);

+			}

+

+			return  "<li>" + item + "</li>\n";

+		}

+	);

+

+	// attacklab: strip sentinel

+	list_str = list_str.replace(/~0/g,"");

+

+	g_list_level--;

+	return list_str;

+}

+

+

+var _DoCodeBlocks = function(text) {

+//

+//  Process Markdown `<pre><code>` blocks.

+//  

+

+	/*

+		text = text.replace(text,

+			/(?:\n\n|^)

+			(								// $1 = the code block -- one or more lines, starting with a space/tab

+				(?:

+					(?:[ ]{4}|\t)			// Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width

+					.*\n+

+				)+

+			)

+			(\n*[ ]{0,3}[^ \t\n]|(?=~0))	// attacklab: g_tab_width

+		/g,function(){...});

+	*/

+

+	// attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug

+	text += "~0";

+	

+	text = text.replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,

+		function(wholeMatch,m1,m2) {

+			var codeblock = m1;

+			var nextChar = m2;

+		

+			codeblock = _EncodeCode( _Outdent(codeblock));

+			codeblock = _Detab(codeblock);

+			codeblock = codeblock.replace(/^\n+/g,""); // trim leading newlines

+			codeblock = codeblock.replace(/\n+$/g,""); // trim trailing whitespace

+

+			codeblock = "<pre><code>" + codeblock + "\n</code></pre>";

+

+			return hashBlock(codeblock) + nextChar;

+		}

+	);

+

+	// attacklab: strip sentinel

+	text = text.replace(/~0/,"");

+

+	return text;

+}

+

+var hashBlock = function(text) {

+	text = text.replace(/(^\n+|\n+$)/g,"");

+	return "\n\n~K" + (g_html_blocks.push(text)-1) + "K\n\n";

+}

+

+

+var _DoCodeSpans = function(text) {

+//

+//   *  Backtick quotes are used for <code></code> spans.

+// 

+//   *  You can use multiple backticks as the delimiters if you want to

+//	 include literal backticks in the code span. So, this input:

+//	 

+//		 Just type ``foo `bar` baz`` at the prompt.

+//	 

+//	   Will translate to:

+//	 

+//		 <p>Just type <code>foo `bar` baz</code> at the prompt.</p>

+//	 

+//	There's no arbitrary limit to the number of backticks you

+//	can use as delimters. If you need three consecutive backticks

+//	in your code, use four for delimiters, etc.

+//

+//  *  You can use spaces to get literal backticks at the edges:

+//	 

+//		 ... type `` `bar` `` ...

+//	 

+//	   Turns to:

+//	 

+//		 ... type <code>`bar`</code> ...

+//

+

+	/*

+		text = text.replace(/

+			(^|[^\\])					// Character before opening ` can't be a backslash

+			(`+)						// $2 = Opening run of `

+			(							// $3 = The code block

+				[^\r]*?

+				[^`]					// attacklab: work around lack of lookbehind

+			)

+			\2							// Matching closer

+			(?!`)

+		/gm, function(){...});

+	*/

+

+	text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,

+		function(wholeMatch,m1,m2,m3,m4) {

+			var c = m3;

+			c = c.replace(/^([ \t]*)/g,"");	// leading whitespace

+			c = c.replace(/[ \t]*$/g,"");	// trailing whitespace

+			c = _EncodeCode(c);

+			return m1+"<code>"+c+"</code>";

+		});

+

+	return text;

+}

+

+

+var _EncodeCode = function(text) {

+//

+// Encode/escape certain characters inside Markdown code runs.

+// The point is that in code, these characters are literals,

+// and lose their special Markdown meanings.

+//

+	// Encode all ampersands; HTML entities are not

+	// entities within a Markdown code span.

+	text = text.replace(/&/g,"&amp;");

+

+	// Do the angle bracket song and dance:

+	text = text.replace(/</g,"&lt;");

+	text = text.replace(/>/g,"&gt;");

+

+	// Now, escape characters that are magic in Markdown:

+	text = escapeCharacters(text,"\*_{}[]\\",false);

+

+// jj the line above breaks this:

+//---

+

+//* Item

+

+//   1. Subitem

+

+//            special char: *

+//---

+

+	return text;

+}

+

+

+var _DoItalicsAndBold = function(text) {

+

+	// <strong> must go first:

+	text = text.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g,

+		"<strong>$2</strong>");

+

+	text = text.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g,

+		"<em>$2</em>");

+

+	return text;

+}

+

+

+var _DoBlockQuotes = function(text) {

+

+	/*

+		text = text.replace(/

+		(								// Wrap whole match in $1

+			(

+				^[ \t]*>[ \t]?			// '>' at the start of a line

+				.+\n					// rest of the first line

+				(.+\n)*					// subsequent consecutive lines

+				\n*						// blanks

+			)+

+		)

+		/gm, function(){...});

+	*/

+

+	text = text.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm,

+		function(wholeMatch,m1) {

+			var bq = m1;

+

+			// attacklab: hack around Konqueror 3.5.4 bug:

+			// "----------bug".replace(/^-/g,"") == "bug"

+

+			bq = bq.replace(/^[ \t]*>[ \t]?/gm,"~0");	// trim one level of quoting

+

+			// attacklab: clean up hack

+			bq = bq.replace(/~0/g,"");

+

+			bq = bq.replace(/^[ \t]+$/gm,"");		// trim whitespace-only lines

+			bq = _RunBlockGamut(bq);				// recurse

+			

+			bq = bq.replace(/(^|\n)/g,"$1  ");

+			// These leading spaces screw with <pre> content, so we need to fix that:

+			bq = bq.replace(

+					/(\s*<pre>[^\r]+?<\/pre>)/gm,

+				function(wholeMatch,m1) {

+					var pre = m1;

+					// attacklab: hack around Konqueror 3.5.4 bug:

+					pre = pre.replace(/^  /mg,"~0");

+					pre = pre.replace(/~0/g,"");

+					return pre;

+				});

+			

+			return hashBlock("<blockquote>\n" + bq + "\n</blockquote>");

+		});

+	return text;

+}

+

+

+var _FormParagraphs = function(text) {

+//

+//  Params:

+//    $text - string to process with html <p> tags

+//

+

+	// Strip leading and trailing lines:

+	text = text.replace(/^\n+/g,"");

+	text = text.replace(/\n+$/g,"");

+

+	var grafs = text.split(/\n{2,}/g);

+	var grafsOut = new Array();

+

+	//

+	// Wrap <p> tags.

+	//

+	var end = grafs.length;

+	for (var i=0; i<end; i++) {

+		var str = grafs[i];

+

+		// if this is an HTML marker, copy it

+		if (str.search(/~K(\d+)K/g) >= 0) {

+			grafsOut.push(str);

+		}

+		else if (str.search(/\S/) >= 0) {

+			str = _RunSpanGamut(str);

+			str = str.replace(/^([ \t]*)/g,"<p>");

+			str += "</p>"

+			grafsOut.push(str);

+		}

+

+	}

+

+	//

+	// Unhashify HTML blocks

+	//

+	end = grafsOut.length;

+	for (var i=0; i<end; i++) {

+		// if this is a marker for an html block...

+		while (grafsOut[i].search(/~K(\d+)K/) >= 0) {

+			var blockText = g_html_blocks[RegExp.$1];

+			blockText = blockText.replace(/\$/g,"$$$$"); // Escape any dollar signs

+			grafsOut[i] = grafsOut[i].replace(/~K\d+K/,blockText);

+		}

+	}

+

+	return grafsOut.join("\n\n");

+}

+

+

+var _EncodeAmpsAndAngles = function(text) {

+// Smart processing for ampersands and angle brackets that need to be encoded.

+	

+	// Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin:

+	//   http://bumppo.net/projects/amputator/

+	text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g,"&amp;");

+	

+	// Encode naked <'s

+	text = text.replace(/<(?![a-z\/?\$!])/gi,"&lt;");

+	

+	return text;

+}

+

+

+var _EncodeBackslashEscapes = function(text) {

+//

+//   Parameter:  String.

+//   Returns:	The string, with after processing the following backslash

+//			   escape sequences.

+//

+

+	// attacklab: The polite way to do this is with the new

+	// escapeCharacters() function:

+	//

+	// 	text = escapeCharacters(text,"\\",true);

+	// 	text = escapeCharacters(text,"`*_{}[]()>#+-.!",true);

+	//

+	// ...but we're sidestepping its use of the (slow) RegExp constructor

+	// as an optimization for Firefox.  This function gets called a LOT.

+

+	text = text.replace(/\\(\\)/g,escapeCharacters_callback);

+	text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g,escapeCharacters_callback);

+	return text;

+}

+

+

+var _DoAutoLinks = function(text) {

+

+	text = text.replace(/<((https?|ftp|dict):[^'">\s]+)>/gi,"<a href=\"$1\">$1</a>");

+

+	// Email addresses: <address@domain.foo>

+

+	/*

+		text = text.replace(/

+			<

+			(?:mailto:)?

+			(

+				[-.\w]+

+				\@

+				[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+

+			)

+			>

+		/gi, _DoAutoLinks_callback());

+	*/

+	text = text.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi,

+		function(wholeMatch,m1) {

+			return _EncodeEmailAddress( _UnescapeSpecialChars(m1) );

+		}

+	);

+

+	return text;

+}

+

+

+var _EncodeEmailAddress = function(addr) {

+//

+//  Input: an email address, e.g. "foo@example.com"

+//

+//  Output: the email address as a mailto link, with each character

+//	of the address encoded as either a decimal or hex entity, in

+//	the hopes of foiling most address harvesting spam bots. E.g.:

+//

+//	<a href="&#x6D;&#97;&#105;&#108;&#x74;&#111;:&#102;&#111;&#111;&#64;&#101;

+//	   x&#x61;&#109;&#x70;&#108;&#x65;&#x2E;&#99;&#111;&#109;">&#102;&#111;&#111;

+//	   &#64;&#101;x&#x61;&#109;&#x70;&#108;&#x65;&#x2E;&#99;&#111;&#109;</a>

+//

+//  Based on a filter by Matthew Wickline, posted to the BBEdit-Talk

+//  mailing list: <http://tinyurl.com/yu7ue>

+//

+

+	// attacklab: why can't javascript speak hex?

+	function char2hex(ch) {

+		var hexDigits = '0123456789ABCDEF';

+		var dec = ch.charCodeAt(0);

+		return(hexDigits.charAt(dec>>4) + hexDigits.charAt(dec&15));

+	}

+

+	var encode = [

+		function(ch){return "&#"+ch.charCodeAt(0)+";";},

+		function(ch){return "&#x"+char2hex(ch)+";";},

+		function(ch){return ch;}

+	];

+

+	addr = "mailto:" + addr;

+

+	addr = addr.replace(/./g, function(ch) {

+		if (ch == "@") {

+		   	// this *must* be encoded. I insist.

+			ch = encode[Math.floor(Math.random()*2)](ch);

+		} else if (ch !=":") {

+			// leave ':' alone (to spot mailto: later)

+			var r = Math.random();

+			// roughly 10% raw, 45% hex, 45% dec

+			ch =  (

+					r > .9  ?	encode[2](ch)   :

+					r > .45 ?	encode[1](ch)   :

+								encode[0](ch)

+				);

+		}

+		return ch;

+	});

+

+	addr = "<a href=\"" + addr + "\">" + addr + "</a>";

+	addr = addr.replace(/">.+:/g,"\">"); // strip the mailto: from the visible part

+

+	return addr;

+}

+

+

+var _UnescapeSpecialChars = function(text) {

+//

+// Swap back in all the special characters we've hidden.

+//

+	text = text.replace(/~E(\d+)E/g,

+		function(wholeMatch,m1) {

+			var charCodeToReplace = parseInt(m1);

+			return String.fromCharCode(charCodeToReplace);

+		}

+	);

+	return text;

+}

+

+

+var _Outdent = function(text) {

+//

+// Remove one level of line-leading tabs or spaces

+//

+

+	// attacklab: hack around Konqueror 3.5.4 bug:

+	// "----------bug".replace(/^-/g,"") == "bug"

+

+	text = text.replace(/^(\t|[ ]{1,4})/gm,"~0"); // attacklab: g_tab_width

+

+	// attacklab: clean up hack

+	text = text.replace(/~0/g,"")

+

+	return text;

+}

+

+var _Detab = function(text) {

+// attacklab: Detab's completely rewritten for speed.

+// In perl we could fix it by anchoring the regexp with \G.

+// In javascript we're less fortunate.

+

+	// expand first n-1 tabs

+	text = text.replace(/\t(?=\t)/g,"    "); // attacklab: g_tab_width

+

+	// replace the nth with two sentinels

+	text = text.replace(/\t/g,"~A~B");

+

+	// use the sentinel to anchor our regex so it doesn't explode

+	text = text.replace(/~B(.+?)~A/g,

+		function(wholeMatch,m1,m2) {

+			var leadingText = m1;

+			var numSpaces = 4 - leadingText.length % 4;  // attacklab: g_tab_width

+

+			// there *must* be a better way to do this:

+			for (var i=0; i<numSpaces; i++) leadingText+=" ";

+

+			return leadingText;

+		}

+	);

+

+	// clean up sentinels

+	text = text.replace(/~A/g,"    ");  // attacklab: g_tab_width

+	text = text.replace(/~B/g,"");

+

+	return text;

+}

+

+

+//

+//  attacklab: Utility functions

+//

+

+

+var escapeCharacters = function(text, charsToEscape, afterBackslash) {

+	// First we have to escape the escape characters so that

+	// we can build a character class out of them

+	var regexString = "([" + charsToEscape.replace(/([\[\]\\])/g,"\\$1") + "])";

+

+	if (afterBackslash) {

+		regexString = "\\\\" + regexString;

+	}

+

+	var regex = new RegExp(regexString,"g");

+	text = text.replace(regex,escapeCharacters_callback);

+

+	return text;

+}

+

+

+var escapeCharacters_callback = function(wholeMatch,m1) {

+	var charCodeToEscape = m1.charCodeAt(0);

+	return "~E"+charCodeToEscape+"E";

+}

+

+} // end of Showdown.converter

+

+// export

+if (typeof exports != 'undefined') exports.Showdown = Showdown;
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/Makefile b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/Makefile
new file mode 100644
index 0000000..ed07610
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/Makefile
@@ -0,0 +1,73 @@
+BUILD_DIR = build
+
+PREFIX = .
+SRC_DIR = ${PREFIX}
+DIST_DIR = ${PREFIX}/dist
+
+
+JS_ENGINE ?= `which node nodejs 2> /dev/null`
+COMPILER = ${JS_ENGINE} ${BUILD_DIR}/uglify.js --unsafe
+POST_COMPILER = ${JS_ENGINE} ${BUILD_DIR}/post-compile.js
+
+SRC = ${SRC_DIR}/jsonselect.js
+DIST = ${DIST_DIR}/jsonselect.js
+DIST_MIN = ${DIST_DIR}/jsonselect.min.js
+
+all: hint project min tests
+	@@echo "Project build complete."
+
+${DIST_DIR}:
+	@@mkdir -p ${DIST_DIR}
+
+project: ${DIST}
+
+${DIST}: ${SRC} | ${DIST_DIR}
+	@@echo "Building" ${DIST}
+	@@echo ${SRC}
+	@@cat ${SRC} > ${DIST};
+
+
+min: project ${DIST_MIN}
+
+${DIST_MIN}: ${DIST}
+	@@if test ! -z ${JS_ENGINE}; then \
+		echo "Minifying Project" ${DIST_MIN}; \
+		${COMPILER} ${DIST} > ${DIST_MIN}.tmp; \
+		${POST_COMPILER} ${DIST_MIN}.tmp > ${DIST_MIN}; \
+		rm -f ${DIST_MIN}.tmp; \
+	else \
+		echo "You must have NodeJS installed in order to minify Project."; \
+	fi
+
+
+hint:
+	@@if test ! -z ${JS_ENGINE}; then \
+		echo "Hinting Project"; \
+		${JS_ENGINE} build/jshint-check.js; \
+	else \
+		echo "Nodejs is missing"; \
+	fi
+
+
+test/tests/README.md: 
+	@@cd .. && git submodule init 
+	@@cd .. && git submodule update 
+
+
+tests: test/tests/README.md
+	@@if test ! -z ${JS_ENGINE}; then \
+		echo "Testing Project"; \
+		${JS_ENGINE} test/run.js; \
+	else \
+		echo "nodejs is missing"; \
+	fi
+
+
+clean:
+	@@echo "Removing Distribution directory:" ${DIST_DIR}
+	@@rm -rf ${DIST_DIR}
+
+
+
+
+.PHONY: all project hint min tests
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/jshint-check.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/jshint-check.js
new file mode 100644
index 0000000..312c643
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/jshint-check.js
@@ -0,0 +1,41 @@
+var JSHINT = require("./lib/jshint").JSHINT,
+	print = require("sys").print,
+	src = require("fs").readFileSync("jsonselect.js", "utf8");
+
+JSHINT(src, { evil: true, forin: true, maxerr: 100 });
+
+var ok = {
+
+	// w.reason
+	"Expected an identifier and instead saw 'undefined' (a reserved word).": true,
+	"Use '===' to compare with 'null'.": true,
+	"Use '!==' to compare with 'null'.": true,
+	"Expected an assignment or function call and instead saw an expression.": true,
+	"Expected a 'break' statement before 'case'.": true,
+	"'e' is already defined.": true,
+	// w.raw
+	"Expected an identifier and instead saw \'{a}\' (a reserved word).": true
+};
+
+var e = JSHINT.errors, 
+		found = 0, 
+		w;
+
+for ( var i = 0; i < e.length; i++ ) {
+	w = e[i];
+
+	//console.log( w );
+	if ( !ok[ w.reason ] && !ok[ w.raw ] ) {
+		found++;
+
+		print( "\n " + found + ". Problem at line " + w.line + " character " + w.character + ": " + w.reason );
+		print( "\n" + w.evidence );
+	}
+}
+
+if ( found > 0 ) {
+	print( "\n\n" + found + " Error(s) found.\n" );
+
+} else {
+	print( "JSHint check passed.\n" );
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/lib/jshint.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/lib/jshint.js
new file mode 100644
index 0000000..6c34fbe
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/lib/jshint.js
@@ -0,0 +1,3854 @@
+/*

+ * JSHint, by JSHint Community.

+ *

+ * Licensed under the same slightly modified MIT license that JSLint is.

+ * It stops evil-doers everywhere.

+ *

+ * JSHint is a derivative work of JSLint:

+ *

+ *   Copyright (c) 2002 Douglas Crockford  (www.JSLint.com)

+ *

+ *   Permission is hereby granted, free of charge, to any person obtaining

+ *   a copy of this software and associated documentation files (the "Software"),

+ *   to deal in the Software without restriction, including without limitation

+ *   the rights to use, copy, modify, merge, publish, distribute, sublicense,

+ *   and/or sell copies of the Software, and to permit persons to whom

+ *   the Software is furnished to do so, subject to the following conditions:

+ *

+ *   The above copyright notice and this permission notice shall be included

+ *   in all copies or substantial portions of the Software.

+ *

+ *   The Software shall be used for Good, not Evil.

+ *

+ *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR

+ *   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,

+ *   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE

+ *   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER

+ *   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING

+ *   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER

+ *   DEALINGS IN THE SOFTWARE.

+ *

+ * JSHint was forked from 2010-12-16 edition of JSLint.

+ *

+ */

+

+/*

+ JSHINT is a global function. It takes two parameters.

+

+     var myResult = JSHINT(source, option);

+

+ The first parameter is either a string or an array of strings. If it is a

+ string, it will be split on '\n' or '\r'. If it is an array of strings, it

+ is assumed that each string represents one line. The source can be a

+ JavaScript text or a JSON text.

+

+ The second parameter is an optional object of options which control the

+ operation of JSHINT. Most of the options are booleans: They are all

+ optional and have a default value of false. One of the options, predef,

+ can be an array of names, which will be used to declare global variables,

+ or an object whose keys are used as global names, with a boolean value

+ that determines if they are assignable.

+

+ If it checks out, JSHINT returns true. Otherwise, it returns false.

+

+ If false, you can inspect JSHINT.errors to find out the problems.

+ JSHINT.errors is an array of objects containing these members:

+

+ {

+     line      : The line (relative to 0) at which the lint was found

+     character : The character (relative to 0) at which the lint was found

+     reason    : The problem

+     evidence  : The text line in which the problem occurred

+     raw       : The raw message before the details were inserted

+     a         : The first detail

+     b         : The second detail

+     c         : The third detail

+     d         : The fourth detail

+ }

+

+ If a fatal error was found, a null will be the last element of the

+ JSHINT.errors array.

+

+ You can request a Function Report, which shows all of the functions

+ and the parameters and vars that they use. This can be used to find

+ implied global variables and other problems. The report is in HTML and

+ can be inserted in an HTML <body>.

+

+     var myReport = JSHINT.report(limited);

+

+ If limited is true, then the report will be limited to only errors.

+

+ You can request a data structure which contains JSHint's results.

+

+     var myData = JSHINT.data();

+

+ It returns a structure with this form:

+

+ {

+     errors: [

+         {

+             line: NUMBER,

+             character: NUMBER,

+             reason: STRING,

+             evidence: STRING

+         }

+     ],

+     functions: [

+         name: STRING,

+         line: NUMBER,

+         last: NUMBER,

+         param: [

+             STRING

+         ],

+         closure: [

+             STRING

+         ],

+         var: [

+             STRING

+         ],

+         exception: [

+             STRING

+         ],

+         outer: [

+             STRING

+         ],

+         unused: [

+             STRING

+         ],

+         global: [

+             STRING

+         ],

+         label: [

+             STRING

+         ]

+     ],

+     globals: [

+         STRING

+     ],

+     member: {

+         STRING: NUMBER

+     },

+     unuseds: [

+         {

+             name: STRING,

+             line: NUMBER

+         }

+     ],

+     implieds: [

+         {

+             name: STRING,

+             line: NUMBER

+         }

+     ],

+     urls: [

+         STRING

+     ],

+     json: BOOLEAN

+ }

+

+ Empty arrays will not be included.

+

+*/

+

+/*jshint

+ evil: true, nomen: false, onevar: false, regexp: false, strict: true, boss: true

+*/

+

+/*members "\b", "\t", "\n", "\f", "\r", "!=", "!==", "\"", "%",

+ "(begin)", "(breakage)", "(context)", "(error)", "(global)",

+ "(identifier)", "(last)", "(line)", "(loopage)", "(name)", "(onevar)",

+ "(params)", "(scope)", "(statement)", "(verb)", "*", "+", "++", "-",

+ "--", "\/", "<", "<=", "==", "===", ">", ">=", $, $$, $A, $F, $H, $R, $break,

+ $continue, $w, Abstract, Ajax, __filename, __dirname, ActiveXObject, Array,

+ ArrayBuffer, ArrayBufferView, Autocompleter, Assets, Boolean, Builder,

+ Buffer, Browser, COM, CScript, Canvas, CustomAnimation, Class, Control,

+ Chain, Color, Cookie, Core, DataView, Date, Debug, Draggable, Draggables,

+ Droppables, Document, DomReady, DOMReady, Drag, E, Enumerator, Enumerable,

+ Element, Elements, Error, Effect, EvalError, Event, Events, FadeAnimation,

+ Field, Flash, Float32Array, Float64Array, Form, FormField, Frame, Function,

+ Fx, Group, Hash, HotKey, HTMLElement, HtmlTable, Iframe, IframeShim, Image,

+ Int16Array, Int32Array, Int8Array, Insertion, InputValidator, JSON, Keyboard,

+ Locale, LN10, LN2, LOG10E, LOG2E, MAX_VALUE, MIN_VALUE, Mask, Math, MenuItem,

+ MoveAnimation, MooTools, Native, NEGATIVE_INFINITY, Number, Object,

+ ObjectRange, Option, Options, OverText, PI, POSITIVE_INFINITY,

+ PeriodicalExecuter, Point, Position, Prototype, RangeError, Rectangle,

+ ReferenceError, RegExp, ResizeAnimation, Request, RotateAnimation, SQRT1_2,

+ SQRT2, ScrollBar, Scriptaculous, Scroller, Slick, Slider, Selector, String,

+ Style, SyntaxError, Sortable, Sortables, SortableObserver, Sound, Spinner,

+ System, Swiff, Text, TextArea, Template, Timer, Tips, Type, TypeError,

+ Toggle, Try, URI, URIError, URL, VBArray, WScript, Web, Window, XMLDOM,

+ XMLHttpRequest, XPathEvaluator, XPathException, XPathExpression,

+ XPathNamespace, XPathNSResolver, XPathResult, "\\", a, addEventListener,

+ address, alert,  apply, applicationCache, arguments, arity, asi, b, bitwise,

+ block, blur, boolOptions, boss, browser, c, call, callee, caller, cases,

+ charAt, charCodeAt, character, clearInterval, clearTimeout, close, closed,

+ closure, comment, condition, confirm, console, constructor, content, couch,

+ create, css, curly, d, data, datalist, dd, debug, decodeURI,

+ decodeURIComponent, defaultStatus, defineClass, deserialize, devel,

+ document, edition, else, emit, encodeURI, encodeURIComponent, entityify,

+ eqeqeq, eqnull, errors, es5, escape, eval, event, evidence, evil, ex,

+ exception, exec, exps, expr, exports, FileReader, first, floor, focus,

+ forin, fragment, frames, from, fromCharCode, fud, funct, function, functions,

+ g, gc, getComputedStyle, getRow, GLOBAL, global, globals, globalstrict,

+ hasOwnProperty, help, history, i, id, identifier, immed, implieds,

+ include, indent, indexOf, init, ins, instanceOf, isAlpha,

+ isApplicationRunning, isArray, isDigit, isFinite, isNaN, join, jshint,

+ JSHINT, json, jquery, jQuery, keys, label, labelled, last, laxbreak,

+ latedef, lbp, led, left, length, line, load, loadClass, localStorage,

+ location, log, loopfunc, m, match, maxerr, maxlen, member,message, meta,

+ module, moveBy, moveTo, mootools, name, navigator, new, newcap, noarg,

+ node, noempty, nomen, nonew, nud, onbeforeunload, onblur, onerror, onevar,

+ onfocus, onload, onresize, onunload, open, openDatabase, openURL, opener,

+ opera, outer, param, parent, parseFloat, parseInt, passfail, plusplus,

+ predef, print, process, prompt, prototype, prototypejs, push, quit, range,

+ raw, reach, reason, regexp, readFile, readUrl, removeEventListener, replace,

+ report, require, reserved, resizeBy, resizeTo, resolvePath, resumeUpdates,

+ respond, rhino, right, runCommand, scroll, screen, scrollBy, scrollTo,

+ scrollbar, search, seal, send, serialize, setInterval, setTimeout, shift,

+ slice, sort,spawn, split, stack, status, start, strict, sub, substr, supernew,

+ shadow, supplant, sum, sync, test, toLowerCase, toString, toUpperCase, toint32,

+ token, top, type, typeOf, Uint16Array, Uint32Array, Uint8Array, undef,

+ unused, urls, value, valueOf, var, version, WebSocket, white, window, Worker

+*/

+

+/*global exports: false */

+

+// We build the application inside a function so that we produce only a single

+// global variable. That function will be invoked immediately, and its return

+// value is the JSHINT function itself.

+

+var JSHINT = (function () {

+    "use strict";

+

+    var anonname,       // The guessed name for anonymous functions.

+

+// These are operators that should not be used with the ! operator.

+

+        bang = {

+            '<'  : true,

+            '<=' : true,

+            '==' : true,

+            '===': true,

+            '!==': true,

+            '!=' : true,

+            '>'  : true,

+            '>=' : true,

+            '+'  : true,

+            '-'  : true,

+            '*'  : true,

+            '/'  : true,

+            '%'  : true

+        },

+

+// These are the JSHint boolean options.

+

+        boolOptions = {

+            asi         : true, // if automatic semicolon insertion should be tolerated

+            bitwise     : true, // if bitwise operators should not be allowed

+            boss        : true, // if advanced usage of assignments should be allowed

+            browser     : true, // if the standard browser globals should be predefined

+            couch       : true, // if CouchDB globals should be predefined

+            curly       : true, // if curly braces around blocks should be required (even in if/for/while)

+            debug       : true, // if debugger statements should be allowed

+            devel       : true, // if logging globals should be predefined (console, alert, etc.)

+            eqeqeq      : true, // if === should be required

+            eqnull      : true, // if == null comparisons should be tolerated

+            es5         : true, // if ES5 syntax should be allowed

+            evil        : true, // if eval should be allowed

+            expr        : true, // if ExpressionStatement should be allowed as Programs

+            forin       : true, // if for in statements must filter

+            globalstrict: true, // if global "use strict"; should be allowed (also enables 'strict')

+            immed       : true, // if immediate invocations must be wrapped in parens

+            jquery      : true, // if jQuery globals should be predefined

+            latedef     : true, // if the use before definition should not be tolerated

+            laxbreak    : true, // if line breaks should not be checked

+            loopfunc    : true, // if functions should be allowed to be defined within loops

+            mootools    : true, // if MooTools globals should be predefined

+            newcap      : true, // if constructor names must be capitalized

+            noarg       : true, // if arguments.caller and arguments.callee should be disallowed

+            node        : true, // if the Node.js environment globals should be predefined

+            noempty     : true, // if empty blocks should be disallowed

+            nonew       : true, // if using `new` for side-effects should be disallowed

+            nomen       : true, // if names should be checked

+            onevar      : true, // if only one var statement per function should be allowed

+            passfail    : true, // if the scan should stop on first error

+            plusplus    : true, // if increment/decrement should not be allowed

+            prototypejs : true, // if Prototype and Scriptaculous globals shoudl be predefined

+            regexp      : true, // if the . should not be allowed in regexp literals

+            rhino       : true, // if the Rhino environment globals should be predefined

+            undef       : true, // if variables should be declared before used

+            shadow      : true, // if variable shadowing should be tolerated

+            strict      : true, // require the "use strict"; pragma

+            sub         : true, // if all forms of subscript notation are tolerated

+            supernew    : true, // if `new function () { ... };` and `new Object;` should be tolerated

+            white       : true  // if strict whitespace rules apply

+        },

+

+// browser contains a set of global names which are commonly provided by a

+// web browser environment.

+

+        browser = {

+            ArrayBuffer     : false,

+            ArrayBufferView : false,

+            addEventListener: false,

+            applicationCache: false,

+            blur            : false,

+            clearInterval   : false,

+            clearTimeout    : false,

+            close           : false,

+            closed          : false,

+            DataView        : false,

+            defaultStatus   : false,

+            document        : false,

+            event           : false,

+            FileReader      : false,

+            Float32Array    : false,

+            Float64Array    : false,

+            focus           : false,

+            frames          : false,

+            getComputedStyle: false,

+            HTMLElement     : false,

+            history         : false,

+            Int16Array      : false,

+            Int32Array      : false,

+            Int8Array       : false,

+            Image           : false,

+            length          : false,

+            localStorage    : false,

+            location        : false,

+            moveBy          : false,

+            moveTo          : false,

+            name            : false,

+            navigator       : false,

+            onbeforeunload  : true,

+            onblur          : true,

+            onerror         : true,

+            onfocus         : true,

+            onload          : true,

+            onresize        : true,

+            onunload        : true,

+            open            : false,

+            openDatabase    : false,

+            opener          : false,

+            Option          : false,

+            parent          : false,

+            print           : false,

+            removeEventListener: false,

+            resizeBy        : false,

+            resizeTo        : false,

+            screen          : false,

+            scroll          : false,

+            scrollBy        : false,

+            scrollTo        : false,

+            setInterval     : false,

+            setTimeout      : false,

+            status          : false,

+            top             : false,

+            Uint16Array     : false,

+            Uint32Array     : false,

+            Uint8Array      : false,

+            WebSocket       : false,

+            window          : false,

+            Worker          : false,

+            XMLHttpRequest  : false,

+            XPathEvaluator  : false,

+            XPathException  : false,

+            XPathExpression : false,

+            XPathNamespace  : false,

+            XPathNSResolver : false,

+            XPathResult     : false

+        },

+

+        couch = {

+            "require" : false,

+            respond   : false,

+            getRow    : false,

+            emit      : false,

+            send      : false,

+            start     : false,

+            sum       : false,

+            log       : false,

+            exports   : false,

+            module    : false

+        },

+

+        devel = {

+            alert           : false,

+            confirm         : false,

+            console         : false,

+            Debug           : false,

+            opera           : false,

+            prompt          : false

+        },

+

+        escapes = {

+            '\b': '\\b',

+            '\t': '\\t',

+            '\n': '\\n',

+            '\f': '\\f',

+            '\r': '\\r',

+            '"' : '\\"',

+            '/' : '\\/',

+            '\\': '\\\\'

+        },

+

+        funct,          // The current function

+

+        functionicity = [

+            'closure', 'exception', 'global', 'label',

+            'outer', 'unused', 'var'

+        ],

+

+        functions,      // All of the functions

+

+        global,         // The global scope

+        implied,        // Implied globals

+        inblock,

+        indent,

+        jsonmode,

+

+        jquery = {

+            '$'    : false,

+            jQuery : false

+        },

+

+        lines,

+        lookahead,

+        member,

+        membersOnly,

+

+        mootools = {

+            '$'             : false,

+            '$$'            : false,

+            Assets          : false,

+            Browser         : false,

+            Chain           : false,

+            Class           : false,

+            Color           : false,

+            Cookie          : false,

+            Core            : false,

+            Document        : false,

+            DomReady        : false,

+            DOMReady        : false,

+            Drag            : false,

+            Element         : false,

+            Elements        : false,

+            Event           : false,

+            Events          : false,

+            Fx              : false,

+            Group           : false,

+            Hash            : false,

+            HtmlTable       : false,

+            Iframe          : false,

+            IframeShim      : false,

+            InputValidator  : false,

+            instanceOf      : false,

+            Keyboard        : false,

+            Locale          : false,

+            Mask            : false,

+            MooTools        : false,

+            Native          : false,

+            Options         : false,

+            OverText        : false,

+            Request         : false,

+            Scroller        : false,

+            Slick           : false,

+            Slider          : false,

+            Sortables       : false,

+            Spinner         : false,

+            Swiff           : false,

+            Tips            : false,

+            Type            : false,

+            typeOf          : false,

+            URI             : false,

+            Window          : false

+        },

+

+        nexttoken,

+

+        node = {

+            __filename  : false,

+            __dirname   : false,

+            exports     : false,

+            Buffer      : false,

+            GLOBAL      : false,

+            global      : false,

+            module      : false,

+            process     : false,

+            require     : false

+        },

+

+        noreach,

+        option,

+        predefined,     // Global variables defined by option

+        prereg,

+        prevtoken,

+

+        prototypejs = {

+            '$'               : false,

+            '$$'              : false,

+            '$A'              : false,

+            '$F'              : false,

+            '$H'              : false,

+            '$R'              : false,

+            '$break'          : false,

+            '$continue'       : false,

+            '$w'              : false,

+            Abstract          : false,

+            Ajax              : false,

+            Class             : false,

+            Enumerable        : false,

+            Element           : false,

+            Event             : false,

+            Field             : false,

+            Form              : false,

+            Hash              : false,

+            Insertion         : false,

+            ObjectRange       : false,

+            PeriodicalExecuter: false,

+            Position          : false,

+            Prototype         : false,

+            Selector          : false,

+            Template          : false,

+            Toggle            : false,

+            Try               : false,

+            Autocompleter     : false,

+            Builder           : false,

+            Control           : false,

+            Draggable         : false,

+            Draggables        : false,

+            Droppables        : false,

+            Effect            : false,

+            Sortable          : false,

+            SortableObserver  : false,

+            Sound             : false,

+            Scriptaculous     : false

+        },

+

+        rhino = {

+            defineClass : false,

+            deserialize : false,

+            gc          : false,

+            help        : false,

+            load        : false,

+            loadClass   : false,

+            print       : false,

+            quit        : false,

+            readFile    : false,

+            readUrl     : false,

+            runCommand  : false,

+            seal        : false,

+            serialize   : false,

+            spawn       : false,

+            sync        : false,

+            toint32     : false,

+            version     : false

+        },

+

+        scope,      // The current scope

+        src,

+        stack,

+

+// standard contains the global names that are provided by the

+// ECMAScript standard.

+

+        standard = {

+            Array               : false,

+            Boolean             : false,

+            Date                : false,

+            decodeURI           : false,

+            decodeURIComponent  : false,

+            encodeURI           : false,

+            encodeURIComponent  : false,

+            Error               : false,

+            'eval'              : false,

+            EvalError           : false,

+            Function            : false,

+            hasOwnProperty      : false,

+            isFinite            : false,

+            isNaN               : false,

+            JSON                : false,

+            Math                : false,

+            Number              : false,

+            Object              : false,

+            parseInt            : false,

+            parseFloat          : false,

+            RangeError          : false,

+            ReferenceError      : false,

+            RegExp              : false,

+            String              : false,

+            SyntaxError         : false,

+            TypeError           : false,

+            URIError            : false

+        },

+

+        standard_member = {

+            E                   : true,

+            LN2                 : true,

+            LN10                : true,

+            LOG2E               : true,

+            LOG10E              : true,

+            MAX_VALUE           : true,

+            MIN_VALUE           : true,

+            NEGATIVE_INFINITY   : true,

+            PI                  : true,

+            POSITIVE_INFINITY   : true,

+            SQRT1_2             : true,

+            SQRT2               : true

+        },

+

+        strict_mode,

+        syntax = {},

+        tab,

+        token,

+        urls,

+        warnings,

+

+// Regular expressions. Some of these are stupidly long.

+

+// unsafe comment or string

+        ax = /@cc|<\/?|script|\]\s*\]|<\s*!|&lt/i,

+// unsafe characters that are silently deleted by one or more browsers

+        cx = /[\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/,

+// token

+        tx = /^\s*([(){}\[.,:;'"~\?\]#@]|==?=?|\/(\*(jshint|jslint|members?|global)?|=|\/)?|\*[\/=]?|\+(?:=|\++)?|-(?:=|-+)?|%=?|&[&=]?|\|[|=]?|>>?>?=?|<([\/=!]|\!(\[|--)?|<=?)?|\^=?|\!=?=?|[a-zA-Z_$][a-zA-Z0-9_$]*|[0-9]+([xX][0-9a-fA-F]+|\.[0-9]*)?([eE][+\-]?[0-9]+)?)/,

+// characters in strings that need escapement

+        nx = /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/,

+        nxg = /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,

+// star slash

+        lx = /\*\/|\/\*/,

+// identifier

+        ix = /^([a-zA-Z_$][a-zA-Z0-9_$]*)$/,

+// javascript url

+        jx = /^(?:javascript|jscript|ecmascript|vbscript|mocha|livescript)\s*:/i,

+// catches /* falls through */ comments

+        ft = /^\s*\/\*\s*falls\sthrough\s*\*\/\s*$/;

+

+    function F() {}     // Used by Object.create

+

+    function is_own(object, name) {

+

+// The object.hasOwnProperty method fails when the property under consideration

+// is named 'hasOwnProperty'. So we have to use this more convoluted form.

+

+        return Object.prototype.hasOwnProperty.call(object, name);

+    }

+

+// Provide critical ES5 functions to ES3.

+

+    if (typeof Array.isArray !== 'function') {

+        Array.isArray = function (o) {

+            return Object.prototype.toString.apply(o) === '[object Array]';

+        };

+    }

+

+    if (typeof Object.create !== 'function') {

+        Object.create = function (o) {

+            F.prototype = o;

+            return new F();

+        };

+    }

+

+    if (typeof Object.keys !== 'function') {

+        Object.keys = function (o) {

+            var a = [], k;

+            for (k in o) {

+                if (is_own(o, k)) {

+                    a.push(k);

+                }

+            }

+            return a;

+        };

+    }

+

+// Non standard methods

+

+    if (typeof String.prototype.entityify !== 'function') {

+        String.prototype.entityify = function () {

+            return this

+                .replace(/&/g, '&amp;')

+                .replace(/</g, '&lt;')

+                .replace(/>/g, '&gt;');

+        };

+    }

+

+    if (typeof String.prototype.isAlpha !== 'function') {

+        String.prototype.isAlpha = function () {

+            return (this >= 'a' && this <= 'z\uffff') ||

+                (this >= 'A' && this <= 'Z\uffff');

+        };

+    }

+

+    if (typeof String.prototype.isDigit !== 'function') {

+        String.prototype.isDigit = function () {

+            return (this >= '0' && this <= '9');

+        };

+    }

+

+    if (typeof String.prototype.supplant !== 'function') {

+        String.prototype.supplant = function (o) {

+            return this.replace(/\{([^{}]*)\}/g, function (a, b) {

+                var r = o[b];

+                return typeof r === 'string' || typeof r === 'number' ? r : a;

+            });

+        };

+    }

+

+    if (typeof String.prototype.name !== 'function') {

+        String.prototype.name = function () {

+

+// If the string looks like an identifier, then we can return it as is.

+// If the string contains no control characters, no quote characters, and no

+// backslash characters, then we can simply slap some quotes around it.

+// Otherwise we must also replace the offending characters with safe

+// sequences.

+

+            if (ix.test(this)) {

+                return this;

+            }

+            if (nx.test(this)) {

+                return '"' + this.replace(nxg, function (a) {

+                    var c = escapes[a];

+                    if (c) {

+                        return c;

+                    }

+                    return '\\u' + ('0000' + a.charCodeAt().toString(16)).slice(-4);

+                }) + '"';

+            }

+            return '"' + this + '"';

+        };

+    }

+

+

+    function combine(t, o) {

+        var n;

+        for (n in o) {

+            if (is_own(o, n)) {

+                t[n] = o[n];

+            }

+        }

+    }

+

+    function assume() {

+        if (option.couch)

+            combine(predefined, couch);

+

+        if (option.rhino)

+            combine(predefined, rhino);

+

+        if (option.prototypejs)

+            combine(predefined, prototypejs);

+

+        if (option.node)

+            combine(predefined, node);

+

+        if (option.devel)

+            combine(predefined, devel);

+

+        if (option.browser)

+            combine(predefined, browser);

+

+        if (option.jquery)

+            combine(predefined, jquery);

+

+        if (option.mootools)

+            combine(predefined, mootools);

+

+        if (option.globalstrict)

+            option.strict = true;

+    }

+

+

+// Produce an error warning.

+

+    function quit(m, l, ch) {

+        throw {

+            name: 'JSHintError',

+            line: l,

+            character: ch,

+            message: m + " (" + Math.floor((l / lines.length) * 100) +

+                    "% scanned)."

+        };

+    }

+

+    function warning(m, t, a, b, c, d) {

+        var ch, l, w;

+        t = t || nexttoken;

+        if (t.id === '(end)') {  // `~

+            t = token;

+        }

+        l = t.line || 0;

+        ch = t.from || 0;

+        w = {

+            id: '(error)',

+            raw: m,

+            evidence: lines[l - 1] || '',

+            line: l,

+            character: ch,

+            a: a,

+            b: b,

+            c: c,

+            d: d

+        };

+        w.reason = m.supplant(w);

+        JSHINT.errors.push(w);

+        if (option.passfail) {

+            quit('Stopping. ', l, ch);

+        }

+        warnings += 1;

+        if (warnings >= option.maxerr) {

+            quit("Too many errors.", l, ch);

+        }

+        return w;

+    }

+

+    function warningAt(m, l, ch, a, b, c, d) {

+        return warning(m, {

+            line: l,

+            from: ch

+        }, a, b, c, d);

+    }

+

+    function error(m, t, a, b, c, d) {

+        var w = warning(m, t, a, b, c, d);

+        quit("Stopping, unable to continue.", w.line, w.character);

+    }

+

+    function errorAt(m, l, ch, a, b, c, d) {

+        return error(m, {

+            line: l,

+            from: ch

+        }, a, b, c, d);

+    }

+

+

+

+// lexical analysis and token construction

+

+    var lex = (function lex() {

+        var character, from, line, s;

+

+// Private lex methods

+

+        function nextLine() {

+            var at,

+                tw; // trailing whitespace check

+

+            if (line >= lines.length)

+                return false;

+

+            character = 1;

+            s = lines[line];

+            line += 1;

+            at = s.search(/ \t/);

+

+            if (at >= 0)

+                warningAt("Mixed spaces and tabs.", line, at + 1);

+

+            s = s.replace(/\t/g, tab);

+            at = s.search(cx);

+

+            if (at >= 0)

+                warningAt("Unsafe character.", line, at);

+

+            if (option.maxlen && option.maxlen < s.length)

+                warningAt("Line too long.", line, s.length);

+

+            // Check for trailing whitespaces

+            tw = s.search(/\s+$/);

+            if (option.white && ~tw)

+                warningAt("Trailing whitespace.", line, tw);

+

+            return true;

+        }

+

+// Produce a token object.  The token inherits from a syntax symbol.

+

+        function it(type, value) {

+            var i, t;

+            if (type === '(color)' || type === '(range)') {

+                t = {type: type};

+            } else if (type === '(punctuator)' ||

+                    (type === '(identifier)' && is_own(syntax, value))) {

+                t = syntax[value] || syntax['(error)'];

+            } else {

+                t = syntax[type];

+            }

+            t = Object.create(t);

+            if (type === '(string)' || type === '(range)') {

+                if (jx.test(value)) {

+                    warningAt("Script URL.", line, from);

+                }

+            }

+            if (type === '(identifier)') {

+                t.identifier = true;

+                if (value === '__iterator__' || value === '__proto__') {

+                    errorAt("Reserved name '{a}'.",

+                        line, from, value);

+                } else if (option.nomen &&

+                        (value.charAt(0) === '_' ||

+                         value.charAt(value.length - 1) === '_')) {

+                    warningAt("Unexpected {a} in '{b}'.", line, from,

+                        "dangling '_'", value);

+                }

+            }

+            t.value = value;

+            t.line = line;

+            t.character = character;

+            t.from = from;

+            i = t.id;

+            if (i !== '(endline)') {

+                prereg = i &&

+                    (('(,=:[!&|?{};'.indexOf(i.charAt(i.length - 1)) >= 0) ||

+                    i === 'return');

+            }

+            return t;

+        }

+

+// Public lex methods

+

+        return {

+            init: function (source) {

+                if (typeof source === 'string') {

+                    lines = source

+                        .replace(/\r\n/g, '\n')

+                        .replace(/\r/g, '\n')

+                        .split('\n');

+                } else {

+                    lines = source;

+                }

+

+                // If the first line is a shebang (#!), make it a blank and move on.

+                // Shebangs are used by Node scripts.

+                if (lines[0] && lines[0].substr(0, 2) == '#!')

+                    lines[0] = '';

+

+                line = 0;

+                nextLine();

+                from = 1;

+            },

+

+            range: function (begin, end) {

+                var c, value = '';

+                from = character;

+                if (s.charAt(0) !== begin) {

+                    errorAt("Expected '{a}' and instead saw '{b}'.",

+                            line, character, begin, s.charAt(0));

+                }

+                for (;;) {

+                    s = s.slice(1);

+                    character += 1;

+                    c = s.charAt(0);

+                    switch (c) {

+                    case '':

+                        errorAt("Missing '{a}'.", line, character, c);

+                        break;

+                    case end:

+                        s = s.slice(1);

+                        character += 1;

+                        return it('(range)', value);

+                    case '\\':

+                        warningAt("Unexpected '{a}'.", line, character, c);

+                    }

+                    value += c;

+                }

+

+            },

+

+// token -- this is called by advance to get the next token.

+

+            token: function () {

+                var b, c, captures, d, depth, high, i, l, low, q, t;

+

+                function match(x) {

+                    var r = x.exec(s), r1;

+                    if (r) {

+                        l = r[0].length;

+                        r1 = r[1];

+                        c = r1.charAt(0);

+                        s = s.substr(l);

+                        from = character + l - r1.length;

+                        character += l;

+                        return r1;

+                    }

+                }

+

+                function string(x) {

+                    var c, j, r = '';

+

+                    if (jsonmode && x !== '"') {

+                        warningAt("Strings must use doublequote.",

+                                line, character);

+                    }

+

+                    function esc(n) {

+                        var i = parseInt(s.substr(j + 1, n), 16);

+                        j += n;

+                        if (i >= 32 && i <= 126 &&

+                                i !== 34 && i !== 92 && i !== 39) {

+                            warningAt("Unnecessary escapement.", line, character);

+                        }

+                        character += n;

+                        c = String.fromCharCode(i);

+                    }

+                    j = 0;

+                    for (;;) {

+                        while (j >= s.length) {

+                            j = 0;

+                            if (!nextLine()) {

+                                errorAt("Unclosed string.", line, from);

+                            }

+                        }

+                        c = s.charAt(j);

+                        if (c === x) {

+                            character += 1;

+                            s = s.substr(j + 1);

+                            return it('(string)', r, x);

+                        }

+                        if (c < ' ') {

+                            if (c === '\n' || c === '\r') {

+                                break;

+                            }

+                            warningAt("Control character in string: {a}.",

+                                    line, character + j, s.slice(0, j));

+                        } else if (c === '\\') {

+                            j += 1;

+                            character += 1;

+                            c = s.charAt(j);

+                            switch (c) {

+                            case '\\':

+                            case '"':

+                            case '/':

+                                break;

+                            case '\'':

+                                if (jsonmode) {

+                                    warningAt("Avoid \\'.", line, character);

+                                }

+                                break;

+                            case 'b':

+                                c = '\b';

+                                break;

+                            case 'f':

+                                c = '\f';

+                                break;

+                            case 'n':

+                                c = '\n';

+                                break;

+                            case 'r':

+                                c = '\r';

+                                break;

+                            case 't':

+                                c = '\t';

+                                break;

+                            case 'u':

+                                esc(4);

+                                break;

+                            case 'v':

+                                if (jsonmode) {

+                                    warningAt("Avoid \\v.", line, character);

+                                }

+                                c = '\v';

+                                break;

+                            case 'x':

+                                if (jsonmode) {

+                                    warningAt("Avoid \\x-.", line, character);

+                                }

+                                esc(2);

+                                break;

+                            default:

+                                warningAt("Bad escapement.", line, character);

+                            }

+                        }

+                        r += c;

+                        character += 1;

+                        j += 1;

+                    }

+                }

+

+                for (;;) {

+                    if (!s) {

+                        return it(nextLine() ? '(endline)' : '(end)', '');

+                    }

+                    t = match(tx);

+                    if (!t) {

+                        t = '';

+                        c = '';

+                        while (s && s < '!') {

+                            s = s.substr(1);

+                        }

+                        if (s) {

+                            errorAt("Unexpected '{a}'.", line, character, s.substr(0, 1));

+                        }

+                    } else {

+

+    //      identifier

+

+                        if (c.isAlpha() || c === '_' || c === '$') {

+                            return it('(identifier)', t);

+                        }

+

+    //      number

+

+                        if (c.isDigit()) {

+                            if (!isFinite(Number(t))) {

+                                warningAt("Bad number '{a}'.",

+                                    line, character, t);

+                            }

+                            if (s.substr(0, 1).isAlpha()) {

+                                warningAt("Missing space after '{a}'.",

+                                        line, character, t);

+                            }

+                            if (c === '0') {

+                                d = t.substr(1, 1);

+                                if (d.isDigit()) {

+                                    if (token.id !== '.') {

+                                        warningAt("Don't use extra leading zeros '{a}'.",

+                                            line, character, t);

+                                    }

+                                } else if (jsonmode && (d === 'x' || d === 'X')) {

+                                    warningAt("Avoid 0x-. '{a}'.",

+                                            line, character, t);

+                                }

+                            }

+                            if (t.substr(t.length - 1) === '.') {

+                                warningAt(

+"A trailing decimal point can be confused with a dot '{a}'.", line, character, t);

+                            }

+                            return it('(number)', t);

+                        }

+                        switch (t) {

+

+    //      string

+

+                        case '"':

+                        case "'":

+                            return string(t);

+

+    //      // comment

+

+                        case '//':

+                            if (src) {

+                                warningAt("Unexpected comment.", line, character);

+                            }

+                            s = '';

+                            token.comment = true;

+                            break;

+

+    //      /* comment

+

+                        case '/*':

+                            if (src) {

+                                warningAt("Unexpected comment.", line, character);

+                            }

+                            for (;;) {

+                                i = s.search(lx);

+                                if (i >= 0) {

+                                    break;

+                                }

+                                if (!nextLine()) {

+                                    errorAt("Unclosed comment.", line, character);

+                                }

+                            }

+                            character += i + 2;

+                            if (s.substr(i, 1) === '/') {

+                                errorAt("Nested comment.", line, character);

+                            }

+                            s = s.substr(i + 2);

+                            token.comment = true;

+                            break;

+

+    //      /*members /*jshint /*global

+

+                        case '/*members':

+                        case '/*member':

+                        case '/*jshint':

+                        case '/*jslint':

+                        case '/*global':

+                        case '*/':

+                            return {

+                                value: t,

+                                type: 'special',

+                                line: line,

+                                character: character,

+                                from: from

+                            };

+

+                        case '':

+                            break;

+    //      /

+                        case '/':

+                            if (token.id === '/=') {

+                                errorAt(

+"A regular expression literal can be confused with '/='.", line, from);

+                            }

+                            if (prereg) {

+                                depth = 0;

+                                captures = 0;

+                                l = 0;

+                                for (;;) {

+                                    b = true;

+                                    c = s.charAt(l);

+                                    l += 1;

+                                    switch (c) {

+                                    case '':

+                                        errorAt("Unclosed regular expression.",

+                                                line, from);

+                                        return;

+                                    case '/':

+                                        if (depth > 0) {

+                                            warningAt("Unescaped '{a}'.",

+                                                    line, from + l, '/');

+                                        }

+                                        c = s.substr(0, l - 1);

+                                        q = {

+                                            g: true,

+                                            i: true,

+                                            m: true

+                                        };

+                                        while (q[s.charAt(l)] === true) {

+                                            q[s.charAt(l)] = false;

+                                            l += 1;

+                                        }

+                                        character += l;

+                                        s = s.substr(l);

+                                        q = s.charAt(0);

+                                        if (q === '/' || q === '*') {

+                                            errorAt("Confusing regular expression.",

+                                                    line, from);

+                                        }

+                                        return it('(regexp)', c);

+                                    case '\\':

+                                        c = s.charAt(l);

+                                        if (c < ' ') {

+                                            warningAt(

+"Unexpected control character in regular expression.", line, from + l);

+                                        } else if (c === '<') {

+                                            warningAt(

+"Unexpected escaped character '{a}' in regular expression.", line, from + l, c);

+                                        }

+                                        l += 1;

+                                        break;

+                                    case '(':

+                                        depth += 1;

+                                        b = false;

+                                        if (s.charAt(l) === '?') {

+                                            l += 1;

+                                            switch (s.charAt(l)) {

+                                            case ':':

+                                            case '=':

+                                            case '!':

+                                                l += 1;

+                                                break;

+                                            default:

+                                                warningAt(

+"Expected '{a}' and instead saw '{b}'.", line, from + l, ':', s.charAt(l));

+                                            }

+                                        } else {

+                                            captures += 1;

+                                        }

+                                        break;

+                                    case '|':

+                                        b = false;

+                                        break;

+                                    case ')':

+                                        if (depth === 0) {

+                                            warningAt("Unescaped '{a}'.",

+                                                    line, from + l, ')');

+                                        } else {

+                                            depth -= 1;

+                                        }

+                                        break;

+                                    case ' ':

+                                        q = 1;

+                                        while (s.charAt(l) === ' ') {

+                                            l += 1;

+                                            q += 1;

+                                        }

+                                        if (q > 1) {

+                                            warningAt(

+"Spaces are hard to count. Use {{a}}.", line, from + l, q);

+                                        }

+                                        break;

+                                    case '[':

+                                        c = s.charAt(l);

+                                        if (c === '^') {

+                                            l += 1;

+                                            if (option.regexp) {

+                                                warningAt("Insecure '{a}'.",

+                                                        line, from + l, c);

+                                            } else if (s.charAt(l) === ']') {

+                                                errorAt("Unescaped '{a}'.",

+                                                    line, from + l, '^');

+                                            }

+                                        }

+                                        q = false;

+                                        if (c === ']') {

+                                            warningAt("Empty class.", line,

+                                                    from + l - 1);

+                                            q = true;

+                                        }

+klass:                                  do {

+                                            c = s.charAt(l);

+                                            l += 1;

+                                            switch (c) {

+                                            case '[':

+                                            case '^':

+                                                warningAt("Unescaped '{a}'.",

+                                                        line, from + l, c);

+                                                q = true;

+                                                break;

+                                            case '-':

+                                                if (q) {

+                                                    q = false;

+                                                } else {

+                                                    warningAt("Unescaped '{a}'.",

+                                                            line, from + l, '-');

+                                                    q = true;

+                                                }

+                                                break;

+                                            case ']':

+                                                if (!q) {

+                                                    warningAt("Unescaped '{a}'.",

+                                                            line, from + l - 1, '-');

+                                                }

+                                                break klass;

+                                            case '\\':

+                                                c = s.charAt(l);

+                                                if (c < ' ') {

+                                                    warningAt(

+"Unexpected control character in regular expression.", line, from + l);

+                                                } else if (c === '<') {

+                                                    warningAt(

+"Unexpected escaped character '{a}' in regular expression.", line, from + l, c);

+                                                }

+                                                l += 1;

+                                                q = true;

+                                                break;

+                                            case '/':

+                                                warningAt("Unescaped '{a}'.",

+                                                        line, from + l - 1, '/');

+                                                q = true;

+                                                break;

+                                            case '<':

+                                                q = true;

+                                                break;

+                                            default:

+                                                q = true;

+                                            }

+                                        } while (c);

+                                        break;

+                                    case '.':

+                                        if (option.regexp) {

+                                            warningAt("Insecure '{a}'.", line,

+                                                    from + l, c);

+                                        }

+                                        break;

+                                    case ']':

+                                    case '?':

+                                    case '{':

+                                    case '}':

+                                    case '+':

+                                    case '*':

+                                        warningAt("Unescaped '{a}'.", line,

+                                                from + l, c);

+                                    }

+                                    if (b) {

+                                        switch (s.charAt(l)) {

+                                        case '?':

+                                        case '+':

+                                        case '*':

+                                            l += 1;

+                                            if (s.charAt(l) === '?') {

+                                                l += 1;

+                                            }

+                                            break;

+                                        case '{':

+                                            l += 1;

+                                            c = s.charAt(l);

+                                            if (c < '0' || c > '9') {

+                                                warningAt(

+"Expected a number and instead saw '{a}'.", line, from + l, c);

+                                            }

+                                            l += 1;

+                                            low = +c;

+                                            for (;;) {

+                                                c = s.charAt(l);

+                                                if (c < '0' || c > '9') {

+                                                    break;

+                                                }

+                                                l += 1;

+                                                low = +c + (low * 10);

+                                            }

+                                            high = low;

+                                            if (c === ',') {

+                                                l += 1;

+                                                high = Infinity;

+                                                c = s.charAt(l);

+                                                if (c >= '0' && c <= '9') {

+                                                    l += 1;

+                                                    high = +c;

+                                                    for (;;) {

+                                                        c = s.charAt(l);

+                                                        if (c < '0' || c > '9') {

+                                                            break;

+                                                        }

+                                                        l += 1;

+                                                        high = +c + (high * 10);

+                                                    }

+                                                }

+                                            }

+                                            if (s.charAt(l) !== '}') {

+                                                warningAt(

+"Expected '{a}' and instead saw '{b}'.", line, from + l, '}', c);

+                                            } else {

+                                                l += 1;

+                                            }

+                                            if (s.charAt(l) === '?') {

+                                                l += 1;

+                                            }

+                                            if (low > high) {

+                                                warningAt(

+"'{a}' should not be greater than '{b}'.", line, from + l, low, high);

+                                            }

+                                        }

+                                    }

+                                }

+                                c = s.substr(0, l - 1);

+                                character += l;

+                                s = s.substr(l);

+                                return it('(regexp)', c);

+                            }

+                            return it('(punctuator)', t);

+

+    //      punctuator

+

+                        case '#':

+                            return it('(punctuator)', t);

+                        default:

+                            return it('(punctuator)', t);

+                        }

+                    }

+                }

+            }

+        };

+    }());

+

+

+    function addlabel(t, type) {

+

+        if (t === 'hasOwnProperty') {

+            warning("'hasOwnProperty' is a really bad name.");

+        }

+

+// Define t in the current function in the current scope.

+

+        if (is_own(funct, t) && !funct['(global)']) {

+            if (funct[t] === true) {

+                if (option.latedef)

+                    warning("'{a}' was used before it was defined.", nexttoken, t);

+            } else {

+                if (!option.shadow)

+                    warning("'{a}' is already defined.", nexttoken, t);

+            }

+        }

+

+        funct[t] = type;

+        if (funct['(global)']) {

+            global[t] = funct;

+            if (is_own(implied, t)) {

+                if (option.latedef)

+                    warning("'{a}' was used before it was defined.", nexttoken, t);

+                delete implied[t];

+            }

+        } else {

+            scope[t] = funct;

+        }

+    }

+

+

+    function doOption() {

+        var b, obj, filter, o = nexttoken.value, t, v;

+        switch (o) {

+        case '*/':

+            error("Unbegun comment.");

+            break;

+        case '/*members':

+        case '/*member':

+            o = '/*members';

+            if (!membersOnly) {

+                membersOnly = {};

+            }

+            obj = membersOnly;

+            break;

+        case '/*jshint':

+        case '/*jslint':

+            obj = option;

+            filter = boolOptions;

+            break;

+        case '/*global':

+            obj = predefined;

+            break;

+        default:

+            error("What?");

+        }

+        t = lex.token();

+loop:   for (;;) {

+            for (;;) {

+                if (t.type === 'special' && t.value === '*/') {

+                    break loop;

+                }

+                if (t.id !== '(endline)' && t.id !== ',') {

+                    break;

+                }

+                t = lex.token();

+            }

+            if (t.type !== '(string)' && t.type !== '(identifier)' &&

+                    o !== '/*members') {

+                error("Bad option.", t);

+            }

+            v = lex.token();

+            if (v.id === ':') {

+                v = lex.token();

+                if (obj === membersOnly) {

+                    error("Expected '{a}' and instead saw '{b}'.",

+                            t, '*/', ':');

+                }

+                if (t.value === 'indent' && (o === '/*jshint' || o === '/*jslint')) {

+                    b = +v.value;

+                    if (typeof b !== 'number' || !isFinite(b) || b <= 0 ||

+                            Math.floor(b) !== b) {

+                        error("Expected a small integer and instead saw '{a}'.",

+                                v, v.value);

+                    }

+                    obj.white = true;

+                    obj.indent = b;

+                } else if (t.value === 'maxerr' && (o === '/*jshint' || o === '/*jslint')) {

+                    b = +v.value;

+                    if (typeof b !== 'number' || !isFinite(b) || b <= 0 ||

+                            Math.floor(b) !== b) {

+                        error("Expected a small integer and instead saw '{a}'.",

+                                v, v.value);

+                    }

+                    obj.maxerr = b;

+                } else if (t.value === 'maxlen' && (o === '/*jshint' || o === '/*jslint')) {

+                    b = +v.value;

+                    if (typeof b !== 'number' || !isFinite(b) || b <= 0 ||

+                            Math.floor(b) !== b) {

+                        error("Expected a small integer and instead saw '{a}'.",

+                                v, v.value);

+                    }

+                    obj.maxlen = b;

+                } else if (v.value === 'true') {

+                    obj[t.value] = true;

+                } else if (v.value === 'false') {

+                    obj[t.value] = false;

+                } else {

+                    error("Bad option value.", v);

+                }

+                t = lex.token();

+            } else {

+                if (o === '/*jshint' || o === '/*jslint') {

+                    error("Missing option value.", t);

+                }

+                obj[t.value] = false;

+                t = v;

+            }

+        }

+        if (filter) {

+            assume();

+        }

+    }

+

+

+// We need a peek function. If it has an argument, it peeks that much farther

+// ahead. It is used to distinguish

+//     for ( var i in ...

+// from

+//     for ( var i = ...

+

+    function peek(p) {

+        var i = p || 0, j = 0, t;

+

+        while (j <= i) {

+            t = lookahead[j];

+            if (!t) {

+                t = lookahead[j] = lex.token();

+            }

+            j += 1;

+        }

+        return t;

+    }

+

+

+

+// Produce the next token. It looks for programming errors.

+

+    function advance(id, t) {

+        switch (token.id) {

+        case '(number)':

+            if (nexttoken.id === '.') {

+                warning("A dot following a number can be confused with a decimal point.", token);

+            }

+            break;

+        case '-':

+            if (nexttoken.id === '-' || nexttoken.id === '--') {

+                warning("Confusing minusses.");

+            }

+            break;

+        case '+':

+            if (nexttoken.id === '+' || nexttoken.id === '++') {

+                warning("Confusing plusses.");

+            }

+            break;

+        }

+        if (token.type === '(string)' || token.identifier) {

+            anonname = token.value;

+        }

+

+        if (id && nexttoken.id !== id) {

+            if (t) {

+                if (nexttoken.id === '(end)') {

+                    warning("Unmatched '{a}'.", t, t.id);

+                } else {

+                    warning("Expected '{a}' to match '{b}' from line {c} and instead saw '{d}'.",

+                            nexttoken, id, t.id, t.line, nexttoken.value);

+                }

+            } else if (nexttoken.type !== '(identifier)' ||

+                            nexttoken.value !== id) {

+                warning("Expected '{a}' and instead saw '{b}'.",

+                        nexttoken, id, nexttoken.value);

+            }

+        }

+        prevtoken = token;

+        token = nexttoken;

+        for (;;) {

+            nexttoken = lookahead.shift() || lex.token();

+            if (nexttoken.id === '(end)' || nexttoken.id === '(error)') {

+                return;

+            }

+            if (nexttoken.type === 'special') {

+                doOption();

+            } else {

+                if (nexttoken.id !== '(endline)') {

+                    break;

+                }

+            }

+        }

+    }

+

+

+// This is the heart of JSHINT, the Pratt parser. In addition to parsing, it

+// is looking for ad hoc lint patterns. We add .fud to Pratt's model, which is

+// like .nud except that it is only used on the first token of a statement.

+// Having .fud makes it much easier to define statement-oriented languages like

+// JavaScript. I retained Pratt's nomenclature.

+

+// .nud     Null denotation

+// .fud     First null denotation

+// .led     Left denotation

+//  lbp     Left binding power

+//  rbp     Right binding power

+

+// They are elements of the parsing method called Top Down Operator Precedence.

+

+    function expression(rbp, initial) {

+        var left, isArray = false;

+

+        if (nexttoken.id === '(end)')

+            error("Unexpected early end of program.", token);

+

+        advance();

+        if (initial) {

+            anonname = 'anonymous';

+            funct['(verb)'] = token.value;

+        }

+        if (initial === true && token.fud) {

+            left = token.fud();

+        } else {

+            if (token.nud) {

+                left = token.nud();

+            } else {

+                if (nexttoken.type === '(number)' && token.id === '.') {

+                    warning("A leading decimal point can be confused with a dot: '.{a}'.",

+                            token, nexttoken.value);

+                    advance();

+                    return token;

+                } else {

+                    error("Expected an identifier and instead saw '{a}'.",

+                            token, token.id);

+                }

+            }

+            while (rbp < nexttoken.lbp) {

+                isArray = token.value == 'Array';

+                advance();

+                if (isArray && token.id == '(' && nexttoken.id == ')')

+                    warning("Use the array literal notation [].", token);

+                if (token.led) {

+                    left = token.led(left);

+                } else {

+                    error("Expected an operator and instead saw '{a}'.",

+                        token, token.id);

+                }

+            }

+        }

+        return left;

+    }

+

+

+// Functions for conformance of style.

+

+    function adjacent(left, right) {

+        left = left || token;

+        right = right || nexttoken;

+        if (option.white) {

+            if (left.character !== right.from && left.line === right.line) {

+                warning("Unexpected space after '{a}'.", right, left.value);

+            }

+        }

+    }

+

+    function nobreak(left, right) {

+        left = left || token;

+        right = right || nexttoken;

+        if (option.white && (left.character !== right.from || left.line !== right.line)) {

+            warning("Unexpected space before '{a}'.", right, right.value);

+        }

+    }

+

+    function nospace(left, right) {

+        left = left || token;

+        right = right || nexttoken;

+        if (option.white && !left.comment) {

+            if (left.line === right.line) {

+                adjacent(left, right);

+            }

+        }

+    }

+

+    function nonadjacent(left, right) {

+        if (option.white) {

+            left = left || token;

+            right = right || nexttoken;

+            if (left.line === right.line && left.character === right.from) {

+                warning("Missing space after '{a}'.",

+                        nexttoken, left.value);

+            }

+        }

+    }

+

+    function nobreaknonadjacent(left, right) {

+        left = left || token;

+        right = right || nexttoken;

+        if (!option.laxbreak && left.line !== right.line) {

+            warning("Bad line breaking before '{a}'.", right, right.id);

+        } else if (option.white) {

+            left = left || token;

+            right = right || nexttoken;

+            if (left.character === right.from) {

+                warning("Missing space after '{a}'.",

+                        nexttoken, left.value);

+            }

+        }

+    }

+

+    function indentation(bias) {

+        var i;

+        if (option.white && nexttoken.id !== '(end)') {

+            i = indent + (bias || 0);

+            if (nexttoken.from !== i) {

+                warning(

+"Expected '{a}' to have an indentation at {b} instead at {c}.",

+                        nexttoken, nexttoken.value, i, nexttoken.from);

+            }

+        }

+    }

+

+    function nolinebreak(t) {

+        t = t || token;

+        if (t.line !== nexttoken.line) {

+            warning("Line breaking error '{a}'.", t, t.value);

+        }

+    }

+

+

+    function comma() {

+        if (token.line !== nexttoken.line) {

+            if (!option.laxbreak) {

+                warning("Bad line breaking before '{a}'.", token, nexttoken.id);

+            }

+        } else if (token.character !== nexttoken.from && option.white) {

+            warning("Unexpected space after '{a}'.", nexttoken, token.value);

+        }

+        advance(',');

+        nonadjacent(token, nexttoken);

+    }

+

+

+// Functional constructors for making the symbols that will be inherited by

+// tokens.

+

+    function symbol(s, p) {

+        var x = syntax[s];

+        if (!x || typeof x !== 'object') {

+            syntax[s] = x = {

+                id: s,

+                lbp: p,

+                value: s

+            };

+        }

+        return x;

+    }

+

+

+    function delim(s) {

+        return symbol(s, 0);

+    }

+

+

+    function stmt(s, f) {

+        var x = delim(s);

+        x.identifier = x.reserved = true;

+        x.fud = f;

+        return x;

+    }

+

+

+    function blockstmt(s, f) {

+        var x = stmt(s, f);

+        x.block = true;

+        return x;

+    }

+

+

+    function reserveName(x) {

+        var c = x.id.charAt(0);

+        if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {

+            x.identifier = x.reserved = true;

+        }

+        return x;

+    }

+

+

+    function prefix(s, f) {

+        var x = symbol(s, 150);

+        reserveName(x);

+        x.nud = (typeof f === 'function') ? f : function () {

+            this.right = expression(150);

+            this.arity = 'unary';

+            if (this.id === '++' || this.id === '--') {

+                if (option.plusplus) {

+                    warning("Unexpected use of '{a}'.", this, this.id);

+                } else if ((!this.right.identifier || this.right.reserved) &&

+                        this.right.id !== '.' && this.right.id !== '[') {

+                    warning("Bad operand.", this);

+                }

+            }

+            return this;

+        };

+        return x;

+    }

+

+

+    function type(s, f) {

+        var x = delim(s);

+        x.type = s;

+        x.nud = f;

+        return x;

+    }

+

+

+    function reserve(s, f) {

+        var x = type(s, f);

+        x.identifier = x.reserved = true;

+        return x;

+    }

+

+

+    function reservevar(s, v) {

+        return reserve(s, function () {

+            if (typeof v === 'function') {

+                v(this);

+            }

+            return this;

+        });

+    }

+

+

+    function infix(s, f, p, w) {

+        var x = symbol(s, p);

+        reserveName(x);

+        x.led = function (left) {

+            if (!w) {

+                nobreaknonadjacent(prevtoken, token);

+                nonadjacent(token, nexttoken);

+            }

+            if (typeof f === 'function') {

+                return f(left, this);

+            } else {

+                this.left = left;

+                this.right = expression(p);

+                return this;

+            }

+        };

+        return x;

+    }

+

+

+    function relation(s, f) {

+        var x = symbol(s, 100);

+        x.led = function (left) {

+            nobreaknonadjacent(prevtoken, token);

+            nonadjacent(token, nexttoken);

+            var right = expression(100);

+            if ((left && left.id === 'NaN') || (right && right.id === 'NaN')) {

+                warning("Use the isNaN function to compare with NaN.", this);

+            } else if (f) {

+                f.apply(this, [left, right]);

+            }

+            if (left.id === '!') {

+                warning("Confusing use of '{a}'.", left, '!');

+            }

+            if (right.id === '!') {

+                warning("Confusing use of '{a}'.", left, '!');

+            }

+            this.left = left;

+            this.right = right;

+            return this;

+        };

+        return x;

+    }

+

+

+    function isPoorRelation(node) {

+        return node &&

+              ((node.type === '(number)' && +node.value === 0) ||

+               (node.type === '(string)' && node.value === '') ||

+               (node.type === 'null' && !option.eqnull) ||

+                node.type === 'true' ||

+                node.type === 'false' ||

+                node.type === 'undefined');

+    }

+

+

+    function assignop(s, f) {

+        symbol(s, 20).exps = true;

+        return infix(s, function (left, that) {

+            var l;

+            that.left = left;

+            if (predefined[left.value] === false &&

+                    scope[left.value]['(global)'] === true) {

+                warning("Read only.", left);

+            } else if (left['function']) {

+                warning("'{a}' is a function.", left, left.value);

+            }

+            if (left) {

+                if (left.id === '.' || left.id === '[') {

+                    if (!left.left || left.left.value === 'arguments') {

+                        warning('Bad assignment.', that);

+                    }

+                    that.right = expression(19);

+                    return that;

+                } else if (left.identifier && !left.reserved) {

+                    if (funct[left.value] === 'exception') {

+                        warning("Do not assign to the exception parameter.", left);

+                    }

+                    that.right = expression(19);

+                    return that;

+                }

+                if (left === syntax['function']) {

+                    warning(

+"Expected an identifier in an assignment and instead saw a function invocation.",

+                                token);

+                }

+            }

+            error("Bad assignment.", that);

+        }, 20);

+    }

+

+

+    function bitwise(s, f, p) {

+        var x = symbol(s, p);

+        reserveName(x);

+        x.led = (typeof f === 'function') ? f : function (left) {

+            if (option.bitwise) {

+                warning("Unexpected use of '{a}'.", this, this.id);

+            }

+            this.left = left;

+            this.right = expression(p);

+            return this;

+        };

+        return x;

+    }

+

+

+    function bitwiseassignop(s) {

+        symbol(s, 20).exps = true;

+        return infix(s, function (left, that) {

+            if (option.bitwise) {

+                warning("Unexpected use of '{a}'.", that, that.id);

+            }

+            nonadjacent(prevtoken, token);

+            nonadjacent(token, nexttoken);

+            if (left) {

+                if (left.id === '.' || left.id === '[' ||

+                        (left.identifier && !left.reserved)) {

+                    expression(19);

+                    return that;

+                }

+                if (left === syntax['function']) {

+                    warning(

+"Expected an identifier in an assignment, and instead saw a function invocation.",

+                                token);

+                }

+                return that;

+            }

+            error("Bad assignment.", that);

+        }, 20);

+    }

+

+

+    function suffix(s, f) {

+        var x = symbol(s, 150);

+        x.led = function (left) {

+            if (option.plusplus) {

+                warning("Unexpected use of '{a}'.", this, this.id);

+            } else if ((!left.identifier || left.reserved) &&

+                    left.id !== '.' && left.id !== '[') {

+                warning("Bad operand.", this);

+            }

+            this.left = left;

+            return this;

+        };

+        return x;

+    }

+

+

+    // fnparam means that this identifier is being defined as a function

+    // argument (see identifier())

+    function optionalidentifier(fnparam) {

+        if (nexttoken.identifier) {

+            advance();

+            if (token.reserved && !option.es5) {

+                // `undefined` as a function param is a common pattern to protect

+                // against the case when somebody does `undefined = true` and

+                // help with minification. More info: https://gist.github.com/315916

+                if (!fnparam || token.value != 'undefined') {

+                    warning("Expected an identifier and instead saw '{a}' (a reserved word).",

+                            token, token.id);

+                }

+            }

+            return token.value;

+        }

+    }

+

+    // fnparam means that this identifier is being defined as a function

+    // argument

+    function identifier(fnparam) {

+        var i = optionalidentifier(fnparam);

+        if (i) {

+            return i;

+        }

+        if (token.id === 'function' && nexttoken.id === '(') {

+            warning("Missing name in function declaration.");

+        } else {

+            error("Expected an identifier and instead saw '{a}'.",

+                    nexttoken, nexttoken.value);

+        }

+    }

+

+

+    function reachable(s) {

+        var i = 0, t;

+        if (nexttoken.id !== ';' || noreach) {

+            return;

+        }

+        for (;;) {

+            t = peek(i);

+            if (t.reach) {

+                return;

+            }

+            if (t.id !== '(endline)') {

+                if (t.id === 'function') {

+                    warning(

+"Inner functions should be listed at the top of the outer function.", t);

+                    break;

+                }

+                warning("Unreachable '{a}' after '{b}'.", t, t.value, s);

+                break;

+            }

+            i += 1;

+        }

+    }

+

+

+    function statement(noindent) {

+        var i = indent, r, s = scope, t = nexttoken;

+

+// We don't like the empty statement.

+

+        if (t.id === ';') {

+            warning("Unnecessary semicolon.", t);

+            advance(';');

+            return;

+        }

+

+// Is this a labelled statement?

+

+        if (t.identifier && !t.reserved && peek().id === ':') {

+            advance();

+            advance(':');

+            scope = Object.create(s);

+            addlabel(t.value, 'label');

+            if (!nexttoken.labelled) {

+                warning("Label '{a}' on {b} statement.",

+                        nexttoken, t.value, nexttoken.value);

+            }

+            if (jx.test(t.value + ':')) {

+                warning("Label '{a}' looks like a javascript url.",

+                        t, t.value);

+            }

+            nexttoken.label = t.value;

+            t = nexttoken;

+        }

+

+// Parse the statement.

+

+        if (!noindent) {

+            indentation();

+        }

+        r = expression(0, true);

+

+// Look for the final semicolon.

+

+        if (!t.block) {

+            if (!option.expr && (!r || !r.exps)) {

+                warning("Expected an assignment or function call and instead saw an expression.", token);

+            } else if (option.nonew && r.id === '(' && r.left.id === 'new') {

+                warning("Do not use 'new' for side effects.");

+            }

+            if (nexttoken.id !== ';') {

+                if (!option.asi) {

+                    warningAt("Missing semicolon.", token.line, token.from + token.value.length);

+                }

+            } else {

+                adjacent(token, nexttoken);

+                advance(';');

+                nonadjacent(token, nexttoken);

+            }

+        }

+

+// Restore the indentation.

+

+        indent = i;

+        scope = s;

+        return r;

+    }

+

+

+    function use_strict() {

+        if (nexttoken.value === 'use strict') {

+            if (strict_mode) {

+                warning("Unnecessary \"use strict\".");

+            }

+            advance();

+            advance(';');

+            strict_mode = true;

+            option.newcap = true;

+            option.undef = true;

+            return true;

+        } else {

+            return false;

+        }

+    }

+

+

+    function statements(begin) {

+        var a = [], f, p;

+

+        while (!nexttoken.reach && nexttoken.id !== '(end)') {

+            if (nexttoken.id === ';') {

+                warning("Unnecessary semicolon.");

+                advance(';');

+            } else {

+                a.push(statement());

+            }

+        }

+        return a;

+    }

+

+

+    /*

+     * Parses a single block. A block is a sequence of statements wrapped in

+     * braces.

+     *

+     * ordinary - true for everything but function bodies and try blocks.

+     * stmt     - true if block can be a single statement (e.g. in if/for/while).

+     */

+    function block(ordinary, stmt) {

+        var a,

+            b = inblock,

+            old_indent = indent,

+            m = strict_mode,

+            s = scope,

+            t;

+

+        inblock = ordinary;

+        scope = Object.create(scope);

+        nonadjacent(token, nexttoken);

+        t = nexttoken;

+

+        if (nexttoken.id === '{') {

+            advance('{');

+            if (nexttoken.id !== '}' || token.line !== nexttoken.line) {

+                indent += option.indent;

+                while (!ordinary && nexttoken.from > indent) {

+                    indent += option.indent;

+                }

+                if (!ordinary && !use_strict() && !m && option.strict &&

+                        funct['(context)']['(global)']) {

+                    warning("Missing \"use strict\" statement.");

+                }

+                a = statements();

+                strict_mode = m;

+                indent -= option.indent;

+                indentation();

+            }

+            advance('}', t);

+            indent = old_indent;

+        } else if (!ordinary) {

+            error("Expected '{a}' and instead saw '{b}'.",

+                  nexttoken, '{', nexttoken.value);

+        } else {

+            if (!stmt || option.curly)

+                warning("Expected '{a}' and instead saw '{b}'.",

+                        nexttoken, '{', nexttoken.value);

+

+            noreach = true;

+            a = [statement()];

+            noreach = false;

+        }

+        funct['(verb)'] = null;

+        scope = s;

+        inblock = b;

+        if (ordinary && option.noempty && (!a || a.length === 0)) {

+            warning("Empty block.");

+        }

+        return a;

+    }

+

+

+    function countMember(m) {

+        if (membersOnly && typeof membersOnly[m] !== 'boolean') {

+            warning("Unexpected /*member '{a}'.", token, m);

+        }

+        if (typeof member[m] === 'number') {

+            member[m] += 1;

+        } else {

+            member[m] = 1;

+        }

+    }

+

+

+    function note_implied(token) {

+        var name = token.value, line = token.line, a = implied[name];

+        if (typeof a === 'function') {

+            a = false;

+        }

+        if (!a) {

+            a = [line];

+            implied[name] = a;

+        } else if (a[a.length - 1] !== line) {

+            a.push(line);

+        }

+    }

+

+// Build the syntax table by declaring the syntactic elements of the language.

+

+    type('(number)', function () {

+        return this;

+    });

+    type('(string)', function () {

+        return this;

+    });

+

+    syntax['(identifier)'] = {

+        type: '(identifier)',

+        lbp: 0,

+        identifier: true,

+        nud: function () {

+            var v = this.value,

+                s = scope[v],

+                f;

+            if (typeof s === 'function') {

+

+// Protection against accidental inheritance.

+

+                s = undefined;

+            } else if (typeof s === 'boolean') {

+                f = funct;

+                funct = functions[0];

+                addlabel(v, 'var');

+                s = funct;

+                funct = f;

+            }

+

+// The name is in scope and defined in the current function.

+

+            if (funct === s) {

+

+//      Change 'unused' to 'var', and reject labels.

+

+                switch (funct[v]) {

+                case 'unused':

+                    funct[v] = 'var';

+                    break;

+                case 'unction':

+                    funct[v] = 'function';

+                    this['function'] = true;

+                    break;

+                case 'function':

+                    this['function'] = true;

+                    break;

+                case 'label':

+                    warning("'{a}' is a statement label.", token, v);

+                    break;

+                }

+

+// The name is not defined in the function.  If we are in the global scope,

+// then we have an undefined variable.

+//

+// Operators typeof and delete do not raise runtime errors even if the base

+// object of a reference is null so no need to display warning if we're

+// inside of typeof or delete.

+

+            } else if (funct['(global)']) {

+                if (anonname != 'typeof' && anonname != 'delete' &&

+                    option.undef && typeof predefined[v] !== 'boolean') {

+                    warning("'{a}' is not defined.", token, v);

+                }

+                note_implied(token);

+

+// If the name is already defined in the current

+// function, but not as outer, then there is a scope error.

+

+            } else {

+                switch (funct[v]) {

+                case 'closure':

+                case 'function':

+                case 'var':

+                case 'unused':

+                    warning("'{a}' used out of scope.", token, v);

+                    break;

+                case 'label':

+                    warning("'{a}' is a statement label.", token, v);

+                    break;

+                case 'outer':

+                case 'global':

+                    break;

+                default:

+

+// If the name is defined in an outer function, make an outer entry, and if

+// it was unused, make it var.

+

+                    if (s === true) {

+                        funct[v] = true;

+                    } else if (s === null) {

+                        warning("'{a}' is not allowed.", token, v);

+                        note_implied(token);

+                    } else if (typeof s !== 'object') {

+

+// Operators typeof and delete do not raise runtime errors even if the base object of

+// a reference is null so no need to display warning if we're inside of typeof or delete.

+

+                        if (anonname != 'typeof' && anonname != 'delete' && option.undef) {

+                            warning("'{a}' is not defined.", token, v);

+                        } else {

+                            funct[v] = true;

+                        }

+                        note_implied(token);

+                    } else {

+                        switch (s[v]) {

+                        case 'function':

+                        case 'unction':

+                            this['function'] = true;

+                            s[v] = 'closure';

+                            funct[v] = s['(global)'] ? 'global' : 'outer';

+                            break;

+                        case 'var':

+                        case 'unused':

+                            s[v] = 'closure';

+                            funct[v] = s['(global)'] ? 'global' : 'outer';

+                            break;

+                        case 'closure':

+                        case 'parameter':

+                            funct[v] = s['(global)'] ? 'global' : 'outer';

+                            break;

+                        case 'label':

+                            warning("'{a}' is a statement label.", token, v);

+                        }

+                    }

+                }

+            }

+            return this;

+        },

+        led: function () {

+            error("Expected an operator and instead saw '{a}'.",

+                nexttoken, nexttoken.value);

+        }

+    };

+

+    type('(regexp)', function () {

+        return this;

+    });

+

+

+// ECMAScript parser

+

+    delim('(endline)');

+    delim('(begin)');

+    delim('(end)').reach = true;

+    delim('</').reach = true;

+    delim('<!');

+    delim('<!--');

+    delim('-->');

+    delim('(error)').reach = true;

+    delim('}').reach = true;

+    delim(')');

+    delim(']');

+    delim('"').reach = true;

+    delim("'").reach = true;

+    delim(';');

+    delim(':').reach = true;

+    delim(',');

+    delim('#');

+    delim('@');

+    reserve('else');

+    reserve('case').reach = true;

+    reserve('catch');

+    reserve('default').reach = true;

+    reserve('finally');

+    reservevar('arguments', function (x) {

+        if (strict_mode && funct['(global)']) {

+            warning("Strict violation.", x);

+        }

+    });

+    reservevar('eval');

+    reservevar('false');

+    reservevar('Infinity');

+    reservevar('NaN');

+    reservevar('null');

+    reservevar('this', function (x) {

+        if (strict_mode && ((funct['(statement)'] &&

+                funct['(name)'].charAt(0) > 'Z') || funct['(global)'])) {

+            warning("Strict violation.", x);

+        }

+    });

+    reservevar('true');

+    reservevar('undefined');

+    assignop('=', 'assign', 20);

+    assignop('+=', 'assignadd', 20);

+    assignop('-=', 'assignsub', 20);

+    assignop('*=', 'assignmult', 20);

+    assignop('/=', 'assigndiv', 20).nud = function () {

+        error("A regular expression literal can be confused with '/='.");

+    };

+    assignop('%=', 'assignmod', 20);

+    bitwiseassignop('&=', 'assignbitand', 20);

+    bitwiseassignop('|=', 'assignbitor', 20);

+    bitwiseassignop('^=', 'assignbitxor', 20);

+    bitwiseassignop('<<=', 'assignshiftleft', 20);

+    bitwiseassignop('>>=', 'assignshiftright', 20);

+    bitwiseassignop('>>>=', 'assignshiftrightunsigned', 20);

+    infix('?', function (left, that) {

+        that.left = left;

+        that.right = expression(10);

+        advance(':');

+        that['else'] = expression(10);

+        return that;

+    }, 30);

+

+    infix('||', 'or', 40);

+    infix('&&', 'and', 50);

+    bitwise('|', 'bitor', 70);

+    bitwise('^', 'bitxor', 80);

+    bitwise('&', 'bitand', 90);

+    relation('==', function (left, right) {

+        var eqnull = option.eqnull &&

+                (left.value == 'null' || right.value == 'null');

+

+        if (!eqnull && option.eqeqeq) {

+            warning("Expected '{a}' and instead saw '{b}'.",

+                    this, '===', '==');

+        } else if (isPoorRelation(left)) {

+            warning("Use '{a}' to compare with '{b}'.",

+                this, '===', left.value);

+        } else if (isPoorRelation(right)) {

+            warning("Use '{a}' to compare with '{b}'.",

+                this, '===', right.value);

+        }

+        return this;

+    });

+    relation('===');

+    relation('!=', function (left, right) {

+        if (option.eqeqeq) {

+            warning("Expected '{a}' and instead saw '{b}'.",

+                    this, '!==', '!=');

+        } else if (isPoorRelation(left)) {

+            warning("Use '{a}' to compare with '{b}'.",

+                    this, '!==', left.value);

+        } else if (isPoorRelation(right)) {

+            warning("Use '{a}' to compare with '{b}'.",

+                    this, '!==', right.value);

+        }

+        return this;

+    });

+    relation('!==');

+    relation('<');

+    relation('>');

+    relation('<=');

+    relation('>=');

+    bitwise('<<', 'shiftleft', 120);

+    bitwise('>>', 'shiftright', 120);

+    bitwise('>>>', 'shiftrightunsigned', 120);

+    infix('in', 'in', 120);

+    infix('instanceof', 'instanceof', 120);

+    infix('+', function (left, that) {

+        var right = expression(130);

+        if (left && right && left.id === '(string)' && right.id === '(string)') {

+            left.value += right.value;

+            left.character = right.character;

+            if (jx.test(left.value)) {

+                warning("JavaScript URL.", left);

+            }

+            return left;

+        }

+        that.left = left;

+        that.right = right;

+        return that;

+    }, 130);

+    prefix('+', 'num');

+    prefix('+++', function () {

+        warning("Confusing pluses.");

+        this.right = expression(150);

+        this.arity = 'unary';

+        return this;

+    });

+    infix('+++', function (left) {

+        warning("Confusing pluses.");

+        this.left = left;

+        this.right = expression(130);

+        return this;

+    }, 130);

+    infix('-', 'sub', 130);

+    prefix('-', 'neg');

+    prefix('---', function () {

+        warning("Confusing minuses.");

+        this.right = expression(150);

+        this.arity = 'unary';

+        return this;

+    });

+    infix('---', function (left) {

+        warning("Confusing minuses.");

+        this.left = left;

+        this.right = expression(130);

+        return this;

+    }, 130);

+    infix('*', 'mult', 140);

+    infix('/', 'div', 140);

+    infix('%', 'mod', 140);

+

+    suffix('++', 'postinc');

+    prefix('++', 'preinc');

+    syntax['++'].exps = true;

+

+    suffix('--', 'postdec');

+    prefix('--', 'predec');

+    syntax['--'].exps = true;

+    prefix('delete', function () {

+        var p = expression(0);

+        if (!p || (p.id !== '.' && p.id !== '[')) {

+            warning("Variables should not be deleted.");

+        }

+        this.first = p;

+        return this;

+    }).exps = true;

+

+    prefix('~', function () {

+        if (option.bitwise) {

+            warning("Unexpected '{a}'.", this, '~');

+        }

+        expression(150);

+        return this;

+    });

+

+    prefix('!', function () {

+        this.right = expression(150);

+        this.arity = 'unary';

+        if (bang[this.right.id] === true) {

+            warning("Confusing use of '{a}'.", this, '!');

+        }

+        return this;

+    });

+    prefix('typeof', 'typeof');

+    prefix('new', function () {

+        var c = expression(155), i;

+        if (c && c.id !== 'function') {

+            if (c.identifier) {

+                c['new'] = true;

+                switch (c.value) {

+                case 'Object':

+                    warning("Use the object literal notation {}.", token);

+                    break;

+                case 'Number':

+                case 'String':

+                case 'Boolean':

+                case 'Math':

+                case 'JSON':

+                    warning("Do not use {a} as a constructor.", token, c.value);

+                    break;

+                case 'Function':

+                    if (!option.evil) {

+                        warning("The Function constructor is eval.");

+                    }

+                    break;

+                case 'Date':

+                case 'RegExp':

+                    break;

+                default:

+                    if (c.id !== 'function') {

+                        i = c.value.substr(0, 1);

+                        if (option.newcap && (i < 'A' || i > 'Z')) {

+                            warning("A constructor name should start with an uppercase letter.", token);

+                        }

+                    }

+                }

+            } else {

+                if (c.id !== '.' && c.id !== '[' && c.id !== '(') {

+                    warning("Bad constructor.", token);

+                }

+            }

+        } else {

+            if (!option.supernew)

+                warning("Weird construction. Delete 'new'.", this);

+        }

+        adjacent(token, nexttoken);

+        if (nexttoken.id !== '(' && !option.supernew) {

+            warning("Missing '()' invoking a constructor.");

+        }

+        this.first = c;

+        return this;

+    });

+    syntax['new'].exps = true;

+

+    prefix('void').exps = true;

+

+    infix('.', function (left, that) {

+        adjacent(prevtoken, token);

+        nobreak();

+        var m = identifier();

+        if (typeof m === 'string') {

+            countMember(m);

+        }

+        that.left = left;

+        that.right = m;

+        if (option.noarg && left && left.value === 'arguments' &&

+                (m === 'callee' || m === 'caller')) {

+            warning("Avoid arguments.{a}.", left, m);

+        } else if (!option.evil && left && left.value === 'document' &&

+                (m === 'write' || m === 'writeln')) {

+            warning("document.write can be a form of eval.", left);

+        }

+        if (!option.evil && (m === 'eval' || m === 'execScript')) {

+            warning('eval is evil.');

+        }

+        return that;

+    }, 160, true);

+

+    infix('(', function (left, that) {

+        if (prevtoken.id !== '}' && prevtoken.id !== ')') {

+            nobreak(prevtoken, token);

+        }

+        nospace();

+        if (option.immed && !left.immed && left.id === 'function') {

+            warning("Wrap an immediate function invocation in parentheses " +

+                "to assist the reader in understanding that the expression " +

+                "is the result of a function, and not the function itself.");

+        }

+        var n = 0,

+            p = [];

+        if (left) {

+            if (left.type === '(identifier)') {

+                if (left.value.match(/^[A-Z]([A-Z0-9_$]*[a-z][A-Za-z0-9_$]*)?$/)) {

+                    if (left.value !== 'Number' && left.value !== 'String' &&

+                            left.value !== 'Boolean' &&

+                            left.value !== 'Date') {

+                        if (left.value === 'Math') {

+                            warning("Math is not a function.", left);

+                        } else if (option.newcap) {

+                            warning(

+"Missing 'new' prefix when invoking a constructor.", left);

+                        }

+                    }

+                }

+            }

+        }

+        if (nexttoken.id !== ')') {

+            for (;;) {

+                p[p.length] = expression(10);

+                n += 1;

+                if (nexttoken.id !== ',') {

+                    break;

+                }

+                comma();

+            }

+        }

+        advance(')');

+        nospace(prevtoken, token);

+        if (typeof left === 'object') {

+            if (left.value === 'parseInt' && n === 1) {

+                warning("Missing radix parameter.", left);

+            }

+            if (!option.evil) {

+                if (left.value === 'eval' || left.value === 'Function' ||

+                        left.value === 'execScript') {

+                    warning("eval is evil.", left);

+                } else if (p[0] && p[0].id === '(string)' &&

+                       (left.value === 'setTimeout' ||

+                        left.value === 'setInterval')) {

+                    warning(

+    "Implied eval is evil. Pass a function instead of a string.", left);

+                }

+            }

+            if (!left.identifier && left.id !== '.' && left.id !== '[' &&

+                    left.id !== '(' && left.id !== '&&' && left.id !== '||' &&

+                    left.id !== '?') {

+                warning("Bad invocation.", left);

+            }

+        }

+        that.left = left;

+        return that;

+    }, 155, true).exps = true;

+

+    prefix('(', function () {

+        nospace();

+        if (nexttoken.id === 'function') {

+            nexttoken.immed = true;

+        }

+        var v = expression(0);

+        advance(')', this);

+        nospace(prevtoken, token);

+        if (option.immed && v.id === 'function') {

+            if (nexttoken.id === '(') {

+                warning(

+"Move the invocation into the parens that contain the function.", nexttoken);

+            } else {

+                warning(

+"Do not wrap function literals in parens unless they are to be immediately invoked.",

+                        this);

+            }

+        }

+        return v;

+    });

+

+    infix('[', function (left, that) {

+        nobreak(prevtoken, token);

+        nospace();

+        var e = expression(0), s;

+        if (e && e.type === '(string)') {

+            if (!option.evil && (e.value === 'eval' || e.value === 'execScript')) {

+                warning("eval is evil.", that);

+            }

+            countMember(e.value);

+            if (!option.sub && ix.test(e.value)) {

+                s = syntax[e.value];

+                if (!s || !s.reserved) {

+                    warning("['{a}'] is better written in dot notation.",

+                            e, e.value);

+                }

+            }

+        }

+        advance(']', that);

+        nospace(prevtoken, token);

+        that.left = left;

+        that.right = e;

+        return that;

+    }, 160, true);

+

+    prefix('[', function () {

+        var b = token.line !== nexttoken.line;

+        this.first = [];

+        if (b) {

+            indent += option.indent;

+            if (nexttoken.from === indent + option.indent) {

+                indent += option.indent;

+            }

+        }

+        while (nexttoken.id !== '(end)') {

+            while (nexttoken.id === ',') {

+                warning("Extra comma.");

+                advance(',');

+            }

+            if (nexttoken.id === ']') {

+                break;

+            }

+            if (b && token.line !== nexttoken.line) {

+                indentation();

+            }

+            this.first.push(expression(10));

+            if (nexttoken.id === ',') {

+                comma();

+                if (nexttoken.id === ']' && !option.es5) {

+                    warning("Extra comma.", token);

+                    break;

+                }

+            } else {

+                break;

+            }

+        }

+        if (b) {

+            indent -= option.indent;

+            indentation();

+        }

+        advance(']', this);

+        return this;

+    }, 160);

+

+

+    function property_name() {

+        var id = optionalidentifier(true);

+        if (!id) {

+            if (nexttoken.id === '(string)') {

+                id = nexttoken.value;

+                advance();

+            } else if (nexttoken.id === '(number)') {

+                id = nexttoken.value.toString();

+                advance();

+            }

+        }

+        return id;

+    }

+

+

+    function functionparams() {

+        var i, t = nexttoken, p = [];

+        advance('(');

+        nospace();

+        if (nexttoken.id === ')') {

+            advance(')');

+            nospace(prevtoken, token);

+            return;

+        }

+        for (;;) {

+            i = identifier(true);

+            p.push(i);

+            addlabel(i, 'parameter');

+            if (nexttoken.id === ',') {

+                comma();

+            } else {

+                advance(')', t);

+                nospace(prevtoken, token);

+                return p;

+            }

+        }

+    }

+

+

+    function doFunction(i, statement) {

+        var f,

+            oldOption = option,

+            oldScope  = scope;

+

+        option = Object.create(option);

+        scope = Object.create(scope);

+

+        funct = {

+            '(name)'     : i || '"' + anonname + '"',

+            '(line)'     : nexttoken.line,

+            '(context)'  : funct,

+            '(breakage)' : 0,

+            '(loopage)'  : 0,

+            '(scope)'    : scope,

+            '(statement)': statement

+        };

+        f = funct;

+        token.funct = funct;

+        functions.push(funct);

+        if (i) {

+            addlabel(i, 'function');

+        }

+        funct['(params)'] = functionparams();

+

+        block(false);

+        scope = oldScope;

+        option = oldOption;

+        funct['(last)'] = token.line;

+        funct = funct['(context)'];

+        return f;

+    }

+

+

+    (function (x) {

+        x.nud = function () {

+            var b, f, i, j, p, seen = {}, t;

+

+            b = token.line !== nexttoken.line;

+            if (b) {

+                indent += option.indent;

+                if (nexttoken.from === indent + option.indent) {

+                    indent += option.indent;

+                }

+            }

+            for (;;) {

+                if (nexttoken.id === '}') {

+                    break;

+                }

+                if (b) {

+                    indentation();

+                }

+                if (nexttoken.value === 'get' && peek().id !== ':') {

+                    advance('get');

+                    if (!option.es5) {

+                        error("get/set are ES5 features.");

+                    }

+                    i = property_name();

+                    if (!i) {

+                        error("Missing property name.");

+                    }

+                    t = nexttoken;

+                    adjacent(token, nexttoken);

+                    f = doFunction();

+                    if (!option.loopfunc && funct['(loopage)']) {

+                        warning("Don't make functions within a loop.", t);

+                    }

+                    p = f['(params)'];

+                    if (p) {

+                        warning("Unexpected parameter '{a}' in get {b} function.", t, p[0], i);

+                    }

+                    adjacent(token, nexttoken);

+                    advance(',');

+                    indentation();

+                    advance('set');

+                    j = property_name();

+                    if (i !== j) {

+                        error("Expected {a} and instead saw {b}.", token, i, j);

+                    }

+                    t = nexttoken;

+                    adjacent(token, nexttoken);

+                    f = doFunction();

+                    p = f['(params)'];

+                    if (!p || p.length !== 1 || p[0] !== 'value') {

+                        warning("Expected (value) in set {a} function.", t, i);

+                    }

+                } else {

+                    i = property_name();

+                    if (typeof i !== 'string') {

+                        break;

+                    }

+                    advance(':');

+                    nonadjacent(token, nexttoken);

+                    expression(10);

+                }

+                if (seen[i] === true) {

+                    warning("Duplicate member '{a}'.", nexttoken, i);

+                }

+                seen[i] = true;

+                countMember(i);

+                if (nexttoken.id === ',') {

+                    comma();

+                    if (nexttoken.id === ',') {

+                        warning("Extra comma.", token);

+                    } else if (nexttoken.id === '}' && !option.es5) {

+                        warning("Extra comma.", token);

+                    }

+                } else {

+                    break;

+                }

+            }

+            if (b) {

+                indent -= option.indent;

+                indentation();

+            }

+            advance('}', this);

+            return this;

+        };

+        x.fud = function () {

+            error("Expected to see a statement and instead saw a block.", token);

+        };

+    }(delim('{')));

+

+    var varstatement = stmt('var', function (prefix) {

+        // JavaScript does not have block scope. It only has function scope. So,

+        // declaring a variable in a block can have unexpected consequences.

+        var id, name, value;

+

+        if (funct['(onevar)'] && option.onevar) {

+            warning("Too many var statements.");

+        } else if (!funct['(global)']) {

+            funct['(onevar)'] = true;

+        }

+        this.first = [];

+        for (;;) {

+            nonadjacent(token, nexttoken);

+            id = identifier();

+            if (funct['(global)'] && predefined[id] === false) {

+                warning("Redefinition of '{a}'.", token, id);

+            }

+            addlabel(id, 'unused');

+            if (prefix) {

+                break;

+            }

+            name = token;

+            this.first.push(token);

+            if (nexttoken.id === '=') {

+                nonadjacent(token, nexttoken);

+                advance('=');

+                nonadjacent(token, nexttoken);

+                if (nexttoken.id === 'undefined') {

+                    warning("It is not necessary to initialize '{a}' to 'undefined'.", token, id);

+                }

+                if (peek(0).id === '=' && nexttoken.identifier) {

+                    error("Variable {a} was not declared correctly.",

+                            nexttoken, nexttoken.value);

+                }

+                value = expression(0);

+                name.first = value;

+            }

+            if (nexttoken.id !== ',') {

+                break;

+            }

+            comma();

+        }

+        return this;

+    });

+    varstatement.exps = true;

+

+    blockstmt('function', function () {

+        if (inblock) {

+            warning(

+"Function declarations should not be placed in blocks. Use a function expression or move the statement to the top of the outer function.", token);

+

+        }

+        var i = identifier();

+        adjacent(token, nexttoken);

+        addlabel(i, 'unction');

+        doFunction(i, true);

+        if (nexttoken.id === '(' && nexttoken.line === token.line) {

+            error(

+"Function declarations are not invocable. Wrap the whole function invocation in parens.");

+        }

+        return this;

+    });

+

+    prefix('function', function () {

+        var i = optionalidentifier();

+        if (i) {

+            adjacent(token, nexttoken);

+        } else {

+            nonadjacent(token, nexttoken);

+        }

+        doFunction(i);

+        if (!option.loopfunc && funct['(loopage)']) {

+            warning("Don't make functions within a loop.");

+        }

+        return this;

+    });

+

+    blockstmt('if', function () {

+        var t = nexttoken;

+        advance('(');

+        nonadjacent(this, t);

+        nospace();

+        expression(20);

+        if (nexttoken.id === '=') {

+            if (!option.boss)

+                warning("Expected a conditional expression and instead saw an assignment.");

+            advance('=');

+            expression(20);

+        }

+        advance(')', t);

+        nospace(prevtoken, token);

+        block(true, true);

+        if (nexttoken.id === 'else') {

+            nonadjacent(token, nexttoken);

+            advance('else');

+            if (nexttoken.id === 'if' || nexttoken.id === 'switch') {

+                statement(true);

+            } else {

+                block(true, true);

+            }

+        }

+        return this;

+    });

+

+    blockstmt('try', function () {

+        var b, e, s;

+

+        block(false);

+        if (nexttoken.id === 'catch') {

+            advance('catch');

+            nonadjacent(token, nexttoken);

+            advance('(');

+            s = scope;

+            scope = Object.create(s);

+            e = nexttoken.value;

+            if (nexttoken.type !== '(identifier)') {

+                warning("Expected an identifier and instead saw '{a}'.",

+                    nexttoken, e);

+            } else {

+                addlabel(e, 'exception');

+            }

+            advance();

+            advance(')');

+            block(false);

+            b = true;

+            scope = s;

+        }

+        if (nexttoken.id === 'finally') {

+            advance('finally');

+            block(false);

+            return;

+        } else if (!b) {

+            error("Expected '{a}' and instead saw '{b}'.",

+                    nexttoken, 'catch', nexttoken.value);

+        }

+        return this;

+    });

+

+    blockstmt('while', function () {

+        var t = nexttoken;

+        funct['(breakage)'] += 1;

+        funct['(loopage)'] += 1;

+        advance('(');

+        nonadjacent(this, t);

+        nospace();

+        expression(20);

+        if (nexttoken.id === '=') {

+            if (!option.boss)

+                warning("Expected a conditional expression and instead saw an assignment.");

+            advance('=');

+            expression(20);

+        }

+        advance(')', t);

+        nospace(prevtoken, token);

+        block(true, true);

+        funct['(breakage)'] -= 1;

+        funct['(loopage)'] -= 1;

+        return this;

+    }).labelled = true;

+

+    reserve('with');

+

+    blockstmt('switch', function () {

+        var t = nexttoken,

+            g = false;

+        funct['(breakage)'] += 1;

+        advance('(');

+        nonadjacent(this, t);

+        nospace();

+        this.condition = expression(20);

+        advance(')', t);

+        nospace(prevtoken, token);

+        nonadjacent(token, nexttoken);

+        t = nexttoken;

+        advance('{');

+        nonadjacent(token, nexttoken);

+        indent += option.indent;

+        this.cases = [];

+        for (;;) {

+            switch (nexttoken.id) {

+            case 'case':

+                switch (funct['(verb)']) {

+                case 'break':

+                case 'case':

+                case 'continue':

+                case 'return':

+                case 'switch':

+                case 'throw':

+                    break;

+                default:

+                    // You can tell JSHint that you don't use break intentionally by

+                    // adding a comment /* falls through */ on a line just before

+                    // the next `case`.

+                    if (!ft.test(lines[nexttoken.line - 2])) {

+                        warning(

+                            "Expected a 'break' statement before 'case'.",

+                            token);

+                    }

+                }

+                indentation(-option.indent);

+                advance('case');

+                this.cases.push(expression(20));

+                g = true;

+                advance(':');

+                funct['(verb)'] = 'case';

+                break;

+            case 'default':

+                switch (funct['(verb)']) {

+                case 'break':

+                case 'continue':

+                case 'return':

+                case 'throw':

+                    break;

+                default:

+                    if (!ft.test(lines[nexttoken.line - 2])) {

+                        warning(

+                            "Expected a 'break' statement before 'default'.",

+                            token);

+                    }

+                }

+                indentation(-option.indent);

+                advance('default');

+                g = true;

+                advance(':');

+                break;

+            case '}':

+                indent -= option.indent;

+                indentation();

+                advance('}', t);

+                if (this.cases.length === 1 || this.condition.id === 'true' ||

+                        this.condition.id === 'false') {

+                    warning("This 'switch' should be an 'if'.", this);

+                }

+                funct['(breakage)'] -= 1;

+                funct['(verb)'] = undefined;

+                return;

+            case '(end)':

+                error("Missing '{a}'.", nexttoken, '}');

+                return;

+            default:

+                if (g) {

+                    switch (token.id) {

+                    case ',':

+                        error("Each value should have its own case label.");

+                        return;

+                    case ':':

+                        statements();

+                        break;

+                    default:

+                        error("Missing ':' on a case clause.", token);

+                    }

+                } else {

+                    error("Expected '{a}' and instead saw '{b}'.",

+                        nexttoken, 'case', nexttoken.value);

+                }

+            }

+        }

+    }).labelled = true;

+

+    stmt('debugger', function () {

+        if (!option.debug) {

+            warning("All 'debugger' statements should be removed.");

+        }

+        return this;

+    }).exps = true;

+

+    (function () {

+        var x = stmt('do', function () {

+            funct['(breakage)'] += 1;

+            funct['(loopage)'] += 1;

+            this.first = block(true);

+            advance('while');

+            var t = nexttoken;

+            nonadjacent(token, t);

+            advance('(');

+            nospace();

+            expression(20);

+            if (nexttoken.id === '=') {

+                if (!option.boss)

+                    warning("Expected a conditional expression and instead saw an assignment.");

+                advance('=');

+                expression(20);

+            }

+            advance(')', t);

+            nospace(prevtoken, token);

+            funct['(breakage)'] -= 1;

+            funct['(loopage)'] -= 1;

+            return this;

+        });

+        x.labelled = true;

+        x.exps = true;

+    }());

+

+    blockstmt('for', function () {

+        var s, t = nexttoken;

+        funct['(breakage)'] += 1;

+        funct['(loopage)'] += 1;

+        advance('(');

+        nonadjacent(this, t);

+        nospace();

+        if (peek(nexttoken.id === 'var' ? 1 : 0).id === 'in') {

+            if (nexttoken.id === 'var') {

+                advance('var');

+                varstatement.fud.call(varstatement, true);

+            } else {

+                switch (funct[nexttoken.value]) {

+                case 'unused':

+                    funct[nexttoken.value] = 'var';

+                    break;

+                case 'var':

+                    break;

+                default:

+                    warning("Bad for in variable '{a}'.",

+                            nexttoken, nexttoken.value);

+                }

+                advance();

+            }

+            advance('in');

+            expression(20);

+            advance(')', t);

+            s = block(true, true);

+            if (option.forin && (s.length > 1 || typeof s[0] !== 'object' ||

+                    s[0].value !== 'if')) {

+                warning("The body of a for in should be wrapped in an if statement to filter unwanted properties from the prototype.", this);

+            }

+            funct['(breakage)'] -= 1;

+            funct['(loopage)'] -= 1;

+            return this;

+        } else {

+            if (nexttoken.id !== ';') {

+                if (nexttoken.id === 'var') {

+                    advance('var');

+                    varstatement.fud.call(varstatement);

+                } else {

+                    for (;;) {

+                        expression(0, 'for');

+                        if (nexttoken.id !== ',') {

+                            break;

+                        }

+                        comma();

+                    }

+                }

+            }

+            nolinebreak(token);

+            advance(';');

+            if (nexttoken.id !== ';') {

+                expression(20);

+                if (nexttoken.id === '=') {

+                    if (!option.boss)

+                        warning("Expected a conditional expression and instead saw an assignment.");

+                    advance('=');

+                    expression(20);

+                }

+            }

+            nolinebreak(token);

+            advance(';');

+            if (nexttoken.id === ';') {

+                error("Expected '{a}' and instead saw '{b}'.",

+                        nexttoken, ')', ';');

+            }

+            if (nexttoken.id !== ')') {

+                for (;;) {

+                    expression(0, 'for');

+                    if (nexttoken.id !== ',') {

+                        break;

+                    }

+                    comma();

+                }

+            }

+            advance(')', t);

+            nospace(prevtoken, token);

+            block(true, true);

+            funct['(breakage)'] -= 1;

+            funct['(loopage)'] -= 1;

+            return this;

+        }

+    }).labelled = true;

+

+

+    stmt('break', function () {

+        var v = nexttoken.value;

+        if (funct['(breakage)'] === 0) {

+            warning("Unexpected '{a}'.", nexttoken, this.value);

+        }

+        nolinebreak(this);

+        if (nexttoken.id !== ';') {

+            if (token.line === nexttoken.line) {

+                if (funct[v] !== 'label') {

+                    warning("'{a}' is not a statement label.", nexttoken, v);

+                } else if (scope[v] !== funct) {

+                    warning("'{a}' is out of scope.", nexttoken, v);

+                }

+                this.first = nexttoken;

+                advance();

+            }

+        }

+        reachable('break');

+        return this;

+    }).exps = true;

+

+

+    stmt('continue', function () {

+        var v = nexttoken.value;

+        if (funct['(breakage)'] === 0) {

+            warning("Unexpected '{a}'.", nexttoken, this.value);

+        }

+        nolinebreak(this);

+        if (nexttoken.id !== ';') {

+            if (token.line === nexttoken.line) {

+                if (funct[v] !== 'label') {

+                    warning("'{a}' is not a statement label.", nexttoken, v);

+                } else if (scope[v] !== funct) {

+                    warning("'{a}' is out of scope.", nexttoken, v);

+                }

+                this.first = nexttoken;

+                advance();

+            }

+        } else if (!funct['(loopage)']) {

+            warning("Unexpected '{a}'.", nexttoken, this.value);

+        }

+        reachable('continue');

+        return this;

+    }).exps = true;

+

+

+    stmt('return', function () {

+        nolinebreak(this);

+        if (nexttoken.id === '(regexp)') {

+            warning("Wrap the /regexp/ literal in parens to disambiguate the slash operator.");

+        }

+        if (nexttoken.id !== ';' && !nexttoken.reach) {

+            nonadjacent(token, nexttoken);

+            this.first = expression(20);

+        }

+        reachable('return');

+        return this;

+    }).exps = true;

+

+

+    stmt('throw', function () {

+        nolinebreak(this);

+        nonadjacent(token, nexttoken);

+        this.first = expression(20);

+        reachable('throw');

+        return this;

+    }).exps = true;

+

+//  Superfluous reserved words

+

+    reserve('class');

+    reserve('const');

+    reserve('enum');

+    reserve('export');

+    reserve('extends');

+    reserve('import');

+    reserve('super');

+

+    reserve('let');

+    reserve('yield');

+    reserve('implements');

+    reserve('interface');

+    reserve('package');

+    reserve('private');

+    reserve('protected');

+    reserve('public');

+    reserve('static');

+

+

+// Parse JSON

+

+    function jsonValue() {

+

+        function jsonObject() {

+            var o = {}, t = nexttoken;

+            advance('{');

+            if (nexttoken.id !== '}') {

+                for (;;) {

+                    if (nexttoken.id === '(end)') {

+                        error("Missing '}' to match '{' from line {a}.",

+                                nexttoken, t.line);

+                    } else if (nexttoken.id === '}') {

+                        warning("Unexpected comma.", token);

+                        break;

+                    } else if (nexttoken.id === ',') {

+                        error("Unexpected comma.", nexttoken);

+                    } else if (nexttoken.id !== '(string)') {

+                        warning("Expected a string and instead saw {a}.",

+                                nexttoken, nexttoken.value);

+                    }

+                    if (o[nexttoken.value] === true) {

+                        warning("Duplicate key '{a}'.",

+                                nexttoken, nexttoken.value);

+                    } else if (nexttoken.value === '__proto__') {

+                        warning("Stupid key '{a}'.",

+                                nexttoken, nexttoken.value);

+                    } else {

+                        o[nexttoken.value] = true;

+                    }

+                    advance();

+                    advance(':');

+                    jsonValue();

+                    if (nexttoken.id !== ',') {

+                        break;

+                    }

+                    advance(',');

+                }

+            }

+            advance('}');

+        }

+

+        function jsonArray() {

+            var t = nexttoken;

+            advance('[');

+            if (nexttoken.id !== ']') {

+                for (;;) {

+                    if (nexttoken.id === '(end)') {

+                        error("Missing ']' to match '[' from line {a}.",

+                                nexttoken, t.line);

+                    } else if (nexttoken.id === ']') {

+                        warning("Unexpected comma.", token);

+                        break;

+                    } else if (nexttoken.id === ',') {

+                        error("Unexpected comma.", nexttoken);

+                    }

+                    jsonValue();

+                    if (nexttoken.id !== ',') {

+                        break;

+                    }

+                    advance(',');

+                }

+            }

+            advance(']');

+        }

+

+        switch (nexttoken.id) {

+        case '{':

+            jsonObject();

+            break;

+        case '[':

+            jsonArray();

+            break;

+        case 'true':

+        case 'false':

+        case 'null':

+        case '(number)':

+        case '(string)':

+            advance();

+            break;

+        case '-':

+            advance('-');

+            if (token.character !== nexttoken.from) {

+                warning("Unexpected space after '-'.", token);

+            }

+            adjacent(token, nexttoken);

+            advance('(number)');

+            break;

+        default:

+            error("Expected a JSON value.", nexttoken);

+        }

+    }

+

+

+// The actual JSHINT function itself.

+

+    var itself = function (s, o, g) {

+        var a, i, k;

+        JSHINT.errors = [];

+        predefined = Object.create(standard);

+        combine(predefined, g || {});

+        if (o) {

+            a = o.predef;

+            if (a) {

+                if (Array.isArray(a)) {

+                    for (i = 0; i < a.length; i += 1) {

+                        predefined[a[i]] = true;

+                    }

+                } else if (typeof a === 'object') {

+                    k = Object.keys(a);

+                    for (i = 0; i < k.length; i += 1) {

+                        predefined[k[i]] = !!a[k];

+                    }

+                }

+            }

+            option = o;

+        } else {

+            option = {};

+        }

+        option.indent = option.indent || 4;

+        option.maxerr = option.maxerr || 50;

+

+        tab = '';

+        for (i = 0; i < option.indent; i += 1) {

+            tab += ' ';

+        }

+        indent = 1;

+        global = Object.create(predefined);

+        scope = global;

+        funct = {

+            '(global)': true,

+            '(name)': '(global)',

+            '(scope)': scope,

+            '(breakage)': 0,

+            '(loopage)': 0

+        };

+        functions = [funct];

+        urls = [];

+        src = false;

+        stack = null;

+        member = {};

+        membersOnly = null;

+        implied = {};

+        inblock = false;

+        lookahead = [];

+        jsonmode = false;

+        warnings = 0;

+        lex.init(s);

+        prereg = true;

+        strict_mode = false;

+

+        prevtoken = token = nexttoken = syntax['(begin)'];

+        assume();

+

+        try {

+            advance();

+            switch (nexttoken.id) {

+            case '{':

+            case '[':

+                option.laxbreak = true;

+                jsonmode = true;

+                jsonValue();

+                break;

+            default:

+                if (nexttoken.value === 'use strict') {

+                    if (!option.globalstrict)

+                        warning("Use the function form of \"use strict\".");

+                    use_strict();

+                }

+                statements('lib');

+            }

+            advance('(end)');

+        } catch (e) {

+            if (e) {

+                JSHINT.errors.push({

+                    reason    : e.message,

+                    line      : e.line || nexttoken.line,

+                    character : e.character || nexttoken.from

+                }, null);

+            }

+        }

+        return JSHINT.errors.length === 0;

+    };

+

+

+// Data summary.

+

+    itself.data = function () {

+

+        var data = {functions: []}, fu, globals, implieds = [], f, i, j,

+            members = [], n, unused = [], v;

+        if (itself.errors.length) {

+            data.errors = itself.errors;

+        }

+

+        if (jsonmode) {

+            data.json = true;

+        }

+

+        for (n in implied) {

+            if (is_own(implied, n)) {

+                implieds.push({

+                    name: n,

+                    line: implied[n]

+                });

+            }

+        }

+        if (implieds.length > 0) {

+            data.implieds = implieds;

+        }

+

+        if (urls.length > 0) {

+            data.urls = urls;

+        }

+

+        globals = Object.keys(scope);

+        if (globals.length > 0) {

+            data.globals = globals;

+        }

+

+        for (i = 1; i < functions.length; i += 1) {

+            f = functions[i];

+            fu = {};

+            for (j = 0; j < functionicity.length; j += 1) {

+                fu[functionicity[j]] = [];

+            }

+            for (n in f) {

+                if (is_own(f, n) && n.charAt(0) !== '(') {

+                    v = f[n];

+                    if (v === 'unction') {

+                        v = 'unused';

+                    }

+                    if (Array.isArray(fu[v])) {

+                        fu[v].push(n);

+                        if (v === 'unused') {

+                            unused.push({

+                                name: n,

+                                line: f['(line)'],

+                                'function': f['(name)']

+                            });

+                        }

+                    }

+                }

+            }

+            for (j = 0; j < functionicity.length; j += 1) {

+                if (fu[functionicity[j]].length === 0) {

+                    delete fu[functionicity[j]];

+                }

+            }

+            fu.name = f['(name)'];

+            fu.param = f['(params)'];

+            fu.line = f['(line)'];

+            fu.last = f['(last)'];

+            data.functions.push(fu);

+        }

+

+        if (unused.length > 0) {

+            data.unused = unused;

+        }

+

+        members = [];

+        for (n in member) {

+            if (typeof member[n] === 'number') {

+                data.member = member;

+                break;

+            }

+        }

+

+        return data;

+    };

+

+    itself.report = function (option) {

+        var data = itself.data();

+

+        var a = [], c, e, err, f, i, k, l, m = '', n, o = [], s;

+

+        function detail(h, array) {

+            var b, i, singularity;

+            if (array) {

+                o.push('<div><i>' + h + '</i> ');

+                array = array.sort();

+                for (i = 0; i < array.length; i += 1) {

+                    if (array[i] !== singularity) {

+                        singularity = array[i];

+                        o.push((b ? ', ' : '') + singularity);

+                        b = true;

+                    }

+                }

+                o.push('</div>');

+            }

+        }

+

+

+        if (data.errors || data.implieds || data.unused) {

+            err = true;

+            o.push('<div id=errors><i>Error:</i>');

+            if (data.errors) {

+                for (i = 0; i < data.errors.length; i += 1) {

+                    c = data.errors[i];

+                    if (c) {

+                        e = c.evidence || '';

+                        o.push('<p>Problem' + (isFinite(c.line) ? ' at line ' +

+                                c.line + ' character ' + c.character : '') +

+                                ': ' + c.reason.entityify() +

+                                '</p><p class=evidence>' +

+                                (e && (e.length > 80 ? e.slice(0, 77) + '...' :

+                                e).entityify()) + '</p>');

+                    }

+                }

+            }

+

+            if (data.implieds) {

+                s = [];

+                for (i = 0; i < data.implieds.length; i += 1) {

+                    s[i] = '<code>' + data.implieds[i].name + '</code>&nbsp;<i>' +

+                        data.implieds[i].line + '</i>';

+                }

+                o.push('<p><i>Implied global:</i> ' + s.join(', ') + '</p>');

+            }

+

+            if (data.unused) {

+                s = [];

+                for (i = 0; i < data.unused.length; i += 1) {

+                    s[i] = '<code><u>' + data.unused[i].name + '</u></code>&nbsp;<i>' +

+                        data.unused[i].line + '</i> <code>' +

+                        data.unused[i]['function'] + '</code>';

+                }

+                o.push('<p><i>Unused variable:</i> ' + s.join(', ') + '</p>');

+            }

+            if (data.json) {

+                o.push('<p>JSON: bad.</p>');

+            }

+            o.push('</div>');

+        }

+

+        if (!option) {

+

+            o.push('<br><div id=functions>');

+

+            if (data.urls) {

+                detail("URLs<br>", data.urls, '<br>');

+            }

+

+            if (data.json && !err) {

+                o.push('<p>JSON: good.</p>');

+            } else if (data.globals) {

+                o.push('<div><i>Global</i> ' +

+                        data.globals.sort().join(', ') + '</div>');

+            } else {

+                o.push('<div><i>No new global variables introduced.</i></div>');

+            }

+

+            for (i = 0; i < data.functions.length; i += 1) {

+                f = data.functions[i];

+

+                o.push('<br><div class=function><i>' + f.line + '-' +

+                        f.last + '</i> ' + (f.name || '') + '(' +

+                        (f.param ? f.param.join(', ') : '') + ')</div>');

+                detail('<big><b>Unused</b></big>', f.unused);

+                detail('Closure', f.closure);

+                detail('Variable', f['var']);

+                detail('Exception', f.exception);

+                detail('Outer', f.outer);

+                detail('Global', f.global);

+                detail('Label', f.label);

+            }

+

+            if (data.member) {

+                a = Object.keys(data.member);

+                if (a.length) {

+                    a = a.sort();

+                    m = '<br><pre id=members>/*members ';

+                    l = 10;

+                    for (i = 0; i < a.length; i += 1) {

+                        k = a[i];

+                        n = k.name();

+                        if (l + n.length > 72) {

+                            o.push(m + '<br>');

+                            m = '    ';

+                            l = 1;

+                        }

+                        l += n.length + 2;

+                        if (data.member[k] === 1) {

+                            n = '<i>' + n + '</i>';

+                        }

+                        if (i < a.length - 1) {

+                            n += ', ';

+                        }

+                        m += n;

+                    }

+                    o.push(m + '<br>*/</pre>');

+                }

+                o.push('</div>');

+            }

+        }

+        return o.join('');

+    };

+    itself.jshint = itself;

+

+    itself.edition = '2011-04-16';

+

+    return itself;

+

+}());

+

+// Make JSHINT a Node module, if possible.

+if (typeof exports == 'object' && exports)

+    exports.JSHINT = JSHINT;

diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/lib/jslint.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/lib/jslint.js
new file mode 100644
index 0000000..f563292
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/lib/jslint.js
@@ -0,0 +1,5504 @@
+// jslint.js
+// 2010-02-20
+
+/*
+Copyright (c) 2002 Douglas Crockford  (www.JSLint.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+/*
+    JSLINT is a global function. It takes two parameters.
+
+        var myResult = JSLINT(source, option);
+
+    The first parameter is either a string or an array of strings. If it is a
+    string, it will be split on '\n' or '\r'. If it is an array of strings, it
+    is assumed that each string represents one line. The source can be a
+    JavaScript text, or HTML text, or a Konfabulator text.
+
+    The second parameter is an optional object of options which control the
+    operation of JSLINT. Most of the options are booleans: They are all are
+    optional and have a default value of false.
+
+    If it checks out, JSLINT returns true. Otherwise, it returns false.
+
+    If false, you can inspect JSLINT.errors to find out the problems.
+    JSLINT.errors is an array of objects containing these members:
+
+    {
+        line      : The line (relative to 0) at which the lint was found
+        character : The character (relative to 0) at which the lint was found
+        reason    : The problem
+        evidence  : The text line in which the problem occurred
+        raw       : The raw message before the details were inserted
+        a         : The first detail
+        b         : The second detail
+        c         : The third detail
+        d         : The fourth detail
+    }
+
+    If a fatal error was found, a null will be the last element of the
+    JSLINT.errors array.
+
+    You can request a Function Report, which shows all of the functions
+    and the parameters and vars that they use. This can be used to find
+    implied global variables and other problems. The report is in HTML and
+    can be inserted in an HTML <body>.
+
+        var myReport = JSLINT.report(limited);
+
+    If limited is true, then the report will be limited to only errors.
+
+    You can request a data structure which contains JSLint's results.
+
+        var myData = JSLINT.data();
+
+    It returns a structure with this form:
+
+    {
+        errors: [
+            {
+                line: NUMBER,
+                character: NUMBER,
+                reason: STRING,
+                evidence: STRING
+            }
+        ],
+        functions: [
+            name: STRING,
+            line: NUMBER,
+            last: NUMBER,
+            param: [
+                STRING
+            ],
+            closure: [
+                STRING
+            ],
+            var: [
+                STRING
+            ],
+            exception: [
+                STRING
+            ],
+            outer: [
+                STRING
+            ],
+            unused: [
+                STRING
+            ],
+            global: [
+                STRING
+            ],
+            label: [
+                STRING
+            ]
+        ],
+        globals: [
+            STRING
+        ],
+        member: {
+            STRING: NUMBER
+        },
+        unuseds: [
+            {
+                name: STRING,
+                line: NUMBER
+            }
+        ],
+        implieds: [
+            {
+                name: STRING,
+                line: NUMBER
+            }
+        ],
+        urls: [
+            STRING
+        ],
+        json: BOOLEAN
+    }
+
+    Empty arrays will not be included.
+
+*/
+
+/*jslint
+    evil: true, nomen: false, onevar: false, regexp: false, strict: true
+*/
+
+/*members "\b", "\t", "\n", "\f", "\r", "!=", "!==", "\"", "%",
+    "(begin)", "(breakage)", "(context)", "(error)", "(global)",
+    "(identifier)", "(last)", "(line)", "(loopage)", "(name)", "(onevar)",
+    "(params)", "(scope)", "(verb)", "*", "+", "++", "-", "--", "\/",
+    "<", "<=", "==", "===", ">", ">=", ADSAFE, Array, Boolean,
+    COM, Canvas, CustomAnimation, Date, Debug, E, Error, EvalError,
+    FadeAnimation, Flash, FormField, Frame, Function, HotKey, Image, JSON,
+    LN10, LN2, LOG10E, LOG2E, MAX_VALUE, MIN_VALUE, Math, MenuItem,
+    MoveAnimation, NEGATIVE_INFINITY, Number, Object, Option, PI,
+    POSITIVE_INFINITY, Point, RangeError, Rectangle, ReferenceError, RegExp,
+    ResizeAnimation, RotateAnimation, SQRT1_2, SQRT2, ScrollBar, String,
+    Style, SyntaxError, System, Text, TextArea, Timer, TypeError, URIError,
+    URL, Web, Window, XMLDOM, XMLHttpRequest, "\\", a, abbr, acronym,
+    addEventListener, address, adsafe, alert, aliceblue, animator,
+    antiquewhite, appleScript, applet, apply, approved, aqua, aquamarine,
+    area, arguments, arity, autocomplete, azure, b, background,
+    "background-attachment", "background-color", "background-image",
+    "background-position", "background-repeat", base, bdo, beep, beige, big,
+    bisque, bitwise, black, blanchedalmond, block, blockquote, blue,
+    blueviolet, blur, body, border, "border-bottom", "border-bottom-color",
+    "border-bottom-style", "border-bottom-width", "border-collapse",
+    "border-color", "border-left", "border-left-color", "border-left-style",
+    "border-left-width", "border-right", "border-right-color",
+    "border-right-style", "border-right-width", "border-spacing",
+    "border-style", "border-top", "border-top-color", "border-top-style",
+    "border-top-width", "border-width", bottom, br, brown, browser,
+    burlywood, button, bytesToUIString, c, cadetblue, call, callee, caller,
+    canvas, cap, caption, "caption-side", cases, center, charAt, charCodeAt,
+    character, chartreuse, chocolate, chooseColor, chooseFile, chooseFolder,
+    cite, clear, clearInterval, clearTimeout, clip, close, closeWidget,
+    closed, closure, cm, code, col, colgroup, color, comment, condition,
+    confirm, console, constructor, content, convertPathToHFS,
+    convertPathToPlatform, coral, cornflowerblue, cornsilk,
+    "counter-increment", "counter-reset", create, crimson, css, cursor,
+    cyan, d, darkblue, darkcyan, darkgoldenrod, darkgray, darkgreen,
+    darkkhaki, darkmagenta, darkolivegreen, darkorange, darkorchid, darkred,
+    darksalmon, darkseagreen, darkslateblue, darkslategray, darkturquoise,
+    darkviolet, data, dd, debug, decodeURI, decodeURIComponent, deeppink,
+    deepskyblue, defaultStatus, defineClass, del, deserialize, devel, dfn,
+    dimension, dimgray, dir, direction, display, div, dl, document,
+    dodgerblue, dt, edition, else, em, embed, empty, "empty-cells",
+    encodeURI, encodeURIComponent, entityify, eqeqeq, errors, escape, eval,
+    event, evidence, evil, ex, exception, exec, exps, fieldset, filesystem,
+    firebrick, first, float, floor, floralwhite, focus, focusWidget, font,
+    "font-face", "font-family", "font-size", "font-size-adjust",
+    "font-stretch", "font-style", "font-variant", "font-weight",
+    forestgreen, forin, form, fragment, frame, frames, frameset, from,
+    fromCharCode, fuchsia, fud, funct, function, functions, g, gainsboro,
+    gc, getComputedStyle, ghostwhite, global, globals, gold, goldenrod,
+    gray, green, greenyellow, h1, h2, h3, h4, h5, h6, hasOwnProperty, head,
+    height, help, history, honeydew, hotpink, hr, html, i, iTunes, id,
+    identifier, iframe, img, immed, implieds, in, include, indent, indexOf,
+    indianred, indigo, init, input, ins, isAlpha, isApplicationRunning,
+    isDigit, isFinite, isNaN, ivory, join, jslint, json, kbd, khaki,
+    konfabulatorVersion, label, labelled, lang, last, lavender,
+    lavenderblush, lawngreen, laxbreak, lbp, led, left, legend,
+    lemonchiffon, length, "letter-spacing", li, lib, lightblue, lightcoral,
+    lightcyan, lightgoldenrodyellow, lightgreen, lightpink, lightsalmon,
+    lightseagreen, lightskyblue, lightslategray, lightsteelblue,
+    lightyellow, lime, limegreen, line, "line-height", linen, link,
+    "list-style", "list-style-image", "list-style-position",
+    "list-style-type", load, loadClass, location, log, m, magenta, map,
+    margin, "margin-bottom", "margin-left", "margin-right", "margin-top",
+    "marker-offset", maroon, match, "max-height", "max-width", maxerr, maxlen,
+    md5, media, mediumaquamarine, mediumblue, mediumorchid, mediumpurple,
+    mediumseagreen, mediumslateblue, mediumspringgreen, mediumturquoise,
+    mediumvioletred, member, menu, message, meta, midnightblue,
+    "min-height", "min-width", mintcream, mistyrose, mm, moccasin, moveBy,
+    moveTo, name, navajowhite, navigator, navy, new, newcap, noframes,
+    nomen, noscript, nud, object, ol, oldlace, olive, olivedrab, on,
+    onbeforeunload, onblur, onerror, onevar, onfocus, onload, onresize,
+    onunload, opacity, open, openURL, opener, opera, optgroup, option,
+    orange, orangered, orchid, outer, outline, "outline-color",
+    "outline-style", "outline-width", overflow, "overflow-x", "overflow-y",
+    p, padding, "padding-bottom", "padding-left", "padding-right",
+    "padding-top", page, "page-break-after", "page-break-before",
+    palegoldenrod, palegreen, paleturquoise, palevioletred, papayawhip,
+    param, parent, parseFloat, parseInt, passfail, pc, peachpuff, peru,
+    pink, play, plum, plusplus, pop, popupMenu, position, powderblue, pre,
+    predef, preferenceGroups, preferences, print, prompt, prototype, pt,
+    purple, push, px, q, quit, quotes, random, range, raw, reach, readFile,
+    readUrl, reason, red, regexp, reloadWidget, removeEventListener,
+    replace, report, reserved, resizeBy, resizeTo, resolvePath,
+    resumeUpdates, rhino, right, rosybrown, royalblue, runCommand,
+    runCommandInBg, saddlebrown, safe, salmon, samp, sandybrown, saveAs,
+    savePreferences, screen, script, scroll, scrollBy, scrollTo, seagreen,
+    seal, search, seashell, select, serialize, setInterval, setTimeout,
+    shift, showWidgetPreferences, sidebar, sienna, silver, skyblue,
+    slateblue, slategray, sleep, slice, small, snow, sort, span, spawn,
+    speak, split, springgreen, src, status, steelblue, strict, strong,
+    style, styleproperty, sub, substr, sup, supplant, suppressUpdates, sync,
+    system, table, "table-layout", tan, tbody, td, teal, tellWidget, test,
+    "text-align", "text-decoration", "text-indent", "text-shadow",
+    "text-transform", textarea, tfoot, th, thead, thistle, title,
+    toLowerCase, toString, toUpperCase, toint32, token, tomato, top, tr, tt,
+    turquoise, type, u, ul, undef, unescape, "unicode-bidi", unused,
+    unwatch, updateNow, urls, value, valueOf, var, version,
+    "vertical-align", violet, visibility, watch, wheat, white,
+    "white-space", whitesmoke, widget, width, "word-spacing", "word-wrap",
+    yahooCheckLogin, yahooLogin, yahooLogout, yellow, yellowgreen,
+    "z-index"
+*/
+
+
+// We build the application inside a function so that we produce only a single
+// global variable. The function will be invoked, its return value is the JSLINT
+// application itself.
+
+"use strict";
+
+var JSLINT = (function () {
+    var adsafe_id,      // The widget's ADsafe id.
+        adsafe_may,     // The widget may load approved scripts.
+        adsafe_went,    // ADSAFE.go has been called.
+        anonname,       // The guessed name for anonymous functions.
+        approved,       // ADsafe approved urls.
+
+        atrule = {
+            media      : true,
+            'font-face': true,
+            page       : true
+        },
+
+// These are operators that should not be used with the ! operator.
+
+        bang = {
+            '<': true,
+            '<=': true,
+            '==': true,
+            '===': true,
+            '!==': true,
+            '!=': true,
+            '>': true,
+            '>=': true,
+            '+': true,
+            '-': true,
+            '*': true,
+            '/': true,
+            '%': true
+        },
+
+// These are members that should not be permitted in the safe subset.
+
+        banned = {              // the member names that ADsafe prohibits.
+            'arguments'     : true,
+            callee          : true,
+            caller          : true,
+            constructor     : true,
+            'eval'          : true,
+            prototype       : true,
+            unwatch         : true,
+            valueOf         : true,
+            watch           : true
+        },
+
+
+// These are the JSLint boolean options.
+
+        boolOptions = {
+            adsafe     : true, // if ADsafe should be enforced
+            bitwise    : true, // if bitwise operators should not be allowed
+            browser    : true, // if the standard browser globals should be predefined
+            cap        : true, // if upper case HTML should be allowed
+            css        : true, // if CSS workarounds should be tolerated
+            debug      : true, // if debugger statements should be allowed
+            devel      : true, // if logging should be allowed (console, alert, etc.)
+            eqeqeq     : true, // if === should be required
+            evil       : true, // if eval should be allowed
+            forin      : true, // if for in statements must filter
+            fragment   : true, // if HTML fragments should be allowed
+            immed      : true, // if immediate invocations must be wrapped in parens
+            laxbreak   : true, // if line breaks should not be checked
+            newcap     : true, // if constructor names must be capitalized
+            nomen      : true, // if names should be checked
+            on         : true, // if HTML event handlers should be allowed
+            onevar     : true, // if only one var statement per function should be allowed
+            passfail   : true, // if the scan should stop on first error
+            plusplus   : true, // if increment/decrement should not be allowed
+            regexp     : true, // if the . should not be allowed in regexp literals
+            rhino      : true, // if the Rhino environment globals should be predefined
+            undef      : true, // if variables should be declared before used
+            safe       : true, // if use of some browser features should be restricted
+            sidebar    : true, // if the System object should be predefined
+            strict     : true, // require the "use strict"; pragma
+            sub        : true, // if all forms of subscript notation are tolerated
+            white      : true, // if strict whitespace rules apply
+            widget     : true  // if the Yahoo Widgets globals should be predefined
+        },
+
+// browser contains a set of global names which are commonly provided by a
+// web browser environment.
+
+        browser = {
+            addEventListener: false,
+            blur            : false,
+            clearInterval   : false,
+            clearTimeout    : false,
+            close           : false,
+            closed          : false,
+            defaultStatus   : false,
+            document        : false,
+            event           : false,
+            focus           : false,
+            frames          : false,
+            getComputedStyle: false,
+            history         : false,
+            Image           : false,
+            length          : false,
+            location        : false,
+            moveBy          : false,
+            moveTo          : false,
+            name            : false,
+            navigator       : false,
+            onbeforeunload  : true,
+            onblur          : true,
+            onerror         : true,
+            onfocus         : true,
+            onload          : true,
+            onresize        : true,
+            onunload        : true,
+            open            : false,
+            opener          : false,
+            Option          : false,
+            parent          : false,
+            print           : false,
+            removeEventListener: false,
+            resizeBy        : false,
+            resizeTo        : false,
+            screen          : false,
+            scroll          : false,
+            scrollBy        : false,
+            scrollTo        : false,
+            setInterval     : false,
+            setTimeout      : false,
+            status          : false,
+            top             : false,
+            XMLHttpRequest  : false
+        },
+
+        cssAttributeData,
+        cssAny,
+
+        cssColorData = {
+            "aliceblue"             : true,
+            "antiquewhite"          : true,
+            "aqua"                  : true,
+            "aquamarine"            : true,
+            "azure"                 : true,
+            "beige"                 : true,
+            "bisque"                : true,
+            "black"                 : true,
+            "blanchedalmond"        : true,
+            "blue"                  : true,
+            "blueviolet"            : true,
+            "brown"                 : true,
+            "burlywood"             : true,
+            "cadetblue"             : true,
+            "chartreuse"            : true,
+            "chocolate"             : true,
+            "coral"                 : true,
+            "cornflowerblue"        : true,
+            "cornsilk"              : true,
+            "crimson"               : true,
+            "cyan"                  : true,
+            "darkblue"              : true,
+            "darkcyan"              : true,
+            "darkgoldenrod"         : true,
+            "darkgray"              : true,
+            "darkgreen"             : true,
+            "darkkhaki"             : true,
+            "darkmagenta"           : true,
+            "darkolivegreen"        : true,
+            "darkorange"            : true,
+            "darkorchid"            : true,
+            "darkred"               : true,
+            "darksalmon"            : true,
+            "darkseagreen"          : true,
+            "darkslateblue"         : true,
+            "darkslategray"         : true,
+            "darkturquoise"         : true,
+            "darkviolet"            : true,
+            "deeppink"              : true,
+            "deepskyblue"           : true,
+            "dimgray"               : true,
+            "dodgerblue"            : true,
+            "firebrick"             : true,
+            "floralwhite"           : true,
+            "forestgreen"           : true,
+            "fuchsia"               : true,
+            "gainsboro"             : true,
+            "ghostwhite"            : true,
+            "gold"                  : true,
+            "goldenrod"             : true,
+            "gray"                  : true,
+            "green"                 : true,
+            "greenyellow"           : true,
+            "honeydew"              : true,
+            "hotpink"               : true,
+            "indianred"             : true,
+            "indigo"                : true,
+            "ivory"                 : true,
+            "khaki"                 : true,
+            "lavender"              : true,
+            "lavenderblush"         : true,
+            "lawngreen"             : true,
+            "lemonchiffon"          : true,
+            "lightblue"             : true,
+            "lightcoral"            : true,
+            "lightcyan"             : true,
+            "lightgoldenrodyellow"  : true,
+            "lightgreen"            : true,
+            "lightpink"             : true,
+            "lightsalmon"           : true,
+            "lightseagreen"         : true,
+            "lightskyblue"          : true,
+            "lightslategray"        : true,
+            "lightsteelblue"        : true,
+            "lightyellow"           : true,
+            "lime"                  : true,
+            "limegreen"             : true,
+            "linen"                 : true,
+            "magenta"               : true,
+            "maroon"                : true,
+            "mediumaquamarine"      : true,
+            "mediumblue"            : true,
+            "mediumorchid"          : true,
+            "mediumpurple"          : true,
+            "mediumseagreen"        : true,
+            "mediumslateblue"       : true,
+            "mediumspringgreen"     : true,
+            "mediumturquoise"       : true,
+            "mediumvioletred"       : true,
+            "midnightblue"          : true,
+            "mintcream"             : true,
+            "mistyrose"             : true,
+            "moccasin"              : true,
+            "navajowhite"           : true,
+            "navy"                  : true,
+            "oldlace"               : true,
+            "olive"                 : true,
+            "olivedrab"             : true,
+            "orange"                : true,
+            "orangered"             : true,
+            "orchid"                : true,
+            "palegoldenrod"         : true,
+            "palegreen"             : true,
+            "paleturquoise"         : true,
+            "palevioletred"         : true,
+            "papayawhip"            : true,
+            "peachpuff"             : true,
+            "peru"                  : true,
+            "pink"                  : true,
+            "plum"                  : true,
+            "powderblue"            : true,
+            "purple"                : true,
+            "red"                   : true,
+            "rosybrown"             : true,
+            "royalblue"             : true,
+            "saddlebrown"           : true,
+            "salmon"                : true,
+            "sandybrown"            : true,
+            "seagreen"              : true,
+            "seashell"              : true,
+            "sienna"                : true,
+            "silver"                : true,
+            "skyblue"               : true,
+            "slateblue"             : true,
+            "slategray"             : true,
+            "snow"                  : true,
+            "springgreen"           : true,
+            "steelblue"             : true,
+            "tan"                   : true,
+            "teal"                  : true,
+            "thistle"               : true,
+            "tomato"                : true,
+            "turquoise"             : true,
+            "violet"                : true,
+            "wheat"                 : true,
+            "white"                 : true,
+            "whitesmoke"            : true,
+            "yellow"                : true,
+            "yellowgreen"           : true
+        },
+
+        cssBorderStyle,
+        cssBreak,
+
+        cssLengthData = {
+            '%': true,
+            'cm': true,
+            'em': true,
+            'ex': true,
+            'in': true,
+            'mm': true,
+            'pc': true,
+            'pt': true,
+            'px': true
+        },
+
+        cssOverflow,
+
+        devel = {
+            alert           : false,
+            confirm         : false,
+            console         : false,
+            Debug           : false,
+            opera           : false,
+            prompt          : false
+        },
+
+        escapes = {
+            '\b': '\\b',
+            '\t': '\\t',
+            '\n': '\\n',
+            '\f': '\\f',
+            '\r': '\\r',
+            '"' : '\\"',
+            '/' : '\\/',
+            '\\': '\\\\'
+        },
+
+        funct,          // The current function
+
+        functionicity = [
+            'closure', 'exception', 'global', 'label',
+            'outer', 'unused', 'var'
+        ],
+
+        functions,      // All of the functions
+
+        global,         // The global scope
+        htmltag = {
+            a:        {},
+            abbr:     {},
+            acronym:  {},
+            address:  {},
+            applet:   {},
+            area:     {empty: true, parent: ' map '},
+            b:        {},
+            base:     {empty: true, parent: ' head '},
+            bdo:      {},
+            big:      {},
+            blockquote: {},
+            body:     {parent: ' html noframes '},
+            br:       {empty: true},
+            button:   {},
+            canvas:   {parent: ' body p div th td '},
+            caption:  {parent: ' table '},
+            center:   {},
+            cite:     {},
+            code:     {},
+            col:      {empty: true, parent: ' table colgroup '},
+            colgroup: {parent: ' table '},
+            dd:       {parent: ' dl '},
+            del:      {},
+            dfn:      {},
+            dir:      {},
+            div:      {},
+            dl:       {},
+            dt:       {parent: ' dl '},
+            em:       {},
+            embed:    {},
+            fieldset: {},
+            font:     {},
+            form:     {},
+            frame:    {empty: true, parent: ' frameset '},
+            frameset: {parent: ' html frameset '},
+            h1:       {},
+            h2:       {},
+            h3:       {},
+            h4:       {},
+            h5:       {},
+            h6:       {},
+            head:     {parent: ' html '},
+            html:     {parent: '*'},
+            hr:       {empty: true},
+            i:        {},
+            iframe:   {},
+            img:      {empty: true},
+            input:    {empty: true},
+            ins:      {},
+            kbd:      {},
+            label:    {},
+            legend:   {parent: ' fieldset '},
+            li:       {parent: ' dir menu ol ul '},
+            link:     {empty: true, parent: ' head '},
+            map:      {},
+            menu:     {},
+            meta:     {empty: true, parent: ' head noframes noscript '},
+            noframes: {parent: ' html body '},
+            noscript: {parent: ' body head noframes '},
+            object:   {},
+            ol:       {},
+            optgroup: {parent: ' select '},
+            option:   {parent: ' optgroup select '},
+            p:        {},
+            param:    {empty: true, parent: ' applet object '},
+            pre:      {},
+            q:        {},
+            samp:     {},
+            script:   {empty: true, parent: ' body div frame head iframe p pre span '},
+            select:   {},
+            small:    {},
+            span:     {},
+            strong:   {},
+            style:    {parent: ' head ', empty: true},
+            sub:      {},
+            sup:      {},
+            table:    {},
+            tbody:    {parent: ' table '},
+            td:       {parent: ' tr '},
+            textarea: {},
+            tfoot:    {parent: ' table '},
+            th:       {parent: ' tr '},
+            thead:    {parent: ' table '},
+            title:    {parent: ' head '},
+            tr:       {parent: ' table tbody thead tfoot '},
+            tt:       {},
+            u:        {},
+            ul:       {},
+            'var':    {}
+        },
+
+        ids,            // HTML ids
+        implied,        // Implied globals
+        inblock,
+        indent,
+        jsonmode,
+        lines,
+        lookahead,
+        member,
+        membersOnly,
+        nexttoken,
+        noreach,
+        option,
+        predefined,     // Global variables defined by option
+        prereg,
+        prevtoken,
+
+        rhino = {
+            defineClass : false,
+            deserialize : false,
+            gc          : false,
+            help        : false,
+            load        : false,
+            loadClass   : false,
+            print       : false,
+            quit        : false,
+            readFile    : false,
+            readUrl     : false,
+            runCommand  : false,
+            seal        : false,
+            serialize   : false,
+            spawn       : false,
+            sync        : false,
+            toint32     : false,
+            version     : false
+        },
+
+        scope,      // The current scope
+
+        sidebar = {
+            System      : false
+        },
+
+        src,
+        stack,
+
+// standard contains the global names that are provided by the
+// ECMAScript standard.
+
+        standard = {
+            Array               : false,
+            Boolean             : false,
+            Date                : false,
+            decodeURI           : false,
+            decodeURIComponent  : false,
+            encodeURI           : false,
+            encodeURIComponent  : false,
+            Error               : false,
+            'eval'              : false,
+            EvalError           : false,
+            Function            : false,
+            hasOwnProperty      : false,
+            isFinite            : false,
+            isNaN               : false,
+            JSON                : false,
+            Math                : false,
+            Number              : false,
+            Object              : false,
+            parseInt            : false,
+            parseFloat          : false,
+            RangeError          : false,
+            ReferenceError      : false,
+            RegExp              : false,
+            String              : false,
+            SyntaxError         : false,
+            TypeError           : false,
+            URIError            : false
+        },
+
+        standard_member = {
+            E                   : true,
+            LN2                 : true,
+            LN10                : true,
+            LOG2E               : true,
+            LOG10E              : true,
+            PI                  : true,
+            SQRT1_2             : true,
+            SQRT2               : true,
+            MAX_VALUE           : true,
+            MIN_VALUE           : true,
+            NEGATIVE_INFINITY   : true,
+            POSITIVE_INFINITY   : true
+        },
+
+        strict_mode,
+        syntax = {},
+        tab,
+        token,
+        urls,
+        warnings,
+
+// widget contains the global names which are provided to a Yahoo
+// (fna Konfabulator) widget.
+
+        widget = {
+            alert                   : true,
+            animator                : true,
+            appleScript             : true,
+            beep                    : true,
+            bytesToUIString         : true,
+            Canvas                  : true,
+            chooseColor             : true,
+            chooseFile              : true,
+            chooseFolder            : true,
+            closeWidget             : true,
+            COM                     : true,
+            convertPathToHFS        : true,
+            convertPathToPlatform   : true,
+            CustomAnimation         : true,
+            escape                  : true,
+            FadeAnimation           : true,
+            filesystem              : true,
+            Flash                   : true,
+            focusWidget             : true,
+            form                    : true,
+            FormField               : true,
+            Frame                   : true,
+            HotKey                  : true,
+            Image                   : true,
+            include                 : true,
+            isApplicationRunning    : true,
+            iTunes                  : true,
+            konfabulatorVersion     : true,
+            log                     : true,
+            md5                     : true,
+            MenuItem                : true,
+            MoveAnimation           : true,
+            openURL                 : true,
+            play                    : true,
+            Point                   : true,
+            popupMenu               : true,
+            preferenceGroups        : true,
+            preferences             : true,
+            print                   : true,
+            prompt                  : true,
+            random                  : true,
+            Rectangle               : true,
+            reloadWidget            : true,
+            ResizeAnimation         : true,
+            resolvePath             : true,
+            resumeUpdates           : true,
+            RotateAnimation         : true,
+            runCommand              : true,
+            runCommandInBg          : true,
+            saveAs                  : true,
+            savePreferences         : true,
+            screen                  : true,
+            ScrollBar               : true,
+            showWidgetPreferences   : true,
+            sleep                   : true,
+            speak                   : true,
+            Style                   : true,
+            suppressUpdates         : true,
+            system                  : true,
+            tellWidget              : true,
+            Text                    : true,
+            TextArea                : true,
+            Timer                   : true,
+            unescape                : true,
+            updateNow               : true,
+            URL                     : true,
+            Web                     : true,
+            widget                  : true,
+            Window                  : true,
+            XMLDOM                  : true,
+            XMLHttpRequest          : true,
+            yahooCheckLogin         : true,
+            yahooLogin              : true,
+            yahooLogout             : true
+        },
+
+//  xmode is used to adapt to the exceptions in html parsing.
+//  It can have these states:
+//      false   .js script file
+//      html
+//      outer
+//      script
+//      style
+//      scriptstring
+//      styleproperty
+
+        xmode,
+        xquote,
+
+// unsafe comment or string
+        ax = /@cc|<\/?|script|\]*s\]|<\s*!|&lt/i,
+// unsafe characters that are silently deleted by one or more browsers
+        cx = /[\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/,
+// token
+        tx = /^\s*([(){}\[.,:;'"~\?\]#@]|==?=?|\/(\*(jslint|members?|global)?|=|\/)?|\*[\/=]?|\+[+=]?|-[\-=]?|%=?|&[&=]?|\|[|=]?|>>?>?=?|<([\/=!]|\!(\[|--)?|<=?)?|\^=?|\!=?=?|[a-zA-Z_$][a-zA-Z0-9_$]*|[0-9]+([xX][0-9a-fA-F]+|\.[0-9]*)?([eE][+\-]?[0-9]+)?)/,
+// html token
+////////        hx = /^\s*(['"=>\/&#]|<(?:\/|\!(?:--)?)?|[a-zA-Z][a-zA-Z0-9_\-]*|[0-9]+|--|.)/,
+        hx = /^\s*(['"=>\/&#]|<(?:\/|\!(?:--)?)?|[a-zA-Z][a-zA-Z0-9_\-]*|[0-9]+|--)/,
+// characters in strings that need escapement
+        nx = /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/,
+        nxg = /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+// outer html token
+        ox = /[>&]|<[\/!]?|--/,
+// star slash
+        lx = /\*\/|\/\*/,
+// identifier
+        ix = /^([a-zA-Z_$][a-zA-Z0-9_$]*)$/,
+// javascript url
+        jx = /^(?:javascript|jscript|ecmascript|vbscript|mocha|livescript)\s*:/i,
+// url badness
+        ux = /&|\+|\u00AD|\.\.|\/\*|%[^;]|base64|url|expression|data|mailto/i,
+// style
+        sx = /^\s*([{:#%.=,>+\[\]@()"';]|\*=?|\$=|\|=|\^=|~=|[a-zA-Z_][a-zA-Z0-9_\-]*|[0-9]+|<\/|\/\*)/,
+        ssx = /^\s*([@#!"'};:\-%.=,+\[\]()*_]|[a-zA-Z][a-zA-Z0-9._\-]*|\/\*?|\d+(?:\.\d+)?|<\/)/,
+// attributes characters
+        qx = /[^a-zA-Z0-9-_\/ ]/,
+// query characters for ids
+        dx = /[\[\]\/\\"'*<>.&:(){}+=#]/,
+
+        rx = {
+            outer: hx,
+            html: hx,
+            style: sx,
+            styleproperty: ssx
+        };
+
+    function F() {}
+
+    if (typeof Object.create !== 'function') {
+        Object.create = function (o) {
+            F.prototype = o;
+            return new F();
+        };
+    }
+
+
+    function is_own(object, name) {
+        return Object.prototype.hasOwnProperty.call(object, name);
+    }
+
+
+    function combine(t, o) {
+        var n;
+        for (n in o) {
+            if (is_own(o, n)) {
+                t[n] = o[n];
+            }
+        }
+    }
+
+    String.prototype.entityify = function () {
+        return this.
+            replace(/&/g, '&amp;').
+            replace(/</g, '&lt;').
+            replace(/>/g, '&gt;');
+    };
+
+    String.prototype.isAlpha = function () {
+        return (this >= 'a' && this <= 'z\uffff') ||
+            (this >= 'A' && this <= 'Z\uffff');
+    };
+
+
+    String.prototype.isDigit = function () {
+        return (this >= '0' && this <= '9');
+    };
+
+
+    String.prototype.supplant = function (o) {
+        return this.replace(/\{([^{}]*)\}/g, function (a, b) {
+            var r = o[b];
+            return typeof r === 'string' || typeof r === 'number' ? r : a;
+        });
+    };
+
+    String.prototype.name = function () {
+
+// If the string looks like an identifier, then we can return it as is.
+// If the string contains no control characters, no quote characters, and no
+// backslash characters, then we can simply slap some quotes around it.
+// Otherwise we must also replace the offending characters with safe
+// sequences.
+
+        if (ix.test(this)) {
+            return this;
+        }
+        if (nx.test(this)) {
+            return '"' + this.replace(nxg, function (a) {
+                var c = escapes[a];
+                if (c) {
+                    return c;
+                }
+                return '\\u' + ('0000' + a.charCodeAt().toString(16)).slice(-4);
+            }) + '"';
+        }
+        return '"' + this + '"';
+    };
+
+
+    function assume() {
+        if (!option.safe) {
+            if (option.rhino) {
+                combine(predefined, rhino);
+            }
+            if (option.devel) {
+                combine(predefined, devel);
+            }
+            if (option.browser || option.sidebar) {
+                combine(predefined, browser);
+            }
+            if (option.sidebar) {
+                combine(predefined, sidebar);
+            }
+            if (option.widget) {
+                combine(predefined, widget);
+            }
+        }
+    }
+
+
+// Produce an error warning.
+
+    function quit(m, l, ch) {
+        throw {
+            name: 'JSLintError',
+            line: l,
+            character: ch,
+            message: m + " (" + Math.floor((l / lines.length) * 100) +
+                    "% scanned)."
+        };
+    }
+
+    function warning(m, t, a, b, c, d) {
+        var ch, l, w;
+        t = t || nexttoken;
+        if (t.id === '(end)') {  // `~
+            t = token;
+        }
+        l = t.line || 0;
+        ch = t.from || 0;
+        w = {
+            id: '(error)',
+            raw: m,
+            evidence: lines[l - 1] || '',
+            line: l,
+            character: ch,
+            a: a,
+            b: b,
+            c: c,
+            d: d
+        };
+        w.reason = m.supplant(w);
+        JSLINT.errors.push(w);
+        if (option.passfail) {
+            quit('Stopping. ', l, ch);
+        }
+        warnings += 1;
+        if (warnings >= option.maxerr) {
+            quit("Too many errors.", l, ch);
+        }
+        return w;
+    }
+
+    function warningAt(m, l, ch, a, b, c, d) {
+        return warning(m, {
+            line: l,
+            from: ch
+        }, a, b, c, d);
+    }
+
+    function error(m, t, a, b, c, d) {
+        var w = warning(m, t, a, b, c, d);
+        quit("Stopping, unable to continue.", w.line, w.character);
+    }
+
+    function errorAt(m, l, ch, a, b, c, d) {
+        return error(m, {
+            line: l,
+            from: ch
+        }, a, b, c, d);
+    }
+
+
+
+// lexical analysis
+
+    var lex = (function lex() {
+        var character, from, line, s;
+
+// Private lex methods
+
+        function nextLine() {
+            var at;
+            if (line >= lines.length) {
+                return false;
+            }
+            character = 1;
+            s = lines[line];
+            line += 1;
+            at = s.search(/ \t/);
+            if (at >= 0) {
+                warningAt("Mixed spaces and tabs.", line, at + 1);
+            }
+            s = s.replace(/\t/g, tab);
+            at = s.search(cx);
+            if (at >= 0) {
+                warningAt("Unsafe character.", line, at);
+            }
+            if (option.maxlen && option.maxlen < s.length) {
+                warningAt("Line too long.", line, s.length);
+            }
+            return true;
+        }
+
+// Produce a token object.  The token inherits from a syntax symbol.
+
+        function it(type, value) {
+            var i, t;
+            if (type === '(color)') {
+                t = {type: type};
+            } else if (type === '(punctuator)' ||
+                    (type === '(identifier)' && is_own(syntax, value))) {
+                t = syntax[value] || syntax['(error)'];
+            } else {
+                t = syntax[type];
+            }
+            t = Object.create(t);
+            if (type === '(string)' || type === '(range)') {
+                if (jx.test(value)) {
+                    warningAt("Script URL.", line, from);
+                }
+            }
+            if (type === '(identifier)') {
+                t.identifier = true;
+                if (value === '__iterator__' || value === '__proto__') {
+                    errorAt("Reserved name '{a}'.",
+                        line, from, value);
+                } else if (option.nomen &&
+                        (value.charAt(0) === '_' ||
+                         value.charAt(value.length - 1) === '_')) {
+                    warningAt("Unexpected {a} in '{b}'.", line, from,
+                        "dangling '_'", value);
+                }
+            }
+            t.value = value;
+            t.line = line;
+            t.character = character;
+            t.from = from;
+            i = t.id;
+            if (i !== '(endline)') {
+                prereg = i &&
+                    (('(,=:[!&|?{};'.indexOf(i.charAt(i.length - 1)) >= 0) ||
+                    i === 'return');
+            }
+            return t;
+        }
+
+// Public lex methods
+
+        return {
+            init: function (source) {
+                if (typeof source === 'string') {
+                    lines = source.
+                        replace(/\r\n/g, '\n').
+                        replace(/\r/g, '\n').
+                        split('\n');
+                } else {
+                    lines = source;
+                }
+                line = 0;
+                nextLine();
+                from = 1;
+            },
+
+            range: function (begin, end) {
+                var c, value = '';
+                from = character;
+                if (s.charAt(0) !== begin) {
+                    errorAt("Expected '{a}' and instead saw '{b}'.",
+                            line, character, begin, s.charAt(0));
+                }
+                for (;;) {
+                    s = s.slice(1);
+                    character += 1;
+                    c = s.charAt(0);
+                    switch (c) {
+                    case '':
+                        errorAt("Missing '{a}'.", line, character, c);
+                        break;
+                    case end:
+                        s = s.slice(1);
+                        character += 1;
+                        return it('(range)', value);
+                    case xquote:
+                    case '\\':
+                        warningAt("Unexpected '{a}'.", line, character, c);
+                    }
+                    value += c;
+                }
+
+            },
+
+// token -- this is called by advance to get the next token.
+
+            token: function () {
+                var b, c, captures, d, depth, high, i, l, low, q, t;
+
+                function match(x) {
+                    var r = x.exec(s), r1;
+                    if (r) {
+                        l = r[0].length;
+                        r1 = r[1];
+                        c = r1.charAt(0);
+                        s = s.substr(l);
+                        from = character + l - r1.length;
+                        character += l;
+                        return r1;
+                    }
+                }
+
+                function string(x) {
+                    var c, j, r = '';
+
+                    if (jsonmode && x !== '"') {
+                        warningAt("Strings must use doublequote.",
+                                line, character);
+                    }
+
+                    if (xquote === x || (xmode === 'scriptstring' && !xquote)) {
+                        return it('(punctuator)', x);
+                    }
+
+                    function esc(n) {
+                        var i = parseInt(s.substr(j + 1, n), 16);
+                        j += n;
+                        if (i >= 32 && i <= 126 &&
+                                i !== 34 && i !== 92 && i !== 39) {
+                            warningAt("Unnecessary escapement.", line, character);
+                        }
+                        character += n;
+                        c = String.fromCharCode(i);
+                    }
+                    j = 0;
+                    for (;;) {
+                        while (j >= s.length) {
+                            j = 0;
+                            if (xmode !== 'html' || !nextLine()) {
+                                errorAt("Unclosed string.", line, from);
+                            }
+                        }
+                        c = s.charAt(j);
+                        if (c === x) {
+                            character += 1;
+                            s = s.substr(j + 1);
+                            return it('(string)', r, x);
+                        }
+                        if (c < ' ') {
+                            if (c === '\n' || c === '\r') {
+                                break;
+                            }
+                            warningAt("Control character in string: {a}.",
+                                    line, character + j, s.slice(0, j));
+                        } else if (c === xquote) {
+                            warningAt("Bad HTML string", line, character + j);
+                        } else if (c === '<') {
+                            if (option.safe && xmode === 'html') {
+                                warningAt("ADsafe string violation.",
+                                        line, character + j);
+                            } else if (s.charAt(j + 1) === '/' && (xmode || option.safe)) {
+                                warningAt("Expected '<\\/' and instead saw '</'.", line, character);
+                            } else if (s.charAt(j + 1) === '!' && (xmode || option.safe)) {
+                                warningAt("Unexpected '<!' in a string.", line, character);
+                            }
+                        } else if (c === '\\') {
+                            if (xmode === 'html') {
+                                if (option.safe) {
+                                    warningAt("ADsafe string violation.",
+                                            line, character + j);
+                                }
+                            } else if (xmode === 'styleproperty') {
+                                j += 1;
+                                character += 1;
+                                c = s.charAt(j);
+                                if (c !== x) {
+                                    warningAt("Escapement in style string.",
+                                            line, character + j);
+                                }
+                            } else {
+                                j += 1;
+                                character += 1;
+                                c = s.charAt(j);
+                                switch (c) {
+                                case xquote:
+                                    warningAt("Bad HTML string", line,
+                                        character + j);
+                                    break;
+                                case '\\':
+                                case '\'':
+                                case '"':
+                                case '/':
+                                    break;
+                                case 'b':
+                                    c = '\b';
+                                    break;
+                                case 'f':
+                                    c = '\f';
+                                    break;
+                                case 'n':
+                                    c = '\n';
+                                    break;
+                                case 'r':
+                                    c = '\r';
+                                    break;
+                                case 't':
+                                    c = '\t';
+                                    break;
+                                case 'u':
+                                    esc(4);
+                                    break;
+                                case 'v':
+                                    c = '\v';
+                                    break;
+                                case 'x':
+                                    if (jsonmode) {
+                                        warningAt("Avoid \\x-.", line, character);
+                                    }
+                                    esc(2);
+                                    break;
+                                default:
+                                    warningAt("Bad escapement.", line, character);
+                                }
+                            }
+                        }
+                        r += c;
+                        character += 1;
+                        j += 1;
+                    }
+                }
+
+                for (;;) {
+                    if (!s) {
+                        return it(nextLine() ? '(endline)' : '(end)', '');
+                    }
+                    while (xmode === 'outer') {
+                        i = s.search(ox);
+                        if (i === 0) {
+                            break;
+                        } else if (i > 0) {
+                            character += 1;
+                            s = s.slice(i);
+                            break;
+                        } else {
+                            if (!nextLine()) {
+                                return it('(end)', '');
+                            }
+                        }
+                    }
+//                     t = match(rx[xmode] || tx);
+//                     if (!t) {
+//                         if (xmode === 'html') {
+//                             return it('(error)', s.charAt(0));
+//                         } else {
+//                             t = '';
+//                             c = '';
+//                             while (s && s < '!') {
+//                                 s = s.substr(1);
+//                             }
+//                             if (s) {
+//                                 errorAt("Unexpected '{a}'.",
+//                                         line, character, s.substr(0, 1));
+//                             }
+//                         }
+                    t = match(rx[xmode] || tx);
+                    if (!t) {
+                        t = '';
+                        c = '';
+                        while (s && s < '!') {
+                            s = s.substr(1);
+                        }
+                        if (s) {
+                            if (xmode === 'html') {
+                                return it('(error)', s.charAt(0));
+                            } else {
+                                errorAt("Unexpected '{a}'.",
+                                        line, character, s.substr(0, 1));
+                            }
+                        }
+                    } else {
+
+    //      identifier
+
+                        if (c.isAlpha() || c === '_' || c === '$') {
+                            return it('(identifier)', t);
+                        }
+
+    //      number
+
+                        if (c.isDigit()) {
+                            if (xmode !== 'style' && !isFinite(Number(t))) {
+                                warningAt("Bad number '{a}'.",
+                                    line, character, t);
+                            }
+                            if (xmode !== 'style' &&
+                                     xmode !== 'styleproperty' &&
+                                     s.substr(0, 1).isAlpha()) {
+                                warningAt("Missing space after '{a}'.",
+                                        line, character, t);
+                            }
+                            if (c === '0') {
+                                d = t.substr(1, 1);
+                                if (d.isDigit()) {
+                                    if (token.id !== '.' && xmode !== 'styleproperty') {
+                                        warningAt("Don't use extra leading zeros '{a}'.",
+                                            line, character, t);
+                                    }
+                                } else if (jsonmode && (d === 'x' || d === 'X')) {
+                                    warningAt("Avoid 0x-. '{a}'.",
+                                            line, character, t);
+                                }
+                            }
+                            if (t.substr(t.length - 1) === '.') {
+                                warningAt(
+        "A trailing decimal point can be confused with a dot '{a}'.",
+                                        line, character, t);
+                            }
+                            return it('(number)', t);
+                        }
+                        switch (t) {
+
+    //      string
+
+                        case '"':
+                        case "'":
+                            return string(t);
+
+    //      // comment
+
+                        case '//':
+                            if (src || (xmode && xmode !== 'script')) {
+                                warningAt("Unexpected comment.", line, character);
+                            } else if (xmode === 'script' && /<\s*\//i.test(s)) {
+                                warningAt("Unexpected <\/ in comment.", line, character);
+                            } else if ((option.safe || xmode === 'script') && ax.test(s)) {
+                                warningAt("Dangerous comment.", line, character);
+                            }
+                            s = '';
+                            token.comment = true;
+                            break;
+
+    //      /* comment
+
+                        case '/*':
+                            if (src || (xmode && xmode !== 'script' && xmode !== 'style' && xmode !== 'styleproperty')) {
+                                warningAt("Unexpected comment.", line, character);
+                            }
+                            if (option.safe && ax.test(s)) {
+                                warningAt("ADsafe comment violation.", line, character);
+                            }
+                            for (;;) {
+                                i = s.search(lx);
+                                if (i >= 0) {
+                                    break;
+                                }
+                                if (!nextLine()) {
+                                    errorAt("Unclosed comment.", line, character);
+                                } else {
+                                    if (option.safe && ax.test(s)) {
+                                        warningAt("ADsafe comment violation.", line, character);
+                                    }
+                                }
+                            }
+                            character += i + 2;
+                            if (s.substr(i, 1) === '/') {
+                                errorAt("Nested comment.", line, character);
+                            }
+                            s = s.substr(i + 2);
+                            token.comment = true;
+                            break;
+
+    //      /*members /*jslint /*global
+
+                        case '/*members':
+                        case '/*member':
+                        case '/*jslint':
+                        case '/*global':
+                        case '*/':
+                            return {
+                                value: t,
+                                type: 'special',
+                                line: line,
+                                character: character,
+                                from: from
+                            };
+
+                        case '':
+                            break;
+    //      /
+                        case '/':
+                            if (token.id === '/=') {
+                                errorAt(
+"A regular expression literal can be confused with '/='.", line, from);
+                            }
+                            if (prereg) {
+                                depth = 0;
+                                captures = 0;
+                                l = 0;
+                                for (;;) {
+                                    b = true;
+                                    c = s.charAt(l);
+                                    l += 1;
+                                    switch (c) {
+                                    case '':
+                                        errorAt("Unclosed regular expression.", line, from);
+                                        return;
+                                    case '/':
+                                        if (depth > 0) {
+                                            warningAt("Unescaped '{a}'.", line, from + l, '/');
+                                        }
+                                        c = s.substr(0, l - 1);
+                                        q = {
+                                            g: true,
+                                            i: true,
+                                            m: true
+                                        };
+                                        while (q[s.charAt(l)] === true) {
+                                            q[s.charAt(l)] = false;
+                                            l += 1;
+                                        }
+                                        character += l;
+                                        s = s.substr(l);
+                                        q = s.charAt(0);
+                                        if (q === '/' || q === '*') {
+                                            errorAt("Confusing regular expression.", line, from);
+                                        }
+                                        return it('(regexp)', c);
+                                    case '\\':
+                                        c = s.charAt(l);
+                                        if (c < ' ') {
+                                            warningAt("Unexpected control character in regular expression.", line, from + l);
+                                        } else if (c === '<') {
+                                            warningAt("Unexpected escaped character '{a}' in regular expression.", line, from + l, c);
+                                        }
+                                        l += 1;
+                                        break;
+                                    case '(':
+                                        depth += 1;
+                                        b = false;
+                                        if (s.charAt(l) === '?') {
+                                            l += 1;
+                                            switch (s.charAt(l)) {
+                                            case ':':
+                                            case '=':
+                                            case '!':
+                                                l += 1;
+                                                break;
+                                            default:
+                                                warningAt("Expected '{a}' and instead saw '{b}'.", line, from + l, ':', s.charAt(l));
+                                            }
+                                        } else {
+                                            captures += 1;
+                                        }
+                                        break;
+                                    case '|':
+                                        b = false;
+                                        break;
+                                    case ')':
+                                        if (depth === 0) {
+                                            warningAt("Unescaped '{a}'.", line, from + l, ')');
+                                        } else {
+                                            depth -= 1;
+                                        }
+                                        break;
+                                    case ' ':
+                                        q = 1;
+                                        while (s.charAt(l) === ' ') {
+                                            l += 1;
+                                            q += 1;
+                                        }
+                                        if (q > 1) {
+                                            warningAt("Spaces are hard to count. Use {{a}}.", line, from + l, q);
+                                        }
+                                        break;
+                                    case '[':
+                                        c = s.charAt(l);
+                                        if (c === '^') {
+                                            l += 1;
+                                            if (option.regexp) {
+                                                warningAt("Insecure '{a}'.", line, from + l, c);
+                                            }
+                                        }
+                                        q = false;
+                                        if (c === ']') {
+                                            warningAt("Empty class.", line, from + l - 1);
+                                            q = true;
+                                        }
+    klass:                              do {
+                                            c = s.charAt(l);
+                                            l += 1;
+                                            switch (c) {
+                                            case '[':
+                                            case '^':
+                                                warningAt("Unescaped '{a}'.", line, from + l, c);
+                                                q = true;
+                                                break;
+                                            case '-':
+                                                if (q) {
+                                                    q = false;
+                                                } else {
+                                                    warningAt("Unescaped '{a}'.", line, from + l, '-');
+                                                    q = true;
+                                                }
+                                                break;
+                                            case ']':
+                                                if (!q) {
+                                                    warningAt("Unescaped '{a}'.", line, from + l - 1, '-');
+                                                }
+                                                break klass;
+                                            case '\\':
+                                                c = s.charAt(l);
+                                                if (c < ' ') {
+                                                    warningAt("Unexpected control character in regular expression.", line, from + l);
+                                                } else if (c === '<') {
+                                                    warningAt("Unexpected escaped character '{a}' in regular expression.", line, from + l, c);
+                                                }
+                                                l += 1;
+                                                q = true;
+                                                break;
+                                            case '/':
+                                                warningAt("Unescaped '{a}'.", line, from + l - 1, '/');
+                                                q = true;
+                                                break;
+                                            case '<':
+                                                if (xmode === 'script') {
+                                                    c = s.charAt(l);
+                                                    if (c === '!' || c === '/') {
+                                                        warningAt("HTML confusion in regular expression '<{a}'.", line, from + l, c);
+                                                    }
+                                                }
+                                                q = true;
+                                                break;
+                                            default:
+                                                q = true;
+                                            }
+                                        } while (c);
+                                        break;
+                                    case '.':
+                                        if (option.regexp) {
+                                            warningAt("Insecure '{a}'.", line, from + l, c);
+                                        }
+                                        break;
+                                    case ']':
+                                    case '?':
+                                    case '{':
+                                    case '}':
+                                    case '+':
+                                    case '*':
+                                        warningAt("Unescaped '{a}'.", line, from + l, c);
+                                        break;
+                                    case '<':
+                                        if (xmode === 'script') {
+                                            c = s.charAt(l);
+                                            if (c === '!' || c === '/') {
+                                                warningAt("HTML confusion in regular expression '<{a}'.", line, from + l, c);
+                                            }
+                                        }
+                                    }
+                                    if (b) {
+                                        switch (s.charAt(l)) {
+                                        case '?':
+                                        case '+':
+                                        case '*':
+                                            l += 1;
+                                            if (s.charAt(l) === '?') {
+                                                l += 1;
+                                            }
+                                            break;
+                                        case '{':
+                                            l += 1;
+                                            c = s.charAt(l);
+                                            if (c < '0' || c > '9') {
+                                                warningAt("Expected a number and instead saw '{a}'.", line, from + l, c);
+                                            }
+                                            l += 1;
+                                            low = +c;
+                                            for (;;) {
+                                                c = s.charAt(l);
+                                                if (c < '0' || c > '9') {
+                                                    break;
+                                                }
+                                                l += 1;
+                                                low = +c + (low * 10);
+                                            }
+                                            high = low;
+                                            if (c === ',') {
+                                                l += 1;
+                                                high = Infinity;
+                                                c = s.charAt(l);
+                                                if (c >= '0' && c <= '9') {
+                                                    l += 1;
+                                                    high = +c;
+                                                    for (;;) {
+                                                        c = s.charAt(l);
+                                                        if (c < '0' || c > '9') {
+                                                            break;
+                                                        }
+                                                        l += 1;
+                                                        high = +c + (high * 10);
+                                                    }
+                                                }
+                                            }
+                                            if (s.charAt(l) !== '}') {
+                                                warningAt("Expected '{a}' and instead saw '{b}'.", line, from + l, '}', c);
+                                            } else {
+                                                l += 1;
+                                            }
+                                            if (s.charAt(l) === '?') {
+                                                l += 1;
+                                            }
+                                            if (low > high) {
+                                                warningAt("'{a}' should not be greater than '{b}'.", line, from + l, low, high);
+                                            }
+                                        }
+                                    }
+                                }
+                                c = s.substr(0, l - 1);
+                                character += l;
+                                s = s.substr(l);
+                                return it('(regexp)', c);
+                            }
+                            return it('(punctuator)', t);
+
+    //      punctuator
+
+                        case '<!--':
+                            l = line;
+                            c = character;
+                            for (;;) {
+                                i = s.indexOf('--');
+                                if (i >= 0) {
+                                    break;
+                                }
+                                i = s.indexOf('<!');
+                                if (i >= 0) {
+                                    errorAt("Nested HTML comment.",
+                                        line, character + i);
+                                }
+                                if (!nextLine()) {
+                                    errorAt("Unclosed HTML comment.", l, c);
+                                }
+                            }
+                            l = s.indexOf('<!');
+                            if (l >= 0 && l < i) {
+                                errorAt("Nested HTML comment.",
+                                    line, character + l);
+                            }
+                            character += i;
+                            if (s[i + 2] !== '>') {
+                                errorAt("Expected -->.", line, character);
+                            }
+                            character += 3;
+                            s = s.slice(i + 3);
+                            break;
+                        case '#':
+                            if (xmode === 'html' || xmode === 'styleproperty') {
+                                for (;;) {
+                                    c = s.charAt(0);
+                                    if ((c < '0' || c > '9') &&
+                                            (c < 'a' || c > 'f') &&
+                                            (c < 'A' || c > 'F')) {
+                                        break;
+                                    }
+                                    character += 1;
+                                    s = s.substr(1);
+                                    t += c;
+                                }
+                                if (t.length !== 4 && t.length !== 7) {
+                                    warningAt("Bad hex color '{a}'.", line,
+                                        from + l, t);
+                                }
+                                return it('(color)', t);
+                            }
+                            return it('(punctuator)', t);
+                        default:
+                            if (xmode === 'outer' && c === '&') {
+                                character += 1;
+                                s = s.substr(1);
+                                for (;;) {
+                                    c = s.charAt(0);
+                                    character += 1;
+                                    s = s.substr(1);
+                                    if (c === ';') {
+                                        break;
+                                    }
+                                    if (!((c >= '0' && c <= '9') ||
+                                            (c >= 'a' && c <= 'z') ||
+                                            c === '#')) {
+                                        errorAt("Bad entity", line, from + l,
+                                        character);
+                                    }
+                                }
+                                break;
+                            }
+                            return it('(punctuator)', t);
+                        }
+                    }
+                }
+            }
+        };
+    }());
+
+
+    function addlabel(t, type) {
+
+        if (option.safe && funct['(global)'] && typeof predefined[t] !== 'boolean') {
+            warning('ADsafe global: ' + t + '.', token);
+        } else if (t === 'hasOwnProperty') {
+            warning("'hasOwnProperty' is a really bad name.");
+        }
+
+// Define t in the current function in the current scope.
+
+        if (is_own(funct, t) && !funct['(global)']) {
+            warning(funct[t] === true ?
+                "'{a}' was used before it was defined." :
+                "'{a}' is already defined.",
+                nexttoken, t);
+        }
+        funct[t] = type;
+        if (funct['(global)']) {
+            global[t] = funct;
+            if (is_own(implied, t)) {
+                warning("'{a}' was used before it was defined.", nexttoken, t);
+                delete implied[t];
+            }
+        } else {
+            scope[t] = funct;
+        }
+    }
+
+
+    function doOption() {
+        var b, obj, filter, o = nexttoken.value, t, v;
+        switch (o) {
+        case '*/':
+            error("Unbegun comment.");
+            break;
+        case '/*members':
+        case '/*member':
+            o = '/*members';
+            if (!membersOnly) {
+                membersOnly = {};
+            }
+            obj = membersOnly;
+            break;
+        case '/*jslint':
+            if (option.safe) {
+                warning("ADsafe restriction.");
+            }
+            obj = option;
+            filter = boolOptions;
+            break;
+        case '/*global':
+            if (option.safe) {
+                warning("ADsafe restriction.");
+            }
+            obj = predefined;
+            break;
+        default:
+        }
+        t = lex.token();
+loop:   for (;;) {
+            for (;;) {
+                if (t.type === 'special' && t.value === '*/') {
+                    break loop;
+                }
+                if (t.id !== '(endline)' && t.id !== ',') {
+                    break;
+                }
+                t = lex.token();
+            }
+            if (t.type !== '(string)' && t.type !== '(identifier)' &&
+                    o !== '/*members') {
+                error("Bad option.", t);
+            }
+            v = lex.token();
+            if (v.id === ':') {
+                v = lex.token();
+                if (obj === membersOnly) {
+                    error("Expected '{a}' and instead saw '{b}'.",
+                            t, '*/', ':');
+                }
+                if (t.value === 'indent' && o === '/*jslint') {
+                    b = +v.value;
+                    if (typeof b !== 'number' || !isFinite(b) || b <= 0 ||
+                            Math.floor(b) !== b) {
+                        error("Expected a small integer and instead saw '{a}'.",
+                                v, v.value);
+                    }
+                    obj.white = true;
+                    obj.indent = b;
+                } else if (t.value === 'maxerr' && o === '/*jslint') {
+                    b = +v.value;
+                    if (typeof b !== 'number' || !isFinite(b) || b <= 0 ||
+                            Math.floor(b) !== b) {
+                        error("Expected a small integer and instead saw '{a}'.",
+                                v, v.value);
+                    }
+                    obj.maxerr = b;
+                } else if (t.value === 'maxlen' && o === '/*jslint') {
+                    b = +v.value;
+                    if (typeof b !== 'number' || !isFinite(b) || b <= 0 ||
+                            Math.floor(b) !== b) {
+                        error("Expected a small integer and instead saw '{a}'.",
+                                v, v.value);
+                    }
+                    obj.maxlen = b;
+                } else if (v.value === 'true') {
+                    obj[t.value] = true;
+                } else if (v.value === 'false') {
+                    obj[t.value] = false;
+                } else {
+                    error("Bad option value.", v);
+                }
+                t = lex.token();
+            } else {
+                if (o === '/*jslint') {
+                    error("Missing option value.", t);
+                }
+                obj[t.value] = false;
+                t = v;
+            }
+        }
+        if (filter) {
+            assume();
+        }
+    }
+
+
+// We need a peek function. If it has an argument, it peeks that much farther
+// ahead. It is used to distinguish
+//     for ( var i in ...
+// from
+//     for ( var i = ...
+
+    function peek(p) {
+        var i = p || 0, j = 0, t;
+
+        while (j <= i) {
+            t = lookahead[j];
+            if (!t) {
+                t = lookahead[j] = lex.token();
+            }
+            j += 1;
+        }
+        return t;
+    }
+
+
+
+// Produce the next token. It looks for programming errors.
+
+    function advance(id, t) {
+        switch (token.id) {
+        case '(number)':
+            if (nexttoken.id === '.') {
+                warning(
+"A dot following a number can be confused with a decimal point.", token);
+            }
+            break;
+        case '-':
+            if (nexttoken.id === '-' || nexttoken.id === '--') {
+                warning("Confusing minusses.");
+            }
+            break;
+        case '+':
+            if (nexttoken.id === '+' || nexttoken.id === '++') {
+                warning("Confusing plusses.");
+            }
+            break;
+        }
+        if (token.type === '(string)' || token.identifier) {
+            anonname = token.value;
+        }
+
+        if (id && nexttoken.id !== id) {
+            if (t) {
+                if (nexttoken.id === '(end)') {
+                    warning("Unmatched '{a}'.", t, t.id);
+                } else {
+                    warning("Expected '{a}' to match '{b}' from line {c} and instead saw '{d}'.",
+                            nexttoken, id, t.id, t.line, nexttoken.value);
+                }
+            } else if (nexttoken.type !== '(identifier)' ||
+                            nexttoken.value !== id) {
+                warning("Expected '{a}' and instead saw '{b}'.",
+                        nexttoken, id, nexttoken.value);
+            }
+        }
+        prevtoken = token;
+        token = nexttoken;
+        for (;;) {
+            nexttoken = lookahead.shift() || lex.token();
+            if (nexttoken.id === '(end)' || nexttoken.id === '(error)') {
+                return;
+            }
+            if (nexttoken.type === 'special') {
+                doOption();
+            } else {
+                if (nexttoken.id !== '(endline)') {
+                    break;
+                }
+            }
+        }
+    }
+
+
+// This is the heart of JSLINT, the Pratt parser. In addition to parsing, it
+// is looking for ad hoc lint patterns. We add to Pratt's model .fud, which is
+// like nud except that it is only used on the first token of a statement.
+// Having .fud makes it much easier to define JavaScript. I retained Pratt's
+// nomenclature.
+
+// .nud     Null denotation
+// .fud     First null denotation
+// .led     Left denotation
+//  lbp     Left binding power
+//  rbp     Right binding power
+
+// They are key to the parsing method called Top Down Operator Precedence.
+
+    function parse(rbp, initial) {
+        var left;
+        if (nexttoken.id === '(end)') {
+            error("Unexpected early end of program.", token);
+        }
+        advance();
+        if (option.safe && typeof predefined[token.value] === 'boolean' &&
+                (nexttoken.id !== '(' && nexttoken.id !== '.')) {
+            warning('ADsafe violation.', token);
+        }
+        if (initial) {
+            anonname = 'anonymous';
+            funct['(verb)'] = token.value;
+        }
+        if (initial === true && token.fud) {
+            left = token.fud();
+        } else {
+            if (token.nud) {
+                left = token.nud();
+            } else {
+                if (nexttoken.type === '(number)' && token.id === '.') {
+                    warning(
+"A leading decimal point can be confused with a dot: '.{a}'.",
+                            token, nexttoken.value);
+                    advance();
+                    return token;
+                } else {
+                    error("Expected an identifier and instead saw '{a}'.",
+                            token, token.id);
+                }
+            }
+            while (rbp < nexttoken.lbp) {
+                advance();
+                if (token.led) {
+                    left = token.led(left);
+                } else {
+                    error("Expected an operator and instead saw '{a}'.",
+                        token, token.id);
+                }
+            }
+        }
+        return left;
+    }
+
+
+// Functions for conformance of style.
+
+    function adjacent(left, right) {
+        left = left || token;
+        right = right || nexttoken;
+        if (option.white || xmode === 'styleproperty' || xmode === 'style') {
+            if (left.character !== right.from && left.line === right.line) {
+                warning("Unexpected space after '{a}'.", right, left.value);
+            }
+        }
+    }
+
+    function nospace(left, right) {
+        left = left || token;
+        right = right || nexttoken;
+        if (option.white && !left.comment) {
+            if (left.line === right.line) {
+                adjacent(left, right);
+            }
+        }
+    }
+
+
+    function nonadjacent(left, right) {
+        if (option.white) {
+            left = left || token;
+            right = right || nexttoken;
+            if (left.line === right.line && left.character === right.from) {
+                warning("Missing space after '{a}'.",
+                        nexttoken, left.value);
+            }
+        }
+    }
+
+    function nobreaknonadjacent(left, right) {
+        left = left || token;
+        right = right || nexttoken;
+        if (!option.laxbreak && left.line !== right.line) {
+            warning("Bad line breaking before '{a}'.", right, right.id);
+        } else if (option.white) {
+            left = left || token;
+            right = right || nexttoken;
+            if (left.character === right.from) {
+                warning("Missing space after '{a}'.",
+                        nexttoken, left.value);
+            }
+        }
+    }
+
+    function indentation(bias) {
+        var i;
+        if (option.white && nexttoken.id !== '(end)') {
+            i = indent + (bias || 0);
+            if (nexttoken.from !== i) {
+                warning("Expected '{a}' to have an indentation at {b} instead at {c}.",
+                        nexttoken, nexttoken.value, i, nexttoken.from);
+            }
+        }
+    }
+
+    function nolinebreak(t) {
+        t = t || token;
+        if (t.line !== nexttoken.line) {
+            warning("Line breaking error '{a}'.", t, t.value);
+        }
+    }
+
+
+    function comma() {
+        if (token.line !== nexttoken.line) {
+            if (!option.laxbreak) {
+                warning("Bad line breaking before '{a}'.", token, nexttoken.id);
+            }
+        } else if (token.character !== nexttoken.from && option.white) {
+            warning("Unexpected space after '{a}'.", nexttoken, token.value);
+        }
+        advance(',');
+        nonadjacent(token, nexttoken);
+    }
+
+
+// Functional constructors for making the symbols that will be inherited by
+// tokens.
+
+    function symbol(s, p) {
+        var x = syntax[s];
+        if (!x || typeof x !== 'object') {
+            syntax[s] = x = {
+                id: s,
+                lbp: p,
+                value: s
+            };
+        }
+        return x;
+    }
+
+
+    function delim(s) {
+        return symbol(s, 0);
+    }
+
+
+    function stmt(s, f) {
+        var x = delim(s);
+        x.identifier = x.reserved = true;
+        x.fud = f;
+        return x;
+    }
+
+
+    function blockstmt(s, f) {
+        var x = stmt(s, f);
+        x.block = true;
+        return x;
+    }
+
+
+    function reserveName(x) {
+        var c = x.id.charAt(0);
+        if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
+            x.identifier = x.reserved = true;
+        }
+        return x;
+    }
+
+
+    function prefix(s, f) {
+        var x = symbol(s, 150);
+        reserveName(x);
+        x.nud = (typeof f === 'function') ? f : function () {
+            this.right = parse(150);
+            this.arity = 'unary';
+            if (this.id === '++' || this.id === '--') {
+                if (option.plusplus) {
+                    warning("Unexpected use of '{a}'.", this, this.id);
+                } else if ((!this.right.identifier || this.right.reserved) &&
+                        this.right.id !== '.' && this.right.id !== '[') {
+                    warning("Bad operand.", this);
+                }
+            }
+            return this;
+        };
+        return x;
+    }
+
+
+    function type(s, f) {
+        var x = delim(s);
+        x.type = s;
+        x.nud = f;
+        return x;
+    }
+
+
+    function reserve(s, f) {
+        var x = type(s, f);
+        x.identifier = x.reserved = true;
+        return x;
+    }
+
+
+    function reservevar(s, v) {
+        return reserve(s, function () {
+            if (this.id === 'this' || this.id === 'arguments') {
+                if (strict_mode && funct['(global)']) {
+                    warning("Strict violation.", this);
+                } else if (option.safe) {
+                    warning("ADsafe violation.", this);
+                }
+            }
+            return this;
+        });
+    }
+
+
+    function infix(s, f, p, w) {
+        var x = symbol(s, p);
+        reserveName(x);
+        x.led = function (left) {
+            if (!w) {
+                nobreaknonadjacent(prevtoken, token);
+                nonadjacent(token, nexttoken);
+            }
+            if (typeof f === 'function') {
+                return f(left, this);
+            } else {
+                this.left = left;
+                this.right = parse(p);
+                return this;
+            }
+        };
+        return x;
+    }
+
+
+    function relation(s, f) {
+        var x = symbol(s, 100);
+        x.led = function (left) {
+            nobreaknonadjacent(prevtoken, token);
+            nonadjacent(token, nexttoken);
+            var right = parse(100);
+            if ((left && left.id === 'NaN') || (right && right.id === 'NaN')) {
+                warning("Use the isNaN function to compare with NaN.", this);
+            } else if (f) {
+                f.apply(this, [left, right]);
+            }
+            if (left.id === '!') {
+                warning("Confusing use of '{a}'.", left, '!');
+            }
+            if (right.id === '!') {
+                warning("Confusing use of '{a}'.", left, '!');
+            }
+            this.left = left;
+            this.right = right;
+            return this;
+        };
+        return x;
+    }
+
+
+    function isPoorRelation(node) {
+        return node &&
+              ((node.type === '(number)' && +node.value === 0) ||
+               (node.type === '(string)' && node.value === ' ') ||
+                node.type === 'true' ||
+                node.type === 'false' ||
+                node.type === 'undefined' ||
+                node.type === 'null');
+    }
+
+
+    function assignop(s, f) {
+        symbol(s, 20).exps = true;
+        return infix(s, function (left, that) {
+            var l;
+            that.left = left;
+            if (predefined[left.value] === false &&
+                    scope[left.value]['(global)'] === true) {
+                warning('Read only.', left);
+            }
+            if (option.safe) {
+                l = left;
+                do {
+                    if (typeof predefined[l.value] === 'boolean') {
+                        warning('ADsafe violation.', l);
+                    }
+                    l = l.left;
+                } while (l);
+            }
+            if (left) {
+                if (left.id === '.' || left.id === '[') {
+                    if (!left.left || left.left.value === 'arguments') {
+                        warning('Bad assignment.', that);
+                    }
+                    that.right = parse(19);
+                    return that;
+                } else if (left.identifier && !left.reserved) {
+                    if (funct[left.value] === 'exception') {
+                        warning("Do not assign to the exception parameter.", left);
+                    }
+                    that.right = parse(19);
+                    return that;
+                }
+                if (left === syntax['function']) {
+                    warning(
+"Expected an identifier in an assignment and instead saw a function invocation.",
+                                token);
+                }
+            }
+            error("Bad assignment.", that);
+        }, 20);
+    }
+
+    function bitwise(s, f, p) {
+        var x = symbol(s, p);
+        reserveName(x);
+        x.led = (typeof f === 'function') ? f : function (left) {
+            if (option.bitwise) {
+                warning("Unexpected use of '{a}'.", this, this.id);
+            }
+            this.left = left;
+            this.right = parse(p);
+            return this;
+        };
+        return x;
+    }
+
+    function bitwiseassignop(s) {
+        symbol(s, 20).exps = true;
+        return infix(s, function (left, that) {
+            if (option.bitwise) {
+                warning("Unexpected use of '{a}'.", that, that.id);
+            }
+            nonadjacent(prevtoken, token);
+            nonadjacent(token, nexttoken);
+            if (left) {
+                if (left.id === '.' || left.id === '[' ||
+                        (left.identifier && !left.reserved)) {
+                    parse(19);
+                    return that;
+                }
+                if (left === syntax['function']) {
+                    warning(
+"Expected an identifier in an assignment, and instead saw a function invocation.",
+                                token);
+                }
+                return that;
+            }
+            error("Bad assignment.", that);
+        }, 20);
+    }
+
+
+    function suffix(s, f) {
+        var x = symbol(s, 150);
+        x.led = function (left) {
+            if (option.plusplus) {
+                warning("Unexpected use of '{a}'.", this, this.id);
+            } else if ((!left.identifier || left.reserved) && left.id !== '.' && left.id !== '[') {
+                warning("Bad operand.", this);
+            }
+            this.left = left;
+            return this;
+        };
+        return x;
+    }
+
+
+    function optionalidentifier() {
+        if (nexttoken.reserved) {
+            warning("Expected an identifier and instead saw '{a}' (a reserved word).",
+                    nexttoken, nexttoken.id);
+        }
+        if (nexttoken.identifier) {
+            advance();
+            return token.value;
+        }
+    }
+
+
+    function identifier() {
+        var i = optionalidentifier();
+        if (i) {
+            return i;
+        }
+        if (token.id === 'function' && nexttoken.id === '(') {
+            warning("Missing name in function statement.");
+        } else {
+            error("Expected an identifier and instead saw '{a}'.",
+                    nexttoken, nexttoken.value);
+        }
+    }
+
+    function reachable(s) {
+        var i = 0, t;
+        if (nexttoken.id !== ';' || noreach) {
+            return;
+        }
+        for (;;) {
+            t = peek(i);
+            if (t.reach) {
+                return;
+            }
+            if (t.id !== '(endline)') {
+                if (t.id === 'function') {
+                    warning(
+"Inner functions should be listed at the top of the outer function.", t);
+                    break;
+                }
+                warning("Unreachable '{a}' after '{b}'.", t, t.value, s);
+                break;
+            }
+            i += 1;
+        }
+    }
+
+
+    function statement(noindent) {
+        var i = indent, r, s = scope, t = nexttoken;
+
+// We don't like the empty statement.
+
+        if (t.id === ';') {
+            warning("Unnecessary semicolon.", t);
+            advance(';');
+            return;
+        }
+
+// Is this a labelled statement?
+
+        if (t.identifier && !t.reserved && peek().id === ':') {
+            advance();
+            advance(':');
+            scope = Object.create(s);
+            addlabel(t.value, 'label');
+            if (!nexttoken.labelled) {
+                warning("Label '{a}' on {b} statement.",
+                        nexttoken, t.value, nexttoken.value);
+            }
+            if (jx.test(t.value + ':')) {
+                warning("Label '{a}' looks like a javascript url.",
+                        t, t.value);
+            }
+            nexttoken.label = t.value;
+            t = nexttoken;
+        }
+
+// Parse the statement.
+
+        if (!noindent) {
+            indentation();
+        }
+        r = parse(0, true);
+
+// Look for the final semicolon.
+
+        if (!t.block) {
+            if (!r || !r.exps) {
+                warning(
+"Expected an assignment or function call and instead saw an expression.",
+                        token);
+            } else if (r.id === '(' && r.left.id === 'new') {
+                warning("Do not use 'new' for side effects.");
+            }
+            if (nexttoken.id !== ';') {
+                warningAt("Missing semicolon.", token.line,
+                        token.from + token.value.length);
+            } else {
+                adjacent(token, nexttoken);
+                advance(';');
+                nonadjacent(token, nexttoken);
+            }
+        }
+
+// Restore the indentation.
+
+        indent = i;
+        scope = s;
+        return r;
+    }
+
+
+    function use_strict() {
+        if (nexttoken.value === 'use strict') {
+            advance();
+            advance(';');
+            strict_mode = true;
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+
+    function statements(begin) {
+        var a = [], f, p;
+        if (begin && !use_strict() && option.strict) {
+            warning('Missing "use strict" statement.', nexttoken);
+        }
+        if (option.adsafe) {
+            switch (begin) {
+            case 'script':
+                if (!adsafe_may) {
+                    if (nexttoken.value !== 'ADSAFE' ||
+                            peek(0).id !== '.' ||
+                            (peek(1).value !== 'id' &&
+                            peek(1).value !== 'go')) {
+                        error('ADsafe violation: Missing ADSAFE.id or ADSAFE.go.',
+                            nexttoken);
+                    }
+                }
+                if (nexttoken.value === 'ADSAFE' &&
+                        peek(0).id === '.' &&
+                        peek(1).value === 'id') {
+                    if (adsafe_may) {
+                        error('ADsafe violation.', nexttoken);
+                    }
+                    advance('ADSAFE');
+                    advance('.');
+                    advance('id');
+                    advance('(');
+                    if (nexttoken.value !== adsafe_id) {
+                        error('ADsafe violation: id does not match.', nexttoken);
+                    }
+                    advance('(string)');
+                    advance(')');
+                    advance(';');
+                    adsafe_may = true;
+                }
+                break;
+            case 'lib':
+                if (nexttoken.value === 'ADSAFE') {
+                    advance('ADSAFE');
+                    advance('.');
+                    advance('lib');
+                    advance('(');
+                    advance('(string)');
+                    comma();
+                    f = parse(0);
+                    if (f.id !== 'function') {
+                        error('The second argument to lib must be a function.', f);
+                    }
+                    p = f.funct['(params)'];
+                    p = p && p.join(', ');
+                    if (p && p !== 'lib') {
+                        error("Expected '{a}' and instead saw '{b}'.",
+                            f, '(lib)', '(' + p + ')');
+                    }
+                    advance(')');
+                    advance(';');
+                    return a;
+                } else {
+                    error("ADsafe lib violation.");
+                }
+            }
+        }
+        while (!nexttoken.reach && nexttoken.id !== '(end)') {
+            if (nexttoken.id === ';') {
+                warning("Unnecessary semicolon.");
+                advance(';');
+            } else {
+                a.push(statement());
+            }
+        }
+        return a;
+    }
+
+
+    function block(f) {
+        var a, b = inblock, old_indent = indent, s = scope, t;
+        inblock = f;
+        scope = Object.create(scope);
+        nonadjacent(token, nexttoken);
+        t = nexttoken;
+        if (nexttoken.id === '{') {
+            advance('{');
+            if (nexttoken.id !== '}' || token.line !== nexttoken.line) {
+                indent += option.indent;
+                while (!f && nexttoken.from > indent) {
+                    indent += option.indent;
+                }
+                if (!f) {
+                    use_strict();
+                }
+                a = statements();
+                indent -= option.indent;
+                indentation();
+            }
+            advance('}', t);
+            indent = old_indent;
+        } else {
+            warning("Expected '{a}' and instead saw '{b}'.",
+                    nexttoken, '{', nexttoken.value);
+            noreach = true;
+            a = [statement()];
+            noreach = false;
+        }
+        funct['(verb)'] = null;
+        scope = s;
+        inblock = b;
+        return a;
+    }
+
+
+// An identity function, used by string and number tokens.
+
+    function idValue() {
+        return this;
+    }
+
+
+    function countMember(m) {
+        if (membersOnly && typeof membersOnly[m] !== 'boolean') {
+            warning("Unexpected /*member '{a}'.", token, m);
+        }
+        if (typeof member[m] === 'number') {
+            member[m] += 1;
+        } else {
+            member[m] = 1;
+        }
+    }
+
+
+    function note_implied(token) {
+        var name = token.value, line = token.line, a = implied[name];
+        if (typeof a === 'function') {
+            a = false;
+        }
+        if (!a) {
+            a = [line];
+            implied[name] = a;
+        } else if (a[a.length - 1] !== line) {
+            a.push(line);
+        }
+    }
+
+// CSS parsing.
+
+
+    function cssName() {
+        if (nexttoken.identifier) {
+            advance();
+            return true;
+        }
+    }
+
+    function cssNumber() {
+        if (nexttoken.id === '-') {
+            advance('-');
+            adjacent();
+            nolinebreak();
+        }
+        if (nexttoken.type === '(number)') {
+            advance('(number)');
+            return true;
+        }
+    }
+
+    function cssString() {
+        if (nexttoken.type === '(string)') {
+            advance();
+            return true;
+        }
+    }
+
+    function cssColor() {
+        var i, number, value;
+        if (nexttoken.identifier) {
+            value = nexttoken.value;
+            if (value === 'rgb' || value === 'rgba') {
+                advance();
+                advance('(');
+                for (i = 0; i < 3; i += 1) {
+                    if (i) {
+                        advance(',');
+                    }
+                    number = nexttoken.value;
+                    if (nexttoken.type !== '(number)' || number < 0) {
+                        warning("Expected a positive number and instead saw '{a}'",
+                            nexttoken, number);
+                        advance();
+                    } else {
+                        advance();
+                        if (nexttoken.id === '%') {
+                            advance('%');
+                            if (number > 100) {
+                                warning("Expected a percentage and instead saw '{a}'",
+                                    token, number);
+                            }
+                        } else {
+                            if (number > 255) {
+                                warning("Expected a small number and instead saw '{a}'",
+                                    token, number);
+                            }
+                        }
+                    }
+                }
+                if (value === 'rgba') {
+                    advance(',');
+                    number = +nexttoken.value;
+                    if (nexttoken.type !== '(number)' || number < 0 || number > 1) {
+                        warning("Expected a number between 0 and 1 and instead saw '{a}'",
+                            nexttoken, number);
+                    }
+                    advance();
+                    if (nexttoken.id === '%') {
+                        warning("Unexpected '%'.");
+                        advance('%');
+                    }
+                }
+                advance(')');
+                return true;
+            } else if (cssColorData[nexttoken.value] === true) {
+                advance();
+                return true;
+            }
+        } else if (nexttoken.type === '(color)') {
+            advance();
+            return true;
+        }
+        return false;
+    }
+
+    function cssLength() {
+        if (nexttoken.id === '-') {
+            advance('-');
+            adjacent();
+            nolinebreak();
+        }
+        if (nexttoken.type === '(number)') {
+            advance();
+            if (nexttoken.type !== '(string)' &&
+                    cssLengthData[nexttoken.value] === true) {
+                adjacent();
+                advance();
+            } else if (+token.value !== 0) {
+                warning("Expected a linear unit and instead saw '{a}'.",
+                    nexttoken, nexttoken.value);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    function cssLineHeight() {
+        if (nexttoken.id === '-') {
+            advance('-');
+            adjacent();
+        }
+        if (nexttoken.type === '(number)') {
+            advance();
+            if (nexttoken.type !== '(string)' &&
+                    cssLengthData[nexttoken.value] === true) {
+                adjacent();
+                advance();
+            }
+            return true;
+        }
+        return false;
+    }
+
+    function cssWidth() {
+        if (nexttoken.identifier) {
+            switch (nexttoken.value) {
+            case 'thin':
+            case 'medium':
+            case 'thick':
+                advance();
+                return true;
+            }
+        } else {
+            return cssLength();
+        }
+    }
+
+    function cssMargin() {
+        if (nexttoken.identifier) {
+            if (nexttoken.value === 'auto') {
+                advance();
+                return true;
+            }
+        } else {
+            return cssLength();
+        }
+    }
+
+    function cssAttr() {
+        if (nexttoken.identifier && nexttoken.value === 'attr') {
+            advance();
+            advance('(');
+            if (!nexttoken.identifier) {
+                warning("Expected a name and instead saw '{a}'.",
+                        nexttoken, nexttoken.value);
+            }
+            advance();
+            advance(')');
+            return true;
+        }
+        return false;
+    }
+
+    function cssCommaList() {
+        while (nexttoken.id !== ';') {
+            if (!cssName() && !cssString()) {
+                warning("Expected a name and instead saw '{a}'.",
+                        nexttoken, nexttoken.value);
+            }
+            if (nexttoken.id !== ',') {
+                return true;
+            }
+            comma();
+        }
+    }
+
+    function cssCounter() {
+        if (nexttoken.identifier && nexttoken.value === 'counter') {
+            advance();
+            advance('(');
+            if (!nexttoken.identifier) {
+            }
+            advance();
+            if (nexttoken.id === ',') {
+                comma();
+                if (nexttoken.type !== '(string)') {
+                    warning("Expected a string and instead saw '{a}'.",
+                        nexttoken, nexttoken.value);
+                }
+                advance();
+            }
+            advance(')');
+            return true;
+        }
+        if (nexttoken.identifier && nexttoken.value === 'counters') {
+            advance();
+            advance('(');
+            if (!nexttoken.identifier) {
+                warning("Expected a name and instead saw '{a}'.",
+                        nexttoken, nexttoken.value);
+            }
+            advance();
+            if (nexttoken.id === ',') {
+                comma();
+                if (nexttoken.type !== '(string)') {
+                    warning("Expected a string and instead saw '{a}'.",
+                        nexttoken, nexttoken.value);
+                }
+                advance();
+            }
+            if (nexttoken.id === ',') {
+                comma();
+                if (nexttoken.type !== '(string)') {
+                    warning("Expected a string and instead saw '{a}'.",
+                        nexttoken, nexttoken.value);
+                }
+                advance();
+            }
+            advance(')');
+            return true;
+        }
+        return false;
+    }
+
+
+    function cssShape() {
+        var i;
+        if (nexttoken.identifier && nexttoken.value === 'rect') {
+            advance();
+            advance('(');
+            for (i = 0; i < 4; i += 1) {
+                if (!cssLength()) {
+                    warning("Expected a number and instead saw '{a}'.",
+                        nexttoken, nexttoken.value);
+                    break;
+                }
+            }
+            advance(')');
+            return true;
+        }
+        return false;
+    }
+
+    function cssUrl() {
+        var c, url;
+        if (nexttoken.identifier && nexttoken.value === 'url') {
+            nexttoken = lex.range('(', ')');
+            url = nexttoken.value;
+            c = url.charAt(0);
+            if (c === '"' || c === '\'') {
+                if (url.slice(-1) !== c) {
+                    warning("Bad url string.");
+                } else {
+                    url = url.slice(1, -1);
+                    if (url.indexOf(c) >= 0) {
+                        warning("Bad url string.");
+                    }
+                }
+            }
+            if (!url) {
+                warning("Missing url.");
+            }
+            advance();
+            if (option.safe && ux.test(url)) {
+                error("ADsafe URL violation.");
+            }
+            urls.push(url);
+            return true;
+        }
+        return false;
+    }
+
+    cssAny = [cssUrl, function () {
+        for (;;) {
+            if (nexttoken.identifier) {
+                switch (nexttoken.value.toLowerCase()) {
+                case 'url':
+                    cssUrl();
+                    break;
+                case 'expression':
+                    warning("Unexpected expression '{a}'.",
+                        nexttoken, nexttoken.value);
+                    advance();
+                    break;
+                default:
+                    advance();
+                }
+            } else {
+                if (nexttoken.id === ';' || nexttoken.id === '!'  ||
+                        nexttoken.id === '(end)' || nexttoken.id === '}') {
+                    return true;
+                }
+                advance();
+            }
+        }
+    }];
+
+    cssBorderStyle = [
+        'none', 'hidden', 'dotted', 'dashed', 'solid', 'double', 'ridge',
+        'inset', 'outset'
+    ];
+
+    cssBreak = [
+        'auto', 'always', 'avoid', 'left', 'right'
+    ];
+
+    cssOverflow = [
+        'auto', 'hidden', 'scroll', 'visible'
+    ];
+
+    cssAttributeData = {
+        background: [
+            true, 'background-attachment', 'background-color',
+            'background-image', 'background-position', 'background-repeat'
+        ],
+        'background-attachment': ['scroll', 'fixed'],
+        'background-color': ['transparent', cssColor],
+        'background-image': ['none', cssUrl],
+        'background-position': [
+            2, [cssLength, 'top', 'bottom', 'left', 'right', 'center']
+        ],
+        'background-repeat': [
+            'repeat', 'repeat-x', 'repeat-y', 'no-repeat'
+        ],
+        'border': [true, 'border-color', 'border-style', 'border-width'],
+        'border-bottom': [
+            true, 'border-bottom-color', 'border-bottom-style',
+            'border-bottom-width'
+        ],
+        'border-bottom-color': cssColor,
+        'border-bottom-style': cssBorderStyle,
+        'border-bottom-width': cssWidth,
+        'border-collapse': ['collapse', 'separate'],
+        'border-color': ['transparent', 4, cssColor],
+        'border-left': [
+            true, 'border-left-color', 'border-left-style', 'border-left-width'
+        ],
+        'border-left-color': cssColor,
+        'border-left-style': cssBorderStyle,
+        'border-left-width': cssWidth,
+        'border-right': [
+            true, 'border-right-color', 'border-right-style',
+            'border-right-width'
+        ],
+        'border-right-color': cssColor,
+        'border-right-style': cssBorderStyle,
+        'border-right-width': cssWidth,
+        'border-spacing': [2, cssLength],
+        'border-style': [4, cssBorderStyle],
+        'border-top': [
+            true, 'border-top-color', 'border-top-style', 'border-top-width'
+        ],
+        'border-top-color': cssColor,
+        'border-top-style': cssBorderStyle,
+        'border-top-width': cssWidth,
+        'border-width': [4, cssWidth],
+        bottom: [cssLength, 'auto'],
+        'caption-side' : ['bottom', 'left', 'right', 'top'],
+        clear: ['both', 'left', 'none', 'right'],
+        clip: [cssShape, 'auto'],
+        color: cssColor,
+        content: [
+            'open-quote', 'close-quote', 'no-open-quote', 'no-close-quote',
+            cssString, cssUrl, cssCounter, cssAttr
+        ],
+        'counter-increment': [
+            cssName, 'none'
+        ],
+        'counter-reset': [
+            cssName, 'none'
+        ],
+        cursor: [
+            cssUrl, 'auto', 'crosshair', 'default', 'e-resize', 'help', 'move',
+            'n-resize', 'ne-resize', 'nw-resize', 'pointer', 's-resize',
+            'se-resize', 'sw-resize', 'w-resize', 'text', 'wait'
+        ],
+        direction: ['ltr', 'rtl'],
+        display: [
+            'block', 'compact', 'inline', 'inline-block', 'inline-table',
+            'list-item', 'marker', 'none', 'run-in', 'table', 'table-caption',
+            'table-cell', 'table-column', 'table-column-group',
+            'table-footer-group', 'table-header-group', 'table-row',
+            'table-row-group'
+        ],
+        'empty-cells': ['show', 'hide'],
+        'float': ['left', 'none', 'right'],
+        font: [
+            'caption', 'icon', 'menu', 'message-box', 'small-caption',
+            'status-bar', true, 'font-size', 'font-style', 'font-weight',
+            'font-family'
+        ],
+        'font-family': cssCommaList,
+        'font-size': [
+            'xx-small', 'x-small', 'small', 'medium', 'large', 'x-large',
+            'xx-large', 'larger', 'smaller', cssLength
+        ],
+        'font-size-adjust': ['none', cssNumber],
+        'font-stretch': [
+            'normal', 'wider', 'narrower', 'ultra-condensed',
+            'extra-condensed', 'condensed', 'semi-condensed',
+            'semi-expanded', 'expanded', 'extra-expanded'
+        ],
+        'font-style': [
+            'normal', 'italic', 'oblique'
+        ],
+        'font-variant': [
+            'normal', 'small-caps'
+        ],
+        'font-weight': [
+            'normal', 'bold', 'bolder', 'lighter', cssNumber
+        ],
+        height: [cssLength, 'auto'],
+        left: [cssLength, 'auto'],
+        'letter-spacing': ['normal', cssLength],
+        'line-height': ['normal', cssLineHeight],
+        'list-style': [
+            true, 'list-style-image', 'list-style-position', 'list-style-type'
+        ],
+        'list-style-image': ['none', cssUrl],
+        'list-style-position': ['inside', 'outside'],
+        'list-style-type': [
+            'circle', 'disc', 'square', 'decimal', 'decimal-leading-zero',
+            'lower-roman', 'upper-roman', 'lower-greek', 'lower-alpha',
+            'lower-latin', 'upper-alpha', 'upper-latin', 'hebrew', 'katakana',
+            'hiragana-iroha', 'katakana-oroha', 'none'
+        ],
+        margin: [4, cssMargin],
+        'margin-bottom': cssMargin,
+        'margin-left': cssMargin,
+        'margin-right': cssMargin,
+        'margin-top': cssMargin,
+        'marker-offset': [cssLength, 'auto'],
+        'max-height': [cssLength, 'none'],
+        'max-width': [cssLength, 'none'],
+        'min-height': cssLength,
+        'min-width': cssLength,
+        opacity: cssNumber,
+        outline: [true, 'outline-color', 'outline-style', 'outline-width'],
+        'outline-color': ['invert', cssColor],
+        'outline-style': [
+            'dashed', 'dotted', 'double', 'groove', 'inset', 'none',
+            'outset', 'ridge', 'solid'
+        ],
+        'outline-width': cssWidth,
+        overflow: cssOverflow,
+        'overflow-x': cssOverflow,
+        'overflow-y': cssOverflow,
+        padding: [4, cssLength],
+        'padding-bottom': cssLength,
+        'padding-left': cssLength,
+        'padding-right': cssLength,
+        'padding-top': cssLength,
+        'page-break-after': cssBreak,
+        'page-break-before': cssBreak,
+        position: ['absolute', 'fixed', 'relative', 'static'],
+        quotes: [8, cssString],
+        right: [cssLength, 'auto'],
+        'table-layout': ['auto', 'fixed'],
+        'text-align': ['center', 'justify', 'left', 'right'],
+        'text-decoration': [
+            'none', 'underline', 'overline', 'line-through', 'blink'
+        ],
+        'text-indent': cssLength,
+        'text-shadow': ['none', 4, [cssColor, cssLength]],
+        'text-transform': ['capitalize', 'uppercase', 'lowercase', 'none'],
+        top: [cssLength, 'auto'],
+        'unicode-bidi': ['normal', 'embed', 'bidi-override'],
+        'vertical-align': [
+            'baseline', 'bottom', 'sub', 'super', 'top', 'text-top', 'middle',
+            'text-bottom', cssLength
+        ],
+        visibility: ['visible', 'hidden', 'collapse'],
+        'white-space': [
+            'normal', 'nowrap', 'pre', 'pre-line', 'pre-wrap', 'inherit'
+        ],
+        width: [cssLength, 'auto'],
+        'word-spacing': ['normal', cssLength],
+        'word-wrap': ['break-word', 'normal'],
+        'z-index': ['auto', cssNumber]
+    };
+
+    function styleAttribute() {
+        var v;
+        while (nexttoken.id === '*' || nexttoken.id === '#' ||
+                nexttoken.value === '_') {
+            if (!option.css) {
+                warning("Unexpected '{a}'.", nexttoken, nexttoken.value);
+            }
+            advance();
+        }
+        if (nexttoken.id === '-') {
+            if (!option.css) {
+                warning("Unexpected '{a}'.", nexttoken, nexttoken.value);
+            }
+            advance('-');
+            if (!nexttoken.identifier) {
+                warning(
+"Expected a non-standard style attribute and instead saw '{a}'.",
+                    nexttoken, nexttoken.value);
+            }
+            advance();
+            return cssAny;
+        } else {
+            if (!nexttoken.identifier) {
+                warning("Excepted a style attribute, and instead saw '{a}'.",
+                    nexttoken, nexttoken.value);
+            } else {
+                if (is_own(cssAttributeData, nexttoken.value)) {
+                    v = cssAttributeData[nexttoken.value];
+                } else {
+                    v = cssAny;
+                    if (!option.css) {
+                        warning("Unrecognized style attribute '{a}'.",
+                                nexttoken, nexttoken.value);
+                    }
+                }
+            }
+            advance();
+            return v;
+        }
+    }
+
+    function styleValue(v) {
+        var i = 0,
+            n,
+            once,
+            match,
+            round,
+            start = 0,
+            vi;
+        switch (typeof v) {
+        case 'function':
+            return v();
+        case 'string':
+            if (nexttoken.identifier && nexttoken.value === v) {
+                advance();
+                return true;
+            }
+            return false;
+        }
+        for (;;) {
+            if (i >= v.length) {
+                return false;
+            }
+            vi = v[i];
+            i += 1;
+            if (vi === true) {
+                break;
+            } else if (typeof vi === 'number') {
+                n = vi;
+                vi = v[i];
+                i += 1;
+            } else {
+                n = 1;
+            }
+            match = false;
+            while (n > 0) {
+                if (styleValue(vi)) {
+                    match = true;
+                    n -= 1;
+                } else {
+                    break;
+                }
+            }
+            if (match) {
+                return true;
+            }
+        }
+        start = i;
+        once = [];
+        for (;;) {
+            round = false;
+            for (i = start; i < v.length; i += 1) {
+                if (!once[i]) {
+                    if (styleValue(cssAttributeData[v[i]])) {
+                        match = true;
+                        round = true;
+                        once[i] = true;
+                        break;
+                    }
+                }
+            }
+            if (!round) {
+                return match;
+            }
+        }
+    }
+
+    function styleChild() {
+        if (nexttoken.id === '(number)') {
+            advance();
+            if (nexttoken.value === 'n' && nexttoken.identifier) {
+                adjacent();
+                advance();
+                if (nexttoken.id === '+') {
+                    adjacent();
+                    advance('+');
+                    adjacent();
+                    advance('(number)');
+                }
+            }
+            return;
+        } else {
+            switch (nexttoken.value) {
+            case 'odd':
+            case 'even':
+                if (nexttoken.identifier) {
+                    advance();
+                    return;
+                }
+            }
+        }
+        warning("Unexpected token '{a}'.", nexttoken, nexttoken.value);
+    }
+
+    function substyle() {
+        var v;
+        for (;;) {
+            if (nexttoken.id === '}' || nexttoken.id === '(end)' ||
+                    xquote && nexttoken.id === xquote) {
+                return;
+            }
+            while (nexttoken.id === ';') {
+                warning("Misplaced ';'.");
+                advance(';');
+            }
+            v = styleAttribute();
+            advance(':');
+            if (nexttoken.identifier && nexttoken.value === 'inherit') {
+                advance();
+            } else {
+                if (!styleValue(v)) {
+                    warning("Unexpected token '{a}'.", nexttoken,
+                        nexttoken.value);
+                    advance();
+                }
+            }
+            if (nexttoken.id === '!') {
+                advance('!');
+                adjacent();
+                if (nexttoken.identifier && nexttoken.value === 'important') {
+                    advance();
+                } else {
+                    warning("Expected '{a}' and instead saw '{b}'.",
+                        nexttoken, 'important', nexttoken.value);
+                }
+            }
+            if (nexttoken.id === '}' || nexttoken.id === xquote) {
+                warning("Missing '{a}'.", nexttoken, ';');
+            } else {
+                advance(';');
+            }
+        }
+    }
+
+    function styleSelector() {
+        if (nexttoken.identifier) {
+            if (!is_own(htmltag, nexttoken.value)) {
+                warning("Expected a tagName, and instead saw {a}.",
+                    nexttoken, nexttoken.value);
+            }
+            advance();
+        } else {
+            switch (nexttoken.id) {
+            case '>':
+            case '+':
+                advance();
+                styleSelector();
+                break;
+            case ':':
+                advance(':');
+                switch (nexttoken.value) {
+                case 'active':
+                case 'after':
+                case 'before':
+                case 'checked':
+                case 'disabled':
+                case 'empty':
+                case 'enabled':
+                case 'first-child':
+                case 'first-letter':
+                case 'first-line':
+                case 'first-of-type':
+                case 'focus':
+                case 'hover':
+                case 'last-of-type':
+                case 'link':
+                case 'only-of-type':
+                case 'root':
+                case 'target':
+                case 'visited':
+                    advance();
+                    break;
+                case 'lang':
+                    advance();
+                    advance('(');
+                    if (!nexttoken.identifier) {
+                        warning("Expected a lang code, and instead saw :{a}.",
+                            nexttoken, nexttoken.value);
+                    }
+                    advance(')');
+                    break;
+                case 'nth-child':
+                case 'nth-last-child':
+                case 'nth-last-of-type':
+                case 'nth-of-type':
+                    advance();
+                    advance('(');
+                    styleChild();
+                    advance(')');
+                    break;
+                case 'not':
+                    advance();
+                    advance('(');
+                    if (nexttoken.id === ':' && peek(0).value === 'not') {
+                        warning("Nested not.");
+                    }
+                    styleSelector();
+                    advance(')');
+                    break;
+                default:
+                    warning("Expected a pseudo, and instead saw :{a}.",
+                        nexttoken, nexttoken.value);
+                }
+                break;
+            case '#':
+                advance('#');
+                if (!nexttoken.identifier) {
+                    warning("Expected an id, and instead saw #{a}.",
+                        nexttoken, nexttoken.value);
+                }
+                advance();
+                break;
+            case '*':
+                advance('*');
+                break;
+            case '.':
+                advance('.');
+                if (!nexttoken.identifier) {
+                    warning("Expected a class, and instead saw #.{a}.",
+                        nexttoken, nexttoken.value);
+                }
+                advance();
+                break;
+            case '[':
+                advance('[');
+                if (!nexttoken.identifier) {
+                    warning("Expected an attribute, and instead saw [{a}].",
+                        nexttoken, nexttoken.value);
+                }
+                advance();
+                if (nexttoken.id === '=' || nexttoken.value === '~=' ||
+                        nexttoken.value === '$=' ||
+                        nexttoken.value === '|=' ||
+                        nexttoken.id === '*=' ||
+                        nexttoken.id === '^=') {
+                    advance();
+                    if (nexttoken.type !== '(string)') {
+                        warning("Expected a string, and instead saw {a}.",
+                            nexttoken, nexttoken.value);
+                    }
+                    advance();
+                }
+                advance(']');
+                break;
+            default:
+                error("Expected a CSS selector, and instead saw {a}.",
+                    nexttoken, nexttoken.value);
+            }
+        }
+    }
+
+    function stylePattern() {
+        var name;
+        if (nexttoken.id === '{') {
+            warning("Expected a style pattern, and instead saw '{a}'.", nexttoken,
+                nexttoken.id);
+        } else if (nexttoken.id === '@') {
+            advance('@');
+            name = nexttoken.value;
+            if (nexttoken.identifier && atrule[name] === true) {
+                advance();
+                return name;
+            }
+            warning("Expected an at-rule, and instead saw @{a}.", nexttoken, name);
+        }
+        for (;;) {
+            styleSelector();
+            if (nexttoken.id === '</' || nexttoken.id === '{' ||
+                    nexttoken.id === '(end)') {
+                return '';
+            }
+            if (nexttoken.id === ',') {
+                comma();
+            }
+        }
+    }
+
+    function styles() {
+        var i;
+        while (nexttoken.id === '@') {
+            i = peek();
+            if (i.identifier && i.value === 'import') {
+                advance('@');
+                advance();
+                if (!cssUrl()) {
+                    warning("Expected '{a}' and instead saw '{b}'.", nexttoken,
+                        'url', nexttoken.value);
+                    advance();
+                }
+                advance(';');
+            } else {
+                break;
+            }
+        }
+        while (nexttoken.id !== '</' && nexttoken.id !== '(end)') {
+            stylePattern();
+            xmode = 'styleproperty';
+            if (nexttoken.id === ';') {
+                advance(';');
+            } else {
+                advance('{');
+                substyle();
+                xmode = 'style';
+                advance('}');
+            }
+        }
+    }
+
+
+// HTML parsing.
+
+    function doBegin(n) {
+        if (n !== 'html' && !option.fragment) {
+            if (n === 'div' && option.adsafe) {
+                error("ADSAFE: Use the fragment option.");
+            } else {
+                error("Expected '{a}' and instead saw '{b}'.",
+                    token, 'html', n);
+            }
+        }
+        if (option.adsafe) {
+            if (n === 'html') {
+                error(
+"Currently, ADsafe does not operate on whole HTML documents. It operates on <div> fragments and .js files.", token);
+            }
+            if (option.fragment) {
+                if (n !== 'div') {
+                    error("ADsafe violation: Wrap the widget in a div.", token);
+                }
+            } else {
+                error("Use the fragment option.", token);
+            }
+        }
+        option.browser = true;
+        assume();
+    }
+
+    function doAttribute(n, a, v) {
+        var u, x;
+        if (a === 'id') {
+            u = typeof v === 'string' ? v.toUpperCase() : '';
+            if (ids[u] === true) {
+                warning("Duplicate id='{a}'.", nexttoken, v);
+            }
+            if (!/^[A-Za-z][A-Za-z0-9._:\-]*$/.test(v)) {
+                warning("Bad id: '{a}'.", nexttoken, v);
+            } else if (option.adsafe) {
+                if (adsafe_id) {
+                    if (v.slice(0, adsafe_id.length) !== adsafe_id) {
+                        warning("ADsafe violation: An id must have a '{a}' prefix",
+                                nexttoken, adsafe_id);
+                    } else if (!/^[A-Z]+_[A-Z]+$/.test(v)) {
+                        warning("ADSAFE violation: bad id.");
+                    }
+                } else {
+                    adsafe_id = v;
+                    if (!/^[A-Z]+_$/.test(v)) {
+                        warning("ADSAFE violation: bad id.");
+                    }
+                }
+            }  
+            x = v.search(dx);
+            if (x >= 0) {
+                warning("Unexpected character '{a}' in {b}.", token, v.charAt(x), a);
+            }
+            ids[u] = true;
+        } else if (a === 'class' || a === 'type' || a === 'name') {
+            x = v.search(qx);
+            if (x >= 0) {
+                warning("Unexpected character '{a}' in {b}.", token, v.charAt(x), a);
+            }
+            ids[u] = true;
+        } else if (a === 'href' || a === 'background' ||
+                a === 'content' || a === 'data' ||
+                a.indexOf('src') >= 0 || a.indexOf('url') >= 0) {
+            if (option.safe && ux.test(v)) {
+                error("ADsafe URL violation.");
+            }
+            urls.push(v);
+        } else if (a === 'for') {
+            if (option.adsafe) {
+                if (adsafe_id) {
+                    if (v.slice(0, adsafe_id.length) !== adsafe_id) {
+                        warning("ADsafe violation: An id must have a '{a}' prefix",
+                                nexttoken, adsafe_id);
+                    } else if (!/^[A-Z]+_[A-Z]+$/.test(v)) {
+                        warning("ADSAFE violation: bad id.");
+                    }
+                } else {
+                    warning("ADSAFE violation: bad id.");
+                }
+            }
+        } else if (a === 'name') {
+            if (option.adsafe && v.indexOf('_') >= 0) {
+                warning("ADsafe name violation.");
+            }
+        }
+    }
+
+    function doTag(n, a) {
+        var i, t = htmltag[n], x;
+        src = false;
+        if (!t) {
+            error("Unrecognized tag '<{a}>'.",
+                    nexttoken,
+                    n === n.toLowerCase() ? n :
+                        n + ' (capitalization error)');
+        }
+        if (stack.length > 0) {
+            if (n === 'html') {
+                error("Too many <html> tags.", token);
+            }
+            x = t.parent;
+            if (x) {
+                if (x.indexOf(' ' + stack[stack.length - 1].name + ' ') < 0) {
+                    error("A '<{a}>' must be within '<{b}>'.",
+                            token, n, x);
+                }
+            } else if (!option.adsafe && !option.fragment) {
+                i = stack.length;
+                do {
+                    if (i <= 0) {
+                        error("A '<{a}>' must be within '<{b}>'.",
+                                token, n, 'body');
+                    }
+                    i -= 1;
+                } while (stack[i].name !== 'body');
+            }
+        }
+        switch (n) {
+        case 'div':
+            if (option.adsafe && stack.length === 1 && !adsafe_id) {
+                warning("ADSAFE violation: missing ID_.");
+            }
+            break;
+        case 'script':
+            xmode = 'script';
+            advance('>');
+            indent = nexttoken.from;
+            if (a.lang) {
+                warning("lang is deprecated.", token);
+            }
+            if (option.adsafe && stack.length !== 1) {
+                warning("ADsafe script placement violation.", token);
+            }
+            if (a.src) {
+                if (option.adsafe && (!adsafe_may || !approved[a.src])) {
+                    warning("ADsafe unapproved script source.", token);
+                }
+                if (a.type) {
+                    warning("type is unnecessary.", token);
+                }
+            } else {
+                if (adsafe_went) {
+                    error("ADsafe script violation.", token);
+                }
+                statements('script');
+            }
+            xmode = 'html';
+            advance('</');
+            if (!nexttoken.identifier && nexttoken.value !== 'script') {
+                warning("Expected '{a}' and instead saw '{b}'.",
+                        nexttoken, 'script', nexttoken.value);
+            }
+            advance();
+            xmode = 'outer';
+            break;
+        case 'style':
+            xmode = 'style';
+            advance('>');
+            styles();
+            xmode = 'html';
+            advance('</');
+            if (!nexttoken.identifier && nexttoken.value !== 'style') {
+                warning("Expected '{a}' and instead saw '{b}'.",
+                        nexttoken, 'style', nexttoken.value);
+            }
+            advance();
+            xmode = 'outer';
+            break;
+        case 'input':
+            switch (a.type) {
+            case 'radio':
+            case 'checkbox':
+            case 'button':
+            case 'reset':
+            case 'submit':
+                break;
+            case 'text':
+            case 'file':
+            case 'password':
+            case 'file':
+            case 'hidden':
+            case 'image':
+                if (option.adsafe && a.autocomplete !== 'off') {
+                    warning("ADsafe autocomplete violation.");
+                }
+                break;
+            default:
+                warning("Bad input type.");
+            }
+            break;
+        case 'applet':
+        case 'body':
+        case 'embed':
+        case 'frame':
+        case 'frameset':
+        case 'head':
+        case 'iframe':
+        case 'noembed':
+        case 'noframes':
+        case 'object':
+        case 'param':
+            if (option.adsafe) {
+                warning("ADsafe violation: Disallowed tag: " + n);
+            }
+            break;
+        }
+    }
+
+
+    function closetag(n) {
+        return '</' + n + '>';
+    }
+
+    function html() {
+        var a, attributes, e, n, q, t, v, w = option.white, wmode;
+        xmode = 'html';
+        xquote = '';
+        stack = null;
+        for (;;) {
+            switch (nexttoken.value) {
+            case '<':
+                xmode = 'html';
+                advance('<');
+                attributes = {};
+                t = nexttoken;
+                if (!t.identifier) {
+                    warning("Bad identifier {a}.", t, t.value);
+                }
+                n = t.value;
+                if (option.cap) {
+                    n = n.toLowerCase();
+                }
+                t.name = n;
+                advance();
+                if (!stack) {
+                    stack = [];
+                    doBegin(n);
+                }
+                v = htmltag[n];
+                if (typeof v !== 'object') {
+                    error("Unrecognized tag '<{a}>'.", t, n);
+                }
+                e = v.empty;
+                t.type = n;
+                for (;;) {
+                    if (nexttoken.id === '/') {
+                        advance('/');
+                        if (nexttoken.id !== '>') {
+                            warning("Expected '{a}' and instead saw '{b}'.",
+                                    nexttoken, '>', nexttoken.value);
+                        }
+                        break;
+                    }
+                    if (nexttoken.id && nexttoken.id.substr(0, 1) === '>') {
+                        break;
+                    }
+                    if (!nexttoken.identifier) {
+                        if (nexttoken.id === '(end)' || nexttoken.id === '(error)') {
+                            error("Missing '>'.", nexttoken);
+                        }
+                        warning("Bad identifier.");
+                    }
+                    option.white = true;
+                    nonadjacent(token, nexttoken);
+                    a = nexttoken.value;
+                    option.white = w;
+                    advance();
+                    if (!option.cap && a !== a.toLowerCase()) {
+                        warning("Attribute '{a}' not all lower case.", nexttoken, a);
+                    }
+                    a = a.toLowerCase();
+                    xquote = '';
+                    if (is_own(attributes, a)) {
+                        warning("Attribute '{a}' repeated.", nexttoken, a);
+                    }
+                    if (a.slice(0, 2) === 'on') {
+                        if (!option.on) {
+                            warning("Avoid HTML event handlers.");
+                        }
+                        xmode = 'scriptstring';
+                        advance('=');
+                        q = nexttoken.id;
+                        if (q !== '"' && q !== "'") {
+                            error("Missing quote.");
+                        }
+                        xquote = q;
+                        wmode = option.white;
+                        option.white = false;
+                        advance(q);
+                        statements('on');
+                        option.white = wmode;
+                        if (nexttoken.id !== q) {
+                            error("Missing close quote on script attribute.");
+                        }
+                        xmode = 'html';
+                        xquote = '';
+                        advance(q);
+                        v = false;
+                    } else if (a === 'style') {
+                        xmode = 'scriptstring';
+                        advance('=');
+                        q = nexttoken.id;
+                        if (q !== '"' && q !== "'") {
+                            error("Missing quote.");
+                        }
+                        xmode = 'styleproperty';
+                        xquote = q;
+                        advance(q);
+                        substyle();
+                        xmode = 'html';
+                        xquote = '';
+                        advance(q);
+                        v = false;
+                    } else {
+                        if (nexttoken.id === '=') {
+                            advance('=');
+                            v = nexttoken.value;
+                            if (!nexttoken.identifier &&
+                                    nexttoken.id !== '"' &&
+                                    nexttoken.id !== '\'' &&
+                                    nexttoken.type !== '(string)' &&
+                                    nexttoken.type !== '(number)' &&
+                                    nexttoken.type !== '(color)') {
+                                warning("Expected an attribute value and instead saw '{a}'.", token, a);
+                            }
+                            advance();
+                        } else {
+                            v = true;
+                        }
+                    }
+                    attributes[a] = v;
+                    doAttribute(n, a, v);
+                }
+                doTag(n, attributes);
+                if (!e) {
+                    stack.push(t);
+                }
+                xmode = 'outer';
+                advance('>');
+                break;
+            case '</':
+                xmode = 'html';
+                advance('</');
+                if (!nexttoken.identifier) {
+                    warning("Bad identifier.");
+                }
+                n = nexttoken.value;
+                if (option.cap) {
+                    n = n.toLowerCase();
+                }
+                advance();
+                if (!stack) {
+                    error("Unexpected '{a}'.", nexttoken, closetag(n));
+                }
+                t = stack.pop();
+                if (!t) {
+                    error("Unexpected '{a}'.", nexttoken, closetag(n));
+                }
+                if (t.name !== n) {
+                    error("Expected '{a}' and instead saw '{b}'.",
+                            nexttoken, closetag(t.name), closetag(n));
+                }
+                if (nexttoken.id !== '>') {
+                    error("Missing '{a}'.", nexttoken, '>');
+                }
+                xmode = 'outer';
+                advance('>');
+                break;
+            case '<!':
+                if (option.safe) {
+                    warning("ADsafe HTML violation.");
+                }
+                xmode = 'html';
+                for (;;) {
+                    advance();
+                    if (nexttoken.id === '>' || nexttoken.id === '(end)') {
+                        break;
+                    }
+                    if (nexttoken.value.indexOf('--') >= 0) {
+                        warning("Unexpected --.");
+                    }
+                    if (nexttoken.value.indexOf('<') >= 0) {
+                        warning("Unexpected <.");
+                    }
+                    if (nexttoken.value.indexOf('>') >= 0) {
+                        warning("Unexpected >.");
+                    }
+                }
+                xmode = 'outer';
+                advance('>');
+                break;
+            case '(end)':
+                return;
+            default:
+                if (nexttoken.id === '(end)') {
+                    error("Missing '{a}'.", nexttoken,
+                            '</' + stack[stack.length - 1].value + '>');
+                } else {
+                    advance();
+                }
+            }
+            if (stack && stack.length === 0 && (option.adsafe ||
+                    !option.fragment || nexttoken.id === '(end)')) {
+                break;
+            }
+        }
+        if (nexttoken.id !== '(end)') {
+            error("Unexpected material after the end.");
+        }
+    }
+
+
+// Build the syntax table by declaring the syntactic elements of the language.
+
+    type('(number)', idValue);
+    type('(string)', idValue);
+
+    syntax['(identifier)'] = {
+        type: '(identifier)',
+        lbp: 0,
+        identifier: true,
+        nud: function () {
+            var v = this.value,
+                s = scope[v],
+                f;
+            if (typeof s === 'function') {
+                s = undefined;
+            } else if (typeof s === 'boolean') {
+                f = funct;
+                funct = functions[0];
+                addlabel(v, 'var');
+                s = funct;
+                funct = f;
+            }
+
+// The name is in scope and defined in the current function.
+
+            if (funct === s) {
+
+//      Change 'unused' to 'var', and reject labels.
+
+                switch (funct[v]) {
+                case 'unused':
+                    funct[v] = 'var';
+                    break;
+                case 'label':
+                    warning("'{a}' is a statement label.", token, v);
+                    break;
+                }
+
+// The name is not defined in the function.  If we are in the global scope,
+// then we have an undefined variable.
+
+            } else if (funct['(global)']) {
+                if (option.undef && predefined[v] !== 'boolean') {
+                    warning("'{a}' is not defined.", token, v);
+                }
+                note_implied(token);
+
+// If the name is already defined in the current
+// function, but not as outer, then there is a scope error.
+
+            } else {
+                switch (funct[v]) {
+                case 'closure':
+                case 'function':
+                case 'var':
+                case 'unused':
+                    warning("'{a}' used out of scope.", token, v);
+                    break;
+                case 'label':
+                    warning("'{a}' is a statement label.", token, v);
+                    break;
+                case 'outer':
+                case 'global':
+                    break;
+                default:
+
+// If the name is defined in an outer function, make an outer entry, and if
+// it was unused, make it var.
+
+                    if (s === true) {
+                        funct[v] = true;
+                    } else if (s === null) {
+                        warning("'{a}' is not allowed.", token, v);
+                        note_implied(token);
+                    } else if (typeof s !== 'object') {
+                        if (option.undef) {
+                            warning("'{a}' is not defined.", token, v);
+                        } else {
+                            funct[v] = true;
+                        }
+                        note_implied(token);
+                    } else {
+                        switch (s[v]) {
+                        case 'function':
+                        case 'var':
+                        case 'unused':
+                            s[v] = 'closure';
+                            funct[v] = s['(global)'] ? 'global' : 'outer';
+                            break;
+                        case 'closure':
+                        case 'parameter':
+                            funct[v] = s['(global)'] ? 'global' : 'outer';
+                            break;
+                        case 'label':
+                            warning("'{a}' is a statement label.", token, v);
+                        }
+                    }
+                }
+            }
+            return this;
+        },
+        led: function () {
+            error("Expected an operator and instead saw '{a}'.",
+                    nexttoken, nexttoken.value);
+        }
+    };
+
+    type('(regexp)', function () {
+        return this;
+    });
+
+    delim('(endline)');
+    delim('(begin)');
+    delim('(end)').reach = true;
+    delim('</').reach = true;
+    delim('<!');
+    delim('<!--');
+    delim('-->');
+    delim('(error)').reach = true;
+    delim('}').reach = true;
+    delim(')');
+    delim(']');
+    delim('"').reach = true;
+    delim("'").reach = true;
+    delim(';');
+    delim(':').reach = true;
+    delim(',');
+    delim('#');
+    delim('@');
+    reserve('else');
+    reserve('case').reach = true;
+    reserve('catch');
+    reserve('default').reach = true;
+    reserve('finally');
+    reservevar('arguments');
+    reservevar('eval');
+    reservevar('false');
+    reservevar('Infinity');
+    reservevar('NaN');
+    reservevar('null');
+    reservevar('this');
+    reservevar('true');
+    reservevar('undefined');
+    assignop('=', 'assign', 20);
+    assignop('+=', 'assignadd', 20);
+    assignop('-=', 'assignsub', 20);
+    assignop('*=', 'assignmult', 20);
+    assignop('/=', 'assigndiv', 20).nud = function () {
+        error("A regular expression literal can be confused with '/='.");
+    };
+    assignop('%=', 'assignmod', 20);
+    bitwiseassignop('&=', 'assignbitand', 20);
+    bitwiseassignop('|=', 'assignbitor', 20);
+    bitwiseassignop('^=', 'assignbitxor', 20);
+    bitwiseassignop('<<=', 'assignshiftleft', 20);
+    bitwiseassignop('>>=', 'assignshiftright', 20);
+    bitwiseassignop('>>>=', 'assignshiftrightunsigned', 20);
+    infix('?', function (left, that) {
+        that.left = left;
+        that.right = parse(10);
+        advance(':');
+        that['else'] = parse(10);
+        return that;
+    }, 30);
+
+    infix('||', 'or', 40);
+    infix('&&', 'and', 50);
+    bitwise('|', 'bitor', 70);
+    bitwise('^', 'bitxor', 80);
+    bitwise('&', 'bitand', 90);
+    relation('==', function (left, right) {
+        if (option.eqeqeq) {
+            warning("Expected '{a}' and instead saw '{b}'.",
+                    this, '===', '==');
+        } else if (isPoorRelation(left)) {
+            warning("Use '{a}' to compare with '{b}'.",
+                this, '===', left.value);
+        } else if (isPoorRelation(right)) {
+            warning("Use '{a}' to compare with '{b}'.",
+                this, '===', right.value);
+        }
+        return this;
+    });
+    relation('===');
+    relation('!=', function (left, right) {
+        if (option.eqeqeq) {
+            warning("Expected '{a}' and instead saw '{b}'.",
+                    this, '!==', '!=');
+        } else if (isPoorRelation(left)) {
+            warning("Use '{a}' to compare with '{b}'.",
+                    this, '!==', left.value);
+        } else if (isPoorRelation(right)) {
+            warning("Use '{a}' to compare with '{b}'.",
+                    this, '!==', right.value);
+        }
+        return this;
+    });
+    relation('!==');
+    relation('<');
+    relation('>');
+    relation('<=');
+    relation('>=');
+    bitwise('<<', 'shiftleft', 120);
+    bitwise('>>', 'shiftright', 120);
+    bitwise('>>>', 'shiftrightunsigned', 120);
+    infix('in', 'in', 120);
+    infix('instanceof', 'instanceof', 120);
+    infix('+', function (left, that) {
+        var right = parse(130);
+        if (left && right && left.id === '(string)' && right.id === '(string)') {
+            left.value += right.value;
+            left.character = right.character;
+            if (jx.test(left.value)) {
+                warning("JavaScript URL.", left);
+            }
+            return left;
+        }
+        that.left = left;
+        that.right = right;
+        return that;
+    }, 130);
+    prefix('+', 'num');
+    infix('-', 'sub', 130);
+    prefix('-', 'neg');
+    infix('*', 'mult', 140);
+    infix('/', 'div', 140);
+    infix('%', 'mod', 140);
+
+    suffix('++', 'postinc');
+    prefix('++', 'preinc');
+    syntax['++'].exps = true;
+
+    suffix('--', 'postdec');
+    prefix('--', 'predec');
+    syntax['--'].exps = true;
+    prefix('delete', function () {
+        var p = parse(0);
+        if (!p || (p.id !== '.' && p.id !== '[')) {
+            warning("Expected '{a}' and instead saw '{b}'.",
+                    nexttoken, '.', nexttoken.value);
+        }
+        this.first = p;
+        return this;
+    }).exps = true;
+
+
+    prefix('~', function () {
+        if (option.bitwise) {
+            warning("Unexpected '{a}'.", this, '~');
+        }
+        parse(150);
+        return this;
+    });
+    prefix('!', function () {
+        this.right = parse(150);
+        this.arity = 'unary';
+        if (bang[this.right.id] === true) {
+            warning("Confusing use of '{a}'.", this, '!');
+        }
+        return this;
+    });
+    prefix('typeof', 'typeof');
+    prefix('new', function () {
+        var c = parse(155), i;
+        if (c && c.id !== 'function') {
+            if (c.identifier) {
+                c['new'] = true;
+                switch (c.value) {
+                case 'Object':
+                    warning("Use the object literal notation {}.", token);
+                    break;
+                case 'Array':
+                    if (nexttoken.id !== '(') {
+                        warning("Use the array literal notation [].", token);
+                    } else {
+                        advance('(');
+                        if (nexttoken.id === ')') {
+                            warning("Use the array literal notation [].", token);
+                        } else {
+                            i = parse(0);
+                            c.dimension = i;
+                            if ((i.id === '(number)' && /[.+\-Ee]/.test(i.value)) ||
+                                    (i.id === '-' && !i.right) ||
+                                    i.id === '(string)' || i.id === '[' ||
+                                    i.id === '{' || i.id === 'true' ||
+                                    i.id === 'false' ||
+                                    i.id === 'null' || i.id === 'undefined' ||
+                                    i.id === 'Infinity') {
+                                warning("Use the array literal notation [].", token);
+                            }
+                            if (nexttoken.id !== ')') {
+                                error("Use the array literal notation [].", token);
+                            }
+                        }
+                        advance(')');
+                    }
+                    this.first = c;
+                    return this;
+                case 'Number':
+                case 'String':
+                case 'Boolean':
+                case 'Math':
+                case 'JSON':
+                    warning("Do not use {a} as a constructor.", token, c.value);
+                    break;
+                case 'Function':
+                    if (!option.evil) {
+                        warning("The Function constructor is eval.");
+                    }
+                    break;
+                case 'Date':
+                case 'RegExp':
+                    break;
+                default:
+                    if (c.id !== 'function') {
+                        i = c.value.substr(0, 1);
+                        if (option.newcap && (i < 'A' || i > 'Z')) {
+                            warning(
+                    "A constructor name should start with an uppercase letter.",
+                                token);
+                        }
+                    }
+                }
+            } else {
+                if (c.id !== '.' && c.id !== '[' && c.id !== '(') {
+                    warning("Bad constructor.", token);
+                }
+            }
+        } else {
+            warning("Weird construction. Delete 'new'.", this);
+        }
+        adjacent(token, nexttoken);
+        if (nexttoken.id !== '(') {
+            warning("Missing '()' invoking a constructor.");
+        }
+        this.first = c;
+        return this;
+    });
+    syntax['new'].exps = true;
+
+    infix('.', function (left, that) {
+        adjacent(prevtoken, token);
+        var m = identifier();
+        if (typeof m === 'string') {
+            countMember(m);
+        }
+        that.left = left;
+        that.right = m;
+        if (!option.evil && left && left.value === 'document' &&
+                (m === 'write' || m === 'writeln')) {
+            warning("document.write can be a form of eval.", left);
+        } else if (option.adsafe) {
+            if (left && left.value === 'ADSAFE') {
+                if (m === 'id' || m === 'lib') {
+                    warning("ADsafe violation.", that);
+                } else if (m === 'go') {
+                    if (xmode !== 'script') {
+                        warning("ADsafe violation.", that);
+                    } else if (adsafe_went || nexttoken.id !== '(' ||
+                            peek(0).id !== '(string)' ||
+                            peek(0).value !== adsafe_id ||
+                            peek(1).id !== ',') {
+                        error("ADsafe violation: go.", that);
+                    }
+                    adsafe_went = true;
+                    adsafe_may = false;
+                }
+            }
+        }
+        if (!option.evil && (m === 'eval' || m === 'execScript')) {
+            warning('eval is evil.');
+        } else if (option.safe) {
+            for (;;) {
+                if (banned[m] === true) {
+                    warning("ADsafe restricted word '{a}'.", token, m);
+                }
+                if (typeof predefined[left.value] !== 'boolean' ||
+                        nexttoken.id === '(') {
+                    break;
+                }
+                if (standard_member[m] === true) {
+                    if (nexttoken.id === '.') {
+                        warning("ADsafe violation.", that);
+                    }
+                    break;
+                }
+                if (nexttoken.id !== '.') {
+                    warning("ADsafe violation.", that);
+                    break;
+                }
+                advance('.');
+                token.left = that;
+                token.right = m;
+                that = token;
+                m = identifier();
+                if (typeof m === 'string') {
+                    countMember(m);
+                }
+            }
+        }
+        return that;
+    }, 160, true);
+
+    infix('(', function (left, that) {
+        adjacent(prevtoken, token);
+        nospace();
+        var n = 0,
+            p = [];
+        if (left) {
+            if (left.type === '(identifier)') {
+                if (left.value.match(/^[A-Z]([A-Z0-9_$]*[a-z][A-Za-z0-9_$]*)?$/)) {
+                    if (left.value !== 'Number' && left.value !== 'String' &&
+                            left.value !== 'Boolean' &&
+                            left.value !== 'Date') {
+                        if (left.value === 'Math') {
+                            warning("Math is not a function.", left);
+                        } else if (option.newcap) {
+                            warning(
+"Missing 'new' prefix when invoking a constructor.", left);
+                        }
+                    }
+                }
+            } else if (left.id === '.') {
+                if (option.safe && left.left.value === 'Math' &&
+                        left.right === 'random') {
+                    warning("ADsafe violation.", left);
+                }
+            }
+        }
+        if (nexttoken.id !== ')') {
+            for (;;) {
+                p[p.length] = parse(10);
+                n += 1;
+                if (nexttoken.id !== ',') {
+                    break;
+                }
+                comma();
+            }
+        }
+        advance(')');
+        if (option.immed && left.id === 'function' && nexttoken.id !== ')') {
+            warning("Wrap the entire immediate function invocation in parens.",
+                that);
+        }
+        nospace(prevtoken, token);
+        if (typeof left === 'object') {
+            if (left.value === 'parseInt' && n === 1) {
+                warning("Missing radix parameter.", left);
+            }
+            if (!option.evil) {
+                if (left.value === 'eval' || left.value === 'Function' ||
+                        left.value === 'execScript') {
+                    warning("eval is evil.", left);
+                } else if (p[0] && p[0].id === '(string)' &&
+                       (left.value === 'setTimeout' ||
+                        left.value === 'setInterval')) {
+                    warning(
+    "Implied eval is evil. Pass a function instead of a string.", left);
+                }
+            }
+            if (!left.identifier && left.id !== '.' && left.id !== '[' &&
+                    left.id !== '(' && left.id !== '&&' && left.id !== '||' &&
+                    left.id !== '?') {
+                warning("Bad invocation.", left);
+            }
+        }
+        that.left = left;
+        return that;
+    }, 155, true).exps = true;
+
+    prefix('(', function () {
+        nospace();
+        var v = parse(0);
+        advance(')', this);
+        nospace(prevtoken, token);
+        if (option.immed && v.id === 'function') {
+            if (nexttoken.id === '(') {
+                warning(
+"Move the invocation into the parens that contain the function.", nexttoken);
+            } else {
+                warning(
+"Do not wrap function literals in parens unless they are to be immediately invoked.",
+                        this);
+            }
+        }
+        return v;
+    });
+
+    infix('[', function (left, that) {
+        nospace();
+        var e = parse(0), s;
+        if (e && e.type === '(string)') {
+            if (option.safe && banned[e.value] === true) {
+                warning("ADsafe restricted word '{a}'.", that, e.value);
+            } else if (!option.evil &&
+                    (e.value === 'eval' || e.value === 'execScript')) {
+                warning("eval is evil.", that);
+            } else if (option.safe &&
+                    (e.value.charAt(0) === '_' || e.value.charAt(0) === '-')) {
+                warning("ADsafe restricted subscript '{a}'.", that, e.value);
+            }
+            countMember(e.value);
+            if (!option.sub && ix.test(e.value)) {
+                s = syntax[e.value];
+                if (!s || !s.reserved) {
+                    warning("['{a}'] is better written in dot notation.",
+                            e, e.value);
+                }
+            }
+        } else if (!e || e.type !== '(number)' || e.value < 0) {
+            if (option.safe) {
+                warning('ADsafe subscripting.');
+            }
+        }
+        advance(']', that);
+        nospace(prevtoken, token);
+        that.left = left;
+        that.right = e;
+        return that;
+    }, 160, true);
+
+    prefix('[', function () {
+        var b = token.line !== nexttoken.line;
+        this.first = [];
+        if (b) {
+            indent += option.indent;
+            if (nexttoken.from === indent + option.indent) {
+                indent += option.indent;
+            }
+        }
+        while (nexttoken.id !== '(end)') {
+            while (nexttoken.id === ',') {
+                warning("Extra comma.");
+                advance(',');
+            }
+            if (nexttoken.id === ']') {
+                break;
+            }
+            if (b && token.line !== nexttoken.line) {
+                indentation();
+            }
+            this.first.push(parse(10));
+            if (nexttoken.id === ',') {
+                comma();
+                if (nexttoken.id === ']') {
+                    warning("Extra comma.", token);
+                    break;
+                }
+            } else {
+                break;
+            }
+        }
+        if (b) {
+            indent -= option.indent;
+            indentation();
+        }
+        advance(']', this);
+        return this;
+    }, 160);
+
+    (function (x) {
+        x.nud = function () {
+            var b, i, s, seen = {};
+            b = token.line !== nexttoken.line;
+            if (b) {
+                indent += option.indent;
+                if (nexttoken.from === indent + option.indent) {
+                    indent += option.indent;
+                }
+            }
+            for (;;) {
+                if (nexttoken.id === '}') {
+                    break;
+                }
+                if (b) {
+                    indentation();
+                }
+                i = optionalidentifier(true);
+                if (!i) {
+                    if (nexttoken.id === '(string)') {
+                        i = nexttoken.value;
+                        if (ix.test(i)) {
+                            s = syntax[i];
+                        }
+                        advance();
+                    } else if (nexttoken.id === '(number)') {
+                        i = nexttoken.value.toString();
+                        advance();
+                    } else {
+                        error("Expected '{a}' and instead saw '{b}'.",
+                                nexttoken, '}', nexttoken.value);
+                    }
+                }
+                if (seen[i] === true) {
+                    warning("Duplicate member '{a}'.", nexttoken, i);
+                }
+                seen[i] = true;
+                countMember(i);
+                advance(':');
+                nonadjacent(token, nexttoken);
+                parse(10);
+                if (nexttoken.id === ',') {
+                    comma();
+                    if (nexttoken.id === ',' || nexttoken.id === '}') {
+                        warning("Extra comma.", token);
+                    }
+                } else {
+                    break;
+                }
+            }
+            if (b) {
+                indent -= option.indent;
+                indentation();
+            }
+            advance('}', this);
+            return this;
+        };
+        x.fud = function () {
+            error("Expected to see a statement and instead saw a block.", token);
+        };
+    }(delim('{')));
+
+
+    function varstatement(prefix) {
+
+// JavaScript does not have block scope. It only has function scope. So,
+// declaring a variable in a block can have unexpected consequences.
+
+        var id, name, value;
+
+        if (funct['(onevar)'] && option.onevar) {
+            warning("Too many var statements.");
+        } else if (!funct['(global)']) {
+            funct['(onevar)'] = true;
+        }
+        this.first = [];
+        for (;;) {
+            nonadjacent(token, nexttoken);
+            id = identifier();
+            if (funct['(global)'] && predefined[id] === false) {
+                warning("Redefinition of '{a}'.", token, id);
+            }
+            addlabel(id, 'unused');
+            if (prefix) {
+                break;
+            }
+            name = token;
+            this.first.push(token);
+            if (nexttoken.id === '=') {
+                nonadjacent(token, nexttoken);
+                advance('=');
+                nonadjacent(token, nexttoken);
+                if (nexttoken.id === 'undefined') {
+                    warning("It is not necessary to initialize '{a}' to 'undefined'.", token, id);
+                }
+                if (peek(0).id === '=' && nexttoken.identifier) {
+                    error("Variable {a} was not declared correctly.",
+                            nexttoken, nexttoken.value);
+                }
+                value = parse(0);
+                name.first = value;
+            }
+            if (nexttoken.id !== ',') {
+                break;
+            }
+            comma();
+        }
+        return this;
+    }
+
+
+    stmt('var', varstatement).exps = true;
+
+
+    function functionparams() {
+        var i, t = nexttoken, p = [];
+        advance('(');
+        nospace();
+        if (nexttoken.id === ')') {
+            advance(')');
+            nospace(prevtoken, token);
+            return;
+        }
+        for (;;) {
+            i = identifier();
+            p.push(i);
+            addlabel(i, 'parameter');
+            if (nexttoken.id === ',') {
+                comma();
+            } else {
+                advance(')', t);
+                nospace(prevtoken, token);
+                return p;
+            }
+        }
+    }
+
+    function doFunction(i) {
+        var s = scope;
+        scope = Object.create(s);
+        funct = {
+            '(name)'    : i || '"' + anonname + '"',
+            '(line)'    : nexttoken.line,
+            '(context)' : funct,
+            '(breakage)': 0,
+            '(loopage)' : 0,
+            '(scope)'   : scope
+        };
+        token.funct = funct;
+        functions.push(funct);
+        if (i) {
+            addlabel(i, 'function');
+        }
+        funct['(params)'] = functionparams();
+
+        block(false);
+        scope = s;
+        funct['(last)'] = token.line;
+        funct = funct['(context)'];
+    }
+
+
+    blockstmt('function', function () {
+        if (inblock) {
+            warning(
+"Function statements cannot be placed in blocks. Use a function expression or move the statement to the top of the outer function.", token);
+
+        }
+        var i = identifier();
+        adjacent(token, nexttoken);
+        addlabel(i, 'unused');
+        doFunction(i);
+        if (nexttoken.id === '(' && nexttoken.line === token.line) {
+            error(
+"Function statements are not invocable. Wrap the whole function invocation in parens.");
+        }
+        return this;
+    });
+
+    prefix('function', function () {
+        var i = optionalidentifier();
+        if (i) {
+            adjacent(token, nexttoken);
+        } else {
+            nonadjacent(token, nexttoken);
+        }
+        doFunction(i);
+        if (funct['(loopage)']) {
+            warning("Don't make functions within a loop.");
+        }
+        return this;
+    });
+
+    blockstmt('if', function () {
+        var t = nexttoken;
+        advance('(');
+        nonadjacent(this, t);
+        nospace();
+        parse(20);
+        if (nexttoken.id === '=') {
+            warning("Expected a conditional expression and instead saw an assignment.");
+            advance('=');
+            parse(20);
+        }
+        advance(')', t);
+        nospace(prevtoken, token);
+        block(true);
+        if (nexttoken.id === 'else') {
+            nonadjacent(token, nexttoken);
+            advance('else');
+            if (nexttoken.id === 'if' || nexttoken.id === 'switch') {
+                statement(true);
+            } else {
+                block(true);
+            }
+        }
+        return this;
+    });
+
+    blockstmt('try', function () {
+        var b, e, s;
+        if (option.adsafe) {
+            warning("ADsafe try violation.", this);
+        }
+        block(false);
+        if (nexttoken.id === 'catch') {
+            advance('catch');
+            nonadjacent(token, nexttoken);
+            advance('(');
+            s = scope;
+            scope = Object.create(s);
+            e = nexttoken.value;
+            if (nexttoken.type !== '(identifier)') {
+                warning("Expected an identifier and instead saw '{a}'.",
+                    nexttoken, e);
+            } else {
+                addlabel(e, 'exception');
+            }
+            advance();
+            advance(')');
+            block(false);
+            b = true;
+            scope = s;
+        }
+        if (nexttoken.id === 'finally') {
+            advance('finally');
+            block(false);
+            return;
+        } else if (!b) {
+            error("Expected '{a}' and instead saw '{b}'.",
+                    nexttoken, 'catch', nexttoken.value);
+        }
+        return this;
+    });
+
+    blockstmt('while', function () {
+        var t = nexttoken;
+        funct['(breakage)'] += 1;
+        funct['(loopage)'] += 1;
+        advance('(');
+        nonadjacent(this, t);
+        nospace();
+        parse(20);
+        if (nexttoken.id === '=') {
+            warning("Expected a conditional expression and instead saw an assignment.");
+            advance('=');
+            parse(20);
+        }
+        advance(')', t);
+        nospace(prevtoken, token);
+        block(true);
+        funct['(breakage)'] -= 1;
+        funct['(loopage)'] -= 1;
+        return this;
+    }).labelled = true;
+
+    reserve('with');
+
+    blockstmt('switch', function () {
+        var t = nexttoken,
+            g = false;
+        funct['(breakage)'] += 1;
+        advance('(');
+        nonadjacent(this, t);
+        nospace();
+        this.condition = parse(20);
+        advance(')', t);
+        nospace(prevtoken, token);
+        nonadjacent(token, nexttoken);
+        t = nexttoken;
+        advance('{');
+        nonadjacent(token, nexttoken);
+        indent += option.indent;
+        this.cases = [];
+        for (;;) {
+            switch (nexttoken.id) {
+            case 'case':
+                switch (funct['(verb)']) {
+                case 'break':
+                case 'case':
+                case 'continue':
+                case 'return':
+                case 'switch':
+                case 'throw':
+                    break;
+                default:
+                    warning(
+                        "Expected a 'break' statement before 'case'.",
+                        token);
+                }
+                indentation(-option.indent);
+                advance('case');
+                this.cases.push(parse(20));
+                g = true;
+                advance(':');
+                funct['(verb)'] = 'case';
+                break;
+            case 'default':
+                switch (funct['(verb)']) {
+                case 'break':
+                case 'continue':
+                case 'return':
+                case 'throw':
+                    break;
+                default:
+                    warning(
+                        "Expected a 'break' statement before 'default'.",
+                        token);
+                }
+                indentation(-option.indent);
+                advance('default');
+                g = true;
+                advance(':');
+                break;
+            case '}':
+                indent -= option.indent;
+                indentation();
+                advance('}', t);
+                if (this.cases.length === 1 || this.condition.id === 'true' ||
+                        this.condition.id === 'false') {
+                    warning("This 'switch' should be an 'if'.", this);
+                }
+                funct['(breakage)'] -= 1;
+                funct['(verb)'] = undefined;
+                return;
+            case '(end)':
+                error("Missing '{a}'.", nexttoken, '}');
+                return;
+            default:
+                if (g) {
+                    switch (token.id) {
+                    case ',':
+                        error("Each value should have its own case label.");
+                        return;
+                    case ':':
+                        statements();
+                        break;
+                    default:
+                        error("Missing ':' on a case clause.", token);
+                    }
+                } else {
+                    error("Expected '{a}' and instead saw '{b}'.",
+                        nexttoken, 'case', nexttoken.value);
+                }
+            }
+        }
+    }).labelled = true;
+
+    stmt('debugger', function () {
+        if (!option.debug) {
+            warning("All 'debugger' statements should be removed.");
+        }
+        return this;
+    }).exps = true;
+
+    (function () {
+        var x = stmt('do', function () {
+            funct['(breakage)'] += 1;
+            funct['(loopage)'] += 1;
+            this.first = block(true);
+            advance('while');
+            var t = nexttoken;
+            nonadjacent(token, t);
+            advance('(');
+            nospace();
+            parse(20);
+            if (nexttoken.id === '=') {
+                warning("Expected a conditional expression and instead saw an assignment.");
+                advance('=');
+                parse(20);
+            }
+            advance(')', t);
+            nospace(prevtoken, token);
+            funct['(breakage)'] -= 1;
+            funct['(loopage)'] -= 1;
+            return this;
+        });
+        x.labelled = true;
+        x.exps = true;
+    }());
+
+    blockstmt('for', function () {
+        var f = option.forin, s, t = nexttoken;
+        funct['(breakage)'] += 1;
+        funct['(loopage)'] += 1;
+        advance('(');
+        nonadjacent(this, t);
+        nospace();
+        if (peek(nexttoken.id === 'var' ? 1 : 0).id === 'in') {
+            if (nexttoken.id === 'var') {
+                advance('var');
+                varstatement(true);
+            } else {
+                switch (funct[nexttoken.value]) {
+                case 'unused':
+                    funct[nexttoken.value] = 'var';
+                    break;
+                case 'var':
+                    break;
+                default:
+                    warning("Bad for in variable '{a}'.",
+                            nexttoken, nexttoken.value);
+                }
+                advance();
+            }
+            advance('in');
+            parse(20);
+            advance(')', t);
+            s = block(true);
+            if (!f && (s.length > 1 || typeof s[0] !== 'object' ||
+                    s[0].value !== 'if')) {
+                warning("The body of a for in should be wrapped in an if statement to filter unwanted properties from the prototype.", this);
+            }
+            funct['(breakage)'] -= 1;
+            funct['(loopage)'] -= 1;
+            return this;
+        } else {
+            if (nexttoken.id !== ';') {
+                if (nexttoken.id === 'var') {
+                    advance('var');
+                    varstatement();
+                } else {
+                    for (;;) {
+                        parse(0, 'for');
+                        if (nexttoken.id !== ',') {
+                            break;
+                        }
+                        comma();
+                    }
+                }
+            }
+            nolinebreak(token);
+            advance(';');
+            if (nexttoken.id !== ';') {
+                parse(20);
+                if (nexttoken.id === '=') {
+                    warning("Expected a conditional expression and instead saw an assignment.");
+                    advance('=');
+                    parse(20);
+                }
+            }
+            nolinebreak(token);
+            advance(';');
+            if (nexttoken.id === ';') {
+                error("Expected '{a}' and instead saw '{b}'.",
+                        nexttoken, ')', ';');
+            }
+            if (nexttoken.id !== ')') {
+                for (;;) {
+                    parse(0, 'for');
+                    if (nexttoken.id !== ',') {
+                        break;
+                    }
+                    comma();
+                }
+            }
+            advance(')', t);
+            nospace(prevtoken, token);
+            block(true);
+            funct['(breakage)'] -= 1;
+            funct['(loopage)'] -= 1;
+            return this;
+        }
+    }).labelled = true;
+
+
+    stmt('break', function () {
+        var v = nexttoken.value;
+        if (funct['(breakage)'] === 0) {
+            warning("Unexpected '{a}'.", nexttoken, this.value);
+        }
+        nolinebreak(this);
+        if (nexttoken.id !== ';') {
+            if (token.line === nexttoken.line) {
+                if (funct[v] !== 'label') {
+                    warning("'{a}' is not a statement label.", nexttoken, v);
+                } else if (scope[v] !== funct) {
+                    warning("'{a}' is out of scope.", nexttoken, v);
+                }
+                this.first = nexttoken;
+                advance();
+            }
+        }
+        reachable('break');
+        return this;
+    }).exps = true;
+
+
+    stmt('continue', function () {
+        var v = nexttoken.value;
+        if (funct['(breakage)'] === 0) {
+            warning("Unexpected '{a}'.", nexttoken, this.value);
+        }
+        nolinebreak(this);
+        if (nexttoken.id !== ';') {
+            if (token.line === nexttoken.line) {
+                if (funct[v] !== 'label') {
+                    warning("'{a}' is not a statement label.", nexttoken, v);
+                } else if (scope[v] !== funct) {
+                    warning("'{a}' is out of scope.", nexttoken, v);
+                }
+                this.first = nexttoken;
+                advance();
+            }
+        } else if (!funct['(loopage)']) {
+            warning("Unexpected '{a}'.", nexttoken, this.value);
+        }
+        reachable('continue');
+        return this;
+    }).exps = true;
+
+
+    stmt('return', function () {
+        nolinebreak(this);
+        if (nexttoken.id === '(regexp)') {
+            warning("Wrap the /regexp/ literal in parens to disambiguate the slash operator.");
+        }
+        if (nexttoken.id !== ';' && !nexttoken.reach) {
+            nonadjacent(token, nexttoken);
+            this.first = parse(20);
+        }
+        reachable('return');
+        return this;
+    }).exps = true;
+
+
+    stmt('throw', function () {
+        nolinebreak(this);
+        nonadjacent(token, nexttoken);
+        this.first = parse(20);
+        reachable('throw');
+        return this;
+    }).exps = true;
+
+    reserve('void');
+
+//  Superfluous reserved words
+
+    reserve('class');
+    reserve('const');
+    reserve('enum');
+    reserve('export');
+    reserve('extends');
+    reserve('import');
+    reserve('super');
+
+    reserve('let');
+    reserve('yield');
+    reserve('implements');
+    reserve('interface');
+    reserve('package');
+    reserve('private');
+    reserve('protected');
+    reserve('public');
+    reserve('static');
+
+    function jsonValue() {
+
+        function jsonObject() {
+            var o = {}, t = nexttoken;
+            advance('{');
+            if (nexttoken.id !== '}') {
+                for (;;) {
+                    if (nexttoken.id === '(end)') {
+                        error("Missing '}' to match '{' from line {a}.",
+                                nexttoken, t.line);
+                    } else if (nexttoken.id === '}') {
+                        warning("Unexpected comma.", token);
+                        break;
+                    } else if (nexttoken.id === ',') {
+                        error("Unexpected comma.", nexttoken);
+                    } else if (nexttoken.id !== '(string)') {
+                        warning("Expected a string and instead saw {a}.",
+                                nexttoken, nexttoken.value);
+                    }
+                    if (o[nexttoken.value] === true) {
+                        warning("Duplicate key '{a}'.",
+                                nexttoken, nexttoken.value);
+                    } else if (nexttoken.value === '__proto__') {
+                        warning("Stupid key '{a}'.",
+                                nexttoken, nexttoken.value);
+                    } else {
+                        o[nexttoken.value] = true;
+                    }
+                    advance();
+                    advance(':');
+                    jsonValue();
+                    if (nexttoken.id !== ',') {
+                        break;
+                    }
+                    advance(',');
+                }
+            }
+            advance('}');
+        }
+
+        function jsonArray() {
+            var t = nexttoken;
+            advance('[');
+            if (nexttoken.id !== ']') {
+                for (;;) {
+                    if (nexttoken.id === '(end)') {
+                        error("Missing ']' to match '[' from line {a}.",
+                                nexttoken, t.line);
+                    } else if (nexttoken.id === ']') {
+                        warning("Unexpected comma.", token);
+                        break;
+                    } else if (nexttoken.id === ',') {
+                        error("Unexpected comma.", nexttoken);
+                    }
+                    jsonValue();
+                    if (nexttoken.id !== ',') {
+                        break;
+                    }
+                    advance(',');
+                }
+            }
+            advance(']');
+        }
+
+        switch (nexttoken.id) {
+        case '{':
+            jsonObject();
+            break;
+        case '[':
+            jsonArray();
+            break;
+        case 'true':
+        case 'false':
+        case 'null':
+        case '(number)':
+        case '(string)':
+            advance();
+            break;
+        case '-':
+            advance('-');
+            if (token.character !== nexttoken.from) {
+                warning("Unexpected space after '-'.", token);
+            }
+            adjacent(token, nexttoken);
+            advance('(number)');
+            break;
+        default:
+            error("Expected a JSON value.", nexttoken);
+        }
+    }
+
+
+// The actual JSLINT function itself.
+
+    var itself = function (s, o) {
+        var a, i;
+        JSLINT.errors = [];
+        predefined = Object.create(standard);
+        if (o) {
+            a = o.predef;
+            if (a instanceof Array) {
+                for (i = 0; i < a.length; i += 1) {
+                    predefined[a[i]] = true;
+                }
+            }
+            if (o.adsafe) {
+                o.safe = true;
+            }
+            if (o.safe) {
+                o.browser = false;
+                o.css     = false;
+                o.debug   = false;
+                o.devel   = false;
+                o.eqeqeq  = true;
+                o.evil    = false;
+                o.forin   = false;
+                o.nomen   = true;
+                o.on      = false;
+                o.rhino   = false;
+                o.safe    = true;
+                o.sidebar = false;
+                o.strict  = true;
+                o.sub     = false;
+                o.undef   = true;
+                o.widget  = false;
+                predefined.Date = null;
+                predefined['eval'] = null;
+                predefined.Function = null;
+                predefined.Object = null;
+                predefined.ADSAFE = false;
+                predefined.lib = false;
+            }
+            option = o;
+        } else {
+            option = {};
+        }
+        option.indent = option.indent || 4;
+        option.maxerr = option.maxerr || 50;
+        adsafe_id = '';
+        adsafe_may = false;
+        adsafe_went = false;
+        approved = {};
+        if (option.approved) {
+            for (i = 0; i < option.approved.length; i += 1) {
+                approved[option.approved[i]] = option.approved[i];
+            }
+        } else {
+            approved.test = 'test';
+        }
+        tab = '';
+        for (i = 0; i < option.indent; i += 1) {
+            tab += ' ';
+        }
+        indent = 1;
+        global = Object.create(predefined);
+        scope = global;
+        funct = {
+            '(global)': true,
+            '(name)': '(global)',
+            '(scope)': scope,
+            '(breakage)': 0,
+            '(loopage)': 0
+        };
+        functions = [funct];
+        ids = {};
+        urls = [];
+        src = false;
+        xmode = false;
+        stack = null;
+        member = {};
+        membersOnly = null;
+        implied = {};
+        inblock = false;
+        lookahead = [];
+        jsonmode = false;
+        warnings = 0;
+        lex.init(s);
+        prereg = true;
+        strict_mode = false;
+
+        prevtoken = token = nexttoken = syntax['(begin)'];
+        assume();
+
+        try {
+            advance();
+            if (nexttoken.value.charAt(0) === '<') {
+                html();
+                if (option.adsafe && !adsafe_went) {
+                    warning("ADsafe violation: Missing ADSAFE.go.", this);
+                }
+            } else {
+                switch (nexttoken.id) {
+                case '{':
+                case '[':
+                    option.laxbreak = true;
+                    jsonmode = true;
+                    jsonValue();
+                    break;
+                case '@':
+                case '*':
+                case '#':
+                case '.':
+                case ':':
+                    xmode = 'style';
+                    advance();
+                    if (token.id !== '@' || !nexttoken.identifier ||
+                            nexttoken.value !== 'charset' || token.line !== 1 ||
+                            token.from !== 1) {
+                        error('A css file should begin with @charset "UTF-8";');
+                    }
+                    advance();
+                    if (nexttoken.type !== '(string)' &&
+                            nexttoken.value !== 'UTF-8') {
+                        error('A css file should begin with @charset "UTF-8";');
+                    }
+                    advance();
+                    advance(';');
+                    styles();
+                    break;
+
+                default:
+                    if (option.adsafe && option.fragment) {
+                        error("Expected '{a}' and instead saw '{b}'.",
+                            nexttoken, '<div>', nexttoken.value);
+                    }
+                    statements('lib');
+                }
+            }
+            advance('(end)');
+        } catch (e) {
+            if (e) {
+                JSLINT.errors.push({
+                    reason    : e.message,
+                    line      : e.line || nexttoken.line,
+                    character : e.character || nexttoken.from
+                }, null);
+            }
+        }
+        return JSLINT.errors.length === 0;
+    };
+
+    function is_array(o) {
+        return Object.prototype.toString.apply(o) === '[object Array]';
+    }
+
+    function to_array(o) {
+        var a = [], k;
+        for (k in o) {
+            if (is_own(o, k)) {
+                a.push(k);
+            }
+        }
+        return a;
+    }
+
+// Data summary.
+
+    itself.data = function () {
+
+        var data = {functions: []}, fu, globals, implieds = [], f, i, j,
+            members = [], n, unused = [], v;
+        if (itself.errors.length) {
+            data.errors = itself.errors;
+        }
+
+        if (jsonmode) {
+            data.json = true;
+        }
+
+        for (n in implied) {
+            if (is_own(implied, n)) {
+                implieds.push({
+                    name: n,
+                    line: implied[n]
+                });
+            }
+        }
+        if (implieds.length > 0) {
+            data.implieds = implieds;
+        }
+
+        if (urls.length > 0) {
+            data.urls = urls;
+        }
+
+        globals = to_array(scope);
+        if (globals.length > 0) {
+            data.globals = globals;
+        }
+
+        for (i = 1; i < functions.length; i += 1) {
+            f = functions[i];
+            fu = {};
+            for (j = 0; j < functionicity.length; j += 1) {
+                fu[functionicity[j]] = [];
+            }
+            for (n in f) {
+                if (is_own(f, n) && n.charAt(0) !== '(') {
+                    v = f[n];
+                    if (is_array(fu[v])) {
+                        fu[v].push(n);
+                        if (v === 'unused') {
+                            unused.push({
+                                name: n,
+                                line: f['(line)'],
+                                'function': f['(name)']
+                            });
+                        }
+                    }
+                }
+            }
+            for (j = 0; j < functionicity.length; j += 1) {
+                if (fu[functionicity[j]].length === 0) {
+                    delete fu[functionicity[j]];
+                }
+            }
+            fu.name = f['(name)'];
+            fu.param = f['(params)'];
+            fu.line = f['(line)'];
+            fu.last = f['(last)'];
+            data.functions.push(fu);
+        }
+
+        if (unused.length > 0) {
+            data.unused = unused;
+        }
+
+        members = [];
+        for (n in member) {
+            if (typeof member[n] === 'number') {
+                data.member = member;
+                break;
+            }
+        }
+
+        return data;
+    };
+
+    itself.report = function (option) {
+        var data = itself.data();
+
+        var a = [], c, e, err, f, i, k, l, m = '', n, o = [], s;
+
+        function detail(h, array) {
+            var b, i, singularity;
+            if (array) {
+                o.push('<div><i>' + h + '</i> ');
+                array = array.sort();
+                for (i = 0; i < array.length; i += 1) {
+                    if (array[i] !== singularity) {
+                        singularity = array[i];
+                        o.push((b ? ', ' : '') + singularity);
+                        b = true;
+                    }
+                }
+                o.push('</div>');
+            }
+        }
+
+
+        if (data.errors || data.implieds || data.unused) {
+            err = true;
+            o.push('<div id=errors><i>Error:</i>');
+            if (data.errors) {
+                for (i = 0; i < data.errors.length; i += 1) {
+                    c = data.errors[i];
+                    if (c) {
+                        e = c.evidence || '';
+                        o.push('<p>Problem' + (isFinite(c.line) ? ' at line ' +
+                                c.line + ' character ' + c.character : '') +
+                                ': ' + c.reason.entityify() +
+                                '</p><p class=evidence>' +
+                                (e && (e.length > 80 ? e.slice(0, 77) + '...' :
+                                e).entityify()) + '</p>');
+                    }
+                }
+            }
+
+            if (data.implieds) {
+                s = [];
+                for (i = 0; i < data.implieds.length; i += 1) {
+                    s[i] = '<code>' + data.implieds[i].name + '</code>&nbsp;<i>' +
+                        data.implieds[i].line + '</i>';
+                }
+                o.push('<p><i>Implied global:</i> ' + s.join(', ') + '</p>');
+            }
+
+            if (data.unused) {
+                s = [];
+                for (i = 0; i < data.unused.length; i += 1) {
+                    s[i] = '<code><u>' + data.unused[i].name + '</u></code>&nbsp;<i>' +
+                        data.unused[i].line + '</i> <code>' +
+                        data.unused[i]['function'] + '</code>';
+                }
+                o.push('<p><i>Unused variable:</i> ' + s.join(', ') + '</p>');
+            }
+            if (data.json) {
+                o.push('<p>JSON: bad.</p>');
+            }
+            o.push('</div>');
+        }
+
+        if (!option) {
+
+            o.push('<br><div id=functions>');
+
+            if (data.urls) {
+                detail("URLs<br>", data.urls, '<br>');
+            }
+
+            if (xmode === 'style') {
+                o.push('<p>CSS.</p>');
+            } else if (data.json && !err) {
+                o.push('<p>JSON: good.</p>');
+            } else if (data.globals) {
+                o.push('<div><i>Global</i> ' +
+                        data.globals.sort().join(', ') + '</div>');
+            } else {
+                o.push('<div><i>No new global variables introduced.</i></div>');
+            }
+
+            for (i = 0; i < data.functions.length; i += 1) {
+                f = data.functions[i];
+
+                o.push('<br><div class=function><i>' + f.line + '-' +
+                        f.last + '</i> ' + (f.name || '') + '(' +
+                        (f.param ? f.param.join(', ') : '') + ')</div>');
+                detail('<big><b>Unused</b></big>', f.unused);
+                detail('Closure', f.closure);
+                detail('Variable', f['var']);
+                detail('Exception', f.exception);
+                detail('Outer', f.outer);
+                detail('Global', f.global);
+                detail('Label', f.label);
+            }
+
+            if (data.member) {
+                a = to_array(data.member);
+                if (a.length) {
+                    a = a.sort();
+                    m = '<br><pre id=members>/*members ';
+                    l = 10;
+                    for (i = 0; i < a.length; i += 1) {
+                        k = a[i];
+                        n = k.name();
+                        if (l + n.length > 72) {
+                            o.push(m + '<br>');
+                            m = '    ';
+                            l = 1;
+                        }
+                        l += n.length + 2;
+                        if (data.member[k] === 1) {
+                            n = '<i>' + n + '</i>';
+                        }
+                        if (i < a.length - 1) {
+                            n += ', ';
+                        }
+                        m += n;
+                    }
+                    o.push(m + '<br>*/</pre>');
+                }
+                o.push('</div>');
+            }
+        }
+        return o.join('');
+    };
+    itself.jslint = itself;
+
+    itself.edition = '2010-02-20';
+
+    if (typeof exports !== "undefined") {
+        exports.JSLINT = itself;
+    }
+
+    return itself;
+
+}());
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/lib/parse-js.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/lib/parse-js.js
new file mode 100644
index 0000000..9f90dfe
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/lib/parse-js.js
@@ -0,0 +1,1319 @@
+/***********************************************************************
+
+  A JavaScript tokenizer / parser / beautifier / compressor.
+
+  This version is suitable for Node.js.  With minimal changes (the
+  exports stuff) it should work on any JS platform.
+
+  This file contains the tokenizer/parser.  It is a port to JavaScript
+  of parse-js [1], a JavaScript parser library written in Common Lisp
+  by Marijn Haverbeke.  Thank you Marijn!
+
+  [1] http://marijn.haverbeke.nl/parse-js/
+
+  Exported functions:
+
+    - tokenizer(code) -- returns a function.  Call the returned
+      function to fetch the next token.
+
+    - parse(code) -- returns an AST of the given JavaScript code.
+
+  -------------------------------- (C) ---------------------------------
+
+                           Author: Mihai Bazon
+                         <mihai.bazon@gmail.com>
+                       http://mihai.bazon.net/blog
+
+  Distributed under the BSD license:
+
+    Copyright 2010 (c) Mihai Bazon <mihai.bazon@gmail.com>
+    Based on parse-js (http://marijn.haverbeke.nl/parse-js/).
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions
+    are met:
+
+        * Redistributions of source code must retain the above
+          copyright notice, this list of conditions and the following
+          disclaimer.
+
+        * Redistributions in binary form must reproduce the above
+          copyright notice, this list of conditions and the following
+          disclaimer in the documentation and/or other materials
+          provided with the distribution.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
+    EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+    PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
+    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+    OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+    TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+    THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+    SUCH DAMAGE.
+
+ ***********************************************************************/
+
+/* -----[ Tokenizer (constants) ]----- */
+
+var KEYWORDS = array_to_hash([
+        "break",
+        "case",
+        "catch",
+        "const",
+        "continue",
+        "default",
+        "delete",
+        "do",
+        "else",
+        "finally",
+        "for",
+        "function",
+        "if",
+        "in",
+        "instanceof",
+        "new",
+        "return",
+        "switch",
+        "throw",
+        "try",
+        "typeof",
+        "var",
+        "void",
+        "while",
+        "with"
+]);
+
+var RESERVED_WORDS = array_to_hash([
+        "abstract",
+        "boolean",
+        "byte",
+        "char",
+        "class",
+        "debugger",
+        "double",
+        "enum",
+        "export",
+        "extends",
+        "final",
+        "float",
+        "goto",
+        "implements",
+        "import",
+        "int",
+        "interface",
+        "long",
+        "native",
+        "package",
+        "private",
+        "protected",
+        "public",
+        "short",
+        "static",
+        "super",
+        "synchronized",
+        "throws",
+        "transient",
+        "volatile"
+]);
+
+var KEYWORDS_BEFORE_EXPRESSION = array_to_hash([
+        "return",
+        "new",
+        "delete",
+        "throw",
+        "else",
+        "case"
+]);
+
+var KEYWORDS_ATOM = array_to_hash([
+        "false",
+        "null",
+        "true",
+        "undefined"
+]);
+
+var OPERATOR_CHARS = array_to_hash(characters("+-*&%=<>!?|~^"));
+
+var RE_HEX_NUMBER = /^0x[0-9a-f]+$/i;
+var RE_OCT_NUMBER = /^0[0-7]+$/;
+var RE_DEC_NUMBER = /^\d*\.?\d*(?:e[+-]?\d*(?:\d\.?|\.?\d)\d*)?$/i;
+
+var OPERATORS = array_to_hash([
+        "in",
+        "instanceof",
+        "typeof",
+        "new",
+        "void",
+        "delete",
+        "++",
+        "--",
+        "+",
+        "-",
+        "!",
+        "~",
+        "&",
+        "|",
+        "^",
+        "*",
+        "/",
+        "%",
+        ">>",
+        "<<",
+        ">>>",
+        "<",
+        ">",
+        "<=",
+        ">=",
+        "==",
+        "===",
+        "!=",
+        "!==",
+        "?",
+        "=",
+        "+=",
+        "-=",
+        "/=",
+        "*=",
+        "%=",
+        ">>=",
+        "<<=",
+        ">>>=",
+        "|=",
+        "^=",
+        "&=",
+        "&&",
+        "||"
+]);
+
+var WHITESPACE_CHARS = array_to_hash(characters(" \n\r\t\u200b"));
+
+var PUNC_BEFORE_EXPRESSION = array_to_hash(characters("[{}(,.;:"));
+
+var PUNC_CHARS = array_to_hash(characters("[]{}(),;:"));
+
+var REGEXP_MODIFIERS = array_to_hash(characters("gmsiy"));
+
+/* -----[ Tokenizer ]----- */
+
+// regexps adapted from http://xregexp.com/plugins/#unicode
+var UNICODE = {
+        letter: new RegExp("[\\u0041-\\u005A\\u0061-\\u007A\\u00AA\\u00B5\\u00BA\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02C1\\u02C6-\\u02D1\\u02E0-\\u02E4\\u02EC\\u02EE\\u0370-\\u0374\\u0376\\u0377\\u037A-\\u037D\\u0386\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03F5\\u03F7-\\u0481\\u048A-\\u0523\\u0531-\\u0556\\u0559\\u0561-\\u0587\\u05D0-\\u05EA\\u05F0-\\u05F2\\u0621-\\u064A\\u066E\\u066F\\u0671-\\u06D3\\u06D5\\u06E5\\u06E6\\u06EE\\u06EF\\u06FA-\\u06FC\\u06FF\\u0710\\u0712-\\u072F\\u074D-\\u07A5\\u07B1\\u07CA-\\u07EA\\u07F4\\u07F5\\u07FA\\u0904-\\u0939\\u093D\\u0950\\u0958-\\u0961\\u0971\\u0972\\u097B-\\u097F\\u0985-\\u098C\\u098F\\u0990\\u0993-\\u09A8\\u09AA-\\u09B0\\u09B2\\u09B6-\\u09B9\\u09BD\\u09CE\\u09DC\\u09DD\\u09DF-\\u09E1\\u09F0\\u09F1\\u0A05-\\u0A0A\\u0A0F\\u0A10\\u0A13-\\u0A28\\u0A2A-\\u0A30\\u0A32\\u0A33\\u0A35\\u0A36\\u0A38\\u0A39\\u0A59-\\u0A5C\\u0A5E\\u0A72-\\u0A74\\u0A85-\\u0A8D\\u0A8F-\\u0A91\\u0A93-\\u0AA8\\u0AAA-\\u0AB0\\u0AB2\\u0AB3\\u0AB5-\\u0AB9\\u0ABD\\u0AD0\\u0AE0\\u0AE1\\u0B05-\\u0B0C\\u0B0F\\u0B10\\u0B13-\\u0B28\\u0B2A-\\u0B30\\u0B32\\u0B33\\u0B35-\\u0B39\\u0B3D\\u0B5C\\u0B5D\\u0B5F-\\u0B61\\u0B71\\u0B83\\u0B85-\\u0B8A\\u0B8E-\\u0B90\\u0B92-\\u0B95\\u0B99\\u0B9A\\u0B9C\\u0B9E\\u0B9F\\u0BA3\\u0BA4\\u0BA8-\\u0BAA\\u0BAE-\\u0BB9\\u0BD0\\u0C05-\\u0C0C\\u0C0E-\\u0C10\\u0C12-\\u0C28\\u0C2A-\\u0C33\\u0C35-\\u0C39\\u0C3D\\u0C58\\u0C59\\u0C60\\u0C61\\u0C85-\\u0C8C\\u0C8E-\\u0C90\\u0C92-\\u0CA8\\u0CAA-\\u0CB3\\u0CB5-\\u0CB9\\u0CBD\\u0CDE\\u0CE0\\u0CE1\\u0D05-\\u0D0C\\u0D0E-\\u0D10\\u0D12-\\u0D28\\u0D2A-\\u0D39\\u0D3D\\u0D60\\u0D61\\u0D7A-\\u0D7F\\u0D85-\\u0D96\\u0D9A-\\u0DB1\\u0DB3-\\u0DBB\\u0DBD\\u0DC0-\\u0DC6\\u0E01-\\u0E30\\u0E32\\u0E33\\u0E40-\\u0E46\\u0E81\\u0E82\\u0E84\\u0E87\\u0E88\\u0E8A\\u0E8D\\u0E94-\\u0E97\\u0E99-\\u0E9F\\u0EA1-\\u0EA3\\u0EA5\\u0EA7\\u0EAA\\u0EAB\\u0EAD-\\u0EB0\\u0EB2\\u0EB3\\u0EBD\\u0EC0-\\u0EC4\\u0EC6\\u0EDC\\u0EDD\\u0F00\\u0F40-\\u0F47\\u0F49-\\u0F6C\\u0F88-\\u0F8B\\u1000-\\u102A\\u103F\\u1050-\\u1055\\u105A-\\u105D\\u1061\\u1065\\u1066\\u106E-\\u1070\\u1075-\\u1081\\u108E\\u10A0-\\u10C5\\u10D0-\\u10FA\\u10FC\\u1100-\\u1159\\u115F-\\u11A2\\u11A8-\\u11F9\\u1200-\\u1248\\u124A-\\u124D\\u1250-\\u1256\\u1258\\u125A-\\u125D\\u1260-\\u1288\\u128A-\\u128D\\u1290-\\u12B0\\u12B2-\\u12B5\\u12B8-\\u12BE\\u12C0\\u12C2-\\u12C5\\u12C8-\\u12D6\\u12D8-\\u1310\\u1312-\\u1315\\u1318-\\u135A\\u1380-\\u138F\\u13A0-\\u13F4\\u1401-\\u166C\\u166F-\\u1676\\u1681-\\u169A\\u16A0-\\u16EA\\u1700-\\u170C\\u170E-\\u1711\\u1720-\\u1731\\u1740-\\u1751\\u1760-\\u176C\\u176E-\\u1770\\u1780-\\u17B3\\u17D7\\u17DC\\u1820-\\u1877\\u1880-\\u18A8\\u18AA\\u1900-\\u191C\\u1950-\\u196D\\u1970-\\u1974\\u1980-\\u19A9\\u19C1-\\u19C7\\u1A00-\\u1A16\\u1B05-\\u1B33\\u1B45-\\u1B4B\\u1B83-\\u1BA0\\u1BAE\\u1BAF\\u1C00-\\u1C23\\u1C4D-\\u1C4F\\u1C5A-\\u1C7D\\u1D00-\\u1DBF\\u1E00-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45\\u1F48-\\u1F4D\\u1F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F7D\\u1F80-\\u1FB4\\u1FB6-\\u1FBC\\u1FBE\\u1FC2-\\u1FC4\\u1FC6-\\u1FCC\\u1FD0-\\u1FD3\\u1FD6-\\u1FDB\\u1FE0-\\u1FEC\\u1FF2-\\u1FF4\\u1FF6-\\u1FFC\\u2071\\u207F\\u2090-\\u2094\\u2102\\u2107\\u210A-\\u2113\\u2115\\u2119-\\u211D\\u2124\\u2126\\u2128\\u212A-\\u212D\\u212F-\\u2139\\u213C-\\u213F\\u2145-\\u2149\\u214E\\u2183\\u2184\\u2C00-\\u2C2E\\u2C30-\\u2C5E\\u2C60-\\u2C6F\\u2C71-\\u2C7D\\u2C80-\\u2CE4\\u2D00-\\u2D25\\u2D30-\\u2D65\\u2D6F\\u2D80-\\u2D96\\u2DA0-\\u2DA6\\u2DA8-\\u2DAE\\u2DB0-\\u2DB6\\u2DB8-\\u2DBE\\u2DC0-\\u2DC6\\u2DC8-\\u2DCE\\u2DD0-\\u2DD6\\u2DD8-\\u2DDE\\u2E2F\\u3005\\u3006\\u3031-\\u3035\\u303B\\u303C\\u3041-\\u3096\\u309D-\\u309F\\u30A1-\\u30FA\\u30FC-\\u30FF\\u3105-\\u312D\\u3131-\\u318E\\u31A0-\\u31B7\\u31F0-\\u31FF\\u3400\\u4DB5\\u4E00\\u9FC3\\uA000-\\uA48C\\uA500-\\uA60C\\uA610-\\uA61F\\uA62A\\uA62B\\uA640-\\uA65F\\uA662-\\uA66E\\uA67F-\\uA697\\uA717-\\uA71F\\uA722-\\uA788\\uA78B\\uA78C\\uA7FB-\\uA801\\uA803-\\uA805\\uA807-\\uA80A\\uA80C-\\uA822\\uA840-\\uA873\\uA882-\\uA8B3\\uA90A-\\uA925\\uA930-\\uA946\\uAA00-\\uAA28\\uAA40-\\uAA42\\uAA44-\\uAA4B\\uAC00\\uD7A3\\uF900-\\uFA2D\\uFA30-\\uFA6A\\uFA70-\\uFAD9\\uFB00-\\uFB06\\uFB13-\\uFB17\\uFB1D\\uFB1F-\\uFB28\\uFB2A-\\uFB36\\uFB38-\\uFB3C\\uFB3E\\uFB40\\uFB41\\uFB43\\uFB44\\uFB46-\\uFBB1\\uFBD3-\\uFD3D\\uFD50-\\uFD8F\\uFD92-\\uFDC7\\uFDF0-\\uFDFB\\uFE70-\\uFE74\\uFE76-\\uFEFC\\uFF21-\\uFF3A\\uFF41-\\uFF5A\\uFF66-\\uFFBE\\uFFC2-\\uFFC7\\uFFCA-\\uFFCF\\uFFD2-\\uFFD7\\uFFDA-\\uFFDC]"),
+        non_spacing_mark: new RegExp("[\\u0300-\\u036F\\u0483-\\u0487\\u0591-\\u05BD\\u05BF\\u05C1\\u05C2\\u05C4\\u05C5\\u05C7\\u0610-\\u061A\\u064B-\\u065E\\u0670\\u06D6-\\u06DC\\u06DF-\\u06E4\\u06E7\\u06E8\\u06EA-\\u06ED\\u0711\\u0730-\\u074A\\u07A6-\\u07B0\\u07EB-\\u07F3\\u0816-\\u0819\\u081B-\\u0823\\u0825-\\u0827\\u0829-\\u082D\\u0900-\\u0902\\u093C\\u0941-\\u0948\\u094D\\u0951-\\u0955\\u0962\\u0963\\u0981\\u09BC\\u09C1-\\u09C4\\u09CD\\u09E2\\u09E3\\u0A01\\u0A02\\u0A3C\\u0A41\\u0A42\\u0A47\\u0A48\\u0A4B-\\u0A4D\\u0A51\\u0A70\\u0A71\\u0A75\\u0A81\\u0A82\\u0ABC\\u0AC1-\\u0AC5\\u0AC7\\u0AC8\\u0ACD\\u0AE2\\u0AE3\\u0B01\\u0B3C\\u0B3F\\u0B41-\\u0B44\\u0B4D\\u0B56\\u0B62\\u0B63\\u0B82\\u0BC0\\u0BCD\\u0C3E-\\u0C40\\u0C46-\\u0C48\\u0C4A-\\u0C4D\\u0C55\\u0C56\\u0C62\\u0C63\\u0CBC\\u0CBF\\u0CC6\\u0CCC\\u0CCD\\u0CE2\\u0CE3\\u0D41-\\u0D44\\u0D4D\\u0D62\\u0D63\\u0DCA\\u0DD2-\\u0DD4\\u0DD6\\u0E31\\u0E34-\\u0E3A\\u0E47-\\u0E4E\\u0EB1\\u0EB4-\\u0EB9\\u0EBB\\u0EBC\\u0EC8-\\u0ECD\\u0F18\\u0F19\\u0F35\\u0F37\\u0F39\\u0F71-\\u0F7E\\u0F80-\\u0F84\\u0F86\\u0F87\\u0F90-\\u0F97\\u0F99-\\u0FBC\\u0FC6\\u102D-\\u1030\\u1032-\\u1037\\u1039\\u103A\\u103D\\u103E\\u1058\\u1059\\u105E-\\u1060\\u1071-\\u1074\\u1082\\u1085\\u1086\\u108D\\u109D\\u135F\\u1712-\\u1714\\u1732-\\u1734\\u1752\\u1753\\u1772\\u1773\\u17B7-\\u17BD\\u17C6\\u17C9-\\u17D3\\u17DD\\u180B-\\u180D\\u18A9\\u1920-\\u1922\\u1927\\u1928\\u1932\\u1939-\\u193B\\u1A17\\u1A18\\u1A56\\u1A58-\\u1A5E\\u1A60\\u1A62\\u1A65-\\u1A6C\\u1A73-\\u1A7C\\u1A7F\\u1B00-\\u1B03\\u1B34\\u1B36-\\u1B3A\\u1B3C\\u1B42\\u1B6B-\\u1B73\\u1B80\\u1B81\\u1BA2-\\u1BA5\\u1BA8\\u1BA9\\u1C2C-\\u1C33\\u1C36\\u1C37\\u1CD0-\\u1CD2\\u1CD4-\\u1CE0\\u1CE2-\\u1CE8\\u1CED\\u1DC0-\\u1DE6\\u1DFD-\\u1DFF\\u20D0-\\u20DC\\u20E1\\u20E5-\\u20F0\\u2CEF-\\u2CF1\\u2DE0-\\u2DFF\\u302A-\\u302F\\u3099\\u309A\\uA66F\\uA67C\\uA67D\\uA6F0\\uA6F1\\uA802\\uA806\\uA80B\\uA825\\uA826\\uA8C4\\uA8E0-\\uA8F1\\uA926-\\uA92D\\uA947-\\uA951\\uA980-\\uA982\\uA9B3\\uA9B6-\\uA9B9\\uA9BC\\uAA29-\\uAA2E\\uAA31\\uAA32\\uAA35\\uAA36\\uAA43\\uAA4C\\uAAB0\\uAAB2-\\uAAB4\\uAAB7\\uAAB8\\uAABE\\uAABF\\uAAC1\\uABE5\\uABE8\\uABED\\uFB1E\\uFE00-\\uFE0F\\uFE20-\\uFE26]"),
+        space_combining_mark: new RegExp("[\\u0903\\u093E-\\u0940\\u0949-\\u094C\\u094E\\u0982\\u0983\\u09BE-\\u09C0\\u09C7\\u09C8\\u09CB\\u09CC\\u09D7\\u0A03\\u0A3E-\\u0A40\\u0A83\\u0ABE-\\u0AC0\\u0AC9\\u0ACB\\u0ACC\\u0B02\\u0B03\\u0B3E\\u0B40\\u0B47\\u0B48\\u0B4B\\u0B4C\\u0B57\\u0BBE\\u0BBF\\u0BC1\\u0BC2\\u0BC6-\\u0BC8\\u0BCA-\\u0BCC\\u0BD7\\u0C01-\\u0C03\\u0C41-\\u0C44\\u0C82\\u0C83\\u0CBE\\u0CC0-\\u0CC4\\u0CC7\\u0CC8\\u0CCA\\u0CCB\\u0CD5\\u0CD6\\u0D02\\u0D03\\u0D3E-\\u0D40\\u0D46-\\u0D48\\u0D4A-\\u0D4C\\u0D57\\u0D82\\u0D83\\u0DCF-\\u0DD1\\u0DD8-\\u0DDF\\u0DF2\\u0DF3\\u0F3E\\u0F3F\\u0F7F\\u102B\\u102C\\u1031\\u1038\\u103B\\u103C\\u1056\\u1057\\u1062-\\u1064\\u1067-\\u106D\\u1083\\u1084\\u1087-\\u108C\\u108F\\u109A-\\u109C\\u17B6\\u17BE-\\u17C5\\u17C7\\u17C8\\u1923-\\u1926\\u1929-\\u192B\\u1930\\u1931\\u1933-\\u1938\\u19B0-\\u19C0\\u19C8\\u19C9\\u1A19-\\u1A1B\\u1A55\\u1A57\\u1A61\\u1A63\\u1A64\\u1A6D-\\u1A72\\u1B04\\u1B35\\u1B3B\\u1B3D-\\u1B41\\u1B43\\u1B44\\u1B82\\u1BA1\\u1BA6\\u1BA7\\u1BAA\\u1C24-\\u1C2B\\u1C34\\u1C35\\u1CE1\\u1CF2\\uA823\\uA824\\uA827\\uA880\\uA881\\uA8B4-\\uA8C3\\uA952\\uA953\\uA983\\uA9B4\\uA9B5\\uA9BA\\uA9BB\\uA9BD-\\uA9C0\\uAA2F\\uAA30\\uAA33\\uAA34\\uAA4D\\uAA7B\\uABE3\\uABE4\\uABE6\\uABE7\\uABE9\\uABEA\\uABEC]"),
+        connector_punctuation: new RegExp("[\\u005F\\u203F\\u2040\\u2054\\uFE33\\uFE34\\uFE4D-\\uFE4F\\uFF3F]")
+};
+
+function is_letter(ch) {
+        return UNICODE.letter.test(ch);
+};
+
+function is_digit(ch) {
+        ch = ch.charCodeAt(0);
+        return ch >= 48 && ch <= 57; //XXX: find out if "UnicodeDigit" means something else than 0..9
+};
+
+function is_alphanumeric_char(ch) {
+        return is_digit(ch) || is_letter(ch);
+};
+
+function is_unicode_combining_mark(ch) {
+        return UNICODE.non_spacing_mark.test(ch) || UNICODE.space_combining_mark.test(ch);
+};
+
+function is_unicode_connector_punctuation(ch) {
+        return UNICODE.connector_punctuation.test(ch);
+};
+
+function is_identifier_start(ch) {
+        return ch == "$" || ch == "_" || is_letter(ch);
+};
+
+function is_identifier_char(ch) {
+        return is_identifier_start(ch)
+                || is_unicode_combining_mark(ch)
+                || is_digit(ch)
+                || is_unicode_connector_punctuation(ch)
+                || ch == "\u200c" // zero-width non-joiner <ZWNJ>
+                || ch == "\u200d" // zero-width joiner <ZWJ> (in my ECMA-262 PDF, this is also 200c)
+        ;
+};
+
+function parse_js_number(num) {
+        if (RE_HEX_NUMBER.test(num)) {
+                return parseInt(num.substr(2), 16);
+        } else if (RE_OCT_NUMBER.test(num)) {
+                return parseInt(num.substr(1), 8);
+        } else if (RE_DEC_NUMBER.test(num)) {
+                return parseFloat(num);
+        }
+};
+
+function JS_Parse_Error(message, line, col, pos) {
+        this.message = message;
+        this.line = line;
+        this.col = col;
+        this.pos = pos;
+        try {
+                ({})();
+        } catch(ex) {
+                this.stack = ex.stack;
+        };
+};
+
+JS_Parse_Error.prototype.toString = function() {
+        return this.message + " (line: " + this.line + ", col: " + this.col + ", pos: " + this.pos + ")" + "\n\n" + this.stack;
+};
+
+function js_error(message, line, col, pos) {
+        throw new JS_Parse_Error(message, line, col, pos);
+};
+
+function is_token(token, type, val) {
+        return token.type == type && (val == null || token.value == val);
+};
+
+var EX_EOF = {};
+
+function tokenizer($TEXT) {
+
+        var S = {
+                text            : $TEXT.replace(/\r\n?|[\n\u2028\u2029]/g, "\n").replace(/^\uFEFF/, ''),
+                pos             : 0,
+                tokpos          : 0,
+                line            : 0,
+                tokline         : 0,
+                col             : 0,
+                tokcol          : 0,
+                newline_before  : false,
+                regex_allowed   : false,
+                comments_before : []
+        };
+
+        function peek() { return S.text.charAt(S.pos); };
+
+        function next(signal_eof) {
+                var ch = S.text.charAt(S.pos++);
+                if (signal_eof && !ch)
+                        throw EX_EOF;
+                if (ch == "\n") {
+                        S.newline_before = true;
+                        ++S.line;
+                        S.col = 0;
+                } else {
+                        ++S.col;
+                }
+                return ch;
+        };
+
+        function eof() {
+                return !S.peek();
+        };
+
+        function find(what, signal_eof) {
+                var pos = S.text.indexOf(what, S.pos);
+                if (signal_eof && pos == -1) throw EX_EOF;
+                return pos;
+        };
+
+        function start_token() {
+                S.tokline = S.line;
+                S.tokcol = S.col;
+                S.tokpos = S.pos;
+        };
+
+        function token(type, value, is_comment) {
+                S.regex_allowed = ((type == "operator" && !HOP(UNARY_POSTFIX, value)) ||
+                                   (type == "keyword" && HOP(KEYWORDS_BEFORE_EXPRESSION, value)) ||
+                                   (type == "punc" && HOP(PUNC_BEFORE_EXPRESSION, value)));
+                var ret = {
+                        type  : type,
+                        value : value,
+                        line  : S.tokline,
+                        col   : S.tokcol,
+                        pos   : S.tokpos,
+                        nlb   : S.newline_before
+                };
+                if (!is_comment) {
+                        ret.comments_before = S.comments_before;
+                        S.comments_before = [];
+                }
+                S.newline_before = false;
+                return ret;
+        };
+
+        function skip_whitespace() {
+                while (HOP(WHITESPACE_CHARS, peek()))
+                        next();
+        };
+
+        function read_while(pred) {
+                var ret = "", ch = peek(), i = 0;
+                while (ch && pred(ch, i++)) {
+                        ret += next();
+                        ch = peek();
+                }
+                return ret;
+        };
+
+        function parse_error(err) {
+                js_error(err, S.tokline, S.tokcol, S.tokpos);
+        };
+
+        function read_num(prefix) {
+                var has_e = false, after_e = false, has_x = false, has_dot = prefix == ".";
+                var num = read_while(function(ch, i){
+                        if (ch == "x" || ch == "X") {
+                                if (has_x) return false;
+                                return has_x = true;
+                        }
+                        if (!has_x && (ch == "E" || ch == "e")) {
+                                if (has_e) return false;
+                                return has_e = after_e = true;
+                        }
+                        if (ch == "-") {
+                                if (after_e || (i == 0 && !prefix)) return true;
+                                return false;
+                        }
+                        if (ch == "+") return after_e;
+                        after_e = false;
+                        if (ch == ".") {
+                                if (!has_dot && !has_x)
+                                        return has_dot = true;
+                                return false;
+                        }
+                        return is_alphanumeric_char(ch);
+                });
+                if (prefix)
+                        num = prefix + num;
+                var valid = parse_js_number(num);
+                if (!isNaN(valid)) {
+                        return token("num", valid);
+                } else {
+                        parse_error("Invalid syntax: " + num);
+                }
+        };
+
+        function read_escaped_char() {
+                var ch = next(true);
+                switch (ch) {
+                    case "n" : return "\n";
+                    case "r" : return "\r";
+                    case "t" : return "\t";
+                    case "b" : return "\b";
+                    case "v" : return "\v";
+                    case "f" : return "\f";
+                    case "0" : return "\0";
+                    case "x" : return String.fromCharCode(hex_bytes(2));
+                    case "u" : return String.fromCharCode(hex_bytes(4));
+                    default  : return ch;
+                }
+        };
+
+        function hex_bytes(n) {
+                var num = 0;
+                for (; n > 0; --n) {
+                        var digit = parseInt(next(true), 16);
+                        if (isNaN(digit))
+                                parse_error("Invalid hex-character pattern in string");
+                        num = (num << 4) | digit;
+                }
+                return num;
+        };
+
+        function read_string() {
+                return with_eof_error("Unterminated string constant", function(){
+                        var quote = next(), ret = "";
+                        for (;;) {
+                                var ch = next(true);
+                                if (ch == "\\") ch = read_escaped_char();
+                                else if (ch == quote) break;
+                                ret += ch;
+                        }
+                        return token("string", ret);
+                });
+        };
+
+        function read_line_comment() {
+                next();
+                var i = find("\n"), ret;
+                if (i == -1) {
+                        ret = S.text.substr(S.pos);
+                        S.pos = S.text.length;
+                } else {
+                        ret = S.text.substring(S.pos, i);
+                        S.pos = i;
+                }
+                return token("comment1", ret, true);
+        };
+
+        function read_multiline_comment() {
+                next();
+                return with_eof_error("Unterminated multiline comment", function(){
+                        var i = find("*/", true),
+                            text = S.text.substring(S.pos, i),
+                            tok = token("comment2", text, true);
+                        S.pos = i + 2;
+                        S.line += text.split("\n").length - 1;
+                        S.newline_before = text.indexOf("\n") >= 0;
+
+                        // https://github.com/mishoo/UglifyJS/issues/#issue/100
+                        if (/^@cc_on/i.test(text)) {
+                                warn("WARNING: at line " + S.line);
+                                warn("*** Found \"conditional comment\": " + text);
+                                warn("*** UglifyJS DISCARDS ALL COMMENTS.  This means your code might no longer work properly in Internet Explorer.");
+                        }
+
+                        return tok;
+                });
+        };
+
+        function read_name() {
+                var backslash = false, name = "", ch;
+                while ((ch = peek()) != null) {
+                        if (!backslash) {
+                                if (ch == "\\") backslash = true, next();
+                                else if (is_identifier_char(ch)) name += next();
+                                else break;
+                        }
+                        else {
+                                if (ch != "u") parse_error("Expecting UnicodeEscapeSequence -- uXXXX");
+                                ch = read_escaped_char();
+                                if (!is_identifier_char(ch)) parse_error("Unicode char: " + ch.charCodeAt(0) + " is not valid in identifier");
+                                name += ch;
+                                backslash = false;
+                        }
+                }
+                return name;
+        };
+
+        function read_regexp() {
+                return with_eof_error("Unterminated regular expression", function(){
+                        var prev_backslash = false, regexp = "", ch, in_class = false;
+                        while ((ch = next(true))) if (prev_backslash) {
+                                regexp += "\\" + ch;
+                                prev_backslash = false;
+                        } else if (ch == "[") {
+                                in_class = true;
+                                regexp += ch;
+                        } else if (ch == "]" && in_class) {
+                                in_class = false;
+                                regexp += ch;
+                        } else if (ch == "/" && !in_class) {
+                                break;
+                        } else if (ch == "\\") {
+                                prev_backslash = true;
+                        } else {
+                                regexp += ch;
+                        }
+                        var mods = read_name();
+                        return token("regexp", [ regexp, mods ]);
+                });
+        };
+
+        function read_operator(prefix) {
+                function grow(op) {
+                        if (!peek()) return op;
+                        var bigger = op + peek();
+                        if (HOP(OPERATORS, bigger)) {
+                                next();
+                                return grow(bigger);
+                        } else {
+                                return op;
+                        }
+                };
+                return token("operator", grow(prefix || next()));
+        };
+
+        function handle_slash() {
+                next();
+                var regex_allowed = S.regex_allowed;
+                switch (peek()) {
+                    case "/":
+                        S.comments_before.push(read_line_comment());
+                        S.regex_allowed = regex_allowed;
+                        return next_token();
+                    case "*":
+                        S.comments_before.push(read_multiline_comment());
+                        S.regex_allowed = regex_allowed;
+                        return next_token();
+                }
+                return S.regex_allowed ? read_regexp() : read_operator("/");
+        };
+
+        function handle_dot() {
+                next();
+                return is_digit(peek())
+                        ? read_num(".")
+                        : token("punc", ".");
+        };
+
+        function read_word() {
+                var word = read_name();
+                return !HOP(KEYWORDS, word)
+                        ? token("name", word)
+                        : HOP(OPERATORS, word)
+                        ? token("operator", word)
+                        : HOP(KEYWORDS_ATOM, word)
+                        ? token("atom", word)
+                        : token("keyword", word);
+        };
+
+        function with_eof_error(eof_error, cont) {
+                try {
+                        return cont();
+                } catch(ex) {
+                        if (ex === EX_EOF) parse_error(eof_error);
+                        else throw ex;
+                }
+        };
+
+        function next_token(force_regexp) {
+                if (force_regexp)
+                        return read_regexp();
+                skip_whitespace();
+                start_token();
+                var ch = peek();
+                if (!ch) return token("eof");
+                if (is_digit(ch)) return read_num();
+                if (ch == '"' || ch == "'") return read_string();
+                if (HOP(PUNC_CHARS, ch)) return token("punc", next());
+                if (ch == ".") return handle_dot();
+                if (ch == "/") return handle_slash();
+                if (HOP(OPERATOR_CHARS, ch)) return read_operator();
+                if (ch == "\\" || is_identifier_start(ch)) return read_word();
+                parse_error("Unexpected character '" + ch + "'");
+        };
+
+        next_token.context = function(nc) {
+                if (nc) S = nc;
+                return S;
+        };
+
+        return next_token;
+
+};
+
+/* -----[ Parser (constants) ]----- */
+
+var UNARY_PREFIX = array_to_hash([
+        "typeof",
+        "void",
+        "delete",
+        "--",
+        "++",
+        "!",
+        "~",
+        "-",
+        "+"
+]);
+
+var UNARY_POSTFIX = array_to_hash([ "--", "++" ]);
+
+var ASSIGNMENT = (function(a, ret, i){
+        while (i < a.length) {
+                ret[a[i]] = a[i].substr(0, a[i].length - 1);
+                i++;
+        }
+        return ret;
+})(
+        ["+=", "-=", "/=", "*=", "%=", ">>=", "<<=", ">>>=", "|=", "^=", "&="],
+        { "=": true },
+        0
+);
+
+var PRECEDENCE = (function(a, ret){
+        for (var i = 0, n = 1; i < a.length; ++i, ++n) {
+                var b = a[i];
+                for (var j = 0; j < b.length; ++j) {
+                        ret[b[j]] = n;
+                }
+        }
+        return ret;
+})(
+        [
+                ["||"],
+                ["&&"],
+                ["|"],
+                ["^"],
+                ["&"],
+                ["==", "===", "!=", "!=="],
+                ["<", ">", "<=", ">=", "in", "instanceof"],
+                [">>", "<<", ">>>"],
+                ["+", "-"],
+                ["*", "/", "%"]
+        ],
+        {}
+);
+
+var STATEMENTS_WITH_LABELS = array_to_hash([ "for", "do", "while", "switch" ]);
+
+var ATOMIC_START_TOKEN = array_to_hash([ "atom", "num", "string", "regexp", "name" ]);
+
+/* -----[ Parser ]----- */
+
+function NodeWithToken(str, start, end) {
+        this.name = str;
+        this.start = start;
+        this.end = end;
+};
+
+NodeWithToken.prototype.toString = function() { return this.name; };
+
+function parse($TEXT, exigent_mode, embed_tokens) {
+
+        var S = {
+                input       : typeof $TEXT == "string" ? tokenizer($TEXT, true) : $TEXT,
+                token       : null,
+                prev        : null,
+                peeked      : null,
+                in_function : 0,
+                in_loop     : 0,
+                labels      : []
+        };
+
+        S.token = next();
+
+        function is(type, value) {
+                return is_token(S.token, type, value);
+        };
+
+        function peek() { return S.peeked || (S.peeked = S.input()); };
+
+        function next() {
+                S.prev = S.token;
+                if (S.peeked) {
+                        S.token = S.peeked;
+                        S.peeked = null;
+                } else {
+                        S.token = S.input();
+                }
+                return S.token;
+        };
+
+        function prev() {
+                return S.prev;
+        };
+
+        function croak(msg, line, col, pos) {
+                var ctx = S.input.context();
+                js_error(msg,
+                         line != null ? line : ctx.tokline,
+                         col != null ? col : ctx.tokcol,
+                         pos != null ? pos : ctx.tokpos);
+        };
+
+        function token_error(token, msg) {
+                croak(msg, token.line, token.col);
+        };
+
+        function unexpected(token) {
+                if (token == null)
+                        token = S.token;
+                token_error(token, "Unexpected token: " + token.type + " (" + token.value + ")");
+        };
+
+        function expect_token(type, val) {
+                if (is(type, val)) {
+                        return next();
+                }
+                token_error(S.token, "Unexpected token " + S.token.type + ", expected " + type);
+        };
+
+        function expect(punc) { return expect_token("punc", punc); };
+
+        function can_insert_semicolon() {
+                return !exigent_mode && (
+                        S.token.nlb || is("eof") || is("punc", "}")
+                );
+        };
+
+        function semicolon() {
+                if (is("punc", ";")) next();
+                else if (!can_insert_semicolon()) unexpected();
+        };
+
+        function as() {
+                return slice(arguments);
+        };
+
+        function parenthesised() {
+                expect("(");
+                var ex = expression();
+                expect(")");
+                return ex;
+        };
+
+        function add_tokens(str, start, end) {
+                return str instanceof NodeWithToken ? str : new NodeWithToken(str, start, end);
+        };
+
+        var statement = embed_tokens ? function() {
+                var start = S.token;
+                var ast = $statement.apply(this, arguments);
+                ast[0] = add_tokens(ast[0], start, prev());
+                return ast;
+        } : $statement;
+
+        function $statement() {
+                if (is("operator", "/")) {
+                        S.peeked = null;
+                        S.token = S.input(true); // force regexp
+                }
+                switch (S.token.type) {
+                    case "num":
+                    case "string":
+                    case "regexp":
+                    case "operator":
+                    case "atom":
+                        return simple_statement();
+
+                    case "name":
+                        return is_token(peek(), "punc", ":")
+                                ? labeled_statement(prog1(S.token.value, next, next))
+                                : simple_statement();
+
+                    case "punc":
+                        switch (S.token.value) {
+                            case "{":
+                                return as("block", block_());
+                            case "[":
+                            case "(":
+                                return simple_statement();
+                            case ";":
+                                next();
+                                return as("block");
+                            default:
+                                unexpected();
+                        }
+
+                    case "keyword":
+                        switch (prog1(S.token.value, next)) {
+                            case "break":
+                                return break_cont("break");
+
+                            case "continue":
+                                return break_cont("continue");
+
+                            case "debugger":
+                                semicolon();
+                                return as("debugger");
+
+                            case "do":
+                                return (function(body){
+                                        expect_token("keyword", "while");
+                                        return as("do", prog1(parenthesised, semicolon), body);
+                                })(in_loop(statement));
+
+                            case "for":
+                                return for_();
+
+                            case "function":
+                                return function_(true);
+
+                            case "if":
+                                return if_();
+
+                            case "return":
+                                if (S.in_function == 0)
+                                        croak("'return' outside of function");
+                                return as("return",
+                                          is("punc", ";")
+                                          ? (next(), null)
+                                          : can_insert_semicolon()
+                                          ? null
+                                          : prog1(expression, semicolon));
+
+                            case "switch":
+                                return as("switch", parenthesised(), switch_block_());
+
+                            case "throw":
+                                return as("throw", prog1(expression, semicolon));
+
+                            case "try":
+                                return try_();
+
+                            case "var":
+                                return prog1(var_, semicolon);
+
+                            case "const":
+                                return prog1(const_, semicolon);
+
+                            case "while":
+                                return as("while", parenthesised(), in_loop(statement));
+
+                            case "with":
+                                return as("with", parenthesised(), statement());
+
+                            default:
+                                unexpected();
+                        }
+                }
+        };
+
+        function labeled_statement(label) {
+                S.labels.push(label);
+                var start = S.token, stat = statement();
+                if (exigent_mode && !HOP(STATEMENTS_WITH_LABELS, stat[0]))
+                        unexpected(start);
+                S.labels.pop();
+                return as("label", label, stat);
+        };
+
+        function simple_statement() {
+                return as("stat", prog1(expression, semicolon));
+        };
+
+        function break_cont(type) {
+                var name = is("name") ? S.token.value : null;
+                if (name != null) {
+                        next();
+                        if (!member(name, S.labels))
+                                croak("Label " + name + " without matching loop or statement");
+                }
+                else if (S.in_loop == 0)
+                        croak(type + " not inside a loop or switch");
+                semicolon();
+                return as(type, name);
+        };
+
+        function for_() {
+                expect("(");
+                var init = null;
+                if (!is("punc", ";")) {
+                        init = is("keyword", "var")
+                                ? (next(), var_(true))
+                                : expression(true, true);
+                        if (is("operator", "in"))
+                                return for_in(init);
+                }
+                return regular_for(init);
+        };
+
+        function regular_for(init) {
+                expect(";");
+                var test = is("punc", ";") ? null : expression();
+                expect(";");
+                var step = is("punc", ")") ? null : expression();
+                expect(")");
+                return as("for", init, test, step, in_loop(statement));
+        };
+
+        function for_in(init) {
+                var lhs = init[0] == "var" ? as("name", init[1][0]) : init;
+                next();
+                var obj = expression();
+                expect(")");
+                return as("for-in", init, lhs, obj, in_loop(statement));
+        };
+
+        var function_ = embed_tokens ? function() {
+                var start = prev();
+                var ast = $function_.apply(this, arguments);
+                ast[0] = add_tokens(ast[0], start, prev());
+                return ast;
+        } : $function_;
+
+        function $function_(in_statement) {
+                var name = is("name") ? prog1(S.token.value, next) : null;
+                if (in_statement && !name)
+                        unexpected();
+                expect("(");
+                return as(in_statement ? "defun" : "function",
+                          name,
+                          // arguments
+                          (function(first, a){
+                                  while (!is("punc", ")")) {
+                                          if (first) first = false; else expect(",");
+                                          if (!is("name")) unexpected();
+                                          a.push(S.token.value);
+                                          next();
+                                  }
+                                  next();
+                                  return a;
+                          })(true, []),
+                          // body
+                          (function(){
+                                  ++S.in_function;
+                                  var loop = S.in_loop;
+                                  S.in_loop = 0;
+                                  var a = block_();
+                                  --S.in_function;
+                                  S.in_loop = loop;
+                                  return a;
+                          })());
+        };
+
+        function if_() {
+                var cond = parenthesised(), body = statement(), belse;
+                if (is("keyword", "else")) {
+                        next();
+                        belse = statement();
+                }
+                return as("if", cond, body, belse);
+        };
+
+        function block_() {
+                expect("{");
+                var a = [];
+                while (!is("punc", "}")) {
+                        if (is("eof")) unexpected();
+                        a.push(statement());
+                }
+                next();
+                return a;
+        };
+
+        var switch_block_ = curry(in_loop, function(){
+                expect("{");
+                var a = [], cur = null;
+                while (!is("punc", "}")) {
+                        if (is("eof")) unexpected();
+                        if (is("keyword", "case")) {
+                                next();
+                                cur = [];
+                                a.push([ expression(), cur ]);
+                                expect(":");
+                        }
+                        else if (is("keyword", "default")) {
+                                next();
+                                expect(":");
+                                cur = [];
+                                a.push([ null, cur ]);
+                        }
+                        else {
+                                if (!cur) unexpected();
+                                cur.push(statement());
+                        }
+                }
+                next();
+                return a;
+        });
+
+        function try_() {
+                var body = block_(), bcatch, bfinally;
+                if (is("keyword", "catch")) {
+                        next();
+                        expect("(");
+                        if (!is("name"))
+                                croak("Name expected");
+                        var name = S.token.value;
+                        next();
+                        expect(")");
+                        bcatch = [ name, block_() ];
+                }
+                if (is("keyword", "finally")) {
+                        next();
+                        bfinally = block_();
+                }
+                if (!bcatch && !bfinally)
+                        croak("Missing catch/finally blocks");
+                return as("try", body, bcatch, bfinally);
+        };
+
+        function vardefs(no_in) {
+                var a = [];
+                for (;;) {
+                        if (!is("name"))
+                                unexpected();
+                        var name = S.token.value;
+                        next();
+                        if (is("operator", "=")) {
+                                next();
+                                a.push([ name, expression(false, no_in) ]);
+                        } else {
+                                a.push([ name ]);
+                        }
+                        if (!is("punc", ","))
+                                break;
+                        next();
+                }
+                return a;
+        };
+
+        function var_(no_in) {
+                return as("var", vardefs(no_in));
+        };
+
+        function const_() {
+                return as("const", vardefs());
+        };
+
+        function new_() {
+                var newexp = expr_atom(false), args;
+                if (is("punc", "(")) {
+                        next();
+                        args = expr_list(")");
+                } else {
+                        args = [];
+                }
+                return subscripts(as("new", newexp, args), true);
+        };
+
+        function expr_atom(allow_calls) {
+                if (is("operator", "new")) {
+                        next();
+                        return new_();
+                }
+                if (is("operator") && HOP(UNARY_PREFIX, S.token.value)) {
+                        return make_unary("unary-prefix",
+                                          prog1(S.token.value, next),
+                                          expr_atom(allow_calls));
+                }
+                if (is("punc")) {
+                        switch (S.token.value) {
+                            case "(":
+                                next();
+                                return subscripts(prog1(expression, curry(expect, ")")), allow_calls);
+                            case "[":
+                                next();
+                                return subscripts(array_(), allow_calls);
+                            case "{":
+                                next();
+                                return subscripts(object_(), allow_calls);
+                        }
+                        unexpected();
+                }
+                if (is("keyword", "function")) {
+                        next();
+                        return subscripts(function_(false), allow_calls);
+                }
+                if (HOP(ATOMIC_START_TOKEN, S.token.type)) {
+                        var atom = S.token.type == "regexp"
+                                ? as("regexp", S.token.value[0], S.token.value[1])
+                                : as(S.token.type, S.token.value);
+                        return subscripts(prog1(atom, next), allow_calls);
+                }
+                unexpected();
+        };
+
+        function expr_list(closing, allow_trailing_comma, allow_empty) {
+                var first = true, a = [];
+                while (!is("punc", closing)) {
+                        if (first) first = false; else expect(",");
+                        if (allow_trailing_comma && is("punc", closing)) break;
+                        if (is("punc", ",") && allow_empty) {
+                                a.push([ "atom", "undefined" ]);
+                        } else {
+                                a.push(expression(false));
+                        }
+                }
+                next();
+                return a;
+        };
+
+        function array_() {
+                return as("array", expr_list("]", !exigent_mode, true));
+        };
+
+        function object_() {
+                var first = true, a = [];
+                while (!is("punc", "}")) {
+                        if (first) first = false; else expect(",");
+                        if (!exigent_mode && is("punc", "}"))
+                                // allow trailing comma
+                                break;
+                        var type = S.token.type;
+                        var name = as_property_name();
+                        if (type == "name" && (name == "get" || name == "set") && !is("punc", ":")) {
+                                a.push([ as_name(), function_(false), name ]);
+                        } else {
+                                expect(":");
+                                a.push([ name, expression(false) ]);
+                        }
+                }
+                next();
+                return as("object", a);
+        };
+
+        function as_property_name() {
+                switch (S.token.type) {
+                    case "num":
+                    case "string":
+                        return prog1(S.token.value, next);
+                }
+                return as_name();
+        };
+
+        function as_name() {
+                switch (S.token.type) {
+                    case "name":
+                    case "operator":
+                    case "keyword":
+                    case "atom":
+                        return prog1(S.token.value, next);
+                    default:
+                        unexpected();
+                }
+        };
+
+        function subscripts(expr, allow_calls) {
+                if (is("punc", ".")) {
+                        next();
+                        return subscripts(as("dot", expr, as_name()), allow_calls);
+                }
+                if (is("punc", "[")) {
+                        next();
+                        return subscripts(as("sub", expr, prog1(expression, curry(expect, "]"))), allow_calls);
+                }
+                if (allow_calls && is("punc", "(")) {
+                        next();
+                        return subscripts(as("call", expr, expr_list(")")), true);
+                }
+                if (allow_calls && is("operator") && HOP(UNARY_POSTFIX, S.token.value)) {
+                        return prog1(curry(make_unary, "unary-postfix", S.token.value, expr),
+                                     next);
+                }
+                return expr;
+        };
+
+        function make_unary(tag, op, expr) {
+                if ((op == "++" || op == "--") && !is_assignable(expr))
+                        croak("Invalid use of " + op + " operator");
+                return as(tag, op, expr);
+        };
+
+        function expr_op(left, min_prec, no_in) {
+                var op = is("operator") ? S.token.value : null;
+                if (op && op == "in" && no_in) op = null;
+                var prec = op != null ? PRECEDENCE[op] : null;
+                if (prec != null && prec > min_prec) {
+                        next();
+                        var right = expr_op(expr_atom(true), prec, no_in);
+                        return expr_op(as("binary", op, left, right), min_prec, no_in);
+                }
+                return left;
+        };
+
+        function expr_ops(no_in) {
+                return expr_op(expr_atom(true), 0, no_in);
+        };
+
+        function maybe_conditional(no_in) {
+                var expr = expr_ops(no_in);
+                if (is("operator", "?")) {
+                        next();
+                        var yes = expression(false);
+                        expect(":");
+                        return as("conditional", expr, yes, expression(false, no_in));
+                }
+                return expr;
+        };
+
+        function is_assignable(expr) {
+                if (!exigent_mode) return true;
+                switch (expr[0]) {
+                    case "dot":
+                    case "sub":
+                    case "new":
+                    case "call":
+                        return true;
+                    case "name":
+                        return expr[1] != "this";
+                }
+        };
+
+        function maybe_assign(no_in) {
+                var left = maybe_conditional(no_in), val = S.token.value;
+                if (is("operator") && HOP(ASSIGNMENT, val)) {
+                        if (is_assignable(left)) {
+                                next();
+                                return as("assign", ASSIGNMENT[val], left, maybe_assign(no_in));
+                        }
+                        croak("Invalid assignment");
+                }
+                return left;
+        };
+
+        function expression(commas, no_in) {
+                if (arguments.length == 0)
+                        commas = true;
+                var expr = maybe_assign(no_in);
+                if (commas && is("punc", ",")) {
+                        next();
+                        return as("seq", expr, expression(true, no_in));
+                }
+                return expr;
+        };
+
+        function in_loop(cont) {
+                try {
+                        ++S.in_loop;
+                        return cont();
+                } finally {
+                        --S.in_loop;
+                }
+        };
+
+        return as("toplevel", (function(a){
+                while (!is("eof"))
+                        a.push(statement());
+                return a;
+        })([]));
+
+};
+
+/* -----[ Utilities ]----- */
+
+function curry(f) {
+        var args = slice(arguments, 1);
+        return function() { return f.apply(this, args.concat(slice(arguments))); };
+};
+
+function prog1(ret) {
+        if (ret instanceof Function)
+                ret = ret();
+        for (var i = 1, n = arguments.length; --n > 0; ++i)
+                arguments[i]();
+        return ret;
+};
+
+function array_to_hash(a) {
+        var ret = {};
+        for (var i = 0; i < a.length; ++i)
+                ret[a[i]] = true;
+        return ret;
+};
+
+function slice(a, start) {
+        return Array.prototype.slice.call(a, start == null ? 0 : start);
+};
+
+function characters(str) {
+        return str.split("");
+};
+
+function member(name, array) {
+        for (var i = array.length; --i >= 0;)
+                if (array[i] === name)
+                        return true;
+        return false;
+};
+
+function HOP(obj, prop) {
+        return Object.prototype.hasOwnProperty.call(obj, prop);
+};
+
+var warn = function() {};
+
+/* -----[ Exports ]----- */
+
+exports.tokenizer = tokenizer;
+exports.parse = parse;
+exports.slice = slice;
+exports.curry = curry;
+exports.member = member;
+exports.array_to_hash = array_to_hash;
+exports.PRECEDENCE = PRECEDENCE;
+exports.KEYWORDS_ATOM = KEYWORDS_ATOM;
+exports.RESERVED_WORDS = RESERVED_WORDS;
+exports.KEYWORDS = KEYWORDS;
+exports.ATOMIC_START_TOKEN = ATOMIC_START_TOKEN;
+exports.OPERATORS = OPERATORS;
+exports.is_alphanumeric_char = is_alphanumeric_char;
+exports.set_logger = function(logger) {
+        warn = logger;
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/lib/process.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/lib/process.js
new file mode 100644
index 0000000..09cbc2a
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/lib/process.js
@@ -0,0 +1,1616 @@
+/***********************************************************************
+
+  A JavaScript tokenizer / parser / beautifier / compressor.
+
+  This version is suitable for Node.js.  With minimal changes (the
+  exports stuff) it should work on any JS platform.
+
+  This file implements some AST processors.  They work on data built
+  by parse-js.
+
+  Exported functions:
+
+    - ast_mangle(ast, options) -- mangles the variable/function names
+      in the AST.  Returns an AST.
+
+    - ast_squeeze(ast) -- employs various optimizations to make the
+      final generated code even smaller.  Returns an AST.
+
+    - gen_code(ast, options) -- generates JS code from the AST.  Pass
+      true (or an object, see the code for some options) as second
+      argument to get "pretty" (indented) code.
+
+  -------------------------------- (C) ---------------------------------
+
+                           Author: Mihai Bazon
+                         <mihai.bazon@gmail.com>
+                       http://mihai.bazon.net/blog
+
+  Distributed under the BSD license:
+
+    Copyright 2010 (c) Mihai Bazon <mihai.bazon@gmail.com>
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions
+    are met:
+
+        * Redistributions of source code must retain the above
+          copyright notice, this list of conditions and the following
+          disclaimer.
+
+        * Redistributions in binary form must reproduce the above
+          copyright notice, this list of conditions and the following
+          disclaimer in the documentation and/or other materials
+          provided with the distribution.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
+    EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+    PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
+    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+    OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+    TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+    THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+    SUCH DAMAGE.
+
+ ***********************************************************************/
+
+var jsp = require("./parse-js"),
+    slice = jsp.slice,
+    member = jsp.member,
+    PRECEDENCE = jsp.PRECEDENCE,
+    OPERATORS = jsp.OPERATORS;
+
+/* -----[ helper for AST traversal ]----- */
+
+function ast_walker(ast) {
+        function _vardefs(defs) {
+                return [ this[0], MAP(defs, function(def){
+                        var a = [ def[0] ];
+                        if (def.length > 1)
+                                a[1] = walk(def[1]);
+                        return a;
+                }) ];
+        };
+        var walkers = {
+                "string": function(str) {
+                        return [ this[0], str ];
+                },
+                "num": function(num) {
+                        return [ this[0], num ];
+                },
+                "name": function(name) {
+                        return [ this[0], name ];
+                },
+                "toplevel": function(statements) {
+                        return [ this[0], MAP(statements, walk) ];
+                },
+                "block": function(statements) {
+                        var out = [ this[0] ];
+                        if (statements != null)
+                                out.push(MAP(statements, walk));
+                        return out;
+                },
+                "var": _vardefs,
+                "const": _vardefs,
+                "try": function(t, c, f) {
+                        return [
+                                this[0],
+                                MAP(t, walk),
+                                c != null ? [ c[0], MAP(c[1], walk) ] : null,
+                                f != null ? MAP(f, walk) : null
+                        ];
+                },
+                "throw": function(expr) {
+                        return [ this[0], walk(expr) ];
+                },
+                "new": function(ctor, args) {
+                        return [ this[0], walk(ctor), MAP(args, walk) ];
+                },
+                "switch": function(expr, body) {
+                        return [ this[0], walk(expr), MAP(body, function(branch){
+                                return [ branch[0] ? walk(branch[0]) : null,
+                                         MAP(branch[1], walk) ];
+                        }) ];
+                },
+                "break": function(label) {
+                        return [ this[0], label ];
+                },
+                "continue": function(label) {
+                        return [ this[0], label ];
+                },
+                "conditional": function(cond, t, e) {
+                        return [ this[0], walk(cond), walk(t), walk(e) ];
+                },
+                "assign": function(op, lvalue, rvalue) {
+                        return [ this[0], op, walk(lvalue), walk(rvalue) ];
+                },
+                "dot": function(expr) {
+                        return [ this[0], walk(expr) ].concat(slice(arguments, 1));
+                },
+                "call": function(expr, args) {
+                        return [ this[0], walk(expr), MAP(args, walk) ];
+                },
+                "function": function(name, args, body) {
+                        return [ this[0], name, args.slice(), MAP(body, walk) ];
+                },
+                "defun": function(name, args, body) {
+                        return [ this[0], name, args.slice(), MAP(body, walk) ];
+                },
+                "if": function(conditional, t, e) {
+                        return [ this[0], walk(conditional), walk(t), walk(e) ];
+                },
+                "for": function(init, cond, step, block) {
+                        return [ this[0], walk(init), walk(cond), walk(step), walk(block) ];
+                },
+                "for-in": function(vvar, key, hash, block) {
+                        return [ this[0], walk(vvar), walk(key), walk(hash), walk(block) ];
+                },
+                "while": function(cond, block) {
+                        return [ this[0], walk(cond), walk(block) ];
+                },
+                "do": function(cond, block) {
+                        return [ this[0], walk(cond), walk(block) ];
+                },
+                "return": function(expr) {
+                        return [ this[0], walk(expr) ];
+                },
+                "binary": function(op, left, right) {
+                        return [ this[0], op, walk(left), walk(right) ];
+                },
+                "unary-prefix": function(op, expr) {
+                        return [ this[0], op, walk(expr) ];
+                },
+                "unary-postfix": function(op, expr) {
+                        return [ this[0], op, walk(expr) ];
+                },
+                "sub": function(expr, subscript) {
+                        return [ this[0], walk(expr), walk(subscript) ];
+                },
+                "object": function(props) {
+                        return [ this[0], MAP(props, function(p){
+                                return p.length == 2
+                                        ? [ p[0], walk(p[1]) ]
+                                        : [ p[0], walk(p[1]), p[2] ]; // get/set-ter
+                        }) ];
+                },
+                "regexp": function(rx, mods) {
+                        return [ this[0], rx, mods ];
+                },
+                "array": function(elements) {
+                        return [ this[0], MAP(elements, walk) ];
+                },
+                "stat": function(stat) {
+                        return [ this[0], walk(stat) ];
+                },
+                "seq": function() {
+                        return [ this[0] ].concat(MAP(slice(arguments), walk));
+                },
+                "label": function(name, block) {
+                        return [ this[0], name, walk(block) ];
+                },
+                "with": function(expr, block) {
+                        return [ this[0], walk(expr), walk(block) ];
+                },
+                "atom": function(name) {
+                        return [ this[0], name ];
+                }
+        };
+
+        var user = {};
+        var stack = [];
+        function walk(ast) {
+                if (ast == null)
+                        return null;
+                try {
+                        stack.push(ast);
+                        var type = ast[0];
+                        var gen = user[type];
+                        if (gen) {
+                                var ret = gen.apply(ast, ast.slice(1));
+                                if (ret != null)
+                                        return ret;
+                        }
+                        gen = walkers[type];
+                        return gen.apply(ast, ast.slice(1));
+                } finally {
+                        stack.pop();
+                }
+        };
+
+        function with_walkers(walkers, cont){
+                var save = {}, i;
+                for (i in walkers) if (HOP(walkers, i)) {
+                        save[i] = user[i];
+                        user[i] = walkers[i];
+                }
+                var ret = cont();
+                for (i in save) if (HOP(save, i)) {
+                        if (!save[i]) delete user[i];
+                        else user[i] = save[i];
+                }
+                return ret;
+        };
+
+        return {
+                walk: walk,
+                with_walkers: with_walkers,
+                parent: function() {
+                        return stack[stack.length - 2]; // last one is current node
+                },
+                stack: function() {
+                        return stack;
+                }
+        };
+};
+
+/* -----[ Scope and mangling ]----- */
+
+function Scope(parent) {
+        this.names = {};        // names defined in this scope
+        this.mangled = {};      // mangled names (orig.name => mangled)
+        this.rev_mangled = {};  // reverse lookup (mangled => orig.name)
+        this.cname = -1;        // current mangled name
+        this.refs = {};         // names referenced from this scope
+        this.uses_with = false; // will become TRUE if with() is detected in this or any subscopes
+        this.uses_eval = false; // will become TRUE if eval() is detected in this or any subscopes
+        this.parent = parent;   // parent scope
+        this.children = [];     // sub-scopes
+        if (parent) {
+                this.level = parent.level + 1;
+                parent.children.push(this);
+        } else {
+                this.level = 0;
+        }
+};
+
+var base54 = (function(){
+        var DIGITS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_";
+        return function(num) {
+                var ret = "";
+                do {
+                        ret = DIGITS.charAt(num % 54) + ret;
+                        num = Math.floor(num / 54);
+                } while (num > 0);
+                return ret;
+        };
+})();
+
+Scope.prototype = {
+        has: function(name) {
+                for (var s = this; s; s = s.parent)
+                        if (HOP(s.names, name))
+                                return s;
+        },
+        has_mangled: function(mname) {
+                for (var s = this; s; s = s.parent)
+                        if (HOP(s.rev_mangled, mname))
+                                return s;
+        },
+        toJSON: function() {
+                return {
+                        names: this.names,
+                        uses_eval: this.uses_eval,
+                        uses_with: this.uses_with
+                };
+        },
+
+        next_mangled: function() {
+                // we must be careful that the new mangled name:
+                //
+                // 1. doesn't shadow a mangled name from a parent
+                //    scope, unless we don't reference the original
+                //    name from this scope OR from any sub-scopes!
+                //    This will get slow.
+                //
+                // 2. doesn't shadow an original name from a parent
+                //    scope, in the event that the name is not mangled
+                //    in the parent scope and we reference that name
+                //    here OR IN ANY SUBSCOPES!
+                //
+                // 3. doesn't shadow a name that is referenced but not
+                //    defined (possibly global defined elsewhere).
+                for (;;) {
+                        var m = base54(++this.cname), prior;
+
+                        // case 1.
+                        prior = this.has_mangled(m);
+                        if (prior && this.refs[prior.rev_mangled[m]] === prior)
+                                continue;
+
+                        // case 2.
+                        prior = this.has(m);
+                        if (prior && prior !== this && this.refs[m] === prior && !prior.has_mangled(m))
+                                continue;
+
+                        // case 3.
+                        if (HOP(this.refs, m) && this.refs[m] == null)
+                                continue;
+
+                        // I got "do" once. :-/
+                        if (!is_identifier(m))
+                                continue;
+
+                        return m;
+                }
+        },
+        get_mangled: function(name, newMangle) {
+                if (this.uses_eval || this.uses_with) return name; // no mangle if eval or with is in use
+                var s = this.has(name);
+                if (!s) return name; // not in visible scope, no mangle
+                if (HOP(s.mangled, name)) return s.mangled[name]; // already mangled in this scope
+                if (!newMangle) return name;                      // not found and no mangling requested
+
+                var m = s.next_mangled();
+                s.rev_mangled[m] = name;
+                return s.mangled[name] = m;
+        },
+        define: function(name) {
+                if (name != null)
+                        return this.names[name] = name;
+        }
+};
+
+function ast_add_scope(ast) {
+
+        var current_scope = null;
+        var w = ast_walker(), walk = w.walk;
+        var having_eval = [];
+
+        function with_new_scope(cont) {
+                current_scope = new Scope(current_scope);
+                var ret = current_scope.body = cont();
+                ret.scope = current_scope;
+                current_scope = current_scope.parent;
+                return ret;
+        };
+
+        function define(name) {
+                return current_scope.define(name);
+        };
+
+        function reference(name) {
+                current_scope.refs[name] = true;
+        };
+
+        function _lambda(name, args, body) {
+                return [ this[0], this[0] == "defun" ? define(name) : name, args, with_new_scope(function(){
+                        MAP(args, define);
+                        return MAP(body, walk);
+                })];
+        };
+
+        return with_new_scope(function(){
+                // process AST
+                var ret = w.with_walkers({
+                        "function": _lambda,
+                        "defun": _lambda,
+                        "with": function(expr, block) {
+                                for (var s = current_scope; s; s = s.parent)
+                                        s.uses_with = true;
+                        },
+                        "var": function(defs) {
+                                MAP(defs, function(d){ define(d[0]) });
+                        },
+                        "const": function(defs) {
+                                MAP(defs, function(d){ define(d[0]) });
+                        },
+                        "try": function(t, c, f) {
+                                if (c != null) return [
+                                        this[0],
+                                        MAP(t, walk),
+                                        [ define(c[0]), MAP(c[1], walk) ],
+                                        f != null ? MAP(f, walk) : null
+                                ];
+                        },
+                        "name": function(name) {
+                                if (name == "eval")
+                                        having_eval.push(current_scope);
+                                reference(name);
+                        }
+                }, function(){
+                        return walk(ast);
+                });
+
+                // the reason why we need an additional pass here is
+                // that names can be used prior to their definition.
+
+                // scopes where eval was detected and their parents
+                // are marked with uses_eval, unless they define the
+                // "eval" name.
+                MAP(having_eval, function(scope){
+                        if (!scope.has("eval")) while (scope) {
+                                scope.uses_eval = true;
+                                scope = scope.parent;
+                        }
+                });
+
+                // for referenced names it might be useful to know
+                // their origin scope.  current_scope here is the
+                // toplevel one.
+                function fixrefs(scope, i) {
+                        // do children first; order shouldn't matter
+                        for (i = scope.children.length; --i >= 0;)
+                                fixrefs(scope.children[i]);
+                        for (i in scope.refs) if (HOP(scope.refs, i)) {
+                                // find origin scope and propagate the reference to origin
+                                for (var origin = scope.has(i), s = scope; s; s = s.parent) {
+                                        s.refs[i] = origin;
+                                        if (s === origin) break;
+                                }
+                        }
+                };
+                fixrefs(current_scope);
+
+                return ret;
+        });
+
+};
+
+/* -----[ mangle names ]----- */
+
+function ast_mangle(ast, options) {
+        var w = ast_walker(), walk = w.walk, scope;
+        options = options || {};
+
+        function get_mangled(name, newMangle) {
+                if (!options.toplevel && !scope.parent) return name; // don't mangle toplevel
+                if (options.except && member(name, options.except))
+                        return name;
+                return scope.get_mangled(name, newMangle);
+        };
+
+        function _lambda(name, args, body) {
+                if (name) name = get_mangled(name);
+                body = with_scope(body.scope, function(){
+                        args = MAP(args, function(name){ return get_mangled(name) });
+                        return MAP(body, walk);
+                });
+                return [ this[0], name, args, body ];
+        };
+
+        function with_scope(s, cont) {
+                var _scope = scope;
+                scope = s;
+                for (var i in s.names) if (HOP(s.names, i)) {
+                        get_mangled(i, true);
+                }
+                var ret = cont();
+                ret.scope = s;
+                scope = _scope;
+                return ret;
+        };
+
+        function _vardefs(defs) {
+                return [ this[0], MAP(defs, function(d){
+                        return [ get_mangled(d[0]), walk(d[1]) ];
+                }) ];
+        };
+
+        return w.with_walkers({
+                "function": _lambda,
+                "defun": function() {
+                        // move function declarations to the top when
+                        // they are not in some block.
+                        var ast = _lambda.apply(this, arguments);
+                        switch (w.parent()[0]) {
+                            case "toplevel":
+                            case "function":
+                            case "defun":
+                                return MAP.at_top(ast);
+                        }
+                        return ast;
+                },
+                "var": _vardefs,
+                "const": _vardefs,
+                "name": function(name) {
+                        return [ this[0], get_mangled(name) ];
+                },
+                "try": function(t, c, f) {
+                        return [ this[0],
+                                 MAP(t, walk),
+                                 c != null ? [ get_mangled(c[0]), MAP(c[1], walk) ] : null,
+                                 f != null ? MAP(f, walk) : null ];
+                },
+                "toplevel": function(body) {
+                        var self = this;
+                        return with_scope(self.scope, function(){
+                                return [ self[0], MAP(body, walk) ];
+                        });
+                }
+        }, function() {
+                return walk(ast_add_scope(ast));
+        });
+};
+
+/* -----[
+   - compress foo["bar"] into foo.bar,
+   - remove block brackets {} where possible
+   - join consecutive var declarations
+   - various optimizations for IFs:
+     - if (cond) foo(); else bar();  ==>  cond?foo():bar();
+     - if (cond) foo();  ==>  cond&&foo();
+     - if (foo) return bar(); else return baz();  ==> return foo?bar():baz(); // also for throw
+     - if (foo) return bar(); else something();  ==> {if(foo)return bar();something()}
+   ]----- */
+
+var warn = function(){};
+
+function best_of(ast1, ast2) {
+        return gen_code(ast1).length > gen_code(ast2[0] == "stat" ? ast2[1] : ast2).length ? ast2 : ast1;
+};
+
+function last_stat(b) {
+        if (b[0] == "block" && b[1] && b[1].length > 0)
+                return b[1][b[1].length - 1];
+        return b;
+}
+
+function aborts(t) {
+        if (t) {
+                t = last_stat(t);
+                if (t[0] == "return" || t[0] == "break" || t[0] == "continue" || t[0] == "throw")
+                        return true;
+        }
+};
+
+function boolean_expr(expr) {
+        return ( (expr[0] == "unary-prefix"
+                  && member(expr[1], [ "!", "delete" ])) ||
+
+                 (expr[0] == "binary"
+                  && member(expr[1], [ "in", "instanceof", "==", "!=", "===", "!==", "<", "<=", ">=", ">" ])) ||
+
+                 (expr[0] == "binary"
+                  && member(expr[1], [ "&&", "||" ])
+                  && boolean_expr(expr[2])
+                  && boolean_expr(expr[3])) ||
+
+                 (expr[0] == "conditional"
+                  && boolean_expr(expr[2])
+                  && boolean_expr(expr[3])) ||
+
+                 (expr[0] == "assign"
+                  && expr[1] === true
+                  && boolean_expr(expr[3])) ||
+
+                 (expr[0] == "seq"
+                  && boolean_expr(expr[expr.length - 1]))
+               );
+};
+
+function make_conditional(c, t, e) {
+        if (c[0] == "unary-prefix" && c[1] == "!") {
+                return e ? [ "conditional", c[2], e, t ] : [ "binary", "||", c[2], t ];
+        } else {
+                return e ? [ "conditional", c, t, e ] : [ "binary", "&&", c, t ];
+        }
+};
+
+function empty(b) {
+        return !b || (b[0] == "block" && (!b[1] || b[1].length == 0));
+};
+
+function is_string(node) {
+        return (node[0] == "string" ||
+                node[0] == "unary-prefix" && node[1] == "typeof" ||
+                node[0] == "binary" && node[1] == "+" &&
+                (is_string(node[2]) || is_string(node[3])));
+};
+
+var when_constant = (function(){
+
+        var $NOT_CONSTANT = {};
+
+        // this can only evaluate constant expressions.  If it finds anything
+        // not constant, it throws $NOT_CONSTANT.
+        function evaluate(expr) {
+                switch (expr[0]) {
+                    case "string":
+                    case "num":
+                        return expr[1];
+                    case "name":
+                    case "atom":
+                        switch (expr[1]) {
+                            case "true": return true;
+                            case "false": return false;
+                        }
+                        break;
+                    case "unary-prefix":
+                        switch (expr[1]) {
+                            case "!": return !evaluate(expr[2]);
+                            case "typeof": return typeof evaluate(expr[2]);
+                            case "~": return ~evaluate(expr[2]);
+                            case "-": return -evaluate(expr[2]);
+                            case "+": return +evaluate(expr[2]);
+                        }
+                        break;
+                    case "binary":
+                        var left = expr[2], right = expr[3];
+                        switch (expr[1]) {
+                            case "&&"         : return evaluate(left) &&         evaluate(right);
+                            case "||"         : return evaluate(left) ||         evaluate(right);
+                            case "|"          : return evaluate(left) |          evaluate(right);
+                            case "&"          : return evaluate(left) &          evaluate(right);
+                            case "^"          : return evaluate(left) ^          evaluate(right);
+                            case "+"          : return evaluate(left) +          evaluate(right);
+                            case "*"          : return evaluate(left) *          evaluate(right);
+                            case "/"          : return evaluate(left) /          evaluate(right);
+                            case "-"          : return evaluate(left) -          evaluate(right);
+                            case "<<"         : return evaluate(left) <<         evaluate(right);
+                            case ">>"         : return evaluate(left) >>         evaluate(right);
+                            case ">>>"        : return evaluate(left) >>>        evaluate(right);
+                            case "=="         : return evaluate(left) ==         evaluate(right);
+                            case "==="        : return evaluate(left) ===        evaluate(right);
+                            case "!="         : return evaluate(left) !=         evaluate(right);
+                            case "!=="        : return evaluate(left) !==        evaluate(right);
+                            case "<"          : return evaluate(left) <          evaluate(right);
+                            case "<="         : return evaluate(left) <=         evaluate(right);
+                            case ">"          : return evaluate(left) >          evaluate(right);
+                            case ">="         : return evaluate(left) >=         evaluate(right);
+                            case "in"         : return evaluate(left) in         evaluate(right);
+                            case "instanceof" : return evaluate(left) instanceof evaluate(right);
+                        }
+                }
+                throw $NOT_CONSTANT;
+        };
+
+        return function(expr, yes, no) {
+                try {
+                        var val = evaluate(expr), ast;
+                        switch (typeof val) {
+                            case "string": ast =  [ "string", val ]; break;
+                            case "number": ast =  [ "num", val ]; break;
+                            case "boolean": ast =  [ "name", String(val) ]; break;
+                            default: throw new Error("Can't handle constant of type: " + (typeof val));
+                        }
+                        return yes.call(expr, ast, val);
+                } catch(ex) {
+                        if (ex === $NOT_CONSTANT) {
+                                if (expr[0] == "binary"
+                                    && (expr[1] == "===" || expr[1] == "!==")
+                                    && ((is_string(expr[2]) && is_string(expr[3]))
+                                        || (boolean_expr(expr[2]) && boolean_expr(expr[3])))) {
+                                        expr[1] = expr[1].substr(0, 2);
+                                }
+                                return no ? no.call(expr, expr) : null;
+                        }
+                        else throw ex;
+                }
+        };
+
+})();
+
+function warn_unreachable(ast) {
+        if (!empty(ast))
+                warn("Dropping unreachable code: " + gen_code(ast, true));
+};
+
+function ast_squeeze(ast, options) {
+        options = defaults(options, {
+                make_seqs   : true,
+                dead_code   : true,
+                keep_comps  : true,
+                no_warnings : false
+        });
+
+        var w = ast_walker(), walk = w.walk, scope;
+
+        function negate(c) {
+                var not_c = [ "unary-prefix", "!", c ];
+                switch (c[0]) {
+                    case "unary-prefix":
+                        return c[1] == "!" && boolean_expr(c[2]) ? c[2] : not_c;
+                    case "seq":
+                        c = slice(c);
+                        c[c.length - 1] = negate(c[c.length - 1]);
+                        return c;
+                    case "conditional":
+                        return best_of(not_c, [ "conditional", c[1], negate(c[2]), negate(c[3]) ]);
+                    case "binary":
+                        var op = c[1], left = c[2], right = c[3];
+                        if (!options.keep_comps) switch (op) {
+                            case "<="  : return [ "binary", ">", left, right ];
+                            case "<"   : return [ "binary", ">=", left, right ];
+                            case ">="  : return [ "binary", "<", left, right ];
+                            case ">"   : return [ "binary", "<=", left, right ];
+                        }
+                        switch (op) {
+                            case "=="  : return [ "binary", "!=", left, right ];
+                            case "!="  : return [ "binary", "==", left, right ];
+                            case "===" : return [ "binary", "!==", left, right ];
+                            case "!==" : return [ "binary", "===", left, right ];
+                            case "&&"  : return best_of(not_c, [ "binary", "||", negate(left), negate(right) ]);
+                            case "||"  : return best_of(not_c, [ "binary", "&&", negate(left), negate(right) ]);
+                        }
+                        break;
+                }
+                return not_c;
+        };
+
+        function with_scope(s, cont) {
+                var _scope = scope;
+                scope = s;
+                var ret = cont();
+                ret.scope = s;
+                scope = _scope;
+                return ret;
+        };
+
+        function rmblock(block) {
+                if (block != null && block[0] == "block" && block[1]) {
+                        if (block[1].length == 1)
+                                block = block[1][0];
+                        else if (block[1].length == 0)
+                                block = [ "block" ];
+                }
+                return block;
+        };
+
+        function _lambda(name, args, body) {
+                return [ this[0], name, args, with_scope(body.scope, function(){
+                        return tighten(MAP(body, walk), "lambda");
+                }) ];
+        };
+
+        // we get here for blocks that have been already transformed.
+        // this function does a few things:
+        // 1. discard useless blocks
+        // 2. join consecutive var declarations
+        // 3. remove obviously dead code
+        // 4. transform consecutive statements using the comma operator
+        // 5. if block_type == "lambda" and it detects constructs like if(foo) return ... - rewrite like if (!foo) { ... }
+        function tighten(statements, block_type) {
+                statements = statements.reduce(function(a, stat){
+                        if (stat[0] == "block") {
+                                if (stat[1]) {
+                                        a.push.apply(a, stat[1]);
+                                }
+                        } else {
+                                a.push(stat);
+                        }
+                        return a;
+                }, []);
+
+                statements = (function(a, prev){
+                        statements.forEach(function(cur){
+                                if (prev && ((cur[0] == "var" && prev[0] == "var") ||
+                                             (cur[0] == "const" && prev[0] == "const"))) {
+                                        prev[1] = prev[1].concat(cur[1]);
+                                } else {
+                                        a.push(cur);
+                                        prev = cur;
+                                }
+                        });
+                        return a;
+                })([]);
+
+                if (options.dead_code) statements = (function(a, has_quit){
+                        statements.forEach(function(st){
+                                if (has_quit) {
+                                        if (member(st[0], [ "function", "defun" , "var", "const" ])) {
+                                                a.push(st);
+                                        }
+                                        else if (!options.no_warnings)
+                                                warn_unreachable(st);
+                                }
+                                else {
+                                        a.push(st);
+                                        if (member(st[0], [ "return", "throw", "break", "continue" ]))
+                                                has_quit = true;
+                                }
+                        });
+                        return a;
+                })([]);
+
+                if (options.make_seqs) statements = (function(a, prev) {
+                        statements.forEach(function(cur){
+                                if (prev && prev[0] == "stat" && cur[0] == "stat") {
+                                        prev[1] = [ "seq", prev[1], cur[1] ];
+                                } else {
+                                        a.push(cur);
+                                        prev = cur;
+                                }
+                        });
+                        return a;
+                })([]);
+
+                if (block_type == "lambda") statements = (function(i, a, stat){
+                        while (i < statements.length) {
+                                stat = statements[i++];
+                                if (stat[0] == "if" && !stat[3]) {
+                                        if (stat[2][0] == "return" && stat[2][1] == null) {
+                                                a.push(make_if(negate(stat[1]), [ "block", statements.slice(i) ]));
+                                                break;
+                                        }
+                                        var last = last_stat(stat[2]);
+                                        if (last[0] == "return" && last[1] == null) {
+                                                a.push(make_if(stat[1], [ "block", stat[2][1].slice(0, -1) ], [ "block", statements.slice(i) ]));
+                                                break;
+                                        }
+                                }
+                                a.push(stat);
+                        }
+                        return a;
+                })(0, []);
+
+                return statements;
+        };
+
+        function make_if(c, t, e) {
+                return when_constant(c, function(ast, val){
+                        if (val) {
+                                warn_unreachable(e);
+                                return t;
+                        } else {
+                                warn_unreachable(t);
+                                return e;
+                        }
+                }, function() {
+                        return make_real_if(c, t, e);
+                });
+        };
+
+        function make_real_if(c, t, e) {
+                c = walk(c);
+                t = walk(t);
+                e = walk(e);
+
+                if (empty(t)) {
+                        c = negate(c);
+                        t = e;
+                        e = null;
+                } else if (empty(e)) {
+                        e = null;
+                } else {
+                        // if we have both else and then, maybe it makes sense to switch them?
+                        (function(){
+                                var a = gen_code(c);
+                                var n = negate(c);
+                                var b = gen_code(n);
+                                if (b.length < a.length) {
+                                        var tmp = t;
+                                        t = e;
+                                        e = tmp;
+                                        c = n;
+                                }
+                        })();
+                }
+                if (empty(e) && empty(t))
+                        return [ "stat", c ];
+                var ret = [ "if", c, t, e ];
+                if (t[0] == "if" && empty(t[3]) && empty(e)) {
+                        ret = best_of(ret, walk([ "if", [ "binary", "&&", c, t[1] ], t[2] ]));
+                }
+                else if (t[0] == "stat") {
+                        if (e) {
+                                if (e[0] == "stat") {
+                                        ret = best_of(ret, [ "stat", make_conditional(c, t[1], e[1]) ]);
+                                }
+                        }
+                        else {
+                                ret = best_of(ret, [ "stat", make_conditional(c, t[1]) ]);
+                        }
+                }
+                else if (e && t[0] == e[0] && (t[0] == "return" || t[0] == "throw") && t[1] && e[1]) {
+                        ret = best_of(ret, [ t[0], make_conditional(c, t[1], e[1] ) ]);
+                }
+                else if (e && aborts(t)) {
+                        ret = [ [ "if", c, t ] ];
+                        if (e[0] == "block") {
+                                if (e[1]) ret = ret.concat(e[1]);
+                        }
+                        else {
+                                ret.push(e);
+                        }
+                        ret = walk([ "block", ret ]);
+                }
+                else if (t && aborts(e)) {
+                        ret = [ [ "if", negate(c), e ] ];
+                        if (t[0] == "block") {
+                                if (t[1]) ret = ret.concat(t[1]);
+                        } else {
+                                ret.push(t);
+                        }
+                        ret = walk([ "block", ret ]);
+                }
+                return ret;
+        };
+
+        function _do_while(cond, body) {
+                return when_constant(cond, function(cond, val){
+                        if (!val) {
+                                warn_unreachable(body);
+                                return [ "block" ];
+                        } else {
+                                return [ "for", null, null, null, walk(body) ];
+                        }
+                });
+        };
+
+        return w.with_walkers({
+                "sub": function(expr, subscript) {
+                        if (subscript[0] == "string") {
+                                var name = subscript[1];
+                                if (is_identifier(name))
+                                        return [ "dot", walk(expr), name ];
+                                else if (/^[1-9][0-9]*$/.test(name) || name === "0")
+                                        return [ "sub", walk(expr), [ "num", parseInt(name, 10) ] ];
+                        }
+                },
+                "if": make_if,
+                "toplevel": function(body) {
+                        return [ "toplevel", with_scope(this.scope, function(){
+                                return tighten(MAP(body, walk));
+                        }) ];
+                },
+                "switch": function(expr, body) {
+                        var last = body.length - 1;
+                        return [ "switch", walk(expr), MAP(body, function(branch, i){
+                                var block = tighten(MAP(branch[1], walk));
+                                if (i == last && block.length > 0) {
+                                        var node = block[block.length - 1];
+                                        if (node[0] == "break" && !node[1])
+                                                block.pop();
+                                }
+                                return [ branch[0] ? walk(branch[0]) : null, block ];
+                        }) ];
+                },
+                "function": function() {
+                        var ret = _lambda.apply(this, arguments);
+                        if (ret[1] && !HOP(scope.refs, ret[1])) {
+                                ret[1] = null;
+                        }
+                        return ret;
+                },
+                "defun": _lambda,
+                "block": function(body) {
+                        if (body) return rmblock([ "block", tighten(MAP(body, walk)) ]);
+                },
+                "binary": function(op, left, right) {
+                        return when_constant([ "binary", op, walk(left), walk(right) ], function yes(c){
+                                return best_of(walk(c), this);
+                        }, function no() {
+                                return this;
+                        });
+                },
+                "conditional": function(c, t, e) {
+                        return make_conditional(walk(c), walk(t), walk(e));
+                },
+                "try": function(t, c, f) {
+                        return [
+                                "try",
+                                tighten(MAP(t, walk)),
+                                c != null ? [ c[0], tighten(MAP(c[1], walk)) ] : null,
+                                f != null ? tighten(MAP(f, walk)) : null
+                        ];
+                },
+                "unary-prefix": function(op, expr) {
+                        expr = walk(expr);
+                        var ret = [ "unary-prefix", op, expr ];
+                        if (op == "!")
+                                ret = best_of(ret, negate(expr));
+                        return when_constant(ret, function(ast, val){
+                                return walk(ast); // it's either true or false, so minifies to !0 or !1
+                        }, function() { return ret });
+                },
+                "name": function(name) {
+                        switch (name) {
+                            case "true": return [ "unary-prefix", "!", [ "num", 0 ]];
+                            case "false": return [ "unary-prefix", "!", [ "num", 1 ]];
+                        }
+                },
+                "new": function(ctor, args) {
+                        if (ctor[0] == "name" && ctor[1] == "Array" && !scope.has("Array")) {
+                                if (args.length != 1) {
+                                        return [ "array", args ];
+                                } else {
+                                        return [ "call", [ "name", "Array" ], args ];
+                                }
+                        }
+                },
+                "call": function(expr, args) {
+                        if (expr[0] == "name" && expr[1] == "Array" && args.length != 1 && !scope.has("Array")) {
+                                return [ "array", args ];
+                        }
+                },
+                "while": _do_while,
+                "do": _do_while
+        }, function() {
+                return walk(ast_add_scope(ast));
+        });
+};
+
+/* -----[ re-generate code from the AST ]----- */
+
+var DOT_CALL_NO_PARENS = jsp.array_to_hash([
+        "name",
+        "array",
+        "object",
+        "string",
+        "dot",
+        "sub",
+        "call",
+        "regexp"
+]);
+
+function make_string(str, ascii_only) {
+        var dq = 0, sq = 0;
+        str = str.replace(/[\\\b\f\n\r\t\x22\x27\u2028\u2029]/g, function(s){
+                switch (s) {
+                    case "\\": return "\\\\";
+                    case "\b": return "\\b";
+                    case "\f": return "\\f";
+                    case "\n": return "\\n";
+                    case "\r": return "\\r";
+                    case "\t": return "\\t";
+                    case "\u2028": return "\\u2028";
+                    case "\u2029": return "\\u2029";
+                    case '"': ++dq; return '"';
+                    case "'": ++sq; return "'";
+                }
+                return s;
+        });
+        if (ascii_only) str = to_ascii(str);
+        if (dq > sq) return "'" + str.replace(/\x27/g, "\\'") + "'";
+        else return '"' + str.replace(/\x22/g, '\\"') + '"';
+};
+
+function to_ascii(str) {
+        return str.replace(/[\u0080-\uffff]/g, function(ch) {
+                var code = ch.charCodeAt(0).toString(16);
+                while (code.length < 4) code = "0" + code;
+                return "\\u" + code;
+        });
+};
+
+function gen_code(ast, options) {
+        options = defaults(options, {
+                indent_start : 0,
+                indent_level : 4,
+                quote_keys   : false,
+                space_colon  : false,
+                beautify     : false,
+                ascii_only   : false
+        });
+        var beautify = !!options.beautify;
+        var indentation = 0,
+            newline = beautify ? "\n" : "",
+            space = beautify ? " " : "";
+
+        function encode_string(str) {
+                return make_string(str, options.ascii_only);
+        };
+
+        function make_name(name) {
+                name = name.toString();
+                if (options.ascii_only)
+                        name = to_ascii(name);
+                return name;
+        };
+
+        function indent(line) {
+                if (line == null)
+                        line = "";
+                if (beautify)
+                        line = repeat_string(" ", options.indent_start + indentation * options.indent_level) + line;
+                return line;
+        };
+
+        function with_indent(cont, incr) {
+                if (incr == null) incr = 1;
+                indentation += incr;
+                try { return cont.apply(null, slice(arguments, 1)); }
+                finally { indentation -= incr; }
+        };
+
+        function add_spaces(a) {
+                if (beautify)
+                        return a.join(" ");
+                var b = [];
+                for (var i = 0; i < a.length; ++i) {
+                        var next = a[i + 1];
+                        b.push(a[i]);
+                        if (next &&
+                            ((/[a-z0-9_\x24]$/i.test(a[i].toString()) && /^[a-z0-9_\x24]/i.test(next.toString())) ||
+                             (/[\+\-]$/.test(a[i].toString()) && /^[\+\-]/.test(next.toString())))) {
+                                b.push(" ");
+                        }
+                }
+                return b.join("");
+        };
+
+        function add_commas(a) {
+                return a.join("," + space);
+        };
+
+        function parenthesize(expr) {
+                var gen = make(expr);
+                for (var i = 1; i < arguments.length; ++i) {
+                        var el = arguments[i];
+                        if ((el instanceof Function && el(expr)) || expr[0] == el)
+                                return "(" + gen + ")";
+                }
+                return gen;
+        };
+
+        function best_of(a) {
+                if (a.length == 1) {
+                        return a[0];
+                }
+                if (a.length == 2) {
+                        var b = a[1];
+                        a = a[0];
+                        return a.length <= b.length ? a : b;
+                }
+                return best_of([ a[0], best_of(a.slice(1)) ]);
+        };
+
+        function needs_parens(expr) {
+                if (expr[0] == "function" || expr[0] == "object") {
+                        // dot/call on a literal function requires the
+                        // function literal itself to be parenthesized
+                        // only if it's the first "thing" in a
+                        // statement.  This means that the parent is
+                        // "stat", but it could also be a "seq" and
+                        // we're the first in this "seq" and the
+                        // parent is "stat", and so on.  Messy stuff,
+                        // but it worths the trouble.
+                        var a = slice($stack), self = a.pop(), p = a.pop();
+                        while (p) {
+                                if (p[0] == "stat") return true;
+                                if (((p[0] == "seq" || p[0] == "call" || p[0] == "dot" || p[0] == "sub" || p[0] == "conditional") && p[1] === self) ||
+                                    ((p[0] == "binary" || p[0] == "assign" || p[0] == "unary-postfix") && p[2] === self)) {
+                                        self = p;
+                                        p = a.pop();
+                                } else {
+                                        return false;
+                                }
+                        }
+                }
+                return !HOP(DOT_CALL_NO_PARENS, expr[0]);
+        };
+
+        function make_num(num) {
+                var str = num.toString(10), a = [ str.replace(/^0\./, ".") ], m;
+                if (Math.floor(num) === num) {
+                        a.push("0x" + num.toString(16).toLowerCase(), // probably pointless
+                               "0" + num.toString(8)); // same.
+                        if ((m = /^(.*?)(0+)$/.exec(num))) {
+                                a.push(m[1] + "e" + m[2].length);
+                        }
+                } else if ((m = /^0?\.(0+)(.*)$/.exec(num))) {
+                        a.push(m[2] + "e-" + (m[1].length + m[2].length),
+                               str.substr(str.indexOf(".")));
+                }
+                return best_of(a);
+        };
+
+        var generators = {
+                "string": encode_string,
+                "num": make_num,
+                "name": make_name,
+                "toplevel": function(statements) {
+                        return make_block_statements(statements)
+                                .join(newline + newline);
+                },
+                "block": make_block,
+                "var": function(defs) {
+                        return "var " + add_commas(MAP(defs, make_1vardef)) + ";";
+                },
+                "const": function(defs) {
+                        return "const " + add_commas(MAP(defs, make_1vardef)) + ";";
+                },
+                "try": function(tr, ca, fi) {
+                        var out = [ "try", make_block(tr) ];
+                        if (ca) out.push("catch", "(" + ca[0] + ")", make_block(ca[1]));
+                        if (fi) out.push("finally", make_block(fi));
+                        return add_spaces(out);
+                },
+                "throw": function(expr) {
+                        return add_spaces([ "throw", make(expr) ]) + ";";
+                },
+                "new": function(ctor, args) {
+                        args = args.length > 0 ? "(" + add_commas(MAP(args, make)) + ")" : "";
+                        return add_spaces([ "new", parenthesize(ctor, "seq", "binary", "conditional", "assign", function(expr){
+                                var w = ast_walker(), has_call = {};
+                                try {
+                                        w.with_walkers({
+                                                "call": function() { throw has_call },
+                                                "function": function() { return this }
+                                        }, function(){
+                                                w.walk(expr);
+                                        });
+                                } catch(ex) {
+                                        if (ex === has_call)
+                                                return true;
+                                        throw ex;
+                                }
+                        }) + args ]);
+                },
+                "switch": function(expr, body) {
+                        return add_spaces([ "switch", "(" + make(expr) + ")", make_switch_block(body) ]);
+                },
+                "break": function(label) {
+                        var out = "break";
+                        if (label != null)
+                                out += " " + make_name(label);
+                        return out + ";";
+                },
+                "continue": function(label) {
+                        var out = "continue";
+                        if (label != null)
+                                out += " " + make_name(label);
+                        return out + ";";
+                },
+                "conditional": function(co, th, el) {
+                        return add_spaces([ parenthesize(co, "assign", "seq", "conditional"), "?",
+                                            parenthesize(th, "seq"), ":",
+                                            parenthesize(el, "seq") ]);
+                },
+                "assign": function(op, lvalue, rvalue) {
+                        if (op && op !== true) op += "=";
+                        else op = "=";
+                        return add_spaces([ make(lvalue), op, parenthesize(rvalue, "seq") ]);
+                },
+                "dot": function(expr) {
+                        var out = make(expr), i = 1;
+                        if (expr[0] == "num") {
+                                if (!/\./.test(expr[1]))
+                                        out += ".";
+                        } else if (needs_parens(expr))
+                                out = "(" + out + ")";
+                        while (i < arguments.length)
+                                out += "." + make_name(arguments[i++]);
+                        return out;
+                },
+                "call": function(func, args) {
+                        var f = make(func);
+                        if (needs_parens(func))
+                                f = "(" + f + ")";
+                        return f + "(" + add_commas(MAP(args, function(expr){
+                                return parenthesize(expr, "seq");
+                        })) + ")";
+                },
+                "function": make_function,
+                "defun": make_function,
+                "if": function(co, th, el) {
+                        var out = [ "if", "(" + make(co) + ")", el ? make_then(th) : make(th) ];
+                        if (el) {
+                                out.push("else", make(el));
+                        }
+                        return add_spaces(out);
+                },
+                "for": function(init, cond, step, block) {
+                        var out = [ "for" ];
+                        init = (init != null ? make(init) : "").replace(/;*\s*$/, ";" + space);
+                        cond = (cond != null ? make(cond) : "").replace(/;*\s*$/, ";" + space);
+                        step = (step != null ? make(step) : "").replace(/;*\s*$/, "");
+                        var args = init + cond + step;
+                        if (args == "; ; ") args = ";;";
+                        out.push("(" + args + ")", make(block));
+                        return add_spaces(out);
+                },
+                "for-in": function(vvar, key, hash, block) {
+                        return add_spaces([ "for", "(" +
+                                            (vvar ? make(vvar).replace(/;+$/, "") : make(key)),
+                                            "in",
+                                            make(hash) + ")", make(block) ]);
+                },
+                "while": function(condition, block) {
+                        return add_spaces([ "while", "(" + make(condition) + ")", make(block) ]);
+                },
+                "do": function(condition, block) {
+                        return add_spaces([ "do", make(block), "while", "(" + make(condition) + ")" ]) + ";";
+                },
+                "return": function(expr) {
+                        var out = [ "return" ];
+                        if (expr != null) out.push(make(expr));
+                        return add_spaces(out) + ";";
+                },
+                "binary": function(operator, lvalue, rvalue) {
+                        var left = make(lvalue), right = make(rvalue);
+                        // XXX: I'm pretty sure other cases will bite here.
+                        //      we need to be smarter.
+                        //      adding parens all the time is the safest bet.
+                        if (member(lvalue[0], [ "assign", "conditional", "seq" ]) ||
+                            lvalue[0] == "binary" && PRECEDENCE[operator] > PRECEDENCE[lvalue[1]]) {
+                                left = "(" + left + ")";
+                        }
+                        if (member(rvalue[0], [ "assign", "conditional", "seq" ]) ||
+                            rvalue[0] == "binary" && PRECEDENCE[operator] >= PRECEDENCE[rvalue[1]] &&
+                            !(rvalue[1] == operator && member(operator, [ "&&", "||", "*" ]))) {
+                                right = "(" + right + ")";
+                        }
+                        return add_spaces([ left, operator, right ]);
+                },
+                "unary-prefix": function(operator, expr) {
+                        var val = make(expr);
+                        if (!(expr[0] == "num" || (expr[0] == "unary-prefix" && !HOP(OPERATORS, operator + expr[1])) || !needs_parens(expr)))
+                                val = "(" + val + ")";
+                        return operator + (jsp.is_alphanumeric_char(operator.charAt(0)) ? " " : "") + val;
+                },
+                "unary-postfix": function(operator, expr) {
+                        var val = make(expr);
+                        if (!(expr[0] == "num" || (expr[0] == "unary-postfix" && !HOP(OPERATORS, operator + expr[1])) || !needs_parens(expr)))
+                                val = "(" + val + ")";
+                        return val + operator;
+                },
+                "sub": function(expr, subscript) {
+                        var hash = make(expr);
+                        if (needs_parens(expr))
+                                hash = "(" + hash + ")";
+                        return hash + "[" + make(subscript) + "]";
+                },
+                "object": function(props) {
+                        if (props.length == 0)
+                                return "{}";
+                        return "{" + newline + with_indent(function(){
+                                return MAP(props, function(p){
+                                        if (p.length == 3) {
+                                                // getter/setter.  The name is in p[0], the arg.list in p[1][2], the
+                                                // body in p[1][3] and type ("get" / "set") in p[2].
+                                                return indent(make_function(p[0], p[1][2], p[1][3], p[2]));
+                                        }
+                                        var key = p[0], val = make(p[1]);
+                                        if (options.quote_keys) {
+                                                key = encode_string(key);
+                                        } else if ((typeof key == "number" || !beautify && +key + "" == key)
+                                                   && parseFloat(key) >= 0) {
+                                                key = make_num(+key);
+                                        } else if (!is_identifier(key)) {
+                                                key = encode_string(key);
+                                        }
+                                        return indent(add_spaces(beautify && options.space_colon
+                                                                 ? [ key, ":", val ]
+                                                                 : [ key + ":", val ]));
+                                }).join("," + newline);
+                        }) + newline + indent("}");
+                },
+                "regexp": function(rx, mods) {
+                        return "/" + rx + "/" + mods;
+                },
+                "array": function(elements) {
+                        if (elements.length == 0) return "[]";
+                        return add_spaces([ "[", add_commas(MAP(elements, function(el){
+                                if (!beautify && el[0] == "atom" && el[1] == "undefined") return "";
+                                return parenthesize(el, "seq");
+                        })), "]" ]);
+                },
+                "stat": function(stmt) {
+                        return make(stmt).replace(/;*\s*$/, ";");
+                },
+                "seq": function() {
+                        return add_commas(MAP(slice(arguments), make));
+                },
+                "label": function(name, block) {
+                        return add_spaces([ make_name(name), ":", make(block) ]);
+                },
+                "with": function(expr, block) {
+                        return add_spaces([ "with", "(" + make(expr) + ")", make(block) ]);
+                },
+                "atom": function(name) {
+                        return make_name(name);
+                }
+        };
+
+        // The squeezer replaces "block"-s that contain only a single
+        // statement with the statement itself; technically, the AST
+        // is correct, but this can create problems when we output an
+        // IF having an ELSE clause where the THEN clause ends in an
+        // IF *without* an ELSE block (then the outer ELSE would refer
+        // to the inner IF).  This function checks for this case and
+        // adds the block brackets if needed.
+        function make_then(th) {
+                if (th[0] == "do") {
+                        // https://github.com/mishoo/UglifyJS/issues/#issue/57
+                        // IE croaks with "syntax error" on code like this:
+                        //     if (foo) do ... while(cond); else ...
+                        // we need block brackets around do/while
+                        return make([ "block", [ th ]]);
+                }
+                var b = th;
+                while (true) {
+                        var type = b[0];
+                        if (type == "if") {
+                                if (!b[3])
+                                        // no else, we must add the block
+                                        return make([ "block", [ th ]]);
+                                b = b[3];
+                        }
+                        else if (type == "while" || type == "do") b = b[2];
+                        else if (type == "for" || type == "for-in") b = b[4];
+                        else break;
+                }
+                return make(th);
+        };
+
+        function make_function(name, args, body, keyword) {
+                var out = keyword || "function";
+                if (name) {
+                        out += " " + make_name(name);
+                }
+                out += "(" + add_commas(MAP(args, make_name)) + ")";
+                return add_spaces([ out, make_block(body) ]);
+        };
+
+        function make_block_statements(statements) {
+                for (var a = [], last = statements.length - 1, i = 0; i <= last; ++i) {
+                        var stat = statements[i];
+                        var code = make(stat);
+                        if (code != ";") {
+                                if (!beautify && i == last) {
+                                        if ((stat[0] == "while" && empty(stat[2])) ||
+                                            (member(stat[0], [ "for", "for-in"] ) && empty(stat[4])) ||
+                                            (stat[0] == "if" && empty(stat[2]) && !stat[3]) ||
+                                            (stat[0] == "if" && stat[3] && empty(stat[3]))) {
+                                                code = code.replace(/;*\s*$/, ";");
+                                        } else {
+                                                code = code.replace(/;+\s*$/, "");
+                                        }
+                                }
+                                a.push(code);
+                        }
+                }
+                return MAP(a, indent);
+        };
+
+        function make_switch_block(body) {
+                var n = body.length;
+                if (n == 0) return "{}";
+                return "{" + newline + MAP(body, function(branch, i){
+                        var has_body = branch[1].length > 0, code = with_indent(function(){
+                                return indent(branch[0]
+                                              ? add_spaces([ "case", make(branch[0]) + ":" ])
+                                              : "default:");
+                        }, 0.5) + (has_body ? newline + with_indent(function(){
+                                return make_block_statements(branch[1]).join(newline);
+                        }) : "");
+                        if (!beautify && has_body && i < n - 1)
+                                code += ";";
+                        return code;
+                }).join(newline) + newline + indent("}");
+        };
+
+        function make_block(statements) {
+                if (!statements) return ";";
+                if (statements.length == 0) return "{}";
+                return "{" + newline + with_indent(function(){
+                        return make_block_statements(statements).join(newline);
+                }) + newline + indent("}");
+        };
+
+        function make_1vardef(def) {
+                var name = def[0], val = def[1];
+                if (val != null)
+                        name = add_spaces([ make_name(name), "=", parenthesize(val, "seq") ]);
+                return name;
+        };
+
+        var $stack = [];
+
+        function make(node) {
+                var type = node[0];
+                var gen = generators[type];
+                if (!gen)
+                        throw new Error("Can't find generator for \"" + type + "\"");
+                $stack.push(node);
+                var ret = gen.apply(type, node.slice(1));
+                $stack.pop();
+                return ret;
+        };
+
+        return make(ast);
+};
+
+function split_lines(code, max_line_length) {
+        var splits = [ 0 ];
+        jsp.parse(function(){
+                var next_token = jsp.tokenizer(code);
+                var last_split = 0;
+                var prev_token;
+                function current_length(tok) {
+                        return tok.pos - last_split;
+                };
+                function split_here(tok) {
+                        last_split = tok.pos;
+                        splits.push(last_split);
+                };
+                function custom(){
+                        var tok = next_token.apply(this, arguments);
+                        out: {
+                                if (prev_token) {
+                                        if (prev_token.type == "keyword") break out;
+                                }
+                                if (current_length(tok) > max_line_length) {
+                                        switch (tok.type) {
+                                            case "keyword":
+                                            case "atom":
+                                            case "name":
+                                            case "punc":
+                                                split_here(tok);
+                                                break out;
+                                        }
+                                }
+                        }
+                        prev_token = tok;
+                        return tok;
+                };
+                custom.context = function() {
+                        return next_token.context.apply(this, arguments);
+                };
+                return custom;
+        }());
+        return splits.map(function(pos, i){
+                return code.substring(pos, splits[i + 1] || code.length);
+        }).join("\n");
+};
+
+/* -----[ Utilities ]----- */
+
+function repeat_string(str, i) {
+        if (i <= 0) return "";
+        if (i == 1) return str;
+        var d = repeat_string(str, i >> 1);
+        d += d;
+        if (i & 1) d += str;
+        return d;
+};
+
+function defaults(args, defs) {
+        var ret = {};
+        if (args === true)
+                args = {};
+        for (var i in defs) if (HOP(defs, i)) {
+                ret[i] = (args && HOP(args, i)) ? args[i] : defs[i];
+        }
+        return ret;
+};
+
+function is_identifier(name) {
+        return /^[a-z_$][a-z0-9_$]*$/i.test(name)
+                && name != "this"
+                && !HOP(jsp.KEYWORDS_ATOM, name)
+                && !HOP(jsp.RESERVED_WORDS, name)
+                && !HOP(jsp.KEYWORDS, name);
+};
+
+function HOP(obj, prop) {
+        return Object.prototype.hasOwnProperty.call(obj, prop);
+};
+
+// some utilities
+
+var MAP;
+
+(function(){
+        MAP = function(a, f, o) {
+                var ret = [];
+                for (var i = 0; i < a.length; ++i) {
+                        var val = f.call(o, a[i], i);
+                        if (val instanceof AtTop) ret.unshift(val.v);
+                        else ret.push(val);
+                }
+                return ret;
+        };
+        MAP.at_top = function(val) { return new AtTop(val) };
+        function AtTop(val) { this.v = val };
+})();
+
+/* -----[ Exports ]----- */
+
+exports.ast_walker = ast_walker;
+exports.ast_mangle = ast_mangle;
+exports.ast_squeeze = ast_squeeze;
+exports.gen_code = gen_code;
+exports.ast_add_scope = ast_add_scope;
+exports.set_logger = function(logger) { warn = logger };
+exports.make_string = make_string;
+exports.split_lines = split_lines;
+exports.MAP = MAP;
+
+// keep this last!
+exports.ast_squeeze_more = require("./squeeze-more").ast_squeeze_more;
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/lib/squeeze-more.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/lib/squeeze-more.js
new file mode 100644
index 0000000..12380af
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/lib/squeeze-more.js
@@ -0,0 +1,22 @@
+var jsp = require("./parse-js"),
+    pro = require("./process"),
+    slice = jsp.slice,
+    member = jsp.member,
+    PRECEDENCE = jsp.PRECEDENCE,
+    OPERATORS = jsp.OPERATORS;
+
+function ast_squeeze_more(ast) {
+        var w = pro.ast_walker(), walk = w.walk;
+        return w.with_walkers({
+                "call": function(expr, args) {
+                        if (expr[0] == "dot" && expr[2] == "toString" && args.length == 0) {
+                                // foo.toString()  ==>  foo+""
+                                return [ "binary", "+", expr[1], [ "string", "" ]];
+                        }
+                }
+        }, function() {
+                return walk(ast);
+        });
+};
+
+exports.ast_squeeze_more = ast_squeeze_more;
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/post-compile.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/post-compile.js
new file mode 100644
index 0000000..4bcafe8
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/post-compile.js
@@ -0,0 +1,7 @@
+#!/usr/bin/env node
+
+var print = require("sys").print,
+	src = require("fs").readFileSync(process.argv[2], "utf8");
+
+// Previously done in sed but reimplemented here due to portability issues
+print(src.replace(/^(\s*\*\/)(.+)/m, "$1\n$2;"));
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/uglify.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/uglify.js
new file mode 100644
index 0000000..943ddd8
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/uglify.js
@@ -0,0 +1,199 @@
+#! /usr/bin/env node
+// -*- js2 -*-
+
+global.sys = require(/^v0\.[012]/.test(process.version) ? "sys" : "util");
+var fs = require("fs"),
+    jsp = require("./lib/parse-js"),
+    pro = require("./lib/process");
+
+pro.set_logger(function(msg){
+        sys.debug(msg);
+});
+
+var options = {
+        ast: false,
+        mangle: true,
+        mangle_toplevel: false,
+        squeeze: true,
+        make_seqs: true,
+        dead_code: true,
+        beautify: false,
+        verbose: false,
+        show_copyright: true,
+        out_same_file: false,
+        extra: false,
+        unsafe: false,            // XXX: extra & unsafe?  but maybe we don't want both, so....
+        beautify_options: {
+                indent_level: 4,
+                indent_start: 0,
+                quote_keys: false,
+                space_colon: false
+        },
+        output: true            // stdout
+};
+
+var args = jsp.slice(process.argv, 2);
+var filename;
+
+out: while (args.length > 0) {
+        var v = args.shift();
+        switch (v) {
+            case "-b":
+            case "--beautify":
+                options.beautify = true;
+                break;
+            case "-i":
+            case "--indent":
+                options.beautify_options.indent_level = args.shift();
+                break;
+            case "-q":
+            case "--quote-keys":
+                options.beautify_options.quote_keys = true;
+                break;
+            case "-mt":
+            case "--mangle-toplevel":
+                options.mangle_toplevel = true;
+                break;
+            case "--no-mangle":
+            case "-nm":
+                options.mangle = false;
+                break;
+            case "--no-squeeze":
+            case "-ns":
+                options.squeeze = false;
+                break;
+            case "--no-seqs":
+                options.make_seqs = false;
+                break;
+            case "--no-dead-code":
+                options.dead_code = false;
+                break;
+            case "--no-copyright":
+            case "-nc":
+                options.show_copyright = false;
+                break;
+            case "-o":
+            case "--output":
+                options.output = args.shift();
+                break;
+            case "--overwrite":
+                options.out_same_file = true;
+                break;
+            case "-v":
+            case "--verbose":
+                options.verbose = true;
+                break;
+            case "--ast":
+                options.ast = true;
+                break;
+            case "--extra":
+                options.extra = true;
+                break;
+            case "--unsafe":
+                options.unsafe = true;
+                break;
+            default:
+                filename = v;
+                break out;
+        }
+}
+
+if (filename) {
+        fs.readFile(filename, "utf8", function(err, text){
+                if (err) {
+                        throw err;
+                }
+                output(squeeze_it(text));
+        });
+} else {
+        var stdin = process.openStdin();
+        stdin.setEncoding("utf8");
+        var text = "";
+        stdin.on("data", function(chunk){
+                text += chunk;
+        });
+        stdin.on("end", function() {
+                output(squeeze_it(text));
+        });
+}
+
+function output(text) {
+        var out;
+        if (options.out_same_file && filename)
+                options.output = filename;
+        if (options.output === true) {
+                out = process.stdout;
+        } else {
+                out = fs.createWriteStream(options.output, {
+                        flags: "w",
+                        encoding: "utf8",
+                        mode: 0644
+                });
+        }
+        out.write(text);
+        out.end();
+};
+
+// --------- main ends here.
+
+function show_copyright(comments) {
+        var ret = "";
+        for (var i = 0; i < comments.length; ++i) {
+                var c = comments[i];
+                if (c.type == "comment1") {
+                        ret += "//" + c.value + "\n";
+                } else {
+                        ret += "/*" + c.value + "*/";
+                }
+        }
+        return ret;
+};
+
+function squeeze_it(code) {
+        var result = "";
+        if (options.show_copyright) {
+                var initial_comments = [];
+                // keep first comment
+                var tok = jsp.tokenizer(code, false), c;
+                c = tok();
+                var prev = null;
+                while (/^comment/.test(c.type) && (!prev || prev == c.type)) {
+                        initial_comments.push(c);
+                        prev = c.type;
+                        c = tok();
+                }
+                result += show_copyright(initial_comments);
+        }
+        try {
+                var ast = time_it("parse", function(){ return jsp.parse(code); });
+                if (options.mangle)
+                        ast = time_it("mangle", function(){ return pro.ast_mangle(ast, options.mangle_toplevel); });
+                if (options.squeeze)
+                        ast = time_it("squeeze", function(){
+                                ast = pro.ast_squeeze(ast, {
+                                        make_seqs : options.make_seqs,
+                                        dead_code : options.dead_code,
+                                        extra     : options.extra
+                                });
+                                if (options.unsafe)
+                                        ast = pro.ast_squeeze_more(ast);
+                                return ast;
+                        });
+                if (options.ast)
+                        return sys.inspect(ast, null, null);
+                result += time_it("generate", function(){ return pro.gen_code(ast, options.beautify && options.beautify_options) });
+                return result;
+        } catch(ex) {
+                sys.debug(ex.stack);
+                sys.debug(sys.inspect(ex));
+                sys.debug(JSON.stringify(ex));
+        }
+};
+
+function time_it(name, cont) {
+        if (!options.verbose)
+                return cont();
+        var t1 = new Date().getTime();
+        try { return cont(); }
+        finally { sys.debug("// " + name + ": " + ((new Date().getTime() - t1) / 1000).toFixed(3) + " sec."); }
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/dist/jsonselect.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/dist/jsonselect.js
new file mode 100644
index 0000000..50059b7
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/dist/jsonselect.js
@@ -0,0 +1,522 @@
+/*! Copyright (c) 2011, Lloyd Hilaiel, ISC License */
+/*
+ * This is the JSONSelect reference implementation, in javascript.
+ */
+(function(exports) {
+
+    var // localize references
+    toString = Object.prototype.toString;
+
+    function jsonParse(str) {
+      try {
+          if(JSON && JSON.parse){
+              return JSON.parse(str);
+          }
+          return (new Function("return " + str))();
+      } catch(e) {
+        te("ijs");
+      }
+    }
+
+    // emitted error codes.
+    var errorCodes = {
+        "bop":  "binary operator expected",
+        "ee":   "expression expected",
+        "epex": "closing paren expected ')'",
+        "ijs":  "invalid json string",
+        "mcp":  "missing closing paren",
+        "mepf": "malformed expression in pseudo-function",
+        "mexp": "multiple expressions not allowed",
+        "mpc":  "multiple pseudo classes (:xxx) not allowed",
+        "nmi":  "multiple ids not allowed",
+        "pex":  "opening paren expected '('",
+        "se":   "selector expected",
+        "sex":  "string expected",
+        "sra":  "string required after '.'",
+        "uc":   "unrecognized char",
+        "ucp":  "unexpected closing paren",
+        "ujs":  "unclosed json string",
+        "upc":  "unrecognized pseudo class"
+    };
+
+    // throw an error message
+    function te(ec) {
+      throw new Error(errorCodes[ec]);
+    }
+
+    // THE LEXER
+    var toks = {
+        psc: 1, // pseudo class
+        psf: 2, // pseudo class function
+        typ: 3, // type
+        str: 4, // string
+        ide: 5  // identifiers (or "classes", stuff after a dot)
+    };
+
+    // The primary lexing regular expression in jsonselect
+    var pat = new RegExp(
+        "^(?:" +
+        // (1) whitespace
+        "([\\r\\n\\t\\ ]+)|" +
+        // (2) one-char ops
+        "([~*,>\\)\\(])|" +
+        // (3) types names
+        "(string|boolean|null|array|object|number)|" +
+        // (4) pseudo classes
+        "(:(?:root|first-child|last-child|only-child))|" +
+        // (5) pseudo functions
+        "(:(?:nth-child|nth-last-child|has|expr|val|contains))|" +
+        // (6) bogusly named pseudo something or others
+        "(:\\w+)|" +
+        // (7 & 8) identifiers and JSON strings
+        "(?:(\\.)?(\\\"(?:[^\\\\]|\\\\[^\\\"])*\\\"))|" +
+        // (8) bogus JSON strings missing a trailing quote
+        "(\\\")|" +
+        // (9) identifiers (unquoted)
+        "\\.((?:[_a-zA-Z]|[^\\0-\\0177]|\\\\[^\\r\\n\\f0-9a-fA-F])(?:[_a-zA-Z0-9\\-]|[^\\u0000-\\u0177]|(?:\\\\[^\\r\\n\\f0-9a-fA-F]))*)" +
+        ")"
+    );
+
+    // A regular expression for matching "nth expressions" (see grammar, what :nth-child() eats)
+    var nthPat = /^\s*\(\s*(?:([+\-]?)([0-9]*)n\s*(?:([+\-])\s*([0-9]))?|(odd|even)|([+\-]?[0-9]+))\s*\)/;
+    function lex(str, off) {
+        if (!off) off = 0;
+        var m = pat.exec(str.substr(off));
+        if (!m) return undefined;
+        off+=m[0].length;
+        var a;
+        if (m[1]) a = [off, " "];
+        else if (m[2]) a = [off, m[0]];
+        else if (m[3]) a = [off, toks.typ, m[0]];
+        else if (m[4]) a = [off, toks.psc, m[0]];
+        else if (m[5]) a = [off, toks.psf, m[0]];
+        else if (m[6]) te("upc");
+        else if (m[8]) a = [off, m[7] ? toks.ide : toks.str, jsonParse(m[8])];
+        else if (m[9]) te("ujs");
+        else if (m[10]) a = [off, toks.ide, m[10].replace(/\\([^\r\n\f0-9a-fA-F])/g,"$1")];
+        return a;
+    }
+
+    // THE EXPRESSION SUBSYSTEM
+
+    var exprPat = new RegExp(
+            // skip and don't capture leading whitespace
+            "^\\s*(?:" +
+            // (1) simple vals
+            "(true|false|null)|" + 
+            // (2) numbers
+            "(-?\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?)|" +
+            // (3) strings
+            "(\"(?:[^\\]|\\[^\"])*\")|" +
+            // (4) the 'x' value placeholder
+            "(x)|" +
+            // (5) binops
+            "(&&|\\|\\||[\\$\\^<>!\\*]=|[=+\\-*/%<>])|" +
+            // (6) parens
+            "([\\(\\)])" +
+            ")"
+    );
+
+    function is(o, t) { return typeof o === t; }
+    var operators = {
+        '*':  [ 9, function(lhs, rhs) { return lhs * rhs; } ],
+        '/':  [ 9, function(lhs, rhs) { return lhs / rhs; } ],
+        '%':  [ 9, function(lhs, rhs) { return lhs % rhs; } ],
+        '+':  [ 7, function(lhs, rhs) { return lhs + rhs; } ],
+        '-':  [ 7, function(lhs, rhs) { return lhs - rhs; } ],
+        '<=': [ 5, function(lhs, rhs) { return is(lhs, 'number') && is(rhs, 'number') && lhs <= rhs; } ],
+        '>=': [ 5, function(lhs, rhs) { return is(lhs, 'number') && is(rhs, 'number') && lhs >= rhs; } ],
+        '$=': [ 5, function(lhs, rhs) { return is(lhs, 'string') && is(rhs, 'string') && lhs.lastIndexOf(rhs) === lhs.length - rhs.length; } ],
+        '^=': [ 5, function(lhs, rhs) { return is(lhs, 'string') && is(rhs, 'string') && lhs.indexOf(rhs) === 0; } ],
+        '*=': [ 5, function(lhs, rhs) { return is(lhs, 'string') && is(rhs, 'string') && lhs.indexOf(rhs) !== -1; } ],
+        '>':  [ 5, function(lhs, rhs) { return is(lhs, 'number') && is(rhs, 'number') && lhs > rhs; } ],
+        '<':  [ 5, function(lhs, rhs) { return is(lhs, 'number') && is(rhs, 'number') && lhs < rhs; } ],
+        '=':  [ 3, function(lhs, rhs) { return lhs === rhs; } ],
+        '!=': [ 3, function(lhs, rhs) { return lhs !== rhs; } ],
+        '&&': [ 2, function(lhs, rhs) { return lhs && rhs; } ],
+        '||': [ 1, function(lhs, rhs) { return lhs || rhs; } ]
+    };
+
+    function exprLex(str, off) {
+        var v, m = exprPat.exec(str.substr(off));
+        if (m) {
+            off += m[0].length;
+            v = m[1] || m[2] || m[3] || m[5] || m[6];
+            if (m[1] || m[2] || m[3]) return [off, 0, jsonParse(v)];
+            else if (m[4]) return [off, 0, undefined];
+            return [off, v];
+        }
+    }
+
+    function exprParse2(str, off) {
+        if (!off) off = 0;
+        // first we expect a value or a '('
+        var l = exprLex(str, off),
+            lhs;
+        if (l && l[1] === '(') {
+            lhs = exprParse2(str, l[0]);
+            var p = exprLex(str, lhs[0]);
+            if (!p || p[1] !== ')') te('epex');
+            off = p[0];
+            lhs = [ '(', lhs[1] ];
+        } else if (!l || (l[1] && l[1] != 'x')) {
+            te("ee");
+        } else {
+            lhs = ((l[1] === 'x') ? undefined : l[2]);
+            off = l[0];
+        }
+
+        // now we expect a binary operator or a ')'
+        var op = exprLex(str, off);
+        if (!op || op[1] == ')') return [off, lhs];
+        else if (op[1] == 'x' || !op[1]) {
+            te('bop');
+        }
+
+        // tail recursion to fetch the rhs expression
+        var rhs = exprParse2(str, op[0]);
+        off = rhs[0];
+        rhs = rhs[1];
+
+        // and now precedence!  how shall we put everything together?
+        var v;
+        if (typeof rhs !== 'object' || rhs[0] === '(' || operators[op[1]][0] < operators[rhs[1]][0] ) {
+            v = [lhs, op[1], rhs];
+        }
+        else {
+            v = rhs;
+            while (typeof rhs[0] === 'object' && rhs[0][0] != '(' && operators[op[1]][0] >= operators[rhs[0][1]][0]) {
+                rhs = rhs[0];
+            }
+            rhs[0] = [lhs, op[1], rhs[0]];
+        }
+        return [off, v];
+    }
+
+    function exprParse(str, off) {
+        function deparen(v) {
+            if (typeof v !== 'object' || v === null) return v;
+            else if (v[0] === '(') return deparen(v[1]);
+            else return [deparen(v[0]), v[1], deparen(v[2])];
+        }
+        var e = exprParse2(str, off ? off : 0);
+        return [e[0], deparen(e[1])];
+    }
+
+    function exprEval(expr, x) {
+        if (expr === undefined) return x;
+        else if (expr === null || typeof expr !== 'object') {
+            return expr;
+        }
+        var lhs = exprEval(expr[0], x),
+            rhs = exprEval(expr[2], x);
+        return operators[expr[1]][1](lhs, rhs);
+    }
+
+    // THE PARSER
+
+    function parse(str, off, nested, hints) {
+        if (!nested) hints = {};
+
+        var a = [], am, readParen;
+        if (!off) off = 0; 
+
+        while (true) {
+            var s = parse_selector(str, off, hints);
+            a.push(s[1]);
+            s = lex(str, off = s[0]);
+            if (s && s[1] === " ") s = lex(str, off = s[0]);
+            if (!s) break;
+            // now we've parsed a selector, and have something else...
+            if (s[1] === ">" || s[1] === "~") {
+                if (s[1] === "~") hints.usesSiblingOp = true;
+                a.push(s[1]);
+                off = s[0];
+            } else if (s[1] === ",") {
+                if (am === undefined) am = [ ",", a ];
+                else am.push(a);
+                a = [];
+                off = s[0];
+            } else if (s[1] === ")") {
+                if (!nested) te("ucp");
+                readParen = 1;
+                off = s[0];
+                break;
+            }
+        }
+        if (nested && !readParen) te("mcp");
+        if (am) am.push(a);
+        var rv;
+        if (!nested && hints.usesSiblingOp) {
+            rv = normalize(am ? am : a);
+        } else {
+            rv = am ? am : a;
+        }
+        return [off, rv];
+    }
+
+    function normalizeOne(sel) {
+        var sels = [], s;
+        for (var i = 0; i < sel.length; i++) {
+            if (sel[i] === '~') {
+                // `A ~ B` maps to `:has(:root > A) > B`
+                // `Z A ~ B` maps to `Z :has(:root > A) > B, Z:has(:root > A) > B`
+                // This first clause, takes care of the first case, and the first half of the latter case.
+                if (i < 2 || sel[i-2] != '>') {
+                    s = sel.slice(0,i-1);
+                    s = s.concat([{has:[[{pc: ":root"}, ">", sel[i-1]]]}, ">"]);
+                    s = s.concat(sel.slice(i+1));
+                    sels.push(s);
+                }
+                // here we take care of the second half of above:
+                // (`Z A ~ B` maps to `Z :has(:root > A) > B, Z :has(:root > A) > B`)
+                // and a new case:
+                // Z > A ~ B maps to Z:has(:root > A) > B
+                if (i > 1) {
+                    var at = sel[i-2] === '>' ? i-3 : i-2;
+                    s = sel.slice(0,at);
+                    var z = {};
+                    for (var k in sel[at]) if (sel[at].hasOwnProperty(k)) z[k] = sel[at][k];
+                    if (!z.has) z.has = [];
+                    z.has.push([{pc: ":root"}, ">", sel[i-1]]);
+                    s = s.concat(z, '>', sel.slice(i+1));
+                    sels.push(s);
+                }
+                break;
+            }
+        }
+        if (i == sel.length) return sel;
+        return sels.length > 1 ? [','].concat(sels) : sels[0];
+    }
+
+    function normalize(sels) {
+        if (sels[0] === ',') {
+            var r = [","];
+            for (var i = i; i < sels.length; i++) {
+                var s = normalizeOne(s[i]);
+                r = r.concat(s[0] === "," ? s.slice(1) : s);
+            }
+            return r;
+        } else {
+            return normalizeOne(sels);
+        }
+    }
+
+    function parse_selector(str, off, hints) {
+        var soff = off;
+        var s = { };
+        var l = lex(str, off);
+        // skip space
+        if (l && l[1] === " ") { soff = off = l[0]; l = lex(str, off); }
+        if (l && l[1] === toks.typ) {
+            s.type = l[2];
+            l = lex(str, (off = l[0]));
+        } else if (l && l[1] === "*") {
+            // don't bother representing the universal sel, '*' in the
+            // parse tree, cause it's the default
+            l = lex(str, (off = l[0]));
+        }
+
+        // now support either an id or a pc
+        while (true) {
+            if (l === undefined) {
+                break;
+            } else if (l[1] === toks.ide) {
+                if (s.id) te("nmi");
+                s.id = l[2];
+            } else if (l[1] === toks.psc) {
+                if (s.pc || s.pf) te("mpc");
+                // collapse first-child and last-child into nth-child expressions
+                if (l[2] === ":first-child") {
+                    s.pf = ":nth-child";
+                    s.a = 0;
+                    s.b = 1;
+                } else if (l[2] === ":last-child") {
+                    s.pf = ":nth-last-child";
+                    s.a = 0;
+                    s.b = 1;
+                } else {
+                    s.pc = l[2];
+                }
+            } else if (l[1] === toks.psf) {
+                if (l[2] === ":val" || l[2] === ":contains") {
+                    s.expr = [ undefined, l[2] === ":val" ? "=" : "*=", undefined];
+                    // any amount of whitespace, followed by paren, string, paren
+                    l = lex(str, (off = l[0]));
+                    if (l && l[1] === " ") l = lex(str, off = l[0]);
+                    if (!l || l[1] !== "(") te("pex");
+                    l = lex(str, (off = l[0]));
+                    if (l && l[1] === " ") l = lex(str, off = l[0]);
+                    if (!l || l[1] !== toks.str) te("sex");
+                    s.expr[2] = l[2];
+                    l = lex(str, (off = l[0]));
+                    if (l && l[1] === " ") l = lex(str, off = l[0]);
+                    if (!l || l[1] !== ")") te("epex");
+                } else if (l[2] === ":has") {
+                    // any amount of whitespace, followed by paren
+                    l = lex(str, (off = l[0]));
+                    if (l && l[1] === " ") l = lex(str, off = l[0]);
+                    if (!l || l[1] !== "(") te("pex");
+                    var h = parse(str, l[0], true);
+                    l[0] = h[0];
+                    if (!s.has) s.has = [];
+                    s.has.push(h[1]);
+                } else if (l[2] === ":expr") {
+                    if (s.expr) te("mexp");
+                    var e = exprParse(str, l[0]);
+                    l[0] = e[0];
+                    s.expr = e[1];
+                } else {
+                    if (s.pc || s.pf ) te("mpc");
+                    s.pf = l[2];
+                    var m = nthPat.exec(str.substr(l[0]));
+                    if (!m) te("mepf");
+                    if (m[5]) {
+                        s.a = 2;
+                        s.b = (m[5] === "odd") ? 1 : 0;
+                    } else if (m[6]) {
+                        s.a = 0;
+                        s.b = parseInt(m[6], 10);
+                    } else {
+                        s.a = parseInt((m[1] ? m[1] : "+") + (m[2] ? m[2] : "1"),10);
+                        s.b = m[3] ? parseInt(m[3] + m[4],10) : 0;
+                    }
+                    l[0] += m[0].length;
+                }
+            } else {
+                break;
+            }
+            l = lex(str, (off = l[0]));
+        }
+
+        // now if we didn't actually parse anything it's an error
+        if (soff === off) te("se");
+
+        return [off, s];
+    }
+
+    // THE EVALUATOR
+
+    function isArray(o) {
+        return Array.isArray ? Array.isArray(o) : 
+          toString.call(o) === "[object Array]";
+    }
+
+    function mytypeof(o) {
+        if (o === null) return "null";
+        var to = typeof o;
+        if (to === "object" && isArray(o)) to = "array";
+        return to;
+    }
+
+    function mn(node, sel, id, num, tot) {
+        var sels = [];
+        var cs = (sel[0] === ">") ? sel[1] : sel[0];
+        var m = true, mod;
+        if (cs.type) m = m && (cs.type === mytypeof(node));
+        if (cs.id)   m = m && (cs.id === id);
+        if (m && cs.pf) {
+            if (cs.pf === ":nth-last-child") num = tot - num;
+            else num++;
+            if (cs.a === 0) {
+                m = cs.b === num;
+            } else {
+                mod = ((num - cs.b) % cs.a);
+
+                m = (!mod && ((num*cs.a + cs.b) >= 0));
+            }
+        }
+        if (m && cs.has) {
+            // perhaps we should augment forEach to handle a return value
+            // that indicates "client cancels traversal"?
+            var bail = function() { throw 42; };
+            for (var i = 0; i < cs.has.length; i++) {
+                try {
+                    forEach(cs.has[i], node, bail);
+                } catch (e) {
+                    if (e === 42) continue;
+                }
+                m = false;
+                break;
+            }
+        }
+        if (m && cs.expr) {
+            m = exprEval(cs.expr, node);
+        }
+        // should we repeat this selector for descendants?
+        if (sel[0] !== ">" && sel[0].pc !== ":root") sels.push(sel);
+
+        if (m) {
+            // is there a fragment that we should pass down?
+            if (sel[0] === ">") { if (sel.length > 2) { m = false; sels.push(sel.slice(2)); } }
+            else if (sel.length > 1) { m = false; sels.push(sel.slice(1)); }
+        }
+
+        return [m, sels];
+    }
+
+    function forEach(sel, obj, fun, id, num, tot) {
+        var a = (sel[0] === ",") ? sel.slice(1) : [sel],
+        a0 = [],
+        call = false,
+        i = 0, j = 0, k, x;
+        for (i = 0; i < a.length; i++) {
+            x = mn(obj, a[i], id, num, tot);
+            if (x[0]) {
+                call = true;
+            }
+            for (j = 0; j < x[1].length; j++) {
+                a0.push(x[1][j]);
+            }
+        }
+        if (a0.length && typeof obj === "object") {
+            if (a0.length >= 1) {
+                a0.unshift(",");
+            }
+            if (isArray(obj)) {
+                for (i = 0; i < obj.length; i++) {
+                    forEach(a0, obj[i], fun, undefined, i, obj.length);
+                }
+            } else {
+                for (k in obj) {
+                    if (obj.hasOwnProperty(k)) {
+                        forEach(a0, obj[k], fun, k);
+                    }
+                }
+            }
+        }
+        if (call && fun) {
+            fun(obj);
+        }
+    }
+
+    function match(sel, obj) {
+        var a = [];
+        forEach(sel, obj, function(x) {
+            a.push(x);
+        });
+        return a;
+    }
+
+    function compile(sel) {
+        return {
+            sel: parse(sel)[1],
+            match: function(obj){
+                return match(this.sel, obj);
+            },
+            forEach: function(obj, fun) {
+                return forEach(this.sel, obj, fun);
+            }
+        };
+    }
+
+    exports._lex = lex;
+    exports._parse = parse;
+    exports.match = function (sel, obj) {
+        return compile(sel).match(obj);
+    };
+    exports.forEach = function(sel, obj, fun) {
+        return compile(sel).forEach(obj, fun);
+    };
+    exports.compile = compile;
+})(typeof exports === "undefined" ? (window.JSONSelect = {}) : exports);
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/dist/jsonselect.min.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/dist/jsonselect.min.js
new file mode 100644
index 0000000..ec5015f
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/dist/jsonselect.min.js
@@ -0,0 +1 @@
+(function(a){function z(a){return{sel:q(a)[1],match:function(a){return y(this.sel,a)},forEach:function(a,b){return x(this.sel,a,b)}}}function y(a,b){var c=[];x(a,b,function(a){c.push(a)});return c}function x(a,b,c,d,e,f){var g=a[0]===","?a.slice(1):[a],h=[],i=!1,j=0,k=0,l,m;for(j=0;j<g.length;j++){m=w(b,g[j],d,e,f),m[0]&&(i=!0);for(k=0;k<m[1].length;k++)h.push(m[1][k])}if(h.length&&typeof b=="object"){h.length>=1&&h.unshift(",");if(u(b))for(j=0;j<b.length;j++)x(h,b[j],c,undefined,j,b.length);else for(l in b)b.hasOwnProperty(l)&&x(h,b[l],c,l)}i&&c&&c(b)}function w(a,b,c,d,e){var f=[],g=b[0]===">"?b[1]:b[0],h=!0,i;g.type&&(h=h&&g.type===v(a)),g.id&&(h=h&&g.id===c),h&&g.pf&&(g.pf===":nth-last-child"?d=e-d:d++,g.a===0?h=g.b===d:(i=(d-g.b)%g.a,h=!i&&d*g.a+g.b>=0));if(h&&g.has){var j=function(){throw 42};for(var k=0;k<g.has.length;k++){try{x(g.has[k],a,j)}catch(l){if(l===42)continue}h=!1;break}}h&&g.expr&&(h=p(g.expr,a)),b[0]!==">"&&b[0].pc!==":root"&&f.push(b),h&&(b[0]===">"?b.length>2&&(h=!1,f.push(b.slice(2))):b.length>1&&(h=!1,f.push(b.slice(1))));return[h,f]}function v(a){if(a===null)return"null";var b=typeof a;b==="object"&&u(a)&&(b="array");return b}function u(a){return Array.isArray?Array.isArray(a):b.call(a)==="[object Array]"}function t(a,b,c){var d=b,g={},j=i(a,b);j&&j[1]===" "&&(d=b=j[0],j=i(a,b)),j&&j[1]===f.typ?(g.type=j[2],j=i(a,b=j[0])):j&&j[1]==="*"&&(j=i(a,b=j[0]));for(;;){if(j===undefined)break;if(j[1]===f.ide)g.id&&e("nmi"),g.id=j[2];else if(j[1]===f.psc)(g.pc||g.pf)&&e("mpc"),j[2]===":first-child"?(g.pf=":nth-child",g.a=0,g.b=1):j[2]===":last-child"?(g.pf=":nth-last-child",g.a=0,g.b=1):g.pc=j[2];else{if(j[1]!==f.psf)break;if(j[2]===":val"||j[2]===":contains")g.expr=[undefined,j[2]===":val"?"=":"*=",undefined],j=i(a,b=j[0]),j&&j[1]===" "&&(j=i(a,b=j[0])),(!j||j[1]!=="(")&&e("pex"),j=i(a,b=j[0]),j&&j[1]===" "&&(j=i(a,b=j[0])),(!j||j[1]!==f.str)&&e("sex"),g.expr[2]=j[2],j=i(a,b=j[0]),j&&j[1]===" "&&(j=i(a,b=j[0])),(!j||j[1]!==")")&&e("epex");else if(j[2]===":has"){j=i(a,b=j[0]),j&&j[1]===" "&&(j=i(a,b=j[0])),(!j||j[1]!=="(")&&e("pex");var k=q(a,j[0],!0);j[0]=k[0],g.has||(g.has=[]),g.has.push(k[1])}else if(j[2]===":expr"){g.expr&&e("mexp");var l=o(a,j[0]);j[0]=l[0],g.expr=l[1]}else{(g.pc||g.pf)&&e("mpc"),g.pf=j[2];var m=h.exec(a.substr(j[0]));m||e("mepf"),m[5]?(g.a=2,g.b=m[5]==="odd"?1:0):m[6]?(g.a=0,g.b=parseInt(m[6],10)):(g.a=parseInt((m[1]?m[1]:"+")+(m[2]?m[2]:"1"),10),g.b=m[3]?parseInt(m[3]+m[4],10):0),j[0]+=m[0].length}}j=i(a,b=j[0])}d===b&&e("se");return[b,g]}function s(a){if(a[0]===","){var b=[","];for(var c=c;c<a.length;c++){var d=r(d[c]);b=b.concat(d[0]===","?d.slice(1):d)}return b}return r(a)}function r(a){var b=[],c;for(var d=0;d<a.length;d++)if(a[d]==="~"){if(d<2||a[d-2]!=">")c=a.slice(0,d-1),c=c.concat([{has:[[{pc:":root"},">",a[d-1]]]},">"]),c=c.concat(a.slice(d+1)),b.push(c);if(d>1){var e=a[d-2]===">"?d-3:d-2;c=a.slice(0,e);var f={};for(var g in a[e])a[e].hasOwnProperty(g)&&(f[g]=a[e][g]);f.has||(f.has=[]),f.has.push([{pc:":root"},">",a[d-1]]),c=c.concat(f,">",a.slice(d+1)),b.push(c)}break}if(d==a.length)return a;return b.length>1?[","].concat(b):b[0]}function q(a,b,c,d){c||(d={});var f=[],g,h;b||(b=0);for(;;){var j=t(a,b,d);f.push(j[1]),j=i(a,b=j[0]),j&&j[1]===" "&&(j=i(a,b=j[0]));if(!j)break;if(j[1]===">"||j[1]==="~")j[1]==="~"&&(d.usesSiblingOp=!0),f.push(j[1]),b=j[0];else if(j[1]===",")g===undefined?g=[",",f]:g.push(f),f=[],b=j[0];else if(j[1]===")"){c||e("ucp"),h=1,b=j[0];break}}c&&!h&&e("mcp"),g&&g.push(f);var k;!c&&d.usesSiblingOp?k=s(g?g:f):k=g?g:f;return[b,k]}function p(a,b){if(a===undefined)return b;if(a===null||typeof a!="object")return a;var c=p(a[0],b),d=p(a[2],b);return l[a[1]][1](c,d)}function o(a,b){function c(a){return typeof a!="object"||a===null?a:a[0]==="("?c(a[1]):[c(a[0]),a[1],c(a[2])]}var d=n(a,b?b:0);return[d[0],c(d[1])]}function n(a,b){b||(b=0);var c=m(a,b),d;if(c&&c[1]==="("){d=n(a,c[0]);var f=m(a,d[0]);(!f||f[1]!==")")&&e("epex"),b=f[0],d=["(",d[1]]}else!c||c[1]&&c[1]!="x"?e("ee"):(d=c[1]==="x"?undefined:c[2],b=c[0]);var g=m(a,b);if(!g||g[1]==")")return[b,d];(g[1]=="x"||!g[1])&&e("bop");var h=n(a,g[0]);b=h[0],h=h[1];var i;if(typeof h!="object"||h[0]==="("||l[g[1]][0]<l[h[1]][0])i=[d,g[1],h];else{i=h;while(typeof h[0]=="object"&&h[0][0]!="("&&l[g[1]][0]>=l[h[0][1]][0])h=h[0];h[0]=[d,g[1],h[0]]}return[b,i]}function m(a,b){var d,e=j.exec(a.substr(b));if(e){b+=e[0].length,d=e[1]||e[2]||e[3]||e[5]||e[6];if(e[1]||e[2]||e[3])return[b,0,c(d)];if(e[4])return[b,0,undefined];return[b,d]}}function k(a,b){return typeof a===b}function i(a,b){b||(b=0);var d=g.exec(a.substr(b));if(!d)return undefined;b+=d[0].length;var h;d[1]?h=[b," "]:d[2]?h=[b,d[0]]:d[3]?h=[b,f.typ,d[0]]:d[4]?h=[b,f.psc,d[0]]:d[5]?h=[b,f.psf,d[0]]:d[6]?e("upc"):d[8]?h=[b,d[7]?f.ide:f.str,c(d[8])]:d[9]?e("ujs"):d[10]&&(h=[b,f.ide,d[10].replace(/\\([^\r\n\f0-9a-fA-F])/g,"$1")]);return h}function e(a){throw new Error(d[a])}function c(a){try{if(JSON&&JSON.parse)return JSON.parse(a);return(new Function("return "+a))()}catch(b){e("ijs")}}var b=Object.prototype.toString,d={bop:"binary operator expected",ee:"expression expected",epex:"closing paren expected ')'",ijs:"invalid json string",mcp:"missing closing paren",mepf:"malformed expression in pseudo-function",mexp:"multiple expressions not allowed",mpc:"multiple pseudo classes (:xxx) not allowed",nmi:"multiple ids not allowed",pex:"opening paren expected '('",se:"selector expected",sex:"string expected",sra:"string required after '.'",uc:"unrecognized char",ucp:"unexpected closing paren",ujs:"unclosed json string",upc:"unrecognized pseudo class"},f={psc:1,psf:2,typ:3,str:4,ide:5},g=new RegExp('^(?:([\\r\\n\\t\\ ]+)|([~*,>\\)\\(])|(string|boolean|null|array|object|number)|(:(?:root|first-child|last-child|only-child))|(:(?:nth-child|nth-last-child|has|expr|val|contains))|(:\\w+)|(?:(\\.)?(\\"(?:[^\\\\]|\\\\[^\\"])*\\"))|(\\")|\\.((?:[_a-zA-Z]|[^\\0-\\0177]|\\\\[^\\r\\n\\f0-9a-fA-F])(?:[_a-zA-Z0-9\\-]|[^\\u0000-\\u0177]|(?:\\\\[^\\r\\n\\f0-9a-fA-F]))*))'),h=/^\s*\(\s*(?:([+\-]?)([0-9]*)n\s*(?:([+\-])\s*([0-9]))?|(odd|even)|([+\-]?[0-9]+))\s*\)/,j=new RegExp('^\\s*(?:(true|false|null)|(-?\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?)|("(?:[^\\]|\\[^"])*")|(x)|(&&|\\|\\||[\\$\\^<>!\\*]=|[=+\\-*/%<>])|([\\(\\)]))'),l={"*":[9,function(a,b){return a*b}],"/":[9,function(a,b){return a/b}],"%":[9,function(a,b){return a%b}],"+":[7,function(a,b){return a+b}],"-":[7,function(a,b){return a-b}],"<=":[5,function(a,b){return k(a,"number")&&k(b,"number")&&a<=b}],">=":[5,function(a,b){return k(a,"number")&&k(b,"number")&&a>=b}],"$=":[5,function(a,b){return k(a,"string")&&k(b,"string")&&a.lastIndexOf(b)===a.length-b.length}],"^=":[5,function(a,b){return k(a,"string")&&k(b,"string")&&a.indexOf(b)===0}],"*=":[5,function(a,b){return k(a,"string")&&k(b,"string")&&a.indexOf(b)!==-1}],">":[5,function(a,b){return k(a,"number")&&k(b,"number")&&a>b}],"<":[5,function(a,b){return k(a,"number")&&k(b,"number")&&a<b}],"=":[3,function(a,b){return a===b}],"!=":[3,function(a,b){return a!==b}],"&&":[2,function(a,b){return a&&b}],"||":[1,function(a,b){return a||b}]};a._lex=i,a._parse=q,a.match=function(a,b){return z(a).match(b)},a.forEach=function(a,b,c){return z(a).forEach(b,c)},a.compile=z})(typeof exports=="undefined"?window.JSONSelect={}:exports)
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/dist/jsonselect.min.js.gz b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/dist/jsonselect.min.js.gz
new file mode 100644
index 0000000..4eb9b37
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/dist/jsonselect.min.js.gz
Binary files differ
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/jsonselect.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/jsonselect.js
new file mode 100644
index 0000000..f0611ea
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/jsonselect.js
@@ -0,0 +1,522 @@
+/*! Copyright (c) 2011, Lloyd Hilaiel, ISC License */
+/*
+ * This is the JSONSelect reference implementation, in javascript.
+ */
+(function(exports) {
+
+    var // localize references
+    toString = Object.prototype.toString;
+
+    function jsonParse(str) {
+      try {
+          if(JSON && JSON.parse){
+              return JSON.parse(str);
+          }
+          return (new Function("return " + str))();
+      } catch(e) {
+        te("ijs", e.message);
+      }
+    }
+
+    // emitted error codes.
+    var errorCodes = {
+        "bop":  "binary operator expected",
+        "ee":   "expression expected",
+        "epex": "closing paren expected ')'",
+        "ijs":  "invalid json string",
+        "mcp":  "missing closing paren",
+        "mepf": "malformed expression in pseudo-function",
+        "mexp": "multiple expressions not allowed",
+        "mpc":  "multiple pseudo classes (:xxx) not allowed",
+        "nmi":  "multiple ids not allowed",
+        "pex":  "opening paren expected '('",
+        "se":   "selector expected",
+        "sex":  "string expected",
+        "sra":  "string required after '.'",
+        "uc":   "unrecognized char",
+        "ucp":  "unexpected closing paren",
+        "ujs":  "unclosed json string",
+        "upc":  "unrecognized pseudo class"
+    };
+
+    // throw an error message
+    function te(ec, context) {
+      throw new Error(errorCodes[ec] + ( context && " in '" + context + "'"));
+    }
+
+    // THE LEXER
+    var toks = {
+        psc: 1, // pseudo class
+        psf: 2, // pseudo class function
+        typ: 3, // type
+        str: 4, // string
+        ide: 5  // identifiers (or "classes", stuff after a dot)
+    };
+
+    // The primary lexing regular expression in jsonselect
+    var pat = new RegExp(
+        "^(?:" +
+        // (1) whitespace
+        "([\\r\\n\\t\\ ]+)|" +
+        // (2) one-char ops
+        "([~*,>\\)\\(])|" +
+        // (3) types names
+        "(string|boolean|null|array|object|number)|" +
+        // (4) pseudo classes
+        "(:(?:root|first-child|last-child|only-child))|" +
+        // (5) pseudo functions
+        "(:(?:nth-child|nth-last-child|has|expr|val|contains))|" +
+        // (6) bogusly named pseudo something or others
+        "(:\\w+)|" +
+        // (7 & 8) identifiers and JSON strings
+        "(?:(\\.)?(\\\"(?:[^\\\\\\\"]|\\\\[^\\\"])*\\\"))|" +
+        // (8) bogus JSON strings missing a trailing quote
+        "(\\\")|" +
+        // (9) identifiers (unquoted)
+        "\\.((?:[_a-zA-Z]|[^\\0-\\0177]|\\\\[^\\r\\n\\f0-9a-fA-F])(?:[_a-zA-Z0-9\\-]|[^\\u0000-\\u0177]|(?:\\\\[^\\r\\n\\f0-9a-fA-F]))*)" +
+        ")"
+    );
+
+    // A regular expression for matching "nth expressions" (see grammar, what :nth-child() eats)
+    var nthPat = /^\s*\(\s*(?:([+\-]?)([0-9]*)n\s*(?:([+\-])\s*([0-9]))?|(odd|even)|([+\-]?[0-9]+))\s*\)/;
+    function lex(str, off) {
+        if (!off) off = 0;
+        var m = pat.exec(str.substr(off));
+        if (!m) return undefined;
+        off+=m[0].length;
+        var a;
+        if (m[1]) a = [off, " "];
+        else if (m[2]) a = [off, m[0]];
+        else if (m[3]) a = [off, toks.typ, m[0]];
+        else if (m[4]) a = [off, toks.psc, m[0]];
+        else if (m[5]) a = [off, toks.psf, m[0]];
+        else if (m[6]) te("upc", str);
+        else if (m[8]) a = [off, m[7] ? toks.ide : toks.str, jsonParse(m[8])];
+        else if (m[9]) te("ujs", str);
+        else if (m[10]) a = [off, toks.ide, m[10].replace(/\\([^\r\n\f0-9a-fA-F])/g,"$1")];
+        return a;
+    }
+
+    // THE EXPRESSION SUBSYSTEM
+
+    var exprPat = new RegExp(
+            // skip and don't capture leading whitespace
+            "^\\s*(?:" +
+            // (1) simple vals
+            "(true|false|null)|" + 
+            // (2) numbers
+            "(-?\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?)|" +
+            // (3) strings
+            "(\"(?:[^\\]|\\[^\"])*\")|" +
+            // (4) the 'x' value placeholder
+            "(x)|" +
+            // (5) binops
+            "(&&|\\|\\||[\\$\\^<>!\\*]=|[=+\\-*/%<>])|" +
+            // (6) parens
+            "([\\(\\)])" +
+            ")"
+    );
+
+    function is(o, t) { return typeof o === t; }
+    var operators = {
+        '*':  [ 9, function(lhs, rhs) { return lhs * rhs; } ],
+        '/':  [ 9, function(lhs, rhs) { return lhs / rhs; } ],
+        '%':  [ 9, function(lhs, rhs) { return lhs % rhs; } ],
+        '+':  [ 7, function(lhs, rhs) { return lhs + rhs; } ],
+        '-':  [ 7, function(lhs, rhs) { return lhs - rhs; } ],
+        '<=': [ 5, function(lhs, rhs) { return is(lhs, 'number') && is(rhs, 'number') && lhs <= rhs; } ],
+        '>=': [ 5, function(lhs, rhs) { return is(lhs, 'number') && is(rhs, 'number') && lhs >= rhs; } ],
+        '$=': [ 5, function(lhs, rhs) { return is(lhs, 'string') && is(rhs, 'string') && lhs.lastIndexOf(rhs) === lhs.length - rhs.length; } ],
+        '^=': [ 5, function(lhs, rhs) { return is(lhs, 'string') && is(rhs, 'string') && lhs.indexOf(rhs) === 0; } ],
+        '*=': [ 5, function(lhs, rhs) { return is(lhs, 'string') && is(rhs, 'string') && lhs.indexOf(rhs) !== -1; } ],
+        '>':  [ 5, function(lhs, rhs) { return is(lhs, 'number') && is(rhs, 'number') && lhs > rhs; } ],
+        '<':  [ 5, function(lhs, rhs) { return is(lhs, 'number') && is(rhs, 'number') && lhs < rhs; } ],
+        '=':  [ 3, function(lhs, rhs) { return lhs === rhs; } ],
+        '!=': [ 3, function(lhs, rhs) { return lhs !== rhs; } ],
+        '&&': [ 2, function(lhs, rhs) { return lhs && rhs; } ],
+        '||': [ 1, function(lhs, rhs) { return lhs || rhs; } ]
+    };
+
+    function exprLex(str, off) {
+        var v, m = exprPat.exec(str.substr(off));
+        if (m) {
+            off += m[0].length;
+            v = m[1] || m[2] || m[3] || m[5] || m[6];
+            if (m[1] || m[2] || m[3]) return [off, 0, jsonParse(v)];
+            else if (m[4]) return [off, 0, undefined];
+            return [off, v];
+        }
+    }
+
+    function exprParse2(str, off) {
+        if (!off) off = 0;
+        // first we expect a value or a '('
+        var l = exprLex(str, off),
+            lhs;
+        if (l && l[1] === '(') {
+            lhs = exprParse2(str, l[0]);
+            var p = exprLex(str, lhs[0]);
+            if (!p || p[1] !== ')') te('epex', str);
+            off = p[0];
+            lhs = [ '(', lhs[1] ];
+        } else if (!l || (l[1] && l[1] != 'x')) {
+            te("ee", str + " - " + ( l[1] && l[1] ));
+        } else {
+            lhs = ((l[1] === 'x') ? undefined : l[2]);
+            off = l[0];
+        }
+
+        // now we expect a binary operator or a ')'
+        var op = exprLex(str, off);
+        if (!op || op[1] == ')') return [off, lhs];
+        else if (op[1] == 'x' || !op[1]) {
+            te('bop', str + " - " + ( op[1] && op[1] ));
+        }
+
+        // tail recursion to fetch the rhs expression
+        var rhs = exprParse2(str, op[0]);
+        off = rhs[0];
+        rhs = rhs[1];
+
+        // and now precedence!  how shall we put everything together?
+        var v;
+        if (typeof rhs !== 'object' || rhs[0] === '(' || operators[op[1]][0] < operators[rhs[1]][0] ) {
+            v = [lhs, op[1], rhs];
+        }
+        else {
+            v = rhs;
+            while (typeof rhs[0] === 'object' && rhs[0][0] != '(' && operators[op[1]][0] >= operators[rhs[0][1]][0]) {
+                rhs = rhs[0];
+            }
+            rhs[0] = [lhs, op[1], rhs[0]];
+        }
+        return [off, v];
+    }
+
+    function exprParse(str, off) {
+        function deparen(v) {
+            if (typeof v !== 'object' || v === null) return v;
+            else if (v[0] === '(') return deparen(v[1]);
+            else return [deparen(v[0]), v[1], deparen(v[2])];
+        }
+        var e = exprParse2(str, off ? off : 0);
+        return [e[0], deparen(e[1])];
+    }
+
+    function exprEval(expr, x) {
+        if (expr === undefined) return x;
+        else if (expr === null || typeof expr !== 'object') {
+            return expr;
+        }
+        var lhs = exprEval(expr[0], x),
+            rhs = exprEval(expr[2], x);
+        return operators[expr[1]][1](lhs, rhs);
+    }
+
+    // THE PARSER
+
+    function parse(str, off, nested, hints) {
+        if (!nested) hints = {};
+
+        var a = [], am, readParen;
+        if (!off) off = 0; 
+
+        while (true) {
+            var s = parse_selector(str, off, hints);
+            a.push(s[1]);
+            s = lex(str, off = s[0]);
+            if (s && s[1] === " ") s = lex(str, off = s[0]);
+            if (!s) break;
+            // now we've parsed a selector, and have something else...
+            if (s[1] === ">" || s[1] === "~") {
+                if (s[1] === "~") hints.usesSiblingOp = true;
+                a.push(s[1]);
+                off = s[0];
+            } else if (s[1] === ",") {
+                if (am === undefined) am = [ ",", a ];
+                else am.push(a);
+                a = [];
+                off = s[0];
+            } else if (s[1] === ")") {
+                if (!nested) te("ucp", s[1]);
+                readParen = 1;
+                off = s[0];
+                break;
+            }
+        }
+        if (nested && !readParen) te("mcp", str);
+        if (am) am.push(a);
+        var rv;
+        if (!nested && hints.usesSiblingOp) {
+            rv = normalize(am ? am : a);
+        } else {
+            rv = am ? am : a;
+        }
+        return [off, rv];
+    }
+
+    function normalizeOne(sel) {
+        var sels = [], s;
+        for (var i = 0; i < sel.length; i++) {
+            if (sel[i] === '~') {
+                // `A ~ B` maps to `:has(:root > A) > B`
+                // `Z A ~ B` maps to `Z :has(:root > A) > B, Z:has(:root > A) > B`
+                // This first clause, takes care of the first case, and the first half of the latter case.
+                if (i < 2 || sel[i-2] != '>') {
+                    s = sel.slice(0,i-1);
+                    s = s.concat([{has:[[{pc: ":root"}, ">", sel[i-1]]]}, ">"]);
+                    s = s.concat(sel.slice(i+1));
+                    sels.push(s);
+                }
+                // here we take care of the second half of above:
+                // (`Z A ~ B` maps to `Z :has(:root > A) > B, Z :has(:root > A) > B`)
+                // and a new case:
+                // Z > A ~ B maps to Z:has(:root > A) > B
+                if (i > 1) {
+                    var at = sel[i-2] === '>' ? i-3 : i-2;
+                    s = sel.slice(0,at);
+                    var z = {};
+                    for (var k in sel[at]) if (sel[at].hasOwnProperty(k)) z[k] = sel[at][k];
+                    if (!z.has) z.has = [];
+                    z.has.push([{pc: ":root"}, ">", sel[i-1]]);
+                    s = s.concat(z, '>', sel.slice(i+1));
+                    sels.push(s);
+                }
+                break;
+            }
+        }
+        if (i == sel.length) return sel;
+        return sels.length > 1 ? [','].concat(sels) : sels[0];
+    }
+
+    function normalize(sels) {
+        if (sels[0] === ',') {
+            var r = [","];
+            for (var i = i; i < sels.length; i++) {
+                var s = normalizeOne(s[i]);
+                r = r.concat(s[0] === "," ? s.slice(1) : s);
+            }
+            return r;
+        } else {
+            return normalizeOne(sels);
+        }
+    }
+
+    function parse_selector(str, off, hints) {
+        var soff = off;
+        var s = { };
+        var l = lex(str, off);
+        // skip space
+        if (l && l[1] === " ") { soff = off = l[0]; l = lex(str, off); }
+        if (l && l[1] === toks.typ) {
+            s.type = l[2];
+            l = lex(str, (off = l[0]));
+        } else if (l && l[1] === "*") {
+            // don't bother representing the universal sel, '*' in the
+            // parse tree, cause it's the default
+            l = lex(str, (off = l[0]));
+        }
+
+        // now support either an id or a pc
+        while (true) {
+            if (l === undefined) {
+                break;
+            } else if (l[1] === toks.ide) {
+                if (s.id) te("nmi", l[1]);
+                s.id = l[2];
+            } else if (l[1] === toks.psc) {
+                if (s.pc || s.pf) te("mpc", l[1]);
+                // collapse first-child and last-child into nth-child expressions
+                if (l[2] === ":first-child") {
+                    s.pf = ":nth-child";
+                    s.a = 0;
+                    s.b = 1;
+                } else if (l[2] === ":last-child") {
+                    s.pf = ":nth-last-child";
+                    s.a = 0;
+                    s.b = 1;
+                } else {
+                    s.pc = l[2];
+                }
+            } else if (l[1] === toks.psf) {
+                if (l[2] === ":val" || l[2] === ":contains") {
+                    s.expr = [ undefined, l[2] === ":val" ? "=" : "*=", undefined];
+                    // any amount of whitespace, followed by paren, string, paren
+                    l = lex(str, (off = l[0]));
+                    if (l && l[1] === " ") l = lex(str, off = l[0]);
+                    if (!l || l[1] !== "(") te("pex", str);
+                    l = lex(str, (off = l[0]));
+                    if (l && l[1] === " ") l = lex(str, off = l[0]);
+                    if (!l || l[1] !== toks.str) te("sex", str);
+                    s.expr[2] = l[2];
+                    l = lex(str, (off = l[0]));
+                    if (l && l[1] === " ") l = lex(str, off = l[0]);
+                    if (!l || l[1] !== ")") te("epex", str);
+                } else if (l[2] === ":has") {
+                    // any amount of whitespace, followed by paren
+                    l = lex(str, (off = l[0]));
+                    if (l && l[1] === " ") l = lex(str, off = l[0]);
+                    if (!l || l[1] !== "(") te("pex", str);
+                    var h = parse(str, l[0], true);
+                    l[0] = h[0];
+                    if (!s.has) s.has = [];
+                    s.has.push(h[1]);
+                } else if (l[2] === ":expr") {
+                    if (s.expr) te("mexp", str);
+                    var e = exprParse(str, l[0]);
+                    l[0] = e[0];
+                    s.expr = e[1];
+                } else {
+                    if (s.pc || s.pf ) te("mpc", str);
+                    s.pf = l[2];
+                    var m = nthPat.exec(str.substr(l[0]));
+                    if (!m) te("mepf", str);
+                    if (m[5]) {
+                        s.a = 2;
+                        s.b = (m[5] === "odd") ? 1 : 0;
+                    } else if (m[6]) {
+                        s.a = 0;
+                        s.b = parseInt(m[6], 10);
+                    } else {
+                        s.a = parseInt((m[1] ? m[1] : "+") + (m[2] ? m[2] : "1"),10);
+                        s.b = m[3] ? parseInt(m[3] + m[4],10) : 0;
+                    }
+                    l[0] += m[0].length;
+                }
+            } else {
+                break;
+            }
+            l = lex(str, (off = l[0]));
+        }
+
+        // now if we didn't actually parse anything it's an error
+        if (soff === off) te("se", str);
+
+        return [off, s];
+    }
+
+    // THE EVALUATOR
+
+    function isArray(o) {
+        return Array.isArray ? Array.isArray(o) : 
+          toString.call(o) === "[object Array]";
+    }
+
+    function mytypeof(o) {
+        if (o === null) return "null";
+        var to = typeof o;
+        if (to === "object" && isArray(o)) to = "array";
+        return to;
+    }
+
+    function mn(node, sel, id, num, tot) {
+        var sels = [];
+        var cs = (sel[0] === ">") ? sel[1] : sel[0];
+        var m = true, mod;
+        if (cs.type) m = m && (cs.type === mytypeof(node));
+        if (cs.id)   m = m && (cs.id === id);
+        if (m && cs.pf) {
+            if (cs.pf === ":nth-last-child") num = tot - num;
+            else num++;
+            if (cs.a === 0) {
+                m = cs.b === num;
+            } else {
+                mod = ((num - cs.b) % cs.a);
+
+                m = (!mod && ((num*cs.a + cs.b) >= 0));
+            }
+        }
+        if (m && cs.has) {
+            // perhaps we should augment forEach to handle a return value
+            // that indicates "client cancels traversal"?
+            var bail = function() { throw 42; };
+            for (var i = 0; i < cs.has.length; i++) {
+                try {
+                    forEach(cs.has[i], node, bail);
+                } catch (e) {
+                    if (e === 42) continue;
+                }
+                m = false;
+                break;
+            }
+        }
+        if (m && cs.expr) {
+            m = exprEval(cs.expr, node);
+        }
+        // should we repeat this selector for descendants?
+        if (sel[0] !== ">" && sel[0].pc !== ":root") sels.push(sel);
+
+        if (m) {
+            // is there a fragment that we should pass down?
+            if (sel[0] === ">") { if (sel.length > 2) { m = false; sels.push(sel.slice(2)); } }
+            else if (sel.length > 1) { m = false; sels.push(sel.slice(1)); }
+        }
+
+        return [m, sels];
+    }
+
+    function forEach(sel, obj, fun, id, num, tot) {
+        var a = (sel[0] === ",") ? sel.slice(1) : [sel],
+        a0 = [],
+        call = false,
+        i = 0, j = 0, k, x;
+        for (i = 0; i < a.length; i++) {
+            x = mn(obj, a[i], id, num, tot);
+            if (x[0]) {
+                call = true;
+            }
+            for (j = 0; j < x[1].length; j++) {
+                a0.push(x[1][j]);
+            }
+        }
+        if (a0.length && typeof obj === "object") {
+            if (a0.length >= 1) {
+                a0.unshift(",");
+            }
+            if (isArray(obj)) {
+                for (i = 0; i < obj.length; i++) {
+                    forEach(a0, obj[i], fun, undefined, i, obj.length);
+                }
+            } else {
+                for (k in obj) {
+                    if (obj.hasOwnProperty(k)) {
+                        forEach(a0, obj[k], fun, k);
+                    }
+                }
+            }
+        }
+        if (call && fun) {
+            fun(obj);
+        }
+    }
+
+    function match(sel, obj) {
+        var a = [];
+        forEach(sel, obj, function(x) {
+            a.push(x);
+        });
+        return a;
+    }
+
+    function compile(sel) {
+        return {
+            sel: parse(sel)[1],
+            match: function(obj){
+                return match(this.sel, obj);
+            },
+            forEach: function(obj, fun) {
+                return forEach(this.sel, obj, fun);
+            }
+        };
+    }
+
+    exports._lex = lex;
+    exports._parse = parse;
+    exports.match = function (sel, obj) {
+        return compile(sel).match(obj);
+    };
+    exports.forEach = function(sel, obj, fun) {
+        return compile(sel).forEach(obj, fun);
+    };
+    exports.compile = compile;
+})(typeof exports === "undefined" ? (window.JSONSelect = {}) : exports);
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/conformance_tests.html b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/conformance_tests.html
new file mode 100644
index 0000000..a53dfd0
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/conformance_tests.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title> JSONSelect conformance tests </title>
+  <link href='css/style.css' type='text/css' rel='stylesheet'>
+</head>
+<body>
+These are the the official JSONQuery conformance tests.  <button id="runit">run tests</button>
+<div id="tests">
+</div>
+</body>
+<script src="../jsonselect.js"></script>
+<script src="js/jquery-1.6.1.min.js"></script>
+<script src="js/conf_tests.js"></script>
+</html>
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/css/style.css b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/css/style.css
new file mode 100644
index 0000000..f62cc9d
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/css/style.css
@@ -0,0 +1,43 @@
+pre {
+  color: #ddd;
+  background-color: #333;
+  padding: 1em;
+  border-radius: 1em;
+  -moz-border-radius: 1em;
+  -webkit-border-radius: 1em;
+}
+
+pre.selector {
+    text-align: center;
+    padding: .5em;
+    cursor: pointer;
+}
+
+pre.document {
+    max-height: 150px;
+    overflow: auto;
+}
+
+pre.success {
+    background-color: #363;
+}
+
+pre.failure {
+    background-color: #633;
+}
+
+table {
+    width: 100%;
+}
+
+table td {
+    width: 50%;
+}
+
+table pre {
+    height: 100%;
+}
+
+h1,h2 {
+    text-align: center;
+}
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/js/conf_tests.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/js/conf_tests.js
new file mode 100644
index 0000000..42711ea
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/js/conf_tests.js
@@ -0,0 +1,77 @@
+$(document).ready(function() {
+    var tests = {};
+
+    $("#runit").click(function() {
+        for (var k in tests) {
+            var obj = JSON.parse($("." + k + "_document").text());
+            for (var i = 0; i < tests[k].length; i++) {
+                var n = tests[k][i];
+                var cl = k + "_" + n;
+                var b = $("." + cl + "_output.before");
+                var a = $("." + cl + "_output.after");
+                var s = $("." + cl + "_selector.selector");
+                try {
+                    a.text("");
+                    JSONSelect.forEach(s.text(), obj, function(m) {
+                        a.text($.trim(a.text() + "\n" + JSON.stringify(m, null, "    ")));
+                    });
+                } catch(e) {
+                    a.text("Error: " + e);
+                }
+                if (a.text() === b.text()) s.addClass("success").removeClass("failure");
+                else s.addClass("failure").removeClass("success");
+            }
+        }
+    });
+
+    function fetchFile(p, c) {
+        $.get(p, function (data) {
+            $("." + c).text($.trim(data));
+        });
+    }
+
+    function renderTests() {
+        function setClickToggle(cTarget, node) {
+            cTarget.click(function() { node.toggle("medium"); });
+        }
+
+        var c = $("<div/>");
+        for (var k in tests) {
+            c.append($("<h1/>").text("document: " + k));
+            var cl = k + "_document";
+            c.append($("<pre/>").addClass(cl).addClass("document").text("loading document..."));
+            fetchFile("tests/" + k + ".json", cl);
+            for (var i = 0; i < tests[k].length; i++) {
+                var n = tests[k][i];
+                var cl = k + "_" + n + "_selector";
+                var s = $("<pre/>").addClass(cl).addClass("selector").text("loading selector...");
+                c.append(s);
+                fetchFile("tests/" + k + "_" + n + ".selector", cl);
+                cl = k + "_" + n + "_output";
+                var t = $("<table/>").append($("<tr/>").append(
+                    $("<td/>").append($("<pre/>").addClass(cl).addClass("before").text("loading output..."))).append(
+                    $("<td/>").append($("<pre/>").addClass(cl).addClass("after").text("... test output ..."))));
+
+                c.append(t);
+                t.hide();
+                setClickToggle(s, t);
+                fetchFile("tests/" + k + "_" + n + ".output", cl + ".before");
+            }
+        }
+        c.appendTo($("#tests"));
+    }
+
+    $.get("tests/alltests.txt", function (data) {
+        var lines = data.split("\n");
+        for (var i = 0; i < lines.length; i++) {
+            var f = $.trim(lines[i]);
+            if (f.length == 0) continue;
+            var m = /^([A-Za-z]+)_(.+)\.selector$/.exec(f);
+            if (m) {
+                if (!tests.hasOwnProperty(m[1])) tests[m[1]] = [];
+                tests[m[1]].push(m[2]);
+            }
+        }
+        renderTests();
+    });
+});
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/js/doctest.css b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/js/doctest.css
new file mode 100644
index 0000000..7403feb
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/js/doctest.css
@@ -0,0 +1,69 @@
+pre {
+  overflow: auto;
+}
+
+pre.doctest {
+  border-left: 3px solid #99f;
+  padding-left: 1em;
+}
+
+pre.output {
+  border-left: 3px solid #9f9;
+  padding-left: 1em;
+}
+
+.doctest-example-prompt {
+  color: #333;
+}
+
+.doctest-success {
+  color: #060;
+}
+
+.doctest-failure {
+  color: #600;
+}
+
+.doctest-example-detail {
+  color: #060;
+  font-weight: bold;
+}
+
+a.doctest-failure-link {
+  text-decoration: none;
+}
+
+a.doctest-failure-link:hover {
+  text-decoration: underline;
+}
+
+.doctest-example:target {
+  border-left: 3px solid #f00;
+}
+
+div.test:target {
+  border: 3px solid #ff0;
+}
+
+div.test {
+  border: 1px solid #999;
+  margin-bottom: 1em;
+}
+
+div.test .test-id {
+  position: relative;
+  float: right;
+  background-color: #000;
+  color: #bbb;
+  padding: 3px;
+}
+
+div.test .test-id a:link,
+div.test .test-id a:visited {
+  color: #bbb;
+  text-decoration: none;
+}
+
+div.test .test-id a:hover {
+  text-decoration: underline;
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/js/doctest.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/js/doctest.js
new file mode 100644
index 0000000..0cfb7eb
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/js/doctest.js
@@ -0,0 +1,1397 @@
+/*
+
+Javascript doctest runner
+Copyright 2006-2010 Ian Bicking
+
+This program is free software; you can redistribute it and/or modify it under
+the terms of the MIT License.
+
+*/
+
+
+function doctest(verbosity/*default=0*/, elements/*optional*/,
+                 outputId/*optional*/) {
+  var output = document.getElementById(outputId || 'doctestOutput');
+  var reporter = new doctest.Reporter(output, verbosity || 0);
+  if (elements) {
+      if (typeof elements == 'string') {
+        // Treat it as an id
+        elements = [document.getElementById(elementId)];
+      }
+      if (! elements.length) {
+          throw('No elements');
+      }
+      var suite = new doctest.TestSuite(elements, reporter);
+  } else {
+      var els = doctest.getElementsByTagAndClassName('pre', 'doctest');
+      var suite = new doctest.TestSuite(els, reporter);
+  }
+  suite.run();
+}
+
+doctest.runDoctest = function (el, reporter) {
+  logDebug('Testing element', el);
+  reporter.startElement(el);
+  if (el === null) {
+    throw('runDoctest() with a null element');
+  }
+  var parsed = new doctest.Parser(el);
+  var runner = new doctest.JSRunner(reporter);
+  runner.runParsed(parsed);
+};
+
+doctest.TestSuite = function (els, reporter) {
+  if (this === window) {
+    throw('you forgot new!');
+  }
+  this.els = els;
+  this.parsers = [];
+  for (var i=0; i<els.length; i++) {
+    this.parsers.push(new doctest.Parser(els[i]));
+  }
+  this.reporter = reporter;
+};
+
+doctest.TestSuite.prototype.run = function (ctx) {
+  if (! ctx) {
+    ctx = new doctest.Context(this);
+  }
+  if (! ctx.runner ) {
+    ctx.runner = new doctest.JSRunner(this.reporter);
+  }
+  return ctx.run();
+};
+
+// FIXME: should this just be part of TestSuite?
+doctest.Context = function (testSuite) {
+  if (this === window) {
+    throw('You forgot new!');
+  }
+  this.testSuite = testSuite;
+  this.runner = null;
+};
+
+doctest.Context.prototype.run = function (parserIndex) {
+  var self = this;
+  parserIndex = parserIndex || 0;
+  if (parserIndex >= this.testSuite.parsers.length) {
+    logInfo('All examples from all sections tested');
+    this.runner.reporter.finish();
+    return;
+  }
+  logInfo('Testing example ' + (parserIndex+1) + ' of '
+           + this.testSuite.parsers.length);
+  var runNext = function () {
+    self.run(parserIndex+1);
+  };
+  this.runner.runParsed(this.testSuite.parsers[parserIndex], 0, runNext);
+};
+
+doctest.Parser = function (el) {
+  if (this === window) {
+    throw('you forgot new!');
+  }
+  if (! el) {
+    throw('Bad call to doctest.Parser');
+  }
+  if (el.getAttribute('parsed-id')) {
+    var examplesID = el.getAttribute('parsed-id');
+    if (doctest._allExamples[examplesID]) {
+      this.examples = doctest._allExamples[examplesID];
+      return;
+    }
+  }
+  var newHTML = document.createElement('span');
+  newHTML.className = 'doctest-example-set';
+  var examplesID = doctest.genID('example-set');
+  newHTML.setAttribute('id', examplesID);
+  el.setAttribute('parsed-id', examplesID);
+  var text = doctest.getText(el);
+  var lines = text.split(/(?:\r\n|\r|\n)/);
+  this.examples = [];
+  var example_lines = [];
+  var output_lines = [];
+  for (var i=0; i<lines.length; i++) {
+    var line = lines[i];
+    if (/^[$]/.test(line)) {
+      if (example_lines.length) {
+        var ex = new doctest.Example(example_lines, output_lines);
+        this.examples.push(ex);
+        newHTML.appendChild(ex.createSpan());
+      }
+      example_lines = [];
+      output_lines = [];
+      line = line.substr(1).replace(/ *$/, '').replace(/^ /, '');
+      example_lines.push(line);
+    } else if (/^>/.test(line)) {
+      if (! example_lines.length) {
+        throw('Bad example: '+doctest.repr(line)+'\n'
+              +'> line not preceded by $');
+      }
+      line = line.substr(1).replace(/ *$/, '').replace(/^ /, '');
+      example_lines.push(line);
+    } else {
+      output_lines.push(line);
+    }
+  }
+  if (example_lines.length) {
+    var ex = new doctest.Example(example_lines, output_lines);
+    this.examples.push(ex);
+    newHTML.appendChild(ex.createSpan());
+  }
+  el.innerHTML = '';
+  el.appendChild(newHTML);
+  doctest._allExamples[examplesID] = this.examples;
+};
+
+doctest._allExamples = {};
+
+doctest.Example = function (example, output) {
+  if (this === window) {
+    throw('you forgot new!');
+  }
+  this.example = example.join('\n');
+  this.output = output.join('\n');
+  this.htmlID = null;
+  this.detailID = null;
+};
+
+doctest.Example.prototype.createSpan = function () {
+  var id = doctest.genID('example');
+  var span = document.createElement('span');
+  span.className = 'doctest-example';
+  span.setAttribute('id', id);
+  this.htmlID = id;
+  var exampleSpan = document.createElement('span');
+  exampleSpan.className = 'doctest-example-code';
+  var exampleLines = this.example.split(/\n/);
+  for (var i=0; i<exampleLines.length; i++) {
+    var promptSpan = document.createElement('span');
+    promptSpan.className = 'doctest-example-prompt';
+    promptSpan.innerHTML = i == 0 ? '$ ' : '&gt; ';
+    exampleSpan.appendChild(promptSpan);
+    var lineSpan = document.createElement('span');
+    lineSpan.className = 'doctest-example-code-line';
+    lineSpan.appendChild(document.createTextNode(doctest.rstrip(exampleLines[i])));
+    exampleSpan.appendChild(lineSpan);
+    exampleSpan.appendChild(document.createTextNode('\n'));
+  }
+  span.appendChild(exampleSpan);
+  var outputSpan = document.createElement('span');
+  outputSpan.className = 'doctest-example-output';
+  outputSpan.appendChild(document.createTextNode(this.output));
+  span.appendChild(outputSpan);
+  span.appendChild(document.createTextNode('\n'));
+  return span;
+};
+
+doctest.Example.prototype.markExample = function (name, detail) {
+  if (! this.htmlID) {
+    return;
+  }
+  if (this.detailID) {
+    var el = document.getElementById(this.detailID);
+    el.parentNode.removeChild(el);
+    this.detailID = null;
+  }
+  var span = document.getElementById(this.htmlID);
+  span.className = span.className.replace(/ doctest-failure/, '')
+                   .replace(/ doctest-success/, '')
+                   + ' ' + name;
+  if (detail) {
+    this.detailID = doctest.genID('doctest-example-detail');
+    var detailSpan = document.createElement('span');
+    detailSpan.className = 'doctest-example-detail';
+    detailSpan.setAttribute('id', this.detailID);
+    detailSpan.appendChild(document.createTextNode(detail));
+    span.appendChild(detailSpan);
+  }
+};
+
+doctest.Reporter = function (container, verbosity) {
+  if (this === window) {
+    throw('you forgot new!');
+  }
+  if (! container) {
+    throw('No container passed to doctest.Reporter');
+  }
+  this.container = container;
+  this.verbosity = verbosity;
+  this.success = 0;
+  this.failure = 0;
+  this.elements = 0;
+};
+
+doctest.Reporter.prototype.startElement = function (el) {
+  this.elements += 1;
+  logDebug('Adding element', el);
+};
+
+doctest.Reporter.prototype.reportSuccess = function (example, output) {
+  if (this.verbosity > 0) {
+    if (this.verbosity > 1) {
+      this.write('Trying:\n');
+      this.write(this.formatOutput(example.example));
+      this.write('Expecting:\n');
+      this.write(this.formatOutput(example.output));
+      this.write('ok\n');
+    } else {
+      this.writeln(example.example + ' ... passed!');
+    }
+  }
+  this.success += 1;
+  if ((example.output.indexOf('...') >= 0
+       || example.output.indexOf('?') >= 0)
+      && output) {
+    example.markExample('doctest-success', 'Output:\n' + output);
+  } else {
+    example.markExample('doctest-success');
+  }
+};
+
+doctest.Reporter.prototype.reportFailure = function (example, output) {
+  this.write('Failed example:\n');
+  this.write('<span style="color: #00f"><a href="#'
+             + example.htmlID
+             + '" class="doctest-failure-link" title="Go to example">'
+             + this.formatOutput(example.example)
+             +'</a></span>');
+  this.write('Expected:\n');
+  this.write(this.formatOutput(example.output));
+  this.write('Got:\n');
+  this.write(this.formatOutput(output));
+  this.failure += 1;
+  example.markExample('doctest-failure', 'Actual output:\n' + output);
+};
+
+doctest.Reporter.prototype.finish = function () {
+  this.writeln((this.success+this.failure)
+               + ' tests in ' + this.elements + ' items.');
+  if (this.failure) {
+    var color = '#f00';
+  } else {
+    var color = '#0f0';
+  }
+  this.writeln('<span class="passed">' + this.success + '</span> tests of '
+               + '<span class="total">' + (this.success+this.failure) + '</span> passed, '
+               + '<span class="failed" style="color: '+color+'">'
+               + this.failure + '</span> failed.');
+};
+
+doctest.Reporter.prototype.writeln = function (text) {
+  this.write(text + '\n');
+};
+
+doctest.Reporter.prototype.write = function (text) {
+  var leading = /^[ ]*/.exec(text)[0];
+  text = text.substr(leading.length);
+  for (var i=0; i<leading.length; i++) {
+    text = String.fromCharCode(160)+text;
+  }
+  text = text.replace(/\n/g, '<br>');
+  this.container.innerHTML += text;
+};
+
+doctest.Reporter.prototype.formatOutput = function (text) {
+  if (! text) {
+    return '    <span style="color: #999">(nothing)</span>\n';
+  }
+  var lines = text.split(/\n/);
+  var output = '';
+  for (var i=0; i<lines.length; i++) {
+    output += '    '+doctest.escapeSpaces(doctest.escapeHTML(lines[i]))+'\n';
+  }
+  return output;
+};
+
+doctest.JSRunner = function (reporter) {
+  if (this === window) {
+    throw('you forgot new!');
+  }
+  this.reporter = reporter;
+};
+
+doctest.JSRunner.prototype.runParsed = function (parsed, index, finishedCallback) {
+  var self = this;
+  index = index || 0;
+  if (index >= parsed.examples.length) {
+    if (finishedCallback) {
+      finishedCallback();
+    }
+    return;
+  }
+  var example = parsed.examples[index];
+  if (typeof example == 'undefined') {
+    throw('Undefined example (' + (index+1) + ' of ' + parsed.examples.length + ')');
+  }
+  doctest._waitCond = null;
+  this.run(example);
+  var finishThisRun = function () {
+    self.finishRun(example);
+    if (doctest._AbortCalled) {
+      // FIXME: I need to find a way to make this more visible:
+      logWarn('Abort() called');
+      return;
+    }
+    self.runParsed(parsed, index+1, finishedCallback);
+  };
+  if (doctest._waitCond !== null) {
+    if (typeof doctest._waitCond == 'number') {
+      var condition = null;
+      var time = doctest._waitCond;
+      var maxTime = null;
+    } else {
+      var condition = doctest._waitCond;
+      // FIXME: shouldn't be hard-coded
+      var time = 100;
+      var maxTime = doctest._waitTimeout || doctest.defaultTimeout;
+    }
+    var start = (new Date()).getTime();
+    var timeoutFunc = function () {
+      if (condition === null
+          || condition()) {
+        finishThisRun();
+      } else {
+        // Condition not met, try again soon...
+        if ((new Date()).getTime() - start > maxTime) {
+          // Time has run out
+          var msg = 'Error: wait(' + repr(condition) + ') has timed out';
+          writeln(msg);
+          logDebug(msg);
+          logDebug('Timeout after ' + ((new Date()).getTime() - start)
+                   + ' milliseconds');
+          finishThisRun();
+          return;
+        }
+        setTimeout(timeoutFunc, time);
+      }
+    };
+    setTimeout(timeoutFunc, time);
+  } else {
+    finishThisRun();
+  }
+};
+
+doctest.formatTraceback = function (e, skipFrames) {
+  skipFrames = skipFrames || 0;
+  var lines = [];
+  if (typeof e == 'undefined' || !e) {
+    var caughtErr = null;
+    try {
+      (null).foo;
+    } catch (caughtErr) {
+      e = caughtErr;
+    }
+    skipFrames++;
+  }
+  if (e.stack) {
+    var stack = e.stack.split('\n');
+    for (var i=skipFrames; i<stack.length; i++) {
+      if (stack[i] == '@:0' || ! stack[i]) {
+        continue;
+      }
+      if (stack[i].indexOf('@') == -1) {
+        lines.push(stack[i]);
+        continue;
+      }
+      var parts = stack[i].split('@');
+      var context = parts[0];
+      parts = parts[1].split(':');
+      var filename = parts[parts.length-2].split('/');
+      filename = filename[filename.length-1];
+      var lineno = parts[parts.length-1];
+      context = context.replace('\\n', '\n');
+      if (context != '' && filename != 'doctest.js') {
+        lines.push('  ' + context + ' -> ' + filename + ':' + lineno);
+      }
+    }
+  }
+  if (lines.length) {
+    return lines;
+  } else {
+    return null;
+  }
+};
+
+doctest.logTraceback = function (e, skipFrames) {
+  var tracebackLines = doctest.formatTraceback(e, skipFrames);
+  if (! tracebackLines) {
+    return;
+  }
+  for (var i=0; i<tracebackLines.length; i++) {
+    logDebug(tracebackLines[i]);
+  }
+};
+
+doctest.JSRunner.prototype.run = function (example) {
+  this.capturer = new doctest.OutputCapturer();
+  this.capturer.capture();
+  try {
+    var result = doctest.eval(example.example);
+  } catch (e) {
+    var tracebackLines = doctest.formatTraceback(e);
+    writeln('Error: ' + (e.message || e));
+    var result = null;
+    logWarn('Error in expression: ' + example.example);
+    logDebug('Traceback for error', e);
+    if (tracebackLines) {
+      for (var i=0; i<tracebackLines.length; i++) {
+        logDebug(tracebackLines[i]);
+      }
+    }
+    if (e instanceof Abort) {
+      throw e;
+    }
+  }
+  if (typeof result != 'undefined'
+      && result !== null
+      && example.output) {
+    writeln(doctest.repr(result));
+  }
+};
+
+doctest._AbortCalled = false;
+
+doctest.Abort = function (message) {
+  if (this === window) {
+    return new Abort(message);
+  }
+  this.message = message;
+  // We register this so Abort can be raised in an async call:
+  doctest._AbortCalled = true;
+};
+
+doctest.Abort.prototype.toString = function () {
+  return this.message;
+};
+
+if (typeof Abort == 'undefined') {
+  Abort = doctest.Abort;
+}
+
+doctest.JSRunner.prototype.finishRun = function(example) {
+  this.capturer.stopCapture();
+  var success = this.checkResult(this.capturer.output, example.output);
+  if (success) {
+    this.reporter.reportSuccess(example, this.capturer.output);
+  } else {
+    this.reporter.reportFailure(example, this.capturer.output);
+    logDebug('Failure: '+doctest.repr(example.output)
+             +' != '+doctest.repr(this.capturer.output));
+    if (location.href.search(/abort/) != -1) {
+      doctest.Abort('abort on first failure');
+    }
+  }
+};
+
+doctest.JSRunner.prototype.checkResult = function (got, expected) {
+  // Make sure trailing whitespace doesn't matter:
+  got = got.replace(/ +\n/, '\n');
+  expected = expected.replace(/ +\n/, '\n');
+  got = got.replace(/[ \n\r]*$/, '') + '\n';
+  expected = expected.replace(/[ \n\r]*$/, '') + '\n';
+  if (expected == '...\n') {
+    return true;
+  }
+  expected = RegExp.escape(expected);
+  // Note: .* doesn't match newlines, [^] doesn't work on IE
+  expected = '^' + expected.replace(/\\\.\\\.\\\./g, "[\\S\\s\\r\\n]*") + '$';
+  expected = expected.replace(/\\\?/g, "[a-zA-Z0-9_.]+");
+  expected = expected.replace(/[ \t]+/g, " +");
+  expected = expected.replace(/\n/g, '\\n');
+  var re = new RegExp(expected);
+  var result = got.search(re) != -1;
+  if (! result) {
+    if (doctest.strip(got).split('\n').length > 1) {
+      // If it's only one line it's not worth showing this
+      var check = this.showCheckDifference(got, expected);
+      logWarn('Mismatch of output (line-by-line comparison follows)');
+      for (var i=0; i<check.length; i++) {
+        logDebug(check[i]);
+      }
+    }
+  }
+  return result;
+};
+
+doctest.JSRunner.prototype.showCheckDifference = function (got, expectedRegex) {
+  if (expectedRegex.charAt(0) != '^') {
+    throw 'Unexpected regex, no leading ^';
+  }
+  if (expectedRegex.charAt(expectedRegex.length-1) != '$') {
+    throw 'Unexpected regex, no trailing $';
+  }
+  expectedRegex = expectedRegex.substr(1, expectedRegex.length-2);
+  // Technically this might not be right, but this is all a heuristic:
+  var expectedRegex = expectedRegex.replace(/\(\?:\.\|\[\\r\\n\]\)\*/g, '...');
+  var expectedLines = expectedRegex.split('\\n');
+  for (var i=0; i<expectedLines.length; i++) {
+    expectedLines[i] = expectedLines[i].replace(/\.\.\./g, '(?:.|[\r\n])*');
+  }
+  var gotLines = got.split('\n');
+  var result = [];
+  var totalLines = expectedLines.length > gotLines.length ?
+    expectedLines.length : gotLines.length;
+  function displayExpectedLine(line) {
+    return line;
+    line = line.replace(/\[a-zA-Z0-9_.\]\+/g, '?');
+    line = line.replace(/ \+/g, ' ');
+    line = line.replace(/\(\?:\.\|\[\\r\\n\]\)\*/g, '...');
+    // FIXME: also unescape values? e.g., * became \*
+    return line;
+  }
+  for (var i=0; i<totalLines; i++) {
+    if (i >= expectedLines.length) {
+      result.push('got extra line: ' + repr(gotLines[i]));
+      continue;
+    } else if (i >= gotLines.length) {
+      result.push('expected extra line: ' + displayExpectedLine(expectedLines[i]));
+      continue;
+    }
+    var gotLine = gotLines[i];
+    try {
+      var expectRE = new RegExp('^' + expectedLines[i] + '$');
+    } catch (e) {
+      result.push('regex match failed: ' + repr(gotLine) + ' ('
+            + expectedLines[i] + ')');
+      continue;
+    }
+    if (gotLine.search(expectRE) != -1) {
+      result.push('match: ' + repr(gotLine));
+    } else {
+      result.push('no match: ' + repr(gotLine) + ' ('
+            + displayExpectedLine(expectedLines[i]) + ')');
+    }
+  }
+  return result;
+};
+
+// Should I really be setting this on RegExp?
+RegExp.escape = function (text) {
+  if (!arguments.callee.sRE) {
+    var specials = [
+      '/', '.', '*', '+', '?', '|',
+      '(', ')', '[', ']', '{', '}', '\\'
+    ];
+    arguments.callee.sRE = new RegExp(
+      '(\\' + specials.join('|\\') + ')', 'g'
+    );
+  }
+  return text.replace(arguments.callee.sRE, '\\$1');
+};
+
+doctest.OutputCapturer = function () {
+  if (this === window) {
+    throw('you forgot new!');
+  }
+  this.output = '';
+};
+
+doctest._output = null;
+
+doctest.OutputCapturer.prototype.capture = function () {
+  doctest._output = this;
+};
+
+doctest.OutputCapturer.prototype.stopCapture = function () {
+  doctest._output = null;
+};
+
+doctest.OutputCapturer.prototype.write = function (text) {
+  if (typeof text == 'string') {
+    this.output += text;
+  } else {
+    this.output += repr(text);
+  }
+};
+
+// Used to create unique IDs:
+doctest._idGen = 0;
+
+doctest.genID = function (prefix) {
+  prefix = prefix || 'generic-doctest';
+  var id = doctest._idGen++;
+  return prefix + '-' + doctest._idGen;
+};
+
+doctest.writeln = function () {
+  for (var i=0; i<arguments.length; i++) {
+    write(arguments[i]);
+    if (i) {
+      write(' ');
+    }
+  }
+  write('\n');
+};
+
+if (typeof writeln == 'undefined') {
+  writeln = doctest.writeln;
+}
+
+doctest.write = function (text) {
+  if (doctest._output !== null) {
+    doctest._output.write(text);
+  } else {
+    log(text);
+  }
+};
+
+if (typeof write == 'undefined') {
+  write = doctest.write;
+}
+
+doctest._waitCond = null;
+
+function wait(conditionOrTime, hardTimeout) {
+  // FIXME: should support a timeout even with a condition
+  if (typeof conditionOrTime == 'undefined'
+      || conditionOrTime === null) {
+    // same as wait-some-small-amount-of-time
+    conditionOrTime = 0;
+  }
+  doctest._waitCond = conditionOrTime;
+  doctest._waitTimeout = hardTimeout;
+};
+
+doctest.wait = wait;
+
+doctest.assert = function (expr, statement) {
+  if (typeof expr == 'string') {
+    if (! statement) {
+      statement = expr;
+    }
+    expr = doctest.eval(expr);
+  }
+  if (! expr) {
+    throw('AssertionError: '+statement);
+  }
+};
+
+if (typeof assert == 'undefined') {
+  assert = doctest.assert;
+}
+
+doctest.getText = function (el) {
+  if (! el) {
+    throw('You must pass in an element');
+  }
+  var text = '';
+  for (var i=0; i<el.childNodes.length; i++) {
+    var sub = el.childNodes[i];
+    if (sub.nodeType == 3) {
+      // TEXT_NODE
+      text += sub.nodeValue;
+    } else if (sub.childNodes) {
+      text += doctest.getText(sub);
+    }
+  }
+  return text;
+};
+
+doctest.reload = function (button/*optional*/) {
+  if (button) {
+    button.innerHTML = 'reloading...';
+    button.disabled = true;
+  }
+  location.reload();
+};
+
+/* Taken from MochiKit, with an addition to print objects */
+doctest.repr = function (o, indentString, maxLen) {
+    indentString = indentString || '';
+    if (doctest._reprTracker === null) {
+      var iAmTheTop = true;
+      doctest._reprTracker = [];
+    } else {
+      var iAmTheTop = false;
+    }
+    try {
+      if (doctest._reprTrackObj(o)) {
+        return '..recursive..';
+      }
+      if (maxLen === undefined) {
+        maxLen = 120;
+      }
+      if (typeof o == 'undefined') {
+          return 'undefined';
+      } else if (o === null) {
+          return "null";
+      }
+      try {
+          if (typeof(o.__repr__) == 'function') {
+              return o.__repr__(indentString, maxLen);
+          } else if (typeof(o.repr) == 'function' && o.repr != arguments.callee) {
+              return o.repr(indentString, maxLen);
+          }
+          for (var i=0; i<doctest.repr.registry.length; i++) {
+              var item = doctest.repr.registry[i];
+              if (item[0](o)) {
+                  return item[1](o, indentString, maxLen);
+              }
+          }
+      } catch (e) {
+          if (typeof(o.NAME) == 'string' && (
+                  o.toString == Function.prototype.toString ||
+                      o.toString == Object.prototype.toString)) {
+              return o.NAME;
+          }
+      }
+      try {
+          var ostring = (o + "");
+          if (ostring == '[object Object]' || ostring == '[object]') {
+            ostring = doctest.objRepr(o, indentString, maxLen);
+          }
+      } catch (e) {
+          return "[" + typeof(o) + "]";
+      }
+      if (typeof(o) == "function") {
+          var ostring = ostring.replace(/^\s+/, "").replace(/\s+/g, " ");
+          var idx = ostring.indexOf("{");
+          if (idx != -1) {
+              ostring = ostring.substr(o, idx) + "{...}";
+          }
+      }
+      return ostring;
+    } finally {
+      if (iAmTheTop) {
+        doctest._reprTracker = null;
+      }
+    }
+};
+
+doctest._reprTracker = null;
+
+doctest._reprTrackObj = function (obj) {
+  if (typeof obj != 'object') {
+    return false;
+  }
+  for (var i=0; i<doctest._reprTracker.length; i++) {
+    if (doctest._reprTracker[i] === obj) {
+      return true;
+    }
+  }
+  doctest._reprTracker.push(obj);
+  return false;
+};
+
+doctest._reprTrackSave = function () {
+  return doctest._reprTracker.length-1;
+};
+
+doctest._reprTrackRestore = function (point) {
+  doctest._reprTracker.splice(point, doctest._reprTracker.length - point);
+};
+
+doctest._sortedKeys = function (obj) {
+  var keys = [];
+  for (var i in obj) {
+    // FIXME: should I use hasOwnProperty?
+    if (typeof obj.prototype == 'undefined'
+        || obj[i] !== obj.prototype[i]) {
+      keys.push(i);
+    }
+  }
+  keys.sort();
+  return keys;
+};
+
+doctest.objRepr = function (obj, indentString, maxLen) {
+  var restorer = doctest._reprTrackSave();
+  var ostring = '{';
+  var keys = doctest._sortedKeys(obj);
+  for (var i=0; i<keys.length; i++) {
+    if (ostring != '{') {
+      ostring += ', ';
+    }
+    ostring += keys[i] + ': ' + doctest.repr(obj[keys[i]], indentString, maxLen);
+  }
+  ostring += '}';
+  if (ostring.length > (maxLen - indentString.length)) {
+    doctest._reprTrackRestore(restorer);
+    return doctest.multilineObjRepr(obj, indentString, maxLen);
+  }
+  return ostring;
+};
+
+doctest.multilineObjRepr = function (obj, indentString, maxLen) {
+  var keys = doctest._sortedKeys(obj);
+  var ostring = '{\n';
+  for (var i=0; i<keys.length; i++) {
+    ostring += indentString + '  ' + keys[i] + ': ';
+    ostring += doctest.repr(obj[keys[i]], indentString+'  ', maxLen);
+    if (i != keys.length - 1) {
+      ostring += ',';
+    }
+    ostring += '\n';
+  }
+  ostring += indentString + '}';
+  return ostring;
+};
+
+doctest.arrayRepr = function (obj, indentString, maxLen) {
+  var restorer = doctest._reprTrackSave();
+  var s = "[";
+  for (var i=0; i<obj.length; i++) {
+    s += doctest.repr(obj[i], indentString, maxLen);
+    if (i != obj.length-1) {
+      s += ", ";
+    }
+  }
+  s += "]";
+  if (s.length > (maxLen + indentString.length)) {
+    doctest._reprTrackRestore(restorer);
+    return doctest.multilineArrayRepr(obj, indentString, maxLen);
+  }
+  return s;
+};
+
+doctest.multilineArrayRepr = function (obj, indentString, maxLen) {
+  var s = "[\n";
+  for (var i=0; i<obj.length; i++) {
+    s += indentString + '  ' + doctest.repr(obj[i], indentString+'  ', maxLen);
+    if (i != obj.length - 1) {
+      s += ',';
+    }
+    s += '\n';
+  }
+  s += indentString + ']';
+  return s;
+};
+
+doctest.xmlRepr = function (doc, indentString) {
+  var i;
+  if (doc.nodeType == doc.DOCUMENT_NODE) {
+    return doctest.xmlRepr(doc.childNodes[0], indentString);
+  }
+  indentString = indentString || '';
+  var s = indentString + '<' + doc.tagName;
+  var attrs = [];
+  if (doc.attributes && doc.attributes.length) {
+    for (i=0; i<doc.attributes.length; i++) {
+      attrs.push(doc.attributes[i].nodeName);
+    }
+    attrs.sort();
+    for (i=0; i<attrs.length; i++) {
+      s += ' ' + attrs[i] + '="';
+      var value = doc.getAttribute(attrs[i]);
+      value = value.replace('&', '&amp;');
+      value = value.replace('"', '&quot;');
+      s += value;
+      s += '"';
+    }
+  }
+  if (! doc.childNodes.length) {
+    s += ' />';
+    return s;
+  } else {
+    s += '>';
+  }
+  var hasNewline = false;
+  for (i=0; i<doc.childNodes.length; i++) {
+    var el = doc.childNodes[i];
+    if (el.nodeType == doc.TEXT_NODE) {
+      s += doctest.strip(el.textContent);
+    } else {
+      if (! hasNewline) {
+        s += '\n';
+        hasNewline = true;
+      }
+      s += doctest.xmlRepr(el, indentString + '  ');
+      s += '\n';
+    }
+  }
+  if (hasNewline) {
+    s += indentString;
+  }
+  s += '</' + doc.tagName + '>';
+  return s;
+};
+
+doctest.repr.registry = [
+    [function (o) {
+         return typeof o == 'string';},
+     function (o) {
+         o = '"' + o.replace(/([\"\\])/g, '\\$1') + '"';
+         o = o.replace(/[\f]/g, "\\f")
+         .replace(/[\b]/g, "\\b")
+         .replace(/[\n]/g, "\\n")
+         .replace(/[\t]/g, "\\t")
+         .replace(/[\r]/g, "\\r");
+         return o;
+     }],
+    [function (o) {
+         return typeof o == 'number';},
+     function (o) {
+         return o + "";
+     }],
+    [function (o) {
+          return (typeof o == 'object' && o.xmlVersion);
+     },
+     doctest.xmlRepr],
+    [function (o) {
+         var typ = typeof o;
+         if ((typ != 'object' && ! (type == 'function' && typeof o.item == 'function')) ||
+             o === null ||
+             typeof o.length != 'number' ||
+             o.nodeType === 3) {
+             return false;
+         }
+         return true;
+     },
+     doctest.arrayRepr
+     ]];
+
+doctest.objDiff = function (orig, current) {
+  var result = {
+    added: {},
+    removed: {},
+    changed: {},
+    same: {}
+  };
+  for (var i in orig) {
+    if (! (i in current)) {
+      result.removed[i] = orig[i];
+    } else if (orig[i] !== current[i]) {
+      result.changed[i] = [orig[i], current[i]];
+    } else {
+      result.same[i] = orig[i];
+    }
+  }
+  for (i in current) {
+    if (! (i in orig)) {
+      result.added[i] = current[i];
+    }
+  }
+  return result;
+};
+
+doctest.writeDiff = function (orig, current, indentString) {
+  if (typeof orig != 'object' || typeof current != 'object') {
+    writeln(indentString + repr(orig, indentString) + ' -> ' + repr(current, indentString));
+    return;
+  }
+  indentString = indentString || '';
+  var diff = doctest.objDiff(orig, current);
+  var i, keys;
+  var any = false;
+  keys = doctest._sortedKeys(diff.added);
+  for (i=0; i<keys.length; i++) {
+    any = true;
+    writeln(indentString + '+' + keys[i] + ': '
+            + repr(diff.added[keys[i]], indentString));
+  }
+  keys = doctest._sortedKeys(diff.removed);
+  for (i=0; i<keys.length; i++) {
+    any = true;
+    writeln(indentString + '-' + keys[i] + ': '
+            + repr(diff.removed[keys[i]], indentString));
+  }
+  keys = doctest._sortedKeys(diff.changed);
+  for (i=0; i<keys.length; i++) {
+    any = true;
+    writeln(indentString + keys[i] + ': '
+            + repr(diff.changed[keys[i]][0], indentString)
+            + ' -> '
+            + repr(diff.changed[keys[i]][1], indentString));
+  }
+  if (! any) {
+    writeln(indentString + '(no changes)');
+  }
+};
+
+doctest.objectsEqual = function (ob1, ob2) {
+  var i;
+  if (typeof ob1 != 'object' || typeof ob2 != 'object') {
+    return ob1 === ob2;
+  }
+  for (i in ob1) {
+    if (ob1[i] !== ob2[i]) {
+      return false;
+    }
+  }
+  for (i in ob2) {
+    if (! (i in ob1)) {
+      return false;
+    }
+  }
+  return true;
+};
+
+doctest.getElementsByTagAndClassName = function (tagName, className, parent/*optional*/) {
+    parent = parent || document;
+    var els = parent.getElementsByTagName(tagName);
+    var result = [];
+    var regexes = [];
+    if (typeof className == 'string') {
+      className = [className];
+    }
+    for (var i=0; i<className.length; i++) {
+      regexes.push(new RegExp("\\b" + className[i] + "\\b"));
+    }
+    for (i=0; i<els.length; i++) {
+      var el = els[i];
+      if (el.className) {
+        var passed = true;
+        for (var j=0; j<regexes.length; j++) {
+          if (el.className.search(regexes[j]) == -1) {
+            passed = false;
+            break;
+          }
+        }
+        if (passed) {
+          result.push(el);
+        }
+      }
+    }
+    return result;
+};
+
+doctest.strip = function (str) {
+    str = str + "";
+    return str.replace(/\s+$/, "").replace(/^\s+/, "");
+};
+
+doctest.rstrip = function (str) {
+  str = str + "";
+  return str.replace(/\s+$/, "");
+};
+
+doctest.escapeHTML = function (s) {
+    return s.replace(/&/g, '&amp;')
+    .replace(/\"/g, "&quot;")
+    .replace(/</g, '&lt;')
+    .replace(/>/g, '&gt;');
+};
+
+doctest.escapeSpaces = function (s) {
+  return s.replace(/  /g, '&nbsp; ');
+};
+
+doctest.extend = function (obj, extendWith) {
+    for (i in extendWith) {
+        obj[i] = extendWith[i];
+    }
+    return obj;
+};
+
+doctest.extendDefault = function (obj, extendWith) {
+    for (i in extendWith) {
+        if (typeof obj[i] == 'undefined') {
+            obj[i] = extendWith[i];
+        }
+    }
+    return obj;
+};
+
+if (typeof repr == 'undefined') {
+    repr = doctest.repr;
+}
+
+doctest._consoleFunc = function (attr) {
+  if (typeof window.console != 'undefined'
+      && typeof window.console[attr] != 'undefined') {
+    if (typeof console[attr].apply === 'function') {
+      result = function() {
+        console[attr].apply(console, arguments);
+      };
+    } else {
+      result = console[attr];
+    }
+  } else {
+    result = function () {
+      // FIXME: do something
+    };
+  }
+  return result;
+};
+
+if (typeof log == 'undefined') {
+  log = doctest._consoleFunc('log');
+}
+
+if (typeof logDebug == 'undefined') {
+  logDebug = doctest._consoleFunc('log');
+}
+
+if (typeof logInfo == 'undefined') {
+  logInfo = doctest._consoleFunc('info');
+}
+
+if (typeof logWarn == 'undefined') {
+  logWarn = doctest._consoleFunc('warn');
+}
+
+doctest.eval = function () {
+  return window.eval.apply(window, arguments);
+};
+
+doctest.useCoffeeScript = function (options) {
+  options = options || {};
+  options.bare = true;
+  options.globals = true;
+  if (! options.fileName) {
+    options.fileName = 'repl';
+  }
+  if (typeof CoffeeScript == 'undefined') {
+    doctest.logWarn('coffee-script.js is not included');
+    throw 'coffee-script.js is not included';
+  }
+  doctest.eval = function (code) {
+    var src = CoffeeScript.compile(code, options);
+    logDebug('Compiled code to:', src);
+    return window.eval(src);
+  };
+};
+
+doctest.autoSetup = function (parent) {
+  var tags = doctest.getElementsByTagAndClassName('div', 'test', parent);
+  // First we'll make sure everything has an ID
+  var tagsById = {};
+  for (var i=0; i<tags.length; i++) {
+    var tagId = tags[i].getAttribute('id');
+    if (! tagId) {
+      tagId = 'test-' + (++doctest.autoSetup._idCount);
+      tags[i].setAttribute('id', tagId);
+    }
+    // FIXME: test uniqueness here, warn
+    tagsById[tagId] = tags[i];
+  }
+  // Then fill in the labels
+  for (i=0; i<tags.length; i++) {
+    var el = document.createElement('span');
+    el.className = 'test-id';
+    var anchor = document.createElement('a');
+    anchor.setAttribute('href', '#' + tags[i].getAttribute('id'));
+    anchor.appendChild(document.createTextNode(tags[i].getAttribute('id')));
+    var button = document.createElement('button');
+    button.innerHTML = 'test';
+    button.setAttribute('type', 'button');
+    button.setAttribute('test-id', tags[i].getAttribute('id'));
+    button.onclick = function () {
+      location.hash = '#' + this.getAttribute('test-id');
+      location.reload();
+    };
+    el.appendChild(anchor);
+    el.appendChild(button);
+    tags[i].insertBefore(el, tags[i].childNodes[0]);
+  }
+  // Lastly, create output areas in each section
+  for (i=0; i<tags.length; i++) {
+    var outEl = doctest.getElementsByTagAndClassName('pre', 'output', tags[i]);
+    if (! outEl.length) {
+      outEl = document.createElement('pre');
+      outEl.className = 'output';
+      outEl.setAttribute('id', tags[i].getAttribute('id') + '-output');
+    }
+  }
+  if (location.hash.length > 1) {
+    // This makes the :target CSS work, since if the hash points to an
+    // element whose id has just been added, it won't be noticed
+    location.hash = location.hash;
+  }
+  var output = document.getElementById('doctestOutput');
+  if (! tags.length) {
+    tags = document.getElementsByTagName('body');
+  }
+  if (! output) {
+    output = document.createElement('pre');
+    output.setAttribute('id', 'doctestOutput');
+    output.className = 'output';
+    tags[0].parentNode.insertBefore(output, tags[0]);
+  }
+  var reloader = document.getElementById('doctestReload');
+  if (! reloader) {
+    reloader = document.createElement('button');
+    reloader.setAttribute('type', 'button');
+    reloader.setAttribute('id', 'doctest-testall');
+    reloader.innerHTML = 'test all';
+    reloader.onclick = function () {
+      location.hash = '#doctest-testall';
+      location.reload();
+    };
+    output.parentNode.insertBefore(reloader, output);
+  }
+};
+
+doctest.autoSetup._idCount = 0;
+
+doctest.Spy = function (name, options, extraOptions) {
+  var self;
+  if (doctest.spies[name]) {
+     self = doctest.spies[name];
+     if (! options && ! extraOptions) {
+       return self;
+     }
+  } else {
+    self = function () {
+      return self.func.apply(this, arguments);
+    };
+  }
+  name = name || 'spy';
+  options = options || {};
+  if (typeof options == 'function') {
+    options = {applies: options};
+  }
+  if (extraOptions) {
+    doctest.extendDefault(options, extraOptions);
+  }
+  doctest.extendDefault(options, doctest.defaultSpyOptions);
+  self._name = name;
+  self.options = options;
+  self.called = false;
+  self.calledWait = false;
+  self.args = null;
+  self.self = null;
+  self.argList = [];
+  self.selfList = [];
+  self.writes = options.writes || false;
+  self.returns = options.returns || null;
+  self.applies = options.applies || null;
+  self.binds = options.binds || null;
+  self.throwError = options.throwError || null;
+  self.ignoreThis = options.ignoreThis || false;
+  self.wrapArgs = options.wrapArgs || false;
+  self.func = function () {
+    self.called = true;
+    self.calledWait = true;
+    self.args = doctest._argsToArray(arguments);
+    self.self = this;
+    self.argList.push(self.args);
+    self.selfList.push(this);
+    // It might be possible to get the caller?
+    if (self.writes) {
+      writeln(self.formatCall());
+    }
+    if (self.throwError) {
+      throw self.throwError;
+    }
+    if (self.applies) {
+      return self.applies.apply(this, arguments);
+    }
+    return self.returns;
+  };
+  self.func.toString = function () {
+    return "Spy('" + self._name + "').func";
+  };
+
+  // Method definitions:
+  self.formatCall = function () {
+    var s = '';
+    if ((! self.ignoreThis) && self.self !== window && self.self !== self) {
+      s += doctest.repr(self.self) + '.';
+    }
+    s += self._name;
+    if (self.args === null) {
+      return s + ':never called';
+    }
+    s += '(';
+    for (var i=0; i<self.args.length; i++) {
+      if (i) {
+        s += ', ';
+      }
+      if (self.wrapArgs) {
+        var maxLen = 10;
+      } else {
+        var maxLen = undefined;
+      }
+      s += doctest.repr(self.args[i], '', maxLen);
+    }
+    s += ')';
+    return s;
+  };
+
+  self.method = function (name, options, extraOptions) {
+    var desc = self._name + '.' + name;
+    var newSpy = Spy(desc, options, extraOptions);
+    self[name] = self.func[name] = newSpy.func;
+    return newSpy;
+  };
+
+  self.methods = function (props) {
+    for (var i in props) {
+      if (props[i] === props.prototype[i]) {
+        continue;
+      }
+      self.method(i, props[i]);
+    }
+    return self;
+  };
+
+  self.wait = function (timeout) {
+    var func = function () {
+      var value = self.calledWait;
+      if (value) {
+        self.calledWait = false;
+      }
+      return value;
+    };
+    func.repr = function () {
+      return 'called:'+repr(self);
+    };
+    doctest.wait(func, timeout);
+  };
+
+  self.repr = function () {
+    return "Spy('" + self._name + "')";
+  };
+
+  if (options.methods) {
+    self.methods(options.methods);
+  }
+  doctest.spies[name] = self;
+  if (options.wait) {
+    self.wait();
+  }
+  return self;
+};
+
+doctest._argsToArray = function (args) {
+  var array = [];
+  for (var i=0; i<args.length; i++) {
+    array.push(args[i]);
+  }
+  return array;
+};
+
+Spy = doctest.Spy;
+
+doctest.spies = {};
+
+doctest.defaultTimeout = 2000;
+
+doctest.defaultSpyOptions = {writes: true};
+
+var docTestOnLoad = function () {
+  var auto = false;
+  if (/\bautodoctest\b/.exec(document.body.className)) {
+    doctest.autoSetup();
+    auto = true;
+  } else {
+    logDebug('No autodoctest class on <body>');
+  }
+  var loc = window.location.search.substring(1);
+  if (auto || (/doctestRun/).exec(loc)) {
+    var elements = null;
+    // FIXME: we need to put the output near the specific test being tested:
+    if (location.hash) {
+      var el = document.getElementById(location.hash.substr(1));
+      if (el) {
+        if (/\btest\b/.exec(el.className)) {
+          var testEls = doctest.getElementsByTagAndClassName('pre', 'doctest', el);
+          elements = doctest.getElementsByTagAndClassName('pre', ['doctest', 'setup']);
+          for (var i=0; i<testEls.length; i++) {
+            elements.push(testEls[i]);
+          }
+        }
+      }
+    }
+    doctest(0, elements);
+  }
+};
+
+if (window.addEventListener) {
+    window.addEventListener('load', docTestOnLoad, false);
+} else if(window.attachEvent) {
+    window.attachEvent('onload', docTestOnLoad);
+}
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/js/jquery-1.6.1.min.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/js/jquery-1.6.1.min.js
new file mode 100644
index 0000000..b2ac174
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/js/jquery-1.6.1.min.js
@@ -0,0 +1,18 @@
+/*!
+ * jQuery JavaScript Library v1.6.1
+ * http://jquery.com/
+ *
+ * Copyright 2011, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ * Copyright 2011, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ *
+ * Date: Thu May 12 15:04:36 2011 -0400
+ */
+(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cv(a){if(!cj[a]){var b=f("<"+a+">").appendTo("body"),d=b.css("display");b.remove();if(d==="none"||d===""){ck||(ck=c.createElement("iframe"),ck.frameBorder=ck.width=ck.height=0),c.body.appendChild(ck);if(!cl||!ck.createElement)cl=(ck.contentWindow||ck.contentDocument).document,cl.write("<!doctype><html><body></body></html>");b=cl.createElement(a),cl.body.appendChild(b),d=f.css(b,"display"),c.body.removeChild(ck)}cj[a]=d}return cj[a]}function cu(a,b){var c={};f.each(cp.concat.apply([],cp.slice(0,b)),function(){c[this]=a});return c}function ct(){cq=b}function cs(){setTimeout(ct,0);return cq=f.now()}function ci(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ch(){try{return new a.XMLHttpRequest}catch(b){}}function cb(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g<i;g++){if(g===1)for(h in a.converters)typeof h=="string"&&(e[h.toLowerCase()]=a.converters[h]);l=k,k=d[g];if(k==="*")k=l;else if(l!=="*"&&l!==k){m=l+" "+k,n=e[m]||e["* "+k];if(!n){p=b;for(o in e){j=o.split(" ");if(j[0]===l||j[0]==="*"){p=e[j[1]+" "+k];if(p){o=e[o],o===!0?n=p:p===!0&&(n=o);break}}}}!n&&!p&&f.error("No conversion from "+m.replace(" "," to ")),n!==!0&&(c=n?n(c):p(o(c)))}}return c}function ca(a,c,d){var e=a.contents,f=a.dataTypes,g=a.responseFields,h,i,j,k;for(i in g)i in d&&(c[g[i]]=d[i]);while(f[0]==="*")f.shift(),h===b&&(h=a.mimeType||c.getResponseHeader("content-type"));if(h)for(i in e)if(e[i]&&e[i].test(h)){f.unshift(i);break}if(f[0]in d)j=f[0];else{for(i in d){if(!f[0]||a.converters[i+" "+f[0]]){j=i;break}k||(k=i)}j=j||k}if(j){j!==f[0]&&f.unshift(j);return d[j]}}function b_(a,b,c,d){if(f.isArray(b))f.each(b,function(b,e){c||bF.test(a)?d(a,e):b_(a+"["+(typeof e=="object"||f.isArray(e)?b:"")+"]",e,c,d)});else if(!c&&b!=null&&typeof b=="object")for(var e in b)b_(a+"["+e+"]",b[e],c,d);else d(a,b)}function b$(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h=a[f],i=0,j=h?h.length:0,k=a===bU,l;for(;i<j&&(k||!l);i++)l=h[i](c,d,e),typeof l=="string"&&(!k||g[l]?l=b:(c.dataTypes.unshift(l),l=b$(a,c,d,e,l,g)));(k||!l)&&!g["*"]&&(l=b$(a,c,d,e,"*",g));return l}function bZ(a){return function(b,c){typeof b!="string"&&(c=b,b="*");if(f.isFunction(c)){var d=b.toLowerCase().split(bQ),e=0,g=d.length,h,i,j;for(;e<g;e++)h=d[e],j=/^\+/.test(h),j&&(h=h.substr(1)||"*"),i=a[h]=a[h]||[],i[j?"unshift":"push"](c)}}}function bD(a,b,c){var d=b==="width"?bx:by,e=b==="width"?a.offsetWidth:a.offsetHeight;if(c==="border")return e;f.each(d,function(){c||(e-=parseFloat(f.css(a,"padding"+this))||0),c==="margin"?e+=parseFloat(f.css(a,"margin"+this))||0:e-=parseFloat(f.css(a,"border"+this+"Width"))||0});return e}function bn(a,b){b.src?f.ajax({url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(bf,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)}function bm(a){f.nodeName(a,"input")?bl(a):a.getElementsByTagName&&f.grep(a.getElementsByTagName("input"),bl)}function bl(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bk(a){return"getElementsByTagName"in a?a.getElementsByTagName("*"):"querySelectorAll"in a?a.querySelectorAll("*"):[]}function bj(a,b){var c;if(b.nodeType===1){b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase();if(c==="object")b.outerHTML=a.outerHTML;else if(c!=="input"||a.type!=="checkbox"&&a.type!=="radio"){if(c==="option")b.selected=a.defaultSelected;else if(c==="input"||c==="textarea")b.defaultValue=a.defaultValue}else a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value);b.removeAttribute(f.expando)}}function bi(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c=f.expando,d=f.data(a),e=f.data(b,d);if(d=d[c]){var g=d.events;e=e[c]=f.extend({},d);if(g){delete e.handle,e.events={};for(var h in g)for(var i=0,j=g[h].length;i<j;i++)f.event.add(b,h+(g[h][i].namespace?".":"")+g[h][i].namespace,g[h][i],g[h][i].data)}}}}function bh(a,b){return f.nodeName(a,"table")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function X(a,b,c){b=b||0;if(f.isFunction(b))return f.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return f.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=f.grep(a,function(a){return a.nodeType===1});if(S.test(b))return f.filter(b,d,!c);b=f.filter(b,d)}return f.grep(a,function(a,d){return f.inArray(a,b)>=0===c})}function W(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function O(a,b){return(a&&a!=="*"?a+".":"")+b.replace(A,"`").replace(B,"&")}function N(a){var b,c,d,e,g,h,i,j,k,l,m,n,o,p=[],q=[],r=f._data(this,"events");if(!(a.liveFired===this||!r||!r.live||a.target.disabled||a.button&&a.type==="click")){a.namespace&&(n=new RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)")),a.liveFired=this;var s=r.live.slice(0);for(i=0;i<s.length;i++)g=s[i],g.origType.replace(y,"")===a.type?q.push(g.selector):s.splice(i--,1);e=f(a.target).closest(q,a.currentTarget);for(j=0,k=e.length;j<k;j++){m=e[j];for(i=0;i<s.length;i++){g=s[i];if(m.selector===g.selector&&(!n||n.test(g.namespace))&&!m.elem.disabled){h=m.elem,d=null;if(g.preType==="mouseenter"||g.preType==="mouseleave")a.type=g.preType,d=f(a.relatedTarget).closest(g.selector)[0],d&&f.contains(h,d)&&(d=h);(!d||d!==h)&&p.push({elem:h,handleObj:g,level:m.level})}}}for(j=0,k=p.length;j<k;j++){e=p[j];if(c&&e.level>c)break;a.currentTarget=e.elem,a.data=e.handleObj.data,a.handleObj=e.handleObj,o=e.handleObj.origHandler.apply(e.elem,arguments);if(o===!1||a.isPropagationStopped()){c=e.level,o===!1&&(b=!1);if(a.isImmediatePropagationStopped())break}}return b}}function L(a,c,d){var e=f.extend({},d[0]);e.type=a,e.originalEvent={},e.liveFired=b,f.event.handle.call(c,e),e.isDefaultPrevented()&&d[0].preventDefault()}function F(){return!0}function E(){return!1}function m(a,c,d){var e=c+"defer",g=c+"queue",h=c+"mark",i=f.data(a,e,b,!0);i&&(d==="queue"||!f.data(a,g,b,!0))&&(d==="mark"||!f.data(a,h,b,!0))&&setTimeout(function(){!f.data(a,g,b,!0)&&!f.data(a,h,b,!0)&&(f.removeData(a,e,!0),i.resolve())},0)}function l(a){for(var b in a)if(b!=="toJSON")return!1;return!0}function k(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(j,"$1-$2").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNaN(d)?i.test(d)?f.parseJSON(d):d:parseFloat(d)}catch(g){}f.data(a,c,d)}else d=b}return d}var c=a.document,d=a.navigator,e=a.location,f=function(){function H(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(H,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/\d/,n=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,o=/^[\],:{}\s]*$/,p=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,q=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,r=/(?:^|:|,)(?:\s*\[)+/g,s=/(webkit)[ \/]([\w.]+)/,t=/(opera)(?:.*version)?[ \/]([\w.]+)/,u=/(msie) ([\w.]+)/,v=/(mozilla)(?:.*? rv:([\w.]+))?/,w=d.userAgent,x,y,z,A=Object.prototype.toString,B=Object.prototype.hasOwnProperty,C=Array.prototype.push,D=Array.prototype.slice,E=String.prototype.trim,F=Array.prototype.indexOf,G={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=n.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.6.1",length:0,size:function(){return this.length},toArray:function(){return D.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?C.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),y.done(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(D.apply(this,arguments),"slice",D.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:C,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j<k;j++)if((a=arguments[j])!=null)for(c in a){d=i[c],f=a[c];if(i===f)continue;l&&f&&(e.isPlainObject(f)||(g=e.isArray(f)))?(g?(g=!1,h=d&&e.isArray(d)?d:[]):h=d&&e.isPlainObject(d)?d:{},i[c]=e.extend(l,h,f)):f!==b&&(i[c]=f)}return i},e.extend({noConflict:function(b){a.$===e&&(a.$=g),b&&a.jQuery===e&&(a.jQuery=f);return e},isReady:!1,readyWait:1,holdReady:function(a){a?e.readyWait++:e.ready(!0)},ready:function(a){if(a===!0&&!--e.readyWait||a!==!0&&!e.isReady){if(!c.body)return setTimeout(e.ready,1);e.isReady=!0;if(a!==!0&&--e.readyWait>0)return;y.resolveWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").unbind("ready")}},bindReady:function(){if(!y){y=e._Deferred();if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",z,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",z),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&H()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNaN:function(a){return a==null||!m.test(a)||isNaN(a)},type:function(a){return a==null?String(a):G[A.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;if(a.constructor&&!B.call(a,"constructor")&&!B.call(a.constructor.prototype,"isPrototypeOf"))return!1;var c;for(c in a);return c===b||B.call(a,c)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw a},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(o.test(b.replace(p,"@").replace(q,"]").replace(r,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(b,c,d){a.DOMParser?(d=new DOMParser,c=d.parseFromString(b,"text/xml")):(c=new ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(b)),d=c.documentElement,(!d||!d.nodeName||d.nodeName==="parsererror")&&e.error("Invalid XML: "+b);return c},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g<h;)if(c.apply(a[g++],d)===!1)break}else if(i){for(f in a)if(c.call(a[f],f,a[f])===!1)break}else for(;g<h;)if(c.call(a[g],g,a[g++])===!1)break;return a},trim:E?function(a){return a==null?"":E.call(a)}:function(a){return a==null?"":(a+"").replace(k,"").replace(l,"")},makeArray:function(a,b){var c=b||[];if(a!=null){var d=e.type(a);a.length==null||d==="string"||d==="function"||d==="regexp"||e.isWindow(a)?C.call(c,a):e.merge(c,a)}return c},inArray:function(a,b){if(F)return F.call(b,a);for(var c=0,d=b.length;c<d;c++)if(b[c]===a)return c;return-1},merge:function(a,c){var d=a.length,e=0;if(typeof c.length=="number")for(var f=c.length;e<f;e++)a[d++]=c[e];else while(c[e]!==b)a[d++]=c[e++];a.length=d;return a},grep:function(a,b,c){var d=[],e;c=!!c;for(var f=0,g=a.length;f<g;f++)e=!!b(a[f],f),c!==e&&d.push(a[f]);return d},map:function(a,c,d){var f,g,h=[],i=0,j=a.length,k=a instanceof e||j!==b&&typeof j=="number"&&(j>0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i<j;i++)f=c(a[i],i,d),f!=null&&(h[h.length]=f);else for(g in a)f=c(a[g],g,d),f!=null&&(h[h.length]=f);return h.concat.apply([],h)},guid:1,proxy:function(a,c){if(typeof c=="string"){var d=a[c];c=a,a=d}if(!e.isFunction(a))return b;var f=D.call(arguments,2),g=function(){return a.apply(c,f.concat(D.call(arguments)))};g.guid=a.guid=a.guid||g.guid||e.guid++;return g},access:function(a,c,d,f,g,h){var i=a.length;if(typeof c=="object"){for(var j in c)e.access(a,j,c[j],f,g,d);return a}if(d!==b){f=!h&&f&&e.isFunction(d);for(var k=0;k<i;k++)g(a[k],c,f?d.call(a[k],k,g(a[k],c)):d,h);return a}return i?g(a[0],c):b},now:function(){return(new Date).getTime()},uaMatch:function(a){a=a.toLowerCase();var b=s.exec(a)||t.exec(a)||u.exec(a)||a.indexOf("compatible")<0&&v.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},sub:function(){function a(b,c){return new a.fn.init(b,c)}e.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function(d,f){f&&f instanceof e&&!(f instanceof a)&&(f=a(f));return e.fn.init.call(this,d,f,b)},a.fn.init.prototype=a.fn;var b=a(c);return a},browser:{}}),e.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){G["[object "+b+"]"]=b.toLowerCase()}),x=e.uaMatch(w),x.browser&&(e.browser[x.browser]=!0,e.browser.version=x.version),e.browser.webkit&&(e.browser.safari=!0),j.test(" ")&&(k=/^[\s\xA0]+/,l=/[\s\xA0]+$/),h=e(c),c.addEventListener?z=function(){c.removeEventListener("DOMContentLoaded",z,!1),e.ready()}:c.attachEvent&&(z=function(){c.readyState==="complete"&&(c.detachEvent("onreadystatechange",z),e.ready())});return e}(),g="done fail isResolved isRejected promise then always pipe".split(" "),h=[].slice;f.extend({_Deferred:function(){var a=[],b,c,d,e={done:function(){if(!d){var c=arguments,g,h,i,j,k;b&&(k=b,b=0);for(g=0,h=c.length;g<h;g++)i=c[g],j=f.type(i),j==="array"?e.done.apply(e,i):j==="function"&&a.push(i);k&&e.resolveWith(k[0],k[1])}return this},resolveWith:function(e,f){if(!d&&!b&&!c){f=f||[],c=1;try{while(a[0])a.shift().apply(e,f)}finally{b=[e,f],c=0}}return this},resolve:function(){e.resolveWith(this,arguments);return this},isResolved:function(){return!!c||!!b},cancel:function(){d=1,a=[];return this}};return e},Deferred:function(a){var b=f._Deferred(),c=f._Deferred(),d;f.extend(b,{then:function(a,c){b.done(a).fail(c);return this},always:function(){return b.done.apply(b,arguments).fail.apply(this,arguments)},fail:c.done,rejectWith:c.resolveWith,reject:c.resolve,isRejected:c.isResolved,pipe:function(a,c){return f.Deferred(function(d){f.each({done:[a,"resolve"],fail:[c,"reject"]},function(a,c){var e=c[0],g=c[1],h;f.isFunction(e)?b[a](function(){h=e.apply(this,arguments),h&&f.isFunction(h.promise)?h.promise().then(d.resolve,d.reject):d[g](h)}):b[a](d[g])})}).promise()},promise:function(a){if(a==null){if(d)return d;d=a={}}var c=g.length;while(c--)a[g[c]]=b[g[c]];return a}}),b.done(c.cancel).fail(b.cancel),delete b.cancel,a&&a.call(b,b);return b},when:function(a){function i(a){return function(c){b[a]=arguments.length>1?h.call(arguments,0):c,--e||g.resolveWith(g,h.call(b,0))}}var b=arguments,c=0,d=b.length,e=d,g=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred();if(d>1){for(;c<d;c++)b[c]&&f.isFunction(b[c].promise)?b[c].promise().then(i(c),g.reject):--e;e||g.resolveWith(g,b)}else g!==a&&g.resolveWith(g,d?[a]:[]);return g.promise()}}),f.support=function(){var a=c.createElement("div"),b=c.documentElement,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r;a.setAttribute("className","t"),a.innerHTML="   <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>",d=a.getElementsByTagName("*"),e=a.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};f=c.createElement("select"),g=f.appendChild(c.createElement("option")),h=a.getElementsByTagName("input")[0],j={leadingWhitespace:a.firstChild.nodeType===3,tbody:!a.getElementsByTagName("tbody").length,htmlSerialize:!!a.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55$/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:h.value==="on",optSelected:g.selected,getSetAttribute:a.className!=="t",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},h.checked=!0,j.noCloneChecked=h.cloneNode(!0).checked,f.disabled=!0,j.optDisabled=!g.disabled;try{delete a.test}catch(s){j.deleteExpando=!1}!a.addEventListener&&a.attachEvent&&a.fireEvent&&(a.attachEvent("onclick",function b(){j.noCloneEvent=!1,a.detachEvent("onclick",b)}),a.cloneNode(!0).fireEvent("onclick")),h=c.createElement("input"),h.value="t",h.setAttribute("type","radio"),j.radioValue=h.value==="t",h.setAttribute("checked","checked"),a.appendChild(h),k=c.createDocumentFragment(),k.appendChild(a.firstChild),j.checkClone=k.cloneNode(!0).cloneNode(!0).lastChild.checked,a.innerHTML="",a.style.width=a.style.paddingLeft="1px",l=c.createElement("body"),m={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"};for(q in m)l.style[q]=m[q];l.appendChild(a),b.insertBefore(l,b.firstChild),j.appendChecked=h.checked,j.boxModel=a.offsetWidth===2,"zoom"in a.style&&(a.style.display="inline",a.style.zoom=1,j.inlineBlockNeedsLayout=a.offsetWidth===2,a.style.display="",a.innerHTML="<div style='width:4px;'></div>",j.shrinkWrapBlocks=a.offsetWidth!==2),a.innerHTML="<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>",n=a.getElementsByTagName("td"),r=n[0].offsetHeight===0,n[0].style.display="",n[1].style.display="none",j.reliableHiddenOffsets=r&&n[0].offsetHeight===0,a.innerHTML="",c.defaultView&&c.defaultView.getComputedStyle&&(i=c.createElement("div"),i.style.width="0",i.style.marginRight="0",a.appendChild(i),j.reliableMarginRight=(parseInt((c.defaultView.getComputedStyle(i,null)||{marginRight:0}).marginRight,10)||0)===0),l.innerHTML="",b.removeChild(l);if(a.attachEvent)for(q in{submit:1,change:1,focusin:1})p="on"+q,r=p in a,r||(a.setAttribute(p,"return;"),r=typeof a[p]=="function"),j[q+"Bubbles"]=r;return j}(),f.boxModel=f.support.boxModel;var i=/^(?:\{.*\}|\[.*\])$/,j=/([a-z])([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!l(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g=f.expando,h=typeof c=="string",i,j=a.nodeType,k=j?f.cache:a,l=j?a[f.expando]:a[f.expando]&&f.expando;if((!l||e&&l&&!k[l][g])&&h&&d===b)return;l||(j?a[f.expando]=l=++f.uuid:l=f.expando),k[l]||(k[l]={},j||(k[l].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?k[l][g]=f.extend(k[l][g],c):k[l]=f.extend(k[l],c);i=k[l],e&&(i[g]||(i[g]={}),i=i[g]),d!==b&&(i[f.camelCase(c)]=d);if(c==="events"&&!i[c])return i[g]&&i[g].events;return h?i[f.camelCase(c)]:i}},removeData:function(b,c,d){if(!!f.acceptData(b)){var e=f.expando,g=b.nodeType,h=g?f.cache:b,i=g?b[f.expando]:f.expando;if(!h[i])return;if(c){var j=d?h[i][e]:h[i];if(j){delete j[c];if(!l(j))return}}if(d){delete h[i][e];if(!l(h[i]))return}var k=h[i][e];f.support.deleteExpando||h!=a?delete h[i]:h[i]=null,k?(h[i]={},g||(h[i].toJSON=f.noop),h[i][e]=k):g&&(f.support.deleteExpando?delete b[f.expando]:b.removeAttribute?b.removeAttribute(f.expando):b[f.expando]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d=null;if(typeof a=="undefined"){if(this.length){d=f.data(this[0]);if(this[0].nodeType===1){var e=this[0].attributes,g;for(var h=0,i=e.length;h<i;h++)g=e[h].name,g.indexOf("data-")===0&&(g=f.camelCase(g.substring(5)),k(this[0],g,d[g]))}}return d}if(typeof a=="object")return this.each(function(){f.data(this,a)});var j=a.split(".");j[1]=j[1]?"."+j[1]:"";if(c===b){d=this.triggerHandler("getData"+j[1]+"!",[j[0]]),d===b&&this.length&&(d=f.data(this[0],a),d=k(this[0],a,d));return d===b&&j[1]?this.data(j[0]):d}return this.each(function(){var b=f(this),d=[j[0],c];b.triggerHandler("setData"+j[1]+"!",d),f.data(this,a,c),b.triggerHandler("changeData"+j[1]+"!",d)})},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,c){a&&(c=(c||"fx")+"mark",f.data(a,c,(f.data(a,c,b,!0)||0)+1,!0))},_unmark:function(a,c,d){a!==!0&&(d=c,c=a,a=!1);if(c){d=d||"fx";var e=d+"mark",g=a?0:(f.data(c,e,b,!0)||1)-1;g?f.data(c,e,g,!0):(f.removeData(c,e,!0),m(c,d,"mark"))}},queue:function(a,c,d){if(a){c=(c||"fx")+"queue";var e=f.data(a,c,b,!0);d&&(!e||f.isArray(d)?e=f.data(a,c,f.makeArray(d),!0):e.push(d));return e||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e;d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),d.call(a,function(){f.dequeue(a,b)})),c.length||(f.removeData(a,b+"queue",!0),m(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){typeof a!="string"&&(c=a,a="fx");if(c===b)return f.queue(this[0],a);return this.each(function(){var b=f.queue(this,a,c);a==="fx"&&b[0]!=="inprogress"&&f.dequeue(this,a)})},dequeue:function(a){return this.each(function(){f.dequeue(this,a)})},delay:function(a,b){a=f.fx?f.fx.speeds[a]||a:a,b=b||"fx";return this.queue(b,function(){var c=this;setTimeout(function(){f.dequeue(c,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){function m(){--h||d.resolveWith(e,[e])}typeof a!="string"&&(c=a,a=b),a=a||"fx";var d=f.Deferred(),e=this,g=e.length,h=1,i=a+"defer",j=a+"queue",k=a+"mark",l;while(g--)if(l=f.data(e[g],i,b,!0)||(f.data(e[g],j,b,!0)||f.data(e[g],k,b,!0))&&f.data(e[g],i,f._Deferred(),!0))h++,l.done(m);m();return d.promise()}});var n=/[\n\t\r]/g,o=/\s+/,p=/\r/g,q=/^(?:button|input)$/i,r=/^(?:button|input|object|select|textarea)$/i,s=/^a(?:rea)?$/i,t=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,u=/\:/,v,w;f.fn.extend({attr:function(a,b){return f.access(this,a,b,!0,f.attr)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,a,b,!0,f.prop)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.addClass(a.call(this,b,c.attr("class")||""))});if(a&&typeof a=="string"){var b=(a||"").split(o);for(var c=0,d=this.length;c<d;c++){var e=this[c];if(e.nodeType===1)if(!e.className)e.className=a;else{var g=" "+e.className+" ",h=e.className;for(var i=0,j=b.length;i<j;i++)g.indexOf(" "+b[i]+" ")<0&&(h+=" "+b[i]);e.className=f.trim(h)}}}return this},removeClass:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.removeClass(a.call(this,b,c.attr("class")))});if(a&&typeof a=="string"||a===b){var c=(a||"").split(o);for(var d=0,e=this.length;d<e;d++){var g=this[d];if(g.nodeType===1&&g.className)if(a){var h=(" "+g.className+" ").replace(n," ");for(var i=0,j=c.length;i<j;i++)h=h.replace(" "+c[i]+" "," ");g.className=f.trim(h)}else g.className=""}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";if(f.isFunction(a))return this.each(function(c){var d=f(this);d.toggleClass(a.call(this,c,d.attr("class"),b),b)});return this.each(function(){if(c==="string"){var e,g=0,h=f(this),i=b,j=a.split(o);while(e=j[g++])i=d?i:!h.hasClass(e),h[i?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&f._data(this,"__className__",this.className),this.className=this.className||a===!1?"":f._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ";for(var c=0,d=this.length;c<d;c++)if((" "+this[c].className+" ").replace(n," ").indexOf(b)>-1)return!0;return!1},val:function(a){var c,d,e=this[0];if(!arguments.length){if(e){c=f.valHooks[e.nodeName.toLowerCase()]||f.valHooks[e.type];if(c&&"get"in c&&(d=c.get(e,"value"))!==b)return d;return(e.value||"").replace(p,"")}return b}var g=f.isFunction(a);return this.each(function(d){var e=f(this),h;if(this.nodeType===1){g?h=a.call(this,d,e.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c=a.selectedIndex,d=[],e=a.options,g=a.type==="select-one";if(c<0)return null;for(var h=g?c:0,i=g?c+1:e.length;h<i;h++){var j=e[h];if(j.selected&&(f.support.optDisabled?!j.disabled:j.getAttribute("disabled")===null)&&(!j.parentNode.disabled||!f.nodeName(j.parentNode,"optgroup"))){b=f(j).val();if(g)return b;d.push(b)}}if(g&&!d.length&&e.length)return f(e[c]).val();return d},set:function(a,b){var c=f.makeArray(b);f(a).find("option").each(function(){this.selected=f.inArray(f(this).val(),c)>=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attrFix:{tabindex:"tabIndex"},attr:function(a,c,d,e){var g=a.nodeType;if(!a||g===3||g===8||g===2)return b;if(e&&c in f.attrFn)return f(a)[c](d);if(!("getAttribute"in a))return f.prop(a,c,d);var h,i,j=g!==1||!f.isXMLDoc(a);c=j&&f.attrFix[c]||c,i=f.attrHooks[c],i||(!t.test(c)||typeof d!="boolean"&&d!==b&&d.toLowerCase()!==c.toLowerCase()?v&&(f.nodeName(a,"form")||u.test(c))&&(i=v):i=w);if(d!==b){if(d===null){f.removeAttr(a,c);return b}if(i&&"set"in i&&j&&(h=i.set(a,d,c))!==b)return h;a.setAttribute(c,""+d);return d}if(i&&"get"in i&&j)return i.get(a,c);h=a.getAttribute(c);return h===null?b:h},removeAttr:function(a,b){var c;a.nodeType===1&&(b=f.attrFix[b]||b,f.support.getSetAttribute?a.removeAttribute(b):(f.attr(a,b,""),a.removeAttributeNode(a.getAttributeNode(b))),t.test(b)&&(c=f.propFix[b]||b)in a&&(a[c]=!1))},attrHooks:{type:{set:function(a,b){if(q.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.value;a.setAttribute("type",b),c&&(a.value=c);return b}}},tabIndex:{get:function(a){var c=a.getAttributeNode("tabIndex");return c&&c.specified?parseInt(c.value,10):r.test(a.nodeName)||s.test(a.nodeName)&&a.href?0:b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e=a.nodeType;if(!a||e===3||e===8||e===2)return b;var g,h,i=e!==1||!f.isXMLDoc(a);c=i&&f.propFix[c]||c,h=f.propHooks[c];return d!==b?h&&"set"in h&&(g=h.set(a,d,c))!==b?g:a[c]=d:h&&"get"in h&&(g=h.get(a,c))!==b?g:a[c]},propHooks:{}}),w={get:function(a,c){return a[f.propFix[c]||c]?c.toLowerCase():b},set:function(a,b,c){var d;b===!1?f.removeAttr(a,c):(d=f.propFix[c]||c,d in a&&(a[d]=b),a.setAttribute(c,c.toLowerCase()));return c}},f.attrHooks.value={get:function(a,b){if(v&&f.nodeName(a,"button"))return v.get(a,b);return a.value},set:function(a,b,c){if(v&&f.nodeName(a,"button"))return v.set(a,b,c);a.value=b}},f.support.getSetAttribute||(f.attrFix=f.propFix,v=f.attrHooks.name=f.valHooks.button={get:function(a,c){var d;d=a.getAttributeNode(c);return d&&d.nodeValue!==""?d.nodeValue:b},set:function(a,b,c){var d=a.getAttributeNode(c);if(d){d.nodeValue=b;return b}}},f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})})),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}})),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var x=Object.prototype.hasOwnProperty,y=/\.(.*)$/,z=/^(?:textarea|input|select)$/i,A=/\./g,B=/ /g,C=/[^\w\s.|`]/g,D=function(a){return a.replace(C,"\\$&")};f.event={add:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){if(d===!1)d=E;else if(!d)return;var g,h;d.handler&&(g=d,d=g.handler),d.guid||(d.guid=f.guid++);var i=f._data(a);if(!i)return;var j=i.events,k=i.handle;j||(i.events=j={}),k||(i.handle=k=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.handle.apply(k.elem,arguments):b}),k.elem=a,c=c.split(" ");var l,m=0,n;while(l=c[m++]){h=g?f.extend({},g):{handler:d,data:e},l.indexOf(".")>-1?(n=l.split("."),l=n.shift(),h.namespace=n.slice(0).sort().join(".")):(n=[],h.namespace=""),h.type=l,h.guid||(h.guid=d.guid);var o=j[l],p=f.event.special[l]||{};if(!o){o=j[l]=[];if(!p.setup||p.setup.call(a,e,n,k)===!1)a.addEventListener?a.addEventListener(l,k,!1):a.attachEvent&&a.attachEvent("on"+l,k)}p.add&&(p.add.call(a,h),h.handler.guid||(h.handler.guid=d.guid)),o.push(h),f.event.global[l]=!0}a=null}},global:{},remove:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){d===!1&&(d=E);var g,h,i,j,k=0,l,m,n,o,p,q,r,s=f.hasData(a)&&f._data(a),t=s&&s.events;if(!s||!t)return;c&&c.type&&(d=c.handler,c=c.type);if(!c||typeof c=="string"&&c.charAt(0)==="."){c=c||"";for(h in t)f.event.remove(a,h+c);return}c=c.split(" ");while(h=c[k++]){r=h,q=null,l=h.indexOf(".")<0,m=[],l||(m=h.split("."),h=m.shift(),n=new RegExp("(^|\\.)"+f.map(m.slice(0).sort(),D).join("\\.(?:.*\\.)?")+"(\\.|$)")),p=t[h];if(!p)continue;if(!d){for(j=0;j<p.length;j++){q=p[j];if(l||n.test(q.namespace))f.event.remove(a,r,q.handler,j),p.splice(j--,1)}continue}o=f.event.special[h]||{};for(j=e||0;j<p.length;j++){q=p[j];if(d.guid===q.guid){if(l||n.test(q.namespace))e==null&&p.splice(j--,1),o.remove&&o.remove.call(a,q);if(e!=null)break}}if(p.length===0||e!=null&&p.length===1)(!o.teardown||o.teardown.call(a,m)===!1)&&f.removeEvent(a,h,s.handle),g=null,delete t[h]}if(f.isEmptyObject(t)){var u=s.handle;u&&(u.elem=null),delete s.events,delete s.handle,f.isEmptyObject(s)&&f.removeData(a,b,!0)}}},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,e,g){var h=c.type||c,i=[],j;h.indexOf("!")>=0&&(h=h.slice(0,-1),j=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if(!!e&&!f.event.customEvent[h]||!!f.event.global[h]){c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.exclusive=j,c.namespace=i.join("."),c.namespace_re=new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)");if(g||!e)c.preventDefault(),c.stopPropagation();if(!e){f.each(f.cache,function(){var a=f.expando,b=this[a];b&&b.events&&b.events[h]&&f.event.trigger(c,d,b.handle.elem
+)});return}if(e.nodeType===3||e.nodeType===8)return;c.result=b,c.target=e,d=d?f.makeArray(d):[],d.unshift(c);var k=e,l=h.indexOf(":")<0?"on"+h:"";do{var m=f._data(k,"handle");c.currentTarget=k,m&&m.apply(k,d),l&&f.acceptData(k)&&k[l]&&k[l].apply(k,d)===!1&&(c.result=!1,c.preventDefault()),k=k.parentNode||k.ownerDocument||k===c.target.ownerDocument&&a}while(k&&!c.isPropagationStopped());if(!c.isDefaultPrevented()){var n,o=f.event.special[h]||{};if((!o._default||o._default.call(e.ownerDocument,c)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)){try{l&&e[h]&&(n=e[l],n&&(e[l]=null),f.event.triggered=h,e[h]())}catch(p){}n&&(e[l]=n),f.event.triggered=b}}return c.result}},handle:function(c){c=f.event.fix(c||a.event);var d=((f._data(this,"events")||{})[c.type]||[]).slice(0),e=!c.exclusive&&!c.namespace,g=Array.prototype.slice.call(arguments,0);g[0]=c,c.currentTarget=this;for(var h=0,i=d.length;h<i;h++){var j=d[h];if(e||c.namespace_re.test(j.namespace)){c.handler=j.handler,c.data=j.data,c.handleObj=j;var k=j.handler.apply(this,g);k!==b&&(c.result=k,k===!1&&(c.preventDefault(),c.stopPropagation()));if(c.isImmediatePropagationStopped())break}}return c.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(a){if(a[f.expando])return a;var d=a;a=f.Event(d);for(var e=this.props.length,g;e;)g=this.props[--e],a[g]=d[g];a.target||(a.target=a.srcElement||c),a.target.nodeType===3&&(a.target=a.target.parentNode),!a.relatedTarget&&a.fromElement&&(a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement);if(a.pageX==null&&a.clientX!=null){var h=a.target.ownerDocument||c,i=h.documentElement,j=h.body;a.pageX=a.clientX+(i&&i.scrollLeft||j&&j.scrollLeft||0)-(i&&i.clientLeft||j&&j.clientLeft||0),a.pageY=a.clientY+(i&&i.scrollTop||j&&j.scrollTop||0)-(i&&i.clientTop||j&&j.clientTop||0)}a.which==null&&(a.charCode!=null||a.keyCode!=null)&&(a.which=a.charCode!=null?a.charCode:a.keyCode),!a.metaKey&&a.ctrlKey&&(a.metaKey=a.ctrlKey),!a.which&&a.button!==b&&(a.which=a.button&1?1:a.button&2?3:a.button&4?2:0);return a},guid:1e8,proxy:f.proxy,special:{ready:{setup:f.bindReady,teardown:f.noop},live:{add:function(a){f.event.add(this,O(a.origType,a.selector),f.extend({},a,{handler:N,guid:a.handler.guid}))},remove:function(a){f.event.remove(this,O(a.origType,a.selector),a)}},beforeunload:{setup:function(a,b,c){f.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}}},f.removeEvent=c.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){a.detachEvent&&a.detachEvent("on"+b,c)},f.Event=function(a,b){if(!this.preventDefault)return new f.Event(a,b);a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?F:E):this.type=a,b&&f.extend(this,b),this.timeStamp=f.now(),this[f.expando]=!0},f.Event.prototype={preventDefault:function(){this.isDefaultPrevented=F;var a=this.originalEvent;!a||(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){this.isPropagationStopped=F;var a=this.originalEvent;!a||(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=F,this.stopPropagation()},isDefaultPrevented:E,isPropagationStopped:E,isImmediatePropagationStopped:E};var G=function(a){var b=a.relatedTarget;a.type=a.data;try{if(b&&b!==c&&!b.parentNode)return;while(b&&b!==this)b=b.parentNode;b!==this&&f.event.handle.apply(this,arguments)}catch(d){}},H=function(a){a.type=a.data,f.event.handle.apply(this,arguments)};f.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){f.event.special[a]={setup:function(c){f.event.add(this,b,c&&c.selector?H:G,a)},teardown:function(a){f.event.remove(this,b,a&&a.selector?H:G)}}}),f.support.submitBubbles||(f.event.special.submit={setup:function(a,b){if(!f.nodeName(this,"form"))f.event.add(this,"click.specialSubmit",function(a){var b=a.target,c=b.type;(c==="submit"||c==="image")&&f(b).closest("form").length&&L("submit",this,arguments)}),f.event.add(this,"keypress.specialSubmit",function(a){var b=a.target,c=b.type;(c==="text"||c==="password")&&f(b).closest("form").length&&a.keyCode===13&&L("submit",this,arguments)});else return!1},teardown:function(a){f.event.remove(this,".specialSubmit")}});if(!f.support.changeBubbles){var I,J=function(a){var b=a.type,c=a.value;b==="radio"||b==="checkbox"?c=a.checked:b==="select-multiple"?c=a.selectedIndex>-1?f.map(a.options,function(a){return a.selected}).join("-"):"":f.nodeName(a,"select")&&(c=a.selectedIndex);return c},K=function(c){var d=c.target,e,g;if(!!z.test(d.nodeName)&&!d.readOnly){e=f._data(d,"_change_data"),g=J(d),(c.type!=="focusout"||d.type!=="radio")&&f._data(d,"_change_data",g);if(e===b||g===e)return;if(e!=null||g)c.type="change",c.liveFired=b,f.event.trigger(c,arguments[1],d)}};f.event.special.change={filters:{focusout:K,beforedeactivate:K,click:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(c==="radio"||c==="checkbox"||f.nodeName(b,"select"))&&K.call(this,a)},keydown:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(a.keyCode===13&&!f.nodeName(b,"textarea")||a.keyCode===32&&(c==="checkbox"||c==="radio")||c==="select-multiple")&&K.call(this,a)},beforeactivate:function(a){var b=a.target;f._data(b,"_change_data",J(b))}},setup:function(a,b){if(this.type==="file")return!1;for(var c in I)f.event.add(this,c+".specialChange",I[c]);return z.test(this.nodeName)},teardown:function(a){f.event.remove(this,".specialChange");return z.test(this.nodeName)}},I=f.event.special.change.filters,I.focus=I.beforeactivate}f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){function e(a){var c=f.event.fix(a);c.type=b,c.originalEvent={},f.event.trigger(c,null,c.target),c.isDefaultPrevented()&&a.preventDefault()}var d=0;f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.each(["bind","one"],function(a,c){f.fn[c]=function(a,d,e){var g;if(typeof a=="object"){for(var h in a)this[c](h,d,a[h],e);return this}if(arguments.length===2||d===!1)e=d,d=b;c==="one"?(g=function(a){f(this).unbind(a,g);return e.apply(this,arguments)},g.guid=e.guid||f.guid++):g=e;if(a==="unload"&&c!=="one")this.one(a,d,e);else for(var i=0,j=this.length;i<j;i++)f.event.add(this[i],a,g,d);return this}}),f.fn.extend({unbind:function(a,b){if(typeof a=="object"&&!a.preventDefault)for(var c in a)this.unbind(c,a[c]);else for(var d=0,e=this.length;d<e;d++)f.event.remove(this[d],a,b);return this},delegate:function(a,b,c,d){return this.live(b,c,d,a)},undelegate:function(a,b,c){return arguments.length===0?this.unbind("live"):this.die(b,null,c,a)},trigger:function(a,b){return this.each(function(){f.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return f.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=arguments,c=a.guid||f.guid++,d=0,e=function(c){var e=(f.data(this,"lastToggle"+a.guid)||0)%d;f.data(this,"lastToggle"+a.guid,e+1),c.preventDefault();return b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});var M={focus:"focusin",blur:"focusout",mouseenter:"mouseover",mouseleave:"mouseout"};f.each(["live","die"],function(a,c){f.fn[c]=function(a,d,e,g){var h,i=0,j,k,l,m=g||this.selector,n=g?this:f(this.context);if(typeof a=="object"&&!a.preventDefault){for(var o in a)n[c](o,d,a[o],m);return this}if(c==="die"&&!a&&g&&g.charAt(0)==="."){n.unbind(g);return this}if(d===!1||f.isFunction(d))e=d||E,d=b;a=(a||"").split(" ");while((h=a[i++])!=null){j=y.exec(h),k="",j&&(k=j[0],h=h.replace(y,""));if(h==="hover"){a.push("mouseenter"+k,"mouseleave"+k);continue}l=h,M[h]?(a.push(M[h]+k),h=h+k):h=(M[h]||h)+k;if(c==="live")for(var p=0,q=n.length;p<q;p++)f.event.add(n[p],"live."+O(h,m),{data:d,selector:m,handler:e,origType:h,origHandler:e,preType:l});else n.unbind("live."+O(h,m),e)}return this}}),f.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error".split(" "),function(a,b){f.fn[b]=function(a,c){c==null&&(c=a,a=null);return arguments.length>0?this.bind(b,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0)}),function(){function u(a,b,c,d,e,f){for(var g=0,h=d.length;g<h;g++){var i=d[g];if(i){var j=!1;i=i[a];while(i){if(i.sizcache===c){j=d[i.sizset];break}if(i.nodeType===1){f||(i.sizcache=c,i.sizset=g);if(typeof b!="string"){if(i===b){j=!0;break}}else if(k.filter(b,[i]).length>0){j=i;break}}i=i[a]}d[g]=j}}}function t(a,b,c,d,e,f){for(var g=0,h=d.length;g<h;g++){var i=d[g];if(i){var j=!1;i=i[a];while(i){if(i.sizcache===c){j=d[i.sizset];break}i.nodeType===1&&!f&&(i.sizcache=c,i.sizset=g);if(i.nodeName.toLowerCase()===b){j=i;break}i=i[a]}d[g]=j}}}var a=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d=0,e=Object.prototype.toString,g=!1,h=!0,i=/\\/g,j=/\W/;[0,0].sort(function(){h=!1;return 0});var k=function(b,d,f,g){f=f||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return f;var i,j,n,o,q,r,s,t,u=!0,w=k.isXML(d),x=[],y=b;do{a.exec(""),i=a.exec(y);if(i){y=i[3],x.push(i[1]);if(i[2]){o=i[3];break}}}while(i);if(x.length>1&&m.exec(b))if(x.length===2&&l.relative[x[0]])j=v(x[0]+x[1],d);else{j=l.relative[x[0]]?[d]:k(x.shift(),d);while(x.length)b=x.shift(),l.relative[b]&&(b+=x.shift()),j=v(b,j)}else{!g&&x.length>1&&d.nodeType===9&&!w&&l.match.ID.test(x[0])&&!l.match.ID.test(x[x.length-1])&&(q=k.find(x.shift(),d,w),d=q.expr?k.filter(q.expr,q.set)[0]:q.set[0]);if(d){q=g?{expr:x.pop(),set:p(g)}:k.find(x.pop(),x.length===1&&(x[0]==="~"||x[0]==="+")&&d.parentNode?d.parentNode:d,w),j=q.expr?k.filter(q.expr,q.set):q.set,x.length>0?n=p(j):u=!1;while(x.length)r=x.pop(),s=r,l.relative[r]?s=x.pop():r="",s==null&&(s=d),l.relative[r](n,s,w)}else n=x=[]}n||(n=j),n||k.error(r||b);if(e.call(n)==="[object Array]")if(!u)f.push.apply(f,n);else if(d&&d.nodeType===1)for(t=0;n[t]!=null;t++)n[t]&&(n[t]===!0||n[t].nodeType===1&&k.contains(d,n[t]))&&f.push(j[t]);else for(t=0;n[t]!=null;t++)n[t]&&n[t].nodeType===1&&f.push(j[t]);else p(n,f);o&&(k(o,h,f,g),k.uniqueSort(f));return f};k.uniqueSort=function(a){if(r){g=h,a.sort(r);if(g)for(var b=1;b<a.length;b++)a[b]===a[b-1]&&a.splice(b--,1)}return a},k.matches=function(a,b){return k(a,null,null,b)},k.matchesSelector=function(a,b){return k(b,null,null,[a]).length>0},k.find=function(a,b,c){var d;if(!a)return[];for(var e=0,f=l.order.length;e<f;e++){var g,h=l.order[e];if(g=l.leftMatch[h].exec(a)){var j=g[1];g.splice(1,1);if(j.substr(j.length-1)!=="\\"){g[1]=(g[1]||"").replace(i,""),d=l.find[h](g,b,c);if(d!=null){a=a.replace(l.match[h],"");break}}}}d||(d=typeof b.getElementsByTagName!="undefined"?b.getElementsByTagName("*"):[]);return{set:d,expr:a}},k.filter=function(a,c,d,e){var f,g,h=a,i=[],j=c,m=c&&c[0]&&k.isXML(c[0]);while(a&&c.length){for(var n in l.filter)if((f=l.leftMatch[n].exec(a))!=null&&f[2]){var o,p,q=l.filter[n],r=f[1];g=!1,f.splice(1,1);if(r.substr(r.length-1)==="\\")continue;j===i&&(i=[]);if(l.preFilter[n]){f=l.preFilter[n](f,j,d,i,e,m);if(!f)g=o=!0;else if(f===!0)continue}if(f)for(var s=0;(p=j[s])!=null;s++)if(p){o=q(p,f,s,j);var t=e^!!o;d&&o!=null?t?g=!0:j[s]=!1:t&&(i.push(p),g=!0)}if(o!==b){d||(j=i),a=a.replace(l.match[n],"");if(!g)return[];break}}if(a===h)if(g==null)k.error(a);else break;h=a}return j},k.error=function(a){throw"Syntax error, unrecognized expression: "+a};var l=k.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(a){return a.getAttribute("href")},type:function(a){return a.getAttribute("type")}},relative:{"+":function(a,b){var c=typeof b=="string",d=c&&!j.test(b),e=c&&!d;d&&(b=b.toLowerCase());for(var f=0,g=a.length,h;f<g;f++)if(h=a[f]){while((h=h.previousSibling)&&h.nodeType!==1);a[f]=e||h&&h.nodeName.toLowerCase()===b?h||!1:h===b}e&&k.filter(b,a,!0)},">":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!j.test(b)){b=b.toLowerCase();for(;e<f;e++){c=a[e];if(c){var g=c.parentNode;a[e]=g.nodeName.toLowerCase()===b?g:!1}}}else{for(;e<f;e++)c=a[e],c&&(a[e]=d?c.parentNode:c.parentNode===b);d&&k.filter(b,a,!0)}},"":function(a,b,c){var e,f=d++,g=u;typeof b=="string"&&!j.test(b)&&(b=b.toLowerCase(),e=b,g=t),g("parentNode",b,f,a,e,c)},"~":function(a,b,c){var e,f=d++,g=u;typeof b=="string"&&!j.test(b)&&(b=b.toLowerCase(),e=b,g=t),g("previousSibling",b,f,a,e,c)}},find:{ID:function(a,b,c){if(typeof b.getElementById!="undefined"&&!c){var d=b.getElementById(a[1]);return d&&d.parentNode?[d]:[]}},NAME:function(a,b){if(typeof b.getElementsByName!="undefined"){var c=[],d=b.getElementsByName(a[1]);for(var e=0,f=d.length;e<f;e++)d[e].getAttribute("name")===a[1]&&c.push(d[e]);return c.length===0?null:c}},TAG:function(a,b){if(typeof b.getElementsByTagName!="undefined")return b.getElementsByTagName(a[1])}},preFilter:{CLASS:function(a,b,c,d,e,f){a=" "+a[1].replace(i,"")+" ";if(f)return a;for(var g=0,h;(h=b[g])!=null;g++)h&&(e^(h.className&&(" "+h.className+" ").replace(/[\t\n\r]/g," ").indexOf(a)>=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(i,"")},TAG:function(a,b){return a[1].replace(i,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||k.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&k.error(a[0]);a[0]=d++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(i,"");!f&&l.attrMap[g]&&(a[1]=l.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(i,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=k(b[3],null,null,c);else{var g=k.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(l.match.POS.test(b[0])||l.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!k(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return b<c[3]-0},gt:function(a,b,c){return b>c[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=l.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||k.getText([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h<i;h++)if(g[h]===a)return!1;return!0}k.error(e)},CHILD:function(a,b){var c=b[1],d=a;switch(c){case"only":case"first":while(d=d.previousSibling)if(d.nodeType===1)return!1;if(c==="first")return!0;d=a;case"last":while(d=d.nextSibling)if(d.nodeType===1)return!1;return!0;case"nth":var e=b[2],f=b[3];if(e===1&&f===0)return!0;var g=b[0],h=a.parentNode;if(h&&(h.sizcache!==g||!a.nodeIndex)){var i=0;for(d=h.firstChild;d;d=d.nextSibling)d.nodeType===1&&(d.nodeIndex=++i);h.sizcache=g}var j=a.nodeIndex-f;return e===0?j===0:j%e===0&&j/e>=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=l.attrHandle[c]?l.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=l.setFilters[e];if(f)return f(a,c,b,d)}}},m=l.match.POS,n=function(a,b){return"\\"+(b-0+1)};for(var o in l.match)l.match[o]=new RegExp(l.match[o].source+/(?![^\[]*\])(?![^\(]*\))/.source),l.leftMatch[o]=new RegExp(/(^(?:.|\r|\n)*?)/.source+l.match[o].source.replace(/\\(\d+)/g,n));var p=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(q){p=function(a,b){var c=0,d=b||[];if(e.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var f=a.length;c<f;c++)d.push(a[c]);else for(;a[c];c++)d.push(a[c]);return d}}var r,s;c.documentElement.compareDocumentPosition?r=function(a,b){if(a===b){g=!0;return 0}if(!a.compareDocumentPosition||!b.compareDocumentPosition)return a.compareDocumentPosition?-1:1;return a.compareDocumentPosition(b)&4?-1:1}:(r=function(a,b){if(a===b){g=!0;return 0}if(a.sourceIndex&&b.sourceIndex)return a.sourceIndex-b.sourceIndex;var c,d,e=[],f=[],h=a.parentNode,i=b.parentNode,j=h;if(h===i)return s(a,b);if(!h)return-1;if(!i)return 1;while(j)e.unshift(j),j=j.parentNode;j=i;while(j)f.unshift(j),j=j.parentNode;c=e.length,d=f.length;for(var k=0;k<c&&k<d;k++)if(e[k]!==f[k])return s(e[k],f[k]);return k===c?s(a,f[k],-1):s(e[k],b,1)},s=function(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}),k.getText=function(a){var b="",c;for(var d=0;a[d];d++)c=a[d],c.nodeType===3||c.nodeType===4?b+=c.nodeValue:c.nodeType!==8&&(b+=k.getText(c.childNodes));return b},function(){var a=c.createElement("div"),d="script"+(new Date).getTime(),e=c.documentElement;a.innerHTML="<a name='"+d+"'/>",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(l.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},l.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(l.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(l.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=k,b=c.createElement("div"),d="__sizzle__";b.innerHTML="<p class='TEST'></p>";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){k=function(b,e,f,g){e=e||c;if(!g&&!k.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return p(e.getElementsByTagName(b),f);if(h[2]&&l.find.CLASS&&e.getElementsByClassName)return p(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return p([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return p([],f);if(i.id===h[3])return p([i],f)}try{return p(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var m=e,n=e.getAttribute("id"),o=n||d,q=e.parentNode,r=/^\s*[+~]/.test(b);n?o=o.replace(/'/g,"\\$&"):e.setAttribute("id",o),r&&q&&(e=e.parentNode);try{if(!r||q)return p(e.querySelectorAll("[id='"+o+"'] "+b),f)}catch(s){}finally{n||m.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)k[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}k.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!k.isXML(a))try{if(e||!l.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return k(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="<div class='test e'></div><div class='test'></div>";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;l.order.splice(1,0,"CLASS"),l.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?k.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?k.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:k.contains=function(){return!1},k.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var v=function(a,b){var c,d=[],e="",f=b.nodeType?[b]:b;while(c=l.match.PSEUDO.exec(a))e+=c[0],a=a.replace(l.match.PSEUDO,"");a=l.relative[a]?a+"*":a;for(var g=0,h=f.length;g<h;g++)k(a,f[g],d);return k.filter(e,d)};f.find=k,f.expr=k.selectors,f.expr[":"]=f.expr.filters,f.unique=k.uniqueSort,f.text=k.getText,f.isXMLDoc=k.isXML,f.contains=k.contains}();var P=/Until$/,Q=/^(?:parents|prevUntil|prevAll)/,R=/,/,S=/^.[^:#\[\.,]*$/,T=Array.prototype.slice,U=f.expr.match.POS,V={children:!0,contents:!0,next:!0,prev:!0};f.fn.extend({find:function(a){var b=this,c,d;if(typeof a!="string")return f(a).filter(function(){for(c=0,d=b.length;c<d;c++)if(f.contains(b[c],this))return!0});var e=this.pushStack("","find",a),g,h,i;for(c=0,d=this.length;c<d;c++){g=e.length,f.find(a,this[c],e);if(c>0)for(h=g;h<e.length;h++)for(i=0;i<g;i++)if(e[i]===e[h]){e.splice(h--,1);break}}return e},has:function(a){var b=f(a);return this.filter(function(){for(var a=0,c=b.length;a<c;a++)if(f.contains(this,b[a]))return!0})},not:function(a){return this.pushStack(X(this,a,!1),"not",a)},filter:function(a){return this.pushStack(X(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h,i,j={},k=1;if(g&&a.length){for(d=0,e=a.length;d<e;d++)i=a[d],j[i]||(j[i]=U.test(i)?f(i,b||this.context):i);while(g&&g.ownerDocument&&g!==b){for(i in j)h=j[i],(h.jquery?h.index(g)>-1:f(g).is(h))&&c.push({selector:i,elem:g,level:k});g=g.parentNode,k++}}return c}var l=U.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d<e;d++){g=this[d];while(g){if(l?l.index(g)>-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a||typeof a=="string")return f.inArray(this[0],a?f(a):this.parent().children());return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(W(c[0])||W(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c),g=T.call(arguments);P.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!V[a]?f.unique(e):e,(this.length>1||R.test(d))&&Q.test(a)&&(e=e.reverse());return this.pushStack(e,a,g.join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var Y=/ jQuery\d+="(?:\d+|null)"/g,Z=/^\s+/,$=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,_=/<([\w:]+)/,ba=/<tbody/i,bb=/<|&#?\w+;/,bc=/<(?:script|object|embed|option|style)/i,bd=/checked\s*(?:[^=]|=\s*.checked.)/i,be=/\/(java|ecma)script/i,bf=/^\s*<!(?:\[CDATA\[|\-\-)/,bg={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]};bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div<div>","</div>"]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){f(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f(arguments[0]).toArray());return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Y,""):null;if(typeof a=="string"&&!bc.test(a)&&(f.support.leadingWhitespace||!Z.test(a))&&!bg[(_.exec(a)||["",""])[1].toLowerCase()]){a=a.replace($,"<$1></$2>");try{for(var c=0,d=this.length;c<d;c++)this[c].nodeType===1&&(f.cleanData(this[c].getElementsByTagName("*")),this[c].innerHTML=a)}catch(e){this.empty().append(a)}}else f.isFunction(a)?this.each(function(b){var c=f(this);c.html(a.call(this,b,c.html()))}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&this[0].parentNode){if(f.isFunction(a))return this.each(function(b){var c=f(this),d=c.html();c.replaceWith(a.call(this,b,d))});typeof a!="string"&&(a=f(a).detach());return this.each(function(){var b=this.nextSibling,c=this.parentNode;f(this).remove(),b?f(b).before(a):f(c).append(a)})}return this.length?this.pushStack(f(f.isFunction(a)?a():a),"replaceWith",a):this},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){var e,g,h,i,j=a[0],k=[];if(!f.support.checkClone&&arguments.length===3&&typeof j=="string"&&bd.test(j))return this.each(function(){f(this).domManip(a,c,d,!0)});if(f.isFunction(j))return this.each(function(e){var g=f(this);a[0]=j.call(this,e,c?g.html():b),g.domManip(a,c,d)});if(this[0]){i=j&&j.parentNode,f.support.parentNode&&i&&i.nodeType===11&&i.childNodes.length===this.length?e={fragment:i}:e=f.buildFragment(a,this,k),h=e.fragment,h.childNodes.length===1?g=h=h.firstChild:g=h.firstChild;if(g){c=c&&f.nodeName(g,"tr");for(var l=0,m=this.length,n=m-1;l<m;l++)d.call(c?bh(this[l],g):this[l],e.cacheable||m>1&&l<n?f.clone(h,!0,!0):h)}k.length&&f.each(k,bn)}return this}}),f.buildFragment=function(a,b,d){var e,g,h,i=b&&b[0]?b[0].ownerDocument||b[0]:c;a.length===1&&typeof a[0]=="string"&&a[0].length<512&&i===c&&a[0].charAt(0)==="<"&&!bc.test(a[0])&&(f.support.checkClone||!bd.test(a[0]))&&(g=!0,h=f.fragments[a[0]],h&&h!==1&&(e=h)),e||(e=i.createDocumentFragment(),f.clean(a,i,e,d)),g&&(f.fragments[a[0]]=h?e:1);return{fragment:e,cacheable:g}},f.fragments={},f.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){f.fn[a]=function(c){var d=[],e=f(c),g=this.length===1&&this[0].parentNode;if(g&&g.nodeType===11&&g.childNodes.length===1&&e.length===1){e[b](this[0]);return this}for(var h=0,i=e.length;h<i;h++){var j=(h>0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d=a.cloneNode(!0),e,g,h;if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bj(a,d),e=bk(a),g=bk(d);for(h=0;e[h];++h)bj(e[h],g[h])}if(b){bi(a,d);if(c){e=bk(a),g=bk(d);for(h=0;e[h];++h)bi(e[h],g[h])}}return d},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||
+b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!bb.test(k))k=b.createTextNode(k);else{k=k.replace($,"<$1></$2>");var l=(_.exec(k)||["",""])[1].toLowerCase(),m=bg[l]||bg._default,n=m[0],o=b.createElement("div");o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=ba.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]==="<table>"&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&Z.test(k)&&o.insertBefore(b.createTextNode(Z.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i<r;i++)bm(k[i]);else bm(k);k.nodeType?h.push(k):h=f.merge(h,k)}if(d){g=function(a){return!a.type||be.test(a.type)};for(j=0;h[j];j++)if(e&&f.nodeName(h[j],"script")&&(!h[j].type||h[j].type.toLowerCase()==="text/javascript"))e.push(h[j].parentNode?h[j].parentNode.removeChild(h[j]):h[j]);else{if(h[j].nodeType===1){var s=f.grep(h[j].getElementsByTagName("script"),g);h.splice.apply(h,[j+1,0].concat(s))}d.appendChild(h[j])}}return h},cleanData:function(a){var b,c,d=f.cache,e=f.expando,g=f.event.special,h=f.support.deleteExpando;for(var i=0,j;(j=a[i])!=null;i++){if(j.nodeName&&f.noData[j.nodeName.toLowerCase()])continue;c=j[f.expando];if(c){b=d[c]&&d[c][e];if(b&&b.events){for(var k in b.events)g[k]?f.event.remove(j,k):f.removeEvent(j,k,b.handle);b.handle&&(b.handle.elem=null)}h?delete j[f.expando]:j.removeAttribute&&j.removeAttribute(f.expando),delete d[c]}}}});var bo=/alpha\([^)]*\)/i,bp=/opacity=([^)]*)/,bq=/-([a-z])/ig,br=/([A-Z]|^ms)/g,bs=/^-?\d+(?:px)?$/i,bt=/^-?\d/,bu=/^[+\-]=/,bv=/[^+\-\.\de]+/g,bw={position:"absolute",visibility:"hidden",display:"block"},bx=["Left","Right"],by=["Top","Bottom"],bz,bA,bB,bC=function(a,b){return b.toUpperCase()};f.fn.css=function(a,c){if(arguments.length===2&&c===b)return this;return f.access(this,a,c,!0,function(a,c,d){return d!==b?f.style(a,c,d):f.css(a,c)})},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bz(a,"opacity","opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{zIndex:!0,fontWeight:!0,opacity:!0,zoom:!0,lineHeight:!0,widows:!0,orphans:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d;if(h==="number"&&isNaN(d)||d==null)return;h==="string"&&bu.test(d)&&(d=+d.replace(bv,"")+parseFloat(f.css(a,c))),h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(bz)return bz(a,c)},swap:function(a,b,c){var d={};for(var e in b)d[e]=a.style[e],a.style[e]=b[e];c.call(a);for(e in b)a.style[e]=d[e]},camelCase:function(a){return a.replace(bq,bC)}}),f.curCSS=f.css,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){var e;if(c){a.offsetWidth!==0?e=bD(a,b,d):f.swap(a,bw,function(){e=bD(a,b,d)});if(e<=0){e=bz(a,b,b),e==="0px"&&bB&&(e=bB(a,b,b));if(e!=null)return e===""||e==="auto"?"0px":e}if(e<0||e==null){e=a.style[b];return e===""||e==="auto"?"0px":e}return typeof e=="string"?e:e+"px"}},set:function(a,b){if(!bs.test(b))return b;b=parseFloat(b);if(b>=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bp.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle;c.zoom=1;var e=f.isNaN(b)?"":"alpha(opacity="+b*100+")",g=d&&d.filter||c.filter||"";c.filter=bo.test(g)?g.replace(bo,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bz(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(bA=function(a,c){var d,e,g;c=c.replace(br,"-$1").toLowerCase();if(!(e=a.ownerDocument.defaultView))return b;if(g=e.getComputedStyle(a,null))d=g.getPropertyValue(c),d===""&&!f.contains(a.ownerDocument.documentElement,a)&&(d=f.style(a,c));return d}),c.documentElement.currentStyle&&(bB=function(a,b){var c,d=a.currentStyle&&a.currentStyle[b],e=a.runtimeStyle&&a.runtimeStyle[b],f=a.style;!bs.test(d)&&bt.test(d)&&(c=f.left,e&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":d||0,d=f.pixelLeft+"px",f.left=c,e&&(a.runtimeStyle.left=e));return d===""?"auto":d}),bz=bA||bB,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bE=/%20/g,bF=/\[\]$/,bG=/\r?\n/g,bH=/#.*$/,bI=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bJ=/^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bK=/^(?:about|app|app\-storage|.+\-extension|file|widget):$/,bL=/^(?:GET|HEAD)$/,bM=/^\/\//,bN=/\?/,bO=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,bP=/^(?:select|textarea)/i,bQ=/\s+/,bR=/([?&])_=[^&]*/,bS=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bT=f.fn.load,bU={},bV={},bW,bX;try{bW=e.href}catch(bY){bW=c.createElement("a"),bW.href="",bW=bW.href}bX=bS.exec(bW.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bT)return bT.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("<div>").append(c.replace(bO,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bP.test(this.nodeName)||bJ.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bG,"\r\n")}}):{name:b.name,value:c.replace(bG,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.bind(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?f.extend(!0,a,f.ajaxSettings,b):(b=a,a=f.extend(!0,f.ajaxSettings,b));for(var c in{context:1,url:1})c in b?a[c]=b[c]:c in f.ajaxSettings&&(a[c]=f.ajaxSettings[c]);return a},ajaxSettings:{url:bW,isLocal:bK.test(bX[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":"*/*"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML}},ajaxPrefilter:bZ(bU),ajaxTransport:bZ(bV),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a?4:0;var o,r,u,w=l?ca(d,v,l):b,x,y;if(a>=200&&a<300||a===304){if(d.ifModified){if(x=v.getResponseHeader("Last-Modified"))f.lastModified[k]=x;if(y=v.getResponseHeader("Etag"))f.etag[k]=y}if(a===304)c="notmodified",o=!0;else try{r=cb(d,w),c="success",o=!0}catch(z){c="parsererror",u=z}}else{u=c;if(!c||a)c="error",a<0&&(a=0)}v.status=a,v.statusText=c,o?h.resolveWith(e,[r,c,v]):h.rejectWith(e,[v,c,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.resolveWith(e,[v,c]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f._Deferred(),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bI.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.done,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bH,"").replace(bM,bX[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bQ),d.crossDomain==null&&(r=bS.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bX[1]&&r[2]==bX[2]&&(r[3]||(r[1]==="http:"?80:443))==(bX[3]||(bX[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),b$(bU,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bL.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bN.test(d.url)?"&":"?")+d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bR,"$1_="+x);d.url=y+(y===d.url?(bN.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", */*; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=b$(bV,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){status<2?w(-1,z):f.error(z)}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)b_(g,a[g],c,e);return d.join("&").replace(bE,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cc=f.now(),cd=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cc++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(cd.test(b.url)||e&&cd.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(cd,l),b.url===j&&(e&&(k=k.replace(cd,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var ce=a.ActiveXObject?function(){for(var a in cg)cg[a](0,1)}:!1,cf=0,cg;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ch()||ci()}:ch,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,ce&&delete cg[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cf,ce&&(cg||(cg={},f(a).unload(ce)),cg[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cj={},ck,cl,cm=/^(?:toggle|show|hide)$/,cn=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,co,cp=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cq,cr=a.webkitRequestAnimationFrame||a.mozRequestAnimationFrame||a.oRequestAnimationFrame;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cu("show",3),a,b,c);for(var g=0,h=this.length;g<h;g++)d=this[g],d.style&&(e=d.style.display,!f._data(d,"olddisplay")&&e==="none"&&(e=d.style.display=""),e===""&&f.css(d,"display")==="none"&&f._data(d,"olddisplay",cv(d.nodeName)));for(g=0;g<h;g++){d=this[g];if(d.style){e=d.style.display;if(e===""||e==="none")d.style.display=f._data(d,"olddisplay")||""}}return this},hide:function(a,b,c){if(a||a===0)return this.animate(cu("hide",3),a,b,c);for(var d=0,e=this.length;d<e;d++)if(this[d].style){var g=f.css(this[d],"display");g!=="none"&&!f._data(this[d],"olddisplay")&&f._data(this[d],"olddisplay",g)}for(d=0;d<e;d++)this[d].style&&(this[d].style.display="none");return this},_toggle:f.fn.toggle,toggle:function(a,b,c){var d=typeof a=="boolean";f.isFunction(a)&&f.isFunction(b)?this._toggle.apply(this,arguments):a==null||d?this.each(function(){var b=d?a:f(this).is(":hidden");f(this)[b?"show":"hide"]()}):this.animate(cu("toggle",3),a,b,c);return this},fadeTo:function(a,b,c,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=f.speed(b,c,d);if(f.isEmptyObject(a))return this.each(e.complete,[!1]);a=f.extend({},a);return this[e.queue===!1?"each":"queue"](function(){e.queue===!1&&f._mark(this);var b=f.extend({},e),c=this.nodeType===1,d=c&&f(this).is(":hidden"),g,h,i,j,k,l,m,n,o;b.animatedProperties={};for(i in a){g=f.camelCase(i),i!==g&&(a[g]=a[i],delete a[i]),h=a[g],f.isArray(h)?(b.animatedProperties[g]=h[1],h=a[g]=h[0]):b.animatedProperties[g]=b.specialEasing&&b.specialEasing[g]||b.easing||"swing";if(h==="hide"&&d||h==="show"&&!d)return b.complete.call(this);c&&(g==="height"||g==="width")&&(b.overflow=[this.style.overflow,this.style.overflowX,this.style.overflowY],f.css(this,"display")==="inline"&&f.css(this,"float")==="none"&&(f.support.inlineBlockNeedsLayout?(j=cv(this.nodeName),j==="inline"?this.style.display="inline-block":(this.style.display="inline",this.style.zoom=1)):this.style.display="inline-block"))}b.overflow!=null&&(this.style.overflow="hidden");for(i in a)k=new f.fx(this,b,i),h=a[i],cm.test(h)?k[h==="toggle"?d?"show":"hide":h]():(l=cn.exec(h),m=k.cur(),l?(n=parseFloat(l[2]),o=l[3]||(f.cssNumber[i]?"":"px"),o!=="px"&&(f.style(this,i,(n||1)+o),m=(n||1)/k.cur()*m,f.style(this,i,m+o)),l[1]&&(n=(l[1]==="-="?-1:1)*n+m),k.custom(m,n,o)):k.custom(m,h,""));return!0})},stop:function(a,b){a&&this.queue([]),this.each(function(){var a=f.timers,c=a.length;b||f._unmark(!0,this);while(c--)a[c].elem===this&&(b&&a[c](!0),a.splice(c,1))}),b||this.dequeue();return this}}),f.each({slideDown:cu("show",1),slideUp:cu("hide",1),slideToggle:cu("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){f.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),f.extend({speed:function(a,b,c){var d=a&&typeof a=="object"?f.extend({},a):{complete:c||!c&&b||f.isFunction(a)&&a,duration:a,easing:c&&b||b&&!f.isFunction(b)&&b};d.duration=f.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in f.fx.speeds?f.fx.speeds[d.duration]:f.fx.speeds._default,d.old=d.complete,d.complete=function(a){d.queue!==!1?f.dequeue(this):a!==!1&&f._unmark(this),f.isFunction(d.old)&&d.old.call(this)};return d},easing:{linear:function(a,b,c,d){return c+d*a},swing:function(a,b,c,d){return(-Math.cos(a*Math.PI)/2+.5)*d+c}},timers:[],fx:function(a,b,c){this.options=b,this.elem=a,this.prop=c,b.orig=b.orig||{}}}),f.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this),(f.fx.step[this.prop]||f.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a,b=f.css(this.elem,this.prop);return isNaN(a=parseFloat(b))?!b||b==="auto"?0:b:a},custom:function(a,b,c){function h(a){return d.step(a)}var d=this,e=f.fx,g;this.startTime=cq||cs(),this.start=a,this.end=b,this.unit=c||this.unit||(f.cssNumber[this.prop]?"":"px"),this.now=this.start,this.pos=this.state=0,h.elem=this.elem,h()&&f.timers.push(h)&&!co&&(cr?(co=1,g=function(){co&&(cr(g),e.tick())},cr(g)):co=setInterval(e.tick,e.interval))},show:function(){this.options.orig[this.prop]=f.style(this.elem,this.prop),this.options.show=!0,this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur()),f(this.elem).show()},hide:function(){this.options.orig[this.prop]=f.style(this.elem,this.prop),this.options.hide=!0,this.custom(this.cur(),0)},step:function(a){var b=cq||cs(),c=!0,d=this.elem,e=this.options,g,h;if(a||b>=e.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),e.animatedProperties[this.prop]=!0;for(g in e.animatedProperties)e.animatedProperties[g]!==!0&&(c=!1);if(c){e.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){d.style["overflow"+b]=e.overflow[a]}),e.hide&&f(d).hide();if(e.hide||e.show)for(var i in e.animatedProperties)f.style(d,i,e.orig[i]);e.complete.call(d)}return!1}e.duration==Infinity?this.now=b:(h=b-this.startTime,this.state=h/e.duration,this.pos=f.easing[e.animatedProperties[this.prop]](this.state,h,0,1,e.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){for(var a=f.timers,b=0;b<a.length;++b)a[b]()||a.splice(b--,1);a.length||f.fx.stop()},interval:13,stop:function(){clearInterval(co),co=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){f.style(a.elem,"opacity",a.now)},_default:function(a){a.elem.style&&a.elem.style[a.prop]!=null?a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit:a.elem[a.prop]=a.now}}}),f.expr&&f.expr.filters&&(f.expr.filters.animated=function(a){return f.grep(f.timers,function(b){return a===b.elem}).length});var cw=/^t(?:able|d|h)$/i,cx=/^(?:body|html)$/i;"getBoundingClientRect"in c.documentElement?f.fn.offset=function(a){var b=this[0],c;if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);try{c=b.getBoundingClientRect()}catch(d){}var e=b.ownerDocument,g=e.documentElement;if(!c||!f.contains(g,b))return c?{top:c.top,left:c.left}:{top:0,left:0};var h=e.body,i=cy(e),j=g.clientTop||h.clientTop||0,k=g.clientLeft||h.clientLeft||0,l=i.pageYOffset||f.support.boxModel&&g.scrollTop||h.scrollTop,m=i.pageXOffset||f.support.boxModel&&g.scrollLeft||h.scrollLeft,n=c.top+l-j,o=c.left+m-k;return{top:n,left:o}}:f.fn.offset=function(a){var b=this[0];if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);f.offset.initialize();var c,d=b.offsetParent,e=b,g=b.ownerDocument,h=g.documentElement,i=g.body,j=g.defaultView,k=j?j.getComputedStyle(b,null):b.currentStyle,l=b.offsetTop,m=b.offsetLeft;while((b=b.parentNode)&&b!==i&&b!==h){if(f.offset.supportsFixedPosition&&k.position==="fixed")break;c=j?j.getComputedStyle(b,null):b.currentStyle,l-=b.scrollTop,m-=b.scrollLeft,b===d&&(l+=b.offsetTop,m+=b.offsetLeft,f.offset.doesNotAddBorder&&(!f.offset.doesAddBorderForTableAndCells||!cw.test(b.nodeName))&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),e=d,d=b.offsetParent),f.offset.subtractsBorderForOverflowNotVisible&&c.overflow!=="visible"&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),k=c}if(k.position==="relative"||k.position==="static")l+=i.offsetTop,m+=i.offsetLeft;f.offset.supportsFixedPosition&&k.position==="fixed"&&(l+=Math.max(h.scrollTop,i.scrollTop),m+=Math.max(h.scrollLeft,i.scrollLeft));return{top:l,left:m}},f.offset={initialize:function(){var a=c.body,b=c.createElement("div"),d,e,g,h,i=parseFloat(f.css(a,"marginTop"))||0,j="<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";f.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"}),b.innerHTML=j,a.insertBefore(b,a.firstChild),d=b.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,this.doesNotAddBorder=e.offsetTop!==5,this.doesAddBorderForTableAndCells=h.offsetTop===5,e.style.position="fixed",e.style.top="20px",this.supportsFixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",this.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==i,a.removeChild(b),f.offset.initialize=f.noop},bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.offset.initialize(),f.offset.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cy(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cy(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){return this[0]?parseFloat(f.css(this[0],d,"padding")):null},f.fn["outer"+c]=function(a){return this[0]?parseFloat(f.css(this[0],d,a?"margin":"border")):null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c];return e.document.compatMode==="CSS1Compat"&&g||e.document.body["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var h=f.css(e,d),i=parseFloat(h);return f.isNaN(i)?h:i}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f})(window);
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/lex_test.html b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/lex_test.html
new file mode 100644
index 0000000..788bfcd
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/lex_test.html
@@ -0,0 +1,122 @@
+<html>
+  <head>
+    <title>JSONSelect JS lex tests</title>
+    <link rel="stylesheet" type="text/css" href="js/doctest.css" />
+    <script src="js/doctest.js"></script>
+    <script src="../jsonselect.js"></script>
+    <meta http-equiv="Content-type" content="text/html; charset=utf-8">
+  </head>
+  <body>
+
+<div>
+  <button onclick="doctest()" type="button">run tests</button>
+  <pre id="doctestOutput"></pre>
+</div>
+
+    <h2> Tests of the JSONSelect lexer </h2>
+
+<div class="test">
+Simple tokens
+<pre class="doctest">
+$ JSONSelect._lex(">");
+[1, ">"]
+$ JSONSelect._lex("*");
+[1, "*"]
+$ JSONSelect._lex(",");
+[1, ","]
+$ JSONSelect._lex(".");
+[1, "."]
+</pre>
+</div>
+
+<div class="test">
+Offsets
+<pre class="doctest">
+$ JSONSelect._lex("foobar>",6);
+[7, ">"]
+</pre>
+</div>
+
+<div class="test">
+Types
+<pre class="doctest">
+$ JSONSelect._lex("string");
+[6, 3, "string"]
+$ JSONSelect._lex("boolean");
+[7, 3, "boolean"]
+$ JSONSelect._lex("null");
+[4, 3, "null"]
+$ JSONSelect._lex("array");
+[5, 3, "array"]
+$ JSONSelect._lex("object");
+[6, 3, "object"]
+$ JSONSelect._lex("number");
+[6, 3, "number"]
+</pre>
+</div>
+
+<div class="test">
+Whitespace
+<pre class="doctest">
+$ JSONSelect._lex("\r");
+[1, " "]
+$ JSONSelect._lex("\n");
+[1, " "]
+$ JSONSelect._lex("\t");
+[1, " "]
+$ JSONSelect._lex(" ");
+[1, " "]
+$ JSONSelect._lex("     \t   \r\n  !");
+[13, " "]
+</pre>
+
+<div class="test">
+pseudo classes
+<pre class="doctest">
+$ JSONSelect._lex(":root");
+[5, 1, ":root"]
+$ JSONSelect._lex(":first-child");
+[12, 1, ":first-child"]
+$ JSONSelect._lex(":last-child");
+[11, 1, ":last-child"]
+$ JSONSelect._lex(":only-child");
+[11, 1, ":only-child"]
+</pre>
+</div>
+
+<div class="test">
+json strings
+<pre class="doctest">
+$ JSONSelect._lex('"foo bar baz"');
+[13, 4, "foo bar baz"]
+$ JSONSelect._lex('"\\u0020"');
+[8, 4, " "]
+$ JSONSelect._lex('\"not terminated');
+Error: unclosed json string
+$ JSONSelect._lex('"invalid escape: \\y"');
+Error: invalid json string
+</pre>
+</div>
+
+<div class="test">
+identifiers (like after '.')
+<pre class="doctest">
+$ JSONSelect._lex("foo");
+[3, 4, "foo"]
+$ JSONSelect._lex("foo\\ bar");
+[8, 4, "foo bar"]
+$ JSONSelect._lex("_aB129bcde-\\:foo\\@$");
+[18, 4, "_aB129bcde-:foo@"]
+</pre>
+</div>
+
+<div class="test">
+non-ascii
+<pre class="doctest">
+$ JSONSelect._lex("обичам\\ те\\!");
+[12, 4, "обичам те!"]
+</pre>
+</div>
+
+  </body>
+</html>
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/match_test.html b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/match_test.html
new file mode 100644
index 0000000..0fc784f
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/match_test.html
@@ -0,0 +1,71 @@
+<html>
+  <head>
+    <title>JSONSelect JS matching tests</title>
+    <link rel="stylesheet" type="text/css" href="js/doctest.css" />
+    <script src="js/doctest.js"></script>
+    <script src="../jsonselect.js"></script>
+    <meta http-equiv="Content-type" content="text/html; charset=utf-8">
+  </head>
+  <body>
+
+<div>
+  <button onclick="doctest()" type="button">run tests</button>
+  <pre id="doctestOutput"></pre>
+</div>
+
+    <h2> Tests of the JSONSelect matcher </h2>
+
+<div class="test">
+Types
+<pre class="doctest">
+$ JSONSelect.match("null", null);
+[null]
+$ JSONSelect.match("array", { 1: [], 2: [] });
+[[], []]
+$ JSONSelect.match("object", [ {}, {} ]);
+[{}, {}]
+$ JSONSelect.match("string", [ "a", 1, true, null, false, "b", 3.1415, "c" ] );
+["a", "b", "c"]
+$ JSONSelect.match("boolean", [ "a", 1, true, null, false, "b", 3.1415, "c" ] );
+[true, false]
+$ JSONSelect.match("number", [ "a", 1, true, null, false, "b", 3.1415, "c" ] );
+[1, 3.1415]
+</pre>
+</div>
+
+<div class="test">
+IDs
+<pre class="doctest">
+$ JSONSelect.match(".foo", {foo: "aMatch", bar: [ { foo: "anotherMatch" } ] });
+["aMatch", "anotherMatch"]
+</pre>
+</div>
+
+<div class="test">
+Descendants
+<pre class="doctest">
+$ JSONSelect.match(".foo .bar", {foo: { baz: 1, bar: 2 }, bar: 3});
+[2]
+$ JSONSelect.match(".foo > .bar", {foo: { baz: 1, bar: 2 }, bar: 3});
+[2]
+$ JSONSelect.match(".foo > .bar", {foo: { baz: { bar: 4 }, bar: 2 }, bar: 3});
+[2]
+$ JSONSelect.match(".foo .bar", {foo: { baz: { bar: 4 }, bar: 2 }, bar: 3});
+[4, 2]
+</pre>
+</div>
+
+<div class="test">
+Grouping
+<pre class="doctest">
+$ JSONSelect.match("number,boolean", [ "a", 1, true, null, false, "b", 3.1415, "c" ] );
+[1, true, false, 3.1415]
+$ JSONSelect.match("number,boolean,null", [ "a", 1, true, null, false, "b", 3.1415, "c" ] );
+[1, true, null, false, 3.1415]
+</pre>
+</div>
+
+  </body>
+</html>
+
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/parse_test.html b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/parse_test.html
new file mode 100644
index 0000000..9744457
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/parse_test.html
@@ -0,0 +1,98 @@
+<html>
+  <head>
+    <title>JSONSelect JS parser tests</title>
+    <link rel="stylesheet" type="text/css" href="js/doctest.css" />
+    <script src="js/doctest.js"></script>
+    <script src="../jsonselect.js"></script>
+    <meta http-equiv="Content-type" content="text/html; charset=utf-8">
+  </head>
+  <body>
+
+<div>
+  <button onclick="doctest()" type="button">run tests</button>
+  <pre id="doctestOutput"></pre>
+</div>
+
+    <h2> Tests of the JSONSelect parser </h2>
+
+<div class="test">
+Selectors
+<pre class="doctest">
+$ JSONSelect._parse(".foo");
+[{id: "foo"}]
+$ JSONSelect._parse('." foo "');
+[{id: " foo "}]
+$ JSONSelect._parse("string.foo:last-child");
+[{a: 0, b: 1, id: "foo", pf: ":nth-last-child", type: "string"}]
+$ JSONSelect._parse("string.foo.bar");
+Error: multiple ids not allowed
+$ JSONSelect._parse("string.foo:first-child.bar");
+Error: multiple ids not allowed
+$ JSONSelect._parse("string:last-child.foo:first-child.bar");
+Error: multiple pseudo classes (:xxx) not allowed
+$ JSONSelect._parse("string.");
+Error: string required after '.'
+$ JSONSelect._parse("string:bogus");
+Error: unrecognized pseudo class
+$ JSONSelect._parse("string.xxx\\@yyy");
+[{id: "xxx@yyy", type: "string"}]
+$ JSONSelect._parse(" ");
+Error: selector expected
+$ JSONSelect._parse("");
+Error: selector expected
+</pre>
+</div>
+
+<div class="test">
+Combinators
+<pre class="doctest">
+$ JSONSelect._parse(".foo .bar");
+[{id: "foo"}, {id: "bar"}]
+$ JSONSelect._parse("string.foo , number.foo");
+[",", [{id: "foo", type: "string"}], [{id: "foo", type: "number"}]]
+$ JSONSelect._parse("string > .foo number.bar");
+[{type: "string"}, ">", {id: "foo"}, {id: "bar", type: "number"}]
+$ JSONSelect._parse("string > .foo number.bar, object");
+[",", [{type: "string"}, ">", {id: "foo"}, {id: "bar", type: "number"}], [{type: "object"}]]
+$ JSONSelect._parse("string > .foo number.bar, object, string, .\"baz bing\", :root");
+[
+  ",",
+  [{type: "string"}, ">", {id: "foo"}, {id: "bar", type: "number"}],
+  [{type: "object"}],
+  [{type: "string"}],
+  [{id: "baz bing"}],
+  [{pc: ":root"}]
+]
+</pre>
+</div>
+
+<div class="test">
+Expressions
+<pre class="doctest">
+$ JSONSelect._parse(":nth-child(1)");
+[{a: 0, b: 1, pf: ":nth-child"}]
+$ JSONSelect._parse(":nth-child(2n+1)");
+[{a: 2, b: 1, pf: ":nth-child"}]
+$ JSONSelect._parse(":nth-child ( 2n + 1 )");
+[{a: 2, b: 1, pf: ":nth-child"}]
+$ JSONSelect._parse(":nth-child(odd)");
+[{a: 2, b: 1, pf: ":nth-child"}]
+$ JSONSelect._parse(":nth-child(even)");
+[{a: 2, b: 0, pf: ":nth-child"}]
+$ JSONSelect._parse(":nth-child(-n+6)");
+[{a: -1, b: 6, pf: ":nth-child"}]
+$ JSONSelect._parse(":nth-child(2n)");
+[{a: 2, b: 0, pf: ":nth-child"}]
+$ JSONSelect._parse(":nth-last-child(-3n - 3)");
+[{a: -3, b: -3, pf: ":nth-last-child"}]
+$ JSONSelect._parse(":first-child");
+[{a: 0, b: 1, pf: ":nth-child"}]
+$ JSONSelect._parse(":last-child");
+[{a: 0, b: 1, pf: ":nth-last-child"}]
+</pre>
+</div>
+
+  </body>
+</html>
+
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/run.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/run.js
new file mode 100644
index 0000000..c873c3a
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/run.js
@@ -0,0 +1,85 @@
+/*
+ * a node.js test runner that executes all conformance
+ * tests and outputs results to console.
+ * Process returns zero on success, non-zero on failure.
+ */
+
+const   fs = require('fs'),
+      path = require('path'),
+jsonselect = require('../jsonselect.js'),
+       sys = require('sys');
+
+var pathToTests = path.join(__dirname, "tests");
+
+// a map: document nametest name -> list of se
+var numTests = 0;
+var numPassed = 0;
+var tests = {};
+
+function runOneSync(name, selname, p) {
+    var testDocPath = path.join(p, name + ".json");
+    var selDocPath = path.join(p, name + '_' +
+                               selname + ".selector");
+    var outputDocPath = selDocPath.replace(/selector$/, "output");
+
+    // take `obj`, apply `sel, get `got`, is it what we `want`? 
+    var obj = JSON.parse(fs.readFileSync(testDocPath));
+    var want = String(fs.readFileSync(outputDocPath)).trim();
+    var got = "";
+    var sel = String(fs.readFileSync(selDocPath)).trim();
+
+    try {
+        jsonselect.forEach(sel, obj, function(m) {
+            got += JSON.stringify(m, undefined, 4) + "\n";
+        });
+    } catch(e) {
+        got = e.toString();
+        if (want.trim() != got.trim()) throw e;
+    }
+    if (want.trim() != got.trim()) throw "mismatch";
+}
+
+
+function runTests() {
+    console.log("Running Tests:"); 
+    for (var l in tests) {
+        for (var d in tests[l]) {
+            console.log("  level " + l + " tests against \"" + d + ".json\":");
+            for (var i = 0; i < tests[l][d].length; i++) {
+                sys.print("    " + tests[l][d][i][0] + ": ");
+                try {
+                    runOneSync(d, tests[l][d][i][0], tests[l][d][i][1]);
+                    numPassed++;
+                    console.log("pass");
+                } catch (e) {
+                    console.log("fail (" + e.toString() + ")");
+                }
+            }
+        }
+    }
+    console.log(numPassed + "/" + numTests + " passed");
+    process.exit(numPassed == numTests ? 0 : 1);
+}
+
+// discover all tests
+var pathToTests = path.join(__dirname, "tests");
+
+fs.readdirSync(pathToTests).forEach(function(subdir) {
+    var p = path.join(pathToTests, subdir);
+    if (!fs.statSync(p).isDirectory()) return;
+    var l = /^level_([\d+])$/.exec(subdir);
+    if (!l) return;
+    l = l[1];
+    var files = fs.readdirSync(p);
+    for (var i = 0; i < files.length; i++) {
+        var f = files[i];
+        var m = /^([A-Za-z]+)_(.+)\.selector$/.exec(f);
+        if (m) {
+            if (!tests.hasOwnProperty(l)) tests[l] = [];
+            if (!tests[l].hasOwnProperty(m[1])) tests[l][m[1]] = [];
+            numTests++;
+            tests[l][m[1]].push([m[2], p]);
+        }
+    }
+});
+runTests();
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/.npmignore b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/.npmignore
new file mode 100644
index 0000000..b25c15b
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/.npmignore
@@ -0,0 +1 @@
+*~
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/README.md b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/README.md
new file mode 100644
index 0000000..a9e59ca
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/README.md
@@ -0,0 +1,22 @@
+## JSONSelect Conformance Tests.
+
+This repository contains conformance tests for the
+[JSONSelect](http://jsonselect.org) selector language.  The tests
+are divided among subdirectories which correspond to "level" supported
+by a particular implementation.  Levels are described more fully in the 
+jsonselect [documentation](http://jsonselect.org/#docs).
+
+## Test organization
+
+Test documents have a suffix of `.json`, like `basic.json`.
+
+Selectors to be applied to test documents have the document name,
+followed by an underbar, followed by a description of the test, with
+a `.selector` file name suffix, like `basic_grouping.selector`.
+
+Expected output files have the same name as the `.selector` file, 
+but have a `.output` suffix, like `basic_grouping.output`.
+
+Expected output files contain a stream of JSON objects that are what
+is expected to be produced when a given selector is applied to a given
+document.
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic.json b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic.json
new file mode 100644
index 0000000..83c3769
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic.json
@@ -0,0 +1,31 @@
+{
+    "name": {
+        "first": "Lloyd",
+        "last": "Hilaiel"
+    },
+    "favoriteColor": "yellow",
+    "languagesSpoken": [
+        {
+            "language": "Bulgarian",
+            "level": "advanced"
+        },
+        {
+            "language": "English",
+            "level": "native"
+        },
+        {
+            "language": "Spanish",
+            "level": "beginner"
+        }
+    ],
+    "seatingPreference": [
+        "window",
+        "aisle"
+    ],
+    "drinkPreference": [
+        "beer",
+        "whiskey",
+        "wine"
+    ],
+    "weight": 172
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_first-child.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_first-child.output
new file mode 100644
index 0000000..ba6c81a
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_first-child.output
@@ -0,0 +1,2 @@
+"window"
+"beer"
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_first-child.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_first-child.selector
new file mode 100644
index 0000000..8288dac
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_first-child.selector
@@ -0,0 +1,2 @@
+string:first-child
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_grouping.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_grouping.output
new file mode 100644
index 0000000..ebbc034
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_grouping.output
@@ -0,0 +1,4 @@
+"advanced"
+"native"
+"beginner"
+172
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_grouping.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_grouping.selector
new file mode 100644
index 0000000..b8d0d79
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_grouping.selector
@@ -0,0 +1 @@
+string.level,number
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id.output
new file mode 100644
index 0000000..1511f18
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id.output
@@ -0,0 +1 @@
+"yellow"
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id.selector
new file mode 100644
index 0000000..7a3e32b
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id.selector
@@ -0,0 +1 @@
+.favoriteColor
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_multiple.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_multiple.output
new file mode 100644
index 0000000..9021aaa
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_multiple.output
@@ -0,0 +1,3 @@
+"Bulgarian"
+"English"
+"Spanish"
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_multiple.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_multiple.selector
new file mode 100644
index 0000000..24170a9
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_multiple.selector
@@ -0,0 +1 @@
+.language
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_quotes.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_quotes.output
new file mode 100644
index 0000000..1912273
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_quotes.output
@@ -0,0 +1,2 @@
+172
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_quotes.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_quotes.selector
new file mode 100644
index 0000000..1adaed0
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_quotes.selector
@@ -0,0 +1 @@
+."weight"
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_with_type.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_with_type.output
new file mode 100644
index 0000000..1511f18
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_with_type.output
@@ -0,0 +1 @@
+"yellow"
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_with_type.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_with_type.selector
new file mode 100644
index 0000000..48c2619
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_with_type.selector
@@ -0,0 +1 @@
+string.favoriteColor
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_last-child.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_last-child.output
new file mode 100644
index 0000000..9a334d0
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_last-child.output
@@ -0,0 +1,2 @@
+"aisle"
+"wine"
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_last-child.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_last-child.selector
new file mode 100644
index 0000000..8591de0
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_last-child.selector
@@ -0,0 +1,2 @@
+string:last-child
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-child-2.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-child-2.output
new file mode 100644
index 0000000..b55a882
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-child-2.output
@@ -0,0 +1,4 @@
+"window"
+"aisle"
+"beer"
+"whiskey"
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-child-2.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-child-2.selector
new file mode 100644
index 0000000..6418a97
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-child-2.selector
@@ -0,0 +1 @@
+string:nth-child(-n+2)
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-child.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-child.output
new file mode 100644
index 0000000..196d836
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-child.output
@@ -0,0 +1,3 @@
+"window"
+"beer"
+"wine"
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-child.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-child.selector
new file mode 100644
index 0000000..9bd4c5f
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-child.selector
@@ -0,0 +1 @@
+string:nth-child(odd)
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-last-child.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-last-child.output
new file mode 100644
index 0000000..9a334d0
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-last-child.output
@@ -0,0 +1,2 @@
+"aisle"
+"wine"
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-last-child.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-last-child.selector
new file mode 100644
index 0000000..bc100d5
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-last-child.selector
@@ -0,0 +1 @@
+string:nth-last-child(1)
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_root_pseudo.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_root_pseudo.output
new file mode 100644
index 0000000..83c3769
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_root_pseudo.output
@@ -0,0 +1,31 @@
+{
+    "name": {
+        "first": "Lloyd",
+        "last": "Hilaiel"
+    },
+    "favoriteColor": "yellow",
+    "languagesSpoken": [
+        {
+            "language": "Bulgarian",
+            "level": "advanced"
+        },
+        {
+            "language": "English",
+            "level": "native"
+        },
+        {
+            "language": "Spanish",
+            "level": "beginner"
+        }
+    ],
+    "seatingPreference": [
+        "window",
+        "aisle"
+    ],
+    "drinkPreference": [
+        "beer",
+        "whiskey",
+        "wine"
+    ],
+    "weight": 172
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_root_pseudo.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_root_pseudo.selector
new file mode 100644
index 0000000..4035e80
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_root_pseudo.selector
@@ -0,0 +1 @@
+:root
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type.output
new file mode 100644
index 0000000..332db77
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type.output
@@ -0,0 +1,14 @@
+"Lloyd"
+"Hilaiel"
+"yellow"
+"Bulgarian"
+"advanced"
+"English"
+"native"
+"Spanish"
+"beginner"
+"window"
+"aisle"
+"beer"
+"whiskey"
+"wine"
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type.selector
new file mode 100644
index 0000000..ec186f1
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type.selector
@@ -0,0 +1 @@
+string
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type2.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type2.output
new file mode 100644
index 0000000..730a054
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type2.output
@@ -0,0 +1 @@
+172
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type2.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type2.selector
new file mode 100644
index 0000000..0dfad6f
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type2.selector
@@ -0,0 +1 @@
+number
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type3.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type3.output
new file mode 100644
index 0000000..8394ec8
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type3.output
@@ -0,0 +1,47 @@
+{
+    "first": "Lloyd",
+    "last": "Hilaiel"
+}
+{
+    "language": "Bulgarian",
+    "level": "advanced"
+}
+{
+    "language": "English",
+    "level": "native"
+}
+{
+    "language": "Spanish",
+    "level": "beginner"
+}
+{
+    "name": {
+        "first": "Lloyd",
+        "last": "Hilaiel"
+    },
+    "favoriteColor": "yellow",
+    "languagesSpoken": [
+        {
+            "language": "Bulgarian",
+            "level": "advanced"
+        },
+        {
+            "language": "English",
+            "level": "native"
+        },
+        {
+            "language": "Spanish",
+            "level": "beginner"
+        }
+    ],
+    "seatingPreference": [
+        "window",
+        "aisle"
+    ],
+    "drinkPreference": [
+        "beer",
+        "whiskey",
+        "wine"
+    ],
+    "weight": 172
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type3.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type3.selector
new file mode 100644
index 0000000..e2f6b77
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type3.selector
@@ -0,0 +1 @@
+object
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_universal.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_universal.output
new file mode 100644
index 0000000..cfbf20a
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_universal.output
@@ -0,0 +1,85 @@
+"Lloyd"
+"Hilaiel"
+{
+    "first": "Lloyd",
+    "last": "Hilaiel"
+}
+"yellow"
+"Bulgarian"
+"advanced"
+{
+    "language": "Bulgarian",
+    "level": "advanced"
+}
+"English"
+"native"
+{
+    "language": "English",
+    "level": "native"
+}
+"Spanish"
+"beginner"
+{
+    "language": "Spanish",
+    "level": "beginner"
+}
+[
+    {
+        "language": "Bulgarian",
+        "level": "advanced"
+    },
+    {
+        "language": "English",
+        "level": "native"
+    },
+    {
+        "language": "Spanish",
+        "level": "beginner"
+    }
+]
+"window"
+"aisle"
+[
+    "window",
+    "aisle"
+]
+"beer"
+"whiskey"
+"wine"
+[
+    "beer",
+    "whiskey",
+    "wine"
+]
+172
+{
+    "name": {
+        "first": "Lloyd",
+        "last": "Hilaiel"
+    },
+    "favoriteColor": "yellow",
+    "languagesSpoken": [
+        {
+            "language": "Bulgarian",
+            "level": "advanced"
+        },
+        {
+            "language": "English",
+            "level": "native"
+        },
+        {
+            "language": "Spanish",
+            "level": "beginner"
+        }
+    ],
+    "seatingPreference": [
+        "window",
+        "aisle"
+    ],
+    "drinkPreference": [
+        "beer",
+        "whiskey",
+        "wine"
+    ],
+    "weight": 172
+}
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_universal.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_universal.selector
new file mode 100644
index 0000000..72e8ffc
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_universal.selector
@@ -0,0 +1 @@
+*
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision.json b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision.json
new file mode 100644
index 0000000..d2bc912
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision.json
@@ -0,0 +1,6 @@
+{
+    "object": {
+      "string": "some string",
+      "stringTwo": "some other string"
+    }
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_nested.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_nested.output
new file mode 100644
index 0000000..e39067c
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_nested.output
@@ -0,0 +1 @@
+"some string"
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_nested.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_nested.selector
new file mode 100644
index 0000000..5eaa374
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_nested.selector
@@ -0,0 +1 @@
+.object .string
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_quoted-string.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_quoted-string.output
new file mode 100644
index 0000000..e39067c
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_quoted-string.output
@@ -0,0 +1 @@
+"some string"
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_quoted-string.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_quoted-string.selector
new file mode 100644
index 0000000..4faa4aa
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_quoted-string.selector
@@ -0,0 +1 @@
+."string"
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_string.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_string.output
new file mode 100644
index 0000000..e39067c
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_string.output
@@ -0,0 +1 @@
+"some string"
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_string.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_string.selector
new file mode 100644
index 0000000..8b2be0c
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_string.selector
@@ -0,0 +1 @@
+.string
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling.json b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling.json
new file mode 100644
index 0000000..6e5aecb
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling.json
@@ -0,0 +1,18 @@
+{
+  "a": 1,
+  "b": 2,
+  "c": {
+    "a": 3,
+    "b": 4,
+    "c": {
+      "a": 5,
+      "b": 6
+    }
+  },
+  "d": {
+    "a": 7
+  },
+  "e": {
+    "b": 8
+  }
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_childof.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_childof.output
new file mode 100644
index 0000000..0cfbf08
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_childof.output
@@ -0,0 +1 @@
+2
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_childof.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_childof.selector
new file mode 100644
index 0000000..f13fbdd
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_childof.selector
@@ -0,0 +1,2 @@
+:root > .a ~ .b
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_descendantof.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_descendantof.output
new file mode 100644
index 0000000..e2ba1ef
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_descendantof.output
@@ -0,0 +1,3 @@
+2
+4
+6
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_descendantof.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_descendantof.selector
new file mode 100644
index 0000000..7c637b9
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_descendantof.selector
@@ -0,0 +1 @@
+:root .a ~ .b
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_unrooted.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_unrooted.output
new file mode 100644
index 0000000..e2ba1ef
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_unrooted.output
@@ -0,0 +1,3 @@
+2
+4
+6
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_unrooted.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_unrooted.selector
new file mode 100644
index 0000000..9a4ae06
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_unrooted.selector
@@ -0,0 +1 @@
+.a ~ .b
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic.json b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic.json
new file mode 100644
index 0000000..83c3769
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic.json
@@ -0,0 +1,31 @@
+{
+    "name": {
+        "first": "Lloyd",
+        "last": "Hilaiel"
+    },
+    "favoriteColor": "yellow",
+    "languagesSpoken": [
+        {
+            "language": "Bulgarian",
+            "level": "advanced"
+        },
+        {
+            "language": "English",
+            "level": "native"
+        },
+        {
+            "language": "Spanish",
+            "level": "beginner"
+        }
+    ],
+    "seatingPreference": [
+        "window",
+        "aisle"
+    ],
+    "drinkPreference": [
+        "beer",
+        "whiskey",
+        "wine"
+    ],
+    "weight": 172
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-multiple.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-multiple.output
new file mode 100644
index 0000000..1cb38cb
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-multiple.output
@@ -0,0 +1,4 @@
+{
+    "first": "Lloyd",
+    "last": "Hilaiel"
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-multiple.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-multiple.selector
new file mode 100644
index 0000000..956f1bc
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-multiple.selector
@@ -0,0 +1 @@
+:root > object:has(string.first):has(string.last)
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-root-in-expr.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-root-in-expr.output
new file mode 100644
index 0000000..1cb38cb
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-root-in-expr.output
@@ -0,0 +1,4 @@
+{
+    "first": "Lloyd",
+    "last": "Hilaiel"
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-root-in-expr.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-root-in-expr.selector
new file mode 100644
index 0000000..329e224
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-root-in-expr.selector
@@ -0,0 +1 @@
+:has(:root > .first)
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-sans-first-paren.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-sans-first-paren.output
new file mode 100644
index 0000000..32f476b
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-sans-first-paren.output
@@ -0,0 +1 @@
+Error: opening paren expected '(' in 'object:has .language)'
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-sans-first-paren.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-sans-first-paren.selector
new file mode 100644
index 0000000..6165481
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-sans-first-paren.selector
@@ -0,0 +1 @@
+object:has .language)
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-sans-paren.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-sans-paren.output
new file mode 100644
index 0000000..3baa674
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-sans-paren.output
@@ -0,0 +1 @@
+Error: missing closing paren in 'object:has(.language'
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-sans-paren.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-sans-paren.selector
new file mode 100644
index 0000000..8111708
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-sans-paren.selector
@@ -0,0 +1,2 @@
+object:has(.language
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-whitespace.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-whitespace.output
new file mode 100644
index 0000000..7a87f46
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-whitespace.output
@@ -0,0 +1,12 @@
+{
+    "language": "Bulgarian",
+    "level": "advanced"
+}
+{
+    "language": "English",
+    "level": "native"
+}
+{
+    "language": "Spanish",
+    "level": "beginner"
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-whitespace.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-whitespace.selector
new file mode 100644
index 0000000..69d2801
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-whitespace.selector
@@ -0,0 +1 @@
+.languagesSpoken object:has       (       .language       ) 
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-with-comma.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-with-comma.output
new file mode 100644
index 0000000..2df8708
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-with-comma.output
@@ -0,0 +1,17 @@
+{
+    "first": "Lloyd",
+    "last": "Hilaiel"
+}
+{
+    "language": "Bulgarian",
+    "level": "advanced"
+}
+{
+    "language": "English",
+    "level": "native"
+}
+{
+    "language": "Spanish",
+    "level": "beginner"
+}
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-with-comma.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-with-comma.selector
new file mode 100644
index 0000000..67e78d3
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-with-comma.selector
@@ -0,0 +1 @@
+:has(:root > .language, :root > .last)
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has.output
new file mode 100644
index 0000000..7a87f46
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has.output
@@ -0,0 +1,12 @@
+{
+    "language": "Bulgarian",
+    "level": "advanced"
+}
+{
+    "language": "English",
+    "level": "native"
+}
+{
+    "language": "Spanish",
+    "level": "beginner"
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has.selector
new file mode 100644
index 0000000..aaa0c19
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has.selector
@@ -0,0 +1 @@
+.languagesSpoken object:has(.language)
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_multiple-has-with-strings.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_multiple-has-with-strings.output
new file mode 100644
index 0000000..1ce5c78
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_multiple-has-with-strings.output
@@ -0,0 +1,5 @@
+{
+    "first": "Lloyd",
+    "last": "Hilaiel"
+}
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_multiple-has-with-strings.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_multiple-has-with-strings.selector
new file mode 100644
index 0000000..7dd0647
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_multiple-has-with-strings.selector
@@ -0,0 +1 @@
+:has(:val("Lloyd")) object:has(:val("Hilaiel"))
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr.json b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr.json
new file mode 100644
index 0000000..e1c9de8
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr.json
@@ -0,0 +1,9 @@
+{
+    "int": 42,
+    "float": 3.1415,
+    "string": "foo.exe",
+    "string2": "bar.dmg",
+    "null": null,
+    "true": true,
+    "false": false
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_div.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_div.output
new file mode 100644
index 0000000..d81cc07
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_div.output
@@ -0,0 +1 @@
+42
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_div.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_div.selector
new file mode 100644
index 0000000..55a288c
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_div.selector
@@ -0,0 +1 @@
+:expr(7 = x / 6)
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_ends-with.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_ends-with.output
new file mode 100644
index 0000000..5517937
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_ends-with.output
@@ -0,0 +1 @@
+"bar.dmg"
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_ends-with.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_ends-with.selector
new file mode 100644
index 0000000..456c961
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_ends-with.selector
@@ -0,0 +1 @@
+:expr(x $= ".dmg")
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_false-eq.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_false-eq.output
new file mode 100644
index 0000000..a232278
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_false-eq.output
@@ -0,0 +1,3 @@
+false
+
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_false-eq.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_false-eq.selector
new file mode 100644
index 0000000..b906ff9
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_false-eq.selector
@@ -0,0 +1,4 @@
+:expr(false=x)
+
+
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_greater-than.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_greater-than.output
new file mode 100644
index 0000000..4f86ad6
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_greater-than.output
@@ -0,0 +1,2 @@
+42
+3.1415
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_greater-than.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_greater-than.selector
new file mode 100644
index 0000000..878cf08
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_greater-than.selector
@@ -0,0 +1 @@
+:expr(x>3.1)
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_less-than.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_less-than.output
new file mode 100644
index 0000000..4f34d97
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_less-than.output
@@ -0,0 +1,2 @@
+3.1415
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_less-than.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_less-than.selector
new file mode 100644
index 0000000..bd4f1bc
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_less-than.selector
@@ -0,0 +1 @@
+:expr(x<4)
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_mod.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_mod.output
new file mode 100644
index 0000000..d81cc07
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_mod.output
@@ -0,0 +1 @@
+42
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_mod.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_mod.selector
new file mode 100644
index 0000000..fec575b
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_mod.selector
@@ -0,0 +1 @@
+:expr((12 % 10) + 40 = x)
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_mult.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_mult.output
new file mode 100644
index 0000000..d81cc07
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_mult.output
@@ -0,0 +1 @@
+42
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_mult.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_mult.selector
new file mode 100644
index 0000000..869c723
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_mult.selector
@@ -0,0 +1 @@
+:expr(7 * 6 = x)
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_null-eq.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_null-eq.output
new file mode 100644
index 0000000..9fba3cf
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_null-eq.output
@@ -0,0 +1,5 @@
+null
+
+
+
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_null-eq.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_null-eq.selector
new file mode 100644
index 0000000..d015ac3
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_null-eq.selector
@@ -0,0 +1,4 @@
+:expr(null=x)
+
+
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_number-eq.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_number-eq.output
new file mode 100644
index 0000000..d81cc07
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_number-eq.output
@@ -0,0 +1 @@
+42
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_number-eq.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_number-eq.selector
new file mode 100644
index 0000000..4440ecb
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_number-eq.selector
@@ -0,0 +1 @@
+:expr(42 = x)
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_precedence-1.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_precedence-1.output
new file mode 100644
index 0000000..f32a580
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_precedence-1.output
@@ -0,0 +1 @@
+true
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_precedence-1.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_precedence-1.selector
new file mode 100644
index 0000000..eb214ef
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_precedence-1.selector
@@ -0,0 +1 @@
+.true:expr( 4 + 5 * 6 / 5 + 3 * 2 = 16 )
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_precedence-2.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_precedence-2.output
new file mode 100644
index 0000000..27ba77d
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_precedence-2.output
@@ -0,0 +1 @@
+true
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_precedence-2.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_precedence-2.selector
new file mode 100644
index 0000000..79c7f2c
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_precedence-2.selector
@@ -0,0 +1 @@
+.true:expr( (4 + 5) * 6 / (2 + 1) * 2 = 36 )
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_simple-false.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_simple-false.output
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_simple-false.output
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_simple-false.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_simple-false.selector
new file mode 100644
index 0000000..d39a80a
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_simple-false.selector
@@ -0,0 +1 @@
+.true:expr(1=2)
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_simple.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_simple.output
new file mode 100644
index 0000000..27ba77d
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_simple.output
@@ -0,0 +1 @@
+true
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_simple.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_simple.selector
new file mode 100644
index 0000000..d912927
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_simple.selector
@@ -0,0 +1 @@
+.true:expr(1=1)
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_starts-with.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_starts-with.output
new file mode 100644
index 0000000..f07ea50
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_starts-with.output
@@ -0,0 +1 @@
+"foo.exe"
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_starts-with.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_starts-with.selector
new file mode 100644
index 0000000..f76cd75
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_starts-with.selector
@@ -0,0 +1 @@
+:expr(x ^= "foo")
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_string-eq.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_string-eq.output
new file mode 100644
index 0000000..f07ea50
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_string-eq.output
@@ -0,0 +1 @@
+"foo.exe"
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_string-eq.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_string-eq.selector
new file mode 100644
index 0000000..0f19d62
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_string-eq.selector
@@ -0,0 +1 @@
+:expr(x = "foo.exe")
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_true-eq.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_true-eq.output
new file mode 100644
index 0000000..28b0c28
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_true-eq.output
@@ -0,0 +1,3 @@
+true
+
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_true-eq.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_true-eq.selector
new file mode 100644
index 0000000..02e1841
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_true-eq.selector
@@ -0,0 +1,2 @@
+:expr(true = x)
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/polykids.json b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/polykids.json
new file mode 100644
index 0000000..2429d0f
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/polykids.json
@@ -0,0 +1,15 @@
+[
+    {
+        "language": "Bulgarian",
+        "level": "advanced"
+    },
+    {
+        "language": "English",
+        "level": "native",
+        "preferred": true
+    },
+    {
+        "language": "Spanish",
+        "level": "beginner"
+    }
+]
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/polykids_has-with_descendant.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/polykids_has-with_descendant.output
new file mode 100644
index 0000000..9699f99
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/polykids_has-with_descendant.output
@@ -0,0 +1 @@
+"English"
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/polykids_has-with_descendant.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/polykids_has-with_descendant.selector
new file mode 100644
index 0000000..84d51be
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/polykids_has-with_descendant.selector
@@ -0,0 +1 @@
+:has(.preferred) > .language
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/polykids_val.output b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/polykids_val.output
new file mode 100644
index 0000000..fd1d03f
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/polykids_val.output
@@ -0,0 +1,2 @@
+"advanced"
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/polykids_val.selector b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/polykids_val.selector
new file mode 100644
index 0000000..076f443
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/polykids_val.selector
@@ -0,0 +1 @@
+:has(:root > .language:val("Bulgarian")) > .level
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/.npmignore b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/.npmignore
new file mode 100644
index 0000000..3c3629e
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/.npmignore
@@ -0,0 +1 @@
+node_modules
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/LICENSE b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/LICENSE
new file mode 100644
index 0000000..7b75500
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/LICENSE
@@ -0,0 +1,24 @@
+Copyright 2010 James Halliday (mail@substack.net)
+
+This project is free software released under the MIT/X11 license:
+http://www.opensource.org/licenses/mit-license.php 
+
+Copyright 2010 James Halliday (mail@substack.net)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/README.markdown b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/README.markdown
new file mode 100644
index 0000000..0734006
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/README.markdown
@@ -0,0 +1,273 @@
+traverse
+========
+
+Traverse and transform objects by visiting every node on a recursive walk.
+
+examples
+========
+
+transform negative numbers in-place
+-----------------------------------
+
+negative.js
+
+````javascript
+var traverse = require('traverse');
+var obj = [ 5, 6, -3, [ 7, 8, -2, 1 ], { f : 10, g : -13 } ];
+
+traverse(obj).forEach(function (x) {
+    if (x < 0) this.update(x + 128);
+});
+
+console.dir(obj);
+````
+
+Output:
+
+    [ 5, 6, 125, [ 7, 8, 126, 1 ], { f: 10, g: 115 } ]
+
+collect leaf nodes
+------------------
+
+leaves.js
+
+````javascript
+var traverse = require('traverse');
+
+var obj = {
+    a : [1,2,3],
+    b : 4,
+    c : [5,6],
+    d : { e : [7,8], f : 9 },
+};
+
+var leaves = traverse(obj).reduce(function (acc, x) {
+    if (this.isLeaf) acc.push(x);
+    return acc;
+}, []);
+
+console.dir(leaves);
+````
+
+Output:
+
+    [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
+
+scrub circular references
+-------------------------
+
+scrub.js:
+
+````javascript
+var traverse = require('traverse');
+
+var obj = { a : 1, b : 2, c : [ 3, 4 ] };
+obj.c.push(obj);
+
+var scrubbed = traverse(obj).map(function (x) {
+    if (this.circular) this.remove()
+});
+console.dir(scrubbed);
+````
+
+output:
+
+    { a: 1, b: 2, c: [ 3, 4 ] }
+
+context
+=======
+
+Each method that takes a callback has a context (its `this` object) with these
+attributes:
+
+this.node
+---------
+
+The present node on the recursive walk
+
+this.path
+---------
+
+An array of string keys from the root to the present node
+
+this.parent
+-----------
+
+The context of the node's parent.
+This is `undefined` for the root node.
+
+this.key
+--------
+
+The name of the key of the present node in its parent.
+This is `undefined` for the root node.
+
+this.isRoot, this.notRoot
+-------------------------
+
+Whether the present node is the root node
+
+this.isLeaf, this.notLeaf
+-------------------------
+
+Whether or not the present node is a leaf node (has no children)
+
+this.level
+----------
+
+Depth of the node within the traversal
+
+this.circular
+-------------
+
+If the node equals one of its parents, the `circular` attribute is set to the
+context of that parent and the traversal progresses no deeper.
+
+this.update(value, stopHere=false)
+----------------------------------
+
+Set a new value for the present node.
+
+All the elements in `value` will be recursively traversed unless `stopHere` is
+true.
+
+this.remove()
+-------------
+
+Remove the current element from the output. If the node is in an Array it will
+be spliced off. Otherwise it will be deleted from its parent.
+
+this.delete()
+-------------
+
+Delete the current element from its parent in the output. Calls `delete` even on
+Arrays.
+
+this.before(fn)
+---------------
+
+Call this function before any of the children are traversed.
+
+You can assign into `this.keys` here to traverse in a custom order.
+
+this.after(fn)
+--------------
+
+Call this function after any of the children are traversed.
+
+this.pre(fn)
+------------
+
+Call this function before each of the children are traversed.
+
+this.post(fn)
+-------------
+
+Call this function after each of the children are traversed.
+
+methods
+=======
+
+.map(fn)
+--------
+
+Execute `fn` for each node in the object and return a new object with the
+results of the walk. To update nodes in the result use `this.update(value)`.
+
+.forEach(fn)
+------------
+
+Execute `fn` for each node in the object but unlike `.map()`, when
+`this.update()` is called it updates the object in-place.
+
+.reduce(fn, acc)
+----------------
+
+For each node in the object, perform a
+[left-fold](http://en.wikipedia.org/wiki/Fold_(higher-order_function))
+with the return value of `fn(acc, node)`.
+
+If `acc` isn't specified, `acc` is set to the root object for the first step
+and the root element is skipped.
+
+.deepEqual(obj)
+---------------
+
+Returns a boolean, whether the instance value is equal to the supplied object
+along a deep traversal using some opinionated choices.
+
+Some notes:
+
+* RegExps are equal if their .toString()s match, but not functions since
+functions can close over different variables.
+
+* Date instances are compared using `.getTime()` just like `assert.deepEqual()`.
+
+* Circular references must refer to the same paths within the data structure for
+both objects. For instance, in this snippet:
+
+````javascript
+var a = [1];
+a.push(a); // a = [ 1, *a ]
+
+var b = [1];
+b.push(a); // b = [ 1, [ 1, *a ] ]
+````
+
+`a` is not the same as `b` since even though the expansion is the same, the
+circular references in each refer to different paths into the data structure.
+
+However, in:
+
+````javascript
+var c = [1];
+c.push(c); // c = [ 1, *c ];
+````
+
+`c` is equal to `a` in a `deepEqual()` because they have the same terminal node
+structure.
+
+* Arguments objects are not arrays and neither are they the same as regular
+objects.
+
+* Instances created with `new` of String, Boolean, and Number types are never
+equal to the native versions.
+
+.paths()
+--------
+
+Return an `Array` of every possible non-cyclic path in the object.
+Paths are `Array`s of string keys.
+
+.nodes()
+--------
+
+Return an `Array` of every node in the object.
+
+.clone()
+--------
+
+Create a deep clone of the object.
+
+installation
+============
+
+Using npm:
+    npm install traverse
+
+Or check out the repository and link your development copy:
+    git clone http://github.com/substack/js-traverse.git
+    cd js-traverse
+    npm link .
+
+You can test traverse with "expresso":http://github.com/visionmedia/expresso
+(`npm install expresso`):
+    js-traverse $ expresso
+    
+    100% wahoo, your stuff is not broken!
+
+hash transforms
+===============
+
+This library formerly had a hash transformation component. It has been
+[moved to the hashish package](https://github.com/substack/node-hashish).
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/examples/json.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/examples/json.js
new file mode 100755
index 0000000..50d612e
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/examples/json.js
@@ -0,0 +1,16 @@
+var traverse = require('traverse');
+
+var id = 54;
+var callbacks = {};
+var obj = { moo : function () {}, foo : [2,3,4, function () {}] };
+
+var scrubbed = traverse(obj).map(function (x) {
+    if (typeof x === 'function') {
+        callbacks[id] = { id : id, f : x, path : this.path };
+        this.update('[Function]');
+        id++;
+    }
+});
+
+console.dir(scrubbed);
+console.dir(callbacks);
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/examples/leaves.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/examples/leaves.js
new file mode 100755
index 0000000..c1b310b
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/examples/leaves.js
@@ -0,0 +1,15 @@
+var traverse = require('traverse');
+
+var obj = {
+    a : [1,2,3],
+    b : 4,
+    c : [5,6],
+    d : { e : [7,8], f : 9 },
+};
+
+var leaves = traverse(obj).reduce(function (acc, x) {
+    if (this.isLeaf) acc.push(x);
+    return acc;
+}, []);
+
+console.dir(leaves);
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/examples/negative.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/examples/negative.js
new file mode 100755
index 0000000..78608a0
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/examples/negative.js
@@ -0,0 +1,8 @@
+var traverse = require('traverse');
+var obj = [ 5, 6, -3, [ 7, 8, -2, 1 ], { f : 10, g : -13 } ];
+
+traverse(obj).forEach(function (x) {
+    if (x < 0) this.update(x + 128);
+});
+
+console.dir(obj);
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/examples/scrub.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/examples/scrub.js
new file mode 100755
index 0000000..5d15b91
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/examples/scrub.js
@@ -0,0 +1,10 @@
+// scrub out circular references
+var traverse = require('traverse');
+
+var obj = { a : 1, b : 2, c : [ 3, 4 ] };
+obj.c.push(obj);
+
+var scrubbed = traverse(obj).map(function (x) {
+    if (this.circular) this.remove()
+});
+console.dir(scrubbed);
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/examples/stringify.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/examples/stringify.js
new file mode 100755
index 0000000..167b68b
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/examples/stringify.js
@@ -0,0 +1,38 @@
+#!/usr/bin/env node
+var traverse = require('traverse');
+
+var obj = [ 'five', 6, -3, [ 7, 8, -2, 1 ], { f : 10, g : -13 } ];
+
+var s = '';
+traverse(obj).forEach(function to_s (node) {
+    if (Array.isArray(node)) {
+        this.before(function () { s += '[' });
+        this.post(function (child) {
+            if (!child.isLast) s += ',';
+        });
+        this.after(function () { s += ']' });
+    }
+    else if (typeof node == 'object') {
+        this.before(function () { s += '{' });
+        this.pre(function (x, key) {
+            to_s(key);
+            s += ':';
+        });
+        this.post(function (child) {
+            if (!child.isLast) s += ',';
+        });
+        this.after(function () { s += '}' });
+    }
+    else if (typeof node == 'string') {
+        s += '"' + node.toString().replace(/"/g, '\\"') + '"';
+    }
+    else if (typeof node == 'function') {
+        s += 'null';
+    }
+    else {
+        s += node.toString();
+    }
+});
+
+console.log('JSON.stringify: ' + JSON.stringify(obj));
+console.log('this stringify: ' + s);
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/index.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/index.js
new file mode 100644
index 0000000..7161685
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/index.js
@@ -0,0 +1,332 @@
+module.exports = Traverse;
+function Traverse (obj) {
+    if (!(this instanceof Traverse)) return new Traverse(obj);
+    this.value = obj;
+}
+
+Traverse.prototype.get = function (ps) {
+    var node = this.value;
+    for (var i = 0; i < ps.length; i ++) {
+        var key = ps[i];
+        if (!Object.hasOwnProperty.call(node, key)) {
+            node = undefined;
+            break;
+        }
+        node = node[key];
+    }
+    return node;
+};
+
+Traverse.prototype.set = function (ps, value) {
+    var node = this.value;
+    for (var i = 0; i < ps.length - 1; i ++) {
+        var key = ps[i];
+        if (!Object.hasOwnProperty.call(node, key)) node[key] = {};
+        node = node[key];
+    }
+    node[ps[i]] = value;
+    return value;
+};
+
+Traverse.prototype.map = function (cb) {
+    return walk(this.value, cb, true);
+};
+
+Traverse.prototype.forEach = function (cb) {
+    this.value = walk(this.value, cb, false);
+    return this.value;
+};
+
+Traverse.prototype.reduce = function (cb, init) {
+    var skip = arguments.length === 1;
+    var acc = skip ? this.value : init;
+    this.forEach(function (x) {
+        if (!this.isRoot || !skip) {
+            acc = cb.call(this, acc, x);
+        }
+    });
+    return acc;
+};
+
+Traverse.prototype.deepEqual = function (obj) {
+    if (arguments.length !== 1) {
+        throw new Error(
+            'deepEqual requires exactly one object to compare against'
+        );
+    }
+    
+    var equal = true;
+    var node = obj;
+    
+    this.forEach(function (y) {
+        var notEqual = (function () {
+            equal = false;
+            //this.stop();
+            return undefined;
+        }).bind(this);
+        
+        //if (node === undefined || node === null) return notEqual();
+        
+        if (!this.isRoot) {
+        /*
+            if (!Object.hasOwnProperty.call(node, this.key)) {
+                return notEqual();
+            }
+        */
+            if (typeof node !== 'object') return notEqual();
+            node = node[this.key];
+        }
+        
+        var x = node;
+        
+        this.post(function () {
+            node = x;
+        });
+        
+        var toS = function (o) {
+            return Object.prototype.toString.call(o);
+        };
+        
+        if (this.circular) {
+            if (Traverse(obj).get(this.circular.path) !== x) notEqual();
+        }
+        else if (typeof x !== typeof y) {
+            notEqual();
+        }
+        else if (x === null || y === null || x === undefined || y === undefined) {
+            if (x !== y) notEqual();
+        }
+        else if (x.__proto__ !== y.__proto__) {
+            notEqual();
+        }
+        else if (x === y) {
+            // nop
+        }
+        else if (typeof x === 'function') {
+            if (x instanceof RegExp) {
+                // both regexps on account of the __proto__ check
+                if (x.toString() != y.toString()) notEqual();
+            }
+            else if (x !== y) notEqual();
+        }
+        else if (typeof x === 'object') {
+            if (toS(y) === '[object Arguments]'
+            || toS(x) === '[object Arguments]') {
+                if (toS(x) !== toS(y)) {
+                    notEqual();
+                }
+            }
+            else if (x instanceof Date || y instanceof Date) {
+                if (!(x instanceof Date) || !(y instanceof Date)
+                || x.getTime() !== y.getTime()) {
+                    notEqual();
+                }
+            }
+            else {
+                var kx = Object.keys(x);
+                var ky = Object.keys(y);
+                if (kx.length !== ky.length) return notEqual();
+                for (var i = 0; i < kx.length; i++) {
+                    var k = kx[i];
+                    if (!Object.hasOwnProperty.call(y, k)) {
+                        notEqual();
+                    }
+                }
+            }
+        }
+    });
+    
+    return equal;
+};
+
+Traverse.prototype.paths = function () {
+    var acc = [];
+    this.forEach(function (x) {
+        acc.push(this.path); 
+    });
+    return acc;
+};
+
+Traverse.prototype.nodes = function () {
+    var acc = [];
+    this.forEach(function (x) {
+        acc.push(this.node);
+    });
+    return acc;
+};
+
+Traverse.prototype.clone = function () {
+    var parents = [], nodes = [];
+    
+    return (function clone (src) {
+        for (var i = 0; i < parents.length; i++) {
+            if (parents[i] === src) {
+                return nodes[i];
+            }
+        }
+        
+        if (typeof src === 'object' && src !== null) {
+            var dst = copy(src);
+            
+            parents.push(src);
+            nodes.push(dst);
+            
+            Object.keys(src).forEach(function (key) {
+                dst[key] = clone(src[key]);
+            });
+            
+            parents.pop();
+            nodes.pop();
+            return dst;
+        }
+        else {
+            return src;
+        }
+    })(this.value);
+};
+
+function walk (root, cb, immutable) {
+    var path = [];
+    var parents = [];
+    var alive = true;
+    
+    return (function walker (node_) {
+        var node = immutable ? copy(node_) : node_;
+        var modifiers = {};
+        
+        var keepGoing = true;
+        
+        var state = {
+            node : node,
+            node_ : node_,
+            path : [].concat(path),
+            parent : parents[parents.length - 1],
+            parents : parents,
+            key : path.slice(-1)[0],
+            isRoot : path.length === 0,
+            level : path.length,
+            circular : null,
+            update : function (x, stopHere) {
+                if (!state.isRoot) {
+                    state.parent.node[state.key] = x;
+                }
+                state.node = x;
+                if (stopHere) keepGoing = false;
+            },
+            'delete' : function () {
+                delete state.parent.node[state.key];
+            },
+            remove : function () {
+                if (Array.isArray(state.parent.node)) {
+                    state.parent.node.splice(state.key, 1);
+                }
+                else {
+                    delete state.parent.node[state.key];
+                }
+            },
+            keys : null,
+            before : function (f) { modifiers.before = f },
+            after : function (f) { modifiers.after = f },
+            pre : function (f) { modifiers.pre = f },
+            post : function (f) { modifiers.post = f },
+            stop : function () { alive = false },
+            block : function () { keepGoing = false }
+        };
+        
+        if (!alive) return state;
+        
+        if (typeof node === 'object' && node !== null) {
+            state.keys = Object.keys(node);
+            
+            state.isLeaf = state.keys.length == 0;
+            
+            for (var i = 0; i < parents.length; i++) {
+                if (parents[i].node_ === node_) {
+                    state.circular = parents[i];
+                    break;
+                }
+            }
+        }
+        else {
+            state.isLeaf = true;
+        }
+        
+        state.notLeaf = !state.isLeaf;
+        state.notRoot = !state.isRoot;
+        
+        // use return values to update if defined
+        var ret = cb.call(state, state.node);
+        if (ret !== undefined && state.update) state.update(ret);
+        
+        if (modifiers.before) modifiers.before.call(state, state.node);
+        
+        if (!keepGoing) return state;
+        
+        if (typeof state.node == 'object'
+        && state.node !== null && !state.circular) {
+            parents.push(state);
+            
+            state.keys.forEach(function (key, i) {
+                path.push(key);
+                
+                if (modifiers.pre) modifiers.pre.call(state, state.node[key], key);
+                
+                var child = walker(state.node[key]);
+                if (immutable && Object.hasOwnProperty.call(state.node, key)) {
+                    state.node[key] = child.node;
+                }
+                
+                child.isLast = i == state.keys.length - 1;
+                child.isFirst = i == 0;
+                
+                if (modifiers.post) modifiers.post.call(state, child);
+                
+                path.pop();
+            });
+            parents.pop();
+        }
+        
+        if (modifiers.after) modifiers.after.call(state, state.node);
+        
+        return state;
+    })(root).node;
+}
+
+Object.keys(Traverse.prototype).forEach(function (key) {
+    Traverse[key] = function (obj) {
+        var args = [].slice.call(arguments, 1);
+        var t = Traverse(obj);
+        return t[key].apply(t, args);
+    };
+});
+
+function copy (src) {
+    if (typeof src === 'object' && src !== null) {
+        var dst;
+        
+        if (Array.isArray(src)) {
+            dst = [];
+        }
+        else if (src instanceof Date) {
+            dst = new Date(src);
+        }
+        else if (src instanceof Boolean) {
+            dst = new Boolean(src);
+        }
+        else if (src instanceof Number) {
+            dst = new Number(src);
+        }
+        else if (src instanceof String) {
+            dst = new String(src);
+        }
+        else {
+            dst = Object.create(Object.getPrototypeOf(src));
+        }
+        
+        Object.keys(src).forEach(function (key) {
+            dst[key] = src[key];
+        });
+        return dst;
+    }
+    else return src;
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/package.json b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/package.json
new file mode 100644
index 0000000..e337200
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/package.json
@@ -0,0 +1,42 @@
+{
+  "name": "traverse",
+  "version": "0.4.6",
+  "description": "Traverse and transform objects by visiting every node on a recursive walk",
+  "author": {
+    "name": "James Halliday"
+  },
+  "license": "MIT/X11",
+  "main": "./index",
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/substack/js-traverse.git"
+  },
+  "devDependencies": {
+    "expresso": "0.7.x"
+  },
+  "scripts": {
+    "test": "expresso"
+  },
+  "_id": "traverse@0.4.6",
+  "dependencies": {},
+  "engines": {
+    "node": "*"
+  },
+  "_engineSupported": true,
+  "_npmVersion": "1.0.10",
+  "_nodeVersion": "v0.5.0-pre",
+  "_defaultsLoaded": true,
+  "dist": {
+    "shasum": "d04b2280e4c792a5815429ef7b8b60c64c9ccc34",
+    "tarball": "http://registry.npmjs.org/traverse/-/traverse-0.4.6.tgz"
+  },
+  "directories": {},
+  "_shasum": "d04b2280e4c792a5815429ef7b8b60c64c9ccc34",
+  "_from": "traverse@0.4.x",
+  "_resolved": "https://registry.npmjs.org/traverse/-/traverse-0.4.6.tgz",
+  "bugs": {
+    "url": "https://github.com/substack/js-traverse/issues"
+  },
+  "readme": "ERROR: No README data found!",
+  "homepage": "https://github.com/substack/js-traverse"
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/circular.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/circular.js
new file mode 100644
index 0000000..e1eef3f
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/circular.js
@@ -0,0 +1,114 @@
+var assert = require('assert');
+var Traverse = require('traverse');
+var util = require('util');
+
+exports.circular = function () {
+    var obj = { x : 3 };
+    obj.y = obj;
+    var foundY = false;
+    Traverse(obj).forEach(function (x) {
+        if (this.path.join('') == 'y') {
+            assert.equal(
+                util.inspect(this.circular.node),
+                util.inspect(obj)
+            );
+            foundY = true;
+        }
+    });
+    assert.ok(foundY);
+};
+
+exports.deepCirc = function () {
+    var obj = { x : [ 1, 2, 3 ], y : [ 4, 5 ] };
+    obj.y[2] = obj;
+    
+    var times = 0;
+    Traverse(obj).forEach(function (x) {
+        if (this.circular) {
+            assert.deepEqual(this.circular.path, []);
+            assert.deepEqual(this.path, [ 'y', 2 ]);
+            times ++;
+        }
+    });
+    
+    assert.deepEqual(times, 1);
+};
+
+exports.doubleCirc = function () {
+    var obj = { x : [ 1, 2, 3 ], y : [ 4, 5 ] };
+    obj.y[2] = obj;
+    obj.x.push(obj.y);
+    
+    var circs = [];
+    Traverse(obj).forEach(function (x) {
+        if (this.circular) {
+            circs.push({ circ : this.circular, self : this, node : x });
+        }
+    });
+    
+    assert.deepEqual(circs[0].self.path, [ 'x', 3, 2 ]);
+    assert.deepEqual(circs[0].circ.path, []);
+     
+    assert.deepEqual(circs[1].self.path, [ 'y', 2 ]);
+    assert.deepEqual(circs[1].circ.path, []);
+    
+    assert.deepEqual(circs.length, 2);
+};
+
+exports.circDubForEach = function () {
+    var obj = { x : [ 1, 2, 3 ], y : [ 4, 5 ] };
+    obj.y[2] = obj;
+    obj.x.push(obj.y);
+    
+    Traverse(obj).forEach(function (x) {
+        if (this.circular) this.update('...');
+    });
+    
+    assert.deepEqual(obj, { x : [ 1, 2, 3, [ 4, 5, '...' ] ], y : [ 4, 5, '...' ] });
+};
+
+exports.circDubMap = function () {
+    var obj = { x : [ 1, 2, 3 ], y : [ 4, 5 ] };
+    obj.y[2] = obj;
+    obj.x.push(obj.y);
+    
+    var c = Traverse(obj).map(function (x) {
+        if (this.circular) {
+            this.update('...');
+        }
+    });
+    
+    assert.deepEqual(c, { x : [ 1, 2, 3, [ 4, 5, '...' ] ], y : [ 4, 5, '...' ] });
+};
+
+exports.circClone = function () {
+    var obj = { x : [ 1, 2, 3 ], y : [ 4, 5 ] };
+    obj.y[2] = obj;
+    obj.x.push(obj.y);
+    
+    var clone = Traverse.clone(obj);
+    assert.ok(obj !== clone);
+    
+    assert.ok(clone.y[2] === clone);
+    assert.ok(clone.y[2] !== obj);
+    assert.ok(clone.x[3][2] === clone);
+    assert.ok(clone.x[3][2] !== obj);
+    assert.deepEqual(clone.x.slice(0,3), [1,2,3]);
+    assert.deepEqual(clone.y.slice(0,2), [4,5]);
+};
+
+exports.circMapScrub = function () {
+    var obj = { a : 1, b : 2 };
+    obj.c = obj;
+    
+    var scrubbed = Traverse(obj).map(function (node) {
+        if (this.circular) this.remove();
+    });
+    assert.deepEqual(
+        Object.keys(scrubbed).sort(),
+        [ 'a', 'b' ]
+    );
+    assert.ok(Traverse.deepEqual(scrubbed, { a : 1, b : 2 }));
+    
+    assert.equal(obj.c, obj);
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/date.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/date.js
new file mode 100644
index 0000000..2cb8252
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/date.js
@@ -0,0 +1,35 @@
+var assert = require('assert');
+var Traverse = require('traverse');
+
+exports.dateEach = function () {
+    var obj = { x : new Date, y : 10, z : 5 };
+    
+    var counts = {};
+    
+    Traverse(obj).forEach(function (node) {
+        var t = (node instanceof Date && 'Date') || typeof node;
+        counts[t] = (counts[t] || 0) + 1;
+    });
+    
+    assert.deepEqual(counts, {
+        object : 1,
+        Date : 1,
+        number : 2,
+    });
+};
+
+exports.dateMap = function () {
+    var obj = { x : new Date, y : 10, z : 5 };
+    
+    var res = Traverse(obj).map(function (node) {
+        if (typeof node === 'number') this.update(node + 100);
+    });
+    
+    assert.ok(obj.x !== res.x);
+    assert.deepEqual(res, {
+        x : obj.x,
+        y : 110,
+        z : 105,
+    });
+};
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/equal.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/equal.js
new file mode 100644
index 0000000..4d732fa
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/equal.js
@@ -0,0 +1,219 @@
+var assert = require('assert');
+var traverse = require('traverse');
+
+exports.deepDates = function () {
+    assert.ok(
+        traverse.deepEqual(
+            { d : new Date, x : [ 1, 2, 3 ] },
+            { d : new Date, x : [ 1, 2, 3 ] }
+        ),
+        'dates should be equal'
+    );
+    
+    var d0 = new Date;
+    setTimeout(function () {
+        assert.ok(
+            !traverse.deepEqual(
+                { d : d0, x : [ 1, 2, 3 ], },
+                { d : new Date, x : [ 1, 2, 3 ] }
+            ),
+            'microseconds should count in date equality'
+        );
+    }, 5);
+};
+
+exports.deepCircular = function () {
+    var a = [1];
+    a.push(a); // a = [ 1, *a ]
+    
+    var b = [1];
+    b.push(a); // b = [ 1, [ 1, *a ] ]
+    
+    assert.ok(
+        !traverse.deepEqual(a, b),
+        'circular ref mount points count towards equality'
+    );
+    
+    var c = [1];
+    c.push(c); // c = [ 1, *c ]
+    assert.ok(
+        traverse.deepEqual(a, c),
+        'circular refs are structurally the same here'
+    );
+    
+    var d = [1];
+    d.push(a); // c = [ 1, [ 1, *d ] ]
+    assert.ok(
+        traverse.deepEqual(b, d),
+        'non-root circular ref structural comparison'
+    );
+};
+
+exports.deepInstances = function () {
+    assert.ok(
+        !traverse.deepEqual([ new Boolean(false) ], [ false ]),
+        'boolean instances are not real booleans'
+    );
+    
+    assert.ok(
+        !traverse.deepEqual([ new String('x') ], [ 'x' ]),
+        'string instances are not real strings'
+    );
+    
+    assert.ok(
+        !traverse.deepEqual([ new Number(4) ], [ 4 ]),
+        'number instances are not real numbers'
+    );
+    
+    assert.ok(
+        traverse.deepEqual([ new RegExp('x') ], [ /x/ ]),
+        'regexp instances are real regexps'
+    );
+    
+    assert.ok(
+        !traverse.deepEqual([ new RegExp(/./) ], [ /../ ]),
+        'these regexps aren\'t the same'
+    );
+    
+    assert.ok(
+        !traverse.deepEqual(
+            [ function (x) { return x * 2 } ],
+            [ function (x) { return x * 2 } ]
+        ),
+        'functions with the same .toString() aren\'t necessarily the same'
+    );
+    
+    var f = function (x) { return x * 2 };
+    assert.ok(
+        traverse.deepEqual([ f ], [ f ]),
+        'these functions are actually equal'
+    );
+};
+
+exports.deepEqual = function () {
+    assert.ok(
+        !traverse.deepEqual([ 1, 2, 3 ], { 0 : 1, 1 : 2, 2 : 3 }),
+        'arrays are not objects'
+    );
+};
+
+exports.falsy = function () {
+    assert.ok(
+        !traverse.deepEqual([ undefined ], [ null ]),
+        'null is not undefined!'
+    );
+    
+    assert.ok(
+        !traverse.deepEqual([ null ], [ undefined ]),
+        'undefined is not null!'
+    );
+    
+    assert.ok(
+        !traverse.deepEqual(
+            { a : 1, b : 2, c : [ 3, undefined, 5 ] },
+            { a : 1, b : 2, c : [ 3, null, 5 ] }
+        ),
+        'undefined is not null, however deeply!'
+    );
+    
+    assert.ok(
+        !traverse.deepEqual(
+            { a : 1, b : 2, c : [ 3, undefined, 5 ] },
+            { a : 1, b : 2, c : [ 3, null, 5 ] }
+        ),
+        'null is not undefined, however deeply!'
+    );
+    
+    assert.ok(
+        !traverse.deepEqual(
+            { a : 1, b : 2, c : [ 3, undefined, 5 ] },
+            { a : 1, b : 2, c : [ 3, null, 5 ] }
+        ),
+        'null is not undefined, however deeply!'
+    );
+};
+
+exports.deletedArrayEqual = function () {
+    var xs = [ 1, 2, 3, 4 ];
+    delete xs[2];
+    
+    var ys = Object.create(Array.prototype);
+    ys[0] = 1;
+    ys[1] = 2;
+    ys[3] = 4;
+    
+    assert.ok(
+        traverse.deepEqual(xs, ys),
+        'arrays with deleted elements are only equal to'
+        + ' arrays with similarly deleted elements'
+    );
+    
+    assert.ok(
+        !traverse.deepEqual(xs, [ 1, 2, undefined, 4 ]),
+        'deleted array elements cannot be undefined'
+    );
+    
+    assert.ok(
+        !traverse.deepEqual(xs, [ 1, 2, null, 4 ]),
+        'deleted array elements cannot be null'
+    );
+};
+
+exports.deletedObjectEqual = function () {
+    var obj = { a : 1, b : 2, c : 3 };
+    delete obj.c;
+    
+    assert.ok(
+        traverse.deepEqual(obj, { a : 1, b : 2 }),
+        'deleted object elements should not show up'
+    );
+    
+    assert.ok(
+        !traverse.deepEqual(obj, { a : 1, b : 2, c : undefined }),
+        'deleted object elements are not undefined'
+    );
+    
+    assert.ok(
+        !traverse.deepEqual(obj, { a : 1, b : 2, c : null }),
+        'deleted object elements are not null'
+    );
+};
+
+exports.emptyKeyEqual = function () {
+    assert.ok(!traverse.deepEqual(
+        { a : 1 }, { a : 1, '' : 55 }
+    ));
+};
+
+exports.deepArguments = function () {
+    assert.ok(
+        !traverse.deepEqual(
+            [ 4, 5, 6 ],
+            (function () { return arguments })(4, 5, 6)
+        ),
+        'arguments are not arrays'
+    );
+    
+    assert.ok(
+        traverse.deepEqual(
+            (function () { return arguments })(4, 5, 6),
+            (function () { return arguments })(4, 5, 6)
+        ),
+        'arguments should equal'
+    );
+};
+
+exports.deepUn = function () {
+    assert.ok(!traverse.deepEqual({ a : 1, b : 2 }, undefined));
+    assert.ok(!traverse.deepEqual({ a : 1, b : 2 }, {}));
+    assert.ok(!traverse.deepEqual(undefined, { a : 1, b : 2 }));
+    assert.ok(!traverse.deepEqual({}, { a : 1, b : 2 }));
+    assert.ok(traverse.deepEqual(undefined, undefined));
+    assert.ok(traverse.deepEqual(null, null));
+    assert.ok(!traverse.deepEqual(undefined, null));
+};
+
+exports.deepLevels = function () {
+    var xs = [ 1, 2, [ 3, 4, [ 5, 6 ] ] ];
+    assert.ok(!traverse.deepEqual(xs, []));
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/instance.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/instance.js
new file mode 100644
index 0000000..501981f
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/instance.js
@@ -0,0 +1,17 @@
+var assert = require('assert');
+var Traverse = require('traverse');
+var EventEmitter = require('events').EventEmitter;
+
+exports['check instanceof on node elems'] = function () {
+    
+    var counts = { emitter : 0 };
+    
+    Traverse([ new EventEmitter, 3, 4, { ev : new EventEmitter }])
+        .forEach(function (node) {
+            if (node instanceof EventEmitter) counts.emitter ++;
+        })
+    ;
+    
+    assert.equal(counts.emitter, 2);
+};
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/interface.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/interface.js
new file mode 100644
index 0000000..df5b037
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/interface.js
@@ -0,0 +1,42 @@
+var assert = require('assert');
+var Traverse = require('traverse');
+
+exports['interface map'] = function () {
+    var obj = { a : [ 5,6,7 ], b : { c : [8] } };
+    
+    assert.deepEqual(
+        Traverse.paths(obj)
+            .sort()
+            .map(function (path) { return path.join('/') })
+            .slice(1)
+            .join(' ')
+         ,
+         'a a/0 a/1 a/2 b b/c b/c/0'
+    );
+    
+    assert.deepEqual(
+        Traverse.nodes(obj),
+        [
+            { a: [ 5, 6, 7 ], b: { c: [ 8 ] } },
+            [ 5, 6, 7 ], 5, 6, 7,
+            { c: [ 8 ] }, [ 8 ], 8
+        ]
+    );
+    
+    assert.deepEqual(
+        Traverse.map(obj, function (node) {
+            if (typeof node == 'number') {
+                return node + 1000;
+            }
+            else if (Array.isArray(node)) {
+                return node.join(' ');
+            }
+        }),
+        { a: '5 6 7', b: { c: '8' } }
+    );
+    
+    var nodes = 0;
+    Traverse.forEach(obj, function (node) { nodes ++ });
+    assert.deepEqual(nodes, 8);
+};
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/json.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/json.js
new file mode 100644
index 0000000..bf36620
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/json.js
@@ -0,0 +1,47 @@
+var assert = require('assert');
+var Traverse = require('traverse');
+
+exports['json test'] = function () {
+    var id = 54;
+    var callbacks = {};
+    var obj = { moo : function () {}, foo : [2,3,4, function () {}] };
+    
+    var scrubbed = Traverse(obj).map(function (x) {
+        if (typeof x === 'function') {
+            callbacks[id] = { id : id, f : x, path : this.path };
+            this.update('[Function]');
+            id++;
+        }
+    });
+    
+    assert.equal(
+        scrubbed.moo, '[Function]',
+        'obj.moo replaced with "[Function]"'
+    );
+    
+    assert.equal(
+        scrubbed.foo[3], '[Function]',
+        'obj.foo[3] replaced with "[Function]"'
+    );
+    
+    assert.deepEqual(scrubbed, {
+        moo : '[Function]',
+        foo : [ 2, 3, 4, "[Function]" ]
+    }, 'Full JSON string matches');
+    
+    assert.deepEqual(
+        typeof obj.moo, 'function',
+        'Original obj.moo still a function'
+    );
+    
+    assert.deepEqual(
+        typeof obj.foo[3], 'function',
+        'Original obj.foo[3] still a function'
+    );
+    
+    assert.deepEqual(callbacks, {
+        54: { id: 54, f : obj.moo, path: [ 'moo' ] },
+        55: { id: 55, f : obj.foo[3], path: [ 'foo', '3' ] },
+    }, 'Check the generated callbacks list');
+};
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/keys.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/keys.js
new file mode 100644
index 0000000..8bf88ed
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/keys.js
@@ -0,0 +1,29 @@
+var assert = require('assert');
+var Traverse = require('traverse');
+
+exports['sort test'] = function () {
+    var acc = [];
+    Traverse({
+        a: 30,
+        b: 22,
+        id: 9
+    }).forEach(function (node) {
+        if ((! Array.isArray(node)) && typeof node === 'object') {
+            this.before(function(node) {
+                this.keys = Object.keys(node);
+                this.keys.sort(function(a, b) {
+                    a = [a === "id" ? 0 : 1, a];
+                    b = [b === "id" ? 0 : 1, b];
+                    return a < b ? -1 : a > b ? 1 : 0;
+                });
+            });
+        }
+        if (this.isLeaf) acc.push(node);
+    });
+    
+    assert.equal(
+        acc.join(' '),
+        '9 30 22',
+        'Traversal in a custom order'
+    );
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/leaves.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/leaves.js
new file mode 100644
index 0000000..4e8d280
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/leaves.js
@@ -0,0 +1,21 @@
+var assert = require('assert');
+var Traverse = require('traverse');
+
+exports['leaves test'] = function () {
+    var acc = [];
+    Traverse({
+        a : [1,2,3],
+        b : 4,
+        c : [5,6],
+        d : { e : [7,8], f : 9 }
+    }).forEach(function (x) {
+        if (this.isLeaf) acc.push(x);
+    });
+    
+    assert.equal(
+        acc.join(' '),
+        '1 2 3 4 5 6 7 8 9',
+        'Traversal in the right(?) order'
+    );
+};
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/mutability.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/mutability.js
new file mode 100644
index 0000000..5a4d6dd
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/mutability.js
@@ -0,0 +1,203 @@
+var assert = require('assert');
+var Traverse = require('traverse');
+
+exports.mutate = function () {
+    var obj = { a : 1, b : 2, c : [ 3, 4 ] };
+    var res = Traverse(obj).forEach(function (x) {
+        if (typeof x === 'number' && x % 2 === 0) {
+            this.update(x * 10);
+        }
+    });
+    assert.deepEqual(obj, res);
+    assert.deepEqual(obj, { a : 1, b : 20, c : [ 3, 40 ] });
+};
+
+exports.mutateT = function () {
+    var obj = { a : 1, b : 2, c : [ 3, 4 ] };
+    var res = Traverse.forEach(obj, function (x) {
+        if (typeof x === 'number' && x % 2 === 0) {
+            this.update(x * 10);
+        }
+    });
+    assert.deepEqual(obj, res);
+    assert.deepEqual(obj, { a : 1, b : 20, c : [ 3, 40 ] });
+};
+
+exports.map = function () {
+    var obj = { a : 1, b : 2, c : [ 3, 4 ] };
+    var res = Traverse(obj).map(function (x) {
+        if (typeof x === 'number' && x % 2 === 0) {
+            this.update(x * 10);
+        }
+    });
+    assert.deepEqual(obj, { a : 1, b : 2, c : [ 3, 4 ] });
+    assert.deepEqual(res, { a : 1, b : 20, c : [ 3, 40 ] });
+};
+
+exports.mapT = function () {
+    var obj = { a : 1, b : 2, c : [ 3, 4 ] };
+    var res = Traverse.map(obj, function (x) {
+        if (typeof x === 'number' && x % 2 === 0) {
+            this.update(x * 10);
+        }
+    });
+    assert.deepEqual(obj, { a : 1, b : 2, c : [ 3, 4 ] });
+    assert.deepEqual(res, { a : 1, b : 20, c : [ 3, 40 ] });
+};
+
+exports.clone = function () {
+    var obj = { a : 1, b : 2, c : [ 3, 4 ] };
+    var res = Traverse(obj).clone();
+    assert.deepEqual(obj, res);
+    assert.ok(obj !== res);
+    obj.a ++;
+    assert.deepEqual(res.a, 1);
+    obj.c.push(5);
+    assert.deepEqual(res.c, [ 3, 4 ]);
+};
+
+exports.cloneT = function () {
+    var obj = { a : 1, b : 2, c : [ 3, 4 ] };
+    var res = Traverse.clone(obj);
+    assert.deepEqual(obj, res);
+    assert.ok(obj !== res);
+    obj.a ++;
+    assert.deepEqual(res.a, 1);
+    obj.c.push(5);
+    assert.deepEqual(res.c, [ 3, 4 ]);
+};
+
+exports.reduce = function () {
+    var obj = { a : 1, b : 2, c : [ 3, 4 ] };
+    var res = Traverse(obj).reduce(function (acc, x) {
+        if (this.isLeaf) acc.push(x);
+        return acc;
+    }, []);
+    assert.deepEqual(obj, { a : 1, b : 2, c : [ 3, 4 ] });
+    assert.deepEqual(res, [ 1, 2, 3, 4 ]);
+};
+
+exports.reduceInit = function () {
+    var obj = { a : 1, b : 2, c : [ 3, 4 ] };
+    var res = Traverse(obj).reduce(function (acc, x) {
+        if (this.isRoot) assert.fail('got root');
+        return acc;
+    });
+    assert.deepEqual(obj, { a : 1, b : 2, c : [ 3, 4 ] });
+    assert.deepEqual(res, obj);
+};
+
+exports.remove = function () {
+    var obj = { a : 1, b : 2, c : [ 3, 4 ] };
+    Traverse(obj).forEach(function (x) {
+        if (this.isLeaf && x % 2 == 0) this.remove();
+    });
+    
+    assert.deepEqual(obj, { a : 1, c : [ 3 ] });
+};
+
+exports.removeMap = function () {
+    var obj = { a : 1, b : 2, c : [ 3, 4 ] };
+    var res = Traverse(obj).map(function (x) {
+        if (this.isLeaf && x % 2 == 0) this.remove();
+    });
+    
+    assert.deepEqual(obj, { a : 1, b : 2, c : [ 3, 4 ] });
+    assert.deepEqual(res, { a : 1, c : [ 3 ] });
+};
+
+exports.delete = function () {
+    var obj = { a : 1, b : 2, c : [ 3, 4 ] };
+    Traverse(obj).forEach(function (x) {
+        if (this.isLeaf && x % 2 == 0) this.delete();
+    });
+    
+    assert.ok(!Traverse.deepEqual(
+        obj, { a : 1, c : [ 3, undefined ] }
+    ));
+    
+    assert.ok(Traverse.deepEqual(
+        obj, { a : 1, c : [ 3 ] }
+    ));
+    
+    assert.ok(!Traverse.deepEqual(
+        obj, { a : 1, c : [ 3, null ] }
+    ));
+};
+
+exports.deleteRedux = function () {
+    var obj = { a : 1, b : 2, c : [ 3, 4, 5 ] };
+    Traverse(obj).forEach(function (x) {
+        if (this.isLeaf && x % 2 == 0) this.delete();
+    });
+    
+    assert.ok(!Traverse.deepEqual(
+        obj, { a : 1, c : [ 3, undefined, 5 ] }
+    ));
+    
+    assert.ok(Traverse.deepEqual(
+        obj, { a : 1, c : [ 3 ,, 5 ] }
+    ));
+    
+    assert.ok(!Traverse.deepEqual(
+        obj, { a : 1, c : [ 3, null, 5 ] }
+    ));
+    
+    assert.ok(!Traverse.deepEqual(
+        obj, { a : 1, c : [ 3, 5 ] }
+    ));
+};
+
+exports.deleteMap = function () {
+    var obj = { a : 1, b : 2, c : [ 3, 4 ] };
+    var res = Traverse(obj).map(function (x) {
+        if (this.isLeaf && x % 2 == 0) this.delete();
+    });
+    
+    assert.ok(Traverse.deepEqual(
+        obj,
+        { a : 1, b : 2, c : [ 3, 4 ] }
+    ));
+    
+    var xs = [ 3, 4 ];
+    delete xs[1];
+    
+    assert.ok(Traverse.deepEqual(
+        res, { a : 1, c : xs }
+    ));
+    
+    assert.ok(Traverse.deepEqual(
+        res, { a : 1, c : [ 3, ] }
+    ));
+    
+    assert.ok(Traverse.deepEqual(
+        res, { a : 1, c : [ 3 ] }
+    ));
+};
+
+exports.deleteMapRedux = function () {
+    var obj = { a : 1, b : 2, c : [ 3, 4, 5 ] };
+    var res = Traverse(obj).map(function (x) {
+        if (this.isLeaf && x % 2 == 0) this.delete();
+    });
+    
+    assert.ok(Traverse.deepEqual(
+        obj,
+        { a : 1, b : 2, c : [ 3, 4, 5 ] }
+    ));
+    
+    var xs = [ 3, 4, 5 ];
+    delete xs[1];
+    
+    assert.ok(Traverse.deepEqual(
+        res, { a : 1, c : xs }
+    ));
+    
+    assert.ok(!Traverse.deepEqual(
+        res, { a : 1, c : [ 3, 5 ] }
+    ));
+    
+    assert.ok(Traverse.deepEqual(
+        res, { a : 1, c : [ 3 ,, 5 ] }
+    ));
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/negative.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/negative.js
new file mode 100644
index 0000000..6cf287d
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/negative.js
@@ -0,0 +1,20 @@
+var Traverse = require('traverse');
+var assert = require('assert');
+
+exports['negative update test'] = function () {
+    var obj = [ 5, 6, -3, [ 7, 8, -2, 1 ], { f : 10, g : -13 } ];
+    var fixed = Traverse.map(obj, function (x) {
+        if (x < 0) this.update(x + 128);
+    });
+    
+    assert.deepEqual(fixed,
+        [ 5, 6, 125, [ 7, 8, 126, 1 ], { f: 10, g: 115 } ],
+        'Negative values += 128'
+    );
+    
+    assert.deepEqual(obj,
+        [ 5, 6, -3, [ 7, 8, -2, 1 ], { f: 10, g: -13 } ],
+        'Original references not modified'
+    );
+}
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/obj.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/obj.js
new file mode 100644
index 0000000..9c3b0db
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/obj.js
@@ -0,0 +1,15 @@
+var assert = require('assert');
+var Traverse = require('traverse');
+
+exports['traverse an object with nested functions'] = function () {
+    var to = setTimeout(function () {
+        assert.fail('never ran');
+    }, 1000);
+    
+    function Cons (x) {
+        clearTimeout(to);
+        assert.equal(x, 10);
+    };
+    Traverse(new Cons(10));
+};
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/siblings.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/siblings.js
new file mode 100644
index 0000000..1236834
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/siblings.js
@@ -0,0 +1,35 @@
+var assert = require('assert');
+var traverse = require('traverse');
+
+exports.siblings = function () {
+    var obj = { a : 1, b : 2, c : [ 4, 5, 6 ] };
+    
+    var res = traverse(obj).reduce(function (acc, x) {
+        var p = '/' + this.path.join('/');
+        if (this.parent) {
+            acc[p] = {
+                siblings : this.parent.keys,
+                key : this.key,
+                index : this.parent.keys.indexOf(this.key)
+            };
+        }
+        else {
+            acc[p] = {
+                siblings : [],
+                key : this.key,
+                index : -1
+            }
+        }
+        return acc;
+    }, {});
+    
+    assert.deepEqual(res, {
+        '/' : { siblings : [], key : undefined, index : -1 },
+        '/a' : { siblings : [ 'a', 'b', 'c' ], key : 'a', index : 0 },
+        '/b' : { siblings : [ 'a', 'b', 'c' ], key : 'b', index : 1 },
+        '/c' : { siblings : [ 'a', 'b', 'c' ], key : 'c', index : 2 },
+        '/c/0' : { siblings : [ '0', '1', '2' ], key : '0', index : 0 },
+        '/c/1' : { siblings : [ '0', '1', '2' ], key : '1', index : 1 },
+        '/c/2' : { siblings : [ '0', '1', '2' ], key : '2', index : 2 }
+    });
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/stop.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/stop.js
new file mode 100644
index 0000000..ef6b36e
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/stop.js
@@ -0,0 +1,41 @@
+var assert = require('assert');
+var traverse = require('traverse');
+
+exports.stop = function () {
+    var visits = 0;
+    traverse('abcdefghij'.split('')).forEach(function (node) {
+        if (typeof node === 'string') {
+            visits ++;
+            if (node === 'e') this.stop()
+        }
+    });
+    
+    assert.equal(visits, 5);
+};
+
+exports.stopMap = function () {
+    var s = traverse('abcdefghij'.split('')).map(function (node) {
+        if (typeof node === 'string') {
+            if (node === 'e') this.stop()
+            return node.toUpperCase();
+        }
+    }).join('');
+    
+    assert.equal(s, 'ABCDEfghij');
+};
+
+exports.stopReduce = function () {
+    var obj = {
+        a : [ 4, 5 ],
+        b : [ 6, [ 7, 8, 9 ] ]
+    };
+    var xs = traverse(obj).reduce(function (acc, node) {
+        if (this.isLeaf) {
+            if (node === 7) this.stop();
+            else acc.push(node)
+        }
+        return acc;
+    }, []);
+    
+    assert.deepEqual(xs, [ 4, 5, 6 ]);
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/stringify.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/stringify.js
new file mode 100644
index 0000000..bf36f63
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/stringify.js
@@ -0,0 +1,36 @@
+var assert = require('assert');
+var Traverse = require('traverse');
+
+exports.stringify = function () {
+    var obj = [ 5, 6, -3, [ 7, 8, -2, 1 ], { f : 10, g : -13 } ];
+    
+    var s = '';
+    Traverse(obj).forEach(function (node) {
+        if (Array.isArray(node)) {
+            this.before(function () { s += '[' });
+            this.post(function (child) {
+                if (!child.isLast) s += ',';
+            });
+            this.after(function () { s += ']' });
+        }
+        else if (typeof node == 'object') {
+            this.before(function () { s += '{' });
+            this.pre(function (x, key) {
+                s += '"' + key + '"' + ':';
+            });
+            this.post(function (child) {
+                if (!child.isLast) s += ',';
+            });
+            this.after(function () { s += '}' });
+        }
+        else if (typeof node == 'function') {
+            s += 'null';
+        }
+        else {
+            s += node.toString();
+        }
+    });
+    
+    assert.equal(s, JSON.stringify(obj));
+}
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/subexpr.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/subexpr.js
new file mode 100644
index 0000000..a4960fb
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/subexpr.js
@@ -0,0 +1,34 @@
+var traverse = require('traverse');
+var assert = require('assert');
+
+exports.subexpr = function () {
+    var obj = [ 'a', 4, 'b', 5, 'c', 6 ];
+    var r = traverse(obj).map(function (x) {
+        if (typeof x === 'number') {
+            this.update([ x - 0.1, x, x + 0.1 ], true);
+        }
+    });
+    
+    assert.deepEqual(obj, [ 'a', 4, 'b', 5, 'c', 6 ]);
+    assert.deepEqual(r, [
+        'a', [ 3.9, 4, 4.1 ],
+        'b', [ 4.9, 5, 5.1 ],
+        'c', [ 5.9, 6, 6.1 ],
+    ]);
+};
+
+exports.block = function () {
+    var obj = [ [ 1 ], [ 2 ], [ 3 ] ];
+    var r = traverse(obj).map(function (x) {
+        if (Array.isArray(x) && !this.isRoot) {
+            if (x[0] === 5) this.block()
+            else this.update([ [ x[0] + 1 ] ])
+        }
+    });
+    
+    assert.deepEqual(r, [
+        [ [ [ [ [ 5 ] ] ] ] ],
+        [ [ [ [ 5 ] ] ] ],
+        [ [ [ 5 ] ] ],
+    ]);
+};
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/super_deep.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/super_deep.js
new file mode 100644
index 0000000..974181e
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/super_deep.js
@@ -0,0 +1,54 @@
+var assert = require('assert');
+var traverse = require('traverse');
+
+exports.super_deep = function () {
+    var util = require('util');
+    var a0 = make();
+    var a1 = make();
+    assert.ok(traverse.deepEqual(a0, a1));
+    
+    a0.c.d.moo = true;
+    assert.ok(!traverse.deepEqual(a0, a1));
+    
+    a1.c.d.moo = true;
+    assert.ok(traverse.deepEqual(a0, a1));
+    
+    // TODO: this one
+    //a0.c.a = a1;
+    //assert.ok(!traverse.deepEqual(a0, a1));
+};
+
+function make () {
+    var a = { self : 'a' };
+    var b = { self : 'b' };
+    var c = { self : 'c' };
+    var d = { self : 'd' };
+    var e = { self : 'e' };
+    
+    a.a = a;
+    a.b = b;
+    a.c = c;
+    
+    b.a = a;
+    b.b = b;
+    b.c = c;
+    
+    c.a = a;
+    c.b = b;
+    c.c = c;
+    c.d = d;
+    
+    d.a = a;
+    d.b = b;
+    d.c = c;
+    d.d = d;
+    d.e = e;
+    
+    e.a = a;
+    e.b = b;
+    e.c = c;
+    e.d = d;
+    e.e = e;
+    
+    return a;
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/package.json b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/package.json
new file mode 100644
index 0000000..074b9db
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/package.json
@@ -0,0 +1,51 @@
+{
+  "name": "js-select",
+  "description": "Traverse and modify objects with JSONSelect selectors",
+  "version": "0.6.0",
+  "author": {
+    "name": "Heather Arthur",
+    "email": "fayearthur@gmail.com"
+  },
+  "repository": {
+    "type": "git",
+    "url": "http://github.com/harthur/js-select.git"
+  },
+  "main": "./index",
+  "dependencies": {
+    "traverse": "0.4.x",
+    "JSONSelect": "0.2.1"
+  },
+  "devDependencies": {
+    "nomnom": "0.6.x",
+    "color": "0.3.x"
+  },
+  "keywords": [
+    "json"
+  ],
+  "readme": "# js-select\n\njs-select uses [js-traverse](https://github.com/substack/js-traverse) to traverse and modify JavaScript object nodes that match [JSONSelect](http://jsonselect.org/) selectors.\n\n```javascript\nvar people = {\n   george: {\n      age : 35,\n      movie: \"Repo Man\"\n   },\n   mary: {\n      age: 15,\n      movie: \"Twilight\"\n   }\n};\n```\n\n### .forEach(fn)\n\nIterates over all matching nodes in the object. The callback gets a special `this` context. See [js-traverse](https://github.com/substack/js-traverse) for all the things you can do to modify and inspect the node with this context. In addition, js-select adds a `this.matches()` which will test if the node matches a selector:\n\n```javascript\nselect(people).forEach(function(node) {\n   if (this.matches(\".mary > .movie\")) {\n      this.remove();\n   }\n});\n```\n\n### .nodes()\n\nReturns all matching nodes from the object.\n\n```javascript\nselect(people, \".age\").nodes(); // [35, 15]\n```\n\n### .remove()\n\nRemoves matching elements from the original object.\n\n```javascript\nselect(people, \".age\").remove();\n```\n\n### .update(fn)\n\nUpdates all matching nodes using the given callback.\n\n```javascript\nselect(people, \".age\").update(function(age) {\n   return age - 5;\n});\n```\n\n### .condense()\n\nReduces the original object down to only the matching elements (the hierarchy is maintained).\n\n```javascript\nselect(people, \".age\").condense();\n```\n\n```javascript\n{\n    george: { age: 35 },\n    mary: { age: 15 }\n}\n```\n\n## Selectors\n\njs-select supports the following [JSONSelect](http://jsonselect.org/) selectors:\n\n```\n*\ntype\n.key\nancestor selector\nparent > selector\nsibling ~ selector\nselector1, selector2\n:root\n:nth-child(n)\n:nth-child(even)\n:nth-child(odd)\n:nth-last-child(n)\n:first-child\n:last-child\n:only-child\n:has(selector)\n:val(\"string\")\n:contains(\"substring\")\n```\n\nSee [details](http://jsonselect.org/#docs/overview) on each selector, and [try them](http://jsonselect.org/#tryit) out on the JSONSelect website.\n\n## Install\n\nFor [node](http://nodejs.org), install with [npm](http://npmjs.org):\n\n```bash\nnpm install js-select\n```\n\nFor the browser, download the select.js file or fetch the latest version from [npm](http://npmjs.org) and build a browser file using [browserify](https://github.com/substack/node-browserify):\n\n```bash\nnpm install browserify -g\nnpm install js-select\n\nbrowserify --require js-select --outfile select.js\n```\nthis will build a browser file with `require('js-select')` available.\n\n## Propers\n\nHuge thanks to [@substack](http://github.com/substack) for the ingenious [js-traverse](https://github.com/substack/js-traverse) and [@lloyd](https://github.com/lloyd) for the ingenious [JSONSelect spec](http://http://jsonselect.org/) and [selector parser](http://search.npmjs.org/#/JSONSelect).",
+  "readmeFilename": "README.md",
+  "_id": "js-select@0.6.0",
+  "dist": {
+    "shasum": "c284e22824d5927aec962dcdf247174aefb0d190",
+    "tarball": "http://registry.npmjs.org/js-select/-/js-select-0.6.0.tgz"
+  },
+  "_from": "js-select@~0.6.0",
+  "_npmVersion": "1.2.14",
+  "_npmUser": {
+    "name": "harth",
+    "email": "fayearthur@gmail.com"
+  },
+  "maintainers": [
+    {
+      "name": "harth",
+      "email": "fayearthur@gmail.com"
+    }
+  ],
+  "directories": {},
+  "_shasum": "c284e22824d5927aec962dcdf247174aefb0d190",
+  "_resolved": "https://registry.npmjs.org/js-select/-/js-select-0.6.0.tgz",
+  "bugs": {
+    "url": "https://github.com/harthur/js-select/issues"
+  },
+  "homepage": "https://github.com/harthur/js-select"
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/select-min.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/select-min.js
new file mode 100644
index 0000000..466ccf7
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/select-min.js
@@ -0,0 +1,37 @@
+var select=function(){var c=function(j,f){var d=c.resolve(j,f||"/"),l=c.modules[d];if(!l)throw Error("Failed to resolve module "+j+", tried "+d);return(d=c.cache[d])?d.exports:l()};c.paths=[];c.modules={};c.cache={};c.extensions=[".js",".coffee"];c._core={assert:!0,events:!0,fs:!0,path:!0,vm:!0};c.resolve=function(j,f){function d(b){b=h.normalize(b);if(c.modules[b])return b;for(var a=0;a<c.extensions.length;a++){var e=c.extensions[a];if(c.modules[b+e])return b+e}}function l(b){var b=b.replace(/\/+$/,
+""),a=h.normalize(b+"/package.json");if(c.modules[a]){var a=c.modules[a](),e=a.browserify;if("object"===typeof e&&e.main){if(a=d(h.resolve(b,e.main)))return a}else if("string"===typeof e){if(a=d(h.resolve(b,e)))return a}else if(a.main&&(a=d(h.resolve(b,a.main))))return a}return d(b+"/index")}f||(f="/");if(c._core[j])return j;var h=c.modules.path(),a=(f=h.resolve("/",f))||"/";if(j.match(/^(?:\.\.?\/|\/)/)){var e=d(h.resolve(a,j))||l(h.resolve(a,j));if(e)return e}a:{for(var e="/"===a?[""]:h.normalize(a).split("/"),
+a=[],g=e.length-1;0<=g;g--)if("node_modules"!==e[g]){var b=e.slice(0,g+1).join("/")+"/node_modules";a.push(b)}for(e=0;e<a.length;e++){g=a[e];if(b=d(g+"/"+j)){a=b;break a}if(g=l(g+"/"+j)){a=g;break a}}a=(b=d(j))?b:void 0}if(a)return a;throw Error("Cannot find module '"+j+"'");};c.alias=function(j,f){var d=c.modules.path(),l=null;try{l=c.resolve(j+"/package.json","/")}catch(h){l=c.resolve(j,"/")}for(var d=d.dirname(l),l=(Object.keys||function(a){var b=[],e;for(e in a)b.push(e);return b})(c.modules),
+a=0;a<l.length;a++){var e=l[a];e.slice(0,d.length+1)===d+"/"?(e=e.slice(d.length),c.modules[f+e]=c.modules[d+e]):e===d&&(c.modules[f]=c.modules[d])}};var u={};c.define=function(j,f){c.modules.__browserify_process&&(u=c.modules.__browserify_process());var d=c._core[j]?"":c.modules.path().dirname(j),l=function(a){var e=c(a,d);if((a=c.cache[c.resolve(a,d)])&&null===a.parent)a.parent=h;return e};l.resolve=function(a){return c.resolve(a,d)};l.modules=c.modules;l.define=c.define;l.cache=c.cache;var h={id:j,
+filename:j,exports:{},loaded:!1,parent:null};c.modules[j]=function(){c.cache[j]=h;f.call(h.exports,l,h,h.exports,d,j,u);h.loaded=!0;return h.exports}};c.define("path",function(c,f,d,l,h,a){function e(a,b){for(var e=[],g=0;g<a.length;g++)b(a[g],g,a)&&e.push(a[g]);return e}function g(a,b){for(var e=0,g=a.length;0<=g;g--){var d=a[g];"."==d?a.splice(g,1):".."===d?(a.splice(g,1),e++):e&&(a.splice(g,1),e--)}if(b)for(;e--;e)a.unshift("..");return a}var b=/^(.+\/(?!$)|\/)?((?:.+?)?(\.[^.]*)?)$/;d.resolve=
+function(){for(var b="",o=!1,d=arguments.length;-1<=d&&!o;d--){var c=0<=d?arguments[d]:a.cwd();"string"===typeof c&&c&&(b=c+"/"+b,o="/"===c.charAt(0))}b=g(e(b.split("/"),function(a){return!!a}),!o).join("/");return(o?"/":"")+b||"."};d.normalize=function(a){var b="/"===a.charAt(0),d="/"===a.slice(-1),a=g(e(a.split("/"),function(a){return!!a}),!b).join("/");!a&&!b&&(a=".");a&&d&&(a+="/");return(b?"/":"")+a};d.join=function(){var a=Array.prototype.slice.call(arguments,0);return d.normalize(e(a,function(a){return a&&
+"string"===typeof a}).join("/"))};d.dirname=function(a){return(a=b.exec(a)[1]||"")?1===a.length?a:a.substring(0,a.length-1):"."};d.basename=function(a,e){var g=b.exec(a)[2]||"";e&&g.substr(-1*e.length)===e&&(g=g.substr(0,g.length-e.length));return g};d.extname=function(a){return b.exec(a)[3]||""}});c.define("__browserify_process",function(c,f,d,l,h,a){var f=a=f.exports={},e=[],g="undefined"!==typeof window&&window.postMessage&&window.addEventListener;g&&window.addEventListener("message",function(a){a.source===
+window&&"browserify-tick"===a.data&&(a.stopPropagation(),0<e.length&&e.shift()())},!0);f.nextTick=function(a){g?(e.push(a),window.postMessage("browserify-tick","*")):setTimeout(a,0)};a.title="browser";a.browser=!0;a.env={};a.argv=[];a.binding=function(a){if("evals"===a)return c("vm");throw Error("No such module. (Possibly not yet loaded)");};var b="/",t;a.cwd=function(){return b};a.chdir=function(a){t||(t=c("path"));b=t.resolve(a,b)}});c.define("vm",function(c,f){f.exports=c("vm-browserify")});c.define("/node_modules/vm-browserify/package.json",
+function(c,f){f.exports={main:"index.js"}});c.define("/node_modules/vm-browserify/index.js",function(c,f,d){var l=function(a){if(Object.keys)return Object.keys(a);var g=[],b;for(b in a)g.push(b);return g},h=function(a,g){if(a.forEach)return a.forEach(g);for(var b=0;b<a.length;b++)g(a[b],b,a)},a=d.Script=function(e){if(!(this instanceof a))return new a(e);this.code=e};a.prototype.runInNewContext=function(a){a||(a={});var g=document.createElement("iframe");g.style||(g.style={});g.style.display="none";
+document.body.appendChild(g);var b=g.contentWindow;h(l(a),function(g){b[g]=a[g]});!b.eval&&b.execScript&&b.execScript("null");var d=b.eval(this.code);h(l(b),function(g){a[g]=b[g]});document.body.removeChild(g);return d};a.prototype.runInThisContext=function(){return eval(this.code)};a.prototype.runInContext=function(a){return this.runInNewContext(a)};h(l(a.prototype),function(e){d[e]=a[e]=function(g){var b=a(g);return b[e].apply(b,[].slice.call(arguments,1))}});d.createScript=function(a){return d.Script(a)};
+d.createContext=a.createContext=function(a){var g={};"object"===typeof a&&h(l(a),function(b){g[b]=a[b]});return g}});c.define("/package.json",function(c,f){f.exports={main:"./index"}});c.define("js-select",function(c,f){function d(a){a=e._parse(a||"*")[1];return","==a[0]?a.slice(1):[a]}function l(a,e){for(var g=0;g<a.length;g++){var d;a:{d=a[g];for(var c=e,l=c.parents.concat([c]),j=l.length-1,f=d.length-1,r=!0;0<=f&&0<=j;){var q=d[f],c=l[j];if(">"==q)f--,r=!0;else{if(h(q,c))f--;else if(r){d=!1;break a}j--;
+r=!1}}d=-1==f}if(d)return!0}return!1}function h(b,e){var d=e.key,c=e.node,h=e.parent;if(b.id&&d!=b.id)return!1;if(b.type){var f=b.type;if("null"==f&&null!==c||"array"==f&&!g(c)||"object"==f&&("object"!=typeof c||null===c||g(c))||("boolean"==f||"string"==f||"number"==f)&&f!=typeof c)return!1}if(":nth-child"==b.pf&&(f=parseInt(d)+1,0==b.a&&f!==b.b||1==b.a&&!(f>=-b.b)||-1==b.a&&!(f<=b.b)||2==b.a&&f%2!=b.b)||":nth-last-child"==b.pf&&(!h||d!=h.node.length-b.b)||":only-child"==b.pc&&(!h||1!=h.node.length)||
+":root"==b.pc&&void 0!==d)return!1;if(b.has){var j=","==b.has[0][0]?b.has[0].slice(1):[b.has[0]],p=!1;a(c).forEach(function(){l(j,this)&&(p=!0)});if(!p)return!1}return b.expr&&(f=b.expr,d=f[0],h=f[1],f=f[2],"string"!=typeof c||!d&&"="==h&&c!=f||!d&&"*="==h&&-1==c.indexOf(f))?!1:!0}var a=c("traverse"),e=c("JSONSelect");f.exports=function(b,e){var g=d(e);return{nodes:function(){var a=[];this.forEach(function(b){a.push(b)});return a},update:function(a){this.forEach(function(b){this.update("function"==
+typeof a?a(b):a)})},remove:function(){this.forEach(function(){this.remove()})},condense:function(){a(b).forEach(function(){if(this.parent)if(this.parent.keep)this.keep=!0;else{var a=l(g,this);(this.keep=a)?this.parent.keep_child=!0:this.isLeaf?this.remove():this.after(function(){this.keep_child&&(this.parent.keep_child=!0);!this.keep&&!this.keep_child&&this.remove()})}})},forEach:function(e){a(b).forEach(function(a){l(g,this)&&(this.matches=function(a){return l(d(a),this)},e.call(this,a))})}}};var g=
+Array.isArray||function(a){return"[object Array]"===toString.call(a)}});c.define("/node_modules/traverse/package.json",function(c,f){f.exports={main:"./index"}});c.define("/node_modules/traverse/index.js",function(c,f){function d(a){if(!(this instanceof d))return new d(a);this.value=a}function l(a,e,g){var b=[],d=[],c=!0;return function x(a){var f=g?h(a):a,l,j,q,v,n=!0,i={node:f,node_:a,path:[].concat(b),parent:d[d.length-1],parents:d,key:b.slice(-1)[0],isRoot:0===b.length,level:b.length,circular:null,
+update:function(a,b){i.isRoot||(i.parent.node[i.key]=a);i.node=a;b&&(n=!1)},"delete":function(){delete i.parent.node[i.key]},remove:function(){Array.isArray(i.parent.node)?i.parent.node.splice(i.key,1):delete i.parent.node[i.key]},keys:null,before:function(a){l=a},after:function(a){j=a},pre:function(a){q=a},post:function(a){v=a},stop:function(){c=!1},block:function(){n=!1}};if(!c)return i;if("object"===typeof f&&null!==f){i.keys=Object.keys(f);i.isLeaf=0==i.keys.length;for(f=0;f<d.length;f++)if(d[f].node_===
+a){i.circular=d[f];break}}else i.isLeaf=!0;i.notLeaf=!i.isLeaf;i.notRoot=!i.isRoot;a=e.call(i,i.node);void 0!==a&&i.update&&i.update(a);l&&l.call(i,i.node);if(!n)return i;"object"==typeof i.node&&(null!==i.node&&!i.circular)&&(d.push(i),i.keys.forEach(function(a,e){b.push(a);q&&q.call(i,i.node[a],a);var d=x(i.node[a]);g&&Object.hasOwnProperty.call(i.node,a)&&(i.node[a]=d.node);d.isLast=e==i.keys.length-1;d.isFirst=0==e;v&&v.call(i,d);b.pop()}),d.pop());j&&j.call(i,i.node);return i}(a).node}function h(a){if("object"===
+typeof a&&null!==a){var e;e=Array.isArray(a)?[]:a instanceof Date?new Date(a):a instanceof Boolean?new Boolean(a):a instanceof Number?new Number(a):a instanceof String?new String(a):Object.create(Object.getPrototypeOf(a));Object.keys(a).forEach(function(d){e[d]=a[d]});return e}return a}f.exports=d;d.prototype.get=function(a){for(var e=this.value,d=0;d<a.length;d++){var b=a[d];if(!Object.hasOwnProperty.call(e,b)){e=void 0;break}e=e[b]}return e};d.prototype.set=function(a,e){for(var d=this.value,b=
+0;b<a.length-1;b++){var c=a[b];Object.hasOwnProperty.call(d,c)||(d[c]={});d=d[c]}return d[a[b]]=e};d.prototype.map=function(a){return l(this.value,a,!0)};d.prototype.forEach=function(a){return this.value=l(this.value,a,!1)};d.prototype.reduce=function(a,d){var c=1===arguments.length,b=c?this.value:d;this.forEach(function(d){if(!this.isRoot||!c)b=a.call(this,b,d)});return b};d.prototype.deepEqual=function(a){if(1!==arguments.length)throw Error("deepEqual requires exactly one object to compare against");
+var e=!0,c=a;this.forEach(function(b){var f=function(){e=!1}.bind(this);if(!this.isRoot){if("object"!==typeof c)return f();c=c[this.key]}var h=c;this.post(function(){c=h});if(this.circular)d(a).get(this.circular.path)!==h&&f();else if(typeof h!==typeof b)f();else if(null===h||null===b||void 0===h||void 0===b)h!==b&&f();else if(h.__proto__!==b.__proto__)f();else if(h!==b)if("function"===typeof h)h instanceof RegExp?h.toString()!=b.toString()&&f():h!==b&&f();else if("object"===typeof h)if("[object Arguments]"===
+Object.prototype.toString.call(b)||"[object Arguments]"===Object.prototype.toString.call(h))Object.prototype.toString.call(h)!==Object.prototype.toString.call(b)&&f();else if(h instanceof Date||b instanceof Date)(!(h instanceof Date)||!(b instanceof Date)||h.getTime()!==b.getTime())&&f();else{var l=Object.keys(h),j=Object.keys(b);if(l.length!==j.length)return f();for(j=0;j<l.length;j++)Object.hasOwnProperty.call(b,l[j])||f()}});return e};d.prototype.paths=function(){var a=[];this.forEach(function(){a.push(this.path)});
+return a};d.prototype.nodes=function(){var a=[];this.forEach(function(){a.push(this.node)});return a};d.prototype.clone=function(){var a=[],d=[];return function b(c){for(var f=0;f<a.length;f++)if(a[f]===c)return d[f];if("object"===typeof c&&null!==c){var j=h(c);a.push(c);d.push(j);Object.keys(c).forEach(function(a){j[a]=b(c[a])});a.pop();d.pop();return j}return c}(this.value)};Object.keys(d.prototype).forEach(function(a){d[a]=function(c){var f=[].slice.call(arguments,1),b=d(c);return b[a].apply(b,
+f)}})});c.define("/node_modules/JSONSelect/package.json",function(c,f){f.exports={main:"src/jsonselect"}});c.define("/node_modules/JSONSelect/src/jsonselect.js",function(c,f,d){var c="undefined"===typeof d?window.JSONSelect={}:d,l=function(a){try{return JSON&&JSON.parse?JSON.parse(a):(new Function("return "+a))()}catch(b){h("ijs",b.message)}},h=function(a,b){throw Error(v[a]+(b&&" in '"+b+"'"));},a=function(a,b){b||(b=0);var d=i.exec(a.substr(b));if(d){var b=b+d[0].length,c;d[1]?c=[b," "]:d[2]?c=
+[b,d[0]]:d[3]?c=[b,n.typ,d[0]]:d[4]?c=[b,n.psc,d[0]]:d[5]?c=[b,n.psf,d[0]]:d[6]?h("upc",a):d[8]?c=[b,d[7]?n.ide:n.str,l(d[8])]:d[9]?h("ujs",a):d[10]&&(c=[b,n.ide,d[10].replace(/\\([^\r\n\f0-9a-fA-F])/g,"$1")]);return c}},e=function(a,b){return typeof a===b},g=function(a,b){var d,c=B.exec(a.substr(b));if(c)return b+=c[0].length,d=c[1]||c[2]||c[3]||c[5]||c[6],c[1]||c[2]||c[3]?[b,0,l(d)]:c[4]?[b,0,void 0]:[b,d]},b=function(a,d){d||(d=0);var c=g(a,d),e;c&&"("===c[1]?(e=b(a,c[0]),c=g(a,e[0]),(!c||")"!==
+c[1])&&h("epex",a),d=c[0],e=["(",e[1]]):!c||c[1]&&"x"!=c[1]?h("ee",a+" - "+(c[1]&&c[1])):(e="x"===c[1]?void 0:c[2],d=c[0]);c=g(a,d);if(!c||")"==c[1])return[d,e];("x"==c[1]||!c[1])&&h("bop",a+" - "+(c[1]&&c[1]));var f=b(a,c[0]),d=f[0],f=f[1],l;if("object"!==typeof f||"("===f[0]||w[c[1]][0]<w[f[1]][0])l=[e,c[1],f];else{for(l=f;"object"===typeof f[0]&&"("!=f[0][0]&&w[c[1]][0]>=w[f[0][1]][0];)f=f[0];f[0]=[e,c[1],f[0]]}return[d,l]},t=function(a,c){function d(a){return"object"!==typeof a||null===a?a:"("===
+a[0]?d(a[1]):[d(a[0]),a[1],d(a[2])]}var e=b(a,c?c:0);return[e[0],d(e[1])]},o=function(a,c){if(void 0===a)return c;if(null===a||"object"!==typeof a)return a;var b=o(a[0],c),d=o(a[2],c);return w[a[1]][1](b,d)},y=function(c,b,d,e){d||(e={});var f=[],g,l;for(b||(b=0);;){var m;m=c;var i=b,b=i,j={},k=a(m,i);k&&" "===k[1]&&(b=i=k[0],k=a(m,i));k&&k[1]===n.typ?(j.type=k[2],k=a(m,i=k[0])):k&&"*"===k[1]&&(k=a(m,i=k[0]));for(;void 0!==k;){if(k[1]===n.ide)j.id&&h("nmi",k[1]),j.id=k[2];else if(k[1]===n.psc)(j.pc||
+j.pf)&&h("mpc",k[1]),":first-child"===k[2]?(j.pf=":nth-child",j.a=0,j.b=1):":last-child"===k[2]?(j.pf=":nth-last-child",j.a=0,j.b=1):j.pc=k[2];else if(k[1]===n.psf)":val"===k[2]||":contains"===k[2]?(j.expr=[void 0,":val"===k[2]?"=":"*=",void 0],(k=a(m,k[0]))&&" "===k[1]&&(k=a(m,k[0])),(!k||"("!==k[1])&&h("pex",m),(k=a(m,k[0]))&&" "===k[1]&&(k=a(m,k[0])),(!k||k[1]!==n.str)&&h("sex",m),j.expr[2]=k[2],(k=a(m,k[0]))&&" "===k[1]&&(k=a(m,k[0])),(!k||")"!==k[1])&&h("epex",m)):":has"===k[2]?((k=a(m,k[0]))&&
+" "===k[1]&&(k=a(m,k[0])),(!k||"("!==k[1])&&h("pex",m),i=y(m,k[0],!0),k[0]=i[0],j.has||(j.has=[]),j.has.push(i[1])):":expr"===k[2]?(j.expr&&h("mexp",m),i=t(m,k[0]),k[0]=i[0],j.expr=i[1]):((j.pc||j.pf)&&h("mpc",m),j.pf=k[2],(i=A.exec(m.substr(k[0])))||h("mepf",m),i[5]?(j.a=2,j.b="odd"===i[5]?1:0):i[6]?(j.a=0,j.b=parseInt(i[6],10)):(j.a=parseInt((i[1]?i[1]:"+")+(i[2]?i[2]:"1"),10),j.b=i[3]?parseInt(i[3]+i[4],10):0),k[0]+=i[0].length);else break;k=a(m,i=k[0])}b===i&&h("se",m);m=[i,j];f.push(m[1]);(m=
+a(c,b=m[0]))&&" "===m[1]&&(m=a(c,b=m[0]));if(!m)break;if(">"===m[1]||"~"===m[1])"~"===m[1]&&(e.usesSiblingOp=!0),f.push(m[1]),b=m[0];else if(","===m[1])void 0===g?g=[",",f]:g.push(f),f=[],b=m[0];else if(")"===m[1]){d||h("ucp",m[1]);l=1;b=m[0];break}}d&&!l&&h("mcp",c);g&&g.push(f);var s;if(!d&&e.usesSiblingOp)if(c=g?g:f,","===c[0]){for(d=[","];s<c.length;s++)var o=x(o[s]),d=d.concat(","===o[0]?o.slice(1):o);s=d}else s=x(c);else s=g?g:f;return[b,s]},x=function(a){for(var c=[],b,d=0;d<a.length;d++)if("~"===
+a[d]){if(2>d||">"!=a[d-2])b=a.slice(0,d-1),b=b.concat([{has:[[{pc:":root"},">",a[d-1]]]},">"]),b=b.concat(a.slice(d+1)),c.push(b);if(1<d){var e=">"===a[d-2]?d-3:d-2;b=a.slice(0,e);var f={},g;for(g in a[e])a[e].hasOwnProperty(g)&&(f[g]=a[e][g]);f.has||(f.has=[]);f.has.push([{pc:":root"},">",a[d-1]]);b=b.concat(f,">",a.slice(d+1));c.push(b)}break}return d==a.length?a:1<c.length?[","].concat(c):c[0]},u=function(a){return Array.isArray?Array.isArray(a):"[object Array]"===q.call(a)},z=function(a,b,c,d,
+e){var f=[],g=">"===b[0]?b[1]:b[0],h=!0;if(g.type&&h){var h=g.type,i;null===a?i="null":(i=typeof a,"object"===i&&u(a)&&(i="array"));h=h===i}g.id&&(h=h&&g.id===c);h&&g.pf&&(":nth-last-child"===g.pf?d=e-d:d++,0===g.a?h=g.b===d:(c=(d-g.b)%g.a,h=!c&&0<=d*g.a+g.b));if(h&&g.has){d=function(){throw 42;};for(c=0;c<g.has.length;c++){try{p(g.has[c],a,d)}catch(j){if(42===j)continue}h=!1;break}}h&&g.expr&&(h=o(g.expr,a));">"!==b[0]&&":root"!==b[0].pc&&f.push(b);h&&(">"===b[0]?2<b.length&&(h=!1,f.push(b.slice(2))):
+1<b.length&&(h=!1,f.push(b.slice(1))));return[h,f]},p=function(a,b,c,d,e,f){for(var a=","===a[0]?a.slice(1):[a],g=[],h=!1,i=0,j=0,k,l,i=0;i<a.length;i++){l=z(b,a[i],d,e,f);l[0]&&(h=!0);for(j=0;j<l[1].length;j++)g.push(l[1][j])}if(g.length&&"object"===typeof b)if(1<=g.length&&g.unshift(","),u(b))for(i=0;i<b.length;i++)p(g,b[i],c,void 0,i,b.length);else for(k in b)b.hasOwnProperty(k)&&p(g,b[k],c,k);h&&c&&c(b)},r=function(a){return{sel:y(a)[1],match:function(a){var b=[];p(this.sel,a,function(a){b.push(a)});
+return b},forEach:function(a,b){return p(this.sel,a,b)}}},q=Object.prototype.toString,v={bop:"binary operator expected",ee:"expression expected",epex:"closing paren expected ')'",ijs:"invalid json string",mcp:"missing closing paren",mepf:"malformed expression in pseudo-function",mexp:"multiple expressions not allowed",mpc:"multiple pseudo classes (:xxx) not allowed",nmi:"multiple ids not allowed",pex:"opening paren expected '('",se:"selector expected",sex:"string expected",sra:"string required after '.'",
+uc:"unrecognized char",ucp:"unexpected closing paren",ujs:"unclosed json string",upc:"unrecognized pseudo class"},n={psc:1,psf:2,typ:3,str:4,ide:5},i=RegExp('^(?:([\\r\\n\\t\\ ]+)|([~*,>\\)\\(])|(string|boolean|null|array|object|number)|(:(?:root|first-child|last-child|only-child))|(:(?:nth-child|nth-last-child|has|expr|val|contains))|(:\\w+)|(?:(\\.)?(\\"(?:[^\\\\\\"]|\\\\[^\\"])*\\"))|(\\")|\\.((?:[_a-zA-Z]|[^\\0-\\0177]|\\\\[^\\r\\n\\f0-9a-fA-F])(?:[_a-zA-Z0-9\\-]|[^\\u0000-\\u0177]|(?:\\\\[^\\r\\n\\f0-9a-fA-F]))*))'),
+A=/^\s*\(\s*(?:([+\-]?)([0-9]*)n\s*(?:([+\-])\s*([0-9]))?|(odd|even)|([+\-]?[0-9]+))\s*\)/,B=RegExp('^\\s*(?:(true|false|null)|(-?\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?)|("(?:[^\\]|\\[^"])*")|(x)|(&&|\\|\\||[\\$\\^<>!\\*]=|[=+\\-*/%<>])|([\\(\\)]))'),w={"*":[9,function(a,b){return a*b}],"/":[9,function(a,b){return a/b}],"%":[9,function(a,b){return a%b}],"+":[7,function(a,b){return a+b}],"-":[7,function(a,b){return a-b}],"<=":[5,function(a,b){return e(a,"number")&&e(b,"number")&&a<=b}],">=":[5,function(a,
+b){return e(a,"number")&&e(b,"number")&&a>=b}],"$=":[5,function(a,b){return e(a,"string")&&e(b,"string")&&a.lastIndexOf(b)===a.length-b.length}],"^=":[5,function(a,b){return e(a,"string")&&e(b,"string")&&0===a.indexOf(b)}],"*=":[5,function(a,b){return e(a,"string")&&e(b,"string")&&-1!==a.indexOf(b)}],">":[5,function(a,b){return e(a,"number")&&e(b,"number")&&a>b}],"<":[5,function(a,b){return e(a,"number")&&e(b,"number")&&a<b}],"=":[3,function(a,b){return a===b}],"!=":[3,function(a,b){return a!==b}],
+"&&":[2,function(a,b){return a&&b}],"||":[1,function(a,b){return a||b}]};c._lex=a;c._parse=y;c.match=function(a,b){return r(a).match(b)};c.forEach=function(a,b,c){return r(a).forEach(b,c)};c.compile=r});return c("js-select")}();
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/select.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/select.js
new file mode 100644
index 0000000..2a5cb31
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/select.js
@@ -0,0 +1,1533 @@
+var select = (function() {
+	var require = function (file, cwd) {
+	    var resolved = require.resolve(file, cwd || '/');
+	    var mod = require.modules[resolved];
+	    if (!mod) throw new Error(
+	        'Failed to resolve module ' + file + ', tried ' + resolved
+	    );
+	    var cached = require.cache[resolved];
+	    var res = cached? cached.exports : mod();
+	    return res;
+	};
+
+	require.paths = [];
+	require.modules = {};
+	require.cache = {};
+	require.extensions = [".js",".coffee"];
+
+	require._core = {
+	    'assert': true,
+	    'events': true,
+	    'fs': true,
+	    'path': true,
+	    'vm': true
+	};
+
+	require.resolve = (function () {
+	    return function (x, cwd) {
+	        if (!cwd) cwd = '/';
+        
+	        if (require._core[x]) return x;
+	        var path = require.modules.path();
+	        cwd = path.resolve('/', cwd);
+	        var y = cwd || '/';
+        
+	        if (x.match(/^(?:\.\.?\/|\/)/)) {
+	            var m = loadAsFileSync(path.resolve(y, x))
+	                || loadAsDirectorySync(path.resolve(y, x));
+	            if (m) return m;
+	        }
+        
+	        var n = loadNodeModulesSync(x, y);
+	        if (n) return n;
+        
+	        throw new Error("Cannot find module '" + x + "'");
+        
+	        function loadAsFileSync (x) {
+	            x = path.normalize(x);
+	            if (require.modules[x]) {
+	                return x;
+	            }
+            
+	            for (var i = 0; i < require.extensions.length; i++) {
+	                var ext = require.extensions[i];
+	                if (require.modules[x + ext]) return x + ext;
+	            }
+	        }
+        
+	        function loadAsDirectorySync (x) {
+	            x = x.replace(/\/+$/, '');
+	            var pkgfile = path.normalize(x + '/package.json');
+	            if (require.modules[pkgfile]) {
+	                var pkg = require.modules[pkgfile]();
+	                var b = pkg.browserify;
+	                if (typeof b === 'object' && b.main) {
+	                    var m = loadAsFileSync(path.resolve(x, b.main));
+	                    if (m) return m;
+	                }
+	                else if (typeof b === 'string') {
+	                    var m = loadAsFileSync(path.resolve(x, b));
+	                    if (m) return m;
+	                }
+	                else if (pkg.main) {
+	                    var m = loadAsFileSync(path.resolve(x, pkg.main));
+	                    if (m) return m;
+	                }
+	            }
+            
+	            return loadAsFileSync(x + '/index');
+	        }
+        
+	        function loadNodeModulesSync (x, start) {
+	            var dirs = nodeModulesPathsSync(start);
+	            for (var i = 0; i < dirs.length; i++) {
+	                var dir = dirs[i];
+	                var m = loadAsFileSync(dir + '/' + x);
+	                if (m) return m;
+	                var n = loadAsDirectorySync(dir + '/' + x);
+	                if (n) return n;
+	            }
+            
+	            var m = loadAsFileSync(x);
+	            if (m) return m;
+	        }
+        
+	        function nodeModulesPathsSync (start) {
+	            var parts;
+	            if (start === '/') parts = [ '' ];
+	            else parts = path.normalize(start).split('/');
+            
+	            var dirs = [];
+	            for (var i = parts.length - 1; i >= 0; i--) {
+	                if (parts[i] === 'node_modules') continue;
+	                var dir = parts.slice(0, i + 1).join('/') + '/node_modules';
+	                dirs.push(dir);
+	            }
+            
+	            return dirs;
+	        }
+	    };
+	})();
+
+	require.alias = function (from, to) {
+	    var path = require.modules.path();
+	    var res = null;
+	    try {
+	        res = require.resolve(from + '/package.json', '/');
+	    }
+	    catch (err) {
+	        res = require.resolve(from, '/');
+	    }
+	    var basedir = path.dirname(res);
+    
+	    var keys = (Object.keys || function (obj) {
+	        var res = [];
+	        for (var key in obj) res.push(key);
+	        return res;
+	    })(require.modules);
+    
+	    for (var i = 0; i < keys.length; i++) {
+	        var key = keys[i];
+	        if (key.slice(0, basedir.length + 1) === basedir + '/') {
+	            var f = key.slice(basedir.length);
+	            require.modules[to + f] = require.modules[basedir + f];
+	        }
+	        else if (key === basedir) {
+	            require.modules[to] = require.modules[basedir];
+	        }
+	    }
+	};
+
+	(function () {
+	    var process = {};
+    
+	    require.define = function (filename, fn) {
+	        if (require.modules.__browserify_process) {
+	            process = require.modules.__browserify_process();
+	        }
+        
+	        var dirname = require._core[filename]
+	            ? ''
+	            : require.modules.path().dirname(filename)
+	        ;
+        
+	        var require_ = function (file) {
+	            var requiredModule = require(file, dirname);
+	            var cached = require.cache[require.resolve(file, dirname)];
+
+	            if (cached && cached.parent === null) {
+	                cached.parent = module_;
+	            }
+
+	            return requiredModule;
+	        };
+	        require_.resolve = function (name) {
+	            return require.resolve(name, dirname);
+	        };
+	        require_.modules = require.modules;
+	        require_.define = require.define;
+	        require_.cache = require.cache;
+	        var module_ = {
+	            id : filename,
+	            filename: filename,
+	            exports : {},
+	            loaded : false,
+	            parent: null
+	        };
+        
+	        require.modules[filename] = function () {
+	            require.cache[filename] = module_;
+	            fn.call(
+	                module_.exports,
+	                require_,
+	                module_,
+	                module_.exports,
+	                dirname,
+	                filename,
+	                process
+	            );
+	            module_.loaded = true;
+	            return module_.exports;
+	        };
+	    };
+	})();
+
+
+	require.define("path",function(require,module,exports,__dirname,__filename,process){function filter (xs, fn) {
+	    var res = [];
+	    for (var i = 0; i < xs.length; i++) {
+	        if (fn(xs[i], i, xs)) res.push(xs[i]);
+	    }
+	    return res;
+	}
+
+	// resolves . and .. elements in a path array with directory names there
+	// must be no slashes, empty elements, or device names (c:\) in the array
+	// (so also no leading and trailing slashes - it does not distinguish
+	// relative and absolute paths)
+	function normalizeArray(parts, allowAboveRoot) {
+	  // if the path tries to go above the root, `up` ends up > 0
+	  var up = 0;
+	  for (var i = parts.length; i >= 0; i--) {
+	    var last = parts[i];
+	    if (last == '.') {
+	      parts.splice(i, 1);
+	    } else if (last === '..') {
+	      parts.splice(i, 1);
+	      up++;
+	    } else if (up) {
+	      parts.splice(i, 1);
+	      up--;
+	    }
+	  }
+
+	  // if the path is allowed to go above the root, restore leading ..s
+	  if (allowAboveRoot) {
+	    for (; up--; up) {
+	      parts.unshift('..');
+	    }
+	  }
+
+	  return parts;
+	}
+
+	// Regex to split a filename into [*, dir, basename, ext]
+	// posix version
+	var splitPathRe = /^(.+\/(?!$)|\/)?((?:.+?)?(\.[^.]*)?)$/;
+
+	// path.resolve([from ...], to)
+	// posix version
+	exports.resolve = function() {
+	var resolvedPath = '',
+	    resolvedAbsolute = false;
+
+	for (var i = arguments.length; i >= -1 && !resolvedAbsolute; i--) {
+	  var path = (i >= 0)
+	      ? arguments[i]
+	      : process.cwd();
+
+	  // Skip empty and invalid entries
+	  if (typeof path !== 'string' || !path) {
+	    continue;
+	  }
+
+	  resolvedPath = path + '/' + resolvedPath;
+	  resolvedAbsolute = path.charAt(0) === '/';
+	}
+
+	// At this point the path should be resolved to a full absolute path, but
+	// handle relative paths to be safe (might happen when process.cwd() fails)
+
+	// Normalize the path
+	resolvedPath = normalizeArray(filter(resolvedPath.split('/'), function(p) {
+	    return !!p;
+	  }), !resolvedAbsolute).join('/');
+
+	  return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.';
+	};
+
+	// path.normalize(path)
+	// posix version
+	exports.normalize = function(path) {
+	var isAbsolute = path.charAt(0) === '/',
+	    trailingSlash = path.slice(-1) === '/';
+
+	// Normalize the path
+	path = normalizeArray(filter(path.split('/'), function(p) {
+	    return !!p;
+	  }), !isAbsolute).join('/');
+
+	  if (!path && !isAbsolute) {
+	    path = '.';
+	  }
+	  if (path && trailingSlash) {
+	    path += '/';
+	  }
+  
+	  return (isAbsolute ? '/' : '') + path;
+	};
+
+
+	// posix version
+	exports.join = function() {
+	  var paths = Array.prototype.slice.call(arguments, 0);
+	  return exports.normalize(filter(paths, function(p, index) {
+	    return p && typeof p === 'string';
+	  }).join('/'));
+	};
+
+
+	exports.dirname = function(path) {
+	  var dir = splitPathRe.exec(path)[1] || '';
+	  var isWindows = false;
+	  if (!dir) {
+	    // No dirname
+	    return '.';
+	  } else if (dir.length === 1 ||
+	      (isWindows && dir.length <= 3 && dir.charAt(1) === ':')) {
+	    // It is just a slash or a drive letter with a slash
+	    return dir;
+	  } else {
+	    // It is a full dirname, strip trailing slash
+	    return dir.substring(0, dir.length - 1);
+	  }
+	};
+
+
+	exports.basename = function(path, ext) {
+	  var f = splitPathRe.exec(path)[2] || '';
+	  // TODO: make this comparison case-insensitive on windows?
+	  if (ext && f.substr(-1 * ext.length) === ext) {
+	    f = f.substr(0, f.length - ext.length);
+	  }
+	  return f;
+	};
+
+
+	exports.extname = function(path) {
+	  return splitPathRe.exec(path)[3] || '';
+	};
+	});
+
+	require.define("__browserify_process",function(require,module,exports,__dirname,__filename,process){var process = module.exports = {};
+
+	process.nextTick = (function () {
+	    var queue = [];
+	    var canPost = typeof window !== 'undefined'
+	        && window.postMessage && window.addEventListener
+	    ;
+    
+	    if (canPost) {
+	        window.addEventListener('message', function (ev) {
+	            if (ev.source === window && ev.data === 'browserify-tick') {
+	                ev.stopPropagation();
+	                if (queue.length > 0) {
+	                    var fn = queue.shift();
+	                    fn();
+	                }
+	            }
+	        }, true);
+	    }
+    
+	    return function (fn) {
+	        if (canPost) {
+	            queue.push(fn);
+	            window.postMessage('browserify-tick', '*');
+	        }
+	        else setTimeout(fn, 0);
+	    };
+	})();
+
+	process.title = 'browser';
+	process.browser = true;
+	process.env = {};
+	process.argv = [];
+
+	process.binding = function (name) {
+	    if (name === 'evals') return (require)('vm')
+	    else throw new Error('No such module. (Possibly not yet loaded)')
+	};
+
+	(function () {
+	    var cwd = '/';
+	    var path;
+	    process.cwd = function () { return cwd };
+	    process.chdir = function (dir) {
+	        if (!path) path = require('path');
+	        cwd = path.resolve(dir, cwd);
+	    };
+	})();
+	});
+
+	require.define("vm",function(require,module,exports,__dirname,__filename,process){module.exports = require("vm-browserify")});
+
+	require.define("/node_modules/vm-browserify/package.json",function(require,module,exports,__dirname,__filename,process){module.exports = {"main":"index.js"}});
+
+	require.define("/node_modules/vm-browserify/index.js",function(require,module,exports,__dirname,__filename,process){var Object_keys = function (obj) {
+	    if (Object.keys) return Object.keys(obj)
+	    else {
+	        var res = [];
+	        for (var key in obj) res.push(key)
+	        return res;
+	    }
+	};
+
+	var forEach = function (xs, fn) {
+	    if (xs.forEach) return xs.forEach(fn)
+	    else for (var i = 0; i < xs.length; i++) {
+	        fn(xs[i], i, xs);
+	    }
+	};
+
+	var Script = exports.Script = function NodeScript (code) {
+	    if (!(this instanceof Script)) return new Script(code);
+	    this.code = code;
+	};
+
+	Script.prototype.runInNewContext = function (context) {
+	    if (!context) context = {};
+    
+	    var iframe = document.createElement('iframe');
+	    if (!iframe.style) iframe.style = {};
+	    iframe.style.display = 'none';
+    
+	    document.body.appendChild(iframe);
+    
+	    var win = iframe.contentWindow;
+    
+	    forEach(Object_keys(context), function (key) {
+	        win[key] = context[key];
+	    });
+     
+	    if (!win.eval && win.execScript) {
+	        // win.eval() magically appears when this is called in IE:
+	        win.execScript('null');
+	    }
+    
+	    var res = win.eval(this.code);
+    
+	    forEach(Object_keys(win), function (key) {
+	        context[key] = win[key];
+	    });
+    
+	    document.body.removeChild(iframe);
+    
+	    return res;
+	};
+
+	Script.prototype.runInThisContext = function () {
+	    return eval(this.code); // maybe...
+	};
+
+	Script.prototype.runInContext = function (context) {
+	    // seems to be just runInNewContext on magical context objects which are
+	    // otherwise indistinguishable from objects except plain old objects
+	    // for the parameter segfaults node
+	    return this.runInNewContext(context);
+	};
+
+	forEach(Object_keys(Script.prototype), function (name) {
+	    exports[name] = Script[name] = function (code) {
+	        var s = Script(code);
+	        return s[name].apply(s, [].slice.call(arguments, 1));
+	    };
+	});
+
+	exports.createScript = function (code) {
+	    return exports.Script(code);
+	};
+
+	exports.createContext = Script.createContext = function (context) {
+	    // not really sure what this one does
+	    // seems to just make a shallow copy
+	    var copy = {};
+	    if(typeof context === 'object') {
+	        forEach(Object_keys(context), function (key) {
+	            copy[key] = context[key];
+	        });
+	    }
+	    return copy;
+	};
+	});
+
+	require.define("/package.json",function(require,module,exports,__dirname,__filename,process){module.exports = {"main":"./index"}});
+
+	require.define("js-select",function(require,module,exports,__dirname,__filename,process){var traverse = require("traverse"),
+	    JSONSelect = require("JSONSelect");
+
+	module.exports = function(obj, string) {
+	   var sels = parseSelectors(string);
+
+	   return {
+	      nodes: function() {
+	         var nodes = [];
+	         this.forEach(function(node) {
+	            nodes.push(node);
+	         });
+	         return nodes;
+	      },
+      
+	      update: function(cb) {
+	         this.forEach(function(node) {
+	            this.update(typeof cb == "function" ? cb(node) : cb);
+	         });
+	      },
+      
+	      remove: function() {
+	         this.forEach(function(node) {
+	            this.remove();
+	         })
+	      },
+      
+	      condense: function() {
+	         traverse(obj).forEach(function(node) {
+	            if (!this.parent) return;
+            
+	            if (this.parent.keep) {
+	               this.keep = true;
+	            } else {
+	               var match = matchesAny(sels, this);
+	               this.keep = match;
+	               if (!match) {
+	                  if (this.isLeaf) {
+	                     this.remove();
+	                  } else {
+	                     this.after(function() {
+	                        if (this.keep_child) {
+	                           this.parent.keep_child = true;
+	                        }
+	                        if (!this.keep && !this.keep_child) {
+	                           this.remove();
+	                        }
+	                     });
+	                  }
+	               } else {
+	                  this.parent.keep_child = true;
+	               }
+	            }
+	         });
+	      },
+      
+	      forEach: function(cb) {
+	         traverse(obj).forEach(function(node) {
+	            if (matchesAny(sels, this)) {
+	               this.matches = function(string) {
+	                  return matchesAny(parseSelectors(string), this);
+	               };
+	               // inherit context from js-traverse
+	               cb.call(this, node);            
+	            }
+	         });
+	      }
+	   };
+	}
+
+	function parseSelectors(string) {
+	   var parsed = JSONSelect._parse(string || "*")[1];
+	   return getSelectors(parsed);
+	}
+
+	function getSelectors(parsed) {
+	   if (parsed[0] == ",") {  // "selector1, selector2"
+	      return parsed.slice(1);
+	   }
+	   return [parsed];
+	}
+
+	function matchesAny(sels, context) {
+	   for (var i = 0; i < sels.length; i++) {
+	      if (matches(sels[i], context)) {
+	         return true;
+	      }
+	   }
+	   return false;
+	}
+
+	function matches(sel, context) {
+	   var path = context.parents.concat([context]),
+	       i = path.length - 1,
+	       j = sel.length - 1;
+
+	   // walk up the ancestors
+	   var must = true;
+	   while(j >= 0 && i >= 0) {
+	      var part = sel[j],
+	          context = path[i];
+
+	      if (part == ">") {
+	         j--;
+	         must = true;
+	         continue;
+	      }
+
+	      if (matchesKey(part, context)) {
+	         j--;
+	      }
+	      else if (must) {
+	         return false;
+	      }
+
+	      i--;
+	      must = false;
+	   }
+	   return j == -1;
+	}
+
+	function matchesKey(part, context) {
+	   var key = context.key,
+	       node = context.node,
+	       parent = context.parent;
+
+	   if (part.id && key != part.id) {
+	      return false;
+	   }
+	   if (part.type) {
+	      var type = part.type;
+
+	      if (type == "null" && node !== null) {
+	         return false;
+	      }
+	      else if (type == "array" && !isArray(node)) {
+	         return false;
+	      }
+	      else if (type == "object" && (typeof node != "object"
+	                 || node === null || isArray(node))) {
+	         return false;
+	      }
+	      else if ((type == "boolean" || type == "string" || type == "number")
+	               && type != typeof node) {
+	         return false;
+	      }
+	   }
+	   if (part.pf == ":nth-child") {
+	      var index = parseInt(key) + 1;
+	      if ((part.a == 0 && index !== part.b)         // :nth-child(i)
+	        || (part.a == 1 && !(index >= -part.b))     // :nth-child(n)
+	        || (part.a == -1 && !(index <= part.b))     // :nth-child(-n + 1)
+	        || (part.a == 2 && index % 2 != part.b)) {  // :nth-child(even)
+	         return false;
+	      }
+	   }
+	   if (part.pf == ":nth-last-child"
+	      && (!parent || key != parent.node.length - part.b)) {
+	         return false;
+	   }
+	   if (part.pc == ":only-child"
+	      && (!parent || parent.node.length != 1)) {
+	         return false;
+	   }
+	   if (part.pc == ":root" && key !== undefined) {
+	      return false;
+	   }
+	   if (part.has) {
+	      var sels = getSelectors(part.has[0]),
+	          match = false;
+	      traverse(node).forEach(function(child) {
+	         if (matchesAny(sels, this)) {
+	            match = true;
+	         }
+	      });
+	      if (!match) {
+	         return false;
+	      }
+	   }
+	   if (part.expr) {
+	      var expr = part.expr, lhs = expr[0], op = expr[1], rhs = expr[2];
+	      if (typeof node != "string"
+	          || (!lhs && op == "=" && node != rhs)   // :val("str")
+	          || (!lhs && op == "*=" && node.indexOf(rhs) == -1)) { // :contains("substr")
+	         return false;
+	      }
+	   }
+	   return true;
+	}
+
+	var isArray = Array.isArray || function(obj) {
+	    return toString.call(obj) === '[object Array]';
+	}
+	});
+
+	require.define("/node_modules/traverse/package.json",function(require,module,exports,__dirname,__filename,process){module.exports = {"main":"./index"}});
+
+	require.define("/node_modules/traverse/index.js",function(require,module,exports,__dirname,__filename,process){module.exports = Traverse;
+	function Traverse (obj) {
+	    if (!(this instanceof Traverse)) return new Traverse(obj);
+	    this.value = obj;
+	}
+
+	Traverse.prototype.get = function (ps) {
+	    var node = this.value;
+	    for (var i = 0; i < ps.length; i ++) {
+	        var key = ps[i];
+	        if (!Object.hasOwnProperty.call(node, key)) {
+	            node = undefined;
+	            break;
+	        }
+	        node = node[key];
+	    }
+	    return node;
+	};
+
+	Traverse.prototype.set = function (ps, value) {
+	    var node = this.value;
+	    for (var i = 0; i < ps.length - 1; i ++) {
+	        var key = ps[i];
+	        if (!Object.hasOwnProperty.call(node, key)) node[key] = {};
+	        node = node[key];
+	    }
+	    node[ps[i]] = value;
+	    return value;
+	};
+
+	Traverse.prototype.map = function (cb) {
+	    return walk(this.value, cb, true);
+	};
+
+	Traverse.prototype.forEach = function (cb) {
+	    this.value = walk(this.value, cb, false);
+	    return this.value;
+	};
+
+	Traverse.prototype.reduce = function (cb, init) {
+	    var skip = arguments.length === 1;
+	    var acc = skip ? this.value : init;
+	    this.forEach(function (x) {
+	        if (!this.isRoot || !skip) {
+	            acc = cb.call(this, acc, x);
+	        }
+	    });
+	    return acc;
+	};
+
+	Traverse.prototype.deepEqual = function (obj) {
+	    if (arguments.length !== 1) {
+	        throw new Error(
+	            'deepEqual requires exactly one object to compare against'
+	        );
+	    }
+    
+	    var equal = true;
+	    var node = obj;
+    
+	    this.forEach(function (y) {
+	        var notEqual = (function () {
+	            equal = false;
+	            //this.stop();
+	            return undefined;
+	        }).bind(this);
+        
+	        //if (node === undefined || node === null) return notEqual();
+        
+	        if (!this.isRoot) {
+	        /*
+	            if (!Object.hasOwnProperty.call(node, this.key)) {
+	                return notEqual();
+	            }
+	        */
+	            if (typeof node !== 'object') return notEqual();
+	            node = node[this.key];
+	        }
+        
+	        var x = node;
+        
+	        this.post(function () {
+	            node = x;
+	        });
+        
+	        var toS = function (o) {
+	            return Object.prototype.toString.call(o);
+	        };
+        
+	        if (this.circular) {
+	            if (Traverse(obj).get(this.circular.path) !== x) notEqual();
+	        }
+	        else if (typeof x !== typeof y) {
+	            notEqual();
+	        }
+	        else if (x === null || y === null || x === undefined || y === undefined) {
+	            if (x !== y) notEqual();
+	        }
+	        else if (x.__proto__ !== y.__proto__) {
+	            notEqual();
+	        }
+	        else if (x === y) {
+	            // nop
+	        }
+	        else if (typeof x === 'function') {
+	            if (x instanceof RegExp) {
+	                // both regexps on account of the __proto__ check
+	                if (x.toString() != y.toString()) notEqual();
+	            }
+	            else if (x !== y) notEqual();
+	        }
+	        else if (typeof x === 'object') {
+	            if (toS(y) === '[object Arguments]'
+	            || toS(x) === '[object Arguments]') {
+	                if (toS(x) !== toS(y)) {
+	                    notEqual();
+	                }
+	            }
+	            else if (x instanceof Date || y instanceof Date) {
+	                if (!(x instanceof Date) || !(y instanceof Date)
+	                || x.getTime() !== y.getTime()) {
+	                    notEqual();
+	                }
+	            }
+	            else {
+	                var kx = Object.keys(x);
+	                var ky = Object.keys(y);
+	                if (kx.length !== ky.length) return notEqual();
+	                for (var i = 0; i < kx.length; i++) {
+	                    var k = kx[i];
+	                    if (!Object.hasOwnProperty.call(y, k)) {
+	                        notEqual();
+	                    }
+	                }
+	            }
+	        }
+	    });
+    
+	    return equal;
+	};
+
+	Traverse.prototype.paths = function () {
+	    var acc = [];
+	    this.forEach(function (x) {
+	        acc.push(this.path); 
+	    });
+	    return acc;
+	};
+
+	Traverse.prototype.nodes = function () {
+	    var acc = [];
+	    this.forEach(function (x) {
+	        acc.push(this.node);
+	    });
+	    return acc;
+	};
+
+	Traverse.prototype.clone = function () {
+	    var parents = [], nodes = [];
+    
+	    return (function clone (src) {
+	        for (var i = 0; i < parents.length; i++) {
+	            if (parents[i] === src) {
+	                return nodes[i];
+	            }
+	        }
+        
+	        if (typeof src === 'object' && src !== null) {
+	            var dst = copy(src);
+            
+	            parents.push(src);
+	            nodes.push(dst);
+            
+	            Object.keys(src).forEach(function (key) {
+	                dst[key] = clone(src[key]);
+	            });
+            
+	            parents.pop();
+	            nodes.pop();
+	            return dst;
+	        }
+	        else {
+	            return src;
+	        }
+	    })(this.value);
+	};
+
+	function walk (root, cb, immutable) {
+	    var path = [];
+	    var parents = [];
+	    var alive = true;
+    
+	    return (function walker (node_) {
+	        var node = immutable ? copy(node_) : node_;
+	        var modifiers = {};
+        
+	        var keepGoing = true;
+        
+	        var state = {
+	            node : node,
+	            node_ : node_,
+	            path : [].concat(path),
+	            parent : parents[parents.length - 1],
+	            parents : parents,
+	            key : path.slice(-1)[0],
+	            isRoot : path.length === 0,
+	            level : path.length,
+	            circular : null,
+	            update : function (x, stopHere) {
+	                if (!state.isRoot) {
+	                    state.parent.node[state.key] = x;
+	                }
+	                state.node = x;
+	                if (stopHere) keepGoing = false;
+	            },
+	            'delete' : function () {
+	                delete state.parent.node[state.key];
+	            },
+	            remove : function () {
+	                if (Array.isArray(state.parent.node)) {
+	                    state.parent.node.splice(state.key, 1);
+	                }
+	                else {
+	                    delete state.parent.node[state.key];
+	                }
+	            },
+	            keys : null,
+	            before : function (f) { modifiers.before = f },
+	            after : function (f) { modifiers.after = f },
+	            pre : function (f) { modifiers.pre = f },
+	            post : function (f) { modifiers.post = f },
+	            stop : function () { alive = false },
+	            block : function () { keepGoing = false }
+	        };
+        
+	        if (!alive) return state;
+        
+	        if (typeof node === 'object' && node !== null) {
+	            state.keys = Object.keys(node);
+            
+	            state.isLeaf = state.keys.length == 0;
+            
+	            for (var i = 0; i < parents.length; i++) {
+	                if (parents[i].node_ === node_) {
+	                    state.circular = parents[i];
+	                    break;
+	                }
+	            }
+	        }
+	        else {
+	            state.isLeaf = true;
+	        }
+        
+	        state.notLeaf = !state.isLeaf;
+	        state.notRoot = !state.isRoot;
+        
+	        // use return values to update if defined
+	        var ret = cb.call(state, state.node);
+	        if (ret !== undefined && state.update) state.update(ret);
+        
+	        if (modifiers.before) modifiers.before.call(state, state.node);
+        
+	        if (!keepGoing) return state;
+        
+	        if (typeof state.node == 'object'
+	        && state.node !== null && !state.circular) {
+	            parents.push(state);
+            
+	            state.keys.forEach(function (key, i) {
+	                path.push(key);
+                
+	                if (modifiers.pre) modifiers.pre.call(state, state.node[key], key);
+                
+	                var child = walker(state.node[key]);
+	                if (immutable && Object.hasOwnProperty.call(state.node, key)) {
+	                    state.node[key] = child.node;
+	                }
+                
+	                child.isLast = i == state.keys.length - 1;
+	                child.isFirst = i == 0;
+                
+	                if (modifiers.post) modifiers.post.call(state, child);
+                
+	                path.pop();
+	            });
+	            parents.pop();
+	        }
+        
+	        if (modifiers.after) modifiers.after.call(state, state.node);
+        
+	        return state;
+	    })(root).node;
+	}
+
+	Object.keys(Traverse.prototype).forEach(function (key) {
+	    Traverse[key] = function (obj) {
+	        var args = [].slice.call(arguments, 1);
+	        var t = Traverse(obj);
+	        return t[key].apply(t, args);
+	    };
+	});
+
+	function copy (src) {
+	    if (typeof src === 'object' && src !== null) {
+	        var dst;
+        
+	        if (Array.isArray(src)) {
+	            dst = [];
+	        }
+	        else if (src instanceof Date) {
+	            dst = new Date(src);
+	        }
+	        else if (src instanceof Boolean) {
+	            dst = new Boolean(src);
+	        }
+	        else if (src instanceof Number) {
+	            dst = new Number(src);
+	        }
+	        else if (src instanceof String) {
+	            dst = new String(src);
+	        }
+	        else {
+	            dst = Object.create(Object.getPrototypeOf(src));
+	        }
+        
+	        Object.keys(src).forEach(function (key) {
+	            dst[key] = src[key];
+	        });
+	        return dst;
+	    }
+	    else return src;
+	}
+	});
+
+	require.define("/node_modules/JSONSelect/package.json",function(require,module,exports,__dirname,__filename,process){module.exports = {"main":"src/jsonselect"}});
+
+	require.define("/node_modules/JSONSelect/src/jsonselect.js",function(require,module,exports,__dirname,__filename,process){/*! Copyright (c) 2011, Lloyd Hilaiel, ISC License */
+	/*
+	 * This is the JSONSelect reference implementation, in javascript.
+	 */
+	(function(exports) {
+
+	    var // localize references
+	    toString = Object.prototype.toString;
+
+	    function jsonParse(str) {
+	      try {
+	          if(JSON && JSON.parse){
+	              return JSON.parse(str);
+	          }
+	          return (new Function("return " + str))();
+	      } catch(e) {
+	        te("ijs", e.message);
+	      }
+	    }
+
+	    // emitted error codes.
+	    var errorCodes = {
+	        "bop":  "binary operator expected",
+	        "ee":   "expression expected",
+	        "epex": "closing paren expected ')'",
+	        "ijs":  "invalid json string",
+	        "mcp":  "missing closing paren",
+	        "mepf": "malformed expression in pseudo-function",
+	        "mexp": "multiple expressions not allowed",
+	        "mpc":  "multiple pseudo classes (:xxx) not allowed",
+	        "nmi":  "multiple ids not allowed",
+	        "pex":  "opening paren expected '('",
+	        "se":   "selector expected",
+	        "sex":  "string expected",
+	        "sra":  "string required after '.'",
+	        "uc":   "unrecognized char",
+	        "ucp":  "unexpected closing paren",
+	        "ujs":  "unclosed json string",
+	        "upc":  "unrecognized pseudo class"
+	    };
+
+	    // throw an error message
+	    function te(ec, context) {
+	      throw new Error(errorCodes[ec] + ( context && " in '" + context + "'"));
+	    }
+
+	    // THE LEXER
+	    var toks = {
+	        psc: 1, // pseudo class
+	        psf: 2, // pseudo class function
+	        typ: 3, // type
+	        str: 4, // string
+	        ide: 5  // identifiers (or "classes", stuff after a dot)
+	    };
+
+	    // The primary lexing regular expression in jsonselect
+	    var pat = new RegExp(
+	        "^(?:" +
+	        // (1) whitespace
+	        "([\\r\\n\\t\\ ]+)|" +
+	        // (2) one-char ops
+	        "([~*,>\\)\\(])|" +
+	        // (3) types names
+	        "(string|boolean|null|array|object|number)|" +
+	        // (4) pseudo classes
+	        "(:(?:root|first-child|last-child|only-child))|" +
+	        // (5) pseudo functions
+	        "(:(?:nth-child|nth-last-child|has|expr|val|contains))|" +
+	        // (6) bogusly named pseudo something or others
+	        "(:\\w+)|" +
+	        // (7 & 8) identifiers and JSON strings
+	        "(?:(\\.)?(\\\"(?:[^\\\\\\\"]|\\\\[^\\\"])*\\\"))|" +
+	        // (8) bogus JSON strings missing a trailing quote
+	        "(\\\")|" +
+	        // (9) identifiers (unquoted)
+	        "\\.((?:[_a-zA-Z]|[^\\0-\\0177]|\\\\[^\\r\\n\\f0-9a-fA-F])(?:[_a-zA-Z0-9\\-]|[^\\u0000-\\u0177]|(?:\\\\[^\\r\\n\\f0-9a-fA-F]))*)" +
+	        ")"
+	    );
+
+	    // A regular expression for matching "nth expressions" (see grammar, what :nth-child() eats)
+	    var nthPat = /^\s*\(\s*(?:([+\-]?)([0-9]*)n\s*(?:([+\-])\s*([0-9]))?|(odd|even)|([+\-]?[0-9]+))\s*\)/;
+	    function lex(str, off) {
+	        if (!off) off = 0;
+	        var m = pat.exec(str.substr(off));
+	        if (!m) return undefined;
+	        off+=m[0].length;
+	        var a;
+	        if (m[1]) a = [off, " "];
+	        else if (m[2]) a = [off, m[0]];
+	        else if (m[3]) a = [off, toks.typ, m[0]];
+	        else if (m[4]) a = [off, toks.psc, m[0]];
+	        else if (m[5]) a = [off, toks.psf, m[0]];
+	        else if (m[6]) te("upc", str);
+	        else if (m[8]) a = [off, m[7] ? toks.ide : toks.str, jsonParse(m[8])];
+	        else if (m[9]) te("ujs", str);
+	        else if (m[10]) a = [off, toks.ide, m[10].replace(/\\([^\r\n\f0-9a-fA-F])/g,"$1")];
+	        return a;
+	    }
+
+	    // THE EXPRESSION SUBSYSTEM
+
+	    var exprPat = new RegExp(
+	            // skip and don't capture leading whitespace
+	            "^\\s*(?:" +
+	            // (1) simple vals
+	            "(true|false|null)|" + 
+	            // (2) numbers
+	            "(-?\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?)|" +
+	            // (3) strings
+	            "(\"(?:[^\\]|\\[^\"])*\")|" +
+	            // (4) the 'x' value placeholder
+	            "(x)|" +
+	            // (5) binops
+	            "(&&|\\|\\||[\\$\\^<>!\\*]=|[=+\\-*/%<>])|" +
+	            // (6) parens
+	            "([\\(\\)])" +
+	            ")"
+	    );
+
+	    function is(o, t) { return typeof o === t; }
+	    var operators = {
+	        '*':  [ 9, function(lhs, rhs) { return lhs * rhs; } ],
+	        '/':  [ 9, function(lhs, rhs) { return lhs / rhs; } ],
+	        '%':  [ 9, function(lhs, rhs) { return lhs % rhs; } ],
+	        '+':  [ 7, function(lhs, rhs) { return lhs + rhs; } ],
+	        '-':  [ 7, function(lhs, rhs) { return lhs - rhs; } ],
+	        '<=': [ 5, function(lhs, rhs) { return is(lhs, 'number') && is(rhs, 'number') && lhs <= rhs; } ],
+	        '>=': [ 5, function(lhs, rhs) { return is(lhs, 'number') && is(rhs, 'number') && lhs >= rhs; } ],
+	        '$=': [ 5, function(lhs, rhs) { return is(lhs, 'string') && is(rhs, 'string') && lhs.lastIndexOf(rhs) === lhs.length - rhs.length; } ],
+	        '^=': [ 5, function(lhs, rhs) { return is(lhs, 'string') && is(rhs, 'string') && lhs.indexOf(rhs) === 0; } ],
+	        '*=': [ 5, function(lhs, rhs) { return is(lhs, 'string') && is(rhs, 'string') && lhs.indexOf(rhs) !== -1; } ],
+	        '>':  [ 5, function(lhs, rhs) { return is(lhs, 'number') && is(rhs, 'number') && lhs > rhs; } ],
+	        '<':  [ 5, function(lhs, rhs) { return is(lhs, 'number') && is(rhs, 'number') && lhs < rhs; } ],
+	        '=':  [ 3, function(lhs, rhs) { return lhs === rhs; } ],
+	        '!=': [ 3, function(lhs, rhs) { return lhs !== rhs; } ],
+	        '&&': [ 2, function(lhs, rhs) { return lhs && rhs; } ],
+	        '||': [ 1, function(lhs, rhs) { return lhs || rhs; } ]
+	    };
+
+	    function exprLex(str, off) {
+	        var v, m = exprPat.exec(str.substr(off));
+	        if (m) {
+	            off += m[0].length;
+	            v = m[1] || m[2] || m[3] || m[5] || m[6];
+	            if (m[1] || m[2] || m[3]) return [off, 0, jsonParse(v)];
+	            else if (m[4]) return [off, 0, undefined];
+	            return [off, v];
+	        }
+	    }
+
+	    function exprParse2(str, off) {
+	        if (!off) off = 0;
+	        // first we expect a value or a '('
+	        var l = exprLex(str, off),
+	            lhs;
+	        if (l && l[1] === '(') {
+	            lhs = exprParse2(str, l[0]);
+	            var p = exprLex(str, lhs[0]);
+	            if (!p || p[1] !== ')') te('epex', str);
+	            off = p[0];
+	            lhs = [ '(', lhs[1] ];
+	        } else if (!l || (l[1] && l[1] != 'x')) {
+	            te("ee", str + " - " + ( l[1] && l[1] ));
+	        } else {
+	            lhs = ((l[1] === 'x') ? undefined : l[2]);
+	            off = l[0];
+	        }
+
+	        // now we expect a binary operator or a ')'
+	        var op = exprLex(str, off);
+	        if (!op || op[1] == ')') return [off, lhs];
+	        else if (op[1] == 'x' || !op[1]) {
+	            te('bop', str + " - " + ( op[1] && op[1] ));
+	        }
+
+	        // tail recursion to fetch the rhs expression
+	        var rhs = exprParse2(str, op[0]);
+	        off = rhs[0];
+	        rhs = rhs[1];
+
+	        // and now precedence!  how shall we put everything together?
+	        var v;
+	        if (typeof rhs !== 'object' || rhs[0] === '(' || operators[op[1]][0] < operators[rhs[1]][0] ) {
+	            v = [lhs, op[1], rhs];
+	        }
+	        else {
+	            v = rhs;
+	            while (typeof rhs[0] === 'object' && rhs[0][0] != '(' && operators[op[1]][0] >= operators[rhs[0][1]][0]) {
+	                rhs = rhs[0];
+	            }
+	            rhs[0] = [lhs, op[1], rhs[0]];
+	        }
+	        return [off, v];
+	    }
+
+	    function exprParse(str, off) {
+	        function deparen(v) {
+	            if (typeof v !== 'object' || v === null) return v;
+	            else if (v[0] === '(') return deparen(v[1]);
+	            else return [deparen(v[0]), v[1], deparen(v[2])];
+	        }
+	        var e = exprParse2(str, off ? off : 0);
+	        return [e[0], deparen(e[1])];
+	    }
+
+	    function exprEval(expr, x) {
+	        if (expr === undefined) return x;
+	        else if (expr === null || typeof expr !== 'object') {
+	            return expr;
+	        }
+	        var lhs = exprEval(expr[0], x),
+	            rhs = exprEval(expr[2], x);
+	        return operators[expr[1]][1](lhs, rhs);
+	    }
+
+	    // THE PARSER
+
+	    function parse(str, off, nested, hints) {
+	        if (!nested) hints = {};
+
+	        var a = [], am, readParen;
+	        if (!off) off = 0; 
+
+	        while (true) {
+	            var s = parse_selector(str, off, hints);
+	            a.push(s[1]);
+	            s = lex(str, off = s[0]);
+	            if (s && s[1] === " ") s = lex(str, off = s[0]);
+	            if (!s) break;
+	            // now we've parsed a selector, and have something else...
+	            if (s[1] === ">" || s[1] === "~") {
+	                if (s[1] === "~") hints.usesSiblingOp = true;
+	                a.push(s[1]);
+	                off = s[0];
+	            } else if (s[1] === ",") {
+	                if (am === undefined) am = [ ",", a ];
+	                else am.push(a);
+	                a = [];
+	                off = s[0];
+	            } else if (s[1] === ")") {
+	                if (!nested) te("ucp", s[1]);
+	                readParen = 1;
+	                off = s[0];
+	                break;
+	            }
+	        }
+	        if (nested && !readParen) te("mcp", str);
+	        if (am) am.push(a);
+	        var rv;
+	        if (!nested && hints.usesSiblingOp) {
+	            rv = normalize(am ? am : a);
+	        } else {
+	            rv = am ? am : a;
+	        }
+	        return [off, rv];
+	    }
+
+	    function normalizeOne(sel) {
+	        var sels = [], s;
+	        for (var i = 0; i < sel.length; i++) {
+	            if (sel[i] === '~') {
+	                // `A ~ B` maps to `:has(:root > A) > B`
+	                // `Z A ~ B` maps to `Z :has(:root > A) > B, Z:has(:root > A) > B`
+	                // This first clause, takes care of the first case, and the first half of the latter case.
+	                if (i < 2 || sel[i-2] != '>') {
+	                    s = sel.slice(0,i-1);
+	                    s = s.concat([{has:[[{pc: ":root"}, ">", sel[i-1]]]}, ">"]);
+	                    s = s.concat(sel.slice(i+1));
+	                    sels.push(s);
+	                }
+	                // here we take care of the second half of above:
+	                // (`Z A ~ B` maps to `Z :has(:root > A) > B, Z :has(:root > A) > B`)
+	                // and a new case:
+	                // Z > A ~ B maps to Z:has(:root > A) > B
+	                if (i > 1) {
+	                    var at = sel[i-2] === '>' ? i-3 : i-2;
+	                    s = sel.slice(0,at);
+	                    var z = {};
+	                    for (var k in sel[at]) if (sel[at].hasOwnProperty(k)) z[k] = sel[at][k];
+	                    if (!z.has) z.has = [];
+	                    z.has.push([{pc: ":root"}, ">", sel[i-1]]);
+	                    s = s.concat(z, '>', sel.slice(i+1));
+	                    sels.push(s);
+	                }
+	                break;
+	            }
+	        }
+	        if (i == sel.length) return sel;
+	        return sels.length > 1 ? [','].concat(sels) : sels[0];
+	    }
+
+	    function normalize(sels) {
+	        if (sels[0] === ',') {
+	            var r = [","];
+	            for (var i = i; i < sels.length; i++) {
+	                var s = normalizeOne(s[i]);
+	                r = r.concat(s[0] === "," ? s.slice(1) : s);
+	            }
+	            return r;
+	        } else {
+	            return normalizeOne(sels);
+	        }
+	    }
+
+	    function parse_selector(str, off, hints) {
+	        var soff = off;
+	        var s = { };
+	        var l = lex(str, off);
+	        // skip space
+	        if (l && l[1] === " ") { soff = off = l[0]; l = lex(str, off); }
+	        if (l && l[1] === toks.typ) {
+	            s.type = l[2];
+	            l = lex(str, (off = l[0]));
+	        } else if (l && l[1] === "*") {
+	            // don't bother representing the universal sel, '*' in the
+	            // parse tree, cause it's the default
+	            l = lex(str, (off = l[0]));
+	        }
+
+	        // now support either an id or a pc
+	        while (true) {
+	            if (l === undefined) {
+	                break;
+	            } else if (l[1] === toks.ide) {
+	                if (s.id) te("nmi", l[1]);
+	                s.id = l[2];
+	            } else if (l[1] === toks.psc) {
+	                if (s.pc || s.pf) te("mpc", l[1]);
+	                // collapse first-child and last-child into nth-child expressions
+	                if (l[2] === ":first-child") {
+	                    s.pf = ":nth-child";
+	                    s.a = 0;
+	                    s.b = 1;
+	                } else if (l[2] === ":last-child") {
+	                    s.pf = ":nth-last-child";
+	                    s.a = 0;
+	                    s.b = 1;
+	                } else {
+	                    s.pc = l[2];
+	                }
+	            } else if (l[1] === toks.psf) {
+	                if (l[2] === ":val" || l[2] === ":contains") {
+	                    s.expr = [ undefined, l[2] === ":val" ? "=" : "*=", undefined];
+	                    // any amount of whitespace, followed by paren, string, paren
+	                    l = lex(str, (off = l[0]));
+	                    if (l && l[1] === " ") l = lex(str, off = l[0]);
+	                    if (!l || l[1] !== "(") te("pex", str);
+	                    l = lex(str, (off = l[0]));
+	                    if (l && l[1] === " ") l = lex(str, off = l[0]);
+	                    if (!l || l[1] !== toks.str) te("sex", str);
+	                    s.expr[2] = l[2];
+	                    l = lex(str, (off = l[0]));
+	                    if (l && l[1] === " ") l = lex(str, off = l[0]);
+	                    if (!l || l[1] !== ")") te("epex", str);
+	                } else if (l[2] === ":has") {
+	                    // any amount of whitespace, followed by paren
+	                    l = lex(str, (off = l[0]));
+	                    if (l && l[1] === " ") l = lex(str, off = l[0]);
+	                    if (!l || l[1] !== "(") te("pex", str);
+	                    var h = parse(str, l[0], true);
+	                    l[0] = h[0];
+	                    if (!s.has) s.has = [];
+	                    s.has.push(h[1]);
+	                } else if (l[2] === ":expr") {
+	                    if (s.expr) te("mexp", str);
+	                    var e = exprParse(str, l[0]);
+	                    l[0] = e[0];
+	                    s.expr = e[1];
+	                } else {
+	                    if (s.pc || s.pf ) te("mpc", str);
+	                    s.pf = l[2];
+	                    var m = nthPat.exec(str.substr(l[0]));
+	                    if (!m) te("mepf", str);
+	                    if (m[5]) {
+	                        s.a = 2;
+	                        s.b = (m[5] === "odd") ? 1 : 0;
+	                    } else if (m[6]) {
+	                        s.a = 0;
+	                        s.b = parseInt(m[6], 10);
+	                    } else {
+	                        s.a = parseInt((m[1] ? m[1] : "+") + (m[2] ? m[2] : "1"),10);
+	                        s.b = m[3] ? parseInt(m[3] + m[4],10) : 0;
+	                    }
+	                    l[0] += m[0].length;
+	                }
+	            } else {
+	                break;
+	            }
+	            l = lex(str, (off = l[0]));
+	        }
+
+	        // now if we didn't actually parse anything it's an error
+	        if (soff === off) te("se", str);
+
+	        return [off, s];
+	    }
+
+	    // THE EVALUATOR
+
+	    function isArray(o) {
+	        return Array.isArray ? Array.isArray(o) : 
+	          toString.call(o) === "[object Array]";
+	    }
+
+	    function mytypeof(o) {
+	        if (o === null) return "null";
+	        var to = typeof o;
+	        if (to === "object" && isArray(o)) to = "array";
+	        return to;
+	    }
+
+	    function mn(node, sel, id, num, tot) {
+	        var sels = [];
+	        var cs = (sel[0] === ">") ? sel[1] : sel[0];
+	        var m = true, mod;
+	        if (cs.type) m = m && (cs.type === mytypeof(node));
+	        if (cs.id)   m = m && (cs.id === id);
+	        if (m && cs.pf) {
+	            if (cs.pf === ":nth-last-child") num = tot - num;
+	            else num++;
+	            if (cs.a === 0) {
+	                m = cs.b === num;
+	            } else {
+	                mod = ((num - cs.b) % cs.a);
+
+	                m = (!mod && ((num*cs.a + cs.b) >= 0));
+	            }
+	        }
+	        if (m && cs.has) {
+	            // perhaps we should augment forEach to handle a return value
+	            // that indicates "client cancels traversal"?
+	            var bail = function() { throw 42; };
+	            for (var i = 0; i < cs.has.length; i++) {
+	                try {
+	                    forEach(cs.has[i], node, bail);
+	                } catch (e) {
+	                    if (e === 42) continue;
+	                }
+	                m = false;
+	                break;
+	            }
+	        }
+	        if (m && cs.expr) {
+	            m = exprEval(cs.expr, node);
+	        }
+	        // should we repeat this selector for descendants?
+	        if (sel[0] !== ">" && sel[0].pc !== ":root") sels.push(sel);
+
+	        if (m) {
+	            // is there a fragment that we should pass down?
+	            if (sel[0] === ">") { if (sel.length > 2) { m = false; sels.push(sel.slice(2)); } }
+	            else if (sel.length > 1) { m = false; sels.push(sel.slice(1)); }
+	        }
+
+	        return [m, sels];
+	    }
+
+	    function forEach(sel, obj, fun, id, num, tot) {
+	        var a = (sel[0] === ",") ? sel.slice(1) : [sel],
+	        a0 = [],
+	        call = false,
+	        i = 0, j = 0, k, x;
+	        for (i = 0; i < a.length; i++) {
+	            x = mn(obj, a[i], id, num, tot);
+	            if (x[0]) {
+	                call = true;
+	            }
+	            for (j = 0; j < x[1].length; j++) {
+	                a0.push(x[1][j]);
+	            }
+	        }
+	        if (a0.length && typeof obj === "object") {
+	            if (a0.length >= 1) {
+	                a0.unshift(",");
+	            }
+	            if (isArray(obj)) {
+	                for (i = 0; i < obj.length; i++) {
+	                    forEach(a0, obj[i], fun, undefined, i, obj.length);
+	                }
+	            } else {
+	                for (k in obj) {
+	                    if (obj.hasOwnProperty(k)) {
+	                        forEach(a0, obj[k], fun, k);
+	                    }
+	                }
+	            }
+	        }
+	        if (call && fun) {
+	            fun(obj);
+	        }
+	    }
+
+	    function match(sel, obj) {
+	        var a = [];
+	        forEach(sel, obj, function(x) {
+	            a.push(x);
+	        });
+	        return a;
+	    }
+
+	    function compile(sel) {
+	        return {
+	            sel: parse(sel)[1],
+	            match: function(obj){
+	                return match(this.sel, obj);
+	            },
+	            forEach: function(obj, fun) {
+	                return forEach(this.sel, obj, fun);
+	            }
+	        };
+	    }
+
+	    exports._lex = lex;
+	    exports._parse = parse;
+	    exports.match = function (sel, obj) {
+	        return compile(sel).match(obj);
+	    };
+	    exports.forEach = function(sel, obj, fun) {
+	        return compile(sel).forEach(obj, fun);
+	    };
+	    exports.compile = compile;
+	})(typeof exports === "undefined" ? (window.JSONSelect = {}) : exports);
+	});
+	
+	return require('js-select');
+})();
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/test/runner.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/test/runner.js
new file mode 100644
index 0000000..9d90ac0
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/test/runner.js
@@ -0,0 +1,58 @@
+/* node runner for the JSONSelect conformance tests
+   https://github.com/lloyd/JSONSelectTests */
+
+var assert = require("assert"),
+    fs = require("fs"),
+    path = require("path"),
+    colors = require("colors"),
+    traverse = require("traverse")
+    select = require("../index");
+
+var options = require("nomnom").opts({
+   directory: {
+      abbr: 'd',
+      help: 'directory of tests to run',
+      metavar: 'PATH',
+      default: __dirname + "/level_1"
+   }
+}).parseArgs();
+
+var directory = options.directory,
+    files = fs.readdirSync(directory);
+
+var jsonFiles = files.filter(function(file) {
+   return path.extname(file) == ".json";
+});
+
+jsonFiles.forEach(function(file) {
+   var jsonName = file.replace(/\..*$/, ""),
+       json = JSON.parse(fs.readFileSync(path.join(directory, file), "utf-8"));
+   
+   var selFiles = files.filter(function(file) {
+      return file.indexOf(jsonName) == 0 && path.extname(file) == ".selector"
+   })
+   
+   selFiles.forEach(function(file) {
+      var test = file.replace(/\..*$/, "");
+      var selector = fs.readFileSync(path.join(directory, file), "utf-8");
+      var output = fs.readFileSync(path.join(directory, test + ".output"), "utf-8");
+      
+      var expected = JSON.parse(output).sort(sort);
+      var got = select(json, selector).nodes().sort(sort);
+      try {
+          assert.deepEqual(got, expected);
+      }
+      catch(AssertionError) {
+         console.log("\nfail".red + " " + test + "\ngot: ".blue + JSON.stringify(got)
+            + "\n\expected: ".blue + JSON.stringify(expected) + "\n")
+      };
+      console.log("pass".green + " " + test);
+   });
+});
+
+function sort(a, b) {
+   if (JSON.stringify(a) < JSON.stringify(b)) {
+      return -1;      
+   }
+   return 1;
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/test/test.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/test/test.js
new file mode 100644
index 0000000..2a215da
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/test/test.js
@@ -0,0 +1,161 @@
+var assert = require("assert"),
+    fs = require("fs"),
+    traverse = require("traverse")
+    select = require("../index");
+
+var people = {
+   "george": {
+       age : 35,
+       movies: [{
+          name: "Repo Man",
+          stars: 5
+      }]
+   },
+   "mary": {
+       age: 15,
+       movies: [{
+           name: "Twilight",
+           stars: 3
+       },
+       {
+          name: "Trudy",
+          stars: 2
+       },
+       {
+          name: "The Fighter",
+          stars: 4
+       }]
+   },
+   "chris" : {
+      car: null,
+      male: true
+   }
+};
+
+var people2, obj;
+
+assert.deepEqual(select(people, "*").nodes(), [{"george":{"age":35,"movies":[{"name":"Repo Man","stars":5}]},"mary":{"age":15,"movies":[{"name":"Twilight","stars":3},{"name":"Trudy","stars":2},{"name":"The Fighter","stars":4}]},"chris":{"car":null,"male":true}},{"age":35,"movies":[{"name":"Repo Man","stars":5}]},35,[{"name":"Repo Man","stars":5}],{"name":"Repo Man","stars":5},"Repo Man",5,{"age":15,"movies":[{"name":"Twilight","stars":3},{"name":"Trudy","stars":2},{"name":"The Fighter","stars":4}]},15,[{"name":"Twilight","stars":3},{"name":"Trudy","stars":2},{"name":"The Fighter","stars":4}],{"name":"Twilight","stars":3},"Twilight",3,{"name":"Trudy","stars":2},"Trudy",2,{"name":"The Fighter","stars":4},"The Fighter",4,{"car":null,"male":true},null,true]);
+assert.deepEqual(select(people, ".george").nodes(), [{"age":35,"movies":[{"name":"Repo Man","stars":5}]}]);
+assert.deepEqual(select(people, ".george .age").nodes(), [35]);
+assert.deepEqual(select(people, ".george .name").nodes(), ["Repo Man"]);
+assert.deepEqual(select(people, ".george *").nodes(), [35,[{"name":"Repo Man","stars":5}],{"name":"Repo Man","stars":5},"Repo Man",5])
+
+assert.deepEqual(select(people, ".george > *").nodes(), [35,[{"name":"Repo Man","stars":5}]]);
+assert.deepEqual(select(people, ".george > .name").nodes(), []);
+
+assert.deepEqual(select(people, ":first-child").nodes(), [{"name":"Repo Man","stars":5},{"name":"Twilight","stars":3}]);
+assert.deepEqual(select(people, ":nth-child(1)").nodes(), select(people, ":first-child").nodes());
+assert.deepEqual(select(people, ":nth-child(2)").nodes(), [{"name":"Trudy","stars":2}]);
+assert.deepEqual(select(people, ":nth-child(odd)").nodes(), [{"name":"Repo Man","stars":5},{"name":"Twilight","stars":3},{"name":"The Fighter","stars":4}]);
+assert.deepEqual(select(people, ":nth-child(even)").nodes(), [{"name":"Trudy","stars":2}]);
+
+assert.deepEqual(select(people, ":nth-child(-n+1)").nodes(), select(people, ":first-child").nodes());
+assert.deepEqual(select(people, ":nth-child(-n+2)").nodes(), [{"name":"Repo Man","stars":5},{"name":"Twilight","stars":3},{"name":"Trudy","stars":2}]);
+assert.deepEqual(select(people, ":nth-child(n)").nodes(), [{"name":"Repo Man","stars":5},{"name":"Twilight","stars":3},{"name":"Trudy","stars":2},{"name":"The Fighter","stars":4}]);
+assert.deepEqual(select(people, ":nth-child(n-1)").nodes(), select(people, ":nth-child(n)").nodes());
+assert.deepEqual(select(people, ":nth-child(n-2)").nodes(), [{"name":"Trudy","stars":2},{"name":"The Fighter","stars":4}]);
+
+assert.deepEqual(select(people, ":last-child").nodes(), [{"name":"Repo Man","stars":5},{"name":"The Fighter","stars":4}]);
+assert.deepEqual(select(people, ":nth-last-child(1)").nodes(), select(people, ":last-child").nodes());
+assert.deepEqual(select(people, ":nth-last-child(2)").nodes(), [{"name":"Trudy","stars":2}]);
+assert.deepEqual(select(people, ":only-child").nodes(), [{"name":"Repo Man","stars":5}]);
+assert.deepEqual(select(people, ":root").nodes(),[{"george":{"age":35,"movies":[{"name":"Repo Man","stars":5}]},"mary":{"age":15,"movies":[{"name":"Twilight","stars":3},{"name":"Trudy","stars":2},{"name":"The Fighter","stars":4}]},"chris":{"car":null,"male":true}}])
+
+assert.deepEqual(select(people, "string").nodes(),["Repo Man","Twilight","Trudy","The Fighter"]);
+assert.deepEqual(select(people, "number").nodes(),[35,5,15,3,2,4]);
+assert.deepEqual(select(people, "boolean").nodes(),[true]);
+assert.deepEqual(select(people, "object").nodes(),[{"george":{"age":35,"movies":[{"name":"Repo Man","stars":5}]},"mary":{"age":15,"movies":[{"name":"Twilight","stars":3},{"name":"Trudy","stars":2},{"name":"The Fighter","stars":4}]},"chris":{"car":null,"male":true}},{"age":35,"movies":[{"name":"Repo Man","stars":5}]},{"name":"Repo Man","stars":5},{"age":15,"movies":[{"name":"Twilight","stars":3},{"name":"Trudy","stars":2},{"name":"The Fighter","stars":4}]},{"name":"Twilight","stars":3},{"name":"Trudy","stars":2},{"name":"The Fighter","stars":4},{"car":null,"male":true}]);
+assert.deepEqual(select(people, "array").nodes(),[[{"name":"Repo Man","stars":5}],[{"name":"Twilight","stars":3},{"name":"Trudy","stars":2},{"name":"The Fighter","stars":4}]]);
+assert.deepEqual(select(people, "null").nodes(),[null]);
+
+assert.deepEqual(select(people, "number, string, boolean").nodes(), [35,"Repo Man",5,15,"Twilight",3,"Trudy",2,"The Fighter",4,true])
+
+assert.deepEqual(select(people, ":has(.car) > .male").nodes(), [true]);
+assert.deepEqual(select(people, ".male ~ .car").nodes(), [null])
+
+assert.deepEqual(select(people, ':val("Twilight")').nodes(), ["Twilight"])
+assert.deepEqual(select(people, ':val("Twi")').nodes(), [])
+assert.deepEqual(select(people, ':contains("Twi")').nodes(), ["Twilight"])
+assert.deepEqual(select(people, ':contains("weif")').nodes(), [])
+
+// invalid
+assert.deepEqual(select(people, ".hmmm").nodes(), []);
+assert.throws(function() {
+   select(people, "afcjwiojwe9q28*C@!(# (!#R($R)))").nodes();
+});
+
+// update()
+people2 = traverse.clone(people);
+
+select(people2, ".age").update(function(age) {
+    return age - 5;
+})
+assert.deepEqual(select(people2, ".age").nodes(), [30, 10]);
+
+obj = select(people2, ".age").update(3)
+assert.deepEqual(select(people2, ".age").nodes(), [3, 3]);
+assert.deepEqual(obj, people2);
+
+// remove()
+people2 = traverse.clone(people);
+
+obj = select(people2, ".age").remove();
+assert.deepEqual(select(people2, ".age").nodes(), []);
+assert.deepEqual(obj, people2);
+
+// condense()
+people2 = traverse.clone(people);
+select(people2, ".george").condense();
+assert.deepEqual(people2, {"george": {age: 35, movies: [{name: "Repo Man", stars: 5}]}});
+
+people2 = traverse.clone(people);
+select(people2, ".hmmm").condense();
+assert.deepEqual(people2, {});
+
+people2 = traverse.clone(people);
+obj = select(people2, ".stars").condense();
+assert.deepEqual(people2, {"george": {movies: [{stars: 5}]}, "mary": {movies: [{stars: 3},{stars: 2},{stars: 4}]}});
+assert.deepEqual(obj, people2);
+
+// forEach()
+people2 = traverse.clone(people);
+
+obj = select(people2, ".age").forEach(function(age) {
+    this.update(age - 5);
+})
+assert.deepEqual(select(people2, ".age").nodes(), [30, 10]);
+assert.deepEqual(obj, people2);
+
+
+// this.matches()
+people2 = traverse.clone(people);
+select(people2).forEach(function(node) {
+   if (this.matches(".age")) {
+      this.update(node + 10);
+   }
+});
+assert.deepEqual(select(people2, ".age").nodes(), [45, 25])
+
+
+// bigger stuff
+var timeline = require("./timeline.js");
+
+console.time("select time");
+assert.equal(select(timeline, ".bug .id").nodes().length, 126);
+assert.equal(select(timeline, ".id").nodes().length, 141);
+assert.equal(select(timeline, ".comments .id").nodes().length, 115);
+assert.equal(select(timeline, ":nth-child(n-2)").nodes().length, 335);
+assert.equal(select(timeline, "object").nodes().length, 927);
+assert.equal(select(timeline, "*").nodes().length, 3281);
+console.timeEnd("select time")
+
+var sel = require("JSONSelect");
+
+console.time("JSONSelect time")
+assert.equal(sel.match(".bug .id", timeline).length, 126);
+assert.equal(sel.match(".id", timeline).length, 141);
+assert.equal(sel.match(".comments .id", timeline).length, 115);
+assert.equal(sel.match(":nth-child(n-2)", timeline).length, 335);
+assert.equal(sel.match("object", timeline).length, 927);
+assert.equal(sel.match("*", timeline).length, 3281);
+console.timeEnd("JSONSelect time")
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/test/timeline.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/test/timeline.js
new file mode 100644
index 0000000..59799a0
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/node_modules/js-select/test/timeline.js
@@ -0,0 +1 @@
+module.exports = [{"bug":{"history":[{"changes":[{"removed":"","added":"gmealer@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/gmealer@mozilla.com","name":"gmealer@mozilla.com"},"change_time":"2011-07-19T22:35:56Z"},{"changes":[{"removed":"NEW","added":"RESOLVED","field_name":"status"},{"removed":"","added":"WONTFIX","field_name":"resolution"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/fayearthur+bugs@gmail.com","name":"fayearthur+bugs@gmail.com"},"change_time":"2011-07-19T22:24:59Z"},{"changes":[{"removed":"","added":"fayearthur+bugs@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/fayearthur+bugs@gmail.com","name":"fayearthur+bugs@gmail.com"},"change_time":"2011-07-13T19:49:06Z"},{"changes":[{"removed":"","added":"hskupin@gmail.com, stomlinson@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-07-13T19:11:32Z"},{"changes":[{"removed":"","added":"ctalbert@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-07-13T18:43:17Z"}],"summary":"Adding isNumber, isFunction, isString, isArray and isObject to assert.js","last_change_time":"2011-07-19T22:35:56Z","comments":[{"is_private":false,"creator":{"real_name":"Geo Mealer [:geo]","name":"gmealer"},"text":"","id":5600392,"creation_time":"2011-07-19T22:35:56Z"},{"is_private":false,"creator":{"real_name":"Heather Arthur [:harth]","name":"fayearthur+bugs"},"text":"","id":5600359,"creation_time":"2011-07-19T22:24:59Z"},{"is_private":false,"creator":{"real_name":"Heather Arthur [:harth]","name":"fayearthur+bugs"},"text":"","id":5589849,"creation_time":"2011-07-13T20:24:38Z"},{"is_private":false,"creator":{"real_name":"Shane Tomlinson","name":"stomlinson"},"text":"","id":5589791,"creation_time":"2011-07-13T19:59:43Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5589782,"creation_time":"2011-07-13T19:56:44Z"},{"is_private":false,"creator":{"real_name":"Heather Arthur [:harth]","name":"fayearthur+bugs"},"text":"","id":5589758,"creation_time":"2011-07-13T19:49:06Z"},{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":5589591,"creation_time":"2011-07-13T18:42:07Z"}],"id":671367},"events":[{"time":"2011-07-19T22:35:56Z","changeset":{"changes":[{"removed":"","added":"gmealer@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/gmealer@mozilla.com","name":"gmealer@mozilla.com"},"change_time":"2011-07-19T22:35:56Z"}},{"time":"2011-07-19T22:35:56Z","comment":{"is_private":false,"creator":{"real_name":"Geo Mealer [:geo]","name":"gmealer"},"text":"","id":5600392,"creation_time":"2011-07-19T22:35:56Z"}},{"time":"2011-07-19T22:24:59Z","changeset":{"changes":[{"removed":"NEW","added":"RESOLVED","field_name":"status"},{"removed":"","added":"WONTFIX","field_name":"resolution"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/fayearthur+bugs@gmail.com","name":"fayearthur+bugs@gmail.com"},"change_time":"2011-07-19T22:24:59Z"}},{"time":"2011-07-19T22:24:59Z","comment":{"is_private":false,"creator":{"real_name":"Heather Arthur [:harth]","name":"fayearthur+bugs"},"text":"","id":5600359,"creation_time":"2011-07-19T22:24:59Z"}}]},{"bug":{"history":[{"changes":[{"removed":"","added":"ctalbert@mozilla.com, fayearthur+bugs@gmail.com, jhammel@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-07-19T20:22:11Z"}],"summary":"mozmill \"--profile\" option not working with relative path","last_change_time":"2011-07-19T20:46:19Z","comments":[{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":5600020,"creation_time":"2011-07-19T20:46:19Z"},{"is_private":false,"creator":{"real_name":"Marc-Aurèle DARCHE","name":"mozdev"},"text":"","id":5599999,"creation_time":"2011-07-19T20:39:07Z"},{"is_private":false,"creator":{"real_name":"Marc-Aurèle DARCHE","name":"mozdev"},"text":"","id":5599848,"creation_time":"2011-07-19T19:29:35Z"}],"id":672605},"events":[{"time":"2011-07-19T20:46:19Z","comment":{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":5600020,"creation_time":"2011-07-19T20:46:19Z"}},{"time":"2011-07-19T20:39:07Z","comment":{"is_private":false,"creator":{"real_name":"Marc-Aurèle DARCHE","name":"mozdev"},"text":"","id":5599999,"creation_time":"2011-07-19T20:39:07Z"}},{"time":"2011-07-19T20:22:11Z","changeset":{"changes":[{"removed":"","added":"ctalbert@mozilla.com, fayearthur+bugs@gmail.com, jhammel@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-07-19T20:22:11Z"}},{"time":"2011-07-19T19:29:35Z","comment":{"is_private":false,"creator":{"real_name":"Marc-Aurèle DARCHE","name":"mozdev"},"text":"","id":5599848,"creation_time":"2011-07-19T19:29:35Z"}}]},{"bug":{"history":[{"changes":[{"removed":"","added":"ehsan@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ehsan@mozilla.com","name":"ehsan@mozilla.com"},"change_time":"2011-07-19T20:37:46Z"},{"changes":[{"removed":"","added":"fayearthur+bugs@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2011-07-19T17:37:11Z"},{"changes":[{"removed":"","added":"ctalbert@mozilla.com, jhammel@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2011-07-19T17:35:58Z"},{"changes":[{"removed":"","added":"ted.mielczarek@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ted.mielczarek@gmail.com","name":"ted.mielczarek@gmail.com"},"change_time":"2011-07-18T12:00:25Z"},{"changes":[{"removed":"","added":"bmo@edmorley.co.uk","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/bmo@edmorley.co.uk","name":"bmo@edmorley.co.uk"},"change_time":"2011-07-17T11:20:51Z"},{"changes":[{"removed":"","added":"bzbarsky@mit.edu","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/bzbarsky@mit.edu","name":"bzbarsky@mit.edu"},"change_time":"2011-07-14T03:29:52Z"},{"changes":[{"removed":"","added":"dmandelin@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/dmandelin@mozilla.com","name":"dmandelin@mozilla.com"},"change_time":"2011-07-14T00:27:21Z"},{"changes":[{"removed":"","added":"kairo@kairo.at","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/kairo@kairo.at","name":"kairo@kairo.at"},"change_time":"2011-07-14T00:04:16Z"},{"changes":[{"removed":"","added":"gavin.sharp@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/gavin.sharp@gmail.com","name":"gavin.sharp@gmail.com"},"change_time":"2011-07-13T23:17:24Z"},{"changes":[{"removed":"","added":"anygregor@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/anygregor@gmail.com","name":"anygregor@gmail.com"},"change_time":"2011-07-13T19:57:34Z"},{"changes":[{"removed":"","added":"luke@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/luke@mozilla.com","name":"luke@mozilla.com"},"change_time":"2011-07-13T18:53:42Z"},{"changes":[{"removed":"","added":"sphink@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/sphink@gmail.com","name":"sphink@gmail.com"},"change_time":"2011-07-13T18:23:30Z"},{"changes":[{"removed":"","added":"davemgarrett@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/davemgarrett@gmail.com","name":"davemgarrett@gmail.com"},"change_time":"2011-07-13T18:21:27Z"},{"changes":[{"removed":"","added":"khuey@kylehuey.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/khuey@kylehuey.com","name":"khuey@kylehuey.com"},"change_time":"2011-07-13T18:21:08Z"}],"summary":"Split chrome into multiple compartments for better accounting of JS memory used by chrome code (and add-ons)","last_change_time":"2011-07-19T20:37:46Z","comments":[{"is_private":false,"creator":{"real_name":"Ehsan Akhgari [:ehsan]","name":"ehsan"},"text":"","id":5599990,"creation_time":"2011-07-19T20:37:46Z"},{"is_private":false,"creator":{"real_name":"Gregor Wagner","name":"anygregor"},"text":"","id":5589947,"creation_time":"2011-07-13T21:04:28Z"},{"is_private":false,"creator":{"real_name":"Andreas Gal :gal","name":"gal"},"text":"","id":5589879,"creation_time":"2011-07-13T20:39:32Z"},{"is_private":false,"creator":{"real_name":"Gregor Wagner","name":"anygregor"},"text":"","id":5589812,"creation_time":"2011-07-13T20:07:18Z"},{"is_private":false,"creator":{"real_name":"Luke Wagner [:luke]","name":"luke"},"text":"","id":5589801,"creation_time":"2011-07-13T20:03:36Z"},{"is_private":false,"creator":{"real_name":"Gregor Wagner","name":"anygregor"},"text":"","id":5589787,"creation_time":"2011-07-13T19:57:34Z"},{"is_private":false,"creator":{"real_name":"Ehsan Akhgari [:ehsan]","name":"ehsan"},"text":"","id":5589485,"creation_time":"2011-07-13T18:15:17Z"}],"id":671352},"events":[{"time":"2011-07-19T20:37:46Z","changeset":{"changes":[{"removed":"","added":"ehsan@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ehsan@mozilla.com","name":"ehsan@mozilla.com"},"change_time":"2011-07-19T20:37:46Z"}},{"time":"2011-07-19T20:37:46Z","comment":{"is_private":false,"creator":{"real_name":"Ehsan Akhgari [:ehsan]","name":"ehsan"},"text":"","id":5599990,"creation_time":"2011-07-19T20:37:46Z"}},{"time":"2011-07-19T17:37:11Z","changeset":{"changes":[{"removed":"","added":"fayearthur+bugs@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2011-07-19T17:37:11Z"}},{"time":"2011-07-19T17:35:58Z","changeset":{"changes":[{"removed":"","added":"ctalbert@mozilla.com, jhammel@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2011-07-19T17:35:58Z"}},{"time":"2011-07-18T12:00:25Z","changeset":{"changes":[{"removed":"","added":"ted.mielczarek@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ted.mielczarek@gmail.com","name":"ted.mielczarek@gmail.com"},"change_time":"2011-07-18T12:00:25Z"}}]},{"bug":{"history":[{"changes":[{"attachment_id":"546836","removed":"text/x-python","added":"text/plain","field_name":"attachment.content_type"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-07-19T18:28:25Z"},{"changes":[{"removed":"","added":"ctalbert@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2011-07-19T17:58:20Z"}],"summary":"mozprofile should set permissions for profiles","last_change_time":"2011-07-19T18:28:25Z","comments":[{"is_private":false,"creator":{"real_name":"Joel Maher (:jmaher)","name":"jmaher"},"text":"","id":5599602,"creation_time":"2011-07-19T18:15:26Z"},{"is_private":false,"creator":{"real_name":"Joel Maher (:jmaher)","name":"jmaher"},"text":"","id":5599285,"creation_time":"2011-07-19T16:21:34Z"},{"is_private":false,"creator":{"real_name":"Joel Maher (:jmaher)","name":"jmaher"},"text":"","id":5554367,"creation_time":"2011-06-24T17:41:38Z"},{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":5552758,"creation_time":"2011-06-23T23:26:21Z"}],"id":666791},"events":[{"time":"2011-07-19T18:28:25Z","changeset":{"changes":[{"attachment_id":"546836","removed":"text/x-python","added":"text/plain","field_name":"attachment.content_type"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-07-19T18:28:25Z"}},{"time":"2011-07-19T18:15:26Z","comment":{"is_private":false,"creator":{"real_name":"Joel Maher (:jmaher)","name":"jmaher"},"text":"","id":5599602,"creation_time":"2011-07-19T18:15:26Z"}},{"time":"2011-07-19T17:58:20Z","changeset":{"changes":[{"removed":"","added":"ctalbert@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2011-07-19T17:58:20Z"}},{"time":"2011-07-19T16:21:34Z","comment":{"is_private":false,"creator":{"real_name":"Joel Maher (:jmaher)","name":"jmaher"},"text":"","id":5599285,"creation_time":"2011-07-19T16:21:34Z"}}]},{"bug":{"history":[{"changes":[{"removed":"NEW","added":"RESOLVED","field_name":"status"},{"removed":"","added":"FIXED","field_name":"resolution"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-07-19T17:57:44Z"},{"changes":[{"removed":"nobody@mozilla.org","added":"jhammel@mozilla.com","field_name":"assigned_to"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-07-19T15:39:01Z"},{"changes":[{"removed":"[mozmill-2.0?]","added":"[mozmill-2.0+]","field_name":"whiteboard"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-07-19T04:24:09Z"},{"changes":[{"attachment_id":"545960","removed":"review?(ctalbert@mozilla.com)","added":"review+","field_name":"flag"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2011-07-18T22:04:12Z"},{"changes":[{"attachment_id":"545960","removed":"","added":"review?(ctalbert@mozilla.com)","field_name":"flag"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-07-14T18:16:53Z"},{"changes":[{"removed":"","added":"ctalbert@mozilla.com, fayearthur+bugs@gmail.com","field_name":"cc"},{"removed":"","added":"[mozmill-2.0?]","field_name":"whiteboard"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-07-13T21:37:01Z"}],"summary":"mutt processhandler and mozprocess processhandler: to merge?","last_change_time":"2011-07-19T17:57:44Z","comments":[{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":5599538,"creation_time":"2011-07-19T17:57:44Z"},{"is_private":false,"creator":{"real_name":"Clint Talbert ( :ctalbert )","name":"ctalbert"},"text":"","id":5597782,"creation_time":"2011-07-18T22:04:12Z"},{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":5591772,"creation_time":"2011-07-14T18:16:53Z"},{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":5590023,"creation_time":"2011-07-13T21:35:42Z"}],"id":671420},"events":[{"time":"2011-07-19T17:57:44Z","changeset":{"changes":[{"removed":"NEW","added":"RESOLVED","field_name":"status"},{"removed":"","added":"FIXED","field_name":"resolution"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-07-19T17:57:44Z"}},{"time":"2011-07-19T17:57:44Z","comment":{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":5599538,"creation_time":"2011-07-19T17:57:44Z"}},{"time":"2011-07-19T15:39:01Z","changeset":{"changes":[{"removed":"nobody@mozilla.org","added":"jhammel@mozilla.com","field_name":"assigned_to"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-07-19T15:39:01Z"}},{"time":"2011-07-19T04:24:09Z","changeset":{"changes":[{"removed":"[mozmill-2.0?]","added":"[mozmill-2.0+]","field_name":"whiteboard"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-07-19T04:24:09Z"}},{"time":"2011-07-18T22:04:12Z","changeset":{"changes":[{"attachment_id":"545960","removed":"review?(ctalbert@mozilla.com)","added":"review+","field_name":"flag"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2011-07-18T22:04:12Z"}},{"time":"2011-07-18T22:04:12Z","comment":{"is_private":false,"creator":{"real_name":"Clint Talbert ( :ctalbert )","name":"ctalbert"},"text":"","id":5597782,"creation_time":"2011-07-18T22:04:12Z"}}]},{"bug":{"history":[{"changes":[{"attachment_id":"546171","removed":"review?(fayearthur+bugs@gmail.com)","added":"review+","field_name":"flag"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/fayearthur+bugs@gmail.com","name":"fayearthur+bugs@gmail.com"},"change_time":"2011-07-19T17:42:04Z"},{"changes":[{"attachment_id":"546171","removed":"","added":"review?(fayearthur+bugs@gmail.com), feedback?(halbersa@gmail.com)","field_name":"flag"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-07-15T16:21:21Z"},{"changes":[{"attachment_id":"545775","removed":"review?(fayearthur+bugs@gmail.com)","added":"review+","field_name":"flag"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/fayearthur+bugs@gmail.com","name":"fayearthur+bugs@gmail.com"},"change_time":"2011-07-14T19:40:03Z"},{"changes":[{"attachment_id":"545771","removed":"0","added":"1","field_name":"attachment.is_obsolete"},{"attachment_id":"545771","removed":"review?(fayearthur+bugs@gmail.com)","added":"","field_name":"flag"},{"attachment_id":"545775","removed":"","added":"review?(fayearthur+bugs@gmail.com)","field_name":"flag"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-07-13T23:19:12Z"},{"changes":[{"attachment_id":"545771","removed":"","added":"review?(fayearthur+bugs@gmail.com)","field_name":"flag"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-07-13T23:14:09Z"},{"changes":[{"attachment_id":"535258","removed":"0","added":"1","field_name":"attachment.is_obsolete"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-07-13T23:12:37Z"},{"changes":[{"removed":"","added":"661408","field_name":"blocks"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-06-28T23:56:05Z"},{"changes":[{"attachment_id":"535258","removed":"feedback?(hskupin@gmail.com)","added":"feedback-","field_name":"flag"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-05-26T07:48:02Z"},{"changes":[{"attachment_id":"535258","removed":"","added":"feedback?(hskupin@gmail.com)","field_name":"flag"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2011-05-26T02:57:15Z"},{"changes":[{"removed":"","added":"halbersa@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2011-05-26T02:48:33Z"},{"changes":[{"removed":"waitForPageLoad() fails if the document owner is not a tab but an HTMLDocument","added":"waitForPageLoad() fails if the document owner is not a tab but an HTMLDocument (add support for iframes)","field_name":"summary"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-05-25T23:56:38Z"},{"changes":[{"attachment_id":"534720","removed":"review?(ctalbert@mozilla.com)","added":"review+","field_name":"flag"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2011-05-25T23:50:33Z"},{"changes":[{"removed":"","added":"fayearthur+bugs@gmail.com","field_name":"cc"},{"removed":"[mozmill-1.5.4+]","added":"[mozmill-1.5.4+][mozmill-2.0+]","field_name":"whiteboard"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-05-25T12:08:34Z"},{"changes":[{"removed":"","added":"ctalbert@mozilla.com","field_name":"cc"},{"removed":"[mozmill-1.5.4?]","added":"[mozmill-1.5.4+]","field_name":"whiteboard"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2011-05-24T21:25:21Z"},{"changes":[{"attachment_id":"534714","removed":"0","added":"1","field_name":"attachment.is_obsolete"},{"attachment_id":"534714","removed":"review?(ctalbert@mozilla.com)","added":"","field_name":"flag"},{"attachment_id":"534720","removed":"","added":"review?(ctalbert@mozilla.com)","field_name":"flag"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-05-24T11:15:13Z"},{"changes":[{"attachment_id":"534445","removed":"0","added":"1","field_name":"attachment.is_obsolete"},{"attachment_id":"534714","removed":"","added":"review?(ctalbert@mozilla.com)","field_name":"flag"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-05-24T10:59:06Z"},{"changes":[{"removed":"waitForPageLoad() fails for Discovery Pane with 'Specified tab hasn't been found.'","added":"waitForPageLoad() fails if the document owner is not a tab but an HTMLDocument","field_name":"summary"},{"removed":"","added":"in-testsuite?","field_name":"flag"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-05-24T10:53:16Z"},{"changes":[{"removed":"waitForPageLoad() fails for iFrames with 'Specified tab hasn't been found.'","added":"waitForPageLoad() fails for Discovery Pane with 'Specified tab hasn't been found.'","field_name":"summary"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-05-24T00:22:33Z"},{"changes":[{"removed":"","added":"[mozmill-1.5.4?]","field_name":"whiteboard"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-05-23T21:33:23Z"},{"changes":[{"removed":"","added":"regression","field_name":"keywords"},{"removed":"NEW","added":"ASSIGNED","field_name":"status"},{"removed":"x86_64","added":"All","field_name":"platform"},{"removed":"","added":"604878","field_name":"blocks"},{"removed":"nobody@mozilla.org","added":"hskupin@gmail.com","field_name":"assigned_to"},{"removed":"waitForPageLoad() does not handle iframes","added":"waitForPageLoad() fails for iFrames with 'Specified tab hasn't been found.'","field_name":"summary"},{"removed":"Linux","added":"All","field_name":"op_sys"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-05-23T15:59:53Z"},{"changes":[{"removed":"","added":"alex.lakatos@softvision.ro, hskupin@gmail.com, vlad.maniac@softvision.ro","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/vlad.maniac@softvision.ro","name":"vlad.maniac@softvision.ro"},"change_time":"2011-05-23T15:24:49Z"}],"summary":"waitForPageLoad() fails if the document owner is not a tab but an HTMLDocument (add support for iframes)","last_change_time":"2011-07-19T17:42:04Z","comments":[{"is_private":false,"creator":{"real_name":"Heather Arthur [:harth]","name":"fayearthur+bugs"},"text":"","id":5599498,"creation_time":"2011-07-19T17:42:04Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5593624,"creation_time":"2011-07-15T16:21:21Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5592472,"creation_time":"2011-07-14T22:38:58Z"},{"is_private":false,"creator":{"real_name":"Heather Arthur [:harth]","name":"fayearthur+bugs"},"text":"","id":5591961,"creation_time":"2011-07-14T19:40:03Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5590242,"creation_time":"2011-07-13T23:19:12Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5590234,"creation_time":"2011-07-13T23:14:09Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5590231,"creation_time":"2011-07-13T23:12:37Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5494401,"creation_time":"2011-05-26T07:48:02Z"},{"is_private":false,"creator":{"real_name":"Clint Talbert ( :ctalbert )","name":"ctalbert"},"text":"","id":5494130,"creation_time":"2011-05-26T02:58:56Z"},{"is_private":false,"creator":{"real_name":"Clint Talbert ( :ctalbert )","name":"ctalbert"},"text":"","id":5494127,"creation_time":"2011-05-26T02:57:15Z"},{"is_private":false,"creator":{"real_name":"Clint Talbert ( :ctalbert )","name":"ctalbert"},"text":"","id":5493838,"creation_time":"2011-05-25T23:50:33Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5491980,"creation_time":"2011-05-25T12:08:34Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5488829,"creation_time":"2011-05-24T11:18:12Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5488828,"creation_time":"2011-05-24T11:15:13Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5488811,"creation_time":"2011-05-24T10:59:06Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5488807,"creation_time":"2011-05-24T10:53:16Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5488698,"creation_time":"2011-05-24T09:37:36Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5488122,"creation_time":"2011-05-24T00:22:33Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5487989,"creation_time":"2011-05-23T23:17:41Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5487883,"creation_time":"2011-05-23T22:39:31Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5487655,"creation_time":"2011-05-23T21:33:23Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5486617,"creation_time":"2011-05-23T15:59:53Z"},{"is_private":false,"creator":{"real_name":"Maniac Vlad Florin (:vladmaniac)","name":"vlad.maniac"},"text":"","id":5486535,"creation_time":"2011-05-23T15:23:48Z"}],"id":659000},"events":[{"time":"2011-07-19T17:42:04Z","changeset":{"changes":[{"attachment_id":"546171","removed":"review?(fayearthur+bugs@gmail.com)","added":"review+","field_name":"flag"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/fayearthur+bugs@gmail.com","name":"fayearthur+bugs@gmail.com"},"change_time":"2011-07-19T17:42:04Z"}},{"time":"2011-07-19T17:42:04Z","comment":{"is_private":false,"creator":{"real_name":"Heather Arthur [:harth]","name":"fayearthur+bugs"},"text":"","id":5599498,"creation_time":"2011-07-19T17:42:04Z"}}]},{"bug":{"history":[],"summary":"QA Companion marked incompatible for Firefox versions higher than 6.*.","last_change_time":"2011-07-18T23:00:06Z","comments":[{"is_private":false,"creator":{"real_name":"Al Billings [:abillings]","name":"abillings"},"text":"","id":5597976,"creation_time":"2011-07-18T23:00:06Z"}],"id":672393},"events":[{"time":"2011-07-18T23:00:06Z","comment":{"is_private":false,"creator":{"real_name":"Al Billings [:abillings]","name":"abillings"},"text":"","id":5597976,"creation_time":"2011-07-18T23:00:06Z"}}]},{"bug":{"history":[{"changes":[{"removed":"","added":"khuey@kylehuey.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/khuey@kylehuey.com","name":"khuey@kylehuey.com"},"change_time":"2011-07-18T20:35:37Z"},{"changes":[{"removed":"","added":"kairo@kairo.at","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/kairo@kairo.at","name":"kairo@kairo.at"},"change_time":"2010-04-05T16:18:55Z"},{"changes":[{"removed":"","added":"mook.moz+mozbz@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/mook@songbirdnest.com","name":"mook@songbirdnest.com"},"change_time":"2010-03-16T21:34:23Z"},{"changes":[{"removed":"","added":"harthur@cmu.edu","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/fayearthur+bugs@gmail.com","name":"fayearthur+bugs@gmail.com"},"change_time":"2010-03-16T04:31:00Z"},{"changes":[{"removed":"","added":"ted.mielczarek@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ted.mielczarek@gmail.com","name":"ted.mielczarek@gmail.com"},"change_time":"2010-03-15T23:52:39Z"},{"changes":[{"removed":"","added":"552533","field_name":"depends_on"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/dwitte@gmail.com","name":"dwitte@gmail.com"},"change_time":"2010-03-15T22:02:20Z"},{"changes":[{"removed":"","added":"447581","field_name":"blocks"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/dietrich@mozilla.com","name":"dietrich@mozilla.com"},"change_time":"2010-03-02T23:57:25Z"},{"changes":[{"removed":"412531","added":"","field_name":"blocks"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/dietrich@mozilla.com","name":"dietrich@mozilla.com"},"change_time":"2009-12-11T02:49:08Z"},{"changes":[{"removed":"","added":"Pidgeot18@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/Pidgeot18@gmail.com","name":"Pidgeot18@gmail.com"},"change_time":"2009-10-26T14:47:22Z"},{"changes":[{"removed":"js-ctypes","added":"js-ctypes","field_name":"component"},{"removed":"Trunk","added":"unspecified","field_name":"version"},{"removed":"Other Applications","added":"Core","field_name":"product"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/mozillamarcia.knous@gmail.com","name":"mozillamarcia.knous@gmail.com"},"change_time":"2009-10-02T10:17:18Z"},{"changes":[{"removed":"","added":"jwalden+bmo@mit.edu","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jwalden+bmo@mit.edu","name":"jwalden+bmo@mit.edu"},"change_time":"2009-08-17T19:11:28Z"},{"changes":[{"removed":"","added":"ajvincent@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ajvincent@gmail.com","name":"ajvincent@gmail.com"},"change_time":"2009-08-13T00:28:01Z"},{"changes":[{"removed":"","added":"benjamin@smedbergs.us","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/benjamin@smedbergs.us","name":"benjamin@smedbergs.us"},"change_time":"2009-07-23T03:14:04Z"},{"changes":[{"removed":"","added":"highmind63@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/highmind63@gmail.com","name":"highmind63@gmail.com"},"change_time":"2009-07-23T01:02:51Z"},{"changes":[{"removed":"","added":"shaver@mozilla.org","field_name":"cc"},{"removed":"JavaScript Engine","added":"js-ctypes","field_name":"component"},{"removed":"general@js.bugs","added":"nobody@mozilla.org","field_name":"assigned_to"},{"removed":"Core","added":"Other Applications","field_name":"product"},{"removed":"add a pinvoke-like method to js","added":"Support C++ calling from JSctypes","field_name":"summary"},{"removed":"general@spidermonkey.bugs","added":"js-ctypes@otherapps.bugs","field_name":"qa_contact"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/shaver@mozilla.org","name":"shaver@mozilla.org"},"change_time":"2009-07-23T00:55:48Z"},{"changes":[{"removed":"","added":"ryanvm@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ryanvm@gmail.com","name":"ryanvm@gmail.com"},"change_time":"2009-07-23T00:34:18Z"},{"changes":[{"removed":"","added":"412531","field_name":"blocks"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/dietrich@mozilla.com","name":"dietrich@mozilla.com"},"change_time":"2009-07-23T00:06:41Z"},{"changes":[{"removed":"","added":"dvander@alliedmods.net","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/dvander@alliedmods.net","name":"dvander@alliedmods.net"},"change_time":"2009-07-23T00:03:46Z"},{"changes":[{"removed":"","added":"dietrich@mozilla.com, mark.finkle@gmail.com","field_name":"cc"},{"removed":"","added":"[ts][tsnap]","field_name":"whiteboard"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/dietrich@mozilla.com","name":"dietrich@mozilla.com"},"change_time":"2009-07-22T23:58:23Z"}],"summary":"Support C++ calling from JSctypes","last_change_time":"2011-07-18T20:35:37Z","comments":[{"is_private":false,"creator":{"real_name":"Ted Mielczarek [:ted, :luser]","name":"ted.mielczarek"},"text":"","id":4586274,"creation_time":"2010-03-15T23:52:39Z"},{"is_private":false,"creator":{"real_name":"Dan Witte (:dwitte)","name":"dwitte"},"text":"","id":4586028,"creation_time":"2010-03-15T22:21:21Z"},{"is_private":false,"creator":{"real_name":"Dan Witte (:dwitte)","name":"dwitte"},"text":"","id":4586006,"creation_time":"2010-03-15T22:11:11Z"},{"is_private":false,"creator":{"real_name":"Joshua Cranmer [:jcranmer]","name":"Pidgeot18"},"text":"","id":4364260,"creation_time":"2009-10-26T18:33:21Z"},{"is_private":false,"creator":{"real_name":"Dan Witte (:dwitte)","name":"dwitte"},"text":"","id":4364119,"creation_time":"2009-10-26T17:27:05Z"},{"is_private":false,"creator":{"real_name":"Joshua Cranmer [:jcranmer]","name":"Pidgeot18"},"text":"","id":4363794,"creation_time":"2009-10-26T14:47:22Z"},{"is_private":false,"creator":{"real_name":"Benjamin Smedberg [:bsmedberg]","name":"benjamin"},"text":"","id":4211892,"creation_time":"2009-07-23T03:14:04Z"},{"is_private":false,"creator":{"real_name":"Mike Shaver (:shaver)","name":"shaver"},"text":"","id":4211710,"creation_time":"2009-07-23T00:56:10Z"},{"is_private":false,"creator":{"real_name":"Taras Glek (:taras)","name":"tglek"},"text":"","id":4211707,"creation_time":"2009-07-23T00:54:06Z"},{"is_private":false,"creator":{"real_name":"Mike Shaver (:shaver)","name":"shaver"},"text":"","id":4211677,"creation_time":"2009-07-23T00:42:01Z"},{"is_private":false,"creator":{"real_name":"Taras Glek (:taras)","name":"tglek"},"text":"","id":4211672,"creation_time":"2009-07-23T00:40:10Z"},{"is_private":false,"creator":{"real_name":"Mike Shaver (:shaver)","name":"shaver"},"text":"","id":4211668,"creation_time":"2009-07-23T00:38:34Z"},{"is_private":false,"creator":{"real_name":"Taras Glek (:taras)","name":"tglek"},"text":"","id":4211665,"creation_time":"2009-07-23T00:36:58Z"},{"is_private":false,"creator":{"real_name":"Mike Shaver (:shaver)","name":"shaver"},"text":"","id":4211660,"creation_time":"2009-07-23T00:34:27Z"},{"is_private":false,"creator":{"real_name":"Taras Glek (:taras)","name":"tglek"},"text":"","id":4211624,"creation_time":"2009-07-23T00:15:38Z"},{"is_private":false,"creator":{"real_name":"Mike Shaver (:shaver)","name":"shaver"},"text":"","id":4211608,"creation_time":"2009-07-23T00:08:16Z"},{"is_private":false,"creator":{"real_name":"Taras Glek (:taras)","name":"tglek"},"text":"","id":4211586,"creation_time":"2009-07-22T23:57:14Z"}],"id":505907},"events":[{"time":"2011-07-18T20:35:37Z","changeset":{"changes":[{"removed":"","added":"khuey@kylehuey.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/khuey@kylehuey.com","name":"khuey@kylehuey.com"},"change_time":"2011-07-18T20:35:37Z"}}]},{"bug":{"history":[{"changes":[{"removed":"","added":"bmo@edmorley.co.uk","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/bmo@edmorley.co.uk","name":"bmo@edmorley.co.uk"},"change_time":"2011-07-10T14:30:39Z"},{"changes":[{"removed":"","added":"bear@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/bear@mozilla.com","name":"bear@mozilla.com"},"change_time":"2011-07-07T21:21:56Z"},{"changes":[{"removed":"","added":"ctalbert@mozilla.com, fayearthur+bugs@gmail.com, sliu@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2011-07-07T21:19:46Z"},{"changes":[{"removed":"","added":"armenzg@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/armenzg@mozilla.com","name":"armenzg@mozilla.com"},"change_time":"2011-07-07T17:04:41Z"}],"summary":"Publish Build Faster metrics","last_change_time":"2011-07-18T20:25:40Z","comments":[{"is_private":false,"creator":{"real_name":"Chris AtLee [:catlee]","name":"catlee"},"text":"","id":5597430,"creation_time":"2011-07-18T20:25:40Z"},{"is_private":false,"creator":{"real_name":"Sam Liu","name":"sliu"},"text":"","id":5597428,"creation_time":"2011-07-18T20:24:35Z"},{"is_private":false,"creator":{"real_name":"Chris AtLee [:catlee]","name":"catlee"},"text":"","id":5597393,"creation_time":"2011-07-18T20:10:45Z"},{"is_private":false,"creator":{"real_name":"Sam Liu","name":"sliu"},"text":"","id":5589839,"creation_time":"2011-07-13T20:20:49Z"},{"is_private":false,"creator":{"real_name":"Chris AtLee [:catlee]","name":"catlee"},"text":"","id":5582189,"creation_time":"2011-07-09T01:25:21Z"},{"is_private":false,"creator":{"real_name":"Sam Liu","name":"sliu"},"text":"","id":5581446,"creation_time":"2011-07-08T18:25:33Z"},{"is_private":false,"creator":{"real_name":"Armen Zambrano G. [:armenzg] - Release Engineer","name":"armenzg"},"text":"","id":5580728,"creation_time":"2011-07-08T12:36:01Z"},{"is_private":false,"creator":{"real_name":"Chris AtLee [:catlee]","name":"catlee"},"text":"","id":5580206,"creation_time":"2011-07-08T03:21:41Z"},{"is_private":false,"creator":{"real_name":"Sam Liu","name":"sliu"},"text":"","id":5579985,"creation_time":"2011-07-08T00:01:37Z"},{"is_private":false,"creator":{"real_name":"Chris AtLee [:catlee]","name":"catlee"},"text":"","id":5579980,"creation_time":"2011-07-07T23:57:48Z"},{"is_private":false,"creator":{"real_name":"Sam Liu","name":"sliu"},"text":"","id":5579965,"creation_time":"2011-07-07T23:51:02Z"},{"is_private":false,"creator":{"real_name":"Chris AtLee [:catlee]","name":"catlee"},"text":"","id":5579631,"creation_time":"2011-07-07T21:32:17Z"},{"is_private":false,"creator":{"real_name":"Mike Taylor [:bear]","name":"bear"},"text":"","id":5579612,"creation_time":"2011-07-07T21:21:56Z"},{"is_private":false,"creator":{"real_name":"Clint Talbert ( :ctalbert )","name":"ctalbert"},"text":"","id":5579605,"creation_time":"2011-07-07T21:19:46Z"},{"is_private":false,"creator":{"real_name":"Chris AtLee [:catlee]","name":"catlee"},"text":"","id":5578932,"creation_time":"2011-07-07T17:02:45Z"}],"id":669930},"events":[{"time":"2011-07-18T20:25:40Z","comment":{"is_private":false,"creator":{"real_name":"Chris AtLee [:catlee]","name":"catlee"},"text":"","id":5597430,"creation_time":"2011-07-18T20:25:40Z"}},{"time":"2011-07-18T20:24:35Z","comment":{"is_private":false,"creator":{"real_name":"Sam Liu","name":"sliu"},"text":"","id":5597428,"creation_time":"2011-07-18T20:24:35Z"}},{"time":"2011-07-18T20:10:45Z","comment":{"is_private":false,"creator":{"real_name":"Chris AtLee [:catlee]","name":"catlee"},"text":"","id":5597393,"creation_time":"2011-07-18T20:10:45Z"}}]},{"bug":{"history":[{"changes":[{"removed":"","added":"jwatt@jwatt.org","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jwatt@jwatt.org","name":"jwatt@jwatt.org"},"change_time":"2011-07-18T12:06:33Z"},{"changes":[{"removed":"","added":"taku.eof@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/taku.eof@gmail.com","name":"taku.eof@gmail.com"},"change_time":"2011-06-27T04:50:23Z"},{"changes":[{"removed":"","added":"jeff@stikman.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jeff@stikman.com","name":"jeff@stikman.com"},"change_time":"2011-05-30T15:15:06Z"},{"changes":[{"removed":"","added":"spencerselander@yahoo.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/spencerselander@yahoo.com","name":"spencerselander@yahoo.com"},"change_time":"2011-05-18T07:58:37Z"},{"changes":[{"removed":"VanillaMozilla@hotmail.com","added":"","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/VanillaMozilla@hotmail.com","name":"VanillaMozilla@hotmail.com"},"change_time":"2011-05-17T02:57:17Z"},{"changes":[{"removed":"","added":"mcdavis941.bugs@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/mcdavis941.bugs@gmail.com","name":"mcdavis941.bugs@gmail.com"},"change_time":"2011-05-16T21:08:18Z"},{"changes":[{"removed":"beltzner@mozilla.com","added":"","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/mbeltzner@gmail.com","name":"mbeltzner@gmail.com"},"change_time":"2011-04-02T00:17:26Z"},{"changes":[{"removed":"","added":"cbaker@customsoftwareconsult.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/cbaker@customsoftwareconsult.com","name":"cbaker@customsoftwareconsult.com"},"change_time":"2011-03-28T14:08:35Z"},{"changes":[{"removed":"","added":"SGrage@gmx.net","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/SGrage@gmx.net","name":"SGrage@gmx.net"},"change_time":"2011-03-28T10:13:51Z"},{"changes":[{"removed":"","added":"dewmigg@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/dewmigg@gmail.com","name":"dewmigg@gmail.com"},"change_time":"2011-03-13T21:34:21Z"},{"changes":[{"removed":"","added":"ulysse@email.it","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/euryalus.0@gmail.com","name":"euryalus.0@gmail.com"},"change_time":"2011-03-13T12:14:04Z"},{"changes":[{"removed":"","added":"heraldo99@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/heraldo99@gmail.com","name":"heraldo99@gmail.com"},"change_time":"2011-03-13T11:36:08Z"},{"changes":[{"removed":"","added":"pardal@gmx.de","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/pardal@gmx.de","name":"pardal@gmx.de"},"change_time":"2011-03-13T02:48:03Z"},{"changes":[{"removed":"","added":"terrell.kelley@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/terrell.kelley@gmail.com","name":"terrell.kelley@gmail.com"},"change_time":"2011-01-31T11:46:28Z"},{"changes":[{"removed":"","added":"kiuzeppe@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/kiuzeppe@gmail.com","name":"kiuzeppe@gmail.com"},"change_time":"2011-01-20T22:13:01Z"},{"changes":[{"removed":"","added":"saneyuki.snyk@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/saneyuki.s.snyk@gmail.com","name":"saneyuki.s.snyk@gmail.com"},"change_time":"2011-01-11T09:25:01Z"},{"changes":[{"removed":"","added":"stephen.donner@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/stephen.donner@gmail.com","name":"stephen.donner@gmail.com"},"change_time":"2011-01-04T21:07:17Z"},{"changes":[{"removed":"","added":"prog_when_resolved_notification@bluebottle.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/prog_when_resolved_notification@bluebottle.com","name":"prog_when_resolved_notification@bluebottle.com"},"change_time":"2011-01-02T18:11:00Z"},{"changes":[{"removed":"jmjeffery@embarqmail.com","added":"","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jmjeffery@embarqmail.com","name":"jmjeffery@embarqmail.com"},"change_time":"2010-12-14T18:17:14Z"},{"changes":[{"removed":"","added":"colmeiro@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/colmeiro@gmail.com","name":"colmeiro@gmail.com"},"change_time":"2010-12-10T23:46:15Z"},{"changes":[{"removed":"","added":"willyaranda@mozilla-hispano.org","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/willyaranda@mozilla-hispano.org","name":"willyaranda@mozilla-hispano.org"},"change_time":"2010-12-10T22:34:30Z"},{"changes":[{"removed":"","added":"dale.bugzilla@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/dale.bugzilla@gmail.com","name":"dale.bugzilla@gmail.com"},"change_time":"2010-12-10T00:50:10Z"},{"changes":[{"removed":"","added":"sabret00the@yahoo.co.uk","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/sabret00the@yahoo.co.uk","name":"sabret00the@yahoo.co.uk"},"change_time":"2010-12-09T17:01:15Z"},{"changes":[{"removed":"","added":"daveryeo@telus.net","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/daveryeo@telus.net","name":"daveryeo@telus.net"},"change_time":"2010-12-09T04:46:59Z"},{"changes":[{"removed":"","added":"bugspam.Callek@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/bugspam.Callek@gmail.com","name":"bugspam.Callek@gmail.com"},"change_time":"2010-12-09T01:05:37Z"},{"changes":[{"removed":"","added":"mcepl@redhat.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/mcepl@redhat.com","name":"mcepl@redhat.com"},"change_time":"2010-12-08T23:55:49Z"},{"changes":[{"removed":"","added":"tymerkaev@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/tymerkaev@gmail.com","name":"tymerkaev@gmail.com"},"change_time":"2010-11-29T10:19:49Z"},{"changes":[{"removed":"","added":"kwierso@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/kwierso@gmail.com","name":"kwierso@gmail.com"},"change_time":"2010-11-29T05:42:05Z"},{"changes":[{"removed":"","added":"mtanalin@yandex.ru","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/mtanalin@yandex.ru","name":"mtanalin@yandex.ru"},"change_time":"2010-11-15T22:33:18Z"},{"changes":[{"removed":"","added":"briks.si@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/briks.si@gmail.com","name":"briks.si@gmail.com"},"change_time":"2010-11-15T20:45:37Z"},{"changes":[{"removed":"","added":"mozdiav@aeons.lv","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/mozdiav@aeons.lv","name":"mozdiav@aeons.lv"},"change_time":"2010-11-12T20:25:54Z"},{"changes":[{"removed":"","added":"soufian.j@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/soufian.j@gmail.com","name":"soufian.j@gmail.com"},"change_time":"2010-11-12T16:43:16Z"},{"changes":[{"removed":"","added":"jmjeffery@embarqmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jmjeffery@embarqmail.com","name":"jmjeffery@embarqmail.com"},"change_time":"2010-11-12T12:59:58Z"},{"changes":[{"removed":"","added":"markc@qsiuk.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/markc@qsiuk.com","name":"markc@qsiuk.com"},"change_time":"2010-11-12T12:14:42Z"},{"changes":[{"removed":"","added":"thibaut.bethune@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/thibaut.bethune@gmail.com","name":"thibaut.bethune@gmail.com"},"change_time":"2010-11-11T22:54:55Z"},{"changes":[{"removed":"","added":"gmontagu@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/gmontagu@gmail.com","name":"gmontagu@gmail.com"},"change_time":"2010-11-11T22:45:52Z"},{"changes":[{"removed":"","added":"KenSaunders@AccessFirefox.org","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/KenSaunders@AccessFirefox.org","name":"KenSaunders@AccessFirefox.org"},"change_time":"2010-11-11T19:26:57Z"},{"changes":[{"removed":"","added":"lists@eitanadler.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/lists@eitanadler.com","name":"lists@eitanadler.com"},"change_time":"2010-11-11T18:07:05Z"},{"changes":[{"removed":"","added":"geekshadow@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/geekshadow@gmail.com","name":"geekshadow@gmail.com"},"change_time":"2010-10-20T09:22:55Z"},{"changes":[{"removed":"","added":"webmaster@keryx.se","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/webmaster@keryx.se","name":"webmaster@keryx.se"},"change_time":"2010-10-20T09:06:57Z"},{"changes":[{"removed":"","added":"bugzilla@zirro.se","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/bugzilla@zirro.se","name":"bugzilla@zirro.se"},"change_time":"2010-10-20T09:03:47Z"},{"changes":[{"removed":"","added":"supernova00@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/supernova00@gmail.com","name":"supernova00@gmail.com"},"change_time":"2010-10-19T21:35:02Z"},{"changes":[{"removed":"","added":"beltzner@mozilla.com, dtownsend@mozilla.com, jgriffin@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jgriffin@mozilla.com","name":"jgriffin@mozilla.com"},"change_time":"2010-10-08T22:07:52Z"},{"changes":[{"removed":"","added":"gavin.sharp@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/gavin.sharp@gmail.com","name":"gavin.sharp@gmail.com"},"change_time":"2010-09-24T18:44:50Z"},{"changes":[{"removed":"","added":"omarb.public@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/omarb.public@gmail.com","name":"omarb.public@gmail.com"},"change_time":"2010-09-06T18:12:39Z"},{"changes":[{"removed":"","added":"David.Vo2+bmo@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/auscompgeek@sumovolunteers.org","name":"auscompgeek@sumovolunteers.org"},"change_time":"2010-09-03T02:57:43Z"},{"changes":[{"removed":"","added":"mcs@pearlcrescent.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/mcs@pearlcrescent.com","name":"mcs@pearlcrescent.com"},"change_time":"2010-09-02T02:33:22Z"},{"changes":[{"removed":"","added":"archaeopteryx@coole-files.de","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/archaeopteryx@coole-files.de","name":"archaeopteryx@coole-files.de"},"change_time":"2010-09-01T09:17:01Z"},{"changes":[{"removed":"","added":"antoine.mechelynck@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/antoine.mechelynck@gmail.com","name":"antoine.mechelynck@gmail.com"},"change_time":"2010-09-01T03:05:29Z"},{"changes":[{"removed":"","added":"m-wada@japan.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/m-wada@japan.com","name":"m-wada@japan.com"},"change_time":"2010-08-31T09:53:39Z"},{"changes":[{"removed":"","added":"bugzilla@standard8.plus.com, ludovic@mozillamessaging.com, vseerror@lehigh.edu","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ludovic@mozilla.com","name":"ludovic@mozilla.com"},"change_time":"2010-08-31T06:10:50Z"},{"changes":[{"removed":"","added":"grenavitar@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/grenavitar@gmail.com","name":"grenavitar@gmail.com"},"change_time":"2010-08-31T03:59:31Z"},{"changes":[{"removed":"","added":"jorge@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jorge@mozilla.com","name":"jorge@mozilla.com"},"change_time":"2010-08-30T18:18:56Z"},{"changes":[{"removed":"","added":"ehsan@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ehsan@mozilla.com","name":"ehsan@mozilla.com"},"change_time":"2010-08-30T18:09:28Z"},{"changes":[{"removed":"","added":"tyler.downer@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/tyler.downer@gmail.com","name":"tyler.downer@gmail.com"},"change_time":"2010-08-30T17:40:02Z"},{"changes":[{"removed":"","added":"asqueella@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/asqueella@gmail.com","name":"asqueella@gmail.com"},"change_time":"2010-08-26T20:47:46Z"},{"changes":[{"removed":"","added":"590788","field_name":"depends_on"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2010-08-26T15:49:52Z"},{"changes":[{"removed":"","added":"benjamin@smedbergs.us","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2010-08-26T08:24:38Z"},{"changes":[{"removed":"","added":"bugs-bmo@unknownbrackets.org","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/bugs-bmo@unknownbrackets.org","name":"bugs-bmo@unknownbrackets.org"},"change_time":"2010-08-26T07:24:55Z"},{"changes":[{"removed":"","added":"highmind63@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/highmind63@gmail.com","name":"highmind63@gmail.com"},"change_time":"2010-08-26T03:45:34Z"},{"changes":[{"removed":"stream@abv.bg","added":"","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/stream@abv.bg","name":"stream@abv.bg"},"change_time":"2010-08-24T16:50:19Z"},{"changes":[{"removed":"Infrastructure","added":"ProfileManager","field_name":"component"},{"removed":"jhammel@mozilla.com","added":"nobody@mozilla.org","field_name":"assigned_to"},{"removed":"infra@testing.bugs","added":"profilemanager@testing.bugs","field_name":"qa_contact"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2010-08-24T15:27:00Z"},{"changes":[{"removed":"","added":"justin.lebar+bug@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/justin.lebar+bug@gmail.com","name":"justin.lebar+bug@gmail.com"},"change_time":"2010-07-28T21:26:38Z"},{"changes":[{"removed":"mozilla.bugs@alyoung.com","added":"","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/mozilla.bugs@alyoung.com","name":"mozilla.bugs@alyoung.com"},"change_time":"2010-07-22T23:59:31Z"},{"changes":[{"removed":"","added":"deletesoftware@yandex.ru","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/deletesoftware@yandex.ru","name":"deletesoftware@yandex.ru"},"change_time":"2010-06-19T14:19:55Z"},{"changes":[{"removed":"NEW","added":"ASSIGNED","field_name":"status"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2010-06-14T17:26:17Z"},{"changes":[{"removed":"nobody@mozilla.org","added":"jhammel@mozilla.com","field_name":"assigned_to"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2010-05-27T15:42:33Z"},{"changes":[{"removed":"","added":"harthur@cmu.edu","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2010-03-18T20:49:42Z"},{"changes":[{"removed":"","added":"k0scist@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2010-03-09T17:10:48Z"},{"changes":[{"removed":"","added":"stream@abv.bg","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/stream@abv.bg","name":"stream@abv.bg"},"change_time":"2010-02-07T01:32:42Z"},{"changes":[{"removed":"","added":"VanillaMozilla@hotmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/VanillaMozilla@hotmail.com","name":"VanillaMozilla@hotmail.com"},"change_time":"2010-01-22T17:28:59Z"},{"changes":[{"removed":"","added":"reed@reedloden.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/reed@reedloden.com","name":"reed@reedloden.com"},"change_time":"2010-01-17T19:22:31Z"},{"changes":[{"removed":"","added":"blizzard@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/blizzard@mozilla.com","name":"blizzard@mozilla.com"},"change_time":"2010-01-17T01:12:28Z"},{"changes":[{"removed":"","added":"540194","field_name":"blocks"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/david@rossde.com","name":"david@rossde.com"},"change_time":"2010-01-16T19:18:12Z"},{"changes":[{"removed":"","added":"fullmetaljacket.xp+bugmail@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/fullmetaljacket.xp+bugmail@gmail.com","name":"fullmetaljacket.xp+bugmail@gmail.com"},"change_time":"2010-01-15T09:22:52Z"},{"changes":[{"removed":"","added":"wladow@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/wladow@gmail.com","name":"wladow@gmail.com"},"change_time":"2010-01-14T22:31:39Z"},{"changes":[{"removed":"278860","added":"","field_name":"depends_on"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2010-01-14T17:46:11Z"},{"changes":[{"removed":"","added":"chado_moz@yahoo.co.jp","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/chado_moz@yahoo.co.jp","name":"chado_moz@yahoo.co.jp"},"change_time":"2010-01-14T15:30:57Z"},{"changes":[{"removed":"","added":"mnyromyr@tprac.de","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/mnyromyr@tprac.de","name":"mnyromyr@tprac.de"},"change_time":"2010-01-14T10:56:22Z"},{"changes":[{"removed":"","added":"spitfire.kuden@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/spitfire.kuden@gmail.com","name":"spitfire.kuden@gmail.com"},"change_time":"2010-01-14T10:15:48Z"},{"changes":[{"removed":"","added":"mozilla.bugs@alyoung.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/mozilla.bugs@alyoung.com","name":"mozilla.bugs@alyoung.com"},"change_time":"2010-01-14T05:57:27Z"},{"changes":[{"removed":"","added":"nori@noasobi.net","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/nori@noasobi.net","name":"nori@noasobi.net"},"change_time":"2010-01-14T05:41:47Z"},{"changes":[{"removed":"","added":"michaelkohler@live.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/michaelkohler@linux.com","name":"michaelkohler@linux.com"},"change_time":"2010-01-14T01:39:36Z"},{"changes":[{"removed":"","added":"kairo@kairo.at","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/kairo@kairo.at","name":"kairo@kairo.at"},"change_time":"2010-01-14T00:16:58Z"},{"changes":[{"removed":"normal","added":"enhancement","field_name":"severity"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/david@rossde.com","name":"david@rossde.com"},"change_time":"2010-01-14T00:08:41Z"},{"changes":[{"removed":"","added":"ryanvm@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ryanvm@gmail.com","name":"ryanvm@gmail.com"},"change_time":"2010-01-14T00:02:33Z"},{"changes":[{"removed":"","added":"phiw@l-c-n.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/phiw@l-c-n.com","name":"phiw@l-c-n.com"},"change_time":"2010-01-13T23:56:59Z"},{"changes":[{"removed":"","added":"pcvrcek@mozilla.cz","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/pcvrcek@mozilla.cz","name":"pcvrcek@mozilla.cz"},"change_time":"2010-01-13T23:45:00Z"},{"changes":[{"removed":"","added":"axel@pike.org","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/l10n@mozilla.com","name":"l10n@mozilla.com"},"change_time":"2010-01-13T23:17:08Z"},{"changes":[{"removed":"","added":"stefanh@inbox.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/stefanh@inbox.com","name":"stefanh@inbox.com"},"change_time":"2010-01-13T23:12:13Z"},{"changes":[{"removed":"x86","added":"All","field_name":"platform"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2010-01-13T23:06:25Z"},{"changes":[{"removed":"","added":"johnath@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/johnath@mozilla.com","name":"johnath@mozilla.com"},"change_time":"2010-01-13T23:04:10Z"},{"changes":[{"removed":"","added":"mrmazda@earthlink.net","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/mrmazda@earthlink.net","name":"mrmazda@earthlink.net"},"change_time":"2010-01-13T22:46:40Z"},{"changes":[{"removed":"","added":"iann_bugzilla@blueyonder.co.uk","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/iann_bugzilla@blueyonder.co.uk","name":"iann_bugzilla@blueyonder.co.uk"},"change_time":"2010-01-13T21:41:32Z"},{"changes":[{"removed":"","added":"xtc4uall@gmail.com","field_name":"cc"},{"removed":"","added":"214675","field_name":"blocks"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/xtc4uall@gmail.com","name":"xtc4uall@gmail.com"},"change_time":"2010-01-13T21:34:20Z"},{"changes":[{"removed":"","added":"hskupin@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2010-01-13T21:31:14Z"},{"changes":[{"removed":"","added":"hickendorffbas@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hickendorffbas@gmail.com","name":"hickendorffbas@gmail.com"},"change_time":"2010-01-13T20:58:31Z"}],"summary":"New Test/Triage Profile Manager Application","last_change_time":"2011-07-18T12:06:33Z","comments":[{"is_private":false,"creator":{"real_name":"Jonathan Griffin (:jgriffin)","name":"jgriffin"},"text":"","id":5480531,"creation_time":"2011-05-19T17:30:57Z"},{"is_private":false,"creator":{"real_name":"Spencer Selander [greenknight]","name":"spencerselander"},"text":"","id":5476603,"creation_time":"2011-05-18T07:58:37Z"},{"is_private":false,"creator":{"real_name":"Paul [sabret00the]","name":"sabret00the"},"text":"","id":5373656,"creation_time":"2011-03-28T06:59:22Z"},{"is_private":false,"creator":{"real_name":"Jonathan Griffin (:jgriffin)","name":"jgriffin"},"text":"","id":5240204,"creation_time":"2011-01-31T17:58:24Z"},{"is_private":false,"creator":{"real_name":"Terrell Kelley","name":"terrell.kelley"},"text":"","id":5239501,"creation_time":"2011-01-31T11:46:28Z"},{"is_private":false,"creator":{"real_name":"Benjamin Smedberg [:bsmedberg]","name":"benjamin"},"text":"","id":5002085,"creation_time":"2010-10-11T18:04:48Z"},{"is_private":false,"creator":{"real_name":"Clint Talbert ( :ctalbert )","name":"ctalbert"},"text":"","id":5002054,"creation_time":"2010-10-11T17:52:13Z"},{"is_private":false,"creator":{"real_name":"Dave Townsend (:Mossop)","name":"dtownsend"},"text":"","id":4999117,"creation_time":"2010-10-08T22:12:23Z"},{"is_private":false,"creator":{"real_name":"Jonathan Griffin (:jgriffin)","name":"jgriffin"},"text":"","id":4999107,"creation_time":"2010-10-08T22:07:52Z"},{"is_private":false,"creator":{"real_name":"Benjamin Smedberg [:bsmedberg]","name":"benjamin"},"text":"","id":4991281,"creation_time":"2010-10-06T18:42:20Z"},{"is_private":false,"creator":{"real_name":"David E. Ross","name":"david"},"text":"","id":4990011,"creation_time":"2010-10-06T17:50:51Z"},{"is_private":false,"creator":{"real_name":"Benjamin Smedberg [:bsmedberg]","name":"benjamin"},"text":"","id":4986111,"creation_time":"2010-10-05T02:54:24Z"},{"is_private":false,"creator":{"real_name":"Clint Talbert ( :ctalbert )","name":"ctalbert"},"text":"","id":4985884,"creation_time":"2010-10-05T00:30:08Z"},{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":4921920,"creation_time":"2010-09-08T15:20:06Z"},{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":4892626,"creation_time":"2010-08-26T16:05:50Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":4891863,"creation_time":"2010-08-26T08:24:38Z"},{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":4886114,"creation_time":"2010-08-24T15:31:05Z"},{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":4743315,"creation_time":"2010-06-14T17:26:17Z"},{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":4716160,"creation_time":"2010-05-27T16:53:53Z"},{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":4716148,"creation_time":"2010-05-27T16:44:32Z"},{"is_private":false,"creator":{"real_name":"David E. Ross","name":"david"},"text":"","id":4492322,"creation_time":"2010-01-18T15:12:52Z"},{"is_private":false,"creator":{"real_name":"Christopher Blizzard (:blizzard)","name":"blizzard"},"text":"","id":4490922,"creation_time":"2010-01-17T01:13:33Z"},{"is_private":false,"creator":{"real_name":"Clint Talbert ( :ctalbert )","name":"ctalbert"},"text":"","id":4487552,"creation_time":"2010-01-14T17:46:11Z"},{"is_private":false,"creator":{"real_name":"David E. Ross","name":"david"},"text":"","id":4486520,"creation_time":"2010-01-14T00:08:41Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":4486434,"creation_time":"2010-01-13T23:06:25Z"},{"is_private":false,"creator":{"real_name":"XtC4UaLL [:xtc4uall]","name":"xtc4uall"},"text":"","id":4486282,"creation_time":"2010-01-13T21:34:20Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":4486275,"creation_time":"2010-01-13T21:31:14Z"},{"is_private":false,"creator":{"real_name":"Clint Talbert ( :ctalbert )","name":"ctalbert"},"text":"","id":4486171,"creation_time":"2010-01-13T20:37:30Z"}],"id":539524},"events":[{"time":"2011-07-18T12:06:33Z","changeset":{"changes":[{"removed":"","added":"jwatt@jwatt.org","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jwatt@jwatt.org","name":"jwatt@jwatt.org"},"change_time":"2011-07-18T12:06:33Z"}}]},{"bug":{"history":[{"changes":[{"removed":"","added":"568943","field_name":"blocks"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-07-18T09:21:42Z"},{"changes":[{"removed":"[mozmill-next?]","added":"[mozmill-2.0-][mozmill-next?]","field_name":"whiteboard"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-05-25T12:01:37Z"},{"changes":[{"removed":"[mozmill-2.0+]","added":"[mozmill-next?]","field_name":"whiteboard"},{"removed":"critical","added":"normal","field_name":"severity"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2011-05-24T22:41:14Z"},{"changes":[{"removed":"[mozmill-2.0?]","added":"[mozmill-2.0+]","field_name":"whiteboard"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2011-03-29T21:43:13Z"},{"changes":[{"removed":"","added":"dataloss","field_name":"keywords"},{"removed":"enhancement","added":"critical","field_name":"severity"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-03-21T00:24:58Z"},{"changes":[{"removed":"nobody@mozilla.org","added":"jhammel@mozilla.com","field_name":"assigned_to"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-03-18T21:05:30Z"},{"changes":[{"removed":"","added":"ahalberstadt@mozilla.com, ctalbert@mozilla.com, fayearthur+bugs@gmail.com, hskupin@gmail.com","field_name":"cc"},{"removed":"","added":"[mozmill-2.0?]","field_name":"whiteboard"},{"removed":"normal","added":"enhancement","field_name":"severity"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-03-18T16:29:22Z"}],"summary":"should be able to clone from a profile as a basis","last_change_time":"2011-07-18T09:21:42Z","comments":[{"is_private":false,"creator":{"real_name":"Clint Talbert ( :ctalbert )","name":"ctalbert"},"text":"","id":5490872,"creation_time":"2011-05-24T22:41:14Z"},{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":5357384,"creation_time":"2011-03-21T16:41:15Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5356453,"creation_time":"2011-03-21T00:24:58Z"},{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":5353489,"creation_time":"2011-03-18T16:32:57Z"},{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":5353480,"creation_time":"2011-03-18T16:29:22Z"},{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":5353472,"creation_time":"2011-03-18T16:27:14Z"}],"id":642843},"events":[{"time":"2011-07-18T09:21:42Z","changeset":{"changes":[{"removed":"","added":"568943","field_name":"blocks"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-07-18T09:21:42Z"}}]}]
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/package.json b/node_modules/node-firefox-find-ports/node_modules/firefox-client/package.json
new file mode 100644
index 0000000..8985573
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/package.json
@@ -0,0 +1,52 @@
+{
+  "name": "firefox-client",
+  "description": "Firefox remote debugging client",
+  "version": "0.3.0",
+  "author": {
+    "name": "Heather Arthur",
+    "email": "fayearthur@gmail.com"
+  },
+  "main": "index.js",
+  "repository": {
+    "type": "git",
+    "url": "http://github.com/harthur/firefox-client.git"
+  },
+  "dependencies": {
+    "colors": "0.5.x",
+    "js-select": "~0.6.0"
+  },
+  "devDependencies": {
+    "connect": "~2.8.2",
+    "mocha": "~1.12.0"
+  },
+  "keywords": [
+    "firefox",
+    "debugger",
+    "remote debugging"
+  ],
+  "bugs": {
+    "url": "https://github.com/harthur/firefox-client/issues"
+  },
+  "homepage": "https://github.com/harthur/firefox-client",
+  "_id": "firefox-client@0.3.0",
+  "dist": {
+    "shasum": "3794460f6eb6afcf41376addcbc7462e24a4cd8b",
+    "tarball": "http://registry.npmjs.org/firefox-client/-/firefox-client-0.3.0.tgz"
+  },
+  "_from": "firefox-client@^0.3.0",
+  "_npmVersion": "1.4.3",
+  "_npmUser": {
+    "name": "harth",
+    "email": "fayearthur@gmail.com"
+  },
+  "maintainers": [
+    {
+      "name": "harth",
+      "email": "fayearthur@gmail.com"
+    }
+  ],
+  "directories": {},
+  "_shasum": "3794460f6eb6afcf41376addcbc7462e24a4cd8b",
+  "_resolved": "https://registry.npmjs.org/firefox-client/-/firefox-client-0.3.0.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/test.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/test.js
new file mode 100644
index 0000000..1f2d334
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/test.js
@@ -0,0 +1,54 @@
+var assert = require('assert'),
+    FirefoxClient = require("./index");
+
+
+var url = "file:///Users/harth/repos/sass-wwcode/index.html";
+
+loadUrl(url, function(tab) {
+  tab.StyleSheets.getStyleSheets(function(err, sheets) {
+    var sheet = sheets[1];
+    sheet.getOriginalSources(function(err, sources) {
+      console.log(err);
+      console.log(sources[0].url);
+      sources[0].getText(function(err, resp) {
+        console.log(err);
+        console.log(resp);
+      })
+    });
+    console.log(sheet.href);
+  });
+})
+
+
+/**
+ * Helper functions
+ */
+function loadUrl(url, callback) {
+  getFirstTab(function(tab) {
+    console.log("GOT TAB");
+    tab.navigateTo(url);
+
+    tab.once("navigate", function() {
+      console.log("NAVIGATED");
+      callback(tab);
+    });
+  });
+}
+
+function getFirstTab(callback) {
+  var client = new FirefoxClient({log: true});
+
+  client.connect(function() {
+    client.listTabs(function(err, tabs) {
+      if (err) throw err;
+
+      var tab = tabs[0];
+
+      // attach so we can receive load events
+      tab.attach(function(err) {
+        if (err) throw err;
+        callback(tab);
+      })
+    });
+  });
+}
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/README.md b/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/README.md
new file mode 100644
index 0000000..75bd86f
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/README.md
@@ -0,0 +1,29 @@
+# testing
+
+### dependencies
+To run the tests in this directory, first install the dev dependencies with this command from the top-level directory:
+
+```
+npm install --dev
+```
+
+You'll also have to globally install [mocha](http://visionmedia.github.io/mocha). `npm install mocha -g`.
+
+### running
+First open up a [Firefox Nightly build](http://nightly.mozilla.org/) and serve the test files up:
+
+```
+node server.js &
+```
+
+visit the url the server tells you to visit.
+
+Finally, run the tests with:
+
+```
+mocha test-dom.js --timeout 10000
+````
+
+The increased timeout is to give you enough time to manually verify the incoming connection in Firefox.
+
+Right now you have to run each test individually, until Firefox [bug 891003](https://bugzilla.mozilla.org/show_bug.cgi?id=891003) is fixed.
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/pages/dom.html b/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/pages/dom.html
new file mode 100644
index 0000000..c662637
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/pages/dom.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>DOM tests</title>
+  <meta charset="utf-8">
+</head>
+<body>
+  <main>
+    <section id="test-section">
+      <div id="test1" class="item"></div>
+      <div id="test2" class="item">
+          <div id="child1"></div>
+          <div id="child2"></div>
+      </div>
+      <div id="test3" class="item"></div>
+    </section>
+  </main>
+</body>
+</html>
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/pages/index.html b/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/pages/index.html
new file mode 100644
index 0000000..4a67c5a
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/pages/index.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Firefox remote debugging client tests</title>
+  <meta charset="utf-8">
+  <style>
+    header {
+      font-family: Georgia, sans-serif;
+      font-size: 3em;
+      margin: 3em;
+      color: hsl(30, 90%, 50%);
+    }
+  </style>
+</head>
+<body>
+  <header>
+    Firefox Client Tests
+  </header>
+  <main>
+  </main>
+</body>
+</html>
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/pages/logs.html b/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/pages/logs.html
new file mode 100644
index 0000000..56b7c1d
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/pages/logs.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Logs tests</title>
+  <meta charset="utf-8">
+
+  <script>
+    console.log("hi");
+    console.dir({a: 3});
+    foo;
+  </script>
+</head>
+<body>
+  <main>
+  </main>
+</body>
+</html>
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/pages/network.html b/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/pages/network.html
new file mode 100644
index 0000000..922e27c
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/pages/network.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Logs tests</title>
+  <meta charset="utf-8">
+
+  <script>
+    function sendRequest() {
+      var req = new XMLHttpRequest();
+
+      req.open("GET", "test-network.json", true);
+      req.responseType = "json";
+      req.setRequestHeader("test-header", "test-value");
+      req.send();
+    }
+  </script>
+</head>
+<body>
+  <main>
+  </main>
+</body>
+</html>
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/pages/stylesheet1.css b/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/pages/stylesheet1.css
new file mode 100644
index 0000000..ff5907f
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/pages/stylesheet1.css
@@ -0,0 +1,9 @@
+main {
+  font-family: Georgia, sans-serif;
+  color: black;
+}
+
+* {
+  padding: 0;
+  margin: 0;
+}
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/pages/stylesheets.html b/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/pages/stylesheets.html
new file mode 100644
index 0000000..8cab1e8
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/pages/stylesheets.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Stylesheets tests</title>
+  <meta charset="utf-8">
+  <style>
+    main {
+      margin: 20px;
+    }
+  </style>
+  <link rel="stylesheet" href="stylesheet1.css"/>
+</head>
+<body>
+  <main>
+    <main>
+      <div>Stylesheet Tests</div>
+    </main>
+  </main>
+</body>
+</html>
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/pages/test-network.json b/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/pages/test-network.json
new file mode 100644
index 0000000..5b89410
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/pages/test-network.json
@@ -0,0 +1,4 @@
+{
+  "a": 2,
+  "b": "hello"
+}
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/server.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/server.js
new file mode 100644
index 0000000..aca94f7
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/server.js
@@ -0,0 +1,8 @@
+var path = require("path"),
+    connect = require('connect');
+
+var port = 3000;
+
+connect.createServer(connect.static(path.join(__dirname, "pages"))).listen(port);
+
+console.log("visit:\nhttp://127.0.0.1:" + port + "/index.html");
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/test-console.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/test-console.js
new file mode 100644
index 0000000..9198ad0
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/test-console.js
@@ -0,0 +1,64 @@
+var assert = require("assert"),
+    utils = require("./utils");
+
+var Console;
+
+before(function(done) {
+  utils.loadTab('dom.html', function(aTab) {
+    Console = aTab.Console;
+    done();
+  });
+});
+
+// Console - evaluateJS()
+
+describe('evaluateJS()', function() {
+  it('should evaluate expr to number', function(done) {
+    Console.evaluateJS('6 + 7', function(err, resp) {
+      assert.strictEqual(err, null);
+      assert.equal(resp.result, 13);
+      done();
+    })
+  })
+
+  it('should evaluate expr to boolean', function(done) {
+    Console.evaluateJS('!!window', function(err, resp) {
+      assert.strictEqual(err, null);
+      assert.strictEqual(resp.result, true);
+      done();
+    })
+  })
+
+  it('should evaluate expr to string', function(done) {
+    Console.evaluateJS('"hello"', function(err, resp) {
+      assert.strictEqual(err, null);
+      assert.equal(resp.result, "hello");
+      done();
+    })
+  })
+
+  it('should evaluate expr to JSObject', function(done) {
+    Console.evaluateJS('x = {a: 2, b: "hello"}', function(err, resp) {
+      assert.strictEqual(err, null);
+      assert.ok(resp.result.ownPropertyNames, "result has JSObject methods");
+      done();
+    })
+  })
+
+  it('should evaluate to undefined', function(done) {
+    Console.evaluateJS('undefined', function(err, resp) {
+      assert.strictEqual(err, null);
+      assert.ok(resp.result.type, "undefined");
+      done();
+    })
+  })
+
+  it('should have exception in response', function(done) {
+    Console.evaluateJS('blargh', function(err, resp) {
+      assert.strictEqual(err, null);
+      assert.equal(resp.exception.class, "Error"); // TODO: error should be JSObject
+      assert.equal(resp.exceptionMessage, "ReferenceError: blargh is not defined");
+      done();
+    })
+  })
+})
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/test-dom.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/test-dom.js
new file mode 100644
index 0000000..9206561
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/test-dom.js
@@ -0,0 +1,329 @@
+var assert = require("assert"),
+    utils = require("./utils");
+
+var doc;
+var DOM;
+var node;
+var firstNode;
+var lastNode;
+
+before(function(done) {
+  utils.loadTab('dom.html', function(aTab) {
+    DOM = aTab.DOM;
+    DOM.document(function(err, aDoc) {
+      doc = aDoc;
+      DOM.querySelectorAll(".item", function(err, list) {
+        list.items(function(err, items) {
+          firstNode = items[0];
+          node = items[1];
+          lastNode = items[2];
+          done();
+        })
+      })
+    })
+  });
+});
+
+// DOM - document(), documentElement()
+
+describe('document()', function() {
+  it('should get document node', function(done) {
+    DOM.document(function(err, doc) {
+      assert.strictEqual(err, null);
+      assert.equal(doc.nodeName, "#document");
+      assert.equal(doc.nodeType, 9);
+      done();
+    })
+  })
+})
+
+
+describe('documentElement()', function() {
+  it('should get documentElement node', function(done) {
+    DOM.documentElement(function(err, elem) {
+      assert.strictEqual(err, null);
+      assert.equal(elem.nodeName, "HTML");
+      assert.equal(elem.nodeType, 1);
+      done();
+    })
+  })
+})
+
+describe('querySelector()', function() {
+  it('should get first item node', function(done) {
+    DOM.querySelector(".item", function(err, child) {
+      assert.strictEqual(err, null);
+      assert.equal(child.getAttribute("id"), "test1");
+      assert.ok(child.querySelector, "node has node methods");
+      done();
+    })
+  })
+})
+
+describe('querySelector()', function() {
+  it('should get all item nodes', function(done) {
+    DOM.querySelectorAll(".item", function(err, list) {
+      assert.strictEqual(err, null);
+      assert.equal(list.length, 3);
+
+      list.items(function(err, children) {
+        assert.strictEqual(err, null);
+        var ids = children.map(function(child) {
+          assert.ok(child.querySelector, "list item has node methods");
+          return child.getAttribute("id");
+        })
+        assert.deepEqual(ids, ["test1","test2","test3"]);
+        done();
+      })
+    })
+  })
+})
+
+// Node - parentNode(), parent(), siblings(), nextSibling(), previousSibling(),
+// querySelector(), querySelectorAll(), innerHTML(), outerHTML(), getAttribute(),
+// setAttribute()
+
+describe('parentNode()', function() {
+  it('should get parent node', function(done) {
+    node.parentNode(function(err, parent) {
+      assert.strictEqual(err, null);
+      assert.equal(parent.nodeName, "SECTION");
+      assert.ok(parent.querySelector, "parent has node methods");
+      done();
+    })
+  })
+
+  it('should be null for document parentNode', function(done) {
+    doc.parentNode(function(err, parent) {
+      assert.strictEqual(err, null);
+      assert.strictEqual(parent, null);
+      done();
+    })
+  })
+})
+
+describe('parents()', function() {
+  it('should get ancestor nodes', function(done) {
+    node.parents(function(err, ancestors) {
+      assert.strictEqual(err, null);
+      var names = ancestors.map(function(ancestor) {
+        assert.ok(ancestor.querySelector, "ancestor has node methods");
+        return ancestor.nodeName;
+      })
+      assert.deepEqual(names, ["SECTION","MAIN","BODY","HTML","#document"]);
+      done();
+    })
+  })
+})
+
+describe('children()', function() {
+  it('should get child nodes', function(done) {
+    node.children(function(err, children) {
+      assert.strictEqual(err, null);
+      var ids = children.map(function(child) {
+        assert.ok(child.querySelector, "child has node methods");
+        return child.getAttribute("id");
+      })
+      assert.deepEqual(ids, ["child1","child2"]);
+      done();
+    })
+  })
+})
+
+describe('siblings()', function() {
+  it('should get sibling nodes', function(done) {
+    node.siblings(function(err, siblings) {
+      assert.strictEqual(err, null);
+      var ids = siblings.map(function(sibling) {
+        assert.ok(sibling.querySelector, "sibling has node methods");
+        return sibling.getAttribute("id");
+      })
+      assert.deepEqual(ids, ["test1","test2","test3"]);
+      done();
+    })
+  })
+})
+
+describe('nextSibling()', function() {
+  it('should get next sibling node', function(done) {
+    node.nextSibling(function(err, sibling) {
+      assert.strictEqual(err, null);
+      assert.equal(sibling.getAttribute("id"), "test3");
+      assert.ok(sibling.querySelector, "next sibling has node methods");
+      done();
+    })
+  })
+
+  it('should be null if no next sibling', function(done) {
+    lastNode.nextSibling(function(err, sibling) {
+      assert.strictEqual(err, null);
+      assert.strictEqual(sibling, null);
+      done();
+    })
+  })
+})
+
+describe('previousSibling()', function() {
+  it('should get next sibling node', function(done) {
+    node.previousSibling(function(err, sibling) {
+      assert.strictEqual(err, null);
+      assert.equal(sibling.getAttribute("id"), "test1");
+      assert.ok(sibling.querySelector, "next sibling has node methods");
+      done();
+    })
+  })
+
+  it('should be null if no prev sibling', function(done) {
+    firstNode.previousSibling(function(err, sibling) {
+      assert.strictEqual(err, null);
+      assert.strictEqual(sibling, null);
+      done();
+    })
+  })
+})
+
+describe('querySelector()', function() {
+  it('should get first child node', function(done) {
+    node.querySelector("*", function(err, child) {
+      assert.strictEqual(err, null);
+      assert.equal(child.getAttribute("id"), "child1");
+      assert.ok(child.querySelector, "node has node methods");
+      done();
+    })
+  })
+
+  it('should be null if no nodes with selector', function(done) {
+    node.querySelector("blarg", function(err, resp) {
+      assert.strictEqual(err, null);
+      assert.strictEqual(resp, null);
+      done();
+    })
+  })
+})
+
+describe('querySelectorAll()', function() {
+  it('should get all child nodes', function(done) {
+    node.querySelectorAll("*", function(err, list) {
+      assert.strictEqual(err, null);
+      assert.equal(list.length, 2);
+
+      list.items(function(err, children) {
+        assert.strictEqual(err, null);
+        var ids = children.map(function(child) {
+          assert.ok(child.querySelector, "list item has node methods");
+          return child.getAttribute("id");
+        })
+        assert.deepEqual(ids, ["child1", "child2"]);
+        done();
+      })
+    })
+  })
+
+  it('should get nodes from "start" to "end"', function(done) {
+    doc.querySelectorAll(".item", function(err, list) {
+      assert.strictEqual(err, null);
+      assert.equal(list.length, 3);
+
+      list.items(1, 2, function(err, items) {
+        assert.strictEqual(err, null);
+        assert.equal(items.length, 1);
+        assert.deepEqual(items[0].getAttribute("id"), "test2")
+        done();
+      })
+    })
+  })
+
+  it('should get nodes from "start"', function(done) {
+    doc.querySelectorAll(".item", function(err, list) {
+      assert.strictEqual(err, null);
+      assert.equal(list.length, 3);
+
+      list.items(1, function(err, items) {
+        assert.strictEqual(err, null);
+        assert.equal(items.length, 2);
+        var ids = items.map(function(item) {
+          assert.ok(item.querySelector, "list item has node methods");
+          return item.getAttribute("id");
+        })
+        assert.deepEqual(ids, ["test2","test3"]);
+        done();
+      })
+    })
+  })
+
+  it('should be empty list if no nodes with selector', function(done) {
+    node.querySelectorAll("blarg", function(err, list) {
+      assert.strictEqual(err, null);
+      assert.equal(list.length, 0);
+
+      list.items(function(err, items) {
+        assert.strictEqual(err, null);
+        assert.deepEqual(items, []);
+        done();
+      })
+    })
+  })
+})
+
+describe('innerHTML()', function() {
+  it('should get innerHTML of node', function(done) {
+    node.innerHTML(function(err, text) {
+      assert.strictEqual(err, null);
+      assert.equal(text, '\n          <div id="child1"></div>\n'
+                   + '          <div id="child2"></div>\n      ');
+      done();
+    })
+  })
+})
+
+describe('outerHTML()', function() {
+  it('should get outerHTML of node', function(done) {
+    node.outerHTML(function(err, text) {
+      assert.strictEqual(err, null);
+      assert.equal(text, '<div id="test2" class="item">\n'
+                   + '          <div id="child1"></div>\n'
+                   + '          <div id="child2"></div>\n      '
+                   + '</div>');
+      done();
+    })
+  })
+})
+
+describe('highlight()', function() {
+  it('should highlight node', function(done) {
+    node.highlight(function(err, resp) {
+      assert.strictEqual(err, null);
+      done();
+    })
+  })
+})
+
+/* MUST BE LAST */
+describe('remove()', function() {
+  it('should remove node', function(done) {
+    node.remove(function(err, nextSibling) {
+      assert.strictEqual(err, null);
+      assert.equal(nextSibling.getAttribute("id"), "test3");
+
+      doc.querySelectorAll(".item", function(err, list) {
+        assert.strictEqual(err, null);
+        assert.equal(list.length, 2);
+        done();
+      })
+    })
+  })
+
+  it("should err if performing further operations after release()", function(done) {
+    node.release(function(err) {
+      assert.strictEqual(err, null);
+
+      node.innerHTML(function(err, text) {
+        assert.equal(err.message, "TypeError: node is null")
+        assert.equal(err.toString(), "unknownError: TypeError: node is null");
+        done();
+      })
+    })
+  })
+})
+
+
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/test-jsobject.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/test-jsobject.js
new file mode 100644
index 0000000..f416674
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/test-jsobject.js
@@ -0,0 +1,115 @@
+var assert = require("assert"),
+    utils = require("./utils");
+
+var Console;
+var obj;
+var func;
+
+before(function(done) {
+  utils.loadTab('dom.html', function(aTab) {
+    Console = aTab.Console;
+    Console.evaluateJS('x = {a: 2, b: {c: 3}, get d() {return 4;}}', function(err, resp) {
+      obj = resp.result;
+
+      var input = 'y = function testfunc(a, b) { return a + b; }';
+      Console.evaluateJS(input, function(err, resp) {
+        func = resp.result;
+        done();
+      })
+    });
+  });
+});
+
+// JSObject - ownPropertyNames(), ownPropertyDescriptor(), prototype(), properties()
+
+describe('ownPropertyNames()', function() {
+  it('should fetch property names', function(done) {
+    obj.ownPropertyNames(function(err, names) {
+      assert.strictEqual(err, null);
+      assert.deepEqual(names, ['a', 'b', 'd']);
+      done();
+    })
+  })
+});
+
+describe('ownPropertyDescriptor()', function() {
+  it('should fetch descriptor for property', function(done) {
+    obj.ownPropertyDescriptor('a', function(err, desc) {
+      assert.strictEqual(err, null);
+      testDescriptor(desc);
+      assert.equal(desc.value, 2);
+      done();
+    })
+  })
+
+  /* TODO: doesn't call callback if not defined property - Server side problem
+  it('should be undefined for nonexistent property', function(done) {
+    obj.ownPropertyDescriptor('g', function(desc) {
+      console.log("desc", desc);
+      done();
+    })
+  }) */
+})
+
+describe('ownProperties()', function() {
+  it('should fetch all own properties and descriptors', function(done) {
+    obj.ownProperties(function(err, props) {
+      assert.strictEqual(err, null);
+      testDescriptor(props.a);
+      assert.equal(props.a.value, 2);
+
+      testDescriptor(props.b);
+      assert.ok(props.b.value.ownProperties, "prop value has JSObject methods");
+      done();
+    })
+  })
+})
+
+describe('prototype()', function() {
+  it('should fetch prototype as an object', function(done) {
+    obj.prototype(function(err, proto) {
+      assert.strictEqual(err, null);
+      assert.ok(proto.ownProperties, "prototype has JSObject methods");
+      done();
+    })
+  })
+})
+
+describe('ownPropertiesAndPrototype()', function() {
+  it('should fetch properties, prototype, and getters', function(done) {
+    obj.ownPropertiesAndPrototype(function(err, resp) {
+      assert.strictEqual(err, null);
+
+      // own properties
+      var props = resp.ownProperties;
+      assert.equal(Object.keys(props).length, 3);
+
+      testDescriptor(props.a);
+      assert.equal(props.a.value, 2);
+
+      // prototype
+      assert.ok(resp.prototype.ownProperties,
+                "prototype has JSObject methods");
+
+      // getters
+      var getters = resp.safeGetterValues;
+      assert.equal(Object.keys(getters).length, 0);
+
+      done();
+    })
+  })
+})
+
+describe('Function objects', function() {
+  it('sould have correct properties', function() {
+    assert.equal(func.class, "Function");
+    assert.equal(func.name, "testfunc");
+    assert.ok(func.ownProperties, "function has JSObject methods")
+  })
+})
+
+function testDescriptor(desc) {
+  assert.strictEqual(desc.configurable, true);
+  assert.strictEqual(desc.enumerable, true);
+  assert.strictEqual(desc.writable, true);
+}
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/test-logs.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/test-logs.js
new file mode 100644
index 0000000..2c076a6
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/test-logs.js
@@ -0,0 +1,106 @@
+var assert = require("assert"),
+    utils = require("./utils");
+
+var tab;
+var Console;
+
+before(function(done) {
+  utils.loadTab('logs.html', function(aTab) {
+    tab = aTab;
+    Console = aTab.Console;
+
+    Console.startListening(function() {
+      done();
+    })
+  });
+});
+
+// Console - startLogging(), stopLogging(), getCachedMessages(),
+// clearCachedMessages(), event:page-error, event:console-api-call
+
+describe('getCachedMessages()', function() {
+  it('should get messages from before listening', function(done) {
+    Console.getCachedLogs(function(err, messages) {
+      assert.strictEqual(err, null);
+
+      var hasLog = messages.some(function(message) {
+        return message.level == "log";
+      })
+      assert.ok(hasLog);
+
+      var hasDir = messages.some(function(message) {
+        return message.level == "dir";
+      })
+      assert.ok(hasDir);
+
+      var hasError = messages.some(function(message) {
+        return message.errorMessage == "ReferenceError: foo is not defined";
+      })
+      assert.ok(hasError);
+      done();
+    });
+  })
+})
+
+describe('clearCachedMessages()', function() {
+  it('should clear cached messages', function(done) {
+    Console.clearCachedLogs(function() {
+      Console.getCachedLogs(function(err, messages) {
+        assert.strictEqual(err, null);
+        // The error message should be left
+        assert.equal(messages.length, 1);
+        assert.equal(messages[0].errorMessage, "ReferenceError: foo is not defined")
+        done();
+      })
+    });
+  })
+})
+
+describe('"page-error" event', function() {
+  it('should receive "page-error" event with message', function(done) {
+    Console.once('page-error', function(event) {
+      assert.equal(event.errorMessage, "ReferenceError: foo is not defined");
+      assert.ok(event.sourceName.indexOf("logs.html") > 0);
+      assert.equal(event.lineNumber, 10);
+      assert.equal(event.columnNumber, 0);
+      assert.ok(event.exception);
+
+      done();
+    });
+
+    tab.reload();
+  })
+})
+
+describe('"console-api-call" event', function() {
+  it('should receive "console-api-call" for console.log', function(done) {
+    Console.on('console-api-call', function(event) {
+      if (event.level == "log") {
+        assert.deepEqual(event.arguments, ["hi"]);
+
+        Console.removeAllListeners('console-api-call');
+        done();
+      }
+    });
+
+    tab.reload();
+  })
+
+  it('should receive "console-api-call" for console.dir', function(done) {
+    Console.on('console-api-call', function(event) {
+      if (event.level == "dir") {
+        var obj = event.arguments[0];
+        assert.ok(obj.ownPropertyNames, "dir argument has JSObject methods");
+
+        Console.removeAllListeners('console-api-call');
+        done();
+      }
+    });
+
+    tab.reload();
+  })
+})
+
+after(function() {
+  Console.stopListening();
+})
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/test-network.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/test-network.js
new file mode 100644
index 0000000..969ec63
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/test-network.js
@@ -0,0 +1,88 @@
+var assert = require("assert"),
+    path = require("path"),
+    utils = require("./utils");
+
+var tab;
+var Network;
+var Console;
+
+before(function(done) {
+  utils.loadTab('network.html', function(aTab) {
+    tab = aTab;
+    Network = aTab.Network;
+    Console = aTab.Console;
+
+    Network.startLogging(function(err) {
+      assert.strictEqual(err, null);
+      done();
+    })
+  });
+});
+
+// Network - startLogging(), stopLogging(), sendHTTPRequest(), event:network-event
+
+describe('"network-event" event', function() {
+  it('should receive "network-event" event with message', function(done) {
+    Network.once('network-event', function(event) {
+      assert.equal(event.method, "GET");
+      assert.equal(path.basename(event.url), "test-network.json");
+      assert.ok(event.isXHR);
+      assert.ok(event.getResponseHeaders, "event has NetworkEvent methods")
+      done();
+    });
+
+    Console.evaluateJS("sendRequest()")
+  })
+})
+
+describe('sendHTTPRequest()', function() {
+  it('should send a new XHR request from page', function(done) {
+    var request = {
+      url: "test-network.json",
+      method: "GET",
+      headers: [{name: "test-header", value: "test-value"}]
+    };
+
+    Network.sendHTTPRequest(request, function(err, netEvent) {
+      assert.strictEqual(err, null);
+      assert.ok(netEvent.getResponseHeaders, "event has NetworkEvent methods");
+      done();
+    });
+  })
+})
+
+// NetworkEvent - getRequestHeaders(), getRequestCookies(), getRequestPostData(),
+// getResponseHeaders(), getResponseCookies(), getResponseContent(), getEventTimings()
+// event:update
+
+
+describe('getRequestHeaders(', function() {
+  it('should get request headers', function(done) {
+    Network.on('network-event', function(netEvent) {
+      netEvent.on("request-headers", function(event) {
+        assert.ok(event.headers);
+        assert.ok(event.headersSize);
+
+        netEvent.getRequestHeaders(function(err, resp) {
+          assert.strictEqual(err, null);
+
+          var found = resp.headers.some(function(header) {
+            return header.name == "test-header" &&
+                   header.value == "test-value";
+          });
+          assert.ok(found, "contains that header we sent");
+          done();
+        })
+      })
+    });
+    Console.evaluateJS("sendRequest()");
+  })
+})
+
+// TODO: NetworkEvent tests
+
+after(function() {
+  Network.stopLogging(function(err) {
+    assert.strictEqual(err, null);
+  });
+})
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/test-raw.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/test-raw.js
new file mode 100644
index 0000000..433eede
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/test-raw.js
@@ -0,0 +1,26 @@
+var assert = require("assert"),
+    FirefoxClient = require("../index");
+
+var client = new FirefoxClient();
+
+before(function(done) {
+  client.connect(function() {
+    done();
+  })
+});
+
+describe('makeRequest()', function() {
+  it('should do listTabs request', function(done) {
+    var message = {
+      to: 'root',
+      type: 'listTabs'
+    };
+
+    client.client.makeRequest(message, function(resp) {
+      assert.equal(resp.from, "root");
+      assert.ok(resp.tabs);
+      assert.ok(resp.profilerActor)
+      done();
+    })
+  })
+})
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/test-stylesheets.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/test-stylesheets.js
new file mode 100644
index 0000000..86af8d0
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/test-stylesheets.js
@@ -0,0 +1,124 @@
+var assert = require("assert"),
+    path = require("path"),
+    utils = require("./utils");
+
+var StyleSheets;
+var styleSheet;
+
+var SS_TEXT = [
+"main {",
+"  font-family: Georgia, sans-serif;",
+"  color: black;",
+"}",
+"",
+"* {",
+"  padding: 0;",
+"  margin: 0;",
+"}"
+].join("\n");
+
+before(function(done) {
+  utils.loadTab('stylesheets.html', function(aTab) {
+    StyleSheets = aTab.StyleSheets;
+    StyleSheets.getStyleSheets(function(err, sheets) {
+      assert.strictEqual(err, null);
+      styleSheet = sheets[1];
+      done();
+    })
+  });
+});
+
+// Stylesheets - getStyleSheets(), addStyleSheet()
+
+describe('getStyleSheets()', function() {
+  it('should list all the stylesheets', function(done) {
+    StyleSheets.getStyleSheets(function(err, sheets) {
+      assert.strictEqual(err, null);
+
+      var hrefs = sheets.map(function(sheet) {
+        assert.ok(sheet.update, "sheet has Stylesheet methods");
+        return path.basename(sheet.href);
+      });
+      assert.deepEqual(hrefs, ["null", "stylesheet1.css"]);
+      done();
+    })
+  })
+})
+
+describe('addStyleSheet()', function() {
+  it('should add a new stylesheet', function(done) {
+    var text = "div { font-weight: bold; }";
+
+    StyleSheets.addStyleSheet(text, function(err, sheet) {
+      assert.strictEqual(err, null);
+      assert.ok(sheet.update, "sheet has Stylesheet methods");
+      assert.equal(sheet.ruleCount, 1);
+      done();
+    })
+  })
+})
+
+// StyleSheet - update(), toggleDisabled()
+
+describe('StyleSheet', function() {
+  it('should have the correct properties', function() {
+    assert.equal(path.basename(styleSheet.href), "stylesheet1.css");
+    assert.strictEqual(styleSheet.disabled, false);
+    assert.equal(styleSheet.ruleCount, 2);
+  })
+})
+
+describe('StyleSheet.getText()', function() {
+  it('should get the text of the style sheet', function(done) {
+    styleSheet.getText(function(err, resp) {
+      assert.strictEqual(err, null);
+      assert.equal(resp, SS_TEXT);
+      done();
+    })
+  })
+});
+
+describe('StyleSheet.update()', function() {
+  it('should update stylesheet', function(done) {
+    var text = "main { color: red; }";
+
+    styleSheet.update(text, function(err, resp) {
+      assert.strictEqual(err, null);
+      // TODO: assert.equal(styleSheet.ruleCount, 1);
+      done();
+    })
+  })
+})
+
+describe('StyleSheet.toggleDisabled()', function() {
+  it('should toggle disabled attribute', function(done) {
+    assert.deepEqual(styleSheet.disabled, false);
+
+    styleSheet.toggleDisabled(function(err, resp) {
+      assert.strictEqual(err, null);
+      assert.deepEqual(styleSheet.disabled, true);
+      done();
+    })
+  })
+
+  it('should fire disabled-changed event', function(done) {
+    styleSheet.toggleDisabled(function(err, resp) {
+      assert.strictEqual(err, null);
+      assert.deepEqual(styleSheet.disabled, false);
+    })
+    styleSheet.on("disabled-changed", function(disabled) {
+      assert.strictEqual(disabled, false);
+      done();
+    })
+  })
+})
+
+describe('StyleSheet.getOriginalSources()', function() {
+  it('should get no original sources', function(done) {
+    styleSheet.getOriginalSources(function(err, resp) {
+      assert.strictEqual(err, null);
+      assert.deepEqual(resp, []);
+      done();
+    })
+  })
+})
diff --git a/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/utils.js b/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/utils.js
new file mode 100644
index 0000000..45dc191
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/firefox-client/test/utils.js
@@ -0,0 +1,35 @@
+var assert = require('assert'),
+    FirefoxClient = require("../index");
+
+var tab;
+
+exports.loadTab = function(url, callback) {
+  getFirstTab(function(tab) {
+    tab.navigateTo(url);
+
+    tab.once("navigate", function() {
+      callback(tab);
+    });
+  })
+};
+
+
+function getFirstTab(callback) {
+  if (tab) {
+    return callback(tab);
+  }
+  var client = new FirefoxClient({log: true});
+
+  client.connect(function() {
+    client.listTabs(function(err, tabs) {
+      if (err) throw err;
+
+      tab = tabs[0];
+
+      tab.attach(function(err) {
+        if (err) throw err;
+        callback(tab);
+      })
+    });
+  });
+}
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-ports/node_modules/shelljs/.documentup.json b/node_modules/node-firefox-find-ports/node_modules/shelljs/.documentup.json
new file mode 100644
index 0000000..57fe301
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/shelljs/.documentup.json
@@ -0,0 +1,6 @@
+{
+  "name": "ShellJS",
+  "twitter": [
+    "r2r"
+  ]
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/shelljs/.jshintrc b/node_modules/node-firefox-find-ports/node_modules/shelljs/.jshintrc
new file mode 100644
index 0000000..a80c559
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/shelljs/.jshintrc
@@ -0,0 +1,7 @@
+{
+  "loopfunc": true,
+  "sub": true,
+  "undef": true,
+  "unused": true,
+  "node": true
+}
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-ports/node_modules/shelljs/.npmignore b/node_modules/node-firefox-find-ports/node_modules/shelljs/.npmignore
new file mode 100644
index 0000000..6b20c38
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/shelljs/.npmignore
@@ -0,0 +1,2 @@
+test/
+tmp/
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-ports/node_modules/shelljs/.travis.yml b/node_modules/node-firefox-find-ports/node_modules/shelljs/.travis.yml
new file mode 100644
index 0000000..99cdc74
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/shelljs/.travis.yml
@@ -0,0 +1,5 @@
+language: node_js
+node_js:
+  - "0.8"
+  - "0.10"
+  - "0.11"
diff --git a/node_modules/node-firefox-find-ports/node_modules/shelljs/LICENSE b/node_modules/node-firefox-find-ports/node_modules/shelljs/LICENSE
new file mode 100644
index 0000000..1b35ee9
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/shelljs/LICENSE
@@ -0,0 +1,26 @@
+Copyright (c) 2012, Artur Adib <aadib@mozilla.com>
+All rights reserved.
+
+You may use this project under the terms of the New BSD license as follows:
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of Artur Adib nor the
+      names of the contributors may be used to endorse or promote products
+      derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+ARE DISCLAIMED. IN NO EVENT SHALL ARTUR ADIB BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 
+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/node_modules/node-firefox-find-ports/node_modules/shelljs/README.md b/node_modules/node-firefox-find-ports/node_modules/shelljs/README.md
new file mode 100644
index 0000000..51358bd
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/shelljs/README.md
@@ -0,0 +1,569 @@
+# ShellJS - Unix shell commands for Node.js [![Build Status](https://secure.travis-ci.org/arturadib/shelljs.png)](http://travis-ci.org/arturadib/shelljs)
+
+ShellJS is a portable **(Windows/Linux/OS X)** implementation of Unix shell commands on top of the Node.js API. You can use it to eliminate your shell script's dependency on Unix while still keeping its familiar and powerful commands. You can also install it globally so you can run it from outside Node projects - say goodbye to those gnarly Bash scripts!
+
+The project is [unit-tested](http://travis-ci.org/arturadib/shelljs) and battled-tested in projects like:
+
++ [PDF.js](http://github.com/mozilla/pdf.js) - Firefox's next-gen PDF reader
++ [Firebug](http://getfirebug.com/) - Firefox's infamous debugger
++ [JSHint](http://jshint.com) - Most popular JavaScript linter
++ [Zepto](http://zeptojs.com) - jQuery-compatible JavaScript library for modern browsers
++ [Yeoman](http://yeoman.io/) - Web application stack and development tool
++ [Deployd.com](http://deployd.com) - Open source PaaS for quick API backend generation
+
+and [many more](https://npmjs.org/browse/depended/shelljs).
+
+## Installing
+
+Via npm:
+
+```bash
+$ npm install [-g] shelljs
+```
+
+If the global option `-g` is specified, the binary `shjs` will be installed. This makes it possible to
+run ShellJS scripts much like any shell script from the command line, i.e. without requiring a `node_modules` folder:
+
+```bash
+$ shjs my_script
+```
+
+You can also just copy `shell.js` into your project's directory, and `require()` accordingly.
+
+
+## Examples
+
+### JavaScript
+
+```javascript
+require('shelljs/global');
+
+if (!which('git')) {
+  echo('Sorry, this script requires git');
+  exit(1);
+}
+
+// Copy files to release dir
+mkdir('-p', 'out/Release');
+cp('-R', 'stuff/*', 'out/Release');
+
+// Replace macros in each .js file
+cd('lib');
+ls('*.js').forEach(function(file) {
+  sed('-i', 'BUILD_VERSION', 'v0.1.2', file);
+  sed('-i', /.*REMOVE_THIS_LINE.*\n/, '', file);
+  sed('-i', /.*REPLACE_LINE_WITH_MACRO.*\n/, cat('macro.js'), file);
+});
+cd('..');
+
+// Run external tool synchronously
+if (exec('git commit -am "Auto-commit"').code !== 0) {
+  echo('Error: Git commit failed');
+  exit(1);
+}
+```
+
+### CoffeeScript
+
+```coffeescript
+require 'shelljs/global'
+
+if not which 'git'
+  echo 'Sorry, this script requires git'
+  exit 1
+
+# Copy files to release dir
+mkdir '-p', 'out/Release'
+cp '-R', 'stuff/*', 'out/Release'
+
+# Replace macros in each .js file
+cd 'lib'
+for file in ls '*.js'
+  sed '-i', 'BUILD_VERSION', 'v0.1.2', file
+  sed '-i', /.*REMOVE_THIS_LINE.*\n/, '', file
+  sed '-i', /.*REPLACE_LINE_WITH_MACRO.*\n/, cat 'macro.js', file
+cd '..'
+
+# Run external tool synchronously
+if (exec 'git commit -am "Auto-commit"').code != 0
+  echo 'Error: Git commit failed'
+  exit 1
+```
+
+## Global vs. Local
+
+The example above uses the convenience script `shelljs/global` to reduce verbosity. If polluting your global namespace is not desirable, simply require `shelljs`.
+
+Example:
+
+```javascript
+var shell = require('shelljs');
+shell.echo('hello world');
+```
+
+## Make tool
+
+A convenience script `shelljs/make` is also provided to mimic the behavior of a Unix Makefile. In this case all shell objects are global, and command line arguments will cause the script to execute only the corresponding function in the global `target` object. To avoid redundant calls, target functions are executed only once per script.
+
+Example (CoffeeScript):
+
+```coffeescript
+require 'shelljs/make'
+
+target.all = ->
+  target.bundle()
+  target.docs()
+
+target.bundle = ->
+  cd __dirname
+  mkdir 'build'
+  cd 'lib'
+  (cat '*.js').to '../build/output.js'
+
+target.docs = ->
+  cd __dirname
+  mkdir 'docs'
+  cd 'lib'
+  for file in ls '*.js'
+    text = grep '//@', file     # extract special comments
+    text.replace '//@', ''      # remove comment tags
+    text.to 'docs/my_docs.md'
+```
+
+To run the target `all`, call the above script without arguments: `$ node make`. To run the target `docs`: `$ node make docs`, and so on.
+
+
+
+<!-- 
+
+  DO NOT MODIFY BEYOND THIS POINT - IT'S AUTOMATICALLY GENERATED
+
+-->
+
+
+## Command reference
+
+
+All commands run synchronously, unless otherwise stated.
+
+
+### cd('dir')
+Changes to directory `dir` for the duration of the script
+
+
+### pwd()
+Returns the current directory.
+
+
+### ls([options ,] path [,path ...])
+### ls([options ,] path_array)
+Available options:
+
++ `-R`: recursive
++ `-A`: all files (include files beginning with `.`, except for `.` and `..`)
+
+Examples:
+
+```javascript
+ls('projs/*.js');
+ls('-R', '/users/me', '/tmp');
+ls('-R', ['/users/me', '/tmp']); // same as above
+```
+
+Returns array of files in the given path, or in current directory if no path provided.
+
+
+### find(path [,path ...])
+### find(path_array)
+Examples:
+
+```javascript
+find('src', 'lib');
+find(['src', 'lib']); // same as above
+find('.').filter(function(file) { return file.match(/\.js$/); });
+```
+
+Returns array of all files (however deep) in the given paths.
+
+The main difference from `ls('-R', path)` is that the resulting file names
+include the base directories, e.g. `lib/resources/file1` instead of just `file1`.
+
+
+### cp([options ,] source [,source ...], dest)
+### cp([options ,] source_array, dest)
+Available options:
+
++ `-f`: force
++ `-r, -R`: recursive
+
+Examples:
+
+```javascript
+cp('file1', 'dir1');
+cp('-Rf', '/tmp/*', '/usr/local/*', '/home/tmp');
+cp('-Rf', ['/tmp/*', '/usr/local/*'], '/home/tmp'); // same as above
+```
+
+Copies files. The wildcard `*` is accepted.
+
+
+### rm([options ,] file [, file ...])
+### rm([options ,] file_array)
+Available options:
+
++ `-f`: force
++ `-r, -R`: recursive
+
+Examples:
+
+```javascript
+rm('-rf', '/tmp/*');
+rm('some_file.txt', 'another_file.txt');
+rm(['some_file.txt', 'another_file.txt']); // same as above
+```
+
+Removes files. The wildcard `*` is accepted.
+
+
+### mv(source [, source ...], dest')
+### mv(source_array, dest')
+Available options:
+
++ `f`: force
+
+Examples:
+
+```javascript
+mv('-f', 'file', 'dir/');
+mv('file1', 'file2', 'dir/');
+mv(['file1', 'file2'], 'dir/'); // same as above
+```
+
+Moves files. The wildcard `*` is accepted.
+
+
+### mkdir([options ,] dir [, dir ...])
+### mkdir([options ,] dir_array)
+Available options:
+
++ `p`: full path (will create intermediate dirs if necessary)
+
+Examples:
+
+```javascript
+mkdir('-p', '/tmp/a/b/c/d', '/tmp/e/f/g');
+mkdir('-p', ['/tmp/a/b/c/d', '/tmp/e/f/g']); // same as above
+```
+
+Creates directories.
+
+
+### test(expression)
+Available expression primaries:
+
++ `'-b', 'path'`: true if path is a block device
++ `'-c', 'path'`: true if path is a character device
++ `'-d', 'path'`: true if path is a directory
++ `'-e', 'path'`: true if path exists
++ `'-f', 'path'`: true if path is a regular file
++ `'-L', 'path'`: true if path is a symboilc link
++ `'-p', 'path'`: true if path is a pipe (FIFO)
++ `'-S', 'path'`: true if path is a socket
+
+Examples:
+
+```javascript
+if (test('-d', path)) { /* do something with dir */ };
+if (!test('-f', path)) continue; // skip if it's a regular file
+```
+
+Evaluates expression using the available primaries and returns corresponding value.
+
+
+### cat(file [, file ...])
+### cat(file_array)
+
+Examples:
+
+```javascript
+var str = cat('file*.txt');
+var str = cat('file1', 'file2');
+var str = cat(['file1', 'file2']); // same as above
+```
+
+Returns a string containing the given file, or a concatenated string
+containing the files if more than one file is given (a new line character is
+introduced between each file). Wildcard `*` accepted.
+
+
+### 'string'.to(file)
+
+Examples:
+
+```javascript
+cat('input.txt').to('output.txt');
+```
+
+Analogous to the redirection operator `>` in Unix, but works with JavaScript strings (such as
+those returned by `cat`, `grep`, etc). _Like Unix redirections, `to()` will overwrite any existing file!_
+
+
+### 'string'.toEnd(file)
+
+Examples:
+
+```javascript
+cat('input.txt').toEnd('output.txt');
+```
+
+Analogous to the redirect-and-append operator `>>` in Unix, but works with JavaScript strings (such as
+those returned by `cat`, `grep`, etc).
+
+
+### sed([options ,] search_regex, replacement, file)
+Available options:
+
++ `-i`: Replace contents of 'file' in-place. _Note that no backups will be created!_
+
+Examples:
+
+```javascript
+sed('-i', 'PROGRAM_VERSION', 'v0.1.3', 'source.js');
+sed(/.*DELETE_THIS_LINE.*\n/, '', 'source.js');
+```
+
+Reads an input string from `file` and performs a JavaScript `replace()` on the input
+using the given search regex and replacement string or function. Returns the new string after replacement.
+
+
+### grep([options ,] regex_filter, file [, file ...])
+### grep([options ,] regex_filter, file_array)
+Available options:
+
++ `-v`: Inverse the sense of the regex and print the lines not matching the criteria.
+
+Examples:
+
+```javascript
+grep('-v', 'GLOBAL_VARIABLE', '*.js');
+grep('GLOBAL_VARIABLE', '*.js');
+```
+
+Reads input string from given files and returns a string containing all lines of the
+file that match the given `regex_filter`. Wildcard `*` accepted.
+
+
+### which(command)
+
+Examples:
+
+```javascript
+var nodeExec = which('node');
+```
+
+Searches for `command` in the system's PATH. On Windows looks for `.exe`, `.cmd`, and `.bat` extensions.
+Returns string containing the absolute path to the command.
+
+
+### echo(string [,string ...])
+
+Examples:
+
+```javascript
+echo('hello world');
+var str = echo('hello world');
+```
+
+Prints string to stdout, and returns string with additional utility methods
+like `.to()`.
+
+
+### pushd([options,] [dir | '-N' | '+N'])
+
+Available options:
+
++ `-n`: Suppresses the normal change of directory when adding directories to the stack, so that only the stack is manipulated.
+
+Arguments:
+
++ `dir`: Makes the current working directory be the top of the stack, and then executes the equivalent of `cd dir`.
++ `+N`: Brings the Nth directory (counting from the left of the list printed by dirs, starting with zero) to the top of the list by rotating the stack.
++ `-N`: Brings the Nth directory (counting from the right of the list printed by dirs, starting with zero) to the top of the list by rotating the stack.
+
+Examples:
+
+```javascript
+// process.cwd() === '/usr'
+pushd('/etc'); // Returns /etc /usr
+pushd('+1');   // Returns /usr /etc
+```
+
+Save the current directory on the top of the directory stack and then cd to `dir`. With no arguments, pushd exchanges the top two directories. Returns an array of paths in the stack.
+
+### popd([options,] ['-N' | '+N'])
+
+Available options:
+
++ `-n`: Suppresses the normal change of directory when removing directories from the stack, so that only the stack is manipulated.
+
+Arguments:
+
++ `+N`: Removes the Nth directory (counting from the left of the list printed by dirs), starting with zero.
++ `-N`: Removes the Nth directory (counting from the right of the list printed by dirs), starting with zero.
+
+Examples:
+
+```javascript
+echo(process.cwd()); // '/usr'
+pushd('/etc');       // '/etc /usr'
+echo(process.cwd()); // '/etc'
+popd();              // '/usr'
+echo(process.cwd()); // '/usr'
+```
+
+When no arguments are given, popd removes the top directory from the stack and performs a cd to the new top directory. The elements are numbered from 0 starting at the first directory listed with dirs; i.e., popd is equivalent to popd +0. Returns an array of paths in the stack.
+
+### dirs([options | '+N' | '-N'])
+
+Available options:
+
++ `-c`: Clears the directory stack by deleting all of the elements.
+
+Arguments:
+
++ `+N`: Displays the Nth directory (counting from the left of the list printed by dirs when invoked without options), starting with zero.
++ `-N`: Displays the Nth directory (counting from the right of the list printed by dirs when invoked without options), starting with zero.
+
+Display the list of currently remembered directories. Returns an array of paths in the stack, or a single path if +N or -N was specified.
+
+See also: pushd, popd
+
+
+### ln(options, source, dest)
+### ln(source, dest)
+Available options:
+
++ `s`: symlink
++ `f`: force
+
+Examples:
+
+```javascript
+ln('file', 'newlink');
+ln('-sf', 'file', 'existing');
+```
+
+Links source to dest. Use -f to force the link, should dest already exist.
+
+
+### exit(code)
+Exits the current process with the given exit code.
+
+### env['VAR_NAME']
+Object containing environment variables (both getter and setter). Shortcut to process.env.
+
+### exec(command [, options] [, callback])
+Available options (all `false` by default):
+
++ `async`: Asynchronous execution. Defaults to true if a callback is provided.
++ `silent`: Do not echo program output to console.
+
+Examples:
+
+```javascript
+var version = exec('node --version', {silent:true}).output;
+
+var child = exec('some_long_running_process', {async:true});
+child.stdout.on('data', function(data) {
+  /* ... do something with data ... */
+});
+
+exec('some_long_running_process', function(code, output) {
+  console.log('Exit code:', code);
+  console.log('Program output:', output);
+});
+```
+
+Executes the given `command` _synchronously_, unless otherwise specified.
+When in synchronous mode returns the object `{ code:..., output:... }`, containing the program's
+`output` (stdout + stderr)  and its exit `code`. Otherwise returns the child process object, and
+the `callback` gets the arguments `(code, output)`.
+
+**Note:** For long-lived processes, it's best to run `exec()` asynchronously as
+the current synchronous implementation uses a lot of CPU. This should be getting
+fixed soon.
+
+
+### chmod(octal_mode || octal_string, file)
+### chmod(symbolic_mode, file)
+
+Available options:
+
++ `-v`: output a diagnostic for every file processed
++ `-c`: like verbose but report only when a change is made
++ `-R`: change files and directories recursively
+
+Examples:
+
+```javascript
+chmod(755, '/Users/brandon');
+chmod('755', '/Users/brandon'); // same as above
+chmod('u+x', '/Users/brandon');
+```
+
+Alters the permissions of a file or directory by either specifying the
+absolute permissions in octal form or expressing the changes in symbols.
+This command tries to mimic the POSIX behavior as much as possible.
+Notable exceptions:
+
++ In symbolic modes, 'a-r' and '-r' are identical.  No consideration is
+  given to the umask.
++ There is no "quiet" option since default behavior is to run silent.
+
+
+## Non-Unix commands
+
+
+### tempdir()
+
+Examples:
+
+```javascript
+var tmp = tempdir(); // "/tmp" for most *nix platforms
+```
+
+Searches and returns string containing a writeable, platform-dependent temporary directory.
+Follows Python's [tempfile algorithm](http://docs.python.org/library/tempfile.html#tempfile.tempdir).
+
+
+### error()
+Tests if error occurred in the last command. Returns `null` if no error occurred,
+otherwise returns string explaining the error
+
+
+## Configuration
+
+
+### config.silent
+Example:
+
+```javascript
+var silentState = config.silent; // save old silent state
+config.silent = true;
+/* ... */
+config.silent = silentState; // restore old silent state
+```
+
+Suppresses all command output if `true`, except for `echo()` calls.
+Default is `false`.
+
+### config.fatal
+Example:
+
+```javascript
+config.fatal = true;
+cp('this_file_does_not_exist', '/dev/null'); // dies here
+/* more commands... */
+```
+
+If `true` the script will die on errors. Default is `false`.
diff --git a/node_modules/node-firefox-find-ports/node_modules/shelljs/bin/shjs b/node_modules/node-firefox-find-ports/node_modules/shelljs/bin/shjs
new file mode 100755
index 0000000..d239a7a
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/shelljs/bin/shjs
@@ -0,0 +1,51 @@
+#!/usr/bin/env node
+require('../global');
+
+if (process.argv.length < 3) {
+  console.log('ShellJS: missing argument (script name)');
+  console.log();
+  process.exit(1);
+}
+
+var args,
+  scriptName = process.argv[2];
+env['NODE_PATH'] = __dirname + '/../..';
+
+if (!scriptName.match(/\.js/) && !scriptName.match(/\.coffee/)) {
+  if (test('-f', scriptName + '.js'))
+    scriptName += '.js';
+  if (test('-f', scriptName + '.coffee'))
+    scriptName += '.coffee';
+}
+
+if (!test('-f', scriptName)) {
+  console.log('ShellJS: script not found ('+scriptName+')');
+  console.log();
+  process.exit(1);
+}
+
+args = process.argv.slice(3);
+
+for (var i = 0, l = args.length; i < l; i++) {
+  if (args[i][0] !== "-"){
+    args[i] = '"' + args[i] + '"'; // fixes arguments with multiple words
+  }
+}
+
+if (scriptName.match(/\.coffee$/)) {
+  //
+  // CoffeeScript
+  //
+  if (which('coffee')) {
+    exec('coffee ' + scriptName + ' ' + args.join(' '), { async: true });
+  } else {
+    console.log('ShellJS: CoffeeScript interpreter not found');
+    console.log();
+    process.exit(1);
+  }
+} else {
+  //
+  // JavaScript
+  //
+  exec('node ' + scriptName + ' ' + args.join(' '), { async: true });
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/shelljs/global.js b/node_modules/node-firefox-find-ports/node_modules/shelljs/global.js
new file mode 100644
index 0000000..97f0033
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/shelljs/global.js
@@ -0,0 +1,3 @@
+var shell = require('./shell.js');
+for (var cmd in shell)
+  global[cmd] = shell[cmd];
diff --git a/node_modules/node-firefox-find-ports/node_modules/shelljs/make.js b/node_modules/node-firefox-find-ports/node_modules/shelljs/make.js
new file mode 100644
index 0000000..53e5e81
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/shelljs/make.js
@@ -0,0 +1,47 @@
+require('./global');
+
+global.config.fatal = true;
+global.target = {};
+
+// This ensures we only execute the script targets after the entire script has
+// been evaluated
+var args = process.argv.slice(2);
+setTimeout(function() {
+  var t;
+
+  if (args.length === 1 && args[0] === '--help') {
+    console.log('Available targets:');
+    for (t in global.target)
+      console.log('  ' + t);
+    return;
+  }
+
+  // Wrap targets to prevent duplicate execution
+  for (t in global.target) {
+    (function(t, oldTarget){
+
+      // Wrap it
+      global.target[t] = function(force) {
+        if (oldTarget.done && !force)
+          return;
+        oldTarget.done = true;
+        return oldTarget.apply(oldTarget, arguments);
+      };
+
+    })(t, global.target[t]);
+  }
+
+  // Execute desired targets
+  if (args.length > 0) {
+    args.forEach(function(arg) {
+      if (arg in global.target)
+        global.target[arg]();
+      else {
+        console.log('no such target: ' + arg);
+      }
+    });
+  } else if ('all' in global.target) {
+    global.target.all();
+  }
+
+}, 0);
diff --git a/node_modules/node-firefox-find-ports/node_modules/shelljs/package.json b/node_modules/node-firefox-find-ports/node_modules/shelljs/package.json
new file mode 100644
index 0000000..c6903c4
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/shelljs/package.json
@@ -0,0 +1,63 @@
+{
+  "name": "shelljs",
+  "version": "0.3.0",
+  "author": {
+    "name": "Artur Adib",
+    "email": "aadib@mozilla.com"
+  },
+  "description": "Portable Unix shell commands for Node.js",
+  "keywords": [
+    "unix",
+    "shell",
+    "makefile",
+    "make",
+    "jake",
+    "synchronous"
+  ],
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/arturadib/shelljs.git"
+  },
+  "license": "BSD*",
+  "homepage": "http://github.com/arturadib/shelljs",
+  "main": "./shell.js",
+  "scripts": {
+    "test": "node scripts/run-tests"
+  },
+  "bin": {
+    "shjs": "./bin/shjs"
+  },
+  "dependencies": {},
+  "devDependencies": {
+    "jshint": "~2.1.11"
+  },
+  "optionalDependencies": {},
+  "engines": {
+    "node": ">=0.8.0"
+  },
+  "bugs": {
+    "url": "https://github.com/arturadib/shelljs/issues"
+  },
+  "_id": "shelljs@0.3.0",
+  "dist": {
+    "shasum": "3596e6307a781544f591f37da618360f31db57b1",
+    "tarball": "http://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz"
+  },
+  "_from": "shelljs@^0.3.0",
+  "_npmVersion": "1.3.11",
+  "_npmUser": {
+    "name": "artur",
+    "email": "arturadib@gmail.com"
+  },
+  "maintainers": [
+    {
+      "name": "artur",
+      "email": "arturadib@gmail.com"
+    }
+  ],
+  "directories": {},
+  "_shasum": "3596e6307a781544f591f37da618360f31db57b1",
+  "_resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz",
+  "readme": "# ShellJS - Unix shell commands for Node.js [![Build Status](https://secure.travis-ci.org/arturadib/shelljs.png)](http://travis-ci.org/arturadib/shelljs)\n\nShellJS is a portable **(Windows/Linux/OS X)** implementation of Unix shell commands on top of the Node.js API. You can use it to eliminate your shell script's dependency on Unix while still keeping its familiar and powerful commands. You can also install it globally so you can run it from outside Node projects - say goodbye to those gnarly Bash scripts!\n\nThe project is [unit-tested](http://travis-ci.org/arturadib/shelljs) and battled-tested in projects like:\n\n+ [PDF.js](http://github.com/mozilla/pdf.js) - Firefox's next-gen PDF reader\n+ [Firebug](http://getfirebug.com/) - Firefox's infamous debugger\n+ [JSHint](http://jshint.com) - Most popular JavaScript linter\n+ [Zepto](http://zeptojs.com) - jQuery-compatible JavaScript library for modern browsers\n+ [Yeoman](http://yeoman.io/) - Web application stack and development tool\n+ [Deployd.com](http://deployd.com) - Open source PaaS for quick API backend generation\n\nand [many more](https://npmjs.org/browse/depended/shelljs).\n\n## Installing\n\nVia npm:\n\n```bash\n$ npm install [-g] shelljs\n```\n\nIf the global option `-g` is specified, the binary `shjs` will be installed. This makes it possible to\nrun ShellJS scripts much like any shell script from the command line, i.e. without requiring a `node_modules` folder:\n\n```bash\n$ shjs my_script\n```\n\nYou can also just copy `shell.js` into your project's directory, and `require()` accordingly.\n\n\n## Examples\n\n### JavaScript\n\n```javascript\nrequire('shelljs/global');\n\nif (!which('git')) {\n  echo('Sorry, this script requires git');\n  exit(1);\n}\n\n// Copy files to release dir\nmkdir('-p', 'out/Release');\ncp('-R', 'stuff/*', 'out/Release');\n\n// Replace macros in each .js file\ncd('lib');\nls('*.js').forEach(function(file) {\n  sed('-i', 'BUILD_VERSION', 'v0.1.2', file);\n  sed('-i', /.*REMOVE_THIS_LINE.*\\n/, '', file);\n  sed('-i', /.*REPLACE_LINE_WITH_MACRO.*\\n/, cat('macro.js'), file);\n});\ncd('..');\n\n// Run external tool synchronously\nif (exec('git commit -am \"Auto-commit\"').code !== 0) {\n  echo('Error: Git commit failed');\n  exit(1);\n}\n```\n\n### CoffeeScript\n\n```coffeescript\nrequire 'shelljs/global'\n\nif not which 'git'\n  echo 'Sorry, this script requires git'\n  exit 1\n\n# Copy files to release dir\nmkdir '-p', 'out/Release'\ncp '-R', 'stuff/*', 'out/Release'\n\n# Replace macros in each .js file\ncd 'lib'\nfor file in ls '*.js'\n  sed '-i', 'BUILD_VERSION', 'v0.1.2', file\n  sed '-i', /.*REMOVE_THIS_LINE.*\\n/, '', file\n  sed '-i', /.*REPLACE_LINE_WITH_MACRO.*\\n/, cat 'macro.js', file\ncd '..'\n\n# Run external tool synchronously\nif (exec 'git commit -am \"Auto-commit\"').code != 0\n  echo 'Error: Git commit failed'\n  exit 1\n```\n\n## Global vs. Local\n\nThe example above uses the convenience script `shelljs/global` to reduce verbosity. If polluting your global namespace is not desirable, simply require `shelljs`.\n\nExample:\n\n```javascript\nvar shell = require('shelljs');\nshell.echo('hello world');\n```\n\n## Make tool\n\nA convenience script `shelljs/make` is also provided to mimic the behavior of a Unix Makefile. In this case all shell objects are global, and command line arguments will cause the script to execute only the corresponding function in the global `target` object. To avoid redundant calls, target functions are executed only once per script.\n\nExample (CoffeeScript):\n\n```coffeescript\nrequire 'shelljs/make'\n\ntarget.all = ->\n  target.bundle()\n  target.docs()\n\ntarget.bundle = ->\n  cd __dirname\n  mkdir 'build'\n  cd 'lib'\n  (cat '*.js').to '../build/output.js'\n\ntarget.docs = ->\n  cd __dirname\n  mkdir 'docs'\n  cd 'lib'\n  for file in ls '*.js'\n    text = grep '//@', file     # extract special comments\n    text.replace '//@', ''      # remove comment tags\n    text.to 'docs/my_docs.md'\n```\n\nTo run the target `all`, call the above script without arguments: `$ node make`. To run the target `docs`: `$ node make docs`, and so on.\n\n\n\n<!-- \n\n  DO NOT MODIFY BEYOND THIS POINT - IT'S AUTOMATICALLY GENERATED\n\n-->\n\n\n## Command reference\n\n\nAll commands run synchronously, unless otherwise stated.\n\n\n### cd('dir')\nChanges to directory `dir` for the duration of the script\n\n\n### pwd()\nReturns the current directory.\n\n\n### ls([options ,] path [,path ...])\n### ls([options ,] path_array)\nAvailable options:\n\n+ `-R`: recursive\n+ `-A`: all files (include files beginning with `.`, except for `.` and `..`)\n\nExamples:\n\n```javascript\nls('projs/*.js');\nls('-R', '/users/me', '/tmp');\nls('-R', ['/users/me', '/tmp']); // same as above\n```\n\nReturns array of files in the given path, or in current directory if no path provided.\n\n\n### find(path [,path ...])\n### find(path_array)\nExamples:\n\n```javascript\nfind('src', 'lib');\nfind(['src', 'lib']); // same as above\nfind('.').filter(function(file) { return file.match(/\\.js$/); });\n```\n\nReturns array of all files (however deep) in the given paths.\n\nThe main difference from `ls('-R', path)` is that the resulting file names\ninclude the base directories, e.g. `lib/resources/file1` instead of just `file1`.\n\n\n### cp([options ,] source [,source ...], dest)\n### cp([options ,] source_array, dest)\nAvailable options:\n\n+ `-f`: force\n+ `-r, -R`: recursive\n\nExamples:\n\n```javascript\ncp('file1', 'dir1');\ncp('-Rf', '/tmp/*', '/usr/local/*', '/home/tmp');\ncp('-Rf', ['/tmp/*', '/usr/local/*'], '/home/tmp'); // same as above\n```\n\nCopies files. The wildcard `*` is accepted.\n\n\n### rm([options ,] file [, file ...])\n### rm([options ,] file_array)\nAvailable options:\n\n+ `-f`: force\n+ `-r, -R`: recursive\n\nExamples:\n\n```javascript\nrm('-rf', '/tmp/*');\nrm('some_file.txt', 'another_file.txt');\nrm(['some_file.txt', 'another_file.txt']); // same as above\n```\n\nRemoves files. The wildcard `*` is accepted.\n\n\n### mv(source [, source ...], dest')\n### mv(source_array, dest')\nAvailable options:\n\n+ `f`: force\n\nExamples:\n\n```javascript\nmv('-f', 'file', 'dir/');\nmv('file1', 'file2', 'dir/');\nmv(['file1', 'file2'], 'dir/'); // same as above\n```\n\nMoves files. The wildcard `*` is accepted.\n\n\n### mkdir([options ,] dir [, dir ...])\n### mkdir([options ,] dir_array)\nAvailable options:\n\n+ `p`: full path (will create intermediate dirs if necessary)\n\nExamples:\n\n```javascript\nmkdir('-p', '/tmp/a/b/c/d', '/tmp/e/f/g');\nmkdir('-p', ['/tmp/a/b/c/d', '/tmp/e/f/g']); // same as above\n```\n\nCreates directories.\n\n\n### test(expression)\nAvailable expression primaries:\n\n+ `'-b', 'path'`: true if path is a block device\n+ `'-c', 'path'`: true if path is a character device\n+ `'-d', 'path'`: true if path is a directory\n+ `'-e', 'path'`: true if path exists\n+ `'-f', 'path'`: true if path is a regular file\n+ `'-L', 'path'`: true if path is a symboilc link\n+ `'-p', 'path'`: true if path is a pipe (FIFO)\n+ `'-S', 'path'`: true if path is a socket\n\nExamples:\n\n```javascript\nif (test('-d', path)) { /* do something with dir */ };\nif (!test('-f', path)) continue; // skip if it's a regular file\n```\n\nEvaluates expression using the available primaries and returns corresponding value.\n\n\n### cat(file [, file ...])\n### cat(file_array)\n\nExamples:\n\n```javascript\nvar str = cat('file*.txt');\nvar str = cat('file1', 'file2');\nvar str = cat(['file1', 'file2']); // same as above\n```\n\nReturns a string containing the given file, or a concatenated string\ncontaining the files if more than one file is given (a new line character is\nintroduced between each file). Wildcard `*` accepted.\n\n\n### 'string'.to(file)\n\nExamples:\n\n```javascript\ncat('input.txt').to('output.txt');\n```\n\nAnalogous to the redirection operator `>` in Unix, but works with JavaScript strings (such as\nthose returned by `cat`, `grep`, etc). _Like Unix redirections, `to()` will overwrite any existing file!_\n\n\n### 'string'.toEnd(file)\n\nExamples:\n\n```javascript\ncat('input.txt').toEnd('output.txt');\n```\n\nAnalogous to the redirect-and-append operator `>>` in Unix, but works with JavaScript strings (such as\nthose returned by `cat`, `grep`, etc).\n\n\n### sed([options ,] search_regex, replacement, file)\nAvailable options:\n\n+ `-i`: Replace contents of 'file' in-place. _Note that no backups will be created!_\n\nExamples:\n\n```javascript\nsed('-i', 'PROGRAM_VERSION', 'v0.1.3', 'source.js');\nsed(/.*DELETE_THIS_LINE.*\\n/, '', 'source.js');\n```\n\nReads an input string from `file` and performs a JavaScript `replace()` on the input\nusing the given search regex and replacement string or function. Returns the new string after replacement.\n\n\n### grep([options ,] regex_filter, file [, file ...])\n### grep([options ,] regex_filter, file_array)\nAvailable options:\n\n+ `-v`: Inverse the sense of the regex and print the lines not matching the criteria.\n\nExamples:\n\n```javascript\ngrep('-v', 'GLOBAL_VARIABLE', '*.js');\ngrep('GLOBAL_VARIABLE', '*.js');\n```\n\nReads input string from given files and returns a string containing all lines of the\nfile that match the given `regex_filter`. Wildcard `*` accepted.\n\n\n### which(command)\n\nExamples:\n\n```javascript\nvar nodeExec = which('node');\n```\n\nSearches for `command` in the system's PATH. On Windows looks for `.exe`, `.cmd`, and `.bat` extensions.\nReturns string containing the absolute path to the command.\n\n\n### echo(string [,string ...])\n\nExamples:\n\n```javascript\necho('hello world');\nvar str = echo('hello world');\n```\n\nPrints string to stdout, and returns string with additional utility methods\nlike `.to()`.\n\n\n### pushd([options,] [dir | '-N' | '+N'])\n\nAvailable options:\n\n+ `-n`: Suppresses the normal change of directory when adding directories to the stack, so that only the stack is manipulated.\n\nArguments:\n\n+ `dir`: Makes the current working directory be the top of the stack, and then executes the equivalent of `cd dir`.\n+ `+N`: Brings the Nth directory (counting from the left of the list printed by dirs, starting with zero) to the top of the list by rotating the stack.\n+ `-N`: Brings the Nth directory (counting from the right of the list printed by dirs, starting with zero) to the top of the list by rotating the stack.\n\nExamples:\n\n```javascript\n// process.cwd() === '/usr'\npushd('/etc'); // Returns /etc /usr\npushd('+1');   // Returns /usr /etc\n```\n\nSave the current directory on the top of the directory stack and then cd to `dir`. With no arguments, pushd exchanges the top two directories. Returns an array of paths in the stack.\n\n### popd([options,] ['-N' | '+N'])\n\nAvailable options:\n\n+ `-n`: Suppresses the normal change of directory when removing directories from the stack, so that only the stack is manipulated.\n\nArguments:\n\n+ `+N`: Removes the Nth directory (counting from the left of the list printed by dirs), starting with zero.\n+ `-N`: Removes the Nth directory (counting from the right of the list printed by dirs), starting with zero.\n\nExamples:\n\n```javascript\necho(process.cwd()); // '/usr'\npushd('/etc');       // '/etc /usr'\necho(process.cwd()); // '/etc'\npopd();              // '/usr'\necho(process.cwd()); // '/usr'\n```\n\nWhen no arguments are given, popd removes the top directory from the stack and performs a cd to the new top directory. The elements are numbered from 0 starting at the first directory listed with dirs; i.e., popd is equivalent to popd +0. Returns an array of paths in the stack.\n\n### dirs([options | '+N' | '-N'])\n\nAvailable options:\n\n+ `-c`: Clears the directory stack by deleting all of the elements.\n\nArguments:\n\n+ `+N`: Displays the Nth directory (counting from the left of the list printed by dirs when invoked without options), starting with zero.\n+ `-N`: Displays the Nth directory (counting from the right of the list printed by dirs when invoked without options), starting with zero.\n\nDisplay the list of currently remembered directories. Returns an array of paths in the stack, or a single path if +N or -N was specified.\n\nSee also: pushd, popd\n\n\n### ln(options, source, dest)\n### ln(source, dest)\nAvailable options:\n\n+ `s`: symlink\n+ `f`: force\n\nExamples:\n\n```javascript\nln('file', 'newlink');\nln('-sf', 'file', 'existing');\n```\n\nLinks source to dest. Use -f to force the link, should dest already exist.\n\n\n### exit(code)\nExits the current process with the given exit code.\n\n### env['VAR_NAME']\nObject containing environment variables (both getter and setter). Shortcut to process.env.\n\n### exec(command [, options] [, callback])\nAvailable options (all `false` by default):\n\n+ `async`: Asynchronous execution. Defaults to true if a callback is provided.\n+ `silent`: Do not echo program output to console.\n\nExamples:\n\n```javascript\nvar version = exec('node --version', {silent:true}).output;\n\nvar child = exec('some_long_running_process', {async:true});\nchild.stdout.on('data', function(data) {\n  /* ... do something with data ... */\n});\n\nexec('some_long_running_process', function(code, output) {\n  console.log('Exit code:', code);\n  console.log('Program output:', output);\n});\n```\n\nExecutes the given `command` _synchronously_, unless otherwise specified.\nWhen in synchronous mode returns the object `{ code:..., output:... }`, containing the program's\n`output` (stdout + stderr)  and its exit `code`. Otherwise returns the child process object, and\nthe `callback` gets the arguments `(code, output)`.\n\n**Note:** For long-lived processes, it's best to run `exec()` asynchronously as\nthe current synchronous implementation uses a lot of CPU. This should be getting\nfixed soon.\n\n\n### chmod(octal_mode || octal_string, file)\n### chmod(symbolic_mode, file)\n\nAvailable options:\n\n+ `-v`: output a diagnostic for every file processed\n+ `-c`: like verbose but report only when a change is made\n+ `-R`: change files and directories recursively\n\nExamples:\n\n```javascript\nchmod(755, '/Users/brandon');\nchmod('755', '/Users/brandon'); // same as above\nchmod('u+x', '/Users/brandon');\n```\n\nAlters the permissions of a file or directory by either specifying the\nabsolute permissions in octal form or expressing the changes in symbols.\nThis command tries to mimic the POSIX behavior as much as possible.\nNotable exceptions:\n\n+ In symbolic modes, 'a-r' and '-r' are identical.  No consideration is\n  given to the umask.\n+ There is no \"quiet\" option since default behavior is to run silent.\n\n\n## Non-Unix commands\n\n\n### tempdir()\n\nExamples:\n\n```javascript\nvar tmp = tempdir(); // \"/tmp\" for most *nix platforms\n```\n\nSearches and returns string containing a writeable, platform-dependent temporary directory.\nFollows Python's [tempfile algorithm](http://docs.python.org/library/tempfile.html#tempfile.tempdir).\n\n\n### error()\nTests if error occurred in the last command. Returns `null` if no error occurred,\notherwise returns string explaining the error\n\n\n## Configuration\n\n\n### config.silent\nExample:\n\n```javascript\nvar silentState = config.silent; // save old silent state\nconfig.silent = true;\n/* ... */\nconfig.silent = silentState; // restore old silent state\n```\n\nSuppresses all command output if `true`, except for `echo()` calls.\nDefault is `false`.\n\n### config.fatal\nExample:\n\n```javascript\nconfig.fatal = true;\ncp('this_file_does_not_exist', '/dev/null'); // dies here\n/* more commands... */\n```\n\nIf `true` the script will die on errors. Default is `false`.\n",
+  "readmeFilename": "README.md"
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/shelljs/scripts/generate-docs.js b/node_modules/node-firefox-find-ports/node_modules/shelljs/scripts/generate-docs.js
new file mode 100755
index 0000000..532fed9
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/shelljs/scripts/generate-docs.js
@@ -0,0 +1,21 @@
+#!/usr/bin/env node
+require('../global');
+
+echo('Appending docs to README.md');
+
+cd(__dirname + '/..');
+
+// Extract docs from shell.js
+var docs = grep('//@', 'shell.js');
+
+docs = docs.replace(/\/\/\@include (.+)/g, function(match, path) {
+  var file = path.match('.js$') ? path : path+'.js';
+  return grep('//@', file);
+});
+
+// Remove '//@'
+docs = docs.replace(/\/\/\@ ?/g, '');
+// Append docs to README
+sed('-i', /## Command reference(.|\n)*/, '## Command reference\n\n' + docs, 'README.md');
+
+echo('All done.');
diff --git a/node_modules/node-firefox-find-ports/node_modules/shelljs/scripts/run-tests.js b/node_modules/node-firefox-find-ports/node_modules/shelljs/scripts/run-tests.js
new file mode 100755
index 0000000..f9d31e0
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/shelljs/scripts/run-tests.js
@@ -0,0 +1,50 @@
+#!/usr/bin/env node
+require('../global');
+
+var path = require('path');
+
+var failed = false;
+
+//
+// Lint
+//
+JSHINT_BIN = './node_modules/jshint/bin/jshint';
+cd(__dirname + '/..');
+
+if (!test('-f', JSHINT_BIN)) {
+  echo('JSHint not found. Run `npm install` in the root dir first.');
+  exit(1);
+}
+
+if (exec(JSHINT_BIN + ' *.js test/*.js').code !== 0) {
+  failed = true;
+  echo('*** JSHINT FAILED! (return code != 0)');
+  echo();
+} else {
+  echo('All JSHint tests passed');
+  echo();
+}
+
+//
+// Unit tests
+//
+cd(__dirname + '/../test');
+ls('*.js').forEach(function(file) {
+  echo('Running test:', file);
+  if (exec('node ' + file).code !== 123) { // 123 avoids false positives (e.g. premature exit)
+    failed = true;
+    echo('*** TEST FAILED! (missing exit code "123")');
+    echo();
+  }
+});
+
+if (failed) {
+  echo();
+  echo('*******************************************************');
+  echo('WARNING: Some tests did not pass!');
+  echo('*******************************************************');
+  exit(1);
+} else {
+  echo();
+  echo('All tests passed.');
+}
diff --git a/node_modules/node-firefox-find-ports/node_modules/shelljs/shell.js b/node_modules/node-firefox-find-ports/node_modules/shelljs/shell.js
new file mode 100644
index 0000000..54402c7
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/shelljs/shell.js
@@ -0,0 +1,157 @@
+//
+// ShellJS
+// Unix shell commands on top of Node's API
+//
+// Copyright (c) 2012 Artur Adib
+// http://github.com/arturadib/shelljs
+//
+
+var common = require('./src/common');
+
+
+//@
+//@ All commands run synchronously, unless otherwise stated.
+//@
+
+//@include ./src/cd
+var _cd = require('./src/cd');
+exports.cd = common.wrap('cd', _cd);
+
+//@include ./src/pwd
+var _pwd = require('./src/pwd');
+exports.pwd = common.wrap('pwd', _pwd);
+
+//@include ./src/ls
+var _ls = require('./src/ls');
+exports.ls = common.wrap('ls', _ls);
+
+//@include ./src/find
+var _find = require('./src/find');
+exports.find = common.wrap('find', _find);
+
+//@include ./src/cp
+var _cp = require('./src/cp');
+exports.cp = common.wrap('cp', _cp);
+
+//@include ./src/rm
+var _rm = require('./src/rm');
+exports.rm = common.wrap('rm', _rm);
+
+//@include ./src/mv
+var _mv = require('./src/mv');
+exports.mv = common.wrap('mv', _mv);
+
+//@include ./src/mkdir
+var _mkdir = require('./src/mkdir');
+exports.mkdir = common.wrap('mkdir', _mkdir);
+
+//@include ./src/test
+var _test = require('./src/test');
+exports.test = common.wrap('test', _test);
+
+//@include ./src/cat
+var _cat = require('./src/cat');
+exports.cat = common.wrap('cat', _cat);
+
+//@include ./src/to
+var _to = require('./src/to');
+String.prototype.to = common.wrap('to', _to);
+
+//@include ./src/toEnd
+var _toEnd = require('./src/toEnd');
+String.prototype.toEnd = common.wrap('toEnd', _toEnd);
+
+//@include ./src/sed
+var _sed = require('./src/sed');
+exports.sed = common.wrap('sed', _sed);
+
+//@include ./src/grep
+var _grep = require('./src/grep');
+exports.grep = common.wrap('grep', _grep);
+
+//@include ./src/which
+var _which = require('./src/which');
+exports.which = common.wrap('which', _which);
+
+//@include ./src/echo
+var _echo = require('./src/echo');
+exports.echo = _echo; // don't common.wrap() as it could parse '-options'
+
+//@include ./src/dirs
+var _dirs = require('./src/dirs').dirs;
+exports.dirs = common.wrap("dirs", _dirs);
+var _pushd = require('./src/dirs').pushd;
+exports.pushd = common.wrap('pushd', _pushd);
+var _popd = require('./src/dirs').popd;
+exports.popd = common.wrap("popd", _popd);
+
+//@include ./src/ln
+var _ln = require('./src/ln');
+exports.ln = common.wrap('ln', _ln);
+
+//@
+//@ ### exit(code)
+//@ Exits the current process with the given exit code.
+exports.exit = process.exit;
+
+//@
+//@ ### env['VAR_NAME']
+//@ Object containing environment variables (both getter and setter). Shortcut to process.env.
+exports.env = process.env;
+
+//@include ./src/exec
+var _exec = require('./src/exec');
+exports.exec = common.wrap('exec', _exec, {notUnix:true});
+
+//@include ./src/chmod
+var _chmod = require('./src/chmod');
+exports.chmod = common.wrap('chmod', _chmod);
+
+
+
+//@
+//@ ## Non-Unix commands
+//@
+
+//@include ./src/tempdir
+var _tempDir = require('./src/tempdir');
+exports.tempdir = common.wrap('tempdir', _tempDir);
+
+
+//@include ./src/error
+var _error = require('./src/error');
+exports.error = _error;
+
+
+
+//@
+//@ ## Configuration
+//@
+
+exports.config = common.config;
+
+//@
+//@ ### config.silent
+//@ Example:
+//@
+//@ ```javascript
+//@ var silentState = config.silent; // save old silent state
+//@ config.silent = true;
+//@ /* ... */
+//@ config.silent = silentState; // restore old silent state
+//@ ```
+//@
+//@ Suppresses all command output if `true`, except for `echo()` calls.
+//@ Default is `false`.
+
+//@
+//@ ### config.fatal
+//@ Example:
+//@
+//@ ```javascript
+//@ config.fatal = true;
+//@ cp('this_file_does_not_exist', '/dev/null'); // dies here
+//@ /* more commands... */
+//@ ```
+//@
+//@ If `true` the script will die on errors. Default is `false`.
diff --git a/node_modules/node-firefox-find-ports/node_modules/shelljs/src/cat.js b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/cat.js
new file mode 100644
index 0000000..f6f4d25
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/cat.js
@@ -0,0 +1,43 @@
+var common = require('./common');
+var fs = require('fs');
+
+//@
+//@ ### cat(file [, file ...])
+//@ ### cat(file_array)
+//@
+//@ Examples:
+//@
+//@ ```javascript
+//@ var str = cat('file*.txt');
+//@ var str = cat('file1', 'file2');
+//@ var str = cat(['file1', 'file2']); // same as above
+//@ ```
+//@
+//@ Returns a string containing the given file, or a concatenated string
+//@ containing the files if more than one file is given (a new line character is
+//@ introduced between each file). Wildcard `*` accepted.
+function _cat(options, files) {
+  var cat = '';
+
+  if (!files)
+    common.error('no paths given');
+
+  if (typeof files === 'string')
+    files = [].slice.call(arguments, 1);
+  // if it's array leave it as it is
+
+  files = common.expand(files);
+
+  files.forEach(function(file) {
+    if (!fs.existsSync(file))
+      common.error('no such file or directory: ' + file);
+
+    cat += fs.readFileSync(file, 'utf8') + '\n';
+  });
+
+  if (cat[cat.length-1] === '\n')
+    cat = cat.substring(0, cat.length-1);
+
+  return common.ShellString(cat);
+}
+module.exports = _cat;
diff --git a/node_modules/node-firefox-find-ports/node_modules/shelljs/src/cd.js b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/cd.js
new file mode 100644
index 0000000..230f432
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/cd.js
@@ -0,0 +1,19 @@
+var fs = require('fs');
+var common = require('./common');
+
+//@
+//@ ### cd('dir')
+//@ Changes to directory `dir` for the duration of the script
+function _cd(options, dir) {
+  if (!dir)
+    common.error('directory not specified');
+
+  if (!fs.existsSync(dir))
+    common.error('no such file or directory: ' + dir);
+
+  if (!fs.statSync(dir).isDirectory())
+    common.error('not a directory: ' + dir);
+
+  process.chdir(dir);
+}
+module.exports = _cd;
diff --git a/node_modules/node-firefox-find-ports/node_modules/shelljs/src/chmod.js b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/chmod.js
new file mode 100644
index 0000000..f288893
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/chmod.js
@@ -0,0 +1,208 @@
+var common = require('./common');
+var fs = require('fs');
+var path = require('path');
+
+var PERMS = (function (base) {
+  return {
+    OTHER_EXEC  : base.EXEC,
+    OTHER_WRITE : base.WRITE,
+    OTHER_READ  : base.READ,
+
+    GROUP_EXEC  : base.EXEC  << 3,
+    GROUP_WRITE : base.WRITE << 3,
+    GROUP_READ  : base.READ << 3,
+
+    OWNER_EXEC  : base.EXEC << 6,
+    OWNER_WRITE : base.WRITE << 6,
+    OWNER_READ  : base.READ << 6,
+
+    // Literal octal numbers are apparently not allowed in "strict" javascript.  Using parseInt is
+    // the preferred way, else a jshint warning is thrown.
+    STICKY      : parseInt('01000', 8),
+    SETGID      : parseInt('02000', 8),
+    SETUID      : parseInt('04000', 8),
+
+    TYPE_MASK   : parseInt('0770000', 8)
+  };
+})({
+  EXEC  : 1,
+  WRITE : 2,
+  READ  : 4
+});
+
+//@
+//@ ### chmod(octal_mode || octal_string, file)
+//@ ### chmod(symbolic_mode, file)
+//@
+//@ Available options:
+//@
+//@ + `-v`: output a diagnostic for every file processed//@
+//@ + `-c`: like verbose but report only when a change is made//@
+//@ + `-R`: change files and directories recursively//@
+//@
+//@ Examples:
+//@
+//@ ```javascript
+//@ chmod(755, '/Users/brandon');
+//@ chmod('755', '/Users/brandon'); // same as above
+//@ chmod('u+x', '/Users/brandon');
+//@ ```
+//@
+//@ Alters the permissions of a file or directory by either specifying the
+//@ absolute permissions in octal form or expressing the changes in symbols.
+//@ This command tries to mimic the POSIX behavior as much as possible.
+//@ Notable exceptions:
+//@
+//@ + In symbolic modes, 'a-r' and '-r' are identical.  No consideration is
+//@   given to the umask.
+//@ + There is no "quiet" option since default behavior is to run silent.
+function _chmod(options, mode, filePattern) {
+  if (!filePattern) {
+    if (options.length > 0 && options.charAt(0) === '-') {
+      // Special case where the specified file permissions started with - to subtract perms, which
+      // get picked up by the option parser as command flags.
+      // If we are down by one argument and options starts with -, shift everything over.
+      filePattern = mode;
+      mode = options;
+      options = '';
+    }
+    else {
+      common.error('You must specify a file.');
+    }
+  }
+
+  options = common.parseOptions(options, {
+    'R': 'recursive',
+    'c': 'changes',
+    'v': 'verbose'
+  });
+
+  if (typeof filePattern === 'string') {
+    filePattern = [ filePattern ];
+  }
+
+  var files;
+
+  if (options.recursive) {
+    files = [];
+    common.expand(filePattern).forEach(function addFile(expandedFile) {
+      var stat = fs.lstatSync(expandedFile);
+
+      if (!stat.isSymbolicLink()) {
+        files.push(expandedFile);
+
+        if (stat.isDirectory()) {  // intentionally does not follow symlinks.
+          fs.readdirSync(expandedFile).forEach(function (child) {
+            addFile(expandedFile + '/' + child);
+          });
+        }
+      }
+    });
+  }
+  else {
+    files = common.expand(filePattern);
+  }
+
+  files.forEach(function innerChmod(file) {
+    file = path.resolve(file);
+    if (!fs.existsSync(file)) {
+      common.error('File not found: ' + file);
+    }
+
+    // When recursing, don't follow symlinks.
+    if (options.recursive && fs.lstatSync(file).isSymbolicLink()) {
+      return;
+    }
+
+    var perms = fs.statSync(file).mode;
+    var type = perms & PERMS.TYPE_MASK;
+
+    var newPerms = perms;
+
+    if (isNaN(parseInt(mode, 8))) {
+      // parse options
+      mode.split(',').forEach(function (symbolicMode) {
+        /*jshint regexdash:true */
+        var pattern = /([ugoa]*)([=\+-])([rwxXst]*)/i;
+        var matches = pattern.exec(symbolicMode);
+
+        if (matches) {
+          var applyTo = matches[1];
+          var operator = matches[2];
+          var change = matches[3];
+
+          var changeOwner = applyTo.indexOf('u') != -1 || applyTo === 'a' || applyTo === '';
+          var changeGroup = applyTo.indexOf('g') != -1 || applyTo === 'a' || applyTo === '';
+          var changeOther = applyTo.indexOf('o') != -1 || applyTo === 'a' || applyTo === '';
+
+          var changeRead   = change.indexOf('r') != -1;
+          var changeWrite  = change.indexOf('w') != -1;
+          var changeExec   = change.indexOf('x') != -1;
+          var changeSticky = change.indexOf('t') != -1;
+          var changeSetuid = change.indexOf('s') != -1;
+
+          var mask = 0;
+          if (changeOwner) {
+            mask |= (changeRead ? PERMS.OWNER_READ : 0) + (changeWrite ? PERMS.OWNER_WRITE : 0) + (changeExec ? PERMS.OWNER_EXEC : 0) + (changeSetuid ? PERMS.SETUID : 0);
+          }
+          if (changeGroup) {
+            mask |= (changeRead ? PERMS.GROUP_READ : 0) + (changeWrite ? PERMS.GROUP_WRITE : 0) + (changeExec ? PERMS.GROUP_EXEC : 0) + (changeSetuid ? PERMS.SETGID : 0);
+          }
+          if (changeOther) {
+            mask |= (changeRead ? PERMS.OTHER_READ : 0) + (changeWrite ? PERMS.OTHER_WRITE : 0) + (changeExec ? PERMS.OTHER_EXEC : 0);
+          }
+
+          // Sticky bit is special - it's not tied to user, group or other.
+          if (changeSticky) {
+            mask |= PERMS.STICKY;
+          }
+
+          switch (operator) {
+            case '+':
+              newPerms |= mask;
+              break;
+
+            case '-':
+              newPerms &= ~mask;
+              break;
+
+            case '=':
+              newPerms = type + mask;
+
+              // According to POSIX, when using = to explicitly set the permissions, setuid and setgid can never be cleared.
+              if (fs.statSync(file).isDirectory()) {
+                newPerms |= (PERMS.SETUID + PERMS.SETGID) & perms;
+              }
+              break;
+          }
+
+          if (options.verbose) {
+            log(file + ' -> ' + newPerms.toString(8));
+          }
+
+          if (perms != newPerms) {
+            if (!options.verbose && options.changes) {
+              log(file + ' -> ' + newPerms.toString(8));
+            }
+            fs.chmodSync(file, newPerms);
+          }
+        }
+        else {
+          common.error('Invalid symbolic mode change: ' + symbolicMode);
+        }
+      });
+    }
+    else {
+      // they gave us a full number
+      newPerms = type + parseInt(mode, 8);
+
+      // POSIX rules are that setuid and setgid can only be added using numeric form, but not cleared.
+      if (fs.statSync(file).isDirectory()) {
+        newPerms |= (PERMS.SETUID + PERMS.SETGID) & perms;
+      }
+
+      fs.chmodSync(file, newPerms);
+    }
+  });
+}
+module.exports = _chmod;
diff --git a/node_modules/node-firefox-find-ports/node_modules/shelljs/src/common.js b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/common.js
new file mode 100644
index 0000000..d8c2312
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/common.js
@@ -0,0 +1,203 @@
+var os = require('os');
+var fs = require('fs');
+var _ls = require('./ls');
+
+// Module globals
+var config = {
+  silent: false,
+  fatal: false
+};
+exports.config = config;
+
+var state = {
+  error: null,
+  currentCmd: 'shell.js',
+  tempDir: null
+};
+exports.state = state;
+
+var platform = os.type().match(/^Win/) ? 'win' : 'unix';
+exports.platform = platform;
+
+function log() {
+  if (!config.silent)
+    console.log.apply(this, arguments);
+}
+exports.log = log;
+
+// Shows error message. Throws unless _continue or config.fatal are true
+function error(msg, _continue) {
+  if (state.error === null)
+    state.error = '';
+  state.error += state.currentCmd + ': ' + msg + '\n';
+
+  if (msg.length > 0)
+    log(state.error);
+
+  if (config.fatal)
+    process.exit(1);
+
+  if (!_continue)
+    throw '';
+}
+exports.error = error;
+
+// In the future, when Proxies are default, we can add methods like `.to()` to primitive strings.
+// For now, this is a dummy function to bookmark places we need such strings
+function ShellString(str) {
+  return str;
+}
+exports.ShellString = ShellString;
+
+// Returns {'alice': true, 'bob': false} when passed a dictionary, e.g.:
+//   parseOptions('-a', {'a':'alice', 'b':'bob'});
+function parseOptions(str, map) {
+  if (!map)
+    error('parseOptions() internal error: no map given');
+
+  // All options are false by default
+  var options = {};
+  for (var letter in map)
+    options[map[letter]] = false;
+
+  if (!str)
+    return options; // defaults
+
+  if (typeof str !== 'string')
+    error('parseOptions() internal error: wrong str');
+
+  // e.g. match[1] = 'Rf' for str = '-Rf'
+  var match = str.match(/^\-(.+)/);
+  if (!match)
+    return options;
+
+  // e.g. chars = ['R', 'f']
+  var chars = match[1].split('');
+
+  chars.forEach(function(c) {
+    if (c in map)
+      options[map[c]] = true;
+    else
+      error('option not recognized: '+c);
+  });
+
+  return options;
+}
+exports.parseOptions = parseOptions;
+
+// Expands wildcards with matching (ie. existing) file names.
+// For example:
+//   expand(['file*.js']) = ['file1.js', 'file2.js', ...]
+//   (if the files 'file1.js', 'file2.js', etc, exist in the current dir)
+function expand(list) {
+  var expanded = [];
+  list.forEach(function(listEl) {
+    // Wildcard present on directory names ?
+    if(listEl.search(/\*[^\/]*\//) > -1 || listEl.search(/\*\*[^\/]*\//) > -1) {
+      var match = listEl.match(/^([^*]+\/|)(.*)/);
+      var root = match[1];
+      var rest = match[2];
+      var restRegex = rest.replace(/\*\*/g, ".*").replace(/\*/g, "[^\\/]*");
+      restRegex = new RegExp(restRegex);
+      
+      _ls('-R', root).filter(function (e) {
+        return restRegex.test(e);
+      }).forEach(function(file) {
+        expanded.push(file);
+      });
+    }
+    // Wildcard present on file names ?
+    else if (listEl.search(/\*/) > -1) {
+      _ls('', listEl).forEach(function(file) {
+        expanded.push(file);
+      });
+    } else {
+      expanded.push(listEl);
+    }
+  });
+  return expanded;
+}
+exports.expand = expand;
+
+// Normalizes _unlinkSync() across platforms to match Unix behavior, i.e.
+// file can be unlinked even if it's read-only, see https://github.com/joyent/node/issues/3006
+function unlinkSync(file) {
+  try {
+    fs.unlinkSync(file);
+  } catch(e) {
+    // Try to override file permission
+    if (e.code === 'EPERM') {
+      fs.chmodSync(file, '0666');
+      fs.unlinkSync(file);
+    } else {
+      throw e;
+    }
+  }
+}
+exports.unlinkSync = unlinkSync;
+
+// e.g. 'shelljs_a5f185d0443ca...'
+function randomFileName() {
+  function randomHash(count) {
+    if (count === 1)
+      return parseInt(16*Math.random(), 10).toString(16);
+    else {
+      var hash = '';
+      for (var i=0; i<count; i++)
+        hash += randomHash(1);
+      return hash;
+    }
+  }
+
+  return 'shelljs_'+randomHash(20);
+}
+exports.randomFileName = randomFileName;
+
+// extend(target_obj, source_obj1 [, source_obj2 ...])
+// Shallow extend, e.g.:
+//    extend({A:1}, {b:2}, {c:3}) returns {A:1, b:2, c:3}
+function extend(target) {
+  var sources = [].slice.call(arguments, 1);
+  sources.forEach(function(source) {
+    for (var key in source)
+      target[key] = source[key];
+  });
+
+  return target;
+}
+exports.extend = extend;
+
+// Common wrapper for all Unix-like commands
+function wrap(cmd, fn, options) {
+  return function() {
+    var retValue = null;
+
+    state.currentCmd = cmd;
+    state.error = null;
+
+    try {
+      var args = [].slice.call(arguments, 0);
+
+      if (options && options.notUnix) {
+        retValue = fn.apply(this, args);
+      } else {
+        if (args.length === 0 || typeof args[0] !== 'string' || args[0][0] !== '-')
+          args.unshift(''); // only add dummy option if '-option' not already present
+        retValue = fn.apply(this, args);
+      }
+    } catch (e) {
+      if (!state.error) {
+        // If state.error hasn't been set it's an error thrown by Node, not us - probably a bug...
+        console.log('shell.js: internal error');
+        console.log(e.stack || e);
+        process.exit(1);
+      }
+      if (config.fatal)
+        throw e;
+    }
+
+    state.currentCmd = 'shell.js';
+    return retValue;
+  };
+} // wrap
+exports.wrap = wrap;
diff --git a/node_modules/node-firefox-find-ports/node_modules/shelljs/src/cp.js b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/cp.js
new file mode 100644
index 0000000..141d8e3
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/cp.js
@@ -0,0 +1,201 @@
+var fs = require('fs');
+var path = require('path');
+var common = require('./common');
+var os = require('os');
+
+// Buffered file copy, synchronous
+// (Using readFileSync() + writeFileSync() could easily cause a memory overflow
+//  with large files)
+function copyFileSync(srcFile, destFile) {
+  if (!fs.existsSync(srcFile))
+    common.error('copyFileSync: no such file or directory: ' + srcFile);
+
+  var BUF_LENGTH = 64*1024,
+      buf = new Buffer(BUF_LENGTH),
+      bytesRead = BUF_LENGTH,
+      pos = 0,
+      fdr = null,
+      fdw = null;
+
+  try {
+    fdr = fs.openSync(srcFile, 'r');
+  } catch(e) {
+    common.error('copyFileSync: could not read src file ('+srcFile+')');
+  }
+
+  try {
+    fdw = fs.openSync(destFile, 'w');
+  } catch(e) {
+    common.error('copyFileSync: could not write to dest file (code='+e.code+'):'+destFile);
+  }
+
+  while (bytesRead === BUF_LENGTH) {
+    bytesRead = fs.readSync(fdr, buf, 0, BUF_LENGTH, pos);
+    fs.writeSync(fdw, buf, 0, bytesRead);
+    pos += bytesRead;
+  }
+
+  fs.closeSync(fdr);
+  fs.closeSync(fdw);
+
+  fs.chmodSync(destFile, fs.statSync(srcFile).mode);
+}
+
+// Recursively copies 'sourceDir' into 'destDir'
+// Adapted from https://github.com/ryanmcgrath/wrench-js
+//
+// Copyright (c) 2010 Ryan McGrath
+// Copyright (c) 2012 Artur Adib
+//
+// Licensed under the MIT License
+// http://www.opensource.org/licenses/mit-license.php
+function cpdirSyncRecursive(sourceDir, destDir, opts) {
+  if (!opts) opts = {};
+
+  /* Create the directory where all our junk is moving to; read the mode of the source directory and mirror it */
+  var checkDir = fs.statSync(sourceDir);
+  try {
+    fs.mkdirSync(destDir, checkDir.mode);
+  } catch (e) {
+    //if the directory already exists, that's okay
+    if (e.code !== 'EEXIST') throw e;
+  }
+
+  var files = fs.readdirSync(sourceDir);
+
+  for (var i = 0; i < files.length; i++) {
+    var srcFile = sourceDir + "/" + files[i];
+    var destFile = destDir + "/" + files[i];
+    var srcFileStat = fs.lstatSync(srcFile);
+
+    if (srcFileStat.isDirectory()) {
+      /* recursion this thing right on back. */
+      cpdirSyncRecursive(srcFile, destFile, opts);
+    } else if (srcFileStat.isSymbolicLink()) {
+      var symlinkFull = fs.readlinkSync(srcFile);
+      fs.symlinkSync(symlinkFull, destFile, os.platform() === "win32" ? "junction" : null);
+    } else {
+      /* At this point, we've hit a file actually worth copying... so copy it on over. */
+      if (fs.existsSync(destFile) && !opts.force) {
+        common.log('skipping existing file: ' + files[i]);
+      } else {
+        copyFileSync(srcFile, destFile);
+      }
+    }
+
+  } // for files
+} // cpdirSyncRecursive
+
+
+//@
+//@ ### cp([options ,] source [,source ...], dest)
+//@ ### cp([options ,] source_array, dest)
+//@ Available options:
+//@
+//@ + `-f`: force
+//@ + `-r, -R`: recursive
+//@
+//@ Examples:
+//@
+//@ ```javascript
+//@ cp('file1', 'dir1');
+//@ cp('-Rf', '/tmp/*', '/usr/local/*', '/home/tmp');
+//@ cp('-Rf', ['/tmp/*', '/usr/local/*'], '/home/tmp'); // same as above
+//@ ```
+//@
+//@ Copies files. The wildcard `*` is accepted.
+function _cp(options, sources, dest) {
+  options = common.parseOptions(options, {
+    'f': 'force',
+    'R': 'recursive',
+    'r': 'recursive'
+  });
+
+  // Get sources, dest
+  if (arguments.length < 3) {
+    common.error('missing <source> and/or <dest>');
+  } else if (arguments.length > 3) {
+    sources = [].slice.call(arguments, 1, arguments.length - 1);
+    dest = arguments[arguments.length - 1];
+  } else if (typeof sources === 'string') {
+    sources = [sources];
+  } else if ('length' in sources) {
+    sources = sources; // no-op for array
+  } else {
+    common.error('invalid arguments');
+  }
+
+  var exists = fs.existsSync(dest),
+      stats = exists && fs.statSync(dest);
+
+  // Dest is not existing dir, but multiple sources given
+  if ((!exists || !stats.isDirectory()) && sources.length > 1)
+    common.error('dest is not a directory (too many sources)');
+
+  // Dest is an existing file, but no -f given
+  if (exists && stats.isFile() && !options.force)
+    common.error('dest file already exists: ' + dest);
+
+  if (options.recursive) {
+    // Recursive allows the shortcut syntax "sourcedir/" for "sourcedir/*"
+    // (see Github issue #15)
+    sources.forEach(function(src, i) {
+      if (src[src.length - 1] === '/')
+        sources[i] += '*';
+    });
+
+    // Create dest
+    try {
+      fs.mkdirSync(dest, parseInt('0777', 8));
+    } catch (e) {
+      // like Unix's cp, keep going even if we can't create dest dir
+    }
+  }
+
+  sources = common.expand(sources);
+
+  sources.forEach(function(src) {
+    if (!fs.existsSync(src)) {
+      common.error('no such file or directory: '+src, true);
+      return; // skip file
+    }
+
+    // If here, src exists
+    if (fs.statSync(src).isDirectory()) {
+      if (!options.recursive) {
+        // Non-Recursive
+        common.log(src + ' is a directory (not copied)');
+      } else {
+        // Recursive
+        // 'cp /a/source dest' should create 'source' in 'dest'
+        var newDest = path.join(dest, path.basename(src)),
+            checkDir = fs.statSync(src);
+        try {
+          fs.mkdirSync(newDest, checkDir.mode);
+        } catch (e) {
+          //if the directory already exists, that's okay
+          if (e.code !== 'EEXIST') throw e;
+        }
+
+        cpdirSyncRecursive(src, newDest, {force: options.force});
+      }
+      return; // done with dir
+    }
+
+    // If here, src is a file
+
+    // When copying to '/path/dir':
+    //    thisDest = '/path/dir/file1'
+    var thisDest = dest;
+    if (fs.existsSync(dest) && fs.statSync(dest).isDirectory())
+      thisDest = path.normalize(dest + '/' + path.basename(src));
+
+    if (fs.existsSync(thisDest) && !options.force) {
+      common.error('dest file already exists: ' + thisDest, true);
+      return; // skip file
+    }
+
+    copyFileSync(src, thisDest);
+  }); // forEach(src)
+}
+module.exports = _cp;
diff --git a/node_modules/node-firefox-find-ports/node_modules/shelljs/src/dirs.js b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/dirs.js
new file mode 100644
index 0000000..58fae8b
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/dirs.js
@@ -0,0 +1,191 @@
+var common = require('./common');
+var _cd = require('./cd');
+var path = require('path');
+
+// Pushd/popd/dirs internals
+var _dirStack = [];
+
+function _isStackIndex(index) {
+  return (/^[\-+]\d+$/).test(index);
+}
+
+function _parseStackIndex(index) {
+  if (_isStackIndex(index)) {
+    if (Math.abs(index) < _dirStack.length + 1) { // +1 for pwd
+      return (/^-/).test(index) ? Number(index) - 1 : Number(index);
+    } else {
+      common.error(index + ': directory stack index out of range');
+    }
+  } else {
+    common.error(index + ': invalid number');
+  }
+}
+
+function _actualDirStack() {
+  return [process.cwd()].concat(_dirStack);
+}
+
+//@
+//@ ### pushd([options,] [dir | '-N' | '+N'])
+//@
+//@ Available options:
+//@
+//@ + `-n`: Suppresses the normal change of directory when adding directories to the stack, so that only the stack is manipulated.
+//@
+//@ Arguments:
+//@
+//@ + `dir`: Makes the current working directory be the top of the stack, and then executes the equivalent of `cd dir`.
+//@ + `+N`: Brings the Nth directory (counting from the left of the list printed by dirs, starting with zero) to the top of the list by rotating the stack.
+//@ + `-N`: Brings the Nth directory (counting from the right of the list printed by dirs, starting with zero) to the top of the list by rotating the stack.
+//@
+//@ Examples:
+//@
+//@ ```javascript
+//@ // process.cwd() === '/usr'
+//@ pushd('/etc'); // Returns /etc /usr
+//@ pushd('+1');   // Returns /usr /etc
+//@ ```
+//@
+//@ Save the current directory on the top of the directory stack and then cd to `dir`. With no arguments, pushd exchanges the top two directories. Returns an array of paths in the stack.
+function _pushd(options, dir) {
+  if (_isStackIndex(options)) {
+    dir = options;
+    options = '';
+  }
+
+  options = common.parseOptions(options, {
+    'n' : 'no-cd'
+  });
+
+  var dirs = _actualDirStack();
+
+  if (dir === '+0') {
+    return dirs; // +0 is a noop
+  } else if (!dir) {
+    if (dirs.length > 1) {
+      dirs = dirs.splice(1, 1).concat(dirs);
+    } else {
+      return common.error('no other directory');
+    }
+  } else if (_isStackIndex(dir)) {
+    var n = _parseStackIndex(dir);
+    dirs = dirs.slice(n).concat(dirs.slice(0, n));
+  } else {
+    if (options['no-cd']) {
+      dirs.splice(1, 0, dir);
+    } else {
+      dirs.unshift(dir);
+    }
+  }
+
+  if (options['no-cd']) {
+    dirs = dirs.slice(1);
+  } else {
+    dir = path.resolve(dirs.shift());
+    _cd('', dir);
+  }
+
+  _dirStack = dirs;
+  return _dirs('');
+}
+exports.pushd = _pushd;
+
+//@
+//@ ### popd([options,] ['-N' | '+N'])
+//@
+//@ Available options:
+//@
+//@ + `-n`: Suppresses the normal change of directory when removing directories from the stack, so that only the stack is manipulated.
+//@
+//@ Arguments:
+//@
+//@ + `+N`: Removes the Nth directory (counting from the left of the list printed by dirs), starting with zero.
+//@ + `-N`: Removes the Nth directory (counting from the right of the list printed by dirs), starting with zero.
+//@
+//@ Examples:
+//@
+//@ ```javascript
+//@ echo(process.cwd()); // '/usr'
+//@ pushd('/etc');       // '/etc /usr'
+//@ echo(process.cwd()); // '/etc'
+//@ popd();              // '/usr'
+//@ echo(process.cwd()); // '/usr'
+//@ ```
+//@
+//@ When no arguments are given, popd removes the top directory from the stack and performs a cd to the new top directory. The elements are numbered from 0 starting at the first directory listed with dirs; i.e., popd is equivalent to popd +0. Returns an array of paths in the stack.
+function _popd(options, index) {
+  if (_isStackIndex(options)) {
+    index = options;
+    options = '';
+  }
+
+  options = common.parseOptions(options, {
+    'n' : 'no-cd'
+  });
+
+  if (!_dirStack.length) {
+    return common.error('directory stack empty');
+  }
+
+  index = _parseStackIndex(index || '+0');
+
+  if (options['no-cd'] || index > 0 || _dirStack.length + index === 0) {
+    index = index > 0 ? index - 1 : index;
+    _dirStack.splice(index, 1);
+  } else {
+    var dir = path.resolve(_dirStack.shift());
+    _cd('', dir);
+  }
+
+  return _dirs('');
+}
+exports.popd = _popd;
+
+//@
+//@ ### dirs([options | '+N' | '-N'])
+//@
+//@ Available options:
+//@
+//@ + `-c`: Clears the directory stack by deleting all of the elements.
+//@
+//@ Arguments:
+//@
+//@ + `+N`: Displays the Nth directory (counting from the left of the list printed by dirs when invoked without options), starting with zero.
+//@ + `-N`: Displays the Nth directory (counting from the right of the list printed by dirs when invoked without options), starting with zero.
+//@
+//@ Display the list of currently remembered directories. Returns an array of paths in the stack, or a single path if +N or -N was specified.
+//@
+//@ See also: pushd, popd
+function _dirs(options, index) {
+  if (_isStackIndex(options)) {
+    index = options;
+    options = '';
+  }
+
+  options = common.parseOptions(options, {
+    'c' : 'clear'
+  });
+
+  if (options['clear']) {
+    _dirStack = [];
+    return _dirStack;
+  }
+
+  var stack = _actualDirStack();
+
+  if (index) {
+    index = _parseStackIndex(index);
+
+    if (index < 0) {
+      index = stack.length + index;
+    }
+
+    common.log(stack[index]);
+    return stack[index];
+  }
+
+  common.log(stack.join(' '));
+
+  return stack;
+}
+exports.dirs = _dirs;
diff --git a/node_modules/node-firefox-find-ports/node_modules/shelljs/src/echo.js b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/echo.js
new file mode 100644
index 0000000..760ea84
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/echo.js
@@ -0,0 +1,20 @@
+var common = require('./common');
+
+//@
+//@ ### echo(string [,string ...])
+//@
+//@ Examples:
+//@
+//@ ```javascript
+//@ echo('hello world');
+//@ var str = echo('hello world');
+//@ ```
+//@
+//@ Prints string to stdout, and returns string with additional utility methods
+//@ like `.to()`.
+function _echo() {
+  var messages = [].slice.call(arguments, 0);
+  console.log.apply(this, messages);
+  return common.ShellString(messages.join(' '));
+}
+module.exports = _echo;
diff --git a/node_modules/node-firefox-find-ports/node_modules/shelljs/src/error.js b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/error.js
new file mode 100644
index 0000000..cca3efb
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/error.js
@@ -0,0 +1,10 @@
+var common = require('./common');
+
+//@
+//@ ### error()
+//@ Tests if error occurred in the last command. Returns `null` if no error occurred,
+//@ otherwise returns string explaining the error
+function error() {
+  return common.state.error;
+};
+module.exports = error;
diff --git a/node_modules/node-firefox-find-ports/node_modules/shelljs/src/exec.js b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/exec.js
new file mode 100644
index 0000000..7ccdbc0
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/exec.js
@@ -0,0 +1,181 @@
+var common = require('./common');
+var _tempDir = require('./tempdir');
+var _pwd = require('./pwd');
+var path = require('path');
+var fs = require('fs');
+var child = require('child_process');
+
+// Hack to run child_process.exec() synchronously (sync avoids callback hell)
+// Uses a custom wait loop that checks for a flag file, created when the child process is done.
+// (Can't do a wait loop that checks for internal Node variables/messages as
+// Node is single-threaded; callbacks and other internal state changes are done in the
+// event loop).
+function execSync(cmd, opts) {
+  var tempDir = _tempDir();
+  var stdoutFile = path.resolve(tempDir+'/'+common.randomFileName()),
+      codeFile = path.resolve(tempDir+'/'+common.randomFileName()),
+      scriptFile = path.resolve(tempDir+'/'+common.randomFileName()),
+      sleepFile = path.resolve(tempDir+'/'+common.randomFileName());
+
+  var options = common.extend({
+    silent: common.config.silent
+  }, opts);
+
+  var previousStdoutContent = '';
+  // Echoes stdout changes from running process, if not silent
+  function updateStdout() {
+    if (options.silent || !fs.existsSync(stdoutFile))
+      return;
+
+    var stdoutContent = fs.readFileSync(stdoutFile, 'utf8');
+    // No changes since last time?
+    if (stdoutContent.length <= previousStdoutContent.length)
+      return;
+
+    process.stdout.write(stdoutContent.substr(previousStdoutContent.length));
+    previousStdoutContent = stdoutContent;
+  }
+
+  function escape(str) {
+    return (str+'').replace(/([\\"'])/g, "\\$1").replace(/\0/g, "\\0");
+  }
+
+  cmd += ' > '+stdoutFile+' 2>&1'; // works on both win/unix
+
+  var script =
+   "var child = require('child_process')," +
+   "     fs = require('fs');" +
+   "child.exec('"+escape(cmd)+"', {env: process.env, maxBuffer: 20*1024*1024}, function(err) {" +
+   "  fs.writeFileSync('"+escape(codeFile)+"', err ? err.code.toString() : '0');" +
+   "});";
+
+  if (fs.existsSync(scriptFile)) common.unlinkSync(scriptFile);
+  if (fs.existsSync(stdoutFile)) common.unlinkSync(stdoutFile);
+  if (fs.existsSync(codeFile)) common.unlinkSync(codeFile);
+
+  fs.writeFileSync(scriptFile, script);
+  child.exec('"'+process.execPath+'" '+scriptFile, {
+    env: process.env,
+    cwd: _pwd(),
+    maxBuffer: 20*1024*1024
+  });
+
+  // The wait loop
+  // sleepFile is used as a dummy I/O op to mitigate unnecessary CPU usage
+  // (tried many I/O sync ops, writeFileSync() seems to be only one that is effective in reducing
+  // CPU usage, though apparently not so much on Windows)
+  while (!fs.existsSync(codeFile)) { updateStdout(); fs.writeFileSync(sleepFile, 'a'); }
+  while (!fs.existsSync(stdoutFile)) { updateStdout(); fs.writeFileSync(sleepFile, 'a'); }
+
+  // At this point codeFile exists, but it's not necessarily flushed yet.
+  // Keep reading it until it is.
+  var code = parseInt('', 10);
+  while (isNaN(code)) {
+    code = parseInt(fs.readFileSync(codeFile, 'utf8'), 10);
+  }
+
+  var stdout = fs.readFileSync(stdoutFile, 'utf8');
+
+  // No biggie if we can't erase the files now -- they're in a temp dir anyway
+  try { common.unlinkSync(scriptFile); } catch(e) {}
+  try { common.unlinkSync(stdoutFile); } catch(e) {}
+  try { common.unlinkSync(codeFile); } catch(e) {}
+  try { common.unlinkSync(sleepFile); } catch(e) {}
+
+  // some shell return codes are defined as errors, per http://tldp.org/LDP/abs/html/exitcodes.html
+  if (code === 1 || code === 2 || code >= 126)  {
+      common.error('', true); // unix/shell doesn't really give an error message after non-zero exit codes
+  }
+  // True if successful, false if not
+  var obj = {
+    code: code,
+    output: stdout
+  };
+  return obj;
+} // execSync()
+
+// Wrapper around exec() to enable echoing output to console in real time
+function execAsync(cmd, opts, callback) {
+  var output = '';
+
+  var options = common.extend({
+    silent: common.config.silent
+  }, opts);
+
+  var c = child.exec(cmd, {env: process.env, maxBuffer: 20*1024*1024}, function(err) {
+    if (callback)
+      callback(err ? err.code : 0, output);
+  });
+
+  c.stdout.on('data', function(data) {
+    output += data;
+    if (!options.silent)
+      process.stdout.write(data);
+  });
+
+  c.stderr.on('data', function(data) {
+    output += data;
+    if (!options.silent)
+      process.stdout.write(data);
+  });
+
+  return c;
+}
+
+//@
+//@ ### exec(command [, options] [, callback])
+//@ Available options (all `false` by default):
+//@
+//@ + `async`: Asynchronous execution. Defaults to true if a callback is provided.
+//@ + `silent`: Do not echo program output to console.
+//@
+//@ Examples:
+//@
+//@ ```javascript
+//@ var version = exec('node --version', {silent:true}).output;
+//@
+//@ var child = exec('some_long_running_process', {async:true});
+//@ child.stdout.on('data', function(data) {
+//@   /* ... do something with data ... */
+//@ });
+//@
+//@ exec('some_long_running_process', function(code, output) {
+//@   console.log('Exit code:', code);
+//@   console.log('Program output:', output);
+//@ });
+//@ ```
+//@
+//@ Executes the given `command` _synchronously_, unless otherwise specified.
+//@ When in synchronous mode returns the object `{ code:..., output:... }`, containing the program's
+//@ `output` (stdout + stderr)  and its exit `code`. Otherwise returns the child process object, and
+//@ the `callback` gets the arguments `(code, output)`.
+//@
+//@ **Note:** For long-lived processes, it's best to run `exec()` asynchronously as
+//@ the current synchronous implementation uses a lot of CPU. This should be getting
+//@ fixed soon.
+function _exec(command, options, callback) {
+  if (!command)
+    common.error('must specify command');
+
+  // Callback is defined instead of options.
+  if (typeof options === 'function') {
+    callback = options;
+    options = { async: true };
+  }
+
+  // Callback is defined with options.
+  if (typeof options === 'object' && typeof callback === 'function') {
+    options.async = true;
+  }
+
+  options = common.extend({
+    silent: common.config.silent,
+    async: false
+  }, options);
+
+  if (options.async)
+    return execAsync(command, options, callback);
+  else
+    return execSync(command, options);
+}
+module.exports = _exec;
diff --git a/node_modules/node-firefox-find-ports/node_modules/shelljs/src/find.js b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/find.js
new file mode 100644
index 0000000..d9eeec2
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/find.js
@@ -0,0 +1,51 @@
+var fs = require('fs');
+var common = require('./common');
+var _ls = require('./ls');
+
+//@
+//@ ### find(path [,path ...])
+//@ ### find(path_array)
+//@ Examples:
+//@
+//@ ```javascript
+//@ find('src', 'lib');
+//@ find(['src', 'lib']); // same as above
+//@ find('.').filter(function(file) { return file.match(/\.js$/); });
+//@ ```
+//@
+//@ Returns array of all files (however deep) in the given paths.
+//@
+//@ The main difference from `ls('-R', path)` is that the resulting file names
+//@ include the base directories, e.g. `lib/resources/file1` instead of just `file1`.
+function _find(options, paths) {
+  if (!paths)
+    common.error('no path specified');
+  else if (typeof paths === 'object')
+    paths = paths; // assume array
+  else if (typeof paths === 'string')
+    paths = [].slice.call(arguments, 1);
+
+  var list = [];
+
+  function pushFile(file) {
+    if (common.platform === 'win')
+      file = file.replace(/\\/g, '/');
+    list.push(file);
+  }
+
+  // why not simply do ls('-R', paths)? because the output wouldn't give the base dirs
+  // to get the base dir in the output, we need instead ls('-R', 'dir/*') for every directory
+
+  paths.forEach(function(file) {
+    pushFile(file);
+
+    if (fs.statSync(file).isDirectory()) {
+      _ls('-RA', file+'/*').forEach(function(subfile) {
+        pushFile(subfile);
+      });
+    }
+  });
+
+  return list;
+}
+module.exports = _find;
diff --git a/node_modules/node-firefox-find-ports/node_modules/shelljs/src/grep.js b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/grep.js
new file mode 100644
index 0000000..00c7d6a
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/grep.js
@@ -0,0 +1,52 @@
+var common = require('./common');
+var fs = require('fs');
+
+//@
+//@ ### grep([options ,] regex_filter, file [, file ...])
+//@ ### grep([options ,] regex_filter, file_array)
+//@ Available options:
+//@
+//@ + `-v`: Inverse the sense of the regex and print the lines not matching the criteria.
+//@
+//@ Examples:
+//@
+//@ ```javascript
+//@ grep('-v', 'GLOBAL_VARIABLE', '*.js');
+//@ grep('GLOBAL_VARIABLE', '*.js');
+//@ ```
+//@
+//@ Reads input string from given files and returns a string containing all lines of the
+//@ file that match the given `regex_filter`. Wildcard `*` accepted.
+function _grep(options, regex, files) {
+  options = common.parseOptions(options, {
+    'v': 'inverse'
+  });
+
+  if (!files)
+    common.error('no paths given');
+
+  if (typeof files === 'string')
+    files = [].slice.call(arguments, 2);
+  // if it's array leave it as it is
+
+  files = common.expand(files);
+
+  var grep = '';
+  files.forEach(function(file) {
+    if (!fs.existsSync(file)) {
+      common.error('no such file or directory: ' + file, true);
+      return;
+    }
+
+    var contents = fs.readFileSync(file, 'utf8'),
+        lines = contents.split(/\r*\n/);
+    lines.forEach(function(line) {
+      var matched = line.match(regex);
+      if ((options.inverse && !matched) || (!options.inverse && matched))
+        grep += line + '\n';
+    });
+  });
+
+  return common.ShellString(grep);
+}
+module.exports = _grep;
diff --git a/node_modules/node-firefox-find-ports/node_modules/shelljs/src/ln.js b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/ln.js
new file mode 100644
index 0000000..a7b9701
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/ln.js
@@ -0,0 +1,53 @@
+var fs = require('fs');
+var path = require('path');
+var common = require('./common');
+var os = require('os');
+
+//@
+//@ ### ln(options, source, dest)
+//@ ### ln(source, dest)
+//@ Available options:
+//@
+//@ + `s`: symlink
+//@ + `f`: force
+//@
+//@ Examples:
+//@
+//@ ```javascript
+//@ ln('file', 'newlink');
+//@ ln('-sf', 'file', 'existing');
+//@ ```
+//@
+//@ Links source to dest. Use -f to force the link, should dest already exist.
+function _ln(options, source, dest) {
+  options = common.parseOptions(options, {
+    's': 'symlink',
+    'f': 'force'
+  });
+
+  if (!source || !dest) {
+    common.error('Missing <source> and/or <dest>');
+  }
+
+  source = path.resolve(process.cwd(), String(source));
+  dest = path.resolve(process.cwd(), String(dest));
+
+  if (!fs.existsSync(source)) {
+    common.error('Source file does not exist', true);
+  }
+
+  if (fs.existsSync(dest)) {
+    if (!options.force) {
+      common.error('Destination file exists', true);
+    }
+
+    fs.unlinkSync(dest);
+  }
+
+  if (options.symlink) {
+    fs.symlinkSync(source, dest, os.platform() === "win32" ? "junction" : null);
+  } else {
+    fs.linkSync(source, dest, os.platform() === "win32" ? "junction" : null);
+  }
+}
+module.exports = _ln;
diff --git a/node_modules/node-firefox-find-ports/node_modules/shelljs/src/ls.js b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/ls.js
new file mode 100644
index 0000000..3345db4
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/ls.js
@@ -0,0 +1,126 @@
+var path = require('path');
+var fs = require('fs');
+var common = require('./common');
+var _cd = require('./cd');
+var _pwd = require('./pwd');
+
+//@
+//@ ### ls([options ,] path [,path ...])
+//@ ### ls([options ,] path_array)
+//@ Available options:
+//@
+//@ + `-R`: recursive
+//@ + `-A`: all files (include files beginning with `.`, except for `.` and `..`)
+//@
+//@ Examples:
+//@
+//@ ```javascript
+//@ ls('projs/*.js');
+//@ ls('-R', '/users/me', '/tmp');
+//@ ls('-R', ['/users/me', '/tmp']); // same as above
+//@ ```
+//@
+//@ Returns array of files in the given path, or in current directory if no path provided.
+function _ls(options, paths) {
+  options = common.parseOptions(options, {
+    'R': 'recursive',
+    'A': 'all',
+    'a': 'all_deprecated'
+  });
+
+  if (options.all_deprecated) {
+    // We won't support the -a option as it's hard to image why it's useful
+    // (it includes '.' and '..' in addition to '.*' files)
+    // For backwards compatibility we'll dump a deprecated message and proceed as before
+    common.log('ls: Option -a is deprecated. Use -A instead');
+    options.all = true;
+  }
+
+  if (!paths)
+    paths = ['.'];
+  else if (typeof paths === 'object')
+    paths = paths; // assume array
+  else if (typeof paths === 'string')
+    paths = [].slice.call(arguments, 1);
+
+  var list = [];
+
+  // Conditionally pushes file to list - returns true if pushed, false otherwise
+  // (e.g. prevents hidden files to be included unless explicitly told so)
+  function pushFile(file, query) {
+    // hidden file?
+    if (path.basename(file)[0] === '.') {
+      // not explicitly asking for hidden files?
+      if (!options.all && !(path.basename(query)[0] === '.' && path.basename(query).length > 1))
+        return false;
+    }
+
+    if (common.platform === 'win')
+      file = file.replace(/\\/g, '/');
+
+    list.push(file);
+    return true;
+  }
+
+  paths.forEach(function(p) {
+    if (fs.existsSync(p)) {
+      var stats = fs.statSync(p);
+      // Simple file?
+      if (stats.isFile()) {
+        pushFile(p, p);
+        return; // continue
+      }
+
+      // Simple dir?
+      if (stats.isDirectory()) {
+        // Iterate over p contents
+        fs.readdirSync(p).forEach(function(file) {
+          if (!pushFile(file, p))
+            return;
+
+          // Recursive?
+          if (options.recursive) {
+            var oldDir = _pwd();
+            _cd('', p);
+            if (fs.statSync(file).isDirectory())
+              list = list.concat(_ls('-R'+(options.all?'A':''), file+'/*'));
+            _cd('', oldDir);
+          }
+        });
+        return; // continue
+      }
+    }
+
+    // p does not exist - possible wildcard present
+
+    var basename = path.basename(p);
+    var dirname = path.dirname(p);
+    // Wildcard present on an existing dir? (e.g. '/tmp/*.js')
+    if (basename.search(/\*/) > -1 && fs.existsSync(dirname) && fs.statSync(dirname).isDirectory) {
+      // Escape special regular expression chars
+      var regexp = basename.replace(/(\^|\$|\(|\)|<|>|\[|\]|\{|\}|\.|\+|\?)/g, '\\$1');
+      // Translates wildcard into regex
+      regexp = '^' + regexp.replace(/\*/g, '.*') + '$';
+      // Iterate over directory contents
+      fs.readdirSync(dirname).forEach(function(file) {
+        if (file.match(new RegExp(regexp))) {
+          if (!pushFile(path.normalize(dirname+'/'+file), basename))
+            return;
+
+          // Recursive?
+          if (options.recursive) {
+            var pp = dirname + '/' + file;
+            if (fs.lstatSync(pp).isDirectory())
+              list = list.concat(_ls('-R'+(options.all?'A':''), pp+'/*'));
+          } // recursive
+        } // if file matches
+      }); // forEach
+      return;
+    }
+
+    common.error('no such file or directory: ' + p, true);
+  });
+
+  return list;
+}
+module.exports = _ls;
diff --git a/node_modules/node-firefox-find-ports/node_modules/shelljs/src/mkdir.js b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/mkdir.js
new file mode 100644
index 0000000..5a7088f
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/mkdir.js
@@ -0,0 +1,68 @@
+var common = require('./common');
+var fs = require('fs');
+var path = require('path');
+
+// Recursively creates 'dir'
+function mkdirSyncRecursive(dir) {
+  var baseDir = path.dirname(dir);
+
+  // Base dir exists, no recursion necessary
+  if (fs.existsSync(baseDir)) {
+    fs.mkdirSync(dir, parseInt('0777', 8));
+    return;
+  }
+
+  // Base dir does not exist, go recursive
+  mkdirSyncRecursive(baseDir);
+
+  // Base dir created, can create dir
+  fs.mkdirSync(dir, parseInt('0777', 8));
+}
+
+//@
+//@ ### mkdir([options ,] dir [, dir ...])
+//@ ### mkdir([options ,] dir_array)
+//@ Available options:
+//@
+//@ + `p`: full path (will create intermediate dirs if necessary)
+//@
+//@ Examples:
+//@
+//@ ```javascript
+//@ mkdir('-p', '/tmp/a/b/c/d', '/tmp/e/f/g');
+//@ mkdir('-p', ['/tmp/a/b/c/d', '/tmp/e/f/g']); // same as above
+//@ ```
+//@
+//@ Creates directories.
+function _mkdir(options, dirs) {
+  options = common.parseOptions(options, {
+    'p': 'fullpath'
+  });
+  if (!dirs)
+    common.error('no paths given');
+
+  if (typeof dirs === 'string')
+    dirs = [].slice.call(arguments, 1);
+  // if it's array leave it as it is
+
+  dirs.forEach(function(dir) {
+    if (fs.existsSync(dir)) {
+      if (!options.fullpath)
+          common.error('path already exists: ' + dir, true);
+      return; // skip dir
+    }
+
+    // Base dir does not exist, and no -p option given
+    var baseDir = path.dirname(dir);
+    if (!fs.existsSync(baseDir) && !options.fullpath) {
+      common.error('no such file or directory: ' + baseDir, true);
+      return; // skip dir
+    }
+
+    if (options.fullpath)
+      mkdirSyncRecursive(dir);
+    else
+      fs.mkdirSync(dir, parseInt('0777', 8));
+  });
+} // mkdir
+module.exports = _mkdir;
diff --git a/node_modules/node-firefox-find-ports/node_modules/shelljs/src/mv.js b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/mv.js
new file mode 100644
index 0000000..11f9607
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/mv.js
@@ -0,0 +1,80 @@
+var fs = require('fs');
+var path = require('path');
+var common = require('./common');
+
+//@
+//@ ### mv(source [, source ...], dest')
+//@ ### mv(source_array, dest')
+//@ Available options:
+//@
+//@ + `f`: force
+//@
+//@ Examples:
+//@
+//@ ```javascript
+//@ mv('-f', 'file', 'dir/');
+//@ mv('file1', 'file2', 'dir/');
+//@ mv(['file1', 'file2'], 'dir/'); // same as above
+//@ ```
+//@
+//@ Moves files. The wildcard `*` is accepted.
+function _mv(options, sources, dest) {
+  options = common.parseOptions(options, {
+    'f': 'force'
+  });
+
+  // Get sources, dest
+  if (arguments.length < 3) {
+    common.error('missing <source> and/or <dest>');
+  } else if (arguments.length > 3) {
+    sources = [].slice.call(arguments, 1, arguments.length - 1);
+    dest = arguments[arguments.length - 1];
+  } else if (typeof sources === 'string') {
+    sources = [sources];
+  } else if ('length' in sources) {
+    sources = sources; // no-op for array
+  } else {
+    common.error('invalid arguments');
+  }
+
+  sources = common.expand(sources);
+
+  var exists = fs.existsSync(dest),
+      stats = exists && fs.statSync(dest);
+
+  // Dest is not existing dir, but multiple sources given
+  if ((!exists || !stats.isDirectory()) && sources.length > 1)
+    common.error('dest is not a directory (too many sources)');
+
+  // Dest is an existing file, but no -f given
+  if (exists && stats.isFile() && !options.force)
+    common.error('dest file already exists: ' + dest);
+
+  sources.forEach(function(src) {
+    if (!fs.existsSync(src)) {
+      common.error('no such file or directory: '+src, true);
+      return; // skip file
+    }
+
+    // If here, src exists
+
+    // When copying to '/path/dir':
+    //    thisDest = '/path/dir/file1'
+    var thisDest = dest;
+    if (fs.existsSync(dest) && fs.statSync(dest).isDirectory())
+      thisDest = path.normalize(dest + '/' + path.basename(src));
+
+    if (fs.existsSync(thisDest) && !options.force) {
+      common.error('dest file already exists: ' + thisDest, true);
+      return; // skip file
+    }
+
+    if (path.resolve(src) === path.dirname(path.resolve(thisDest))) {
+      common.error('cannot move to self: '+src, true);
+      return; // skip file
+    }
+
+    fs.renameSync(src, thisDest);
+  }); // forEach(src)
+} // mv
+module.exports = _mv;
diff --git a/node_modules/node-firefox-find-ports/node_modules/shelljs/src/popd.js b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/popd.js
new file mode 100644
index 0000000..11ea24f
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/popd.js
@@ -0,0 +1 @@
+// see dirs.js
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-ports/node_modules/shelljs/src/pushd.js b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/pushd.js
new file mode 100644
index 0000000..11ea24f
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/pushd.js
@@ -0,0 +1 @@
+// see dirs.js
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-ports/node_modules/shelljs/src/pwd.js b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/pwd.js
new file mode 100644
index 0000000..41727bb
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/pwd.js
@@ -0,0 +1,11 @@
+var path = require('path');
+var common = require('./common');
+
+//@
+//@ ### pwd()
+//@ Returns the current directory.
+function _pwd(options) {
+  var pwd = path.resolve(process.cwd());
+  return common.ShellString(pwd);
+}
+module.exports = _pwd;
diff --git a/node_modules/node-firefox-find-ports/node_modules/shelljs/src/rm.js b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/rm.js
new file mode 100644
index 0000000..3abe6e1
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/rm.js
@@ -0,0 +1,145 @@
+var common = require('./common');
+var fs = require('fs');
+
+// Recursively removes 'dir'
+// Adapted from https://github.com/ryanmcgrath/wrench-js
+//
+// Copyright (c) 2010 Ryan McGrath
+// Copyright (c) 2012 Artur Adib
+//
+// Licensed under the MIT License
+// http://www.opensource.org/licenses/mit-license.php
+function rmdirSyncRecursive(dir, force) {
+  var files;
+
+  files = fs.readdirSync(dir);
+
+  // Loop through and delete everything in the sub-tree after checking it
+  for(var i = 0; i < files.length; i++) {
+    var file = dir + "/" + files[i],
+        currFile = fs.lstatSync(file);
+
+    if(currFile.isDirectory()) { // Recursive function back to the beginning
+      rmdirSyncRecursive(file, force);
+    }
+
+    else if(currFile.isSymbolicLink()) { // Unlink symlinks
+      if (force || isWriteable(file)) {
+        try {
+          common.unlinkSync(file);
+        } catch (e) {
+          common.error('could not remove file (code '+e.code+'): ' + file, true);
+        }
+      }
+    }
+
+    else // Assume it's a file - perhaps a try/catch belongs here?
+      if (force || isWriteable(file)) {
+        try {
+          common.unlinkSync(file);
+        } catch (e) {
+          common.error('could not remove file (code '+e.code+'): ' + file, true);
+        }
+      }
+  }
+
+  // Now that we know everything in the sub-tree has been deleted, we can delete the main directory.
+  // Huzzah for the shopkeep.
+
+  var result;
+  try {
+    result = fs.rmdirSync(dir);
+  } catch(e) {
+    common.error('could not remove directory (code '+e.code+'): ' + dir, true);
+  }
+
+  return result;
+} // rmdirSyncRecursive
+
+// Hack to determine if file has write permissions for current user
+// Avoids having to check user, group, etc, but it's probably slow
+function isWriteable(file) {
+  var writePermission = true;
+  try {
+    var __fd = fs.openSync(file, 'a');
+    fs.closeSync(__fd);
+  } catch(e) {
+    writePermission = false;
+  }
+
+  return writePermission;
+}
+
+//@
+//@ ### rm([options ,] file [, file ...])
+//@ ### rm([options ,] file_array)
+//@ Available options:
+//@
+//@ + `-f`: force
+//@ + `-r, -R`: recursive
+//@
+//@ Examples:
+//@
+//@ ```javascript
+//@ rm('-rf', '/tmp/*');
+//@ rm('some_file.txt', 'another_file.txt');
+//@ rm(['some_file.txt', 'another_file.txt']); // same as above
+//@ ```
+//@
+//@ Removes files. The wildcard `*` is accepted.
+function _rm(options, files) {
+  options = common.parseOptions(options, {
+    'f': 'force',
+    'r': 'recursive',
+    'R': 'recursive'
+  });
+  if (!files)
+    common.error('no paths given');
+
+  if (typeof files === 'string')
+    files = [].slice.call(arguments, 1);
+  // if it's array leave it as it is
+
+  files = common.expand(files);
+
+  files.forEach(function(file) {
+    if (!fs.existsSync(file)) {
+      // Path does not exist, no force flag given
+      if (!options.force)
+        common.error('no such file or directory: '+file, true);
+
+      return; // skip file
+    }
+
+    // If here, path exists
+
+    var stats = fs.lstatSync(file);
+    if (stats.isFile() || stats.isSymbolicLink()) {
+
+      // Do not check for file writing permissions
+      if (options.force) {
+        common.unlinkSync(file);
+        return;
+      }
+
+      if (isWriteable(file))
+        common.unlinkSync(file);
+      else
+        common.error('permission denied: '+file, true);
+
+      return;
+    } // simple file
+
+    // Path is an existing directory, but no -r flag given
+    if (stats.isDirectory() && !options.recursive) {
+      common.error('path is a directory', true);
+      return; // skip path
+    }
+
+    // Recursively remove existing directory
+    if (stats.isDirectory() && options.recursive) {
+      rmdirSyncRecursive(file, options.force);
+    }
+  }); // forEach(file)
+} // rm
+module.exports = _rm;
diff --git a/node_modules/node-firefox-find-ports/node_modules/shelljs/src/sed.js b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/sed.js
new file mode 100644
index 0000000..65f7cb4
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/sed.js
@@ -0,0 +1,43 @@
+var common = require('./common');
+var fs = require('fs');
+
+//@
+//@ ### sed([options ,] search_regex, replacement, file)
+//@ Available options:
+//@
+//@ + `-i`: Replace contents of 'file' in-place. _Note that no backups will be created!_
+//@
+//@ Examples:
+//@
+//@ ```javascript
+//@ sed('-i', 'PROGRAM_VERSION', 'v0.1.3', 'source.js');
+//@ sed(/.*DELETE_THIS_LINE.*\n/, '', 'source.js');
+//@ ```
+//@
+//@ Reads an input string from `file` and performs a JavaScript `replace()` on the input
+//@ using the given search regex and replacement string or function. Returns the new string after replacement.
+function _sed(options, regex, replacement, file) {
+  options = common.parseOptions(options, {
+    'i': 'inplace'
+  });
+
+  if (typeof replacement === 'string' || typeof replacement === 'function')
+    replacement = replacement; // no-op
+  else if (typeof replacement === 'number')
+    replacement = replacement.toString(); // fallback
+  else
+    common.error('invalid replacement string');
+
+  if (!file)
+    common.error('no file given');
+
+  if (!fs.existsSync(file))
+    common.error('no such file or directory: ' + file);
+
+  var result = fs.readFileSync(file, 'utf8').replace(regex, replacement);
+  if (options.inplace)
+    fs.writeFileSync(file, result, 'utf8');
+
+  return common.ShellString(result);
+}
+module.exports = _sed;
diff --git a/node_modules/node-firefox-find-ports/node_modules/shelljs/src/tempdir.js b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/tempdir.js
new file mode 100644
index 0000000..45953c2
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/tempdir.js
@@ -0,0 +1,56 @@
+var common = require('./common');
+var os = require('os');
+var fs = require('fs');
+
+// Returns false if 'dir' is not a writeable directory, 'dir' otherwise
+function writeableDir(dir) {
+  if (!dir || !fs.existsSync(dir))
+    return false;
+
+  if (!fs.statSync(dir).isDirectory())
+    return false;
+
+  var testFile = dir+'/'+common.randomFileName();
+  try {
+    fs.writeFileSync(testFile, ' ');
+    common.unlinkSync(testFile);
+    return dir;
+  } catch (e) {
+    return false;
+  }
+}
+
+
+//@
+//@ ### tempdir()
+//@
+//@ Examples:
+//@
+//@ ```javascript
+//@ var tmp = tempdir(); // "/tmp" for most *nix platforms
+//@ ```
+//@
+//@ Searches and returns string containing a writeable, platform-dependent temporary directory.
+//@ Follows Python's [tempfile algorithm](http://docs.python.org/library/tempfile.html#tempfile.tempdir).
+function _tempDir() {
+  var state = common.state;
+  if (state.tempDir)
+    return state.tempDir; // from cache
+
+  state.tempDir = writeableDir(os.tempDir && os.tempDir()) || // node 0.8+
+                  writeableDir(process.env['TMPDIR']) ||
+                  writeableDir(process.env['TEMP']) ||
+                  writeableDir(process.env['TMP']) ||
+                  writeableDir(process.env['Wimp$ScrapDir']) || // RiscOS
+                  writeableDir('C:\\TEMP') || // Windows
+                  writeableDir('C:\\TMP') || // Windows
+                  writeableDir('\\TEMP') || // Windows
+                  writeableDir('\\TMP') || // Windows
+                  writeableDir('/tmp') ||
+                  writeableDir('/var/tmp') ||
+                  writeableDir('/usr/tmp') ||
+                  writeableDir('.'); // last resort
+
+  return state.tempDir;
+}
+module.exports = _tempDir;
diff --git a/node_modules/node-firefox-find-ports/node_modules/shelljs/src/test.js b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/test.js
new file mode 100644
index 0000000..8a4ac7d
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/test.js
@@ -0,0 +1,85 @@
+var common = require('./common');
+var fs = require('fs');
+
+//@
+//@ ### test(expression)
+//@ Available expression primaries:
+//@
+//@ + `'-b', 'path'`: true if path is a block device
+//@ + `'-c', 'path'`: true if path is a character device
+//@ + `'-d', 'path'`: true if path is a directory
+//@ + `'-e', 'path'`: true if path exists
+//@ + `'-f', 'path'`: true if path is a regular file
+//@ + `'-L', 'path'`: true if path is a symboilc link
+//@ + `'-p', 'path'`: true if path is a pipe (FIFO)
+//@ + `'-S', 'path'`: true if path is a socket
+//@
+//@ Examples:
+//@
+//@ ```javascript
+//@ if (test('-d', path)) { /* do something with dir */ };
+//@ if (!test('-f', path)) continue; // skip if it's a regular file
+//@ ```
+//@
+//@ Evaluates expression using the available primaries and returns corresponding value.
+function _test(options, path) {
+  if (!path)
+    common.error('no path given');
+
+  // hack - only works with unary primaries
+  options = common.parseOptions(options, {
+    'b': 'block',
+    'c': 'character',
+    'd': 'directory',
+    'e': 'exists',
+    'f': 'file',
+    'L': 'link',
+    'p': 'pipe',
+    'S': 'socket'
+  });
+
+  var canInterpret = false;
+  for (var key in options)
+    if (options[key] === true) {
+      canInterpret = true;
+      break;
+    }
+
+  if (!canInterpret)
+    common.error('could not interpret expression');
+
+  if (options.link) {
+    try {
+      return fs.lstatSync(path).isSymbolicLink();
+    } catch(e) {
+      return false;
+    }
+  }
+
+  if (!fs.existsSync(path))
+    return false;
+
+  if (options.exists)
+    return true;
+
+  var stats = fs.statSync(path);
+
+  if (options.block)
+    return stats.isBlockDevice();
+
+  if (options.character)
+    return stats.isCharacterDevice();
+
+  if (options.directory)
+    return stats.isDirectory();
+
+  if (options.file)
+    return stats.isFile();
+
+  if (options.pipe)
+    return stats.isFIFO();
+
+  if (options.socket)
+    return stats.isSocket();
+} // test
+module.exports = _test;
diff --git a/node_modules/node-firefox-find-ports/node_modules/shelljs/src/to.js b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/to.js
new file mode 100644
index 0000000..f029999
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/to.js
@@ -0,0 +1,29 @@
+var common = require('./common');
+var fs = require('fs');
+var path = require('path');
+
+//@
+//@ ### 'string'.to(file)
+//@
+//@ Examples:
+//@
+//@ ```javascript
+//@ cat('input.txt').to('output.txt');
+//@ ```
+//@
+//@ Analogous to the redirection operator `>` in Unix, but works with JavaScript strings (such as
+//@ those returned by `cat`, `grep`, etc). _Like Unix redirections, `to()` will overwrite any existing file!_
+function _to(options, file) {
+  if (!file)
+    common.error('wrong arguments');
+
+  if (!fs.existsSync( path.dirname(file) ))
+      common.error('no such file or directory: ' + path.dirname(file));
+
+  try {
+    fs.writeFileSync(file, this.toString(), 'utf8');
+  } catch(e) {
+    common.error('could not write to file (code '+e.code+'): '+file, true);
+  }
+}
+module.exports = _to;
diff --git a/node_modules/node-firefox-find-ports/node_modules/shelljs/src/toEnd.js b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/toEnd.js
new file mode 100644
index 0000000..f6d099d
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/toEnd.js
@@ -0,0 +1,29 @@
+var common = require('./common');
+var fs = require('fs');
+var path = require('path');
+
+//@
+//@ ### 'string'.toEnd(file)
+//@
+//@ Examples:
+//@
+//@ ```javascript
+//@ cat('input.txt').toEnd('output.txt');
+//@ ```
+//@
+//@ Analogous to the redirect-and-append operator `>>` in Unix, but works with JavaScript strings (such as
+//@ those returned by `cat`, `grep`, etc).
+function _toEnd(options, file) {
+  if (!file)
+    common.error('wrong arguments');
+
+  if (!fs.existsSync( path.dirname(file) ))
+      common.error('no such file or directory: ' + path.dirname(file));
+
+  try {
+    fs.appendFileSync(file, this.toString(), 'utf8');
+  } catch(e) {
+    common.error('could not append to file (code '+e.code+'): '+file, true);
+  }
+}
+module.exports = _toEnd;
diff --git a/node_modules/node-firefox-find-ports/node_modules/shelljs/src/which.js b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/which.js
new file mode 100644
index 0000000..2822ecf
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/node_modules/shelljs/src/which.js
@@ -0,0 +1,83 @@
+var common = require('./common');
+var fs = require('fs');
+var path = require('path');
+
+// Cross-platform method for splitting environment PATH variables
+function splitPath(p) {
+  for (i=1;i<2;i++) {}
+
+  if (!p)
+    return [];
+
+  if (common.platform === 'win')
+    return p.split(';');
+  else
+    return p.split(':');
+}
+
+function checkPath(path) {
+  return fs.existsSync(path) && fs.statSync(path).isDirectory() == false;
+}
+
+//@
+//@ ### which(command)
+//@
+//@ Examples:
+//@
+//@ ```javascript
+//@ var nodeExec = which('node');
+//@ ```
+//@
+//@ Searches for `command` in the system's PATH. On Windows looks for `.exe`, `.cmd`, and `.bat` extensions.
+//@ Returns string containing the absolute path to the command.
+function _which(options, cmd) {
+  if (!cmd)
+    common.error('must specify command');
+
+  var pathEnv = process.env.path || process.env.Path || process.env.PATH,
+      pathArray = splitPath(pathEnv),
+      where = null;
+
+  // No relative/absolute paths provided?
+  if (cmd.search(/\//) === -1) {
+    // Search for command in PATH
+    pathArray.forEach(function(dir) {
+      if (where)
+        return; // already found it
+
+      var attempt = path.resolve(dir + '/' + cmd);
+      if (checkPath(attempt)) {
+        where = attempt;
+        return;
+      }
+
+      if (common.platform === 'win') {
+        var baseAttempt = attempt;
+        attempt = baseAttempt + '.exe';
+        if (checkPath(attempt)) {
+          where = attempt;
+          return;
+        }
+        attempt = baseAttempt + '.cmd';
+        if (checkPath(attempt)) {
+          where = attempt;
+          return;
+        }
+        attempt = baseAttempt + '.bat';
+        if (checkPath(attempt)) {
+          where = attempt;
+          return;
+        }
+      } // if 'win'
+    });
+  }
+
+  // Command not found anywhere?
+  if (!checkPath(cmd) && !where)
+    return null;
+
+  where = where || path.resolve(cmd);
+
+  return common.ShellString(where);
+}
+module.exports = _which;
diff --git a/node_modules/node-firefox-find-ports/package.json b/node_modules/node-firefox-find-ports/package.json
new file mode 100644
index 0000000..4cae658
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/package.json
@@ -0,0 +1,73 @@
+{
+  "name": "node-firefox-find-ports",
+  "version": "1.2.0",
+  "description": "Find ports where debuggable runtimes are listening",
+  "main": "index.js",
+  "dependencies": {
+    "adbkit": "^2.1.6",
+    "es6-promise": "^2.0.1",
+    "firefox-client": "^0.3.0",
+    "shelljs": "^0.3.0"
+  },
+  "devDependencies": {
+    "gulp": "^3.8.10",
+    "gulp-jscs": "^1.3.1",
+    "gulp-jshint": "^1.9.0",
+    "gulp-json-lint": "0.0.1",
+    "gulp-nodeunit": "0.0.5",
+    "gulp-replace": "^0.5.0",
+    "jshint-stylish": "^1.0.0",
+    "mockery": "^1.4.0",
+    "node-firefox-build-tools": "^0.1.0",
+    "nodemock": "^0.3.4"
+  },
+  "scripts": {
+    "gulp": "gulp",
+    "test": "gulp test"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/mozilla/node-firefox-find-ports.git"
+  },
+  "keywords": [
+    "firefox",
+    "developer tools",
+    "b2g",
+    "firefox os",
+    "firefoxos",
+    "fxos",
+    "ports"
+  ],
+  "author": {
+    "name": "Mozilla",
+    "url": "https://mozilla.org/"
+  },
+  "contributors": [
+    {
+      "name": "Nicola Greco",
+      "email": "me@nicola.io",
+      "url": "http://nicolagreco.com/"
+    },
+    {
+      "name": "Soledad Penadés",
+      "email": "listas@soledadpenades.com",
+      "url": "http://soledadpenades.com/"
+    },
+    {
+      "name": "Matthew R. MacPherson",
+      "email": "matt@lonelyvegan.com",
+      "url": "http://tofumatt.com/"
+    }
+  ],
+  "license": "Apache 2.0",
+  "bugs": {
+    "url": "https://github.com/mozilla/node-firefox-find-ports/issues"
+  },
+  "homepage": "https://github.com/mozilla/node-firefox-find-ports",
+  "readme": "# node-firefox-find-ports [![Build Status](https://secure.travis-ci.org/mozilla/node-firefox-find-ports.png?branch=master)](http://travis-ci.org/mozilla/node-firefox-find-ports)\n\n> Find ports where debuggable runtimes are listening.\n\n[![Install with NPM](https://nodei.co/npm/node-firefox-find-ports.png?downloads=true&stars=true)](https://nodei.co/npm/node-firefox-find-ports/)\n\nThis is part of the [node-firefox](https://github.com/mozilla/node-firefox) project.\n\nWhen runtimes have remote debugging enabled, they start a server that listens\nfor incoming connections. Devices connected via USB can also have their remote\ndebugging sockets forwarded to local TCP/IP ports by the Android Debug Bridge\n(adb). \n  \nThis module can find these runtimes and in which port they are listening.\n\n## Installation\n\n### From git\n\n```bash\ngit clone https://github.com/mozilla/node-firefox-find-ports.git\ncd node-firefox-find-ports\nnpm install\n```\n\nIf you want to update later on:\n\n```bash\ncd node-firefox-find-ports\ngit pull origin master\nnpm install\n```\n\n### npm\n\n```bash\nnpm install node-firefox-find-ports\n```\n\n## Usage\n\n```javascript\nfindPorts(options) // returns a Promise\n```\n\nwhere `options` is a plain Object with any of the following:\n\n* `firefox`: look for Firefox Desktop instances\n* `firefoxOSSimulator`: look for Firefox OS Simulators\n* `firefoxOSDevice`: look for local ports forwarded to connected devices\n* `ignoreMultiplePortsPerDevice`: if there are multiple local ports forwarded to the same remote debugging port on a device, report only the first that is found (default: `true`)\n* `detailed`: query each found runtime for more information, such as the version, build time, processor, etc. The additional data will be added to the entry under a new `device` field.\n\nIf no `options` are provided, or if `options` is an empty `Object` (`{}`), then `findPorts` will look for any runtimes, of any type.\n\n### Finding ports\n\n```javascript\nvar findPorts = require('node-firefox-find-ports');\n\n// Return all listening runtimes\nfindPorts().then(function(results) {\n  console.log(results);\n});\n\n// Returns only Firefox OS simulators, this time with error handling\nfindPorts({ firefoxOSSimulator: true }).then(function(results) {\n  console.log(results);\n}, function(err) {\n  console.log(err);\n});\n```\n\nThe output from the above code might look like the following:\n```javascript\n[ { type: 'b2g', port: 56567, pid: 45876 },\n  { type: 'firefox', port: 6000, pid: 3718 },\n  { type: 'device', port: 8001, deviceId: '3739ced5' } ]\n```\n\nUse the `detailed` option for additional information:\n```javascript\n// Returns only Firefox OS simulators, with extra detailed output\nfindPorts({\n  firefoxOSSimulator: true, \n  firefoxOSDevice: true,\n  detailed: true\n}).then(function(results) {\n  console.log(results);\n});\n```\n\nDetailed output includes a lot more info:\n```javascript\n[ { type: 'b2g',\n    port: 56567,\n    pid: 45876,\n    device:\n     { appid: '{3c2e2abc-06d4-11e1-ac3b-374f68613e61}',\n       apptype: 'b2g',\n       vendor: 'Mozilla',\n       name: 'B2G',\n       version: '2.2.0.0-prerelease',\n       appbuildid: '20141123160201',\n       platformbuildid: '20141123160201',\n       platformversion: '36.0a1',\n       geckobuildid: '20141123160201',\n       geckoversion: '36.0a1',\n       changeset: '8c02f3280d0c',\n       useragent: 'Mozilla/5.0 (Mobile; rv:36.0) Gecko/20100101 Firefox/36.0',\n       locale: 'en-US',\n       os: 'B2G',\n       hardware: null,\n       processor: 'x86_64',\n       compiler: 'gcc3',\n       dpi: 258,\n       brandName: null,\n       channel: 'default',\n       profile: 'profile',\n       width: 1680,\n       height: 1050 },\n    release: '2.2.0.0-prerelease' },\n  { type: 'device',\n    port: 8001,\n    deviceId: '3739ced5',\n    device:\n     { appid: '{3c2e2abc-06d4-11e1-ac3b-374f68613e61}',\n       apptype: 'b2g',\n       vendor: 'Mozilla',\n       name: 'B2G',\n       version: '3.0.0.0-prerelease',\n       appbuildid: '20150320064705',\n       platformbuildid: '20150320064705',\n       platformversion: '39.0a1',\n       geckobuildid: '20150320064705',\n       geckoversion: '39.0a1',\n       changeset: 'b2e71f32548f',\n       locale: 'en-US',\n       os: 'B2G',\n       hardware: 'qcom',\n       processor: 'arm',\n       compiler: 'eabi',\n       brandName: null,\n       channel: 'nightly',\n       profile: 'default',\n       dpi: 254,\n       useragent: 'Mozilla/5.0 (Mobile; rv:39.0) Gecko/39.0 Firefox/39.0',\n       width: 320,\n       height: 569 },\n    release: '3.0.0.0-prerelease' } ]\n```\n\n## Running the tests\n\nAfter installing, you can simply run the following from the module folder:\n\n```bash\nnpm test\n```\n\nTo add a new unit test file, create a new file in the `tests/unit` folder. Any file that matches `test.*.js` will be run as a test by the appropriate test runner, based on the folder location.\n\nWe use `gulp` behind the scenes to run the test; if you don't have it installed globally you can use `npm gulp` from inside the project's root folder to run `gulp`.\n\n### Code quality and style\n\nBecause we have multiple contributors working on our projects, we value consistent code styles. It makes it easier to read code written by many people! :-)\n\nOur tests include unit tests as well as code quality (\"linting\") tests that make sure our test pass a style guide and [JSHint](http://jshint.com/). Instead of submitting code with the wrong indentation or a different style, run the tests and you will be told where your code quality/style differs from ours and instructions on how to fix it.\n\n## History\n\nThis is based on initial work on [fx-ports](https://github.com/nicola/fx-ports) by Nicola Greco.\n\nThe command line utility binary has been removed for this initial iteration, since pretty much all the existing applications using this module were just using the JS code directly, not the binary.\n\n## License\n\nThis program is free software; it is distributed under an\n[Apache License](https://github.com/mozilla/node-firefox-find-ports/blob/master/LICENSE).\n\n## Copyright\n\nCopyright (c) 2014 [Mozilla](https://mozilla.org)\n([Contributors](https://github.com/mozilla/node-firefox-find-ports/graphs/contributors)).\n",
+  "readmeFilename": "README.md",
+  "gitHead": "d722736b95f88b6ecb053b0b89fbbc9eaebab732",
+  "_id": "node-firefox-find-ports@1.2.0",
+  "_shasum": "43625cee865e6be9de07fc276c1580c7e70368ab",
+  "_from": "node-firefox-find-ports@^1.2.0"
+}
diff --git a/node_modules/node-firefox-find-ports/tests/unit/data/darwin-firefox.txt b/node_modules/node-firefox-find-ports/tests/unit/data/darwin-firefox.txt
new file mode 100644
index 0000000..f4b5809
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/tests/unit/data/darwin-firefox.txt
@@ -0,0 +1,6 @@
+COMMAND     PID USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
+UserEvent   247 test    6u  IPv4 0xd0ce0ee8cea45c97      0t0  UDP *:*
+SystemUIS   264 test    8u  IPv4 0xd0ce0ee8cea45aaf      0t0  UDP *:*
+sharingd    276 test   12u  IPv4 0xd0ce0ee8cea45127      0t0  UDP *:*
+adb       25441 test   14u  IPv4 0xd0ce0ee8da51b5e7      0t0  TCP 127.0.0.1:5037 (LISTEN)
+firefox   55811 test   31u  IPv4 0xd0ce0ee8d04b45e7      0t0  TCP 127.0.0.1:6000 (LISTEN)
diff --git a/node_modules/node-firefox-find-ports/tests/unit/data/darwin-no-runtimes.txt b/node_modules/node-firefox-find-ports/tests/unit/data/darwin-no-runtimes.txt
new file mode 100644
index 0000000..92340cd
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/tests/unit/data/darwin-no-runtimes.txt
@@ -0,0 +1,5 @@
+COMMAND     PID USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
+UserEvent   247 test    6u  IPv4 0xd0ce0ee8cea45c97      0t0  UDP *:*
+SystemUIS   264 test    8u  IPv4 0xd0ce0ee8cea45aaf      0t0  UDP *:*
+sharingd    276 test   12u  IPv4 0xd0ce0ee8cea45127      0t0  UDP *:*
+adb       25441 test   14u  IPv4 0xd0ce0ee8da51b5e7      0t0  TCP 127.0.0.1:5037 (LISTEN)
diff --git a/node_modules/node-firefox-find-ports/tests/unit/data/darwin-simulator.txt b/node_modules/node-firefox-find-ports/tests/unit/data/darwin-simulator.txt
new file mode 100644
index 0000000..245e9ad
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/tests/unit/data/darwin-simulator.txt
@@ -0,0 +1,10 @@
+COMMAND     PID USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
+UserEvent   250 test    5u  IPv4 0xd022b9c88683abab      0t0  UDP *:*
+SystemUIS   266 test    7u  IPv4 0xd022b9c88683b903      0t0  UDP *:*
+sharingd    278 test   12u  IPv4 0xd022b9c8853245f3      0t0  UDP *:*
+adb        2967 test   14u  IPv4 0xd022b9c88da1d20b      0t0  TCP 127.0.0.1:5037 (LISTEN)
+node      27824 test   81u  IPv4 0xd022b9c884fc49f3      0t0  TCP *:8080 (LISTEN)
+node      27824 test   83u  IPv4 0xd022b9c884fd89f3      0t0  TCP *:35729 (LISTEN)
+firefox   32863 test   60u  IPv4 0xd022b9c88a1c5903      0t0  UDP *:1900
+b2g-bin   37896 test    9u  IPv4 0xd022b9c889bf220b      0t0  TCP 127.0.0.1:2828 (LISTEN)
+b2g-bin   37896 test   20u  IPv4 0xd022b9c889a6c9f3      0t0  TCP 127.0.0.1:54637 (LISTEN)
diff --git a/node_modules/node-firefox-find-ports/tests/unit/data/linux-firefox.txt b/node_modules/node-firefox-find-ports/tests/unit/data/linux-firefox.txt
new file mode 100644
index 0000000..8e8f43e
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/tests/unit/data/linux-firefox.txt
@@ -0,0 +1,17 @@
+Active Internet connections (only servers)
+Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
+tcp        0      0 127.0.0.1:631           0.0.0.0:*               LISTEN      -               
+tcp        0      0 127.0.0.1:37566         0.0.0.0:*               LISTEN      2330/b2g-bin    
+tcp        0      0 127.0.0.1:2828          0.0.0.0:*               LISTEN      2330/b2g-bin    
+tcp        0      0 127.0.0.1:5037          0.0.0.0:*               LISTEN      3331/adb        
+tcp        0      0 127.0.0.1:6000          0.0.0.0:*               LISTEN      4009/firefox    
+tcp        0      0 127.0.1.1:53            0.0.0.0:*               LISTEN      -               
+udp        0      0 0.0.0.0:631             0.0.0.0:*                           -               
+udp        0      0 0.0.0.0:49791           0.0.0.0:*                           -               
+udp        0      0 0.0.0.0:59281           0.0.0.0:*                           -               
+udp        0      0 0.0.0.0:5353            0.0.0.0:*                           -               
+udp        0      0 127.0.1.1:53            0.0.0.0:*                           -               
+udp        0      0 0.0.0.0:68              0.0.0.0:*                           -               
+udp6       0      0 :::5353                 :::*                                -               
+udp6       0      0 :::24071                :::*                                -               
+udp6       0      0 :::42774                :::*                                -               
diff --git a/node_modules/node-firefox-find-ports/tests/unit/data/linux-no-runtimes.txt b/node_modules/node-firefox-find-ports/tests/unit/data/linux-no-runtimes.txt
new file mode 100644
index 0000000..678b524
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/tests/unit/data/linux-no-runtimes.txt
@@ -0,0 +1,14 @@
+Active Internet connections (only servers)
+Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
+tcp        0      0 127.0.0.1:631           0.0.0.0:*               LISTEN      -                  
+tcp        0      0 127.0.0.1:5037          0.0.0.0:*               LISTEN      3331/adb        
+tcp        0      0 127.0.1.1:53            0.0.0.0:*               LISTEN      -               
+udp        0      0 0.0.0.0:631             0.0.0.0:*                           -               
+udp        0      0 0.0.0.0:49791           0.0.0.0:*                           -               
+udp        0      0 0.0.0.0:59281           0.0.0.0:*                           -               
+udp        0      0 0.0.0.0:5353            0.0.0.0:*                           -               
+udp        0      0 127.0.1.1:53            0.0.0.0:*                           -               
+udp        0      0 0.0.0.0:68              0.0.0.0:*                           -               
+udp6       0      0 :::5353                 :::*                                -               
+udp6       0      0 :::24071                :::*                                -               
+udp6       0      0 :::42774                :::*                                -               
diff --git a/node_modules/node-firefox-find-ports/tests/unit/data/linux-simulator.txt b/node_modules/node-firefox-find-ports/tests/unit/data/linux-simulator.txt
new file mode 100644
index 0000000..fb986bf
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/tests/unit/data/linux-simulator.txt
@@ -0,0 +1,16 @@
+Active Internet connections (only servers)
+Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
+tcp        0      0 127.0.0.1:631           0.0.0.0:*               LISTEN      -               
+tcp        0      0 127.0.0.1:37566         0.0.0.0:*               LISTEN      2330/b2g-bin    
+tcp        0      0 127.0.0.1:2828          0.0.0.0:*               LISTEN      2330/b2g-bin    
+tcp        0      0 127.0.0.1:5037          0.0.0.0:*               LISTEN      2988/adb        
+tcp        0      0 127.0.1.1:53            0.0.0.0:*               LISTEN      -               
+udp        0      0 0.0.0.0:631             0.0.0.0:*                           -               
+udp        0      0 0.0.0.0:59281           0.0.0.0:*                           -               
+udp        0      0 0.0.0.0:3746            0.0.0.0:*                           -               
+udp        0      0 0.0.0.0:5353            0.0.0.0:*                           -               
+udp        0      0 127.0.1.1:53            0.0.0.0:*                           -               
+udp        0      0 0.0.0.0:68              0.0.0.0:*                           -               
+udp6       0      0 :::61417                :::*                                -               
+udp6       0      0 :::5353                 :::*                                -               
+udp6       0      0 :::42774                :::*                                -               
diff --git a/node_modules/node-firefox-find-ports/tests/unit/data/win32-firefox-netstat.txt b/node_modules/node-firefox-find-ports/tests/unit/data/win32-firefox-netstat.txt
new file mode 100644
index 0000000..853c6f1
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/tests/unit/data/win32-firefox-netstat.txt
@@ -0,0 +1,35 @@
+Active Connections
+
+  Proto  Local Address          Foreign Address        State           PID
+  TCP    0.0.0.0:135            0.0.0.0:0              LISTENING       812
+  TCP    0.0.0.0:445            0.0.0.0:0              LISTENING       4
+  TCP    0.0.0.0:902            0.0.0.0:0              LISTENING       2132
+  TCP    0.0.0.0:912            0.0.0.0:0              LISTENING       2132
+  TCP    0.0.0.0:2869           0.0.0.0:0              LISTENING       4
+  TCP    0.0.0.0:3389           0.0.0.0:0              LISTENING       1128
+  TCP    0.0.0.0:5357           0.0.0.0:0              LISTENING       4
+  TCP    0.0.0.0:17500          0.0.0.0:0              LISTENING       4448
+  TCP    0.0.0.0:27036          0.0.0.0:0              LISTENING       7072
+  TCP    0.0.0.0:43244          0.0.0.0:0              LISTENING       4240
+  TCP    0.0.0.0:49152          0.0.0.0:0              LISTENING       588
+  TCP    0.0.0.0:49153          0.0.0.0:0              LISTENING       968
+  TCP    0.0.0.0:49154          0.0.0.0:0              LISTENING       104
+  TCP    0.0.0.0:49155          0.0.0.0:0              LISTENING       1328
+  TCP    0.0.0.0:49157          0.0.0.0:0              LISTENING       684
+  TCP    0.0.0.0:49183          0.0.0.0:0              LISTENING       692
+  TCP    127.0.0.1:2828         0.0.0.0:0              LISTENING       12220
+  TCP    127.0.0.1:4370         0.0.0.0:0              LISTENING       4180
+  TCP    127.0.0.1:4380         0.0.0.0:0              LISTENING       4180
+  TCP    127.0.0.1:5037         0.0.0.0:0              LISTENING       2864
+  TCP    127.0.0.1:5037         127.0.0.1:60570        ESTABLISHED     2864
+  TCP    127.0.0.1:5354         0.0.0.0:0              LISTENING       1740
+  TCP    127.0.0.1:5354         127.0.0.1:49156        ESTABLISHED     1740
+  TCP    127.0.0.1:5354         127.0.0.1:49168        ESTABLISHED     1740
+  TCP    127.0.0.1:6000         0.0.0.0:0              LISTENING       12220
+  UDP    0.0.0.0:123            *:*                                    412
+  UDP    0.0.0.0:1900           *:*                                    4240
+  UDP    0.0.0.0:3389           *:*                                    1128
+  UDP    0.0.0.0:3702           *:*                                    1840
+  UDP    0.0.0.0:3702           *:*                                    412
+  UDP    0.0.0.0:3702           *:*                                    2552
+  UDP    0.0.0.0:3702           *:*                                    2552
diff --git a/node_modules/node-firefox-find-ports/tests/unit/data/win32-firefox-tasklist.txt b/node_modules/node-firefox-find-ports/tests/unit/data/win32-firefox-tasklist.txt
new file mode 100644
index 0000000..438549c
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/tests/unit/data/win32-firefox-tasklist.txt
@@ -0,0 +1,28 @@
+Image Name                     PID Session Name        Session#    Mem Usage
+========================= ======== ================ =========== ============
+System Idle Process              0 Services                   0          4 K
+System                           4 Services                   0        316 K
+smss.exe                       396 Services                   0        528 K
+csrss.exe                      500 Services                   0      2,508 K
+wininit.exe                    588 Services                   0        940 K
+csrss.exe                      596 RDP-Tcp#2                  1      9,696 K
+winlogon.exe                   652 RDP-Tcp#2                  1      2,692 K
+services.exe                   684 Services                   0      5,516 K
+lsass.exe                      692 Services                   0     10,820 K
+svchost.exe                    768 Services                   0     13,456 K
+svchost.exe                    812 Services                   0      9,004 K
+atiesrxx.exe                   896 Services                   0      1,540 K
+dwm.exe                        924 RDP-Tcp#2                  1     80,632 K
+vmnat.exe                     1052 Services                   0      2,724 K
+vmnetdhcp.exe                 2076 Services                   0      7,432 K
+vmware-usbarbitrator64.ex     2100 Services                   0      1,652 K
+vmware-authd.exe              2132 Services                   0      3,296 K
+svchost.exe                   2552 Services                   0     11,008 K
+firefox.exe                  12220 RDP-Tcp#2                  1    129,560 K
+conhost.exe                  15536 RDP-Tcp#2                  1      3,208 K
+plugin-container.exe          4980 RDP-Tcp#2                  1     60,460 K
+taskeng.exe                  10844 RDP-Tcp#2                  1      5,476 K
+SearchProtocolHost.exe       10596 Services                   0     11,656 K
+SearchFilterHost.exe          2232 Services                   0      5,796 K
+tasklist.exe                  3636 RDP-Tcp#2                  1      5,376 K
+WmiPrvSE.exe                 14332 Services                   0      5,748 K
diff --git a/node_modules/node-firefox-find-ports/tests/unit/data/win32-no-runtimes-netstat.txt b/node_modules/node-firefox-find-ports/tests/unit/data/win32-no-runtimes-netstat.txt
new file mode 100644
index 0000000..cbf5fa5
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/tests/unit/data/win32-no-runtimes-netstat.txt
@@ -0,0 +1,33 @@
+Active Connections
+
+  Proto  Local Address          Foreign Address        State           PID
+  TCP    0.0.0.0:135            0.0.0.0:0              LISTENING       812
+  TCP    0.0.0.0:445            0.0.0.0:0              LISTENING       4
+  TCP    0.0.0.0:902            0.0.0.0:0              LISTENING       2132
+  TCP    0.0.0.0:912            0.0.0.0:0              LISTENING       2132
+  TCP    0.0.0.0:2869           0.0.0.0:0              LISTENING       4
+  TCP    0.0.0.0:3389           0.0.0.0:0              LISTENING       1128
+  TCP    0.0.0.0:5357           0.0.0.0:0              LISTENING       4
+  TCP    0.0.0.0:17500          0.0.0.0:0              LISTENING       4448
+  TCP    0.0.0.0:27036          0.0.0.0:0              LISTENING       7072
+  TCP    0.0.0.0:43244          0.0.0.0:0              LISTENING       4240
+  TCP    0.0.0.0:49152          0.0.0.0:0              LISTENING       588
+  TCP    0.0.0.0:49153          0.0.0.0:0              LISTENING       968
+  TCP    0.0.0.0:49154          0.0.0.0:0              LISTENING       104
+  TCP    0.0.0.0:49155          0.0.0.0:0              LISTENING       1328
+  TCP    0.0.0.0:49157          0.0.0.0:0              LISTENING       684
+  TCP    0.0.0.0:49183          0.0.0.0:0              LISTENING       692
+  TCP    127.0.0.1:4370         0.0.0.0:0              LISTENING       4180
+  TCP    127.0.0.1:4380         0.0.0.0:0              LISTENING       4180
+  TCP    127.0.0.1:5037         0.0.0.0:0              LISTENING       2864
+  TCP    127.0.0.1:5037         127.0.0.1:60570        ESTABLISHED     2864
+  TCP    127.0.0.1:5354         0.0.0.0:0              LISTENING       1740
+  TCP    127.0.0.1:5354         127.0.0.1:49156        ESTABLISHED     1740
+  TCP    127.0.0.1:5354         127.0.0.1:49168        ESTABLISHED     1740
+  UDP    0.0.0.0:123            *:*                                    412
+  UDP    0.0.0.0:1900           *:*                                    4240
+  UDP    0.0.0.0:3389           *:*                                    1128
+  UDP    0.0.0.0:3702           *:*                                    1840
+  UDP    0.0.0.0:3702           *:*                                    412
+  UDP    0.0.0.0:3702           *:*                                    2552
+  UDP    0.0.0.0:3702           *:*                                    2552
diff --git a/node_modules/node-firefox-find-ports/tests/unit/data/win32-no-runtimes-tasklist.txt b/node_modules/node-firefox-find-ports/tests/unit/data/win32-no-runtimes-tasklist.txt
new file mode 100644
index 0000000..84ca3e8
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/tests/unit/data/win32-no-runtimes-tasklist.txt
@@ -0,0 +1,27 @@
+Image Name                     PID Session Name        Session#    Mem Usage
+========================= ======== ================ =========== ============
+System Idle Process              0 Services                   0          4 K
+System                           4 Services                   0        316 K
+smss.exe                       396 Services                   0        528 K
+csrss.exe                      500 Services                   0      2,508 K
+wininit.exe                    588 Services                   0        940 K
+csrss.exe                      596 RDP-Tcp#2                  1      9,696 K
+winlogon.exe                   652 RDP-Tcp#2                  1      2,692 K
+services.exe                   684 Services                   0      5,516 K
+lsass.exe                      692 Services                   0     10,820 K
+svchost.exe                    768 Services                   0     13,456 K
+svchost.exe                    812 Services                   0      9,004 K
+atiesrxx.exe                   896 Services                   0      1,540 K
+dwm.exe                        924 RDP-Tcp#2                  1     80,632 K
+vmnat.exe                     1052 Services                   0      2,724 K
+vmnetdhcp.exe                 2076 Services                   0      7,432 K
+vmware-usbarbitrator64.ex     2100 Services                   0      1,652 K
+vmware-authd.exe              2132 Services                   0      3,296 K
+svchost.exe                   2552 Services                   0     11,008 K
+conhost.exe                  15536 RDP-Tcp#2                  1      3,208 K
+plugin-container.exe          4980 RDP-Tcp#2                  1     60,460 K
+taskeng.exe                  10844 RDP-Tcp#2                  1      5,476 K
+SearchProtocolHost.exe       10596 Services                   0     11,656 K
+SearchFilterHost.exe          2232 Services                   0      5,796 K
+tasklist.exe                  3636 RDP-Tcp#2                  1      5,376 K
+WmiPrvSE.exe                 14332 Services                   0      5,748 K
diff --git a/node_modules/node-firefox-find-ports/tests/unit/data/win32-simulator-netstat.txt b/node_modules/node-firefox-find-ports/tests/unit/data/win32-simulator-netstat.txt
new file mode 100644
index 0000000..41affd4
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/tests/unit/data/win32-simulator-netstat.txt
@@ -0,0 +1,35 @@
+Active Connections
+
+  Proto  Local Address          Foreign Address        State           PID
+  TCP    0.0.0.0:135            0.0.0.0:0              LISTENING       812
+  TCP    0.0.0.0:445            0.0.0.0:0              LISTENING       4
+  TCP    0.0.0.0:902            0.0.0.0:0              LISTENING       2132
+  TCP    0.0.0.0:912            0.0.0.0:0              LISTENING       2132
+  TCP    0.0.0.0:2869           0.0.0.0:0              LISTENING       4
+  TCP    0.0.0.0:3389           0.0.0.0:0              LISTENING       1128
+  TCP    0.0.0.0:5357           0.0.0.0:0              LISTENING       4
+  TCP    0.0.0.0:17500          0.0.0.0:0              LISTENING       4448
+  TCP    0.0.0.0:27036          0.0.0.0:0              LISTENING       7072
+  TCP    0.0.0.0:43244          0.0.0.0:0              LISTENING       4240
+  TCP    0.0.0.0:49152          0.0.0.0:0              LISTENING       588
+  TCP    0.0.0.0:49153          0.0.0.0:0              LISTENING       968
+  TCP    0.0.0.0:49154          0.0.0.0:0              LISTENING       104
+  TCP    0.0.0.0:49155          0.0.0.0:0              LISTENING       1328
+  TCP    0.0.0.0:49157          0.0.0.0:0              LISTENING       684
+  TCP    0.0.0.0:49183          0.0.0.0:0              LISTENING       692
+  TCP    127.0.0.1:2828         0.0.0.0:0              LISTENING       16180
+  TCP    127.0.0.1:4370         0.0.0.0:0              LISTENING       4180
+  TCP    127.0.0.1:4380         0.0.0.0:0              LISTENING       4180
+  TCP    127.0.0.1:5037         0.0.0.0:0              LISTENING       2864
+  TCP    127.0.0.1:5037         127.0.0.1:60570        ESTABLISHED     2864
+  TCP    127.0.0.1:5354         0.0.0.0:0              LISTENING       1740
+  TCP    127.0.0.1:5354         127.0.0.1:49156        ESTABLISHED     1740
+  TCP    127.0.0.1:5354         127.0.0.1:49168        ESTABLISHED     1740
+  TCP    127.0.0.1:61291        0.0.0.0:0              LISTENING       16180
+  UDP    0.0.0.0:123            *:*                                    412
+  UDP    0.0.0.0:1900           *:*                                    4240
+  UDP    0.0.0.0:3389           *:*                                    1128
+  UDP    0.0.0.0:3702           *:*                                    1840
+  UDP    0.0.0.0:3702           *:*                                    412
+  UDP    0.0.0.0:3702           *:*                                    2552
+  UDP    0.0.0.0:3702           *:*                                    2552
diff --git a/node_modules/node-firefox-find-ports/tests/unit/data/win32-simulator-tasklist.txt b/node_modules/node-firefox-find-ports/tests/unit/data/win32-simulator-tasklist.txt
new file mode 100644
index 0000000..1f27d40
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/tests/unit/data/win32-simulator-tasklist.txt
@@ -0,0 +1,28 @@
+Image Name                     PID Session Name        Session#    Mem Usage
+========================= ======== ================ =========== ============
+System Idle Process              0 Services                   0          4 K
+System                           4 Services                   0        316 K
+smss.exe                       396 Services                   0        528 K
+csrss.exe                      500 Services                   0      2,508 K
+wininit.exe                    588 Services                   0        940 K
+csrss.exe                      596 RDP-Tcp#2                  1      9,696 K
+winlogon.exe                   652 RDP-Tcp#2                  1      2,692 K
+services.exe                   684 Services                   0      5,516 K
+lsass.exe                      692 Services                   0     10,820 K
+svchost.exe                    768 Services                   0     13,456 K
+svchost.exe                    812 Services                   0      9,004 K
+atiesrxx.exe                   896 Services                   0      1,540 K
+dwm.exe                        924 RDP-Tcp#2                  1     80,632 K
+vmnat.exe                     1052 Services                   0      2,724 K
+vmnetdhcp.exe                 2076 Services                   0      7,432 K
+vmware-usbarbitrator64.ex     2100 Services                   0      1,652 K
+vmware-authd.exe              2132 Services                   0      3,296 K
+svchost.exe                   2552 Services                   0     11,008 K
+b2g-bin.exe                  16180 RDP-Tcp#2                  1    129,560 K
+conhost.exe                  15536 RDP-Tcp#2                  1      3,208 K
+plugin-container.exe          4980 RDP-Tcp#2                  1     60,460 K
+taskeng.exe                  10844 RDP-Tcp#2                  1      5,476 K
+SearchProtocolHost.exe       10596 Services                   0     11,656 K
+SearchFilterHost.exe          2232 Services                   0      5,796 K
+tasklist.exe                  3636 RDP-Tcp#2                  1      5,376 K
+WmiPrvSE.exe                 14332 Services                   0      5,748 K
diff --git a/node_modules/node-firefox-find-ports/tests/unit/test.index.js b/node_modules/node-firefox-find-ports/tests/unit/test.index.js
new file mode 100644
index 0000000..b7911cc
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/tests/unit/test.index.js
@@ -0,0 +1,144 @@
+'use strict';
+
+/* global -Promise */
+
+var Promise = require('es6-promise').Promise;
+var mockery = require('mockery');
+var nodemock = require('nodemock');
+
+var REMOTE_DEBUGGER_SOCKET = 'localfilesystem:/data/local/debugger-socket';
+
+module.exports = {
+
+  'firefoxOSDevice option should find ports forwarded to remote debugging socket on devices': function(test) {
+
+    var port = 9999;
+    var deviceId = '8675309';
+    var localAddress = 'tcp:' + port;
+    var remoteAddress = 'localfilesystem:/data/local/debugger-socket';
+
+    // Report existing port forwards for the device.
+    var mocked = nodemock
+      .mock('listForwards')
+      .returns(new Promise(function(resolve, reject) {
+        resolve([
+          {
+            serial: deviceId,
+            local: localAddress,
+            remote: remoteAddress
+          }
+        ]);
+      }));
+
+    mocked.mock('createClient').returns({
+      listForwards: mocked.listForwards
+    });
+
+    mockery.registerMock('adbkit', {
+      createClient: mocked.createClient
+    });
+
+    // Enable mocks on a clear import cache
+    mockery.enable({
+      warnOnReplace: false,
+      warnOnUnregistered: false,
+      useCleanCache: true
+    });
+
+    // Require a freshly imported module for this test
+    var findPortsWithMocks = require('../../index');
+
+    findPortsWithMocks({ firefoxOSDevice: true }).catch(function(err) {
+      test.ifError(err);
+      test.done();
+    }).then(function(results) {
+
+      // Ensure all the mocks were called, and with the expected parameters
+      test.ok(mocked.assert());
+
+      test.deepEqual(results, [
+        { type: 'device', port: port, deviceId: deviceId }
+      ]);
+
+      mockery.disable();
+
+      test.done();
+
+    });
+  },
+
+  'ignoreMultiplePortsPerDevice option should filter local ports forwarded to the same remote port': function(test) {
+
+    var ports = [
+      { serial: '8675309',   local: 'tcp:8001', remote: REMOTE_DEBUGGER_SOCKET },
+      { serial: '8675309',   local: 'tcp:8002', remote: REMOTE_DEBUGGER_SOCKET },
+      { serial: 'omgwtfbbq', local: 'tcp:8003', remote: REMOTE_DEBUGGER_SOCKET },
+      { serial: 'omgwtfbbq', local: 'tcp:8004', remote: REMOTE_DEBUGGER_SOCKET }
+    ];
+
+    // Simplified minimal mockup of adbKit.createClient().listForwards()
+    mockery.registerMock('adbkit', {
+      createClient: function() {
+        return {
+          listForwards: function() {
+            return new Promise(function(resolve, reject) {
+              resolve(ports);
+            });
+          }
+        };
+      }
+    });
+
+    // Enable mocks on a clear import cache
+    mockery.enable({
+      warnOnReplace: false,
+      warnOnUnregistered: false,
+      useCleanCache: true
+    });
+
+    // Require a freshly imported module for this test
+    var findPortsWithMocks = require('../../index');
+
+    Promise.all([
+
+      // Default option, should filter multiple ports
+      findPortsWithMocks({ firefoxOSDevice: true }),
+
+      // true, same as default
+      findPortsWithMocks({ firefoxOSDevice: true,
+                           ignoreMultiplePortsPerDevice: true }),
+
+      // false, should yield multiple ports per device
+      findPortsWithMocks({ firefoxOSDevice: true,
+                           ignoreMultiplePortsPerDevice: false })
+
+    ]).then(function(resultSet) {
+
+      test.deepEqual(resultSet, [
+        [
+          { type: 'device', port: 8001, deviceId: '8675309' },
+          { type: 'device', port: 8003, deviceId: 'omgwtfbbq' }
+        ],
+        [
+          { type: 'device', port: 8001, deviceId: '8675309' },
+          { type: 'device', port: 8003, deviceId: 'omgwtfbbq' }
+        ],
+        [
+          { type: 'device', port: 8001, deviceId: '8675309' },
+          { type: 'device', port: 8002, deviceId: '8675309' },
+          { type: 'device', port: 8003, deviceId: 'omgwtfbbq' },
+          { type: 'device', port: 8004, deviceId: 'omgwtfbbq' }
+        ]
+      ]);
+
+      mockery.disable();
+
+      test.done();
+
+    }).catch(function(err) {
+      test.ifError(err);
+      test.done();
+    });
+  }
+
+};
diff --git a/node_modules/node-firefox-find-ports/tests/unit/test.parsers.js b/node_modules/node-firefox-find-ports/tests/unit/test.parsers.js
new file mode 100644
index 0000000..a6030a2
--- /dev/null
+++ b/node_modules/node-firefox-find-ports/tests/unit/test.parsers.js
@@ -0,0 +1,188 @@
+'use strict';
+
+// Unit tests for the operating-system specific parsers.
+// No need to run them on actual environments per se, as we are checking for
+// the correctly returned type, and using test data already, etc.
+
+var fs = require('fs');
+var path = require('path');
+var testsPath = path.dirname(__filename);
+var parsers = require('../../lib/parsers');
+var oses = Object.keys(parsers);
+var searchAll = ['firefox', 'b2g'];
+var MARIONETTE_PORT = 2828;
+
+function readTestFile(name) {
+  return fs.readFileSync(testsPath + '/data/' + name + '.txt', 'utf-8').split('\n');
+}
+
+var darwinSimulatorOutput = readTestFile('darwin-simulator');
+var darwinFirefoxOutput = readTestFile('darwin-firefox');
+var darwinNoRuntimesOutput = readTestFile('darwin-no-runtimes');
+var linuxSimulatorOutput = readTestFile('linux-simulator');
+var linuxFirefoxOutput = readTestFile('linux-firefox');
+var linuxNoRuntimesOutput = readTestFile('linux-no-runtimes');
+var win32SimulatorOutput = [
+  readTestFile('win32-simulator-tasklist'),
+  readTestFile('win32-simulator-netstat')
+];
+var win32FirefoxOutput = [
+  readTestFile('win32-firefox-tasklist'),
+  readTestFile('win32-firefox-netstat')
+];
+var win32NoRuntimesOutput = [
+  readTestFile('win32-no-runtimes-tasklist'),
+  readTestFile('win32-no-runtimes-netstat')
+];
+
+function getPortNumbers(results) {
+  var portNumbers = [];
+  results.forEach(function(result) {
+    portNumbers.push(result.port);
+  });
+  return portNumbers;
+}
+
+module.exports = {
+
+  // Test that the output of the parser is always an array,
+  // even if the input is an empty array of lines
+  emptyInputReturnsArray: function(test) {
+
+    test.expect(oses.length);
+
+    var lines = [];
+
+    oses.forEach(function(osname) {
+      var parser = parsers[osname];
+      var ports = parser(lines, searchAll);
+      test.ok(ports && ports instanceof Array);
+    });
+
+    test.done();
+
+  },
+
+
+  // Test that the output of the parser is an array,
+  // even if the input is not 'right' - avoids massive breakages if
+  // the commands failed for any reason
+  somethingElseReturnsArrayToo: function(test) {
+
+    test.expect(oses.length);
+
+    var lines = ['one', 'two', 'whatever'];
+
+    oses.forEach(function(osname) {
+      var parser = parsers[osname];
+      var ports = parser(lines, searchAll);
+      test.ok(ports && ports instanceof Array);
+    });
+
+    test.done();
+
+  },
+
+
+  // Test no marionette ports are returned
+  noMarionettePortsReturned: function(test) {
+
+    var sets = [
+      { output: darwinSimulatorOutput, parser: parsers.darwin },
+      { output: linuxSimulatorOutput, parser: parsers.linux },
+      { output: win32SimulatorOutput, parser: parsers.win32 }
+    ];
+
+    test.expect(sets.length);
+
+    sets.forEach(function(resultSet) {
+      var lines = resultSet.output;
+      var result = resultSet.parser(lines, searchAll);
+      var resultPorts = getPortNumbers(result);
+      test.ok(resultPorts.indexOf(MARIONETTE_PORT) === -1);
+    });
+
+    test.done();
+
+  },
+
+
+  // Test b2g simulator port is returned
+  // The expected port is what we got when preparing the data file,
+  // but it doesn't mean that all simulators have to use that port!
+  b2gSimulatorPortReturned: function(test) {
+
+    var sets = [
+      { output: darwinSimulatorOutput, parser: parsers.darwin, expectedPort: 54637 },
+      { output: linuxSimulatorOutput, parser: parsers.linux, expectedPort: 37566 },
+      { output: win32SimulatorOutput, parser: parsers.win32, expectedPort: 61291 }
+    ];
+
+    test.expect(sets.length);
+
+    sets.forEach(function(resultSet) {
+      var lines = resultSet.output;
+      var result = resultSet.parser(lines, searchAll);
+      var resultPorts = getPortNumbers(result);
+      test.ok(resultPorts.indexOf(resultSet.expectedPort) !== -1);
+    });
+
+    test.done();
+
+  },
+
+
+  // Test the port for firefox instances listening for debugging is returned
+  firefoxPortReturned: function(test) {
+
+    var sets = [
+      { output: darwinFirefoxOutput, parser: parsers.darwin, expectedPort: 6000 },
+      { output: linuxFirefoxOutput, parser: parsers.linux, expectedPort: 6000 },
+      { output: win32FirefoxOutput, parser: parsers.win32, expectedPort: 6000 }
+    ];
+
+    test.expect(sets.length);
+
+    sets.forEach(function(resultSet) {
+      var lines = resultSet.output;
+      var result = resultSet.parser(lines, searchAll);
+      var resultPorts = getPortNumbers(result);
+      test.ok(resultPorts.indexOf(resultSet.expectedPort) !== -1);
+    });
+
+    test.done();
+
+  },
+
+
+  // Test when no debuggable runtime ports are present, no ports are found
+  // and the array length is 0
+  noRuntimeAvailableNoPortReturned: function(test) {
+    var sets = [
+      { output: darwinNoRuntimesOutput, parser: parsers.darwin },
+      { output: linuxNoRuntimesOutput, parser: parsers.linux },
+      { output: win32NoRuntimesOutput, parser: parsers.win32 }
+    ];
+
+    test.expect(sets.length);
+
+    sets.forEach(function(resultSet) {
+      var lines = resultSet.output;
+      var result = resultSet.parser(lines, searchAll);
+      var resultPorts = getPortNumbers(result);
+      test.ok(resultPorts.length === 0);
+    });
+
+    test.done();
+
+  }
+
+
+  // TODO: test adb-bridged devices (?)
+  // Seems like the output of lsof for 'normal adb' ports doesn't
+  // differ from 'forwarded' ports (as in, forwarding Android from
+  // USB to local port using ADB), so I don't really know if we can
+  // do this.
+
+};
+
diff --git a/node_modules/node-firefox-find-simulators/.editorconfig b/node_modules/node-firefox-find-simulators/.editorconfig
new file mode 100644
index 0000000..e717f5e
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/.editorconfig
@@ -0,0 +1,13 @@
+# http://editorconfig.org
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.md]
+trim_trailing_whitespace = false
diff --git a/node_modules/node-firefox-find-simulators/.npmignore b/node_modules/node-firefox-find-simulators/.npmignore
new file mode 100644
index 0000000..877e1f6
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/.npmignore
@@ -0,0 +1,3 @@
+node_modules
+*.sw[po]
+.DS_Store
diff --git a/node_modules/node-firefox-find-simulators/.travis.yml b/node_modules/node-firefox-find-simulators/.travis.yml
new file mode 100644
index 0000000..cf99939
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/.travis.yml
@@ -0,0 +1,6 @@
+language: node_js
+node_js:
+  - 0.10
+os:
+  - linux
+  - osx
diff --git a/node_modules/node-firefox-find-simulators/LICENSE b/node_modules/node-firefox-find-simulators/LICENSE
new file mode 100644
index 0000000..a7c6b5e
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/LICENSE
@@ -0,0 +1,13 @@
+Copyright 2015 Mozilla
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/node_modules/node-firefox-find-simulators/README.md b/node_modules/node-firefox-find-simulators/README.md
new file mode 100644
index 0000000..153d0c7
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/README.md
@@ -0,0 +1,115 @@
+# node-firefox-find-simulators [![Build Status](https://secure.travis-ci.org/mozilla/node-firefox-find-simulators.png?branch=master)](http://travis-ci.org/mozilla/node-firefox-find-simulators)
+
+> Find installed Firefox OS simulators.
+
+[![Install with NPM](https://nodei.co/npm/node-firefox-find-simulators.png?downloads=true&stars=true)](https://nodei.co/npm/node-firefox-find-simulators/)
+
+This is part of the [node-firefox](https://github.com/mozilla/node-firefox) project.
+
+## Current limitations
+
+We do not support Windows yet. But there are placeholders in the code marked with `TODO: Windows` that indicate where the Windows code would need to be added. If you want to contribute, those are the *gaps* that need to be filled in order for this to work on Windows.
+
+**NOTE**
+
+*This is a work in progress. Things will probably be missing and broken while we move from `fxos-simulators` to `node-firefox-find-simulators`. Please have a look at the [existing issues](https://github.com/mozilla/node-firefox-find-simulators/issues), and/or [file more](https://github.com/mozilla/node-firefox-find-simulators/issues/new) if you find any! :-)*
+
+## Installation
+
+### From git
+
+```bash
+git clone https://github.com/mozilla/node-firefox-find-simulators.git
+cd node-firefox-find-simulators
+npm install
+```
+
+If you want to update later on:
+
+```bash
+cd node-firefox-find-simulators
+git pull origin master
+npm install
+```
+
+### npm
+
+```bash
+npm install node-firefox-find-simulators
+```
+
+## Usage
+
+```javascript
+findSimulators(options) // returns a Promise
+```
+
+where `options` is a plain Object with any of the following:
+
+* `version`: only return simulators if their version matches this
+
+If no `options` are provided, or if `options` is an empty `Object` (`{}`), then `findSimulators` will return all installed simulators.
+
+### Finding simulators
+
+```javascript
+var findSimulators = require('node-firefox-find-simulators');
+
+// Return all installed simulators
+findSimulators().then(function(results) {
+  console.log(results);
+});
+
+// Returns all installed simulators, this time with error handling
+findSimulators().then(function(results) {
+  console.log(results);
+}, function(err) {
+  console.log(err);
+});
+
+// Returns only v2.1 simulators
+findSimulators({ version: '2.1' }).then(function(results) {
+  console.log(results);
+});
+
+// Returns only v2.1 or v2.2 simulators
+findSimulators({ version: ['2.1', '2.2'] }).then(function(results) {
+  console.log(results);
+});
+
+
+
+```
+
+## Running the tests
+
+After installing, you can simply run the following from the module folder:
+
+```bash
+npm test
+```
+
+To add a new unit test file, create a new file in the `tests/unit` folder. Any file that matches `test.*.js` will be run as a test by the appropriate test runner, based on the folder location.
+
+We use `gulp` behind the scenes to run the test; if you don't have it installed globally you can use `npm gulp` from inside the project's root folder to run `gulp`.
+
+### Code quality and style
+
+Because we have multiple contributors working on our projects, we value consistent code styles. It makes it easier to read code written by many people! :-)
+
+Our tests include unit tests as well as code quality ("linting") tests that make sure our test pass a style guide and [JSHint](http://jshint.com/). Instead of submitting code with the wrong indentation or a different style, run the tests and you will be told where your code quality/style differs from ours and instructions on how to fix it.
+
+## History
+
+This is based on initial work on [fxos-simulators](https://github.com/nicola/fxos-simulators) by Nicola Greco.
+
+## License
+
+This program is free software; it is distributed under an
+[Apache License](https://github.com/mozilla/node-firefox-find-simulators/blob/master/LICENSE).
+
+## Copyright
+
+Copyright (c) 2015 [Mozilla](https://mozilla.org)
+([Contributors](https://github.com/mozilla/node-firefox-find-simulators/graphs/contributors)).
+
diff --git a/node_modules/node-firefox-find-simulators/examples/findAllSimulators.js b/node_modules/node-firefox-find-simulators/examples/findAllSimulators.js
new file mode 100644
index 0000000..3d2c462
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/examples/findAllSimulators.js
@@ -0,0 +1,9 @@
+'use strict';
+
+var findSimulators = require('../');
+
+findSimulators().then(function(results) {
+  console.log(results);
+}, function(err) {
+  console.log('error', err);
+});
diff --git a/node_modules/node-firefox-find-simulators/examples/findCertainVersions.js b/node_modules/node-firefox-find-simulators/examples/findCertainVersions.js
new file mode 100644
index 0000000..a166d6b
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/examples/findCertainVersions.js
@@ -0,0 +1,17 @@
+'use strict';
+
+var findSimulators = require('../.');
+
+// Only find simulators at version 2.0
+findSimulators({ version: '2.0' }).then(function(simulators) {
+  console.log(simulators);
+}, function(err) {
+  console.error(err);
+});
+
+// Or find simulators at 2.0 and 2.1
+findSimulators({ version: ['2.0', '2.1'] }).then(function(simulators) {
+  console.log(simulators);
+}, function(err) {
+  console.error(err);
+});
diff --git a/node_modules/node-firefox-find-simulators/gulpfile.js b/node_modules/node-firefox-find-simulators/gulpfile.js
new file mode 100644
index 0000000..75941db
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/gulpfile.js
@@ -0,0 +1,4 @@
+var gulp = require('gulp');
+var buildTools = require('node-firefox-build-tools');
+
+buildTools.loadGulpTasks(gulp);
diff --git a/node_modules/node-firefox-find-simulators/index.js b/node_modules/node-firefox-find-simulators/index.js
new file mode 100644
index 0000000..03c8113
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/index.js
@@ -0,0 +1,107 @@
+'use strict';
+
+// See https://github.com/jshint/jshint/issues/1747 for context
+/* global -Promise */
+var Promise = require('es6-promise').Promise;
+var fs = require('fs');
+var path = require('path');
+
+module.exports = findSimulators;
+
+var currentPlatform = require('./lib/platform')(process.platform);
+
+
+function isDir(dirPath) {
+  var stat = fs.statSync(dirPath);
+  return stat.isDirectory();
+}
+
+
+function extensionDir(profileDir) {
+  return path.join(profileDir, 'extensions');
+}
+
+
+function hasExtensions(profileDir) {
+  var extensionsDir = extensionDir(profileDir);
+  return fs.existsSync(extensionsDir);
+}
+
+
+function listExtensions(profileDir) {
+  var extensionsDir = extensionDir(profileDir);
+  var directories = listDirectories(extensionsDir);
+  return directories;
+}
+
+
+function listDirectories(directoryPath) {
+  var dirContents = fs.readdirSync(directoryPath);
+
+  return dirContents.map(function(entry) {
+    return path.join(directoryPath, entry);
+  }).filter(isDir);
+}
+
+
+function flatten(arrays) {
+  if (arrays.length === 0) {
+    return [];
+  } else {
+    var firstElement = arrays[0];
+    var restOfArrays = arrays.slice(1);
+    return firstElement.concat(flatten(restOfArrays));
+  }
+}
+
+
+function getSimulatorInfo(extensionDir) {
+  var binaryDir = currentPlatform.simulatorBinary;
+  var simulatorRegex = /fxos_(.*)_simulator@mozilla\.org$/;
+  var matches = simulatorRegex.exec(extensionDir);
+  if (matches && matches[1]) {
+    binaryDir = currentPlatform.simulatorBinary(matches[1], process.arch);
+    var version = matches[1].replace('_', '.');
+    return {
+      version: version,
+      bin: path.join(extensionDir, binaryDir),
+      profile: path.join(extensionDir, 'profile')
+    };
+  } else {
+    return null;
+  }
+}
+
+
+function findSimulators(options) {
+
+  options = options || {};
+
+  var simulators = [];
+  var profilesDir = currentPlatform.firefoxProfilesDir;
+  var profileFolders = listDirectories(profilesDir);
+  var profilesWithExtensions = profileFolders.filter(hasExtensions);
+
+  // per profile -> get extensions, then filter to only simulator extensions
+  var extensionsPerProfile = profilesWithExtensions.map(listExtensions);
+  var extensions = flatten(extensionsPerProfile);
+  var extensionsWithInfo = extensions.map(getSimulatorInfo);
+  simulators = extensionsWithInfo.filter(function(info) {
+    return info !== null;
+  });
+
+  if (options.version) {
+    var version = options.version;
+    if (version instanceof String) {
+      version = [ version ];
+    }
+    simulators = simulators.filter(function(sim) {
+      return version.indexOf(sim.version) !== -1;
+    });
+  }
+
+  return new Promise(function(resolve, reject) {
+    resolve(simulators);
+  });
+
+}
diff --git a/node_modules/node-firefox-find-simulators/lib/platform.js b/node_modules/node-firefox-find-simulators/lib/platform.js
new file mode 100644
index 0000000..2c45490
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/lib/platform.js
@@ -0,0 +1,81 @@
+'use strict';
+
+var path = require('path');
+
+module.exports = getConstants;
+
+var firefoxProfilesDir = {
+  darwin: 'Library/Application Support/Firefox/Profiles',
+  linux: '.mozilla/firefox',
+  win32: 'AppData\\Roaming\\Mozilla\\Firefox\\Profiles'
+};
+
+var simulatorBinary = {
+  darwin: function(version) {
+    var basePath = 'B2G.app/Contents/MacOS/b2g-bin';
+    var pathPrefix = getPathPrefix(version);
+
+    if (pathPrefix) {
+      basePath = pathPrefix + 'mac64/' + basePath;
+    } else {
+      basePath = 'b2g/' + basePath;
+    }
+
+    return basePath;
+  },
+  linux: function(version, arch) {
+    var basePath = 'b2g/b2g-bin';
+    var pathPrefix = getPathPrefix(version);
+
+    if (pathPrefix) {
+      var archPath = (arch.indexOf('64') !== -1) ? 'linux64/' : 'linux/';
+      basePath = pathPrefix + archPath + basePath;
+    }
+
+    return basePath;
+  },
+  win32: function(version) {
+    return 'b2g\\b2g-bin.exe';
+  }
+};
+
+
+function getPathPrefix(version) {
+  if (version === '1_2' || version === '1_3') {
+    return 'resources/fxos_' + version + '_simulator/data/';
+  } else {
+    return false;
+  }
+}
+
+
+function getHomeDir(platform) {
+  var homeEnvVar;
+
+  if (platform === 'win32') {
+    homeEnvVar = 'USERPROFILE';
+  } else {
+    homeEnvVar = 'HOME';
+  }
+
+  return process.env[homeEnvVar];
+}
+
+
+function getFirefoxProfilesDir(platform) {
+  var home = getHomeDir(platform);
+  return path.join(home, firefoxProfilesDir[platform]);
+}
+
+
+function getSimulatorBinary(platform) {
+  return simulatorBinary[platform];
+}
+
+
+function getConstants(platform) {
+  return {
+    firefoxProfilesDir: getFirefoxProfilesDir(platform),
+    simulatorBinary: getSimulatorBinary(platform)
+  };
+}
diff --git a/node_modules/node-firefox-find-simulators/node_modules/es6-promise/.release.json b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/.release.json
new file mode 100644
index 0000000..dee8cbc
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/.release.json
@@ -0,0 +1,17 @@
+{
+  "non-interactive": true,
+  "dry-run": false,
+  "verbose": false,
+  "force": false,
+  "pkgFiles": ["package.json", "bower.json"],
+  "increment": "patch",
+  "commitMessage": "Release %s",
+  "tagName": "%s",
+  "tagAnnotation": "Release %s",
+  "buildCommand": "npm run-script build-all",
+  "distRepo": "git@github.com:components/rsvp.js.git",
+  "distStageDir": "tmp/stage",
+  "distBase": "dist",
+  "distFiles": ["**/*", "../package.json", "../bower.json"],
+  "publish": false
+}
diff --git a/node_modules/node-firefox-find-simulators/node_modules/es6-promise/Brocfile.js b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/Brocfile.js
new file mode 100644
index 0000000..d34f458
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/Brocfile.js
@@ -0,0 +1,75 @@
+/* jshint node:true, undef:true, unused:true */
+var AMDFormatter     = require('es6-module-transpiler-amd-formatter');
+var closureCompiler  = require('broccoli-closure-compiler');
+var compileModules   = require('broccoli-compile-modules');
+var mergeTrees       = require('broccoli-merge-trees');
+var moveFile         = require('broccoli-file-mover');
+var es3Recast        = require('broccoli-es3-safe-recast');
+var concat           = require('broccoli-concat');
+var replace          = require('broccoli-string-replace');
+var calculateVersion = require('./lib/calculateVersion');
+var path             = require('path');
+var trees            = [];
+var env              = process.env.EMBER_ENV || 'development';
+
+var bundle = compileModules('lib', {
+  inputFiles: ['es6-promise.umd.js'],
+  output: '/es6-promise.js',
+  formatter: 'bundle',
+});
+
+trees.push(bundle);
+trees.push(compileModules('lib', {
+  inputFiles: ['**/*.js'],
+  output: '/amd/',
+  formatter: new AMDFormatter()
+}));
+
+if (env === 'production') {
+  trees.push(closureCompiler(moveFile(bundle, {
+    srcFile: 'es6-promise.js',
+    destFile: 'es6-promise.min.js'
+  }), {
+    compilation_level: 'ADVANCED_OPTIMIZATIONS',
+    externs: ['node'],
+  }));
+}
+
+var distTree = mergeTrees(trees.concat('config'));
+var distTrees = [];
+
+distTrees.push(concat(distTree, {
+  inputFiles: [
+    'versionTemplate.txt',
+    'es6-promise.js'
+  ],
+  outputFile: '/es6-promise.js'
+}));
+
+if (env === 'production') {
+  distTrees.push(concat(distTree, {
+    inputFiles: [
+      'versionTemplate.txt',
+      'es6-promise.min.js'
+    ],
+    outputFile: '/es6-promise.min.js'
+  }));
+}
+
+if (env !== 'development') {
+  distTrees = distTrees.map(es3Recast);
+}
+
+distTree = mergeTrees(distTrees);
+var distTree = replace(distTree, {
+  files: [
+    'es6-promise.js',
+    'es6-promise.min.js'
+  ],
+  pattern: {
+    match: /VERSION_PLACEHOLDER_STRING/g,
+    replacement: calculateVersion()
+  }
+});
+
+module.exports = distTree;
diff --git a/node_modules/node-firefox-find-simulators/node_modules/es6-promise/CHANGELOG.md b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/CHANGELOG.md
new file mode 100644
index 0000000..e06b496
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/CHANGELOG.md
@@ -0,0 +1,9 @@
+# Master
+
+# 2.0.0
+
+* re-sync with RSVP. Many large performance improvements and bugfixes.
+
+# 1.0.0
+
+* first subset of RSVP
diff --git a/node_modules/node-firefox-find-simulators/node_modules/es6-promise/LICENSE b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/LICENSE
new file mode 100644
index 0000000..954ec59
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/node_modules/node-firefox-find-simulators/node_modules/es6-promise/README.md b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/README.md
new file mode 100644
index 0000000..b974ce4
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/README.md
@@ -0,0 +1,58 @@
+# ES6-Promise (subset of [rsvp.js](https://github.com/tildeio/rsvp.js))
+
+This is a polyfill of the [ES6 Promise](http://people.mozilla.org/~jorendorff/es6-draft.html#sec-promise-constructor). The implementation is a subset of [rsvp.js](https://github.com/tildeio/rsvp.js), if you're wanting extra features and more debugging options, check out the [full library](https://github.com/tildeio/rsvp.js).
+
+For API details and how to use promises, see the <a href="http://www.html5rocks.com/en/tutorials/es6/promises/">JavaScript Promises HTML5Rocks article</a>.
+
+## Downloads
+
+* [es6-promise](https://es6-promises.s3.amazonaws.com/es6-promise-2.0.1.js)
+* [es6-promise-min](https://es6-promises.s3.amazonaws.com/es6-promise-2.0.1.min.js) (~2.2k gzipped)
+
+## Node.js
+
+To install:
+
+```sh
+npm install es6-promise
+```
+
+To use:
+
+```js
+var Promise = require('es6-promise').Promise;
+```
+
+## Usage in IE<9
+
+`catch` is a reserved word in IE<9, meaning `promise.catch(func)` throws a syntax error. To work around this, you can use a string to access the property as shown in the following example.
+
+However, please remember that such technique is already provided by most common minifiers, making the resulting code safe for old browsers and production:
+
+```js
+promise['catch'](function(err) {
+  // ...
+});
+```
+
+Or use `.then` instead:
+
+```js
+promise.then(undefined, function(err) {
+  // ...
+});
+```
+
+## Auto-polyfill
+
+To polyfill the global environment (either in Node or in the browser via CommonJS) use the following code snippet:
+
+```js
+require('es6-promise').polyfill();
+```
+
+Notice that we don't assign the result of `polyfill()` to any variable. The `polyfill()` method will patch the global environment (in this case to the `Promise` name) when called.
+
+## Building & Testing
+
+* `npm run build-all && npm test` - Run Mocha tests through Node and PhantomJS.
diff --git a/node_modules/node-firefox-find-simulators/node_modules/es6-promise/bin/publish_to_s3.js b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/bin/publish_to_s3.js
new file mode 100755
index 0000000..7daf49a
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/bin/publish_to_s3.js
@@ -0,0 +1,28 @@
+#!/usr/bin/env node
+
+// To invoke this from the commandline you need the following to env vars to exist:
+//
+// S3_BUCKET_NAME
+// TRAVIS_BRANCH
+// TRAVIS_TAG
+// TRAVIS_COMMIT
+// S3_SECRET_ACCESS_KEY
+// S3_ACCESS_KEY_ID
+//
+// Once you have those you execute with the following:
+//
+// ```sh
+// ./bin/publish_to_s3.js
+// ```
+var S3Publisher = require('ember-publisher');
+var configPath = require('path').join(__dirname, '../config/s3ProjectConfig.js');
+publisher = new S3Publisher({ projectConfigPath: configPath });
+
+// Always use wildcard section of project config.
+// This is useful when the including library does not
+// require channels (like in ember.js / ember-data).
+publisher.currentBranch = function() {
+  return (process.env.TRAVIS_BRANCH === 'master') ? 'wildcard' : 'no-op';
+};
+publisher.publish();
+
diff --git a/node_modules/node-firefox-find-simulators/node_modules/es6-promise/bower.json b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/bower.json
new file mode 100644
index 0000000..84ec5ad
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/bower.json
@@ -0,0 +1,29 @@
+{
+  "name": "es6-promise",
+  "namespace": "Promise",
+  "version": "2.0.1",
+  "description": "A polyfill for ES6-style Promises, tracking rsvp",
+  "authors": [
+    "Stefan Penner <stefan.penner@gmail.com>"
+  ],
+  "main": "dist/es6-promise.js",
+  "keywords": [
+    "promise"
+  ],
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/jakearchibald/ES6-Promises.git"
+  },
+  "bugs": {
+    "url": "https://github.com/jakearchibald/ES6-Promises/issues"
+  },
+  "license": "MIT",
+  "ignore": [
+    "node_modules",
+    "bower_components",
+    "test",
+    "tests",
+    "vendor",
+    "tasks"
+  ]
+}
diff --git a/node_modules/node-firefox-find-simulators/node_modules/es6-promise/config/s3ProjectConfig.js b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/config/s3ProjectConfig.js
new file mode 100644
index 0000000..5f3349a
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/config/s3ProjectConfig.js
@@ -0,0 +1,26 @@
+/*
+ * Using wildcard because es6-promise does not currently have a
+ * channel system in place.
+ */
+module.exports = function(revision,tag,date){
+  return {
+    'es6-promise.js':
+      { contentType: 'text/javascript',
+        destinations: {
+          wildcard: [
+            'es6-promise-latest.js',
+            'es6-promise-' + revision + '.js'
+          ]
+        }
+      },
+    'es6-promise.min.js':
+      { contentType: 'text/javascript',
+        destinations: {
+          wildcard: [
+            'es6-promise-latest.min.js',
+            'es6-promise-' + revision + '.min.js'
+          ]
+        }
+      }
+  }
+}
diff --git a/node_modules/node-firefox-find-simulators/node_modules/es6-promise/config/versionTemplate.txt b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/config/versionTemplate.txt
new file mode 100644
index 0000000..999a5fc
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/config/versionTemplate.txt
@@ -0,0 +1,7 @@
+/*!
+ * @overview es6-promise - a tiny implementation of Promises/A+.
+ * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald)
+ * @license   Licensed under MIT license
+ *            See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE
+ * @version   VERSION_PLACEHOLDER_STRING
+ */
diff --git a/node_modules/node-firefox-find-simulators/node_modules/es6-promise/dist/es6-promise.js b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/dist/es6-promise.js
new file mode 100644
index 0000000..30a6d81
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/dist/es6-promise.js
@@ -0,0 +1,960 @@
+/*!
+ * @overview es6-promise - a tiny implementation of Promises/A+.
+ * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald)
+ * @license   Licensed under MIT license
+ *            See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE
+ * @version   2.0.1
+ */
+
+(function() {
+    "use strict";
+
+    function $$utils$$objectOrFunction(x) {
+      return typeof x === 'function' || (typeof x === 'object' && x !== null);
+    }
+
+    function $$utils$$isFunction(x) {
+      return typeof x === 'function';
+    }
+
+    function $$utils$$isMaybeThenable(x) {
+      return typeof x === 'object' && x !== null;
+    }
+
+    var $$utils$$_isArray;
+
+    if (!Array.isArray) {
+      $$utils$$_isArray = function (x) {
+        return Object.prototype.toString.call(x) === '[object Array]';
+      };
+    } else {
+      $$utils$$_isArray = Array.isArray;
+    }
+
+    var $$utils$$isArray = $$utils$$_isArray;
+    var $$utils$$now = Date.now || function() { return new Date().getTime(); };
+    function $$utils$$F() { }
+
+    var $$utils$$o_create = (Object.create || function (o) {
+      if (arguments.length > 1) {
+        throw new Error('Second argument not supported');
+      }
+      if (typeof o !== 'object') {
+        throw new TypeError('Argument must be an object');
+      }
+      $$utils$$F.prototype = o;
+      return new $$utils$$F();
+    });
+
+    var $$asap$$len = 0;
+
+    var $$asap$$default = function asap(callback, arg) {
+      $$asap$$queue[$$asap$$len] = callback;
+      $$asap$$queue[$$asap$$len + 1] = arg;
+      $$asap$$len += 2;
+      if ($$asap$$len === 2) {
+        // If len is 1, that means that we need to schedule an async flush.
+        // If additional callbacks are queued before the queue is flushed, they
+        // will be processed by this flush that we are scheduling.
+        $$asap$$scheduleFlush();
+      }
+    };
+
+    var $$asap$$browserGlobal = (typeof window !== 'undefined') ? window : {};
+    var $$asap$$BrowserMutationObserver = $$asap$$browserGlobal.MutationObserver || $$asap$$browserGlobal.WebKitMutationObserver;
+
+    // test for web worker but not in IE10
+    var $$asap$$isWorker = typeof Uint8ClampedArray !== 'undefined' &&
+      typeof importScripts !== 'undefined' &&
+      typeof MessageChannel !== 'undefined';
+
+    // node
+    function $$asap$$useNextTick() {
+      return function() {
+        process.nextTick($$asap$$flush);
+      };
+    }
+
+    function $$asap$$useMutationObserver() {
+      var iterations = 0;
+      var observer = new $$asap$$BrowserMutationObserver($$asap$$flush);
+      var node = document.createTextNode('');
+      observer.observe(node, { characterData: true });
+
+      return function() {
+        node.data = (iterations = ++iterations % 2);
+      };
+    }
+
+    // web worker
+    function $$asap$$useMessageChannel() {
+      var channel = new MessageChannel();
+      channel.port1.onmessage = $$asap$$flush;
+      return function () {
+        channel.port2.postMessage(0);
+      };
+    }
+
+    function $$asap$$useSetTimeout() {
+      return function() {
+        setTimeout($$asap$$flush, 1);
+      };
+    }
+
+    var $$asap$$queue = new Array(1000);
+
+    function $$asap$$flush() {
+      for (var i = 0; i < $$asap$$len; i+=2) {
+        var callback = $$asap$$queue[i];
+        var arg = $$asap$$queue[i+1];
+
+        callback(arg);
+
+        $$asap$$queue[i] = undefined;
+        $$asap$$queue[i+1] = undefined;
+      }
+
+      $$asap$$len = 0;
+    }
+
+    var $$asap$$scheduleFlush;
+
+    // Decide what async method to use to triggering processing of queued callbacks:
+    if (typeof process !== 'undefined' && {}.toString.call(process) === '[object process]') {
+      $$asap$$scheduleFlush = $$asap$$useNextTick();
+    } else if ($$asap$$BrowserMutationObserver) {
+      $$asap$$scheduleFlush = $$asap$$useMutationObserver();
+    } else if ($$asap$$isWorker) {
+      $$asap$$scheduleFlush = $$asap$$useMessageChannel();
+    } else {
+      $$asap$$scheduleFlush = $$asap$$useSetTimeout();
+    }
+
+    function $$$internal$$noop() {}
+    var $$$internal$$PENDING   = void 0;
+    var $$$internal$$FULFILLED = 1;
+    var $$$internal$$REJECTED  = 2;
+    var $$$internal$$GET_THEN_ERROR = new $$$internal$$ErrorObject();
+
+    function $$$internal$$selfFullfillment() {
+      return new TypeError("You cannot resolve a promise with itself");
+    }
+
+    function $$$internal$$cannotReturnOwn() {
+      return new TypeError('A promises callback cannot return that same promise.')
+    }
+
+    function $$$internal$$getThen(promise) {
+      try {
+        return promise.then;
+      } catch(error) {
+        $$$internal$$GET_THEN_ERROR.error = error;
+        return $$$internal$$GET_THEN_ERROR;
+      }
+    }
+
+    function $$$internal$$tryThen(then, value, fulfillmentHandler, rejectionHandler) {
+      try {
+        then.call(value, fulfillmentHandler, rejectionHandler);
+      } catch(e) {
+        return e;
+      }
+    }
+
+    function $$$internal$$handleForeignThenable(promise, thenable, then) {
+       $$asap$$default(function(promise) {
+        var sealed = false;
+        var error = $$$internal$$tryThen(then, thenable, function(value) {
+          if (sealed) { return; }
+          sealed = true;
+          if (thenable !== value) {
+            $$$internal$$resolve(promise, value);
+          } else {
+            $$$internal$$fulfill(promise, value);
+          }
+        }, function(reason) {
+          if (sealed) { return; }
+          sealed = true;
+
+          $$$internal$$reject(promise, reason);
+        }, 'Settle: ' + (promise._label || ' unknown promise'));
+
+        if (!sealed && error) {
+          sealed = true;
+          $$$internal$$reject(promise, error);
+        }
+      }, promise);
+    }
+
+    function $$$internal$$handleOwnThenable(promise, thenable) {
+      if (thenable._state === $$$internal$$FULFILLED) {
+        $$$internal$$fulfill(promise, thenable._result);
+      } else if (promise._state === $$$internal$$REJECTED) {
+        $$$internal$$reject(promise, thenable._result);
+      } else {
+        $$$internal$$subscribe(thenable, undefined, function(value) {
+          $$$internal$$resolve(promise, value);
+        }, function(reason) {
+          $$$internal$$reject(promise, reason);
+        });
+      }
+    }
+
+    function $$$internal$$handleMaybeThenable(promise, maybeThenable) {
+      if (maybeThenable.constructor === promise.constructor) {
+        $$$internal$$handleOwnThenable(promise, maybeThenable);
+      } else {
+        var then = $$$internal$$getThen(maybeThenable);
+
+        if (then === $$$internal$$GET_THEN_ERROR) {
+          $$$internal$$reject(promise, $$$internal$$GET_THEN_ERROR.error);
+        } else if (then === undefined) {
+          $$$internal$$fulfill(promise, maybeThenable);
+        } else if ($$utils$$isFunction(then)) {
+          $$$internal$$handleForeignThenable(promise, maybeThenable, then);
+        } else {
+          $$$internal$$fulfill(promise, maybeThenable);
+        }
+      }
+    }
+
+    function $$$internal$$resolve(promise, value) {
+      if (promise === value) {
+        $$$internal$$reject(promise, $$$internal$$selfFullfillment());
+      } else if ($$utils$$objectOrFunction(value)) {
+        $$$internal$$handleMaybeThenable(promise, value);
+      } else {
+        $$$internal$$fulfill(promise, value);
+      }
+    }
+
+    function $$$internal$$publishRejection(promise) {
+      if (promise._onerror) {
+        promise._onerror(promise._result);
+      }
+
+      $$$internal$$publish(promise);
+    }
+
+    function $$$internal$$fulfill(promise, value) {
+      if (promise._state !== $$$internal$$PENDING) { return; }
+
+      promise._result = value;
+      promise._state = $$$internal$$FULFILLED;
+
+      if (promise._subscribers.length === 0) {
+      } else {
+        $$asap$$default($$$internal$$publish, promise);
+      }
+    }
+
+    function $$$internal$$reject(promise, reason) {
+      if (promise._state !== $$$internal$$PENDING) { return; }
+      promise._state = $$$internal$$REJECTED;
+      promise._result = reason;
+
+      $$asap$$default($$$internal$$publishRejection, promise);
+    }
+
+    function $$$internal$$subscribe(parent, child, onFulfillment, onRejection) {
+      var subscribers = parent._subscribers;
+      var length = subscribers.length;
+
+      parent._onerror = null;
+
+      subscribers[length] = child;
+      subscribers[length + $$$internal$$FULFILLED] = onFulfillment;
+      subscribers[length + $$$internal$$REJECTED]  = onRejection;
+
+      if (length === 0 && parent._state) {
+        $$asap$$default($$$internal$$publish, parent);
+      }
+    }
+
+    function $$$internal$$publish(promise) {
+      var subscribers = promise._subscribers;
+      var settled = promise._state;
+
+      if (subscribers.length === 0) { return; }
+
+      var child, callback, detail = promise._result;
+
+      for (var i = 0; i < subscribers.length; i += 3) {
+        child = subscribers[i];
+        callback = subscribers[i + settled];
+
+        if (child) {
+          $$$internal$$invokeCallback(settled, child, callback, detail);
+        } else {
+          callback(detail);
+        }
+      }
+
+      promise._subscribers.length = 0;
+    }
+
+    function $$$internal$$ErrorObject() {
+      this.error = null;
+    }
+
+    var $$$internal$$TRY_CATCH_ERROR = new $$$internal$$ErrorObject();
+
+    function $$$internal$$tryCatch(callback, detail) {
+      try {
+        return callback(detail);
+      } catch(e) {
+        $$$internal$$TRY_CATCH_ERROR.error = e;
+        return $$$internal$$TRY_CATCH_ERROR;
+      }
+    }
+
+    function $$$internal$$invokeCallback(settled, promise, callback, detail) {
+      var hasCallback = $$utils$$isFunction(callback),
+          value, error, succeeded, failed;
+
+      if (hasCallback) {
+        value = $$$internal$$tryCatch(callback, detail);
+
+        if (value === $$$internal$$TRY_CATCH_ERROR) {
+          failed = true;
+          error = value.error;
+          value = null;
+        } else {
+          succeeded = true;
+        }
+
+        if (promise === value) {
+          $$$internal$$reject(promise, $$$internal$$cannotReturnOwn());
+          return;
+        }
+
+      } else {
+        value = detail;
+        succeeded = true;
+      }
+
+      if (promise._state !== $$$internal$$PENDING) {
+        // noop
+      } else if (hasCallback && succeeded) {
+        $$$internal$$resolve(promise, value);
+      } else if (failed) {
+        $$$internal$$reject(promise, error);
+      } else if (settled === $$$internal$$FULFILLED) {
+        $$$internal$$fulfill(promise, value);
+      } else if (settled === $$$internal$$REJECTED) {
+        $$$internal$$reject(promise, value);
+      }
+    }
+
+    function $$$internal$$initializePromise(promise, resolver) {
+      try {
+        resolver(function resolvePromise(value){
+          $$$internal$$resolve(promise, value);
+        }, function rejectPromise(reason) {
+          $$$internal$$reject(promise, reason);
+        });
+      } catch(e) {
+        $$$internal$$reject(promise, e);
+      }
+    }
+
+    function $$$enumerator$$makeSettledResult(state, position, value) {
+      if (state === $$$internal$$FULFILLED) {
+        return {
+          state: 'fulfilled',
+          value: value
+        };
+      } else {
+        return {
+          state: 'rejected',
+          reason: value
+        };
+      }
+    }
+
+    function $$$enumerator$$Enumerator(Constructor, input, abortOnReject, label) {
+      this._instanceConstructor = Constructor;
+      this.promise = new Constructor($$$internal$$noop, label);
+      this._abortOnReject = abortOnReject;
+
+      if (this._validateInput(input)) {
+        this._input     = input;
+        this.length     = input.length;
+        this._remaining = input.length;
+
+        this._init();
+
+        if (this.length === 0) {
+          $$$internal$$fulfill(this.promise, this._result);
+        } else {
+          this.length = this.length || 0;
+          this._enumerate();
+          if (this._remaining === 0) {
+            $$$internal$$fulfill(this.promise, this._result);
+          }
+        }
+      } else {
+        $$$internal$$reject(this.promise, this._validationError());
+      }
+    }
+
+    $$$enumerator$$Enumerator.prototype._validateInput = function(input) {
+      return $$utils$$isArray(input);
+    };
+
+    $$$enumerator$$Enumerator.prototype._validationError = function() {
+      return new Error('Array Methods must be provided an Array');
+    };
+
+    $$$enumerator$$Enumerator.prototype._init = function() {
+      this._result = new Array(this.length);
+    };
+
+    var $$$enumerator$$default = $$$enumerator$$Enumerator;
+
+    $$$enumerator$$Enumerator.prototype._enumerate = function() {
+      var length  = this.length;
+      var promise = this.promise;
+      var input   = this._input;
+
+      for (var i = 0; promise._state === $$$internal$$PENDING && i < length; i++) {
+        this._eachEntry(input[i], i);
+      }
+    };
+
+    $$$enumerator$$Enumerator.prototype._eachEntry = function(entry, i) {
+      var c = this._instanceConstructor;
+      if ($$utils$$isMaybeThenable(entry)) {
+        if (entry.constructor === c && entry._state !== $$$internal$$PENDING) {
+          entry._onerror = null;
+          this._settledAt(entry._state, i, entry._result);
+        } else {
+          this._willSettleAt(c.resolve(entry), i);
+        }
+      } else {
+        this._remaining--;
+        this._result[i] = this._makeResult($$$internal$$FULFILLED, i, entry);
+      }
+    };
+
+    $$$enumerator$$Enumerator.prototype._settledAt = function(state, i, value) {
+      var promise = this.promise;
+
+      if (promise._state === $$$internal$$PENDING) {
+        this._remaining--;
+
+        if (this._abortOnReject && state === $$$internal$$REJECTED) {
+          $$$internal$$reject(promise, value);
+        } else {
+          this._result[i] = this._makeResult(state, i, value);
+        }
+      }
+
+      if (this._remaining === 0) {
+        $$$internal$$fulfill(promise, this._result);
+      }
+    };
+
+    $$$enumerator$$Enumerator.prototype._makeResult = function(state, i, value) {
+      return value;
+    };
+
+    $$$enumerator$$Enumerator.prototype._willSettleAt = function(promise, i) {
+      var enumerator = this;
+
+      $$$internal$$subscribe(promise, undefined, function(value) {
+        enumerator._settledAt($$$internal$$FULFILLED, i, value);
+      }, function(reason) {
+        enumerator._settledAt($$$internal$$REJECTED, i, reason);
+      });
+    };
+
+    var $$promise$all$$default = function all(entries, label) {
+      return new $$$enumerator$$default(this, entries, true /* abort on reject */, label).promise;
+    };
+
+    var $$promise$race$$default = function race(entries, label) {
+      /*jshint validthis:true */
+      var Constructor = this;
+
+      var promise = new Constructor($$$internal$$noop, label);
+
+      if (!$$utils$$isArray(entries)) {
+        $$$internal$$reject(promise, new TypeError('You must pass an array to race.'));
+        return promise;
+      }
+
+      var length = entries.length;
+
+      function onFulfillment(value) {
+        $$$internal$$resolve(promise, value);
+      }
+
+      function onRejection(reason) {
+        $$$internal$$reject(promise, reason);
+      }
+
+      for (var i = 0; promise._state === $$$internal$$PENDING && i < length; i++) {
+        $$$internal$$subscribe(Constructor.resolve(entries[i]), undefined, onFulfillment, onRejection);
+      }
+
+      return promise;
+    };
+
+    var $$promise$resolve$$default = function resolve(object, label) {
+      /*jshint validthis:true */
+      var Constructor = this;
+
+      if (object && typeof object === 'object' && object.constructor === Constructor) {
+        return object;
+      }
+
+      var promise = new Constructor($$$internal$$noop, label);
+      $$$internal$$resolve(promise, object);
+      return promise;
+    };
+
+    var $$promise$reject$$default = function reject(reason, label) {
+      /*jshint validthis:true */
+      var Constructor = this;
+      var promise = new Constructor($$$internal$$noop, label);
+      $$$internal$$reject(promise, reason);
+      return promise;
+    };
+
+    var $$es6$promise$promise$$counter = 0;
+
+    function $$es6$promise$promise$$needsResolver() {
+      throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
+    }
+
+    function $$es6$promise$promise$$needsNew() {
+      throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");
+    }
+
+    var $$es6$promise$promise$$default = $$es6$promise$promise$$Promise;
+
+    /**
+      Promise objects represent the eventual result of an asynchronous operation. The
+      primary way of interacting with a promise is through its `then` method, which
+      registers callbacks to receive either a promise’s eventual value or the reason
+      why the promise cannot be fulfilled.
+
+      Terminology
+      -----------
+
+      - `promise` is an object or function with a `then` method whose behavior conforms to this specification.
+      - `thenable` is an object or function that defines a `then` method.
+      - `value` is any legal JavaScript value (including undefined, a thenable, or a promise).
+      - `exception` is a value that is thrown using the throw statement.
+      - `reason` is a value that indicates why a promise was rejected.
+      - `settled` the final resting state of a promise, fulfilled or rejected.
+
+      A promise can be in one of three states: pending, fulfilled, or rejected.
+
+      Promises that are fulfilled have a fulfillment value and are in the fulfilled
+      state.  Promises that are rejected have a rejection reason and are in the
+      rejected state.  A fulfillment value is never a thenable.
+
+      Promises can also be said to *resolve* a value.  If this value is also a
+      promise, then the original promise's settled state will match the value's
+      settled state.  So a promise that *resolves* a promise that rejects will
+      itself reject, and a promise that *resolves* a promise that fulfills will
+      itself fulfill.
+
+
+      Basic Usage:
+      ------------
+
+      ```js
+      var promise = new Promise(function(resolve, reject) {
+        // on success
+        resolve(value);
+
+        // on failure
+        reject(reason);
+      });
+
+      promise.then(function(value) {
+        // on fulfillment
+      }, function(reason) {
+        // on rejection
+      });
+      ```
+
+      Advanced Usage:
+      ---------------
+
+      Promises shine when abstracting away asynchronous interactions such as
+      `XMLHttpRequest`s.
+
+      ```js
+      function getJSON(url) {
+        return new Promise(function(resolve, reject){
+          var xhr = new XMLHttpRequest();
+
+          xhr.open('GET', url);
+          xhr.onreadystatechange = handler;
+          xhr.responseType = 'json';
+          xhr.setRequestHeader('Accept', 'application/json');
+          xhr.send();
+
+          function handler() {
+            if (this.readyState === this.DONE) {
+              if (this.status === 200) {
+                resolve(this.response);
+              } else {
+                reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']'));
+              }
+            }
+          };
+        });
+      }
+
+      getJSON('/posts.json').then(function(json) {
+        // on fulfillment
+      }, function(reason) {
+        // on rejection
+      });
+      ```
+
+      Unlike callbacks, promises are great composable primitives.
+
+      ```js
+      Promise.all([
+        getJSON('/posts'),
+        getJSON('/comments')
+      ]).then(function(values){
+        values[0] // => postsJSON
+        values[1] // => commentsJSON
+
+        return values;
+      });
+      ```
+
+      @class Promise
+      @param {function} resolver
+      Useful for tooling.
+      @constructor
+    */
+    function $$es6$promise$promise$$Promise(resolver) {
+      this._id = $$es6$promise$promise$$counter++;
+      this._state = undefined;
+      this._result = undefined;
+      this._subscribers = [];
+
+      if ($$$internal$$noop !== resolver) {
+        if (!$$utils$$isFunction(resolver)) {
+          $$es6$promise$promise$$needsResolver();
+        }
+
+        if (!(this instanceof $$es6$promise$promise$$Promise)) {
+          $$es6$promise$promise$$needsNew();
+        }
+
+        $$$internal$$initializePromise(this, resolver);
+      }
+    }
+
+    $$es6$promise$promise$$Promise.all = $$promise$all$$default;
+    $$es6$promise$promise$$Promise.race = $$promise$race$$default;
+    $$es6$promise$promise$$Promise.resolve = $$promise$resolve$$default;
+    $$es6$promise$promise$$Promise.reject = $$promise$reject$$default;
+
+    $$es6$promise$promise$$Promise.prototype = {
+      constructor: $$es6$promise$promise$$Promise,
+
+    /**
+      The primary way of interacting with a promise is through its `then` method,
+      which registers callbacks to receive either a promise's eventual value or the
+      reason why the promise cannot be fulfilled.
+
+      ```js
+      findUser().then(function(user){
+        // user is available
+      }, function(reason){
+        // user is unavailable, and you are given the reason why
+      });
+      ```
+
+      Chaining
+      --------
+
+      The return value of `then` is itself a promise.  This second, 'downstream'
+      promise is resolved with the return value of the first promise's fulfillment
+      or rejection handler, or rejected if the handler throws an exception.
+
+      ```js
+      findUser().then(function (user) {
+        return user.name;
+      }, function (reason) {
+        return 'default name';
+      }).then(function (userName) {
+        // If `findUser` fulfilled, `userName` will be the user's name, otherwise it
+        // will be `'default name'`
+      });
+
+      findUser().then(function (user) {
+        throw new Error('Found user, but still unhappy');
+      }, function (reason) {
+        throw new Error('`findUser` rejected and we're unhappy');
+      }).then(function (value) {
+        // never reached
+      }, function (reason) {
+        // if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'.
+        // If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'.
+      });
+      ```
+      If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream.
+
+      ```js
+      findUser().then(function (user) {
+        throw new PedagogicalException('Upstream error');
+      }).then(function (value) {
+        // never reached
+      }).then(function (value) {
+        // never reached
+      }, function (reason) {
+        // The `PedgagocialException` is propagated all the way down to here
+      });
+      ```
+
+      Assimilation
+      ------------
+
+      Sometimes the value you want to propagate to a downstream promise can only be
+      retrieved asynchronously. This can be achieved by returning a promise in the
+      fulfillment or rejection handler. The downstream promise will then be pending
+      until the returned promise is settled. This is called *assimilation*.
+
+      ```js
+      findUser().then(function (user) {
+        return findCommentsByAuthor(user);
+      }).then(function (comments) {
+        // The user's comments are now available
+      });
+      ```
+
+      If the assimliated promise rejects, then the downstream promise will also reject.
+
+      ```js
+      findUser().then(function (user) {
+        return findCommentsByAuthor(user);
+      }).then(function (comments) {
+        // If `findCommentsByAuthor` fulfills, we'll have the value here
+      }, function (reason) {
+        // If `findCommentsByAuthor` rejects, we'll have the reason here
+      });
+      ```
+
+      Simple Example
+      --------------
+
+      Synchronous Example
+
+      ```javascript
+      var result;
+
+      try {
+        result = findResult();
+        // success
+      } catch(reason) {
+        // failure
+      }
+      ```
+
+      Errback Example
+
+      ```js
+      findResult(function(result, err){
+        if (err) {
+          // failure
+        } else {
+          // success
+        }
+      });
+      ```
+
+      Promise Example;
+
+      ```javascript
+      findResult().then(function(result){
+        // success
+      }, function(reason){
+        // failure
+      });
+      ```
+
+      Advanced Example
+      --------------
+
+      Synchronous Example
+
+      ```javascript
+      var author, books;
+
+      try {
+        author = findAuthor();
+        books  = findBooksByAuthor(author);
+        // success
+      } catch(reason) {
+        // failure
+      }
+      ```
+
+      Errback Example
+
+      ```js
+
+      function foundBooks(books) {
+
+      }
+
+      function failure(reason) {
+
+      }
+
+      findAuthor(function(author, err){
+        if (err) {
+          failure(err);
+          // failure
+        } else {
+          try {
+            findBoooksByAuthor(author, function(books, err) {
+              if (err) {
+                failure(err);
+              } else {
+                try {
+                  foundBooks(books);
+                } catch(reason) {
+                  failure(reason);
+                }
+              }
+            });
+          } catch(error) {
+            failure(err);
+          }
+          // success
+        }
+      });
+      ```
+
+      Promise Example;
+
+      ```javascript
+      findAuthor().
+        then(findBooksByAuthor).
+        then(function(books){
+          // found books
+      }).catch(function(reason){
+        // something went wrong
+      });
+      ```
+
+      @method then
+      @param {Function} onFulfilled
+      @param {Function} onRejected
+      Useful for tooling.
+      @return {Promise}
+    */
+      then: function(onFulfillment, onRejection) {
+        var parent = this;
+        var state = parent._state;
+
+        if (state === $$$internal$$FULFILLED && !onFulfillment || state === $$$internal$$REJECTED && !onRejection) {
+          return this;
+        }
+
+        var child = new this.constructor($$$internal$$noop);
+        var result = parent._result;
+
+        if (state) {
+          var callback = arguments[state - 1];
+          $$asap$$default(function(){
+            $$$internal$$invokeCallback(state, child, callback, result);
+          });
+        } else {
+          $$$internal$$subscribe(parent, child, onFulfillment, onRejection);
+        }
+
+        return child;
+      },
+
+    /**
+      `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same
+      as the catch block of a try/catch statement.
+
+      ```js
+      function findAuthor(){
+        throw new Error('couldn't find that author');
+      }
+
+      // synchronous
+      try {
+        findAuthor();
+      } catch(reason) {
+        // something went wrong
+      }
+
+      // async with promises
+      findAuthor().catch(function(reason){
+        // something went wrong
+      });
+      ```
+
+      @method catch
+      @param {Function} onRejection
+      Useful for tooling.
+      @return {Promise}
+    */
+      'catch': function(onRejection) {
+        return this.then(null, onRejection);
+      }
+    };
+
+    var $$es6$promise$polyfill$$default = function polyfill() {
+      var local;
+
+      if (typeof global !== 'undefined') {
+        local = global;
+      } else if (typeof window !== 'undefined' && window.document) {
+        local = window;
+      } else {
+        local = self;
+      }
+
+      var es6PromiseSupport =
+        "Promise" in local &&
+        // Some of these methods are missing from
+        // Firefox/Chrome experimental implementations
+        "resolve" in local.Promise &&
+        "reject" in local.Promise &&
+        "all" in local.Promise &&
+        "race" in local.Promise &&
+        // Older version of the spec had a resolver object
+        // as the arg rather than a function
+        (function() {
+          var resolve;
+          new local.Promise(function(r) { resolve = r; });
+          return $$utils$$isFunction(resolve);
+        }());
+
+      if (!es6PromiseSupport) {
+        local.Promise = $$es6$promise$promise$$default;
+      }
+    };
+
+    var es6$promise$umd$$ES6Promise = {
+      'Promise': $$es6$promise$promise$$default,
+      'polyfill': $$es6$promise$polyfill$$default
+    };
+
+    /* global define:true module:true window: true */
+    if (typeof define === 'function' && define['amd']) {
+      define(function() { return es6$promise$umd$$ES6Promise; });
+    } else if (typeof module !== 'undefined' && module['exports']) {
+      module['exports'] = es6$promise$umd$$ES6Promise;
+    } else if (typeof this !== 'undefined') {
+      this['ES6Promise'] = es6$promise$umd$$ES6Promise;
+    }
+}).call(this);
\ No newline at end of file
diff --git a/node_modules/node-firefox-find-simulators/node_modules/es6-promise/dist/es6-promise.min.js b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/dist/es6-promise.min.js
new file mode 100644
index 0000000..6e0c99f
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/dist/es6-promise.min.js
@@ -0,0 +1,18 @@
+/*!
+ * @overview es6-promise - a tiny implementation of Promises/A+.
+ * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald)
+ * @license   Licensed under MIT license
+ *            See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE
+ * @version   2.0.1
+ */
+
+(function(){function r(a,b){n[l]=a;n[l+1]=b;l+=2;2===l&&A()}function s(a){return"function"===typeof a}function F(){return function(){process.nextTick(t)}}function G(){var a=0,b=new B(t),c=document.createTextNode("");b.observe(c,{characterData:!0});return function(){c.data=a=++a%2}}function H(){var a=new MessageChannel;a.port1.onmessage=t;return function(){a.port2.postMessage(0)}}function I(){return function(){setTimeout(t,1)}}function t(){for(var a=0;a<l;a+=2)(0,n[a])(n[a+1]),n[a]=void 0,n[a+1]=void 0;
+l=0}function p(){}function J(a,b,c,d){try{a.call(b,c,d)}catch(e){return e}}function K(a,b,c){r(function(a){var e=!1,f=J(c,b,function(c){e||(e=!0,b!==c?q(a,c):m(a,c))},function(b){e||(e=!0,g(a,b))});!e&&f&&(e=!0,g(a,f))},a)}function L(a,b){1===b.a?m(a,b.b):2===a.a?g(a,b.b):u(b,void 0,function(b){q(a,b)},function(b){g(a,b)})}function q(a,b){if(a===b)g(a,new TypeError("You cannot resolve a promise with itself"));else if("function"===typeof b||"object"===typeof b&&null!==b)if(b.constructor===a.constructor)L(a,
+b);else{var c;try{c=b.then}catch(d){v.error=d,c=v}c===v?g(a,v.error):void 0===c?m(a,b):s(c)?K(a,b,c):m(a,b)}else m(a,b)}function M(a){a.f&&a.f(a.b);x(a)}function m(a,b){void 0===a.a&&(a.b=b,a.a=1,0!==a.e.length&&r(x,a))}function g(a,b){void 0===a.a&&(a.a=2,a.b=b,r(M,a))}function u(a,b,c,d){var e=a.e,f=e.length;a.f=null;e[f]=b;e[f+1]=c;e[f+2]=d;0===f&&a.a&&r(x,a)}function x(a){var b=a.e,c=a.a;if(0!==b.length){for(var d,e,f=a.b,g=0;g<b.length;g+=3)d=b[g],e=b[g+c],d?C(c,d,e,f):e(f);a.e.length=0}}function D(){this.error=
+null}function C(a,b,c,d){var e=s(c),f,k,h,l;if(e){try{f=c(d)}catch(n){y.error=n,f=y}f===y?(l=!0,k=f.error,f=null):h=!0;if(b===f){g(b,new TypeError("A promises callback cannot return that same promise."));return}}else f=d,h=!0;void 0===b.a&&(e&&h?q(b,f):l?g(b,k):1===a?m(b,f):2===a&&g(b,f))}function N(a,b){try{b(function(b){q(a,b)},function(b){g(a,b)})}catch(c){g(a,c)}}function k(a,b,c,d){this.n=a;this.c=new a(p,d);this.i=c;this.o(b)?(this.m=b,this.d=this.length=b.length,this.l(),0===this.length?m(this.c,
+this.b):(this.length=this.length||0,this.k(),0===this.d&&m(this.c,this.b))):g(this.c,this.p())}function h(a){O++;this.b=this.a=void 0;this.e=[];if(p!==a){if(!s(a))throw new TypeError("You must pass a resolver function as the first argument to the promise constructor");if(!(this instanceof h))throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");N(this,a)}}var E=Array.isArray?Array.isArray:function(a){return"[object Array]"===
+Object.prototype.toString.call(a)},l=0,w="undefined"!==typeof window?window:{},B=w.MutationObserver||w.WebKitMutationObserver,w="undefined"!==typeof Uint8ClampedArray&&"undefined"!==typeof importScripts&&"undefined"!==typeof MessageChannel,n=Array(1E3),A;A="undefined"!==typeof process&&"[object process]"==={}.toString.call(process)?F():B?G():w?H():I();var v=new D,y=new D;k.prototype.o=function(a){return E(a)};k.prototype.p=function(){return Error("Array Methods must be provided an Array")};k.prototype.l=
+function(){this.b=Array(this.length)};k.prototype.k=function(){for(var a=this.length,b=this.c,c=this.m,d=0;void 0===b.a&&d<a;d++)this.j(c[d],d)};k.prototype.j=function(a,b){var c=this.n;"object"===typeof a&&null!==a?a.constructor===c&&void 0!==a.a?(a.f=null,this.g(a.a,b,a.b)):this.q(c.resolve(a),b):(this.d--,this.b[b]=this.h(a))};k.prototype.g=function(a,b,c){var d=this.c;void 0===d.a&&(this.d--,this.i&&2===a?g(d,c):this.b[b]=this.h(c));0===this.d&&m(d,this.b)};k.prototype.h=function(a){return a};
+k.prototype.q=function(a,b){var c=this;u(a,void 0,function(a){c.g(1,b,a)},function(a){c.g(2,b,a)})};var O=0;h.all=function(a,b){return(new k(this,a,!0,b)).c};h.race=function(a,b){function c(a){q(e,a)}function d(a){g(e,a)}var e=new this(p,b);if(!E(a))return (g(e,new TypeError("You must pass an array to race.")), e);for(var f=a.length,h=0;void 0===e.a&&h<f;h++)u(this.resolve(a[h]),void 0,c,d);return e};h.resolve=function(a,b){if(a&&"object"===typeof a&&a.constructor===this)return a;var c=new this(p,b);
+q(c,a);return c};h.reject=function(a,b){var c=new this(p,b);g(c,a);return c};h.prototype={constructor:h,then:function(a,b){var c=this.a;if(1===c&&!a||2===c&&!b)return this;var d=new this.constructor(p),e=this.b;if(c){var f=arguments[c-1];r(function(){C(c,d,f,e)})}else u(this,d,a,b);return d},"catch":function(a){return this.then(null,a)}};var z={Promise:h,polyfill:function(){var a;a="undefined"!==typeof global?global:"undefined"!==typeof window&&window.document?window:self;"Promise"in a&&"resolve"in
+a.Promise&&"reject"in a.Promise&&"all"in a.Promise&&"race"in a.Promise&&function(){var b;new a.Promise(function(a){b=a});return s(b)}()||(a.Promise=h)}};"function"===typeof define&&define.amd?define(function(){return z}):"undefined"!==typeof module&&module.exports?module.exports=z:"undefined"!==typeof this&&(this.ES6Promise=z)}).call(this);
diff --git a/node_modules/node-firefox-find-simulators/node_modules/es6-promise/lib/calculateVersion.js b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/lib/calculateVersion.js
new file mode 100644
index 0000000..018e364
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/lib/calculateVersion.js
@@ -0,0 +1,36 @@
+'use strict';
+
+var fs   = require('fs');
+var path = require('path');
+
+module.exports = function () {
+  var packageVersion = require('../package.json').version;
+  var output         = [packageVersion];
+  var gitPath        = path.join(__dirname,'..','.git');
+  var headFilePath   = path.join(gitPath, 'HEAD');
+
+  if (packageVersion.indexOf('+') > -1) {
+    try {
+      if (fs.existsSync(headFilePath)) {
+        var headFile = fs.readFileSync(headFilePath, {encoding: 'utf8'});
+        var branchName = headFile.split('/').slice(-1)[0].trim();
+        var refPath = headFile.split(' ')[1];
+        var branchSHA;
+
+        if (refPath) {
+          var branchPath = path.join(gitPath, refPath.trim());
+          branchSHA  = fs.readFileSync(branchPath);
+        } else {
+          branchSHA = branchName;
+        }
+
+        output.push(branchSHA.slice(0,10));
+      }
+    } catch (err) {
+      console.error(err.stack);
+    }
+    return output.join('.');
+  } else {
+    return packageVersion;
+  }
+};
diff --git a/node_modules/node-firefox-find-simulators/node_modules/es6-promise/lib/es6-promise.umd.js b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/lib/es6-promise.umd.js
new file mode 100644
index 0000000..cd01553
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/lib/es6-promise.umd.js
@@ -0,0 +1,16 @@
+import Promise from './es6-promise/promise';
+import polyfill from './es6-promise/polyfill';
+
+var ES6Promise = {
+  'Promise': Promise,
+  'polyfill': polyfill
+};
+
+/* global define:true module:true window: true */
+if (typeof define === 'function' && define['amd']) {
+  define(function() { return ES6Promise; });
+} else if (typeof module !== 'undefined' && module['exports']) {
+  module['exports'] = ES6Promise;
+} else if (typeof this !== 'undefined') {
+  this['ES6Promise'] = ES6Promise;
+}
diff --git a/node_modules/node-firefox-find-simulators/node_modules/es6-promise/lib/es6-promise/-internal.js b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/lib/es6-promise/-internal.js
new file mode 100644
index 0000000..afef22e
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/lib/es6-promise/-internal.js
@@ -0,0 +1,251 @@
+import {
+  objectOrFunction,
+  isFunction
+} from './utils';
+
+import asap from './asap';
+
+function noop() {}
+
+var PENDING   = void 0;
+var FULFILLED = 1;
+var REJECTED  = 2;
+
+var GET_THEN_ERROR = new ErrorObject();
+
+function selfFullfillment() {
+  return new TypeError("You cannot resolve a promise with itself");
+}
+
+function cannotReturnOwn() {
+  return new TypeError('A promises callback cannot return that same promise.')
+}
+
+function getThen(promise) {
+  try {
+    return promise.then;
+  } catch(error) {
+    GET_THEN_ERROR.error = error;
+    return GET_THEN_ERROR;
+  }
+}
+
+function tryThen(then, value, fulfillmentHandler, rejectionHandler) {
+  try {
+    then.call(value, fulfillmentHandler, rejectionHandler);
+  } catch(e) {
+    return e;
+  }
+}
+
+function handleForeignThenable(promise, thenable, then) {
+   asap(function(promise) {
+    var sealed = false;
+    var error = tryThen(then, thenable, function(value) {
+      if (sealed) { return; }
+      sealed = true;
+      if (thenable !== value) {
+        resolve(promise, value);
+      } else {
+        fulfill(promise, value);
+      }
+    }, function(reason) {
+      if (sealed) { return; }
+      sealed = true;
+
+      reject(promise, reason);
+    }, 'Settle: ' + (promise._label || ' unknown promise'));
+
+    if (!sealed && error) {
+      sealed = true;
+      reject(promise, error);
+    }
+  }, promise);
+}
+
+function handleOwnThenable(promise, thenable) {
+  if (thenable._state === FULFILLED) {
+    fulfill(promise, thenable._result);
+  } else if (promise._state === REJECTED) {
+    reject(promise, thenable._result);
+  } else {
+    subscribe(thenable, undefined, function(value) {
+      resolve(promise, value);
+    }, function(reason) {
+      reject(promise, reason);
+    });
+  }
+}
+
+function handleMaybeThenable(promise, maybeThenable) {
+  if (maybeThenable.constructor === promise.constructor) {
+    handleOwnThenable(promise, maybeThenable);
+  } else {
+    var then = getThen(maybeThenable);
+
+    if (then === GET_THEN_ERROR) {
+      reject(promise, GET_THEN_ERROR.error);
+    } else if (then === undefined) {
+      fulfill(promise, maybeThenable);
+    } else if (isFunction(then)) {
+      handleForeignThenable(promise, maybeThenable, then);
+    } else {
+      fulfill(promise, maybeThenable);
+    }
+  }
+}
+
+function resolve(promise, value) {
+  if (promise === value) {
+    reject(promise, selfFullfillment());
+  } else if (objectOrFunction(value)) {
+    handleMaybeThenable(promise, value);
+  } else {
+    fulfill(promise, value);
+  }
+}
+
+function publishRejection(promise) {
+  if (promise._onerror) {
+    promise._onerror(promise._result);
+  }
+
+  publish(promise);
+}
+
+function fulfill(promise, value) {
+  if (promise._state !== PENDING) { return; }
+
+  promise._result = value;
+  promise._state = FULFILLED;
+
+  if (promise._subscribers.length === 0) {
+  } else {
+    asap(publish, promise);
+  }
+}
+
+function reject(promise, reason) {
+  if (promise._state !== PENDING) { return; }
+  promise._state = REJECTED;
+  promise._result = reason;
+
+  asap(publishRejection, promise);
+}
+
+function subscribe(parent, child, onFulfillment, onRejection) {
+  var subscribers = parent._subscribers;
+  var length = subscribers.length;
+
+  parent._onerror = null;
+
+  subscribers[length] = child;
+  subscribers[length + FULFILLED] = onFulfillment;
+  subscribers[length + REJECTED]  = onRejection;
+
+  if (length === 0 && parent._state) {
+    asap(publish, parent);
+  }
+}
+
+function publish(promise) {
+  var subscribers = promise._subscribers;
+  var settled = promise._state;
+
+  if (subscribers.length === 0) { return; }
+
+  var child, callback, detail = promise._result;
+
+  for (var i = 0; i < subscribers.length; i += 3) {
+    child = subscribers[i];
+    callback = subscribers[i + settled];
+
+    if (child) {
+      invokeCallback(settled, child, callback, detail);
+    } else {
+      callback(detail);
+    }
+  }
+
+  promise._subscribers.length = 0;
+}
+
+function ErrorObject() {
+  this.error = null;
+}
+
+var TRY_CATCH_ERROR = new ErrorObject();
+
+function tryCatch(callback, detail) {
+  try {
+    return callback(detail);
+  } catch(e) {
+    TRY_CATCH_ERROR.error = e;
+    return TRY_CATCH_ERROR;
+  }
+}
+
+function invokeCallback(settled, promise, callback, detail) {
+  var hasCallback = isFunction(callback),
+      value, error, succeeded, failed;
+
+  if (hasCallback) {
+    value = tryCatch(callback, detail);
+
+    if (value === TRY_CATCH_ERROR) {
+      failed = true;
+      error = value.error;
+      value = null;
+    } else {
+      succeeded = true;
+    }
+
+    if (promise === value) {
+      reject(promise, cannotReturnOwn());
+      return;
+    }
+
+  } else {
+    value = detail;
+    succeeded = true;
+  }
+
+  if (promise._state !== PENDING) {
+    // noop
+  } else if (hasCallback && succeeded) {
+    resolve(promise, value);
+  } else if (failed) {
+    reject(promise, error);
+  } else if (settled === FULFILLED) {
+    fulfill(promise, value);
+  } else if (settled === REJECTED) {
+    reject(promise, value);
+  }
+}
+
+function initializePromise(promise, resolver) {
+  try {
+    resolver(function resolvePromise(value){
+      resolve(promise, value);
+    }, function rejectPromise(reason) {
+      reject(promise, reason);
+    });
+  } catch(e) {
+    reject(promise, e);
+  }
+}
+
+export {
+  noop,
+  resolve,
+  reject,
+  fulfill,
+  subscribe,
+  publish,
+  publishRejection,
+  initializePromise,
+  invokeCallback,
+  FULFILLED,
+  REJECTED,
+  PENDING
+};
diff --git a/node_modules/node-firefox-find-simulators/node_modules/es6-promise/lib/es6-promise/asap.js b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/lib/es6-promise/asap.js
new file mode 100644
index 0000000..4872589
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/lib/es6-promise/asap.js
@@ -0,0 +1,82 @@
+var len = 0;
+
+export default function asap(callback, arg) {
+  queue[len] = callback;
+  queue[len + 1] = arg;
+  len += 2;
+  if (len === 2) {
+    // If len is 1, that means that we need to schedule an async flush.
+    // If additional callbacks are queued before the queue is flushed, they
+    // will be processed by this flush that we are scheduling.
+    scheduleFlush();
+  }
+}
+
+var browserGlobal = (typeof window !== 'undefined') ? window : {};
+var BrowserMutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver;
+
+// test for web worker but not in IE10
+var isWorker = typeof Uint8ClampedArray !== 'undefined' &&
+  typeof importScripts !== 'undefined' &&
+  typeof MessageChannel !== 'undefined';
+
+// node
+function useNextTick() {
+  return function() {
+    process.nextTick(flush);
+  };
+}
+
+function useMutationObserver() {
+  var iterations = 0;
+  var observer = new BrowserMutationObserver(flush);
+  var node = document.createTextNode('');
+  observer.observe(node, { characterData: true });
+
+  return function() {
+    node.data = (iterations = ++iterations % 2);
+  };
+}
+
+// web worker
+function useMessageChannel() {
+  var channel = new MessageChannel();
+  channel.port1.onmessage = flush;
+  return function () {
+    channel.port2.postMessage(0);
+  };
+}
+
+function useSetTimeout() {
+  return function() {
+    setTimeout(flush, 1);
+  };
+}
+
+var queue = new Array(1000);
+function flush() {
+  for (var i = 0; i < len; i+=2) {
+    var callback = queue[i];
+    var arg = queue[i+1];
+
+    callback(arg);
+
+    queue[i] = undefined;
+    queue[i+1] = undefined;
+  }
+
+  len = 0;
+}
+
+var scheduleFlush;
+
+// Decide what async method to use to triggering processing of queued callbacks:
+if (typeof process !== 'undefined' && {}.toString.call(process) === '[object process]') {
+  scheduleFlush = useNextTick();
+} else if (BrowserMutationObserver) {
+  scheduleFlush = useMutationObserver();
+} else if (isWorker) {
+  scheduleFlush = useMessageChannel();
+} else {
+  scheduleFlush = useSetTimeout();
+}
diff --git a/node_modules/node-firefox-find-simulators/node_modules/es6-promise/lib/es6-promise/enumerator.js b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/lib/es6-promise/enumerator.js
new file mode 100644
index 0000000..7d3f803
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/lib/es6-promise/enumerator.js
@@ -0,0 +1,125 @@
+import {
+  isArray,
+  isMaybeThenable
+} from './utils';
+
+import {
+  noop,
+  reject,
+  fulfill,
+  subscribe,
+  FULFILLED,
+  REJECTED,
+  PENDING
+} from './-internal';
+
+export function makeSettledResult(state, position, value) {
+  if (state === FULFILLED) {
+    return {
+      state: 'fulfilled',
+      value: value
+    };
+  } else {
+    return {
+      state: 'rejected',
+      reason: value
+    };
+  }
+}
+
+function Enumerator(Constructor, input, abortOnReject, label) {
+  this._instanceConstructor = Constructor;
+  this.promise = new Constructor(noop, label);
+  this._abortOnReject = abortOnReject;
+
+  if (this._validateInput(input)) {
+    this._input     = input;
+    this.length     = input.length;
+    this._remaining = input.length;
+
+    this._init();
+
+    if (this.length === 0) {
+      fulfill(this.promise, this._result);
+    } else {
+      this.length = this.length || 0;
+      this._enumerate();
+      if (this._remaining === 0) {
+        fulfill(this.promise, this._result);
+      }
+    }
+  } else {
+    reject(this.promise, this._validationError());
+  }
+}
+
+Enumerator.prototype._validateInput = function(input) {
+  return isArray(input);
+};
+
+Enumerator.prototype._validationError = function() {
+  return new Error('Array Methods must be provided an Array');
+};
+
+Enumerator.prototype._init = function() {
+  this._result = new Array(this.length);
+};
+
+export default Enumerator;
+
+Enumerator.prototype._enumerate = function() {
+  var length  = this.length;
+  var promise = this.promise;
+  var input   = this._input;
+
+  for (var i = 0; promise._state === PENDING && i < length; i++) {
+    this._eachEntry(input[i], i);
+  }
+};
+
+Enumerator.prototype._eachEntry = function(entry, i) {
+  var c = this._instanceConstructor;
+  if (isMaybeThenable(entry)) {
+    if (entry.constructor === c && entry._state !== PENDING) {
+      entry._onerror = null;
+      this._settledAt(entry._state, i, entry._result);
+    } else {
+      this._willSettleAt(c.resolve(entry), i);
+    }
+  } else {
+    this._remaining--;
+    this._result[i] = this._makeResult(FULFILLED, i, entry);
+  }
+};
+
+Enumerator.prototype._settledAt = function(state, i, value) {
+  var promise = this.promise;
+
+  if (promise._state === PENDING) {
+    this._remaining--;
+
+    if (this._abortOnReject && state === REJECTED) {
+      reject(promise, value);
+    } else {
+      this._result[i] = this._makeResult(state, i, value);
+    }
+  }
+
+  if (this._remaining === 0) {
+    fulfill(promise, this._result);
+  }
+};
+
+Enumerator.prototype._makeResult = function(state, i, value) {
+  return value;
+};
+
+Enumerator.prototype._willSettleAt = function(promise, i) {
+  var enumerator = this;
+
+  subscribe(promise, undefined, function(value) {
+    enumerator._settledAt(FULFILLED, i, value);
+  }, function(reason) {
+    enumerator._settledAt(REJECTED, i, reason);
+  });
+};
diff --git a/node_modules/node-firefox-find-simulators/node_modules/es6-promise/lib/es6-promise/polyfill.js b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/lib/es6-promise/polyfill.js
new file mode 100644
index 0000000..e5b0165
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/lib/es6-promise/polyfill.js
@@ -0,0 +1,35 @@
+/*global self*/
+import { default as RSVPPromise } from "./promise";
+import { isFunction } from "./utils";
+
+export default function polyfill() {
+  var local;
+
+  if (typeof global !== 'undefined') {
+    local = global;
+  } else if (typeof window !== 'undefined' && window.document) {
+    local = window;
+  } else {
+    local = self;
+  }
+
+  var es6PromiseSupport =
+    "Promise" in local &&
+    // Some of these methods are missing from
+    // Firefox/Chrome experimental implementations
+    "resolve" in local.Promise &&
+    "reject" in local.Promise &&
+    "all" in local.Promise &&
+    "race" in local.Promise &&
+    // Older version of the spec had a resolver object
+    // as the arg rather than a function
+    (function() {
+      var resolve;
+      new local.Promise(function(r) { resolve = r; });
+      return isFunction(resolve);
+    }());
+
+  if (!es6PromiseSupport) {
+    local.Promise = RSVPPromise;
+  }
+}
diff --git a/node_modules/node-firefox-find-simulators/node_modules/es6-promise/lib/es6-promise/promise.js b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/lib/es6-promise/promise.js
new file mode 100644
index 0000000..5e865a7
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/lib/es6-promise/promise.js
@@ -0,0 +1,409 @@
+import {
+  isFunction,
+  now
+} from './utils';
+
+import {
+  noop,
+  subscribe,
+  initializePromise,
+  invokeCallback,
+  FULFILLED,
+  REJECTED
+} from './-internal';
+
+import asap from './asap';
+
+import all from './promise/all';
+import race from './promise/race';
+import Resolve from './promise/resolve';
+import Reject from './promise/reject';
+
+var counter = 0;
+
+function needsResolver() {
+  throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
+}
+
+function needsNew() {
+  throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");
+}
+
+export default Promise;
+/**
+  Promise objects represent the eventual result of an asynchronous operation. The
+  primary way of interacting with a promise is through its `then` method, which
+  registers callbacks to receive either a promise’s eventual value or the reason
+  why the promise cannot be fulfilled.
+
+  Terminology
+  -----------
+
+  - `promise` is an object or function with a `then` method whose behavior conforms to this specification.
+  - `thenable` is an object or function that defines a `then` method.
+  - `value` is any legal JavaScript value (including undefined, a thenable, or a promise).
+  - `exception` is a value that is thrown using the throw statement.
+  - `reason` is a value that indicates why a promise was rejected.
+  - `settled` the final resting state of a promise, fulfilled or rejected.
+
+  A promise can be in one of three states: pending, fulfilled, or rejected.
+
+  Promises that are fulfilled have a fulfillment value and are in the fulfilled
+  state.  Promises that are rejected have a rejection reason and are in the
+  rejected state.  A fulfillment value is never a thenable.
+
+  Promises can also be said to *resolve* a value.  If this value is also a
+  promise, then the original promise's settled state will match the value's
+  settled state.  So a promise that *resolves* a promise that rejects will
+  itself reject, and a promise that *resolves* a promise that fulfills will
+  itself fulfill.
+
+
+  Basic Usage:
+  ------------
+
+  ```js
+  var promise = new Promise(function(resolve, reject) {
+    // on success
+    resolve(value);
+
+    // on failure
+    reject(reason);
+  });
+
+  promise.then(function(value) {
+    // on fulfillment
+  }, function(reason) {
+    // on rejection
+  });
+  ```
+
+  Advanced Usage:
+  ---------------
+
+  Promises shine when abstracting away asynchronous interactions such as
+  `XMLHttpRequest`s.
+
+  ```js
+  function getJSON(url) {
+    return new Promise(function(resolve, reject){
+      var xhr = new XMLHttpRequest();
+
+      xhr.open('GET', url);
+      xhr.onreadystatechange = handler;
+      xhr.responseType = 'json';
+      xhr.setRequestHeader('Accept', 'application/json');
+      xhr.send();
+
+      function handler() {
+        if (this.readyState === this.DONE) {
+          if (this.status === 200) {
+            resolve(this.response);
+          } else {
+            reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']'));
+          }
+        }
+      };
+    });
+  }
+
+  getJSON('/posts.json').then(function(json) {
+    // on fulfillment
+  }, function(reason) {
+    // on rejection
+  });
+  ```
+
+  Unlike callbacks, promises are great composable primitives.
+
+  ```js
+  Promise.all([
+    getJSON('/posts'),
+    getJSON('/comments')
+  ]).then(function(values){
+    values[0] // => postsJSON
+    values[1] // => commentsJSON
+
+    return values;
+  });
+  ```
+
+  @class Promise
+  @param {function} resolver
+  Useful for tooling.
+  @constructor
+*/
+function Promise(resolver) {
+  this._id = counter++;
+  this._state = undefined;
+  this._result = undefined;
+  this._subscribers = [];
+
+  if (noop !== resolver) {
+    if (!isFunction(resolver)) {
+      needsResolver();
+    }
+
+    if (!(this instanceof Promise)) {
+      needsNew();
+    }
+
+    initializePromise(this, resolver);
+  }
+}
+
+Promise.all = all;
+Promise.race = race;
+Promise.resolve = Resolve;
+Promise.reject = Reject;
+
+Promise.prototype = {
+  constructor: Promise,
+
+/**
+  The primary way of interacting with a promise is through its `then` method,
+  which registers callbacks to receive either a promise's eventual value or the
+  reason why the promise cannot be fulfilled.
+
+  ```js
+  findUser().then(function(user){
+    // user is available
+  }, function(reason){
+    // user is unavailable, and you are given the reason why
+  });
+  ```
+
+  Chaining
+  --------
+
+  The return value of `then` is itself a promise.  This second, 'downstream'
+  promise is resolved with the return value of the first promise's fulfillment
+  or rejection handler, or rejected if the handler throws an exception.
+
+  ```js
+  findUser().then(function (user) {
+    return user.name;
+  }, function (reason) {
+    return 'default name';
+  }).then(function (userName) {
+    // If `findUser` fulfilled, `userName` will be the user's name, otherwise it
+    // will be `'default name'`
+  });
+
+  findUser().then(function (user) {
+    throw new Error('Found user, but still unhappy');
+  }, function (reason) {
+    throw new Error('`findUser` rejected and we're unhappy');
+  }).then(function (value) {
+    // never reached
+  }, function (reason) {
+    // if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'.
+    // If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'.
+  });
+  ```
+  If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream.
+
+  ```js
+  findUser().then(function (user) {
+    throw new PedagogicalException('Upstream error');
+  }).then(function (value) {
+    // never reached
+  }).then(function (value) {
+    // never reached
+  }, function (reason) {
+    // The `PedgagocialException` is propagated all the way down to here
+  });
+  ```
+
+  Assimilation
+  ------------
+
+  Sometimes the value you want to propagate to a downstream promise can only be
+  retrieved asynchronously. This can be achieved by returning a promise in the
+  fulfillment or rejection handler. The downstream promise will then be pending
+  until the returned promise is settled. This is called *assimilation*.
+
+  ```js
+  findUser().then(function (user) {
+    return findCommentsByAuthor(user);
+  }).then(function (comments) {
+    // The user's comments are now available
+  });
+  ```
+
+  If the assimliated promise rejects, then the downstream promise will also reject.
+
+  ```js
+  findUser().then(function (user) {
+    return findCommentsByAuthor(user);
+  }).then(function (comments) {
+    // If `findCommentsByAuthor` fulfills, we'll have the value here
+  }, function (reason) {
+    // If `findCommentsByAuthor` rejects, we'll have the reason here
+  });
+  ```
+
+  Simple Example
+  --------------
+
+  Synchronous Example
+
+  ```javascript
+  var result;
+
+  try {
+    result = findResult();
+    // success
+  } catch(reason) {
+    // failure
+  }
+  ```
+
+  Errback Example
+
+  ```js
+  findResult(function(result, err){
+    if (err) {
+      // failure
+    } else {
+      // success
+    }
+  });
+  ```
+
+  Promise Example;
+
+  ```javascript
+  findResult().then(function(result){
+    // success
+  }, function(reason){
+    // failure
+  });
+  ```
+
+  Advanced Example
+  --------------
+
+  Synchronous Example
+
+  ```javascript
+  var author, books;
+
+  try {
+    author = findAuthor();
+    books  = findBooksByAuthor(author);
+    // success
+  } catch(reason) {
+    // failure
+  }
+  ```
+
+  Errback Example
+
+  ```js
+
+  function foundBooks(books) {
+
+  }
+
+  function failure(reason) {
+
+  }
+
+  findAuthor(function(author, err){
+    if (err) {
+      failure(err);
+      // failure
+    } else {
+      try {
+        findBoooksByAuthor(author, function(books, err) {
+          if (err) {
+            failure(err);
+          } else {
+            try {
+              foundBooks(books);
+            } catch(reason) {
+              failure(reason);
+            }
+          }
+        });
+      } catch(error) {
+        failure(err);
+      }
+      // success
+    }
+  });
+  ```
+
+  Promise Example;
+
+  ```javascript
+  findAuthor().
+    then(findBooksByAuthor).
+    then(function(books){
+      // found books
+  }).catch(function(reason){
+    // something went wrong
+  });
+  ```
+
+  @method then
+  @param {Function} onFulfilled
+  @param {Function} onRejected
+  Useful for tooling.
+  @return {Promise}
+*/
+  then: function(onFulfillment, onRejection) {
+    var parent = this;
+    var state = parent._state;
+
+    if (state === FULFILLED && !onFulfillment || state === REJECTED && !onRejection) {
+      return this;
+    }
+
+    var child = new this.constructor(noop);
+    var result = parent._result;
+
+    if (state) {
+      var callback = arguments[state - 1];
+      asap(function(){
+        invokeCallback(state, child, callback, result);
+      });
+    } else {
+      subscribe(parent, child, onFulfillment, onRejection);
+    }
+
+    return child;
+  },
+
+/**
+  `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same
+  as the catch block of a try/catch statement.
+
+  ```js
+  function findAuthor(){
+    throw new Error('couldn't find that author');
+  }
+
+  // synchronous
+  try {
+    findAuthor();
+  } catch(reason) {
+    // something went wrong
+  }
+
+  // async with promises
+  findAuthor().catch(function(reason){
+    // something went wrong
+  });
+  ```
+
+  @method catch
+  @param {Function} onRejection
+  Useful for tooling.
+  @return {Promise}
+*/
+  'catch': function(onRejection) {
+    return this.then(null, onRejection);
+  }
+};
diff --git a/node_modules/node-firefox-find-simulators/node_modules/es6-promise/lib/es6-promise/promise/all.js b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/lib/es6-promise/promise/all.js
new file mode 100644
index 0000000..8db7e71
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/lib/es6-promise/promise/all.js
@@ -0,0 +1,52 @@
+import Enumerator from '../enumerator';
+
+/**
+  `Promise.all` accepts an array of promises, and returns a new promise which
+  is fulfilled with an array of fulfillment values for the passed promises, or
+  rejected with the reason of the first passed promise to be rejected. It casts all
+  elements of the passed iterable to promises as it runs this algorithm.
+
+  Example:
+
+  ```javascript
+  var promise1 = resolve(1);
+  var promise2 = resolve(2);
+  var promise3 = resolve(3);
+  var promises = [ promise1, promise2, promise3 ];
+
+  Promise.all(promises).then(function(array){
+    // The array here would be [ 1, 2, 3 ];
+  });
+  ```
+
+  If any of the `promises` given to `all` are rejected, the first promise
+  that is rejected will be given as an argument to the returned promises's
+  rejection handler. For example:
+
+  Example:
+
+  ```javascript
+  var promise1 = resolve(1);
+  var promise2 = reject(new Error("2"));
+  var promise3 = reject(new Error("3"));
+  var promises = [ promise1, promise2, promise3 ];
+
+  Promise.all(promises).then(function(array){
+    // Code here never runs because there are rejected promises!
+  }, function(error) {
+    // error.message === "2"
+  });
+  ```
+
+  @method all
+  @static
+  @param {Array} entries array of promises
+  @param {String} label optional string for labeling the promise.
+  Useful for tooling.
+  @return {Promise} promise that is fulfilled when all `promises` have been
+  fulfilled, or rejected if any of them become rejected.
+  @static
+*/
+export default function all(entries, label) {
+  return new Enumerator(this, entries, true /* abort on reject */, label).promise;
+}
diff --git a/node_modules/node-firefox-find-simulators/node_modules/es6-promise/lib/es6-promise/promise/race.js b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/lib/es6-promise/promise/race.js
new file mode 100644
index 0000000..7daa28a
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/lib/es6-promise/promise/race.js
@@ -0,0 +1,105 @@
+import {
+  isArray
+} from "../utils";
+
+import {
+  noop,
+  resolve,
+  reject,
+  subscribe,
+  PENDING
+} from '../-internal';
+
+/**
+  `Promise.race` returns a new promise which is settled in the same way as the
+  first passed promise to settle.
+
+  Example:
+
+  ```javascript
+  var promise1 = new Promise(function(resolve, reject){
+    setTimeout(function(){
+      resolve('promise 1');
+    }, 200);
+  });
+
+  var promise2 = new Promise(function(resolve, reject){
+    setTimeout(function(){
+      resolve('promise 2');
+    }, 100);
+  });
+
+  Promise.race([promise1, promise2]).then(function(result){
+    // result === 'promise 2' because it was resolved before promise1
+    // was resolved.
+  });
+  ```
+
+  `Promise.race` is deterministic in that only the state of the first
+  settled promise matters. For example, even if other promises given to the
+  `promises` array argument are resolved, but the first settled promise has
+  become rejected before the other promises became fulfilled, the returned
+  promise will become rejected:
+
+  ```javascript
+  var promise1 = new Promise(function(resolve, reject){
+    setTimeout(function(){
+      resolve('promise 1');
+    }, 200);
+  });
+
+  var promise2 = new Promise(function(resolve, reject){
+    setTimeout(function(){
+      reject(new Error('promise 2'));
+    }, 100);
+  });
+
+  Promise.race([promise1, promise2]).then(function(result){
+    // Code here never runs
+  }, function(reason){
+    // reason.message === 'promise 2' because promise 2 became rejected before
+    // promise 1 became fulfilled
+  });
+  ```
+
+  An example real-world use case is implementing timeouts:
+
+  ```javascript
+  Promise.race([ajax('foo.json'), timeout(5000)])
+  ```
+
+  @method race
+  @static
+  @param {Array} promises array of promises to observe
+  @param {String} label optional string for describing the promise returned.
+  Useful for tooling.
+  @return {Promise} a promise which settles in the same way as the first passed
+  promise to settle.
+*/
+export default function race(entries, label) {
+  /*jshint validthis:true */
+  var Constructor = this;
+
+  var promise = new Constructor(noop, label);
+
+  if (!isArray(entries)) {
+    reject(promise, new TypeError('You must pass an array to race.'));
+    return promise;
+  }
+
+  var length = entries.length;
+
+  function onFulfillment(value) {
+    resolve(promise, value);
+  }
+
+  function onRejection(reason) {
+    reject(promise, reason);
+  }
+
+  for (var i = 0; promise._state === PENDING && i < length; i++) {
+    subscribe(Constructor.resolve(entries[i]), undefined, onFulfillment, onRejection);
+  }
+
+  return promise;
+}
diff --git a/node_modules/node-firefox-find-simulators/node_modules/es6-promise/lib/es6-promise/promise/reject.js b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/lib/es6-promise/promise/reject.js
new file mode 100644
index 0000000..518eff7
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/lib/es6-promise/promise/reject.js
@@ -0,0 +1,47 @@
+import {
+  noop,
+  reject as _reject
+} from '../-internal';
+
+/**
+  `Promise.reject` returns a promise rejected with the passed `reason`.
+  It is shorthand for the following:
+
+  ```javascript
+  var promise = new Promise(function(resolve, reject){
+    reject(new Error('WHOOPS'));
+  });
+
+  promise.then(function(value){
+    // Code here doesn't run because the promise is rejected!
+  }, function(reason){
+    // reason.message === 'WHOOPS'
+  });
+  ```
+
+  Instead of writing the above, your code now simply becomes the following:
+
+  ```javascript
+  var promise = Promise.reject(new Error('WHOOPS'));
+
+  promise.then(function(value){
+    // Code here doesn't run because the promise is rejected!
+  }, function(reason){
+    // reason.message === 'WHOOPS'
+  });
+  ```
+
+  @method reject
+  @static
+  @param {Any} reason value that the returned promise will be rejected with.
+  @param {String} label optional string for identifying the returned promise.
+  Useful for tooling.
+  @return {Promise} a promise rejected with the given `reason`.
+*/
+export default function reject(reason, label) {
+  /*jshint validthis:true */
+  var Constructor = this;
+  var promise = new Constructor(noop, label);
+  _reject(promise, reason);
+  return promise;
+}
diff --git a/node_modules/node-firefox-find-simulators/node_modules/es6-promise/lib/es6-promise/promise/resolve.js b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/lib/es6-promise/promise/resolve.js
new file mode 100644
index 0000000..1b3c533
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/lib/es6-promise/promise/resolve.js
@@ -0,0 +1,49 @@
+import {
+  noop,
+  resolve as _resolve
+} from '../-internal';
+
+/**
+  `Promise.resolve` returns a promise that will become resolved with the
+  passed `value`. It is shorthand for the following:
+
+  ```javascript
+  var promise = new Promise(function(resolve, reject){
+    resolve(1);
+  });
+
+  promise.then(function(value){
+    // value === 1
+  });
+  ```
+
+  Instead of writing the above, your code now simply becomes the following:
+
+  ```javascript
+  var promise = Promise.resolve(1);
+
+  promise.then(function(value){
+    // value === 1
+  });
+  ```
+
+  @method resolve
+  @static
+  @param {Any} value value that the returned promise will be resolved with
+  @param {String} label optional string for identifying the returned promise.
+  Useful for tooling.
+  @return {Promise} a promise that will become fulfilled with the given
+  `value`
+*/
+export default function resolve(object, label) {
+  /*jshint validthis:true */
+  var Constructor = this;
+
+  if (object && typeof object === 'object' && object.constructor === Constructor) {
+    return object;
+  }
+
+  var promise = new Constructor(noop, label);
+  _resolve(promise, object);
+  return promise;
+}
diff --git a/node_modules/node-firefox-find-simulators/node_modules/es6-promise/lib/es6-promise/utils.js b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/lib/es6-promise/utils.js
new file mode 100644
index 0000000..6b49bbf
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/lib/es6-promise/utils.js
@@ -0,0 +1,39 @@
+export function objectOrFunction(x) {
+  return typeof x === 'function' || (typeof x === 'object' && x !== null);
+}
+
+export function isFunction(x) {
+  return typeof x === 'function';
+}
+
+export function isMaybeThenable(x) {
+  return typeof x === 'object' && x !== null;
+}
+
+var _isArray;
+if (!Array.isArray) {
+  _isArray = function (x) {
+    return Object.prototype.toString.call(x) === '[object Array]';
+  };
+} else {
+  _isArray = Array.isArray;
+}
+
+export var isArray = _isArray;
+
+// Date.now is not available in browsers < IE9
+// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now#Compatibility
+export var now = Date.now || function() { return new Date().getTime(); };
+
+function F() { }
+
+export var o_create = (Object.create || function (o) {
+  if (arguments.length > 1) {
+    throw new Error('Second argument not supported');
+  }
+  if (typeof o !== 'object') {
+    throw new TypeError('Argument must be an object');
+  }
+  F.prototype = o;
+  return new F();
+});
diff --git a/node_modules/node-firefox-find-simulators/node_modules/es6-promise/package.json b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/package.json
new file mode 100644
index 0000000..28494ce
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/package.json
@@ -0,0 +1,81 @@
+{
+  "name": "es6-promise",
+  "namespace": "es6-promise",
+  "version": "2.0.1",
+  "description": "A lightweight library that provides tools for organizing asynchronous code",
+  "main": "dist/es6-promise.js",
+  "directories": {
+    "lib": "lib"
+  },
+  "devDependencies": {
+    "bower": "^1.3.9",
+    "brfs": "0.0.8",
+    "broccoli-closure-compiler": "^0.2.0",
+    "broccoli-compile-modules": "git+https://github.com/eventualbuddha/broccoli-compile-modules",
+    "broccoli-concat": "0.0.7",
+    "broccoli-es3-safe-recast": "0.0.8",
+    "broccoli-file-mover": "^0.4.0",
+    "broccoli-jshint": "^0.5.1",
+    "broccoli-merge-trees": "^0.1.4",
+    "broccoli-static-compiler": "^0.1.4",
+    "broccoli-string-replace": "0.0.1",
+    "browserify": "^4.2.0",
+    "ember-cli": "0.0.40",
+    "ember-publisher": "0.0.7",
+    "es6-module-transpiler-amd-formatter": "0.0.1",
+    "express": "^4.5.0",
+    "jshint": "~0.9.1",
+    "mkdirp": "^0.5.0",
+    "mocha": "^1.20.1",
+    "promises-aplus-tests": "git://github.com/stefanpenner/promises-tests.git",
+    "release-it": "0.0.10",
+    "testem": "^0.6.17",
+    "json3": "^3.3.2"
+  },
+  "scripts": {
+    "test": "testem ci -R dot",
+    "test-server": "testem",
+    "lint": "jshint lib",
+    "prepublish": "ember build --environment production",
+    "aplus": "browserify test/main.js",
+    "build-all": "ember build --environment production && browserify ./test/main.js -o tmp/test-bundle.js",
+    "dry-run-release": "ember build --environment production && release-it --dry-run --non-interactive"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/jakearchibald/ES6-Promises.git"
+  },
+  "bugs": {
+    "url": "https://github.com/jakearchibald/ES6-Promises/issues"
+  },
+  "keywords": [
+    "promises",
+    "futures"
+  ],
+  "author": {
+    "name": "Yehuda Katz, Tom Dale, Stefan Penner and contributors",
+    "url": "Conversion to ES6 API by Jake Archibald"
+  },
+  "license": "MIT",
+  "homepage": "https://github.com/jakearchibald/ES6-Promises",
+  "_id": "es6-promise@2.0.1",
+  "dist": {
+    "shasum": "ccc4963e679f0ca9fb187c777b9e583d3c7573c2",
+    "tarball": "http://registry.npmjs.org/es6-promise/-/es6-promise-2.0.1.tgz"
+  },
+  "_from": "es6-promise@^2.0.1",
+  "_npmVersion": "1.3.24",
+  "_npmUser": {
+    "name": "jaffathecake",
+    "email": "jaffathecake@gmail.com"
+  },
+  "maintainers": [
+    {
+      "name": "jaffathecake",
+      "email": "jaffathecake@gmail.com"
+    }
+  ],
+  "_shasum": "ccc4963e679f0ca9fb187c777b9e583d3c7573c2",
+  "_resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-2.0.1.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-find-simulators/node_modules/es6-promise/server/.jshintrc b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/server/.jshintrc
new file mode 100644
index 0000000..c1f2978
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/server/.jshintrc
@@ -0,0 +1,3 @@
+{
+  "node": true
+}
diff --git a/node_modules/node-firefox-find-simulators/node_modules/es6-promise/server/index.js b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/server/index.js
new file mode 100644
index 0000000..8a54d36
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/server/index.js
@@ -0,0 +1,6 @@
+module.exports = function(app) {
+  app.use(require('express').static(__dirname + '/../'));
+  app.get('/', function(req, res) {
+    res.redirect('/test/');
+  })
+};
diff --git a/node_modules/node-firefox-find-simulators/node_modules/es6-promise/testem.json b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/testem.json
new file mode 100644
index 0000000..7494912
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/node_modules/es6-promise/testem.json
@@ -0,0 +1,11 @@
+{
+  "test_page": "test/index.html",
+  "parallel": 5,
+  "before_tests": "npm run build-all",
+  "launchers": {
+    "Mocha": {
+      "command": "./node_modules/.bin/mocha test/main.js"
+    }
+  },
+  "launch_in_ci":  ["PhantomJS", "Mocha"]
+}
diff --git a/node_modules/node-firefox-find-simulators/package.json b/node_modules/node-firefox-find-simulators/package.json
new file mode 100644
index 0000000..3ef7c94
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/package.json
@@ -0,0 +1,86 @@
+{
+  "name": "node-firefox-find-simulators",
+  "version": "1.1.0",
+  "description": "Find installed Firefox OS simulators",
+  "main": "index.js",
+  "dependencies": {
+    "es6-promise": "^2.0.1"
+  },
+  "devDependencies": {
+    "gulp": "^3.8.10",
+    "gulp-jscs": "^1.3.1",
+    "gulp-jshint": "^1.9.0",
+    "gulp-nodeunit": "0.0.5",
+    "jshint-stylish": "^1.0.0",
+    "node-firefox-build-tools": "^0.1.0"
+  },
+  "scripts": {
+    "gulp": "gulp",
+    "test": "gulp test"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/mozilla/node-firefox-find-simulators.git"
+  },
+  "keywords": [
+    "firefox",
+    "developer tools",
+    "b2g",
+    "firefox os",
+    "firefoxos",
+    "fxos",
+    "simulators"
+  ],
+  "author": {
+    "name": "Mozilla",
+    "url": "https://mozilla.org/"
+  },
+  "contributors": [
+    {
+      "name": "Nicola Greco",
+      "email": "me@nicola.io",
+      "url": "http://nicolagreco.com/"
+    },
+    {
+      "name": "Soledad Penadés",
+      "email": "listas@soledadpenades.com",
+      "url": "http://soledadpenades.com/"
+    }
+  ],
+  "license": "Apache 2.0",
+  "bugs": {
+    "url": "https://github.com/mozilla/node-firefox-find-simulators/issues"
+  },
+  "homepage": "https://github.com/mozilla/node-firefox-find-simulators",
+  "gitHead": "edefa2cf374e54375d66e23610ff74d486dcb7b3",
+  "_id": "node-firefox-find-simulators@1.1.0",
+  "_shasum": "a9938c2ff9e912b6fcc4cc79c018165ec808577f",
+  "_from": "node-firefox-find-simulators@",
+  "_npmVersion": "2.1.4",
+  "_nodeVersion": "0.10.31",
+  "_npmUser": {
+    "name": "brittanystoroz",
+    "email": "brittanystoroz@gmail.com"
+  },
+  "maintainers": [
+    {
+      "name": "sole",
+      "email": "listas@soledadpenades.com"
+    },
+    {
+      "name": "brittanystoroz",
+      "email": "brittanystoroz@gmail.com"
+    },
+    {
+      "name": "tofumatt",
+      "email": "matt@lonelyvegan.com"
+    }
+  ],
+  "dist": {
+    "shasum": "a9938c2ff9e912b6fcc4cc79c018165ec808577f",
+    "tarball": "http://registry.npmjs.org/node-firefox-find-simulators/-/node-firefox-find-simulators-1.1.0.tgz"
+  },
+  "directories": {},
+  "_resolved": "https://registry.npmjs.org/node-firefox-find-simulators/-/node-firefox-find-simulators-1.1.0.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-find-simulators/tests/unit/data/darwin/Library/Application Support/Firefox/Profiles/asdflulz.Release/extensions/fxos_1_3_simulator@mozilla.org/resources/fxos_1_3_simulator/data/mac64/B2G.app/Contents/MacOS/b2g-bin b/node_modules/node-firefox-find-simulators/tests/unit/data/darwin/Library/Application Support/Firefox/Profiles/asdflulz.Release/extensions/fxos_1_3_simulator@mozilla.org/resources/fxos_1_3_simulator/data/mac64/B2G.app/Contents/MacOS/b2g-bin
new file mode 100644
index 0000000..b66ecf6
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/tests/unit/data/darwin/Library/Application Support/Firefox/Profiles/asdflulz.Release/extensions/fxos_1_3_simulator@mozilla.org/resources/fxos_1_3_simulator/data/mac64/B2G.app/Contents/MacOS/b2g-bin
@@ -0,0 +1 @@
+Shh, I am pretending to be a simulator.
diff --git a/node_modules/node-firefox-find-simulators/tests/unit/data/darwin/Library/Application Support/Firefox/Profiles/asdflulz.Release/extensions/fxos_2_0_simulator@mozilla.org/b2g/B2G.app/Contents/MacOS/b2g-bin b/node_modules/node-firefox-find-simulators/tests/unit/data/darwin/Library/Application Support/Firefox/Profiles/asdflulz.Release/extensions/fxos_2_0_simulator@mozilla.org/b2g/B2G.app/Contents/MacOS/b2g-bin
new file mode 100644
index 0000000..b66ecf6
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/tests/unit/data/darwin/Library/Application Support/Firefox/Profiles/asdflulz.Release/extensions/fxos_2_0_simulator@mozilla.org/b2g/B2G.app/Contents/MacOS/b2g-bin
@@ -0,0 +1 @@
+Shh, I am pretending to be a simulator.
diff --git a/node_modules/node-firefox-find-simulators/tests/unit/data/darwin/Library/Application Support/Firefox/Profiles/omgbbqya.dev-edition-default/extensions/fxos_1_4_simulator@mozilla.org/b2g/B2G.app/Contents/MacOS/b2g-bin b/node_modules/node-firefox-find-simulators/tests/unit/data/darwin/Library/Application Support/Firefox/Profiles/omgbbqya.dev-edition-default/extensions/fxos_1_4_simulator@mozilla.org/b2g/B2G.app/Contents/MacOS/b2g-bin
new file mode 100644
index 0000000..b66ecf6
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/tests/unit/data/darwin/Library/Application Support/Firefox/Profiles/omgbbqya.dev-edition-default/extensions/fxos_1_4_simulator@mozilla.org/b2g/B2G.app/Contents/MacOS/b2g-bin
@@ -0,0 +1 @@
+Shh, I am pretending to be a simulator.
diff --git a/node_modules/node-firefox-find-simulators/tests/unit/data/darwin/Library/Application Support/Firefox/Profiles/omgbbqya.dev-edition-default/extensions/fxos_2_1_simulator@mozilla.org/b2g/B2G.app/Contents/MacOS/b2g-bin b/node_modules/node-firefox-find-simulators/tests/unit/data/darwin/Library/Application Support/Firefox/Profiles/omgbbqya.dev-edition-default/extensions/fxos_2_1_simulator@mozilla.org/b2g/B2G.app/Contents/MacOS/b2g-bin
new file mode 100644
index 0000000..b66ecf6
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/tests/unit/data/darwin/Library/Application Support/Firefox/Profiles/omgbbqya.dev-edition-default/extensions/fxos_2_1_simulator@mozilla.org/b2g/B2G.app/Contents/MacOS/b2g-bin
@@ -0,0 +1 @@
+Shh, I am pretending to be a simulator.
diff --git a/node_modules/node-firefox-find-simulators/tests/unit/data/darwin/Library/Application Support/Firefox/Profiles/ou812wow.Nightly/extensions/fxos_1_5_simulator@mozilla.org/b2g/B2G.app/Contents/MacOS/b2g-bin b/node_modules/node-firefox-find-simulators/tests/unit/data/darwin/Library/Application Support/Firefox/Profiles/ou812wow.Nightly/extensions/fxos_1_5_simulator@mozilla.org/b2g/B2G.app/Contents/MacOS/b2g-bin
new file mode 100644
index 0000000..b66ecf6
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/tests/unit/data/darwin/Library/Application Support/Firefox/Profiles/ou812wow.Nightly/extensions/fxos_1_5_simulator@mozilla.org/b2g/B2G.app/Contents/MacOS/b2g-bin
@@ -0,0 +1 @@
+Shh, I am pretending to be a simulator.
diff --git a/node_modules/node-firefox-find-simulators/tests/unit/data/darwin/Library/Application Support/Firefox/Profiles/ou812wow.Nightly/extensions/fxos_2_2_simulator@mozilla.org/b2g/B2G.app/Contents/MacOS/b2g-bin b/node_modules/node-firefox-find-simulators/tests/unit/data/darwin/Library/Application Support/Firefox/Profiles/ou812wow.Nightly/extensions/fxos_2_2_simulator@mozilla.org/b2g/B2G.app/Contents/MacOS/b2g-bin
new file mode 100644
index 0000000..b66ecf6
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/tests/unit/data/darwin/Library/Application Support/Firefox/Profiles/ou812wow.Nightly/extensions/fxos_2_2_simulator@mozilla.org/b2g/B2G.app/Contents/MacOS/b2g-bin
@@ -0,0 +1 @@
+Shh, I am pretending to be a simulator.
diff --git a/node_modules/node-firefox-find-simulators/tests/unit/data/linux/.mozilla/firefox/asdflulz.Release/extensions/fxos_1_3_simulator@mozilla.org/resources/fxos_1_3_simulator/data/linux/b2g/b2g-bin b/node_modules/node-firefox-find-simulators/tests/unit/data/linux/.mozilla/firefox/asdflulz.Release/extensions/fxos_1_3_simulator@mozilla.org/resources/fxos_1_3_simulator/data/linux/b2g/b2g-bin
new file mode 100644
index 0000000..b66ecf6
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/tests/unit/data/linux/.mozilla/firefox/asdflulz.Release/extensions/fxos_1_3_simulator@mozilla.org/resources/fxos_1_3_simulator/data/linux/b2g/b2g-bin
@@ -0,0 +1 @@
+Shh, I am pretending to be a simulator.
diff --git a/node_modules/node-firefox-find-simulators/tests/unit/data/linux/.mozilla/firefox/asdflulz.Release/extensions/fxos_1_3_simulator@mozilla.org/resources/fxos_1_3_simulator/data/linux64/b2g/b2g-bin b/node_modules/node-firefox-find-simulators/tests/unit/data/linux/.mozilla/firefox/asdflulz.Release/extensions/fxos_1_3_simulator@mozilla.org/resources/fxos_1_3_simulator/data/linux64/b2g/b2g-bin
new file mode 100644
index 0000000..b66ecf6
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/tests/unit/data/linux/.mozilla/firefox/asdflulz.Release/extensions/fxos_1_3_simulator@mozilla.org/resources/fxos_1_3_simulator/data/linux64/b2g/b2g-bin
@@ -0,0 +1 @@
+Shh, I am pretending to be a simulator.
diff --git a/node_modules/node-firefox-find-simulators/tests/unit/data/linux/.mozilla/firefox/asdflulz.Release/extensions/fxos_2_0_simulator@mozilla.org/b2g/b2g-bin b/node_modules/node-firefox-find-simulators/tests/unit/data/linux/.mozilla/firefox/asdflulz.Release/extensions/fxos_2_0_simulator@mozilla.org/b2g/b2g-bin
new file mode 100644
index 0000000..b66ecf6
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/tests/unit/data/linux/.mozilla/firefox/asdflulz.Release/extensions/fxos_2_0_simulator@mozilla.org/b2g/b2g-bin
@@ -0,0 +1 @@
+Shh, I am pretending to be a simulator.
diff --git a/node_modules/node-firefox-find-simulators/tests/unit/data/linux/.mozilla/firefox/omgbbqya.dev-edition-default/extensions/fxos_1_4_simulator@mozilla.org/b2g/b2g-bin b/node_modules/node-firefox-find-simulators/tests/unit/data/linux/.mozilla/firefox/omgbbqya.dev-edition-default/extensions/fxos_1_4_simulator@mozilla.org/b2g/b2g-bin
new file mode 100644
index 0000000..b66ecf6
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/tests/unit/data/linux/.mozilla/firefox/omgbbqya.dev-edition-default/extensions/fxos_1_4_simulator@mozilla.org/b2g/b2g-bin
@@ -0,0 +1 @@
+Shh, I am pretending to be a simulator.
diff --git a/node_modules/node-firefox-find-simulators/tests/unit/data/linux/.mozilla/firefox/omgbbqya.dev-edition-default/extensions/fxos_2_1_simulator@mozilla.org/b2g/b2g-bin b/node_modules/node-firefox-find-simulators/tests/unit/data/linux/.mozilla/firefox/omgbbqya.dev-edition-default/extensions/fxos_2_1_simulator@mozilla.org/b2g/b2g-bin
new file mode 100644
index 0000000..b66ecf6
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/tests/unit/data/linux/.mozilla/firefox/omgbbqya.dev-edition-default/extensions/fxos_2_1_simulator@mozilla.org/b2g/b2g-bin
@@ -0,0 +1 @@
+Shh, I am pretending to be a simulator.
diff --git a/node_modules/node-firefox-find-simulators/tests/unit/data/linux/.mozilla/firefox/ou812wow.Nightly/extensions/fxos_1_5_simulator@mozilla.org/b2g/b2g-bin b/node_modules/node-firefox-find-simulators/tests/unit/data/linux/.mozilla/firefox/ou812wow.Nightly/extensions/fxos_1_5_simulator@mozilla.org/b2g/b2g-bin
new file mode 100644
index 0000000..b66ecf6
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/tests/unit/data/linux/.mozilla/firefox/ou812wow.Nightly/extensions/fxos_1_5_simulator@mozilla.org/b2g/b2g-bin
@@ -0,0 +1 @@
+Shh, I am pretending to be a simulator.
diff --git a/node_modules/node-firefox-find-simulators/tests/unit/data/linux/.mozilla/firefox/ou812wow.Nightly/extensions/fxos_2_2_simulator@mozilla.org/b2g/b2g-bin b/node_modules/node-firefox-find-simulators/tests/unit/data/linux/.mozilla/firefox/ou812wow.Nightly/extensions/fxos_2_2_simulator@mozilla.org/b2g/b2g-bin
new file mode 100644
index 0000000..b66ecf6
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/tests/unit/data/linux/.mozilla/firefox/ou812wow.Nightly/extensions/fxos_2_2_simulator@mozilla.org/b2g/b2g-bin
@@ -0,0 +1 @@
+Shh, I am pretending to be a simulator.
diff --git a/node_modules/node-firefox-find-simulators/tests/unit/data/win32/AppData/Roaming/Mozilla/Firefox/Profiles/asdflulz.Release/extensions/fxos_1_3_simulator@mozilla.org/b2g/b2g-bin.exe b/node_modules/node-firefox-find-simulators/tests/unit/data/win32/AppData/Roaming/Mozilla/Firefox/Profiles/asdflulz.Release/extensions/fxos_1_3_simulator@mozilla.org/b2g/b2g-bin.exe
new file mode 100644
index 0000000..b66ecf6
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/tests/unit/data/win32/AppData/Roaming/Mozilla/Firefox/Profiles/asdflulz.Release/extensions/fxos_1_3_simulator@mozilla.org/b2g/b2g-bin.exe
@@ -0,0 +1 @@
+Shh, I am pretending to be a simulator.
diff --git a/node_modules/node-firefox-find-simulators/tests/unit/data/win32/AppData/Roaming/Mozilla/Firefox/Profiles/asdflulz.Release/extensions/fxos_2_0_simulator@mozilla.org/b2g/b2g-bin.exe b/node_modules/node-firefox-find-simulators/tests/unit/data/win32/AppData/Roaming/Mozilla/Firefox/Profiles/asdflulz.Release/extensions/fxos_2_0_simulator@mozilla.org/b2g/b2g-bin.exe
new file mode 100644
index 0000000..b66ecf6
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/tests/unit/data/win32/AppData/Roaming/Mozilla/Firefox/Profiles/asdflulz.Release/extensions/fxos_2_0_simulator@mozilla.org/b2g/b2g-bin.exe
@@ -0,0 +1 @@
+Shh, I am pretending to be a simulator.
diff --git a/node_modules/node-firefox-find-simulators/tests/unit/data/win32/AppData/Roaming/Mozilla/Firefox/Profiles/omgbbqya.dev-edition-default/extensions/fxos_1_4_simulator@mozilla.org/b2g/b2g-bin.exe b/node_modules/node-firefox-find-simulators/tests/unit/data/win32/AppData/Roaming/Mozilla/Firefox/Profiles/omgbbqya.dev-edition-default/extensions/fxos_1_4_simulator@mozilla.org/b2g/b2g-bin.exe
new file mode 100644
index 0000000..b66ecf6
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/tests/unit/data/win32/AppData/Roaming/Mozilla/Firefox/Profiles/omgbbqya.dev-edition-default/extensions/fxos_1_4_simulator@mozilla.org/b2g/b2g-bin.exe
@@ -0,0 +1 @@
+Shh, I am pretending to be a simulator.
diff --git a/node_modules/node-firefox-find-simulators/tests/unit/data/win32/AppData/Roaming/Mozilla/Firefox/Profiles/omgbbqya.dev-edition-default/extensions/fxos_2_1_simulator@mozilla.org/b2g/b2g-bin.exe b/node_modules/node-firefox-find-simulators/tests/unit/data/win32/AppData/Roaming/Mozilla/Firefox/Profiles/omgbbqya.dev-edition-default/extensions/fxos_2_1_simulator@mozilla.org/b2g/b2g-bin.exe
new file mode 100644
index 0000000..b66ecf6
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/tests/unit/data/win32/AppData/Roaming/Mozilla/Firefox/Profiles/omgbbqya.dev-edition-default/extensions/fxos_2_1_simulator@mozilla.org/b2g/b2g-bin.exe
@@ -0,0 +1 @@
+Shh, I am pretending to be a simulator.
diff --git a/node_modules/node-firefox-find-simulators/tests/unit/data/win32/AppData/Roaming/Mozilla/Firefox/Profiles/ou812wow.Nightly/extensions/fxos_1_5_simulator@mozilla.org/b2g/b2g-bin.exe b/node_modules/node-firefox-find-simulators/tests/unit/data/win32/AppData/Roaming/Mozilla/Firefox/Profiles/ou812wow.Nightly/extensions/fxos_1_5_simulator@mozilla.org/b2g/b2g-bin.exe
new file mode 100644
index 0000000..b66ecf6
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/tests/unit/data/win32/AppData/Roaming/Mozilla/Firefox/Profiles/ou812wow.Nightly/extensions/fxos_1_5_simulator@mozilla.org/b2g/b2g-bin.exe
@@ -0,0 +1 @@
+Shh, I am pretending to be a simulator.
diff --git a/node_modules/node-firefox-find-simulators/tests/unit/data/win32/AppData/Roaming/Mozilla/Firefox/Profiles/ou812wow.Nightly/extensions/fxos_2_2_simulator@mozilla.org/b2g/b2g-bin.exe b/node_modules/node-firefox-find-simulators/tests/unit/data/win32/AppData/Roaming/Mozilla/Firefox/Profiles/ou812wow.Nightly/extensions/fxos_2_2_simulator@mozilla.org/b2g/b2g-bin.exe
new file mode 100644
index 0000000..b66ecf6
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/tests/unit/data/win32/AppData/Roaming/Mozilla/Firefox/Profiles/ou812wow.Nightly/extensions/fxos_2_2_simulator@mozilla.org/b2g/b2g-bin.exe
@@ -0,0 +1 @@
+Shh, I am pretending to be a simulator.
diff --git a/node_modules/node-firefox-find-simulators/tests/unit/test.index.js b/node_modules/node-firefox-find-simulators/tests/unit/test.index.js
new file mode 100644
index 0000000..87428b2
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/tests/unit/test.index.js
@@ -0,0 +1,71 @@
+'use strict';
+
+var path = require('path');
+
+var home = path.join(__dirname, 'data', process.platform);
+process.env.HOME = home;
+process.env.USERPROFILE = home;
+
+var findSimulators = require('../../index');
+var platform = require('../../lib/platform');
+
+module.exports = {
+
+  'findSimulators works on current platform': function(test) {
+    var currentPlatform = platform(process.platform);
+    var vars = {
+      PROFILEDIR: currentPlatform.firefoxProfilesDir,
+      BINARY: currentPlatform.simulatorBinary
+    };
+
+    var expected = [
+      { version: '1.3',
+        bin: '%PROFILEDIR%/asdflulz.Release/extensions/fxos_1_3_simulator@mozilla.org/%BINARY%',
+        profile: '%PROFILEDIR%/asdflulz.Release/extensions/fxos_1_3_simulator@mozilla.org/profile' },
+      { version: '2.0',
+        bin: '%PROFILEDIR%/asdflulz.Release/extensions/fxos_2_0_simulator@mozilla.org/%BINARY%',
+        profile: '%PROFILEDIR%/asdflulz.Release/extensions/fxos_2_0_simulator@mozilla.org/profile' },
+      { version: '1.4',
+        bin: '%PROFILEDIR%/omgbbqya.dev-edition-default/extensions/fxos_1_4_simulator@mozilla.org/%BINARY%',
+        profile: '%PROFILEDIR%/omgbbqya.dev-edition-default/extensions/fxos_1_4_simulator@mozilla.org/profile' },
+      { version: '2.1',
+        bin: '%PROFILEDIR%/omgbbqya.dev-edition-default/extensions/fxos_2_1_simulator@mozilla.org/%BINARY%',
+        profile: '%PROFILEDIR%/omgbbqya.dev-edition-default/extensions/fxos_2_1_simulator@mozilla.org/profile' },
+      { version: '1.5',
+        bin: '%PROFILEDIR%/ou812wow.Nightly/extensions/fxos_1_5_simulator@mozilla.org/%BINARY%',
+        profile: '%PROFILEDIR%/ou812wow.Nightly/extensions/fxos_1_5_simulator@mozilla.org/profile' },
+      { version: '2.2',
+        bin: '%PROFILEDIR%/ou812wow.Nightly/extensions/fxos_2_2_simulator@mozilla.org/%BINARY%',
+        profile: '%PROFILEDIR%/ou812wow.Nightly/extensions/fxos_2_2_simulator@mozilla.org/profile' }
+    ];
+
+    // Replace platform-specific placeholders in the expected data
+    for (var expectedIndex = 0; expectedIndex < expected.length; expectedIndex++) {
+      var item = expected[expectedIndex];
+      for (var itemKey in item) {
+        for (var varKey in vars) {
+          var computedValue;
+
+          if (varKey === 'BINARY') {
+            computedValue = vars.BINARY.call(item, item.version.replace('.', '_'), process.arch);
+          } else {
+            computedValue = vars[varKey];
+          }
+
+          item[itemKey] = item[itemKey].replace('%' + varKey + '%', computedValue);
+        }
+        if (process.platform === 'win32') {
+          // HACK: Correct expected path when running tests on win32
+          item[itemKey] = item[itemKey].replace(/\//g,'\\');
+        }
+      }
+    }
+
+    findSimulators().then(function(result) {
+      // Ensure all the expected simulators are found in the result
+      test.deepEqual(result, expected);
+      test.done();
+    });
+  }
+
+};
diff --git a/node_modules/node-firefox-find-simulators/tests/unit/test.platform.js b/node_modules/node-firefox-find-simulators/tests/unit/test.platform.js
new file mode 100644
index 0000000..e2a1105
--- /dev/null
+++ b/node_modules/node-firefox-find-simulators/tests/unit/test.platform.js
@@ -0,0 +1,54 @@
+'use strict';
+
+var platform = require('../../lib/platform');
+
+module.exports = {
+
+  setUp: function(next) {
+    this.oldHOME = process.env.HOME;
+    this.oldUSERPROFILE = process.env.USERPROFILE;
+    process.env.HOME = '/home/testuser';
+    process.env.USERPROFILE = 'C:\\Users\\testuser';
+    next();
+  },
+
+  tearDown: function(next) {
+    process.env.HOME = this.oldHOME;
+    process.env.USERPROFILE = this.oldUSERPROFILE;
+    next();
+  },
+
+  'darwin': function(test) {
+    testPlatformPaths(test, platform('darwin'), {
+      firefoxProfilesDir: '/home/testuser/Library/Application Support/Firefox/Profiles'
+    });
+    test.done();
+  },
+
+  'linux': function(test) {
+    testPlatformPaths(test, platform('linux'), {
+      firefoxProfilesDir: '/home/testuser/.mozilla/firefox'
+    });
+    test.done();
+  },
+
+  'win32': function(test) {
+    testPlatformPaths(test, platform('win32'), {
+      // HACK: On posix platforms, path.join results in this
+      firefoxProfilesDir: 'C:\\Users\\testuser/AppData\\Roaming\\Mozilla\\Firefox\\Profiles'
+    });
+    test.done();
+  }
+
+};
+
+function testPlatformPaths(test, result, expected) {
+  test.expect(2);
+  if (process.platform === 'win32') {
+    // HACK: Correct expected path when running tests on win32
+    expected.firefoxProfilesDir = expected.firefoxProfilesDir.replace(/\//g,'\\');
+  }
+
+  test.ok(result.simulatorBinary && typeof result.simulatorBinary === 'function');
+  test.equal(result.firefoxProfilesDir, expected.firefoxProfilesDir);
+}
diff --git a/node_modules/node-firefox-forward-ports/.npmignore b/node_modules/node-firefox-forward-ports/.npmignore
new file mode 100644
index 0000000..ae7e1e3
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/.npmignore
@@ -0,0 +1,4 @@
+node_modules
+npm-debug.log
+.DS_Store
+*.sw?
diff --git a/node_modules/node-firefox-forward-ports/.travis.yml b/node_modules/node-firefox-forward-ports/.travis.yml
new file mode 100644
index 0000000..0394ba7
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/.travis.yml
@@ -0,0 +1,3 @@
+language: node_js
+node_js:
+- 0.10
diff --git a/node_modules/node-firefox-forward-ports/LICENSE b/node_modules/node-firefox-forward-ports/LICENSE
new file mode 100644
index 0000000..a7c6b5e
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/LICENSE
@@ -0,0 +1,13 @@
+Copyright 2015 Mozilla
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/node_modules/node-firefox-forward-ports/README.md b/node_modules/node-firefox-forward-ports/README.md
new file mode 100644
index 0000000..7ee040d
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/README.md
@@ -0,0 +1,144 @@
+# node-firefox-forward-ports [![Build Status](https://secure.travis-ci.org/mozilla/node-firefox-forward-ports.png?branch=master)](http://travis-ci.org/mozilla/node-firefox-forward-ports)
+
+> Forward local ports to remote debugging interfaces on connected Firefox OS devices
+
+[![Install with NPM](https://nodei.co/npm/node-firefox-forward-ports.png?downloads=true&stars=true)](https://nodei.co/npm/node-firefox-forward-ports/)
+
+This is part of the [node-firefox](https://github.com/mozilla/node-firefox) project.
+
+Firefox OS devices offer a [Remote Debugging service](https://wiki.mozilla.org/Remote_Debugging_Protocol) 
+on a local network socket. This can be forwarded to a host computer over USB
+using ADB. This module takes care of that as necessary, reusing forwarded ports
+when possible.
+
+## Installation
+
+### From git
+
+```bash
+git clone https://github.com/mozilla/node-firefox-forward-ports.git
+cd node-firefox-forward-ports
+npm install
+```
+
+If you want to update later on:
+
+```bash
+cd node-firefox-forward-ports
+git pull origin master
+npm install
+```
+
+### npm
+
+```bash
+npm install node-firefox-forward-ports
+```
+
+## Usage
+
+```javascript
+var forwardPorts = require('node-firefox-forward-ports');
+
+// Forwards ports as necessary, while reusing existing ports.
+// Returns the list of devices annotated with details on forwarded ports.
+forwardPorts([
+  {id: '3739ced5'},
+  {id: 'full_keon'},
+  {id: 'full_unagi'}
+]).then(function(results) {
+  console.log(results);
+}).catch(function(err) {
+  console.error(err);
+});
+
+// forwardPorts() can also accept an options object, for future expansion
+// when additional options are supported.
+forwardPorts({
+  devices: [
+    {id: '3739ced5'},
+    {id: 'full_keon'},
+    {id: 'full_unagi'}
+  ]
+}).then(function(results) {
+  console.log(results);
+}).catch(function(err) {
+  console.error(err);
+});
+
+```
+
+## Example
+
+This example uses [node-firefox-find-devices](https://github.com/mozilla/node-firefox-find-devices)
+to first assemble a list of connected Firefox OS devices. The output of
+`findDevices()` is basically what `forwardPorts()` expects for its `devices`
+option.
+
+```javascript
+var findDevices = require('node-firefox-find-devices');
+var forwardPorts = require('node-firefox-forward-ports');
+
+findDevices().then(forwardPorts).then(function(results) {
+  console.log(results);
+});
+```
+
+This example would result in output like the following, which consists of the
+output of `findDevices()` altered to include a list of forward ports for each
+device.
+
+```javascript
+[
+  {
+    "id": "3739ced5",
+    "type": "device",
+    "isFirefoxOS": true,
+    "ports": [
+      {
+        "local": "tcp:8001",
+        "remote": "localfilesystem:/data/local/debugger-socket",
+        "port": "8001"
+      }
+    ]
+  }
+]
+```
+
+The listing of each device returned from `findDevices()` will be annotated with
+a new `ports` property. This is a list of details on each port forwarded to the
+device.
+
+The `local` property contains the local side of the port forward. If this is a
+TCP/IP port, then its numerical port can be found in the `port` property.
+
+The `remote` property contains the remote side of the port forward, useful for
+filtering and selecting particular kinds of forwarded ports. Currently, this
+module only supports Firefox remote debugging sockets - but that could expand
+in the future.
+
+## Running the tests
+
+After installing, you can simply run the following from the module folder:
+
+```bash
+npm test
+```
+
+To add a new unit test file, create a new file in the `tests/unit` folder. Any file that matches `test.*.js` will be run as a test by the appropriate test runner, based on the folder location.
+
+We use `gulp` behind the scenes to run the test; if you don't have it installed globally you can use `npm gulp` from inside the project's root folder to run `gulp`.
+
+### Code quality and style
+
+Because we have multiple contributors working on our projects, we value consistent code styles. It makes it easier to read code written by many people! :-)
+
+Our tests include unit tests as well as code quality ("linting") tests that make sure our test pass a style guide and [JSHint](http://jshint.com/). Instead of submitting code with the wrong indentation or a different style, run the tests and you will be told where your code quality/style differs from ours and instructions on how to fix it.
+
+This program is free software; it is distributed under an
+[Apache License](https://github.com/mozilla/node-firefox-forward-ports/blob/master/LICENSE).
+
+## Copyright
+
+Copyright (c) 2015 [Mozilla](https://mozilla.org)
+([Contributors](https://github.com/mozilla/node-firefox-forward-ports/graphs/contributors)).
diff --git a/node_modules/node-firefox-forward-ports/gulpfile.js b/node_modules/node-firefox-forward-ports/gulpfile.js
new file mode 100644
index 0000000..f5e5ab9
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/gulpfile.js
@@ -0,0 +1,3 @@
+var gulp = require('gulp');
+
+require('node-firefox-build-tools').loadGulpTasks(gulp);
diff --git a/node_modules/node-firefox-forward-ports/index.js b/node_modules/node-firefox-forward-ports/index.js
new file mode 100644
index 0000000..761a82d
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/index.js
@@ -0,0 +1,115 @@
+'use strict';
+
+// See https://github.com/jshint/jshint/issues/1747 for context
+/* global -Promise */
+var Promise = require('es6-promise').Promise;
+var adb = require('adbkit');
+var portfinder = require('portfinder');
+
+var REMOTE_DEBUGGER_SOCKET = 'localfilesystem:/data/local/debugger-socket';
+
+module.exports = forwardPorts;
+
+function forwardPorts(options) {
+
+  var devices;
+
+  if (Array.isArray(options)) {
+    devices = options;
+  } else {
+    options = options || {};
+    devices = options.devices;
+  }
+
+  var adbClient = adb.createClient();
+
+  return new Promise(function(resolve, reject) {
+    if (devices === undefined || !Array.isArray(devices)) {
+      return reject(new Error('an array of devices is required to forward ports'));
+    }
+    return resolve();
+  }).then(function() {
+    return findExistingPortForwards(adbClient, devices);
+  }).then(function(devicesWithPorts) {
+    return forwardDevices(adbClient, devicesWithPorts);
+  });
+}
+
+function findExistingPortForwards(adbClient, devices) {
+  return adbClient.listForwards().then(function(ports) {
+
+    // Index the list of forwarded ports by device ID.
+    var portMap = {};
+    ports.forEach(function(port) {
+
+      // Filter for remote debugging sockets, just in case something else has
+      // been forwarded.
+      if (port.remote !== REMOTE_DEBUGGER_SOCKET) {
+        return;
+      }
+
+      // Add this port to a list associated with the device ID
+      if (!portMap[port.serial]) {
+        portMap[port.serial] = [];
+      }
+      portMap[port.serial].push(port);
+
+    });
+
+    // Annotate the list of devices with associated ports.
+    return devices.map(function(device) {
+      device.ports = portMap[device.id] || [];
+      return device;
+    });
+
+  });
+}
+
+function forwardDevices(adbClient, devices) {
+  return Promise.all(devices.map(function(device) {
+
+    if (device.ports.length > 0) {
+      // Skip forwarding for this device. But, extract port number from the
+      // tcp:{port} format returned by adbClient.listForwards()
+      device.ports.forEach(function(port) {
+        delete port.serial;
+        if (port.local.indexOf('tcp:') === -1) {
+          port.port = null;
+        } else {
+          port.port = port.local.replace('tcp:', '');
+        }
+      });
+      return device;
+    }
+
+    return getPort().then(function(port) {
+      var localAddress = 'tcp:' + port;
+      var remoteAddress = REMOTE_DEBUGGER_SOCKET;
+      return adbClient
+        .forward(device.id, localAddress, remoteAddress)
+        .then(function() {
+          device.ports = [
+            {
+              port: port,
+              local: localAddress,
+              remote: remoteAddress
+            }
+          ];
+          return device;
+        });
+    });
+
+  }));
+}
+
+function getPort() {
+  return new Promise(function(resolve, reject) {
+    portfinder.getPort({
+      // Ensure we're looking at localhost, rather than 0.0.0.0
+      // see: https://github.com/indexzero/node-portfinder/issues/13
+      host: '127.0.0.1'
+    }, function(err, port) {
+      return err ? reject(err) : resolve(port);
+    });
+  });
+}
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/LICENSE b/node_modules/node-firefox-forward-ports/node_modules/adbkit/LICENSE
new file mode 100644
index 0000000..2ffff3d
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/LICENSE
@@ -0,0 +1,13 @@
+Copyright © CyberAgent, Inc. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/README.md b/node_modules/node-firefox-forward-ports/node_modules/adbkit/README.md
new file mode 100644
index 0000000..c8557ce
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/README.md
@@ -0,0 +1,1089 @@
+# adbkit
+
+**adbkit** is a pure [Node.js][nodejs] client for the [Android Debug Bridge][adb-site] server. It can be used either as a library in your own application, or simply as a convenient utility for playing with your device.
+
+Most of the `adb` command line tool's functionality is supported (including pushing/pulling files, installing APKs and processing logs), with some added functionality such as being able to generate touch/key events and take screenshots. Some shims are provided for older devices, but we have not and will not test anything below Android 2.3.
+
+Internally, we use this library to drive a multitude of Android devices from a variety of manufacturers, so we can say with a fairly high degree of confidence that it will most likely work with your device(s), too.
+
+## Requirements
+
+* [Node.js][nodejs] >= 0.10
+* The `adb` command line tool
+
+Please note that although it may happen at some point, **this project is NOT an implementation of the ADB _server_**. The target host (where the devices are connected) must still have ADB installed and either already running (e.g. via `adb start-server`) or available in `$PATH`. An attempt will be made to start the server locally via the aforementioned command if the initial connection fails. This is the only case where we fall back to the `adb` binary.
+
+When targeting a remote host, starting the server is entirely your responsibility.
+
+Alternatively, you may want to consider using the Chrome [ADB][chrome-adb] extension, as it includes the ADB server and can be started/stopped quite easily.
+
+## Getting started
+
+Install via NPM:
+
+```bash
+npm install --save adbkit
+```
+
+We use [debug][node-debug], and our debug namespace is `adb`. Some of the dependencies may provide debug output of their own. To see the debug output, set the `DEBUG` environment variable. For example, run your program with `DEBUG=adb:* node app.js`.
+
+Note that even though the module is written in [CoffeeScript][coffeescript], only the compiled JavaScript is published to [NPM][npm], which means that it can easily be used with pure JavaScript codebases, too.
+
+### Examples
+
+The examples may be a bit verbose, but that's because we're trying to keep them as close to real-life code as possible, with flow control and error handling taken care of.
+
+#### Checking for NFC support
+
+```js
+var Promise = require('bluebird')
+var adb = require('adbkit')
+var client = adb.createClient()
+
+client.listDevices()
+  .then(function(devices) {
+    return Promise.filter(devices, function(device) {
+      return client.getFeatures(device.id)
+        .then(function(features) {
+          return features['android.hardware.nfc']
+        })
+    })
+  })
+  .then(function(supportedDevices) {
+    console.log('The following devices support NFC:', supportedDevices)
+  })
+  .catch(function(err) {
+    console.error('Something went wrong:', err.stack)
+  })
+```
+
+#### Installing an APK
+
+```js
+var Promise = require('bluebird')
+var adb = require('adbkit')
+var client = adb.createClient()
+var apk = 'vendor/app.apk'
+
+client.listDevices()
+  .then(function(devices) {
+    return Promise.map(devices, function(device) {
+      return client.install(device.id, apk)
+    })
+  })
+  .then(function() {
+    console.log('Installed %s on all connected devices', apk)
+  })
+  .catch(function(err) {
+    console.error('Something went wrong:', err.stack)
+  })
+```
+
+#### Tracking devices
+
+```js
+var adb = require('adbkit')
+var client = adb.createClient()
+
+client.trackDevices()
+  .then(function(tracker) {
+    tracker.on('add', function(device) {
+      console.log('Device %s was plugged in', device.id)
+    })
+    tracker.on('remove', function(device) {
+      console.log('Device %s was unplugged', device.id)
+    })
+    tracker.on('end', function() {
+      console.log('Tracking stopped')
+    })
+  })
+  .catch(function(err) {
+    console.error('Something went wrong:', err.stack)
+  })
+```
+
+#### Pulling a file from all connected devices
+
+```js
+var Promise = require('bluebird')
+var fs = require('fs')
+var adb = require('adbkit')
+var client = adb.createClient()
+
+client.listDevices()
+  .then(function(devices) {
+    return Promise.map(devices, function(device) {
+      return client.pull(device.id, '/system/build.prop')
+        .then(function(transfer) {
+          return new Promise(function(resolve, reject) {
+            var fn = '/tmp/' + device.id + '.build.prop'
+            transfer.on('progress', function(stats) {
+              console.log('[%s] Pulled %d bytes so far',
+                device.id,
+                stats.bytesTransferred)
+            })
+            transfer.on('end', function() {
+              console.log('[%s] Pull complete', device.id)
+              resolve(device.id)
+            })
+            transfer.on('error', reject)
+            transfer.pipe(fs.createWriteStream(fn))
+          })
+        })
+    })
+  })
+  .then(function() {
+    console.log('Done pulling /system/build.prop from all connected devices')
+  })
+  .catch(function(err) {
+    console.error('Something went wrong:', err.stack)
+  })
+```
+
+#### Pushing a file to all connected devices
+
+```js
+var Promise = require('bluebird')
+var adb = require('adbkit')
+var client = adb.createClient()
+
+client.listDevices()
+  .then(function(devices) {
+    return Promise.map(devices, function(device) {
+      return client.push(device.id, 'temp/foo.txt', '/data/local/tmp/foo.txt')
+        .then(function(transfer) {
+          return new Promise(function(resolve, reject) {
+            transfer.on('progress', function(stats) {
+              console.log('[%s] Pushed %d bytes so far',
+                device.id,
+                stats.bytesTransferred)
+            })
+            transfer.on('end', function() {
+              console.log('[%s] Push complete', device.id)
+              resolve()
+            })
+            transfer.on('error', reject)
+          })
+        })
+    })
+  })
+  .then(function() {
+    console.log('Done pushing foo.txt to all connected devices')
+  })
+  .catch(function(err) {
+    console.error('Something went wrong:', err.stack)
+  })
+```
+
+#### List files in a folder
+
+```js
+var Promise = require('bluebird')
+var adb = require('adbkit')
+var client = adb.createClient()
+
+client.listDevices()
+  .then(function(devices) {
+    return Promise.map(devices, function(device) {
+      return client.readdir(device.id, '/sdcard')
+        .then(function(files) {
+          // Synchronous, so we don't have to care about returning at the
+          // right time
+          files.forEach(function(file) {
+            if (file.isFile()) {
+              console.log('[%s] Found file "%s"', device.id, file.name)
+            }
+          })
+        })
+    })
+  })
+  .then(function() {
+    console.log('Done checking /sdcard files on connected devices')
+  })
+  .catch(function(err) {
+    console.error('Something went wrong:', err.stack)
+  })
+```
+
+## API
+
+### ADB
+
+#### adb.createClient([options])
+
+Creates a client instance with the provided options. Note that this will not automatically establish a connection, it will only be done when necessary.
+
+* **options** An object compatible with [Net.connect][net-connect]'s options:
+    - **port** The port where the ADB server is listening. Defaults to `5037`.
+    - **host** The host of the ADB server. Defaults to `'localhost'`.
+    - **bin** As the sole exception, this option provides the path to the `adb` binary, used for starting the server locally if initial connection fails. Defaults to `'adb'`.
+* Returns: The client instance.
+
+#### adb.util.parsePublicKey(androidKey[, callback])
+
+Parses an Android-formatted mincrypt public key (e.g. `~/.android/adbkey.pub`).
+
+* **androidKey** The key String or [`Buffer`][node-buffer] to parse. Not a filename.
+* **callback(err, output)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **key** The key as a [forge.pki](https://github.com/digitalbazaar/forge#rsa) public key. You may need [node-forge](https://github.com/digitalbazaar/forge) for complicated operations. Additionally the following properties are present:
+      * **fingerprint** The key fingerprint, like it would display on a device. Note that this is different than the one you'd get from `forge.ssh.getPublicKeyFingerprint(key)`, because the device fingerprint is based on the original format.
+      * **comment** The key comment, if any.
+* Returns: `Promise`
+* Resolves with: `key` (see callback)
+
+#### adb.util.readAll(stream[, callback])
+
+Takes a [`Stream`][node-stream] and reads everything it outputs until the stream ends. Then it resolves with the collected output. Convenient with `client.shell()`.
+
+* **stream** The [`Stream`][node-stream] to read.
+* **callback(err, output)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **output** All the output as a [`Buffer`][node-buffer]. Use `output.toString('utf-8')` to get a readable string from it.
+* Returns: `Promise`
+* Resolves with: `output` (see callback)
+
+### Client
+
+#### client.clear(serial, pkg[, callback])
+
+Deletes all data associated with a package from the device. This is roughly analogous to `adb shell pm clear <pkg>`.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **pkg** The package name. This is NOT the APK.
+* **callback(err)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+* Returns: `Promise`
+* Resolves with: `true`
+
+#### client.connect(host[, port]&#91;, callback])
+
+Connects to the given device, which must have its ADB daemon running in tcp mode (see `client.tcpip()`) and be accessible on the same network. Same as `adb connect <host>:<port>`.
+
+* **host** The target host. Can also contain the port, in which case the port argument is not used and can be skipped.
+* **port** Optional. The target port. Defaults to `5555`.
+* **callback(err, id)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **id** The connected device ID. Can be used as `serial` in other commands.
+* Returns: `Promise`
+* Resolves with: `id` (see callback)
+
+##### Example - switch to TCP mode and set up a forward for Chrome devtools
+
+Note: be careful with using `client.listDevices()` together with `client.tcpip()` and other similar methods that modify the connection with ADB. You might have the same device twice in your device list (i.e. one device connected via both USB and TCP), which can cause havoc if run simultaneously.
+
+```javascript
+var Promise = require('bluebird')
+var client = require('adbkit').createClient()
+
+client.listDevices()
+  .then(function(devices) {
+    return Promise.map(devices, function(device) {
+      return client.tcpip(device.id)
+        .then(function(port) {
+          // Switching to TCP mode causes ADB to lose the device for a
+          // moment, so let's just wait till we get it back.
+          return client.waitForDevice(device.id).return(port)
+        })
+        .then(function(port) {
+          return client.getDHCPIpAddress(device.id)
+            .then(function(ip) {
+              return client.connect(ip, port)
+            })
+            .then(function(id) {
+              // It can take a moment for the connection to happen.
+              return client.waitForDevice(id)
+            })
+            .then(function(id) {
+              return client.forward(id, 'tcp:9222', 'localabstract:chrome_devtools_remote')
+                .then(function() {
+                  console.log('Setup devtools on "%s"', id)
+                })
+            })
+        })
+    })
+  })
+```
+
+#### client.disconnect(host[, port]&#91;, callback])
+
+Disconnects from the given device, which should have been connected via `client.connect()` or just `adb connect <host>:<port>`.
+
+* **host** The target host. Can also contain the port, in which case the port argument is not used and can be skipped. In other words you can just put the `id` you got from `client.connect()` here and it will be fine.
+* **port** Optional. The target port. Defaults to `5555`.
+* **callback(err, id)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **id** The disconnected device ID. Will no longer be usable as a `serial` in other commands until you've connected again.
+* Returns: `Promise`
+* Resolves with: `id` (see callback)
+
+#### client.forward(serial, local, remote[, callback])
+
+Forwards socket connections from the ADB server host (local) to the device (remote). This is analogous to `adb forward <local> <remote>`. It's important to note that if you are connected to a remote ADB server, the forward will be created on that host.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **local** A string representing the local endpoint on the ADB host. At time of writing, can be one of:
+    - `tcp:<port>`
+    - `localabstract:<unix domain socket name>`
+    - `localreserved:<unix domain socket name>`
+    - `localfilesystem:<unix domain socket name>`
+    - `dev:<character device name>`
+* **remote** A string representing the remote endpoint on the device. At time of writing, can be one of:
+    - Any value accepted by the `local` argument
+    - `jdwp:<process pid>`
+* **callback(err)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+* Returns: `Promise`
+* Resolves with: `true`
+
+#### client.framebuffer(serial[, format]&#91;, callback])
+
+Fetches the current **raw** framebuffer (i.e. what is visible on the screen) from the device, and optionally converts it into something more usable by using [GraphicsMagick][graphicsmagick]'s `gm` command, which must be available in `$PATH` if conversion is desired. Note that we don't bother supporting really old framebuffer formats such as RGB_565. If for some mysterious reason you happen to run into a `>=2.3` device that uses RGB_565, let us know.
+
+Note that high-resolution devices can have quite massive framebuffers. For example, a device with a resolution of 1920x1080 and 32 bit colors would have a roughly 8MB (`1920*1080*4` byte) RGBA framebuffer. Empirical tests point to about 5MB/s bandwidth limit for the ADB USB connection, which means that it can take ~1.6 seconds for the raw data to arrive, or even more if the USB connection is already congested. Using a conversion will further slow down completion.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **format** The desired output format. Any output format supported by [GraphicsMagick][graphicsmagick] (such as `'png'`) is supported. Defaults to `'raw'` for raw framebuffer data.
+* **callback(err, framebuffer)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **framebuffer** The possibly converted framebuffer stream. The stream also has a `meta` property with the following values:
+        * **version** The framebuffer version. Useful for patching possible backwards-compatibility issues.
+        * **bpp** Bits per pixel (i.e. color depth).
+        * **size** The raw byte size of the framebuffer.
+        * **width** The horizontal resolution of the framebuffer. This SHOULD always be the same as screen width. We have not encountered any device with incorrect framebuffer metadata, but according to rumors there might be some.
+        * **height** The vertical resolution of the framebuffer. This SHOULD always be the same as screen height.
+        * **red_offset** The bit offset of the red color in a pixel.
+        * **red_length** The bit length of the red color in a pixel.
+        * **blue_offset** The bit offset of the blue color in a pixel.
+        * **blue_length** The bit length of the blue color in a pixel.
+        * **green_offset** The bit offset of the green color in a pixel.
+        * **green_length** The bit length of the green color in a pixel.
+        * **alpha_offset** The bit offset of alpha in a pixel.
+        * **alpha_length** The bit length of alpha in a pixel. `0` when not available.
+        * **format** The framebuffer format for convenience. This can be one of `'bgr'`,  `'bgra'`, `'rgb'`, `'rgba'`.
+* Returns: `Promise`
+* Resolves with: `framebuffer` (see callback)
+
+#### client.getDevicePath(serial[, callback])
+
+Gets the device path of the device identified by the given serial number.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err, path)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **path** The device path. This corresponds to the device path in `client.listDevicesWithPaths()`.
+* Returns: `Promise`
+* Resolves with: `path` (see callback)
+
+#### client.getDHCPIpAddress(serial[, iface]&#91;, callback])
+
+Attemps to retrieve the IP address of the device. Roughly analogous to `adb shell getprop dhcp.<iface>.ipaddress`.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **iface** Optional. The network interface. Defaults to `'wlan0'`.
+* **callback(err, ip)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **ip** The IP address as a `String`.
+* Returns: `Promise`
+* Resolves with: `ip` (see callback)
+
+#### client.getFeatures(serial[, callback])
+
+Retrieves the features of the device identified by the given serial number. This is analogous to `adb shell pm list features`. Useful for checking whether hardware features such as NFC are available (you'd check for `'android.hardware.nfc'`).
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err, features)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **features** An object of device features. Each key corresponds to a device feature, with the value being either `true` for a boolean feature, or the feature value as a string (e.g. `'0x20000'` for `reqGlEsVersion`).
+* Returns: `Promise`
+* Resolves with: `features` (see callback)
+
+#### client.getPackages(serial[, callback])
+
+Retrieves the list of packages present on the device. This is analogous to `adb shell pm list packages`. If you just want to see if something's installed, consider using `client.isInstalled()` instead.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err, packages)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **packages** An array of package names.
+* Returns: `Promise`
+* Resolves with: `packages` (see callback)
+
+#### client.getProperties(serial[, callback])
+
+Retrieves the properties of the device identified by the given serial number. This is analogous to `adb shell getprop`.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err, properties)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **properties** An object of device properties. Each key corresponds to a device property. Convenient for accessing things like `'ro.product.model'`.
+* Returns: `Promise`
+* Resolves with: `properties` (see callback)
+
+#### client.getSerialNo(serial[, callback])
+
+Gets the serial number of the device identified by the given serial number. With our API this doesn't really make much sense, but it has been implemented for completeness. _FYI: in the raw ADB protocol you can specify a device in other ways, too._
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err, serial)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **serial** The serial number of the device.
+* Returns: `Promise`
+* Resolves with: `serial` (see callback)
+
+#### client.getState(serial[, callback])
+
+Gets the state of the device identified by the given serial number.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err, state)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **state** The device state. This corresponds to the device type in `client.listDevices()`.
+* Returns: `Promise`
+* Resolves with: `state` (see callback)
+
+#### client.install(serial, apk[, callback])
+
+Installs the APK on the device, replacing any previously installed version. This is roughly analogous to `adb install -r <apk>`.
+
+Note that if the call seems to stall, you may have to accept a dialog on the phone first.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **apk** When `String`, interpreted as a path to an APK file. When [`Stream`][node-stream], installs directly from the stream, which must be a valid APK.
+* **callback(err)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise. It may have a `.code` property containing the error code reported by the device.
+* Returns: `Promise`
+* Resolves with: `true`
+
+##### Example - install an APK from a URL
+
+This example requires the [request](https://www.npmjs.org/package/request) module. It also doesn't do any error handling (404 responses, timeouts, invalid URLs etc).
+
+```javascript
+var client = require('adbkit').createClient()
+var request = require('request')
+var Readable = require('stream').Readable
+
+// The request module implements old-style streams, so we have to wrap it.
+client.install('<serial>', new Readable().wrap(request('http://example.org/app.apk')))
+  .then(function() {
+    console.log('Installed')
+  })
+```
+
+#### client.installRemote(serial, apk[, callback])
+
+Installs an APK file which must already be located on the device file system, and replaces any previously installed version. Useful if you've previously pushed the file to the device for some reason (perhaps to have direct access to `client.push()`'s transfer stats). This is roughly analogous to `adb shell pm install -r <apk>` followed by `adb shell rm -f <apk>`.
+
+Note that if the call seems to stall, you may have to accept a dialog on the phone first.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **apk** The path to the APK file on the device. The file will be removed when the command completes.
+* **callback(err)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+* Returns: `Promise`
+* Resolves with: `true`
+
+#### client.isInstalled(serial, pkg[, callback])
+
+Tells you if the specific package is installed or not. This is analogous to `adb shell pm path <pkg>` and some output parsing.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **pkg** The package name. This is NOT the APK.
+* **callback(err, installed)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **installed** `true` if the package is installed, `false` otherwise.
+* Returns: `Promise`
+* Resolves with: `installed` (see callback)
+
+#### client.kill([callback])
+
+This kills the ADB server. Note that the next connection will attempt to start the server again when it's unable to connect.
+
+* **callback(err)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+* Returns: `Promise`
+* Resolves with: `true`
+
+#### client.listDevices([callback])
+
+Gets the list of currently connected devices and emulators.
+
+* **callback(err, devices)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **devices** An array of device objects. The device objects are plain JavaScript objects with two properties: `id` and `type`.
+        * **id** The ID of the device. For real devices, this is usually the USB identifier.
+        * **type** The device type. Values include `'emulator'` for emulators, `'device'` for devices, and `'offline'` for offline devices. `'offline'` can occur for example during boot, in low-battery conditions or when the ADB connection has not yet been approved on the device.
+* Returns: `Promise`
+* Resolves with: `devices` (see callback)
+
+#### client.listDevicesWithPaths([callback])
+
+Like `client.listDevices()`, but includes the "path" of every device.
+
+* **callback(err, devices)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **devices** An array of device objects. The device objects are plain JavaScript objects with the following properties:
+        * **id** See `client.listDevices()`.
+        * **type** See `client.listDevices()`.
+        * **path** The device path. This can be something like `usb:FD120000` for real devices.
+* Returns: `Promise`
+* Resolves with: `devices` (see callback)
+
+#### client.listForwards(serial[, callback])
+
+Lists forwarded connections on the device. This is analogous to `adb forward --list`.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err, forwards)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **forwards** An array of forward objects with the following properties:
+        * **serial** The device serial.
+        * **local** The local endpoint. Same format as `client.forward()`'s `local` argument.
+        * **remote** The remote endpoint on the device. Same format as `client.forward()`'s `remote` argument.
+* Returns: `Promise`
+* Resolves with: `forwards` (see callback)
+
+#### client.openLocal(serial, path[, callback])
+
+Opens a direct connection to a unix domain socket in the given path.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **path** The path to the socket. Prefixed with `'localfilesystem:'` by default, include another prefix (e.g. `'localabstract:'`) in the path to override.
+* **callback(err, conn)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **conn** The connection (i.e. [`net.Socket`][node-net]). Read and write as you please. Call `conn.end()` to end the connection.
+* Returns: `Promise`
+* Resolves with: `conn` (see callback)
+
+#### client.openLog(serial, name[, callback])
+
+Opens a direct connection to a binary log file, providing access to the raw log data. Note that it is usually much more convenient to use the `client.openLogcat()` method, described separately.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **name** The name of the log. Available logs include `'main'`, `'system'`, `'radio'` and `'events'`.
+* **callback(err, log)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **log** The binary log stream. Call `log.end()` when you wish to stop receiving data.
+* Returns: `Promise`
+* Resolves with: `log` (see callback)
+
+#### client.openLogcat(serial[, options]&#91;, callback])
+
+Calls the `logcat` utility on the device and hands off the connection to [adbkit-logcat][adbkit-logcat], a pure Node.js Logcat client. This is analogous to `adb logcat -B`, but the event stream will be parsed for you and a separate event will be emitted for every log entry, allowing for easy processing.
+
+For more information, check out the [adbkit-logcat][adbkit-logcat] documentation.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **options** Optional. The following options are supported:
+    - **clear** When `true`, clears logcat before opening the reader. Not set by default.
+* **callback(err, logcat)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **logcat** The Logcat client. Please see the [adbkit-logcat][adbkit-logcat] documentation for details.
+* Returns: `Promise`
+* Resolves with: `logcat` (see callback)
+
+#### client.openMonkey(serial[, port]&#91;, callback])
+
+Starts the built-in `monkey` utility on the device, connects to it using `client.openTcp()` and hands the connection to [adbkit-monkey][adbkit-monkey], a pure Node.js Monkey client. This allows you to create touch and key events, among other things.
+
+For more information, check out the [adbkit-monkey][adbkit-monkey] documentation.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **port** Optional. The device port where you'd like Monkey to run at. Defaults to `1080`.
+* **callback(err, monkey)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **monkey** The Monkey client. Please see the [adbkit-monkey][adbkit-monkey] documentation for details.
+* Returns: `Promise`
+* Resolves with: `monkey` (see callback)
+
+#### client.openProcStat(serial[, callback])
+
+Tracks `/proc/stat` and emits useful information, such as CPU load. A single sync service instance is used to download the `/proc/stat` file for processing. While doing this does consume some resources, it is very light and should not be a problem.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err, stats)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **stats** The `/proc/stat` tracker, which is an [`EventEmitter`][node-events]. Call `stat.end()` to stop tracking. The following events are available:
+        * **load** **(loads)** Emitted when a CPU load calculation is available.
+            - **loads** CPU loads of **online** CPUs. Each key is a CPU id (e.g. `'cpu0'`, `'cpu1'`) and the value an object with the following properties:
+                * **user** Percentage (0-100) of ticks spent on user programs.
+                * **nice** Percentage (0-100) of ticks spent on `nice`d user programs.
+                * **system** Percentage (0-100) of ticks spent on system programs.
+                * **idle** Percentage (0-100) of ticks spent idling.
+                * **iowait** Percentage (0-100) of ticks spent waiting for IO.
+                * **irq** Percentage (0-100) of ticks spent on hardware interrupts.
+                * **softirq** Percentage (0-100) of ticks spent on software interrupts.
+                * **steal** Percentage (0-100) of ticks stolen by others.
+                * **guest** Percentage (0-100) of ticks spent by a guest.
+                * **guestnice** Percentage (0-100) of ticks spent by a `nice`d guest.
+                * **total** Total. Always 100.
+* Returns: `Promise`
+* Resolves with: `stats` (see callback)
+
+#### client.openTcp(serial, port[, host]&#91;, callback])
+
+Opens a direct TCP connection to a port on the device, without any port forwarding required.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **port** The port number to connect to.
+* **host** Optional. The host to connect to. Allegedly this is supposed to establish a connection to the given host from the device, but we have not been able to get it to work at all. Skip the host and everything works great.
+* **callback(err, conn)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **conn** The TCP connection (i.e. [`net.Socket`][node-net]). Read and write as you please. Call `conn.end()` to end the connection.
+* Returns: `Promise`
+* Resolves with: `conn` (see callback)
+
+#### client.pull(serial, path[, callback])
+
+A convenience shortcut for `sync.pull()`, mainly for one-off use cases. The connection cannot be reused, resulting in poorer performance over multiple calls. However, the Sync client will be closed automatically for you, so that's one less thing to worry about.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **path** See `sync.pull()` for details.
+* **callback(err, transfer)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **transfer** A `PullTransfer` instance (see below)
+* Returns: `Promise`
+* Resolves with: `transfer` (see callback)
+
+#### client.push(serial, contents, path[, mode]&#91;, callback])
+
+A convenience shortcut for `sync.push()`, mainly for one-off use cases. The connection cannot be reused, resulting in poorer performance over multiple calls. However, the Sync client will be closed automatically for you, so that's one less thing to worry about.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **contents** See `sync.push()` for details.
+* **path** See `sync.push()` for details.
+* **mode** See `sync.push()` for details.
+* **callback(err, transfer)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **transfer** A `PushTransfer` instance (see below)
+* Returns: `Promise`
+* Resolves with: `transfer` (see callback)
+
+#### client.readdir(serial, path[, callback])
+
+A convenience shortcut for `sync.readdir()`, mainly for one-off use cases. The connection cannot be reused, resulting in poorer performance over multiple calls. However, the Sync client will be closed automatically for you, so that's one less thing to worry about.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **path** See `sync.readdir()` for details.
+* **callback(err, files)** Optional. Use this or the returned `Promise`. See `sync.readdir()` for details.
+* Returns: `Promise`
+* Resolves with: See `sync.readdir()` for details.
+
+#### client.reboot(serial[, callback])
+
+Reboots the device. Similar to `adb reboot`. Note that the method resolves when ADB reports that the device has been rebooted (i.e. the reboot command was successful), not when the device becomes available again.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+* Returns: `Promise`
+* Resolves with: `true`
+
+#### client.remount(serial[, callback])
+
+Attempts to remount the `/system` partition in read-write mode. This will usually only work on emulators and developer devices.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+* Returns: `Promise`
+* Resolves with: `true`
+
+#### client.screencap(serial[, callback])
+
+Takes a screenshot in PNG format using the built-in `screencap` utility. This is analogous to `adb shell screencap -p`. Sadly, the utility is not available on most Android `<=2.3` devices, but a silent fallback to the `client.framebuffer()` command in PNG mode is attempted, so you should have its dependencies installed just in case.
+
+Generating the PNG on the device naturally requires considerably more processing time on that side. However, as the data transferred over USB easily decreases by ~95%, and no conversion being required on the host, this method is usually several times faster than using the framebuffer. Naturally, this benefit does not apply if we're forced to fall back to the framebuffer.
+
+For convenience purposes, if the screencap command fails (e.g. because it doesn't exist on older Androids), we fall back to `client.framebuffer(serial, 'png')`, which is slower and has additional installation requirements.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err, screencap)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **screencap** The PNG stream.
+* Returns: `Promise`
+* Resolves with: `screencap` (see callback)
+
+#### client.shell(serial, command[, callback])
+
+Runs a shell command on the device. Note that you'll be limited to the permissions of the `shell` user, which ADB uses.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **command** The shell command to execute. When `String`, the command is run as-is. When `Array`, the elements will be rudimentarily escaped (for convenience, not security) and joined to form a command.
+* **callback(err, output)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **output** A Buffer containing all the output. Call `output.toString('utf-8')` to get a readable String from it.
+* Returns: `Promise`
+* Resolves with: `output` (see callback)
+
+##### Example
+
+```js
+var Promise = require('bluebird')
+var adb = require('adbkit')
+var client = adb.createClient()
+
+client.listDevices()
+  .then(function(devices) {
+    return Promise.map(devices, function(device) {
+      return client.shell(device.id, 'echo $RANDOM')
+        // Use the readAll() utility to read all the content without
+        // having to deal with the events. `output` will be a Buffer
+        // containing all the output.
+        .then(adb.util.readAll)
+        .then(function(output) {
+          console.log('[%s] %s', device.id, output.toString().trim())
+        })
+    })
+  })
+  .then(function() {
+    console.log('Done.')
+  })
+  .catch(function(err) {
+    console.error('Something went wrong:', err.stack)
+  })
+```
+
+#### client.startActivity(serial, options[, callback])
+
+Starts the configured activity on the device. Roughly analogous to `adb shell am start <options>`.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **options** The activity configuration. The following options are available:
+    - **debug** Set to `true` to enable debugging.
+    - **wait** Set to `true` to wait for the activity to launch.
+    - **user** The user to run as. Not set by default. If the option is unsupported by the device, an attempt will be made to run the same command again without the user option.
+    - **action** The action.
+    - **data** The data URI, if any.
+    - **mimeType** The mime type, if any.
+    - **category** The category. For multiple categories, pass an `Array`.
+    - **component** The component.
+    - **flags** Numeric flags.
+    - **extras** Any extra data.
+        * When an `Array`, each item must be an `Object` the following properties:
+            - **key** The key name.
+            - **type** The type, which can be one of `'string'`, `'null'`, `'bool'`, `'int'`, `'long'`, `'float'`, `'uri'`, `'component'`.
+            - **value** The value. Optional and unused if type is `'null'`. If an `Array`, type is automatically set to be an array of `<type>`.
+        * When an `Object`, each key is treated as the key name. Simple values like `null`, `String`, `Boolean` and `Number` are type-mapped automatically (`Number` maps to `'int'`) and can be used as-is. For more complex types, like arrays and URIs, set the value to be an `Object` like in the Array syntax (see above), but leave out the `key` property.
+* **callback(err)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+* Returns: `Promise`
+* Resolves with: `true`
+
+#### client.startService(serial, options[, callback])
+
+Starts the configured service on the device. Roughly analogous to `adb shell am startservice <options>`.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **options** The service configuration. The following options are available:
+    - **user** The user to run as. Defaults to `0`. If the option is unsupported by the device, an attempt will be made to run the same command again without the user option.
+    - **action** See `client.startActivity()` for details.
+    - **data** See `client.startActivity()` for details.
+    - **mimeType** See `client.startActivity()` for details.
+    - **category** See `client.startActivity()` for details.
+    - **component** See `client.startActivity()` for details.
+    - **flags** See `client.startActivity()` for details.
+    - **extras** See `client.startActivity()` for details.
+* Returns: `Promise`
+* Resolves with: `true`
+
+#### client.stat(serial, path[, callback])
+
+A convenience shortcut for `sync.stat()`, mainly for one-off use cases. The connection cannot be reused, resulting in poorer performance over multiple calls. However, the Sync client will be closed automatically for you, so that's one less thing to worry about.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **path** See `sync.stat()` for details.
+* **callback(err, stats)** Optional. Use this or the returned `Promise`. See `sync.stat()` for details.
+* Returns: `Promise`
+* Resolves with: See `sync.stat()` for details.
+
+#### client.syncService(serial[, callback])
+
+Establishes a new Sync connection that can be used to push and pull files. This method provides the most freedom and the best performance for repeated use, but can be a bit cumbersome to use. For simple use cases, consider using `client.stat()`, `client.push()` and `client.pull()`.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err, sync)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **sync** The Sync client. See below for details. Call `sync.end()` when done.
+* Returns: `Promise`
+* Resolves with: `sync` (see callback)
+
+#### client.tcpip(serial, port[, callback])
+
+Puts the device's ADB daemon into tcp mode, allowing you to use `adb connect` or `client.connect()` to connect to it. Note that the device will still be visible to ADB as a regular USB-connected device until you unplug it. Same as `adb tcpip <port>`.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **port** Optional. The port the device should listen on. Defaults to `5555`.
+* **callback(err, port)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **port** The port the device started listening on.
+* Returns: `Promise`
+* Resolves with: `port` (see callback)
+
+#### client.trackDevices([callback])
+
+Gets a device tracker. Events will be emitted when devices are added, removed, or their type changes (i.e. to/from `offline`). Note that the same events will be emitted for the initially connected devices also, so that you don't need to use both `client.listDevices()` and `client.trackDevices()`.
+
+Note that as the tracker will keep a connection open, you must call `tracker.end()` if you wish to stop tracking devices.
+
+* **callback(err, tracker)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **tracker** The device tracker, which is an [`EventEmitter`][node-events]. The following events are available:
+        * **add** **(device)** Emitted when a new device is connected, once per device. See `client.listDevices()` for details on the device object.
+        * **remove** **(device)** Emitted when a device is unplugged, once per device. This does not include `offline` devices, those devices are connected but unavailable to ADB. See `client.listDevices()` for details on the device object.
+        * **change** **(device)** Emitted when the `type` property of a device changes, once per device. The current value of `type` is the new value. This event usually occurs the type changes from `'device'` to `'offline'` or the other way around. See `client.listDevices()` for details on the device object and the `'offline'` type.
+        * **changeSet** **(changes)** Emitted once for all changes reported by ADB in a single run. Multiple changes can occur when, for example, a USB hub is connected/unplugged and the device list changes quickly. If you wish to process all changes at once, use this event instead of the once-per-device ones. Keep in mind that the other events will still be emitted, though.
+            - **changes** An object with the following properties always present:
+                * **added** An array of added device objects, each one as in the `add` event. Empty if none.
+                * **removed** An array of removed device objects, each one as in the `remove` event. Empty if none.
+                * **changed** An array of changed device objects, each one as in the `change` event. Empty if none.
+        * **end** Emitted when the underlying connection ends.
+        * **error** **(err)** Emitted if there's an error.
+* Returns: `Promise`
+* Resolves with: `tracker` (see callback)
+
+#### client.trackJdwp(serial[, callback])
+
+Starts a JDWP tracker for the given device.
+
+Note that as the tracker will keep a connection open, you must call `tracker.end()` if you wish to stop tracking JDWP processes.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err, tracker)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **tracker** The JDWP tracker, which is an [`EventEmitter`][node-events]. The following events are available:
+        * **add** **(pid)** Emitted when a new JDWP process becomes available, once per pid.
+        * **remove** **(pid)** Emitted when a JDWP process becomes unavailable, once per pid.
+        * **changeSet** **(changes, pids)** All changes in a single event.
+            - **changes** An object with the following properties always present:
+                * **added** An array of pids that were added. Empty if none.
+                * **removed** An array of pids that were removed. Empty if none.
+            - **pids** All currently active pids (including pids from previous runs).
+        * **end** Emitted when the underlying connection ends.
+        * **error** **(err)** Emitted if there's an error.
+* Returns: `Promise`
+* Resolves with: `tracker` (see callback)
+
+#### client.uninstall(serial, pkg[, callback])
+
+Uninstalls the package from the device. This is roughly analogous to `adb uninstall <pkg>`.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **pkg** The package name. This is NOT the APK.
+* **callback(err)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+* Returns: `Promise`
+* Resolves with: `true`
+
+#### client.usb(serial[, callback])
+
+Puts the device's ADB daemon back into USB mode. Reverses `client.tcpip()`. Same as `adb usb`.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+* Returns: `Promise`
+* Resolves with: `true`
+
+#### client.version([callback])
+
+Queries the ADB server for its version. This is mainly useful for backwards-compatibility purposes.
+
+* **callback(err, version)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **version** The version of the ADB server.
+* Returns: `Promise`
+* Resolves with: `version` (see callback)
+
+#### client.waitBootComplete(serial[, callback])
+
+Waits until the device has finished booting. Note that the device must already be seen by ADB. This is roughly analogous to periodically checking `adb shell getprop sys.boot_completed`.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err)** Optional. Use this or the returned `Promise`.
+    - **err** `null` if the device has completed booting, `Error` otherwise (can occur if the connection dies while checking).
+* Returns: `Promise`
+* Resolves with: `true`
+
+#### client.waitForDevice(serial[, callback])
+
+Waits until ADB can see the device. Note that you must know the serial in advance. Other than that, works like `adb -s serial wait-for-device`. If you're planning on reacting to random devices being plugged in and out, consider using `client.trackDevices()` instead.
+
+* **serial** The serial number of the device. Corresponds to the device ID in `client.listDevices()`.
+* **callback(err, id)** Optional. Use this or the returned `Promise`.
+    - **err** `null` if the device has completed booting, `Error` otherwise (can occur if the connection dies while checking).
+    - **id** The device ID. Can be useful for chaining.
+* Returns: `Promise`
+* Resolves with: `id` (see callback)
+
+### Sync
+
+#### sync.end()
+
+Closes the Sync connection, allowing Node to quit (assuming nothing else is keeping it alive, of course).
+
+* Returns: The sync instance.
+
+#### sync.pull(path)
+
+Pulls a file from the device as a `PullTransfer` [`Stream`][node-stream].
+
+* **path** The path to pull from.
+* Returns: A `PullTransfer` instance. See below for details.
+
+#### sync.push(contents, path[, mode])
+
+Attempts to identify `contents` and calls the appropriate `push*` method for it.
+
+* **contents** When `String`, treated as a local file path and forwarded to `sync.pushFile()`. Otherwise, treated as a [`Stream`][node-stream] and forwarded to `sync.pushStream()`.
+* **path** The path to push to.
+* **mode** Optional. The mode of the file. Defaults to `0644`.
+* Returns: A `PushTransfer` instance. See below for details.
+
+#### sync.pushFile(file, path[, mode])
+
+Pushes a local file to the given path. Note that the path must be writable by the ADB user (usually `shell`). When in doubt, use `'/data/local/tmp'` with an appropriate filename.
+
+* **file** The local file path.
+* **path** See `sync.push()` for details.
+* **mode** See `sync.push()` for details.
+* Returns: See `sync.push()` for details.
+
+#### sync.pushStream(stream, path[, mode])
+
+Pushes a [`Stream`][node-stream] to the given path. Note that the path must be writable by the ADB user (usually `shell`). When in doubt, use `'/data/local/tmp'` with an appropriate filename.
+
+* **stream** The readable stream.
+* **path** See `sync.push()` for details.
+* **mode** See `sync.push()` for details.
+* Returns: See `sync.push()` for details.
+
+#### sync.readdir(path[, callback])
+
+Retrieves a list of directory entries (e.g. files) in the given path, not including the `.` and `..` entries, just like [`fs.readdir`][node-fs]. If given a non-directory path, no entries are returned.
+
+* **path** The path.
+* **callback(err, files)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **files** An `Array` of [`fs.Stats`][node-fs-stats]-compatible instances. While the `stats.is*` methods are available, only the following properties are supported (in addition to the `name` field which contains the filename):
+        * **name** The filename.
+        * **mode** The raw mode.
+        * **size** The file size.
+        * **mtime** The time of last modification as a `Date`.
+* Returns: `Promise`
+* Resolves with: `files` (see callback)
+
+#### sync.stat(path[, callback])
+
+Retrieves information about the given path.
+
+* **path** The path.
+* **callback(err, stats)** Optional. Use this or the returned `Promise`.
+    - **err** `null` when successful, `Error` otherwise.
+    - **stats** An [`fs.Stats`][node-fs-stats] instance. While the `stats.is*` methods are available, only the following properties are supported:
+        * **mode** The raw mode.
+        * **size** The file size.
+        * **mtime** The time of last modification as a `Date`.
+* Returns: `Promise`
+* Resolves with: `stats` (see callback)
+
+#### sync.tempFile(path)
+
+A simple helper method for creating appropriate temporary filenames for pushing files. This is essentially the same as taking the basename of the file and appending it to `'/data/local/tmp/'`.
+
+* **path** The path of the file.
+* Returns: An appropriate temporary file path.
+
+### PushTransfer
+
+A simple EventEmitter, mainly for keeping track of the progress.
+
+List of events:
+
+* **progress** **(stats)** Emitted when a chunk has been flushed to the ADB connection.
+    - **stats** An object with the following stats about the transfer:
+        * **bytesTransferred** The number of bytes transferred so far.
+* **error** **(err)** Emitted on error.
+    - **err** An `Error`.
+* **end** Emitted when the transfer has successfully completed.
+
+#### pushTransfer.cancel()
+
+Cancels the transfer by ending both the stream that is being pushed and the sync connection. This will most likely end up creating a broken file on your device. **Use at your own risk.** Also note that you must create a new sync connection if you wish to continue using the sync service.
+
+* Returns: The pushTransfer instance.
+
+### PullTransfer
+
+`PullTransfer` is a [`Stream`][node-stream]. Use [`fs.createWriteStream()`][node-fs] to pipe the stream to a file if necessary.
+
+List of events:
+
+* **progress** **(stats)** Emitted when a new chunk is received.
+    - **stats** An object with the following stats about the transfer:
+        * **bytesTransferred** The number of bytes transferred so far.
+* **error** **(err)** Emitted on error.
+    - **err** An `Error`.
+* **end** Emitted when the transfer has successfully completed.
+
+#### pullTransfer.cancel()
+
+Cancels the transfer by ending the connection. Can be useful for reading endless streams of data, such as `/dev/urandom` or `/dev/zero`, perhaps for benchmarking use. Note that you must create a new sync connection if you wish to continue using the sync service.
+
+* Returns: The pullTransfer instance.
+
+# Incompatible changes in version 2.x
+
+Previously, we made extensive use of callbacks in almost every feature. While this normally works okay, ADB connections can be quite fickle, and it was starting to become difficult to handle every possible error. For example, we'd often fail to properly clean up after ourselves when a connection suddenly died in an unexpected place, causing memory and resource leaks.
+
+In version 2, we've replaced nearly all callbacks with [Promises](http://promisesaplus.com/) (using [Bluebird](https://github.com/petkaantonov/bluebird)), allowing for much more reliable error propagation and resource cleanup (thanks to `.finally()`). Additionally, many commands can now be cancelled on the fly, and although unimplemented at this point, we'll also be able to report progress on long-running commands without any changes to the API.
+
+Unfortunately, some API changes were required for this change. `client.framebuffer()`'s callback, for example, previously accepted more than one argument, which doesn't translate into Promises so well. Thankfully, it made sense to combine the arguments anyway, and we were able to do it quite cleanly.
+
+Furthermore, most API methods were returning the current instance for chaining purposes. While perhaps useful in some contexts, most of the time it probably didn't quite do what users expected, as chained calls were run in parallel rather than in serial fashion. Now every applicable API method returns a Promise, which is an incompatible but welcome change. This will also allow you to hook into `yield` and coroutines in Node 0.12.
+
+**However, all methods still accept (and will accept in the future) callbacks for those who prefer them.**
+
+Test coverage was also massively improved, although we've still got ways to go.
+
+## More information
+
+* [Android Debug Bridge][adb-site]
+    - [SERVICES.TXT][adb-services] (ADB socket protocol)
+* [Android ADB Protocols][adb-protocols] (a blog post explaining the protocol)
+* [adb.js][adb-js] (another Node.js ADB implementation)
+* [ADB Chrome extension][chrome-adb]
+
+## Contributing
+
+See [CONTRIBUTING.md](CONTRIBUTING.md).
+
+## License
+
+See [LICENSE](LICENSE).
+
+Copyright © CyberAgent, Inc. All Rights Reserved.
+
+[nodejs]: <http://nodejs.org/>
+[coffeescript]: <http://coffeescript.org/>
+[npm]: <https://npmjs.org/>
+[adb-js]: <https://github.com/flier/adb.js>
+[adb-site]: <http://developer.android.com/tools/help/adb.html>
+[adb-services]: <https://github.com/android/platform_system_core/blob/master/adb/SERVICES.TXT>
+[adb-protocols]: <http://blogs.kgsoft.co.uk/2013_03_15_prg.htm>
+[file_sync_service.h]: <https://github.com/android/platform_system_core/blob/master/adb/file_sync_service.h>
+[chrome-adb]: <https://chrome.google.com/webstore/detail/adb/dpngiggdglpdnjdoaefidgiigpemgage>
+[node-debug]: <https://npmjs.org/package/debug>
+[net-connect]: <http://nodejs.org/api/net.html#net_net_connect_options_connectionlistener>
+[node-events]: <http://nodejs.org/api/events.html>
+[node-stream]: <http://nodejs.org/api/stream.html>
+[node-buffer]: <http://nodejs.org/api/buffer.html>
+[node-net]: <http://nodejs.org/api/net.html>
+[node-fs]: <http://nodejs.org/api/fs.html>
+[node-fs-stats]: <http://nodejs.org/api/fs.html#fs_class_fs_stats>
+[node-gm]: <https://github.com/aheckmann/gm>
+[graphicsmagick]: <http://www.graphicsmagick.org/>
+[imagemagick]: <http://www.imagemagick.org/>
+[adbkit-logcat]: <https://npmjs.org/package/adbkit-logcat>
+[adbkit-monkey]: <https://npmjs.org/package/adbkit-monkey>
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/index.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/index.js
new file mode 100644
index 0000000..a4c08ac
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/index.js
@@ -0,0 +1,15 @@
+(function() {
+  var Path;
+
+  Path = require('path');
+
+  module.exports = (function() {
+    switch (Path.extname(__filename)) {
+      case '.coffee':
+        return require('./src/adb');
+      default:
+        return require('./lib/adb');
+    }
+  })();
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb.js
new file mode 100644
index 0000000..50e8e1b
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb.js
@@ -0,0 +1,27 @@
+(function() {
+  var Adb, Client, Keycode, util;
+
+  Client = require('./adb/client');
+
+  Keycode = require('./adb/keycode');
+
+  util = require('./adb/util');
+
+  Adb = (function() {
+    function Adb() {}
+
+    Adb.createClient = function(options) {
+      return new Client(options);
+    };
+
+    return Adb;
+
+  })();
+
+  Adb.Keycode = Keycode;
+
+  Adb.util = util;
+
+  module.exports = Adb;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/auth.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/auth.js
new file mode 100644
index 0000000..868ed63
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/auth.js
@@ -0,0 +1,81 @@
+(function() {
+  var Auth, BigInteger, Promise, forge;
+
+  Promise = require('bluebird');
+
+  forge = require('node-forge');
+
+  BigInteger = forge.jsbn.BigInteger;
+
+
+  /*
+  The stucture of an ADB RSAPublicKey is as follows:
+  
+       *define RSANUMBYTES 256           // 2048 bit key length
+       *define RSANUMWORDS (RSANUMBYTES / sizeof(uint32_t))
+  
+      typedef struct RSAPublicKey {
+          int len;                  // Length of n[] in number of uint32_t
+          uint32_t n0inv;           // -1 / n[0] mod 2^32
+          uint32_t n[RSANUMWORDS];  // modulus as little endian array
+          uint32_t rr[RSANUMWORDS]; // R^2 as little endian array
+          int exponent;             // 3 or 65537
+      } RSAPublicKey;
+   */
+
+  Auth = (function() {
+    var RE, readPublicKeyFromStruct;
+
+    function Auth() {}
+
+    RE = /^((?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?) (.*)$/;
+
+    readPublicKeyFromStruct = function(struct, comment) {
+      var e, key, len, md, n, offset;
+      if (!struct.length) {
+        throw new Error("Invalid public key");
+      }
+      offset = 0;
+      len = struct.readUInt32LE(offset) * 4;
+      offset += 4;
+      if (struct.length !== 4 + 4 + len + len + 4) {
+        throw new Error("Invalid public key");
+      }
+      offset += 4;
+      n = new Buffer(len);
+      struct.copy(n, 0, offset, offset + len);
+      [].reverse.call(n);
+      offset += len;
+      offset += len;
+      e = struct.readUInt32LE(offset);
+      if (!(e === 3 || e === 65537)) {
+        throw new Error("Invalid exponent " + e + ", only 3 and 65537 are supported");
+      }
+      key = forge.pki.setRsaPublicKey(new BigInteger(n.toString('hex'), 16), new BigInteger(e.toString(), 10));
+      md = forge.md.md5.create();
+      md.update(struct.toString('binary'));
+      key.fingerprint = md.digest().toHex().match(/../g).join(':');
+      key.comment = comment;
+      return key;
+    };
+
+    Auth.parsePublicKey = function(buffer) {
+      return new Promise(function(resolve, reject) {
+        var comment, match, struct;
+        if (match = RE.exec(buffer)) {
+          struct = new Buffer(match[1], 'base64');
+          comment = match[2];
+          return resolve(readPublicKeyFromStruct(struct, comment));
+        } else {
+          return reject(new Error("Unrecognizable public key format"));
+        }
+      });
+    };
+
+    return Auth;
+
+  })();
+
+  module.exports = Auth;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/client.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/client.js
new file mode 100644
index 0000000..abfc40e
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/client.js
@@ -0,0 +1,552 @@
+(function() {
+  var ClearCommand, Client, Connection, ForwardCommand, FrameBufferCommand, GetDevicePathCommand, GetFeaturesCommand, GetPackagesCommand, GetPropertiesCommand, GetSerialNoCommand, GetStateCommand, HostConnectCommand, HostDevicesCommand, HostDevicesWithPathsCommand, HostDisconnectCommand, HostKillCommand, HostTrackDevicesCommand, HostTransportCommand, HostVersionCommand, InstallCommand, IsInstalledCommand, ListForwardsCommand, LocalCommand, LogCommand, Logcat, LogcatCommand, Monkey, MonkeyCommand, Parser, ProcStat, Promise, RebootCommand, RemountCommand, ScreencapCommand, ShellCommand, StartActivityCommand, StartServiceCommand, Sync, SyncCommand, TcpCommand, TcpIpCommand, TcpUsbServer, TrackJdwpCommand, UninstallCommand, UsbCommand, WaitBootCompleteCommand, WaitForDeviceCommand, debug;
+
+  Monkey = require('adbkit-monkey');
+
+  Logcat = require('adbkit-logcat');
+
+  Promise = require('bluebird');
+
+  debug = require('debug')('adb:client');
+
+  Connection = require('./connection');
+
+  Sync = require('./sync');
+
+  Parser = require('./parser');
+
+  ProcStat = require('./proc/stat');
+
+  HostVersionCommand = require('./command/host/version');
+
+  HostConnectCommand = require('./command/host/connect');
+
+  HostDevicesCommand = require('./command/host/devices');
+
+  HostDevicesWithPathsCommand = require('./command/host/deviceswithpaths');
+
+  HostDisconnectCommand = require('./command/host/disconnect');
+
+  HostTrackDevicesCommand = require('./command/host/trackdevices');
+
+  HostKillCommand = require('./command/host/kill');
+
+  HostTransportCommand = require('./command/host/transport');
+
+  ClearCommand = require('./command/host-transport/clear');
+
+  FrameBufferCommand = require('./command/host-transport/framebuffer');
+
+  GetFeaturesCommand = require('./command/host-transport/getfeatures');
+
+  GetPackagesCommand = require('./command/host-transport/getpackages');
+
+  GetPropertiesCommand = require('./command/host-transport/getproperties');
+
+  InstallCommand = require('./command/host-transport/install');
+
+  IsInstalledCommand = require('./command/host-transport/isinstalled');
+
+  LocalCommand = require('./command/host-transport/local');
+
+  LogcatCommand = require('./command/host-transport/logcat');
+
+  LogCommand = require('./command/host-transport/log');
+
+  MonkeyCommand = require('./command/host-transport/monkey');
+
+  RebootCommand = require('./command/host-transport/reboot');
+
+  RemountCommand = require('./command/host-transport/remount');
+
+  ScreencapCommand = require('./command/host-transport/screencap');
+
+  ShellCommand = require('./command/host-transport/shell');
+
+  StartActivityCommand = require('./command/host-transport/startactivity');
+
+  StartServiceCommand = require('./command/host-transport/startservice');
+
+  SyncCommand = require('./command/host-transport/sync');
+
+  TcpCommand = require('./command/host-transport/tcp');
+
+  TcpIpCommand = require('./command/host-transport/tcpip');
+
+  TrackJdwpCommand = require('./command/host-transport/trackjdwp');
+
+  UninstallCommand = require('./command/host-transport/uninstall');
+
+  UsbCommand = require('./command/host-transport/usb');
+
+  WaitBootCompleteCommand = require('./command/host-transport/waitbootcomplete');
+
+  ForwardCommand = require('./command/host-serial/forward');
+
+  GetDevicePathCommand = require('./command/host-serial/getdevicepath');
+
+  GetSerialNoCommand = require('./command/host-serial/getserialno');
+
+  GetStateCommand = require('./command/host-serial/getstate');
+
+  ListForwardsCommand = require('./command/host-serial/listforwards');
+
+  WaitForDeviceCommand = require('./command/host-serial/waitfordevice');
+
+  TcpUsbServer = require('./tcpusb/server');
+
+  Client = (function() {
+    var NoUserOptionError;
+
+    function Client(options) {
+      var _base, _base1;
+      this.options = options != null ? options : {};
+      (_base = this.options).port || (_base.port = 5037);
+      (_base1 = this.options).bin || (_base1.bin = 'adb');
+    }
+
+    Client.prototype.createTcpUsbBridge = function(serial, options) {
+      return new TcpUsbServer(this, serial, options);
+    };
+
+    Client.prototype.connection = function() {
+      var conn, connectListener, errorListener, resolver;
+      resolver = Promise.defer();
+      conn = new Connection(this.options).on('error', errorListener = function(err) {
+        return resolver.reject(err);
+      }).on('connect', connectListener = function() {
+        return resolver.resolve(conn);
+      }).connect();
+      return resolver.promise["finally"](function() {
+        conn.removeListener('error', errorListener);
+        return conn.removeListener('connect', connectListener);
+      });
+    };
+
+    Client.prototype.version = function(callback) {
+      return this.connection().then(function(conn) {
+        return new HostVersionCommand(conn).execute();
+      }).nodeify(callback);
+    };
+
+    Client.prototype.connect = function(host, port, callback) {
+      var _ref;
+      if (port == null) {
+        port = 5555;
+      }
+      if (typeof port === 'function') {
+        callback = port;
+        port = 5555;
+      }
+      if (host.indexOf(':') !== -1) {
+        _ref = host.split(':', 2), host = _ref[0], port = _ref[1];
+      }
+      return this.connection().then(function(conn) {
+        return new HostConnectCommand(conn).execute(host, port);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.disconnect = function(host, port, callback) {
+      var _ref;
+      if (port == null) {
+        port = 5555;
+      }
+      if (typeof port === 'function') {
+        callback = port;
+        port = 5555;
+      }
+      if (host.indexOf(':') !== -1) {
+        _ref = host.split(':', 2), host = _ref[0], port = _ref[1];
+      }
+      return this.connection().then(function(conn) {
+        return new HostDisconnectCommand(conn).execute(host, port);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.listDevices = function(callback) {
+      return this.connection().then(function(conn) {
+        return new HostDevicesCommand(conn).execute();
+      }).nodeify(callback);
+    };
+
+    Client.prototype.listDevicesWithPaths = function(callback) {
+      return this.connection().then(function(conn) {
+        return new HostDevicesWithPathsCommand(conn).execute();
+      }).nodeify(callback);
+    };
+
+    Client.prototype.trackDevices = function(callback) {
+      return this.connection().then(function(conn) {
+        return new HostTrackDevicesCommand(conn).execute();
+      }).nodeify(callback);
+    };
+
+    Client.prototype.kill = function(callback) {
+      return this.connection().then(function(conn) {
+        return new HostKillCommand(conn).execute();
+      }).nodeify(callback);
+    };
+
+    Client.prototype.getSerialNo = function(serial, callback) {
+      return this.connection().then(function(conn) {
+        return new GetSerialNoCommand(conn).execute(serial);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.getDevicePath = function(serial, callback) {
+      return this.connection().then(function(conn) {
+        return new GetDevicePathCommand(conn).execute(serial);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.getState = function(serial, callback) {
+      return this.connection().then(function(conn) {
+        return new GetStateCommand(conn).execute(serial);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.getProperties = function(serial, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new GetPropertiesCommand(transport).execute();
+      }).nodeify(callback);
+    };
+
+    Client.prototype.getFeatures = function(serial, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new GetFeaturesCommand(transport).execute();
+      }).nodeify(callback);
+    };
+
+    Client.prototype.getPackages = function(serial, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new GetPackagesCommand(transport).execute();
+      }).nodeify(callback);
+    };
+
+    Client.prototype.getDHCPIpAddress = function(serial, iface, callback) {
+      if (iface == null) {
+        iface = 'wlan0';
+      }
+      if (typeof iface === 'function') {
+        callback = iface;
+        iface = 'wlan0';
+      }
+      return this.getProperties(serial).then(function(properties) {
+        var ip;
+        if (ip = properties["dhcp." + iface + ".ipaddress"]) {
+          return ip;
+        }
+        throw new Error("Unable to find ipaddress for '" + iface + "'");
+      });
+    };
+
+    Client.prototype.forward = function(serial, local, remote, callback) {
+      return this.connection().then(function(conn) {
+        return new ForwardCommand(conn).execute(serial, local, remote);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.listForwards = function(serial, callback) {
+      return this.connection().then(function(conn) {
+        return new ListForwardsCommand(conn).execute(serial);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.transport = function(serial, callback) {
+      return this.connection().then(function(conn) {
+        return new HostTransportCommand(conn).execute(serial)["return"](conn);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.shell = function(serial, command, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new ShellCommand(transport).execute(command);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.reboot = function(serial, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new RebootCommand(transport).execute();
+      }).nodeify(callback);
+    };
+
+    Client.prototype.remount = function(serial, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new RemountCommand(transport).execute();
+      }).nodeify(callback);
+    };
+
+    Client.prototype.trackJdwp = function(serial, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new TrackJdwpCommand(transport).execute();
+      }).nodeify(callback);
+    };
+
+    Client.prototype.framebuffer = function(serial, format, callback) {
+      if (format == null) {
+        format = 'raw';
+      }
+      if (typeof format === 'function') {
+        callback = format;
+        format = 'raw';
+      }
+      return this.transport(serial).then(function(transport) {
+        return new FrameBufferCommand(transport).execute(format);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.screencap = function(serial, callback) {
+      return this.transport(serial).then((function(_this) {
+        return function(transport) {
+          return new ScreencapCommand(transport).execute()["catch"](function(err) {
+            debug("Emulating screencap command due to '" + err + "'");
+            return _this.framebuffer(serial, 'png');
+          });
+        };
+      })(this)).nodeify(callback);
+    };
+
+    Client.prototype.openLocal = function(serial, path, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new LocalCommand(transport).execute(path);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.openLog = function(serial, name, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new LogCommand(transport).execute(name);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.openTcp = function(serial, port, host, callback) {
+      if (typeof host === 'function') {
+        callback = host;
+        host = void 0;
+      }
+      return this.transport(serial).then(function(transport) {
+        return new TcpCommand(transport).execute(port, host);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.openMonkey = function(serial, port, callback) {
+      var tryConnect;
+      if (port == null) {
+        port = 1080;
+      }
+      if (typeof port === 'function') {
+        callback = port;
+        port = 1080;
+      }
+      tryConnect = (function(_this) {
+        return function(times) {
+          return _this.openTcp(serial, port).then(function(stream) {
+            return Monkey.connectStream(stream);
+          })["catch"](function(err) {
+            if (times -= 1) {
+              debug("Monkey can't be reached, trying " + times + " more times");
+              return Promise.delay(100).then(function() {
+                return tryConnect(times);
+              });
+            } else {
+              throw err;
+            }
+          });
+        };
+      })(this);
+      return tryConnect(1)["catch"]((function(_this) {
+        return function(err) {
+          return _this.transport(serial).then(function(transport) {
+            return new MonkeyCommand(transport).execute(port);
+          }).then(function(out) {
+            return tryConnect(20).then(function(monkey) {
+              return monkey.once('end', function() {
+                return out.end();
+              });
+            });
+          });
+        };
+      })(this)).nodeify(callback);
+    };
+
+    Client.prototype.openLogcat = function(serial, options, callback) {
+      if (typeof options === 'function') {
+        callback = options;
+        options = {};
+      }
+      return this.transport(serial).then(function(transport) {
+        return new LogcatCommand(transport).execute(options);
+      }).then(function(stream) {
+        return Logcat.readStream(stream, {
+          fixLineFeeds: false
+        });
+      }).nodeify(callback);
+    };
+
+    Client.prototype.openProcStat = function(serial, callback) {
+      return this.syncService(serial).then(function(sync) {
+        return new ProcStat(sync);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.clear = function(serial, pkg, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new ClearCommand(transport).execute(pkg);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.install = function(serial, apk, callback) {
+      var temp;
+      temp = Sync.temp(typeof apk === 'string' ? apk : '_stream.apk');
+      return this.push(serial, apk, temp).then((function(_this) {
+        return function(transfer) {
+          var endListener, errorListener, resolver;
+          resolver = Promise.defer();
+          transfer.on('error', errorListener = function(err) {
+            return resolver.reject(err);
+          });
+          transfer.on('end', endListener = function() {
+            return resolver.resolve(_this.installRemote(serial, temp));
+          });
+          return resolver.promise["finally"](function() {
+            transfer.removeListener('error', errorListener);
+            return transfer.removeListener('end', endListener);
+          });
+        };
+      })(this)).nodeify(callback);
+    };
+
+    Client.prototype.installRemote = function(serial, apk, callback) {
+      return this.transport(serial).then((function(_this) {
+        return function(transport) {
+          return new InstallCommand(transport).execute(apk).then(function() {
+            return _this.shell(serial, ['rm', '-f', apk]);
+          }).then(function(stream) {
+            return new Parser(stream).readAll();
+          }).then(function(out) {
+            return true;
+          });
+        };
+      })(this)).nodeify(callback);
+    };
+
+    Client.prototype.uninstall = function(serial, pkg, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new UninstallCommand(transport).execute(pkg);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.isInstalled = function(serial, pkg, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new IsInstalledCommand(transport).execute(pkg);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.startActivity = function(serial, options, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new StartActivityCommand(transport).execute(options);
+      })["catch"](NoUserOptionError, (function(_this) {
+        return function() {
+          options.user = null;
+          return _this.startActivity(serial, options);
+        };
+      })(this)).nodeify(callback);
+    };
+
+    Client.prototype.startService = function(serial, options, callback) {
+      return this.transport(serial).then(function(transport) {
+        if (!(options.user || options.user === null)) {
+          options.user = 0;
+        }
+        return new StartServiceCommand(transport).execute(options);
+      })["catch"](NoUserOptionError, (function(_this) {
+        return function() {
+          options.user = null;
+          return _this.startService(serial, options);
+        };
+      })(this)).nodeify(callback);
+    };
+
+    Client.prototype.syncService = function(serial, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new SyncCommand(transport).execute();
+      }).nodeify(callback);
+    };
+
+    Client.prototype.stat = function(serial, path, callback) {
+      return this.syncService(serial).then(function(sync) {
+        return sync.stat(path)["finally"](function() {
+          return sync.end();
+        });
+      }).nodeify(callback);
+    };
+
+    Client.prototype.readdir = function(serial, path, callback) {
+      return this.syncService(serial).then(function(sync) {
+        return sync.readdir(path)["finally"](function() {
+          return sync.end();
+        });
+      }).nodeify(callback);
+    };
+
+    Client.prototype.pull = function(serial, path, callback) {
+      return this.syncService(serial).then(function(sync) {
+        return sync.pull(path).on('end', function() {
+          return sync.end();
+        });
+      }).nodeify(callback);
+    };
+
+    Client.prototype.push = function(serial, contents, path, mode, callback) {
+      if (typeof mode === 'function') {
+        callback = mode;
+        mode = void 0;
+      }
+      return this.syncService(serial).then(function(sync) {
+        return sync.push(contents, path, mode).on('end', function() {
+          return sync.end();
+        });
+      }).nodeify(callback);
+    };
+
+    Client.prototype.tcpip = function(serial, port, callback) {
+      if (port == null) {
+        port = 5555;
+      }
+      if (typeof port === 'function') {
+        callback = port;
+        port = 5555;
+      }
+      return this.transport(serial).then(function(transport) {
+        return new TcpIpCommand(transport).execute(port);
+      }).nodeify(callback);
+    };
+
+    Client.prototype.usb = function(serial, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new UsbCommand(transport).execute();
+      }).nodeify(callback);
+    };
+
+    Client.prototype.waitBootComplete = function(serial, callback) {
+      return this.transport(serial).then(function(transport) {
+        return new WaitBootCompleteCommand(transport).execute();
+      }).nodeify(callback);
+    };
+
+    Client.prototype.waitForDevice = function(serial, callback) {
+      return this.connection().then(function(conn) {
+        return new WaitForDeviceCommand(conn).execute(serial);
+      }).nodeify(callback);
+    };
+
+    NoUserOptionError = function(err) {
+      return err.message.indexOf('--user') !== -1;
+    };
+
+    return Client;
+
+  })();
+
+  module.exports = Client;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command.js
new file mode 100644
index 0000000..a01f7d3
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command.js
@@ -0,0 +1,48 @@
+(function() {
+  var Command, Parser, Protocol, debug;
+
+  debug = require('debug')('adb:command');
+
+  Parser = require('./parser');
+
+  Protocol = require('./protocol');
+
+  Command = (function() {
+    var RE_SQUOT;
+
+    RE_SQUOT = /'/g;
+
+    function Command(connection) {
+      this.connection = connection;
+      this.parser = this.connection.parser;
+      this.protocol = Protocol;
+    }
+
+    Command.prototype.execute = function() {
+      throw new Exception('Missing implementation');
+    };
+
+    Command.prototype._send = function(data) {
+      var encoded;
+      encoded = Protocol.encodeData(data);
+      debug("Send '" + encoded + "'");
+      this.connection.write(encoded);
+      return this;
+    };
+
+    Command.prototype._escape = function(arg) {
+      switch (typeof arg) {
+        case 'number':
+          return arg;
+        default:
+          return "'" + arg.toString().replace(RE_SQUOT, "'\"'\"'") + "'";
+      }
+    };
+
+    return Command;
+
+  })();
+
+  module.exports = Command;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-serial/forward.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-serial/forward.js
new file mode 100644
index 0000000..597d37f
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-serial/forward.js
@@ -0,0 +1,48 @@
+(function() {
+  var Command, ForwardCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  ForwardCommand = (function(_super) {
+    __extends(ForwardCommand, _super);
+
+    function ForwardCommand() {
+      return ForwardCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    ForwardCommand.prototype.execute = function(serial, local, remote) {
+      this._send("host-serial:" + serial + ":forward:" + local + ";" + remote);
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readAscii(4).then(function(reply) {
+                switch (reply) {
+                  case Protocol.OKAY:
+                    return true;
+                  case Protocol.FAIL:
+                    return _this.parser.readError();
+                  default:
+                    return _this.parser.unexpected(reply, 'OKAY or FAIL');
+                }
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return ForwardCommand;
+
+  })(Command);
+
+  module.exports = ForwardCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-serial/getdevicepath.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-serial/getdevicepath.js
new file mode 100644
index 0000000..b171cae
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-serial/getdevicepath.js
@@ -0,0 +1,41 @@
+(function() {
+  var Command, GetDevicePathCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  GetDevicePathCommand = (function(_super) {
+    __extends(GetDevicePathCommand, _super);
+
+    function GetDevicePathCommand() {
+      return GetDevicePathCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    GetDevicePathCommand.prototype.execute = function(serial) {
+      this._send("host-serial:" + serial + ":get-devpath");
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readValue().then(function(value) {
+                return value.toString();
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return GetDevicePathCommand;
+
+  })(Command);
+
+  module.exports = GetDevicePathCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-serial/getserialno.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-serial/getserialno.js
new file mode 100644
index 0000000..28f858e
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-serial/getserialno.js
@@ -0,0 +1,41 @@
+(function() {
+  var Command, GetSerialNoCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  GetSerialNoCommand = (function(_super) {
+    __extends(GetSerialNoCommand, _super);
+
+    function GetSerialNoCommand() {
+      return GetSerialNoCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    GetSerialNoCommand.prototype.execute = function(serial) {
+      this._send("host-serial:" + serial + ":get-serialno");
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readValue().then(function(value) {
+                return value.toString();
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return GetSerialNoCommand;
+
+  })(Command);
+
+  module.exports = GetSerialNoCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-serial/getstate.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-serial/getstate.js
new file mode 100644
index 0000000..fa6378f
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-serial/getstate.js
@@ -0,0 +1,41 @@
+(function() {
+  var Command, GetStateCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  GetStateCommand = (function(_super) {
+    __extends(GetStateCommand, _super);
+
+    function GetStateCommand() {
+      return GetStateCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    GetStateCommand.prototype.execute = function(serial) {
+      this._send("host-serial:" + serial + ":get-state");
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readValue().then(function(value) {
+                return value.toString();
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return GetStateCommand;
+
+  })(Command);
+
+  module.exports = GetStateCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-serial/listforwards.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-serial/listforwards.js
new file mode 100644
index 0000000..e426d9d
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-serial/listforwards.js
@@ -0,0 +1,59 @@
+(function() {
+  var Command, ListForwardsCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  ListForwardsCommand = (function(_super) {
+    __extends(ListForwardsCommand, _super);
+
+    function ListForwardsCommand() {
+      return ListForwardsCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    ListForwardsCommand.prototype.execute = function(serial) {
+      this._send("host-serial:" + serial + ":list-forward");
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readValue().then(function(value) {
+                return _this._parseForwards(value);
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    ListForwardsCommand.prototype._parseForwards = function(value) {
+      var forward, forwards, local, remote, serial, _i, _len, _ref, _ref1;
+      forwards = [];
+      _ref = value.toString().split('\n');
+      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+        forward = _ref[_i];
+        if (forward) {
+          _ref1 = forward.split(/\s+/), serial = _ref1[0], local = _ref1[1], remote = _ref1[2];
+          forwards.push({
+            serial: serial,
+            local: local,
+            remote: remote
+          });
+        }
+      }
+      return forwards;
+    };
+
+    return ListForwardsCommand;
+
+  })(Command);
+
+  module.exports = ListForwardsCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-serial/waitfordevice.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-serial/waitfordevice.js
new file mode 100644
index 0000000..efa1a0b
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-serial/waitfordevice.js
@@ -0,0 +1,48 @@
+(function() {
+  var Command, Protocol, WaitForDeviceCommand,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  WaitForDeviceCommand = (function(_super) {
+    __extends(WaitForDeviceCommand, _super);
+
+    function WaitForDeviceCommand() {
+      return WaitForDeviceCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    WaitForDeviceCommand.prototype.execute = function(serial) {
+      this._send("host-serial:" + serial + ":wait-for-any");
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readAscii(4).then(function(reply) {
+                switch (reply) {
+                  case Protocol.OKAY:
+                    return serial;
+                  case Protocol.FAIL:
+                    return _this.parser.readError();
+                  default:
+                    return _this.parser.unexpected(reply, 'OKAY or FAIL');
+                }
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return WaitForDeviceCommand;
+
+  })(Command);
+
+  module.exports = WaitForDeviceCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/clear.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/clear.js
new file mode 100644
index 0000000..7a8acb0
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/clear.js
@@ -0,0 +1,47 @@
+(function() {
+  var ClearCommand, Command, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  ClearCommand = (function(_super) {
+    __extends(ClearCommand, _super);
+
+    function ClearCommand() {
+      return ClearCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    ClearCommand.prototype.execute = function(pkg) {
+      this._send("shell:pm clear " + pkg);
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.searchLine(/^(Success|Failed)$/).then(function(result) {
+                switch (result[0]) {
+                  case 'Success':
+                    return true;
+                  case 'Failed':
+                    _this.connection.end();
+                    throw new Error("Package '" + pkg + "' could not be cleared");
+                }
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return ClearCommand;
+
+  })(Command);
+
+  module.exports = ClearCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/framebuffer.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/framebuffer.js
new file mode 100644
index 0000000..5689f3c
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/framebuffer.js
@@ -0,0 +1,117 @@
+(function() {
+  var Assert, Command, FrameBufferCommand, Protocol, RgbTransform, debug, spawn,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Assert = require('assert');
+
+  spawn = require('child_process').spawn;
+
+  debug = require('debug')('adb:command:framebuffer');
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  RgbTransform = require('../../framebuffer/rgbtransform');
+
+  FrameBufferCommand = (function(_super) {
+    __extends(FrameBufferCommand, _super);
+
+    function FrameBufferCommand() {
+      return FrameBufferCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    FrameBufferCommand.prototype.execute = function(format) {
+      this._send('framebuffer:');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readBytes(52).then(function(header) {
+                var meta, stream;
+                meta = _this._parseHeader(header);
+                switch (format) {
+                  case 'raw':
+                    stream = _this.parser.raw();
+                    stream.meta = meta;
+                    return stream;
+                  default:
+                    stream = _this._convert(meta);
+                    stream.meta = meta;
+                    return stream;
+                }
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    FrameBufferCommand.prototype._convert = function(meta, format, raw) {
+      var proc, transform;
+      debug("Converting raw framebuffer stream into " + (format.toUpperCase()));
+      switch (meta.format) {
+        case 'rgb':
+        case 'rgba':
+          break;
+        default:
+          debug("Silently transforming '" + meta.format + "' into 'rgb' for `gm`");
+          transform = new RgbTransform(meta);
+          meta.format = 'rgb';
+          raw = this.parser.raw().pipe(transform);
+      }
+      proc = spawn('gm', ['convert', '-size', "" + meta.width + "x" + meta.height, "" + meta.format + ":-", "" + format + ":-"]);
+      raw.pipe(proc.stdin);
+      return proc.stdout;
+    };
+
+    FrameBufferCommand.prototype._parseHeader = function(header) {
+      var meta, offset;
+      meta = {};
+      offset = 0;
+      meta.version = header.readUInt32LE(offset);
+      if (meta.version === 16) {
+        throw new Error('Old-style raw images are not supported');
+      }
+      offset += 4;
+      meta.bpp = header.readUInt32LE(offset);
+      offset += 4;
+      meta.size = header.readUInt32LE(offset);
+      offset += 4;
+      meta.width = header.readUInt32LE(offset);
+      offset += 4;
+      meta.height = header.readUInt32LE(offset);
+      offset += 4;
+      meta.red_offset = header.readUInt32LE(offset);
+      offset += 4;
+      meta.red_length = header.readUInt32LE(offset);
+      offset += 4;
+      meta.blue_offset = header.readUInt32LE(offset);
+      offset += 4;
+      meta.blue_length = header.readUInt32LE(offset);
+      offset += 4;
+      meta.green_offset = header.readUInt32LE(offset);
+      offset += 4;
+      meta.green_length = header.readUInt32LE(offset);
+      offset += 4;
+      meta.alpha_offset = header.readUInt32LE(offset);
+      offset += 4;
+      meta.alpha_length = header.readUInt32LE(offset);
+      meta.format = meta.blue_offset === 0 ? 'bgr' : 'rgb';
+      if (meta.bpp === 32 || meta.alpha_length) {
+        meta.format += 'a';
+      }
+      return meta;
+    };
+
+    return FrameBufferCommand;
+
+  })(Command);
+
+  module.exports = FrameBufferCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/getfeatures.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/getfeatures.js
new file mode 100644
index 0000000..be6083d
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/getfeatures.js
@@ -0,0 +1,54 @@
+(function() {
+  var Command, GetFeaturesCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  GetFeaturesCommand = (function(_super) {
+    var RE_FEATURE;
+
+    __extends(GetFeaturesCommand, _super);
+
+    function GetFeaturesCommand() {
+      return GetFeaturesCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    RE_FEATURE = /^feature:(.*?)(?:=(.*?))?\r?$/gm;
+
+    GetFeaturesCommand.prototype.execute = function() {
+      this._send('shell:pm list features 2>/dev/null');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readAll().then(function(data) {
+                return _this._parseFeatures(data.toString());
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    GetFeaturesCommand.prototype._parseFeatures = function(value) {
+      var features, match;
+      features = {};
+      while (match = RE_FEATURE.exec(value)) {
+        features[match[1]] = match[2] || true;
+      }
+      return features;
+    };
+
+    return GetFeaturesCommand;
+
+  })(Command);
+
+  module.exports = GetFeaturesCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/getpackages.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/getpackages.js
new file mode 100644
index 0000000..6e98fdf
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/getpackages.js
@@ -0,0 +1,54 @@
+(function() {
+  var Command, GetPackagesCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  GetPackagesCommand = (function(_super) {
+    var RE_PACKAGE;
+
+    __extends(GetPackagesCommand, _super);
+
+    function GetPackagesCommand() {
+      return GetPackagesCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    RE_PACKAGE = /^package:(.*?)\r?$/gm;
+
+    GetPackagesCommand.prototype.execute = function() {
+      this._send('shell:pm list packages 2>/dev/null');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readAll().then(function(data) {
+                return _this._parsePackages(data.toString());
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    GetPackagesCommand.prototype._parsePackages = function(value) {
+      var features, match;
+      features = [];
+      while (match = RE_PACKAGE.exec(value)) {
+        features.push(match[1]);
+      }
+      return features;
+    };
+
+    return GetPackagesCommand;
+
+  })(Command);
+
+  module.exports = GetPackagesCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/getproperties.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/getproperties.js
new file mode 100644
index 0000000..1c3756b
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/getproperties.js
@@ -0,0 +1,56 @@
+(function() {
+  var Command, GetPropertiesCommand, LineTransform, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  LineTransform = require('../../linetransform');
+
+  GetPropertiesCommand = (function(_super) {
+    var RE_KEYVAL;
+
+    __extends(GetPropertiesCommand, _super);
+
+    function GetPropertiesCommand() {
+      return GetPropertiesCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    RE_KEYVAL = /^\[([\s\S]*?)\]: \[([\s\S]*?)\]\r?$/gm;
+
+    GetPropertiesCommand.prototype.execute = function() {
+      this._send('shell:getprop');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readAll().then(function(data) {
+                return _this._parseProperties(data.toString());
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    GetPropertiesCommand.prototype._parseProperties = function(value) {
+      var match, properties;
+      properties = {};
+      while (match = RE_KEYVAL.exec(value)) {
+        properties[match[1]] = match[2];
+      }
+      return properties;
+    };
+
+    return GetPropertiesCommand;
+
+  })(Command);
+
+  module.exports = GetPropertiesCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/install.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/install.js
new file mode 100644
index 0000000..634dbab
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/install.js
@@ -0,0 +1,51 @@
+(function() {
+  var Command, InstallCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  InstallCommand = (function(_super) {
+    __extends(InstallCommand, _super);
+
+    function InstallCommand() {
+      return InstallCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    InstallCommand.prototype.execute = function(apk) {
+      this._send("shell:pm install -r '" + apk + "'");
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.searchLine(/^(Success|Failure \[(.*?)\])$/).then(function(match) {
+                var code, err;
+                if (match[1] === 'Success') {
+                  return true;
+                } else {
+                  code = match[2];
+                  err = new Error("" + apk + " could not be installed [" + code + "]");
+                  err.code = code;
+                  throw err;
+                }
+              })["finally"](function() {
+                return _this.parser.readAll();
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return InstallCommand;
+
+  })(Command);
+
+  module.exports = InstallCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/isinstalled.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/isinstalled.js
new file mode 100644
index 0000000..463c0f9
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/isinstalled.js
@@ -0,0 +1,50 @@
+(function() {
+  var Command, IsInstalledCommand, Parser, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  Parser = require('../../parser');
+
+  IsInstalledCommand = (function(_super) {
+    __extends(IsInstalledCommand, _super);
+
+    function IsInstalledCommand() {
+      return IsInstalledCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    IsInstalledCommand.prototype.execute = function(pkg) {
+      this._send("shell:pm path " + pkg + " 2>/dev/null");
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readAscii(8).then(function(reply) {
+                switch (reply) {
+                  case 'package:':
+                    return true;
+                  default:
+                    return _this.parser.unexpected(reply, "'package:'");
+                }
+              })["catch"](Parser.PrematureEOFError, function(err) {
+                return false;
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return IsInstalledCommand;
+
+  })(Command);
+
+  module.exports = IsInstalledCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/local.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/local.js
new file mode 100644
index 0000000..fc764a1
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/local.js
@@ -0,0 +1,39 @@
+(function() {
+  var Command, LocalCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  LocalCommand = (function(_super) {
+    __extends(LocalCommand, _super);
+
+    function LocalCommand() {
+      return LocalCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    LocalCommand.prototype.execute = function(path) {
+      this._send(/:/.test(path) ? path : "localfilesystem:" + path);
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.raw();
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return LocalCommand;
+
+  })(Command);
+
+  module.exports = LocalCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/log.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/log.js
new file mode 100644
index 0000000..1e8ddc9
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/log.js
@@ -0,0 +1,39 @@
+(function() {
+  var Command, LogCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  LogCommand = (function(_super) {
+    __extends(LogCommand, _super);
+
+    function LogCommand() {
+      return LogCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    LogCommand.prototype.execute = function(name) {
+      this._send("log:" + name);
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.raw();
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return LogCommand;
+
+  })(Command);
+
+  module.exports = LogCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/logcat.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/logcat.js
new file mode 100644
index 0000000..778ec34
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/logcat.js
@@ -0,0 +1,49 @@
+(function() {
+  var Command, LineTransform, LogcatCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  LineTransform = require('../../linetransform');
+
+  LogcatCommand = (function(_super) {
+    __extends(LogcatCommand, _super);
+
+    function LogcatCommand() {
+      return LogcatCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    LogcatCommand.prototype.execute = function(options) {
+      var cmd;
+      if (options == null) {
+        options = {};
+      }
+      cmd = 'logcat -B *:I 2>/dev/null';
+      if (options.clear) {
+        cmd = "logcat -c 2>/dev/null && " + cmd;
+      }
+      this._send("shell:" + cmd);
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.raw().pipe(new LineTransform);
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return LogcatCommand;
+
+  })(Command);
+
+  module.exports = LogcatCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/monkey.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/monkey.js
new file mode 100644
index 0000000..d913e9e
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/monkey.js
@@ -0,0 +1,45 @@
+(function() {
+  var Command, MonkeyCommand, Promise, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Promise = require('bluebird');
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  MonkeyCommand = (function(_super) {
+    __extends(MonkeyCommand, _super);
+
+    function MonkeyCommand() {
+      return MonkeyCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    MonkeyCommand.prototype.execute = function(port) {
+      this._send("shell:EXTERNAL_STORAGE=/data/local/tmp monkey --port " + port + " -v");
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.searchLine(/^:Monkey:/).timeout(1000).then(function() {
+                return _this.parser.raw();
+              })["catch"](Promise.TimeoutError, function(err) {
+                return _this.parser.raw();
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return MonkeyCommand;
+
+  })(Command);
+
+  module.exports = MonkeyCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/reboot.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/reboot.js
new file mode 100644
index 0000000..57a8b51
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/reboot.js
@@ -0,0 +1,39 @@
+(function() {
+  var Command, Protocol, RebootCommand,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  RebootCommand = (function(_super) {
+    __extends(RebootCommand, _super);
+
+    function RebootCommand() {
+      return RebootCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    RebootCommand.prototype.execute = function() {
+      this._send('reboot:');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readAll()["return"](true);
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return RebootCommand;
+
+  })(Command);
+
+  module.exports = RebootCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/remount.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/remount.js
new file mode 100644
index 0000000..173f45d
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/remount.js
@@ -0,0 +1,39 @@
+(function() {
+  var Command, Protocol, RemountCommand,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  RemountCommand = (function(_super) {
+    __extends(RemountCommand, _super);
+
+    function RemountCommand() {
+      return RemountCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    RemountCommand.prototype.execute = function() {
+      this._send('remount:');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return true;
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return RemountCommand;
+
+  })(Command);
+
+  module.exports = RemountCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/screencap.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/screencap.js
new file mode 100644
index 0000000..30098c2
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/screencap.js
@@ -0,0 +1,57 @@
+(function() {
+  var Command, LineTransform, Parser, Promise, Protocol, ScreencapCommand,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Promise = require('bluebird');
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  Parser = require('../../parser');
+
+  LineTransform = require('../../linetransform');
+
+  ScreencapCommand = (function(_super) {
+    __extends(ScreencapCommand, _super);
+
+    function ScreencapCommand() {
+      return ScreencapCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    ScreencapCommand.prototype.execute = function() {
+      this._send('shell:screencap -p 2>/dev/null');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          var endListener, out, readableListener, resolver;
+          switch (reply) {
+            case Protocol.OKAY:
+              resolver = Promise.defer();
+              out = _this.parser.raw().pipe(new LineTransform);
+              out.on('readable', readableListener = function() {
+                return resolver.resolve(out);
+              });
+              out.on('end', endListener = function() {
+                return resolver.reject(new Error('Unable to run screencap command'));
+              });
+              return resolver.promise["finally"](function() {
+                out.removeListener('end', endListener);
+                return out.removeListener('readable', readableListener);
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return ScreencapCommand;
+
+  })(Command);
+
+  module.exports = ScreencapCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/shell.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/shell.js
new file mode 100644
index 0000000..4ac2074
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/shell.js
@@ -0,0 +1,42 @@
+(function() {
+  var Command, Protocol, ShellCommand,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  ShellCommand = (function(_super) {
+    __extends(ShellCommand, _super);
+
+    function ShellCommand() {
+      return ShellCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    ShellCommand.prototype.execute = function(command) {
+      if (Array.isArray(command)) {
+        command = command.map(this._escape).join(' ');
+      }
+      this._send("shell:" + command);
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.raw();
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return ShellCommand;
+
+  })(Command);
+
+  module.exports = ShellCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/startactivity.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/startactivity.js
new file mode 100644
index 0000000..572abda
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/startactivity.js
@@ -0,0 +1,187 @@
+(function() {
+  var Command, Parser, Protocol, StartActivityCommand,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  Parser = require('../../parser');
+
+  StartActivityCommand = (function(_super) {
+    var EXTRA_TYPES, RE_ERROR;
+
+    __extends(StartActivityCommand, _super);
+
+    function StartActivityCommand() {
+      return StartActivityCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    RE_ERROR = /^Error: (.*)$/;
+
+    EXTRA_TYPES = {
+      string: 's',
+      "null": 'sn',
+      bool: 'z',
+      int: 'i',
+      long: 'l',
+      float: 'l',
+      uri: 'u',
+      component: 'cn'
+    };
+
+    StartActivityCommand.prototype.execute = function(options) {
+      var args;
+      args = this._intentArgs(options);
+      if (options.debug) {
+        args.push('-D');
+      }
+      if (options.wait) {
+        args.push('-W');
+      }
+      if (options.user || options.user === 0) {
+        args.push('--user', this._escape(options.user));
+      }
+      return this._run('start', args);
+    };
+
+    StartActivityCommand.prototype._run = function(command, args) {
+      this._send("shell:am " + command + " " + (args.join(' ')));
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.searchLine(RE_ERROR)["finally"](function() {
+                return _this.connection.end();
+              }).then(function(match) {
+                throw new Error(match[1]);
+              })["catch"](Parser.PrematureEOFError, function(err) {
+                return true;
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    StartActivityCommand.prototype._intentArgs = function(options) {
+      var args;
+      args = [];
+      if (options.extras) {
+        args.push.apply(args, this._formatExtras(options.extras));
+      }
+      if (options.action) {
+        args.push('-a', this._escape(options.action));
+      }
+      if (options.data) {
+        args.push('-d', this._escape(options.data));
+      }
+      if (options.mimeType) {
+        args.push('-t', this._escape(options.mimeType));
+      }
+      if (options.category) {
+        if (Array.isArray(options.category)) {
+          options.category.forEach((function(_this) {
+            return function(category) {
+              return args.push('-c', _this._escape(category));
+            };
+          })(this));
+        } else {
+          args.push('-c', this._escape(options.category));
+        }
+      }
+      if (options.component) {
+        args.push('-n', this._escape(options.component));
+      }
+      if (options.flags) {
+        args.push('-f', this._escape(options.flags));
+      }
+      return args;
+    };
+
+    StartActivityCommand.prototype._formatExtras = function(extras) {
+      if (!extras) {
+        return [];
+      }
+      if (Array.isArray(extras)) {
+        return extras.reduce((function(_this) {
+          return function(all, extra) {
+            return all.concat(_this._formatLongExtra(extra));
+          };
+        })(this), []);
+      } else {
+        return Object.keys(extras).reduce((function(_this) {
+          return function(all, key) {
+            return all.concat(_this._formatShortExtra(key, extras[key]));
+          };
+        })(this), []);
+      }
+    };
+
+    StartActivityCommand.prototype._formatShortExtra = function(key, value) {
+      var sugared;
+      sugared = {
+        key: key
+      };
+      if (value === null) {
+        sugared.type = 'null';
+      } else if (Array.isArray(value)) {
+        throw new Error("Refusing to format array value '" + key + "' using short syntax; empty array would cause unpredictable results due to unknown type. Please use long syntax instead.");
+      } else {
+        switch (typeof value) {
+          case 'string':
+            sugared.type = 'string';
+            sugared.value = value;
+            break;
+          case 'boolean':
+            sugared.type = 'bool';
+            sugared.value = value;
+            break;
+          case 'number':
+            sugared.type = 'int';
+            sugared.value = value;
+            break;
+          case 'object':
+            sugared = value;
+            sugared.key = key;
+        }
+      }
+      return this._formatLongExtra(sugared);
+    };
+
+    StartActivityCommand.prototype._formatLongExtra = function(extra) {
+      var args, type;
+      args = [];
+      if (!extra.type) {
+        extra.type = 'string';
+      }
+      type = EXTRA_TYPES[extra.type];
+      if (!type) {
+        throw new Error("Unsupported type '" + extra.type + "' for extra '" + extra.key + "'");
+      }
+      if (extra.type === 'null') {
+        args.push("--e" + type);
+        args.push(this._escape(extra.key));
+      } else if (Array.isArray(extra.value)) {
+        args.push("--e" + type + "a");
+        args.push(this._escape(extra.key));
+        args.push(this._escape(extra.value.join(',')));
+      } else {
+        args.push("--e" + type);
+        args.push(this._escape(extra.key));
+        args.push(this._escape(extra.value));
+      }
+      return args;
+    };
+
+    return StartActivityCommand;
+
+  })(Command);
+
+  module.exports = StartActivityCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/startservice.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/startservice.js
new file mode 100644
index 0000000..a119a36
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/startservice.js
@@ -0,0 +1,36 @@
+(function() {
+  var Command, Parser, Protocol, StartActivityCommand, StartServiceCommand,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  Parser = require('../../parser');
+
+  StartActivityCommand = require('./startactivity');
+
+  StartServiceCommand = (function(_super) {
+    __extends(StartServiceCommand, _super);
+
+    function StartServiceCommand() {
+      return StartServiceCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    StartServiceCommand.prototype.execute = function(options) {
+      var args;
+      args = this._intentArgs(options);
+      if (options.user || options.user === 0) {
+        args.push('--user', this._escape(options.user));
+      }
+      return this._run('startservice', args);
+    };
+
+    return StartServiceCommand;
+
+  })(StartActivityCommand);
+
+  module.exports = StartServiceCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/sync.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/sync.js
new file mode 100644
index 0000000..638cc6e
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/sync.js
@@ -0,0 +1,41 @@
+(function() {
+  var Command, Protocol, Sync, SyncCommand,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  Sync = require('../../sync');
+
+  SyncCommand = (function(_super) {
+    __extends(SyncCommand, _super);
+
+    function SyncCommand() {
+      return SyncCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    SyncCommand.prototype.execute = function() {
+      this._send('sync:');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return new Sync(_this.connection);
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return SyncCommand;
+
+  })(Command);
+
+  module.exports = SyncCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/tcp.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/tcp.js
new file mode 100644
index 0000000..05c7ec2
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/tcp.js
@@ -0,0 +1,39 @@
+(function() {
+  var Command, Protocol, TcpCommand,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  TcpCommand = (function(_super) {
+    __extends(TcpCommand, _super);
+
+    function TcpCommand() {
+      return TcpCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    TcpCommand.prototype.execute = function(port, host) {
+      this._send(("tcp:" + port) + (host ? ":" + host : ''));
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.raw();
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return TcpCommand;
+
+  })(Command);
+
+  module.exports = TcpCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/tcpip.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/tcpip.js
new file mode 100644
index 0000000..821b5fa
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/tcpip.js
@@ -0,0 +1,51 @@
+(function() {
+  var Command, LineTransform, Protocol, TcpIpCommand,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  LineTransform = require('../../linetransform');
+
+  TcpIpCommand = (function(_super) {
+    var RE_OK;
+
+    __extends(TcpIpCommand, _super);
+
+    function TcpIpCommand() {
+      return TcpIpCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    RE_OK = /restarting in/;
+
+    TcpIpCommand.prototype.execute = function(port) {
+      this._send("tcpip:" + port);
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readAll().then(function(value) {
+                if (RE_OK.test(value)) {
+                  return port;
+                } else {
+                  throw new Error(value.toString().trim());
+                }
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return TcpIpCommand;
+
+  })(Command);
+
+  module.exports = TcpIpCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/trackjdwp.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/trackjdwp.js
new file mode 100644
index 0000000..07ec6a9
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/trackjdwp.js
@@ -0,0 +1,123 @@
+(function() {
+  var Command, EventEmitter, Parser, Promise, Protocol, TrackJdwpCommand,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  EventEmitter = require('events').EventEmitter;
+
+  Promise = require('bluebird');
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  Parser = require('../../parser');
+
+  TrackJdwpCommand = (function(_super) {
+    var Tracker;
+
+    __extends(TrackJdwpCommand, _super);
+
+    function TrackJdwpCommand() {
+      return TrackJdwpCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    TrackJdwpCommand.prototype.execute = function() {
+      this._send('track-jdwp');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return new Tracker(_this);
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    Tracker = (function(_super1) {
+      __extends(Tracker, _super1);
+
+      function Tracker(command) {
+        this.command = command;
+        this.pids = [];
+        this.pidMap = Object.create(null);
+        this.reader = this.read()["catch"](Parser.PrematureEOFError, (function(_this) {
+          return function(err) {
+            return _this.emit('end');
+          };
+        })(this))["catch"](Promise.CancellationError, (function(_this) {
+          return function(err) {
+            _this.command.connection.end();
+            return _this.emit('end');
+          };
+        })(this))["catch"]((function(_this) {
+          return function(err) {
+            _this.command.connection.end();
+            _this.emit('error', err);
+            return _this.emit('end');
+          };
+        })(this));
+      }
+
+      Tracker.prototype.read = function() {
+        return this.command.parser.readValue().cancellable().then((function(_this) {
+          return function(list) {
+            var maybeEmpty, pids;
+            pids = list.toString().split('\n');
+            if (maybeEmpty = pids.pop()) {
+              pids.push(maybeEmpty);
+            }
+            return _this.update(pids);
+          };
+        })(this));
+      };
+
+      Tracker.prototype.update = function(newList) {
+        var changeSet, newMap, pid, _i, _j, _len, _len1, _ref;
+        changeSet = {
+          removed: [],
+          added: []
+        };
+        newMap = Object.create(null);
+        for (_i = 0, _len = newList.length; _i < _len; _i++) {
+          pid = newList[_i];
+          if (!this.pidMap[pid]) {
+            changeSet.added.push(pid);
+            this.emit('add', pid);
+            newMap[pid] = pid;
+          }
+        }
+        _ref = this.pids;
+        for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) {
+          pid = _ref[_j];
+          if (!newMap[pid]) {
+            changeSet.removed.push(pid);
+            this.emit('remove', pid);
+          }
+        }
+        this.pids = newList;
+        this.pidMap = newMap;
+        this.emit('changeSet', changeSet, newList);
+        return this;
+      };
+
+      Tracker.prototype.end = function() {
+        this.reader.cancel();
+        return this;
+      };
+
+      return Tracker;
+
+    })(EventEmitter);
+
+    return TrackJdwpCommand;
+
+  })(Command);
+
+  module.exports = TrackJdwpCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/uninstall.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/uninstall.js
new file mode 100644
index 0000000..0fdb286
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/uninstall.js
@@ -0,0 +1,47 @@
+(function() {
+  var Command, Protocol, UninstallCommand,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  UninstallCommand = (function(_super) {
+    __extends(UninstallCommand, _super);
+
+    function UninstallCommand() {
+      return UninstallCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    UninstallCommand.prototype.execute = function(pkg) {
+      this._send("shell:pm uninstall " + pkg + " 2>/dev/null");
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readAscii(7).then(function(reply) {
+                switch (reply) {
+                  case 'Success':
+                  case 'Failure':
+                    return true;
+                  default:
+                    return _this.parser.unexpected(reply, "'Success' or 'Failure'");
+                }
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, "OKAY or FAIL");
+          }
+        };
+      })(this));
+    };
+
+    return UninstallCommand;
+
+  })(Command);
+
+  module.exports = UninstallCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/usb.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/usb.js
new file mode 100644
index 0000000..8c5e797
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/usb.js
@@ -0,0 +1,51 @@
+(function() {
+  var Command, LineTransform, Protocol, UsbCommand,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  LineTransform = require('../../linetransform');
+
+  UsbCommand = (function(_super) {
+    var RE_OK;
+
+    __extends(UsbCommand, _super);
+
+    function UsbCommand() {
+      return UsbCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    RE_OK = /restarting in/;
+
+    UsbCommand.prototype.execute = function() {
+      this._send('usb:');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readAll().then(function(value) {
+                if (RE_OK.test(value)) {
+                  return true;
+                } else {
+                  throw new Error(value.toString().trim());
+                }
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return UsbCommand;
+
+  })(Command);
+
+  module.exports = UsbCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/waitbootcomplete.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/waitbootcomplete.js
new file mode 100644
index 0000000..739e397
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host-transport/waitbootcomplete.js
@@ -0,0 +1,45 @@
+(function() {
+  var Command, Protocol, WaitBootCompleteCommand, debug,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  debug = require('debug')('adb:command:waitboot');
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  WaitBootCompleteCommand = (function(_super) {
+    __extends(WaitBootCompleteCommand, _super);
+
+    function WaitBootCompleteCommand() {
+      return WaitBootCompleteCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    WaitBootCompleteCommand.prototype.execute = function() {
+      this._send('shell:while getprop sys.boot_completed 2>/dev/null; do sleep 1; done');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.searchLine(/^1$/)["finally"](function() {
+                return _this.connection.end();
+              }).then(function() {
+                return true;
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return WaitBootCompleteCommand;
+
+  })(Command);
+
+  module.exports = WaitBootCompleteCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host/connect.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host/connect.js
new file mode 100644
index 0000000..fd9da1c
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host/connect.js
@@ -0,0 +1,49 @@
+(function() {
+  var Command, ConnectCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  ConnectCommand = (function(_super) {
+    var RE_OK;
+
+    __extends(ConnectCommand, _super);
+
+    function ConnectCommand() {
+      return ConnectCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    RE_OK = /connected to|already connected/;
+
+    ConnectCommand.prototype.execute = function(host, port) {
+      this._send("host:connect:" + host + ":" + port);
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readValue().then(function(value) {
+                if (RE_OK.test(value)) {
+                  return "" + host + ":" + port;
+                } else {
+                  throw new Error(value.toString());
+                }
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return ConnectCommand;
+
+  })(Command);
+
+  module.exports = ConnectCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host/devices.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host/devices.js
new file mode 100644
index 0000000..e9d5a46
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host/devices.js
@@ -0,0 +1,67 @@
+(function() {
+  var Command, HostDevicesCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  HostDevicesCommand = (function(_super) {
+    __extends(HostDevicesCommand, _super);
+
+    function HostDevicesCommand() {
+      return HostDevicesCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    HostDevicesCommand.prototype.execute = function() {
+      this._send('host:devices');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this._readDevices();
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    HostDevicesCommand.prototype._readDevices = function() {
+      return this.parser.readValue().then((function(_this) {
+        return function(value) {
+          return _this._parseDevices(value);
+        };
+      })(this));
+    };
+
+    HostDevicesCommand.prototype._parseDevices = function(value) {
+      var devices, id, line, type, _i, _len, _ref, _ref1;
+      devices = [];
+      if (!value.length) {
+        return devices;
+      }
+      _ref = value.toString('ascii').split('\n');
+      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+        line = _ref[_i];
+        if (line) {
+          _ref1 = line.split('\t'), id = _ref1[0], type = _ref1[1];
+          devices.push({
+            id: id,
+            type: type
+          });
+        }
+      }
+      return devices;
+    };
+
+    return HostDevicesCommand;
+
+  })(Command);
+
+  module.exports = HostDevicesCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host/deviceswithpaths.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host/deviceswithpaths.js
new file mode 100644
index 0000000..a63cdf1
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host/deviceswithpaths.js
@@ -0,0 +1,68 @@
+(function() {
+  var Command, HostDevicesWithPathsCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  HostDevicesWithPathsCommand = (function(_super) {
+    __extends(HostDevicesWithPathsCommand, _super);
+
+    function HostDevicesWithPathsCommand() {
+      return HostDevicesWithPathsCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    HostDevicesWithPathsCommand.prototype.execute = function() {
+      this._send('host:devices-l');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this._readDevices();
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    HostDevicesWithPathsCommand.prototype._readDevices = function() {
+      return this.parser.readValue().then((function(_this) {
+        return function(value) {
+          return _this._parseDevices(value);
+        };
+      })(this));
+    };
+
+    HostDevicesWithPathsCommand.prototype._parseDevices = function(value) {
+      var devices, id, line, path, type, _i, _len, _ref, _ref1;
+      devices = [];
+      if (!value.length) {
+        return devices;
+      }
+      _ref = value.toString('ascii').split('\n');
+      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+        line = _ref[_i];
+        if (line) {
+          _ref1 = line.split(/\s+/), id = _ref1[0], type = _ref1[1], path = _ref1[2];
+          devices.push({
+            id: id,
+            type: type,
+            path: path
+          });
+        }
+      }
+      return devices;
+    };
+
+    return HostDevicesWithPathsCommand;
+
+  })(Command);
+
+  module.exports = HostDevicesWithPathsCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host/disconnect.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host/disconnect.js
new file mode 100644
index 0000000..9cf978e
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host/disconnect.js
@@ -0,0 +1,49 @@
+(function() {
+  var Command, DisconnectCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  DisconnectCommand = (function(_super) {
+    var RE_OK;
+
+    __extends(DisconnectCommand, _super);
+
+    function DisconnectCommand() {
+      return DisconnectCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    RE_OK = /^$/;
+
+    DisconnectCommand.prototype.execute = function(host, port) {
+      this._send("host:disconnect:" + host + ":" + port);
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readValue().then(function(value) {
+                if (RE_OK.test(value)) {
+                  return "" + host + ":" + port;
+                } else {
+                  throw new Error(value.toString());
+                }
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return DisconnectCommand;
+
+  })(Command);
+
+  module.exports = DisconnectCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host/kill.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host/kill.js
new file mode 100644
index 0000000..74d531c
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host/kill.js
@@ -0,0 +1,39 @@
+(function() {
+  var Command, HostKillCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  HostKillCommand = (function(_super) {
+    __extends(HostKillCommand, _super);
+
+    function HostKillCommand() {
+      return HostKillCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    HostKillCommand.prototype.execute = function() {
+      this._send('host:kill');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return true;
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return HostKillCommand;
+
+  })(Command);
+
+  module.exports = HostKillCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host/trackdevices.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host/trackdevices.js
new file mode 100644
index 0000000..103793a
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host/trackdevices.js
@@ -0,0 +1,43 @@
+(function() {
+  var Command, HostDevicesCommand, HostTrackDevicesCommand, Protocol, Tracker,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  Tracker = require('../../tracker');
+
+  HostDevicesCommand = require('./devices');
+
+  HostTrackDevicesCommand = (function(_super) {
+    __extends(HostTrackDevicesCommand, _super);
+
+    function HostTrackDevicesCommand() {
+      return HostTrackDevicesCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    HostTrackDevicesCommand.prototype.execute = function() {
+      this._send('host:track-devices');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return new Tracker(_this);
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return HostTrackDevicesCommand;
+
+  })(HostDevicesCommand);
+
+  module.exports = HostTrackDevicesCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host/transport.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host/transport.js
new file mode 100644
index 0000000..8264c4f
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host/transport.js
@@ -0,0 +1,39 @@
+(function() {
+  var Command, HostTransportCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  HostTransportCommand = (function(_super) {
+    __extends(HostTransportCommand, _super);
+
+    function HostTransportCommand() {
+      return HostTransportCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    HostTransportCommand.prototype.execute = function(serial) {
+      this._send("host:transport:" + serial);
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return true;
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this.parser.unexpected(reply, 'OKAY or FAIL');
+          }
+        };
+      })(this));
+    };
+
+    return HostTransportCommand;
+
+  })(Command);
+
+  module.exports = HostTransportCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host/version.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host/version.js
new file mode 100644
index 0000000..48accc1
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/command/host/version.js
@@ -0,0 +1,45 @@
+(function() {
+  var Command, HostVersionCommand, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Command = require('../../command');
+
+  Protocol = require('../../protocol');
+
+  HostVersionCommand = (function(_super) {
+    __extends(HostVersionCommand, _super);
+
+    function HostVersionCommand() {
+      return HostVersionCommand.__super__.constructor.apply(this, arguments);
+    }
+
+    HostVersionCommand.prototype.execute = function() {
+      this._send('host:version');
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.OKAY:
+              return _this.parser.readValue().then(function(value) {
+                return _this._parseVersion(value);
+              });
+            case Protocol.FAIL:
+              return _this.parser.readError();
+            default:
+              return _this._parseVersion(reply);
+          }
+        };
+      })(this));
+    };
+
+    HostVersionCommand.prototype._parseVersion = function(version) {
+      return parseInt(version, 16);
+    };
+
+    return HostVersionCommand;
+
+  })(Command);
+
+  module.exports = HostVersionCommand;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/connection.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/connection.js
new file mode 100644
index 0000000..bccd503
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/connection.js
@@ -0,0 +1,108 @@
+(function() {
+  var Connection, EventEmitter, Net, Parser, debug, execFile,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Net = require('net');
+
+  debug = require('debug')('adb:connection');
+
+  EventEmitter = require('events').EventEmitter;
+
+  execFile = require('child_process').execFile;
+
+  Parser = require('./parser');
+
+  Connection = (function(_super) {
+    __extends(Connection, _super);
+
+    function Connection(options) {
+      this.options = options;
+      this.socket = null;
+      this.parser = null;
+      this.triedStarting = false;
+    }
+
+    Connection.prototype.connect = function() {
+      this.socket = Net.connect(this.options);
+      this.parser = new Parser(this.socket);
+      this.socket.on('connect', (function(_this) {
+        return function() {
+          return _this.emit('connect');
+        };
+      })(this));
+      this.socket.on('end', (function(_this) {
+        return function() {
+          return _this.emit('end');
+        };
+      })(this));
+      this.socket.on('drain', (function(_this) {
+        return function() {
+          return _this.emit('drain');
+        };
+      })(this));
+      this.socket.on('timeout', (function(_this) {
+        return function() {
+          return _this.emit('timeout');
+        };
+      })(this));
+      this.socket.on('error', (function(_this) {
+        return function(err) {
+          return _this._handleError(err);
+        };
+      })(this));
+      this.socket.on('close', (function(_this) {
+        return function(hadError) {
+          return _this.emit('close', hadError);
+        };
+      })(this));
+      return this;
+    };
+
+    Connection.prototype.end = function() {
+      this.socket.end();
+      return this;
+    };
+
+    Connection.prototype.write = function(data, callback) {
+      this.socket.write(data, callback);
+      return this;
+    };
+
+    Connection.prototype.startServer = function(callback) {
+      debug("Starting ADB server via '" + this.options.bin + " start-server'");
+      return this._exec(['start-server'], {}, callback);
+    };
+
+    Connection.prototype._exec = function(args, options, callback) {
+      debug("CLI: " + this.options.bin + " " + (args.join(' ')));
+      execFile(this.options.bin, args, options, callback);
+      return this;
+    };
+
+    Connection.prototype._handleError = function(err) {
+      if (err.code === 'ECONNREFUSED' && !this.triedStarting) {
+        debug("Connection was refused, let's try starting the server once");
+        this.triedStarting = true;
+        this.startServer((function(_this) {
+          return function(err) {
+            if (err) {
+              return _this._handleError(err);
+            }
+            return _this.connect();
+          };
+        })(this));
+      } else {
+        debug("Connection had an error: " + err.message);
+        this.emit('error', err);
+        this.end();
+      }
+    };
+
+    return Connection;
+
+  })(EventEmitter);
+
+  module.exports = Connection;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/framebuffer/rgbtransform.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/framebuffer/rgbtransform.js
new file mode 100644
index 0000000..e759722
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/framebuffer/rgbtransform.js
@@ -0,0 +1,58 @@
+(function() {
+  var Assert, RgbTransform, Stream,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Assert = require('assert');
+
+  Stream = require('stream');
+
+  RgbTransform = (function(_super) {
+    __extends(RgbTransform, _super);
+
+    function RgbTransform(meta, options) {
+      this.meta = meta;
+      this._buffer = new Buffer('');
+      Assert.ok(this.meta.bpp === 24 || this.meta.bpp === 32, 'Only 24-bit and 32-bit raw images with 8-bits per color are supported');
+      this._r_pos = this.meta.red_offset / 8;
+      this._g_pos = this.meta.green_offset / 8;
+      this._b_pos = this.meta.blue_offset / 8;
+      this._a_pos = this.meta.alpha_offset / 8;
+      this._pixel_bytes = this.meta.bpp / 8;
+      RgbTransform.__super__.constructor.call(this, options);
+    }
+
+    RgbTransform.prototype._transform = function(chunk, encoding, done) {
+      var b, g, r, sourceCursor, target, targetCursor;
+      if (this._buffer.length) {
+        this._buffer = Buffer.concat([this._buffer, chunk], this._buffer.length + chunk.length);
+      } else {
+        this._buffer = chunk;
+      }
+      sourceCursor = 0;
+      targetCursor = 0;
+      target = this._pixel_bytes === 3 ? this._buffer : new Buffer(Math.max(4, chunk.length / this._pixel_bytes * 3));
+      while (this._buffer.length - sourceCursor >= this._pixel_bytes) {
+        r = this._buffer[sourceCursor + this._r_pos];
+        g = this._buffer[sourceCursor + this._g_pos];
+        b = this._buffer[sourceCursor + this._b_pos];
+        target[targetCursor + 0] = r;
+        target[targetCursor + 1] = g;
+        target[targetCursor + 2] = b;
+        sourceCursor += this._pixel_bytes;
+        targetCursor += 3;
+      }
+      if (targetCursor) {
+        this.push(target.slice(0, targetCursor));
+        this._buffer = this._buffer.slice(sourceCursor);
+      }
+      done();
+    };
+
+    return RgbTransform;
+
+  })(Stream.Transform);
+
+  module.exports = RgbTransform;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/keycode.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/keycode.js
new file mode 100644
index 0000000..0ca3c08
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/keycode.js
@@ -0,0 +1,228 @@
+(function() {
+  module.exports = {
+    KEYCODE_UNKNOWN: 0,
+    KEYCODE_SOFT_LEFT: 1,
+    KEYCODE_SOFT_RIGHT: 2,
+    KEYCODE_HOME: 3,
+    KEYCODE_BACK: 4,
+    KEYCODE_CALL: 5,
+    KEYCODE_ENDCALL: 6,
+    KEYCODE_0: 7,
+    KEYCODE_1: 8,
+    KEYCODE_2: 9,
+    KEYCODE_3: 10,
+    KEYCODE_4: 11,
+    KEYCODE_5: 12,
+    KEYCODE_6: 13,
+    KEYCODE_7: 14,
+    KEYCODE_8: 15,
+    KEYCODE_9: 16,
+    KEYCODE_STAR: 17,
+    KEYCODE_POUND: 18,
+    KEYCODE_DPAD_UP: 19,
+    KEYCODE_DPAD_DOWN: 20,
+    KEYCODE_DPAD_LEFT: 21,
+    KEYCODE_DPAD_RIGHT: 22,
+    KEYCODE_DPAD_CENTER: 23,
+    KEYCODE_VOLUME_UP: 24,
+    KEYCODE_VOLUME_DOWN: 25,
+    KEYCODE_POWER: 26,
+    KEYCODE_CAMERA: 27,
+    KEYCODE_CLEAR: 28,
+    KEYCODE_A: 29,
+    KEYCODE_B: 30,
+    KEYCODE_C: 31,
+    KEYCODE_D: 32,
+    KEYCODE_E: 33,
+    KEYCODE_F: 34,
+    KEYCODE_G: 35,
+    KEYCODE_H: 36,
+    KEYCODE_I: 37,
+    KEYCODE_J: 38,
+    KEYCODE_K: 39,
+    KEYCODE_L: 40,
+    KEYCODE_M: 41,
+    KEYCODE_N: 42,
+    KEYCODE_O: 43,
+    KEYCODE_P: 44,
+    KEYCODE_Q: 45,
+    KEYCODE_R: 46,
+    KEYCODE_S: 47,
+    KEYCODE_T: 48,
+    KEYCODE_U: 49,
+    KEYCODE_V: 50,
+    KEYCODE_W: 51,
+    KEYCODE_X: 52,
+    KEYCODE_Y: 53,
+    KEYCODE_Z: 54,
+    KEYCODE_COMMA: 55,
+    KEYCODE_PERIOD: 56,
+    KEYCODE_ALT_LEFT: 57,
+    KEYCODE_ALT_RIGHT: 58,
+    KEYCODE_SHIFT_LEFT: 59,
+    KEYCODE_SHIFT_RIGHT: 60,
+    KEYCODE_TAB: 61,
+    KEYCODE_SPACE: 62,
+    KEYCODE_SYM: 63,
+    KEYCODE_EXPLORER: 64,
+    KEYCODE_ENVELOPE: 65,
+    KEYCODE_ENTER: 66,
+    KEYCODE_DEL: 67,
+    KEYCODE_GRAVE: 68,
+    KEYCODE_MINUS: 69,
+    KEYCODE_EQUALS: 70,
+    KEYCODE_LEFT_BRACKET: 71,
+    KEYCODE_RIGHT_BRACKET: 72,
+    KEYCODE_BACKSLASH: 73,
+    KEYCODE_SEMICOLON: 74,
+    KEYCODE_APOSTROPHE: 75,
+    KEYCODE_SLASH: 76,
+    KEYCODE_AT: 77,
+    KEYCODE_NUM: 78,
+    KEYCODE_HEADSETHOOK: 79,
+    KEYCODE_FOCUS: 80,
+    KEYCODE_PLUS: 81,
+    KEYCODE_MENU: 82,
+    KEYCODE_NOTIFICATION: 83,
+    KEYCODE_SEARCH: 84,
+    KEYCODE_MEDIA_PLAY_PAUSE: 85,
+    KEYCODE_MEDIA_STOP: 86,
+    KEYCODE_MEDIA_NEXT: 87,
+    KEYCODE_MEDIA_PREVIOUS: 88,
+    KEYCODE_MEDIA_REWIND: 89,
+    KEYCODE_MEDIA_FAST_FORWARD: 90,
+    KEYCODE_MUTE: 91,
+    KEYCODE_PAGE_UP: 92,
+    KEYCODE_PAGE_DOWN: 93,
+    KEYCODE_PICTSYMBOLS: 94,
+    KEYCODE_SWITCH_CHARSET: 95,
+    KEYCODE_BUTTON_A: 96,
+    KEYCODE_BUTTON_B: 97,
+    KEYCODE_BUTTON_C: 98,
+    KEYCODE_BUTTON_X: 99,
+    KEYCODE_BUTTON_Y: 100,
+    KEYCODE_BUTTON_Z: 101,
+    KEYCODE_BUTTON_L1: 102,
+    KEYCODE_BUTTON_R1: 103,
+    KEYCODE_BUTTON_L2: 104,
+    KEYCODE_BUTTON_R2: 105,
+    KEYCODE_BUTTON_THUMBL: 106,
+    KEYCODE_BUTTON_THUMBR: 107,
+    KEYCODE_BUTTON_START: 108,
+    KEYCODE_BUTTON_SELECT: 109,
+    KEYCODE_BUTTON_MODE: 110,
+    KEYCODE_ESCAPE: 111,
+    KEYCODE_FORWARD_DEL: 112,
+    KEYCODE_CTRL_LEFT: 113,
+    KEYCODE_CTRL_RIGHT: 114,
+    KEYCODE_CAPS_LOCK: 115,
+    KEYCODE_SCROLL_LOCK: 116,
+    KEYCODE_META_LEFT: 117,
+    KEYCODE_META_RIGHT: 118,
+    KEYCODE_FUNCTION: 119,
+    KEYCODE_SYSRQ: 120,
+    KEYCODE_BREAK: 121,
+    KEYCODE_MOVE_HOME: 122,
+    KEYCODE_MOVE_END: 123,
+    KEYCODE_INSERT: 124,
+    KEYCODE_FORWARD: 125,
+    KEYCODE_MEDIA_PLAY: 126,
+    KEYCODE_MEDIA_PAUSE: 127,
+    KEYCODE_MEDIA_CLOSE: 128,
+    KEYCODE_MEDIA_EJECT: 129,
+    KEYCODE_MEDIA_RECORD: 130,
+    KEYCODE_F1: 131,
+    KEYCODE_F2: 132,
+    KEYCODE_F3: 133,
+    KEYCODE_F4: 134,
+    KEYCODE_F5: 135,
+    KEYCODE_F6: 136,
+    KEYCODE_F7: 137,
+    KEYCODE_F8: 138,
+    KEYCODE_F9: 139,
+    KEYCODE_F10: 140,
+    KEYCODE_F11: 141,
+    KEYCODE_F12: 142,
+    KEYCODE_NUM_LOCK: 143,
+    KEYCODE_NUMPAD_0: 144,
+    KEYCODE_NUMPAD_1: 145,
+    KEYCODE_NUMPAD_2: 146,
+    KEYCODE_NUMPAD_3: 147,
+    KEYCODE_NUMPAD_4: 148,
+    KEYCODE_NUMPAD_5: 149,
+    KEYCODE_NUMPAD_6: 150,
+    KEYCODE_NUMPAD_7: 151,
+    KEYCODE_NUMPAD_8: 152,
+    KEYCODE_NUMPAD_9: 153,
+    KEYCODE_NUMPAD_DIVIDE: 154,
+    KEYCODE_NUMPAD_MULTIPLY: 155,
+    KEYCODE_NUMPAD_SUBTRACT: 156,
+    KEYCODE_NUMPAD_ADD: 157,
+    KEYCODE_NUMPAD_DOT: 158,
+    KEYCODE_NUMPAD_COMMA: 159,
+    KEYCODE_NUMPAD_ENTER: 160,
+    KEYCODE_NUMPAD_EQUALS: 161,
+    KEYCODE_NUMPAD_LEFT_PAREN: 162,
+    KEYCODE_NUMPAD_RIGHT_PAREN: 163,
+    KEYCODE_VOLUME_MUTE: 164,
+    KEYCODE_INFO: 165,
+    KEYCODE_CHANNEL_UP: 166,
+    KEYCODE_CHANNEL_DOWN: 167,
+    KEYCODE_ZOOM_IN: 168,
+    KEYCODE_ZOOM_OUT: 169,
+    KEYCODE_TV: 170,
+    KEYCODE_WINDOW: 171,
+    KEYCODE_GUIDE: 172,
+    KEYCODE_DVR: 173,
+    KEYCODE_BOOKMARK: 174,
+    KEYCODE_CAPTIONS: 175,
+    KEYCODE_SETTINGS: 176,
+    KEYCODE_TV_POWER: 177,
+    KEYCODE_TV_INPUT: 178,
+    KEYCODE_STB_POWER: 179,
+    KEYCODE_STB_INPUT: 180,
+    KEYCODE_AVR_POWER: 181,
+    KEYCODE_AVR_INPUT: 182,
+    KEYCODE_PROG_RED: 183,
+    KEYCODE_PROG_GREEN: 184,
+    KEYCODE_PROG_YELLOW: 185,
+    KEYCODE_PROG_BLUE: 186,
+    KEYCODE_APP_SWITCH: 187,
+    KEYCODE_BUTTON_1: 188,
+    KEYCODE_BUTTON_2: 189,
+    KEYCODE_BUTTON_3: 190,
+    KEYCODE_BUTTON_4: 191,
+    KEYCODE_BUTTON_5: 192,
+    KEYCODE_BUTTON_6: 193,
+    KEYCODE_BUTTON_7: 194,
+    KEYCODE_BUTTON_8: 195,
+    KEYCODE_BUTTON_9: 196,
+    KEYCODE_BUTTON_10: 197,
+    KEYCODE_BUTTON_11: 198,
+    KEYCODE_BUTTON_12: 199,
+    KEYCODE_BUTTON_13: 200,
+    KEYCODE_BUTTON_14: 201,
+    KEYCODE_BUTTON_15: 202,
+    KEYCODE_BUTTON_16: 203,
+    KEYCODE_LANGUAGE_SWITCH: 204,
+    KEYCODE_MANNER_MODE: 205,
+    KEYCODE_3D_MODE: 206,
+    KEYCODE_CONTACTS: 207,
+    KEYCODE_CALENDAR: 208,
+    KEYCODE_MUSIC: 209,
+    KEYCODE_CALCULATOR: 210,
+    KEYCODE_ZENKAKU_HANKAKU: 211,
+    KEYCODE_EISU: 212,
+    KEYCODE_MUHENKAN: 213,
+    KEYCODE_HENKAN: 214,
+    KEYCODE_KATAKANA_HIRAGANA: 215,
+    KEYCODE_YEN: 216,
+    KEYCODE_RO: 217,
+    KEYCODE_KANA: 218,
+    KEYCODE_ASSIST: 219,
+    KEYCODE_BRIGHTNESS_DOWN: 220,
+    KEYCODE_BRIGHTNESS_UP: 221,
+    KEYCODE_MEDIA_AUDIO_TRACK: 222
+  };
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/linetransform.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/linetransform.js
new file mode 100644
index 0000000..05efce1
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/linetransform.js
@@ -0,0 +1,58 @@
+(function() {
+  var LineTransform, Stream,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Stream = require('stream');
+
+  LineTransform = (function(_super) {
+    __extends(LineTransform, _super);
+
+    function LineTransform(options) {
+      this.savedR = null;
+      LineTransform.__super__.constructor.call(this, options);
+    }
+
+    LineTransform.prototype._transform = function(chunk, encoding, done) {
+      var hi, last, lo;
+      lo = 0;
+      hi = 0;
+      if (this.savedR) {
+        if (chunk[0] !== 0x0a) {
+          this.push(this.savedR);
+        }
+        this.savedR = null;
+      }
+      last = chunk.length - 1;
+      while (hi <= last) {
+        if (chunk[hi] === 0x0d) {
+          if (hi === last) {
+            this.savedR = chunk.slice(last);
+            break;
+          } else if (chunk[hi + 1] === 0x0a) {
+            this.push(chunk.slice(lo, hi));
+            lo = hi + 1;
+          }
+        }
+        hi += 1;
+      }
+      if (hi !== lo) {
+        this.push(chunk.slice(lo, hi));
+      }
+      done();
+    };
+
+    LineTransform.prototype._flush = function(done) {
+      if (this.savedR) {
+        this.push(this.savedR);
+      }
+      return done();
+    };
+
+    return LineTransform;
+
+  })(Stream.Transform);
+
+  module.exports = LineTransform;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/parser.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/parser.js
new file mode 100644
index 0000000..65c50f6
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/parser.js
@@ -0,0 +1,247 @@
+(function() {
+  var Parser, Promise, Protocol,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Promise = require('bluebird');
+
+  Protocol = require('./protocol');
+
+  Parser = (function() {
+    Parser.FailError = (function(_super) {
+      __extends(FailError, _super);
+
+      function FailError(message) {
+        Error.call(this);
+        this.name = 'FailError';
+        this.message = "Failure: '" + message + "'";
+        Error.captureStackTrace(this, Parser.FailError);
+      }
+
+      return FailError;
+
+    })(Error);
+
+    Parser.PrematureEOFError = (function(_super) {
+      __extends(PrematureEOFError, _super);
+
+      function PrematureEOFError(howManyMissing) {
+        Error.call(this);
+        this.name = 'PrematureEOFError';
+        this.message = "Premature end of stream, needed " + howManyMissing + " more bytes";
+        this.missingBytes = howManyMissing;
+        Error.captureStackTrace(this, Parser.PrematureEOFError);
+      }
+
+      return PrematureEOFError;
+
+    })(Error);
+
+    Parser.UnexpectedDataError = (function(_super) {
+      __extends(UnexpectedDataError, _super);
+
+      function UnexpectedDataError(unexpected, expected) {
+        Error.call(this);
+        this.name = 'UnexpectedDataError';
+        this.message = "Unexpected '" + unexpected + "', was expecting " + expected;
+        this.unexpected = unexpected;
+        this.expected = expected;
+        Error.captureStackTrace(this, Parser.UnexpectedDataError);
+      }
+
+      return UnexpectedDataError;
+
+    })(Error);
+
+    function Parser(stream) {
+      this.stream = stream;
+    }
+
+    Parser.prototype.raw = function() {
+      return this.stream;
+    };
+
+    Parser.prototype.readAll = function() {
+      var all, endListener, errorListener, resolver, tryRead;
+      all = new Buffer(0);
+      resolver = Promise.defer();
+      tryRead = (function(_this) {
+        return function() {
+          var chunk, _results;
+          _results = [];
+          while (chunk = _this.stream.read()) {
+            _results.push(all = Buffer.concat([all, chunk]));
+          }
+          return _results;
+        };
+      })(this);
+      this.stream.on('readable', tryRead);
+      this.stream.on('error', errorListener = function(err) {
+        return resolver.reject(err);
+      });
+      this.stream.on('end', endListener = function() {
+        return resolver.resolve(all);
+      });
+      tryRead();
+      return resolver.promise.cancellable()["finally"]((function(_this) {
+        return function() {
+          _this.stream.removeListener('readable', tryRead);
+          _this.stream.removeListener('error', errorListener);
+          return _this.stream.removeListener('end', endListener);
+        };
+      })(this));
+    };
+
+    Parser.prototype.readAscii = function(howMany) {
+      return this.readBytes(howMany).then(function(chunk) {
+        return chunk.toString('ascii');
+      });
+    };
+
+    Parser.prototype.readBytes = function(howMany) {
+      var endListener, errorListener, resolver, tryRead;
+      resolver = Promise.defer();
+      tryRead = (function(_this) {
+        return function() {
+          var chunk;
+          if (howMany) {
+            if (chunk = _this.stream.read(howMany)) {
+              howMany -= chunk.length;
+              if (howMany === 0) {
+                return resolver.resolve(chunk);
+              }
+            }
+          } else {
+            return resolver.resolve(new Buffer(0));
+          }
+        };
+      })(this);
+      endListener = function() {
+        return resolver.reject(new Parser.PrematureEOFError(howMany));
+      };
+      errorListener = function(err) {
+        return resolver.reject(err);
+      };
+      this.stream.on('readable', tryRead);
+      this.stream.on('error', errorListener);
+      this.stream.on('end', endListener);
+      tryRead();
+      return resolver.promise.cancellable()["finally"]((function(_this) {
+        return function() {
+          _this.stream.removeListener('readable', tryRead);
+          _this.stream.removeListener('error', errorListener);
+          return _this.stream.removeListener('end', endListener);
+        };
+      })(this));
+    };
+
+    Parser.prototype.readByteFlow = function(howMany) {
+      var endListener, errorListener, resolver, tryRead;
+      resolver = Promise.defer();
+      tryRead = (function(_this) {
+        return function() {
+          var chunk, _results;
+          if (howMany) {
+            _results = [];
+            while (chunk = _this.stream.read(howMany) || _this.stream.read()) {
+              howMany -= chunk.length;
+              if (howMany === 0) {
+                resolver.progress(chunk);
+                resolver.resolve();
+                break;
+              }
+              _results.push(resolver.progress(chunk));
+            }
+            return _results;
+          } else {
+            return resolver.resolve();
+          }
+        };
+      })(this);
+      endListener = function() {
+        return resolver.reject(new Parser.PrematureEOFError(howMany));
+      };
+      errorListener = function(err) {
+        return resolver.reject(err);
+      };
+      this.stream.on('readable', tryRead);
+      this.stream.on('error', errorListener);
+      this.stream.on('end', endListener);
+      tryRead();
+      return resolver.promise.cancellable()["finally"]((function(_this) {
+        return function() {
+          _this.stream.removeListener('readable', tryRead);
+          _this.stream.removeListener('error', errorListener);
+          return _this.stream.removeListener('end', endListener);
+        };
+      })(this));
+    };
+
+    Parser.prototype.readError = function() {
+      return this.readValue().then(function(value) {
+        return Promise.reject(new Parser.FailError(value.toString()));
+      });
+    };
+
+    Parser.prototype.readValue = function() {
+      return this.readAscii(4).then((function(_this) {
+        return function(value) {
+          var length;
+          length = Protocol.decodeLength(value);
+          return _this.readBytes(length);
+        };
+      })(this));
+    };
+
+    Parser.prototype.readUntil = function(code) {
+      var read, skipped;
+      skipped = new Buffer(0);
+      read = (function(_this) {
+        return function() {
+          return _this.readBytes(1).then(function(chunk) {
+            if (chunk[0] === code) {
+              return skipped;
+            } else {
+              skipped = Buffer.concat([skipped, chunk]);
+              return read();
+            }
+          });
+        };
+      })(this);
+      return read();
+    };
+
+    Parser.prototype.searchLine = function(re) {
+      return this.readLine().then((function(_this) {
+        return function(line) {
+          var match;
+          if (match = re.exec(line)) {
+            return match;
+          } else {
+            return _this.searchLine(re);
+          }
+        };
+      })(this));
+    };
+
+    Parser.prototype.readLine = function() {
+      return this.readUntil(0x0a).then(function(line) {
+        if (line[line.length - 1] === 0x0d) {
+          return line.slice(0, -1);
+        } else {
+          return line;
+        }
+      });
+    };
+
+    Parser.prototype.unexpected = function(data, expected) {
+      return Promise.reject(new Parser.UnexpectedDataError(data, expected));
+    };
+
+    return Parser;
+
+  })();
+
+  module.exports = Parser;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/proc/stat.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/proc/stat.js
new file mode 100644
index 0000000..76ac4e9
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/proc/stat.js
@@ -0,0 +1,140 @@
+(function() {
+  var EventEmitter, Parser, ProcStat, split,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  EventEmitter = require('events').EventEmitter;
+
+  split = require('split');
+
+  Parser = require('../parser');
+
+  ProcStat = (function(_super) {
+    var RE_COLSEP, RE_CPULINE;
+
+    __extends(ProcStat, _super);
+
+    RE_CPULINE = /^cpu[0-9]+ .*$/mg;
+
+    RE_COLSEP = /\ +/g;
+
+    function ProcStat(sync) {
+      this.sync = sync;
+      this.interval = 1000;
+      this.stats = this._emptyStats();
+      this._ignore = {};
+      this._timer = setInterval((function(_this) {
+        return function() {
+          return _this.update();
+        };
+      })(this), this.interval);
+      this.update();
+    }
+
+    ProcStat.prototype.end = function() {
+      clearInterval(this._timer);
+      this.sync.end();
+      return this.sync = null;
+    };
+
+    ProcStat.prototype.update = function() {
+      return new Parser(this.sync.pull('/proc/stat')).readAll().then((function(_this) {
+        return function(out) {
+          return _this._parse(out);
+        };
+      })(this))["catch"]((function(_this) {
+        return function(err) {
+          _this._error(err);
+        };
+      })(this));
+    };
+
+    ProcStat.prototype._parse = function(out) {
+      var cols, line, match, stats, total, type, val, _i, _len;
+      stats = this._emptyStats();
+      while (match = RE_CPULINE.exec(out)) {
+        line = match[0];
+        cols = line.split(RE_COLSEP);
+        type = cols.shift();
+        if (this._ignore[type] === line) {
+          continue;
+        }
+        total = 0;
+        for (_i = 0, _len = cols.length; _i < _len; _i++) {
+          val = cols[_i];
+          total += +val;
+        }
+        stats.cpus[type] = {
+          line: line,
+          user: +cols[0] || 0,
+          nice: +cols[1] || 0,
+          system: +cols[2] || 0,
+          idle: +cols[3] || 0,
+          iowait: +cols[4] || 0,
+          irq: +cols[5] || 0,
+          softirq: +cols[6] || 0,
+          steal: +cols[7] || 0,
+          guest: +cols[8] || 0,
+          guestnice: +cols[9] || 0,
+          total: total
+        };
+      }
+      return this._set(stats);
+    };
+
+    ProcStat.prototype._set = function(stats) {
+      var cur, found, id, loads, m, old, ticks, _ref;
+      loads = {};
+      found = false;
+      _ref = stats.cpus;
+      for (id in _ref) {
+        cur = _ref[id];
+        old = this.stats.cpus[id];
+        if (!old) {
+          continue;
+        }
+        ticks = cur.total - old.total;
+        if (ticks > 0) {
+          found = true;
+          m = 100 / ticks;
+          loads[id] = {
+            user: Math.floor(m * (cur.user - old.user)),
+            nice: Math.floor(m * (cur.nice - old.nice)),
+            system: Math.floor(m * (cur.system - old.system)),
+            idle: Math.floor(m * (cur.idle - old.idle)),
+            iowait: Math.floor(m * (cur.iowait - old.iowait)),
+            irq: Math.floor(m * (cur.irq - old.irq)),
+            softirq: Math.floor(m * (cur.softirq - old.softirq)),
+            steal: Math.floor(m * (cur.steal - old.steal)),
+            guest: Math.floor(m * (cur.guest - old.guest)),
+            guestnice: Math.floor(m * (cur.guestnice - old.guestnice)),
+            total: 100
+          };
+        } else {
+          this._ignore[id] = cur.line;
+          delete stats.cpus[id];
+        }
+      }
+      if (found) {
+        this.emit('load', loads);
+      }
+      return this.stats = stats;
+    };
+
+    ProcStat.prototype._error = function(err) {
+      return this.emit('error', err);
+    };
+
+    ProcStat.prototype._emptyStats = function() {
+      return {
+        cpus: {}
+      };
+    };
+
+    return ProcStat;
+
+  })(EventEmitter);
+
+  module.exports = ProcStat;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/protocol.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/protocol.js
new file mode 100644
index 0000000..72ac5f3
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/protocol.js
@@ -0,0 +1,48 @@
+(function() {
+  var Protocol;
+
+  Protocol = (function() {
+    function Protocol() {}
+
+    Protocol.OKAY = 'OKAY';
+
+    Protocol.FAIL = 'FAIL';
+
+    Protocol.STAT = 'STAT';
+
+    Protocol.LIST = 'LIST';
+
+    Protocol.DENT = 'DENT';
+
+    Protocol.RECV = 'RECV';
+
+    Protocol.DATA = 'DATA';
+
+    Protocol.DONE = 'DONE';
+
+    Protocol.SEND = 'SEND';
+
+    Protocol.QUIT = 'QUIT';
+
+    Protocol.decodeLength = function(length) {
+      return parseInt(length, 16);
+    };
+
+    Protocol.encodeLength = function(length) {
+      return ('0000' + length.toString(16)).slice(-4).toUpperCase();
+    };
+
+    Protocol.encodeData = function(data) {
+      if (!Buffer.isBuffer(data)) {
+        data = new Buffer(data);
+      }
+      return Buffer.concat([new Buffer(Protocol.encodeLength(data.length)), data]);
+    };
+
+    return Protocol;
+
+  })();
+
+  module.exports = Protocol;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/sync.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/sync.js
new file mode 100644
index 0000000..8c157a9
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/sync.js
@@ -0,0 +1,337 @@
+(function() {
+  var Entry, EventEmitter, Fs, Path, Promise, Protocol, PullTransfer, PushTransfer, Stats, Sync, debug,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Fs = require('fs');
+
+  Path = require('path');
+
+  Promise = require('bluebird');
+
+  EventEmitter = require('events').EventEmitter;
+
+  debug = require('debug')('adb:sync');
+
+  Protocol = require('./protocol');
+
+  Stats = require('./sync/stats');
+
+  Entry = require('./sync/entry');
+
+  PushTransfer = require('./sync/pushtransfer');
+
+  PullTransfer = require('./sync/pulltransfer');
+
+  Sync = (function(_super) {
+    var DATA_MAX_LENGTH, DEFAULT_CHMOD, TEMP_PATH;
+
+    __extends(Sync, _super);
+
+    TEMP_PATH = '/data/local/tmp';
+
+    DEFAULT_CHMOD = 0x1a4;
+
+    DATA_MAX_LENGTH = 65536;
+
+    Sync.temp = function(path) {
+      return "" + TEMP_PATH + "/" + (Path.basename(path));
+    };
+
+    function Sync(connection) {
+      this.connection = connection;
+      this.parser = this.connection.parser;
+    }
+
+    Sync.prototype.stat = function(path, callback) {
+      this._sendCommandWithArg(Protocol.STAT, path);
+      return this.parser.readAscii(4).then((function(_this) {
+        return function(reply) {
+          switch (reply) {
+            case Protocol.STAT:
+              return _this.parser.readBytes(12).then(function(stat) {
+                var mode, mtime, size;
+                mode = stat.readUInt32LE(0);
+                size = stat.readUInt32LE(4);
+                mtime = stat.readUInt32LE(8);
+                if (mode === 0) {
+                  return _this._enoent(path);
+                } else {
+                  return new Stats(mode, size, mtime);
+                }
+              });
+            case Protocol.FAIL:
+              return _this._readError();
+            default:
+              return _this.parser.unexpected(reply, 'STAT or FAIL');
+          }
+        };
+      })(this)).nodeify(callback);
+    };
+
+    Sync.prototype.readdir = function(path, callback) {
+      var files, readNext;
+      files = [];
+      readNext = (function(_this) {
+        return function() {
+          return _this.parser.readAscii(4).then(function(reply) {
+            switch (reply) {
+              case Protocol.DENT:
+                return _this.parser.readBytes(16).then(function(stat) {
+                  var mode, mtime, namelen, size;
+                  mode = stat.readUInt32LE(0);
+                  size = stat.readUInt32LE(4);
+                  mtime = stat.readUInt32LE(8);
+                  namelen = stat.readUInt32LE(12);
+                  return _this.parser.readBytes(namelen).then(function(name) {
+                    name = name.toString();
+                    if (!(name === '.' || name === '..')) {
+                      files.push(new Entry(name, mode, size, mtime));
+                    }
+                    return readNext();
+                  });
+                });
+              case Protocol.DONE:
+                return _this.parser.readBytes(16).then(function(zero) {
+                  return files;
+                });
+              case Protocol.FAIL:
+                return _this._readError();
+              default:
+                return _this.parser.unexpected(reply, 'DENT, DONE or FAIL');
+            }
+          });
+        };
+      })(this);
+      this._sendCommandWithArg(Protocol.LIST, path);
+      return readNext().nodeify(callback);
+    };
+
+    Sync.prototype.push = function(contents, path, mode) {
+      if (typeof contents === 'string') {
+        return this.pushFile(contents, path, mode);
+      } else {
+        return this.pushStream(contents, path, mode);
+      }
+    };
+
+    Sync.prototype.pushFile = function(file, path, mode) {
+      if (mode == null) {
+        mode = DEFAULT_CHMOD;
+      }
+      mode || (mode = DEFAULT_CHMOD);
+      return this.pushStream(Fs.createReadStream(file), path, mode);
+    };
+
+    Sync.prototype.pushStream = function(stream, path, mode) {
+      if (mode == null) {
+        mode = DEFAULT_CHMOD;
+      }
+      mode |= Stats.S_IFREG;
+      this._sendCommandWithArg(Protocol.SEND, "" + path + "," + mode);
+      return this._writeData(stream, Math.floor(Date.now() / 1000));
+    };
+
+    Sync.prototype.pull = function(path) {
+      this._sendCommandWithArg(Protocol.RECV, "" + path);
+      return this._readData();
+    };
+
+    Sync.prototype.end = function() {
+      this.connection.end();
+      return this;
+    };
+
+    Sync.prototype.tempFile = function(path) {
+      return Sync.temp(path);
+    };
+
+    Sync.prototype._writeData = function(stream, timeStamp) {
+      var readReply, reader, transfer, writeData, writer;
+      transfer = new PushTransfer;
+      writeData = (function(_this) {
+        return function() {
+          var endListener, errorListener, readableListener, resolver, track, waitForDrain, writeNext, writer;
+          resolver = Promise.defer();
+          writer = Promise.resolve().cancellable();
+          stream.on('end', endListener = function() {
+            return writer.then(function() {
+              _this._sendCommandWithLength(Protocol.DONE, timeStamp);
+              return resolver.resolve();
+            });
+          });
+          waitForDrain = function() {
+            var drainListener;
+            resolver = Promise.defer();
+            _this.connection.on('drain', drainListener = function() {
+              return resolver.resolve();
+            });
+            return resolver.promise["finally"](function() {
+              return _this.connection.removeListener('drain', drainListener);
+            });
+          };
+          track = function() {
+            return transfer.pop();
+          };
+          writeNext = function() {
+            var chunk;
+            if (chunk = stream.read(DATA_MAX_LENGTH) || stream.read()) {
+              _this._sendCommandWithLength(Protocol.DATA, chunk.length);
+              transfer.push(chunk.length);
+              if (_this.connection.write(chunk, track)) {
+                return writeNext();
+              } else {
+                return waitForDrain().then(writeNext);
+              }
+            } else {
+              return Promise.resolve();
+            }
+          };
+          stream.on('readable', readableListener = function() {
+            return writer.then(writeNext);
+          });
+          stream.on('error', errorListener = function(err) {
+            return resolver.reject(err);
+          });
+          return resolver.promise["finally"](function() {
+            stream.removeListener('end', endListener);
+            stream.removeListener('readable', readableListener);
+            stream.removeListener('error', errorListener);
+            return writer.cancel();
+          });
+        };
+      })(this);
+      readReply = (function(_this) {
+        return function() {
+          return _this.parser.readAscii(4).then(function(reply) {
+            switch (reply) {
+              case Protocol.OKAY:
+                return _this.parser.readBytes(4).then(function(zero) {
+                  return true;
+                });
+              case Protocol.FAIL:
+                return _this._readError();
+              default:
+                return _this.parser.unexpected(reply, 'OKAY or FAIL');
+            }
+          });
+        };
+      })(this);
+      writer = writeData().cancellable()["catch"](Promise.CancellationError, (function(_this) {
+        return function(err) {
+          return _this.connection.end();
+        };
+      })(this))["catch"](function(err) {
+        transfer.emit('error', err);
+        return reader.cancel();
+      });
+      reader = readReply().cancellable()["catch"](Promise.CancellationError, function(err) {
+        return true;
+      })["catch"](function(err) {
+        transfer.emit('error', err);
+        return writer.cancel();
+      })["finally"](function() {
+        return transfer.end();
+      });
+      transfer.on('cancel', function() {
+        writer.cancel();
+        return reader.cancel();
+      });
+      return transfer;
+    };
+
+    Sync.prototype._readData = function() {
+      var cancelListener, readNext, reader, transfer;
+      transfer = new PullTransfer;
+      readNext = (function(_this) {
+        return function() {
+          return _this.parser.readAscii(4).cancellable().then(function(reply) {
+            switch (reply) {
+              case Protocol.DATA:
+                return _this.parser.readBytes(4).then(function(lengthData) {
+                  var length;
+                  length = lengthData.readUInt32LE(0);
+                  return _this.parser.readByteFlow(length).progressed(function(chunk) {
+                    return transfer.write(chunk);
+                  }).then(function() {
+                    return readNext();
+                  });
+                });
+              case Protocol.DONE:
+                return _this.parser.readBytes(4).then(function(zero) {
+                  return true;
+                });
+              case Protocol.FAIL:
+                return _this._readError();
+              default:
+                return _this.parser.unexpected(reply, 'DATA, DONE or FAIL');
+            }
+          });
+        };
+      })(this);
+      reader = readNext()["catch"](Promise.CancellationError, (function(_this) {
+        return function(err) {
+          return _this.connection.end();
+        };
+      })(this))["catch"](function(err) {
+        return transfer.emit('error', err);
+      })["finally"](function() {
+        transfer.removeListener('cancel', cancelListener);
+        return transfer.end();
+      });
+      transfer.on('cancel', cancelListener = function() {
+        return reader.cancel();
+      });
+      return transfer;
+    };
+
+    Sync.prototype._readError = function() {
+      return this.parser.readBytes(4).then((function(_this) {
+        return function(zero) {
+          return _this.parser.readAll().then(function(buf) {
+            return Promise.reject(new Parser.FailError(buf.toString()));
+          });
+        };
+      })(this));
+    };
+
+    Sync.prototype._sendCommandWithLength = function(cmd, length) {
+      var payload;
+      if (cmd !== Protocol.DATA) {
+        debug(cmd);
+      }
+      payload = new Buffer(cmd.length + 4);
+      payload.write(cmd, 0, cmd.length);
+      payload.writeUInt32LE(length, cmd.length);
+      return this.connection.write(payload);
+    };
+
+    Sync.prototype._sendCommandWithArg = function(cmd, arg) {
+      var payload, pos;
+      debug("" + cmd + " " + arg);
+      payload = new Buffer(cmd.length + 4 + arg.length);
+      pos = 0;
+      payload.write(cmd, pos, cmd.length);
+      pos += cmd.length;
+      payload.writeUInt32LE(arg.length, pos);
+      pos += 4;
+      payload.write(arg, pos);
+      return this.connection.write(payload);
+    };
+
+    Sync.prototype._enoent = function(path) {
+      var err;
+      err = new Error("ENOENT, no such file or directory '" + path + "'");
+      err.errno = 34;
+      err.code = 'ENOENT';
+      err.path = path;
+      return Promise.reject(err);
+    };
+
+    return Sync;
+
+  })(EventEmitter);
+
+  module.exports = Sync;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/sync/entry.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/sync/entry.js
new file mode 100644
index 0000000..1025e82
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/sync/entry.js
@@ -0,0 +1,26 @@
+(function() {
+  var Entry, Stats,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Stats = require('./stats');
+
+  Entry = (function(_super) {
+    __extends(Entry, _super);
+
+    function Entry(name, mode, size, mtime) {
+      this.name = name;
+      Entry.__super__.constructor.call(this, mode, size, mtime);
+    }
+
+    Entry.prototype.toString = function() {
+      return this.name;
+    };
+
+    return Entry;
+
+  })(Stats);
+
+  module.exports = Entry;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/sync/pulltransfer.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/sync/pulltransfer.js
new file mode 100644
index 0000000..1032956
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/sync/pulltransfer.js
@@ -0,0 +1,34 @@
+(function() {
+  var PullTransfer, Stream,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Stream = require('stream');
+
+  PullTransfer = (function(_super) {
+    __extends(PullTransfer, _super);
+
+    function PullTransfer() {
+      this.stats = {
+        bytesTransferred: 0
+      };
+      PullTransfer.__super__.constructor.call(this);
+    }
+
+    PullTransfer.prototype.cancel = function() {
+      return this.emit('cancel');
+    };
+
+    PullTransfer.prototype.write = function(chunk, encoding, callback) {
+      this.stats.bytesTransferred += chunk.length;
+      this.emit('progress', this.stats);
+      return PullTransfer.__super__.write.call(this, chunk, encoding, callback);
+    };
+
+    return PullTransfer;
+
+  })(Stream.PassThrough);
+
+  module.exports = PullTransfer;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/sync/pushtransfer.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/sync/pushtransfer.js
new file mode 100644
index 0000000..a966c3c
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/sync/pushtransfer.js
@@ -0,0 +1,43 @@
+(function() {
+  var EventEmitter, PushTransfer,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  EventEmitter = require('events').EventEmitter;
+
+  PushTransfer = (function(_super) {
+    __extends(PushTransfer, _super);
+
+    function PushTransfer() {
+      this._stack = [];
+      this.stats = {
+        bytesTransferred: 0
+      };
+    }
+
+    PushTransfer.prototype.cancel = function() {
+      return this.emit('cancel');
+    };
+
+    PushTransfer.prototype.push = function(byteCount) {
+      return this._stack.push(byteCount);
+    };
+
+    PushTransfer.prototype.pop = function() {
+      var byteCount;
+      byteCount = this._stack.pop();
+      this.stats.bytesTransferred += byteCount;
+      return this.emit('progress', this.stats);
+    };
+
+    PushTransfer.prototype.end = function() {
+      return this.emit('end');
+    };
+
+    return PushTransfer;
+
+  })(EventEmitter);
+
+  module.exports = PushTransfer;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/sync/stats.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/sync/stats.js
new file mode 100644
index 0000000..39d2c9b
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/sync/stats.js
@@ -0,0 +1,57 @@
+(function() {
+  var Fs, Stats,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Fs = require('fs');
+
+  Stats = (function(_super) {
+    __extends(Stats, _super);
+
+    Stats.S_IFMT = 0xf000;
+
+    Stats.S_IFSOCK = 0xc000;
+
+    Stats.S_IFLNK = 0xa000;
+
+    Stats.S_IFREG = 0x8000;
+
+    Stats.S_IFBLK = 0x6000;
+
+    Stats.S_IFDIR = 0x4000;
+
+    Stats.S_IFCHR = 0x2000;
+
+    Stats.S_IFIFO = 0x1000;
+
+    Stats.S_ISUID = 0x800;
+
+    Stats.S_ISGID = 0x400;
+
+    Stats.S_ISVTX = 0x200;
+
+    Stats.S_IRWXU = 0x1c0;
+
+    Stats.S_IRUSR = 0x100;
+
+    Stats.S_IWUSR = 0x80;
+
+    Stats.S_IXUSR = 0x40;
+
+    Stats.S_IRWXG = 0x38;
+
+    Stats.S_IRGRP = 0x20;
+
+    function Stats(mode, size, mtime) {
+      this.mode = mode;
+      this.size = size;
+      this.mtime = new Date(mtime * 1000);
+    }
+
+    return Stats;
+
+  })(Fs.Stats);
+
+  module.exports = Stats;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/tcpusb/rollingcounter.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/tcpusb/rollingcounter.js
new file mode 100644
index 0000000..f339afa
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/tcpusb/rollingcounter.js
@@ -0,0 +1,24 @@
+(function() {
+  var RollingCounter;
+
+  RollingCounter = (function() {
+    function RollingCounter(max, min) {
+      this.max = max;
+      this.min = min != null ? min : 1;
+      this.now = this.min;
+    }
+
+    RollingCounter.prototype.next = function() {
+      if (!(this.now < this.max)) {
+        this.now = this.min;
+      }
+      return ++this.now;
+    };
+
+    return RollingCounter;
+
+  })();
+
+  module.exports = RollingCounter;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/tcpusb/server.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/tcpusb/server.js
new file mode 100644
index 0000000..0d13739
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/tcpusb/server.js
@@ -0,0 +1,72 @@
+(function() {
+  var EventEmitter, Net, Server, Socket,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Net = require('net');
+
+  EventEmitter = require('events').EventEmitter;
+
+  Socket = require('./socket');
+
+  Server = (function(_super) {
+    __extends(Server, _super);
+
+    function Server(client, serial, options) {
+      this.client = client;
+      this.serial = serial;
+      this.options = options;
+      this.connections = [];
+      this.server = Net.createServer();
+      this.server.on('error', (function(_this) {
+        return function(err) {
+          return _this.emit('error', err);
+        };
+      })(this));
+      this.server.on('listening', (function(_this) {
+        return function() {
+          return _this.emit('listening');
+        };
+      })(this));
+      this.server.on('close', (function(_this) {
+        return function() {
+          return _this.emit('close');
+        };
+      })(this));
+      this.server.on('connection', (function(_this) {
+        return function(conn) {
+          var socket;
+          socket = new Socket(_this.client, _this.serial, conn, _this.options);
+          _this.connections.push(socket);
+          return _this.emit('connection', socket);
+        };
+      })(this));
+    }
+
+    Server.prototype.listen = function() {
+      this.server.listen.apply(this.server, arguments);
+      return this;
+    };
+
+    Server.prototype.close = function() {
+      this.server.close();
+      return this;
+    };
+
+    Server.prototype.end = function() {
+      var conn, _i, _len, _ref;
+      _ref = this.connections;
+      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+        conn = _ref[_i];
+        conn.end();
+      }
+      return this;
+    };
+
+    return Server;
+
+  })(EventEmitter);
+
+  module.exports = Server;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/tcpusb/servicemap.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/tcpusb/servicemap.js
new file mode 100644
index 0000000..d4b3ccc
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/tcpusb/servicemap.js
@@ -0,0 +1,43 @@
+(function() {
+  var ServiceMap;
+
+  ServiceMap = (function() {
+    function ServiceMap() {
+      this.remotes = Object.create(null);
+    }
+
+    ServiceMap.prototype.end = function() {
+      var remote, remoteId, _ref;
+      _ref = this.remotes;
+      for (remoteId in _ref) {
+        remote = _ref[remoteId];
+        remote.end();
+      }
+      this.remotes = Object.create(null);
+    };
+
+    ServiceMap.prototype.put = function(remoteId, socket) {
+      return this.remotes[remoteId] = socket;
+    };
+
+    ServiceMap.prototype.get = function(remoteId) {
+      return this.remotes[remoteId] || null;
+    };
+
+    ServiceMap.prototype.remove = function(remoteId) {
+      var remote;
+      if (remote = this.remotes[remoteId]) {
+        delete this.remotes[remoteId];
+        return remote;
+      } else {
+        return null;
+      }
+    };
+
+    return ServiceMap;
+
+  })();
+
+  module.exports = ServiceMap;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/tcpusb/socket.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/tcpusb/socket.js
new file mode 100644
index 0000000..67fa54f
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/tcpusb/socket.js
@@ -0,0 +1,391 @@
+(function() {
+  var Auth, EventEmitter, Forge, Parser, Promise, Protocol, RollingCounter, ServiceMap, Socket, crypto, debug,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  crypto = require('crypto');
+
+  EventEmitter = require('events').EventEmitter;
+
+  Promise = require('bluebird');
+
+  Forge = require('node-forge');
+
+  debug = require('debug')('adb:tcpusb:socket');
+
+  Parser = require('../parser');
+
+  Protocol = require('../protocol');
+
+  Auth = require('../auth');
+
+  ServiceMap = require('./servicemap');
+
+  RollingCounter = require('./rollingcounter');
+
+  Socket = (function(_super) {
+    var AUTH_RSAPUBLICKEY, AUTH_SIGNATURE, AUTH_TOKEN, A_AUTH, A_CLSE, A_CNXN, A_OKAY, A_OPEN, A_SYNC, A_WRTE, TOKEN_LENGTH, UINT32_MAX;
+
+    __extends(Socket, _super);
+
+    A_SYNC = 0x434e5953;
+
+    A_CNXN = 0x4e584e43;
+
+    A_OPEN = 0x4e45504f;
+
+    A_OKAY = 0x59414b4f;
+
+    A_CLSE = 0x45534c43;
+
+    A_WRTE = 0x45545257;
+
+    A_AUTH = 0x48545541;
+
+    UINT32_MAX = 0xFFFFFFFF;
+
+    AUTH_TOKEN = 1;
+
+    AUTH_SIGNATURE = 2;
+
+    AUTH_RSAPUBLICKEY = 3;
+
+    TOKEN_LENGTH = 20;
+
+    function Socket(client, serial, socket, options) {
+      var _base;
+      this.client = client;
+      this.serial = serial;
+      this.socket = socket;
+      this.options = options != null ? options : {};
+      (_base = this.options).auth || (_base.auth = Promise.resolve(true));
+      this.ended = false;
+      this.parser = new Parser(this.socket);
+      this.version = 1;
+      this.maxPayload = 4096;
+      this.authorized = false;
+      this.syncToken = new RollingCounter(UINT32_MAX);
+      this.remoteId = new RollingCounter(UINT32_MAX);
+      this.services = new ServiceMap;
+      this.remoteAddress = this.socket.remoteAddress;
+      this.token = null;
+      this.signature = null;
+      this._inputLoop();
+    }
+
+    Socket.prototype.end = function() {
+      if (!this.ended) {
+        this.socket.end();
+        this.services.end();
+        this.emit('end');
+        this.ended = true;
+      }
+      return this;
+    };
+
+    Socket.prototype._inputLoop = function() {
+      return this._readMessage().then((function(_this) {
+        return function(message) {
+          return _this._route(message);
+        };
+      })(this)).then((function(_this) {
+        return function() {
+          return setImmediate(_this._inputLoop.bind(_this));
+        };
+      })(this))["catch"](Parser.PrematureEOFError, (function(_this) {
+        return function() {
+          return _this.end();
+        };
+      })(this))["catch"]((function(_this) {
+        return function(err) {
+          debug("Error: " + err.message);
+          _this.emit('error', err);
+          return _this.end();
+        };
+      })(this));
+    };
+
+    Socket.prototype._readMessage = function() {
+      return this.parser.readBytes(24).then(function(header) {
+        return {
+          command: header.readUInt32LE(0),
+          arg0: header.readUInt32LE(4),
+          arg1: header.readUInt32LE(8),
+          length: header.readUInt32LE(12),
+          check: header.readUInt32LE(16),
+          magic: header.readUInt32LE(20)
+        };
+      }).then((function(_this) {
+        return function(message) {
+          return _this.parser.readBytes(message.length).then(function(data) {
+            message.data = data;
+            return message;
+          });
+        };
+      })(this)).then((function(_this) {
+        return function(message) {
+          return _this._validateMessage(message);
+        };
+      })(this));
+    };
+
+    Socket.prototype._route = function(message) {
+      if (this.ended) {
+        return;
+      }
+      this.emit('userActivity', message);
+      switch (message.command) {
+        case A_SYNC:
+          return this._handleSyncMessage(message);
+        case A_CNXN:
+          return this._handleConnectionMessage(message);
+        case A_OPEN:
+          return this._handleOpenMessage(message);
+        case A_OKAY:
+          return this._handleOkayMessage(message);
+        case A_CLSE:
+          return this._handleCloseMessage(message);
+        case A_WRTE:
+          return this._handleWriteMessage(message);
+        case A_AUTH:
+          return this._handleAuthMessage(message);
+        default:
+          return this.emit('error', new Error("Unknown command " + message.command));
+      }
+    };
+
+    Socket.prototype._handleSyncMessage = function(message) {
+      return this._writeHeader(A_SYNC, 1, this.syncToken.next(), 0);
+    };
+
+    Socket.prototype._handleConnectionMessage = function(message) {
+      debug('A_CNXN', message);
+      return this._createToken().then((function(_this) {
+        return function(token) {
+          _this.token = token;
+          return _this._writeMessage(A_AUTH, AUTH_TOKEN, 0, _this.token);
+        };
+      })(this));
+    };
+
+    Socket.prototype._handleAuthMessage = function(message) {
+      debug('A_AUTH', message);
+      switch (message.arg0) {
+        case AUTH_SIGNATURE:
+          if (!this.signature) {
+            this.signature = message.data;
+          }
+          return this._writeMessage(A_AUTH, AUTH_TOKEN, 0, this.token);
+        case AUTH_RSAPUBLICKEY:
+          return Auth.parsePublicKey(message.data.slice(0, -1)).then((function(_this) {
+            return function(key) {
+              var digest, sig;
+              digest = _this.token.toString('binary');
+              sig = _this.signature.toString('binary');
+              if (key.verify(digest, sig)) {
+                return _this.options.auth(key).then(function() {
+                  return _this._deviceId().then(function(id) {
+                    _this.authorized = true;
+                    return _this._writeMessage(A_CNXN, _this.version, _this.maxPayload, "device::" + id);
+                  });
+                })["catch"](function(err) {
+                  return _this.end();
+                });
+              } else {
+                return _this.end();
+              }
+            };
+          })(this));
+        default:
+          return this.end();
+      }
+    };
+
+    Socket.prototype._handleOpenMessage = function(message) {
+      var command, localId, remoteId, service;
+      if (!this.authorized) {
+        return;
+      }
+      debug('A_OPEN', message);
+      localId = message.arg0;
+      remoteId = this.remoteId.next();
+      service = message.data.slice(0, -1);
+      command = service.toString().split(':', 1)[0];
+      return this.client.transport(this.serial).then((function(_this) {
+        return function(transport) {
+          var parser, pump;
+          if (_this.ended) {
+            return;
+          }
+          debug("Calling " + service);
+          _this.services.put(remoteId, transport);
+          transport.write(Protocol.encodeData(service));
+          parser = transport.parser;
+          pump = function() {
+            return new Promise(function(resolve, reject) {
+              var maybeRead, out;
+              out = parser.raw();
+              maybeRead = function() {
+                var chunk, _results;
+                _results = [];
+                while (chunk = _this._readChunk(out)) {
+                  _results.push(_this._writeMessage(A_WRTE, remoteId, localId, chunk));
+                }
+                return _results;
+              };
+              out.on('readable', maybeRead);
+              return out.on('end', resolve);
+            });
+          };
+          parser.readAscii(4).then(function(reply) {
+            switch (reply) {
+              case Protocol.OKAY:
+                _this._writeHeader(A_OKAY, remoteId, localId);
+                return pump();
+              case Protocol.FAIL:
+                return parser.readError();
+              default:
+                return parser.unexpected(reply, 'OKAY or FAIL');
+            }
+          })["catch"](Parser.PrematureEOFError, function() {
+            return true;
+          })["finally"](function() {
+            return _this._close(remoteId, localId);
+          })["catch"](Parser.FailError, function(err) {
+            debug("Unable to open transport: " + err);
+            return _this.end();
+          });
+        };
+      })(this));
+    };
+
+    Socket.prototype._handleOkayMessage = function(message) {
+      var localId, remoteId;
+      if (!this.authorized) {
+        return;
+      }
+      debug('A_OKAY', message);
+      localId = message.arg0;
+      return remoteId = message.arg1;
+    };
+
+    Socket.prototype._handleCloseMessage = function(message) {
+      var localId, remoteId;
+      if (!this.authorized) {
+        return;
+      }
+      debug('A_CLSE', message);
+      localId = message.arg0;
+      remoteId = message.arg1;
+      return this._close(remoteId, localId);
+    };
+
+    Socket.prototype._handleWriteMessage = function(message) {
+      var localId, remote, remoteId;
+      if (!this.authorized) {
+        return;
+      }
+      debug('A_WRTE', message);
+      localId = message.arg0;
+      remoteId = message.arg1;
+      if (remote = this.services.get(remoteId)) {
+        remote.write(message.data);
+        this._writeHeader(A_OKAY, remoteId, localId);
+      } else {
+        debug("A_WRTE to unknown socket pair " + localId + "/" + remoteId);
+      }
+      return true;
+    };
+
+    Socket.prototype._close = function(remoteId, localId) {
+      var remote;
+      if (remote = this.services.remove(remoteId)) {
+        remote.end();
+        return this._writeHeader(A_CLSE, remoteId, localId);
+      }
+    };
+
+    Socket.prototype._writeHeader = function(command, arg0, arg1, length, checksum) {
+      var header;
+      if (this.ended) {
+        return;
+      }
+      header = new Buffer(24);
+      header.writeUInt32LE(command, 0);
+      header.writeUInt32LE(arg0 || 0, 4);
+      header.writeUInt32LE(arg1 || 0, 8);
+      header.writeUInt32LE(length || 0, 12);
+      header.writeUInt32LE(checksum || 0, 16);
+      header.writeUInt32LE(this._magic(command), 20);
+      return this.socket.write(header);
+    };
+
+    Socket.prototype._writeMessage = function(command, arg0, arg1, data) {
+      if (this.ended) {
+        return;
+      }
+      if (!Buffer.isBuffer(data)) {
+        data = new Buffer(data);
+      }
+      this._writeHeader(command, arg0, arg1, data.length, this._checksum(data));
+      return this.socket.write(data);
+    };
+
+    Socket.prototype._validateMessage = function(message) {
+      if (message.magic !== this._magic(message.command)) {
+        throw new Error("Command failed magic check");
+      }
+      if (message.check !== this._checksum(message.data)) {
+        throw new Error("Message checksum doesn't match received message");
+      }
+      return message;
+    };
+
+    Socket.prototype._readChunk = function(stream) {
+      return stream.read(this.maxPayload) || stream.read();
+    };
+
+    Socket.prototype._checksum = function(data) {
+      var char, sum, _i, _len;
+      if (!Buffer.isBuffer(data)) {
+        throw new Error("Unable to calculate checksum of non-Buffer");
+      }
+      sum = 0;
+      for (_i = 0, _len = data.length; _i < _len; _i++) {
+        char = data[_i];
+        sum += char;
+      }
+      return sum;
+    };
+
+    Socket.prototype._magic = function(command) {
+      return (command ^ 0xffffffff) >>> 0;
+    };
+
+    Socket.prototype._createToken = function() {
+      return Promise.promisify(crypto.randomBytes)(TOKEN_LENGTH);
+    };
+
+    Socket.prototype._deviceId = function() {
+      return this.client.getProperties(this.serial).then(function(properties) {
+        var prop;
+        return ((function() {
+          var _i, _len, _ref, _results;
+          _ref = ['ro.product.name', 'ro.product.model', 'ro.product.device'];
+          _results = [];
+          for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+            prop = _ref[_i];
+            _results.push("" + prop + "=" + properties[prop] + ";");
+          }
+          return _results;
+        })()).join('');
+      });
+    };
+
+    return Socket;
+
+  })(EventEmitter);
+
+  module.exports = Socket;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/tracker.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/tracker.js
new file mode 100644
index 0000000..ca96230
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/tracker.js
@@ -0,0 +1,92 @@
+(function() {
+  var EventEmitter, Parser, Promise, Tracker,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  EventEmitter = require('events').EventEmitter;
+
+  Promise = require('bluebird');
+
+  Parser = require('./parser');
+
+  Tracker = (function(_super) {
+    __extends(Tracker, _super);
+
+    function Tracker(command) {
+      this.command = command;
+      this.deviceList = [];
+      this.deviceMap = {};
+      this.reader = this.read()["catch"](Parser.PrematureEOFError, (function(_this) {
+        return function(err) {
+          return _this.emit('end');
+        };
+      })(this))["catch"](Promise.CancellationError, (function(_this) {
+        return function(err) {
+          _this.command.connection.end();
+          return _this.emit('end');
+        };
+      })(this))["catch"]((function(_this) {
+        return function(err) {
+          _this.emit('error', err);
+          return _this.emit('end');
+        };
+      })(this));
+    }
+
+    Tracker.prototype.read = function() {
+      return this.command._readDevices().cancellable().then((function(_this) {
+        return function(list) {
+          _this.update(list);
+          return _this.read();
+        };
+      })(this));
+    };
+
+    Tracker.prototype.update = function(newList) {
+      var changeSet, device, newMap, oldDevice, _i, _j, _len, _len1, _ref;
+      changeSet = {
+        removed: [],
+        changed: [],
+        added: []
+      };
+      newMap = {};
+      for (_i = 0, _len = newList.length; _i < _len; _i++) {
+        device = newList[_i];
+        oldDevice = this.deviceMap[device.id];
+        if (oldDevice) {
+          if (oldDevice.type !== device.type) {
+            changeSet.changed.push(device);
+            this.emit('change', device, oldDevice);
+          }
+        } else {
+          changeSet.added.push(device);
+          this.emit('add', device);
+        }
+        newMap[device.id] = device;
+      }
+      _ref = this.deviceList;
+      for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) {
+        device = _ref[_j];
+        if (!newMap[device.id]) {
+          changeSet.removed.push(device);
+          this.emit('remove', device);
+        }
+      }
+      this.emit('changeSet', changeSet);
+      this.deviceList = newList;
+      this.deviceMap = newMap;
+      return this;
+    };
+
+    Tracker.prototype.end = function() {
+      this.reader.cancel();
+      return this;
+    };
+
+    return Tracker;
+
+  })(EventEmitter);
+
+  module.exports = Tracker;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/util.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/util.js
new file mode 100644
index 0000000..8207028
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/adb/util.js
@@ -0,0 +1,16 @@
+(function() {
+  var Auth, Parser;
+
+  Parser = require('./parser');
+
+  Auth = require('./auth');
+
+  module.exports.readAll = function(stream, callback) {
+    return new Parser(stream).readAll(stream).nodeify(callback);
+  };
+
+  module.exports.parsePublicKey = function(keyString, callback) {
+    return Auth.parsePublicKey(keyString).nodeify(callback);
+  };
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/cli.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/cli.js
new file mode 100644
index 0000000..11dc9cf
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/lib/cli.js
@@ -0,0 +1,42 @@
+(function() {
+  var Auth, Promise, forge, fs, pkg, program;
+
+  fs = require('fs');
+
+  program = require('commander');
+
+  Promise = require('bluebird');
+
+  forge = require('node-forge');
+
+  pkg = require('../package');
+
+  Auth = require('./adb/auth');
+
+  Promise.longStackTraces();
+
+  program.version(pkg.version);
+
+  program.command('pubkey-convert <file>').option('-f, --format <format>', 'format (pem or openssh)', String, 'pem').description('Converts an ADB-generated public key into PEM format.').action(function(file, options) {
+    return Auth.parsePublicKey(fs.readFileSync(file)).then(function(key) {
+      switch (options.format.toLowerCase()) {
+        case 'pem':
+          return console.log(forge.pki.publicKeyToPem(key).trim());
+        case 'openssh':
+          return console.log(forge.ssh.publicKeyToOpenSSH(key, 'adbkey').trim());
+        default:
+          console.error("Unsupported format '" + options.format + "'");
+          return process.exit(1);
+      }
+    });
+  });
+
+  program.command('pubkey-fingerprint <file>').description('Outputs the fingerprint of an ADB-generated public key.').action(function(file) {
+    return Auth.parsePublicKey(fs.readFileSync(file)).then(function(key) {
+      return console.log('%s %s', key.fingerprint, key.comment);
+    });
+  });
+
+  program.parse(process.argv);
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-logcat/LICENSE b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-logcat/LICENSE
new file mode 100644
index 0000000..2ffff3d
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-logcat/LICENSE
@@ -0,0 +1,13 @@
+Copyright © CyberAgent, Inc. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-logcat/README.md b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-logcat/README.md
new file mode 100644
index 0000000..df54e35
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-logcat/README.md
@@ -0,0 +1,240 @@
+# adbkit-logcat
+
+**adbkit-logcat** provides a [Node.js][nodejs] interface for working with output produced by the Android [`logcat` tool][logcat-site]. It takes a log stream (that you must create separately), parses it, and emits log entries in real-time as they occur. Possible use cases include storing logs in a database, forwarding logs via [MessagePack][msgpack], or just advanced filtering.
+
+## Getting started
+
+Install via NPM:
+
+```bash
+npm install --save adbkit-logcat
+```
+
+Note that while adbkit-logcat is written in CoffeeScript, it is compiled to JavaScript before publishing to NPM, which means that you are not required to use CoffeeScript.
+
+### Examples
+
+#### Output all log messages
+
+##### JavaScript
+
+```javascript
+var logcat = require('adbkit-logcat');
+var spawn = require('child_process').spawn;
+
+// Retrieve a binary log stream
+var proc = spawn('adb', ['logcat', '-B']);
+
+// Connect logcat to the stream
+reader = logcat.readStream(proc.stdout);
+reader.on('entry', function(entry) {
+  console.log(entry.message);
+});
+
+// Make sure we don't leave anything hanging
+process.on('exit', function() {
+  proc.kill();
+});
+```
+
+##### CoffeeScript
+
+```coffeescript
+Logcat = require 'adbkit-logcat'
+{spawn} = require 'child_process'
+
+# Retrieve a binary log stream
+proc = spawn 'adb', ['logcat', '-B']
+
+# Connect logcat to the stream
+reader = Logcat.readStream proc.stdout
+reader.on 'entry', (entry) ->
+  console.log entry.message
+
+# Make sure we don't leave anything hanging
+process.on 'exit', ->
+  proc.kill()
+```
+
+## API
+
+### Logcat
+
+#### logcat.Priority
+
+Exposes `Priority`. See below for details.
+
+#### logcat.Reader
+
+Exposes `Reader`. See below for details.
+
+#### logcat.readStream(stream[, options])
+
+Creates a logcat reader instance from the provided logcat event [`Stream`][node-stream]. Note that you must create the stream separately.
+
+* **stream** The event stream to read.
+* **options** Optional. The following options are supported:
+    - **format** The format of the stream. Currently, the only supported value is `'binary'`, which (for example) `adb logcat -B` produces. Defaults to `'binary'`.
+    - **fixLineFeeds** All programs run via the ADB shell transform any `'\n'` in the output to `'\r\n'`, which breaks binary content. If set, this option reverses the transformation before parsing the stream. Defaults to `true`.
+* Returns: The `Reader` instance.
+
+### Priority
+
+#### Constants
+
+The following static properties are available:
+
+* **Priority.UNKNOWN** i.e. `0`.
+* **Priority.DEFAULT** i.e. `1`. Not available when reading a stream.
+* **Priority.VERBOSE** i.e. `2`.
+* **Priority.DEBUG** i.e. `3`.
+* **Priority.INFO** i.e. `4`.
+* **Priority.WARN** i.e. `5`.
+* **Priority.ERROR** i.e. `6`.
+* **Priority.FATAL** i.e. `7`.
+* **Priority.SILENT** i.e. `8`. Not available when reading a stream.
+
+#### Priority.fromLetter(letter)
+
+Static method to convert the given `letter` into a numeric priority. For example, `Priority.fromName('d')` would return `Priority.DEBUG`.
+
+* **letter** The priority as a `String`. Any single, case-insensitive character matching the first character of any `Priority` constant is accepted.
+* Returns: The priority as a `Number`, or `undefined`.
+
+#### Priority.fromName(name)
+
+Static method to convert the given `name` into a numeric priority. For example, `Priority.fromName('debug')` (or `Priority.fromName('d')`) would return `Priority.DEBUG`.
+
+* **name** The priority as a `String`. Any full, case-insensitive match of the `Priority` constants is accepted. If no match is found, falls back to `Priority.fromLetter()`.
+* Returns: The priority as a `Number`, or `undefined`.
+
+#### Priority.toLetter(priority)
+
+Static method to convert the numeric priority into its letter representation. For example, `Priority.toLetter(Priority.DEBUG)` would return `'D'`.
+
+* **priority** The priority as a `Number`. Any `Priority` constant value is accepted.
+* Returns: The priority as a `String` letter, or `undefined`.
+
+#### Priority.toName(priority)
+
+Static method to convert the numeric priority into its full string representation. For example, `Priority.toLetter(Priority.DEBUG)` would return `'DEBUG'`.
+
+* **priority** The priority as a `Number`. Any `Priority` constant value is accepted.
+* Returns: The priority as a `String`, or `undefined`.
+
+### Reader
+
+A reader instance, which is an [`EventEmitter`][node-events].
+
+#### Events
+
+The following events are available:
+
+* **error** **(err)** Emitted when an error occurs.
+    * **err** An `Error`.
+* **end** Emitted when the stream ends.
+* **finish** Emitted when the stream finishes.
+* **entry** **(entry)** Emitted when the stream finishes.
+    * **entry** A log `Entry`. See below for details.
+
+#### constructor([options])
+
+For advanced users. Manually constructs a `Reader` instance. Useful for testing and/or playing around. Normally you would use `logcat.readStream()` to create the instance.
+
+* **options** See `logcat.readStream()` for details.
+* Returns: N/A
+
+#### reader.connect(stream)
+
+For advanced users. When instantiated manually (not via `logcat.readStream()`), connects the `Reader` instance to the given stream.
+
+* **stream** See `logcat.readStream()` for details.
+* Returns: The `Reader` instance.
+
+#### reader.end()
+
+Convenience method for ending the stream.
+
+* Returns: The `Reader` instance.
+
+#### reader.exclude(tag)
+
+Skip entries with the provided tag. Alias for `reader.include(tag, Priority.SILENT)`. Note that even skipped events have to be parsed so that they can be ignored.
+
+* **tag** The tag string to exclude. If `'*'`, works the same as `reader.excludeAll()`.
+* Returns: The `Reader` instance.
+
+#### reader.excludeAll()
+
+Skip **ALL** entries. Alias for `reader.includeAll(Priority.SILENT)`. Any entries you wish to see must be included via `include()`/`includeAll()`.
+
+* Returns: The `Reader` instance.
+
+#### reader.include(tag[, priority])
+
+Include all entries with the given tag and a priority higher or equal to the given `priority`.
+
+* **tag** The tag string to include. If `'*'`, works the same as `reader.includeAll(priority)`.
+* **priority** Optional. A lower bound for the priority. Any numeric `Priority` constant or any `String` value accepted by `Priority.fromName()` is accepted. Defaults to `Priority.DEBUG`.
+* Returns: The `Reader` instance.
+
+#### reader.includeAll([priority])
+
+Include all entries with a priority higher or equal to the given `priority`.
+
+* **tag** The tag string to exclude.
+* **priority** Optional. See `reader.include()` for details.
+* Returns: The `Reader` instance.
+
+#### reader.resetFilters()
+
+Resets all inclusions/exclusions.
+
+* Returns: The `Reader` instance.
+
+### Entry
+
+A log entry.
+
+#### Properties
+
+The following properties are available:
+
+* **date** Event time as a `Date`.
+* **pid** Process ID as a `Number`.
+* **tid** Thread ID as a `Number`.
+* **priority** Event priority as a `Number`. You can use `logcat.Priority` to convert the value into a `String`.
+* **tag** Event tag as a `String`.
+* **message** Message as a `String`.
+
+#### entry.toBinary()
+
+Converts the entry back to the binary log format.
+
+* Returns: The binary event as a [`Buffer`][node-buffer].
+
+## More information
+
+* Liblog
+    - [logprint.c][logprint-source]
+* Logcat
+    - [logcat.cpp][logcat-source]
+
+## Contributing
+
+See [CONTRIBUTING.md](CONTRIBUTING.md).
+
+## License
+
+See [LICENSE](LICENSE).
+
+Copyright © CyberAgent, Inc. All Rights Reserved.
+
+[nodejs]: <http://nodejs.org/>
+[msgpack]: <http://msgpack.org/>
+[logcat-site]: <http://developer.android.com/tools/help/logcat.html>
+[logprint-source]: <https://github.com/android/platform_system_core/blob/master/liblog/logprint.c>
+[logcat-source]: <https://github.com/android/platform_system_core/blob/master/logcat/logcat.cpp>
+[node-stream]: <http://nodejs.org/api/stream.html>
+[node-events]: <http://nodejs.org/api/events.html>
+[node-buffer]: <http://nodejs.org/api/buffer.html>
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-logcat/index.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-logcat/index.js
new file mode 100644
index 0000000..607433c
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-logcat/index.js
@@ -0,0 +1,15 @@
+(function() {
+  var Path;
+
+  Path = require('path');
+
+  module.exports = (function() {
+    switch (Path.extname(__filename)) {
+      case '.coffee':
+        return require('./src/logcat');
+      default:
+        return require('./lib/logcat');
+    }
+  })();
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat.js
new file mode 100644
index 0000000..d224f07
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat.js
@@ -0,0 +1,25 @@
+(function() {
+  var Logcat, Priority, Reader;
+
+  Reader = require('./logcat/reader');
+
+  Priority = require('./logcat/priority');
+
+  Logcat = (function() {
+    function Logcat() {}
+
+    Logcat.readStream = function(stream, options) {
+      return new Reader(options).connect(stream);
+    };
+
+    return Logcat;
+
+  })();
+
+  Logcat.Reader = Reader;
+
+  Logcat.Priority = Priority;
+
+  module.exports = Logcat;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/entry.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/entry.js
new file mode 100644
index 0000000..1a7f5d2
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/entry.js
@@ -0,0 +1,76 @@
+(function() {
+  var Entry;
+
+  Entry = (function() {
+    function Entry() {
+      this.date = null;
+      this.pid = -1;
+      this.tid = -1;
+      this.priority = null;
+      this.tag = null;
+      this.message = null;
+    }
+
+    Entry.prototype.setDate = function(date) {
+      this.date = date;
+    };
+
+    Entry.prototype.setPid = function(pid) {
+      this.pid = pid;
+    };
+
+    Entry.prototype.setTid = function(tid) {
+      this.tid = tid;
+    };
+
+    Entry.prototype.setPriority = function(priority) {
+      this.priority = priority;
+    };
+
+    Entry.prototype.setTag = function(tag) {
+      this.tag = tag;
+    };
+
+    Entry.prototype.setMessage = function(message) {
+      this.message = message;
+    };
+
+    Entry.prototype.toBinary = function() {
+      var buffer, cursor, length;
+      length = 20;
+      length += 1;
+      length += this.tag.length;
+      length += 1;
+      length += this.message.length;
+      length += 1;
+      buffer = new Buffer(length);
+      cursor = 0;
+      buffer.writeUInt16LE(length - 20, cursor);
+      cursor += 4;
+      buffer.writeInt32LE(this.pid, cursor);
+      cursor += 4;
+      buffer.writeInt32LE(this.tid, cursor);
+      cursor += 4;
+      buffer.writeInt32LE(Math.floor(this.date.getTime() / 1000), cursor);
+      cursor += 4;
+      buffer.writeInt32LE((this.date.getTime() % 1000) * 1000000, cursor);
+      cursor += 4;
+      buffer[cursor] = this.priority;
+      cursor += 1;
+      buffer.write(this.tag, cursor, this.tag.length);
+      cursor += this.tag.length;
+      buffer[cursor] = 0x00;
+      cursor += 1;
+      buffer.write(this.message, cursor, this.message.length);
+      cursor += this.message.length;
+      buffer[cursor] = 0x00;
+      return buffer;
+    };
+
+    return Entry;
+
+  })();
+
+  module.exports = Entry;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/parser.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/parser.js
new file mode 100644
index 0000000..e3c2939
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/parser.js
@@ -0,0 +1,32 @@
+(function() {
+  var EventEmitter, Parser, _ref,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  EventEmitter = require('events').EventEmitter;
+
+  Parser = (function(_super) {
+    __extends(Parser, _super);
+
+    function Parser() {
+      _ref = Parser.__super__.constructor.apply(this, arguments);
+      return _ref;
+    }
+
+    Parser.get = function(type) {
+      var parser;
+      parser = require("./parser/" + type);
+      return new parser();
+    };
+
+    Parser.prototype.parse = function() {
+      throw new Error("parse() is unimplemented");
+    };
+
+    return Parser;
+
+  })(EventEmitter);
+
+  module.exports = Parser;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/parser/binary.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/parser/binary.js
new file mode 100644
index 0000000..b861ace
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/parser/binary.js
@@ -0,0 +1,78 @@
+(function() {
+  var Binary, Entry, Parser, Priority,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Parser = require('../parser');
+
+  Entry = require('../entry');
+
+  Priority = require('../priority');
+
+  Binary = (function(_super) {
+    var HEADER_LENGTH;
+
+    __extends(Binary, _super);
+
+    HEADER_LENGTH = 20;
+
+    function Binary() {
+      this.buffer = new Buffer('');
+    }
+
+    Binary.prototype.parse = function(chunk) {
+      var cursor, data, entry, length, nsec, sec;
+      this.buffer = Buffer.concat([this.buffer, chunk]);
+      while (this.buffer.length > HEADER_LENGTH) {
+        cursor = 0;
+        length = this.buffer.readUInt16LE(cursor);
+        if (this.buffer.length < HEADER_LENGTH + length) {
+          break;
+        }
+        entry = new Entry;
+        cursor += 4;
+        entry.setPid(this.buffer.readInt32LE(cursor));
+        cursor += 4;
+        entry.setTid(this.buffer.readInt32LE(cursor));
+        cursor += 4;
+        sec = this.buffer.readInt32LE(cursor);
+        cursor += 4;
+        nsec = this.buffer.readInt32LE(cursor);
+        entry.setDate(new Date(sec * 1000 + nsec / 1000000));
+        cursor += 4;
+        data = this.buffer.slice(cursor, cursor + length);
+        cursor += length;
+        this.buffer = this.buffer.slice(cursor);
+        this._processEntry(entry, data);
+      }
+      if (this.buffer.length) {
+        this.emit('wait');
+      } else {
+        this.emit('drain');
+      }
+    };
+
+    Binary.prototype._processEntry = function(entry, data) {
+      var cursor, length;
+      entry.setPriority(data[0]);
+      cursor = 1;
+      length = data.length;
+      while (cursor < length) {
+        if (data[cursor] === 0) {
+          entry.setTag(data.slice(1, cursor).toString());
+          entry.setMessage(data.slice(cursor + 1, length - 1).toString());
+          this.emit('entry', entry);
+          return;
+        }
+        cursor += 1;
+      }
+      this.emit('error', new Error("Unprocessable entry data '" + data + "'"));
+    };
+
+    return Binary;
+
+  })(Parser);
+
+  module.exports = Binary;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/priority.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/priority.js
new file mode 100644
index 0000000..532ea62
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/priority.js
@@ -0,0 +1,89 @@
+(function() {
+  var Priority;
+
+  Priority = (function() {
+    var letterNames, letters, names;
+
+    function Priority() {}
+
+    Priority.UNKNOWN = 0;
+
+    Priority.DEFAULT = 1;
+
+    Priority.VERBOSE = 2;
+
+    Priority.DEBUG = 3;
+
+    Priority.INFO = 4;
+
+    Priority.WARN = 5;
+
+    Priority.ERROR = 6;
+
+    Priority.FATAL = 7;
+
+    Priority.SILENT = 8;
+
+    names = {
+      0: 'UNKNOWN',
+      1: 'DEFAULT',
+      2: 'VERBOSE',
+      3: 'DEBUG',
+      4: 'INFO',
+      5: 'WARN',
+      6: 'ERROR',
+      7: 'FATAL',
+      8: 'SILENT'
+    };
+
+    letters = {
+      '?': Priority.UNKNOWN,
+      'V': Priority.VERBOSE,
+      'D': Priority.DEBUG,
+      'I': Priority.INFO,
+      'W': Priority.WARN,
+      'E': Priority.ERROR,
+      'F': Priority.FATAL,
+      'S': Priority.SILENT
+    };
+
+    letterNames = {
+      0: '?',
+      1: '?',
+      2: 'V',
+      3: 'D',
+      4: 'I',
+      5: 'W',
+      6: 'E',
+      7: 'F',
+      8: 'S'
+    };
+
+    Priority.fromName = function(name) {
+      var value;
+      value = Priority[name.toUpperCase()];
+      if (value || value === 0) {
+        return value;
+      }
+      return Priority.fromLetter(name);
+    };
+
+    Priority.toName = function(value) {
+      return names[value];
+    };
+
+    Priority.fromLetter = function(letter) {
+      return letters[letter.toUpperCase()];
+    };
+
+    Priority.toLetter = function(value) {
+      return letterNames[value];
+    };
+
+    return Priority;
+
+  })();
+
+  module.exports = Priority;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/reader.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/reader.js
new file mode 100644
index 0000000..7f07626
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/reader.js
@@ -0,0 +1,137 @@
+(function() {
+  var EventEmitter, Parser, Priority, Reader, Transform,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  EventEmitter = require('events').EventEmitter;
+
+  Parser = require('./parser');
+
+  Transform = require('./transform');
+
+  Priority = require('./priority');
+
+  Reader = (function(_super) {
+    __extends(Reader, _super);
+
+    Reader.ANY = '*';
+
+    function Reader(options) {
+      var _base;
+      this.options = options != null ? options : {};
+      (_base = this.options).format || (_base.format = 'binary');
+      if (this.options.fixLineFeeds == null) {
+        this.options.fixLineFeeds = true;
+      }
+      this.filters = {
+        all: -1,
+        tags: {}
+      };
+      this.parser = Parser.get(this.options.format);
+      this.stream = null;
+    }
+
+    Reader.prototype.exclude = function(tag) {
+      if (tag === Reader.ANY) {
+        return this.excludeAll();
+      }
+      this.filters.tags[tag] = Priority.SILENT;
+      return this;
+    };
+
+    Reader.prototype.excludeAll = function() {
+      this.filters.all = Priority.SILENT;
+      return this;
+    };
+
+    Reader.prototype.include = function(tag, priority) {
+      if (priority == null) {
+        priority = Priority.DEBUG;
+      }
+      if (tag === Reader.ANY) {
+        return this.includeAll(priority);
+      }
+      this.filters.tags[tag] = this._priority(priority);
+      return this;
+    };
+
+    Reader.prototype.includeAll = function(priority) {
+      if (priority == null) {
+        priority = Priority.DEBUG;
+      }
+      this.filters.all = this._priority(priority);
+      return this;
+    };
+
+    Reader.prototype.resetFilters = function() {
+      this.filters.all = -1;
+      this.filters.tags = {};
+      return this;
+    };
+
+    Reader.prototype._hook = function() {
+      var transform,
+        _this = this;
+      if (this.options.fixLineFeeds) {
+        transform = this.stream.pipe(new Transform);
+        transform.on('data', function(data) {
+          return _this.parser.parse(data);
+        });
+      } else {
+        this.stream.on('data', function(data) {
+          return _this.parser.parse(data);
+        });
+      }
+      this.stream.on('error', function(err) {
+        return _this.emit('error', err);
+      });
+      this.stream.on('end', function() {
+        return _this.emit('end');
+      });
+      this.stream.on('finish', function() {
+        return _this.emit('finish');
+      });
+      this.parser.on('entry', function(entry) {
+        if (_this._filter(entry)) {
+          return _this.emit('entry', entry);
+        }
+      });
+      this.parser.on('error', function(err) {
+        return _this.emit('error', err);
+      });
+    };
+
+    Reader.prototype._filter = function(entry) {
+      var priority;
+      priority = this.filters.tags[entry.tag];
+      if (!(priority >= 0)) {
+        priority = this.filters.all;
+      }
+      return entry.priority >= priority;
+    };
+
+    Reader.prototype._priority = function(priority) {
+      if (typeof priority === 'number') {
+        return priority;
+      }
+      return Priority.fromName(priority);
+    };
+
+    Reader.prototype.connect = function(stream) {
+      this.stream = stream;
+      this._hook();
+      return this;
+    };
+
+    Reader.prototype.end = function() {
+      this.stream.end();
+      return this;
+    };
+
+    return Reader;
+
+  })(EventEmitter);
+
+  module.exports = Reader;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/transform.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/transform.js
new file mode 100644
index 0000000..b6fe7a2
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-logcat/lib/logcat/transform.js
@@ -0,0 +1,51 @@
+(function() {
+  var Stream, Transform,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Stream = require('stream');
+
+  Transform = (function(_super) {
+    __extends(Transform, _super);
+
+    function Transform(options) {
+      this.savedR = null;
+      Transform.__super__.constructor.call(this, options);
+    }
+
+    Transform.prototype._transform = function(chunk, encoding, done) {
+      var hi, last, lo;
+      lo = 0;
+      hi = 0;
+      if (this.savedR) {
+        if (chunk[0] !== 0x0a) {
+          this.push(this.savedR);
+        }
+        this.savedR = null;
+      }
+      last = chunk.length - 1;
+      while (hi <= last) {
+        if (chunk[hi] === 0x0d) {
+          if (hi === last) {
+            this.savedR = chunk.slice(last);
+            break;
+          } else if (chunk[hi + 1] === 0x0a) {
+            this.push(chunk.slice(lo, hi));
+            lo = hi + 1;
+          }
+        }
+        hi += 1;
+      }
+      if (hi !== lo) {
+        this.push(chunk.slice(lo, hi));
+      }
+      done();
+    };
+
+    return Transform;
+
+  })(Stream.Transform);
+
+  module.exports = Transform;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-logcat/package.json b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-logcat/package.json
new file mode 100644
index 0000000..6fe20fd
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-logcat/package.json
@@ -0,0 +1,75 @@
+{
+  "name": "adbkit-logcat",
+  "version": "1.0.3",
+  "description": "A Node.js interface for working with Android's logcat output.",
+  "keywords": [
+    "adb",
+    "adbkit",
+    "logcat"
+  ],
+  "bugs": {
+    "url": "https://github.com/CyberAgent/adbkit-logcat/issues"
+  },
+  "license": "Apache-2.0",
+  "author": {
+    "name": "CyberAgent, Inc.",
+    "email": "npm@cyberagent.co.jp",
+    "url": "http://www.cyberagent.co.jp/"
+  },
+  "main": "./index",
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/CyberAgent/adbkit-logcat.git"
+  },
+  "scripts": {
+    "postpublish": "grunt clean",
+    "prepublish": "grunt coffee",
+    "test": "grunt test"
+  },
+  "dependencies": {},
+  "devDependencies": {
+    "chai": "~1.8.1",
+    "coffee-script": "~1.6.3",
+    "grunt": "~0.4.1",
+    "grunt-cli": "~0.1.11",
+    "grunt-coffeelint": "~0.0.7",
+    "grunt-contrib-clean": "~0.5.0",
+    "grunt-contrib-coffee": "~0.7.0",
+    "grunt-contrib-watch": "~0.5.3",
+    "grunt-exec": "~0.4.2",
+    "grunt-jsonlint": "~1.0.2",
+    "grunt-notify": "~0.2.16",
+    "mocha": "~1.14.0",
+    "sinon": "~1.7.3",
+    "sinon-chai": "~2.4.0"
+  },
+  "engines": {
+    "node": ">= 0.10.4"
+  },
+  "homepage": "https://github.com/CyberAgent/adbkit-logcat",
+  "_id": "adbkit-logcat@1.0.3",
+  "dist": {
+    "shasum": "8b5fef57086c02c9e24004af5706ee508d83db07",
+    "tarball": "http://registry.npmjs.org/adbkit-logcat/-/adbkit-logcat-1.0.3.tgz"
+  },
+  "_from": "adbkit-logcat@~1.0.3",
+  "_npmVersion": "1.4.3",
+  "_npmUser": {
+    "name": "sorccu",
+    "email": "simo@shoqolate.com"
+  },
+  "maintainers": [
+    {
+      "name": "sorccu",
+      "email": "simo@shoqolate.com"
+    },
+    {
+      "name": "cyberagent",
+      "email": "npm@cyberagent.co.jp"
+    }
+  ],
+  "directories": {},
+  "_shasum": "8b5fef57086c02c9e24004af5706ee508d83db07",
+  "_resolved": "https://registry.npmjs.org/adbkit-logcat/-/adbkit-logcat-1.0.3.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/LICENSE b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/LICENSE
new file mode 100644
index 0000000..2ffff3d
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/LICENSE
@@ -0,0 +1,13 @@
+Copyright © CyberAgent, Inc. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/README.md b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/README.md
new file mode 100644
index 0000000..97977bb
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/README.md
@@ -0,0 +1,469 @@
+# adbkit-monkey
+
+**adbkit-monkey** provides a [Node.js][nodejs] interface for working with the Android [`monkey` tool][monkey-site]. Albeit undocumented, they monkey program can be started in TCP mode with the `--port` argument. In this mode, it accepts a [range of commands][monkey-proto] that can be used to interact with the UI in a non-random manner. This mode is also used internally by the [`monkeyrunner` tool][monkeyrunner-site], although the documentation claims no relation to the monkey tool.
+
+## Getting started
+
+Install via NPM:
+
+```bash
+npm install --save adbkit-monkey
+```
+
+Note that while adbkit-monkey is written in CoffeeScript, it is compiled to JavaScript before publishing to NPM, which means that you are not required to use CoffeeScript.
+
+### Examples
+
+The following examples assume that monkey is already running (via `adb shell monkey --port 1080`) and a port forwarding (`adb forward tcp:1080 tcp:1080`) has been set up.
+
+#### Press the home button
+
+```javascript
+var assert = require('assert');
+var monkey = require('adbkit-monkey');
+
+var client = monkey.connect({ port: 1080 });
+
+client.press(3 /* KEYCODE_HOME */, function(err) {
+  assert.ifError(err);
+  console.log('Pressed home button');
+  client.end();
+});
+```
+
+#### Drag out the notification bar
+
+```javascript
+var assert = require('assert');
+var monkey = require('adbkit-monkey');
+
+var client = monkey.connect({ port: 1080 });
+
+client.multi()
+  .touchDown(100, 0)
+  .sleep(5)
+  .touchMove(100, 20)
+  .sleep(5)
+  .touchMove(100, 40)
+  .sleep(5)
+  .touchMove(100, 60)
+  .sleep(5)
+  .touchMove(100, 80)
+  .sleep(5)
+  .touchMove(100, 100)
+  .sleep(5)
+  .touchUp(100, 100)
+  .sleep(5)
+  .execute(function(err) {
+    assert.ifError(err);
+    console.log('Dragged out the notification bar');
+    client.end();
+  });
+```
+
+#### Get display size
+
+```javascript
+var assert = require('assert');
+var monkey = require('adbkit-monkey');
+
+var client = monkey.connect({ port: 1080 });
+
+client.getDisplayWidth(function(err, width) {
+  assert.ifError(err);
+  client.getDisplayHeight(function(err, height) {
+    assert.ifError(err);
+    console.log('Display size is %dx%d', width, height);
+    client.end();
+  });
+});
+```
+
+#### Type text
+
+Note that you should manually focus a text field first.
+
+```javascript
+var assert = require('assert');
+var monkey = require('adbkit-monkey');
+
+var client = monkey.connect({ port: 1080 });
+
+client.type('hello monkey!', function(err) {
+  assert.ifError(err);
+  console.log('Said hello to monkey');
+  client.end();
+});
+```
+
+## API
+
+### Monkey
+
+#### monkey.connect(options)
+
+Uses [Net.connect()][node-net] to open a new TCP connection to monkey. Useful when combined with `adb forward`.
+
+* **options** Any options [`Net.connect()`][node-net] accepts.
+* Returns: A new monkey `Client` instance.
+
+#### monkey.connectStream(stream)
+
+Attaches a monkey client to an existing monkey protocol stream.
+
+* **stream** The monkey protocol [`Stream`][node-stream].
+* Returns: A new monkey `Client` instance.
+
+### Client
+
+Implements `Api`. See below for details.
+
+#### Events
+
+The following events are available:
+
+* **error** **(err)** Emitted when an error occurs.
+    * **err** An `Error`.
+* **end** Emitted when the stream ends.
+* **finish** Emitted when the stream finishes.
+
+#### client.end()
+
+Ends the underlying stream/connection.
+
+* Returns: The `Client` instance.
+
+#### client.multi()
+
+Returns a new API wrapper that buffers commands for simultaneous delivery instead of sending them individually. When used with `api.sleep()`, allows simple gestures to be executed.
+
+* Returns: A new `Multi` instance. See `Multi` below.
+
+#### client.send(command, callback)
+
+Sends a raw protocol command to monkey.
+
+* **command** The command to send. When `String`, a single command is sent. When `Array`, a series of commands is sent at once.
+* **callback(err, value, command)** Called when monkey responds to the command. If multiple commands were sent, the callback will be called once for each command.
+    * **err** `null` when successful, `Error` otherwise.
+    * **value** The response value, if any.
+    * **command** The command the response is for.
+* Returns: The `Client` instance.
+
+### Api
+
+The monkey API implemented by `Client` and `Multi`.
+
+#### api.done(callback)
+
+Closes the current monkey session and allows a new session to connect.
+
+* **callback(err)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+* Returns: The `Api` implementation instance.
+
+#### api.flipClose(callback)
+
+Simulates closing the keyboard.
+
+* **callback(err)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+* Returns: The `Api` implementation instance.
+
+#### api.flipOpen(callback)
+
+Simulates opening the keyboard.
+
+* **callback(err)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+* Returns: The `Api` implementation instance.
+
+#### api.get(name, callback)
+
+Gets the value of a variable. Use `api.list()` to retrieve a list of supported variables.
+
+* **name** The name of the variable.
+* **callback(err, value)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+    * **value** The value of the variable.
+* Returns: The `Api` implementation instance.
+
+#### api.getAmCurrentAction(callback)
+
+Alias for `api.get('am.current.action', callback)`.
+
+#### api.getAmCurrentCategories(callback)
+
+Alias for `api.get('am.current.categories', callback)`.
+
+#### api.getAmCurrentCompClass(callback)
+
+Alias for `api.get('am.current.comp.class', callback)`.
+
+#### api.getAmCurrentCompPackage(callback)
+
+Alias for `api.get('am.current.comp.package', callback)`.
+
+#### api.getCurrentData(callback)
+
+Alias for `api.get('am.current.data', callback)`.
+
+#### api.getAmCurrentPackage(callback)
+
+Alias for `api.get('am.current.package', callback)`.
+
+#### api.getBuildBoard(callback)
+
+Alias for `api.get('build.board', callback)`.
+
+#### api.getBuildBrand(callback)
+
+Alias for `api.get('build.brand', callback)`.
+
+#### api.getBuildCpuAbi(callback)
+
+Alias for `api.get('build.cpu_abi', callback)`.
+
+#### api.getBuildDevice(callback)
+
+Alias for `api.get('build.device', callback)`.
+
+#### api.getBuildDisplay(callback)
+
+Alias for `api.get('build.display', callback)`.
+
+#### api.getBuildFingerprint(callback)
+
+Alias for `api.get('build.fingerprint', callback)`.
+
+#### api.getBuildHost(callback)
+
+Alias for `api.get('build.host', callback)`.
+
+#### api.getBuildId(callback)
+
+Alias for `api.get('build.id', callback)`.
+
+#### api.getBuildManufacturer(callback)
+
+Alias for `api.get('build.manufacturer', callback)`.
+
+#### api.getBuildModel(callback)
+
+Alias for `api.get('build.model', callback)`.
+
+#### api.getBuildProduct(callback)
+
+Alias for `api.get('build.product', callback)`.
+
+#### api.getBuildTags(callback)
+
+Alias for `api.get('build.tags', callback)`.
+
+#### api.getBuildType(callback)
+
+Alias for `api.get('build.type', callback)`.
+
+#### api.getBuildUser(callback)
+
+Alias for `api.get('build.user', callback)`.
+
+#### api.getBuildVersionCodename(callback)
+
+Alias for `api.get('build.version.codename', callback)`.
+
+#### api.getBuildVersionIncremental(callback)
+
+Alias for `api.get('build.version.incremental', callback)`.
+
+#### api.getBuildVersionRelease(callback)
+
+Alias for `api.get('build.version.release', callback)`.
+
+#### api.getBuildVersionSdk(callback)
+
+Alias for `api.get('build.version.sdk', callback)`.
+
+#### api.getClockMillis(callback)
+
+Alias for `api.get('clock.millis', callback)`.
+
+#### api.getClockRealtime(callback)
+
+Alias for `api.get('clock.realtime', callback)`.
+
+#### api.getClockUptime(callback)
+
+Alias for `api.get('clock.uptime', callback)`.
+
+#### api.getDisplayDensity(callback)
+
+Alias for `api.get('display.density', callback)`.
+
+#### api.getDisplayHeight(callback)
+
+Alias for `api.get('display.height', callback)`. Note that the height may exclude any virtual home button row.
+
+#### api.getDisplayWidth(callback)
+
+Alias for `api.get('display.width', callback)`.
+
+#### api.keyDown(keyCode, callback)
+
+Sends a key down event. Should be coupled with `api.keyUp()`. Note that `api.press()` performs the two events automatically.
+
+* **keyCode** The [key code][android-keycodes]. All monkeys support numeric keycodes, and some support automatic conversion from key names to key codes (e.g. `'home'` to `KEYCODE_HOME`). This will not work for number keys however. The most portable method is to simply use numeric key codes.
+* **callback(err)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+* Returns: The `Api` implementation instance.
+
+#### api.keyUp(keyCode, callback)
+
+Sends a key up event. Should be coupled with `api.keyDown()`. Note that `api.press()` performs the two events automatically.
+
+* **keyCode** See `api.keyDown()`.
+* **callback(err)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+* Returns: The `Api` implementation instance.
+
+#### api.list(callback)
+
+Lists supported variables.
+
+* **callback(err, vars)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+    * **vars** An array of supported variable names, to be used with `api.get()`.
+* Returns: The `Api` implementation instance.
+
+#### api.press(keyCode, callback)
+
+Sends a key press event.
+
+* **keyCode** See `api.keyDown()`.
+* **callback(err)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+* Returns: The `Api` implementation instance.
+
+#### api.quit(callback)
+
+Closes the current monkey session and quits monkey.
+
+* **callback(err)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+* Returns: The `Api` implementation instance.
+
+#### api.sleep(ms, callback)
+
+Sleeps for the given duration. Can be useful for simulating gestures.
+
+* **ms** How many milliseconds to sleep for.
+* **callback(err)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+* Returns: The `Api` implementation instance.
+
+#### api.tap(x, y, callback)
+
+Taps the given coordinates.
+
+* **x** The x coordinate.
+* **y** The y coordinate.
+* **callback(err)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+* Returns: The `Api` implementation instance.
+
+#### api.touchDown(x, y, callback)
+
+Sends a touch down event on the given coordinates.
+
+* **x** The x coordinate.
+* **y** The y coordinate.
+* **callback(err)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+* Returns: The `Api` implementation instance.
+
+#### api.touchMove(x, y, callback)
+
+Sends a touch move event on the given coordinates.
+
+* **x** The x coordinate.
+* **y** The y coordinate.
+* **callback(err)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+* Returns: The `Api` implementation instance.
+
+#### api.touchUp(x, y, callback)
+
+Sends a touch up event on the given coordinates.
+
+* **x** The x coordinate.
+* **y** The y coordinate.
+* **callback(err)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+* Returns: The `Api` implementation instance.
+
+#### api.trackball(x, y, callback)
+
+Sends a trackball event on the given coordinates.
+
+* **x** The x coordinate.
+* **y** The y coordinate.
+* **callback(err)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+* Returns: The `Api` implementation instance.
+
+#### api.type(text, callback)
+
+Types the given text.
+
+* **text** A text `String`. Note that only characters for which [key codes][android-keycodes] exist can be entered. Also note that any IME in use may or may not transform the text.
+* **callback(err)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+* Returns: The `Api` implementation instance.
+
+#### api.wake(callback)
+
+Wakes the device from sleep and allows user input.
+
+* **callback(err)** Called when monkey responds.
+    * **err** `null` when successful, `Error` otherwise.
+* Returns: The `Api` implementation instance.
+
+### Multi
+
+Buffers `Api` commands and delivers them simultaneously for greater control over timing.
+
+Implements all `Api` methods, but without the last `callback` parameter.
+
+#### multi.execute(callback)
+
+Sends all buffered commands.
+
+* **callback(err, values)** Called when monkey has responded to all commands (i.e. just once at the end).
+    * **err** `null` when successful, `Error` otherwise.
+    * **values** An array of all response values, identical to individual `Api` responses.
+
+## More information
+
+* [Monkey][monkey-site]
+    - [Source code][monkey-source]
+    - [Protocol][monkey-proto]
+* [Monkeyrunner][monkeyrunner-site]
+
+## Contributing
+
+See [CONTRIBUTING.md](CONTRIBUTING.md).
+
+## License
+
+See [LICENSE](LICENSE).
+
+Copyright © CyberAgent, Inc. All Rights Reserved.
+
+[nodejs]: <http://nodejs.org/>
+[monkey-site]: <http://developer.android.com/tools/help/monkey.html>
+[monkey-source]: <https://github.com/android/platform_development/blob/master/cmds/monkey/>
+[monkey-proto]: <https://github.com/android/platform_development/blob/master/cmds/monkey/README.NETWORK.txt>
+[monkeyrunner-site]: <http://developer.android.com/tools/help/monkeyrunner_concepts.html>
+[node-net]: <http://nodejs.org/api/net.html>
+[node-stream]: <http://nodejs.org/api/stream.html>
+[android-keycodes]: <http://developer.android.com/reference/android/view/KeyEvent.html>
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/index.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/index.js
new file mode 100644
index 0000000..a81226d
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/index.js
@@ -0,0 +1,15 @@
+(function() {
+  var Path;
+
+  Path = require('path');
+
+  module.exports = (function() {
+    switch (Path.extname(__filename)) {
+      case '.coffee':
+        return require('./src/monkey');
+      default:
+        return require('./lib/monkey');
+    }
+  })();
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey.js
new file mode 100644
index 0000000..9e81c56
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey.js
@@ -0,0 +1,29 @@
+(function() {
+  var Client, Connection, Monkey;
+
+  Client = require('./monkey/client');
+
+  Connection = require('./monkey/connection');
+
+  Monkey = (function() {
+    function Monkey() {}
+
+    Monkey.connect = function(options) {
+      return new Connection().connect(options);
+    };
+
+    Monkey.connectStream = function(stream) {
+      return new Client().connect(stream);
+    };
+
+    return Monkey;
+
+  })();
+
+  Monkey.Connection = Connection;
+
+  Monkey.Client = Client;
+
+  module.exports = Monkey;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/api.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/api.js
new file mode 100644
index 0000000..34fce09
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/api.js
@@ -0,0 +1,276 @@
+(function() {
+  var Api, EventEmitter, _ref,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  EventEmitter = require('events').EventEmitter;
+
+  Api = (function(_super) {
+    __extends(Api, _super);
+
+    function Api() {
+      _ref = Api.__super__.constructor.apply(this, arguments);
+      return _ref;
+    }
+
+    Api.prototype.send = function() {
+      throw new Error("send is not implemented");
+    };
+
+    Api.prototype.keyDown = function(keyCode, callback) {
+      this.send("key down " + keyCode, callback);
+      return this;
+    };
+
+    Api.prototype.keyUp = function(keyCode, callback) {
+      this.send("key up " + keyCode, callback);
+      return this;
+    };
+
+    Api.prototype.touchDown = function(x, y, callback) {
+      this.send("touch down " + x + " " + y, callback);
+      return this;
+    };
+
+    Api.prototype.touchUp = function(x, y, callback) {
+      this.send("touch up " + x + " " + y, callback);
+      return this;
+    };
+
+    Api.prototype.touchMove = function(x, y, callback) {
+      this.send("touch move " + x + " " + y, callback);
+      return this;
+    };
+
+    Api.prototype.trackball = function(dx, dy, callback) {
+      this.send("trackball " + dx + " " + dy, callback);
+      return this;
+    };
+
+    Api.prototype.flipOpen = function(callback) {
+      this.send("flip open", callback);
+      return this;
+    };
+
+    Api.prototype.flipClose = function(callback) {
+      this.send("flip close", callback);
+      return this;
+    };
+
+    Api.prototype.wake = function(callback) {
+      this.send("wake", callback);
+      return this;
+    };
+
+    Api.prototype.tap = function(x, y, callback) {
+      this.send("tap " + x + " " + y, callback);
+      return this;
+    };
+
+    Api.prototype.press = function(keyCode, callback) {
+      this.send("press " + keyCode, callback);
+      return this;
+    };
+
+    Api.prototype.type = function(str, callback) {
+      str = str.replace(/"/g, '\\"');
+      if (str.indexOf(' ') === -1) {
+        this.send("type " + str, callback);
+      } else {
+        this.send("type \"" + str + "\"", callback);
+      }
+      return this;
+    };
+
+    Api.prototype.list = function(callback) {
+      var _this = this;
+      this.send("listvar", function(err, vars) {
+        if (err) {
+          return _this(callback(err));
+        }
+        if (err) {
+          return callback(err);
+        } else {
+          return callback(null, vars.split(/\s+/g));
+        }
+      });
+      return this;
+    };
+
+    Api.prototype.get = function(name, callback) {
+      this.send("getvar " + name, callback);
+      return this;
+    };
+
+    Api.prototype.quit = function(callback) {
+      this.send("quit", callback);
+      return this;
+    };
+
+    Api.prototype.done = function(callback) {
+      this.send("done", callback);
+      return this;
+    };
+
+    Api.prototype.sleep = function(ms, callback) {
+      this.send("sleep " + ms, callback);
+      return this;
+    };
+
+    Api.prototype.getAmCurrentAction = function(callback) {
+      this.get('am.current.action', callback);
+      return this;
+    };
+
+    Api.prototype.getAmCurrentCategories = function(callback) {
+      this.get('am.current.categories', callback);
+      return this;
+    };
+
+    Api.prototype.getAmCurrentCompClass = function(callback) {
+      this.get('am.current.comp.class', callback);
+      return this;
+    };
+
+    Api.prototype.getAmCurrentCompPackage = function(callback) {
+      this.get('am.current.comp.package', callback);
+      return this;
+    };
+
+    Api.prototype.getAmCurrentData = function(callback) {
+      this.get('am.current.data', callback);
+      return this;
+    };
+
+    Api.prototype.getAmCurrentPackage = function(callback) {
+      this.get('am.current.package', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildBoard = function(callback) {
+      this.get('build.board', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildBrand = function(callback) {
+      this.get('build.brand', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildCpuAbi = function(callback) {
+      this.get('build.cpu_abi', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildDevice = function(callback) {
+      this.get('build.device', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildDisplay = function(callback) {
+      this.get('build.display', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildFingerprint = function(callback) {
+      this.get('build.fingerprint', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildHost = function(callback) {
+      this.get('build.host', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildId = function(callback) {
+      this.get('build.id', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildManufacturer = function(callback) {
+      this.get('build.manufacturer', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildModel = function(callback) {
+      this.get('build.model', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildProduct = function(callback) {
+      this.get('build.product', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildTags = function(callback) {
+      this.get('build.tags', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildType = function(callback) {
+      this.get('build.type', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildUser = function(callback) {
+      this.get('build.user', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildVersionCodename = function(callback) {
+      this.get('build.version.codename', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildVersionIncremental = function(callback) {
+      this.get('build.version.incremental', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildVersionRelease = function(callback) {
+      this.get('build.version.release', callback);
+      return this;
+    };
+
+    Api.prototype.getBuildVersionSdk = function(callback) {
+      this.get('build.version.sdk', callback);
+      return this;
+    };
+
+    Api.prototype.getClockMillis = function(callback) {
+      this.get('clock.millis', callback);
+      return this;
+    };
+
+    Api.prototype.getClockRealtime = function(callback) {
+      this.get('clock.realtime', callback);
+      return this;
+    };
+
+    Api.prototype.getClockUptime = function(callback) {
+      this.get('clock.uptime', callback);
+      return this;
+    };
+
+    Api.prototype.getDisplayDensity = function(callback) {
+      this.get('display.density', callback);
+      return this;
+    };
+
+    Api.prototype.getDisplayHeight = function(callback) {
+      this.get('display.height', callback);
+      return this;
+    };
+
+    Api.prototype.getDisplayWidth = function(callback) {
+      this.get('display.width', callback);
+      return this;
+    };
+
+    return Api;
+
+  })(EventEmitter);
+
+  module.exports = Api;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/client.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/client.js
new file mode 100644
index 0000000..b6e1278
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/client.js
@@ -0,0 +1,98 @@
+(function() {
+  var Api, Client, Command, Multi, Parser, Queue, Reply,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Api = require('./api');
+
+  Command = require('./command');
+
+  Reply = require('./reply');
+
+  Queue = require('./queue');
+
+  Multi = require('./multi');
+
+  Parser = require('./parser');
+
+  Client = (function(_super) {
+    __extends(Client, _super);
+
+    function Client() {
+      this.commandQueue = new Queue;
+      this.parser = new Parser;
+      this.stream = null;
+    }
+
+    Client.prototype._hook = function() {
+      var _this = this;
+      this.stream.on('data', function(data) {
+        return _this.parser.parse(data);
+      });
+      this.stream.on('error', function(err) {
+        return _this.emit('error', err);
+      });
+      this.stream.on('end', function() {
+        return _this.emit('end');
+      });
+      this.stream.on('finish', function() {
+        return _this.emit('finish');
+      });
+      this.parser.on('reply', function(reply) {
+        return _this._consume(reply);
+      });
+      this.parser.on('error', function(err) {
+        return _this.emit('error', err);
+      });
+    };
+
+    Client.prototype._consume = function(reply) {
+      var command;
+      if (command = this.commandQueue.dequeue()) {
+        if (reply.isError()) {
+          command.callback(reply.toError(), null, command.command);
+        } else {
+          command.callback(null, reply.value, command.command);
+        }
+      } else {
+        throw new Error("Command queue depleted, but replies still coming in");
+      }
+    };
+
+    Client.prototype.connect = function(stream) {
+      this.stream = stream;
+      this._hook();
+      return this;
+    };
+
+    Client.prototype.end = function() {
+      this.stream.end();
+      return this;
+    };
+
+    Client.prototype.send = function(commands, callback) {
+      var command, _i, _len;
+      if (Array.isArray(commands)) {
+        for (_i = 0, _len = commands.length; _i < _len; _i++) {
+          command = commands[_i];
+          this.commandQueue.enqueue(new Command(command, callback));
+        }
+        this.stream.write("" + (commands.join('\n')) + "\n");
+      } else {
+        this.commandQueue.enqueue(new Command(commands, callback));
+        this.stream.write("" + commands + "\n");
+      }
+      return this;
+    };
+
+    Client.prototype.multi = function() {
+      return new Multi(this);
+    };
+
+    return Client;
+
+  })(Api);
+
+  module.exports = Client;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/command.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/command.js
new file mode 100644
index 0000000..41062e0
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/command.js
@@ -0,0 +1,17 @@
+(function() {
+  var Command;
+
+  Command = (function() {
+    function Command(command, callback) {
+      this.command = command;
+      this.callback = callback;
+      this.next = null;
+    }
+
+    return Command;
+
+  })();
+
+  module.exports = Command;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/connection.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/connection.js
new file mode 100644
index 0000000..b52ee14
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/connection.js
@@ -0,0 +1,42 @@
+(function() {
+  var Client, Connection, Net, _ref,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Net = require('net');
+
+  Client = require('./client');
+
+  Connection = (function(_super) {
+    __extends(Connection, _super);
+
+    function Connection() {
+      _ref = Connection.__super__.constructor.apply(this, arguments);
+      return _ref;
+    }
+
+    Connection.prototype.connect = function(options) {
+      var stream;
+      stream = Net.connect(options);
+      stream.setNoDelay(true);
+      return Connection.__super__.connect.call(this, stream);
+    };
+
+    Connection.prototype._hook = function() {
+      var _this = this;
+      this.stream.on('connect', function() {
+        return _this.emit('connect');
+      });
+      this.stream.on('close', function(hadError) {
+        return _this.emit('close', hadError);
+      });
+      return Connection.__super__._hook.call(this);
+    };
+
+    return Connection;
+
+  })(Client);
+
+  module.exports = Connection;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/multi.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/multi.js
new file mode 100644
index 0000000..80e8214
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/multi.js
@@ -0,0 +1,85 @@
+(function() {
+  var Api, Command, Multi,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  Api = require('./api');
+
+  Command = require('./command');
+
+  Multi = (function(_super) {
+    __extends(Multi, _super);
+
+    function Multi(monkey) {
+      var _this = this;
+      this.monkey = monkey;
+      this.commands = [];
+      this.replies = [];
+      this.errors = [];
+      this.counter = 0;
+      this.sent = false;
+      this.callback = null;
+      this.collector = function(err, result, cmd) {
+        if (err) {
+          _this.errors.push("" + cmd + ": " + err.message);
+        }
+        _this.replies.push(result);
+        _this.counter -= 1;
+        return _this._maybeFinish();
+      };
+    }
+
+    Multi.prototype._maybeFinish = function() {
+      var _this = this;
+      if (this.counter === 0) {
+        if (this.errors.length) {
+          setImmediate(function() {
+            return _this.callback(new Error(_this.errors.join(', ')));
+          });
+        } else {
+          setImmediate(function() {
+            return _this.callback(null, _this.replies);
+          });
+        }
+      }
+    };
+
+    Multi.prototype._forbidReuse = function() {
+      if (this.sent) {
+        throw new Error("Reuse not supported");
+      }
+    };
+
+    Multi.prototype.send = function(command) {
+      this._forbidReuse();
+      this.commands.push(new Command(command, this.collector));
+    };
+
+    Multi.prototype.execute = function(callback) {
+      var command, parts, _i, _len, _ref;
+      this._forbidReuse();
+      this.counter = this.commands.length;
+      this.sent = true;
+      this.callback = callback;
+      if (this.counter === 0) {
+        return;
+      }
+      parts = [];
+      _ref = this.commands;
+      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+        command = _ref[_i];
+        this.monkey.commandQueue.enqueue(command);
+        parts.push(command.command);
+      }
+      parts.push('');
+      this.commands = [];
+      this.monkey.stream.write(parts.join('\n'));
+    };
+
+    return Multi;
+
+  })(Api);
+
+  module.exports = Multi;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/parser.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/parser.js
new file mode 100644
index 0000000..75dcb9f
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/parser.js
@@ -0,0 +1,66 @@
+(function() {
+  var EventEmitter, Parser, Reply,
+    __hasProp = {}.hasOwnProperty,
+    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+  EventEmitter = require('events').EventEmitter;
+
+  Reply = require('./reply');
+
+  Parser = (function(_super) {
+    __extends(Parser, _super);
+
+    function Parser(options) {
+      this.column = 0;
+      this.buffer = new Buffer('');
+    }
+
+    Parser.prototype.parse = function(chunk) {
+      this.buffer = Buffer.concat([this.buffer, chunk]);
+      while (this.column < this.buffer.length) {
+        if (this.buffer[this.column] === 0x0a) {
+          this._parseLine(this.buffer.slice(0, this.column));
+          this.buffer = this.buffer.slice(this.column + 1);
+          this.column = 0;
+        }
+        this.column += 1;
+      }
+      if (this.buffer.length) {
+        this.emit('wait');
+      } else {
+        this.emit('drain');
+      }
+    };
+
+    Parser.prototype._parseLine = function(line) {
+      switch (line[0]) {
+        case 0x4f:
+          if (line.length === 2) {
+            this.emit('reply', new Reply(Reply.OK, null));
+          } else {
+            this.emit('reply', new Reply(Reply.OK, line.toString('ascii', 3)));
+          }
+          break;
+        case 0x45:
+          if (line.length === 5) {
+            this.emit('reply', new Reply(Reply.ERROR, null));
+          } else {
+            this.emit('reply', new Reply(Reply.ERROR, line.toString('ascii', 6)));
+          }
+          break;
+        default:
+          this._complain(line);
+      }
+    };
+
+    Parser.prototype._complain = function(line) {
+      this.emit('error', new SyntaxError("Unparseable line '" + line + "'"));
+    };
+
+    return Parser;
+
+  })(EventEmitter);
+
+  module.exports = Parser;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/queue.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/queue.js
new file mode 100644
index 0000000..19d6615
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/queue.js
@@ -0,0 +1,38 @@
+(function() {
+  var Queue;
+
+  Queue = (function() {
+    function Queue() {
+      this.head = null;
+      this.tail = null;
+    }
+
+    Queue.prototype.enqueue = function(item) {
+      if (this.tail) {
+        this.tail.next = item;
+      } else {
+        this.head = item;
+      }
+      this.tail = item;
+    };
+
+    Queue.prototype.dequeue = function() {
+      var item;
+      item = this.head;
+      if (item) {
+        if (item === this.tail) {
+          this.tail = null;
+        }
+        this.head = item.next;
+        item.next = null;
+      }
+      return item;
+    };
+
+    return Queue;
+
+  })();
+
+  module.exports = Queue;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/reply.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/reply.js
new file mode 100644
index 0000000..349c920
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/lib/monkey/reply.js
@@ -0,0 +1,31 @@
+(function() {
+  var Reply;
+
+  Reply = (function() {
+    Reply.ERROR = 'ERROR';
+
+    Reply.OK = 'OK';
+
+    function Reply(type, value) {
+      this.type = type;
+      this.value = value;
+    }
+
+    Reply.prototype.isError = function() {
+      return this.type === Reply.ERROR;
+    };
+
+    Reply.prototype.toError = function() {
+      if (!this.isError()) {
+        throw new Error('toError() cannot be called for non-errors');
+      }
+      return new Error(this.value);
+    };
+
+    return Reply;
+
+  })();
+
+  module.exports = Reply;
+
+}).call(this);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/node_modules/async/LICENSE b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/node_modules/async/LICENSE
new file mode 100644
index 0000000..b7f9d50
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/node_modules/async/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2010 Caolan McMahon
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/node_modules/async/README.md b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/node_modules/async/README.md
new file mode 100644
index 0000000..951f76e
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/node_modules/async/README.md
@@ -0,0 +1,1425 @@
+# Async.js
+
+Async is a utility module which provides straight-forward, powerful functions
+for working with asynchronous JavaScript. Although originally designed for
+use with [node.js](http://nodejs.org), it can also be used directly in the
+browser. Also supports [component](https://github.com/component/component).
+
+Async provides around 20 functions that include the usual 'functional'
+suspects (map, reduce, filter, each…) as well as some common patterns
+for asynchronous control flow (parallel, series, waterfall…). All these
+functions assume you follow the node.js convention of providing a single
+callback as the last argument of your async function.
+
+
+## Quick Examples
+
+```javascript
+async.map(['file1','file2','file3'], fs.stat, function(err, results){
+    // results is now an array of stats for each file
+});
+
+async.filter(['file1','file2','file3'], fs.exists, function(results){
+    // results now equals an array of the existing files
+});
+
+async.parallel([
+    function(){ ... },
+    function(){ ... }
+], callback);
+
+async.series([
+    function(){ ... },
+    function(){ ... }
+]);
+```
+
+There are many more functions available so take a look at the docs below for a
+full list. This module aims to be comprehensive, so if you feel anything is
+missing please create a GitHub issue for it.
+
+## Common Pitfalls
+
+### Binding a context to an iterator
+
+This section is really about bind, not about async. If you are wondering how to
+make async execute your iterators in a given context, or are confused as to why
+a method of another library isn't working as an iterator, study this example:
+
+```js
+// Here is a simple object with an (unnecessarily roundabout) squaring method
+var AsyncSquaringLibrary = {
+  squareExponent: 2,
+  square: function(number, callback){ 
+    var result = Math.pow(number, this.squareExponent);
+    setTimeout(function(){
+      callback(null, result);
+    }, 200);
+  }
+};
+
+async.map([1, 2, 3], AsyncSquaringLibrary.square, function(err, result){
+  // result is [NaN, NaN, NaN]
+  // This fails because the `this.squareExponent` expression in the square
+  // function is not evaluated in the context of AsyncSquaringLibrary, and is
+  // therefore undefined.
+});
+
+async.map([1, 2, 3], AsyncSquaringLibrary.square.bind(AsyncSquaringLibrary), function(err, result){
+  // result is [1, 4, 9]
+  // With the help of bind we can attach a context to the iterator before
+  // passing it to async. Now the square function will be executed in its 
+  // 'home' AsyncSquaringLibrary context and the value of `this.squareExponent`
+  // will be as expected.
+});
+```
+
+## Download
+
+The source is available for download from
+[GitHub](http://github.com/caolan/async).
+Alternatively, you can install using Node Package Manager (npm):
+
+    npm install async
+
+__Development:__ [async.js](https://github.com/caolan/async/raw/master/lib/async.js) - 29.6kb Uncompressed
+
+## In the Browser
+
+So far it's been tested in IE6, IE7, IE8, FF3.6 and Chrome 5. Usage:
+
+```html
+<script type="text/javascript" src="async.js"></script>
+<script type="text/javascript">
+
+    async.map(data, asyncProcess, function(err, results){
+        alert(results);
+    });
+
+</script>
+```
+
+## Documentation
+
+### Collections
+
+* [each](#each)
+* [eachSeries](#eachSeries)
+* [eachLimit](#eachLimit)
+* [map](#map)
+* [mapSeries](#mapSeries)
+* [mapLimit](#mapLimit)
+* [filter](#filter)
+* [filterSeries](#filterSeries)
+* [reject](#reject)
+* [rejectSeries](#rejectSeries)
+* [reduce](#reduce)
+* [reduceRight](#reduceRight)
+* [detect](#detect)
+* [detectSeries](#detectSeries)
+* [sortBy](#sortBy)
+* [some](#some)
+* [every](#every)
+* [concat](#concat)
+* [concatSeries](#concatSeries)
+
+### Control Flow
+
+* [series](#series)
+* [parallel](#parallel)
+* [parallelLimit](#parallellimittasks-limit-callback)
+* [whilst](#whilst)
+* [doWhilst](#doWhilst)
+* [until](#until)
+* [doUntil](#doUntil)
+* [forever](#forever)
+* [waterfall](#waterfall)
+* [compose](#compose)
+* [applyEach](#applyEach)
+* [applyEachSeries](#applyEachSeries)
+* [queue](#queue)
+* [cargo](#cargo)
+* [auto](#auto)
+* [iterator](#iterator)
+* [apply](#apply)
+* [nextTick](#nextTick)
+* [times](#times)
+* [timesSeries](#timesSeries)
+
+### Utils
+
+* [memoize](#memoize)
+* [unmemoize](#unmemoize)
+* [log](#log)
+* [dir](#dir)
+* [noConflict](#noConflict)
+
+
+## Collections
+
+<a name="forEach" />
+<a name="each" />
+### each(arr, iterator, callback)
+
+Applies an iterator function to each item in an array, in parallel.
+The iterator is called with an item from the list and a callback for when it
+has finished. If the iterator passes an error to this callback, the main
+callback for the each function is immediately called with the error.
+
+Note, that since this function applies the iterator to each item in parallel
+there is no guarantee that the iterator functions will complete in order.
+
+__Arguments__
+
+* arr - An array to iterate over.
+* iterator(item, callback) - A function to apply to each item in the array.
+  The iterator is passed a callback(err) which must be called once it has 
+  completed. If no error has occured, the callback should be run without 
+  arguments or with an explicit null argument.
+* callback(err) - A callback which is called after all the iterator functions
+  have finished, or an error has occurred.
+
+__Example__
+
+```js
+// assuming openFiles is an array of file names and saveFile is a function
+// to save the modified contents of that file:
+
+async.each(openFiles, saveFile, function(err){
+    // if any of the saves produced an error, err would equal that error
+});
+```
+
+---------------------------------------
+
+<a name="forEachSeries" />
+<a name="eachSeries" />
+### eachSeries(arr, iterator, callback)
+
+The same as each only the iterator is applied to each item in the array in
+series. The next iterator is only called once the current one has completed
+processing. This means the iterator functions will complete in order.
+
+
+---------------------------------------
+
+<a name="forEachLimit" />
+<a name="eachLimit" />
+### eachLimit(arr, limit, iterator, callback)
+
+The same as each only no more than "limit" iterators will be simultaneously 
+running at any time.
+
+Note that the items are not processed in batches, so there is no guarantee that
+ the first "limit" iterator functions will complete before any others are 
+started.
+
+__Arguments__
+
+* arr - An array to iterate over.
+* limit - The maximum number of iterators to run at any time.
+* iterator(item, callback) - A function to apply to each item in the array.
+  The iterator is passed a callback(err) which must be called once it has 
+  completed. If no error has occured, the callback should be run without 
+  arguments or with an explicit null argument.
+* callback(err) - A callback which is called after all the iterator functions
+  have finished, or an error has occurred.
+
+__Example__
+
+```js
+// Assume documents is an array of JSON objects and requestApi is a
+// function that interacts with a rate-limited REST api.
+
+async.eachLimit(documents, 20, requestApi, function(err){
+    // if any of the saves produced an error, err would equal that error
+});
+```
+
+---------------------------------------
+
+<a name="map" />
+### map(arr, iterator, callback)
+
+Produces a new array of values by mapping each value in the given array through
+the iterator function. The iterator is called with an item from the array and a
+callback for when it has finished processing. The callback takes 2 arguments, 
+an error and the transformed item from the array. If the iterator passes an
+error to this callback, the main callback for the map function is immediately
+called with the error.
+
+Note, that since this function applies the iterator to each item in parallel
+there is no guarantee that the iterator functions will complete in order, however
+the results array will be in the same order as the original array.
+
+__Arguments__
+
+* arr - An array to iterate over.
+* iterator(item, callback) - A function to apply to each item in the array.
+  The iterator is passed a callback(err, transformed) which must be called once 
+  it has completed with an error (which can be null) and a transformed item.
+* callback(err, results) - A callback which is called after all the iterator
+  functions have finished, or an error has occurred. Results is an array of the
+  transformed items from the original array.
+
+__Example__
+
+```js
+async.map(['file1','file2','file3'], fs.stat, function(err, results){
+    // results is now an array of stats for each file
+});
+```
+
+---------------------------------------
+
+<a name="mapSeries" />
+### mapSeries(arr, iterator, callback)
+
+The same as map only the iterator is applied to each item in the array in
+series. The next iterator is only called once the current one has completed
+processing. The results array will be in the same order as the original.
+
+
+---------------------------------------
+
+<a name="mapLimit" />
+### mapLimit(arr, limit, iterator, callback)
+
+The same as map only no more than "limit" iterators will be simultaneously 
+running at any time.
+
+Note that the items are not processed in batches, so there is no guarantee that
+ the first "limit" iterator functions will complete before any others are 
+started.
+
+__Arguments__
+
+* arr - An array to iterate over.
+* limit - The maximum number of iterators to run at any time.
+* iterator(item, callback) - A function to apply to each item in the array.
+  The iterator is passed a callback(err, transformed) which must be called once 
+  it has completed with an error (which can be null) and a transformed item.
+* callback(err, results) - A callback which is called after all the iterator
+  functions have finished, or an error has occurred. Results is an array of the
+  transformed items from the original array.
+
+__Example__
+
+```js
+async.mapLimit(['file1','file2','file3'], 1, fs.stat, function(err, results){
+    // results is now an array of stats for each file
+});
+```
+
+---------------------------------------
+
+<a name="filter" />
+### filter(arr, iterator, callback)
+
+__Alias:__ select
+
+Returns a new array of all the values which pass an async truth test.
+_The callback for each iterator call only accepts a single argument of true or
+false, it does not accept an error argument first!_ This is in-line with the
+way node libraries work with truth tests like fs.exists. This operation is
+performed in parallel, but the results array will be in the same order as the
+original.
+
+__Arguments__
+
+* arr - An array to iterate over.
+* iterator(item, callback) - A truth test to apply to each item in the array.
+  The iterator is passed a callback(truthValue) which must be called with a 
+  boolean argument once it has completed.
+* callback(results) - A callback which is called after all the iterator
+  functions have finished.
+
+__Example__
+
+```js
+async.filter(['file1','file2','file3'], fs.exists, function(results){
+    // results now equals an array of the existing files
+});
+```
+
+---------------------------------------
+
+<a name="filterSeries" />
+### filterSeries(arr, iterator, callback)
+
+__alias:__ selectSeries
+
+The same as filter only the iterator is applied to each item in the array in
+series. The next iterator is only called once the current one has completed
+processing. The results array will be in the same order as the original.
+
+---------------------------------------
+
+<a name="reject" />
+### reject(arr, iterator, callback)
+
+The opposite of filter. Removes values that pass an async truth test.
+
+---------------------------------------
+
+<a name="rejectSeries" />
+### rejectSeries(arr, iterator, callback)
+
+The same as reject, only the iterator is applied to each item in the array
+in series.
+
+
+---------------------------------------
+
+<a name="reduce" />
+### reduce(arr, memo, iterator, callback)
+
+__aliases:__ inject, foldl
+
+Reduces a list of values into a single value using an async iterator to return
+each successive step. Memo is the initial state of the reduction. This
+function only operates in series. For performance reasons, it may make sense to
+split a call to this function into a parallel map, then use the normal
+Array.prototype.reduce on the results. This function is for situations where
+each step in the reduction needs to be async, if you can get the data before
+reducing it then it's probably a good idea to do so.
+
+__Arguments__
+
+* arr - An array to iterate over.
+* memo - The initial state of the reduction.
+* iterator(memo, item, callback) - A function applied to each item in the
+  array to produce the next step in the reduction. The iterator is passed a
+  callback(err, reduction) which accepts an optional error as its first 
+  argument, and the state of the reduction as the second. If an error is 
+  passed to the callback, the reduction is stopped and the main callback is 
+  immediately called with the error.
+* callback(err, result) - A callback which is called after all the iterator
+  functions have finished. Result is the reduced value.
+
+__Example__
+
+```js
+async.reduce([1,2,3], 0, function(memo, item, callback){
+    // pointless async:
+    process.nextTick(function(){
+        callback(null, memo + item)
+    });
+}, function(err, result){
+    // result is now equal to the last value of memo, which is 6
+});
+```
+
+---------------------------------------
+
+<a name="reduceRight" />
+### reduceRight(arr, memo, iterator, callback)
+
+__Alias:__ foldr
+
+Same as reduce, only operates on the items in the array in reverse order.
+
+
+---------------------------------------
+
+<a name="detect" />
+### detect(arr, iterator, callback)
+
+Returns the first value in a list that passes an async truth test. The
+iterator is applied in parallel, meaning the first iterator to return true will
+fire the detect callback with that result. That means the result might not be
+the first item in the original array (in terms of order) that passes the test.
+
+If order within the original array is important then look at detectSeries.
+
+__Arguments__
+
+* arr - An array to iterate over.
+* iterator(item, callback) - A truth test to apply to each item in the array.
+  The iterator is passed a callback(truthValue) which must be called with a 
+  boolean argument once it has completed.
+* callback(result) - A callback which is called as soon as any iterator returns
+  true, or after all the iterator functions have finished. Result will be
+  the first item in the array that passes the truth test (iterator) or the
+  value undefined if none passed.
+
+__Example__
+
+```js
+async.detect(['file1','file2','file3'], fs.exists, function(result){
+    // result now equals the first file in the list that exists
+});
+```
+
+---------------------------------------
+
+<a name="detectSeries" />
+### detectSeries(arr, iterator, callback)
+
+The same as detect, only the iterator is applied to each item in the array
+in series. This means the result is always the first in the original array (in
+terms of array order) that passes the truth test.
+
+
+---------------------------------------
+
+<a name="sortBy" />
+### sortBy(arr, iterator, callback)
+
+Sorts a list by the results of running each value through an async iterator.
+
+__Arguments__
+
+* arr - An array to iterate over.
+* iterator(item, callback) - A function to apply to each item in the array.
+  The iterator is passed a callback(err, sortValue) which must be called once it
+  has completed with an error (which can be null) and a value to use as the sort
+  criteria.
+* callback(err, results) - A callback which is called after all the iterator
+  functions have finished, or an error has occurred. Results is the items from
+  the original array sorted by the values returned by the iterator calls.
+
+__Example__
+
+```js
+async.sortBy(['file1','file2','file3'], function(file, callback){
+    fs.stat(file, function(err, stats){
+        callback(err, stats.mtime);
+    });
+}, function(err, results){
+    // results is now the original array of files sorted by
+    // modified date
+});
+```
+
+---------------------------------------
+
+<a name="some" />
+### some(arr, iterator, callback)
+
+__Alias:__ any
+
+Returns true if at least one element in the array satisfies an async test.
+_The callback for each iterator call only accepts a single argument of true or
+false, it does not accept an error argument first!_ This is in-line with the
+way node libraries work with truth tests like fs.exists. Once any iterator
+call returns true, the main callback is immediately called.
+
+__Arguments__
+
+* arr - An array to iterate over.
+* iterator(item, callback) - A truth test to apply to each item in the array.
+  The iterator is passed a callback(truthValue) which must be called with a 
+  boolean argument once it has completed.
+* callback(result) - A callback which is called as soon as any iterator returns
+  true, or after all the iterator functions have finished. Result will be
+  either true or false depending on the values of the async tests.
+
+__Example__
+
+```js
+async.some(['file1','file2','file3'], fs.exists, function(result){
+    // if result is true then at least one of the files exists
+});
+```
+
+---------------------------------------
+
+<a name="every" />
+### every(arr, iterator, callback)
+
+__Alias:__ all
+
+Returns true if every element in the array satisfies an async test.
+_The callback for each iterator call only accepts a single argument of true or
+false, it does not accept an error argument first!_ This is in-line with the
+way node libraries work with truth tests like fs.exists.
+
+__Arguments__
+
+* arr - An array to iterate over.
+* iterator(item, callback) - A truth test to apply to each item in the array.
+  The iterator is passed a callback(truthValue) which must be called with a 
+  boolean argument once it has completed.
+* callback(result) - A callback which is called after all the iterator
+  functions have finished. Result will be either true or false depending on
+  the values of the async tests.
+
+__Example__
+
+```js
+async.every(['file1','file2','file3'], fs.exists, function(result){
+    // if result is true then every file exists
+});
+```
+
+---------------------------------------
+
+<a name="concat" />
+### concat(arr, iterator, callback)
+
+Applies an iterator to each item in a list, concatenating the results. Returns the
+concatenated list. The iterators are called in parallel, and the results are
+concatenated as they return. There is no guarantee that the results array will
+be returned in the original order of the arguments passed to the iterator function.
+
+__Arguments__
+
+* arr - An array to iterate over
+* iterator(item, callback) - A function to apply to each item in the array.
+  The iterator is passed a callback(err, results) which must be called once it 
+  has completed with an error (which can be null) and an array of results.
+* callback(err, results) - A callback which is called after all the iterator
+  functions have finished, or an error has occurred. Results is an array containing
+  the concatenated results of the iterator function.
+
+__Example__
+
+```js
+async.concat(['dir1','dir2','dir3'], fs.readdir, function(err, files){
+    // files is now a list of filenames that exist in the 3 directories
+});
+```
+
+---------------------------------------
+
+<a name="concatSeries" />
+### concatSeries(arr, iterator, callback)
+
+Same as async.concat, but executes in series instead of parallel.
+
+
+## Control Flow
+
+<a name="series" />
+### series(tasks, [callback])
+
+Run an array of functions in series, each one running once the previous
+function has completed. If any functions in the series pass an error to its
+callback, no more functions are run and the callback for the series is
+immediately called with the value of the error. Once the tasks have completed,
+the results are passed to the final callback as an array.
+
+It is also possible to use an object instead of an array. Each property will be
+run as a function and the results will be passed to the final callback as an object
+instead of an array. This can be a more readable way of handling results from
+async.series.
+
+
+__Arguments__
+
+* tasks - An array or object containing functions to run, each function is passed
+  a callback(err, result) it must call on completion with an error (which can
+  be null) and an optional result value.
+* callback(err, results) - An optional callback to run once all the functions
+  have completed. This function gets a results array (or object) containing all 
+  the result arguments passed to the task callbacks.
+
+__Example__
+
+```js
+async.series([
+    function(callback){
+        // do some stuff ...
+        callback(null, 'one');
+    },
+    function(callback){
+        // do some more stuff ...
+        callback(null, 'two');
+    }
+],
+// optional callback
+function(err, results){
+    // results is now equal to ['one', 'two']
+});
+
+
+// an example using an object instead of an array
+async.series({
+    one: function(callback){
+        setTimeout(function(){
+            callback(null, 1);
+        }, 200);
+    },
+    two: function(callback){
+        setTimeout(function(){
+            callback(null, 2);
+        }, 100);
+    }
+},
+function(err, results) {
+    // results is now equal to: {one: 1, two: 2}
+});
+```
+
+---------------------------------------
+
+<a name="parallel" />
+### parallel(tasks, [callback])
+
+Run an array of functions in parallel, without waiting until the previous
+function has completed. If any of the functions pass an error to its
+callback, the main callback is immediately called with the value of the error.
+Once the tasks have completed, the results are passed to the final callback as an
+array.
+
+It is also possible to use an object instead of an array. Each property will be
+run as a function and the results will be passed to the final callback as an object
+instead of an array. This can be a more readable way of handling results from
+async.parallel.
+
+
+__Arguments__
+
+* tasks - An array or object containing functions to run, each function is passed 
+  a callback(err, result) it must call on completion with an error (which can
+  be null) and an optional result value.
+* callback(err, results) - An optional callback to run once all the functions
+  have completed. This function gets a results array (or object) containing all 
+  the result arguments passed to the task callbacks.
+
+__Example__
+
+```js
+async.parallel([
+    function(callback){
+        setTimeout(function(){
+            callback(null, 'one');
+        }, 200);
+    },
+    function(callback){
+        setTimeout(function(){
+            callback(null, 'two');
+        }, 100);
+    }
+],
+// optional callback
+function(err, results){
+    // the results array will equal ['one','two'] even though
+    // the second function had a shorter timeout.
+});
+
+
+// an example using an object instead of an array
+async.parallel({
+    one: function(callback){
+        setTimeout(function(){
+            callback(null, 1);
+        }, 200);
+    },
+    two: function(callback){
+        setTimeout(function(){
+            callback(null, 2);
+        }, 100);
+    }
+},
+function(err, results) {
+    // results is now equals to: {one: 1, two: 2}
+});
+```
+
+---------------------------------------
+
+<a name="parallel" />
+### parallelLimit(tasks, limit, [callback])
+
+The same as parallel only the tasks are executed in parallel with a maximum of "limit" 
+tasks executing at any time.
+
+Note that the tasks are not executed in batches, so there is no guarantee that 
+the first "limit" tasks will complete before any others are started.
+
+__Arguments__
+
+* tasks - An array or object containing functions to run, each function is passed 
+  a callback(err, result) it must call on completion with an error (which can
+  be null) and an optional result value.
+* limit - The maximum number of tasks to run at any time.
+* callback(err, results) - An optional callback to run once all the functions
+  have completed. This function gets a results array (or object) containing all 
+  the result arguments passed to the task callbacks.
+
+---------------------------------------
+
+<a name="whilst" />
+### whilst(test, fn, callback)
+
+Repeatedly call fn, while test returns true. Calls the callback when stopped,
+or an error occurs.
+
+__Arguments__
+
+* test() - synchronous truth test to perform before each execution of fn.
+* fn(callback) - A function to call each time the test passes. The function is
+  passed a callback(err) which must be called once it has completed with an 
+  optional error argument.
+* callback(err) - A callback which is called after the test fails and repeated
+  execution of fn has stopped.
+
+__Example__
+
+```js
+var count = 0;
+
+async.whilst(
+    function () { return count < 5; },
+    function (callback) {
+        count++;
+        setTimeout(callback, 1000);
+    },
+    function (err) {
+        // 5 seconds have passed
+    }
+);
+```
+
+---------------------------------------
+
+<a name="doWhilst" />
+### doWhilst(fn, test, callback)
+
+The post check version of whilst. To reflect the difference in the order of operations `test` and `fn` arguments are switched. `doWhilst` is to `whilst` as `do while` is to `while` in plain JavaScript.
+
+---------------------------------------
+
+<a name="until" />
+### until(test, fn, callback)
+
+Repeatedly call fn, until test returns true. Calls the callback when stopped,
+or an error occurs.
+
+The inverse of async.whilst.
+
+---------------------------------------
+
+<a name="doUntil" />
+### doUntil(fn, test, callback)
+
+Like doWhilst except the test is inverted. Note the argument ordering differs from `until`.
+
+---------------------------------------
+
+<a name="forever" />
+### forever(fn, callback)
+
+Calls the asynchronous function 'fn' repeatedly, in series, indefinitely.
+If an error is passed to fn's callback then 'callback' is called with the
+error, otherwise it will never be called.
+
+---------------------------------------
+
+<a name="waterfall" />
+### waterfall(tasks, [callback])
+
+Runs an array of functions in series, each passing their results to the next in
+the array. However, if any of the functions pass an error to the callback, the
+next function is not executed and the main callback is immediately called with
+the error.
+
+__Arguments__
+
+* tasks - An array of functions to run, each function is passed a 
+  callback(err, result1, result2, ...) it must call on completion. The first
+  argument is an error (which can be null) and any further arguments will be 
+  passed as arguments in order to the next task.
+* callback(err, [results]) - An optional callback to run once all the functions
+  have completed. This will be passed the results of the last task's callback.
+
+
+
+__Example__
+
+```js
+async.waterfall([
+    function(callback){
+        callback(null, 'one', 'two');
+    },
+    function(arg1, arg2, callback){
+        callback(null, 'three');
+    },
+    function(arg1, callback){
+        // arg1 now equals 'three'
+        callback(null, 'done');
+    }
+], function (err, result) {
+   // result now equals 'done'    
+});
+```
+
+---------------------------------------
+<a name="compose" />
+### compose(fn1, fn2...)
+
+Creates a function which is a composition of the passed asynchronous
+functions. Each function consumes the return value of the function that
+follows. Composing functions f(), g() and h() would produce the result of
+f(g(h())), only this version uses callbacks to obtain the return values.
+
+Each function is executed with the `this` binding of the composed function.
+
+__Arguments__
+
+* functions... - the asynchronous functions to compose
+
+
+__Example__
+
+```js
+function add1(n, callback) {
+    setTimeout(function () {
+        callback(null, n + 1);
+    }, 10);
+}
+
+function mul3(n, callback) {
+    setTimeout(function () {
+        callback(null, n * 3);
+    }, 10);
+}
+
+var add1mul3 = async.compose(mul3, add1);
+
+add1mul3(4, function (err, result) {
+   // result now equals 15
+});
+```
+
+---------------------------------------
+<a name="applyEach" />
+### applyEach(fns, args..., callback)
+
+Applies the provided arguments to each function in the array, calling the
+callback after all functions have completed. If you only provide the first
+argument then it will return a function which lets you pass in the
+arguments as if it were a single function call.
+
+__Arguments__
+
+* fns - the asynchronous functions to all call with the same arguments
+* args... - any number of separate arguments to pass to the function
+* callback - the final argument should be the callback, called when all
+  functions have completed processing
+
+
+__Example__
+
+```js
+async.applyEach([enableSearch, updateSchema], 'bucket', callback);
+
+// partial application example:
+async.each(
+    buckets,
+    async.applyEach([enableSearch, updateSchema]),
+    callback
+);
+```
+
+---------------------------------------
+
+<a name="applyEachSeries" />
+### applyEachSeries(arr, iterator, callback)
+
+The same as applyEach only the functions are applied in series.
+
+---------------------------------------
+
+<a name="queue" />
+### queue(worker, concurrency)
+
+Creates a queue object with the specified concurrency. Tasks added to the
+queue will be processed in parallel (up to the concurrency limit). If all
+workers are in progress, the task is queued until one is available. Once
+a worker has completed a task, the task's callback is called.
+
+__Arguments__
+
+* worker(task, callback) - An asynchronous function for processing a queued
+  task, which must call its callback(err) argument when finished, with an 
+  optional error as an argument.
+* concurrency - An integer for determining how many worker functions should be
+  run in parallel.
+
+__Queue objects__
+
+The queue object returned by this function has the following properties and
+methods:
+
+* length() - a function returning the number of items waiting to be processed.
+* concurrency - an integer for determining how many worker functions should be
+  run in parallel. This property can be changed after a queue is created to
+  alter the concurrency on-the-fly.
+* push(task, [callback]) - add a new task to the queue, the callback is called
+  once the worker has finished processing the task.
+  instead of a single task, an array of tasks can be submitted. the respective callback is used for every task in the list.
+* unshift(task, [callback]) - add a new task to the front of the queue.
+* saturated - a callback that is called when the queue length hits the concurrency and further tasks will be queued
+* empty - a callback that is called when the last item from the queue is given to a worker
+* drain - a callback that is called when the last item from the queue has returned from the worker
+
+__Example__
+
+```js
+// create a queue object with concurrency 2
+
+var q = async.queue(function (task, callback) {
+    console.log('hello ' + task.name);
+    callback();
+}, 2);
+
+
+// assign a callback
+q.drain = function() {
+    console.log('all items have been processed');
+}
+
+// add some items to the queue
+
+q.push({name: 'foo'}, function (err) {
+    console.log('finished processing foo');
+});
+q.push({name: 'bar'}, function (err) {
+    console.log('finished processing bar');
+});
+
+// add some items to the queue (batch-wise)
+
+q.push([{name: 'baz'},{name: 'bay'},{name: 'bax'}], function (err) {
+    console.log('finished processing bar');
+});
+
+// add some items to the front of the queue
+
+q.unshift({name: 'bar'}, function (err) {
+    console.log('finished processing bar');
+});
+```
+
+---------------------------------------
+
+<a name="cargo" />
+### cargo(worker, [payload])
+
+Creates a cargo object with the specified payload. Tasks added to the
+cargo will be processed altogether (up to the payload limit). If the
+worker is in progress, the task is queued until it is available. Once
+the worker has completed some tasks, each callback of those tasks is called.
+
+__Arguments__
+
+* worker(tasks, callback) - An asynchronous function for processing an array of
+  queued tasks, which must call its callback(err) argument when finished, with 
+  an optional error as an argument.
+* payload - An optional integer for determining how many tasks should be
+  processed per round; if omitted, the default is unlimited.
+
+__Cargo objects__
+
+The cargo object returned by this function has the following properties and
+methods:
+
+* length() - a function returning the number of items waiting to be processed.
+* payload - an integer for determining how many tasks should be
+  process per round. This property can be changed after a cargo is created to
+  alter the payload on-the-fly.
+* push(task, [callback]) - add a new task to the queue, the callback is called
+  once the worker has finished processing the task.
+  instead of a single task, an array of tasks can be submitted. the respective callback is used for every task in the list.
+* saturated - a callback that is called when the queue length hits the concurrency and further tasks will be queued
+* empty - a callback that is called when the last item from the queue is given to a worker
+* drain - a callback that is called when the last item from the queue has returned from the worker
+
+__Example__
+
+```js
+// create a cargo object with payload 2
+
+var cargo = async.cargo(function (tasks, callback) {
+    for(var i=0; i<tasks.length; i++){
+      console.log('hello ' + tasks[i].name);
+    }
+    callback();
+}, 2);
+
+
+// add some items
+
+cargo.push({name: 'foo'}, function (err) {
+    console.log('finished processing foo');
+});
+cargo.push({name: 'bar'}, function (err) {
+    console.log('finished processing bar');
+});
+cargo.push({name: 'baz'}, function (err) {
+    console.log('finished processing baz');
+});
+```
+
+---------------------------------------
+
+<a name="auto" />
+### auto(tasks, [callback])
+
+Determines the best order for running functions based on their requirements.
+Each function can optionally depend on other functions being completed first,
+and each function is run as soon as its requirements are satisfied. If any of
+the functions pass an error to their callback, that function will not complete
+(so any other functions depending on it will not run) and the main callback
+will be called immediately with the error. Functions also receive an object
+containing the results of functions which have completed so far.
+
+Note, all functions are called with a results object as a second argument, 
+so it is unsafe to pass functions in the tasks object which cannot handle the
+extra argument. For example, this snippet of code:
+
+```js
+async.auto({
+  readData: async.apply(fs.readFile, 'data.txt', 'utf-8')
+}, callback);
+```
+
+will have the effect of calling readFile with the results object as the last
+argument, which will fail:
+
+```js
+fs.readFile('data.txt', 'utf-8', cb, {});
+```
+
+Instead, wrap the call to readFile in a function which does not forward the 
+results object:
+
+```js
+async.auto({
+  readData: function(cb, results){
+    fs.readFile('data.txt', 'utf-8', cb);
+  }
+}, callback);
+```
+
+__Arguments__
+
+* tasks - An object literal containing named functions or an array of
+  requirements, with the function itself the last item in the array. The key
+  used for each function or array is used when specifying requirements. The 
+  function receives two arguments: (1) a callback(err, result) which must be 
+  called when finished, passing an error (which can be null) and the result of 
+  the function's execution, and (2) a results object, containing the results of
+  the previously executed functions.
+* callback(err, results) - An optional callback which is called when all the
+  tasks have been completed. The callback will receive an error as an argument
+  if any tasks pass an error to their callback. Results will always be passed
+	but if an error occurred, no other tasks will be performed, and the results
+	object will only contain partial results.
+  
+
+__Example__
+
+```js
+async.auto({
+    get_data: function(callback){
+        // async code to get some data
+    },
+    make_folder: function(callback){
+        // async code to create a directory to store a file in
+        // this is run at the same time as getting the data
+    },
+    write_file: ['get_data', 'make_folder', function(callback){
+        // once there is some data and the directory exists,
+        // write the data to a file in the directory
+        callback(null, filename);
+    }],
+    email_link: ['write_file', function(callback, results){
+        // once the file is written let's email a link to it...
+        // results.write_file contains the filename returned by write_file.
+    }]
+});
+```
+
+This is a fairly trivial example, but to do this using the basic parallel and
+series functions would look like this:
+
+```js
+async.parallel([
+    function(callback){
+        // async code to get some data
+    },
+    function(callback){
+        // async code to create a directory to store a file in
+        // this is run at the same time as getting the data
+    }
+],
+function(err, results){
+    async.series([
+        function(callback){
+            // once there is some data and the directory exists,
+            // write the data to a file in the directory
+        },
+        function(callback){
+            // once the file is written let's email a link to it...
+        }
+    ]);
+});
+```
+
+For a complicated series of async tasks using the auto function makes adding
+new tasks much easier and makes the code more readable.
+
+
+---------------------------------------
+
+<a name="iterator" />
+### iterator(tasks)
+
+Creates an iterator function which calls the next function in the array,
+returning a continuation to call the next one after that. It's also possible to
+'peek' the next iterator by doing iterator.next().
+
+This function is used internally by the async module but can be useful when
+you want to manually control the flow of functions in series.
+
+__Arguments__
+
+* tasks - An array of functions to run.
+
+__Example__
+
+```js
+var iterator = async.iterator([
+    function(){ sys.p('one'); },
+    function(){ sys.p('two'); },
+    function(){ sys.p('three'); }
+]);
+
+node> var iterator2 = iterator();
+'one'
+node> var iterator3 = iterator2();
+'two'
+node> iterator3();
+'three'
+node> var nextfn = iterator2.next();
+node> nextfn();
+'three'
+```
+
+---------------------------------------
+
+<a name="apply" />
+### apply(function, arguments..)
+
+Creates a continuation function with some arguments already applied, a useful
+shorthand when combined with other control flow functions. Any arguments
+passed to the returned function are added to the arguments originally passed
+to apply.
+
+__Arguments__
+
+* function - The function you want to eventually apply all arguments to.
+* arguments... - Any number of arguments to automatically apply when the
+  continuation is called.
+
+__Example__
+
+```js
+// using apply
+
+async.parallel([
+    async.apply(fs.writeFile, 'testfile1', 'test1'),
+    async.apply(fs.writeFile, 'testfile2', 'test2'),
+]);
+
+
+// the same process without using apply
+
+async.parallel([
+    function(callback){
+        fs.writeFile('testfile1', 'test1', callback);
+    },
+    function(callback){
+        fs.writeFile('testfile2', 'test2', callback);
+    }
+]);
+```
+
+It's possible to pass any number of additional arguments when calling the
+continuation:
+
+```js
+node> var fn = async.apply(sys.puts, 'one');
+node> fn('two', 'three');
+one
+two
+three
+```
+
+---------------------------------------
+
+<a name="nextTick" />
+### nextTick(callback)
+
+Calls the callback on a later loop around the event loop. In node.js this just
+calls process.nextTick, in the browser it falls back to setImmediate(callback)
+if available, otherwise setTimeout(callback, 0), which means other higher priority
+events may precede the execution of the callback.
+
+This is used internally for browser-compatibility purposes.
+
+__Arguments__
+
+* callback - The function to call on a later loop around the event loop.
+
+__Example__
+
+```js
+var call_order = [];
+async.nextTick(function(){
+    call_order.push('two');
+    // call_order now equals ['one','two']
+});
+call_order.push('one')
+```
+
+<a name="times" />
+### times(n, callback)
+
+Calls the callback n times and accumulates results in the same manner
+you would use with async.map.
+
+__Arguments__
+
+* n - The number of times to run the function.
+* callback - The function to call n times.
+
+__Example__
+
+```js
+// Pretend this is some complicated async factory
+var createUser = function(id, callback) {
+  callback(null, {
+    id: 'user' + id
+  })
+}
+// generate 5 users
+async.times(5, function(n, next){
+    createUser(n, function(err, user) {
+      next(err, user)
+    })
+}, function(err, users) {
+  // we should now have 5 users
+});
+```
+
+<a name="timesSeries" />
+### timesSeries(n, callback)
+
+The same as times only the iterator is applied to each item in the array in
+series. The next iterator is only called once the current one has completed
+processing. The results array will be in the same order as the original.
+
+
+## Utils
+
+<a name="memoize" />
+### memoize(fn, [hasher])
+
+Caches the results of an async function. When creating a hash to store function
+results against, the callback is omitted from the hash and an optional hash
+function can be used.
+
+The cache of results is exposed as the `memo` property of the function returned
+by `memoize`.
+
+__Arguments__
+
+* fn - the function you to proxy and cache results from.
+* hasher - an optional function for generating a custom hash for storing
+  results, it has all the arguments applied to it apart from the callback, and
+  must be synchronous.
+
+__Example__
+
+```js
+var slow_fn = function (name, callback) {
+    // do something
+    callback(null, result);
+};
+var fn = async.memoize(slow_fn);
+
+// fn can now be used as if it were slow_fn
+fn('some name', function () {
+    // callback
+});
+```
+
+<a name="unmemoize" />
+### unmemoize(fn)
+
+Undoes a memoized function, reverting it to the original, unmemoized
+form. Comes handy in tests.
+
+__Arguments__
+
+* fn - the memoized function
+
+<a name="log" />
+### log(function, arguments)
+
+Logs the result of an async function to the console. Only works in node.js or
+in browsers that support console.log and console.error (such as FF and Chrome).
+If multiple arguments are returned from the async function, console.log is
+called on each argument in order.
+
+__Arguments__
+
+* function - The function you want to eventually apply all arguments to.
+* arguments... - Any number of arguments to apply to the function.
+
+__Example__
+
+```js
+var hello = function(name, callback){
+    setTimeout(function(){
+        callback(null, 'hello ' + name);
+    }, 1000);
+};
+```
+```js
+node> async.log(hello, 'world');
+'hello world'
+```
+
+---------------------------------------
+
+<a name="dir" />
+### dir(function, arguments)
+
+Logs the result of an async function to the console using console.dir to
+display the properties of the resulting object. Only works in node.js or
+in browsers that support console.dir and console.error (such as FF and Chrome).
+If multiple arguments are returned from the async function, console.dir is
+called on each argument in order.
+
+__Arguments__
+
+* function - The function you want to eventually apply all arguments to.
+* arguments... - Any number of arguments to apply to the function.
+
+__Example__
+
+```js
+var hello = function(name, callback){
+    setTimeout(function(){
+        callback(null, {hello: name});
+    }, 1000);
+};
+```
+```js
+node> async.dir(hello, 'world');
+{hello: 'world'}
+```
+
+---------------------------------------
+
+<a name="noConflict" />
+### noConflict()
+
+Changes the value of async back to its original value, returning a reference to the
+async object.
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/node_modules/async/component.json b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/node_modules/async/component.json
new file mode 100644
index 0000000..bbb0115
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/node_modules/async/component.json
@@ -0,0 +1,11 @@
+{
+  "name": "async",
+  "repo": "caolan/async",
+  "description": "Higher-order functions and common patterns for asynchronous code",
+  "version": "0.1.23",
+  "keywords": [],
+  "dependencies": {},
+  "development": {},
+  "main": "lib/async.js",
+  "scripts": [ "lib/async.js" ]
+}
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/node_modules/async/lib/async.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/node_modules/async/lib/async.js
new file mode 100755
index 0000000..1eebb15
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/node_modules/async/lib/async.js
@@ -0,0 +1,958 @@
+/*global setImmediate: false, setTimeout: false, console: false */
+(function () {
+
+    var async = {};
+
+    // global on the server, window in the browser
+    var root, previous_async;
+
+    root = this;
+    if (root != null) {
+      previous_async = root.async;
+    }
+
+    async.noConflict = function () {
+        root.async = previous_async;
+        return async;
+    };
+
+    function only_once(fn) {
+        var called = false;
+        return function() {
+            if (called) throw new Error("Callback was already called.");
+            called = true;
+            fn.apply(root, arguments);
+        }
+    }
+
+    //// cross-browser compatiblity functions ////
+
+    var _each = function (arr, iterator) {
+        if (arr.forEach) {
+            return arr.forEach(iterator);
+        }
+        for (var i = 0; i < arr.length; i += 1) {
+            iterator(arr[i], i, arr);
+        }
+    };
+
+    var _map = function (arr, iterator) {
+        if (arr.map) {
+            return arr.map(iterator);
+        }
+        var results = [];
+        _each(arr, function (x, i, a) {
+            results.push(iterator(x, i, a));
+        });
+        return results;
+    };
+
+    var _reduce = function (arr, iterator, memo) {
+        if (arr.reduce) {
+            return arr.reduce(iterator, memo);
+        }
+        _each(arr, function (x, i, a) {
+            memo = iterator(memo, x, i, a);
+        });
+        return memo;
+    };
+
+    var _keys = function (obj) {
+        if (Object.keys) {
+            return Object.keys(obj);
+        }
+        var keys = [];
+        for (var k in obj) {
+            if (obj.hasOwnProperty(k)) {
+                keys.push(k);
+            }
+        }
+        return keys;
+    };
+
+    //// exported async module functions ////
+
+    //// nextTick implementation with browser-compatible fallback ////
+    if (typeof process === 'undefined' || !(process.nextTick)) {
+        if (typeof setImmediate === 'function') {
+            async.nextTick = function (fn) {
+                // not a direct alias for IE10 compatibility
+                setImmediate(fn);
+            };
+            async.setImmediate = async.nextTick;
+        }
+        else {
+            async.nextTick = function (fn) {
+                setTimeout(fn, 0);
+            };
+            async.setImmediate = async.nextTick;
+        }
+    }
+    else {
+        async.nextTick = process.nextTick;
+        if (typeof setImmediate !== 'undefined') {
+            async.setImmediate = function (fn) {
+              // not a direct alias for IE10 compatibility
+              setImmediate(fn);
+            };
+        }
+        else {
+            async.setImmediate = async.nextTick;
+        }
+    }
+
+    async.each = function (arr, iterator, callback) {
+        callback = callback || function () {};
+        if (!arr.length) {
+            return callback();
+        }
+        var completed = 0;
+        _each(arr, function (x) {
+            iterator(x, only_once(function (err) {
+                if (err) {
+                    callback(err);
+                    callback = function () {};
+                }
+                else {
+                    completed += 1;
+                    if (completed >= arr.length) {
+                        callback(null);
+                    }
+                }
+            }));
+        });
+    };
+    async.forEach = async.each;
+
+    async.eachSeries = function (arr, iterator, callback) {
+        callback = callback || function () {};
+        if (!arr.length) {
+            return callback();
+        }
+        var completed = 0;
+        var iterate = function () {
+            iterator(arr[completed], function (err) {
+                if (err) {
+                    callback(err);
+                    callback = function () {};
+                }
+                else {
+                    completed += 1;
+                    if (completed >= arr.length) {
+                        callback(null);
+                    }
+                    else {
+                        iterate();
+                    }
+                }
+            });
+        };
+        iterate();
+    };
+    async.forEachSeries = async.eachSeries;
+
+    async.eachLimit = function (arr, limit, iterator, callback) {
+        var fn = _eachLimit(limit);
+        fn.apply(null, [arr, iterator, callback]);
+    };
+    async.forEachLimit = async.eachLimit;
+
+    var _eachLimit = function (limit) {
+
+        return function (arr, iterator, callback) {
+            callback = callback || function () {};
+            if (!arr.length || limit <= 0) {
+                return callback();
+            }
+            var completed = 0;
+            var started = 0;
+            var running = 0;
+
+            (function replenish () {
+                if (completed >= arr.length) {
+                    return callback();
+                }
+
+                while (running < limit && started < arr.length) {
+                    started += 1;
+                    running += 1;
+                    iterator(arr[started - 1], function (err) {
+                        if (err) {
+                            callback(err);
+                            callback = function () {};
+                        }
+                        else {
+                            completed += 1;
+                            running -= 1;
+                            if (completed >= arr.length) {
+                                callback();
+                            }
+                            else {
+                                replenish();
+                            }
+                        }
+                    });
+                }
+            })();
+        };
+    };
+
+
+    var doParallel = function (fn) {
+        return function () {
+            var args = Array.prototype.slice.call(arguments);
+            return fn.apply(null, [async.each].concat(args));
+        };
+    };
+    var doParallelLimit = function(limit, fn) {
+        return function () {
+            var args = Array.prototype.slice.call(arguments);
+            return fn.apply(null, [_eachLimit(limit)].concat(args));
+        };
+    };
+    var doSeries = function (fn) {
+        return function () {
+            var args = Array.prototype.slice.call(arguments);
+            return fn.apply(null, [async.eachSeries].concat(args));
+        };
+    };
+
+
+    var _asyncMap = function (eachfn, arr, iterator, callback) {
+        var results = [];
+        arr = _map(arr, function (x, i) {
+            return {index: i, value: x};
+        });
+        eachfn(arr, function (x, callback) {
+            iterator(x.value, function (err, v) {
+                results[x.index] = v;
+                callback(err);
+            });
+        }, function (err) {
+            callback(err, results);
+        });
+    };
+    async.map = doParallel(_asyncMap);
+    async.mapSeries = doSeries(_asyncMap);
+    async.mapLimit = function (arr, limit, iterator, callback) {
+        return _mapLimit(limit)(arr, iterator, callback);
+    };
+
+    var _mapLimit = function(limit) {
+        return doParallelLimit(limit, _asyncMap);
+    };
+
+    // reduce only has a series version, as doing reduce in parallel won't
+    // work in many situations.
+    async.reduce = function (arr, memo, iterator, callback) {
+        async.eachSeries(arr, function (x, callback) {
+            iterator(memo, x, function (err, v) {
+                memo = v;
+                callback(err);
+            });
+        }, function (err) {
+            callback(err, memo);
+        });
+    };
+    // inject alias
+    async.inject = async.reduce;
+    // foldl alias
+    async.foldl = async.reduce;
+
+    async.reduceRight = function (arr, memo, iterator, callback) {
+        var reversed = _map(arr, function (x) {
+            return x;
+        }).reverse();
+        async.reduce(reversed, memo, iterator, callback);
+    };
+    // foldr alias
+    async.foldr = async.reduceRight;
+
+    var _filter = function (eachfn, arr, iterator, callback) {
+        var results = [];
+        arr = _map(arr, function (x, i) {
+            return {index: i, value: x};
+        });
+        eachfn(arr, function (x, callback) {
+            iterator(x.value, function (v) {
+                if (v) {
+                    results.push(x);
+                }
+                callback();
+            });
+        }, function (err) {
+            callback(_map(results.sort(function (a, b) {
+                return a.index - b.index;
+            }), function (x) {
+                return x.value;
+            }));
+        });
+    };
+    async.filter = doParallel(_filter);
+    async.filterSeries = doSeries(_filter);
+    // select alias
+    async.select = async.filter;
+    async.selectSeries = async.filterSeries;
+
+    var _reject = function (eachfn, arr, iterator, callback) {
+        var results = [];
+        arr = _map(arr, function (x, i) {
+            return {index: i, value: x};
+        });
+        eachfn(arr, function (x, callback) {
+            iterator(x.value, function (v) {
+                if (!v) {
+                    results.push(x);
+                }
+                callback();
+            });
+        }, function (err) {
+            callback(_map(results.sort(function (a, b) {
+                return a.index - b.index;
+            }), function (x) {
+                return x.value;
+            }));
+        });
+    };
+    async.reject = doParallel(_reject);
+    async.rejectSeries = doSeries(_reject);
+
+    var _detect = function (eachfn, arr, iterator, main_callback) {
+        eachfn(arr, function (x, callback) {
+            iterator(x, function (result) {
+                if (result) {
+                    main_callback(x);
+                    main_callback = function () {};
+                }
+                else {
+                    callback();
+                }
+            });
+        }, function (err) {
+            main_callback();
+        });
+    };
+    async.detect = doParallel(_detect);
+    async.detectSeries = doSeries(_detect);
+
+    async.some = function (arr, iterator, main_callback) {
+        async.each(arr, function (x, callback) {
+            iterator(x, function (v) {
+                if (v) {
+                    main_callback(true);
+                    main_callback = function () {};
+                }
+                callback();
+            });
+        }, function (err) {
+            main_callback(false);
+        });
+    };
+    // any alias
+    async.any = async.some;
+
+    async.every = function (arr, iterator, main_callback) {
+        async.each(arr, function (x, callback) {
+            iterator(x, function (v) {
+                if (!v) {
+                    main_callback(false);
+                    main_callback = function () {};
+                }
+                callback();
+            });
+        }, function (err) {
+            main_callback(true);
+        });
+    };
+    // all alias
+    async.all = async.every;
+
+    async.sortBy = function (arr, iterator, callback) {
+        async.map(arr, function (x, callback) {
+            iterator(x, function (err, criteria) {
+                if (err) {
+                    callback(err);
+                }
+                else {
+                    callback(null, {value: x, criteria: criteria});
+                }
+            });
+        }, function (err, results) {
+            if (err) {
+                return callback(err);
+            }
+            else {
+                var fn = function (left, right) {
+                    var a = left.criteria, b = right.criteria;
+                    return a < b ? -1 : a > b ? 1 : 0;
+                };
+                callback(null, _map(results.sort(fn), function (x) {
+                    return x.value;
+                }));
+            }
+        });
+    };
+
+    async.auto = function (tasks, callback) {
+        callback = callback || function () {};
+        var keys = _keys(tasks);
+        if (!keys.length) {
+            return callback(null);
+        }
+
+        var results = {};
+
+        var listeners = [];
+        var addListener = function (fn) {
+            listeners.unshift(fn);
+        };
+        var removeListener = function (fn) {
+            for (var i = 0; i < listeners.length; i += 1) {
+                if (listeners[i] === fn) {
+                    listeners.splice(i, 1);
+                    return;
+                }
+            }
+        };
+        var taskComplete = function () {
+            _each(listeners.slice(0), function (fn) {
+                fn();
+            });
+        };
+
+        addListener(function () {
+            if (_keys(results).length === keys.length) {
+                callback(null, results);
+                callback = function () {};
+            }
+        });
+
+        _each(keys, function (k) {
+            var task = (tasks[k] instanceof Function) ? [tasks[k]]: tasks[k];
+            var taskCallback = function (err) {
+                var args = Array.prototype.slice.call(arguments, 1);
+                if (args.length <= 1) {
+                    args = args[0];
+                }
+                if (err) {
+                    var safeResults = {};
+                    _each(_keys(results), function(rkey) {
+                        safeResults[rkey] = results[rkey];
+                    });
+                    safeResults[k] = args;
+                    callback(err, safeResults);
+                    // stop subsequent errors hitting callback multiple times
+                    callback = function () {};
+                }
+                else {
+                    results[k] = args;
+                    async.setImmediate(taskComplete);
+                }
+            };
+            var requires = task.slice(0, Math.abs(task.length - 1)) || [];
+            var ready = function () {
+                return _reduce(requires, function (a, x) {
+                    return (a && results.hasOwnProperty(x));
+                }, true) && !results.hasOwnProperty(k);
+            };
+            if (ready()) {
+                task[task.length - 1](taskCallback, results);
+            }
+            else {
+                var listener = function () {
+                    if (ready()) {
+                        removeListener(listener);
+                        task[task.length - 1](taskCallback, results);
+                    }
+                };
+                addListener(listener);
+            }
+        });
+    };
+
+    async.waterfall = function (tasks, callback) {
+        callback = callback || function () {};
+        if (tasks.constructor !== Array) {
+          var err = new Error('First argument to waterfall must be an array of functions');
+          return callback(err);
+        }
+        if (!tasks.length) {
+            return callback();
+        }
+        var wrapIterator = function (iterator) {
+            return function (err) {
+                if (err) {
+                    callback.apply(null, arguments);
+                    callback = function () {};
+                }
+                else {
+                    var args = Array.prototype.slice.call(arguments, 1);
+                    var next = iterator.next();
+                    if (next) {
+                        args.push(wrapIterator(next));
+                    }
+                    else {
+                        args.push(callback);
+                    }
+                    async.setImmediate(function () {
+                        iterator.apply(null, args);
+                    });
+                }
+            };
+        };
+        wrapIterator(async.iterator(tasks))();
+    };
+
+    var _parallel = function(eachfn, tasks, callback) {
+        callback = callback || function () {};
+        if (tasks.constructor === Array) {
+            eachfn.map(tasks, function (fn, callback) {
+                if (fn) {
+                    fn(function (err) {
+                        var args = Array.prototype.slice.call(arguments, 1);
+                        if (args.length <= 1) {
+                            args = args[0];
+                        }
+                        callback.call(null, err, args);
+                    });
+                }
+            }, callback);
+        }
+        else {
+            var results = {};
+            eachfn.each(_keys(tasks), function (k, callback) {
+                tasks[k](function (err) {
+                    var args = Array.prototype.slice.call(arguments, 1);
+                    if (args.length <= 1) {
+                        args = args[0];
+                    }
+                    results[k] = args;
+                    callback(err);
+                });
+            }, function (err) {
+                callback(err, results);
+            });
+        }
+    };
+
+    async.parallel = function (tasks, callback) {
+        _parallel({ map: async.map, each: async.each }, tasks, callback);
+    };
+
+    async.parallelLimit = function(tasks, limit, callback) {
+        _parallel({ map: _mapLimit(limit), each: _eachLimit(limit) }, tasks, callback);
+    };
+
+    async.series = function (tasks, callback) {
+        callback = callback || function () {};
+        if (tasks.constructor === Array) {
+            async.mapSeries(tasks, function (fn, callback) {
+                if (fn) {
+                    fn(function (err) {
+                        var args = Array.prototype.slice.call(arguments, 1);
+                        if (args.length <= 1) {
+                            args = args[0];
+                        }
+                        callback.call(null, err, args);
+                    });
+                }
+            }, callback);
+        }
+        else {
+            var results = {};
+            async.eachSeries(_keys(tasks), function (k, callback) {
+                tasks[k](function (err) {
+                    var args = Array.prototype.slice.call(arguments, 1);
+                    if (args.length <= 1) {
+                        args = args[0];
+                    }
+                    results[k] = args;
+                    callback(err);
+                });
+            }, function (err) {
+                callback(err, results);
+            });
+        }
+    };
+
+    async.iterator = function (tasks) {
+        var makeCallback = function (index) {
+            var fn = function () {
+                if (tasks.length) {
+                    tasks[index].apply(null, arguments);
+                }
+                return fn.next();
+            };
+            fn.next = function () {
+                return (index < tasks.length - 1) ? makeCallback(index + 1): null;
+            };
+            return fn;
+        };
+        return makeCallback(0);
+    };
+
+    async.apply = function (fn) {
+        var args = Array.prototype.slice.call(arguments, 1);
+        return function () {
+            return fn.apply(
+                null, args.concat(Array.prototype.slice.call(arguments))
+            );
+        };
+    };
+
+    var _concat = function (eachfn, arr, fn, callback) {
+        var r = [];
+        eachfn(arr, function (x, cb) {
+            fn(x, function (err, y) {
+                r = r.concat(y || []);
+                cb(err);
+            });
+        }, function (err) {
+            callback(err, r);
+        });
+    };
+    async.concat = doParallel(_concat);
+    async.concatSeries = doSeries(_concat);
+
+    async.whilst = function (test, iterator, callback) {
+        if (test()) {
+            iterator(function (err) {
+                if (err) {
+                    return callback(err);
+                }
+                async.whilst(test, iterator, callback);
+            });
+        }
+        else {
+            callback();
+        }
+    };
+
+    async.doWhilst = function (iterator, test, callback) {
+        iterator(function (err) {
+            if (err) {
+                return callback(err);
+            }
+            if (test()) {
+                async.doWhilst(iterator, test, callback);
+            }
+            else {
+                callback();
+            }
+        });
+    };
+
+    async.until = function (test, iterator, callback) {
+        if (!test()) {
+            iterator(function (err) {
+                if (err) {
+                    return callback(err);
+                }
+                async.until(test, iterator, callback);
+            });
+        }
+        else {
+            callback();
+        }
+    };
+
+    async.doUntil = function (iterator, test, callback) {
+        iterator(function (err) {
+            if (err) {
+                return callback(err);
+            }
+            if (!test()) {
+                async.doUntil(iterator, test, callback);
+            }
+            else {
+                callback();
+            }
+        });
+    };
+
+    async.queue = function (worker, concurrency) {
+        if (concurrency === undefined) {
+            concurrency = 1;
+        }
+        function _insert(q, data, pos, callback) {
+          if(data.constructor !== Array) {
+              data = [data];
+          }
+          _each(data, function(task) {
+              var item = {
+                  data: task,
+                  callback: typeof callback === 'function' ? callback : null
+              };
+
+              if (pos) {
+                q.tasks.unshift(item);
+              } else {
+                q.tasks.push(item);
+              }
+
+              if (q.saturated && q.tasks.length === concurrency) {
+                  q.saturated();
+              }
+              async.setImmediate(q.process);
+          });
+        }
+
+        var workers = 0;
+        var q = {
+            tasks: [],
+            concurrency: concurrency,
+            saturated: null,
+            empty: null,
+            drain: null,
+            push: function (data, callback) {
+              _insert(q, data, false, callback);
+            },
+            unshift: function (data, callback) {
+              _insert(q, data, true, callback);
+            },
+            process: function () {
+                if (workers < q.concurrency && q.tasks.length) {
+                    var task = q.tasks.shift();
+                    if (q.empty && q.tasks.length === 0) {
+                        q.empty();
+                    }
+                    workers += 1;
+                    var next = function () {
+                        workers -= 1;
+                        if (task.callback) {
+                            task.callback.apply(task, arguments);
+                        }
+                        if (q.drain && q.tasks.length + workers === 0) {
+                            q.drain();
+                        }
+                        q.process();
+                    };
+                    var cb = only_once(next);
+                    worker(task.data, cb);
+                }
+            },
+            length: function () {
+                return q.tasks.length;
+            },
+            running: function () {
+                return workers;
+            }
+        };
+        return q;
+    };
+
+    async.cargo = function (worker, payload) {
+        var working     = false,
+            tasks       = [];
+
+        var cargo = {
+            tasks: tasks,
+            payload: payload,
+            saturated: null,
+            empty: null,
+            drain: null,
+            push: function (data, callback) {
+                if(data.constructor !== Array) {
+                    data = [data];
+                }
+                _each(data, function(task) {
+                    tasks.push({
+                        data: task,
+                        callback: typeof callback === 'function' ? callback : null
+                    });
+                    if (cargo.saturated && tasks.length === payload) {
+                        cargo.saturated();
+                    }
+                });
+                async.setImmediate(cargo.process);
+            },
+            process: function process() {
+                if (working) return;
+                if (tasks.length === 0) {
+                    if(cargo.drain) cargo.drain();
+                    return;
+                }
+
+                var ts = typeof payload === 'number'
+                            ? tasks.splice(0, payload)
+                            : tasks.splice(0);
+
+                var ds = _map(ts, function (task) {
+                    return task.data;
+                });
+
+                if(cargo.empty) cargo.empty();
+                working = true;
+                worker(ds, function () {
+                    working = false;
+
+                    var args = arguments;
+                    _each(ts, function (data) {
+                        if (data.callback) {
+                            data.callback.apply(null, args);
+                        }
+                    });
+
+                    process();
+                });
+            },
+            length: function () {
+                return tasks.length;
+            },
+            running: function () {
+                return working;
+            }
+        };
+        return cargo;
+    };
+
+    var _console_fn = function (name) {
+        return function (fn) {
+            var args = Array.prototype.slice.call(arguments, 1);
+            fn.apply(null, args.concat([function (err) {
+                var args = Array.prototype.slice.call(arguments, 1);
+                if (typeof console !== 'undefined') {
+                    if (err) {
+                        if (console.error) {
+                            console.error(err);
+                        }
+                    }
+                    else if (console[name]) {
+                        _each(args, function (x) {
+                            console[name](x);
+                        });
+                    }
+                }
+            }]));
+        };
+    };
+    async.log = _console_fn('log');
+    async.dir = _console_fn('dir');
+    /*async.info = _console_fn('info');
+    async.warn = _console_fn('warn');
+    async.error = _console_fn('error');*/
+
+    async.memoize = function (fn, hasher) {
+        var memo = {};
+        var queues = {};
+        hasher = hasher || function (x) {
+            return x;
+        };
+        var memoized = function () {
+            var args = Array.prototype.slice.call(arguments);
+            var callback = args.pop();
+            var key = hasher.apply(null, args);
+            if (key in memo) {
+                callback.apply(null, memo[key]);
+            }
+            else if (key in queues) {
+                queues[key].push(callback);
+            }
+            else {
+                queues[key] = [callback];
+                fn.apply(null, args.concat([function () {
+                    memo[key] = arguments;
+                    var q = queues[key];
+                    delete queues[key];
+                    for (var i = 0, l = q.length; i < l; i++) {
+                      q[i].apply(null, arguments);
+                    }
+                }]));
+            }
+        };
+        memoized.memo = memo;
+        memoized.unmemoized = fn;
+        return memoized;
+    };
+
+    async.unmemoize = function (fn) {
+      return function () {
+        return (fn.unmemoized || fn).apply(null, arguments);
+      };
+    };
+
+    async.times = function (count, iterator, callback) {
+        var counter = [];
+        for (var i = 0; i < count; i++) {
+            counter.push(i);
+        }
+        return async.map(counter, iterator, callback);
+    };
+
+    async.timesSeries = function (count, iterator, callback) {
+        var counter = [];
+        for (var i = 0; i < count; i++) {
+            counter.push(i);
+        }
+        return async.mapSeries(counter, iterator, callback);
+    };
+
+    async.compose = function (/* functions... */) {
+        var fns = Array.prototype.reverse.call(arguments);
+        return function () {
+            var that = this;
+            var args = Array.prototype.slice.call(arguments);
+            var callback = args.pop();
+            async.reduce(fns, args, function (newargs, fn, cb) {
+                fn.apply(that, newargs.concat([function () {
+                    var err = arguments[0];
+                    var nextargs = Array.prototype.slice.call(arguments, 1);
+                    cb(err, nextargs);
+                }]))
+            },
+            function (err, results) {
+                callback.apply(that, [err].concat(results));
+            });
+        };
+    };
+
+    var _applyEach = function (eachfn, fns /*args...*/) {
+        var go = function () {
+            var that = this;
+            var args = Array.prototype.slice.call(arguments);
+            var callback = args.pop();
+            return eachfn(fns, function (fn, cb) {
+                fn.apply(that, args.concat([cb]));
+            },
+            callback);
+        };
+        if (arguments.length > 2) {
+            var args = Array.prototype.slice.call(arguments, 2);
+            return go.apply(this, args);
+        }
+        else {
+            return go;
+        }
+    };
+    async.applyEach = doParallel(_applyEach);
+    async.applyEachSeries = doSeries(_applyEach);
+
+    async.forever = function (fn, callback) {
+        function next(err) {
+            if (err) {
+                if (callback) {
+                    return callback(err);
+                }
+                throw err;
+            }
+            fn(next);
+        }
+        next();
+    };
+
+    // AMD / RequireJS
+    if (typeof define !== 'undefined' && define.amd) {
+        define([], function () {
+            return async;
+        });
+    }
+    // Node.js
+    else if (typeof module !== 'undefined' && module.exports) {
+        module.exports = async;
+    }
+    // included directly via <script> tag
+    else {
+        root.async = async;
+    }
+
+}());
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/node_modules/async/package.json b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/node_modules/async/package.json
new file mode 100644
index 0000000..5648629
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/node_modules/async/package.json
@@ -0,0 +1,45 @@
+{
+  "name": "async",
+  "description": "Higher-order functions and common patterns for asynchronous code",
+  "main": "./lib/async",
+  "author": {
+    "name": "Caolan McMahon"
+  },
+  "version": "0.2.10",
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/caolan/async.git"
+  },
+  "bugs": {
+    "url": "https://github.com/caolan/async/issues"
+  },
+  "licenses": [
+    {
+      "type": "MIT",
+      "url": "https://github.com/caolan/async/raw/master/LICENSE"
+    }
+  ],
+  "devDependencies": {
+    "nodeunit": ">0.0.0",
+    "uglify-js": "1.2.x",
+    "nodelint": ">0.0.0"
+  },
+  "jam": {
+    "main": "lib/async.js",
+    "include": [
+      "lib/async.js",
+      "README.md",
+      "LICENSE"
+    ]
+  },
+  "scripts": {
+    "test": "nodeunit test/test-async.js"
+  },
+  "readme": "# Async.js\n\nAsync is a utility module which provides straight-forward, powerful functions\nfor working with asynchronous JavaScript. Although originally designed for\nuse with [node.js](http://nodejs.org), it can also be used directly in the\nbrowser. Also supports [component](https://github.com/component/component).\n\nAsync provides around 20 functions that include the usual 'functional'\nsuspects (map, reduce, filter, each…) as well as some common patterns\nfor asynchronous control flow (parallel, series, waterfall…). All these\nfunctions assume you follow the node.js convention of providing a single\ncallback as the last argument of your async function.\n\n\n## Quick Examples\n\n```javascript\nasync.map(['file1','file2','file3'], fs.stat, function(err, results){\n    // results is now an array of stats for each file\n});\n\nasync.filter(['file1','file2','file3'], fs.exists, function(results){\n    // results now equals an array of the existing files\n});\n\nasync.parallel([\n    function(){ ... },\n    function(){ ... }\n], callback);\n\nasync.series([\n    function(){ ... },\n    function(){ ... }\n]);\n```\n\nThere are many more functions available so take a look at the docs below for a\nfull list. This module aims to be comprehensive, so if you feel anything is\nmissing please create a GitHub issue for it.\n\n## Common Pitfalls\n\n### Binding a context to an iterator\n\nThis section is really about bind, not about async. If you are wondering how to\nmake async execute your iterators in a given context, or are confused as to why\na method of another library isn't working as an iterator, study this example:\n\n```js\n// Here is a simple object with an (unnecessarily roundabout) squaring method\nvar AsyncSquaringLibrary = {\n  squareExponent: 2,\n  square: function(number, callback){ \n    var result = Math.pow(number, this.squareExponent);\n    setTimeout(function(){\n      callback(null, result);\n    }, 200);\n  }\n};\n\nasync.map([1, 2, 3], AsyncSquaringLibrary.square, function(err, result){\n  // result is [NaN, NaN, NaN]\n  // This fails because the `this.squareExponent` expression in the square\n  // function is not evaluated in the context of AsyncSquaringLibrary, and is\n  // therefore undefined.\n});\n\nasync.map([1, 2, 3], AsyncSquaringLibrary.square.bind(AsyncSquaringLibrary), function(err, result){\n  // result is [1, 4, 9]\n  // With the help of bind we can attach a context to the iterator before\n  // passing it to async. Now the square function will be executed in its \n  // 'home' AsyncSquaringLibrary context and the value of `this.squareExponent`\n  // will be as expected.\n});\n```\n\n## Download\n\nThe source is available for download from\n[GitHub](http://github.com/caolan/async).\nAlternatively, you can install using Node Package Manager (npm):\n\n    npm install async\n\n__Development:__ [async.js](https://github.com/caolan/async/raw/master/lib/async.js) - 29.6kb Uncompressed\n\n## In the Browser\n\nSo far it's been tested in IE6, IE7, IE8, FF3.6 and Chrome 5. Usage:\n\n```html\n<script type=\"text/javascript\" src=\"async.js\"></script>\n<script type=\"text/javascript\">\n\n    async.map(data, asyncProcess, function(err, results){\n        alert(results);\n    });\n\n</script>\n```\n\n## Documentation\n\n### Collections\n\n* [each](#each)\n* [eachSeries](#eachSeries)\n* [eachLimit](#eachLimit)\n* [map](#map)\n* [mapSeries](#mapSeries)\n* [mapLimit](#mapLimit)\n* [filter](#filter)\n* [filterSeries](#filterSeries)\n* [reject](#reject)\n* [rejectSeries](#rejectSeries)\n* [reduce](#reduce)\n* [reduceRight](#reduceRight)\n* [detect](#detect)\n* [detectSeries](#detectSeries)\n* [sortBy](#sortBy)\n* [some](#some)\n* [every](#every)\n* [concat](#concat)\n* [concatSeries](#concatSeries)\n\n### Control Flow\n\n* [series](#series)\n* [parallel](#parallel)\n* [parallelLimit](#parallellimittasks-limit-callback)\n* [whilst](#whilst)\n* [doWhilst](#doWhilst)\n* [until](#until)\n* [doUntil](#doUntil)\n* [forever](#forever)\n* [waterfall](#waterfall)\n* [compose](#compose)\n* [applyEach](#applyEach)\n* [applyEachSeries](#applyEachSeries)\n* [queue](#queue)\n* [cargo](#cargo)\n* [auto](#auto)\n* [iterator](#iterator)\n* [apply](#apply)\n* [nextTick](#nextTick)\n* [times](#times)\n* [timesSeries](#timesSeries)\n\n### Utils\n\n* [memoize](#memoize)\n* [unmemoize](#unmemoize)\n* [log](#log)\n* [dir](#dir)\n* [noConflict](#noConflict)\n\n\n## Collections\n\n<a name=\"forEach\" />\n<a name=\"each\" />\n### each(arr, iterator, callback)\n\nApplies an iterator function to each item in an array, in parallel.\nThe iterator is called with an item from the list and a callback for when it\nhas finished. If the iterator passes an error to this callback, the main\ncallback for the each function is immediately called with the error.\n\nNote, that since this function applies the iterator to each item in parallel\nthere is no guarantee that the iterator functions will complete in order.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* iterator(item, callback) - A function to apply to each item in the array.\n  The iterator is passed a callback(err) which must be called once it has \n  completed. If no error has occured, the callback should be run without \n  arguments or with an explicit null argument.\n* callback(err) - A callback which is called after all the iterator functions\n  have finished, or an error has occurred.\n\n__Example__\n\n```js\n// assuming openFiles is an array of file names and saveFile is a function\n// to save the modified contents of that file:\n\nasync.each(openFiles, saveFile, function(err){\n    // if any of the saves produced an error, err would equal that error\n});\n```\n\n---------------------------------------\n\n<a name=\"forEachSeries\" />\n<a name=\"eachSeries\" />\n### eachSeries(arr, iterator, callback)\n\nThe same as each only the iterator is applied to each item in the array in\nseries. The next iterator is only called once the current one has completed\nprocessing. This means the iterator functions will complete in order.\n\n\n---------------------------------------\n\n<a name=\"forEachLimit\" />\n<a name=\"eachLimit\" />\n### eachLimit(arr, limit, iterator, callback)\n\nThe same as each only no more than \"limit\" iterators will be simultaneously \nrunning at any time.\n\nNote that the items are not processed in batches, so there is no guarantee that\n the first \"limit\" iterator functions will complete before any others are \nstarted.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* limit - The maximum number of iterators to run at any time.\n* iterator(item, callback) - A function to apply to each item in the array.\n  The iterator is passed a callback(err) which must be called once it has \n  completed. If no error has occured, the callback should be run without \n  arguments or with an explicit null argument.\n* callback(err) - A callback which is called after all the iterator functions\n  have finished, or an error has occurred.\n\n__Example__\n\n```js\n// Assume documents is an array of JSON objects and requestApi is a\n// function that interacts with a rate-limited REST api.\n\nasync.eachLimit(documents, 20, requestApi, function(err){\n    // if any of the saves produced an error, err would equal that error\n});\n```\n\n---------------------------------------\n\n<a name=\"map\" />\n### map(arr, iterator, callback)\n\nProduces a new array of values by mapping each value in the given array through\nthe iterator function. The iterator is called with an item from the array and a\ncallback for when it has finished processing. The callback takes 2 arguments, \nan error and the transformed item from the array. If the iterator passes an\nerror to this callback, the main callback for the map function is immediately\ncalled with the error.\n\nNote, that since this function applies the iterator to each item in parallel\nthere is no guarantee that the iterator functions will complete in order, however\nthe results array will be in the same order as the original array.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* iterator(item, callback) - A function to apply to each item in the array.\n  The iterator is passed a callback(err, transformed) which must be called once \n  it has completed with an error (which can be null) and a transformed item.\n* callback(err, results) - A callback which is called after all the iterator\n  functions have finished, or an error has occurred. Results is an array of the\n  transformed items from the original array.\n\n__Example__\n\n```js\nasync.map(['file1','file2','file3'], fs.stat, function(err, results){\n    // results is now an array of stats for each file\n});\n```\n\n---------------------------------------\n\n<a name=\"mapSeries\" />\n### mapSeries(arr, iterator, callback)\n\nThe same as map only the iterator is applied to each item in the array in\nseries. The next iterator is only called once the current one has completed\nprocessing. The results array will be in the same order as the original.\n\n\n---------------------------------------\n\n<a name=\"mapLimit\" />\n### mapLimit(arr, limit, iterator, callback)\n\nThe same as map only no more than \"limit\" iterators will be simultaneously \nrunning at any time.\n\nNote that the items are not processed in batches, so there is no guarantee that\n the first \"limit\" iterator functions will complete before any others are \nstarted.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* limit - The maximum number of iterators to run at any time.\n* iterator(item, callback) - A function to apply to each item in the array.\n  The iterator is passed a callback(err, transformed) which must be called once \n  it has completed with an error (which can be null) and a transformed item.\n* callback(err, results) - A callback which is called after all the iterator\n  functions have finished, or an error has occurred. Results is an array of the\n  transformed items from the original array.\n\n__Example__\n\n```js\nasync.mapLimit(['file1','file2','file3'], 1, fs.stat, function(err, results){\n    // results is now an array of stats for each file\n});\n```\n\n---------------------------------------\n\n<a name=\"filter\" />\n### filter(arr, iterator, callback)\n\n__Alias:__ select\n\nReturns a new array of all the values which pass an async truth test.\n_The callback for each iterator call only accepts a single argument of true or\nfalse, it does not accept an error argument first!_ This is in-line with the\nway node libraries work with truth tests like fs.exists. This operation is\nperformed in parallel, but the results array will be in the same order as the\noriginal.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* iterator(item, callback) - A truth test to apply to each item in the array.\n  The iterator is passed a callback(truthValue) which must be called with a \n  boolean argument once it has completed.\n* callback(results) - A callback which is called after all the iterator\n  functions have finished.\n\n__Example__\n\n```js\nasync.filter(['file1','file2','file3'], fs.exists, function(results){\n    // results now equals an array of the existing files\n});\n```\n\n---------------------------------------\n\n<a name=\"filterSeries\" />\n### filterSeries(arr, iterator, callback)\n\n__alias:__ selectSeries\n\nThe same as filter only the iterator is applied to each item in the array in\nseries. The next iterator is only called once the current one has completed\nprocessing. The results array will be in the same order as the original.\n\n---------------------------------------\n\n<a name=\"reject\" />\n### reject(arr, iterator, callback)\n\nThe opposite of filter. Removes values that pass an async truth test.\n\n---------------------------------------\n\n<a name=\"rejectSeries\" />\n### rejectSeries(arr, iterator, callback)\n\nThe same as reject, only the iterator is applied to each item in the array\nin series.\n\n\n---------------------------------------\n\n<a name=\"reduce\" />\n### reduce(arr, memo, iterator, callback)\n\n__aliases:__ inject, foldl\n\nReduces a list of values into a single value using an async iterator to return\neach successive step. Memo is the initial state of the reduction. This\nfunction only operates in series. For performance reasons, it may make sense to\nsplit a call to this function into a parallel map, then use the normal\nArray.prototype.reduce on the results. This function is for situations where\neach step in the reduction needs to be async, if you can get the data before\nreducing it then it's probably a good idea to do so.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* memo - The initial state of the reduction.\n* iterator(memo, item, callback) - A function applied to each item in the\n  array to produce the next step in the reduction. The iterator is passed a\n  callback(err, reduction) which accepts an optional error as its first \n  argument, and the state of the reduction as the second. If an error is \n  passed to the callback, the reduction is stopped and the main callback is \n  immediately called with the error.\n* callback(err, result) - A callback which is called after all the iterator\n  functions have finished. Result is the reduced value.\n\n__Example__\n\n```js\nasync.reduce([1,2,3], 0, function(memo, item, callback){\n    // pointless async:\n    process.nextTick(function(){\n        callback(null, memo + item)\n    });\n}, function(err, result){\n    // result is now equal to the last value of memo, which is 6\n});\n```\n\n---------------------------------------\n\n<a name=\"reduceRight\" />\n### reduceRight(arr, memo, iterator, callback)\n\n__Alias:__ foldr\n\nSame as reduce, only operates on the items in the array in reverse order.\n\n\n---------------------------------------\n\n<a name=\"detect\" />\n### detect(arr, iterator, callback)\n\nReturns the first value in a list that passes an async truth test. The\niterator is applied in parallel, meaning the first iterator to return true will\nfire the detect callback with that result. That means the result might not be\nthe first item in the original array (in terms of order) that passes the test.\n\nIf order within the original array is important then look at detectSeries.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* iterator(item, callback) - A truth test to apply to each item in the array.\n  The iterator is passed a callback(truthValue) which must be called with a \n  boolean argument once it has completed.\n* callback(result) - A callback which is called as soon as any iterator returns\n  true, or after all the iterator functions have finished. Result will be\n  the first item in the array that passes the truth test (iterator) or the\n  value undefined if none passed.\n\n__Example__\n\n```js\nasync.detect(['file1','file2','file3'], fs.exists, function(result){\n    // result now equals the first file in the list that exists\n});\n```\n\n---------------------------------------\n\n<a name=\"detectSeries\" />\n### detectSeries(arr, iterator, callback)\n\nThe same as detect, only the iterator is applied to each item in the array\nin series. This means the result is always the first in the original array (in\nterms of array order) that passes the truth test.\n\n\n---------------------------------------\n\n<a name=\"sortBy\" />\n### sortBy(arr, iterator, callback)\n\nSorts a list by the results of running each value through an async iterator.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* iterator(item, callback) - A function to apply to each item in the array.\n  The iterator is passed a callback(err, sortValue) which must be called once it\n  has completed with an error (which can be null) and a value to use as the sort\n  criteria.\n* callback(err, results) - A callback which is called after all the iterator\n  functions have finished, or an error has occurred. Results is the items from\n  the original array sorted by the values returned by the iterator calls.\n\n__Example__\n\n```js\nasync.sortBy(['file1','file2','file3'], function(file, callback){\n    fs.stat(file, function(err, stats){\n        callback(err, stats.mtime);\n    });\n}, function(err, results){\n    // results is now the original array of files sorted by\n    // modified date\n});\n```\n\n---------------------------------------\n\n<a name=\"some\" />\n### some(arr, iterator, callback)\n\n__Alias:__ any\n\nReturns true if at least one element in the array satisfies an async test.\n_The callback for each iterator call only accepts a single argument of true or\nfalse, it does not accept an error argument first!_ This is in-line with the\nway node libraries work with truth tests like fs.exists. Once any iterator\ncall returns true, the main callback is immediately called.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* iterator(item, callback) - A truth test to apply to each item in the array.\n  The iterator is passed a callback(truthValue) which must be called with a \n  boolean argument once it has completed.\n* callback(result) - A callback which is called as soon as any iterator returns\n  true, or after all the iterator functions have finished. Result will be\n  either true or false depending on the values of the async tests.\n\n__Example__\n\n```js\nasync.some(['file1','file2','file3'], fs.exists, function(result){\n    // if result is true then at least one of the files exists\n});\n```\n\n---------------------------------------\n\n<a name=\"every\" />\n### every(arr, iterator, callback)\n\n__Alias:__ all\n\nReturns true if every element in the array satisfies an async test.\n_The callback for each iterator call only accepts a single argument of true or\nfalse, it does not accept an error argument first!_ This is in-line with the\nway node libraries work with truth tests like fs.exists.\n\n__Arguments__\n\n* arr - An array to iterate over.\n* iterator(item, callback) - A truth test to apply to each item in the array.\n  The iterator is passed a callback(truthValue) which must be called with a \n  boolean argument once it has completed.\n* callback(result) - A callback which is called after all the iterator\n  functions have finished. Result will be either true or false depending on\n  the values of the async tests.\n\n__Example__\n\n```js\nasync.every(['file1','file2','file3'], fs.exists, function(result){\n    // if result is true then every file exists\n});\n```\n\n---------------------------------------\n\n<a name=\"concat\" />\n### concat(arr, iterator, callback)\n\nApplies an iterator to each item in a list, concatenating the results. Returns the\nconcatenated list. The iterators are called in parallel, and the results are\nconcatenated as they return. There is no guarantee that the results array will\nbe returned in the original order of the arguments passed to the iterator function.\n\n__Arguments__\n\n* arr - An array to iterate over\n* iterator(item, callback) - A function to apply to each item in the array.\n  The iterator is passed a callback(err, results) which must be called once it \n  has completed with an error (which can be null) and an array of results.\n* callback(err, results) - A callback which is called after all the iterator\n  functions have finished, or an error has occurred. Results is an array containing\n  the concatenated results of the iterator function.\n\n__Example__\n\n```js\nasync.concat(['dir1','dir2','dir3'], fs.readdir, function(err, files){\n    // files is now a list of filenames that exist in the 3 directories\n});\n```\n\n---------------------------------------\n\n<a name=\"concatSeries\" />\n### concatSeries(arr, iterator, callback)\n\nSame as async.concat, but executes in series instead of parallel.\n\n\n## Control Flow\n\n<a name=\"series\" />\n### series(tasks, [callback])\n\nRun an array of functions in series, each one running once the previous\nfunction has completed. If any functions in the series pass an error to its\ncallback, no more functions are run and the callback for the series is\nimmediately called with the value of the error. Once the tasks have completed,\nthe results are passed to the final callback as an array.\n\nIt is also possible to use an object instead of an array. Each property will be\nrun as a function and the results will be passed to the final callback as an object\ninstead of an array. This can be a more readable way of handling results from\nasync.series.\n\n\n__Arguments__\n\n* tasks - An array or object containing functions to run, each function is passed\n  a callback(err, result) it must call on completion with an error (which can\n  be null) and an optional result value.\n* callback(err, results) - An optional callback to run once all the functions\n  have completed. This function gets a results array (or object) containing all \n  the result arguments passed to the task callbacks.\n\n__Example__\n\n```js\nasync.series([\n    function(callback){\n        // do some stuff ...\n        callback(null, 'one');\n    },\n    function(callback){\n        // do some more stuff ...\n        callback(null, 'two');\n    }\n],\n// optional callback\nfunction(err, results){\n    // results is now equal to ['one', 'two']\n});\n\n\n// an example using an object instead of an array\nasync.series({\n    one: function(callback){\n        setTimeout(function(){\n            callback(null, 1);\n        }, 200);\n    },\n    two: function(callback){\n        setTimeout(function(){\n            callback(null, 2);\n        }, 100);\n    }\n},\nfunction(err, results) {\n    // results is now equal to: {one: 1, two: 2}\n});\n```\n\n---------------------------------------\n\n<a name=\"parallel\" />\n### parallel(tasks, [callback])\n\nRun an array of functions in parallel, without waiting until the previous\nfunction has completed. If any of the functions pass an error to its\ncallback, the main callback is immediately called with the value of the error.\nOnce the tasks have completed, the results are passed to the final callback as an\narray.\n\nIt is also possible to use an object instead of an array. Each property will be\nrun as a function and the results will be passed to the final callback as an object\ninstead of an array. This can be a more readable way of handling results from\nasync.parallel.\n\n\n__Arguments__\n\n* tasks - An array or object containing functions to run, each function is passed \n  a callback(err, result) it must call on completion with an error (which can\n  be null) and an optional result value.\n* callback(err, results) - An optional callback to run once all the functions\n  have completed. This function gets a results array (or object) containing all \n  the result arguments passed to the task callbacks.\n\n__Example__\n\n```js\nasync.parallel([\n    function(callback){\n        setTimeout(function(){\n            callback(null, 'one');\n        }, 200);\n    },\n    function(callback){\n        setTimeout(function(){\n            callback(null, 'two');\n        }, 100);\n    }\n],\n// optional callback\nfunction(err, results){\n    // the results array will equal ['one','two'] even though\n    // the second function had a shorter timeout.\n});\n\n\n// an example using an object instead of an array\nasync.parallel({\n    one: function(callback){\n        setTimeout(function(){\n            callback(null, 1);\n        }, 200);\n    },\n    two: function(callback){\n        setTimeout(function(){\n            callback(null, 2);\n        }, 100);\n    }\n},\nfunction(err, results) {\n    // results is now equals to: {one: 1, two: 2}\n});\n```\n\n---------------------------------------\n\n<a name=\"parallel\" />\n### parallelLimit(tasks, limit, [callback])\n\nThe same as parallel only the tasks are executed in parallel with a maximum of \"limit\" \ntasks executing at any time.\n\nNote that the tasks are not executed in batches, so there is no guarantee that \nthe first \"limit\" tasks will complete before any others are started.\n\n__Arguments__\n\n* tasks - An array or object containing functions to run, each function is passed \n  a callback(err, result) it must call on completion with an error (which can\n  be null) and an optional result value.\n* limit - The maximum number of tasks to run at any time.\n* callback(err, results) - An optional callback to run once all the functions\n  have completed. This function gets a results array (or object) containing all \n  the result arguments passed to the task callbacks.\n\n---------------------------------------\n\n<a name=\"whilst\" />\n### whilst(test, fn, callback)\n\nRepeatedly call fn, while test returns true. Calls the callback when stopped,\nor an error occurs.\n\n__Arguments__\n\n* test() - synchronous truth test to perform before each execution of fn.\n* fn(callback) - A function to call each time the test passes. The function is\n  passed a callback(err) which must be called once it has completed with an \n  optional error argument.\n* callback(err) - A callback which is called after the test fails and repeated\n  execution of fn has stopped.\n\n__Example__\n\n```js\nvar count = 0;\n\nasync.whilst(\n    function () { return count < 5; },\n    function (callback) {\n        count++;\n        setTimeout(callback, 1000);\n    },\n    function (err) {\n        // 5 seconds have passed\n    }\n);\n```\n\n---------------------------------------\n\n<a name=\"doWhilst\" />\n### doWhilst(fn, test, callback)\n\nThe post check version of whilst. To reflect the difference in the order of operations `test` and `fn` arguments are switched. `doWhilst` is to `whilst` as `do while` is to `while` in plain JavaScript.\n\n---------------------------------------\n\n<a name=\"until\" />\n### until(test, fn, callback)\n\nRepeatedly call fn, until test returns true. Calls the callback when stopped,\nor an error occurs.\n\nThe inverse of async.whilst.\n\n---------------------------------------\n\n<a name=\"doUntil\" />\n### doUntil(fn, test, callback)\n\nLike doWhilst except the test is inverted. Note the argument ordering differs from `until`.\n\n---------------------------------------\n\n<a name=\"forever\" />\n### forever(fn, callback)\n\nCalls the asynchronous function 'fn' repeatedly, in series, indefinitely.\nIf an error is passed to fn's callback then 'callback' is called with the\nerror, otherwise it will never be called.\n\n---------------------------------------\n\n<a name=\"waterfall\" />\n### waterfall(tasks, [callback])\n\nRuns an array of functions in series, each passing their results to the next in\nthe array. However, if any of the functions pass an error to the callback, the\nnext function is not executed and the main callback is immediately called with\nthe error.\n\n__Arguments__\n\n* tasks - An array of functions to run, each function is passed a \n  callback(err, result1, result2, ...) it must call on completion. The first\n  argument is an error (which can be null) and any further arguments will be \n  passed as arguments in order to the next task.\n* callback(err, [results]) - An optional callback to run once all the functions\n  have completed. This will be passed the results of the last task's callback.\n\n\n\n__Example__\n\n```js\nasync.waterfall([\n    function(callback){\n        callback(null, 'one', 'two');\n    },\n    function(arg1, arg2, callback){\n        callback(null, 'three');\n    },\n    function(arg1, callback){\n        // arg1 now equals 'three'\n        callback(null, 'done');\n    }\n], function (err, result) {\n   // result now equals 'done'    \n});\n```\n\n---------------------------------------\n<a name=\"compose\" />\n### compose(fn1, fn2...)\n\nCreates a function which is a composition of the passed asynchronous\nfunctions. Each function consumes the return value of the function that\nfollows. Composing functions f(), g() and h() would produce the result of\nf(g(h())), only this version uses callbacks to obtain the return values.\n\nEach function is executed with the `this` binding of the composed function.\n\n__Arguments__\n\n* functions... - the asynchronous functions to compose\n\n\n__Example__\n\n```js\nfunction add1(n, callback) {\n    setTimeout(function () {\n        callback(null, n + 1);\n    }, 10);\n}\n\nfunction mul3(n, callback) {\n    setTimeout(function () {\n        callback(null, n * 3);\n    }, 10);\n}\n\nvar add1mul3 = async.compose(mul3, add1);\n\nadd1mul3(4, function (err, result) {\n   // result now equals 15\n});\n```\n\n---------------------------------------\n<a name=\"applyEach\" />\n### applyEach(fns, args..., callback)\n\nApplies the provided arguments to each function in the array, calling the\ncallback after all functions have completed. If you only provide the first\nargument then it will return a function which lets you pass in the\narguments as if it were a single function call.\n\n__Arguments__\n\n* fns - the asynchronous functions to all call with the same arguments\n* args... - any number of separate arguments to pass to the function\n* callback - the final argument should be the callback, called when all\n  functions have completed processing\n\n\n__Example__\n\n```js\nasync.applyEach([enableSearch, updateSchema], 'bucket', callback);\n\n// partial application example:\nasync.each(\n    buckets,\n    async.applyEach([enableSearch, updateSchema]),\n    callback\n);\n```\n\n---------------------------------------\n\n<a name=\"applyEachSeries\" />\n### applyEachSeries(arr, iterator, callback)\n\nThe same as applyEach only the functions are applied in series.\n\n---------------------------------------\n\n<a name=\"queue\" />\n### queue(worker, concurrency)\n\nCreates a queue object with the specified concurrency. Tasks added to the\nqueue will be processed in parallel (up to the concurrency limit). If all\nworkers are in progress, the task is queued until one is available. Once\na worker has completed a task, the task's callback is called.\n\n__Arguments__\n\n* worker(task, callback) - An asynchronous function for processing a queued\n  task, which must call its callback(err) argument when finished, with an \n  optional error as an argument.\n* concurrency - An integer for determining how many worker functions should be\n  run in parallel.\n\n__Queue objects__\n\nThe queue object returned by this function has the following properties and\nmethods:\n\n* length() - a function returning the number of items waiting to be processed.\n* concurrency - an integer for determining how many worker functions should be\n  run in parallel. This property can be changed after a queue is created to\n  alter the concurrency on-the-fly.\n* push(task, [callback]) - add a new task to the queue, the callback is called\n  once the worker has finished processing the task.\n  instead of a single task, an array of tasks can be submitted. the respective callback is used for every task in the list.\n* unshift(task, [callback]) - add a new task to the front of the queue.\n* saturated - a callback that is called when the queue length hits the concurrency and further tasks will be queued\n* empty - a callback that is called when the last item from the queue is given to a worker\n* drain - a callback that is called when the last item from the queue has returned from the worker\n\n__Example__\n\n```js\n// create a queue object with concurrency 2\n\nvar q = async.queue(function (task, callback) {\n    console.log('hello ' + task.name);\n    callback();\n}, 2);\n\n\n// assign a callback\nq.drain = function() {\n    console.log('all items have been processed');\n}\n\n// add some items to the queue\n\nq.push({name: 'foo'}, function (err) {\n    console.log('finished processing foo');\n});\nq.push({name: 'bar'}, function (err) {\n    console.log('finished processing bar');\n});\n\n// add some items to the queue (batch-wise)\n\nq.push([{name: 'baz'},{name: 'bay'},{name: 'bax'}], function (err) {\n    console.log('finished processing bar');\n});\n\n// add some items to the front of the queue\n\nq.unshift({name: 'bar'}, function (err) {\n    console.log('finished processing bar');\n});\n```\n\n---------------------------------------\n\n<a name=\"cargo\" />\n### cargo(worker, [payload])\n\nCreates a cargo object with the specified payload. Tasks added to the\ncargo will be processed altogether (up to the payload limit). If the\nworker is in progress, the task is queued until it is available. Once\nthe worker has completed some tasks, each callback of those tasks is called.\n\n__Arguments__\n\n* worker(tasks, callback) - An asynchronous function for processing an array of\n  queued tasks, which must call its callback(err) argument when finished, with \n  an optional error as an argument.\n* payload - An optional integer for determining how many tasks should be\n  processed per round; if omitted, the default is unlimited.\n\n__Cargo objects__\n\nThe cargo object returned by this function has the following properties and\nmethods:\n\n* length() - a function returning the number of items waiting to be processed.\n* payload - an integer for determining how many tasks should be\n  process per round. This property can be changed after a cargo is created to\n  alter the payload on-the-fly.\n* push(task, [callback]) - add a new task to the queue, the callback is called\n  once the worker has finished processing the task.\n  instead of a single task, an array of tasks can be submitted. the respective callback is used for every task in the list.\n* saturated - a callback that is called when the queue length hits the concurrency and further tasks will be queued\n* empty - a callback that is called when the last item from the queue is given to a worker\n* drain - a callback that is called when the last item from the queue has returned from the worker\n\n__Example__\n\n```js\n// create a cargo object with payload 2\n\nvar cargo = async.cargo(function (tasks, callback) {\n    for(var i=0; i<tasks.length; i++){\n      console.log('hello ' + tasks[i].name);\n    }\n    callback();\n}, 2);\n\n\n// add some items\n\ncargo.push({name: 'foo'}, function (err) {\n    console.log('finished processing foo');\n});\ncargo.push({name: 'bar'}, function (err) {\n    console.log('finished processing bar');\n});\ncargo.push({name: 'baz'}, function (err) {\n    console.log('finished processing baz');\n});\n```\n\n---------------------------------------\n\n<a name=\"auto\" />\n### auto(tasks, [callback])\n\nDetermines the best order for running functions based on their requirements.\nEach function can optionally depend on other functions being completed first,\nand each function is run as soon as its requirements are satisfied. If any of\nthe functions pass an error to their callback, that function will not complete\n(so any other functions depending on it will not run) and the main callback\nwill be called immediately with the error. Functions also receive an object\ncontaining the results of functions which have completed so far.\n\nNote, all functions are called with a results object as a second argument, \nso it is unsafe to pass functions in the tasks object which cannot handle the\nextra argument. For example, this snippet of code:\n\n```js\nasync.auto({\n  readData: async.apply(fs.readFile, 'data.txt', 'utf-8')\n}, callback);\n```\n\nwill have the effect of calling readFile with the results object as the last\nargument, which will fail:\n\n```js\nfs.readFile('data.txt', 'utf-8', cb, {});\n```\n\nInstead, wrap the call to readFile in a function which does not forward the \nresults object:\n\n```js\nasync.auto({\n  readData: function(cb, results){\n    fs.readFile('data.txt', 'utf-8', cb);\n  }\n}, callback);\n```\n\n__Arguments__\n\n* tasks - An object literal containing named functions or an array of\n  requirements, with the function itself the last item in the array. The key\n  used for each function or array is used when specifying requirements. The \n  function receives two arguments: (1) a callback(err, result) which must be \n  called when finished, passing an error (which can be null) and the result of \n  the function's execution, and (2) a results object, containing the results of\n  the previously executed functions.\n* callback(err, results) - An optional callback which is called when all the\n  tasks have been completed. The callback will receive an error as an argument\n  if any tasks pass an error to their callback. Results will always be passed\n\tbut if an error occurred, no other tasks will be performed, and the results\n\tobject will only contain partial results.\n  \n\n__Example__\n\n```js\nasync.auto({\n    get_data: function(callback){\n        // async code to get some data\n    },\n    make_folder: function(callback){\n        // async code to create a directory to store a file in\n        // this is run at the same time as getting the data\n    },\n    write_file: ['get_data', 'make_folder', function(callback){\n        // once there is some data and the directory exists,\n        // write the data to a file in the directory\n        callback(null, filename);\n    }],\n    email_link: ['write_file', function(callback, results){\n        // once the file is written let's email a link to it...\n        // results.write_file contains the filename returned by write_file.\n    }]\n});\n```\n\nThis is a fairly trivial example, but to do this using the basic parallel and\nseries functions would look like this:\n\n```js\nasync.parallel([\n    function(callback){\n        // async code to get some data\n    },\n    function(callback){\n        // async code to create a directory to store a file in\n        // this is run at the same time as getting the data\n    }\n],\nfunction(err, results){\n    async.series([\n        function(callback){\n            // once there is some data and the directory exists,\n            // write the data to a file in the directory\n        },\n        function(callback){\n            // once the file is written let's email a link to it...\n        }\n    ]);\n});\n```\n\nFor a complicated series of async tasks using the auto function makes adding\nnew tasks much easier and makes the code more readable.\n\n\n---------------------------------------\n\n<a name=\"iterator\" />\n### iterator(tasks)\n\nCreates an iterator function which calls the next function in the array,\nreturning a continuation to call the next one after that. It's also possible to\n'peek' the next iterator by doing iterator.next().\n\nThis function is used internally by the async module but can be useful when\nyou want to manually control the flow of functions in series.\n\n__Arguments__\n\n* tasks - An array of functions to run.\n\n__Example__\n\n```js\nvar iterator = async.iterator([\n    function(){ sys.p('one'); },\n    function(){ sys.p('two'); },\n    function(){ sys.p('three'); }\n]);\n\nnode> var iterator2 = iterator();\n'one'\nnode> var iterator3 = iterator2();\n'two'\nnode> iterator3();\n'three'\nnode> var nextfn = iterator2.next();\nnode> nextfn();\n'three'\n```\n\n---------------------------------------\n\n<a name=\"apply\" />\n### apply(function, arguments..)\n\nCreates a continuation function with some arguments already applied, a useful\nshorthand when combined with other control flow functions. Any arguments\npassed to the returned function are added to the arguments originally passed\nto apply.\n\n__Arguments__\n\n* function - The function you want to eventually apply all arguments to.\n* arguments... - Any number of arguments to automatically apply when the\n  continuation is called.\n\n__Example__\n\n```js\n// using apply\n\nasync.parallel([\n    async.apply(fs.writeFile, 'testfile1', 'test1'),\n    async.apply(fs.writeFile, 'testfile2', 'test2'),\n]);\n\n\n// the same process without using apply\n\nasync.parallel([\n    function(callback){\n        fs.writeFile('testfile1', 'test1', callback);\n    },\n    function(callback){\n        fs.writeFile('testfile2', 'test2', callback);\n    }\n]);\n```\n\nIt's possible to pass any number of additional arguments when calling the\ncontinuation:\n\n```js\nnode> var fn = async.apply(sys.puts, 'one');\nnode> fn('two', 'three');\none\ntwo\nthree\n```\n\n---------------------------------------\n\n<a name=\"nextTick\" />\n### nextTick(callback)\n\nCalls the callback on a later loop around the event loop. In node.js this just\ncalls process.nextTick, in the browser it falls back to setImmediate(callback)\nif available, otherwise setTimeout(callback, 0), which means other higher priority\nevents may precede the execution of the callback.\n\nThis is used internally for browser-compatibility purposes.\n\n__Arguments__\n\n* callback - The function to call on a later loop around the event loop.\n\n__Example__\n\n```js\nvar call_order = [];\nasync.nextTick(function(){\n    call_order.push('two');\n    // call_order now equals ['one','two']\n});\ncall_order.push('one')\n```\n\n<a name=\"times\" />\n### times(n, callback)\n\nCalls the callback n times and accumulates results in the same manner\nyou would use with async.map.\n\n__Arguments__\n\n* n - The number of times to run the function.\n* callback - The function to call n times.\n\n__Example__\n\n```js\n// Pretend this is some complicated async factory\nvar createUser = function(id, callback) {\n  callback(null, {\n    id: 'user' + id\n  })\n}\n// generate 5 users\nasync.times(5, function(n, next){\n    createUser(n, function(err, user) {\n      next(err, user)\n    })\n}, function(err, users) {\n  // we should now have 5 users\n});\n```\n\n<a name=\"timesSeries\" />\n### timesSeries(n, callback)\n\nThe same as times only the iterator is applied to each item in the array in\nseries. The next iterator is only called once the current one has completed\nprocessing. The results array will be in the same order as the original.\n\n\n## Utils\n\n<a name=\"memoize\" />\n### memoize(fn, [hasher])\n\nCaches the results of an async function. When creating a hash to store function\nresults against, the callback is omitted from the hash and an optional hash\nfunction can be used.\n\nThe cache of results is exposed as the `memo` property of the function returned\nby `memoize`.\n\n__Arguments__\n\n* fn - the function you to proxy and cache results from.\n* hasher - an optional function for generating a custom hash for storing\n  results, it has all the arguments applied to it apart from the callback, and\n  must be synchronous.\n\n__Example__\n\n```js\nvar slow_fn = function (name, callback) {\n    // do something\n    callback(null, result);\n};\nvar fn = async.memoize(slow_fn);\n\n// fn can now be used as if it were slow_fn\nfn('some name', function () {\n    // callback\n});\n```\n\n<a name=\"unmemoize\" />\n### unmemoize(fn)\n\nUndoes a memoized function, reverting it to the original, unmemoized\nform. Comes handy in tests.\n\n__Arguments__\n\n* fn - the memoized function\n\n<a name=\"log\" />\n### log(function, arguments)\n\nLogs the result of an async function to the console. Only works in node.js or\nin browsers that support console.log and console.error (such as FF and Chrome).\nIf multiple arguments are returned from the async function, console.log is\ncalled on each argument in order.\n\n__Arguments__\n\n* function - The function you want to eventually apply all arguments to.\n* arguments... - Any number of arguments to apply to the function.\n\n__Example__\n\n```js\nvar hello = function(name, callback){\n    setTimeout(function(){\n        callback(null, 'hello ' + name);\n    }, 1000);\n};\n```\n```js\nnode> async.log(hello, 'world');\n'hello world'\n```\n\n---------------------------------------\n\n<a name=\"dir\" />\n### dir(function, arguments)\n\nLogs the result of an async function to the console using console.dir to\ndisplay the properties of the resulting object. Only works in node.js or\nin browsers that support console.dir and console.error (such as FF and Chrome).\nIf multiple arguments are returned from the async function, console.dir is\ncalled on each argument in order.\n\n__Arguments__\n\n* function - The function you want to eventually apply all arguments to.\n* arguments... - Any number of arguments to apply to the function.\n\n__Example__\n\n```js\nvar hello = function(name, callback){\n    setTimeout(function(){\n        callback(null, {hello: name});\n    }, 1000);\n};\n```\n```js\nnode> async.dir(hello, 'world');\n{hello: 'world'}\n```\n\n---------------------------------------\n\n<a name=\"noConflict\" />\n### noConflict()\n\nChanges the value of async back to its original value, returning a reference to the\nasync object.\n",
+  "readmeFilename": "README.md",
+  "homepage": "https://github.com/caolan/async",
+  "_id": "async@0.2.10",
+  "_shasum": "b6bbe0b0674b9d719708ca38de8c237cb526c3d1",
+  "_from": "async@~0.2.9",
+  "_resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz"
+}
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/package.json b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/package.json
new file mode 100644
index 0000000..473485c
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/adbkit-monkey/package.json
@@ -0,0 +1,79 @@
+{
+  "name": "adbkit-monkey",
+  "version": "1.0.1",
+  "description": "A Node.js interface to the Android monkey tool.",
+  "keywords": [
+    "adb",
+    "adbkit",
+    "monkey",
+    "monkeyrunner"
+  ],
+  "bugs": {
+    "url": "https://github.com/CyberAgent/adbkit-monkey/issues"
+  },
+  "license": "Apache-2.0",
+  "author": {
+    "name": "CyberAgent, Inc.",
+    "email": "npm@cyberagent.co.jp",
+    "url": "http://www.cyberagent.co.jp/"
+  },
+  "main": "./index",
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/CyberAgent/adbkit-monkey.git"
+  },
+  "scripts": {
+    "postpublish": "grunt clean",
+    "prepublish": "grunt coffee",
+    "test": "grunt test"
+  },
+  "dependencies": {
+    "async": "~0.2.9"
+  },
+  "devDependencies": {
+    "chai": "~1.8.1",
+    "coffee-script": "~1.6.3",
+    "grunt": "~0.4.1",
+    "grunt-cli": "~0.1.11",
+    "grunt-coffeelint": "~0.0.7",
+    "grunt-contrib-clean": "~0.5.0",
+    "grunt-contrib-coffee": "~0.7.0",
+    "grunt-contrib-watch": "~0.5.3",
+    "grunt-exec": "~0.4.2",
+    "grunt-jsonlint": "~1.0.2",
+    "grunt-notify": "~0.2.16",
+    "mocha": "~1.14.0",
+    "sinon": "~1.7.3",
+    "sinon-chai": "~2.4.0"
+  },
+  "engines": {
+    "node": ">= 0.10.4"
+  },
+  "readme": "# adbkit-monkey\n\n**adbkit-monkey** provides a [Node.js][nodejs] interface for working with the Android [`monkey` tool][monkey-site]. Albeit undocumented, they monkey program can be started in TCP mode with the `--port` argument. In this mode, it accepts a [range of commands][monkey-proto] that can be used to interact with the UI in a non-random manner. This mode is also used internally by the [`monkeyrunner` tool][monkeyrunner-site], although the documentation claims no relation to the monkey tool.\n\n## Getting started\n\nInstall via NPM:\n\n```bash\nnpm install --save adbkit-monkey\n```\n\nNote that while adbkit-monkey is written in CoffeeScript, it is compiled to JavaScript before publishing to NPM, which means that you are not required to use CoffeeScript.\n\n### Examples\n\nThe following examples assume that monkey is already running (via `adb shell monkey --port 1080`) and a port forwarding (`adb forward tcp:1080 tcp:1080`) has been set up.\n\n#### Press the home button\n\n```javascript\nvar assert = require('assert');\nvar monkey = require('adbkit-monkey');\n\nvar client = monkey.connect({ port: 1080 });\n\nclient.press(3 /* KEYCODE_HOME */, function(err) {\n  assert.ifError(err);\n  console.log('Pressed home button');\n  client.end();\n});\n```\n\n#### Drag out the notification bar\n\n```javascript\nvar assert = require('assert');\nvar monkey = require('adbkit-monkey');\n\nvar client = monkey.connect({ port: 1080 });\n\nclient.multi()\n  .touchDown(100, 0)\n  .sleep(5)\n  .touchMove(100, 20)\n  .sleep(5)\n  .touchMove(100, 40)\n  .sleep(5)\n  .touchMove(100, 60)\n  .sleep(5)\n  .touchMove(100, 80)\n  .sleep(5)\n  .touchMove(100, 100)\n  .sleep(5)\n  .touchUp(100, 100)\n  .sleep(5)\n  .execute(function(err) {\n    assert.ifError(err);\n    console.log('Dragged out the notification bar');\n    client.end();\n  });\n```\n\n#### Get display size\n\n```javascript\nvar assert = require('assert');\nvar monkey = require('adbkit-monkey');\n\nvar client = monkey.connect({ port: 1080 });\n\nclient.getDisplayWidth(function(err, width) {\n  assert.ifError(err);\n  client.getDisplayHeight(function(err, height) {\n    assert.ifError(err);\n    console.log('Display size is %dx%d', width, height);\n    client.end();\n  });\n});\n```\n\n#### Type text\n\nNote that you should manually focus a text field first.\n\n```javascript\nvar assert = require('assert');\nvar monkey = require('adbkit-monkey');\n\nvar client = monkey.connect({ port: 1080 });\n\nclient.type('hello monkey!', function(err) {\n  assert.ifError(err);\n  console.log('Said hello to monkey');\n  client.end();\n});\n```\n\n## API\n\n### Monkey\n\n#### monkey.connect(options)\n\nUses [Net.connect()][node-net] to open a new TCP connection to monkey. Useful when combined with `adb forward`.\n\n* **options** Any options [`Net.connect()`][node-net] accepts.\n* Returns: A new monkey `Client` instance.\n\n#### monkey.connectStream(stream)\n\nAttaches a monkey client to an existing monkey protocol stream.\n\n* **stream** The monkey protocol [`Stream`][node-stream].\n* Returns: A new monkey `Client` instance.\n\n### Client\n\nImplements `Api`. See below for details.\n\n#### Events\n\nThe following events are available:\n\n* **error** **(err)** Emitted when an error occurs.\n    * **err** An `Error`.\n* **end** Emitted when the stream ends.\n* **finish** Emitted when the stream finishes.\n\n#### client.end()\n\nEnds the underlying stream/connection.\n\n* Returns: The `Client` instance.\n\n#### client.multi()\n\nReturns a new API wrapper that buffers commands for simultaneous delivery instead of sending them individually. When used with `api.sleep()`, allows simple gestures to be executed.\n\n* Returns: A new `Multi` instance. See `Multi` below.\n\n#### client.send(command, callback)\n\nSends a raw protocol command to monkey.\n\n* **command** The command to send. When `String`, a single command is sent. When `Array`, a series of commands is sent at once.\n* **callback(err, value, command)** Called when monkey responds to the command. If multiple commands were sent, the callback will be called once for each command.\n    * **err** `null` when successful, `Error` otherwise.\n    * **value** The response value, if any.\n    * **command** The command the response is for.\n* Returns: The `Client` instance.\n\n### Api\n\nThe monkey API implemented by `Client` and `Multi`.\n\n#### api.done(callback)\n\nCloses the current monkey session and allows a new session to connect.\n\n* **callback(err)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n* Returns: The `Api` implementation instance.\n\n#### api.flipClose(callback)\n\nSimulates closing the keyboard.\n\n* **callback(err)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n* Returns: The `Api` implementation instance.\n\n#### api.flipOpen(callback)\n\nSimulates opening the keyboard.\n\n* **callback(err)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n* Returns: The `Api` implementation instance.\n\n#### api.get(name, callback)\n\nGets the value of a variable. Use `api.list()` to retrieve a list of supported variables.\n\n* **name** The name of the variable.\n* **callback(err, value)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n    * **value** The value of the variable.\n* Returns: The `Api` implementation instance.\n\n#### api.getAmCurrentAction(callback)\n\nAlias for `api.get('am.current.action', callback)`.\n\n#### api.getAmCurrentCategories(callback)\n\nAlias for `api.get('am.current.categories', callback)`.\n\n#### api.getAmCurrentCompClass(callback)\n\nAlias for `api.get('am.current.comp.class', callback)`.\n\n#### api.getAmCurrentCompPackage(callback)\n\nAlias for `api.get('am.current.comp.package', callback)`.\n\n#### api.getCurrentData(callback)\n\nAlias for `api.get('am.current.data', callback)`.\n\n#### api.getAmCurrentPackage(callback)\n\nAlias for `api.get('am.current.package', callback)`.\n\n#### api.getBuildBoard(callback)\n\nAlias for `api.get('build.board', callback)`.\n\n#### api.getBuildBrand(callback)\n\nAlias for `api.get('build.brand', callback)`.\n\n#### api.getBuildCpuAbi(callback)\n\nAlias for `api.get('build.cpu_abi', callback)`.\n\n#### api.getBuildDevice(callback)\n\nAlias for `api.get('build.device', callback)`.\n\n#### api.getBuildDisplay(callback)\n\nAlias for `api.get('build.display', callback)`.\n\n#### api.getBuildFingerprint(callback)\n\nAlias for `api.get('build.fingerprint', callback)`.\n\n#### api.getBuildHost(callback)\n\nAlias for `api.get('build.host', callback)`.\n\n#### api.getBuildId(callback)\n\nAlias for `api.get('build.id', callback)`.\n\n#### api.getBuildManufacturer(callback)\n\nAlias for `api.get('build.manufacturer', callback)`.\n\n#### api.getBuildModel(callback)\n\nAlias for `api.get('build.model', callback)`.\n\n#### api.getBuildProduct(callback)\n\nAlias for `api.get('build.product', callback)`.\n\n#### api.getBuildTags(callback)\n\nAlias for `api.get('build.tags', callback)`.\n\n#### api.getBuildType(callback)\n\nAlias for `api.get('build.type', callback)`.\n\n#### api.getBuildUser(callback)\n\nAlias for `api.get('build.user', callback)`.\n\n#### api.getBuildVersionCodename(callback)\n\nAlias for `api.get('build.version.codename', callback)`.\n\n#### api.getBuildVersionIncremental(callback)\n\nAlias for `api.get('build.version.incremental', callback)`.\n\n#### api.getBuildVersionRelease(callback)\n\nAlias for `api.get('build.version.release', callback)`.\n\n#### api.getBuildVersionSdk(callback)\n\nAlias for `api.get('build.version.sdk', callback)`.\n\n#### api.getClockMillis(callback)\n\nAlias for `api.get('clock.millis', callback)`.\n\n#### api.getClockRealtime(callback)\n\nAlias for `api.get('clock.realtime', callback)`.\n\n#### api.getClockUptime(callback)\n\nAlias for `api.get('clock.uptime', callback)`.\n\n#### api.getDisplayDensity(callback)\n\nAlias for `api.get('display.density', callback)`.\n\n#### api.getDisplayHeight(callback)\n\nAlias for `api.get('display.height', callback)`. Note that the height may exclude any virtual home button row.\n\n#### api.getDisplayWidth(callback)\n\nAlias for `api.get('display.width', callback)`.\n\n#### api.keyDown(keyCode, callback)\n\nSends a key down event. Should be coupled with `api.keyUp()`. Note that `api.press()` performs the two events automatically.\n\n* **keyCode** The [key code][android-keycodes]. All monkeys support numeric keycodes, and some support automatic conversion from key names to key codes (e.g. `'home'` to `KEYCODE_HOME`). This will not work for number keys however. The most portable method is to simply use numeric key codes.\n* **callback(err)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n* Returns: The `Api` implementation instance.\n\n#### api.keyUp(keyCode, callback)\n\nSends a key up event. Should be coupled with `api.keyDown()`. Note that `api.press()` performs the two events automatically.\n\n* **keyCode** See `api.keyDown()`.\n* **callback(err)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n* Returns: The `Api` implementation instance.\n\n#### api.list(callback)\n\nLists supported variables.\n\n* **callback(err, vars)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n    * **vars** An array of supported variable names, to be used with `api.get()`.\n* Returns: The `Api` implementation instance.\n\n#### api.press(keyCode, callback)\n\nSends a key press event.\n\n* **keyCode** See `api.keyDown()`.\n* **callback(err)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n* Returns: The `Api` implementation instance.\n\n#### api.quit(callback)\n\nCloses the current monkey session and quits monkey.\n\n* **callback(err)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n* Returns: The `Api` implementation instance.\n\n#### api.sleep(ms, callback)\n\nSleeps for the given duration. Can be useful for simulating gestures.\n\n* **ms** How many milliseconds to sleep for.\n* **callback(err)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n* Returns: The `Api` implementation instance.\n\n#### api.tap(x, y, callback)\n\nTaps the given coordinates.\n\n* **x** The x coordinate.\n* **y** The y coordinate.\n* **callback(err)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n* Returns: The `Api` implementation instance.\n\n#### api.touchDown(x, y, callback)\n\nSends a touch down event on the given coordinates.\n\n* **x** The x coordinate.\n* **y** The y coordinate.\n* **callback(err)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n* Returns: The `Api` implementation instance.\n\n#### api.touchMove(x, y, callback)\n\nSends a touch move event on the given coordinates.\n\n* **x** The x coordinate.\n* **y** The y coordinate.\n* **callback(err)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n* Returns: The `Api` implementation instance.\n\n#### api.touchUp(x, y, callback)\n\nSends a touch up event on the given coordinates.\n\n* **x** The x coordinate.\n* **y** The y coordinate.\n* **callback(err)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n* Returns: The `Api` implementation instance.\n\n#### api.trackball(x, y, callback)\n\nSends a trackball event on the given coordinates.\n\n* **x** The x coordinate.\n* **y** The y coordinate.\n* **callback(err)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n* Returns: The `Api` implementation instance.\n\n#### api.type(text, callback)\n\nTypes the given text.\n\n* **text** A text `String`. Note that only characters for which [key codes][android-keycodes] exist can be entered. Also note that any IME in use may or may not transform the text.\n* **callback(err)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n* Returns: The `Api` implementation instance.\n\n#### api.wake(callback)\n\nWakes the device from sleep and allows user input.\n\n* **callback(err)** Called when monkey responds.\n    * **err** `null` when successful, `Error` otherwise.\n* Returns: The `Api` implementation instance.\n\n### Multi\n\nBuffers `Api` commands and delivers them simultaneously for greater control over timing.\n\nImplements all `Api` methods, but without the last `callback` parameter.\n\n#### multi.execute(callback)\n\nSends all buffered commands.\n\n* **callback(err, values)** Called when monkey has responded to all commands (i.e. just once at the end).\n    * **err** `null` when successful, `Error` otherwise.\n    * **values** An array of all response values, identical to individual `Api` responses.\n\n## More information\n\n* [Monkey][monkey-site]\n    - [Source code][monkey-source]\n    - [Protocol][monkey-proto]\n* [Monkeyrunner][monkeyrunner-site]\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md).\n\n## License\n\nSee [LICENSE](LICENSE).\n\nCopyright © CyberAgent, Inc. All Rights Reserved.\n\n[nodejs]: <http://nodejs.org/>\n[monkey-site]: <http://developer.android.com/tools/help/monkey.html>\n[monkey-source]: <https://github.com/android/platform_development/blob/master/cmds/monkey/>\n[monkey-proto]: <https://github.com/android/platform_development/blob/master/cmds/monkey/README.NETWORK.txt>\n[monkeyrunner-site]: <http://developer.android.com/tools/help/monkeyrunner_concepts.html>\n[node-net]: <http://nodejs.org/api/net.html>\n[node-stream]: <http://nodejs.org/api/stream.html>\n[android-keycodes]: <http://developer.android.com/reference/android/view/KeyEvent.html>\n",
+  "readmeFilename": "README.md",
+  "homepage": "https://github.com/CyberAgent/adbkit-monkey",
+  "_id": "adbkit-monkey@1.0.1",
+  "dist": {
+    "shasum": "f291be701a2efc567a63fc7aa6afcded31430be1",
+    "tarball": "http://registry.npmjs.org/adbkit-monkey/-/adbkit-monkey-1.0.1.tgz"
+  },
+  "_from": "adbkit-monkey@~1.0.1",
+  "_npmVersion": "1.3.14",
+  "_npmUser": {
+    "name": "sorccu",
+    "email": "simo@shoqolate.com"
+  },
+  "maintainers": [
+    {
+      "name": "sorccu",
+      "email": "simo@shoqolate.com"
+    },
+    {
+      "name": "cyberagent",
+      "email": "npm@cyberagent.co.jp"
+    }
+  ],
+  "directories": {},
+  "_shasum": "f291be701a2efc567a63fc7aa6afcded31430be1",
+  "_resolved": "https://registry.npmjs.org/adbkit-monkey/-/adbkit-monkey-1.0.1.tgz"
+}
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/.npmignore b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/.npmignore
new file mode 100644
index 0000000..36ee3f7
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/.npmignore
@@ -0,0 +1,30 @@
+node_modules/*
+todo.txt
+npm-debug.log
+test/*
+benchmark/*
+browser/*
+src/*
+async
+sync
+mixed
+bench.json
+js/browser
+js/browser/*
+js/debug
+js/debug/*
+reader.js
+read.txt
+bench
+.editorconfig
+.jshintrc
+ast_passes.js
+mocharun.js
+throwaway.js
+throwaway.html
+bluebird.sublime-workspace
+bluebird.sublime-project
+changelog.js
+.travis.yml
+sauce_connect.log
+bump.js
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/API.md b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/API.md
new file mode 100644
index 0000000..7e940c2
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/API.md
@@ -0,0 +1,1741 @@
+#API Reference
+
+- [Core](#core)
+    - [`new Promise(Function<Function resolve, Function reject> resolver)`](#new-promisefunctionfunction-resolve-function-reject-resolver---promise)
+    - [`.then([Function fulfilledHandler] [, Function rejectedHandler ] [, Function progressHandler ])`](#thenfunction-fulfilledhandler--function-rejectedhandler---function-progresshandler----promise)
+    - [`.catch(Function handler)`](#catchfunction-handler---promise)
+    - [`.catch([Function ErrorClass|Function predicate...], Function handler)`](#catchfunction-errorclassfunction-predicate-function-handler---promise)
+    - [`.error( [rejectedHandler] )`](#error-rejectedhandler----promise)
+    - [`.finally(Function handler)`](#finallyfunction-handler---promise)
+    - [`.tap(Function handler)`](#tapfunction-handler---promise)
+    - [`.bind(dynamic thisArg)`](#binddynamic-thisarg---promise)
+    - [`.done([Function fulfilledHandler] [, Function rejectedHandler ] [, Function progressHandler ])`](#donefunction-fulfilledhandler--function-rejectedhandler---function-progresshandler----promise)
+    - [`Promise.try(Function fn [, Array<dynamic>|dynamic arguments] [, dynamic ctx] )`](#promisetryfunction-fn--arraydynamicdynamic-arguments--dynamic-ctx----promise)
+    - [`Promise.method(Function fn)`](#promisemethodfunction-fn---function)
+    - [`Promise.resolve(dynamic value)`](#promiseresolvedynamic-value---promise)
+    - [`Promise.reject(dynamic reason)`](#promiserejectdynamic-reason---promise)
+    - [`Promise.defer()`](#promisedefer---promiseresolver)
+    - [`Promise.cast(dynamic value)`](#promisecastdynamic-value---promise)
+    - [`Promise.bind(dynamic thisArg)`](#promisebinddynamic-thisarg---promise)
+    - [`Promise.is(dynamic value)`](#promiseisdynamic-value---boolean)
+    - [`Promise.longStackTraces()`](#promiselongstacktraces---void)
+- [Progression](#progression)
+    - [`.progressed(Function handler)`](#progressedfunction-handler---promise)
+- [Promise resolution](#promise-resolution)
+    - [`.resolve(dynamic value)`](#resolvedynamic-value---undefined)
+    - [`.reject(dynamic reason)`](#rejectdynamic-reason---undefined)
+    - [`.progress(dynamic value)`](#progressdynamic-value---undefined)
+    - [`.callback`](#callback---function)
+- [Timers](#timers)
+    - [`.delay(int ms)`](#delayint-ms---promise)
+    - [`.timeout(int ms [, String message])`](#timeoutint-ms--string-message---promise)
+    - [`Promise.delay([dynamic value], int ms)`](#promisedelaydynamic-value-int-ms---promise)
+- [Promisification](#promisification)
+    - [`Promise.promisify(Function nodeFunction [, dynamic receiver])`](#promisepromisifyfunction-nodefunction--dynamic-receiver---function)
+    - [`Promise.promisify(Object target)`](#promisepromisifyobject-target---object)
+    - [`Promise.promisifyAll(Object target)`](#promisepromisifyallobject-target---object)
+    - [`.nodeify([Function callback])`](#nodeifyfunction-callback---promise)
+- [Cancellation](#cancellation)
+    - [`.cancellable()`](#cancellable---promise)
+    - [`.cancel()`](#cancel---promise)
+    - [`.fork([Function fulfilledHandler] [, Function rejectedHandler ] [, Function progressHandler ])`](#forkfunction-fulfilledhandler--function-rejectedhandler---function-progresshandler----promise)
+    - [`.uncancellable()`](#uncancellable---promise)
+    - [`.isCancellable()`](#iscancellable---boolean)
+- [Synchronous inspection](#synchronous-inspection)
+    - [`.isFulfilled()`](#isfulfilled---boolean)
+    - [`.isRejected()`](#isrejected---boolean)
+    - [`.isPending()`](#isdefer---boolean)
+    - [`.isResolved()`](#isresolved---boolean)
+    - [`.inspect()`](#inspect---promiseinspection)
+- [Generators](#generators)
+    - [`Promise.coroutine(GeneratorFunction generatorFunction)`](#promisecoroutinegeneratorfunction-generatorfunction---function)
+    - [`Promise.coroutine.addYieldHandler(function handler)`](#promisecoroutineaddyieldhandlerfunction-handler---void)
+- [Utility](#utility)
+    - [`.call(String propertyName [, dynamic arg...])`](#callstring-propertyname--dynamic-arg---promise)
+    - [`.get(String propertyName)`](#getstring-propertyname---promise)
+    - [`.return(dynamic value)`](#returndynamic-value---promise)
+    - [`.throw(dynamic reason)`](#throwdynamic-reason---promise)
+    - [`.toString()`](#tostring---string)
+    - [`.toJSON()`](#tojson---object)
+    - [`Promise.noConflict()`](#promisenoconflict---object)
+    - [`Promise.onPossiblyUnhandledRejection(Function handler)`](#promiseonpossiblyunhandledrejectionfunction-handler---undefined)
+- [Collections](#collections)
+    - [`.all()`](#all---promise)
+    - [`.props()`](#props---promise)
+    - [`.settle()`](#settle---promise)
+    - [`.any()`](#any---promise)
+    - [`.race()`](#race---promise)
+    - [`.some(int count)`](#someint-count---promise)
+    - [`.spread([Function fulfilledHandler] [, Function rejectedHandler ])`](#spreadfunction-fulfilledhandler--function-rejectedhandler----promise)
+    - [`.map(Function mapper)`](#mapfunction-mapper---promise)
+    - [`.reduce(Function reducer [, dynamic initialValue])`](#reducefunction-reducer--dynamic-initialvalue---promise)
+    - [`.filter(Function filterer)`](#filterfunction-filterer---promise)
+    - [`Promise.all(Array<dynamic>|Promise values)`](#promiseallarraydynamicpromise-values---promise)
+    - [`Promise.props(Object|Promise object)`](#promisepropsobjectpromise-object---promise)
+    - [`Promise.settle(Array<dynamic>|Promise values)`](#promisesettlearraydynamicpromise-values---promise)
+    - [`Promise.any(Array<dynamic>|Promise values)`](#promiseanyarraydynamicpromise-values---promise)
+    - [`Promise.race(Array|Promise promises)`](#promiseracearraypromise-promises---promise)
+    - [`Promise.some(Array<dynamic>|Promise values, int count)`](#promisesomearraydynamicpromise-values-int-count---promise)
+    - [`Promise.join([dynamic value...])`](#promisejoindynamic-value---promise)
+    - [`Promise.map(Array<dynamic>|Promise values, Function mapper)`](#promisemaparraydynamicpromise-values-function-mapper---promise)
+    - [`Promise.reduce(Array<dynamic>|Promise values, Function reducer [, dynamic initialValue])`](#promisereducearraydynamicpromise-values-function-reducer--dynamic-initialvalue---promise)
+    - [`Promise.filter(Array<dynamic>|Promise values, Function filterer)`](#promisefilterarraydynamicpromise-values-function-filterer---promise)
+
+##Core
+
+Core methods of `Promise` instances and core static methods of the Promise class.
+
+#####`new Promise(Function<Function resolve, Function reject> resolver)` -> `Promise`
+
+Create a new promise. The passed in function will receive functions `resolve` and `reject` as its arguments which can be called to seal the fate of the created promise.
+
+Example:
+
+```js
+function ajaxGetAsync(url) {
+    return new Promise(function (resolve, reject) {
+        var xhr = new XMLHttpRequest;
+        xhr.addEventListener("error", reject);
+        xhr.addEventListener("load", resolve);
+        xhr.open("GET", url);
+        xhr.send(null);
+    });
+}
+```
+
+If you pass a promise object to the `resolve` function, the created promise will follow the state of that promise.
+
+<hr>
+
+To make sure a function that returns a promise is following the implicit but critically important contract of promises, you can start a function with `new Promise` if you cannot start a chain immediately:
+
+```js
+function getConnection(urlString) {
+    return new Promise(function(resolve) {
+        //Without new Promise, this throwing will throw an actual exception
+        var params = parse(urlString);
+        resolve(getAdapater(params).getConnection());
+    });
+}
+```
+
+The above ensures `getConnection()` fulfills the contract of a promise-returning function of never throwing a synchronous exception. Also see [`Promise.try`](#promisetryfunction-fn--arraydynamicdynamic-arguments--dynamic-ctx----promise) and [`Promise.method`](#promisemethodfunction-fn---function)
+
+<hr>
+
+#####`.then([Function fulfilledHandler] [, Function rejectedHandler ] [, Function progressHandler ])` -> `Promise`
+
+[Promises/A+ `.then()`](http://promises-aplus.github.io/promises-spec/) with progress handler. Returns a new promise chained from this promise. The new promise will be rejected or resolved dedefer on the passed `fulfilledHandler`, `rejectedHandler` and the state of this promise.
+
+Example:
+
+```js
+promptAsync("Which url to visit?").then(function(url){
+    return ajaxGetAsync(url);
+}).then(function(contents){
+    alertAsync("The contents were: " + contents);
+}).catch(function(e){
+    alertAsync("Exception " + e);
+});
+```
+
+<hr>
+
+#####`.catch(Function handler)` -> `Promise`
+
+This is a catch-all exception handler, shortcut for calling `.then(null, handler)` on this promise. Any exception happening in a `.then`-chain will propagate to nearest `.catch` handler.
+
+*For compatibility with earlier ECMAScript version, an alias `.caught()` is provided for `.catch()`.*
+
+<hr>
+
+#####`.catch([Function ErrorClass|Function predicate...], Function handler)` -> `Promise`
+
+This extends `.catch` to work more like catch-clauses in languages like Java or C#. Instead of manually checking `instanceof` or `.name === "SomeError"`, you may specify a number of error constructors which are eligible for this catch handler. The catch handler that is first met that has eligible constructors specified, is the one that will be called.
+
+Example:
+
+```js
+somePromise.then(function(){
+    return a.b.c.d();
+}).catch(TypeError, function(e){
+    //If a is defined, will end up here because
+    //it is a type error to reference property of undefined
+}).catch(ReferenceError, function(e){
+    //Will end up here if a wasn't defined at all
+}).catch(function(e){
+    //Generic catch-the rest, error wasn't TypeError nor
+    //ReferenceError
+});
+ ```
+
+You may also add multiple filters for a catch handler:
+
+```js
+somePromise.then(function(){
+    return a.b.c.d();
+}).catch(TypeError, ReferenceError, function(e){
+    //Will end up here on programmer error
+}).catch(NetworkError, TimeoutError, function(e){
+    //Will end up here on expected everyday network errors
+}).catch(function(e){
+    //Catch any unexpected errors
+});
+```
+
+For a parameter to be considered a type of error that you want to filter, you need the constructor to have its `.prototype` property be `instanceof Error`.
+
+Such a constructor can be minimally created like so:
+
+```js
+function MyCustomError() {}
+MyCustomError.prototype = Object.create(Error.prototype);
+```
+
+Using it:
+
+```js
+Promise.resolve().then(function(){
+    throw new MyCustomError();
+}).catch(MyCustomError, function(e){
+    //will end up here now
+});
+```
+
+However if you  want stack traces and cleaner string output, then you should do:
+
+*in Node.js and other V8 environments, with support for `Error.captureStackTrace`*
+
+```js
+function MyCustomError(message) {
+    this.message = message;
+    this.name = "MyCustomError";
+    Error.captureStackTrace(this, MyCustomError);
+}
+MyCustomError.prototype = Object.create(Error.prototype);
+MyCustomError.prototype.constructor = MyCustomError;
+```
+
+Using CoffeeScript's `class` for the same:
+
+```coffee
+class MyCustomError extends Error
+  constructor: (@message) ->
+    @name = "MyCustomError"
+    Error.captureStackTrace(this, MyCustomError)
+```
+
+This method also supports predicate-based filters. If you pass a
+predicate function instead of an error constructor, the predicate will receive
+the error as an argument. The return result of the predicate will be used
+determine whether the error handler should be called.
+
+Predicates should allow for very fine grained control over caught errors:
+pattern matching, error-type sets with set operations and many other techniques
+can be implemented on top of them.
+
+Example of using a predicate-based filter:
+
+```js
+var Promise = require("bluebird");
+var request = Promise.promisify(require("request"));
+
+function clientError(e) {
+    return e.code >= 400 && e.code < 500;
+}
+
+request("http://www.google.com").then(function(contents){
+    console.log(contents);
+}).catch(clientError, function(e){
+   //A client error like 400 Bad Request happened
+});
+```
+
+*For compatibility with earlier ECMAScript version, an alias `.caught()` is provided for `.catch()`.*
+
+<hr>
+
+#####`.error( [rejectedHandler] )` -> `Promise`
+
+Like `.catch` but instead of catching all types of exceptions, it only catches those that don't originate from thrown errors but rather from explicit rejections.
+
+*Note, "errors" mean errors, as in objects that are `instanceof Error` - not strings, numbers and so on. See [a string is not an error](http://www.devthought.com/2011/12/22/a-string-is-not-an-error/).*
+
+For example, if a promisified function errbacks the node-style callback with an error, that could be caught with `.error()`. However if the node-style callback **throws** an error, only `.catch` would catch that.
+
+In the following example you might want to handle just the `SyntaxError` from JSON.parse and Filesystem errors from `fs` but let programmer errors bubble as unhandled rejections:
+
+```js
+var fs = Promise.promisifyAll(require("fs"));
+
+fs.readFileAsync("myfile.json").then(JSON.parse).then(function (json) {
+    console.log("Successful json")
+}).catch(SyntaxError, function (e) {
+    console.error("file contains invalid json");
+}).error(function (e) {
+    console.error("unable to read file, because: ", e.message);
+});
+```
+
+Now, because there is no catch-all handler, if you typed `console.lag` (causes an error you don't expect), you will see:
+
+```
+Possibly unhandled TypeError: Object #<Console> has no method 'lag'
+    at application.js:8:13
+From previous event:
+    at Object.<anonymous> (application.js:7:4)
+    at Module._compile (module.js:449:26)
+    at Object.Module._extensions..js (module.js:467:10)
+    at Module.load (module.js:349:32)
+    at Function.Module._load (module.js:305:12)
+    at Function.Module.runMain (module.js:490:10)
+    at startup (node.js:121:16)
+    at node.js:761:3
+```
+
+*( If you don't get the above - you need to enable [long stack traces](#promiselongstacktraces---void) )*
+
+And if the file contains invalid JSON:
+
+```
+file contains invalid json
+```
+
+And if the `fs` module causes an error like file not found:
+
+```
+unable to read file, because:  ENOENT, open 'not_there.txt'
+```
+
+<hr>
+
+#####`.finally(Function handler)` -> `Promise`
+
+Pass a handler that will be called regardless of this promise's fate. Returns a new promise chained from this promise. There are special semantics for `.finally()` in that the final value cannot be modified from the handler.
+
+Consider the example:
+
+```js
+function anyway() {
+    $("#ajax-loader-animation").hide();
+}
+
+function ajaxGetAsync(url) {
+    return new Promise(function (resolve, reject) {
+        var xhr = new XMLHttpRequest;
+        xhr.addEventListener("error", reject);
+        xhr.addEventListener("load", resolve);
+        xhr.open("GET", url);
+        xhr.send(null);
+    }).then(anyway, anyway);
+}
+```
+
+This example doesn't work as intended because the `then` handler actually swallows the exception and returns `undefined` for any further chainers.
+
+The situation can be fixed with `.finally`:
+
+```js
+function ajaxGetAsync(url) {
+    return new Promise(function (resolve, reject) {
+        var xhr = new XMLHttpRequest;
+        xhr.addEventListener("error", reject);
+        xhr.addEventListener("load", resolve);
+        xhr.open("GET", url);
+        xhr.send(null);
+    }).finally(function(){
+        $("#ajax-loader-animation").hide();
+    });
+}
+```
+
+Now the animation is hidden but an exception or the actual return value will automatically skip the finally and propagate to further chainers. This is more in line with the synchronous `finally` keyword.
+
+The `.finally` works like [Q's finally method](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback).
+
+*For compatibility with earlier ECMAScript version, an alias `.lastly()` is provided for `.finally()`.*
+
+<hr>
+
+#####`.tap(Function handler)` -> `Promise`
+
+Like `.finally` that is not called for rejections.
+
+```js
+getUser().tap(function(user) {
+    //Like in finally, if you return a promise from the handler
+    //the promise is awaited for before passing the original value through
+    return recordStatsAsync();
+}).then(function(user) {
+    //user is the user from getUser(), not recordStatsAsync()
+});
+```
+
+<hr>
+
+#####`.bind(dynamic thisArg)` -> `Promise`
+
+Create a promise that follows this promise, but is bound to the given `thisArg` value. A bound promise will call its handlers with the bound value set to `this`. Additionally promises derived from a bound promise will also be bound promises with the same `thisArg` binding as the original promise.
+
+<hr>
+
+Without arrow functions that provide lexical `this`, the correspondence between async and sync code breaks down when writing object-oriented code. `.bind()` alleviates this.
+
+Consider:
+
+```js
+MyClass.prototype.method = function() {
+    try {
+        var contents = fs.readFileSync(this.file);
+        var url = urlParse(contents);
+        var result = this.httpGetSync(url);
+        var refined = this.refine(result);
+        return this.writeRefinedSync(refined);
+    }
+    catch (e) {
+        this.error(e.stack);
+    }
+};
+```
+
+The above has a direct translation:
+
+```js
+MyClass.prototype.method = function() {
+    return fs.readFileAsync(this.file).bind(this)
+    .then(function(contents) {
+        var url = urlParse(contents);
+        return this.httpGetAsync(url);
+    }).then(function(result){
+        var refined = this.refine(result);
+        return this.writeRefinedAsync(refined);
+    }).catch(function(e){
+        this.error(e.stack);
+    });
+};
+```
+
+`.bind()` is the most efficient way of utilizing `this` with promises. The handler functions in the above code are not closures and can therefore even be hoisted out if needed. There is literally no overhead when propagating the bound value from one promise to another.
+
+<hr>
+
+`.bind()` also has a useful side purpose - promise handlers don't need to share a function to use shared state:
+
+```js
+somethingAsync().bind({})
+.then(function (aValue, bValue) {
+    this.aValue = aValue;
+    this.bValue = bValue;
+    return somethingElseAsync(aValue, bValue);
+})
+.then(function (cValue) {
+    return this.aValue + this.bValue + cValue;
+});
+```
+
+The above without `.bind()` could be achieved with:
+
+```js
+var scope = {};
+somethingAsync()
+.then(function (aValue, bValue) {
+    scope.aValue = aValue;
+    scope.bValue = bValue;
+    return somethingElseAsync(aValue, bValue);
+})
+.then(function (cValue) {
+    return scope.aValue + scope.bValue + cValue;
+});
+```
+
+However, there are many differences when you look closer:
+
+- Requires a statement so cannot be used in an expression context
+- If not there already, an additional wrapper function is required to avoid leaking or sharing `scope`
+- The handler functions are now closures, thus less efficient and not reusable
+
+<hr>
+
+Note that bind is only propagated with promise transformation. If you create new promise chains inside a handler, those chains are not bound to the "upper" `this`:
+
+```js
+something().bind(var1).then(function(){
+    //`this` is var1 here
+    return Promise.all(getStuff()).then(function(results){
+        //`this` is undefined here
+        //refine results here etc
+    });
+}).then(function(){
+    //`this` is var1 here
+});
+```
+
+However, if you are utilizing the full bluebird API offering, you will *almost never* need to resort to nesting promises in the first place. The above should be written more like:
+
+```js
+something().bind(var1).then(function() {
+    //`this` is var1 here
+    return getStuff();
+}).map(function(result){
+    //`this` is var1 here
+    //refine result here
+}).then(function(){
+    //`this` is var1 here
+});
+```
+
+Also see [this Stackoverflow answer](http://stackoverflow.com/a/19467053/995876) on a good example on how utilizing the collection instance methods like [`.map()`](#mapfunction-mapper---promise) can clean up code.
+
+<hr>
+
+If you don't want to return a bound promise to the consumers of a promise, you can rebind the chain at the end:
+
+```js
+MyClass.prototype.method = function() {
+    return fs.readFileAsync(this.file).bind(this)
+    .then(function(contents) {
+        var url = urlParse(contents);
+        return this.httpGetAsync(url);
+    }).then(function(result){
+        var refined = this.refine(result);
+        return this.writeRefinedAsync(refined);
+    }).catch(function(e){
+        this.error(e.stack);
+    }).bind(); //The `thisArg` is implicitly undefined - I.E. the default promise `this` value
+};
+```
+
+Rebinding can also be abused to do something gratuitous like this:
+
+```js
+Promise.resolve("my-element")
+    .bind(document)
+    .then(document.getElementById)
+    .bind(console)
+    .then(console.log);
+```
+
+The above does `console.log(document.getElementById("my-element"));`. The `.bind()`s are necessary because in browser neither of the methods can be called as a stand-alone function.
+
+<hr>
+
+#####`.done([Function fulfilledHandler] [, Function rejectedHandler ] [, Function progressHandler ])` -> `Promise`
+
+Like `.then()`, but any unhandled rejection that ends up here will be thrown as an error.
+
+<hr>
+
+#####`Promise.try(Function fn [, Array<dynamic>|dynamic arguments] [, dynamic ctx] )` -> `Promise`
+
+Start the chain of promises with `Promise.try`. Any synchronous exceptions will be turned into rejections on the returned promise.
+
+```js
+function getUserById(id) {
+    return Promise.try(function(){
+        if (typeof id !== "number") {
+            throw new Error("id must be a number");
+        }
+        return db.getUserById(id);
+    });
+}
+```
+
+Now if someone uses this function, they will catch all errors in their Promise `.catch` handlers instead of having to handle both synchronous and asynchronous exception flows.
+
+Note about second argument: if it's specifically a true array, its values become respective arguments for the function call. Otherwise it is passed as is as the first argument for the function call.
+
+*For compatibility with earlier ECMAScript version, an alias `Promise.attempt()` is provided for `Promise.try()`.*
+
+<hr>
+
+#####`Promise.method(Function fn)` -> `Function`
+
+Returns a new function that wraps the given function `fn`. The new function will always return a promise that is fulfilled with the original functions return values or rejected with thrown exceptions from the original function.
+
+This method is convenient when a function can sometimes return synchronously or throw synchronously.
+
+Example without using `Promise.method`:
+
+```js
+MyClass.prototype.method = function(input) {
+    if (!this.isValid(input)) {
+        return Promise.reject(new TypeError("input is not valid"));
+    }
+
+    if (this.cache(input)) {
+        return Promise.resolve(this.someCachedValue);
+    }
+
+    return db.queryAsync(input).bind(this).then(function(value) {
+        this.someCachedValue = value;
+        return value;
+    });
+};
+```
+
+Using the same function `Promise.method`, there is no need to manually wrap direct return or throw values into a promise:
+
+```js
+MyClass.prototype.method = Promise.method(function(input) {
+    if (!this.isValid(input)) {
+        throw new TypeError("input is not valid");
+    }
+
+    if (this.cachedFor(input)) {
+        return this.someCachedValue;
+    }
+
+    return db.queryAsync(input).bind(this).then(function(value) {
+        this.someCachedValue = value;
+        return value;
+    });
+});
+```
+
+<hr>
+
+#####`Promise.resolve(dynamic value)` -> `Promise`
+
+Create a promise that is resolved with the given `value`. If `value` is a thenable or promise, the returned promise will assume its state.
+
+<hr>
+
+#####`Promise.reject(dynamic reason)` -> `Promise`
+
+Create a promise that is rejected with the given `reason`.
+
+<hr>
+
+#####`Promise.defer()` -> `PromiseResolver`
+
+Create a promise with undecided fate and return a `PromiseResolver` to control it. See [Promise resolution](#promise-resolution).
+
+The use of `Promise.defer` is discouraged - it is much more awkward and error-prone than using `new Promise`.
+
+<hr>
+
+#####`Promise.cast(dynamic value)` -> `Promise`
+
+Cast the given `value` to a trusted promise. If `value` is already a trusted `Promise`, it is returned as is. If `value` is not a thenable, a fulfilled Promise is returned with `value` as its fulfillment value. If `value` is a thenable (Promise-like object, like those returned by jQuery's `$.ajax`), returns a trusted Promise that assimilates the state of the thenable.
+
+Example: (`$` is jQuery)
+
+```js
+Promise.cast($.get("http://www.google.com")).then(function(){
+    //Returning a thenable from a handler is automatically
+    //cast to a trusted Promise as per Promises/A+ specification
+    return $.post("http://www.yahoo.com");
+}).then(function(){
+
+}).catch(function(e){
+    //jQuery doesn't throw real errors so use catch-all
+    console.log(e.statusText);
+});
+```
+
+<hr>
+
+#####`Promise.bind(dynamic thisArg)` -> `Promise`
+
+Sugar for `Promise.resolve(undefined).bind(thisArg);`. See [`.bind()`](#binddynamic-thisarg---promise).
+
+<hr>
+
+#####`Promise.is(dynamic value)` -> `boolean`
+
+See if `value` is a trusted Promise.
+
+```js
+Promise.is($.get("http://www.google.com")); //false , thenable returned from $.get is not a `Promise` instance
+Promise.is(Promise.cast($.get("http://www.google.com"))) //true, `.cast` cast the thenable into a `Promise`
+```
+
+Trusted Promises are promises originating in the currently used copy of Bluebird. Promises originating in other libraries are called thenables and are _not_ trusted promises. This method is used for checking if a passed value is of the same type as `Promise` itself creates.
+
+<hr>
+
+#####`Promise.longStackTraces()` -> `void`
+
+Call this right after the library is loaded to enabled long stack traces. Long stack traces cannot be disabled after being enabled, and cannot be enabled after promises have alread been created. Long stack traces imply a substantial performance penalty, around 4-5x for throughput and 0.5x for latency.
+
+Long stack traces are enabled by default in the debug build.
+
+To enable them in all instances of bluebird in node.js, use the environment variable `BLUEBIRD_DEBUG`:
+
+```
+BLUEBIRD_DEBUG=1 node server.js
+```
+
+You should enabled long stack traces if you want better debugging experience. For example:
+
+```js
+Promise.longStackTraces();
+Promise.resolve().then(function outer() {
+    return Promise.resolve().then(function inner() {
+        return Promise.resolve().then(function evenMoreInner() {
+            a.b.c.d()
+        }).catch(function catcher(e){
+            console.error(e.stack);
+        });
+    });
+});
+```
+
+Gives
+
+    ReferenceError: a is not defined
+        at evenMoreInner (<anonymous>:6:13)
+    From previous event:
+        at inner (<anonymous>:5:24)
+    From previous event:
+        at outer (<anonymous>:4:20)
+    From previous event:
+        at <anonymous>:3:9
+        at Object.InjectedScript._evaluateOn (<anonymous>:581:39)
+        at Object.InjectedScript._evaluateAndWrap (<anonymous>:540:52)
+        at Object.InjectedScript.evaluate (<anonymous>:459:21)
+
+While with long stack traces disabled, you would get:
+
+    ReferenceError: a is not defined
+        at evenMoreInner (<anonymous>:6:13)
+        at tryCatch1 (<anonymous>:41:19)
+        at Promise$_resolvePromise [as _resolvePromise] (<anonymous>:1739:13)
+        at Promise$_resolveLast [as _resolveLast] (<anonymous>:1520:14)
+        at Async$_consumeFunctionBuffer [as _consumeFunctionBuffer] (<anonymous>:560:33)
+        at Async$consumeFunctionBuffer (<anonymous>:515:14)
+        at MutationObserver.Promise$_Deferred (<anonymous>:433:17)
+
+On client side, long stack traces currently only work in Firefox and Chrome.
+
+<hr>
+
+##Progression
+
+#####`.progressed(Function handler)` -> `Promise`
+
+Shorthand for `.then(null, null, handler);`. Attach a progress handler that will be called if this promise is progressed. Returns a new promise chained from this promise.
+
+<hr>
+
+##Promise resolution
+
+A `PromiseResolver` can be used to control the fate of a promise. It is like "Deferred" known in jQuery. The `PromiseResolver` objects have a `.promise` property which returns a reference to the controlled promise that can be passed to clients. `.promise` of a `PromiseResolver` is not a getter function to match other implementations.
+
+The methods of a `PromiseResolver` have no effect if the fate of the underlying promise is already decided (follow, reject, fulfill).
+
+The use of `Promise.defer` and deferred objects is discouraged - it is much more awkward and error-prone than using `new Promise`.
+
+<hr>
+
+#####`.resolve(dynamic value)` -> `undefined`
+
+Resolve the underlying promise with `value` as the resolution value. If `value` is a thenable or a promise, the underlying promise will assume its state.
+
+<hr>
+
+#####`.reject(dynamic reason)` -> `undefined`
+
+Reject the underlying promise with `reason` as the rejection reason.
+
+<hr>
+
+#####`.progress(dynamic value)` -> `undefined`
+
+Progress the underlying promise with `value` as the progression value.
+
+Example
+
+```js
+function delay(ms) {
+    var resolver = Promise.defer();
+    var now = Date.now();
+    setTimeout(function(){
+        resolver.resolve(Date.now() - now);
+    }, ms);
+    return resolver.promise;
+}
+
+delay(500).then(function(ms){
+    console.log(ms + " ms passed");
+});
+```
+
+<hr>
+
+#####`.callback` -> `Function`
+
+Gives you a callback representation of the `PromiseResolver`. Note that this is not a method but a property. The callback accepts error object in first argument and success values on the 2nd parameter and the rest, I.E. node js conventions.
+
+If the the callback is called with multiple success values, the resolver fullfills its promise with an array of the values.
+
+```js
+var fs = require("fs");
+function readAbc() {
+    var resolver = Promise.defer();
+    fs.readFile("abc.txt", resolver.callback);
+    return resolver.promise;
+}
+
+readAbc()
+.then(function(abcContents) {
+    console.log(abcContents);
+})
+.catch(function(e) {
+    console.error(e);
+});
+```
+
+This example is an alternative to automatic promisification of node functions.
+
+*Performance tips*
+
+The `callback` is actually an accessor property (except on legacy browsers where it's eager data property) - so save the result if you need to call it multiple times.
+
+This is more efficient way of promisification than using `new Promise`.
+
+<hr>
+
+##Timers
+
+Methods to delay and time promises out.
+
+#####`.delay(int ms)` -> `Promise`
+
+Same as calling [`Promise.delay(this, ms)`](#promisedelaydynamic-value-int-ms---promise). With the exception that if this promise is [bound](#binddynamic-thisarg---promise) to a value, the returned promise is bound to that value too.
+
+<hr>
+
+#####`.timeout(int ms [, String message])` -> `Promise`
+
+Returns a promise that will be fulfilled with this promise's fulfillment value or rejection reason. However, if this promise is not fulfilled or rejected within `ms` milliseconds, the returned promise is rejected with a `Promise.TimeoutError` instance.
+
+You may specify a custom error message with the `message` parameter.
+
+The example function `fetchContent` tries to fetch the contents of a web page with a 50ms timeout and sleeping 100ms between each retry. If there is no response after 5 retries, then the returned promise is rejected with a `ServerError` (made up error type). Additionally the whole process can be cancelled from outside at any point.
+
+```js
+function fetchContent(retries) {
+    if (!retries) retries = 0;
+    var jqXHR = $.get("http://www.slowpage.com");
+    //Cast the jQuery promise into a bluebird promise
+    return Promise.cast(jqXHR)
+        .cancellable()
+        .timeout(50)
+        .catch(Promise.TimeoutError, function() {
+            if (retries < 5) {
+                return Promise.delay(100).then(function(){
+                    return fetchContent(retries+1);
+                });
+            }
+            else {
+                throw new ServerError("not responding after 5 retries");
+            }
+        })
+        .catch(Promise.CancellationError, function(er) {
+            jqXHR.abort();
+            throw er; //Don't swallow it
+        });
+}
+```
+
+<hr>
+
+#####`Promise.delay([dynamic value], int ms)` -> `Promise`
+
+Returns a promise that will be fulfilled with `value` (or `undefined`) after given `ms` milliseconds. If `value` is a promise, the delay will start counting down when it is fulfilled and the returned promise will be fulfilled with the fulfillment value of the `value` promise.
+
+```js
+Promise.delay(500).then(function(){
+    console.log("500 ms passed");
+    return "Hello world";
+}).delay(500).then(function(helloWorldString) {
+    console.log(helloWorldString);
+    console.log("another 500 ms passed") ;
+});
+```
+
+<hr>
+
+##Promisification
+
+#####`Promise.promisify(Function nodeFunction [, dynamic receiver])` -> `Function`
+
+Returns a function that will wrap the given `nodeFunction`. Instead of taking a callback, the returned function will return a promise whose fate is decided by the callback behavior of the given node function. The node function should conform to node.js convention of accepting a callback as last argument and calling that callback with error as the first argument and success value on the second argument.
+
+If the `nodeFunction` calls its callback with multiple success values, the fulfillment value will be an array of them.
+
+If you pass a `receiver`, the `nodeFunction` will be called as a method on the `receiver`.
+
+Example of promisifying the asynchronous `readFile` of node.js `fs`-module:
+
+```js
+var readFile = Promise.promisify(require("fs").readFile);
+
+readFile("myfile.js", "utf8").then(function(contents){
+    return eval(contents);
+}).then(function(result){
+    console.log("The result of evaluating myfile.js", result);
+}).catch(SyntaxError, function(e){
+    console.log("File had syntax error", e);
+//Catch any other error
+}).catch(function(e){
+    console.log("Error reading file", e);
+});
+```
+
+Note that if the node function is a method of some object, you need to pass the object as the second argument like so:
+
+```js
+var redisGet = Promise.promisify(redisClient.get, redisClient);
+redisGet.then(function(){
+    //...
+});
+```
+
+**Tip**
+
+Use [`.spread`](#spreadfunction-fulfilledhandler--function-rejectedhandler----promise) with APIs that have multiple success values:
+
+```js
+var Promise = require("bluebird");
+var request = Promise.promisify(require('request'));
+request("http://www.google.com").spread(function(request, body) {
+    console.log(body);
+}).catch(function(err) {
+    console.error(err);
+});
+```
+
+The above uses [request](https://github.com/mikeal/request) library which has a callback signature of multiple success values.
+
+<hr>
+
+#####`Promise.promisify(Object target)` -> `Object`
+
+This overload has been **deprecated**. The overload will continue working for now. The recommended method for promisifying multiple methods at once is [`Promise.promisifyAll(Object target)`](#promisepromisifyallobject-target---object)
+
+<hr>
+
+#####`Promise.promisifyAll(Object target)` -> `Object`
+
+Promisifies the entire object by going through the object's properties and creating an async equivalent of each function on the object and its prototype chain. The promisified method name will be the original method name postfixed with `Async`. Returns the input object.
+
+Note that the original methods on the object are not overwritten but new methods are created with the `Async`-postfix. For example, if you `promisifyAll()` the node.js `fs` object use `fs.statAsync()` to call the promisified `stat` method.
+
+Example:
+
+```js
+Promise.promisifyAll(RedisClient.prototype);
+
+//Later on, all redis client instances have promise returning functions:
+
+redisClient.hexistsAsync("myhash", "field").then(function(v){
+
+}).catch(function(e){
+
+});
+```
+
+If you don't want to write on foreign prototypes, you can sub-class the target and promisify your subclass:
+
+```js
+function MyRedisClient() {
+    RedisClient.apply(this, arguments);
+}
+MyRedisClient.prototype = Object.create(RedisClient.prototype);
+MyRedisClient.prototype.constructor = MyRedisClient;
+Promise.promisify(MyRedisClient.prototype);
+```
+
+The promisified methods will be written on the `MyRedisClient.prototype` instead. This specific example doesn't actually work with `node_redis` because the `createClient` factory is hardcoded to instantiate `RedisClient` from closure.
+
+
+It also works on singletons or specific instances:
+
+```js
+var fs = Promise.promisifyAll(require("fs"));
+
+fs.readFileAsync("myfile.js", "utf8").then(function(contents){
+    console.log(contents);
+}).catch(function(e){
+    console.error(e.stack);
+});
+```
+
+The entire prototype chain of the object is promisified on the object. Only enumerable are considered. If the object already has a promisified version of the method, it will be skipped. The target methods are assumed to conform to node.js callback convention of accepting a callback as last argument and calling that callback with error as the first argument and success value on the second argument. If the node method calls its callback with multiple success values, the fulfillment value will be an array of them.
+
+If a method already has `"Async"` postfix, it will be duplicated. E.g. `getAsync`'s promisified name is `getAsyncAsync`.
+
+<hr>
+
+#####`.nodeify([Function callback])` -> `Promise`
+
+Register a node-style callback on this promise. When this promise is is either fulfilled or rejected, the node callback will be called back with the node.js convention where error reason is the first argument and success value is the second argument. The error argument will be `null` in case of success.
+
+Returns back this promise instead of creating a new one. If the `callback` argument is not a function, this method does not do anything.
+
+This can be used to create APIs that both accept node-style callbacks and return promises:
+
+```js
+function getDataFor(input, callback) {
+    return dataFromDataBase(input).nodeify(callback);
+}
+```
+
+The above function can then make everyone happy.
+
+Promises:
+
+```js
+getDataFor("me").then(function(dataForMe) {
+    console.log(dataForMe);
+});
+```
+
+Normal callbacks:
+
+```js
+getDataFor("me", function(err, dataForMe) {
+    if( err ) {
+        console.error( err );
+    }
+    console.log(dataForMe);
+});
+```
+
+There is no effect on peformance if the user doesn't actually pass a node-style callback function.
+
+<hr>
+
+##Cancellation
+
+By default, a promise is not cancellable. A promise can be marked as cancellable with `.cancellable()`. A cancellable promise can be cancelled if it's not resolved. Cancelling a promise propagates to the farthest cancellable ancestor of the target promise that is still pending, and rejects that promise with `CancellationError`. The rejection will then propagate back to the original promise and to its descendants. This roughly follows the semantics described [here](https://github.com/promises-aplus/cancellation-spec/issues/7).
+
+Promises marked with `.cancellable()` return cancellable promises automatically.
+
+If you are the resolver for a promise, you can react to a cancel in your promise by catching the `CancellationError`:
+
+```js
+function ajaxGetAsync(url) {
+    var xhr = new XMLHttpRequest;
+    return new Promise(function (resolve, reject) {
+        xhr.addEventListener("error", reject);
+        xhr.addEventListener("load", resolve);
+        xhr.open("GET", url);
+        xhr.send(null);
+    }).cancellable().catch(Promise.CancellationError, function(e) {
+        xhr.abort();
+        throw e; //Don't swallow it
+    });
+}
+```
+
+<hr>
+
+#####`.cancellable()` -> `Promise`
+
+Marks this promise as cancellable. Promises by default are not cancellable after v0.11 and must be marked as such for [`.cancel()`](#cancel---promise) to have any effect. Marking a promise as cancellable is infectious and you don't need to remark any descendant promise.
+
+If you have code written prior v0.11 using cancellation, add calls to `.cancellable()` at the starts of promise chains that need to support
+cancellation in themselves or somewhere in their descendants.
+
+<hr>
+
+#####`.cancel()` -> `Promise`
+
+Cancel this promise. The cancellation will propagate
+to farthest cancellable ancestor promise which is still pending.
+
+That ancestor will then be rejected with a `CancellationError` (get a reference from `Promise.CancellationError`)
+object as the rejection reason.
+
+In a promise rejection handler you may check for a cancellation
+by seeing if the reason object has `.name === "Cancel"`.
+
+Promises are by default not cancellable. Use [`.cancellable()`](#cancellable---promise) to mark a promise as cancellable.
+
+<hr>
+
+#####`.fork([Function fulfilledHandler] [, Function rejectedHandler ] [, Function progressHandler ])` -> `Promise`
+
+Like `.then()`, but cancellation of the the returned promise
+or any of its descendant will not propagate cancellation
+to this promise or this promise's ancestors.
+
+<hr>
+
+#####`.uncancellable()` -> `Promise`
+
+Create an uncancellable promise based on this promise.
+
+<hr>
+
+#####`.isCancellable()` -> `boolean`
+
+See if this promise can be cancelled.
+
+<hr>
+
+##Synchronous inspection
+
+Because `.then()` must give asynchronous guarantees, it cannot be used to inspect a given promise's state synchronously. The following code won't work:
+
+```js
+var wasFulfilled = false;
+var wasRejected = false;
+var resolutionValueOrRejectionReason = null;
+somePromise.then(function(v){
+    wasFulfilled = true;
+    resolutionValueOrRejectionReason = v
+}).catch(function(v){
+    wasRejected = true;
+    resolutionValueOrRejectionReason = v
+});
+//Using the variables won't work here because .then must be called asynchronously
+```
+
+Synchronous inspection API allows you to do this like so:
+
+```js
+var inspection = somePromise.inspect();
+
+if(inspection.isFulfilled()){
+    console.log("Was fulfilled with", inspection.value());
+}
+```
+
+<hr>
+
+#####`.isFulfilled()` -> `boolean`
+
+See if this `promise` has been fulfilled.
+
+<hr>
+
+#####`.isRejected()` -> `boolean`
+
+See if this `promise` has been rejected.
+
+<hr>
+
+#####`.isPending()` -> `boolean`
+
+See if this `promise` is still defer.
+
+<hr>
+
+#####`.isResolved()` -> `boolean`
+
+See if this `promise` is resolved -> either fulfilled or rejected.
+
+<hr>
+
+#####`.inspect()` -> `PromiseInspection`
+
+Synchronously inspect the state of this `promise`. The `PromiseInspection` will represent the state of the promise as snapshotted at the time of calling `.inspect()`. It will have the following methods:
+
+`.isFulfilled()` -> `boolean`
+
+See if the underlying promise was fulfilled at the creation time of this inspection object.
+
+`.isRejected()` -> `boolean`
+
+See if the underlying promise was rejected at the creation time of this inspection object.
+
+`.isPending()` -> `boolean`
+
+See if the underlying promise was defer at the creation time of this inspection object.
+
+`.value()` -> `dynamic`, throws `TypeError`
+
+Get the fulfillment value of the underlying promise. Throws if the promise wasn't fulfilled at the creation time of this inspection object.
+
+`.error()` -> `dynamic`, throws `TypeError`
+
+Get the rejection reason for the underlying promise. Throws if the promise wasn't rejected at the creation time of this inspection object.
+
+<hr>
+
+##Generators
+
+Using ECMAScript6 generators feature to implement C# 5.0 `async/await` like syntax.
+
+#####`Promise.coroutine(GeneratorFunction generatorFunction)` -> `Function`
+
+Returns a function that can use `yield` to run asynchronous code synchronously. This feature requires the support of generators which are drafted in the next version of the language. Node version greater than `0.11.2` is required and needs to be executed with the `--harmony-generators` (or `--harmony`) command-line switch.
+
+This is the recommended, simplest and most performant way of using asynchronous generators with bluebird. It is even faster than typical promise code because the creation of new anonymous function identities at runtime can be completely avoided without obfuscating your code.
+
+```js
+var Promise = require("bluebird");
+
+function delay(ms) {
+    return new Promise(function(f){
+        setTimeout(f, ms);
+    });
+}
+
+function PingPong() {
+
+}
+
+PingPong.prototype.ping = Promise.coroutine(function* (val) {
+    console.log("Ping?", val)
+    yield delay(500)
+    this.pong(val+1)
+});
+
+PingPong.prototype.pong = Promise.coroutine(function* (val) {
+    console.log("Pong!", val)
+    yield delay(500);
+    this.ping(val+1)
+});
+
+var a = new PingPong();
+a.ping(0);
+```
+
+Running the example with node version at least 0.11.2:
+
+    $ node --harmony test.js
+    Ping? 0
+    Pong! 1
+    Ping? 2
+    Pong! 3
+    Ping? 4
+    Pong! 5
+    Ping? 6
+    Pong! 7
+    Ping? 8
+    ...
+
+When called, the coroutine function will start an instance of the generator and returns a promise for its final value.
+
+Doing `Promise.coroutine(function*(){})` is almost like using the C# `async` keyword to mark the function, with `yield` working as the `await` keyword. Promises are somewhat like `Task`s.
+
+**Tip**
+
+If you yield an array then its elements are implicitly waited for. You may add your own custom special treatments with [`Promise.coroutine.addYieldHandler`](#promisecoroutineaddyieldhandlerfunction-handler---void)
+
+You can combine it with ES6 destructuring for some neat syntax:
+
+```js
+var getData = Promise.coroutine(function* (urlA, urlB) {
+    [resultA, resultB] = yield [http.getAsync(urlA), http.getAsync(urlB)];
+    //use resultA
+    //use resultB
+});
+```
+
+You might wonder why not just do this?
+
+```js
+var getData = Promise.coroutine(function* (urlA, urlB) {
+    var resultA = yield http.getAsync(urlA);
+    var resultB = yield http.getAsync(urlB);
+});
+```
+
+The problem with the above is that the requests are not done in parallel. It will completely wait for request A to complete before even starting request B. In the array syntax both requests fire off at the same time in parallel.
+
+<hr>
+
+#####`Promise.coroutine.addYieldHandler(function handler)` -> `void`
+
+By default you can only yield Promises, Thenables and Arrays inside coroutines. You can use this function to add yielding support for arbitrary types.
+
+For example, if you wanted `yield 500` to be same as `yield Promise.delay(500)`:
+
+```js
+Promise.coroutine.addYieldHandler(function(value) {
+     if (typeof value === "number") return Promise.delay(value);
+});
+```
+
+Yield handlers are called when you yield something that is not supported by default. The first yield handler to return a promise or a thenable will be used.
+If no yield handler returns a promise or a thenable then an error is raised.
+
+An example of implementing callback support with `addYieldHandler`:
+
+*This is a demonstration of how powerful the feature is and not the recommended usage. For best performance you need to use `promisifyAll` and yield promises directly.*
+
+```js
+var Promise = require("bluebird");
+var fs = require("fs");
+
+var _ = (function() {
+    var promise = null;
+    Promise.coroutine.addYieldHandler(function(v) {
+        if (v === void 0 && promise != null) {
+            return promise;
+        }
+        promise = null;
+    });
+    return function() {
+        var def = Promise.defer();
+        promise = def.promise;
+        return def.callback;
+    };
+})();
+
+
+var readFileJSON = Promise.coroutine(function* (fileName) {
+   var contents = yield fs.readFile(fileName, "utf8", _());
+   return JSON.parse(contents);
+});
+```
+
+An example of implementing thunks support with `addYieldHandler`:
+
+*This is a demonstration of how powerful the feature is and not the recommended usage. For best performance you need to use `promisifyAll` and yield promises directly.*
+
+```js
+var Promise = require("bluebird");
+var fs = require("fs");
+
+Promise.coroutine.addYieldHandler(function(v) {
+    if (typeof v === "function") {
+        var def = Promise.defer();
+        try { v(def.callback); } catch(e) { def.reject(e); }
+        return def.promise;
+    }
+});
+
+var readFileThunk = function(fileName, encoding) {
+    return function(cb) {
+        return fs.readFile(fileName, encoding, cb);
+    };
+};
+
+var readFileJSON = Promise.coroutine(function* (fileName) {
+   var contents = yield readFileThunk(fileName, "utf8");
+   return JSON.parse(contents);
+});
+```
+
+##Utility
+
+Functions that could potentially be handy in some situations.
+
+#####`.call(String propertyName [, dynamic arg...])` -> `Promise`
+
+This is a convenience method for doing:
+
+```js
+promise.then(function(obj){
+    return obj[propertyName].call(obj, arg...);
+});
+```
+
+<hr>
+
+#####`.get(String propertyName)` -> `Promise`
+
+This is a convenience method for doing:
+
+```js
+promise.then(function(obj){
+    return obj[propertyName];
+});
+```
+
+<hr>
+
+#####`.return(dynamic value)` -> `Promise`
+
+Convenience method for:
+
+```js
+.then(function() {
+   return value;
+});
+```
+
+in the case where `value` doesn't change its value.
+
+That means `value` is bound at the time of calling `.return()` so this will not work as expected:
+
+```js
+function getData() {
+    var data;
+
+    return query().then(function(result) {
+        data = result;
+    }).return(data);
+}
+```
+
+because `data` is `undefined` at the time `.return` is called.
+
+*For compatibility with earlier ECMAScript version, an alias `.thenReturn()` is provided for `.return()`.*
+
+<hr>
+
+#####`.throw(dynamic reason)` -> `Promise`
+
+Convenience method for:
+
+```js
+.then(function() {
+   throw reason;
+});
+```
+
+Same limitations apply as with `.return()`.
+
+*For compatibility with earlier ECMAScript version, an alias `.thenThrow()` is provided for `.throw()`.*
+
+<hr>
+
+#####`.toString()` -> `String`
+
+<hr>
+
+#####`.toJSON()` -> `Object`
+
+This is implicitly called by `JSON.stringify` when serializing the object. Returns a serialized representation of the `Promise`.
+
+<hr>
+
+#####`Promise.noConflict()` -> `Object`
+
+This is relevant to browser environments with no module loader.
+
+Release control of the `Promise` namespace to whatever it was before this library was loaded. Returns a reference to the library namespace so you can attach it to something else.
+
+```html
+<!-- the other promise library must be loaded first -->
+<script type="text/javascript" src="/scripts/other_promise.js"></script>
+<script type="text/javascript" src="/scripts/bluebird_debug.js"></script>
+<script type="text/javascript">
+//Release control right after
+var Bluebird = Promise.noConflict();
+
+//Cast a promise from some other Promise library using the Promise namespace to Bluebird:
+var promise = Bluebird.cast(new Promise());
+</script>
+```
+
+<hr>
+
+#####`Promise.onPossiblyUnhandledRejection(Function handler)` -> `undefined`
+
+Add `handler` as the handler to call when there is a possibly unhandled rejection. The default handler logs the error stack to stderr or `console.error` in browsers.
+
+```html
+Promise.onPossiblyUnhandledRejection(function(e, promise){
+    throw e;
+});
+```
+
+Passing no value or a non-function will have the effect of removing any kind of handling for possibly unhandled rejections.
+
+<hr>
+
+##Collections
+
+Methods of `Promise` instances and core static methods of the Promise class to deal with
+collections of promises or mixed promises and values.
+
+#####`.all()` -> `Promise`
+
+Same as calling [Promise.all\(thisPromise\)](#promiseallarraydynamicpromise-values---promise). With the exception that if this promise is [bound](#binddynamic-thisarg---promise) to a value, the returned promise is bound to that value too.
+
+<hr>
+
+#####`.props()` -> `Promise`
+
+Same as calling [Promise.props\(thisPromise\)](#promisepropsobjectpromise-object---promise). With the exception that if this promise is [bound](#binddynamic-thisarg---promise) to a value, the returned promise is bound to that value too.
+
+<hr>
+
+#####`.settle()` -> `Promise`
+
+Same as calling [Promise.settle\(thisPromise\)](#promisesettlearraydynamicpromise-values---promise). With the exception that if this promise is [bound](#binddynamic-thisarg---promise) to a value, the returned promise is bound to that value too.
+
+<hr>
+
+#####`.any()` -> `Promise`
+
+Same as calling [Promise.any\(thisPromise\)](#promiseanyarraydynamicpromise-values---promise). With the exception that if this promise is [bound](#binddynamic-thisarg---promise) to a value, the returned promise is bound to that value too.
+
+<hr>
+
+#####`.race()` -> `Promise`
+
+Same as calling [Promise.race\(thisPromise\)](#promiseracearraypromise-promises---promise). With the exception that if this promise is [bound](#binddynamic-thisarg---promise) to a value, the returned promise is bound to that value too.
+
+<hr>
+
+
+#####`.some(int count)` -> `Promise`
+
+Same as calling [Promise.some\(thisPromise, count\)](#promisesomearraydynamicpromise-values-int-count---promise). With the exception that if this promise is [bound](#binddynamic-thisarg---promise) to a value, the returned promise is bound to that value too.
+
+<hr>
+
+#####`.spread([Function fulfilledHandler] [, Function rejectedHandler ])` -> `Promise`
+
+Like calling `.then`, but the fulfillment value or rejection reason is assumed to be an array, which is flattened to the formal parameters of the handlers.
+
+```js
+Promise.all([task1, task2, task3]).spread(function(result1, result2, result3){
+
+});
+```
+
+Normally when using `.then` the code would be like:
+
+```js
+Promise.all([task1, task2, task3]).then(function(results){
+    var result1 = results[0];
+    var result2 = results[1];
+    var result3 = results[2];
+});
+```
+
+This is useful when the `results` array contains items that are not conceptually items of the same list.
+
+<hr>
+
+#####`.map(Function mapper)` -> `Promise`
+
+Same as calling [Promise.map\(thisPromise, mapper\)](#promisemaparraydynamicpromise-values-function-mapper---promise). With the exception that if this promise is [bound](#binddynamic-thisarg---promise) to a value, the returned promise is bound to that value too.
+
+<hr>
+
+#####`.reduce(Function reducer [, dynamic initialValue])` -> `Promise`
+
+Same as calling [Promise.reduce\(thisPromise, Function reducer, initialValue\)](#promisereducearraydynamicpromise-values-function-reducer--dynamic-initialvalue---promise). With the exception that if this promise is [bound](#binddynamic-thisarg---promise) to a value, the returned promise is bound to that value too.
+
+<hr>
+
+#####`.filter(Function filterer)` -> `Promise`
+
+Same as calling [`Promise.filter(thisPromise, filterer)`](#promisefilterarraydynamicpromise-values-function-filterer---promise). With the exception that if this promise is [bound](#binddynamic-thisarg---promise) to a value, the returned promise is bound to that value too.
+
+In this example, a list of websites are pinged with 100ms timeout. [`.settle()`](#settle---promise) is used to wait until all pings are either fulfilled or rejected. Then the settled
+list of [`PromiseInspections`](#inspect---promiseinspection) is filtered for those that fulfilled (responded in under 100ms) and [`mapped`](#promisemaparraydynamicpromise-values-function-mapper---promise) to the actual fulfillment value.
+
+```js
+pingWebsitesAsync({timeout: 100}).settle()
+.filter(function(inspection){
+    return inspection.isFulfilled();
+})
+.map(function(inspection){
+    return inspection.value();
+})
+.then(function(websites){
+   //List of website names which answered
+});
+```
+
+The above pattern is actually reusable and can be captured in a method:
+
+```js
+Promise.prototype.settledWithFulfill = function() {
+    return this.settle()
+        .filter(function(inspection){
+            return inspection.isFulfilled();
+        })
+        .map(function(inspection){
+            return inspection.value();
+        });
+};
+```
+
+<hr>
+
+#####`Promise.all(Array<dynamic>|Promise values)` -> `Promise`
+
+Given an array, or a promise of an array, which contains promises (or a mix of promises and values) return a promise that is fulfilled when all the items in the array are fulfilled. The promise's fulfillment value is an array with fulfillment values at respective positions to the original array. If any promise in the array rejects, the returned promise is rejected with the rejection reason.
+
+In this example we create a promise that is fulfilled only when the pictures, comments and tweets are all loaded.
+
+```js
+Promise.all([getPictures(), getComments(), getTweets()]).then(function(results){
+    //Everything loaded and good to go
+    var pictures = results[0];
+    var comments = results[1];
+    var tweets = results[2];
+}).catch(function(e){
+    alertAsync("error when getting your stuff");
+});
+```
+
+See [`.spread()`](#spreadfunction-fulfilledhandler--function-rejectedhandler----promise) for a more convenient way to extract the fulfillment values.
+
+*The original array is not modified. The input array sparsity is retained in the resulting array.*
+
+<hr>
+
+#####`Promise.props(Object|Promise object)` -> `Promise`
+
+Like [`Promise.all`](#promiseallarraydynamic-values---promise) but for object properties instead of array items. Returns a promise that is fulfilled when all the properties of the object are fulfilled. The promise's fulfillment value is an object with fulfillment values at respective keys to the original object. If any promise in the object rejects, the returned promise is rejected with the rejection reason.
+
+If `object` is a trusted `Promise`, then it will be treated as a promise for object rather than for its properties. All other objects are treated for their properties as is returned by `Object.keys` - the object's own enumerable properties.
+
+```js
+Promise.props({
+    pictures: getPictures(),
+    comments: getComments(),
+    tweets: getTweets()
+}).then(function(result){
+    console.log(result.tweets, result.pictures, result.comments);
+});
+```
+
+Note that if you have no use for the result object other than retrieving the properties, it is more convenient to use [`Promise.all`](#promiseallarraydynamic-values---promise) and [`.spread()`](#spreadfunction-fulfilledhandler--function-rejectedhandler----promise):
+
+```js
+Promise.all([getPictures(), getComments(), getTweets()])
+.spread(function(pictures, comments, tweets) {
+    console.log(pictures, comments, tweets);
+});
+```
+
+*The original object is not modified.*
+
+<hr>
+
+#####`Promise.settle(Array<dynamic>|Promise values)` -> `Promise`
+
+Given an array, or a promise of an array, which contains promises (or a mix of promises and values) return a promise that is fulfilled when all the items in the array are either fulfilled or rejected. The fulfillment value is an array of [`PromiseInspection`](#inspect---promiseinspection) instances at respective positions in relation to the input array.
+
+*The original array is not modified. The input array sparsity is retained in the resulting array.*
+
+<hr>
+
+#####`Promise.any(Array<dynamic>|Promise values)` -> `Promise`
+
+Like [`Promise.some\(\)`](#someint-count---promise), with 1 as `count`. However, if the promise fulfills, the fulfillment value is not an array of 1 but the value directly.
+
+<hr>
+
+#####`Promise.race(Array|Promise promises)` -> `Promise`
+
+Given an array, or a promise of an array, which contains promises (or a mix of promises and values) return a promise that is fulfilled or rejected as soon as a promise in the array is fulfilled or rejected with the respective rejection reason or fulfillment value.
+
+Example of implementing a timeout in terms of `Promise.race`:
+
+```js
+var Promise = require("bluebird");
+var fs = Promise.promisifyAll(require("fs"));
+
+function delay(ms) {
+    return new Promise(function (v) {
+        setTimeout(v, ms);
+    });
+}
+
+function timeout(promise, time) {
+    var timeout = delay(time).then(function () {
+        throw new Promise.TimeoutError("Operation timed out after " + time + " ms");
+    });
+
+    return Promise.race([promise, timeout]);
+}
+
+timeout(fs.readFileAsync("slowfile.txt"), 300).then(function (contents) {
+    console.log("Here are the contents", contents);
+}).
+catch(Promise.TimeoutError, function (e) {
+    console.error("Sorry retrieving file took too long");
+});
+```
+
+**Note** If you pass empty array or a sparse array with no values, or a promise/thenable for such, it will be forever pending.
+
+<hr>
+
+#####`Promise.some(Array<dynamic>|Promise values, int count)` -> `Promise`
+
+Initiate a competetive race between multiple promises or values (values will become immediately fulfilled promises). When `count` amount of promises have been fulfilled, the returned promise is fulfilled with an array that contains the fulfillment values of the winners in order of resolution.
+
+This example pings 4 nameservers, and logs the fastest 2 on console:
+
+```js
+Promise.some([
+    ping("ns1.example.com"),
+    ping("ns2.example.com"),
+    ping("ns3.example.com"),
+    ping("ns4.example.com")
+], 2).spread(function(first, second) {
+    console.log(first, second);
+});
+```
+
+If too many promises are rejected so that the promise can never become fulfilled, it will be immediately rejected with an array of rejection reasons in the order they were thrown in.
+
+*The original array is not modified.*
+
+<hr>
+
+#####`Promise.join([dynamic value...])` -> `Promise`
+
+Like [`Promise.all\(\)`](#promiseallarraydynamic-values---promise) but instead of having to pass an array, the array is generated from the passed variadic arguments.
+
+So instead of:
+
+```js
+Promise.all([a, b]).spread(function(aResult, bResult) {
+
+});
+```
+
+You can do:
+
+```js
+Promise.join(a, b).spread(function(aResult, bResult) {
+
+});
+```
+
+<hr>
+
+#####`Promise.map(Array<dynamic>|Promise values, Function mapper)` -> `Promise`
+
+Map an array, or a promise of an array, which contains a promises (or a mix of promises and values) with the given `mapper` function with the signature `(item, index, arrayLength)` where `item` is the resolved value of a respective promise in the input array. If any promise in the input array is rejected the returned promise is rejected as well.
+
+If the `mapper` function returns promises or thenables, the returned promise will wait for all the mapped results to be resolved as well.
+
+*(TODO: an example where this is useful)*
+
+*The original array is not modified.*
+
+<hr>
+
+#####`Promise.reduce(Array<dynamic>|Promise values, Function reducer [, dynamic initialValue])` -> `Promise`
+
+Reduce an array, or a promise of an array, which contains a promises (or a mix of promises and values) with the given `reducer` function with the signature `(total, current, index, arrayLength)` where `item` is the resolved value of a respective promise in the input array. If any promise in the input array is rejected the returned promise is rejected as well.
+
+If the reducer function returns a promise or a thenable, the result for the promise is awaited for before continuing with next iteration.
+
+Read given files sequentially while summing their contents as an integer. Each file contains just the text `10`.
+
+```js
+Promise.reduce(["file1.txt", "file2.txt", "file3.txt"], function(total, fileName) {
+    return fs.readFileAsync(fileName, "utf8").then(function(contents) {
+        return total + parseInt(contents, 10);
+    });
+}, 0).then(function(total) {
+    //Total is 30
+});
+```
+
+*The original array is not modified. If `intialValue` is `undefined` (or a promise that resolves to `undefined`) and the array contains only 1 item, the callback will not be called and `undefined` is returned. If the array is empty, the callback will not be called and `initialValue` is returned (which may be `undefined`).*
+
+<hr>
+
+#####`Promise.filter(Array<dynamic>|Promise values, Function filterer)` -> `Promise`
+
+Filter an array, or a promise of an array, which contains a promises (or a mix of promises and values) with the given `filterer` function with the signature `(item, index, arrayLength)` where `item` is the resolved value of a respective promise in the input array. If any promise in the input array is rejected the returned promise is rejected as well.
+
+The return values from the filtered functions are coerced to booleans, with the exception of promises and thenables which are awaited for their eventual result.
+
+[See the instance method `.filter()` for an example.](#filterfunction-filterer---promise)
+
+*The original array is not modified.
+
+<hr>
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/CONTRIBUTING.md b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/CONTRIBUTING.md
new file mode 100644
index 0000000..7042bcb
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/CONTRIBUTING.md
@@ -0,0 +1,68 @@
+# Contributing to bluebird
+
+1. [Directory structure](#directory-structure)
+2. [Style guide](#style-guide)
+3. [Scripts and macros](#scripts-and-macros)
+4. [JSHint](#jshint)
+5. [Testing](#testing)
+
+## Directory structure
+
+- `/benchmark` contains benchmark scripts and stats of benchmarks
+
+- `/browser` contains scripts and output for browser testing environment
+
+- `/js` contains automatically generated build output. **NOTE** never commit any changes to these files to git.
+
+    - `/js/browser` contains a file suitable for use in browsers
+    - `/js/main` contains the main build to be used with node. The npm package points to /js/main/bluebird.js
+    - `/js/debug` contains the debug build to be used with node. Used when running tests
+    - `/js/zalgo` contains the zalgo build not to be used by any mortals.
+
+- `/node_modules` contains development dependencies such as grunt
+
+- `/src` contains the source code
+
+- `/test/mocha` contains tests using the mocha testing framework
+
+## Scripts and macros
+
+Scripts and macros are necessary for the code the code to remain readable and performant. For example, there is no way to turn the `arguments` object into an array without using a build step macro unless you want to compromise readability or performance.
+
+`/ast_passes.js` contains functions called ast passes that will parse input source code into an AST, modify it in some way and spit out new source code with the changes reflected.
+
+`/src/constants.js` contains declarations for constants that will be inlined in the resulting code during in the build step. JavaScript lacks a way to express constants, particularly if you are expecting the performance implications.
+
+`/Gruntfile.js` contains task definitions to be used with the Grunt build framework. It for example sets up source code transformations.
+
+`/bench` a bash script to run benchmarks.
+
+`/mocharun.js` a hack script to make mocha work when running multiple tests in parallel processes
+
+## JSHint
+
+Due to JSHint globals being dynamic, the JSHint rules are declared in `/Gruntfile.js`.
+
+## Style guide
+
+Use the same style as is used in the surrounding code.
+
+###Whitespace
+
+- No more than 80 columns per line
+- 4 space indentation
+- No trailing whitespace
+- LF at end of files
+- Curly braces can be left out of single statement `if/else/else if`s when it is obvious there will never be multiple statements such as null check at the top of a function for an early return.
+- Add an additional new line between logical sections of code.
+
+###Variables
+
+- Use multiple `var` statements instead of a single one with comma separator. Do not declare variables until you need them.
+
+###Equality and type checks
+
+- Always use `===` except when checking for null or undefined. To check for null or undefined, use `x == null`.
+- For checks that can be done with `typeof`: do not make helper functions, save results of `typeof` to a variable or make the type string a non-constant. Always write the check in the form `typeof expression === "constant string"` even if it feels like repeating yourself.
+
+## Testing
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/Gruntfile.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/Gruntfile.js
new file mode 100644
index 0000000..77fc162
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/Gruntfile.js
@@ -0,0 +1,725 @@
+"use strict";
+Error.stackTraceLimit = 100;
+var astPasses = require("./ast_passes.js");
+var node11 = parseInt(process.versions.node.split(".")[1], 10) >= 11;
+var Q = require("q");
+Q.longStackSupport = true;
+
+module.exports = function( grunt ) {
+    var isCI = !!grunt.option("ci");
+
+
+    function getBrowsers() {
+        //Terse format to generate the verbose format required by sauce
+        var browsers = {
+            "internet explorer|WIN8": ["10"],
+            "internet explorer|WIN8.1": ["11"],
+            "firefox|Windows 7": ["3.5", "3.6", "4", "25"],
+            "firefox|WIN8.1": null,
+            "chrome|Windows 7": null,
+            "safari|Windows 7": ["5"],
+            "iphone|OS X 10.8": ["6.0"]
+        };
+
+        var ret = [];
+        for( var browserAndPlatform in browsers) {
+            var split = browserAndPlatform.split("|");
+            var browser = split[0];
+            var platform = split[1];
+            var versions = browsers[browserAndPlatform];
+            if( versions != null ) {
+                for( var i = 0, len = versions.length; i < len; ++i ) {
+                    ret.push({
+                        browserName: browser,
+                        platform: platform,
+                        version: versions[i]
+                    });
+                }
+            }
+            else {
+                ret.push({
+                    browserName: browser,
+                    platform: platform
+                });
+            }
+        }
+        return ret;
+    }
+
+
+    var optionalModuleDependencyMap = {
+        "timers.js": ['Promise', 'INTERNAL'],
+        "any.js": ['Promise', 'Promise$_CreatePromiseArray', 'PromiseArray'],
+        "race.js": ['Promise', 'INTERNAL'],
+        "call_get.js": ['Promise'],
+        "filter.js": ['Promise', 'Promise$_CreatePromiseArray', 'PromiseArray', 'apiRejection'],
+        "generators.js": ['Promise', 'apiRejection', 'INTERNAL'],
+        "map.js": ['Promise', 'Promise$_CreatePromiseArray', 'PromiseArray', 'apiRejection'],
+        "nodeify.js": ['Promise'],
+        "promisify.js": ['Promise', 'INTERNAL'],
+        "props.js": ['Promise', 'PromiseArray'],
+        "reduce.js": ['Promise', 'Promise$_CreatePromiseArray', 'PromiseArray', 'apiRejection', 'INTERNAL'],
+        "settle.js": ['Promise', 'Promise$_CreatePromiseArray', 'PromiseArray'],
+        "some.js": ['Promise', 'Promise$_CreatePromiseArray', 'PromiseArray', 'apiRejection'],
+        "progress.js": ['Promise', 'isPromiseArrayProxy'],
+        "cancel.js": ['Promise', 'INTERNAL'],
+        "synchronous_inspection.js": ['Promise']
+
+    };
+
+    var optionalModuleRequireMap = {
+        "timers.js": true,
+        "race.js": true,
+        "any.js": true,
+        "call_get.js": true,
+        "filter.js": true,
+        "generators.js": true,
+        "map.js": true,
+        "nodeify.js": true,
+        "promisify.js": true,
+        "props.js": true,
+        "reduce.js": true,
+        "settle.js": true,
+        "some.js": true,
+        "progress.js": true,
+        "cancel.js": true,
+        "synchronous_inspection.js": true
+
+    };
+
+    function getOptionalRequireCode( srcs ) {
+        return srcs.reduce(function(ret, cur, i){
+            if( optionalModuleRequireMap[cur] ) {
+                ret += "require('./"+cur+"')("+ optionalModuleDependencyMap[cur] +");\n";
+            }
+            return ret;
+        }, "") + "\nPromise.prototype = Promise.prototype;\nreturn Promise;\n";
+    }
+
+    function getBrowserBuildHeader( sources ) {
+        var header = "/**\n * bluebird build version " + gruntConfig.pkg.version + "\n";
+        var enabledFeatures = ["core"];
+        var disabledFeatures = [];
+        featureLoop: for( var key in optionalModuleRequireMap ) {
+            for( var i = 0, len = sources.length; i < len; ++i ) {
+                var source = sources[i];
+                if( source.fileName === key ) {
+                    enabledFeatures.push( key.replace( ".js", "") );
+                    continue featureLoop;
+                }
+            }
+            disabledFeatures.push( key.replace( ".js", "") );
+        }
+
+        header += ( " * Features enabled: " + enabledFeatures.join(", ") + "\n" );
+
+        if( disabledFeatures.length ) {
+            header += " * Features disabled: " + disabledFeatures.join(", ") + "\n";
+        }
+        header += "*/\n";
+        return header;
+    }
+
+    function applyOptionalRequires( src, optionalRequireCode ) {
+        return src.replace( /};([^}]*)$/, optionalRequireCode + "\n};$1");
+    }
+
+    var CONSTANTS_FILE = './src/constants.js';
+    var BUILD_DEBUG_DEST = "./js/debug/bluebird.js";
+
+    var license;
+    function getLicense() {
+        if( !license ) {
+            var fs = require("fs");
+            var text = fs.readFileSync("LICENSE", "utf8");
+            text = text.split("\n").map(function(line, index){
+                return " * " + line;
+            }).join("\n")
+            license = "/**\n" + text + "\n */\n";
+        }
+        return license
+    }
+
+    var preserved;
+    function getLicensePreserve() {
+        if( !preserved ) {
+            var fs = require("fs");
+            var text = fs.readFileSync("LICENSE", "utf8");
+            text = text.split("\n").map(function(line, index){
+                if( index === 0 ) {
+                    return " * @preserve " + line;
+                }
+                return " * " + line;
+            }).join("\n")
+            preserved = "/**\n" + text + "\n */\n";
+        }
+        return preserved;
+    }
+
+    function writeFile( dest, content ) {
+        grunt.file.write( dest, content );
+        grunt.log.writeln('File "' + dest + '" created.');
+    }
+
+    function writeFileAsync( dest, content ) {
+        var fs = require("fs");
+        return Q.nfcall(fs.writeFile, dest, content).then(function(){
+            grunt.log.writeln('File "' + dest + '" created.');
+        });
+    }
+
+    var gruntConfig = {};
+
+    var getGlobals = function() {
+        var fs = require("fs");
+        var file = "./src/constants.js";
+        var contents = fs.readFileSync(file, "utf8");
+        var rconstantname = /CONSTANT\(\s*([^,]+)/g;
+        var m;
+        var globals = {
+            Error: true,
+            args: true,
+            INLINE_SLICE: false,
+            TypeError: true,
+            RangeError: true,
+            __DEBUG__: false,
+            __BROWSER__: false,
+            process: true,
+            "console": false,
+            "require": false,
+            "module": false,
+            "define": false
+        };
+        while( ( m = rconstantname.exec( contents ) ) ) {
+            globals[m[1]] = false;
+        }
+        return globals;
+    }
+
+    gruntConfig.pkg = grunt.file.readJSON("package.json");
+
+    gruntConfig.jshint = {
+        all: {
+            options: {
+                globals: getGlobals(),
+
+                "bitwise": false,
+                "camelcase": true,
+                "curly": true,
+                "eqeqeq": true,
+                "es3": true,
+                "forin": true,
+                "immed": true,
+                "latedef": false,
+                "newcap": true,
+                "noarg": true,
+                "noempty": true,
+                "nonew": true,
+                "plusplus": false,
+                "quotmark": "double",
+                "undef": true,
+                "unused": true,
+                "strict": false,
+                "trailing": true,
+                "maxparams": 6,
+                "maxlen": 80,
+
+                "asi": false,
+                "boss": true,
+                "eqnull": true,
+                "evil": true,
+                "expr": false,
+                "funcscope": false,
+                "globalstrict": false,
+                "lastsemic": false,
+                "laxcomma": false,
+                "laxbreak": false,
+                "loopfunc": true,
+                "multistr": true,
+                "proto": false,
+                "scripturl": true,
+                "smarttabs": false,
+                "shadow": true,
+                "sub": true,
+                "supernew": false,
+                "validthis": true,
+
+                "browser": true,
+                "jquery": true,
+                "devel": true,
+
+
+                '-W014': true,
+                '-W116': true,
+                '-W106': true,
+                '-W064': true,
+                '-W097': true
+            },
+
+            files: {
+                src: [
+                    "./src/finally.js",
+                    "./src/direct_resolve.js",
+                    "./src/synchronous_inspection.js",
+                    "./src/thenables.js",
+                    "./src/progress.js",
+                    "./src/cancel.js",
+                    "./src/any.js",
+                    "./src/race.js",
+                    "./src/call_get.js",
+                    "./src/filter.js",
+                    "./src/generators.js",
+                    "./src/map.js",
+                    "./src/nodeify.js",
+                    "./src/promisify.js",
+                    "./src/props.js",
+                    "./src/reduce.js",
+                    "./src/settle.js",
+                    "./src/some.js",
+                    "./src/util.js",
+                    "./src/schedule.js",
+                    "./src/queue.js",
+                    "./src/errors.js",
+                    "./src/captured_trace.js",
+                    "./src/async.js",
+                    "./src/catch_filter.js",
+                    "./src/promise.js",
+                    "./src/promise_array.js",
+                    "./src/settled_promise_array.js",
+                    "./src/some_promise_array.js",
+                    "./src/properties_promise_array.js",
+                    "./src/promise_inspection.js",
+                    "./src/promise_resolver.js",
+                    "./src/promise_spawn.js"
+                ]
+            }
+        }
+    };
+
+    if( !isCI ) {
+        gruntConfig.jshint.all.options.reporter = require("jshint-stylish");
+    }
+
+    gruntConfig.connect = {
+        server: {
+            options: {
+                base: "./browser",
+                port: 9999
+            }
+        }
+    };
+
+    gruntConfig.watch = {};
+
+    gruntConfig["saucelabs-mocha"] = {
+        all: {
+            options: {
+                urls: ["http://127.0.0.1:9999/index.html"],
+                tunnelTimeout: 5,
+                build: process.env.TRAVIS_JOB_ID,
+                concurrency: 3,
+                browsers: getBrowsers(),
+                testname: "mocha tests",
+                tags: ["master"]
+            }
+        }
+    };
+
+    gruntConfig.bump = {
+      options: {
+        files: ['package.json'],
+        updateConfigs: [],
+        commit: true,
+        commitMessage: 'Release v%VERSION%',
+        commitFiles: ['-a'],
+        createTag: true,
+        tagName: 'v%VERSION%',
+        tagMessage: 'Version %VERSION%',
+        false: true,
+        pushTo: 'master',
+        gitDescribeOptions: '--tags --always --abbrev=1 --dirty=-d' // options to use with '$ git describe'
+      }
+    };
+
+    grunt.initConfig(gruntConfig);
+    grunt.loadNpmTasks("grunt-contrib-connect");
+    grunt.loadNpmTasks("grunt-saucelabs");
+    grunt.loadNpmTasks('grunt-contrib-jshint');
+    grunt.loadNpmTasks('grunt-contrib-watch');
+    grunt.loadNpmTasks('grunt-contrib-concat');
+
+    function runIndependentTest( file, cb , env) {
+        var fs = require("fs");
+        var path = require("path");
+        var sys = require('sys');
+        var spawn = require('child_process').spawn;
+        var p = path.join(process.cwd(), "test");
+
+        var stdio = [
+            'ignore',
+            grunt.option("verbose")
+                ? process.stdout
+                : 'ignore',
+            process.stderr
+        ];
+        var flags = node11 ? ["--harmony-generators"] : [];
+        flags.push("--allow-natives-syntax");
+        if( file.indexOf( "mocha/") > -1 || file === "aplus.js" ) {
+            var node = spawn(typeof node11 === "string" ? node11 : 'node',
+                flags.concat(["../mocharun.js", file]),
+                {cwd: p, stdio: stdio, env: env});
+        }
+        else {
+            var node = spawn('node', flags.concat(["./"+file]),
+                             {cwd: p, stdio: stdio, env:env});
+        }
+        node.on('exit', exit );
+
+        function exit( code ) {
+            if( code !== 0 ) {
+                cb(new Error("process didn't exit normally. Code: " + code));
+            }
+            else {
+                cb(null);
+            }
+        }
+
+
+    }
+
+    function buildMain( sources, optionalRequireCode ) {
+        var fs = require("fs");
+        var Q = require("q");
+        var root = cleanDirectory("./js/main/");
+
+        return Q.all(sources.map(function( source ) {
+            var src = astPasses.removeAsserts( source.sourceCode, source.fileName );
+            src = astPasses.inlineExpansion( src, source.fileName );
+            src = astPasses.expandConstants( src, source.fileName );
+            src = src.replace( /__DEBUG__/g, "false" );
+            src = src.replace( /__BROWSER__/g, "false" );
+            if( source.fileName === "promise.js" ) {
+                src = applyOptionalRequires( src, optionalRequireCode );
+            }
+            var path = root + source.fileName;
+            return writeFileAsync(path, src);
+        }));
+    }
+
+    function buildDebug( sources, optionalRequireCode ) {
+        var fs = require("fs");
+        var Q = require("q");
+        var root = cleanDirectory("./js/debug/");
+
+        return Q.nfcall(require('mkdirp'), root).then(function(){
+            return Q.all(sources.map(function( source ) {
+                var src = astPasses.expandAsserts( source.sourceCode, source.fileName );
+                src = astPasses.inlineExpansion( src, source.fileName );
+                src = astPasses.expandConstants( src, source.fileName );
+                src = src.replace( /__DEBUG__/g, "true" );
+                src = src.replace( /__BROWSER__/g, "false" );
+                if( source.fileName === "promise.js" ) {
+                    src = applyOptionalRequires( src, optionalRequireCode );
+                }
+                var path = root + source.fileName;
+                return writeFileAsync(path, src);
+            }));
+        });
+    }
+
+    function buildZalgo( sources, optionalRequireCode ) {
+        var fs = require("fs");
+        var Q = require("q");
+        var root = cleanDirectory("./js/zalgo/");
+
+        return Q.all(sources.map(function( source ) {
+            var src = astPasses.removeAsserts( source.sourceCode, source.fileName );
+            src = astPasses.inlineExpansion( src, source.fileName );
+            src = astPasses.expandConstants( src, source.fileName );
+            src = astPasses.asyncConvert( src, "async", "invoke", source.fileName);
+            src = src.replace( /__DEBUG__/g, "false" );
+            src = src.replace( /__BROWSER__/g, "false" );
+            if( source.fileName === "promise.js" ) {
+                src = applyOptionalRequires( src, optionalRequireCode );
+            }
+            var path = root + source.fileName;
+            return writeFileAsync(path, src);
+        }));
+    }
+
+    function buildBrowser( sources ) {
+        var fs = require("fs");
+        var browserify = require("browserify");
+        var b = browserify("./js/main/bluebird.js");
+        var dest = "./js/browser/bluebird.js";
+
+        var header = getBrowserBuildHeader( sources );
+
+        return Q.nbind(b.bundle, b)({
+                detectGlobals: false,
+                standalone: "Promise"
+        }).then(function(src) {
+            return writeFileAsync( dest,
+                getLicensePreserve() + src )
+        }).then(function() {
+            return Q.nfcall(fs.readFile, dest, "utf8" );
+        }).then(function( src ) {
+            src = header + src;
+            return Q.nfcall(fs.writeFile, dest, src );
+        });
+    }
+
+    function cleanDirectory(dir) {
+        if (isCI) return dir;
+        var fs = require("fs");
+        require("rimraf").sync(dir);
+        fs.mkdirSync(dir);
+        return dir;
+    }
+
+    function getOptionalPathsFromOption( opt ) {
+        opt = (opt + "").toLowerCase().split(/\s+/g);
+        return optionalPaths.filter(function(v){
+            v = v.replace("./src/", "").replace( ".js", "" ).toLowerCase();
+            return opt.indexOf(v) > -1;
+        });
+    }
+
+    var optionalPaths = [
+        "./src/timers.js",
+        "./src/synchronous_inspection.js",
+        "./src/any.js",
+        "./src/race.js",
+        "./src/call_get.js",
+        "./src/filter.js",
+        "./src/generators.js",
+        "./src/map.js",
+        "./src/nodeify.js",
+        "./src/promisify.js",
+        "./src/props.js",
+        "./src/reduce.js",
+        "./src/settle.js",
+        "./src/some.js",
+        "./src/progress.js",
+        "./src/cancel.js"
+    ];
+
+    var mandatoryPaths = [
+        "./src/finally.js",
+        "./src/es5.js",
+        "./src/bluebird.js",
+        "./src/thenables.js",
+        "./src/assert.js",
+        "./src/global.js",
+        "./src/util.js",
+        "./src/schedule.js",
+        "./src/queue.js",
+        "./src/errors.js",
+        "./src/errors_api_rejection.js",
+        "./src/captured_trace.js",
+        "./src/async.js",
+        "./src/catch_filter.js",
+        "./src/promise.js",
+        "./src/promise_array.js",
+        "./src/settled_promise_array.js",
+        "./src/some_promise_array.js",
+        "./src/properties_promise_array.js",
+        "./src/promise_inspection.js",
+        "./src/promise_resolver.js",
+        "./src/promise_spawn.js",
+        "./src/direct_resolve.js"
+    ];
+
+
+
+    function build( paths, isCI ) {
+        var fs = require("fs");
+        astPasses.readConstants(fs.readFileSync(CONSTANTS_FILE, "utf8"), CONSTANTS_FILE);
+        if( !paths ) {
+            paths = optionalPaths.concat(mandatoryPaths);
+        }
+        var optionalRequireCode = getOptionalRequireCode(paths.map(function(v) {
+            return v.replace("./src/", "");
+        }));
+
+        var Q = require("q");
+
+        var promises = [];
+        var sources = paths.map(function(v){
+            var promise = Q.nfcall(fs.readFile, v, "utf8");
+            promises.push(promise);
+            var ret = {};
+
+            ret.fileName = v.replace("./src/", "");
+            ret.sourceCode = promise.then(function(v){
+                ret.sourceCode = v;
+            });
+            return ret;
+        });
+
+        //Perform common AST passes on all builds
+        return Q.all(promises.slice()).then(function(){
+            sources.forEach( function( source ) {
+                var src = source.sourceCode
+                src = astPasses.removeComments(src, source.fileName);
+                src = getLicense() + src;
+                source.sourceCode = src;
+            });
+
+            if( isCI ) {
+                return buildDebug( sources, optionalRequireCode );
+            }
+            else {
+                return Q.all([
+                    buildMain( sources, optionalRequireCode ).then( function() {
+                        return buildBrowser( sources );
+                    }),
+                    buildDebug( sources, optionalRequireCode ),
+                    buildZalgo( sources, optionalRequireCode )
+                ]);
+            }
+        });
+    }
+
+    String.prototype.contains = function String$contains( str ) {
+        return this.indexOf( str ) >= 0;
+    };
+
+    function isSlowTest( file ) {
+        return file.contains("2.3.3") ||
+            file.contains("bind") ||
+            file.contains("unhandled_rejections");
+    }
+
+    function testRun( testOption ) {
+        var fs = require("fs");
+        var path = require("path");
+        var done = this.async();
+
+        var totalTests = 0;
+        var testsDone = 0;
+        function testDone() {
+            testsDone++;
+            if( testsDone >= totalTests ) {
+                done();
+            }
+        }
+        var files;
+        if( testOption === "aplus" ) {
+            files = fs.readdirSync("test/mocha").filter(function(f){
+                return /^\d+\.\d+\.\d+/.test(f);
+            }).map(function( f ){
+                return "mocha/" + f;
+            });
+        }
+        else {
+            files = testOption === "all"
+                ? fs.readdirSync('test')
+                    .concat(fs.readdirSync('test/mocha')
+                        .map(function(fileName){
+                            return "mocha/" + fileName
+                        })
+                    )
+                : [testOption + ".js" ];
+
+
+            if( testOption !== "all" &&
+                !fs.existsSync( "./test/" + files[0] ) ) {
+                files[0] = "mocha/" + files[0];
+            }
+        }
+        files = files.filter(function(fileName){
+            if( !node11 && fileName.indexOf("generator") > -1 ) {
+                return false;
+            }
+            return /\.js$/.test(fileName);
+        }).map(function(f){
+            return f.replace( /(\d)(\d)(\d)/, "$1.$2.$3" );
+        });
+
+
+        var slowTests = files.filter(isSlowTest);
+        files = files.filter(function(file){
+            return !isSlowTest(file);
+        });
+
+        function runFile(file) {
+            totalTests++;
+            grunt.log.writeln("Running test " + file );
+            var env = undefined;
+            if (file.indexOf("bluebird-debug-env-flag") >= 0) {
+                env = Object.create(process.env);
+                env["BLUEBIRD_DEBUG"] = true;
+            }
+            runIndependentTest(file, function(err) {
+                if( err ) throw new Error(err + " " + file + " failed");
+                grunt.log.writeln("Test " + file + " succeeded");
+                testDone();
+                if( files.length > 0 ) {
+                    runFile( files.shift() );
+                }
+            }, env);
+        }
+
+        slowTests.forEach(runFile);
+
+        var maxParallelProcesses = 10;
+        var len = Math.min( files.length, maxParallelProcesses );
+        for( var i = 0; i < len; ++i ) {
+            runFile( files.shift() );
+        }
+    }
+
+    grunt.registerTask( "build", function() {
+
+        var done = this.async();
+        var features = grunt.option("features");
+        var paths = null;
+        if( features ) {
+            paths = getOptionalPathsFromOption( features ).concat( mandatoryPaths );
+        }
+
+        build( paths, isCI ).then(function() {
+            done();
+        }).catch(function(e) {
+            if( e.fileName && e.stack ) {
+                console.log(e.scriptSrc);
+                var stack = e.stack.split("\n");
+                stack[0] = stack[0] + " " + e.fileName;
+                console.error(stack.join("\n"));
+                if (!grunt.option("verbose")) {
+                    console.error("use --verbose to see the source code");
+                }
+
+            }
+            else {
+                console.error(e.stack);
+            }
+            done(false);
+        });
+    });
+
+    grunt.registerTask( "testrun", function(){
+        var testOption = grunt.option("run");
+        var node11path = grunt.option("node11");
+
+        if (typeof node11path === "string" && node11path) {
+            node11 = node11path;
+        }
+
+        if( !testOption ) testOption = "all";
+        else {
+            testOption = ("" + testOption);
+            testOption = testOption
+                .replace( /\.js$/, "" )
+                .replace( /[^a-zA-Z0-9_-]/g, "" );
+        }
+        testRun.call( this, testOption );
+    });
+
+    grunt.registerTask( "test", ["jshint", "build", "testrun"] );
+    grunt.registerTask( "test-browser", ["connect", "saucelabs-mocha"]);
+    grunt.registerTask( "default", ["jshint", "build"] );
+    grunt.registerTask( "dev", ["connect", "watch"] );
+
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/LICENSE b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/LICENSE
new file mode 100644
index 0000000..9ed7b98
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2014 Petka Antonov
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:</p>
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/README.md b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/README.md
new file mode 100644
index 0000000..7d4d1ae
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/README.md
@@ -0,0 +1,696 @@
+[![Build Status](https://travis-ci.org/petkaantonov/bluebird.png?branch=master)](https://travis-ci.org/petkaantonov/bluebird)
+
+<a href="http://promisesaplus.com/">
+    <img src="http://promisesaplus.com/assets/logo-small.png" alt="Promises/A+ logo"
+         title="Promises/A+ 1.0 compliant" align="right" />
+</a>
+
+#Introduction
+
+Bluebird is a fully featured [promise](#what-are-promises-and-why-should-i-use-them) library with focus on innovative features and performance
+
+#Topics
+
+- [Features](#features)
+- [Quick start](#quick-start)
+- [API Reference and examples](https://github.com/petkaantonov/bluebird/blob/master/API.md)
+- [What are promises and why should I use them?](#what-are-promises-and-why-should-i-use-them)
+- [Questions and issues](#questions-and-issues)
+- [Error handling](#error-handling)
+- [Development](#development)
+    - [Testing](#testing)
+    - [Benchmarking](#benchmarks)
+    - [Custom builds](#custom-builds)
+    - [For library authors](#for-library-authors)
+- [What is the sync build?](#what-is-the-sync-build)
+- [License](#license)
+- [Snippets for common problems](https://github.com/petkaantonov/bluebird/wiki/Snippets)
+- [Promise anti-patterns](https://github.com/petkaantonov/bluebird/wiki/Promise-anti-patterns)
+- [Changelog](https://github.com/petkaantonov/bluebird/blob/master/changelog.md)
+- [Optimization guide](#optimization-guide)
+
+#Features:
+
+- [Promises A+ 2.0.2](http://promisesaplus.com)
+- [Cancellation](https://github.com/promises-aplus)
+- [Progression](https://github.com/promises-aplus/progress-spec)
+- [Synchronous inspection](https://github.com/promises-aplus/synchronous-inspection-spec)
+- [`.bind`](https://github.com/petkaantonov/bluebird/blob/master/API.md#binddynamic-thisarg---promise)
+- [Complete parallel for C# 5.0 async and await](https://github.com/petkaantonov/bluebird/blob/master/API.md#promisecoroutinegeneratorfunction-generatorfunction---function)
+- [Collection methods](https://github.com/petkaantonov/bluebird/blob/master/API.md#collections) such as All, any, some, settle, map, filter, reduce, spread, join, race...
+- [Practical debugging solutions](#error-handling) such as unhandled rejection reporting, typed catches, catching only what you expect and very long, relevant stack traces without losing perf
+- [Sick performance](https://github.com/petkaantonov/bluebird/tree/master/benchmark/stats)
+
+Passes [AP2](https://github.com/petkaantonov/bluebird/tree/master/test/mocha), [AP3](https://github.com/petkaantonov/bluebird/tree/master/test/mocha), [Cancellation](https://github.com/petkaantonov/bluebird/blob/master/test/mocha/cancel.js), [Progress](https://github.com/petkaantonov/bluebird/blob/master/test/mocha/q_progress.js) tests and more. See [testing](#testing).
+
+<hr>
+
+#Quick start
+
+##Node.js
+
+    npm install bluebird
+
+Then:
+
+```js
+var Promise = require("bluebird");
+```
+
+##Browsers
+
+Download the [bluebird.js](https://github.com/petkaantonov/bluebird/tree/master/js/browser) file. And then use a script tag:
+
+```html
+<script type="text/javascript" src="/scripts/bluebird.js"></script>
+```
+
+The global variable `Promise` becomes available after the above script tag.
+
+####Browser support
+
+Browsers that [implement ECMA-262, edition 3](http://en.wikipedia.org/wiki/Ecmascript#Implementations) and later are supported.
+
+[![Selenium Test Status](https://saucelabs.com/browser-matrix/petka_antonov.svg)](https://saucelabs.com/u/petka_antonov)
+
+*IE7 and IE8 had to be removed from tests due to SauceLabs bug but are supported and pass all tests*
+
+**Note** that in ECMA-262, edition 3 (IE7, IE8 etc) it is not possible to use methods that have keyword names like `.catch` and `.finally`. The [API documentation](https://github.com/petkaantonov/bluebird/blob/master/API.md) always lists a compatible alternative name that you can use if you need to support these browsers. For example `.catch` is replaced with `.caught` and `.finally` with `.lastly`.
+
+Also, [long stack trace](https://github.com/petkaantonov/bluebird/blob/master/API.md#promiselongstacktraces---void) support is only available in Chrome and Firefox.
+
+<sub>Previously bluebird required es5-shim.js and es5-sham.js to support Edition 3 - these are **no longer required** as of **0.10.4**.</sub>
+
+After quick start, see [API Reference and examples](https://github.com/petkaantonov/bluebird/blob/master/API.md)
+
+<hr>
+
+#What are promises and why should I use them?
+
+You should use promises to turn this:
+
+```js
+readFile("file.json", function(err, val) {
+    if( err ) {
+        console.error("unable to read file");
+    }
+    else {
+        try {
+            val = JSON.parse(val);
+            console.log(val.success);
+        }
+        catch( e ) {
+            console.error("invalid json in file");
+        }
+    }
+});
+```
+
+Into this:
+
+```js
+readFile("file.json").then(JSON.parse).then(function(val) {
+    console.log(val.success);
+})
+.catch(SyntaxError, function(e) {
+    console.error("invalid json in file");
+})
+.catch(function(e){
+    console.error("unable to read file")
+});
+```
+
+Actually you might notice the latter has a lot in common with code that would do the same using synchronous I/O:
+
+```js
+try {
+    var val = JSON.parse(readFile("file.json"));
+    console.log(val.success);
+}
+//Syntax actually not supported in JS but drives the point
+catch(SyntaxError e) {
+    console.error("invalid json in file");
+}
+catch(Error e) {
+    console.error("unable to read file")
+}
+```
+
+And that is the point - being able to have something that is a lot like `return` and `throw` in synchronous code.
+
+You can also use promises to improve code that was written with callback helpers:
+
+
+```js
+//Copyright Plato http://stackoverflow.com/a/19385911/995876
+//CC BY-SA 2.5
+mapSeries(URLs, function (URL, done) {
+    var options = {};
+    needle.get(URL, options, function (error, response, body) {
+        if (error) {
+            return done(error)
+        }
+        try {
+            var ret = JSON.parse(body);
+            return done(null, ret);
+        }
+        catch (e) {
+            done(e);
+        }
+    });
+}, function (err, results) {
+    if (err) {
+        console.log(err)
+    } else {
+        console.log('All Needle requests successful');
+        // results is a 1 to 1 mapping in order of URLs > needle.body
+        processAndSaveAllInDB(results, function (err) {
+            if (err) {
+                return done(err)
+            }
+            console.log('All Needle requests saved');
+            done(null);
+        });
+    }
+});
+```
+
+Is more pleasing to the eye when done with promises:
+
+```js
+Promise.promisifyAll(needle);
+var options = {};
+
+var current = Promise.resolve();
+Promise.map(URLs, function(URL) {
+    current = current.then(function () {
+        return needle.getAsync(URL, options);
+    });
+    return current;
+}).map(function(responseAndBody){
+    return JSON.parse(responseAndBody[1]);
+}).then(function (results) {
+    return processAndSaveAllInDB(results);
+}).then(function(){
+    console.log('All Needle requests saved');
+}).catch(function (e) {
+    console.log(e);
+});
+```
+
+Also promises don't just give you correspondences for synchronous features but can also be used as limited event emitters or callback aggregators.
+
+More reading:
+
+ - [Promise nuggets](http://spion.github.io/promise-nuggets/)
+ - [Why I am switching to promises](http://spion.github.io/posts/why-i-am-switching-to-promises.html)
+ - [What is the the point of promises](http://domenic.me/2012/10/14/youre-missing-the-point-of-promises/#toc_1)
+ - [Snippets for common problems](https://github.com/petkaantonov/bluebird/wiki/Snippets)
+ - [Promise anti-patterns](https://github.com/petkaantonov/bluebird/wiki/Promise-anti-patterns)
+
+#Questions and issues
+
+If you find a bug in bluebird or have a feature request, file an issue in the [github issue tracker](https://github.com/petkaantonov/bluebird/issues). Anything else, such as questions for help in using the library, should be posted in [StackOverflow](http://stackoverflow.com/questions/tagged/bluebird) under tags `promise` and `bluebird`.
+
+#Error handling
+
+This is a problem every promise library needs to handle in some way. Unhandled rejections/exceptions don't really have a good agreed-on asynchronous correspondence. The problem is that it is impossible to predict the future and know if a rejected promise will eventually be handled.
+
+There are two common pragmatic attempts at solving the problem that promise libraries do.
+
+The more popular one is to have the user explicitly communicate that they are done and any unhandled rejections should be thrown, like so:
+
+```js
+download().then(...).then(...).done();
+```
+
+For handling this problem, in my opinion, this is completely unacceptable and pointless. The user must remember to explicitly call `.done` and that cannot be justified when the problem is forgetting to create an error handler in the first place.
+
+The second approach, which is what bluebird by default takes, is to call a registered handler if a rejection is unhandled by the start of a second turn. The default handler is to write the stack trace to stderr or `console.error` in browsers. This is close to what happens with synchronous code - your code doens't work as expected and you open console and see a stack trace. Nice.
+
+Of course this is not perfect, if your code for some reason needs to swoop in and attach error handler to some promise after the promise has been hanging around a while then you will see annoying messages. In that case you can use the `.done()` method to signal that any hanging exceptions should be thrown.
+
+If you want to override the default handler for these possibly unhandled rejections, you can pass yours like so:
+
+```js
+Promise.onPossiblyUnhandledRejection(function(error){
+    throw error;
+});
+```
+
+If you want to also enable long stack traces, call:
+
+```js
+Promise.longStackTraces();
+```
+
+right after the library is loaded.
+
+In node.js use the environment flag `BLUEBIRD_DEBUG`:
+
+```
+BLUEBIRD_DEBUG=1 node server.js
+```
+
+to enable long stack traces in all instances of bluebird.
+
+Long stack traces cannot be disabled after being enabled, and cannot be enabled after promises have alread been created. Long stack traces imply a substantial performance penalty, even after using every trick to optimize them.
+
+Long stack traces are enabled by default in the debug build.
+
+####Expected and unexpected errors
+
+A practical problem with Promises/A+ is that it models Javascript `try-catch` too closely for its own good. Therefore by default promises inherit `try-catch` warts such as the inability to specify the error types that the catch block is eligible for. It is an anti-pattern in every other language to use catch-all handlers because they swallow exceptions that you might not know about.
+
+Now, Javascript does have a perfectly fine and working way of creating error type hierarchies. It is still quite awkward to use them with the built-in `try-catch` however:
+
+```js
+try {
+    //code
+}
+catch(e) {
+    if( e instanceof WhatIWantError) {
+        //handle
+    }
+    else {
+        throw e;
+    }
+}
+```
+
+Without such checking, unexpected errors would be silently swallowed. However, with promises, bluebird brings the future (hopefully) here now and extends the `.catch` to [accept potential error type eligibility](https://github.com/petkaantonov/bluebird/blob/master/API.md#catchfunction-errorclass-function-handler---promise).
+
+For instance here it is expected that some evil or incompetent entity will try to crash our server from `SyntaxError` by providing syntactically invalid JSON:
+
+```js
+getJSONFromSomewhere().then(function(jsonString) {
+    return JSON.parse(jsonString);
+}).then(function(object) {
+    console.log("it was valid json: ", object);
+}).catch(SyntaxError, function(e){
+    console.log("don't be evil");
+});
+```
+
+Here any kind of unexpected error will automatically reported on stderr along with a stack trace because we only register a handler for the expected `SyntaxError`.
+
+Ok, so, that's pretty neat. But actually not many libraries define error types and it is in fact a complete ghetto out there with ad hoc strings being attached as some arbitrary property name like `.name`, `.type`, `.code`, not having any property at all or even throwing strings as errors and so on. So how can we still listen for expected errors?
+
+Bluebird defines a special error type `RejectionError` (you can get a reference from `Promise.RejectionError`). This type of error is given as rejection reason by promisified methods when
+their underlying library gives an untyped, but expected error. Primitives such as strings, and error objects that are directly created like `new Error("database didn't respond")` are considered untyped.
+
+Example of such library is the node core library `fs`. So if we promisify it, we can catch just the errors we want pretty easily and have programmer errors be redirected to unhandled rejection handler so that we notice them:
+
+```js
+//Read more about promisification in the API Reference:
+//https://github.com/petkaantonov/bluebird/blob/master/API.md
+var fs = Promise.promisifyAll(require("fs"));
+
+fs.readFileAsync("myfile.json").then(JSON.parse).then(function (json) {
+    console.log("Successful json")
+}).catch(SyntaxError, function (e) {
+    console.error("file contains invalid json");
+}).catch(Promise.RejectionError, function (e) {
+    console.error("unable to read file, because: ", e.message);
+});
+```
+
+The last `catch` handler is only invoked when the `fs` module explicitly used the `err` argument convention of async callbacks to inform of an expected error. The `RejectionError` instance will contain the original error in its `.cause` property but it does have a direct copy of the `.message` and `.stack` too. In this code any unexpected error - be it in our code or the `fs` module - would not be caught by these handlers and therefore not swallowed.
+
+Since a `catch` handler typed to `Promise.RejectionError` is expected to be used very often, it has a neat shorthand:
+
+```js
+.error(function (e) {
+    console.error("unable to read file, because: ", e.message);
+});
+```
+
+See [API documentation for `.error()`](https://github.com/petkaantonov/bluebird/blob/master/API.md#error-rejectedhandler----promise)
+
+Finally, Bluebird also supports predicate-based filters. If you pass a
+predicate function instead of an error type, the predicate will receive
+the error as an argument. The return result will be used determine whether
+the error handler should be called.
+
+Predicates should allow for very fine grained control over caught errors:
+pattern matching, error typesets with set operations and many other techniques
+can be implemented on top of them.
+
+Example of using a predicate-based filter:
+
+```js
+var Promise = require("bluebird");
+var request = Promise.promisify(require("request"));
+
+function clientError(e) {
+    return e.code >= 400 && e.code < 500;
+}
+
+request("http://www.google.com").then(function(contents){
+    console.log(contents);
+}).catch(clientError, function(e){
+   //A client error like 400 Bad Request happened
+});
+```
+
+**Danger:** The JavaScript language allows throwing primitive values like strings. Throwing primitives can lead to worse or no stack traces. Primitives [are not exceptions](http://www.devthought.com/2011/12/22/a-string-is-not-an-error/). You should consider always throwing Error objects when handling exceptions.
+
+<hr>
+
+####How do long stack traces differ from e.g. Q?
+
+Bluebird attempts to have more elaborate traces. Consider:
+
+```js
+Error.stackTraceLimit = 25;
+Q.longStackSupport = true;
+Q().then(function outer() {
+    return Q().then(function inner() {
+        return Q().then(function evenMoreInner() {
+            a.b.c.d();
+        }).catch(function catcher(e){
+            console.error(e.stack);
+        });
+    })
+});
+```
+
+You will see
+
+    ReferenceError: a is not defined
+        at evenMoreInner (<anonymous>:7:13)
+    From previous event:
+        at inner (<anonymous>:6:20)
+
+Compare to:
+
+```js
+Error.stackTraceLimit = 25;
+Promise.longStackTraces();
+Promise.resolve().then(function outer() {
+    return Promise.resolve().then(function inner() {
+        return Promise.resolve().then(function evenMoreInner() {
+            a.b.c.d()
+        }).catch(function catcher(e){
+            console.error(e.stack);
+        });
+    });
+});
+```
+
+    ReferenceError: a is not defined
+        at evenMoreInner (<anonymous>:7:13)
+    From previous event:
+        at inner (<anonymous>:6:36)
+    From previous event:
+        at outer (<anonymous>:5:32)
+    From previous event:
+        at <anonymous>:4:21
+        at Object.InjectedScript._evaluateOn (<anonymous>:572:39)
+        at Object.InjectedScript._evaluateAndWrap (<anonymous>:531:52)
+        at Object.InjectedScript.evaluate (<anonymous>:450:21)
+
+
+A better and more practical example of the differences can be seen in gorgikosev's [debuggability competition](https://github.com/spion/async-compare#debuggability).
+
+<hr>
+
+####Can I use long stack traces in production?
+
+Probably yes. Bluebird uses multiple innovative techniques to optimize long stack traces. Even with long stack traces, it is still way faster than similarly featured implementations that don't have long stack traces enabled and about same speed as minimal implementations. A slowdown of 4-5x is expected, not 50x.
+
+What techniques are used?
+
+#####V8 API second argument
+
+This technique utilizes the [slightly under-documented](https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi#Stack_trace_collection_for_custom_exceptions) second argument of V8 `Error.captureStackTrace`. It turns out that the second argument can actually be used to make V8 skip all library internal stack frames [for free](https://github.com/v8/v8/blob/b5fabb9225e1eb1c20fd527b037e3f877296e52a/src/isolate.cc#L665). It only requires propagation of callers manually in library internals but this is not visible to you as user at all.
+
+Without this technique, every promise (well not every, see second technique) created would have to waste time creating and collecting library internal frames which will just be thrown away anyway. It also allows one to use smaller stack trace limits because skipped frames are not counted towards the limit whereas with collecting everything upfront and filtering afterwards would likely have to use higher limits to get more user stack frames in.
+
+#####Sharing stack traces
+
+Consider:
+
+```js
+function getSomethingAsync(fileName) {
+    return readFileAsync(fileName).then(function(){
+        //...
+    }).then(function() {
+        //...
+    }).then(function() {
+        //...
+    });
+}
+```
+
+Everytime you call this function it creates 4 promises and in a straight-forward long stack traces implementation it would collect 4 almost identical stack traces. Bluebird has a light weight internal data-structure (kcnown as context stack in the source code) to help tracking when traces can be re-used and this example would only collect one trace.
+
+#####Lazy formatting
+
+After a stack trace has been collected on an object, one must be careful not to reference the `.stack` property until necessary. Referencing the property causes
+an expensive format call and the stack property is turned into a string which uses much more memory.
+
+What about [Q #111](https://github.com/kriskowal/q/issues/111)?
+
+Long stack traces is not inherently the problem. For example with latest Q with stack traces disabled:
+
+```js
+var Q = require("q");
+
+
+function test(i){
+    if (i <= 0){
+       return Q.when('done')
+   } else {
+       return Q.when(i-1).then(test)
+   }
+}
+test(1000000000).then(function(output){console.log(output) });
+```
+
+After 2 minutes of running this, it will give:
+
+```js
+FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - process out of memory
+```
+
+So the problem with this is how much absolute memory is used per promise - not whether long traces are enabled or not.
+
+For some purpose, let's say 100000 parallel pending promises in memory at the same time is the maximum. You would then roughly use 100MB for them instead of 10MB with stack traces disabled.For comparison, just creating 100000 functions alone will use 14MB if they're closures. All numbers can be halved for 32-bit node.
+
+<hr>
+
+#Development
+
+For development tasks such as running benchmarks or testing, you need to clone the repository and install dev-dependencies.
+
+Install [node](http://nodejs.org/), [npm](https://npmjs.org/), and [grunt](http://gruntjs.com/).
+
+    git clone git@github.com:petkaantonov/bluebird.git
+    cd bluebird
+    npm install
+
+##Testing
+
+To run all tests, run `grunt test`. Note that 10 processes are created to run the tests in parallel. The stdout of tests is ignored by default and everything will stop at the first failure.
+
+Individual files can be run with `grunt test --run=filename` where `filename` is a test file name in `/test` folder or `/test/mocha` folder. The `.js` prefix is not needed. The dots for AP compliance tests are not needed, so to run `/test/mocha/2.3.3.js` for instance:
+
+    grunt test --run=233
+
+When trying to get a test to pass, run only that individual test file with `--verbose` to see the output from that test:
+
+    grunt test --run=233 --verbose
+
+The reason for the unusual way of testing is because the majority of tests are from different libraries using different testing frameworks and because it takes forever to test sequentially.
+
+
+###Testing in browsers
+
+To test in browsers:
+
+    cd browser
+    setup
+
+Then open the `index.html` in your browser. Requires bash (on windows the mingw32 that comes with git works fine too).
+
+You may also [visit the github hosted page](http://petkaantonov.github.io/bluebird/browser/).
+
+Keep the test tab active because some tests are timing-sensitive and will fail if the browser is throttling timeouts. Chrome will do this for example when the tab is not active.
+
+##Benchmarks
+
+To run a benchmark, run the given command for a benchmark while on the project root. Requires bash (on windows the mingw32 that comes with git works fine too).
+
+Node 0.11.2+ is required to run the generator examples.
+
+###1\. DoxBee sequential
+
+Currently the most relevant benchmark is @gorkikosev's benchmark in the article [Analysis of generators and other async patterns in node](http://spion.github.io/posts/analysis-generators-and-other-async-patterns-node.html). The benchmark emulates a situation where n amount of users are making a request in parallel to execute some mixed async/sync action. The benchmark has been modified to include a warm-up phase to minimize any JITing during timed sections.
+
+Command: `bench doxbee`
+
+###2\. Made-up parallel
+
+This made-up scenario runs 15 shimmed queries in parallel.
+
+Command: `bench parallel`
+
+##Custom builds
+
+Custom builds for browsers are supported through a command-line utility.
+
+
+
+
+<table>
+    <caption>The following features can be disabled</caption>
+    <thead>
+        <tr>
+            <th>Feature(s)</th>
+            <th>Command line identifier</th>
+        </tr>
+    </thead>
+    <tbody>
+
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#any---promise"><code>.any</code></a> and <a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#promiseanyarraydynamicpromise-values---promise"><code>Promise.any</code></a></td><td><code>any</code></td></tr>
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#race---promise"><code>.race</code></a> and <a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#promiseracearraypromise-promises---promise"><code>Promise.race</code></a></td><td><code>race</code></td></tr>
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#callstring-propertyname--dynamic-arg---promise"><code>.call</code></a> and <a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#getstring-propertyname---promise"><code>.get</code></a></td><td><code>call_get</code></td></tr>
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#filterfunction-filterer---promise"><code>.filter</code></a> and <a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#promisefilterarraydynamicpromise-values-function-filterer---promise"><code>Promise.filter</code></a></td><td><code>filter</code></td></tr>
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#mapfunction-mapper---promise"><code>.map</code></a> and <a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#promisemaparraydynamicpromise-values-function-mapper---promise"><code>Promise.map</code></a></td><td><code>map</code></td></tr>
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#reducefunction-reducer--dynamic-initialvalue---promise"><code>.reduce</code></a> and <a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#promisereducearraydynamicpromise-values-function-reducer--dynamic-initialvalue---promise"><code>Promise.reduce</code></a></td><td><code>reduce</code></td></tr>
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#props---promise"><code>.props</code></a> and <a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#promisepropsobjectpromise-object---promise"><code>Promise.props</code></a></td><td><code>props</code></td></tr>
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#settle---promise"><code>.settle</code></a> and <a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#promisesettlearraydynamicpromise-values---promise"><code>Promise.settle</code></a></td><td><code>settle</code></td></tr>
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#someint-count---promise"><code>.some</code></a> and <a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#promisesomearraydynamicpromise-values-int-count---promise"><code>Promise.some</code></a></td><td><code>some</code></td></tr>
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#nodeifyfunction-callback---promise"><code>.nodeify</code></a></td><td><code>nodeify</code></td></tr>
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#promisecoroutinegeneratorfunction-generatorfunction---function"><code>Promise.coroutine</code></a> and <a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#promisespawngeneratorfunction-generatorfunction---promise"><code>Promise.spawn</code></a></td><td><code>generators</code></td></tr>
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#progression">Progression</a></td><td><code>progress</code></td></tr>
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#promisification">Promisification</a></td><td><code>promisify</code></td></tr>
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#cancellation">Cancellation</a></td><td><code>cancel</code></td></tr>
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#synchronous-inspection">Synchronous inspection</a></td><td><code>synchronous_inspection</code></td></tr>
+        <tr><td><a href="https://github.com/petkaantonov/bluebird/blob/master/API.md#timers">Timers</a></td><td><code>timers</code></td></tr>
+
+    </tbody>
+</table>
+
+
+Make sure you have cloned the repo somewhere and did `npm install` successfully.
+
+After that you can run:
+
+    grunt build --features="core"
+
+
+The above builds the most minimal build you can get. You can add more features separated by spaces from the above list:
+
+    grunt build --features="core filter map reduce"
+
+The custom build file will be found from `/js/browser/bluebird.js`. It will have a comment that lists the disabled and enabled features.
+
+Note that the build leaves the `/js/main` etc folders with same features so if you use the folder for node.js at the same time, don't forget to build
+a full version afterwards (after having taken a copy of the bluebird.js somewhere):
+
+    grunt build
+
+<hr>
+
+##For library authors
+
+Building a library that depends on bluebird? You should know about a few features.
+
+If your library needs to do something obtrusive like adding or modifying methods on the `Promise` prototype, uses long stack traces or uses a custom unhandled rejection handler then... that's totally ok as long as you don't use `require("bluebird")`. Instead you should create a file
+that creates an isolated copy. For example, creating a file called `bluebird-extended.js` that contains:
+
+```js
+                //NOTE the function call right after
+module.exports = require("bluebird/js/main/promise")();
+```
+
+Your library can then use `var Promise = require("bluebird-extended");` and do whatever it wants with it. Then if the application or other library uses their own bluebird promises they will all play well together because of Promises/A+ thenable assimilation magic.
+
+You should also know about [`.nodeify()`](https://github.com/petkaantonov/bluebird/blob/master/API.md#nodeifyfunction-callback---promise) which makes it easy to provide a dual callback/promise API.
+
+<hr>
+
+##What is the sync build?
+
+You may now use sync build by:
+
+    var Promise = require("bluebird/zalgo");
+
+The sync build is provided to see how forced asynchronity affects benchmarks. It should not be used in real code due to the implied hazards.
+
+The normal async build gives Promises/A+ guarantees about asynchronous resolution of promises. Some people think this affects performance or just plain love their code having a possibility
+of stack overflow errors and non-deterministic behavior.
+
+The sync build skips the async call trampoline completely, e.g code like:
+
+    async.invoke( this.fn, this, val );
+
+Appears as this in the sync build:
+
+    this.fn(val);
+
+This should pressure the CPU slightly less and thus the sync build should perform better. Indeed it does, but only marginally. The biggest performance boosts are from writing efficient Javascript, not from compromising determinism.
+
+Note that while some benchmarks are waiting for the next event tick, the CPU is actually not in use during that time. So the resulting benchmark result is not completely accurate because on node.js you only care about how much the CPU is taxed. Any time spent on CPU is time the whole process (or server) is paralyzed. And it is not graceful like it would be with threads.
+
+
+```js
+var cache = new Map(); //ES6 Map or DataStructures/Map or whatever...
+function getResult(url) {
+    var resolver = Promise.pending();
+    if (cache.has(url)) {
+        resolver.resolve(cache.get(url));
+    }
+    else {
+        http.get(url, function(err, content) {
+            if (err) resolver.reject(err);
+            else {
+                cache.set(url, content);
+                resolver.resolve(content);
+            }
+        });
+    }
+    return resolver.promise;
+}
+
+
+
+//The result of console.log is truly random without async guarantees
+function guessWhatItPrints( url ) {
+    var i = 3;
+    getResult(url).then(function(){
+        i = 4;
+    });
+    console.log(i);
+}
+```
+
+#Optimization guide
+
+Articles about optimization will be periodically posted in [the wiki section](https://github.com/petkaantonov/bluebird/wiki), polishing edits are welcome.
+
+A single cohesive guide compiled from the articles will probably be done eventually.
+
+#License
+
+Copyright (c) 2014 Petka Antonov
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:</p>
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/benchmark/package.json b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/benchmark/package.json
new file mode 100644
index 0000000..19cefed
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/benchmark/package.json
@@ -0,0 +1,34 @@
+{
+  "name": "async-compare",
+  "version": "0.1.1",
+  "description": "Compare the performance and code of multiple async patterns",
+  "main": "perf.js",
+  "dependencies": {
+    "async": "~0.2.9",
+    "deferred": "~0.6.6",
+    "kew": "~0.3.0",
+    "liar": "~0.6.0",
+    "optimist": "~0.6.0",
+    "q": "~1.0.0",
+    "rsvp": "~3.0.0",
+    "text-table": "~0.2.0",
+    "when": "~3.0.0",
+    "vow": "~0.4.1",
+    "davy": "~0.2.0"
+  },
+  "devDependencies": {},
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "keywords": [
+    "generators",
+    "fibers",
+    "promises",
+    "callbacks",
+    "comparison",
+    "compare",
+    "async"
+  ],
+  "author": "spion",
+  "license": "MIT"
+}
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/bower.json b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/bower.json
new file mode 100644
index 0000000..396073a
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/bower.json
@@ -0,0 +1,35 @@
+{
+  "name": "bluebird",
+  "version": "1.0.0",
+  "homepage": "https://github.com/petkaantonov/bluebird",
+  "authors": [
+    "Petka Antonov <petka_antonov@hotmail.com>"
+  ],
+  "description": "Bluebird is a full featured promise library with unmatched performance.",
+  "main": "js/browser/bluebird.js",
+  "license": "MIT",
+  "ignore": [
+    "**/.*",
+    "benchmark",
+    "bower_components",
+    "./browser",
+    "js/zalgo",
+    "node_modules",
+    "test"
+  ],
+  "keywords": [
+    "promise",
+    "performance",
+    "promises",
+    "promises-a",
+    "promises-aplus",
+    "async",
+    "await",
+    "deferred",
+    "deferreds",
+    "future",
+    "flow control",
+    "dsl",
+    "fluent interface"
+  ]
+}
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/bundle.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/bundle.js
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/bundle.js
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/changelog.md b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/changelog.md
new file mode 100644
index 0000000..9ae6be2
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/changelog.md
@@ -0,0 +1,1135 @@
+## 1.1.1 (2014-03-18)
+
+Bugfixes:
+
+ - [#138](https://github.com/petkaantonov/bluebird/issues/138)
+ - [#144](https://github.com/petkaantonov/bluebird/issues/144)
+ - [#148](https://github.com/petkaantonov/bluebird/issues/148)
+ - [#151](https://github.com/petkaantonov/bluebird/issues/151)
+
+## 1.1.0 (2014-03-08)
+
+Features:
+
+ - Implement [`Promise.prototype.tap()`](https://github.com/petkaantonov/bluebird/blob/master/API.md#tapfunction-handler---promise)
+ - Implement [`Promise.coroutine.addYieldHandler()`](https://github.com/petkaantonov/bluebird/blob/master/API.md#promisecoroutineaddyieldhandlerfunction-handler---void)
+ - Deprecate `Promise.prototype.spawn`
+
+Bugfixes:
+
+ - Fix already rejected promises being reported as unhandled when handled through collection methods
+ - Fix browserisfy crashing from checking `process.version.indexOf`
+
+## 1.0.8 (2014-03-03)
+
+Bugfixes:
+
+ - Fix active domain being lost across asynchronous boundaries in Node.JS 10.xx
+
+## 1.0.7 (2014-02-25)
+
+Bugfixes:
+
+ - Fix handled errors being reported
+
+## 1.0.6 (2014-02-17)
+
+Bugfixes:
+
+ -  Fix bug with unhandled rejections not being reported
+    when using `Promise.try` or `Promise.method` without
+    attaching further handlers
+
+## 1.0.5 (2014-02-15)
+
+Features:
+
+ - Node.js performance: promisified functions try to check amount of passed arguments in most optimal order
+ - Node.js promisified functions will have same `.length` as the original function minus one (for the callback parameter)
+
+## 1.0.4 (2014-02-09)
+
+Features:
+
+ - Possibly unhandled rejection handler will always get a stack trace, even if the rejection or thrown error was not an error
+ - Unhandled rejections are tracked per promise, not per error. So if you create multiple branches from a single ancestor and that ancestor gets rejected, each branch with no error handler with the end will cause a possibly unhandled rejection handler invocation
+
+Bugfixes:
+
+ - Fix unhandled non-writable objects or primitives not reported by possibly unhandled rejection handler
+
+## 1.0.3 (2014-02-05)
+
+Bugfixes:
+
+ - [#93](https://github.com/petkaantonov/bluebird/issues/88)
+
+## 1.0.2 (2014-02-04)
+
+Features:
+
+ - Significantly improve performance of foreign bluebird thenables
+
+Bugfixes:
+
+ - [#88](https://github.com/petkaantonov/bluebird/issues/88)
+
+## 1.0.1 (2014-01-28)
+
+Features:
+
+ - Error objects that have property `.isAsync = true` will now be caught by `.error()`
+
+Bugfixes:
+
+ - Fix TypeError and RangeError shims not working without `new` operator
+
+## 1.0.0 (2014-01-12)
+
+Features:
+
+ - `.filter`, `.map`, and `.reduce` no longer skip sparse array holes. This is a backwards incompatible change.
+ - Like `.map` and `.filter`, `.reduce` now allows returning promises and thenables from the iteration function.
+
+Bugfixes:
+
+ - [#58](https://github.com/petkaantonov/bluebird/issues/58)
+ - [#61](https://github.com/petkaantonov/bluebird/issues/61)
+ - [#64](https://github.com/petkaantonov/bluebird/issues/64)
+ - [#60](https://github.com/petkaantonov/bluebird/issues/60)
+
+## 0.11.6-1 (2013-12-29)
+
+## 0.11.6-0 (2013-12-29)
+
+Features:
+
+ - You may now return promises and thenables from the filterer function used in `Promise.filter` and `Promise.prototype.filter`.
+
+ - `.error()` now catches additional sources of rejections:
+
+    - Rejections originating from `Promise.reject`
+
+    - Rejections originating from thenables using
+    the `reject` callback
+
+    - Rejections originating from promisified callbacks
+    which use the `errback` argument
+
+    - Rejections originating from `new Promise` constructor
+    where the `reject` callback is called explicitly
+
+    - Rejections originating from `PromiseResolver` where
+    `.reject()` method is called explicitly
+
+Bugfixes:
+
+ - Fix `captureStackTrace` being called when it was `null`
+ - Fix `Promise.map` not unwrapping thenables
+
+## 0.11.5-1 (2013-12-15)
+
+## 0.11.5-0 (2013-12-03)
+
+Features:
+
+ - Improve performance of collection methods
+ - Improve performance of promise chains
+
+## 0.11.4-1 (2013-12-02)
+
+## 0.11.4-0 (2013-12-02)
+
+Bugfixes:
+
+ - Fix `Promise.some` behavior with arguments like negative integers, 0...
+ - Fix stack traces of synchronously throwing promisified functions'
+
+## 0.11.3-0 (2013-12-02)
+
+Features:
+
+ - Improve performance of generators
+
+Bugfixes:
+
+ - Fix critical bug with collection methods.
+
+## 0.11.2-0 (2013-12-02)
+
+Features:
+
+ - Improve performance of all collection methods
+
+## 0.11.1-0 (2013-12-02)
+
+Features:
+
+- Improve overall performance.
+- Improve performance of promisified functions.
+- Improve performance of catch filters.
+- Improve performance of .finally.
+
+Bugfixes:
+
+- Fix `.finally()` rejecting if passed non-function. It will now ignore non-functions like `.then`.
+- Fix `.finally()` not converting thenables returned from the handler to promises.
+- `.spread()` now rejects if the ultimate value given to it is not spreadable.
+
+## 0.11.0-0 (2013-12-02)
+
+Features:
+
+ - Improve overall performance when not using `.bind()` or cancellation.
+ - Promises are now not cancellable by default. This is backwards incompatible change - see [`.cancellable()`](https://github.com/petkaantonov/bluebird/blob/master/API.md#cancellable---promise)
+ - [`Promise.delay`](https://github.com/petkaantonov/bluebird/blob/master/API.md#promisedelaydynamic-value-int-ms---promise)
+ - [`.delay()`](https://github.com/petkaantonov/bluebird/blob/master/API.md#delayint-ms---promise)
+ - [`.timeout()`](https://github.com/petkaantonov/bluebird/blob/master/API.md#timeoutint-ms--string-message---promise)
+
+## 0.10.14-0 (2013-12-01)
+
+Bugfixes:
+
+ - Fix race condition when mixing 3rd party asynchrony.
+
+## 0.10.13-1 (2013-11-30)
+
+## 0.10.13-0 (2013-11-30)
+
+Bugfixes:
+
+ - Fix another bug with progression.
+
+## 0.10.12-0 (2013-11-30)
+
+Bugfixes:
+
+ - Fix bug with progression.
+
+## 0.10.11-4 (2013-11-29)
+
+## 0.10.11-2 (2013-11-29)
+
+Bugfixes:
+
+ - Fix `.race()` not propagating bound values.
+
+## 0.10.11-1 (2013-11-29)
+
+Features:
+
+ - Improve performance of `Promise.race`
+
+## 0.10.11-0 (2013-11-29)
+
+Bugfixes:
+
+ - Fixed `Promise.promisifyAll` invoking property accessors. Only data properties with function values are considered.
+
+## 0.10.10-0 (2013-11-28)
+
+Features:
+
+ - Disable long stack traces in browsers by default. Call `Promise.longStackTraces()` to enable them.
+
+## 0.10.9-1 (2013-11-27)
+
+Bugfixes:
+
+ - Fail early when `new Promise` is constructed incorrectly
+
+## 0.10.9-0 (2013-11-27)
+
+Bugfixes:
+
+ - Promise.props now takes a [thenable-for-collection](https://github.com/petkaantonov/bluebird/blob/f41edac61b7c421608ff439bb5a09b7cffeadcf9/test/mocha/props.js#L197-L217)
+ - All promise collection methods now reject when a promise-or-thenable-for-collection turns out not to give a collection
+
+## 0.10.8-0 (2013-11-25)
+
+Features:
+
+ - All static collection methods take thenable-for-collection
+
+## 0.10.7-0 (2013-11-25)
+
+Features:
+
+ - throw TypeError when thenable resolves with itself
+ - Make .race() and Promise.race() forever pending on empty collections
+
+## 0.10.6-0 (2013-11-25)
+
+Bugfixes:
+
+ - Promise.resolve and PromiseResolver.resolve follow thenables too.
+
+## 0.10.5-0 (2013-11-24)
+
+Bugfixes:
+
+ - Fix infinite loop when thenable resolves with itself
+
+## 0.10.4-1 (2013-11-24)
+
+Bugfixes:
+
+ - Fix a file missing from build. (Critical fix)
+
+## 0.10.4-0 (2013-11-24)
+
+Features:
+
+ - Remove dependency of es5-shim and es5-sham when using ES3.
+
+## 0.10.3-0 (2013-11-24)
+
+Features:
+
+ - Improve performance of `Promise.method`
+
+## 0.10.2-1 (2013-11-24)
+
+Features:
+
+ - Rename PromiseResolver#asCallback to PromiseResolver#callback
+
+## 0.10.2-0 (2013-11-24)
+
+Features:
+
+ - Remove memoization of thenables
+
+## 0.10.1-0 (2013-11-21)
+
+Features:
+
+ - Add methods `Promise.resolve()`, `Promise.reject()`, `Promise.defer()` and `.resolve()`.
+
+## 0.10.0-1 (2013-11-17)
+
+## 0.10.0-0 (2013-11-17)
+
+Features:
+
+ - Implement `Promise.method()`
+ - Implement `.return()`
+ - Implement `.throw()`
+
+Bugfixes:
+
+ - Fix promises being able to use themselves as resolution or follower value
+
+## 0.9.11-1 (2013-11-14)
+
+Features:
+
+ - Implicit `Promise.all()` when yielding an array from generators
+
+## 0.9.11-0 (2013-11-13)
+
+Bugfixes:
+
+ - Fix `.spread` not unwrapping thenables
+
+## 0.9.10-2 (2013-11-13)
+
+Features:
+
+ - Improve performance of promisified functions on V8
+
+Bugfixes:
+
+ - Report unhandled rejections even when long stack traces are disabled
+ - Fix `.error()` showing up in stack traces
+
+## 0.9.10-1 (2013-11-05)
+
+Bugfixes:
+
+ - Catch filter method calls showing in stack traces
+
+## 0.9.10-0 (2013-11-05)
+
+Bugfixes:
+
+ - Support primitives in catch filters
+
+## 0.9.9-0 (2013-11-05)
+
+Features:
+
+ - Add `Promise.race()` and `.race()`
+
+## 0.9.8-0 (2013-11-01)
+
+Bugfixes:
+
+ - Fix bug with `Promise.try` not unwrapping returned promises and thenables
+
+## 0.9.7-0 (2013-10-29)
+
+Bugfixes:
+
+ - Fix bug with build files containing duplicated code for promise.js
+
+## 0.9.6-0 (2013-10-28)
+
+Features:
+
+ - Improve output of reporting unhandled non-errors
+ - Implement RejectionError wrapping and `.error()` method
+
+## 0.9.5-0 (2013-10-27)
+
+Features:
+
+ - Allow fresh copies of the library to be made
+
+## 0.9.4-1 (2013-10-27)
+
+## 0.9.4-0 (2013-10-27)
+
+Bugfixes:
+
+ - Rollback non-working multiple fresh copies feature
+
+## 0.9.3-0 (2013-10-27)
+
+Features:
+
+ - Allow fresh copies of the library to be made
+ - Add more components to customized builds
+
+## 0.9.2-1 (2013-10-25)
+
+## 0.9.2-0 (2013-10-25)
+
+Features:
+
+ - Allow custom builds
+
+## 0.9.1-1 (2013-10-22)
+
+Bugfixes:
+
+ - Fix unhandled rethrown exceptions not reported
+
+## 0.9.1-0 (2013-10-22)
+
+Features:
+
+ - Improve performance of `Promise.try`
+ - Extend `Promise.try` to accept arguments and ctx to make it more usable in promisification of synchronous functions.
+
+## 0.9.0-0 (2013-10-18)
+
+Features:
+
+ - Implement `.bind` and `Promise.bind`
+
+Bugfixes:
+
+ - Fix `.some()` when argument is a pending promise that later resolves to an array
+
+## 0.8.5-1 (2013-10-17)
+
+Features:
+
+ - Enable process wide long stack traces through BLUEBIRD_DEBUG environment variable
+
+## 0.8.5-0 (2013-10-16)
+
+Features:
+
+ - Improve performance of all collection methods
+
+Bugfixes:
+
+ - Fix .finally passing the value to handlers
+ - Remove kew from benchmarks due to bugs in the library breaking the benchmark
+ - Fix some bluebird library calls potentially appearing in stack traces
+
+## 0.8.4-1 (2013-10-15)
+
+Bugfixes:
+
+ - Fix .pending() call showing in long stack traces
+
+## 0.8.4-0 (2013-10-15)
+
+Bugfixes:
+
+ - Fix PromiseArray and its sub-classes swallowing possibly unhandled rejections
+
+## 0.8.3-3 (2013-10-14)
+
+Bugfixes:
+
+ - Fix AMD-declaration using named module.
+
+## 0.8.3-2 (2013-10-14)
+
+Features:
+
+ - The mortals that can handle it may now release Zalgo by `require("bluebird/zalgo");`
+
+## 0.8.3-1 (2013-10-14)
+
+Bugfixes:
+
+ - Fix memory leak when using the same promise to attach handlers over and over again
+
+## 0.8.3-0 (2013-10-13)
+
+Features:
+
+ - Add `Promise.props()` and `Promise.prototype.props()`. They work like `.all()` for object properties.
+
+Bugfixes:
+
+ - Fix bug with .some returning garbage when sparse arrays have rejections
+
+## 0.8.2-2 (2013-10-13)
+
+Features:
+
+ - Improve performance of `.reduce()` when `initialValue` can be synchronously cast to a value
+
+## 0.8.2-1 (2013-10-12)
+
+Bugfixes:
+
+ - Fix .npmignore having irrelevant files
+
+## 0.8.2-0 (2013-10-12)
+
+Features:
+
+ - Improve performance of `.some()`
+
+## 0.8.1-0 (2013-10-11)
+
+Bugfixes:
+
+ - Remove uses of dynamic evaluation (`new Function`, `eval` etc) when strictly not necessary. Use feature detection to use static evaluation to avoid errors when dynamic evaluation is prohibited.
+
+## 0.8.0-3 (2013-10-10)
+
+Features:
+
+ - Add `.asCallback` property to `PromiseResolver`s
+
+## 0.8.0-2 (2013-10-10)
+
+## 0.8.0-1 (2013-10-09)
+
+Features:
+
+ - Improve overall performance. Be able to sustain infinite recursion when using promises.
+
+## 0.8.0-0 (2013-10-09)
+
+Bugfixes:
+
+ - Fix stackoverflow error when function calls itself "synchronously" from a promise handler
+
+## 0.7.12-2 (2013-10-09)
+
+Bugfixes:
+
+ - Fix safari 6 not using `MutationObserver` as a scheduler
+ - Fix process exceptions interfering with internal queue flushing
+
+## 0.7.12-1 (2013-10-09)
+
+Bugfixes:
+
+ - Don't try to detect if generators are available to allow shims to be used
+
+## 0.7.12-0 (2013-10-08)
+
+Features:
+
+ - Promisification now consider all functions on the object and its prototype chain
+ - Individual promisifcation uses current `this` if no explicit receiver is given
+ - Give better stack traces when promisified callbacks throw or errback primitives such as strings by wrapping them in an `Error` object.
+
+Bugfixes:
+
+ - Fix runtime APIs throwing synchronous errors
+
+## 0.7.11-0 (2013-10-08)
+
+Features:
+
+ - Deprecate `Promise.promisify(Object target)` in favor of `Promise.promisifyAll(Object target)` to avoid confusion with function objects
+ - Coroutines now throw error when a non-promise is `yielded`
+
+## 0.7.10-1 (2013-10-05)
+
+Features:
+
+ - Make tests pass Internet Explorer 8
+
+## 0.7.10-0 (2013-10-05)
+
+Features:
+
+ - Create browser tests
+
+## 0.7.9-1 (2013-10-03)
+
+Bugfixes:
+
+ - Fix promise cast bug when thenable fulfills using itself as the fulfillment value
+
+## 0.7.9-0 (2013-10-03)
+
+Features:
+
+ - More performance improvements when long stack traces are enabled
+
+## 0.7.8-1 (2013-10-02)
+
+Features:
+
+ - Performance improvements when long stack traces are enabled
+
+## 0.7.8-0 (2013-10-02)
+
+Bugfixes:
+
+ - Fix promisified methods not turning synchronous exceptions into rejections
+
+## 0.7.7-1 (2013-10-02)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.7-0 (2013-10-01)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.6-0 (2013-09-29)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.5-0 (2013-09-28)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.4-1 (2013-09-28)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.4-0 (2013-09-28)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.3-1 (2013-09-28)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.3-0 (2013-09-27)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.2-0 (2013-09-27)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.1-5 (2013-09-26)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.1-4 (2013-09-25)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.1-3 (2013-09-25)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.1-2 (2013-09-24)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.1-1 (2013-09-24)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.1-0 (2013-09-24)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.0-1 (2013-09-23)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.7.0-0 (2013-09-23)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.6.5-2 (2013-09-20)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.6.5-1 (2013-09-18)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.6.5-0 (2013-09-18)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.6.4-1 (2013-09-18)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.6.4-0 (2013-09-18)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.6.3-4 (2013-09-18)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.6.3-3 (2013-09-18)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.6.3-2 (2013-09-16)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.6.3-1 (2013-09-16)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.6.3-0 (2013-09-15)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.6.2-1 (2013-09-14)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.6.2-0 (2013-09-14)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.6.1-0 (2013-09-14)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.6.0-0 (2013-09-13)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.9-6 (2013-09-12)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.9-5 (2013-09-12)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.9-4 (2013-09-12)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.9-3 (2013-09-11)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.9-2 (2013-09-11)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.9-1 (2013-09-11)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.9-0 (2013-09-11)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.8-1 (2013-09-11)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.8-0 (2013-09-11)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.7-0 (2013-09-11)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.6-1 (2013-09-10)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.6-0 (2013-09-10)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.5-1 (2013-09-10)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.5-0 (2013-09-09)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.4-1 (2013-09-08)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.4-0 (2013-09-08)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.3-0 (2013-09-07)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.2-0 (2013-09-07)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.1-0 (2013-09-07)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.5.0-0 (2013-09-07)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.4.0-0 (2013-09-06)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.3.0-1 (2013-09-06)
+
+Features:
+
+ - feature
+
+Bugfixes:
+
+ - bugfix
+
+## 0.3.0 (2013-09-06)
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/any.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/any.js
new file mode 100644
index 0000000..8d174cf
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/any.js
@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, Promise$_CreatePromiseArray, PromiseArray) {
+
+    var SomePromiseArray = require("./some_promise_array.js")(PromiseArray);
+    function Promise$_Any(promises, useBound, caller) {
+        var ret = Promise$_CreatePromiseArray(
+            promises,
+            SomePromiseArray,
+            caller,
+            useBound === true && promises._isBound()
+                ? promises._boundTo
+                : void 0
+       );
+        var promise = ret.promise();
+        if (promise.isRejected()) {
+            return promise;
+        }
+        ret.setHowMany(1);
+        ret.setUnwrap();
+        ret.init();
+        return promise;
+    }
+
+    Promise.any = function Promise$Any(promises) {
+        return Promise$_Any(promises, false, Promise.any);
+    };
+
+    Promise.prototype.any = function Promise$any() {
+        return Promise$_Any(this, true, this.any);
+    };
+
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/assert.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/assert.js
new file mode 100644
index 0000000..4adb8c2
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/assert.js
@@ -0,0 +1,83 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = (function(){
+    var AssertionError = (function() {
+        function AssertionError(a) {
+            this.constructor$(a);
+            this.message = a;
+            this.name = "AssertionError";
+        }
+        AssertionError.prototype = new Error();
+        AssertionError.prototype.constructor = AssertionError;
+        AssertionError.prototype.constructor$ = Error;
+        return AssertionError;
+    })();
+
+    function getParams(args) {
+        var params = [];
+        for (var i = 0; i < args.length; ++i) params.push("arg" + i);
+        return params;
+    }
+
+    function nativeAssert(callName, args, expect) {
+        try {
+            var params = getParams(args);
+            var constructorArgs = params;
+            constructorArgs.push("return " +
+                    callName + "("+ params.join(",") + ");");
+            var fn = Function.apply(null, constructorArgs);
+            return fn.apply(null, args);
+        }
+        catch (e) {
+            if (!(e instanceof SyntaxError)) {
+                throw e;
+            }
+            else {
+                return expect;
+            }
+        }
+    }
+
+    return function assert(boolExpr, message) {
+        if (boolExpr === true) return;
+
+        if (typeof boolExpr === "string" &&
+            boolExpr.charAt(0) === "%") {
+            var nativeCallName = boolExpr;
+            var $_len = arguments.length;var args = new Array($_len - 2); for(var $_i = 2; $_i < $_len; ++$_i) {args[$_i - 2] = arguments[$_i];}
+            if (nativeAssert(nativeCallName, args, message) === message) return;
+            message = (nativeCallName + " !== " + message);
+        }
+
+        var ret = new AssertionError(message);
+        if (Error.captureStackTrace) {
+            Error.captureStackTrace(ret, assert);
+        }
+        if (console && console.error) {
+            console.error(ret.stack + "");
+        }
+        throw ret;
+
+    };
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/async.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/async.js
new file mode 100644
index 0000000..6f32b10
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/async.js
@@ -0,0 +1,111 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var schedule = require("./schedule.js");
+var Queue = require("./queue.js");
+var errorObj = require("./util.js").errorObj;
+var tryCatch1 = require("./util.js").tryCatch1;
+var process = require("./global.js").process;
+
+function Async() {
+    this._isTickUsed = false;
+    this._length = 0;
+    this._lateBuffer = new Queue();
+    this._functionBuffer = new Queue(25000 * 3);
+    var self = this;
+    this.consumeFunctionBuffer = function Async$consumeFunctionBuffer() {
+        self._consumeFunctionBuffer();
+    };
+}
+
+Async.prototype.haveItemsQueued = function Async$haveItemsQueued() {
+    return this._length > 0;
+};
+
+Async.prototype.invokeLater = function Async$invokeLater(fn, receiver, arg) {
+    if (process !== void 0 &&
+        process.domain != null &&
+        !fn.domain) {
+        fn = process.domain.bind(fn);
+    }
+    this._lateBuffer.push(fn, receiver, arg);
+    this._queueTick();
+};
+
+Async.prototype.invoke = function Async$invoke(fn, receiver, arg) {
+    if (process !== void 0 &&
+        process.domain != null &&
+        !fn.domain) {
+        fn = process.domain.bind(fn);
+    }
+    var functionBuffer = this._functionBuffer;
+    functionBuffer.push(fn, receiver, arg);
+    this._length = functionBuffer.length();
+    this._queueTick();
+};
+
+Async.prototype._consumeFunctionBuffer =
+function Async$_consumeFunctionBuffer() {
+    var functionBuffer = this._functionBuffer;
+    while(functionBuffer.length() > 0) {
+        var fn = functionBuffer.shift();
+        var receiver = functionBuffer.shift();
+        var arg = functionBuffer.shift();
+        fn.call(receiver, arg);
+    }
+    this._reset();
+    this._consumeLateBuffer();
+};
+
+Async.prototype._consumeLateBuffer = function Async$_consumeLateBuffer() {
+    var buffer = this._lateBuffer;
+    while(buffer.length() > 0) {
+        var fn = buffer.shift();
+        var receiver = buffer.shift();
+        var arg = buffer.shift();
+        var res = tryCatch1(fn, receiver, arg);
+        if (res === errorObj) {
+            this._queueTick();
+            if (fn.domain != null) {
+                fn.domain.emit("error", res.e);
+            }
+            else {
+                throw res.e;
+            }
+        }
+    }
+};
+
+Async.prototype._queueTick = function Async$_queue() {
+    if (!this._isTickUsed) {
+        schedule(this.consumeFunctionBuffer);
+        this._isTickUsed = true;
+    }
+};
+
+Async.prototype._reset = function Async$_reset() {
+    this._isTickUsed = false;
+    this._length = 0;
+};
+
+module.exports = new Async();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/bluebird.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/bluebird.js
new file mode 100644
index 0000000..6fd85f1
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/bluebird.js
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var Promise = require("./promise.js")();
+module.exports = Promise;
\ No newline at end of file
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/call_get.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/call_get.js
new file mode 100644
index 0000000..2a3c1f5
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/call_get.js
@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise) {
+    Promise.prototype.call = function Promise$call(propertyName) {
+        var $_len = arguments.length;var args = new Array($_len - 1); for(var $_i = 1; $_i < $_len; ++$_i) {args[$_i - 1] = arguments[$_i];}
+
+        return this._then(function(obj) {
+                return obj[propertyName].apply(obj, args);
+            },
+            void 0,
+            void 0,
+            void 0,
+            void 0,
+            this.call
+       );
+    };
+
+    function Promise$getter(obj) {
+        var prop = typeof this === "string"
+            ? this
+            : ("" + this);
+        return obj[prop];
+    }
+    Promise.prototype.get = function Promise$get(propertyName) {
+        return this._then(
+            Promise$getter,
+            void 0,
+            void 0,
+            propertyName,
+            void 0,
+            this.get
+       );
+    };
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/cancel.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/cancel.js
new file mode 100644
index 0000000..542b488
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/cancel.js
@@ -0,0 +1,77 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, INTERNAL) {
+    var errors = require("./errors.js");
+    var async = require("./async.js");
+    var CancellationError = errors.CancellationError;
+    var SYNC_TOKEN = {};
+
+    Promise.prototype._cancel = function Promise$_cancel() {
+        if (!this.isCancellable()) return this;
+        var parent;
+        if ((parent = this._cancellationParent) !== void 0) {
+            parent.cancel(SYNC_TOKEN);
+            return;
+        }
+        var err = new CancellationError();
+        this._attachExtraTrace(err);
+        this._rejectUnchecked(err);
+    };
+
+    Promise.prototype.cancel = function Promise$cancel(token) {
+        if (!this.isCancellable()) return this;
+        if (token === SYNC_TOKEN) {
+            this._cancel();
+            return this;
+        }
+        async.invokeLater(this._cancel, this, void 0);
+        return this;
+    };
+
+    Promise.prototype.cancellable = function Promise$cancellable() {
+        if (this._cancellable()) return this;
+        this._setCancellable();
+        this._cancellationParent = void 0;
+        return this;
+    };
+
+    Promise.prototype.uncancellable = function Promise$uncancellable() {
+        var ret = new Promise(INTERNAL);
+        ret._setTrace(this.uncancellable, this);
+        ret._follow(this);
+        ret._unsetCancellable();
+        if (this._isBound()) ret._setBoundTo(this._boundTo);
+        return ret;
+    };
+
+    Promise.prototype.fork =
+    function Promise$fork(didFulfill, didReject, didProgress) {
+        var ret = this._then(didFulfill, didReject, didProgress,
+            void 0, void 0, this.fork);
+
+        ret._setCancellable();
+        ret._cancellationParent = void 0;
+        return ret;
+    };
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/captured_trace.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/captured_trace.js
new file mode 100644
index 0000000..af34f11
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/captured_trace.js
@@ -0,0 +1,237 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function() {
+var inherits = require("./util.js").inherits;
+var defineProperty = require("./es5.js").defineProperty;
+
+var rignore = new RegExp(
+    "\\b(?:[\\w.]*Promise(?:Array|Spawn)?\\$_\\w+|" +
+    "tryCatch(?:1|2|Apply)|new \\w*PromiseArray|" +
+    "\\w*PromiseArray\\.\\w*PromiseArray|" +
+    "setTimeout|CatchFilter\\$_\\w+|makeNodePromisified|processImmediate|" +
+    "process._tickCallback|nextTick|Async\\$\\w+)\\b"
+);
+
+var rtraceline = null;
+var formatStack = null;
+var areNamesMangled = false;
+
+function formatNonError(obj) {
+    var str;
+    if (typeof obj === "function") {
+        str = "[function " +
+            (obj.name || "anonymous") +
+            "]";
+    }
+    else {
+        str = obj.toString();
+        var ruselessToString = /\[object [a-zA-Z0-9$_]+\]/;
+        if (ruselessToString.test(str)) {
+            try {
+                var newStr = JSON.stringify(obj);
+                str = newStr;
+            }
+            catch(e) {
+
+            }
+        }
+        if (str.length === 0) {
+            str = "(empty array)";
+        }
+    }
+    return ("(<" + snip(str) + ">, no stack trace)");
+}
+
+function snip(str) {
+    var maxChars = 41;
+    if (str.length < maxChars) {
+        return str;
+    }
+    return str.substr(0, maxChars - 3) + "...";
+}
+
+function CapturedTrace(ignoreUntil, isTopLevel) {
+    if (!areNamesMangled) {
+    }
+    this.captureStackTrace(ignoreUntil, isTopLevel);
+
+}
+inherits(CapturedTrace, Error);
+
+CapturedTrace.prototype.captureStackTrace =
+function CapturedTrace$captureStackTrace(ignoreUntil, isTopLevel) {
+    captureStackTrace(this, ignoreUntil, isTopLevel);
+};
+
+CapturedTrace.possiblyUnhandledRejection =
+function CapturedTrace$PossiblyUnhandledRejection(reason) {
+    if (typeof console === "object") {
+        var message;
+        if (typeof reason === "object" || typeof reason === "function") {
+            var stack = reason.stack;
+            message = "Possibly unhandled " + formatStack(stack, reason);
+        }
+        else {
+            message = "Possibly unhandled " + String(reason);
+        }
+        if (typeof console.error === "function" ||
+            typeof console.error === "object") {
+            console.error(message);
+        }
+        else if (typeof console.log === "function" ||
+            typeof console.error === "object") {
+            console.log(message);
+        }
+    }
+};
+
+areNamesMangled = CapturedTrace.prototype.captureStackTrace.name !==
+    "CapturedTrace$captureStackTrace";
+
+CapturedTrace.combine = function CapturedTrace$Combine(current, prev) {
+    var curLast = current.length - 1;
+    for (var i = prev.length - 1; i >= 0; --i) {
+        var line = prev[i];
+        if (current[curLast] === line) {
+            current.pop();
+            curLast--;
+        }
+        else {
+            break;
+        }
+    }
+
+    current.push("From previous event:");
+    var lines = current.concat(prev);
+
+    var ret = [];
+
+
+    for (var i = 0, len = lines.length; i < len; ++i) {
+
+        if ((rignore.test(lines[i]) ||
+            (i > 0 && !rtraceline.test(lines[i])) &&
+            lines[i] !== "From previous event:")
+       ) {
+            continue;
+        }
+        ret.push(lines[i]);
+    }
+    return ret;
+};
+
+CapturedTrace.isSupported = function CapturedTrace$IsSupported() {
+    return typeof captureStackTrace === "function";
+};
+
+var captureStackTrace = (function stackDetection() {
+    if (typeof Error.stackTraceLimit === "number" &&
+        typeof Error.captureStackTrace === "function") {
+        rtraceline = /^\s*at\s*/;
+        formatStack = function(stack, error) {
+            if (typeof stack === "string") return stack;
+
+            if (error.name !== void 0 &&
+                error.message !== void 0) {
+                return error.name + ". " + error.message;
+            }
+            return formatNonError(error);
+
+
+        };
+        var captureStackTrace = Error.captureStackTrace;
+        return function CapturedTrace$_captureStackTrace(
+            receiver, ignoreUntil) {
+            captureStackTrace(receiver, ignoreUntil);
+        };
+    }
+    var err = new Error();
+
+    if (!areNamesMangled && typeof err.stack === "string" &&
+        typeof "".startsWith === "function" &&
+        (err.stack.startsWith("stackDetection@")) &&
+        stackDetection.name === "stackDetection") {
+
+        defineProperty(Error, "stackTraceLimit", {
+            writable: true,
+            enumerable: false,
+            configurable: false,
+            value: 25
+        });
+        rtraceline = /@/;
+        var rline = /[@\n]/;
+
+        formatStack = function(stack, error) {
+            if (typeof stack === "string") {
+                return (error.name + ". " + error.message + "\n" + stack);
+            }
+
+            if (error.name !== void 0 &&
+                error.message !== void 0) {
+                return error.name + ". " + error.message;
+            }
+            return formatNonError(error);
+        };
+
+        return function captureStackTrace(o, fn) {
+            var name = fn.name;
+            var stack = new Error().stack;
+            var split = stack.split(rline);
+            var i, len = split.length;
+            for (i = 0; i < len; i += 2) {
+                if (split[i] === name) {
+                    break;
+                }
+            }
+            split = split.slice(i + 2);
+            len = split.length - 2;
+            var ret = "";
+            for (i = 0; i < len; i += 2) {
+                ret += split[i];
+                ret += "@";
+                ret += split[i + 1];
+                ret += "\n";
+            }
+            o.stack = ret;
+        };
+    }
+    else {
+        formatStack = function(stack, error) {
+            if (typeof stack === "string") return stack;
+
+            if ((typeof error === "object" ||
+                typeof error === "function") &&
+                error.name !== void 0 &&
+                error.message !== void 0) {
+                return error.name + ". " + error.message;
+            }
+            return formatNonError(error);
+        };
+
+        return null;
+    }
+})();
+
+return CapturedTrace;
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/catch_filter.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/catch_filter.js
new file mode 100644
index 0000000..8b42af1
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/catch_filter.js
@@ -0,0 +1,93 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(NEXT_FILTER) {
+var util = require("./util.js");
+var errors = require("./errors.js");
+var tryCatch1 = util.tryCatch1;
+var errorObj = util.errorObj;
+var keys = require("./es5.js").keys;
+
+function CatchFilter(instances, callback, promise) {
+    this._instances = instances;
+    this._callback = callback;
+    this._promise = promise;
+}
+
+function CatchFilter$_safePredicate(predicate, e) {
+    var safeObject = {};
+    var retfilter = tryCatch1(predicate, safeObject, e);
+
+    if (retfilter === errorObj) return retfilter;
+
+    var safeKeys = keys(safeObject);
+    if (safeKeys.length) {
+        errorObj.e = new TypeError(
+            "Catch filter must inherit from Error "
+          + "or be a simple predicate function");
+        return errorObj;
+    }
+    return retfilter;
+}
+
+CatchFilter.prototype.doFilter = function CatchFilter$_doFilter(e) {
+    var cb = this._callback;
+    var promise = this._promise;
+    var boundTo = promise._isBound() ? promise._boundTo : void 0;
+    for (var i = 0, len = this._instances.length; i < len; ++i) {
+        var item = this._instances[i];
+        var itemIsErrorType = item === Error ||
+            (item != null && item.prototype instanceof Error);
+
+        if (itemIsErrorType && e instanceof item) {
+            var ret = tryCatch1(cb, boundTo, e);
+            if (ret === errorObj) {
+                NEXT_FILTER.e = ret.e;
+                return NEXT_FILTER;
+            }
+            return ret;
+        } else if (typeof item === "function" && !itemIsErrorType) {
+            var shouldHandle = CatchFilter$_safePredicate(item, e);
+            if (shouldHandle === errorObj) {
+                var trace = errors.canAttach(errorObj.e)
+                    ? errorObj.e
+                    : new Error(errorObj.e + "");
+                this._promise._attachExtraTrace(trace);
+                e = errorObj.e;
+                break;
+            } else if (shouldHandle) {
+                var ret = tryCatch1(cb, boundTo, e);
+                if (ret === errorObj) {
+                    NEXT_FILTER.e = ret.e;
+                    return NEXT_FILTER;
+                }
+                return ret;
+            }
+        }
+    }
+    NEXT_FILTER.e = e;
+    return NEXT_FILTER;
+};
+
+return CatchFilter;
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/direct_resolve.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/direct_resolve.js
new file mode 100644
index 0000000..f4d5384
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/direct_resolve.js
@@ -0,0 +1,83 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var util = require("./util.js");
+var isPrimitive = util.isPrimitive;
+var wrapsPrimitiveReceiver = util.wrapsPrimitiveReceiver;
+
+module.exports = function(Promise) {
+var returner = function Promise$_returner() {
+    return this;
+};
+var thrower = function Promise$_thrower() {
+    throw this;
+};
+
+var wrapper = function Promise$_wrapper(value, action) {
+    if (action === 1) {
+        return function Promise$_thrower() {
+            throw value;
+        };
+    }
+    else if (action === 2) {
+        return function Promise$_returner() {
+            return value;
+        };
+    }
+};
+
+
+Promise.prototype["return"] =
+Promise.prototype.thenReturn =
+function Promise$thenReturn(value) {
+    if (wrapsPrimitiveReceiver && isPrimitive(value)) {
+        return this._then(
+            wrapper(value, 2),
+            void 0,
+            void 0,
+            void 0,
+            void 0,
+            this.thenReturn
+       );
+    }
+    return this._then(returner, void 0, void 0,
+                        value, void 0, this.thenReturn);
+};
+
+Promise.prototype["throw"] =
+Promise.prototype.thenThrow =
+function Promise$thenThrow(reason) {
+    if (wrapsPrimitiveReceiver && isPrimitive(reason)) {
+        return this._then(
+            wrapper(reason, 1),
+            void 0,
+            void 0,
+            void 0,
+            void 0,
+            this.thenThrow
+       );
+    }
+    return this._then(thrower, void 0, void 0,
+                        reason, void 0, this.thenThrow);
+};
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/errors.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/errors.js
new file mode 100644
index 0000000..35fb66e
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/errors.js
@@ -0,0 +1,114 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var global = require("./global.js");
+var Objectfreeze = require("./es5.js").freeze;
+var util = require("./util.js");
+var inherits = util.inherits;
+var notEnumerableProp = util.notEnumerableProp;
+var Error = global.Error;
+
+function markAsOriginatingFromRejection(e) {
+    try {
+        notEnumerableProp(e, "isAsync", true);
+    }
+    catch(ignore) {}
+}
+
+function originatesFromRejection(e) {
+    if (e == null) return false;
+    return ((e instanceof RejectionError) ||
+        e["isAsync"] === true);
+}
+
+function isError(obj) {
+    return obj instanceof Error;
+}
+
+function canAttach(obj) {
+    return isError(obj);
+}
+
+function subError(nameProperty, defaultMessage) {
+    function SubError(message) {
+        if (!(this instanceof SubError)) return new SubError(message);
+        this.message = typeof message === "string" ? message : defaultMessage;
+        this.name = nameProperty;
+        if (Error.captureStackTrace) {
+            Error.captureStackTrace(this, this.constructor);
+        }
+    }
+    inherits(SubError, Error);
+    return SubError;
+}
+
+var TypeError = global.TypeError;
+if (typeof TypeError !== "function") {
+    TypeError = subError("TypeError", "type error");
+}
+var RangeError = global.RangeError;
+if (typeof RangeError !== "function") {
+    RangeError = subError("RangeError", "range error");
+}
+var CancellationError = subError("CancellationError", "cancellation error");
+var TimeoutError = subError("TimeoutError", "timeout error");
+
+function RejectionError(message) {
+    this.name = "RejectionError";
+    this.message = message;
+    this.cause = message;
+    this.isAsync = true;
+
+    if (message instanceof Error) {
+        this.message = message.message;
+        this.stack = message.stack;
+    }
+    else if (Error.captureStackTrace) {
+        Error.captureStackTrace(this, this.constructor);
+    }
+
+}
+inherits(RejectionError, Error);
+
+var key = "__BluebirdErrorTypes__";
+var errorTypes = global[key];
+if (!errorTypes) {
+    errorTypes = Objectfreeze({
+        CancellationError: CancellationError,
+        TimeoutError: TimeoutError,
+        RejectionError: RejectionError
+    });
+    notEnumerableProp(global, key, errorTypes);
+}
+
+module.exports = {
+    Error: Error,
+    TypeError: TypeError,
+    RangeError: RangeError,
+    CancellationError: errorTypes.CancellationError,
+    RejectionError: errorTypes.RejectionError,
+    TimeoutError: errorTypes.TimeoutError,
+    originatesFromRejection: originatesFromRejection,
+    markAsOriginatingFromRejection: markAsOriginatingFromRejection,
+    canAttach: canAttach
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/errors_api_rejection.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/errors_api_rejection.js
new file mode 100644
index 0000000..e953e3b
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/errors_api_rejection.js
@@ -0,0 +1,38 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise) {
+var TypeError = require('./errors.js').TypeError;
+
+function apiRejection(msg) {
+    var error = new TypeError(msg);
+    var ret = Promise.rejected(error);
+    var parent = ret._peekContext();
+    if (parent != null) {
+        parent._attachExtraTrace(error);
+    }
+    return ret;
+}
+
+return apiRejection;
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/es5.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/es5.js
new file mode 100644
index 0000000..e22a0a9
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/es5.js
@@ -0,0 +1,89 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+var isES5 = (function(){
+    "use strict";
+    return this === void 0;
+})();
+
+if (isES5) {
+    module.exports = {
+        freeze: Object.freeze,
+        defineProperty: Object.defineProperty,
+        keys: Object.keys,
+        getPrototypeOf: Object.getPrototypeOf,
+        isArray: Array.isArray,
+        isES5: isES5
+    };
+}
+
+else {
+    var has = {}.hasOwnProperty;
+    var str = {}.toString;
+    var proto = {}.constructor.prototype;
+
+    function ObjectKeys(o) {
+        var ret = [];
+        for (var key in o) {
+            if (has.call(o, key)) {
+                ret.push(key);
+            }
+        }
+        return ret;
+    }
+
+    function ObjectDefineProperty(o, key, desc) {
+        o[key] = desc.value;
+        return o;
+    }
+
+    function ObjectFreeze(obj) {
+        return obj;
+    }
+
+    function ObjectGetPrototypeOf(obj) {
+        try {
+            return Object(obj).constructor.prototype;
+        }
+        catch (e) {
+            return proto;
+        }
+    }
+
+    function ArrayIsArray(obj) {
+        try {
+            return str.call(obj) === "[object Array]";
+        }
+        catch(e) {
+            return false;
+        }
+    }
+
+    module.exports = {
+        isArray: ArrayIsArray,
+        keys: ObjectKeys,
+        defineProperty: ObjectDefineProperty,
+        freeze: ObjectFreeze,
+        getPrototypeOf: ObjectGetPrototypeOf,
+        isES5: isES5
+    };
+}
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/filter.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/filter.js
new file mode 100644
index 0000000..a4b8ae7
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/filter.js
@@ -0,0 +1,53 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise) {
+    var isArray = require("./util.js").isArray;
+
+    function Promise$_filter(booleans) {
+        var values = this._settledValue;
+        var len = values.length;
+        var ret = new Array(len);
+        var j = 0;
+
+        for (var i = 0; i < len; ++i) {
+            if (booleans[i]) ret[j++] = values[i];
+
+        }
+        ret.length = j;
+        return ret;
+    }
+
+    var ref = {ref: null};
+    Promise.filter = function Promise$Filter(promises, fn) {
+        return Promise.map(promises, fn, ref)
+            ._then(Promise$_filter, void 0, void 0,
+                    ref.ref, void 0, Promise.filter);
+    };
+
+    Promise.prototype.filter = function Promise$filter(fn) {
+        return this.map(fn, ref)
+            ._then(Promise$_filter, void 0, void 0,
+                    ref.ref, void 0, this.filter);
+    };
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/finally.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/finally.js
new file mode 100644
index 0000000..ef1e0ef
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/finally.js
@@ -0,0 +1,132 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, NEXT_FILTER) {
+    var util = require("./util.js");
+    var wrapsPrimitiveReceiver = util.wrapsPrimitiveReceiver;
+    var isPrimitive = util.isPrimitive;
+    var thrower = util.thrower;
+
+
+    function returnThis() {
+        return this;
+    }
+    function throwThis() {
+        throw this;
+    }
+    function makeReturner(r) {
+        return function Promise$_returner() {
+            return r;
+        };
+    }
+    function makeThrower(r) {
+        return function Promise$_thrower() {
+            throw r;
+        };
+    }
+    function promisedFinally(ret, reasonOrValue, isFulfilled) {
+        var useConstantFunction =
+                        wrapsPrimitiveReceiver && isPrimitive(reasonOrValue);
+
+        if (isFulfilled) {
+            return ret._then(
+                useConstantFunction
+                    ? returnThis
+                    : makeReturner(reasonOrValue),
+                thrower, void 0, reasonOrValue, void 0, promisedFinally);
+        }
+        else {
+            return ret._then(
+                useConstantFunction
+                    ? throwThis
+                    : makeThrower(reasonOrValue),
+                thrower, void 0, reasonOrValue, void 0, promisedFinally);
+        }
+    }
+
+    function finallyHandler(reasonOrValue) {
+        var promise = this.promise;
+        var handler = this.handler;
+
+        var ret = promise._isBound()
+                        ? handler.call(promise._boundTo)
+                        : handler();
+
+        if (ret !== void 0) {
+            var maybePromise = Promise._cast(ret, finallyHandler, void 0);
+            if (maybePromise instanceof Promise) {
+                return promisedFinally(maybePromise, reasonOrValue,
+                                        promise.isFulfilled());
+            }
+        }
+
+        if (promise.isRejected()) {
+            NEXT_FILTER.e = reasonOrValue;
+            return NEXT_FILTER;
+        }
+        else {
+            return reasonOrValue;
+        }
+    }
+
+    function tapHandler(value) {
+        var promise = this.promise;
+        var handler = this.handler;
+
+        var ret = promise._isBound()
+                        ? handler.call(promise._boundTo, value)
+                        : handler(value);
+
+        if (ret !== void 0) {
+            var maybePromise = Promise._cast(ret, tapHandler, void 0);
+            if (maybePromise instanceof Promise) {
+                return promisedFinally(maybePromise, value, true);
+            }
+        }
+        return value;
+    }
+
+    Promise.prototype._passThroughHandler =
+    function Promise$_passThroughHandler(handler, isFinally, caller) {
+        if (typeof handler !== "function") return this.then();
+
+        var promiseAndHandler = {
+            promise: this,
+            handler: handler
+        };
+
+        return this._then(
+                isFinally ? finallyHandler : tapHandler,
+                isFinally ? finallyHandler : void 0, void 0,
+                promiseAndHandler, void 0, caller);
+    };
+
+    Promise.prototype.lastly =
+    Promise.prototype["finally"] = function Promise$finally(handler) {
+        return this._passThroughHandler(handler, true, this.lastly);
+    };
+
+    Promise.prototype.tap = function Promise$tap(handler) {
+        return this._passThroughHandler(handler, false, this.tap);
+    };
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/generators.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/generators.js
new file mode 100644
index 0000000..9632ae7
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/generators.js
@@ -0,0 +1,56 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, apiRejection, INTERNAL) {
+    var PromiseSpawn = require("./promise_spawn.js")(Promise, INTERNAL);
+    var errors = require("./errors.js");
+    var TypeError = errors.TypeError;
+    var deprecated = require("./util.js").deprecated;
+
+    Promise.coroutine = function Promise$Coroutine(generatorFunction) {
+        if (typeof generatorFunction !== "function") {
+            throw new TypeError("generatorFunction must be a function");
+        }
+        var PromiseSpawn$ = PromiseSpawn;
+        return function anonymous() {
+            var generator = generatorFunction.apply(this, arguments);
+            var spawn = new PromiseSpawn$(void 0, void 0, anonymous);
+            spawn._generator = generator;
+            spawn._next(void 0);
+            return spawn.promise();
+        };
+    };
+
+    Promise.coroutine.addYieldHandler = PromiseSpawn.addYieldHandler;
+
+    Promise.spawn = function Promise$Spawn(generatorFunction) {
+        deprecated("Promise.spawn is deprecated. Use Promise.coroutine instead.");
+        if (typeof generatorFunction !== "function") {
+            return apiRejection("generatorFunction must be a function");
+        }
+        var spawn = new PromiseSpawn(generatorFunction, this, Promise.spawn);
+        var ret = spawn.promise();
+        spawn._run(Promise.spawn);
+        return ret;
+    };
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/global.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/global.js
new file mode 100644
index 0000000..1ab1947
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/global.js
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = (function(){
+    if (typeof this !== "undefined") {
+        return this;
+    }
+    if (typeof process !== "undefined" &&
+        typeof global !== "undefined" &&
+        typeof process.execPath === "string") {
+        return global;
+    }
+    if (typeof window !== "undefined" &&
+        typeof document !== "undefined" &&
+        typeof navigator !== "undefined" && navigator !== null &&
+        typeof navigator.appName === "string") {
+            if(window.wrappedJSObject !== undefined){
+                return window.wrappedJSObject;
+            }
+        return window;
+    }
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/map.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/map.js
new file mode 100644
index 0000000..b2a36b0
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/map.js
@@ -0,0 +1,127 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(
+    Promise, Promise$_CreatePromiseArray, PromiseArray, apiRejection) {
+
+    function Promise$_mapper(values) {
+        var fn = this;
+        var receiver = void 0;
+
+        if (typeof fn !== "function")  {
+            receiver = fn.receiver;
+            fn = fn.fn;
+        }
+        var shouldDefer = false;
+
+        var ret = new Array(values.length);
+
+        if (receiver === void 0) {
+            for (var i = 0, len = values.length; i < len; ++i) {
+                var value = fn(values[i], i, len);
+                if (!shouldDefer) {
+                    var maybePromise = Promise._cast(value,
+                            Promise$_mapper, void 0);
+                    if (maybePromise instanceof Promise) {
+                        if (maybePromise.isFulfilled()) {
+                            ret[i] = maybePromise._settledValue;
+                            continue;
+                        }
+                        else {
+                            shouldDefer = true;
+                        }
+                        value = maybePromise;
+                    }
+                }
+                ret[i] = value;
+            }
+        }
+        else {
+            for (var i = 0, len = values.length; i < len; ++i) {
+                var value = fn.call(receiver, values[i], i, len);
+                if (!shouldDefer) {
+                    var maybePromise = Promise._cast(value,
+                            Promise$_mapper, void 0);
+                    if (maybePromise instanceof Promise) {
+                        if (maybePromise.isFulfilled()) {
+                            ret[i] = maybePromise._settledValue;
+                            continue;
+                        }
+                        else {
+                            shouldDefer = true;
+                        }
+                        value = maybePromise;
+                    }
+                }
+                ret[i] = value;
+            }
+        }
+        return shouldDefer
+            ? Promise$_CreatePromiseArray(ret, PromiseArray,
+                Promise$_mapper, void 0).promise()
+            : ret;
+    }
+
+    function Promise$_Map(promises, fn, useBound, caller, ref) {
+        if (typeof fn !== "function") {
+            return apiRejection("fn must be a function");
+        }
+
+        if (useBound === true && promises._isBound()) {
+            fn = {
+                fn: fn,
+                receiver: promises._boundTo
+            };
+        }
+
+        var ret = Promise$_CreatePromiseArray(
+            promises,
+            PromiseArray,
+            caller,
+            useBound === true && promises._isBound()
+                ? promises._boundTo
+                : void 0
+       ).promise();
+
+        if (ref !== void 0) {
+            ref.ref = ret;
+        }
+
+        return ret._then(
+            Promise$_mapper,
+            void 0,
+            void 0,
+            fn,
+            void 0,
+            caller
+       );
+    }
+
+    Promise.prototype.map = function Promise$map(fn, ref) {
+        return Promise$_Map(this, fn, true, this.map, ref);
+    };
+
+    Promise.map = function Promise$Map(promises, fn, ref) {
+        return Promise$_Map(promises, fn, false, Promise.map, ref);
+    };
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/nodeify.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/nodeify.js
new file mode 100644
index 0000000..9fe25f9
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/nodeify.js
@@ -0,0 +1,64 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise) {
+    var util = require("./util.js");
+    var async = require("./async.js");
+    var tryCatch2 = util.tryCatch2;
+    var tryCatch1 = util.tryCatch1;
+    var errorObj = util.errorObj;
+
+    function thrower(r) {
+        throw r;
+    }
+
+    function Promise$_successAdapter(val, receiver) {
+        var nodeback = this;
+        var ret = tryCatch2(nodeback, receiver, null, val);
+        if (ret === errorObj) {
+            async.invokeLater(thrower, void 0, ret.e);
+        }
+    }
+    function Promise$_errorAdapter(reason, receiver) {
+        var nodeback = this;
+        var ret = tryCatch1(nodeback, receiver, reason);
+        if (ret === errorObj) {
+            async.invokeLater(thrower, void 0, ret.e);
+        }
+    }
+
+    Promise.prototype.nodeify = function Promise$nodeify(nodeback) {
+        if (typeof nodeback == "function") {
+            this._then(
+                Promise$_successAdapter,
+                Promise$_errorAdapter,
+                void 0,
+                nodeback,
+                this._isBound() ? this._boundTo : null,
+                this.nodeify
+            );
+            return;
+        }
+        return this;
+    };
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/progress.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/progress.js
new file mode 100644
index 0000000..106bc58
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/progress.js
@@ -0,0 +1,113 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, isPromiseArrayProxy) {
+    var util = require("./util.js");
+    var async = require("./async.js");
+    var errors = require("./errors.js");
+    var tryCatch1 = util.tryCatch1;
+    var errorObj = util.errorObj;
+
+    Promise.prototype.progressed = function Promise$progressed(handler) {
+        return this._then(void 0, void 0, handler,
+                            void 0, void 0, this.progressed);
+    };
+
+    Promise.prototype._progress = function Promise$_progress(progressValue) {
+        if (this._isFollowingOrFulfilledOrRejected()) return;
+        this._progressUnchecked(progressValue);
+
+    };
+
+    Promise.prototype._progressHandlerAt =
+    function Promise$_progressHandlerAt(index) {
+        if (index === 0) return this._progressHandler0;
+        return this[index + 2 - 5];
+    };
+
+    Promise.prototype._doProgressWith =
+    function Promise$_doProgressWith(progression) {
+        var progressValue = progression.value;
+        var handler = progression.handler;
+        var promise = progression.promise;
+        var receiver = progression.receiver;
+
+        this._pushContext();
+        var ret = tryCatch1(handler, receiver, progressValue);
+        this._popContext();
+
+        if (ret === errorObj) {
+            if (ret.e != null &&
+                ret.e.name !== "StopProgressPropagation") {
+                var trace = errors.canAttach(ret.e)
+                    ? ret.e : new Error(ret.e + "");
+                promise._attachExtraTrace(trace);
+                promise._progress(ret.e);
+            }
+        }
+        else if (Promise.is(ret)) {
+            ret._then(promise._progress, null, null, promise, void 0,
+                this._progress);
+        }
+        else {
+            promise._progress(ret);
+        }
+    };
+
+
+    Promise.prototype._progressUnchecked =
+    function Promise$_progressUnchecked(progressValue) {
+        if (!this.isPending()) return;
+        var len = this._length();
+
+        for (var i = 0; i < len; i += 5) {
+            var handler = this._progressHandlerAt(i);
+            var promise = this._promiseAt(i);
+            if (!Promise.is(promise)) {
+                var receiver = this._receiverAt(i);
+                if (typeof handler === "function") {
+                    handler.call(receiver, progressValue, promise);
+                }
+                else if (Promise.is(receiver) && receiver._isProxied()) {
+                    receiver._progressUnchecked(progressValue);
+                }
+                else if (isPromiseArrayProxy(receiver, promise)) {
+                    receiver._promiseProgressed(progressValue, promise);
+                }
+                continue;
+            }
+
+            if (typeof handler === "function") {
+                async.invoke(this._doProgressWith, this, {
+                    handler: handler,
+                    promise: promise,
+                    receiver: this._receiverAt(i),
+                    value: progressValue
+                });
+            }
+            else {
+                async.invoke(promise._progress, promise, progressValue);
+            }
+        }
+    };
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/promise.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/promise.js
new file mode 100644
index 0000000..a5fe1d6
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/promise.js
@@ -0,0 +1,1167 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function() {
+var global = require("./global.js");
+var util = require("./util.js");
+var async = require("./async.js");
+var errors = require("./errors.js");
+
+var INTERNAL = function(){};
+var APPLY = {};
+var NEXT_FILTER = {e: null};
+
+var PromiseArray = require("./promise_array.js")(Promise, INTERNAL);
+var CapturedTrace = require("./captured_trace.js")();
+var CatchFilter = require("./catch_filter.js")(NEXT_FILTER);
+var PromiseResolver = require("./promise_resolver.js");
+
+var isArray = util.isArray;
+
+var errorObj = util.errorObj;
+var tryCatch1 = util.tryCatch1;
+var tryCatch2 = util.tryCatch2;
+var tryCatchApply = util.tryCatchApply;
+var RangeError = errors.RangeError;
+var TypeError = errors.TypeError;
+var CancellationError = errors.CancellationError;
+var TimeoutError = errors.TimeoutError;
+var RejectionError = errors.RejectionError;
+var originatesFromRejection = errors.originatesFromRejection;
+var markAsOriginatingFromRejection = errors.markAsOriginatingFromRejection;
+var canAttach = errors.canAttach;
+var thrower = util.thrower;
+var apiRejection = require("./errors_api_rejection")(Promise);
+
+
+var makeSelfResolutionError = function Promise$_makeSelfResolutionError() {
+    return new TypeError("circular promise resolution chain");
+};
+
+function isPromise(obj) {
+    if (obj === void 0) return false;
+    return obj instanceof Promise;
+}
+
+function isPromiseArrayProxy(receiver, promiseSlotValue) {
+    if (receiver instanceof PromiseArray) {
+        return promiseSlotValue >= 0;
+    }
+    return false;
+}
+
+function Promise(resolver) {
+    if (typeof resolver !== "function") {
+        throw new TypeError("the promise constructor requires a resolver function");
+    }
+    if (this.constructor !== Promise) {
+        throw new TypeError("the promise constructor cannot be invoked directly");
+    }
+    this._bitField = 0;
+    this._fulfillmentHandler0 = void 0;
+    this._rejectionHandler0 = void 0;
+    this._promise0 = void 0;
+    this._receiver0 = void 0;
+    this._settledValue = void 0;
+    this._boundTo = void 0;
+    if (resolver !== INTERNAL) this._resolveFromResolver(resolver);
+}
+
+Promise.prototype.bind = function Promise$bind(thisArg) {
+    var ret = new Promise(INTERNAL);
+    if (debugging) ret._setTrace(this.bind, this);
+    ret._follow(this);
+    ret._setBoundTo(thisArg);
+    if (this._cancellable()) {
+        ret._setCancellable();
+        ret._cancellationParent = this;
+    }
+    return ret;
+};
+
+Promise.prototype.toString = function Promise$toString() {
+    return "[object Promise]";
+};
+
+Promise.prototype.caught = Promise.prototype["catch"] =
+function Promise$catch(fn) {
+    var len = arguments.length;
+    if (len > 1) {
+        var catchInstances = new Array(len - 1),
+            j = 0, i;
+        for (i = 0; i < len - 1; ++i) {
+            var item = arguments[i];
+            if (typeof item === "function") {
+                catchInstances[j++] = item;
+            }
+            else {
+                var catchFilterTypeError =
+                    new TypeError(
+                        "A catch filter must be an error constructor "
+                        + "or a filter function");
+
+                this._attachExtraTrace(catchFilterTypeError);
+                async.invoke(this._reject, this, catchFilterTypeError);
+                return;
+            }
+        }
+        catchInstances.length = j;
+        fn = arguments[i];
+
+        this._resetTrace(this.caught);
+        var catchFilter = new CatchFilter(catchInstances, fn, this);
+        return this._then(void 0, catchFilter.doFilter, void 0,
+            catchFilter, void 0, this.caught);
+    }
+    return this._then(void 0, fn, void 0, void 0, void 0, this.caught);
+};
+
+Promise.prototype.then =
+function Promise$then(didFulfill, didReject, didProgress) {
+    return this._then(didFulfill, didReject, didProgress,
+        void 0, void 0, this.then);
+};
+
+
+Promise.prototype.done =
+function Promise$done(didFulfill, didReject, didProgress) {
+    var promise = this._then(didFulfill, didReject, didProgress,
+        void 0, void 0, this.done);
+    promise._setIsFinal();
+};
+
+Promise.prototype.spread = function Promise$spread(didFulfill, didReject) {
+    return this._then(didFulfill, didReject, void 0,
+        APPLY, void 0, this.spread);
+};
+
+Promise.prototype.isFulfilled = function Promise$isFulfilled() {
+    return (this._bitField & 268435456) > 0;
+};
+
+
+Promise.prototype.isRejected = function Promise$isRejected() {
+    return (this._bitField & 134217728) > 0;
+};
+
+Promise.prototype.isPending = function Promise$isPending() {
+    return !this.isResolved();
+};
+
+
+Promise.prototype.isResolved = function Promise$isResolved() {
+    return (this._bitField & 402653184) > 0;
+};
+
+
+Promise.prototype.isCancellable = function Promise$isCancellable() {
+    return !this.isResolved() &&
+        this._cancellable();
+};
+
+Promise.prototype.toJSON = function Promise$toJSON() {
+    var ret = {
+        isFulfilled: false,
+        isRejected: false,
+        fulfillmentValue: void 0,
+        rejectionReason: void 0
+    };
+    if (this.isFulfilled()) {
+        ret.fulfillmentValue = this._settledValue;
+        ret.isFulfilled = true;
+    }
+    else if (this.isRejected()) {
+        ret.rejectionReason = this._settledValue;
+        ret.isRejected = true;
+    }
+    return ret;
+};
+
+Promise.prototype.all = function Promise$all() {
+    return Promise$_all(this, true, this.all);
+};
+
+
+Promise.is = isPromise;
+
+function Promise$_all(promises, useBound, caller) {
+    return Promise$_CreatePromiseArray(
+        promises,
+        PromiseArray,
+        caller,
+        useBound === true && promises._isBound()
+            ? promises._boundTo
+            : void 0
+   ).promise();
+}
+Promise.all = function Promise$All(promises) {
+    return Promise$_all(promises, false, Promise.all);
+};
+
+Promise.join = function Promise$Join() {
+    var $_len = arguments.length;var args = new Array($_len); for(var $_i = 0; $_i < $_len; ++$_i) {args[$_i] = arguments[$_i];}
+    return Promise$_CreatePromiseArray(
+        args, PromiseArray, Promise.join, void 0).promise();
+};
+
+Promise.resolve = Promise.fulfilled =
+function Promise$Resolve(value, caller) {
+    var ret = new Promise(INTERNAL);
+    if (debugging) ret._setTrace(typeof caller === "function"
+        ? caller
+        : Promise.resolve, void 0);
+    if (ret._tryFollow(value)) {
+        return ret;
+    }
+    ret._cleanValues();
+    ret._setFulfilled();
+    ret._settledValue = value;
+    return ret;
+};
+
+Promise.reject = Promise.rejected = function Promise$Reject(reason) {
+    var ret = new Promise(INTERNAL);
+    if (debugging) ret._setTrace(Promise.reject, void 0);
+    markAsOriginatingFromRejection(reason);
+    ret._cleanValues();
+    ret._setRejected();
+    ret._settledValue = reason;
+    if (!canAttach(reason)) {
+        var trace = new Error(reason + "");
+        ret._setCarriedStackTrace(trace);
+    }
+    ret._ensurePossibleRejectionHandled();
+    return ret;
+};
+
+Promise.prototype.error = function Promise$_error(fn) {
+    return this.caught(originatesFromRejection, fn);
+};
+
+Promise.prototype._resolveFromSyncValue =
+function Promise$_resolveFromSyncValue(value, caller) {
+    if (value === errorObj) {
+        this._cleanValues();
+        this._setRejected();
+        this._settledValue = value.e;
+        this._ensurePossibleRejectionHandled();
+    }
+    else {
+        var maybePromise = Promise._cast(value, caller, void 0);
+        if (maybePromise instanceof Promise) {
+            this._follow(maybePromise);
+        }
+        else {
+            this._cleanValues();
+            this._setFulfilled();
+            this._settledValue = value;
+        }
+    }
+};
+
+Promise.method = function Promise$_Method(fn) {
+    if (typeof fn !== "function") {
+        throw new TypeError("fn must be a function");
+    }
+    return function Promise$_method() {
+        var value;
+        switch(arguments.length) {
+        case 0: value = tryCatch1(fn, this, void 0); break;
+        case 1: value = tryCatch1(fn, this, arguments[0]); break;
+        case 2: value = tryCatch2(fn, this, arguments[0], arguments[1]); break;
+        default:
+            var $_len = arguments.length;var args = new Array($_len); for(var $_i = 0; $_i < $_len; ++$_i) {args[$_i] = arguments[$_i];}
+            value = tryCatchApply(fn, args, this); break;
+        }
+        var ret = new Promise(INTERNAL);
+        if (debugging) ret._setTrace(Promise$_method, void 0);
+        ret._resolveFromSyncValue(value, Promise$_method);
+        return ret;
+    };
+};
+
+Promise.attempt = Promise["try"] = function Promise$_Try(fn, args, ctx) {
+
+    if (typeof fn !== "function") {
+        return apiRejection("fn must be a function");
+    }
+    var value = isArray(args)
+        ? tryCatchApply(fn, args, ctx)
+        : tryCatch1(fn, ctx, args);
+
+    var ret = new Promise(INTERNAL);
+    if (debugging) ret._setTrace(Promise.attempt, void 0);
+    ret._resolveFromSyncValue(value, Promise.attempt);
+    return ret;
+};
+
+Promise.defer = Promise.pending = function Promise$Defer(caller) {
+    var promise = new Promise(INTERNAL);
+    if (debugging) promise._setTrace(typeof caller === "function"
+                              ? caller : Promise.defer, void 0);
+    return new PromiseResolver(promise);
+};
+
+Promise.bind = function Promise$Bind(thisArg) {
+    var ret = new Promise(INTERNAL);
+    if (debugging) ret._setTrace(Promise.bind, void 0);
+    ret._setFulfilled();
+    ret._setBoundTo(thisArg);
+    return ret;
+};
+
+Promise.cast = function Promise$_Cast(obj, caller) {
+    if (typeof caller !== "function") {
+        caller = Promise.cast;
+    }
+    var ret = Promise._cast(obj, caller, void 0);
+    if (!(ret instanceof Promise)) {
+        return Promise.resolve(ret, caller);
+    }
+    return ret;
+};
+
+Promise.onPossiblyUnhandledRejection =
+function Promise$OnPossiblyUnhandledRejection(fn) {
+    if (typeof fn === "function") {
+        CapturedTrace.possiblyUnhandledRejection = fn;
+    }
+    else {
+        CapturedTrace.possiblyUnhandledRejection = void 0;
+    }
+};
+
+var debugging = false || !!(
+    typeof process !== "undefined" &&
+    typeof process.execPath === "string" &&
+    typeof process.env === "object" &&
+    (process.env["BLUEBIRD_DEBUG"] ||
+        process.env["NODE_ENV"] === "development")
+);
+
+
+Promise.longStackTraces = function Promise$LongStackTraces() {
+    if (async.haveItemsQueued() &&
+        debugging === false
+   ) {
+        throw new Error("cannot enable long stack traces after promises have been created");
+    }
+    debugging = CapturedTrace.isSupported();
+};
+
+Promise.hasLongStackTraces = function Promise$HasLongStackTraces() {
+    return debugging && CapturedTrace.isSupported();
+};
+
+Promise.prototype._setProxyHandlers =
+function Promise$_setProxyHandlers(receiver, promiseSlotValue) {
+    var index = this._length();
+
+    if (index >= 1048575 - 5) {
+        index = 0;
+        this._setLength(0);
+    }
+    if (index === 0) {
+        this._promise0 = promiseSlotValue;
+        this._receiver0 = receiver;
+    }
+    else {
+        var i = index - 5;
+        this[i + 3] = promiseSlotValue;
+        this[i + 4] = receiver;
+        this[i + 0] =
+        this[i + 1] =
+        this[i + 2] = void 0;
+    }
+    this._setLength(index + 5);
+};
+
+Promise.prototype._proxyPromiseArray =
+function Promise$_proxyPromiseArray(promiseArray, index) {
+    this._setProxyHandlers(promiseArray, index);
+};
+
+Promise.prototype._proxyPromise = function Promise$_proxyPromise(promise) {
+    promise._setProxied();
+    this._setProxyHandlers(promise, -1);
+};
+
+Promise.prototype._then =
+function Promise$_then(
+    didFulfill,
+    didReject,
+    didProgress,
+    receiver,
+    internalData,
+    caller
+) {
+    var haveInternalData = internalData !== void 0;
+    var ret = haveInternalData ? internalData : new Promise(INTERNAL);
+
+    if (debugging && !haveInternalData) {
+        var haveSameContext = this._peekContext() === this._traceParent;
+        ret._traceParent = haveSameContext ? this._traceParent : this;
+        ret._setTrace(typeof caller === "function"
+                ? caller
+                : this._then, this);
+    }
+
+    if (!haveInternalData && this._isBound()) {
+        ret._setBoundTo(this._boundTo);
+    }
+
+    var callbackIndex =
+        this._addCallbacks(didFulfill, didReject, didProgress, ret, receiver);
+
+    if (!haveInternalData && this._cancellable()) {
+        ret._setCancellable();
+        ret._cancellationParent = this;
+    }
+
+    if (this.isResolved()) {
+        async.invoke(this._queueSettleAt, this, callbackIndex);
+    }
+
+    return ret;
+};
+
+Promise.prototype._length = function Promise$_length() {
+    return this._bitField & 1048575;
+};
+
+Promise.prototype._isFollowingOrFulfilledOrRejected =
+function Promise$_isFollowingOrFulfilledOrRejected() {
+    return (this._bitField & 939524096) > 0;
+};
+
+Promise.prototype._isFollowing = function Promise$_isFollowing() {
+    return (this._bitField & 536870912) === 536870912;
+};
+
+Promise.prototype._setLength = function Promise$_setLength(len) {
+    this._bitField = (this._bitField & -1048576) |
+        (len & 1048575);
+};
+
+Promise.prototype._setFulfilled = function Promise$_setFulfilled() {
+    this._bitField = this._bitField | 268435456;
+};
+
+Promise.prototype._setRejected = function Promise$_setRejected() {
+    this._bitField = this._bitField | 134217728;
+};
+
+Promise.prototype._setFollowing = function Promise$_setFollowing() {
+    this._bitField = this._bitField | 536870912;
+};
+
+Promise.prototype._setIsFinal = function Promise$_setIsFinal() {
+    this._bitField = this._bitField | 33554432;
+};
+
+Promise.prototype._isFinal = function Promise$_isFinal() {
+    return (this._bitField & 33554432) > 0;
+};
+
+Promise.prototype._cancellable = function Promise$_cancellable() {
+    return (this._bitField & 67108864) > 0;
+};
+
+Promise.prototype._setCancellable = function Promise$_setCancellable() {
+    this._bitField = this._bitField | 67108864;
+};
+
+Promise.prototype._unsetCancellable = function Promise$_unsetCancellable() {
+    this._bitField = this._bitField & (~67108864);
+};
+
+Promise.prototype._setRejectionIsUnhandled =
+function Promise$_setRejectionIsUnhandled() {
+    this._bitField = this._bitField | 2097152;
+};
+
+Promise.prototype._unsetRejectionIsUnhandled =
+function Promise$_unsetRejectionIsUnhandled() {
+    this._bitField = this._bitField & (~2097152);
+};
+
+Promise.prototype._isRejectionUnhandled =
+function Promise$_isRejectionUnhandled() {
+    return (this._bitField & 2097152) > 0;
+};
+
+Promise.prototype._setCarriedStackTrace =
+function Promise$_setCarriedStackTrace(capturedTrace) {
+    this._bitField = this._bitField | 1048576;
+    this._fulfillmentHandler0 = capturedTrace;
+};
+
+Promise.prototype._unsetCarriedStackTrace =
+function Promise$_unsetCarriedStackTrace() {
+    this._bitField = this._bitField & (~1048576);
+    this._fulfillmentHandler0 = void 0;
+};
+
+Promise.prototype._isCarryingStackTrace =
+function Promise$_isCarryingStackTrace() {
+    return (this._bitField & 1048576) > 0;
+};
+
+Promise.prototype._getCarriedStackTrace =
+function Promise$_getCarriedStackTrace() {
+    return this._isCarryingStackTrace()
+        ? this._fulfillmentHandler0
+        : void 0;
+};
+
+Promise.prototype._receiverAt = function Promise$_receiverAt(index) {
+    var ret;
+    if (index === 0) {
+        ret = this._receiver0;
+    }
+    else {
+        ret = this[index + 4 - 5];
+    }
+    if (this._isBound() && ret === void 0) {
+        return this._boundTo;
+    }
+    return ret;
+};
+
+Promise.prototype._promiseAt = function Promise$_promiseAt(index) {
+    if (index === 0) return this._promise0;
+    return this[index + 3 - 5];
+};
+
+Promise.prototype._fulfillmentHandlerAt =
+function Promise$_fulfillmentHandlerAt(index) {
+    if (index === 0) return this._fulfillmentHandler0;
+    return this[index + 0 - 5];
+};
+
+Promise.prototype._rejectionHandlerAt =
+function Promise$_rejectionHandlerAt(index) {
+    if (index === 0) return this._rejectionHandler0;
+    return this[index + 1 - 5];
+};
+
+Promise.prototype._unsetAt = function Promise$_unsetAt(index) {
+     if (index === 0) {
+        this._rejectionHandler0 =
+        this._progressHandler0 =
+        this._promise0 =
+        this._receiver0 = void 0;
+        if (!this._isCarryingStackTrace()) {
+            this._fulfillmentHandler0 = void 0;
+        }
+    }
+    else {
+        this[index - 5 + 0] =
+        this[index - 5 + 1] =
+        this[index - 5 + 2] =
+        this[index - 5 + 3] =
+        this[index - 5 + 4] = void 0;
+    }
+};
+
+Promise.prototype._resolveFromResolver =
+function Promise$_resolveFromResolver(resolver) {
+    var promise = this;
+    var localDebugging = debugging;
+    if (localDebugging) {
+        this._setTrace(this._resolveFromResolver, void 0);
+        this._pushContext();
+    }
+    function Promise$_resolver(val) {
+        if (promise._tryFollow(val)) {
+            return;
+        }
+        promise._fulfill(val);
+    }
+    function Promise$_rejecter(val) {
+        var trace = canAttach(val) ? val : new Error(val + "");
+        promise._attachExtraTrace(trace);
+        markAsOriginatingFromRejection(val);
+        promise._reject(val, trace === val ? void 0 : trace);
+    }
+    var r = tryCatch2(resolver, void 0, Promise$_resolver, Promise$_rejecter);
+    if (localDebugging) this._popContext();
+
+    if (r !== void 0 && r === errorObj) {
+        var e = r.e;
+        var trace = canAttach(e) ? e : new Error(e + "");
+        promise._reject(e, trace);
+    }
+};
+
+Promise.prototype._addCallbacks = function Promise$_addCallbacks(
+    fulfill,
+    reject,
+    progress,
+    promise,
+    receiver
+) {
+    var index = this._length();
+
+    if (index >= 1048575 - 5) {
+        index = 0;
+        this._setLength(0);
+    }
+
+    if (index === 0) {
+        this._promise0 = promise;
+        if (receiver !== void 0) this._receiver0 = receiver;
+        if (typeof fulfill === "function" && !this._isCarryingStackTrace())
+            this._fulfillmentHandler0 = fulfill;
+        if (typeof reject === "function") this._rejectionHandler0 = reject;
+        if (typeof progress === "function") this._progressHandler0 = progress;
+    }
+    else {
+        var i = index - 5;
+        this[i + 3] = promise;
+        this[i + 4] = receiver;
+        this[i + 0] = typeof fulfill === "function"
+                                            ? fulfill : void 0;
+        this[i + 1] = typeof reject === "function"
+                                            ? reject : void 0;
+        this[i + 2] = typeof progress === "function"
+                                            ? progress : void 0;
+    }
+    this._setLength(index + 5);
+    return index;
+};
+
+
+
+Promise.prototype._setBoundTo = function Promise$_setBoundTo(obj) {
+    if (obj !== void 0) {
+        this._bitField = this._bitField | 8388608;
+        this._boundTo = obj;
+    }
+    else {
+        this._bitField = this._bitField & (~8388608);
+    }
+};
+
+Promise.prototype._isBound = function Promise$_isBound() {
+    return (this._bitField & 8388608) === 8388608;
+};
+
+Promise.prototype._spreadSlowCase =
+function Promise$_spreadSlowCase(targetFn, promise, values, boundTo) {
+    var promiseForAll =
+            Promise$_CreatePromiseArray
+                (values, PromiseArray, this._spreadSlowCase, boundTo)
+            .promise()
+            ._then(function() {
+                return targetFn.apply(boundTo, arguments);
+            }, void 0, void 0, APPLY, void 0, this._spreadSlowCase);
+
+    promise._follow(promiseForAll);
+};
+
+Promise.prototype._callSpread =
+function Promise$_callSpread(handler, promise, value, localDebugging) {
+    var boundTo = this._isBound() ? this._boundTo : void 0;
+    if (isArray(value)) {
+        var caller = this._settlePromiseFromHandler;
+        for (var i = 0, len = value.length; i < len; ++i) {
+            if (isPromise(Promise._cast(value[i], caller, void 0))) {
+                this._spreadSlowCase(handler, promise, value, boundTo);
+                return;
+            }
+        }
+    }
+    if (localDebugging) promise._pushContext();
+    return tryCatchApply(handler, value, boundTo);
+};
+
+Promise.prototype._callHandler =
+function Promise$_callHandler(
+    handler, receiver, promise, value, localDebugging) {
+    var x;
+    if (receiver === APPLY && !this.isRejected()) {
+        x = this._callSpread(handler, promise, value, localDebugging);
+    }
+    else {
+        if (localDebugging) promise._pushContext();
+        x = tryCatch1(handler, receiver, value);
+    }
+    if (localDebugging) promise._popContext();
+    return x;
+};
+
+Promise.prototype._settlePromiseFromHandler =
+function Promise$_settlePromiseFromHandler(
+    handler, receiver, value, promise
+) {
+    if (!isPromise(promise)) {
+        handler.call(receiver, value, promise);
+        return;
+    }
+
+    var localDebugging = debugging;
+    var x = this._callHandler(handler, receiver,
+                                promise, value, localDebugging);
+
+    if (promise._isFollowing()) return;
+
+    if (x === errorObj || x === promise || x === NEXT_FILTER) {
+        var err = x === promise
+                    ? makeSelfResolutionError()
+                    : x.e;
+        var trace = canAttach(err) ? err : new Error(err + "");
+        if (x !== NEXT_FILTER) promise._attachExtraTrace(trace);
+        promise._rejectUnchecked(err, trace);
+    }
+    else {
+        var castValue = Promise._cast(x,
+                    localDebugging ? this._settlePromiseFromHandler : void 0,
+                    promise);
+
+        if (isPromise(castValue)) {
+            if (castValue.isRejected() &&
+                !castValue._isCarryingStackTrace() &&
+                !canAttach(castValue._settledValue)) {
+                var trace = new Error(castValue._settledValue + "");
+                promise._attachExtraTrace(trace);
+                castValue._setCarriedStackTrace(trace);
+            }
+            promise._follow(castValue);
+            if (castValue._cancellable()) {
+                promise._cancellationParent = castValue;
+                promise._setCancellable();
+            }
+        }
+        else {
+            promise._fulfillUnchecked(x);
+        }
+    }
+};
+
+Promise.prototype._follow =
+function Promise$_follow(promise) {
+    this._setFollowing();
+
+    if (promise.isPending()) {
+        if (promise._cancellable() ) {
+            this._cancellationParent = promise;
+            this._setCancellable();
+        }
+        promise._proxyPromise(this);
+    }
+    else if (promise.isFulfilled()) {
+        this._fulfillUnchecked(promise._settledValue);
+    }
+    else {
+        this._rejectUnchecked(promise._settledValue,
+            promise._getCarriedStackTrace());
+    }
+
+    if (promise._isRejectionUnhandled()) promise._unsetRejectionIsUnhandled();
+
+    if (debugging &&
+        promise._traceParent == null) {
+        promise._traceParent = this;
+    }
+};
+
+Promise.prototype._tryFollow =
+function Promise$_tryFollow(value) {
+    if (this._isFollowingOrFulfilledOrRejected() ||
+        value === this) {
+        return false;
+    }
+    var maybePromise = Promise._cast(value, this._tryFollow, void 0);
+    if (!isPromise(maybePromise)) {
+        return false;
+    }
+    this._follow(maybePromise);
+    return true;
+};
+
+Promise.prototype._resetTrace = function Promise$_resetTrace(caller) {
+    if (debugging) {
+        var context = this._peekContext();
+        var isTopLevel = context === void 0;
+        this._trace = new CapturedTrace(
+            typeof caller === "function"
+            ? caller
+            : this._resetTrace,
+            isTopLevel
+       );
+    }
+};
+
+Promise.prototype._setTrace = function Promise$_setTrace(caller, parent) {
+    if (debugging) {
+        var context = this._peekContext();
+        this._traceParent = context;
+        var isTopLevel = context === void 0;
+        if (parent !== void 0 &&
+            parent._traceParent === context) {
+            this._trace = parent._trace;
+        }
+        else {
+            this._trace = new CapturedTrace(
+                typeof caller === "function"
+                ? caller
+                : this._setTrace,
+                isTopLevel
+           );
+        }
+    }
+    return this;
+};
+
+Promise.prototype._attachExtraTrace =
+function Promise$_attachExtraTrace(error) {
+    if (debugging) {
+        var promise = this;
+        var stack = error.stack;
+        stack = typeof stack === "string"
+            ? stack.split("\n") : [];
+        var headerLineCount = 1;
+
+        while(promise != null &&
+            promise._trace != null) {
+            stack = CapturedTrace.combine(
+                stack,
+                promise._trace.stack.split("\n")
+           );
+            promise = promise._traceParent;
+        }
+
+        var max = Error.stackTraceLimit + headerLineCount;
+        var len = stack.length;
+        if (len  > max) {
+            stack.length = max;
+        }
+        if (stack.length <= headerLineCount) {
+            error.stack = "(No stack trace)";
+        }
+        else {
+            error.stack = stack.join("\n");
+        }
+    }
+};
+
+Promise.prototype._cleanValues = function Promise$_cleanValues() {
+    if (this._cancellable()) {
+        this._cancellationParent = void 0;
+    }
+};
+
+Promise.prototype._fulfill = function Promise$_fulfill(value) {
+    if (this._isFollowingOrFulfilledOrRejected()) return;
+    this._fulfillUnchecked(value);
+};
+
+Promise.prototype._reject =
+function Promise$_reject(reason, carriedStackTrace) {
+    if (this._isFollowingOrFulfilledOrRejected()) return;
+    this._rejectUnchecked(reason, carriedStackTrace);
+};
+
+Promise.prototype._settlePromiseAt = function Promise$_settlePromiseAt(index) {
+    var handler = this.isFulfilled()
+        ? this._fulfillmentHandlerAt(index)
+        : this._rejectionHandlerAt(index);
+
+    var value = this._settledValue;
+    var receiver = this._receiverAt(index);
+    var promise = this._promiseAt(index);
+
+    if (typeof handler === "function") {
+        this._settlePromiseFromHandler(handler, receiver, value, promise);
+    }
+    else {
+        var done = false;
+        var isFulfilled = this.isFulfilled();
+        if (receiver !== void 0) {
+            if (receiver instanceof Promise &&
+                receiver._isProxied()) {
+                receiver._unsetProxied();
+
+                if (isFulfilled) receiver._fulfillUnchecked(value);
+                else receiver._rejectUnchecked(value,
+                    this._getCarriedStackTrace());
+                done = true;
+            }
+            else if (isPromiseArrayProxy(receiver, promise)) {
+
+                if (isFulfilled) receiver._promiseFulfilled(value, promise);
+                else receiver._promiseRejected(value, promise);
+
+                done = true;
+            }
+        }
+
+        if (!done) {
+
+            if (isFulfilled) promise._fulfill(value);
+            else promise._reject(value, this._getCarriedStackTrace());
+
+        }
+    }
+
+    if (index >= 256) {
+        this._queueGC();
+    }
+};
+
+Promise.prototype._isProxied = function Promise$_isProxied() {
+    return (this._bitField & 4194304) === 4194304;
+};
+
+Promise.prototype._setProxied = function Promise$_setProxied() {
+    this._bitField = this._bitField | 4194304;
+};
+
+Promise.prototype._unsetProxied = function Promise$_unsetProxied() {
+    this._bitField = this._bitField & (~4194304);
+};
+
+Promise.prototype._isGcQueued = function Promise$_isGcQueued() {
+    return (this._bitField & -1073741824) === -1073741824;
+};
+
+Promise.prototype._setGcQueued = function Promise$_setGcQueued() {
+    this._bitField = this._bitField | -1073741824;
+};
+
+Promise.prototype._unsetGcQueued = function Promise$_unsetGcQueued() {
+    this._bitField = this._bitField & (~-1073741824);
+};
+
+Promise.prototype._queueGC = function Promise$_queueGC() {
+    if (this._isGcQueued()) return;
+    this._setGcQueued();
+    async.invokeLater(this._gc, this, void 0);
+};
+
+Promise.prototype._gc = function Promise$gc() {
+    var len = this._length();
+    this._unsetAt(0);
+    for (var i = 0; i < len; i++) {
+        delete this[i];
+    }
+    this._setLength(0);
+    this._unsetGcQueued();
+};
+
+Promise.prototype._queueSettleAt = function Promise$_queueSettleAt(index) {
+    if (this._isRejectionUnhandled()) this._unsetRejectionIsUnhandled();
+    async.invoke(this._settlePromiseAt, this, index);
+};
+
+Promise.prototype._fulfillUnchecked =
+function Promise$_fulfillUnchecked(value) {
+    if (!this.isPending()) return;
+    if (value === this) {
+        var err = makeSelfResolutionError();
+        this._attachExtraTrace(err);
+        return this._rejectUnchecked(err, void 0);
+    }
+    this._cleanValues();
+    this._setFulfilled();
+    this._settledValue = value;
+    var len = this._length();
+
+    if (len > 0) {
+        async.invoke(this._fulfillPromises, this, len);
+    }
+};
+
+Promise.prototype._rejectUncheckedCheckError =
+function Promise$_rejectUncheckedCheckError(reason) {
+    var trace = canAttach(reason) ? reason : new Error(reason + "");
+    this._rejectUnchecked(reason, trace === reason ? void 0 : trace);
+};
+
+Promise.prototype._rejectUnchecked =
+function Promise$_rejectUnchecked(reason, trace) {
+    if (!this.isPending()) return;
+    if (reason === this) {
+        var err = makeSelfResolutionError();
+        this._attachExtraTrace(err);
+        return this._rejectUnchecked(err);
+    }
+    this._cleanValues();
+    this._setRejected();
+    this._settledValue = reason;
+
+    if (this._isFinal()) {
+        async.invokeLater(thrower, void 0, trace === void 0 ? reason : trace);
+        return;
+    }
+    var len = this._length();
+
+    if (trace !== void 0) this._setCarriedStackTrace(trace);
+
+    if (len > 0) {
+        async.invoke(this._rejectPromises, this, null);
+    }
+    else {
+        this._ensurePossibleRejectionHandled();
+    }
+};
+
+Promise.prototype._rejectPromises = function Promise$_rejectPromises() {
+    var len = this._length();
+    for (var i = 0; i < len; i+= 5) {
+        this._settlePromiseAt(i);
+    }
+    this._unsetCarriedStackTrace();
+};
+
+Promise.prototype._fulfillPromises = function Promise$_fulfillPromises(len) {
+    len = this._length();
+    for (var i = 0; i < len; i+= 5) {
+        this._settlePromiseAt(i);
+    }
+};
+
+Promise.prototype._ensurePossibleRejectionHandled =
+function Promise$_ensurePossibleRejectionHandled() {
+    this._setRejectionIsUnhandled();
+    if (CapturedTrace.possiblyUnhandledRejection !== void 0) {
+        async.invokeLater(this._notifyUnhandledRejection, this, void 0);
+    }
+};
+
+Promise.prototype._notifyUnhandledRejection =
+function Promise$_notifyUnhandledRejection() {
+    if (this._isRejectionUnhandled()) {
+        var reason = this._settledValue;
+        var trace = this._getCarriedStackTrace();
+
+        this._unsetRejectionIsUnhandled();
+
+        if (trace !== void 0) {
+            this._unsetCarriedStackTrace();
+            reason = trace;
+        }
+        if (typeof CapturedTrace.possiblyUnhandledRejection === "function") {
+            CapturedTrace.possiblyUnhandledRejection(reason, this);
+        }
+    }
+};
+
+var contextStack = [];
+Promise.prototype._peekContext = function Promise$_peekContext() {
+    var lastIndex = contextStack.length - 1;
+    if (lastIndex >= 0) {
+        return contextStack[lastIndex];
+    }
+    return void 0;
+
+};
+
+Promise.prototype._pushContext = function Promise$_pushContext() {
+    if (!debugging) return;
+    contextStack.push(this);
+};
+
+Promise.prototype._popContext = function Promise$_popContext() {
+    if (!debugging) return;
+    contextStack.pop();
+};
+
+function Promise$_CreatePromiseArray(
+    promises, PromiseArrayConstructor, caller, boundTo) {
+
+    var list = null;
+    if (isArray(promises)) {
+        list = promises;
+    }
+    else {
+        list = Promise._cast(promises, caller, void 0);
+        if (list !== promises) {
+            list._setBoundTo(boundTo);
+        }
+        else if (!isPromise(list)) {
+            list = null;
+        }
+    }
+    if (list !== null) {
+        return new PromiseArrayConstructor(
+            list,
+            typeof caller === "function"
+                ? caller
+                : Promise$_CreatePromiseArray,
+            boundTo
+       );
+    }
+    return {
+        promise: function() {return apiRejection("expecting an array, a promise or a thenable");}
+    };
+}
+
+var old = global.Promise;
+
+Promise.noConflict = function() {
+    if (global.Promise === Promise) {
+        global.Promise = old;
+    }
+    return Promise;
+};
+
+if (!CapturedTrace.isSupported()) {
+    Promise.longStackTraces = function(){};
+    debugging = false;
+}
+
+Promise._makeSelfResolutionError = makeSelfResolutionError;
+require("./finally.js")(Promise, NEXT_FILTER);
+require("./direct_resolve.js")(Promise);
+require("./thenables.js")(Promise, INTERNAL);
+Promise.RangeError = RangeError;
+Promise.CancellationError = CancellationError;
+Promise.TimeoutError = TimeoutError;
+Promise.TypeError = TypeError;
+Promise.RejectionError = RejectionError;
+
+util.toFastProperties(Promise);
+util.toFastProperties(Promise.prototype);
+require('./timers.js')(Promise,INTERNAL);
+require('./synchronous_inspection.js')(Promise);
+require('./any.js')(Promise,Promise$_CreatePromiseArray,PromiseArray);
+require('./race.js')(Promise,INTERNAL);
+require('./call_get.js')(Promise);
+require('./filter.js')(Promise,Promise$_CreatePromiseArray,PromiseArray,apiRejection);
+require('./generators.js')(Promise,apiRejection,INTERNAL);
+require('./map.js')(Promise,Promise$_CreatePromiseArray,PromiseArray,apiRejection);
+require('./nodeify.js')(Promise);
+require('./promisify.js')(Promise,INTERNAL);
+require('./props.js')(Promise,PromiseArray);
+require('./reduce.js')(Promise,Promise$_CreatePromiseArray,PromiseArray,apiRejection,INTERNAL);
+require('./settle.js')(Promise,Promise$_CreatePromiseArray,PromiseArray);
+require('./some.js')(Promise,Promise$_CreatePromiseArray,PromiseArray,apiRejection);
+require('./progress.js')(Promise,isPromiseArrayProxy);
+require('./cancel.js')(Promise,INTERNAL);
+
+Promise.prototype = Promise.prototype;
+return Promise;
+
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/promise_array.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/promise_array.js
new file mode 100644
index 0000000..377f0ec
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/promise_array.js
@@ -0,0 +1,233 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, INTERNAL) {
+var canAttach = require("./errors.js").canAttach;
+var util = require("./util.js");
+var async = require("./async.js");
+var hasOwn = {}.hasOwnProperty;
+var isArray = util.isArray;
+
+function toResolutionValue(val) {
+    switch(val) {
+    case -1: return void 0;
+    case -2: return [];
+    case -3: return {};
+    }
+}
+
+function PromiseArray(values, caller, boundTo) {
+    var promise = this._promise = new Promise(INTERNAL);
+    var parent = void 0;
+    if (Promise.is(values)) {
+        parent = values;
+        if (values._cancellable()) {
+            promise._setCancellable();
+            promise._cancellationParent = values;
+        }
+        if (values._isBound()) {
+            promise._setBoundTo(boundTo);
+        }
+    }
+    promise._setTrace(caller, parent);
+    this._values = values;
+    this._length = 0;
+    this._totalResolved = 0;
+    this._init(void 0, -2);
+}
+PromiseArray.PropertiesPromiseArray = function() {};
+
+PromiseArray.prototype.length = function PromiseArray$length() {
+    return this._length;
+};
+
+PromiseArray.prototype.promise = function PromiseArray$promise() {
+    return this._promise;
+};
+
+PromiseArray.prototype._init =
+function PromiseArray$_init(_, resolveValueIfEmpty) {
+    var values = this._values;
+    if (Promise.is(values)) {
+        if (values.isFulfilled()) {
+            values = values._settledValue;
+            if (!isArray(values)) {
+                var err = new Promise.TypeError("expecting an array, a promise or a thenable");
+                this.__hardReject__(err);
+                return;
+            }
+            this._values = values;
+        }
+        else if (values.isPending()) {
+            values._then(
+                this._init,
+                this._reject,
+                void 0,
+                this,
+                resolveValueIfEmpty,
+                this.constructor
+           );
+            return;
+        }
+        else {
+            this._reject(values._settledValue);
+            return;
+        }
+    }
+
+    if (values.length === 0) {
+        this._resolve(toResolutionValue(resolveValueIfEmpty));
+        return;
+    }
+    var len = values.length;
+    var newLen = len;
+    var newValues;
+    if (this instanceof PromiseArray.PropertiesPromiseArray) {
+        newValues = this._values;
+    }
+    else {
+        newValues = new Array(len);
+    }
+    var isDirectScanNeeded = false;
+    for (var i = 0; i < len; ++i) {
+        var promise = values[i];
+        if (promise === void 0 && !hasOwn.call(values, i)) {
+            newLen--;
+            continue;
+        }
+        var maybePromise = Promise._cast(promise, void 0, void 0);
+        if (maybePromise instanceof Promise) {
+            if (maybePromise.isPending()) {
+                maybePromise._proxyPromiseArray(this, i);
+            }
+            else {
+                maybePromise._unsetRejectionIsUnhandled();
+                isDirectScanNeeded = true;
+            }
+        }
+        else {
+            isDirectScanNeeded = true;
+        }
+        newValues[i] = maybePromise;
+    }
+    if (newLen === 0) {
+        if (resolveValueIfEmpty === -2) {
+            this._resolve(newValues);
+        }
+        else {
+            this._resolve(toResolutionValue(resolveValueIfEmpty));
+        }
+        return;
+    }
+    this._values = newValues;
+    this._length = newLen;
+    if (isDirectScanNeeded) {
+        var scanMethod = newLen === len
+            ? this._scanDirectValues
+            : this._scanDirectValuesHoled;
+        async.invoke(scanMethod, this, len);
+    }
+};
+
+PromiseArray.prototype._settlePromiseAt =
+function PromiseArray$_settlePromiseAt(index) {
+    var value = this._values[index];
+    if (!Promise.is(value)) {
+        this._promiseFulfilled(value, index);
+    }
+    else if (value.isFulfilled()) {
+        this._promiseFulfilled(value._settledValue, index);
+    }
+    else if (value.isRejected()) {
+        this._promiseRejected(value._settledValue, index);
+    }
+};
+
+PromiseArray.prototype._scanDirectValuesHoled =
+function PromiseArray$_scanDirectValuesHoled(len) {
+    for (var i = 0; i < len; ++i) {
+        if (this._isResolved()) {
+            break;
+        }
+        if (hasOwn.call(this._values, i)) {
+            this._settlePromiseAt(i);
+        }
+    }
+};
+
+PromiseArray.prototype._scanDirectValues =
+function PromiseArray$_scanDirectValues(len) {
+    for (var i = 0; i < len; ++i) {
+        if (this._isResolved()) {
+            break;
+        }
+        this._settlePromiseAt(i);
+    }
+};
+
+PromiseArray.prototype._isResolved = function PromiseArray$_isResolved() {
+    return this._values === null;
+};
+
+PromiseArray.prototype._resolve = function PromiseArray$_resolve(value) {
+    this._values = null;
+    this._promise._fulfill(value);
+};
+
+PromiseArray.prototype.__hardReject__ =
+PromiseArray.prototype._reject = function PromiseArray$_reject(reason) {
+    this._values = null;
+    var trace = canAttach(reason) ? reason : new Error(reason + "");
+    this._promise._attachExtraTrace(trace);
+    this._promise._reject(reason, trace);
+};
+
+PromiseArray.prototype._promiseProgressed =
+function PromiseArray$_promiseProgressed(progressValue, index) {
+    if (this._isResolved()) return;
+    this._promise._progress({
+        index: index,
+        value: progressValue
+    });
+};
+
+
+PromiseArray.prototype._promiseFulfilled =
+function PromiseArray$_promiseFulfilled(value, index) {
+    if (this._isResolved()) return;
+    this._values[index] = value;
+    var totalResolved = ++this._totalResolved;
+    if (totalResolved >= this._length) {
+        this._resolve(this._values);
+    }
+};
+
+PromiseArray.prototype._promiseRejected =
+function PromiseArray$_promiseRejected(reason, index) {
+    if (this._isResolved()) return;
+    this._totalResolved++;
+    this._reject(reason);
+};
+
+return PromiseArray;
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/promise_inspection.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/promise_inspection.js
new file mode 100644
index 0000000..0aa233b
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/promise_inspection.js
@@ -0,0 +1,66 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var TypeError = require("./errors.js").TypeError;
+
+function PromiseInspection(promise) {
+    if (promise !== void 0) {
+        this._bitField = promise._bitField;
+        this._settledValue = promise.isResolved()
+            ? promise._settledValue
+            : void 0;
+    }
+    else {
+        this._bitField = 0;
+        this._settledValue = void 0;
+    }
+}
+PromiseInspection.prototype.isFulfilled =
+function PromiseInspection$isFulfilled() {
+    return (this._bitField & 268435456) > 0;
+};
+
+PromiseInspection.prototype.isRejected =
+function PromiseInspection$isRejected() {
+    return (this._bitField & 134217728) > 0;
+};
+
+PromiseInspection.prototype.isPending = function PromiseInspection$isPending() {
+    return (this._bitField & 402653184) === 0;
+};
+
+PromiseInspection.prototype.value = function PromiseInspection$value() {
+    if (!this.isFulfilled()) {
+        throw new TypeError("cannot get fulfillment value of a non-fulfilled promise");
+    }
+    return this._settledValue;
+};
+
+PromiseInspection.prototype.error = function PromiseInspection$error() {
+    if (!this.isRejected()) {
+        throw new TypeError("cannot get rejection reason of a non-rejected promise");
+    }
+    return this._settledValue;
+};
+
+module.exports = PromiseInspection;
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/promise_resolver.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/promise_resolver.js
new file mode 100644
index 0000000..53ead00
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/promise_resolver.js
@@ -0,0 +1,152 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var util = require("./util.js");
+var maybeWrapAsError = util.maybeWrapAsError;
+var errors = require("./errors.js");
+var TimeoutError = errors.TimeoutError;
+var RejectionError = errors.RejectionError;
+var async = require("./async.js");
+var haveGetters = util.haveGetters;
+var es5 = require("./es5.js");
+
+function isUntypedError(obj) {
+    return obj instanceof Error &&
+        es5.getPrototypeOf(obj) === Error.prototype;
+}
+
+function wrapAsRejectionError(obj) {
+    var ret;
+    if (isUntypedError(obj)) {
+        ret = new RejectionError(obj);
+    }
+    else {
+        ret = obj;
+    }
+    errors.markAsOriginatingFromRejection(ret);
+    return ret;
+}
+
+function nodebackForPromise(promise) {
+    function PromiseResolver$_callback(err, value) {
+        if (promise === null) return;
+
+        if (err) {
+            var wrapped = wrapAsRejectionError(maybeWrapAsError(err));
+            promise._attachExtraTrace(wrapped);
+            promise._reject(wrapped);
+        }
+        else {
+            if (arguments.length > 2) {
+                var $_len = arguments.length;var args = new Array($_len - 1); for(var $_i = 1; $_i < $_len; ++$_i) {args[$_i - 1] = arguments[$_i];}
+                promise._fulfill(args);
+            }
+            else {
+                promise._fulfill(value);
+            }
+        }
+
+        promise = null;
+    }
+    return PromiseResolver$_callback;
+}
+
+
+var PromiseResolver;
+if (!haveGetters) {
+    PromiseResolver = function PromiseResolver(promise) {
+        this.promise = promise;
+        this.asCallback = nodebackForPromise(promise);
+        this.callback = this.asCallback;
+    };
+}
+else {
+    PromiseResolver = function PromiseResolver(promise) {
+        this.promise = promise;
+    };
+}
+if (haveGetters) {
+    var prop = {
+        get: function() {
+            return nodebackForPromise(this.promise);
+        }
+    };
+    es5.defineProperty(PromiseResolver.prototype, "asCallback", prop);
+    es5.defineProperty(PromiseResolver.prototype, "callback", prop);
+}
+
+PromiseResolver._nodebackForPromise = nodebackForPromise;
+
+PromiseResolver.prototype.toString = function PromiseResolver$toString() {
+    return "[object PromiseResolver]";
+};
+
+PromiseResolver.prototype.resolve =
+PromiseResolver.prototype.fulfill = function PromiseResolver$resolve(value) {
+    var promise = this.promise;
+    if (promise._tryFollow(value)) {
+        return;
+    }
+    async.invoke(promise._fulfill, promise, value);
+};
+
+PromiseResolver.prototype.reject = function PromiseResolver$reject(reason) {
+    var promise = this.promise;
+    errors.markAsOriginatingFromRejection(reason);
+    var trace = errors.canAttach(reason) ? reason : new Error(reason + "");
+    promise._attachExtraTrace(trace);
+    async.invoke(promise._reject, promise, reason);
+    if (trace !== reason) {
+        async.invoke(this._setCarriedStackTrace, this, trace);
+    }
+};
+
+PromiseResolver.prototype.progress =
+function PromiseResolver$progress(value) {
+    async.invoke(this.promise._progress, this.promise, value);
+};
+
+PromiseResolver.prototype.cancel = function PromiseResolver$cancel() {
+    async.invoke(this.promise.cancel, this.promise, void 0);
+};
+
+PromiseResolver.prototype.timeout = function PromiseResolver$timeout() {
+    this.reject(new TimeoutError("timeout"));
+};
+
+PromiseResolver.prototype.isResolved = function PromiseResolver$isResolved() {
+    return this.promise.isResolved();
+};
+
+PromiseResolver.prototype.toJSON = function PromiseResolver$toJSON() {
+    return this.promise.toJSON();
+};
+
+PromiseResolver.prototype._setCarriedStackTrace =
+function PromiseResolver$_setCarriedStackTrace(trace) {
+    if (this.promise.isRejected()) {
+        this.promise._setCarriedStackTrace(trace);
+    }
+};
+
+module.exports = PromiseResolver;
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/promise_spawn.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/promise_spawn.js
new file mode 100644
index 0000000..2d6525f
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/promise_spawn.js
@@ -0,0 +1,131 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, INTERNAL) {
+var errors = require("./errors.js");
+var TypeError = errors.TypeError;
+var util = require("./util.js");
+var isArray = util.isArray;
+var errorObj = util.errorObj;
+var tryCatch1 = util.tryCatch1;
+var yieldHandlers = [];
+
+function promiseFromYieldHandler(value) {
+    var _yieldHandlers = yieldHandlers;
+    var _errorObj = errorObj;
+    var _Promise = Promise;
+    var len = _yieldHandlers.length;
+    for (var i = 0; i < len; ++i) {
+        var result = tryCatch1(_yieldHandlers[i], void 0, value);
+        if (result === _errorObj) {
+            return _Promise.reject(_errorObj.e);
+        }
+        var maybePromise = _Promise._cast(result,
+            promiseFromYieldHandler, void 0);
+        if (maybePromise instanceof _Promise) return maybePromise;
+    }
+    return null;
+}
+
+function PromiseSpawn(generatorFunction, receiver, caller) {
+    var promise = this._promise = new Promise(INTERNAL);
+    promise._setTrace(caller, void 0);
+    this._generatorFunction = generatorFunction;
+    this._receiver = receiver;
+    this._generator = void 0;
+}
+
+PromiseSpawn.prototype.promise = function PromiseSpawn$promise() {
+    return this._promise;
+};
+
+PromiseSpawn.prototype._run = function PromiseSpawn$_run() {
+    this._generator = this._generatorFunction.call(this._receiver);
+    this._receiver =
+        this._generatorFunction = void 0;
+    this._next(void 0);
+};
+
+PromiseSpawn.prototype._continue = function PromiseSpawn$_continue(result) {
+    if (result === errorObj) {
+        this._generator = void 0;
+        var trace = errors.canAttach(result.e)
+            ? result.e : new Error(result.e + "");
+        this._promise._attachExtraTrace(trace);
+        this._promise._reject(result.e, trace);
+        return;
+    }
+
+    var value = result.value;
+    if (result.done === true) {
+        this._generator = void 0;
+        if (!this._promise._tryFollow(value)) {
+            this._promise._fulfill(value);
+        }
+    }
+    else {
+        var maybePromise = Promise._cast(value, PromiseSpawn$_continue, void 0);
+        if (!(maybePromise instanceof Promise)) {
+            if (isArray(maybePromise)) {
+                maybePromise = Promise.all(maybePromise);
+            }
+            else {
+                maybePromise = promiseFromYieldHandler(maybePromise);
+            }
+            if (maybePromise === null) {
+                this._throw(new TypeError("A value was yielded that could not be treated as a promise"));
+                return;
+            }
+        }
+        maybePromise._then(
+            this._next,
+            this._throw,
+            void 0,
+            this,
+            null,
+            void 0
+       );
+    }
+};
+
+PromiseSpawn.prototype._throw = function PromiseSpawn$_throw(reason) {
+    if (errors.canAttach(reason))
+        this._promise._attachExtraTrace(reason);
+    this._continue(
+        tryCatch1(this._generator["throw"], this._generator, reason)
+   );
+};
+
+PromiseSpawn.prototype._next = function PromiseSpawn$_next(value) {
+    this._continue(
+        tryCatch1(this._generator.next, this._generator, value)
+   );
+};
+
+PromiseSpawn.addYieldHandler = function PromiseSpawn$AddYieldHandler(fn) {
+    if (typeof fn !== "function") throw new TypeError("fn must be a function");
+    yieldHandlers.push(fn);
+};
+
+return PromiseSpawn;
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/promisify.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/promisify.js
new file mode 100644
index 0000000..a550fd0
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/promisify.js
@@ -0,0 +1,278 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, INTERNAL) {
+var THIS = {};
+var util = require("./util.js");
+var es5 = require("./es5.js");
+var nodebackForPromise = require("./promise_resolver.js")
+    ._nodebackForPromise;
+var withAppended = util.withAppended;
+var maybeWrapAsError = util.maybeWrapAsError;
+var canEvaluate = util.canEvaluate;
+var notEnumerableProp = util.notEnumerableProp;
+var deprecated = util.deprecated;
+var roriginal = new RegExp("__beforePromisified__" + "$");
+var hasProp = {}.hasOwnProperty;
+function isPromisified(fn) {
+    return fn.__isPromisified__ === true;
+}
+var inheritedMethods = (function() {
+    if (es5.isES5) {
+        var create = Object.create;
+        var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
+        return function(cur) {
+            var original = cur;
+            var ret = [];
+            var visitedKeys = create(null);
+            while (cur !== null) {
+                var keys = es5.keys(cur);
+                for (var i = 0, len = keys.length; i < len; ++i) {
+                    var key = keys[i];
+                    if (visitedKeys[key] ||
+                        roriginal.test(key) ||
+                        hasProp.call(original, key + "__beforePromisified__")
+                   ) {
+                        continue;
+                    }
+                    visitedKeys[key] = true;
+                    var desc = getOwnPropertyDescriptor(cur, key);
+                    if (desc != null &&
+                        typeof desc.value === "function" &&
+                        !isPromisified(desc.value)) {
+                        ret.push(key, desc.value);
+                    }
+                }
+                cur = es5.getPrototypeOf(cur);
+            }
+            return ret;
+        };
+    }
+    else {
+        return function(obj) {
+            var ret = [];
+            /*jshint forin:false */
+            for (var key in obj) {
+                if (roriginal.test(key) ||
+                    hasProp.call(obj, key + "__beforePromisified__")) {
+                    continue;
+                }
+                var fn = obj[key];
+                if (typeof fn === "function" &&
+                    !isPromisified(fn)) {
+                    ret.push(key, fn);
+                }
+            }
+            return ret;
+        };
+    }
+})();
+
+function switchCaseArgumentOrder(likelyArgumentCount) {
+    var ret = [likelyArgumentCount];
+    var min = Math.max(0, likelyArgumentCount - 1 - 5);
+    for(var i = likelyArgumentCount - 1; i >= min; --i) {
+        if (i === likelyArgumentCount) continue;
+        ret.push(i);
+    }
+    for(var i = likelyArgumentCount + 1; i <= 5; ++i) {
+        ret.push(i);
+    }
+    return ret;
+}
+
+function parameterDeclaration(parameterCount) {
+    var ret = new Array(parameterCount);
+    for(var i = 0; i < ret.length; ++i) {
+        ret[i] = "_arg" + i;
+    }
+    return ret.join(", ");
+}
+
+function parameterCount(fn) {
+    if (typeof fn.length === "number") {
+        return Math.max(Math.min(fn.length, 1023 + 1), 0);
+    }
+    return 0;
+}
+
+function propertyAccess(id) {
+    var rident = /^[a-z$_][a-z$_0-9]*$/i;
+
+    if (rident.test(id)) {
+        return "." + id;
+    }
+    else return "['" + id.replace(/(['\\])/g, "\\$1") + "']";
+}
+
+function makeNodePromisifiedEval(callback, receiver, originalName, fn) {
+    var newParameterCount = Math.max(0, parameterCount(fn) - 1);
+    var argumentOrder = switchCaseArgumentOrder(newParameterCount);
+
+    var callbackName = (typeof originalName === "string" ?
+        originalName + "Async" :
+        "promisified");
+
+    function generateCallForArgumentCount(count) {
+        var args = new Array(count);
+        for (var i = 0, len = args.length; i < len; ++i) {
+            args[i] = "arguments[" + i + "]";
+        }
+        var comma = count > 0 ? "," : "";
+
+        if (typeof callback === "string" &&
+            receiver === THIS) {
+            return "this" + propertyAccess(callback) + "("+args.join(",") +
+                comma +" fn);"+
+                "break;";
+        }
+        return (receiver === void 0
+            ? "callback("+args.join(",")+ comma +" fn);"
+            : "callback.call("+(receiver === THIS
+                ? "this"
+                : "receiver")+", "+args.join(",") + comma + " fn);") +
+        "break;";
+    }
+
+    function generateArgumentSwitchCase() {
+        var ret = "";
+        for(var i = 0; i < argumentOrder.length; ++i) {
+            ret += "case " + argumentOrder[i] +":" +
+                generateCallForArgumentCount(argumentOrder[i]);
+        }
+        ret += "default: var args = new Array(len + 1);" +
+            "var i = 0;" +
+            "for (var i = 0; i < len; ++i) { " +
+            "   args[i] = arguments[i];" +
+            "}" +
+            "args[i] = fn;" +
+
+            (typeof callback === "string"
+            ? "this" + propertyAccess(callback) + ".apply("
+            : "callback.apply(") +
+
+            (receiver === THIS ? "this" : "receiver") +
+            ", args); break;";
+        return ret;
+    }
+
+    return new Function("Promise", "callback", "receiver",
+            "withAppended", "maybeWrapAsError", "nodebackForPromise",
+            "INTERNAL",
+        "var ret = function " + callbackName +
+        "(" + parameterDeclaration(newParameterCount) + ") {\"use strict\";" +
+        "var len = arguments.length;" +
+        "var promise = new Promise(INTERNAL);"+
+        "promise._setTrace(" + callbackName + ", void 0);" +
+        "var fn = nodebackForPromise(promise);"+
+        "try {" +
+        "switch(len) {" +
+        generateArgumentSwitchCase() +
+        "}" +
+        "}" +
+        "catch(e){ " +
+        "var wrapped = maybeWrapAsError(e);" +
+        "promise._attachExtraTrace(wrapped);" +
+        "promise._reject(wrapped);" +
+        "}" +
+        "return promise;" +
+        "" +
+        "}; ret.__isPromisified__ = true; return ret;"
+   )(Promise, callback, receiver, withAppended,
+        maybeWrapAsError, nodebackForPromise, INTERNAL);
+}
+
+function makeNodePromisifiedClosure(callback, receiver) {
+    function promisified() {
+        var _receiver = receiver;
+        if (receiver === THIS) _receiver = this;
+        if (typeof callback === "string") {
+            callback = _receiver[callback];
+        }
+        var promise = new Promise(INTERNAL);
+        promise._setTrace(promisified, void 0);
+        var fn = nodebackForPromise(promise);
+        try {
+            callback.apply(_receiver, withAppended(arguments, fn));
+        }
+        catch(e) {
+            var wrapped = maybeWrapAsError(e);
+            promise._attachExtraTrace(wrapped);
+            promise._reject(wrapped);
+        }
+        return promise;
+    }
+    promisified.__isPromisified__ = true;
+    return promisified;
+}
+
+var makeNodePromisified = canEvaluate
+    ? makeNodePromisifiedEval
+    : makeNodePromisifiedClosure;
+
+function _promisify(callback, receiver, isAll) {
+    if (isAll) {
+        var methods = inheritedMethods(callback);
+        for (var i = 0, len = methods.length; i < len; i+= 2) {
+            var key = methods[i];
+            var fn = methods[i+1];
+            var originalKey = key + "__beforePromisified__";
+            var promisifiedKey = key + "Async";
+            notEnumerableProp(callback, originalKey, fn);
+            callback[promisifiedKey] =
+                makeNodePromisified(originalKey, THIS,
+                    key, fn);
+        }
+        util.toFastProperties(callback);
+        return callback;
+    }
+    else {
+        return makeNodePromisified(callback, receiver, void 0, callback);
+    }
+}
+
+Promise.promisify = function Promise$Promisify(fn, receiver) {
+    if (typeof fn === "object" && fn !== null) {
+        deprecated("Promise.promisify for promisifying entire objects is deprecated. Use Promise.promisifyAll instead.");
+        return _promisify(fn, receiver, true);
+    }
+    if (typeof fn !== "function") {
+        throw new TypeError("fn must be a function");
+    }
+    if (isPromisified(fn)) {
+        return fn;
+    }
+    return _promisify(
+        fn,
+        arguments.length < 2 ? THIS : receiver,
+        false);
+};
+
+Promise.promisifyAll = function Promise$PromisifyAll(target) {
+    if (typeof target !== "function" && typeof target !== "object") {
+        throw new TypeError("the target of promisifyAll must be an object or a function");
+    }
+    return _promisify(target, void 0, true);
+};
+};
+
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/properties_promise_array.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/properties_promise_array.js
new file mode 100644
index 0000000..85f5990
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/properties_promise_array.js
@@ -0,0 +1,77 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, PromiseArray) {
+var util = require("./util.js");
+var inherits = util.inherits;
+var es5 = require("./es5.js");
+
+function PropertiesPromiseArray(obj, caller, boundTo) {
+    var keys = es5.keys(obj);
+    var values = new Array(keys.length);
+    for (var i = 0, len = values.length; i < len; ++i) {
+        values[i] = obj[keys[i]];
+    }
+    this.constructor$(values, caller, boundTo);
+    if (!this._isResolved()) {
+        for (var i = 0, len = keys.length; i < len; ++i) {
+            values.push(keys[i]);
+        }
+    }
+}
+inherits(PropertiesPromiseArray, PromiseArray);
+
+PropertiesPromiseArray.prototype._init =
+function PropertiesPromiseArray$_init() {
+    this._init$(void 0, -3) ;
+};
+
+PropertiesPromiseArray.prototype._promiseFulfilled =
+function PropertiesPromiseArray$_promiseFulfilled(value, index) {
+    if (this._isResolved()) return;
+    this._values[index] = value;
+    var totalResolved = ++this._totalResolved;
+    if (totalResolved >= this._length) {
+        var val = {};
+        var keyOffset = this.length();
+        for (var i = 0, len = this.length(); i < len; ++i) {
+            val[this._values[i + keyOffset]] = this._values[i];
+        }
+        this._resolve(val);
+    }
+};
+
+PropertiesPromiseArray.prototype._promiseProgressed =
+function PropertiesPromiseArray$_promiseProgressed(value, index) {
+    if (this._isResolved()) return;
+
+    this._promise._progress({
+        key: this._values[index + this.length()],
+        value: value
+    });
+};
+
+PromiseArray.PropertiesPromiseArray = PropertiesPromiseArray;
+
+return PropertiesPromiseArray;
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/props.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/props.js
new file mode 100644
index 0000000..3cdba43
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/props.js
@@ -0,0 +1,65 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, PromiseArray) {
+    var PropertiesPromiseArray = require("./properties_promise_array.js")(
+        Promise, PromiseArray);
+    var util = require("./util.js");
+    var apiRejection = require("./errors_api_rejection")(Promise);
+    var isObject = util.isObject;
+
+    function Promise$_Props(promises, useBound, caller) {
+        var ret;
+        var castValue = Promise._cast(promises, caller, void 0);
+
+        if (!isObject(castValue)) {
+            return apiRejection("cannot await properties of a non-object");
+        }
+        else if (Promise.is(castValue)) {
+            ret = castValue._then(Promise.props, void 0, void 0,
+                            void 0, void 0, caller);
+        }
+        else {
+            ret = new PropertiesPromiseArray(
+                castValue,
+                caller,
+                useBound === true && castValue._isBound()
+                            ? castValue._boundTo
+                            : void 0
+           ).promise();
+            useBound = false;
+        }
+        if (useBound === true && castValue._isBound()) {
+            ret._setBoundTo(castValue._boundTo);
+        }
+        return ret;
+    }
+
+    Promise.prototype.props = function Promise$props() {
+        return Promise$_Props(this, true, this.props);
+    };
+
+    Promise.props = function Promise$Props(promises) {
+        return Promise$_Props(promises, false, Promise.props);
+    };
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/queue.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/queue.js
new file mode 100644
index 0000000..bbd3f1b
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/queue.js
@@ -0,0 +1,135 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+function arrayCopy(src, srcIndex, dst, dstIndex, len) {
+    for (var j = 0; j < len; ++j) {
+        dst[j + dstIndex] = src[j + srcIndex];
+    }
+}
+
+function pow2AtLeast(n) {
+    n = n >>> 0;
+    n = n - 1;
+    n = n | (n >> 1);
+    n = n | (n >> 2);
+    n = n | (n >> 4);
+    n = n | (n >> 8);
+    n = n | (n >> 16);
+    return n + 1;
+}
+
+function getCapacity(capacity) {
+    if (typeof capacity !== "number") return 16;
+    return pow2AtLeast(
+        Math.min(
+            Math.max(16, capacity), 1073741824)
+   );
+}
+
+function Queue(capacity) {
+    this._capacity = getCapacity(capacity);
+    this._length = 0;
+    this._front = 0;
+    this._makeCapacity();
+}
+
+Queue.prototype._willBeOverCapacity =
+function Queue$_willBeOverCapacity(size) {
+    return this._capacity < size;
+};
+
+Queue.prototype._pushOne = function Queue$_pushOne(arg) {
+    var length = this.length();
+    this._checkCapacity(length + 1);
+    var i = (this._front + length) & (this._capacity - 1);
+    this[i] = arg;
+    this._length = length + 1;
+};
+
+Queue.prototype.push = function Queue$push(fn, receiver, arg) {
+    var length = this.length() + 3;
+    if (this._willBeOverCapacity(length)) {
+        this._pushOne(fn);
+        this._pushOne(receiver);
+        this._pushOne(arg);
+        return;
+    }
+    var j = this._front + length - 3;
+    this._checkCapacity(length);
+    var wrapMask = this._capacity - 1;
+    this[(j + 0) & wrapMask] = fn;
+    this[(j + 1) & wrapMask] = receiver;
+    this[(j + 2) & wrapMask] = arg;
+    this._length = length;
+};
+
+Queue.prototype.shift = function Queue$shift() {
+    var front = this._front,
+        ret = this[front];
+
+    this[front] = void 0;
+    this._front = (front + 1) & (this._capacity - 1);
+    this._length--;
+    return ret;
+};
+
+Queue.prototype.length = function Queue$length() {
+    return this._length;
+};
+
+Queue.prototype._makeCapacity = function Queue$_makeCapacity() {
+    var len = this._capacity;
+    for (var i = 0; i < len; ++i) {
+        this[i] = void 0;
+    }
+};
+
+Queue.prototype._checkCapacity = function Queue$_checkCapacity(size) {
+    if (this._capacity < size) {
+        this._resizeTo(this._capacity << 3);
+    }
+};
+
+Queue.prototype._resizeTo = function Queue$_resizeTo(capacity) {
+    var oldFront = this._front;
+    var oldCapacity = this._capacity;
+    var oldQueue = new Array(oldCapacity);
+    var length = this.length();
+
+    arrayCopy(this, 0, oldQueue, 0, oldCapacity);
+    this._capacity = capacity;
+    this._makeCapacity();
+    this._front = 0;
+    if (oldFront + length <= oldCapacity) {
+        arrayCopy(oldQueue, oldFront, this, 0, length);
+    }
+    else {        var lengthBeforeWrapping =
+            length - ((oldFront + length) & (oldCapacity - 1));
+
+        arrayCopy(oldQueue, oldFront, this, 0, lengthBeforeWrapping);
+        arrayCopy(oldQueue, 0, this, lengthBeforeWrapping,
+                    length - lengthBeforeWrapping);
+    }
+};
+
+module.exports = Queue;
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/race.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/race.js
new file mode 100644
index 0000000..82b8ce1
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/race.js
@@ -0,0 +1,85 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, INTERNAL) {
+    var apiRejection = require("./errors_api_rejection.js")(Promise);
+    var isArray = require("./util.js").isArray;
+
+    var raceLater = function Promise$_raceLater(promise) {
+        return promise.then(function Promise$_lateRacer(array) {
+            return Promise$_Race(array, Promise$_lateRacer, promise);
+        });
+    };
+
+    var hasOwn = {}.hasOwnProperty;
+    function Promise$_Race(promises, caller, parent) {
+        var maybePromise = Promise._cast(promises, caller, void 0);
+
+        if (Promise.is(maybePromise)) {
+            return raceLater(maybePromise);
+        }
+        else if (!isArray(promises)) {
+            return apiRejection("expecting an array, a promise or a thenable");
+        }
+
+        var ret = new Promise(INTERNAL);
+        ret._setTrace(caller, parent);
+        if (parent !== void 0) {
+            if (parent._isBound()) {
+                ret._setBoundTo(parent._boundTo);
+            }
+            if (parent._cancellable()) {
+                ret._setCancellable();
+                ret._cancellationParent = parent;
+            }
+        }
+        var fulfill = ret._fulfill;
+        var reject = ret._reject;
+        for (var i = 0, len = promises.length; i < len; ++i) {
+            var val = promises[i];
+
+            if (val === void 0 && !(hasOwn.call(promises, i))) {
+                continue;
+            }
+
+            Promise.cast(val)._then(
+                fulfill,
+                reject,
+                void 0,
+                ret,
+                null,
+                caller
+           );
+        }
+        return ret;
+    }
+
+    Promise.race = function Promise$Race(promises) {
+        return Promise$_Race(promises, Promise.race, void 0);
+    };
+
+    Promise.prototype.race = function Promise$race() {
+        return Promise$_Race(this, this.race, void 0);
+    };
+
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/reduce.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/reduce.js
new file mode 100644
index 0000000..e9ef95b
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/reduce.js
@@ -0,0 +1,173 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(
+    Promise, Promise$_CreatePromiseArray,
+    PromiseArray, apiRejection, INTERNAL) {
+
+    function Reduction(callback, index, accum, items, receiver) {
+        this.promise = new Promise(INTERNAL);
+        this.index = index;
+        this.length = items.length;
+        this.items = items;
+        this.callback = callback;
+        this.receiver = receiver;
+        this.accum = accum;
+    }
+
+    Reduction.prototype.reject = function Reduction$reject(e) {
+        this.promise._reject(e);
+    };
+
+    Reduction.prototype.fulfill = function Reduction$fulfill(value, index) {
+        this.accum = value;
+        this.index = index + 1;
+        this.iterate();
+    };
+
+    Reduction.prototype.iterate = function Reduction$iterate() {
+        var i = this.index;
+        var len = this.length;
+        var items = this.items;
+        var result = this.accum;
+        var receiver = this.receiver;
+        var callback = this.callback;
+        var iterate = this.iterate;
+
+        for(; i < len; ++i) {
+            result = Promise._cast(
+                callback.call(
+                    receiver,
+                    result,
+                    items[i],
+                    i,
+                    len
+                ),
+                iterate,
+                void 0
+            );
+
+            if (result instanceof Promise) {
+                result._then(
+                    this.fulfill, this.reject, void 0, this, i, iterate);
+                return;
+            }
+        }
+        this.promise._fulfill(result);
+    };
+
+    function Promise$_reducer(fulfilleds, initialValue) {
+        var fn = this;
+        var receiver = void 0;
+        if (typeof fn !== "function")  {
+            receiver = fn.receiver;
+            fn = fn.fn;
+        }
+        var len = fulfilleds.length;
+        var accum = void 0;
+        var startIndex = 0;
+
+        if (initialValue !== void 0) {
+            accum = initialValue;
+            startIndex = 0;
+        }
+        else {
+            startIndex = 1;
+            if (len > 0) accum = fulfilleds[0];
+        }
+        var i = startIndex;
+
+        if (i >= len) {
+            return accum;
+        }
+
+        var reduction = new Reduction(fn, i, accum, fulfilleds, receiver);
+        reduction.iterate();
+        return reduction.promise;
+    }
+
+    function Promise$_unpackReducer(fulfilleds) {
+        var fn = this.fn;
+        var initialValue = this.initialValue;
+        return Promise$_reducer.call(fn, fulfilleds, initialValue);
+    }
+
+    function Promise$_slowReduce(
+        promises, fn, initialValue, useBound, caller) {
+        return initialValue._then(function callee(initialValue) {
+            return Promise$_Reduce(
+                promises, fn, initialValue, useBound, callee);
+        }, void 0, void 0, void 0, void 0, caller);
+    }
+
+    function Promise$_Reduce(promises, fn, initialValue, useBound, caller) {
+        if (typeof fn !== "function") {
+            return apiRejection("fn must be a function");
+        }
+
+        if (useBound === true && promises._isBound()) {
+            fn = {
+                fn: fn,
+                receiver: promises._boundTo
+            };
+        }
+
+        if (initialValue !== void 0) {
+            if (Promise.is(initialValue)) {
+                if (initialValue.isFulfilled()) {
+                    initialValue = initialValue._settledValue;
+                }
+                else {
+                    return Promise$_slowReduce(promises,
+                        fn, initialValue, useBound, caller);
+                }
+            }
+
+            return Promise$_CreatePromiseArray(promises, PromiseArray, caller,
+                useBound === true && promises._isBound()
+                    ? promises._boundTo
+                    : void 0)
+                .promise()
+                ._then(Promise$_unpackReducer, void 0, void 0, {
+                    fn: fn,
+                    initialValue: initialValue
+                }, void 0, Promise.reduce);
+        }
+        return Promise$_CreatePromiseArray(promises, PromiseArray, caller,
+                useBound === true && promises._isBound()
+                    ? promises._boundTo
+                    : void 0).promise()
+            ._then(Promise$_reducer, void 0, void 0, fn, void 0, caller);
+    }
+
+
+    Promise.reduce = function Promise$Reduce(promises, fn, initialValue) {
+        return Promise$_Reduce(promises, fn,
+            initialValue, false, Promise.reduce);
+    };
+
+    Promise.prototype.reduce = function Promise$reduce(fn, initialValue) {
+        return Promise$_Reduce(this, fn, initialValue,
+                                true, this.reduce);
+    };
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/schedule.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/schedule.js
new file mode 100644
index 0000000..ae2271e
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/schedule.js
@@ -0,0 +1,121 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var global = require("./global.js");
+var schedule;
+if (typeof process !== "undefined" && process !== null &&
+    typeof process.cwd === "function" &&
+    typeof process.nextTick === "function" &&
+    typeof process.version === "string") {
+    schedule = function Promise$_Scheduler(fn) {
+        process.nextTick(fn);
+    };
+}
+else if ((typeof global.MutationObserver === "function" ||
+        typeof global.WebkitMutationObserver === "function" ||
+        typeof global.WebKitMutationObserver === "function") &&
+        typeof document !== "undefined" &&
+        typeof document.createElement === "function") {
+
+
+    schedule = (function(){
+        var MutationObserver = global.MutationObserver ||
+            global.WebkitMutationObserver ||
+            global.WebKitMutationObserver;
+        var div = document.createElement("div");
+        var queuedFn = void 0;
+        var observer = new MutationObserver(
+            function Promise$_Scheduler() {
+                var fn = queuedFn;
+                queuedFn = void 0;
+                fn();
+            }
+       );
+        observer.observe(div, {
+            attributes: true
+        });
+        return function Promise$_Scheduler(fn) {
+            queuedFn = fn;
+            div.setAttribute("class", "foo");
+        };
+
+    })();
+}
+else if (typeof global.postMessage === "function" &&
+    typeof global.importScripts !== "function" &&
+    typeof global.addEventListener === "function" &&
+    typeof global.removeEventListener === "function") {
+
+    var MESSAGE_KEY = "bluebird_message_key_" + Math.random();
+    schedule = (function(){
+        var queuedFn = void 0;
+
+        function Promise$_Scheduler(e) {
+            if (e.source === global &&
+                e.data === MESSAGE_KEY) {
+                var fn = queuedFn;
+                queuedFn = void 0;
+                fn();
+            }
+        }
+
+        global.addEventListener("message", Promise$_Scheduler, false);
+
+        return function Promise$_Scheduler(fn) {
+            queuedFn = fn;
+            global.postMessage(
+                MESSAGE_KEY, "*"
+           );
+        };
+
+    })();
+}
+else if (typeof global.MessageChannel === "function") {
+    schedule = (function(){
+        var queuedFn = void 0;
+
+        var channel = new global.MessageChannel();
+        channel.port1.onmessage = function Promise$_Scheduler() {
+                var fn = queuedFn;
+                queuedFn = void 0;
+                fn();
+        };
+
+        return function Promise$_Scheduler(fn) {
+            queuedFn = fn;
+            channel.port2.postMessage(null);
+        };
+    })();
+}
+else if (global.setTimeout) {
+    schedule = function Promise$_Scheduler(fn) {
+        setTimeout(fn, 4);
+    };
+}
+else {
+    schedule = function Promise$_Scheduler(fn) {
+        fn();
+    };
+}
+
+module.exports = schedule;
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/settle.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/settle.js
new file mode 100644
index 0000000..863882f
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/settle.js
@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports =
+    function(Promise, Promise$_CreatePromiseArray, PromiseArray) {
+
+    var SettledPromiseArray = require("./settled_promise_array.js")(
+        Promise, PromiseArray);
+
+    function Promise$_Settle(promises, useBound, caller) {
+        return Promise$_CreatePromiseArray(
+            promises,
+            SettledPromiseArray,
+            caller,
+            useBound === true && promises._isBound()
+                ? promises._boundTo
+                : void 0
+       ).promise();
+    }
+
+    Promise.settle = function Promise$Settle(promises) {
+        return Promise$_Settle(promises, false, Promise.settle);
+    };
+
+    Promise.prototype.settle = function Promise$settle() {
+        return Promise$_Settle(this, true, this.settle);
+    };
+
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/settled_promise_array.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/settled_promise_array.js
new file mode 100644
index 0000000..fd25d4d
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/settled_promise_array.js
@@ -0,0 +1,60 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, PromiseArray) {
+var PromiseInspection = require("./promise_inspection.js");
+var util = require("./util.js");
+var inherits = util.inherits;
+function SettledPromiseArray(values, caller, boundTo) {
+    this.constructor$(values, caller, boundTo);
+}
+inherits(SettledPromiseArray, PromiseArray);
+
+SettledPromiseArray.prototype._promiseResolved =
+function SettledPromiseArray$_promiseResolved(index, inspection) {
+    this._values[index] = inspection;
+    var totalResolved = ++this._totalResolved;
+    if (totalResolved >= this._length) {
+        this._resolve(this._values);
+    }
+};
+
+SettledPromiseArray.prototype._promiseFulfilled =
+function SettledPromiseArray$_promiseFulfilled(value, index) {
+    if (this._isResolved()) return;
+    var ret = new PromiseInspection();
+    ret._bitField = 268435456;
+    ret._settledValue = value;
+    this._promiseResolved(index, ret);
+};
+SettledPromiseArray.prototype._promiseRejected =
+function SettledPromiseArray$_promiseRejected(reason, index) {
+    if (this._isResolved()) return;
+    var ret = new PromiseInspection();
+    ret._bitField = 134217728;
+    ret._settledValue = reason;
+    this._promiseResolved(index, ret);
+};
+
+return SettledPromiseArray;
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/some.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/some.js
new file mode 100644
index 0000000..21c4ecd
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/some.js
@@ -0,0 +1,57 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports =
+function(Promise, Promise$_CreatePromiseArray, PromiseArray, apiRejection) {
+
+    var SomePromiseArray = require("./some_promise_array.js")(PromiseArray);
+    function Promise$_Some(promises, howMany, useBound, caller) {
+        if ((howMany | 0) !== howMany || howMany < 0) {
+            return apiRejection("expecting a positive integer");
+        }
+        var ret = Promise$_CreatePromiseArray(
+            promises,
+            SomePromiseArray,
+            caller,
+            useBound === true && promises._isBound()
+                ? promises._boundTo
+                : void 0
+       );
+        var promise = ret.promise();
+        if (promise.isRejected()) {
+            return promise;
+        }
+        ret.setHowMany(howMany);
+        ret.init();
+        return promise;
+    }
+
+    Promise.some = function Promise$Some(promises, howMany) {
+        return Promise$_Some(promises, howMany, false, Promise.some);
+    };
+
+    Promise.prototype.some = function Promise$some(count) {
+        return Promise$_Some(this, count, true, this.some);
+    };
+
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/some_promise_array.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/some_promise_array.js
new file mode 100644
index 0000000..d3b89d4
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/some_promise_array.js
@@ -0,0 +1,131 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function (PromiseArray) {
+var util = require("./util.js");
+var RangeError = require("./errors.js").RangeError;
+var inherits = util.inherits;
+var isArray = util.isArray;
+
+function SomePromiseArray(values, caller, boundTo) {
+    this.constructor$(values, caller, boundTo);
+    this._howMany = 0;
+    this._unwrap = false;
+    this._initialized = false;
+}
+inherits(SomePromiseArray, PromiseArray);
+
+SomePromiseArray.prototype._init = function SomePromiseArray$_init() {
+    if (!this._initialized) {
+        return;
+    }
+    if (this._howMany === 0) {
+        this._resolve([]);
+        return;
+    }
+    this._init$(void 0, -2);
+    var isArrayResolved = isArray(this._values);
+    this._holes = isArrayResolved ? this._values.length - this.length() : 0;
+
+    if (!this._isResolved() &&
+        isArrayResolved &&
+        this._howMany > this._canPossiblyFulfill()) {
+        var message = "(Promise.some) input array contains less than " +
+                        this._howMany  + " promises";
+        this._reject(new RangeError(message));
+    }
+};
+
+SomePromiseArray.prototype.init = function SomePromiseArray$init() {
+    this._initialized = true;
+    this._init();
+};
+
+SomePromiseArray.prototype.setUnwrap = function SomePromiseArray$setUnwrap() {
+    this._unwrap = true;
+};
+
+SomePromiseArray.prototype.howMany = function SomePromiseArray$howMany() {
+    return this._howMany;
+};
+
+SomePromiseArray.prototype.setHowMany =
+function SomePromiseArray$setHowMany(count) {
+    if (this._isResolved()) return;
+    this._howMany = count;
+};
+
+SomePromiseArray.prototype._promiseFulfilled =
+function SomePromiseArray$_promiseFulfilled(value) {
+    if (this._isResolved()) return;
+    this._addFulfilled(value);
+    if (this._fulfilled() === this.howMany()) {
+        this._values.length = this.howMany();
+        if (this.howMany() === 1 && this._unwrap) {
+            this._resolve(this._values[0]);
+        }
+        else {
+            this._resolve(this._values);
+        }
+    }
+
+};
+SomePromiseArray.prototype._promiseRejected =
+function SomePromiseArray$_promiseRejected(reason) {
+    if (this._isResolved()) return;
+    this._addRejected(reason);
+    if (this.howMany() > this._canPossiblyFulfill()) {
+        if (this._values.length === this.length()) {
+            this._reject([]);
+        }
+        else {
+            this._reject(this._values.slice(this.length() + this._holes));
+        }
+    }
+};
+
+SomePromiseArray.prototype._fulfilled = function SomePromiseArray$_fulfilled() {
+    return this._totalResolved;
+};
+
+SomePromiseArray.prototype._rejected = function SomePromiseArray$_rejected() {
+    return this._values.length - this.length() - this._holes;
+};
+
+SomePromiseArray.prototype._addRejected =
+function SomePromiseArray$_addRejected(reason) {
+    this._values.push(reason);
+};
+
+SomePromiseArray.prototype._addFulfilled =
+function SomePromiseArray$_addFulfilled(value) {
+    this._values[this._totalResolved++] = value;
+};
+
+SomePromiseArray.prototype._canPossiblyFulfill =
+function SomePromiseArray$_canPossiblyFulfill() {
+    return this.length() - this._rejected();
+};
+
+return SomePromiseArray;
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/synchronous_inspection.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/synchronous_inspection.js
new file mode 100644
index 0000000..dcbdc90
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/synchronous_inspection.js
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise) {
+    var PromiseInspection = require("./promise_inspection.js");
+
+    Promise.prototype.inspect = function Promise$inspect() {
+        return new PromiseInspection(this);
+    };
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/thenables.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/thenables.js
new file mode 100644
index 0000000..510da18
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/thenables.js
@@ -0,0 +1,138 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, INTERNAL) {
+    var util = require("./util.js");
+    var canAttach = require("./errors.js").canAttach;
+    var errorObj = util.errorObj;
+    var isObject = util.isObject;
+
+    function getThen(obj) {
+        try {
+            return obj.then;
+        }
+        catch(e) {
+            errorObj.e = e;
+            return errorObj;
+        }
+    }
+
+    function Promise$_Cast(obj, caller, originalPromise) {
+        if (isObject(obj)) {
+            if (obj instanceof Promise) {
+                return obj;
+            }
+            else if (isAnyBluebirdPromise(obj)) {
+                var ret = new Promise(INTERNAL);
+                ret._setTrace(caller, void 0);
+                obj._then(
+                    ret._fulfillUnchecked,
+                    ret._rejectUncheckedCheckError,
+                    ret._progressUnchecked,
+                    ret,
+                    null,
+                    void 0
+                );
+                ret._setFollowing();
+                return ret;
+            }
+            var then = getThen(obj);
+            if (then === errorObj) {
+                caller = typeof caller === "function" ? caller : Promise$_Cast;
+                if (originalPromise !== void 0 && canAttach(then.e)) {
+                    originalPromise._attachExtraTrace(then.e);
+                }
+                return Promise.reject(then.e, caller);
+            }
+            else if (typeof then === "function") {
+                caller = typeof caller === "function" ? caller : Promise$_Cast;
+                return Promise$_doThenable(obj, then, caller, originalPromise);
+            }
+        }
+        return obj;
+    }
+
+    var hasProp = {}.hasOwnProperty;
+    function isAnyBluebirdPromise(obj) {
+        return hasProp.call(obj, "_promise0");
+    }
+
+    function Promise$_doThenable(x, then, caller, originalPromise) {
+        var resolver = Promise.defer(caller);
+        var called = false;
+        try {
+            then.call(
+                x,
+                Promise$_resolveFromThenable,
+                Promise$_rejectFromThenable,
+                Promise$_progressFromThenable
+            );
+        }
+        catch(e) {
+            if (!called) {
+                called = true;
+                var trace = canAttach(e) ? e : new Error(e + "");
+                if (originalPromise !== void 0) {
+                    originalPromise._attachExtraTrace(trace);
+                }
+                resolver.promise._reject(e, trace);
+            }
+        }
+        return resolver.promise;
+
+        function Promise$_resolveFromThenable(y) {
+            if (called) return;
+            called = true;
+
+            if (x === y) {
+                var e = Promise._makeSelfResolutionError();
+                if (originalPromise !== void 0) {
+                    originalPromise._attachExtraTrace(e);
+                }
+                resolver.promise._reject(e, void 0);
+                return;
+            }
+            resolver.resolve(y);
+        }
+
+        function Promise$_rejectFromThenable(r) {
+            if (called) return;
+            called = true;
+            var trace = canAttach(r) ? r : new Error(r + "");
+            if (originalPromise !== void 0) {
+                originalPromise._attachExtraTrace(trace);
+            }
+            resolver.promise._reject(r, trace);
+        }
+
+        function Promise$_progressFromThenable(v) {
+            if (called) return;
+            var promise = resolver.promise;
+            if (typeof promise._progress === "function") {
+                promise._progress(v);
+            }
+        }
+    }
+
+    Promise._cast = Promise$_Cast;
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/timers.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/timers.js
new file mode 100644
index 0000000..8edcac2
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/timers.js
@@ -0,0 +1,114 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+
+var global = require("./global.js");
+var setTimeout = function(fn, time) {
+    var $_len = arguments.length;var args = new Array($_len - 2); for(var $_i = 2; $_i < $_len; ++$_i) {args[$_i - 2] = arguments[$_i];}
+    global.setTimeout(function() {
+        fn.apply(void 0, args);
+    }, time);
+};
+
+var pass = {};
+global.setTimeout( function(_) {
+    if(_ === pass) {
+        setTimeout = global.setTimeout;
+    }
+}, 1, pass);
+
+module.exports = function(Promise, INTERNAL) {
+    var util = require("./util.js");
+    var errors = require("./errors.js");
+    var apiRejection = require("./errors_api_rejection")(Promise);
+    var TimeoutError = Promise.TimeoutError;
+
+    var afterTimeout = function Promise$_afterTimeout(promise, message, ms) {
+        if (!promise.isPending()) return;
+        if (typeof message !== "string") {
+            message = "operation timed out after" + " " + ms + " ms"
+        }
+        var err = new TimeoutError(message);
+        errors.markAsOriginatingFromRejection(err);
+        promise._attachExtraTrace(err);
+        promise._rejectUnchecked(err);
+    };
+
+    var afterDelay = function Promise$_afterDelay(value, promise) {
+        promise._fulfill(value);
+    };
+
+    Promise.delay = function Promise$Delay(value, ms, caller) {
+        if (ms === void 0) {
+            ms = value;
+            value = void 0;
+        }
+        ms = +ms;
+        if (typeof caller !== "function") {
+            caller = Promise.delay;
+        }
+        var maybePromise = Promise._cast(value, caller, void 0);
+        var promise = new Promise(INTERNAL);
+
+        if (Promise.is(maybePromise)) {
+            if (maybePromise._isBound()) {
+                promise._setBoundTo(maybePromise._boundTo);
+            }
+            if (maybePromise._cancellable()) {
+                promise._setCancellable();
+                promise._cancellationParent = maybePromise;
+            }
+            promise._setTrace(caller, maybePromise);
+            promise._follow(maybePromise);
+            return promise.then(function(value) {
+                return Promise.delay(value, ms);
+            });
+        }
+        else {
+            promise._setTrace(caller, void 0);
+            setTimeout(afterDelay, ms, value, promise);
+        }
+        return promise;
+    };
+
+    Promise.prototype.delay = function Promise$delay(ms) {
+        return Promise.delay(this, ms, this.delay);
+    };
+
+    Promise.prototype.timeout = function Promise$timeout(ms, message) {
+        ms = +ms;
+
+        var ret = new Promise(INTERNAL);
+        ret._setTrace(this.timeout, this);
+
+        if (this._isBound()) ret._setBoundTo(this._boundTo);
+        if (this._cancellable()) {
+            ret._setCancellable();
+            ret._cancellationParent = this;
+        }
+        ret._follow(this);
+        setTimeout(afterTimeout, ms, ret, message, ms);
+        return ret;
+    };
+
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/util.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/util.js
new file mode 100644
index 0000000..fbd34e9
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/main/util.js
@@ -0,0 +1,193 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var global = require("./global.js");
+var es5 = require("./es5.js");
+var haveGetters = (function(){
+    try {
+        var o = {};
+        es5.defineProperty(o, "f", {
+            get: function () {
+                return 3;
+            }
+        });
+        return o.f === 3;
+    }
+    catch (e) {
+        return false;
+    }
+
+})();
+
+var canEvaluate = (function() {
+    if (typeof window !== "undefined" && window !== null &&
+        typeof window.document !== "undefined" &&
+        typeof navigator !== "undefined" && navigator !== null &&
+        typeof navigator.appName === "string" &&
+        window === global) {
+        return false;
+    }
+    return true;
+})();
+
+function deprecated(msg) {
+    if (typeof console !== "undefined" && console !== null &&
+        typeof console.warn === "function") {
+        console.warn("Bluebird: " + msg);
+    }
+}
+
+var errorObj = {e: {}};
+function tryCatch1(fn, receiver, arg) {
+    try {
+        return fn.call(receiver, arg);
+    }
+    catch (e) {
+        errorObj.e = e;
+        return errorObj;
+    }
+}
+
+function tryCatch2(fn, receiver, arg, arg2) {
+    try {
+        return fn.call(receiver, arg, arg2);
+    }
+    catch (e) {
+        errorObj.e = e;
+        return errorObj;
+    }
+}
+
+function tryCatchApply(fn, args, receiver) {
+    try {
+        return fn.apply(receiver, args);
+    }
+    catch (e) {
+        errorObj.e = e;
+        return errorObj;
+    }
+}
+
+var inherits = function(Child, Parent) {
+    var hasProp = {}.hasOwnProperty;
+
+    function T() {
+        this.constructor = Child;
+        this.constructor$ = Parent;
+        for (var propertyName in Parent.prototype) {
+            if (hasProp.call(Parent.prototype, propertyName) &&
+                propertyName.charAt(propertyName.length-1) !== "$"
+           ) {
+                this[propertyName + "$"] = Parent.prototype[propertyName];
+            }
+        }
+    }
+    T.prototype = Parent.prototype;
+    Child.prototype = new T();
+    return Child.prototype;
+};
+
+function asString(val) {
+    return typeof val === "string" ? val : ("" + val);
+}
+
+function isPrimitive(val) {
+    return val == null || val === true || val === false ||
+        typeof val === "string" || typeof val === "number";
+
+}
+
+function isObject(value) {
+    return !isPrimitive(value);
+}
+
+function maybeWrapAsError(maybeError) {
+    if (!isPrimitive(maybeError)) return maybeError;
+
+    return new Error(asString(maybeError));
+}
+
+function withAppended(target, appendee) {
+    var len = target.length;
+    var ret = new Array(len + 1);
+    var i;
+    for (i = 0; i < len; ++i) {
+        ret[i] = target[i];
+    }
+    ret[i] = appendee;
+    return ret;
+}
+
+
+function notEnumerableProp(obj, name, value) {
+    if (isPrimitive(obj)) return obj;
+    var descriptor = {
+        value: value,
+        configurable: true,
+        enumerable: false,
+        writable: true
+    };
+    es5.defineProperty(obj, name, descriptor);
+    return obj;
+}
+
+
+var wrapsPrimitiveReceiver = (function() {
+    return this !== "string";
+}).call("string");
+
+function thrower(r) {
+    throw r;
+}
+
+
+function toFastProperties(obj) {
+    /*jshint -W027*/
+    function f() {}
+    f.prototype = obj;
+    return f;
+    eval(obj);
+}
+
+var ret = {
+    thrower: thrower,
+    isArray: es5.isArray,
+    haveGetters: haveGetters,
+    notEnumerableProp: notEnumerableProp,
+    isPrimitive: isPrimitive,
+    isObject: isObject,
+    canEvaluate: canEvaluate,
+    deprecated: deprecated,
+    errorObj: errorObj,
+    tryCatch1: tryCatch1,
+    tryCatch2: tryCatch2,
+    tryCatchApply: tryCatchApply,
+    inherits: inherits,
+    withAppended: withAppended,
+    asString: asString,
+    maybeWrapAsError: maybeWrapAsError,
+    wrapsPrimitiveReceiver: wrapsPrimitiveReceiver,
+    toFastProperties: toFastProperties
+};
+
+module.exports = ret;
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/any.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/any.js
new file mode 100644
index 0000000..8d174cf
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/any.js
@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, Promise$_CreatePromiseArray, PromiseArray) {
+
+    var SomePromiseArray = require("./some_promise_array.js")(PromiseArray);
+    function Promise$_Any(promises, useBound, caller) {
+        var ret = Promise$_CreatePromiseArray(
+            promises,
+            SomePromiseArray,
+            caller,
+            useBound === true && promises._isBound()
+                ? promises._boundTo
+                : void 0
+       );
+        var promise = ret.promise();
+        if (promise.isRejected()) {
+            return promise;
+        }
+        ret.setHowMany(1);
+        ret.setUnwrap();
+        ret.init();
+        return promise;
+    }
+
+    Promise.any = function Promise$Any(promises) {
+        return Promise$_Any(promises, false, Promise.any);
+    };
+
+    Promise.prototype.any = function Promise$any() {
+        return Promise$_Any(this, true, this.any);
+    };
+
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/assert.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/assert.js
new file mode 100644
index 0000000..4adb8c2
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/assert.js
@@ -0,0 +1,83 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = (function(){
+    var AssertionError = (function() {
+        function AssertionError(a) {
+            this.constructor$(a);
+            this.message = a;
+            this.name = "AssertionError";
+        }
+        AssertionError.prototype = new Error();
+        AssertionError.prototype.constructor = AssertionError;
+        AssertionError.prototype.constructor$ = Error;
+        return AssertionError;
+    })();
+
+    function getParams(args) {
+        var params = [];
+        for (var i = 0; i < args.length; ++i) params.push("arg" + i);
+        return params;
+    }
+
+    function nativeAssert(callName, args, expect) {
+        try {
+            var params = getParams(args);
+            var constructorArgs = params;
+            constructorArgs.push("return " +
+                    callName + "("+ params.join(",") + ");");
+            var fn = Function.apply(null, constructorArgs);
+            return fn.apply(null, args);
+        }
+        catch (e) {
+            if (!(e instanceof SyntaxError)) {
+                throw e;
+            }
+            else {
+                return expect;
+            }
+        }
+    }
+
+    return function assert(boolExpr, message) {
+        if (boolExpr === true) return;
+
+        if (typeof boolExpr === "string" &&
+            boolExpr.charAt(0) === "%") {
+            var nativeCallName = boolExpr;
+            var $_len = arguments.length;var args = new Array($_len - 2); for(var $_i = 2; $_i < $_len; ++$_i) {args[$_i - 2] = arguments[$_i];}
+            if (nativeAssert(nativeCallName, args, message) === message) return;
+            message = (nativeCallName + " !== " + message);
+        }
+
+        var ret = new AssertionError(message);
+        if (Error.captureStackTrace) {
+            Error.captureStackTrace(ret, assert);
+        }
+        if (console && console.error) {
+            console.error(ret.stack + "");
+        }
+        throw ret;
+
+    };
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/async.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/async.js
new file mode 100644
index 0000000..6f32b10
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/async.js
@@ -0,0 +1,111 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var schedule = require("./schedule.js");
+var Queue = require("./queue.js");
+var errorObj = require("./util.js").errorObj;
+var tryCatch1 = require("./util.js").tryCatch1;
+var process = require("./global.js").process;
+
+function Async() {
+    this._isTickUsed = false;
+    this._length = 0;
+    this._lateBuffer = new Queue();
+    this._functionBuffer = new Queue(25000 * 3);
+    var self = this;
+    this.consumeFunctionBuffer = function Async$consumeFunctionBuffer() {
+        self._consumeFunctionBuffer();
+    };
+}
+
+Async.prototype.haveItemsQueued = function Async$haveItemsQueued() {
+    return this._length > 0;
+};
+
+Async.prototype.invokeLater = function Async$invokeLater(fn, receiver, arg) {
+    if (process !== void 0 &&
+        process.domain != null &&
+        !fn.domain) {
+        fn = process.domain.bind(fn);
+    }
+    this._lateBuffer.push(fn, receiver, arg);
+    this._queueTick();
+};
+
+Async.prototype.invoke = function Async$invoke(fn, receiver, arg) {
+    if (process !== void 0 &&
+        process.domain != null &&
+        !fn.domain) {
+        fn = process.domain.bind(fn);
+    }
+    var functionBuffer = this._functionBuffer;
+    functionBuffer.push(fn, receiver, arg);
+    this._length = functionBuffer.length();
+    this._queueTick();
+};
+
+Async.prototype._consumeFunctionBuffer =
+function Async$_consumeFunctionBuffer() {
+    var functionBuffer = this._functionBuffer;
+    while(functionBuffer.length() > 0) {
+        var fn = functionBuffer.shift();
+        var receiver = functionBuffer.shift();
+        var arg = functionBuffer.shift();
+        fn.call(receiver, arg);
+    }
+    this._reset();
+    this._consumeLateBuffer();
+};
+
+Async.prototype._consumeLateBuffer = function Async$_consumeLateBuffer() {
+    var buffer = this._lateBuffer;
+    while(buffer.length() > 0) {
+        var fn = buffer.shift();
+        var receiver = buffer.shift();
+        var arg = buffer.shift();
+        var res = tryCatch1(fn, receiver, arg);
+        if (res === errorObj) {
+            this._queueTick();
+            if (fn.domain != null) {
+                fn.domain.emit("error", res.e);
+            }
+            else {
+                throw res.e;
+            }
+        }
+    }
+};
+
+Async.prototype._queueTick = function Async$_queue() {
+    if (!this._isTickUsed) {
+        schedule(this.consumeFunctionBuffer);
+        this._isTickUsed = true;
+    }
+};
+
+Async.prototype._reset = function Async$_reset() {
+    this._isTickUsed = false;
+    this._length = 0;
+};
+
+module.exports = new Async();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/bluebird.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/bluebird.js
new file mode 100644
index 0000000..6fd85f1
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/bluebird.js
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var Promise = require("./promise.js")();
+module.exports = Promise;
\ No newline at end of file
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/call_get.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/call_get.js
new file mode 100644
index 0000000..2a3c1f5
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/call_get.js
@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise) {
+    Promise.prototype.call = function Promise$call(propertyName) {
+        var $_len = arguments.length;var args = new Array($_len - 1); for(var $_i = 1; $_i < $_len; ++$_i) {args[$_i - 1] = arguments[$_i];}
+
+        return this._then(function(obj) {
+                return obj[propertyName].apply(obj, args);
+            },
+            void 0,
+            void 0,
+            void 0,
+            void 0,
+            this.call
+       );
+    };
+
+    function Promise$getter(obj) {
+        var prop = typeof this === "string"
+            ? this
+            : ("" + this);
+        return obj[prop];
+    }
+    Promise.prototype.get = function Promise$get(propertyName) {
+        return this._then(
+            Promise$getter,
+            void 0,
+            void 0,
+            propertyName,
+            void 0,
+            this.get
+       );
+    };
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/cancel.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/cancel.js
new file mode 100644
index 0000000..542b488
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/cancel.js
@@ -0,0 +1,77 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, INTERNAL) {
+    var errors = require("./errors.js");
+    var async = require("./async.js");
+    var CancellationError = errors.CancellationError;
+    var SYNC_TOKEN = {};
+
+    Promise.prototype._cancel = function Promise$_cancel() {
+        if (!this.isCancellable()) return this;
+        var parent;
+        if ((parent = this._cancellationParent) !== void 0) {
+            parent.cancel(SYNC_TOKEN);
+            return;
+        }
+        var err = new CancellationError();
+        this._attachExtraTrace(err);
+        this._rejectUnchecked(err);
+    };
+
+    Promise.prototype.cancel = function Promise$cancel(token) {
+        if (!this.isCancellable()) return this;
+        if (token === SYNC_TOKEN) {
+            this._cancel();
+            return this;
+        }
+        async.invokeLater(this._cancel, this, void 0);
+        return this;
+    };
+
+    Promise.prototype.cancellable = function Promise$cancellable() {
+        if (this._cancellable()) return this;
+        this._setCancellable();
+        this._cancellationParent = void 0;
+        return this;
+    };
+
+    Promise.prototype.uncancellable = function Promise$uncancellable() {
+        var ret = new Promise(INTERNAL);
+        ret._setTrace(this.uncancellable, this);
+        ret._follow(this);
+        ret._unsetCancellable();
+        if (this._isBound()) ret._setBoundTo(this._boundTo);
+        return ret;
+    };
+
+    Promise.prototype.fork =
+    function Promise$fork(didFulfill, didReject, didProgress) {
+        var ret = this._then(didFulfill, didReject, didProgress,
+            void 0, void 0, this.fork);
+
+        ret._setCancellable();
+        ret._cancellationParent = void 0;
+        return ret;
+    };
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/captured_trace.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/captured_trace.js
new file mode 100644
index 0000000..af34f11
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/captured_trace.js
@@ -0,0 +1,237 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function() {
+var inherits = require("./util.js").inherits;
+var defineProperty = require("./es5.js").defineProperty;
+
+var rignore = new RegExp(
+    "\\b(?:[\\w.]*Promise(?:Array|Spawn)?\\$_\\w+|" +
+    "tryCatch(?:1|2|Apply)|new \\w*PromiseArray|" +
+    "\\w*PromiseArray\\.\\w*PromiseArray|" +
+    "setTimeout|CatchFilter\\$_\\w+|makeNodePromisified|processImmediate|" +
+    "process._tickCallback|nextTick|Async\\$\\w+)\\b"
+);
+
+var rtraceline = null;
+var formatStack = null;
+var areNamesMangled = false;
+
+function formatNonError(obj) {
+    var str;
+    if (typeof obj === "function") {
+        str = "[function " +
+            (obj.name || "anonymous") +
+            "]";
+    }
+    else {
+        str = obj.toString();
+        var ruselessToString = /\[object [a-zA-Z0-9$_]+\]/;
+        if (ruselessToString.test(str)) {
+            try {
+                var newStr = JSON.stringify(obj);
+                str = newStr;
+            }
+            catch(e) {
+
+            }
+        }
+        if (str.length === 0) {
+            str = "(empty array)";
+        }
+    }
+    return ("(<" + snip(str) + ">, no stack trace)");
+}
+
+function snip(str) {
+    var maxChars = 41;
+    if (str.length < maxChars) {
+        return str;
+    }
+    return str.substr(0, maxChars - 3) + "...";
+}
+
+function CapturedTrace(ignoreUntil, isTopLevel) {
+    if (!areNamesMangled) {
+    }
+    this.captureStackTrace(ignoreUntil, isTopLevel);
+
+}
+inherits(CapturedTrace, Error);
+
+CapturedTrace.prototype.captureStackTrace =
+function CapturedTrace$captureStackTrace(ignoreUntil, isTopLevel) {
+    captureStackTrace(this, ignoreUntil, isTopLevel);
+};
+
+CapturedTrace.possiblyUnhandledRejection =
+function CapturedTrace$PossiblyUnhandledRejection(reason) {
+    if (typeof console === "object") {
+        var message;
+        if (typeof reason === "object" || typeof reason === "function") {
+            var stack = reason.stack;
+            message = "Possibly unhandled " + formatStack(stack, reason);
+        }
+        else {
+            message = "Possibly unhandled " + String(reason);
+        }
+        if (typeof console.error === "function" ||
+            typeof console.error === "object") {
+            console.error(message);
+        }
+        else if (typeof console.log === "function" ||
+            typeof console.error === "object") {
+            console.log(message);
+        }
+    }
+};
+
+areNamesMangled = CapturedTrace.prototype.captureStackTrace.name !==
+    "CapturedTrace$captureStackTrace";
+
+CapturedTrace.combine = function CapturedTrace$Combine(current, prev) {
+    var curLast = current.length - 1;
+    for (var i = prev.length - 1; i >= 0; --i) {
+        var line = prev[i];
+        if (current[curLast] === line) {
+            current.pop();
+            curLast--;
+        }
+        else {
+            break;
+        }
+    }
+
+    current.push("From previous event:");
+    var lines = current.concat(prev);
+
+    var ret = [];
+
+
+    for (var i = 0, len = lines.length; i < len; ++i) {
+
+        if ((rignore.test(lines[i]) ||
+            (i > 0 && !rtraceline.test(lines[i])) &&
+            lines[i] !== "From previous event:")
+       ) {
+            continue;
+        }
+        ret.push(lines[i]);
+    }
+    return ret;
+};
+
+CapturedTrace.isSupported = function CapturedTrace$IsSupported() {
+    return typeof captureStackTrace === "function";
+};
+
+var captureStackTrace = (function stackDetection() {
+    if (typeof Error.stackTraceLimit === "number" &&
+        typeof Error.captureStackTrace === "function") {
+        rtraceline = /^\s*at\s*/;
+        formatStack = function(stack, error) {
+            if (typeof stack === "string") return stack;
+
+            if (error.name !== void 0 &&
+                error.message !== void 0) {
+                return error.name + ". " + error.message;
+            }
+            return formatNonError(error);
+
+
+        };
+        var captureStackTrace = Error.captureStackTrace;
+        return function CapturedTrace$_captureStackTrace(
+            receiver, ignoreUntil) {
+            captureStackTrace(receiver, ignoreUntil);
+        };
+    }
+    var err = new Error();
+
+    if (!areNamesMangled && typeof err.stack === "string" &&
+        typeof "".startsWith === "function" &&
+        (err.stack.startsWith("stackDetection@")) &&
+        stackDetection.name === "stackDetection") {
+
+        defineProperty(Error, "stackTraceLimit", {
+            writable: true,
+            enumerable: false,
+            configurable: false,
+            value: 25
+        });
+        rtraceline = /@/;
+        var rline = /[@\n]/;
+
+        formatStack = function(stack, error) {
+            if (typeof stack === "string") {
+                return (error.name + ". " + error.message + "\n" + stack);
+            }
+
+            if (error.name !== void 0 &&
+                error.message !== void 0) {
+                return error.name + ". " + error.message;
+            }
+            return formatNonError(error);
+        };
+
+        return function captureStackTrace(o, fn) {
+            var name = fn.name;
+            var stack = new Error().stack;
+            var split = stack.split(rline);
+            var i, len = split.length;
+            for (i = 0; i < len; i += 2) {
+                if (split[i] === name) {
+                    break;
+                }
+            }
+            split = split.slice(i + 2);
+            len = split.length - 2;
+            var ret = "";
+            for (i = 0; i < len; i += 2) {
+                ret += split[i];
+                ret += "@";
+                ret += split[i + 1];
+                ret += "\n";
+            }
+            o.stack = ret;
+        };
+    }
+    else {
+        formatStack = function(stack, error) {
+            if (typeof stack === "string") return stack;
+
+            if ((typeof error === "object" ||
+                typeof error === "function") &&
+                error.name !== void 0 &&
+                error.message !== void 0) {
+                return error.name + ". " + error.message;
+            }
+            return formatNonError(error);
+        };
+
+        return null;
+    }
+})();
+
+return CapturedTrace;
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/catch_filter.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/catch_filter.js
new file mode 100644
index 0000000..8b42af1
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/catch_filter.js
@@ -0,0 +1,93 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(NEXT_FILTER) {
+var util = require("./util.js");
+var errors = require("./errors.js");
+var tryCatch1 = util.tryCatch1;
+var errorObj = util.errorObj;
+var keys = require("./es5.js").keys;
+
+function CatchFilter(instances, callback, promise) {
+    this._instances = instances;
+    this._callback = callback;
+    this._promise = promise;
+}
+
+function CatchFilter$_safePredicate(predicate, e) {
+    var safeObject = {};
+    var retfilter = tryCatch1(predicate, safeObject, e);
+
+    if (retfilter === errorObj) return retfilter;
+
+    var safeKeys = keys(safeObject);
+    if (safeKeys.length) {
+        errorObj.e = new TypeError(
+            "Catch filter must inherit from Error "
+          + "or be a simple predicate function");
+        return errorObj;
+    }
+    return retfilter;
+}
+
+CatchFilter.prototype.doFilter = function CatchFilter$_doFilter(e) {
+    var cb = this._callback;
+    var promise = this._promise;
+    var boundTo = promise._isBound() ? promise._boundTo : void 0;
+    for (var i = 0, len = this._instances.length; i < len; ++i) {
+        var item = this._instances[i];
+        var itemIsErrorType = item === Error ||
+            (item != null && item.prototype instanceof Error);
+
+        if (itemIsErrorType && e instanceof item) {
+            var ret = tryCatch1(cb, boundTo, e);
+            if (ret === errorObj) {
+                NEXT_FILTER.e = ret.e;
+                return NEXT_FILTER;
+            }
+            return ret;
+        } else if (typeof item === "function" && !itemIsErrorType) {
+            var shouldHandle = CatchFilter$_safePredicate(item, e);
+            if (shouldHandle === errorObj) {
+                var trace = errors.canAttach(errorObj.e)
+                    ? errorObj.e
+                    : new Error(errorObj.e + "");
+                this._promise._attachExtraTrace(trace);
+                e = errorObj.e;
+                break;
+            } else if (shouldHandle) {
+                var ret = tryCatch1(cb, boundTo, e);
+                if (ret === errorObj) {
+                    NEXT_FILTER.e = ret.e;
+                    return NEXT_FILTER;
+                }
+                return ret;
+            }
+        }
+    }
+    NEXT_FILTER.e = e;
+    return NEXT_FILTER;
+};
+
+return CatchFilter;
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/direct_resolve.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/direct_resolve.js
new file mode 100644
index 0000000..f4d5384
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/direct_resolve.js
@@ -0,0 +1,83 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var util = require("./util.js");
+var isPrimitive = util.isPrimitive;
+var wrapsPrimitiveReceiver = util.wrapsPrimitiveReceiver;
+
+module.exports = function(Promise) {
+var returner = function Promise$_returner() {
+    return this;
+};
+var thrower = function Promise$_thrower() {
+    throw this;
+};
+
+var wrapper = function Promise$_wrapper(value, action) {
+    if (action === 1) {
+        return function Promise$_thrower() {
+            throw value;
+        };
+    }
+    else if (action === 2) {
+        return function Promise$_returner() {
+            return value;
+        };
+    }
+};
+
+
+Promise.prototype["return"] =
+Promise.prototype.thenReturn =
+function Promise$thenReturn(value) {
+    if (wrapsPrimitiveReceiver && isPrimitive(value)) {
+        return this._then(
+            wrapper(value, 2),
+            void 0,
+            void 0,
+            void 0,
+            void 0,
+            this.thenReturn
+       );
+    }
+    return this._then(returner, void 0, void 0,
+                        value, void 0, this.thenReturn);
+};
+
+Promise.prototype["throw"] =
+Promise.prototype.thenThrow =
+function Promise$thenThrow(reason) {
+    if (wrapsPrimitiveReceiver && isPrimitive(reason)) {
+        return this._then(
+            wrapper(reason, 1),
+            void 0,
+            void 0,
+            void 0,
+            void 0,
+            this.thenThrow
+       );
+    }
+    return this._then(thrower, void 0, void 0,
+                        reason, void 0, this.thenThrow);
+};
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/errors.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/errors.js
new file mode 100644
index 0000000..35fb66e
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/errors.js
@@ -0,0 +1,114 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var global = require("./global.js");
+var Objectfreeze = require("./es5.js").freeze;
+var util = require("./util.js");
+var inherits = util.inherits;
+var notEnumerableProp = util.notEnumerableProp;
+var Error = global.Error;
+
+function markAsOriginatingFromRejection(e) {
+    try {
+        notEnumerableProp(e, "isAsync", true);
+    }
+    catch(ignore) {}
+}
+
+function originatesFromRejection(e) {
+    if (e == null) return false;
+    return ((e instanceof RejectionError) ||
+        e["isAsync"] === true);
+}
+
+function isError(obj) {
+    return obj instanceof Error;
+}
+
+function canAttach(obj) {
+    return isError(obj);
+}
+
+function subError(nameProperty, defaultMessage) {
+    function SubError(message) {
+        if (!(this instanceof SubError)) return new SubError(message);
+        this.message = typeof message === "string" ? message : defaultMessage;
+        this.name = nameProperty;
+        if (Error.captureStackTrace) {
+            Error.captureStackTrace(this, this.constructor);
+        }
+    }
+    inherits(SubError, Error);
+    return SubError;
+}
+
+var TypeError = global.TypeError;
+if (typeof TypeError !== "function") {
+    TypeError = subError("TypeError", "type error");
+}
+var RangeError = global.RangeError;
+if (typeof RangeError !== "function") {
+    RangeError = subError("RangeError", "range error");
+}
+var CancellationError = subError("CancellationError", "cancellation error");
+var TimeoutError = subError("TimeoutError", "timeout error");
+
+function RejectionError(message) {
+    this.name = "RejectionError";
+    this.message = message;
+    this.cause = message;
+    this.isAsync = true;
+
+    if (message instanceof Error) {
+        this.message = message.message;
+        this.stack = message.stack;
+    }
+    else if (Error.captureStackTrace) {
+        Error.captureStackTrace(this, this.constructor);
+    }
+
+}
+inherits(RejectionError, Error);
+
+var key = "__BluebirdErrorTypes__";
+var errorTypes = global[key];
+if (!errorTypes) {
+    errorTypes = Objectfreeze({
+        CancellationError: CancellationError,
+        TimeoutError: TimeoutError,
+        RejectionError: RejectionError
+    });
+    notEnumerableProp(global, key, errorTypes);
+}
+
+module.exports = {
+    Error: Error,
+    TypeError: TypeError,
+    RangeError: RangeError,
+    CancellationError: errorTypes.CancellationError,
+    RejectionError: errorTypes.RejectionError,
+    TimeoutError: errorTypes.TimeoutError,
+    originatesFromRejection: originatesFromRejection,
+    markAsOriginatingFromRejection: markAsOriginatingFromRejection,
+    canAttach: canAttach
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/errors_api_rejection.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/errors_api_rejection.js
new file mode 100644
index 0000000..e953e3b
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/errors_api_rejection.js
@@ -0,0 +1,38 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise) {
+var TypeError = require('./errors.js').TypeError;
+
+function apiRejection(msg) {
+    var error = new TypeError(msg);
+    var ret = Promise.rejected(error);
+    var parent = ret._peekContext();
+    if (parent != null) {
+        parent._attachExtraTrace(error);
+    }
+    return ret;
+}
+
+return apiRejection;
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/es5.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/es5.js
new file mode 100644
index 0000000..e22a0a9
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/es5.js
@@ -0,0 +1,89 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+var isES5 = (function(){
+    "use strict";
+    return this === void 0;
+})();
+
+if (isES5) {
+    module.exports = {
+        freeze: Object.freeze,
+        defineProperty: Object.defineProperty,
+        keys: Object.keys,
+        getPrototypeOf: Object.getPrototypeOf,
+        isArray: Array.isArray,
+        isES5: isES5
+    };
+}
+
+else {
+    var has = {}.hasOwnProperty;
+    var str = {}.toString;
+    var proto = {}.constructor.prototype;
+
+    function ObjectKeys(o) {
+        var ret = [];
+        for (var key in o) {
+            if (has.call(o, key)) {
+                ret.push(key);
+            }
+        }
+        return ret;
+    }
+
+    function ObjectDefineProperty(o, key, desc) {
+        o[key] = desc.value;
+        return o;
+    }
+
+    function ObjectFreeze(obj) {
+        return obj;
+    }
+
+    function ObjectGetPrototypeOf(obj) {
+        try {
+            return Object(obj).constructor.prototype;
+        }
+        catch (e) {
+            return proto;
+        }
+    }
+
+    function ArrayIsArray(obj) {
+        try {
+            return str.call(obj) === "[object Array]";
+        }
+        catch(e) {
+            return false;
+        }
+    }
+
+    module.exports = {
+        isArray: ArrayIsArray,
+        keys: ObjectKeys,
+        defineProperty: ObjectDefineProperty,
+        freeze: ObjectFreeze,
+        getPrototypeOf: ObjectGetPrototypeOf,
+        isES5: isES5
+    };
+}
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/filter.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/filter.js
new file mode 100644
index 0000000..a4b8ae7
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/filter.js
@@ -0,0 +1,53 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise) {
+    var isArray = require("./util.js").isArray;
+
+    function Promise$_filter(booleans) {
+        var values = this._settledValue;
+        var len = values.length;
+        var ret = new Array(len);
+        var j = 0;
+
+        for (var i = 0; i < len; ++i) {
+            if (booleans[i]) ret[j++] = values[i];
+
+        }
+        ret.length = j;
+        return ret;
+    }
+
+    var ref = {ref: null};
+    Promise.filter = function Promise$Filter(promises, fn) {
+        return Promise.map(promises, fn, ref)
+            ._then(Promise$_filter, void 0, void 0,
+                    ref.ref, void 0, Promise.filter);
+    };
+
+    Promise.prototype.filter = function Promise$filter(fn) {
+        return this.map(fn, ref)
+            ._then(Promise$_filter, void 0, void 0,
+                    ref.ref, void 0, this.filter);
+    };
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/finally.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/finally.js
new file mode 100644
index 0000000..ef1e0ef
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/finally.js
@@ -0,0 +1,132 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, NEXT_FILTER) {
+    var util = require("./util.js");
+    var wrapsPrimitiveReceiver = util.wrapsPrimitiveReceiver;
+    var isPrimitive = util.isPrimitive;
+    var thrower = util.thrower;
+
+
+    function returnThis() {
+        return this;
+    }
+    function throwThis() {
+        throw this;
+    }
+    function makeReturner(r) {
+        return function Promise$_returner() {
+            return r;
+        };
+    }
+    function makeThrower(r) {
+        return function Promise$_thrower() {
+            throw r;
+        };
+    }
+    function promisedFinally(ret, reasonOrValue, isFulfilled) {
+        var useConstantFunction =
+                        wrapsPrimitiveReceiver && isPrimitive(reasonOrValue);
+
+        if (isFulfilled) {
+            return ret._then(
+                useConstantFunction
+                    ? returnThis
+                    : makeReturner(reasonOrValue),
+                thrower, void 0, reasonOrValue, void 0, promisedFinally);
+        }
+        else {
+            return ret._then(
+                useConstantFunction
+                    ? throwThis
+                    : makeThrower(reasonOrValue),
+                thrower, void 0, reasonOrValue, void 0, promisedFinally);
+        }
+    }
+
+    function finallyHandler(reasonOrValue) {
+        var promise = this.promise;
+        var handler = this.handler;
+
+        var ret = promise._isBound()
+                        ? handler.call(promise._boundTo)
+                        : handler();
+
+        if (ret !== void 0) {
+            var maybePromise = Promise._cast(ret, finallyHandler, void 0);
+            if (maybePromise instanceof Promise) {
+                return promisedFinally(maybePromise, reasonOrValue,
+                                        promise.isFulfilled());
+            }
+        }
+
+        if (promise.isRejected()) {
+            NEXT_FILTER.e = reasonOrValue;
+            return NEXT_FILTER;
+        }
+        else {
+            return reasonOrValue;
+        }
+    }
+
+    function tapHandler(value) {
+        var promise = this.promise;
+        var handler = this.handler;
+
+        var ret = promise._isBound()
+                        ? handler.call(promise._boundTo, value)
+                        : handler(value);
+
+        if (ret !== void 0) {
+            var maybePromise = Promise._cast(ret, tapHandler, void 0);
+            if (maybePromise instanceof Promise) {
+                return promisedFinally(maybePromise, value, true);
+            }
+        }
+        return value;
+    }
+
+    Promise.prototype._passThroughHandler =
+    function Promise$_passThroughHandler(handler, isFinally, caller) {
+        if (typeof handler !== "function") return this.then();
+
+        var promiseAndHandler = {
+            promise: this,
+            handler: handler
+        };
+
+        return this._then(
+                isFinally ? finallyHandler : tapHandler,
+                isFinally ? finallyHandler : void 0, void 0,
+                promiseAndHandler, void 0, caller);
+    };
+
+    Promise.prototype.lastly =
+    Promise.prototype["finally"] = function Promise$finally(handler) {
+        return this._passThroughHandler(handler, true, this.lastly);
+    };
+
+    Promise.prototype.tap = function Promise$tap(handler) {
+        return this._passThroughHandler(handler, false, this.tap);
+    };
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/generators.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/generators.js
new file mode 100644
index 0000000..9632ae7
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/generators.js
@@ -0,0 +1,56 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, apiRejection, INTERNAL) {
+    var PromiseSpawn = require("./promise_spawn.js")(Promise, INTERNAL);
+    var errors = require("./errors.js");
+    var TypeError = errors.TypeError;
+    var deprecated = require("./util.js").deprecated;
+
+    Promise.coroutine = function Promise$Coroutine(generatorFunction) {
+        if (typeof generatorFunction !== "function") {
+            throw new TypeError("generatorFunction must be a function");
+        }
+        var PromiseSpawn$ = PromiseSpawn;
+        return function anonymous() {
+            var generator = generatorFunction.apply(this, arguments);
+            var spawn = new PromiseSpawn$(void 0, void 0, anonymous);
+            spawn._generator = generator;
+            spawn._next(void 0);
+            return spawn.promise();
+        };
+    };
+
+    Promise.coroutine.addYieldHandler = PromiseSpawn.addYieldHandler;
+
+    Promise.spawn = function Promise$Spawn(generatorFunction) {
+        deprecated("Promise.spawn is deprecated. Use Promise.coroutine instead.");
+        if (typeof generatorFunction !== "function") {
+            return apiRejection("generatorFunction must be a function");
+        }
+        var spawn = new PromiseSpawn(generatorFunction, this, Promise.spawn);
+        var ret = spawn.promise();
+        spawn._run(Promise.spawn);
+        return ret;
+    };
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/global.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/global.js
new file mode 100644
index 0000000..1ab1947
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/global.js
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = (function(){
+    if (typeof this !== "undefined") {
+        return this;
+    }
+    if (typeof process !== "undefined" &&
+        typeof global !== "undefined" &&
+        typeof process.execPath === "string") {
+        return global;
+    }
+    if (typeof window !== "undefined" &&
+        typeof document !== "undefined" &&
+        typeof navigator !== "undefined" && navigator !== null &&
+        typeof navigator.appName === "string") {
+            if(window.wrappedJSObject !== undefined){
+                return window.wrappedJSObject;
+            }
+        return window;
+    }
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/map.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/map.js
new file mode 100644
index 0000000..b2a36b0
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/map.js
@@ -0,0 +1,127 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(
+    Promise, Promise$_CreatePromiseArray, PromiseArray, apiRejection) {
+
+    function Promise$_mapper(values) {
+        var fn = this;
+        var receiver = void 0;
+
+        if (typeof fn !== "function")  {
+            receiver = fn.receiver;
+            fn = fn.fn;
+        }
+        var shouldDefer = false;
+
+        var ret = new Array(values.length);
+
+        if (receiver === void 0) {
+            for (var i = 0, len = values.length; i < len; ++i) {
+                var value = fn(values[i], i, len);
+                if (!shouldDefer) {
+                    var maybePromise = Promise._cast(value,
+                            Promise$_mapper, void 0);
+                    if (maybePromise instanceof Promise) {
+                        if (maybePromise.isFulfilled()) {
+                            ret[i] = maybePromise._settledValue;
+                            continue;
+                        }
+                        else {
+                            shouldDefer = true;
+                        }
+                        value = maybePromise;
+                    }
+                }
+                ret[i] = value;
+            }
+        }
+        else {
+            for (var i = 0, len = values.length; i < len; ++i) {
+                var value = fn.call(receiver, values[i], i, len);
+                if (!shouldDefer) {
+                    var maybePromise = Promise._cast(value,
+                            Promise$_mapper, void 0);
+                    if (maybePromise instanceof Promise) {
+                        if (maybePromise.isFulfilled()) {
+                            ret[i] = maybePromise._settledValue;
+                            continue;
+                        }
+                        else {
+                            shouldDefer = true;
+                        }
+                        value = maybePromise;
+                    }
+                }
+                ret[i] = value;
+            }
+        }
+        return shouldDefer
+            ? Promise$_CreatePromiseArray(ret, PromiseArray,
+                Promise$_mapper, void 0).promise()
+            : ret;
+    }
+
+    function Promise$_Map(promises, fn, useBound, caller, ref) {
+        if (typeof fn !== "function") {
+            return apiRejection("fn must be a function");
+        }
+
+        if (useBound === true && promises._isBound()) {
+            fn = {
+                fn: fn,
+                receiver: promises._boundTo
+            };
+        }
+
+        var ret = Promise$_CreatePromiseArray(
+            promises,
+            PromiseArray,
+            caller,
+            useBound === true && promises._isBound()
+                ? promises._boundTo
+                : void 0
+       ).promise();
+
+        if (ref !== void 0) {
+            ref.ref = ret;
+        }
+
+        return ret._then(
+            Promise$_mapper,
+            void 0,
+            void 0,
+            fn,
+            void 0,
+            caller
+       );
+    }
+
+    Promise.prototype.map = function Promise$map(fn, ref) {
+        return Promise$_Map(this, fn, true, this.map, ref);
+    };
+
+    Promise.map = function Promise$Map(promises, fn, ref) {
+        return Promise$_Map(promises, fn, false, Promise.map, ref);
+    };
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/nodeify.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/nodeify.js
new file mode 100644
index 0000000..9fe25f9
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/nodeify.js
@@ -0,0 +1,64 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise) {
+    var util = require("./util.js");
+    var async = require("./async.js");
+    var tryCatch2 = util.tryCatch2;
+    var tryCatch1 = util.tryCatch1;
+    var errorObj = util.errorObj;
+
+    function thrower(r) {
+        throw r;
+    }
+
+    function Promise$_successAdapter(val, receiver) {
+        var nodeback = this;
+        var ret = tryCatch2(nodeback, receiver, null, val);
+        if (ret === errorObj) {
+            async.invokeLater(thrower, void 0, ret.e);
+        }
+    }
+    function Promise$_errorAdapter(reason, receiver) {
+        var nodeback = this;
+        var ret = tryCatch1(nodeback, receiver, reason);
+        if (ret === errorObj) {
+            async.invokeLater(thrower, void 0, ret.e);
+        }
+    }
+
+    Promise.prototype.nodeify = function Promise$nodeify(nodeback) {
+        if (typeof nodeback == "function") {
+            this._then(
+                Promise$_successAdapter,
+                Promise$_errorAdapter,
+                void 0,
+                nodeback,
+                this._isBound() ? this._boundTo : null,
+                this.nodeify
+            );
+            return;
+        }
+        return this;
+    };
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/progress.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/progress.js
new file mode 100644
index 0000000..668f5f7
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/progress.js
@@ -0,0 +1,111 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, isPromiseArrayProxy) {
+    var util = require("./util.js");
+    var async = require("./async.js");
+    var errors = require("./errors.js");
+    var tryCatch1 = util.tryCatch1;
+    var errorObj = util.errorObj;
+
+    Promise.prototype.progressed = function Promise$progressed(handler) {
+        return this._then(void 0, void 0, handler,
+                            void 0, void 0, this.progressed);
+    };
+
+    Promise.prototype._progress = function Promise$_progress(progressValue) {
+        if (this._isFollowingOrFulfilledOrRejected()) return;
+        this._progressUnchecked(progressValue);
+
+    };
+
+    Promise.prototype._progressHandlerAt =
+    function Promise$_progressHandlerAt(index) {
+        if (index === 0) return this._progressHandler0;
+        return this[index + 2 - 5];
+    };
+
+    Promise.prototype._doProgressWith =
+    function Promise$_doProgressWith(progression) {
+        var progressValue = progression.value;
+        var handler = progression.handler;
+        var promise = progression.promise;
+        var receiver = progression.receiver;
+
+        this._pushContext();
+        var ret = tryCatch1(handler, receiver, progressValue);
+        this._popContext();
+
+        if (ret === errorObj) {
+            if (ret.e != null &&
+                ret.e.name !== "StopProgressPropagation") {
+                var trace = errors.canAttach(ret.e)
+                    ? ret.e : new Error(ret.e + "");
+                promise._attachExtraTrace(trace);
+                promise._progress(ret.e);
+            }
+        }
+        else if (Promise.is(ret)) {
+            ret._then(promise._progress, null, null, promise, void 0,
+                this._progress);
+        }
+        else {
+            promise._progress(ret);
+        }
+    };
+
+
+    Promise.prototype._progressUnchecked =
+    function Promise$_progressUnchecked(progressValue) {
+        if (!this.isPending()) return;
+        var len = this._length();
+
+        for (var i = 0; i < len; i += 5) {
+            var handler = this._progressHandlerAt(i);
+            var promise = this._promiseAt(i);
+            if (!Promise.is(promise)) {
+                var receiver = this._receiverAt(i);
+                if (typeof handler === "function") {
+                    handler.call(receiver, progressValue, promise);
+                }
+                else if (Promise.is(receiver) && receiver._isProxied()) {
+                    receiver._progressUnchecked(progressValue);
+                }
+                else if (isPromiseArrayProxy(receiver, promise)) {
+                    receiver._promiseProgressed(progressValue, promise);
+                }
+                continue;
+            }
+
+            if (typeof handler === "function") {
+                this._doProgressWith(({handler: handler,
+promise: promise,
+receiver: this._receiverAt(i),
+value: progressValue}));
+            }
+            else {
+                promise._progress(progressValue);
+            }
+        }
+    };
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/promise.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/promise.js
new file mode 100644
index 0000000..f1c9ddc
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/promise.js
@@ -0,0 +1,1167 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function() {
+var global = require("./global.js");
+var util = require("./util.js");
+var async = require("./async.js");
+var errors = require("./errors.js");
+
+var INTERNAL = function(){};
+var APPLY = {};
+var NEXT_FILTER = {e: null};
+
+var PromiseArray = require("./promise_array.js")(Promise, INTERNAL);
+var CapturedTrace = require("./captured_trace.js")();
+var CatchFilter = require("./catch_filter.js")(NEXT_FILTER);
+var PromiseResolver = require("./promise_resolver.js");
+
+var isArray = util.isArray;
+
+var errorObj = util.errorObj;
+var tryCatch1 = util.tryCatch1;
+var tryCatch2 = util.tryCatch2;
+var tryCatchApply = util.tryCatchApply;
+var RangeError = errors.RangeError;
+var TypeError = errors.TypeError;
+var CancellationError = errors.CancellationError;
+var TimeoutError = errors.TimeoutError;
+var RejectionError = errors.RejectionError;
+var originatesFromRejection = errors.originatesFromRejection;
+var markAsOriginatingFromRejection = errors.markAsOriginatingFromRejection;
+var canAttach = errors.canAttach;
+var thrower = util.thrower;
+var apiRejection = require("./errors_api_rejection")(Promise);
+
+
+var makeSelfResolutionError = function Promise$_makeSelfResolutionError() {
+    return new TypeError("circular promise resolution chain");
+};
+
+function isPromise(obj) {
+    if (obj === void 0) return false;
+    return obj instanceof Promise;
+}
+
+function isPromiseArrayProxy(receiver, promiseSlotValue) {
+    if (receiver instanceof PromiseArray) {
+        return promiseSlotValue >= 0;
+    }
+    return false;
+}
+
+function Promise(resolver) {
+    if (typeof resolver !== "function") {
+        throw new TypeError("the promise constructor requires a resolver function");
+    }
+    if (this.constructor !== Promise) {
+        throw new TypeError("the promise constructor cannot be invoked directly");
+    }
+    this._bitField = 0;
+    this._fulfillmentHandler0 = void 0;
+    this._rejectionHandler0 = void 0;
+    this._promise0 = void 0;
+    this._receiver0 = void 0;
+    this._settledValue = void 0;
+    this._boundTo = void 0;
+    if (resolver !== INTERNAL) this._resolveFromResolver(resolver);
+}
+
+Promise.prototype.bind = function Promise$bind(thisArg) {
+    var ret = new Promise(INTERNAL);
+    if (debugging) ret._setTrace(this.bind, this);
+    ret._follow(this);
+    ret._setBoundTo(thisArg);
+    if (this._cancellable()) {
+        ret._setCancellable();
+        ret._cancellationParent = this;
+    }
+    return ret;
+};
+
+Promise.prototype.toString = function Promise$toString() {
+    return "[object Promise]";
+};
+
+Promise.prototype.caught = Promise.prototype["catch"] =
+function Promise$catch(fn) {
+    var len = arguments.length;
+    if (len > 1) {
+        var catchInstances = new Array(len - 1),
+            j = 0, i;
+        for (i = 0; i < len - 1; ++i) {
+            var item = arguments[i];
+            if (typeof item === "function") {
+                catchInstances[j++] = item;
+            }
+            else {
+                var catchFilterTypeError =
+                    new TypeError(
+                        "A catch filter must be an error constructor "
+                        + "or a filter function");
+
+                this._attachExtraTrace(catchFilterTypeError);
+                this._reject(catchFilterTypeError);
+                return;
+            }
+        }
+        catchInstances.length = j;
+        fn = arguments[i];
+
+        this._resetTrace(this.caught);
+        var catchFilter = new CatchFilter(catchInstances, fn, this);
+        return this._then(void 0, catchFilter.doFilter, void 0,
+            catchFilter, void 0, this.caught);
+    }
+    return this._then(void 0, fn, void 0, void 0, void 0, this.caught);
+};
+
+Promise.prototype.then =
+function Promise$then(didFulfill, didReject, didProgress) {
+    return this._then(didFulfill, didReject, didProgress,
+        void 0, void 0, this.then);
+};
+
+
+Promise.prototype.done =
+function Promise$done(didFulfill, didReject, didProgress) {
+    var promise = this._then(didFulfill, didReject, didProgress,
+        void 0, void 0, this.done);
+    promise._setIsFinal();
+};
+
+Promise.prototype.spread = function Promise$spread(didFulfill, didReject) {
+    return this._then(didFulfill, didReject, void 0,
+        APPLY, void 0, this.spread);
+};
+
+Promise.prototype.isFulfilled = function Promise$isFulfilled() {
+    return (this._bitField & 268435456) > 0;
+};
+
+
+Promise.prototype.isRejected = function Promise$isRejected() {
+    return (this._bitField & 134217728) > 0;
+};
+
+Promise.prototype.isPending = function Promise$isPending() {
+    return !this.isResolved();
+};
+
+
+Promise.prototype.isResolved = function Promise$isResolved() {
+    return (this._bitField & 402653184) > 0;
+};
+
+
+Promise.prototype.isCancellable = function Promise$isCancellable() {
+    return !this.isResolved() &&
+        this._cancellable();
+};
+
+Promise.prototype.toJSON = function Promise$toJSON() {
+    var ret = {
+        isFulfilled: false,
+        isRejected: false,
+        fulfillmentValue: void 0,
+        rejectionReason: void 0
+    };
+    if (this.isFulfilled()) {
+        ret.fulfillmentValue = this._settledValue;
+        ret.isFulfilled = true;
+    }
+    else if (this.isRejected()) {
+        ret.rejectionReason = this._settledValue;
+        ret.isRejected = true;
+    }
+    return ret;
+};
+
+Promise.prototype.all = function Promise$all() {
+    return Promise$_all(this, true, this.all);
+};
+
+
+Promise.is = isPromise;
+
+function Promise$_all(promises, useBound, caller) {
+    return Promise$_CreatePromiseArray(
+        promises,
+        PromiseArray,
+        caller,
+        useBound === true && promises._isBound()
+            ? promises._boundTo
+            : void 0
+   ).promise();
+}
+Promise.all = function Promise$All(promises) {
+    return Promise$_all(promises, false, Promise.all);
+};
+
+Promise.join = function Promise$Join() {
+    var $_len = arguments.length;var args = new Array($_len); for(var $_i = 0; $_i < $_len; ++$_i) {args[$_i] = arguments[$_i];}
+    return Promise$_CreatePromiseArray(
+        args, PromiseArray, Promise.join, void 0).promise();
+};
+
+Promise.resolve = Promise.fulfilled =
+function Promise$Resolve(value, caller) {
+    var ret = new Promise(INTERNAL);
+    if (debugging) ret._setTrace(typeof caller === "function"
+        ? caller
+        : Promise.resolve, void 0);
+    if (ret._tryFollow(value)) {
+        return ret;
+    }
+    ret._cleanValues();
+    ret._setFulfilled();
+    ret._settledValue = value;
+    return ret;
+};
+
+Promise.reject = Promise.rejected = function Promise$Reject(reason) {
+    var ret = new Promise(INTERNAL);
+    if (debugging) ret._setTrace(Promise.reject, void 0);
+    markAsOriginatingFromRejection(reason);
+    ret._cleanValues();
+    ret._setRejected();
+    ret._settledValue = reason;
+    if (!canAttach(reason)) {
+        var trace = new Error(reason + "");
+        ret._setCarriedStackTrace(trace);
+    }
+    ret._ensurePossibleRejectionHandled();
+    return ret;
+};
+
+Promise.prototype.error = function Promise$_error(fn) {
+    return this.caught(originatesFromRejection, fn);
+};
+
+Promise.prototype._resolveFromSyncValue =
+function Promise$_resolveFromSyncValue(value, caller) {
+    if (value === errorObj) {
+        this._cleanValues();
+        this._setRejected();
+        this._settledValue = value.e;
+        this._ensurePossibleRejectionHandled();
+    }
+    else {
+        var maybePromise = Promise._cast(value, caller, void 0);
+        if (maybePromise instanceof Promise) {
+            this._follow(maybePromise);
+        }
+        else {
+            this._cleanValues();
+            this._setFulfilled();
+            this._settledValue = value;
+        }
+    }
+};
+
+Promise.method = function Promise$_Method(fn) {
+    if (typeof fn !== "function") {
+        throw new TypeError("fn must be a function");
+    }
+    return function Promise$_method() {
+        var value;
+        switch(arguments.length) {
+        case 0: value = tryCatch1(fn, this, void 0); break;
+        case 1: value = tryCatch1(fn, this, arguments[0]); break;
+        case 2: value = tryCatch2(fn, this, arguments[0], arguments[1]); break;
+        default:
+            var $_len = arguments.length;var args = new Array($_len); for(var $_i = 0; $_i < $_len; ++$_i) {args[$_i] = arguments[$_i];}
+            value = tryCatchApply(fn, args, this); break;
+        }
+        var ret = new Promise(INTERNAL);
+        if (debugging) ret._setTrace(Promise$_method, void 0);
+        ret._resolveFromSyncValue(value, Promise$_method);
+        return ret;
+    };
+};
+
+Promise.attempt = Promise["try"] = function Promise$_Try(fn, args, ctx) {
+
+    if (typeof fn !== "function") {
+        return apiRejection("fn must be a function");
+    }
+    var value = isArray(args)
+        ? tryCatchApply(fn, args, ctx)
+        : tryCatch1(fn, ctx, args);
+
+    var ret = new Promise(INTERNAL);
+    if (debugging) ret._setTrace(Promise.attempt, void 0);
+    ret._resolveFromSyncValue(value, Promise.attempt);
+    return ret;
+};
+
+Promise.defer = Promise.pending = function Promise$Defer(caller) {
+    var promise = new Promise(INTERNAL);
+    if (debugging) promise._setTrace(typeof caller === "function"
+                              ? caller : Promise.defer, void 0);
+    return new PromiseResolver(promise);
+};
+
+Promise.bind = function Promise$Bind(thisArg) {
+    var ret = new Promise(INTERNAL);
+    if (debugging) ret._setTrace(Promise.bind, void 0);
+    ret._setFulfilled();
+    ret._setBoundTo(thisArg);
+    return ret;
+};
+
+Promise.cast = function Promise$_Cast(obj, caller) {
+    if (typeof caller !== "function") {
+        caller = Promise.cast;
+    }
+    var ret = Promise._cast(obj, caller, void 0);
+    if (!(ret instanceof Promise)) {
+        return Promise.resolve(ret, caller);
+    }
+    return ret;
+};
+
+Promise.onPossiblyUnhandledRejection =
+function Promise$OnPossiblyUnhandledRejection(fn) {
+    if (typeof fn === "function") {
+        CapturedTrace.possiblyUnhandledRejection = fn;
+    }
+    else {
+        CapturedTrace.possiblyUnhandledRejection = void 0;
+    }
+};
+
+var debugging = false || !!(
+    typeof process !== "undefined" &&
+    typeof process.execPath === "string" &&
+    typeof process.env === "object" &&
+    (process.env["BLUEBIRD_DEBUG"] ||
+        process.env["NODE_ENV"] === "development")
+);
+
+
+Promise.longStackTraces = function Promise$LongStackTraces() {
+    if (async.haveItemsQueued() &&
+        debugging === false
+   ) {
+        throw new Error("cannot enable long stack traces after promises have been created");
+    }
+    debugging = CapturedTrace.isSupported();
+};
+
+Promise.hasLongStackTraces = function Promise$HasLongStackTraces() {
+    return debugging && CapturedTrace.isSupported();
+};
+
+Promise.prototype._setProxyHandlers =
+function Promise$_setProxyHandlers(receiver, promiseSlotValue) {
+    var index = this._length();
+
+    if (index >= 1048575 - 5) {
+        index = 0;
+        this._setLength(0);
+    }
+    if (index === 0) {
+        this._promise0 = promiseSlotValue;
+        this._receiver0 = receiver;
+    }
+    else {
+        var i = index - 5;
+        this[i + 3] = promiseSlotValue;
+        this[i + 4] = receiver;
+        this[i + 0] =
+        this[i + 1] =
+        this[i + 2] = void 0;
+    }
+    this._setLength(index + 5);
+};
+
+Promise.prototype._proxyPromiseArray =
+function Promise$_proxyPromiseArray(promiseArray, index) {
+    this._setProxyHandlers(promiseArray, index);
+};
+
+Promise.prototype._proxyPromise = function Promise$_proxyPromise(promise) {
+    promise._setProxied();
+    this._setProxyHandlers(promise, -1);
+};
+
+Promise.prototype._then =
+function Promise$_then(
+    didFulfill,
+    didReject,
+    didProgress,
+    receiver,
+    internalData,
+    caller
+) {
+    var haveInternalData = internalData !== void 0;
+    var ret = haveInternalData ? internalData : new Promise(INTERNAL);
+
+    if (debugging && !haveInternalData) {
+        var haveSameContext = this._peekContext() === this._traceParent;
+        ret._traceParent = haveSameContext ? this._traceParent : this;
+        ret._setTrace(typeof caller === "function"
+                ? caller
+                : this._then, this);
+    }
+
+    if (!haveInternalData && this._isBound()) {
+        ret._setBoundTo(this._boundTo);
+    }
+
+    var callbackIndex =
+        this._addCallbacks(didFulfill, didReject, didProgress, ret, receiver);
+
+    if (!haveInternalData && this._cancellable()) {
+        ret._setCancellable();
+        ret._cancellationParent = this;
+    }
+
+    if (this.isResolved()) {
+        this._queueSettleAt(callbackIndex);
+    }
+
+    return ret;
+};
+
+Promise.prototype._length = function Promise$_length() {
+    return this._bitField & 1048575;
+};
+
+Promise.prototype._isFollowingOrFulfilledOrRejected =
+function Promise$_isFollowingOrFulfilledOrRejected() {
+    return (this._bitField & 939524096) > 0;
+};
+
+Promise.prototype._isFollowing = function Promise$_isFollowing() {
+    return (this._bitField & 536870912) === 536870912;
+};
+
+Promise.prototype._setLength = function Promise$_setLength(len) {
+    this._bitField = (this._bitField & -1048576) |
+        (len & 1048575);
+};
+
+Promise.prototype._setFulfilled = function Promise$_setFulfilled() {
+    this._bitField = this._bitField | 268435456;
+};
+
+Promise.prototype._setRejected = function Promise$_setRejected() {
+    this._bitField = this._bitField | 134217728;
+};
+
+Promise.prototype._setFollowing = function Promise$_setFollowing() {
+    this._bitField = this._bitField | 536870912;
+};
+
+Promise.prototype._setIsFinal = function Promise$_setIsFinal() {
+    this._bitField = this._bitField | 33554432;
+};
+
+Promise.prototype._isFinal = function Promise$_isFinal() {
+    return (this._bitField & 33554432) > 0;
+};
+
+Promise.prototype._cancellable = function Promise$_cancellable() {
+    return (this._bitField & 67108864) > 0;
+};
+
+Promise.prototype._setCancellable = function Promise$_setCancellable() {
+    this._bitField = this._bitField | 67108864;
+};
+
+Promise.prototype._unsetCancellable = function Promise$_unsetCancellable() {
+    this._bitField = this._bitField & (~67108864);
+};
+
+Promise.prototype._setRejectionIsUnhandled =
+function Promise$_setRejectionIsUnhandled() {
+    this._bitField = this._bitField | 2097152;
+};
+
+Promise.prototype._unsetRejectionIsUnhandled =
+function Promise$_unsetRejectionIsUnhandled() {
+    this._bitField = this._bitField & (~2097152);
+};
+
+Promise.prototype._isRejectionUnhandled =
+function Promise$_isRejectionUnhandled() {
+    return (this._bitField & 2097152) > 0;
+};
+
+Promise.prototype._setCarriedStackTrace =
+function Promise$_setCarriedStackTrace(capturedTrace) {
+    this._bitField = this._bitField | 1048576;
+    this._fulfillmentHandler0 = capturedTrace;
+};
+
+Promise.prototype._unsetCarriedStackTrace =
+function Promise$_unsetCarriedStackTrace() {
+    this._bitField = this._bitField & (~1048576);
+    this._fulfillmentHandler0 = void 0;
+};
+
+Promise.prototype._isCarryingStackTrace =
+function Promise$_isCarryingStackTrace() {
+    return (this._bitField & 1048576) > 0;
+};
+
+Promise.prototype._getCarriedStackTrace =
+function Promise$_getCarriedStackTrace() {
+    return this._isCarryingStackTrace()
+        ? this._fulfillmentHandler0
+        : void 0;
+};
+
+Promise.prototype._receiverAt = function Promise$_receiverAt(index) {
+    var ret;
+    if (index === 0) {
+        ret = this._receiver0;
+    }
+    else {
+        ret = this[index + 4 - 5];
+    }
+    if (this._isBound() && ret === void 0) {
+        return this._boundTo;
+    }
+    return ret;
+};
+
+Promise.prototype._promiseAt = function Promise$_promiseAt(index) {
+    if (index === 0) return this._promise0;
+    return this[index + 3 - 5];
+};
+
+Promise.prototype._fulfillmentHandlerAt =
+function Promise$_fulfillmentHandlerAt(index) {
+    if (index === 0) return this._fulfillmentHandler0;
+    return this[index + 0 - 5];
+};
+
+Promise.prototype._rejectionHandlerAt =
+function Promise$_rejectionHandlerAt(index) {
+    if (index === 0) return this._rejectionHandler0;
+    return this[index + 1 - 5];
+};
+
+Promise.prototype._unsetAt = function Promise$_unsetAt(index) {
+     if (index === 0) {
+        this._rejectionHandler0 =
+        this._progressHandler0 =
+        this._promise0 =
+        this._receiver0 = void 0;
+        if (!this._isCarryingStackTrace()) {
+            this._fulfillmentHandler0 = void 0;
+        }
+    }
+    else {
+        this[index - 5 + 0] =
+        this[index - 5 + 1] =
+        this[index - 5 + 2] =
+        this[index - 5 + 3] =
+        this[index - 5 + 4] = void 0;
+    }
+};
+
+Promise.prototype._resolveFromResolver =
+function Promise$_resolveFromResolver(resolver) {
+    var promise = this;
+    var localDebugging = debugging;
+    if (localDebugging) {
+        this._setTrace(this._resolveFromResolver, void 0);
+        this._pushContext();
+    }
+    function Promise$_resolver(val) {
+        if (promise._tryFollow(val)) {
+            return;
+        }
+        promise._fulfill(val);
+    }
+    function Promise$_rejecter(val) {
+        var trace = canAttach(val) ? val : new Error(val + "");
+        promise._attachExtraTrace(trace);
+        markAsOriginatingFromRejection(val);
+        promise._reject(val, trace === val ? void 0 : trace);
+    }
+    var r = tryCatch2(resolver, void 0, Promise$_resolver, Promise$_rejecter);
+    if (localDebugging) this._popContext();
+
+    if (r !== void 0 && r === errorObj) {
+        var e = r.e;
+        var trace = canAttach(e) ? e : new Error(e + "");
+        promise._reject(e, trace);
+    }
+};
+
+Promise.prototype._addCallbacks = function Promise$_addCallbacks(
+    fulfill,
+    reject,
+    progress,
+    promise,
+    receiver
+) {
+    var index = this._length();
+
+    if (index >= 1048575 - 5) {
+        index = 0;
+        this._setLength(0);
+    }
+
+    if (index === 0) {
+        this._promise0 = promise;
+        if (receiver !== void 0) this._receiver0 = receiver;
+        if (typeof fulfill === "function" && !this._isCarryingStackTrace())
+            this._fulfillmentHandler0 = fulfill;
+        if (typeof reject === "function") this._rejectionHandler0 = reject;
+        if (typeof progress === "function") this._progressHandler0 = progress;
+    }
+    else {
+        var i = index - 5;
+        this[i + 3] = promise;
+        this[i + 4] = receiver;
+        this[i + 0] = typeof fulfill === "function"
+                                            ? fulfill : void 0;
+        this[i + 1] = typeof reject === "function"
+                                            ? reject : void 0;
+        this[i + 2] = typeof progress === "function"
+                                            ? progress : void 0;
+    }
+    this._setLength(index + 5);
+    return index;
+};
+
+
+
+Promise.prototype._setBoundTo = function Promise$_setBoundTo(obj) {
+    if (obj !== void 0) {
+        this._bitField = this._bitField | 8388608;
+        this._boundTo = obj;
+    }
+    else {
+        this._bitField = this._bitField & (~8388608);
+    }
+};
+
+Promise.prototype._isBound = function Promise$_isBound() {
+    return (this._bitField & 8388608) === 8388608;
+};
+
+Promise.prototype._spreadSlowCase =
+function Promise$_spreadSlowCase(targetFn, promise, values, boundTo) {
+    var promiseForAll =
+            Promise$_CreatePromiseArray
+                (values, PromiseArray, this._spreadSlowCase, boundTo)
+            .promise()
+            ._then(function() {
+                return targetFn.apply(boundTo, arguments);
+            }, void 0, void 0, APPLY, void 0, this._spreadSlowCase);
+
+    promise._follow(promiseForAll);
+};
+
+Promise.prototype._callSpread =
+function Promise$_callSpread(handler, promise, value, localDebugging) {
+    var boundTo = this._isBound() ? this._boundTo : void 0;
+    if (isArray(value)) {
+        var caller = this._settlePromiseFromHandler;
+        for (var i = 0, len = value.length; i < len; ++i) {
+            if (isPromise(Promise._cast(value[i], caller, void 0))) {
+                this._spreadSlowCase(handler, promise, value, boundTo);
+                return;
+            }
+        }
+    }
+    if (localDebugging) promise._pushContext();
+    return tryCatchApply(handler, value, boundTo);
+};
+
+Promise.prototype._callHandler =
+function Promise$_callHandler(
+    handler, receiver, promise, value, localDebugging) {
+    var x;
+    if (receiver === APPLY && !this.isRejected()) {
+        x = this._callSpread(handler, promise, value, localDebugging);
+    }
+    else {
+        if (localDebugging) promise._pushContext();
+        x = tryCatch1(handler, receiver, value);
+    }
+    if (localDebugging) promise._popContext();
+    return x;
+};
+
+Promise.prototype._settlePromiseFromHandler =
+function Promise$_settlePromiseFromHandler(
+    handler, receiver, value, promise
+) {
+    if (!isPromise(promise)) {
+        handler.call(receiver, value, promise);
+        return;
+    }
+
+    var localDebugging = debugging;
+    var x = this._callHandler(handler, receiver,
+                                promise, value, localDebugging);
+
+    if (promise._isFollowing()) return;
+
+    if (x === errorObj || x === promise || x === NEXT_FILTER) {
+        var err = x === promise
+                    ? makeSelfResolutionError()
+                    : x.e;
+        var trace = canAttach(err) ? err : new Error(err + "");
+        if (x !== NEXT_FILTER) promise._attachExtraTrace(trace);
+        promise._rejectUnchecked(err, trace);
+    }
+    else {
+        var castValue = Promise._cast(x,
+                    localDebugging ? this._settlePromiseFromHandler : void 0,
+                    promise);
+
+        if (isPromise(castValue)) {
+            if (castValue.isRejected() &&
+                !castValue._isCarryingStackTrace() &&
+                !canAttach(castValue._settledValue)) {
+                var trace = new Error(castValue._settledValue + "");
+                promise._attachExtraTrace(trace);
+                castValue._setCarriedStackTrace(trace);
+            }
+            promise._follow(castValue);
+            if (castValue._cancellable()) {
+                promise._cancellationParent = castValue;
+                promise._setCancellable();
+            }
+        }
+        else {
+            promise._fulfillUnchecked(x);
+        }
+    }
+};
+
+Promise.prototype._follow =
+function Promise$_follow(promise) {
+    this._setFollowing();
+
+    if (promise.isPending()) {
+        if (promise._cancellable() ) {
+            this._cancellationParent = promise;
+            this._setCancellable();
+        }
+        promise._proxyPromise(this);
+    }
+    else if (promise.isFulfilled()) {
+        this._fulfillUnchecked(promise._settledValue);
+    }
+    else {
+        this._rejectUnchecked(promise._settledValue,
+            promise._getCarriedStackTrace());
+    }
+
+    if (promise._isRejectionUnhandled()) promise._unsetRejectionIsUnhandled();
+
+    if (debugging &&
+        promise._traceParent == null) {
+        promise._traceParent = this;
+    }
+};
+
+Promise.prototype._tryFollow =
+function Promise$_tryFollow(value) {
+    if (this._isFollowingOrFulfilledOrRejected() ||
+        value === this) {
+        return false;
+    }
+    var maybePromise = Promise._cast(value, this._tryFollow, void 0);
+    if (!isPromise(maybePromise)) {
+        return false;
+    }
+    this._follow(maybePromise);
+    return true;
+};
+
+Promise.prototype._resetTrace = function Promise$_resetTrace(caller) {
+    if (debugging) {
+        var context = this._peekContext();
+        var isTopLevel = context === void 0;
+        this._trace = new CapturedTrace(
+            typeof caller === "function"
+            ? caller
+            : this._resetTrace,
+            isTopLevel
+       );
+    }
+};
+
+Promise.prototype._setTrace = function Promise$_setTrace(caller, parent) {
+    if (debugging) {
+        var context = this._peekContext();
+        this._traceParent = context;
+        var isTopLevel = context === void 0;
+        if (parent !== void 0 &&
+            parent._traceParent === context) {
+            this._trace = parent._trace;
+        }
+        else {
+            this._trace = new CapturedTrace(
+                typeof caller === "function"
+                ? caller
+                : this._setTrace,
+                isTopLevel
+           );
+        }
+    }
+    return this;
+};
+
+Promise.prototype._attachExtraTrace =
+function Promise$_attachExtraTrace(error) {
+    if (debugging) {
+        var promise = this;
+        var stack = error.stack;
+        stack = typeof stack === "string"
+            ? stack.split("\n") : [];
+        var headerLineCount = 1;
+
+        while(promise != null &&
+            promise._trace != null) {
+            stack = CapturedTrace.combine(
+                stack,
+                promise._trace.stack.split("\n")
+           );
+            promise = promise._traceParent;
+        }
+
+        var max = Error.stackTraceLimit + headerLineCount;
+        var len = stack.length;
+        if (len  > max) {
+            stack.length = max;
+        }
+        if (stack.length <= headerLineCount) {
+            error.stack = "(No stack trace)";
+        }
+        else {
+            error.stack = stack.join("\n");
+        }
+    }
+};
+
+Promise.prototype._cleanValues = function Promise$_cleanValues() {
+    if (this._cancellable()) {
+        this._cancellationParent = void 0;
+    }
+};
+
+Promise.prototype._fulfill = function Promise$_fulfill(value) {
+    if (this._isFollowingOrFulfilledOrRejected()) return;
+    this._fulfillUnchecked(value);
+};
+
+Promise.prototype._reject =
+function Promise$_reject(reason, carriedStackTrace) {
+    if (this._isFollowingOrFulfilledOrRejected()) return;
+    this._rejectUnchecked(reason, carriedStackTrace);
+};
+
+Promise.prototype._settlePromiseAt = function Promise$_settlePromiseAt(index) {
+    var handler = this.isFulfilled()
+        ? this._fulfillmentHandlerAt(index)
+        : this._rejectionHandlerAt(index);
+
+    var value = this._settledValue;
+    var receiver = this._receiverAt(index);
+    var promise = this._promiseAt(index);
+
+    if (typeof handler === "function") {
+        this._settlePromiseFromHandler(handler, receiver, value, promise);
+    }
+    else {
+        var done = false;
+        var isFulfilled = this.isFulfilled();
+        if (receiver !== void 0) {
+            if (receiver instanceof Promise &&
+                receiver._isProxied()) {
+                receiver._unsetProxied();
+
+                if (isFulfilled) receiver._fulfillUnchecked(value);
+                else receiver._rejectUnchecked(value,
+                    this._getCarriedStackTrace());
+                done = true;
+            }
+            else if (isPromiseArrayProxy(receiver, promise)) {
+
+                if (isFulfilled) receiver._promiseFulfilled(value, promise);
+                else receiver._promiseRejected(value, promise);
+
+                done = true;
+            }
+        }
+
+        if (!done) {
+
+            if (isFulfilled) promise._fulfill(value);
+            else promise._reject(value, this._getCarriedStackTrace());
+
+        }
+    }
+
+    if (index >= 256) {
+        this._queueGC();
+    }
+};
+
+Promise.prototype._isProxied = function Promise$_isProxied() {
+    return (this._bitField & 4194304) === 4194304;
+};
+
+Promise.prototype._setProxied = function Promise$_setProxied() {
+    this._bitField = this._bitField | 4194304;
+};
+
+Promise.prototype._unsetProxied = function Promise$_unsetProxied() {
+    this._bitField = this._bitField & (~4194304);
+};
+
+Promise.prototype._isGcQueued = function Promise$_isGcQueued() {
+    return (this._bitField & -1073741824) === -1073741824;
+};
+
+Promise.prototype._setGcQueued = function Promise$_setGcQueued() {
+    this._bitField = this._bitField | -1073741824;
+};
+
+Promise.prototype._unsetGcQueued = function Promise$_unsetGcQueued() {
+    this._bitField = this._bitField & (~-1073741824);
+};
+
+Promise.prototype._queueGC = function Promise$_queueGC() {
+    if (this._isGcQueued()) return;
+    this._setGcQueued();
+    async.invokeLater(this._gc, this, void 0);
+};
+
+Promise.prototype._gc = function Promise$gc() {
+    var len = this._length();
+    this._unsetAt(0);
+    for (var i = 0; i < len; i++) {
+        delete this[i];
+    }
+    this._setLength(0);
+    this._unsetGcQueued();
+};
+
+Promise.prototype._queueSettleAt = function Promise$_queueSettleAt(index) {
+    if (this._isRejectionUnhandled()) this._unsetRejectionIsUnhandled();
+    this._settlePromiseAt(index);
+};
+
+Promise.prototype._fulfillUnchecked =
+function Promise$_fulfillUnchecked(value) {
+    if (!this.isPending()) return;
+    if (value === this) {
+        var err = makeSelfResolutionError();
+        this._attachExtraTrace(err);
+        return this._rejectUnchecked(err, void 0);
+    }
+    this._cleanValues();
+    this._setFulfilled();
+    this._settledValue = value;
+    var len = this._length();
+
+    if (len > 0) {
+        this._fulfillPromises(len);
+    }
+};
+
+Promise.prototype._rejectUncheckedCheckError =
+function Promise$_rejectUncheckedCheckError(reason) {
+    var trace = canAttach(reason) ? reason : new Error(reason + "");
+    this._rejectUnchecked(reason, trace === reason ? void 0 : trace);
+};
+
+Promise.prototype._rejectUnchecked =
+function Promise$_rejectUnchecked(reason, trace) {
+    if (!this.isPending()) return;
+    if (reason === this) {
+        var err = makeSelfResolutionError();
+        this._attachExtraTrace(err);
+        return this._rejectUnchecked(err);
+    }
+    this._cleanValues();
+    this._setRejected();
+    this._settledValue = reason;
+
+    if (this._isFinal()) {
+        async.invokeLater(thrower, void 0, trace === void 0 ? reason : trace);
+        return;
+    }
+    var len = this._length();
+
+    if (trace !== void 0) this._setCarriedStackTrace(trace);
+
+    if (len > 0) {
+        this._rejectPromises(null);
+    }
+    else {
+        this._ensurePossibleRejectionHandled();
+    }
+};
+
+Promise.prototype._rejectPromises = function Promise$_rejectPromises() {
+    var len = this._length();
+    for (var i = 0; i < len; i+= 5) {
+        this._settlePromiseAt(i);
+    }
+    this._unsetCarriedStackTrace();
+};
+
+Promise.prototype._fulfillPromises = function Promise$_fulfillPromises(len) {
+    len = this._length();
+    for (var i = 0; i < len; i+= 5) {
+        this._settlePromiseAt(i);
+    }
+};
+
+Promise.prototype._ensurePossibleRejectionHandled =
+function Promise$_ensurePossibleRejectionHandled() {
+    this._setRejectionIsUnhandled();
+    if (CapturedTrace.possiblyUnhandledRejection !== void 0) {
+        async.invokeLater(this._notifyUnhandledRejection, this, void 0);
+    }
+};
+
+Promise.prototype._notifyUnhandledRejection =
+function Promise$_notifyUnhandledRejection() {
+    if (this._isRejectionUnhandled()) {
+        var reason = this._settledValue;
+        var trace = this._getCarriedStackTrace();
+
+        this._unsetRejectionIsUnhandled();
+
+        if (trace !== void 0) {
+            this._unsetCarriedStackTrace();
+            reason = trace;
+        }
+        if (typeof CapturedTrace.possiblyUnhandledRejection === "function") {
+            CapturedTrace.possiblyUnhandledRejection(reason, this);
+        }
+    }
+};
+
+var contextStack = [];
+Promise.prototype._peekContext = function Promise$_peekContext() {
+    var lastIndex = contextStack.length - 1;
+    if (lastIndex >= 0) {
+        return contextStack[lastIndex];
+    }
+    return void 0;
+
+};
+
+Promise.prototype._pushContext = function Promise$_pushContext() {
+    if (!debugging) return;
+    contextStack.push(this);
+};
+
+Promise.prototype._popContext = function Promise$_popContext() {
+    if (!debugging) return;
+    contextStack.pop();
+};
+
+function Promise$_CreatePromiseArray(
+    promises, PromiseArrayConstructor, caller, boundTo) {
+
+    var list = null;
+    if (isArray(promises)) {
+        list = promises;
+    }
+    else {
+        list = Promise._cast(promises, caller, void 0);
+        if (list !== promises) {
+            list._setBoundTo(boundTo);
+        }
+        else if (!isPromise(list)) {
+            list = null;
+        }
+    }
+    if (list !== null) {
+        return new PromiseArrayConstructor(
+            list,
+            typeof caller === "function"
+                ? caller
+                : Promise$_CreatePromiseArray,
+            boundTo
+       );
+    }
+    return {
+        promise: function() {return apiRejection("expecting an array, a promise or a thenable");}
+    };
+}
+
+var old = global.Promise;
+
+Promise.noConflict = function() {
+    if (global.Promise === Promise) {
+        global.Promise = old;
+    }
+    return Promise;
+};
+
+if (!CapturedTrace.isSupported()) {
+    Promise.longStackTraces = function(){};
+    debugging = false;
+}
+
+Promise._makeSelfResolutionError = makeSelfResolutionError;
+require("./finally.js")(Promise, NEXT_FILTER);
+require("./direct_resolve.js")(Promise);
+require("./thenables.js")(Promise, INTERNAL);
+Promise.RangeError = RangeError;
+Promise.CancellationError = CancellationError;
+Promise.TimeoutError = TimeoutError;
+Promise.TypeError = TypeError;
+Promise.RejectionError = RejectionError;
+
+util.toFastProperties(Promise);
+util.toFastProperties(Promise.prototype);
+require('./timers.js')(Promise,INTERNAL);
+require('./synchronous_inspection.js')(Promise);
+require('./any.js')(Promise,Promise$_CreatePromiseArray,PromiseArray);
+require('./race.js')(Promise,INTERNAL);
+require('./call_get.js')(Promise);
+require('./filter.js')(Promise,Promise$_CreatePromiseArray,PromiseArray,apiRejection);
+require('./generators.js')(Promise,apiRejection,INTERNAL);
+require('./map.js')(Promise,Promise$_CreatePromiseArray,PromiseArray,apiRejection);
+require('./nodeify.js')(Promise);
+require('./promisify.js')(Promise,INTERNAL);
+require('./props.js')(Promise,PromiseArray);
+require('./reduce.js')(Promise,Promise$_CreatePromiseArray,PromiseArray,apiRejection,INTERNAL);
+require('./settle.js')(Promise,Promise$_CreatePromiseArray,PromiseArray);
+require('./some.js')(Promise,Promise$_CreatePromiseArray,PromiseArray,apiRejection);
+require('./progress.js')(Promise,isPromiseArrayProxy);
+require('./cancel.js')(Promise,INTERNAL);
+
+Promise.prototype = Promise.prototype;
+return Promise;
+
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/promise_array.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/promise_array.js
new file mode 100644
index 0000000..6d43606
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/promise_array.js
@@ -0,0 +1,233 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, INTERNAL) {
+var canAttach = require("./errors.js").canAttach;
+var util = require("./util.js");
+var async = require("./async.js");
+var hasOwn = {}.hasOwnProperty;
+var isArray = util.isArray;
+
+function toResolutionValue(val) {
+    switch(val) {
+    case -1: return void 0;
+    case -2: return [];
+    case -3: return {};
+    }
+}
+
+function PromiseArray(values, caller, boundTo) {
+    var promise = this._promise = new Promise(INTERNAL);
+    var parent = void 0;
+    if (Promise.is(values)) {
+        parent = values;
+        if (values._cancellable()) {
+            promise._setCancellable();
+            promise._cancellationParent = values;
+        }
+        if (values._isBound()) {
+            promise._setBoundTo(boundTo);
+        }
+    }
+    promise._setTrace(caller, parent);
+    this._values = values;
+    this._length = 0;
+    this._totalResolved = 0;
+    this._init(void 0, -2);
+}
+PromiseArray.PropertiesPromiseArray = function() {};
+
+PromiseArray.prototype.length = function PromiseArray$length() {
+    return this._length;
+};
+
+PromiseArray.prototype.promise = function PromiseArray$promise() {
+    return this._promise;
+};
+
+PromiseArray.prototype._init =
+function PromiseArray$_init(_, resolveValueIfEmpty) {
+    var values = this._values;
+    if (Promise.is(values)) {
+        if (values.isFulfilled()) {
+            values = values._settledValue;
+            if (!isArray(values)) {
+                var err = new Promise.TypeError("expecting an array, a promise or a thenable");
+                this.__hardReject__(err);
+                return;
+            }
+            this._values = values;
+        }
+        else if (values.isPending()) {
+            values._then(
+                this._init,
+                this._reject,
+                void 0,
+                this,
+                resolveValueIfEmpty,
+                this.constructor
+           );
+            return;
+        }
+        else {
+            this._reject(values._settledValue);
+            return;
+        }
+    }
+
+    if (values.length === 0) {
+        this._resolve(toResolutionValue(resolveValueIfEmpty));
+        return;
+    }
+    var len = values.length;
+    var newLen = len;
+    var newValues;
+    if (this instanceof PromiseArray.PropertiesPromiseArray) {
+        newValues = this._values;
+    }
+    else {
+        newValues = new Array(len);
+    }
+    var isDirectScanNeeded = false;
+    for (var i = 0; i < len; ++i) {
+        var promise = values[i];
+        if (promise === void 0 && !hasOwn.call(values, i)) {
+            newLen--;
+            continue;
+        }
+        var maybePromise = Promise._cast(promise, void 0, void 0);
+        if (maybePromise instanceof Promise) {
+            if (maybePromise.isPending()) {
+                maybePromise._proxyPromiseArray(this, i);
+            }
+            else {
+                maybePromise._unsetRejectionIsUnhandled();
+                isDirectScanNeeded = true;
+            }
+        }
+        else {
+            isDirectScanNeeded = true;
+        }
+        newValues[i] = maybePromise;
+    }
+    if (newLen === 0) {
+        if (resolveValueIfEmpty === -2) {
+            this._resolve(newValues);
+        }
+        else {
+            this._resolve(toResolutionValue(resolveValueIfEmpty));
+        }
+        return;
+    }
+    this._values = newValues;
+    this._length = newLen;
+    if (isDirectScanNeeded) {
+        var scanMethod = newLen === len
+            ? this._scanDirectValues
+            : this._scanDirectValuesHoled;
+        scanMethod.call(this, len);
+    }
+};
+
+PromiseArray.prototype._settlePromiseAt =
+function PromiseArray$_settlePromiseAt(index) {
+    var value = this._values[index];
+    if (!Promise.is(value)) {
+        this._promiseFulfilled(value, index);
+    }
+    else if (value.isFulfilled()) {
+        this._promiseFulfilled(value._settledValue, index);
+    }
+    else if (value.isRejected()) {
+        this._promiseRejected(value._settledValue, index);
+    }
+};
+
+PromiseArray.prototype._scanDirectValuesHoled =
+function PromiseArray$_scanDirectValuesHoled(len) {
+    for (var i = 0; i < len; ++i) {
+        if (this._isResolved()) {
+            break;
+        }
+        if (hasOwn.call(this._values, i)) {
+            this._settlePromiseAt(i);
+        }
+    }
+};
+
+PromiseArray.prototype._scanDirectValues =
+function PromiseArray$_scanDirectValues(len) {
+    for (var i = 0; i < len; ++i) {
+        if (this._isResolved()) {
+            break;
+        }
+        this._settlePromiseAt(i);
+    }
+};
+
+PromiseArray.prototype._isResolved = function PromiseArray$_isResolved() {
+    return this._values === null;
+};
+
+PromiseArray.prototype._resolve = function PromiseArray$_resolve(value) {
+    this._values = null;
+    this._promise._fulfill(value);
+};
+
+PromiseArray.prototype.__hardReject__ =
+PromiseArray.prototype._reject = function PromiseArray$_reject(reason) {
+    this._values = null;
+    var trace = canAttach(reason) ? reason : new Error(reason + "");
+    this._promise._attachExtraTrace(trace);
+    this._promise._reject(reason, trace);
+};
+
+PromiseArray.prototype._promiseProgressed =
+function PromiseArray$_promiseProgressed(progressValue, index) {
+    if (this._isResolved()) return;
+    this._promise._progress({
+        index: index,
+        value: progressValue
+    });
+};
+
+
+PromiseArray.prototype._promiseFulfilled =
+function PromiseArray$_promiseFulfilled(value, index) {
+    if (this._isResolved()) return;
+    this._values[index] = value;
+    var totalResolved = ++this._totalResolved;
+    if (totalResolved >= this._length) {
+        this._resolve(this._values);
+    }
+};
+
+PromiseArray.prototype._promiseRejected =
+function PromiseArray$_promiseRejected(reason, index) {
+    if (this._isResolved()) return;
+    this._totalResolved++;
+    this._reject(reason);
+};
+
+return PromiseArray;
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/promise_inspection.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/promise_inspection.js
new file mode 100644
index 0000000..0aa233b
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/promise_inspection.js
@@ -0,0 +1,66 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var TypeError = require("./errors.js").TypeError;
+
+function PromiseInspection(promise) {
+    if (promise !== void 0) {
+        this._bitField = promise._bitField;
+        this._settledValue = promise.isResolved()
+            ? promise._settledValue
+            : void 0;
+    }
+    else {
+        this._bitField = 0;
+        this._settledValue = void 0;
+    }
+}
+PromiseInspection.prototype.isFulfilled =
+function PromiseInspection$isFulfilled() {
+    return (this._bitField & 268435456) > 0;
+};
+
+PromiseInspection.prototype.isRejected =
+function PromiseInspection$isRejected() {
+    return (this._bitField & 134217728) > 0;
+};
+
+PromiseInspection.prototype.isPending = function PromiseInspection$isPending() {
+    return (this._bitField & 402653184) === 0;
+};
+
+PromiseInspection.prototype.value = function PromiseInspection$value() {
+    if (!this.isFulfilled()) {
+        throw new TypeError("cannot get fulfillment value of a non-fulfilled promise");
+    }
+    return this._settledValue;
+};
+
+PromiseInspection.prototype.error = function PromiseInspection$error() {
+    if (!this.isRejected()) {
+        throw new TypeError("cannot get rejection reason of a non-rejected promise");
+    }
+    return this._settledValue;
+};
+
+module.exports = PromiseInspection;
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/promise_resolver.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/promise_resolver.js
new file mode 100644
index 0000000..ea14069
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/promise_resolver.js
@@ -0,0 +1,152 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var util = require("./util.js");
+var maybeWrapAsError = util.maybeWrapAsError;
+var errors = require("./errors.js");
+var TimeoutError = errors.TimeoutError;
+var RejectionError = errors.RejectionError;
+var async = require("./async.js");
+var haveGetters = util.haveGetters;
+var es5 = require("./es5.js");
+
+function isUntypedError(obj) {
+    return obj instanceof Error &&
+        es5.getPrototypeOf(obj) === Error.prototype;
+}
+
+function wrapAsRejectionError(obj) {
+    var ret;
+    if (isUntypedError(obj)) {
+        ret = new RejectionError(obj);
+    }
+    else {
+        ret = obj;
+    }
+    errors.markAsOriginatingFromRejection(ret);
+    return ret;
+}
+
+function nodebackForPromise(promise) {
+    function PromiseResolver$_callback(err, value) {
+        if (promise === null) return;
+
+        if (err) {
+            var wrapped = wrapAsRejectionError(maybeWrapAsError(err));
+            promise._attachExtraTrace(wrapped);
+            promise._reject(wrapped);
+        }
+        else {
+            if (arguments.length > 2) {
+                var $_len = arguments.length;var args = new Array($_len - 1); for(var $_i = 1; $_i < $_len; ++$_i) {args[$_i - 1] = arguments[$_i];}
+                promise._fulfill(args);
+            }
+            else {
+                promise._fulfill(value);
+            }
+        }
+
+        promise = null;
+    }
+    return PromiseResolver$_callback;
+}
+
+
+var PromiseResolver;
+if (!haveGetters) {
+    PromiseResolver = function PromiseResolver(promise) {
+        this.promise = promise;
+        this.asCallback = nodebackForPromise(promise);
+        this.callback = this.asCallback;
+    };
+}
+else {
+    PromiseResolver = function PromiseResolver(promise) {
+        this.promise = promise;
+    };
+}
+if (haveGetters) {
+    var prop = {
+        get: function() {
+            return nodebackForPromise(this.promise);
+        }
+    };
+    es5.defineProperty(PromiseResolver.prototype, "asCallback", prop);
+    es5.defineProperty(PromiseResolver.prototype, "callback", prop);
+}
+
+PromiseResolver._nodebackForPromise = nodebackForPromise;
+
+PromiseResolver.prototype.toString = function PromiseResolver$toString() {
+    return "[object PromiseResolver]";
+};
+
+PromiseResolver.prototype.resolve =
+PromiseResolver.prototype.fulfill = function PromiseResolver$resolve(value) {
+    var promise = this.promise;
+    if (promise._tryFollow(value)) {
+        return;
+    }
+    promise._fulfill(value);
+};
+
+PromiseResolver.prototype.reject = function PromiseResolver$reject(reason) {
+    var promise = this.promise;
+    errors.markAsOriginatingFromRejection(reason);
+    var trace = errors.canAttach(reason) ? reason : new Error(reason + "");
+    promise._attachExtraTrace(trace);
+    promise._reject(reason);
+    if (trace !== reason) {
+        this._setCarriedStackTrace(trace);
+    }
+};
+
+PromiseResolver.prototype.progress =
+function PromiseResolver$progress(value) {
+    this.promise._progress(value);
+};
+
+PromiseResolver.prototype.cancel = function PromiseResolver$cancel() {
+    this.promise.cancel((void 0));
+};
+
+PromiseResolver.prototype.timeout = function PromiseResolver$timeout() {
+    this.reject(new TimeoutError("timeout"));
+};
+
+PromiseResolver.prototype.isResolved = function PromiseResolver$isResolved() {
+    return this.promise.isResolved();
+};
+
+PromiseResolver.prototype.toJSON = function PromiseResolver$toJSON() {
+    return this.promise.toJSON();
+};
+
+PromiseResolver.prototype._setCarriedStackTrace =
+function PromiseResolver$_setCarriedStackTrace(trace) {
+    if (this.promise.isRejected()) {
+        this.promise._setCarriedStackTrace(trace);
+    }
+};
+
+module.exports = PromiseResolver;
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/promise_spawn.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/promise_spawn.js
new file mode 100644
index 0000000..2d6525f
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/promise_spawn.js
@@ -0,0 +1,131 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, INTERNAL) {
+var errors = require("./errors.js");
+var TypeError = errors.TypeError;
+var util = require("./util.js");
+var isArray = util.isArray;
+var errorObj = util.errorObj;
+var tryCatch1 = util.tryCatch1;
+var yieldHandlers = [];
+
+function promiseFromYieldHandler(value) {
+    var _yieldHandlers = yieldHandlers;
+    var _errorObj = errorObj;
+    var _Promise = Promise;
+    var len = _yieldHandlers.length;
+    for (var i = 0; i < len; ++i) {
+        var result = tryCatch1(_yieldHandlers[i], void 0, value);
+        if (result === _errorObj) {
+            return _Promise.reject(_errorObj.e);
+        }
+        var maybePromise = _Promise._cast(result,
+            promiseFromYieldHandler, void 0);
+        if (maybePromise instanceof _Promise) return maybePromise;
+    }
+    return null;
+}
+
+function PromiseSpawn(generatorFunction, receiver, caller) {
+    var promise = this._promise = new Promise(INTERNAL);
+    promise._setTrace(caller, void 0);
+    this._generatorFunction = generatorFunction;
+    this._receiver = receiver;
+    this._generator = void 0;
+}
+
+PromiseSpawn.prototype.promise = function PromiseSpawn$promise() {
+    return this._promise;
+};
+
+PromiseSpawn.prototype._run = function PromiseSpawn$_run() {
+    this._generator = this._generatorFunction.call(this._receiver);
+    this._receiver =
+        this._generatorFunction = void 0;
+    this._next(void 0);
+};
+
+PromiseSpawn.prototype._continue = function PromiseSpawn$_continue(result) {
+    if (result === errorObj) {
+        this._generator = void 0;
+        var trace = errors.canAttach(result.e)
+            ? result.e : new Error(result.e + "");
+        this._promise._attachExtraTrace(trace);
+        this._promise._reject(result.e, trace);
+        return;
+    }
+
+    var value = result.value;
+    if (result.done === true) {
+        this._generator = void 0;
+        if (!this._promise._tryFollow(value)) {
+            this._promise._fulfill(value);
+        }
+    }
+    else {
+        var maybePromise = Promise._cast(value, PromiseSpawn$_continue, void 0);
+        if (!(maybePromise instanceof Promise)) {
+            if (isArray(maybePromise)) {
+                maybePromise = Promise.all(maybePromise);
+            }
+            else {
+                maybePromise = promiseFromYieldHandler(maybePromise);
+            }
+            if (maybePromise === null) {
+                this._throw(new TypeError("A value was yielded that could not be treated as a promise"));
+                return;
+            }
+        }
+        maybePromise._then(
+            this._next,
+            this._throw,
+            void 0,
+            this,
+            null,
+            void 0
+       );
+    }
+};
+
+PromiseSpawn.prototype._throw = function PromiseSpawn$_throw(reason) {
+    if (errors.canAttach(reason))
+        this._promise._attachExtraTrace(reason);
+    this._continue(
+        tryCatch1(this._generator["throw"], this._generator, reason)
+   );
+};
+
+PromiseSpawn.prototype._next = function PromiseSpawn$_next(value) {
+    this._continue(
+        tryCatch1(this._generator.next, this._generator, value)
+   );
+};
+
+PromiseSpawn.addYieldHandler = function PromiseSpawn$AddYieldHandler(fn) {
+    if (typeof fn !== "function") throw new TypeError("fn must be a function");
+    yieldHandlers.push(fn);
+};
+
+return PromiseSpawn;
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/promisify.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/promisify.js
new file mode 100644
index 0000000..a550fd0
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/promisify.js
@@ -0,0 +1,278 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, INTERNAL) {
+var THIS = {};
+var util = require("./util.js");
+var es5 = require("./es5.js");
+var nodebackForPromise = require("./promise_resolver.js")
+    ._nodebackForPromise;
+var withAppended = util.withAppended;
+var maybeWrapAsError = util.maybeWrapAsError;
+var canEvaluate = util.canEvaluate;
+var notEnumerableProp = util.notEnumerableProp;
+var deprecated = util.deprecated;
+var roriginal = new RegExp("__beforePromisified__" + "$");
+var hasProp = {}.hasOwnProperty;
+function isPromisified(fn) {
+    return fn.__isPromisified__ === true;
+}
+var inheritedMethods = (function() {
+    if (es5.isES5) {
+        var create = Object.create;
+        var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
+        return function(cur) {
+            var original = cur;
+            var ret = [];
+            var visitedKeys = create(null);
+            while (cur !== null) {
+                var keys = es5.keys(cur);
+                for (var i = 0, len = keys.length; i < len; ++i) {
+                    var key = keys[i];
+                    if (visitedKeys[key] ||
+                        roriginal.test(key) ||
+                        hasProp.call(original, key + "__beforePromisified__")
+                   ) {
+                        continue;
+                    }
+                    visitedKeys[key] = true;
+                    var desc = getOwnPropertyDescriptor(cur, key);
+                    if (desc != null &&
+                        typeof desc.value === "function" &&
+                        !isPromisified(desc.value)) {
+                        ret.push(key, desc.value);
+                    }
+                }
+                cur = es5.getPrototypeOf(cur);
+            }
+            return ret;
+        };
+    }
+    else {
+        return function(obj) {
+            var ret = [];
+            /*jshint forin:false */
+            for (var key in obj) {
+                if (roriginal.test(key) ||
+                    hasProp.call(obj, key + "__beforePromisified__")) {
+                    continue;
+                }
+                var fn = obj[key];
+                if (typeof fn === "function" &&
+                    !isPromisified(fn)) {
+                    ret.push(key, fn);
+                }
+            }
+            return ret;
+        };
+    }
+})();
+
+function switchCaseArgumentOrder(likelyArgumentCount) {
+    var ret = [likelyArgumentCount];
+    var min = Math.max(0, likelyArgumentCount - 1 - 5);
+    for(var i = likelyArgumentCount - 1; i >= min; --i) {
+        if (i === likelyArgumentCount) continue;
+        ret.push(i);
+    }
+    for(var i = likelyArgumentCount + 1; i <= 5; ++i) {
+        ret.push(i);
+    }
+    return ret;
+}
+
+function parameterDeclaration(parameterCount) {
+    var ret = new Array(parameterCount);
+    for(var i = 0; i < ret.length; ++i) {
+        ret[i] = "_arg" + i;
+    }
+    return ret.join(", ");
+}
+
+function parameterCount(fn) {
+    if (typeof fn.length === "number") {
+        return Math.max(Math.min(fn.length, 1023 + 1), 0);
+    }
+    return 0;
+}
+
+function propertyAccess(id) {
+    var rident = /^[a-z$_][a-z$_0-9]*$/i;
+
+    if (rident.test(id)) {
+        return "." + id;
+    }
+    else return "['" + id.replace(/(['\\])/g, "\\$1") + "']";
+}
+
+function makeNodePromisifiedEval(callback, receiver, originalName, fn) {
+    var newParameterCount = Math.max(0, parameterCount(fn) - 1);
+    var argumentOrder = switchCaseArgumentOrder(newParameterCount);
+
+    var callbackName = (typeof originalName === "string" ?
+        originalName + "Async" :
+        "promisified");
+
+    function generateCallForArgumentCount(count) {
+        var args = new Array(count);
+        for (var i = 0, len = args.length; i < len; ++i) {
+            args[i] = "arguments[" + i + "]";
+        }
+        var comma = count > 0 ? "," : "";
+
+        if (typeof callback === "string" &&
+            receiver === THIS) {
+            return "this" + propertyAccess(callback) + "("+args.join(",") +
+                comma +" fn);"+
+                "break;";
+        }
+        return (receiver === void 0
+            ? "callback("+args.join(",")+ comma +" fn);"
+            : "callback.call("+(receiver === THIS
+                ? "this"
+                : "receiver")+", "+args.join(",") + comma + " fn);") +
+        "break;";
+    }
+
+    function generateArgumentSwitchCase() {
+        var ret = "";
+        for(var i = 0; i < argumentOrder.length; ++i) {
+            ret += "case " + argumentOrder[i] +":" +
+                generateCallForArgumentCount(argumentOrder[i]);
+        }
+        ret += "default: var args = new Array(len + 1);" +
+            "var i = 0;" +
+            "for (var i = 0; i < len; ++i) { " +
+            "   args[i] = arguments[i];" +
+            "}" +
+            "args[i] = fn;" +
+
+            (typeof callback === "string"
+            ? "this" + propertyAccess(callback) + ".apply("
+            : "callback.apply(") +
+
+            (receiver === THIS ? "this" : "receiver") +
+            ", args); break;";
+        return ret;
+    }
+
+    return new Function("Promise", "callback", "receiver",
+            "withAppended", "maybeWrapAsError", "nodebackForPromise",
+            "INTERNAL",
+        "var ret = function " + callbackName +
+        "(" + parameterDeclaration(newParameterCount) + ") {\"use strict\";" +
+        "var len = arguments.length;" +
+        "var promise = new Promise(INTERNAL);"+
+        "promise._setTrace(" + callbackName + ", void 0);" +
+        "var fn = nodebackForPromise(promise);"+
+        "try {" +
+        "switch(len) {" +
+        generateArgumentSwitchCase() +
+        "}" +
+        "}" +
+        "catch(e){ " +
+        "var wrapped = maybeWrapAsError(e);" +
+        "promise._attachExtraTrace(wrapped);" +
+        "promise._reject(wrapped);" +
+        "}" +
+        "return promise;" +
+        "" +
+        "}; ret.__isPromisified__ = true; return ret;"
+   )(Promise, callback, receiver, withAppended,
+        maybeWrapAsError, nodebackForPromise, INTERNAL);
+}
+
+function makeNodePromisifiedClosure(callback, receiver) {
+    function promisified() {
+        var _receiver = receiver;
+        if (receiver === THIS) _receiver = this;
+        if (typeof callback === "string") {
+            callback = _receiver[callback];
+        }
+        var promise = new Promise(INTERNAL);
+        promise._setTrace(promisified, void 0);
+        var fn = nodebackForPromise(promise);
+        try {
+            callback.apply(_receiver, withAppended(arguments, fn));
+        }
+        catch(e) {
+            var wrapped = maybeWrapAsError(e);
+            promise._attachExtraTrace(wrapped);
+            promise._reject(wrapped);
+        }
+        return promise;
+    }
+    promisified.__isPromisified__ = true;
+    return promisified;
+}
+
+var makeNodePromisified = canEvaluate
+    ? makeNodePromisifiedEval
+    : makeNodePromisifiedClosure;
+
+function _promisify(callback, receiver, isAll) {
+    if (isAll) {
+        var methods = inheritedMethods(callback);
+        for (var i = 0, len = methods.length; i < len; i+= 2) {
+            var key = methods[i];
+            var fn = methods[i+1];
+            var originalKey = key + "__beforePromisified__";
+            var promisifiedKey = key + "Async";
+            notEnumerableProp(callback, originalKey, fn);
+            callback[promisifiedKey] =
+                makeNodePromisified(originalKey, THIS,
+                    key, fn);
+        }
+        util.toFastProperties(callback);
+        return callback;
+    }
+    else {
+        return makeNodePromisified(callback, receiver, void 0, callback);
+    }
+}
+
+Promise.promisify = function Promise$Promisify(fn, receiver) {
+    if (typeof fn === "object" && fn !== null) {
+        deprecated("Promise.promisify for promisifying entire objects is deprecated. Use Promise.promisifyAll instead.");
+        return _promisify(fn, receiver, true);
+    }
+    if (typeof fn !== "function") {
+        throw new TypeError("fn must be a function");
+    }
+    if (isPromisified(fn)) {
+        return fn;
+    }
+    return _promisify(
+        fn,
+        arguments.length < 2 ? THIS : receiver,
+        false);
+};
+
+Promise.promisifyAll = function Promise$PromisifyAll(target) {
+    if (typeof target !== "function" && typeof target !== "object") {
+        throw new TypeError("the target of promisifyAll must be an object or a function");
+    }
+    return _promisify(target, void 0, true);
+};
+};
+
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/properties_promise_array.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/properties_promise_array.js
new file mode 100644
index 0000000..85f5990
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/properties_promise_array.js
@@ -0,0 +1,77 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, PromiseArray) {
+var util = require("./util.js");
+var inherits = util.inherits;
+var es5 = require("./es5.js");
+
+function PropertiesPromiseArray(obj, caller, boundTo) {
+    var keys = es5.keys(obj);
+    var values = new Array(keys.length);
+    for (var i = 0, len = values.length; i < len; ++i) {
+        values[i] = obj[keys[i]];
+    }
+    this.constructor$(values, caller, boundTo);
+    if (!this._isResolved()) {
+        for (var i = 0, len = keys.length; i < len; ++i) {
+            values.push(keys[i]);
+        }
+    }
+}
+inherits(PropertiesPromiseArray, PromiseArray);
+
+PropertiesPromiseArray.prototype._init =
+function PropertiesPromiseArray$_init() {
+    this._init$(void 0, -3) ;
+};
+
+PropertiesPromiseArray.prototype._promiseFulfilled =
+function PropertiesPromiseArray$_promiseFulfilled(value, index) {
+    if (this._isResolved()) return;
+    this._values[index] = value;
+    var totalResolved = ++this._totalResolved;
+    if (totalResolved >= this._length) {
+        var val = {};
+        var keyOffset = this.length();
+        for (var i = 0, len = this.length(); i < len; ++i) {
+            val[this._values[i + keyOffset]] = this._values[i];
+        }
+        this._resolve(val);
+    }
+};
+
+PropertiesPromiseArray.prototype._promiseProgressed =
+function PropertiesPromiseArray$_promiseProgressed(value, index) {
+    if (this._isResolved()) return;
+
+    this._promise._progress({
+        key: this._values[index + this.length()],
+        value: value
+    });
+};
+
+PromiseArray.PropertiesPromiseArray = PropertiesPromiseArray;
+
+return PropertiesPromiseArray;
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/props.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/props.js
new file mode 100644
index 0000000..3cdba43
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/props.js
@@ -0,0 +1,65 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, PromiseArray) {
+    var PropertiesPromiseArray = require("./properties_promise_array.js")(
+        Promise, PromiseArray);
+    var util = require("./util.js");
+    var apiRejection = require("./errors_api_rejection")(Promise);
+    var isObject = util.isObject;
+
+    function Promise$_Props(promises, useBound, caller) {
+        var ret;
+        var castValue = Promise._cast(promises, caller, void 0);
+
+        if (!isObject(castValue)) {
+            return apiRejection("cannot await properties of a non-object");
+        }
+        else if (Promise.is(castValue)) {
+            ret = castValue._then(Promise.props, void 0, void 0,
+                            void 0, void 0, caller);
+        }
+        else {
+            ret = new PropertiesPromiseArray(
+                castValue,
+                caller,
+                useBound === true && castValue._isBound()
+                            ? castValue._boundTo
+                            : void 0
+           ).promise();
+            useBound = false;
+        }
+        if (useBound === true && castValue._isBound()) {
+            ret._setBoundTo(castValue._boundTo);
+        }
+        return ret;
+    }
+
+    Promise.prototype.props = function Promise$props() {
+        return Promise$_Props(this, true, this.props);
+    };
+
+    Promise.props = function Promise$Props(promises) {
+        return Promise$_Props(promises, false, Promise.props);
+    };
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/queue.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/queue.js
new file mode 100644
index 0000000..bbd3f1b
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/queue.js
@@ -0,0 +1,135 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+function arrayCopy(src, srcIndex, dst, dstIndex, len) {
+    for (var j = 0; j < len; ++j) {
+        dst[j + dstIndex] = src[j + srcIndex];
+    }
+}
+
+function pow2AtLeast(n) {
+    n = n >>> 0;
+    n = n - 1;
+    n = n | (n >> 1);
+    n = n | (n >> 2);
+    n = n | (n >> 4);
+    n = n | (n >> 8);
+    n = n | (n >> 16);
+    return n + 1;
+}
+
+function getCapacity(capacity) {
+    if (typeof capacity !== "number") return 16;
+    return pow2AtLeast(
+        Math.min(
+            Math.max(16, capacity), 1073741824)
+   );
+}
+
+function Queue(capacity) {
+    this._capacity = getCapacity(capacity);
+    this._length = 0;
+    this._front = 0;
+    this._makeCapacity();
+}
+
+Queue.prototype._willBeOverCapacity =
+function Queue$_willBeOverCapacity(size) {
+    return this._capacity < size;
+};
+
+Queue.prototype._pushOne = function Queue$_pushOne(arg) {
+    var length = this.length();
+    this._checkCapacity(length + 1);
+    var i = (this._front + length) & (this._capacity - 1);
+    this[i] = arg;
+    this._length = length + 1;
+};
+
+Queue.prototype.push = function Queue$push(fn, receiver, arg) {
+    var length = this.length() + 3;
+    if (this._willBeOverCapacity(length)) {
+        this._pushOne(fn);
+        this._pushOne(receiver);
+        this._pushOne(arg);
+        return;
+    }
+    var j = this._front + length - 3;
+    this._checkCapacity(length);
+    var wrapMask = this._capacity - 1;
+    this[(j + 0) & wrapMask] = fn;
+    this[(j + 1) & wrapMask] = receiver;
+    this[(j + 2) & wrapMask] = arg;
+    this._length = length;
+};
+
+Queue.prototype.shift = function Queue$shift() {
+    var front = this._front,
+        ret = this[front];
+
+    this[front] = void 0;
+    this._front = (front + 1) & (this._capacity - 1);
+    this._length--;
+    return ret;
+};
+
+Queue.prototype.length = function Queue$length() {
+    return this._length;
+};
+
+Queue.prototype._makeCapacity = function Queue$_makeCapacity() {
+    var len = this._capacity;
+    for (var i = 0; i < len; ++i) {
+        this[i] = void 0;
+    }
+};
+
+Queue.prototype._checkCapacity = function Queue$_checkCapacity(size) {
+    if (this._capacity < size) {
+        this._resizeTo(this._capacity << 3);
+    }
+};
+
+Queue.prototype._resizeTo = function Queue$_resizeTo(capacity) {
+    var oldFront = this._front;
+    var oldCapacity = this._capacity;
+    var oldQueue = new Array(oldCapacity);
+    var length = this.length();
+
+    arrayCopy(this, 0, oldQueue, 0, oldCapacity);
+    this._capacity = capacity;
+    this._makeCapacity();
+    this._front = 0;
+    if (oldFront + length <= oldCapacity) {
+        arrayCopy(oldQueue, oldFront, this, 0, length);
+    }
+    else {        var lengthBeforeWrapping =
+            length - ((oldFront + length) & (oldCapacity - 1));
+
+        arrayCopy(oldQueue, oldFront, this, 0, lengthBeforeWrapping);
+        arrayCopy(oldQueue, 0, this, lengthBeforeWrapping,
+                    length - lengthBeforeWrapping);
+    }
+};
+
+module.exports = Queue;
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/race.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/race.js
new file mode 100644
index 0000000..82b8ce1
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/race.js
@@ -0,0 +1,85 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, INTERNAL) {
+    var apiRejection = require("./errors_api_rejection.js")(Promise);
+    var isArray = require("./util.js").isArray;
+
+    var raceLater = function Promise$_raceLater(promise) {
+        return promise.then(function Promise$_lateRacer(array) {
+            return Promise$_Race(array, Promise$_lateRacer, promise);
+        });
+    };
+
+    var hasOwn = {}.hasOwnProperty;
+    function Promise$_Race(promises, caller, parent) {
+        var maybePromise = Promise._cast(promises, caller, void 0);
+
+        if (Promise.is(maybePromise)) {
+            return raceLater(maybePromise);
+        }
+        else if (!isArray(promises)) {
+            return apiRejection("expecting an array, a promise or a thenable");
+        }
+
+        var ret = new Promise(INTERNAL);
+        ret._setTrace(caller, parent);
+        if (parent !== void 0) {
+            if (parent._isBound()) {
+                ret._setBoundTo(parent._boundTo);
+            }
+            if (parent._cancellable()) {
+                ret._setCancellable();
+                ret._cancellationParent = parent;
+            }
+        }
+        var fulfill = ret._fulfill;
+        var reject = ret._reject;
+        for (var i = 0, len = promises.length; i < len; ++i) {
+            var val = promises[i];
+
+            if (val === void 0 && !(hasOwn.call(promises, i))) {
+                continue;
+            }
+
+            Promise.cast(val)._then(
+                fulfill,
+                reject,
+                void 0,
+                ret,
+                null,
+                caller
+           );
+        }
+        return ret;
+    }
+
+    Promise.race = function Promise$Race(promises) {
+        return Promise$_Race(promises, Promise.race, void 0);
+    };
+
+    Promise.prototype.race = function Promise$race() {
+        return Promise$_Race(this, this.race, void 0);
+    };
+
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/reduce.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/reduce.js
new file mode 100644
index 0000000..e9ef95b
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/reduce.js
@@ -0,0 +1,173 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(
+    Promise, Promise$_CreatePromiseArray,
+    PromiseArray, apiRejection, INTERNAL) {
+
+    function Reduction(callback, index, accum, items, receiver) {
+        this.promise = new Promise(INTERNAL);
+        this.index = index;
+        this.length = items.length;
+        this.items = items;
+        this.callback = callback;
+        this.receiver = receiver;
+        this.accum = accum;
+    }
+
+    Reduction.prototype.reject = function Reduction$reject(e) {
+        this.promise._reject(e);
+    };
+
+    Reduction.prototype.fulfill = function Reduction$fulfill(value, index) {
+        this.accum = value;
+        this.index = index + 1;
+        this.iterate();
+    };
+
+    Reduction.prototype.iterate = function Reduction$iterate() {
+        var i = this.index;
+        var len = this.length;
+        var items = this.items;
+        var result = this.accum;
+        var receiver = this.receiver;
+        var callback = this.callback;
+        var iterate = this.iterate;
+
+        for(; i < len; ++i) {
+            result = Promise._cast(
+                callback.call(
+                    receiver,
+                    result,
+                    items[i],
+                    i,
+                    len
+                ),
+                iterate,
+                void 0
+            );
+
+            if (result instanceof Promise) {
+                result._then(
+                    this.fulfill, this.reject, void 0, this, i, iterate);
+                return;
+            }
+        }
+        this.promise._fulfill(result);
+    };
+
+    function Promise$_reducer(fulfilleds, initialValue) {
+        var fn = this;
+        var receiver = void 0;
+        if (typeof fn !== "function")  {
+            receiver = fn.receiver;
+            fn = fn.fn;
+        }
+        var len = fulfilleds.length;
+        var accum = void 0;
+        var startIndex = 0;
+
+        if (initialValue !== void 0) {
+            accum = initialValue;
+            startIndex = 0;
+        }
+        else {
+            startIndex = 1;
+            if (len > 0) accum = fulfilleds[0];
+        }
+        var i = startIndex;
+
+        if (i >= len) {
+            return accum;
+        }
+
+        var reduction = new Reduction(fn, i, accum, fulfilleds, receiver);
+        reduction.iterate();
+        return reduction.promise;
+    }
+
+    function Promise$_unpackReducer(fulfilleds) {
+        var fn = this.fn;
+        var initialValue = this.initialValue;
+        return Promise$_reducer.call(fn, fulfilleds, initialValue);
+    }
+
+    function Promise$_slowReduce(
+        promises, fn, initialValue, useBound, caller) {
+        return initialValue._then(function callee(initialValue) {
+            return Promise$_Reduce(
+                promises, fn, initialValue, useBound, callee);
+        }, void 0, void 0, void 0, void 0, caller);
+    }
+
+    function Promise$_Reduce(promises, fn, initialValue, useBound, caller) {
+        if (typeof fn !== "function") {
+            return apiRejection("fn must be a function");
+        }
+
+        if (useBound === true && promises._isBound()) {
+            fn = {
+                fn: fn,
+                receiver: promises._boundTo
+            };
+        }
+
+        if (initialValue !== void 0) {
+            if (Promise.is(initialValue)) {
+                if (initialValue.isFulfilled()) {
+                    initialValue = initialValue._settledValue;
+                }
+                else {
+                    return Promise$_slowReduce(promises,
+                        fn, initialValue, useBound, caller);
+                }
+            }
+
+            return Promise$_CreatePromiseArray(promises, PromiseArray, caller,
+                useBound === true && promises._isBound()
+                    ? promises._boundTo
+                    : void 0)
+                .promise()
+                ._then(Promise$_unpackReducer, void 0, void 0, {
+                    fn: fn,
+                    initialValue: initialValue
+                }, void 0, Promise.reduce);
+        }
+        return Promise$_CreatePromiseArray(promises, PromiseArray, caller,
+                useBound === true && promises._isBound()
+                    ? promises._boundTo
+                    : void 0).promise()
+            ._then(Promise$_reducer, void 0, void 0, fn, void 0, caller);
+    }
+
+
+    Promise.reduce = function Promise$Reduce(promises, fn, initialValue) {
+        return Promise$_Reduce(promises, fn,
+            initialValue, false, Promise.reduce);
+    };
+
+    Promise.prototype.reduce = function Promise$reduce(fn, initialValue) {
+        return Promise$_Reduce(this, fn, initialValue,
+                                true, this.reduce);
+    };
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/schedule.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/schedule.js
new file mode 100644
index 0000000..ae2271e
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/schedule.js
@@ -0,0 +1,121 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var global = require("./global.js");
+var schedule;
+if (typeof process !== "undefined" && process !== null &&
+    typeof process.cwd === "function" &&
+    typeof process.nextTick === "function" &&
+    typeof process.version === "string") {
+    schedule = function Promise$_Scheduler(fn) {
+        process.nextTick(fn);
+    };
+}
+else if ((typeof global.MutationObserver === "function" ||
+        typeof global.WebkitMutationObserver === "function" ||
+        typeof global.WebKitMutationObserver === "function") &&
+        typeof document !== "undefined" &&
+        typeof document.createElement === "function") {
+
+
+    schedule = (function(){
+        var MutationObserver = global.MutationObserver ||
+            global.WebkitMutationObserver ||
+            global.WebKitMutationObserver;
+        var div = document.createElement("div");
+        var queuedFn = void 0;
+        var observer = new MutationObserver(
+            function Promise$_Scheduler() {
+                var fn = queuedFn;
+                queuedFn = void 0;
+                fn();
+            }
+       );
+        observer.observe(div, {
+            attributes: true
+        });
+        return function Promise$_Scheduler(fn) {
+            queuedFn = fn;
+            div.setAttribute("class", "foo");
+        };
+
+    })();
+}
+else if (typeof global.postMessage === "function" &&
+    typeof global.importScripts !== "function" &&
+    typeof global.addEventListener === "function" &&
+    typeof global.removeEventListener === "function") {
+
+    var MESSAGE_KEY = "bluebird_message_key_" + Math.random();
+    schedule = (function(){
+        var queuedFn = void 0;
+
+        function Promise$_Scheduler(e) {
+            if (e.source === global &&
+                e.data === MESSAGE_KEY) {
+                var fn = queuedFn;
+                queuedFn = void 0;
+                fn();
+            }
+        }
+
+        global.addEventListener("message", Promise$_Scheduler, false);
+
+        return function Promise$_Scheduler(fn) {
+            queuedFn = fn;
+            global.postMessage(
+                MESSAGE_KEY, "*"
+           );
+        };
+
+    })();
+}
+else if (typeof global.MessageChannel === "function") {
+    schedule = (function(){
+        var queuedFn = void 0;
+
+        var channel = new global.MessageChannel();
+        channel.port1.onmessage = function Promise$_Scheduler() {
+                var fn = queuedFn;
+                queuedFn = void 0;
+                fn();
+        };
+
+        return function Promise$_Scheduler(fn) {
+            queuedFn = fn;
+            channel.port2.postMessage(null);
+        };
+    })();
+}
+else if (global.setTimeout) {
+    schedule = function Promise$_Scheduler(fn) {
+        setTimeout(fn, 4);
+    };
+}
+else {
+    schedule = function Promise$_Scheduler(fn) {
+        fn();
+    };
+}
+
+module.exports = schedule;
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/settle.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/settle.js
new file mode 100644
index 0000000..863882f
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/settle.js
@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports =
+    function(Promise, Promise$_CreatePromiseArray, PromiseArray) {
+
+    var SettledPromiseArray = require("./settled_promise_array.js")(
+        Promise, PromiseArray);
+
+    function Promise$_Settle(promises, useBound, caller) {
+        return Promise$_CreatePromiseArray(
+            promises,
+            SettledPromiseArray,
+            caller,
+            useBound === true && promises._isBound()
+                ? promises._boundTo
+                : void 0
+       ).promise();
+    }
+
+    Promise.settle = function Promise$Settle(promises) {
+        return Promise$_Settle(promises, false, Promise.settle);
+    };
+
+    Promise.prototype.settle = function Promise$settle() {
+        return Promise$_Settle(this, true, this.settle);
+    };
+
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/settled_promise_array.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/settled_promise_array.js
new file mode 100644
index 0000000..fd25d4d
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/settled_promise_array.js
@@ -0,0 +1,60 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, PromiseArray) {
+var PromiseInspection = require("./promise_inspection.js");
+var util = require("./util.js");
+var inherits = util.inherits;
+function SettledPromiseArray(values, caller, boundTo) {
+    this.constructor$(values, caller, boundTo);
+}
+inherits(SettledPromiseArray, PromiseArray);
+
+SettledPromiseArray.prototype._promiseResolved =
+function SettledPromiseArray$_promiseResolved(index, inspection) {
+    this._values[index] = inspection;
+    var totalResolved = ++this._totalResolved;
+    if (totalResolved >= this._length) {
+        this._resolve(this._values);
+    }
+};
+
+SettledPromiseArray.prototype._promiseFulfilled =
+function SettledPromiseArray$_promiseFulfilled(value, index) {
+    if (this._isResolved()) return;
+    var ret = new PromiseInspection();
+    ret._bitField = 268435456;
+    ret._settledValue = value;
+    this._promiseResolved(index, ret);
+};
+SettledPromiseArray.prototype._promiseRejected =
+function SettledPromiseArray$_promiseRejected(reason, index) {
+    if (this._isResolved()) return;
+    var ret = new PromiseInspection();
+    ret._bitField = 134217728;
+    ret._settledValue = reason;
+    this._promiseResolved(index, ret);
+};
+
+return SettledPromiseArray;
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/some.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/some.js
new file mode 100644
index 0000000..21c4ecd
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/some.js
@@ -0,0 +1,57 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports =
+function(Promise, Promise$_CreatePromiseArray, PromiseArray, apiRejection) {
+
+    var SomePromiseArray = require("./some_promise_array.js")(PromiseArray);
+    function Promise$_Some(promises, howMany, useBound, caller) {
+        if ((howMany | 0) !== howMany || howMany < 0) {
+            return apiRejection("expecting a positive integer");
+        }
+        var ret = Promise$_CreatePromiseArray(
+            promises,
+            SomePromiseArray,
+            caller,
+            useBound === true && promises._isBound()
+                ? promises._boundTo
+                : void 0
+       );
+        var promise = ret.promise();
+        if (promise.isRejected()) {
+            return promise;
+        }
+        ret.setHowMany(howMany);
+        ret.init();
+        return promise;
+    }
+
+    Promise.some = function Promise$Some(promises, howMany) {
+        return Promise$_Some(promises, howMany, false, Promise.some);
+    };
+
+    Promise.prototype.some = function Promise$some(count) {
+        return Promise$_Some(this, count, true, this.some);
+    };
+
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/some_promise_array.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/some_promise_array.js
new file mode 100644
index 0000000..d3b89d4
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/some_promise_array.js
@@ -0,0 +1,131 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function (PromiseArray) {
+var util = require("./util.js");
+var RangeError = require("./errors.js").RangeError;
+var inherits = util.inherits;
+var isArray = util.isArray;
+
+function SomePromiseArray(values, caller, boundTo) {
+    this.constructor$(values, caller, boundTo);
+    this._howMany = 0;
+    this._unwrap = false;
+    this._initialized = false;
+}
+inherits(SomePromiseArray, PromiseArray);
+
+SomePromiseArray.prototype._init = function SomePromiseArray$_init() {
+    if (!this._initialized) {
+        return;
+    }
+    if (this._howMany === 0) {
+        this._resolve([]);
+        return;
+    }
+    this._init$(void 0, -2);
+    var isArrayResolved = isArray(this._values);
+    this._holes = isArrayResolved ? this._values.length - this.length() : 0;
+
+    if (!this._isResolved() &&
+        isArrayResolved &&
+        this._howMany > this._canPossiblyFulfill()) {
+        var message = "(Promise.some) input array contains less than " +
+                        this._howMany  + " promises";
+        this._reject(new RangeError(message));
+    }
+};
+
+SomePromiseArray.prototype.init = function SomePromiseArray$init() {
+    this._initialized = true;
+    this._init();
+};
+
+SomePromiseArray.prototype.setUnwrap = function SomePromiseArray$setUnwrap() {
+    this._unwrap = true;
+};
+
+SomePromiseArray.prototype.howMany = function SomePromiseArray$howMany() {
+    return this._howMany;
+};
+
+SomePromiseArray.prototype.setHowMany =
+function SomePromiseArray$setHowMany(count) {
+    if (this._isResolved()) return;
+    this._howMany = count;
+};
+
+SomePromiseArray.prototype._promiseFulfilled =
+function SomePromiseArray$_promiseFulfilled(value) {
+    if (this._isResolved()) return;
+    this._addFulfilled(value);
+    if (this._fulfilled() === this.howMany()) {
+        this._values.length = this.howMany();
+        if (this.howMany() === 1 && this._unwrap) {
+            this._resolve(this._values[0]);
+        }
+        else {
+            this._resolve(this._values);
+        }
+    }
+
+};
+SomePromiseArray.prototype._promiseRejected =
+function SomePromiseArray$_promiseRejected(reason) {
+    if (this._isResolved()) return;
+    this._addRejected(reason);
+    if (this.howMany() > this._canPossiblyFulfill()) {
+        if (this._values.length === this.length()) {
+            this._reject([]);
+        }
+        else {
+            this._reject(this._values.slice(this.length() + this._holes));
+        }
+    }
+};
+
+SomePromiseArray.prototype._fulfilled = function SomePromiseArray$_fulfilled() {
+    return this._totalResolved;
+};
+
+SomePromiseArray.prototype._rejected = function SomePromiseArray$_rejected() {
+    return this._values.length - this.length() - this._holes;
+};
+
+SomePromiseArray.prototype._addRejected =
+function SomePromiseArray$_addRejected(reason) {
+    this._values.push(reason);
+};
+
+SomePromiseArray.prototype._addFulfilled =
+function SomePromiseArray$_addFulfilled(value) {
+    this._values[this._totalResolved++] = value;
+};
+
+SomePromiseArray.prototype._canPossiblyFulfill =
+function SomePromiseArray$_canPossiblyFulfill() {
+    return this.length() - this._rejected();
+};
+
+return SomePromiseArray;
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/synchronous_inspection.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/synchronous_inspection.js
new file mode 100644
index 0000000..dcbdc90
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/synchronous_inspection.js
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise) {
+    var PromiseInspection = require("./promise_inspection.js");
+
+    Promise.prototype.inspect = function Promise$inspect() {
+        return new PromiseInspection(this);
+    };
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/thenables.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/thenables.js
new file mode 100644
index 0000000..510da18
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/thenables.js
@@ -0,0 +1,138 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+module.exports = function(Promise, INTERNAL) {
+    var util = require("./util.js");
+    var canAttach = require("./errors.js").canAttach;
+    var errorObj = util.errorObj;
+    var isObject = util.isObject;
+
+    function getThen(obj) {
+        try {
+            return obj.then;
+        }
+        catch(e) {
+            errorObj.e = e;
+            return errorObj;
+        }
+    }
+
+    function Promise$_Cast(obj, caller, originalPromise) {
+        if (isObject(obj)) {
+            if (obj instanceof Promise) {
+                return obj;
+            }
+            else if (isAnyBluebirdPromise(obj)) {
+                var ret = new Promise(INTERNAL);
+                ret._setTrace(caller, void 0);
+                obj._then(
+                    ret._fulfillUnchecked,
+                    ret._rejectUncheckedCheckError,
+                    ret._progressUnchecked,
+                    ret,
+                    null,
+                    void 0
+                );
+                ret._setFollowing();
+                return ret;
+            }
+            var then = getThen(obj);
+            if (then === errorObj) {
+                caller = typeof caller === "function" ? caller : Promise$_Cast;
+                if (originalPromise !== void 0 && canAttach(then.e)) {
+                    originalPromise._attachExtraTrace(then.e);
+                }
+                return Promise.reject(then.e, caller);
+            }
+            else if (typeof then === "function") {
+                caller = typeof caller === "function" ? caller : Promise$_Cast;
+                return Promise$_doThenable(obj, then, caller, originalPromise);
+            }
+        }
+        return obj;
+    }
+
+    var hasProp = {}.hasOwnProperty;
+    function isAnyBluebirdPromise(obj) {
+        return hasProp.call(obj, "_promise0");
+    }
+
+    function Promise$_doThenable(x, then, caller, originalPromise) {
+        var resolver = Promise.defer(caller);
+        var called = false;
+        try {
+            then.call(
+                x,
+                Promise$_resolveFromThenable,
+                Promise$_rejectFromThenable,
+                Promise$_progressFromThenable
+            );
+        }
+        catch(e) {
+            if (!called) {
+                called = true;
+                var trace = canAttach(e) ? e : new Error(e + "");
+                if (originalPromise !== void 0) {
+                    originalPromise._attachExtraTrace(trace);
+                }
+                resolver.promise._reject(e, trace);
+            }
+        }
+        return resolver.promise;
+
+        function Promise$_resolveFromThenable(y) {
+            if (called) return;
+            called = true;
+
+            if (x === y) {
+                var e = Promise._makeSelfResolutionError();
+                if (originalPromise !== void 0) {
+                    originalPromise._attachExtraTrace(e);
+                }
+                resolver.promise._reject(e, void 0);
+                return;
+            }
+            resolver.resolve(y);
+        }
+
+        function Promise$_rejectFromThenable(r) {
+            if (called) return;
+            called = true;
+            var trace = canAttach(r) ? r : new Error(r + "");
+            if (originalPromise !== void 0) {
+                originalPromise._attachExtraTrace(trace);
+            }
+            resolver.promise._reject(r, trace);
+        }
+
+        function Promise$_progressFromThenable(v) {
+            if (called) return;
+            var promise = resolver.promise;
+            if (typeof promise._progress === "function") {
+                promise._progress(v);
+            }
+        }
+    }
+
+    Promise._cast = Promise$_Cast;
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/timers.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/timers.js
new file mode 100644
index 0000000..8edcac2
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/timers.js
@@ -0,0 +1,114 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+
+var global = require("./global.js");
+var setTimeout = function(fn, time) {
+    var $_len = arguments.length;var args = new Array($_len - 2); for(var $_i = 2; $_i < $_len; ++$_i) {args[$_i - 2] = arguments[$_i];}
+    global.setTimeout(function() {
+        fn.apply(void 0, args);
+    }, time);
+};
+
+var pass = {};
+global.setTimeout( function(_) {
+    if(_ === pass) {
+        setTimeout = global.setTimeout;
+    }
+}, 1, pass);
+
+module.exports = function(Promise, INTERNAL) {
+    var util = require("./util.js");
+    var errors = require("./errors.js");
+    var apiRejection = require("./errors_api_rejection")(Promise);
+    var TimeoutError = Promise.TimeoutError;
+
+    var afterTimeout = function Promise$_afterTimeout(promise, message, ms) {
+        if (!promise.isPending()) return;
+        if (typeof message !== "string") {
+            message = "operation timed out after" + " " + ms + " ms"
+        }
+        var err = new TimeoutError(message);
+        errors.markAsOriginatingFromRejection(err);
+        promise._attachExtraTrace(err);
+        promise._rejectUnchecked(err);
+    };
+
+    var afterDelay = function Promise$_afterDelay(value, promise) {
+        promise._fulfill(value);
+    };
+
+    Promise.delay = function Promise$Delay(value, ms, caller) {
+        if (ms === void 0) {
+            ms = value;
+            value = void 0;
+        }
+        ms = +ms;
+        if (typeof caller !== "function") {
+            caller = Promise.delay;
+        }
+        var maybePromise = Promise._cast(value, caller, void 0);
+        var promise = new Promise(INTERNAL);
+
+        if (Promise.is(maybePromise)) {
+            if (maybePromise._isBound()) {
+                promise._setBoundTo(maybePromise._boundTo);
+            }
+            if (maybePromise._cancellable()) {
+                promise._setCancellable();
+                promise._cancellationParent = maybePromise;
+            }
+            promise._setTrace(caller, maybePromise);
+            promise._follow(maybePromise);
+            return promise.then(function(value) {
+                return Promise.delay(value, ms);
+            });
+        }
+        else {
+            promise._setTrace(caller, void 0);
+            setTimeout(afterDelay, ms, value, promise);
+        }
+        return promise;
+    };
+
+    Promise.prototype.delay = function Promise$delay(ms) {
+        return Promise.delay(this, ms, this.delay);
+    };
+
+    Promise.prototype.timeout = function Promise$timeout(ms, message) {
+        ms = +ms;
+
+        var ret = new Promise(INTERNAL);
+        ret._setTrace(this.timeout, this);
+
+        if (this._isBound()) ret._setBoundTo(this._boundTo);
+        if (this._cancellable()) {
+            ret._setCancellable();
+            ret._cancellationParent = this;
+        }
+        ret._follow(this);
+        setTimeout(afterTimeout, ms, ret, message, ms);
+        return ret;
+    };
+
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/util.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/util.js
new file mode 100644
index 0000000..fbd34e9
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/js/zalgo/util.js
@@ -0,0 +1,193 @@
+/**
+ * Copyright (c) 2014 Petka Antonov
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ * 
+ */
+"use strict";
+var global = require("./global.js");
+var es5 = require("./es5.js");
+var haveGetters = (function(){
+    try {
+        var o = {};
+        es5.defineProperty(o, "f", {
+            get: function () {
+                return 3;
+            }
+        });
+        return o.f === 3;
+    }
+    catch (e) {
+        return false;
+    }
+
+})();
+
+var canEvaluate = (function() {
+    if (typeof window !== "undefined" && window !== null &&
+        typeof window.document !== "undefined" &&
+        typeof navigator !== "undefined" && navigator !== null &&
+        typeof navigator.appName === "string" &&
+        window === global) {
+        return false;
+    }
+    return true;
+})();
+
+function deprecated(msg) {
+    if (typeof console !== "undefined" && console !== null &&
+        typeof console.warn === "function") {
+        console.warn("Bluebird: " + msg);
+    }
+}
+
+var errorObj = {e: {}};
+function tryCatch1(fn, receiver, arg) {
+    try {
+        return fn.call(receiver, arg);
+    }
+    catch (e) {
+        errorObj.e = e;
+        return errorObj;
+    }
+}
+
+function tryCatch2(fn, receiver, arg, arg2) {
+    try {
+        return fn.call(receiver, arg, arg2);
+    }
+    catch (e) {
+        errorObj.e = e;
+        return errorObj;
+    }
+}
+
+function tryCatchApply(fn, args, receiver) {
+    try {
+        return fn.apply(receiver, args);
+    }
+    catch (e) {
+        errorObj.e = e;
+        return errorObj;
+    }
+}
+
+var inherits = function(Child, Parent) {
+    var hasProp = {}.hasOwnProperty;
+
+    function T() {
+        this.constructor = Child;
+        this.constructor$ = Parent;
+        for (var propertyName in Parent.prototype) {
+            if (hasProp.call(Parent.prototype, propertyName) &&
+                propertyName.charAt(propertyName.length-1) !== "$"
+           ) {
+                this[propertyName + "$"] = Parent.prototype[propertyName];
+            }
+        }
+    }
+    T.prototype = Parent.prototype;
+    Child.prototype = new T();
+    return Child.prototype;
+};
+
+function asString(val) {
+    return typeof val === "string" ? val : ("" + val);
+}
+
+function isPrimitive(val) {
+    return val == null || val === true || val === false ||
+        typeof val === "string" || typeof val === "number";
+
+}
+
+function isObject(value) {
+    return !isPrimitive(value);
+}
+
+function maybeWrapAsError(maybeError) {
+    if (!isPrimitive(maybeError)) return maybeError;
+
+    return new Error(asString(maybeError));
+}
+
+function withAppended(target, appendee) {
+    var len = target.length;
+    var ret = new Array(len + 1);
+    var i;
+    for (i = 0; i < len; ++i) {
+        ret[i] = target[i];
+    }
+    ret[i] = appendee;
+    return ret;
+}
+
+
+function notEnumerableProp(obj, name, value) {
+    if (isPrimitive(obj)) return obj;
+    var descriptor = {
+        value: value,
+        configurable: true,
+        enumerable: false,
+        writable: true
+    };
+    es5.defineProperty(obj, name, descriptor);
+    return obj;
+}
+
+
+var wrapsPrimitiveReceiver = (function() {
+    return this !== "string";
+}).call("string");
+
+function thrower(r) {
+    throw r;
+}
+
+
+function toFastProperties(obj) {
+    /*jshint -W027*/
+    function f() {}
+    f.prototype = obj;
+    return f;
+    eval(obj);
+}
+
+var ret = {
+    thrower: thrower,
+    isArray: es5.isArray,
+    haveGetters: haveGetters,
+    notEnumerableProp: notEnumerableProp,
+    isPrimitive: isPrimitive,
+    isObject: isObject,
+    canEvaluate: canEvaluate,
+    deprecated: deprecated,
+    errorObj: errorObj,
+    tryCatch1: tryCatch1,
+    tryCatch2: tryCatch2,
+    tryCatchApply: tryCatchApply,
+    inherits: inherits,
+    withAppended: withAppended,
+    asString: asString,
+    maybeWrapAsError: maybeWrapAsError,
+    wrapsPrimitiveReceiver: wrapsPrimitiveReceiver,
+    toFastProperties: toFastProperties
+};
+
+module.exports = ret;
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/package.json b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/package.json
new file mode 100644
index 0000000..5980304
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/package.json
@@ -0,0 +1,85 @@
+{
+  "name": "bluebird",
+  "description": "Full featured Promises/A+ implementation with exceptionally good performance",
+  "version": "1.1.1",
+  "keywords": [
+    "promise",
+    "performance",
+    "promises",
+    "promises-a",
+    "promises-aplus",
+    "async",
+    "await",
+    "deferred",
+    "deferreds",
+    "future",
+    "flow control",
+    "dsl",
+    "fluent interface"
+  ],
+  "scripts": {
+    "test": "grunt test"
+  },
+  "homepage": "https://github.com/petkaantonov/bluebird",
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/petkaantonov/bluebird.git"
+  },
+  "bugs": {
+    "url": "http://github.com/petkaantonov/bluebird/issues"
+  },
+  "license": "MIT",
+  "author": {
+    "name": "Petka Antonov",
+    "email": "petka_antonov@hotmail.com",
+    "url": "http://github.com/petkaantonov/"
+  },
+  "devDependencies": {
+    "grunt": "~0.4.1",
+    "grunt-contrib-jshint": "~0.6.4",
+    "grunt-contrib-watch": "latest",
+    "grunt-contrib-connect": "latest",
+    "grunt-saucelabs": "latest",
+    "acorn": "~0.3.1",
+    "mocha": "~1.12.1",
+    "q": "~0.9.7",
+    "when": "~2.4.0",
+    "deferred": "~0.6.5",
+    "rsvp": "~2.0.4",
+    "avow": "~2.0.1",
+    "jsdom": "~0.8.4",
+    "jquery-browserify": "~1.8.1",
+    "sinon": "~1.7.3",
+    "kew": "~0.2.2",
+    "browserify": "~2.35.0",
+    "concurrent": "~0.3.2",
+    "text-table": "~0.2.0",
+    "grunt-cli": "~0.1.9",
+    "jshint-stylish": "~0.1.3",
+    "semver-utils": "~1.1.0",
+    "rimraf": "~2.2.6",
+    "mkdirp": "~0.3.5"
+  },
+  "main": "./js/main/bluebird.js",
+  "_id": "bluebird@1.1.1",
+  "dist": {
+    "shasum": "744e9980145e2ebc41a9e34826f913096667fb33",
+    "tarball": "http://registry.npmjs.org/bluebird/-/bluebird-1.1.1.tgz"
+  },
+  "_from": "bluebird@~1.1.0",
+  "_npmVersion": "1.4.3",
+  "_npmUser": {
+    "name": "esailija",
+    "email": "petka_antonov@hotmail.com"
+  },
+  "maintainers": [
+    {
+      "name": "esailija",
+      "email": "petka_antonov@hotmail.com"
+    }
+  ],
+  "directories": {},
+  "_shasum": "744e9980145e2ebc41a9e34826f913096667fb33",
+  "_resolved": "https://registry.npmjs.org/bluebird/-/bluebird-1.1.1.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/zalgo.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/zalgo.js
new file mode 100644
index 0000000..1357352
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/bluebird/zalgo.js
@@ -0,0 +1 @@
+module.exports = require('./js/zalgo/bluebird.js');
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/commander/History.md b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/commander/History.md
new file mode 100644
index 0000000..3a69559
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/commander/History.md
@@ -0,0 +1,239 @@
+
+2.7.1 / 2015-03-11
+==================
+
+ * Revert #347 (fix collisions when option and first arg have same name) which causes a bug in #367.
+
+2.7.0 / 2015-03-09
+==================
+
+ * Fix git-style bug when installed globally. Close #335 #349 @zhiyelee
+ * Fix collisions when option and first arg have same name. Close #346 #347 @tonylukasavage
+ * Add support for camelCase on `opts()`. Close #353  @nkzawa
+ * Add node.js 0.12 and io.js to travis.yml
+ * Allow RegEx options. #337 @palanik
+ * Fixes exit code when sub-command failing.  Close #260 #332 @pirelenito
+ * git-style `bin` files in $PATH make sense. Close #196 #327  @zhiyelee
+
+2.6.0 / 2014-12-30
+==================
+
+  * added `Command#allowUnknownOption` method. Close #138 #318 @doozr @zhiyelee
+  * Add application description to the help msg. Close #112 @dalssoft
+
+2.5.1 / 2014-12-15
+==================
+
+  * fixed two bugs incurred by variadic arguments. Close #291 @Quentin01 #302 @zhiyelee
+
+2.5.0 / 2014-10-24
+==================
+
+ * add support for variadic arguments. Closes #277 @whitlockjc
+
+2.4.0 / 2014-10-17
+==================
+
+ * fixed a bug on executing the coercion function of subcommands option. Closes #270
+ * added `Command.prototype.name` to retrieve command name. Closes #264 #266 @tonylukasavage
+ * added `Command.prototype.opts` to retrieve all the options as a simple object of key-value pairs. Closes #262 @tonylukasavage
+ * fixed a bug on subcommand name. Closes #248 @jonathandelgado
+ * fixed function normalize doesn’t honor option terminator. Closes #216 @abbr
+
+2.3.0 / 2014-07-16
+==================
+
+ * add command alias'. Closes PR #210
+ * fix: Typos. Closes #99
+ * fix: Unused fs module. Closes #217
+
+2.2.0 / 2014-03-29
+==================
+
+ * add passing of previous option value
+ * fix: support subcommands on windows. Closes #142
+ * Now the defaultValue passed as the second argument of the coercion function.
+
+2.1.0 / 2013-11-21
+==================
+
+ * add: allow cflag style option params, unit test, fixes #174
+
+2.0.0 / 2013-07-18
+==================
+
+ * remove input methods (.prompt, .confirm, etc)
+
+1.3.2 / 2013-07-18
+==================
+
+ * add support for sub-commands to co-exist with the original command
+
+1.3.1 / 2013-07-18
+==================
+
+ * add quick .runningCommand hack so you can opt-out of other logic when running a sub command
+
+1.3.0 / 2013-07-09
+==================
+
+ * add EACCES error handling
+ * fix sub-command --help
+
+1.2.0 / 2013-06-13
+==================
+
+ * allow "-" hyphen as an option argument
+ * support for RegExp coercion
+
+1.1.1 / 2012-11-20
+==================
+
+  * add more sub-command padding
+  * fix .usage() when args are present. Closes #106
+
+1.1.0 / 2012-11-16
+==================
+
+  * add git-style executable subcommand support. Closes #94
+
+1.0.5 / 2012-10-09
+==================
+
+  * fix `--name` clobbering. Closes #92
+  * fix examples/help. Closes #89
+
+1.0.4 / 2012-09-03
+==================
+
+  * add `outputHelp()` method.
+
+1.0.3 / 2012-08-30
+==================
+
+  * remove invalid .version() defaulting
+
+1.0.2 / 2012-08-24
+==================
+
+  * add `--foo=bar` support [arv]
+  * fix password on node 0.8.8. Make backward compatible with 0.6 [focusaurus]
+
+1.0.1 / 2012-08-03
+==================
+
+  * fix issue #56
+  * fix tty.setRawMode(mode) was moved to tty.ReadStream#setRawMode() (i.e. process.stdin.setRawMode())
+
+1.0.0 / 2012-07-05
+==================
+
+  * add support for optional option descriptions
+  * add defaulting of `.version()` to package.json's version
+
+0.6.1 / 2012-06-01
+==================
+
+  * Added: append (yes or no) on confirmation
+  * Added: allow node.js v0.7.x
+
+0.6.0 / 2012-04-10
+==================
+
+  * Added `.prompt(obj, callback)` support. Closes #49
+  * Added default support to .choose(). Closes #41
+  * Fixed the choice example
+
+0.5.1 / 2011-12-20
+==================
+
+  * Fixed `password()` for recent nodes. Closes #36
+
+0.5.0 / 2011-12-04
+==================
+
+  * Added sub-command option support [itay]
+
+0.4.3 / 2011-12-04
+==================
+
+  * Fixed custom help ordering. Closes #32
+
+0.4.2 / 2011-11-24
+==================
+
+  * Added travis support
+  * Fixed: line-buffered input automatically trimmed. Closes #31
+
+0.4.1 / 2011-11-18
+==================
+
+  * Removed listening for "close" on --help
+
+0.4.0 / 2011-11-15
+==================
+
+  * Added support for `--`. Closes #24
+
+0.3.3 / 2011-11-14
+==================
+
+  * Fixed: wait for close event when writing help info [Jerry Hamlet]
+
+0.3.2 / 2011-11-01
+==================
+
+  * Fixed long flag definitions with values [felixge]
+
+0.3.1 / 2011-10-31
+==================
+
+  * Changed `--version` short flag to `-V` from `-v`
+  * Changed `.version()` so it's configurable [felixge]
+
+0.3.0 / 2011-10-31
+==================
+
+  * Added support for long flags only. Closes #18
+
+0.2.1 / 2011-10-24
+==================
+
+  * "node": ">= 0.4.x < 0.7.0". Closes #20
+
+0.2.0 / 2011-09-26
+==================
+
+  * Allow for defaults that are not just boolean. Default peassignment only occurs for --no-*, optional, and required arguments. [Jim Isaacs]
+
+0.1.0 / 2011-08-24
+==================
+
+  * Added support for custom `--help` output
+
+0.0.5 / 2011-08-18
+==================
+
+  * Changed: when the user enters nothing prompt for password again
+  * Fixed issue with passwords beginning with numbers [NuckChorris]
+
+0.0.4 / 2011-08-15
+==================
+
+  * Fixed `Commander#args`
+
+0.0.3 / 2011-08-15
+==================
+
+  * Added default option value support
+
+0.0.2 / 2011-08-15
+==================
+
+  * Added mask support to `Command#password(str[, mask], fn)`
+  * Added `Command#password(str, fn)`
+
+0.0.1 / 2010-01-03
+==================
+
+  * Initial release
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/commander/LICENSE b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/commander/LICENSE
new file mode 100644
index 0000000..10f997a
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/commander/LICENSE
@@ -0,0 +1,22 @@
+(The MIT License)
+
+Copyright (c) 2011 TJ Holowaychuk <tj@vision-media.ca>
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/commander/Readme.md b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/commander/Readme.md
new file mode 100644
index 0000000..4e091d2
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/commander/Readme.md
@@ -0,0 +1,311 @@
+# Commander.js
+
+
+[![Build Status](https://api.travis-ci.org/tj/commander.js.svg)](http://travis-ci.org/tj/commander.js)
+[![NPM Version](http://img.shields.io/npm/v/commander.svg?style=flat)](https://www.npmjs.org/package/commander)
+[![NPM Downloads](https://img.shields.io/npm/dm/commander.svg?style=flat)](https://www.npmjs.org/package/commander)
+[![Join the chat at https://gitter.im/tj/commander.js](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/tj/commander.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+
+  The complete solution for [node.js](http://nodejs.org) command-line interfaces, inspired by Ruby's [commander](https://github.com/tj/commander).  
+  [API documentation](http://tj.github.com/commander.js/)
+
+
+## Installation
+
+    $ npm install commander
+
+## Option parsing
+
+ Options with commander are defined with the `.option()` method, also serving as documentation for the options. The example below parses args and options from `process.argv`, leaving remaining args as the `program.args` array which were not consumed by options.
+
+```js
+#!/usr/bin/env node
+
+/**
+ * Module dependencies.
+ */
+
+var program = require('commander');
+
+program
+  .version('0.0.1')
+  .option('-p, --peppers', 'Add peppers')
+  .option('-P, --pineapple', 'Add pineapple')
+  .option('-b, --bbq', 'Add bbq sauce')
+  .option('-c, --cheese [type]', 'Add the specified type of cheese [marble]', 'marble')
+  .parse(process.argv);
+
+console.log('you ordered a pizza with:');
+if (program.peppers) console.log('  - peppers');
+if (program.pineapple) console.log('  - pineapple');
+if (program.bbq) console.log('  - bbq');
+console.log('  - %s cheese', program.cheese);
+```
+
+ Short flags may be passed as a single arg, for example `-abc` is equivalent to `-a -b -c`. Multi-word options such as "--template-engine" are camel-cased, becoming `program.templateEngine` etc.
+
+
+## Coercion
+
+```js
+function range(val) {
+  return val.split('..').map(Number);
+}
+
+function list(val) {
+  return val.split(',');
+}
+
+function collect(val, memo) {
+  memo.push(val);
+  return memo;
+}
+
+function increaseVerbosity(v, total) {
+  return total + 1;
+}
+
+program
+  .version('0.0.1')
+  .usage('[options] <file ...>')
+  .option('-i, --integer <n>', 'An integer argument', parseInt)
+  .option('-f, --float <n>', 'A float argument', parseFloat)
+  .option('-r, --range <a>..<b>', 'A range', range)
+  .option('-l, --list <items>', 'A list', list)
+  .option('-o, --optional [value]', 'An optional value')
+  .option('-c, --collect [value]', 'A repeatable value', collect, [])
+  .option('-v, --verbose', 'A value that can be increased', increaseVerbosity, 0)
+  .parse(process.argv);
+
+console.log(' int: %j', program.integer);
+console.log(' float: %j', program.float);
+console.log(' optional: %j', program.optional);
+program.range = program.range || [];
+console.log(' range: %j..%j', program.range[0], program.range[1]);
+console.log(' list: %j', program.list);
+console.log(' collect: %j', program.collect);
+console.log(' verbosity: %j', program.verbose);
+console.log(' args: %j', program.args);
+```
+
+## Regular Expression
+```js
+program
+  .version('0.0.1')
+  .option('-s --size <size>', 'Pizza size', /^(large|medium|small)$/i, 'medium')
+  .option('-d --drink [drink]', 'Drink', /^(coke|pepsi|izze)$/i)
+  .parse(process.argv);
+  
+console.log(' size: %j', program.size);
+console.log(' drink: %j', program.drink);
+```
+
+## Variadic arguments
+
+ The last argument of a command can be variadic, and only the last argument.  To make an argument variadic you have to
+ append `...` to the argument name.  Here is an example:
+
+```js
+#!/usr/bin/env node
+
+/**
+ * Module dependencies.
+ */
+
+var program = require('commander');
+
+program
+  .version('0.0.1')
+  .command('rmdir <dir> [otherDirs...]')
+  .action(function (dir, otherDirs) {
+    console.log('rmdir %s', dir);
+    if (otherDirs) {
+      otherDirs.forEach(function (oDir) {
+        console.log('rmdir %s', oDir);
+      });
+    }
+  });
+
+program.parse(process.argv);
+```
+
+ An `Array` is used for the value of a variadic argument.  This applies to `program.args` as well as the argument passed
+ to your action as demonstrated above.
+
+## Git-style sub-commands
+
+```js
+// file: ./examples/pm
+var program = require('..');
+
+program
+  .version('0.0.1')
+  .command('install [name]', 'install one or more packages')
+  .command('search [query]', 'search with optional query')
+  .command('list', 'list packages installed')
+  .parse(process.argv);
+```
+
+When `.command()` is invoked with a description argument, no `.action(callback)` should be called to handle sub-commands, otherwise there will be an error. This tells commander that you're going to use separate executables for sub-commands, much like `git(1)` and other popular tools.  
+The commander will try to search the executables in the directory of the entry script (like `./examples/pm`) with the name `program-command`, like `pm-install`, `pm-search`.
+
+If the program is designed to installed globally, make sure the executables have proper modes, like `755`.
+
+## Automated --help
+
+ The help information is auto-generated based on the information commander already knows about your program, so the following `--help` info is for free:
+
+```  
+ $ ./examples/pizza --help
+
+   Usage: pizza [options]
+
+   An application for pizzas ordering
+
+   Options:
+
+     -h, --help           output usage information
+     -V, --version        output the version number
+     -p, --peppers        Add peppers
+     -P, --pineapple      Add pineapple
+     -b, --bbq            Add bbq sauce
+     -c, --cheese <type>  Add the specified type of cheese [marble]
+     -C, --no-cheese      You do not want any cheese
+
+```
+
+## Custom help
+
+ You can display arbitrary `-h, --help` information
+ by listening for "--help". Commander will automatically
+ exit once you are done so that the remainder of your program
+ does not execute causing undesired behaviours, for example
+ in the following executable "stuff" will not output when
+ `--help` is used.
+
+```js
+#!/usr/bin/env node
+
+/**
+ * Module dependencies.
+ */
+
+var program = require('commander');
+
+program
+  .version('0.0.1')
+  .option('-f, --foo', 'enable some foo')
+  .option('-b, --bar', 'enable some bar')
+  .option('-B, --baz', 'enable some baz');
+
+// must be before .parse() since
+// node's emit() is immediate
+
+program.on('--help', function(){
+  console.log('  Examples:');
+  console.log('');
+  console.log('    $ custom-help --help');
+  console.log('    $ custom-help -h');
+  console.log('');
+});
+
+program.parse(process.argv);
+
+console.log('stuff');
+```
+
+Yields the following help output when `node script-name.js -h` or `node script-name.js --help` are run:
+
+```
+
+Usage: custom-help [options]
+
+Options:
+
+  -h, --help     output usage information
+  -V, --version  output the version number
+  -f, --foo      enable some foo
+  -b, --bar      enable some bar
+  -B, --baz      enable some baz
+
+Examples:
+
+  $ custom-help --help
+  $ custom-help -h
+
+```
+
+## .outputHelp()
+
+Output help information without exiting.
+
+If you want to display help by default (e.g. if no command was provided), you can use something like:
+
+```js
+var program = require('commander');
+
+program
+  .version('0.0.1')
+  .command('getstream [url]', 'get stream URL')
+  .parse(process.argv);
+
+  if (!process.argv.slice(2).length) {
+    program.outputHelp();
+  }
+```
+
+## .help()
+
+  Output help information and exit immediately.
+
+## Examples
+
+```js
+var program = require('commander');
+
+program
+  .version('0.0.1')
+  .option('-C, --chdir <path>', 'change the working directory')
+  .option('-c, --config <path>', 'set config path. defaults to ./deploy.conf')
+  .option('-T, --no-tests', 'ignore test hook')
+
+program
+  .command('setup [env]')
+  .description('run setup commands for all envs')
+  .option("-s, --setup_mode [mode]", "Which setup mode to use")
+  .action(function(env, options){
+    var mode = options.setup_mode || "normal";
+    env = env || 'all';
+    console.log('setup for %s env(s) with %s mode', env, mode);
+  });
+
+program
+  .command('exec <cmd>')
+  .alias('ex')
+  .description('execute the given remote cmd')
+  .option("-e, --exec_mode <mode>", "Which exec mode to use")
+  .action(function(cmd, options){
+    console.log('exec "%s" using %s mode', cmd, options.exec_mode);
+  }).on('--help', function() {
+    console.log('  Examples:');
+    console.log();
+    console.log('    $ deploy exec sequential');
+    console.log('    $ deploy exec async');
+    console.log();
+  });
+
+program
+  .command('*')
+  .action(function(env){
+    console.log('deploying "%s"', env);
+  });
+
+program.parse(process.argv);
+```
+
+More Demos can be found in the [examples](https://github.com/tj/commander.js/tree/master/examples) directory.
+
+## License
+
+MIT
+
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/commander/index.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/commander/index.js
new file mode 100644
index 0000000..1abf4df
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/commander/index.js
@@ -0,0 +1,1051 @@
+
+/**
+ * Module dependencies.
+ */
+
+var EventEmitter = require('events').EventEmitter;
+var spawn = require('child_process').spawn;
+var readlink = require('graceful-readlink').readlinkSync;
+var path = require('path');
+var dirname = path.dirname;
+var basename = path.basename;
+var fs = require('fs');
+
+/**
+ * Expose the root command.
+ */
+
+exports = module.exports = new Command();
+
+/**
+ * Expose `Command`.
+ */
+
+exports.Command = Command;
+
+/**
+ * Expose `Option`.
+ */
+
+exports.Option = Option;
+
+/**
+ * Initialize a new `Option` with the given `flags` and `description`.
+ *
+ * @param {String} flags
+ * @param {String} description
+ * @api public
+ */
+
+function Option(flags, description) {
+  this.flags = flags;
+  this.required = ~flags.indexOf('<');
+  this.optional = ~flags.indexOf('[');
+  this.bool = !~flags.indexOf('-no-');
+  flags = flags.split(/[ ,|]+/);
+  if (flags.length > 1 && !/^[[<]/.test(flags[1])) this.short = flags.shift();
+  this.long = flags.shift();
+  this.description = description || '';
+}
+
+/**
+ * Return option name.
+ *
+ * @return {String}
+ * @api private
+ */
+
+Option.prototype.name = function() {
+  return this.long
+    .replace('--', '')
+    .replace('no-', '');
+};
+
+/**
+ * Check if `arg` matches the short or long flag.
+ *
+ * @param {String} arg
+ * @return {Boolean}
+ * @api private
+ */
+
+Option.prototype.is = function(arg) {
+  return arg == this.short || arg == this.long;
+};
+
+/**
+ * Initialize a new `Command`.
+ *
+ * @param {String} name
+ * @api public
+ */
+
+function Command(name) {
+  this.commands = [];
+  this.options = [];
+  this._execs = [];
+  this._allowUnknownOption = false;
+  this._args = [];
+  this._name = name;
+}
+
+/**
+ * Inherit from `EventEmitter.prototype`.
+ */
+
+Command.prototype.__proto__ = EventEmitter.prototype;
+
+/**
+ * Add command `name`.
+ *
+ * The `.action()` callback is invoked when the
+ * command `name` is specified via __ARGV__,
+ * and the remaining arguments are applied to the
+ * function for access.
+ *
+ * When the `name` is "*" an un-matched command
+ * will be passed as the first arg, followed by
+ * the rest of __ARGV__ remaining.
+ *
+ * Examples:
+ *
+ *      program
+ *        .version('0.0.1')
+ *        .option('-C, --chdir <path>', 'change the working directory')
+ *        .option('-c, --config <path>', 'set config path. defaults to ./deploy.conf')
+ *        .option('-T, --no-tests', 'ignore test hook')
+ *
+ *      program
+ *        .command('setup')
+ *        .description('run remote setup commands')
+ *        .action(function() {
+ *          console.log('setup');
+ *        });
+ *
+ *      program
+ *        .command('exec <cmd>')
+ *        .description('run the given remote command')
+ *        .action(function(cmd) {
+ *          console.log('exec "%s"', cmd);
+ *        });
+ *
+ *      program
+ *        .command('teardown <dir> [otherDirs...]')
+ *        .description('run teardown commands')
+ *        .action(function(dir, otherDirs) {
+ *          console.log('dir "%s"', dir);
+ *          if (otherDirs) {
+ *            otherDirs.forEach(function (oDir) {
+ *              console.log('dir "%s"', oDir);
+ *            });
+ *          }
+ *        });
+ *
+ *      program
+ *        .command('*')
+ *        .description('deploy the given env')
+ *        .action(function(env) {
+ *          console.log('deploying "%s"', env);
+ *        });
+ *
+ *      program.parse(process.argv);
+  *
+ * @param {String} name
+ * @param {String} [desc] for git-style sub-commands
+ * @return {Command} the new command
+ * @api public
+ */
+
+Command.prototype.command = function(name, desc) {
+  var args = name.split(/ +/);
+  var cmd = new Command(args.shift());
+
+  if (desc) {
+    cmd.description(desc);
+    this.executables = true;
+    this._execs[cmd._name] = true;
+  }
+
+  this.commands.push(cmd);
+  cmd.parseExpectedArgs(args);
+  cmd.parent = this;
+
+  if (desc) return this;
+  return cmd;
+};
+
+/**
+ * Add an implicit `help [cmd]` subcommand
+ * which invokes `--help` for the given command.
+ *
+ * @api private
+ */
+
+Command.prototype.addImplicitHelpCommand = function() {
+  this.command('help [cmd]', 'display help for [cmd]');
+};
+
+/**
+ * Parse expected `args`.
+ *
+ * For example `["[type]"]` becomes `[{ required: false, name: 'type' }]`.
+ *
+ * @param {Array} args
+ * @return {Command} for chaining
+ * @api public
+ */
+
+Command.prototype.parseExpectedArgs = function(args) {
+  if (!args.length) return;
+  var self = this;
+  args.forEach(function(arg) {
+    var argDetails = {
+      required: false,
+      name: '',
+      variadic: false
+    };
+
+    switch (arg[0]) {
+      case '<':
+        argDetails.required = true;
+        argDetails.name = arg.slice(1, -1);
+        break;
+      case '[':
+        argDetails.name = arg.slice(1, -1);
+        break;
+    }
+
+    if (argDetails.name.length > 3 && argDetails.name.slice(-3) === '...') {
+      argDetails.variadic = true;
+      argDetails.name = argDetails.name.slice(0, -3);
+    }
+    if (argDetails.name) {
+      self._args.push(argDetails);
+    }
+  });
+  return this;
+};
+
+/**
+ * Register callback `fn` for the command.
+ *
+ * Examples:
+ *
+ *      program
+ *        .command('help')
+ *        .description('display verbose help')
+ *        .action(function() {
+ *           // output help here
+ *        });
+ *
+ * @param {Function} fn
+ * @return {Command} for chaining
+ * @api public
+ */
+
+Command.prototype.action = function(fn) {
+  var self = this;
+  var listener = function(args, unknown) {
+    // Parse any so-far unknown options
+    args = args || [];
+    unknown = unknown || [];
+
+    var parsed = self.parseOptions(unknown);
+
+    // Output help if necessary
+    outputHelpIfNecessary(self, parsed.unknown);
+
+    // If there are still any unknown options, then we simply
+    // die, unless someone asked for help, in which case we give it
+    // to them, and then we die.
+    if (parsed.unknown.length > 0) {
+      self.unknownOption(parsed.unknown[0]);
+    }
+
+    // Leftover arguments need to be pushed back. Fixes issue #56
+    if (parsed.args.length) args = parsed.args.concat(args);
+
+    self._args.forEach(function(arg, i) {
+      if (arg.required && null == args[i]) {
+        self.missingArgument(arg.name);
+      } else if (arg.variadic) {
+        if (i !== self._args.length - 1) {
+          self.variadicArgNotLast(arg.name);
+        }
+
+        args[i] = args.splice(i);
+      }
+    });
+
+    // Always append ourselves to the end of the arguments,
+    // to make sure we match the number of arguments the user
+    // expects
+    if (self._args.length) {
+      args[self._args.length] = self;
+    } else {
+      args.push(self);
+    }
+
+    fn.apply(self, args);
+  };
+  this.parent.on(this._name, listener);
+  if (this._alias) this.parent.on(this._alias, listener);
+  return this;
+};
+
+/**
+ * Define option with `flags`, `description` and optional
+ * coercion `fn`.
+ *
+ * The `flags` string should contain both the short and long flags,
+ * separated by comma, a pipe or space. The following are all valid
+ * all will output this way when `--help` is used.
+ *
+ *    "-p, --pepper"
+ *    "-p|--pepper"
+ *    "-p --pepper"
+ *
+ * Examples:
+ *
+ *     // simple boolean defaulting to false
+ *     program.option('-p, --pepper', 'add pepper');
+ *
+ *     --pepper
+ *     program.pepper
+ *     // => Boolean
+ *
+ *     // simple boolean defaulting to true
+ *     program.option('-C, --no-cheese', 'remove cheese');
+ *
+ *     program.cheese
+ *     // => true
+ *
+ *     --no-cheese
+ *     program.cheese
+ *     // => false
+ *
+ *     // required argument
+ *     program.option('-C, --chdir <path>', 'change the working directory');
+ *
+ *     --chdir /tmp
+ *     program.chdir
+ *     // => "/tmp"
+ *
+ *     // optional argument
+ *     program.option('-c, --cheese [type]', 'add cheese [marble]');
+ *
+ * @param {String} flags
+ * @param {String} description
+ * @param {Function|Mixed} fn or default
+ * @param {Mixed} defaultValue
+ * @return {Command} for chaining
+ * @api public
+ */
+
+Command.prototype.option = function(flags, description, fn, defaultValue) {
+  var self = this
+    , option = new Option(flags, description)
+    , oname = option.name()
+    , name = camelcase(oname);
+
+  // default as 3rd arg
+  if (typeof fn != 'function') {
+    if (fn instanceof RegExp) {
+      var regex = fn;
+      fn = function(val, def) {
+        var m = regex.exec(val);
+        return m ? m[0] : def;
+      }
+    }
+    else {
+      defaultValue = fn;
+      fn = null;
+    }
+  }
+
+  // preassign default value only for --no-*, [optional], or <required>
+  if (false == option.bool || option.optional || option.required) {
+    // when --no-* we make sure default is true
+    if (false == option.bool) defaultValue = true;
+    // preassign only if we have a default
+    if (undefined !== defaultValue) self[name] = defaultValue;
+  }
+
+  // register the option
+  this.options.push(option);
+
+  // when it's passed assign the value
+  // and conditionally invoke the callback
+  this.on(oname, function(val) {
+    // coercion
+    if (null !== val && fn) val = fn(val, undefined === self[name]
+      ? defaultValue
+      : self[name]);
+
+    // unassigned or bool
+    if ('boolean' == typeof self[name] || 'undefined' == typeof self[name]) {
+      // if no value, bool true, and we have a default, then use it!
+      if (null == val) {
+        self[name] = option.bool
+          ? defaultValue || true
+          : false;
+      } else {
+        self[name] = val;
+      }
+    } else if (null !== val) {
+      // reassign
+      self[name] = val;
+    }
+  });
+
+  return this;
+};
+
+/**
+ * Allow unknown options on the command line.
+ *
+ * @param {Boolean} arg if `true` or omitted, no error will be thrown
+ * for unknown options.
+ * @api public
+ */
+Command.prototype.allowUnknownOption = function(arg) {
+    this._allowUnknownOption = arguments.length === 0 || arg;
+    return this;
+};
+
+/**
+ * Parse `argv`, settings options and invoking commands when defined.
+ *
+ * @param {Array} argv
+ * @return {Command} for chaining
+ * @api public
+ */
+
+Command.prototype.parse = function(argv) {
+  // implicit help
+  if (this.executables) this.addImplicitHelpCommand();
+
+  // store raw args
+  this.rawArgs = argv;
+
+  // guess name
+  this._name = this._name || basename(argv[1], '.js');
+
+  // process argv
+  var parsed = this.parseOptions(this.normalize(argv.slice(2)));
+  var args = this.args = parsed.args;
+
+  var result = this.parseArgs(this.args, parsed.unknown);
+
+  // executable sub-commands
+  var name = result.args[0];
+  if (this._execs[name] && typeof this._execs[name] != "function") {
+    return this.executeSubCommand(argv, args, parsed.unknown);
+  }
+
+  return result;
+};
+
+/**
+ * Execute a sub-command executable.
+ *
+ * @param {Array} argv
+ * @param {Array} args
+ * @param {Array} unknown
+ * @api private
+ */
+
+Command.prototype.executeSubCommand = function(argv, args, unknown) {
+  args = args.concat(unknown);
+
+  if (!args.length) this.help();
+  if ('help' == args[0] && 1 == args.length) this.help();
+
+  // <cmd> --help
+  if ('help' == args[0]) {
+    args[0] = args[1];
+    args[1] = '--help';
+  }
+
+  // executable
+  var f = argv[1];
+  var link = readlink(f);
+  if (link !== f && link.charAt(0) !== '/') {
+    link = path.join(dirname(f), link)
+  }
+  var dir = dirname(link);
+  var bin = basename(f, '.js') + '-' + args[0];
+
+  // prefer local `./<bin>` to bin in the $PATH
+  var local = path.join(dir, bin);
+  try {
+    // for versions before node v0.8 when there weren't `fs.existsSync`
+    if (fs.statSync(local).isFile()) {
+      bin = local;
+    }
+  } catch (e) {}
+
+  // run it
+  args = args.slice(1);
+
+  var proc;
+  if (process.platform !== 'win32') {
+    proc = spawn(bin, args, { stdio: 'inherit', customFds: [0, 1, 2] });
+  } else {
+    args.unshift(local);
+    proc = spawn(process.execPath, args, { stdio: 'inherit'});
+  }
+
+  proc.on('close', process.exit.bind(process));
+  proc.on('error', function(err) {
+    if (err.code == "ENOENT") {
+      console.error('\n  %s(1) does not exist, try --help\n', bin);
+    } else if (err.code == "EACCES") {
+      console.error('\n  %s(1) not executable. try chmod or run with root\n', bin);
+    }
+    process.exit(1);
+  });
+
+  this.runningCommand = proc;
+};
+
+/**
+ * Normalize `args`, splitting joined short flags. For example
+ * the arg "-abc" is equivalent to "-a -b -c".
+ * This also normalizes equal sign and splits "--abc=def" into "--abc def".
+ *
+ * @param {Array} args
+ * @return {Array}
+ * @api private
+ */
+
+Command.prototype.normalize = function(args) {
+  var ret = []
+    , arg
+    , lastOpt
+    , index;
+
+  for (var i = 0, len = args.length; i < len; ++i) {
+    arg = args[i];
+    if (i > 0) {
+      lastOpt = this.optionFor(args[i-1]);
+    }
+
+    if (arg === '--') {
+      // Honor option terminator
+      ret = ret.concat(args.slice(i));
+      break;
+    } else if (lastOpt && lastOpt.required) {
+      ret.push(arg);
+    } else if (arg.length > 1 && '-' == arg[0] && '-' != arg[1]) {
+      arg.slice(1).split('').forEach(function(c) {
+        ret.push('-' + c);
+      });
+    } else if (/^--/.test(arg) && ~(index = arg.indexOf('='))) {
+      ret.push(arg.slice(0, index), arg.slice(index + 1));
+    } else {
+      ret.push(arg);
+    }
+  }
+
+  return ret;
+};
+
+/**
+ * Parse command `args`.
+ *
+ * When listener(s) are available those
+ * callbacks are invoked, otherwise the "*"
+ * event is emitted and those actions are invoked.
+ *
+ * @param {Array} args
+ * @return {Command} for chaining
+ * @api private
+ */
+
+Command.prototype.parseArgs = function(args, unknown) {
+  var name;
+
+  if (args.length) {
+    name = args[0];
+    if (this.listeners(name).length) {
+      this.emit(args.shift(), args, unknown);
+    } else {
+      this.emit('*', args);
+    }
+  } else {
+    outputHelpIfNecessary(this, unknown);
+
+    // If there were no args and we have unknown options,
+    // then they are extraneous and we need to error.
+    if (unknown.length > 0) {
+      this.unknownOption(unknown[0]);
+    }
+  }
+
+  return this;
+};
+
+/**
+ * Return an option matching `arg` if any.
+ *
+ * @param {String} arg
+ * @return {Option}
+ * @api private
+ */
+
+Command.prototype.optionFor = function(arg) {
+  for (var i = 0, len = this.options.length; i < len; ++i) {
+    if (this.options[i].is(arg)) {
+      return this.options[i];
+    }
+  }
+};
+
+/**
+ * Parse options from `argv` returning `argv`
+ * void of these options.
+ *
+ * @param {Array} argv
+ * @return {Array}
+ * @api public
+ */
+
+Command.prototype.parseOptions = function(argv) {
+  var args = []
+    , len = argv.length
+    , literal
+    , option
+    , arg;
+
+  var unknownOptions = [];
+
+  // parse options
+  for (var i = 0; i < len; ++i) {
+    arg = argv[i];
+
+    // literal args after --
+    if ('--' == arg) {
+      literal = true;
+      continue;
+    }
+
+    if (literal) {
+      args.push(arg);
+      continue;
+    }
+
+    // find matching Option
+    option = this.optionFor(arg);
+
+    // option is defined
+    if (option) {
+      // requires arg
+      if (option.required) {
+        arg = argv[++i];
+        if (null == arg) return this.optionMissingArgument(option);
+        this.emit(option.name(), arg);
+      // optional arg
+      } else if (option.optional) {
+        arg = argv[i+1];
+        if (null == arg || ('-' == arg[0] && '-' != arg)) {
+          arg = null;
+        } else {
+          ++i;
+        }
+        this.emit(option.name(), arg);
+      // bool
+      } else {
+        this.emit(option.name());
+      }
+      continue;
+    }
+
+    // looks like an option
+    if (arg.length > 1 && '-' == arg[0]) {
+      unknownOptions.push(arg);
+
+      // If the next argument looks like it might be
+      // an argument for this option, we pass it on.
+      // If it isn't, then it'll simply be ignored
+      if (argv[i+1] && '-' != argv[i+1][0]) {
+        unknownOptions.push(argv[++i]);
+      }
+      continue;
+    }
+
+    // arg
+    args.push(arg);
+  }
+
+  return { args: args, unknown: unknownOptions };
+};
+
+/**
+ * Return an object containing options as key-value pairs
+ *
+ * @return {Object}
+ * @api public
+ */
+Command.prototype.opts = function() {
+  var result = {}
+    , len = this.options.length;
+
+  for (var i = 0 ; i < len; i++) {
+    var key = camelcase(this.options[i].name());
+    result[key] = key === 'version' ? this._version : this[key];
+  }
+  return result;
+};
+
+/**
+ * Argument `name` is missing.
+ *
+ * @param {String} name
+ * @api private
+ */
+
+Command.prototype.missingArgument = function(name) {
+  console.error();
+  console.error("  error: missing required argument `%s'", name);
+  console.error();
+  process.exit(1);
+};
+
+/**
+ * `Option` is missing an argument, but received `flag` or nothing.
+ *
+ * @param {String} option
+ * @param {String} flag
+ * @api private
+ */
+
+Command.prototype.optionMissingArgument = function(option, flag) {
+  console.error();
+  if (flag) {
+    console.error("  error: option `%s' argument missing, got `%s'", option.flags, flag);
+  } else {
+    console.error("  error: option `%s' argument missing", option.flags);
+  }
+  console.error();
+  process.exit(1);
+};
+
+/**
+ * Unknown option `flag`.
+ *
+ * @param {String} flag
+ * @api private
+ */
+
+Command.prototype.unknownOption = function(flag) {
+  if (this._allowUnknownOption) return;
+  console.error();
+  console.error("  error: unknown option `%s'", flag);
+  console.error();
+  process.exit(1);
+};
+
+/**
+ * Variadic argument with `name` is not the last argument as required.
+ *
+ * @param {String} name
+ * @api private
+ */
+
+Command.prototype.variadicArgNotLast = function(name) {
+  console.error();
+  console.error("  error: variadic arguments must be last `%s'", name);
+  console.error();
+  process.exit(1);
+};
+
+/**
+ * Set the program version to `str`.
+ *
+ * This method auto-registers the "-V, --version" flag
+ * which will print the version number when passed.
+ *
+ * @param {String} str
+ * @param {String} flags
+ * @return {Command} for chaining
+ * @api public
+ */
+
+Command.prototype.version = function(str, flags) {
+  if (0 == arguments.length) return this._version;
+  this._version = str;
+  flags = flags || '-V, --version';
+  this.option(flags, 'output the version number');
+  this.on('version', function() {
+    process.stdout.write(str + '\n');
+    process.exit(0);
+  });
+  return this;
+};
+
+/**
+ * Set the description to `str`.
+ *
+ * @param {String} str
+ * @return {String|Command}
+ * @api public
+ */
+
+Command.prototype.description = function(str) {
+  if (0 == arguments.length) return this._description;
+  this._description = str;
+  return this;
+};
+
+/**
+ * Set an alias for the command
+ *
+ * @param {String} alias
+ * @return {String|Command}
+ * @api public
+ */
+
+Command.prototype.alias = function(alias) {
+  if (0 == arguments.length) return this._alias;
+  this._alias = alias;
+  return this;
+};
+
+/**
+ * Set / get the command usage `str`.
+ *
+ * @param {String} str
+ * @return {String|Command}
+ * @api public
+ */
+
+Command.prototype.usage = function(str) {
+  var args = this._args.map(function(arg) {
+    return humanReadableArgName(arg);
+  });
+
+  var usage = '[options]'
+    + (this.commands.length ? ' [command]' : '')
+    + (this._args.length ? ' ' + args.join(' ') : '');
+
+  if (0 == arguments.length) return this._usage || usage;
+  this._usage = str;
+
+  return this;
+};
+
+/**
+ * Get the name of the command
+ *
+ * @param {String} name
+ * @return {String|Command}
+ * @api public
+ */
+
+Command.prototype.name = function() {
+  return this._name;
+};
+
+/**
+ * Return the largest option length.
+ *
+ * @return {Number}
+ * @api private
+ */
+
+Command.prototype.largestOptionLength = function() {
+  return this.options.reduce(function(max, option) {
+    return Math.max(max, option.flags.length);
+  }, 0);
+};
+
+/**
+ * Return help for options.
+ *
+ * @return {String}
+ * @api private
+ */
+
+Command.prototype.optionHelp = function() {
+  var width = this.largestOptionLength();
+
+  // Prepend the help information
+  return [pad('-h, --help', width) + '  ' + 'output usage information']
+    .concat(this.options.map(function(option) {
+      return pad(option.flags, width) + '  ' + option.description;
+      }))
+    .join('\n');
+};
+
+/**
+ * Return command help documentation.
+ *
+ * @return {String}
+ * @api private
+ */
+
+Command.prototype.commandHelp = function() {
+  if (!this.commands.length) return '';
+
+  var commands = this.commands.map(function(cmd) {
+    var args = cmd._args.map(function(arg) {
+      return humanReadableArgName(arg);
+    }).join(' ');
+
+    return [
+      cmd._name
+        + (cmd._alias
+          ? '|' + cmd._alias
+          : '')
+        + (cmd.options.length
+          ? ' [options]'
+          : '')
+        + ' ' + args
+    , cmd.description()
+    ];
+  });
+
+  var width = commands.reduce(function(max, command) {
+    return Math.max(max, command[0].length);
+  }, 0);
+
+  return [
+      ''
+    , '  Commands:'
+    , ''
+    , commands.map(function(cmd) {
+      return pad(cmd[0], width) + '  ' + cmd[1];
+    }).join('\n').replace(/^/gm, '    ')
+    , ''
+  ].join('\n');
+};
+
+/**
+ * Return program help documentation.
+ *
+ * @return {String}
+ * @api private
+ */
+
+Command.prototype.helpInformation = function() {
+  var desc = [];
+  if (this._description) {
+    desc = [
+      '  ' + this._description
+      , ''
+    ];
+  }
+
+  var cmdName = this._name;
+  if (this._alias) {
+    cmdName = cmdName + '|' + this._alias;
+  }
+  var usage = [
+    ''
+    ,'  Usage: ' + cmdName + ' ' + this.usage()
+    , ''
+  ];
+
+  var cmds = [];
+  var commandHelp = this.commandHelp();
+  if (commandHelp) cmds = [commandHelp];
+
+  var options = [
+    '  Options:'
+    , ''
+    , '' + this.optionHelp().replace(/^/gm, '    ')
+    , ''
+    , ''
+  ];
+
+  return usage
+    .concat(cmds)
+    .concat(desc)
+    .concat(options)
+    .join('\n');
+};
+
+/**
+ * Output help information for this command
+ *
+ * @api public
+ */
+
+Command.prototype.outputHelp = function() {
+  process.stdout.write(this.helpInformation());
+  this.emit('--help');
+};
+
+/**
+ * Output help information and exit.
+ *
+ * @api public
+ */
+
+Command.prototype.help = function() {
+  this.outputHelp();
+  process.exit();
+};
+
+/**
+ * Camel-case the given `flag`
+ *
+ * @param {String} flag
+ * @return {String}
+ * @api private
+ */
+
+function camelcase(flag) {
+  return flag.split('-').reduce(function(str, word) {
+    return str + word[0].toUpperCase() + word.slice(1);
+  });
+}
+
+/**
+ * Pad `str` to `width`.
+ *
+ * @param {String} str
+ * @param {Number} width
+ * @return {String}
+ * @api private
+ */
+
+function pad(str, width) {
+  var len = Math.max(0, width - str.length);
+  return str + Array(len + 1).join(' ');
+}
+
+/**
+ * Output help information if necessary
+ *
+ * @param {Command} command to output help for
+ * @param {Array} array of options to search for -h or --help
+ * @api private
+ */
+
+function outputHelpIfNecessary(cmd, options) {
+  options = options || [];
+  for (var i = 0; i < options.length; i++) {
+    if (options[i] == '--help' || options[i] == '-h') {
+      cmd.outputHelp();
+      process.exit(0);
+    }
+  }
+}
+
+/**
+ * Takes an argument an returns its human readable equivalent for help usage.
+ *
+ * @param {Object} arg
+ * @return {String}
+ * @api private
+ */
+
+function humanReadableArgName(arg) {
+  var nameOutput = arg.name + (arg.variadic === true ? '...' : '');
+
+  return arg.required
+    ? '<' + nameOutput + '>'
+    : '[' + nameOutput + ']'
+}
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/.npmignore b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/.npmignore
new file mode 100644
index 0000000..3ac7d16
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/.npmignore
@@ -0,0 +1,3 @@
+.idea/
+.DS_Store
+node_modules/
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/.travis.yml b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/.travis.yml
new file mode 100644
index 0000000..baf9be7
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/.travis.yml
@@ -0,0 +1,5 @@
+language: node_js
+node_js:
+  - "0.10"
+  - "0.12"
+  - "io.js"
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/LICENSE b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/LICENSE
new file mode 100644
index 0000000..d1f842f
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Zhiye Li
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/README.md b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/README.md
new file mode 100644
index 0000000..fc63b50
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/README.md
@@ -0,0 +1,17 @@
+# graceful-readlink
+[![NPM Version](http://img.shields.io/npm/v/graceful-readlink.svg?style=flat)](https://www.npmjs.org/package/graceful-readlink)
+[![NPM Downloads](https://img.shields.io/npm/dm/graceful-readlink.svg?style=flat)](https://www.npmjs.org/package/graceful-readlink)
+
+
+## Usage
+
+```js
+var readlinkSync = require('graceful-readlink').readlinkSync;
+console.log(readlinkSync(f));
+// output
+//  the file pointed to when `f` is a symbolic link
+//  the `f` itself when `f` is not a symbolic link
+```
+## Licence
+
+MIT License
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/index.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/index.js
new file mode 100644
index 0000000..7e9fc70
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/index.js
@@ -0,0 +1,12 @@
+var fs = require('fs')
+  , lstat = fs.lstatSync;
+
+exports.readlinkSync = function (p) {
+  if (lstat(p).isSymbolicLink()) {
+    return fs.readlinkSync(p);
+  } else {
+    return p;
+  }
+};
+
+
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/package.json b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/package.json
new file mode 100644
index 0000000..00bc841
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/commander/node_modules/graceful-readlink/package.json
@@ -0,0 +1,48 @@
+{
+  "name": "graceful-readlink",
+  "version": "1.0.1",
+  "description": "graceful fs.readlink",
+  "main": "index.js",
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/zhiyelee/graceful-readlink.git"
+  },
+  "homepage": "https://github.com/zhiyelee/graceful-readlink",
+  "bugs": {
+    "url": "https://github.com/zhiyelee/graceful-readlink/issues"
+  },
+  "keywords": [
+    "fs.readlink",
+    "readlink"
+  ],
+  "author": {
+    "name": "zhiyelee"
+  },
+  "license": "MIT",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "gitHead": "f6655275bebef706fb63fd01b5f062a7052419a5",
+  "_id": "graceful-readlink@1.0.1",
+  "_shasum": "4cafad76bc62f02fa039b2f94e9a3dd3a391a725",
+  "_from": "graceful-readlink@>= 1.0.0",
+  "_npmVersion": "2.1.17",
+  "_nodeVersion": "0.11.14",
+  "_npmUser": {
+    "name": "zhiyelee",
+    "email": "zhiyelee@gmail.com"
+  },
+  "maintainers": [
+    {
+      "name": "zhiyelee",
+      "email": "zhiyelee@gmail.com"
+    }
+  ],
+  "dist": {
+    "shasum": "4cafad76bc62f02fa039b2f94e9a3dd3a391a725",
+    "tarball": "http://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz"
+  },
+  "directories": {},
+  "_resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/commander/package.json b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/commander/package.json
new file mode 100644
index 0000000..d39156f
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/commander/package.json
@@ -0,0 +1,74 @@
+{
+  "name": "commander",
+  "version": "2.7.1",
+  "description": "the complete solution for node.js command-line programs",
+  "keywords": [
+    "command",
+    "option",
+    "parser"
+  ],
+  "author": {
+    "name": "TJ Holowaychuk",
+    "email": "tj@vision-media.ca"
+  },
+  "license": "MIT",
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/tj/commander.js.git"
+  },
+  "devDependencies": {
+    "should": ">= 0.0.1"
+  },
+  "scripts": {
+    "test": "make test"
+  },
+  "main": "index",
+  "engines": {
+    "node": ">= 0.6.x"
+  },
+  "files": [
+    "index.js"
+  ],
+  "dependencies": {
+    "graceful-readlink": ">= 1.0.0"
+  },
+  "gitHead": "103654f8f32c010ad1e62cefc9ab92d7c8d18c8e",
+  "bugs": {
+    "url": "https://github.com/tj/commander.js/issues"
+  },
+  "homepage": "https://github.com/tj/commander.js",
+  "_id": "commander@2.7.1",
+  "_shasum": "5d419a2bbed2c32ee3e4dca9bb45ab83ecc3065a",
+  "_from": "commander@^2.3.0",
+  "_npmVersion": "2.1.17",
+  "_nodeVersion": "0.11.14",
+  "_npmUser": {
+    "name": "zhiyelee",
+    "email": "zhiyelee@gmail.com"
+  },
+  "maintainers": [
+    {
+      "name": "tjholowaychuk",
+      "email": "tj@vision-media.ca"
+    },
+    {
+      "name": "somekittens",
+      "email": "rkoutnik@gmail.com"
+    },
+    {
+      "name": "zhiyelee",
+      "email": "zhiyelee@gmail.com"
+    },
+    {
+      "name": "thethomaseffect",
+      "email": "thethomaseffect@gmail.com"
+    }
+  ],
+  "dist": {
+    "shasum": "5d419a2bbed2c32ee3e4dca9bb45ab83ecc3065a",
+    "tarball": "http://registry.npmjs.org/commander/-/commander-2.7.1.tgz"
+  },
+  "directories": {},
+  "_resolved": "https://registry.npmjs.org/commander/-/commander-2.7.1.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/debug/Readme.md b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/debug/Readme.md
new file mode 100644
index 0000000..c5a34e8
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/debug/Readme.md
@@ -0,0 +1,115 @@
+# debug
+
+  tiny node.js debugging utility modelled after node core's debugging technique.
+
+## Installation
+
+```
+$ npm install debug
+```
+
+## Usage
+
+ With `debug` you simply invoke the exported function to generate your debug function, passing it a name which will determine if a noop function is returned, or a decorated `console.error`, so all of the `console` format string goodies you're used to work fine. A unique color is selected per-function for visibility.
+ 
+Example _app.js_:
+
+```js
+var debug = require('debug')('http')
+  , http = require('http')
+  , name = 'My App';
+
+// fake app
+
+debug('booting %s', name);
+
+http.createServer(function(req, res){
+  debug(req.method + ' ' + req.url);
+  res.end('hello\n');
+}).listen(3000, function(){
+  debug('listening');
+});
+
+// fake worker of some kind
+
+require('./worker');
+```
+
+Example _worker.js_:
+
+```js
+var debug = require('debug')('worker');
+
+setInterval(function(){
+  debug('doing some work');
+}, 1000);
+```
+
+ The __DEBUG__ environment variable is then used to enable these based on space or comma-delimited names. Here are some examples:
+
+  ![debug http and worker](http://f.cl.ly/items/18471z1H402O24072r1J/Screenshot.png)
+
+  ![debug worker](http://f.cl.ly/items/1X413v1a3M0d3C2c1E0i/Screenshot.png)
+
+## Millisecond diff
+
+  When actively developing an application it can be useful to see when the time spent between one `debug()` call and the next. Suppose for example you invoke `debug()` before requesting a resource, and after as well, the "+NNNms" will show you how much time was spent between calls.
+
+  ![](http://f.cl.ly/items/2i3h1d3t121M2Z1A3Q0N/Screenshot.png)
+
+  When stderr is not a TTY, `Date#toUTCString()` is used, making it more useful for logging the debug information as shown below:
+  _(NOTE: Debug now uses stderr instead of stdout, so the correct shell command for this example is actually `DEBUG=* node example/worker 2> out &`)_
+  
+  ![](http://f.cl.ly/items/112H3i0e0o0P0a2Q2r11/Screenshot.png)
+  
+## Conventions
+
+ If you're using this in one or more of your libraries, you _should_ use the name of your library so that developers may toggle debugging as desired without guessing names. If you have more than one debuggers you _should_ prefix them with your library name and use ":" to separate features. For example "bodyParser" from Connect would then be "connect:bodyParser". 
+
+## Wildcards
+
+  The "*" character may be used as a wildcard. Suppose for example your library has debuggers named "connect:bodyParser", "connect:compress", "connect:session", instead of listing all three with `DEBUG=connect:bodyParser,connect.compress,connect:session`, you may simply do `DEBUG=connect:*`, or to run everything using this module simply use `DEBUG=*`.
+
+  You can also exclude specific debuggers by prefixing them with a "-" character.  For example, `DEBUG=* -connect:*` would include all debuggers except those starting with "connect:".
+
+## Browser support
+
+ Debug works in the browser as well, currently persisted by `localStorage`. For example if you have `worker:a` and `worker:b` as shown below, and wish to debug both type `debug.enable('worker:*')` in the console and refresh the page, this will remain until you disable with `debug.disable()`. 
+
+```js
+a = debug('worker:a');
+b = debug('worker:b');
+
+setInterval(function(){
+  a('doing some work');
+}, 1000);
+
+setInterval(function(){
+  a('doing some work');
+}, 1200);
+```
+
+## License 
+
+(The MIT License)
+
+Copyright (c) 2011 TJ Holowaychuk &lt;tj@vision-media.ca&gt;
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/debug/debug.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/debug/debug.js
new file mode 100644
index 0000000..509dc0d
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/debug/debug.js
@@ -0,0 +1,137 @@
+
+/**
+ * Expose `debug()` as the module.
+ */
+
+module.exports = debug;
+
+/**
+ * Create a debugger with the given `name`.
+ *
+ * @param {String} name
+ * @return {Type}
+ * @api public
+ */
+
+function debug(name) {
+  if (!debug.enabled(name)) return function(){};
+
+  return function(fmt){
+    fmt = coerce(fmt);
+
+    var curr = new Date;
+    var ms = curr - (debug[name] || curr);
+    debug[name] = curr;
+
+    fmt = name
+      + ' '
+      + fmt
+      + ' +' + debug.humanize(ms);
+
+    // This hackery is required for IE8
+    // where `console.log` doesn't have 'apply'
+    window.console
+      && console.log
+      && Function.prototype.apply.call(console.log, console, arguments);
+  }
+}
+
+/**
+ * The currently active debug mode names.
+ */
+
+debug.names = [];
+debug.skips = [];
+
+/**
+ * Enables a debug mode by name. This can include modes
+ * separated by a colon and wildcards.
+ *
+ * @param {String} name
+ * @api public
+ */
+
+debug.enable = function(name) {
+  try {
+    localStorage.debug = name;
+  } catch(e){}
+
+  var split = (name || '').split(/[\s,]+/)
+    , len = split.length;
+
+  for (var i = 0; i < len; i++) {
+    name = split[i].replace('*', '.*?');
+    if (name[0] === '-') {
+      debug.skips.push(new RegExp('^' + name.substr(1) + '$'));
+    }
+    else {
+      debug.names.push(new RegExp('^' + name + '$'));
+    }
+  }
+};
+
+/**
+ * Disable debug output.
+ *
+ * @api public
+ */
+
+debug.disable = function(){
+  debug.enable('');
+};
+
+/**
+ * Humanize the given `ms`.
+ *
+ * @param {Number} m
+ * @return {String}
+ * @api private
+ */
+
+debug.humanize = function(ms) {
+  var sec = 1000
+    , min = 60 * 1000
+    , hour = 60 * min;
+
+  if (ms >= hour) return (ms / hour).toFixed(1) + 'h';
+  if (ms >= min) return (ms / min).toFixed(1) + 'm';
+  if (ms >= sec) return (ms / sec | 0) + 's';
+  return ms + 'ms';
+};
+
+/**
+ * Returns true if the given mode name is enabled, false otherwise.
+ *
+ * @param {String} name
+ * @return {Boolean}
+ * @api public
+ */
+
+debug.enabled = function(name) {
+  for (var i = 0, len = debug.skips.length; i < len; i++) {
+    if (debug.skips[i].test(name)) {
+      return false;
+    }
+  }
+  for (var i = 0, len = debug.names.length; i < len; i++) {
+    if (debug.names[i].test(name)) {
+      return true;
+    }
+  }
+  return false;
+};
+
+/**
+ * Coerce `val`.
+ */
+
+function coerce(val) {
+  if (val instanceof Error) return val.stack || val.message;
+  return val;
+}
+
+// persist
+
+try {
+  if (window.localStorage) debug.enable(localStorage.debug);
+} catch(e){}
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/debug/index.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/debug/index.js
new file mode 100644
index 0000000..e02c13b
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/debug/index.js
@@ -0,0 +1,5 @@
+if ('undefined' == typeof window) {
+  module.exports = require('./lib/debug');
+} else {
+  module.exports = require('./debug');
+}
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/debug/lib/debug.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/debug/lib/debug.js
new file mode 100644
index 0000000..3b0a918
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/debug/lib/debug.js
@@ -0,0 +1,147 @@
+/**
+ * Module dependencies.
+ */
+
+var tty = require('tty');
+
+/**
+ * Expose `debug()` as the module.
+ */
+
+module.exports = debug;
+
+/**
+ * Enabled debuggers.
+ */
+
+var names = []
+  , skips = [];
+
+(process.env.DEBUG || '')
+  .split(/[\s,]+/)
+  .forEach(function(name){
+    name = name.replace('*', '.*?');
+    if (name[0] === '-') {
+      skips.push(new RegExp('^' + name.substr(1) + '$'));
+    } else {
+      names.push(new RegExp('^' + name + '$'));
+    }
+  });
+
+/**
+ * Colors.
+ */
+
+var colors = [6, 2, 3, 4, 5, 1];
+
+/**
+ * Previous debug() call.
+ */
+
+var prev = {};
+
+/**
+ * Previously assigned color.
+ */
+
+var prevColor = 0;
+
+/**
+ * Is stdout a TTY? Colored output is disabled when `true`.
+ */
+
+var isatty = tty.isatty(2);
+
+/**
+ * Select a color.
+ *
+ * @return {Number}
+ * @api private
+ */
+
+function color() {
+  return colors[prevColor++ % colors.length];
+}
+
+/**
+ * Humanize the given `ms`.
+ *
+ * @param {Number} m
+ * @return {String}
+ * @api private
+ */
+
+function humanize(ms) {
+  var sec = 1000
+    , min = 60 * 1000
+    , hour = 60 * min;
+
+  if (ms >= hour) return (ms / hour).toFixed(1) + 'h';
+  if (ms >= min) return (ms / min).toFixed(1) + 'm';
+  if (ms >= sec) return (ms / sec | 0) + 's';
+  return ms + 'ms';
+}
+
+/**
+ * Create a debugger with the given `name`.
+ *
+ * @param {String} name
+ * @return {Type}
+ * @api public
+ */
+
+function debug(name) {
+  function disabled(){}
+  disabled.enabled = false;
+
+  var match = skips.some(function(re){
+    return re.test(name);
+  });
+
+  if (match) return disabled;
+
+  match = names.some(function(re){
+    return re.test(name);
+  });
+
+  if (!match) return disabled;
+  var c = color();
+
+  function colored(fmt) {
+    fmt = coerce(fmt);
+
+    var curr = new Date;
+    var ms = curr - (prev[name] || curr);
+    prev[name] = curr;
+
+    fmt = '  \u001b[9' + c + 'm' + name + ' '
+      + '\u001b[3' + c + 'm\u001b[90m'
+      + fmt + '\u001b[3' + c + 'm'
+      + ' +' + humanize(ms) + '\u001b[0m';
+
+    console.error.apply(this, arguments);
+  }
+
+  function plain(fmt) {
+    fmt = coerce(fmt);
+
+    fmt = new Date().toUTCString()
+      + ' ' + name + ' ' + fmt;
+    console.error.apply(this, arguments);
+  }
+
+  colored.enabled = plain.enabled = true;
+
+  return isatty || process.env.DEBUG_COLORS
+    ? colored
+    : plain;
+}
+
+/**
+ * Coerce `val`.
+ */
+
+function coerce(val) {
+  if (val instanceof Error) return val.stack || val.message;
+  return val;
+}
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/debug/package.json b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/debug/package.json
new file mode 100644
index 0000000..a88241d
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/debug/package.json
@@ -0,0 +1,65 @@
+{
+  "name": "debug",
+  "version": "0.7.4",
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/visionmedia/debug.git"
+  },
+  "description": "small debugging utility",
+  "keywords": [
+    "debug",
+    "log",
+    "debugger"
+  ],
+  "author": {
+    "name": "TJ Holowaychuk",
+    "email": "tj@vision-media.ca"
+  },
+  "dependencies": {},
+  "devDependencies": {
+    "mocha": "*"
+  },
+  "main": "lib/debug.js",
+  "browser": "./debug.js",
+  "engines": {
+    "node": "*"
+  },
+  "files": [
+    "lib/debug.js",
+    "debug.js",
+    "index.js"
+  ],
+  "component": {
+    "scripts": {
+      "debug/index.js": "index.js",
+      "debug/debug.js": "debug.js"
+    }
+  },
+  "bugs": {
+    "url": "https://github.com/visionmedia/debug/issues"
+  },
+  "homepage": "https://github.com/visionmedia/debug",
+  "_id": "debug@0.7.4",
+  "dist": {
+    "shasum": "06e1ea8082c2cb14e39806e22e2f6f757f92af39",
+    "tarball": "http://registry.npmjs.org/debug/-/debug-0.7.4.tgz"
+  },
+  "_from": "debug@~0.7.4",
+  "_npmVersion": "1.3.13",
+  "_npmUser": {
+    "name": "tjholowaychuk",
+    "email": "tj@vision-media.ca"
+  },
+  "maintainers": [
+    {
+      "name": "tjholowaychuk",
+      "email": "tj@vision-media.ca"
+    }
+  ],
+  "directories": {},
+  "_shasum": "06e1ea8082c2cb14e39806e22e2f6f757f92af39",
+  "_resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz",
+  "readme": "# debug\n\n  tiny node.js debugging utility modelled after node core's debugging technique.\n\n## Installation\n\n```\n$ npm install debug\n```\n\n## Usage\n\n With `debug` you simply invoke the exported function to generate your debug function, passing it a name which will determine if a noop function is returned, or a decorated `console.error`, so all of the `console` format string goodies you're used to work fine. A unique color is selected per-function for visibility.\n \nExample _app.js_:\n\n```js\nvar debug = require('debug')('http')\n  , http = require('http')\n  , name = 'My App';\n\n// fake app\n\ndebug('booting %s', name);\n\nhttp.createServer(function(req, res){\n  debug(req.method + ' ' + req.url);\n  res.end('hello\\n');\n}).listen(3000, function(){\n  debug('listening');\n});\n\n// fake worker of some kind\n\nrequire('./worker');\n```\n\nExample _worker.js_:\n\n```js\nvar debug = require('debug')('worker');\n\nsetInterval(function(){\n  debug('doing some work');\n}, 1000);\n```\n\n The __DEBUG__ environment variable is then used to enable these based on space or comma-delimited names. Here are some examples:\n\n  ![debug http and worker](http://f.cl.ly/items/18471z1H402O24072r1J/Screenshot.png)\n\n  ![debug worker](http://f.cl.ly/items/1X413v1a3M0d3C2c1E0i/Screenshot.png)\n\n## Millisecond diff\n\n  When actively developing an application it can be useful to see when the time spent between one `debug()` call and the next. Suppose for example you invoke `debug()` before requesting a resource, and after as well, the \"+NNNms\" will show you how much time was spent between calls.\n\n  ![](http://f.cl.ly/items/2i3h1d3t121M2Z1A3Q0N/Screenshot.png)\n\n  When stderr is not a TTY, `Date#toUTCString()` is used, making it more useful for logging the debug information as shown below:\n  _(NOTE: Debug now uses stderr instead of stdout, so the correct shell command for this example is actually `DEBUG=* node example/worker 2> out &`)_\n  \n  ![](http://f.cl.ly/items/112H3i0e0o0P0a2Q2r11/Screenshot.png)\n  \n## Conventions\n\n If you're using this in one or more of your libraries, you _should_ use the name of your library so that developers may toggle debugging as desired without guessing names. If you have more than one debuggers you _should_ prefix them with your library name and use \":\" to separate features. For example \"bodyParser\" from Connect would then be \"connect:bodyParser\". \n\n## Wildcards\n\n  The \"*\" character may be used as a wildcard. Suppose for example your library has debuggers named \"connect:bodyParser\", \"connect:compress\", \"connect:session\", instead of listing all three with `DEBUG=connect:bodyParser,connect.compress,connect:session`, you may simply do `DEBUG=connect:*`, or to run everything using this module simply use `DEBUG=*`.\n\n  You can also exclude specific debuggers by prefixing them with a \"-\" character.  For example, `DEBUG=* -connect:*` would include all debuggers except those starting with \"connect:\".\n\n## Browser support\n\n Debug works in the browser as well, currently persisted by `localStorage`. For example if you have `worker:a` and `worker:b` as shown below, and wish to debug both type `debug.enable('worker:*')` in the console and refresh the page, this will remain until you disable with `debug.disable()`. \n\n```js\na = debug('worker:a');\nb = debug('worker:b');\n\nsetInterval(function(){\n  a('doing some work');\n}, 1000);\n\nsetInterval(function(){\n  a('doing some work');\n}, 1200);\n```\n\n## License \n\n(The MIT License)\n\nCopyright (c) 2011 TJ Holowaychuk &lt;tj@vision-media.ca&gt;\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n'Software'), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n",
+  "readmeFilename": "Readme.md",
+  "scripts": {}
+}
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/.jscsrc b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/.jscsrc
new file mode 100644
index 0000000..06245e6
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/.jscsrc
@@ -0,0 +1,64 @@
+{
+  "excludeFiles": [
+    "./js/forge.bundle.js",
+    "./js/forge.min.js",
+    "./js/jsbn.js",
+    "./nodejs/ui/require.js",
+    "./nodejs/ui/test.min.js"
+  ],
+  "disallowKeywords": ["with"],
+  "disallowKeywordsOnNewLine": ["else", "catch"],
+  // FIXME: enable this?
+  //"disallowImplicitTypeConversion": ["string"],
+  "disallowMixedSpacesAndTabs": true,
+  "disallowMultipleLineBreaks": true,
+  // FIXME: enable this or do we prefer to
+  // use w/angular directive templates?
+  //"disallowMultipleLineStrings": true,
+  "disallowNewlineBeforeBlockStatements": true,
+  "disallowSpaceAfterObjectKeys": true,
+  "disallowSpaceAfterPrefixUnaryOperators": ["++", "--", "+", "-", "~", "!"],
+  "disallowSpaceBeforeBinaryOperators": [","],
+  "disallowSpaceBeforePostfixUnaryOperators": ["++", "--"],
+  "disallowSpacesInAnonymousFunctionExpression": {
+    "beforeOpeningRoundBrace": true
+  },
+  "disallowSpacesInFunctionDeclaration": {
+    "beforeOpeningRoundBrace": true
+  },
+  "disallowSpacesInNamedFunctionExpression": {
+    "beforeOpeningRoundBrace": true
+  },
+  "disallowSpacesInsideParentheses": true,
+  "disallowTrailingComma": true,
+  "disallowTrailingWhitespace": true,
+  "requireCommaBeforeLineBreak": true,
+  "requireCurlyBraces": ["if", "else", "for", "while", "do", "try", "catch"],
+  "requireLineFeedAtFileEnd": true,
+  "requireSpaceAfterBinaryOperators": ["?", ":", "+", "-", "/", "*", "%", "==", "===", "!=", "!==", ">", ">=", "<", "<=", "&&", "||"],
+  "requireSpaceBeforeBinaryOperators": ["?", ":", "+", "-", "/", "*", "%", "==", "===", "!=", "!==", ">", ">=", "<", "<=", "&&", "||"],
+  "requireSpaceAfterKeywords": [
+    "else",
+    "do",
+    "return",
+    "try"
+  ],
+  "requireSpaceBeforeBlockStatements": true,
+  "requireSpacesInConditionalExpression": {
+    "afterTest": true,
+    "beforeConsequent": true,
+    "afterConsequent": true,
+    "beforeAlternate": true
+  },
+  "requireSpacesInFunction": {
+    "beforeOpeningCurlyBrace": true
+  },
+  "safeContextKeyword": "self",
+  "validateLineBreaks": "LF",
+  // FIXME: enable doc checks (update to use newer jscs jsdoc module)
+  //"validateJSDoc": {
+  //  "checkParamNames": true,
+  //  "requireParamTypes": true
+  //},
+  "validateParameterSeparator": ", "
+}
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/.jshintignore b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/.jshintignore
new file mode 100644
index 0000000..c50f474
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/.jshintignore
@@ -0,0 +1,6 @@
+js/forge.bundle.js
+minify.js
+nodejs/build.js
+nodejs/minify.js
+nodejs/ui/require.js
+nodejs/ui/test.min.js
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/.jshintrc b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/.jshintrc
new file mode 100644
index 0000000..26d261c
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/.jshintrc
@@ -0,0 +1,3 @@
+{
+  "sub": true
+}
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/.npmignore b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/.npmignore
new file mode 100644
index 0000000..50980d3
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/.npmignore
@@ -0,0 +1,22 @@
+*.py[co]
+*.sw[nop]
+*~
+.bower.json
+.cdtproject
+.classpath
+.cproject
+.project
+.settings
+Makefile
+TAGS
+aclocal.m4
+autom4te.cache
+build
+config.log
+config.status
+configure
+dist
+js/forge.bundle.js
+js/forge.min.js
+node_modules
+tests/forge
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/.travis.yml b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/.travis.yml
new file mode 100644
index 0000000..c4aa766
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/.travis.yml
@@ -0,0 +1,10 @@
+language: node_js
+node_js:
+  - "0.10"
+install: (cd nodejs && npm install)
+script: 
+  - (cd nodejs && npm test)
+notifications:
+  email:
+    on_success: change
+    on_failure: change
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/HACKING.md b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/HACKING.md
new file mode 100644
index 0000000..762dfe1
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/HACKING.md
@@ -0,0 +1,37 @@
+Hacking on forge
+================
+
+Want to hack on forge? Great! Here are a few notes:
+
+Code
+----
+
+* In general, follow a common [Node.js Style Guide][].
+* Use version X.Y.Z-dev in dev mode.
+* Use version X.Y.Z for releases.
+
+Versioning
+----------
+
+* Follow the [Semantic Versioning][] guidelines.
+
+Release Process
+---------------
+
+* commit changes
+* `$EDITOR package.json`: update to release version and remove `-dev` suffix.
+* `git commit package.json -m "Release {version}."`
+* `git tag {version}`
+* `$EDITOR package.json`: update to next version and add `-dev` suffix.
+* `git commit package.json -m "Start {next-version}."`
+* `git push`
+* `git push --tags`
+
+To ensure a clean upload, use a clean updated checkout, and run the following:
+
+* `git checkout {version}`
+* `npm publish`
+
+[Node.js Style Guide]: http://nodeguide.com/style.html
+[jshint]: http://www.jshint.com/install/
+[Semantic Versioning]: http://semver.org/
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/LICENSE b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/LICENSE
new file mode 100644
index 0000000..1bba5ce
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/LICENSE
@@ -0,0 +1,331 @@
+You may use the Forge project under the terms of either the BSD License or the 
+GNU General Public License (GPL) Version 2.
+
+The BSD License is recommended for most projects. It is simple and easy to 
+understand and it places almost no restrictions on what you can do with the
+Forge project.
+
+If the GPL suits your project better you are also free to use Forge under 
+that license.
+
+You don't have to do anything special to choose one license or the other and
+you don't have to notify anyone which license you are using. You are free to
+use this project in commercial projects as long as the copyright header is
+left intact.
+
+If you are a commercial entity and use this set of libraries in your
+commercial software then reasonable payment to Digital Bazaar, if you can
+afford it, is not required but is expected and would be appreciated. If this
+library saves you time, then it's saving you money. The cost of developing
+the Forge software was on the order of several hundred hours and tens of
+thousands of dollars. We are attempting to strike a balance between helping
+the development community while not being taken advantage of by lucrative
+commercial entities for our efforts.
+
+-------------------------------------------------------------------------------
+New BSD License (3-clause)
+Copyright (c) 2010, Digital Bazaar, Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of Digital Bazaar, Inc. nor the
+      names of its contributors may be used to endorse or promote products
+      derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL DIGITAL BAZAAR BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+-------------------------------------------------------------------------------
+        GNU GENERAL PUBLIC LICENSE
+           Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+          Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+        GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+          NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/Makefile.in b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/Makefile.in
new file mode 100644
index 0000000..eb4495e
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/Makefile.in
@@ -0,0 +1,110 @@
+# Makefile for Forge
+
+# Top-level build and dist dir
+BUILD_DIR=@FORGE_DIR@/build
+TOP_DIST_DIR=@FORGE_DIR@/dist
+DIST_DIR=$(TOP_DIST_DIR)/forge
+
+_FLASH := $(DIST_DIR)/SocketPool.swf
+ifeq (@BUILD_FLASH@,yes)
+FLASH := $(_FLASH)
+else
+ifeq (@USE_PRE_BUILT_FLASH@,yes)
+FLASH := $(_FLASH)
+endif
+endif
+JS_SOURCES := $(wildcard js/*.js)
+JS_DIST := $(JS_SOURCES:js/%.js=$(DIST_DIR)/%.js)
+JS_DIST_MIN := $(JS_DIST:%.js=%.min.js)
+TESTS_FORGE_LINK := @FORGE_DIR@/tests/forge
+
+ifeq (@BUILD_PYTHON_MODULES@,yes)
+SSL_SESSIONS_DIR = \
+	$(TOP_DIST_DIR)/forge_ssl/lib/python@PYTHON_VERSION@/site-packages
+SSL_SESSIONS_FILES = \
+	$(SSL_SESSIONS_DIR)/_forge_ssl.so \
+	$(SSL_SESSIONS_DIR)/forge/ssl.py
+endif
+
+# Whether or not to print commands as they are being executed, helpful for
+# debugging the build system.
+ifdef PRINT_COMMANDS
+PCMD=
+else
+PCMD=@
+endif
+
+.PHONY: all build-all update-all verbose clean verbose-commands
+
+# debug flags for flash build
+ifeq (@MXMLC_DEBUG_MODE@,yes)
+FLASH_FLAGS = \
+	-debug=true \
+	-define=CONFIG::debugging,true \
+	-define=CONFIG::release,false
+else
+FLASH_FLAGS = \
+	-debug=false \
+	-define=CONFIG::debugging,false \
+	-define=CONFIG::release,true
+endif
+
+all: $(BUILD_DIR) $(DIST_DIR) $(FLASH) $(JS_DIST) $(TESTS_FORGE_LINK) $(SSL_SESSIONS_FILES)
+	@echo "forge build complete."
+
+build-all: all
+
+update-all:
+	@git pull && ./build-setup && make all
+
+$(BUILD_DIR):
+	$(PCMD) mkdir -p $@
+$(DIST_DIR):
+	$(PCMD) mkdir -p $@
+
+ifeq (@BUILD_FLASH@,yes)
+$(DIST_DIR)/SocketPool.swf: flash/SocketPool.as flash/PooledSocket.as flash/SocketEvent.as
+	@echo "Building $@..."
+	$(PCMD) @MXMLC@ $(FLASH_FLAGS) \
+		-load-config+=build-flash.xml \
+		-output=$@ $<
+else
+ifeq (@USE_PRE_BUILT_FLASH@,yes)
+$(DIST_DIR)/SocketPool.swf: @FORGE_DIR@/swf/SocketPool.swf
+	@echo "Copying pre-built $(@F)..."
+	$(PCMD) cp $< $@
+endif
+endif
+
+$(DIST_DIR)/%.js: js/%.js
+	@echo "Linking $@..."
+	$(PCMD) ln -sf $(realpath $<) $@
+
+$(TESTS_FORGE_LINK): $(DIST_DIR)
+	@echo "Linking $@..."
+	$(PCMD) ln -sf $(realpath $<) $@
+
+ifeq (@BUILD_PYTHON_MODULES@,yes)
+$(SSL_SESSIONS_DIR)/_forge_ssl.so: \
+	@FORGE_DIR@/tests/forge_ssl/forge/_ssl.c \
+	@FORGE_DIR@/tests/forge_ssl/forge/socketmodule.h \
+	@FORGE_DIR@/tests/forge_ssl/setup.py
+$(SSL_SESSIONS_DIR)/forge/ssl.py: \
+	@FORGE_DIR@/tests/forge_ssl/forge/ssl.py \
+	@FORGE_DIR@/tests/forge_ssl/setup.py
+	(cd @FORGE_DIR@/tests/forge_ssl && \
+	@PYTHON@ setup.py \
+	   build --build-base $(BUILD_DIR) \
+	   install --prefix=$(TOP_DIST_DIR)/forge_ssl)
+	@# fix distutils timestamp issue
+	@# (sub-seconds of source file are truncated on target so rebuild is
+	@# always triggered)
+	@touch $@
+endif
+
+clean:
+	$(PCMD) rm -rf $(BUILD_DIR) $(TOP_DIST_DIR)
+	@echo "Removed all generated files."
+
+verbose-commands:
+	PRINT_COMMANDS=true $(MAKE) all
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/README.md b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/README.md
new file mode 100644
index 0000000..00eb178
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/README.md
@@ -0,0 +1,1782 @@
+# Forge
+
+[![Build Status][travis-ci-png]][travis-ci-site]
+[travis-ci-png]: https://travis-ci.org/digitalbazaar/forge.png?branch=master
+[travis-ci-site]: https://travis-ci.org/digitalbazaar/forge
+
+A native implementation of [TLS][] (and various other cryptographic tools) in
+[JavaScript][].
+
+## Introduction
+
+The Forge software is a fully native implementation of the [TLS][] protocol in
+JavaScript as well as a set of tools for developing Web Apps that utilize many
+network resources.
+
+## Performance
+
+Forge is fast. Benchmarks against other popular JavaScript cryptography
+libraries can be found here:
+
+http://dominictarr.github.io/crypto-bench/
+
+http://cryptojs.altervista.org/test/simulate-threading-speed_test.html
+
+## Getting Started
+------------------
+
+### Node.js ###
+
+If you want to use forge with [node.js][], it is available through `npm`:
+
+https://npmjs.org/package/node-forge
+
+Installation:
+
+    npm install node-forge
+
+You can then use forge as a regular module:
+
+    var forge = require('node-forge');
+
+### Requirements ###
+
+* General
+  * Optional: GNU autotools for the build infrastructure if using Flash.
+* Building a Browser Bundle:
+  * nodejs
+  * npm
+* Testing
+  * nodejs
+  * Optional: Python and OpenSSL development environment to build
+  * a special SSL module with session cache support for testing with flash.
+  * http://www.python.org/dev/
+  * http://www.openssl.org/
+  * Debian users should install python-dev and libssl-dev.
+* Optional: Flash
+  * A pre-built SocketPool.swf is included.
+  * Adobe Flex 3 SDK to build the Flash socket code.
+  * http://opensource.adobe.com/wiki/display/flexsdk/
+
+### Building a browser bundle ###
+
+To create a minimized JavaScript bundle, run the following:
+
+```
+npm install
+npm run minify
+```
+
+This will create a single minimized file that can be included in
+the browser:
+
+```
+js/forge.min.js
+```
+
+Include the file via:
+
+```html
+<script src="js/forge.min.js"></script>
+```
+
+Note that the minify script depends on the requirejs package,
+and that the requirejs binary 'r.js' assumes that the name of
+the node binary is 'node' not 'nodejs', as it is on some
+systems. You may need to change the hashbang line to use
+'nodejs' or run the command manually.
+
+To create a single non-minimized file that can be included in
+the browser:
+
+```
+npm install
+npm run bundle
+```
+
+This will create:
+
+```
+js/forge.bundle.js
+```
+
+Include the file via:
+
+```html
+<script src="js/forge.bundle.js"></script>
+```
+
+The above bundles will synchronously create a global 'forge' object.
+
+Keep in mind that these bundles will not include any WebWorker
+scripts (eg: prime.worker.js) or their dependencies, so these will
+need to be accessible from the browser if any WebWorkers are used.
+
+### Testing with NodeJS & RequireJS ###
+
+A test server for [node.js][] can be found at `./nodejs`. The following are included:
+
+  * Example of how to use `forge` within NodeJS in the form of a [mocha](http://visionmedia.github.io/mocha/) test.
+  * Example of how to serve `forge` to the browser using [RequireJS](http://requirejs.org/).
+
+To run:
+
+    cd nodejs
+    npm install
+    npm test
+    npm start
+
+
+### Old build system that includes flash support ###
+
+To build the whole project, including Flash, run the following:
+
+    $ ./build-setup
+    $ make
+
+This will create the SWF, symlink all the JavaScript files, and build a Python
+SSL module for testing. To see configure options, run `./configure --help`.
+
+### Old test system including flash support ###
+
+A test server is provided which can be run in TLS mode and non-TLS mode. Use
+the --help option to get help for configuring ports. The server will print out
+the local URL you can vist to run tests.
+
+Some of the simplier tests should be run with just the non-TLS server::
+
+    $ ./tests/server.py
+
+More advanced tests need TLS enabled::
+
+    $ ./tests/server.py --tls
+
+## Contributing
+---------------
+
+Any contributions (eg: PRs) that are accepted will be brought under the same
+license used by the rest of the Forge project. This license allows Forge to
+be used under the terms of either the BSD License or the GNU General Public
+License (GPL) Version 2.
+
+See: [LICENSE](https://github.com/digitalbazaar/forge/blob/cbebca3780658703d925b61b2caffb1d263a6c1d/LICENSE)
+
+If a contribution contains 3rd party source code with its own license, it
+may retain it, so long as that license is compatible with the Forge license.
+
+## Documentation
+----------------
+
+### Transports
+
+* [TLS](#tls)
+* [HTTP](#http)
+* [SSH](#ssh)
+* [XHR](#xhr)
+* [Sockets](#socket)
+
+### Ciphers
+
+* [CIPHER](#cipher)
+* [AES](#aes)
+* [DES](#des)
+* [RC2](#rc2)
+
+### PKI
+
+* [RSA](#rsa)
+* [RSA-KEM](#rsakem)
+* [X.509](#x509)
+* [PKCS#5](#pkcs5)
+* [PKCS#7](#pkcs7)
+* [PKCS#8](#pkcs8)
+* [PKCS#10](#pkcs10)
+* [PKCS#12](#pkcs12)
+* [ASN.1](#asn)
+
+### Message Digests
+
+* [SHA1](#sha1)
+* [SHA256](#sha256)
+* [SHA384](#sha384)
+* [SHA512](#sha512)
+* [MD5](#md5)
+* [HMAC](#hmac)
+
+### Utilities
+
+* [Prime](#prime)
+* [PRNG](#prng)
+* [Tasks](#task)
+* [Utilities](#util)
+* [Logging](#log)
+* [Debugging](#debug)
+* [Flash Socket Policy Module](#fsp)
+
+---------------------------------------
+
+If at any time you wish to disable the use of native code, where available,
+for particular forge features like its secure random number generator, you
+may set the ```disableNativeCode``` flag on ```forge``` to ```true```. It
+is not recommended that you set this flag as native code is typically more
+performant and may have stronger security properties. It may be useful to
+set this flag to test certain features that you plan to run in environments
+that are different from your testing environment.
+
+To disable native code when including forge in the browser:
+
+```js
+forge = {disableNativeCode: true};
+// now include forge script file(s)
+// Note: with this approach, script files *must*
+// be included after initializing the global forge var
+
+// alternatively, include script files first and then call
+forge = forge({disableNativeCode: true});
+
+// Note: forge will be permanently reconfigured now;
+// to avoid this but use the same "forge" var name,
+// you can wrap your code in a function to shadow the
+// global var, eg:
+(function(forge) {
+  // ...
+})(forge({disableNativeCode: true}));
+```
+
+To disable native code when using node.js:
+
+```js
+var forge = require('node-forge')({disableNativeCode: true});
+```
+
+---------------------------------------
+## Transports
+
+<a name="tls" />
+### TLS
+
+Provides a native javascript client and server-side [TLS][] implementation.
+
+__Examples__
+
+```js
+// create TLS client
+var client = forge.tls.createConnection({
+  server: false,
+  caStore: /* Array of PEM-formatted certs or a CA store object */,
+  sessionCache: {},
+  // supported cipher suites in order of preference
+  cipherSuites: [
+    forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+    forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+  virtualHost: 'example.com',
+  verify: function(connection, verified, depth, certs) {
+    if(depth === 0) {
+      var cn = certs[0].subject.getField('CN').value;
+      if(cn !== 'example.com') {
+        verified = {
+          alert: forge.tls.Alert.Description.bad_certificate,
+          message: 'Certificate common name does not match hostname.'
+        };
+      }
+    }
+    return verified;
+  },
+  connected: function(connection) {
+    console.log('connected');
+    // send message to server
+    connection.prepare(forge.util.encodeUtf8('Hi server!'));
+    /* NOTE: experimental, start heartbeat retransmission timer
+    myHeartbeatTimer = setInterval(function() {
+      connection.prepareHeartbeatRequest(forge.util.createBuffer('1234'));
+    }, 5*60*1000);*/
+  },
+  /* provide a client-side cert if you want
+  getCertificate: function(connection, hint) {
+    return myClientCertificate;
+  },
+  /* the private key for the client-side cert if provided */
+  getPrivateKey: function(connection, cert) {
+    return myClientPrivateKey;
+  },
+  tlsDataReady: function(connection) {
+    // TLS data (encrypted) is ready to be sent to the server
+    sendToServerSomehow(connection.tlsData.getBytes());
+    // if you were communicating with the server below, you'd do:
+    // server.process(connection.tlsData.getBytes());
+  },
+  dataReady: function(connection) {
+    // clear data from the server is ready
+    console.log('the server sent: ' +
+      forge.util.decodeUtf8(connection.data.getBytes()));
+    // close connection
+    connection.close();
+  },
+  /* NOTE: experimental
+  heartbeatReceived: function(connection, payload) {
+    // restart retransmission timer, look at payload
+    clearInterval(myHeartbeatTimer);
+    myHeartbeatTimer = setInterval(function() {
+      connection.prepareHeartbeatRequest(forge.util.createBuffer('1234'));
+    }, 5*60*1000);
+    payload.getBytes();
+  },*/
+  closed: function(connection) {
+    console.log('disconnected');
+  },
+  error: function(connection, error) {
+    console.log('uh oh', error);
+  }
+});
+
+// start the handshake process
+client.handshake();
+
+// when encrypted TLS data is received from the server, process it
+client.process(encryptedBytesFromServer);
+
+// create TLS server
+var server = forge.tls.createConnection({
+  server: true,
+  caStore: /* Array of PEM-formatted certs or a CA store object */,
+  sessionCache: {},
+  // supported cipher suites in order of preference
+  cipherSuites: [
+    forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+    forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+  // require a client-side certificate if you want
+  verifyClient: true,
+  verify: function(connection, verified, depth, certs) {
+    if(depth === 0) {
+      var cn = certs[0].subject.getField('CN').value;
+      if(cn !== 'the-client') {
+        verified = {
+          alert: forge.tls.Alert.Description.bad_certificate,
+          message: 'Certificate common name does not match expected client.'
+        };
+      }
+    }
+    return verified;
+  },
+  connected: function(connection) {
+    console.log('connected');
+    // send message to client
+    connection.prepare(forge.util.encodeUtf8('Hi client!'));
+    /* NOTE: experimental, start heartbeat retransmission timer
+    myHeartbeatTimer = setInterval(function() {
+      connection.prepareHeartbeatRequest(forge.util.createBuffer('1234'));
+    }, 5*60*1000);*/
+  },
+  getCertificate: function(connection, hint) {
+    return myServerCertificate;
+  },
+  getPrivateKey: function(connection, cert) {
+    return myServerPrivateKey;
+  },
+  tlsDataReady: function(connection) {
+    // TLS data (encrypted) is ready to be sent to the client
+    sendToClientSomehow(connection.tlsData.getBytes());
+    // if you were communicating with the client above you'd do:
+    // client.process(connection.tlsData.getBytes());
+  },
+  dataReady: function(connection) {
+    // clear data from the client is ready
+    console.log('the client sent: ' +
+      forge.util.decodeUtf8(connection.data.getBytes()));
+    // close connection
+    connection.close();
+  },
+  /* NOTE: experimental
+  heartbeatReceived: function(connection, payload) {
+    // restart retransmission timer, look at payload
+    clearInterval(myHeartbeatTimer);
+    myHeartbeatTimer = setInterval(function() {
+      connection.prepareHeartbeatRequest(forge.util.createBuffer('1234'));
+    }, 5*60*1000);
+    payload.getBytes();
+  },*/
+  closed: function(connection) {
+    console.log('disconnected');
+  },
+  error: function(connection, error) {
+    console.log('uh oh', error);
+  }
+});
+
+// when encrypted TLS data is received from the client, process it
+server.process(encryptedBytesFromClient);
+```
+
+Connect to a TLS server using node's net.Socket:
+
+```js
+var socket = new net.Socket();
+
+var client = forge.tls.createConnection({
+  server: false,
+  verify: function(connection, verified, depth, certs) {
+    // skip verification for testing
+    console.log('[tls] server certificate verified');
+    return true;
+  },
+  connected: function(connection) {
+    console.log('[tls] connected');
+    // prepare some data to send (note that the string is interpreted as
+    // 'binary' encoded, which works for HTTP which only uses ASCII, use
+    // forge.util.encodeUtf8(str) otherwise
+    client.prepare('GET / HTTP/1.0\r\n\r\n');
+  },
+  tlsDataReady: function(connection) {
+    // encrypted data is ready to be sent to the server
+    var data = connection.tlsData.getBytes();
+    socket.write(data, 'binary'); // encoding should be 'binary'
+  },
+  dataReady: function(connection) {
+    // clear data from the server is ready
+    var data = connection.data.getBytes();
+    console.log('[tls] data received from the server: ' + data);
+  },
+  closed: function() {
+    console.log('[tls] disconnected');
+  },
+  error: function(connection, error) {
+    console.log('[tls] error', error);
+  }
+});
+
+socket.on('connect', function() {
+  console.log('[socket] connected');
+  client.handshake();
+});
+socket.on('data', function(data) {
+  client.process(data.toString('binary')); // encoding should be 'binary'
+});
+socket.on('end', function() {
+  console.log('[socket] disconnected');
+});
+
+// connect to google.com
+socket.connect(443, 'google.com');
+
+// or connect to gmail's imap server (but don't send the HTTP header above)
+//socket.connect(993, 'imap.gmail.com');
+```
+
+<a name="http" />
+### HTTP
+
+Provides a native [JavaScript][] mini-implementation of an http client that
+uses pooled sockets.
+
+__Examples__
+
+```js
+// create an HTTP GET request
+var request = forge.http.createRequest({method: 'GET', path: url.path});
+
+// send the request somewhere
+sendSomehow(request.toString());
+
+// receive response
+var buffer = forge.util.createBuffer();
+var response = forge.http.createResponse();
+var someAsyncDataHandler = function(bytes) {
+  if(!response.bodyReceived) {
+    buffer.putBytes(bytes);
+    if(!response.headerReceived) {
+      if(response.readHeader(buffer)) {
+        console.log('HTTP response header: ' + response.toString());
+      }
+    }
+    if(response.headerReceived && !response.bodyReceived) {
+      if(response.readBody(buffer)) {
+        console.log('HTTP response body: ' + response.body);
+      }
+    }
+  }
+};
+```
+
+<a name="ssh" />
+### SSH
+
+Provides some SSH utility functions.
+
+__Examples__
+
+```js
+// encodes (and optionally encrypts) a private RSA key as a Putty PPK file
+forge.ssh.privateKeyToPutty(privateKey, passphrase, comment);
+
+// encodes a public RSA key as an OpenSSH file
+forge.ssh.publicKeyToOpenSSH(key, comment);
+
+// encodes a private RSA key as an OpenSSH file
+forge.ssh.privateKeyToOpenSSH(privateKey, passphrase);
+
+// gets the SSH public key fingerprint in a byte buffer
+forge.ssh.getPublicKeyFingerprint(key);
+
+// gets a hex-encoded, colon-delimited SSH public key fingerprint
+forge.ssh.getPublicKeyFingerprint(key, {encoding: 'hex', delimiter: ':'});
+```
+
+<a name="xhr" />
+### XHR
+
+Provides an XmlHttpRequest implementation using forge.http as a backend.
+
+__Examples__
+
+```js
+```
+
+<a name="socket" />
+### Sockets
+
+Provides an interface to create and use raw sockets provided via Flash.
+
+__Examples__
+
+```js
+```
+
+---------------------------------------
+## Ciphers
+
+<a name="cipher" />
+### CIPHER
+
+Provides a basic API for block encryption and decryption. There is built-in
+support for the ciphers: [AES][], [3DES][], and [DES][], and for the modes
+of operation: [ECB][], [CBC][], [CFB][], [OFB][], [CTR][], and [GCM][].
+
+These algorithms are currently supported:
+
+* AES-CBC
+* AES-CFB
+* AES-OFB
+* AES-CTR
+* AES-GCM
+* 3DES-ECB
+* 3DES-CBC
+* DES-ECB
+* DES-CBC
+
+When using an [AES][] algorithm, the key size will determine whether
+AES-128, AES-192, or AES-256 is used (all are supported). When a [DES][]
+algorithm is used, the key size will determine whether [3DES][] or regular
+[DES][] is used. Use a [3DES][] algorithm to enforce Triple-DES.
+
+__Examples__
+
+```js
+// generate a random key and IV
+// Note: a key size of 16 bytes will use AES-128, 24 => AES-192, 32 => AES-256
+var key = forge.random.getBytesSync(16);
+var iv = forge.random.getBytesSync(16);
+
+/* alternatively, generate a password-based 16-byte key
+var salt = forge.random.getBytesSync(128);
+var key = forge.pkcs5.pbkdf2('password', salt, numIterations, 16);
+*/
+
+// encrypt some bytes using CBC mode
+// (other modes include: CFB, OFB, CTR, and GCM)
+var cipher = forge.cipher.createCipher('AES-CBC', key);
+cipher.start({iv: iv});
+cipher.update(forge.util.createBuffer(someBytes));
+cipher.finish();
+var encrypted = cipher.output;
+// outputs encrypted hex
+console.log(encrypted.toHex());
+
+// decrypt some bytes using CBC mode
+// (other modes include: CFB, OFB, CTR, and GCM)
+var decipher = forge.cipher.createDecipher('AES-CBC', key);
+decipher.start({iv: iv});
+decipher.update(encrypted);
+decipher.finish();
+// outputs decrypted hex
+console.log(decipher.output.toHex());
+
+// encrypt some bytes using GCM mode
+var cipher = forge.cipher.createCipher('AES-GCM', key);
+cipher.start({
+  iv: iv, // should be a 12-byte binary-encoded string or byte buffer
+  additionalData: 'binary-encoded string', // optional
+  tagLength: 128 // optional, defaults to 128 bits
+});
+cipher.update(forge.util.createBuffer(someBytes));
+cipher.finish();
+var encrypted = cipher.output;
+var tag = cipher.mode.tag;
+// outputs encrypted hex
+console.log(encrypted.toHex());
+// outputs authentication tag
+console.log(tag.toHex());
+
+// decrypt some bytes using GCM mode
+var decipher = forge.cipher.createDecipher('AES-GCM', key);
+decipher.start({
+  iv: iv,
+  additionalData: 'binary-encoded string', // optional
+  tagLength: 128, // optional, defaults to 128 bits
+  tag: tag // authentication tag from encryption
+});
+decipher.update(encrypted);
+var pass = decipher.finish();
+// pass is false if there was a failure (eg: authentication tag didn't match)
+if(pass) {
+  // outputs decrypted hex
+  console.log(decipher.output.toHex());
+}
+```
+
+Using forge in node.js to match openssl's "enc" command line tool (**Note**: OpenSSL "enc" uses a non-standard file format with a custom key derivation function and a fixed iteration count of 1, which some consider less secure than alternatives such as [OpenPGP](https://tools.ietf.org/html/rfc4880)/[GnuPG](https://www.gnupg.org/)):
+
+```js
+var forge = require('node-forge');
+var fs = require('fs');
+
+// openssl enc -des3 -in input.txt -out input.enc
+function encrypt(password) {
+  var input = fs.readFileSync('input.txt', {encoding: 'binary'});
+
+  // 3DES key and IV sizes
+  var keySize = 24;
+  var ivSize = 8;
+
+  // get derived bytes
+  // Notes:
+  // 1. If using an alternative hash (eg: "-md sha1") pass
+  //   "forge.md.sha1.create()" as the final parameter.
+  // 2. If using "-nosalt", set salt to null.
+  var salt = forge.random.getBytesSync(8);
+  // var md = forge.md.sha1.create(); // "-md sha1"
+  var derivedBytes = forge.pbe.opensslDeriveBytes(
+    password, salt, keySize + ivSize/*, md*/);
+  var buffer = forge.util.createBuffer(derivedBytes);
+  var key = buffer.getBytes(keySize);
+  var iv = buffer.getBytes(ivSize);
+
+  var cipher = forge.cipher.createCipher('3DES-CBC', key);
+  cipher.start({iv: iv});
+  cipher.update(forge.util.createBuffer(input, 'binary'));
+  cipher.finish();
+
+  var output = forge.util.createBuffer();
+
+  // if using a salt, prepend this to the output:
+  if(salt !== null) {
+    output.putBytes('Salted__'); // (add to match openssl tool output)
+    output.putBytes(salt);
+  }
+  output.putBuffer(cipher.output);
+
+  fs.writeFileSync('input.enc', output.getBytes(), {encoding: 'binary'});
+}
+
+// openssl enc -d -des3 -in input.enc -out input.dec.txt
+function decrypt(password) {
+  var input = fs.readFileSync('input.enc', {encoding: 'binary'});
+
+  // parse salt from input
+  input = forge.util.createBuffer(input, 'binary');
+  // skip "Salted__" (if known to be present)
+  input.getBytes('Salted__'.length);
+  // read 8-byte salt
+  var salt = input.getBytes(8);
+
+  // Note: if using "-nosalt", skip above parsing and use
+  // var salt = null;
+
+  // 3DES key and IV sizes
+  var keySize = 24;
+  var ivSize = 8;
+
+  var derivedBytes = forge.pbe.opensslDeriveBytes(
+    password, salt, keySize + ivSize);
+  var buffer = forge.util.createBuffer(derivedBytes);
+  var key = buffer.getBytes(keySize);
+  var iv = buffer.getBytes(ivSize);
+
+  var decipher = forge.cipher.createDecipher('3DES-CBC', key);
+  decipher.start({iv: iv});
+  decipher.update(input);
+  var result = decipher.finish(); // check 'result' for true/false
+
+  fs.writeFileSync(
+    'input.dec.txt', decipher.output.getBytes(), {encoding: 'binary'});
+}
+```
+
+<a name="aes" />
+### AES
+
+Provides [AES][] encryption and decryption in [CBC][], [CFB][], [OFB][],
+[CTR][], and [GCM][] modes. See [CIPHER](#cipher) for examples.
+
+<a name="des" />
+### DES
+
+Provides [3DES][] and [DES][] encryption and decryption in [ECB][] and
+[CBC][] modes. See [CIPHER](#cipher) for examples.
+
+<a name="rc2" />
+### RC2
+
+__Examples__
+
+```js
+// generate a random key and IV
+var key = forge.random.getBytesSync(16);
+var iv = forge.random.getBytesSync(8);
+
+// encrypt some bytes
+var cipher = forge.rc2.createEncryptionCipher(key);
+cipher.start(iv);
+cipher.update(forge.util.createBuffer(someBytes));
+cipher.finish();
+var encrypted = cipher.output;
+// outputs encrypted hex
+console.log(encrypted.toHex());
+
+// decrypt some bytes
+var cipher = forge.rc2.createDecryptionCipher(key);
+cipher.start(iv);
+cipher.update(encrypted);
+cipher.finish();
+// outputs decrypted hex
+console.log(cipher.output.toHex());
+```
+---------------------------------------
+## PKI
+
+Provides [X.509][] certificate and RSA public and private key encoding,
+decoding, encryption/decryption, and signing/verifying.
+
+<a name="rsa" />
+### RSA
+
+__Examples__
+
+```js
+var rsa = forge.pki.rsa;
+
+// generate an RSA key pair synchronously
+var keypair = rsa.generateKeyPair({bits: 2048, e: 0x10001});
+
+// generate an RSA key pair asynchronously (uses web workers if available)
+// use workers: -1 to run a fast core estimator to optimize # of workers
+rsa.generateKeyPair({bits: 2048, workers: 2}, function(err, keypair) {
+  // keypair.privateKey, keypair.publicKey
+});
+
+// generate an RSA key pair in steps that attempt to run for a specified period
+// of time on the main JS thread
+var state = rsa.createKeyPairGenerationState(2048, 0x10001);
+var step = function() {
+  // run for 100 ms
+  if(!rsa.stepKeyPairGenerationState(state, 100)) {
+    setTimeout(step, 1);
+  }
+  else {
+    // done, turn off progress indicator, use state.keys
+  }
+};
+// turn on progress indicator, schedule generation to run
+setTimeout(step);
+
+// sign data with a private key and output DigestInfo DER-encoded bytes
+// (defaults to RSASSA PKCS#1 v1.5)
+var md = forge.md.sha1.create();
+md.update('sign this', 'utf8');
+var signature = privateKey.sign(md);
+
+// verify data with a public key
+// (defaults to RSASSA PKCS#1 v1.5)
+var verified = publicKey.verify(md.digest().bytes(), signature);
+
+// sign data using RSASSA-PSS where PSS uses a SHA-1 hash, a SHA-1 based
+// masking function MGF1, and a 20 byte salt
+var md = forge.md.sha1.create();
+md.update('sign this', 'utf8');
+var pss = forge.pss.create({
+  md: forge.md.sha1.create(),
+  mgf: forge.mgf.mgf1.create(forge.md.sha1.create()),
+  saltLength: 20
+  // optionally pass 'prng' with a custom PRNG implementation
+  // optionalls pass 'salt' with a forge.util.ByteBuffer w/custom salt
+});
+var signature = privateKey.sign(md, pss);
+
+// verify RSASSA-PSS signature
+var pss = forge.pss.create({
+  md: forge.md.sha1.create(),
+  mgf: forge.mgf.mgf1.create(forge.md.sha1.create()),
+  saltLength: 20
+  // optionally pass 'prng' with a custom PRNG implementation
+});
+var md = forge.md.sha1.create();
+md.update('sign this', 'utf8');
+publicKey.verify(md.digest().getBytes(), signature, pss);
+
+// encrypt data with a public key (defaults to RSAES PKCS#1 v1.5)
+var encrypted = publicKey.encrypt(bytes);
+
+// decrypt data with a private key (defaults to RSAES PKCS#1 v1.5)
+var decrypted = privateKey.decrypt(encrypted);
+
+// encrypt data with a public key using RSAES PKCS#1 v1.5
+var encrypted = publicKey.encrypt(bytes, 'RSAES-PKCS1-V1_5');
+
+// decrypt data with a private key using RSAES PKCS#1 v1.5
+var decrypted = privateKey.decrypt(encrypted, 'RSAES-PKCS1-V1_5');
+
+// encrypt data with a public key using RSAES-OAEP
+var encrypted = publicKey.encrypt(bytes, 'RSA-OAEP');
+
+// decrypt data with a private key using RSAES-OAEP
+var decrypted = privateKey.decrypt(encrypted, 'RSA-OAEP');
+
+// encrypt data with a public key using RSAES-OAEP/SHA-256
+var encrypted = publicKey.encrypt(bytes, 'RSA-OAEP', {
+  md: forge.md.sha256.create()
+});
+
+// decrypt data with a private key using RSAES-OAEP/SHA-256
+var decrypted = privateKey.decrypt(encrypted, 'RSA-OAEP', {
+  md: forge.md.sha256.create()
+});
+
+// encrypt data with a public key using RSAES-OAEP/SHA-256/MGF1-SHA-1
+// compatible with Java's RSA/ECB/OAEPWithSHA-256AndMGF1Padding
+var encrypted = publicKey.encrypt(bytes, 'RSA-OAEP', {
+  md: forge.md.sha256.create(),
+  mgf1: {
+    md: forge.md.sha1.create()
+  }
+});
+
+// decrypt data with a private key using RSAES-OAEP/SHA-256/MGF1-SHA-1
+// compatible with Java's RSA/ECB/OAEPWithSHA-256AndMGF1Padding
+var decrypted = privateKey.decrypt(encrypted, 'RSA-OAEP', {
+  md: forge.md.sha256.create(),
+  mgf1: {
+    md: forge.md.sha1.create()
+  }
+});
+
+```
+
+<a name="rsakem" />
+### RSA-KEM
+
+__Examples__
+
+```js
+// generate an RSA key pair asynchronously (uses web workers if available)
+// use workers: -1 to run a fast core estimator to optimize # of workers
+forge.rsa.generateKeyPair({bits: 2048, workers: -1}, function(err, keypair) {
+  // keypair.privateKey, keypair.publicKey
+});
+
+// generate and encapsulate a 16-byte secret key
+var kdf1 = new forge.kem.kdf1(forge.md.sha1.create());
+var kem = forge.kem.rsa.create(kdf1);
+var result = kem.encrypt(keypair.publicKey, 16);
+// result has 'encapsulation' and 'key'
+
+// encrypt some bytes
+var iv = forge.random.getBytesSync(12);
+var someBytes = 'hello world!';
+var cipher = forge.cipher.createCipher('AES-GCM', result.key);
+cipher.start({iv: iv});
+cipher.update(forge.util.createBuffer(someBytes));
+cipher.finish();
+var encrypted = cipher.output.getBytes();
+var tag = cipher.mode.tag.getBytes();
+
+// send 'encrypted', 'iv', 'tag', and result.encapsulation to recipient
+
+// decrypt encapsulated 16-byte secret key
+var kdf1 = new forge.kem.kdf1(forge.md.sha1.create());
+var kem = forge.kem.rsa.create(kdf1);
+var key = kem.decrypt(keypair.privateKey, result.encapsulation, 16);
+
+// decrypt some bytes
+var decipher = forge.cipher.createDecipher('AES-GCM', key);
+decipher.start({iv: iv, tag: tag});
+decipher.update(forge.util.createBuffer(encrypted));
+var pass = decipher.finish();
+// pass is false if there was a failure (eg: authentication tag didn't match)
+if(pass) {
+  // outputs 'hello world!'
+  console.log(decipher.output.getBytes());
+}
+
+```
+
+<a name="x509" />
+### X.509
+
+__Examples__
+
+```js
+var pki = forge.pki;
+
+// convert a PEM-formatted public key to a Forge public key
+var publicKey = pki.publicKeyFromPem(pem);
+
+// convert a Forge public key to PEM-format
+var pem = pki.publicKeyToPem(publicKey);
+
+// convert an ASN.1 SubjectPublicKeyInfo to a Forge public key
+var publicKey = pki.publicKeyFromAsn1(subjectPublicKeyInfo);
+
+// convert a Forge public key to an ASN.1 SubjectPublicKeyInfo
+var subjectPublicKeyInfo = pki.publicKeyToAsn1(publicKey);
+
+// gets a SHA-1 RSAPublicKey fingerprint a byte buffer
+pki.getPublicKeyFingerprint(key);
+
+// gets a SHA-1 SubjectPublicKeyInfo fingerprint a byte buffer
+pki.getPublicKeyFingerprint(key, {type: 'SubjectPublicKeyInfo'});
+
+// gets a hex-encoded, colon-delimited SHA-1 RSAPublicKey public key fingerprint
+pki.getPublicKeyFingerprint(key, {encoding: 'hex', delimiter: ':'});
+
+// gets a hex-encoded, colon-delimited SHA-1 SubjectPublicKeyInfo public key fingerprint
+pki.getPublicKeyFingerprint(key, {
+  type: 'SubjectPublicKeyInfo',
+  encoding: 'hex',
+  delimiter: ':'
+});
+
+// gets a hex-encoded, colon-delimited MD5 RSAPublicKey public key fingerprint
+pki.getPublicKeyFingerprint(key, {
+  md: forge.md.md5.create(),
+  encoding: 'hex',
+  delimiter: ':'
+});
+
+// creates a CA store
+var caStore = pki.createCaStore([/* PEM-encoded cert */, ...]);
+
+// add a certificate to the CA store
+caStore.addCertificate(certObjectOrPemString);
+
+// gets the issuer (its certificate) for the given certificate
+var issuerCert = caStore.getIssuer(subjectCert);
+
+// verifies a certificate chain against a CA store
+pki.verifyCertificateChain(caStore, chain, customVerifyCallback);
+
+// signs a certificate using the given private key
+cert.sign(privateKey);
+
+// signs a certificate using SHA-256 instead of SHA-1
+cert.sign(privateKey, forge.md.sha256.create());
+
+// verifies an issued certificate using the certificates public key
+var verified = issuer.verify(issued);
+
+// generate a keypair and create an X.509v3 certificate
+var keys = pki.rsa.generateKeyPair(2048);
+var cert = pki.createCertificate();
+cert.publicKey = keys.publicKey;
+// alternatively set public key from a csr
+//cert.publicKey = csr.publicKey;
+cert.serialNumber = '01';
+cert.validity.notBefore = new Date();
+cert.validity.notAfter = new Date();
+cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1);
+var attrs = [{
+  name: 'commonName',
+  value: 'example.org'
+}, {
+  name: 'countryName',
+  value: 'US'
+}, {
+  shortName: 'ST',
+  value: 'Virginia'
+}, {
+  name: 'localityName',
+  value: 'Blacksburg'
+}, {
+  name: 'organizationName',
+  value: 'Test'
+}, {
+  shortName: 'OU',
+  value: 'Test'
+}];
+cert.setSubject(attrs);
+// alternatively set subject from a csr
+//cert.setSubject(csr.subject.attributes);
+cert.setIssuer(attrs);
+cert.setExtensions([{
+  name: 'basicConstraints',
+  cA: true
+}, {
+  name: 'keyUsage',
+  keyCertSign: true,
+  digitalSignature: true,
+  nonRepudiation: true,
+  keyEncipherment: true,
+  dataEncipherment: true
+}, {
+  name: 'extKeyUsage',
+  serverAuth: true,
+  clientAuth: true,
+  codeSigning: true,
+  emailProtection: true,
+  timeStamping: true
+}, {
+  name: 'nsCertType',
+  client: true,
+  server: true,
+  email: true,
+  objsign: true,
+  sslCA: true,
+  emailCA: true,
+  objCA: true
+}, {
+  name: 'subjectAltName',
+  altNames: [{
+    type: 6, // URI
+    value: 'http://example.org/webid#me'
+  }, {
+    type: 7, // IP
+    ip: '127.0.0.1'
+  }]
+}, {
+  name: 'subjectKeyIdentifier'
+}]);
+/* alternatively set extensions from a csr
+var extensions = csr.getAttribute({name: 'extensionRequest'}).extensions;
+// optionally add more extensions
+extensions.push.apply(extensions, [{
+  name: 'basicConstraints',
+  cA: true
+}, {
+  name: 'keyUsage',
+  keyCertSign: true,
+  digitalSignature: true,
+  nonRepudiation: true,
+  keyEncipherment: true,
+  dataEncipherment: true
+}]);
+cert.setExtensions(extensions);
+*/
+// self-sign certificate
+cert.sign(keys.privateKey);
+
+// convert a Forge certificate to PEM
+var pem = pki.certificateToPem(cert);
+
+// convert a Forge certificate from PEM
+var cert = pki.certificateFromPem(pem);
+
+// convert an ASN.1 X.509x3 object to a Forge certificate
+var cert = pki.certificateFromAsn1(obj);
+
+// convert a Forge certificate to an ASN.1 X.509v3 object
+var asn1Cert = pki.certificateToAsn1(cert);
+```
+
+<a name="pkcs5" />
+### PKCS#5
+
+Provides the password-based key-derivation function from [PKCS#5][].
+
+__Examples__
+
+```js
+// generate a password-based 16-byte key
+// note an optional message digest can be passed as the final parameter
+var salt = forge.random.getBytesSync(128);
+var derivedKey = forge.pkcs5.pbkdf2('password', salt, numIterations, 16);
+
+// generate key asynchronously
+// note an optional message digest can be passed before the callback
+forge.pkcs5.pbkdf2('password', salt, numIterations, 16, function(err, derivedKey) {
+  // do something w/derivedKey
+});
+```
+
+<a name="pkcs7" />
+### PKCS#7
+
+Provides cryptographically protected messages from [PKCS#7][].
+
+__Examples__
+
+```js
+// convert a message from PEM
+var p7 = forge.pkcs7.messageFromPem(pem);
+// look at p7.recipients
+
+// find a recipient by the issuer of a certificate
+var recipient = p7.findRecipient(cert);
+
+// decrypt
+p7.decrypt(p7.recipients[0], privateKey);
+
+// create a p7 enveloped message
+var p7 = forge.pkcs7.createEnvelopedData();
+
+// add a recipient
+var cert = forge.pki.certificateFromPem(certPem);
+p7.addRecipient(cert);
+
+// set content
+p7.content = forge.util.createBuffer('Hello');
+
+// encrypt
+p7.encrypt();
+
+// convert message to PEM
+var pem = forge.pkcs7.messageToPem(p7);
+
+// create a degenerate PKCS#7 certificate container
+// (CRLs not currently supported, only certificates)
+var p7 = forge.pkcs7.createSignedData();
+p7.addCertificate(certOrCertPem1);
+p7.addCertificate(certOrCertPem2);
+var pem = forge.pkcs7.messageToPem(p7);
+```
+
+<a name="pkcs8" />
+### PKCS#8
+
+__Examples__
+
+```js
+var pki = forge.pki;
+
+// convert a PEM-formatted private key to a Forge private key
+var privateKey = pki.privateKeyFromPem(pem);
+
+// convert a Forge private key to PEM-format
+var pem = pki.privateKeyToPem(privateKey);
+
+// convert an ASN.1 PrivateKeyInfo or RSAPrivateKey to a Forge private key
+var privateKey = pki.privateKeyFromAsn1(rsaPrivateKey);
+
+// convert a Forge private key to an ASN.1 RSAPrivateKey
+var rsaPrivateKey = pki.privateKeyToAsn1(privateKey);
+
+// wrap an RSAPrivateKey ASN.1 object in a PKCS#8 ASN.1 PrivateKeyInfo
+var privateKeyInfo = pki.wrapRsaPrivateKey(rsaPrivateKey);
+
+// convert a PKCS#8 ASN.1 PrivateKeyInfo to PEM
+var pem = pki.privateKeyInfoToPem(privateKeyInfo);
+
+// encrypts a PrivateKeyInfo and outputs an EncryptedPrivateKeyInfo
+var encryptedPrivateKeyInfo = pki.encryptPrivateKeyInfo(
+  privateKeyInfo, 'password', {
+    algorithm: 'aes256', // 'aes128', 'aes192', 'aes256', '3des'
+  });
+
+// decrypts an ASN.1 EncryptedPrivateKeyInfo
+var privateKeyInfo = pki.decryptPrivateKeyInfo(
+  encryptedPrivateKeyInfo, 'password');
+
+// converts an EncryptedPrivateKeyInfo to PEM
+var pem = pki.encryptedPrivateKeyToPem(encryptedPrivateKeyInfo);
+
+// converts a PEM-encoded EncryptedPrivateKeyInfo to ASN.1 format
+var encryptedPrivateKeyInfo = pki.encryptedPrivateKeyFromPem(pem);
+
+// wraps and encrypts a Forge private key and outputs it in PEM format
+var pem = pki.encryptRsaPrivateKey(privateKey, 'password');
+
+// encrypts a Forge private key and outputs it in PEM format using OpenSSL's
+// proprietary legacy format + encapsulated PEM headers (DEK-Info)
+var pem = pki.encryptRsaPrivateKey(privateKey, 'password', {legacy: true});
+
+// decrypts a PEM-formatted, encrypted private key
+var privateKey = pki.decryptRsaPrivateKey(pem, 'password');
+
+// sets an RSA public key from a private key
+var publicKey = pki.setRsaPublicKey(privateKey.n, privateKey.e);
+```
+
+<a name="pkcs10" />
+### PKCS#10
+
+Provides certification requests or certificate signing requests (CSR) from
+[PKCS#10][].
+
+__Examples__
+
+```js
+// generate a key pair
+var keys = forge.pki.rsa.generateKeyPair(1024);
+
+// create a certification request (CSR)
+var csr = forge.pki.createCertificationRequest();
+csr.publicKey = keys.publicKey;
+csr.setSubject([{
+  name: 'commonName',
+  value: 'example.org'
+}, {
+  name: 'countryName',
+  value: 'US'
+}, {
+  shortName: 'ST',
+  value: 'Virginia'
+}, {
+  name: 'localityName',
+  value: 'Blacksburg'
+}, {
+  name: 'organizationName',
+  value: 'Test'
+}, {
+  shortName: 'OU',
+  value: 'Test'
+}]);
+// set (optional) attributes
+csr.setAttributes([{
+  name: 'challengePassword',
+  value: 'password'
+}, {
+  name: 'unstructuredName',
+  value: 'My Company, Inc.'
+}, {
+  name: 'extensionRequest',
+  extensions: [{
+    name: 'subjectAltName',
+    altNames: [{
+      // 2 is DNS type
+      type: 2,
+      value: 'test.domain.com'
+    }, {
+      type: 2,
+      value: 'other.domain.com',
+    }, {
+      type: 2,
+      value: 'www.domain.net'
+    }]
+  }]
+}]);
+
+// sign certification request
+csr.sign(keys.privateKey);
+
+// verify certification request
+var verified = csr.verify();
+
+// convert certification request to PEM-format
+var pem = forge.pki.certificationRequestToPem(csr);
+
+// convert a Forge certification request from PEM-format
+var csr = forge.pki.certificationRequestFromPem(pem);
+
+// get an attribute
+csr.getAttribute({name: 'challengePassword'});
+
+// get extensions array
+csr.getAttribute({name: 'extensionRequest'}).extensions;
+
+```
+
+<a name="pkcs12" />
+### PKCS#12
+
+Provides the cryptographic archive file format from [PKCS#12][].
+
+__Examples__
+
+```js
+// decode p12 from base64
+var p12Der = forge.util.decode64(p12b64);
+// get p12 as ASN.1 object
+var p12Asn1 = forge.asn1.fromDer(p12Der);
+// decrypt p12 using the password 'password'
+var p12 = forge.pkcs12.pkcs12FromAsn1(p12Asn1, 'password');
+// decrypt p12 using non-strict parsing mode (resolves some ASN.1 parse errors)
+var p12 = forge.pkcs12.pkcs12FromAsn1(p12Asn1, false, 'password');
+// decrypt p12 using literally no password (eg: Mac OS X/apple push)
+var p12 = forge.pkcs12.pkcs12FromAsn1(p12Asn1);
+// decrypt p12 using an "empty" password (eg: OpenSSL with no password input)
+var p12 = forge.pkcs12.pkcs12FromAsn1(p12Asn1, '');
+// p12.safeContents is an array of safe contents, each of
+// which contains an array of safeBags
+
+// get bags by friendlyName
+var bags = p12.getBags({friendlyName: 'test'});
+// bags are key'd by attribute type (here "friendlyName")
+// and the key values are an array of matching objects
+var cert = bags.friendlyName[0];
+
+// get bags by localKeyId
+var bags = p12.getBags({localKeyId: buffer});
+// bags are key'd by attribute type (here "localKeyId")
+// and the key values are an array of matching objects
+var cert = bags.localKeyId[0];
+
+// get bags by localKeyId (input in hex)
+var bags = p12.getBags({localKeyIdHex: '7b59377ff142d0be4565e9ac3d396c01401cd879'});
+// bags are key'd by attribute type (here "localKeyId", *not* "localKeyIdHex")
+// and the key values are an array of matching objects
+var cert = bags.localKeyId[0];
+
+// get bags by type
+var bags = p12.getBags({bagType: forge.pki.oids.certBag});
+// bags are key'd by bagType and each bagType key's value
+// is an array of matches (in this case, certificate objects)
+var cert = bags[forge.pki.oids.certBag][0];
+
+// get bags by friendlyName and filter on bag type
+var bags = p12.getBags({
+  friendlyName: 'test',
+  bagType: forge.pki.oids.certBag
+});
+
+// generate a p12 using AES (default)
+var p12Asn1 = forge.pkcs12.toPkcs12Asn1(
+  privateKey, certificateChain, 'password');
+
+// generate a p12 that can be imported by Chrome/Firefox
+// (requires the use of Triple DES instead of AES)
+var p12Asn1 = forge.pkcs12.toPkcs12Asn1(
+  privateKey, certificateChain, 'password',
+  {algorithm: '3des'});
+
+// base64-encode p12
+var p12Der = forge.asn1.toDer(p12Asn1).getBytes();
+var p12b64 = forge.util.encode64(p12Der);
+
+// create download link for p12
+var a = document.createElement('a');
+a.download = 'example.p12';
+a.setAttribute('href', 'data:application/x-pkcs12;base64,' + p12b64);
+a.appendChild(document.createTextNode('Download'));
+```
+
+<a name="asn" />
+### ASN.1
+
+Provides [ASN.1][] DER encoding and decoding.
+
+__Examples__
+
+```js
+var asn1 = forge.asn1;
+
+// create a SubjectPublicKeyInfo
+var subjectPublicKeyInfo =
+  asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+    // AlgorithmIdentifier
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+      // algorithm
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+        asn1.oidToDer(pki.oids['rsaEncryption']).getBytes()),
+      // parameters (null)
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
+    ]),
+    // subjectPublicKey
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.BITSTRING, false, [
+      // RSAPublicKey
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+        // modulus (n)
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+          _bnToBytes(key.n)),
+        // publicExponent (e)
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+          _bnToBytes(key.e))
+      ])
+    ])
+  ]);
+
+// serialize an ASN.1 object to DER format
+var derBuffer = asn1.toDer(subjectPublicKeyInfo);
+
+// deserialize to an ASN.1 object from a byte buffer filled with DER data
+var object = asn1.fromDer(derBuffer);
+
+// convert an OID dot-separated string to a byte buffer
+var derOidBuffer = asn1.oidToDer('1.2.840.113549.1.1.5');
+
+// convert a byte buffer with a DER-encoded OID to a dot-separated string
+console.log(asn1.derToDer(derOidBuffer));
+// output: 1.2.840.113549.1.1.5
+
+// validates that an ASN.1 object matches a particular ASN.1 structure and
+// captures data of interest from that structure for easy access
+var publicKeyValidator = {
+  name: 'SubjectPublicKeyInfo',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  captureAsn1: 'subjectPublicKeyInfo',
+  value: [{
+    name: 'SubjectPublicKeyInfo.AlgorithmIdentifier',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true,
+    value: [{
+      name: 'AlgorithmIdentifier.algorithm',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.OID,
+      constructed: false,
+      capture: 'publicKeyOid'
+    }]
+  }, {
+    // subjectPublicKey
+    name: 'SubjectPublicKeyInfo.subjectPublicKey',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.BITSTRING,
+    constructed: false,
+    value: [{
+      // RSAPublicKey
+      name: 'SubjectPublicKeyInfo.subjectPublicKey.RSAPublicKey',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.SEQUENCE,
+      constructed: true,
+      optional: true,
+      captureAsn1: 'rsaPublicKey'
+    }]
+  }]
+};
+
+var capture = {};
+var errors = [];
+if(!asn1.validate(
+  publicKeyValidator, subjectPublicKeyInfo, validator, capture, errors)) {
+  throw 'ASN.1 object is not a SubjectPublicKeyInfo.';
+}
+// capture.subjectPublicKeyInfo contains the full ASN.1 object
+// capture.rsaPublicKey contains the full ASN.1 object for the RSA public key
+// capture.publicKeyOid only contains the value for the OID
+var oid = asn1.derToOid(capture.publicKeyOid);
+if(oid !== pki.oids['rsaEncryption']) {
+  throw 'Unsupported OID.';
+}
+
+// pretty print an ASN.1 object to a string for debugging purposes
+asn1.prettyPrint(object);
+```
+
+---------------------------------------
+## Message Digests
+
+<a name="sha1" />
+### SHA1
+
+Provides [SHA-1][] message digests.
+
+__Examples__
+
+```js
+var md = forge.md.sha1.create();
+md.update('The quick brown fox jumps over the lazy dog');
+console.log(md.digest().toHex());
+// output: 2fd4e1c67a2d28fced849ee1bb76e7391b93eb12
+```
+
+<a name="sha256" />
+### SHA256
+
+Provides [SHA-256][] message digests.
+
+__Examples__
+
+```js
+var md = forge.md.sha256.create();
+md.update('The quick brown fox jumps over the lazy dog');
+console.log(md.digest().toHex());
+// output: d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592
+```
+
+<a name="sha384" />
+### SHA384
+
+Provides [SHA-384][] message digests.
+
+__Examples__
+
+```js
+var md = forge.md.sha384.create();
+md.update('The quick brown fox jumps over the lazy dog');
+console.log(md.digest().toHex());
+// output: ca737f1014a48f4c0b6dd43cb177b0afd9e5169367544c494011e3317dbf9a509cb1e5dc1e85a941bbee3d7f2afbc9b1
+```
+
+<a name="sha512" />
+### SHA512
+
+Provides [SHA-512][] message digests.
+
+__Examples__
+
+```js
+// SHA-512
+var md = forge.md.sha512.create();
+md.update('The quick brown fox jumps over the lazy dog');
+console.log(md.digest().toHex());
+// output: 07e547d9586f6a73f73fbac0435ed76951218fb7d0c8d788a309d785436bbb642e93a252a954f23912547d1e8a3b5ed6e1bfd7097821233fa0538f3db854fee6
+
+// SHA-512/224
+var md = forge.md.sha512.sha224.create();
+md.update('The quick brown fox jumps over the lazy dog');
+console.log(md.digest().toHex());
+// output: 944cd2847fb54558d4775db0485a50003111c8e5daa63fe722c6aa37
+
+// SHA-512/256
+var md = forge.md.sha512.sha256.create();
+md.update('The quick brown fox jumps over the lazy dog');
+console.log(md.digest().toHex());
+// output: dd9d67b371519c339ed8dbd25af90e976a1eeefd4ad3d889005e532fc5bef04d
+```
+
+<a name="md5" />
+### MD5
+
+Provides [MD5][] message digests.
+
+__Examples__
+
+```js
+var md = forge.md.md5.create();
+md.update('The quick brown fox jumps over the lazy dog');
+console.log(md.digest().toHex());
+// output: 9e107d9d372bb6826bd81d3542a419d6
+```
+
+<a name="hmac" />
+### HMAC
+
+Provides [HMAC][] w/any supported message digest algorithm.
+
+__Examples__
+
+```js
+var hmac = forge.hmac.create();
+hmac.start('sha1', 'Jefe');
+hmac.update('what do ya want for nothing?');
+console.log(hmac.digest().toHex());
+// output: effcdf6ae5eb2fa2d27416d5f184df9c259a7c79
+```
+
+---------------------------------------
+## Utilities
+
+<a name="prime" />
+### Prime
+
+Provides an API for generating large, random, probable primes.
+
+__Examples__
+
+```js
+// generate a random prime on the main JS thread
+var bits = 1024;
+forge.prime.generateProbablePrime(bits, function(err, num) {
+  console.log('random prime', num.toString(16));
+});
+
+// generate a random prime using Web Workers (if available, otherwise
+// falls back to the main thread)
+var bits = 1024;
+var options = {
+  algorithm: {
+    name: 'PRIMEINC',
+    workers: -1 // auto-optimize # of workers
+  }
+};
+forge.prime.generateProbablePrime(bits, options, function(err, num) {
+  console.log('random prime', num.toString(16));
+});
+```
+
+<a name="prng" />
+### PRNG
+
+Provides a [Fortuna][]-based cryptographically-secure pseudo-random number
+generator, to be used with a cryptographic function backend, e.g. [AES][]. An
+implementation using [AES][] as a backend is provided. An API for collecting
+entropy is given, though if window.crypto.getRandomValues is available, it will
+be used automatically.
+
+__Examples__
+
+```js
+// get some random bytes synchronously
+var bytes = forge.random.getBytesSync(32);
+console.log(forge.util.bytesToHex(bytes));
+
+// get some random bytes asynchronously
+forge.random.getBytes(32, function(err, bytes) {
+  console.log(forge.util.bytesToHex(bytes));
+});
+
+// collect some entropy if you'd like
+forge.random.collect(someRandomBytes);
+jQuery().mousemove(function(e) {
+  forge.random.collectInt(e.clientX, 16);
+  forge.random.collectInt(e.clientY, 16);
+});
+
+// specify a seed file for use with the synchronous API if you'd like
+forge.random.seedFileSync = function(needed) {
+  // get 'needed' number of random bytes from somewhere
+  return fetchedRandomBytes;
+};
+
+// specify a seed file for use with the asynchronous API if you'd like
+forge.random.seedFile = function(needed, callback) {
+  // get the 'needed' number of random bytes from somewhere
+  callback(null, fetchedRandomBytes);
+});
+
+// register the main thread to send entropy or a Web Worker to receive
+// entropy on demand from the main thread
+forge.random.registerWorker(self);
+
+// generate a new instance of a PRNG with no collected entropy
+var myPrng = forge.random.createInstance();
+```
+
+<a name="task" />
+### Tasks
+
+Provides queuing and synchronizing tasks in a web application.
+
+__Examples__
+
+```js
+```
+
+<a name="util" />
+### Utilities
+
+Provides utility functions, including byte buffer support, base64,
+bytes to/from hex, zlib inflate/deflate, etc.
+
+__Examples__
+
+```js
+// encode/decode base64
+var encoded = forge.util.encode64(str);
+var str = forge.util.decode64(encoded);
+
+// encode/decode UTF-8
+var encoded = forge.util.encodeUtf8(str);
+var str = forge.util.decodeUtf8(encoded);
+
+// bytes to/from hex
+var bytes = forge.util.hexToBytes(hex);
+var hex = forge.util.bytesToHex(bytes);
+
+// create an empty byte buffer
+var buffer = forge.util.createBuffer();
+// create a byte buffer from raw binary bytes
+var buffer = forge.util.createBuffer(input, 'raw');
+// create a byte buffer from utf8 bytes
+var buffer = forge.util.createBuffer(input, 'utf8');
+
+// get the length of the buffer in bytes
+buffer.length();
+// put bytes into the buffer
+buffer.putBytes(bytes);
+// put a 32-bit integer into the buffer
+buffer.putInt32(10);
+// buffer to hex
+buffer.toHex();
+// get a copy of the bytes in the buffer
+bytes.bytes(/* count */);
+// empty this buffer and get its contents
+bytes.getBytes(/* count */);
+
+// convert a forge buffer into a node.js Buffer
+// make sure you specify the encoding as 'binary'
+var forgeBuffer = forge.util.createBuffer();
+var nodeBuffer = new Buffer(forgeBuffer.getBytes(), 'binary');
+
+// convert a node.js Buffer into a forge buffer
+// make sure you specify the encoding as 'binary'
+var nodeBuffer = new Buffer();
+var forgeBuffer = forge.util.createBuffer(nodeBuffer.toString('binary'));
+
+// parse a URL
+var parsed = forge.util.parseUrl('http://example.com/foo?bar=baz');
+// parsed.scheme, parsed.host, parsed.port, parsed.path, parsed.fullHost
+```
+
+<a name="log" />
+### Logging
+
+Provides logging to a javascript console using various categories and
+levels of verbosity.
+
+__Examples__
+
+```js
+```
+
+<a name="debug" />
+### Debugging
+
+Provides storage of debugging information normally inaccessible in
+closures for viewing/investigation.
+
+__Examples__
+
+```js
+```
+
+<a name="fsp" />
+### Flash Socket Policy Module
+
+Provides an [Apache][] module "mod_fsp" that can serve up a Flash Socket
+Policy. See `mod_fsp/README` for more details. This module makes it easy to
+modify an [Apache][] server to allow cross domain requests to be made to it.
+
+
+Library Details
+---------------
+
+* http://digitalbazaar.com/2010/07/20/javascript-tls-1/
+* http://digitalbazaar.com/2010/07/20/javascript-tls-2/
+
+Contact
+-------
+
+* Code: https://github.com/digitalbazaar/forge
+* Bugs: https://github.com/digitalbazaar/forge/issues
+* Email: support@digitalbazaar.com
+
+Donations welcome:
+
+* Donate: paypal@digitalbazaar.com
+
+[AES]: http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
+[ASN.1]: http://en.wikipedia.org/wiki/ASN.1
+[Apache]: http://httpd.apache.org/
+[CFB]: http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation
+[CBC]: http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation
+[CTR]: http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation
+[3DES]: http://en.wikipedia.org/wiki/Triple_DES
+[DES]: http://en.wikipedia.org/wiki/Data_Encryption_Standard
+[ECB]: http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation
+[Fortuna]: http://en.wikipedia.org/wiki/Fortuna_(PRNG)
+[GCM]: http://en.wikipedia.org/wiki/GCM_mode
+[HMAC]: http://en.wikipedia.org/wiki/HMAC
+[JavaScript]: http://en.wikipedia.org/wiki/JavaScript
+[MD5]: http://en.wikipedia.org/wiki/MD5
+[OFB]: http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation
+[PKCS#5]: http://en.wikipedia.org/wiki/PKCS
+[PKCS#7]: http://en.wikipedia.org/wiki/Cryptographic_Message_Syntax
+[PKCS#10]: http://en.wikipedia.org/wiki/Certificate_signing_request
+[PKCS#12]: http://en.wikipedia.org/wiki/PKCS_%E2%99%AF12
+[RC2]: http://en.wikipedia.org/wiki/RC2
+[SHA-1]: http://en.wikipedia.org/wiki/SHA-1
+[SHA-256]: http://en.wikipedia.org/wiki/SHA-256
+[SHA-384]: http://en.wikipedia.org/wiki/SHA-384
+[SHA-512]: http://en.wikipedia.org/wiki/SHA-512
+[TLS]: http://en.wikipedia.org/wiki/Transport_Layer_Security
+[X.509]: http://en.wikipedia.org/wiki/X.509
+[node.js]: http://nodejs.org/
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/bower.json b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/bower.json
new file mode 100644
index 0000000..c65a95e
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/bower.json
@@ -0,0 +1,15 @@
+{
+  "name": "forge",
+  "version": "0.6.21",
+  "description": "JavaScript implementations of network transports, cryptography, ciphers, PKI, message digests, and various utilities.",
+  "authors": [
+    "Digital Bazaar, Inc."
+  ],
+  "license": "BSD",
+  "main": ["js/forge.js"],
+  "dependencies": {},
+  "ignore": [
+    "node_modules",
+    "bower_components"
+  ]
+}
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/build-flash.xml b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/build-flash.xml
new file mode 100644
index 0000000..8d8829c
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/build-flash.xml
@@ -0,0 +1,7 @@
+<flex-config>
+   <compiler>
+      <source-path>
+         <path-element>flash</path-element>
+      </source-path>
+   </compiler>
+</flex-config>
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/build-setup b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/build-setup
new file mode 100755
index 0000000..5c3866e
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/build-setup
@@ -0,0 +1,46 @@
+#!/bin/sh
+#
+# This shell script sets up the software to be built using 'make'. In 
+# order to perform a build from a fresh source tree, do the following:
+#
+# 1. ./build-setup
+# 2. make
+#
+# If you don't want ./configure to be run automatically, you can do
+# the following: ./build-setup -s
+
+# Process command line options
+SKIP_CONFIGURE=0
+for arg in "$*"
+do
+   case $arg in
+      "-s" | "--setup-only" ) SKIP_CONFIGURE=1 ;;
+   esac
+done
+
+# Check and add potential aclocal dirs
+MAYBE_AC_DIRS="
+   /usr/local/share/aclocal
+   /opt/local/share/aclocal
+   /sw/share/aclocal
+   "
+ACDIRS="-I m4"
+for dir in $MAYBE_AC_DIRS; do
+   if test -d $dir; then
+      ACDIRS="$ACDIRS -I $dir"
+   fi
+done
+
+# Run aclocal on the set of local ac scripts
+cd setup
+aclocal $ACDIRS
+# Generate the configure script
+autoconf && mv configure ..
+cd ..
+
+# Run the configure script if "-s" isn't a command line option
+if [ $SKIP_CONFIGURE -eq 0 ]; then
+   # Run the configure script in default development mode
+   ./configure $*
+fi
+
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/end.frag b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/end.frag
new file mode 100644
index 0000000..cbcf226
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/end.frag
@@ -0,0 +1,4 @@
+
+return require('js/forge');
+
+});
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/flash/PooledSocket.as b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/flash/PooledSocket.as
new file mode 100644
index 0000000..15e3ae4
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/flash/PooledSocket.as
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2009 Digital Bazaar, Inc. All rights reserved.
+ * 
+ * @author Dave Longley
+ */
+package
+{
+   import flash.net.Socket;
+   
+   /**
+    * A helper class that contains the ID for a Socket.
+    */
+   public class PooledSocket extends Socket
+   {
+      // the ID in the related socket pool
+      public var id:String;
+   }
+}
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/flash/SocketEvent.as b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/flash/SocketEvent.as
new file mode 100644
index 0000000..56f5e7f
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/flash/SocketEvent.as
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2009 Digital Bazaar, Inc. All rights reserved.
+ * 
+ * @author Dave Longley
+ */
+package
+{
+   import flash.events.Event;
+   
+   /**
+    * A helper class that contains the ID for a Socket.
+    */
+   public class SocketEvent extends Event
+   {
+      // the associated socket
+      public var socket:PooledSocket;
+      // an associated message
+      public var message:String;
+      
+      /**
+       * Creates a new SocketEvent.
+       * 
+       * @param type the type of event.
+       * @param socket the associated PooledSocket.
+       * @param message an associated message.
+       */
+      public function SocketEvent(
+         type:String, socket:PooledSocket, message:String = null)
+      {
+         super(type, false, false);
+         this.socket = socket;
+         this.message = message;
+      }
+   }
+}
+
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/flash/SocketPool.as b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/flash/SocketPool.as
new file mode 100644
index 0000000..d99b3ec
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/flash/SocketPool.as
@@ -0,0 +1,754 @@
+/*
+ * Copyright (c) 2009-2010 Digital Bazaar, Inc. All rights reserved.
+ * 
+ * @author Dave Longley
+ */
+package
+{
+   import flash.display.Sprite;
+   
+   /**
+    * A SocketPool is a flash object that can be embedded in a web page to
+    * allow javascript access to pools of Sockets.
+    * 
+    * Javascript can create a pool and then as many Sockets as it desires. Each
+    * Socket will be assigned a unique ID that allows continued javascript
+    * access to it. There is no limit on the number of persistent socket
+    * connections.
+    */
+   public class SocketPool extends Sprite
+   {
+      import flash.events.Event;
+      import flash.events.EventDispatcher;
+      import flash.errors.IOError;
+      import flash.events.IOErrorEvent;
+      import flash.events.ProgressEvent;
+      import flash.events.SecurityErrorEvent;
+      import flash.events.TextEvent;
+      import flash.external.ExternalInterface;
+      import flash.net.SharedObject;
+      import flash.system.Security;
+      import flash.utils.ByteArray;
+      import mx.utils.Base64Decoder;
+      import mx.utils.Base64Encoder;
+      
+      // a map of ID to Socket
+      private var mSocketMap:Object;
+      
+      // a counter for Socket IDs (Note: assumes there will be no overflow)
+      private var mNextId:uint;
+      
+      // an event dispatcher for sending events to javascript
+      private var mEventDispatcher:EventDispatcher;
+      
+      /**
+       * Creates a new, unitialized SocketPool.
+       * 
+       * @throws Error - if no external interface is available to provide
+       *                 javascript access.
+       */
+      public function SocketPool()
+      {
+         if(!ExternalInterface.available)
+         {
+            trace("ExternalInterface is not available");
+            throw new Error(
+               "Flash's ExternalInterface is not available. This is a " +
+               "requirement of SocketPool and therefore, it will be " +
+               "unavailable.");
+         }
+         else
+         {
+            try
+            {
+               // set up javascript access:
+               
+               // initializes/cleans up the SocketPool
+               ExternalInterface.addCallback("init", init);
+               ExternalInterface.addCallback("cleanup", cleanup);
+               
+               // creates/destroys a socket
+               ExternalInterface.addCallback("create", create);
+               ExternalInterface.addCallback("destroy", destroy);
+               
+               // connects/closes a socket
+               ExternalInterface.addCallback("connect", connect);
+               ExternalInterface.addCallback("close", close);
+               
+               // checks for a connection
+               ExternalInterface.addCallback("isConnected", isConnected);
+               
+               // sends/receives data over the socket
+               ExternalInterface.addCallback("send", send);
+               ExternalInterface.addCallback("receive", receive);
+               
+               // gets the number of bytes available on a socket
+               ExternalInterface.addCallback(
+                  "getBytesAvailable", getBytesAvailable);
+               
+               // add a callback for subscribing to socket events
+               ExternalInterface.addCallback("subscribe", subscribe);
+               
+               // add callbacks for deflate/inflate
+               ExternalInterface.addCallback("deflate", deflate);
+               ExternalInterface.addCallback("inflate", inflate);
+               
+               // add callbacks for local disk storage
+               ExternalInterface.addCallback("setItem", setItem);
+               ExternalInterface.addCallback("getItem", getItem);
+               ExternalInterface.addCallback("removeItem", removeItem);
+               ExternalInterface.addCallback("clearItems", clearItems);
+               
+               // socket pool is now ready
+               ExternalInterface.call("window.forge.socketPool.ready");
+            }
+            catch(e:Error)
+            {
+               log("error=" + e.errorID + "," + e.name + "," + e.message);
+               throw e;
+            }
+            
+            log("ready");
+         }
+      }
+      
+      /**
+       * A debug logging function.
+       * 
+       * @param obj the string or error to log.
+       */
+      CONFIG::debugging
+      private function log(obj:Object):void
+      {
+         if(obj is String)
+         {
+            var str:String = obj as String;
+            ExternalInterface.call("console.log", "SocketPool", str);
+         }
+         else if(obj is Error)
+         {
+            var e:Error = obj as Error;
+            log("error=" + e.errorID + "," + e.name + "," + e.message);
+         }
+      }
+      
+      CONFIG::release
+      private function log(obj:Object):void
+      {
+         // log nothing in release mode
+      }
+      
+      /**
+       * Called by javascript to initialize this SocketPool.
+       * 
+       * @param options:
+       *        marshallExceptions: true to pass exceptions to and from
+       *           javascript.
+       */
+      private function init(... args):void
+      {
+         log("init()");
+         
+         // get options from first argument
+         var options:Object = args.length > 0 ? args[0] : null;
+         
+         // create socket map, set next ID, and create event dispatcher
+         mSocketMap = new Object();
+         mNextId = 1;
+         mEventDispatcher = new EventDispatcher();
+         
+         // enable marshalling exceptions if appropriate
+         if(options != null &&
+            "marshallExceptions" in options &&
+            options.marshallExceptions === true)
+         {
+            try
+            {
+               // Note: setting marshallExceptions in IE, even inside of a
+               // try-catch block will terminate flash. Don't set this on IE.
+               ExternalInterface.marshallExceptions = true;
+            }
+            catch(e:Error)
+            {
+               log(e);
+            }
+         }
+         
+         log("init() done");
+      }
+      
+      /**
+       * Called by javascript to clean up a SocketPool.
+       */
+      private function cleanup():void
+      {
+         log("cleanup()");
+         
+         mSocketMap = null;
+         mNextId = 1;
+         mEventDispatcher = null;
+         
+         log("cleanup() done");
+      }
+      
+      /**
+       * Handles events.
+       * 
+       * @param e the event to handle.
+       */
+      private function handleEvent(e:Event):void
+      {
+         // dispatch socket event
+         var message:String = (e is TextEvent) ? (e as TextEvent).text : null;
+         mEventDispatcher.dispatchEvent(
+            new SocketEvent(e.type, e.target as PooledSocket, message));
+      }
+      
+      /**
+       * Called by javascript to create a Socket.
+       * 
+       * @return the Socket ID.
+       */
+      private function create():String
+      {
+         log("create()");
+         
+         // create a Socket
+         var id:String = "" + mNextId++;
+         var s:PooledSocket = new PooledSocket();
+         s.id = id;
+         s.addEventListener(Event.CONNECT, handleEvent);
+         s.addEventListener(Event.CLOSE, handleEvent);
+         s.addEventListener(ProgressEvent.SOCKET_DATA, handleEvent);
+         s.addEventListener(IOErrorEvent.IO_ERROR, handleEvent);
+         s.addEventListener(SecurityErrorEvent.SECURITY_ERROR, handleEvent);
+         mSocketMap[id] = s;
+         
+         log("socket " + id + " created");
+         log("create() done");
+         
+         return id;
+      }
+      
+      /**
+       * Called by javascript to clean up a Socket.
+       * 
+       * @param id the ID of the Socket to clean up.
+       */
+      private function destroy(id:String):void
+      {
+         log("destroy(" + id + ")");
+         
+         if(id in mSocketMap)
+         {
+            // remove Socket
+            delete mSocketMap[id];
+            log("socket " + id + " destroyed");
+         }
+         
+         log("destroy(" + id + ") done");
+      }
+      
+      /**
+       * Connects the Socket with the given ID to the given host and port,
+       * using the given socket policy port.
+       *
+       * @param id the ID of the Socket.
+       * @param host the host to connect to.
+       * @param port the port to connect to.
+       * @param spPort the security policy port to use, 0 to use a url.
+       * @param spUrl the http URL to the policy file to use, null for default.
+       */
+      private function connect(
+         id:String, host:String, port:uint, spPort:uint,
+         spUrl:String = null):void
+      {
+         log("connect(" +
+            id + "," + host + "," + port + "," + spPort + "," + spUrl + ")");
+         
+         if(id in mSocketMap)
+         {
+            // get the Socket
+            var s:PooledSocket = mSocketMap[id];
+            
+            // load socket policy file
+            // (permits socket access to backend)
+            if(spPort !== 0)
+            {
+               spUrl = "xmlsocket://" + host + ":" + spPort;
+               log("using cross-domain url: " + spUrl);
+               Security.loadPolicyFile(spUrl);
+            }
+            else if(spUrl !== null && typeof(spUrl) !== undefined)
+            {
+               log("using cross-domain url: " + spUrl);
+               Security.loadPolicyFile(spUrl);
+            }
+            else
+            {
+               log("not loading any cross-domain url");
+            }
+            
+            // connect
+            s.connect(host, port);
+         }
+         else
+         {
+            // no such socket
+            log("socket " + id + " does not exist");
+         }
+         
+         log("connect(" + id + ") done");
+      }
+      
+      /**
+       * Closes the Socket with the given ID.
+       *
+       * @param id the ID of the Socket.
+       */
+      private function close(id:String):void
+      {
+         log("close(" + id + ")");
+         
+         if(id in mSocketMap)
+         {
+            // close the Socket
+            var s:PooledSocket = mSocketMap[id];
+            if(s.connected)
+            {
+               s.close();
+            }
+         }
+         else
+         {
+            // no such socket
+            log("socket " + id + " does not exist");
+         }
+         
+         log("close(" + id + ") done");
+      }
+      
+      /**
+       * Determines if the Socket with the given ID is connected or not.
+       *
+       * @param id the ID of the Socket.
+       *
+       * @return true if the socket is connected, false if not.
+       */
+      private function isConnected(id:String):Boolean
+      {
+         var rval:Boolean = false;
+         log("isConnected(" + id + ")");
+         
+         if(id in mSocketMap)
+         {
+            // check the Socket
+            var s:PooledSocket = mSocketMap[id];
+            rval = s.connected;
+         }
+         else
+         {
+            // no such socket
+            log("socket " + id + " does not exist");
+         }
+         
+         log("isConnected(" + id + ") done");
+         return rval;
+      }
+      
+      /**
+       * Writes bytes to a Socket.
+       *
+       * @param id the ID of the Socket.
+       * @param bytes the string of base64-encoded bytes to write.
+       *
+       * @return true on success, false on failure. 
+       */
+      private function send(id:String, bytes:String):Boolean
+      {
+         var rval:Boolean = false;
+         log("send(" + id + ")");
+         
+         if(id in mSocketMap)
+         {
+         	// write bytes to socket
+            var s:PooledSocket = mSocketMap[id];
+            try
+            {
+               var b64:Base64Decoder = new Base64Decoder();
+               b64.decode(bytes);
+               var b:ByteArray = b64.toByteArray();
+               s.writeBytes(b, 0, b.length);
+               s.flush();
+               rval = true;
+            }
+            catch(e:IOError)
+            {
+               log(e);
+               
+               // dispatch IO error event
+               mEventDispatcher.dispatchEvent(new SocketEvent(
+                  IOErrorEvent.IO_ERROR, s, e.message));
+               if(s.connected)
+               {
+                  s.close();
+               }
+            }
+         }
+         else
+         {
+            // no such socket
+            log("socket " + id + " does not exist");
+         }
+         
+         log("send(" + id + ") done");
+         return rval;
+      }
+      
+      /**
+       * Receives bytes from a Socket.
+       *
+       * @param id the ID of the Socket.
+       * @param count the maximum number of bytes to receive.
+       *
+       * @return an object with 'rval' set to the received bytes,
+       *         base64-encoded, or set to null on error.
+       */
+      private function receive(id:String, count:uint):Object
+      {
+      	 var rval:String = null;
+         log("receive(" + id + "," + count + ")");
+         
+         if(id in mSocketMap)
+         {
+         	// only read what is available
+            var s:PooledSocket = mSocketMap[id];
+            if(count > s.bytesAvailable)
+            {
+               count = s.bytesAvailable;
+            }
+            
+            try
+            {
+               // read bytes from socket
+               var b:ByteArray = new ByteArray();
+               s.readBytes(b, 0, count);
+               b.position = 0;
+               var b64:Base64Encoder = new Base64Encoder();
+               b64.insertNewLines = false;
+               b64.encodeBytes(b, 0, b.length);
+               rval = b64.toString();
+            }
+            catch(e:IOError)
+            {
+               log(e);
+               
+               // dispatch IO error event
+               mEventDispatcher.dispatchEvent(new SocketEvent(
+                  IOErrorEvent.IO_ERROR, s, e.message));
+               if(s.connected)
+               {
+                  s.close();
+               }
+            }
+         }
+         else
+         {
+            // no such socket
+            log("socket " + id + " does not exist");
+         }
+         
+         log("receive(" + id + "," + count + ") done");
+         return {rval: rval};
+      }
+      
+      /**
+       * Gets the number of bytes available from a Socket.
+       *
+       * @param id the ID of the Socket.
+       *
+       * @return the number of available bytes.
+       */
+      private function getBytesAvailable(id:String):uint
+      {
+         var rval:uint = 0;
+         log("getBytesAvailable(" + id + ")");
+         
+         if(id in mSocketMap)
+         {
+            var s:PooledSocket = mSocketMap[id];
+            rval = s.bytesAvailable;
+         }
+         else
+         {
+            // no such socket
+            log("socket " + id + " does not exist");
+         }
+         
+         log("getBytesAvailable(" + id +") done");
+         return rval;
+      }      
+      
+      /**
+       * Registers a javascript function as a callback for an event.
+       * 
+       * @param eventType the type of event (socket event types).
+       * @param callback the name of the callback function.
+       */
+      private function subscribe(eventType:String, callback:String):void
+      {
+         log("subscribe(" + eventType + "," + callback + ")");
+         
+         switch(eventType)
+         {
+            case Event.CONNECT:
+            case Event.CLOSE:
+            case IOErrorEvent.IO_ERROR:
+            case SecurityErrorEvent.SECURITY_ERROR:
+            case ProgressEvent.SOCKET_DATA:
+            {
+               log(eventType + " => " + callback);
+               mEventDispatcher.addEventListener(
+                  eventType, function(event:SocketEvent):void
+               {
+                  log("event dispatched: " + eventType);
+                  
+                  // build event for javascript
+                  var e:Object = new Object();
+                  e.id = event.socket ? event.socket.id : 0;
+                  e.type = eventType;
+                  if(event.socket && event.socket.connected)
+                  {
+                     e.bytesAvailable = event.socket.bytesAvailable;
+                  }
+                  else
+                  {
+                     e.bytesAvailable = 0;
+                  }
+                  if(event.message)
+                  {
+                     e.message = event.message;
+                  }
+                  
+                  // send event to javascript
+                  ExternalInterface.call(callback, e);
+               });
+               break;
+            }
+            default:
+               throw new ArgumentError(
+                  "Could not subscribe to event. " +
+                  "Invalid event type specified: " + eventType);
+         }
+         
+         log("subscribe(" + eventType + "," + callback + ") done");
+      }
+      
+      /**
+       * Deflates the given data.
+       * 
+       * @param data the base64-encoded data to deflate.
+       * 
+       * @return an object with 'rval' set to deflated data, base64-encoded.
+       */
+      private function deflate(data:String):Object
+      {
+         log("deflate");
+         
+         var b64d:Base64Decoder = new Base64Decoder();
+         b64d.decode(data);
+         var b:ByteArray = b64d.toByteArray();
+         b.compress();
+         b.position = 0;
+         var b64e:Base64Encoder = new Base64Encoder();
+         b64e.insertNewLines = false;
+         b64e.encodeBytes(b, 0, b.length);
+         
+         log("deflate done");
+         return {rval: b64e.toString()};
+      }
+      
+      /**
+       * Inflates the given data.
+       * 
+       * @param data the base64-encoded data to inflate.
+       * 
+       * @return an object with 'rval' set to the inflated data,
+       *         base64-encoded, null on error.
+       */
+      private function inflate(data:String):Object
+      {
+         log("inflate");
+         var rval:Object = {rval: null};
+      	 
+      	 try
+      	 {
+            var b64d:Base64Decoder = new Base64Decoder();
+            b64d.decode(data);
+            var b:ByteArray = b64d.toByteArray();
+            b.uncompress();
+            b.position = 0;
+            var b64e:Base64Encoder = new Base64Encoder();
+            b64e.insertNewLines = false;
+            b64e.encodeBytes(b, 0, b.length);
+            rval.rval = b64e.toString();
+         }
+         catch(e:Error)
+         {
+         	log(e);
+            rval.error = {
+                id: e.errorID,
+                name: e.name,
+                message: e.message
+            };
+         }
+         
+         log("inflate done");
+         return rval;
+      }
+      
+      /**
+       * Stores an item with a key and arbitrary base64-encoded data on local
+       * disk.
+       * 
+       * @param key the key for the item.
+       * @param data the base64-encoded item data.
+       * @param storeId the storage ID to use, defaults to "forge.storage".
+       * 
+       * @return an object with rval set to true on success, false on failure
+       *         with error included.
+       */
+      private function setItem(
+         key:String, data:String, storeId:String = "forge.storage"):Object
+      {
+         var rval:Object = {rval: false};
+         try
+         {
+            var store:SharedObject = SharedObject.getLocal(storeId);
+            if(!('keys' in store.data))
+            {
+               store.data.keys = {};
+            }
+            store.data.keys[key] = data;
+            store.flush();
+            rval.rval = true;
+         }
+         catch(e:Error)
+         {
+         	log(e);
+         	rval.error = {
+                id: e.errorID,
+                name: e.name,
+                message: e.message
+            };
+         }
+         return rval;
+      }
+      
+      /**
+       * Gets an item from the local disk.
+       * 
+       * @param key the key for the item.
+       * @param storeId the storage ID to use, defaults to "forge.storage".
+       * 
+       * @return an object with rval set to the item data (which may be null),
+       *         check for error object if null.
+       */
+      private function getItem(
+         key:String, storeId:String = "forge.storage"):Object
+      {
+         var rval:Object = {rval: null};
+         try
+         {
+            var store:SharedObject = SharedObject.getLocal(storeId);
+            if('keys' in store.data && key in store.data.keys)
+            {
+               rval.rval = store.data.keys[key];
+            }
+         }
+         catch(e:Error)
+         {
+         	log(e);
+            rval.error = {
+                id: e.errorID,
+                name: e.name,
+                message: e.message
+            };
+         }
+         return rval;
+      }
+      
+      /**
+       * Removes an item from the local disk.
+       * 
+       * @param key the key for the item.
+       * @param storeId the storage ID to use, defaults to "forge.storage".
+       * 
+       * @return an object with rval set to true if removed, false if not.
+       */
+      private function removeItem(
+         key:String, storeId:String = "forge.storage"):Object
+      {
+         var rval:Object = {rval: false};
+         try
+         {
+            var store:SharedObject = SharedObject.getLocal(storeId);
+            if('keys' in store.data && key in store.data.keys)
+            {
+               delete store.data.keys[key];
+               
+               // clean up storage entirely if empty
+               var empty:Boolean = true;
+               for(var prop:String in store.data.keys)
+               {
+                  empty = false;
+                  break;
+               }
+               if(empty)
+               {
+                  store.clear();
+               }
+               rval.rval = true;
+            }
+         }
+         catch(e:Error)
+         {
+            log(e);
+            rval.error = {
+                id: e.errorID,
+                name: e.name,
+                message: e.message
+            };
+         }
+         return rval;
+      }
+      
+      /**
+       * Clears an entire store of all of its items.
+       * 
+       * @param storeId the storage ID to use, defaults to "forge.storage".
+       * 
+       * @return an object with rval set to true if cleared, false if not.
+       */
+      private function clearItems(storeId:String = "forge.storage"):Object
+      {
+         var rval:Object = {rval: false};
+         try
+         {
+            var store:SharedObject = SharedObject.getLocal(storeId);
+            store.clear();
+            rval.rval = true;
+         }
+         catch(e:Error)
+         {
+            log(e);
+            rval.error = {
+                id: e.errorID,
+                name: e.name,
+                message: e.message
+            };
+         }
+         return rval;
+      }
+   }
+}
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/aes.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/aes.js
new file mode 100644
index 0000000..d4b745b
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/aes.js
@@ -0,0 +1,1146 @@
+/**
+ * Advanced Encryption Standard (AES) implementation.
+ *
+ * This implementation is based on the public domain library 'jscrypto' which
+ * was written by:
+ *
+ * Emily Stark (estark@stanford.edu)
+ * Mike Hamburg (mhamburg@stanford.edu)
+ * Dan Boneh (dabo@cs.stanford.edu)
+ *
+ * Parts of this code are based on the OpenSSL implementation of AES:
+ * http://www.openssl.org
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+/* AES API */
+forge.aes = forge.aes || {};
+
+/**
+ * Deprecated. Instead, use:
+ *
+ * var cipher = forge.cipher.createCipher('AES-<mode>', key);
+ * cipher.start({iv: iv});
+ *
+ * Creates an AES cipher object to encrypt data using the given symmetric key.
+ * The output will be stored in the 'output' member of the returned cipher.
+ *
+ * The key and iv may be given as a string of bytes, an array of bytes,
+ * a byte buffer, or an array of 32-bit words.
+ *
+ * @param key the symmetric key to use.
+ * @param iv the initialization vector to use.
+ * @param output the buffer to write to, null to create one.
+ * @param mode the cipher mode to use (default: 'CBC').
+ *
+ * @return the cipher.
+ */
+forge.aes.startEncrypting = function(key, iv, output, mode) {
+  var cipher = _createCipher({
+    key: key,
+    output: output,
+    decrypt: false,
+    mode: mode
+  });
+  cipher.start(iv);
+  return cipher;
+};
+
+/**
+ * Deprecated. Instead, use:
+ *
+ * var cipher = forge.cipher.createCipher('AES-<mode>', key);
+ *
+ * Creates an AES cipher object to encrypt data using the given symmetric key.
+ *
+ * The key may be given as a string of bytes, an array of bytes, a
+ * byte buffer, or an array of 32-bit words.
+ *
+ * @param key the symmetric key to use.
+ * @param mode the cipher mode to use (default: 'CBC').
+ *
+ * @return the cipher.
+ */
+forge.aes.createEncryptionCipher = function(key, mode) {
+  return _createCipher({
+    key: key,
+    output: null,
+    decrypt: false,
+    mode: mode
+  });
+};
+
+/**
+ * Deprecated. Instead, use:
+ *
+ * var decipher = forge.cipher.createDecipher('AES-<mode>', key);
+ * decipher.start({iv: iv});
+ *
+ * Creates an AES cipher object to decrypt data using the given symmetric key.
+ * The output will be stored in the 'output' member of the returned cipher.
+ *
+ * The key and iv may be given as a string of bytes, an array of bytes,
+ * a byte buffer, or an array of 32-bit words.
+ *
+ * @param key the symmetric key to use.
+ * @param iv the initialization vector to use.
+ * @param output the buffer to write to, null to create one.
+ * @param mode the cipher mode to use (default: 'CBC').
+ *
+ * @return the cipher.
+ */
+forge.aes.startDecrypting = function(key, iv, output, mode) {
+  var cipher = _createCipher({
+    key: key,
+    output: output,
+    decrypt: true,
+    mode: mode
+  });
+  cipher.start(iv);
+  return cipher;
+};
+
+/**
+ * Deprecated. Instead, use:
+ *
+ * var decipher = forge.cipher.createDecipher('AES-<mode>', key);
+ *
+ * Creates an AES cipher object to decrypt data using the given symmetric key.
+ *
+ * The key may be given as a string of bytes, an array of bytes, a
+ * byte buffer, or an array of 32-bit words.
+ *
+ * @param key the symmetric key to use.
+ * @param mode the cipher mode to use (default: 'CBC').
+ *
+ * @return the cipher.
+ */
+forge.aes.createDecryptionCipher = function(key, mode) {
+  return _createCipher({
+    key: key,
+    output: null,
+    decrypt: true,
+    mode: mode
+  });
+};
+
+/**
+ * Creates a new AES cipher algorithm object.
+ *
+ * @param name the name of the algorithm.
+ * @param mode the mode factory function.
+ *
+ * @return the AES algorithm object.
+ */
+forge.aes.Algorithm = function(name, mode) {
+  if(!init) {
+    initialize();
+  }
+  var self = this;
+  self.name = name;
+  self.mode = new mode({
+    blockSize: 16,
+    cipher: {
+      encrypt: function(inBlock, outBlock) {
+        return _updateBlock(self._w, inBlock, outBlock, false);
+      },
+      decrypt: function(inBlock, outBlock) {
+        return _updateBlock(self._w, inBlock, outBlock, true);
+      }
+    }
+  });
+  self._init = false;
+};
+
+/**
+ * Initializes this AES algorithm by expanding its key.
+ *
+ * @param options the options to use.
+ *          key the key to use with this algorithm.
+ *          decrypt true if the algorithm should be initialized for decryption,
+ *            false for encryption.
+ */
+forge.aes.Algorithm.prototype.initialize = function(options) {
+  if(this._init) {
+    return;
+  }
+
+  var key = options.key;
+  var tmp;
+
+  /* Note: The key may be a string of bytes, an array of bytes, a byte
+    buffer, or an array of 32-bit integers. If the key is in bytes, then
+    it must be 16, 24, or 32 bytes in length. If it is in 32-bit
+    integers, it must be 4, 6, or 8 integers long. */
+
+  if(typeof key === 'string' &&
+    (key.length === 16 || key.length === 24 || key.length === 32)) {
+    // convert key string into byte buffer
+    key = forge.util.createBuffer(key);
+  } else if(forge.util.isArray(key) &&
+    (key.length === 16 || key.length === 24 || key.length === 32)) {
+    // convert key integer array into byte buffer
+    tmp = key;
+    key = forge.util.createBuffer();
+    for(var i = 0; i < tmp.length; ++i) {
+      key.putByte(tmp[i]);
+    }
+  }
+
+  // convert key byte buffer into 32-bit integer array
+  if(!forge.util.isArray(key)) {
+    tmp = key;
+    key = [];
+
+    // key lengths of 16, 24, 32 bytes allowed
+    var len = tmp.length();
+    if(len === 16 || len === 24 || len === 32) {
+      len = len >>> 2;
+      for(var i = 0; i < len; ++i) {
+        key.push(tmp.getInt32());
+      }
+    }
+  }
+
+  // key must be an array of 32-bit integers by now
+  if(!forge.util.isArray(key) ||
+    !(key.length === 4 || key.length === 6 || key.length === 8)) {
+    throw new Error('Invalid key parameter.');
+  }
+
+  // encryption operation is always used for these modes
+  var mode = this.mode.name;
+  var encryptOp = (['CFB', 'OFB', 'CTR', 'GCM'].indexOf(mode) !== -1);
+
+  // do key expansion
+  this._w = _expandKey(key, options.decrypt && !encryptOp);
+  this._init = true;
+};
+
+/**
+ * Expands a key. Typically only used for testing.
+ *
+ * @param key the symmetric key to expand, as an array of 32-bit words.
+ * @param decrypt true to expand for decryption, false for encryption.
+ *
+ * @return the expanded key.
+ */
+forge.aes._expandKey = function(key, decrypt) {
+  if(!init) {
+    initialize();
+  }
+  return _expandKey(key, decrypt);
+};
+
+/**
+ * Updates a single block. Typically only used for testing.
+ *
+ * @param w the expanded key to use.
+ * @param input an array of block-size 32-bit words.
+ * @param output an array of block-size 32-bit words.
+ * @param decrypt true to decrypt, false to encrypt.
+ */
+forge.aes._updateBlock = _updateBlock;
+
+
+/** Register AES algorithms **/
+
+registerAlgorithm('AES-CBC', forge.cipher.modes.cbc);
+registerAlgorithm('AES-CFB', forge.cipher.modes.cfb);
+registerAlgorithm('AES-OFB', forge.cipher.modes.ofb);
+registerAlgorithm('AES-CTR', forge.cipher.modes.ctr);
+registerAlgorithm('AES-GCM', forge.cipher.modes.gcm);
+
+function registerAlgorithm(name, mode) {
+  var factory = function() {
+    return new forge.aes.Algorithm(name, mode);
+  };
+  forge.cipher.registerAlgorithm(name, factory);
+}
+
+
+/** AES implementation **/
+
+var init = false; // not yet initialized
+var Nb = 4;       // number of words comprising the state (AES = 4)
+var sbox;         // non-linear substitution table used in key expansion
+var isbox;        // inversion of sbox
+var rcon;         // round constant word array
+var mix;          // mix-columns table
+var imix;         // inverse mix-columns table
+
+/**
+ * Performs initialization, ie: precomputes tables to optimize for speed.
+ *
+ * One way to understand how AES works is to imagine that 'addition' and
+ * 'multiplication' are interfaces that require certain mathematical
+ * properties to hold true (ie: they are associative) but they might have
+ * different implementations and produce different kinds of results ...
+ * provided that their mathematical properties remain true. AES defines
+ * its own methods of addition and multiplication but keeps some important
+ * properties the same, ie: associativity and distributivity. The
+ * explanation below tries to shed some light on how AES defines addition
+ * and multiplication of bytes and 32-bit words in order to perform its
+ * encryption and decryption algorithms.
+ *
+ * The basics:
+ *
+ * The AES algorithm views bytes as binary representations of polynomials
+ * that have either 1 or 0 as the coefficients. It defines the addition
+ * or subtraction of two bytes as the XOR operation. It also defines the
+ * multiplication of two bytes as a finite field referred to as GF(2^8)
+ * (Note: 'GF' means "Galois Field" which is a field that contains a finite
+ * number of elements so GF(2^8) has 256 elements).
+ *
+ * This means that any two bytes can be represented as binary polynomials;
+ * when they multiplied together and modularly reduced by an irreducible
+ * polynomial of the 8th degree, the results are the field GF(2^8). The
+ * specific irreducible polynomial that AES uses in hexadecimal is 0x11b.
+ * This multiplication is associative with 0x01 as the identity:
+ *
+ * (b * 0x01 = GF(b, 0x01) = b).
+ *
+ * The operation GF(b, 0x02) can be performed at the byte level by left
+ * shifting b once and then XOR'ing it (to perform the modular reduction)
+ * with 0x11b if b is >= 128. Repeated application of the multiplication
+ * of 0x02 can be used to implement the multiplication of any two bytes.
+ *
+ * For instance, multiplying 0x57 and 0x13, denoted as GF(0x57, 0x13), can
+ * be performed by factoring 0x13 into 0x01, 0x02, and 0x10. Then these
+ * factors can each be multiplied by 0x57 and then added together. To do
+ * the multiplication, values for 0x57 multiplied by each of these 3 factors
+ * can be precomputed and stored in a table. To add them, the values from
+ * the table are XOR'd together.
+ *
+ * AES also defines addition and multiplication of words, that is 4-byte
+ * numbers represented as polynomials of 3 degrees where the coefficients
+ * are the values of the bytes.
+ *
+ * The word [a0, a1, a2, a3] is a polynomial a3x^3 + a2x^2 + a1x + a0.
+ *
+ * Addition is performed by XOR'ing like powers of x. Multiplication
+ * is performed in two steps, the first is an algebriac expansion as
+ * you would do normally (where addition is XOR). But the result is
+ * a polynomial larger than 3 degrees and thus it cannot fit in a word. So
+ * next the result is modularly reduced by an AES-specific polynomial of
+ * degree 4 which will always produce a polynomial of less than 4 degrees
+ * such that it will fit in a word. In AES, this polynomial is x^4 + 1.
+ *
+ * The modular product of two polynomials 'a' and 'b' is thus:
+ *
+ * d(x) = d3x^3 + d2x^2 + d1x + d0
+ * with
+ * d0 = GF(a0, b0) ^ GF(a3, b1) ^ GF(a2, b2) ^ GF(a1, b3)
+ * d1 = GF(a1, b0) ^ GF(a0, b1) ^ GF(a3, b2) ^ GF(a2, b3)
+ * d2 = GF(a2, b0) ^ GF(a1, b1) ^ GF(a0, b2) ^ GF(a3, b3)
+ * d3 = GF(a3, b0) ^ GF(a2, b1) ^ GF(a1, b2) ^ GF(a0, b3)
+ *
+ * As a matrix:
+ *
+ * [d0] = [a0 a3 a2 a1][b0]
+ * [d1]   [a1 a0 a3 a2][b1]
+ * [d2]   [a2 a1 a0 a3][b2]
+ * [d3]   [a3 a2 a1 a0][b3]
+ *
+ * Special polynomials defined by AES (0x02 == {02}):
+ * a(x)    = {03}x^3 + {01}x^2 + {01}x + {02}
+ * a^-1(x) = {0b}x^3 + {0d}x^2 + {09}x + {0e}.
+ *
+ * These polynomials are used in the MixColumns() and InverseMixColumns()
+ * operations, respectively, to cause each element in the state to affect
+ * the output (referred to as diffusing).
+ *
+ * RotWord() uses: a0 = a1 = a2 = {00} and a3 = {01}, which is the
+ * polynomial x3.
+ *
+ * The ShiftRows() method modifies the last 3 rows in the state (where
+ * the state is 4 words with 4 bytes per word) by shifting bytes cyclically.
+ * The 1st byte in the second row is moved to the end of the row. The 1st
+ * and 2nd bytes in the third row are moved to the end of the row. The 1st,
+ * 2nd, and 3rd bytes are moved in the fourth row.
+ *
+ * More details on how AES arithmetic works:
+ *
+ * In the polynomial representation of binary numbers, XOR performs addition
+ * and subtraction and multiplication in GF(2^8) denoted as GF(a, b)
+ * corresponds with the multiplication of polynomials modulo an irreducible
+ * polynomial of degree 8. In other words, for AES, GF(a, b) will multiply
+ * polynomial 'a' with polynomial 'b' and then do a modular reduction by
+ * an AES-specific irreducible polynomial of degree 8.
+ *
+ * A polynomial is irreducible if its only divisors are one and itself. For
+ * the AES algorithm, this irreducible polynomial is:
+ *
+ * m(x) = x^8 + x^4 + x^3 + x + 1,
+ *
+ * or {01}{1b} in hexadecimal notation, where each coefficient is a bit:
+ * 100011011 = 283 = 0x11b.
+ *
+ * For example, GF(0x57, 0x83) = 0xc1 because
+ *
+ * 0x57 = 87  = 01010111 = x^6 + x^4 + x^2 + x + 1
+ * 0x85 = 131 = 10000101 = x^7 + x + 1
+ *
+ * (x^6 + x^4 + x^2 + x + 1) * (x^7 + x + 1)
+ * =  x^13 + x^11 + x^9 + x^8 + x^7 +
+ *    x^7 + x^5 + x^3 + x^2 + x +
+ *    x^6 + x^4 + x^2 + x + 1
+ * =  x^13 + x^11 + x^9 + x^8 + x^6 + x^5 + x^4 + x^3 + 1 = y
+ *    y modulo (x^8 + x^4 + x^3 + x + 1)
+ * =  x^7 + x^6 + 1.
+ *
+ * The modular reduction by m(x) guarantees the result will be a binary
+ * polynomial of less than degree 8, so that it can fit in a byte.
+ *
+ * The operation to multiply a binary polynomial b with x (the polynomial
+ * x in binary representation is 00000010) is:
+ *
+ * b_7x^8 + b_6x^7 + b_5x^6 + b_4x^5 + b_3x^4 + b_2x^3 + b_1x^2 + b_0x^1
+ *
+ * To get GF(b, x) we must reduce that by m(x). If b_7 is 0 (that is the
+ * most significant bit is 0 in b) then the result is already reduced. If
+ * it is 1, then we can reduce it by subtracting m(x) via an XOR.
+ *
+ * It follows that multiplication by x (00000010 or 0x02) can be implemented
+ * by performing a left shift followed by a conditional bitwise XOR with
+ * 0x1b. This operation on bytes is denoted by xtime(). Multiplication by
+ * higher powers of x can be implemented by repeated application of xtime().
+ *
+ * By adding intermediate results, multiplication by any constant can be
+ * implemented. For instance:
+ *
+ * GF(0x57, 0x13) = 0xfe because:
+ *
+ * xtime(b) = (b & 128) ? (b << 1 ^ 0x11b) : (b << 1)
+ *
+ * Note: We XOR with 0x11b instead of 0x1b because in javascript our
+ * datatype for b can be larger than 1 byte, so a left shift will not
+ * automatically eliminate bits that overflow a byte ... by XOR'ing the
+ * overflow bit with 1 (the extra one from 0x11b) we zero it out.
+ *
+ * GF(0x57, 0x02) = xtime(0x57) = 0xae
+ * GF(0x57, 0x04) = xtime(0xae) = 0x47
+ * GF(0x57, 0x08) = xtime(0x47) = 0x8e
+ * GF(0x57, 0x10) = xtime(0x8e) = 0x07
+ *
+ * GF(0x57, 0x13) = GF(0x57, (0x01 ^ 0x02 ^ 0x10))
+ *
+ * And by the distributive property (since XOR is addition and GF() is
+ * multiplication):
+ *
+ * = GF(0x57, 0x01) ^ GF(0x57, 0x02) ^ GF(0x57, 0x10)
+ * = 0x57 ^ 0xae ^ 0x07
+ * = 0xfe.
+ */
+function initialize() {
+  init = true;
+
+  /* Populate the Rcon table. These are the values given by
+    [x^(i-1),{00},{00},{00}] where x^(i-1) are powers of x (and x = 0x02)
+    in the field of GF(2^8), where i starts at 1.
+
+    rcon[0] = [0x00, 0x00, 0x00, 0x00]
+    rcon[1] = [0x01, 0x00, 0x00, 0x00] 2^(1-1) = 2^0 = 1
+    rcon[2] = [0x02, 0x00, 0x00, 0x00] 2^(2-1) = 2^1 = 2
+    ...
+    rcon[9]  = [0x1B, 0x00, 0x00, 0x00] 2^(9-1)  = 2^8 = 0x1B
+    rcon[10] = [0x36, 0x00, 0x00, 0x00] 2^(10-1) = 2^9 = 0x36
+
+    We only store the first byte because it is the only one used.
+  */
+  rcon = [0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36];
+
+  // compute xtime table which maps i onto GF(i, 0x02)
+  var xtime = new Array(256);
+  for(var i = 0; i < 128; ++i) {
+    xtime[i] = i << 1;
+    xtime[i + 128] = (i + 128) << 1 ^ 0x11B;
+  }
+
+  // compute all other tables
+  sbox = new Array(256);
+  isbox = new Array(256);
+  mix = new Array(4);
+  imix = new Array(4);
+  for(var i = 0; i < 4; ++i) {
+    mix[i] = new Array(256);
+    imix[i] = new Array(256);
+  }
+  var e = 0, ei = 0, e2, e4, e8, sx, sx2, me, ime;
+  for(var i = 0; i < 256; ++i) {
+    /* We need to generate the SubBytes() sbox and isbox tables so that
+      we can perform byte substitutions. This requires us to traverse
+      all of the elements in GF, find their multiplicative inverses,
+      and apply to each the following affine transformation:
+
+      bi' = bi ^ b(i + 4) mod 8 ^ b(i + 5) mod 8 ^ b(i + 6) mod 8 ^
+            b(i + 7) mod 8 ^ ci
+      for 0 <= i < 8, where bi is the ith bit of the byte, and ci is the
+      ith bit of a byte c with the value {63} or {01100011}.
+
+      It is possible to traverse every possible value in a Galois field
+      using what is referred to as a 'generator'. There are many
+      generators (128 out of 256): 3,5,6,9,11,82 to name a few. To fully
+      traverse GF we iterate 255 times, multiplying by our generator
+      each time.
+
+      On each iteration we can determine the multiplicative inverse for
+      the current element.
+
+      Suppose there is an element in GF 'e'. For a given generator 'g',
+      e = g^x. The multiplicative inverse of e is g^(255 - x). It turns
+      out that if use the inverse of a generator as another generator
+      it will produce all of the corresponding multiplicative inverses
+      at the same time. For this reason, we choose 5 as our inverse
+      generator because it only requires 2 multiplies and 1 add and its
+      inverse, 82, requires relatively few operations as well.
+
+      In order to apply the affine transformation, the multiplicative
+      inverse 'ei' of 'e' can be repeatedly XOR'd (4 times) with a
+      bit-cycling of 'ei'. To do this 'ei' is first stored in 's' and
+      'x'. Then 's' is left shifted and the high bit of 's' is made the
+      low bit. The resulting value is stored in 's'. Then 'x' is XOR'd
+      with 's' and stored in 'x'. On each subsequent iteration the same
+      operation is performed. When 4 iterations are complete, 'x' is
+      XOR'd with 'c' (0x63) and the transformed value is stored in 'x'.
+      For example:
+
+      s = 01000001
+      x = 01000001
+
+      iteration 1: s = 10000010, x ^= s
+      iteration 2: s = 00000101, x ^= s
+      iteration 3: s = 00001010, x ^= s
+      iteration 4: s = 00010100, x ^= s
+      x ^= 0x63
+
+      This can be done with a loop where s = (s << 1) | (s >> 7). However,
+      it can also be done by using a single 16-bit (in this case 32-bit)
+      number 'sx'. Since XOR is an associative operation, we can set 'sx'
+      to 'ei' and then XOR it with 'sx' left-shifted 1,2,3, and 4 times.
+      The most significant bits will flow into the high 8 bit positions
+      and be correctly XOR'd with one another. All that remains will be
+      to cycle the high 8 bits by XOR'ing them all with the lower 8 bits
+      afterwards.
+
+      At the same time we're populating sbox and isbox we can precompute
+      the multiplication we'll need to do to do MixColumns() later.
+    */
+
+    // apply affine transformation
+    sx = ei ^ (ei << 1) ^ (ei << 2) ^ (ei << 3) ^ (ei << 4);
+    sx = (sx >> 8) ^ (sx & 255) ^ 0x63;
+
+    // update tables
+    sbox[e] = sx;
+    isbox[sx] = e;
+
+    /* Mixing columns is done using matrix multiplication. The columns
+      that are to be mixed are each a single word in the current state.
+      The state has Nb columns (4 columns). Therefore each column is a
+      4 byte word. So to mix the columns in a single column 'c' where
+      its rows are r0, r1, r2, and r3, we use the following matrix
+      multiplication:
+
+      [2 3 1 1]*[r0,c]=[r'0,c]
+      [1 2 3 1] [r1,c] [r'1,c]
+      [1 1 2 3] [r2,c] [r'2,c]
+      [3 1 1 2] [r3,c] [r'3,c]
+
+      r0, r1, r2, and r3 are each 1 byte of one of the words in the
+      state (a column). To do matrix multiplication for each mixed
+      column c' we multiply the corresponding row from the left matrix
+      with the corresponding column from the right matrix. In total, we
+      get 4 equations:
+
+      r0,c' = 2*r0,c + 3*r1,c + 1*r2,c + 1*r3,c
+      r1,c' = 1*r0,c + 2*r1,c + 3*r2,c + 1*r3,c
+      r2,c' = 1*r0,c + 1*r1,c + 2*r2,c + 3*r3,c
+      r3,c' = 3*r0,c + 1*r1,c + 1*r2,c + 2*r3,c
+
+      As usual, the multiplication is as previously defined and the
+      addition is XOR. In order to optimize mixing columns we can store
+      the multiplication results in tables. If you think of the whole
+      column as a word (it might help to visualize by mentally rotating
+      the equations above by counterclockwise 90 degrees) then you can
+      see that it would be useful to map the multiplications performed on
+      each byte (r0, r1, r2, r3) onto a word as well. For instance, we
+      could map 2*r0,1*r0,1*r0,3*r0 onto a word by storing 2*r0 in the
+      highest 8 bits and 3*r0 in the lowest 8 bits (with the other two
+      respectively in the middle). This means that a table can be
+      constructed that uses r0 as an index to the word. We can do the
+      same with r1, r2, and r3, creating a total of 4 tables.
+
+      To construct a full c', we can just look up each byte of c in
+      their respective tables and XOR the results together.
+
+      Also, to build each table we only have to calculate the word
+      for 2,1,1,3 for every byte ... which we can do on each iteration
+      of this loop since we will iterate over every byte. After we have
+      calculated 2,1,1,3 we can get the results for the other tables
+      by cycling the byte at the end to the beginning. For instance
+      we can take the result of table 2,1,1,3 and produce table 3,2,1,1
+      by moving the right most byte to the left most position just like
+      how you can imagine the 3 moved out of 2,1,1,3 and to the front
+      to produce 3,2,1,1.
+
+      There is another optimization in that the same multiples of
+      the current element we need in order to advance our generator
+      to the next iteration can be reused in performing the 2,1,1,3
+      calculation. We also calculate the inverse mix column tables,
+      with e,9,d,b being the inverse of 2,1,1,3.
+
+      When we're done, and we need to actually mix columns, the first
+      byte of each state word should be put through mix[0] (2,1,1,3),
+      the second through mix[1] (3,2,1,1) and so forth. Then they should
+      be XOR'd together to produce the fully mixed column.
+    */
+
+    // calculate mix and imix table values
+    sx2 = xtime[sx];
+    e2 = xtime[e];
+    e4 = xtime[e2];
+    e8 = xtime[e4];
+    me =
+      (sx2 << 24) ^  // 2
+      (sx << 16) ^   // 1
+      (sx << 8) ^    // 1
+      (sx ^ sx2);    // 3
+    ime =
+      (e2 ^ e4 ^ e8) << 24 ^  // E (14)
+      (e ^ e8) << 16 ^        // 9
+      (e ^ e4 ^ e8) << 8 ^    // D (13)
+      (e ^ e2 ^ e8);          // B (11)
+    // produce each of the mix tables by rotating the 2,1,1,3 value
+    for(var n = 0; n < 4; ++n) {
+      mix[n][e] = me;
+      imix[n][sx] = ime;
+      // cycle the right most byte to the left most position
+      // ie: 2,1,1,3 becomes 3,2,1,1
+      me = me << 24 | me >>> 8;
+      ime = ime << 24 | ime >>> 8;
+    }
+
+    // get next element and inverse
+    if(e === 0) {
+      // 1 is the inverse of 1
+      e = ei = 1;
+    } else {
+      // e = 2e + 2*2*2*(10e)) = multiply e by 82 (chosen generator)
+      // ei = ei + 2*2*ei = multiply ei by 5 (inverse generator)
+      e = e2 ^ xtime[xtime[xtime[e2 ^ e8]]];
+      ei ^= xtime[xtime[ei]];
+    }
+  }
+}
+
+/**
+ * Generates a key schedule using the AES key expansion algorithm.
+ *
+ * The AES algorithm takes the Cipher Key, K, and performs a Key Expansion
+ * routine to generate a key schedule. The Key Expansion generates a total
+ * of Nb*(Nr + 1) words: the algorithm requires an initial set of Nb words,
+ * and each of the Nr rounds requires Nb words of key data. The resulting
+ * key schedule consists of a linear array of 4-byte words, denoted [wi ],
+ * with i in the range 0 ≤ i < Nb(Nr + 1).
+ *
+ * KeyExpansion(byte key[4*Nk], word w[Nb*(Nr+1)], Nk)
+ * AES-128 (Nb=4, Nk=4, Nr=10)
+ * AES-192 (Nb=4, Nk=6, Nr=12)
+ * AES-256 (Nb=4, Nk=8, Nr=14)
+ * Note: Nr=Nk+6.
+ *
+ * Nb is the number of columns (32-bit words) comprising the State (or
+ * number of bytes in a block). For AES, Nb=4.
+ *
+ * @param key the key to schedule (as an array of 32-bit words).
+ * @param decrypt true to modify the key schedule to decrypt, false not to.
+ *
+ * @return the generated key schedule.
+ */
+function _expandKey(key, decrypt) {
+  // copy the key's words to initialize the key schedule
+  var w = key.slice(0);
+
+  /* RotWord() will rotate a word, moving the first byte to the last
+    byte's position (shifting the other bytes left).
+
+    We will be getting the value of Rcon at i / Nk. 'i' will iterate
+    from Nk to (Nb * Nr+1). Nk = 4 (4 byte key), Nb = 4 (4 words in
+    a block), Nr = Nk + 6 (10). Therefore 'i' will iterate from
+    4 to 44 (exclusive). Each time we iterate 4 times, i / Nk will
+    increase by 1. We use a counter iNk to keep track of this.
+   */
+
+  // go through the rounds expanding the key
+  var temp, iNk = 1;
+  var Nk = w.length;
+  var Nr1 = Nk + 6 + 1;
+  var end = Nb * Nr1;
+  for(var i = Nk; i < end; ++i) {
+    temp = w[i - 1];
+    if(i % Nk === 0) {
+      // temp = SubWord(RotWord(temp)) ^ Rcon[i / Nk]
+      temp =
+        sbox[temp >>> 16 & 255] << 24 ^
+        sbox[temp >>> 8 & 255] << 16 ^
+        sbox[temp & 255] << 8 ^
+        sbox[temp >>> 24] ^ (rcon[iNk] << 24);
+      iNk++;
+    } else if(Nk > 6 && (i % Nk === 4)) {
+      // temp = SubWord(temp)
+      temp =
+        sbox[temp >>> 24] << 24 ^
+        sbox[temp >>> 16 & 255] << 16 ^
+        sbox[temp >>> 8 & 255] << 8 ^
+        sbox[temp & 255];
+    }
+    w[i] = w[i - Nk] ^ temp;
+  }
+
+   /* When we are updating a cipher block we always use the code path for
+     encryption whether we are decrypting or not (to shorten code and
+     simplify the generation of look up tables). However, because there
+     are differences in the decryption algorithm, other than just swapping
+     in different look up tables, we must transform our key schedule to
+     account for these changes:
+
+     1. The decryption algorithm gets its key rounds in reverse order.
+     2. The decryption algorithm adds the round key before mixing columns
+       instead of afterwards.
+
+     We don't need to modify our key schedule to handle the first case,
+     we can just traverse the key schedule in reverse order when decrypting.
+
+     The second case requires a little work.
+
+     The tables we built for performing rounds will take an input and then
+     perform SubBytes() and MixColumns() or, for the decrypt version,
+     InvSubBytes() and InvMixColumns(). But the decrypt algorithm requires
+     us to AddRoundKey() before InvMixColumns(). This means we'll need to
+     apply some transformations to the round key to inverse-mix its columns
+     so they'll be correct for moving AddRoundKey() to after the state has
+     had its columns inverse-mixed.
+
+     To inverse-mix the columns of the state when we're decrypting we use a
+     lookup table that will apply InvSubBytes() and InvMixColumns() at the
+     same time. However, the round key's bytes are not inverse-substituted
+     in the decryption algorithm. To get around this problem, we can first
+     substitute the bytes in the round key so that when we apply the
+     transformation via the InvSubBytes()+InvMixColumns() table, it will
+     undo our substitution leaving us with the original value that we
+     want -- and then inverse-mix that value.
+
+     This change will correctly alter our key schedule so that we can XOR
+     each round key with our already transformed decryption state. This
+     allows us to use the same code path as the encryption algorithm.
+
+     We make one more change to the decryption key. Since the decryption
+     algorithm runs in reverse from the encryption algorithm, we reverse
+     the order of the round keys to avoid having to iterate over the key
+     schedule backwards when running the encryption algorithm later in
+     decryption mode. In addition to reversing the order of the round keys,
+     we also swap each round key's 2nd and 4th rows. See the comments
+     section where rounds are performed for more details about why this is
+     done. These changes are done inline with the other substitution
+     described above.
+  */
+  if(decrypt) {
+    var tmp;
+    var m0 = imix[0];
+    var m1 = imix[1];
+    var m2 = imix[2];
+    var m3 = imix[3];
+    var wnew = w.slice(0);
+    end = w.length;
+    for(var i = 0, wi = end - Nb; i < end; i += Nb, wi -= Nb) {
+      // do not sub the first or last round key (round keys are Nb
+      // words) as no column mixing is performed before they are added,
+      // but do change the key order
+      if(i === 0 || i === (end - Nb)) {
+        wnew[i] = w[wi];
+        wnew[i + 1] = w[wi + 3];
+        wnew[i + 2] = w[wi + 2];
+        wnew[i + 3] = w[wi + 1];
+      } else {
+        // substitute each round key byte because the inverse-mix
+        // table will inverse-substitute it (effectively cancel the
+        // substitution because round key bytes aren't sub'd in
+        // decryption mode) and swap indexes 3 and 1
+        for(var n = 0; n < Nb; ++n) {
+          tmp = w[wi + n];
+          wnew[i + (3&-n)] =
+            m0[sbox[tmp >>> 24]] ^
+            m1[sbox[tmp >>> 16 & 255]] ^
+            m2[sbox[tmp >>> 8 & 255]] ^
+            m3[sbox[tmp & 255]];
+        }
+      }
+    }
+    w = wnew;
+  }
+
+  return w;
+}
+
+/**
+ * Updates a single block (16 bytes) using AES. The update will either
+ * encrypt or decrypt the block.
+ *
+ * @param w the key schedule.
+ * @param input the input block (an array of 32-bit words).
+ * @param output the updated output block.
+ * @param decrypt true to decrypt the block, false to encrypt it.
+ */
+function _updateBlock(w, input, output, decrypt) {
+  /*
+  Cipher(byte in[4*Nb], byte out[4*Nb], word w[Nb*(Nr+1)])
+  begin
+    byte state[4,Nb]
+    state = in
+    AddRoundKey(state, w[0, Nb-1])
+    for round = 1 step 1 to Nr–1
+      SubBytes(state)
+      ShiftRows(state)
+      MixColumns(state)
+      AddRoundKey(state, w[round*Nb, (round+1)*Nb-1])
+    end for
+    SubBytes(state)
+    ShiftRows(state)
+    AddRoundKey(state, w[Nr*Nb, (Nr+1)*Nb-1])
+    out = state
+  end
+
+  InvCipher(byte in[4*Nb], byte out[4*Nb], word w[Nb*(Nr+1)])
+  begin
+    byte state[4,Nb]
+    state = in
+    AddRoundKey(state, w[Nr*Nb, (Nr+1)*Nb-1])
+    for round = Nr-1 step -1 downto 1
+      InvShiftRows(state)
+      InvSubBytes(state)
+      AddRoundKey(state, w[round*Nb, (round+1)*Nb-1])
+      InvMixColumns(state)
+    end for
+    InvShiftRows(state)
+    InvSubBytes(state)
+    AddRoundKey(state, w[0, Nb-1])
+    out = state
+  end
+  */
+
+  // Encrypt: AddRoundKey(state, w[0, Nb-1])
+  // Decrypt: AddRoundKey(state, w[Nr*Nb, (Nr+1)*Nb-1])
+  var Nr = w.length / 4 - 1;
+  var m0, m1, m2, m3, sub;
+  if(decrypt) {
+    m0 = imix[0];
+    m1 = imix[1];
+    m2 = imix[2];
+    m3 = imix[3];
+    sub = isbox;
+  } else {
+    m0 = mix[0];
+    m1 = mix[1];
+    m2 = mix[2];
+    m3 = mix[3];
+    sub = sbox;
+  }
+  var a, b, c, d, a2, b2, c2;
+  a = input[0] ^ w[0];
+  b = input[decrypt ? 3 : 1] ^ w[1];
+  c = input[2] ^ w[2];
+  d = input[decrypt ? 1 : 3] ^ w[3];
+  var i = 3;
+
+  /* In order to share code we follow the encryption algorithm when both
+    encrypting and decrypting. To account for the changes required in the
+    decryption algorithm, we use different lookup tables when decrypting
+    and use a modified key schedule to account for the difference in the
+    order of transformations applied when performing rounds. We also get
+    key rounds in reverse order (relative to encryption). */
+  for(var round = 1; round < Nr; ++round) {
+    /* As described above, we'll be using table lookups to perform the
+      column mixing. Each column is stored as a word in the state (the
+      array 'input' has one column as a word at each index). In order to
+      mix a column, we perform these transformations on each row in c,
+      which is 1 byte in each word. The new column for c0 is c'0:
+
+               m0      m1      m2      m3
+      r0,c'0 = 2*r0,c0 + 3*r1,c0 + 1*r2,c0 + 1*r3,c0
+      r1,c'0 = 1*r0,c0 + 2*r1,c0 + 3*r2,c0 + 1*r3,c0
+      r2,c'0 = 1*r0,c0 + 1*r1,c0 + 2*r2,c0 + 3*r3,c0
+      r3,c'0 = 3*r0,c0 + 1*r1,c0 + 1*r2,c0 + 2*r3,c0
+
+      So using mix tables where c0 is a word with r0 being its upper
+      8 bits and r3 being its lower 8 bits:
+
+      m0[c0 >> 24] will yield this word: [2*r0,1*r0,1*r0,3*r0]
+      ...
+      m3[c0 & 255] will yield this word: [1*r3,1*r3,3*r3,2*r3]
+
+      Therefore to mix the columns in each word in the state we
+      do the following (& 255 omitted for brevity):
+      c'0,r0 = m0[c0 >> 24] ^ m1[c1 >> 16] ^ m2[c2 >> 8] ^ m3[c3]
+      c'0,r1 = m0[c0 >> 24] ^ m1[c1 >> 16] ^ m2[c2 >> 8] ^ m3[c3]
+      c'0,r2 = m0[c0 >> 24] ^ m1[c1 >> 16] ^ m2[c2 >> 8] ^ m3[c3]
+      c'0,r3 = m0[c0 >> 24] ^ m1[c1 >> 16] ^ m2[c2 >> 8] ^ m3[c3]
+
+      However, before mixing, the algorithm requires us to perform
+      ShiftRows(). The ShiftRows() transformation cyclically shifts the
+      last 3 rows of the state over different offsets. The first row
+      (r = 0) is not shifted.
+
+      s'_r,c = s_r,(c + shift(r, Nb) mod Nb
+      for 0 < r < 4 and 0 <= c < Nb and
+      shift(1, 4) = 1
+      shift(2, 4) = 2
+      shift(3, 4) = 3.
+
+      This causes the first byte in r = 1 to be moved to the end of
+      the row, the first 2 bytes in r = 2 to be moved to the end of
+      the row, the first 3 bytes in r = 3 to be moved to the end of
+      the row:
+
+      r1: [c0 c1 c2 c3] => [c1 c2 c3 c0]
+      r2: [c0 c1 c2 c3]    [c2 c3 c0 c1]
+      r3: [c0 c1 c2 c3]    [c3 c0 c1 c2]
+
+      We can make these substitutions inline with our column mixing to
+      generate an updated set of equations to produce each word in the
+      state (note the columns have changed positions):
+
+      c0 c1 c2 c3 => c0 c1 c2 c3
+      c0 c1 c2 c3    c1 c2 c3 c0  (cycled 1 byte)
+      c0 c1 c2 c3    c2 c3 c0 c1  (cycled 2 bytes)
+      c0 c1 c2 c3    c3 c0 c1 c2  (cycled 3 bytes)
+
+      Therefore:
+
+      c'0 = 2*r0,c0 + 3*r1,c1 + 1*r2,c2 + 1*r3,c3
+      c'0 = 1*r0,c0 + 2*r1,c1 + 3*r2,c2 + 1*r3,c3
+      c'0 = 1*r0,c0 + 1*r1,c1 + 2*r2,c2 + 3*r3,c3
+      c'0 = 3*r0,c0 + 1*r1,c1 + 1*r2,c2 + 2*r3,c3
+
+      c'1 = 2*r0,c1 + 3*r1,c2 + 1*r2,c3 + 1*r3,c0
+      c'1 = 1*r0,c1 + 2*r1,c2 + 3*r2,c3 + 1*r3,c0
+      c'1 = 1*r0,c1 + 1*r1,c2 + 2*r2,c3 + 3*r3,c0
+      c'1 = 3*r0,c1 + 1*r1,c2 + 1*r2,c3 + 2*r3,c0
+
+      ... and so forth for c'2 and c'3. The important distinction is
+      that the columns are cycling, with c0 being used with the m0
+      map when calculating c0, but c1 being used with the m0 map when
+      calculating c1 ... and so forth.
+
+      When performing the inverse we transform the mirror image and
+      skip the bottom row, instead of the top one, and move upwards:
+
+      c3 c2 c1 c0 => c0 c3 c2 c1  (cycled 3 bytes) *same as encryption
+      c3 c2 c1 c0    c1 c0 c3 c2  (cycled 2 bytes)
+      c3 c2 c1 c0    c2 c1 c0 c3  (cycled 1 byte)  *same as encryption
+      c3 c2 c1 c0    c3 c2 c1 c0
+
+      If you compare the resulting matrices for ShiftRows()+MixColumns()
+      and for InvShiftRows()+InvMixColumns() the 2nd and 4th columns are
+      different (in encrypt mode vs. decrypt mode). So in order to use
+      the same code to handle both encryption and decryption, we will
+      need to do some mapping.
+
+      If in encryption mode we let a=c0, b=c1, c=c2, d=c3, and r<N> be
+      a row number in the state, then the resulting matrix in encryption
+      mode for applying the above transformations would be:
+
+      r1: a b c d
+      r2: b c d a
+      r3: c d a b
+      r4: d a b c
+
+      If we did the same in decryption mode we would get:
+
+      r1: a d c b
+      r2: b a d c
+      r3: c b a d
+      r4: d c b a
+
+      If instead we swap d and b (set b=c3 and d=c1), then we get:
+
+      r1: a b c d
+      r2: d a b c
+      r3: c d a b
+      r4: b c d a
+
+      Now the 1st and 3rd rows are the same as the encryption matrix. All
+      we need to do then to make the mapping exactly the same is to swap
+      the 2nd and 4th rows when in decryption mode. To do this without
+      having to do it on each iteration, we swapped the 2nd and 4th rows
+      in the decryption key schedule. We also have to do the swap above
+      when we first pull in the input and when we set the final output. */
+    a2 =
+      m0[a >>> 24] ^
+      m1[b >>> 16 & 255] ^
+      m2[c >>> 8 & 255] ^
+      m3[d & 255] ^ w[++i];
+    b2 =
+      m0[b >>> 24] ^
+      m1[c >>> 16 & 255] ^
+      m2[d >>> 8 & 255] ^
+      m3[a & 255] ^ w[++i];
+    c2 =
+      m0[c >>> 24] ^
+      m1[d >>> 16 & 255] ^
+      m2[a >>> 8 & 255] ^
+      m3[b & 255] ^ w[++i];
+    d =
+      m0[d >>> 24] ^
+      m1[a >>> 16 & 255] ^
+      m2[b >>> 8 & 255] ^
+      m3[c & 255] ^ w[++i];
+    a = a2;
+    b = b2;
+    c = c2;
+  }
+
+  /*
+    Encrypt:
+    SubBytes(state)
+    ShiftRows(state)
+    AddRoundKey(state, w[Nr*Nb, (Nr+1)*Nb-1])
+
+    Decrypt:
+    InvShiftRows(state)
+    InvSubBytes(state)
+    AddRoundKey(state, w[0, Nb-1])
+   */
+   // Note: rows are shifted inline
+  output[0] =
+    (sub[a >>> 24] << 24) ^
+    (sub[b >>> 16 & 255] << 16) ^
+    (sub[c >>> 8 & 255] << 8) ^
+    (sub[d & 255]) ^ w[++i];
+  output[decrypt ? 3 : 1] =
+    (sub[b >>> 24] << 24) ^
+    (sub[c >>> 16 & 255] << 16) ^
+    (sub[d >>> 8 & 255] << 8) ^
+    (sub[a & 255]) ^ w[++i];
+  output[2] =
+    (sub[c >>> 24] << 24) ^
+    (sub[d >>> 16 & 255] << 16) ^
+    (sub[a >>> 8 & 255] << 8) ^
+    (sub[b & 255]) ^ w[++i];
+  output[decrypt ? 1 : 3] =
+    (sub[d >>> 24] << 24) ^
+    (sub[a >>> 16 & 255] << 16) ^
+    (sub[b >>> 8 & 255] << 8) ^
+    (sub[c & 255]) ^ w[++i];
+}
+
+/**
+ * Deprecated. Instead, use:
+ *
+ * forge.cipher.createCipher('AES-<mode>', key);
+ * forge.cipher.createDecipher('AES-<mode>', key);
+ *
+ * Creates a deprecated AES cipher object. This object's mode will default to
+ * CBC (cipher-block-chaining).
+ *
+ * The key and iv may be given as a string of bytes, an array of bytes, a
+ * byte buffer, or an array of 32-bit words.
+ *
+ * @param options the options to use.
+ *          key the symmetric key to use.
+ *          output the buffer to write to.
+ *          decrypt true for decryption, false for encryption.
+ *          mode the cipher mode to use (default: 'CBC').
+ *
+ * @return the cipher.
+ */
+function _createCipher(options) {
+  options = options || {};
+  var mode = (options.mode || 'CBC').toUpperCase();
+  var algorithm = 'AES-' + mode;
+
+  var cipher;
+  if(options.decrypt) {
+    cipher = forge.cipher.createDecipher(algorithm, options.key);
+  } else {
+    cipher = forge.cipher.createCipher(algorithm, options.key);
+  }
+
+  // backwards compatible start API
+  var start = cipher.start;
+  cipher.start = function(iv, options) {
+    // backwards compatibility: support second arg as output buffer
+    var output = null;
+    if(options instanceof forge.util.ByteBuffer) {
+      output = options;
+      options = {};
+    }
+    options = options || {};
+    options.output = output;
+    options.iv = iv;
+    start.call(cipher, options);
+  };
+
+  return cipher;
+}
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'aes';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(
+  ['require', 'module', './cipher', './cipherModes', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/aesCipherSuites.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/aesCipherSuites.js
new file mode 100644
index 0000000..b0b4b4f
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/aesCipherSuites.js
@@ -0,0 +1,312 @@
+/**
+ * A Javascript implementation of AES Cipher Suites for TLS.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2009-2014 Digital Bazaar, Inc.
+ *
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+var tls = forge.tls;
+
+/**
+ * Supported cipher suites.
+ */
+tls.CipherSuites['TLS_RSA_WITH_AES_128_CBC_SHA'] = {
+  id: [0x00,0x2f],
+  name: 'TLS_RSA_WITH_AES_128_CBC_SHA',
+  initSecurityParameters: function(sp) {
+    sp.bulk_cipher_algorithm = tls.BulkCipherAlgorithm.aes;
+    sp.cipher_type = tls.CipherType.block;
+    sp.enc_key_length = 16;
+    sp.block_length = 16;
+    sp.fixed_iv_length = 16;
+    sp.record_iv_length = 16;
+    sp.mac_algorithm = tls.MACAlgorithm.hmac_sha1;
+    sp.mac_length = 20;
+    sp.mac_key_length = 20;
+  },
+  initConnectionState: initConnectionState
+};
+tls.CipherSuites['TLS_RSA_WITH_AES_256_CBC_SHA'] = {
+  id: [0x00,0x35],
+  name: 'TLS_RSA_WITH_AES_256_CBC_SHA',
+  initSecurityParameters: function(sp) {
+    sp.bulk_cipher_algorithm = tls.BulkCipherAlgorithm.aes;
+    sp.cipher_type = tls.CipherType.block;
+    sp.enc_key_length = 32;
+    sp.block_length = 16;
+    sp.fixed_iv_length = 16;
+    sp.record_iv_length = 16;
+    sp.mac_algorithm = tls.MACAlgorithm.hmac_sha1;
+    sp.mac_length = 20;
+    sp.mac_key_length = 20;
+  },
+  initConnectionState: initConnectionState
+};
+
+function initConnectionState(state, c, sp) {
+  var client = (c.entity === forge.tls.ConnectionEnd.client);
+
+  // cipher setup
+  state.read.cipherState = {
+    init: false,
+    cipher: forge.cipher.createDecipher('AES-CBC', client ?
+      sp.keys.server_write_key : sp.keys.client_write_key),
+    iv: client ? sp.keys.server_write_IV : sp.keys.client_write_IV
+  };
+  state.write.cipherState = {
+    init: false,
+    cipher: forge.cipher.createCipher('AES-CBC', client ?
+      sp.keys.client_write_key : sp.keys.server_write_key),
+    iv: client ? sp.keys.client_write_IV : sp.keys.server_write_IV
+  };
+  state.read.cipherFunction = decrypt_aes_cbc_sha1;
+  state.write.cipherFunction = encrypt_aes_cbc_sha1;
+
+  // MAC setup
+  state.read.macLength = state.write.macLength = sp.mac_length;
+  state.read.macFunction = state.write.macFunction = tls.hmac_sha1;
+}
+
+/**
+ * Encrypts the TLSCompressed record into a TLSCipherText record using AES
+ * in CBC mode.
+ *
+ * @param record the TLSCompressed record to encrypt.
+ * @param s the ConnectionState to use.
+ *
+ * @return true on success, false on failure.
+ */
+function encrypt_aes_cbc_sha1(record, s) {
+  var rval = false;
+
+  // append MAC to fragment, update sequence number
+  var mac = s.macFunction(s.macKey, s.sequenceNumber, record);
+  record.fragment.putBytes(mac);
+  s.updateSequenceNumber();
+
+  // TLS 1.1+ use an explicit IV every time to protect against CBC attacks
+  var iv;
+  if(record.version.minor === tls.Versions.TLS_1_0.minor) {
+    // use the pre-generated IV when initializing for TLS 1.0, otherwise use
+    // the residue from the previous encryption
+    iv = s.cipherState.init ? null : s.cipherState.iv;
+  } else {
+    iv = forge.random.getBytesSync(16);
+  }
+
+  s.cipherState.init = true;
+
+  // start cipher
+  var cipher = s.cipherState.cipher;
+  cipher.start({iv: iv});
+
+  // TLS 1.1+ write IV into output
+  if(record.version.minor >= tls.Versions.TLS_1_1.minor) {
+    cipher.output.putBytes(iv);
+  }
+
+  // do encryption (default padding is appropriate)
+  cipher.update(record.fragment);
+  if(cipher.finish(encrypt_aes_cbc_sha1_padding)) {
+    // set record fragment to encrypted output
+    record.fragment = cipher.output;
+    record.length = record.fragment.length();
+    rval = true;
+  }
+
+  return rval;
+}
+
+/**
+ * Handles padding for aes_cbc_sha1 in encrypt mode.
+ *
+ * @param blockSize the block size.
+ * @param input the input buffer.
+ * @param decrypt true in decrypt mode, false in encrypt mode.
+ *
+ * @return true on success, false on failure.
+ */
+function encrypt_aes_cbc_sha1_padding(blockSize, input, decrypt) {
+  /* The encrypted data length (TLSCiphertext.length) is one more than the sum
+   of SecurityParameters.block_length, TLSCompressed.length,
+   SecurityParameters.mac_length, and padding_length.
+
+   The padding may be any length up to 255 bytes long, as long as it results in
+   the TLSCiphertext.length being an integral multiple of the block length.
+   Lengths longer than necessary might be desirable to frustrate attacks on a
+   protocol based on analysis of the lengths of exchanged messages. Each uint8
+   in the padding data vector must be filled with the padding length value.
+
+   The padding length should be such that the total size of the
+   GenericBlockCipher structure is a multiple of the cipher's block length.
+   Legal values range from zero to 255, inclusive. This length specifies the
+   length of the padding field exclusive of the padding_length field itself.
+
+   This is slightly different from PKCS#7 because the padding value is 1
+   less than the actual number of padding bytes if you include the
+   padding_length uint8 itself as a padding byte. */
+  if(!decrypt) {
+    // get the number of padding bytes required to reach the blockSize and
+    // subtract 1 for the padding value (to make room for the padding_length
+    // uint8)
+    var padding = blockSize - (input.length() % blockSize);
+    input.fillWithByte(padding - 1, padding);
+  }
+  return true;
+}
+
+/**
+ * Handles padding for aes_cbc_sha1 in decrypt mode.
+ *
+ * @param blockSize the block size.
+ * @param output the output buffer.
+ * @param decrypt true in decrypt mode, false in encrypt mode.
+ *
+ * @return true on success, false on failure.
+ */
+function decrypt_aes_cbc_sha1_padding(blockSize, output, decrypt) {
+  var rval = true;
+  if(decrypt) {
+    /* The last byte in the output specifies the number of padding bytes not
+      including itself. Each of the padding bytes has the same value as that
+      last byte (known as the padding_length). Here we check all padding
+      bytes to ensure they have the value of padding_length even if one of
+      them is bad in order to ward-off timing attacks. */
+    var len = output.length();
+    var paddingLength = output.last();
+    for(var i = len - 1 - paddingLength; i < len - 1; ++i) {
+      rval = rval && (output.at(i) == paddingLength);
+    }
+    if(rval) {
+      // trim off padding bytes and last padding length byte
+      output.truncate(paddingLength + 1);
+    }
+  }
+  return rval;
+}
+
+/**
+ * Decrypts a TLSCipherText record into a TLSCompressed record using
+ * AES in CBC mode.
+ *
+ * @param record the TLSCipherText record to decrypt.
+ * @param s the ConnectionState to use.
+ *
+ * @return true on success, false on failure.
+ */
+var count = 0;
+function decrypt_aes_cbc_sha1(record, s) {
+  var rval = false;
+  ++count;
+
+  var iv;
+  if(record.version.minor === tls.Versions.TLS_1_0.minor) {
+    // use pre-generated IV when initializing for TLS 1.0, otherwise use the
+    // residue from the previous decryption
+    iv = s.cipherState.init ? null : s.cipherState.iv;
+  } else {
+    // TLS 1.1+ use an explicit IV every time to protect against CBC attacks
+    // that is appended to the record fragment
+    iv = record.fragment.getBytes(16);
+  }
+
+  s.cipherState.init = true;
+
+  // start cipher
+  var cipher = s.cipherState.cipher;
+  cipher.start({iv: iv});
+
+  // do decryption
+  cipher.update(record.fragment);
+  rval = cipher.finish(decrypt_aes_cbc_sha1_padding);
+
+  // even if decryption fails, keep going to minimize timing attacks
+
+  // decrypted data:
+  // first (len - 20) bytes = application data
+  // last 20 bytes          = MAC
+  var macLen = s.macLength;
+
+  // create a zero'd out mac
+  var mac = '';
+  for(var i = 0; i < macLen; ++i) {
+    mac += String.fromCharCode(0);
+  }
+
+  // get fragment and mac
+  var len = cipher.output.length();
+  if(len >= macLen) {
+    record.fragment = cipher.output.getBytes(len - macLen);
+    mac = cipher.output.getBytes(macLen);
+  } else {
+    // bad data, but get bytes anyway to try to keep timing consistent
+    record.fragment = cipher.output.getBytes();
+  }
+  record.fragment = forge.util.createBuffer(record.fragment);
+  record.length = record.fragment.length();
+
+  // see if data integrity checks out, update sequence number
+  var mac2 = s.macFunction(s.macKey, s.sequenceNumber, record);
+  s.updateSequenceNumber();
+  rval = (mac2 === mac) && rval;
+  return rval;
+}
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'aesCipherSuites';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './aes', './tls'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/asn1.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/asn1.js
new file mode 100644
index 0000000..9ac7df4
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/asn1.js
@@ -0,0 +1,1114 @@
+/**
+ * Javascript implementation of Abstract Syntax Notation Number One.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2014 Digital Bazaar, Inc.
+ *
+ * An API for storing data using the Abstract Syntax Notation Number One
+ * format using DER (Distinguished Encoding Rules) encoding. This encoding is
+ * commonly used to store data for PKI, i.e. X.509 Certificates, and this
+ * implementation exists for that purpose.
+ *
+ * Abstract Syntax Notation Number One (ASN.1) is used to define the abstract
+ * syntax of information without restricting the way the information is encoded
+ * for transmission. It provides a standard that allows for open systems
+ * communication. ASN.1 defines the syntax of information data and a number of
+ * simple data types as well as a notation for describing them and specifying
+ * values for them.
+ *
+ * The RSA algorithm creates public and private keys that are often stored in
+ * X.509 or PKCS#X formats -- which use ASN.1 (encoded in DER format). This
+ * class provides the most basic functionality required to store and load DSA
+ * keys that are encoded according to ASN.1.
+ *
+ * The most common binary encodings for ASN.1 are BER (Basic Encoding Rules)
+ * and DER (Distinguished Encoding Rules). DER is just a subset of BER that
+ * has stricter requirements for how data must be encoded.
+ *
+ * Each ASN.1 structure has a tag (a byte identifying the ASN.1 structure type)
+ * and a byte array for the value of this ASN1 structure which may be data or a
+ * list of ASN.1 structures.
+ *
+ * Each ASN.1 structure using BER is (Tag-Length-Value):
+ *
+ * | byte 0 | bytes X | bytes Y |
+ * |--------|---------|----------
+ * |  tag   | length  |  value  |
+ *
+ * ASN.1 allows for tags to be of "High-tag-number form" which allows a tag to
+ * be two or more octets, but that is not supported by this class. A tag is
+ * only 1 byte. Bits 1-5 give the tag number (ie the data type within a
+ * particular 'class'), 6 indicates whether or not the ASN.1 value is
+ * constructed from other ASN.1 values, and bits 7 and 8 give the 'class'. If
+ * bits 7 and 8 are both zero, the class is UNIVERSAL. If only bit 7 is set,
+ * then the class is APPLICATION. If only bit 8 is set, then the class is
+ * CONTEXT_SPECIFIC. If both bits 7 and 8 are set, then the class is PRIVATE.
+ * The tag numbers for the data types for the class UNIVERSAL are listed below:
+ *
+ * UNIVERSAL 0 Reserved for use by the encoding rules
+ * UNIVERSAL 1 Boolean type
+ * UNIVERSAL 2 Integer type
+ * UNIVERSAL 3 Bitstring type
+ * UNIVERSAL 4 Octetstring type
+ * UNIVERSAL 5 Null type
+ * UNIVERSAL 6 Object identifier type
+ * UNIVERSAL 7 Object descriptor type
+ * UNIVERSAL 8 External type and Instance-of type
+ * UNIVERSAL 9 Real type
+ * UNIVERSAL 10 Enumerated type
+ * UNIVERSAL 11 Embedded-pdv type
+ * UNIVERSAL 12 UTF8String type
+ * UNIVERSAL 13 Relative object identifier type
+ * UNIVERSAL 14-15 Reserved for future editions
+ * UNIVERSAL 16 Sequence and Sequence-of types
+ * UNIVERSAL 17 Set and Set-of types
+ * UNIVERSAL 18-22, 25-30 Character string types
+ * UNIVERSAL 23-24 Time types
+ *
+ * The length of an ASN.1 structure is specified after the tag identifier.
+ * There is a definite form and an indefinite form. The indefinite form may
+ * be used if the encoding is constructed and not all immediately available.
+ * The indefinite form is encoded using a length byte with only the 8th bit
+ * set. The end of the constructed object is marked using end-of-contents
+ * octets (two zero bytes).
+ *
+ * The definite form looks like this:
+ *
+ * The length may take up 1 or more bytes, it depends on the length of the
+ * value of the ASN.1 structure. DER encoding requires that if the ASN.1
+ * structure has a value that has a length greater than 127, more than 1 byte
+ * will be used to store its length, otherwise just one byte will be used.
+ * This is strict.
+ *
+ * In the case that the length of the ASN.1 value is less than 127, 1 octet
+ * (byte) is used to store the "short form" length. The 8th bit has a value of
+ * 0 indicating the length is "short form" and not "long form" and bits 7-1
+ * give the length of the data. (The 8th bit is the left-most, most significant
+ * bit: also known as big endian or network format).
+ *
+ * In the case that the length of the ASN.1 value is greater than 127, 2 to
+ * 127 octets (bytes) are used to store the "long form" length. The first
+ * byte's 8th bit is set to 1 to indicate the length is "long form." Bits 7-1
+ * give the number of additional octets. All following octets are in base 256
+ * with the most significant digit first (typical big-endian binary unsigned
+ * integer storage). So, for instance, if the length of a value was 257, the
+ * first byte would be set to:
+ *
+ * 10000010 = 130 = 0x82.
+ *
+ * This indicates there are 2 octets (base 256) for the length. The second and
+ * third bytes (the octets just mentioned) would store the length in base 256:
+ *
+ * octet 2: 00000001 = 1 * 256^1 = 256
+ * octet 3: 00000001 = 1 * 256^0 = 1
+ * total = 257
+ *
+ * The algorithm for converting a js integer value of 257 to base-256 is:
+ *
+ * var value = 257;
+ * var bytes = [];
+ * bytes[0] = (value >>> 8) & 0xFF; // most significant byte first
+ * bytes[1] = value & 0xFF;        // least significant byte last
+ *
+ * On the ASN.1 UNIVERSAL Object Identifier (OID) type:
+ *
+ * An OID can be written like: "value1.value2.value3...valueN"
+ *
+ * The DER encoding rules:
+ *
+ * The first byte has the value 40 * value1 + value2.
+ * The following bytes, if any, encode the remaining values. Each value is
+ * encoded in base 128, most significant digit first (big endian), with as
+ * few digits as possible, and the most significant bit of each byte set
+ * to 1 except the last in each value's encoding. For example: Given the
+ * OID "1.2.840.113549", its DER encoding is (remember each byte except the
+ * last one in each encoding is OR'd with 0x80):
+ *
+ * byte 1: 40 * 1 + 2 = 42 = 0x2A.
+ * bytes 2-3: 128 * 6 + 72 = 840 = 6 72 = 6 72 = 0x0648 = 0x8648
+ * bytes 4-6: 16384 * 6 + 128 * 119 + 13 = 6 119 13 = 0x06770D = 0x86F70D
+ *
+ * The final value is: 0x2A864886F70D.
+ * The full OID (including ASN.1 tag and length of 6 bytes) is:
+ * 0x06062A864886F70D
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+/* ASN.1 API */
+var asn1 = forge.asn1 = forge.asn1 || {};
+
+/**
+ * ASN.1 classes.
+ */
+asn1.Class = {
+  UNIVERSAL:        0x00,
+  APPLICATION:      0x40,
+  CONTEXT_SPECIFIC: 0x80,
+  PRIVATE:          0xC0
+};
+
+/**
+ * ASN.1 types. Not all types are supported by this implementation, only
+ * those necessary to implement a simple PKI are implemented.
+ */
+asn1.Type = {
+  NONE:             0,
+  BOOLEAN:          1,
+  INTEGER:          2,
+  BITSTRING:        3,
+  OCTETSTRING:      4,
+  NULL:             5,
+  OID:              6,
+  ODESC:            7,
+  EXTERNAL:         8,
+  REAL:             9,
+  ENUMERATED:      10,
+  EMBEDDED:        11,
+  UTF8:            12,
+  ROID:            13,
+  SEQUENCE:        16,
+  SET:             17,
+  PRINTABLESTRING: 19,
+  IA5STRING:       22,
+  UTCTIME:         23,
+  GENERALIZEDTIME: 24,
+  BMPSTRING:       30
+};
+
+/**
+ * Creates a new asn1 object.
+ *
+ * @param tagClass the tag class for the object.
+ * @param type the data type (tag number) for the object.
+ * @param constructed true if the asn1 object is in constructed form.
+ * @param value the value for the object, if it is not constructed.
+ *
+ * @return the asn1 object.
+ */
+asn1.create = function(tagClass, type, constructed, value) {
+  /* An asn1 object has a tagClass, a type, a constructed flag, and a
+    value. The value's type depends on the constructed flag. If
+    constructed, it will contain a list of other asn1 objects. If not,
+    it will contain the ASN.1 value as an array of bytes formatted
+    according to the ASN.1 data type. */
+
+  // remove undefined values
+  if(forge.util.isArray(value)) {
+    var tmp = [];
+    for(var i = 0; i < value.length; ++i) {
+      if(value[i] !== undefined) {
+        tmp.push(value[i]);
+      }
+    }
+    value = tmp;
+  }
+
+  return {
+    tagClass: tagClass,
+    type: type,
+    constructed: constructed,
+    composed: constructed || forge.util.isArray(value),
+    value: value
+  };
+};
+
+/**
+ * Gets the length of an ASN.1 value.
+ *
+ * In case the length is not specified, undefined is returned.
+ *
+ * @param b the ASN.1 byte buffer.
+ *
+ * @return the length of the ASN.1 value.
+ */
+var _getValueLength = function(b) {
+  var b2 = b.getByte();
+  if(b2 === 0x80) {
+    return undefined;
+  }
+
+  // see if the length is "short form" or "long form" (bit 8 set)
+  var length;
+  var longForm = b2 & 0x80;
+  if(!longForm) {
+    // length is just the first byte
+    length = b2;
+  } else {
+    // the number of bytes the length is specified in bits 7 through 1
+    // and each length byte is in big-endian base-256
+    length = b.getInt((b2 & 0x7F) << 3);
+  }
+  return length;
+};
+
+/**
+ * Parses an asn1 object from a byte buffer in DER format.
+ *
+ * @param bytes the byte buffer to parse from.
+ * @param strict true to be strict when checking value lengths, false to
+ *          allow truncated values (default: true).
+ *
+ * @return the parsed asn1 object.
+ */
+asn1.fromDer = function(bytes, strict) {
+  if(strict === undefined) {
+    strict = true;
+  }
+
+  // wrap in buffer if needed
+  if(typeof bytes === 'string') {
+    bytes = forge.util.createBuffer(bytes);
+  }
+
+  // minimum length for ASN.1 DER structure is 2
+  if(bytes.length() < 2) {
+    var error = new Error('Too few bytes to parse DER.');
+    error.bytes = bytes.length();
+    throw error;
+  }
+
+  // get the first byte
+  var b1 = bytes.getByte();
+
+  // get the tag class
+  var tagClass = (b1 & 0xC0);
+
+  // get the type (bits 1-5)
+  var type = b1 & 0x1F;
+
+  // get the value length
+  var length = _getValueLength(bytes);
+
+  // ensure there are enough bytes to get the value
+  if(bytes.length() < length) {
+    if(strict) {
+      var error = new Error('Too few bytes to read ASN.1 value.');
+      error.detail = bytes.length() + ' < ' + length;
+      throw error;
+    }
+    // Note: be lenient with truncated values
+    length = bytes.length();
+  }
+
+  // prepare to get value
+  var value;
+
+  // constructed flag is bit 6 (32 = 0x20) of the first byte
+  var constructed = ((b1 & 0x20) === 0x20);
+
+  // determine if the value is composed of other ASN.1 objects (if its
+  // constructed it will be and if its a BITSTRING it may be)
+  var composed = constructed;
+  if(!composed && tagClass === asn1.Class.UNIVERSAL &&
+    type === asn1.Type.BITSTRING && length > 1) {
+    /* The first octet gives the number of bits by which the length of the
+      bit string is less than the next multiple of eight (this is called
+      the "number of unused bits").
+
+      The second and following octets give the value of the bit string
+      converted to an octet string. */
+    // if there are no unused bits, maybe the bitstring holds ASN.1 objs
+    var read = bytes.read;
+    var unused = bytes.getByte();
+    if(unused === 0) {
+      // if the first byte indicates UNIVERSAL or CONTEXT_SPECIFIC,
+      // and the length is valid, assume we've got an ASN.1 object
+      b1 = bytes.getByte();
+      var tc = (b1 & 0xC0);
+      if(tc === asn1.Class.UNIVERSAL || tc === asn1.Class.CONTEXT_SPECIFIC) {
+        try {
+          var len = _getValueLength(bytes);
+          composed = (len === length - (bytes.read - read));
+          if(composed) {
+            // adjust read/length to account for unused bits byte
+            ++read;
+            --length;
+          }
+        } catch(ex) {}
+      }
+    }
+    // restore read pointer
+    bytes.read = read;
+  }
+
+  if(composed) {
+    // parse child asn1 objects from the value
+    value = [];
+    if(length === undefined) {
+      // asn1 object of indefinite length, read until end tag
+      for(;;) {
+        if(bytes.bytes(2) === String.fromCharCode(0, 0)) {
+          bytes.getBytes(2);
+          break;
+        }
+        value.push(asn1.fromDer(bytes, strict));
+      }
+    } else {
+      // parsing asn1 object of definite length
+      var start = bytes.length();
+      while(length > 0) {
+        value.push(asn1.fromDer(bytes, strict));
+        length -= start - bytes.length();
+        start = bytes.length();
+      }
+    }
+  } else {
+    // asn1 not composed, get raw value
+    // TODO: do DER to OID conversion and vice-versa in .toDer?
+
+    if(length === undefined) {
+      if(strict) {
+        throw new Error('Non-constructed ASN.1 object of indefinite length.');
+      }
+      // be lenient and use remaining bytes
+      length = bytes.length();
+    }
+
+    if(type === asn1.Type.BMPSTRING) {
+      value = '';
+      for(var i = 0; i < length; i += 2) {
+        value += String.fromCharCode(bytes.getInt16());
+      }
+    } else {
+      value = bytes.getBytes(length);
+    }
+  }
+
+  // create and return asn1 object
+  return asn1.create(tagClass, type, constructed, value);
+};
+
+/**
+ * Converts the given asn1 object to a buffer of bytes in DER format.
+ *
+ * @param asn1 the asn1 object to convert to bytes.
+ *
+ * @return the buffer of bytes.
+ */
+asn1.toDer = function(obj) {
+  var bytes = forge.util.createBuffer();
+
+  // build the first byte
+  var b1 = obj.tagClass | obj.type;
+
+  // for storing the ASN.1 value
+  var value = forge.util.createBuffer();
+
+  // if composed, use each child asn1 object's DER bytes as value
+  if(obj.composed) {
+    // turn on 6th bit (0x20 = 32) to indicate asn1 is constructed
+    // from other asn1 objects
+    if(obj.constructed) {
+      b1 |= 0x20;
+    } else {
+      // type is a bit string, add unused bits of 0x00
+      value.putByte(0x00);
+    }
+
+    // add all of the child DER bytes together
+    for(var i = 0; i < obj.value.length; ++i) {
+      if(obj.value[i] !== undefined) {
+        value.putBuffer(asn1.toDer(obj.value[i]));
+      }
+    }
+  } else {
+    // use asn1.value directly
+    if(obj.type === asn1.Type.BMPSTRING) {
+      for(var i = 0; i < obj.value.length; ++i) {
+        value.putInt16(obj.value.charCodeAt(i));
+      }
+    } else {
+      value.putBytes(obj.value);
+    }
+  }
+
+  // add tag byte
+  bytes.putByte(b1);
+
+  // use "short form" encoding
+  if(value.length() <= 127) {
+    // one byte describes the length
+    // bit 8 = 0 and bits 7-1 = length
+    bytes.putByte(value.length() & 0x7F);
+  } else {
+    // use "long form" encoding
+    // 2 to 127 bytes describe the length
+    // first byte: bit 8 = 1 and bits 7-1 = # of additional bytes
+    // other bytes: length in base 256, big-endian
+    var len = value.length();
+    var lenBytes = '';
+    do {
+      lenBytes += String.fromCharCode(len & 0xFF);
+      len = len >>> 8;
+    } while(len > 0);
+
+    // set first byte to # bytes used to store the length and turn on
+    // bit 8 to indicate long-form length is used
+    bytes.putByte(lenBytes.length | 0x80);
+
+    // concatenate length bytes in reverse since they were generated
+    // little endian and we need big endian
+    for(var i = lenBytes.length - 1; i >= 0; --i) {
+      bytes.putByte(lenBytes.charCodeAt(i));
+    }
+  }
+
+  // concatenate value bytes
+  bytes.putBuffer(value);
+  return bytes;
+};
+
+/**
+ * Converts an OID dot-separated string to a byte buffer. The byte buffer
+ * contains only the DER-encoded value, not any tag or length bytes.
+ *
+ * @param oid the OID dot-separated string.
+ *
+ * @return the byte buffer.
+ */
+asn1.oidToDer = function(oid) {
+  // split OID into individual values
+  var values = oid.split('.');
+  var bytes = forge.util.createBuffer();
+
+  // first byte is 40 * value1 + value2
+  bytes.putByte(40 * parseInt(values[0], 10) + parseInt(values[1], 10));
+  // other bytes are each value in base 128 with 8th bit set except for
+  // the last byte for each value
+  var last, valueBytes, value, b;
+  for(var i = 2; i < values.length; ++i) {
+    // produce value bytes in reverse because we don't know how many
+    // bytes it will take to store the value
+    last = true;
+    valueBytes = [];
+    value = parseInt(values[i], 10);
+    do {
+      b = value & 0x7F;
+      value = value >>> 7;
+      // if value is not last, then turn on 8th bit
+      if(!last) {
+        b |= 0x80;
+      }
+      valueBytes.push(b);
+      last = false;
+    } while(value > 0);
+
+    // add value bytes in reverse (needs to be in big endian)
+    for(var n = valueBytes.length - 1; n >= 0; --n) {
+      bytes.putByte(valueBytes[n]);
+    }
+  }
+
+  return bytes;
+};
+
+/**
+ * Converts a DER-encoded byte buffer to an OID dot-separated string. The
+ * byte buffer should contain only the DER-encoded value, not any tag or
+ * length bytes.
+ *
+ * @param bytes the byte buffer.
+ *
+ * @return the OID dot-separated string.
+ */
+asn1.derToOid = function(bytes) {
+  var oid;
+
+  // wrap in buffer if needed
+  if(typeof bytes === 'string') {
+    bytes = forge.util.createBuffer(bytes);
+  }
+
+  // first byte is 40 * value1 + value2
+  var b = bytes.getByte();
+  oid = Math.floor(b / 40) + '.' + (b % 40);
+
+  // other bytes are each value in base 128 with 8th bit set except for
+  // the last byte for each value
+  var value = 0;
+  while(bytes.length() > 0) {
+    b = bytes.getByte();
+    value = value << 7;
+    // not the last byte for the value
+    if(b & 0x80) {
+      value += b & 0x7F;
+    } else {
+      // last byte
+      oid += '.' + (value + b);
+      value = 0;
+    }
+  }
+
+  return oid;
+};
+
+/**
+ * Converts a UTCTime value to a date.
+ *
+ * Note: GeneralizedTime has 4 digits for the year and is used for X.509
+ * dates passed 2049. Parsing that structure hasn't been implemented yet.
+ *
+ * @param utc the UTCTime value to convert.
+ *
+ * @return the date.
+ */
+asn1.utcTimeToDate = function(utc) {
+  /* The following formats can be used:
+
+    YYMMDDhhmmZ
+    YYMMDDhhmm+hh'mm'
+    YYMMDDhhmm-hh'mm'
+    YYMMDDhhmmssZ
+    YYMMDDhhmmss+hh'mm'
+    YYMMDDhhmmss-hh'mm'
+
+    Where:
+
+    YY is the least significant two digits of the year
+    MM is the month (01 to 12)
+    DD is the day (01 to 31)
+    hh is the hour (00 to 23)
+    mm are the minutes (00 to 59)
+    ss are the seconds (00 to 59)
+    Z indicates that local time is GMT, + indicates that local time is
+    later than GMT, and - indicates that local time is earlier than GMT
+    hh' is the absolute value of the offset from GMT in hours
+    mm' is the absolute value of the offset from GMT in minutes */
+  var date = new Date();
+
+  // if YY >= 50 use 19xx, if YY < 50 use 20xx
+  var year = parseInt(utc.substr(0, 2), 10);
+  year = (year >= 50) ? 1900 + year : 2000 + year;
+  var MM = parseInt(utc.substr(2, 2), 10) - 1; // use 0-11 for month
+  var DD = parseInt(utc.substr(4, 2), 10);
+  var hh = parseInt(utc.substr(6, 2), 10);
+  var mm = parseInt(utc.substr(8, 2), 10);
+  var ss = 0;
+
+  // not just YYMMDDhhmmZ
+  if(utc.length > 11) {
+    // get character after minutes
+    var c = utc.charAt(10);
+    var end = 10;
+
+    // see if seconds are present
+    if(c !== '+' && c !== '-') {
+      // get seconds
+      ss = parseInt(utc.substr(10, 2), 10);
+      end += 2;
+    }
+  }
+
+  // update date
+  date.setUTCFullYear(year, MM, DD);
+  date.setUTCHours(hh, mm, ss, 0);
+
+  if(end) {
+    // get +/- after end of time
+    c = utc.charAt(end);
+    if(c === '+' || c === '-') {
+      // get hours+minutes offset
+      var hhoffset = parseInt(utc.substr(end + 1, 2), 10);
+      var mmoffset = parseInt(utc.substr(end + 4, 2), 10);
+
+      // calculate offset in milliseconds
+      var offset = hhoffset * 60 + mmoffset;
+      offset *= 60000;
+
+      // apply offset
+      if(c === '+') {
+        date.setTime(+date - offset);
+      } else {
+        date.setTime(+date + offset);
+      }
+    }
+  }
+
+  return date;
+};
+
+/**
+ * Converts a GeneralizedTime value to a date.
+ *
+ * @param gentime the GeneralizedTime value to convert.
+ *
+ * @return the date.
+ */
+asn1.generalizedTimeToDate = function(gentime) {
+  /* The following formats can be used:
+
+    YYYYMMDDHHMMSS
+    YYYYMMDDHHMMSS.fff
+    YYYYMMDDHHMMSSZ
+    YYYYMMDDHHMMSS.fffZ
+    YYYYMMDDHHMMSS+hh'mm'
+    YYYYMMDDHHMMSS.fff+hh'mm'
+    YYYYMMDDHHMMSS-hh'mm'
+    YYYYMMDDHHMMSS.fff-hh'mm'
+
+    Where:
+
+    YYYY is the year
+    MM is the month (01 to 12)
+    DD is the day (01 to 31)
+    hh is the hour (00 to 23)
+    mm are the minutes (00 to 59)
+    ss are the seconds (00 to 59)
+    .fff is the second fraction, accurate to three decimal places
+    Z indicates that local time is GMT, + indicates that local time is
+    later than GMT, and - indicates that local time is earlier than GMT
+    hh' is the absolute value of the offset from GMT in hours
+    mm' is the absolute value of the offset from GMT in minutes */
+  var date = new Date();
+
+  var YYYY = parseInt(gentime.substr(0, 4), 10);
+  var MM = parseInt(gentime.substr(4, 2), 10) - 1; // use 0-11 for month
+  var DD = parseInt(gentime.substr(6, 2), 10);
+  var hh = parseInt(gentime.substr(8, 2), 10);
+  var mm = parseInt(gentime.substr(10, 2), 10);
+  var ss = parseInt(gentime.substr(12, 2), 10);
+  var fff = 0;
+  var offset = 0;
+  var isUTC = false;
+
+  if(gentime.charAt(gentime.length - 1) === 'Z') {
+    isUTC = true;
+  }
+
+  var end = gentime.length - 5, c = gentime.charAt(end);
+  if(c === '+' || c === '-') {
+    // get hours+minutes offset
+    var hhoffset = parseInt(gentime.substr(end + 1, 2), 10);
+    var mmoffset = parseInt(gentime.substr(end + 4, 2), 10);
+
+    // calculate offset in milliseconds
+    offset = hhoffset * 60 + mmoffset;
+    offset *= 60000;
+
+    // apply offset
+    if(c === '+') {
+      offset *= -1;
+    }
+
+    isUTC = true;
+  }
+
+  // check for second fraction
+  if(gentime.charAt(14) === '.') {
+    fff = parseFloat(gentime.substr(14), 10) * 1000;
+  }
+
+  if(isUTC) {
+    date.setUTCFullYear(YYYY, MM, DD);
+    date.setUTCHours(hh, mm, ss, fff);
+
+    // apply offset
+    date.setTime(+date + offset);
+  } else {
+    date.setFullYear(YYYY, MM, DD);
+    date.setHours(hh, mm, ss, fff);
+  }
+
+  return date;
+};
+
+
+/**
+ * Converts a date to a UTCTime value.
+ *
+ * Note: GeneralizedTime has 4 digits for the year and is used for X.509
+ * dates passed 2049. Converting to a GeneralizedTime hasn't been
+ * implemented yet.
+ *
+ * @param date the date to convert.
+ *
+ * @return the UTCTime value.
+ */
+asn1.dateToUtcTime = function(date) {
+  var rval = '';
+
+  // create format YYMMDDhhmmssZ
+  var format = [];
+  format.push(('' + date.getUTCFullYear()).substr(2));
+  format.push('' + (date.getUTCMonth() + 1));
+  format.push('' + date.getUTCDate());
+  format.push('' + date.getUTCHours());
+  format.push('' + date.getUTCMinutes());
+  format.push('' + date.getUTCSeconds());
+
+  // ensure 2 digits are used for each format entry
+  for(var i = 0; i < format.length; ++i) {
+    if(format[i].length < 2) {
+      rval += '0';
+    }
+    rval += format[i];
+  }
+  rval += 'Z';
+
+  return rval;
+};
+
+/**
+ * Converts a javascript integer to a DER-encoded byte buffer to be used
+ * as the value for an INTEGER type.
+ *
+ * @param x the integer.
+ *
+ * @return the byte buffer.
+ */
+asn1.integerToDer = function(x) {
+  var rval = forge.util.createBuffer();
+  if(x >= -0x80 && x < 0x80) {
+    return rval.putSignedInt(x, 8);
+  }
+  if(x >= -0x8000 && x < 0x8000) {
+    return rval.putSignedInt(x, 16);
+  }
+  if(x >= -0x800000 && x < 0x800000) {
+    return rval.putSignedInt(x, 24);
+  }
+  if(x >= -0x80000000 && x < 0x80000000) {
+    return rval.putSignedInt(x, 32);
+  }
+  var error = new Error('Integer too large; max is 32-bits.');
+  error.integer = x;
+  throw error;
+};
+
+/**
+ * Converts a DER-encoded byte buffer to a javascript integer. This is
+ * typically used to decode the value of an INTEGER type.
+ *
+ * @param bytes the byte buffer.
+ *
+ * @return the integer.
+ */
+asn1.derToInteger = function(bytes) {
+  // wrap in buffer if needed
+  if(typeof bytes === 'string') {
+    bytes = forge.util.createBuffer(bytes);
+  }
+
+  var n = bytes.length() * 8;
+  if(n > 32) {
+    throw new Error('Integer too large; max is 32-bits.');
+  }
+  return bytes.getSignedInt(n);
+};
+
+/**
+ * Validates the that given ASN.1 object is at least a super set of the
+ * given ASN.1 structure. Only tag classes and types are checked. An
+ * optional map may also be provided to capture ASN.1 values while the
+ * structure is checked.
+ *
+ * To capture an ASN.1 value, set an object in the validator's 'capture'
+ * parameter to the key to use in the capture map. To capture the full
+ * ASN.1 object, specify 'captureAsn1'.
+ *
+ * Objects in the validator may set a field 'optional' to true to indicate
+ * that it isn't necessary to pass validation.
+ *
+ * @param obj the ASN.1 object to validate.
+ * @param v the ASN.1 structure validator.
+ * @param capture an optional map to capture values in.
+ * @param errors an optional array for storing validation errors.
+ *
+ * @return true on success, false on failure.
+ */
+asn1.validate = function(obj, v, capture, errors) {
+  var rval = false;
+
+  // ensure tag class and type are the same if specified
+  if((obj.tagClass === v.tagClass || typeof(v.tagClass) === 'undefined') &&
+    (obj.type === v.type || typeof(v.type) === 'undefined')) {
+    // ensure constructed flag is the same if specified
+    if(obj.constructed === v.constructed ||
+      typeof(v.constructed) === 'undefined') {
+      rval = true;
+
+      // handle sub values
+      if(v.value && forge.util.isArray(v.value)) {
+        var j = 0;
+        for(var i = 0; rval && i < v.value.length; ++i) {
+          rval = v.value[i].optional || false;
+          if(obj.value[j]) {
+            rval = asn1.validate(obj.value[j], v.value[i], capture, errors);
+            if(rval) {
+              ++j;
+            } else if(v.value[i].optional) {
+              rval = true;
+            }
+          }
+          if(!rval && errors) {
+            errors.push(
+              '[' + v.name + '] ' +
+              'Tag class "' + v.tagClass + '", type "' +
+              v.type + '" expected value length "' +
+              v.value.length + '", got "' +
+              obj.value.length + '"');
+          }
+        }
+      }
+
+      if(rval && capture) {
+        if(v.capture) {
+          capture[v.capture] = obj.value;
+        }
+        if(v.captureAsn1) {
+          capture[v.captureAsn1] = obj;
+        }
+      }
+    } else if(errors) {
+      errors.push(
+        '[' + v.name + '] ' +
+        'Expected constructed "' + v.constructed + '", got "' +
+        obj.constructed + '"');
+    }
+  } else if(errors) {
+    if(obj.tagClass !== v.tagClass) {
+      errors.push(
+        '[' + v.name + '] ' +
+        'Expected tag class "' + v.tagClass + '", got "' +
+        obj.tagClass + '"');
+    }
+    if(obj.type !== v.type) {
+      errors.push(
+        '[' + v.name + '] ' +
+        'Expected type "' + v.type + '", got "' + obj.type + '"');
+    }
+  }
+  return rval;
+};
+
+// regex for testing for non-latin characters
+var _nonLatinRegex = /[^\\u0000-\\u00ff]/;
+
+/**
+ * Pretty prints an ASN.1 object to a string.
+ *
+ * @param obj the object to write out.
+ * @param level the level in the tree.
+ * @param indentation the indentation to use.
+ *
+ * @return the string.
+ */
+asn1.prettyPrint = function(obj, level, indentation) {
+  var rval = '';
+
+  // set default level and indentation
+  level = level || 0;
+  indentation = indentation || 2;
+
+  // start new line for deep levels
+  if(level > 0) {
+    rval += '\n';
+  }
+
+  // create indent
+  var indent = '';
+  for(var i = 0; i < level * indentation; ++i) {
+    indent += ' ';
+  }
+
+  // print class:type
+  rval += indent + 'Tag: ';
+  switch(obj.tagClass) {
+  case asn1.Class.UNIVERSAL:
+    rval += 'Universal:';
+    break;
+  case asn1.Class.APPLICATION:
+    rval += 'Application:';
+    break;
+  case asn1.Class.CONTEXT_SPECIFIC:
+    rval += 'Context-Specific:';
+    break;
+  case asn1.Class.PRIVATE:
+    rval += 'Private:';
+    break;
+  }
+
+  if(obj.tagClass === asn1.Class.UNIVERSAL) {
+    rval += obj.type;
+
+    // known types
+    switch(obj.type) {
+    case asn1.Type.NONE:
+      rval += ' (None)';
+      break;
+    case asn1.Type.BOOLEAN:
+      rval += ' (Boolean)';
+      break;
+    case asn1.Type.BITSTRING:
+      rval += ' (Bit string)';
+      break;
+    case asn1.Type.INTEGER:
+      rval += ' (Integer)';
+      break;
+    case asn1.Type.OCTETSTRING:
+      rval += ' (Octet string)';
+      break;
+    case asn1.Type.NULL:
+      rval += ' (Null)';
+      break;
+    case asn1.Type.OID:
+      rval += ' (Object Identifier)';
+      break;
+    case asn1.Type.ODESC:
+      rval += ' (Object Descriptor)';
+      break;
+    case asn1.Type.EXTERNAL:
+      rval += ' (External or Instance of)';
+      break;
+    case asn1.Type.REAL:
+      rval += ' (Real)';
+      break;
+    case asn1.Type.ENUMERATED:
+      rval += ' (Enumerated)';
+      break;
+    case asn1.Type.EMBEDDED:
+      rval += ' (Embedded PDV)';
+      break;
+    case asn1.Type.UTF8:
+      rval += ' (UTF8)';
+      break;
+    case asn1.Type.ROID:
+      rval += ' (Relative Object Identifier)';
+      break;
+    case asn1.Type.SEQUENCE:
+      rval += ' (Sequence)';
+      break;
+    case asn1.Type.SET:
+      rval += ' (Set)';
+      break;
+    case asn1.Type.PRINTABLESTRING:
+      rval += ' (Printable String)';
+      break;
+    case asn1.Type.IA5String:
+      rval += ' (IA5String (ASCII))';
+      break;
+    case asn1.Type.UTCTIME:
+      rval += ' (UTC time)';
+      break;
+    case asn1.Type.GENERALIZEDTIME:
+      rval += ' (Generalized time)';
+      break;
+    case asn1.Type.BMPSTRING:
+      rval += ' (BMP String)';
+      break;
+    }
+  } else {
+    rval += obj.type;
+  }
+
+  rval += '\n';
+  rval += indent + 'Constructed: ' + obj.constructed + '\n';
+
+  if(obj.composed) {
+    var subvalues = 0;
+    var sub = '';
+    for(var i = 0; i < obj.value.length; ++i) {
+      if(obj.value[i] !== undefined) {
+        subvalues += 1;
+        sub += asn1.prettyPrint(obj.value[i], level + 1, indentation);
+        if((i + 1) < obj.value.length) {
+          sub += ',';
+        }
+      }
+    }
+    rval += indent + 'Sub values: ' + subvalues + sub;
+  } else {
+    rval += indent + 'Value: ';
+    if(obj.type === asn1.Type.OID) {
+      var oid = asn1.derToOid(obj.value);
+      rval += oid;
+      if(forge.pki && forge.pki.oids) {
+        if(oid in forge.pki.oids) {
+          rval += ' (' + forge.pki.oids[oid] + ') ';
+        }
+      }
+    }
+    if(obj.type === asn1.Type.INTEGER) {
+      try {
+        rval += asn1.derToInteger(obj.value);
+      } catch(ex) {
+        rval += '0x' + forge.util.bytesToHex(obj.value);
+      }
+    } else if(obj.type === asn1.Type.OCTETSTRING) {
+      if(!_nonLatinRegex.test(obj.value)) {
+        rval += '(' + obj.value + ') ';
+      }
+      rval += '0x' + forge.util.bytesToHex(obj.value);
+    } else if(obj.type === asn1.Type.UTF8) {
+      rval += forge.util.decodeUtf8(obj.value);
+    } else if(obj.type === asn1.Type.PRINTABLESTRING ||
+      obj.type === asn1.Type.IA5String) {
+      rval += obj.value;
+    } else if(_nonLatinRegex.test(obj.value)) {
+      rval += '0x' + forge.util.bytesToHex(obj.value);
+    } else if(obj.value.length === 0) {
+      rval += '[null]';
+    } else {
+      rval += obj.value;
+    }
+  }
+
+  return rval;
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'asn1';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './util', './oids'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/cipher.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/cipher.js
new file mode 100644
index 0000000..58db4ce
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/cipher.js
@@ -0,0 +1,286 @@
+/**
+ * Cipher base API.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+forge.cipher = forge.cipher || {};
+
+// registered algorithms
+forge.cipher.algorithms = forge.cipher.algorithms || {};
+
+/**
+ * Creates a cipher object that can be used to encrypt data using the given
+ * algorithm and key. The algorithm may be provided as a string value for a
+ * previously registered algorithm or it may be given as a cipher algorithm
+ * API object.
+ *
+ * @param algorithm the algorithm to use, either a string or an algorithm API
+ *          object.
+ * @param key the key to use, as a binary-encoded string of bytes or a
+ *          byte buffer.
+ *
+ * @return the cipher.
+ */
+forge.cipher.createCipher = function(algorithm, key) {
+  var api = algorithm;
+  if(typeof api === 'string') {
+    api = forge.cipher.getAlgorithm(api);
+    if(api) {
+      api = api();
+    }
+  }
+  if(!api) {
+    throw new Error('Unsupported algorithm: ' + algorithm);
+  }
+
+  // assume block cipher
+  return new forge.cipher.BlockCipher({
+    algorithm: api,
+    key: key,
+    decrypt: false
+  });
+};
+
+/**
+ * Creates a decipher object that can be used to decrypt data using the given
+ * algorithm and key. The algorithm may be provided as a string value for a
+ * previously registered algorithm or it may be given as a cipher algorithm
+ * API object.
+ *
+ * @param algorithm the algorithm to use, either a string or an algorithm API
+ *          object.
+ * @param key the key to use, as a binary-encoded string of bytes or a
+ *          byte buffer.
+ *
+ * @return the cipher.
+ */
+forge.cipher.createDecipher = function(algorithm, key) {
+  var api = algorithm;
+  if(typeof api === 'string') {
+    api = forge.cipher.getAlgorithm(api);
+    if(api) {
+      api = api();
+    }
+  }
+  if(!api) {
+    throw new Error('Unsupported algorithm: ' + algorithm);
+  }
+
+  // assume block cipher
+  return new forge.cipher.BlockCipher({
+    algorithm: api,
+    key: key,
+    decrypt: true
+  });
+};
+
+/**
+ * Registers an algorithm by name. If the name was already registered, the
+ * algorithm API object will be overwritten.
+ *
+ * @param name the name of the algorithm.
+ * @param algorithm the algorithm API object.
+ */
+forge.cipher.registerAlgorithm = function(name, algorithm) {
+  name = name.toUpperCase();
+  forge.cipher.algorithms[name] = algorithm;
+};
+
+/**
+ * Gets a registered algorithm by name.
+ *
+ * @param name the name of the algorithm.
+ *
+ * @return the algorithm, if found, null if not.
+ */
+forge.cipher.getAlgorithm = function(name) {
+  name = name.toUpperCase();
+  if(name in forge.cipher.algorithms) {
+    return forge.cipher.algorithms[name];
+  }
+  return null;
+};
+
+var BlockCipher = forge.cipher.BlockCipher = function(options) {
+  this.algorithm = options.algorithm;
+  this.mode = this.algorithm.mode;
+  this.blockSize = this.mode.blockSize;
+  this._finish = false;
+  this._input = null;
+  this.output = null;
+  this._op = options.decrypt ? this.mode.decrypt : this.mode.encrypt;
+  this._decrypt = options.decrypt;
+  this.algorithm.initialize(options);
+};
+
+/**
+ * Starts or restarts the encryption or decryption process, whichever
+ * was previously configured.
+ *
+ * For non-GCM mode, the IV may be a binary-encoded string of bytes, an array
+ * of bytes, a byte buffer, or an array of 32-bit integers. If the IV is in
+ * bytes, then it must be Nb (16) bytes in length. If the IV is given in as
+ * 32-bit integers, then it must be 4 integers long.
+ *
+ * For GCM-mode, the IV must be given as a binary-encoded string of bytes or
+ * a byte buffer. The number of bytes should be 12 (96 bits) as recommended
+ * by NIST SP-800-38D but another length may be given.
+ *
+ * @param options the options to use:
+ *          iv the initialization vector to use as a binary-encoded string of
+ *            bytes, null to reuse the last ciphered block from a previous
+ *            update() (this "residue" method is for legacy support only).
+ *          additionalData additional authentication data as a binary-encoded
+ *            string of bytes, for 'GCM' mode, (default: none).
+ *          tagLength desired length of authentication tag, in bits, for
+ *            'GCM' mode (0-128, default: 128).
+ *          tag the authentication tag to check if decrypting, as a
+ *             binary-encoded string of bytes.
+ *          output the output the buffer to write to, null to create one.
+ */
+BlockCipher.prototype.start = function(options) {
+  options = options || {};
+  var opts = {};
+  for(var key in options) {
+    opts[key] = options[key];
+  }
+  opts.decrypt = this._decrypt;
+  this._finish = false;
+  this._input = forge.util.createBuffer();
+  this.output = options.output || forge.util.createBuffer();
+  this.mode.start(opts);
+};
+
+/**
+ * Updates the next block according to the cipher mode.
+ *
+ * @param input the buffer to read from.
+ */
+BlockCipher.prototype.update = function(input) {
+  if(!this._finish) {
+    // not finishing, so fill the input buffer with more input
+    this._input.putBuffer(input);
+  }
+
+  // do cipher operation while input contains full blocks or if finishing
+  while(this._input.length() >= this.blockSize ||
+    (this._input.length() > 0 && this._finish)) {
+    this._op.call(this.mode, this._input, this.output);
+  }
+
+  // free consumed memory from input buffer
+  this._input.compact();
+};
+
+/**
+ * Finishes encrypting or decrypting.
+ *
+ * @param pad a padding function to use in CBC mode, null for default,
+ *          signature(blockSize, buffer, decrypt).
+ *
+ * @return true if successful, false on error.
+ */
+BlockCipher.prototype.finish = function(pad) {
+  // backwards-compatibility w/deprecated padding API
+  // Note: will overwrite padding functions even after another start() call
+  if(pad && this.mode.name === 'CBC') {
+    this.mode.pad = function(input) {
+      return pad(this.blockSize, input, false);
+    };
+    this.mode.unpad = function(output) {
+      return pad(this.blockSize, output, true);
+    };
+  }
+
+  // build options for padding and afterFinish functions
+  var options = {};
+  options.decrypt = this._decrypt;
+
+  // get # of bytes that won't fill a block
+  options.overflow = this._input.length() % this.blockSize;
+
+  if(!this._decrypt && this.mode.pad) {
+    if(!this.mode.pad(this._input, options)) {
+      return false;
+    }
+  }
+
+  // do final update
+  this._finish = true;
+  this.update();
+
+  if(this._decrypt && this.mode.unpad) {
+    if(!this.mode.unpad(this.output, options)) {
+      return false;
+    }
+  }
+
+  if(this.mode.afterFinish) {
+    if(!this.mode.afterFinish(this.output, options)) {
+      return false;
+    }
+  }
+
+  return true;
+};
+
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'cipher';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/cipherModes.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/cipherModes.js
new file mode 100644
index 0000000..9ed318f
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/cipherModes.js
@@ -0,0 +1,817 @@
+/**
+ * Supported cipher modes.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+forge.cipher = forge.cipher || {};
+
+// supported cipher modes
+var modes = forge.cipher.modes = forge.cipher.modes || {};
+
+
+/** Electronic codebook (ECB) (Don't use this; it's not secure) **/
+
+modes.ecb = function(options) {
+  options = options || {};
+  this.name = 'ECB';
+  this.cipher = options.cipher;
+  this.blockSize = options.blockSize || 16;
+  this._blocks = this.blockSize / 4;
+  this._inBlock = new Array(this._blocks);
+  this._outBlock = new Array(this._blocks);
+};
+
+modes.ecb.prototype.start = function(options) {};
+
+modes.ecb.prototype.encrypt = function(input, output) {
+  // get next block
+  for(var i = 0; i < this._blocks; ++i) {
+    this._inBlock[i] = input.getInt32();
+  }
+
+  // encrypt block
+  this.cipher.encrypt(this._inBlock, this._outBlock);
+
+  // write output
+  for(var i = 0; i < this._blocks; ++i) {
+    output.putInt32(this._outBlock[i]);
+  }
+};
+
+modes.ecb.prototype.decrypt = function(input, output) {
+  // get next block
+  for(var i = 0; i < this._blocks; ++i) {
+    this._inBlock[i] = input.getInt32();
+  }
+
+  // decrypt block
+  this.cipher.decrypt(this._inBlock, this._outBlock);
+
+  // write output
+  for(var i = 0; i < this._blocks; ++i) {
+    output.putInt32(this._outBlock[i]);
+  }
+};
+
+modes.ecb.prototype.pad = function(input, options) {
+  // add PKCS#7 padding to block (each pad byte is the
+  // value of the number of pad bytes)
+  var padding = (input.length() === this.blockSize ?
+    this.blockSize : (this.blockSize - input.length()));
+  input.fillWithByte(padding, padding);
+  return true;
+};
+
+modes.ecb.prototype.unpad = function(output, options) {
+  // check for error: input data not a multiple of blockSize
+  if(options.overflow > 0) {
+    return false;
+  }
+
+  // ensure padding byte count is valid
+  var len = output.length();
+  var count = output.at(len - 1);
+  if(count > (this.blockSize << 2)) {
+    return false;
+  }
+
+  // trim off padding bytes
+  output.truncate(count);
+  return true;
+};
+
+
+/** Cipher-block Chaining (CBC) **/
+
+modes.cbc = function(options) {
+  options = options || {};
+  this.name = 'CBC';
+  this.cipher = options.cipher;
+  this.blockSize = options.blockSize || 16;
+  this._blocks = this.blockSize / 4;
+  this._inBlock = new Array(this._blocks);
+  this._outBlock = new Array(this._blocks);
+};
+
+modes.cbc.prototype.start = function(options) {
+  // Note: legacy support for using IV residue (has security flaws)
+  // if IV is null, reuse block from previous processing
+  if(options.iv === null) {
+    // must have a previous block
+    if(!this._prev) {
+      throw new Error('Invalid IV parameter.');
+    }
+    this._iv = this._prev.slice(0);
+  } else if(!('iv' in options)) {
+    throw new Error('Invalid IV parameter.');
+  } else {
+    // save IV as "previous" block
+    this._iv = transformIV(options.iv);
+    this._prev = this._iv.slice(0);
+  }
+};
+
+modes.cbc.prototype.encrypt = function(input, output) {
+  // get next block
+  // CBC XOR's IV (or previous block) with plaintext
+  for(var i = 0; i < this._blocks; ++i) {
+    this._inBlock[i] = this._prev[i] ^ input.getInt32();
+  }
+
+  // encrypt block
+  this.cipher.encrypt(this._inBlock, this._outBlock);
+
+  // write output, save previous block
+  for(var i = 0; i < this._blocks; ++i) {
+    output.putInt32(this._outBlock[i]);
+  }
+  this._prev = this._outBlock;
+};
+
+modes.cbc.prototype.decrypt = function(input, output) {
+  // get next block
+  for(var i = 0; i < this._blocks; ++i) {
+    this._inBlock[i] = input.getInt32();
+  }
+
+  // decrypt block
+  this.cipher.decrypt(this._inBlock, this._outBlock);
+
+  // write output, save previous ciphered block
+  // CBC XOR's IV (or previous block) with ciphertext
+  for(var i = 0; i < this._blocks; ++i) {
+    output.putInt32(this._prev[i] ^ this._outBlock[i]);
+  }
+  this._prev = this._inBlock.slice(0);
+};
+
+modes.cbc.prototype.pad = function(input, options) {
+  // add PKCS#7 padding to block (each pad byte is the
+  // value of the number of pad bytes)
+  var padding = (input.length() === this.blockSize ?
+    this.blockSize : (this.blockSize - input.length()));
+  input.fillWithByte(padding, padding);
+  return true;
+};
+
+modes.cbc.prototype.unpad = function(output, options) {
+  // check for error: input data not a multiple of blockSize
+  if(options.overflow > 0) {
+    return false;
+  }
+
+  // ensure padding byte count is valid
+  var len = output.length();
+  var count = output.at(len - 1);
+  if(count > (this.blockSize << 2)) {
+    return false;
+  }
+
+  // trim off padding bytes
+  output.truncate(count);
+  return true;
+};
+
+
+/** Cipher feedback (CFB) **/
+
+modes.cfb = function(options) {
+  options = options || {};
+  this.name = 'CFB';
+  this.cipher = options.cipher;
+  this.blockSize = options.blockSize || 16;
+  this._blocks = this.blockSize / 4;
+  this._inBlock = null;
+  this._outBlock = new Array(this._blocks);
+};
+
+modes.cfb.prototype.start = function(options) {
+  if(!('iv' in options)) {
+    throw new Error('Invalid IV parameter.');
+  }
+  // use IV as first input
+  this._iv = transformIV(options.iv);
+  this._inBlock = this._iv.slice(0);
+};
+
+modes.cfb.prototype.encrypt = function(input, output) {
+  // encrypt block
+  this.cipher.encrypt(this._inBlock, this._outBlock);
+
+  // XOR input with output, write input as output
+  for(var i = 0; i < this._blocks; ++i) {
+    this._inBlock[i] = input.getInt32() ^ this._outBlock[i];
+    output.putInt32(this._inBlock[i]);
+  }
+};
+
+modes.cfb.prototype.decrypt = function(input, output) {
+  // encrypt block (CFB always uses encryption mode)
+  this.cipher.encrypt(this._inBlock, this._outBlock);
+
+  // XOR input with output
+  for(var i = 0; i < this._blocks; ++i) {
+    this._inBlock[i] = input.getInt32();
+    output.putInt32(this._inBlock[i] ^ this._outBlock[i]);
+  }
+};
+
+modes.cfb.prototype.afterFinish = function(output, options) {
+  // handle stream mode truncation
+  if(options.overflow > 0) {
+    output.truncate(this.blockSize - options.overflow);
+  }
+  return true;
+};
+
+/** Output feedback (OFB) **/
+
+modes.ofb = function(options) {
+  options = options || {};
+  this.name = 'OFB';
+  this.cipher = options.cipher;
+  this.blockSize = options.blockSize || 16;
+  this._blocks = this.blockSize / 4;
+  this._inBlock = null;
+  this._outBlock = new Array(this._blocks);
+};
+
+modes.ofb.prototype.start = function(options) {
+  if(!('iv' in options)) {
+    throw new Error('Invalid IV parameter.');
+  }
+  // use IV as first input
+  this._iv = transformIV(options.iv);
+  this._inBlock = this._iv.slice(0);
+};
+
+modes.ofb.prototype.encrypt = function(input, output) {
+  // encrypt block (OFB always uses encryption mode)
+  this.cipher.encrypt(this._inBlock, this._outBlock);
+
+  // XOR input with output and update next input
+  for(var i = 0; i < this._blocks; ++i) {
+    output.putInt32(input.getInt32() ^ this._outBlock[i]);
+    this._inBlock[i] = this._outBlock[i];
+  }
+};
+
+modes.ofb.prototype.decrypt = modes.ofb.prototype.encrypt;
+
+modes.ofb.prototype.afterFinish = function(output, options) {
+  // handle stream mode truncation
+  if(options.overflow > 0) {
+    output.truncate(this.blockSize - options.overflow);
+  }
+  return true;
+};
+
+
+/** Counter (CTR) **/
+
+modes.ctr = function(options) {
+  options = options || {};
+  this.name = 'CTR';
+  this.cipher = options.cipher;
+  this.blockSize = options.blockSize || 16;
+  this._blocks = this.blockSize / 4;
+  this._inBlock = null;
+  this._outBlock = new Array(this._blocks);
+};
+
+modes.ctr.prototype.start = function(options) {
+  if(!('iv' in options)) {
+    throw new Error('Invalid IV parameter.');
+  }
+  // use IV as first input
+  this._iv = transformIV(options.iv);
+  this._inBlock = this._iv.slice(0);
+};
+
+modes.ctr.prototype.encrypt = function(input, output) {
+  // encrypt block (CTR always uses encryption mode)
+  this.cipher.encrypt(this._inBlock, this._outBlock);
+
+  // increment counter (input block)
+  inc32(this._inBlock);
+
+  // XOR input with output
+  for(var i = 0; i < this._blocks; ++i) {
+    output.putInt32(input.getInt32() ^ this._outBlock[i]);
+  }
+};
+
+modes.ctr.prototype.decrypt = modes.ctr.prototype.encrypt;
+
+modes.ctr.prototype.afterFinish = function(output, options) {
+  // handle stream mode truncation
+  if(options.overflow > 0) {
+    output.truncate(this.blockSize - options.overflow);
+  }
+  return true;
+};
+
+
+/** Galois/Counter Mode (GCM) **/
+
+modes.gcm = function(options) {
+  options = options || {};
+  this.name = 'GCM';
+  this.cipher = options.cipher;
+  this.blockSize = options.blockSize || 16;
+  this._blocks = this.blockSize / 4;
+  this._inBlock = new Array(this._blocks);
+  this._outBlock = new Array(this._blocks);
+
+  // R is actually this value concatenated with 120 more zero bits, but
+  // we only XOR against R so the other zeros have no effect -- we just
+  // apply this value to the first integer in a block
+  this._R = 0xE1000000;
+};
+
+modes.gcm.prototype.start = function(options) {
+  if(!('iv' in options)) {
+    throw new Error('Invalid IV parameter.');
+  }
+  // ensure IV is a byte buffer
+  var iv = forge.util.createBuffer(options.iv);
+
+  // no ciphered data processed yet
+  this._cipherLength = 0;
+
+  // default additional data is none
+  var additionalData;
+  if('additionalData' in options) {
+    additionalData = forge.util.createBuffer(options.additionalData);
+  } else {
+    additionalData = forge.util.createBuffer();
+  }
+
+  // default tag length is 128 bits
+  if('tagLength' in options) {
+    this._tagLength = options.tagLength;
+  } else {
+    this._tagLength = 128;
+  }
+
+  // if tag is given, ensure tag matches tag length
+  this._tag = null;
+  if(options.decrypt) {
+    // save tag to check later
+    this._tag = forge.util.createBuffer(options.tag).getBytes();
+    if(this._tag.length !== (this._tagLength / 8)) {
+      throw new Error('Authentication tag does not match tag length.');
+    }
+  }
+
+  // create tmp storage for hash calculation
+  this._hashBlock = new Array(this._blocks);
+
+  // no tag generated yet
+  this.tag = null;
+
+  // generate hash subkey
+  // (apply block cipher to "zero" block)
+  this._hashSubkey = new Array(this._blocks);
+  this.cipher.encrypt([0, 0, 0, 0], this._hashSubkey);
+
+  // generate table M
+  // use 4-bit tables (32 component decomposition of a 16 byte value)
+  // 8-bit tables take more space and are known to have security
+  // vulnerabilities (in native implementations)
+  this.componentBits = 4;
+  this._m = this.generateHashTable(this._hashSubkey, this.componentBits);
+
+  // Note: support IV length different from 96 bits? (only supporting
+  // 96 bits is recommended by NIST SP-800-38D)
+  // generate J_0
+  var ivLength = iv.length();
+  if(ivLength === 12) {
+    // 96-bit IV
+    this._j0 = [iv.getInt32(), iv.getInt32(), iv.getInt32(), 1];
+  } else {
+    // IV is NOT 96-bits
+    this._j0 = [0, 0, 0, 0];
+    while(iv.length() > 0) {
+      this._j0 = this.ghash(
+        this._hashSubkey, this._j0,
+        [iv.getInt32(), iv.getInt32(), iv.getInt32(), iv.getInt32()]);
+    }
+    this._j0 = this.ghash(
+      this._hashSubkey, this._j0, [0, 0].concat(from64To32(ivLength * 8)));
+  }
+
+  // generate ICB (initial counter block)
+  this._inBlock = this._j0.slice(0);
+  inc32(this._inBlock);
+
+  // consume authentication data
+  additionalData = forge.util.createBuffer(additionalData);
+  // save additional data length as a BE 64-bit number
+  this._aDataLength = from64To32(additionalData.length() * 8);
+  // pad additional data to 128 bit (16 byte) block size
+  var overflow = additionalData.length() % this.blockSize;
+  if(overflow) {
+    additionalData.fillWithByte(0, this.blockSize - overflow);
+  }
+  this._s = [0, 0, 0, 0];
+  while(additionalData.length() > 0) {
+    this._s = this.ghash(this._hashSubkey, this._s, [
+      additionalData.getInt32(),
+      additionalData.getInt32(),
+      additionalData.getInt32(),
+      additionalData.getInt32()
+    ]);
+  }
+};
+
+modes.gcm.prototype.encrypt = function(input, output) {
+  // encrypt block
+  this.cipher.encrypt(this._inBlock, this._outBlock);
+
+  // increment counter (input block)
+  inc32(this._inBlock);
+
+  // save input length
+  var inputLength = input.length();
+
+  // XOR input with output
+  for(var i = 0; i < this._blocks; ++i) {
+    this._outBlock[i] ^= input.getInt32();
+  }
+
+  // handle overflow prior to hashing
+  if(inputLength < this.blockSize) {
+    // get block overflow
+    var overflow = inputLength % this.blockSize;
+    this._cipherLength += overflow;
+
+    // truncate for hash function
+    var tmp = forge.util.createBuffer();
+    tmp.putInt32(this._outBlock[0]);
+    tmp.putInt32(this._outBlock[1]);
+    tmp.putInt32(this._outBlock[2]);
+    tmp.putInt32(this._outBlock[3]);
+    tmp.truncate(this.blockSize - overflow);
+    this._outBlock[0] = tmp.getInt32();
+    this._outBlock[1] = tmp.getInt32();
+    this._outBlock[2] = tmp.getInt32();
+    this._outBlock[3] = tmp.getInt32();
+  } else {
+    this._cipherLength += this.blockSize;
+  }
+
+  for(var i = 0; i < this._blocks; ++i) {
+    output.putInt32(this._outBlock[i]);
+  }
+
+  // update hash block S
+  this._s = this.ghash(this._hashSubkey, this._s, this._outBlock);
+};
+
+modes.gcm.prototype.decrypt = function(input, output) {
+  // encrypt block (GCM always uses encryption mode)
+  this.cipher.encrypt(this._inBlock, this._outBlock);
+
+  // increment counter (input block)
+  inc32(this._inBlock);
+
+  // save input length
+  var inputLength = input.length();
+
+  // update hash block S
+  this._hashBlock[0] = input.getInt32();
+  this._hashBlock[1] = input.getInt32();
+  this._hashBlock[2] = input.getInt32();
+  this._hashBlock[3] = input.getInt32();
+  this._s = this.ghash(this._hashSubkey, this._s, this._hashBlock);
+
+  // XOR hash input with output
+  for(var i = 0; i < this._blocks; ++i) {
+    output.putInt32(this._outBlock[i] ^ this._hashBlock[i]);
+  }
+
+  // increment cipher data length
+  if(inputLength < this.blockSize) {
+    this._cipherLength += inputLength % this.blockSize;
+  } else {
+    this._cipherLength += this.blockSize;
+  }
+};
+
+modes.gcm.prototype.afterFinish = function(output, options) {
+  var rval = true;
+
+  // handle overflow
+  if(options.overflow) {
+    output.truncate(this.blockSize - options.overflow);
+  }
+
+  // handle authentication tag
+  this.tag = forge.util.createBuffer();
+
+  // concatenate additional data length with cipher length
+  var lengths = this._aDataLength.concat(from64To32(this._cipherLength * 8));
+
+  // include lengths in hash
+  this._s = this.ghash(this._hashSubkey, this._s, lengths);
+
+  // do GCTR(J_0, S)
+  var tag = [];
+  this.cipher.encrypt(this._j0, tag);
+  for(var i = 0; i < this._blocks; ++i) {
+    this.tag.putInt32(this._s[i] ^ tag[i]);
+  }
+
+  // trim tag to length
+  this.tag.truncate(this.tag.length() % (this._tagLength / 8));
+
+  // check authentication tag
+  if(options.decrypt && this.tag.bytes() !== this._tag) {
+    rval = false;
+  }
+
+  return rval;
+};
+
+/**
+ * See NIST SP-800-38D 6.3 (Algorithm 1). This function performs Galois
+ * field multiplication. The field, GF(2^128), is defined by the polynomial:
+ *
+ * x^128 + x^7 + x^2 + x + 1
+ *
+ * Which is represented in little-endian binary form as: 11100001 (0xe1). When
+ * the value of a coefficient is 1, a bit is set. The value R, is the
+ * concatenation of this value and 120 zero bits, yielding a 128-bit value
+ * which matches the block size.
+ *
+ * This function will multiply two elements (vectors of bytes), X and Y, in
+ * the field GF(2^128). The result is initialized to zero. For each bit of
+ * X (out of 128), x_i, if x_i is set, then the result is multiplied (XOR'd)
+ * by the current value of Y. For each bit, the value of Y will be raised by
+ * a power of x (multiplied by the polynomial x). This can be achieved by
+ * shifting Y once to the right. If the current value of Y, prior to being
+ * multiplied by x, has 0 as its LSB, then it is a 127th degree polynomial.
+ * Otherwise, we must divide by R after shifting to find the remainder.
+ *
+ * @param x the first block to multiply by the second.
+ * @param y the second block to multiply by the first.
+ *
+ * @return the block result of the multiplication.
+ */
+modes.gcm.prototype.multiply = function(x, y) {
+  var z_i = [0, 0, 0, 0];
+  var v_i = y.slice(0);
+
+  // calculate Z_128 (block has 128 bits)
+  for(var i = 0; i < 128; ++i) {
+    // if x_i is 0, Z_{i+1} = Z_i (unchanged)
+    // else Z_{i+1} = Z_i ^ V_i
+    // get x_i by finding 32-bit int position, then left shift 1 by remainder
+    var x_i = x[(i / 32) | 0] & (1 << (31 - i % 32));
+    if(x_i) {
+      z_i[0] ^= v_i[0];
+      z_i[1] ^= v_i[1];
+      z_i[2] ^= v_i[2];
+      z_i[3] ^= v_i[3];
+    }
+
+    // if LSB(V_i) is 1, V_i = V_i >> 1
+    // else V_i = (V_i >> 1) ^ R
+    this.pow(v_i, v_i);
+  }
+
+  return z_i;
+};
+
+modes.gcm.prototype.pow = function(x, out) {
+  // if LSB(x) is 1, x = x >>> 1
+  // else x = (x >>> 1) ^ R
+  var lsb = x[3] & 1;
+
+  // always do x >>> 1:
+  // starting with the rightmost integer, shift each integer to the right
+  // one bit, pulling in the bit from the integer to the left as its top
+  // most bit (do this for the last 3 integers)
+  for(var i = 3; i > 0; --i) {
+    out[i] = (x[i] >>> 1) | ((x[i - 1] & 1) << 31);
+  }
+  // shift the first integer normally
+  out[0] = x[0] >>> 1;
+
+  // if lsb was not set, then polynomial had a degree of 127 and doesn't
+  // need to divided; otherwise, XOR with R to find the remainder; we only
+  // need to XOR the first integer since R technically ends w/120 zero bits
+  if(lsb) {
+    out[0] ^= this._R;
+  }
+};
+
+modes.gcm.prototype.tableMultiply = function(x) {
+  // assumes 4-bit tables are used
+  var z = [0, 0, 0, 0];
+  for(var i = 0; i < 32; ++i) {
+    var idx = (i / 8) | 0;
+    var x_i = (x[idx] >>> ((7 - (i % 8)) * 4)) & 0xF;
+    var ah = this._m[i][x_i];
+    z[0] ^= ah[0];
+    z[1] ^= ah[1];
+    z[2] ^= ah[2];
+    z[3] ^= ah[3];
+  }
+  return z;
+};
+
+/**
+ * A continuing version of the GHASH algorithm that operates on a single
+ * block. The hash block, last hash value (Ym) and the new block to hash
+ * are given.
+ *
+ * @param h the hash block.
+ * @param y the previous value for Ym, use [0, 0, 0, 0] for a new hash.
+ * @param x the block to hash.
+ *
+ * @return the hashed value (Ym).
+ */
+modes.gcm.prototype.ghash = function(h, y, x) {
+  y[0] ^= x[0];
+  y[1] ^= x[1];
+  y[2] ^= x[2];
+  y[3] ^= x[3];
+  return this.tableMultiply(y);
+  //return this.multiply(y, h);
+};
+
+/**
+ * Precomputes a table for multiplying against the hash subkey. This
+ * mechanism provides a substantial speed increase over multiplication
+ * performed without a table. The table-based multiplication this table is
+ * for solves X * H by multiplying each component of X by H and then
+ * composing the results together using XOR.
+ *
+ * This function can be used to generate tables with different bit sizes
+ * for the components, however, this implementation assumes there are
+ * 32 components of X (which is a 16 byte vector), therefore each component
+ * takes 4-bits (so the table is constructed with bits=4).
+ *
+ * @param h the hash subkey.
+ * @param bits the bit size for a component.
+ */
+modes.gcm.prototype.generateHashTable = function(h, bits) {
+  // TODO: There are further optimizations that would use only the
+  // first table M_0 (or some variant) along with a remainder table;
+  // this can be explored in the future
+  var multiplier = 8 / bits;
+  var perInt = 4 * multiplier;
+  var size = 16 * multiplier;
+  var m = new Array(size);
+  for(var i = 0; i < size; ++i) {
+    var tmp = [0, 0, 0, 0];
+    var idx = (i / perInt) | 0;
+    var shft = ((perInt - 1 - (i % perInt)) * bits);
+    tmp[idx] = (1 << (bits - 1)) << shft;
+    m[i] = this.generateSubHashTable(this.multiply(tmp, h), bits);
+  }
+  return m;
+};
+
+/**
+ * Generates a table for multiplying against the hash subkey for one
+ * particular component (out of all possible component values).
+ *
+ * @param mid the pre-multiplied value for the middle key of the table.
+ * @param bits the bit size for a component.
+ */
+modes.gcm.prototype.generateSubHashTable = function(mid, bits) {
+  // compute the table quickly by minimizing the number of
+  // POW operations -- they only need to be performed for powers of 2,
+  // all other entries can be composed from those powers using XOR
+  var size = 1 << bits;
+  var half = size >>> 1;
+  var m = new Array(size);
+  m[half] = mid.slice(0);
+  var i = half >>> 1;
+  while(i > 0) {
+    // raise m0[2 * i] and store in m0[i]
+    this.pow(m[2 * i], m[i] = []);
+    i >>= 1;
+  }
+  i = 2;
+  while(i < half) {
+    for(var j = 1; j < i; ++j) {
+      var m_i = m[i];
+      var m_j = m[j];
+      m[i + j] = [
+        m_i[0] ^ m_j[0],
+        m_i[1] ^ m_j[1],
+        m_i[2] ^ m_j[2],
+        m_i[3] ^ m_j[3]
+      ];
+    }
+    i *= 2;
+  }
+  m[0] = [0, 0, 0, 0];
+  /* Note: We could avoid storing these by doing composition during multiply
+  calculate top half using composition by speed is preferred. */
+  for(i = half + 1; i < size; ++i) {
+    var c = m[i ^ half];
+    m[i] = [mid[0] ^ c[0], mid[1] ^ c[1], mid[2] ^ c[2], mid[3] ^ c[3]];
+  }
+  return m;
+};
+
+
+/** Utility functions */
+
+function transformIV(iv) {
+  if(typeof iv === 'string') {
+    // convert iv string into byte buffer
+    iv = forge.util.createBuffer(iv);
+  }
+
+  if(forge.util.isArray(iv) && iv.length > 4) {
+    // convert iv byte array into byte buffer
+    var tmp = iv;
+    iv = forge.util.createBuffer();
+    for(var i = 0; i < tmp.length; ++i) {
+      iv.putByte(tmp[i]);
+    }
+  }
+  if(!forge.util.isArray(iv)) {
+    // convert iv byte buffer into 32-bit integer array
+    iv = [iv.getInt32(), iv.getInt32(), iv.getInt32(), iv.getInt32()];
+  }
+
+  return iv;
+}
+
+function inc32(block) {
+  // increment last 32 bits of block only
+  block[block.length - 1] = (block[block.length - 1] + 1) & 0xFFFFFFFF;
+}
+
+function from64To32(num) {
+  // convert 64-bit number to two BE Int32s
+  return [(num / 0x100000000) | 0, num & 0xFFFFFFFF];
+}
+
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'cipherModes';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/debug.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/debug.js
new file mode 100644
index 0000000..4f7c13d
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/debug.js
@@ -0,0 +1,134 @@
+/**
+ * Debugging support for web applications.
+ *
+ * @author David I. Lehn <dlehn@digitalbazaar.com>
+ *
+ * Copyright 2008-2013 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+/* DEBUG API */
+forge.debug = forge.debug || {};
+
+// Private storage for debugging.
+// Useful to expose data that is otherwise unviewable behind closures.
+// NOTE: remember that this can hold references to data and cause leaks!
+// format is "forge._debug.<modulename>.<dataname> = data"
+// Example:
+// (function() {
+//   var cat = 'forge.test.Test'; // debugging category
+//   var sState = {...}; // local state
+//   forge.debug.set(cat, 'sState', sState);
+// })();
+forge.debug.storage = {};
+
+/**
+ * Gets debug data. Omit name for all cat data  Omit name and cat for
+ * all data.
+ *
+ * @param cat name of debugging category.
+ * @param name name of data to get (optional).
+ * @return object with requested debug data or undefined.
+ */
+forge.debug.get = function(cat, name) {
+  var rval;
+  if(typeof(cat) === 'undefined') {
+    rval = forge.debug.storage;
+  } else if(cat in forge.debug.storage) {
+    if(typeof(name) === 'undefined') {
+      rval = forge.debug.storage[cat];
+    } else {
+      rval = forge.debug.storage[cat][name];
+    }
+  }
+  return rval;
+};
+
+/**
+ * Sets debug data.
+ *
+ * @param cat name of debugging category.
+ * @param name name of data to set.
+ * @param data data to set.
+ */
+forge.debug.set = function(cat, name, data) {
+  if(!(cat in forge.debug.storage)) {
+    forge.debug.storage[cat] = {};
+  }
+  forge.debug.storage[cat][name] = data;
+};
+
+/**
+ * Clears debug data. Omit name for all cat data. Omit name and cat for
+ * all data.
+ *
+ * @param cat name of debugging category.
+ * @param name name of data to clear or omit to clear entire category.
+ */
+forge.debug.clear = function(cat, name) {
+  if(typeof(cat) === 'undefined') {
+    forge.debug.storage = {};
+  } else if(cat in forge.debug.storage) {
+    if(typeof(name) === 'undefined') {
+      delete forge.debug.storage[cat];
+    } else {
+      delete forge.debug.storage[cat][name];
+    }
+  }
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'debug';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/des.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/des.js
new file mode 100644
index 0000000..bf6d477
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/des.js
@@ -0,0 +1,552 @@
+/**
+ * DES (Data Encryption Standard) implementation.
+ *
+ * This implementation supports DES as well as 3DES-EDE in ECB and CBC mode.
+ * It is based on the BSD-licensed implementation by Paul Tero:
+ *
+ * Paul Tero, July 2001
+ * http://www.tero.co.uk/des/
+ *
+ * Optimised for performance with large blocks by Michael Hayworth, November 2001
+ * http://www.netdealing.com
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @author Stefan Siegl
+ * @author Dave Longley
+ *
+ * Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
+ * Copyright (c) 2012-2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+/* DES API */
+forge.des = forge.des || {};
+
+/**
+ * Deprecated. Instead, use:
+ *
+ * var cipher = forge.cipher.createCipher('DES-<mode>', key);
+ * cipher.start({iv: iv});
+ *
+ * Creates an DES cipher object to encrypt data using the given symmetric key.
+ * The output will be stored in the 'output' member of the returned cipher.
+ *
+ * The key and iv may be given as binary-encoded strings of bytes or
+ * byte buffers.
+ *
+ * @param key the symmetric key to use (64 or 192 bits).
+ * @param iv the initialization vector to use.
+ * @param output the buffer to write to, null to create one.
+ * @param mode the cipher mode to use (default: 'CBC' if IV is
+ *          given, 'ECB' if null).
+ *
+ * @return the cipher.
+ */
+forge.des.startEncrypting = function(key, iv, output, mode) {
+  var cipher = _createCipher({
+    key: key,
+    output: output,
+    decrypt: false,
+    mode: mode || (iv === null ? 'ECB' : 'CBC')
+  });
+  cipher.start(iv);
+  return cipher;
+};
+
+/**
+ * Deprecated. Instead, use:
+ *
+ * var cipher = forge.cipher.createCipher('DES-<mode>', key);
+ *
+ * Creates an DES cipher object to encrypt data using the given symmetric key.
+ *
+ * The key may be given as a binary-encoded string of bytes or a byte buffer.
+ *
+ * @param key the symmetric key to use (64 or 192 bits).
+ * @param mode the cipher mode to use (default: 'CBC').
+ *
+ * @return the cipher.
+ */
+forge.des.createEncryptionCipher = function(key, mode) {
+  return _createCipher({
+    key: key,
+    output: null,
+    decrypt: false,
+    mode: mode
+  });
+};
+
+/**
+ * Deprecated. Instead, use:
+ *
+ * var decipher = forge.cipher.createDecipher('DES-<mode>', key);
+ * decipher.start({iv: iv});
+ *
+ * Creates an DES cipher object to decrypt data using the given symmetric key.
+ * The output will be stored in the 'output' member of the returned cipher.
+ *
+ * The key and iv may be given as binary-encoded strings of bytes or
+ * byte buffers.
+ *
+ * @param key the symmetric key to use (64 or 192 bits).
+ * @param iv the initialization vector to use.
+ * @param output the buffer to write to, null to create one.
+ * @param mode the cipher mode to use (default: 'CBC' if IV is
+ *          given, 'ECB' if null).
+ *
+ * @return the cipher.
+ */
+forge.des.startDecrypting = function(key, iv, output, mode) {
+  var cipher = _createCipher({
+    key: key,
+    output: output,
+    decrypt: true,
+    mode: mode || (iv === null ? 'ECB' : 'CBC')
+  });
+  cipher.start(iv);
+  return cipher;
+};
+
+/**
+ * Deprecated. Instead, use:
+ *
+ * var decipher = forge.cipher.createDecipher('DES-<mode>', key);
+ *
+ * Creates an DES cipher object to decrypt data using the given symmetric key.
+ *
+ * The key may be given as a binary-encoded string of bytes or a byte buffer.
+ *
+ * @param key the symmetric key to use (64 or 192 bits).
+ * @param mode the cipher mode to use (default: 'CBC').
+ *
+ * @return the cipher.
+ */
+forge.des.createDecryptionCipher = function(key, mode) {
+  return _createCipher({
+    key: key,
+    output: null,
+    decrypt: true,
+    mode: mode
+  });
+};
+
+/**
+ * Creates a new DES cipher algorithm object.
+ *
+ * @param name the name of the algorithm.
+ * @param mode the mode factory function.
+ *
+ * @return the DES algorithm object.
+ */
+forge.des.Algorithm = function(name, mode) {
+  var self = this;
+  self.name = name;
+  self.mode = new mode({
+    blockSize: 8,
+    cipher: {
+      encrypt: function(inBlock, outBlock) {
+        return _updateBlock(self._keys, inBlock, outBlock, false);
+      },
+      decrypt: function(inBlock, outBlock) {
+        return _updateBlock(self._keys, inBlock, outBlock, true);
+      }
+    }
+  });
+  self._init = false;
+};
+
+/**
+ * Initializes this DES algorithm by expanding its key.
+ *
+ * @param options the options to use.
+ *          key the key to use with this algorithm.
+ *          decrypt true if the algorithm should be initialized for decryption,
+ *            false for encryption.
+ */
+forge.des.Algorithm.prototype.initialize = function(options) {
+  if(this._init) {
+    return;
+  }
+
+  var key = forge.util.createBuffer(options.key);
+  if(this.name.indexOf('3DES') === 0) {
+    if(key.length() !== 24) {
+      throw new Error('Invalid Triple-DES key size: ' + key.length() * 8);
+    }
+  }
+
+  // do key expansion to 16 or 48 subkeys (single or triple DES)
+  this._keys = _createKeys(key);
+  this._init = true;
+};
+
+
+/** Register DES algorithms **/
+
+registerAlgorithm('DES-ECB', forge.cipher.modes.ecb);
+registerAlgorithm('DES-CBC', forge.cipher.modes.cbc);
+registerAlgorithm('DES-CFB', forge.cipher.modes.cfb);
+registerAlgorithm('DES-OFB', forge.cipher.modes.ofb);
+registerAlgorithm('DES-CTR', forge.cipher.modes.ctr);
+
+registerAlgorithm('3DES-ECB', forge.cipher.modes.ecb);
+registerAlgorithm('3DES-CBC', forge.cipher.modes.cbc);
+registerAlgorithm('3DES-CFB', forge.cipher.modes.cfb);
+registerAlgorithm('3DES-OFB', forge.cipher.modes.ofb);
+registerAlgorithm('3DES-CTR', forge.cipher.modes.ctr);
+
+function registerAlgorithm(name, mode) {
+  var factory = function() {
+    return new forge.des.Algorithm(name, mode);
+  };
+  forge.cipher.registerAlgorithm(name, factory);
+}
+
+
+/** DES implementation **/
+
+var spfunction1 = [0x1010400,0,0x10000,0x1010404,0x1010004,0x10404,0x4,0x10000,0x400,0x1010400,0x1010404,0x400,0x1000404,0x1010004,0x1000000,0x4,0x404,0x1000400,0x1000400,0x10400,0x10400,0x1010000,0x1010000,0x1000404,0x10004,0x1000004,0x1000004,0x10004,0,0x404,0x10404,0x1000000,0x10000,0x1010404,0x4,0x1010000,0x1010400,0x1000000,0x1000000,0x400,0x1010004,0x10000,0x10400,0x1000004,0x400,0x4,0x1000404,0x10404,0x1010404,0x10004,0x1010000,0x1000404,0x1000004,0x404,0x10404,0x1010400,0x404,0x1000400,0x1000400,0,0x10004,0x10400,0,0x1010004];
+var spfunction2 = [-0x7fef7fe0,-0x7fff8000,0x8000,0x108020,0x100000,0x20,-0x7fefffe0,-0x7fff7fe0,-0x7fffffe0,-0x7fef7fe0,-0x7fef8000,-0x80000000,-0x7fff8000,0x100000,0x20,-0x7fefffe0,0x108000,0x100020,-0x7fff7fe0,0,-0x80000000,0x8000,0x108020,-0x7ff00000,0x100020,-0x7fffffe0,0,0x108000,0x8020,-0x7fef8000,-0x7ff00000,0x8020,0,0x108020,-0x7fefffe0,0x100000,-0x7fff7fe0,-0x7ff00000,-0x7fef8000,0x8000,-0x7ff00000,-0x7fff8000,0x20,-0x7fef7fe0,0x108020,0x20,0x8000,-0x80000000,0x8020,-0x7fef8000,0x100000,-0x7fffffe0,0x100020,-0x7fff7fe0,-0x7fffffe0,0x100020,0x108000,0,-0x7fff8000,0x8020,-0x80000000,-0x7fefffe0,-0x7fef7fe0,0x108000];
+var spfunction3 = [0x208,0x8020200,0,0x8020008,0x8000200,0,0x20208,0x8000200,0x20008,0x8000008,0x8000008,0x20000,0x8020208,0x20008,0x8020000,0x208,0x8000000,0x8,0x8020200,0x200,0x20200,0x8020000,0x8020008,0x20208,0x8000208,0x20200,0x20000,0x8000208,0x8,0x8020208,0x200,0x8000000,0x8020200,0x8000000,0x20008,0x208,0x20000,0x8020200,0x8000200,0,0x200,0x20008,0x8020208,0x8000200,0x8000008,0x200,0,0x8020008,0x8000208,0x20000,0x8000000,0x8020208,0x8,0x20208,0x20200,0x8000008,0x8020000,0x8000208,0x208,0x8020000,0x20208,0x8,0x8020008,0x20200];
+var spfunction4 = [0x802001,0x2081,0x2081,0x80,0x802080,0x800081,0x800001,0x2001,0,0x802000,0x802000,0x802081,0x81,0,0x800080,0x800001,0x1,0x2000,0x800000,0x802001,0x80,0x800000,0x2001,0x2080,0x800081,0x1,0x2080,0x800080,0x2000,0x802080,0x802081,0x81,0x800080,0x800001,0x802000,0x802081,0x81,0,0,0x802000,0x2080,0x800080,0x800081,0x1,0x802001,0x2081,0x2081,0x80,0x802081,0x81,0x1,0x2000,0x800001,0x2001,0x802080,0x800081,0x2001,0x2080,0x800000,0x802001,0x80,0x800000,0x2000,0x802080];
+var spfunction5 = [0x100,0x2080100,0x2080000,0x42000100,0x80000,0x100,0x40000000,0x2080000,0x40080100,0x80000,0x2000100,0x40080100,0x42000100,0x42080000,0x80100,0x40000000,0x2000000,0x40080000,0x40080000,0,0x40000100,0x42080100,0x42080100,0x2000100,0x42080000,0x40000100,0,0x42000000,0x2080100,0x2000000,0x42000000,0x80100,0x80000,0x42000100,0x100,0x2000000,0x40000000,0x2080000,0x42000100,0x40080100,0x2000100,0x40000000,0x42080000,0x2080100,0x40080100,0x100,0x2000000,0x42080000,0x42080100,0x80100,0x42000000,0x42080100,0x2080000,0,0x40080000,0x42000000,0x80100,0x2000100,0x40000100,0x80000,0,0x40080000,0x2080100,0x40000100];
+var spfunction6 = [0x20000010,0x20400000,0x4000,0x20404010,0x20400000,0x10,0x20404010,0x400000,0x20004000,0x404010,0x400000,0x20000010,0x400010,0x20004000,0x20000000,0x4010,0,0x400010,0x20004010,0x4000,0x404000,0x20004010,0x10,0x20400010,0x20400010,0,0x404010,0x20404000,0x4010,0x404000,0x20404000,0x20000000,0x20004000,0x10,0x20400010,0x404000,0x20404010,0x400000,0x4010,0x20000010,0x400000,0x20004000,0x20000000,0x4010,0x20000010,0x20404010,0x404000,0x20400000,0x404010,0x20404000,0,0x20400010,0x10,0x4000,0x20400000,0x404010,0x4000,0x400010,0x20004010,0,0x20404000,0x20000000,0x400010,0x20004010];
+var spfunction7 = [0x200000,0x4200002,0x4000802,0,0x800,0x4000802,0x200802,0x4200800,0x4200802,0x200000,0,0x4000002,0x2,0x4000000,0x4200002,0x802,0x4000800,0x200802,0x200002,0x4000800,0x4000002,0x4200000,0x4200800,0x200002,0x4200000,0x800,0x802,0x4200802,0x200800,0x2,0x4000000,0x200800,0x4000000,0x200800,0x200000,0x4000802,0x4000802,0x4200002,0x4200002,0x2,0x200002,0x4000000,0x4000800,0x200000,0x4200800,0x802,0x200802,0x4200800,0x802,0x4000002,0x4200802,0x4200000,0x200800,0,0x2,0x4200802,0,0x200802,0x4200000,0x800,0x4000002,0x4000800,0x800,0x200002];
+var spfunction8 = [0x10001040,0x1000,0x40000,0x10041040,0x10000000,0x10001040,0x40,0x10000000,0x40040,0x10040000,0x10041040,0x41000,0x10041000,0x41040,0x1000,0x40,0x10040000,0x10000040,0x10001000,0x1040,0x41000,0x40040,0x10040040,0x10041000,0x1040,0,0,0x10040040,0x10000040,0x10001000,0x41040,0x40000,0x41040,0x40000,0x10041000,0x1000,0x40,0x10040040,0x1000,0x41040,0x10001000,0x40,0x10000040,0x10040000,0x10040040,0x10000000,0x40000,0x10001040,0,0x10041040,0x40040,0x10000040,0x10040000,0x10001000,0x10001040,0,0x10041040,0x41000,0x41000,0x1040,0x1040,0x40040,0x10000000,0x10041000];
+
+/**
+ * Create necessary sub keys.
+ *
+ * @param key the 64-bit or 192-bit key.
+ *
+ * @return the expanded keys.
+ */
+function _createKeys(key) {
+  var pc2bytes0  = [0,0x4,0x20000000,0x20000004,0x10000,0x10004,0x20010000,0x20010004,0x200,0x204,0x20000200,0x20000204,0x10200,0x10204,0x20010200,0x20010204],
+      pc2bytes1  = [0,0x1,0x100000,0x100001,0x4000000,0x4000001,0x4100000,0x4100001,0x100,0x101,0x100100,0x100101,0x4000100,0x4000101,0x4100100,0x4100101],
+      pc2bytes2  = [0,0x8,0x800,0x808,0x1000000,0x1000008,0x1000800,0x1000808,0,0x8,0x800,0x808,0x1000000,0x1000008,0x1000800,0x1000808],
+      pc2bytes3  = [0,0x200000,0x8000000,0x8200000,0x2000,0x202000,0x8002000,0x8202000,0x20000,0x220000,0x8020000,0x8220000,0x22000,0x222000,0x8022000,0x8222000],
+      pc2bytes4  = [0,0x40000,0x10,0x40010,0,0x40000,0x10,0x40010,0x1000,0x41000,0x1010,0x41010,0x1000,0x41000,0x1010,0x41010],
+      pc2bytes5  = [0,0x400,0x20,0x420,0,0x400,0x20,0x420,0x2000000,0x2000400,0x2000020,0x2000420,0x2000000,0x2000400,0x2000020,0x2000420],
+      pc2bytes6  = [0,0x10000000,0x80000,0x10080000,0x2,0x10000002,0x80002,0x10080002,0,0x10000000,0x80000,0x10080000,0x2,0x10000002,0x80002,0x10080002],
+      pc2bytes7  = [0,0x10000,0x800,0x10800,0x20000000,0x20010000,0x20000800,0x20010800,0x20000,0x30000,0x20800,0x30800,0x20020000,0x20030000,0x20020800,0x20030800],
+      pc2bytes8  = [0,0x40000,0,0x40000,0x2,0x40002,0x2,0x40002,0x2000000,0x2040000,0x2000000,0x2040000,0x2000002,0x2040002,0x2000002,0x2040002],
+      pc2bytes9  = [0,0x10000000,0x8,0x10000008,0,0x10000000,0x8,0x10000008,0x400,0x10000400,0x408,0x10000408,0x400,0x10000400,0x408,0x10000408],
+      pc2bytes10 = [0,0x20,0,0x20,0x100000,0x100020,0x100000,0x100020,0x2000,0x2020,0x2000,0x2020,0x102000,0x102020,0x102000,0x102020],
+      pc2bytes11 = [0,0x1000000,0x200,0x1000200,0x200000,0x1200000,0x200200,0x1200200,0x4000000,0x5000000,0x4000200,0x5000200,0x4200000,0x5200000,0x4200200,0x5200200],
+      pc2bytes12 = [0,0x1000,0x8000000,0x8001000,0x80000,0x81000,0x8080000,0x8081000,0x10,0x1010,0x8000010,0x8001010,0x80010,0x81010,0x8080010,0x8081010],
+      pc2bytes13 = [0,0x4,0x100,0x104,0,0x4,0x100,0x104,0x1,0x5,0x101,0x105,0x1,0x5,0x101,0x105];
+
+  // how many iterations (1 for des, 3 for triple des)
+  // changed by Paul 16/6/2007 to use Triple DES for 9+ byte keys
+  var iterations = key.length() > 8 ? 3 : 1;
+
+  // stores the return keys
+  var keys = [];
+
+  // now define the left shifts which need to be done
+  var shifts = [0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0];
+
+  var n = 0, tmp;
+  for(var j = 0; j < iterations; j ++) {
+    var left = key.getInt32();
+    var right = key.getInt32();
+
+    tmp = ((left >>> 4) ^ right) & 0x0f0f0f0f;
+    right ^= tmp;
+    left ^= (tmp << 4);
+
+    tmp = ((right >>> -16) ^ left) & 0x0000ffff;
+    left ^= tmp;
+    right ^= (tmp << -16);
+
+    tmp = ((left >>> 2) ^ right) & 0x33333333;
+    right ^= tmp;
+    left ^= (tmp << 2);
+
+    tmp = ((right >>> -16) ^ left) & 0x0000ffff;
+    left ^= tmp;
+    right ^= (tmp << -16);
+
+    tmp = ((left >>> 1) ^ right) & 0x55555555;
+    right ^= tmp;
+    left ^= (tmp << 1);
+
+    tmp = ((right >>> 8) ^ left) & 0x00ff00ff;
+    left ^= tmp;
+    right ^= (tmp << 8);
+
+    tmp = ((left >>> 1) ^ right) & 0x55555555;
+    right ^= tmp;
+    left ^= (tmp << 1);
+
+    // right needs to be shifted and OR'd with last four bits of left
+    tmp = (left << 8) | ((right >>> 20) & 0x000000f0);
+
+    // left needs to be put upside down
+    left = ((right << 24) | ((right << 8) & 0xff0000) |
+      ((right >>> 8) & 0xff00) | ((right >>> 24) & 0xf0));
+    right = tmp;
+
+    // now go through and perform these shifts on the left and right keys
+    for(var i = 0; i < shifts.length; ++i) {
+      //shift the keys either one or two bits to the left
+      if(shifts[i]) {
+        left = (left << 2) | (left >>> 26);
+        right = (right << 2) | (right >>> 26);
+      } else {
+        left = (left << 1) | (left >>> 27);
+        right = (right << 1) | (right >>> 27);
+      }
+      left &= -0xf;
+      right &= -0xf;
+
+      // now apply PC-2, in such a way that E is easier when encrypting or
+      // decrypting this conversion will look like PC-2 except only the last 6
+      // bits of each byte are used rather than 48 consecutive bits and the
+      // order of lines will be according to how the S selection functions will
+      // be applied: S2, S4, S6, S8, S1, S3, S5, S7
+      var lefttmp = (
+        pc2bytes0[left >>> 28] | pc2bytes1[(left >>> 24) & 0xf] |
+        pc2bytes2[(left >>> 20) & 0xf] | pc2bytes3[(left >>> 16) & 0xf] |
+        pc2bytes4[(left >>> 12) & 0xf] | pc2bytes5[(left >>> 8) & 0xf] |
+        pc2bytes6[(left >>> 4) & 0xf]);
+      var righttmp = (
+        pc2bytes7[right >>> 28] | pc2bytes8[(right >>> 24) & 0xf] |
+        pc2bytes9[(right >>> 20) & 0xf] | pc2bytes10[(right >>> 16) & 0xf] |
+        pc2bytes11[(right >>> 12) & 0xf] | pc2bytes12[(right >>> 8) & 0xf] |
+        pc2bytes13[(right >>> 4) & 0xf]);
+      tmp = ((righttmp >>> 16) ^ lefttmp) & 0x0000ffff;
+      keys[n++] = lefttmp ^ tmp;
+      keys[n++] = righttmp ^ (tmp << 16);
+    }
+  }
+
+  return keys;
+}
+
+/**
+ * Updates a single block (1 byte) using DES. The update will either
+ * encrypt or decrypt the block.
+ *
+ * @param keys the expanded keys.
+ * @param input the input block (an array of 32-bit words).
+ * @param output the updated output block.
+ * @param decrypt true to decrypt the block, false to encrypt it.
+ */
+function _updateBlock(keys, input, output, decrypt) {
+  // set up loops for single or triple DES
+  var iterations = keys.length === 32 ? 3 : 9;
+  var looping;
+  if(iterations === 3) {
+    looping = decrypt ? [30, -2, -2] : [0, 32, 2];
+  } else {
+    looping = (decrypt ?
+      [94, 62, -2, 32, 64, 2, 30, -2, -2] :
+      [0, 32, 2, 62, 30, -2, 64, 96, 2]);
+  }
+
+  var tmp;
+
+  var left = input[0];
+  var right = input[1];
+
+  // first each 64 bit chunk of the message must be permuted according to IP
+  tmp = ((left >>> 4) ^ right) & 0x0f0f0f0f;
+  right ^= tmp;
+  left ^= (tmp << 4);
+
+  tmp = ((left >>> 16) ^ right) & 0x0000ffff;
+  right ^= tmp;
+  left ^= (tmp << 16);
+
+  tmp = ((right >>> 2) ^ left) & 0x33333333;
+  left ^= tmp;
+  right ^= (tmp << 2);
+
+  tmp = ((right >>> 8) ^ left) & 0x00ff00ff;
+  left ^= tmp;
+  right ^= (tmp << 8);
+
+  tmp = ((left >>> 1) ^ right) & 0x55555555;
+  right ^= tmp;
+  left ^= (tmp << 1);
+
+  // rotate left 1 bit
+  left = ((left << 1) | (left >>> 31));
+  right = ((right << 1) | (right >>> 31));
+
+  for(var j = 0; j < iterations; j += 3) {
+    var endloop = looping[j + 1];
+    var loopinc = looping[j + 2];
+
+    // now go through and perform the encryption or decryption
+    for(var i = looping[j]; i != endloop; i += loopinc) {
+      var right1 = right ^ keys[i];
+      var right2 = ((right >>> 4) | (right << 28)) ^ keys[i + 1];
+
+      // passing these bytes through the S selection functions
+      tmp = left;
+      left = right;
+      right = tmp ^ (
+        spfunction2[(right1 >>> 24) & 0x3f] |
+        spfunction4[(right1 >>> 16) & 0x3f] |
+        spfunction6[(right1 >>>  8) & 0x3f] |
+        spfunction8[right1 & 0x3f] |
+        spfunction1[(right2 >>> 24) & 0x3f] |
+        spfunction3[(right2 >>> 16) & 0x3f] |
+        spfunction5[(right2 >>>  8) & 0x3f] |
+        spfunction7[right2 & 0x3f]);
+    }
+    // unreverse left and right
+    tmp = left;
+    left = right;
+    right = tmp;
+  }
+
+  // rotate right 1 bit
+  left = ((left >>> 1) | (left << 31));
+  right = ((right >>> 1) | (right << 31));
+
+  // now perform IP-1, which is IP in the opposite direction
+  tmp = ((left >>> 1) ^ right) & 0x55555555;
+  right ^= tmp;
+  left ^= (tmp << 1);
+
+  tmp = ((right >>> 8) ^ left) & 0x00ff00ff;
+  left ^= tmp;
+  right ^= (tmp << 8);
+
+  tmp = ((right >>> 2) ^ left) & 0x33333333;
+  left ^= tmp;
+  right ^= (tmp << 2);
+
+  tmp = ((left >>> 16) ^ right) & 0x0000ffff;
+  right ^= tmp;
+  left ^= (tmp << 16);
+
+  tmp = ((left >>> 4) ^ right) & 0x0f0f0f0f;
+  right ^= tmp;
+  left ^= (tmp << 4);
+
+  output[0] = left;
+  output[1] = right;
+}
+
+/**
+ * Deprecated. Instead, use:
+ *
+ * forge.cipher.createCipher('DES-<mode>', key);
+ * forge.cipher.createDecipher('DES-<mode>', key);
+ *
+ * Creates a deprecated DES cipher object. This object's mode will default to
+ * CBC (cipher-block-chaining).
+ *
+ * The key may be given as a binary-encoded string of bytes or a byte buffer.
+ *
+ * @param options the options to use.
+ *          key the symmetric key to use (64 or 192 bits).
+ *          output the buffer to write to.
+ *          decrypt true for decryption, false for encryption.
+ *          mode the cipher mode to use (default: 'CBC').
+ *
+ * @return the cipher.
+ */
+function _createCipher(options) {
+  options = options || {};
+  var mode = (options.mode || 'CBC').toUpperCase();
+  var algorithm = 'DES-' + mode;
+
+  var cipher;
+  if(options.decrypt) {
+    cipher = forge.cipher.createDecipher(algorithm, options.key);
+  } else {
+    cipher = forge.cipher.createCipher(algorithm, options.key);
+  }
+
+  // backwards compatible start API
+  var start = cipher.start;
+  cipher.start = function(iv, options) {
+    // backwards compatibility: support second arg as output buffer
+    var output = null;
+    if(options instanceof forge.util.ByteBuffer) {
+      output = options;
+      options = {};
+    }
+    options = options || {};
+    options.output = output;
+    options.iv = iv;
+    start.call(cipher, options);
+  };
+
+  return cipher;
+}
+
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'des';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(
+  ['require', 'module', './cipher', './cipherModes', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/forge.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/forge.js
new file mode 100644
index 0000000..b314e22
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/forge.js
@@ -0,0 +1,92 @@
+/**
+ * Node.js module for Forge.
+ *
+ * @author Dave Longley
+ *
+ * Copyright 2011-2014 Digital Bazaar, Inc.
+ */
+(function() {
+var name = 'forge';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      // set to true to disable native code if even it's available
+      forge = {disableNativeCode: false};
+    }
+    return;
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    });
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge;
+  };
+  // set to true to disable native code if even it's available
+  module.exports.disableNativeCode = false;
+  module.exports(module.exports);
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define([
+  'require',
+  'module',
+  './aes',
+  './aesCipherSuites',
+  './asn1',
+  './cipher',
+  './cipherModes',
+  './debug',
+  './des',
+  './hmac',
+  './kem',
+  './log',
+  './md',
+  './mgf1',
+  './pbkdf2',
+  './pem',
+  './pkcs7',
+  './pkcs1',
+  './pkcs12',
+  './pki',
+  './prime',
+  './prng',
+  './pss',
+  './random',
+  './rc2',
+  './ssh',
+  './task',
+  './tls',
+  './util'
+], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/form.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/form.js
new file mode 100644
index 0000000..62d4424
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/form.js
@@ -0,0 +1,157 @@
+/**
+ * Functions for manipulating web forms.
+ *
+ * @author David I. Lehn <dlehn@digitalbazaar.com>
+ * @author Dave Longley
+ * @author Mike Johnson
+ *
+ * Copyright (c) 2011-2014 Digital Bazaar, Inc. All rights reserved.
+ */
+(function($) {
+
+/**
+ * The form namespace.
+ */
+var form = {};
+
+/**
+ * Regex for parsing a single name property (handles array brackets).
+ */
+var _regex = /(.*?)\[(.*?)\]/g;
+
+/**
+ * Parses a single name property into an array with the name and any
+ * array indices.
+ *
+ * @param name the name to parse.
+ *
+ * @return the array of the name and its array indices in order.
+ */
+var _parseName = function(name) {
+  var rval = [];
+
+  var matches;
+  while(!!(matches = _regex.exec(name))) {
+    if(matches[1].length > 0) {
+      rval.push(matches[1]);
+    }
+    if(matches.length >= 2) {
+      rval.push(matches[2]);
+    }
+  }
+  if(rval.length === 0) {
+    rval.push(name);
+  }
+
+  return rval;
+};
+
+/**
+ * Adds a field from the given form to the given object.
+ *
+ * @param obj the object.
+ * @param names the field as an array of object property names.
+ * @param value the value of the field.
+ * @param dict a dictionary of names to replace.
+ */
+var _addField = function(obj, names, value, dict) {
+  // combine array names that fall within square brackets
+  var tmp = [];
+  for(var i = 0; i < names.length; ++i) {
+    // check name for starting square bracket but no ending one
+    var name = names[i];
+    if(name.indexOf('[') !== -1 && name.indexOf(']') === -1 &&
+      i < names.length - 1) {
+      do {
+        name += '.' + names[++i];
+      } while(i < names.length - 1 && names[i].indexOf(']') === -1);
+    }
+    tmp.push(name);
+  }
+  names = tmp;
+
+  // split out array indexes
+  var tmp = [];
+  $.each(names, function(n, name) {
+    tmp = tmp.concat(_parseName(name));
+  });
+  names = tmp;
+
+  // iterate over object property names until value is set
+  $.each(names, function(n, name) {
+    // do dictionary name replacement
+    if(dict && name.length !== 0 && name in dict) {
+       name = dict[name];
+    }
+
+    // blank name indicates appending to an array, set name to
+    // new last index of array
+    if(name.length === 0) {
+       name = obj.length;
+    }
+
+    // value already exists, append value
+    if(obj[name]) {
+      // last name in the field
+      if(n == names.length - 1) {
+        // more than one value, so convert into an array
+        if(!$.isArray(obj[name])) {
+          obj[name] = [obj[name]];
+        }
+        obj[name].push(value);
+      } else {
+        // not last name, go deeper into object
+        obj = obj[name];
+      }
+    } else if(n == names.length - 1) {
+      // new value, last name in the field, set value
+      obj[name] = value;
+    } else {
+      // new value, not last name, go deeper
+      // get next name
+      var next = names[n + 1];
+
+      // blank next value indicates array-appending, so create array
+      if(next.length === 0) {
+         obj[name] = [];
+      } else {
+        // if next name is a number create an array, otherwise a map
+        var isNum = ((next - 0) == next && next.length > 0);
+        obj[name] = isNum ? [] : {};
+      }
+      obj = obj[name];
+    }
+  });
+};
+
+/**
+ * Serializes a form to a JSON object. Object properties will be separated
+ * using the given separator (defaults to '.') and by square brackets.
+ *
+ * @param input the jquery form to serialize.
+ * @param sep the object-property separator (defaults to '.').
+ * @param dict a dictionary of names to replace (name=replace).
+ *
+ * @return the JSON-serialized form.
+ */
+form.serialize = function(input, sep, dict) {
+  var rval = {};
+
+  // add all fields in the form to the object
+  sep = sep || '.';
+  $.each(input.serializeArray(), function() {
+    _addField(rval, this.name.split(sep), this.value || '', dict);
+  });
+
+  return rval;
+};
+
+/**
+ * The forge namespace and form API.
+ */
+if(typeof forge === 'undefined') {
+  forge = {};
+}
+forge.form = form;
+
+})(jQuery);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/hmac.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/hmac.js
new file mode 100644
index 0000000..eee58bc
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/hmac.js
@@ -0,0 +1,200 @@
+/**
+ * Hash-based Message Authentication Code implementation. Requires a message
+ * digest object that can be obtained, for example, from forge.md.sha1 or
+ * forge.md.md5.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2012 Digital Bazaar, Inc. All rights reserved.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+/* HMAC API */
+var hmac = forge.hmac = forge.hmac || {};
+
+/**
+ * Creates an HMAC object that uses the given message digest object.
+ *
+ * @return an HMAC object.
+ */
+hmac.create = function() {
+  // the hmac key to use
+  var _key = null;
+
+  // the message digest to use
+  var _md = null;
+
+  // the inner padding
+  var _ipadding = null;
+
+  // the outer padding
+  var _opadding = null;
+
+  // hmac context
+  var ctx = {};
+
+  /**
+   * Starts or restarts the HMAC with the given key and message digest.
+   *
+   * @param md the message digest to use, null to reuse the previous one,
+   *           a string to use builtin 'sha1', 'md5', 'sha256'.
+   * @param key the key to use as a string, array of bytes, byte buffer,
+   *           or null to reuse the previous key.
+   */
+  ctx.start = function(md, key) {
+    if(md !== null) {
+      if(typeof md === 'string') {
+        // create builtin message digest
+        md = md.toLowerCase();
+        if(md in forge.md.algorithms) {
+          _md = forge.md.algorithms[md].create();
+        } else {
+          throw new Error('Unknown hash algorithm "' + md + '"');
+        }
+      } else {
+        // store message digest
+        _md = md;
+      }
+    }
+
+    if(key === null) {
+      // reuse previous key
+      key = _key;
+    } else {
+      if(typeof key === 'string') {
+        // convert string into byte buffer
+        key = forge.util.createBuffer(key);
+      } else if(forge.util.isArray(key)) {
+        // convert byte array into byte buffer
+        var tmp = key;
+        key = forge.util.createBuffer();
+        for(var i = 0; i < tmp.length; ++i) {
+          key.putByte(tmp[i]);
+        }
+      }
+
+      // if key is longer than blocksize, hash it
+      var keylen = key.length();
+      if(keylen > _md.blockLength) {
+        _md.start();
+        _md.update(key.bytes());
+        key = _md.digest();
+      }
+
+      // mix key into inner and outer padding
+      // ipadding = [0x36 * blocksize] ^ key
+      // opadding = [0x5C * blocksize] ^ key
+      _ipadding = forge.util.createBuffer();
+      _opadding = forge.util.createBuffer();
+      keylen = key.length();
+      for(var i = 0; i < keylen; ++i) {
+        var tmp = key.at(i);
+        _ipadding.putByte(0x36 ^ tmp);
+        _opadding.putByte(0x5C ^ tmp);
+      }
+
+      // if key is shorter than blocksize, add additional padding
+      if(keylen < _md.blockLength) {
+        var tmp = _md.blockLength - keylen;
+        for(var i = 0; i < tmp; ++i) {
+          _ipadding.putByte(0x36);
+          _opadding.putByte(0x5C);
+        }
+      }
+      _key = key;
+      _ipadding = _ipadding.bytes();
+      _opadding = _opadding.bytes();
+    }
+
+    // digest is done like so: hash(opadding | hash(ipadding | message))
+
+    // prepare to do inner hash
+    // hash(ipadding | message)
+    _md.start();
+    _md.update(_ipadding);
+  };
+
+  /**
+   * Updates the HMAC with the given message bytes.
+   *
+   * @param bytes the bytes to update with.
+   */
+  ctx.update = function(bytes) {
+    _md.update(bytes);
+  };
+
+  /**
+   * Produces the Message Authentication Code (MAC).
+   *
+   * @return a byte buffer containing the digest value.
+   */
+  ctx.getMac = function() {
+    // digest is done like so: hash(opadding | hash(ipadding | message))
+    // here we do the outer hashing
+    var inner = _md.digest().bytes();
+    _md.start();
+    _md.update(_opadding);
+    _md.update(inner);
+    return _md.digest();
+  };
+  // alias for getMac
+  ctx.digest = ctx.getMac;
+
+  return ctx;
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'hmac';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './md', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/http.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/http.js
new file mode 100644
index 0000000..fa01aed
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/http.js
@@ -0,0 +1,1369 @@
+/**
+ * HTTP client-side implementation that uses forge.net sockets.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2014 Digital Bazaar, Inc. All rights reserved.
+ */
+(function() {
+
+// define http namespace
+var http = {};
+
+// logging category
+var cat = 'forge.http';
+
+// add array of clients to debug storage
+if(forge.debug) {
+  forge.debug.set('forge.http', 'clients', []);
+}
+
+// normalizes an http header field name
+var _normalize = function(name) {
+  return name.toLowerCase().replace(/(^.)|(-.)/g,
+    function(a){return a.toUpperCase();});
+};
+
+/**
+ * Gets the local storage ID for the given client.
+ *
+ * @param client the client to get the local storage ID for.
+ *
+ * @return the local storage ID to use.
+ */
+var _getStorageId = function(client) {
+  // TODO: include browser in ID to avoid sharing cookies between
+  // browsers (if this is undesirable)
+  // navigator.userAgent
+  return 'forge.http.' +
+    client.url.scheme + '.' +
+    client.url.host + '.' +
+    client.url.port;
+};
+
+/**
+ * Loads persistent cookies from disk for the given client.
+ *
+ * @param client the client.
+ */
+var _loadCookies = function(client) {
+  if(client.persistCookies) {
+    try {
+      var cookies = forge.util.getItem(
+        client.socketPool.flashApi,
+        _getStorageId(client), 'cookies');
+      client.cookies = cookies || {};
+    } catch(ex) {
+      // no flash storage available, just silently fail
+      // TODO: i assume we want this logged somewhere or
+      // should it actually generate an error
+      //forge.log.error(cat, ex);
+    }
+  }
+};
+
+/**
+ * Saves persistent cookies on disk for the given client.
+ *
+ * @param client the client.
+ */
+var _saveCookies = function(client) {
+  if(client.persistCookies) {
+    try {
+      forge.util.setItem(
+        client.socketPool.flashApi,
+        _getStorageId(client), 'cookies', client.cookies);
+    } catch(ex) {
+      // no flash storage available, just silently fail
+      // TODO: i assume we want this logged somewhere or
+      // should it actually generate an error
+      //forge.log.error(cat, ex);
+    }
+  }
+
+  // FIXME: remove me
+  _loadCookies(client);
+};
+
+/**
+ * Clears persistent cookies on disk for the given client.
+ *
+ * @param client the client.
+ */
+var _clearCookies = function(client) {
+  if(client.persistCookies) {
+    try {
+      // only thing stored is 'cookies', so clear whole storage
+      forge.util.clearItems(
+        client.socketPool.flashApi,
+        _getStorageId(client));
+    } catch(ex) {
+      // no flash storage available, just silently fail
+      // TODO: i assume we want this logged somewhere or
+      // should it actually generate an error
+      //forge.log.error(cat, ex);
+    }
+  }
+};
+
+/**
+ * Connects and sends a request.
+ *
+ * @param client the http client.
+ * @param socket the socket to use.
+ */
+var _doRequest = function(client, socket) {
+  if(socket.isConnected()) {
+    // already connected
+    socket.options.request.connectTime = +new Date();
+    socket.connected({
+      type: 'connect',
+      id: socket.id
+    });
+  } else {
+    // connect
+    socket.options.request.connectTime = +new Date();
+    socket.connect({
+      host: client.url.host,
+      port: client.url.port,
+      policyPort: client.policyPort,
+      policyUrl: client.policyUrl
+    });
+  }
+};
+
+/**
+ * Handles the next request or marks a socket as idle.
+ *
+ * @param client the http client.
+ * @param socket the socket.
+ */
+var _handleNextRequest = function(client, socket) {
+  // clear buffer
+  socket.buffer.clear();
+
+  // get pending request
+  var pending = null;
+  while(pending === null && client.requests.length > 0) {
+    pending = client.requests.shift();
+    if(pending.request.aborted) {
+      pending = null;
+    }
+  }
+
+  // mark socket idle if no pending requests
+  if(pending === null) {
+    if(socket.options !== null) {
+      socket.options = null;
+    }
+    client.idle.push(socket);
+  } else {
+    // handle pending request, allow 1 retry
+    socket.retries = 1;
+    socket.options = pending;
+    _doRequest(client, socket);
+  }
+};
+
+/**
+ * Sets up a socket for use with an http client.
+ *
+ * @param client the parent http client.
+ * @param socket the socket to set up.
+ * @param tlsOptions if the socket must use TLS, the TLS options.
+ */
+var _initSocket = function(client, socket, tlsOptions) {
+  // no socket options yet
+  socket.options = null;
+
+  // set up handlers
+  socket.connected = function(e) {
+    // socket primed by caching TLS session, handle next request
+    if(socket.options === null) {
+      _handleNextRequest(client, socket);
+    } else {
+      // socket in use
+      var request = socket.options.request;
+      request.connectTime = +new Date() - request.connectTime;
+      e.socket = socket;
+      socket.options.connected(e);
+      if(request.aborted) {
+        socket.close();
+      } else {
+        var out = request.toString();
+        if(request.body) {
+          out += request.body;
+        }
+        request.time = +new Date();
+        socket.send(out);
+        request.time = +new Date() - request.time;
+        socket.options.response.time = +new Date();
+        socket.sending = true;
+      }
+    }
+  };
+  socket.closed = function(e) {
+    if(socket.sending) {
+      socket.sending = false;
+      if(socket.retries > 0) {
+        --socket.retries;
+        _doRequest(client, socket);
+      } else {
+        // error, closed during send
+        socket.error({
+          id: socket.id,
+          type: 'ioError',
+          message: 'Connection closed during send. Broken pipe.',
+          bytesAvailable: 0
+        });
+      }
+    } else {
+      // handle unspecified content-length transfer
+      var response = socket.options.response;
+      if(response.readBodyUntilClose) {
+        response.time = +new Date() - response.time;
+        response.bodyReceived = true;
+        socket.options.bodyReady({
+          request: socket.options.request,
+          response: response,
+          socket: socket
+        });
+      }
+      socket.options.closed(e);
+      _handleNextRequest(client, socket);
+    }
+  };
+  socket.data = function(e) {
+    socket.sending = false;
+    var request = socket.options.request;
+    if(request.aborted) {
+      socket.close();
+    } else {
+      // receive all bytes available
+      var response = socket.options.response;
+      var bytes = socket.receive(e.bytesAvailable);
+      if(bytes !== null) {
+        // receive header and then body
+        socket.buffer.putBytes(bytes);
+        if(!response.headerReceived) {
+          response.readHeader(socket.buffer);
+          if(response.headerReceived) {
+            socket.options.headerReady({
+              request: socket.options.request,
+              response: response,
+              socket: socket
+            });
+          }
+        }
+        if(response.headerReceived && !response.bodyReceived) {
+          response.readBody(socket.buffer);
+        }
+        if(response.bodyReceived) {
+          socket.options.bodyReady({
+            request: socket.options.request,
+            response: response,
+            socket: socket
+          });
+          // close connection if requested or by default on http/1.0
+          var value = response.getField('Connection') || '';
+          if(value.indexOf('close') != -1 ||
+            (response.version === 'HTTP/1.0' &&
+            response.getField('Keep-Alive') === null)) {
+            socket.close();
+          } else {
+            _handleNextRequest(client, socket);
+          }
+        }
+      }
+    }
+  };
+  socket.error = function(e) {
+    // do error callback, include request
+    socket.options.error({
+      type: e.type,
+      message: e.message,
+      request: socket.options.request,
+      response: socket.options.response,
+      socket: socket
+    });
+    socket.close();
+  };
+
+  // wrap socket for TLS
+  if(tlsOptions) {
+    socket = forge.tls.wrapSocket({
+      sessionId: null,
+      sessionCache: {},
+      caStore: tlsOptions.caStore,
+      cipherSuites: tlsOptions.cipherSuites,
+      socket: socket,
+      virtualHost: tlsOptions.virtualHost,
+      verify: tlsOptions.verify,
+      getCertificate: tlsOptions.getCertificate,
+      getPrivateKey: tlsOptions.getPrivateKey,
+      getSignature: tlsOptions.getSignature,
+      deflate: tlsOptions.deflate || null,
+      inflate: tlsOptions.inflate || null
+    });
+
+    socket.options = null;
+    socket.buffer = forge.util.createBuffer();
+    client.sockets.push(socket);
+    if(tlsOptions.prime) {
+      // prime socket by connecting and caching TLS session, will do
+      // next request from there
+      socket.connect({
+        host: client.url.host,
+        port: client.url.port,
+        policyPort: client.policyPort,
+        policyUrl: client.policyUrl
+      });
+    } else {
+      // do not prime socket, just add as idle
+      client.idle.push(socket);
+    }
+  } else {
+    // no need to prime non-TLS sockets
+    socket.buffer = forge.util.createBuffer();
+    client.sockets.push(socket);
+    client.idle.push(socket);
+  }
+};
+
+/**
+ * Checks to see if the given cookie has expired. If the cookie's max-age
+ * plus its created time is less than the time now, it has expired, unless
+ * its max-age is set to -1 which indicates it will never expire.
+ *
+ * @param cookie the cookie to check.
+ *
+ * @return true if it has expired, false if not.
+ */
+var _hasCookieExpired = function(cookie) {
+  var rval = false;
+
+  if(cookie.maxAge !== -1) {
+    var now = _getUtcTime(new Date());
+    var expires = cookie.created + cookie.maxAge;
+    if(expires <= now) {
+      rval = true;
+    }
+  }
+
+  return rval;
+};
+
+/**
+ * Adds cookies in the given client to the given request.
+ *
+ * @param client the client.
+ * @param request the request.
+ */
+var _writeCookies = function(client, request) {
+  var expired = [];
+  var url = client.url;
+  var cookies = client.cookies;
+  for(var name in cookies) {
+    // get cookie paths
+    var paths = cookies[name];
+    for(var p in paths) {
+      var cookie = paths[p];
+      if(_hasCookieExpired(cookie)) {
+        // store for clean up
+        expired.push(cookie);
+      } else if(request.path.indexOf(cookie.path) === 0) {
+        // path or path's ancestor must match cookie.path
+        request.addCookie(cookie);
+      }
+    }
+  }
+
+  // clean up expired cookies
+  for(var i = 0; i < expired.length; ++i) {
+    var cookie = expired[i];
+    client.removeCookie(cookie.name, cookie.path);
+  }
+};
+
+/**
+ * Gets cookies from the given response and adds the to the given client.
+ *
+ * @param client the client.
+ * @param response the response.
+ */
+var _readCookies = function(client, response) {
+  var cookies = response.getCookies();
+  for(var i = 0; i < cookies.length; ++i) {
+    try {
+      client.setCookie(cookies[i]);
+    } catch(ex) {
+      // ignore failure to add other-domain, etc. cookies
+    }
+  }
+};
+
+/**
+ * Creates an http client that uses forge.net sockets as a backend and
+ * forge.tls for security.
+ *
+ * @param options:
+ *   url: the url to connect to (scheme://host:port).
+ *     socketPool: the flash socket pool to use.
+ *   policyPort: the flash policy port to use (if other than the
+ *     socket pool default), use 0 for flash default.
+ *   policyUrl: the flash policy file URL to use (if provided will
+ *     be used instead of a policy port).
+ *   connections: number of connections to use to handle requests.
+ *   caCerts: an array of certificates to trust for TLS, certs may
+ *     be PEM-formatted or cert objects produced via forge.pki.
+ *   cipherSuites: an optional array of cipher suites to use,
+ *     see forge.tls.CipherSuites.
+ *   virtualHost: the virtual server name to use in a TLS SNI
+ *     extension, if not provided the url host will be used.
+ *   verify: a custom TLS certificate verify callback to use.
+ *   getCertificate: an optional callback used to get a client-side
+ *     certificate (see forge.tls for details).
+ *   getPrivateKey: an optional callback used to get a client-side
+ *     private key (see forge.tls for details).
+ *   getSignature: an optional callback used to get a client-side
+ *     signature (see forge.tls for details).
+ *   persistCookies: true to use persistent cookies via flash local
+ *     storage, false to only keep cookies in javascript.
+ *   primeTlsSockets: true to immediately connect TLS sockets on
+ *     their creation so that they will cache TLS sessions for reuse.
+ *
+ * @return the client.
+ */
+http.createClient = function(options) {
+  // create CA store to share with all TLS connections
+  var caStore = null;
+  if(options.caCerts) {
+    caStore = forge.pki.createCaStore(options.caCerts);
+  }
+
+  // get scheme, host, and port from url
+  options.url = (options.url ||
+    window.location.protocol + '//' + window.location.host);
+  var url = http.parseUrl(options.url);
+  if(!url) {
+    var error = new Error('Invalid url.');
+    error.details = {url: options.url};
+    throw error;
+  }
+
+  // default to 1 connection
+  options.connections = options.connections || 1;
+
+  // create client
+  var sp = options.socketPool;
+  var client = {
+    // url
+    url: url,
+    // socket pool
+    socketPool: sp,
+    // the policy port to use
+    policyPort: options.policyPort,
+    // policy url to use
+    policyUrl: options.policyUrl,
+    // queue of requests to service
+    requests: [],
+    // all sockets
+    sockets: [],
+    // idle sockets
+    idle: [],
+    // whether or not the connections are secure
+    secure: (url.scheme === 'https'),
+    // cookie jar (key'd off of name and then path, there is only 1 domain
+    // and one setting for secure per client so name+path is unique)
+    cookies: {},
+    // default to flash storage of cookies
+    persistCookies: (typeof(options.persistCookies) === 'undefined') ?
+      true : options.persistCookies
+  };
+
+  // add client to debug storage
+  if(forge.debug) {
+    forge.debug.get('forge.http', 'clients').push(client);
+  }
+
+  // load cookies from disk
+  _loadCookies(client);
+
+  /**
+   * A default certificate verify function that checks a certificate common
+   * name against the client's URL host.
+   *
+   * @param c the TLS connection.
+   * @param verified true if cert is verified, otherwise alert number.
+   * @param depth the chain depth.
+   * @param certs the cert chain.
+   *
+   * @return true if verified and the common name matches the host, error
+   *         otherwise.
+   */
+  var _defaultCertificateVerify = function(c, verified, depth, certs) {
+    if(depth === 0 && verified === true) {
+      // compare common name to url host
+      var cn = certs[depth].subject.getField('CN');
+      if(cn === null || client.url.host !== cn.value) {
+        verified = {
+          message: 'Certificate common name does not match url host.'
+        };
+      }
+    }
+    return verified;
+  };
+
+  // determine if TLS is used
+  var tlsOptions = null;
+  if(client.secure) {
+    tlsOptions = {
+      caStore: caStore,
+      cipherSuites: options.cipherSuites || null,
+      virtualHost: options.virtualHost || url.host,
+      verify: options.verify || _defaultCertificateVerify,
+      getCertificate: options.getCertificate || null,
+      getPrivateKey: options.getPrivateKey || null,
+      getSignature: options.getSignature || null,
+      prime: options.primeTlsSockets || false
+    };
+
+    // if socket pool uses a flash api, then add deflate support to TLS
+    if(sp.flashApi !== null) {
+      tlsOptions.deflate = function(bytes) {
+        // strip 2 byte zlib header and 4 byte trailer
+        return forge.util.deflate(sp.flashApi, bytes, true);
+      };
+      tlsOptions.inflate = function(bytes) {
+        return forge.util.inflate(sp.flashApi, bytes, true);
+      };
+    }
+  }
+
+  // create and initialize sockets
+  for(var i = 0; i < options.connections; ++i) {
+    _initSocket(client, sp.createSocket(), tlsOptions);
+  }
+
+  /**
+   * Sends a request. A method 'abort' will be set on the request that
+   * can be called to attempt to abort the request.
+   *
+   * @param options:
+   *          request: the request to send.
+   *          connected: a callback for when the connection is open.
+   *          closed: a callback for when the connection is closed.
+   *          headerReady: a callback for when the response header arrives.
+   *          bodyReady: a callback for when the response body arrives.
+   *          error: a callback for if an error occurs.
+   */
+  client.send = function(options) {
+    // add host header if not set
+    if(options.request.getField('Host') === null) {
+      options.request.setField('Host', client.url.fullHost);
+    }
+
+    // set default dummy handlers
+    var opts = {};
+    opts.request = options.request;
+    opts.connected = options.connected || function(){};
+    opts.closed = options.close || function(){};
+    opts.headerReady = function(e) {
+      // read cookies
+      _readCookies(client, e.response);
+      if(options.headerReady) {
+        options.headerReady(e);
+      }
+    };
+    opts.bodyReady = options.bodyReady || function(){};
+    opts.error = options.error || function(){};
+
+    // create response
+    opts.response = http.createResponse();
+    opts.response.time = 0;
+    opts.response.flashApi = client.socketPool.flashApi;
+    opts.request.flashApi = client.socketPool.flashApi;
+
+    // create abort function
+    opts.request.abort = function() {
+      // set aborted, clear handlers
+      opts.request.aborted = true;
+      opts.connected = function(){};
+      opts.closed = function(){};
+      opts.headerReady = function(){};
+      opts.bodyReady = function(){};
+      opts.error = function(){};
+    };
+
+    // add cookies to request
+    _writeCookies(client, opts.request);
+
+    // queue request options if there are no idle sockets
+    if(client.idle.length === 0) {
+      client.requests.push(opts);
+    } else {
+      // use an idle socket, prefer an idle *connected* socket first
+      var socket = null;
+      var len = client.idle.length;
+      for(var i = 0; socket === null && i < len; ++i) {
+        socket = client.idle[i];
+        if(socket.isConnected()) {
+          client.idle.splice(i, 1);
+        } else {
+          socket = null;
+        }
+      }
+      // no connected socket available, get unconnected socket
+      if(socket === null) {
+        socket = client.idle.pop();
+      }
+      socket.options = opts;
+      _doRequest(client, socket);
+    }
+  };
+
+  /**
+   * Destroys this client.
+   */
+  client.destroy = function() {
+    // clear pending requests, close and destroy sockets
+    client.requests = [];
+    for(var i = 0; i < client.sockets.length; ++i) {
+      client.sockets[i].close();
+      client.sockets[i].destroy();
+    }
+    client.socketPool = null;
+    client.sockets = [];
+    client.idle = [];
+  };
+
+  /**
+   * Sets a cookie for use with all connections made by this client. Any
+   * cookie with the same name will be replaced. If the cookie's value
+   * is undefined, null, or the blank string, the cookie will be removed.
+   *
+   * If the cookie's domain doesn't match this client's url host or the
+   * cookie's secure flag doesn't match this client's url scheme, then
+   * setting the cookie will fail with an exception.
+   *
+   * @param cookie the cookie with parameters:
+   *   name: the name of the cookie.
+   *   value: the value of the cookie.
+   *   comment: an optional comment string.
+   *   maxAge: the age of the cookie in seconds relative to created time.
+   *   secure: true if the cookie must be sent over a secure protocol.
+   *   httpOnly: true to restrict access to the cookie from javascript
+   *     (inaffective since the cookies are stored in javascript).
+   *   path: the path for the cookie.
+   *   domain: optional domain the cookie belongs to (must start with dot).
+   *   version: optional version of the cookie.
+   *   created: creation time, in UTC seconds, of the cookie.
+   */
+  client.setCookie = function(cookie) {
+    var rval;
+    if(typeof(cookie.name) !== 'undefined') {
+      if(cookie.value === null || typeof(cookie.value) === 'undefined' ||
+        cookie.value === '') {
+        // remove cookie
+        rval = client.removeCookie(cookie.name, cookie.path);
+      } else {
+        // set cookie defaults
+        cookie.comment = cookie.comment || '';
+        cookie.maxAge = cookie.maxAge || 0;
+        cookie.secure = (typeof(cookie.secure) === 'undefined') ?
+          true : cookie.secure;
+        cookie.httpOnly = cookie.httpOnly || true;
+        cookie.path = cookie.path || '/';
+        cookie.domain = cookie.domain || null;
+        cookie.version = cookie.version || null;
+        cookie.created = _getUtcTime(new Date());
+
+        // do secure check
+        if(cookie.secure !== client.secure) {
+          var error = new Error('Http client url scheme is incompatible ' +
+            'with cookie secure flag.');
+          error.url = client.url;
+          error.cookie = cookie;
+          throw error;
+        }
+        // make sure url host is within cookie.domain
+        if(!http.withinCookieDomain(client.url, cookie)) {
+          var error = new Error('Http client url scheme is incompatible ' +
+            'with cookie secure flag.');
+          error.url = client.url;
+          error.cookie = cookie;
+          throw error;
+        }
+
+        // add new cookie
+        if(!(cookie.name in client.cookies)) {
+          client.cookies[cookie.name] = {};
+        }
+        client.cookies[cookie.name][cookie.path] = cookie;
+        rval = true;
+
+        // save cookies
+        _saveCookies(client);
+      }
+    }
+
+    return rval;
+  };
+
+  /**
+   * Gets a cookie by its name.
+   *
+   * @param name the name of the cookie to retrieve.
+   * @param path an optional path for the cookie (if there are multiple
+   *          cookies with the same name but different paths).
+   *
+   * @return the cookie or null if not found.
+   */
+  client.getCookie = function(name, path) {
+    var rval = null;
+    if(name in client.cookies) {
+      var paths = client.cookies[name];
+
+      // get path-specific cookie
+      if(path) {
+        if(path in paths) {
+          rval = paths[path];
+        }
+      } else {
+        // get first cookie
+        for(var p in paths) {
+          rval = paths[p];
+          break;
+        }
+      }
+    }
+    return rval;
+  };
+
+  /**
+   * Removes a cookie.
+   *
+   * @param name the name of the cookie to remove.
+   * @param path an optional path for the cookie (if there are multiple
+   *          cookies with the same name but different paths).
+   *
+   * @return true if a cookie was removed, false if not.
+   */
+  client.removeCookie = function(name, path) {
+    var rval = false;
+    if(name in client.cookies) {
+      // delete the specific path
+      if(path) {
+        var paths = client.cookies[name];
+        if(path in paths) {
+          rval = true;
+          delete client.cookies[name][path];
+          // clean up entry if empty
+          var empty = true;
+          for(var i in client.cookies[name]) {
+            empty = false;
+            break;
+          }
+          if(empty) {
+            delete client.cookies[name];
+          }
+        }
+      } else {
+        // delete all cookies with the given name
+        rval = true;
+        delete client.cookies[name];
+      }
+    }
+    if(rval) {
+      // save cookies
+      _saveCookies(client);
+    }
+    return rval;
+  };
+
+  /**
+   * Clears all cookies stored in this client.
+   */
+  client.clearCookies = function() {
+    client.cookies = {};
+    _clearCookies(client);
+  };
+
+  if(forge.log) {
+    forge.log.debug('forge.http', 'created client', options);
+  }
+
+  return client;
+};
+
+/**
+ * Trims the whitespace off of the beginning and end of a string.
+ *
+ * @param str the string to trim.
+ *
+ * @return the trimmed string.
+ */
+var _trimString = function(str) {
+  return str.replace(/^\s*/, '').replace(/\s*$/, '');
+};
+
+/**
+ * Creates an http header object.
+ *
+ * @return the http header object.
+ */
+var _createHeader = function() {
+  var header = {
+    fields: {},
+    setField: function(name, value) {
+      // normalize field name, trim value
+      header.fields[_normalize(name)] = [_trimString('' + value)];
+    },
+    appendField: function(name, value) {
+      name = _normalize(name);
+      if(!(name in header.fields)) {
+        header.fields[name] = [];
+      }
+      header.fields[name].push(_trimString('' + value));
+    },
+    getField: function(name, index) {
+      var rval = null;
+      name = _normalize(name);
+      if(name in header.fields) {
+        index = index || 0;
+        rval = header.fields[name][index];
+      }
+      return rval;
+    }
+  };
+  return header;
+};
+
+/**
+ * Gets the time in utc seconds given a date.
+ *
+ * @param d the date to use.
+ *
+ * @return the time in utc seconds.
+ */
+var _getUtcTime = function(d) {
+  var utc = +d + d.getTimezoneOffset() * 60000;
+  return Math.floor(+new Date() / 1000);
+};
+
+/**
+ * Creates an http request.
+ *
+ * @param options:
+ *          version: the version.
+ *          method: the method.
+ *          path: the path.
+ *          body: the body.
+ *          headers: custom header fields to add,
+ *            eg: [{'Content-Length': 0}].
+ *
+ * @return the http request.
+ */
+http.createRequest = function(options) {
+  options = options || {};
+  var request = _createHeader();
+  request.version = options.version || 'HTTP/1.1';
+  request.method = options.method || null;
+  request.path = options.path || null;
+  request.body = options.body || null;
+  request.bodyDeflated = false;
+  request.flashApi = null;
+
+  // add custom headers
+  var headers = options.headers || [];
+  if(!forge.util.isArray(headers)) {
+    headers = [headers];
+  }
+  for(var i = 0; i < headers.length; ++i) {
+    for(var name in headers[i]) {
+      request.appendField(name, headers[i][name]);
+    }
+  }
+
+  /**
+   * Adds a cookie to the request 'Cookie' header.
+   *
+   * @param cookie a cookie to add.
+   */
+  request.addCookie = function(cookie) {
+    var value = '';
+    var field = request.getField('Cookie');
+    if(field !== null) {
+      // separate cookies by semi-colons
+      value = field + '; ';
+    }
+
+    // get current time in utc seconds
+    var now = _getUtcTime(new Date());
+
+    // output cookie name and value
+    value += cookie.name + '=' + cookie.value;
+    request.setField('Cookie', value);
+  };
+
+  /**
+   * Converts an http request into a string that can be sent as an
+   * HTTP request. Does not include any data.
+   *
+   * @return the string representation of the request.
+   */
+  request.toString = function() {
+    /* Sample request header:
+      GET /some/path/?query HTTP/1.1
+      Host: www.someurl.com
+      Connection: close
+      Accept-Encoding: deflate
+      Accept: image/gif, text/html
+      User-Agent: Mozilla 4.0
+     */
+
+    // set default headers
+    if(request.getField('User-Agent') === null) {
+      request.setField('User-Agent', 'forge.http 1.0');
+    }
+    if(request.getField('Accept') === null) {
+      request.setField('Accept', '*/*');
+    }
+    if(request.getField('Connection') === null) {
+      request.setField('Connection', 'keep-alive');
+      request.setField('Keep-Alive', '115');
+    }
+
+    // add Accept-Encoding if not specified
+    if(request.flashApi !== null &&
+      request.getField('Accept-Encoding') === null) {
+      request.setField('Accept-Encoding', 'deflate');
+    }
+
+    // if the body isn't null, deflate it if its larger than 100 bytes
+    if(request.flashApi !== null && request.body !== null &&
+      request.getField('Content-Encoding') === null &&
+      !request.bodyDeflated && request.body.length > 100) {
+      // use flash to compress data
+      request.body = forge.util.deflate(request.flashApi, request.body);
+      request.bodyDeflated = true;
+      request.setField('Content-Encoding', 'deflate');
+      request.setField('Content-Length', request.body.length);
+    } else if(request.body !== null) {
+      // set content length for body
+      request.setField('Content-Length', request.body.length);
+    }
+
+    // build start line
+    var rval =
+      request.method.toUpperCase() + ' ' + request.path + ' ' +
+      request.version + '\r\n';
+
+    // add each header
+    for(var name in request.fields) {
+      var fields = request.fields[name];
+      for(var i = 0; i < fields.length; ++i) {
+        rval += name + ': ' + fields[i] + '\r\n';
+      }
+    }
+    // final terminating CRLF
+    rval += '\r\n';
+
+    return rval;
+  };
+
+  return request;
+};
+
+/**
+ * Creates an empty http response header.
+ *
+ * @return the empty http response header.
+ */
+http.createResponse = function() {
+  // private vars
+  var _first = true;
+  var _chunkSize = 0;
+  var _chunksFinished = false;
+
+  // create response
+  var response = _createHeader();
+  response.version = null;
+  response.code = 0;
+  response.message = null;
+  response.body = null;
+  response.headerReceived = false;
+  response.bodyReceived = false;
+  response.flashApi = null;
+
+  /**
+   * Reads a line that ends in CRLF from a byte buffer.
+   *
+   * @param b the byte buffer.
+   *
+   * @return the line or null if none was found.
+   */
+  var _readCrlf = function(b) {
+    var line = null;
+    var i = b.data.indexOf('\r\n', b.read);
+    if(i != -1) {
+      // read line, skip CRLF
+      line = b.getBytes(i - b.read);
+      b.getBytes(2);
+    }
+    return line;
+  };
+
+  /**
+   * Parses a header field and appends it to the response.
+   *
+   * @param line the header field line.
+   */
+  var _parseHeader = function(line) {
+    var tmp = line.indexOf(':');
+    var name = line.substring(0, tmp++);
+    response.appendField(
+      name, (tmp < line.length) ? line.substring(tmp) : '');
+  };
+
+  /**
+   * Reads an http response header from a buffer of bytes.
+   *
+   * @param b the byte buffer to parse the header from.
+   *
+   * @return true if the whole header was read, false if not.
+   */
+  response.readHeader = function(b) {
+    // read header lines (each ends in CRLF)
+    var line = '';
+    while(!response.headerReceived && line !== null) {
+      line = _readCrlf(b);
+      if(line !== null) {
+        // parse first line
+        if(_first) {
+          _first = false;
+          var tmp = line.split(' ');
+          if(tmp.length >= 3) {
+            response.version = tmp[0];
+            response.code = parseInt(tmp[1], 10);
+            response.message = tmp.slice(2).join(' ');
+          } else {
+            // invalid header
+            var error = new Error('Invalid http response header.');
+            error.details = {'line': line};
+            throw error;
+          }
+        } else if(line.length === 0) {
+          // handle final line, end of header
+          response.headerReceived = true;
+        } else {
+          _parseHeader(line);
+        }
+      }
+    }
+
+    return response.headerReceived;
+  };
+
+  /**
+   * Reads some chunked http response entity-body from the given buffer of
+   * bytes.
+   *
+   * @param b the byte buffer to read from.
+   *
+   * @return true if the whole body was read, false if not.
+   */
+  var _readChunkedBody = function(b) {
+    /* Chunked transfer-encoding sends data in a series of chunks,
+      followed by a set of 0-N http trailers.
+      The format is as follows:
+
+      chunk-size (in hex) CRLF
+      chunk data (with "chunk-size" many bytes) CRLF
+      ... (N many chunks)
+      chunk-size (of 0 indicating the last chunk) CRLF
+      N many http trailers followed by CRLF
+      blank line + CRLF (terminates the trailers)
+
+      If there are no http trailers, then after the chunk-size of 0,
+      there is still a single CRLF (indicating the blank line + CRLF
+      that terminates the trailers). In other words, you always terminate
+      the trailers with blank line + CRLF, regardless of 0-N trailers. */
+
+      /* From RFC-2616, section 3.6.1, here is the pseudo-code for
+      implementing chunked transfer-encoding:
+
+      length := 0
+      read chunk-size, chunk-extension (if any) and CRLF
+      while (chunk-size > 0) {
+        read chunk-data and CRLF
+        append chunk-data to entity-body
+        length := length + chunk-size
+        read chunk-size and CRLF
+      }
+      read entity-header
+      while (entity-header not empty) {
+        append entity-header to existing header fields
+        read entity-header
+      }
+      Content-Length := length
+      Remove "chunked" from Transfer-Encoding
+    */
+
+    var line = '';
+    while(line !== null && b.length() > 0) {
+      // if in the process of reading a chunk
+      if(_chunkSize > 0) {
+        // if there are not enough bytes to read chunk and its
+        // trailing CRLF,  we must wait for more data to be received
+        if(_chunkSize + 2 > b.length()) {
+          break;
+        }
+
+        // read chunk data, skip CRLF
+        response.body += b.getBytes(_chunkSize);
+        b.getBytes(2);
+        _chunkSize = 0;
+      } else if(!_chunksFinished) {
+        // more chunks, read next chunk-size line
+        line = _readCrlf(b);
+        if(line !== null) {
+          // parse chunk-size (ignore any chunk extension)
+          _chunkSize = parseInt(line.split(';', 1)[0], 16);
+          _chunksFinished = (_chunkSize === 0);
+        }
+      } else {
+        // chunks finished, read next trailer
+        line = _readCrlf(b);
+        while(line !== null) {
+          if(line.length > 0) {
+            // parse trailer
+            _parseHeader(line);
+            // read next trailer
+            line = _readCrlf(b);
+          } else {
+            // body received
+            response.bodyReceived = true;
+            line = null;
+          }
+        }
+      }
+    }
+
+    return response.bodyReceived;
+  };
+
+  /**
+   * Reads an http response body from a buffer of bytes.
+   *
+   * @param b the byte buffer to read from.
+   *
+   * @return true if the whole body was read, false if not.
+   */
+  response.readBody = function(b) {
+    var contentLength = response.getField('Content-Length');
+    var transferEncoding = response.getField('Transfer-Encoding');
+    if(contentLength !== null) {
+      contentLength = parseInt(contentLength);
+    }
+
+    // read specified length
+    if(contentLength !== null && contentLength >= 0) {
+      response.body = response.body || '';
+      response.body += b.getBytes(contentLength);
+      response.bodyReceived = (response.body.length === contentLength);
+    } else if(transferEncoding !== null) {
+      // read chunked encoding
+      if(transferEncoding.indexOf('chunked') != -1) {
+        response.body = response.body || '';
+        _readChunkedBody(b);
+      } else {
+        var error = new Error('Unknown Transfer-Encoding.');
+        error.details = {'transferEncoding': transferEncoding};
+        throw error;
+      }
+    } else if((contentLength !== null && contentLength < 0) ||
+      (contentLength === null &&
+      response.getField('Content-Type') !== null)) {
+      // read all data in the buffer
+      response.body = response.body || '';
+      response.body += b.getBytes();
+      response.readBodyUntilClose = true;
+    } else {
+      // no body
+      response.body = null;
+      response.bodyReceived = true;
+    }
+
+    if(response.bodyReceived) {
+      response.time = +new Date() - response.time;
+    }
+
+    if(response.flashApi !== null &&
+      response.bodyReceived && response.body !== null &&
+      response.getField('Content-Encoding') === 'deflate') {
+      // inflate using flash api
+      response.body = forge.util.inflate(
+        response.flashApi, response.body);
+    }
+
+    return response.bodyReceived;
+  };
+
+   /**
+    * Parses an array of cookies from the 'Set-Cookie' field, if present.
+    *
+    * @return the array of cookies.
+    */
+   response.getCookies = function() {
+     var rval = [];
+
+     // get Set-Cookie field
+     if('Set-Cookie' in response.fields) {
+       var field = response.fields['Set-Cookie'];
+
+       // get current local time in seconds
+       var now = +new Date() / 1000;
+
+       // regex for parsing 'name1=value1; name2=value2; name3'
+       var regex = /\s*([^=]*)=?([^;]*)(;|$)/g;
+
+       // examples:
+       // Set-Cookie: cookie1_name=cookie1_value; max-age=0; path=/
+       // Set-Cookie: c2=v2; expires=Thu, 21-Aug-2008 23:47:25 GMT; path=/
+       for(var i = 0; i < field.length; ++i) {
+         var fv = field[i];
+         var m;
+         regex.lastIndex = 0;
+         var first = true;
+         var cookie = {};
+         do {
+           m = regex.exec(fv);
+           if(m !== null) {
+             var name = _trimString(m[1]);
+             var value = _trimString(m[2]);
+
+             // cookie_name=value
+             if(first) {
+               cookie.name = name;
+               cookie.value = value;
+               first = false;
+             } else {
+               // property_name=value
+               name = name.toLowerCase();
+               switch(name) {
+               case 'expires':
+                 // replace hyphens w/spaces so date will parse
+                 value = value.replace(/-/g, ' ');
+                 var secs = Date.parse(value) / 1000;
+                 cookie.maxAge = Math.max(0, secs - now);
+                 break;
+               case 'max-age':
+                 cookie.maxAge = parseInt(value, 10);
+                 break;
+               case 'secure':
+                 cookie.secure = true;
+                 break;
+               case 'httponly':
+                 cookie.httpOnly = true;
+                 break;
+               default:
+                 if(name !== '') {
+                   cookie[name] = value;
+                 }
+               }
+             }
+           }
+         } while(m !== null && m[0] !== '');
+         rval.push(cookie);
+       }
+     }
+
+     return rval;
+  };
+
+  /**
+   * Converts an http response into a string that can be sent as an
+   * HTTP response. Does not include any data.
+   *
+   * @return the string representation of the response.
+   */
+  response.toString = function() {
+    /* Sample response header:
+      HTTP/1.0 200 OK
+      Host: www.someurl.com
+      Connection: close
+     */
+
+    // build start line
+    var rval =
+      response.version + ' ' + response.code + ' ' + response.message + '\r\n';
+
+    // add each header
+    for(var name in response.fields) {
+      var fields = response.fields[name];
+      for(var i = 0; i < fields.length; ++i) {
+        rval += name + ': ' + fields[i] + '\r\n';
+      }
+    }
+    // final terminating CRLF
+    rval += '\r\n';
+
+    return rval;
+  };
+
+  return response;
+};
+
+/**
+ * Parses the scheme, host, and port from an http(s) url.
+ *
+ * @param str the url string.
+ *
+ * @return the parsed url object or null if the url is invalid.
+ */
+http.parseUrl = forge.util.parseUrl;
+
+/**
+ * Returns true if the given url is within the given cookie's domain.
+ *
+ * @param url the url to check.
+ * @param cookie the cookie or cookie domain to check.
+ */
+http.withinCookieDomain = function(url, cookie) {
+  var rval = false;
+
+  // cookie may be null, a cookie object, or a domain string
+  var domain = (cookie === null || typeof cookie === 'string') ?
+    cookie : cookie.domain;
+
+  // any domain will do
+  if(domain === null) {
+    rval = true;
+  } else if(domain.charAt(0) === '.') {
+    // ensure domain starts with a '.'
+    // parse URL as necessary
+    if(typeof url === 'string') {
+      url = http.parseUrl(url);
+    }
+
+    // add '.' to front of URL host to match against domain
+    var host = '.' + url.host;
+
+    // if the host ends with domain then it falls within it
+    var idx = host.lastIndexOf(domain);
+    if(idx !== -1 && (idx + domain.length === host.length)) {
+      rval = true;
+    }
+  }
+
+  return rval;
+};
+
+// public access to http namespace
+if(typeof forge === 'undefined') {
+  forge = {};
+}
+forge.http = http;
+
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/jsbn.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/jsbn.js
new file mode 100644
index 0000000..29a1509
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/jsbn.js
@@ -0,0 +1,1321 @@
+// Copyright (c) 2005  Tom Wu
+// All Rights Reserved.
+// See "LICENSE" for details.
+
+// Basic JavaScript BN library - subset useful for RSA encryption.
+
+/*
+Licensing (LICENSE)
+-------------------
+
+This software is covered under the following copyright:
+*/
+/*
+ * Copyright (c) 2003-2005  Tom Wu
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
+ * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
+ * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF
+ * THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * In addition, the following condition applies:
+ *
+ * All redistributions must retain an intact copy of this copyright notice
+ * and disclaimer.
+ */
+/*
+Address all questions regarding this license to:
+
+  Tom Wu
+  tjw@cs.Stanford.EDU
+*/
+
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+// Bits per digit
+var dbits;
+
+// JavaScript engine analysis
+var canary = 0xdeadbeefcafe;
+var j_lm = ((canary&0xffffff)==0xefcafe);
+
+// (public) Constructor
+function BigInteger(a,b,c) {
+  this.data = [];
+  if(a != null)
+    if("number" == typeof a) this.fromNumber(a,b,c);
+    else if(b == null && "string" != typeof a) this.fromString(a,256);
+    else this.fromString(a,b);
+}
+
+// return new, unset BigInteger
+function nbi() { return new BigInteger(null); }
+
+// am: Compute w_j += (x*this_i), propagate carries,
+// c is initial carry, returns final carry.
+// c < 3*dvalue, x < 2*dvalue, this_i < dvalue
+// We need to select the fastest one that works in this environment.
+
+// am1: use a single mult and divide to get the high bits,
+// max digit bits should be 26 because
+// max internal value = 2*dvalue^2-2*dvalue (< 2^53)
+function am1(i,x,w,j,c,n) {
+  while(--n >= 0) {
+    var v = x*this.data[i++]+w.data[j]+c;
+    c = Math.floor(v/0x4000000);
+    w.data[j++] = v&0x3ffffff;
+  }
+  return c;
+}
+// am2 avoids a big mult-and-extract completely.
+// Max digit bits should be <= 30 because we do bitwise ops
+// on values up to 2*hdvalue^2-hdvalue-1 (< 2^31)
+function am2(i,x,w,j,c,n) {
+  var xl = x&0x7fff, xh = x>>15;
+  while(--n >= 0) {
+    var l = this.data[i]&0x7fff;
+    var h = this.data[i++]>>15;
+    var m = xh*l+h*xl;
+    l = xl*l+((m&0x7fff)<<15)+w.data[j]+(c&0x3fffffff);
+    c = (l>>>30)+(m>>>15)+xh*h+(c>>>30);
+    w.data[j++] = l&0x3fffffff;
+  }
+  return c;
+}
+// Alternately, set max digit bits to 28 since some
+// browsers slow down when dealing with 32-bit numbers.
+function am3(i,x,w,j,c,n) {
+  var xl = x&0x3fff, xh = x>>14;
+  while(--n >= 0) {
+    var l = this.data[i]&0x3fff;
+    var h = this.data[i++]>>14;
+    var m = xh*l+h*xl;
+    l = xl*l+((m&0x3fff)<<14)+w.data[j]+c;
+    c = (l>>28)+(m>>14)+xh*h;
+    w.data[j++] = l&0xfffffff;
+  }
+  return c;
+}
+
+// node.js (no browser)
+if(typeof(navigator) === 'undefined')
+{
+   BigInteger.prototype.am = am3;
+   dbits = 28;
+} else if(j_lm && (navigator.appName == "Microsoft Internet Explorer")) {
+  BigInteger.prototype.am = am2;
+  dbits = 30;
+} else if(j_lm && (navigator.appName != "Netscape")) {
+  BigInteger.prototype.am = am1;
+  dbits = 26;
+} else { // Mozilla/Netscape seems to prefer am3
+  BigInteger.prototype.am = am3;
+  dbits = 28;
+}
+
+BigInteger.prototype.DB = dbits;
+BigInteger.prototype.DM = ((1<<dbits)-1);
+BigInteger.prototype.DV = (1<<dbits);
+
+var BI_FP = 52;
+BigInteger.prototype.FV = Math.pow(2,BI_FP);
+BigInteger.prototype.F1 = BI_FP-dbits;
+BigInteger.prototype.F2 = 2*dbits-BI_FP;
+
+// Digit conversions
+var BI_RM = "0123456789abcdefghijklmnopqrstuvwxyz";
+var BI_RC = new Array();
+var rr,vv;
+rr = "0".charCodeAt(0);
+for(vv = 0; vv <= 9; ++vv) BI_RC[rr++] = vv;
+rr = "a".charCodeAt(0);
+for(vv = 10; vv < 36; ++vv) BI_RC[rr++] = vv;
+rr = "A".charCodeAt(0);
+for(vv = 10; vv < 36; ++vv) BI_RC[rr++] = vv;
+
+function int2char(n) { return BI_RM.charAt(n); }
+function intAt(s,i) {
+  var c = BI_RC[s.charCodeAt(i)];
+  return (c==null)?-1:c;
+}
+
+// (protected) copy this to r
+function bnpCopyTo(r) {
+  for(var i = this.t-1; i >= 0; --i) r.data[i] = this.data[i];
+  r.t = this.t;
+  r.s = this.s;
+}
+
+// (protected) set from integer value x, -DV <= x < DV
+function bnpFromInt(x) {
+  this.t = 1;
+  this.s = (x<0)?-1:0;
+  if(x > 0) this.data[0] = x;
+  else if(x < -1) this.data[0] = x+this.DV;
+  else this.t = 0;
+}
+
+// return bigint initialized to value
+function nbv(i) { var r = nbi(); r.fromInt(i); return r; }
+
+// (protected) set from string and radix
+function bnpFromString(s,b) {
+  var k;
+  if(b == 16) k = 4;
+  else if(b == 8) k = 3;
+  else if(b == 256) k = 8; // byte array
+  else if(b == 2) k = 1;
+  else if(b == 32) k = 5;
+  else if(b == 4) k = 2;
+  else { this.fromRadix(s,b); return; }
+  this.t = 0;
+  this.s = 0;
+  var i = s.length, mi = false, sh = 0;
+  while(--i >= 0) {
+    var x = (k==8)?s[i]&0xff:intAt(s,i);
+    if(x < 0) {
+      if(s.charAt(i) == "-") mi = true;
+      continue;
+    }
+    mi = false;
+    if(sh == 0)
+      this.data[this.t++] = x;
+    else if(sh+k > this.DB) {
+      this.data[this.t-1] |= (x&((1<<(this.DB-sh))-1))<<sh;
+      this.data[this.t++] = (x>>(this.DB-sh));
+    } else
+      this.data[this.t-1] |= x<<sh;
+    sh += k;
+    if(sh >= this.DB) sh -= this.DB;
+  }
+  if(k == 8 && (s[0]&0x80) != 0) {
+    this.s = -1;
+    if(sh > 0) this.data[this.t-1] |= ((1<<(this.DB-sh))-1)<<sh;
+  }
+  this.clamp();
+  if(mi) BigInteger.ZERO.subTo(this,this);
+}
+
+// (protected) clamp off excess high words
+function bnpClamp() {
+  var c = this.s&this.DM;
+  while(this.t > 0 && this.data[this.t-1] == c) --this.t;
+}
+
+// (public) return string representation in given radix
+function bnToString(b) {
+  if(this.s < 0) return "-"+this.negate().toString(b);
+  var k;
+  if(b == 16) k = 4;
+  else if(b == 8) k = 3;
+  else if(b == 2) k = 1;
+  else if(b == 32) k = 5;
+  else if(b == 4) k = 2;
+  else return this.toRadix(b);
+  var km = (1<<k)-1, d, m = false, r = "", i = this.t;
+  var p = this.DB-(i*this.DB)%k;
+  if(i-- > 0) {
+    if(p < this.DB && (d = this.data[i]>>p) > 0) { m = true; r = int2char(d); }
+    while(i >= 0) {
+      if(p < k) {
+        d = (this.data[i]&((1<<p)-1))<<(k-p);
+        d |= this.data[--i]>>(p+=this.DB-k);
+      } else {
+        d = (this.data[i]>>(p-=k))&km;
+        if(p <= 0) { p += this.DB; --i; }
+      }
+      if(d > 0) m = true;
+      if(m) r += int2char(d);
+    }
+  }
+  return m?r:"0";
+}
+
+// (public) -this
+function bnNegate() { var r = nbi(); BigInteger.ZERO.subTo(this,r); return r; }
+
+// (public) |this|
+function bnAbs() { return (this.s<0)?this.negate():this; }
+
+// (public) return + if this > a, - if this < a, 0 if equal
+function bnCompareTo(a) {
+  var r = this.s-a.s;
+  if(r != 0) return r;
+  var i = this.t;
+  r = i-a.t;
+  if(r != 0) return (this.s<0)?-r:r;
+  while(--i >= 0) if((r=this.data[i]-a.data[i]) != 0) return r;
+  return 0;
+}
+
+// returns bit length of the integer x
+function nbits(x) {
+  var r = 1, t;
+  if((t=x>>>16) != 0) { x = t; r += 16; }
+  if((t=x>>8) != 0) { x = t; r += 8; }
+  if((t=x>>4) != 0) { x = t; r += 4; }
+  if((t=x>>2) != 0) { x = t; r += 2; }
+  if((t=x>>1) != 0) { x = t; r += 1; }
+  return r;
+}
+
+// (public) return the number of bits in "this"
+function bnBitLength() {
+  if(this.t <= 0) return 0;
+  return this.DB*(this.t-1)+nbits(this.data[this.t-1]^(this.s&this.DM));
+}
+
+// (protected) r = this << n*DB
+function bnpDLShiftTo(n,r) {
+  var i;
+  for(i = this.t-1; i >= 0; --i) r.data[i+n] = this.data[i];
+  for(i = n-1; i >= 0; --i) r.data[i] = 0;
+  r.t = this.t+n;
+  r.s = this.s;
+}
+
+// (protected) r = this >> n*DB
+function bnpDRShiftTo(n,r) {
+  for(var i = n; i < this.t; ++i) r.data[i-n] = this.data[i];
+  r.t = Math.max(this.t-n,0);
+  r.s = this.s;
+}
+
+// (protected) r = this << n
+function bnpLShiftTo(n,r) {
+  var bs = n%this.DB;
+  var cbs = this.DB-bs;
+  var bm = (1<<cbs)-1;
+  var ds = Math.floor(n/this.DB), c = (this.s<<bs)&this.DM, i;
+  for(i = this.t-1; i >= 0; --i) {
+    r.data[i+ds+1] = (this.data[i]>>cbs)|c;
+    c = (this.data[i]&bm)<<bs;
+  }
+  for(i = ds-1; i >= 0; --i) r.data[i] = 0;
+  r.data[ds] = c;
+  r.t = this.t+ds+1;
+  r.s = this.s;
+  r.clamp();
+}
+
+// (protected) r = this >> n
+function bnpRShiftTo(n,r) {
+  r.s = this.s;
+  var ds = Math.floor(n/this.DB);
+  if(ds >= this.t) { r.t = 0; return; }
+  var bs = n%this.DB;
+  var cbs = this.DB-bs;
+  var bm = (1<<bs)-1;
+  r.data[0] = this.data[ds]>>bs;
+  for(var i = ds+1; i < this.t; ++i) {
+    r.data[i-ds-1] |= (this.data[i]&bm)<<cbs;
+    r.data[i-ds] = this.data[i]>>bs;
+  }
+  if(bs > 0) r.data[this.t-ds-1] |= (this.s&bm)<<cbs;
+  r.t = this.t-ds;
+  r.clamp();
+}
+
+// (protected) r = this - a
+function bnpSubTo(a,r) {
+  var i = 0, c = 0, m = Math.min(a.t,this.t);
+  while(i < m) {
+    c += this.data[i]-a.data[i];
+    r.data[i++] = c&this.DM;
+    c >>= this.DB;
+  }
+  if(a.t < this.t) {
+    c -= a.s;
+    while(i < this.t) {
+      c += this.data[i];
+      r.data[i++] = c&this.DM;
+      c >>= this.DB;
+    }
+    c += this.s;
+  } else {
+    c += this.s;
+    while(i < a.t) {
+      c -= a.data[i];
+      r.data[i++] = c&this.DM;
+      c >>= this.DB;
+    }
+    c -= a.s;
+  }
+  r.s = (c<0)?-1:0;
+  if(c < -1) r.data[i++] = this.DV+c;
+  else if(c > 0) r.data[i++] = c;
+  r.t = i;
+  r.clamp();
+}
+
+// (protected) r = this * a, r != this,a (HAC 14.12)
+// "this" should be the larger one if appropriate.
+function bnpMultiplyTo(a,r) {
+  var x = this.abs(), y = a.abs();
+  var i = x.t;
+  r.t = i+y.t;
+  while(--i >= 0) r.data[i] = 0;
+  for(i = 0; i < y.t; ++i) r.data[i+x.t] = x.am(0,y.data[i],r,i,0,x.t);
+  r.s = 0;
+  r.clamp();
+  if(this.s != a.s) BigInteger.ZERO.subTo(r,r);
+}
+
+// (protected) r = this^2, r != this (HAC 14.16)
+function bnpSquareTo(r) {
+  var x = this.abs();
+  var i = r.t = 2*x.t;
+  while(--i >= 0) r.data[i] = 0;
+  for(i = 0; i < x.t-1; ++i) {
+    var c = x.am(i,x.data[i],r,2*i,0,1);
+    if((r.data[i+x.t]+=x.am(i+1,2*x.data[i],r,2*i+1,c,x.t-i-1)) >= x.DV) {
+      r.data[i+x.t] -= x.DV;
+      r.data[i+x.t+1] = 1;
+    }
+  }
+  if(r.t > 0) r.data[r.t-1] += x.am(i,x.data[i],r,2*i,0,1);
+  r.s = 0;
+  r.clamp();
+}
+
+// (protected) divide this by m, quotient and remainder to q, r (HAC 14.20)
+// r != q, this != m.  q or r may be null.
+function bnpDivRemTo(m,q,r) {
+  var pm = m.abs();
+  if(pm.t <= 0) return;
+  var pt = this.abs();
+  if(pt.t < pm.t) {
+    if(q != null) q.fromInt(0);
+    if(r != null) this.copyTo(r);
+    return;
+  }
+  if(r == null) r = nbi();
+  var y = nbi(), ts = this.s, ms = m.s;
+  var nsh = this.DB-nbits(pm.data[pm.t-1]);	// normalize modulus
+  if(nsh > 0) { pm.lShiftTo(nsh,y); pt.lShiftTo(nsh,r); } else { pm.copyTo(y); pt.copyTo(r); }
+  var ys = y.t;
+  var y0 = y.data[ys-1];
+  if(y0 == 0) return;
+  var yt = y0*(1<<this.F1)+((ys>1)?y.data[ys-2]>>this.F2:0);
+  var d1 = this.FV/yt, d2 = (1<<this.F1)/yt, e = 1<<this.F2;
+  var i = r.t, j = i-ys, t = (q==null)?nbi():q;
+  y.dlShiftTo(j,t);
+  if(r.compareTo(t) >= 0) {
+    r.data[r.t++] = 1;
+    r.subTo(t,r);
+  }
+  BigInteger.ONE.dlShiftTo(ys,t);
+  t.subTo(y,y);	// "negative" y so we can replace sub with am later
+  while(y.t < ys) y.data[y.t++] = 0;
+  while(--j >= 0) {
+    // Estimate quotient digit
+    var qd = (r.data[--i]==y0)?this.DM:Math.floor(r.data[i]*d1+(r.data[i-1]+e)*d2);
+    if((r.data[i]+=y.am(0,qd,r,j,0,ys)) < qd) {	// Try it out
+      y.dlShiftTo(j,t);
+      r.subTo(t,r);
+      while(r.data[i] < --qd) r.subTo(t,r);
+    }
+  }
+  if(q != null) {
+    r.drShiftTo(ys,q);
+    if(ts != ms) BigInteger.ZERO.subTo(q,q);
+  }
+  r.t = ys;
+  r.clamp();
+  if(nsh > 0) r.rShiftTo(nsh,r);	// Denormalize remainder
+  if(ts < 0) BigInteger.ZERO.subTo(r,r);
+}
+
+// (public) this mod a
+function bnMod(a) {
+  var r = nbi();
+  this.abs().divRemTo(a,null,r);
+  if(this.s < 0 && r.compareTo(BigInteger.ZERO) > 0) a.subTo(r,r);
+  return r;
+}
+
+// Modular reduction using "classic" algorithm
+function Classic(m) { this.m = m; }
+function cConvert(x) {
+  if(x.s < 0 || x.compareTo(this.m) >= 0) return x.mod(this.m);
+  else return x;
+}
+function cRevert(x) { return x; }
+function cReduce(x) { x.divRemTo(this.m,null,x); }
+function cMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); }
+function cSqrTo(x,r) { x.squareTo(r); this.reduce(r); }
+
+Classic.prototype.convert = cConvert;
+Classic.prototype.revert = cRevert;
+Classic.prototype.reduce = cReduce;
+Classic.prototype.mulTo = cMulTo;
+Classic.prototype.sqrTo = cSqrTo;
+
+// (protected) return "-1/this % 2^DB"; useful for Mont. reduction
+// justification:
+//         xy == 1 (mod m)
+//         xy =  1+km
+//   xy(2-xy) = (1+km)(1-km)
+// x[y(2-xy)] = 1-k^2m^2
+// x[y(2-xy)] == 1 (mod m^2)
+// if y is 1/x mod m, then y(2-xy) is 1/x mod m^2
+// should reduce x and y(2-xy) by m^2 at each step to keep size bounded.
+// JS multiply "overflows" differently from C/C++, so care is needed here.
+function bnpInvDigit() {
+  if(this.t < 1) return 0;
+  var x = this.data[0];
+  if((x&1) == 0) return 0;
+  var y = x&3;		// y == 1/x mod 2^2
+  y = (y*(2-(x&0xf)*y))&0xf;	// y == 1/x mod 2^4
+  y = (y*(2-(x&0xff)*y))&0xff;	// y == 1/x mod 2^8
+  y = (y*(2-(((x&0xffff)*y)&0xffff)))&0xffff;	// y == 1/x mod 2^16
+  // last step - calculate inverse mod DV directly;
+  // assumes 16 < DB <= 32 and assumes ability to handle 48-bit ints
+  y = (y*(2-x*y%this.DV))%this.DV;		// y == 1/x mod 2^dbits
+  // we really want the negative inverse, and -DV < y < DV
+  return (y>0)?this.DV-y:-y;
+}
+
+// Montgomery reduction
+function Montgomery(m) {
+  this.m = m;
+  this.mp = m.invDigit();
+  this.mpl = this.mp&0x7fff;
+  this.mph = this.mp>>15;
+  this.um = (1<<(m.DB-15))-1;
+  this.mt2 = 2*m.t;
+}
+
+// xR mod m
+function montConvert(x) {
+  var r = nbi();
+  x.abs().dlShiftTo(this.m.t,r);
+  r.divRemTo(this.m,null,r);
+  if(x.s < 0 && r.compareTo(BigInteger.ZERO) > 0) this.m.subTo(r,r);
+  return r;
+}
+
+// x/R mod m
+function montRevert(x) {
+  var r = nbi();
+  x.copyTo(r);
+  this.reduce(r);
+  return r;
+}
+
+// x = x/R mod m (HAC 14.32)
+function montReduce(x) {
+  while(x.t <= this.mt2)	// pad x so am has enough room later
+    x.data[x.t++] = 0;
+  for(var i = 0; i < this.m.t; ++i) {
+    // faster way of calculating u0 = x.data[i]*mp mod DV
+    var j = x.data[i]&0x7fff;
+    var u0 = (j*this.mpl+(((j*this.mph+(x.data[i]>>15)*this.mpl)&this.um)<<15))&x.DM;
+    // use am to combine the multiply-shift-add into one call
+    j = i+this.m.t;
+    x.data[j] += this.m.am(0,u0,x,i,0,this.m.t);
+    // propagate carry
+    while(x.data[j] >= x.DV) { x.data[j] -= x.DV; x.data[++j]++; }
+  }
+  x.clamp();
+  x.drShiftTo(this.m.t,x);
+  if(x.compareTo(this.m) >= 0) x.subTo(this.m,x);
+}
+
+// r = "x^2/R mod m"; x != r
+function montSqrTo(x,r) { x.squareTo(r); this.reduce(r); }
+
+// r = "xy/R mod m"; x,y != r
+function montMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); }
+
+Montgomery.prototype.convert = montConvert;
+Montgomery.prototype.revert = montRevert;
+Montgomery.prototype.reduce = montReduce;
+Montgomery.prototype.mulTo = montMulTo;
+Montgomery.prototype.sqrTo = montSqrTo;
+
+// (protected) true iff this is even
+function bnpIsEven() { return ((this.t>0)?(this.data[0]&1):this.s) == 0; }
+
+// (protected) this^e, e < 2^32, doing sqr and mul with "r" (HAC 14.79)
+function bnpExp(e,z) {
+  if(e > 0xffffffff || e < 1) return BigInteger.ONE;
+  var r = nbi(), r2 = nbi(), g = z.convert(this), i = nbits(e)-1;
+  g.copyTo(r);
+  while(--i >= 0) {
+    z.sqrTo(r,r2);
+    if((e&(1<<i)) > 0) z.mulTo(r2,g,r);
+    else { var t = r; r = r2; r2 = t; }
+  }
+  return z.revert(r);
+}
+
+// (public) this^e % m, 0 <= e < 2^32
+function bnModPowInt(e,m) {
+  var z;
+  if(e < 256 || m.isEven()) z = new Classic(m); else z = new Montgomery(m);
+  return this.exp(e,z);
+}
+
+// protected
+BigInteger.prototype.copyTo = bnpCopyTo;
+BigInteger.prototype.fromInt = bnpFromInt;
+BigInteger.prototype.fromString = bnpFromString;
+BigInteger.prototype.clamp = bnpClamp;
+BigInteger.prototype.dlShiftTo = bnpDLShiftTo;
+BigInteger.prototype.drShiftTo = bnpDRShiftTo;
+BigInteger.prototype.lShiftTo = bnpLShiftTo;
+BigInteger.prototype.rShiftTo = bnpRShiftTo;
+BigInteger.prototype.subTo = bnpSubTo;
+BigInteger.prototype.multiplyTo = bnpMultiplyTo;
+BigInteger.prototype.squareTo = bnpSquareTo;
+BigInteger.prototype.divRemTo = bnpDivRemTo;
+BigInteger.prototype.invDigit = bnpInvDigit;
+BigInteger.prototype.isEven = bnpIsEven;
+BigInteger.prototype.exp = bnpExp;
+
+// public
+BigInteger.prototype.toString = bnToString;
+BigInteger.prototype.negate = bnNegate;
+BigInteger.prototype.abs = bnAbs;
+BigInteger.prototype.compareTo = bnCompareTo;
+BigInteger.prototype.bitLength = bnBitLength;
+BigInteger.prototype.mod = bnMod;
+BigInteger.prototype.modPowInt = bnModPowInt;
+
+// "constants"
+BigInteger.ZERO = nbv(0);
+BigInteger.ONE = nbv(1);
+
+// jsbn2 lib
+
+//Copyright (c) 2005-2009  Tom Wu
+//All Rights Reserved.
+//See "LICENSE" for details (See jsbn.js for LICENSE).
+
+//Extended JavaScript BN functions, required for RSA private ops.
+
+//Version 1.1: new BigInteger("0", 10) returns "proper" zero
+
+//(public)
+function bnClone() { var r = nbi(); this.copyTo(r); return r; }
+
+//(public) return value as integer
+function bnIntValue() {
+if(this.s < 0) {
+ if(this.t == 1) return this.data[0]-this.DV;
+ else if(this.t == 0) return -1;
+} else if(this.t == 1) return this.data[0];
+else if(this.t == 0) return 0;
+// assumes 16 < DB < 32
+return ((this.data[1]&((1<<(32-this.DB))-1))<<this.DB)|this.data[0];
+}
+
+//(public) return value as byte
+function bnByteValue() { return (this.t==0)?this.s:(this.data[0]<<24)>>24; }
+
+//(public) return value as short (assumes DB>=16)
+function bnShortValue() { return (this.t==0)?this.s:(this.data[0]<<16)>>16; }
+
+//(protected) return x s.t. r^x < DV
+function bnpChunkSize(r) { return Math.floor(Math.LN2*this.DB/Math.log(r)); }
+
+//(public) 0 if this == 0, 1 if this > 0
+function bnSigNum() {
+if(this.s < 0) return -1;
+else if(this.t <= 0 || (this.t == 1 && this.data[0] <= 0)) return 0;
+else return 1;
+}
+
+//(protected) convert to radix string
+function bnpToRadix(b) {
+if(b == null) b = 10;
+if(this.signum() == 0 || b < 2 || b > 36) return "0";
+var cs = this.chunkSize(b);
+var a = Math.pow(b,cs);
+var d = nbv(a), y = nbi(), z = nbi(), r = "";
+this.divRemTo(d,y,z);
+while(y.signum() > 0) {
+ r = (a+z.intValue()).toString(b).substr(1) + r;
+ y.divRemTo(d,y,z);
+}
+return z.intValue().toString(b) + r;
+}
+
+//(protected) convert from radix string
+function bnpFromRadix(s,b) {
+this.fromInt(0);
+if(b == null) b = 10;
+var cs = this.chunkSize(b);
+var d = Math.pow(b,cs), mi = false, j = 0, w = 0;
+for(var i = 0; i < s.length; ++i) {
+ var x = intAt(s,i);
+ if(x < 0) {
+   if(s.charAt(i) == "-" && this.signum() == 0) mi = true;
+   continue;
+ }
+ w = b*w+x;
+ if(++j >= cs) {
+   this.dMultiply(d);
+   this.dAddOffset(w,0);
+   j = 0;
+   w = 0;
+ }
+}
+if(j > 0) {
+ this.dMultiply(Math.pow(b,j));
+ this.dAddOffset(w,0);
+}
+if(mi) BigInteger.ZERO.subTo(this,this);
+}
+
+//(protected) alternate constructor
+function bnpFromNumber(a,b,c) {
+if("number" == typeof b) {
+ // new BigInteger(int,int,RNG)
+ if(a < 2) this.fromInt(1);
+ else {
+   this.fromNumber(a,c);
+   if(!this.testBit(a-1))  // force MSB set
+     this.bitwiseTo(BigInteger.ONE.shiftLeft(a-1),op_or,this);
+   if(this.isEven()) this.dAddOffset(1,0); // force odd
+   while(!this.isProbablePrime(b)) {
+     this.dAddOffset(2,0);
+     if(this.bitLength() > a) this.subTo(BigInteger.ONE.shiftLeft(a-1),this);
+   }
+ }
+} else {
+ // new BigInteger(int,RNG)
+ var x = new Array(), t = a&7;
+ x.length = (a>>3)+1;
+ b.nextBytes(x);
+ if(t > 0) x[0] &= ((1<<t)-1); else x[0] = 0;
+ this.fromString(x,256);
+}
+}
+
+//(public) convert to bigendian byte array
+function bnToByteArray() {
+var i = this.t, r = new Array();
+r[0] = this.s;
+var p = this.DB-(i*this.DB)%8, d, k = 0;
+if(i-- > 0) {
+ if(p < this.DB && (d = this.data[i]>>p) != (this.s&this.DM)>>p)
+   r[k++] = d|(this.s<<(this.DB-p));
+ while(i >= 0) {
+   if(p < 8) {
+     d = (this.data[i]&((1<<p)-1))<<(8-p);
+     d |= this.data[--i]>>(p+=this.DB-8);
+   } else {
+     d = (this.data[i]>>(p-=8))&0xff;
+     if(p <= 0) { p += this.DB; --i; }
+   }
+   if((d&0x80) != 0) d |= -256;
+   if(k == 0 && (this.s&0x80) != (d&0x80)) ++k;
+   if(k > 0 || d != this.s) r[k++] = d;
+ }
+}
+return r;
+}
+
+function bnEquals(a) { return(this.compareTo(a)==0); }
+function bnMin(a) { return(this.compareTo(a)<0)?this:a; }
+function bnMax(a) { return(this.compareTo(a)>0)?this:a; }
+
+//(protected) r = this op a (bitwise)
+function bnpBitwiseTo(a,op,r) {
+var i, f, m = Math.min(a.t,this.t);
+for(i = 0; i < m; ++i) r.data[i] = op(this.data[i],a.data[i]);
+if(a.t < this.t) {
+ f = a.s&this.DM;
+ for(i = m; i < this.t; ++i) r.data[i] = op(this.data[i],f);
+ r.t = this.t;
+} else {
+ f = this.s&this.DM;
+ for(i = m; i < a.t; ++i) r.data[i] = op(f,a.data[i]);
+ r.t = a.t;
+}
+r.s = op(this.s,a.s);
+r.clamp();
+}
+
+//(public) this & a
+function op_and(x,y) { return x&y; }
+function bnAnd(a) { var r = nbi(); this.bitwiseTo(a,op_and,r); return r; }
+
+//(public) this | a
+function op_or(x,y) { return x|y; }
+function bnOr(a) { var r = nbi(); this.bitwiseTo(a,op_or,r); return r; }
+
+//(public) this ^ a
+function op_xor(x,y) { return x^y; }
+function bnXor(a) { var r = nbi(); this.bitwiseTo(a,op_xor,r); return r; }
+
+//(public) this & ~a
+function op_andnot(x,y) { return x&~y; }
+function bnAndNot(a) { var r = nbi(); this.bitwiseTo(a,op_andnot,r); return r; }
+
+//(public) ~this
+function bnNot() {
+var r = nbi();
+for(var i = 0; i < this.t; ++i) r.data[i] = this.DM&~this.data[i];
+r.t = this.t;
+r.s = ~this.s;
+return r;
+}
+
+//(public) this << n
+function bnShiftLeft(n) {
+var r = nbi();
+if(n < 0) this.rShiftTo(-n,r); else this.lShiftTo(n,r);
+return r;
+}
+
+//(public) this >> n
+function bnShiftRight(n) {
+var r = nbi();
+if(n < 0) this.lShiftTo(-n,r); else this.rShiftTo(n,r);
+return r;
+}
+
+//return index of lowest 1-bit in x, x < 2^31
+function lbit(x) {
+if(x == 0) return -1;
+var r = 0;
+if((x&0xffff) == 0) { x >>= 16; r += 16; }
+if((x&0xff) == 0) { x >>= 8; r += 8; }
+if((x&0xf) == 0) { x >>= 4; r += 4; }
+if((x&3) == 0) { x >>= 2; r += 2; }
+if((x&1) == 0) ++r;
+return r;
+}
+
+//(public) returns index of lowest 1-bit (or -1 if none)
+function bnGetLowestSetBit() {
+for(var i = 0; i < this.t; ++i)
+ if(this.data[i] != 0) return i*this.DB+lbit(this.data[i]);
+if(this.s < 0) return this.t*this.DB;
+return -1;
+}
+
+//return number of 1 bits in x
+function cbit(x) {
+var r = 0;
+while(x != 0) { x &= x-1; ++r; }
+return r;
+}
+
+//(public) return number of set bits
+function bnBitCount() {
+var r = 0, x = this.s&this.DM;
+for(var i = 0; i < this.t; ++i) r += cbit(this.data[i]^x);
+return r;
+}
+
+//(public) true iff nth bit is set
+function bnTestBit(n) {
+var j = Math.floor(n/this.DB);
+if(j >= this.t) return(this.s!=0);
+return((this.data[j]&(1<<(n%this.DB)))!=0);
+}
+
+//(protected) this op (1<<n)
+function bnpChangeBit(n,op) {
+var r = BigInteger.ONE.shiftLeft(n);
+this.bitwiseTo(r,op,r);
+return r;
+}
+
+//(public) this | (1<<n)
+function bnSetBit(n) { return this.changeBit(n,op_or); }
+
+//(public) this & ~(1<<n)
+function bnClearBit(n) { return this.changeBit(n,op_andnot); }
+
+//(public) this ^ (1<<n)
+function bnFlipBit(n) { return this.changeBit(n,op_xor); }
+
+//(protected) r = this + a
+function bnpAddTo(a,r) {
+var i = 0, c = 0, m = Math.min(a.t,this.t);
+while(i < m) {
+ c += this.data[i]+a.data[i];
+ r.data[i++] = c&this.DM;
+ c >>= this.DB;
+}
+if(a.t < this.t) {
+ c += a.s;
+ while(i < this.t) {
+   c += this.data[i];
+   r.data[i++] = c&this.DM;
+   c >>= this.DB;
+ }
+ c += this.s;
+} else {
+ c += this.s;
+ while(i < a.t) {
+   c += a.data[i];
+   r.data[i++] = c&this.DM;
+   c >>= this.DB;
+ }
+ c += a.s;
+}
+r.s = (c<0)?-1:0;
+if(c > 0) r.data[i++] = c;
+else if(c < -1) r.data[i++] = this.DV+c;
+r.t = i;
+r.clamp();
+}
+
+//(public) this + a
+function bnAdd(a) { var r = nbi(); this.addTo(a,r); return r; }
+
+//(public) this - a
+function bnSubtract(a) { var r = nbi(); this.subTo(a,r); return r; }
+
+//(public) this * a
+function bnMultiply(a) { var r = nbi(); this.multiplyTo(a,r); return r; }
+
+//(public) this / a
+function bnDivide(a) { var r = nbi(); this.divRemTo(a,r,null); return r; }
+
+//(public) this % a
+function bnRemainder(a) { var r = nbi(); this.divRemTo(a,null,r); return r; }
+
+//(public) [this/a,this%a]
+function bnDivideAndRemainder(a) {
+var q = nbi(), r = nbi();
+this.divRemTo(a,q,r);
+return new Array(q,r);
+}
+
+//(protected) this *= n, this >= 0, 1 < n < DV
+function bnpDMultiply(n) {
+this.data[this.t] = this.am(0,n-1,this,0,0,this.t);
+++this.t;
+this.clamp();
+}
+
+//(protected) this += n << w words, this >= 0
+function bnpDAddOffset(n,w) {
+if(n == 0) return;
+while(this.t <= w) this.data[this.t++] = 0;
+this.data[w] += n;
+while(this.data[w] >= this.DV) {
+ this.data[w] -= this.DV;
+ if(++w >= this.t) this.data[this.t++] = 0;
+ ++this.data[w];
+}
+}
+
+//A "null" reducer
+function NullExp() {}
+function nNop(x) { return x; }
+function nMulTo(x,y,r) { x.multiplyTo(y,r); }
+function nSqrTo(x,r) { x.squareTo(r); }
+
+NullExp.prototype.convert = nNop;
+NullExp.prototype.revert = nNop;
+NullExp.prototype.mulTo = nMulTo;
+NullExp.prototype.sqrTo = nSqrTo;
+
+//(public) this^e
+function bnPow(e) { return this.exp(e,new NullExp()); }
+
+//(protected) r = lower n words of "this * a", a.t <= n
+//"this" should be the larger one if appropriate.
+function bnpMultiplyLowerTo(a,n,r) {
+var i = Math.min(this.t+a.t,n);
+r.s = 0; // assumes a,this >= 0
+r.t = i;
+while(i > 0) r.data[--i] = 0;
+var j;
+for(j = r.t-this.t; i < j; ++i) r.data[i+this.t] = this.am(0,a.data[i],r,i,0,this.t);
+for(j = Math.min(a.t,n); i < j; ++i) this.am(0,a.data[i],r,i,0,n-i);
+r.clamp();
+}
+
+//(protected) r = "this * a" without lower n words, n > 0
+//"this" should be the larger one if appropriate.
+function bnpMultiplyUpperTo(a,n,r) {
+--n;
+var i = r.t = this.t+a.t-n;
+r.s = 0; // assumes a,this >= 0
+while(--i >= 0) r.data[i] = 0;
+for(i = Math.max(n-this.t,0); i < a.t; ++i)
+ r.data[this.t+i-n] = this.am(n-i,a.data[i],r,0,0,this.t+i-n);
+r.clamp();
+r.drShiftTo(1,r);
+}
+
+//Barrett modular reduction
+function Barrett(m) {
+// setup Barrett
+this.r2 = nbi();
+this.q3 = nbi();
+BigInteger.ONE.dlShiftTo(2*m.t,this.r2);
+this.mu = this.r2.divide(m);
+this.m = m;
+}
+
+function barrettConvert(x) {
+if(x.s < 0 || x.t > 2*this.m.t) return x.mod(this.m);
+else if(x.compareTo(this.m) < 0) return x;
+else { var r = nbi(); x.copyTo(r); this.reduce(r); return r; }
+}
+
+function barrettRevert(x) { return x; }
+
+//x = x mod m (HAC 14.42)
+function barrettReduce(x) {
+x.drShiftTo(this.m.t-1,this.r2);
+if(x.t > this.m.t+1) { x.t = this.m.t+1; x.clamp(); }
+this.mu.multiplyUpperTo(this.r2,this.m.t+1,this.q3);
+this.m.multiplyLowerTo(this.q3,this.m.t+1,this.r2);
+while(x.compareTo(this.r2) < 0) x.dAddOffset(1,this.m.t+1);
+x.subTo(this.r2,x);
+while(x.compareTo(this.m) >= 0) x.subTo(this.m,x);
+}
+
+//r = x^2 mod m; x != r
+function barrettSqrTo(x,r) { x.squareTo(r); this.reduce(r); }
+
+//r = x*y mod m; x,y != r
+function barrettMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); }
+
+Barrett.prototype.convert = barrettConvert;
+Barrett.prototype.revert = barrettRevert;
+Barrett.prototype.reduce = barrettReduce;
+Barrett.prototype.mulTo = barrettMulTo;
+Barrett.prototype.sqrTo = barrettSqrTo;
+
+//(public) this^e % m (HAC 14.85)
+function bnModPow(e,m) {
+var i = e.bitLength(), k, r = nbv(1), z;
+if(i <= 0) return r;
+else if(i < 18) k = 1;
+else if(i < 48) k = 3;
+else if(i < 144) k = 4;
+else if(i < 768) k = 5;
+else k = 6;
+if(i < 8)
+ z = new Classic(m);
+else if(m.isEven())
+ z = new Barrett(m);
+else
+ z = new Montgomery(m);
+
+// precomputation
+var g = new Array(), n = 3, k1 = k-1, km = (1<<k)-1;
+g[1] = z.convert(this);
+if(k > 1) {
+ var g2 = nbi();
+ z.sqrTo(g[1],g2);
+ while(n <= km) {
+   g[n] = nbi();
+   z.mulTo(g2,g[n-2],g[n]);
+   n += 2;
+ }
+}
+
+var j = e.t-1, w, is1 = true, r2 = nbi(), t;
+i = nbits(e.data[j])-1;
+while(j >= 0) {
+ if(i >= k1) w = (e.data[j]>>(i-k1))&km;
+ else {
+   w = (e.data[j]&((1<<(i+1))-1))<<(k1-i);
+   if(j > 0) w |= e.data[j-1]>>(this.DB+i-k1);
+ }
+
+ n = k;
+ while((w&1) == 0) { w >>= 1; --n; }
+ if((i -= n) < 0) { i += this.DB; --j; }
+ if(is1) {  // ret == 1, don't bother squaring or multiplying it
+   g[w].copyTo(r);
+   is1 = false;
+ } else {
+   while(n > 1) { z.sqrTo(r,r2); z.sqrTo(r2,r); n -= 2; }
+   if(n > 0) z.sqrTo(r,r2); else { t = r; r = r2; r2 = t; }
+   z.mulTo(r2,g[w],r);
+ }
+
+ while(j >= 0 && (e.data[j]&(1<<i)) == 0) {
+   z.sqrTo(r,r2); t = r; r = r2; r2 = t;
+   if(--i < 0) { i = this.DB-1; --j; }
+ }
+}
+return z.revert(r);
+}
+
+//(public) gcd(this,a) (HAC 14.54)
+function bnGCD(a) {
+var x = (this.s<0)?this.negate():this.clone();
+var y = (a.s<0)?a.negate():a.clone();
+if(x.compareTo(y) < 0) { var t = x; x = y; y = t; }
+var i = x.getLowestSetBit(), g = y.getLowestSetBit();
+if(g < 0) return x;
+if(i < g) g = i;
+if(g > 0) {
+ x.rShiftTo(g,x);
+ y.rShiftTo(g,y);
+}
+while(x.signum() > 0) {
+ if((i = x.getLowestSetBit()) > 0) x.rShiftTo(i,x);
+ if((i = y.getLowestSetBit()) > 0) y.rShiftTo(i,y);
+ if(x.compareTo(y) >= 0) {
+   x.subTo(y,x);
+   x.rShiftTo(1,x);
+ } else {
+   y.subTo(x,y);
+   y.rShiftTo(1,y);
+ }
+}
+if(g > 0) y.lShiftTo(g,y);
+return y;
+}
+
+//(protected) this % n, n < 2^26
+function bnpModInt(n) {
+if(n <= 0) return 0;
+var d = this.DV%n, r = (this.s<0)?n-1:0;
+if(this.t > 0)
+ if(d == 0) r = this.data[0]%n;
+ else for(var i = this.t-1; i >= 0; --i) r = (d*r+this.data[i])%n;
+return r;
+}
+
+//(public) 1/this % m (HAC 14.61)
+function bnModInverse(m) {
+var ac = m.isEven();
+if((this.isEven() && ac) || m.signum() == 0) return BigInteger.ZERO;
+var u = m.clone(), v = this.clone();
+var a = nbv(1), b = nbv(0), c = nbv(0), d = nbv(1);
+while(u.signum() != 0) {
+ while(u.isEven()) {
+   u.rShiftTo(1,u);
+   if(ac) {
+     if(!a.isEven() || !b.isEven()) { a.addTo(this,a); b.subTo(m,b); }
+     a.rShiftTo(1,a);
+   } else if(!b.isEven()) b.subTo(m,b);
+   b.rShiftTo(1,b);
+ }
+ while(v.isEven()) {
+   v.rShiftTo(1,v);
+   if(ac) {
+     if(!c.isEven() || !d.isEven()) { c.addTo(this,c); d.subTo(m,d); }
+     c.rShiftTo(1,c);
+   } else if(!d.isEven()) d.subTo(m,d);
+   d.rShiftTo(1,d);
+ }
+ if(u.compareTo(v) >= 0) {
+   u.subTo(v,u);
+   if(ac) a.subTo(c,a);
+   b.subTo(d,b);
+ } else {
+   v.subTo(u,v);
+   if(ac) c.subTo(a,c);
+   d.subTo(b,d);
+ }
+}
+if(v.compareTo(BigInteger.ONE) != 0) return BigInteger.ZERO;
+if(d.compareTo(m) >= 0) return d.subtract(m);
+if(d.signum() < 0) d.addTo(m,d); else return d;
+if(d.signum() < 0) return d.add(m); else return d;
+}
+
+var lowprimes = [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,503,509];
+var lplim = (1<<26)/lowprimes[lowprimes.length-1];
+
+//(public) test primality with certainty >= 1-.5^t
+function bnIsProbablePrime(t) {
+var i, x = this.abs();
+if(x.t == 1 && x.data[0] <= lowprimes[lowprimes.length-1]) {
+ for(i = 0; i < lowprimes.length; ++i)
+   if(x.data[0] == lowprimes[i]) return true;
+ return false;
+}
+if(x.isEven()) return false;
+i = 1;
+while(i < lowprimes.length) {
+ var m = lowprimes[i], j = i+1;
+ while(j < lowprimes.length && m < lplim) m *= lowprimes[j++];
+ m = x.modInt(m);
+ while(i < j) if(m%lowprimes[i++] == 0) return false;
+}
+return x.millerRabin(t);
+}
+
+//(protected) true if probably prime (HAC 4.24, Miller-Rabin)
+function bnpMillerRabin(t) {
+var n1 = this.subtract(BigInteger.ONE);
+var k = n1.getLowestSetBit();
+if(k <= 0) return false;
+var r = n1.shiftRight(k);
+var prng = bnGetPrng();
+var a;
+for(var i = 0; i < t; ++i) {
+ // select witness 'a' at random from between 1 and n1
+ do {
+   a = new BigInteger(this.bitLength(), prng);
+ }
+ while(a.compareTo(BigInteger.ONE) <= 0 || a.compareTo(n1) >= 0);
+ var y = a.modPow(r,this);
+ if(y.compareTo(BigInteger.ONE) != 0 && y.compareTo(n1) != 0) {
+   var j = 1;
+   while(j++ < k && y.compareTo(n1) != 0) {
+     y = y.modPowInt(2,this);
+     if(y.compareTo(BigInteger.ONE) == 0) return false;
+   }
+   if(y.compareTo(n1) != 0) return false;
+ }
+}
+return true;
+}
+
+// get pseudo random number generator
+function bnGetPrng() {
+  // create prng with api that matches BigInteger secure random
+  return {
+    // x is an array to fill with bytes
+    nextBytes: function(x) {
+      for(var i = 0; i < x.length; ++i) {
+        x[i] = Math.floor(Math.random() * 0xFF);
+      }
+    }
+  };
+}
+
+//protected
+BigInteger.prototype.chunkSize = bnpChunkSize;
+BigInteger.prototype.toRadix = bnpToRadix;
+BigInteger.prototype.fromRadix = bnpFromRadix;
+BigInteger.prototype.fromNumber = bnpFromNumber;
+BigInteger.prototype.bitwiseTo = bnpBitwiseTo;
+BigInteger.prototype.changeBit = bnpChangeBit;
+BigInteger.prototype.addTo = bnpAddTo;
+BigInteger.prototype.dMultiply = bnpDMultiply;
+BigInteger.prototype.dAddOffset = bnpDAddOffset;
+BigInteger.prototype.multiplyLowerTo = bnpMultiplyLowerTo;
+BigInteger.prototype.multiplyUpperTo = bnpMultiplyUpperTo;
+BigInteger.prototype.modInt = bnpModInt;
+BigInteger.prototype.millerRabin = bnpMillerRabin;
+
+//public
+BigInteger.prototype.clone = bnClone;
+BigInteger.prototype.intValue = bnIntValue;
+BigInteger.prototype.byteValue = bnByteValue;
+BigInteger.prototype.shortValue = bnShortValue;
+BigInteger.prototype.signum = bnSigNum;
+BigInteger.prototype.toByteArray = bnToByteArray;
+BigInteger.prototype.equals = bnEquals;
+BigInteger.prototype.min = bnMin;
+BigInteger.prototype.max = bnMax;
+BigInteger.prototype.and = bnAnd;
+BigInteger.prototype.or = bnOr;
+BigInteger.prototype.xor = bnXor;
+BigInteger.prototype.andNot = bnAndNot;
+BigInteger.prototype.not = bnNot;
+BigInteger.prototype.shiftLeft = bnShiftLeft;
+BigInteger.prototype.shiftRight = bnShiftRight;
+BigInteger.prototype.getLowestSetBit = bnGetLowestSetBit;
+BigInteger.prototype.bitCount = bnBitCount;
+BigInteger.prototype.testBit = bnTestBit;
+BigInteger.prototype.setBit = bnSetBit;
+BigInteger.prototype.clearBit = bnClearBit;
+BigInteger.prototype.flipBit = bnFlipBit;
+BigInteger.prototype.add = bnAdd;
+BigInteger.prototype.subtract = bnSubtract;
+BigInteger.prototype.multiply = bnMultiply;
+BigInteger.prototype.divide = bnDivide;
+BigInteger.prototype.remainder = bnRemainder;
+BigInteger.prototype.divideAndRemainder = bnDivideAndRemainder;
+BigInteger.prototype.modPow = bnModPow;
+BigInteger.prototype.modInverse = bnModInverse;
+BigInteger.prototype.pow = bnPow;
+BigInteger.prototype.gcd = bnGCD;
+BigInteger.prototype.isProbablePrime = bnIsProbablePrime;
+
+//BigInteger interfaces not implemented in jsbn:
+
+//BigInteger(int signum, byte[] magnitude)
+//double doubleValue()
+//float floatValue()
+//int hashCode()
+//long longValue()
+//static BigInteger valueOf(long val)
+
+forge.jsbn = forge.jsbn || {};
+forge.jsbn.BigInteger = BigInteger;
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'jsbn';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/kem.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/kem.js
new file mode 100644
index 0000000..7ac7851
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/kem.js
@@ -0,0 +1,221 @@
+/**
+ * Javascript implementation of RSA-KEM.
+ *
+ * @author Lautaro Cozzani Rodriguez
+ * @author Dave Longley
+ *
+ * Copyright (c) 2014 Lautaro Cozzani <lautaro.cozzani@scytl.com>
+ * Copyright (c) 2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+forge.kem = forge.kem || {};
+
+var BigInteger = forge.jsbn.BigInteger;
+
+/**
+ * The API for the RSA Key Encapsulation Mechanism (RSA-KEM) from ISO 18033-2.
+ */
+forge.kem.rsa = {};
+
+/**
+ * Creates an RSA KEM API object for generating a secret asymmetric key.
+ *
+ * The symmetric key may be generated via a call to 'encrypt', which will
+ * produce a ciphertext to be transmitted to the recipient and a key to be
+ * kept secret. The ciphertext is a parameter to be passed to 'decrypt' which
+ * will produce the same secret key for the recipient to use to decrypt a
+ * message that was encrypted with the secret key.
+ *
+ * @param kdf the KDF API to use (eg: new forge.kem.kdf1()).
+ * @param options the options to use.
+ *          [prng] a custom crypto-secure pseudo-random number generator to use,
+ *            that must define "getBytesSync".
+ */
+forge.kem.rsa.create = function(kdf, options) {
+  options = options || {};
+  var prng = options.prng || forge.random;
+
+  var kem = {};
+
+  /**
+   * Generates a secret key and its encapsulation.
+   *
+   * @param publicKey the RSA public key to encrypt with.
+   * @param keyLength the length, in bytes, of the secret key to generate.
+   *
+   * @return an object with:
+   *   encapsulation: the ciphertext for generating the secret key, as a
+   *     binary-encoded string of bytes.
+   *   key: the secret key to use for encrypting a message.
+   */
+  kem.encrypt = function(publicKey, keyLength) {
+    // generate a random r where 1 > r > n
+    var byteLength = Math.ceil(publicKey.n.bitLength() / 8);
+    var r;
+    do {
+      r = new BigInteger(
+        forge.util.bytesToHex(prng.getBytesSync(byteLength)),
+        16).mod(publicKey.n);
+    } while(r.equals(BigInteger.ZERO));
+
+    // prepend r with zeros
+    r = forge.util.hexToBytes(r.toString(16));
+    var zeros = byteLength - r.length;
+    if(zeros > 0) {
+      r = forge.util.fillString(String.fromCharCode(0), zeros) + r;
+    }
+
+    // encrypt the random
+    var encapsulation = publicKey.encrypt(r, 'NONE');
+
+    // generate the secret key
+    var key = kdf.generate(r, keyLength);
+
+    return {encapsulation: encapsulation, key: key};
+  };
+
+  /**
+   * Decrypts an encapsulated secret key.
+   *
+   * @param privateKey the RSA private key to decrypt with.
+   * @param encapsulation the ciphertext for generating the secret key, as
+   *          a binary-encoded string of bytes.
+   * @param keyLength the length, in bytes, of the secret key to generate.
+   *
+   * @return the secret key as a binary-encoded string of bytes.
+   */
+  kem.decrypt = function(privateKey, encapsulation, keyLength) {
+    // decrypt the encapsulation and generate the secret key
+    var r = privateKey.decrypt(encapsulation, 'NONE');
+    return kdf.generate(r, keyLength);
+  };
+
+  return kem;
+};
+
+// TODO: add forge.kem.kdf.create('KDF1', {md: ..., ...}) API?
+
+/**
+ * Creates a key derivation API object that implements KDF1 per ISO 18033-2.
+ *
+ * @param md the hash API to use.
+ * @param [digestLength] an optional digest length that must be positive and
+ *          less than or equal to md.digestLength.
+ *
+ * @return a KDF1 API object.
+ */
+forge.kem.kdf1 = function(md, digestLength) {
+  _createKDF(this, md, 0, digestLength || md.digestLength);
+};
+
+/**
+ * Creates a key derivation API object that implements KDF2 per ISO 18033-2.
+ *
+ * @param md the hash API to use.
+ * @param [digestLength] an optional digest length that must be positive and
+ *          less than or equal to md.digestLength.
+ *
+ * @return a KDF2 API object.
+ */
+forge.kem.kdf2 = function(md, digestLength) {
+  _createKDF(this, md, 1, digestLength || md.digestLength);
+};
+
+/**
+ * Creates a KDF1 or KDF2 API object.
+ *
+ * @param md the hash API to use.
+ * @param counterStart the starting index for the counter.
+ * @param digestLength the digest length to use.
+ *
+ * @return the KDF API object.
+ */
+function _createKDF(kdf, md, counterStart, digestLength) {
+  /**
+   * Generate a key of the specified length.
+   *
+   * @param x the binary-encoded byte string to generate a key from.
+   * @param length the number of bytes to generate (the size of the key).
+   *
+   * @return the key as a binary-encoded string.
+   */
+  kdf.generate = function(x, length) {
+    var key = new forge.util.ByteBuffer();
+
+    // run counter from counterStart to ceil(length / Hash.len)
+    var k = Math.ceil(length / digestLength) + counterStart;
+
+    var c = new forge.util.ByteBuffer();
+    for(var i = counterStart; i < k; ++i) {
+      // I2OSP(i, 4): convert counter to an octet string of 4 octets
+      c.putInt32(i);
+
+      // digest 'x' and the counter and add the result to the key
+      md.start();
+      md.update(x + c.getBytes());
+      var hash = md.digest();
+      key.putBytes(hash.getBytes(digestLength));
+    }
+
+    // truncate to the correct key length
+    key.truncate(key.length() - length);
+    return key.getBytes();
+  };
+}
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'kem';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './util','./random','./jsbn'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/log.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/log.js
new file mode 100644
index 0000000..c7931f5
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/log.js
@@ -0,0 +1,372 @@
+/**
+ * Cross-browser support for logging in a web application.
+ *
+ * @author David I. Lehn <dlehn@digitalbazaar.com>
+ *
+ * Copyright (c) 2008-2013 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+/* LOG API */
+forge.log = forge.log || {};
+
+/**
+ * Application logging system.
+ *
+ * Each logger level available as it's own function of the form:
+ *   forge.log.level(category, args...)
+ * The category is an arbitrary string, and the args are the same as
+ * Firebug's console.log API. By default the call will be output as:
+ *   'LEVEL [category] <args[0]>, args[1], ...'
+ * This enables proper % formatting via the first argument.
+ * Each category is enabled by default but can be enabled or disabled with
+ * the setCategoryEnabled() function.
+ */
+// list of known levels
+forge.log.levels = [
+  'none', 'error', 'warning', 'info', 'debug', 'verbose', 'max'];
+// info on the levels indexed by name:
+//   index: level index
+//   name: uppercased display name
+var sLevelInfo = {};
+// list of loggers
+var sLoggers = [];
+/**
+ * Standard console logger. If no console support is enabled this will
+ * remain null. Check before using.
+ */
+var sConsoleLogger = null;
+
+// logger flags
+/**
+ * Lock the level at the current value. Used in cases where user config may
+ * set the level such that only critical messages are seen but more verbose
+ * messages are needed for debugging or other purposes.
+ */
+forge.log.LEVEL_LOCKED = (1 << 1);
+/**
+ * Always call log function. By default, the logging system will check the
+ * message level against logger.level before calling the log function. This
+ * flag allows the function to do its own check.
+ */
+forge.log.NO_LEVEL_CHECK = (1 << 2);
+/**
+ * Perform message interpolation with the passed arguments. "%" style
+ * fields in log messages will be replaced by arguments as needed. Some
+ * loggers, such as Firebug, may do this automatically. The original log
+ * message will be available as 'message' and the interpolated version will
+ * be available as 'fullMessage'.
+ */
+forge.log.INTERPOLATE = (1 << 3);
+
+// setup each log level
+for(var i = 0; i < forge.log.levels.length; ++i) {
+  var level = forge.log.levels[i];
+  sLevelInfo[level] = {
+    index: i,
+    name: level.toUpperCase()
+  };
+}
+
+/**
+ * Message logger. Will dispatch a message to registered loggers as needed.
+ *
+ * @param message message object
+ */
+forge.log.logMessage = function(message) {
+  var messageLevelIndex = sLevelInfo[message.level].index;
+  for(var i = 0; i < sLoggers.length; ++i) {
+    var logger = sLoggers[i];
+    if(logger.flags & forge.log.NO_LEVEL_CHECK) {
+      logger.f(message);
+    } else {
+      // get logger level
+      var loggerLevelIndex = sLevelInfo[logger.level].index;
+      // check level
+      if(messageLevelIndex <= loggerLevelIndex) {
+        // message critical enough, call logger
+        logger.f(logger, message);
+      }
+    }
+  }
+};
+
+/**
+ * Sets the 'standard' key on a message object to:
+ * "LEVEL [category] " + message
+ *
+ * @param message a message log object
+ */
+forge.log.prepareStandard = function(message) {
+  if(!('standard' in message)) {
+    message.standard =
+      sLevelInfo[message.level].name +
+      //' ' + +message.timestamp +
+      ' [' + message.category + '] ' +
+      message.message;
+  }
+};
+
+/**
+ * Sets the 'full' key on a message object to the original message
+ * interpolated via % formatting with the message arguments.
+ *
+ * @param message a message log object.
+ */
+forge.log.prepareFull = function(message) {
+  if(!('full' in message)) {
+    // copy args and insert message at the front
+    var args = [message.message];
+    args = args.concat([] || message['arguments']);
+    // format the message
+    message.full = forge.util.format.apply(this, args);
+  }
+};
+
+/**
+ * Applies both preparseStandard() and prepareFull() to a message object and
+ * store result in 'standardFull'.
+ *
+ * @param message a message log object.
+ */
+forge.log.prepareStandardFull = function(message) {
+  if(!('standardFull' in message)) {
+    // FIXME implement 'standardFull' logging
+    forge.log.prepareStandard(message);
+    message.standardFull = message.standard;
+  }
+};
+
+// create log level functions
+if(true) {
+  // levels for which we want functions
+  var levels = ['error', 'warning', 'info', 'debug', 'verbose'];
+  for(var i = 0; i < levels.length; ++i) {
+    // wrap in a function to ensure proper level var is passed
+    (function(level) {
+      // create function for this level
+      forge.log[level] = function(category, message/*, args...*/) {
+        // convert arguments to real array, remove category and message
+        var args = Array.prototype.slice.call(arguments).slice(2);
+        // create message object
+        // Note: interpolation and standard formatting is done lazily
+        var msg = {
+          timestamp: new Date(),
+          level: level,
+          category: category,
+          message: message,
+          'arguments': args
+          /*standard*/
+          /*full*/
+          /*fullMessage*/
+        };
+        // process this message
+        forge.log.logMessage(msg);
+      };
+    })(levels[i]);
+  }
+}
+
+/**
+ * Creates a new logger with specified custom logging function.
+ *
+ * The logging function has a signature of:
+ *   function(logger, message)
+ * logger: current logger
+ * message: object:
+ *   level: level id
+ *   category: category
+ *   message: string message
+ *   arguments: Array of extra arguments
+ *   fullMessage: interpolated message and arguments if INTERPOLATE flag set
+ *
+ * @param logFunction a logging function which takes a log message object
+ *          as a parameter.
+ *
+ * @return a logger object.
+ */
+forge.log.makeLogger = function(logFunction) {
+  var logger = {
+    flags: 0,
+    f: logFunction
+  };
+  forge.log.setLevel(logger, 'none');
+  return logger;
+};
+
+/**
+ * Sets the current log level on a logger.
+ *
+ * @param logger the target logger.
+ * @param level the new maximum log level as a string.
+ *
+ * @return true if set, false if not.
+ */
+forge.log.setLevel = function(logger, level) {
+  var rval = false;
+  if(logger && !(logger.flags & forge.log.LEVEL_LOCKED)) {
+    for(var i = 0; i < forge.log.levels.length; ++i) {
+      var aValidLevel = forge.log.levels[i];
+      if(level == aValidLevel) {
+        // set level
+        logger.level = level;
+        rval = true;
+        break;
+      }
+    }
+  }
+
+  return rval;
+};
+
+/**
+ * Locks the log level at its current value.
+ *
+ * @param logger the target logger.
+ * @param lock boolean lock value, default to true.
+ */
+forge.log.lock = function(logger, lock) {
+  if(typeof lock === 'undefined' || lock) {
+    logger.flags |= forge.log.LEVEL_LOCKED;
+  } else {
+    logger.flags &= ~forge.log.LEVEL_LOCKED;
+  }
+};
+
+/**
+ * Adds a logger.
+ *
+ * @param logger the logger object.
+ */
+forge.log.addLogger = function(logger) {
+  sLoggers.push(logger);
+};
+
+// setup the console logger if possible, else create fake console.log
+if(typeof(console) !== 'undefined' && 'log' in console) {
+  var logger;
+  if(console.error && console.warn && console.info && console.debug) {
+    // looks like Firebug-style logging is available
+    // level handlers map
+    var levelHandlers = {
+      error: console.error,
+      warning: console.warn,
+      info: console.info,
+      debug: console.debug,
+      verbose: console.debug
+    };
+    var f = function(logger, message) {
+      forge.log.prepareStandard(message);
+      var handler = levelHandlers[message.level];
+      // prepend standard message and concat args
+      var args = [message.standard];
+      args = args.concat(message['arguments'].slice());
+      // apply to low-level console function
+      handler.apply(console, args);
+    };
+    logger = forge.log.makeLogger(f);
+  } else {
+    // only appear to have basic console.log
+    var f = function(logger, message) {
+      forge.log.prepareStandardFull(message);
+      console.log(message.standardFull);
+    };
+    logger = forge.log.makeLogger(f);
+  }
+  forge.log.setLevel(logger, 'debug');
+  forge.log.addLogger(logger);
+  sConsoleLogger = logger;
+} else {
+  // define fake console.log to avoid potential script errors on
+  // browsers that do not have console logging
+  console = {
+    log: function() {}
+  };
+}
+
+/*
+ * Check for logging control query vars.
+ *
+ * console.level=<level-name>
+ * Set's the console log level by name.  Useful to override defaults and
+ * allow more verbose logging before a user config is loaded.
+ *
+ * console.lock=<true|false>
+ * Lock the console log level at whatever level it is set at.  This is run
+ * after console.level is processed.  Useful to force a level of verbosity
+ * that could otherwise be limited by a user config.
+ */
+if(sConsoleLogger !== null) {
+  var query = forge.util.getQueryVariables();
+  if('console.level' in query) {
+    // set with last value
+    forge.log.setLevel(
+      sConsoleLogger, query['console.level'].slice(-1)[0]);
+  }
+  if('console.lock' in query) {
+    // set with last value
+    var lock = query['console.lock'].slice(-1)[0];
+    if(lock == 'true') {
+      forge.log.lock(sConsoleLogger);
+    }
+  }
+}
+
+// provide public access to console logger
+forge.log.consoleLogger = sConsoleLogger;
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'log';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/md.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/md.js
new file mode 100644
index 0000000..e980cfd
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/md.js
@@ -0,0 +1,75 @@
+/**
+ * Node.js module for Forge message digests.
+ *
+ * @author Dave Longley
+ *
+ * Copyright 2011-2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+forge.md = forge.md || {};
+forge.md.algorithms = {
+  md5: forge.md5,
+  sha1: forge.sha1,
+  sha256: forge.sha256
+};
+forge.md.md5 = forge.md5;
+forge.md.sha1 = forge.sha1;
+forge.md.sha256 = forge.sha256;
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'md';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(
+  ['require', 'module', './md5', './sha1', './sha256', './sha512'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/md5.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/md5.js
new file mode 100644
index 0000000..acf7d11
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/md5.js
@@ -0,0 +1,322 @@
+/**
+ * Message Digest Algorithm 5 with 128-bit digest (MD5) implementation.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+var md5 = forge.md5 = forge.md5 || {};
+forge.md = forge.md || {};
+forge.md.algorithms = forge.md.algorithms || {};
+forge.md.md5 = forge.md.algorithms.md5 = md5;
+
+/**
+ * Creates an MD5 message digest object.
+ *
+ * @return a message digest object.
+ */
+md5.create = function() {
+  // do initialization as necessary
+  if(!_initialized) {
+    _init();
+  }
+
+  // MD5 state contains four 32-bit integers
+  var _state = null;
+
+  // input buffer
+  var _input = forge.util.createBuffer();
+
+  // used for word storage
+  var _w = new Array(16);
+
+  // message digest object
+  var md = {
+    algorithm: 'md5',
+    blockLength: 64,
+    digestLength: 16,
+    // 56-bit length of message so far (does not including padding)
+    messageLength: 0,
+    // true 64-bit message length as two 32-bit ints
+    messageLength64: [0, 0]
+  };
+
+  /**
+   * Starts the digest.
+   *
+   * @return this digest object.
+   */
+  md.start = function() {
+    md.messageLength = 0;
+    md.messageLength64 = [0, 0];
+    _input = forge.util.createBuffer();
+    _state = {
+      h0: 0x67452301,
+      h1: 0xEFCDAB89,
+      h2: 0x98BADCFE,
+      h3: 0x10325476
+    };
+    return md;
+  };
+  // start digest automatically for first time
+  md.start();
+
+  /**
+   * Updates the digest with the given message input. The given input can
+   * treated as raw input (no encoding will be applied) or an encoding of
+   * 'utf8' maybe given to encode the input using UTF-8.
+   *
+   * @param msg the message input to update with.
+   * @param encoding the encoding to use (default: 'raw', other: 'utf8').
+   *
+   * @return this digest object.
+   */
+  md.update = function(msg, encoding) {
+    if(encoding === 'utf8') {
+      msg = forge.util.encodeUtf8(msg);
+    }
+
+    // update message length
+    md.messageLength += msg.length;
+    md.messageLength64[0] += (msg.length / 0x100000000) >>> 0;
+    md.messageLength64[1] += msg.length >>> 0;
+
+    // add bytes to input buffer
+    _input.putBytes(msg);
+
+    // process bytes
+    _update(_state, _w, _input);
+
+    // compact input buffer every 2K or if empty
+    if(_input.read > 2048 || _input.length() === 0) {
+      _input.compact();
+    }
+
+    return md;
+  };
+
+  /**
+   * Produces the digest.
+   *
+   * @return a byte buffer containing the digest value.
+   */
+  md.digest = function() {
+    /* Note: Here we copy the remaining bytes in the input buffer and
+    add the appropriate MD5 padding. Then we do the final update
+    on a copy of the state so that if the user wants to get
+    intermediate digests they can do so. */
+
+    /* Determine the number of bytes that must be added to the message
+    to ensure its length is congruent to 448 mod 512. In other words,
+    the data to be digested must be a multiple of 512 bits (or 128 bytes).
+    This data includes the message, some padding, and the length of the
+    message. Since the length of the message will be encoded as 8 bytes (64
+    bits), that means that the last segment of the data must have 56 bytes
+    (448 bits) of message and padding. Therefore, the length of the message
+    plus the padding must be congruent to 448 mod 512 because
+    512 - 128 = 448.
+
+    In order to fill up the message length it must be filled with
+    padding that begins with 1 bit followed by all 0 bits. Padding
+    must *always* be present, so if the message length is already
+    congruent to 448 mod 512, then 512 padding bits must be added. */
+
+    // 512 bits == 64 bytes, 448 bits == 56 bytes, 64 bits = 8 bytes
+    // _padding starts with 1 byte with first bit is set in it which
+    // is byte value 128, then there may be up to 63 other pad bytes
+    var padBytes = forge.util.createBuffer();
+    padBytes.putBytes(_input.bytes());
+    // 64 - (remaining msg + 8 bytes msg length) mod 64
+    padBytes.putBytes(
+      _padding.substr(0, 64 - ((md.messageLength64[1] + 8) & 0x3F)));
+
+    /* Now append length of the message. The length is appended in bits
+    as a 64-bit number in little-endian order. Since we store the length in
+    bytes, we must multiply the 64-bit length by 8 (or left shift by 3). */
+    padBytes.putInt32Le(md.messageLength64[1] << 3);
+    padBytes.putInt32Le(
+      (md.messageLength64[0] << 3) | (md.messageLength64[0] >>> 28));
+    var s2 = {
+      h0: _state.h0,
+      h1: _state.h1,
+      h2: _state.h2,
+      h3: _state.h3
+    };
+    _update(s2, _w, padBytes);
+    var rval = forge.util.createBuffer();
+    rval.putInt32Le(s2.h0);
+    rval.putInt32Le(s2.h1);
+    rval.putInt32Le(s2.h2);
+    rval.putInt32Le(s2.h3);
+    return rval;
+  };
+
+  return md;
+};
+
+// padding, constant tables for calculating md5
+var _padding = null;
+var _g = null;
+var _r = null;
+var _k = null;
+var _initialized = false;
+
+/**
+ * Initializes the constant tables.
+ */
+function _init() {
+  // create padding
+  _padding = String.fromCharCode(128);
+  _padding += forge.util.fillString(String.fromCharCode(0x00), 64);
+
+  // g values
+  _g = [
+    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+    1, 6, 11, 0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12,
+    5, 8, 11, 14, 1, 4, 7, 10, 13, 0, 3, 6, 9, 12, 15, 2,
+    0, 7, 14, 5, 12, 3, 10, 1, 8, 15, 6, 13, 4, 11, 2, 9];
+
+  // rounds table
+  _r = [
+    7, 12, 17, 22,  7, 12, 17, 22,  7, 12, 17, 22,  7, 12, 17, 22,
+    5,  9, 14, 20,  5,  9, 14, 20,  5,  9, 14, 20,  5,  9, 14, 20,
+    4, 11, 16, 23,  4, 11, 16, 23,  4, 11, 16, 23,  4, 11, 16, 23,
+    6, 10, 15, 21,  6, 10, 15, 21,  6, 10, 15, 21,  6, 10, 15, 21];
+
+  // get the result of abs(sin(i + 1)) as a 32-bit integer
+  _k = new Array(64);
+  for(var i = 0; i < 64; ++i) {
+    _k[i] = Math.floor(Math.abs(Math.sin(i + 1)) * 0x100000000);
+  }
+
+  // now initialized
+  _initialized = true;
+}
+
+/**
+ * Updates an MD5 state with the given byte buffer.
+ *
+ * @param s the MD5 state to update.
+ * @param w the array to use to store words.
+ * @param bytes the byte buffer to update with.
+ */
+function _update(s, w, bytes) {
+  // consume 512 bit (64 byte) chunks
+  var t, a, b, c, d, f, r, i;
+  var len = bytes.length();
+  while(len >= 64) {
+    // initialize hash value for this chunk
+    a = s.h0;
+    b = s.h1;
+    c = s.h2;
+    d = s.h3;
+
+    // round 1
+    for(i = 0; i < 16; ++i) {
+      w[i] = bytes.getInt32Le();
+      f = d ^ (b & (c ^ d));
+      t = (a + f + _k[i] + w[i]);
+      r = _r[i];
+      a = d;
+      d = c;
+      c = b;
+      b += (t << r) | (t >>> (32 - r));
+    }
+    // round 2
+    for(; i < 32; ++i) {
+      f = c ^ (d & (b ^ c));
+      t = (a + f + _k[i] + w[_g[i]]);
+      r = _r[i];
+      a = d;
+      d = c;
+      c = b;
+      b += (t << r) | (t >>> (32 - r));
+    }
+    // round 3
+    for(; i < 48; ++i) {
+      f = b ^ c ^ d;
+      t = (a + f + _k[i] + w[_g[i]]);
+      r = _r[i];
+      a = d;
+      d = c;
+      c = b;
+      b += (t << r) | (t >>> (32 - r));
+    }
+    // round 4
+    for(; i < 64; ++i) {
+      f = c ^ (b | ~d);
+      t = (a + f + _k[i] + w[_g[i]]);
+      r = _r[i];
+      a = d;
+      d = c;
+      c = b;
+      b += (t << r) | (t >>> (32 - r));
+    }
+
+    // update hash state
+    s.h0 = (s.h0 + a) | 0;
+    s.h1 = (s.h1 + b) | 0;
+    s.h2 = (s.h2 + c) | 0;
+    s.h3 = (s.h3 + d) | 0;
+
+    len -= 64;
+  }
+}
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'md5';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/mgf.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/mgf.js
new file mode 100644
index 0000000..927082a
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/mgf.js
@@ -0,0 +1,67 @@
+/**
+ * Node.js module for Forge mask generation functions.
+ *
+ * @author Stefan Siegl
+ *
+ * Copyright 2012 Stefan Siegl <stesie@brokenpipe.de>
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+forge.mgf = forge.mgf || {};
+forge.mgf.mgf1 = forge.mgf1;
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'mgf';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './mgf1'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/mgf1.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/mgf1.js
new file mode 100644
index 0000000..82d62cd
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/mgf1.js
@@ -0,0 +1,112 @@
+/**
+ * Javascript implementation of mask generation function MGF1.
+ *
+ * @author Stefan Siegl
+ * @author Dave Longley
+ *
+ * Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
+ * Copyright (c) 2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+forge.mgf = forge.mgf || {};
+var mgf1 = forge.mgf.mgf1 = forge.mgf1 = forge.mgf1 || {};
+
+/**
+ * Creates a MGF1 mask generation function object.
+ *
+ * @param md the message digest API to use (eg: forge.md.sha1.create()).
+ *
+ * @return a mask generation function object.
+ */
+mgf1.create = function(md) {
+  var mgf = {
+    /**
+     * Generate mask of specified length.
+     *
+     * @param {String} seed The seed for mask generation.
+     * @param maskLen Number of bytes to generate.
+     * @return {String} The generated mask.
+     */
+    generate: function(seed, maskLen) {
+      /* 2. Let T be the empty octet string. */
+      var t = new forge.util.ByteBuffer();
+
+      /* 3. For counter from 0 to ceil(maskLen / hLen), do the following: */
+      var len = Math.ceil(maskLen / md.digestLength);
+      for(var i = 0; i < len; i++) {
+        /* a. Convert counter to an octet string C of length 4 octets */
+        var c = new forge.util.ByteBuffer();
+        c.putInt32(i);
+
+        /* b. Concatenate the hash of the seed mgfSeed and C to the octet
+         * string T: */
+        md.start();
+        md.update(seed + c.getBytes());
+        t.putBuffer(md.digest());
+      }
+
+      /* Output the leading maskLen octets of T as the octet string mask. */
+      t.truncate(t.length() - maskLen);
+      return t.getBytes();
+    }
+  };
+
+  return mgf;
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'mgf1';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/oids.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/oids.js
new file mode 100644
index 0000000..ef3e67d
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/oids.js
@@ -0,0 +1,269 @@
+/**
+ * Object IDs for ASN.1.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2013 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+forge.pki = forge.pki || {};
+var oids = forge.pki.oids = forge.oids = forge.oids || {};
+
+// algorithm OIDs
+oids['1.2.840.113549.1.1.1'] = 'rsaEncryption';
+oids['rsaEncryption'] = '1.2.840.113549.1.1.1';
+// Note: md2 & md4 not implemented
+//oids['1.2.840.113549.1.1.2'] = 'md2WithRSAEncryption';
+//oids['md2WithRSAEncryption'] = '1.2.840.113549.1.1.2';
+//oids['1.2.840.113549.1.1.3'] = 'md4WithRSAEncryption';
+//oids['md4WithRSAEncryption'] = '1.2.840.113549.1.1.3';
+oids['1.2.840.113549.1.1.4'] = 'md5WithRSAEncryption';
+oids['md5WithRSAEncryption'] = '1.2.840.113549.1.1.4';
+oids['1.2.840.113549.1.1.5'] = 'sha1WithRSAEncryption';
+oids['sha1WithRSAEncryption'] = '1.2.840.113549.1.1.5';
+oids['1.2.840.113549.1.1.7'] = 'RSAES-OAEP';
+oids['RSAES-OAEP'] = '1.2.840.113549.1.1.7';
+oids['1.2.840.113549.1.1.8'] = 'mgf1';
+oids['mgf1'] = '1.2.840.113549.1.1.8';
+oids['1.2.840.113549.1.1.9'] = 'pSpecified';
+oids['pSpecified'] = '1.2.840.113549.1.1.9';
+oids['1.2.840.113549.1.1.10'] = 'RSASSA-PSS';
+oids['RSASSA-PSS'] = '1.2.840.113549.1.1.10';
+oids['1.2.840.113549.1.1.11'] = 'sha256WithRSAEncryption';
+oids['sha256WithRSAEncryption'] = '1.2.840.113549.1.1.11';
+oids['1.2.840.113549.1.1.12'] = 'sha384WithRSAEncryption';
+oids['sha384WithRSAEncryption'] = '1.2.840.113549.1.1.12';
+oids['1.2.840.113549.1.1.13'] = 'sha512WithRSAEncryption';
+oids['sha512WithRSAEncryption'] = '1.2.840.113549.1.1.13';
+
+oids['1.3.14.3.2.7'] = 'desCBC';
+oids['desCBC'] = '1.3.14.3.2.7';
+
+oids['1.3.14.3.2.26'] = 'sha1';
+oids['sha1'] = '1.3.14.3.2.26';
+oids['2.16.840.1.101.3.4.2.1'] = 'sha256';
+oids['sha256'] = '2.16.840.1.101.3.4.2.1';
+oids['2.16.840.1.101.3.4.2.2'] = 'sha384';
+oids['sha384'] = '2.16.840.1.101.3.4.2.2';
+oids['2.16.840.1.101.3.4.2.3'] = 'sha512';
+oids['sha512'] = '2.16.840.1.101.3.4.2.3';
+oids['1.2.840.113549.2.5'] = 'md5';
+oids['md5'] = '1.2.840.113549.2.5';
+
+// pkcs#7 content types
+oids['1.2.840.113549.1.7.1'] = 'data';
+oids['data'] = '1.2.840.113549.1.7.1';
+oids['1.2.840.113549.1.7.2'] = 'signedData';
+oids['signedData'] = '1.2.840.113549.1.7.2';
+oids['1.2.840.113549.1.7.3'] = 'envelopedData';
+oids['envelopedData'] = '1.2.840.113549.1.7.3';
+oids['1.2.840.113549.1.7.4'] = 'signedAndEnvelopedData';
+oids['signedAndEnvelopedData'] = '1.2.840.113549.1.7.4';
+oids['1.2.840.113549.1.7.5'] = 'digestedData';
+oids['digestedData'] = '1.2.840.113549.1.7.5';
+oids['1.2.840.113549.1.7.6'] = 'encryptedData';
+oids['encryptedData'] = '1.2.840.113549.1.7.6';
+
+// pkcs#9 oids
+oids['1.2.840.113549.1.9.1'] = 'emailAddress';
+oids['emailAddress'] = '1.2.840.113549.1.9.1';
+oids['1.2.840.113549.1.9.2'] = 'unstructuredName';
+oids['unstructuredName'] = '1.2.840.113549.1.9.2';
+oids['1.2.840.113549.1.9.3'] = 'contentType';
+oids['contentType'] = '1.2.840.113549.1.9.3';
+oids['1.2.840.113549.1.9.4'] = 'messageDigest';
+oids['messageDigest'] = '1.2.840.113549.1.9.4';
+oids['1.2.840.113549.1.9.5'] = 'signingTime';
+oids['signingTime'] = '1.2.840.113549.1.9.5';
+oids['1.2.840.113549.1.9.6'] = 'counterSignature';
+oids['counterSignature'] = '1.2.840.113549.1.9.6';
+oids['1.2.840.113549.1.9.7'] = 'challengePassword';
+oids['challengePassword'] = '1.2.840.113549.1.9.7';
+oids['1.2.840.113549.1.9.8'] = 'unstructuredAddress';
+oids['unstructuredAddress'] = '1.2.840.113549.1.9.8';
+oids['1.2.840.113549.1.9.14'] = 'extensionRequest';
+oids['extensionRequest'] = '1.2.840.113549.1.9.14';
+
+oids['1.2.840.113549.1.9.20'] = 'friendlyName';
+oids['friendlyName'] = '1.2.840.113549.1.9.20';
+oids['1.2.840.113549.1.9.21'] = 'localKeyId';
+oids['localKeyId'] = '1.2.840.113549.1.9.21';
+oids['1.2.840.113549.1.9.22.1'] = 'x509Certificate';
+oids['x509Certificate'] = '1.2.840.113549.1.9.22.1';
+
+// pkcs#12 safe bags
+oids['1.2.840.113549.1.12.10.1.1'] = 'keyBag';
+oids['keyBag'] = '1.2.840.113549.1.12.10.1.1';
+oids['1.2.840.113549.1.12.10.1.2'] = 'pkcs8ShroudedKeyBag';
+oids['pkcs8ShroudedKeyBag'] = '1.2.840.113549.1.12.10.1.2';
+oids['1.2.840.113549.1.12.10.1.3'] = 'certBag';
+oids['certBag'] = '1.2.840.113549.1.12.10.1.3';
+oids['1.2.840.113549.1.12.10.1.4'] = 'crlBag';
+oids['crlBag'] = '1.2.840.113549.1.12.10.1.4';
+oids['1.2.840.113549.1.12.10.1.5'] = 'secretBag';
+oids['secretBag'] = '1.2.840.113549.1.12.10.1.5';
+oids['1.2.840.113549.1.12.10.1.6'] = 'safeContentsBag';
+oids['safeContentsBag'] = '1.2.840.113549.1.12.10.1.6';
+
+// password-based-encryption for pkcs#12
+oids['1.2.840.113549.1.5.13'] = 'pkcs5PBES2';
+oids['pkcs5PBES2'] = '1.2.840.113549.1.5.13';
+oids['1.2.840.113549.1.5.12'] = 'pkcs5PBKDF2';
+oids['pkcs5PBKDF2'] = '1.2.840.113549.1.5.12';
+
+oids['1.2.840.113549.1.12.1.1'] = 'pbeWithSHAAnd128BitRC4';
+oids['pbeWithSHAAnd128BitRC4'] = '1.2.840.113549.1.12.1.1';
+oids['1.2.840.113549.1.12.1.2'] = 'pbeWithSHAAnd40BitRC4';
+oids['pbeWithSHAAnd40BitRC4'] = '1.2.840.113549.1.12.1.2';
+oids['1.2.840.113549.1.12.1.3'] = 'pbeWithSHAAnd3-KeyTripleDES-CBC';
+oids['pbeWithSHAAnd3-KeyTripleDES-CBC'] = '1.2.840.113549.1.12.1.3';
+oids['1.2.840.113549.1.12.1.4'] = 'pbeWithSHAAnd2-KeyTripleDES-CBC';
+oids['pbeWithSHAAnd2-KeyTripleDES-CBC'] = '1.2.840.113549.1.12.1.4';
+oids['1.2.840.113549.1.12.1.5'] = 'pbeWithSHAAnd128BitRC2-CBC';
+oids['pbeWithSHAAnd128BitRC2-CBC'] = '1.2.840.113549.1.12.1.5';
+oids['1.2.840.113549.1.12.1.6'] = 'pbewithSHAAnd40BitRC2-CBC';
+oids['pbewithSHAAnd40BitRC2-CBC'] = '1.2.840.113549.1.12.1.6';
+
+// symmetric key algorithm oids
+oids['1.2.840.113549.3.7'] = 'des-EDE3-CBC';
+oids['des-EDE3-CBC'] = '1.2.840.113549.3.7';
+oids['2.16.840.1.101.3.4.1.2'] = 'aes128-CBC';
+oids['aes128-CBC'] = '2.16.840.1.101.3.4.1.2';
+oids['2.16.840.1.101.3.4.1.22'] = 'aes192-CBC';
+oids['aes192-CBC'] = '2.16.840.1.101.3.4.1.22';
+oids['2.16.840.1.101.3.4.1.42'] = 'aes256-CBC';
+oids['aes256-CBC'] = '2.16.840.1.101.3.4.1.42';
+
+// certificate issuer/subject OIDs
+oids['2.5.4.3'] = 'commonName';
+oids['commonName'] = '2.5.4.3';
+oids['2.5.4.5'] = 'serialName';
+oids['serialName'] = '2.5.4.5';
+oids['2.5.4.6'] = 'countryName';
+oids['countryName'] = '2.5.4.6';
+oids['2.5.4.7'] = 'localityName';
+oids['localityName'] = '2.5.4.7';
+oids['2.5.4.8'] = 'stateOrProvinceName';
+oids['stateOrProvinceName'] = '2.5.4.8';
+oids['2.5.4.10'] = 'organizationName';
+oids['organizationName'] = '2.5.4.10';
+oids['2.5.4.11'] = 'organizationalUnitName';
+oids['organizationalUnitName'] = '2.5.4.11';
+
+// X.509 extension OIDs
+oids['2.16.840.1.113730.1.1'] = 'nsCertType';
+oids['nsCertType'] = '2.16.840.1.113730.1.1';
+oids['2.5.29.1'] = 'authorityKeyIdentifier'; // deprecated, use .35
+oids['2.5.29.2'] = 'keyAttributes'; // obsolete use .37 or .15
+oids['2.5.29.3'] = 'certificatePolicies'; // deprecated, use .32
+oids['2.5.29.4'] = 'keyUsageRestriction'; // obsolete use .37 or .15
+oids['2.5.29.5'] = 'policyMapping'; // deprecated use .33
+oids['2.5.29.6'] = 'subtreesConstraint'; // obsolete use .30
+oids['2.5.29.7'] = 'subjectAltName'; // deprecated use .17
+oids['2.5.29.8'] = 'issuerAltName'; // deprecated use .18
+oids['2.5.29.9'] = 'subjectDirectoryAttributes';
+oids['2.5.29.10'] = 'basicConstraints'; // deprecated use .19
+oids['2.5.29.11'] = 'nameConstraints'; // deprecated use .30
+oids['2.5.29.12'] = 'policyConstraints'; // deprecated use .36
+oids['2.5.29.13'] = 'basicConstraints'; // deprecated use .19
+oids['2.5.29.14'] = 'subjectKeyIdentifier';
+oids['subjectKeyIdentifier'] = '2.5.29.14';
+oids['2.5.29.15'] = 'keyUsage';
+oids['keyUsage'] = '2.5.29.15';
+oids['2.5.29.16'] = 'privateKeyUsagePeriod';
+oids['2.5.29.17'] = 'subjectAltName';
+oids['subjectAltName'] = '2.5.29.17';
+oids['2.5.29.18'] = 'issuerAltName';
+oids['issuerAltName'] = '2.5.29.18';
+oids['2.5.29.19'] = 'basicConstraints';
+oids['basicConstraints'] = '2.5.29.19';
+oids['2.5.29.20'] = 'cRLNumber';
+oids['2.5.29.21'] = 'cRLReason';
+oids['2.5.29.22'] = 'expirationDate';
+oids['2.5.29.23'] = 'instructionCode';
+oids['2.5.29.24'] = 'invalidityDate';
+oids['2.5.29.25'] = 'cRLDistributionPoints'; // deprecated use .31
+oids['2.5.29.26'] = 'issuingDistributionPoint'; // deprecated use .28
+oids['2.5.29.27'] = 'deltaCRLIndicator';
+oids['2.5.29.28'] = 'issuingDistributionPoint';
+oids['2.5.29.29'] = 'certificateIssuer';
+oids['2.5.29.30'] = 'nameConstraints';
+oids['2.5.29.31'] = 'cRLDistributionPoints';
+oids['2.5.29.32'] = 'certificatePolicies';
+oids['2.5.29.33'] = 'policyMappings';
+oids['2.5.29.34'] = 'policyConstraints'; // deprecated use .36
+oids['2.5.29.35'] = 'authorityKeyIdentifier';
+oids['2.5.29.36'] = 'policyConstraints';
+oids['2.5.29.37'] = 'extKeyUsage';
+oids['extKeyUsage'] = '2.5.29.37';
+oids['2.5.29.46'] = 'freshestCRL';
+oids['2.5.29.54'] = 'inhibitAnyPolicy';
+
+// extKeyUsage purposes
+oids['1.3.6.1.5.5.7.3.1'] = 'serverAuth';
+oids['serverAuth'] = '1.3.6.1.5.5.7.3.1';
+oids['1.3.6.1.5.5.7.3.2'] = 'clientAuth';
+oids['clientAuth'] = '1.3.6.1.5.5.7.3.2';
+oids['1.3.6.1.5.5.7.3.3'] = 'codeSigning';
+oids['codeSigning'] = '1.3.6.1.5.5.7.3.3';
+oids['1.3.6.1.5.5.7.3.4'] = 'emailProtection';
+oids['emailProtection'] = '1.3.6.1.5.5.7.3.4';
+oids['1.3.6.1.5.5.7.3.8'] = 'timeStamping';
+oids['timeStamping'] = '1.3.6.1.5.5.7.3.8';
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'oids';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/pbe.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/pbe.js
new file mode 100644
index 0000000..0b25758
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/pbe.js
@@ -0,0 +1,975 @@
+/**
+ * Password-based encryption functions.
+ *
+ * @author Dave Longley
+ * @author Stefan Siegl <stesie@brokenpipe.de>
+ *
+ * Copyright (c) 2010-2013 Digital Bazaar, Inc.
+ * Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
+ *
+ * An EncryptedPrivateKeyInfo:
+ *
+ * EncryptedPrivateKeyInfo ::= SEQUENCE {
+ *   encryptionAlgorithm  EncryptionAlgorithmIdentifier,
+ *   encryptedData        EncryptedData }
+ *
+ * EncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
+ *
+ * EncryptedData ::= OCTET STRING
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+if(typeof BigInteger === 'undefined') {
+  var BigInteger = forge.jsbn.BigInteger;
+}
+
+// shortcut for asn.1 API
+var asn1 = forge.asn1;
+
+/* Password-based encryption implementation. */
+var pki = forge.pki = forge.pki || {};
+pki.pbe = forge.pbe = forge.pbe || {};
+var oids = pki.oids;
+
+// validator for an EncryptedPrivateKeyInfo structure
+// Note: Currently only works w/algorithm params
+var encryptedPrivateKeyValidator = {
+  name: 'EncryptedPrivateKeyInfo',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'EncryptedPrivateKeyInfo.encryptionAlgorithm',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true,
+    value: [{
+      name: 'AlgorithmIdentifier.algorithm',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.OID,
+      constructed: false,
+      capture: 'encryptionOid'
+    }, {
+      name: 'AlgorithmIdentifier.parameters',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.SEQUENCE,
+      constructed: true,
+      captureAsn1: 'encryptionParams'
+    }]
+  }, {
+    // encryptedData
+    name: 'EncryptedPrivateKeyInfo.encryptedData',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.OCTETSTRING,
+    constructed: false,
+    capture: 'encryptedData'
+  }]
+};
+
+// validator for a PBES2Algorithms structure
+// Note: Currently only works w/PBKDF2 + AES encryption schemes
+var PBES2AlgorithmsValidator = {
+  name: 'PBES2Algorithms',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'PBES2Algorithms.keyDerivationFunc',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true,
+    value: [{
+      name: 'PBES2Algorithms.keyDerivationFunc.oid',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.OID,
+      constructed: false,
+      capture: 'kdfOid'
+    }, {
+      name: 'PBES2Algorithms.params',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.SEQUENCE,
+      constructed: true,
+      value: [{
+        name: 'PBES2Algorithms.params.salt',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.OCTETSTRING,
+        constructed: false,
+        capture: 'kdfSalt'
+      }, {
+        name: 'PBES2Algorithms.params.iterationCount',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.INTEGER,
+        onstructed: true,
+        capture: 'kdfIterationCount'
+      }]
+    }]
+  }, {
+    name: 'PBES2Algorithms.encryptionScheme',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true,
+    value: [{
+      name: 'PBES2Algorithms.encryptionScheme.oid',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.OID,
+      constructed: false,
+      capture: 'encOid'
+    }, {
+      name: 'PBES2Algorithms.encryptionScheme.iv',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.OCTETSTRING,
+      constructed: false,
+      capture: 'encIv'
+    }]
+  }]
+};
+
+var pkcs12PbeParamsValidator = {
+  name: 'pkcs-12PbeParams',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'pkcs-12PbeParams.salt',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.OCTETSTRING,
+    constructed: false,
+    capture: 'salt'
+  }, {
+    name: 'pkcs-12PbeParams.iterations',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'iterations'
+  }]
+};
+
+/**
+ * Encrypts a ASN.1 PrivateKeyInfo object, producing an EncryptedPrivateKeyInfo.
+ *
+ * PBES2Algorithms ALGORITHM-IDENTIFIER ::=
+ *   { {PBES2-params IDENTIFIED BY id-PBES2}, ...}
+ *
+ * id-PBES2 OBJECT IDENTIFIER ::= {pkcs-5 13}
+ *
+ * PBES2-params ::= SEQUENCE {
+ *   keyDerivationFunc AlgorithmIdentifier {{PBES2-KDFs}},
+ *   encryptionScheme AlgorithmIdentifier {{PBES2-Encs}}
+ * }
+ *
+ * PBES2-KDFs ALGORITHM-IDENTIFIER ::=
+ *   { {PBKDF2-params IDENTIFIED BY id-PBKDF2}, ... }
+ *
+ * PBES2-Encs ALGORITHM-IDENTIFIER ::= { ... }
+ *
+ * PBKDF2-params ::= SEQUENCE {
+ *   salt CHOICE {
+ *     specified OCTET STRING,
+ *     otherSource AlgorithmIdentifier {{PBKDF2-SaltSources}}
+ *   },
+ *   iterationCount INTEGER (1..MAX),
+ *   keyLength INTEGER (1..MAX) OPTIONAL,
+ *   prf AlgorithmIdentifier {{PBKDF2-PRFs}} DEFAULT algid-hmacWithSHA1
+ * }
+ *
+ * @param obj the ASN.1 PrivateKeyInfo object.
+ * @param password the password to encrypt with.
+ * @param options:
+ *          algorithm the encryption algorithm to use
+ *            ('aes128', 'aes192', 'aes256', '3des'), defaults to 'aes128'.
+ *          count the iteration count to use.
+ *          saltSize the salt size to use.
+ *
+ * @return the ASN.1 EncryptedPrivateKeyInfo.
+ */
+pki.encryptPrivateKeyInfo = function(obj, password, options) {
+  // set default options
+  options = options || {};
+  options.saltSize = options.saltSize || 8;
+  options.count = options.count || 2048;
+  options.algorithm = options.algorithm || 'aes128';
+
+  // generate PBE params
+  var salt = forge.random.getBytesSync(options.saltSize);
+  var count = options.count;
+  var countBytes = asn1.integerToDer(count);
+  var dkLen;
+  var encryptionAlgorithm;
+  var encryptedData;
+  if(options.algorithm.indexOf('aes') === 0 || options.algorithm === 'des') {
+    // Do PBES2
+    var ivLen, encOid, cipherFn;
+    switch(options.algorithm) {
+    case 'aes128':
+      dkLen = 16;
+      ivLen = 16;
+      encOid = oids['aes128-CBC'];
+      cipherFn = forge.aes.createEncryptionCipher;
+      break;
+    case 'aes192':
+      dkLen = 24;
+      ivLen = 16;
+      encOid = oids['aes192-CBC'];
+      cipherFn = forge.aes.createEncryptionCipher;
+      break;
+    case 'aes256':
+      dkLen = 32;
+      ivLen = 16;
+      encOid = oids['aes256-CBC'];
+      cipherFn = forge.aes.createEncryptionCipher;
+      break;
+    case 'des':
+      dkLen = 8;
+      ivLen = 8;
+      encOid = oids['desCBC'];
+      cipherFn = forge.des.createEncryptionCipher;
+      break;
+    default:
+      var error = new Error('Cannot encrypt private key. Unknown encryption algorithm.');
+      error.algorithm = options.algorithm;
+      throw error;
+    }
+
+    // encrypt private key using pbe SHA-1 and AES/DES
+    var dk = forge.pkcs5.pbkdf2(password, salt, count, dkLen);
+    var iv = forge.random.getBytesSync(ivLen);
+    var cipher = cipherFn(dk);
+    cipher.start(iv);
+    cipher.update(asn1.toDer(obj));
+    cipher.finish();
+    encryptedData = cipher.output.getBytes();
+
+    encryptionAlgorithm = asn1.create(
+      asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+        asn1.oidToDer(oids['pkcs5PBES2']).getBytes()),
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+        // keyDerivationFunc
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+            asn1.oidToDer(oids['pkcs5PBKDF2']).getBytes()),
+          // PBKDF2-params
+          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+            // salt
+            asn1.create(
+              asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, salt),
+            // iteration count
+            asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+              countBytes.getBytes())
+          ])
+        ]),
+        // encryptionScheme
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+            asn1.oidToDer(encOid).getBytes()),
+          // iv
+          asn1.create(
+            asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, iv)
+        ])
+      ])
+    ]);
+  } else if(options.algorithm === '3des') {
+    // Do PKCS12 PBE
+    dkLen = 24;
+
+    var saltBytes = new forge.util.ByteBuffer(salt);
+    var dk = pki.pbe.generatePkcs12Key(password, saltBytes, 1, count, dkLen);
+    var iv = pki.pbe.generatePkcs12Key(password, saltBytes, 2, count, dkLen);
+    var cipher = forge.des.createEncryptionCipher(dk);
+    cipher.start(iv);
+    cipher.update(asn1.toDer(obj));
+    cipher.finish();
+    encryptedData = cipher.output.getBytes();
+
+    encryptionAlgorithm = asn1.create(
+      asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+        asn1.oidToDer(oids['pbeWithSHAAnd3-KeyTripleDES-CBC']).getBytes()),
+      // pkcs-12PbeParams
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+        // salt
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, salt),
+        // iteration count
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+          countBytes.getBytes())
+      ])
+    ]);
+  } else {
+    var error = new Error('Cannot encrypt private key. Unknown encryption algorithm.');
+    error.algorithm = options.algorithm;
+    throw error;
+  }
+
+  // EncryptedPrivateKeyInfo
+  var rval = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+    // encryptionAlgorithm
+    encryptionAlgorithm,
+    // encryptedData
+    asn1.create(
+      asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, encryptedData)
+  ]);
+  return rval;
+};
+
+/**
+ * Decrypts a ASN.1 PrivateKeyInfo object.
+ *
+ * @param obj the ASN.1 EncryptedPrivateKeyInfo object.
+ * @param password the password to decrypt with.
+ *
+ * @return the ASN.1 PrivateKeyInfo on success, null on failure.
+ */
+pki.decryptPrivateKeyInfo = function(obj, password) {
+  var rval = null;
+
+  // get PBE params
+  var capture = {};
+  var errors = [];
+  if(!asn1.validate(obj, encryptedPrivateKeyValidator, capture, errors)) {
+    var error = new Error('Cannot read encrypted private key. ' +
+      'ASN.1 object is not a supported EncryptedPrivateKeyInfo.');
+    error.errors = errors;
+    throw error;
+  }
+
+  // get cipher
+  var oid = asn1.derToOid(capture.encryptionOid);
+  var cipher = pki.pbe.getCipher(oid, capture.encryptionParams, password);
+
+  // get encrypted data
+  var encrypted = forge.util.createBuffer(capture.encryptedData);
+
+  cipher.update(encrypted);
+  if(cipher.finish()) {
+    rval = asn1.fromDer(cipher.output);
+  }
+
+  return rval;
+};
+
+/**
+ * Converts a EncryptedPrivateKeyInfo to PEM format.
+ *
+ * @param epki the EncryptedPrivateKeyInfo.
+ * @param maxline the maximum characters per line, defaults to 64.
+ *
+ * @return the PEM-formatted encrypted private key.
+ */
+pki.encryptedPrivateKeyToPem = function(epki, maxline) {
+  // convert to DER, then PEM-encode
+  var msg = {
+    type: 'ENCRYPTED PRIVATE KEY',
+    body: asn1.toDer(epki).getBytes()
+  };
+  return forge.pem.encode(msg, {maxline: maxline});
+};
+
+/**
+ * Converts a PEM-encoded EncryptedPrivateKeyInfo to ASN.1 format. Decryption
+ * is not performed.
+ *
+ * @param pem the EncryptedPrivateKeyInfo in PEM-format.
+ *
+ * @return the ASN.1 EncryptedPrivateKeyInfo.
+ */
+pki.encryptedPrivateKeyFromPem = function(pem) {
+  var msg = forge.pem.decode(pem)[0];
+
+  if(msg.type !== 'ENCRYPTED PRIVATE KEY') {
+    var error = new Error('Could not convert encrypted private key from PEM; ' +
+      'PEM header type is "ENCRYPTED PRIVATE KEY".');
+    error.headerType = msg.type;
+    throw error;
+  }
+  if(msg.procType && msg.procType.type === 'ENCRYPTED') {
+    throw new Error('Could not convert encrypted private key from PEM; ' +
+      'PEM is encrypted.');
+  }
+
+  // convert DER to ASN.1 object
+  return asn1.fromDer(msg.body);
+};
+
+/**
+ * Encrypts an RSA private key. By default, the key will be wrapped in
+ * a PrivateKeyInfo and encrypted to produce a PKCS#8 EncryptedPrivateKeyInfo.
+ * This is the standard, preferred way to encrypt a private key.
+ *
+ * To produce a non-standard PEM-encrypted private key that uses encapsulated
+ * headers to indicate the encryption algorithm (old-style non-PKCS#8 OpenSSL
+ * private key encryption), set the 'legacy' option to true. Note: Using this
+ * option will cause the iteration count to be forced to 1.
+ *
+ * Note: The 'des' algorithm is supported, but it is not considered to be
+ * secure because it only uses a single 56-bit key. If possible, it is highly
+ * recommended that a different algorithm be used.
+ *
+ * @param rsaKey the RSA key to encrypt.
+ * @param password the password to use.
+ * @param options:
+ *          algorithm: the encryption algorithm to use
+ *            ('aes128', 'aes192', 'aes256', '3des', 'des').
+ *          count: the iteration count to use.
+ *          saltSize: the salt size to use.
+ *          legacy: output an old non-PKCS#8 PEM-encrypted+encapsulated
+ *            headers (DEK-Info) private key.
+ *
+ * @return the PEM-encoded ASN.1 EncryptedPrivateKeyInfo.
+ */
+pki.encryptRsaPrivateKey = function(rsaKey, password, options) {
+  // standard PKCS#8
+  options = options || {};
+  if(!options.legacy) {
+    // encrypt PrivateKeyInfo
+    var rval = pki.wrapRsaPrivateKey(pki.privateKeyToAsn1(rsaKey));
+    rval = pki.encryptPrivateKeyInfo(rval, password, options);
+    return pki.encryptedPrivateKeyToPem(rval);
+  }
+
+  // legacy non-PKCS#8
+  var algorithm;
+  var iv;
+  var dkLen;
+  var cipherFn;
+  switch(options.algorithm) {
+  case 'aes128':
+    algorithm = 'AES-128-CBC';
+    dkLen = 16;
+    iv = forge.random.getBytesSync(16);
+    cipherFn = forge.aes.createEncryptionCipher;
+    break;
+  case 'aes192':
+    algorithm = 'AES-192-CBC';
+    dkLen = 24;
+    iv = forge.random.getBytesSync(16);
+    cipherFn = forge.aes.createEncryptionCipher;
+    break;
+  case 'aes256':
+    algorithm = 'AES-256-CBC';
+    dkLen = 32;
+    iv = forge.random.getBytesSync(16);
+    cipherFn = forge.aes.createEncryptionCipher;
+    break;
+  case '3des':
+    algorithm = 'DES-EDE3-CBC';
+    dkLen = 24;
+    iv = forge.random.getBytesSync(8);
+    cipherFn = forge.des.createEncryptionCipher;
+    break;
+  case 'des':
+    algorithm = 'DES-CBC';
+    dkLen = 8;
+    iv = forge.random.getBytesSync(8);
+    cipherFn = forge.des.createEncryptionCipher;
+    break;
+  default:
+    var error = new Error('Could not encrypt RSA private key; unsupported ' +
+      'encryption algorithm "' + options.algorithm + '".');
+    error.algorithm = options.algorithm;
+    throw error;
+  }
+
+  // encrypt private key using OpenSSL legacy key derivation
+  var dk = forge.pbe.opensslDeriveBytes(password, iv.substr(0, 8), dkLen);
+  var cipher = cipherFn(dk);
+  cipher.start(iv);
+  cipher.update(asn1.toDer(pki.privateKeyToAsn1(rsaKey)));
+  cipher.finish();
+
+  var msg = {
+    type: 'RSA PRIVATE KEY',
+    procType: {
+      version: '4',
+      type: 'ENCRYPTED'
+    },
+    dekInfo: {
+      algorithm: algorithm,
+      parameters: forge.util.bytesToHex(iv).toUpperCase()
+    },
+    body: cipher.output.getBytes()
+  };
+  return forge.pem.encode(msg);
+};
+
+/**
+ * Decrypts an RSA private key.
+ *
+ * @param pem the PEM-formatted EncryptedPrivateKeyInfo to decrypt.
+ * @param password the password to use.
+ *
+ * @return the RSA key on success, null on failure.
+ */
+pki.decryptRsaPrivateKey = function(pem, password) {
+  var rval = null;
+
+  var msg = forge.pem.decode(pem)[0];
+
+  if(msg.type !== 'ENCRYPTED PRIVATE KEY' &&
+    msg.type !== 'PRIVATE KEY' &&
+    msg.type !== 'RSA PRIVATE KEY') {
+    var error = new Error('Could not convert private key from PEM; PEM header type ' +
+      'is not "ENCRYPTED PRIVATE KEY", "PRIVATE KEY", or "RSA PRIVATE KEY".');
+    error.headerType = error;
+    throw error;
+  }
+
+  if(msg.procType && msg.procType.type === 'ENCRYPTED') {
+    var dkLen;
+    var cipherFn;
+    switch(msg.dekInfo.algorithm) {
+    case 'DES-CBC':
+      dkLen = 8;
+      cipherFn = forge.des.createDecryptionCipher;
+      break;
+    case 'DES-EDE3-CBC':
+      dkLen = 24;
+      cipherFn = forge.des.createDecryptionCipher;
+      break;
+    case 'AES-128-CBC':
+      dkLen = 16;
+      cipherFn = forge.aes.createDecryptionCipher;
+      break;
+    case 'AES-192-CBC':
+      dkLen = 24;
+      cipherFn = forge.aes.createDecryptionCipher;
+      break;
+    case 'AES-256-CBC':
+      dkLen = 32;
+      cipherFn = forge.aes.createDecryptionCipher;
+      break;
+    case 'RC2-40-CBC':
+      dkLen = 5;
+      cipherFn = function(key) {
+        return forge.rc2.createDecryptionCipher(key, 40);
+      };
+      break;
+    case 'RC2-64-CBC':
+      dkLen = 8;
+      cipherFn = function(key) {
+        return forge.rc2.createDecryptionCipher(key, 64);
+      };
+      break;
+    case 'RC2-128-CBC':
+      dkLen = 16;
+      cipherFn = function(key) {
+        return forge.rc2.createDecryptionCipher(key, 128);
+      };
+      break;
+    default:
+      var error = new Error('Could not decrypt private key; unsupported ' +
+        'encryption algorithm "' + msg.dekInfo.algorithm + '".');
+      error.algorithm = msg.dekInfo.algorithm;
+      throw error;
+    }
+
+    // use OpenSSL legacy key derivation
+    var iv = forge.util.hexToBytes(msg.dekInfo.parameters);
+    var dk = forge.pbe.opensslDeriveBytes(password, iv.substr(0, 8), dkLen);
+    var cipher = cipherFn(dk);
+    cipher.start(iv);
+    cipher.update(forge.util.createBuffer(msg.body));
+    if(cipher.finish()) {
+      rval = cipher.output.getBytes();
+    } else {
+      return rval;
+    }
+  } else {
+    rval = msg.body;
+  }
+
+  if(msg.type === 'ENCRYPTED PRIVATE KEY') {
+    rval = pki.decryptPrivateKeyInfo(asn1.fromDer(rval), password);
+  } else {
+    // decryption already performed above
+    rval = asn1.fromDer(rval);
+  }
+
+  if(rval !== null) {
+    rval = pki.privateKeyFromAsn1(rval);
+  }
+
+  return rval;
+};
+
+/**
+ * Derives a PKCS#12 key.
+ *
+ * @param password the password to derive the key material from, null or
+ *          undefined for none.
+ * @param salt the salt, as a ByteBuffer, to use.
+ * @param id the PKCS#12 ID byte (1 = key material, 2 = IV, 3 = MAC).
+ * @param iter the iteration count.
+ * @param n the number of bytes to derive from the password.
+ * @param md the message digest to use, defaults to SHA-1.
+ *
+ * @return a ByteBuffer with the bytes derived from the password.
+ */
+pki.pbe.generatePkcs12Key = function(password, salt, id, iter, n, md) {
+  var j, l;
+
+  if(typeof md === 'undefined' || md === null) {
+    md = forge.md.sha1.create();
+  }
+
+  var u = md.digestLength;
+  var v = md.blockLength;
+  var result = new forge.util.ByteBuffer();
+
+  /* Convert password to Unicode byte buffer + trailing 0-byte. */
+  var passBuf = new forge.util.ByteBuffer();
+  if(password !== null && password !== undefined) {
+    for(l = 0; l < password.length; l++) {
+      passBuf.putInt16(password.charCodeAt(l));
+    }
+    passBuf.putInt16(0);
+  }
+
+  /* Length of salt and password in BYTES. */
+  var p = passBuf.length();
+  var s = salt.length();
+
+  /* 1. Construct a string, D (the "diversifier"), by concatenating
+        v copies of ID. */
+  var D = new forge.util.ByteBuffer();
+  D.fillWithByte(id, v);
+
+  /* 2. Concatenate copies of the salt together to create a string S of length
+        v * ceil(s / v) bytes (the final copy of the salt may be trunacted
+        to create S).
+        Note that if the salt is the empty string, then so is S. */
+  var Slen = v * Math.ceil(s / v);
+  var S = new forge.util.ByteBuffer();
+  for(l = 0; l < Slen; l ++) {
+    S.putByte(salt.at(l % s));
+  }
+
+  /* 3. Concatenate copies of the password together to create a string P of
+        length v * ceil(p / v) bytes (the final copy of the password may be
+        truncated to create P).
+        Note that if the password is the empty string, then so is P. */
+  var Plen = v * Math.ceil(p / v);
+  var P = new forge.util.ByteBuffer();
+  for(l = 0; l < Plen; l ++) {
+    P.putByte(passBuf.at(l % p));
+  }
+
+  /* 4. Set I=S||P to be the concatenation of S and P. */
+  var I = S;
+  I.putBuffer(P);
+
+  /* 5. Set c=ceil(n / u). */
+  var c = Math.ceil(n / u);
+
+  /* 6. For i=1, 2, ..., c, do the following: */
+  for(var i = 1; i <= c; i ++) {
+    /* a) Set Ai=H^r(D||I). (l.e. the rth hash of D||I, H(H(H(...H(D||I)))) */
+    var buf = new forge.util.ByteBuffer();
+    buf.putBytes(D.bytes());
+    buf.putBytes(I.bytes());
+    for(var round = 0; round < iter; round ++) {
+      md.start();
+      md.update(buf.getBytes());
+      buf = md.digest();
+    }
+
+    /* b) Concatenate copies of Ai to create a string B of length v bytes (the
+          final copy of Ai may be truncated to create B). */
+    var B = new forge.util.ByteBuffer();
+    for(l = 0; l < v; l ++) {
+      B.putByte(buf.at(l % u));
+    }
+
+    /* c) Treating I as a concatenation I0, I1, ..., Ik-1 of v-byte blocks,
+          where k=ceil(s / v) + ceil(p / v), modify I by setting
+          Ij=(Ij+B+1) mod 2v for each j.  */
+    var k = Math.ceil(s / v) + Math.ceil(p / v);
+    var Inew = new forge.util.ByteBuffer();
+    for(j = 0; j < k; j ++) {
+      var chunk = new forge.util.ByteBuffer(I.getBytes(v));
+      var x = 0x1ff;
+      for(l = B.length() - 1; l >= 0; l --) {
+        x = x >> 8;
+        x += B.at(l) + chunk.at(l);
+        chunk.setAt(l, x & 0xff);
+      }
+      Inew.putBuffer(chunk);
+    }
+    I = Inew;
+
+    /* Add Ai to A. */
+    result.putBuffer(buf);
+  }
+
+  result.truncate(result.length() - n);
+  return result;
+};
+
+/**
+ * Get new Forge cipher object instance.
+ *
+ * @param oid the OID (in string notation).
+ * @param params the ASN.1 params object.
+ * @param password the password to decrypt with.
+ *
+ * @return new cipher object instance.
+ */
+pki.pbe.getCipher = function(oid, params, password) {
+  switch(oid) {
+  case pki.oids['pkcs5PBES2']:
+    return pki.pbe.getCipherForPBES2(oid, params, password);
+
+  case pki.oids['pbeWithSHAAnd3-KeyTripleDES-CBC']:
+  case pki.oids['pbewithSHAAnd40BitRC2-CBC']:
+    return pki.pbe.getCipherForPKCS12PBE(oid, params, password);
+
+  default:
+    var error = new Error('Cannot read encrypted PBE data block. Unsupported OID.');
+    error.oid = oid;
+    error.supportedOids = [
+      'pkcs5PBES2',
+      'pbeWithSHAAnd3-KeyTripleDES-CBC',
+      'pbewithSHAAnd40BitRC2-CBC'
+    ];
+    throw error;
+  }
+};
+
+/**
+ * Get new Forge cipher object instance according to PBES2 params block.
+ *
+ * The returned cipher instance is already started using the IV
+ * from PBES2 parameter block.
+ *
+ * @param oid the PKCS#5 PBKDF2 OID (in string notation).
+ * @param params the ASN.1 PBES2-params object.
+ * @param password the password to decrypt with.
+ *
+ * @return new cipher object instance.
+ */
+pki.pbe.getCipherForPBES2 = function(oid, params, password) {
+  // get PBE params
+  var capture = {};
+  var errors = [];
+  if(!asn1.validate(params, PBES2AlgorithmsValidator, capture, errors)) {
+    var error = new Error('Cannot read password-based-encryption algorithm ' +
+      'parameters. ASN.1 object is not a supported EncryptedPrivateKeyInfo.');
+    error.errors = errors;
+    throw error;
+  }
+
+  // check oids
+  oid = asn1.derToOid(capture.kdfOid);
+  if(oid !== pki.oids['pkcs5PBKDF2']) {
+    var error = new Error('Cannot read encrypted private key. ' +
+      'Unsupported key derivation function OID.');
+    error.oid = oid;
+    error.supportedOids = ['pkcs5PBKDF2'];
+    throw error;
+  }
+  oid = asn1.derToOid(capture.encOid);
+  if(oid !== pki.oids['aes128-CBC'] &&
+    oid !== pki.oids['aes192-CBC'] &&
+    oid !== pki.oids['aes256-CBC'] &&
+    oid !== pki.oids['des-EDE3-CBC'] &&
+    oid !== pki.oids['desCBC']) {
+    var error = new Error('Cannot read encrypted private key. ' +
+      'Unsupported encryption scheme OID.');
+    error.oid = oid;
+    error.supportedOids = [
+      'aes128-CBC', 'aes192-CBC', 'aes256-CBC', 'des-EDE3-CBC', 'desCBC'];
+    throw error;
+  }
+
+  // set PBE params
+  var salt = capture.kdfSalt;
+  var count = forge.util.createBuffer(capture.kdfIterationCount);
+  count = count.getInt(count.length() << 3);
+  var dkLen;
+  var cipherFn;
+  switch(pki.oids[oid]) {
+  case 'aes128-CBC':
+    dkLen = 16;
+    cipherFn = forge.aes.createDecryptionCipher;
+    break;
+  case 'aes192-CBC':
+    dkLen = 24;
+    cipherFn = forge.aes.createDecryptionCipher;
+    break;
+  case 'aes256-CBC':
+    dkLen = 32;
+    cipherFn = forge.aes.createDecryptionCipher;
+    break;
+  case 'des-EDE3-CBC':
+    dkLen = 24;
+    cipherFn = forge.des.createDecryptionCipher;
+    break;
+  case 'desCBC':
+    dkLen = 8;
+    cipherFn = forge.des.createDecryptionCipher;
+    break;
+  }
+
+  // decrypt private key using pbe SHA-1 and AES/DES
+  var dk = forge.pkcs5.pbkdf2(password, salt, count, dkLen);
+  var iv = capture.encIv;
+  var cipher = cipherFn(dk);
+  cipher.start(iv);
+
+  return cipher;
+};
+
+/**
+ * Get new Forge cipher object instance for PKCS#12 PBE.
+ *
+ * The returned cipher instance is already started using the key & IV
+ * derived from the provided password and PKCS#12 PBE salt.
+ *
+ * @param oid The PKCS#12 PBE OID (in string notation).
+ * @param params The ASN.1 PKCS#12 PBE-params object.
+ * @param password The password to decrypt with.
+ *
+ * @return the new cipher object instance.
+ */
+pki.pbe.getCipherForPKCS12PBE = function(oid, params, password) {
+  // get PBE params
+  var capture = {};
+  var errors = [];
+  if(!asn1.validate(params, pkcs12PbeParamsValidator, capture, errors)) {
+    var error = new Error('Cannot read password-based-encryption algorithm ' +
+      'parameters. ASN.1 object is not a supported EncryptedPrivateKeyInfo.');
+    error.errors = errors;
+    throw error;
+  }
+
+  var salt = forge.util.createBuffer(capture.salt);
+  var count = forge.util.createBuffer(capture.iterations);
+  count = count.getInt(count.length() << 3);
+
+  var dkLen, dIvLen, cipherFn;
+  switch(oid) {
+    case pki.oids['pbeWithSHAAnd3-KeyTripleDES-CBC']:
+      dkLen = 24;
+      dIvLen = 8;
+      cipherFn = forge.des.startDecrypting;
+      break;
+
+    case pki.oids['pbewithSHAAnd40BitRC2-CBC']:
+      dkLen = 5;
+      dIvLen = 8;
+      cipherFn = function(key, iv) {
+        var cipher = forge.rc2.createDecryptionCipher(key, 40);
+        cipher.start(iv, null);
+        return cipher;
+      };
+      break;
+
+    default:
+      var error = new Error('Cannot read PKCS #12 PBE data block. Unsupported OID.');
+      error.oid = oid;
+      throw error;
+  }
+
+  var key = pki.pbe.generatePkcs12Key(password, salt, 1, count, dkLen);
+  var iv = pki.pbe.generatePkcs12Key(password, salt, 2, count, dIvLen);
+
+  return cipherFn(key, iv);
+};
+
+/**
+ * OpenSSL's legacy key derivation function.
+ *
+ * See: http://www.openssl.org/docs/crypto/EVP_BytesToKey.html
+ *
+ * @param password the password to derive the key from.
+ * @param salt the salt to use, null for none.
+ * @param dkLen the number of bytes needed for the derived key.
+ * @param [options] the options to use:
+ *          [md] an optional message digest object to use.
+ */
+pki.pbe.opensslDeriveBytes = function(password, salt, dkLen, md) {
+  if(typeof md === 'undefined' || md === null) {
+    md = forge.md.md5.create();
+  }
+  if(salt === null) {
+    salt = '';
+  }
+  var digests = [hash(md, password + salt)];
+  for(var length = 16, i = 1; length < dkLen; ++i, length += 16) {
+    digests.push(hash(md, digests[i - 1] + password + salt));
+  }
+  return digests.join('').substr(0, dkLen);
+};
+
+function hash(md, bytes) {
+  return md.start().update(bytes).digest().getBytes();
+}
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'pbe';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define([
+  'require',
+  'module',
+  './aes',
+  './asn1',
+  './des',
+  './md',
+  './oids',
+  './pem',
+  './pbkdf2',
+  './random',
+  './rc2',
+  './rsa',
+  './util'
+], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/pbkdf2.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/pbkdf2.js
new file mode 100644
index 0000000..d983610
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/pbkdf2.js
@@ -0,0 +1,214 @@
+/**
+ * Password-Based Key-Derivation Function #2 implementation.
+ *
+ * See RFC 2898 for details.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2013 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+var pkcs5 = forge.pkcs5 = forge.pkcs5 || {};
+
+/**
+ * Derives a key from a password.
+ *
+ * @param p the password as a string of bytes.
+ * @param s the salt as a string of bytes.
+ * @param c the iteration count, a positive integer.
+ * @param dkLen the intended length, in bytes, of the derived key,
+ *          (max: 2^32 - 1) * hash length of the PRF.
+ * @param md the message digest to use in the PRF, defaults to SHA-1.
+ *
+ * @return the derived key, as a string of bytes.
+ */
+forge.pbkdf2 = pkcs5.pbkdf2 = function(p, s, c, dkLen, md, callback) {
+  if(typeof md === 'function') {
+    callback = md;
+    md = null;
+  }
+  // default prf to SHA-1
+  if(typeof md === 'undefined' || md === null) {
+    md = forge.md.sha1.create();
+  }
+
+  var hLen = md.digestLength;
+
+  /* 1. If dkLen > (2^32 - 1) * hLen, output "derived key too long" and
+    stop. */
+  if(dkLen > (0xFFFFFFFF * hLen)) {
+    var err = new Error('Derived key is too long.');
+    if(callback) {
+      return callback(err);
+    }
+    throw err;
+  }
+
+  /* 2. Let len be the number of hLen-octet blocks in the derived key,
+    rounding up, and let r be the number of octets in the last
+    block:
+
+    len = CEIL(dkLen / hLen),
+    r = dkLen - (len - 1) * hLen. */
+  var len = Math.ceil(dkLen / hLen);
+  var r = dkLen - (len - 1) * hLen;
+
+  /* 3. For each block of the derived key apply the function F defined
+    below to the password P, the salt S, the iteration count c, and
+    the block index to compute the block:
+
+    T_1 = F(P, S, c, 1),
+    T_2 = F(P, S, c, 2),
+    ...
+    T_len = F(P, S, c, len),
+
+    where the function F is defined as the exclusive-or sum of the
+    first c iterates of the underlying pseudorandom function PRF
+    applied to the password P and the concatenation of the salt S
+    and the block index i:
+
+    F(P, S, c, i) = u_1 XOR u_2 XOR ... XOR u_c
+
+    where
+
+    u_1 = PRF(P, S || INT(i)),
+    u_2 = PRF(P, u_1),
+    ...
+    u_c = PRF(P, u_{c-1}).
+
+    Here, INT(i) is a four-octet encoding of the integer i, most
+    significant octet first. */
+  var prf = forge.hmac.create();
+  prf.start(md, p);
+  var dk = '';
+  var xor, u_c, u_c1;
+
+  // sync version
+  if(!callback) {
+    for(var i = 1; i <= len; ++i) {
+      // PRF(P, S || INT(i)) (first iteration)
+      prf.start(null, null);
+      prf.update(s);
+      prf.update(forge.util.int32ToBytes(i));
+      xor = u_c1 = prf.digest().getBytes();
+
+      // PRF(P, u_{c-1}) (other iterations)
+      for(var j = 2; j <= c; ++j) {
+        prf.start(null, null);
+        prf.update(u_c1);
+        u_c = prf.digest().getBytes();
+        // F(p, s, c, i)
+        xor = forge.util.xorBytes(xor, u_c, hLen);
+        u_c1 = u_c;
+      }
+
+      /* 4. Concatenate the blocks and extract the first dkLen octets to
+        produce a derived key DK:
+
+        DK = T_1 || T_2 ||  ...  || T_len<0..r-1> */
+      dk += (i < len) ? xor : xor.substr(0, r);
+    }
+    /* 5. Output the derived key DK. */
+    return dk;
+  }
+
+  // async version
+  var i = 1, j;
+  function outer() {
+    if(i > len) {
+      // done
+      return callback(null, dk);
+    }
+
+    // PRF(P, S || INT(i)) (first iteration)
+    prf.start(null, null);
+    prf.update(s);
+    prf.update(forge.util.int32ToBytes(i));
+    xor = u_c1 = prf.digest().getBytes();
+
+    // PRF(P, u_{c-1}) (other iterations)
+    j = 2;
+    inner();
+  }
+
+  function inner() {
+    if(j <= c) {
+      prf.start(null, null);
+      prf.update(u_c1);
+      u_c = prf.digest().getBytes();
+      // F(p, s, c, i)
+      xor = forge.util.xorBytes(xor, u_c, hLen);
+      u_c1 = u_c;
+      ++j;
+      return forge.util.setImmediate(inner);
+    }
+
+    /* 4. Concatenate the blocks and extract the first dkLen octets to
+      produce a derived key DK:
+
+      DK = T_1 || T_2 ||  ...  || T_len<0..r-1> */
+    dk += (i < len) ? xor : xor.substr(0, r);
+
+    ++i;
+    outer();
+  }
+
+  outer();
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'pbkdf2';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './hmac', './md', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/pem.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/pem.js
new file mode 100644
index 0000000..e3085dc
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/pem.js
@@ -0,0 +1,285 @@
+/**
+ * Javascript implementation of basic PEM (Privacy Enhanced Mail) algorithms.
+ *
+ * See: RFC 1421.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2013-2014 Digital Bazaar, Inc.
+ *
+ * A Forge PEM object has the following fields:
+ *
+ * type: identifies the type of message (eg: "RSA PRIVATE KEY").
+ *
+ * procType: identifies the type of processing performed on the message,
+ *   it has two subfields: version and type, eg: 4,ENCRYPTED.
+ *
+ * contentDomain: identifies the type of content in the message, typically
+ *   only uses the value: "RFC822".
+ *
+ * dekInfo: identifies the message encryption algorithm and mode and includes
+ *   any parameters for the algorithm, it has two subfields: algorithm and
+ *   parameters, eg: DES-CBC,F8143EDE5960C597.
+ *
+ * headers: contains all other PEM encapsulated headers -- where order is
+ *   significant (for pairing data like recipient ID + key info).
+ *
+ * body: the binary-encoded body.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+// shortcut for pem API
+var pem = forge.pem = forge.pem || {};
+
+/**
+ * Encodes (serializes) the given PEM object.
+ *
+ * @param msg the PEM message object to encode.
+ * @param options the options to use:
+ *          maxline the maximum characters per line for the body, (default: 64).
+ *
+ * @return the PEM-formatted string.
+ */
+pem.encode = function(msg, options) {
+  options = options || {};
+  var rval = '-----BEGIN ' + msg.type + '-----\r\n';
+
+  // encode special headers
+  var header;
+  if(msg.procType) {
+    header = {
+      name: 'Proc-Type',
+      values: [String(msg.procType.version), msg.procType.type]
+    };
+    rval += foldHeader(header);
+  }
+  if(msg.contentDomain) {
+    header = {name: 'Content-Domain', values: [msg.contentDomain]};
+    rval += foldHeader(header);
+  }
+  if(msg.dekInfo) {
+    header = {name: 'DEK-Info', values: [msg.dekInfo.algorithm]};
+    if(msg.dekInfo.parameters) {
+      header.values.push(msg.dekInfo.parameters);
+    }
+    rval += foldHeader(header);
+  }
+
+  if(msg.headers) {
+    // encode all other headers
+    for(var i = 0; i < msg.headers.length; ++i) {
+      rval += foldHeader(msg.headers[i]);
+    }
+  }
+
+  // terminate header
+  if(msg.procType) {
+    rval += '\r\n';
+  }
+
+  // add body
+  rval += forge.util.encode64(msg.body, options.maxline || 64) + '\r\n';
+
+  rval += '-----END ' + msg.type + '-----\r\n';
+  return rval;
+};
+
+/**
+ * Decodes (deserializes) all PEM messages found in the given string.
+ *
+ * @param str the PEM-formatted string to decode.
+ *
+ * @return the PEM message objects in an array.
+ */
+pem.decode = function(str) {
+  var rval = [];
+
+  // split string into PEM messages (be lenient w/EOF on BEGIN line)
+  var rMessage = /\s*-----BEGIN ([A-Z0-9- ]+)-----\r?\n?([\x21-\x7e\s]+?(?:\r?\n\r?\n))?([:A-Za-z0-9+\/=\s]+?)-----END \1-----/g;
+  var rHeader = /([\x21-\x7e]+):\s*([\x21-\x7e\s^:]+)/;
+  var rCRLF = /\r?\n/;
+  var match;
+  while(true) {
+    match = rMessage.exec(str);
+    if(!match) {
+      break;
+    }
+
+    var msg = {
+      type: match[1],
+      procType: null,
+      contentDomain: null,
+      dekInfo: null,
+      headers: [],
+      body: forge.util.decode64(match[3])
+    };
+    rval.push(msg);
+
+    // no headers
+    if(!match[2]) {
+      continue;
+    }
+
+    // parse headers
+    var lines = match[2].split(rCRLF);
+    var li = 0;
+    while(match && li < lines.length) {
+      // get line, trim any rhs whitespace
+      var line = lines[li].replace(/\s+$/, '');
+
+      // RFC2822 unfold any following folded lines
+      for(var nl = li + 1; nl < lines.length; ++nl) {
+        var next = lines[nl];
+        if(!/\s/.test(next[0])) {
+          break;
+        }
+        line += next;
+        li = nl;
+      }
+
+      // parse header
+      match = line.match(rHeader);
+      if(match) {
+        var header = {name: match[1], values: []};
+        var values = match[2].split(',');
+        for(var vi = 0; vi < values.length; ++vi) {
+          header.values.push(ltrim(values[vi]));
+        }
+
+        // Proc-Type must be the first header
+        if(!msg.procType) {
+          if(header.name !== 'Proc-Type') {
+            throw new Error('Invalid PEM formatted message. The first ' +
+              'encapsulated header must be "Proc-Type".');
+          } else if(header.values.length !== 2) {
+            throw new Error('Invalid PEM formatted message. The "Proc-Type" ' +
+              'header must have two subfields.');
+          }
+          msg.procType = {version: values[0], type: values[1]};
+        } else if(!msg.contentDomain && header.name === 'Content-Domain') {
+          // special-case Content-Domain
+          msg.contentDomain = values[0] || '';
+        } else if(!msg.dekInfo && header.name === 'DEK-Info') {
+          // special-case DEK-Info
+          if(header.values.length === 0) {
+            throw new Error('Invalid PEM formatted message. The "DEK-Info" ' +
+              'header must have at least one subfield.');
+          }
+          msg.dekInfo = {algorithm: values[0], parameters: values[1] || null};
+        } else {
+          msg.headers.push(header);
+        }
+      }
+
+      ++li;
+    }
+
+    if(msg.procType === 'ENCRYPTED' && !msg.dekInfo) {
+      throw new Error('Invalid PEM formatted message. The "DEK-Info" ' +
+        'header must be present if "Proc-Type" is "ENCRYPTED".');
+    }
+  }
+
+  if(rval.length === 0) {
+    throw new Error('Invalid PEM formatted message.');
+  }
+
+  return rval;
+};
+
+function foldHeader(header) {
+  var rval = header.name + ': ';
+
+  // ensure values with CRLF are folded
+  var values = [];
+  var insertSpace = function(match, $1) {
+    return ' ' + $1;
+  };
+  for(var i = 0; i < header.values.length; ++i) {
+    values.push(header.values[i].replace(/^(\S+\r\n)/, insertSpace));
+  }
+  rval += values.join(',') + '\r\n';
+
+  // do folding
+  var length = 0;
+  var candidate = -1;
+  for(var i = 0; i < rval.length; ++i, ++length) {
+    if(length > 65 && candidate !== -1) {
+      var insert = rval[candidate];
+      if(insert === ',') {
+        ++candidate;
+        rval = rval.substr(0, candidate) + '\r\n ' + rval.substr(candidate);
+      } else {
+        rval = rval.substr(0, candidate) +
+          '\r\n' + insert + rval.substr(candidate + 1);
+      }
+      length = (i - candidate - 1);
+      candidate = -1;
+      ++i;
+    } else if(rval[i] === ' ' || rval[i] === '\t' || rval[i] === ',') {
+      candidate = i;
+    }
+  }
+
+  return rval;
+}
+
+function ltrim(str) {
+  return str.replace(/^\s+/, '');
+}
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'pem';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/pkcs1.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/pkcs1.js
new file mode 100644
index 0000000..7bf734c
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/pkcs1.js
@@ -0,0 +1,329 @@
+/**
+ * Partial implementation of PKCS#1 v2.2: RSA-OEAP
+ *
+ * Modified but based on the following MIT and BSD licensed code:
+ *
+ * https://github.com/kjur/jsjws/blob/master/rsa.js:
+ *
+ * The 'jsjws'(JSON Web Signature JavaScript Library) License
+ *
+ * Copyright (c) 2012 Kenji Urushima
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * http://webrsa.cvs.sourceforge.net/viewvc/webrsa/Client/RSAES-OAEP.js?content-type=text%2Fplain:
+ *
+ * RSAES-OAEP.js
+ * $Id: RSAES-OAEP.js,v 1.1.1.1 2003/03/19 15:37:20 ellispritchard Exp $
+ * JavaScript Implementation of PKCS #1 v2.1 RSA CRYPTOGRAPHY STANDARD (RSA Laboratories, June 14, 2002)
+ * Copyright (C) Ellis Pritchard, Guardian Unlimited 2003.
+ * Contact: ellis@nukinetics.com
+ * Distributed under the BSD License.
+ *
+ * Official documentation: http://www.rsa.com/rsalabs/node.asp?id=2125
+ *
+ * @author Evan Jones (http://evanjones.ca/)
+ * @author Dave Longley
+ *
+ * Copyright (c) 2013-2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+// shortcut for PKCS#1 API
+var pkcs1 = forge.pkcs1 = forge.pkcs1 || {};
+
+/**
+ * Encode the given RSAES-OAEP message (M) using key, with optional label (L)
+ * and seed.
+ *
+ * This method does not perform RSA encryption, it only encodes the message
+ * using RSAES-OAEP.
+ *
+ * @param key the RSA key to use.
+ * @param message the message to encode.
+ * @param options the options to use:
+ *          label an optional label to use.
+ *          seed the seed to use.
+ *          md the message digest object to use, undefined for SHA-1.
+ *          mgf1 optional mgf1 parameters:
+ *            md the message digest object to use for MGF1.
+ *
+ * @return the encoded message bytes.
+ */
+pkcs1.encode_rsa_oaep = function(key, message, options) {
+  // parse arguments
+  var label;
+  var seed;
+  var md;
+  var mgf1Md;
+  // legacy args (label, seed, md)
+  if(typeof options === 'string') {
+    label = options;
+    seed = arguments[3] || undefined;
+    md = arguments[4] || undefined;
+  } else if(options) {
+    label = options.label || undefined;
+    seed = options.seed || undefined;
+    md = options.md || undefined;
+    if(options.mgf1 && options.mgf1.md) {
+      mgf1Md = options.mgf1.md;
+    }
+  }
+
+  // default OAEP to SHA-1 message digest
+  if(!md) {
+    md = forge.md.sha1.create();
+  } else {
+    md.start();
+  }
+
+  // default MGF-1 to same as OAEP
+  if(!mgf1Md) {
+    mgf1Md = md;
+  }
+
+  // compute length in bytes and check output
+  var keyLength = Math.ceil(key.n.bitLength() / 8);
+  var maxLength = keyLength - 2 * md.digestLength - 2;
+  if(message.length > maxLength) {
+    var error = new Error('RSAES-OAEP input message length is too long.');
+    error.length = message.length;
+    error.maxLength = maxLength;
+    throw error;
+  }
+
+  if(!label) {
+    label = '';
+  }
+  md.update(label, 'raw');
+  var lHash = md.digest();
+
+  var PS = '';
+  var PS_length = maxLength - message.length;
+  for (var i = 0; i < PS_length; i++) {
+    PS += '\x00';
+  }
+
+  var DB = lHash.getBytes() + PS + '\x01' + message;
+
+  if(!seed) {
+    seed = forge.random.getBytes(md.digestLength);
+  } else if(seed.length !== md.digestLength) {
+    var error = new Error('Invalid RSAES-OAEP seed. The seed length must ' +
+      'match the digest length.')
+    error.seedLength = seed.length;
+    error.digestLength = md.digestLength;
+    throw error;
+  }
+
+  var dbMask = rsa_mgf1(seed, keyLength - md.digestLength - 1, mgf1Md);
+  var maskedDB = forge.util.xorBytes(DB, dbMask, DB.length);
+
+  var seedMask = rsa_mgf1(maskedDB, md.digestLength, mgf1Md);
+  var maskedSeed = forge.util.xorBytes(seed, seedMask, seed.length);
+
+  // return encoded message
+  return '\x00' + maskedSeed + maskedDB;
+};
+
+/**
+ * Decode the given RSAES-OAEP encoded message (EM) using key, with optional
+ * label (L).
+ *
+ * This method does not perform RSA decryption, it only decodes the message
+ * using RSAES-OAEP.
+ *
+ * @param key the RSA key to use.
+ * @param em the encoded message to decode.
+ * @param options the options to use:
+ *          label an optional label to use.
+ *          md the message digest object to use for OAEP, undefined for SHA-1.
+ *          mgf1 optional mgf1 parameters:
+ *            md the message digest object to use for MGF1.
+ *
+ * @return the decoded message bytes.
+ */
+pkcs1.decode_rsa_oaep = function(key, em, options) {
+  // parse args
+  var label;
+  var md;
+  var mgf1Md;
+  // legacy args
+  if(typeof options === 'string') {
+    label = options;
+    md = arguments[3] || undefined;
+  } else if(options) {
+    label = options.label || undefined;
+    md = options.md || undefined;
+    if(options.mgf1 && options.mgf1.md) {
+      mgf1Md = options.mgf1.md;
+    }
+  }
+
+  // compute length in bytes
+  var keyLength = Math.ceil(key.n.bitLength() / 8);
+
+  if(em.length !== keyLength) {
+    var error = new Error('RSAES-OAEP encoded message length is invalid.');
+    error.length = em.length;
+    error.expectedLength = keyLength;
+    throw error;
+  }
+
+  // default OAEP to SHA-1 message digest
+  if(md === undefined) {
+    md = forge.md.sha1.create();
+  } else {
+    md.start();
+  }
+
+  // default MGF-1 to same as OAEP
+  if(!mgf1Md) {
+    mgf1Md = md;
+  }
+
+  if(keyLength < 2 * md.digestLength + 2) {
+    throw new Error('RSAES-OAEP key is too short for the hash function.');
+  }
+
+  if(!label) {
+    label = '';
+  }
+  md.update(label, 'raw');
+  var lHash = md.digest().getBytes();
+
+  // split the message into its parts
+  var y = em.charAt(0);
+  var maskedSeed = em.substring(1, md.digestLength + 1);
+  var maskedDB = em.substring(1 + md.digestLength);
+
+  var seedMask = rsa_mgf1(maskedDB, md.digestLength, mgf1Md);
+  var seed = forge.util.xorBytes(maskedSeed, seedMask, maskedSeed.length);
+
+  var dbMask = rsa_mgf1(seed, keyLength - md.digestLength - 1, mgf1Md);
+  var db = forge.util.xorBytes(maskedDB, dbMask, maskedDB.length);
+
+  var lHashPrime = db.substring(0, md.digestLength);
+
+  // constant time check that all values match what is expected
+  var error = (y !== '\x00');
+
+  // constant time check lHash vs lHashPrime
+  for(var i = 0; i < md.digestLength; ++i) {
+    error |= (lHash.charAt(i) !== lHashPrime.charAt(i));
+  }
+
+  // "constant time" find the 0x1 byte separating the padding (zeros) from the
+  // message
+  // TODO: It must be possible to do this in a better/smarter way?
+  var in_ps = 1;
+  var index = md.digestLength;
+  for(var j = md.digestLength; j < db.length; j++) {
+    var code = db.charCodeAt(j);
+
+    var is_0 = (code & 0x1) ^ 0x1;
+
+    // non-zero if not 0 or 1 in the ps section
+    var error_mask = in_ps ? 0xfffe : 0x0000;
+    error |= (code & error_mask);
+
+    // latch in_ps to zero after we find 0x1
+    in_ps = in_ps & is_0;
+    index += in_ps;
+  }
+
+  if(error || db.charCodeAt(index) !== 0x1) {
+    throw new Error('Invalid RSAES-OAEP padding.');
+  }
+
+  return db.substring(index + 1);
+};
+
+function rsa_mgf1(seed, maskLength, hash) {
+  // default to SHA-1 message digest
+  if(!hash) {
+    hash = forge.md.sha1.create();
+  }
+  var t = '';
+  var count = Math.ceil(maskLength / hash.digestLength);
+  for(var i = 0; i < count; ++i) {
+    var c = String.fromCharCode(
+      (i >> 24) & 0xFF, (i >> 16) & 0xFF, (i >> 8) & 0xFF, i & 0xFF);
+    hash.start();
+    hash.update(seed + c);
+    t += hash.digest().getBytes();
+  }
+  return t.substring(0, maskLength);
+}
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'pkcs1';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './util', './random', './sha1'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/pkcs12.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/pkcs12.js
new file mode 100644
index 0000000..19f1c55
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/pkcs12.js
@@ -0,0 +1,1121 @@
+/**
+ * Javascript implementation of PKCS#12.
+ *
+ * @author Dave Longley
+ * @author Stefan Siegl <stesie@brokenpipe.de>
+ *
+ * Copyright (c) 2010-2014 Digital Bazaar, Inc.
+ * Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
+ *
+ * The ASN.1 representation of PKCS#12 is as follows
+ * (see ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-12/pkcs-12-tc1.pdf for details)
+ *
+ * PFX ::= SEQUENCE {
+ *   version  INTEGER {v3(3)}(v3,...),
+ *   authSafe ContentInfo,
+ *   macData  MacData OPTIONAL
+ * }
+ *
+ * MacData ::= SEQUENCE {
+ *   mac DigestInfo,
+ *   macSalt OCTET STRING,
+ *   iterations INTEGER DEFAULT 1
+ * }
+ * Note: The iterations default is for historical reasons and its use is
+ * deprecated. A higher value, like 1024, is recommended.
+ *
+ * DigestInfo is defined in PKCS#7 as follows:
+ *
+ * DigestInfo ::= SEQUENCE {
+ *   digestAlgorithm DigestAlgorithmIdentifier,
+ *   digest Digest
+ * }
+ *
+ * DigestAlgorithmIdentifier ::= AlgorithmIdentifier
+ *
+ * The AlgorithmIdentifier contains an Object Identifier (OID) and parameters
+ * for the algorithm, if any. In the case of SHA1 there is none.
+ *
+ * AlgorithmIdentifer ::= SEQUENCE {
+ *    algorithm OBJECT IDENTIFIER,
+ *    parameters ANY DEFINED BY algorithm OPTIONAL
+ * }
+ *
+ * Digest ::= OCTET STRING
+ *
+ *
+ * ContentInfo ::= SEQUENCE {
+ *   contentType ContentType,
+ *   content     [0] EXPLICIT ANY DEFINED BY contentType OPTIONAL
+ * }
+ *
+ * ContentType ::= OBJECT IDENTIFIER
+ *
+ * AuthenticatedSafe ::= SEQUENCE OF ContentInfo
+ * -- Data if unencrypted
+ * -- EncryptedData if password-encrypted
+ * -- EnvelopedData if public key-encrypted
+ *
+ *
+ * SafeContents ::= SEQUENCE OF SafeBag
+ *
+ * SafeBag ::= SEQUENCE {
+ *   bagId     BAG-TYPE.&id ({PKCS12BagSet})
+ *   bagValue  [0] EXPLICIT BAG-TYPE.&Type({PKCS12BagSet}{@bagId}),
+ *   bagAttributes SET OF PKCS12Attribute OPTIONAL
+ * }
+ *
+ * PKCS12Attribute ::= SEQUENCE {
+ *   attrId ATTRIBUTE.&id ({PKCS12AttrSet}),
+ *   attrValues SET OF ATTRIBUTE.&Type ({PKCS12AttrSet}{@attrId})
+ * } -- This type is compatible with the X.500 type ’Attribute’
+ *
+ * PKCS12AttrSet ATTRIBUTE ::= {
+ *   friendlyName | -- from PKCS #9
+ *   localKeyId, -- from PKCS #9
+ *   ... -- Other attributes are allowed
+ * }
+ *
+ * CertBag ::= SEQUENCE {
+ *   certId    BAG-TYPE.&id   ({CertTypes}),
+ *   certValue [0] EXPLICIT BAG-TYPE.&Type ({CertTypes}{@certId})
+ * }
+ *
+ * x509Certificate BAG-TYPE ::= {OCTET STRING IDENTIFIED BY {certTypes 1}}
+ *   -- DER-encoded X.509 certificate stored in OCTET STRING
+ *
+ * sdsiCertificate BAG-TYPE ::= {IA5String IDENTIFIED BY {certTypes 2}}
+ * -- Base64-encoded SDSI certificate stored in IA5String
+ *
+ * CertTypes BAG-TYPE ::= {
+ *   x509Certificate |
+ *   sdsiCertificate,
+ *   ... -- For future extensions
+ * }
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+// shortcut for asn.1 & PKI API
+var asn1 = forge.asn1;
+var pki = forge.pki;
+
+// shortcut for PKCS#12 API
+var p12 = forge.pkcs12 = forge.pkcs12 || {};
+
+var contentInfoValidator = {
+  name: 'ContentInfo',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,  // a ContentInfo
+  constructed: true,
+  value: [{
+    name: 'ContentInfo.contentType',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.OID,
+    constructed: false,
+    capture: 'contentType'
+  }, {
+    name: 'ContentInfo.content',
+    tagClass: asn1.Class.CONTEXT_SPECIFIC,
+    constructed: true,
+    captureAsn1: 'content'
+  }]
+};
+
+var pfxValidator = {
+  name: 'PFX',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'PFX.version',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'version'
+  },
+  contentInfoValidator, {
+    name: 'PFX.macData',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true,
+    optional: true,
+    captureAsn1: 'mac',
+    value: [{
+      name: 'PFX.macData.mac',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.SEQUENCE,  // DigestInfo
+      constructed: true,
+      value: [{
+        name: 'PFX.macData.mac.digestAlgorithm',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.SEQUENCE,  // DigestAlgorithmIdentifier
+        constructed: true,
+        value: [{
+          name: 'PFX.macData.mac.digestAlgorithm.algorithm',
+          tagClass: asn1.Class.UNIVERSAL,
+          type: asn1.Type.OID,
+          constructed: false,
+          capture: 'macAlgorithm'
+        }, {
+          name: 'PFX.macData.mac.digestAlgorithm.parameters',
+          tagClass: asn1.Class.UNIVERSAL,
+          captureAsn1: 'macAlgorithmParameters'
+        }]
+      }, {
+        name: 'PFX.macData.mac.digest',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.OCTETSTRING,
+        constructed: false,
+        capture: 'macDigest'
+      }]
+    }, {
+      name: 'PFX.macData.macSalt',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.OCTETSTRING,
+      constructed: false,
+      capture: 'macSalt'
+    }, {
+      name: 'PFX.macData.iterations',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.INTEGER,
+      constructed: false,
+      optional: true,
+      capture: 'macIterations'
+    }]
+  }]
+};
+
+var safeBagValidator = {
+  name: 'SafeBag',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'SafeBag.bagId',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.OID,
+    constructed: false,
+    capture: 'bagId'
+  }, {
+    name: 'SafeBag.bagValue',
+    tagClass: asn1.Class.CONTEXT_SPECIFIC,
+    constructed: true,
+    captureAsn1: 'bagValue'
+  }, {
+    name: 'SafeBag.bagAttributes',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SET,
+    constructed: true,
+    optional: true,
+    capture: 'bagAttributes'
+  }]
+};
+
+var attributeValidator = {
+  name: 'Attribute',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'Attribute.attrId',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.OID,
+    constructed: false,
+    capture: 'oid'
+  }, {
+    name: 'Attribute.attrValues',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SET,
+    constructed: true,
+    capture: 'values'
+  }]
+};
+
+var certBagValidator = {
+  name: 'CertBag',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'CertBag.certId',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.OID,
+    constructed: false,
+    capture: 'certId'
+  }, {
+    name: 'CertBag.certValue',
+    tagClass: asn1.Class.CONTEXT_SPECIFIC,
+    constructed: true,
+    /* So far we only support X.509 certificates (which are wrapped in
+       an OCTET STRING, hence hard code that here). */
+    value: [{
+      name: 'CertBag.certValue[0]',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Class.OCTETSTRING,
+      constructed: false,
+      capture: 'cert'
+    }]
+  }]
+};
+
+/**
+ * Search SafeContents structure for bags with matching attributes.
+ *
+ * The search can optionally be narrowed by a certain bag type.
+ *
+ * @param safeContents the SafeContents structure to search in.
+ * @param attrName the name of the attribute to compare against.
+ * @param attrValue the attribute value to search for.
+ * @param [bagType] bag type to narrow search by.
+ *
+ * @return an array of matching bags.
+ */
+function _getBagsByAttribute(safeContents, attrName, attrValue, bagType) {
+  var result = [];
+
+  for(var i = 0; i < safeContents.length; i ++) {
+    for(var j = 0; j < safeContents[i].safeBags.length; j ++) {
+      var bag = safeContents[i].safeBags[j];
+      if(bagType !== undefined && bag.type !== bagType) {
+        continue;
+      }
+      // only filter by bag type, no attribute specified
+      if(attrName === null) {
+        result.push(bag);
+        continue;
+      }
+      if(bag.attributes[attrName] !== undefined &&
+        bag.attributes[attrName].indexOf(attrValue) >= 0) {
+        result.push(bag);
+      }
+    }
+  }
+
+  return result;
+}
+
+/**
+ * Converts a PKCS#12 PFX in ASN.1 notation into a PFX object.
+ *
+ * @param obj The PKCS#12 PFX in ASN.1 notation.
+ * @param strict true to use strict DER decoding, false not to (default: true).
+ * @param {String} password Password to decrypt with (optional).
+ *
+ * @return PKCS#12 PFX object.
+ */
+p12.pkcs12FromAsn1 = function(obj, strict, password) {
+  // handle args
+  if(typeof strict === 'string') {
+    password = strict;
+    strict = true;
+  } else if(strict === undefined) {
+    strict = true;
+  }
+
+  // validate PFX and capture data
+  var capture = {};
+  var errors = [];
+  if(!asn1.validate(obj, pfxValidator, capture, errors)) {
+    var error = new Error('Cannot read PKCS#12 PFX. ' +
+      'ASN.1 object is not an PKCS#12 PFX.');
+    error.errors = error;
+    throw error;
+  }
+
+  var pfx = {
+    version: capture.version.charCodeAt(0),
+    safeContents: [],
+
+    /**
+     * Gets bags with matching attributes.
+     *
+     * @param filter the attributes to filter by:
+     *          [localKeyId] the localKeyId to search for.
+     *          [localKeyIdHex] the localKeyId in hex to search for.
+     *          [friendlyName] the friendly name to search for.
+     *          [bagType] bag type to narrow each attribute search by.
+     *
+     * @return a map of attribute type to an array of matching bags or, if no
+     *           attribute was given but a bag type, the map key will be the
+     *           bag type.
+     */
+    getBags: function(filter) {
+      var rval = {};
+
+      var localKeyId;
+      if('localKeyId' in filter) {
+        localKeyId = filter.localKeyId;
+      } else if('localKeyIdHex' in filter) {
+        localKeyId = forge.util.hexToBytes(filter.localKeyIdHex);
+      }
+
+      // filter on bagType only
+      if(localKeyId === undefined && !('friendlyName' in filter) &&
+        'bagType' in filter) {
+        rval[filter.bagType] = _getBagsByAttribute(
+          pfx.safeContents, null, null, filter.bagType);
+      }
+
+      if(localKeyId !== undefined) {
+        rval.localKeyId = _getBagsByAttribute(
+          pfx.safeContents, 'localKeyId',
+          localKeyId, filter.bagType);
+      }
+      if('friendlyName' in filter) {
+        rval.friendlyName = _getBagsByAttribute(
+          pfx.safeContents, 'friendlyName',
+          filter.friendlyName, filter.bagType);
+      }
+
+      return rval;
+    },
+
+    /**
+     * DEPRECATED: use getBags() instead.
+     *
+     * Get bags with matching friendlyName attribute.
+     *
+     * @param friendlyName the friendly name to search for.
+     * @param [bagType] bag type to narrow search by.
+     *
+     * @return an array of bags with matching friendlyName attribute.
+     */
+    getBagsByFriendlyName: function(friendlyName, bagType) {
+      return _getBagsByAttribute(
+        pfx.safeContents, 'friendlyName', friendlyName, bagType);
+    },
+
+    /**
+     * DEPRECATED: use getBags() instead.
+     *
+     * Get bags with matching localKeyId attribute.
+     *
+     * @param localKeyId the localKeyId to search for.
+     * @param [bagType] bag type to narrow search by.
+     *
+     * @return an array of bags with matching localKeyId attribute.
+     */
+    getBagsByLocalKeyId: function(localKeyId, bagType) {
+      return _getBagsByAttribute(
+        pfx.safeContents, 'localKeyId', localKeyId, bagType);
+    }
+  };
+
+  if(capture.version.charCodeAt(0) !== 3) {
+    var error = new Error('PKCS#12 PFX of version other than 3 not supported.');
+    error.version = capture.version.charCodeAt(0);
+    throw error;
+  }
+
+  if(asn1.derToOid(capture.contentType) !== pki.oids.data) {
+    var error = new Error('Only PKCS#12 PFX in password integrity mode supported.');
+    error.oid = asn1.derToOid(capture.contentType);
+    throw error;
+  }
+
+  var data = capture.content.value[0];
+  if(data.tagClass !== asn1.Class.UNIVERSAL ||
+     data.type !== asn1.Type.OCTETSTRING) {
+    throw new Error('PKCS#12 authSafe content data is not an OCTET STRING.');
+  }
+  data = _decodePkcs7Data(data);
+
+  // check for MAC
+  if(capture.mac) {
+    var md = null;
+    var macKeyBytes = 0;
+    var macAlgorithm = asn1.derToOid(capture.macAlgorithm);
+    switch(macAlgorithm) {
+    case pki.oids.sha1:
+      md = forge.md.sha1.create();
+      macKeyBytes = 20;
+      break;
+    case pki.oids.sha256:
+      md = forge.md.sha256.create();
+      macKeyBytes = 32;
+      break;
+    case pki.oids.sha384:
+      md = forge.md.sha384.create();
+      macKeyBytes = 48;
+      break;
+    case pki.oids.sha512:
+      md = forge.md.sha512.create();
+      macKeyBytes = 64;
+      break;
+    case pki.oids.md5:
+      md = forge.md.md5.create();
+      macKeyBytes = 16;
+      break;
+    }
+    if(md === null) {
+      throw new Error('PKCS#12 uses unsupported MAC algorithm: ' + macAlgorithm);
+    }
+
+    // verify MAC (iterations default to 1)
+    var macSalt = new forge.util.ByteBuffer(capture.macSalt);
+    var macIterations = (('macIterations' in capture) ?
+      parseInt(forge.util.bytesToHex(capture.macIterations), 16) : 1);
+    var macKey = p12.generateKey(
+      password, macSalt, 3, macIterations, macKeyBytes, md);
+    var mac = forge.hmac.create();
+    mac.start(md, macKey);
+    mac.update(data.value);
+    var macValue = mac.getMac();
+    if(macValue.getBytes() !== capture.macDigest) {
+      throw new Error('PKCS#12 MAC could not be verified. Invalid password?');
+    }
+  }
+
+  _decodeAuthenticatedSafe(pfx, data.value, strict, password);
+  return pfx;
+};
+
+/**
+ * Decodes PKCS#7 Data. PKCS#7 (RFC 2315) defines "Data" as an OCTET STRING,
+ * but it is sometimes an OCTET STRING that is composed/constructed of chunks,
+ * each its own OCTET STRING. This is BER-encoding vs. DER-encoding. This
+ * function transforms this corner-case into the usual simple,
+ * non-composed/constructed OCTET STRING.
+ *
+ * This function may be moved to ASN.1 at some point to better deal with
+ * more BER-encoding issues, should they arise.
+ *
+ * @param data the ASN.1 Data object to transform.
+ */
+function _decodePkcs7Data(data) {
+  // handle special case of "chunked" data content: an octet string composed
+  // of other octet strings
+  if(data.composed || data.constructed) {
+    var value = forge.util.createBuffer();
+    for(var i = 0; i < data.value.length; ++i) {
+      value.putBytes(data.value[i].value);
+    }
+    data.composed = data.constructed = false;
+    data.value = value.getBytes();
+  }
+  return data;
+}
+
+/**
+ * Decode PKCS#12 AuthenticatedSafe (BER encoded) into PFX object.
+ *
+ * The AuthenticatedSafe is a BER-encoded SEQUENCE OF ContentInfo.
+ *
+ * @param pfx The PKCS#12 PFX object to fill.
+ * @param {String} authSafe BER-encoded AuthenticatedSafe.
+ * @param strict true to use strict DER decoding, false not to.
+ * @param {String} password Password to decrypt with (optional).
+ */
+function _decodeAuthenticatedSafe(pfx, authSafe, strict, password) {
+  authSafe = asn1.fromDer(authSafe, strict);  /* actually it's BER encoded */
+
+  if(authSafe.tagClass !== asn1.Class.UNIVERSAL ||
+     authSafe.type !== asn1.Type.SEQUENCE ||
+     authSafe.constructed !== true) {
+    throw new Error('PKCS#12 AuthenticatedSafe expected to be a ' +
+      'SEQUENCE OF ContentInfo');
+  }
+
+  for(var i = 0; i < authSafe.value.length; i ++) {
+    var contentInfo = authSafe.value[i];
+
+    // validate contentInfo and capture data
+    var capture = {};
+    var errors = [];
+    if(!asn1.validate(contentInfo, contentInfoValidator, capture, errors)) {
+      var error = new Error('Cannot read ContentInfo.');
+      error.errors = errors;
+      throw error;
+    }
+
+    var obj = {
+      encrypted: false
+    };
+    var safeContents = null;
+    var data = capture.content.value[0];
+    switch(asn1.derToOid(capture.contentType)) {
+    case pki.oids.data:
+      if(data.tagClass !== asn1.Class.UNIVERSAL ||
+         data.type !== asn1.Type.OCTETSTRING) {
+        throw new Error('PKCS#12 SafeContents Data is not an OCTET STRING.');
+      }
+      safeContents = _decodePkcs7Data(data).value;
+      break;
+    case pki.oids.encryptedData:
+      safeContents = _decryptSafeContents(data, password);
+      obj.encrypted = true;
+      break;
+    default:
+      var error = new Error('Unsupported PKCS#12 contentType.');
+      error.contentType = asn1.derToOid(capture.contentType);
+      throw error;
+    }
+
+    obj.safeBags = _decodeSafeContents(safeContents, strict, password);
+    pfx.safeContents.push(obj);
+  }
+}
+
+/**
+ * Decrypt PKCS#7 EncryptedData structure.
+ *
+ * @param data ASN.1 encoded EncryptedContentInfo object.
+ * @param password The user-provided password.
+ *
+ * @return The decrypted SafeContents (ASN.1 object).
+ */
+function _decryptSafeContents(data, password) {
+  var capture = {};
+  var errors = [];
+  if(!asn1.validate(
+    data, forge.pkcs7.asn1.encryptedDataValidator, capture, errors)) {
+    var error = new Error('Cannot read EncryptedContentInfo.');
+    error.errors = errors;
+    throw error;
+  }
+
+  var oid = asn1.derToOid(capture.contentType);
+  if(oid !== pki.oids.data) {
+    var error = new Error(
+      'PKCS#12 EncryptedContentInfo ContentType is not Data.');
+    error.oid = oid;
+    throw error;
+  }
+
+  // get cipher
+  oid = asn1.derToOid(capture.encAlgorithm);
+  var cipher = pki.pbe.getCipher(oid, capture.encParameter, password);
+
+  // get encrypted data
+  var encryptedContentAsn1 = _decodePkcs7Data(capture.encryptedContentAsn1);
+  var encrypted = forge.util.createBuffer(encryptedContentAsn1.value);
+
+  cipher.update(encrypted);
+  if(!cipher.finish()) {
+    throw new Error('Failed to decrypt PKCS#12 SafeContents.');
+  }
+
+  return cipher.output.getBytes();
+}
+
+/**
+ * Decode PKCS#12 SafeContents (BER-encoded) into array of Bag objects.
+ *
+ * The safeContents is a BER-encoded SEQUENCE OF SafeBag.
+ *
+ * @param {String} safeContents BER-encoded safeContents.
+ * @param strict true to use strict DER decoding, false not to.
+ * @param {String} password Password to decrypt with (optional).
+ *
+ * @return {Array} Array of Bag objects.
+ */
+function _decodeSafeContents(safeContents, strict, password) {
+  // if strict and no safe contents, return empty safes
+  if(!strict && safeContents.length === 0) {
+    return [];
+  }
+
+  // actually it's BER-encoded
+  safeContents = asn1.fromDer(safeContents, strict);
+
+  if(safeContents.tagClass !== asn1.Class.UNIVERSAL ||
+    safeContents.type !== asn1.Type.SEQUENCE ||
+    safeContents.constructed !== true) {
+    throw new Error(
+      'PKCS#12 SafeContents expected to be a SEQUENCE OF SafeBag.');
+  }
+
+  var res = [];
+  for(var i = 0; i < safeContents.value.length; i++) {
+    var safeBag = safeContents.value[i];
+
+    // validate SafeBag and capture data
+    var capture = {};
+    var errors = [];
+    if(!asn1.validate(safeBag, safeBagValidator, capture, errors)) {
+      var error = new Error('Cannot read SafeBag.');
+      error.errors = errors;
+      throw error;
+    }
+
+    /* Create bag object and push to result array. */
+    var bag = {
+      type: asn1.derToOid(capture.bagId),
+      attributes: _decodeBagAttributes(capture.bagAttributes)
+    };
+    res.push(bag);
+
+    var validator, decoder;
+    var bagAsn1 = capture.bagValue.value[0];
+    switch(bag.type) {
+      case pki.oids.pkcs8ShroudedKeyBag:
+        /* bagAsn1 has a EncryptedPrivateKeyInfo, which we need to decrypt.
+           Afterwards we can handle it like a keyBag,
+           which is a PrivateKeyInfo. */
+        bagAsn1 = pki.decryptPrivateKeyInfo(bagAsn1, password);
+        if(bagAsn1 === null) {
+          throw new Error(
+            'Unable to decrypt PKCS#8 ShroudedKeyBag, wrong password?');
+        }
+
+        /* fall through */
+      case pki.oids.keyBag:
+        /* A PKCS#12 keyBag is a simple PrivateKeyInfo as understood by our
+           PKI module, hence we don't have to do validation/capturing here,
+           just pass what we already got. */
+        bag.key = pki.privateKeyFromAsn1(bagAsn1);
+        continue;  /* Nothing more to do. */
+
+      case pki.oids.certBag:
+        /* A PKCS#12 certBag can wrap both X.509 and sdsi certificates.
+           Therefore put the SafeBag content through another validator to
+           capture the fields.  Afterwards check & store the results. */
+        validator = certBagValidator;
+        decoder = function() {
+          if(asn1.derToOid(capture.certId) !== pki.oids.x509Certificate) {
+            var error = new Error(
+              'Unsupported certificate type, only X.509 supported.');
+            error.oid = asn1.derToOid(capture.certId);
+            throw error;
+          }
+
+          // true=produce cert hash
+          bag.cert = pki.certificateFromAsn1(
+            asn1.fromDer(capture.cert, strict), true);
+        };
+        break;
+
+      default:
+        var error = new Error('Unsupported PKCS#12 SafeBag type.');
+        error.oid = bag.type;
+        throw error;
+    }
+
+    /* Validate SafeBag value (i.e. CertBag, etc.) and capture data if needed. */
+    if(validator !== undefined &&
+       !asn1.validate(bagAsn1, validator, capture, errors)) {
+      var error = new Error('Cannot read PKCS#12 ' + validator.name);
+      error.errors = errors;
+      throw error;
+    }
+
+    /* Call decoder function from above to store the results. */
+    decoder();
+  }
+
+  return res;
+}
+
+/**
+ * Decode PKCS#12 SET OF PKCS12Attribute into JavaScript object.
+ *
+ * @param attributes SET OF PKCS12Attribute (ASN.1 object).
+ *
+ * @return the decoded attributes.
+ */
+function _decodeBagAttributes(attributes) {
+  var decodedAttrs = {};
+
+  if(attributes !== undefined) {
+    for(var i = 0; i < attributes.length; ++i) {
+      var capture = {};
+      var errors = [];
+      if(!asn1.validate(attributes[i], attributeValidator, capture, errors)) {
+        var error = new Error('Cannot read PKCS#12 BagAttribute.');
+        error.errors = errors;
+        throw error;
+      }
+
+      var oid = asn1.derToOid(capture.oid);
+      if(pki.oids[oid] === undefined) {
+        // unsupported attribute type, ignore.
+        continue;
+      }
+
+      decodedAttrs[pki.oids[oid]] = [];
+      for(var j = 0; j < capture.values.length; ++j) {
+        decodedAttrs[pki.oids[oid]].push(capture.values[j].value);
+      }
+    }
+  }
+
+  return decodedAttrs;
+}
+
+/**
+ * Wraps a private key and certificate in a PKCS#12 PFX wrapper. If a
+ * password is provided then the private key will be encrypted.
+ *
+ * An entire certificate chain may also be included. To do this, pass
+ * an array for the "cert" parameter where the first certificate is
+ * the one that is paired with the private key and each subsequent one
+ * verifies the previous one. The certificates may be in PEM format or
+ * have been already parsed by Forge.
+ *
+ * @todo implement password-based-encryption for the whole package
+ *
+ * @param key the private key.
+ * @param cert the certificate (may be an array of certificates in order
+ *          to specify a certificate chain).
+ * @param password the password to use, null for none.
+ * @param options:
+ *          algorithm the encryption algorithm to use
+ *            ('aes128', 'aes192', 'aes256', '3des'), defaults to 'aes128'.
+ *          count the iteration count to use.
+ *          saltSize the salt size to use.
+ *          useMac true to include a MAC, false not to, defaults to true.
+ *          localKeyId the local key ID to use, in hex.
+ *          friendlyName the friendly name to use.
+ *          generateLocalKeyId true to generate a random local key ID,
+ *            false not to, defaults to true.
+ *
+ * @return the PKCS#12 PFX ASN.1 object.
+ */
+p12.toPkcs12Asn1 = function(key, cert, password, options) {
+  // set default options
+  options = options || {};
+  options.saltSize = options.saltSize || 8;
+  options.count = options.count || 2048;
+  options.algorithm = options.algorithm || options.encAlgorithm || 'aes128';
+  if(!('useMac' in options)) {
+    options.useMac = true;
+  }
+  if(!('localKeyId' in options)) {
+    options.localKeyId = null;
+  }
+  if(!('generateLocalKeyId' in options)) {
+    options.generateLocalKeyId = true;
+  }
+
+  var localKeyId = options.localKeyId;
+  var bagAttrs;
+  if(localKeyId !== null) {
+    localKeyId = forge.util.hexToBytes(localKeyId);
+  } else if(options.generateLocalKeyId) {
+    // use SHA-1 of paired cert, if available
+    if(cert) {
+      var pairedCert = forge.util.isArray(cert) ? cert[0] : cert;
+      if(typeof pairedCert === 'string') {
+        pairedCert = pki.certificateFromPem(pairedCert);
+      }
+      var sha1 = forge.md.sha1.create();
+      sha1.update(asn1.toDer(pki.certificateToAsn1(pairedCert)).getBytes());
+      localKeyId = sha1.digest().getBytes();
+    } else {
+      // FIXME: consider using SHA-1 of public key (which can be generated
+      // from private key components), see: cert.generateSubjectKeyIdentifier
+      // generate random bytes
+      localKeyId = forge.random.getBytes(20);
+    }
+  }
+
+  var attrs = [];
+  if(localKeyId !== null) {
+    attrs.push(
+      // localKeyID
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+        // attrId
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+          asn1.oidToDer(pki.oids.localKeyId).getBytes()),
+        // attrValues
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true, [
+          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
+            localKeyId)
+        ])
+      ]));
+  }
+  if('friendlyName' in options) {
+    attrs.push(
+      // friendlyName
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+        // attrId
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+          asn1.oidToDer(pki.oids.friendlyName).getBytes()),
+        // attrValues
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true, [
+          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.BMPSTRING, false,
+            options.friendlyName)
+        ])
+      ]));
+  }
+
+  if(attrs.length > 0) {
+    bagAttrs = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true, attrs);
+  }
+
+  // collect contents for AuthenticatedSafe
+  var contents = [];
+
+  // create safe bag(s) for certificate chain
+  var chain = [];
+  if(cert !== null) {
+    if(forge.util.isArray(cert)) {
+      chain = cert;
+    } else {
+      chain = [cert];
+    }
+  }
+
+  var certSafeBags = [];
+  for(var i = 0; i < chain.length; ++i) {
+    // convert cert from PEM as necessary
+    cert = chain[i];
+    if(typeof cert === 'string') {
+      cert = pki.certificateFromPem(cert);
+    }
+
+    // SafeBag
+    var certBagAttrs = (i === 0) ? bagAttrs : undefined;
+    var certAsn1 = pki.certificateToAsn1(cert);
+    var certSafeBag =
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+        // bagId
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+          asn1.oidToDer(pki.oids.certBag).getBytes()),
+        // bagValue
+        asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
+          // CertBag
+          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+            // certId
+            asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+              asn1.oidToDer(pki.oids.x509Certificate).getBytes()),
+            // certValue (x509Certificate)
+            asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
+              asn1.create(
+                asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
+                asn1.toDer(certAsn1).getBytes())
+            ])])]),
+        // bagAttributes (OPTIONAL)
+        certBagAttrs
+      ]);
+    certSafeBags.push(certSafeBag);
+  }
+
+  if(certSafeBags.length > 0) {
+    // SafeContents
+    var certSafeContents = asn1.create(
+      asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, certSafeBags);
+
+    // ContentInfo
+    var certCI =
+      // PKCS#7 ContentInfo
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+        // contentType
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+          // OID for the content type is 'data'
+          asn1.oidToDer(pki.oids.data).getBytes()),
+        // content
+        asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
+          asn1.create(
+            asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
+            asn1.toDer(certSafeContents).getBytes())
+        ])
+      ]);
+    contents.push(certCI);
+  }
+
+  // create safe contents for private key
+  var keyBag = null;
+  if(key !== null) {
+    // SafeBag
+    var pkAsn1 = pki.wrapRsaPrivateKey(pki.privateKeyToAsn1(key));
+    if(password === null) {
+      // no encryption
+      keyBag = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+        // bagId
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+          asn1.oidToDer(pki.oids.keyBag).getBytes()),
+        // bagValue
+        asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
+          // PrivateKeyInfo
+          pkAsn1
+        ]),
+        // bagAttributes (OPTIONAL)
+        bagAttrs
+      ]);
+    } else {
+      // encrypted PrivateKeyInfo
+      keyBag = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+        // bagId
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+          asn1.oidToDer(pki.oids.pkcs8ShroudedKeyBag).getBytes()),
+        // bagValue
+        asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
+          // EncryptedPrivateKeyInfo
+          pki.encryptPrivateKeyInfo(pkAsn1, password, options)
+        ]),
+        // bagAttributes (OPTIONAL)
+        bagAttrs
+      ]);
+    }
+
+    // SafeContents
+    var keySafeContents =
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [keyBag]);
+
+    // ContentInfo
+    var keyCI =
+      // PKCS#7 ContentInfo
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+        // contentType
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+          // OID for the content type is 'data'
+          asn1.oidToDer(pki.oids.data).getBytes()),
+        // content
+        asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
+          asn1.create(
+            asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
+            asn1.toDer(keySafeContents).getBytes())
+        ])
+      ]);
+    contents.push(keyCI);
+  }
+
+  // create AuthenticatedSafe by stringing together the contents
+  var safe = asn1.create(
+    asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, contents);
+
+  var macData;
+  if(options.useMac) {
+    // MacData
+    var sha1 = forge.md.sha1.create();
+    var macSalt = new forge.util.ByteBuffer(
+      forge.random.getBytes(options.saltSize));
+    var count = options.count;
+    // 160-bit key
+    var key = p12.generateKey(password, macSalt, 3, count, 20);
+    var mac = forge.hmac.create();
+    mac.start(sha1, key);
+    mac.update(asn1.toDer(safe).getBytes());
+    var macValue = mac.getMac();
+    macData = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+      // mac DigestInfo
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+        // digestAlgorithm
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+          // algorithm = SHA-1
+          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+            asn1.oidToDer(pki.oids.sha1).getBytes()),
+          // parameters = Null
+          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
+        ]),
+        // digest
+        asn1.create(
+          asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING,
+          false, macValue.getBytes())
+      ]),
+      // macSalt OCTET STRING
+      asn1.create(
+        asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, macSalt.getBytes()),
+      // iterations INTEGER (XXX: Only support count < 65536)
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+        asn1.integerToDer(count).getBytes()
+      )
+    ]);
+  }
+
+  // PFX
+  return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+    // version (3)
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      asn1.integerToDer(3).getBytes()),
+    // PKCS#7 ContentInfo
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+      // contentType
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+        // OID for the content type is 'data'
+        asn1.oidToDer(pki.oids.data).getBytes()),
+      // content
+      asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
+        asn1.create(
+          asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
+          asn1.toDer(safe).getBytes())
+      ])
+    ]),
+    macData
+  ]);
+};
+
+/**
+ * Derives a PKCS#12 key.
+ *
+ * @param password the password to derive the key material from, null or
+ *          undefined for none.
+ * @param salt the salt, as a ByteBuffer, to use.
+ * @param id the PKCS#12 ID byte (1 = key material, 2 = IV, 3 = MAC).
+ * @param iter the iteration count.
+ * @param n the number of bytes to derive from the password.
+ * @param md the message digest to use, defaults to SHA-1.
+ *
+ * @return a ByteBuffer with the bytes derived from the password.
+ */
+p12.generateKey = forge.pbe.generatePkcs12Key;
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'pkcs12';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define([
+  'require',
+  'module',
+  './asn1',
+  './hmac',
+  './oids',
+  './pkcs7asn1',
+  './pbe',
+  './random',
+  './rsa',
+  './sha1',
+  './util',
+  './x509'
+], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/pkcs7.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/pkcs7.js
new file mode 100644
index 0000000..ffa7413
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/pkcs7.js
@@ -0,0 +1,842 @@
+/**
+ * Javascript implementation of PKCS#7 v1.5. Currently only certain parts of
+ * PKCS#7 are implemented, especially the enveloped-data content type.
+ *
+ * @author Stefan Siegl
+ *
+ * Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
+ *
+ * Currently this implementation only supports ContentType of either
+ * EnvelopedData or EncryptedData on root level.  The top level elements may
+ * contain only a ContentInfo of ContentType Data, i.e. plain data.  Further
+ * nesting is not (yet) supported.
+ *
+ * The Forge validators for PKCS #7's ASN.1 structures are available from
+ * a seperate file pkcs7asn1.js, since those are referenced from other
+ * PKCS standards like PKCS #12.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+// shortcut for ASN.1 API
+var asn1 = forge.asn1;
+
+// shortcut for PKCS#7 API
+var p7 = forge.pkcs7 = forge.pkcs7 || {};
+
+/**
+ * Converts a PKCS#7 message from PEM format.
+ *
+ * @param pem the PEM-formatted PKCS#7 message.
+ *
+ * @return the PKCS#7 message.
+ */
+p7.messageFromPem = function(pem) {
+  var msg = forge.pem.decode(pem)[0];
+
+  if(msg.type !== 'PKCS7') {
+    var error = new Error('Could not convert PKCS#7 message from PEM; PEM ' +
+      'header type is not "PKCS#7".');
+    error.headerType = msg.type;
+    throw error;
+  }
+  if(msg.procType && msg.procType.type === 'ENCRYPTED') {
+    throw new Error('Could not convert PKCS#7 message from PEM; PEM is encrypted.');
+  }
+
+  // convert DER to ASN.1 object
+  var obj = asn1.fromDer(msg.body);
+
+  return p7.messageFromAsn1(obj);
+};
+
+/**
+ * Converts a PKCS#7 message to PEM format.
+ *
+ * @param msg The PKCS#7 message object
+ * @param maxline The maximum characters per line, defaults to 64.
+ *
+ * @return The PEM-formatted PKCS#7 message.
+ */
+p7.messageToPem = function(msg, maxline) {
+  // convert to ASN.1, then DER, then PEM-encode
+  var pemObj = {
+    type: 'PKCS7',
+    body: asn1.toDer(msg.toAsn1()).getBytes()
+  };
+  return forge.pem.encode(pemObj, {maxline: maxline});
+};
+
+/**
+ * Converts a PKCS#7 message from an ASN.1 object.
+ *
+ * @param obj the ASN.1 representation of a ContentInfo.
+ *
+ * @return the PKCS#7 message.
+ */
+p7.messageFromAsn1 = function(obj) {
+  // validate root level ContentInfo and capture data
+  var capture = {};
+  var errors = [];
+  if(!asn1.validate(obj, p7.asn1.contentInfoValidator, capture, errors))
+  {
+    var error = new Error('Cannot read PKCS#7 message. ' +
+      'ASN.1 object is not an PKCS#7 ContentInfo.');
+    error.errors = errors;
+    throw error;
+  }
+
+  var contentType = asn1.derToOid(capture.contentType);
+  var msg;
+
+  switch(contentType) {
+    case forge.pki.oids.envelopedData:
+      msg = p7.createEnvelopedData();
+      break;
+
+    case forge.pki.oids.encryptedData:
+      msg = p7.createEncryptedData();
+      break;
+
+    case forge.pki.oids.signedData:
+      msg = p7.createSignedData();
+      break;
+
+    default:
+      throw new Error('Cannot read PKCS#7 message. ContentType with OID ' +
+        contentType + ' is not (yet) supported.');
+  }
+
+  msg.fromAsn1(capture.content.value[0]);
+  return msg;
+};
+
+/**
+ * Converts a single RecipientInfo from an ASN.1 object.
+ *
+ * @param obj The ASN.1 representation of a RecipientInfo.
+ *
+ * @return The recipientInfo object.
+ */
+var _recipientInfoFromAsn1 = function(obj) {
+  // Validate EnvelopedData content block and capture data.
+  var capture = {};
+  var errors = [];
+  if(!asn1.validate(obj, p7.asn1.recipientInfoValidator, capture, errors))
+  {
+    var error = new Error('Cannot read PKCS#7 message. ' +
+      'ASN.1 object is not an PKCS#7 EnvelopedData.');
+    error.errors = errors;
+    throw error;
+  }
+
+  return {
+    version: capture.version.charCodeAt(0),
+    issuer: forge.pki.RDNAttributesAsArray(capture.issuer),
+    serialNumber: forge.util.createBuffer(capture.serial).toHex(),
+    encryptedContent: {
+      algorithm: asn1.derToOid(capture.encAlgorithm),
+      parameter: capture.encParameter.value,
+      content: capture.encKey
+    }
+  };
+};
+
+/**
+ * Converts a single recipientInfo object to an ASN.1 object.
+ *
+ * @param obj The recipientInfo object.
+ *
+ * @return The ASN.1 representation of a RecipientInfo.
+ */
+var _recipientInfoToAsn1 = function(obj) {
+  return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+    // Version
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      asn1.integerToDer(obj.version).getBytes()),
+    // IssuerAndSerialNumber
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+      // Name
+      forge.pki.distinguishedNameToAsn1({attributes: obj.issuer}),
+      // Serial
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+        forge.util.hexToBytes(obj.serialNumber))
+    ]),
+    // KeyEncryptionAlgorithmIdentifier
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+      // Algorithm
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+        asn1.oidToDer(obj.encryptedContent.algorithm).getBytes()),
+      // Parameter, force NULL, only RSA supported for now.
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
+    ]),
+    // EncryptedKey
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
+      obj.encryptedContent.content)
+  ]);
+};
+
+/**
+ * Map a set of RecipientInfo ASN.1 objects to recipientInfo objects.
+ *
+ * @param objArr Array of ASN.1 representations RecipientInfo (i.e. SET OF).
+ *
+ * @return array of recipientInfo objects.
+ */
+var _recipientInfosFromAsn1 = function(objArr) {
+  var ret = [];
+  for(var i = 0; i < objArr.length; i ++) {
+    ret.push(_recipientInfoFromAsn1(objArr[i]));
+  }
+  return ret;
+};
+
+/**
+ * Map an array of recipientInfo objects to ASN.1 objects.
+ *
+ * @param recipientsArr Array of recipientInfo objects.
+ *
+ * @return Array of ASN.1 representations RecipientInfo.
+ */
+var _recipientInfosToAsn1 = function(recipientsArr) {
+  var ret = [];
+  for(var i = 0; i < recipientsArr.length; i ++) {
+    ret.push(_recipientInfoToAsn1(recipientsArr[i]));
+  }
+  return ret;
+};
+
+/**
+ * Map messages encrypted content to ASN.1 objects.
+ *
+ * @param ec The encryptedContent object of the message.
+ *
+ * @return ASN.1 representation of the encryptedContent object (SEQUENCE).
+ */
+var _encryptedContentToAsn1 = function(ec) {
+  return [
+    // ContentType, always Data for the moment
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+      asn1.oidToDer(forge.pki.oids.data).getBytes()),
+    // ContentEncryptionAlgorithmIdentifier
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+      // Algorithm
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+        asn1.oidToDer(ec.algorithm).getBytes()),
+      // Parameters (IV)
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
+        ec.parameter.getBytes())
+    ]),
+    // [0] EncryptedContent
+    asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
+        ec.content.getBytes())
+    ])
+  ];
+};
+
+/**
+ * Reads the "common part" of an PKCS#7 content block (in ASN.1 format)
+ *
+ * This function reads the "common part" of the PKCS#7 content blocks
+ * EncryptedData and EnvelopedData, i.e. version number and symmetrically
+ * encrypted content block.
+ *
+ * The result of the ASN.1 validate and capture process is returned
+ * to allow the caller to extract further data, e.g. the list of recipients
+ * in case of a EnvelopedData object.
+ *
+ * @param msg the PKCS#7 object to read the data to.
+ * @param obj the ASN.1 representation of the content block.
+ * @param validator the ASN.1 structure validator object to use.
+ *
+ * @return the value map captured by validator object.
+ */
+var _fromAsn1 = function(msg, obj, validator) {
+  var capture = {};
+  var errors = [];
+  if(!asn1.validate(obj, validator, capture, errors)) {
+    var error = new Error('Cannot read PKCS#7 message. ' +
+      'ASN.1 object is not a supported PKCS#7 message.');
+    error.errors = error;
+    throw error;
+  }
+
+  // Check contentType, so far we only support (raw) Data.
+  var contentType = asn1.derToOid(capture.contentType);
+  if(contentType !== forge.pki.oids.data) {
+    throw new Error('Unsupported PKCS#7 message. ' +
+      'Only wrapped ContentType Data supported.');
+  }
+
+  if(capture.encryptedContent) {
+    var content = '';
+    if(forge.util.isArray(capture.encryptedContent)) {
+      for(var i = 0; i < capture.encryptedContent.length; ++i) {
+        if(capture.encryptedContent[i].type !== asn1.Type.OCTETSTRING) {
+          throw new Error('Malformed PKCS#7 message, expecting encrypted ' +
+            'content constructed of only OCTET STRING objects.');
+        }
+        content += capture.encryptedContent[i].value;
+      }
+    } else {
+      content = capture.encryptedContent;
+    }
+    msg.encryptedContent = {
+      algorithm: asn1.derToOid(capture.encAlgorithm),
+      parameter: forge.util.createBuffer(capture.encParameter.value),
+      content: forge.util.createBuffer(content)
+    };
+  }
+
+  if(capture.content) {
+    var content = '';
+    if(forge.util.isArray(capture.content)) {
+      for(var i = 0; i < capture.content.length; ++i) {
+        if(capture.content[i].type !== asn1.Type.OCTETSTRING) {
+          throw new Error('Malformed PKCS#7 message, expecting ' +
+            'content constructed of only OCTET STRING objects.');
+        }
+        content += capture.content[i].value;
+      }
+    } else {
+      content = capture.content;
+    }
+    msg.content = forge.util.createBuffer(content);
+  }
+
+  msg.version = capture.version.charCodeAt(0);
+  msg.rawCapture = capture;
+
+  return capture;
+};
+
+/**
+ * Decrypt the symmetrically encrypted content block of the PKCS#7 message.
+ *
+ * Decryption is skipped in case the PKCS#7 message object already has a
+ * (decrypted) content attribute.  The algorithm, key and cipher parameters
+ * (probably the iv) are taken from the encryptedContent attribute of the
+ * message object.
+ *
+ * @param The PKCS#7 message object.
+ */
+var _decryptContent = function (msg) {
+  if(msg.encryptedContent.key === undefined) {
+    throw new Error('Symmetric key not available.');
+  }
+
+  if(msg.content === undefined) {
+    var ciph;
+
+    switch(msg.encryptedContent.algorithm) {
+      case forge.pki.oids['aes128-CBC']:
+      case forge.pki.oids['aes192-CBC']:
+      case forge.pki.oids['aes256-CBC']:
+        ciph = forge.aes.createDecryptionCipher(msg.encryptedContent.key);
+        break;
+
+      case forge.pki.oids['desCBC']:
+      case forge.pki.oids['des-EDE3-CBC']:
+        ciph = forge.des.createDecryptionCipher(msg.encryptedContent.key);
+        break;
+
+      default:
+        throw new Error('Unsupported symmetric cipher, OID ' +
+          msg.encryptedContent.algorithm);
+    }
+    ciph.start(msg.encryptedContent.parameter);
+    ciph.update(msg.encryptedContent.content);
+
+    if(!ciph.finish()) {
+      throw new Error('Symmetric decryption failed.');
+    }
+
+    msg.content = ciph.output;
+  }
+};
+
+p7.createSignedData = function() {
+  var msg = null;
+  msg = {
+    type: forge.pki.oids.signedData,
+    version: 1,
+    certificates: [],
+    crls: [],
+    // populated during sign()
+    digestAlgorithmIdentifiers: [],
+    contentInfo: null,
+    signerInfos: [],
+
+    fromAsn1: function(obj) {
+      // validate SignedData content block and capture data.
+      _fromAsn1(msg, obj, p7.asn1.signedDataValidator);
+      msg.certificates = [];
+      msg.crls = [];
+      msg.digestAlgorithmIdentifiers = [];
+      msg.contentInfo = null;
+      msg.signerInfos = [];
+
+      var certs = msg.rawCapture.certificates.value;
+      for(var i = 0; i < certs.length; ++i) {
+        msg.certificates.push(forge.pki.certificateFromAsn1(certs[i]));
+      }
+
+      // TODO: parse crls
+    },
+
+    toAsn1: function() {
+      // TODO: add support for more data types here
+      if('content' in msg) {
+        throw new Error('Signing PKCS#7 content not yet implemented.');
+      }
+
+      // degenerate case with no content
+      if(!msg.contentInfo) {
+        msg.sign();
+      }
+
+      var certs = [];
+      for(var i = 0; i < msg.certificates.length; ++i) {
+        certs.push(forge.pki.certificateToAsn1(msg.certificates[0]));
+      }
+
+      var crls = [];
+      // TODO: implement CRLs
+
+      // ContentInfo
+      return asn1.create(
+        asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+          // ContentType
+          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+            asn1.oidToDer(msg.type).getBytes()),
+          // [0] SignedData
+          asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
+            asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+              // Version
+              asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+                asn1.integerToDer(msg.version).getBytes()),
+              // DigestAlgorithmIdentifiers
+              asn1.create(
+                asn1.Class.UNIVERSAL, asn1.Type.SET, true,
+                msg.digestAlgorithmIdentifiers),
+              // ContentInfo
+              msg.contentInfo,
+              // [0] IMPLICIT ExtendedCertificatesAndCertificates
+              asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, certs),
+              // [1] IMPLICIT CertificateRevocationLists
+              asn1.create(asn1.Class.CONTEXT_SPECIFIC, 1, true, crls),
+              // SignerInfos
+              asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true,
+                msg.signerInfos)
+            ])
+          ])
+      ]);
+    },
+
+    /**
+     * Signs the content.
+     *
+     * @param signer the signer (or array of signers) to sign as, for each:
+     *          key the private key to sign with.
+     *          [md] the message digest to use, defaults to sha-1.
+     */
+    sign: function(signer) {
+      if('content' in msg) {
+        throw new Error('PKCS#7 signing not yet implemented.');
+      }
+
+      if(typeof msg.content !== 'object') {
+        // use Data ContentInfo
+        msg.contentInfo = asn1.create(
+          asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+            // ContentType
+            asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+              asn1.oidToDer(forge.pki.oids.data).getBytes())
+          ]);
+
+        // add actual content, if present
+        if('content' in msg) {
+          msg.contentInfo.value.push(
+            // [0] EXPLICIT content
+            asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
+              asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
+                msg.content)
+            ]));
+        }
+      }
+
+      // TODO: generate digest algorithm identifiers
+
+      // TODO: generate signerInfos
+    },
+
+    verify: function() {
+      throw new Error('PKCS#7 signature verification not yet implemented.');
+    },
+
+    /**
+     * Add a certificate.
+     *
+     * @param cert the certificate to add.
+     */
+    addCertificate: function(cert) {
+      // convert from PEM
+      if(typeof cert === 'string') {
+        cert = forge.pki.certificateFromPem(cert);
+      }
+      msg.certificates.push(cert);
+    },
+
+    /**
+     * Add a certificate revokation list.
+     *
+     * @param crl the certificate revokation list to add.
+     */
+    addCertificateRevokationList: function(crl) {
+      throw new Error('PKCS#7 CRL support not yet implemented.');
+    }
+  };
+  return msg;
+};
+
+/**
+ * Creates an empty PKCS#7 message of type EncryptedData.
+ *
+ * @return the message.
+ */
+p7.createEncryptedData = function() {
+  var msg = null;
+  msg = {
+    type: forge.pki.oids.encryptedData,
+    version: 0,
+    encryptedContent: {
+      algorithm: forge.pki.oids['aes256-CBC']
+    },
+
+    /**
+     * Reads an EncryptedData content block (in ASN.1 format)
+     *
+     * @param obj The ASN.1 representation of the EncryptedData content block
+     */
+    fromAsn1: function(obj) {
+      // Validate EncryptedData content block and capture data.
+      _fromAsn1(msg, obj, p7.asn1.encryptedDataValidator);
+    },
+
+    /**
+     * Decrypt encrypted content
+     *
+     * @param key The (symmetric) key as a byte buffer
+     */
+    decrypt: function(key) {
+      if(key !== undefined) {
+        msg.encryptedContent.key = key;
+      }
+      _decryptContent(msg);
+    }
+  };
+  return msg;
+};
+
+/**
+ * Creates an empty PKCS#7 message of type EnvelopedData.
+ *
+ * @return the message.
+ */
+p7.createEnvelopedData = function() {
+  var msg = null;
+  msg = {
+    type: forge.pki.oids.envelopedData,
+    version: 0,
+    recipients: [],
+    encryptedContent: {
+      algorithm: forge.pki.oids['aes256-CBC']
+    },
+
+    /**
+     * Reads an EnvelopedData content block (in ASN.1 format)
+     *
+     * @param obj the ASN.1 representation of the EnvelopedData content block.
+     */
+    fromAsn1: function(obj) {
+      // validate EnvelopedData content block and capture data
+      var capture = _fromAsn1(msg, obj, p7.asn1.envelopedDataValidator);
+      msg.recipients = _recipientInfosFromAsn1(capture.recipientInfos.value);
+    },
+
+    toAsn1: function() {
+      // ContentInfo
+      return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+        // ContentType
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+          asn1.oidToDer(msg.type).getBytes()),
+        // [0] EnvelopedData
+        asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
+          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+            // Version
+            asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+              asn1.integerToDer(msg.version).getBytes()),
+            // RecipientInfos
+            asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true,
+              _recipientInfosToAsn1(msg.recipients)),
+            // EncryptedContentInfo
+            asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true,
+              _encryptedContentToAsn1(msg.encryptedContent))
+          ])
+        ])
+      ]);
+    },
+
+    /**
+     * Find recipient by X.509 certificate's issuer.
+     *
+     * @param cert the certificate with the issuer to look for.
+     *
+     * @return the recipient object.
+     */
+    findRecipient: function(cert) {
+      var sAttr = cert.issuer.attributes;
+
+      for(var i = 0; i < msg.recipients.length; ++i) {
+        var r = msg.recipients[i];
+        var rAttr = r.issuer;
+
+        if(r.serialNumber !== cert.serialNumber) {
+          continue;
+        }
+
+        if(rAttr.length !== sAttr.length) {
+          continue;
+        }
+
+        var match = true;
+        for(var j = 0; j < sAttr.length; ++j) {
+          if(rAttr[j].type !== sAttr[j].type ||
+            rAttr[j].value !== sAttr[j].value) {
+            match = false;
+            break;
+          }
+        }
+
+        if(match) {
+          return r;
+        }
+      }
+
+      return null;
+    },
+
+    /**
+     * Decrypt enveloped content
+     *
+     * @param recipient The recipient object related to the private key
+     * @param privKey The (RSA) private key object
+     */
+    decrypt: function(recipient, privKey) {
+      if(msg.encryptedContent.key === undefined && recipient !== undefined &&
+        privKey !== undefined) {
+        switch(recipient.encryptedContent.algorithm) {
+          case forge.pki.oids.rsaEncryption:
+          case forge.pki.oids.desCBC:
+            var key = privKey.decrypt(recipient.encryptedContent.content);
+            msg.encryptedContent.key = forge.util.createBuffer(key);
+            break;
+
+          default:
+            throw new Error('Unsupported asymmetric cipher, ' +
+              'OID ' + recipient.encryptedContent.algorithm);
+        }
+      }
+
+      _decryptContent(msg);
+    },
+
+    /**
+     * Add (another) entity to list of recipients.
+     *
+     * @param cert The certificate of the entity to add.
+     */
+    addRecipient: function(cert) {
+      msg.recipients.push({
+        version: 0,
+        issuer: cert.issuer.attributes,
+        serialNumber: cert.serialNumber,
+        encryptedContent: {
+          // We simply assume rsaEncryption here, since forge.pki only
+          // supports RSA so far.  If the PKI module supports other
+          // ciphers one day, we need to modify this one as well.
+          algorithm: forge.pki.oids.rsaEncryption,
+          key: cert.publicKey
+        }
+      });
+    },
+
+    /**
+     * Encrypt enveloped content.
+     *
+     * This function supports two optional arguments, cipher and key, which
+     * can be used to influence symmetric encryption.  Unless cipher is
+     * provided, the cipher specified in encryptedContent.algorithm is used
+     * (defaults to AES-256-CBC).  If no key is provided, encryptedContent.key
+     * is (re-)used.  If that one's not set, a random key will be generated
+     * automatically.
+     *
+     * @param [key] The key to be used for symmetric encryption.
+     * @param [cipher] The OID of the symmetric cipher to use.
+     */
+    encrypt: function(key, cipher) {
+      // Part 1: Symmetric encryption
+      if(msg.encryptedContent.content === undefined) {
+        cipher = cipher || msg.encryptedContent.algorithm;
+        key = key || msg.encryptedContent.key;
+
+        var keyLen, ivLen, ciphFn;
+        switch(cipher) {
+          case forge.pki.oids['aes128-CBC']:
+            keyLen = 16;
+            ivLen = 16;
+            ciphFn = forge.aes.createEncryptionCipher;
+            break;
+
+          case forge.pki.oids['aes192-CBC']:
+            keyLen = 24;
+            ivLen = 16;
+            ciphFn = forge.aes.createEncryptionCipher;
+            break;
+
+          case forge.pki.oids['aes256-CBC']:
+            keyLen = 32;
+            ivLen = 16;
+            ciphFn = forge.aes.createEncryptionCipher;
+            break;
+
+          case forge.pki.oids['des-EDE3-CBC']:
+            keyLen = 24;
+            ivLen = 8;
+            ciphFn = forge.des.createEncryptionCipher;
+            break;
+
+          default:
+            throw new Error('Unsupported symmetric cipher, OID ' + cipher);
+        }
+
+        if(key === undefined) {
+          key = forge.util.createBuffer(forge.random.getBytes(keyLen));
+        } else if(key.length() != keyLen) {
+          throw new Error('Symmetric key has wrong length; ' +
+            'got ' + key.length() + ' bytes, expected ' + keyLen + '.');
+        }
+
+        // Keep a copy of the key & IV in the object, so the caller can
+        // use it for whatever reason.
+        msg.encryptedContent.algorithm = cipher;
+        msg.encryptedContent.key = key;
+        msg.encryptedContent.parameter = forge.util.createBuffer(
+          forge.random.getBytes(ivLen));
+
+        var ciph = ciphFn(key);
+        ciph.start(msg.encryptedContent.parameter.copy());
+        ciph.update(msg.content);
+
+        // The finish function does PKCS#7 padding by default, therefore
+        // no action required by us.
+        if(!ciph.finish()) {
+          throw new Error('Symmetric encryption failed.');
+        }
+
+        msg.encryptedContent.content = ciph.output;
+      }
+
+      // Part 2: asymmetric encryption for each recipient
+      for(var i = 0; i < msg.recipients.length; i ++) {
+        var recipient = msg.recipients[i];
+
+        // Nothing to do, encryption already done.
+        if(recipient.encryptedContent.content !== undefined) {
+          continue;
+        }
+
+        switch(recipient.encryptedContent.algorithm) {
+          case forge.pki.oids.rsaEncryption:
+            recipient.encryptedContent.content =
+              recipient.encryptedContent.key.encrypt(
+                msg.encryptedContent.key.data);
+            break;
+
+          default:
+            throw new Error('Unsupported asymmetric cipher, OID ' +
+              recipient.encryptedContent.algorithm);
+        }
+      }
+    }
+  };
+  return msg;
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'pkcs7';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define([
+  'require',
+  'module',
+  './aes',
+  './asn1',
+  './des',
+  './oids',
+  './pem',
+  './pkcs7asn1',
+  './random',
+  './util',
+  './x509'
+], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/pkcs7asn1.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/pkcs7asn1.js
new file mode 100644
index 0000000..f7c4df6
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/pkcs7asn1.js
@@ -0,0 +1,399 @@
+/**
+ * Javascript implementation of PKCS#7 v1.5.  Currently only certain parts of
+ * PKCS#7 are implemented, especially the enveloped-data content type.
+ *
+ * @author Stefan Siegl
+ *
+ * Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
+ *
+ * The ASN.1 representation of PKCS#7 is as follows
+ * (see RFC #2315 for details, http://www.ietf.org/rfc/rfc2315.txt):
+ *
+ * A PKCS#7 message consists of a ContentInfo on root level, which may
+ * contain any number of further ContentInfo nested into it.
+ *
+ * ContentInfo ::= SEQUENCE {
+ *    contentType                ContentType,
+ *    content               [0]  EXPLICIT ANY DEFINED BY contentType OPTIONAL
+ * }
+ *
+ * ContentType ::= OBJECT IDENTIFIER
+ *
+ * EnvelopedData ::= SEQUENCE {
+ *    version                    Version,
+ *    recipientInfos             RecipientInfos,
+ *    encryptedContentInfo       EncryptedContentInfo
+ * }
+ *
+ * EncryptedData ::= SEQUENCE {
+ *    version                    Version,
+ *    encryptedContentInfo       EncryptedContentInfo
+ * }
+ *
+ * Version ::= INTEGER
+ *
+ * RecipientInfos ::= SET OF RecipientInfo
+ *
+ * EncryptedContentInfo ::= SEQUENCE {
+ *   contentType                 ContentType,
+ *   contentEncryptionAlgorithm  ContentEncryptionAlgorithmIdentifier,
+ *   encryptedContent       [0]  IMPLICIT EncryptedContent OPTIONAL
+ * }
+ *
+ * ContentEncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
+ *
+ * The AlgorithmIdentifier contains an Object Identifier (OID) and parameters
+ * for the algorithm, if any. In the case of AES and DES3, there is only one,
+ * the IV.
+ *
+ * AlgorithmIdentifer ::= SEQUENCE {
+ *    algorithm OBJECT IDENTIFIER,
+ *    parameters ANY DEFINED BY algorithm OPTIONAL
+ * }
+ *
+ * EncryptedContent ::= OCTET STRING
+ *
+ * RecipientInfo ::= SEQUENCE {
+ *   version                     Version,
+ *   issuerAndSerialNumber       IssuerAndSerialNumber,
+ *   keyEncryptionAlgorithm      KeyEncryptionAlgorithmIdentifier,
+ *   encryptedKey                EncryptedKey
+ * }
+ *
+ * IssuerAndSerialNumber ::= SEQUENCE {
+ *   issuer                      Name,
+ *   serialNumber                CertificateSerialNumber
+ * }
+ *
+ * CertificateSerialNumber ::= INTEGER
+ *
+ * KeyEncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
+ *
+ * EncryptedKey ::= OCTET STRING
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+// shortcut for ASN.1 API
+var asn1 = forge.asn1;
+
+// shortcut for PKCS#7 API
+var p7v = forge.pkcs7asn1 = forge.pkcs7asn1 || {};
+forge.pkcs7 = forge.pkcs7 || {};
+forge.pkcs7.asn1 = p7v;
+
+var contentInfoValidator = {
+  name: 'ContentInfo',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'ContentInfo.ContentType',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.OID,
+    constructed: false,
+    capture: 'contentType'
+  }, {
+    name: 'ContentInfo.content',
+    tagClass: asn1.Class.CONTEXT_SPECIFIC,
+    type: 0,
+    constructed: true,
+    optional: true,
+    captureAsn1: 'content'
+  }]
+};
+p7v.contentInfoValidator = contentInfoValidator;
+
+var encryptedContentInfoValidator = {
+  name: 'EncryptedContentInfo',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'EncryptedContentInfo.contentType',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.OID,
+    constructed: false,
+    capture: 'contentType'
+  }, {
+    name: 'EncryptedContentInfo.contentEncryptionAlgorithm',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true,
+    value: [{
+      name: 'EncryptedContentInfo.contentEncryptionAlgorithm.algorithm',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.OID,
+      constructed: false,
+      capture: 'encAlgorithm'
+    }, {
+      name: 'EncryptedContentInfo.contentEncryptionAlgorithm.parameter',
+      tagClass: asn1.Class.UNIVERSAL,
+      captureAsn1: 'encParameter'
+    }]
+  }, {
+    name: 'EncryptedContentInfo.encryptedContent',
+    tagClass: asn1.Class.CONTEXT_SPECIFIC,
+    type: 0,
+    /* The PKCS#7 structure output by OpenSSL somewhat differs from what
+     * other implementations do generate.
+     *
+     * OpenSSL generates a structure like this:
+     * SEQUENCE {
+     *    ...
+     *    [0]
+     *       26 DA 67 D2 17 9C 45 3C B1 2A A8 59 2F 29 33 38
+     *       C3 C3 DF 86 71 74 7A 19 9F 40 D0 29 BE 85 90 45
+     *       ...
+     * }
+     *
+     * Whereas other implementations (and this PKCS#7 module) generate:
+     * SEQUENCE {
+     *    ...
+     *    [0] {
+     *       OCTET STRING
+     *          26 DA 67 D2 17 9C 45 3C B1 2A A8 59 2F 29 33 38
+     *          C3 C3 DF 86 71 74 7A 19 9F 40 D0 29 BE 85 90 45
+     *          ...
+     *    }
+     * }
+     *
+     * In order to support both, we just capture the context specific
+     * field here.  The OCTET STRING bit is removed below.
+     */
+    capture: 'encryptedContent',
+    captureAsn1: 'encryptedContentAsn1'
+  }]
+};
+
+p7v.envelopedDataValidator = {
+  name: 'EnvelopedData',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'EnvelopedData.Version',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'version'
+  }, {
+    name: 'EnvelopedData.RecipientInfos',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SET,
+    constructed: true,
+    captureAsn1: 'recipientInfos'
+  }].concat(encryptedContentInfoValidator)
+};
+
+p7v.encryptedDataValidator = {
+  name: 'EncryptedData',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'EncryptedData.Version',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'version'
+  }].concat(encryptedContentInfoValidator)
+};
+
+var signerValidator = {
+  name: 'SignerInfo',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'SignerInfo.Version',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false
+  }, {
+    name: 'SignerInfo.IssuerAndSerialNumber',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true
+  }, {
+    name: 'SignerInfo.DigestAlgorithm',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true
+  }, {
+    name: 'SignerInfo.AuthenticatedAttributes',
+    tagClass: asn1.Class.CONTEXT_SPECIFIC,
+    type: 0,
+    constructed: true,
+    optional: true,
+    capture: 'authenticatedAttributes'
+  }, {
+    name: 'SignerInfo.DigestEncryptionAlgorithm',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true
+  }, {
+    name: 'SignerInfo.EncryptedDigest',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.OCTETSTRING,
+    constructed: false,
+    capture: 'signature'
+  }, {
+    name: 'SignerInfo.UnauthenticatedAttributes',
+    tagClass: asn1.Class.CONTEXT_SPECIFIC,
+    type: 1,
+    constructed: true,
+    optional: true
+  }]
+};
+
+p7v.signedDataValidator = {
+  name: 'SignedData',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'SignedData.Version',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'version'
+  }, {
+    name: 'SignedData.DigestAlgorithms',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SET,
+    constructed: true,
+    captureAsn1: 'digestAlgorithms'
+  },
+  contentInfoValidator,
+  {
+    name: 'SignedData.Certificates',
+    tagClass: asn1.Class.CONTEXT_SPECIFIC,
+    type: 0,
+    optional: true,
+    captureAsn1: 'certificates'
+  }, {
+    name: 'SignedData.CertificateRevocationLists',
+    tagClass: asn1.Class.CONTEXT_SPECIFIC,
+    type: 1,
+    optional: true,
+    captureAsn1: 'crls'
+  }, {
+    name: 'SignedData.SignerInfos',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SET,
+    capture: 'signerInfos',
+    optional: true,
+    value: [signerValidator]
+  }]
+};
+
+p7v.recipientInfoValidator = {
+  name: 'RecipientInfo',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'RecipientInfo.version',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'version'
+  }, {
+    name: 'RecipientInfo.issuerAndSerial',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true,
+    value: [{
+      name: 'RecipientInfo.issuerAndSerial.issuer',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.SEQUENCE,
+      constructed: true,
+      captureAsn1: 'issuer'
+    }, {
+      name: 'RecipientInfo.issuerAndSerial.serialNumber',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.INTEGER,
+      constructed: false,
+      capture: 'serial'
+    }]
+  }, {
+    name: 'RecipientInfo.keyEncryptionAlgorithm',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true,
+    value: [{
+      name: 'RecipientInfo.keyEncryptionAlgorithm.algorithm',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.OID,
+      constructed: false,
+      capture: 'encAlgorithm'
+    }, {
+      name: 'RecipientInfo.keyEncryptionAlgorithm.parameter',
+      tagClass: asn1.Class.UNIVERSAL,
+      constructed: false,
+      captureAsn1: 'encParameter'
+    }]
+  }, {
+    name: 'RecipientInfo.encryptedKey',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.OCTETSTRING,
+    constructed: false,
+    capture: 'encKey'
+  }]
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'pkcs7asn1';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './asn1', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/pki.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/pki.js
new file mode 100644
index 0000000..3df7805
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/pki.js
@@ -0,0 +1,161 @@
+/**
+ * Javascript implementation of a basic Public Key Infrastructure, including
+ * support for RSA public and private keys.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2013 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+// shortcut for asn.1 API
+var asn1 = forge.asn1;
+
+/* Public Key Infrastructure (PKI) implementation. */
+var pki = forge.pki = forge.pki || {};
+
+/**
+ * NOTE: THIS METHOD IS DEPRECATED. Use pem.decode() instead.
+ *
+ * Converts PEM-formatted data to DER.
+ *
+ * @param pem the PEM-formatted data.
+ *
+ * @return the DER-formatted data.
+ */
+pki.pemToDer = function(pem) {
+  var msg = forge.pem.decode(pem)[0];
+  if(msg.procType && msg.procType.type === 'ENCRYPTED') {
+    throw new Error('Could not convert PEM to DER; PEM is encrypted.');
+  }
+  return forge.util.createBuffer(msg.body);
+};
+
+/**
+ * Converts an RSA private key from PEM format.
+ *
+ * @param pem the PEM-formatted private key.
+ *
+ * @return the private key.
+ */
+pki.privateKeyFromPem = function(pem) {
+  var msg = forge.pem.decode(pem)[0];
+
+  if(msg.type !== 'PRIVATE KEY' && msg.type !== 'RSA PRIVATE KEY') {
+    var error = new Error('Could not convert private key from PEM; PEM ' +
+      'header type is not "PRIVATE KEY" or "RSA PRIVATE KEY".');
+    error.headerType = msg.type;
+    throw error;
+  }
+  if(msg.procType && msg.procType.type === 'ENCRYPTED') {
+    throw new Error('Could not convert private key from PEM; PEM is encrypted.');
+  }
+
+  // convert DER to ASN.1 object
+  var obj = asn1.fromDer(msg.body);
+
+  return pki.privateKeyFromAsn1(obj);
+};
+
+/**
+ * Converts an RSA private key to PEM format.
+ *
+ * @param key the private key.
+ * @param maxline the maximum characters per line, defaults to 64.
+ *
+ * @return the PEM-formatted private key.
+ */
+pki.privateKeyToPem = function(key, maxline) {
+  // convert to ASN.1, then DER, then PEM-encode
+  var msg = {
+    type: 'RSA PRIVATE KEY',
+    body: asn1.toDer(pki.privateKeyToAsn1(key)).getBytes()
+  };
+  return forge.pem.encode(msg, {maxline: maxline});
+};
+
+/**
+ * Converts a PrivateKeyInfo to PEM format.
+ *
+ * @param pki the PrivateKeyInfo.
+ * @param maxline the maximum characters per line, defaults to 64.
+ *
+ * @return the PEM-formatted private key.
+ */
+pki.privateKeyInfoToPem = function(pki, maxline) {
+  // convert to DER, then PEM-encode
+  var msg = {
+    type: 'PRIVATE KEY',
+    body: asn1.toDer(pki).getBytes()
+  };
+  return forge.pem.encode(msg, {maxline: maxline});
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'pki';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define([
+  'require',
+  'module',
+  './asn1',
+  './oids',
+  './pbe',
+  './pem',
+  './pbkdf2',
+  './pkcs12',
+  './pss',
+  './rsa',
+  './util',
+  './x509'
+], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/prime.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/prime.js
new file mode 100644
index 0000000..2857c36
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/prime.js
@@ -0,0 +1,337 @@
+/**
+ * Prime number generation API.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+// forge.prime already defined
+if(forge.prime) {
+  return;
+}
+
+/* PRIME API */
+var prime = forge.prime = forge.prime || {};
+
+var BigInteger = forge.jsbn.BigInteger;
+
+// primes are 30k+i for i = 1, 7, 11, 13, 17, 19, 23, 29
+var GCD_30_DELTA = [6, 4, 2, 4, 2, 4, 6, 2];
+var THIRTY = new BigInteger(null);
+THIRTY.fromInt(30);
+var op_or = function(x, y) {return x|y;};
+
+/**
+ * Generates a random probable prime with the given number of bits.
+ *
+ * Alternative algorithms can be specified by name as a string or as an
+ * object with custom options like so:
+ *
+ * {
+ *   name: 'PRIMEINC',
+ *   options: {
+ *     maxBlockTime: <the maximum amount of time to block the main
+ *       thread before allowing I/O other JS to run>,
+ *     millerRabinTests: <the number of miller-rabin tests to run>,
+ *     workerScript: <the worker script URL>,
+ *     workers: <the number of web workers (if supported) to use,
+ *       -1 to use estimated cores minus one>.
+ *     workLoad: the size of the work load, ie: number of possible prime
+ *       numbers for each web worker to check per work assignment,
+ *       (default: 100).
+ *   }
+ * }
+ *
+ * @param bits the number of bits for the prime number.
+ * @param options the options to use.
+ *          [algorithm] the algorithm to use (default: 'PRIMEINC').
+ *          [prng] a custom crypto-secure pseudo-random number generator to use,
+ *            that must define "getBytesSync".
+ *
+ * @return callback(err, num) called once the operation completes.
+ */
+prime.generateProbablePrime = function(bits, options, callback) {
+  if(typeof options === 'function') {
+    callback = options;
+    options = {};
+  }
+  options = options || {};
+
+  // default to PRIMEINC algorithm
+  var algorithm = options.algorithm || 'PRIMEINC';
+  if(typeof algorithm === 'string') {
+    algorithm = {name: algorithm};
+  }
+  algorithm.options = algorithm.options || {};
+
+  // create prng with api that matches BigInteger secure random
+  var prng = options.prng || forge.random;
+  var rng = {
+    // x is an array to fill with bytes
+    nextBytes: function(x) {
+      var b = prng.getBytesSync(x.length);
+      for(var i = 0; i < x.length; ++i) {
+        x[i] = b.charCodeAt(i);
+      }
+    }
+  };
+
+  if(algorithm.name === 'PRIMEINC') {
+    return primeincFindPrime(bits, rng, algorithm.options, callback);
+  }
+
+  throw new Error('Invalid prime generation algorithm: ' + algorithm.name);
+};
+
+function primeincFindPrime(bits, rng, options, callback) {
+  if('workers' in options) {
+    return primeincFindPrimeWithWorkers(bits, rng, options, callback);
+  }
+  return primeincFindPrimeWithoutWorkers(bits, rng, options, callback);
+}
+
+function primeincFindPrimeWithoutWorkers(bits, rng, options, callback) {
+  // initialize random number
+  var num = generateRandom(bits, rng);
+
+  /* Note: All primes are of the form 30k+i for i < 30 and gcd(30, i)=1. The
+  number we are given is always aligned at 30k + 1. Each time the number is
+  determined not to be prime we add to get to the next 'i', eg: if the number
+  was at 30k + 1 we add 6. */
+  var deltaIdx = 0;
+
+  // get required number of MR tests
+  var mrTests = getMillerRabinTests(num.bitLength());
+  if('millerRabinTests' in options) {
+    mrTests = options.millerRabinTests;
+  }
+
+  // find prime nearest to 'num' for maxBlockTime ms
+  // 10 ms gives 5ms of leeway for other calculations before dropping
+  // below 60fps (1000/60 == 16.67), but in reality, the number will
+  // likely be higher due to an 'atomic' big int modPow
+  var maxBlockTime = 10;
+  if('maxBlockTime' in options) {
+    maxBlockTime = options.maxBlockTime;
+  }
+  var start = +new Date();
+  do {
+    // overflow, regenerate random number
+    if(num.bitLength() > bits) {
+      num = generateRandom(bits, rng);
+    }
+    // do primality test
+    if(num.isProbablePrime(mrTests)) {
+      return callback(null, num);
+    }
+    // get next potential prime
+    num.dAddOffset(GCD_30_DELTA[deltaIdx++ % 8], 0);
+  } while(maxBlockTime < 0 || (+new Date() - start < maxBlockTime));
+
+  // keep trying (setImmediate would be better here)
+  forge.util.setImmediate(function() {
+    primeincFindPrimeWithoutWorkers(bits, rng, options, callback);
+  });
+}
+
+function primeincFindPrimeWithWorkers(bits, rng, options, callback) {
+  // web workers unavailable
+  if(typeof Worker === 'undefined') {
+    return primeincFindPrimeWithoutWorkers(bits, rng, options, callback);
+  }
+
+  // initialize random number
+  var num = generateRandom(bits, rng);
+
+  // use web workers to generate keys
+  var numWorkers = options.workers;
+  var workLoad = options.workLoad || 100;
+  var range = workLoad * 30 / 8;
+  var workerScript = options.workerScript || 'forge/prime.worker.js';
+  if(numWorkers === -1) {
+    return forge.util.estimateCores(function(err, cores) {
+      if(err) {
+        // default to 2
+        cores = 2;
+      }
+      numWorkers = cores - 1;
+      generate();
+    });
+  }
+  generate();
+
+  function generate() {
+    // require at least 1 worker
+    numWorkers = Math.max(1, numWorkers);
+
+    // TODO: consider optimizing by starting workers outside getPrime() ...
+    // note that in order to clean up they will have to be made internally
+    // asynchronous which may actually be slower
+
+    // start workers immediately
+    var workers = [];
+    for(var i = 0; i < numWorkers; ++i) {
+      // FIXME: fix path or use blob URLs
+      workers[i] = new Worker(workerScript);
+    }
+    var running = numWorkers;
+
+    // listen for requests from workers and assign ranges to find prime
+    for(var i = 0; i < numWorkers; ++i) {
+      workers[i].addEventListener('message', workerMessage);
+    }
+
+    /* Note: The distribution of random numbers is unknown. Therefore, each
+    web worker is continuously allocated a range of numbers to check for a
+    random number until one is found.
+
+    Every 30 numbers will be checked just 8 times, because prime numbers
+    have the form:
+
+    30k+i, for i < 30 and gcd(30, i)=1 (there are 8 values of i for this)
+
+    Therefore, if we want a web worker to run N checks before asking for
+    a new range of numbers, each range must contain N*30/8 numbers.
+
+    For 100 checks (workLoad), this is a range of 375. */
+
+    var found = false;
+    function workerMessage(e) {
+      // ignore message, prime already found
+      if(found) {
+        return;
+      }
+
+      --running;
+      var data = e.data;
+      if(data.found) {
+        // terminate all workers
+        for(var i = 0; i < workers.length; ++i) {
+          workers[i].terminate();
+        }
+        found = true;
+        return callback(null, new BigInteger(data.prime, 16));
+      }
+
+      // overflow, regenerate random number
+      if(num.bitLength() > bits) {
+        num = generateRandom(bits, rng);
+      }
+
+      // assign new range to check
+      var hex = num.toString(16);
+
+      // start prime search
+      e.target.postMessage({
+        hex: hex,
+        workLoad: workLoad
+      });
+
+      num.dAddOffset(range, 0);
+    }
+  }
+}
+
+/**
+ * Generates a random number using the given number of bits and RNG.
+ *
+ * @param bits the number of bits for the number.
+ * @param rng the random number generator to use.
+ *
+ * @return the random number.
+ */
+function generateRandom(bits, rng) {
+  var num = new BigInteger(bits, rng);
+  // force MSB set
+  var bits1 = bits - 1;
+  if(!num.testBit(bits1)) {
+    num.bitwiseTo(BigInteger.ONE.shiftLeft(bits1), op_or, num);
+  }
+  // align number on 30k+1 boundary
+  num.dAddOffset(31 - num.mod(THIRTY).byteValue(), 0);
+  return num;
+}
+
+/**
+ * Returns the required number of Miller-Rabin tests to generate a
+ * prime with an error probability of (1/2)^80.
+ *
+ * See Handbook of Applied Cryptography Chapter 4, Table 4.4.
+ *
+ * @param bits the bit size.
+ *
+ * @return the required number of iterations.
+ */
+function getMillerRabinTests(bits) {
+  if(bits <= 100) return 27;
+  if(bits <= 150) return 18;
+  if(bits <= 200) return 15;
+  if(bits <= 250) return 12;
+  if(bits <= 300) return 9;
+  if(bits <= 350) return 8;
+  if(bits <= 400) return 7;
+  if(bits <= 500) return 6;
+  if(bits <= 600) return 5;
+  if(bits <= 800) return 4;
+  if(bits <= 1250) return 3;
+  return 2;
+}
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'prime';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './util', './jsbn', './random'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/prime.worker.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/prime.worker.js
new file mode 100644
index 0000000..5fdaa7f
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/prime.worker.js
@@ -0,0 +1,165 @@
+/**
+ * RSA Key Generation Worker.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2013 Digital Bazaar, Inc.
+ */
+importScripts('jsbn.js');
+
+// prime constants
+var LOW_PRIMES = [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,587,593,599,601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691,701,709,719,727,733,739,743,751,757,761,769,773,787,797,809,811,821,823,827,829,839,853,857,859,863,877,881,883,887,907,911,919,929,937,941,947,953,967,971,977,983,991,997];
+var LP_LIMIT = (1 << 26) / LOW_PRIMES[LOW_PRIMES.length - 1];
+
+var BigInteger = forge.jsbn.BigInteger;
+var BIG_TWO = new BigInteger(null);
+BIG_TWO.fromInt(2);
+
+self.addEventListener('message', function(e) {
+  var result = findPrime(e.data);
+  self.postMessage(result);
+});
+
+// start receiving ranges to check
+self.postMessage({found: false});
+
+// primes are 30k+i for i = 1, 7, 11, 13, 17, 19, 23, 29
+var GCD_30_DELTA = [6, 4, 2, 4, 2, 4, 6, 2];
+
+function findPrime(data) {
+  // TODO: abstract based on data.algorithm (PRIMEINC vs. others)
+
+  // create BigInteger from given random bytes
+  var num = new BigInteger(data.hex, 16);
+
+  /* Note: All primes are of the form 30k+i for i < 30 and gcd(30, i)=1. The
+    number we are given is always aligned at 30k + 1. Each time the number is
+    determined not to be prime we add to get to the next 'i', eg: if the number
+    was at 30k + 1 we add 6. */
+  var deltaIdx = 0;
+
+  // find nearest prime
+  var workLoad = data.workLoad;
+  for(var i = 0; i < workLoad; ++i) {
+    // do primality test
+    if(isProbablePrime(num)) {
+      return {found: true, prime: num.toString(16)};
+    }
+    // get next potential prime
+    num.dAddOffset(GCD_30_DELTA[deltaIdx++ % 8], 0);
+  }
+
+  return {found: false};
+}
+
+function isProbablePrime(n) {
+  // divide by low primes, ignore even checks, etc (n alread aligned properly)
+  var i = 1;
+  while(i < LOW_PRIMES.length) {
+    var m = LOW_PRIMES[i];
+    var j = i + 1;
+    while(j < LOW_PRIMES.length && m < LP_LIMIT) {
+      m *= LOW_PRIMES[j++];
+    }
+    m = n.modInt(m);
+    while(i < j) {
+      if(m % LOW_PRIMES[i++] === 0) {
+        return false;
+      }
+    }
+  }
+  return runMillerRabin(n);
+}
+
+// HAC 4.24, Miller-Rabin
+function runMillerRabin(n) {
+  // n1 = n - 1
+  var n1 = n.subtract(BigInteger.ONE);
+
+  // get s and d such that n1 = 2^s * d
+  var s = n1.getLowestSetBit();
+  if(s <= 0) {
+    return false;
+  }
+  var d = n1.shiftRight(s);
+
+  var k = _getMillerRabinTests(n.bitLength());
+  var prng = getPrng();
+  var a;
+  for(var i = 0; i < k; ++i) {
+    // select witness 'a' at random from between 1 and n - 1
+    do {
+      a = new BigInteger(n.bitLength(), prng);
+    } while(a.compareTo(BigInteger.ONE) <= 0 || a.compareTo(n1) >= 0);
+
+    /* See if 'a' is a composite witness. */
+
+    // x = a^d mod n
+    var x = a.modPow(d, n);
+
+    // probably prime
+    if(x.compareTo(BigInteger.ONE) === 0 || x.compareTo(n1) === 0) {
+      continue;
+    }
+
+    var j = s;
+    while(--j) {
+      // x = x^2 mod a
+      x = x.modPowInt(2, n);
+
+      // 'n' is composite because no previous x == -1 mod n
+      if(x.compareTo(BigInteger.ONE) === 0) {
+        return false;
+      }
+      // x == -1 mod n, so probably prime
+      if(x.compareTo(n1) === 0) {
+        break;
+      }
+    }
+
+    // 'x' is first_x^(n1/2) and is not +/- 1, so 'n' is not prime
+    if(j === 0) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+// get pseudo random number generator
+function getPrng() {
+  // create prng with api that matches BigInteger secure random
+  return {
+    // x is an array to fill with bytes
+    nextBytes: function(x) {
+      for(var i = 0; i < x.length; ++i) {
+        x[i] = Math.floor(Math.random() * 0xFF);
+      }
+    }
+  };
+}
+
+/**
+ * Returns the required number of Miller-Rabin tests to generate a
+ * prime with an error probability of (1/2)^80.
+ *
+ * See Handbook of Applied Cryptography Chapter 4, Table 4.4.
+ *
+ * @param bits the bit size.
+ *
+ * @return the required number of iterations.
+ */
+function _getMillerRabinTests(bits) {
+  if(bits <= 100) return 27;
+  if(bits <= 150) return 18;
+  if(bits <= 200) return 15;
+  if(bits <= 250) return 12;
+  if(bits <= 300) return 9;
+  if(bits <= 350) return 8;
+  if(bits <= 400) return 7;
+  if(bits <= 500) return 6;
+  if(bits <= 600) return 5;
+  if(bits <= 800) return 4;
+  if(bits <= 1250) return 3;
+  return 2;
+}
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/prng.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/prng.js
new file mode 100644
index 0000000..72b4594
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/prng.js
@@ -0,0 +1,458 @@
+/**
+ * A javascript implementation of a cryptographically-secure
+ * Pseudo Random Number Generator (PRNG). The Fortuna algorithm is followed
+ * here though the use of SHA-256 is not enforced; when generating an
+ * a PRNG context, the hashing algorithm and block cipher used for
+ * the generator are specified via a plugin.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+var _nodejs = (
+  typeof process !== 'undefined' && process.versions && process.versions.node);
+var _crypto = null;
+if(!forge.disableNativeCode && _nodejs && !process.versions['node-webkit']) {
+  _crypto = require('crypto');
+}
+
+/* PRNG API */
+var prng = forge.prng = forge.prng || {};
+
+/**
+ * Creates a new PRNG context.
+ *
+ * A PRNG plugin must be passed in that will provide:
+ *
+ * 1. A function that initializes the key and seed of a PRNG context. It
+ *   will be given a 16 byte key and a 16 byte seed. Any key expansion
+ *   or transformation of the seed from a byte string into an array of
+ *   integers (or similar) should be performed.
+ * 2. The cryptographic function used by the generator. It takes a key and
+ *   a seed.
+ * 3. A seed increment function. It takes the seed and returns seed + 1.
+ * 4. An api to create a message digest.
+ *
+ * For an example, see random.js.
+ *
+ * @param plugin the PRNG plugin to use.
+ */
+prng.create = function(plugin) {
+  var ctx = {
+    plugin: plugin,
+    key: null,
+    seed: null,
+    time: null,
+    // number of reseeds so far
+    reseeds: 0,
+    // amount of data generated so far
+    generated: 0
+  };
+
+  // create 32 entropy pools (each is a message digest)
+  var md = plugin.md;
+  var pools = new Array(32);
+  for(var i = 0; i < 32; ++i) {
+    pools[i] = md.create();
+  }
+  ctx.pools = pools;
+
+  // entropy pools are written to cyclically, starting at index 0
+  ctx.pool = 0;
+
+  /**
+   * Generates random bytes. The bytes may be generated synchronously or
+   * asynchronously. Web workers must use the asynchronous interface or
+   * else the behavior is undefined.
+   *
+   * @param count the number of random bytes to generate.
+   * @param [callback(err, bytes)] called once the operation completes.
+   *
+   * @return count random bytes as a string.
+   */
+  ctx.generate = function(count, callback) {
+    // do synchronously
+    if(!callback) {
+      return ctx.generateSync(count);
+    }
+
+    // simple generator using counter-based CBC
+    var cipher = ctx.plugin.cipher;
+    var increment = ctx.plugin.increment;
+    var formatKey = ctx.plugin.formatKey;
+    var formatSeed = ctx.plugin.formatSeed;
+    var b = forge.util.createBuffer();
+
+    // reset key for every request
+    ctx.key = null;
+
+    generate();
+
+    function generate(err) {
+      if(err) {
+        return callback(err);
+      }
+
+      // sufficient bytes generated
+      if(b.length() >= count) {
+        return callback(null, b.getBytes(count));
+      }
+
+      // if amount of data generated is greater than 1 MiB, trigger reseed
+      if(ctx.generated > 0xfffff) {
+        ctx.key = null;
+      }
+
+      if(ctx.key === null) {
+        // prevent stack overflow
+        return forge.util.nextTick(function() {
+          _reseed(generate);
+        });
+      }
+
+      // generate the random bytes
+      var bytes = cipher(ctx.key, ctx.seed);
+      ctx.generated += bytes.length;
+      b.putBytes(bytes);
+
+      // generate bytes for a new key and seed
+      ctx.key = formatKey(cipher(ctx.key, increment(ctx.seed)));
+      ctx.seed = formatSeed(cipher(ctx.key, ctx.seed));
+
+      forge.util.setImmediate(generate);
+    }
+  };
+
+  /**
+   * Generates random bytes synchronously.
+   *
+   * @param count the number of random bytes to generate.
+   *
+   * @return count random bytes as a string.
+   */
+  ctx.generateSync = function(count) {
+    // simple generator using counter-based CBC
+    var cipher = ctx.plugin.cipher;
+    var increment = ctx.plugin.increment;
+    var formatKey = ctx.plugin.formatKey;
+    var formatSeed = ctx.plugin.formatSeed;
+
+    // reset key for every request
+    ctx.key = null;
+
+    var b = forge.util.createBuffer();
+    while(b.length() < count) {
+      // if amount of data generated is greater than 1 MiB, trigger reseed
+      if(ctx.generated > 0xfffff) {
+        ctx.key = null;
+      }
+
+      if(ctx.key === null) {
+        _reseedSync();
+      }
+
+      // generate the random bytes
+      var bytes = cipher(ctx.key, ctx.seed);
+      ctx.generated += bytes.length;
+      b.putBytes(bytes);
+
+      // generate bytes for a new key and seed
+      ctx.key = formatKey(cipher(ctx.key, increment(ctx.seed)));
+      ctx.seed = formatSeed(cipher(ctx.key, ctx.seed));
+    }
+
+    return b.getBytes(count);
+  };
+
+  /**
+   * Private function that asynchronously reseeds a generator.
+   *
+   * @param callback(err) called once the operation completes.
+   */
+  function _reseed(callback) {
+    if(ctx.pools[0].messageLength >= 32) {
+      _seed();
+      return callback();
+    }
+    // not enough seed data...
+    var needed = (32 - ctx.pools[0].messageLength) << 5;
+    ctx.seedFile(needed, function(err, bytes) {
+      if(err) {
+        return callback(err);
+      }
+      ctx.collect(bytes);
+      _seed();
+      callback();
+    });
+  }
+
+  /**
+   * Private function that synchronously reseeds a generator.
+   */
+  function _reseedSync() {
+    if(ctx.pools[0].messageLength >= 32) {
+      return _seed();
+    }
+    // not enough seed data...
+    var needed = (32 - ctx.pools[0].messageLength) << 5;
+    ctx.collect(ctx.seedFileSync(needed));
+    _seed();
+  }
+
+  /**
+   * Private function that seeds a generator once enough bytes are available.
+   */
+  function _seed() {
+    // create a plugin-based message digest
+    var md = ctx.plugin.md.create();
+
+    // digest pool 0's entropy and restart it
+    md.update(ctx.pools[0].digest().getBytes());
+    ctx.pools[0].start();
+
+    // digest the entropy of other pools whose index k meet the
+    // condition '2^k mod n == 0' where n is the number of reseeds
+    var k = 1;
+    for(var i = 1; i < 32; ++i) {
+      // prevent signed numbers from being used
+      k = (k === 31) ? 0x80000000 : (k << 2);
+      if(k % ctx.reseeds === 0) {
+        md.update(ctx.pools[i].digest().getBytes());
+        ctx.pools[i].start();
+      }
+    }
+
+    // get digest for key bytes and iterate again for seed bytes
+    var keyBytes = md.digest().getBytes();
+    md.start();
+    md.update(keyBytes);
+    var seedBytes = md.digest().getBytes();
+
+    // update
+    ctx.key = ctx.plugin.formatKey(keyBytes);
+    ctx.seed = ctx.plugin.formatSeed(seedBytes);
+    ctx.reseeds = (ctx.reseeds === 0xffffffff) ? 0 : ctx.reseeds + 1;
+    ctx.generated = 0;
+  }
+
+  /**
+   * The built-in default seedFile. This seedFile is used when entropy
+   * is needed immediately.
+   *
+   * @param needed the number of bytes that are needed.
+   *
+   * @return the random bytes.
+   */
+  function defaultSeedFile(needed) {
+    // use window.crypto.getRandomValues strong source of entropy if available
+    var getRandomValues = null;
+    if(typeof window !== 'undefined') {
+      var _crypto = window.crypto || window.msCrypto;
+      if(_crypto && _crypto.getRandomValues) {
+        getRandomValues = function(arr) {
+          return _crypto.getRandomValues(arr);
+        };
+      }
+    }
+
+    var b = forge.util.createBuffer();
+    if(getRandomValues) {
+      while(b.length() < needed) {
+        // max byte length is 65536 before QuotaExceededError is thrown
+        // http://www.w3.org/TR/WebCryptoAPI/#RandomSource-method-getRandomValues
+        var count = Math.max(1, Math.min(needed - b.length(), 65536) / 4);
+        var entropy = new Uint32Array(Math.floor(count));
+        try {
+          getRandomValues(entropy);
+          for(var i = 0; i < entropy.length; ++i) {
+            b.putInt32(entropy[i]);
+          }
+        } catch(e) {
+          /* only ignore QuotaExceededError */
+          if(!(typeof QuotaExceededError !== 'undefined' &&
+            e instanceof QuotaExceededError)) {
+            throw e;
+          }
+        }
+      }
+    }
+
+    // be sad and add some weak random data
+    if(b.length() < needed) {
+      /* Draws from Park-Miller "minimal standard" 31 bit PRNG,
+      implemented with David G. Carta's optimization: with 32 bit math
+      and without division (Public Domain). */
+      var hi, lo, next;
+      var seed = Math.floor(Math.random() * 0x010000);
+      while(b.length() < needed) {
+        lo = 16807 * (seed & 0xFFFF);
+        hi = 16807 * (seed >> 16);
+        lo += (hi & 0x7FFF) << 16;
+        lo += hi >> 15;
+        lo = (lo & 0x7FFFFFFF) + (lo >> 31);
+        seed = lo & 0xFFFFFFFF;
+
+        // consume lower 3 bytes of seed
+        for(var i = 0; i < 3; ++i) {
+          // throw in more pseudo random
+          next = seed >>> (i << 3);
+          next ^= Math.floor(Math.random() * 0x0100);
+          b.putByte(String.fromCharCode(next & 0xFF));
+        }
+      }
+    }
+
+    return b.getBytes(needed);
+  }
+  // initialize seed file APIs
+  if(_crypto) {
+    // use nodejs async API
+    ctx.seedFile = function(needed, callback) {
+      _crypto.randomBytes(needed, function(err, bytes) {
+        if(err) {
+          return callback(err);
+        }
+        callback(null, bytes.toString());
+      });
+    };
+    // use nodejs sync API
+    ctx.seedFileSync = function(needed) {
+      return _crypto.randomBytes(needed).toString();
+    };
+  } else {
+    ctx.seedFile = function(needed, callback) {
+      try {
+        callback(null, defaultSeedFile(needed));
+      } catch(e) {
+        callback(e);
+      }
+    };
+    ctx.seedFileSync = defaultSeedFile;
+  }
+
+  /**
+   * Adds entropy to a prng ctx's accumulator.
+   *
+   * @param bytes the bytes of entropy as a string.
+   */
+  ctx.collect = function(bytes) {
+    // iterate over pools distributing entropy cyclically
+    var count = bytes.length;
+    for(var i = 0; i < count; ++i) {
+      ctx.pools[ctx.pool].update(bytes.substr(i, 1));
+      ctx.pool = (ctx.pool === 31) ? 0 : ctx.pool + 1;
+    }
+  };
+
+  /**
+   * Collects an integer of n bits.
+   *
+   * @param i the integer entropy.
+   * @param n the number of bits in the integer.
+   */
+  ctx.collectInt = function(i, n) {
+    var bytes = '';
+    for(var x = 0; x < n; x += 8) {
+      bytes += String.fromCharCode((i >> x) & 0xFF);
+    }
+    ctx.collect(bytes);
+  };
+
+  /**
+   * Registers a Web Worker to receive immediate entropy from the main thread.
+   * This method is required until Web Workers can access the native crypto
+   * API. This method should be called twice for each created worker, once in
+   * the main thread, and once in the worker itself.
+   *
+   * @param worker the worker to register.
+   */
+  ctx.registerWorker = function(worker) {
+    // worker receives random bytes
+    if(worker === self) {
+      ctx.seedFile = function(needed, callback) {
+        function listener(e) {
+          var data = e.data;
+          if(data.forge && data.forge.prng) {
+            self.removeEventListener('message', listener);
+            callback(data.forge.prng.err, data.forge.prng.bytes);
+          }
+        }
+        self.addEventListener('message', listener);
+        self.postMessage({forge: {prng: {needed: needed}}});
+      };
+    } else {
+      // main thread sends random bytes upon request
+      var listener = function(e) {
+        var data = e.data;
+        if(data.forge && data.forge.prng) {
+          ctx.seedFile(data.forge.prng.needed, function(err, bytes) {
+            worker.postMessage({forge: {prng: {err: err, bytes: bytes}}});
+          });
+        }
+      };
+      // TODO: do we need to remove the event listener when the worker dies?
+      worker.addEventListener('message', listener);
+    }
+  };
+
+  return ctx;
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'prng';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './md', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/pss.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/pss.js
new file mode 100644
index 0000000..1b284fc
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/pss.js
@@ -0,0 +1,295 @@
+/**
+ * Javascript implementation of PKCS#1 PSS signature padding.
+ *
+ * @author Stefan Siegl
+ *
+ * Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+// shortcut for PSS API
+var pss = forge.pss = forge.pss || {};
+
+/**
+ * Creates a PSS signature scheme object.
+ *
+ * There are several ways to provide a salt for encoding:
+ *
+ * 1. Specify the saltLength only and the built-in PRNG will generate it.
+ * 2. Specify the saltLength and a custom PRNG with 'getBytesSync' defined that
+ *   will be used.
+ * 3. Specify the salt itself as a forge.util.ByteBuffer.
+ *
+ * @param options the options to use:
+ *          md the message digest object to use, a forge md instance.
+ *          mgf the mask generation function to use, a forge mgf instance.
+ *          [saltLength] the length of the salt in octets.
+ *          [prng] the pseudo-random number generator to use to produce a salt.
+ *          [salt] the salt to use when encoding.
+ *
+ * @return a signature scheme object.
+ */
+pss.create = function(options) {
+  // backwards compatibility w/legacy args: hash, mgf, sLen
+  if(arguments.length === 3) {
+    options = {
+      md: arguments[0],
+      mgf: arguments[1],
+      saltLength: arguments[2]
+    };
+  }
+
+  var hash = options.md;
+  var mgf = options.mgf;
+  var hLen = hash.digestLength;
+
+  var salt_ = options.salt || null;
+  if(typeof salt_ === 'string') {
+    // assume binary-encoded string
+    salt_ = forge.util.createBuffer(salt_);
+  }
+
+  var sLen;
+  if('saltLength' in options) {
+    sLen = options.saltLength;
+  } else if(salt_ !== null) {
+    sLen = salt_.length();
+  } else {
+    throw new Error('Salt length not specified or specific salt not given.');
+  }
+
+  if(salt_ !== null && salt_.length() !== sLen) {
+    throw new Error('Given salt length does not match length of given salt.');
+  }
+
+  var prng = options.prng || forge.random;
+
+  var pssobj = {};
+
+  /**
+   * Encodes a PSS signature.
+   *
+   * This function implements EMSA-PSS-ENCODE as per RFC 3447, section 9.1.1.
+   *
+   * @param md the message digest object with the hash to sign.
+   * @param modsBits the length of the RSA modulus in bits.
+   *
+   * @return the encoded message as a binary-encoded string of length
+   *           ceil((modBits - 1) / 8).
+   */
+  pssobj.encode = function(md, modBits) {
+    var i;
+    var emBits = modBits - 1;
+    var emLen = Math.ceil(emBits / 8);
+
+    /* 2. Let mHash = Hash(M), an octet string of length hLen. */
+    var mHash = md.digest().getBytes();
+
+    /* 3. If emLen < hLen + sLen + 2, output "encoding error" and stop. */
+    if(emLen < hLen + sLen + 2) {
+      throw new Error('Message is too long to encrypt.');
+    }
+
+    /* 4. Generate a random octet string salt of length sLen; if sLen = 0,
+     *    then salt is the empty string. */
+    var salt;
+    if(salt_ === null) {
+      salt = prng.getBytesSync(sLen);
+    } else {
+      salt = salt_.bytes();
+    }
+
+    /* 5. Let M' = (0x)00 00 00 00 00 00 00 00 || mHash || salt; */
+    var m_ = new forge.util.ByteBuffer();
+    m_.fillWithByte(0, 8);
+    m_.putBytes(mHash);
+    m_.putBytes(salt);
+
+    /* 6. Let H = Hash(M'), an octet string of length hLen. */
+    hash.start();
+    hash.update(m_.getBytes());
+    var h = hash.digest().getBytes();
+
+    /* 7. Generate an octet string PS consisting of emLen - sLen - hLen - 2
+     *    zero octets.  The length of PS may be 0. */
+    var ps = new forge.util.ByteBuffer();
+    ps.fillWithByte(0, emLen - sLen - hLen - 2);
+
+    /* 8. Let DB = PS || 0x01 || salt; DB is an octet string of length
+     *    emLen - hLen - 1. */
+    ps.putByte(0x01);
+    ps.putBytes(salt);
+    var db = ps.getBytes();
+
+    /* 9. Let dbMask = MGF(H, emLen - hLen - 1). */
+    var maskLen = emLen - hLen - 1;
+    var dbMask = mgf.generate(h, maskLen);
+
+    /* 10. Let maskedDB = DB \xor dbMask. */
+    var maskedDB = '';
+    for(i = 0; i < maskLen; i ++) {
+      maskedDB += String.fromCharCode(db.charCodeAt(i) ^ dbMask.charCodeAt(i));
+    }
+
+    /* 11. Set the leftmost 8emLen - emBits bits of the leftmost octet in
+     *     maskedDB to zero. */
+    var mask = (0xFF00 >> (8 * emLen - emBits)) & 0xFF;
+    maskedDB = String.fromCharCode(maskedDB.charCodeAt(0) & ~mask) +
+      maskedDB.substr(1);
+
+    /* 12. Let EM = maskedDB || H || 0xbc.
+     * 13. Output EM. */
+    return maskedDB + h + String.fromCharCode(0xbc);
+  };
+
+  /**
+   * Verifies a PSS signature.
+   *
+   * This function implements EMSA-PSS-VERIFY as per RFC 3447, section 9.1.2.
+   *
+   * @param mHash the message digest hash, as a binary-encoded string, to
+   *         compare against the signature.
+   * @param em the encoded message, as a binary-encoded string
+   *          (RSA decryption result).
+   * @param modsBits the length of the RSA modulus in bits.
+   *
+   * @return true if the signature was verified, false if not.
+   */
+  pssobj.verify = function(mHash, em, modBits) {
+    var i;
+    var emBits = modBits - 1;
+    var emLen = Math.ceil(emBits / 8);
+
+    /* c. Convert the message representative m to an encoded message EM
+     *    of length emLen = ceil((modBits - 1) / 8) octets, where modBits
+     *    is the length in bits of the RSA modulus n */
+    em = em.substr(-emLen);
+
+    /* 3. If emLen < hLen + sLen + 2, output "inconsistent" and stop. */
+    if(emLen < hLen + sLen + 2) {
+      throw new Error('Inconsistent parameters to PSS signature verification.');
+    }
+
+    /* 4. If the rightmost octet of EM does not have hexadecimal value
+     *    0xbc, output "inconsistent" and stop. */
+    if(em.charCodeAt(emLen - 1) !== 0xbc) {
+      throw new Error('Encoded message does not end in 0xBC.');
+    }
+
+    /* 5. Let maskedDB be the leftmost emLen - hLen - 1 octets of EM, and
+     *    let H be the next hLen octets. */
+    var maskLen = emLen - hLen - 1;
+    var maskedDB = em.substr(0, maskLen);
+    var h = em.substr(maskLen, hLen);
+
+    /* 6. If the leftmost 8emLen - emBits bits of the leftmost octet in
+     *    maskedDB are not all equal to zero, output "inconsistent" and stop. */
+    var mask = (0xFF00 >> (8 * emLen - emBits)) & 0xFF;
+    if((maskedDB.charCodeAt(0) & mask) !== 0) {
+      throw new Error('Bits beyond keysize not zero as expected.');
+    }
+
+    /* 7. Let dbMask = MGF(H, emLen - hLen - 1). */
+    var dbMask = mgf.generate(h, maskLen);
+
+    /* 8. Let DB = maskedDB \xor dbMask. */
+    var db = '';
+    for(i = 0; i < maskLen; i ++) {
+      db += String.fromCharCode(maskedDB.charCodeAt(i) ^ dbMask.charCodeAt(i));
+    }
+
+    /* 9. Set the leftmost 8emLen - emBits bits of the leftmost octet
+     * in DB to zero. */
+    db = String.fromCharCode(db.charCodeAt(0) & ~mask) + db.substr(1);
+
+    /* 10. If the emLen - hLen - sLen - 2 leftmost octets of DB are not zero
+     * or if the octet at position emLen - hLen - sLen - 1 (the leftmost
+     * position is "position 1") does not have hexadecimal value 0x01,
+     * output "inconsistent" and stop. */
+    var checkLen = emLen - hLen - sLen - 2;
+    for(i = 0; i < checkLen; i ++) {
+      if(db.charCodeAt(i) !== 0x00) {
+        throw new Error('Leftmost octets not zero as expected');
+      }
+    }
+
+    if(db.charCodeAt(checkLen) !== 0x01) {
+      throw new Error('Inconsistent PSS signature, 0x01 marker not found');
+    }
+
+    /* 11. Let salt be the last sLen octets of DB. */
+    var salt = db.substr(-sLen);
+
+    /* 12.  Let M' = (0x)00 00 00 00 00 00 00 00 || mHash || salt */
+    var m_ = new forge.util.ByteBuffer();
+    m_.fillWithByte(0, 8);
+    m_.putBytes(mHash);
+    m_.putBytes(salt);
+
+    /* 13. Let H' = Hash(M'), an octet string of length hLen. */
+    hash.start();
+    hash.update(m_.getBytes());
+    var h_ = hash.digest().getBytes();
+
+    /* 14. If H = H', output "consistent." Otherwise, output "inconsistent." */
+    return h === h_;
+  };
+
+  return pssobj;
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'pss';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './random', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/random.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/random.js
new file mode 100644
index 0000000..febc1fd
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/random.js
@@ -0,0 +1,237 @@
+/**
+ * An API for getting cryptographically-secure random bytes. The bytes are
+ * generated using the Fortuna algorithm devised by Bruce Schneier and
+ * Niels Ferguson.
+ *
+ * Getting strong random bytes is not yet easy to do in javascript. The only
+ * truish random entropy that can be collected is from the mouse, keyboard, or
+ * from timing with respect to page loads, etc. This generator makes a poor
+ * attempt at providing random bytes when those sources haven't yet provided
+ * enough entropy to initially seed or to reseed the PRNG.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2009-2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+// forge.random already defined
+if(forge.random && forge.random.getBytes) {
+  return;
+}
+
+(function(jQuery) {
+
+// the default prng plugin, uses AES-128
+var prng_aes = {};
+var _prng_aes_output = new Array(4);
+var _prng_aes_buffer = forge.util.createBuffer();
+prng_aes.formatKey = function(key) {
+  // convert the key into 32-bit integers
+  var tmp = forge.util.createBuffer(key);
+  key = new Array(4);
+  key[0] = tmp.getInt32();
+  key[1] = tmp.getInt32();
+  key[2] = tmp.getInt32();
+  key[3] = tmp.getInt32();
+
+  // return the expanded key
+  return forge.aes._expandKey(key, false);
+};
+prng_aes.formatSeed = function(seed) {
+  // convert seed into 32-bit integers
+  var tmp = forge.util.createBuffer(seed);
+  seed = new Array(4);
+  seed[0] = tmp.getInt32();
+  seed[1] = tmp.getInt32();
+  seed[2] = tmp.getInt32();
+  seed[3] = tmp.getInt32();
+  return seed;
+};
+prng_aes.cipher = function(key, seed) {
+  forge.aes._updateBlock(key, seed, _prng_aes_output, false);
+  _prng_aes_buffer.putInt32(_prng_aes_output[0]);
+  _prng_aes_buffer.putInt32(_prng_aes_output[1]);
+  _prng_aes_buffer.putInt32(_prng_aes_output[2]);
+  _prng_aes_buffer.putInt32(_prng_aes_output[3]);
+  return _prng_aes_buffer.getBytes();
+};
+prng_aes.increment = function(seed) {
+  // FIXME: do we care about carry or signed issues?
+  ++seed[3];
+  return seed;
+};
+prng_aes.md = forge.md.sha256;
+
+/**
+ * Creates a new PRNG.
+ */
+function spawnPrng() {
+  var ctx = forge.prng.create(prng_aes);
+
+  /**
+   * Gets random bytes. If a native secure crypto API is unavailable, this
+   * method tries to make the bytes more unpredictable by drawing from data that
+   * can be collected from the user of the browser, eg: mouse movement.
+   *
+   * If a callback is given, this method will be called asynchronously.
+   *
+   * @param count the number of random bytes to get.
+   * @param [callback(err, bytes)] called once the operation completes.
+   *
+   * @return the random bytes in a string.
+   */
+  ctx.getBytes = function(count, callback) {
+    return ctx.generate(count, callback);
+  };
+
+  /**
+   * Gets random bytes asynchronously. If a native secure crypto API is
+   * unavailable, this method tries to make the bytes more unpredictable by
+   * drawing from data that can be collected from the user of the browser,
+   * eg: mouse movement.
+   *
+   * @param count the number of random bytes to get.
+   *
+   * @return the random bytes in a string.
+   */
+  ctx.getBytesSync = function(count) {
+    return ctx.generate(count);
+  };
+
+  return ctx;
+}
+
+// create default prng context
+var _ctx = spawnPrng();
+
+// add other sources of entropy only if window.crypto.getRandomValues is not
+// available -- otherwise this source will be automatically used by the prng
+var _nodejs = (
+  typeof process !== 'undefined' && process.versions && process.versions.node);
+var getRandomValues = null;
+if(typeof window !== 'undefined') {
+  var _crypto = window.crypto || window.msCrypto;
+  if(_crypto && _crypto.getRandomValues) {
+    getRandomValues = function(arr) {
+      return _crypto.getRandomValues(arr);
+    };
+  }
+}
+if(forge.disableNativeCode || (!_nodejs && !getRandomValues)) {
+  // if this is a web worker, do not use weak entropy, instead register to
+  // receive strong entropy asynchronously from the main thread
+  if(typeof window === 'undefined' || window.document === undefined) {
+    // FIXME:
+  }
+
+  // get load time entropy
+  _ctx.collectInt(+new Date(), 32);
+
+  // add some entropy from navigator object
+  if(typeof(navigator) !== 'undefined') {
+    var _navBytes = '';
+    for(var key in navigator) {
+      try {
+        if(typeof(navigator[key]) == 'string') {
+          _navBytes += navigator[key];
+        }
+      } catch(e) {
+        /* Some navigator keys might not be accessible, e.g. the geolocation
+          attribute throws an exception if touched in Mozilla chrome://
+          context.
+
+          Silently ignore this and just don't use this as a source of
+          entropy. */
+      }
+    }
+    _ctx.collect(_navBytes);
+    _navBytes = null;
+  }
+
+  // add mouse and keyboard collectors if jquery is available
+  if(jQuery) {
+    // set up mouse entropy capture
+    jQuery().mousemove(function(e) {
+      // add mouse coords
+      _ctx.collectInt(e.clientX, 16);
+      _ctx.collectInt(e.clientY, 16);
+    });
+
+    // set up keyboard entropy capture
+    jQuery().keypress(function(e) {
+      _ctx.collectInt(e.charCode, 8);
+    });
+  }
+}
+
+/* Random API */
+if(!forge.random) {
+  forge.random = _ctx;
+} else {
+  // extend forge.random with _ctx
+  for(var key in _ctx) {
+    forge.random[key] = _ctx[key];
+  }
+}
+
+// expose spawn PRNG
+forge.random.createInstance = spawnPrng;
+
+})(typeof(jQuery) !== 'undefined' ? jQuery : null);
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'random';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './aes', './md', './prng', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/rc2.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/rc2.js
new file mode 100644
index 0000000..0a67011
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/rc2.js
@@ -0,0 +1,470 @@
+/**
+ * RC2 implementation.
+ *
+ * @author Stefan Siegl
+ *
+ * Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
+ *
+ * Information on the RC2 cipher is available from RFC #2268,
+ * http://www.ietf.org/rfc/rfc2268.txt
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+var piTable = [
+  0xd9, 0x78, 0xf9, 0xc4, 0x19, 0xdd, 0xb5, 0xed, 0x28, 0xe9, 0xfd, 0x79, 0x4a, 0xa0, 0xd8, 0x9d,
+  0xc6, 0x7e, 0x37, 0x83, 0x2b, 0x76, 0x53, 0x8e, 0x62, 0x4c, 0x64, 0x88, 0x44, 0x8b, 0xfb, 0xa2,
+  0x17, 0x9a, 0x59, 0xf5, 0x87, 0xb3, 0x4f, 0x13, 0x61, 0x45, 0x6d, 0x8d, 0x09, 0x81, 0x7d, 0x32,
+  0xbd, 0x8f, 0x40, 0xeb, 0x86, 0xb7, 0x7b, 0x0b, 0xf0, 0x95, 0x21, 0x22, 0x5c, 0x6b, 0x4e, 0x82,
+  0x54, 0xd6, 0x65, 0x93, 0xce, 0x60, 0xb2, 0x1c, 0x73, 0x56, 0xc0, 0x14, 0xa7, 0x8c, 0xf1, 0xdc,
+  0x12, 0x75, 0xca, 0x1f, 0x3b, 0xbe, 0xe4, 0xd1, 0x42, 0x3d, 0xd4, 0x30, 0xa3, 0x3c, 0xb6, 0x26,
+  0x6f, 0xbf, 0x0e, 0xda, 0x46, 0x69, 0x07, 0x57, 0x27, 0xf2, 0x1d, 0x9b, 0xbc, 0x94, 0x43, 0x03,
+  0xf8, 0x11, 0xc7, 0xf6, 0x90, 0xef, 0x3e, 0xe7, 0x06, 0xc3, 0xd5, 0x2f, 0xc8, 0x66, 0x1e, 0xd7,
+  0x08, 0xe8, 0xea, 0xde, 0x80, 0x52, 0xee, 0xf7, 0x84, 0xaa, 0x72, 0xac, 0x35, 0x4d, 0x6a, 0x2a,
+  0x96, 0x1a, 0xd2, 0x71, 0x5a, 0x15, 0x49, 0x74, 0x4b, 0x9f, 0xd0, 0x5e, 0x04, 0x18, 0xa4, 0xec,
+  0xc2, 0xe0, 0x41, 0x6e, 0x0f, 0x51, 0xcb, 0xcc, 0x24, 0x91, 0xaf, 0x50, 0xa1, 0xf4, 0x70, 0x39,
+  0x99, 0x7c, 0x3a, 0x85, 0x23, 0xb8, 0xb4, 0x7a, 0xfc, 0x02, 0x36, 0x5b, 0x25, 0x55, 0x97, 0x31,
+  0x2d, 0x5d, 0xfa, 0x98, 0xe3, 0x8a, 0x92, 0xae, 0x05, 0xdf, 0x29, 0x10, 0x67, 0x6c, 0xba, 0xc9,
+  0xd3, 0x00, 0xe6, 0xcf, 0xe1, 0x9e, 0xa8, 0x2c, 0x63, 0x16, 0x01, 0x3f, 0x58, 0xe2, 0x89, 0xa9,
+  0x0d, 0x38, 0x34, 0x1b, 0xab, 0x33, 0xff, 0xb0, 0xbb, 0x48, 0x0c, 0x5f, 0xb9, 0xb1, 0xcd, 0x2e,
+  0xc5, 0xf3, 0xdb, 0x47, 0xe5, 0xa5, 0x9c, 0x77, 0x0a, 0xa6, 0x20, 0x68, 0xfe, 0x7f, 0xc1, 0xad
+];
+
+var s = [1, 2, 3, 5];
+
+
+/**
+ * Rotate a word left by given number of bits.
+ *
+ * Bits that are shifted out on the left are put back in on the right
+ * hand side.
+ *
+ * @param word The word to shift left.
+ * @param bits The number of bits to shift by.
+ * @return The rotated word.
+ */
+var rol = function(word, bits) {
+  return ((word << bits) & 0xffff) | ((word & 0xffff) >> (16 - bits));
+};
+
+/**
+ * Rotate a word right by given number of bits.
+ *
+ * Bits that are shifted out on the right are put back in on the left
+ * hand side.
+ *
+ * @param word The word to shift right.
+ * @param bits The number of bits to shift by.
+ * @return The rotated word.
+ */
+var ror = function(word, bits) {
+  return ((word & 0xffff) >> bits) | ((word << (16 - bits)) & 0xffff);
+};
+
+
+/* RC2 API */
+forge.rc2 = forge.rc2 || {};
+
+/**
+ * Perform RC2 key expansion as per RFC #2268, section 2.
+ *
+ * @param key variable-length user key (between 1 and 128 bytes)
+ * @param effKeyBits number of effective key bits (default: 128)
+ * @return the expanded RC2 key (ByteBuffer of 128 bytes)
+ */
+forge.rc2.expandKey = function(key, effKeyBits) {
+  if(typeof key === 'string') {
+    key = forge.util.createBuffer(key);
+  }
+  effKeyBits = effKeyBits || 128;
+
+  /* introduce variables that match the names used in RFC #2268 */
+  var L = key;
+  var T = key.length();
+  var T1 = effKeyBits;
+  var T8 = Math.ceil(T1 / 8);
+  var TM = 0xff >> (T1 & 0x07);
+  var i;
+
+  for(i = T; i < 128; i ++) {
+    L.putByte(piTable[(L.at(i - 1) + L.at(i - T)) & 0xff]);
+  }
+
+  L.setAt(128 - T8, piTable[L.at(128 - T8) & TM]);
+
+  for(i = 127 - T8; i >= 0; i --) {
+    L.setAt(i, piTable[L.at(i + 1) ^ L.at(i + T8)]);
+  }
+
+  return L;
+};
+
+
+/**
+ * Creates a RC2 cipher object.
+ *
+ * @param key the symmetric key to use (as base for key generation).
+ * @param bits the number of effective key bits.
+ * @param encrypt false for decryption, true for encryption.
+ *
+ * @return the cipher.
+ */
+var createCipher = function(key, bits, encrypt) {
+  var _finish = false, _input = null, _output = null, _iv = null;
+  var mixRound, mashRound;
+  var i, j, K = [];
+
+  /* Expand key and fill into K[] Array */
+  key = forge.rc2.expandKey(key, bits);
+  for(i = 0; i < 64; i ++) {
+    K.push(key.getInt16Le());
+  }
+
+  if(encrypt) {
+    /**
+     * Perform one mixing round "in place".
+     *
+     * @param R Array of four words to perform mixing on.
+     */
+    mixRound = function(R) {
+      for(i = 0; i < 4; i++) {
+        R[i] += K[j] + (R[(i + 3) % 4] & R[(i + 2) % 4]) +
+          ((~R[(i + 3) % 4]) & R[(i + 1) % 4]);
+        R[i] = rol(R[i], s[i]);
+        j ++;
+      }
+    };
+
+    /**
+     * Perform one mashing round "in place".
+     *
+     * @param R Array of four words to perform mashing on.
+     */
+    mashRound = function(R) {
+      for(i = 0; i < 4; i ++) {
+        R[i] += K[R[(i + 3) % 4] & 63];
+      }
+    };
+  } else {
+    /**
+     * Perform one r-mixing round "in place".
+     *
+     * @param R Array of four words to perform mixing on.
+     */
+    mixRound = function(R) {
+      for(i = 3; i >= 0; i--) {
+        R[i] = ror(R[i], s[i]);
+        R[i] -= K[j] + (R[(i + 3) % 4] & R[(i + 2) % 4]) +
+          ((~R[(i + 3) % 4]) & R[(i + 1) % 4]);
+        j --;
+      }
+    };
+
+    /**
+     * Perform one r-mashing round "in place".
+     *
+     * @param R Array of four words to perform mashing on.
+     */
+    mashRound = function(R) {
+      for(i = 3; i >= 0; i--) {
+        R[i] -= K[R[(i + 3) % 4] & 63];
+      }
+    };
+  }
+
+  /**
+   * Run the specified cipher execution plan.
+   *
+   * This function takes four words from the input buffer, applies the IV on
+   * it (if requested) and runs the provided execution plan.
+   *
+   * The plan must be put together in form of a array of arrays.  Where the
+   * outer one is simply a list of steps to perform and the inner one needs
+   * to have two elements: the first one telling how many rounds to perform,
+   * the second one telling what to do (i.e. the function to call).
+   *
+   * @param {Array} plan The plan to execute.
+   */
+  var runPlan = function(plan) {
+    var R = [];
+
+    /* Get data from input buffer and fill the four words into R */
+    for(i = 0; i < 4; i ++) {
+      var val = _input.getInt16Le();
+
+      if(_iv !== null) {
+        if(encrypt) {
+          /* We're encrypting, apply the IV first. */
+          val ^= _iv.getInt16Le();
+        } else {
+          /* We're decryption, keep cipher text for next block. */
+          _iv.putInt16Le(val);
+        }
+      }
+
+      R.push(val & 0xffff);
+    }
+
+    /* Reset global "j" variable as per spec. */
+    j = encrypt ? 0 : 63;
+
+    /* Run execution plan. */
+    for(var ptr = 0; ptr < plan.length; ptr ++) {
+      for(var ctr = 0; ctr < plan[ptr][0]; ctr ++) {
+        plan[ptr][1](R);
+      }
+    }
+
+    /* Write back result to output buffer. */
+    for(i = 0; i < 4; i ++) {
+      if(_iv !== null) {
+        if(encrypt) {
+          /* We're encrypting in CBC-mode, feed back encrypted bytes into
+             IV buffer to carry it forward to next block. */
+          _iv.putInt16Le(R[i]);
+        } else {
+          R[i] ^= _iv.getInt16Le();
+        }
+      }
+
+      _output.putInt16Le(R[i]);
+    }
+  };
+
+
+  /* Create cipher object */
+  var cipher = null;
+  cipher = {
+    /**
+     * Starts or restarts the encryption or decryption process, whichever
+     * was previously configured.
+     *
+     * To use the cipher in CBC mode, iv may be given either as a string
+     * of bytes, or as a byte buffer.  For ECB mode, give null as iv.
+     *
+     * @param iv the initialization vector to use, null for ECB mode.
+     * @param output the output the buffer to write to, null to create one.
+     */
+    start: function(iv, output) {
+      if(iv) {
+        /* CBC mode */
+        if(typeof iv === 'string') {
+          iv = forge.util.createBuffer(iv);
+        }
+      }
+
+      _finish = false;
+      _input = forge.util.createBuffer();
+      _output = output || new forge.util.createBuffer();
+      _iv = iv;
+
+      cipher.output = _output;
+    },
+
+    /**
+     * Updates the next block.
+     *
+     * @param input the buffer to read from.
+     */
+    update: function(input) {
+      if(!_finish) {
+        // not finishing, so fill the input buffer with more input
+        _input.putBuffer(input);
+      }
+
+      while(_input.length() >= 8) {
+        runPlan([
+            [ 5, mixRound ],
+            [ 1, mashRound ],
+            [ 6, mixRound ],
+            [ 1, mashRound ],
+            [ 5, mixRound ]
+          ]);
+      }
+    },
+
+    /**
+     * Finishes encrypting or decrypting.
+     *
+     * @param pad a padding function to use, null for PKCS#7 padding,
+     *           signature(blockSize, buffer, decrypt).
+     *
+     * @return true if successful, false on error.
+     */
+    finish: function(pad) {
+      var rval = true;
+
+      if(encrypt) {
+        if(pad) {
+          rval = pad(8, _input, !encrypt);
+        } else {
+          // add PKCS#7 padding to block (each pad byte is the
+          // value of the number of pad bytes)
+          var padding = (_input.length() === 8) ? 8 : (8 - _input.length());
+          _input.fillWithByte(padding, padding);
+        }
+      }
+
+      if(rval) {
+        // do final update
+        _finish = true;
+        cipher.update();
+      }
+
+      if(!encrypt) {
+        // check for error: input data not a multiple of block size
+        rval = (_input.length() === 0);
+        if(rval) {
+          if(pad) {
+            rval = pad(8, _output, !encrypt);
+          } else {
+            // ensure padding byte count is valid
+            var len = _output.length();
+            var count = _output.at(len - 1);
+
+            if(count > len) {
+              rval = false;
+            } else {
+              // trim off padding bytes
+              _output.truncate(count);
+            }
+          }
+        }
+      }
+
+      return rval;
+    }
+  };
+
+  return cipher;
+};
+
+
+/**
+ * Creates an RC2 cipher object to encrypt data in ECB or CBC mode using the
+ * given symmetric key. The output will be stored in the 'output' member
+ * of the returned cipher.
+ *
+ * The key and iv may be given as a string of bytes or a byte buffer.
+ * The cipher is initialized to use 128 effective key bits.
+ *
+ * @param key the symmetric key to use.
+ * @param iv the initialization vector to use.
+ * @param output the buffer to write to, null to create one.
+ *
+ * @return the cipher.
+ */
+forge.rc2.startEncrypting = function(key, iv, output) {
+  var cipher = forge.rc2.createEncryptionCipher(key, 128);
+  cipher.start(iv, output);
+  return cipher;
+};
+
+/**
+ * Creates an RC2 cipher object to encrypt data in ECB or CBC mode using the
+ * given symmetric key.
+ *
+ * The key may be given as a string of bytes or a byte buffer.
+ *
+ * To start encrypting call start() on the cipher with an iv and optional
+ * output buffer.
+ *
+ * @param key the symmetric key to use.
+ *
+ * @return the cipher.
+ */
+forge.rc2.createEncryptionCipher = function(key, bits) {
+  return createCipher(key, bits, true);
+};
+
+/**
+ * Creates an RC2 cipher object to decrypt data in ECB or CBC mode using the
+ * given symmetric key. The output will be stored in the 'output' member
+ * of the returned cipher.
+ *
+ * The key and iv may be given as a string of bytes or a byte buffer.
+ * The cipher is initialized to use 128 effective key bits.
+ *
+ * @param key the symmetric key to use.
+ * @param iv the initialization vector to use.
+ * @param output the buffer to write to, null to create one.
+ *
+ * @return the cipher.
+ */
+forge.rc2.startDecrypting = function(key, iv, output) {
+  var cipher = forge.rc2.createDecryptionCipher(key, 128);
+  cipher.start(iv, output);
+  return cipher;
+};
+
+/**
+ * Creates an RC2 cipher object to decrypt data in ECB or CBC mode using the
+ * given symmetric key.
+ *
+ * The key may be given as a string of bytes or a byte buffer.
+ *
+ * To start decrypting call start() on the cipher with an iv and optional
+ * output buffer.
+ *
+ * @param key the symmetric key to use.
+ *
+ * @return the cipher.
+ */
+forge.rc2.createDecryptionCipher = function(key, bits) {
+  return createCipher(key, bits, false);
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'rc2';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/rsa.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/rsa.js
new file mode 100644
index 0000000..90f8c0a
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/rsa.js
@@ -0,0 +1,1712 @@
+/**
+ * Javascript implementation of basic RSA algorithms.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2014 Digital Bazaar, Inc.
+ *
+ * The only algorithm currently supported for PKI is RSA.
+ *
+ * An RSA key is often stored in ASN.1 DER format. The SubjectPublicKeyInfo
+ * ASN.1 structure is composed of an algorithm of type AlgorithmIdentifier
+ * and a subjectPublicKey of type bit string.
+ *
+ * The AlgorithmIdentifier contains an Object Identifier (OID) and parameters
+ * for the algorithm, if any. In the case of RSA, there aren't any.
+ *
+ * SubjectPublicKeyInfo ::= SEQUENCE {
+ *   algorithm AlgorithmIdentifier,
+ *   subjectPublicKey BIT STRING
+ * }
+ *
+ * AlgorithmIdentifer ::= SEQUENCE {
+ *   algorithm OBJECT IDENTIFIER,
+ *   parameters ANY DEFINED BY algorithm OPTIONAL
+ * }
+ *
+ * For an RSA public key, the subjectPublicKey is:
+ *
+ * RSAPublicKey ::= SEQUENCE {
+ *   modulus            INTEGER,    -- n
+ *   publicExponent     INTEGER     -- e
+ * }
+ *
+ * PrivateKeyInfo ::= SEQUENCE {
+ *   version                   Version,
+ *   privateKeyAlgorithm       PrivateKeyAlgorithmIdentifier,
+ *   privateKey                PrivateKey,
+ *   attributes           [0]  IMPLICIT Attributes OPTIONAL
+ * }
+ *
+ * Version ::= INTEGER
+ * PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
+ * PrivateKey ::= OCTET STRING
+ * Attributes ::= SET OF Attribute
+ *
+ * An RSA private key as the following structure:
+ *
+ * RSAPrivateKey ::= SEQUENCE {
+ *   version Version,
+ *   modulus INTEGER, -- n
+ *   publicExponent INTEGER, -- e
+ *   privateExponent INTEGER, -- d
+ *   prime1 INTEGER, -- p
+ *   prime2 INTEGER, -- q
+ *   exponent1 INTEGER, -- d mod (p-1)
+ *   exponent2 INTEGER, -- d mod (q-1)
+ *   coefficient INTEGER -- (inverse of q) mod p
+ * }
+ *
+ * Version ::= INTEGER
+ *
+ * The OID for the RSA key algorithm is: 1.2.840.113549.1.1.1
+ */
+(function() {
+function initModule(forge) {
+/* ########## Begin module implementation ########## */
+
+if(typeof BigInteger === 'undefined') {
+  var BigInteger = forge.jsbn.BigInteger;
+}
+
+// shortcut for asn.1 API
+var asn1 = forge.asn1;
+
+/*
+ * RSA encryption and decryption, see RFC 2313.
+ */
+forge.pki = forge.pki || {};
+forge.pki.rsa = forge.rsa = forge.rsa || {};
+var pki = forge.pki;
+
+// for finding primes, which are 30k+i for i = 1, 7, 11, 13, 17, 19, 23, 29
+var GCD_30_DELTA = [6, 4, 2, 4, 2, 4, 6, 2];
+
+// validator for a PrivateKeyInfo structure
+var privateKeyValidator = {
+  // PrivateKeyInfo
+  name: 'PrivateKeyInfo',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    // Version (INTEGER)
+    name: 'PrivateKeyInfo.version',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'privateKeyVersion'
+  }, {
+    // privateKeyAlgorithm
+    name: 'PrivateKeyInfo.privateKeyAlgorithm',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true,
+    value: [{
+      name: 'AlgorithmIdentifier.algorithm',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.OID,
+      constructed: false,
+      capture: 'privateKeyOid'
+    }]
+  }, {
+    // PrivateKey
+    name: 'PrivateKeyInfo',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.OCTETSTRING,
+    constructed: false,
+    capture: 'privateKey'
+  }]
+};
+
+// validator for an RSA private key
+var rsaPrivateKeyValidator = {
+  // RSAPrivateKey
+  name: 'RSAPrivateKey',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    // Version (INTEGER)
+    name: 'RSAPrivateKey.version',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'privateKeyVersion'
+  }, {
+    // modulus (n)
+    name: 'RSAPrivateKey.modulus',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'privateKeyModulus'
+  }, {
+    // publicExponent (e)
+    name: 'RSAPrivateKey.publicExponent',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'privateKeyPublicExponent'
+  }, {
+    // privateExponent (d)
+    name: 'RSAPrivateKey.privateExponent',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'privateKeyPrivateExponent'
+  }, {
+    // prime1 (p)
+    name: 'RSAPrivateKey.prime1',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'privateKeyPrime1'
+  }, {
+    // prime2 (q)
+    name: 'RSAPrivateKey.prime2',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'privateKeyPrime2'
+  }, {
+    // exponent1 (d mod (p-1))
+    name: 'RSAPrivateKey.exponent1',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'privateKeyExponent1'
+  }, {
+    // exponent2 (d mod (q-1))
+    name: 'RSAPrivateKey.exponent2',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'privateKeyExponent2'
+  }, {
+    // coefficient ((inverse of q) mod p)
+    name: 'RSAPrivateKey.coefficient',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'privateKeyCoefficient'
+  }]
+};
+
+// validator for an RSA public key
+var rsaPublicKeyValidator = {
+  // RSAPublicKey
+  name: 'RSAPublicKey',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    // modulus (n)
+    name: 'RSAPublicKey.modulus',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'publicKeyModulus'
+  }, {
+    // publicExponent (e)
+    name: 'RSAPublicKey.exponent',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'publicKeyExponent'
+  }]
+};
+
+// validator for an SubjectPublicKeyInfo structure
+// Note: Currently only works with an RSA public key
+var publicKeyValidator = forge.pki.rsa.publicKeyValidator = {
+  name: 'SubjectPublicKeyInfo',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  captureAsn1: 'subjectPublicKeyInfo',
+  value: [{
+    name: 'SubjectPublicKeyInfo.AlgorithmIdentifier',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true,
+    value: [{
+      name: 'AlgorithmIdentifier.algorithm',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.OID,
+      constructed: false,
+      capture: 'publicKeyOid'
+    }]
+  }, {
+    // subjectPublicKey
+    name: 'SubjectPublicKeyInfo.subjectPublicKey',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.BITSTRING,
+    constructed: false,
+    value: [{
+      // RSAPublicKey
+      name: 'SubjectPublicKeyInfo.subjectPublicKey.RSAPublicKey',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.SEQUENCE,
+      constructed: true,
+      optional: true,
+      captureAsn1: 'rsaPublicKey'
+    }]
+  }]
+};
+
+/**
+ * Wrap digest in DigestInfo object.
+ *
+ * This function implements EMSA-PKCS1-v1_5-ENCODE as per RFC 3447.
+ *
+ * DigestInfo ::= SEQUENCE {
+ *   digestAlgorithm DigestAlgorithmIdentifier,
+ *   digest Digest
+ * }
+ *
+ * DigestAlgorithmIdentifier ::= AlgorithmIdentifier
+ * Digest ::= OCTET STRING
+ *
+ * @param md the message digest object with the hash to sign.
+ *
+ * @return the encoded message (ready for RSA encrytion)
+ */
+var emsaPkcs1v15encode = function(md) {
+  // get the oid for the algorithm
+  var oid;
+  if(md.algorithm in pki.oids) {
+    oid = pki.oids[md.algorithm];
+  } else {
+    var error = new Error('Unknown message digest algorithm.');
+    error.algorithm = md.algorithm;
+    throw error;
+  }
+  var oidBytes = asn1.oidToDer(oid).getBytes();
+
+  // create the digest info
+  var digestInfo = asn1.create(
+    asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []);
+  var digestAlgorithm = asn1.create(
+    asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []);
+  digestAlgorithm.value.push(asn1.create(
+    asn1.Class.UNIVERSAL, asn1.Type.OID, false, oidBytes));
+  digestAlgorithm.value.push(asn1.create(
+    asn1.Class.UNIVERSAL, asn1.Type.NULL, false, ''));
+  var digest = asn1.create(
+    asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING,
+    false, md.digest().getBytes());
+  digestInfo.value.push(digestAlgorithm);
+  digestInfo.value.push(digest);
+
+  // encode digest info
+  return asn1.toDer(digestInfo).getBytes();
+};
+
+/**
+ * Performs x^c mod n (RSA encryption or decryption operation).
+ *
+ * @param x the number to raise and mod.
+ * @param key the key to use.
+ * @param pub true if the key is public, false if private.
+ *
+ * @return the result of x^c mod n.
+ */
+var _modPow = function(x, key, pub) {
+  if(pub) {
+    return x.modPow(key.e, key.n);
+  }
+
+  if(!key.p || !key.q) {
+    // allow calculation without CRT params (slow)
+    return x.modPow(key.d, key.n);
+  }
+
+  // pre-compute dP, dQ, and qInv if necessary
+  if(!key.dP) {
+    key.dP = key.d.mod(key.p.subtract(BigInteger.ONE));
+  }
+  if(!key.dQ) {
+    key.dQ = key.d.mod(key.q.subtract(BigInteger.ONE));
+  }
+  if(!key.qInv) {
+    key.qInv = key.q.modInverse(key.p);
+  }
+
+  /* Chinese remainder theorem (CRT) states:
+
+    Suppose n1, n2, ..., nk are positive integers which are pairwise
+    coprime (n1 and n2 have no common factors other than 1). For any
+    integers x1, x2, ..., xk there exists an integer x solving the
+    system of simultaneous congruences (where ~= means modularly
+    congruent so a ~= b mod n means a mod n = b mod n):
+
+    x ~= x1 mod n1
+    x ~= x2 mod n2
+    ...
+    x ~= xk mod nk
+
+    This system of congruences has a single simultaneous solution x
+    between 0 and n - 1. Furthermore, each xk solution and x itself
+    is congruent modulo the product n = n1*n2*...*nk.
+    So x1 mod n = x2 mod n = xk mod n = x mod n.
+
+    The single simultaneous solution x can be solved with the following
+    equation:
+
+    x = sum(xi*ri*si) mod n where ri = n/ni and si = ri^-1 mod ni.
+
+    Where x is less than n, xi = x mod ni.
+
+    For RSA we are only concerned with k = 2. The modulus n = pq, where
+    p and q are coprime. The RSA decryption algorithm is:
+
+    y = x^d mod n
+
+    Given the above:
+
+    x1 = x^d mod p
+    r1 = n/p = q
+    s1 = q^-1 mod p
+    x2 = x^d mod q
+    r2 = n/q = p
+    s2 = p^-1 mod q
+
+    So y = (x1r1s1 + x2r2s2) mod n
+         = ((x^d mod p)q(q^-1 mod p) + (x^d mod q)p(p^-1 mod q)) mod n
+
+    According to Fermat's Little Theorem, if the modulus P is prime,
+    for any integer A not evenly divisible by P, A^(P-1) ~= 1 mod P.
+    Since A is not divisible by P it follows that if:
+    N ~= M mod (P - 1), then A^N mod P = A^M mod P. Therefore:
+
+    A^N mod P = A^(M mod (P - 1)) mod P. (The latter takes less effort
+    to calculate). In order to calculate x^d mod p more quickly the
+    exponent d mod (p - 1) is stored in the RSA private key (the same
+    is done for x^d mod q). These values are referred to as dP and dQ
+    respectively. Therefore we now have:
+
+    y = ((x^dP mod p)q(q^-1 mod p) + (x^dQ mod q)p(p^-1 mod q)) mod n
+
+    Since we'll be reducing x^dP by modulo p (same for q) we can also
+    reduce x by p (and q respectively) before hand. Therefore, let
+
+    xp = ((x mod p)^dP mod p), and
+    xq = ((x mod q)^dQ mod q), yielding:
+
+    y = (xp*q*(q^-1 mod p) + xq*p*(p^-1 mod q)) mod n
+
+    This can be further reduced to a simple algorithm that only
+    requires 1 inverse (the q inverse is used) to be used and stored.
+    The algorithm is called Garner's algorithm. If qInv is the
+    inverse of q, we simply calculate:
+
+    y = (qInv*(xp - xq) mod p) * q + xq
+
+    However, there are two further complications. First, we need to
+    ensure that xp > xq to prevent signed BigIntegers from being used
+    so we add p until this is true (since we will be mod'ing with
+    p anyway). Then, there is a known timing attack on algorithms
+    using the CRT. To mitigate this risk, "cryptographic blinding"
+    should be used. This requires simply generating a random number r between
+    0 and n-1 and its inverse and multiplying x by r^e before calculating y
+    and then multiplying y by r^-1 afterwards.
+  */
+
+  // cryptographic blinding
+  var r;
+  do {
+    r = new BigInteger(
+      forge.util.bytesToHex(forge.random.getBytes(key.n.bitLength() / 8)),
+      16).mod(key.n);
+  } while(r.equals(BigInteger.ZERO));
+  x = x.multiply(r.modPow(key.e, key.n)).mod(key.n);
+
+  // calculate xp and xq
+  var xp = x.mod(key.p).modPow(key.dP, key.p);
+  var xq = x.mod(key.q).modPow(key.dQ, key.q);
+
+  // xp must be larger than xq to avoid signed bit usage
+  while(xp.compareTo(xq) < 0) {
+    xp = xp.add(key.p);
+  }
+
+  // do last step
+  var y = xp.subtract(xq)
+    .multiply(key.qInv).mod(key.p)
+    .multiply(key.q).add(xq);
+
+  // remove effect of random for cryptographic blinding
+  y = y.multiply(r.modInverse(key.n)).mod(key.n);
+
+  return y;
+};
+
+/**
+ * NOTE: THIS METHOD IS DEPRECATED, use 'sign' on a private key object or
+ * 'encrypt' on a public key object instead.
+ *
+ * Performs RSA encryption.
+ *
+ * The parameter bt controls whether to put padding bytes before the
+ * message passed in. Set bt to either true or false to disable padding
+ * completely (in order to handle e.g. EMSA-PSS encoding seperately before),
+ * signaling whether the encryption operation is a public key operation
+ * (i.e. encrypting data) or not, i.e. private key operation (data signing).
+ *
+ * For PKCS#1 v1.5 padding pass in the block type to use, i.e. either 0x01
+ * (for signing) or 0x02 (for encryption). The key operation mode (private
+ * or public) is derived from this flag in that case).
+ *
+ * @param m the message to encrypt as a byte string.
+ * @param key the RSA key to use.
+ * @param bt for PKCS#1 v1.5 padding, the block type to use
+ *   (0x01 for private key, 0x02 for public),
+ *   to disable padding: true = public key, false = private key.
+ *
+ * @return the encrypted bytes as a string.
+ */
+pki.rsa.encrypt = function(m, key, bt) {
+  var pub = bt;
+  var eb;
+
+  // get the length of the modulus in bytes
+  var k = Math.ceil(key.n.bitLength() / 8);
+
+  if(bt !== false && bt !== true) {
+    // legacy, default to PKCS#1 v1.5 padding
+    pub = (bt === 0x02);
+    eb = _encodePkcs1_v1_5(m, key, bt);
+  } else {
+    eb = forge.util.createBuffer();
+    eb.putBytes(m);
+  }
+
+  // load encryption block as big integer 'x'
+  // FIXME: hex conversion inefficient, get BigInteger w/byte strings
+  var x = new BigInteger(eb.toHex(), 16);
+
+  // do RSA encryption
+  var y = _modPow(x, key, pub);
+
+  // convert y into the encrypted data byte string, if y is shorter in
+  // bytes than k, then prepend zero bytes to fill up ed
+  // FIXME: hex conversion inefficient, get BigInteger w/byte strings
+  var yhex = y.toString(16);
+  var ed = forge.util.createBuffer();
+  var zeros = k - Math.ceil(yhex.length / 2);
+  while(zeros > 0) {
+    ed.putByte(0x00);
+    --zeros;
+  }
+  ed.putBytes(forge.util.hexToBytes(yhex));
+  return ed.getBytes();
+};
+
+/**
+ * NOTE: THIS METHOD IS DEPRECATED, use 'decrypt' on a private key object or
+ * 'verify' on a public key object instead.
+ *
+ * Performs RSA decryption.
+ *
+ * The parameter ml controls whether to apply PKCS#1 v1.5 padding
+ * or not.  Set ml = false to disable padding removal completely
+ * (in order to handle e.g. EMSA-PSS later on) and simply pass back
+ * the RSA encryption block.
+ *
+ * @param ed the encrypted data to decrypt in as a byte string.
+ * @param key the RSA key to use.
+ * @param pub true for a public key operation, false for private.
+ * @param ml the message length, if known, false to disable padding.
+ *
+ * @return the decrypted message as a byte string.
+ */
+pki.rsa.decrypt = function(ed, key, pub, ml) {
+  // get the length of the modulus in bytes
+  var k = Math.ceil(key.n.bitLength() / 8);
+
+  // error if the length of the encrypted data ED is not k
+  if(ed.length !== k) {
+    var error = new Error('Encrypted message length is invalid.');
+    error.length = ed.length;
+    error.expected = k;
+    throw error;
+  }
+
+  // convert encrypted data into a big integer
+  // FIXME: hex conversion inefficient, get BigInteger w/byte strings
+  var y = new BigInteger(forge.util.createBuffer(ed).toHex(), 16);
+
+  // y must be less than the modulus or it wasn't the result of
+  // a previous mod operation (encryption) using that modulus
+  if(y.compareTo(key.n) >= 0) {
+    throw new Error('Encrypted message is invalid.');
+  }
+
+  // do RSA decryption
+  var x = _modPow(y, key, pub);
+
+  // create the encryption block, if x is shorter in bytes than k, then
+  // prepend zero bytes to fill up eb
+  // FIXME: hex conversion inefficient, get BigInteger w/byte strings
+  var xhex = x.toString(16);
+  var eb = forge.util.createBuffer();
+  var zeros = k - Math.ceil(xhex.length / 2);
+  while(zeros > 0) {
+    eb.putByte(0x00);
+    --zeros;
+  }
+  eb.putBytes(forge.util.hexToBytes(xhex));
+
+  if(ml !== false) {
+    // legacy, default to PKCS#1 v1.5 padding
+    return _decodePkcs1_v1_5(eb.getBytes(), key, pub);
+  }
+
+  // return message
+  return eb.getBytes();
+};
+
+/**
+ * Creates an RSA key-pair generation state object. It is used to allow
+ * key-generation to be performed in steps. It also allows for a UI to
+ * display progress updates.
+ *
+ * @param bits the size for the private key in bits, defaults to 2048.
+ * @param e the public exponent to use, defaults to 65537 (0x10001).
+ * @param [options] the options to use.
+ *          prng a custom crypto-secure pseudo-random number generator to use,
+ *            that must define "getBytesSync".
+ *          algorithm the algorithm to use (default: 'PRIMEINC').
+ *
+ * @return the state object to use to generate the key-pair.
+ */
+pki.rsa.createKeyPairGenerationState = function(bits, e, options) {
+  // TODO: migrate step-based prime generation code to forge.prime
+
+  // set default bits
+  if(typeof(bits) === 'string') {
+    bits = parseInt(bits, 10);
+  }
+  bits = bits || 2048;
+
+  // create prng with api that matches BigInteger secure random
+  options = options || {};
+  var prng = options.prng || forge.random;
+  var rng = {
+    // x is an array to fill with bytes
+    nextBytes: function(x) {
+      var b = prng.getBytesSync(x.length);
+      for(var i = 0; i < x.length; ++i) {
+        x[i] = b.charCodeAt(i);
+      }
+    }
+  };
+
+  var algorithm = options.algorithm || 'PRIMEINC';
+
+  // create PRIMEINC algorithm state
+  var rval;
+  if(algorithm === 'PRIMEINC') {
+    rval = {
+      algorithm: algorithm,
+      state: 0,
+      bits: bits,
+      rng: rng,
+      eInt: e || 65537,
+      e: new BigInteger(null),
+      p: null,
+      q: null,
+      qBits: bits >> 1,
+      pBits: bits - (bits >> 1),
+      pqState: 0,
+      num: null,
+      keys: null
+    };
+    rval.e.fromInt(rval.eInt);
+  } else {
+    throw new Error('Invalid key generation algorithm: ' + algorithm);
+  }
+
+  return rval;
+};
+
+/**
+ * Attempts to runs the key-generation algorithm for at most n seconds
+ * (approximately) using the given state. When key-generation has completed,
+ * the keys will be stored in state.keys.
+ *
+ * To use this function to update a UI while generating a key or to prevent
+ * causing browser lockups/warnings, set "n" to a value other than 0. A
+ * simple pattern for generating a key and showing a progress indicator is:
+ *
+ * var state = pki.rsa.createKeyPairGenerationState(2048);
+ * var step = function() {
+ *   // step key-generation, run algorithm for 100 ms, repeat
+ *   if(!forge.pki.rsa.stepKeyPairGenerationState(state, 100)) {
+ *     setTimeout(step, 1);
+ *   } else {
+ *     // key-generation complete
+ *     // TODO: turn off progress indicator here
+ *     // TODO: use the generated key-pair in "state.keys"
+ *   }
+ * };
+ * // TODO: turn on progress indicator here
+ * setTimeout(step, 0);
+ *
+ * @param state the state to use.
+ * @param n the maximum number of milliseconds to run the algorithm for, 0
+ *          to run the algorithm to completion.
+ *
+ * @return true if the key-generation completed, false if not.
+ */
+pki.rsa.stepKeyPairGenerationState = function(state, n) {
+  // set default algorithm if not set
+  if(!('algorithm' in state)) {
+    state.algorithm = 'PRIMEINC';
+  }
+
+  // TODO: migrate step-based prime generation code to forge.prime
+  // TODO: abstract as PRIMEINC algorithm
+
+  // do key generation (based on Tom Wu's rsa.js, see jsbn.js license)
+  // with some minor optimizations and designed to run in steps
+
+  // local state vars
+  var THIRTY = new BigInteger(null);
+  THIRTY.fromInt(30);
+  var deltaIdx = 0;
+  var op_or = function(x,y) { return x|y; };
+
+  // keep stepping until time limit is reached or done
+  var t1 = +new Date();
+  var t2;
+  var total = 0;
+  while(state.keys === null && (n <= 0 || total < n)) {
+    // generate p or q
+    if(state.state === 0) {
+      /* Note: All primes are of the form:
+
+        30k+i, for i < 30 and gcd(30, i)=1, where there are 8 values for i
+
+        When we generate a random number, we always align it at 30k + 1. Each
+        time the number is determined not to be prime we add to get to the
+        next 'i', eg: if the number was at 30k + 1 we add 6. */
+      var bits = (state.p === null) ? state.pBits : state.qBits;
+      var bits1 = bits - 1;
+
+      // get a random number
+      if(state.pqState === 0) {
+        state.num = new BigInteger(bits, state.rng);
+        // force MSB set
+        if(!state.num.testBit(bits1)) {
+          state.num.bitwiseTo(
+            BigInteger.ONE.shiftLeft(bits1), op_or, state.num);
+        }
+        // align number on 30k+1 boundary
+        state.num.dAddOffset(31 - state.num.mod(THIRTY).byteValue(), 0);
+        deltaIdx = 0;
+
+        ++state.pqState;
+      } else if(state.pqState === 1) {
+        // try to make the number a prime
+        if(state.num.bitLength() > bits) {
+          // overflow, try again
+          state.pqState = 0;
+          // do primality test
+        } else if(state.num.isProbablePrime(
+          _getMillerRabinTests(state.num.bitLength()))) {
+          ++state.pqState;
+        } else {
+          // get next potential prime
+          state.num.dAddOffset(GCD_30_DELTA[deltaIdx++ % 8], 0);
+        }
+      } else if(state.pqState === 2) {
+        // ensure number is coprime with e
+        state.pqState =
+          (state.num.subtract(BigInteger.ONE).gcd(state.e)
+          .compareTo(BigInteger.ONE) === 0) ? 3 : 0;
+      } else if(state.pqState === 3) {
+        // store p or q
+        state.pqState = 0;
+        if(state.p === null) {
+          state.p = state.num;
+        } else {
+          state.q = state.num;
+        }
+
+        // advance state if both p and q are ready
+        if(state.p !== null && state.q !== null) {
+          ++state.state;
+        }
+        state.num = null;
+      }
+    } else if(state.state === 1) {
+      // ensure p is larger than q (swap them if not)
+      if(state.p.compareTo(state.q) < 0) {
+        state.num = state.p;
+        state.p = state.q;
+        state.q = state.num;
+      }
+      ++state.state;
+    } else if(state.state === 2) {
+      // compute phi: (p - 1)(q - 1) (Euler's totient function)
+      state.p1 = state.p.subtract(BigInteger.ONE);
+      state.q1 = state.q.subtract(BigInteger.ONE);
+      state.phi = state.p1.multiply(state.q1);
+      ++state.state;
+    } else if(state.state === 3) {
+      // ensure e and phi are coprime
+      if(state.phi.gcd(state.e).compareTo(BigInteger.ONE) === 0) {
+        // phi and e are coprime, advance
+        ++state.state;
+      } else {
+        // phi and e aren't coprime, so generate a new p and q
+        state.p = null;
+        state.q = null;
+        state.state = 0;
+      }
+    } else if(state.state === 4) {
+      // create n, ensure n is has the right number of bits
+      state.n = state.p.multiply(state.q);
+
+      // ensure n is right number of bits
+      if(state.n.bitLength() === state.bits) {
+        // success, advance
+        ++state.state;
+      } else {
+        // failed, get new q
+        state.q = null;
+        state.state = 0;
+      }
+    } else if(state.state === 5) {
+      // set keys
+      var d = state.e.modInverse(state.phi);
+      state.keys = {
+        privateKey: pki.rsa.setPrivateKey(
+          state.n, state.e, d, state.p, state.q,
+          d.mod(state.p1), d.mod(state.q1),
+          state.q.modInverse(state.p)),
+        publicKey: pki.rsa.setPublicKey(state.n, state.e)
+      };
+    }
+
+    // update timing
+    t2 = +new Date();
+    total += t2 - t1;
+    t1 = t2;
+  }
+
+  return state.keys !== null;
+};
+
+/**
+ * Generates an RSA public-private key pair in a single call.
+ *
+ * To generate a key-pair in steps (to allow for progress updates and to
+ * prevent blocking or warnings in slow browsers) then use the key-pair
+ * generation state functions.
+ *
+ * To generate a key-pair asynchronously (either through web-workers, if
+ * available, or by breaking up the work on the main thread), pass a
+ * callback function.
+ *
+ * @param [bits] the size for the private key in bits, defaults to 2048.
+ * @param [e] the public exponent to use, defaults to 65537.
+ * @param [options] options for key-pair generation, if given then 'bits'
+ *          and 'e' must *not* be given:
+ *          bits the size for the private key in bits, (default: 2048).
+ *          e the public exponent to use, (default: 65537 (0x10001)).
+ *          workerScript the worker script URL.
+ *          workers the number of web workers (if supported) to use,
+ *            (default: 2).
+ *          workLoad the size of the work load, ie: number of possible prime
+ *            numbers for each web worker to check per work assignment,
+ *            (default: 100).
+ *          e the public exponent to use, defaults to 65537.
+ *          prng a custom crypto-secure pseudo-random number generator to use,
+ *            that must define "getBytesSync".
+ *          algorithm the algorithm to use (default: 'PRIMEINC').
+ * @param [callback(err, keypair)] called once the operation completes.
+ *
+ * @return an object with privateKey and publicKey properties.
+ */
+pki.rsa.generateKeyPair = function(bits, e, options, callback) {
+  // (bits), (options), (callback)
+  if(arguments.length === 1) {
+    if(typeof bits === 'object') {
+      options = bits;
+      bits = undefined;
+    } else if(typeof bits === 'function') {
+      callback = bits;
+      bits = undefined;
+    }
+  } else if(arguments.length === 2) {
+    // (bits, e), (bits, options), (bits, callback), (options, callback)
+    if(typeof bits === 'number') {
+      if(typeof e === 'function') {
+        callback = e;
+        e = undefined;
+      } else if(typeof e !== 'number') {
+        options = e;
+        e = undefined;
+      }
+    } else {
+      options = bits;
+      callback = e;
+      bits = undefined;
+      e = undefined;
+    }
+  } else if(arguments.length === 3) {
+    // (bits, e, options), (bits, e, callback), (bits, options, callback)
+    if(typeof e === 'number') {
+      if(typeof options === 'function') {
+        callback = options;
+        options = undefined;
+      }
+    } else {
+      callback = options;
+      options = e;
+      e = undefined;
+    }
+  }
+  options = options || {};
+  if(bits === undefined) {
+    bits = options.bits || 2048;
+  }
+  if(e === undefined) {
+    e = options.e || 0x10001;
+  }
+  var state = pki.rsa.createKeyPairGenerationState(bits, e, options);
+  if(!callback) {
+    pki.rsa.stepKeyPairGenerationState(state, 0);
+    return state.keys;
+  }
+  _generateKeyPair(state, options, callback);
+};
+
+/**
+ * Sets an RSA public key from BigIntegers modulus and exponent.
+ *
+ * @param n the modulus.
+ * @param e the exponent.
+ *
+ * @return the public key.
+ */
+pki.setRsaPublicKey = pki.rsa.setPublicKey = function(n, e) {
+  var key = {
+    n: n,
+    e: e
+  };
+
+  /**
+   * Encrypts the given data with this public key. Newer applications
+   * should use the 'RSA-OAEP' decryption scheme, 'RSAES-PKCS1-V1_5' is for
+   * legacy applications.
+   *
+   * @param data the byte string to encrypt.
+   * @param scheme the encryption scheme to use:
+   *          'RSAES-PKCS1-V1_5' (default),
+   *          'RSA-OAEP',
+   *          'RAW', 'NONE', or null to perform raw RSA encryption,
+   *          an object with an 'encode' property set to a function
+   *          with the signature 'function(data, key)' that returns
+   *          a binary-encoded string representing the encoded data.
+   * @param schemeOptions any scheme-specific options.
+   *
+   * @return the encrypted byte string.
+   */
+  key.encrypt = function(data, scheme, schemeOptions) {
+    if(typeof scheme === 'string') {
+      scheme = scheme.toUpperCase();
+    } else if(scheme === undefined) {
+      scheme = 'RSAES-PKCS1-V1_5';
+    }
+
+    if(scheme === 'RSAES-PKCS1-V1_5') {
+      scheme = {
+        encode: function(m, key, pub) {
+          return _encodePkcs1_v1_5(m, key, 0x02).getBytes();
+        }
+      };
+    } else if(scheme === 'RSA-OAEP' || scheme === 'RSAES-OAEP') {
+      scheme = {
+        encode: function(m, key) {
+          return forge.pkcs1.encode_rsa_oaep(key, m, schemeOptions);
+        }
+      };
+    } else if(['RAW', 'NONE', 'NULL', null].indexOf(scheme) !== -1) {
+      scheme = { encode: function(e) { return e; } };
+    } else if(typeof scheme === 'string') {
+      throw new Error('Unsupported encryption scheme: "' + scheme + '".');
+    }
+
+    // do scheme-based encoding then rsa encryption
+    var e = scheme.encode(data, key, true);
+    return pki.rsa.encrypt(e, key, true);
+  };
+
+  /**
+   * Verifies the given signature against the given digest.
+   *
+   * PKCS#1 supports multiple (currently two) signature schemes:
+   * RSASSA-PKCS1-V1_5 and RSASSA-PSS.
+   *
+   * By default this implementation uses the "old scheme", i.e.
+   * RSASSA-PKCS1-V1_5, in which case once RSA-decrypted, the
+   * signature is an OCTET STRING that holds a DigestInfo.
+   *
+   * DigestInfo ::= SEQUENCE {
+   *   digestAlgorithm DigestAlgorithmIdentifier,
+   *   digest Digest
+   * }
+   * DigestAlgorithmIdentifier ::= AlgorithmIdentifier
+   * Digest ::= OCTET STRING
+   *
+   * To perform PSS signature verification, provide an instance
+   * of Forge PSS object as the scheme parameter.
+   *
+   * @param digest the message digest hash to compare against the signature,
+   *          as a binary-encoded string.
+   * @param signature the signature to verify, as a binary-encoded string.
+   * @param scheme signature verification scheme to use:
+   *          'RSASSA-PKCS1-V1_5' or undefined for RSASSA PKCS#1 v1.5,
+   *          a Forge PSS object for RSASSA-PSS,
+   *          'NONE' or null for none, DigestInfo will not be expected, but
+   *            PKCS#1 v1.5 padding will still be used.
+   *
+   * @return true if the signature was verified, false if not.
+   */
+   key.verify = function(digest, signature, scheme) {
+     if(typeof scheme === 'string') {
+       scheme = scheme.toUpperCase();
+     } else if(scheme === undefined) {
+       scheme = 'RSASSA-PKCS1-V1_5';
+     }
+
+     if(scheme === 'RSASSA-PKCS1-V1_5') {
+       scheme = {
+         verify: function(digest, d) {
+           // remove padding
+           d = _decodePkcs1_v1_5(d, key, true);
+           // d is ASN.1 BER-encoded DigestInfo
+           var obj = asn1.fromDer(d);
+           // compare the given digest to the decrypted one
+           return digest === obj.value[1].value;
+         }
+       };
+     } else if(scheme === 'NONE' || scheme === 'NULL' || scheme === null) {
+       scheme = {
+         verify: function(digest, d) {
+           // remove padding
+           d = _decodePkcs1_v1_5(d, key, true);
+           return digest === d;
+         }
+       };
+     }
+
+     // do rsa decryption w/o any decoding, then verify -- which does decoding
+     var d = pki.rsa.decrypt(signature, key, true, false);
+     return scheme.verify(digest, d, key.n.bitLength());
+  };
+
+  return key;
+};
+
+/**
+ * Sets an RSA private key from BigIntegers modulus, exponent, primes,
+ * prime exponents, and modular multiplicative inverse.
+ *
+ * @param n the modulus.
+ * @param e the public exponent.
+ * @param d the private exponent ((inverse of e) mod n).
+ * @param p the first prime.
+ * @param q the second prime.
+ * @param dP exponent1 (d mod (p-1)).
+ * @param dQ exponent2 (d mod (q-1)).
+ * @param qInv ((inverse of q) mod p)
+ *
+ * @return the private key.
+ */
+pki.setRsaPrivateKey = pki.rsa.setPrivateKey = function(
+  n, e, d, p, q, dP, dQ, qInv) {
+  var key = {
+    n: n,
+    e: e,
+    d: d,
+    p: p,
+    q: q,
+    dP: dP,
+    dQ: dQ,
+    qInv: qInv
+  };
+
+  /**
+   * Decrypts the given data with this private key. The decryption scheme
+   * must match the one used to encrypt the data.
+   *
+   * @param data the byte string to decrypt.
+   * @param scheme the decryption scheme to use:
+   *          'RSAES-PKCS1-V1_5' (default),
+   *          'RSA-OAEP',
+   *          'RAW', 'NONE', or null to perform raw RSA decryption.
+   * @param schemeOptions any scheme-specific options.
+   *
+   * @return the decrypted byte string.
+   */
+  key.decrypt = function(data, scheme, schemeOptions) {
+    if(typeof scheme === 'string') {
+      scheme = scheme.toUpperCase();
+    } else if(scheme === undefined) {
+      scheme = 'RSAES-PKCS1-V1_5';
+    }
+
+    // do rsa decryption w/o any decoding
+    var d = pki.rsa.decrypt(data, key, false, false);
+
+    if(scheme === 'RSAES-PKCS1-V1_5') {
+      scheme = { decode: _decodePkcs1_v1_5 };
+    } else if(scheme === 'RSA-OAEP' || scheme === 'RSAES-OAEP') {
+      scheme = {
+        decode: function(d, key) {
+          return forge.pkcs1.decode_rsa_oaep(key, d, schemeOptions);
+        }
+      };
+    } else if(['RAW', 'NONE', 'NULL', null].indexOf(scheme) !== -1) {
+      scheme = { decode: function(d) { return d; } };
+    } else {
+      throw new Error('Unsupported encryption scheme: "' + scheme + '".');
+    }
+
+    // decode according to scheme
+    return scheme.decode(d, key, false);
+  };
+
+  /**
+   * Signs the given digest, producing a signature.
+   *
+   * PKCS#1 supports multiple (currently two) signature schemes:
+   * RSASSA-PKCS1-V1_5 and RSASSA-PSS.
+   *
+   * By default this implementation uses the "old scheme", i.e.
+   * RSASSA-PKCS1-V1_5. In order to generate a PSS signature, provide
+   * an instance of Forge PSS object as the scheme parameter.
+   *
+   * @param md the message digest object with the hash to sign.
+   * @param scheme the signature scheme to use:
+   *          'RSASSA-PKCS1-V1_5' or undefined for RSASSA PKCS#1 v1.5,
+   *          a Forge PSS object for RSASSA-PSS,
+   *          'NONE' or null for none, DigestInfo will not be used but
+   *            PKCS#1 v1.5 padding will still be used.
+   *
+   * @return the signature as a byte string.
+   */
+  key.sign = function(md, scheme) {
+    /* Note: The internal implementation of RSA operations is being
+      transitioned away from a PKCS#1 v1.5 hard-coded scheme. Some legacy
+      code like the use of an encoding block identifier 'bt' will eventually
+      be removed. */
+
+    // private key operation
+    var bt = false;
+
+    if(typeof scheme === 'string') {
+      scheme = scheme.toUpperCase();
+    }
+
+    if(scheme === undefined || scheme === 'RSASSA-PKCS1-V1_5') {
+      scheme = { encode: emsaPkcs1v15encode };
+      bt = 0x01;
+    } else if(scheme === 'NONE' || scheme === 'NULL' || scheme === null) {
+      scheme = { encode: function() { return md; } };
+      bt = 0x01;
+    }
+
+    // encode and then encrypt
+    var d = scheme.encode(md, key.n.bitLength());
+    return pki.rsa.encrypt(d, key, bt);
+  };
+
+  return key;
+};
+
+/**
+ * Wraps an RSAPrivateKey ASN.1 object in an ASN.1 PrivateKeyInfo object.
+ *
+ * @param rsaKey the ASN.1 RSAPrivateKey.
+ *
+ * @return the ASN.1 PrivateKeyInfo.
+ */
+pki.wrapRsaPrivateKey = function(rsaKey) {
+  // PrivateKeyInfo
+  return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+    // version (0)
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      asn1.integerToDer(0).getBytes()),
+    // privateKeyAlgorithm
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+      asn1.create(
+        asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+        asn1.oidToDer(pki.oids.rsaEncryption).getBytes()),
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
+    ]),
+    // PrivateKey
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
+      asn1.toDer(rsaKey).getBytes())
+    ]);
+};
+
+/**
+ * Converts a private key from an ASN.1 object.
+ *
+ * @param obj the ASN.1 representation of a PrivateKeyInfo containing an
+ *          RSAPrivateKey or an RSAPrivateKey.
+ *
+ * @return the private key.
+ */
+pki.privateKeyFromAsn1 = function(obj) {
+  // get PrivateKeyInfo
+  var capture = {};
+  var errors = [];
+  if(asn1.validate(obj, privateKeyValidator, capture, errors)) {
+    obj = asn1.fromDer(forge.util.createBuffer(capture.privateKey));
+  }
+
+  // get RSAPrivateKey
+  capture = {};
+  errors = [];
+  if(!asn1.validate(obj, rsaPrivateKeyValidator, capture, errors)) {
+    var error = new Error('Cannot read private key. ' +
+      'ASN.1 object does not contain an RSAPrivateKey.');
+    error.errors = errors;
+    throw error;
+  }
+
+  // Note: Version is currently ignored.
+  // capture.privateKeyVersion
+  // FIXME: inefficient, get a BigInteger that uses byte strings
+  var n, e, d, p, q, dP, dQ, qInv;
+  n = forge.util.createBuffer(capture.privateKeyModulus).toHex();
+  e = forge.util.createBuffer(capture.privateKeyPublicExponent).toHex();
+  d = forge.util.createBuffer(capture.privateKeyPrivateExponent).toHex();
+  p = forge.util.createBuffer(capture.privateKeyPrime1).toHex();
+  q = forge.util.createBuffer(capture.privateKeyPrime2).toHex();
+  dP = forge.util.createBuffer(capture.privateKeyExponent1).toHex();
+  dQ = forge.util.createBuffer(capture.privateKeyExponent2).toHex();
+  qInv = forge.util.createBuffer(capture.privateKeyCoefficient).toHex();
+
+  // set private key
+  return pki.setRsaPrivateKey(
+    new BigInteger(n, 16),
+    new BigInteger(e, 16),
+    new BigInteger(d, 16),
+    new BigInteger(p, 16),
+    new BigInteger(q, 16),
+    new BigInteger(dP, 16),
+    new BigInteger(dQ, 16),
+    new BigInteger(qInv, 16));
+};
+
+/**
+ * Converts a private key to an ASN.1 RSAPrivateKey.
+ *
+ * @param key the private key.
+ *
+ * @return the ASN.1 representation of an RSAPrivateKey.
+ */
+pki.privateKeyToAsn1 = pki.privateKeyToRSAPrivateKey = function(key) {
+  // RSAPrivateKey
+  return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+    // version (0 = only 2 primes, 1 multiple primes)
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      asn1.integerToDer(0).getBytes()),
+    // modulus (n)
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      _bnToBytes(key.n)),
+    // publicExponent (e)
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      _bnToBytes(key.e)),
+    // privateExponent (d)
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      _bnToBytes(key.d)),
+    // privateKeyPrime1 (p)
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      _bnToBytes(key.p)),
+    // privateKeyPrime2 (q)
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      _bnToBytes(key.q)),
+    // privateKeyExponent1 (dP)
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      _bnToBytes(key.dP)),
+    // privateKeyExponent2 (dQ)
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      _bnToBytes(key.dQ)),
+    // coefficient (qInv)
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      _bnToBytes(key.qInv))
+  ]);
+};
+
+/**
+ * Converts a public key from an ASN.1 SubjectPublicKeyInfo or RSAPublicKey.
+ *
+ * @param obj the asn1 representation of a SubjectPublicKeyInfo or RSAPublicKey.
+ *
+ * @return the public key.
+ */
+pki.publicKeyFromAsn1 = function(obj) {
+  // get SubjectPublicKeyInfo
+  var capture = {};
+  var errors = [];
+  if(asn1.validate(obj, publicKeyValidator, capture, errors)) {
+    // get oid
+    var oid = asn1.derToOid(capture.publicKeyOid);
+    if(oid !== pki.oids.rsaEncryption) {
+      var error = new Error('Cannot read public key. Unknown OID.');
+      error.oid = oid;
+      throw error;
+    }
+    obj = capture.rsaPublicKey;
+  }
+
+  // get RSA params
+  errors = [];
+  if(!asn1.validate(obj, rsaPublicKeyValidator, capture, errors)) {
+    var error = new Error('Cannot read public key. ' +
+      'ASN.1 object does not contain an RSAPublicKey.');
+    error.errors = errors;
+    throw error;
+  }
+
+  // FIXME: inefficient, get a BigInteger that uses byte strings
+  var n = forge.util.createBuffer(capture.publicKeyModulus).toHex();
+  var e = forge.util.createBuffer(capture.publicKeyExponent).toHex();
+
+  // set public key
+  return pki.setRsaPublicKey(
+    new BigInteger(n, 16),
+    new BigInteger(e, 16));
+};
+
+/**
+ * Converts a public key to an ASN.1 SubjectPublicKeyInfo.
+ *
+ * @param key the public key.
+ *
+ * @return the asn1 representation of a SubjectPublicKeyInfo.
+ */
+pki.publicKeyToAsn1 = pki.publicKeyToSubjectPublicKeyInfo = function(key) {
+  // SubjectPublicKeyInfo
+  return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+    // AlgorithmIdentifier
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+      // algorithm
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+        asn1.oidToDer(pki.oids.rsaEncryption).getBytes()),
+      // parameters (null)
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
+    ]),
+    // subjectPublicKey
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.BITSTRING, false, [
+      pki.publicKeyToRSAPublicKey(key)
+    ])
+  ]);
+};
+
+/**
+ * Converts a public key to an ASN.1 RSAPublicKey.
+ *
+ * @param key the public key.
+ *
+ * @return the asn1 representation of a RSAPublicKey.
+ */
+pki.publicKeyToRSAPublicKey = function(key) {
+  // RSAPublicKey
+  return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+    // modulus (n)
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      _bnToBytes(key.n)),
+    // publicExponent (e)
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      _bnToBytes(key.e))
+  ]);
+};
+
+/**
+ * Encodes a message using PKCS#1 v1.5 padding.
+ *
+ * @param m the message to encode.
+ * @param key the RSA key to use.
+ * @param bt the block type to use, i.e. either 0x01 (for signing) or 0x02
+ *          (for encryption).
+ *
+ * @return the padded byte buffer.
+ */
+function _encodePkcs1_v1_5(m, key, bt) {
+  var eb = forge.util.createBuffer();
+
+  // get the length of the modulus in bytes
+  var k = Math.ceil(key.n.bitLength() / 8);
+
+  /* use PKCS#1 v1.5 padding */
+  if(m.length > (k - 11)) {
+    var error = new Error('Message is too long for PKCS#1 v1.5 padding.');
+    error.length = m.length;
+    error.max = k - 11;
+    throw error;
+  }
+
+  /* A block type BT, a padding string PS, and the data D shall be
+    formatted into an octet string EB, the encryption block:
+
+    EB = 00 || BT || PS || 00 || D
+
+    The block type BT shall be a single octet indicating the structure of
+    the encryption block. For this version of the document it shall have
+    value 00, 01, or 02. For a private-key operation, the block type
+    shall be 00 or 01. For a public-key operation, it shall be 02.
+
+    The padding string PS shall consist of k-3-||D|| octets. For block
+    type 00, the octets shall have value 00; for block type 01, they
+    shall have value FF; and for block type 02, they shall be
+    pseudorandomly generated and nonzero. This makes the length of the
+    encryption block EB equal to k. */
+
+  // build the encryption block
+  eb.putByte(0x00);
+  eb.putByte(bt);
+
+  // create the padding
+  var padNum = k - 3 - m.length;
+  var padByte;
+  // private key op
+  if(bt === 0x00 || bt === 0x01) {
+    padByte = (bt === 0x00) ? 0x00 : 0xFF;
+    for(var i = 0; i < padNum; ++i) {
+      eb.putByte(padByte);
+    }
+  } else {
+    // public key op
+    // pad with random non-zero values
+    while(padNum > 0) {
+      var numZeros = 0;
+      var padBytes = forge.random.getBytes(padNum);
+      for(var i = 0; i < padNum; ++i) {
+        padByte = padBytes.charCodeAt(i);
+        if(padByte === 0) {
+          ++numZeros;
+        } else {
+          eb.putByte(padByte);
+        }
+      }
+      padNum = numZeros;
+    }
+  }
+
+  // zero followed by message
+  eb.putByte(0x00);
+  eb.putBytes(m);
+
+  return eb;
+}
+
+/**
+ * Decodes a message using PKCS#1 v1.5 padding.
+ *
+ * @param em the message to decode.
+ * @param key the RSA key to use.
+ * @param pub true if the key is a public key, false if it is private.
+ * @param ml the message length, if specified.
+ *
+ * @return the decoded bytes.
+ */
+function _decodePkcs1_v1_5(em, key, pub, ml) {
+  // get the length of the modulus in bytes
+  var k = Math.ceil(key.n.bitLength() / 8);
+
+  /* It is an error if any of the following conditions occurs:
+
+    1. The encryption block EB cannot be parsed unambiguously.
+    2. The padding string PS consists of fewer than eight octets
+      or is inconsisent with the block type BT.
+    3. The decryption process is a public-key operation and the block
+      type BT is not 00 or 01, or the decryption process is a
+      private-key operation and the block type is not 02.
+   */
+
+  // parse the encryption block
+  var eb = forge.util.createBuffer(em);
+  var first = eb.getByte();
+  var bt = eb.getByte();
+  if(first !== 0x00 ||
+    (pub && bt !== 0x00 && bt !== 0x01) ||
+    (!pub && bt != 0x02) ||
+    (pub && bt === 0x00 && typeof(ml) === 'undefined')) {
+    throw new Error('Encryption block is invalid.');
+  }
+
+  var padNum = 0;
+  if(bt === 0x00) {
+    // check all padding bytes for 0x00
+    padNum = k - 3 - ml;
+    for(var i = 0; i < padNum; ++i) {
+      if(eb.getByte() !== 0x00) {
+        throw new Error('Encryption block is invalid.');
+      }
+    }
+  } else if(bt === 0x01) {
+    // find the first byte that isn't 0xFF, should be after all padding
+    padNum = 0;
+    while(eb.length() > 1) {
+      if(eb.getByte() !== 0xFF) {
+        --eb.read;
+        break;
+      }
+      ++padNum;
+    }
+  } else if(bt === 0x02) {
+    // look for 0x00 byte
+    padNum = 0;
+    while(eb.length() > 1) {
+      if(eb.getByte() === 0x00) {
+        --eb.read;
+        break;
+      }
+      ++padNum;
+    }
+  }
+
+  // zero must be 0x00 and padNum must be (k - 3 - message length)
+  var zero = eb.getByte();
+  if(zero !== 0x00 || padNum !== (k - 3 - eb.length())) {
+    throw new Error('Encryption block is invalid.');
+  }
+
+  return eb.getBytes();
+}
+
+/**
+ * Runs the key-generation algorithm asynchronously, either in the background
+ * via Web Workers, or using the main thread and setImmediate.
+ *
+ * @param state the key-pair generation state.
+ * @param [options] options for key-pair generation:
+ *          workerScript the worker script URL.
+ *          workers the number of web workers (if supported) to use,
+ *            (default: 2, -1 to use estimated cores minus one).
+ *          workLoad the size of the work load, ie: number of possible prime
+ *            numbers for each web worker to check per work assignment,
+ *            (default: 100).
+ * @param callback(err, keypair) called once the operation completes.
+ */
+function _generateKeyPair(state, options, callback) {
+  if(typeof options === 'function') {
+    callback = options;
+    options = {};
+  }
+  options = options || {};
+
+  var opts = {
+    algorithm: {
+      name: options.algorithm || 'PRIMEINC',
+      options: {
+        workers: options.workers || 2,
+        workLoad: options.workLoad || 100,
+        workerScript: options.workerScript
+      }
+    }
+  };
+  if('prng' in options) {
+    opts.prng = options.prng;
+  }
+
+  generate();
+
+  function generate() {
+    // find p and then q (done in series to simplify)
+    getPrime(state.pBits, function(err, num) {
+      if(err) {
+        return callback(err);
+      }
+      state.p = num;
+      if(state.q !== null) {
+        return finish(err, state.q);
+      }
+      getPrime(state.qBits, finish);
+    });
+  }
+
+  function getPrime(bits, callback) {
+    forge.prime.generateProbablePrime(bits, opts, callback);
+  }
+
+  function finish(err, num) {
+    if(err) {
+      return callback(err);
+    }
+
+    // set q
+    state.q = num;
+
+    // ensure p is larger than q (swap them if not)
+    if(state.p.compareTo(state.q) < 0) {
+      var tmp = state.p;
+      state.p = state.q;
+      state.q = tmp;
+    }
+
+    // ensure p is coprime with e
+    if(state.p.subtract(BigInteger.ONE).gcd(state.e)
+      .compareTo(BigInteger.ONE) !== 0) {
+      state.p = null;
+      generate();
+      return;
+    }
+
+    // ensure q is coprime with e
+    if(state.q.subtract(BigInteger.ONE).gcd(state.e)
+      .compareTo(BigInteger.ONE) !== 0) {
+      state.q = null;
+      getPrime(state.qBits, finish);
+      return;
+    }
+
+    // compute phi: (p - 1)(q - 1) (Euler's totient function)
+    state.p1 = state.p.subtract(BigInteger.ONE);
+    state.q1 = state.q.subtract(BigInteger.ONE);
+    state.phi = state.p1.multiply(state.q1);
+
+    // ensure e and phi are coprime
+    if(state.phi.gcd(state.e).compareTo(BigInteger.ONE) !== 0) {
+      // phi and e aren't coprime, so generate a new p and q
+      state.p = state.q = null;
+      generate();
+      return;
+    }
+
+    // create n, ensure n is has the right number of bits
+    state.n = state.p.multiply(state.q);
+    if(state.n.bitLength() !== state.bits) {
+      // failed, get new q
+      state.q = null;
+      getPrime(state.qBits, finish);
+      return;
+    }
+
+    // set keys
+    var d = state.e.modInverse(state.phi);
+    state.keys = {
+      privateKey: pki.rsa.setPrivateKey(
+        state.n, state.e, d, state.p, state.q,
+        d.mod(state.p1), d.mod(state.q1),
+        state.q.modInverse(state.p)),
+      publicKey: pki.rsa.setPublicKey(state.n, state.e)
+    };
+
+    callback(null, state.keys);
+  }
+}
+
+/**
+ * Converts a positive BigInteger into 2's-complement big-endian bytes.
+ *
+ * @param b the big integer to convert.
+ *
+ * @return the bytes.
+ */
+function _bnToBytes(b) {
+  // prepend 0x00 if first byte >= 0x80
+  var hex = b.toString(16);
+  if(hex[0] >= '8') {
+    hex = '00' + hex;
+  }
+  return forge.util.hexToBytes(hex);
+}
+
+/**
+ * Returns the required number of Miller-Rabin tests to generate a
+ * prime with an error probability of (1/2)^80.
+ *
+ * See Handbook of Applied Cryptography Chapter 4, Table 4.4.
+ *
+ * @param bits the bit size.
+ *
+ * @return the required number of iterations.
+ */
+function _getMillerRabinTests(bits) {
+  if(bits <= 100) return 27;
+  if(bits <= 150) return 18;
+  if(bits <= 200) return 15;
+  if(bits <= 250) return 12;
+  if(bits <= 300) return 9;
+  if(bits <= 350) return 8;
+  if(bits <= 400) return 7;
+  if(bits <= 500) return 6;
+  if(bits <= 600) return 5;
+  if(bits <= 800) return 4;
+  if(bits <= 1250) return 3;
+  return 2;
+}
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'rsa';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define([
+  'require',
+  'module',
+  './asn1',
+  './jsbn',
+  './oids',
+  './pkcs1',
+  './prime',
+  './random',
+  './util'
+], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/sha1.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/sha1.js
new file mode 100644
index 0000000..53f65d2
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/sha1.js
@@ -0,0 +1,342 @@
+/**
+ * Secure Hash Algorithm with 160-bit digest (SHA-1) implementation.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+var sha1 = forge.sha1 = forge.sha1 || {};
+forge.md = forge.md || {};
+forge.md.algorithms = forge.md.algorithms || {};
+forge.md.sha1 = forge.md.algorithms.sha1 = sha1;
+
+/**
+ * Creates a SHA-1 message digest object.
+ *
+ * @return a message digest object.
+ */
+sha1.create = function() {
+  // do initialization as necessary
+  if(!_initialized) {
+    _init();
+  }
+
+  // SHA-1 state contains five 32-bit integers
+  var _state = null;
+
+  // input buffer
+  var _input = forge.util.createBuffer();
+
+  // used for word storage
+  var _w = new Array(80);
+
+  // message digest object
+  var md = {
+    algorithm: 'sha1',
+    blockLength: 64,
+    digestLength: 20,
+    // 56-bit length of message so far (does not including padding)
+    messageLength: 0,
+    // true 64-bit message length as two 32-bit ints
+    messageLength64: [0, 0]
+  };
+
+  /**
+   * Starts the digest.
+   *
+   * @return this digest object.
+   */
+  md.start = function() {
+    md.messageLength = 0;
+    md.messageLength64 = [0, 0];
+    _input = forge.util.createBuffer();
+    _state = {
+      h0: 0x67452301,
+      h1: 0xEFCDAB89,
+      h2: 0x98BADCFE,
+      h3: 0x10325476,
+      h4: 0xC3D2E1F0
+    };
+    return md;
+  };
+  // start digest automatically for first time
+  md.start();
+
+  /**
+   * Updates the digest with the given message input. The given input can
+   * treated as raw input (no encoding will be applied) or an encoding of
+   * 'utf8' maybe given to encode the input using UTF-8.
+   *
+   * @param msg the message input to update with.
+   * @param encoding the encoding to use (default: 'raw', other: 'utf8').
+   *
+   * @return this digest object.
+   */
+  md.update = function(msg, encoding) {
+    if(encoding === 'utf8') {
+      msg = forge.util.encodeUtf8(msg);
+    }
+
+    // update message length
+    md.messageLength += msg.length;
+    md.messageLength64[0] += (msg.length / 0x100000000) >>> 0;
+    md.messageLength64[1] += msg.length >>> 0;
+
+    // add bytes to input buffer
+    _input.putBytes(msg);
+
+    // process bytes
+    _update(_state, _w, _input);
+
+    // compact input buffer every 2K or if empty
+    if(_input.read > 2048 || _input.length() === 0) {
+      _input.compact();
+    }
+
+    return md;
+  };
+
+   /**
+    * Produces the digest.
+    *
+    * @return a byte buffer containing the digest value.
+    */
+   md.digest = function() {
+    /* Note: Here we copy the remaining bytes in the input buffer and
+    add the appropriate SHA-1 padding. Then we do the final update
+    on a copy of the state so that if the user wants to get
+    intermediate digests they can do so. */
+
+    /* Determine the number of bytes that must be added to the message
+    to ensure its length is congruent to 448 mod 512. In other words,
+    the data to be digested must be a multiple of 512 bits (or 128 bytes).
+    This data includes the message, some padding, and the length of the
+    message. Since the length of the message will be encoded as 8 bytes (64
+    bits), that means that the last segment of the data must have 56 bytes
+    (448 bits) of message and padding. Therefore, the length of the message
+    plus the padding must be congruent to 448 mod 512 because
+    512 - 128 = 448.
+
+    In order to fill up the message length it must be filled with
+    padding that begins with 1 bit followed by all 0 bits. Padding
+    must *always* be present, so if the message length is already
+    congruent to 448 mod 512, then 512 padding bits must be added. */
+
+    // 512 bits == 64 bytes, 448 bits == 56 bytes, 64 bits = 8 bytes
+    // _padding starts with 1 byte with first bit is set in it which
+    // is byte value 128, then there may be up to 63 other pad bytes
+    var padBytes = forge.util.createBuffer();
+    padBytes.putBytes(_input.bytes());
+    // 64 - (remaining msg + 8 bytes msg length) mod 64
+    padBytes.putBytes(
+      _padding.substr(0, 64 - ((md.messageLength64[1] + 8) & 0x3F)));
+
+    /* Now append length of the message. The length is appended in bits
+    as a 64-bit number in big-endian order. Since we store the length in
+    bytes, we must multiply the 64-bit length by 8 (or left shift by 3). */
+    padBytes.putInt32(
+      (md.messageLength64[0] << 3) | (md.messageLength64[0] >>> 28));
+    padBytes.putInt32(md.messageLength64[1] << 3);
+    var s2 = {
+      h0: _state.h0,
+      h1: _state.h1,
+      h2: _state.h2,
+      h3: _state.h3,
+      h4: _state.h4
+    };
+    _update(s2, _w, padBytes);
+    var rval = forge.util.createBuffer();
+    rval.putInt32(s2.h0);
+    rval.putInt32(s2.h1);
+    rval.putInt32(s2.h2);
+    rval.putInt32(s2.h3);
+    rval.putInt32(s2.h4);
+    return rval;
+  };
+
+  return md;
+};
+
+// sha-1 padding bytes not initialized yet
+var _padding = null;
+var _initialized = false;
+
+/**
+ * Initializes the constant tables.
+ */
+function _init() {
+  // create padding
+  _padding = String.fromCharCode(128);
+  _padding += forge.util.fillString(String.fromCharCode(0x00), 64);
+
+  // now initialized
+  _initialized = true;
+}
+
+/**
+ * Updates a SHA-1 state with the given byte buffer.
+ *
+ * @param s the SHA-1 state to update.
+ * @param w the array to use to store words.
+ * @param bytes the byte buffer to update with.
+ */
+function _update(s, w, bytes) {
+  // consume 512 bit (64 byte) chunks
+  var t, a, b, c, d, e, f, i;
+  var len = bytes.length();
+  while(len >= 64) {
+    // the w array will be populated with sixteen 32-bit big-endian words
+    // and then extended into 80 32-bit words according to SHA-1 algorithm
+    // and for 32-79 using Max Locktyukhin's optimization
+
+    // initialize hash value for this chunk
+    a = s.h0;
+    b = s.h1;
+    c = s.h2;
+    d = s.h3;
+    e = s.h4;
+
+    // round 1
+    for(i = 0; i < 16; ++i) {
+      t = bytes.getInt32();
+      w[i] = t;
+      f = d ^ (b & (c ^ d));
+      t = ((a << 5) | (a >>> 27)) + f + e + 0x5A827999 + t;
+      e = d;
+      d = c;
+      c = (b << 30) | (b >>> 2);
+      b = a;
+      a = t;
+    }
+    for(; i < 20; ++i) {
+      t = (w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]);
+      t = (t << 1) | (t >>> 31);
+      w[i] = t;
+      f = d ^ (b & (c ^ d));
+      t = ((a << 5) | (a >>> 27)) + f + e + 0x5A827999 + t;
+      e = d;
+      d = c;
+      c = (b << 30) | (b >>> 2);
+      b = a;
+      a = t;
+    }
+    // round 2
+    for(; i < 32; ++i) {
+      t = (w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]);
+      t = (t << 1) | (t >>> 31);
+      w[i] = t;
+      f = b ^ c ^ d;
+      t = ((a << 5) | (a >>> 27)) + f + e + 0x6ED9EBA1 + t;
+      e = d;
+      d = c;
+      c = (b << 30) | (b >>> 2);
+      b = a;
+      a = t;
+    }
+    for(; i < 40; ++i) {
+      t = (w[i - 6] ^ w[i - 16] ^ w[i - 28] ^ w[i - 32]);
+      t = (t << 2) | (t >>> 30);
+      w[i] = t;
+      f = b ^ c ^ d;
+      t = ((a << 5) | (a >>> 27)) + f + e + 0x6ED9EBA1 + t;
+      e = d;
+      d = c;
+      c = (b << 30) | (b >>> 2);
+      b = a;
+      a = t;
+    }
+    // round 3
+    for(; i < 60; ++i) {
+      t = (w[i - 6] ^ w[i - 16] ^ w[i - 28] ^ w[i - 32]);
+      t = (t << 2) | (t >>> 30);
+      w[i] = t;
+      f = (b & c) | (d & (b ^ c));
+      t = ((a << 5) | (a >>> 27)) + f + e + 0x8F1BBCDC + t;
+      e = d;
+      d = c;
+      c = (b << 30) | (b >>> 2);
+      b = a;
+      a = t;
+    }
+    // round 4
+    for(; i < 80; ++i) {
+      t = (w[i - 6] ^ w[i - 16] ^ w[i - 28] ^ w[i - 32]);
+      t = (t << 2) | (t >>> 30);
+      w[i] = t;
+      f = b ^ c ^ d;
+      t = ((a << 5) | (a >>> 27)) + f + e + 0xCA62C1D6 + t;
+      e = d;
+      d = c;
+      c = (b << 30) | (b >>> 2);
+      b = a;
+      a = t;
+    }
+
+    // update hash state
+    s.h0 = (s.h0 + a) | 0;
+    s.h1 = (s.h1 + b) | 0;
+    s.h2 = (s.h2 + c) | 0;
+    s.h3 = (s.h3 + d) | 0;
+    s.h4 = (s.h4 + e) | 0;
+
+    len -= 64;
+  }
+}
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'sha1';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/sha256.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/sha256.js
new file mode 100644
index 0000000..fdbc4fc
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/sha256.js
@@ -0,0 +1,352 @@
+/**
+ * Secure Hash Algorithm with 256-bit digest (SHA-256) implementation.
+ *
+ * See FIPS 180-2 for details.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+var sha256 = forge.sha256 = forge.sha256 || {};
+forge.md = forge.md || {};
+forge.md.algorithms = forge.md.algorithms || {};
+forge.md.sha256 = forge.md.algorithms.sha256 = sha256;
+
+/**
+ * Creates a SHA-256 message digest object.
+ *
+ * @return a message digest object.
+ */
+sha256.create = function() {
+  // do initialization as necessary
+  if(!_initialized) {
+    _init();
+  }
+
+  // SHA-256 state contains eight 32-bit integers
+  var _state = null;
+
+  // input buffer
+  var _input = forge.util.createBuffer();
+
+  // used for word storage
+  var _w = new Array(64);
+
+  // message digest object
+  var md = {
+    algorithm: 'sha256',
+    blockLength: 64,
+    digestLength: 32,
+    // 56-bit length of message so far (does not including padding)
+    messageLength: 0,
+    // true 64-bit message length as two 32-bit ints
+    messageLength64: [0, 0]
+  };
+
+  /**
+   * Starts the digest.
+   *
+   * @return this digest object.
+   */
+  md.start = function() {
+    md.messageLength = 0;
+    md.messageLength64 = [0, 0];
+    _input = forge.util.createBuffer();
+    _state = {
+      h0: 0x6A09E667,
+      h1: 0xBB67AE85,
+      h2: 0x3C6EF372,
+      h3: 0xA54FF53A,
+      h4: 0x510E527F,
+      h5: 0x9B05688C,
+      h6: 0x1F83D9AB,
+      h7: 0x5BE0CD19
+    };
+    return md;
+  };
+  // start digest automatically for first time
+  md.start();
+
+  /**
+   * Updates the digest with the given message input. The given input can
+   * treated as raw input (no encoding will be applied) or an encoding of
+   * 'utf8' maybe given to encode the input using UTF-8.
+   *
+   * @param msg the message input to update with.
+   * @param encoding the encoding to use (default: 'raw', other: 'utf8').
+   *
+   * @return this digest object.
+   */
+  md.update = function(msg, encoding) {
+    if(encoding === 'utf8') {
+      msg = forge.util.encodeUtf8(msg);
+    }
+
+    // update message length
+    md.messageLength += msg.length;
+    md.messageLength64[0] += (msg.length / 0x100000000) >>> 0;
+    md.messageLength64[1] += msg.length >>> 0;
+
+    // add bytes to input buffer
+    _input.putBytes(msg);
+
+    // process bytes
+    _update(_state, _w, _input);
+
+    // compact input buffer every 2K or if empty
+    if(_input.read > 2048 || _input.length() === 0) {
+      _input.compact();
+    }
+
+    return md;
+  };
+
+  /**
+   * Produces the digest.
+   *
+   * @return a byte buffer containing the digest value.
+   */
+  md.digest = function() {
+    /* Note: Here we copy the remaining bytes in the input buffer and
+    add the appropriate SHA-256 padding. Then we do the final update
+    on a copy of the state so that if the user wants to get
+    intermediate digests they can do so. */
+
+    /* Determine the number of bytes that must be added to the message
+    to ensure its length is congruent to 448 mod 512. In other words,
+    the data to be digested must be a multiple of 512 bits (or 128 bytes).
+    This data includes the message, some padding, and the length of the
+    message. Since the length of the message will be encoded as 8 bytes (64
+    bits), that means that the last segment of the data must have 56 bytes
+    (448 bits) of message and padding. Therefore, the length of the message
+    plus the padding must be congruent to 448 mod 512 because
+    512 - 128 = 448.
+
+    In order to fill up the message length it must be filled with
+    padding that begins with 1 bit followed by all 0 bits. Padding
+    must *always* be present, so if the message length is already
+    congruent to 448 mod 512, then 512 padding bits must be added. */
+
+    // 512 bits == 64 bytes, 448 bits == 56 bytes, 64 bits = 8 bytes
+    // _padding starts with 1 byte with first bit is set in it which
+    // is byte value 128, then there may be up to 63 other pad bytes
+    var padBytes = forge.util.createBuffer();
+    padBytes.putBytes(_input.bytes());
+    // 64 - (remaining msg + 8 bytes msg length) mod 64
+    padBytes.putBytes(
+      _padding.substr(0, 64 - ((md.messageLength64[1] + 8) & 0x3F)));
+
+    /* Now append length of the message. The length is appended in bits
+    as a 64-bit number in big-endian order. Since we store the length in
+    bytes, we must multiply the 64-bit length by 8 (or left shift by 3). */
+    padBytes.putInt32(
+      (md.messageLength64[0] << 3) | (md.messageLength64[0] >>> 28));
+    padBytes.putInt32(md.messageLength64[1] << 3);
+    var s2 = {
+      h0: _state.h0,
+      h1: _state.h1,
+      h2: _state.h2,
+      h3: _state.h3,
+      h4: _state.h4,
+      h5: _state.h5,
+      h6: _state.h6,
+      h7: _state.h7
+    };
+    _update(s2, _w, padBytes);
+    var rval = forge.util.createBuffer();
+    rval.putInt32(s2.h0);
+    rval.putInt32(s2.h1);
+    rval.putInt32(s2.h2);
+    rval.putInt32(s2.h3);
+    rval.putInt32(s2.h4);
+    rval.putInt32(s2.h5);
+    rval.putInt32(s2.h6);
+    rval.putInt32(s2.h7);
+    return rval;
+  };
+
+  return md;
+};
+
+// sha-256 padding bytes not initialized yet
+var _padding = null;
+var _initialized = false;
+
+// table of constants
+var _k = null;
+
+/**
+ * Initializes the constant tables.
+ */
+function _init() {
+  // create padding
+  _padding = String.fromCharCode(128);
+  _padding += forge.util.fillString(String.fromCharCode(0x00), 64);
+
+  // create K table for SHA-256
+  _k = [
+    0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
+    0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+    0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
+    0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
+    0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
+    0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+    0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
+    0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
+    0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
+    0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+    0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
+    0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+    0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
+    0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
+    0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
+    0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2];
+
+  // now initialized
+  _initialized = true;
+}
+
+/**
+ * Updates a SHA-256 state with the given byte buffer.
+ *
+ * @param s the SHA-256 state to update.
+ * @param w the array to use to store words.
+ * @param bytes the byte buffer to update with.
+ */
+function _update(s, w, bytes) {
+  // consume 512 bit (64 byte) chunks
+  var t1, t2, s0, s1, ch, maj, i, a, b, c, d, e, f, g, h;
+  var len = bytes.length();
+  while(len >= 64) {
+    // the w array will be populated with sixteen 32-bit big-endian words
+    // and then extended into 64 32-bit words according to SHA-256
+    for(i = 0; i < 16; ++i) {
+      w[i] = bytes.getInt32();
+    }
+    for(; i < 64; ++i) {
+      // XOR word 2 words ago rot right 17, rot right 19, shft right 10
+      t1 = w[i - 2];
+      t1 =
+        ((t1 >>> 17) | (t1 << 15)) ^
+        ((t1 >>> 19) | (t1 << 13)) ^
+        (t1 >>> 10);
+      // XOR word 15 words ago rot right 7, rot right 18, shft right 3
+      t2 = w[i - 15];
+      t2 =
+        ((t2 >>> 7) | (t2 << 25)) ^
+        ((t2 >>> 18) | (t2 << 14)) ^
+        (t2 >>> 3);
+      // sum(t1, word 7 ago, t2, word 16 ago) modulo 2^32
+      w[i] = (t1 + w[i - 7] + t2 + w[i - 16]) | 0;
+    }
+
+    // initialize hash value for this chunk
+    a = s.h0;
+    b = s.h1;
+    c = s.h2;
+    d = s.h3;
+    e = s.h4;
+    f = s.h5;
+    g = s.h6;
+    h = s.h7;
+
+    // round function
+    for(i = 0; i < 64; ++i) {
+      // Sum1(e)
+      s1 =
+        ((e >>> 6) | (e << 26)) ^
+        ((e >>> 11) | (e << 21)) ^
+        ((e >>> 25) | (e << 7));
+      // Ch(e, f, g) (optimized the same way as SHA-1)
+      ch = g ^ (e & (f ^ g));
+      // Sum0(a)
+      s0 =
+        ((a >>> 2) | (a << 30)) ^
+        ((a >>> 13) | (a << 19)) ^
+        ((a >>> 22) | (a << 10));
+      // Maj(a, b, c) (optimized the same way as SHA-1)
+      maj = (a & b) | (c & (a ^ b));
+
+      // main algorithm
+      t1 = h + s1 + ch + _k[i] + w[i];
+      t2 = s0 + maj;
+      h = g;
+      g = f;
+      f = e;
+      e = (d + t1) | 0;
+      d = c;
+      c = b;
+      b = a;
+      a = (t1 + t2) | 0;
+    }
+
+    // update hash state
+    s.h0 = (s.h0 + a) | 0;
+    s.h1 = (s.h1 + b) | 0;
+    s.h2 = (s.h2 + c) | 0;
+    s.h3 = (s.h3 + d) | 0;
+    s.h4 = (s.h4 + e) | 0;
+    s.h5 = (s.h5 + f) | 0;
+    s.h6 = (s.h6 + g) | 0;
+    s.h7 = (s.h7 + h) | 0;
+    len -= 64;
+  }
+}
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'sha256';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/sha512.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/sha512.js
new file mode 100644
index 0000000..12a9d94
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/sha512.js
@@ -0,0 +1,590 @@
+/**
+ * Secure Hash Algorithm with a 1024-bit block size implementation.
+ *
+ * This includes: SHA-512, SHA-384, SHA-512/224, and SHA-512/256. For
+ * SHA-256 (block size 512 bits), see sha256.js.
+ *
+ * See FIPS 180-4 for details.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+var sha512 = forge.sha512 = forge.sha512 || {};
+forge.md = forge.md || {};
+forge.md.algorithms = forge.md.algorithms || {};
+
+// SHA-512
+forge.md.sha512 = forge.md.algorithms.sha512 = sha512;
+
+// SHA-384
+var sha384 = forge.sha384 = forge.sha512.sha384 = forge.sha512.sha384 || {};
+sha384.create = function() {
+  return sha512.create('SHA-384');
+};
+forge.md.sha384 = forge.md.algorithms.sha384 = sha384;
+
+// SHA-512/256
+forge.sha512.sha256 = forge.sha512.sha256 || {
+  create: function() {
+    return sha512.create('SHA-512/256');
+  }
+};
+forge.md['sha512/256'] = forge.md.algorithms['sha512/256'] =
+  forge.sha512.sha256;
+
+// SHA-512/224
+forge.sha512.sha224 = forge.sha512.sha224 || {
+  create: function() {
+    return sha512.create('SHA-512/224');
+  }
+};
+forge.md['sha512/224'] = forge.md.algorithms['sha512/224'] =
+  forge.sha512.sha224;
+
+/**
+ * Creates a SHA-2 message digest object.
+ *
+ * @param algorithm the algorithm to use (SHA-512, SHA-384, SHA-512/224,
+ *          SHA-512/256).
+ *
+ * @return a message digest object.
+ */
+sha512.create = function(algorithm) {
+  // do initialization as necessary
+  if(!_initialized) {
+    _init();
+  }
+
+  if(typeof algorithm === 'undefined') {
+    algorithm = 'SHA-512';
+  }
+
+  if(!(algorithm in _states)) {
+    throw new Error('Invalid SHA-512 algorithm: ' + algorithm);
+  }
+
+  // SHA-512 state contains eight 64-bit integers (each as two 32-bit ints)
+  var _state = _states[algorithm];
+  var _h = null;
+
+  // input buffer
+  var _input = forge.util.createBuffer();
+
+  // used for 64-bit word storage
+  var _w = new Array(80);
+  for(var wi = 0; wi < 80; ++wi) {
+    _w[wi] = new Array(2);
+  }
+
+  // message digest object
+  var md = {
+    // SHA-512 => sha512
+    algorithm: algorithm.replace('-', '').toLowerCase(),
+    blockLength: 128,
+    digestLength: 64,
+    // 56-bit length of message so far (does not including padding)
+    messageLength: 0,
+    // true 128-bit message length as four 32-bit ints
+    messageLength128: [0, 0, 0, 0]
+  };
+
+  /**
+   * Starts the digest.
+   *
+   * @return this digest object.
+   */
+  md.start = function() {
+    md.messageLength = 0;
+    md.messageLength128 = [0, 0, 0, 0];
+    _input = forge.util.createBuffer();
+    _h = new Array(_state.length);
+    for(var i = 0; i < _state.length; ++i) {
+      _h[i] = _state[i].slice(0);
+    }
+    return md;
+  };
+  // start digest automatically for first time
+  md.start();
+
+  /**
+   * Updates the digest with the given message input. The given input can
+   * treated as raw input (no encoding will be applied) or an encoding of
+   * 'utf8' maybe given to encode the input using UTF-8.
+   *
+   * @param msg the message input to update with.
+   * @param encoding the encoding to use (default: 'raw', other: 'utf8').
+   *
+   * @return this digest object.
+   */
+  md.update = function(msg, encoding) {
+    if(encoding === 'utf8') {
+      msg = forge.util.encodeUtf8(msg);
+    }
+
+    // update message length
+    md.messageLength += msg.length;
+    var len = msg.length;
+    len = [(len / 0x100000000) >>> 0, len >>> 0];
+    for(var i = 3; i >= 0; --i) {
+      md.messageLength128[i] += len[1];
+      len[1] = len[0] + ((md.messageLength128[i] / 0x100000000) >>> 0);
+      md.messageLength128[i] = md.messageLength128[i] >>> 0;
+      len[0] = ((len[1] / 0x100000000) >>> 0);
+    }
+
+    // add bytes to input buffer
+    _input.putBytes(msg);
+
+    // process bytes
+    _update(_h, _w, _input);
+
+    // compact input buffer every 2K or if empty
+    if(_input.read > 2048 || _input.length() === 0) {
+      _input.compact();
+    }
+
+    return md;
+  };
+
+  /**
+   * Produces the digest.
+   *
+   * @return a byte buffer containing the digest value.
+   */
+  md.digest = function() {
+    /* Note: Here we copy the remaining bytes in the input buffer and
+    add the appropriate SHA-512 padding. Then we do the final update
+    on a copy of the state so that if the user wants to get
+    intermediate digests they can do so. */
+
+    /* Determine the number of bytes that must be added to the message
+    to ensure its length is congruent to 896 mod 1024. In other words,
+    the data to be digested must be a multiple of 1024 bits (or 128 bytes).
+    This data includes the message, some padding, and the length of the
+    message. Since the length of the message will be encoded as 16 bytes (128
+    bits), that means that the last segment of the data must have 112 bytes
+    (896 bits) of message and padding. Therefore, the length of the message
+    plus the padding must be congruent to 896 mod 1024 because
+    1024 - 128 = 896.
+
+    In order to fill up the message length it must be filled with
+    padding that begins with 1 bit followed by all 0 bits. Padding
+    must *always* be present, so if the message length is already
+    congruent to 896 mod 1024, then 1024 padding bits must be added. */
+
+    // 1024 bits == 128 bytes, 896 bits == 112 bytes, 128 bits = 16 bytes
+    // _padding starts with 1 byte with first bit is set in it which
+    // is byte value 128, then there may be up to 127 other pad bytes
+    var padBytes = forge.util.createBuffer();
+    padBytes.putBytes(_input.bytes());
+    // 128 - (remaining msg + 16 bytes msg length) mod 128
+    padBytes.putBytes(
+      _padding.substr(0, 128 - ((md.messageLength128[3] + 16) & 0x7F)));
+
+    /* Now append length of the message. The length is appended in bits
+    as a 128-bit number in big-endian order. Since we store the length in
+    bytes, we must multiply the 128-bit length by 8 (or left shift by 3). */
+    var bitLength = [];
+    for(var i = 0; i < 3; ++i) {
+      bitLength[i] = ((md.messageLength128[i] << 3) |
+        (md.messageLength128[i - 1] >>> 28));
+    }
+    // shift the last integer normally
+    bitLength[3] = md.messageLength128[3] << 3;
+    padBytes.putInt32(bitLength[0]);
+    padBytes.putInt32(bitLength[1]);
+    padBytes.putInt32(bitLength[2]);
+    padBytes.putInt32(bitLength[3]);
+    var h = new Array(_h.length);
+    for(var i = 0; i < _h.length; ++i) {
+      h[i] = _h[i].slice(0);
+    }
+    _update(h, _w, padBytes);
+    var rval = forge.util.createBuffer();
+    var hlen;
+    if(algorithm === 'SHA-512') {
+      hlen = h.length;
+    } else if(algorithm === 'SHA-384') {
+      hlen = h.length - 2;
+    } else {
+      hlen = h.length - 4;
+    }
+    for(var i = 0; i < hlen; ++i) {
+      rval.putInt32(h[i][0]);
+      if(i !== hlen - 1 || algorithm !== 'SHA-512/224') {
+        rval.putInt32(h[i][1]);
+      }
+    }
+    return rval;
+  };
+
+  return md;
+};
+
+// sha-512 padding bytes not initialized yet
+var _padding = null;
+var _initialized = false;
+
+// table of constants
+var _k = null;
+
+// initial hash states
+var _states = null;
+
+/**
+ * Initializes the constant tables.
+ */
+function _init() {
+  // create padding
+  _padding = String.fromCharCode(128);
+  _padding += forge.util.fillString(String.fromCharCode(0x00), 128);
+
+  // create K table for SHA-512
+  _k = [
+    [0x428a2f98, 0xd728ae22], [0x71374491, 0x23ef65cd],
+    [0xb5c0fbcf, 0xec4d3b2f], [0xe9b5dba5, 0x8189dbbc],
+    [0x3956c25b, 0xf348b538], [0x59f111f1, 0xb605d019],
+    [0x923f82a4, 0xaf194f9b], [0xab1c5ed5, 0xda6d8118],
+    [0xd807aa98, 0xa3030242], [0x12835b01, 0x45706fbe],
+    [0x243185be, 0x4ee4b28c], [0x550c7dc3, 0xd5ffb4e2],
+    [0x72be5d74, 0xf27b896f], [0x80deb1fe, 0x3b1696b1],
+    [0x9bdc06a7, 0x25c71235], [0xc19bf174, 0xcf692694],
+    [0xe49b69c1, 0x9ef14ad2], [0xefbe4786, 0x384f25e3],
+    [0x0fc19dc6, 0x8b8cd5b5], [0x240ca1cc, 0x77ac9c65],
+    [0x2de92c6f, 0x592b0275], [0x4a7484aa, 0x6ea6e483],
+    [0x5cb0a9dc, 0xbd41fbd4], [0x76f988da, 0x831153b5],
+    [0x983e5152, 0xee66dfab], [0xa831c66d, 0x2db43210],
+    [0xb00327c8, 0x98fb213f], [0xbf597fc7, 0xbeef0ee4],
+    [0xc6e00bf3, 0x3da88fc2], [0xd5a79147, 0x930aa725],
+    [0x06ca6351, 0xe003826f], [0x14292967, 0x0a0e6e70],
+    [0x27b70a85, 0x46d22ffc], [0x2e1b2138, 0x5c26c926],
+    [0x4d2c6dfc, 0x5ac42aed], [0x53380d13, 0x9d95b3df],
+    [0x650a7354, 0x8baf63de], [0x766a0abb, 0x3c77b2a8],
+    [0x81c2c92e, 0x47edaee6], [0x92722c85, 0x1482353b],
+    [0xa2bfe8a1, 0x4cf10364], [0xa81a664b, 0xbc423001],
+    [0xc24b8b70, 0xd0f89791], [0xc76c51a3, 0x0654be30],
+    [0xd192e819, 0xd6ef5218], [0xd6990624, 0x5565a910],
+    [0xf40e3585, 0x5771202a], [0x106aa070, 0x32bbd1b8],
+    [0x19a4c116, 0xb8d2d0c8], [0x1e376c08, 0x5141ab53],
+    [0x2748774c, 0xdf8eeb99], [0x34b0bcb5, 0xe19b48a8],
+    [0x391c0cb3, 0xc5c95a63], [0x4ed8aa4a, 0xe3418acb],
+    [0x5b9cca4f, 0x7763e373], [0x682e6ff3, 0xd6b2b8a3],
+    [0x748f82ee, 0x5defb2fc], [0x78a5636f, 0x43172f60],
+    [0x84c87814, 0xa1f0ab72], [0x8cc70208, 0x1a6439ec],
+    [0x90befffa, 0x23631e28], [0xa4506ceb, 0xde82bde9],
+    [0xbef9a3f7, 0xb2c67915], [0xc67178f2, 0xe372532b],
+    [0xca273ece, 0xea26619c], [0xd186b8c7, 0x21c0c207],
+    [0xeada7dd6, 0xcde0eb1e], [0xf57d4f7f, 0xee6ed178],
+    [0x06f067aa, 0x72176fba], [0x0a637dc5, 0xa2c898a6],
+    [0x113f9804, 0xbef90dae], [0x1b710b35, 0x131c471b],
+    [0x28db77f5, 0x23047d84], [0x32caab7b, 0x40c72493],
+    [0x3c9ebe0a, 0x15c9bebc], [0x431d67c4, 0x9c100d4c],
+    [0x4cc5d4be, 0xcb3e42b6], [0x597f299c, 0xfc657e2a],
+    [0x5fcb6fab, 0x3ad6faec], [0x6c44198c, 0x4a475817]
+  ];
+
+  // initial hash states
+  _states = {};
+  _states['SHA-512'] = [
+    [0x6a09e667, 0xf3bcc908],
+    [0xbb67ae85, 0x84caa73b],
+    [0x3c6ef372, 0xfe94f82b],
+    [0xa54ff53a, 0x5f1d36f1],
+    [0x510e527f, 0xade682d1],
+    [0x9b05688c, 0x2b3e6c1f],
+    [0x1f83d9ab, 0xfb41bd6b],
+    [0x5be0cd19, 0x137e2179]
+  ];
+  _states['SHA-384'] = [
+    [0xcbbb9d5d, 0xc1059ed8],
+    [0x629a292a, 0x367cd507],
+    [0x9159015a, 0x3070dd17],
+    [0x152fecd8, 0xf70e5939],
+    [0x67332667, 0xffc00b31],
+    [0x8eb44a87, 0x68581511],
+    [0xdb0c2e0d, 0x64f98fa7],
+    [0x47b5481d, 0xbefa4fa4]
+  ];
+  _states['SHA-512/256'] = [
+    [0x22312194, 0xFC2BF72C],
+    [0x9F555FA3, 0xC84C64C2],
+    [0x2393B86B, 0x6F53B151],
+    [0x96387719, 0x5940EABD],
+    [0x96283EE2, 0xA88EFFE3],
+    [0xBE5E1E25, 0x53863992],
+    [0x2B0199FC, 0x2C85B8AA],
+    [0x0EB72DDC, 0x81C52CA2]
+  ];
+  _states['SHA-512/224'] = [
+    [0x8C3D37C8, 0x19544DA2],
+    [0x73E19966, 0x89DCD4D6],
+    [0x1DFAB7AE, 0x32FF9C82],
+    [0x679DD514, 0x582F9FCF],
+    [0x0F6D2B69, 0x7BD44DA8],
+    [0x77E36F73, 0x04C48942],
+    [0x3F9D85A8, 0x6A1D36C8],
+    [0x1112E6AD, 0x91D692A1]
+  ];
+
+  // now initialized
+  _initialized = true;
+}
+
+/**
+ * Updates a SHA-512 state with the given byte buffer.
+ *
+ * @param s the SHA-512 state to update.
+ * @param w the array to use to store words.
+ * @param bytes the byte buffer to update with.
+ */
+function _update(s, w, bytes) {
+  // consume 512 bit (128 byte) chunks
+  var t1_hi, t1_lo;
+  var t2_hi, t2_lo;
+  var s0_hi, s0_lo;
+  var s1_hi, s1_lo;
+  var ch_hi, ch_lo;
+  var maj_hi, maj_lo;
+  var a_hi, a_lo;
+  var b_hi, b_lo;
+  var c_hi, c_lo;
+  var d_hi, d_lo;
+  var e_hi, e_lo;
+  var f_hi, f_lo;
+  var g_hi, g_lo;
+  var h_hi, h_lo;
+  var i, hi, lo, w2, w7, w15, w16;
+  var len = bytes.length();
+  while(len >= 128) {
+    // the w array will be populated with sixteen 64-bit big-endian words
+    // and then extended into 64 64-bit words according to SHA-512
+    for(i = 0; i < 16; ++i) {
+      w[i][0] = bytes.getInt32() >>> 0;
+      w[i][1] = bytes.getInt32() >>> 0;
+    }
+    for(; i < 80; ++i) {
+      // for word 2 words ago: ROTR 19(x) ^ ROTR 61(x) ^ SHR 6(x)
+      w2 = w[i - 2];
+      hi = w2[0];
+      lo = w2[1];
+
+      // high bits
+      t1_hi = (
+        ((hi >>> 19) | (lo << 13)) ^ // ROTR 19
+        ((lo >>> 29) | (hi << 3)) ^ // ROTR 61/(swap + ROTR 29)
+        (hi >>> 6)) >>> 0; // SHR 6
+      // low bits
+      t1_lo = (
+        ((hi << 13) | (lo >>> 19)) ^ // ROTR 19
+        ((lo << 3) | (hi >>> 29)) ^ // ROTR 61/(swap + ROTR 29)
+        ((hi << 26) | (lo >>> 6))) >>> 0; // SHR 6
+
+      // for word 15 words ago: ROTR 1(x) ^ ROTR 8(x) ^ SHR 7(x)
+      w15 = w[i - 15];
+      hi = w15[0];
+      lo = w15[1];
+
+      // high bits
+      t2_hi = (
+        ((hi >>> 1) | (lo << 31)) ^ // ROTR 1
+        ((hi >>> 8) | (lo << 24)) ^ // ROTR 8
+        (hi >>> 7)) >>> 0; // SHR 7
+      // low bits
+      t2_lo = (
+        ((hi << 31) | (lo >>> 1)) ^ // ROTR 1
+        ((hi << 24) | (lo >>> 8)) ^ // ROTR 8
+        ((hi << 25) | (lo >>> 7))) >>> 0; // SHR 7
+
+      // sum(t1, word 7 ago, t2, word 16 ago) modulo 2^64 (carry lo overflow)
+      w7 = w[i - 7];
+      w16 = w[i - 16];
+      lo = (t1_lo + w7[1] + t2_lo + w16[1]);
+      w[i][0] = (t1_hi + w7[0] + t2_hi + w16[0] +
+        ((lo / 0x100000000) >>> 0)) >>> 0;
+      w[i][1] = lo >>> 0;
+    }
+
+    // initialize hash value for this chunk
+    a_hi = s[0][0];
+    a_lo = s[0][1];
+    b_hi = s[1][0];
+    b_lo = s[1][1];
+    c_hi = s[2][0];
+    c_lo = s[2][1];
+    d_hi = s[3][0];
+    d_lo = s[3][1];
+    e_hi = s[4][0];
+    e_lo = s[4][1];
+    f_hi = s[5][0];
+    f_lo = s[5][1];
+    g_hi = s[6][0];
+    g_lo = s[6][1];
+    h_hi = s[7][0];
+    h_lo = s[7][1];
+
+    // round function
+    for(i = 0; i < 80; ++i) {
+      // Sum1(e) = ROTR 14(e) ^ ROTR 18(e) ^ ROTR 41(e)
+      s1_hi = (
+        ((e_hi >>> 14) | (e_lo << 18)) ^ // ROTR 14
+        ((e_hi >>> 18) | (e_lo << 14)) ^ // ROTR 18
+        ((e_lo >>> 9) | (e_hi << 23))) >>> 0; // ROTR 41/(swap + ROTR 9)
+      s1_lo = (
+        ((e_hi << 18) | (e_lo >>> 14)) ^ // ROTR 14
+        ((e_hi << 14) | (e_lo >>> 18)) ^ // ROTR 18
+        ((e_lo << 23) | (e_hi >>> 9))) >>> 0; // ROTR 41/(swap + ROTR 9)
+
+      // Ch(e, f, g) (optimized the same way as SHA-1)
+      ch_hi = (g_hi ^ (e_hi & (f_hi ^ g_hi))) >>> 0;
+      ch_lo = (g_lo ^ (e_lo & (f_lo ^ g_lo))) >>> 0;
+
+      // Sum0(a) = ROTR 28(a) ^ ROTR 34(a) ^ ROTR 39(a)
+      s0_hi = (
+        ((a_hi >>> 28) | (a_lo << 4)) ^ // ROTR 28
+        ((a_lo >>> 2) | (a_hi << 30)) ^ // ROTR 34/(swap + ROTR 2)
+        ((a_lo >>> 7) | (a_hi << 25))) >>> 0; // ROTR 39/(swap + ROTR 7)
+      s0_lo = (
+        ((a_hi << 4) | (a_lo >>> 28)) ^ // ROTR 28
+        ((a_lo << 30) | (a_hi >>> 2)) ^ // ROTR 34/(swap + ROTR 2)
+        ((a_lo << 25) | (a_hi >>> 7))) >>> 0; // ROTR 39/(swap + ROTR 7)
+
+      // Maj(a, b, c) (optimized the same way as SHA-1)
+      maj_hi = ((a_hi & b_hi) | (c_hi & (a_hi ^ b_hi))) >>> 0;
+      maj_lo = ((a_lo & b_lo) | (c_lo & (a_lo ^ b_lo))) >>> 0;
+
+      // main algorithm
+      // t1 = (h + s1 + ch + _k[i] + _w[i]) modulo 2^64 (carry lo overflow)
+      lo = (h_lo + s1_lo + ch_lo + _k[i][1] + w[i][1]);
+      t1_hi = (h_hi + s1_hi + ch_hi + _k[i][0] + w[i][0] +
+        ((lo / 0x100000000) >>> 0)) >>> 0;
+      t1_lo = lo >>> 0;
+
+      // t2 = s0 + maj modulo 2^64 (carry lo overflow)
+      lo = s0_lo + maj_lo;
+      t2_hi = (s0_hi + maj_hi + ((lo / 0x100000000) >>> 0)) >>> 0;
+      t2_lo = lo >>> 0;
+
+      h_hi = g_hi;
+      h_lo = g_lo;
+
+      g_hi = f_hi;
+      g_lo = f_lo;
+
+      f_hi = e_hi;
+      f_lo = e_lo;
+
+      // e = (d + t1) modulo 2^64 (carry lo overflow)
+      lo = d_lo + t1_lo;
+      e_hi = (d_hi + t1_hi + ((lo / 0x100000000) >>> 0)) >>> 0;
+      e_lo = lo >>> 0;
+
+      d_hi = c_hi;
+      d_lo = c_lo;
+
+      c_hi = b_hi;
+      c_lo = b_lo;
+
+      b_hi = a_hi;
+      b_lo = a_lo;
+
+      // a = (t1 + t2) modulo 2^64 (carry lo overflow)
+      lo = t1_lo + t2_lo;
+      a_hi = (t1_hi + t2_hi + ((lo / 0x100000000) >>> 0)) >>> 0;
+      a_lo = lo >>> 0;
+    }
+
+    // update hash state (additional modulo 2^64)
+    lo = s[0][1] + a_lo;
+    s[0][0] = (s[0][0] + a_hi + ((lo / 0x100000000) >>> 0)) >>> 0;
+    s[0][1] = lo >>> 0;
+
+    lo = s[1][1] + b_lo;
+    s[1][0] = (s[1][0] + b_hi + ((lo / 0x100000000) >>> 0)) >>> 0;
+    s[1][1] = lo >>> 0;
+
+    lo = s[2][1] + c_lo;
+    s[2][0] = (s[2][0] + c_hi + ((lo / 0x100000000) >>> 0)) >>> 0;
+    s[2][1] = lo >>> 0;
+
+    lo = s[3][1] + d_lo;
+    s[3][0] = (s[3][0] + d_hi + ((lo / 0x100000000) >>> 0)) >>> 0;
+    s[3][1] = lo >>> 0;
+
+    lo = s[4][1] + e_lo;
+    s[4][0] = (s[4][0] + e_hi + ((lo / 0x100000000) >>> 0)) >>> 0;
+    s[4][1] = lo >>> 0;
+
+    lo = s[5][1] + f_lo;
+    s[5][0] = (s[5][0] + f_hi + ((lo / 0x100000000) >>> 0)) >>> 0;
+    s[5][1] = lo >>> 0;
+
+    lo = s[6][1] + g_lo;
+    s[6][0] = (s[6][0] + g_hi + ((lo / 0x100000000) >>> 0)) >>> 0;
+    s[6][1] = lo >>> 0;
+
+    lo = s[7][1] + h_lo;
+    s[7][0] = (s[7][0] + h_hi + ((lo / 0x100000000) >>> 0)) >>> 0;
+    s[7][1] = lo >>> 0;
+
+    len -= 128;
+  }
+}
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'sha512';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/socket.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/socket.js
new file mode 100644
index 0000000..e50e1aa
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/socket.js
@@ -0,0 +1,342 @@
+/**
+ * Socket implementation that uses flash SocketPool class as a backend.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2013 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+// define net namespace
+var net = forge.net = forge.net || {};
+
+// map of flash ID to socket pool
+net.socketPools = {};
+
+/**
+ * Creates a flash socket pool.
+ *
+ * @param options:
+ *          flashId: the dom ID for the flash object element.
+ *          policyPort: the default policy port for sockets, 0 to use the
+ *            flash default.
+ *          policyUrl: the default policy file URL for sockets (if provided
+ *            used instead of a policy port).
+ *          msie: true if the browser is msie, false if not.
+ *
+ * @return the created socket pool.
+ */
+net.createSocketPool = function(options) {
+  // set default
+  options.msie = options.msie || false;
+
+  // initialize the flash interface
+  var spId = options.flashId;
+  var api = document.getElementById(spId);
+  api.init({marshallExceptions: !options.msie});
+
+  // create socket pool entry
+  var sp = {
+    // ID of the socket pool
+    id: spId,
+    // flash interface
+    flashApi: api,
+    // map of socket ID to sockets
+    sockets: {},
+    // default policy port
+    policyPort: options.policyPort || 0,
+    // default policy URL
+    policyUrl: options.policyUrl || null
+  };
+  net.socketPools[spId] = sp;
+
+  // create event handler, subscribe to flash events
+  if(options.msie === true) {
+    sp.handler = function(e) {
+      if(e.id in sp.sockets) {
+        // get handler function
+        var f;
+        switch(e.type) {
+        case 'connect':
+          f = 'connected';
+          break;
+        case 'close':
+          f = 'closed';
+          break;
+        case 'socketData':
+          f = 'data';
+          break;
+        default:
+          f = 'error';
+          break;
+        }
+        /* IE calls javascript on the thread of the external object
+          that triggered the event (in this case flash) ... which will
+          either run concurrently with other javascript or pre-empt any
+          running javascript in the middle of its execution (BAD!) ...
+          calling setTimeout() will schedule the javascript to run on
+          the javascript thread and solve this EVIL problem. */
+        setTimeout(function(){sp.sockets[e.id][f](e);}, 0);
+      }
+    };
+  } else {
+    sp.handler = function(e) {
+      if(e.id in sp.sockets) {
+        // get handler function
+        var f;
+        switch(e.type) {
+        case 'connect':
+          f = 'connected';
+          break;
+        case 'close':
+          f = 'closed';
+          break;
+        case 'socketData':
+          f = 'data';
+          break;
+        default:
+          f = 'error';
+          break;
+        }
+        sp.sockets[e.id][f](e);
+      }
+    };
+  }
+  var handler = 'forge.net.socketPools[\'' + spId + '\'].handler';
+  api.subscribe('connect', handler);
+  api.subscribe('close', handler);
+  api.subscribe('socketData', handler);
+  api.subscribe('ioError', handler);
+  api.subscribe('securityError', handler);
+
+  /**
+   * Destroys a socket pool. The socket pool still needs to be cleaned
+   * up via net.cleanup().
+   */
+  sp.destroy = function() {
+    delete net.socketPools[options.flashId];
+    for(var id in sp.sockets) {
+      sp.sockets[id].destroy();
+    }
+    sp.sockets = {};
+    api.cleanup();
+  };
+
+  /**
+   * Creates a new socket.
+   *
+   * @param options:
+   *          connected: function(event) called when the socket connects.
+   *          closed: function(event) called when the socket closes.
+   *          data: function(event) called when socket data has arrived,
+   *            it can be read from the socket using receive().
+   *          error: function(event) called when a socket error occurs.
+   */
+   sp.createSocket = function(options) {
+     // default to empty options
+     options = options || {};
+
+     // create flash socket
+     var id = api.create();
+
+     // create javascript socket wrapper
+     var socket = {
+       id: id,
+       // set handlers
+       connected: options.connected || function(e){},
+       closed: options.closed || function(e){},
+       data: options.data || function(e){},
+       error: options.error || function(e){}
+     };
+
+     /**
+      * Destroys this socket.
+      */
+     socket.destroy = function() {
+       api.destroy(id);
+       delete sp.sockets[id];
+     };
+
+     /**
+      * Connects this socket.
+      *
+      * @param options:
+      *          host: the host to connect to.
+      *          port: the port to connect to.
+      *          policyPort: the policy port to use (if non-default), 0 to
+      *            use the flash default.
+      *          policyUrl: the policy file URL to use (instead of port).
+      */
+     socket.connect = function(options) {
+       // give precedence to policy URL over policy port
+       // if no policy URL and passed port isn't 0, use default port,
+       // otherwise use 0 for the port
+       var policyUrl = options.policyUrl || null;
+       var policyPort = 0;
+       if(policyUrl === null && options.policyPort !== 0) {
+         policyPort = options.policyPort || sp.policyPort;
+       }
+       api.connect(id, options.host, options.port, policyPort, policyUrl);
+     };
+
+     /**
+      * Closes this socket.
+      */
+     socket.close = function() {
+       api.close(id);
+       socket.closed({
+         id: socket.id,
+         type: 'close',
+         bytesAvailable: 0
+       });
+     };
+
+     /**
+      * Determines if the socket is connected or not.
+      *
+      * @return true if connected, false if not.
+      */
+     socket.isConnected = function() {
+       return api.isConnected(id);
+     };
+
+     /**
+      * Writes bytes to this socket.
+      *
+      * @param bytes the bytes (as a string) to write.
+      *
+      * @return true on success, false on failure.
+      */
+     socket.send = function(bytes) {
+       return api.send(id, forge.util.encode64(bytes));
+     };
+
+     /**
+      * Reads bytes from this socket (non-blocking). Fewer than the number
+      * of bytes requested may be read if enough bytes are not available.
+      *
+      * This method should be called from the data handler if there are
+      * enough bytes available. To see how many bytes are available, check
+      * the 'bytesAvailable' property on the event in the data handler or
+      * call the bytesAvailable() function on the socket. If the browser is
+      * msie, then the bytesAvailable() function should be used to avoid
+      * race conditions. Otherwise, using the property on the data handler's
+      * event may be quicker.
+      *
+      * @param count the maximum number of bytes to read.
+      *
+      * @return the bytes read (as a string) or null on error.
+      */
+     socket.receive = function(count) {
+       var rval = api.receive(id, count).rval;
+       return (rval === null) ? null : forge.util.decode64(rval);
+     };
+
+     /**
+      * Gets the number of bytes available for receiving on the socket.
+      *
+      * @return the number of bytes available for receiving.
+      */
+     socket.bytesAvailable = function() {
+       return api.getBytesAvailable(id);
+     };
+
+     // store and return socket
+     sp.sockets[id] = socket;
+     return socket;
+  };
+
+  return sp;
+};
+
+/**
+ * Destroys a flash socket pool.
+ *
+ * @param options:
+ *          flashId: the dom ID for the flash object element.
+ */
+net.destroySocketPool = function(options) {
+  if(options.flashId in net.socketPools) {
+    var sp = net.socketPools[options.flashId];
+    sp.destroy();
+  }
+};
+
+/**
+ * Creates a new socket.
+ *
+ * @param options:
+ *          flashId: the dom ID for the flash object element.
+ *          connected: function(event) called when the socket connects.
+ *          closed: function(event) called when the socket closes.
+ *          data: function(event) called when socket data has arrived, it
+ *            can be read from the socket using receive().
+ *          error: function(event) called when a socket error occurs.
+ *
+ * @return the created socket.
+ */
+net.createSocket = function(options) {
+  var socket = null;
+  if(options.flashId in net.socketPools) {
+    // get related socket pool
+    var sp = net.socketPools[options.flashId];
+    socket = sp.createSocket(options);
+  }
+  return socket;
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'net';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/ssh.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/ssh.js
new file mode 100644
index 0000000..ef76c82
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/ssh.js
@@ -0,0 +1,295 @@
+/**
+ * Functions to output keys in SSH-friendly formats.
+ *
+ * This is part of the Forge project which may be used under the terms of
+ * either the BSD License or the GNU General Public License (GPL) Version 2.
+ *
+ * See: https://github.com/digitalbazaar/forge/blob/cbebca3780658703d925b61b2caffb1d263a6c1d/LICENSE
+ *
+ * @author https://github.com/shellac
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+var ssh = forge.ssh = forge.ssh || {};
+
+/**
+ * Encodes (and optionally encrypts) a private RSA key as a Putty PPK file.
+ *
+ * @param privateKey the key.
+ * @param passphrase a passphrase to protect the key (falsy for no encryption).
+ * @param comment a comment to include in the key file.
+ *
+ * @return the PPK file as a string.
+ */
+ssh.privateKeyToPutty = function(privateKey, passphrase, comment) {
+  comment = comment || '';
+  passphrase = passphrase || '';
+  var algorithm = 'ssh-rsa';
+  var encryptionAlgorithm = (passphrase === '') ? 'none' : 'aes256-cbc';
+
+  var ppk = 'PuTTY-User-Key-File-2: ' + algorithm + '\r\n';
+  ppk += 'Encryption: ' + encryptionAlgorithm + '\r\n';
+  ppk += 'Comment: ' + comment + '\r\n';
+
+  // public key into buffer for ppk
+  var pubbuffer = forge.util.createBuffer();
+  _addStringToBuffer(pubbuffer, algorithm);
+  _addBigIntegerToBuffer(pubbuffer, privateKey.e);
+  _addBigIntegerToBuffer(pubbuffer, privateKey.n);
+
+  // write public key
+  var pub = forge.util.encode64(pubbuffer.bytes(), 64);
+  var length = Math.floor(pub.length / 66) + 1; // 66 = 64 + \r\n
+  ppk += 'Public-Lines: ' + length + '\r\n';
+  ppk += pub;
+
+  // private key into a buffer
+  var privbuffer = forge.util.createBuffer();
+  _addBigIntegerToBuffer(privbuffer, privateKey.d);
+  _addBigIntegerToBuffer(privbuffer, privateKey.p);
+  _addBigIntegerToBuffer(privbuffer, privateKey.q);
+  _addBigIntegerToBuffer(privbuffer, privateKey.qInv);
+
+  // optionally encrypt the private key
+  var priv;
+  if(!passphrase) {
+    // use the unencrypted buffer
+    priv = forge.util.encode64(privbuffer.bytes(), 64);
+  } else {
+    // encrypt RSA key using passphrase
+    var encLen = privbuffer.length() + 16 - 1;
+    encLen -= encLen % 16;
+
+    // pad private key with sha1-d data -- needs to be a multiple of 16
+    var padding = _sha1(privbuffer.bytes());
+
+    padding.truncate(padding.length() - encLen + privbuffer.length());
+    privbuffer.putBuffer(padding);
+
+    var aeskey = forge.util.createBuffer();
+    aeskey.putBuffer(_sha1('\x00\x00\x00\x00', passphrase));
+    aeskey.putBuffer(_sha1('\x00\x00\x00\x01', passphrase));
+
+    // encrypt some bytes using CBC mode
+    // key is 40 bytes, so truncate *by* 8 bytes
+    var cipher = forge.aes.createEncryptionCipher(aeskey.truncate(8), 'CBC');
+    cipher.start(forge.util.createBuffer().fillWithByte(0, 16));
+    cipher.update(privbuffer.copy());
+    cipher.finish();
+    var encrypted = cipher.output;
+
+    // Note: this appears to differ from Putty -- is forge wrong, or putty?
+    // due to padding we finish as an exact multiple of 16
+    encrypted.truncate(16); // all padding
+
+    priv = forge.util.encode64(encrypted.bytes(), 64);
+  }
+
+  // output private key
+  length = Math.floor(priv.length / 66) + 1; // 64 + \r\n
+  ppk += '\r\nPrivate-Lines: ' + length + '\r\n';
+  ppk += priv;
+
+  // MAC
+  var mackey = _sha1('putty-private-key-file-mac-key', passphrase);
+
+  var macbuffer = forge.util.createBuffer();
+  _addStringToBuffer(macbuffer, algorithm);
+  _addStringToBuffer(macbuffer, encryptionAlgorithm);
+  _addStringToBuffer(macbuffer, comment);
+  macbuffer.putInt32(pubbuffer.length());
+  macbuffer.putBuffer(pubbuffer);
+  macbuffer.putInt32(privbuffer.length());
+  macbuffer.putBuffer(privbuffer);
+
+  var hmac = forge.hmac.create();
+  hmac.start('sha1', mackey);
+  hmac.update(macbuffer.bytes());
+
+  ppk += '\r\nPrivate-MAC: ' + hmac.digest().toHex() + '\r\n';
+
+  return ppk;
+};
+
+/**
+ * Encodes a public RSA key as an OpenSSH file.
+ *
+ * @param key the key.
+ * @param comment a comment.
+ *
+ * @return the public key in OpenSSH format.
+ */
+ssh.publicKeyToOpenSSH = function(key, comment) {
+  var type = 'ssh-rsa';
+  comment = comment || '';
+
+  var buffer = forge.util.createBuffer();
+  _addStringToBuffer(buffer, type);
+  _addBigIntegerToBuffer(buffer, key.e);
+  _addBigIntegerToBuffer(buffer, key.n);
+
+  return type + ' ' + forge.util.encode64(buffer.bytes()) + ' ' + comment;
+};
+
+/**
+ * Encodes a private RSA key as an OpenSSH file.
+ *
+ * @param key the key.
+ * @param passphrase a passphrase to protect the key (falsy for no encryption).
+ *
+ * @return the public key in OpenSSH format.
+ */
+ssh.privateKeyToOpenSSH = function(privateKey, passphrase) {
+  if(!passphrase) {
+    return forge.pki.privateKeyToPem(privateKey);
+  }
+  // OpenSSH private key is just a legacy format, it seems
+  return forge.pki.encryptRsaPrivateKey(privateKey, passphrase,
+    {legacy: true, algorithm: 'aes128'});
+};
+
+/**
+ * Gets the SSH fingerprint for the given public key.
+ *
+ * @param options the options to use.
+ *          [md] the message digest object to use (defaults to forge.md.md5).
+ *          [encoding] an alternative output encoding, such as 'hex'
+ *            (defaults to none, outputs a byte buffer).
+ *          [delimiter] the delimiter to use between bytes for 'hex' encoded
+ *            output, eg: ':' (defaults to none).
+ *
+ * @return the fingerprint as a byte buffer or other encoding based on options.
+ */
+ssh.getPublicKeyFingerprint = function(key, options) {
+  options = options || {};
+  var md = options.md || forge.md.md5.create();
+
+  var type = 'ssh-rsa';
+  var buffer = forge.util.createBuffer();
+  _addStringToBuffer(buffer, type);
+  _addBigIntegerToBuffer(buffer, key.e);
+  _addBigIntegerToBuffer(buffer, key.n);
+
+  // hash public key bytes
+  md.start();
+  md.update(buffer.getBytes());
+  var digest = md.digest();
+  if(options.encoding === 'hex') {
+    var hex = digest.toHex();
+    if(options.delimiter) {
+      return hex.match(/.{2}/g).join(options.delimiter);
+    }
+    return hex;
+  } else if(options.encoding === 'binary') {
+    return digest.getBytes();
+  } else if(options.encoding) {
+    throw new Error('Unknown encoding "' + options.encoding + '".');
+  }
+  return digest;
+};
+
+/**
+ * Adds len(val) then val to a buffer.
+ *
+ * @param buffer the buffer to add to.
+ * @param val a big integer.
+ */
+function _addBigIntegerToBuffer(buffer, val) {
+  var hexVal = val.toString(16);
+  // ensure 2s complement +ve
+  if(hexVal[0] >= '8') {
+    hexVal = '00' + hexVal;
+  }
+  var bytes = forge.util.hexToBytes(hexVal);
+  buffer.putInt32(bytes.length);
+  buffer.putBytes(bytes);
+}
+
+/**
+ * Adds len(val) then val to a buffer.
+ *
+ * @param buffer the buffer to add to.
+ * @param val a string.
+ */
+function _addStringToBuffer(buffer, val) {
+  buffer.putInt32(val.length);
+  buffer.putString(val);
+}
+
+/**
+ * Hashes the arguments into one value using SHA-1.
+ *
+ * @return the sha1 hash of the provided arguments.
+ */
+function _sha1() {
+  var sha = forge.md.sha1.create();
+  var num = arguments.length;
+  for (var i = 0; i < num; ++i) {
+    sha.update(arguments[i]);
+  }
+  return sha.digest();
+}
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'ssh';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define([
+  'require',
+  'module',
+  './aes',
+  './hmac',
+  './md5',
+  './sha1',
+  './util'
+], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/task.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/task.js
new file mode 100644
index 0000000..f49bbf7
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/task.js
@@ -0,0 +1,778 @@
+/**
+ * Support for concurrent task management and synchronization in web
+ * applications.
+ *
+ * @author Dave Longley
+ * @author David I. Lehn <dlehn@digitalbazaar.com>
+ *
+ * Copyright (c) 2009-2013 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+// logging category
+var cat = 'forge.task';
+
+// verbose level
+// 0: off, 1: a little, 2: a whole lot
+// Verbose debug logging is surrounded by a level check to avoid the
+// performance issues with even calling the logging code regardless if it
+// is actually logged.  For performance reasons this should not be set to 2
+// for production use.
+// ex: if(sVL >= 2) forge.log.verbose(....)
+var sVL = 0;
+
+// track tasks for debugging
+var sTasks = {};
+var sNextTaskId = 0;
+// debug access
+forge.debug.set(cat, 'tasks', sTasks);
+
+// a map of task type to task queue
+var sTaskQueues = {};
+// debug access
+forge.debug.set(cat, 'queues', sTaskQueues);
+
+// name for unnamed tasks
+var sNoTaskName = '?';
+
+// maximum number of doNext() recursions before a context swap occurs
+// FIXME: might need to tweak this based on the browser
+var sMaxRecursions = 30;
+
+// time slice for doing tasks before a context swap occurs
+// FIXME: might need to tweak this based on the browser
+var sTimeSlice = 20;
+
+/**
+ * Task states.
+ *
+ * READY: ready to start processing
+ * RUNNING: task or a subtask is running
+ * BLOCKED: task is waiting to acquire N permits to continue
+ * SLEEPING: task is sleeping for a period of time
+ * DONE: task is done
+ * ERROR: task has an error
+ */
+var READY = 'ready';
+var RUNNING = 'running';
+var BLOCKED = 'blocked';
+var SLEEPING = 'sleeping';
+var DONE = 'done';
+var ERROR = 'error';
+
+/**
+ * Task actions.  Used to control state transitions.
+ *
+ * STOP: stop processing
+ * START: start processing tasks
+ * BLOCK: block task from continuing until 1 or more permits are released
+ * UNBLOCK: release one or more permits
+ * SLEEP: sleep for a period of time
+ * WAKEUP: wakeup early from SLEEPING state
+ * CANCEL: cancel further tasks
+ * FAIL: a failure occured
+ */
+var STOP = 'stop';
+var START = 'start';
+var BLOCK = 'block';
+var UNBLOCK = 'unblock';
+var SLEEP = 'sleep';
+var WAKEUP = 'wakeup';
+var CANCEL = 'cancel';
+var FAIL = 'fail';
+
+/**
+ * State transition table.
+ *
+ * nextState = sStateTable[currentState][action]
+ */
+var sStateTable = {};
+
+sStateTable[READY] = {};
+sStateTable[READY][STOP] = READY;
+sStateTable[READY][START] = RUNNING;
+sStateTable[READY][CANCEL] = DONE;
+sStateTable[READY][FAIL] = ERROR;
+
+sStateTable[RUNNING] = {};
+sStateTable[RUNNING][STOP] = READY;
+sStateTable[RUNNING][START] = RUNNING;
+sStateTable[RUNNING][BLOCK] = BLOCKED;
+sStateTable[RUNNING][UNBLOCK] = RUNNING;
+sStateTable[RUNNING][SLEEP] = SLEEPING;
+sStateTable[RUNNING][WAKEUP] = RUNNING;
+sStateTable[RUNNING][CANCEL] = DONE;
+sStateTable[RUNNING][FAIL] = ERROR;
+
+sStateTable[BLOCKED] = {};
+sStateTable[BLOCKED][STOP] = BLOCKED;
+sStateTable[BLOCKED][START] = BLOCKED;
+sStateTable[BLOCKED][BLOCK] = BLOCKED;
+sStateTable[BLOCKED][UNBLOCK] = BLOCKED;
+sStateTable[BLOCKED][SLEEP] = BLOCKED;
+sStateTable[BLOCKED][WAKEUP] = BLOCKED;
+sStateTable[BLOCKED][CANCEL] = DONE;
+sStateTable[BLOCKED][FAIL] = ERROR;
+
+sStateTable[SLEEPING] = {};
+sStateTable[SLEEPING][STOP] = SLEEPING;
+sStateTable[SLEEPING][START] = SLEEPING;
+sStateTable[SLEEPING][BLOCK] = SLEEPING;
+sStateTable[SLEEPING][UNBLOCK] = SLEEPING;
+sStateTable[SLEEPING][SLEEP] = SLEEPING;
+sStateTable[SLEEPING][WAKEUP] = SLEEPING;
+sStateTable[SLEEPING][CANCEL] = DONE;
+sStateTable[SLEEPING][FAIL] = ERROR;
+
+sStateTable[DONE] = {};
+sStateTable[DONE][STOP] = DONE;
+sStateTable[DONE][START] = DONE;
+sStateTable[DONE][BLOCK] = DONE;
+sStateTable[DONE][UNBLOCK] = DONE;
+sStateTable[DONE][SLEEP] = DONE;
+sStateTable[DONE][WAKEUP] = DONE;
+sStateTable[DONE][CANCEL] = DONE;
+sStateTable[DONE][FAIL] = ERROR;
+
+sStateTable[ERROR] = {};
+sStateTable[ERROR][STOP] = ERROR;
+sStateTable[ERROR][START] = ERROR;
+sStateTable[ERROR][BLOCK] = ERROR;
+sStateTable[ERROR][UNBLOCK] = ERROR;
+sStateTable[ERROR][SLEEP] = ERROR;
+sStateTable[ERROR][WAKEUP] = ERROR;
+sStateTable[ERROR][CANCEL] = ERROR;
+sStateTable[ERROR][FAIL] = ERROR;
+
+/**
+ * Creates a new task.
+ *
+ * @param options options for this task
+ *   run: the run function for the task (required)
+ *   name: the run function for the task (optional)
+ *   parent: parent of this task (optional)
+ *
+ * @return the empty task.
+ */
+var Task = function(options) {
+  // task id
+  this.id = -1;
+
+  // task name
+  this.name = options.name || sNoTaskName;
+
+  // task has no parent
+  this.parent = options.parent || null;
+
+  // save run function
+  this.run = options.run;
+
+  // create a queue of subtasks to run
+  this.subtasks = [];
+
+  // error flag
+  this.error = false;
+
+  // state of the task
+  this.state = READY;
+
+  // number of times the task has been blocked (also the number
+  // of permits needed to be released to continue running)
+  this.blocks = 0;
+
+  // timeout id when sleeping
+  this.timeoutId = null;
+
+  // no swap time yet
+  this.swapTime = null;
+
+  // no user data
+  this.userData = null;
+
+  // initialize task
+  // FIXME: deal with overflow
+  this.id = sNextTaskId++;
+  sTasks[this.id] = this;
+  if(sVL >= 1) {
+    forge.log.verbose(cat, '[%s][%s] init', this.id, this.name, this);
+  }
+};
+
+/**
+ * Logs debug information on this task and the system state.
+ */
+Task.prototype.debug = function(msg) {
+  msg = msg || '';
+  forge.log.debug(cat, msg,
+    '[%s][%s] task:', this.id, this.name, this,
+    'subtasks:', this.subtasks.length,
+    'queue:', sTaskQueues);
+};
+
+/**
+ * Adds a subtask to run after task.doNext() or task.fail() is called.
+ *
+ * @param name human readable name for this task (optional).
+ * @param subrun a function to run that takes the current task as
+ *          its first parameter.
+ *
+ * @return the current task (useful for chaining next() calls).
+ */
+Task.prototype.next = function(name, subrun) {
+  // juggle parameters if it looks like no name is given
+  if(typeof(name) === 'function') {
+    subrun = name;
+
+    // inherit parent's name
+    name = this.name;
+  }
+  // create subtask, set parent to this task, propagate callbacks
+  var subtask = new Task({
+    run: subrun,
+    name: name,
+    parent: this
+  });
+  // start subtasks running
+  subtask.state = RUNNING;
+  subtask.type = this.type;
+  subtask.successCallback = this.successCallback || null;
+  subtask.failureCallback = this.failureCallback || null;
+
+  // queue a new subtask
+  this.subtasks.push(subtask);
+
+  return this;
+};
+
+/**
+ * Adds subtasks to run in parallel after task.doNext() or task.fail()
+ * is called.
+ *
+ * @param name human readable name for this task (optional).
+ * @param subrun functions to run that take the current task as
+ *          their first parameter.
+ *
+ * @return the current task (useful for chaining next() calls).
+ */
+Task.prototype.parallel = function(name, subrun) {
+  // juggle parameters if it looks like no name is given
+  if(forge.util.isArray(name)) {
+    subrun = name;
+
+    // inherit parent's name
+    name = this.name;
+  }
+  // Wrap parallel tasks in a regular task so they are started at the
+  // proper time.
+  return this.next(name, function(task) {
+    // block waiting for subtasks
+    var ptask = task;
+    ptask.block(subrun.length);
+
+    // we pass the iterator from the loop below as a parameter
+    // to a function because it is otherwise included in the
+    // closure and changes as the loop changes -- causing i
+    // to always be set to its highest value
+    var startParallelTask = function(pname, pi) {
+      forge.task.start({
+        type: pname,
+        run: function(task) {
+           subrun[pi](task);
+        },
+        success: function(task) {
+           ptask.unblock();
+        },
+        failure: function(task) {
+           ptask.unblock();
+        }
+      });
+    };
+
+    for(var i = 0; i < subrun.length; i++) {
+      // Type must be unique so task starts in parallel:
+      //    name + private string + task id + sub-task index
+      // start tasks in parallel and unblock when the finish
+      var pname = name + '__parallel-' + task.id + '-' + i;
+      var pi = i;
+      startParallelTask(pname, pi);
+    }
+  });
+};
+
+/**
+ * Stops a running task.
+ */
+Task.prototype.stop = function() {
+  this.state = sStateTable[this.state][STOP];
+};
+
+/**
+ * Starts running a task.
+ */
+Task.prototype.start = function() {
+  this.error = false;
+  this.state = sStateTable[this.state][START];
+
+  // try to restart
+  if(this.state === RUNNING) {
+    this.start = new Date();
+    this.run(this);
+    runNext(this, 0);
+  }
+};
+
+/**
+ * Blocks a task until it one or more permits have been released. The
+ * task will not resume until the requested number of permits have
+ * been released with call(s) to unblock().
+ *
+ * @param n number of permits to wait for(default: 1).
+ */
+Task.prototype.block = function(n) {
+  n = typeof(n) === 'undefined' ? 1 : n;
+  this.blocks += n;
+  if(this.blocks > 0) {
+    this.state = sStateTable[this.state][BLOCK];
+  }
+};
+
+/**
+ * Releases a permit to unblock a task. If a task was blocked by
+ * requesting N permits via block(), then it will only continue
+ * running once enough permits have been released via unblock() calls.
+ *
+ * If multiple processes need to synchronize with a single task then
+ * use a condition variable (see forge.task.createCondition). It is
+ * an error to unblock a task more times than it has been blocked.
+ *
+ * @param n number of permits to release (default: 1).
+ *
+ * @return the current block count (task is unblocked when count is 0)
+ */
+Task.prototype.unblock = function(n) {
+  n = typeof(n) === 'undefined' ? 1 : n;
+  this.blocks -= n;
+  if(this.blocks === 0 && this.state !== DONE) {
+    this.state = RUNNING;
+    runNext(this, 0);
+  }
+  return this.blocks;
+};
+
+/**
+ * Sleep for a period of time before resuming tasks.
+ *
+ * @param n number of milliseconds to sleep (default: 0).
+ */
+Task.prototype.sleep = function(n) {
+  n = typeof(n) === 'undefined' ? 0 : n;
+  this.state = sStateTable[this.state][SLEEP];
+  var self = this;
+  this.timeoutId = setTimeout(function() {
+    self.timeoutId = null;
+    self.state = RUNNING;
+    runNext(self, 0);
+  }, n);
+};
+
+/**
+ * Waits on a condition variable until notified. The next task will
+ * not be scheduled until notification. A condition variable can be
+ * created with forge.task.createCondition().
+ *
+ * Once cond.notify() is called, the task will continue.
+ *
+ * @param cond the condition variable to wait on.
+ */
+Task.prototype.wait = function(cond) {
+  cond.wait(this);
+};
+
+/**
+ * If sleeping, wakeup and continue running tasks.
+ */
+Task.prototype.wakeup = function() {
+  if(this.state === SLEEPING) {
+    cancelTimeout(this.timeoutId);
+    this.timeoutId = null;
+    this.state = RUNNING;
+    runNext(this, 0);
+  }
+};
+
+/**
+ * Cancel all remaining subtasks of this task.
+ */
+Task.prototype.cancel = function() {
+  this.state = sStateTable[this.state][CANCEL];
+  // remove permits needed
+  this.permitsNeeded = 0;
+  // cancel timeouts
+  if(this.timeoutId !== null) {
+    cancelTimeout(this.timeoutId);
+    this.timeoutId = null;
+  }
+  // remove subtasks
+  this.subtasks = [];
+};
+
+/**
+ * Finishes this task with failure and sets error flag. The entire
+ * task will be aborted unless the next task that should execute
+ * is passed as a parameter. This allows levels of subtasks to be
+ * skipped. For instance, to abort only this tasks's subtasks, then
+ * call fail(task.parent). To abort this task's subtasks and its
+ * parent's subtasks, call fail(task.parent.parent). To abort
+ * all tasks and simply call the task callback, call fail() or
+ * fail(null).
+ *
+ * The task callback (success or failure) will always, eventually, be
+ * called.
+ *
+ * @param next the task to continue at, or null to abort entirely.
+ */
+Task.prototype.fail = function(next) {
+  // set error flag
+  this.error = true;
+
+  // finish task
+  finish(this, true);
+
+  if(next) {
+    // propagate task info
+    next.error = this.error;
+    next.swapTime = this.swapTime;
+    next.userData = this.userData;
+
+    // do next task as specified
+    runNext(next, 0);
+  } else {
+    if(this.parent !== null) {
+      // finish root task (ensures it is removed from task queue)
+      var parent = this.parent;
+      while(parent.parent !== null) {
+        // propagate task info
+        parent.error = this.error;
+        parent.swapTime = this.swapTime;
+        parent.userData = this.userData;
+        parent = parent.parent;
+      }
+      finish(parent, true);
+    }
+
+    // call failure callback if one exists
+    if(this.failureCallback) {
+      this.failureCallback(this);
+    }
+  }
+};
+
+/**
+ * Asynchronously start a task.
+ *
+ * @param task the task to start.
+ */
+var start = function(task) {
+  task.error = false;
+  task.state = sStateTable[task.state][START];
+  setTimeout(function() {
+    if(task.state === RUNNING) {
+      task.swapTime = +new Date();
+      task.run(task);
+      runNext(task, 0);
+    }
+  }, 0);
+};
+
+/**
+ * Run the next subtask or finish this task.
+ *
+ * @param task the task to process.
+ * @param recurse the recursion count.
+ */
+var runNext = function(task, recurse) {
+  // get time since last context swap (ms), if enough time has passed set
+  // swap to true to indicate that doNext was performed asynchronously
+  // also, if recurse is too high do asynchronously
+  var swap =
+    (recurse > sMaxRecursions) ||
+    (+new Date() - task.swapTime) > sTimeSlice;
+
+  var doNext = function(recurse) {
+    recurse++;
+    if(task.state === RUNNING) {
+      if(swap) {
+        // update swap time
+        task.swapTime = +new Date();
+      }
+
+      if(task.subtasks.length > 0) {
+        // run next subtask
+        var subtask = task.subtasks.shift();
+        subtask.error = task.error;
+        subtask.swapTime = task.swapTime;
+        subtask.userData = task.userData;
+        subtask.run(subtask);
+        if(!subtask.error) {
+           runNext(subtask, recurse);
+        }
+      } else {
+        finish(task);
+
+        if(!task.error) {
+          // chain back up and run parent
+          if(task.parent !== null) {
+            // propagate task info
+            task.parent.error = task.error;
+            task.parent.swapTime = task.swapTime;
+            task.parent.userData = task.userData;
+
+            // no subtasks left, call run next subtask on parent
+            runNext(task.parent, recurse);
+          }
+        }
+      }
+    }
+  };
+
+  if(swap) {
+    // we're swapping, so run asynchronously
+    setTimeout(doNext, 0);
+  } else {
+    // not swapping, so run synchronously
+    doNext(recurse);
+  }
+};
+
+/**
+ * Finishes a task and looks for the next task in the queue to start.
+ *
+ * @param task the task to finish.
+ * @param suppressCallbacks true to suppress callbacks.
+ */
+var finish = function(task, suppressCallbacks) {
+  // subtask is now done
+  task.state = DONE;
+
+  delete sTasks[task.id];
+  if(sVL >= 1) {
+    forge.log.verbose(cat, '[%s][%s] finish',
+      task.id, task.name, task);
+  }
+
+  // only do queue processing for root tasks
+  if(task.parent === null) {
+    // report error if queue is missing
+    if(!(task.type in sTaskQueues)) {
+      forge.log.error(cat,
+        '[%s][%s] task queue missing [%s]',
+        task.id, task.name, task.type);
+    } else if(sTaskQueues[task.type].length === 0) {
+      // report error if queue is empty
+      forge.log.error(cat,
+        '[%s][%s] task queue empty [%s]',
+        task.id, task.name, task.type);
+    } else if(sTaskQueues[task.type][0] !== task) {
+      // report error if this task isn't the first in the queue
+      forge.log.error(cat,
+        '[%s][%s] task not first in queue [%s]',
+        task.id, task.name, task.type);
+    } else {
+      // remove ourselves from the queue
+      sTaskQueues[task.type].shift();
+      // clean up queue if it is empty
+      if(sTaskQueues[task.type].length === 0) {
+        if(sVL >= 1) {
+          forge.log.verbose(cat, '[%s][%s] delete queue [%s]',
+            task.id, task.name, task.type);
+        }
+        /* Note: Only a task can delete a queue of its own type. This
+         is used as a way to synchronize tasks. If a queue for a certain
+         task type exists, then a task of that type is running.
+         */
+        delete sTaskQueues[task.type];
+      } else {
+        // dequeue the next task and start it
+        if(sVL >= 1) {
+          forge.log.verbose(cat,
+            '[%s][%s] queue start next [%s] remain:%s',
+            task.id, task.name, task.type,
+            sTaskQueues[task.type].length);
+        }
+        sTaskQueues[task.type][0].start();
+      }
+    }
+
+    if(!suppressCallbacks) {
+      // call final callback if one exists
+      if(task.error && task.failureCallback) {
+        task.failureCallback(task);
+      } else if(!task.error && task.successCallback) {
+        task.successCallback(task);
+      }
+    }
+  }
+};
+
+/* Tasks API */
+forge.task = forge.task || {};
+
+/**
+ * Starts a new task that will run the passed function asynchronously.
+ *
+ * In order to finish the task, either task.doNext() or task.fail()
+ * *must* be called.
+ *
+ * The task must have a type (a string identifier) that can be used to
+ * synchronize it with other tasks of the same type. That type can also
+ * be used to cancel tasks that haven't started yet.
+ *
+ * To start a task, the following object must be provided as a parameter
+ * (each function takes a task object as its first parameter):
+ *
+ * {
+ *   type: the type of task.
+ *   run: the function to run to execute the task.
+ *   success: a callback to call when the task succeeds (optional).
+ *   failure: a callback to call when the task fails (optional).
+ * }
+ *
+ * @param options the object as described above.
+ */
+forge.task.start = function(options) {
+  // create a new task
+  var task = new Task({
+    run: options.run,
+    name: options.name || sNoTaskName
+  });
+  task.type = options.type;
+  task.successCallback = options.success || null;
+  task.failureCallback = options.failure || null;
+
+  // append the task onto the appropriate queue
+  if(!(task.type in sTaskQueues)) {
+    if(sVL >= 1) {
+      forge.log.verbose(cat, '[%s][%s] create queue [%s]',
+        task.id, task.name, task.type);
+    }
+    // create the queue with the new task
+    sTaskQueues[task.type] = [task];
+    start(task);
+  } else {
+    // push the task onto the queue, it will be run after a task
+    // with the same type completes
+    sTaskQueues[options.type].push(task);
+  }
+};
+
+/**
+ * Cancels all tasks of the given type that haven't started yet.
+ *
+ * @param type the type of task to cancel.
+ */
+forge.task.cancel = function(type) {
+  // find the task queue
+  if(type in sTaskQueues) {
+    // empty all but the current task from the queue
+    sTaskQueues[type] = [sTaskQueues[type][0]];
+  }
+};
+
+/**
+ * Creates a condition variable to synchronize tasks. To make a task wait
+ * on the condition variable, call task.wait(condition). To notify all
+ * tasks that are waiting, call condition.notify().
+ *
+ * @return the condition variable.
+ */
+forge.task.createCondition = function() {
+  var cond = {
+    // all tasks that are blocked
+    tasks: {}
+  };
+
+  /**
+   * Causes the given task to block until notify is called. If the task
+   * is already waiting on this condition then this is a no-op.
+   *
+   * @param task the task to cause to wait.
+   */
+  cond.wait = function(task) {
+    // only block once
+    if(!(task.id in cond.tasks)) {
+       task.block();
+       cond.tasks[task.id] = task;
+    }
+  };
+
+  /**
+   * Notifies all waiting tasks to wake up.
+   */
+  cond.notify = function() {
+    // since unblock() will run the next task from here, make sure to
+    // clear the condition's blocked task list before unblocking
+    var tmp = cond.tasks;
+    cond.tasks = {};
+    for(var id in tmp) {
+      tmp[id].unblock();
+    }
+  };
+
+  return cond;
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'task';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './debug', './log', './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/tls.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/tls.js
new file mode 100644
index 0000000..b3bb2e8
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/tls.js
@@ -0,0 +1,4316 @@
+/**
+ * A Javascript implementation of Transport Layer Security (TLS).
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2009-2014 Digital Bazaar, Inc.
+ *
+ * The TLS Handshake Protocol involves the following steps:
+ *
+ * - Exchange hello messages to agree on algorithms, exchange random values,
+ * and check for session resumption.
+ *
+ * - Exchange the necessary cryptographic parameters to allow the client and
+ * server to agree on a premaster secret.
+ *
+ * - Exchange certificates and cryptographic information to allow the client
+ * and server to authenticate themselves.
+ *
+ * - Generate a master secret from the premaster secret and exchanged random
+ * values.
+ *
+ * - Provide security parameters to the record layer.
+ *
+ * - Allow the client and server to verify that their peer has calculated the
+ * same security parameters and that the handshake occurred without tampering
+ * by an attacker.
+ *
+ * Up to 4 different messages may be sent during a key exchange. The server
+ * certificate, the server key exchange, the client certificate, and the
+ * client key exchange.
+ *
+ * A typical handshake (from the client's perspective).
+ *
+ * 1. Client sends ClientHello.
+ * 2. Client receives ServerHello.
+ * 3. Client receives optional Certificate.
+ * 4. Client receives optional ServerKeyExchange.
+ * 5. Client receives ServerHelloDone.
+ * 6. Client sends optional Certificate.
+ * 7. Client sends ClientKeyExchange.
+ * 8. Client sends optional CertificateVerify.
+ * 9. Client sends ChangeCipherSpec.
+ * 10. Client sends Finished.
+ * 11. Client receives ChangeCipherSpec.
+ * 12. Client receives Finished.
+ * 13. Client sends/receives application data.
+ *
+ * To reuse an existing session:
+ *
+ * 1. Client sends ClientHello with session ID for reuse.
+ * 2. Client receives ServerHello with same session ID if reusing.
+ * 3. Client receives ChangeCipherSpec message if reusing.
+ * 4. Client receives Finished.
+ * 5. Client sends ChangeCipherSpec.
+ * 6. Client sends Finished.
+ *
+ * Note: Client ignores HelloRequest if in the middle of a handshake.
+ *
+ * Record Layer:
+ *
+ * The record layer fragments information blocks into TLSPlaintext records
+ * carrying data in chunks of 2^14 bytes or less. Client message boundaries are
+ * not preserved in the record layer (i.e., multiple client messages of the
+ * same ContentType MAY be coalesced into a single TLSPlaintext record, or a
+ * single message MAY be fragmented across several records).
+ *
+ * struct {
+ *   uint8 major;
+ *   uint8 minor;
+ * } ProtocolVersion;
+ *
+ * struct {
+ *   ContentType type;
+ *   ProtocolVersion version;
+ *   uint16 length;
+ *   opaque fragment[TLSPlaintext.length];
+ * } TLSPlaintext;
+ *
+ * type:
+ *   The higher-level protocol used to process the enclosed fragment.
+ *
+ * version:
+ *   The version of the protocol being employed. TLS Version 1.2 uses version
+ *   {3, 3}. TLS Version 1.0 uses version {3, 1}. Note that a client that
+ *   supports multiple versions of TLS may not know what version will be
+ *   employed before it receives the ServerHello.
+ *
+ * length:
+ *   The length (in bytes) of the following TLSPlaintext.fragment. The length
+ *   MUST NOT exceed 2^14 = 16384 bytes.
+ *
+ * fragment:
+ *   The application data. This data is transparent and treated as an
+ *   independent block to be dealt with by the higher-level protocol specified
+ *   by the type field.
+ *
+ * Implementations MUST NOT send zero-length fragments of Handshake, Alert, or
+ * ChangeCipherSpec content types. Zero-length fragments of Application data
+ * MAY be sent as they are potentially useful as a traffic analysis
+ * countermeasure.
+ *
+ * Note: Data of different TLS record layer content types MAY be interleaved.
+ * Application data is generally of lower precedence for transmission than
+ * other content types. However, records MUST be delivered to the network in
+ * the same order as they are protected by the record layer. Recipients MUST
+ * receive and process interleaved application layer traffic during handshakes
+ * subsequent to the first one on a connection.
+ *
+ * struct {
+ *   ContentType type;       // same as TLSPlaintext.type
+ *   ProtocolVersion version;// same as TLSPlaintext.version
+ *   uint16 length;
+ *   opaque fragment[TLSCompressed.length];
+ * } TLSCompressed;
+ *
+ * length:
+ *   The length (in bytes) of the following TLSCompressed.fragment.
+ *   The length MUST NOT exceed 2^14 + 1024.
+ *
+ * fragment:
+ *   The compressed form of TLSPlaintext.fragment.
+ *
+ * Note: A CompressionMethod.null operation is an identity operation; no fields
+ * are altered. In this implementation, since no compression is supported,
+ * uncompressed records are always the same as compressed records.
+ *
+ * Encryption Information:
+ *
+ * The encryption and MAC functions translate a TLSCompressed structure into a
+ * TLSCiphertext. The decryption functions reverse the process. The MAC of the
+ * record also includes a sequence number so that missing, extra, or repeated
+ * messages are detectable.
+ *
+ * struct {
+ *   ContentType type;
+ *   ProtocolVersion version;
+ *   uint16 length;
+ *   select (SecurityParameters.cipher_type) {
+ *     case stream: GenericStreamCipher;
+ *     case block:  GenericBlockCipher;
+ *     case aead:   GenericAEADCipher;
+ *   } fragment;
+ * } TLSCiphertext;
+ *
+ * type:
+ *   The type field is identical to TLSCompressed.type.
+ *
+ * version:
+ *   The version field is identical to TLSCompressed.version.
+ *
+ * length:
+ *   The length (in bytes) of the following TLSCiphertext.fragment.
+ *   The length MUST NOT exceed 2^14 + 2048.
+ *
+ * fragment:
+ *   The encrypted form of TLSCompressed.fragment, with the MAC.
+ *
+ * Note: Only CBC Block Ciphers are supported by this implementation.
+ *
+ * The TLSCompressed.fragment structures are converted to/from block
+ * TLSCiphertext.fragment structures.
+ *
+ * struct {
+ *   opaque IV[SecurityParameters.record_iv_length];
+ *   block-ciphered struct {
+ *     opaque content[TLSCompressed.length];
+ *     opaque MAC[SecurityParameters.mac_length];
+ *     uint8 padding[GenericBlockCipher.padding_length];
+ *     uint8 padding_length;
+ *   };
+ * } GenericBlockCipher;
+ *
+ * The MAC is generated as described in Section 6.2.3.1.
+ *
+ * IV:
+ *   The Initialization Vector (IV) SHOULD be chosen at random, and MUST be
+ *   unpredictable. Note that in versions of TLS prior to 1.1, there was no
+ *   IV field, and the last ciphertext block of the previous record (the "CBC
+ *   residue") was used as the IV. This was changed to prevent the attacks
+ *   described in [CBCATT]. For block ciphers, the IV length is of length
+ *   SecurityParameters.record_iv_length, which is equal to the
+ *   SecurityParameters.block_size.
+ *
+ * padding:
+ *   Padding that is added to force the length of the plaintext to be an
+ *   integral multiple of the block cipher's block length. The padding MAY be
+ *   any length up to 255 bytes, as long as it results in the
+ *   TLSCiphertext.length being an integral multiple of the block length.
+ *   Lengths longer than necessary might be desirable to frustrate attacks on
+ *   a protocol that are based on analysis of the lengths of exchanged
+ *   messages. Each uint8 in the padding data vector MUST be filled with the
+ *   padding length value. The receiver MUST check this padding and MUST use
+ *   the bad_record_mac alert to indicate padding errors.
+ *
+ * padding_length:
+ *   The padding length MUST be such that the total size of the
+ *   GenericBlockCipher structure is a multiple of the cipher's block length.
+ *   Legal values range from zero to 255, inclusive. This length specifies the
+ *   length of the padding field exclusive of the padding_length field itself.
+ *
+ * The encrypted data length (TLSCiphertext.length) is one more than the sum of
+ * SecurityParameters.block_length, TLSCompressed.length,
+ * SecurityParameters.mac_length, and padding_length.
+ *
+ * Example: If the block length is 8 bytes, the content length
+ * (TLSCompressed.length) is 61 bytes, and the MAC length is 20 bytes, then the
+ * length before padding is 82 bytes (this does not include the IV. Thus, the
+ * padding length modulo 8 must be equal to 6 in order to make the total length
+ * an even multiple of 8 bytes (the block length). The padding length can be
+ * 6, 14, 22, and so on, through 254. If the padding length were the minimum
+ * necessary, 6, the padding would be 6 bytes, each containing the value 6.
+ * Thus, the last 8 octets of the GenericBlockCipher before block encryption
+ * would be xx 06 06 06 06 06 06 06, where xx is the last octet of the MAC.
+ *
+ * Note: With block ciphers in CBC mode (Cipher Block Chaining), it is critical
+ * that the entire plaintext of the record be known before any ciphertext is
+ * transmitted. Otherwise, it is possible for the attacker to mount the attack
+ * described in [CBCATT].
+ *
+ * Implementation note: Canvel et al. [CBCTIME] have demonstrated a timing
+ * attack on CBC padding based on the time required to compute the MAC. In
+ * order to defend against this attack, implementations MUST ensure that
+ * record processing time is essentially the same whether or not the padding
+ * is correct. In general, the best way to do this is to compute the MAC even
+ * if the padding is incorrect, and only then reject the packet. For instance,
+ * if the pad appears to be incorrect, the implementation might assume a
+ * zero-length pad and then compute the MAC. This leaves a small timing
+ * channel, since MAC performance depends, to some extent, on the size of the
+ * data fragment, but it is not believed to be large enough to be exploitable,
+ * due to the large block size of existing MACs and the small size of the
+ * timing signal.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+/**
+ * Generates pseudo random bytes by mixing the result of two hash functions,
+ * MD5 and SHA-1.
+ *
+ * prf_TLS1(secret, label, seed) =
+ *   P_MD5(S1, label + seed) XOR P_SHA-1(S2, label + seed);
+ *
+ * Each P_hash function functions as follows:
+ *
+ * P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) +
+ *                        HMAC_hash(secret, A(2) + seed) +
+ *                        HMAC_hash(secret, A(3) + seed) + ...
+ * A() is defined as:
+ *   A(0) = seed
+ *   A(i) = HMAC_hash(secret, A(i-1))
+ *
+ * The '+' operator denotes concatenation.
+ *
+ * As many iterations A(N) as are needed are performed to generate enough
+ * pseudo random byte output. If an iteration creates more data than is
+ * necessary, then it is truncated.
+ *
+ * Therefore:
+ * A(1) = HMAC_hash(secret, A(0))
+ *      = HMAC_hash(secret, seed)
+ * A(2) = HMAC_hash(secret, A(1))
+ *      = HMAC_hash(secret, HMAC_hash(secret, seed))
+ *
+ * Therefore:
+ * P_hash(secret, seed) =
+ *   HMAC_hash(secret, HMAC_hash(secret, A(0)) + seed) +
+ *   HMAC_hash(secret, HMAC_hash(secret, A(1)) + seed) +
+ *   ...
+ *
+ * Therefore:
+ * P_hash(secret, seed) =
+ *   HMAC_hash(secret, HMAC_hash(secret, seed) + seed) +
+ *   HMAC_hash(secret, HMAC_hash(secret, HMAC_hash(secret, seed)) + seed) +
+ *   ...
+ *
+ * @param secret the secret to use.
+ * @param label the label to use.
+ * @param seed the seed value to use.
+ * @param length the number of bytes to generate.
+ *
+ * @return the pseudo random bytes in a byte buffer.
+ */
+var prf_TLS1 = function(secret, label, seed, length) {
+  var rval = forge.util.createBuffer();
+
+  /* For TLS 1.0, the secret is split in half, into two secrets of equal
+    length. If the secret has an odd length then the last byte of the first
+    half will be the same as the first byte of the second. The length of the
+    two secrets is half of the secret rounded up. */
+  var idx = (secret.length >> 1);
+  var slen = idx + (secret.length & 1);
+  var s1 = secret.substr(0, slen);
+  var s2 = secret.substr(idx, slen);
+  var ai = forge.util.createBuffer();
+  var hmac = forge.hmac.create();
+  seed = label + seed;
+
+  // determine the number of iterations that must be performed to generate
+  // enough output bytes, md5 creates 16 byte hashes, sha1 creates 20
+  var md5itr = Math.ceil(length / 16);
+  var sha1itr = Math.ceil(length / 20);
+
+  // do md5 iterations
+  hmac.start('MD5', s1);
+  var md5bytes = forge.util.createBuffer();
+  ai.putBytes(seed);
+  for(var i = 0; i < md5itr; ++i) {
+    // HMAC_hash(secret, A(i-1))
+    hmac.start(null, null);
+    hmac.update(ai.getBytes());
+    ai.putBuffer(hmac.digest());
+
+    // HMAC_hash(secret, A(i) + seed)
+    hmac.start(null, null);
+    hmac.update(ai.bytes() + seed);
+    md5bytes.putBuffer(hmac.digest());
+  }
+
+  // do sha1 iterations
+  hmac.start('SHA1', s2);
+  var sha1bytes = forge.util.createBuffer();
+  ai.clear();
+  ai.putBytes(seed);
+  for(var i = 0; i < sha1itr; ++i) {
+    // HMAC_hash(secret, A(i-1))
+    hmac.start(null, null);
+    hmac.update(ai.getBytes());
+    ai.putBuffer(hmac.digest());
+
+    // HMAC_hash(secret, A(i) + seed)
+    hmac.start(null, null);
+    hmac.update(ai.bytes() + seed);
+    sha1bytes.putBuffer(hmac.digest());
+  }
+
+  // XOR the md5 bytes with the sha1 bytes
+  rval.putBytes(forge.util.xorBytes(
+    md5bytes.getBytes(), sha1bytes.getBytes(), length));
+
+  return rval;
+};
+
+/**
+ * Generates pseudo random bytes using a SHA256 algorithm. For TLS 1.2.
+ *
+ * @param secret the secret to use.
+ * @param label the label to use.
+ * @param seed the seed value to use.
+ * @param length the number of bytes to generate.
+ *
+ * @return the pseudo random bytes in a byte buffer.
+ */
+var prf_sha256 = function(secret, label, seed, length) {
+   // FIXME: implement me for TLS 1.2
+};
+
+/**
+ * Gets a MAC for a record using the SHA-1 hash algorithm.
+ *
+ * @param key the mac key.
+ * @param state the sequence number (array of two 32-bit integers).
+ * @param record the record.
+ *
+ * @return the sha-1 hash (20 bytes) for the given record.
+ */
+var hmac_sha1 = function(key, seqNum, record) {
+  /* MAC is computed like so:
+  HMAC_hash(
+    key, seqNum +
+      TLSCompressed.type +
+      TLSCompressed.version +
+      TLSCompressed.length +
+      TLSCompressed.fragment)
+  */
+  var hmac = forge.hmac.create();
+  hmac.start('SHA1', key);
+  var b = forge.util.createBuffer();
+  b.putInt32(seqNum[0]);
+  b.putInt32(seqNum[1]);
+  b.putByte(record.type);
+  b.putByte(record.version.major);
+  b.putByte(record.version.minor);
+  b.putInt16(record.length);
+  b.putBytes(record.fragment.bytes());
+  hmac.update(b.getBytes());
+  return hmac.digest().getBytes();
+};
+
+/**
+ * Compresses the TLSPlaintext record into a TLSCompressed record using the
+ * deflate algorithm.
+ *
+ * @param c the TLS connection.
+ * @param record the TLSPlaintext record to compress.
+ * @param s the ConnectionState to use.
+ *
+ * @return true on success, false on failure.
+ */
+var deflate = function(c, record, s) {
+  var rval = false;
+
+  try {
+    var bytes = c.deflate(record.fragment.getBytes());
+    record.fragment = forge.util.createBuffer(bytes);
+    record.length = bytes.length;
+    rval = true;
+  } catch(ex) {
+    // deflate error, fail out
+  }
+
+  return rval;
+};
+
+/**
+ * Decompresses the TLSCompressed record into a TLSPlaintext record using the
+ * deflate algorithm.
+ *
+ * @param c the TLS connection.
+ * @param record the TLSCompressed record to decompress.
+ * @param s the ConnectionState to use.
+ *
+ * @return true on success, false on failure.
+ */
+var inflate = function(c, record, s) {
+  var rval = false;
+
+  try {
+    var bytes = c.inflate(record.fragment.getBytes());
+    record.fragment = forge.util.createBuffer(bytes);
+    record.length = bytes.length;
+    rval = true;
+  } catch(ex) {
+    // inflate error, fail out
+  }
+
+  return rval;
+};
+
+/**
+ * Reads a TLS variable-length vector from a byte buffer.
+ *
+ * Variable-length vectors are defined by specifying a subrange of legal
+ * lengths, inclusively, using the notation <floor..ceiling>. When these are
+ * encoded, the actual length precedes the vector's contents in the byte
+ * stream. The length will be in the form of a number consuming as many bytes
+ * as required to hold the vector's specified maximum (ceiling) length. A
+ * variable-length vector with an actual length field of zero is referred to
+ * as an empty vector.
+ *
+ * @param b the byte buffer.
+ * @param lenBytes the number of bytes required to store the length.
+ *
+ * @return the resulting byte buffer.
+ */
+var readVector = function(b, lenBytes) {
+  var len = 0;
+  switch(lenBytes) {
+  case 1:
+    len = b.getByte();
+    break;
+  case 2:
+    len = b.getInt16();
+    break;
+  case 3:
+    len = b.getInt24();
+    break;
+  case 4:
+    len = b.getInt32();
+    break;
+  }
+
+  // read vector bytes into a new buffer
+  return forge.util.createBuffer(b.getBytes(len));
+};
+
+/**
+ * Writes a TLS variable-length vector to a byte buffer.
+ *
+ * @param b the byte buffer.
+ * @param lenBytes the number of bytes required to store the length.
+ * @param v the byte buffer vector.
+ */
+var writeVector = function(b, lenBytes, v) {
+  // encode length at the start of the vector, where the number of bytes for
+  // the length is the maximum number of bytes it would take to encode the
+  // vector's ceiling
+  b.putInt(v.length(), lenBytes << 3);
+  b.putBuffer(v);
+};
+
+/**
+ * The tls implementation.
+ */
+var tls = {};
+
+/**
+ * Version: TLS 1.2 = 3.3, TLS 1.1 = 3.2, TLS 1.0 = 3.1. Both TLS 1.1 and
+ * TLS 1.2 were still too new (ie: openSSL didn't implement them) at the time
+ * of this implementation so TLS 1.0 was implemented instead.
+ */
+tls.Versions = {
+  TLS_1_0: {major: 3, minor: 1},
+  TLS_1_1: {major: 3, minor: 2},
+  TLS_1_2: {major: 3, minor: 3}
+};
+tls.SupportedVersions = [
+  tls.Versions.TLS_1_1,
+  tls.Versions.TLS_1_0
+];
+tls.Version = tls.SupportedVersions[0];
+
+/**
+ * Maximum fragment size. True maximum is 16384, but we fragment before that
+ * to allow for unusual small increases during compression.
+ */
+tls.MaxFragment = 16384 - 1024;
+
+/**
+ * Whether this entity is considered the "client" or "server".
+ * enum { server, client } ConnectionEnd;
+ */
+tls.ConnectionEnd = {
+  server: 0,
+  client: 1
+};
+
+/**
+ * Pseudo-random function algorithm used to generate keys from the master
+ * secret.
+ * enum { tls_prf_sha256 } PRFAlgorithm;
+ */
+tls.PRFAlgorithm = {
+  tls_prf_sha256: 0
+};
+
+/**
+ * Bulk encryption algorithms.
+ * enum { null, rc4, des3, aes } BulkCipherAlgorithm;
+ */
+tls.BulkCipherAlgorithm = {
+  none: null,
+  rc4: 0,
+  des3: 1,
+  aes: 2
+};
+
+/**
+ * Cipher types.
+ * enum { stream, block, aead } CipherType;
+ */
+tls.CipherType = {
+  stream: 0,
+  block: 1,
+  aead: 2
+};
+
+/**
+ * MAC (Message Authentication Code) algorithms.
+ * enum { null, hmac_md5, hmac_sha1, hmac_sha256,
+ *   hmac_sha384, hmac_sha512} MACAlgorithm;
+ */
+tls.MACAlgorithm = {
+  none: null,
+  hmac_md5: 0,
+  hmac_sha1: 1,
+  hmac_sha256: 2,
+  hmac_sha384: 3,
+  hmac_sha512: 4
+};
+
+/**
+ * Compression algorithms.
+ * enum { null(0), deflate(1), (255) } CompressionMethod;
+ */
+tls.CompressionMethod = {
+  none: 0,
+  deflate: 1
+};
+
+/**
+ * TLS record content types.
+ * enum {
+ *   change_cipher_spec(20), alert(21), handshake(22),
+ *   application_data(23), (255)
+ * } ContentType;
+ */
+tls.ContentType = {
+  change_cipher_spec: 20,
+  alert: 21,
+  handshake: 22,
+  application_data: 23,
+  heartbeat: 24
+};
+
+/**
+ * TLS handshake types.
+ * enum {
+ *   hello_request(0), client_hello(1), server_hello(2),
+ *   certificate(11), server_key_exchange (12),
+ *   certificate_request(13), server_hello_done(14),
+ *   certificate_verify(15), client_key_exchange(16),
+ *   finished(20), (255)
+ * } HandshakeType;
+ */
+tls.HandshakeType = {
+  hello_request: 0,
+  client_hello: 1,
+  server_hello: 2,
+  certificate: 11,
+  server_key_exchange: 12,
+  certificate_request: 13,
+  server_hello_done: 14,
+  certificate_verify: 15,
+  client_key_exchange: 16,
+  finished: 20
+};
+
+/**
+ * TLS Alert Protocol.
+ *
+ * enum { warning(1), fatal(2), (255) } AlertLevel;
+ *
+ * enum {
+ *   close_notify(0),
+ *   unexpected_message(10),
+ *   bad_record_mac(20),
+ *   decryption_failed(21),
+ *   record_overflow(22),
+ *   decompression_failure(30),
+ *   handshake_failure(40),
+ *   bad_certificate(42),
+ *   unsupported_certificate(43),
+ *   certificate_revoked(44),
+ *   certificate_expired(45),
+ *   certificate_unknown(46),
+ *   illegal_parameter(47),
+ *   unknown_ca(48),
+ *   access_denied(49),
+ *   decode_error(50),
+ *   decrypt_error(51),
+ *   export_restriction(60),
+ *   protocol_version(70),
+ *   insufficient_security(71),
+ *   internal_error(80),
+ *   user_canceled(90),
+ *   no_renegotiation(100),
+ *   (255)
+ * } AlertDescription;
+ *
+ * struct {
+ *   AlertLevel level;
+ *   AlertDescription description;
+ * } Alert;
+ */
+tls.Alert = {};
+tls.Alert.Level = {
+  warning: 1,
+  fatal: 2
+};
+tls.Alert.Description = {
+  close_notify: 0,
+  unexpected_message: 10,
+  bad_record_mac: 20,
+  decryption_failed: 21,
+  record_overflow: 22,
+  decompression_failure: 30,
+  handshake_failure: 40,
+  bad_certificate: 42,
+  unsupported_certificate: 43,
+  certificate_revoked: 44,
+  certificate_expired: 45,
+  certificate_unknown: 46,
+  illegal_parameter: 47,
+  unknown_ca: 48,
+  access_denied: 49,
+  decode_error: 50,
+  decrypt_error: 51,
+  export_restriction: 60,
+  protocol_version: 70,
+  insufficient_security: 71,
+  internal_error: 80,
+  user_canceled: 90,
+  no_renegotiation: 100
+};
+
+/**
+ * TLS Heartbeat Message types.
+ * enum {
+ *   heartbeat_request(1),
+ *   heartbeat_response(2),
+ *   (255)
+ * } HeartbeatMessageType;
+ */
+tls.HeartbeatMessageType = {
+  heartbeat_request: 1,
+  heartbeat_response: 2
+};
+
+/**
+ * Supported cipher suites.
+ */
+tls.CipherSuites = {};
+
+/**
+ * Gets a supported cipher suite from its 2 byte ID.
+ *
+ * @param twoBytes two bytes in a string.
+ *
+ * @return the matching supported cipher suite or null.
+ */
+tls.getCipherSuite = function(twoBytes) {
+  var rval = null;
+  for(var key in tls.CipherSuites) {
+    var cs = tls.CipherSuites[key];
+    if(cs.id[0] === twoBytes.charCodeAt(0) &&
+      cs.id[1] === twoBytes.charCodeAt(1)) {
+      rval = cs;
+      break;
+    }
+  }
+  return rval;
+};
+
+/**
+ * Called when an unexpected record is encountered.
+ *
+ * @param c the connection.
+ * @param record the record.
+ */
+tls.handleUnexpected = function(c, record) {
+  // if connection is client and closed, ignore unexpected messages
+  var ignore = (!c.open && c.entity === tls.ConnectionEnd.client);
+  if(!ignore) {
+    c.error(c, {
+      message: 'Unexpected message. Received TLS record out of order.',
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.unexpected_message
+      }
+    });
+  }
+};
+
+/**
+ * Called when a client receives a HelloRequest record.
+ *
+ * @param c the connection.
+ * @param record the record.
+ * @param length the length of the handshake message.
+ */
+tls.handleHelloRequest = function(c, record, length) {
+  // ignore renegotiation requests from the server during a handshake, but
+  // if handshaking, send a warning alert that renegotation is denied
+  if(!c.handshaking && c.handshakes > 0) {
+    // send alert warning
+    tls.queue(c, tls.createAlert(c, {
+       level: tls.Alert.Level.warning,
+       description: tls.Alert.Description.no_renegotiation
+    }));
+    tls.flush(c);
+  }
+
+  // continue
+  c.process();
+};
+
+/**
+ * Parses a hello message from a ClientHello or ServerHello record.
+ *
+ * @param record the record to parse.
+ *
+ * @return the parsed message.
+ */
+tls.parseHelloMessage = function(c, record, length) {
+  var msg = null;
+
+  var client = (c.entity === tls.ConnectionEnd.client);
+
+  // minimum of 38 bytes in message
+  if(length < 38) {
+    c.error(c, {
+      message: client ?
+        'Invalid ServerHello message. Message too short.' :
+        'Invalid ClientHello message. Message too short.',
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.illegal_parameter
+      }
+    });
+  } else {
+    // use 'remaining' to calculate # of remaining bytes in the message
+    var b = record.fragment;
+    var remaining = b.length();
+    msg = {
+      version: {
+        major: b.getByte(),
+        minor: b.getByte()
+      },
+      random: forge.util.createBuffer(b.getBytes(32)),
+      session_id: readVector(b, 1),
+      extensions: []
+    };
+    if(client) {
+      msg.cipher_suite = b.getBytes(2);
+      msg.compression_method = b.getByte();
+    } else {
+      msg.cipher_suites = readVector(b, 2);
+      msg.compression_methods = readVector(b, 1);
+    }
+
+    // read extensions if there are any bytes left in the message
+    remaining = length - (remaining - b.length());
+    if(remaining > 0) {
+      // parse extensions
+      var exts = readVector(b, 2);
+      while(exts.length() > 0) {
+        msg.extensions.push({
+          type: [exts.getByte(), exts.getByte()],
+          data: readVector(exts, 2)
+        });
+      }
+
+      // TODO: make extension support modular
+      if(!client) {
+        for(var i = 0; i < msg.extensions.length; ++i) {
+          var ext = msg.extensions[i];
+
+          // support SNI extension
+          if(ext.type[0] === 0x00 && ext.type[1] === 0x00) {
+            // get server name list
+            var snl = readVector(ext.data, 2);
+            while(snl.length() > 0) {
+              // read server name type
+              var snType = snl.getByte();
+
+              // only HostName type (0x00) is known, break out if
+              // another type is detected
+              if(snType !== 0x00) {
+                break;
+              }
+
+              // add host name to server name list
+              c.session.extensions.server_name.serverNameList.push(
+                readVector(snl, 2).getBytes());
+            }
+          }
+        }
+      }
+    }
+
+    // version already set, do not allow version change
+    if(c.session.version) {
+      if(msg.version.major !== c.session.version.major ||
+        msg.version.minor !== c.session.version.minor) {
+        return c.error(c, {
+          message: 'TLS version change is disallowed during renegotiation.',
+          send: true,
+          alert: {
+            level: tls.Alert.Level.fatal,
+            description: tls.Alert.Description.protocol_version
+          }
+        });
+      }
+    }
+
+    // get the chosen (ServerHello) cipher suite
+    if(client) {
+      // FIXME: should be checking configured acceptable cipher suites
+      c.session.cipherSuite = tls.getCipherSuite(msg.cipher_suite);
+    } else {
+      // get a supported preferred (ClientHello) cipher suite
+      // choose the first supported cipher suite
+      var tmp = forge.util.createBuffer(msg.cipher_suites.bytes());
+      while(tmp.length() > 0) {
+        // FIXME: should be checking configured acceptable suites
+        // cipher suites take up 2 bytes
+        c.session.cipherSuite = tls.getCipherSuite(tmp.getBytes(2));
+        if(c.session.cipherSuite !== null) {
+          break;
+        }
+      }
+    }
+
+    // cipher suite not supported
+    if(c.session.cipherSuite === null) {
+      return c.error(c, {
+        message: 'No cipher suites in common.',
+        send: true,
+        alert: {
+          level: tls.Alert.Level.fatal,
+          description: tls.Alert.Description.handshake_failure
+        },
+        cipherSuite: forge.util.bytesToHex(msg.cipher_suite)
+      });
+    }
+
+    // TODO: handle compression methods
+    if(client) {
+      c.session.compressionMethod = msg.compression_method;
+    } else {
+      // no compression
+      c.session.compressionMethod = tls.CompressionMethod.none;
+    }
+  }
+
+  return msg;
+};
+
+/**
+ * Creates security parameters for the given connection based on the given
+ * hello message.
+ *
+ * @param c the TLS connection.
+ * @param msg the hello message.
+ */
+tls.createSecurityParameters = function(c, msg) {
+  /* Note: security params are from TLS 1.2, some values like prf_algorithm
+  are ignored for TLS 1.0/1.1 and the builtin as specified in the spec is
+  used. */
+
+  // TODO: handle other options from server when more supported
+
+  // get client and server randoms
+  var client = (c.entity === tls.ConnectionEnd.client);
+  var msgRandom = msg.random.bytes();
+  var cRandom = client ? c.session.sp.client_random : msgRandom;
+  var sRandom = client ? msgRandom : tls.createRandom().getBytes();
+
+  // create new security parameters
+  c.session.sp = {
+    entity: c.entity,
+    prf_algorithm: tls.PRFAlgorithm.tls_prf_sha256,
+    bulk_cipher_algorithm: null,
+    cipher_type: null,
+    enc_key_length: null,
+    block_length: null,
+    fixed_iv_length: null,
+    record_iv_length: null,
+    mac_algorithm: null,
+    mac_length: null,
+    mac_key_length: null,
+    compression_algorithm: c.session.compressionMethod,
+    pre_master_secret: null,
+    master_secret: null,
+    client_random: cRandom,
+    server_random: sRandom
+  };
+};
+
+/**
+ * Called when a client receives a ServerHello record.
+ *
+ * When a ServerHello message will be sent:
+ *   The server will send this message in response to a client hello message
+ *   when it was able to find an acceptable set of algorithms. If it cannot
+ *   find such a match, it will respond with a handshake failure alert.
+ *
+ * uint24 length;
+ * struct {
+ *   ProtocolVersion server_version;
+ *   Random random;
+ *   SessionID session_id;
+ *   CipherSuite cipher_suite;
+ *   CompressionMethod compression_method;
+ *   select(extensions_present) {
+ *     case false:
+ *       struct {};
+ *     case true:
+ *       Extension extensions<0..2^16-1>;
+ *   };
+ * } ServerHello;
+ *
+ * @param c the connection.
+ * @param record the record.
+ * @param length the length of the handshake message.
+ */
+tls.handleServerHello = function(c, record, length) {
+  var msg = tls.parseHelloMessage(c, record, length);
+  if(c.fail) {
+    return;
+  }
+
+  // ensure server version is compatible
+  if(msg.version.minor <= c.version.minor) {
+    c.version.minor = msg.version.minor;
+  } else {
+    return c.error(c, {
+      message: 'Incompatible TLS version.',
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.protocol_version
+      }
+    });
+  }
+
+  // indicate session version has been set
+  c.session.version = c.version;
+
+  // get the session ID from the message
+  var sessionId = msg.session_id.bytes();
+
+  // if the session ID is not blank and matches the cached one, resume
+  // the session
+  if(sessionId.length > 0 && sessionId === c.session.id) {
+    // resuming session, expect a ChangeCipherSpec next
+    c.expect = SCC;
+    c.session.resuming = true;
+
+    // get new server random
+    c.session.sp.server_random = msg.random.bytes();
+  } else {
+    // not resuming, expect a server Certificate message next
+    c.expect = SCE;
+    c.session.resuming = false;
+
+    // create new security parameters
+    tls.createSecurityParameters(c, msg);
+  }
+
+  // set new session ID
+  c.session.id = sessionId;
+
+  // continue
+  c.process();
+};
+
+/**
+ * Called when a server receives a ClientHello record.
+ *
+ * When a ClientHello message will be sent:
+ *   When a client first connects to a server it is required to send the
+ *   client hello as its first message. The client can also send a client
+ *   hello in response to a hello request or on its own initiative in order
+ *   to renegotiate the security parameters in an existing connection.
+ *
+ * @param c the connection.
+ * @param record the record.
+ * @param length the length of the handshake message.
+ */
+tls.handleClientHello = function(c, record, length) {
+  var msg = tls.parseHelloMessage(c, record, length);
+  if(c.fail) {
+    return;
+  }
+
+  // get the session ID from the message
+  var sessionId = msg.session_id.bytes();
+
+  // see if the given session ID is in the cache
+  var session = null;
+  if(c.sessionCache) {
+    session = c.sessionCache.getSession(sessionId);
+    if(session === null) {
+      // session ID not found
+      sessionId = '';
+    } else if(session.version.major !== msg.version.major ||
+      session.version.minor > msg.version.minor) {
+      // if session version is incompatible with client version, do not resume
+      session = null;
+      sessionId = '';
+    }
+  }
+
+  // no session found to resume, generate a new session ID
+  if(sessionId.length === 0) {
+    sessionId = forge.random.getBytes(32);
+  }
+
+  // update session
+  c.session.id = sessionId;
+  c.session.clientHelloVersion = msg.version;
+  c.session.sp = {};
+  if(session) {
+    // use version and security parameters from resumed session
+    c.version = c.session.version = session.version;
+    c.session.sp = session.sp;
+  } else {
+    // use highest compatible minor version
+    var version;
+    for(var i = 1; i < tls.SupportedVersions.length; ++i) {
+      version = tls.SupportedVersions[i];
+      if(version.minor <= msg.version.minor) {
+        break;
+      }
+    }
+    c.version = {major: version.major, minor: version.minor};
+    c.session.version = c.version;
+  }
+
+  // if a session is set, resume it
+  if(session !== null) {
+    // resuming session, expect a ChangeCipherSpec next
+    c.expect = CCC;
+    c.session.resuming = true;
+
+    // get new client random
+    c.session.sp.client_random = msg.random.bytes();
+  } else {
+    // not resuming, expect a Certificate or ClientKeyExchange
+    c.expect = (c.verifyClient !== false) ? CCE : CKE;
+    c.session.resuming = false;
+
+    // create new security parameters
+    tls.createSecurityParameters(c, msg);
+  }
+
+  // connection now open
+  c.open = true;
+
+  // queue server hello
+  tls.queue(c, tls.createRecord(c, {
+    type: tls.ContentType.handshake,
+    data: tls.createServerHello(c)
+  }));
+
+  if(c.session.resuming) {
+    // queue change cipher spec message
+    tls.queue(c, tls.createRecord(c, {
+      type: tls.ContentType.change_cipher_spec,
+      data: tls.createChangeCipherSpec()
+    }));
+
+    // create pending state
+    c.state.pending = tls.createConnectionState(c);
+
+    // change current write state to pending write state
+    c.state.current.write = c.state.pending.write;
+
+    // queue finished
+    tls.queue(c, tls.createRecord(c, {
+      type: tls.ContentType.handshake,
+      data: tls.createFinished(c)
+    }));
+  } else {
+    // queue server certificate
+    tls.queue(c, tls.createRecord(c, {
+      type: tls.ContentType.handshake,
+      data: tls.createCertificate(c)
+    }));
+
+    if(!c.fail) {
+      // queue server key exchange
+      tls.queue(c, tls.createRecord(c, {
+        type: tls.ContentType.handshake,
+        data: tls.createServerKeyExchange(c)
+      }));
+
+      // request client certificate if set
+      if(c.verifyClient !== false) {
+        // queue certificate request
+        tls.queue(c, tls.createRecord(c, {
+          type: tls.ContentType.handshake,
+          data: tls.createCertificateRequest(c)
+        }));
+      }
+
+      // queue server hello done
+      tls.queue(c, tls.createRecord(c, {
+        type: tls.ContentType.handshake,
+        data: tls.createServerHelloDone(c)
+      }));
+    }
+  }
+
+  // send records
+  tls.flush(c);
+
+  // continue
+  c.process();
+};
+
+/**
+ * Called when a client receives a Certificate record.
+ *
+ * When this message will be sent:
+ *   The server must send a certificate whenever the agreed-upon key exchange
+ *   method is not an anonymous one. This message will always immediately
+ *   follow the server hello message.
+ *
+ * Meaning of this message:
+ *   The certificate type must be appropriate for the selected cipher suite's
+ *   key exchange algorithm, and is generally an X.509v3 certificate. It must
+ *   contain a key which matches the key exchange method, as follows. Unless
+ *   otherwise specified, the signing algorithm for the certificate must be
+ *   the same as the algorithm for the certificate key. Unless otherwise
+ *   specified, the public key may be of any length.
+ *
+ * opaque ASN.1Cert<1..2^24-1>;
+ * struct {
+ *   ASN.1Cert certificate_list<1..2^24-1>;
+ * } Certificate;
+ *
+ * @param c the connection.
+ * @param record the record.
+ * @param length the length of the handshake message.
+ */
+tls.handleCertificate = function(c, record, length) {
+  // minimum of 3 bytes in message
+  if(length < 3) {
+    return c.error(c, {
+      message: 'Invalid Certificate message. Message too short.',
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.illegal_parameter
+      }
+    });
+  }
+
+  var b = record.fragment;
+  var msg = {
+    certificate_list: readVector(b, 3)
+  };
+
+  /* The sender's certificate will be first in the list (chain), each
+    subsequent one that follows will certify the previous one, but root
+    certificates (self-signed) that specify the certificate authority may
+    be omitted under the assumption that clients must already possess it. */
+  var cert, asn1;
+  var certs = [];
+  try {
+    while(msg.certificate_list.length() > 0) {
+      // each entry in msg.certificate_list is a vector with 3 len bytes
+      cert = readVector(msg.certificate_list, 3);
+      asn1 = forge.asn1.fromDer(cert);
+      cert = forge.pki.certificateFromAsn1(asn1, true);
+      certs.push(cert);
+    }
+  } catch(ex) {
+    return c.error(c, {
+      message: 'Could not parse certificate list.',
+      cause: ex,
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.bad_certificate
+      }
+    });
+  }
+
+  // ensure at least 1 certificate was provided if in client-mode
+  // or if verifyClient was set to true to require a certificate
+  // (as opposed to 'optional')
+  var client = (c.entity === tls.ConnectionEnd.client);
+  if((client || c.verifyClient === true) && certs.length === 0) {
+    // error, no certificate
+    c.error(c, {
+      message: client ?
+        'No server certificate provided.' :
+        'No client certificate provided.',
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.illegal_parameter
+      }
+    });
+  } else if(certs.length === 0) {
+    // no certs to verify
+    // expect a ServerKeyExchange or ClientKeyExchange message next
+    c.expect = client ? SKE : CKE;
+  } else {
+    // save certificate in session
+    if(client) {
+      c.session.serverCertificate = certs[0];
+    } else {
+      c.session.clientCertificate = certs[0];
+    }
+
+    if(tls.verifyCertificateChain(c, certs)) {
+      // expect a ServerKeyExchange or ClientKeyExchange message next
+      c.expect = client ? SKE : CKE;
+    }
+  }
+
+  // continue
+  c.process();
+};
+
+/**
+ * Called when a client receives a ServerKeyExchange record.
+ *
+ * When this message will be sent:
+ *   This message will be sent immediately after the server certificate
+ *   message (or the server hello message, if this is an anonymous
+ *   negotiation).
+ *
+ *   The server key exchange message is sent by the server only when the
+ *   server certificate message (if sent) does not contain enough data to
+ *   allow the client to exchange a premaster secret.
+ *
+ * Meaning of this message:
+ *   This message conveys cryptographic information to allow the client to
+ *   communicate the premaster secret: either an RSA public key to encrypt
+ *   the premaster secret with, or a Diffie-Hellman public key with which the
+ *   client can complete a key exchange (with the result being the premaster
+ *   secret.)
+ *
+ * enum {
+ *   dhe_dss, dhe_rsa, dh_anon, rsa, dh_dss, dh_rsa
+ * } KeyExchangeAlgorithm;
+ *
+ * struct {
+ *   opaque dh_p<1..2^16-1>;
+ *   opaque dh_g<1..2^16-1>;
+ *   opaque dh_Ys<1..2^16-1>;
+ * } ServerDHParams;
+ *
+ * struct {
+ *   select(KeyExchangeAlgorithm) {
+ *     case dh_anon:
+ *       ServerDHParams params;
+ *     case dhe_dss:
+ *     case dhe_rsa:
+ *       ServerDHParams params;
+ *       digitally-signed struct {
+ *         opaque client_random[32];
+ *         opaque server_random[32];
+ *         ServerDHParams params;
+ *       } signed_params;
+ *     case rsa:
+ *     case dh_dss:
+ *     case dh_rsa:
+ *       struct {};
+ *   };
+ * } ServerKeyExchange;
+ *
+ * @param c the connection.
+ * @param record the record.
+ * @param length the length of the handshake message.
+ */
+tls.handleServerKeyExchange = function(c, record, length) {
+  // this implementation only supports RSA, no Diffie-Hellman support
+  // so any length > 0 is invalid
+  if(length > 0) {
+    return c.error(c, {
+      message: 'Invalid key parameters. Only RSA is supported.',
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.unsupported_certificate
+      }
+    });
+  }
+
+  // expect an optional CertificateRequest message next
+  c.expect = SCR;
+
+  // continue
+  c.process();
+};
+
+/**
+ * Called when a client receives a ClientKeyExchange record.
+ *
+ * @param c the connection.
+ * @param record the record.
+ * @param length the length of the handshake message.
+ */
+tls.handleClientKeyExchange = function(c, record, length) {
+  // this implementation only supports RSA, no Diffie-Hellman support
+  // so any length < 48 is invalid
+  if(length < 48) {
+    return c.error(c, {
+      message: 'Invalid key parameters. Only RSA is supported.',
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.unsupported_certificate
+      }
+    });
+  }
+
+  var b = record.fragment;
+  var msg = {
+    enc_pre_master_secret: readVector(b, 2).getBytes()
+  };
+
+  // do rsa decryption
+  var privateKey = null;
+  if(c.getPrivateKey) {
+    try {
+      privateKey = c.getPrivateKey(c, c.session.serverCertificate);
+      privateKey = forge.pki.privateKeyFromPem(privateKey);
+    } catch(ex) {
+      c.error(c, {
+        message: 'Could not get private key.',
+        cause: ex,
+        send: true,
+        alert: {
+          level: tls.Alert.Level.fatal,
+          description: tls.Alert.Description.internal_error
+        }
+      });
+    }
+  }
+
+  if(privateKey === null) {
+    return c.error(c, {
+      message: 'No private key set.',
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.internal_error
+      }
+    });
+  }
+
+  try {
+    // decrypt 48-byte pre-master secret
+    var sp = c.session.sp;
+    sp.pre_master_secret = privateKey.decrypt(msg.enc_pre_master_secret);
+
+    // ensure client hello version matches first 2 bytes
+    var version = c.session.clientHelloVersion;
+    if(version.major !== sp.pre_master_secret.charCodeAt(0) ||
+      version.minor !== sp.pre_master_secret.charCodeAt(1)) {
+      // error, do not send alert (see BLEI attack below)
+      throw new Error('TLS version rollback attack detected.');
+    }
+  } catch(ex) {
+    /* Note: Daniel Bleichenbacher [BLEI] can be used to attack a
+      TLS server which is using PKCS#1 encoded RSA, so instead of
+      failing here, we generate 48 random bytes and use that as
+      the pre-master secret. */
+    sp.pre_master_secret = forge.random.getBytes(48);
+  }
+
+  // expect a CertificateVerify message if a Certificate was received that
+  // does not have fixed Diffie-Hellman params, otherwise expect
+  // ChangeCipherSpec
+  c.expect = CCC;
+  if(c.session.clientCertificate !== null) {
+    // only RSA support, so expect CertificateVerify
+    // TODO: support Diffie-Hellman
+    c.expect = CCV;
+  }
+
+  // continue
+  c.process();
+};
+
+/**
+ * Called when a client receives a CertificateRequest record.
+ *
+ * When this message will be sent:
+ *   A non-anonymous server can optionally request a certificate from the
+ *   client, if appropriate for the selected cipher suite. This message, if
+ *   sent, will immediately follow the Server Key Exchange message (if it is
+ *   sent; otherwise, the Server Certificate message).
+ *
+ * enum {
+ *   rsa_sign(1), dss_sign(2), rsa_fixed_dh(3), dss_fixed_dh(4),
+ *   rsa_ephemeral_dh_RESERVED(5), dss_ephemeral_dh_RESERVED(6),
+ *   fortezza_dms_RESERVED(20), (255)
+ * } ClientCertificateType;
+ *
+ * opaque DistinguishedName<1..2^16-1>;
+ *
+ * struct {
+ *   ClientCertificateType certificate_types<1..2^8-1>;
+ *   SignatureAndHashAlgorithm supported_signature_algorithms<2^16-1>;
+ *   DistinguishedName certificate_authorities<0..2^16-1>;
+ * } CertificateRequest;
+ *
+ * @param c the connection.
+ * @param record the record.
+ * @param length the length of the handshake message.
+ */
+tls.handleCertificateRequest = function(c, record, length) {
+  // minimum of 3 bytes in message
+  if(length < 3) {
+    return c.error(c, {
+      message: 'Invalid CertificateRequest. Message too short.',
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.illegal_parameter
+      }
+    });
+  }
+
+  // TODO: TLS 1.2+ has different format including
+  // SignatureAndHashAlgorithm after cert types
+  var b = record.fragment;
+  var msg = {
+    certificate_types: readVector(b, 1),
+    certificate_authorities: readVector(b, 2)
+  };
+
+  // save certificate request in session
+  c.session.certificateRequest = msg;
+
+  // expect a ServerHelloDone message next
+  c.expect = SHD;
+
+  // continue
+  c.process();
+};
+
+/**
+ * Called when a server receives a CertificateVerify record.
+ *
+ * @param c the connection.
+ * @param record the record.
+ * @param length the length of the handshake message.
+ */
+tls.handleCertificateVerify = function(c, record, length) {
+  if(length < 2) {
+    return c.error(c, {
+      message: 'Invalid CertificateVerify. Message too short.',
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.illegal_parameter
+      }
+    });
+  }
+
+  // rewind to get full bytes for message so it can be manually
+  // digested below (special case for CertificateVerify messages because
+  // they must be digested *after* handling as opposed to all others)
+  var b = record.fragment;
+  b.read -= 4;
+  var msgBytes = b.bytes();
+  b.read += 4;
+
+  var msg = {
+    signature: readVector(b, 2).getBytes()
+  };
+
+  // TODO: add support for DSA
+
+  // generate data to verify
+  var verify = forge.util.createBuffer();
+  verify.putBuffer(c.session.md5.digest());
+  verify.putBuffer(c.session.sha1.digest());
+  verify = verify.getBytes();
+
+  try {
+    var cert = c.session.clientCertificate;
+    /*b = forge.pki.rsa.decrypt(
+      msg.signature, cert.publicKey, true, verify.length);
+    if(b !== verify) {*/
+    if(!cert.publicKey.verify(verify, msg.signature, 'NONE')) {
+      throw new Error('CertificateVerify signature does not match.');
+    }
+
+    // digest message now that it has been handled
+    c.session.md5.update(msgBytes);
+    c.session.sha1.update(msgBytes);
+  } catch(ex) {
+    return c.error(c, {
+      message: 'Bad signature in CertificateVerify.',
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.handshake_failure
+      }
+    });
+  }
+
+  // expect ChangeCipherSpec
+  c.expect = CCC;
+
+  // continue
+  c.process();
+};
+
+/**
+ * Called when a client receives a ServerHelloDone record.
+ *
+ * When this message will be sent:
+ *   The server hello done message is sent by the server to indicate the end
+ *   of the server hello and associated messages. After sending this message
+ *   the server will wait for a client response.
+ *
+ * Meaning of this message:
+ *   This message means that the server is done sending messages to support
+ *   the key exchange, and the client can proceed with its phase of the key
+ *   exchange.
+ *
+ *   Upon receipt of the server hello done message the client should verify
+ *   that the server provided a valid certificate if required and check that
+ *   the server hello parameters are acceptable.
+ *
+ * struct {} ServerHelloDone;
+ *
+ * @param c the connection.
+ * @param record the record.
+ * @param length the length of the handshake message.
+ */
+tls.handleServerHelloDone = function(c, record, length) {
+  // len must be 0 bytes
+  if(length > 0) {
+    return c.error(c, {
+      message: 'Invalid ServerHelloDone message. Invalid length.',
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.record_overflow
+      }
+    });
+  }
+
+  if(c.serverCertificate === null) {
+    // no server certificate was provided
+    var error = {
+      message: 'No server certificate provided. Not enough security.',
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.insufficient_security
+      }
+    };
+
+    // call application callback
+    var depth = 0;
+    var ret = c.verify(c, error.alert.description, depth, []);
+    if(ret !== true) {
+      // check for custom alert info
+      if(ret || ret === 0) {
+        // set custom message and alert description
+        if(typeof ret === 'object' && !forge.util.isArray(ret)) {
+          if(ret.message) {
+            error.message = ret.message;
+          }
+          if(ret.alert) {
+            error.alert.description = ret.alert;
+          }
+        } else if(typeof ret === 'number') {
+          // set custom alert description
+          error.alert.description = ret;
+        }
+      }
+
+      // send error
+      return c.error(c, error);
+    }
+  }
+
+  // create client certificate message if requested
+  if(c.session.certificateRequest !== null) {
+    record = tls.createRecord(c, {
+      type: tls.ContentType.handshake,
+      data: tls.createCertificate(c)
+    });
+    tls.queue(c, record);
+  }
+
+  // create client key exchange message
+  record = tls.createRecord(c, {
+     type: tls.ContentType.handshake,
+     data: tls.createClientKeyExchange(c)
+  });
+  tls.queue(c, record);
+
+  // expect no messages until the following callback has been called
+  c.expect = SER;
+
+  // create callback to handle client signature (for client-certs)
+  var callback = function(c, signature) {
+    if(c.session.certificateRequest !== null &&
+      c.session.clientCertificate !== null) {
+      // create certificate verify message
+      tls.queue(c, tls.createRecord(c, {
+        type: tls.ContentType.handshake,
+        data: tls.createCertificateVerify(c, signature)
+      }));
+    }
+
+    // create change cipher spec message
+    tls.queue(c, tls.createRecord(c, {
+      type: tls.ContentType.change_cipher_spec,
+      data: tls.createChangeCipherSpec()
+    }));
+
+    // create pending state
+    c.state.pending = tls.createConnectionState(c);
+
+    // change current write state to pending write state
+    c.state.current.write = c.state.pending.write;
+
+    // create finished message
+    tls.queue(c, tls.createRecord(c, {
+      type: tls.ContentType.handshake,
+      data: tls.createFinished(c)
+    }));
+
+    // expect a server ChangeCipherSpec message next
+    c.expect = SCC;
+
+    // send records
+    tls.flush(c);
+
+    // continue
+    c.process();
+  };
+
+  // if there is no certificate request or no client certificate, do
+  // callback immediately
+  if(c.session.certificateRequest === null ||
+    c.session.clientCertificate === null) {
+    return callback(c, null);
+  }
+
+  // otherwise get the client signature
+  tls.getClientSignature(c, callback);
+};
+
+/**
+ * Called when a ChangeCipherSpec record is received.
+ *
+ * @param c the connection.
+ * @param record the record.
+ */
+tls.handleChangeCipherSpec = function(c, record) {
+  if(record.fragment.getByte() !== 0x01) {
+    return c.error(c, {
+      message: 'Invalid ChangeCipherSpec message received.',
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.illegal_parameter
+      }
+    });
+  }
+
+  // create pending state if:
+  // 1. Resuming session in client mode OR
+  // 2. NOT resuming session in server mode
+  var client = (c.entity === tls.ConnectionEnd.client);
+  if((c.session.resuming && client) || (!c.session.resuming && !client)) {
+    c.state.pending = tls.createConnectionState(c);
+  }
+
+  // change current read state to pending read state
+  c.state.current.read = c.state.pending.read;
+
+  // clear pending state if:
+  // 1. NOT resuming session in client mode OR
+  // 2. resuming a session in server mode
+  if((!c.session.resuming && client) || (c.session.resuming && !client)) {
+    c.state.pending = null;
+  }
+
+  // expect a Finished record next
+  c.expect = client ? SFI : CFI;
+
+  // continue
+  c.process();
+};
+
+/**
+ * Called when a Finished record is received.
+ *
+ * When this message will be sent:
+ *   A finished message is always sent immediately after a change
+ *   cipher spec message to verify that the key exchange and
+ *   authentication processes were successful. It is essential that a
+ *   change cipher spec message be received between the other
+ *   handshake messages and the Finished message.
+ *
+ * Meaning of this message:
+ *   The finished message is the first protected with the just-
+ *   negotiated algorithms, keys, and secrets. Recipients of finished
+ *   messages must verify that the contents are correct.  Once a side
+ *   has sent its Finished message and received and validated the
+ *   Finished message from its peer, it may begin to send and receive
+ *   application data over the connection.
+ *
+ * struct {
+ *   opaque verify_data[verify_data_length];
+ * } Finished;
+ *
+ * verify_data
+ *   PRF(master_secret, finished_label, Hash(handshake_messages))
+ *     [0..verify_data_length-1];
+ *
+ * finished_label
+ *   For Finished messages sent by the client, the string
+ *   "client finished". For Finished messages sent by the server, the
+ *   string "server finished".
+ *
+ * verify_data_length depends on the cipher suite. If it is not specified
+ * by the cipher suite, then it is 12. Versions of TLS < 1.2 always used
+ * 12 bytes.
+ *
+ * @param c the connection.
+ * @param record the record.
+ * @param length the length of the handshake message.
+ */
+tls.handleFinished = function(c, record, length) {
+  // rewind to get full bytes for message so it can be manually
+  // digested below (special case for Finished messages because they
+  // must be digested *after* handling as opposed to all others)
+  var b = record.fragment;
+  b.read -= 4;
+  var msgBytes = b.bytes();
+  b.read += 4;
+
+  // message contains only verify_data
+  var vd = record.fragment.getBytes();
+
+  // ensure verify data is correct
+  b = forge.util.createBuffer();
+  b.putBuffer(c.session.md5.digest());
+  b.putBuffer(c.session.sha1.digest());
+
+  // set label based on entity type
+  var client = (c.entity === tls.ConnectionEnd.client);
+  var label = client ? 'server finished' : 'client finished';
+
+  // TODO: determine prf function and verify length for TLS 1.2
+  var sp = c.session.sp;
+  var vdl = 12;
+  var prf = prf_TLS1;
+  b = prf(sp.master_secret, label, b.getBytes(), vdl);
+  if(b.getBytes() !== vd) {
+    return c.error(c, {
+      message: 'Invalid verify_data in Finished message.',
+      send: true,
+      alert: {
+        level: tls.Alert.Level.fatal,
+        description: tls.Alert.Description.decrypt_error
+      }
+    });
+  }
+
+  // digest finished message now that it has been handled
+  c.session.md5.update(msgBytes);
+  c.session.sha1.update(msgBytes);
+
+  // resuming session as client or NOT resuming session as server
+  if((c.session.resuming && client) || (!c.session.resuming && !client)) {
+    // create change cipher spec message
+    tls.queue(c, tls.createRecord(c, {
+      type: tls.ContentType.change_cipher_spec,
+      data: tls.createChangeCipherSpec()
+    }));
+
+    // change current write state to pending write state, clear pending
+    c.state.current.write = c.state.pending.write;
+    c.state.pending = null;
+
+    // create finished message
+    tls.queue(c, tls.createRecord(c, {
+      type: tls.ContentType.handshake,
+      data: tls.createFinished(c)
+    }));
+  }
+
+  // expect application data next
+  c.expect = client ? SAD : CAD;
+
+  // handshake complete
+  c.handshaking = false;
+  ++c.handshakes;
+
+  // save access to peer certificate
+  c.peerCertificate = client ?
+    c.session.serverCertificate : c.session.clientCertificate;
+
+  // send records
+  tls.flush(c);
+
+  // now connected
+  c.isConnected = true;
+  c.connected(c);
+
+  // continue
+  c.process();
+};
+
+/**
+ * Called when an Alert record is received.
+ *
+ * @param c the connection.
+ * @param record the record.
+ */
+tls.handleAlert = function(c, record) {
+  // read alert
+  var b = record.fragment;
+  var alert = {
+    level: b.getByte(),
+    description: b.getByte()
+  };
+
+  // TODO: consider using a table?
+  // get appropriate message
+  var msg;
+  switch(alert.description) {
+  case tls.Alert.Description.close_notify:
+    msg = 'Connection closed.';
+    break;
+  case tls.Alert.Description.unexpected_message:
+    msg = 'Unexpected message.';
+    break;
+  case tls.Alert.Description.bad_record_mac:
+    msg = 'Bad record MAC.';
+    break;
+  case tls.Alert.Description.decryption_failed:
+    msg = 'Decryption failed.';
+    break;
+  case tls.Alert.Description.record_overflow:
+    msg = 'Record overflow.';
+    break;
+  case tls.Alert.Description.decompression_failure:
+    msg = 'Decompression failed.';
+    break;
+  case tls.Alert.Description.handshake_failure:
+    msg = 'Handshake failure.';
+    break;
+  case tls.Alert.Description.bad_certificate:
+    msg = 'Bad certificate.';
+    break;
+  case tls.Alert.Description.unsupported_certificate:
+    msg = 'Unsupported certificate.';
+    break;
+  case tls.Alert.Description.certificate_revoked:
+    msg = 'Certificate revoked.';
+    break;
+  case tls.Alert.Description.certificate_expired:
+    msg = 'Certificate expired.';
+    break;
+  case tls.Alert.Description.certificate_unknown:
+    msg = 'Certificate unknown.';
+    break;
+  case tls.Alert.Description.illegal_parameter:
+    msg = 'Illegal parameter.';
+    break;
+  case tls.Alert.Description.unknown_ca:
+    msg = 'Unknown certificate authority.';
+    break;
+  case tls.Alert.Description.access_denied:
+    msg = 'Access denied.';
+    break;
+  case tls.Alert.Description.decode_error:
+    msg = 'Decode error.';
+    break;
+  case tls.Alert.Description.decrypt_error:
+    msg = 'Decrypt error.';
+    break;
+  case tls.Alert.Description.export_restriction:
+    msg = 'Export restriction.';
+    break;
+  case tls.Alert.Description.protocol_version:
+    msg = 'Unsupported protocol version.';
+    break;
+  case tls.Alert.Description.insufficient_security:
+    msg = 'Insufficient security.';
+    break;
+  case tls.Alert.Description.internal_error:
+    msg = 'Internal error.';
+    break;
+  case tls.Alert.Description.user_canceled:
+    msg = 'User canceled.';
+    break;
+  case tls.Alert.Description.no_renegotiation:
+    msg = 'Renegotiation not supported.';
+    break;
+  default:
+    msg = 'Unknown error.';
+    break;
+  }
+
+  // close connection on close_notify, not an error
+  if(alert.description === tls.Alert.Description.close_notify) {
+    return c.close();
+  }
+
+  // call error handler
+  c.error(c, {
+    message: msg,
+    send: false,
+    // origin is the opposite end
+    origin: (c.entity === tls.ConnectionEnd.client) ? 'server' : 'client',
+    alert: alert
+  });
+
+  // continue
+  c.process();
+};
+
+/**
+ * Called when a Handshake record is received.
+ *
+ * @param c the connection.
+ * @param record the record.
+ */
+tls.handleHandshake = function(c, record) {
+  // get the handshake type and message length
+  var b = record.fragment;
+  var type = b.getByte();
+  var length = b.getInt24();
+
+  // see if the record fragment doesn't yet contain the full message
+  if(length > b.length()) {
+    // cache the record, clear its fragment, and reset the buffer read
+    // pointer before the type and length were read
+    c.fragmented = record;
+    record.fragment = forge.util.createBuffer();
+    b.read -= 4;
+
+    // continue
+    return c.process();
+  }
+
+  // full message now available, clear cache, reset read pointer to
+  // before type and length
+  c.fragmented = null;
+  b.read -= 4;
+
+  // save the handshake bytes for digestion after handler is found
+  // (include type and length of handshake msg)
+  var bytes = b.bytes(length + 4);
+
+  // restore read pointer
+  b.read += 4;
+
+  // handle expected message
+  if(type in hsTable[c.entity][c.expect]) {
+    // initialize server session
+    if(c.entity === tls.ConnectionEnd.server && !c.open && !c.fail) {
+      c.handshaking = true;
+      c.session = {
+        version: null,
+        extensions: {
+          server_name: {
+            serverNameList: []
+          }
+        },
+        cipherSuite: null,
+        compressionMethod: null,
+        serverCertificate: null,
+        clientCertificate: null,
+        md5: forge.md.md5.create(),
+        sha1: forge.md.sha1.create()
+      };
+    }
+
+    /* Update handshake messages digest. Finished and CertificateVerify
+      messages are not digested here. They can't be digested as part of
+      the verify_data that they contain. These messages are manually
+      digested in their handlers. HelloRequest messages are simply never
+      included in the handshake message digest according to spec. */
+    if(type !== tls.HandshakeType.hello_request &&
+      type !== tls.HandshakeType.certificate_verify &&
+      type !== tls.HandshakeType.finished) {
+      c.session.md5.update(bytes);
+      c.session.sha1.update(bytes);
+    }
+
+    // handle specific handshake type record
+    hsTable[c.entity][c.expect][type](c, record, length);
+  } else {
+    // unexpected record
+    tls.handleUnexpected(c, record);
+  }
+};
+
+/**
+ * Called when an ApplicationData record is received.
+ *
+ * @param c the connection.
+ * @param record the record.
+ */
+tls.handleApplicationData = function(c, record) {
+  // buffer data, notify that its ready
+  c.data.putBuffer(record.fragment);
+  c.dataReady(c);
+
+  // continue
+  c.process();
+};
+
+/**
+ * Called when a Heartbeat record is received.
+ *
+ * @param c the connection.
+ * @param record the record.
+ */
+tls.handleHeartbeat = function(c, record) {
+  // get the heartbeat type and payload
+  var b = record.fragment;
+  var type = b.getByte();
+  var length = b.getInt16();
+  var payload = b.getBytes(length);
+
+  if(type === tls.HeartbeatMessageType.heartbeat_request) {
+    // discard request during handshake or if length is too large
+    if(c.handshaking || length > payload.length) {
+      // continue
+      return c.process();
+    }
+    // retransmit payload
+    tls.queue(c, tls.createRecord(c, {
+      type: tls.ContentType.heartbeat,
+      data: tls.createHeartbeat(
+        tls.HeartbeatMessageType.heartbeat_response, payload)
+    }));
+    tls.flush(c);
+  } else if(type === tls.HeartbeatMessageType.heartbeat_response) {
+    // check payload against expected payload, discard heartbeat if no match
+    if(payload !== c.expectedHeartbeatPayload) {
+      // continue
+      return c.process();
+    }
+
+    // notify that a valid heartbeat was received
+    if(c.heartbeatReceived) {
+      c.heartbeatReceived(c, forge.util.createBuffer(payload));
+    }
+  }
+
+  // continue
+  c.process();
+};
+
+/**
+ * The transistional state tables for receiving TLS records. It maps the
+ * current TLS engine state and a received record to a function to handle the
+ * record and update the state.
+ *
+ * For instance, if the current state is SHE, then the TLS engine is expecting
+ * a ServerHello record. Once a record is received, the handler function is
+ * looked up using the state SHE and the record's content type.
+ *
+ * The resulting function will either be an error handler or a record handler.
+ * The function will take whatever action is appropriate and update the state
+ * for the next record.
+ *
+ * The states are all based on possible server record types. Note that the
+ * client will never specifically expect to receive a HelloRequest or an alert
+ * from the server so there is no state that reflects this. These messages may
+ * occur at any time.
+ *
+ * There are two tables for mapping states because there is a second tier of
+ * types for handshake messages. Once a record with a content type of handshake
+ * is received, the handshake record handler will look up the handshake type in
+ * the secondary map to get its appropriate handler.
+ *
+ * Valid message orders are as follows:
+ *
+ * =======================FULL HANDSHAKE======================
+ * Client                                               Server
+ *
+ * ClientHello                  -------->
+ *                                                 ServerHello
+ *                                                Certificate*
+ *                                          ServerKeyExchange*
+ *                                         CertificateRequest*
+ *                              <--------      ServerHelloDone
+ * Certificate*
+ * ClientKeyExchange
+ * CertificateVerify*
+ * [ChangeCipherSpec]
+ * Finished                     -------->
+ *                                          [ChangeCipherSpec]
+ *                              <--------             Finished
+ * Application Data             <------->     Application Data
+ *
+ * =====================SESSION RESUMPTION=====================
+ * Client                                                Server
+ *
+ * ClientHello                   -------->
+ *                                                  ServerHello
+ *                                           [ChangeCipherSpec]
+ *                               <--------             Finished
+ * [ChangeCipherSpec]
+ * Finished                      -------->
+ * Application Data              <------->     Application Data
+ */
+// client expect states (indicate which records are expected to be received)
+var SHE = 0; // rcv server hello
+var SCE = 1; // rcv server certificate
+var SKE = 2; // rcv server key exchange
+var SCR = 3; // rcv certificate request
+var SHD = 4; // rcv server hello done
+var SCC = 5; // rcv change cipher spec
+var SFI = 6; // rcv finished
+var SAD = 7; // rcv application data
+var SER = 8; // not expecting any messages at this point
+
+// server expect states
+var CHE = 0; // rcv client hello
+var CCE = 1; // rcv client certificate
+var CKE = 2; // rcv client key exchange
+var CCV = 3; // rcv certificate verify
+var CCC = 4; // rcv change cipher spec
+var CFI = 5; // rcv finished
+var CAD = 6; // rcv application data
+var CER = 7; // not expecting any messages at this point
+
+// map client current expect state and content type to function
+var __ = tls.handleUnexpected;
+var R0 = tls.handleChangeCipherSpec;
+var R1 = tls.handleAlert;
+var R2 = tls.handleHandshake;
+var R3 = tls.handleApplicationData;
+var R4 = tls.handleHeartbeat;
+var ctTable = [];
+ctTable[tls.ConnectionEnd.client] = [
+//      CC,AL,HS,AD,HB
+/*SHE*/[__,R1,R2,__,R4],
+/*SCE*/[__,R1,R2,__,R4],
+/*SKE*/[__,R1,R2,__,R4],
+/*SCR*/[__,R1,R2,__,R4],
+/*SHD*/[__,R1,R2,__,R4],
+/*SCC*/[R0,R1,__,__,R4],
+/*SFI*/[__,R1,R2,__,R4],
+/*SAD*/[__,R1,R2,R3,R4],
+/*SER*/[__,R1,R2,__,R4]
+];
+
+// map server current expect state and content type to function
+ctTable[tls.ConnectionEnd.server] = [
+//      CC,AL,HS,AD
+/*CHE*/[__,R1,R2,__,R4],
+/*CCE*/[__,R1,R2,__,R4],
+/*CKE*/[__,R1,R2,__,R4],
+/*CCV*/[__,R1,R2,__,R4],
+/*CCC*/[R0,R1,__,__,R4],
+/*CFI*/[__,R1,R2,__,R4],
+/*CAD*/[__,R1,R2,R3,R4],
+/*CER*/[__,R1,R2,__,R4]
+];
+
+// map client current expect state and handshake type to function
+var H0 = tls.handleHelloRequest;
+var H1 = tls.handleServerHello;
+var H2 = tls.handleCertificate;
+var H3 = tls.handleServerKeyExchange;
+var H4 = tls.handleCertificateRequest;
+var H5 = tls.handleServerHelloDone;
+var H6 = tls.handleFinished;
+var hsTable = [];
+hsTable[tls.ConnectionEnd.client] = [
+//      HR,01,SH,03,04,05,06,07,08,09,10,SC,SK,CR,HD,15,CK,17,18,19,FI
+/*SHE*/[__,__,H1,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__],
+/*SCE*/[H0,__,__,__,__,__,__,__,__,__,__,H2,H3,H4,H5,__,__,__,__,__,__],
+/*SKE*/[H0,__,__,__,__,__,__,__,__,__,__,__,H3,H4,H5,__,__,__,__,__,__],
+/*SCR*/[H0,__,__,__,__,__,__,__,__,__,__,__,__,H4,H5,__,__,__,__,__,__],
+/*SHD*/[H0,__,__,__,__,__,__,__,__,__,__,__,__,__,H5,__,__,__,__,__,__],
+/*SCC*/[H0,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__],
+/*SFI*/[H0,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,H6],
+/*SAD*/[H0,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__],
+/*SER*/[H0,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__]
+];
+
+// map server current expect state and handshake type to function
+// Note: CAD[CH] does not map to FB because renegotation is prohibited
+var H7 = tls.handleClientHello;
+var H8 = tls.handleClientKeyExchange;
+var H9 = tls.handleCertificateVerify;
+hsTable[tls.ConnectionEnd.server] = [
+//      01,CH,02,03,04,05,06,07,08,09,10,CC,12,13,14,CV,CK,17,18,19,FI
+/*CHE*/[__,H7,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__],
+/*CCE*/[__,__,__,__,__,__,__,__,__,__,__,H2,__,__,__,__,__,__,__,__,__],
+/*CKE*/[__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,H8,__,__,__,__],
+/*CCV*/[__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,H9,__,__,__,__,__],
+/*CCC*/[__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__],
+/*CFI*/[__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,H6],
+/*CAD*/[__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__],
+/*CER*/[__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__]
+];
+
+/**
+ * Generates the master_secret and keys using the given security parameters.
+ *
+ * The security parameters for a TLS connection state are defined as such:
+ *
+ * struct {
+ *   ConnectionEnd          entity;
+ *   PRFAlgorithm           prf_algorithm;
+ *   BulkCipherAlgorithm    bulk_cipher_algorithm;
+ *   CipherType             cipher_type;
+ *   uint8                  enc_key_length;
+ *   uint8                  block_length;
+ *   uint8                  fixed_iv_length;
+ *   uint8                  record_iv_length;
+ *   MACAlgorithm           mac_algorithm;
+ *   uint8                  mac_length;
+ *   uint8                  mac_key_length;
+ *   CompressionMethod      compression_algorithm;
+ *   opaque                 master_secret[48];
+ *   opaque                 client_random[32];
+ *   opaque                 server_random[32];
+ * } SecurityParameters;
+ *
+ * Note that this definition is from TLS 1.2. In TLS 1.0 some of these
+ * parameters are ignored because, for instance, the PRFAlgorithm is a
+ * builtin-fixed algorithm combining iterations of MD5 and SHA-1 in TLS 1.0.
+ *
+ * The Record Protocol requires an algorithm to generate keys required by the
+ * current connection state.
+ *
+ * The master secret is expanded into a sequence of secure bytes, which is then
+ * split to a client write MAC key, a server write MAC key, a client write
+ * encryption key, and a server write encryption key. In TLS 1.0 a client write
+ * IV and server write IV are also generated. Each of these is generated from
+ * the byte sequence in that order. Unused values are empty. In TLS 1.2, some
+ * AEAD ciphers may additionally require a client write IV and a server write
+ * IV (see Section 6.2.3.3).
+ *
+ * When keys, MAC keys, and IVs are generated, the master secret is used as an
+ * entropy source.
+ *
+ * To generate the key material, compute:
+ *
+ * master_secret = PRF(pre_master_secret, "master secret",
+ *                     ClientHello.random + ServerHello.random)
+ *
+ * key_block = PRF(SecurityParameters.master_secret,
+ *                 "key expansion",
+ *                 SecurityParameters.server_random +
+ *                 SecurityParameters.client_random);
+ *
+ * until enough output has been generated. Then, the key_block is
+ * partitioned as follows:
+ *
+ * client_write_MAC_key[SecurityParameters.mac_key_length]
+ * server_write_MAC_key[SecurityParameters.mac_key_length]
+ * client_write_key[SecurityParameters.enc_key_length]
+ * server_write_key[SecurityParameters.enc_key_length]
+ * client_write_IV[SecurityParameters.fixed_iv_length]
+ * server_write_IV[SecurityParameters.fixed_iv_length]
+ *
+ * In TLS 1.2, the client_write_IV and server_write_IV are only generated for
+ * implicit nonce techniques as described in Section 3.2.1 of [AEAD]. This
+ * implementation uses TLS 1.0 so IVs are generated.
+ *
+ * Implementation note: The currently defined cipher suite which requires the
+ * most material is AES_256_CBC_SHA256. It requires 2 x 32 byte keys and 2 x 32
+ * byte MAC keys, for a total 128 bytes of key material. In TLS 1.0 it also
+ * requires 2 x 16 byte IVs, so it actually takes 160 bytes of key material.
+ *
+ * @param c the connection.
+ * @param sp the security parameters to use.
+ *
+ * @return the security keys.
+ */
+tls.generateKeys = function(c, sp) {
+  // TLS_RSA_WITH_AES_128_CBC_SHA (required to be compliant with TLS 1.2) &
+  // TLS_RSA_WITH_AES_256_CBC_SHA are the only cipher suites implemented
+  // at present
+
+  // TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA is required to be compliant with
+  // TLS 1.0 but we don't care right now because AES is better and we have
+  // an implementation for it
+
+  // TODO: TLS 1.2 implementation
+  /*
+  // determine the PRF
+  var prf;
+  switch(sp.prf_algorithm) {
+  case tls.PRFAlgorithm.tls_prf_sha256:
+    prf = prf_sha256;
+    break;
+  default:
+    // should never happen
+    throw new Error('Invalid PRF');
+  }
+  */
+
+  // TLS 1.0/1.1 implementation
+  var prf = prf_TLS1;
+
+  // concatenate server and client random
+  var random = sp.client_random + sp.server_random;
+
+  // only create master secret if session is new
+  if(!c.session.resuming) {
+    // create master secret, clean up pre-master secret
+    sp.master_secret = prf(
+      sp.pre_master_secret, 'master secret', random, 48).bytes();
+    sp.pre_master_secret = null;
+  }
+
+  // generate the amount of key material needed
+  random = sp.server_random + sp.client_random;
+  var length = 2 * sp.mac_key_length + 2 * sp.enc_key_length;
+
+  // include IV for TLS/1.0
+  var tls10 = (c.version.major === tls.Versions.TLS_1_0.major &&
+    c.version.minor === tls.Versions.TLS_1_0.minor);
+  if(tls10) {
+    length += 2 * sp.fixed_iv_length;
+  }
+  var km = prf(sp.master_secret, 'key expansion', random, length);
+
+  // split the key material into the MAC and encryption keys
+  var rval = {
+    client_write_MAC_key: km.getBytes(sp.mac_key_length),
+    server_write_MAC_key: km.getBytes(sp.mac_key_length),
+    client_write_key: km.getBytes(sp.enc_key_length),
+    server_write_key: km.getBytes(sp.enc_key_length)
+  };
+
+  // include TLS 1.0 IVs
+  if(tls10) {
+    rval.client_write_IV = km.getBytes(sp.fixed_iv_length);
+    rval.server_write_IV = km.getBytes(sp.fixed_iv_length);
+  }
+
+  return rval;
+};
+
+/**
+ * Creates a new initialized TLS connection state. A connection state has
+ * a read mode and a write mode.
+ *
+ * compression state:
+ *   The current state of the compression algorithm.
+ *
+ * cipher state:
+ *   The current state of the encryption algorithm. This will consist of the
+ *   scheduled key for that connection. For stream ciphers, this will also
+ *   contain whatever state information is necessary to allow the stream to
+ *   continue to encrypt or decrypt data.
+ *
+ * MAC key:
+ *   The MAC key for the connection.
+ *
+ * sequence number:
+ *   Each connection state contains a sequence number, which is maintained
+ *   separately for read and write states. The sequence number MUST be set to
+ *   zero whenever a connection state is made the active state. Sequence
+ *   numbers are of type uint64 and may not exceed 2^64-1. Sequence numbers do
+ *   not wrap. If a TLS implementation would need to wrap a sequence number,
+ *   it must renegotiate instead. A sequence number is incremented after each
+ *   record: specifically, the first record transmitted under a particular
+ *   connection state MUST use sequence number 0.
+ *
+ * @param c the connection.
+ *
+ * @return the new initialized TLS connection state.
+ */
+tls.createConnectionState = function(c) {
+  var client = (c.entity === tls.ConnectionEnd.client);
+
+  var createMode = function() {
+    var mode = {
+      // two 32-bit numbers, first is most significant
+      sequenceNumber: [0, 0],
+      macKey: null,
+      macLength: 0,
+      macFunction: null,
+      cipherState: null,
+      cipherFunction: function(record) {return true;},
+      compressionState: null,
+      compressFunction: function(record) {return true;},
+      updateSequenceNumber: function() {
+        if(mode.sequenceNumber[1] === 0xFFFFFFFF) {
+          mode.sequenceNumber[1] = 0;
+          ++mode.sequenceNumber[0];
+        } else {
+          ++mode.sequenceNumber[1];
+        }
+      }
+    };
+    return mode;
+  };
+  var state = {
+    read: createMode(),
+    write: createMode()
+  };
+
+  // update function in read mode will decrypt then decompress a record
+  state.read.update = function(c, record) {
+    if(!state.read.cipherFunction(record, state.read)) {
+      c.error(c, {
+        message: 'Could not decrypt record or bad MAC.',
+        send: true,
+        alert: {
+          level: tls.Alert.Level.fatal,
+          // doesn't matter if decryption failed or MAC was
+          // invalid, return the same error so as not to reveal
+          // which one occurred
+          description: tls.Alert.Description.bad_record_mac
+        }
+      });
+    } else if(!state.read.compressFunction(c, record, state.read)) {
+      c.error(c, {
+        message: 'Could not decompress record.',
+        send: true,
+        alert: {
+          level: tls.Alert.Level.fatal,
+          description: tls.Alert.Description.decompression_failure
+        }
+      });
+    }
+    return !c.fail;
+  };
+
+  // update function in write mode will compress then encrypt a record
+  state.write.update = function(c, record) {
+    if(!state.write.compressFunction(c, record, state.write)) {
+      // error, but do not send alert since it would require
+      // compression as well
+      c.error(c, {
+        message: 'Could not compress record.',
+        send: false,
+        alert: {
+          level: tls.Alert.Level.fatal,
+          description: tls.Alert.Description.internal_error
+        }
+      });
+    } else if(!state.write.cipherFunction(record, state.write)) {
+      // error, but do not send alert since it would require
+      // encryption as well
+      c.error(c, {
+        message: 'Could not encrypt record.',
+        send: false,
+        alert: {
+          level: tls.Alert.Level.fatal,
+          description: tls.Alert.Description.internal_error
+        }
+      });
+    }
+    return !c.fail;
+  };
+
+  // handle security parameters
+  if(c.session) {
+    var sp = c.session.sp;
+    c.session.cipherSuite.initSecurityParameters(sp);
+
+    // generate keys
+    sp.keys = tls.generateKeys(c, sp);
+    state.read.macKey = client ?
+      sp.keys.server_write_MAC_key : sp.keys.client_write_MAC_key;
+    state.write.macKey = client ?
+      sp.keys.client_write_MAC_key : sp.keys.server_write_MAC_key;
+
+    // cipher suite setup
+    c.session.cipherSuite.initConnectionState(state, c, sp);
+
+    // compression setup
+    switch(sp.compression_algorithm) {
+    case tls.CompressionMethod.none:
+      break;
+    case tls.CompressionMethod.deflate:
+      state.read.compressFunction = inflate;
+      state.write.compressFunction = deflate;
+      break;
+    default:
+      throw new Error('Unsupported compression algorithm.');
+    }
+  }
+
+  return state;
+};
+
+/**
+ * Creates a Random structure.
+ *
+ * struct {
+ *   uint32 gmt_unix_time;
+ *   opaque random_bytes[28];
+ * } Random;
+ *
+ * gmt_unix_time:
+ *   The current time and date in standard UNIX 32-bit format (seconds since
+ *   the midnight starting Jan 1, 1970, UTC, ignoring leap seconds) according
+ *   to the sender's internal clock. Clocks are not required to be set
+ *   correctly by the basic TLS protocol; higher-level or application
+ *   protocols may define additional requirements. Note that, for historical
+ *   reasons, the data element is named using GMT, the predecessor of the
+ *   current worldwide time base, UTC.
+ * random_bytes:
+ *   28 bytes generated by a secure random number generator.
+ *
+ * @return the Random structure as a byte array.
+ */
+tls.createRandom = function() {
+  // get UTC milliseconds
+  var d = new Date();
+  var utc = +d + d.getTimezoneOffset() * 60000;
+  var rval = forge.util.createBuffer();
+  rval.putInt32(utc);
+  rval.putBytes(forge.random.getBytes(28));
+  return rval;
+};
+
+/**
+ * Creates a TLS record with the given type and data.
+ *
+ * @param c the connection.
+ * @param options:
+ *   type: the record type.
+ *   data: the plain text data in a byte buffer.
+ *
+ * @return the created record.
+ */
+tls.createRecord = function(c, options) {
+  if(!options.data) {
+    return null;
+  }
+  var record = {
+    type: options.type,
+    version: {
+      major: c.version.major,
+      minor: c.version.minor
+    },
+    length: options.data.length(),
+    fragment: options.data
+  };
+  return record;
+};
+
+/**
+ * Creates a TLS alert record.
+ *
+ * @param c the connection.
+ * @param alert:
+ *   level: the TLS alert level.
+ *   description: the TLS alert description.
+ *
+ * @return the created alert record.
+ */
+tls.createAlert = function(c, alert) {
+  var b = forge.util.createBuffer();
+  b.putByte(alert.level);
+  b.putByte(alert.description);
+  return tls.createRecord(c, {
+    type: tls.ContentType.alert,
+    data: b
+  });
+};
+
+/* The structure of a TLS handshake message.
+ *
+ * struct {
+ *    HandshakeType msg_type;    // handshake type
+ *    uint24 length;             // bytes in message
+ *    select(HandshakeType) {
+ *       case hello_request:       HelloRequest;
+ *       case client_hello:        ClientHello;
+ *       case server_hello:        ServerHello;
+ *       case certificate:         Certificate;
+ *       case server_key_exchange: ServerKeyExchange;
+ *       case certificate_request: CertificateRequest;
+ *       case server_hello_done:   ServerHelloDone;
+ *       case certificate_verify:  CertificateVerify;
+ *       case client_key_exchange: ClientKeyExchange;
+ *       case finished:            Finished;
+ *    } body;
+ * } Handshake;
+ */
+
+/**
+ * Creates a ClientHello message.
+ *
+ * opaque SessionID<0..32>;
+ * enum { null(0), deflate(1), (255) } CompressionMethod;
+ * uint8 CipherSuite[2];
+ *
+ * struct {
+ *   ProtocolVersion client_version;
+ *   Random random;
+ *   SessionID session_id;
+ *   CipherSuite cipher_suites<2..2^16-2>;
+ *   CompressionMethod compression_methods<1..2^8-1>;
+ *   select(extensions_present) {
+ *     case false:
+ *       struct {};
+ *     case true:
+ *       Extension extensions<0..2^16-1>;
+ *   };
+ * } ClientHello;
+ *
+ * The extension format for extended client hellos and server hellos is:
+ *
+ * struct {
+ *   ExtensionType extension_type;
+ *   opaque extension_data<0..2^16-1>;
+ * } Extension;
+ *
+ * Here:
+ *
+ * - "extension_type" identifies the particular extension type.
+ * - "extension_data" contains information specific to the particular
+ * extension type.
+ *
+ * The extension types defined in this document are:
+ *
+ * enum {
+ *   server_name(0), max_fragment_length(1),
+ *   client_certificate_url(2), trusted_ca_keys(3),
+ *   truncated_hmac(4), status_request(5), (65535)
+ * } ExtensionType;
+ *
+ * @param c the connection.
+ *
+ * @return the ClientHello byte buffer.
+ */
+tls.createClientHello = function(c) {
+  // save hello version
+  c.session.clientHelloVersion = {
+    major: c.version.major,
+    minor: c.version.minor
+  };
+
+  // create supported cipher suites
+  var cipherSuites = forge.util.createBuffer();
+  for(var i = 0; i < c.cipherSuites.length; ++i) {
+    var cs = c.cipherSuites[i];
+    cipherSuites.putByte(cs.id[0]);
+    cipherSuites.putByte(cs.id[1]);
+  }
+  var cSuites = cipherSuites.length();
+
+  // create supported compression methods, null always supported, but
+  // also support deflate if connection has inflate and deflate methods
+  var compressionMethods = forge.util.createBuffer();
+  compressionMethods.putByte(tls.CompressionMethod.none);
+  // FIXME: deflate support disabled until issues with raw deflate data
+  // without zlib headers are resolved
+  /*
+  if(c.inflate !== null && c.deflate !== null) {
+    compressionMethods.putByte(tls.CompressionMethod.deflate);
+  }
+  */
+  var cMethods = compressionMethods.length();
+
+  // create TLS SNI (server name indication) extension if virtual host
+  // has been specified, see RFC 3546
+  var extensions = forge.util.createBuffer();
+  if(c.virtualHost) {
+    // create extension struct
+    var ext = forge.util.createBuffer();
+    ext.putByte(0x00); // type server_name (ExtensionType is 2 bytes)
+    ext.putByte(0x00);
+
+    /* In order to provide the server name, clients MAY include an
+     * extension of type "server_name" in the (extended) client hello.
+     * The "extension_data" field of this extension SHALL contain
+     * "ServerNameList" where:
+     *
+     * struct {
+     *   NameType name_type;
+     *   select(name_type) {
+     *     case host_name: HostName;
+     *   } name;
+     * } ServerName;
+     *
+     * enum {
+     *   host_name(0), (255)
+     * } NameType;
+     *
+     * opaque HostName<1..2^16-1>;
+     *
+     * struct {
+     *   ServerName server_name_list<1..2^16-1>
+     * } ServerNameList;
+     */
+    var serverName = forge.util.createBuffer();
+    serverName.putByte(0x00); // type host_name
+    writeVector(serverName, 2, forge.util.createBuffer(c.virtualHost));
+
+    // ServerNameList is in extension_data
+    var snList = forge.util.createBuffer();
+    writeVector(snList, 2, serverName);
+    writeVector(ext, 2, snList);
+    extensions.putBuffer(ext);
+  }
+  var extLength = extensions.length();
+  if(extLength > 0) {
+    // add extension vector length
+    extLength += 2;
+  }
+
+  // determine length of the handshake message
+  // cipher suites and compression methods size will need to be
+  // updated if more get added to the list
+  var sessionId = c.session.id;
+  var length =
+    sessionId.length + 1 + // session ID vector
+    2 +                    // version (major + minor)
+    4 + 28 +               // random time and random bytes
+    2 + cSuites +          // cipher suites vector
+    1 + cMethods +         // compression methods vector
+    extLength;             // extensions vector
+
+  // build record fragment
+  var rval = forge.util.createBuffer();
+  rval.putByte(tls.HandshakeType.client_hello);
+  rval.putInt24(length);                     // handshake length
+  rval.putByte(c.version.major);             // major version
+  rval.putByte(c.version.minor);             // minor version
+  rval.putBytes(c.session.sp.client_random); // random time + bytes
+  writeVector(rval, 1, forge.util.createBuffer(sessionId));
+  writeVector(rval, 2, cipherSuites);
+  writeVector(rval, 1, compressionMethods);
+  if(extLength > 0) {
+    writeVector(rval, 2, extensions);
+  }
+  return rval;
+};
+
+/**
+ * Creates a ServerHello message.
+ *
+ * @param c the connection.
+ *
+ * @return the ServerHello byte buffer.
+ */
+tls.createServerHello = function(c) {
+  // determine length of the handshake message
+  var sessionId = c.session.id;
+  var length =
+    sessionId.length + 1 + // session ID vector
+    2 +                    // version (major + minor)
+    4 + 28 +               // random time and random bytes
+    2 +                    // chosen cipher suite
+    1;                     // chosen compression method
+
+  // build record fragment
+  var rval = forge.util.createBuffer();
+  rval.putByte(tls.HandshakeType.server_hello);
+  rval.putInt24(length);                     // handshake length
+  rval.putByte(c.version.major);             // major version
+  rval.putByte(c.version.minor);             // minor version
+  rval.putBytes(c.session.sp.server_random); // random time + bytes
+  writeVector(rval, 1, forge.util.createBuffer(sessionId));
+  rval.putByte(c.session.cipherSuite.id[0]);
+  rval.putByte(c.session.cipherSuite.id[1]);
+  rval.putByte(c.session.compressionMethod);
+  return rval;
+};
+
+/**
+ * Creates a Certificate message.
+ *
+ * When this message will be sent:
+ *   This is the first message the client can send after receiving a server
+ *   hello done message and the first message the server can send after
+ *   sending a ServerHello. This client message is only sent if the server
+ *   requests a certificate. If no suitable certificate is available, the
+ *   client should send a certificate message containing no certificates. If
+ *   client authentication is required by the server for the handshake to
+ *   continue, it may respond with a fatal handshake failure alert.
+ *
+ * opaque ASN.1Cert<1..2^24-1>;
+ *
+ * struct {
+ *   ASN.1Cert certificate_list<0..2^24-1>;
+ * } Certificate;
+ *
+ * @param c the connection.
+ *
+ * @return the Certificate byte buffer.
+ */
+tls.createCertificate = function(c) {
+  // TODO: check certificate request to ensure types are supported
+
+  // get a certificate (a certificate as a PEM string)
+  var client = (c.entity === tls.ConnectionEnd.client);
+  var cert = null;
+  if(c.getCertificate) {
+    var hint;
+    if(client) {
+      hint = c.session.certificateRequest;
+    } else {
+      hint = c.session.extensions.server_name.serverNameList;
+    }
+    cert = c.getCertificate(c, hint);
+  }
+
+  // buffer to hold certificate list
+  var certList = forge.util.createBuffer();
+  if(cert !== null) {
+    try {
+      // normalize cert to a chain of certificates
+      if(!forge.util.isArray(cert)) {
+        cert = [cert];
+      }
+      var asn1 = null;
+      for(var i = 0; i < cert.length; ++i) {
+        var msg = forge.pem.decode(cert[i])[0];
+        if(msg.type !== 'CERTIFICATE' &&
+          msg.type !== 'X509 CERTIFICATE' &&
+          msg.type !== 'TRUSTED CERTIFICATE') {
+          var error = new Error('Could not convert certificate from PEM; PEM ' +
+            'header type is not "CERTIFICATE", "X509 CERTIFICATE", or ' +
+            '"TRUSTED CERTIFICATE".');
+          error.headerType = msg.type;
+          throw error;
+        }
+        if(msg.procType && msg.procType.type === 'ENCRYPTED') {
+          throw new Error('Could not convert certificate from PEM; PEM is encrypted.');
+        }
+
+        var der = forge.util.createBuffer(msg.body);
+        if(asn1 === null) {
+          asn1 = forge.asn1.fromDer(der.bytes(), false);
+        }
+
+        // certificate entry is itself a vector with 3 length bytes
+        var certBuffer = forge.util.createBuffer();
+        writeVector(certBuffer, 3, der);
+
+        // add cert vector to cert list vector
+        certList.putBuffer(certBuffer);
+      }
+
+      // save certificate
+      cert = forge.pki.certificateFromAsn1(asn1);
+      if(client) {
+        c.session.clientCertificate = cert;
+      } else {
+        c.session.serverCertificate = cert;
+      }
+    } catch(ex) {
+      return c.error(c, {
+        message: 'Could not send certificate list.',
+        cause: ex,
+        send: true,
+        alert: {
+          level: tls.Alert.Level.fatal,
+          description: tls.Alert.Description.bad_certificate
+        }
+      });
+    }
+  }
+
+  // determine length of the handshake message
+  var length = 3 + certList.length(); // cert list vector
+
+  // build record fragment
+  var rval = forge.util.createBuffer();
+  rval.putByte(tls.HandshakeType.certificate);
+  rval.putInt24(length);
+  writeVector(rval, 3, certList);
+  return rval;
+};
+
+/**
+ * Creates a ClientKeyExchange message.
+ *
+ * When this message will be sent:
+ *   This message is always sent by the client. It will immediately follow the
+ *   client certificate message, if it is sent. Otherwise it will be the first
+ *   message sent by the client after it receives the server hello done
+ *   message.
+ *
+ * Meaning of this message:
+ *   With this message, the premaster secret is set, either though direct
+ *   transmission of the RSA-encrypted secret, or by the transmission of
+ *   Diffie-Hellman parameters which will allow each side to agree upon the
+ *   same premaster secret. When the key exchange method is DH_RSA or DH_DSS,
+ *   client certification has been requested, and the client was able to
+ *   respond with a certificate which contained a Diffie-Hellman public key
+ *   whose parameters (group and generator) matched those specified by the
+ *   server in its certificate, this message will not contain any data.
+ *
+ * Meaning of this message:
+ *   If RSA is being used for key agreement and authentication, the client
+ *   generates a 48-byte premaster secret, encrypts it using the public key
+ *   from the server's certificate or the temporary RSA key provided in a
+ *   server key exchange message, and sends the result in an encrypted
+ *   premaster secret message. This structure is a variant of the client
+ *   key exchange message, not a message in itself.
+ *
+ * struct {
+ *   select(KeyExchangeAlgorithm) {
+ *     case rsa: EncryptedPreMasterSecret;
+ *     case diffie_hellman: ClientDiffieHellmanPublic;
+ *   } exchange_keys;
+ * } ClientKeyExchange;
+ *
+ * struct {
+ *   ProtocolVersion client_version;
+ *   opaque random[46];
+ * } PreMasterSecret;
+ *
+ * struct {
+ *   public-key-encrypted PreMasterSecret pre_master_secret;
+ * } EncryptedPreMasterSecret;
+ *
+ * A public-key-encrypted element is encoded as a vector <0..2^16-1>.
+ *
+ * @param c the connection.
+ *
+ * @return the ClientKeyExchange byte buffer.
+ */
+tls.createClientKeyExchange = function(c) {
+  // create buffer to encrypt
+  var b = forge.util.createBuffer();
+
+  // add highest client-supported protocol to help server avoid version
+  // rollback attacks
+  b.putByte(c.session.clientHelloVersion.major);
+  b.putByte(c.session.clientHelloVersion.minor);
+
+  // generate and add 46 random bytes
+  b.putBytes(forge.random.getBytes(46));
+
+  // save pre-master secret
+  var sp = c.session.sp;
+  sp.pre_master_secret = b.getBytes();
+
+  // RSA-encrypt the pre-master secret
+  var key = c.session.serverCertificate.publicKey;
+  b = key.encrypt(sp.pre_master_secret);
+
+  /* Note: The encrypted pre-master secret will be stored in a
+    public-key-encrypted opaque vector that has the length prefixed using
+    2 bytes, so include those 2 bytes in the handshake message length. This
+    is done as a minor optimization instead of calling writeVector(). */
+
+  // determine length of the handshake message
+  var length = b.length + 2;
+
+  // build record fragment
+  var rval = forge.util.createBuffer();
+  rval.putByte(tls.HandshakeType.client_key_exchange);
+  rval.putInt24(length);
+  // add vector length bytes
+  rval.putInt16(b.length);
+  rval.putBytes(b);
+  return rval;
+};
+
+/**
+ * Creates a ServerKeyExchange message.
+ *
+ * @param c the connection.
+ *
+ * @return the ServerKeyExchange byte buffer.
+ */
+tls.createServerKeyExchange = function(c) {
+  // this implementation only supports RSA, no Diffie-Hellman support,
+  // so this record is empty
+
+  // determine length of the handshake message
+  var length = 0;
+
+  // build record fragment
+  var rval = forge.util.createBuffer();
+  if(length > 0) {
+    rval.putByte(tls.HandshakeType.server_key_exchange);
+    rval.putInt24(length);
+  }
+  return rval;
+};
+
+/**
+ * Gets the signed data used to verify a client-side certificate. See
+ * tls.createCertificateVerify() for details.
+ *
+ * @param c the connection.
+ * @param callback the callback to call once the signed data is ready.
+ */
+tls.getClientSignature = function(c, callback) {
+  // generate data to RSA encrypt
+  var b = forge.util.createBuffer();
+  b.putBuffer(c.session.md5.digest());
+  b.putBuffer(c.session.sha1.digest());
+  b = b.getBytes();
+
+  // create default signing function as necessary
+  c.getSignature = c.getSignature || function(c, b, callback) {
+    // do rsa encryption, call callback
+    var privateKey = null;
+    if(c.getPrivateKey) {
+      try {
+        privateKey = c.getPrivateKey(c, c.session.clientCertificate);
+        privateKey = forge.pki.privateKeyFromPem(privateKey);
+      } catch(ex) {
+        c.error(c, {
+          message: 'Could not get private key.',
+          cause: ex,
+          send: true,
+          alert: {
+            level: tls.Alert.Level.fatal,
+            description: tls.Alert.Description.internal_error
+          }
+        });
+      }
+    }
+    if(privateKey === null) {
+      c.error(c, {
+        message: 'No private key set.',
+        send: true,
+        alert: {
+          level: tls.Alert.Level.fatal,
+          description: tls.Alert.Description.internal_error
+        }
+      });
+    } else {
+      b = privateKey.sign(b, null);
+    }
+    callback(c, b);
+  };
+
+  // get client signature
+  c.getSignature(c, b, callback);
+};
+
+/**
+ * Creates a CertificateVerify message.
+ *
+ * Meaning of this message:
+ *   This structure conveys the client's Diffie-Hellman public value
+ *   (Yc) if it was not already included in the client's certificate.
+ *   The encoding used for Yc is determined by the enumerated
+ *   PublicValueEncoding. This structure is a variant of the client
+ *   key exchange message, not a message in itself.
+ *
+ * When this message will be sent:
+ *   This message is used to provide explicit verification of a client
+ *   certificate. This message is only sent following a client
+ *   certificate that has signing capability (i.e. all certificates
+ *   except those containing fixed Diffie-Hellman parameters). When
+ *   sent, it will immediately follow the client key exchange message.
+ *
+ * struct {
+ *   Signature signature;
+ * } CertificateVerify;
+ *
+ * CertificateVerify.signature.md5_hash
+ *   MD5(handshake_messages);
+ *
+ * Certificate.signature.sha_hash
+ *   SHA(handshake_messages);
+ *
+ * Here handshake_messages refers to all handshake messages sent or
+ * received starting at client hello up to but not including this
+ * message, including the type and length fields of the handshake
+ * messages.
+ *
+ * select(SignatureAlgorithm) {
+ *   case anonymous: struct { };
+ *   case rsa:
+ *     digitally-signed struct {
+ *       opaque md5_hash[16];
+ *       opaque sha_hash[20];
+ *     };
+ *   case dsa:
+ *     digitally-signed struct {
+ *       opaque sha_hash[20];
+ *     };
+ * } Signature;
+ *
+ * In digital signing, one-way hash functions are used as input for a
+ * signing algorithm. A digitally-signed element is encoded as an opaque
+ * vector <0..2^16-1>, where the length is specified by the signing
+ * algorithm and key.
+ *
+ * In RSA signing, a 36-byte structure of two hashes (one SHA and one
+ * MD5) is signed (encrypted with the private key). It is encoded with
+ * PKCS #1 block type 0 or type 1 as described in [PKCS1].
+ *
+ * In DSS, the 20 bytes of the SHA hash are run directly through the
+ * Digital Signing Algorithm with no additional hashing.
+ *
+ * @param c the connection.
+ * @param signature the signature to include in the message.
+ *
+ * @return the CertificateVerify byte buffer.
+ */
+tls.createCertificateVerify = function(c, signature) {
+  /* Note: The signature will be stored in a "digitally-signed" opaque
+    vector that has the length prefixed using 2 bytes, so include those
+    2 bytes in the handshake message length. This is done as a minor
+    optimization instead of calling writeVector(). */
+
+  // determine length of the handshake message
+  var length = signature.length + 2;
+
+  // build record fragment
+  var rval = forge.util.createBuffer();
+  rval.putByte(tls.HandshakeType.certificate_verify);
+  rval.putInt24(length);
+  // add vector length bytes
+  rval.putInt16(signature.length);
+  rval.putBytes(signature);
+  return rval;
+};
+
+/**
+ * Creates a CertificateRequest message.
+ *
+ * @param c the connection.
+ *
+ * @return the CertificateRequest byte buffer.
+ */
+tls.createCertificateRequest = function(c) {
+  // TODO: support other certificate types
+  var certTypes = forge.util.createBuffer();
+
+  // common RSA certificate type
+  certTypes.putByte(0x01);
+
+  // TODO: verify that this data format is correct
+  // add distinguished names from CA store
+  var cAs = forge.util.createBuffer();
+  for(var key in c.caStore.certs) {
+    var cert = c.caStore.certs[key];
+    var dn = forge.pki.distinguishedNameToAsn1(cert.subject);
+    cAs.putBuffer(forge.asn1.toDer(dn));
+  }
+
+  // TODO: TLS 1.2+ has a different format
+
+  // determine length of the handshake message
+  var length =
+    1 + certTypes.length() +
+    2 + cAs.length();
+
+  // build record fragment
+  var rval = forge.util.createBuffer();
+  rval.putByte(tls.HandshakeType.certificate_request);
+  rval.putInt24(length);
+  writeVector(rval, 1, certTypes);
+  writeVector(rval, 2, cAs);
+  return rval;
+};
+
+/**
+ * Creates a ServerHelloDone message.
+ *
+ * @param c the connection.
+ *
+ * @return the ServerHelloDone byte buffer.
+ */
+tls.createServerHelloDone = function(c) {
+  // build record fragment
+  var rval = forge.util.createBuffer();
+  rval.putByte(tls.HandshakeType.server_hello_done);
+  rval.putInt24(0);
+  return rval;
+};
+
+/**
+ * Creates a ChangeCipherSpec message.
+ *
+ * The change cipher spec protocol exists to signal transitions in
+ * ciphering strategies. The protocol consists of a single message,
+ * which is encrypted and compressed under the current (not the pending)
+ * connection state. The message consists of a single byte of value 1.
+ *
+ * struct {
+ *   enum { change_cipher_spec(1), (255) } type;
+ * } ChangeCipherSpec;
+ *
+ * @return the ChangeCipherSpec byte buffer.
+ */
+tls.createChangeCipherSpec = function() {
+  var rval = forge.util.createBuffer();
+  rval.putByte(0x01);
+  return rval;
+};
+
+/**
+ * Creates a Finished message.
+ *
+ * struct {
+ *   opaque verify_data[12];
+ * } Finished;
+ *
+ * verify_data
+ *   PRF(master_secret, finished_label, MD5(handshake_messages) +
+ *   SHA-1(handshake_messages)) [0..11];
+ *
+ * finished_label
+ *   For Finished messages sent by the client, the string "client
+ *   finished". For Finished messages sent by the server, the
+ *   string "server finished".
+ *
+ * handshake_messages
+ *   All of the data from all handshake messages up to but not
+ *   including this message. This is only data visible at the
+ *   handshake layer and does not include record layer headers.
+ *   This is the concatenation of all the Handshake structures as
+ *   defined in 7.4 exchanged thus far.
+ *
+ * @param c the connection.
+ *
+ * @return the Finished byte buffer.
+ */
+tls.createFinished = function(c) {
+  // generate verify_data
+  var b = forge.util.createBuffer();
+  b.putBuffer(c.session.md5.digest());
+  b.putBuffer(c.session.sha1.digest());
+
+  // TODO: determine prf function and verify length for TLS 1.2
+  var client = (c.entity === tls.ConnectionEnd.client);
+  var sp = c.session.sp;
+  var vdl = 12;
+  var prf = prf_TLS1;
+  var label = client ? 'client finished' : 'server finished';
+  b = prf(sp.master_secret, label, b.getBytes(), vdl);
+
+  // build record fragment
+  var rval = forge.util.createBuffer();
+  rval.putByte(tls.HandshakeType.finished);
+  rval.putInt24(b.length());
+  rval.putBuffer(b);
+  return rval;
+};
+
+/**
+ * Creates a HeartbeatMessage (See RFC 6520).
+ *
+ * struct {
+ *   HeartbeatMessageType type;
+ *   uint16 payload_length;
+ *   opaque payload[HeartbeatMessage.payload_length];
+ *   opaque padding[padding_length];
+ * } HeartbeatMessage;
+ *
+ * The total length of a HeartbeatMessage MUST NOT exceed 2^14 or
+ * max_fragment_length when negotiated as defined in [RFC6066].
+ *
+ * type: The message type, either heartbeat_request or heartbeat_response.
+ *
+ * payload_length: The length of the payload.
+ *
+ * payload: The payload consists of arbitrary content.
+ *
+ * padding: The padding is random content that MUST be ignored by the
+ *   receiver. The length of a HeartbeatMessage is TLSPlaintext.length
+ *   for TLS and DTLSPlaintext.length for DTLS. Furthermore, the
+ *   length of the type field is 1 byte, and the length of the
+ *   payload_length is 2. Therefore, the padding_length is
+ *   TLSPlaintext.length - payload_length - 3 for TLS and
+ *   DTLSPlaintext.length - payload_length - 3 for DTLS. The
+ *   padding_length MUST be at least 16.
+ *
+ * The sender of a HeartbeatMessage MUST use a random padding of at
+ * least 16 bytes. The padding of a received HeartbeatMessage message
+ * MUST be ignored.
+ *
+ * If the payload_length of a received HeartbeatMessage is too large,
+ * the received HeartbeatMessage MUST be discarded silently.
+ *
+ * @param c the connection.
+ * @param type the tls.HeartbeatMessageType.
+ * @param payload the heartbeat data to send as the payload.
+ * @param [payloadLength] the payload length to use, defaults to the
+ *          actual payload length.
+ *
+ * @return the HeartbeatRequest byte buffer.
+ */
+tls.createHeartbeat = function(type, payload, payloadLength) {
+  if(typeof payloadLength === 'undefined') {
+    payloadLength = payload.length;
+  }
+  // build record fragment
+  var rval = forge.util.createBuffer();
+  rval.putByte(type);               // heartbeat message type
+  rval.putInt16(payloadLength);     // payload length
+  rval.putBytes(payload);           // payload
+  // padding
+  var plaintextLength = rval.length();
+  var paddingLength = Math.max(16, plaintextLength - payloadLength - 3);
+  rval.putBytes(forge.random.getBytes(paddingLength));
+  return rval;
+};
+
+/**
+ * Fragments, compresses, encrypts, and queues a record for delivery.
+ *
+ * @param c the connection.
+ * @param record the record to queue.
+ */
+tls.queue = function(c, record) {
+  // error during record creation
+  if(!record) {
+    return;
+  }
+
+  // if the record is a handshake record, update handshake hashes
+  if(record.type === tls.ContentType.handshake) {
+    var bytes = record.fragment.bytes();
+    c.session.md5.update(bytes);
+    c.session.sha1.update(bytes);
+    bytes = null;
+  }
+
+  // handle record fragmentation
+  var records;
+  if(record.fragment.length() <= tls.MaxFragment) {
+    records = [record];
+  } else {
+    // fragment data as long as it is too long
+    records = [];
+    var data = record.fragment.bytes();
+    while(data.length > tls.MaxFragment) {
+      records.push(tls.createRecord(c, {
+        type: record.type,
+        data: forge.util.createBuffer(data.slice(0, tls.MaxFragment))
+      }));
+      data = data.slice(tls.MaxFragment);
+    }
+    // add last record
+    if(data.length > 0) {
+      records.push(tls.createRecord(c, {
+        type: record.type,
+        data: forge.util.createBuffer(data)
+      }));
+    }
+  }
+
+  // compress and encrypt all fragmented records
+  for(var i = 0; i < records.length && !c.fail; ++i) {
+    // update the record using current write state
+    var rec = records[i];
+    var s = c.state.current.write;
+    if(s.update(c, rec)) {
+      // store record
+      c.records.push(rec);
+    }
+  }
+};
+
+/**
+ * Flushes all queued records to the output buffer and calls the
+ * tlsDataReady() handler on the given connection.
+ *
+ * @param c the connection.
+ *
+ * @return true on success, false on failure.
+ */
+tls.flush = function(c) {
+  for(var i = 0; i < c.records.length; ++i) {
+    var record = c.records[i];
+
+    // add record header and fragment
+    c.tlsData.putByte(record.type);
+    c.tlsData.putByte(record.version.major);
+    c.tlsData.putByte(record.version.minor);
+    c.tlsData.putInt16(record.fragment.length());
+    c.tlsData.putBuffer(c.records[i].fragment);
+  }
+  c.records = [];
+  return c.tlsDataReady(c);
+};
+
+/**
+ * Maps a pki.certificateError to a tls.Alert.Description.
+ *
+ * @param error the error to map.
+ *
+ * @return the alert description.
+ */
+var _certErrorToAlertDesc = function(error) {
+  switch(error) {
+  case true:
+    return true;
+  case forge.pki.certificateError.bad_certificate:
+    return tls.Alert.Description.bad_certificate;
+  case forge.pki.certificateError.unsupported_certificate:
+    return tls.Alert.Description.unsupported_certificate;
+  case forge.pki.certificateError.certificate_revoked:
+    return tls.Alert.Description.certificate_revoked;
+  case forge.pki.certificateError.certificate_expired:
+    return tls.Alert.Description.certificate_expired;
+  case forge.pki.certificateError.certificate_unknown:
+    return tls.Alert.Description.certificate_unknown;
+  case forge.pki.certificateError.unknown_ca:
+    return tls.Alert.Description.unknown_ca;
+  default:
+    return tls.Alert.Description.bad_certificate;
+  }
+};
+
+/**
+ * Maps a tls.Alert.Description to a pki.certificateError.
+ *
+ * @param desc the alert description.
+ *
+ * @return the certificate error.
+ */
+var _alertDescToCertError = function(desc) {
+  switch(desc) {
+  case true:
+    return true;
+  case tls.Alert.Description.bad_certificate:
+    return forge.pki.certificateError.bad_certificate;
+  case tls.Alert.Description.unsupported_certificate:
+    return forge.pki.certificateError.unsupported_certificate;
+  case tls.Alert.Description.certificate_revoked:
+    return forge.pki.certificateError.certificate_revoked;
+  case tls.Alert.Description.certificate_expired:
+    return forge.pki.certificateError.certificate_expired;
+  case tls.Alert.Description.certificate_unknown:
+    return forge.pki.certificateError.certificate_unknown;
+  case tls.Alert.Description.unknown_ca:
+    return forge.pki.certificateError.unknown_ca;
+  default:
+    return forge.pki.certificateError.bad_certificate;
+  }
+};
+
+/**
+ * Verifies a certificate chain against the given connection's
+ * Certificate Authority store.
+ *
+ * @param c the TLS connection.
+ * @param chain the certificate chain to verify, with the root or highest
+ *          authority at the end.
+ *
+ * @return true if successful, false if not.
+ */
+tls.verifyCertificateChain = function(c, chain) {
+  try {
+    // verify chain
+    forge.pki.verifyCertificateChain(c.caStore, chain,
+      function verify(vfd, depth, chain) {
+        // convert pki.certificateError to tls alert description
+        var desc = _certErrorToAlertDesc(vfd);
+
+        // call application callback
+        var ret = c.verify(c, vfd, depth, chain);
+        if(ret !== true) {
+          if(typeof ret === 'object' && !forge.util.isArray(ret)) {
+            // throw custom error
+            var error = new Error('The application rejected the certificate.');
+            error.send = true;
+            error.alert = {
+              level: tls.Alert.Level.fatal,
+              description: tls.Alert.Description.bad_certificate
+            };
+            if(ret.message) {
+              error.message = ret.message;
+            }
+            if(ret.alert) {
+              error.alert.description = ret.alert;
+            }
+            throw error;
+          }
+
+          // convert tls alert description to pki.certificateError
+          if(ret !== vfd) {
+            ret = _alertDescToCertError(ret);
+          }
+        }
+
+        return ret;
+      });
+  } catch(ex) {
+    // build tls error if not already customized
+    var err = ex;
+    if(typeof err !== 'object' || forge.util.isArray(err)) {
+      err = {
+        send: true,
+        alert: {
+          level: tls.Alert.Level.fatal,
+          description: _certErrorToAlertDesc(ex)
+        }
+      };
+    }
+    if(!('send' in err)) {
+      err.send = true;
+    }
+    if(!('alert' in err)) {
+      err.alert = {
+        level: tls.Alert.Level.fatal,
+        description: _certErrorToAlertDesc(err.error)
+      };
+    }
+
+    // send error
+    c.error(c, err);
+  }
+
+  return !c.fail;
+};
+
+/**
+ * Creates a new TLS session cache.
+ *
+ * @param cache optional map of session ID to cached session.
+ * @param capacity the maximum size for the cache (default: 100).
+ *
+ * @return the new TLS session cache.
+ */
+tls.createSessionCache = function(cache, capacity) {
+  var rval = null;
+
+  // assume input is already a session cache object
+  if(cache && cache.getSession && cache.setSession && cache.order) {
+    rval = cache;
+  } else {
+    // create cache
+    rval = {};
+    rval.cache = cache || {};
+    rval.capacity = Math.max(capacity || 100, 1);
+    rval.order = [];
+
+    // store order for sessions, delete session overflow
+    for(var key in cache) {
+      if(rval.order.length <= capacity) {
+        rval.order.push(key);
+      } else {
+        delete cache[key];
+      }
+    }
+
+    // get a session from a session ID (or get any session)
+    rval.getSession = function(sessionId) {
+      var session = null;
+      var key = null;
+
+      // if session ID provided, use it
+      if(sessionId) {
+        key = forge.util.bytesToHex(sessionId);
+      } else if(rval.order.length > 0) {
+        // get first session from cache
+        key = rval.order[0];
+      }
+
+      if(key !== null && key in rval.cache) {
+        // get cached session and remove from cache
+        session = rval.cache[key];
+        delete rval.cache[key];
+        for(var i in rval.order) {
+          if(rval.order[i] === key) {
+            rval.order.splice(i, 1);
+            break;
+          }
+        }
+      }
+
+      return session;
+    };
+
+    // set a session in the cache
+    rval.setSession = function(sessionId, session) {
+      // remove session from cache if at capacity
+      if(rval.order.length === rval.capacity) {
+        var key = rval.order.shift();
+        delete rval.cache[key];
+      }
+      // add session to cache
+      var key = forge.util.bytesToHex(sessionId);
+      rval.order.push(key);
+      rval.cache[key] = session;
+    };
+  }
+
+  return rval;
+};
+
+/**
+ * Creates a new TLS connection.
+ *
+ * See public createConnection() docs for more details.
+ *
+ * @param options the options for this connection.
+ *
+ * @return the new TLS connection.
+ */
+tls.createConnection = function(options) {
+  var caStore = null;
+  if(options.caStore) {
+    // if CA store is an array, convert it to a CA store object
+    if(forge.util.isArray(options.caStore)) {
+      caStore = forge.pki.createCaStore(options.caStore);
+    } else {
+      caStore = options.caStore;
+    }
+  } else {
+    // create empty CA store
+    caStore = forge.pki.createCaStore();
+  }
+
+  // setup default cipher suites
+  var cipherSuites = options.cipherSuites || null;
+  if(cipherSuites === null) {
+    cipherSuites = [];
+    for(var key in tls.CipherSuites) {
+      cipherSuites.push(tls.CipherSuites[key]);
+    }
+  }
+
+  // set default entity
+  var entity = (options.server || false) ?
+    tls.ConnectionEnd.server : tls.ConnectionEnd.client;
+
+  // create session cache if requested
+  var sessionCache = options.sessionCache ?
+    tls.createSessionCache(options.sessionCache) : null;
+
+  // create TLS connection
+  var c = {
+    version: {major: tls.Version.major, minor: tls.Version.minor},
+    entity: entity,
+    sessionId: options.sessionId,
+    caStore: caStore,
+    sessionCache: sessionCache,
+    cipherSuites: cipherSuites,
+    connected: options.connected,
+    virtualHost: options.virtualHost || null,
+    verifyClient: options.verifyClient || false,
+    verify: options.verify || function(cn, vfd, dpth, cts) {return vfd;},
+    getCertificate: options.getCertificate || null,
+    getPrivateKey: options.getPrivateKey || null,
+    getSignature: options.getSignature || null,
+    input: forge.util.createBuffer(),
+    tlsData: forge.util.createBuffer(),
+    data: forge.util.createBuffer(),
+    tlsDataReady: options.tlsDataReady,
+    dataReady: options.dataReady,
+    heartbeatReceived: options.heartbeatReceived,
+    closed: options.closed,
+    error: function(c, ex) {
+      // set origin if not set
+      ex.origin = ex.origin ||
+        ((c.entity === tls.ConnectionEnd.client) ? 'client' : 'server');
+
+      // send TLS alert
+      if(ex.send) {
+        tls.queue(c, tls.createAlert(c, ex.alert));
+        tls.flush(c);
+      }
+
+      // error is fatal by default
+      var fatal = (ex.fatal !== false);
+      if(fatal) {
+        // set fail flag
+        c.fail = true;
+      }
+
+      // call error handler first
+      options.error(c, ex);
+
+      if(fatal) {
+        // fatal error, close connection, do not clear fail
+        c.close(false);
+      }
+    },
+    deflate: options.deflate || null,
+    inflate: options.inflate || null
+  };
+
+  /**
+   * Resets a closed TLS connection for reuse. Called in c.close().
+   *
+   * @param clearFail true to clear the fail flag (default: true).
+   */
+  c.reset = function(clearFail) {
+    c.version = {major: tls.Version.major, minor: tls.Version.minor};
+    c.record = null;
+    c.session = null;
+    c.peerCertificate = null;
+    c.state = {
+      pending: null,
+      current: null
+    };
+    c.expect = (c.entity === tls.ConnectionEnd.client) ? SHE : CHE;
+    c.fragmented = null;
+    c.records = [];
+    c.open = false;
+    c.handshakes = 0;
+    c.handshaking = false;
+    c.isConnected = false;
+    c.fail = !(clearFail || typeof(clearFail) === 'undefined');
+    c.input.clear();
+    c.tlsData.clear();
+    c.data.clear();
+    c.state.current = tls.createConnectionState(c);
+  };
+
+  // do initial reset of connection
+  c.reset();
+
+  /**
+   * Updates the current TLS engine state based on the given record.
+   *
+   * @param c the TLS connection.
+   * @param record the TLS record to act on.
+   */
+  var _update = function(c, record) {
+    // get record handler (align type in table by subtracting lowest)
+    var aligned = record.type - tls.ContentType.change_cipher_spec;
+    var handlers = ctTable[c.entity][c.expect];
+    if(aligned in handlers) {
+      handlers[aligned](c, record);
+    } else {
+      // unexpected record
+      tls.handleUnexpected(c, record);
+    }
+  };
+
+  /**
+   * Reads the record header and initializes the next record on the given
+   * connection.
+   *
+   * @param c the TLS connection with the next record.
+   *
+   * @return 0 if the input data could be processed, otherwise the
+   *         number of bytes required for data to be processed.
+   */
+  var _readRecordHeader = function(c) {
+    var rval = 0;
+
+    // get input buffer and its length
+    var b = c.input;
+    var len = b.length();
+
+    // need at least 5 bytes to initialize a record
+    if(len < 5) {
+      rval = 5 - len;
+    } else {
+      // enough bytes for header
+      // initialize record
+      c.record = {
+        type: b.getByte(),
+        version: {
+          major: b.getByte(),
+          minor: b.getByte()
+        },
+        length: b.getInt16(),
+        fragment: forge.util.createBuffer(),
+        ready: false
+      };
+
+      // check record version
+      var compatibleVersion = (c.record.version.major === c.version.major);
+      if(compatibleVersion && c.session && c.session.version) {
+        // session version already set, require same minor version
+        compatibleVersion = (c.record.version.minor === c.version.minor);
+      }
+      if(!compatibleVersion) {
+        c.error(c, {
+          message: 'Incompatible TLS version.',
+          send: true,
+          alert: {
+            level: tls.Alert.Level.fatal,
+            description: tls.Alert.Description.protocol_version
+          }
+        });
+      }
+    }
+
+    return rval;
+  };
+
+  /**
+   * Reads the next record's contents and appends its message to any
+   * previously fragmented message.
+   *
+   * @param c the TLS connection with the next record.
+   *
+   * @return 0 if the input data could be processed, otherwise the
+   *         number of bytes required for data to be processed.
+   */
+  var _readRecord = function(c) {
+    var rval = 0;
+
+    // ensure there is enough input data to get the entire record
+    var b = c.input;
+    var len = b.length();
+    if(len < c.record.length) {
+      // not enough data yet, return how much is required
+      rval = c.record.length - len;
+    } else {
+      // there is enough data to parse the pending record
+      // fill record fragment and compact input buffer
+      c.record.fragment.putBytes(b.getBytes(c.record.length));
+      b.compact();
+
+      // update record using current read state
+      var s = c.state.current.read;
+      if(s.update(c, c.record)) {
+        // see if there is a previously fragmented message that the
+        // new record's message fragment should be appended to
+        if(c.fragmented !== null) {
+          // if the record type matches a previously fragmented
+          // record, append the record fragment to it
+          if(c.fragmented.type === c.record.type) {
+            // concatenate record fragments
+            c.fragmented.fragment.putBuffer(c.record.fragment);
+            c.record = c.fragmented;
+          } else {
+            // error, invalid fragmented record
+            c.error(c, {
+              message: 'Invalid fragmented record.',
+              send: true,
+              alert: {
+                level: tls.Alert.Level.fatal,
+                description:
+                  tls.Alert.Description.unexpected_message
+              }
+            });
+          }
+        }
+
+        // record is now ready
+        c.record.ready = true;
+      }
+    }
+
+    return rval;
+  };
+
+  /**
+   * Performs a handshake using the TLS Handshake Protocol, as a client.
+   *
+   * This method should only be called if the connection is in client mode.
+   *
+   * @param sessionId the session ID to use, null to start a new one.
+   */
+  c.handshake = function(sessionId) {
+    // error to call this in non-client mode
+    if(c.entity !== tls.ConnectionEnd.client) {
+      // not fatal error
+      c.error(c, {
+        message: 'Cannot initiate handshake as a server.',
+        fatal: false
+      });
+    } else if(c.handshaking) {
+      // handshake is already in progress, fail but not fatal error
+      c.error(c, {
+        message: 'Handshake already in progress.',
+        fatal: false
+      });
+    } else {
+      // clear fail flag on reuse
+      if(c.fail && !c.open && c.handshakes === 0) {
+        c.fail = false;
+      }
+
+      // now handshaking
+      c.handshaking = true;
+
+      // default to blank (new session)
+      sessionId = sessionId || '';
+
+      // if a session ID was specified, try to find it in the cache
+      var session = null;
+      if(sessionId.length > 0) {
+        if(c.sessionCache) {
+          session = c.sessionCache.getSession(sessionId);
+        }
+
+        // matching session not found in cache, clear session ID
+        if(session === null) {
+          sessionId = '';
+        }
+      }
+
+      // no session given, grab a session from the cache, if available
+      if(sessionId.length === 0 && c.sessionCache) {
+        session = c.sessionCache.getSession();
+        if(session !== null) {
+          sessionId = session.id;
+        }
+      }
+
+      // set up session
+      c.session = {
+        id: sessionId,
+        version: null,
+        cipherSuite: null,
+        compressionMethod: null,
+        serverCertificate: null,
+        certificateRequest: null,
+        clientCertificate: null,
+        sp: {},
+        md5: forge.md.md5.create(),
+        sha1: forge.md.sha1.create()
+      };
+
+      // use existing session information
+      if(session) {
+        // only update version on connection, session version not yet set
+        c.version = session.version;
+        c.session.sp = session.sp;
+      }
+
+      // generate new client random
+      c.session.sp.client_random = tls.createRandom().getBytes();
+
+      // connection now open
+      c.open = true;
+
+      // send hello
+      tls.queue(c, tls.createRecord(c, {
+        type: tls.ContentType.handshake,
+        data: tls.createClientHello(c)
+      }));
+      tls.flush(c);
+    }
+  };
+
+  /**
+   * Called when TLS protocol data has been received from somewhere and should
+   * be processed by the TLS engine.
+   *
+   * @param data the TLS protocol data, as a string, to process.
+   *
+   * @return 0 if the data could be processed, otherwise the number of bytes
+   *         required for data to be processed.
+   */
+  c.process = function(data) {
+    var rval = 0;
+
+    // buffer input data
+    if(data) {
+      c.input.putBytes(data);
+    }
+
+    // process next record if no failure, process will be called after
+    // each record is handled (since handling can be asynchronous)
+    if(!c.fail) {
+      // reset record if ready and now empty
+      if(c.record !== null &&
+        c.record.ready && c.record.fragment.isEmpty()) {
+        c.record = null;
+      }
+
+      // if there is no pending record, try to read record header
+      if(c.record === null) {
+        rval = _readRecordHeader(c);
+      }
+
+      // read the next record (if record not yet ready)
+      if(!c.fail && c.record !== null && !c.record.ready) {
+        rval = _readRecord(c);
+      }
+
+      // record ready to be handled, update engine state
+      if(!c.fail && c.record !== null && c.record.ready) {
+        _update(c, c.record);
+      }
+    }
+
+    return rval;
+  };
+
+  /**
+   * Requests that application data be packaged into a TLS record. The
+   * tlsDataReady handler will be called when the TLS record(s) have been
+   * prepared.
+   *
+   * @param data the application data, as a raw 'binary' encoded string, to
+   *          be sent; to send utf-16/utf-8 string data, use the return value
+   *          of util.encodeUtf8(str).
+   *
+   * @return true on success, false on failure.
+   */
+  c.prepare = function(data) {
+    tls.queue(c, tls.createRecord(c, {
+      type: tls.ContentType.application_data,
+      data: forge.util.createBuffer(data)
+    }));
+    return tls.flush(c);
+  };
+
+  /**
+   * Requests that a heartbeat request be packaged into a TLS record for
+   * transmission. The tlsDataReady handler will be called when TLS record(s)
+   * have been prepared.
+   *
+   * When a heartbeat response has been received, the heartbeatReceived
+   * handler will be called with the matching payload. This handler can
+   * be used to clear a retransmission timer, etc.
+   *
+   * @param payload the heartbeat data to send as the payload in the message.
+   * @param [payloadLength] the payload length to use, defaults to the
+   *          actual payload length.
+   *
+   * @return true on success, false on failure.
+   */
+  c.prepareHeartbeatRequest = function(payload, payloadLength) {
+    if(payload instanceof forge.util.ByteBuffer) {
+      payload = payload.bytes();
+    }
+    if(typeof payloadLength === 'undefined') {
+      payloadLength = payload.length;
+    }
+    c.expectedHeartbeatPayload = payload;
+    tls.queue(c, tls.createRecord(c, {
+      type: tls.ContentType.heartbeat,
+      data: tls.createHeartbeat(
+        tls.HeartbeatMessageType.heartbeat_request, payload, payloadLength)
+    }));
+    return tls.flush(c);
+  };
+
+  /**
+   * Closes the connection (sends a close_notify alert).
+   *
+   * @param clearFail true to clear the fail flag (default: true).
+   */
+  c.close = function(clearFail) {
+    // save session if connection didn't fail
+    if(!c.fail && c.sessionCache && c.session) {
+      // only need to preserve session ID, version, and security params
+      var session = {
+        id: c.session.id,
+        version: c.session.version,
+        sp: c.session.sp
+      };
+      session.sp.keys = null;
+      c.sessionCache.setSession(session.id, session);
+    }
+
+    if(c.open) {
+      // connection no longer open, clear input
+      c.open = false;
+      c.input.clear();
+
+      // if connected or handshaking, send an alert
+      if(c.isConnected || c.handshaking) {
+        c.isConnected = c.handshaking = false;
+
+        // send close_notify alert
+        tls.queue(c, tls.createAlert(c, {
+          level: tls.Alert.Level.warning,
+          description: tls.Alert.Description.close_notify
+        }));
+        tls.flush(c);
+      }
+
+      // call handler
+      c.closed(c);
+    }
+
+    // reset TLS connection, do not clear fail flag
+    c.reset(clearFail);
+  };
+
+  return c;
+};
+
+/* TLS API */
+forge.tls = forge.tls || {};
+
+// expose non-functions
+for(var key in tls) {
+  if(typeof tls[key] !== 'function') {
+    forge.tls[key] = tls[key];
+  }
+}
+
+// expose prf_tls1 for testing
+forge.tls.prf_tls1 = prf_TLS1;
+
+// expose sha1 hmac method
+forge.tls.hmac_sha1 = hmac_sha1;
+
+// expose session cache creation
+forge.tls.createSessionCache = tls.createSessionCache;
+
+/**
+ * Creates a new TLS connection. This does not make any assumptions about the
+ * transport layer that TLS is working on top of, ie: it does not assume there
+ * is a TCP/IP connection or establish one. A TLS connection is totally
+ * abstracted away from the layer is runs on top of, it merely establishes a
+ * secure channel between a client" and a "server".
+ *
+ * A TLS connection contains 4 connection states: pending read and write, and
+ * current read and write.
+ *
+ * At initialization, the current read and write states will be null. Only once
+ * the security parameters have been set and the keys have been generated can
+ * the pending states be converted into current states. Current states will be
+ * updated for each record processed.
+ *
+ * A custom certificate verify callback may be provided to check information
+ * like the common name on the server's certificate. It will be called for
+ * every certificate in the chain. It has the following signature:
+ *
+ * variable func(c, certs, index, preVerify)
+ * Where:
+ * c         The TLS connection
+ * verified  Set to true if certificate was verified, otherwise the alert
+ *           tls.Alert.Description for why the certificate failed.
+ * depth     The current index in the chain, where 0 is the server's cert.
+ * certs     The certificate chain, *NOTE* if the server was anonymous then
+ *           the chain will be empty.
+ *
+ * The function returns true on success and on failure either the appropriate
+ * tls.Alert.Description or an object with 'alert' set to the appropriate
+ * tls.Alert.Description and 'message' set to a custom error message. If true
+ * is not returned then the connection will abort using, in order of
+ * availability, first the returned alert description, second the preVerify
+ * alert description, and lastly the default 'bad_certificate'.
+ *
+ * There are three callbacks that can be used to make use of client-side
+ * certificates where each takes the TLS connection as the first parameter:
+ *
+ * getCertificate(conn, hint)
+ *   The second parameter is a hint as to which certificate should be
+ *   returned. If the connection entity is a client, then the hint will be
+ *   the CertificateRequest message from the server that is part of the
+ *   TLS protocol. If the connection entity is a server, then it will be
+ *   the servername list provided via an SNI extension the ClientHello, if
+ *   one was provided (empty array if not). The hint can be examined to
+ *   determine which certificate to use (advanced). Most implementations
+ *   will just return a certificate. The return value must be a
+ *   PEM-formatted certificate or an array of PEM-formatted certificates
+ *   that constitute a certificate chain, with the first in the array/chain
+ *   being the client's certificate.
+ * getPrivateKey(conn, certificate)
+ *   The second parameter is an forge.pki X.509 certificate object that
+ *   is associated with the requested private key. The return value must
+ *   be a PEM-formatted private key.
+ * getSignature(conn, bytes, callback)
+ *   This callback can be used instead of getPrivateKey if the private key
+ *   is not directly accessible in javascript or should not be. For
+ *   instance, a secure external web service could provide the signature
+ *   in exchange for appropriate credentials. The second parameter is a
+ *   string of bytes to be signed that are part of the TLS protocol. These
+ *   bytes are used to verify that the private key for the previously
+ *   provided client-side certificate is accessible to the client. The
+ *   callback is a function that takes 2 parameters, the TLS connection
+ *   and the RSA encrypted (signed) bytes as a string. This callback must
+ *   be called once the signature is ready.
+ *
+ * @param options the options for this connection:
+ *   server: true if the connection is server-side, false for client.
+ *   sessionId: a session ID to reuse, null for a new connection.
+ *   caStore: an array of certificates to trust.
+ *   sessionCache: a session cache to use.
+ *   cipherSuites: an optional array of cipher suites to use,
+ *     see tls.CipherSuites.
+ *   connected: function(conn) called when the first handshake completes.
+ *   virtualHost: the virtual server name to use in a TLS SNI extension.
+ *   verifyClient: true to require a client certificate in server mode,
+ *     'optional' to request one, false not to (default: false).
+ *   verify: a handler used to custom verify certificates in the chain.
+ *   getCertificate: an optional callback used to get a certificate or
+ *     a chain of certificates (as an array).
+ *   getPrivateKey: an optional callback used to get a private key.
+ *   getSignature: an optional callback used to get a signature.
+ *   tlsDataReady: function(conn) called when TLS protocol data has been
+ *     prepared and is ready to be used (typically sent over a socket
+ *     connection to its destination), read from conn.tlsData buffer.
+ *   dataReady: function(conn) called when application data has
+ *     been parsed from a TLS record and should be consumed by the
+ *     application, read from conn.data buffer.
+ *   closed: function(conn) called when the connection has been closed.
+ *   error: function(conn, error) called when there was an error.
+ *   deflate: function(inBytes) if provided, will deflate TLS records using
+ *     the deflate algorithm if the server supports it.
+ *   inflate: function(inBytes) if provided, will inflate TLS records using
+ *     the deflate algorithm if the server supports it.
+ *
+ * @return the new TLS connection.
+ */
+forge.tls.createConnection = tls.createConnection;
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'tls';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define([
+  'require',
+  'module',
+  './asn1',
+  './hmac',
+  './md',
+  './pem',
+  './pki',
+  './random',
+  './util'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/tlssocket.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/tlssocket.js
new file mode 100644
index 0000000..9a00ea2
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/tlssocket.js
@@ -0,0 +1,304 @@
+/**
+ * Socket wrapping functions for TLS.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2009-2012 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+/**
+ * Wraps a forge.net socket with a TLS layer.
+ *
+ * @param options:
+ *   sessionId: a session ID to reuse, null for a new connection if no session
+ *     cache is provided or it is empty.
+ *   caStore: an array of certificates to trust.
+ *   sessionCache: a session cache to use.
+ *   cipherSuites: an optional array of cipher suites to use, see
+ *     tls.CipherSuites.
+ *   socket: the socket to wrap.
+ *   virtualHost: the virtual server name to use in a TLS SNI extension.
+ *   verify: a handler used to custom verify certificates in the chain.
+ *   getCertificate: an optional callback used to get a certificate.
+ *   getPrivateKey: an optional callback used to get a private key.
+ *   getSignature: an optional callback used to get a signature.
+ *   deflate: function(inBytes) if provided, will deflate TLS records using
+ *     the deflate algorithm if the server supports it.
+ *   inflate: function(inBytes) if provided, will inflate TLS records using
+ *     the deflate algorithm if the server supports it.
+ *
+ * @return the TLS-wrapped socket.
+ */
+forge.tls.wrapSocket = function(options) {
+  // get raw socket
+  var socket = options.socket;
+
+  // create TLS socket
+  var tlsSocket = {
+    id: socket.id,
+    // set handlers
+    connected: socket.connected || function(e){},
+    closed: socket.closed || function(e){},
+    data: socket.data || function(e){},
+    error: socket.error || function(e){}
+  };
+
+  // create TLS connection
+  var c = forge.tls.createConnection({
+    server: false,
+    sessionId: options.sessionId || null,
+    caStore: options.caStore || [],
+    sessionCache: options.sessionCache || null,
+    cipherSuites: options.cipherSuites || null,
+    virtualHost: options.virtualHost,
+    verify: options.verify,
+    getCertificate: options.getCertificate,
+    getPrivateKey: options.getPrivateKey,
+    getSignature: options.getSignature,
+    deflate: options.deflate,
+    inflate: options.inflate,
+    connected: function(c) {
+      // first handshake complete, call handler
+      if(c.handshakes === 1) {
+        tlsSocket.connected({
+          id: socket.id,
+          type: 'connect',
+          bytesAvailable: c.data.length()
+        });
+      }
+    },
+    tlsDataReady: function(c) {
+      // send TLS data over socket
+      return socket.send(c.tlsData.getBytes());
+    },
+    dataReady: function(c) {
+      // indicate application data is ready
+      tlsSocket.data({
+        id: socket.id,
+        type: 'socketData',
+        bytesAvailable: c.data.length()
+      });
+    },
+    closed: function(c) {
+      // close socket
+      socket.close();
+    },
+    error: function(c, e) {
+      // send error, close socket
+      tlsSocket.error({
+        id: socket.id,
+        type: 'tlsError',
+        message: e.message,
+        bytesAvailable: 0,
+        error: e
+      });
+      socket.close();
+    }
+  });
+
+  // handle doing handshake after connecting
+  socket.connected = function(e) {
+    c.handshake(options.sessionId);
+  };
+
+  // handle closing TLS connection
+  socket.closed = function(e) {
+    if(c.open && c.handshaking) {
+      // error
+      tlsSocket.error({
+        id: socket.id,
+        type: 'ioError',
+        message: 'Connection closed during handshake.',
+        bytesAvailable: 0
+      });
+    }
+    c.close();
+
+    // call socket handler
+    tlsSocket.closed({
+      id: socket.id,
+      type: 'close',
+      bytesAvailable: 0
+    });
+  };
+
+  // handle error on socket
+  socket.error = function(e) {
+    // error
+    tlsSocket.error({
+      id: socket.id,
+      type: e.type,
+      message: e.message,
+      bytesAvailable: 0
+    });
+    c.close();
+  };
+
+  // handle receiving raw TLS data from socket
+  var _requiredBytes = 0;
+  socket.data = function(e) {
+    // drop data if connection not open
+    if(!c.open) {
+      socket.receive(e.bytesAvailable);
+    } else {
+      // only receive if there are enough bytes available to
+      // process a record
+      if(e.bytesAvailable >= _requiredBytes) {
+        var count = Math.max(e.bytesAvailable, _requiredBytes);
+        var data = socket.receive(count);
+        if(data !== null) {
+          _requiredBytes = c.process(data);
+        }
+      }
+    }
+  };
+
+  /**
+   * Destroys this socket.
+   */
+  tlsSocket.destroy = function() {
+    socket.destroy();
+  };
+
+  /**
+   * Sets this socket's TLS session cache. This should be called before
+   * the socket is connected or after it is closed.
+   *
+   * The cache is an object mapping session IDs to internal opaque state.
+   * An application might need to change the cache used by a particular
+   * tlsSocket between connections if it accesses multiple TLS hosts.
+   *
+   * @param cache the session cache to use.
+   */
+  tlsSocket.setSessionCache = function(cache) {
+    c.sessionCache = tls.createSessionCache(cache);
+  };
+
+  /**
+   * Connects this socket.
+   *
+   * @param options:
+   *           host: the host to connect to.
+   *           port: the port to connect to.
+   *           policyPort: the policy port to use (if non-default), 0 to
+   *              use the flash default.
+   *           policyUrl: the policy file URL to use (instead of port).
+   */
+  tlsSocket.connect = function(options) {
+    socket.connect(options);
+  };
+
+  /**
+   * Closes this socket.
+   */
+  tlsSocket.close = function() {
+    c.close();
+  };
+
+  /**
+   * Determines if the socket is connected or not.
+   *
+   * @return true if connected, false if not.
+   */
+  tlsSocket.isConnected = function() {
+    return c.isConnected && socket.isConnected();
+  };
+
+  /**
+   * Writes bytes to this socket.
+   *
+   * @param bytes the bytes (as a string) to write.
+   *
+   * @return true on success, false on failure.
+   */
+  tlsSocket.send = function(bytes) {
+    return c.prepare(bytes);
+  };
+
+  /**
+   * Reads bytes from this socket (non-blocking). Fewer than the number of
+   * bytes requested may be read if enough bytes are not available.
+   *
+   * This method should be called from the data handler if there are enough
+   * bytes available. To see how many bytes are available, check the
+   * 'bytesAvailable' property on the event in the data handler or call the
+   * bytesAvailable() function on the socket. If the browser is msie, then the
+   * bytesAvailable() function should be used to avoid race conditions.
+   * Otherwise, using the property on the data handler's event may be quicker.
+   *
+   * @param count the maximum number of bytes to read.
+   *
+   * @return the bytes read (as a string) or null on error.
+   */
+  tlsSocket.receive = function(count) {
+    return c.data.getBytes(count);
+  };
+
+  /**
+   * Gets the number of bytes available for receiving on the socket.
+   *
+   * @return the number of bytes available for receiving.
+   */
+  tlsSocket.bytesAvailable = function() {
+    return c.data.length();
+  };
+
+  return tlsSocket;
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'tlssocket';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module', './tls'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/util.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/util.js
new file mode 100644
index 0000000..0290842
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/util.js
@@ -0,0 +1,2953 @@
+/**
+ * Utility functions for web applications.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2014 Digital Bazaar, Inc.
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+/* Utilities API */
+var util = forge.util = forge.util || {};
+
+// define setImmediate and nextTick
+if(typeof process === 'undefined' || !process.nextTick) {
+  if(typeof setImmediate === 'function') {
+    util.setImmediate = setImmediate;
+    util.nextTick = function(callback) {
+      return setImmediate(callback);
+    };
+  } else {
+    util.setImmediate = function(callback) {
+      setTimeout(callback, 0);
+    };
+    util.nextTick = util.setImmediate;
+  }
+} else {
+  util.nextTick = process.nextTick;
+  if(typeof setImmediate === 'function') {
+    util.setImmediate = setImmediate;
+  } else {
+    util.setImmediate = util.nextTick;
+  }
+}
+
+// define isArray
+util.isArray = Array.isArray || function(x) {
+  return Object.prototype.toString.call(x) === '[object Array]';
+};
+
+// define isArrayBuffer
+util.isArrayBuffer = function(x) {
+  return typeof ArrayBuffer !== 'undefined' && x instanceof ArrayBuffer;
+};
+
+// define isArrayBufferView
+var _arrayBufferViews = [];
+if(typeof DataView !== 'undefined') {
+  _arrayBufferViews.push(DataView);
+}
+if(typeof Int8Array !== 'undefined') {
+  _arrayBufferViews.push(Int8Array);
+}
+if(typeof Uint8Array !== 'undefined') {
+  _arrayBufferViews.push(Uint8Array);
+}
+if(typeof Uint8ClampedArray !== 'undefined') {
+  _arrayBufferViews.push(Uint8ClampedArray);
+}
+if(typeof Int16Array !== 'undefined') {
+  _arrayBufferViews.push(Int16Array);
+}
+if(typeof Uint16Array !== 'undefined') {
+  _arrayBufferViews.push(Uint16Array);
+}
+if(typeof Int32Array !== 'undefined') {
+  _arrayBufferViews.push(Int32Array);
+}
+if(typeof Uint32Array !== 'undefined') {
+  _arrayBufferViews.push(Uint32Array);
+}
+if(typeof Float32Array !== 'undefined') {
+  _arrayBufferViews.push(Float32Array);
+}
+if(typeof Float64Array !== 'undefined') {
+  _arrayBufferViews.push(Float64Array);
+}
+util.isArrayBufferView = function(x) {
+  for(var i = 0; i < _arrayBufferViews.length; ++i) {
+    if(x instanceof _arrayBufferViews[i]) {
+      return true;
+    }
+  }
+  return false;
+};
+
+// TODO: set ByteBuffer to best available backing
+util.ByteBuffer = ByteStringBuffer;
+
+/** Buffer w/BinaryString backing */
+
+/**
+ * Constructor for a binary string backed byte buffer.
+ *
+ * @param [b] the bytes to wrap (either encoded as string, one byte per
+ *          character, or as an ArrayBuffer or Typed Array).
+ */
+function ByteStringBuffer(b) {
+  // TODO: update to match DataBuffer API
+
+  // the data in this buffer
+  this.data = '';
+  // the pointer for reading from this buffer
+  this.read = 0;
+
+  if(typeof b === 'string') {
+    this.data = b;
+  } else if(util.isArrayBuffer(b) || util.isArrayBufferView(b)) {
+    // convert native buffer to forge buffer
+    // FIXME: support native buffers internally instead
+    var arr = new Uint8Array(b);
+    try {
+      this.data = String.fromCharCode.apply(null, arr);
+    } catch(e) {
+      for(var i = 0; i < arr.length; ++i) {
+        this.putByte(arr[i]);
+      }
+    }
+  } else if(b instanceof ByteStringBuffer ||
+    (typeof b === 'object' && typeof b.data === 'string' &&
+    typeof b.read === 'number')) {
+    // copy existing buffer
+    this.data = b.data;
+    this.read = b.read;
+  }
+
+  // used for v8 optimization
+  this._constructedStringLength = 0;
+}
+util.ByteStringBuffer = ByteStringBuffer;
+
+/* Note: This is an optimization for V8-based browsers. When V8 concatenates
+  a string, the strings are only joined logically using a "cons string" or
+  "constructed/concatenated string". These containers keep references to one
+  another and can result in very large memory usage. For example, if a 2MB
+  string is constructed by concatenating 4 bytes together at a time, the
+  memory usage will be ~44MB; so ~22x increase. The strings are only joined
+  together when an operation requiring their joining takes place, such as
+  substr(). This function is called when adding data to this buffer to ensure
+  these types of strings are periodically joined to reduce the memory
+  footprint. */
+var _MAX_CONSTRUCTED_STRING_LENGTH = 4096;
+util.ByteStringBuffer.prototype._optimizeConstructedString = function(x) {
+  this._constructedStringLength += x;
+  if(this._constructedStringLength > _MAX_CONSTRUCTED_STRING_LENGTH) {
+    // this substr() should cause the constructed string to join
+    this.data.substr(0, 1);
+    this._constructedStringLength = 0;
+  }
+};
+
+/**
+ * Gets the number of bytes in this buffer.
+ *
+ * @return the number of bytes in this buffer.
+ */
+util.ByteStringBuffer.prototype.length = function() {
+  return this.data.length - this.read;
+};
+
+/**
+ * Gets whether or not this buffer is empty.
+ *
+ * @return true if this buffer is empty, false if not.
+ */
+util.ByteStringBuffer.prototype.isEmpty = function() {
+  return this.length() <= 0;
+};
+
+/**
+ * Puts a byte in this buffer.
+ *
+ * @param b the byte to put.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.putByte = function(b) {
+  return this.putBytes(String.fromCharCode(b));
+};
+
+/**
+ * Puts a byte in this buffer N times.
+ *
+ * @param b the byte to put.
+ * @param n the number of bytes of value b to put.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.fillWithByte = function(b, n) {
+  b = String.fromCharCode(b);
+  var d = this.data;
+  while(n > 0) {
+    if(n & 1) {
+      d += b;
+    }
+    n >>>= 1;
+    if(n > 0) {
+      b += b;
+    }
+  }
+  this.data = d;
+  this._optimizeConstructedString(n);
+  return this;
+};
+
+/**
+ * Puts bytes in this buffer.
+ *
+ * @param bytes the bytes (as a UTF-8 encoded string) to put.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.putBytes = function(bytes) {
+  this.data += bytes;
+  this._optimizeConstructedString(bytes.length);
+  return this;
+};
+
+/**
+ * Puts a UTF-16 encoded string into this buffer.
+ *
+ * @param str the string to put.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.putString = function(str) {
+  return this.putBytes(util.encodeUtf8(str));
+};
+
+/**
+ * Puts a 16-bit integer in this buffer in big-endian order.
+ *
+ * @param i the 16-bit integer.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.putInt16 = function(i) {
+  return this.putBytes(
+    String.fromCharCode(i >> 8 & 0xFF) +
+    String.fromCharCode(i & 0xFF));
+};
+
+/**
+ * Puts a 24-bit integer in this buffer in big-endian order.
+ *
+ * @param i the 24-bit integer.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.putInt24 = function(i) {
+  return this.putBytes(
+    String.fromCharCode(i >> 16 & 0xFF) +
+    String.fromCharCode(i >> 8 & 0xFF) +
+    String.fromCharCode(i & 0xFF));
+};
+
+/**
+ * Puts a 32-bit integer in this buffer in big-endian order.
+ *
+ * @param i the 32-bit integer.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.putInt32 = function(i) {
+  return this.putBytes(
+    String.fromCharCode(i >> 24 & 0xFF) +
+    String.fromCharCode(i >> 16 & 0xFF) +
+    String.fromCharCode(i >> 8 & 0xFF) +
+    String.fromCharCode(i & 0xFF));
+};
+
+/**
+ * Puts a 16-bit integer in this buffer in little-endian order.
+ *
+ * @param i the 16-bit integer.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.putInt16Le = function(i) {
+  return this.putBytes(
+    String.fromCharCode(i & 0xFF) +
+    String.fromCharCode(i >> 8 & 0xFF));
+};
+
+/**
+ * Puts a 24-bit integer in this buffer in little-endian order.
+ *
+ * @param i the 24-bit integer.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.putInt24Le = function(i) {
+  return this.putBytes(
+    String.fromCharCode(i & 0xFF) +
+    String.fromCharCode(i >> 8 & 0xFF) +
+    String.fromCharCode(i >> 16 & 0xFF));
+};
+
+/**
+ * Puts a 32-bit integer in this buffer in little-endian order.
+ *
+ * @param i the 32-bit integer.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.putInt32Le = function(i) {
+  return this.putBytes(
+    String.fromCharCode(i & 0xFF) +
+    String.fromCharCode(i >> 8 & 0xFF) +
+    String.fromCharCode(i >> 16 & 0xFF) +
+    String.fromCharCode(i >> 24 & 0xFF));
+};
+
+/**
+ * Puts an n-bit integer in this buffer in big-endian order.
+ *
+ * @param i the n-bit integer.
+ * @param n the number of bits in the integer.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.putInt = function(i, n) {
+  var bytes = '';
+  do {
+    n -= 8;
+    bytes += String.fromCharCode((i >> n) & 0xFF);
+  } while(n > 0);
+  return this.putBytes(bytes);
+};
+
+/**
+ * Puts a signed n-bit integer in this buffer in big-endian order. Two's
+ * complement representation is used.
+ *
+ * @param i the n-bit integer.
+ * @param n the number of bits in the integer.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.putSignedInt = function(i, n) {
+  if(i < 0) {
+    i += 2 << (n - 1);
+  }
+  return this.putInt(i, n);
+};
+
+/**
+ * Puts the given buffer into this buffer.
+ *
+ * @param buffer the buffer to put into this one.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.putBuffer = function(buffer) {
+  return this.putBytes(buffer.getBytes());
+};
+
+/**
+ * Gets a byte from this buffer and advances the read pointer by 1.
+ *
+ * @return the byte.
+ */
+util.ByteStringBuffer.prototype.getByte = function() {
+  return this.data.charCodeAt(this.read++);
+};
+
+/**
+ * Gets a uint16 from this buffer in big-endian order and advances the read
+ * pointer by 2.
+ *
+ * @return the uint16.
+ */
+util.ByteStringBuffer.prototype.getInt16 = function() {
+  var rval = (
+    this.data.charCodeAt(this.read) << 8 ^
+    this.data.charCodeAt(this.read + 1));
+  this.read += 2;
+  return rval;
+};
+
+/**
+ * Gets a uint24 from this buffer in big-endian order and advances the read
+ * pointer by 3.
+ *
+ * @return the uint24.
+ */
+util.ByteStringBuffer.prototype.getInt24 = function() {
+  var rval = (
+    this.data.charCodeAt(this.read) << 16 ^
+    this.data.charCodeAt(this.read + 1) << 8 ^
+    this.data.charCodeAt(this.read + 2));
+  this.read += 3;
+  return rval;
+};
+
+/**
+ * Gets a uint32 from this buffer in big-endian order and advances the read
+ * pointer by 4.
+ *
+ * @return the word.
+ */
+util.ByteStringBuffer.prototype.getInt32 = function() {
+  var rval = (
+    this.data.charCodeAt(this.read) << 24 ^
+    this.data.charCodeAt(this.read + 1) << 16 ^
+    this.data.charCodeAt(this.read + 2) << 8 ^
+    this.data.charCodeAt(this.read + 3));
+  this.read += 4;
+  return rval;
+};
+
+/**
+ * Gets a uint16 from this buffer in little-endian order and advances the read
+ * pointer by 2.
+ *
+ * @return the uint16.
+ */
+util.ByteStringBuffer.prototype.getInt16Le = function() {
+  var rval = (
+    this.data.charCodeAt(this.read) ^
+    this.data.charCodeAt(this.read + 1) << 8);
+  this.read += 2;
+  return rval;
+};
+
+/**
+ * Gets a uint24 from this buffer in little-endian order and advances the read
+ * pointer by 3.
+ *
+ * @return the uint24.
+ */
+util.ByteStringBuffer.prototype.getInt24Le = function() {
+  var rval = (
+    this.data.charCodeAt(this.read) ^
+    this.data.charCodeAt(this.read + 1) << 8 ^
+    this.data.charCodeAt(this.read + 2) << 16);
+  this.read += 3;
+  return rval;
+};
+
+/**
+ * Gets a uint32 from this buffer in little-endian order and advances the read
+ * pointer by 4.
+ *
+ * @return the word.
+ */
+util.ByteStringBuffer.prototype.getInt32Le = function() {
+  var rval = (
+    this.data.charCodeAt(this.read) ^
+    this.data.charCodeAt(this.read + 1) << 8 ^
+    this.data.charCodeAt(this.read + 2) << 16 ^
+    this.data.charCodeAt(this.read + 3) << 24);
+  this.read += 4;
+  return rval;
+};
+
+/**
+ * Gets an n-bit integer from this buffer in big-endian order and advances the
+ * read pointer by n/8.
+ *
+ * @param n the number of bits in the integer.
+ *
+ * @return the integer.
+ */
+util.ByteStringBuffer.prototype.getInt = function(n) {
+  var rval = 0;
+  do {
+    rval = (rval << 8) + this.data.charCodeAt(this.read++);
+    n -= 8;
+  } while(n > 0);
+  return rval;
+};
+
+/**
+ * Gets a signed n-bit integer from this buffer in big-endian order, using
+ * two's complement, and advances the read pointer by n/8.
+ *
+ * @param n the number of bits in the integer.
+ *
+ * @return the integer.
+ */
+util.ByteStringBuffer.prototype.getSignedInt = function(n) {
+  var x = this.getInt(n);
+  var max = 2 << (n - 2);
+  if(x >= max) {
+    x -= max << 1;
+  }
+  return x;
+};
+
+/**
+ * Reads bytes out into a UTF-8 string and clears them from the buffer.
+ *
+ * @param count the number of bytes to read, undefined or null for all.
+ *
+ * @return a UTF-8 string of bytes.
+ */
+util.ByteStringBuffer.prototype.getBytes = function(count) {
+  var rval;
+  if(count) {
+    // read count bytes
+    count = Math.min(this.length(), count);
+    rval = this.data.slice(this.read, this.read + count);
+    this.read += count;
+  } else if(count === 0) {
+    rval = '';
+  } else {
+    // read all bytes, optimize to only copy when needed
+    rval = (this.read === 0) ? this.data : this.data.slice(this.read);
+    this.clear();
+  }
+  return rval;
+};
+
+/**
+ * Gets a UTF-8 encoded string of the bytes from this buffer without modifying
+ * the read pointer.
+ *
+ * @param count the number of bytes to get, omit to get all.
+ *
+ * @return a string full of UTF-8 encoded characters.
+ */
+util.ByteStringBuffer.prototype.bytes = function(count) {
+  return (typeof(count) === 'undefined' ?
+    this.data.slice(this.read) :
+    this.data.slice(this.read, this.read + count));
+};
+
+/**
+ * Gets a byte at the given index without modifying the read pointer.
+ *
+ * @param i the byte index.
+ *
+ * @return the byte.
+ */
+util.ByteStringBuffer.prototype.at = function(i) {
+  return this.data.charCodeAt(this.read + i);
+};
+
+/**
+ * Puts a byte at the given index without modifying the read pointer.
+ *
+ * @param i the byte index.
+ * @param b the byte to put.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.setAt = function(i, b) {
+  this.data = this.data.substr(0, this.read + i) +
+    String.fromCharCode(b) +
+    this.data.substr(this.read + i + 1);
+  return this;
+};
+
+/**
+ * Gets the last byte without modifying the read pointer.
+ *
+ * @return the last byte.
+ */
+util.ByteStringBuffer.prototype.last = function() {
+  return this.data.charCodeAt(this.data.length - 1);
+};
+
+/**
+ * Creates a copy of this buffer.
+ *
+ * @return the copy.
+ */
+util.ByteStringBuffer.prototype.copy = function() {
+  var c = util.createBuffer(this.data);
+  c.read = this.read;
+  return c;
+};
+
+/**
+ * Compacts this buffer.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.compact = function() {
+  if(this.read > 0) {
+    this.data = this.data.slice(this.read);
+    this.read = 0;
+  }
+  return this;
+};
+
+/**
+ * Clears this buffer.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.clear = function() {
+  this.data = '';
+  this.read = 0;
+  return this;
+};
+
+/**
+ * Shortens this buffer by triming bytes off of the end of this buffer.
+ *
+ * @param count the number of bytes to trim off.
+ *
+ * @return this buffer.
+ */
+util.ByteStringBuffer.prototype.truncate = function(count) {
+  var len = Math.max(0, this.length() - count);
+  this.data = this.data.substr(this.read, len);
+  this.read = 0;
+  return this;
+};
+
+/**
+ * Converts this buffer to a hexadecimal string.
+ *
+ * @return a hexadecimal string.
+ */
+util.ByteStringBuffer.prototype.toHex = function() {
+  var rval = '';
+  for(var i = this.read; i < this.data.length; ++i) {
+    var b = this.data.charCodeAt(i);
+    if(b < 16) {
+      rval += '0';
+    }
+    rval += b.toString(16);
+  }
+  return rval;
+};
+
+/**
+ * Converts this buffer to a UTF-16 string (standard JavaScript string).
+ *
+ * @return a UTF-16 string.
+ */
+util.ByteStringBuffer.prototype.toString = function() {
+  return util.decodeUtf8(this.bytes());
+};
+
+/** End Buffer w/BinaryString backing */
+
+
+/** Buffer w/UInt8Array backing */
+
+/**
+ * FIXME: Experimental. Do not use yet.
+ *
+ * Constructor for an ArrayBuffer-backed byte buffer.
+ *
+ * The buffer may be constructed from a string, an ArrayBuffer, DataView, or a
+ * TypedArray.
+ *
+ * If a string is given, its encoding should be provided as an option,
+ * otherwise it will default to 'binary'. A 'binary' string is encoded such
+ * that each character is one byte in length and size.
+ *
+ * If an ArrayBuffer, DataView, or TypedArray is given, it will be used
+ * *directly* without any copying. Note that, if a write to the buffer requires
+ * more space, the buffer will allocate a new backing ArrayBuffer to
+ * accommodate. The starting read and write offsets for the buffer may be
+ * given as options.
+ *
+ * @param [b] the initial bytes for this buffer.
+ * @param options the options to use:
+ *          [readOffset] the starting read offset to use (default: 0).
+ *          [writeOffset] the starting write offset to use (default: the
+ *            length of the first parameter).
+ *          [growSize] the minimum amount, in bytes, to grow the buffer by to
+ *            accommodate writes (default: 1024).
+ *          [encoding] the encoding ('binary', 'utf8', 'utf16', 'hex') for the
+ *            first parameter, if it is a string (default: 'binary').
+ */
+function DataBuffer(b, options) {
+  // default options
+  options = options || {};
+
+  // pointers for read from/write to buffer
+  this.read = options.readOffset || 0;
+  this.growSize = options.growSize || 1024;
+
+  var isArrayBuffer = util.isArrayBuffer(b);
+  var isArrayBufferView = util.isArrayBufferView(b);
+  if(isArrayBuffer || isArrayBufferView) {
+    // use ArrayBuffer directly
+    if(isArrayBuffer) {
+      this.data = new DataView(b);
+    } else {
+      // TODO: adjust read/write offset based on the type of view
+      // or specify that this must be done in the options ... that the
+      // offsets are byte-based
+      this.data = new DataView(b.buffer, b.byteOffset, b.byteLength);
+    }
+    this.write = ('writeOffset' in options ?
+      options.writeOffset : this.data.byteLength);
+    return;
+  }
+
+  // initialize to empty array buffer and add any given bytes using putBytes
+  this.data = new DataView(new ArrayBuffer(0));
+  this.write = 0;
+
+  if(b !== null && b !== undefined) {
+    this.putBytes(b);
+  }
+
+  if('writeOffset' in options) {
+    this.write = options.writeOffset;
+  }
+}
+util.DataBuffer = DataBuffer;
+
+/**
+ * Gets the number of bytes in this buffer.
+ *
+ * @return the number of bytes in this buffer.
+ */
+util.DataBuffer.prototype.length = function() {
+  return this.write - this.read;
+};
+
+/**
+ * Gets whether or not this buffer is empty.
+ *
+ * @return true if this buffer is empty, false if not.
+ */
+util.DataBuffer.prototype.isEmpty = function() {
+  return this.length() <= 0;
+};
+
+/**
+ * Ensures this buffer has enough empty space to accommodate the given number
+ * of bytes. An optional parameter may be given that indicates a minimum
+ * amount to grow the buffer if necessary. If the parameter is not given,
+ * the buffer will be grown by some previously-specified default amount
+ * or heuristic.
+ *
+ * @param amount the number of bytes to accommodate.
+ * @param [growSize] the minimum amount, in bytes, to grow the buffer by if
+ *          necessary.
+ */
+util.DataBuffer.prototype.accommodate = function(amount, growSize) {
+  if(this.length() >= amount) {
+    return this;
+  }
+  growSize = Math.max(growSize || this.growSize, amount);
+
+  // grow buffer
+  var src = new Uint8Array(
+    this.data.buffer, this.data.byteOffset, this.data.byteLength);
+  var dst = new Uint8Array(this.length() + growSize);
+  dst.set(src);
+  this.data = new DataView(dst.buffer);
+
+  return this;
+};
+
+/**
+ * Puts a byte in this buffer.
+ *
+ * @param b the byte to put.
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.putByte = function(b) {
+  this.accommodate(1);
+  this.data.setUint8(this.write++, b);
+  return this;
+};
+
+/**
+ * Puts a byte in this buffer N times.
+ *
+ * @param b the byte to put.
+ * @param n the number of bytes of value b to put.
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.fillWithByte = function(b, n) {
+  this.accommodate(n);
+  for(var i = 0; i < n; ++i) {
+    this.data.setUint8(b);
+  }
+  return this;
+};
+
+/**
+ * Puts bytes in this buffer. The bytes may be given as a string, an
+ * ArrayBuffer, a DataView, or a TypedArray.
+ *
+ * @param bytes the bytes to put.
+ * @param [encoding] the encoding for the first parameter ('binary', 'utf8',
+ *          'utf16', 'hex'), if it is a string (default: 'binary').
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.putBytes = function(bytes, encoding) {
+  if(util.isArrayBufferView(bytes)) {
+    var src = new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength);
+    var len = src.byteLength - src.byteOffset;
+    this.accommodate(len);
+    var dst = new Uint8Array(this.data.buffer, this.write);
+    dst.set(src);
+    this.write += len;
+    return this;
+  }
+
+  if(util.isArrayBuffer(bytes)) {
+    var src = new Uint8Array(bytes);
+    this.accommodate(src.byteLength);
+    var dst = new Uint8Array(this.data.buffer);
+    dst.set(src, this.write);
+    this.write += src.byteLength;
+    return this;
+  }
+
+  // bytes is a util.DataBuffer or equivalent
+  if(bytes instanceof util.DataBuffer ||
+    (typeof bytes === 'object' &&
+    typeof bytes.read === 'number' && typeof bytes.write === 'number' &&
+    util.isArrayBufferView(bytes.data))) {
+    var src = new Uint8Array(bytes.data.byteLength, bytes.read, bytes.length());
+    this.accommodate(src.byteLength);
+    var dst = new Uint8Array(bytes.data.byteLength, this.write);
+    dst.set(src);
+    this.write += src.byteLength;
+    return this;
+  }
+
+  if(bytes instanceof util.ByteStringBuffer) {
+    // copy binary string and process as the same as a string parameter below
+    bytes = bytes.data;
+    encoding = 'binary';
+  }
+
+  // string conversion
+  encoding = encoding || 'binary';
+  if(typeof bytes === 'string') {
+    var view;
+
+    // decode from string
+    if(encoding === 'hex') {
+      this.accommodate(Math.ceil(bytes.length / 2));
+      view = new Uint8Array(this.data.buffer, this.write);
+      this.write += util.binary.hex.decode(bytes, view, this.write);
+      return this;
+    }
+    if(encoding === 'base64') {
+      this.accommodate(Math.ceil(bytes.length / 4) * 3);
+      view = new Uint8Array(this.data.buffer, this.write);
+      this.write += util.binary.base64.decode(bytes, view, this.write);
+      return this;
+    }
+
+    // encode text as UTF-8 bytes
+    if(encoding === 'utf8') {
+      // encode as UTF-8 then decode string as raw binary
+      bytes = util.encodeUtf8(bytes);
+      encoding = 'binary';
+    }
+
+    // decode string as raw binary
+    if(encoding === 'binary' || encoding === 'raw') {
+      // one byte per character
+      this.accommodate(bytes.length);
+      view = new Uint8Array(this.data.buffer, this.write);
+      this.write += util.binary.raw.decode(view);
+      return this;
+    }
+
+    // encode text as UTF-16 bytes
+    if(encoding === 'utf16') {
+      // two bytes per character
+      this.accommodate(bytes.length * 2);
+      view = new Uint16Array(this.data.buffer, this.write);
+      this.write += util.text.utf16.encode(view);
+      return this;
+    }
+
+    throw new Error('Invalid encoding: ' + encoding);
+  }
+
+  throw Error('Invalid parameter: ' + bytes);
+};
+
+/**
+ * Puts the given buffer into this buffer.
+ *
+ * @param buffer the buffer to put into this one.
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.putBuffer = function(buffer) {
+  this.putBytes(buffer);
+  buffer.clear();
+  return this;
+};
+
+/**
+ * Puts a string into this buffer.
+ *
+ * @param str the string to put.
+ * @param [encoding] the encoding for the string (default: 'utf16').
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.putString = function(str) {
+  return this.putBytes(str, 'utf16');
+};
+
+/**
+ * Puts a 16-bit integer in this buffer in big-endian order.
+ *
+ * @param i the 16-bit integer.
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.putInt16 = function(i) {
+  this.accommodate(2);
+  this.data.setInt16(this.write, i);
+  this.write += 2;
+  return this;
+};
+
+/**
+ * Puts a 24-bit integer in this buffer in big-endian order.
+ *
+ * @param i the 24-bit integer.
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.putInt24 = function(i) {
+  this.accommodate(3);
+  this.data.setInt16(this.write, i >> 8 & 0xFFFF);
+  this.data.setInt8(this.write, i >> 16 & 0xFF);
+  this.write += 3;
+  return this;
+};
+
+/**
+ * Puts a 32-bit integer in this buffer in big-endian order.
+ *
+ * @param i the 32-bit integer.
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.putInt32 = function(i) {
+  this.accommodate(4);
+  this.data.setInt32(this.write, i);
+  this.write += 4;
+  return this;
+};
+
+/**
+ * Puts a 16-bit integer in this buffer in little-endian order.
+ *
+ * @param i the 16-bit integer.
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.putInt16Le = function(i) {
+  this.accommodate(2);
+  this.data.setInt16(this.write, i, true);
+  this.write += 2;
+  return this;
+};
+
+/**
+ * Puts a 24-bit integer in this buffer in little-endian order.
+ *
+ * @param i the 24-bit integer.
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.putInt24Le = function(i) {
+  this.accommodate(3);
+  this.data.setInt8(this.write, i >> 16 & 0xFF);
+  this.data.setInt16(this.write, i >> 8 & 0xFFFF, true);
+  this.write += 3;
+  return this;
+};
+
+/**
+ * Puts a 32-bit integer in this buffer in little-endian order.
+ *
+ * @param i the 32-bit integer.
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.putInt32Le = function(i) {
+  this.accommodate(4);
+  this.data.setInt32(this.write, i, true);
+  this.write += 4;
+  return this;
+};
+
+/**
+ * Puts an n-bit integer in this buffer in big-endian order.
+ *
+ * @param i the n-bit integer.
+ * @param n the number of bits in the integer.
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.putInt = function(i, n) {
+  this.accommodate(n / 8);
+  do {
+    n -= 8;
+    this.data.setInt8(this.write++, (i >> n) & 0xFF);
+  } while(n > 0);
+  return this;
+};
+
+/**
+ * Puts a signed n-bit integer in this buffer in big-endian order. Two's
+ * complement representation is used.
+ *
+ * @param i the n-bit integer.
+ * @param n the number of bits in the integer.
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.putSignedInt = function(i, n) {
+  this.accommodate(n / 8);
+  if(i < 0) {
+    i += 2 << (n - 1);
+  }
+  return this.putInt(i, n);
+};
+
+/**
+ * Gets a byte from this buffer and advances the read pointer by 1.
+ *
+ * @return the byte.
+ */
+util.DataBuffer.prototype.getByte = function() {
+  return this.data.getInt8(this.read++);
+};
+
+/**
+ * Gets a uint16 from this buffer in big-endian order and advances the read
+ * pointer by 2.
+ *
+ * @return the uint16.
+ */
+util.DataBuffer.prototype.getInt16 = function() {
+  var rval = this.data.getInt16(this.read);
+  this.read += 2;
+  return rval;
+};
+
+/**
+ * Gets a uint24 from this buffer in big-endian order and advances the read
+ * pointer by 3.
+ *
+ * @return the uint24.
+ */
+util.DataBuffer.prototype.getInt24 = function() {
+  var rval = (
+    this.data.getInt16(this.read) << 8 ^
+    this.data.getInt8(this.read + 2));
+  this.read += 3;
+  return rval;
+};
+
+/**
+ * Gets a uint32 from this buffer in big-endian order and advances the read
+ * pointer by 4.
+ *
+ * @return the word.
+ */
+util.DataBuffer.prototype.getInt32 = function() {
+  var rval = this.data.getInt32(this.read);
+  this.read += 4;
+  return rval;
+};
+
+/**
+ * Gets a uint16 from this buffer in little-endian order and advances the read
+ * pointer by 2.
+ *
+ * @return the uint16.
+ */
+util.DataBuffer.prototype.getInt16Le = function() {
+  var rval = this.data.getInt16(this.read, true);
+  this.read += 2;
+  return rval;
+};
+
+/**
+ * Gets a uint24 from this buffer in little-endian order and advances the read
+ * pointer by 3.
+ *
+ * @return the uint24.
+ */
+util.DataBuffer.prototype.getInt24Le = function() {
+  var rval = (
+    this.data.getInt8(this.read) ^
+    this.data.getInt16(this.read + 1, true) << 8);
+  this.read += 3;
+  return rval;
+};
+
+/**
+ * Gets a uint32 from this buffer in little-endian order and advances the read
+ * pointer by 4.
+ *
+ * @return the word.
+ */
+util.DataBuffer.prototype.getInt32Le = function() {
+  var rval = this.data.getInt32(this.read, true);
+  this.read += 4;
+  return rval;
+};
+
+/**
+ * Gets an n-bit integer from this buffer in big-endian order and advances the
+ * read pointer by n/8.
+ *
+ * @param n the number of bits in the integer.
+ *
+ * @return the integer.
+ */
+util.DataBuffer.prototype.getInt = function(n) {
+  var rval = 0;
+  do {
+    rval = (rval << 8) + this.data.getInt8(this.read++);
+    n -= 8;
+  } while(n > 0);
+  return rval;
+};
+
+/**
+ * Gets a signed n-bit integer from this buffer in big-endian order, using
+ * two's complement, and advances the read pointer by n/8.
+ *
+ * @param n the number of bits in the integer.
+ *
+ * @return the integer.
+ */
+util.DataBuffer.prototype.getSignedInt = function(n) {
+  var x = this.getInt(n);
+  var max = 2 << (n - 2);
+  if(x >= max) {
+    x -= max << 1;
+  }
+  return x;
+};
+
+/**
+ * Reads bytes out into a UTF-8 string and clears them from the buffer.
+ *
+ * @param count the number of bytes to read, undefined or null for all.
+ *
+ * @return a UTF-8 string of bytes.
+ */
+util.DataBuffer.prototype.getBytes = function(count) {
+  // TODO: deprecate this method, it is poorly named and
+  // this.toString('binary') replaces it
+  // add a toTypedArray()/toArrayBuffer() function
+  var rval;
+  if(count) {
+    // read count bytes
+    count = Math.min(this.length(), count);
+    rval = this.data.slice(this.read, this.read + count);
+    this.read += count;
+  } else if(count === 0) {
+    rval = '';
+  } else {
+    // read all bytes, optimize to only copy when needed
+    rval = (this.read === 0) ? this.data : this.data.slice(this.read);
+    this.clear();
+  }
+  return rval;
+};
+
+/**
+ * Gets a UTF-8 encoded string of the bytes from this buffer without modifying
+ * the read pointer.
+ *
+ * @param count the number of bytes to get, omit to get all.
+ *
+ * @return a string full of UTF-8 encoded characters.
+ */
+util.DataBuffer.prototype.bytes = function(count) {
+  // TODO: deprecate this method, it is poorly named, add "getString()"
+  return (typeof(count) === 'undefined' ?
+    this.data.slice(this.read) :
+    this.data.slice(this.read, this.read + count));
+};
+
+/**
+ * Gets a byte at the given index without modifying the read pointer.
+ *
+ * @param i the byte index.
+ *
+ * @return the byte.
+ */
+util.DataBuffer.prototype.at = function(i) {
+  return this.data.getUint8(this.read + i);
+};
+
+/**
+ * Puts a byte at the given index without modifying the read pointer.
+ *
+ * @param i the byte index.
+ * @param b the byte to put.
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.setAt = function(i, b) {
+  this.data.setUint8(i, b);
+  return this;
+};
+
+/**
+ * Gets the last byte without modifying the read pointer.
+ *
+ * @return the last byte.
+ */
+util.DataBuffer.prototype.last = function() {
+  return this.data.getUint8(this.write - 1);
+};
+
+/**
+ * Creates a copy of this buffer.
+ *
+ * @return the copy.
+ */
+util.DataBuffer.prototype.copy = function() {
+  return new util.DataBuffer(this);
+};
+
+/**
+ * Compacts this buffer.
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.compact = function() {
+  if(this.read > 0) {
+    var src = new Uint8Array(this.data.buffer, this.read);
+    var dst = new Uint8Array(src.byteLength);
+    dst.set(src);
+    this.data = new DataView(dst);
+    this.write -= this.read;
+    this.read = 0;
+  }
+  return this;
+};
+
+/**
+ * Clears this buffer.
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.clear = function() {
+  this.data = new DataView(new ArrayBuffer(0));
+  this.read = this.write = 0;
+  return this;
+};
+
+/**
+ * Shortens this buffer by triming bytes off of the end of this buffer.
+ *
+ * @param count the number of bytes to trim off.
+ *
+ * @return this buffer.
+ */
+util.DataBuffer.prototype.truncate = function(count) {
+  this.write = Math.max(0, this.length() - count);
+  this.read = Math.min(this.read, this.write);
+  return this;
+};
+
+/**
+ * Converts this buffer to a hexadecimal string.
+ *
+ * @return a hexadecimal string.
+ */
+util.DataBuffer.prototype.toHex = function() {
+  var rval = '';
+  for(var i = this.read; i < this.data.byteLength; ++i) {
+    var b = this.data.getUint8(i);
+    if(b < 16) {
+      rval += '0';
+    }
+    rval += b.toString(16);
+  }
+  return rval;
+};
+
+/**
+ * Converts this buffer to a string, using the given encoding. If no
+ * encoding is given, 'utf8' (UTF-8) is used.
+ *
+ * @param [encoding] the encoding to use: 'binary', 'utf8', 'utf16', 'hex',
+ *          'base64' (default: 'utf8').
+ *
+ * @return a string representation of the bytes in this buffer.
+ */
+util.DataBuffer.prototype.toString = function(encoding) {
+  var view = new Uint8Array(this.data, this.read, this.length());
+  encoding = encoding || 'utf8';
+
+  // encode to string
+  if(encoding === 'binary' || encoding === 'raw') {
+    return util.binary.raw.encode(view);
+  }
+  if(encoding === 'hex') {
+    return util.binary.hex.encode(view);
+  }
+  if(encoding === 'base64') {
+    return util.binary.base64.encode(view);
+  }
+
+  // decode to text
+  if(encoding === 'utf8') {
+    return util.text.utf8.decode(view);
+  }
+  if(encoding === 'utf16') {
+    return util.text.utf16.decode(view);
+  }
+
+  throw new Error('Invalid encoding: ' + encoding);
+};
+
+/** End Buffer w/UInt8Array backing */
+
+
+/**
+ * Creates a buffer that stores bytes. A value may be given to put into the
+ * buffer that is either a string of bytes or a UTF-16 string that will
+ * be encoded using UTF-8 (to do the latter, specify 'utf8' as the encoding).
+ *
+ * @param [input] the bytes to wrap (as a string) or a UTF-16 string to encode
+ *          as UTF-8.
+ * @param [encoding] (default: 'raw', other: 'utf8').
+ */
+util.createBuffer = function(input, encoding) {
+  // TODO: deprecate, use new ByteBuffer() instead
+  encoding = encoding || 'raw';
+  if(input !== undefined && encoding === 'utf8') {
+    input = util.encodeUtf8(input);
+  }
+  return new util.ByteBuffer(input);
+};
+
+/**
+ * Fills a string with a particular value. If you want the string to be a byte
+ * string, pass in String.fromCharCode(theByte).
+ *
+ * @param c the character to fill the string with, use String.fromCharCode
+ *          to fill the string with a byte value.
+ * @param n the number of characters of value c to fill with.
+ *
+ * @return the filled string.
+ */
+util.fillString = function(c, n) {
+  var s = '';
+  while(n > 0) {
+    if(n & 1) {
+      s += c;
+    }
+    n >>>= 1;
+    if(n > 0) {
+      c += c;
+    }
+  }
+  return s;
+};
+
+/**
+ * Performs a per byte XOR between two byte strings and returns the result as a
+ * string of bytes.
+ *
+ * @param s1 first string of bytes.
+ * @param s2 second string of bytes.
+ * @param n the number of bytes to XOR.
+ *
+ * @return the XOR'd result.
+ */
+util.xorBytes = function(s1, s2, n) {
+  var s3 = '';
+  var b = '';
+  var t = '';
+  var i = 0;
+  var c = 0;
+  for(; n > 0; --n, ++i) {
+    b = s1.charCodeAt(i) ^ s2.charCodeAt(i);
+    if(c >= 10) {
+      s3 += t;
+      t = '';
+      c = 0;
+    }
+    t += String.fromCharCode(b);
+    ++c;
+  }
+  s3 += t;
+  return s3;
+};
+
+/**
+ * Converts a hex string into a 'binary' encoded string of bytes.
+ *
+ * @param hex the hexadecimal string to convert.
+ *
+ * @return the binary-encoded string of bytes.
+ */
+util.hexToBytes = function(hex) {
+  // TODO: deprecate: "Deprecated. Use util.binary.hex.decode instead."
+  var rval = '';
+  var i = 0;
+  if(hex.length & 1 == 1) {
+    // odd number of characters, convert first character alone
+    i = 1;
+    rval += String.fromCharCode(parseInt(hex[0], 16));
+  }
+  // convert 2 characters (1 byte) at a time
+  for(; i < hex.length; i += 2) {
+    rval += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
+  }
+  return rval;
+};
+
+/**
+ * Converts a 'binary' encoded string of bytes to hex.
+ *
+ * @param bytes the byte string to convert.
+ *
+ * @return the string of hexadecimal characters.
+ */
+util.bytesToHex = function(bytes) {
+  // TODO: deprecate: "Deprecated. Use util.binary.hex.encode instead."
+  return util.createBuffer(bytes).toHex();
+};
+
+/**
+ * Converts an 32-bit integer to 4-big-endian byte string.
+ *
+ * @param i the integer.
+ *
+ * @return the byte string.
+ */
+util.int32ToBytes = function(i) {
+  return (
+    String.fromCharCode(i >> 24 & 0xFF) +
+    String.fromCharCode(i >> 16 & 0xFF) +
+    String.fromCharCode(i >> 8 & 0xFF) +
+    String.fromCharCode(i & 0xFF));
+};
+
+// base64 characters, reverse mapping
+var _base64 =
+  'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
+var _base64Idx = [
+/*43 -43 = 0*/
+/*'+',  1,  2,  3,'/' */
+   62, -1, -1, -1, 63,
+
+/*'0','1','2','3','4','5','6','7','8','9' */
+   52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
+
+/*15, 16, 17,'=', 19, 20, 21 */
+  -1, -1, -1, 64, -1, -1, -1,
+
+/*65 - 43 = 22*/
+/*'A','B','C','D','E','F','G','H','I','J','K','L','M', */
+   0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12,
+
+/*'N','O','P','Q','R','S','T','U','V','W','X','Y','Z' */
+   13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
+
+/*91 - 43 = 48 */
+/*48, 49, 50, 51, 52, 53 */
+  -1, -1, -1, -1, -1, -1,
+
+/*97 - 43 = 54*/
+/*'a','b','c','d','e','f','g','h','i','j','k','l','m' */
+   26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
+
+/*'n','o','p','q','r','s','t','u','v','w','x','y','z' */
+   39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
+];
+
+/**
+ * Base64 encodes a 'binary' encoded string of bytes.
+ *
+ * @param input the binary encoded string of bytes to base64-encode.
+ * @param maxline the maximum number of encoded characters per line to use,
+ *          defaults to none.
+ *
+ * @return the base64-encoded output.
+ */
+util.encode64 = function(input, maxline) {
+  // TODO: deprecate: "Deprecated. Use util.binary.base64.encode instead."
+  var line = '';
+  var output = '';
+  var chr1, chr2, chr3;
+  var i = 0;
+  while(i < input.length) {
+    chr1 = input.charCodeAt(i++);
+    chr2 = input.charCodeAt(i++);
+    chr3 = input.charCodeAt(i++);
+
+    // encode 4 character group
+    line += _base64.charAt(chr1 >> 2);
+    line += _base64.charAt(((chr1 & 3) << 4) | (chr2 >> 4));
+    if(isNaN(chr2)) {
+      line += '==';
+    } else {
+      line += _base64.charAt(((chr2 & 15) << 2) | (chr3 >> 6));
+      line += isNaN(chr3) ? '=' : _base64.charAt(chr3 & 63);
+    }
+
+    if(maxline && line.length > maxline) {
+      output += line.substr(0, maxline) + '\r\n';
+      line = line.substr(maxline);
+    }
+  }
+  output += line;
+  return output;
+};
+
+/**
+ * Base64 decodes a string into a 'binary' encoded string of bytes.
+ *
+ * @param input the base64-encoded input.
+ *
+ * @return the binary encoded string.
+ */
+util.decode64 = function(input) {
+  // TODO: deprecate: "Deprecated. Use util.binary.base64.decode instead."
+
+  // remove all non-base64 characters
+  input = input.replace(/[^A-Za-z0-9\+\/\=]/g, '');
+
+  var output = '';
+  var enc1, enc2, enc3, enc4;
+  var i = 0;
+
+  while(i < input.length) {
+    enc1 = _base64Idx[input.charCodeAt(i++) - 43];
+    enc2 = _base64Idx[input.charCodeAt(i++) - 43];
+    enc3 = _base64Idx[input.charCodeAt(i++) - 43];
+    enc4 = _base64Idx[input.charCodeAt(i++) - 43];
+
+    output += String.fromCharCode((enc1 << 2) | (enc2 >> 4));
+    if(enc3 !== 64) {
+      // decoded at least 2 bytes
+      output += String.fromCharCode(((enc2 & 15) << 4) | (enc3 >> 2));
+      if(enc4 !== 64) {
+        // decoded 3 bytes
+        output += String.fromCharCode(((enc3 & 3) << 6) | enc4);
+      }
+    }
+  }
+
+  return output;
+};
+
+/**
+ * UTF-8 encodes the given UTF-16 encoded string (a standard JavaScript
+ * string). Non-ASCII characters will be encoded as multiple bytes according
+ * to UTF-8.
+ *
+ * @param str the string to encode.
+ *
+ * @return the UTF-8 encoded string.
+ */
+util.encodeUtf8 = function(str) {
+  return unescape(encodeURIComponent(str));
+};
+
+/**
+ * Decodes a UTF-8 encoded string into a UTF-16 string.
+ *
+ * @param str the string to decode.
+ *
+ * @return the UTF-16 encoded string (standard JavaScript string).
+ */
+util.decodeUtf8 = function(str) {
+  return decodeURIComponent(escape(str));
+};
+
+// binary encoding/decoding tools
+// FIXME: Experimental. Do not use yet.
+util.binary = {
+  raw: {},
+  hex: {},
+  base64: {}
+};
+
+/**
+ * Encodes a Uint8Array as a binary-encoded string. This encoding uses
+ * a value between 0 and 255 for each character.
+ *
+ * @param bytes the Uint8Array to encode.
+ *
+ * @return the binary-encoded string.
+ */
+util.binary.raw.encode = function(bytes) {
+  return String.fromCharCode.apply(null, bytes);
+};
+
+/**
+ * Decodes a binary-encoded string to a Uint8Array. This encoding uses
+ * a value between 0 and 255 for each character.
+ *
+ * @param str the binary-encoded string to decode.
+ * @param [output] an optional Uint8Array to write the output to; if it
+ *          is too small, an exception will be thrown.
+ * @param [offset] the start offset for writing to the output (default: 0).
+ *
+ * @return the Uint8Array or the number of bytes written if output was given.
+ */
+util.binary.raw.decode = function(str, output, offset) {
+  var out = output;
+  if(!out) {
+    out = new Uint8Array(str.length);
+  }
+  offset = offset || 0;
+  var j = offset;
+  for(var i = 0; i < str.length; ++i) {
+    out[j++] = str.charCodeAt(i);
+  }
+  return output ? (j - offset) : out;
+};
+
+/**
+ * Encodes a 'binary' string, ArrayBuffer, DataView, TypedArray, or
+ * ByteBuffer as a string of hexadecimal characters.
+ *
+ * @param bytes the bytes to convert.
+ *
+ * @return the string of hexadecimal characters.
+ */
+util.binary.hex.encode = util.bytesToHex;
+
+/**
+ * Decodes a hex-encoded string to a Uint8Array.
+ *
+ * @param hex the hexadecimal string to convert.
+ * @param [output] an optional Uint8Array to write the output to; if it
+ *          is too small, an exception will be thrown.
+ * @param [offset] the start offset for writing to the output (default: 0).
+ *
+ * @return the Uint8Array or the number of bytes written if output was given.
+ */
+util.binary.hex.decode = function(hex, output, offset) {
+  var out = output;
+  if(!out) {
+    out = new Uint8Array(Math.ceil(hex.length / 2));
+  }
+  offset = offset || 0;
+  var i = 0, j = offset;
+  if(hex.length & 1) {
+    // odd number of characters, convert first character alone
+    i = 1;
+    out[j++] = parseInt(hex[0], 16);
+  }
+  // convert 2 characters (1 byte) at a time
+  for(; i < hex.length; i += 2) {
+    out[j++] = parseInt(hex.substr(i, 2), 16);
+  }
+  return output ? (j - offset) : out;
+};
+
+/**
+ * Base64-encodes a Uint8Array.
+ *
+ * @param input the Uint8Array to encode.
+ * @param maxline the maximum number of encoded characters per line to use,
+ *          defaults to none.
+ *
+ * @return the base64-encoded output string.
+ */
+util.binary.base64.encode = function(input, maxline) {
+  var line = '';
+  var output = '';
+  var chr1, chr2, chr3;
+  var i = 0;
+  while(i < input.byteLength) {
+    chr1 = input[i++];
+    chr2 = input[i++];
+    chr3 = input[i++];
+
+    // encode 4 character group
+    line += _base64.charAt(chr1 >> 2);
+    line += _base64.charAt(((chr1 & 3) << 4) | (chr2 >> 4));
+    if(isNaN(chr2)) {
+      line += '==';
+    } else {
+      line += _base64.charAt(((chr2 & 15) << 2) | (chr3 >> 6));
+      line += isNaN(chr3) ? '=' : _base64.charAt(chr3 & 63);
+    }
+
+    if(maxline && line.length > maxline) {
+      output += line.substr(0, maxline) + '\r\n';
+      line = line.substr(maxline);
+    }
+  }
+  output += line;
+  return output;
+};
+
+/**
+ * Decodes a base64-encoded string to a Uint8Array.
+ *
+ * @param input the base64-encoded input string.
+ * @param [output] an optional Uint8Array to write the output to; if it
+ *          is too small, an exception will be thrown.
+ * @param [offset] the start offset for writing to the output (default: 0).
+ *
+ * @return the Uint8Array or the number of bytes written if output was given.
+ */
+util.binary.base64.decode = function(input, output, offset) {
+  var out = output;
+  if(!out) {
+    out = new Uint8Array(Math.ceil(input.length / 4) * 3);
+  }
+
+  // remove all non-base64 characters
+  input = input.replace(/[^A-Za-z0-9\+\/\=]/g, '');
+
+  offset = offset || 0;
+  var enc1, enc2, enc3, enc4;
+  var i = 0, j = offset;
+
+  while(i < input.length) {
+    enc1 = _base64Idx[input.charCodeAt(i++) - 43];
+    enc2 = _base64Idx[input.charCodeAt(i++) - 43];
+    enc3 = _base64Idx[input.charCodeAt(i++) - 43];
+    enc4 = _base64Idx[input.charCodeAt(i++) - 43];
+
+    out[j++] = (enc1 << 2) | (enc2 >> 4);
+    if(enc3 !== 64) {
+      // decoded at least 2 bytes
+      out[j++] = ((enc2 & 15) << 4) | (enc3 >> 2);
+      if(enc4 !== 64) {
+        // decoded 3 bytes
+        out[j++] = ((enc3 & 3) << 6) | enc4;
+      }
+    }
+  }
+  
+  // make sure result is the exact decoded length
+  return output ?
+         (j - offset) :
+         out.subarray(0, j);
+};
+
+// text encoding/decoding tools
+// FIXME: Experimental. Do not use yet.
+util.text = {
+  utf8: {},
+  utf16: {}
+};
+
+/**
+ * Encodes the given string as UTF-8 in a Uint8Array.
+ *
+ * @param str the string to encode.
+ * @param [output] an optional Uint8Array to write the output to; if it
+ *          is too small, an exception will be thrown.
+ * @param [offset] the start offset for writing to the output (default: 0).
+ *
+ * @return the Uint8Array or the number of bytes written if output was given.
+ */
+util.text.utf8.encode = function(str, output, offset) {
+  str = util.encodeUtf8(str);
+  var out = output;
+  if(!out) {
+    out = new Uint8Array(str.length);
+  }
+  offset = offset || 0;
+  var j = offset;
+  for(var i = 0; i < str.length; ++i) {
+    out[j++] = str.charCodeAt(i);
+  }
+  return output ? (j - offset) : out;
+};
+
+/**
+ * Decodes the UTF-8 contents from a Uint8Array.
+ *
+ * @param bytes the Uint8Array to decode.
+ *
+ * @return the resulting string.
+ */
+util.text.utf8.decode = function(bytes) {
+  return util.decodeUtf8(String.fromCharCode.apply(null, bytes));
+};
+
+/**
+ * Encodes the given string as UTF-16 in a Uint8Array.
+ *
+ * @param str the string to encode.
+ * @param [output] an optional Uint8Array to write the output to; if it
+ *          is too small, an exception will be thrown.
+ * @param [offset] the start offset for writing to the output (default: 0).
+ *
+ * @return the Uint8Array or the number of bytes written if output was given.
+ */
+util.text.utf16.encode = function(str, output, offset) {
+  var out = output;
+  if(!out) {
+    out = new Uint8Array(str.length);
+  }
+  var view = new Uint16Array(out);
+  offset = offset || 0;
+  var j = offset;
+  var k = offset;
+  for(var i = 0; i < str.length; ++i) {
+    view[k++] = str.charCodeAt(i);
+    j += 2;
+  }
+  return output ? (j - offset) : out;
+};
+
+/**
+ * Decodes the UTF-16 contents from a Uint8Array.
+ *
+ * @param bytes the Uint8Array to decode.
+ *
+ * @return the resulting string.
+ */
+util.text.utf16.decode = function(bytes) {
+  return String.fromCharCode.apply(null, new Uint16Array(bytes));
+};
+
+/**
+ * Deflates the given data using a flash interface.
+ *
+ * @param api the flash interface.
+ * @param bytes the data.
+ * @param raw true to return only raw deflate data, false to include zlib
+ *          header and trailer.
+ *
+ * @return the deflated data as a string.
+ */
+util.deflate = function(api, bytes, raw) {
+  bytes = util.decode64(api.deflate(util.encode64(bytes)).rval);
+
+  // strip zlib header and trailer if necessary
+  if(raw) {
+    // zlib header is 2 bytes (CMF,FLG) where FLG indicates that
+    // there is a 4-byte DICT (alder-32) block before the data if
+    // its 5th bit is set
+    var start = 2;
+    var flg = bytes.charCodeAt(1);
+    if(flg & 0x20) {
+      start = 6;
+    }
+    // zlib trailer is 4 bytes of adler-32
+    bytes = bytes.substring(start, bytes.length - 4);
+  }
+
+  return bytes;
+};
+
+/**
+ * Inflates the given data using a flash interface.
+ *
+ * @param api the flash interface.
+ * @param bytes the data.
+ * @param raw true if the incoming data has no zlib header or trailer and is
+ *          raw DEFLATE data.
+ *
+ * @return the inflated data as a string, null on error.
+ */
+util.inflate = function(api, bytes, raw) {
+  // TODO: add zlib header and trailer if necessary/possible
+  var rval = api.inflate(util.encode64(bytes)).rval;
+  return (rval === null) ? null : util.decode64(rval);
+};
+
+/**
+ * Sets a storage object.
+ *
+ * @param api the storage interface.
+ * @param id the storage ID to use.
+ * @param obj the storage object, null to remove.
+ */
+var _setStorageObject = function(api, id, obj) {
+  if(!api) {
+    throw new Error('WebStorage not available.');
+  }
+
+  var rval;
+  if(obj === null) {
+    rval = api.removeItem(id);
+  } else {
+    // json-encode and base64-encode object
+    obj = util.encode64(JSON.stringify(obj));
+    rval = api.setItem(id, obj);
+  }
+
+  // handle potential flash error
+  if(typeof(rval) !== 'undefined' && rval.rval !== true) {
+    var error = new Error(rval.error.message);
+    error.id = rval.error.id;
+    error.name = rval.error.name;
+    throw error;
+  }
+};
+
+/**
+ * Gets a storage object.
+ *
+ * @param api the storage interface.
+ * @param id the storage ID to use.
+ *
+ * @return the storage object entry or null if none exists.
+ */
+var _getStorageObject = function(api, id) {
+  if(!api) {
+    throw new Error('WebStorage not available.');
+  }
+
+  // get the existing entry
+  var rval = api.getItem(id);
+
+  /* Note: We check api.init because we can't do (api == localStorage)
+    on IE because of "Class doesn't support Automation" exception. Only
+    the flash api has an init method so this works too, but we need a
+    better solution in the future. */
+
+  // flash returns item wrapped in an object, handle special case
+  if(api.init) {
+    if(rval.rval === null) {
+      if(rval.error) {
+        var error = new Error(rval.error.message);
+        error.id = rval.error.id;
+        error.name = rval.error.name;
+        throw error;
+      }
+      // no error, but also no item
+      rval = null;
+    } else {
+      rval = rval.rval;
+    }
+  }
+
+  // handle decoding
+  if(rval !== null) {
+    // base64-decode and json-decode data
+    rval = JSON.parse(util.decode64(rval));
+  }
+
+  return rval;
+};
+
+/**
+ * Stores an item in local storage.
+ *
+ * @param api the storage interface.
+ * @param id the storage ID to use.
+ * @param key the key for the item.
+ * @param data the data for the item (any javascript object/primitive).
+ */
+var _setItem = function(api, id, key, data) {
+  // get storage object
+  var obj = _getStorageObject(api, id);
+  if(obj === null) {
+    // create a new storage object
+    obj = {};
+  }
+  // update key
+  obj[key] = data;
+
+  // set storage object
+  _setStorageObject(api, id, obj);
+};
+
+/**
+ * Gets an item from local storage.
+ *
+ * @param api the storage interface.
+ * @param id the storage ID to use.
+ * @param key the key for the item.
+ *
+ * @return the item.
+ */
+var _getItem = function(api, id, key) {
+  // get storage object
+  var rval = _getStorageObject(api, id);
+  if(rval !== null) {
+    // return data at key
+    rval = (key in rval) ? rval[key] : null;
+  }
+
+  return rval;
+};
+
+/**
+ * Removes an item from local storage.
+ *
+ * @param api the storage interface.
+ * @param id the storage ID to use.
+ * @param key the key for the item.
+ */
+var _removeItem = function(api, id, key) {
+  // get storage object
+  var obj = _getStorageObject(api, id);
+  if(obj !== null && key in obj) {
+    // remove key
+    delete obj[key];
+
+    // see if entry has no keys remaining
+    var empty = true;
+    for(var prop in obj) {
+      empty = false;
+      break;
+    }
+    if(empty) {
+      // remove entry entirely if no keys are left
+      obj = null;
+    }
+
+    // set storage object
+    _setStorageObject(api, id, obj);
+  }
+};
+
+/**
+ * Clears the local disk storage identified by the given ID.
+ *
+ * @param api the storage interface.
+ * @param id the storage ID to use.
+ */
+var _clearItems = function(api, id) {
+  _setStorageObject(api, id, null);
+};
+
+/**
+ * Calls a storage function.
+ *
+ * @param func the function to call.
+ * @param args the arguments for the function.
+ * @param location the location argument.
+ *
+ * @return the return value from the function.
+ */
+var _callStorageFunction = function(func, args, location) {
+  var rval = null;
+
+  // default storage types
+  if(typeof(location) === 'undefined') {
+    location = ['web', 'flash'];
+  }
+
+  // apply storage types in order of preference
+  var type;
+  var done = false;
+  var exception = null;
+  for(var idx in location) {
+    type = location[idx];
+    try {
+      if(type === 'flash' || type === 'both') {
+        if(args[0] === null) {
+          throw new Error('Flash local storage not available.');
+        }
+        rval = func.apply(this, args);
+        done = (type === 'flash');
+      }
+      if(type === 'web' || type === 'both') {
+        args[0] = localStorage;
+        rval = func.apply(this, args);
+        done = true;
+      }
+    } catch(ex) {
+      exception = ex;
+    }
+    if(done) {
+      break;
+    }
+  }
+
+  if(!done) {
+    throw exception;
+  }
+
+  return rval;
+};
+
+/**
+ * Stores an item on local disk.
+ *
+ * The available types of local storage include 'flash', 'web', and 'both'.
+ *
+ * The type 'flash' refers to flash local storage (SharedObject). In order
+ * to use flash local storage, the 'api' parameter must be valid. The type
+ * 'web' refers to WebStorage, if supported by the browser. The type 'both'
+ * refers to storing using both 'flash' and 'web', not just one or the
+ * other.
+ *
+ * The location array should list the storage types to use in order of
+ * preference:
+ *
+ * ['flash']: flash only storage
+ * ['web']: web only storage
+ * ['both']: try to store in both
+ * ['flash','web']: store in flash first, but if not available, 'web'
+ * ['web','flash']: store in web first, but if not available, 'flash'
+ *
+ * The location array defaults to: ['web', 'flash']
+ *
+ * @param api the flash interface, null to use only WebStorage.
+ * @param id the storage ID to use.
+ * @param key the key for the item.
+ * @param data the data for the item (any javascript object/primitive).
+ * @param location an array with the preferred types of storage to use.
+ */
+util.setItem = function(api, id, key, data, location) {
+  _callStorageFunction(_setItem, arguments, location);
+};
+
+/**
+ * Gets an item on local disk.
+ *
+ * Set setItem() for details on storage types.
+ *
+ * @param api the flash interface, null to use only WebStorage.
+ * @param id the storage ID to use.
+ * @param key the key for the item.
+ * @param location an array with the preferred types of storage to use.
+ *
+ * @return the item.
+ */
+util.getItem = function(api, id, key, location) {
+  return _callStorageFunction(_getItem, arguments, location);
+};
+
+/**
+ * Removes an item on local disk.
+ *
+ * Set setItem() for details on storage types.
+ *
+ * @param api the flash interface.
+ * @param id the storage ID to use.
+ * @param key the key for the item.
+ * @param location an array with the preferred types of storage to use.
+ */
+util.removeItem = function(api, id, key, location) {
+  _callStorageFunction(_removeItem, arguments, location);
+};
+
+/**
+ * Clears the local disk storage identified by the given ID.
+ *
+ * Set setItem() for details on storage types.
+ *
+ * @param api the flash interface if flash is available.
+ * @param id the storage ID to use.
+ * @param location an array with the preferred types of storage to use.
+ */
+util.clearItems = function(api, id, location) {
+  _callStorageFunction(_clearItems, arguments, location);
+};
+
+/**
+ * Parses the scheme, host, and port from an http(s) url.
+ *
+ * @param str the url string.
+ *
+ * @return the parsed url object or null if the url is invalid.
+ */
+util.parseUrl = function(str) {
+  // FIXME: this regex looks a bit broken
+  var regex = /^(https?):\/\/([^:&^\/]*):?(\d*)(.*)$/g;
+  regex.lastIndex = 0;
+  var m = regex.exec(str);
+  var url = (m === null) ? null : {
+    full: str,
+    scheme: m[1],
+    host: m[2],
+    port: m[3],
+    path: m[4]
+  };
+  if(url) {
+    url.fullHost = url.host;
+    if(url.port) {
+      if(url.port !== 80 && url.scheme === 'http') {
+        url.fullHost += ':' + url.port;
+      } else if(url.port !== 443 && url.scheme === 'https') {
+        url.fullHost += ':' + url.port;
+      }
+    } else if(url.scheme === 'http') {
+      url.port = 80;
+    } else if(url.scheme === 'https') {
+      url.port = 443;
+    }
+    url.full = url.scheme + '://' + url.fullHost;
+  }
+  return url;
+};
+
+/* Storage for query variables */
+var _queryVariables = null;
+
+/**
+ * Returns the window location query variables. Query is parsed on the first
+ * call and the same object is returned on subsequent calls. The mapping
+ * is from keys to an array of values. Parameters without values will have
+ * an object key set but no value added to the value array. Values are
+ * unescaped.
+ *
+ * ...?k1=v1&k2=v2:
+ * {
+ *   "k1": ["v1"],
+ *   "k2": ["v2"]
+ * }
+ *
+ * ...?k1=v1&k1=v2:
+ * {
+ *   "k1": ["v1", "v2"]
+ * }
+ *
+ * ...?k1=v1&k2:
+ * {
+ *   "k1": ["v1"],
+ *   "k2": []
+ * }
+ *
+ * ...?k1=v1&k1:
+ * {
+ *   "k1": ["v1"]
+ * }
+ *
+ * ...?k1&k1:
+ * {
+ *   "k1": []
+ * }
+ *
+ * @param query the query string to parse (optional, default to cached
+ *          results from parsing window location search query).
+ *
+ * @return object mapping keys to variables.
+ */
+util.getQueryVariables = function(query) {
+  var parse = function(q) {
+    var rval = {};
+    var kvpairs = q.split('&');
+    for(var i = 0; i < kvpairs.length; i++) {
+      var pos = kvpairs[i].indexOf('=');
+      var key;
+      var val;
+      if(pos > 0) {
+        key = kvpairs[i].substring(0, pos);
+        val = kvpairs[i].substring(pos + 1);
+      } else {
+        key = kvpairs[i];
+        val = null;
+      }
+      if(!(key in rval)) {
+        rval[key] = [];
+      }
+      // disallow overriding object prototype keys
+      if(!(key in Object.prototype) && val !== null) {
+        rval[key].push(unescape(val));
+      }
+    }
+    return rval;
+  };
+
+   var rval;
+   if(typeof(query) === 'undefined') {
+     // set cached variables if needed
+     if(_queryVariables === null) {
+       if(typeof(window) === 'undefined') {
+          // no query variables available
+          _queryVariables = {};
+       } else {
+          // parse window search query
+          _queryVariables = parse(window.location.search.substring(1));
+       }
+     }
+     rval = _queryVariables;
+   } else {
+     // parse given query
+     rval = parse(query);
+   }
+   return rval;
+};
+
+/**
+ * Parses a fragment into a path and query. This method will take a URI
+ * fragment and break it up as if it were the main URI. For example:
+ *    /bar/baz?a=1&b=2
+ * results in:
+ *    {
+ *       path: ["bar", "baz"],
+ *       query: {"k1": ["v1"], "k2": ["v2"]}
+ *    }
+ *
+ * @return object with a path array and query object.
+ */
+util.parseFragment = function(fragment) {
+  // default to whole fragment
+  var fp = fragment;
+  var fq = '';
+  // split into path and query if possible at the first '?'
+  var pos = fragment.indexOf('?');
+  if(pos > 0) {
+    fp = fragment.substring(0, pos);
+    fq = fragment.substring(pos + 1);
+  }
+  // split path based on '/' and ignore first element if empty
+  var path = fp.split('/');
+  if(path.length > 0 && path[0] === '') {
+    path.shift();
+  }
+  // convert query into object
+  var query = (fq === '') ? {} : util.getQueryVariables(fq);
+
+  return {
+    pathString: fp,
+    queryString: fq,
+    path: path,
+    query: query
+  };
+};
+
+/**
+ * Makes a request out of a URI-like request string. This is intended to
+ * be used where a fragment id (after a URI '#') is parsed as a URI with
+ * path and query parts. The string should have a path beginning and
+ * delimited by '/' and optional query parameters following a '?'. The
+ * query should be a standard URL set of key value pairs delimited by
+ * '&'. For backwards compatibility the initial '/' on the path is not
+ * required. The request object has the following API, (fully described
+ * in the method code):
+ *    {
+ *       path: <the path string part>.
+ *       query: <the query string part>,
+ *       getPath(i): get part or all of the split path array,
+ *       getQuery(k, i): get part or all of a query key array,
+ *       getQueryLast(k, _default): get last element of a query key array.
+ *    }
+ *
+ * @return object with request parameters.
+ */
+util.makeRequest = function(reqString) {
+  var frag = util.parseFragment(reqString);
+  var req = {
+    // full path string
+    path: frag.pathString,
+    // full query string
+    query: frag.queryString,
+    /**
+     * Get path or element in path.
+     *
+     * @param i optional path index.
+     *
+     * @return path or part of path if i provided.
+     */
+    getPath: function(i) {
+      return (typeof(i) === 'undefined') ? frag.path : frag.path[i];
+    },
+    /**
+     * Get query, values for a key, or value for a key index.
+     *
+     * @param k optional query key.
+     * @param i optional query key index.
+     *
+     * @return query, values for a key, or value for a key index.
+     */
+    getQuery: function(k, i) {
+      var rval;
+      if(typeof(k) === 'undefined') {
+        rval = frag.query;
+      } else {
+        rval = frag.query[k];
+        if(rval && typeof(i) !== 'undefined') {
+           rval = rval[i];
+        }
+      }
+      return rval;
+    },
+    getQueryLast: function(k, _default) {
+      var rval;
+      var vals = req.getQuery(k);
+      if(vals) {
+        rval = vals[vals.length - 1];
+      } else {
+        rval = _default;
+      }
+      return rval;
+    }
+  };
+  return req;
+};
+
+/**
+ * Makes a URI out of a path, an object with query parameters, and a
+ * fragment. Uses jQuery.param() internally for query string creation.
+ * If the path is an array, it will be joined with '/'.
+ *
+ * @param path string path or array of strings.
+ * @param query object with query parameters. (optional)
+ * @param fragment fragment string. (optional)
+ *
+ * @return string object with request parameters.
+ */
+util.makeLink = function(path, query, fragment) {
+  // join path parts if needed
+  path = jQuery.isArray(path) ? path.join('/') : path;
+
+  var qstr = jQuery.param(query || {});
+  fragment = fragment || '';
+  return path +
+    ((qstr.length > 0) ? ('?' + qstr) : '') +
+    ((fragment.length > 0) ? ('#' + fragment) : '');
+};
+
+/**
+ * Follows a path of keys deep into an object hierarchy and set a value.
+ * If a key does not exist or it's value is not an object, create an
+ * object in it's place. This can be destructive to a object tree if
+ * leaf nodes are given as non-final path keys.
+ * Used to avoid exceptions from missing parts of the path.
+ *
+ * @param object the starting object.
+ * @param keys an array of string keys.
+ * @param value the value to set.
+ */
+util.setPath = function(object, keys, value) {
+  // need to start at an object
+  if(typeof(object) === 'object' && object !== null) {
+    var i = 0;
+    var len = keys.length;
+    while(i < len) {
+      var next = keys[i++];
+      if(i == len) {
+        // last
+        object[next] = value;
+      } else {
+        // more
+        var hasNext = (next in object);
+        if(!hasNext ||
+          (hasNext && typeof(object[next]) !== 'object') ||
+          (hasNext && object[next] === null)) {
+          object[next] = {};
+        }
+        object = object[next];
+      }
+    }
+  }
+};
+
+/**
+ * Follows a path of keys deep into an object hierarchy and return a value.
+ * If a key does not exist, create an object in it's place.
+ * Used to avoid exceptions from missing parts of the path.
+ *
+ * @param object the starting object.
+ * @param keys an array of string keys.
+ * @param _default value to return if path not found.
+ *
+ * @return the value at the path if found, else default if given, else
+ *         undefined.
+ */
+util.getPath = function(object, keys, _default) {
+  var i = 0;
+  var len = keys.length;
+  var hasNext = true;
+  while(hasNext && i < len &&
+    typeof(object) === 'object' && object !== null) {
+    var next = keys[i++];
+    hasNext = next in object;
+    if(hasNext) {
+      object = object[next];
+    }
+  }
+  return (hasNext ? object : _default);
+};
+
+/**
+ * Follow a path of keys deep into an object hierarchy and delete the
+ * last one. If a key does not exist, do nothing.
+ * Used to avoid exceptions from missing parts of the path.
+ *
+ * @param object the starting object.
+ * @param keys an array of string keys.
+ */
+util.deletePath = function(object, keys) {
+  // need to start at an object
+  if(typeof(object) === 'object' && object !== null) {
+    var i = 0;
+    var len = keys.length;
+    while(i < len) {
+      var next = keys[i++];
+      if(i == len) {
+        // last
+        delete object[next];
+      } else {
+        // more
+        if(!(next in object) ||
+          (typeof(object[next]) !== 'object') ||
+          (object[next] === null)) {
+           break;
+        }
+        object = object[next];
+      }
+    }
+  }
+};
+
+/**
+ * Check if an object is empty.
+ *
+ * Taken from:
+ * http://stackoverflow.com/questions/679915/how-do-i-test-for-an-empty-javascript-object-from-json/679937#679937
+ *
+ * @param object the object to check.
+ */
+util.isEmpty = function(obj) {
+  for(var prop in obj) {
+    if(obj.hasOwnProperty(prop)) {
+      return false;
+    }
+  }
+  return true;
+};
+
+/**
+ * Format with simple printf-style interpolation.
+ *
+ * %%: literal '%'
+ * %s,%o: convert next argument into a string.
+ *
+ * @param format the string to format.
+ * @param ... arguments to interpolate into the format string.
+ */
+util.format = function(format) {
+  var re = /%./g;
+  // current match
+  var match;
+  // current part
+  var part;
+  // current arg index
+  var argi = 0;
+  // collected parts to recombine later
+  var parts = [];
+  // last index found
+  var last = 0;
+  // loop while matches remain
+  while((match = re.exec(format))) {
+    part = format.substring(last, re.lastIndex - 2);
+    // don't add empty strings (ie, parts between %s%s)
+    if(part.length > 0) {
+      parts.push(part);
+    }
+    last = re.lastIndex;
+    // switch on % code
+    var code = match[0][1];
+    switch(code) {
+    case 's':
+    case 'o':
+      // check if enough arguments were given
+      if(argi < arguments.length) {
+        parts.push(arguments[argi++ + 1]);
+      } else {
+        parts.push('<?>');
+      }
+      break;
+    // FIXME: do proper formating for numbers, etc
+    //case 'f':
+    //case 'd':
+    case '%':
+      parts.push('%');
+      break;
+    default:
+      parts.push('<%' + code + '?>');
+    }
+  }
+  // add trailing part of format string
+  parts.push(format.substring(last));
+  return parts.join('');
+};
+
+/**
+ * Formats a number.
+ *
+ * http://snipplr.com/view/5945/javascript-numberformat--ported-from-php/
+ */
+util.formatNumber = function(number, decimals, dec_point, thousands_sep) {
+  // http://kevin.vanzonneveld.net
+  // +   original by: Jonas Raoni Soares Silva (http://www.jsfromhell.com)
+  // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
+  // +     bugfix by: Michael White (http://crestidg.com)
+  // +     bugfix by: Benjamin Lupton
+  // +     bugfix by: Allan Jensen (http://www.winternet.no)
+  // +    revised by: Jonas Raoni Soares Silva (http://www.jsfromhell.com)
+  // *     example 1: number_format(1234.5678, 2, '.', '');
+  // *     returns 1: 1234.57
+
+  var n = number, c = isNaN(decimals = Math.abs(decimals)) ? 2 : decimals;
+  var d = dec_point === undefined ? ',' : dec_point;
+  var t = thousands_sep === undefined ?
+   '.' : thousands_sep, s = n < 0 ? '-' : '';
+  var i = parseInt((n = Math.abs(+n || 0).toFixed(c)), 10) + '';
+  var j = (i.length > 3) ? i.length % 3 : 0;
+  return s + (j ? i.substr(0, j) + t : '') +
+    i.substr(j).replace(/(\d{3})(?=\d)/g, '$1' + t) +
+    (c ? d + Math.abs(n - i).toFixed(c).slice(2) : '');
+};
+
+/**
+ * Formats a byte size.
+ *
+ * http://snipplr.com/view/5949/format-humanize-file-byte-size-presentation-in-javascript/
+ */
+util.formatSize = function(size) {
+  if(size >= 1073741824) {
+    size = util.formatNumber(size / 1073741824, 2, '.', '') + ' GiB';
+  } else if(size >= 1048576) {
+    size = util.formatNumber(size / 1048576, 2, '.', '') + ' MiB';
+  } else if(size >= 1024) {
+    size = util.formatNumber(size / 1024, 0) + ' KiB';
+  } else {
+    size = util.formatNumber(size, 0) + ' bytes';
+  }
+  return size;
+};
+
+/**
+ * Converts an IPv4 or IPv6 string representation into bytes (in network order).
+ *
+ * @param ip the IPv4 or IPv6 address to convert.
+ *
+ * @return the 4-byte IPv6 or 16-byte IPv6 address or null if the address can't
+ *         be parsed.
+ */
+util.bytesFromIP = function(ip) {
+  if(ip.indexOf('.') !== -1) {
+    return util.bytesFromIPv4(ip);
+  }
+  if(ip.indexOf(':') !== -1) {
+    return util.bytesFromIPv6(ip);
+  }
+  return null;
+};
+
+/**
+ * Converts an IPv4 string representation into bytes (in network order).
+ *
+ * @param ip the IPv4 address to convert.
+ *
+ * @return the 4-byte address or null if the address can't be parsed.
+ */
+util.bytesFromIPv4 = function(ip) {
+  ip = ip.split('.');
+  if(ip.length !== 4) {
+    return null;
+  }
+  var b = util.createBuffer();
+  for(var i = 0; i < ip.length; ++i) {
+    var num = parseInt(ip[i], 10);
+    if(isNaN(num)) {
+      return null;
+    }
+    b.putByte(num);
+  }
+  return b.getBytes();
+};
+
+/**
+ * Converts an IPv6 string representation into bytes (in network order).
+ *
+ * @param ip the IPv6 address to convert.
+ *
+ * @return the 16-byte address or null if the address can't be parsed.
+ */
+util.bytesFromIPv6 = function(ip) {
+  var blanks = 0;
+  ip = ip.split(':').filter(function(e) {
+    if(e.length === 0) ++blanks;
+    return true;
+  });
+  var zeros = (8 - ip.length + blanks) * 2;
+  var b = util.createBuffer();
+  for(var i = 0; i < 8; ++i) {
+    if(!ip[i] || ip[i].length === 0) {
+      b.fillWithByte(0, zeros);
+      zeros = 0;
+      continue;
+    }
+    var bytes = util.hexToBytes(ip[i]);
+    if(bytes.length < 2) {
+      b.putByte(0);
+    }
+    b.putBytes(bytes);
+  }
+  return b.getBytes();
+};
+
+/**
+ * Converts 4-bytes into an IPv4 string representation or 16-bytes into
+ * an IPv6 string representation. The bytes must be in network order.
+ *
+ * @param bytes the bytes to convert.
+ *
+ * @return the IPv4 or IPv6 string representation if 4 or 16 bytes,
+ *         respectively, are given, otherwise null.
+ */
+util.bytesToIP = function(bytes) {
+  if(bytes.length === 4) {
+    return util.bytesToIPv4(bytes);
+  }
+  if(bytes.length === 16) {
+    return util.bytesToIPv6(bytes);
+  }
+  return null;
+};
+
+/**
+ * Converts 4-bytes into an IPv4 string representation. The bytes must be
+ * in network order.
+ *
+ * @param bytes the bytes to convert.
+ *
+ * @return the IPv4 string representation or null for an invalid # of bytes.
+ */
+util.bytesToIPv4 = function(bytes) {
+  if(bytes.length !== 4) {
+    return null;
+  }
+  var ip = [];
+  for(var i = 0; i < bytes.length; ++i) {
+    ip.push(bytes.charCodeAt(i));
+  }
+  return ip.join('.');
+};
+
+/**
+ * Converts 16-bytes into an IPv16 string representation. The bytes must be
+ * in network order.
+ *
+ * @param bytes the bytes to convert.
+ *
+ * @return the IPv16 string representation or null for an invalid # of bytes.
+ */
+util.bytesToIPv6 = function(bytes) {
+  if(bytes.length !== 16) {
+    return null;
+  }
+  var ip = [];
+  var zeroGroups = [];
+  var zeroMaxGroup = 0;
+  for(var i = 0; i < bytes.length; i += 2) {
+    var hex = util.bytesToHex(bytes[i] + bytes[i + 1]);
+    // canonicalize zero representation
+    while(hex[0] === '0' && hex !== '0') {
+      hex = hex.substr(1);
+    }
+    if(hex === '0') {
+      var last = zeroGroups[zeroGroups.length - 1];
+      var idx = ip.length;
+      if(!last || idx !== last.end + 1) {
+        zeroGroups.push({start: idx, end: idx});
+      } else {
+        last.end = idx;
+        if((last.end - last.start) >
+          (zeroGroups[zeroMaxGroup].end - zeroGroups[zeroMaxGroup].start)) {
+          zeroMaxGroup = zeroGroups.length - 1;
+        }
+      }
+    }
+    ip.push(hex);
+  }
+  if(zeroGroups.length > 0) {
+    var group = zeroGroups[zeroMaxGroup];
+    // only shorten group of length > 0
+    if(group.end - group.start > 0) {
+      ip.splice(group.start, group.end - group.start + 1, '');
+      if(group.start === 0) {
+        ip.unshift('');
+      }
+      if(group.end === 7) {
+        ip.push('');
+      }
+    }
+  }
+  return ip.join(':');
+};
+
+/**
+ * Estimates the number of processes that can be run concurrently. If
+ * creating Web Workers, keep in mind that the main JavaScript process needs
+ * its own core.
+ *
+ * @param options the options to use:
+ *          update true to force an update (not use the cached value).
+ * @param callback(err, max) called once the operation completes.
+ */
+util.estimateCores = function(options, callback) {
+  if(typeof options === 'function') {
+    callback = options;
+    options = {};
+  }
+  options = options || {};
+  if('cores' in util && !options.update) {
+    return callback(null, util.cores);
+  }
+  if(typeof navigator !== 'undefined' &&
+    'hardwareConcurrency' in navigator &&
+    navigator.hardwareConcurrency > 0) {
+    util.cores = navigator.hardwareConcurrency;
+    return callback(null, util.cores);
+  }
+  if(typeof Worker === 'undefined') {
+    // workers not available
+    util.cores = 1;
+    return callback(null, util.cores);
+  }
+  if(typeof Blob === 'undefined') {
+    // can't estimate, default to 2
+    util.cores = 2;
+    return callback(null, util.cores);
+  }
+
+  // create worker concurrency estimation code as blob
+  var blobUrl = URL.createObjectURL(new Blob(['(',
+    function() {
+      self.addEventListener('message', function(e) {
+        // run worker for 4 ms
+        var st = Date.now();
+        var et = st + 4;
+        while(Date.now() < et);
+        self.postMessage({st: st, et: et});
+      });
+    }.toString(),
+  ')()'], {type: 'application/javascript'}));
+
+  // take 5 samples using 16 workers
+  sample([], 5, 16);
+
+  function sample(max, samples, numWorkers) {
+    if(samples === 0) {
+      // get overlap average
+      var avg = Math.floor(max.reduce(function(avg, x) {
+        return avg + x;
+      }, 0) / max.length);
+      util.cores = Math.max(1, avg);
+      URL.revokeObjectURL(blobUrl);
+      return callback(null, util.cores);
+    }
+    map(numWorkers, function(err, results) {
+      max.push(reduce(numWorkers, results));
+      sample(max, samples - 1, numWorkers);
+    });
+  }
+
+  function map(numWorkers, callback) {
+    var workers = [];
+    var results = [];
+    for(var i = 0; i < numWorkers; ++i) {
+      var worker = new Worker(blobUrl);
+      worker.addEventListener('message', function(e) {
+        results.push(e.data);
+        if(results.length === numWorkers) {
+          for(var i = 0; i < numWorkers; ++i) {
+            workers[i].terminate();
+          }
+          callback(null, results);
+        }
+      });
+      workers.push(worker);
+    }
+    for(var i = 0; i < numWorkers; ++i) {
+      workers[i].postMessage(i);
+    }
+  }
+
+  function reduce(numWorkers, results) {
+    // find overlapping time windows
+    var overlaps = [];
+    for(var n = 0; n < numWorkers; ++n) {
+      var r1 = results[n];
+      var overlap = overlaps[n] = [];
+      for(var i = 0; i < numWorkers; ++i) {
+        if(n === i) {
+          continue;
+        }
+        var r2 = results[i];
+        if((r1.st > r2.st && r1.st < r2.et) ||
+          (r2.st > r1.st && r2.st < r1.et)) {
+          overlap.push(i);
+        }
+      }
+    }
+    // get maximum overlaps ... don't include overlapping worker itself
+    // as the main JS process was also being scheduled during the work and
+    // would have to be subtracted from the estimate anyway
+    return overlaps.reduce(function(max, overlap) {
+      return Math.max(max, overlap.length);
+    }, 0);
+  }
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'util';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge[name];
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define(['require', 'module'], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/x509.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/x509.js
new file mode 100644
index 0000000..6cd8585
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/x509.js
@@ -0,0 +1,3178 @@
+/**
+ * Javascript implementation of X.509 and related components (such as
+ * Certification Signing Requests) of a Public Key Infrastructure.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2014 Digital Bazaar, Inc.
+ *
+ * The ASN.1 representation of an X.509v3 certificate is as follows
+ * (see RFC 2459):
+ *
+ * Certificate ::= SEQUENCE {
+ *   tbsCertificate       TBSCertificate,
+ *   signatureAlgorithm   AlgorithmIdentifier,
+ *   signatureValue       BIT STRING
+ * }
+ *
+ * TBSCertificate ::= SEQUENCE {
+ *   version         [0]  EXPLICIT Version DEFAULT v1,
+ *   serialNumber         CertificateSerialNumber,
+ *   signature            AlgorithmIdentifier,
+ *   issuer               Name,
+ *   validity             Validity,
+ *   subject              Name,
+ *   subjectPublicKeyInfo SubjectPublicKeyInfo,
+ *   issuerUniqueID  [1]  IMPLICIT UniqueIdentifier OPTIONAL,
+ *                        -- If present, version shall be v2 or v3
+ *   subjectUniqueID [2]  IMPLICIT UniqueIdentifier OPTIONAL,
+ *                        -- If present, version shall be v2 or v3
+ *   extensions      [3]  EXPLICIT Extensions OPTIONAL
+ *                        -- If present, version shall be v3
+ * }
+ *
+ * Version ::= INTEGER  { v1(0), v2(1), v3(2) }
+ *
+ * CertificateSerialNumber ::= INTEGER
+ *
+ * Name ::= CHOICE {
+ *   // only one possible choice for now
+ *   RDNSequence
+ * }
+ *
+ * RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
+ *
+ * RelativeDistinguishedName ::= SET OF AttributeTypeAndValue
+ *
+ * AttributeTypeAndValue ::= SEQUENCE {
+ *   type     AttributeType,
+ *   value    AttributeValue
+ * }
+ * AttributeType ::= OBJECT IDENTIFIER
+ * AttributeValue ::= ANY DEFINED BY AttributeType
+ *
+ * Validity ::= SEQUENCE {
+ *   notBefore      Time,
+ *   notAfter       Time
+ * }
+ *
+ * Time ::= CHOICE {
+ *   utcTime        UTCTime,
+ *   generalTime    GeneralizedTime
+ * }
+ *
+ * UniqueIdentifier ::= BIT STRING
+ *
+ * SubjectPublicKeyInfo ::= SEQUENCE {
+ *   algorithm            AlgorithmIdentifier,
+ *   subjectPublicKey     BIT STRING
+ * }
+ *
+ * Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension
+ *
+ * Extension ::= SEQUENCE {
+ *   extnID      OBJECT IDENTIFIER,
+ *   critical    BOOLEAN DEFAULT FALSE,
+ *   extnValue   OCTET STRING
+ * }
+ *
+ * The only key algorithm currently supported for PKI is RSA.
+ *
+ * RSASSA-PSS signatures are described in RFC 3447 and RFC 4055.
+ *
+ * PKCS#10 v1.7 describes certificate signing requests:
+ *
+ * CertificationRequestInfo:
+ *
+ * CertificationRequestInfo ::= SEQUENCE {
+ *   version       INTEGER { v1(0) } (v1,...),
+ *   subject       Name,
+ *   subjectPKInfo SubjectPublicKeyInfo{{ PKInfoAlgorithms }},
+ *   attributes    [0] Attributes{{ CRIAttributes }}
+ * }
+ *
+ * Attributes { ATTRIBUTE:IOSet } ::= SET OF Attribute{{ IOSet }}
+ *
+ * CRIAttributes  ATTRIBUTE  ::= {
+ *   ... -- add any locally defined attributes here -- }
+ *
+ * Attribute { ATTRIBUTE:IOSet } ::= SEQUENCE {
+ *   type   ATTRIBUTE.&id({IOSet}),
+ *   values SET SIZE(1..MAX) OF ATTRIBUTE.&Type({IOSet}{@type})
+ * }
+ *
+ * CertificationRequest ::= SEQUENCE {
+ *   certificationRequestInfo CertificationRequestInfo,
+ *   signatureAlgorithm AlgorithmIdentifier{{ SignatureAlgorithms }},
+ *   signature          BIT STRING
+ * }
+ */
+(function() {
+/* ########## Begin module implementation ########## */
+function initModule(forge) {
+
+// shortcut for asn.1 API
+var asn1 = forge.asn1;
+
+/* Public Key Infrastructure (PKI) implementation. */
+var pki = forge.pki = forge.pki || {};
+var oids = pki.oids;
+
+// short name OID mappings
+var _shortNames = {};
+_shortNames['CN'] = oids['commonName'];
+_shortNames['commonName'] = 'CN';
+_shortNames['C'] = oids['countryName'];
+_shortNames['countryName'] = 'C';
+_shortNames['L'] = oids['localityName'];
+_shortNames['localityName'] = 'L';
+_shortNames['ST'] = oids['stateOrProvinceName'];
+_shortNames['stateOrProvinceName'] = 'ST';
+_shortNames['O'] = oids['organizationName'];
+_shortNames['organizationName'] = 'O';
+_shortNames['OU'] = oids['organizationalUnitName'];
+_shortNames['organizationalUnitName'] = 'OU';
+_shortNames['E'] = oids['emailAddress'];
+_shortNames['emailAddress'] = 'E';
+
+// validator for an SubjectPublicKeyInfo structure
+// Note: Currently only works with an RSA public key
+var publicKeyValidator = forge.pki.rsa.publicKeyValidator;
+
+// validator for an X.509v3 certificate
+var x509CertificateValidator = {
+  name: 'Certificate',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'Certificate.TBSCertificate',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true,
+    captureAsn1: 'tbsCertificate',
+    value: [{
+      name: 'Certificate.TBSCertificate.version',
+      tagClass: asn1.Class.CONTEXT_SPECIFIC,
+      type: 0,
+      constructed: true,
+      optional: true,
+      value: [{
+        name: 'Certificate.TBSCertificate.version.integer',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.INTEGER,
+        constructed: false,
+        capture: 'certVersion'
+      }]
+    }, {
+      name: 'Certificate.TBSCertificate.serialNumber',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.INTEGER,
+      constructed: false,
+      capture: 'certSerialNumber'
+    }, {
+      name: 'Certificate.TBSCertificate.signature',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.SEQUENCE,
+      constructed: true,
+      value: [{
+        name: 'Certificate.TBSCertificate.signature.algorithm',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.OID,
+        constructed: false,
+        capture: 'certinfoSignatureOid'
+      }, {
+        name: 'Certificate.TBSCertificate.signature.parameters',
+        tagClass: asn1.Class.UNIVERSAL,
+        optional: true,
+        captureAsn1: 'certinfoSignatureParams'
+      }]
+    }, {
+      name: 'Certificate.TBSCertificate.issuer',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.SEQUENCE,
+      constructed: true,
+      captureAsn1: 'certIssuer'
+    }, {
+      name: 'Certificate.TBSCertificate.validity',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.SEQUENCE,
+      constructed: true,
+      // Note: UTC and generalized times may both appear so the capture
+      // names are based on their detected order, the names used below
+      // are only for the common case, which validity time really means
+      // "notBefore" and which means "notAfter" will be determined by order
+      value: [{
+        // notBefore (Time) (UTC time case)
+        name: 'Certificate.TBSCertificate.validity.notBefore (utc)',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.UTCTIME,
+        constructed: false,
+        optional: true,
+        capture: 'certValidity1UTCTime'
+      }, {
+        // notBefore (Time) (generalized time case)
+        name: 'Certificate.TBSCertificate.validity.notBefore (generalized)',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.GENERALIZEDTIME,
+        constructed: false,
+        optional: true,
+        capture: 'certValidity2GeneralizedTime'
+      }, {
+        // notAfter (Time) (only UTC time is supported)
+        name: 'Certificate.TBSCertificate.validity.notAfter (utc)',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.UTCTIME,
+        constructed: false,
+        optional: true,
+        capture: 'certValidity3UTCTime'
+      }, {
+        // notAfter (Time) (only UTC time is supported)
+        name: 'Certificate.TBSCertificate.validity.notAfter (generalized)',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.GENERALIZEDTIME,
+        constructed: false,
+        optional: true,
+        capture: 'certValidity4GeneralizedTime'
+      }]
+    }, {
+      // Name (subject) (RDNSequence)
+      name: 'Certificate.TBSCertificate.subject',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.SEQUENCE,
+      constructed: true,
+      captureAsn1: 'certSubject'
+    },
+      // SubjectPublicKeyInfo
+      publicKeyValidator,
+    {
+      // issuerUniqueID (optional)
+      name: 'Certificate.TBSCertificate.issuerUniqueID',
+      tagClass: asn1.Class.CONTEXT_SPECIFIC,
+      type: 1,
+      constructed: true,
+      optional: true,
+      value: [{
+        name: 'Certificate.TBSCertificate.issuerUniqueID.id',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.BITSTRING,
+        constructed: false,
+        capture: 'certIssuerUniqueId'
+      }]
+    }, {
+      // subjectUniqueID (optional)
+      name: 'Certificate.TBSCertificate.subjectUniqueID',
+      tagClass: asn1.Class.CONTEXT_SPECIFIC,
+      type: 2,
+      constructed: true,
+      optional: true,
+      value: [{
+        name: 'Certificate.TBSCertificate.subjectUniqueID.id',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.BITSTRING,
+        constructed: false,
+        capture: 'certSubjectUniqueId'
+      }]
+    }, {
+      // Extensions (optional)
+      name: 'Certificate.TBSCertificate.extensions',
+      tagClass: asn1.Class.CONTEXT_SPECIFIC,
+      type: 3,
+      constructed: true,
+      captureAsn1: 'certExtensions',
+      optional: true
+    }]
+  }, {
+    // AlgorithmIdentifier (signature algorithm)
+    name: 'Certificate.signatureAlgorithm',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true,
+    value: [{
+      // algorithm
+      name: 'Certificate.signatureAlgorithm.algorithm',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.OID,
+      constructed: false,
+      capture: 'certSignatureOid'
+    }, {
+      name: 'Certificate.TBSCertificate.signature.parameters',
+      tagClass: asn1.Class.UNIVERSAL,
+      optional: true,
+      captureAsn1: 'certSignatureParams'
+    }]
+  }, {
+    // SignatureValue
+    name: 'Certificate.signatureValue',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.BITSTRING,
+    constructed: false,
+    capture: 'certSignature'
+  }]
+};
+
+var rsassaPssParameterValidator = {
+  name: 'rsapss',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  value: [{
+    name: 'rsapss.hashAlgorithm',
+    tagClass: asn1.Class.CONTEXT_SPECIFIC,
+    type: 0,
+    constructed: true,
+    value: [{
+      name: 'rsapss.hashAlgorithm.AlgorithmIdentifier',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Class.SEQUENCE,
+      constructed: true,
+      optional: true,
+      value: [{
+        name: 'rsapss.hashAlgorithm.AlgorithmIdentifier.algorithm',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.OID,
+        constructed: false,
+        capture: 'hashOid'
+        /* parameter block omitted, for SHA1 NULL anyhow. */
+      }]
+    }]
+  }, {
+    name: 'rsapss.maskGenAlgorithm',
+    tagClass: asn1.Class.CONTEXT_SPECIFIC,
+    type: 1,
+    constructed: true,
+    value: [{
+      name: 'rsapss.maskGenAlgorithm.AlgorithmIdentifier',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Class.SEQUENCE,
+      constructed: true,
+      optional: true,
+      value: [{
+        name: 'rsapss.maskGenAlgorithm.AlgorithmIdentifier.algorithm',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.OID,
+        constructed: false,
+        capture: 'maskGenOid'
+      }, {
+        name: 'rsapss.maskGenAlgorithm.AlgorithmIdentifier.params',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.SEQUENCE,
+        constructed: true,
+        value: [{
+          name: 'rsapss.maskGenAlgorithm.AlgorithmIdentifier.params.algorithm',
+          tagClass: asn1.Class.UNIVERSAL,
+          type: asn1.Type.OID,
+          constructed: false,
+          capture: 'maskGenHashOid'
+          /* parameter block omitted, for SHA1 NULL anyhow. */
+        }]
+      }]
+    }]
+  }, {
+    name: 'rsapss.saltLength',
+    tagClass: asn1.Class.CONTEXT_SPECIFIC,
+    type: 2,
+    optional: true,
+    value: [{
+      name: 'rsapss.saltLength.saltLength',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Class.INTEGER,
+      constructed: false,
+      capture: 'saltLength'
+    }]
+  }, {
+    name: 'rsapss.trailerField',
+    tagClass: asn1.Class.CONTEXT_SPECIFIC,
+    type: 3,
+    optional: true,
+    value: [{
+      name: 'rsapss.trailer.trailer',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Class.INTEGER,
+      constructed: false,
+      capture: 'trailer'
+    }]
+  }]
+};
+
+// validator for a CertificationRequestInfo structure
+var certificationRequestInfoValidator = {
+  name: 'CertificationRequestInfo',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  captureAsn1: 'certificationRequestInfo',
+  value: [{
+    name: 'CertificationRequestInfo.integer',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.INTEGER,
+    constructed: false,
+    capture: 'certificationRequestInfoVersion'
+  }, {
+    // Name (subject) (RDNSequence)
+    name: 'CertificationRequestInfo.subject',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true,
+    captureAsn1: 'certificationRequestInfoSubject'
+  },
+  // SubjectPublicKeyInfo
+  publicKeyValidator,
+  {
+    name: 'CertificationRequestInfo.attributes',
+    tagClass: asn1.Class.CONTEXT_SPECIFIC,
+    type: 0,
+    constructed: true,
+    optional: true,
+    capture: 'certificationRequestInfoAttributes',
+    value: [{
+      name: 'CertificationRequestInfo.attributes',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.SEQUENCE,
+      constructed: true,
+      value: [{
+        name: 'CertificationRequestInfo.attributes.type',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.OID,
+        constructed: false
+      }, {
+        name: 'CertificationRequestInfo.attributes.value',
+        tagClass: asn1.Class.UNIVERSAL,
+        type: asn1.Type.SET,
+        constructed: true
+      }]
+    }]
+  }]
+};
+
+// validator for a CertificationRequest structure
+var certificationRequestValidator = {
+  name: 'CertificationRequest',
+  tagClass: asn1.Class.UNIVERSAL,
+  type: asn1.Type.SEQUENCE,
+  constructed: true,
+  captureAsn1: 'csr',
+  value: [
+    certificationRequestInfoValidator, {
+    // AlgorithmIdentifier (signature algorithm)
+    name: 'CertificationRequest.signatureAlgorithm',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.SEQUENCE,
+    constructed: true,
+    value: [{
+      // algorithm
+      name: 'CertificationRequest.signatureAlgorithm.algorithm',
+      tagClass: asn1.Class.UNIVERSAL,
+      type: asn1.Type.OID,
+      constructed: false,
+      capture: 'csrSignatureOid'
+    }, {
+      name: 'CertificationRequest.signatureAlgorithm.parameters',
+      tagClass: asn1.Class.UNIVERSAL,
+      optional: true,
+      captureAsn1: 'csrSignatureParams'
+    }]
+  }, {
+    // signature
+    name: 'CertificationRequest.signature',
+    tagClass: asn1.Class.UNIVERSAL,
+    type: asn1.Type.BITSTRING,
+    constructed: false,
+    capture: 'csrSignature'
+  }]
+};
+
+/**
+ * Converts an RDNSequence of ASN.1 DER-encoded RelativeDistinguishedName
+ * sets into an array with objects that have type and value properties.
+ *
+ * @param rdn the RDNSequence to convert.
+ * @param md a message digest to append type and value to if provided.
+ */
+pki.RDNAttributesAsArray = function(rdn, md) {
+  var rval = [];
+
+  // each value in 'rdn' in is a SET of RelativeDistinguishedName
+  var set, attr, obj;
+  for(var si = 0; si < rdn.value.length; ++si) {
+    // get the RelativeDistinguishedName set
+    set = rdn.value[si];
+
+    // each value in the SET is an AttributeTypeAndValue sequence
+    // containing first a type (an OID) and second a value (defined by
+    // the OID)
+    for(var i = 0; i < set.value.length; ++i) {
+      obj = {};
+      attr = set.value[i];
+      obj.type = asn1.derToOid(attr.value[0].value);
+      obj.value = attr.value[1].value;
+      obj.valueTagClass = attr.value[1].type;
+      // if the OID is known, get its name and short name
+      if(obj.type in oids) {
+        obj.name = oids[obj.type];
+        if(obj.name in _shortNames) {
+          obj.shortName = _shortNames[obj.name];
+        }
+      }
+      if(md) {
+        md.update(obj.type);
+        md.update(obj.value);
+      }
+      rval.push(obj);
+    }
+  }
+
+  return rval;
+};
+
+/**
+ * Converts ASN.1 CRIAttributes into an array with objects that have type and
+ * value properties.
+ *
+ * @param attributes the CRIAttributes to convert.
+ */
+pki.CRIAttributesAsArray = function(attributes) {
+  var rval = [];
+
+  // each value in 'attributes' in is a SEQUENCE with an OID and a SET
+  for(var si = 0; si < attributes.length; ++si) {
+    // get the attribute sequence
+    var seq = attributes[si];
+
+    // each value in the SEQUENCE containing first a type (an OID) and
+    // second a set of values (defined by the OID)
+    var type = asn1.derToOid(seq.value[0].value);
+    var values = seq.value[1].value;
+    for(var vi = 0; vi < values.length; ++vi) {
+      var obj = {};
+      obj.type = type;
+      obj.value = values[vi].value;
+      obj.valueTagClass = values[vi].type;
+      // if the OID is known, get its name and short name
+      if(obj.type in oids) {
+        obj.name = oids[obj.type];
+        if(obj.name in _shortNames) {
+          obj.shortName = _shortNames[obj.name];
+        }
+      }
+      // parse extensions
+      if(obj.type === oids.extensionRequest) {
+        obj.extensions = [];
+        for(var ei = 0; ei < obj.value.length; ++ei) {
+          obj.extensions.push(pki.certificateExtensionFromAsn1(obj.value[ei]));
+        }
+      }
+      rval.push(obj);
+    }
+  }
+
+  return rval;
+};
+
+/**
+ * Gets an issuer or subject attribute from its name, type, or short name.
+ *
+ * @param obj the issuer or subject object.
+ * @param options a short name string or an object with:
+ *          shortName the short name for the attribute.
+ *          name the name for the attribute.
+ *          type the type for the attribute.
+ *
+ * @return the attribute.
+ */
+function _getAttribute(obj, options) {
+  if(typeof options === 'string') {
+    options = {shortName: options};
+  }
+
+  var rval = null;
+  var attr;
+  for(var i = 0; rval === null && i < obj.attributes.length; ++i) {
+    attr = obj.attributes[i];
+    if(options.type && options.type === attr.type) {
+      rval = attr;
+    } else if(options.name && options.name === attr.name) {
+      rval = attr;
+    } else if(options.shortName && options.shortName === attr.shortName) {
+      rval = attr;
+    }
+  }
+  return rval;
+}
+
+/**
+ * Converts signature parameters from ASN.1 structure.
+ *
+ * Currently only RSASSA-PSS supported.  The PKCS#1 v1.5 signature scheme had
+ * no parameters.
+ *
+ * RSASSA-PSS-params  ::=  SEQUENCE  {
+ *   hashAlgorithm      [0] HashAlgorithm DEFAULT
+ *                             sha1Identifier,
+ *   maskGenAlgorithm   [1] MaskGenAlgorithm DEFAULT
+ *                             mgf1SHA1Identifier,
+ *   saltLength         [2] INTEGER DEFAULT 20,
+ *   trailerField       [3] INTEGER DEFAULT 1
+ * }
+ *
+ * HashAlgorithm  ::=  AlgorithmIdentifier
+ *
+ * MaskGenAlgorithm  ::=  AlgorithmIdentifier
+ *
+ * AlgorithmIdentifer ::= SEQUENCE {
+ *   algorithm OBJECT IDENTIFIER,
+ *   parameters ANY DEFINED BY algorithm OPTIONAL
+ * }
+ *
+ * @param oid The OID specifying the signature algorithm
+ * @param obj The ASN.1 structure holding the parameters
+ * @param fillDefaults Whether to use return default values where omitted
+ * @return signature parameter object
+ */
+var _readSignatureParameters = function(oid, obj, fillDefaults) {
+  var params = {};
+
+  if(oid !== oids['RSASSA-PSS']) {
+    return params;
+  }
+
+  if(fillDefaults) {
+    params = {
+      hash: {
+        algorithmOid: oids['sha1']
+      },
+      mgf: {
+        algorithmOid: oids['mgf1'],
+        hash: {
+          algorithmOid: oids['sha1']
+        }
+      },
+      saltLength: 20
+    };
+  }
+
+  var capture = {};
+  var errors = [];
+  if(!asn1.validate(obj, rsassaPssParameterValidator, capture, errors)) {
+    var error = new Error('Cannot read RSASSA-PSS parameter block.');
+    error.errors = errors;
+    throw error;
+  }
+
+  if(capture.hashOid !== undefined) {
+    params.hash = params.hash || {};
+    params.hash.algorithmOid = asn1.derToOid(capture.hashOid);
+  }
+
+  if(capture.maskGenOid !== undefined) {
+    params.mgf = params.mgf || {};
+    params.mgf.algorithmOid = asn1.derToOid(capture.maskGenOid);
+    params.mgf.hash = params.mgf.hash || {};
+    params.mgf.hash.algorithmOid = asn1.derToOid(capture.maskGenHashOid);
+  }
+
+  if(capture.saltLength !== undefined) {
+    params.saltLength = capture.saltLength.charCodeAt(0);
+  }
+
+  return params;
+};
+
+/**
+ * Converts an X.509 certificate from PEM format.
+ *
+ * Note: If the certificate is to be verified then compute hash should
+ * be set to true. This will scan the TBSCertificate part of the ASN.1
+ * object while it is converted so it doesn't need to be converted back
+ * to ASN.1-DER-encoding later.
+ *
+ * @param pem the PEM-formatted certificate.
+ * @param computeHash true to compute the hash for verification.
+ * @param strict true to be strict when checking ASN.1 value lengths, false to
+ *          allow truncated values (default: true).
+ *
+ * @return the certificate.
+ */
+pki.certificateFromPem = function(pem, computeHash, strict) {
+  var msg = forge.pem.decode(pem)[0];
+
+  if(msg.type !== 'CERTIFICATE' &&
+    msg.type !== 'X509 CERTIFICATE' &&
+    msg.type !== 'TRUSTED CERTIFICATE') {
+    var error = new Error('Could not convert certificate from PEM; PEM header type ' +
+      'is not "CERTIFICATE", "X509 CERTIFICATE", or "TRUSTED CERTIFICATE".');
+    error.headerType = msg.type;
+    throw error;
+  }
+  if(msg.procType && msg.procType.type === 'ENCRYPTED') {
+    throw new Error('Could not convert certificate from PEM; PEM is encrypted.');
+  }
+
+  // convert DER to ASN.1 object
+  var obj = asn1.fromDer(msg.body, strict);
+
+  return pki.certificateFromAsn1(obj, computeHash);
+};
+
+/**
+ * Converts an X.509 certificate to PEM format.
+ *
+ * @param cert the certificate.
+ * @param maxline the maximum characters per line, defaults to 64.
+ *
+ * @return the PEM-formatted certificate.
+ */
+pki.certificateToPem = function(cert, maxline) {
+  // convert to ASN.1, then DER, then PEM-encode
+  var msg = {
+    type: 'CERTIFICATE',
+    body: asn1.toDer(pki.certificateToAsn1(cert)).getBytes()
+  };
+  return forge.pem.encode(msg, {maxline: maxline});
+};
+
+/**
+ * Converts an RSA public key from PEM format.
+ *
+ * @param pem the PEM-formatted public key.
+ *
+ * @return the public key.
+ */
+pki.publicKeyFromPem = function(pem) {
+  var msg = forge.pem.decode(pem)[0];
+
+  if(msg.type !== 'PUBLIC KEY' && msg.type !== 'RSA PUBLIC KEY') {
+    var error = new Error('Could not convert public key from PEM; PEM header ' +
+      'type is not "PUBLIC KEY" or "RSA PUBLIC KEY".');
+    error.headerType = msg.type;
+    throw error;
+  }
+  if(msg.procType && msg.procType.type === 'ENCRYPTED') {
+    throw new Error('Could not convert public key from PEM; PEM is encrypted.');
+  }
+
+  // convert DER to ASN.1 object
+  var obj = asn1.fromDer(msg.body);
+
+  return pki.publicKeyFromAsn1(obj);
+};
+
+/**
+ * Converts an RSA public key to PEM format (using a SubjectPublicKeyInfo).
+ *
+ * @param key the public key.
+ * @param maxline the maximum characters per line, defaults to 64.
+ *
+ * @return the PEM-formatted public key.
+ */
+pki.publicKeyToPem = function(key, maxline) {
+  // convert to ASN.1, then DER, then PEM-encode
+  var msg = {
+    type: 'PUBLIC KEY',
+    body: asn1.toDer(pki.publicKeyToAsn1(key)).getBytes()
+  };
+  return forge.pem.encode(msg, {maxline: maxline});
+};
+
+/**
+ * Converts an RSA public key to PEM format (using an RSAPublicKey).
+ *
+ * @param key the public key.
+ * @param maxline the maximum characters per line, defaults to 64.
+ *
+ * @return the PEM-formatted public key.
+ */
+pki.publicKeyToRSAPublicKeyPem = function(key, maxline) {
+  // convert to ASN.1, then DER, then PEM-encode
+  var msg = {
+    type: 'RSA PUBLIC KEY',
+    body: asn1.toDer(pki.publicKeyToRSAPublicKey(key)).getBytes()
+  };
+  return forge.pem.encode(msg, {maxline: maxline});
+};
+
+/**
+ * Gets a fingerprint for the given public key.
+ *
+ * @param options the options to use.
+ *          [md] the message digest object to use (defaults to forge.md.sha1).
+ *          [type] the type of fingerprint, such as 'RSAPublicKey',
+ *            'SubjectPublicKeyInfo' (defaults to 'RSAPublicKey').
+ *          [encoding] an alternative output encoding, such as 'hex'
+ *            (defaults to none, outputs a byte buffer).
+ *          [delimiter] the delimiter to use between bytes for 'hex' encoded
+ *            output, eg: ':' (defaults to none).
+ *
+ * @return the fingerprint as a byte buffer or other encoding based on options.
+ */
+pki.getPublicKeyFingerprint = function(key, options) {
+  options = options || {};
+  var md = options.md || forge.md.sha1.create();
+  var type = options.type || 'RSAPublicKey';
+
+  var bytes;
+  switch(type) {
+  case 'RSAPublicKey':
+    bytes = asn1.toDer(pki.publicKeyToRSAPublicKey(key)).getBytes();
+    break;
+  case 'SubjectPublicKeyInfo':
+    bytes = asn1.toDer(pki.publicKeyToAsn1(key)).getBytes();
+    break;
+  default:
+    throw new Error('Unknown fingerprint type "' + options.type + '".');
+  }
+
+  // hash public key bytes
+  md.start();
+  md.update(bytes);
+  var digest = md.digest();
+  if(options.encoding === 'hex') {
+    var hex = digest.toHex();
+    if(options.delimiter) {
+      return hex.match(/.{2}/g).join(options.delimiter);
+    }
+    return hex;
+  } else if(options.encoding === 'binary') {
+    return digest.getBytes();
+  } else if(options.encoding) {
+    throw new Error('Unknown encoding "' + options.encoding + '".');
+  }
+  return digest;
+};
+
+/**
+ * Converts a PKCS#10 certification request (CSR) from PEM format.
+ *
+ * Note: If the certification request is to be verified then compute hash
+ * should be set to true. This will scan the CertificationRequestInfo part of
+ * the ASN.1 object while it is converted so it doesn't need to be converted
+ * back to ASN.1-DER-encoding later.
+ *
+ * @param pem the PEM-formatted certificate.
+ * @param computeHash true to compute the hash for verification.
+ * @param strict true to be strict when checking ASN.1 value lengths, false to
+ *          allow truncated values (default: true).
+ *
+ * @return the certification request (CSR).
+ */
+pki.certificationRequestFromPem = function(pem, computeHash, strict) {
+  var msg = forge.pem.decode(pem)[0];
+
+  if(msg.type !== 'CERTIFICATE REQUEST') {
+    var error = new Error('Could not convert certification request from PEM; ' +
+      'PEM header type is not "CERTIFICATE REQUEST".');
+    error.headerType = msg.type;
+    throw error;
+  }
+  if(msg.procType && msg.procType.type === 'ENCRYPTED') {
+    throw new Error('Could not convert certification request from PEM; ' +
+      'PEM is encrypted.');
+  }
+
+  // convert DER to ASN.1 object
+  var obj = asn1.fromDer(msg.body, strict);
+
+  return pki.certificationRequestFromAsn1(obj, computeHash);
+};
+
+/**
+ * Converts a PKCS#10 certification request (CSR) to PEM format.
+ *
+ * @param csr the certification request.
+ * @param maxline the maximum characters per line, defaults to 64.
+ *
+ * @return the PEM-formatted certification request.
+ */
+pki.certificationRequestToPem = function(csr, maxline) {
+  // convert to ASN.1, then DER, then PEM-encode
+  var msg = {
+    type: 'CERTIFICATE REQUEST',
+    body: asn1.toDer(pki.certificationRequestToAsn1(csr)).getBytes()
+  };
+  return forge.pem.encode(msg, {maxline: maxline});
+};
+
+/**
+ * Creates an empty X.509v3 RSA certificate.
+ *
+ * @return the certificate.
+ */
+pki.createCertificate = function() {
+  var cert = {};
+  cert.version = 0x02;
+  cert.serialNumber = '00';
+  cert.signatureOid = null;
+  cert.signature = null;
+  cert.siginfo = {};
+  cert.siginfo.algorithmOid = null;
+  cert.validity = {};
+  cert.validity.notBefore = new Date();
+  cert.validity.notAfter = new Date();
+
+  cert.issuer = {};
+  cert.issuer.getField = function(sn) {
+    return _getAttribute(cert.issuer, sn);
+  };
+  cert.issuer.addField = function(attr) {
+    _fillMissingFields([attr]);
+    cert.issuer.attributes.push(attr);
+  };
+  cert.issuer.attributes = [];
+  cert.issuer.hash = null;
+
+  cert.subject = {};
+  cert.subject.getField = function(sn) {
+    return _getAttribute(cert.subject, sn);
+  };
+  cert.subject.addField = function(attr) {
+    _fillMissingFields([attr]);
+    cert.subject.attributes.push(attr);
+  };
+  cert.subject.attributes = [];
+  cert.subject.hash = null;
+
+  cert.extensions = [];
+  cert.publicKey = null;
+  cert.md = null;
+
+  /**
+   * Sets the subject of this certificate.
+   *
+   * @param attrs the array of subject attributes to use.
+   * @param uniqueId an optional a unique ID to use.
+   */
+  cert.setSubject = function(attrs, uniqueId) {
+    // set new attributes, clear hash
+    _fillMissingFields(attrs);
+    cert.subject.attributes = attrs;
+    delete cert.subject.uniqueId;
+    if(uniqueId) {
+      cert.subject.uniqueId = uniqueId;
+    }
+    cert.subject.hash = null;
+  };
+
+  /**
+   * Sets the issuer of this certificate.
+   *
+   * @param attrs the array of issuer attributes to use.
+   * @param uniqueId an optional a unique ID to use.
+   */
+  cert.setIssuer = function(attrs, uniqueId) {
+    // set new attributes, clear hash
+    _fillMissingFields(attrs);
+    cert.issuer.attributes = attrs;
+    delete cert.issuer.uniqueId;
+    if(uniqueId) {
+      cert.issuer.uniqueId = uniqueId;
+    }
+    cert.issuer.hash = null;
+  };
+
+  /**
+   * Sets the extensions of this certificate.
+   *
+   * @param exts the array of extensions to use.
+   */
+  cert.setExtensions = function(exts) {
+    for(var i = 0; i < exts.length; ++i) {
+      _fillMissingExtensionFields(exts[i], {cert: cert});
+    }
+    // set new extensions
+    cert.extensions = exts;
+  };
+
+  /**
+   * Gets an extension by its name or id.
+   *
+   * @param options the name to use or an object with:
+   *          name the name to use.
+   *          id the id to use.
+   *
+   * @return the extension or null if not found.
+   */
+  cert.getExtension = function(options) {
+    if(typeof options === 'string') {
+      options = {name: options};
+    }
+
+    var rval = null;
+    var ext;
+    for(var i = 0; rval === null && i < cert.extensions.length; ++i) {
+      ext = cert.extensions[i];
+      if(options.id && ext.id === options.id) {
+        rval = ext;
+      } else if(options.name && ext.name === options.name) {
+        rval = ext;
+      }
+    }
+    return rval;
+  };
+
+  /**
+   * Signs this certificate using the given private key.
+   *
+   * @param key the private key to sign with.
+   * @param md the message digest object to use (defaults to forge.md.sha1).
+   */
+  cert.sign = function(key, md) {
+    // TODO: get signature OID from private key
+    cert.md = md || forge.md.sha1.create();
+    var algorithmOid = oids[cert.md.algorithm + 'WithRSAEncryption'];
+    if(!algorithmOid) {
+      var error = new Error('Could not compute certificate digest. ' +
+        'Unknown message digest algorithm OID.');
+      error.algorithm = cert.md.algorithm;
+      throw error;
+    }
+    cert.signatureOid = cert.siginfo.algorithmOid = algorithmOid;
+
+    // get TBSCertificate, convert to DER
+    cert.tbsCertificate = pki.getTBSCertificate(cert);
+    var bytes = asn1.toDer(cert.tbsCertificate);
+
+    // digest and sign
+    cert.md.update(bytes.getBytes());
+    cert.signature = key.sign(cert.md);
+  };
+
+  /**
+   * Attempts verify the signature on the passed certificate using this
+   * certificate's public key.
+   *
+   * @param child the certificate to verify.
+   *
+   * @return true if verified, false if not.
+   */
+  cert.verify = function(child) {
+    var rval = false;
+
+    if(!cert.issued(child)) {
+      var issuer = child.issuer;
+      var subject = cert.subject;
+      var error = new Error('The parent certificate did not issue the given child ' +
+        'certificate; the child certificate\'s issuer does not match the ' +
+        'parent\'s subject.');
+      error.expectedIssuer = issuer.attributes;
+      error.actualIssuer = subject.attributes;
+      throw error;
+    }
+
+    var md = child.md;
+    if(md === null) {
+      // check signature OID for supported signature types
+      if(child.signatureOid in oids) {
+        var oid = oids[child.signatureOid];
+        switch(oid) {
+        case 'sha1WithRSAEncryption':
+          md = forge.md.sha1.create();
+          break;
+        case 'md5WithRSAEncryption':
+          md = forge.md.md5.create();
+          break;
+        case 'sha256WithRSAEncryption':
+          md = forge.md.sha256.create();
+          break;
+        case 'RSASSA-PSS':
+          md = forge.md.sha256.create();
+          break;
+        }
+      }
+      if(md === null) {
+        var error = new Error('Could not compute certificate digest. ' +
+          'Unknown signature OID.');
+        error.signatureOid = child.signatureOid;
+        throw error;
+      }
+
+      // produce DER formatted TBSCertificate and digest it
+      var tbsCertificate = child.tbsCertificate || pki.getTBSCertificate(child);
+      var bytes = asn1.toDer(tbsCertificate);
+      md.update(bytes.getBytes());
+    }
+
+    if(md !== null) {
+      var scheme;
+
+      switch(child.signatureOid) {
+      case oids.sha1WithRSAEncryption:
+        scheme = undefined;  /* use PKCS#1 v1.5 padding scheme */
+        break;
+      case oids['RSASSA-PSS']:
+        var hash, mgf;
+
+        /* initialize mgf */
+        hash = oids[child.signatureParameters.mgf.hash.algorithmOid];
+        if(hash === undefined || forge.md[hash] === undefined) {
+          var error = new Error('Unsupported MGF hash function.');
+          error.oid = child.signatureParameters.mgf.hash.algorithmOid;
+          error.name = hash;
+          throw error;
+        }
+
+        mgf = oids[child.signatureParameters.mgf.algorithmOid];
+        if(mgf === undefined || forge.mgf[mgf] === undefined) {
+          var error = new Error('Unsupported MGF function.');
+          error.oid = child.signatureParameters.mgf.algorithmOid;
+          error.name = mgf;
+          throw error;
+        }
+
+        mgf = forge.mgf[mgf].create(forge.md[hash].create());
+
+        /* initialize hash function */
+        hash = oids[child.signatureParameters.hash.algorithmOid];
+        if(hash === undefined || forge.md[hash] === undefined) {
+          throw {
+            message: 'Unsupported RSASSA-PSS hash function.',
+            oid: child.signatureParameters.hash.algorithmOid,
+            name: hash
+          };
+        }
+
+        scheme = forge.pss.create(forge.md[hash].create(), mgf,
+          child.signatureParameters.saltLength);
+        break;
+      }
+
+      // verify signature on cert using public key
+      rval = cert.publicKey.verify(
+        md.digest().getBytes(), child.signature, scheme);
+    }
+
+    return rval;
+  };
+
+  /**
+   * Returns true if this certificate's issuer matches the passed
+   * certificate's subject. Note that no signature check is performed.
+   *
+   * @param parent the certificate to check.
+   *
+   * @return true if this certificate's issuer matches the passed certificate's
+   *         subject.
+   */
+  cert.isIssuer = function(parent) {
+    var rval = false;
+
+    var i = cert.issuer;
+    var s = parent.subject;
+
+    // compare hashes if present
+    if(i.hash && s.hash) {
+      rval = (i.hash === s.hash);
+    } else if(i.attributes.length === s.attributes.length) {
+      // all attributes are the same so issuer matches subject
+      rval = true;
+      var iattr, sattr;
+      for(var n = 0; rval && n < i.attributes.length; ++n) {
+        iattr = i.attributes[n];
+        sattr = s.attributes[n];
+        if(iattr.type !== sattr.type || iattr.value !== sattr.value) {
+          // attribute mismatch
+          rval = false;
+        }
+      }
+    }
+
+    return rval;
+  };
+
+  /**
+   * Returns true if this certificate's subject matches the issuer of the
+   * given certificate). Note that not signature check is performed.
+   *
+   * @param child the certificate to check.
+   *
+   * @return true if this certificate's subject matches the passed
+   *         certificate's issuer.
+   */
+  cert.issued = function(child) {
+    return child.isIssuer(cert);
+  };
+
+  /**
+   * Generates the subjectKeyIdentifier for this certificate as byte buffer.
+   *
+   * @return the subjectKeyIdentifier for this certificate as byte buffer.
+   */
+  cert.generateSubjectKeyIdentifier = function() {
+    /* See: 4.2.1.2 section of the the RFC3280, keyIdentifier is either:
+
+      (1) The keyIdentifier is composed of the 160-bit SHA-1 hash of the
+        value of the BIT STRING subjectPublicKey (excluding the tag,
+        length, and number of unused bits).
+
+      (2) The keyIdentifier is composed of a four bit type field with
+        the value 0100 followed by the least significant 60 bits of the
+        SHA-1 hash of the value of the BIT STRING subjectPublicKey
+        (excluding the tag, length, and number of unused bit string bits).
+    */
+
+    // skipping the tag, length, and number of unused bits is the same
+    // as just using the RSAPublicKey (for RSA keys, which are the
+    // only ones supported)
+    return pki.getPublicKeyFingerprint(cert.publicKey, {type: 'RSAPublicKey'});
+  };
+
+  /**
+   * Verifies the subjectKeyIdentifier extension value for this certificate
+   * against its public key. If no extension is found, false will be
+   * returned.
+   *
+   * @return true if verified, false if not.
+   */
+  cert.verifySubjectKeyIdentifier = function() {
+    var oid = oids['subjectKeyIdentifier'];
+    for(var i = 0; i < cert.extensions.length; ++i) {
+      var ext = cert.extensions[i];
+      if(ext.id === oid) {
+        var ski = cert.generateSubjectKeyIdentifier().getBytes();
+        return (forge.util.hexToBytes(ext.subjectKeyIdentifier) === ski);
+      }
+    }
+    return false;
+  };
+
+  return cert;
+};
+
+/**
+ * Converts an X.509v3 RSA certificate from an ASN.1 object.
+ *
+ * Note: If the certificate is to be verified then compute hash should
+ * be set to true. There is currently no implementation for converting
+ * a certificate back to ASN.1 so the TBSCertificate part of the ASN.1
+ * object needs to be scanned before the cert object is created.
+ *
+ * @param obj the asn1 representation of an X.509v3 RSA certificate.
+ * @param computeHash true to compute the hash for verification.
+ *
+ * @return the certificate.
+ */
+pki.certificateFromAsn1 = function(obj, computeHash) {
+  // validate certificate and capture data
+  var capture = {};
+  var errors = [];
+  if(!asn1.validate(obj, x509CertificateValidator, capture, errors)) {
+    var error = new Error('Cannot read X.509 certificate. ' +
+      'ASN.1 object is not an X509v3 Certificate.');
+    error.errors = errors;
+    throw error;
+  }
+
+  // ensure signature is not interpreted as an embedded ASN.1 object
+  if(typeof capture.certSignature !== 'string') {
+    var certSignature = '\x00';
+    for(var i = 0; i < capture.certSignature.length; ++i) {
+      certSignature += asn1.toDer(capture.certSignature[i]).getBytes();
+    }
+    capture.certSignature = certSignature;
+  }
+
+  // get oid
+  var oid = asn1.derToOid(capture.publicKeyOid);
+  if(oid !== pki.oids['rsaEncryption']) {
+    throw new Error('Cannot read public key. OID is not RSA.');
+  }
+
+  // create certificate
+  var cert = pki.createCertificate();
+  cert.version = capture.certVersion ?
+    capture.certVersion.charCodeAt(0) : 0;
+  var serial = forge.util.createBuffer(capture.certSerialNumber);
+  cert.serialNumber = serial.toHex();
+  cert.signatureOid = forge.asn1.derToOid(capture.certSignatureOid);
+  cert.signatureParameters = _readSignatureParameters(
+    cert.signatureOid, capture.certSignatureParams, true);
+  cert.siginfo.algorithmOid = forge.asn1.derToOid(capture.certinfoSignatureOid);
+  cert.siginfo.parameters = _readSignatureParameters(cert.siginfo.algorithmOid,
+    capture.certinfoSignatureParams, false);
+  // skip "unused bits" in signature value BITSTRING
+  var signature = forge.util.createBuffer(capture.certSignature);
+  ++signature.read;
+  cert.signature = signature.getBytes();
+
+  var validity = [];
+  if(capture.certValidity1UTCTime !== undefined) {
+    validity.push(asn1.utcTimeToDate(capture.certValidity1UTCTime));
+  }
+  if(capture.certValidity2GeneralizedTime !== undefined) {
+    validity.push(asn1.generalizedTimeToDate(
+      capture.certValidity2GeneralizedTime));
+  }
+  if(capture.certValidity3UTCTime !== undefined) {
+    validity.push(asn1.utcTimeToDate(capture.certValidity3UTCTime));
+  }
+  if(capture.certValidity4GeneralizedTime !== undefined) {
+    validity.push(asn1.generalizedTimeToDate(
+      capture.certValidity4GeneralizedTime));
+  }
+  if(validity.length > 2) {
+    throw new Error('Cannot read notBefore/notAfter validity times; more ' +
+      'than two times were provided in the certificate.');
+  }
+  if(validity.length < 2) {
+    throw new Error('Cannot read notBefore/notAfter validity times; they ' +
+      'were not provided as either UTCTime or GeneralizedTime.');
+  }
+  cert.validity.notBefore = validity[0];
+  cert.validity.notAfter = validity[1];
+
+  // keep TBSCertificate to preserve signature when exporting
+  cert.tbsCertificate = capture.tbsCertificate;
+
+  if(computeHash) {
+    // check signature OID for supported signature types
+    cert.md = null;
+    if(cert.signatureOid in oids) {
+      var oid = oids[cert.signatureOid];
+      switch(oid) {
+      case 'sha1WithRSAEncryption':
+        cert.md = forge.md.sha1.create();
+        break;
+      case 'md5WithRSAEncryption':
+        cert.md = forge.md.md5.create();
+        break;
+      case 'sha256WithRSAEncryption':
+        cert.md = forge.md.sha256.create();
+        break;
+      case 'RSASSA-PSS':
+        cert.md = forge.md.sha256.create();
+        break;
+      }
+    }
+    if(cert.md === null) {
+      var error = new Error('Could not compute certificate digest. ' +
+        'Unknown signature OID.');
+      error.signatureOid = cert.signatureOid;
+      throw error;
+    }
+
+    // produce DER formatted TBSCertificate and digest it
+    var bytes = asn1.toDer(cert.tbsCertificate);
+    cert.md.update(bytes.getBytes());
+  }
+
+  // handle issuer, build issuer message digest
+  var imd = forge.md.sha1.create();
+  cert.issuer.getField = function(sn) {
+    return _getAttribute(cert.issuer, sn);
+  };
+  cert.issuer.addField = function(attr) {
+    _fillMissingFields([attr]);
+    cert.issuer.attributes.push(attr);
+  };
+  cert.issuer.attributes = pki.RDNAttributesAsArray(capture.certIssuer, imd);
+  if(capture.certIssuerUniqueId) {
+    cert.issuer.uniqueId = capture.certIssuerUniqueId;
+  }
+  cert.issuer.hash = imd.digest().toHex();
+
+  // handle subject, build subject message digest
+  var smd = forge.md.sha1.create();
+  cert.subject.getField = function(sn) {
+    return _getAttribute(cert.subject, sn);
+  };
+  cert.subject.addField = function(attr) {
+    _fillMissingFields([attr]);
+    cert.subject.attributes.push(attr);
+  };
+  cert.subject.attributes = pki.RDNAttributesAsArray(capture.certSubject, smd);
+  if(capture.certSubjectUniqueId) {
+    cert.subject.uniqueId = capture.certSubjectUniqueId;
+  }
+  cert.subject.hash = smd.digest().toHex();
+
+  // handle extensions
+  if(capture.certExtensions) {
+    cert.extensions = pki.certificateExtensionsFromAsn1(capture.certExtensions);
+  } else {
+    cert.extensions = [];
+  }
+
+  // convert RSA public key from ASN.1
+  cert.publicKey = pki.publicKeyFromAsn1(capture.subjectPublicKeyInfo);
+
+  return cert;
+};
+
+/**
+ * Converts an ASN.1 extensions object (with extension sequences as its
+ * values) into an array of extension objects with types and values.
+ *
+ * Supported extensions:
+ *
+ * id-ce-keyUsage OBJECT IDENTIFIER ::=  { id-ce 15 }
+ * KeyUsage ::= BIT STRING {
+ *   digitalSignature        (0),
+ *   nonRepudiation          (1),
+ *   keyEncipherment         (2),
+ *   dataEncipherment        (3),
+ *   keyAgreement            (4),
+ *   keyCertSign             (5),
+ *   cRLSign                 (6),
+ *   encipherOnly            (7),
+ *   decipherOnly            (8)
+ * }
+ *
+ * id-ce-basicConstraints OBJECT IDENTIFIER ::=  { id-ce 19 }
+ * BasicConstraints ::= SEQUENCE {
+ *   cA                      BOOLEAN DEFAULT FALSE,
+ *   pathLenConstraint       INTEGER (0..MAX) OPTIONAL
+ * }
+ *
+ * subjectAltName EXTENSION ::= {
+ *   SYNTAX GeneralNames
+ *   IDENTIFIED BY id-ce-subjectAltName
+ * }
+ *
+ * GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
+ *
+ * GeneralName ::= CHOICE {
+ *   otherName      [0] INSTANCE OF OTHER-NAME,
+ *   rfc822Name     [1] IA5String,
+ *   dNSName        [2] IA5String,
+ *   x400Address    [3] ORAddress,
+ *   directoryName  [4] Name,
+ *   ediPartyName   [5] EDIPartyName,
+ *   uniformResourceIdentifier [6] IA5String,
+ *   IPAddress      [7] OCTET STRING,
+ *   registeredID   [8] OBJECT IDENTIFIER
+ * }
+ *
+ * OTHER-NAME ::= TYPE-IDENTIFIER
+ *
+ * EDIPartyName ::= SEQUENCE {
+ *   nameAssigner [0] DirectoryString {ub-name} OPTIONAL,
+ *   partyName    [1] DirectoryString {ub-name}
+ * }
+ *
+ * @param exts the extensions ASN.1 with extension sequences to parse.
+ *
+ * @return the array.
+ */
+pki.certificateExtensionsFromAsn1 = function(exts) {
+  var rval = [];
+  for(var i = 0; i < exts.value.length; ++i) {
+    // get extension sequence
+    var extseq = exts.value[i];
+    for(var ei = 0; ei < extseq.value.length; ++ei) {
+      rval.push(pki.certificateExtensionFromAsn1(extseq.value[ei]));
+    }
+  }
+
+  return rval;
+};
+
+/**
+ * Parses a single certificate extension from ASN.1.
+ *
+ * @param ext the extension in ASN.1 format.
+ *
+ * @return the parsed extension as an object.
+ */
+pki.certificateExtensionFromAsn1 = function(ext) {
+  // an extension has:
+  // [0] extnID      OBJECT IDENTIFIER
+  // [1] critical    BOOLEAN DEFAULT FALSE
+  // [2] extnValue   OCTET STRING
+  var e = {};
+  e.id = asn1.derToOid(ext.value[0].value);
+  e.critical = false;
+  if(ext.value[1].type === asn1.Type.BOOLEAN) {
+    e.critical = (ext.value[1].value.charCodeAt(0) !== 0x00);
+    e.value = ext.value[2].value;
+  } else {
+    e.value = ext.value[1].value;
+  }
+  // if the oid is known, get its name
+  if(e.id in oids) {
+    e.name = oids[e.id];
+
+    // handle key usage
+    if(e.name === 'keyUsage') {
+      // get value as BIT STRING
+      var ev = asn1.fromDer(e.value);
+      var b2 = 0x00;
+      var b3 = 0x00;
+      if(ev.value.length > 1) {
+        // skip first byte, just indicates unused bits which
+        // will be padded with 0s anyway
+        // get bytes with flag bits
+        b2 = ev.value.charCodeAt(1);
+        b3 = ev.value.length > 2 ? ev.value.charCodeAt(2) : 0;
+      }
+      // set flags
+      e.digitalSignature = (b2 & 0x80) === 0x80;
+      e.nonRepudiation = (b2 & 0x40) === 0x40;
+      e.keyEncipherment = (b2 & 0x20) === 0x20;
+      e.dataEncipherment = (b2 & 0x10) === 0x10;
+      e.keyAgreement = (b2 & 0x08) === 0x08;
+      e.keyCertSign = (b2 & 0x04) === 0x04;
+      e.cRLSign = (b2 & 0x02) === 0x02;
+      e.encipherOnly = (b2 & 0x01) === 0x01;
+      e.decipherOnly = (b3 & 0x80) === 0x80;
+    } else if(e.name === 'basicConstraints') {
+      // handle basic constraints
+      // get value as SEQUENCE
+      var ev = asn1.fromDer(e.value);
+      // get cA BOOLEAN flag (defaults to false)
+      if(ev.value.length > 0 && ev.value[0].type === asn1.Type.BOOLEAN) {
+        e.cA = (ev.value[0].value.charCodeAt(0) !== 0x00);
+      } else {
+        e.cA = false;
+      }
+      // get path length constraint
+      var value = null;
+      if(ev.value.length > 0 && ev.value[0].type === asn1.Type.INTEGER) {
+        value = ev.value[0].value;
+      } else if(ev.value.length > 1) {
+        value = ev.value[1].value;
+      }
+      if(value !== null) {
+        e.pathLenConstraint = asn1.derToInteger(value);
+      }
+    } else if(e.name === 'extKeyUsage') {
+      // handle extKeyUsage
+      // value is a SEQUENCE of OIDs
+      var ev = asn1.fromDer(e.value);
+      for(var vi = 0; vi < ev.value.length; ++vi) {
+        var oid = asn1.derToOid(ev.value[vi].value);
+        if(oid in oids) {
+          e[oids[oid]] = true;
+        } else {
+          e[oid] = true;
+        }
+      }
+    } else if(e.name === 'nsCertType') {
+      // handle nsCertType
+      // get value as BIT STRING
+      var ev = asn1.fromDer(e.value);
+      var b2 = 0x00;
+      if(ev.value.length > 1) {
+        // skip first byte, just indicates unused bits which
+        // will be padded with 0s anyway
+        // get bytes with flag bits
+        b2 = ev.value.charCodeAt(1);
+      }
+      // set flags
+      e.client = (b2 & 0x80) === 0x80;
+      e.server = (b2 & 0x40) === 0x40;
+      e.email = (b2 & 0x20) === 0x20;
+      e.objsign = (b2 & 0x10) === 0x10;
+      e.reserved = (b2 & 0x08) === 0x08;
+      e.sslCA = (b2 & 0x04) === 0x04;
+      e.emailCA = (b2 & 0x02) === 0x02;
+      e.objCA = (b2 & 0x01) === 0x01;
+    } else if(
+      e.name === 'subjectAltName' ||
+      e.name === 'issuerAltName') {
+      // handle subjectAltName/issuerAltName
+      e.altNames = [];
+
+      // ev is a SYNTAX SEQUENCE
+      var gn;
+      var ev = asn1.fromDer(e.value);
+      for(var n = 0; n < ev.value.length; ++n) {
+        // get GeneralName
+        gn = ev.value[n];
+
+        var altName = {
+          type: gn.type,
+          value: gn.value
+        };
+        e.altNames.push(altName);
+
+        // Note: Support for types 1,2,6,7,8
+        switch(gn.type) {
+        // rfc822Name
+        case 1:
+        // dNSName
+        case 2:
+        // uniformResourceIdentifier (URI)
+        case 6:
+          break;
+        // IPAddress
+        case 7:
+          // convert to IPv4/IPv6 string representation
+          altName.ip = forge.util.bytesToIP(gn.value);
+          break;
+        // registeredID
+        case 8:
+          altName.oid = asn1.derToOid(gn.value);
+          break;
+        default:
+          // unsupported
+        }
+      }
+    } else if(e.name === 'subjectKeyIdentifier') {
+      // value is an OCTETSTRING w/the hash of the key-type specific
+      // public key structure (eg: RSAPublicKey)
+      var ev = asn1.fromDer(e.value);
+      e.subjectKeyIdentifier = forge.util.bytesToHex(ev.value);
+    }
+  }
+  return e;
+};
+
+/**
+ * Converts a PKCS#10 certification request (CSR) from an ASN.1 object.
+ *
+ * Note: If the certification request is to be verified then compute hash
+ * should be set to true. There is currently no implementation for converting
+ * a certificate back to ASN.1 so the CertificationRequestInfo part of the
+ * ASN.1 object needs to be scanned before the csr object is created.
+ *
+ * @param obj the asn1 representation of a PKCS#10 certification request (CSR).
+ * @param computeHash true to compute the hash for verification.
+ *
+ * @return the certification request (CSR).
+ */
+pki.certificationRequestFromAsn1 = function(obj, computeHash) {
+  // validate certification request and capture data
+  var capture = {};
+  var errors = [];
+  if(!asn1.validate(obj, certificationRequestValidator, capture, errors)) {
+    var error = new Error('Cannot read PKCS#10 certificate request. ' +
+      'ASN.1 object is not a PKCS#10 CertificationRequest.');
+    error.errors = errors;
+    throw error;
+  }
+
+  // ensure signature is not interpreted as an embedded ASN.1 object
+  if(typeof capture.csrSignature !== 'string') {
+    var csrSignature = '\x00';
+    for(var i = 0; i < capture.csrSignature.length; ++i) {
+      csrSignature += asn1.toDer(capture.csrSignature[i]).getBytes();
+    }
+    capture.csrSignature = csrSignature;
+  }
+
+  // get oid
+  var oid = asn1.derToOid(capture.publicKeyOid);
+  if(oid !== pki.oids.rsaEncryption) {
+    throw new Error('Cannot read public key. OID is not RSA.');
+  }
+
+  // create certification request
+  var csr = pki.createCertificationRequest();
+  csr.version = capture.csrVersion ? capture.csrVersion.charCodeAt(0) : 0;
+  csr.signatureOid = forge.asn1.derToOid(capture.csrSignatureOid);
+  csr.signatureParameters = _readSignatureParameters(
+    csr.signatureOid, capture.csrSignatureParams, true);
+  csr.siginfo.algorithmOid = forge.asn1.derToOid(capture.csrSignatureOid);
+  csr.siginfo.parameters = _readSignatureParameters(
+    csr.siginfo.algorithmOid, capture.csrSignatureParams, false);
+  // skip "unused bits" in signature value BITSTRING
+  var signature = forge.util.createBuffer(capture.csrSignature);
+  ++signature.read;
+  csr.signature = signature.getBytes();
+
+  // keep CertificationRequestInfo to preserve signature when exporting
+  csr.certificationRequestInfo = capture.certificationRequestInfo;
+
+  if(computeHash) {
+    // check signature OID for supported signature types
+    csr.md = null;
+    if(csr.signatureOid in oids) {
+      var oid = oids[csr.signatureOid];
+      switch(oid) {
+      case 'sha1WithRSAEncryption':
+        csr.md = forge.md.sha1.create();
+        break;
+      case 'md5WithRSAEncryption':
+        csr.md = forge.md.md5.create();
+        break;
+      case 'sha256WithRSAEncryption':
+        csr.md = forge.md.sha256.create();
+        break;
+      case 'RSASSA-PSS':
+        csr.md = forge.md.sha256.create();
+        break;
+      }
+    }
+    if(csr.md === null) {
+      var error = new Error('Could not compute certification request digest. ' +
+        'Unknown signature OID.');
+      error.signatureOid = csr.signatureOid;
+      throw error;
+    }
+
+    // produce DER formatted CertificationRequestInfo and digest it
+    var bytes = asn1.toDer(csr.certificationRequestInfo);
+    csr.md.update(bytes.getBytes());
+  }
+
+  // handle subject, build subject message digest
+  var smd = forge.md.sha1.create();
+  csr.subject.getField = function(sn) {
+    return _getAttribute(csr.subject, sn);
+  };
+  csr.subject.addField = function(attr) {
+    _fillMissingFields([attr]);
+    csr.subject.attributes.push(attr);
+  };
+  csr.subject.attributes = pki.RDNAttributesAsArray(
+    capture.certificationRequestInfoSubject, smd);
+  csr.subject.hash = smd.digest().toHex();
+
+  // convert RSA public key from ASN.1
+  csr.publicKey = pki.publicKeyFromAsn1(capture.subjectPublicKeyInfo);
+
+  // convert attributes from ASN.1
+  csr.getAttribute = function(sn) {
+    return _getAttribute(csr, sn);
+  };
+  csr.addAttribute = function(attr) {
+    _fillMissingFields([attr]);
+    csr.attributes.push(attr);
+  };
+  csr.attributes = pki.CRIAttributesAsArray(
+    capture.certificationRequestInfoAttributes || []);
+
+  return csr;
+};
+
+/**
+ * Creates an empty certification request (a CSR or certificate signing
+ * request). Once created, its public key and attributes can be set and then
+ * it can be signed.
+ *
+ * @return the empty certification request.
+ */
+pki.createCertificationRequest = function() {
+  var csr = {};
+  csr.version = 0x00;
+  csr.signatureOid = null;
+  csr.signature = null;
+  csr.siginfo = {};
+  csr.siginfo.algorithmOid = null;
+
+  csr.subject = {};
+  csr.subject.getField = function(sn) {
+    return _getAttribute(csr.subject, sn);
+  };
+  csr.subject.addField = function(attr) {
+    _fillMissingFields([attr]);
+    csr.subject.attributes.push(attr);
+  };
+  csr.subject.attributes = [];
+  csr.subject.hash = null;
+
+  csr.publicKey = null;
+  csr.attributes = [];
+  csr.getAttribute = function(sn) {
+    return _getAttribute(csr, sn);
+  };
+  csr.addAttribute = function(attr) {
+    _fillMissingFields([attr]);
+    csr.attributes.push(attr);
+  };
+  csr.md = null;
+
+  /**
+   * Sets the subject of this certification request.
+   *
+   * @param attrs the array of subject attributes to use.
+   */
+  csr.setSubject = function(attrs) {
+    // set new attributes
+    _fillMissingFields(attrs);
+    csr.subject.attributes = attrs;
+    csr.subject.hash = null;
+  };
+
+  /**
+   * Sets the attributes of this certification request.
+   *
+   * @param attrs the array of attributes to use.
+   */
+  csr.setAttributes = function(attrs) {
+    // set new attributes
+    _fillMissingFields(attrs);
+    csr.attributes = attrs;
+  };
+
+  /**
+   * Signs this certification request using the given private key.
+   *
+   * @param key the private key to sign with.
+   * @param md the message digest object to use (defaults to forge.md.sha1).
+   */
+  csr.sign = function(key, md) {
+    // TODO: get signature OID from private key
+    csr.md = md || forge.md.sha1.create();
+    var algorithmOid = oids[csr.md.algorithm + 'WithRSAEncryption'];
+    if(!algorithmOid) {
+      var error = new Error('Could not compute certification request digest. ' +
+        'Unknown message digest algorithm OID.');
+      error.algorithm = csr.md.algorithm;
+      throw error;
+    }
+    csr.signatureOid = csr.siginfo.algorithmOid = algorithmOid;
+
+    // get CertificationRequestInfo, convert to DER
+    csr.certificationRequestInfo = pki.getCertificationRequestInfo(csr);
+    var bytes = asn1.toDer(csr.certificationRequestInfo);
+
+    // digest and sign
+    csr.md.update(bytes.getBytes());
+    csr.signature = key.sign(csr.md);
+  };
+
+  /**
+   * Attempts verify the signature on the passed certification request using
+   * its public key.
+   *
+   * A CSR that has been exported to a file in PEM format can be verified using
+   * OpenSSL using this command:
+   *
+   * openssl req -in <the-csr-pem-file> -verify -noout -text
+   *
+   * @return true if verified, false if not.
+   */
+  csr.verify = function() {
+    var rval = false;
+
+    var md = csr.md;
+    if(md === null) {
+      // check signature OID for supported signature types
+      if(csr.signatureOid in oids) {
+        var oid = oids[csr.signatureOid];
+        switch(oid) {
+        case 'sha1WithRSAEncryption':
+          md = forge.md.sha1.create();
+          break;
+        case 'md5WithRSAEncryption':
+          md = forge.md.md5.create();
+          break;
+        case 'sha256WithRSAEncryption':
+          md = forge.md.sha256.create();
+          break;
+        case 'RSASSA-PSS':
+          md = forge.md.sha256.create();
+          break;
+        }
+      }
+      if(md === null) {
+        var error = new Error('Could not compute certification request digest. ' +
+          'Unknown signature OID.');
+        error.signatureOid = csr.signatureOid;
+        throw error;
+      }
+
+      // produce DER formatted CertificationRequestInfo and digest it
+      var cri = csr.certificationRequestInfo ||
+        pki.getCertificationRequestInfo(csr);
+      var bytes = asn1.toDer(cri);
+      md.update(bytes.getBytes());
+    }
+
+    if(md !== null) {
+      var scheme;
+
+      switch(csr.signatureOid) {
+      case oids.sha1WithRSAEncryption:
+        /* use PKCS#1 v1.5 padding scheme */
+        break;
+      case oids['RSASSA-PSS']:
+        var hash, mgf;
+
+        /* initialize mgf */
+        hash = oids[csr.signatureParameters.mgf.hash.algorithmOid];
+        if(hash === undefined || forge.md[hash] === undefined) {
+          var error = new Error('Unsupported MGF hash function.');
+          error.oid = csr.signatureParameters.mgf.hash.algorithmOid;
+          error.name = hash;
+          throw error;
+        }
+
+        mgf = oids[csr.signatureParameters.mgf.algorithmOid];
+        if(mgf === undefined || forge.mgf[mgf] === undefined) {
+          var error = new Error('Unsupported MGF function.');
+          error.oid = csr.signatureParameters.mgf.algorithmOid;
+          error.name = mgf;
+          throw error;
+        }
+
+        mgf = forge.mgf[mgf].create(forge.md[hash].create());
+
+        /* initialize hash function */
+        hash = oids[csr.signatureParameters.hash.algorithmOid];
+        if(hash === undefined || forge.md[hash] === undefined) {
+          var error = new Error('Unsupported RSASSA-PSS hash function.');
+          error.oid = csr.signatureParameters.hash.algorithmOid;
+          error.name = hash;
+          throw error;
+        }
+
+        scheme = forge.pss.create(forge.md[hash].create(), mgf,
+          csr.signatureParameters.saltLength);
+        break;
+      }
+
+      // verify signature on csr using its public key
+      rval = csr.publicKey.verify(
+        md.digest().getBytes(), csr.signature, scheme);
+    }
+
+    return rval;
+  };
+
+  return csr;
+};
+
+/**
+ * Converts an X.509 subject or issuer to an ASN.1 RDNSequence.
+ *
+ * @param obj the subject or issuer (distinguished name).
+ *
+ * @return the ASN.1 RDNSequence.
+ */
+function _dnToAsn1(obj) {
+  // create an empty RDNSequence
+  var rval = asn1.create(
+    asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []);
+
+  // iterate over attributes
+  var attr, set;
+  var attrs = obj.attributes;
+  for(var i = 0; i < attrs.length; ++i) {
+    attr = attrs[i];
+    var value = attr.value;
+
+    // reuse tag class for attribute value if available
+    var valueTagClass = asn1.Type.PRINTABLESTRING;
+    if('valueTagClass' in attr) {
+      valueTagClass = attr.valueTagClass;
+
+      if(valueTagClass === asn1.Type.UTF8) {
+        value = forge.util.encodeUtf8(value);
+      }
+      // FIXME: handle more encodings
+    }
+
+    // create a RelativeDistinguishedName set
+    // each value in the set is an AttributeTypeAndValue first
+    // containing the type (an OID) and second the value
+    set = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true, [
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+        // AttributeType
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+          asn1.oidToDer(attr.type).getBytes()),
+        // AttributeValue
+        asn1.create(asn1.Class.UNIVERSAL, valueTagClass, false, value)
+      ])
+    ]);
+    rval.value.push(set);
+  }
+
+  return rval;
+}
+
+/**
+ * Gets all printable attributes (typically of an issuer or subject) in a
+ * simplified JSON format for display.
+ *
+ * @param attrs the attributes.
+ *
+ * @return the JSON for display.
+ */
+function _getAttributesAsJson(attrs) {
+  var rval = {};
+  for(var i = 0; i < attrs.length; ++i) {
+    var attr = attrs[i];
+    if(attr.shortName && (
+      attr.valueTagClass === asn1.Type.UTF8 ||
+      attr.valueTagClass === asn1.Type.PRINTABLESTRING ||
+      attr.valueTagClass === asn1.Type.IA5STRING)) {
+      var value = attr.value;
+      if(attr.valueTagClass === asn1.Type.UTF8) {
+        value = forge.util.encodeUtf8(attr.value);
+      }
+      if(!(attr.shortName in rval)) {
+        rval[attr.shortName] = value;
+      } else if(forge.util.isArray(rval[attr.shortName])) {
+        rval[attr.shortName].push(value);
+      } else {
+        rval[attr.shortName] = [rval[attr.shortName], value];
+      }
+    }
+  }
+  return rval;
+}
+
+/**
+ * Fills in missing fields in attributes.
+ *
+ * @param attrs the attributes to fill missing fields in.
+ */
+function _fillMissingFields(attrs) {
+  var attr;
+  for(var i = 0; i < attrs.length; ++i) {
+    attr = attrs[i];
+
+    // populate missing name
+    if(typeof attr.name === 'undefined') {
+      if(attr.type && attr.type in pki.oids) {
+        attr.name = pki.oids[attr.type];
+      } else if(attr.shortName && attr.shortName in _shortNames) {
+        attr.name = pki.oids[_shortNames[attr.shortName]];
+      }
+    }
+
+    // populate missing type (OID)
+    if(typeof attr.type === 'undefined') {
+      if(attr.name && attr.name in pki.oids) {
+        attr.type = pki.oids[attr.name];
+      } else {
+        var error = new Error('Attribute type not specified.');
+        error.attribute = attr;
+        throw error;
+      }
+    }
+
+    // populate missing shortname
+    if(typeof attr.shortName === 'undefined') {
+      if(attr.name && attr.name in _shortNames) {
+        attr.shortName = _shortNames[attr.name];
+      }
+    }
+
+    // convert extensions to value
+    if(attr.type === oids.extensionRequest) {
+      attr.valueConstructed = true;
+      attr.valueTagClass = asn1.Type.SEQUENCE;
+      if(!attr.value && attr.extensions) {
+        attr.value = [];
+        for(var ei = 0; ei < attr.extensions.length; ++ei) {
+          attr.value.push(pki.certificateExtensionToAsn1(
+            _fillMissingExtensionFields(attr.extensions[ei])));
+        }
+      }
+    }
+
+    if(typeof attr.value === 'undefined') {
+      var error = new Error('Attribute value not specified.');
+      error.attribute = attr;
+      throw error;
+    }
+  }
+}
+
+/**
+ * Fills in missing fields in certificate extensions.
+ *
+ * @param e the extension.
+ * @param [options] the options to use.
+ *          [cert] the certificate the extensions are for.
+ *
+ * @return the extension.
+ */
+function _fillMissingExtensionFields(e, options) {
+  options = options || {};
+
+  // populate missing name
+  if(typeof e.name === 'undefined') {
+    if(e.id && e.id in pki.oids) {
+      e.name = pki.oids[e.id];
+    }
+  }
+
+  // populate missing id
+  if(typeof e.id === 'undefined') {
+    if(e.name && e.name in pki.oids) {
+      e.id = pki.oids[e.name];
+    } else {
+      var error = new Error('Extension ID not specified.');
+      error.extension = e;
+      throw error;
+    }
+  }
+
+  if(typeof e.value !== 'undefined') {
+    return;
+  }
+
+  // handle missing value:
+
+  // value is a BIT STRING
+  if(e.name === 'keyUsage') {
+    // build flags
+    var unused = 0;
+    var b2 = 0x00;
+    var b3 = 0x00;
+    if(e.digitalSignature) {
+      b2 |= 0x80;
+      unused = 7;
+    }
+    if(e.nonRepudiation) {
+      b2 |= 0x40;
+      unused = 6;
+    }
+    if(e.keyEncipherment) {
+      b2 |= 0x20;
+      unused = 5;
+    }
+    if(e.dataEncipherment) {
+      b2 |= 0x10;
+      unused = 4;
+    }
+    if(e.keyAgreement) {
+      b2 |= 0x08;
+      unused = 3;
+    }
+    if(e.keyCertSign) {
+      b2 |= 0x04;
+      unused = 2;
+    }
+    if(e.cRLSign) {
+      b2 |= 0x02;
+      unused = 1;
+    }
+    if(e.encipherOnly) {
+      b2 |= 0x01;
+      unused = 0;
+    }
+    if(e.decipherOnly) {
+      b3 |= 0x80;
+      unused = 7;
+    }
+
+    // create bit string
+    var value = String.fromCharCode(unused);
+    if(b3 !== 0) {
+      value += String.fromCharCode(b2) + String.fromCharCode(b3);
+    } else if(b2 !== 0) {
+      value += String.fromCharCode(b2);
+    }
+    e.value = asn1.create(
+      asn1.Class.UNIVERSAL, asn1.Type.BITSTRING, false, value);
+  } else if(e.name === 'basicConstraints') {
+    // basicConstraints is a SEQUENCE
+    e.value = asn1.create(
+      asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []);
+    // cA BOOLEAN flag defaults to false
+    if(e.cA) {
+      e.value.value.push(asn1.create(
+        asn1.Class.UNIVERSAL, asn1.Type.BOOLEAN, false,
+        String.fromCharCode(0xFF)));
+    }
+    if('pathLenConstraint' in e) {
+      e.value.value.push(asn1.create(
+        asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+        asn1.integerToDer(e.pathLenConstraint).getBytes()));
+    }
+  } else if(e.name === 'extKeyUsage') {
+    // extKeyUsage is a SEQUENCE of OIDs
+    e.value = asn1.create(
+      asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []);
+    var seq = e.value.value;
+    for(var key in e) {
+      if(e[key] !== true) {
+        continue;
+      }
+      // key is name in OID map
+      if(key in oids) {
+        seq.push(asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID,
+          false, asn1.oidToDer(oids[key]).getBytes()));
+      } else if(key.indexOf('.') !== -1) {
+        // assume key is an OID
+        seq.push(asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID,
+          false, asn1.oidToDer(key).getBytes()));
+      }
+    }
+  } else if(e.name === 'nsCertType') {
+    // nsCertType is a BIT STRING
+    // build flags
+    var unused = 0;
+    var b2 = 0x00;
+
+    if(e.client) {
+      b2 |= 0x80;
+      unused = 7;
+    }
+    if(e.server) {
+      b2 |= 0x40;
+      unused = 6;
+    }
+    if(e.email) {
+      b2 |= 0x20;
+      unused = 5;
+    }
+    if(e.objsign) {
+      b2 |= 0x10;
+      unused = 4;
+    }
+    if(e.reserved) {
+      b2 |= 0x08;
+      unused = 3;
+    }
+    if(e.sslCA) {
+      b2 |= 0x04;
+      unused = 2;
+    }
+    if(e.emailCA) {
+      b2 |= 0x02;
+      unused = 1;
+    }
+    if(e.objCA) {
+      b2 |= 0x01;
+      unused = 0;
+    }
+
+    // create bit string
+    var value = String.fromCharCode(unused);
+    if(b2 !== 0) {
+      value += String.fromCharCode(b2);
+    }
+    e.value = asn1.create(
+      asn1.Class.UNIVERSAL, asn1.Type.BITSTRING, false, value);
+  } else if(e.name === 'subjectAltName' || e.name === 'issuerAltName') {
+    // SYNTAX SEQUENCE
+    e.value = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []);
+
+    var altName;
+    for(var n = 0; n < e.altNames.length; ++n) {
+      altName = e.altNames[n];
+      var value = altName.value;
+      // handle IP
+      if(altName.type === 7 && altName.ip) {
+        value = forge.util.bytesFromIP(altName.ip);
+        if(value === null) {
+          var error = new Error(
+            'Extension "ip" value is not a valid IPv4 or IPv6 address.');
+          error.extension = e;
+          throw error;
+        }
+      } else if(altName.type === 8) {
+        // handle OID
+        if(altName.oid) {
+          value = asn1.oidToDer(asn1.oidToDer(altName.oid));
+        } else {
+          // deprecated ... convert value to OID
+          value = asn1.oidToDer(value);
+        }
+      }
+      e.value.value.push(asn1.create(
+        asn1.Class.CONTEXT_SPECIFIC, altName.type, false,
+        value));
+    }
+  } else if(e.name === 'subjectKeyIdentifier' && options.cert) {
+    var ski = options.cert.generateSubjectKeyIdentifier();
+    e.subjectKeyIdentifier = ski.toHex();
+    // OCTETSTRING w/digest
+    e.value = asn1.create(
+      asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, ski.getBytes());
+  }
+
+  // ensure value has been defined by now
+  if(typeof e.value === 'undefined') {
+    var error = new Error('Extension value not specified.');
+    error.extension = e;
+    throw error;
+  }
+
+  return e;
+}
+
+/**
+ * Convert signature parameters object to ASN.1
+ *
+ * @param {String} oid Signature algorithm OID
+ * @param params The signature parametrs object
+ * @return ASN.1 object representing signature parameters
+ */
+function _signatureParametersToAsn1(oid, params) {
+  switch(oid) {
+  case oids['RSASSA-PSS']:
+    var parts = [];
+
+    if(params.hash.algorithmOid !== undefined) {
+      parts.push(asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+            asn1.oidToDer(params.hash.algorithmOid).getBytes()),
+          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
+        ])
+      ]));
+    }
+
+    if(params.mgf.algorithmOid !== undefined) {
+      parts.push(asn1.create(asn1.Class.CONTEXT_SPECIFIC, 1, true, [
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+            asn1.oidToDer(params.mgf.algorithmOid).getBytes()),
+          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+            asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+              asn1.oidToDer(params.mgf.hash.algorithmOid).getBytes()),
+            asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
+          ])
+        ])
+      ]));
+    }
+
+    if(params.saltLength !== undefined) {
+      parts.push(asn1.create(asn1.Class.CONTEXT_SPECIFIC, 2, true, [
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+          asn1.integerToDer(params.saltLength).getBytes())
+      ]));
+    }
+
+    return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, parts);
+
+  default:
+    return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '');
+  }
+}
+
+/**
+ * Converts a certification request's attributes to an ASN.1 set of
+ * CRIAttributes.
+ *
+ * @param csr certification request.
+ *
+ * @return the ASN.1 set of CRIAttributes.
+ */
+function _CRIAttributesToAsn1(csr) {
+  // create an empty context-specific container
+  var rval = asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, []);
+
+  // no attributes, return empty container
+  if(csr.attributes.length === 0) {
+    return rval;
+  }
+
+  // each attribute has a sequence with a type and a set of values
+  var attrs = csr.attributes;
+  for(var i = 0; i < attrs.length; ++i) {
+    var attr = attrs[i];
+    var value = attr.value;
+
+    // reuse tag class for attribute value if available
+    var valueTagClass = asn1.Type.UTF8;
+    if('valueTagClass' in attr) {
+      valueTagClass = attr.valueTagClass;
+    }
+    if(valueTagClass === asn1.Type.UTF8) {
+      value = forge.util.encodeUtf8(value);
+    }
+    var valueConstructed = false;
+    if('valueConstructed' in attr) {
+      valueConstructed = attr.valueConstructed;
+    }
+    // FIXME: handle more encodings
+
+    // create a RelativeDistinguishedName set
+    // each value in the set is an AttributeTypeAndValue first
+    // containing the type (an OID) and second the value
+    var seq = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+      // AttributeType
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+        asn1.oidToDer(attr.type).getBytes()),
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true, [
+        // AttributeValue
+        asn1.create(
+          asn1.Class.UNIVERSAL, valueTagClass, valueConstructed, value)
+      ])
+    ]);
+    rval.value.push(seq);
+  }
+
+  return rval;
+}
+
+/**
+ * Gets the ASN.1 TBSCertificate part of an X.509v3 certificate.
+ *
+ * @param cert the certificate.
+ *
+ * @return the asn1 TBSCertificate.
+ */
+pki.getTBSCertificate = function(cert) {
+  // TBSCertificate
+  var tbs = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+    // version
+    asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
+      // integer
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+        asn1.integerToDer(cert.version).getBytes())
+    ]),
+    // serialNumber
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      forge.util.hexToBytes(cert.serialNumber)),
+    // signature
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+      // algorithm
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+        asn1.oidToDer(cert.siginfo.algorithmOid).getBytes()),
+      // parameters
+      _signatureParametersToAsn1(
+        cert.siginfo.algorithmOid, cert.siginfo.parameters)
+    ]),
+    // issuer
+    _dnToAsn1(cert.issuer),
+    // validity
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+      // notBefore
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.UTCTIME, false,
+        asn1.dateToUtcTime(cert.validity.notBefore)),
+      // notAfter
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.UTCTIME, false,
+        asn1.dateToUtcTime(cert.validity.notAfter))
+    ]),
+    // subject
+    _dnToAsn1(cert.subject),
+    // SubjectPublicKeyInfo
+    pki.publicKeyToAsn1(cert.publicKey)
+  ]);
+
+  if(cert.issuer.uniqueId) {
+    // issuerUniqueID (optional)
+    tbs.value.push(
+      asn1.create(asn1.Class.CONTEXT_SPECIFIC, 1, true, [
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.BITSTRING, false,
+          String.fromCharCode(0x00) +
+          cert.issuer.uniqueId
+        )
+      ])
+    );
+  }
+  if(cert.subject.uniqueId) {
+    // subjectUniqueID (optional)
+    tbs.value.push(
+      asn1.create(asn1.Class.CONTEXT_SPECIFIC, 2, true, [
+        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.BITSTRING, false,
+          String.fromCharCode(0x00) +
+          cert.subject.uniqueId
+        )
+      ])
+    );
+  }
+
+  if(cert.extensions.length > 0) {
+    // extensions (optional)
+    tbs.value.push(pki.certificateExtensionsToAsn1(cert.extensions));
+  }
+
+  return tbs;
+};
+
+/**
+ * Gets the ASN.1 CertificationRequestInfo part of a
+ * PKCS#10 CertificationRequest.
+ *
+ * @param csr the certification request.
+ *
+ * @return the asn1 CertificationRequestInfo.
+ */
+pki.getCertificationRequestInfo = function(csr) {
+  // CertificationRequestInfo
+  var cri = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+    // version
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
+      asn1.integerToDer(csr.version).getBytes()),
+    // subject
+    _dnToAsn1(csr.subject),
+    // SubjectPublicKeyInfo
+    pki.publicKeyToAsn1(csr.publicKey),
+    // attributes
+    _CRIAttributesToAsn1(csr)
+  ]);
+
+  return cri;
+};
+
+/**
+ * Converts a DistinguishedName (subject or issuer) to an ASN.1 object.
+ *
+ * @param dn the DistinguishedName.
+ *
+ * @return the asn1 representation of a DistinguishedName.
+ */
+pki.distinguishedNameToAsn1 = function(dn) {
+  return _dnToAsn1(dn);
+};
+
+/**
+ * Converts an X.509v3 RSA certificate to an ASN.1 object.
+ *
+ * @param cert the certificate.
+ *
+ * @return the asn1 representation of an X.509v3 RSA certificate.
+ */
+pki.certificateToAsn1 = function(cert) {
+  // prefer cached TBSCertificate over generating one
+  var tbsCertificate = cert.tbsCertificate || pki.getTBSCertificate(cert);
+
+  // Certificate
+  return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+    // TBSCertificate
+    tbsCertificate,
+    // AlgorithmIdentifier (signature algorithm)
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+      // algorithm
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+        asn1.oidToDer(cert.signatureOid).getBytes()),
+      // parameters
+      _signatureParametersToAsn1(cert.signatureOid, cert.signatureParameters)
+    ]),
+    // SignatureValue
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.BITSTRING, false,
+      String.fromCharCode(0x00) + cert.signature)
+  ]);
+};
+
+/**
+ * Converts X.509v3 certificate extensions to ASN.1.
+ *
+ * @param exts the extensions to convert.
+ *
+ * @return the extensions in ASN.1 format.
+ */
+pki.certificateExtensionsToAsn1 = function(exts) {
+  // create top-level extension container
+  var rval = asn1.create(asn1.Class.CONTEXT_SPECIFIC, 3, true, []);
+
+  // create extension sequence (stores a sequence for each extension)
+  var seq = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []);
+  rval.value.push(seq);
+
+  for(var i = 0; i < exts.length; ++i) {
+    seq.value.push(pki.certificateExtensionToAsn1(exts[i]));
+  }
+
+  return rval;
+};
+
+/**
+ * Converts a single certificate extension to ASN.1.
+ *
+ * @param ext the extension to convert.
+ *
+ * @return the extension in ASN.1 format.
+ */
+pki.certificateExtensionToAsn1 = function(ext) {
+  // create a sequence for each extension
+  var extseq = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []);
+
+  // extnID (OID)
+  extseq.value.push(asn1.create(
+    asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+    asn1.oidToDer(ext.id).getBytes()));
+
+  // critical defaults to false
+  if(ext.critical) {
+    // critical BOOLEAN DEFAULT FALSE
+    extseq.value.push(asn1.create(
+      asn1.Class.UNIVERSAL, asn1.Type.BOOLEAN, false,
+      String.fromCharCode(0xFF)));
+  }
+
+  var value = ext.value;
+  if(typeof ext.value !== 'string') {
+    // value is asn.1
+    value = asn1.toDer(value).getBytes();
+  }
+
+  // extnValue (OCTET STRING)
+  extseq.value.push(asn1.create(
+    asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, value));
+
+  return extseq;
+};
+
+/**
+ * Converts a PKCS#10 certification request to an ASN.1 object.
+ *
+ * @param csr the certification request.
+ *
+ * @return the asn1 representation of a certification request.
+ */
+pki.certificationRequestToAsn1 = function(csr) {
+  // prefer cached CertificationRequestInfo over generating one
+  var cri = csr.certificationRequestInfo ||
+    pki.getCertificationRequestInfo(csr);
+
+  // Certificate
+  return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+    // CertificationRequestInfo
+    cri,
+    // AlgorithmIdentifier (signature algorithm)
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
+      // algorithm
+      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
+        asn1.oidToDer(csr.signatureOid).getBytes()),
+      // parameters
+      _signatureParametersToAsn1(csr.signatureOid, csr.signatureParameters)
+    ]),
+    // signature
+    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.BITSTRING, false,
+      String.fromCharCode(0x00) + csr.signature)
+  ]);
+};
+
+/**
+ * Creates a CA store.
+ *
+ * @param certs an optional array of certificate objects or PEM-formatted
+ *          certificate strings to add to the CA store.
+ *
+ * @return the CA store.
+ */
+pki.createCaStore = function(certs) {
+  // create CA store
+  var caStore = {
+    // stored certificates
+    certs: {}
+  };
+
+  /**
+   * Gets the certificate that issued the passed certificate or its
+   * 'parent'.
+   *
+   * @param cert the certificate to get the parent for.
+   *
+   * @return the parent certificate or null if none was found.
+   */
+  caStore.getIssuer = function(cert) {
+    var rval = getBySubject(cert.issuer);
+
+    // see if there are multiple matches
+    /*if(forge.util.isArray(rval)) {
+      // TODO: resolve multiple matches by checking
+      // authorityKey/subjectKey/issuerUniqueID/other identifiers, etc.
+      // FIXME: or alternatively do authority key mapping
+      // if possible (X.509v1 certs can't work?)
+      throw new Error('Resolving multiple issuer matches not implemented yet.');
+    }*/
+
+    return rval;
+  };
+
+  /**
+   * Adds a trusted certificate to the store.
+   *
+   * @param cert the certificate to add as a trusted certificate (either a
+   *          pki.certificate object or a PEM-formatted certificate).
+   */
+  caStore.addCertificate = function(cert) {
+    // convert from pem if necessary
+    if(typeof cert === 'string') {
+      cert = forge.pki.certificateFromPem(cert);
+    }
+
+    // produce subject hash if it doesn't exist
+    if(!cert.subject.hash) {
+      var md = forge.md.sha1.create();
+      cert.subject.attributes =  pki.RDNAttributesAsArray(
+        _dnToAsn1(cert.subject), md);
+      cert.subject.hash = md.digest().toHex();
+    }
+
+    if(cert.subject.hash in caStore.certs) {
+      // subject hash already exists, append to array
+      var tmp = caStore.certs[cert.subject.hash];
+      if(!forge.util.isArray(tmp)) {
+        tmp = [tmp];
+      }
+      tmp.push(cert);
+    } else {
+      caStore.certs[cert.subject.hash] = cert;
+    }
+  };
+
+  /**
+   * Checks to see if the given certificate is in the store.
+   *
+   * @param cert the certificate to check.
+   *
+   * @return true if the certificate is in the store, false if not.
+   */
+  caStore.hasCertificate = function(cert) {
+    var match = getBySubject(cert.subject);
+    if(!match) {
+      return false;
+    }
+    if(!forge.util.isArray(match)) {
+      match = [match];
+    }
+    // compare DER-encoding of certificates
+    var der1 = asn1.toDer(pki.certificateToAsn1(cert)).getBytes();
+    for(var i = 0; i < match.length; ++i) {
+      var der2 = asn1.toDer(pki.certificateToAsn1(match[i])).getBytes();
+      if(der1 === der2) {
+        return true;
+      }
+    }
+    return false;
+  };
+
+  function getBySubject(subject) {
+    // produce subject hash if it doesn't exist
+    if(!subject.hash) {
+      var md = forge.md.sha1.create();
+      subject.attributes =  pki.RDNAttributesAsArray(_dnToAsn1(subject), md);
+      subject.hash = md.digest().toHex();
+    }
+    return caStore.certs[subject.hash] || null;
+  }
+
+  // auto-add passed in certs
+  if(certs) {
+    // parse PEM-formatted certificates as necessary
+    for(var i = 0; i < certs.length; ++i) {
+      var cert = certs[i];
+      caStore.addCertificate(cert);
+    }
+  }
+
+  return caStore;
+};
+
+/**
+ * Certificate verification errors, based on TLS.
+ */
+pki.certificateError = {
+  bad_certificate: 'forge.pki.BadCertificate',
+  unsupported_certificate: 'forge.pki.UnsupportedCertificate',
+  certificate_revoked: 'forge.pki.CertificateRevoked',
+  certificate_expired: 'forge.pki.CertificateExpired',
+  certificate_unknown: 'forge.pki.CertificateUnknown',
+  unknown_ca: 'forge.pki.UnknownCertificateAuthority'
+};
+
+/**
+ * Verifies a certificate chain against the given Certificate Authority store
+ * with an optional custom verify callback.
+ *
+ * @param caStore a certificate store to verify against.
+ * @param chain the certificate chain to verify, with the root or highest
+ *          authority at the end (an array of certificates).
+ * @param verify called for every certificate in the chain.
+ *
+ * The verify callback has the following signature:
+ *
+ * verified - Set to true if certificate was verified, otherwise the
+ *   pki.certificateError for why the certificate failed.
+ * depth - The current index in the chain, where 0 is the end point's cert.
+ * certs - The certificate chain, *NOTE* an empty chain indicates an anonymous
+ *   end point.
+ *
+ * The function returns true on success and on failure either the appropriate
+ * pki.certificateError or an object with 'error' set to the appropriate
+ * pki.certificateError and 'message' set to a custom error message.
+ *
+ * @return true if successful, error thrown if not.
+ */
+pki.verifyCertificateChain = function(caStore, chain, verify) {
+  /* From: RFC3280 - Internet X.509 Public Key Infrastructure Certificate
+    Section 6: Certification Path Validation
+    See inline parentheticals related to this particular implementation.
+
+    The primary goal of path validation is to verify the binding between
+    a subject distinguished name or a subject alternative name and subject
+    public key, as represented in the end entity certificate, based on the
+    public key of the trust anchor. This requires obtaining a sequence of
+    certificates that support that binding. That sequence should be provided
+    in the passed 'chain'. The trust anchor should be in the given CA
+    store. The 'end entity' certificate is the certificate provided by the
+    end point (typically a server) and is the first in the chain.
+
+    To meet this goal, the path validation process verifies, among other
+    things, that a prospective certification path (a sequence of n
+    certificates or a 'chain') satisfies the following conditions:
+
+    (a) for all x in {1, ..., n-1}, the subject of certificate x is
+          the issuer of certificate x+1;
+
+    (b) certificate 1 is issued by the trust anchor;
+
+    (c) certificate n is the certificate to be validated; and
+
+    (d) for all x in {1, ..., n}, the certificate was valid at the
+          time in question.
+
+    Note that here 'n' is index 0 in the chain and 1 is the last certificate
+    in the chain and it must be signed by a certificate in the connection's
+    CA store.
+
+    The path validation process also determines the set of certificate
+    policies that are valid for this path, based on the certificate policies
+    extension, policy mapping extension, policy constraints extension, and
+    inhibit any-policy extension.
+
+    Note: Policy mapping extension not supported (Not Required).
+
+    Note: If the certificate has an unsupported critical extension, then it
+    must be rejected.
+
+    Note: A certificate is self-issued if the DNs that appear in the subject
+    and issuer fields are identical and are not empty.
+
+    The path validation algorithm assumes the following seven inputs are
+    provided to the path processing logic. What this specific implementation
+    will use is provided parenthetically:
+
+    (a) a prospective certification path of length n (the 'chain')
+    (b) the current date/time: ('now').
+    (c) user-initial-policy-set: A set of certificate policy identifiers
+          naming the policies that are acceptable to the certificate user.
+          The user-initial-policy-set contains the special value any-policy
+          if the user is not concerned about certificate policy
+          (Not implemented. Any policy is accepted).
+    (d) trust anchor information, describing a CA that serves as a trust
+          anchor for the certification path. The trust anchor information
+          includes:
+
+      (1)  the trusted issuer name,
+      (2)  the trusted public key algorithm,
+      (3)  the trusted public key, and
+      (4)  optionally, the trusted public key parameters associated
+             with the public key.
+
+      (Trust anchors are provided via certificates in the CA store).
+
+      The trust anchor information may be provided to the path processing
+      procedure in the form of a self-signed certificate. The trusted anchor
+      information is trusted because it was delivered to the path processing
+      procedure by some trustworthy out-of-band procedure. If the trusted
+      public key algorithm requires parameters, then the parameters are
+      provided along with the trusted public key (No parameters used in this
+      implementation).
+
+    (e) initial-policy-mapping-inhibit, which indicates if policy mapping is
+          allowed in the certification path.
+          (Not implemented, no policy checking)
+
+    (f) initial-explicit-policy, which indicates if the path must be valid
+          for at least one of the certificate policies in the user-initial-
+          policy-set.
+          (Not implemented, no policy checking)
+
+    (g) initial-any-policy-inhibit, which indicates whether the
+          anyPolicy OID should be processed if it is included in a
+          certificate.
+          (Not implemented, so any policy is valid provided that it is
+          not marked as critical) */
+
+  /* Basic Path Processing:
+
+    For each certificate in the 'chain', the following is checked:
+
+    1. The certificate validity period includes the current time.
+    2. The certificate was signed by its parent (where the parent is either
+       the next in the chain or from the CA store). Allow processing to
+       continue to the next step if no parent is found but the certificate is
+       in the CA store.
+    3. TODO: The certificate has not been revoked.
+    4. The certificate issuer name matches the parent's subject name.
+    5. TODO: If the certificate is self-issued and not the final certificate
+       in the chain, skip this step, otherwise verify that the subject name
+       is within one of the permitted subtrees of X.500 distinguished names
+       and that each of the alternative names in the subjectAltName extension
+       (critical or non-critical) is within one of the permitted subtrees for
+       that name type.
+    6. TODO: If the certificate is self-issued and not the final certificate
+       in the chain, skip this step, otherwise verify that the subject name
+       is not within one of the excluded subtrees for X.500 distinguished
+       names and none of the subjectAltName extension names are excluded for
+       that name type.
+    7. The other steps in the algorithm for basic path processing involve
+       handling the policy extension which is not presently supported in this
+       implementation. Instead, if a critical policy extension is found, the
+       certificate is rejected as not supported.
+    8. If the certificate is not the first or if its the only certificate in
+       the chain (having no parent from the CA store or is self-signed) and it
+       has a critical key usage extension, verify that the keyCertSign bit is
+       set. If the key usage extension exists, verify that the basic
+       constraints extension exists. If the basic constraints extension exists,
+       verify that the cA flag is set. If pathLenConstraint is set, ensure that
+       the number of certificates that precede in the chain (come earlier
+       in the chain as implemented below), excluding the very first in the
+       chain (typically the end-entity one), isn't greater than the
+       pathLenConstraint. This constraint limits the number of intermediate
+       CAs that may appear below a CA before only end-entity certificates
+       may be issued. */
+
+  // copy cert chain references to another array to protect against changes
+  // in verify callback
+  chain = chain.slice(0);
+  var certs = chain.slice(0);
+
+  // get current date
+  var now = new Date();
+
+  // verify each cert in the chain using its parent, where the parent
+  // is either the next in the chain or from the CA store
+  var first = true;
+  var error = null;
+  var depth = 0;
+  do {
+    var cert = chain.shift();
+    var parent = null;
+    var selfSigned = false;
+
+    // 1. check valid time
+    if(now < cert.validity.notBefore || now > cert.validity.notAfter) {
+      error = {
+        message: 'Certificate is not valid yet or has expired.',
+        error: pki.certificateError.certificate_expired,
+        notBefore: cert.validity.notBefore,
+        notAfter: cert.validity.notAfter,
+        now: now
+      };
+    }
+
+    // 2. verify with parent from chain or CA store
+    if(error === null) {
+      parent = chain[0] || caStore.getIssuer(cert);
+      if(parent === null) {
+        // check for self-signed cert
+        if(cert.isIssuer(cert)) {
+          selfSigned = true;
+          parent = cert;
+        }
+      }
+
+      if(parent) {
+        // FIXME: current CA store implementation might have multiple
+        // certificates where the issuer can't be determined from the
+        // certificate (happens rarely with, eg: old certificates) so normalize
+        // by always putting parents into an array
+        // TODO: there's may be an extreme degenerate case currently uncovered
+        // where an old intermediate certificate seems to have a matching parent
+        // but none of the parents actually verify ... but the intermediate
+        // is in the CA and it should pass this check; needs investigation
+        var parents = parent;
+        if(!forge.util.isArray(parents)) {
+          parents = [parents];
+        }
+
+        // try to verify with each possible parent (typically only one)
+        var verified = false;
+        while(!verified && parents.length > 0) {
+          parent = parents.shift();
+          try {
+            verified = parent.verify(cert);
+          } catch(ex) {
+            // failure to verify, don't care why, try next one
+          }
+        }
+
+        if(!verified) {
+          error = {
+            message: 'Certificate signature is invalid.',
+            error: pki.certificateError.bad_certificate
+          };
+        }
+      }
+
+      if(error === null && (!parent || selfSigned) &&
+        !caStore.hasCertificate(cert)) {
+        // no parent issuer and certificate itself is not trusted
+        error = {
+          message: 'Certificate is not trusted.',
+          error: pki.certificateError.unknown_ca
+        };
+      }
+    }
+
+    // TODO: 3. check revoked
+
+    // 4. check for matching issuer/subject
+    if(error === null && parent && !cert.isIssuer(parent)) {
+      // parent is not issuer
+      error = {
+        message: 'Certificate issuer is invalid.',
+        error: pki.certificateError.bad_certificate
+      };
+    }
+
+    // 5. TODO: check names with permitted names tree
+
+    // 6. TODO: check names against excluded names tree
+
+    // 7. check for unsupported critical extensions
+    if(error === null) {
+      // supported extensions
+      var se = {
+        keyUsage: true,
+        basicConstraints: true
+      };
+      for(var i = 0; error === null && i < cert.extensions.length; ++i) {
+        var ext = cert.extensions[i];
+        if(ext.critical && !(ext.name in se)) {
+          error = {
+            message:
+              'Certificate has an unsupported critical extension.',
+            error: pki.certificateError.unsupported_certificate
+          };
+        }
+      }
+    }
+
+    // 8. check for CA if cert is not first or is the only certificate
+    // remaining in chain with no parent or is self-signed
+    if(error === null &&
+      (!first || (chain.length === 0 && (!parent || selfSigned)))) {
+      // first check keyUsage extension and then basic constraints
+      var bcExt = cert.getExtension('basicConstraints');
+      var keyUsageExt = cert.getExtension('keyUsage');
+      if(keyUsageExt !== null) {
+        // keyCertSign must be true and there must be a basic
+        // constraints extension
+        if(!keyUsageExt.keyCertSign || bcExt === null) {
+          // bad certificate
+          error = {
+            message:
+              'Certificate keyUsage or basicConstraints conflict ' +
+              'or indicate that the certificate is not a CA. ' +
+              'If the certificate is the only one in the chain or ' +
+              'isn\'t the first then the certificate must be a ' +
+              'valid CA.',
+            error: pki.certificateError.bad_certificate
+          };
+        }
+      }
+      // basic constraints cA flag must be set
+      if(error === null && bcExt !== null && !bcExt.cA) {
+        // bad certificate
+        error = {
+          message:
+            'Certificate basicConstraints indicates the certificate ' +
+            'is not a CA.',
+          error: pki.certificateError.bad_certificate
+        };
+      }
+      // if error is not null and keyUsage is available, then we know it
+      // has keyCertSign and there is a basic constraints extension too,
+      // which means we can check pathLenConstraint (if it exists)
+      if(error === null && keyUsageExt !== null &&
+        'pathLenConstraint' in bcExt) {
+        // pathLen is the maximum # of intermediate CA certs that can be
+        // found between the current certificate and the end-entity (depth 0)
+        // certificate; this number does not include the end-entity (depth 0,
+        // last in the chain) even if it happens to be a CA certificate itself
+        var pathLen = depth - 1;
+        if(pathLen > bcExt.pathLenConstraint) {
+          // pathLenConstraint violated, bad certificate
+          error = {
+            message:
+              'Certificate basicConstraints pathLenConstraint violated.',
+            error: pki.certificateError.bad_certificate
+          };
+        }
+      }
+    }
+
+    // call application callback
+    var vfd = (error === null) ? true : error.error;
+    var ret = verify ? verify(vfd, depth, certs) : vfd;
+    if(ret === true) {
+      // clear any set error
+      error = null;
+    } else {
+      // if passed basic tests, set default message and alert
+      if(vfd === true) {
+        error = {
+          message: 'The application rejected the certificate.',
+          error: pki.certificateError.bad_certificate
+        };
+      }
+
+      // check for custom error info
+      if(ret || ret === 0) {
+        // set custom message and error
+        if(typeof ret === 'object' && !forge.util.isArray(ret)) {
+          if(ret.message) {
+             error.message = ret.message;
+          }
+          if(ret.error) {
+            error.error = ret.error;
+          }
+        } else if(typeof ret === 'string') {
+          // set custom error
+          error.error = ret;
+        }
+      }
+
+      // throw error
+      throw error;
+    }
+
+    // no longer first cert in chain
+    first = false;
+    ++depth;
+  } while(chain.length > 0);
+
+  return true;
+};
+
+} // end module implementation
+
+/* ########## Begin module wrapper ########## */
+var name = 'x509';
+if(typeof define !== 'function') {
+  // NodeJS -> AMD
+  if(typeof module === 'object' && module.exports) {
+    var nodeJS = true;
+    define = function(ids, factory) {
+      factory(require, module);
+    };
+  } else {
+    // <script>
+    if(typeof forge === 'undefined') {
+      forge = {};
+    }
+    return initModule(forge);
+  }
+}
+// AMD
+var deps;
+var defineFunc = function(require, module) {
+  module.exports = function(forge) {
+    var mods = deps.map(function(dep) {
+      return require(dep);
+    }).concat(initModule);
+    // handle circular dependencies
+    forge = forge || {};
+    forge.defined = forge.defined || {};
+    if(forge.defined[name]) {
+      return forge[name];
+    }
+    forge.defined[name] = true;
+    for(var i = 0; i < mods.length; ++i) {
+      mods[i](forge);
+    }
+    return forge.pki;
+  };
+};
+var tmpDefine = define;
+define = function(ids, factory) {
+  deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
+  if(nodeJS) {
+    delete define;
+    return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
+  }
+  define = tmpDefine;
+  return define.apply(null, Array.prototype.slice.call(arguments, 0));
+};
+define([
+  'require',
+  'module',
+  './aes',
+  './asn1',
+  './des',
+  './md',
+  './mgf',
+  './oids',
+  './pem',
+  './pss',
+  './rsa',
+  './util'
+], function() {
+  defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
+});
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/xhr.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/xhr.js
new file mode 100644
index 0000000..96082ad
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/js/xhr.js
@@ -0,0 +1,739 @@
+/**
+ * XmlHttpRequest implementation that uses TLS and flash SocketPool.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010-2013 Digital Bazaar, Inc.
+ */
+(function($) {
+
+// logging category
+var cat = 'forge.xhr';
+
+/*
+XMLHttpRequest interface definition from:
+http://www.w3.org/TR/XMLHttpRequest
+
+interface XMLHttpRequest {
+  // event handler
+  attribute EventListener onreadystatechange;
+
+  // state
+  const unsigned short UNSENT = 0;
+  const unsigned short OPENED = 1;
+  const unsigned short HEADERS_RECEIVED = 2;
+  const unsigned short LOADING = 3;
+  const unsigned short DONE = 4;
+  readonly attribute unsigned short readyState;
+
+  // request
+  void open(in DOMString method, in DOMString url);
+  void open(in DOMString method, in DOMString url, in boolean async);
+  void open(in DOMString method, in DOMString url,
+            in boolean async, in DOMString user);
+  void open(in DOMString method, in DOMString url,
+            in boolean async, in DOMString user, in DOMString password);
+  void setRequestHeader(in DOMString header, in DOMString value);
+  void send();
+  void send(in DOMString data);
+  void send(in Document data);
+  void abort();
+
+  // response
+  DOMString getAllResponseHeaders();
+  DOMString getResponseHeader(in DOMString header);
+  readonly attribute DOMString responseText;
+  readonly attribute Document responseXML;
+  readonly attribute unsigned short status;
+  readonly attribute DOMString statusText;
+};
+*/
+
+// readyStates
+var UNSENT = 0;
+var OPENED = 1;
+var HEADERS_RECEIVED = 2;
+var LOADING = 3;
+var DONE = 4;
+
+// exceptions
+var INVALID_STATE_ERR = 11;
+var SYNTAX_ERR = 12;
+var SECURITY_ERR = 18;
+var NETWORK_ERR = 19;
+var ABORT_ERR = 20;
+
+// private flash socket pool vars
+var _sp = null;
+var _policyPort = 0;
+var _policyUrl = null;
+
+// default client (used if no special URL provided when creating an XHR)
+var _client = null;
+
+// all clients including the default, key'd by full base url
+// (multiple cross-domain http clients are permitted so there may be more
+// than one client in this map)
+// TODO: provide optional clean up API for non-default clients
+var _clients = {};
+
+// the default maximum number of concurrents connections per client
+var _maxConnections = 10;
+
+// local aliases
+if(typeof forge === 'undefined') {
+  forge = {};
+}
+var net = forge.net;
+var http = forge.http;
+
+// define the xhr interface
+var xhrApi = {};
+
+/**
+ * Initializes flash XHR support.
+ *
+ * @param options:
+ *   url: the default base URL to connect to if xhr URLs are relative,
+ *     ie: https://myserver.com.
+ *   flashId: the dom ID of the flash SocketPool.
+ *   policyPort: the port that provides the server's flash policy, 0 to use
+ *     the flash default.
+ *   policyUrl: the policy file URL to use instead of a policy port.
+ *   msie: true if browser is internet explorer, false if not.
+ *   connections: the maximum number of concurrent connections.
+ *   caCerts: a list of PEM-formatted certificates to trust.
+ *   cipherSuites: an optional array of cipher suites to use,
+ *     see forge.tls.CipherSuites.
+ *   verify: optional TLS certificate verify callback to use (see forge.tls
+ *     for details).
+ *   getCertificate: an optional callback used to get a client-side
+ *     certificate (see forge.tls for details).
+ *   getPrivateKey: an optional callback used to get a client-side private
+ *     key (see forge.tls for details).
+ *   getSignature: an optional callback used to get a client-side signature
+ *     (see forge.tls for details).
+ *   persistCookies: true to use persistent cookies via flash local storage,
+ *     false to only keep cookies in javascript.
+ *   primeTlsSockets: true to immediately connect TLS sockets on their
+ *     creation so that they will cache TLS sessions for reuse.
+ */
+xhrApi.init = function(options) {
+  forge.log.debug(cat, 'initializing', options);
+
+  // update default policy port and max connections
+  _policyPort = options.policyPort || _policyPort;
+  _policyUrl = options.policyUrl || _policyUrl;
+  _maxConnections = options.connections || _maxConnections;
+
+  // create the flash socket pool
+  _sp = net.createSocketPool({
+    flashId: options.flashId,
+    policyPort: _policyPort,
+    policyUrl: _policyUrl,
+    msie: options.msie || false
+  });
+
+  // create default http client
+  _client = http.createClient({
+    url: options.url || (
+      window.location.protocol + '//' + window.location.host),
+    socketPool: _sp,
+    policyPort: _policyPort,
+    policyUrl: _policyUrl,
+    connections: options.connections || _maxConnections,
+    caCerts: options.caCerts,
+    cipherSuites: options.cipherSuites,
+    persistCookies: options.persistCookies || true,
+    primeTlsSockets: options.primeTlsSockets || false,
+    verify: options.verify,
+    getCertificate: options.getCertificate,
+    getPrivateKey: options.getPrivateKey,
+    getSignature: options.getSignature
+  });
+  _clients[_client.url.full] = _client;
+
+  forge.log.debug(cat, 'ready');
+};
+
+/**
+ * Called to clean up the clients and socket pool.
+ */
+xhrApi.cleanup = function() {
+  // destroy all clients
+  for(var key in _clients) {
+    _clients[key].destroy();
+  }
+  _clients = {};
+  _client = null;
+
+  // destroy socket pool
+  _sp.destroy();
+  _sp = null;
+};
+
+/**
+ * Sets a cookie.
+ *
+ * @param cookie the cookie with parameters:
+ *   name: the name of the cookie.
+ *   value: the value of the cookie.
+ *   comment: an optional comment string.
+ *   maxAge: the age of the cookie in seconds relative to created time.
+ *   secure: true if the cookie must be sent over a secure protocol.
+ *   httpOnly: true to restrict access to the cookie from javascript
+ *     (inaffective since the cookies are stored in javascript).
+ *   path: the path for the cookie.
+ *   domain: optional domain the cookie belongs to (must start with dot).
+ *   version: optional version of the cookie.
+ *   created: creation time, in UTC seconds, of the cookie.
+ */
+xhrApi.setCookie = function(cookie) {
+  // default cookie expiration to never
+  cookie.maxAge = cookie.maxAge || -1;
+
+  // if the cookie's domain is set, use the appropriate client
+  if(cookie.domain) {
+    // add the cookies to the applicable domains
+    for(var key in _clients) {
+      var client = _clients[key];
+      if(http.withinCookieDomain(client.url, cookie) &&
+        client.secure === cookie.secure) {
+        client.setCookie(cookie);
+      }
+    }
+  } else {
+    // use the default domain
+    // FIXME: should a null domain cookie be added to all clients? should
+    // this be an option?
+    _client.setCookie(cookie);
+  }
+};
+
+/**
+ * Gets a cookie.
+ *
+ * @param name the name of the cookie.
+ * @param path an optional path for the cookie (if there are multiple cookies
+ *          with the same name but different paths).
+ * @param domain an optional domain for the cookie (if not using the default
+ *          domain).
+ *
+ * @return the cookie, cookies (if multiple matches), or null if not found.
+ */
+xhrApi.getCookie = function(name, path, domain) {
+  var rval = null;
+
+  if(domain) {
+    // get the cookies from the applicable domains
+    for(var key in _clients) {
+      var client = _clients[key];
+      if(http.withinCookieDomain(client.url, domain)) {
+        var cookie = client.getCookie(name, path);
+        if(cookie !== null) {
+          if(rval === null) {
+            rval = cookie;
+          } else if(!forge.util.isArray(rval)) {
+            rval = [rval, cookie];
+          } else {
+            rval.push(cookie);
+          }
+        }
+      }
+    }
+  } else {
+    // get cookie from default domain
+    rval = _client.getCookie(name, path);
+  }
+
+  return rval;
+};
+
+/**
+ * Removes a cookie.
+ *
+ * @param name the name of the cookie.
+ * @param path an optional path for the cookie (if there are multiple cookies
+ *          with the same name but different paths).
+ * @param domain an optional domain for the cookie (if not using the default
+ *          domain).
+ *
+ * @return true if a cookie was removed, false if not.
+ */
+xhrApi.removeCookie = function(name, path, domain) {
+  var rval = false;
+
+  if(domain) {
+    // remove the cookies from the applicable domains
+    for(var key in _clients) {
+      var client = _clients[key];
+      if(http.withinCookieDomain(client.url, domain)) {
+        if(client.removeCookie(name, path)) {
+           rval = true;
+        }
+      }
+    }
+  } else {
+    // remove cookie from default domain
+    rval = _client.removeCookie(name, path);
+  }
+
+  return rval;
+};
+
+/**
+ * Creates a new XmlHttpRequest. By default the base URL, flash policy port,
+ * etc, will be used. However, an XHR can be created to point at another
+ * cross-domain URL.
+ *
+ * @param options:
+ *   logWarningOnError: If true and an HTTP error status code is received then
+ *     log a warning, otherwise log a verbose message.
+ *   verbose: If true be very verbose in the output including the response
+ *     event and response body, otherwise only include status, timing, and
+ *     data size.
+ *   logError: a multi-var log function for warnings that takes the log
+ *     category as the first var.
+ *   logWarning: a multi-var log function for warnings that takes the log
+ *     category as the first var.
+ *   logDebug: a multi-var log function for warnings that takes the log
+ *     category as the first var.
+ *   logVerbose: a multi-var log function for warnings that takes the log
+ *     category as the first var.
+ *   url: the default base URL to connect to if xhr URLs are relative,
+ *     eg: https://myserver.com, and note that the following options will be
+ *     ignored if the URL is absent or the same as the default base URL.
+ *   policyPort: the port that provides the server's flash policy, 0 to use
+ *     the flash default.
+ *   policyUrl: the policy file URL to use instead of a policy port.
+ *   connections: the maximum number of concurrent connections.
+ *   caCerts: a list of PEM-formatted certificates to trust.
+ *   cipherSuites: an optional array of cipher suites to use, see
+ *     forge.tls.CipherSuites.
+ *   verify: optional TLS certificate verify callback to use (see forge.tls
+ *     for details).
+ *   getCertificate: an optional callback used to get a client-side
+ *     certificate.
+ *   getPrivateKey: an optional callback used to get a client-side private key.
+ *   getSignature: an optional callback used to get a client-side signature.
+ *   persistCookies: true to use persistent cookies via flash local storage,
+ *     false to only keep cookies in javascript.
+ *   primeTlsSockets: true to immediately connect TLS sockets on their
+ *     creation so that they will cache TLS sessions for reuse.
+ *
+ * @return the XmlHttpRequest.
+ */
+xhrApi.create = function(options) {
+  // set option defaults
+  options = $.extend({
+    logWarningOnError: true,
+    verbose: false,
+    logError: function(){},
+    logWarning: function(){},
+    logDebug: function(){},
+    logVerbose: function(){},
+    url: null
+  }, options || {});
+
+  // private xhr state
+  var _state = {
+    // the http client to use
+    client: null,
+    // request storage
+    request: null,
+    // response storage
+    response: null,
+    // asynchronous, true if doing asynchronous communication
+    asynchronous: true,
+    // sendFlag, true if send has been called
+    sendFlag: false,
+    // errorFlag, true if a network error occurred
+    errorFlag: false
+  };
+
+  // private log functions
+  var _log = {
+    error: options.logError || forge.log.error,
+    warning: options.logWarning || forge.log.warning,
+    debug: options.logDebug || forge.log.debug,
+    verbose: options.logVerbose || forge.log.verbose
+  };
+
+  // create public xhr interface
+  var xhr = {
+    // an EventListener
+    onreadystatechange: null,
+    // readonly, the current readyState
+    readyState: UNSENT,
+    // a string with the response entity-body
+    responseText: '',
+    // a Document for response entity-bodies that are XML
+    responseXML: null,
+    // readonly, returns the HTTP status code (i.e. 404)
+    status: 0,
+    // readonly, returns the HTTP status message (i.e. 'Not Found')
+    statusText: ''
+  };
+
+  // determine which http client to use
+  if(options.url === null) {
+    // use default
+    _state.client = _client;
+  } else {
+    var url = http.parseUrl(options.url);
+    if(!url) {
+      var error = new Error('Invalid url.');
+      error.details = {
+        url: options.url
+      };
+    }
+
+    // find client
+    if(url.full in _clients) {
+      // client found
+      _state.client = _clients[url.full];
+    } else {
+      // create client
+      _state.client = http.createClient({
+        url: options.url,
+        socketPool: _sp,
+        policyPort: options.policyPort || _policyPort,
+        policyUrl: options.policyUrl || _policyUrl,
+        connections: options.connections || _maxConnections,
+        caCerts: options.caCerts,
+        cipherSuites: options.cipherSuites,
+        persistCookies: options.persistCookies || true,
+        primeTlsSockets: options.primeTlsSockets || false,
+        verify: options.verify,
+        getCertificate: options.getCertificate,
+        getPrivateKey: options.getPrivateKey,
+        getSignature: options.getSignature
+      });
+      _clients[url.full] = _state.client;
+    }
+  }
+
+  /**
+   * Opens the request. This method will create the HTTP request to send.
+   *
+   * @param method the HTTP method (i.e. 'GET').
+   * @param url the relative url (the HTTP request path).
+   * @param async always true, ignored.
+   * @param user always null, ignored.
+   * @param password always null, ignored.
+   */
+  xhr.open = function(method, url, async, user, password) {
+    // 1. validate Document if one is associated
+    // TODO: not implemented (not used yet)
+
+    // 2. validate method token
+    // 3. change method to uppercase if it matches a known
+    // method (here we just require it to be uppercase, and
+    // we do not allow the standard methods)
+    // 4. disallow CONNECT, TRACE, or TRACK with a security error
+    switch(method) {
+    case 'DELETE':
+    case 'GET':
+    case 'HEAD':
+    case 'OPTIONS':
+    case 'PATCH':
+    case 'POST':
+    case 'PUT':
+      // valid method
+      break;
+    case 'CONNECT':
+    case 'TRACE':
+    case 'TRACK':
+      throw new Error('CONNECT, TRACE and TRACK methods are disallowed');
+    default:
+      throw new Error('Invalid method: ' + method);;
+    }
+
+    // TODO: other validation steps in algorithm are not implemented
+
+    // 19. set send flag to false
+    // set response body to null
+    // empty list of request headers
+    // set request method to given method
+    // set request URL
+    // set username, password
+    // set asychronous flag
+    _state.sendFlag = false;
+    xhr.responseText = '';
+    xhr.responseXML = null;
+
+    // custom: reset status and statusText
+    xhr.status = 0;
+    xhr.statusText = '';
+
+    // create the HTTP request
+    _state.request = http.createRequest({
+      method: method,
+      path: url
+    });
+
+    // 20. set state to OPENED
+    xhr.readyState = OPENED;
+
+    // 21. dispatch onreadystatechange
+    if(xhr.onreadystatechange) {
+       xhr.onreadystatechange();
+    }
+  };
+
+  /**
+   * Adds an HTTP header field to the request.
+   *
+   * @param header the name of the header field.
+   * @param value the value of the header field.
+   */
+  xhr.setRequestHeader = function(header, value) {
+    // 1. if state is not OPENED or send flag is true, raise exception
+    if(xhr.readyState != OPENED || _state.sendFlag) {
+      throw new Error('XHR not open or sending');
+    }
+
+    // TODO: other validation steps in spec aren't implemented
+
+    // set header
+    _state.request.setField(header, value);
+  };
+
+  /**
+   * Sends the request and any associated data.
+   *
+   * @param data a string or Document object to send, null to send no data.
+   */
+  xhr.send = function(data) {
+    // 1. if state is not OPENED or 2. send flag is true, raise
+    // an invalid state exception
+    if(xhr.readyState != OPENED || _state.sendFlag) {
+      throw new Error('XHR not open or sending');
+    }
+
+    // 3. ignore data if method is GET or HEAD
+    if(data &&
+      _state.request.method !== 'GET' &&
+      _state.request.method !== 'HEAD') {
+      // handle non-IE case
+      if(typeof(XMLSerializer) !== 'undefined') {
+        if(data instanceof Document) {
+          var xs = new XMLSerializer();
+          _state.request.body = xs.serializeToString(data);
+        } else {
+          _state.request.body = data;
+        }
+      } else {
+        // poorly implemented IE case
+        if(typeof(data.xml) !== 'undefined') {
+          _state.request.body = data.xml;
+        } else {
+          _state.request.body = data;
+        }
+      }
+    }
+
+    // 4. release storage mutex (not used)
+
+    // 5. set error flag to false
+    _state.errorFlag = false;
+
+    // 6. if asynchronous is true (must be in this implementation)
+
+    // 6.1 set send flag to true
+    _state.sendFlag = true;
+
+    // 6.2 dispatch onreadystatechange
+    if(xhr.onreadystatechange) {
+      xhr.onreadystatechange();
+    }
+
+    // create send options
+    var options = {};
+    options.request = _state.request;
+    options.headerReady = function(e) {
+      // make cookies available for ease of use/iteration
+      xhr.cookies = _state.client.cookies;
+
+      // TODO: update document.cookie with any cookies where the
+      // script's domain matches
+
+      // headers received
+      xhr.readyState = HEADERS_RECEIVED;
+      xhr.status = e.response.code;
+      xhr.statusText = e.response.message;
+      _state.response = e.response;
+      if(xhr.onreadystatechange) {
+        xhr.onreadystatechange();
+      }
+      if(!_state.response.aborted) {
+        // now loading body
+        xhr.readyState = LOADING;
+        if(xhr.onreadystatechange) {
+           xhr.onreadystatechange();
+        }
+      }
+    };
+    options.bodyReady = function(e) {
+      xhr.readyState = DONE;
+      var ct = e.response.getField('Content-Type');
+      // Note: this null/undefined check is done outside because IE
+      // dies otherwise on a "'null' is null" error
+      if(ct) {
+        if(ct.indexOf('text/xml') === 0 ||
+          ct.indexOf('application/xml') === 0 ||
+          ct.indexOf('+xml') !== -1) {
+          try {
+            var doc = new ActiveXObject('MicrosoftXMLDOM');
+            doc.async = false;
+            doc.loadXML(e.response.body);
+            xhr.responseXML = doc;
+          } catch(ex) {
+            var parser = new DOMParser();
+            xhr.responseXML = parser.parseFromString(ex.body, 'text/xml');
+          }
+        }
+      }
+
+      var length = 0;
+      if(e.response.body !== null) {
+        xhr.responseText = e.response.body;
+        length = e.response.body.length;
+      }
+      // build logging output
+      var req = _state.request;
+      var output =
+        req.method + ' ' + req.path + ' ' +
+        xhr.status + ' ' + xhr.statusText + ' ' +
+        length + 'B ' +
+        (e.request.connectTime + e.request.time + e.response.time) +
+        'ms';
+      var lFunc;
+      if(options.verbose) {
+        lFunc = (xhr.status >= 400 && options.logWarningOnError) ?
+          _log.warning : _log.verbose;
+        lFunc(cat, output,
+          e, e.response.body ? '\n' + e.response.body : '\nNo content');
+      } else {
+        lFunc = (xhr.status >= 400 && options.logWarningOnError) ?
+          _log.warning : _log.debug;
+        lFunc(cat, output);
+      }
+      if(xhr.onreadystatechange) {
+        xhr.onreadystatechange();
+      }
+    };
+    options.error = function(e) {
+      var req = _state.request;
+      _log.error(cat, req.method + ' ' + req.path, e);
+
+      // 1. set response body to null
+      xhr.responseText = '';
+      xhr.responseXML = null;
+
+      // 2. set error flag to true (and reset status)
+      _state.errorFlag = true;
+      xhr.status = 0;
+      xhr.statusText = '';
+
+      // 3. set state to done
+      xhr.readyState = DONE;
+
+      // 4. asyc flag is always true, so dispatch onreadystatechange
+      if(xhr.onreadystatechange) {
+        xhr.onreadystatechange();
+      }
+    };
+
+    // 7. send request
+    _state.client.send(options);
+  };
+
+  /**
+   * Aborts the request.
+   */
+  xhr.abort = function() {
+    // 1. abort send
+    // 2. stop network activity
+    _state.request.abort();
+
+    // 3. set response to null
+    xhr.responseText = '';
+    xhr.responseXML = null;
+
+    // 4. set error flag to true (and reset status)
+    _state.errorFlag = true;
+    xhr.status = 0;
+    xhr.statusText = '';
+
+    // 5. clear user headers
+    _state.request = null;
+    _state.response = null;
+
+    // 6. if state is DONE or UNSENT, or if OPENED and send flag is false
+    if(xhr.readyState === DONE || xhr.readyState === UNSENT ||
+     (xhr.readyState === OPENED && !_state.sendFlag)) {
+      // 7. set ready state to unsent
+      xhr.readyState = UNSENT;
+    } else {
+      // 6.1 set state to DONE
+      xhr.readyState = DONE;
+
+      // 6.2 set send flag to false
+      _state.sendFlag = false;
+
+      // 6.3 dispatch onreadystatechange
+      if(xhr.onreadystatechange) {
+        xhr.onreadystatechange();
+      }
+
+      // 7. set state to UNSENT
+      xhr.readyState = UNSENT;
+    }
+  };
+
+  /**
+   * Gets all response headers as a string.
+   *
+   * @return the HTTP-encoded response header fields.
+   */
+  xhr.getAllResponseHeaders = function() {
+    var rval = '';
+    if(_state.response !== null) {
+      var fields = _state.response.fields;
+      $.each(fields, function(name, array) {
+        $.each(array, function(i, value) {
+          rval += name + ': ' + value + '\r\n';
+        });
+      });
+    }
+    return rval;
+  };
+
+  /**
+   * Gets a single header field value or, if there are multiple
+   * fields with the same name, a comma-separated list of header
+   * values.
+   *
+   * @return the header field value(s) or null.
+   */
+  xhr.getResponseHeader = function(header) {
+    var rval = null;
+    if(_state.response !== null) {
+      if(header in _state.response.fields) {
+        rval = _state.response.fields[header];
+        if(forge.util.isArray(rval)) {
+          rval = rval.join();
+        }
+      }
+    }
+    return rval;
+  };
+
+  return xhr;
+};
+
+// expose public api
+forge.xhr = xhrApi;
+
+})(jQuery);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/minify.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/minify.js
new file mode 100644
index 0000000..36430a2
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/minify.js
@@ -0,0 +1,10 @@
+({
+  name: 'node_modules/almond/almond',
+  include: ['js/forge'],
+  out: 'js/forge.min.js',
+  wrap: {
+    startFile: 'start.frag',
+    endFile: 'end.frag'
+  },
+  preserveLicenseComments: false
+})
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/mod_fsp/README b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/mod_fsp/README
new file mode 100644
index 0000000..0331e01
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/mod_fsp/README
@@ -0,0 +1,135 @@
+=======
+mod_fsp
+=======
+
+mod_fsp is an Apache2 module for providing a Flash Socket Policy on the
+same port that HTTP is served. The cross-domain policy that is served is
+specified via a configuration option 'FSPPolicyFile'.
+
+If a flash application sends a policy file request to an Apache server
+that has enabled and configured the mod_fsp module over its HTTP port,
+then the configured cross-domain policy will be returned as the response.
+
+========
+Building
+========
+
+To build the mod_fsp source code you can use Apache2's module
+build and installation tool: 'apxs2' which is, at the time of
+this writing, available on debian in the package:
+
+apache2-threaded-dev
+
+To compile mod_fsp you would run the following command:
+
+apxs2 -c mod_fsp.c
+
+============
+Installation
+============
+
+To install mod_fsp you the following command as root:
+
+apxs2 -c -i -a mod_fsp.c
+
+You must then restart your apache2 process, typically like so:
+
+/etc/init.d/apache2 restart
+
+===================
+Manual Installation
+===================
+
+To manually enable mod_dsp on your Apache2 server, you must copy the
+module file to the appropriate directory and create a load file.
+
+The module file:
+
+fsp.so (The library extension may vary if you are not using linux).
+
+Must be copied to Apache's module installation directory which is
+typically located (on a debian system):
+
+/usr/lib/apache2/modules
+
+The load file:
+
+fsp.load
+
+Must be created in Apache2's 'mods-available' directory, typically
+located (on a debian system):
+
+/etc/apache2/mods-available
+
+The load file should contain:
+
+LoadModule fsp_module         /usr/lib/apache2/modules/mod_fsp.so
+
+If your Apache module installation directory is different from
+the one listed above, you will need to set the correct one in the
+fsp.load file.
+
+To actually enable the module you must create a symbolic link in
+Apache's 'mods-enabled' directory, typically located (on debian):
+
+/etc/apache2/mods-enabled
+
+By typing (from that directory):
+
+ln -s ../mods-available/fsp.load fsp.load
+
+=============
+Configuration
+=============
+
+Once mod_fsp is installed, it must be configured. There is currently
+only one configuration option for mod_fsp: 'FSPPolicyFile'. This
+configuration option will set the file that mod_fsp will look in
+on apache startup for the cross-domain policy to serve. This option
+can be provided on a per-port basis. Each port can use a different
+one, but VirtualServers on a single port will use the same one. This
+is a limitation of the design by Adobe.
+
+Note: The cross-domain policy may fail to be served if the configuration
+option isn't added in the first VirtualHost entry (for a given port) read
+by Apache.
+
+An example of this configuration in use:
+
+<VirtualHost *:80>
+   ServerName example.com
+   DocumentRoot /var/www/example.com
+   ErrorLog /var/log/apache2/example.com-error.log
+   CustomLog /var/log/apache2/example.com-access.log vhost_combined
+
+   # mod_fsp config option
+   FSPPolicyFile /etc/apache2/crossdomain/crossdomain.xml
+
+   <Directory /var/www/example.com>
+      Options Indexes FollowSymLinks MultiViews
+      AllowOverride All
+      Order allow,deny
+      allow from all
+   </Directory>
+
+</VirtualHost>
+
+And example of the most permissive cross-domain policy file for flash:
+
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE cross-domain-policy SYSTEM
+"http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">
+<cross-domain-policy>
+<site-control permitted-cross-domain-policies="all"/>
+<allow-access-from domain="*" to-ports="*"/>
+<allow-http-request-headers-from domain="*" headers="*"/>
+</cross-domain-policy>
+
+==================
+Note about SSL/TLS
+==================
+
+Flash currently has no built-in SSL/TLS support so there is no
+reason to specify an 'FSPPolicyFile' option for SSL servers. The
+Flash player cannot directly communicate with them when doing
+internal look ups of policy files.
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/mod_fsp/mod_fsp.c b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/mod_fsp/mod_fsp.c
new file mode 100644
index 0000000..8beb824
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/mod_fsp/mod_fsp.c
@@ -0,0 +1,415 @@
+/**
+ * Flash Socket Policy Apache Module.
+ *
+ * This module provides a flash socket policy file on the same port that
+ * serves HTTP on Apache. This can help simplify setting up a server that
+ * supports cross-domain communication with flash.
+ *
+ * Quick note about Apache memory handling: Data is allocated from pools and
+ * is not manually returned to those pools. The pools are typically considered
+ * short-lived and will be cleaned up automatically by Apache.
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010 Digital Bazaar, Inc. All rights reserved.
+ */
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+
+#include "ap_compat.h"
+
+#include <string.h>
+
+// length of a policy file request
+#define PFR_LENGTH 23
+
+// declare main module
+module AP_MODULE_DECLARE_DATA fsp_module;
+
+// configuration for the module
+typedef struct fsp_config
+{
+   // the cross-domain policy to serve
+   char* policy;
+   apr_size_t policy_length;
+} fsp_config;
+
+// filter state for keeping track of detected policy file requests
+typedef struct filter_state
+{
+   fsp_config* cfg;
+   int checked;
+   int found;
+} filter_state;
+
+// for registering hooks, filters, etc.
+static void fsp_register_hooks(apr_pool_t *p);
+static int fsp_pre_connection(conn_rec *c, void *csd);
+
+// filter handler declarations
+static apr_status_t fsp_input_filter(
+   ap_filter_t* f, apr_bucket_brigade* bb,
+	ap_input_mode_t mode, apr_read_type_e block, apr_off_t nbytes);
+static int fsp_output_filter(ap_filter_t* f, apr_bucket_brigade* bb);
+
+/**
+ * Registers the hooks for this module.
+ *
+ * @param p the pool to allocate from, if necessary.
+ */
+static void fsp_register_hooks(apr_pool_t* p)
+{
+   // registers the pre-connection hook to handle adding filters
+   ap_hook_pre_connection(
+      fsp_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
+
+   // will parse a policy file request, to be added in pre_connection
+   ap_register_input_filter(
+      "fsp_request", fsp_input_filter,
+      NULL, AP_FTYPE_CONNECTION);
+
+   // will emit a cross-domain policy response to be added in pre_connection
+   ap_register_output_filter(
+      "fsp_response", fsp_output_filter,
+      NULL, AP_FTYPE_CONNECTION);
+}
+
+/**
+ * A hook that is called before a connection is handled. This function will
+ * get the module configuration and add the flash socket policy filters if
+ * a cross-domain policy has been specified in the configuration.
+ *
+ * @param c the connection.
+ * @param csd the connection socket descriptor.
+ *
+ * @return OK on success.
+ */
+static int fsp_pre_connection(conn_rec* c, void* csd)
+{
+   // only install filters if a policy was specified in the module config
+   fsp_config* cfg = ap_get_module_config(
+      c->base_server->module_config, &fsp_module);
+   if(cfg->policy != NULL)
+   {
+      // allocate filter state
+      filter_state* state = apr_palloc(c->pool, sizeof(filter_state));
+      if(state != NULL)
+      {
+         // initialize state
+         state->cfg = cfg;
+         state->checked = state->found = 0;
+
+         // add filters
+         ap_add_input_filter("fsp_request", state, NULL, c);
+         ap_add_output_filter("fsp_response", state, NULL, c);
+      }
+   }
+
+   return OK;
+}
+
+/**
+ * Searches the input request for a flash socket policy request. This request,
+ * unfortunately, does not follow the HTTP protocol and cannot be handled
+ * via a special HTTP handler. Instead, it is a short xml string followed by
+ * a null character:
+ *
+ * '<policy-file-request/>\0'
+ *
+ * A peek into the incoming data checks the first character of the stream to
+ * see if it is '<' (as opposed to typically something else for HTTP). If it
+ * is not, then this function returns and HTTP input is read normally. If it
+ * is, then the remaining bytes in the policy-file-request are read and
+ * checked. If a match is found, then the filter state will be updated to
+ * inform the output filter to send a cross-domain policy as a response. If
+ * no match is found, HTTP traffic will proceed as usual.
+ *
+ * @param f the input filter.
+ * @param state the filter state.
+ *
+ * @return APR_SUCCESS on success, some other status on failure.
+ */
+static apr_status_t find_policy_file_request(
+   ap_filter_t* f, filter_state* state)
+{
+   apr_status_t rval = APR_SUCCESS;
+
+   // create a temp buffer for speculative reads
+   apr_bucket_brigade* tmp = apr_brigade_create(f->c->pool, f->c->bucket_alloc);
+
+   // FIXME: not sure how blocking mode works ... can it return fewer than
+   // the number of specified bytes?
+
+   // peek at the first PFR_LENGTH bytes
+   rval = ap_get_brigade(
+      f->next, tmp, AP_MODE_SPECULATIVE, APR_BLOCK_READ, PFR_LENGTH);
+   if(rval == APR_SUCCESS)
+   {
+      // quickly check the first bucket for the beginning of a pfr
+      const char* data;
+      apr_size_t length;
+      apr_bucket* b = APR_BRIGADE_FIRST(tmp);
+      rval = apr_bucket_read(b, &data, &length, APR_BLOCK_READ);
+      if(rval == APR_SUCCESS && length > 0 && data[0] == '<')
+      {
+         // possible policy file request, fill local buffer
+         char pfr[PFR_LENGTH];
+         char* ptr = pfr;
+         memcpy(ptr, data, length);
+         ptr += length;
+         memset(ptr, '\0', PFR_LENGTH - length);
+         b = APR_BUCKET_NEXT(b);
+         while(rval == APR_SUCCESS && b != APR_BRIGADE_SENTINEL(tmp))
+         {
+            rval = apr_bucket_read(b, &data, &length, APR_BLOCK_READ);
+            if(rval == APR_SUCCESS)
+            {
+               memcpy(ptr, data, length);
+               ptr += length;
+               b = APR_BUCKET_NEXT(b);
+            }
+         }
+
+         if(rval == APR_SUCCESS)
+         {
+            // see if pfr is a policy file request: '<policy-file-request/>\0'
+            if((ptr - pfr == PFR_LENGTH) && (pfr[PFR_LENGTH - 1] == '\0') &&
+               (strncmp(pfr, "<policy-file-request/>", PFR_LENGTH -1) == 0))
+            {
+               // pfr found
+               state->found = 1;
+            }
+         }
+      }
+   }
+
+   return rval;
+}
+
+/**
+ * Handles incoming data. If an attempt has not yet been made to look for
+ * a policy request (it is the beginning of the connection), then one is
+ * made. Otherwise this filter does nothing.
+ *
+ * If an attempt is made to find a policy request and one is not found, then
+ * reads proceed as normal. If one is found, then the filter state is modified
+ * to inform the output filter to send a policy request and the return value
+ * of this filter is EOF indicating that the connection should close after
+ * sending the cross-domain policy.
+ *
+ * @param f the input filter.
+ * @param bb the brigate to fill with input from the next filters in the chain
+ *           and then process (look for a policy file request).
+ * @param mode the type of read requested (ie: AP_MODE_GETLINE means read until
+ *           a CRLF is found, AP_MODE_GETBYTES means 'nbytes' of data, etc).
+ * @param block APR_BLOCK_READ or APR_NONBLOCK_READ, indicates the type of
+ *           blocking to do when trying to read.
+ * @param nbytes used if the read mode is appropriate to specify the number of
+ *           bytes to read (set to 0 for AP_MODE_GETLINE).
+ *
+ * @return the status of the input (ie: APR_SUCCESS for read success, APR_EOF
+ *         for end of stream, APR_EAGAIN to read again when non-blocking).
+ */
+static apr_status_t fsp_input_filter(
+   ap_filter_t* f, apr_bucket_brigade* bb,
+	ap_input_mode_t mode, apr_read_type_e block, apr_off_t nbytes)
+{
+   apr_status_t rval = APR_SUCCESS;
+
+   filter_state* state = f->ctx;
+   if(state->checked == 1)
+   {
+      // already checked for policy file request, just read from other filters
+      rval = ap_get_brigade(f->next, bb, mode, block, nbytes);
+   }
+   else
+   {
+      // try to find a policy file request
+      rval = find_policy_file_request(f, state);
+      state->checked = 1;
+
+      if(rval == APR_SUCCESS)
+      {
+         if(state->found)
+         {
+            // do read of PFR_LENGTH bytes, consider end of stream
+            rval = ap_get_brigade(
+               f->next, bb, AP_MODE_READBYTES, APR_BLOCK_READ, PFR_LENGTH);
+            rval = APR_EOF;
+         }
+         else
+         {
+            // do normal read
+            rval = ap_get_brigade(f->next, bb, mode, block, nbytes);
+         }
+      }
+   }
+
+   return rval;
+}
+
+/**
+ * Handles outgoing data. If the filter state indicates that a cross-domain
+ * policy should be sent then it is added to the outgoing brigade of data. If
+ * a policy request was not detected, then this filter makes no changes to
+ * the outgoing data.
+ *
+ * @param f the output filter.
+ * @param bb the outgoing brigade of data.
+ *
+ * @return APR_SUCCESS on success, some other status on error.
+ */
+static int fsp_output_filter(ap_filter_t* f, apr_bucket_brigade* bb)
+{
+   apr_status_t rval = APR_SUCCESS;
+
+   filter_state* state = f->ctx;
+   if(state->found)
+   {
+      // found policy-file-request, add response bucket
+      // bucket is immortal because the data is stored in the configuration
+      // and doesn't need to be copied
+      apr_bucket* head = apr_bucket_immortal_create(
+         state->cfg->policy, state->cfg->policy_length, bb->bucket_alloc);
+      APR_BRIGADE_INSERT_HEAD(bb, head);
+   }
+
+   if(rval == APR_SUCCESS)
+   {
+      // pass brigade to next filter
+      rval = ap_pass_brigade(f->next, bb);
+   }
+
+   return rval;
+}
+
+/**
+ * Creates the configuration for this module.
+ *
+ * @param p the pool to allocate from.
+ * @param s the server the configuration is for.
+ *
+ * @return the configuration data.
+ */
+static void* fsp_create_config(apr_pool_t* p, server_rec* s)
+{
+   // allocate config
+   fsp_config* cfg = apr_palloc(p, sizeof(fsp_config));
+
+   // no default policy
+   cfg->policy = NULL;
+   cfg->policy_length = 0;
+   return cfg;
+}
+
+/**
+ * Sets the policy file to use from the configuration.
+ *
+ * @param parms the command directive parameters.
+ * @param userdata NULL, not used.
+ * @param arg the string argument to the command directive (the file with
+ *           the cross-domain policy to serve as content).
+ *
+ * @return NULL on success, otherwise an error string to display.
+ */
+static const char* fsp_set_policy_file(
+   cmd_parms* parms, void* userdata, const char* arg)
+{
+   const char* rval = NULL;
+
+   apr_pool_t* pool = (apr_pool_t*)parms->pool;
+   fsp_config* cfg = ap_get_module_config(
+      parms->server->module_config, &fsp_module);
+
+   // ensure command is in the correct context
+   rval = ap_check_cmd_context(parms, NOT_IN_DIR_LOC_FILE|NOT_IN_LIMIT);
+   if(rval == NULL)
+   {
+      // get canonical file name
+      char* fname = ap_server_root_relative(pool, arg);
+      if(fname == NULL)
+      {
+         rval = (const char*)apr_psprintf(
+            pool, "%s: Invalid policy file '%s'",
+            parms->cmd->name, arg);
+      }
+      else
+      {
+         // try to open the file
+         apr_status_t rv;
+         apr_file_t* fd;
+         apr_finfo_t finfo;
+         rv = apr_file_open(&fd, fname, APR_READ, APR_OS_DEFAULT, pool);
+         if(rv == APR_SUCCESS)
+         {
+            // stat file
+            rv = apr_file_info_get(&finfo, APR_FINFO_NORM, fd);
+            if(rv == APR_SUCCESS)
+            {
+               // ensure file is not empty
+               apr_size_t length = (apr_size_t)finfo.size;
+               if(length <= 0)
+               {
+                  rval = (const char*)apr_psprintf(
+                     pool, "%s: policy file '%s' is empty",
+                     parms->cmd->name, fname);
+               }
+               // read file
+               else
+               {
+                  char* buf = (char*)apr_palloc(pool, length + 1);
+                  buf[length] = '\0';
+                  rv = apr_file_read_full(fd, buf, length, NULL);
+                  if(rv == APR_SUCCESS)
+                  {
+                     // TODO: validate file
+                     // save policy string
+                     cfg->policy = buf;
+                     cfg->policy_length = length + 1;
+                  }
+               }
+
+               // close the file
+               apr_file_close(fd);
+            }
+         }
+
+         // handle error case
+         if(rv != APR_SUCCESS)
+         {
+            char errmsg[120];
+            rval = (const char*)apr_psprintf(
+               pool, "%s: Invalid policy file '%s' (%s)",
+               parms->cmd->name, fname,
+               apr_strerror(rv, errmsg, sizeof(errmsg)));
+         }
+      }
+   }
+
+   return rval;
+}
+
+// table of configuration directives
+static const command_rec fsp_cmds[] =
+{
+   AP_INIT_TAKE1(
+      "FSPPolicyFile", /* the directive */
+      fsp_set_policy_file, /* function to call when directive is found */
+      NULL, /* user data to pass to function, not used */
+      RSRC_CONF, /* indicates the directive appears outside of <Location> */
+      "FSPPolicyFile (string) The cross-domain policy file to use"), /* docs */
+   {NULL}
+};
+
+// module setup
+module AP_MODULE_DECLARE_DATA fsp_module =
+{
+    STANDARD20_MODULE_STUFF,    /* stuff declared in every 2.0 mod       */
+    NULL,                       /* create per-directory config structure */
+    NULL,                       /* merge per-directory config structures */
+    fsp_create_config,          /* create per-server config structure    */
+    NULL,                       /* merge per-server config structures    */
+    fsp_cmds,                   /* command apr_table_t                   */
+    fsp_register_hooks          /* register hooks                        */
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/README.md b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/README.md
new file mode 100644
index 0000000..1be00fa
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/README.md
@@ -0,0 +1,34 @@
+Running all the tests (headless-browser and node.js)
+====================================================
+
+    npm install
+    npm test
+
+Running the browser-based tests
+===============================
+
+    npm install
+    node server.js
+
+Then go to http://localhost:8083/.
+
+Testing Require.js optimised version of the JavaScript
+------------------------------------------------------
+
+    npm install -g requirejs
+    r.js -o build.js
+
+You will now have a single optimised JS file at ui/test.min.js, containing the
+tests and all the forge dependencies.
+
+Now edit ui/index.html and change `data-main="test"` to `data-main="test.min"`,
+then reload http://localhost:8083/.
+
+Building a minimized single file for all forge modules
+------------------------------------------------------
+
+    npm install -g requirejs
+    r.js -o minify.js
+
+You will now have forge.min.js, in the 'js' directory, which will contain all
+forge modules.
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/build.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/build.js
new file mode 100644
index 0000000..30ba7b5
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/build.js
@@ -0,0 +1,8 @@
+({
+    paths: {
+        forge: '../js'
+    },
+    name: 'ui/test.js',
+    out: 'ui/test.min.js',
+    preserveLicenseComments: false
+})
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/minify.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/minify.js
new file mode 100644
index 0000000..69d96a9
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/minify.js
@@ -0,0 +1,8 @@
+({
+    paths: {
+        forge: '../js'
+    },
+    name: '../js/forge',
+    out: '../js/forge.min.js',
+    preserveLicenseComments: false
+})
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/package.json b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/package.json
new file mode 100644
index 0000000..f60f52a
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/package.json
@@ -0,0 +1,17 @@
+{
+    "name": "forge-nodejs-example",
+    "version": "0.1.0",
+    "private": true,
+    "main": "server.js",
+    "dependencies": {
+        "express": "~3.1.0",
+        "mocha": "~1.8.2",
+        "chai": "~1.5.0",
+        "grunt": "~0.4.1",
+        "grunt-mocha": "~0.3.1"
+    },
+    "scripts": {
+        "test": "mocha -t 20000 -R spec test/*.js",
+        "run": "node server"
+    }
+}
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/server.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/server.js
new file mode 100644
index 0000000..175bd56
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/server.js
@@ -0,0 +1,46 @@
+var PATH = require('path');
+var express = require('express');
+var PORT = 8083;
+
+exports.main = function(callback) {
+  try {
+    var app = express();
+
+    mountStaticDir(app, /^\/forge\/(.*)$/, PATH.join(__dirname, '../js'));
+    mountStaticDir(app, /^\/test\/(.*)$/, PATH.join(__dirname, 'test'));
+    mountStaticDir(app, /^\/mocha\/(.*)$/, PATH.join(__dirname, 'node_modules/mocha'));
+    mountStaticDir(app, /^\/chai\/(.*)$/, PATH.join(__dirname, 'node_modules/chai'));
+    app.get(/^\//, express.static(PATH.join(__dirname, 'ui')));
+
+    var server = app.listen(PORT);
+
+    console.log('open http://localhost:' + PORT + '/');
+
+    return callback(null, {
+      server: server,
+      port: PORT
+    });
+  } catch(err) {
+    return callback(err);
+  }
+};
+
+function mountStaticDir(app, route, path) {
+  app.get(route, function(req, res, next) {
+    var originalUrl = req.url;
+    req.url = req.params[0];
+    express.static(path)(req, res, function() {
+      req.url = originalUrl;
+      return next.apply(null, arguments);
+    });
+  });
+}
+
+if(require.main === module) {
+  exports.main(function(err) {
+    if(err) {
+      console.error(err.stack);
+      process.exit(1);
+    }
+  });
+}
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/aes.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/aes.js
new file mode 100644
index 0000000..ddd91a4
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/aes.js
@@ -0,0 +1,1213 @@
+(function() {
+
+function Tests(ASSERT, CIPHER, AES, UTIL) {
+  describe('aes', function() {
+    it('should encrypt a single block with a 128-bit key', function() {
+      var key = [0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f];
+      var block = [0x00112233, 0x44556677, 0x8899aabb, 0xccddeeff];
+
+      var output = [];
+      var w = AES._expandKey(key, false);
+      AES._updateBlock(w, block, output, false);
+
+      var out = UTIL.createBuffer();
+      out.putInt32(output[0]);
+      out.putInt32(output[1]);
+      out.putInt32(output[2]);
+      out.putInt32(output[3]);
+
+      ASSERT.equal(out.toHex(), '69c4e0d86a7b0430d8cdb78070b4c55a');
+    });
+
+    it('should decrypt a single block with a 128-bit key', function() {
+      var key = [0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f];
+      var block = [0x69c4e0d8, 0x6a7b0430, 0xd8cdb780, 0x70b4c55a];
+
+      var output = [];
+      var w = AES._expandKey(key, true);
+      AES._updateBlock(w, block, output, true);
+
+      var out = UTIL.createBuffer();
+      out.putInt32(output[0]);
+      out.putInt32(output[1]);
+      out.putInt32(output[2]);
+      out.putInt32(output[3]);
+
+      ASSERT.equal(out.toHex(), '00112233445566778899aabbccddeeff');
+    });
+
+    it('should encrypt a single block with a 192-bit key', function() {
+        var key = [
+          0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f,
+          0x10111213, 0x14151617];
+        var block = [0x00112233, 0x44556677, 0x8899aabb, 0xccddeeff];
+
+        var output = [];
+        var w = AES._expandKey(key, false);
+        AES._updateBlock(w, block, output, false);
+
+        var out = UTIL.createBuffer();
+        out.putInt32(output[0]);
+        out.putInt32(output[1]);
+        out.putInt32(output[2]);
+        out.putInt32(output[3]);
+
+        ASSERT.equal(out.toHex(), 'dda97ca4864cdfe06eaf70a0ec0d7191');
+    });
+
+    it('should decrypt a single block with a 192-bit key', function() {
+        var key = [
+          0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f,
+          0x10111213, 0x14151617];
+        var block = [0xdda97ca4, 0x864cdfe0, 0x6eaf70a0, 0xec0d7191];
+
+        var output = [];
+        var w = AES._expandKey(key, true);
+        AES._updateBlock(w, block, output, true);
+
+        var out = UTIL.createBuffer();
+        out.putInt32(output[0]);
+        out.putInt32(output[1]);
+        out.putInt32(output[2]);
+        out.putInt32(output[3]);
+
+        ASSERT.equal(out.toHex(), '00112233445566778899aabbccddeeff');
+    });
+
+    it('should encrypt a single block with a 256-bit key', function() {
+        var key = [
+          0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f,
+          0x10111213, 0x14151617, 0x18191a1b, 0x1c1d1e1f];
+        var block = [0x00112233, 0x44556677, 0x8899aabb, 0xccddeeff];
+
+        var output = [];
+        var w = AES._expandKey(key, false);
+        AES._updateBlock(w, block, output, false);
+
+        var out = UTIL.createBuffer();
+        out.putInt32(output[0]);
+        out.putInt32(output[1]);
+        out.putInt32(output[2]);
+        out.putInt32(output[3]);
+
+        ASSERT.equal(out.toHex(), '8ea2b7ca516745bfeafc49904b496089');
+    });
+
+    it('should decrypt a single block with a 256-bit key', function() {
+        var key = [
+          0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f,
+          0x10111213, 0x14151617, 0x18191a1b, 0x1c1d1e1f];
+        var block = [0x8ea2b7ca, 0x516745bf, 0xeafc4990, 0x4b496089];
+
+        var output = [];
+        var w = AES._expandKey(key, true);
+        AES._updateBlock(w, block, output, true);
+
+        var out = UTIL.createBuffer();
+        out.putInt32(output[0]);
+        out.putInt32(output[1]);
+        out.putInt32(output[2]);
+        out.putInt32(output[3]);
+
+        ASSERT.equal(out.toHex(), '00112233445566778899aabbccddeeff');
+    });
+
+    // AES-128-CBC
+    (function() {
+      var keys = [
+        '06a9214036b8a15b512e03d534120006',
+        'c286696d887c9aa0611bbb3e2025a45a',
+        '6c3ea0477630ce21a2ce334aa746c2cd',
+        '56e47a38c5598974bc46903dba290349'
+      ];
+
+      var ivs = [
+        '3dafba429d9eb430b422da802c9fac41',
+        '562e17996d093d28ddb3ba695a2e6f58',
+        'c782dc4c098c66cbd9cd27d825682c81',
+        '8ce82eefbea0da3c44699ed7db51b7d9'
+      ];
+
+      var inputs = [
+        'Single block msg',
+        '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f',
+        'This is a 48-byte message (exactly 3 AES blocks)',
+        'a0a1a2a3a4a5a6a7a8a9aaabacadaeaf' +
+          'b0b1b2b3b4b5b6b7b8b9babbbcbdbebf' +
+          'c0c1c2c3c4c5c6c7c8c9cacbcccdcecf' +
+          'd0d1d2d3d4d5d6d7d8d9dadbdcdddedf'
+      ];
+
+      var outputs = [
+        'e353779c1079aeb82708942dbe77181a',
+        'd296cd94c2cccf8a3a863028b5e1dc0a7586602d253cfff91b8266bea6d61ab1',
+        'd0a02b3836451753d493665d33f0e886' +
+          '2dea54cdb293abc7506939276772f8d5' +
+          '021c19216bad525c8579695d83ba2684',
+        'c30e32ffedc0774e6aff6af0869f71aa' +
+          '0f3af07a9a31a9c684db207eb0ef8e4e' +
+          '35907aa632c3ffdf868bb7b29d3d46ad' +
+          '83ce9f9a102ee99d49a53e87f4c3da55'
+      ];
+
+      for(var i = 0; i < keys.length; ++i) {
+        (function(i) {
+          var key = UTIL.hexToBytes(keys[i]);
+          var iv = UTIL.hexToBytes(ivs[i]);
+          var input = (i & 1) ? UTIL.hexToBytes(inputs[i]) : inputs[i];
+          var output = UTIL.hexToBytes(outputs[i]);
+
+          it('should aes-128-cbc encrypt: ' + inputs[i], function() {
+            // encrypt w/no padding
+            var cipher = CIPHER.createCipher('AES-CBC', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(input));
+            cipher.finish(function(){return true;});
+            ASSERT.equal(cipher.output.toHex(), outputs[i]);
+          });
+
+          it('should aes-128-cbc decrypt: ' + outputs[i], function() {
+            // decrypt w/no padding
+            var cipher = CIPHER.createDecipher('AES-CBC', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(output));
+            cipher.finish(function(){return true;});
+            var out = (i & 1) ? cipher.output.toHex() : cipher.output.bytes();
+            ASSERT.equal(out, inputs[i]);
+          });
+        })(i);
+      }
+    })();
+
+    // AES-192-CBC
+    (function() {
+      var keys = [
+        '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b',
+        '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b',
+        '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b',
+        '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b'
+      ];
+
+      var ivs = [
+        '000102030405060708090A0B0C0D0E0F',
+        '4F021DB243BC633D7178183A9FA071E8',
+        'B4D9ADA9AD7DEDF4E5E738763F69145A',
+        '571B242012FB7AE07FA9BAAC3DF102E0'
+      ];
+
+      var inputs = [
+        '6bc1bee22e409f96e93d7e117393172a',
+        'ae2d8a571e03ac9c9eb76fac45af8e51',
+        '30c81c46a35ce411e5fbc1191a0a52ef',
+        'f69f2445df4f9b17ad2b417be66c3710'
+      ];
+
+      var outputs = [
+        '4f021db243bc633d7178183a9fa071e8',
+        'b4d9ada9ad7dedf4e5e738763f69145a',
+        '571b242012fb7ae07fa9baac3df102e0',
+        '08b0e27988598881d920a9e64f5615cd'
+      ];
+
+      for(var i = 0; i < keys.length; ++i) {
+        (function(i) {
+          var key = UTIL.hexToBytes(keys[i]);
+          var iv = UTIL.hexToBytes(ivs[i]);
+          var input = UTIL.hexToBytes(inputs[i]);
+          var output = UTIL.hexToBytes(outputs[i]);
+
+          it('should aes-192-cbc encrypt: ' + inputs[i], function() {
+            // encrypt w/no padding
+            var cipher = CIPHER.createCipher('AES-CBC', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(input));
+            cipher.finish(function(){return true;});
+            ASSERT.equal(cipher.output.toHex(), outputs[i]);
+          });
+
+          it('should aes-192-cbc decrypt: ' + outputs[i], function() {
+            // decrypt w/no padding
+            var cipher = CIPHER.createDecipher('AES-CBC', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(output));
+            cipher.finish(function(){return true;});
+            var out = cipher.output.toHex();
+            ASSERT.equal(out, inputs[i]);
+          });
+        })(i);
+      }
+    })();
+
+    // AES-256-CBC
+    (function() {
+      var keys = [
+        '603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4',
+        '603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4',
+        '603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4',
+        '603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4'
+      ];
+
+      var ivs = [
+        '000102030405060708090A0B0C0D0E0F',
+        'F58C4C04D6E5F1BA779EABFB5F7BFBD6',
+        '9CFC4E967EDB808D679F777BC6702C7D',
+        '39F23369A9D9BACFA530E26304231461'
+      ];
+
+      var inputs = [
+        '6bc1bee22e409f96e93d7e117393172a',
+        'ae2d8a571e03ac9c9eb76fac45af8e51',
+        '30c81c46a35ce411e5fbc1191a0a52ef',
+        'f69f2445df4f9b17ad2b417be66c3710'
+      ];
+
+      var outputs = [
+        'f58c4c04d6e5f1ba779eabfb5f7bfbd6',
+        '9cfc4e967edb808d679f777bc6702c7d',
+        '39f23369a9d9bacfa530e26304231461',
+        'b2eb05e2c39be9fcda6c19078c6a9d1b'
+      ];
+
+      for(var i = 0; i < keys.length; ++i) {
+        (function(i) {
+          var key = UTIL.hexToBytes(keys[i]);
+          var iv = UTIL.hexToBytes(ivs[i]);
+          var input = UTIL.hexToBytes(inputs[i]);
+          var output = UTIL.hexToBytes(outputs[i]);
+
+          it('should aes-256-cbc encrypt: ' + inputs[i], function() {
+            // encrypt w/no padding
+            var cipher = CIPHER.createCipher('AES-CBC', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(input));
+            cipher.finish(function(){return true;});
+            ASSERT.equal(cipher.output.toHex(), outputs[i]);
+          });
+
+          it('should aes-256-cbc decrypt: ' + outputs[i], function() {
+            // decrypt w/no padding
+            var cipher = CIPHER.createDecipher('AES-CBC', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(output));
+            cipher.finish(function(){return true;});
+            var out = cipher.output.toHex();
+            ASSERT.equal(out, inputs[i]);
+          });
+        })(i);
+      }
+    })();
+
+    // AES-128-CFB
+    (function() {
+      var keys = [
+        '00000000000000000000000000000000',
+        '2b7e151628aed2a6abf7158809cf4f3c',
+        '2b7e151628aed2a6abf7158809cf4f3c',
+        '2b7e151628aed2a6abf7158809cf4f3c',
+        '2b7e151628aed2a6abf7158809cf4f3c',
+        '00000000000000000000000000000000'
+      ];
+
+      var ivs = [
+        '80000000000000000000000000000000',
+        '000102030405060708090a0b0c0d0e0f',
+        '3B3FD92EB72DAD20333449F8E83CFB4A',
+        'C8A64537A0B3A93FCDE3CDAD9F1CE58B',
+        '26751F67A3CBB140B1808CF187A4F4DF',
+        '60f9ff04fac1a25657bf5b36b5efaf75'
+      ];
+
+      var inputs = [
+        '00000000000000000000000000000000',
+        '6bc1bee22e409f96e93d7e117393172a',
+        'ae2d8a571e03ac9c9eb76fac45af8e51',
+        '30c81c46a35ce411e5fbc1191a0a52ef',
+        'f69f2445df4f9b17ad2b417be66c3710',
+        'This is a 48-byte message (exactly 3 AES blocks)'
+      ];
+
+      var outputs = [
+        '3ad78e726c1ec02b7ebfe92b23d9ec34',
+        '3b3fd92eb72dad20333449f8e83cfb4a',
+        'c8a64537a0b3a93fcde3cdad9f1ce58b',
+        '26751f67a3cbb140b1808cf187a4f4df',
+        'c04b05357c5d1c0eeac4c66f9ff7f2e6',
+        '52396a2ba1ba420c5e5b699a814944d8' +
+          'f4e7fbf984a038319fbc0b4ee45cfa6f' +
+          '07b2564beab5b5e92dbd44cb345f49b4'
+      ];
+
+      for(var i = 0; i < keys.length; ++i) {
+        (function(i) {
+          var key = UTIL.hexToBytes(keys[i]);
+          var iv = UTIL.hexToBytes(ivs[i]);
+          var input = (i !== 5) ? UTIL.hexToBytes(inputs[i]) : inputs[i];
+          var output = UTIL.hexToBytes(outputs[i]);
+
+          it('should aes-128-cfb encrypt: ' + inputs[i], function() {
+            // encrypt
+            var cipher = CIPHER.createCipher('AES-CFB', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(input));
+            cipher.finish();
+            ASSERT.equal(cipher.output.toHex(), outputs[i]);
+          });
+
+          it('should aes-128-cfb decrypt: ' + outputs[i], function() {
+            // decrypt
+            var cipher = CIPHER.createDecipher('AES-CFB', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(output));
+            cipher.finish();
+            var out = (i !== 5) ?
+              cipher.output.toHex() : cipher.output.getBytes();
+            ASSERT.equal(out, inputs[i]);
+          });
+        })(i);
+      }
+    })();
+
+    // AES-192-CFB
+    (function() {
+      var keys = [
+        '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b',
+        '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b',
+        '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b',
+        '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b'
+      ];
+
+      var ivs = [
+        '000102030405060708090A0B0C0D0E0F',
+        'CDC80D6FDDF18CAB34C25909C99A4174',
+        '67CE7F7F81173621961A2B70171D3D7A',
+        '2E1E8A1DD59B88B1C8E60FED1EFAC4C9'
+      ];
+
+      var inputs = [
+        '6bc1bee22e409f96e93d7e117393172a',
+        'ae2d8a571e03ac9c9eb76fac45af8e51',
+        '30c81c46a35ce411e5fbc1191a0a52ef',
+        'f69f2445df4f9b17ad2b417be66c3710'
+      ];
+
+      var outputs = [
+        'cdc80d6fddf18cab34c25909c99a4174',
+        '67ce7f7f81173621961a2b70171d3d7a',
+        '2e1e8a1dd59b88b1c8e60fed1efac4c9',
+        'c05f9f9ca9834fa042ae8fba584b09ff'
+      ];
+
+      for(var i = 0; i < keys.length; ++i) {
+        (function(i) {
+          var key = UTIL.hexToBytes(keys[i]);
+          var iv = UTIL.hexToBytes(ivs[i]);
+          var input = UTIL.hexToBytes(inputs[i]);
+          var output = UTIL.hexToBytes(outputs[i]);
+
+          it('should aes-192-cfb encrypt: ' + inputs[i], function() {
+            // encrypt
+            var cipher = CIPHER.createCipher('AES-CFB', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(input));
+            cipher.finish();
+            ASSERT.equal(cipher.output.toHex(), outputs[i]);
+          });
+
+          it('should aes-192-cfb decrypt: ' + outputs[i], function() {
+            // decrypt
+            var cipher = CIPHER.createDecipher('AES-CFB', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(output));
+            cipher.finish();
+            var out = cipher.output.toHex();
+            ASSERT.equal(out, inputs[i]);
+          });
+        })(i);
+      }
+    })();
+
+    // AES-256-CFB
+    (function() {
+      var keys = [
+        '861009ec4d599fab1f40abc76e6f89880cff5833c79c548c99f9045f191cd90b'
+      ];
+
+      var ivs = [
+        'd927ad81199aa7dcadfdb4e47b6dc694'
+      ];
+
+      var inputs = [
+        'MY-DATA-AND-HERE-IS-MORE-DATA'
+      ];
+
+      var outputs = [
+        '80eb666a9fc9e263faf71e87ffc94451d7d8df7cfcf2606470351dd5ac'
+      ];
+
+      for(var i = 0; i < keys.length; ++i) {
+        (function(i) {
+          var key = UTIL.hexToBytes(keys[i]);
+          var iv = UTIL.hexToBytes(ivs[i]);
+          var input = inputs[i];
+          var output = UTIL.hexToBytes(outputs[i]);
+
+          it('should aes-256-cfb encrypt: ' + inputs[i], function() {
+            // encrypt
+            var cipher = CIPHER.createCipher('AES-CFB', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(input));
+            cipher.finish();
+            ASSERT.equal(cipher.output.toHex(), outputs[i]);
+          });
+
+          it('should aes-256-cfb decrypt: ' + outputs[i], function() {
+            // decrypt
+            var cipher = CIPHER.createDecipher('AES-CFB', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(output));
+            cipher.finish();
+            var out = cipher.output.getBytes();
+            ASSERT.equal(out, inputs[i]);
+          });
+        })(i);
+      }
+    })();
+
+    // AES-128-OFB
+    (function() {
+      var keys = [
+        '00000000000000000000000000000000',
+        '00000000000000000000000000000000'
+      ];
+
+      var ivs = [
+        '80000000000000000000000000000000',
+        'c8ca0d6a35dbeac776e911ee16bea7d3'
+      ];
+
+      var inputs = [
+        '00000000000000000000000000000000',
+        'This is a 48-byte message (exactly 3 AES blocks)'
+      ];
+
+      var outputs = [
+        '3ad78e726c1ec02b7ebfe92b23d9ec34',
+        '39c0190727a76b2a90963426f63689cf' +
+          'cdb8a2be8e20c5e877a81a724e3611f6' +
+          '2ecc386f2e941b2441c838906002be19'
+      ];
+
+      for(var i = 0; i < keys.length; ++i) {
+        (function(i) {
+          var key = UTIL.hexToBytes(keys[i]);
+          var iv = UTIL.hexToBytes(ivs[i]);
+          var input = (i !== 1) ? UTIL.hexToBytes(inputs[i]) : inputs[i];
+          var output = UTIL.hexToBytes(outputs[i]);
+
+          it('should aes-128-ofb encrypt: ' + inputs[i], function() {
+            // encrypt
+            var cipher = CIPHER.createCipher('AES-OFB', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(input));
+            cipher.finish();
+            ASSERT.equal(cipher.output.toHex(), outputs[i]);
+          });
+
+          it('should aes-128-ofb decrypt: ' + outputs[i], function() {
+            // decrypt
+            var cipher = CIPHER.createDecipher('AES-OFB', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(output));
+            cipher.finish();
+            var out = (i !== 1) ?
+              cipher.output.toHex() : cipher.output.getBytes();
+            ASSERT.equal(out, inputs[i]);
+          });
+        })(i);
+      }
+    })();
+
+    // AES-192-OFB
+    (function() {
+      var keys = [
+        '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b',
+        '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b',
+        '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b',
+        '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b'
+      ];
+
+      var ivs = [
+        '000102030405060708090A0B0C0D0E0F',
+        'A609B38DF3B1133DDDFF2718BA09565E',
+        '52EF01DA52602FE0975F78AC84BF8A50',
+        'BD5286AC63AABD7EB067AC54B553F71D'
+      ];
+
+      var inputs = [
+        '6bc1bee22e409f96e93d7e117393172a',
+        'ae2d8a571e03ac9c9eb76fac45af8e51',
+        '30c81c46a35ce411e5fbc1191a0a52ef',
+        'f69f2445df4f9b17ad2b417be66c3710'
+      ];
+
+      var outputs = [
+        'cdc80d6fddf18cab34c25909c99a4174',
+        'fcc28b8d4c63837c09e81700c1100401',
+        '8d9a9aeac0f6596f559c6d4daf59a5f2',
+        '6d9f200857ca6c3e9cac524bd9acc92a'
+      ];
+
+      for(var i = 0; i < keys.length; ++i) {
+        (function(i) {
+          var key = UTIL.hexToBytes(keys[i]);
+          var iv = UTIL.hexToBytes(ivs[i]);
+          var input = UTIL.hexToBytes(inputs[i]);
+          var output = UTIL.hexToBytes(outputs[i]);
+
+          it('should aes-192-ofb encrypt: ' + inputs[i], function() {
+            // encrypt
+            var cipher = CIPHER.createCipher('AES-OFB', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(input));
+            cipher.finish();
+            ASSERT.equal(cipher.output.toHex(), outputs[i]);
+          });
+
+          it('should aes-192-ofb decrypt: ' + outputs[i], function() {
+            // decrypt
+            var cipher = CIPHER.createDecipher('AES-OFB', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(output));
+            cipher.finish();
+            var out = cipher.output.toHex();
+            ASSERT.equal(out, inputs[i]);
+          });
+        })(i);
+      }
+    })();
+
+    // AES-256-OFB
+    (function() {
+      var keys = [
+        '603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4',
+        '603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4',
+        '603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4',
+        '603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4'
+      ];
+
+      var ivs = [
+        '000102030405060708090A0B0C0D0E0F',
+        'B7BF3A5DF43989DD97F0FA97EBCE2F4A',
+        'E1C656305ED1A7A6563805746FE03EDC',
+        '41635BE625B48AFC1666DD42A09D96E7'
+      ];
+
+      var inputs = [
+        '6bc1bee22e409f96e93d7e117393172a',
+        'ae2d8a571e03ac9c9eb76fac45af8e51',
+        '30c81c46a35ce411e5fbc1191a0a52ef',
+        'f69f2445df4f9b17ad2b417be66c3710'
+      ];
+
+      var outputs = [
+        'dc7e84bfda79164b7ecd8486985d3860',
+        '4febdc6740d20b3ac88f6ad82a4fb08d',
+        '71ab47a086e86eedf39d1c5bba97c408',
+        '0126141d67f37be8538f5a8be740e484'
+      ];
+
+      for(var i = 0; i < keys.length; ++i) {
+        (function(i) {
+          var key = UTIL.hexToBytes(keys[i]);
+          var iv = UTIL.hexToBytes(ivs[i]);
+          var input = UTIL.hexToBytes(inputs[i]);
+          var output = UTIL.hexToBytes(outputs[i]);
+
+          it('should aes-256-ofb encrypt: ' + inputs[i], function() {
+            // encrypt
+            var cipher = CIPHER.createCipher('AES-OFB', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(input));
+            cipher.finish();
+            ASSERT.equal(cipher.output.toHex(), outputs[i]);
+          });
+
+          it('should aes-256-ofb decrypt: ' + outputs[i], function() {
+            // decrypt
+            var cipher = CIPHER.createDecipher('AES-OFB', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(output));
+            cipher.finish();
+            var out = cipher.output.toHex();
+            ASSERT.equal(out, inputs[i]);
+          });
+        })(i);
+      }
+    })();
+
+    // AES-128-CTR
+    (function() {
+      var keys = [
+        '2b7e151628aed2a6abf7158809cf4f3c',
+        '00000000000000000000000000000000'
+      ];
+
+      var ivs = [
+        'f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff',
+        '650cdb80ff9fc758342d2bd99ee2abcf'
+      ];
+
+      var inputs = [
+        '6bc1bee22e409f96e93d7e117393172a',
+        'This is a 48-byte message (exactly 3 AES blocks)'
+      ];
+
+      var outputs = [
+        '874d6191b620e3261bef6864990db6ce',
+        '5ede11d00e9a76ec1d5e7e811ea3dd1c' +
+          'e09ee941210f825d35718d3282796f1c' +
+          '07c3f1cb424f2b365766ab5229f5b5a4'
+      ];
+
+      for(var i = 0; i < keys.length; ++i) {
+        (function(i) {
+          var key = UTIL.hexToBytes(keys[i]);
+          var iv = UTIL.hexToBytes(ivs[i]);
+          var input = (i !== 1) ? UTIL.hexToBytes(inputs[i]) : inputs[i];
+          var output = UTIL.hexToBytes(outputs[i]);
+
+          it('should aes-128-ctr encrypt: ' + inputs[i], function() {
+            // encrypt
+            var cipher = CIPHER.createCipher('AES-CTR', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(input));
+            cipher.finish();
+            ASSERT.equal(cipher.output.toHex(), outputs[i]);
+          });
+
+          it('should aes-128-ctr decrypt: ' + outputs[i], function() {
+            // decrypt
+            var cipher = CIPHER.createDecipher('AES-CTR', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(output));
+            cipher.finish();
+            var out = (i !== 1) ?
+              cipher.output.toHex() : cipher.output.getBytes();
+            ASSERT.equal(out, inputs[i]);
+          });
+        })(i);
+      }
+    })();
+
+    // AES-192-CTR
+    (function() {
+      var keys = [
+        '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b'
+      ];
+
+      var ivs = [
+        'f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff'
+      ];
+
+      var inputs = [
+        '6bc1bee22e409f96e93d7e117393172a' +
+          'ae2d8a571e03ac9c9eb76fac45af8e51' +
+          '30c81c46a35ce411e5fbc1191a0a52ef' +
+          'f69f2445df4f9b17ad2b417be66c3710'
+      ];
+
+      var outputs = [
+        '1abc932417521ca24f2b0459fe7e6e0b' +
+          '090339ec0aa6faefd5ccc2c6f4ce8e94' +
+          '1e36b26bd1ebc670d1bd1d665620abf7' +
+          '4f78a7f6d29809585a97daec58c6b050'
+      ];
+
+      for(var i = 0; i < keys.length; ++i) {
+        (function(i) {
+          var key = UTIL.hexToBytes(keys[i]);
+          var iv = UTIL.hexToBytes(ivs[i]);
+          var input = UTIL.hexToBytes(inputs[i]);
+          var output = UTIL.hexToBytes(outputs[i]);
+
+          it('should aes-192-ctr encrypt: ' + inputs[i], function() {
+            // encrypt
+            var cipher = CIPHER.createCipher('AES-CTR', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(input));
+            cipher.finish();
+            ASSERT.equal(cipher.output.toHex(), outputs[i]);
+          });
+
+          it('should aes-192-ctr decrypt: ' + outputs[i], function() {
+            // decrypt
+            var cipher = CIPHER.createDecipher('AES-CTR', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(output));
+            cipher.finish();
+            var out = cipher.output.toHex();
+            ASSERT.equal(out, inputs[i]);
+          });
+        })(i);
+      }
+    })();
+
+    // AES-256-CTR
+    (function() {
+      var keys = [
+        '603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4'
+      ];
+
+      var ivs = [
+        'f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff'
+      ];
+
+      var inputs = [
+        '6bc1bee22e409f96e93d7e117393172a' +
+          'ae2d8a571e03ac9c9eb76fac45af8e51' +
+          '30c81c46a35ce411e5fbc1191a0a52ef' +
+          'f69f2445df4f9b17ad2b417be66c3710'
+      ];
+
+      var outputs = [
+        '601ec313775789a5b7a7f504bbf3d228' +
+          'f443e3ca4d62b59aca84e990cacaf5c5' +
+          '2b0930daa23de94ce87017ba2d84988d' +
+          'dfc9c58db67aada613c2dd08457941a6'
+      ];
+
+      for(var i = 0; i < keys.length; ++i) {
+        (function(i) {
+          var key = UTIL.hexToBytes(keys[i]);
+          var iv = UTIL.hexToBytes(ivs[i]);
+          var input = UTIL.hexToBytes(inputs[i]);
+          var output = UTIL.hexToBytes(outputs[i]);
+
+          it('should aes-256-ctr encrypt: ' + inputs[i], function() {
+            // encrypt
+            var cipher = CIPHER.createCipher('AES-CTR', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(input));
+            cipher.finish();
+            ASSERT.equal(cipher.output.toHex(), outputs[i]);
+          });
+
+          it('should aes-256-ctr decrypt: ' + outputs[i], function() {
+            // decrypt
+            var cipher = CIPHER.createDecipher('AES-CTR', key);
+            cipher.start({iv: iv});
+            cipher.update(UTIL.createBuffer(output));
+            cipher.finish();
+            var out = cipher.output.toHex();
+            ASSERT.equal(out, inputs[i]);
+          });
+        })(i);
+      }
+    })();
+
+    // AES-128-GCM
+    (function() {
+      var keys = [
+        '00000000000000000000000000000000',
+        '00000000000000000000000000000000',
+        'feffe9928665731c6d6a8f9467308308',
+        'feffe9928665731c6d6a8f9467308308',
+        'feffe9928665731c6d6a8f9467308308',
+        'feffe9928665731c6d6a8f9467308308'
+      ];
+
+      var ivs = [
+        '000000000000000000000000',
+        '000000000000000000000000',
+        'cafebabefacedbaddecaf888',
+        'cafebabefacedbaddecaf888',
+        'cafebabefacedbad',
+        '9313225df88406e555909c5aff5269aa' +
+          '6a7a9538534f7da1e4c303d2a318a728' +
+          'c3c0c95156809539fcf0e2429a6b5254' +
+          '16aedbf5a0de6a57a637b39b'
+      ];
+
+      var adatas = [
+        '',
+        '',
+        '',
+        'feedfacedeadbeeffeedfacedeadbeef' +
+          'abaddad2',
+        'feedfacedeadbeeffeedfacedeadbeef' +
+          'abaddad2',
+        'feedfacedeadbeeffeedfacedeadbeef' +
+          'abaddad2'
+      ];
+
+      var inputs = [
+        '',
+        '00000000000000000000000000000000',
+        'd9313225f88406e5a55909c5aff5269a' +
+          '86a7a9531534f7da2e4c303d8a318a72' +
+          '1c3c0c95956809532fcf0e2449a6b525' +
+          'b16aedf5aa0de657ba637b391aafd255',
+        'd9313225f88406e5a55909c5aff5269a' +
+          '86a7a9531534f7da2e4c303d8a318a72' +
+          '1c3c0c95956809532fcf0e2449a6b525' +
+          'b16aedf5aa0de657ba637b39',
+        'd9313225f88406e5a55909c5aff5269a' +
+          '86a7a9531534f7da2e4c303d8a318a72' +
+          '1c3c0c95956809532fcf0e2449a6b525' +
+          'b16aedf5aa0de657ba637b39',
+        'd9313225f88406e5a55909c5aff5269a' +
+          '86a7a9531534f7da2e4c303d8a318a72' +
+          '1c3c0c95956809532fcf0e2449a6b525' +
+          'b16aedf5aa0de657ba637b39'
+      ];
+
+      var outputs = [
+        '',
+        '0388dace60b6a392f328c2b971b2fe78',
+        '42831ec2217774244b7221b784d0d49c' +
+          'e3aa212f2c02a4e035c17e2329aca12e' +
+          '21d514b25466931c7d8f6a5aac84aa05' +
+          '1ba30b396a0aac973d58e091473f5985',
+        '42831ec2217774244b7221b784d0d49c' +
+          'e3aa212f2c02a4e035c17e2329aca12e' +
+          '21d514b25466931c7d8f6a5aac84aa05' +
+          '1ba30b396a0aac973d58e091',
+        '61353b4c2806934a777ff51fa22a4755' +
+          '699b2a714fcdc6f83766e5f97b6c7423' +
+          '73806900e49f24b22b097544d4896b42' +
+          '4989b5e1ebac0f07c23f4598',
+        '8ce24998625615b603a033aca13fb894' +
+          'be9112a5c3a211a8ba262a3cca7e2ca7' +
+          '01e4a9a4fba43c90ccdcb281d48c7c6f' +
+          'd62875d2aca417034c34aee5'
+      ];
+
+      var tags = [
+        '58e2fccefa7e3061367f1d57a4e7455a',
+        'ab6e47d42cec13bdf53a67b21257bddf',
+        '4d5c2af327cd64a62cf35abd2ba6fab4',
+        '5bc94fbc3221a5db94fae95ae7121a47',
+        '3612d2e79e3b0785561be14aaca2fccb',
+        '619cc5aefffe0bfa462af43c1699d050'
+      ];
+
+      for(var i = 0; i < keys.length; ++i) {
+        (function(i) {
+          var key = UTIL.hexToBytes(keys[i]);
+          var iv = UTIL.hexToBytes(ivs[i]);
+          var adata = UTIL.hexToBytes(adatas[i]);
+          var input = UTIL.hexToBytes(inputs[i]);
+          var output = UTIL.hexToBytes(outputs[i]);
+
+          it('should aes-128-gcm encrypt: ' + inputs[i], function() {
+            // encrypt
+            var cipher = CIPHER.createCipher('AES-GCM', key);
+            cipher.start({iv: iv, additionalData: adata});
+            cipher.update(UTIL.createBuffer(input));
+            cipher.finish();
+            ASSERT.equal(cipher.output.toHex(), outputs[i]);
+            ASSERT.equal(cipher.mode.tag.toHex(), tags[i]);
+          });
+
+          it('should aes-128-gcm decrypt: ' + outputs[i], function() {
+            // decrypt
+            var cipher = CIPHER.createDecipher('AES-GCM', key);
+            cipher.start({
+              iv: iv,
+              additionalData: adata,
+              tag: UTIL.hexToBytes(tags[i])
+            });
+            cipher.update(UTIL.createBuffer(output));
+            var pass = cipher.finish();
+            ASSERT.equal(cipher.mode.tag.toHex(), tags[i]);
+            ASSERT.equal(pass, true);
+            ASSERT.equal(cipher.output.toHex(), inputs[i]);
+          });
+        })(i);
+      }
+    })();
+
+    // AES-192-GCM
+    (function() {
+      var keys = [
+        '00000000000000000000000000000000' +
+          '0000000000000000',
+        '00000000000000000000000000000000' +
+          '0000000000000000',
+        'feffe9928665731c6d6a8f9467308308' +
+          'feffe9928665731c',
+        'feffe9928665731c6d6a8f9467308308' +
+          'feffe9928665731c',
+        'feffe9928665731c6d6a8f9467308308' +
+          'feffe9928665731c',
+        'feffe9928665731c6d6a8f9467308308' +
+          'feffe9928665731c'
+      ];
+
+      var ivs = [
+        '000000000000000000000000',
+        '000000000000000000000000',
+        'cafebabefacedbaddecaf888',
+        'cafebabefacedbaddecaf888',
+        'cafebabefacedbad',
+        '9313225df88406e555909c5aff5269aa' +
+          '6a7a9538534f7da1e4c303d2a318a728' +
+          'c3c0c95156809539fcf0e2429a6b5254' +
+          '16aedbf5a0de6a57a637b39b'
+      ];
+
+      var adatas = [
+        '',
+        '',
+        '',
+        'feedfacedeadbeeffeedfacedeadbeef' +
+          'abaddad2',
+        'feedfacedeadbeeffeedfacedeadbeef' +
+          'abaddad2',
+        'feedfacedeadbeeffeedfacedeadbeef' +
+          'abaddad2'
+      ];
+
+      var inputs = [
+        '',
+        '00000000000000000000000000000000',
+        'd9313225f88406e5a55909c5aff5269a' +
+          '86a7a9531534f7da2e4c303d8a318a72' +
+          '1c3c0c95956809532fcf0e2449a6b525' +
+          'b16aedf5aa0de657ba637b391aafd255',
+        'd9313225f88406e5a55909c5aff5269a' +
+          '86a7a9531534f7da2e4c303d8a318a72' +
+          '1c3c0c95956809532fcf0e2449a6b525' +
+          'b16aedf5aa0de657ba637b39',
+        'd9313225f88406e5a55909c5aff5269a' +
+          '86a7a9531534f7da2e4c303d8a318a72' +
+          '1c3c0c95956809532fcf0e2449a6b525' +
+          'b16aedf5aa0de657ba637b39',
+        'd9313225f88406e5a55909c5aff5269a' +
+          '86a7a9531534f7da2e4c303d8a318a72' +
+          '1c3c0c95956809532fcf0e2449a6b525' +
+          'b16aedf5aa0de657ba637b39'
+      ];
+
+      var outputs = [
+        '',
+        '98e7247c07f0fe411c267e4384b0f600',
+        '3980ca0b3c00e841eb06fac4872a2757' +
+          '859e1ceaa6efd984628593b40ca1e19c' +
+          '7d773d00c144c525ac619d18c84a3f47' +
+          '18e2448b2fe324d9ccda2710acade256',
+        '3980ca0b3c00e841eb06fac4872a2757' +
+          '859e1ceaa6efd984628593b40ca1e19c' +
+          '7d773d00c144c525ac619d18c84a3f47' +
+          '18e2448b2fe324d9ccda2710',
+        '0f10f599ae14a154ed24b36e25324db8' +
+          'c566632ef2bbb34f8347280fc4507057' +
+          'fddc29df9a471f75c66541d4d4dad1c9' +
+          'e93a19a58e8b473fa0f062f7',
+        'd27e88681ce3243c4830165a8fdcf9ff' +
+          '1de9a1d8e6b447ef6ef7b79828666e45' +
+          '81e79012af34ddd9e2f037589b292db3' +
+          'e67c036745fa22e7e9b7373b'
+      ];
+
+      var tags = [
+        'cd33b28ac773f74ba00ed1f312572435',
+        '2ff58d80033927ab8ef4d4587514f0fb',
+        '9924a7c8587336bfb118024db8674a14',
+        '2519498e80f1478f37ba55bd6d27618c',
+        '65dcc57fcf623a24094fcca40d3533f8',
+        'dcf566ff291c25bbb8568fc3d376a6d9'
+      ];
+
+      for(var i = 0; i < keys.length; ++i) {
+        (function(i) {
+          var key = UTIL.hexToBytes(keys[i]);
+          var iv = UTIL.hexToBytes(ivs[i]);
+          var adata = UTIL.hexToBytes(adatas[i]);
+          var input = UTIL.hexToBytes(inputs[i]);
+          var output = UTIL.hexToBytes(outputs[i]);
+
+          it('should aes-128-gcm encrypt: ' + inputs[i], function() {
+            // encrypt
+            var cipher = CIPHER.createCipher('AES-GCM', key);
+            cipher.start({iv: iv, additionalData: adata});
+            cipher.update(UTIL.createBuffer(input));
+            cipher.finish();
+            ASSERT.equal(cipher.output.toHex(), outputs[i]);
+            ASSERT.equal(cipher.mode.tag.toHex(), tags[i]);
+          });
+
+          it('should aes-128-gcm decrypt: ' + outputs[i], function() {
+            // decrypt
+            var cipher = CIPHER.createDecipher('AES-GCM', key);
+            cipher.start({
+              iv: iv,
+              additionalData: adata,
+              tag: UTIL.hexToBytes(tags[i])
+            });
+            cipher.update(UTIL.createBuffer(output));
+            var pass = cipher.finish();
+            ASSERT.equal(cipher.mode.tag.toHex(), tags[i]);
+            ASSERT.equal(pass, true);
+            ASSERT.equal(cipher.output.toHex(), inputs[i]);
+          });
+        })(i);
+      }
+    })();
+
+    // AES-256-GCM
+    (function() {
+      var keys = [
+        '00000000000000000000000000000000' +
+          '00000000000000000000000000000000',
+        '00000000000000000000000000000000' +
+          '00000000000000000000000000000000',
+        'feffe9928665731c6d6a8f9467308308' +
+          'feffe9928665731c6d6a8f9467308308',
+        'feffe9928665731c6d6a8f9467308308' +
+          'feffe9928665731c6d6a8f9467308308',
+        'feffe9928665731c6d6a8f9467308308' +
+          'feffe9928665731c6d6a8f9467308308',
+        'feffe9928665731c6d6a8f9467308308' +
+          'feffe9928665731c6d6a8f9467308308'
+      ];
+
+      var ivs = [
+        '000000000000000000000000',
+        '000000000000000000000000',
+        'cafebabefacedbaddecaf888',
+        'cafebabefacedbaddecaf888',
+        'cafebabefacedbad',
+        '9313225df88406e555909c5aff5269aa' +
+          '6a7a9538534f7da1e4c303d2a318a728' +
+          'c3c0c95156809539fcf0e2429a6b5254' +
+          '16aedbf5a0de6a57a637b39b'
+      ];
+
+      var adatas = [
+        '',
+        '',
+        '',
+        'feedfacedeadbeeffeedfacedeadbeef' +
+          'abaddad2',
+        'feedfacedeadbeeffeedfacedeadbeef' +
+          'abaddad2',
+        'feedfacedeadbeeffeedfacedeadbeef' +
+          'abaddad2'
+      ];
+
+      var inputs = [
+        '',
+        '00000000000000000000000000000000',
+        'd9313225f88406e5a55909c5aff5269a' +
+          '86a7a9531534f7da2e4c303d8a318a72' +
+          '1c3c0c95956809532fcf0e2449a6b525' +
+          'b16aedf5aa0de657ba637b391aafd255',
+        'd9313225f88406e5a55909c5aff5269a' +
+          '86a7a9531534f7da2e4c303d8a318a72' +
+          '1c3c0c95956809532fcf0e2449a6b525' +
+          'b16aedf5aa0de657ba637b39',
+        'd9313225f88406e5a55909c5aff5269a' +
+          '86a7a9531534f7da2e4c303d8a318a72' +
+          '1c3c0c95956809532fcf0e2449a6b525' +
+          'b16aedf5aa0de657ba637b39',
+        'd9313225f88406e5a55909c5aff5269a' +
+          '86a7a9531534f7da2e4c303d8a318a72' +
+          '1c3c0c95956809532fcf0e2449a6b525' +
+          'b16aedf5aa0de657ba637b39'
+      ];
+
+      var outputs = [
+        '',
+        'cea7403d4d606b6e074ec5d3baf39d18',
+        '522dc1f099567d07f47f37a32a84427d' +
+          '643a8cdcbfe5c0c97598a2bd2555d1aa' +
+          '8cb08e48590dbb3da7b08b1056828838' +
+          'c5f61e6393ba7a0abcc9f662898015ad',
+        '522dc1f099567d07f47f37a32a84427d' +
+          '643a8cdcbfe5c0c97598a2bd2555d1aa' +
+          '8cb08e48590dbb3da7b08b1056828838' +
+          'c5f61e6393ba7a0abcc9f662',
+        'c3762df1ca787d32ae47c13bf19844cb' +
+          'af1ae14d0b976afac52ff7d79bba9de0' +
+          'feb582d33934a4f0954cc2363bc73f78' +
+          '62ac430e64abe499f47c9b1f',
+        '5a8def2f0c9e53f1f75d7853659e2a20' +
+          'eeb2b22aafde6419a058ab4f6f746bf4' +
+          '0fc0c3b780f244452da3ebf1c5d82cde' +
+          'a2418997200ef82e44ae7e3f'
+      ];
+
+      var tags = [
+        '530f8afbc74536b9a963b4f1c4cb738b',
+        'd0d1c8a799996bf0265b98b5d48ab919',
+        'b094dac5d93471bdec1a502270e3cc6c',
+        '76fc6ece0f4e1768cddf8853bb2d551b',
+        '3a337dbf46a792c45e454913fe2ea8f2',
+        'a44a8266ee1c8eb0c8b5d4cf5ae9f19a'
+      ];
+
+      for(var i = 0; i < keys.length; ++i) {
+        (function(i) {
+          var key = UTIL.hexToBytes(keys[i]);
+          var iv = UTIL.hexToBytes(ivs[i]);
+          var adata = UTIL.hexToBytes(adatas[i]);
+          var input = UTIL.hexToBytes(inputs[i]);
+          var output = UTIL.hexToBytes(outputs[i]);
+
+          it('should aes-128-gcm encrypt: ' + inputs[i], function() {
+            // encrypt
+            var cipher = CIPHER.createCipher('AES-GCM', key);
+            cipher.start({iv: iv, additionalData: adata});
+            cipher.update(UTIL.createBuffer(input));
+            cipher.finish();
+            ASSERT.equal(cipher.output.toHex(), outputs[i]);
+            ASSERT.equal(cipher.mode.tag.toHex(), tags[i]);
+          });
+
+          it('should aes-128-gcm decrypt: ' + outputs[i], function() {
+            // decrypt
+            var cipher = CIPHER.createDecipher('AES-GCM', key);
+            cipher.start({
+              iv: iv,
+              additionalData: adata,
+              tag: UTIL.hexToBytes(tags[i])
+            });
+            cipher.update(UTIL.createBuffer(output));
+            var pass = cipher.finish();
+            ASSERT.equal(cipher.mode.tag.toHex(), tags[i]);
+            ASSERT.equal(pass, true);
+            ASSERT.equal(cipher.output.toHex(), inputs[i]);
+          });
+        })(i);
+      }
+    })();
+  });
+}
+
+// check for AMD
+var forge = {};
+if(typeof define === 'function') {
+  define([
+    'forge/cipher',
+    'forge/aes',
+    'forge/util'
+  ], function(CIPHER, AES, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      CIPHER(forge),
+      AES(forge),
+      UTIL(forge)
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/cipher')(forge),
+    require('../../js/aes')(forge),
+    require('../../js/util')(forge));
+}
+
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/asn1.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/asn1.js
new file mode 100644
index 0000000..7d0880e
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/asn1.js
@@ -0,0 +1,262 @@
+(function() {
+
+function Tests(ASSERT, ASN1, UTIL) {
+  describe('asn1', function() {
+    // TODO: add more ASN.1 coverage
+
+    it('should convert an OID to DER', function() {
+      ASSERT.equal(ASN1.oidToDer('1.2.840.113549').toHex(), '2a864886f70d');
+    });
+
+    it('should convert an OID from DER', function() {
+      var der = UTIL.hexToBytes('2a864886f70d');
+      ASSERT.equal(ASN1.derToOid(der), '1.2.840.113549');
+    });
+
+    it('should convert INTEGER 0 to DER', function() {
+      ASSERT.equal(ASN1.integerToDer(0).toHex(), '00');
+    });
+
+    it('should convert INTEGER 1 to DER', function() {
+      ASSERT.equal(ASN1.integerToDer(1).toHex(), '01');
+    });
+
+    it('should convert INTEGER 127 to DER', function() {
+      ASSERT.equal(ASN1.integerToDer(127).toHex(), '7f');
+    });
+
+    it('should convert INTEGER 128 to DER', function() {
+      ASSERT.equal(ASN1.integerToDer(128).toHex(), '0080');
+    });
+
+    it('should convert INTEGER 256 to DER', function() {
+      ASSERT.equal(ASN1.integerToDer(256).toHex(), '0100');
+    });
+
+    it('should convert INTEGER -128 to DER', function() {
+      ASSERT.equal(ASN1.integerToDer(-128).toHex(), '80');
+    });
+
+    it('should convert INTEGER -129 to DER', function() {
+      ASSERT.equal(ASN1.integerToDer(-129).toHex(), 'ff7f');
+    });
+
+    it('should convert INTEGER 32768 to DER', function() {
+      ASSERT.equal(ASN1.integerToDer(32768).toHex(), '008000');
+    });
+
+    it('should convert INTEGER -32768 to DER', function() {
+      ASSERT.equal(ASN1.integerToDer(-32768).toHex(), '8000');
+    });
+
+    it('should convert INTEGER -32769 to DER', function() {
+      ASSERT.equal(ASN1.integerToDer(-32769).toHex(), 'ff7fff');
+    });
+
+    it('should convert INTEGER 8388608 to DER', function() {
+      ASSERT.equal(ASN1.integerToDer(8388608).toHex(), '00800000');
+    });
+
+    it('should convert INTEGER -8388608 to DER', function() {
+      ASSERT.equal(ASN1.integerToDer(-8388608).toHex(), '800000');
+    });
+
+    it('should convert INTEGER -8388609 to DER', function() {
+      ASSERT.equal(ASN1.integerToDer(-8388609).toHex(), 'ff7fffff');
+    });
+
+    it('should convert INTEGER 2147483647 to DER', function() {
+      ASSERT.equal(ASN1.integerToDer(2147483647).toHex(), '7fffffff');
+    });
+
+    it('should convert INTEGER -2147483648 to DER', function() {
+      ASSERT.equal(ASN1.integerToDer(-2147483648).toHex(), '80000000');
+    });
+
+    it('should convert INTEGER 0 from DER', function() {
+      var der = UTIL.hexToBytes('00');
+      ASSERT.equal(ASN1.derToInteger(der), 0);
+    });
+
+    it('should convert INTEGER 1 from DER', function() {
+      var der = UTIL.hexToBytes('01');
+      ASSERT.equal(ASN1.derToInteger(der), 1);
+    });
+
+    it('should convert INTEGER 127 from DER', function() {
+      var der = UTIL.hexToBytes('7f');
+      ASSERT.equal(ASN1.derToInteger(der), 127);
+    });
+
+    it('should convert INTEGER 128 from DER', function() {
+      var der = UTIL.hexToBytes('0080');
+      ASSERT.equal(ASN1.derToInteger(der), 128);
+    });
+
+    it('should convert INTEGER 256 from DER', function() {
+      var der = UTIL.hexToBytes('0100');
+      ASSERT.equal(ASN1.derToInteger(der), 256);
+    });
+
+    it('should convert INTEGER -128 from DER', function() {
+      var der = UTIL.hexToBytes('80');
+      ASSERT.equal(ASN1.derToInteger(der), -128);
+    });
+
+    it('should convert INTEGER -129 from DER', function() {
+      var der = UTIL.hexToBytes('ff7f');
+      ASSERT.equal(ASN1.derToInteger(der), -129);
+    });
+
+    it('should convert INTEGER 32768 from DER', function() {
+      var der = UTIL.hexToBytes('008000');
+      ASSERT.equal(ASN1.derToInteger(der), 32768);
+    });
+
+    it('should convert INTEGER -32768 from DER', function() {
+      var der = UTIL.hexToBytes('8000');
+      ASSERT.equal(ASN1.derToInteger(der), -32768);
+    });
+
+    it('should convert INTEGER -32769 from DER', function() {
+      var der = UTIL.hexToBytes('ff7fff');
+      ASSERT.equal(ASN1.derToInteger(der), -32769);
+    });
+
+    it('should convert INTEGER 8388608 from DER', function() {
+      var der = UTIL.hexToBytes('00800000');
+      ASSERT.equal(ASN1.derToInteger(der), 8388608);
+    });
+
+    it('should convert INTEGER -8388608 from DER', function() {
+      var der = UTIL.hexToBytes('800000');
+      ASSERT.equal(ASN1.derToInteger(der), -8388608);
+    });
+
+    it('should convert INTEGER -8388609 from DER', function() {
+      var der = UTIL.hexToBytes('ff7fffff');
+      ASSERT.equal(ASN1.derToInteger(der), -8388609);
+    });
+
+    it('should convert INTEGER 2147483647 from DER', function() {
+      var der = UTIL.hexToBytes('7fffffff');
+      ASSERT.equal(ASN1.derToInteger(der), 2147483647);
+    });
+
+    it('should convert INTEGER -2147483648 from DER', function() {
+      var der = UTIL.hexToBytes('80000000');
+      ASSERT.equal(ASN1.derToInteger(der), -2147483648);
+    });
+
+    (function() {
+      var tests = [{
+        in: '20110223123400',
+        out: 1298464440000
+      }, {
+        in: '20110223123400.1',
+        out: 1298464440100
+      }, {
+        in: '20110223123400.123',
+        out: 1298464440123
+      }];
+      for(var i = 0; i < tests.length; ++i) {
+        var test = tests[i];
+        it('should convert local generalized time "' + test.in + '" to a Date', function() {
+          var d = ASN1.generalizedTimeToDate(test.in);
+          var localOffset = d.getTimezoneOffset() * 60000;
+          ASSERT.equal(d.getTime(), test.out + localOffset);
+        });
+      }
+    })();
+
+    (function() {
+      var tests = [{
+        in: '20110223123400Z', // Wed Feb 23 12:34:00.000 UTC 2011
+        out: 1298464440000
+      }, {
+        in: '20110223123400.1Z', // Wed Feb 23 12:34:00.100 UTC 2011
+        out: 1298464440100
+      }, {
+        in: '20110223123400.123Z', // Wed Feb 23 12:34:00.123 UTC 2011
+        out: 1298464440123
+      }, {
+        in: '20110223123400+0200', // Wed Feb 23 10:34:00.000 UTC 2011
+        out: 1298457240000
+      }, {
+        in: '20110223123400.1+0200', // Wed Feb 23 10:34:00.100 UTC 2011
+        out: 1298457240100
+      }, {
+        in: '20110223123400.123+0200', // Wed Feb 23 10:34:00.123 UTC 2011
+        out: 1298457240123
+      }, {
+        in: '20110223123400-0200', // Wed Feb 23 14:34:00.000 UTC 2011
+        out: 1298471640000
+      }, {
+        in: '20110223123400.1-0200', // Wed Feb 23 14:34:00.100 UTC 2011
+        out: 1298471640100
+      }, {
+        in: '20110223123400.123-0200', // Wed Feb 23 14:34:00.123 UTC 2011
+        out: 1298471640123
+      }];
+      for(var i = 0; i < tests.length; ++i) {
+        var test = tests[i];
+        it('should convert utc generalized time "' + test.in + '" to a Date', function() {
+          var d = ASN1.generalizedTimeToDate(test.in);
+          ASSERT.equal(d.getTime(), test.out);
+        });
+      }
+    })();
+
+    (function() {
+      var tests = [{
+        in: '1102231234Z', // Wed Feb 23 12:34:00 UTC 2011
+        out: 1298464440000
+      }, {
+        in: '1102231234+0200', // Wed Feb 23 10:34:00 UTC 2011
+        out: 1298457240000
+      }, {
+        in: '1102231234-0200', // Wed Feb 23 14:34:00 UTC 2011
+        out: 1298471640000
+      }, {
+        in: '110223123456Z', // Wed Feb 23 12:34:56 UTC 2011
+        out: 1298464496000
+      }, {
+        in: '110223123456+0200', // Wed Feb 23 10:34:56 UTC 2011
+        out: 1298457296000
+      }, {
+        in: '110223123456-0200', // Wed Feb 23 14:34:56 UTC 2011
+        out: 1298471696000
+      }];
+      for(var i = 0; i < tests.length; ++i) {
+        var test = tests[i];
+        it('should convert utc time "' + test.in + '" to a Date', function() {
+          var d = ASN1.utcTimeToDate(test.in);
+          ASSERT.equal(d.getTime(), test.out);
+        });
+      }
+    })();
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/asn1',
+    'forge/util'
+  ], function(ASN1, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      ASN1(),
+      UTIL()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/asn1')(),
+    require('../../js/util')());
+}
+
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/browser.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/browser.js
new file mode 100644
index 0000000..a96b2d6
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/browser.js
@@ -0,0 +1,41 @@
+var server = require('../server');
+var grunt = require('grunt');
+
+describe('browser', function() {
+  it('should run tests', function(done) {
+    this.timeout(60 * 1000 * 5);
+
+    return server.main(function(err, info) {
+      if(err) {
+        return done(err);
+      }
+
+      grunt.initConfig({
+        mocha: {
+          all: {
+            options: {
+              reporter: 'List',
+              urls: ['http://localhost:' + info.port + '/index.html']
+            }
+          }
+        }
+      });
+
+      grunt.loadNpmTasks('grunt-mocha');
+
+      grunt.registerInitTask('default', function() {
+        grunt.task.run(['mocha']);
+      });
+      grunt.tasks(['default'], {
+        //debug: true
+      }, function() {
+        if(err) {
+          return done(err);
+        }
+        // finish immediately
+        done(null);
+        return info.server.close();
+      });
+    });
+  });
+});
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/csr.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/csr.js
new file mode 100644
index 0000000..340c09f
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/csr.js
@@ -0,0 +1,148 @@
+(function() {
+
+function Tests(ASSERT, PKI) {
+  var _pem = {
+    privateKey: '-----BEGIN RSA PRIVATE KEY-----\r\n' +
+      'MIICXQIBAAKBgQDL0EugUiNGMWscLAVM0VoMdhDZEJOqdsUMpx9U0YZI7szokJqQ\r\n' +
+      'NIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMGTkP3VF29vXBo+dLq5e+8VyAy\r\n' +
+      'Q3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGtvnM+z0MYDdKo80efzwIDAQAB\r\n' +
+      'AoGAIzkGONi5G+JifmXlLJdplom486p3upf4Ce2/7mqfaG9MnkyPSairKD/JXvfh\r\n' +
+      'NNWkkN8DKKDKBcVVElPgORYT0qwrWc7ueLBMUCbRXb1ZyfEulimG0R3kjUh7NYau\r\n' +
+      'DaIkVgfykXGSQMZx8FoaT6L080zd+0emKDDYRrb+/kgJNJECQQDoUZoiC2K/DWNY\r\n' +
+      'h3/ppZ0ane2y4SBmJUHJVMPQ2CEgxsrJTxet668ckNCKaOP/3VFPoWC41f17DvKq\r\n' +
+      'noYINNntAkEA4JbZBZBVUrQFhHlrpXT4jzqtO2RlKZzEq8qmFZfEErxOT1WMyyCi\r\n' +
+      'lAQ5gUKardo1Kf0omC8Xq/uO9ZYdED55KwJBALs6cJ65UFaq4oLJiQPzLd7yokuE\r\n' +
+      'dcj8g71PLBTW6jPxIiMFNA89nz3FU9wIVp+xbMNhSoMMKqIPVPC+m0Rn260CQQDA\r\n' +
+      'I83fWK/mZWUjBM33a68KumRiH238v8XyQxj7+C8i6D8G2GXvkigFAehAkb7LZZd+\r\n' +
+      'KLuGFyPlWv3fVWHf99KpAkBQFKk3MRMl6IGJZUEFQe4l5whm8LkGU4acSqv9B3xt\r\n' +
+      'qROkCrsFrMPqjuuzEmyHoQZ64r2PLJg7FOuyhBnQUOt4\r\n' +
+      '-----END RSA PRIVATE KEY-----\r\n',
+    publicKey: '-----BEGIN PUBLIC KEY-----\r\n' +
+      'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDL0EugUiNGMWscLAVM0VoMdhDZ\r\n' +
+      'EJOqdsUMpx9U0YZI7szokJqQNIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMG\r\n' +
+      'TkP3VF29vXBo+dLq5e+8VyAyQ3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGt\r\n' +
+      'vnM+z0MYDdKo80efzwIDAQAB\r\n' +
+      '-----END PUBLIC KEY-----\r\n'
+  };
+
+  describe('csr', function() {
+    it('should generate a certification request', function() {
+      var keys = {
+        privateKey: PKI.privateKeyFromPem(_pem.privateKey),
+        publicKey: PKI.publicKeyFromPem(_pem.publicKey)
+      };
+      var csr = PKI.createCertificationRequest();
+      csr.publicKey = keys.publicKey;
+      csr.setSubject([{
+        name: 'commonName',
+        value: 'example.org'
+      }, {
+        name: 'countryName',
+        value: 'US'
+      }, {
+        shortName: 'ST',
+        value: 'Virginia'
+      }, {
+        name: 'localityName',
+        value: 'Blacksburg'
+      }, {
+        name: 'organizationName',
+        value: 'Test'
+      }, {
+        shortName: 'OU',
+        value: 'Test'
+      }]);
+      // add optional attributes
+      csr.setAttributes([{
+        name: 'challengePassword',
+        value: 'password'
+      }, {
+        name: 'unstructuredName',
+        value: 'My company'
+      }, {
+        name: 'extensionRequest',
+        extensions: [{
+          name: 'subjectAltName',
+          altNames: [{
+            // type 2 is DNS
+            type: 2,
+            value: 'test.domain.com'
+          }, {
+            type: 2,
+            value: 'other.domain.com'
+          }, {
+            type: 2,
+            value: 'www.domain.net'
+          }]
+        }]
+      }]);
+
+      // sign certification request
+      csr.sign(keys.privateKey);
+
+      var pem = PKI.certificationRequestToPem(csr);
+      csr = PKI.certificationRequestFromPem(pem);
+      ASSERT.ok(csr.getAttribute({name: 'extensionRequest'}));
+      ASSERT.equal(csr.getAttribute({name: 'extensionRequest'}).extensions[0].name, 'subjectAltName');
+      ASSERT.deepEqual(csr.getAttribute({name: 'extensionRequest'}).extensions[0].altNames, [{
+        // type 2 is DNS
+        type: 2,
+        value: 'test.domain.com'
+      }, {
+        type: 2,
+        value: 'other.domain.com'
+      }, {
+        type: 2,
+        value: 'www.domain.net'
+      }]);
+      ASSERT.ok(csr.verify());
+    });
+
+    it('should load an OpenSSL-generated certification request', function() {
+      var pem = '-----BEGIN CERTIFICATE REQUEST-----\r\n' +
+        'MIICdTCCAV0CAQAwMDEVMBMGA1UEAwwMTXlDb21tb25OYW1lMRcwFQYDVQQKDA5N\r\n' +
+        'eU9yZ2FuaXphdGlvbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKRU\r\n' +
+        'zrAMbiiSjAYYl3PWsOrNwY0VtemgRZc0t7+3FlWp1e8uIA3KxZFZY875wo0QOvD+\r\n' +
+        'AdNv5+YnokgzOi83F3T4yewBSR0TiO3Pa4tL4C7CzWnhYliC/owk5bHCV0HLkYUW\r\n' +
+        'F6z7Lx3HyhoxlKmrHySSPPZRLKp7KcwxbjFc2EfhQV21I73Z1mCG6MEp7cN2qBbQ\r\n' +
+        'PyOMNjAUibOWs4JJEdUjWhm86EZm9+qfgpL5tlpZCe+kXySrKTp56mMsfSOQvlol\r\n' +
+        'pRO8pP9AUjaEqRikCZ745I/9W7dHNPUoyxkWV5jRDwcT7s652+L6oxtoqVOXpg28\r\n' +
+        'uAL0kUZQMa8wkYUKZiMCAwEAAaAAMA0GCSqGSIb3DQEBBQUAA4IBAQCXQH+ut6tr\r\n' +
+        'Z/FdIDOljrc7uh8XpFRKS3GqC/PJsEwrV7d3CX5HuWPTuPc9FU5FQ88w6evXEA0o\r\n' +
+        'ijxHuydeXmdjpy433vXWo1TaRSXh1WaBMG5pW/SlGZK9/Hr1P0v7KN/KCY5nXxoQ\r\n' +
+        'k3Ndg9HzGrYnRoJVXzvdQeBGwCoJFk4FH+Rxa/F03VTUU5nwx66TsL9JUp9pnbI7\r\n' +
+        'MR6DIA97LnTmut8Xp0Uurw+zsS5rif9iv0BKHd7eGpNNGl0RXu8E5dbT0zD90TSa\r\n' +
+        'P5WjxjvY+Udg8XZU+UwT3kcyTEFpiQdkzTIKXg0dFurfUE9XG/9aic9oMZ/IBZz9\r\n' +
+        'a535a7e9RkbJ\r\n' +
+        '-----END CERTIFICATE REQUEST-----\r\n';
+
+      var csr = PKI.certificationRequestFromPem(pem);
+      ASSERT.equal(csr.subject.getField('CN').value, 'MyCommonName');
+      ASSERT.equal(csr.subject.getField('O').value, 'MyOrganization');
+      ASSERT.equal(csr.signatureOid, PKI.oids.sha1WithRSAEncryption);
+      ASSERT.equal(csr.publicKey.e.toString(16), '10001');
+      ASSERT.equal(csr.publicKey.n.toString(16).toUpperCase(), 'A454CEB00C6E28928C06189773D6B0EACDC18D15B5E9A0459734B7BFB71655A9D5EF2E200DCAC5915963CEF9C28D103AF0FE01D36FE7E627A248333A2F371774F8C9EC01491D1388EDCF6B8B4BE02EC2CD69E1625882FE8C24E5B1C25741CB91851617ACFB2F1DC7CA1A3194A9AB1F24923CF6512CAA7B29CC316E315CD847E1415DB523BDD9D66086E8C129EDC376A816D03F238C36301489B396B3824911D5235A19BCE84666F7EA9F8292F9B65A5909EFA45F24AB293A79EA632C7D2390BE5A25A513BCA4FF40523684A918A4099EF8E48FFD5BB74734F528CB19165798D10F0713EECEB9DBE2FAA31B68A95397A60DBCB802F491465031AF3091850A6623');
+      ASSERT.ok(csr.verify());
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/pki'
+  ], function(PKI) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      PKI()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/pki')());
+}
+
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/des.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/des.js
new file mode 100644
index 0000000..8be2c68
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/des.js
@@ -0,0 +1,155 @@
+(function() {
+
+function Tests(ASSERT, CIPHER, DES, UTIL) {
+  describe('des', function() {
+    // OpenSSL equivalent:
+    // openssl enc -des-ecb -K a1c06b381adf3651 -nosalt
+    it('should des-ecb encrypt: foobar', function() {
+      var key = new UTIL.createBuffer(
+        UTIL.hexToBytes('a1c06b381adf3651'));
+
+      var cipher = CIPHER.createCipher('DES-ECB', key);
+      cipher.start();
+      cipher.update(UTIL.createBuffer('foobar'));
+      cipher.finish();
+      ASSERT.equal(cipher.output.toHex(), 'b705ffcf3dff06b3');
+    });
+
+    // OpenSSL equivalent:
+    // openssl enc -d -des-ecb -K a1c06b381adf3651 -nosalt
+    it('should des-ecb decrypt: b705ffcf3dff06b3', function() {
+      var key = new UTIL.createBuffer(
+        UTIL.hexToBytes('a1c06b381adf3651'));
+
+      var decipher = CIPHER.createDecipher('DES-ECB', key);
+      decipher.start();
+      decipher.update(UTIL.createBuffer(UTIL.hexToBytes('b705ffcf3dff06b3')));
+      decipher.finish();
+      ASSERT.equal(decipher.output.getBytes(), 'foobar');
+    });
+
+    // OpenSSL equivalent:
+    // openssl enc -des -K a1c06b381adf3651 -iv 818bcf76efc59662 -nosalt
+    it('should des-cbc encrypt: foobar', function() {
+      var key = new UTIL.createBuffer(
+        UTIL.hexToBytes('a1c06b381adf3651'));
+      var iv = new UTIL.createBuffer(
+        UTIL.hexToBytes('818bcf76efc59662'));
+
+      var cipher = CIPHER.createCipher('DES-CBC', key);
+      cipher.start({iv: iv});
+      cipher.update(UTIL.createBuffer('foobar'));
+      cipher.finish();
+      ASSERT.equal(cipher.output.toHex(), '3261e5839a990454');
+    });
+
+    // OpenSSL equivalent:
+    // openssl enc -d -des -K a1c06b381adf3651 -iv 818bcf76efc59662 -nosalt
+    it('should des-cbc decrypt: 3261e5839a990454', function() {
+      var key = new UTIL.createBuffer(
+        UTIL.hexToBytes('a1c06b381adf3651'));
+      var iv = new UTIL.createBuffer(
+        UTIL.hexToBytes('818bcf76efc59662'));
+
+      var decipher = CIPHER.createDecipher('DES-CBC', key);
+      decipher.start({iv: iv});
+      decipher.update(UTIL.createBuffer(UTIL.hexToBytes('3261e5839a990454')));
+      decipher.finish();
+      ASSERT.equal(decipher.output.getBytes(), 'foobar');
+    });
+
+    // OpenSSL equivalent:
+    // openssl enc -des-ede3 -K a1c06b381adf36517e84575552777779da5e3d9f994b05b5 -nosalt
+    it('should 3des-ecb encrypt: foobar', function() {
+      var key = new UTIL.createBuffer(
+        UTIL.hexToBytes('a1c06b381adf36517e84575552777779da5e3d9f994b05b5'));
+
+      var cipher = CIPHER.createCipher('3DES-ECB', key);
+      cipher.start();
+      cipher.update(UTIL.createBuffer('foobar'));
+      cipher.finish();
+      ASSERT.equal(cipher.output.toHex(), 'fce8b1ee8c6440d1');
+    });
+
+    // OpenSSL equivalent:
+    // openssl enc -d -des-ede3 -K a1c06b381adf36517e84575552777779da5e3d9f994b05b5 -nosalt
+    it('should 3des-ecb decrypt: fce8b1ee8c6440d1', function() {
+      var key = new UTIL.createBuffer(
+        UTIL.hexToBytes('a1c06b381adf36517e84575552777779da5e3d9f994b05b5'));
+
+      var decipher = CIPHER.createDecipher('3DES-ECB', key);
+      decipher.start();
+      decipher.update(UTIL.createBuffer(UTIL.hexToBytes('fce8b1ee8c6440d1')));
+      decipher.finish();
+      ASSERT.equal(decipher.output.getBytes(), 'foobar');
+    });
+
+    // OpenSSL equivalent:
+    // openssl enc -des3 -K a1c06b381adf36517e84575552777779da5e3d9f994b05b5 -iv 818bcf76efc59662 -nosalt
+    it('should 3des-cbc encrypt "foobar", restart, and encrypt "foobar,,"', function() {
+      var key = new UTIL.createBuffer(
+        UTIL.hexToBytes('a1c06b381adf36517e84575552777779da5e3d9f994b05b5'));
+      var iv = new UTIL.createBuffer(
+        UTIL.hexToBytes('818bcf76efc59662'));
+
+      var cipher = CIPHER.createCipher('3DES-CBC', key);
+      cipher.start({iv: iv.copy()});
+      cipher.update(UTIL.createBuffer('foobar'));
+      cipher.finish();
+      ASSERT.equal(cipher.output.toHex(), '209225f7687ca0b2');
+
+      cipher.start({iv: iv.copy()});
+      cipher.update(UTIL.createBuffer('foobar,,'));
+      cipher.finish();
+      ASSERT.equal(cipher.output.toHex(), '57156174c48dfc37293831bf192a6742');
+    });
+
+    // OpenSSL equivalent:
+    // openssl enc -d -des3 -K a1c06b381adf36517e84575552777779da5e3d9f994b05b5 -iv 818bcf76efc59662 -nosalt
+    it('should 3des-cbc decrypt "209225f7687ca0b2", restart, and decrypt "57156174c48dfc37293831bf192a6742,,"', function() {
+      var key = new UTIL.createBuffer(
+        UTIL.hexToBytes('a1c06b381adf36517e84575552777779da5e3d9f994b05b5'));
+      var iv = new UTIL.createBuffer(
+        UTIL.hexToBytes('818bcf76efc59662'));
+
+      var decipher = CIPHER.createDecipher('3DES-CBC', key);
+      decipher.start({iv: iv.copy()});
+      decipher.update(UTIL.createBuffer(UTIL.hexToBytes('209225f7687ca0b2')));
+      decipher.finish();
+      ASSERT.equal(decipher.output.getBytes(), 'foobar');
+
+      decipher.start({iv: iv.copy()});
+      decipher.update(
+        UTIL.createBuffer(UTIL.hexToBytes('57156174c48dfc37293831bf192a6742')));
+      decipher.finish();
+      ASSERT.equal(decipher.output.getBytes(), 'foobar,,');
+    });
+  });
+}
+
+// check for AMD
+var forge = {};
+if(typeof define === 'function') {
+  define([
+    'forge/cipher',
+    'forge/des',
+    'forge/util'
+  ], function(CIPHER, DES, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      CIPHER(forge),
+      DES(forge),
+      UTIL(forge)
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/cipher')(forge),
+    require('../../js/des')(forge),
+    require('../../js/util')(forge));
+}
+
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/hmac.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/hmac.js
new file mode 100644
index 0000000..404b36b
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/hmac.js
@@ -0,0 +1,85 @@
+(function() {
+
+function Tests(ASSERT, HMAC, UTIL) {
+  describe('hmac', function() {
+    it('should md5 hash "Hi There", 16-byte key', function() {
+      var key = UTIL.hexToBytes('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b');
+      var hmac = HMAC.create();
+      hmac.start('MD5', key);
+      hmac.update('Hi There');
+      ASSERT.equal(hmac.digest().toHex(), '9294727a3638bb1c13f48ef8158bfc9d');
+    });
+
+    it('should md5 hash "what do ya want for nothing?", "Jefe" key', function() {
+      var hmac = HMAC.create();
+      hmac.start('MD5', 'Jefe');
+      hmac.update('what do ya want for nothing?');
+      ASSERT.equal(hmac.digest().toHex(), '750c783e6ab0b503eaa86e310a5db738');
+    });
+
+    it('should md5 hash "Test Using Larger Than Block-Size Key - Hash Key First", 80-byte key', function() {
+      var key = UTIL.hexToBytes(
+        'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
+        'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
+        'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
+        'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
+      var hmac = HMAC.create();
+      hmac.start('MD5', key);
+      hmac.update('Test Using Larger Than Block-Size Key - Hash Key First');
+      ASSERT.equal(hmac.digest().toHex(), '6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd');
+    });
+
+    it('should sha1 hash "Hi There", 20-byte key', function() {
+      var key = UTIL.hexToBytes('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b');
+      var hmac = HMAC.create();
+      hmac.start('SHA1', key);
+      hmac.update('Hi There');
+      ASSERT.equal(
+        hmac.digest().toHex(), 'b617318655057264e28bc0b6fb378c8ef146be00');
+    });
+
+    it('should sha1 hash "what do ya want for nothing?", "Jefe" key', function() {
+      var hmac = HMAC.create();
+      hmac.start('SHA1', 'Jefe');
+      hmac.update('what do ya want for nothing?');
+      ASSERT.equal(
+        hmac.digest().toHex(), 'effcdf6ae5eb2fa2d27416d5f184df9c259a7c79');
+    });
+
+    it('should sha1 hash "Test Using Larger Than Block-Size Key - Hash Key First", 80-byte key', function() {
+      var key = UTIL.hexToBytes(
+        'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
+        'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
+        'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
+        'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
+      var hmac = HMAC.create();
+      hmac.start('SHA1', key);
+      hmac.update('Test Using Larger Than Block-Size Key - Hash Key First');
+      ASSERT.equal(
+        hmac.digest().toHex(), 'aa4ae5e15272d00e95705637ce8a3b55ed402112');
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/hmac',
+    'forge/util'
+  ], function(HMAC, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      HMAC(),
+      UTIL()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/hmac')(),
+    require('../../js/util')());
+}
+
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/kem.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/kem.js
new file mode 100644
index 0000000..0415abe
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/kem.js
@@ -0,0 +1,198 @@
+(function() {
+
+function Tests(ASSERT, KEM, MD, RSA, UTIL, JSBN, RANDOM) {
+
+  function FixedSecureRandom(str) {
+    var bytes = UTIL.hexToBytes(str);
+    this.getBytesSync = function(count) {
+      // prepend zeros
+      return UTIL.fillString(String.fromCharCode(0), bytes.length - count) +
+        bytes;
+    };
+  }
+
+  describe('kem', function() {
+    it('should generate and encrypt a symmetric key and decrypt it 10x', function() {
+      for(var i = 0; i < 10; ++i) {
+        var kdf = new KEM.kdf1(MD.sha256.create());
+        var kem = KEM.rsa.create(kdf);
+
+        var pair = RSA.generateKeyPair(512);
+
+        var result = kem.encrypt(pair.publicKey, 256);
+        var key1 = result.key;
+        var key2 = kem.decrypt(pair.privateKey, result.encapsulation, 256);
+
+        ASSERT.equal(UTIL.bytesToHex(key1), UTIL.bytesToHex(key2));
+      }
+    });
+  });
+
+  /**
+   * According to section "C.6 Test vectors for RSA-KEM" from ISO-18033-2 final
+   * draft.
+   */
+  describe('C.6 Test vectors for RSA-KEM from ISO-18033-2 final', function() {
+    it('should pass test vector C.6.1', function() {
+      var n = '5888113332502691251761936431009284884966640757179802337490546478326238537107326596800820237597139824869184990638749556269785797065508097452399642780486933';
+      var e = '65537';
+      var d = '3202313555859948186315374524474173995679783580392140237044349728046479396037520308981353808895461806395564474639124525446044708705259675840210989546479265';
+
+      var C0 = '4603e5324cab9cef8365c817052d954d44447b1667099edc69942d32cd594e4ffcf268ae3836e2c35744aaa53ae201fe499806b67dedaa26bf72ecbd117a6fc0';
+      var K = '5f8de105b5e96b2e490ddecbd147dd1def7e3b8e0e6a26eb7b956ccb8b3bdc1ca975bc57c3989e8fbad31a224655d800c46954840ff32052cdf0d640562bdfadfa263cfccf3c52b29f2af4a1869959bc77f854cf15bd7a25192985a842dbff8e13efee5b7e7e55bbe4d389647c686a9a9ab3fb889b2d7767d3837eea4e0a2f04';
+
+      var kdf = new KEM.kdf1(MD.sha1.create());
+      var rnd = new FixedSecureRandom('032e45326fa859a72ec235acff929b15d1372e30b207255f0611b8f785d764374152e0ac009e509e7ba30cd2f1778e113b64e135cf4e2292c75efe5288edfda4');
+      var kem = KEM.rsa.create(kdf, {prng: rnd});
+
+      var rsaPublicKey = RSA.setPublicKey(
+        new JSBN.BigInteger(n), new JSBN.BigInteger(e));
+      var rsaPrivateKey = RSA.setPrivateKey(
+        new JSBN.BigInteger(n), null, new JSBN.BigInteger(d));
+
+      var result = kem.encrypt(rsaPublicKey, 128);
+      ASSERT.equal(UTIL.bytesToHex(result.encapsulation), C0);
+      ASSERT.equal(UTIL.bytesToHex(result.key), K);
+
+      var decryptedKey = kem.decrypt(rsaPrivateKey, result.encapsulation, 128);
+      ASSERT.equal(UTIL.bytesToHex(decryptedKey), K);
+    });
+
+    it('should pass test vector C.6.2', function() {
+      var n = '5888113332502691251761936431009284884966640757179802337490546478326238537107326596800820237597139824869184990638749556269785797065508097452399642780486933';
+      var e = '65537';
+      var d = '3202313555859948186315374524474173995679783580392140237044349728046479396037520308981353808895461806395564474639124525446044708705259675840210989546479265';
+
+      var C0 = '4603e5324cab9cef8365c817052d954d44447b1667099edc69942d32cd594e4ffcf268ae3836e2c35744aaa53ae201fe499806b67dedaa26bf72ecbd117a6fc0';
+      var K = '0e6a26eb7b956ccb8b3bdc1ca975bc57c3989e8fbad31a224655d800c46954840ff32052cdf0d640562bdfadfa263cfccf3c52b29f2af4a1869959bc77f854cf15bd7a25192985a842dbff8e13efee5b7e7e55bbe4d389647c686a9a9ab3fb889b2d7767d3837eea4e0a2f04b53ca8f50fb31225c1be2d0126c8c7a4753b0807';
+
+      var kdf = new KEM.kdf2(MD.sha1.create());
+      var rnd = new FixedSecureRandom('032e45326fa859a72ec235acff929b15d1372e30b207255f0611b8f785d764374152e0ac009e509e7ba30cd2f1778e113b64e135cf4e2292c75efe5288edfda4');
+      var kem = KEM.rsa.create(kdf, {prng: rnd});
+
+      var rsaPublicKey = RSA.setPublicKey(
+        new JSBN.BigInteger(n), new JSBN.BigInteger(e));
+      var rsaPrivateKey = RSA.setPrivateKey(
+        new JSBN.BigInteger(n), null, new JSBN.BigInteger(d));
+
+      var result = kem.encrypt(rsaPublicKey, 128);
+      ASSERT.equal(UTIL.bytesToHex(result.encapsulation), C0);
+      ASSERT.equal(UTIL.bytesToHex(result.key), K);
+
+      var decryptedKey = kem.decrypt(rsaPrivateKey, result.encapsulation, 128);
+      ASSERT.equal(UTIL.bytesToHex(decryptedKey), K);
+    });
+
+    it('should pass test vector C.6.3', function() {
+      var n = '5888113332502691251761936431009284884966640757179802337490546478326238537107326596800820237597139824869184990638749556269785797065508097452399642780486933';
+      var e = '65537';
+      var d = '3202313555859948186315374524474173995679783580392140237044349728046479396037520308981353808895461806395564474639124525446044708705259675840210989546479265';
+
+      var C0 = '4603e5324cab9cef8365c817052d954d44447b1667099edc69942d32cd594e4ffcf268ae3836e2c35744aaa53ae201fe499806b67dedaa26bf72ecbd117a6fc0';
+      var K = '09e2decf2a6e1666c2f6071ff4298305e2643fd510a2403db42a8743cb989de86e668d168cbe604611ac179f819a3d18412e9eb45668f2923c087c12fee0c5a0d2a8aa70185401fbbd99379ec76c663e875a60b4aacb1319fa11c3365a8b79a44669f26fb555c80391847b05eca1cb5cf8c2d531448d33fbaca19f6410ee1fcb';
+
+      var kdf = new KEM.kdf1(MD.sha256.create(), 20);
+      var rnd = new FixedSecureRandom('032e45326fa859a72ec235acff929b15d1372e30b207255f0611b8f785d764374152e0ac009e509e7ba30cd2f1778e113b64e135cf4e2292c75efe5288edfda4');
+      var kem = KEM.rsa.create(kdf, {prng: rnd});
+
+      var rsaPublicKey = RSA.setPublicKey(
+        new JSBN.BigInteger(n), new JSBN.BigInteger(e));
+      var rsaPrivateKey = RSA.setPrivateKey(
+        new JSBN.BigInteger(n), null, new JSBN.BigInteger(d));
+
+      var result = kem.encrypt(rsaPublicKey, 128);
+      ASSERT.equal(UTIL.bytesToHex(result.encapsulation), C0);
+      ASSERT.equal(UTIL.bytesToHex(result.key), K);
+
+      var decryptedKey = kem.decrypt(rsaPrivateKey, result.encapsulation, 128);
+      ASSERT.equal(UTIL.bytesToHex(decryptedKey), K);
+    });
+
+    it('should pass test vector C.6.4', function() {
+      var n = '5888113332502691251761936431009284884966640757179802337490546478326238537107326596800820237597139824869184990638749556269785797065508097452399642780486933';
+      var e = '65537';
+      var d = '3202313555859948186315374524474173995679783580392140237044349728046479396037520308981353808895461806395564474639124525446044708705259675840210989546479265';
+
+      var C0 = '4603e5324cab9cef8365c817052d954d44447b1667099edc69942d32cd594e4ffcf268ae3836e2c35744aaa53ae201fe499806b67dedaa26bf72ecbd117a6fc0';
+      var K = '10a2403db42a8743cb989de86e668d168cbe604611ac179f819a3d18412e9eb45668f2923c087c12fee0c5a0d2a8aa70185401fbbd99379ec76c663e875a60b4aacb1319fa11c3365a8b79a44669f26fb555c80391847b05eca1cb5cf8c2d531448d33fbaca19f6410ee1fcb260892670e0814c348664f6a7248aaf998a3acc6';
+
+      var kdf = new KEM.kdf2(MD.sha256.create(), 20);
+      var rnd = new FixedSecureRandom('00032e45326fa859a72ec235acff929b15d1372e30b207255f0611b8f785d764374152e0ac009e509e7ba30cd2f1778e113b64e135cf4e2292c75efe5288edfda4');
+      var kem = KEM.rsa.create(kdf, {prng: rnd});
+
+      var rsaPublicKey = RSA.setPublicKey(
+        new JSBN.BigInteger(n), new JSBN.BigInteger(e));
+      var rsaPrivateKey = RSA.setPrivateKey(
+        new JSBN.BigInteger(n), null, new JSBN.BigInteger(d));
+
+      var result = kem.encrypt(rsaPublicKey, 128);
+      ASSERT.equal(UTIL.bytesToHex(result.encapsulation), C0);
+      ASSERT.equal(UTIL.bytesToHex(result.key), K);
+
+      var decryptedKey = kem.decrypt(rsaPrivateKey, result.encapsulation, 128);
+      ASSERT.equal(UTIL.bytesToHex(decryptedKey), K);
+    });
+  });
+
+  describe('prepended zeros test', function() {
+    it('should pass when random has leading zeros', function() {
+      var n = '5888113332502691251761936431009284884966640757179802337490546478326238537107326596800820237597139824869184990638749556269785797065508097452399642780486933';
+      var e = '65537';
+      var d = '3202313555859948186315374524474173995679783580392140237044349728046479396037520308981353808895461806395564474639124525446044708705259675840210989546479265';
+
+      var C0 = '5f268a76c1aed04bc195a143d7ee768bee0aad308d16196274a02d9c1a72bbe10cbf718de323fc0135c5f8129f96ac8f504d9623960dc54cd87bddee94f5a0b2';
+      var K = '8bf41e59dc1b83142ee32569a347a94539e48c98347c685a29e3aa8b7a3ea714d68c1a43c4a760c9d4a45149b0ce8b681e98076bdd4393394c7832a7fa71848257772ac38a4e7fbe96e8bb383becbb7242841946e82e35d9ef1667245fc82601e7edf53b897f5ce2b6bce8e1e3212abd5a8a99a0c9b99472e22a313dac396383';
+
+      var kdf = new KEM.kdf1(MD.sha1.create());
+      var rnd = new FixedSecureRandom('000e45326fa859a72ec235acff929b15d1372e30b207255f0611b8f785d764374152e0ac009e509e7ba30cd2f1778e113b64e135cf4e2292c75efe5288edfda4');
+      var kem = KEM.rsa.create(kdf, {prng: rnd});
+
+      var rsaPublicKey = RSA.setPublicKey(
+        new JSBN.BigInteger(n), new JSBN.BigInteger(e));
+      var rsaPrivateKey = RSA.setPrivateKey(
+        new JSBN.BigInteger(n), null, new JSBN.BigInteger(d));
+
+      var result = kem.encrypt(rsaPublicKey, 128);
+      ASSERT.equal(UTIL.bytesToHex(result.encapsulation), C0);
+      ASSERT.equal(UTIL.bytesToHex(result.key), K);
+
+      var decryptedKey = kem.decrypt(rsaPrivateKey, result.encapsulation, 128);
+      ASSERT.equal(UTIL.bytesToHex(decryptedKey), K);
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/kem',
+    'forge/md',
+    'forge/rsa',
+    'forge/util',
+    'forge/jsbn',
+    'forge/random'
+  ], function(KEM, MD, RSA, UTIL, JSBN, RANDOM) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      KEM(),
+      MD(),
+      RSA(),
+      UTIL(),
+      JSBN(),
+      RANDOM()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/kem')(),
+    require('../../js/md')(),
+    require('../../js/rsa')(),
+    require('../../js/util')(),
+    require('../../js/jsbn')(),
+    require('../../js/random')());
+}
+
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/md5.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/md5.js
new file mode 100644
index 0000000..5ab3d58
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/md5.js
@@ -0,0 +1,117 @@
+(function() {
+
+function Tests(ASSERT, MD5, UTIL) {
+  describe('md5', function() {
+    it('should digest the empty string', function() {
+      var md = MD5.create();
+      ASSERT.equal(md.digest().toHex(), 'd41d8cd98f00b204e9800998ecf8427e');
+    });
+
+    it('should digest "abc"', function() {
+      var md = MD5.create();
+      md.update('abc');
+      ASSERT.equal(md.digest().toHex(), '900150983cd24fb0d6963f7d28e17f72');
+    });
+
+    it('should digest "The quick brown fox jumps over the lazy dog"', function() {
+      var md = MD5.create();
+      md.update('The quick brown fox jumps over the lazy dog');
+      ASSERT.equal(md.digest().toHex(), '9e107d9d372bb6826bd81d3542a419d6');
+    });
+
+    it('should digest "c\'\u00e8"', function() {
+      var md = MD5.create();
+      md.update("c\'\u00e8", 'utf8');
+      ASSERT.equal(md.digest().toHex(), '8ef7c2941d78fe89f31e614437c9db59');
+    });
+
+    it('should digest "THIS IS A MESSAGE"', function() {
+      var md = MD5.create();
+      md.start();
+      md.update('THIS IS ');
+      md.update('A MESSAGE');
+      // do twice to check continuing digest
+      ASSERT.equal(md.digest().toHex(), '78eebfd9d42958e3f31244f116ab7bbe');
+      ASSERT.equal(md.digest().toHex(), '78eebfd9d42958e3f31244f116ab7bbe');
+    });
+
+    it('should digest a long message', function() {
+      var input = UTIL.hexToBytes(
+        '0100002903018d32e9c6dc423774c4c39a5a1b78f44cc2cab5f676d39' +
+        'f703d29bfa27dfeb870000002002f01000200004603014c2c1e835d39' +
+        'da71bc0857eb04c2b50fe90dbb2a8477fe7364598d6f0575999c20a6c' +
+        '7248c5174da6d03ac711888f762fc4ed54f7254b32273690de849c843' +
+        '073d002f000b0003d20003cf0003cc308203c8308202b0a0030201020' +
+        '20100300d06092a864886f70d0101050500308186310b300906035504' +
+        '0613025553311d301b060355040a13144469676974616c2042617a616' +
+        '1722c20496e632e31443042060355040b133b4269746d756e6b206c6f' +
+        '63616c686f73742d6f6e6c7920436572746966696361746573202d204' +
+        '17574686f72697a6174696f6e20766961204254503112301006035504' +
+        '0313096c6f63616c686f7374301e170d3130303231343137303931395' +
+        'a170d3230303231333137303931395a308186310b3009060355040613' +
+        '025553311d301b060355040a13144469676974616c2042617a6161722' +
+        'c20496e632e31443042060355040b133b4269746d756e6b206c6f6361' +
+        '6c686f73742d6f6e6c7920436572746966696361746573202d2041757' +
+        '4686f72697a6174696f6e207669612042545031123010060355040313' +
+        '096c6f63616c686f737430820122300d06092a864886f70d010101050' +
+        '00382010f003082010a0282010100dc436f17d6909d8a9d6186ea218e' +
+        'b5c86b848bae02219bd56a71203daf07e81bc19e7e98134136bcb0128' +
+        '81864bf03b3774652ad5eab85dba411a5114ffeac09babce75f313143' +
+        '45512cd87c91318b2e77433270a52185fc16f428c3ca412ad6e9484bc' +
+        '2fb87abb4e8fb71bf0f619e31a42340b35967f06c24a741a31c979c0b' +
+        'b8921a90a47025fbeb8adca576979e70a56830c61170c9647c18c0794' +
+        'd68c0df38f3aac5fc3b530e016ea5659715339f3f3c209cdee9dbe794' +
+        'b5af92530c5754c1d874b78974bfad994e0dfc582275e79feb522f6e4' +
+        'bcc2b2945baedfb0dbdaebb605f9483ff0bea29ecd5f4d6f2769965d1' +
+        'b3e04f8422716042680011ff676f0203010001a33f303d300c0603551' +
+        'd130101ff04023000300e0603551d0f0101ff0404030204f0301d0603' +
+        '551d250416301406082b0601050507030106082b06010505070302300' +
+        'd06092a864886f70d010105050003820101009c4562be3f2d8d8e3880' +
+        '85a697f2f106eaeff4992a43f198fe3dcf15c8229cf1043f061a38204' +
+        'f73d86f4fb6348048cc5279ed719873aa10e3773d92b629c2c3fcce04' +
+        '012c81ba3b4ec451e9644ec5191078402d845e05d02c7b4d974b45882' +
+        '76e5037aba7ef26a8bddeb21e10698c82f425e767dc401adf722fa73a' +
+        'b78cfa069bd69052d7ca6a75cc9225550e315d71c5f8764362ea4dbc6' +
+        'ecb837a8471043c5a7f826a71af145a053090bd4bccca6a2c552841cd' +
+        'b1908a8352f49283d2e641acdef667c7543af441a16f8294251e2ac37' +
+        '6fa507b53ae418dd038cd20cef1e7bfbf5ae03a7c88d93d843abaabbd' +
+        'c5f3431132f3e559d2dd414c3eda38a210b80e0000001000010201002' +
+        '6a220b7be857402819b78d81080d01a682599bbd00902985cc64edf8e' +
+        '520e4111eb0e1729a14ffa3498ca259cc9ad6fc78fa130d968ebdb78d' +
+        'c0b950c0aa44355f13ba678419185d7e4608fe178ca6b2cef33e41937' +
+        '78d1a70fe4d0dfcb110be4bbb4dbaa712177655728f914ab4c0f6c4ae' +
+        'f79a46b3d996c82b2ebe9ed1748eb5cace7dc44fb67e73f452a047f2e' +
+        'd199b3d50d5db960acf03244dc8efa4fc129faf8b65f9e52e62b55447' +
+        '22bd17d2358e817a777618a4265a3db277fc04851a82a91fe6cdcb812' +
+        '7f156e0b4a5d1f54ce2742eb70c895f5f8b85f5febe69bc73e891f928' +
+        '0826860a0c2ef94c7935e6215c3c4cd6b0e43e80cca396d913d36be');
+
+      var md = MD5.create();
+      md.update(input);
+      ASSERT.equal(md.digest().toHex(), 'd15a2da0e92c3da55dc573f885b6e653');
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/md5',
+    'forge/util'
+  ], function(MD5, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      MD5(),
+      UTIL()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/md5')(),
+    require('../../js/util')());
+}
+
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/mgf1.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/mgf1.js
new file mode 100644
index 0000000..6c54ff1
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/mgf1.js
@@ -0,0 +1,39 @@
+(function() {
+
+function Tests(ASSERT, MGF, MD, UTIL) {
+  describe('mgf1', function() {
+    it('should digest the empty string', function() {
+      var seed = UTIL.hexToBytes('032e45326fa859a72ec235acff929b15d1372e30b207255f0611b8f785d764374152e0ac009e509e7ba30cd2f1778e113b64e135cf4e2292c75efe5288edfda4');
+      var expect = UTIL.hexToBytes('5f8de105b5e96b2e490ddecbd147dd1def7e3b8e0e6a26eb7b956ccb8b3bdc1ca975bc57c3989e8fbad31a224655d800c46954840ff32052cdf0d640562bdfadfa263cfccf3c52b29f2af4a1869959bc77f854cf15bd7a25192985a842dbff8e13efee5b7e7e55bbe4d389647c686a9a9ab3fb889b2d7767d3837eea4e0a2f04');
+      var mgf = MGF.mgf1.create(MD.sha1.create());
+      var result = mgf.generate(seed, expect.length);
+      ASSERT.equal(result, expect);
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/mgf',
+    'forge/md',
+    'forge/util'
+  ], function(MGF, MD, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      MGF(),
+      MD(),
+      UTIL()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/mgf')(),
+    require('../../js/md')(),
+    require('../../js/util')());
+}
+
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/pbkdf2.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/pbkdf2.js
new file mode 100644
index 0000000..0b53e27
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/pbkdf2.js
@@ -0,0 +1,123 @@
+(function() {
+
+function Tests(ASSERT, PBKDF2, MD, UTIL) {
+  describe('pbkdf2', function() {
+    it('should derive a password with hmac-sha-1 c=1', function() {
+      var dkHex = UTIL.bytesToHex(PBKDF2('password', 'salt', 1, 20));
+      ASSERT.equal(dkHex, '0c60c80f961f0e71f3a9b524af6012062fe037a6');
+    });
+
+    it('should derive a password with hmac-sha-1 c=2', function() {
+      var dkHex = UTIL.bytesToHex(PBKDF2('password', 'salt', 2, 20));
+      ASSERT.equal(dkHex, 'ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957');
+    });
+
+    it('should derive a password with hmac-sha-1 c=5 keylen=8', function() {
+      var salt = UTIL.hexToBytes('1234567878563412');
+      var dkHex = UTIL.bytesToHex(PBKDF2('password', salt, 5, 8));
+      ASSERT.equal(dkHex, 'd1daa78615f287e6');
+    });
+
+    it('should derive a password with hmac-sha-1 c=4096', function() {
+      // Note: might be too slow on old browsers
+      var dkHex = UTIL.bytesToHex(PBKDF2('password', 'salt', 4096, 20));
+      ASSERT.equal(dkHex, '4b007901b765489abead49d926f721d065a429c1');
+    });
+
+    /*
+    it('should derive a password with hmac-sha-1 c=16777216', function() {
+      // Note: too slow
+      var dkHex = UTIL.bytesToHex(PBKDF2('password', 'salt', 16777216, 20));
+      ASSERT.equal(dkHex, 'eefe3d61cd4da4e4e9945b3d6ba2158c2634e984');
+    });*/
+
+    it('should derive a password with hmac-sha-256 c=1000', function() {
+      // Note: might be too slow on old browsers
+      var salt = '4bcda0d1c689fe465c5b8a817f0ddf3d';
+      var md = MD.sha256.create();
+      var dkHex = UTIL.bytesToHex(PBKDF2('password', salt, 1000, 48, md));
+      ASSERT.equal(dkHex, '9da8a5f4ae605f35e82e5beac5f362df15c4255d88f738d641466a4107f9970238e768e72af29ac89a1b16ff277b31d2');
+    });
+
+    it('should asynchronously derive a password with hmac-sha-1 c=1', function(done) {
+      PBKDF2('password', 'salt', 1, 20, function(err, dk) {
+        var dkHex = UTIL.bytesToHex(dk);
+        ASSERT.equal(dkHex, '0c60c80f961f0e71f3a9b524af6012062fe037a6');
+        done();
+      });
+    });
+
+    it('should asynchronously derive a password with hmac-sha-1 c=2', function(done) {
+      PBKDF2('password', 'salt', 2, 20, function(err, dk) {
+        var dkHex = UTIL.bytesToHex(dk);
+        ASSERT.equal(dkHex, 'ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957');
+        done();
+      });
+    });
+
+    it('should asynchronously derive a password with hmac-sha-1 c=5 keylen=8', function(done) {
+      var salt = UTIL.hexToBytes('1234567878563412');
+      PBKDF2('password', salt, 5, 8, function(err, dk) {
+        var dkHex = UTIL.bytesToHex(dk);
+        ASSERT.equal(dkHex, 'd1daa78615f287e6');
+        done();
+      });
+    });
+
+    it('should asynchronously derive a password with hmac-sha-1 c=4096', function(done) {
+      // Note: might be too slow on old browsers
+      PBKDF2('password', 'salt', 4096, 20, function(err, dk) {
+        var dkHex = UTIL.bytesToHex(dk);
+        ASSERT.equal(dkHex, '4b007901b765489abead49d926f721d065a429c1');
+        done();
+      });
+    });
+
+    /*
+    it('should asynchronously derive a password with hmac-sha-1 c=16777216', function(done) {
+      // Note: too slow
+      PBKDF2('password', 'salt', 16777216, 20, function(err, dk) {
+        var dkHex = UTIL.bytesToHex(dk);
+        ASSERT.equal(dkHex, 'eefe3d61cd4da4e4e9945b3d6ba2158c2634e984');
+        done();
+      });
+    });*/
+
+    it('should asynchronously derive a password with hmac-sha-256 c=1000', function(done) {
+      // Note: might be too slow on old browsers
+      var salt = '4bcda0d1c689fe465c5b8a817f0ddf3d';
+      var md = MD.sha256.create();
+      PBKDF2('password', salt, 1000, 48, md, function(err, dk) {
+        var dkHex = UTIL.bytesToHex(dk);
+        ASSERT.equal(dkHex, '9da8a5f4ae605f35e82e5beac5f362df15c4255d88f738d641466a4107f9970238e768e72af29ac89a1b16ff277b31d2');
+        done();
+      });
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/pbkdf2',
+    'forge/md',
+    'forge/util'
+  ], function(PBKDF2, MD, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      PBKDF2(),
+      MD(),
+      UTIL()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/pbkdf2')(),
+    require('../../js/md')(),
+    require('../../js/util')());
+}
+
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/pem.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/pem.js
new file mode 100644
index 0000000..6b405cb
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/pem.js
@@ -0,0 +1,104 @@
+(function() {
+
+function Tests(ASSERT, PEM) {
+  var _input = '-----BEGIN PRIVACY-ENHANCED MESSAGE-----\r\n' +
+    'Proc-Type: 4,ENCRYPTED\r\n' +
+    'Content-Domain: RFC822\r\n' +
+    'DEK-Info: DES-CBC,F8143EDE5960C597\r\n' +
+    'Originator-ID-Symmetric: linn@zendia.enet.dec.com,,\r\n' +
+    'Recipient-ID-Symmetric: linn@zendia.enet.dec.com,ptf-kmc,3\r\n' +
+    'Key-Info: DES-ECB,RSA-MD2,9FD3AAD2F2691B9A,\r\n' +
+    ' B70665BB9BF7CBCDA60195DB94F727D3\r\n' +
+    'Recipient-ID-Symmetric: pem-dev@tis.com,ptf-kmc,4\r\n' +
+    'Key-Info: DES-ECB,RSA-MD2,161A3F75DC82EF26,\r\n' +
+    ' E2EF532C65CBCFF79F83A2658132DB47\r\n' +
+    '\r\n' +
+    'LLrHB0eJzyhP+/fSStdW8okeEnv47jxe7SJ/iN72ohNcUk2jHEUSoH1nvNSIWL9M\r\n' +
+    '8tEjmF/zxB+bATMtPjCUWbz8Lr9wloXIkjHUlBLpvXR0UrUzYbkNpk0agV2IzUpk\r\n' +
+    'J6UiRRGcDSvzrsoK+oNvqu6z7Xs5Xfz5rDqUcMlK1Z6720dcBWGGsDLpTpSCnpot\r\n' +
+    'dXd/H5LMDWnonNvPCwQUHg==\r\n' +
+    '-----END PRIVACY-ENHANCED MESSAGE-----\r\n' +
+    '-----BEGIN PRIVACY-ENHANCED MESSAGE-----\r\n' +
+    'Proc-Type: 4,ENCRYPTED\r\n' +
+    'Content-Domain: RFC822\r\n' +
+    'DEK-Info: DES-CBC,BFF968AA74691AC1\r\n' +
+    'Originator-Certificate:\r\n' +
+    ' MIIBlTCCAScCAWUwDQYJKoZIhvcNAQECBQAwUTELMAkGA1UEBhMCVVMxIDAeBgNV\r\n' +
+    ' BAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMQ8wDQYDVQQLEwZCZXRhIDExDzAN\r\n' +
+    ' BgNVBAsTBk5PVEFSWTAeFw05MTA5MDQxODM4MTdaFw05MzA5MDMxODM4MTZaMEUx\r\n' +
+    ' CzAJBgNVBAYTAlVTMSAwHgYDVQQKExdSU0EgRGF0YSBTZWN1cml0eSwgSW5jLjEU\r\n' +
+    ' MBIGA1UEAxMLVGVzdCBVc2VyIDEwWTAKBgRVCAEBAgICAANLADBIAkEAwHZHl7i+\r\n' +
+    ' yJcqDtjJCowzTdBJrdAiLAnSC+CnnjOJELyuQiBgkGrgIh3j8/x0fM+YrsyF1u3F\r\n' +
+    ' LZPVtzlndhYFJQIDAQABMA0GCSqGSIb3DQEBAgUAA1kACKr0PqphJYw1j+YPtcIq\r\n' +
+    ' iWlFPuN5jJ79Khfg7ASFxskYkEMjRNZV/HZDZQEhtVaU7Jxfzs2wfX5byMp2X3U/\r\n' +
+    ' 5XUXGx7qusDgHQGs7Jk9W8CW1fuSWUgN4w==\r\n' +
+    'Key-Info: RSA,\r\n' +
+    ' I3rRIGXUGWAF8js5wCzRTkdhO34PTHdRZY9Tuvm03M+NM7fx6qc5udixps2Lng0+\r\n' +
+    ' wGrtiUm/ovtKdinz6ZQ/aQ==\r\n' +
+    'Issuer-Certificate:\r\n' +
+    ' MIIB3DCCAUgCAQowDQYJKoZIhvcNAQECBQAwTzELMAkGA1UEBhMCVVMxIDAeBgNV\r\n' +
+    ' BAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMQ8wDQYDVQQLEwZCZXRhIDExDTAL\r\n' +
+    ' BgNVBAsTBFRMQ0EwHhcNOTEwOTAxMDgwMDAwWhcNOTIwOTAxMDc1OTU5WjBRMQsw\r\n' +
+    ' CQYDVQQGEwJVUzEgMB4GA1UEChMXUlNBIERhdGEgU2VjdXJpdHksIEluYy4xDzAN\r\n' +
+    ' BgNVBAsTBkJldGEgMTEPMA0GA1UECxMGTk9UQVJZMHAwCgYEVQgBAQICArwDYgAw\r\n' +
+    ' XwJYCsnp6lQCxYykNlODwutF/jMJ3kL+3PjYyHOwk+/9rLg6X65B/LD4bJHtO5XW\r\n' +
+    ' cqAz/7R7XhjYCm0PcqbdzoACZtIlETrKrcJiDYoP+DkZ8k1gCk7hQHpbIwIDAQAB\r\n' +
+    ' MA0GCSqGSIb3DQEBAgUAA38AAICPv4f9Gx/tY4+p+4DB7MV+tKZnvBoy8zgoMGOx\r\n' +
+    ' dD2jMZ/3HsyWKWgSF0eH/AJB3qr9zosG47pyMnTf3aSy2nBO7CMxpUWRBcXUpE+x\r\n' +
+    ' EREZd9++32ofGBIXaialnOgVUn0OzSYgugiQ077nJLDUj0hQehCizEs5wUJ35a5h\r\n' +
+    'MIC-Info: RSA-MD5,RSA,\r\n' +
+    ' UdFJR8u/TIGhfH65ieewe2lOW4tooa3vZCvVNGBZirf/7nrgzWDABz8w9NsXSexv\r\n' +
+    ' AjRFbHoNPzBuxwmOAFeA0HJszL4yBvhG\r\n' +
+    'Recipient-ID-Asymmetric:\r\n' +
+    ' MFExCzAJBgNVBAYTAlVTMSAwHgYDVQQKExdSU0EgRGF0YSBTZWN1cml0eSwgSW5j\r\n' +
+    ' LjEPMA0GA1UECxMGQmV0YSAxMQ8wDQYDVQQLEwZOT1RBUlk=,66\r\n' +
+    'Key-Info: RSA,\r\n' +
+    ' O6BS1ww9CTyHPtS3bMLD+L0hejdvX6Qv1HK2ds2sQPEaXhX8EhvVphHYTjwekdWv\r\n' +
+    ' 7x0Z3Jx2vTAhOYHMcqqCjA==\r\n' +
+    '\r\n' +
+    'qeWlj/YJ2Uf5ng9yznPbtD0mYloSwIuV9FRYx+gzY+8iXd/NQrXHfi6/MhPfPF3d\r\n' +
+    'jIqCJAxvld2xgqQimUzoS1a4r7kQQ5c/Iua4LqKeq3ciFzEv/MbZhA==\r\n' +
+    '-----END PRIVACY-ENHANCED MESSAGE-----\r\n' +
+    '-----BEGIN RSA PRIVATE KEY-----\r\n' +
+    'MIIBPAIBAAJBALjXU+IdHkSkdBscgXf+EBoa55ruAIsU50uDFjFBkp+rWFt5AOGF\r\n' +
+    '9xL1/HNIby5M64BCw021nJTZKEOmXKdmzYsCAwEAAQJBAApyYRNOgf9vLAC8Q7T8\r\n' +
+    'bvyKuLxQ50b1D319EywFgLv1Yn0s/F9F+Rew6c04Q0pIqmuOGUM7z94ul/y5OlNJ\r\n' +
+    '2cECIQDveEW1ib2+787l7Y0tMeDzf/HQl4MAWdcxXWOeUFK+7QIhAMWZsukutEn9\r\n' +
+    '9/yqFMt8bL/dclfNn1IAgUL4+dMJ7zdXAiEAhaxGhVKxN28XuCOFhe/s2R/XdQ/O\r\n' +
+    'UZjU1bqCzDGcLvUCIGYmxu71Tg7SVFkyM/3eHPozKOFrU2m5CRnuTHhlMl2RAiEA\r\n' +
+    '0vhM5TEmmNWz0anPVabqDj9TA0z5MsDJQcn5NmO9xnw=\r\n' +
+    '-----END RSA PRIVATE KEY-----\r\n';
+
+  describe('pem', function() {
+    it('should decode and re-encode PEM messages', function() {
+      var msgs = PEM.decode(_input);
+
+      var output = '';
+      for(var i = 0; i < msgs.length; ++i) {
+        output += PEM.encode(msgs[i]);
+      }
+
+      ASSERT.equal(output, _input);
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/pem'
+  ], function(PEM) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      PEM()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/pem')());
+}
+
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/pkcs1.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/pkcs1.js
new file mode 100644
index 0000000..889eb6d
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/pkcs1.js
@@ -0,0 +1,1105 @@
+(function() {
+
+function Tests(ASSERT, PKI, PKCS1, MD, JSBN, UTIL) {
+  var BigInteger = JSBN.BigInteger;
+
+  // RSA's test vectors for Forge's RSA-OAEP implementation:
+  // http://www.rsa.com/rsalabs/node.asp?id=2125
+  describe('pkcs1', function() {
+    it('should detect invalid RSAES-OAEP padding', function() {
+      var keys = makeKey();
+
+      // provide the seed to test the same input each time
+      var seed = UTIL.decode64('JRTfRpV1WmeyiOr0kFw27sZv0v0=');
+
+      // test decrypting corrupted data: flip every bit (skip first byte to
+      // avoid triggering other invalid encryption error) in the message this
+      // tests the padding error handling
+      var encoded = PKCS1.encode_rsa_oaep(
+        keys.publicKey, 'datadatadatadata', {seed: seed});
+      var encrypted = keys.publicKey.encrypt(encoded, null);
+      var bitLength = encrypted.length * 8;
+      // FIXME: test it too slow to run all the time -- temporary
+      // change only does partial checks, need a longer term fix
+      bitLength /= 8;
+      for(var bit = 8; bit < bitLength; ++bit) {
+        var byteIndex = bit / 8;
+        var bitInByte = bit % 8;
+
+        var out = encrypted.substring(0, byteIndex);
+        var mask = 0x1 << bitInByte;
+        out += String.fromCharCode(encrypted.charCodeAt(byteIndex) ^ mask);
+        out += encrypted.substring(byteIndex + 1);
+
+        try {
+          var decrypted = keys.privateKey.decrypt(out, null);
+          PKCS1.decode_rsa_oaep(keys.privateKey, decrypted);
+          throw {
+            message: 'Expected an exception.'
+          };
+        } catch(e) {
+          ASSERT.equal(e.message, 'Invalid RSAES-OAEP padding.');
+        }
+      }
+    });
+
+    it('should detect leading zero bytes', function() {
+      var keys = makeKey();
+      var message = UTIL.fillString('\x00', 80);
+      var encoded = PKCS1.encode_rsa_oaep(keys.publicKey, message);
+      var ciphertext = keys.publicKey.encrypt(encoded, null);
+      var decrypted = keys.privateKey.decrypt(ciphertext, null);
+      var decoded = PKCS1.decode_rsa_oaep(keys.privateKey, decrypted);
+      ASSERT.equal(message, decoded);
+    });
+
+    testOAEP();
+    testOAEPSHA256();
+
+    function testOAEP() {
+      var modulus, exponent, d, p, q, dP, dQ, qInv, pubkey, privateKey;
+      var examples;
+
+      // Example 1: A 1024-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'qLOyhK+OtQs4cDSoYPFGxJGfMYdjzWxVmMiuSBGh4KvEx+CwgtaTpef87Wdc9GaFEncsDLxkp0LGxjD1M8jMcvYq6DPEC/JYQumEu3i9v5fAEH1VvbZi9cTg+rmEXLUUjvc5LdOq/5OuHmtme7PUJHYW1PW6ENTP0ibeiNOfFvs=';
+      exponent = 'AQAB';
+      d = 'UzOc/befyEZqZVxzFqyoXFX9j23YmP2vEZUX709S6P2OJY35P+4YD6DkqylpPNg7FSpVPUrE0YEri5+lrw5/Vf5zBN9BVwkm8zEfFcTWWnMsSDEW7j09LQrzVJrZv3y/t4rYhPhNW+sEck3HNpsx3vN9DPU56c/N095lNynq1dE=';
+      p = '0yc35yZ//hNBstXA0VCoG1hvsxMr7S+NUmKGSpy58wrzi+RIWY1BOhcu+4AsIazxwRxSDC8mpHHcrSEurHyjnQ==';
+      q = 'zIhT0dVNpjD6wAT0cfKBx7iYLYIkpJDtvrM9Pj1cyTxHZXA9HdeRZC8fEWoN2FK+JBmyr3K/6aAw6GCwKItddw==';
+      dP = 'DhK/FxjpzvVZm6HDiC/oBGqQh07vzo8szCDk8nQfsKM6OEiuyckwX77L0tdoGZZ9RnGsxkMeQDeWjbN4eOaVwQ==';
+      dQ = 'lSl7D5Wi+mfQBwfWCd/U/AXIna/C721upVvsdx6jM3NNklHnkILs2oZu/vE8RZ4aYxOGt+NUyJn18RLKhdcVgw==';
+      qInv = 'T0VsUCSTvcDtKrdWo6btTWc1Kml9QhbpMhKxJ6Y9VBHOb6mNXb79cyY+NygUJ0OBgWbtfdY2h90qjKHS9PvY4Q==';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 1.1',
+        message: 'ZigZThIHPbA7qUzanvlTI5fVDbp5uYcASv7+NA==',
+        seed: 'GLd26iEGnWl3ajPpa61I4d2gpe8=',
+        encrypted: 'NU/me0oSbV01/jbHd3kaP3uhPe9ITi05CK/3IvrUaPshaW3pXQvpEcLTF0+K/MIBA197bY5pQC3lRRYYwhpTX6nXv8W43Z/CQ/jPkn2zEyLW6IHqqRqZYXDmV6BaJmQm2YyIAD+Ed8EicJSg2foejEAkMJzh7My1IQA11HrHLoo='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 1.2',
+        message: 'dQxAR/VH6OQUEYVlIymKybriRe+vE5f75W+d1Q==',
+        seed: 'DMdCzkqbfzL5UbyyUe/ZJf5P418=',
+        encrypted: 'ZA2xrMWOBWj+VAfl+bcB3/jDyR5xbFNvx/zsbLW3HBFlmI1KJ54Vd9cw/Hopky4/AMgVFSNtjY4xAXp6Cd9DUtkEzet5qlg63MMeppikwFKD2rqQib5UkfZ8Gk7kjcdLu+ZkOu+EZnm0yzlaNS1e0RWRLfaW/+BwKTKUbXFJK0Q='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 1.3',
+        message: '2Urggy5kRc5CMxywbVMagrHbS6rTD3RtyRbfJNTjwkUf/1mmQj6w4dAtT+ZGz2md/YGMbpewUQ==',
+        seed: 'JRTfRpV1WmeyiOr0kFw27sZv0v0=',
+        encrypted: 'Qjc27QNfYCavJ2w1wLN0GzZeX3bKCRtOjCni8L7+5gNZWqgyLWAtLmJeleuBsvHJck6CLsp224YYzwnFNDUDpDYINbWQO8Y344efsF4O8yaF1a7FBnzXzJb+SyZwturDBmsfz1aGtoWJqvt9YpsC2PhiXKODNiTUgA+wgbHPlOs='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 1.4',
+        message: 'UuZQ2Y5/KgSLT4aFIVO5fgHdMW80ahn2eoU=',
+        seed: 'xENaPhoYpotoIENikKN877hds/s=',
+        encrypted: 'RerUylUeZiyYAPGsqCg7BSXmq64wvktKunYvpA/T044iq+/Gl5T267vAXduxEhYkfS9BL9D7qHxuOs2IiBNkb9DkjnhSBPnD9z1tgjlWJyLd3Ydx/sSLg6Me5vWSxM/UvIgXTzsToRKq47n3uA4PxvclW6iA3H2AIeIq1qhfB1U='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 1.5',
+        message: 'jaif2eX5dKKf7/tGK0kYD2z56AI=',
+        seed: 'sxjELfO+D4P+qCP1p7R+1eQlo7U=',
+        encrypted: 'NvbjTZSo002qy6M6ITnQCthak0WoYFHnMHFiAFa5IOIZAFhVohOg8jiXzc1zG0UlfHd/6QggK+/dC1g4axJE6gz1OaBdXRAynaROEwMP12Dc1kTP7yCU0ZENP0M+HHxt0YvB8t9/ZD1mL7ndN+rZBZGQ9PpmyjnoacTrRJy9xDk='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 1.6',
+        message: 'JlIQUIRCcQ==',
+        seed: '5OwJgsIzbzpnf2o1YXTrDOiHq8I=',
+        encrypted: 'Qs7iYXsezqTbP0gpOG+9Ydr78DjhgNg3yWNm3yTAl7SrD6xr31kNghyfEGQuaBrQW414s3jA9Gzi+tY/dOCtPfBrB11+tfVjb41AO5BZynYbXGK7UqpFAC6nC6rOCN7SQ7nYy9YqaK3iZYMrVlZOQ6b6Qu0ZmgmXaXQt8VOeglU='
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha1', examples);
+
+      // Example 2: A 1025-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'AZR8f86QQl9HJ55whR8l1eYjFv6KHfGTcePmKOJgVD5JAe9ggfaMC4FBGQ0q6Nq6fRJQ7G22NulE7Dcih3x8HQpn8UsWlMXwN5RRpD5Joy3eg2cLc9qRocmbwjtDamAFXGEPC6+ZwaB5VluVo/FSZjLR1Npg8g7aJeZTxPACdm9F';
+      exponent = 'AQAB';
+      d = 'CCPyD6212okIip0AiT4h+kobEfvJPGSjvguq6pf7O5PD/3E3BMGcljwdEHqumQVHOfeeAuGG3ob4em3e/qbYzNHTyBpHv6clW+IGAaSksvCKFnteJ51xWxtFW91+qyRZQdl2i5rO+zzNpZUto87nJSW0UBZjqO4VyemS2SRi/jk=';
+      p = 'AVnb3gSjPvBvtgi4CxkPTT4ivME6yOSggQM6v6QW7bCzOKoItXMJ6lpSQOfcblQ3jGlBTDHZfdsfQG2zdpzEGkM=';
+      q = 'AStlLzBAOzi0CZX9b/QaGsyK2nA3Mja3IC05su4wz7RtsJUR9vMHzGHMIWBsGKdbimL4It8DG6DfDa/VUG9Wi9c=';
+      dP = 'Q271CN5zZRnC2kxYDZjILLdFKj+1763Ducd4mhvGWE95Wt270yQ5x0aGVS7LbCwwek069/U57sFXJIx7MfGiVQ==';
+      dQ = 'ASsVqJ89+ys5Bz5z8CvdDBp7N53UNfBc3eLv+eRilIt87GLukFDV4IFuB4WoVrSRCNy3XzaDh00cpjKaGQEwZv8=';
+      qInv = 'AnDbF9WRSwGNdhGLJDiac1Dsg2sAY6IXISNv2O222JtR5+64e2EbcTLLfqc1bCMVHB53UVB8eG2e4XlBcKjI6A==';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 2.1',
+        message: 'j/AMqmBccCgwY02abD1CxlK1jPHZL+xXC+7n',
+        seed: 'jEB7XsKJnlCZxT6M55O/lOcbF4I=',
+        encrypted: 'AYGviSK5/LTXnZLr4ZgVmS/AwUOdi81JE5ig9K06Mppb2ThVYNtTJoPIt9oE5LEq7Wqs30ccNMnNqJGt3MLfNFZlOqY4Lprlm1RFUlfrCZ1WK74QRT8rbRPFnALhDx+Ku12g0FcJMtrPLQkB23KdD+/MBU5wlo6lQMgbBLyu/nIO'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 2.2',
+        message: 'LQ==',
+        seed: 'tgDPPC5QbX8Wd4yRDTqLAD7uYdU=',
+        encrypted: 'AYdZ/x32OyeSQQViMUQWqK6vKsY0tG+UCrgtZNvxZe7jMBHadJ1Lq24vzRgSnJ5JJ32EUxErQpoiKoRxsHCZOZjnWIYcTT9tdJ2RxCkNMyx6SrP36jX/OgfUl8lV/w/8lQBrYsbSloENm/qwJBlseTQBLC35eO8pmrojmUDLoQJF'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 2.3',
+        message: 'dPyIxRvJD3evnV6aSnATPUtOCzTaPDfH744=',
+        seed: 'pzdoruqpH52MHtb50rY0Z/B8yuM=',
+        encrypted: 'AYgCurBMYDJegcSWIxHyvnwq3OkwQaAHGciPlXV18sefG3vIztEVxwazEcCKLZhso7apM2sUfCnG8ilAnd7GUb0f3VoLf2EMmTf9tKOnYjZLizIGtOpIX9CY0I9j1KqLsml9Ant1DDLX906vUYDS6bZrF8svpVUjvCgNoQ0UviBT'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 2.4',
+        message: 'p+sqUDaTHSfU6JEybZlpL/rdqb9+/T405iLErcCF9yHf6IUHLHiiA7FRc5vlQPqMFToQ8Ao=',
+        seed: 'mns7DnCL2W+BkOyrT7mys4BagVY=',
+        encrypted: 'AKRXjLwXYximOPun0B3xV0avRNT2zZbX58SVy/QlsJxknTK/iG2kj7r5iaIRcYfK+x+1gDF2kOPM1EaSC3r4KzHbWATYfQFRSsv6kVbngvhn9r7ZRJ4OmiwJvOzGqgh2NpZeNLPsdm8v4uQwGKL93rFAYWoOnYLlMxAk7gZS/HZB'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 2.5',
+        message: 'LvKwZvhUwz873LtZlKQ15z1sbA==',
+        seed: '6zzrvErcFrtI6IyK7A40r39Cf9M=',
+        encrypted: 'AOvF9f2nfP2tPINkGpAl531y2Kb7M6gQ9ZUPjXTHPo2THoY02GqxJGJWrge2AFtxt/L7mDUSGDMc5puP+9ydoIu8nHBPh23rnfn8LsBlyth/kJCweswXqn+ZeyespIgG6Jf3cdlRQf5FJtilMBtnhifvq3B/1A++vW55KiVhPnrs'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 2.6',
+        message: 'in+zRMi2yyzy7x9kP5oyGPbhm7qJwA==',
+        seed: 'TEXPTVfJjj1tIJWtxRxInrUN/4Q=',
+        encrypted: 'AQg57CDCe5BS5Vvvubd+b8JukHXXpUN4xkar31HkRb1XFd6BeJ9W8YA9kXB2Sp6Ty3h5hpQCPuc5POBLxdj4xaUsFx1Dg346ymL2CesKpf+wlg7wQZjddU9X9/vmq/dlzxGLTKRDsjtaqyZvlSMmrEWBEAZEMl+LchrNXQT/FO86'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha1', examples);
+
+      // Example 3: A 1026-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'ArWP7AOahgcApNe2Ri+T5s3UkRYd3XT06BC0DjwWUgBqXCd7J3TBEwWky6taeO+lfheobfej+jb8Sx0iSfIux8LdakYyMqzOqQbWbr6AtXBLEHKdpvgzI0q7Xv3UopLL+tM7TTP6ehS4w5e1bjrNISA0KLd836M6bacGs9iw/EPp';
+      exponent = 'AQAB';
+      d = 'FbSKW1aDqUZw4jtXGPgU+g4T+FA49QcRGCy6YVEFgfPSLH4jLvk34i5VHWi4bi+MsarYvi5Ij13379J54/Vo1Orzb4DPcUGs5g/MkRP7bEqEH9ULvHxRL/y+/yFIeqgR6zyoxiAFNGqG3oa/odipSP0/NIwi6q3zM8PObOEyCP0=';
+      p = 'Ab8B0hbXNZXPAnDCvreNQKDYRH0x2pGamD9+6ngbd9hf43Gz6Tc+e2khfTFQoC2JWN5/rZ1VUWCVi0RUEn4Ofq8=';
+      q = 'AY0zmWWBZts4KYFteylUFnWenJGYf1stiuzWOwS0i9ey/PIpu3+KbciLoT3S45rVW20aBhYHCPlwC+gLj9N0TOc=';
+      dP = 'BsCiSdIKby7nXIi0lNU/aq6ZqkJ8iMKLFjp2lEXl85DPQMJ0/W6mMppc58fOA6IVg5buKnhFeG4J4ohalyjk5Q==';
+      dQ = '0dJ8Kf7dkthsNI7dDMv6wU90bgUc4dGBHfNdYfLuHJfUvygEgC9kJxh7qOkKivRCQ7QHmwNEXmAuKfpRk+ZP6Q==';
+      qInv = 'jLL3Vr2JQbHTt3DlrTHuNzsorNpp/5tvQP5Xi58a+4WDb5Yn03rP9zwneeY0uyYBHCyPfzNhriqepl7WieNjmg==';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 3.1',
+        message: 'CHggtWno+o0=',
+        seed: 'jO1rGWKQgFeQ6QkHQBXmogsMSJQ=',
+        encrypted: 'AmoEhdlq69lrQ4IIUJm5Yuaivew9kMjbYl4UNy3oXi1be6q2XI+vkbtVBPtJWvzlyYiz9qUuIOHWy9NWbFzR8rgxi7VCzA6iXEqrmTKvogdg6t3seEOWoH6g7yTU5vTTflBSp6MeFGqkgKERu+kmQBMH4A9BADOEK22C/lzk366A'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 3.2',
+        message: 'RlOsrxcZYLAfUqe+Y6OrIdw2jsQ7UNguw3geBA==',
+        seed: 'tCkdZWdVCEjMFWlnyAm6q2ylB/A=',
+        encrypted: 'Ak24nHgCmJvgeDhHhjCElBvyCddhmH44+Xy19vG8iNpypQtz668RyHnE+V3ze4ULj2XXYi4lsbiJ6A/oC6yiBp1uDh2CmVP8RZBp3pjql5i0UeVX6Zq/j+PZzPkJbrvz5SVdO04cbS7K3wZ6NZ7qhkBazUfV4WVRfMr9R9bb7kv1'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 3.3',
+        message: '2UzQ4I+kBO2J',
+        seed: 'zoko9gWVWCVACLrdl5T63NL9H2U=',
+        encrypted: 'Ajm85oEDJEFSiHfW0ci7KKo7yX8d9YRWNhiZV5doOETKhmZHMvS+16CqsIOqq/tyOPWC4wlYwgJOROVwQ7l5UP1UPal3yQzd5TN9YYRC+Z5g13g6tZzm3Z1pxHrR6WK+wi0FiVz/jT9k7VJh2SsmeFEDk0hJkLo/fwaBiub/zoo6'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 3.4',
+        message: 'bMZBtrYeb5Y5dNrSOpATKE7x',
+        seed: 'bil59S1oFKV9g7CQBUiI8RmluaM=',
+        encrypted: 'AplMYq/Xb0mLof0s9kKFf8qB9Dc8sI8cuu5vAlw7UStCw+h3kRNHZkgDnb4Ek/kkYpL6wolQYA58DzLt+cgbnexFw73gzI2IR1kBaZB7fcWZHOspuwcU1hPZbfDxLsXY01B8jueueN2D8hb6Yd4QA2OspIp+kUrp9C3fvpQ7Cdmg'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 3.5',
+        message: '31FRgyth9PJYkftBcvMo0u3fg3H/z9vpl5OSlfMOymkYAXz9oRU796avh1kyIw==',
+        seed: 'LXYL/jjFneNM3IuMeKOOZihKLSc=',
+        encrypted: 'AWIEL/aWlZKmFnAxgRojmDTOY4q/VP7IuZR4Eir+LuZ/jFsYsDOYBb/bxaTmcgs3xZz7qUJGTFl/9TKhGYIVRf0uWbEU5h2vcYIFKfUCnPUklUMnw07F5vW6fvzE3pQ6uK1O14exRUMp9w23mKOo9NkvgnTispSK3mJ86O4z5Dxg'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 3.6',
+        message: 'PDutiTxUSm1SCrAiMZGIyNUEt6eIuFCQO4WXLqoYVS4RNKetYJiCYlT/erZys9jrMVj6xtTLrvE=',
+        seed: '8XR3nF/Tz+AHuty3o2ybVb/Pvw4=',
+        encrypted: 'ABEgUeddBklDvER4B15DSC/VnO4Ged5ok+7DqUPapJC5aRyT38BGS2YjufPb0+cAgyZPA0s3T3QWThoAdjcl5XR0S6C524NDTzHflvbiom9tjro0i9RobCI4rAfDeqw3hdHH7qL4Gf2RSReY7Y6c715Dt4Gw4CduN8Q/+UktAFcw'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha1', examples);
+
+      // Example 4: A 1027-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'BRJAtswABPpI0BNGccB4x8jew7Pi8lvCVkRnM52ziFPQa4XupbLeNTv/QqwuRryX+uaslhjalTelyPVTweNXYlmR1hCNzXiF+zolQT9T78rZSMs1zZua6cHGdibRE9V93kxb6na7W7felsANBzculoWm11z50jn6FI1wkxtfP7A5';
+      exponent = 'AQAB';
+      d = 'BBH/yjt8penpvn/jioUQXjU4ltsFxXlq7NKnJRYes2UchimpuGK5BNewx7N/jLWhwrVAAQGKAKHrLK/k7k6UksNIvCvtq0ueu/Bk6O/zIrkAn47sZTkF9A34ijzcSdRWf3VifUGspiQSm0agt8aY5eZfK3uhAsdJoQE1tlQNBAE=';
+      p = 'AnRYwZ7BY2kZ5zbJryXWCaUbj1YdGca/aUPdHuGriko/IyEAvUC4jezGuiNVSLbveSoRyd6CPQp5IscJW266VwE=';
+      q = 'AhDumzOrYXFuJ9JRvUZfSzWhojLi2gCQHClL8iNQzkkNCZ9kK1N1YS22O6HyA4ZJK/BNNLPCK865CdE0QbU7UTk=';
+      dP = 'OfoCi4JuiMESG3UKiyQvqaNcW2a9/R+mN9PMSKhKT0V6GU53J+Sfe8xuWlpBJlf8RwxzIuvDdBbvRYwweowJAQ==';
+      dQ = 'AV2ZqEGVlDl5+p4b4sPBtp9DL0b9A+R9W++7v9ax0Tcdg++zMKPgIJQrL+0RXl0CviT9kskBnRzs1t1M8eVMyJk=';
+      qInv = 'AfC3AVFws/XkIiO6MDAcQabYfLtw4wy308Z9JUc9sfbL8D4/kSbj6XloJ5qGWywrQmUkz8UqaD0x7TDrmEvkEro=';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 4.1',
+        message: 'SoZglTTuQ0psvKP36WLnbUVeMmTBn2Bfbl/2E3xlxW1/s0TNUryTN089FmyfDG+cUGutGTMJctI=',
+        seed: 'HKwZzpk971X5ggP2hSiWyVzMofM=',
+        encrypted: 'BMzhlhSEXglBUqP+GOVOMzDETl77xkrhaIbLGGkBTMV4Gx+PngRThNARKhNcoNEunIio5AY0Ft6q44RPYNbpb+FVFF9FJbmjRDHKN2YYD3DhWl5djosaUW/4cGCfE/iWk1ztGIJ5pY7RPQcRQnfXXGVoYH4KsJL9gDoiPkqO4LGo'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 4.2',
+        message: 'sK3E8/4R2lnOmSdz2QWZQ8AwRkl+6dn5oG3xFm20bZj1jSfsB0wC7ubL4kSci5/FCAxcP0QzCSUS7EaqeTdDyA==',
+        seed: '9UXViXWF49txqgy42nbFHQMq6WM=',
+        encrypted: 'AJe2mMYWVkWzA0hvv1oqRHnA7oWIm1QabwuFjWtll7E7hU60+DmvAzmagNeb2mV4yEH5DWRXFbKA03FDmS3RhsgLlJt3XK6XNw5OyXRDE2xtpITpcP/bEyOiCEeCHTsYOB3hO7SarqZlMMSkuCcfPq4XLNNm4H5mNvEBnSoortFe'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 4.3',
+        message: 'v21C5wFwex0CBrDItFoccmQf8SiJIZqCveqWW155qWsNAWPtnVeOya2iDy+88eo8QInYNBm6gbDGDzYG2pk=',
+        seed: 'rZl/7vcw1up75g0NxS5y6sv90nU=',
+        encrypted: 'AwH5NenEery0isu+CYldn1lxrxSDnaT/lUF+5FPR/XcxkHK7cpfhtV11Yc2dG7JMGpo3xhmGQwgkKASHnYbr0AHc5Rg5deFQaYm3DlqDQ0FU1cv9aiR4fmDrDGWNKsGTMC0RksbmItShKtS1OSO8okbfMcY5XjdwLGp4rggfudBl'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 4.4',
+        message: '+y7xEvXnZuuUAZKXk0eU974vb8HFjg==',
+        seed: 'E2RU31cw9zyAen5A2MGjEqxbndM=',
+        encrypted: 'AtEQrTCvtye+tpHdDPF9CvGh5/oMwEDsGkuiakLFnQp5ai4iyPNXzMmLZRms62gulF5iy3NGFKUpQHzUUr7j5E/s6EI8wZ5VVIuLmUuEnH7N5JM+dgN+HQzkQnWwhxDGjkMBMLkpcw7XfgmwFWQsVZPwTk/7lBB5gQKo6W/9/hHk'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 4.5',
+        message: 'KMzUR7uehRZtq7nlt9GtrcS5058gTpbV5EDOmtkovBwihA==',
+        seed: 'vKgFf4JLLqJX8oYUB+72PTMghoE=',
+        encrypted: 'ANu4p0OdkO/ZGaN3xU+uj+EexYw7hYNi4jrRuKRDEHmQZrmTR6pSVpHSrcWNmwbjTyiMFwOQxfDhHAqjZFlZ8Y7nno8r6NesXCPQYfGN10uMXypY/LXrDFT5nwGoMkdWgpJTZYM0CUjXqMl8Ss0emNHincMg6XomBTKoqnp1ih7C'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 4.6',
+        message: '8iJCdR7GsQ==',
+        seed: 'Ln4eF/ZHtd3QM+FUcvkPaBLzrE4=',
+        encrypted: 'AKX/pHaMi77K7i23fo8u7JlZWTNUVSCDXlun25ST0+F83e/mpfVnYkRxkI204tg6D77mBgj8hASVA7IjSgfcg7J7IoR62JIP9C9nTvebdigLACM9K1G4yycDqdQr+8glDJbsMsBR5X8bS6Uo24nDfkxU4n5uZKxpY1roh9lUFhmp'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha1', examples);
+
+      // Example 5: A 1028-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'Cq3z+cEl5diR8xrESOmT3v5YD4ArRfnX8iulAh6cR1drWh5oAxup205tq+TZah1vPSZyaM/0CABfEY78rbmYiNHCNEZxZrKiuEmgWoicBgrA2gxfrotV8wm6YucDdC+gMm8tELARAhSJ/0l3cBkNiV/Tn1IpPDnv1zppi9q58Q7Z';
+      exponent = 'AQAB';
+      d = 'AlbrTLpwZ/LSvlQNzf9FgqNrfTHRyQmbshS3mEhGaiaPgPWKSawEwONkiTSgIGwEU3wZsjZkOmCCcyFE33X6IXWI95RoK+iRaCdtxybFwMvbhNMbvybQpDr0lXF/fVKKz+40FWH2/zyuBcV4+EcNloL5wNBy+fYGi1bViA9oK+LF';
+      p = 'A7DTli9tF1Scv8oRKUNI3PDn45+MK8aCTyFktgbWh4YNrh5jI5PP7fUTIoIpBp4vYOSs1+YzpDYGP4I4X0iZNwc=';
+      q = 'AuTDLi9Rcmm3ByMJ8AwOMTZffOKLI2uCkS3yOavzlXLPDtYEsCmC5TVkxS1qBTl95cBSov3cFB73GJg2NGrrMx8=';
+      dP = 'AehLEZ0lFh+mewAlalvZtkXSsjLssFsBUYACmohiKtw/CbOurN5hYat83iLCrSbneX31TgcsvTsmc4ALPkM429U=';
+      dQ = '65CqGkATW0zqBxl87ciBm+Hny/8lR2YhFvRlpKn0h6sS87pP7xOCImWmUpfZi3ve2TcuP/6Bo4s+lgD+0FV1Tw==';
+      qInv = 'AS9/gTj5QEBi64WkKSRSCzj1u4hqAZb0i7jc6mD9kswCfxjngVijSlxdX4YKD2wEBxp9ATEsBlBi8etIt50cg8s=';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 5.1',
+        message: 'r3GpAeOmHTEy8Pwf20dPnqZXklf/wk0WQXAUWz296A==',
+        seed: 'RMkuKD93uUmcYD2WNmDIfS+TlGE=',
+        encrypted: 'A2BGpKR9ntO6mokTnBBQOOt0krBaXWi/1TrM/0WX96aGUbR7SkYn2Sfkhe7XtFZkIOi0CYeeXWBuriUdIqXfeZ95IL/BF7mSVypTsSYxRrzqAzhcxehTyaEByMPhvaMaUZgHSWxsteXvtAiCOjUrj6BmH7Zk763Vk965n/9e0ADl'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 5.2',
+        message: 'o7hEoII5qKxBYFrxemz9pNNQE2WFkDpBenkmh2BRmktKwzA+xz8Ph8+zI5k=',
+        seed: 'yyj1hgZZ/O7knD7q/OYlpwgDvTI=',
+        encrypted: 'A9brZU7c5hW8WfRVJl7U5aGCI8u5vk5AabRzgE1d6W9U3KqmA9BJxdlKoUcN/NIlQGa3x7Yf8fb2dw4yFcUTmf1ONOxQgrxI8ImECtBDVK5m3A8b0Y5GGjPMEli0Q6KDem3yZ1mqIwIzSYb4c4DJzJ1Tvp+ZYF0smpfaewkVpKet'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 5.3',
+        message: 'MIsOy9LHbLd/xvcMXt0jP9LyCSnWKfAmlTu2Ko9KOjFL3hld6FtfgW2iqrB00my2rN3zI647nGeKw88S+93n',
+        seed: 'IoX0DXcEgvmp76LHLLOsVXFtwMo=',
+        encrypted: 'B3CVIYFkn5+fB/9ib/OiLDXEYkQ9kF1Fap/Qv/Q8rCynqfVU6UeLmsw6yDiwIED/0+GEfeLkJTkp+d2e5ARDJamwXKu4CLLuhA004V0QWj8feydpWhoHotc/4I7KqjycnU1aif+JDVRyfXrkDA7BqN2GFl2O4sY2gUEBaki1W2ln'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 5.4',
+        message: 'FcW57hGF',
+        seed: 'SfpF06eN0Q39V3OZ0esAr37tVRM=',
+        encrypted: 'CBK3Z2jry2QtBAJY5fREGgGFIb2WaH5sXomfzWwXWI/1moLMiuA6S0WzEpmvF4jDKffc0oX4z0ztgmBrl2EmcaRb7coTNEIUTRYX0RT4AoV/D51zl1HFej+e5ACRLGHi5pkr4DGkPdSPproU7vfEIrXtxOevoE/dOPQC0ci7cZq/'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 5.5',
+        message: 'IQJuaADH+nKPyqug0ZauKNeirE/9irznlPCYX2DIpnNydzZdP+oR24kjogKa',
+        seed: '8Ch0EyNMxQNHJKCUxFhrh6/xM/w=',
+        encrypted: 'B7YOFOyVS/0p5g0AR+eJ9R1XGGxjWJkDMGeTztP2gkHHQ1KaumpjdPkuGeAWPvozaX4Zb3Zh36qkeqxr3l5R3rUHxyxYmiyhaT2WsUYDgSSbLNuerER2nySJxdPS+Z8O48fuW/ZKWsecQr1DPxSb6MtZVINhZAWVUTyXr3vCUJcj'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 5.6',
+        message: 'VB43totsiHK4TAI=',
+        seed: '2fukXJbyHm4m0p6yzctlhb6cs0E=',
+        encrypted: 'CMNtTdozQjsu1oMNhfZBG6Hc9HCh+uDr7+58CJ8lbO90y5bqacOPYPOavuRBKby0yS3n95diOyAHTj2cKJlwHtkHHh76C92E1MPlEwMC2PAkC6ukuEpxzAMvIjWl/w+uJ3w+j5ESvvRMmuINF1/JpAWL/JMLoxsC4uT0REg3EPJK'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha1', examples);
+
+      // Example 6: A 1029-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'ErF/ba0uzRn/RtwT94YPCeDgz7Z3s4pSWSMFzq8CLBZtuQ0ErCnjP33RLZ+vZuCBa7Y+rSZ8x9RsF8N74hS8oqItcjpk5EQHQ2tvyWVymu/CVU83bNXc6mgpN4CmK/OdAClIWhYLu55dwJctIaUE9S5e4CiqQWMy9RCy6c/19yKv';
+      exponent = 'AQAB';
+      d = 'ApXso1YGGDaVWc7NMDqpz9r8HZ8GlZ33X/75KaqJaWG80ZDcaZftp/WWPnJNB7TcEfMGXlrpfZaDURIoC5CEuxTyoh69ToidQbnEEy7BlW/KuLsv7QV1iEk2Uixf99MyYZBIJOfK3uTguzctJFfPeOK9EoYij/g/EHMc5jyQz/P5';
+      p = 'BKbOi3NY36ab3PdCYXAFr7U4X186WKJO90oiqMBct8w469TMnZqdeJpizQ9g8MuUHTQjyWku+k/jrf8pDEdJo4s=';
+      q = 'BATJqAM3H+20xb4588ALAJ5eCKY74eQANc2spQEcxwHPfuvLmfD/4Xz9Ckv3vv0t1TaslG23l/28Sr6PKTSbke0=';
+      dP = 'A5Ycj3YKor1RVMeq/XciWzus0BOa57WUjqMxH8zYb7lcda+nZyhLmy3lWVcvFdjQRMfrg6G+X63yzDd8DYR1KUs=';
+      dQ = 'AiGX4GZ0IZaqvAP6L+605wsVy3h9YXrNMbt1x7wjStcG98SNIYLR8P+cIo3PQZZ7bAum0sCtEQobhXgx7CReLLE=';
+      qInv = 'BAHEwMU9RdvbXp2W0P7PQnXfCXS8Sgc2tKdMMmkFPvtoas4kBuIsngWN20rlQGJ64v2wgmHo5+S8vJlNqvowXEU=';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 6.1',
+        message: 'QEbKi6ozR8on9J4NgfnMHXG+m6UX1A==',
+        seed: '3Q9s/kFeiOWkaaUfu6bf1ArbQ4Q=',
+        encrypted: 'BjDuvNKFbCT3mIBuQfnmc0Xtqc7aOGrMn6yuoe7tBqzlg3CXGNnRafrfQU1cdvkploM+8wW3Wx5LlfZiog+u3DuuDEgnqL+KiO29V+wgOieoQfAuQ6YVurGoysBwHeNN6972KgiAibVew26nUi/T7I0GtqBz5t+DMVO8Cu/ZO9Gj'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 6.2',
+        message: 'XMcsYCMd8Ds9QPm1eTG8MRCflyUn8osZ50gMcojLPJKyJRIhTkvmyRR5Ldq99X+qiqc=',
+        seed: 'jRS9lGoTURSPXK4u2aDGU+hevYU=',
+        encrypted: 'Drw3N2FzpP0vicxVwspismsR1Rw8fOSeiEX3TnYHMXxDa8jSO5Zn3+udCHI0tHvGg3F1rlwFWfa4HX0iQW0+UPSsUz2PCBLy2555H+nHdayLatD1Na2c6yOkoCAUxYqz+NMWFJmiYPOTSOcUriodNEMgj9i3Isz9+zk+mAEfmeY/'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 6.3',
+        message: 'sg5lEwMJL0vMtDBwwPhtIwSTYu2WZC/FYywn20pS49gx8qsGiyOxSYecAC9r8/7ul1kRElYs',
+        seed: 'bAdbxFUg8WXAv16kxd8ZG8nvDkQ=',
+        encrypted: 'Cpi/EJNhk5RDbPaNjzji8Vj96OpU80NfI5uNBrgyGEQgJHau7ZYAlJJIDOOo1wVJjEyMaPAVAdyB22CPYAhzUMjDsL0unvaoFFi3yAG4ny5P6Z1JALpqS15althl3Gdsd1WSh5QTDWKAqBYKGQ8t8+p8+aoCcdiOnmkF7PHFFS1l'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 6.4',
+        message: 'aE4wOMXAQfc=',
+        seed: 'O7w71mN9/hKEaQECm/WwwHEDQ5w=',
+        encrypted: 'AI56Z8rPtcTiS+x97hSRF/GVmM6MRYCP74jGCP+c1uaVJjuaPArUuLpMlSOOlqhCK4U1YpyNU4I3RHmtE/o5l0skL5p1nur5yDrVqMoYlAoBYrp1WHbfJj9L1QxlJcVgkCZ8Hw4JzgiZoM81nogSCr2b+JNEWzyud9Ngc1mumlL4'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 6.5',
+        message: 'MkiMsmLQQdbk3TX5h788ppbbHwasKaRGkw==',
+        seed: 'tGtBiT6L7zJvZ1k4OoMHHa5/yrw=',
+        encrypted: 'AAA0dEFse2i9+WHDhXN5RNfx9AyzlTQ8aTzAtP5jsx/t8erurJzMBnizHcMuCXdIlRTE8JCF9imKllPwGupARf9YLuiHviauV1tz7vfzd0kh43Wj0ZrdoMoxqhhJiHwfQsrJZ396L06SP25ahos4wITvGHWU3J9/BI/qLgKVU4Sr'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 6.6',
+        message: 'ULoUvoRicgJ5wwa6',
+        seed: 'CiQDMSpB49UvBg+8E6Z95c92Cac=',
+        encrypted: 'CgJt2l/IeF972b91Mntj6F4sD97l2ttl69ysmuHelcksZyq0M6p6jmnOam2Il/rErEpU3oQa5eW7znaHh515Y0zqejBoQGXHFNUkCbkoJWu/U+q81SMetyWVBFNzmb0pFktybTOkbacBNgpBaKCRzKty1Epi/tJGwP/qWxNIq1Rw'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha1', examples);
+
+      // Example 7: A 1030-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'MRF58Lz8m508oxXQDvMNe906LPrpkRv+3LlIs6R4LQcytqtEqkvwN0GmRNwBvsPmmwGgM+Z12KzXxJJcaxrsMRkFHf2Jdi0hXUVHX/y1n5CBSGI/NxdxVvauht16fF9D3B4fkIJUBYooSl8GwAIXk6h/GsX+/33K7mnF5Ro3ieNz';
+      exponent = 'AQAB';
+      d = 'Bwz8/y/rgnbidDLEXf7kj0m3kX1lMOHwyjRg8y4CdhdEh8VuIqRdJQDXd1SVIZ19Flqc872Swyr5qY2NycwpaACtyUoKVPtA80KRv4TujqErbxCTWcbTVCpQ+cdn9c//BaaBwuZW+3fKqttL6UaNirzU35j1jobSBT+hNJ90jiGx';
+      p = 'B0kmLBEc1HDsJWbms3MvwJMpRpqhkHHTucAZBlFMbx0muqFL6rCXHIt+YRpPeQCdb+p3aSjKJShbDeNkPRo/jHE=';
+      q = 'BrweUOlsAr9jbp7qi4mbvr92Ud533UdMPpvCO62BgrYZBMfZffvr+x4AEIh4tuZ+QVOR1nlCwrK/m0Q1+IsMsCM=';
+      dP = 'A7x+p/CqsUOrxs6LlxGGNqMBcuTP4CyPoN2jt7qvkPgJKYKYVSX0iL38tL1ybiJjmsZKMJKrf/y/HVM0z6ULW/E=';
+      dQ = 'AmKmqinCo8Z9xTRsBjga/Zh6o8yTz7/s9U/dn514fX9ZpSPTmJedoTei9jgf6UgB98lNohUY3DTLQIcMRpeZStk=';
+      qInv = 'ZJ1MF7buFyHnctA4mlWcPTzflVDUV8RrA3t0ZBsdUhZq+KITyDliBs37pEIvGNb2Hby10hTJcb9IKuuXanNwwg==';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 7.1',
+        message: 'R6rpCQ==',
+        seed: 'Q90JoH/0ysccqkYy7l4cHa7kzY8=',
+        encrypted: 'FojkzneUu6bLcBQWns1VnO3iowtWpSto2f4Yzxlz75eyoDFTlRx1X2KUqkmtvbVYRatodfs5hsk+z5J5YoQNKC+eVM6LaQ98DLi71zRA2VcdGxbNkmD56rR4PMSC5SI9xglzhxeD7Cewrg/UdzLLwoahc/ySsA+0umgkZHzZPIXB'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 7.2',
+        message: 'HZsuIiPZvBO/ufFiznNdtIunxo9oIqChp7auFlg05w==',
+        seed: 'Opw87HuE+b063svGc+yZ1UsivJs=',
+        encrypted: 'EFLtOXsuAeHQ7hxQvyQ2P5XlBPSgNDSgj9giV07WuXNu27XzkNsQMhR5qKE5NQ4r1Jd8N3jvMx8+eK4RiyaEUfIKLwHUcfXVPFZpNxcbLbwtS95FmleZ8DctZXQjmyMj0kXQu4HChrY8iaNhAXM35JAviKRn9MfyRL/Vq0ZDf/O2'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 7.3',
+        message: '2Xb8',
+        seed: 'dqdeW2FXpVbPiIS7LkXCk91UXPU=',
+        encrypted: 'IVXNhD/ySk7outt2lCYAKKSQgTuos2mky/EG7BSOUphwf1llvn0QHBBJ6oWEwkzWNFWtnBBNaGKC0/uAOkwRwcLpuRxxeIAdG2ZA8AP1co3wB7ikzMkrzgXkGicnjXyFAYxSQUMTpQd3iQAdTwGRC3Kq0F0iCqFKWHM6dIm8VFVr'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 7.4',
+        message: '1HOGI98iOqQ4Q9+EZ1NMQdAT4MgDxiTiY2ZrI5veQKXymuuN5549qmHdA3D0m9SwE4NLmCEq72scXuNzs8s=',
+        seed: 'eGYxSmrW8rJQo1lB2yj1hktYWFk=',
+        encrypted: 'CrFMNzrrfUMo0KqtjAlNiLnrCYuV8hBUopCCUivnwnoxKHi2N5F+PYGebDxWjbXYQ4ArBtUdnpiivgv0DAMUI7AO37/4Mg77kXG9IERlOky5xRIvbGXoPNouw8EmAnqcGla6h00P6iPzgLgs8kC4z1QABHWMTHfZNBV6dPP8Er+s'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 7.5',
+        message: 'u0cjHKXqHTrUbJk0XZqKYQ==',
+        seed: 'shZu1HLVjbEMqyxrAAzM8Qp9xQk=',
+        encrypted: 'AoOHoxgndDR5i02X9GAGjfUpj6ulBBuhF2Ghy3MWskGEEU7FACV+JYntO2B6HrvpemzC4CvxtoH0IxKjO3p32OeFXEpt4D48BGQ/eGuRomSg1oBeLOqR5oF363pk2SVeTyfnE7fM7ADcIA69IcLqK7iQ/q5JQt+UHcP5eJDtNHR4'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 7.6',
+        message: 'IYSCcJXTXD+G9gDo5ZdUATKW',
+        seed: 'Umc73iyhZsKqRhMawdyAjWfX07E=',
+        encrypted: 'FMZ4qUrWBSXvOelZsvO6XAl6lP+RK2fbrOgFNcGHq9R9B1QgsYchUrugj3/DHzE7v5JzyRL8TAFJqbDPt5gH40brMyBpYRvsD/m80Wjx98M+dzE86kVLlOJUnuzwAuKs9/by0oRdT+CqsuWpLd9oxICuESR5NdH2JXSEIhauZ0EV'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha1', examples);
+
+      // Example 8: A 1031-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'W98OMNMh3aUUf4gkCPppGVSA34+A0/bov1gYUE82QnypsfVUC5xlqPaXTPhEeiRNkoAgG7Sfy75jeNGUTNIn4jD5bj0Q+Bnc7ydsZKALKktnAefQHeX6veOx6aDfgvRjE1nNImaWR/uxcXJGE07XtJfP/73EK1nHOpbtkBZiEt/3';
+      exponent = 'AQAB';
+      d = 'D30enlqqJf0T5KBmOuFE4NFfXNGLzbCd8sx+ZOPF6RWtYmRTBBYdCYxxW7eri9AdB+rz/tfH7QivKopi70SrFrMg4Ur3Kkj5av4mKgrkz2XmNekQeQzU7lzqdopLJjn35vZ3s/C7a+MrdXR9iQkDbwJk9Y1AHNuhMXFhV6dez2Mx';
+      p = 'CgLvhEjZ+ti70NAEyMKql1HvlyHBsNAyNqVLDflHy67VolXuno4g1JHqFyP+CUcEqXYuiK/RbrtZlEEsqWbcT58=';
+      q = 'CS02Ln7ToL/Z6f0ObAMBtt8pFZz1DMg7mwz01u6nGmHgArRuCuny3mLSW110UtSYuByaxvxYWT1MP7T11y37sKk=';
+      dP = 'B8cUEK8QOWLbNnQE43roULqk6cKd2SFFgVKUpnx9HG3tJjqgMKm2M65QMD4UA10a8BQSPrpoeCAwjY68hbaVfX0=';
+      dQ = 'rix1OAwCwBatBYkbMwHeiB8orhFxGCtrLIO+p8UV7KnKKYx7HKtYF6WXBo/IUGDeTaigFjeKrkPH+We8w3kEuQ==';
+      qInv = 'BZjRBZ462k9jIHUsCdgF/30fGuDQF67u6c76DX3X/3deRLV4Mi9kBdYhHaGVGWZqqH/cTNjIj2tuPWfpYdy7o9A=';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 8.1',
+        message: 'BQt1Xl5ogPe56daSp0w3quRJsxv+pt7/g3R6iX9sLIJbsa2/hQo8lplLXeWzPLx9SheROnln',
+        seed: 'dwb/yh7PsevuKlXlxuJM0nl6QSU=',
+        encrypted: 'CbNoPYousPspW2LtH7kpC3FEV7eCUxn0ZHhyr4ibMECUcgIK0SkSvxmxHUgZ9JYUgk/9hNCcChfn0XMJ0SkZeQQQqimVaZ9qhtvjJCtazCOvRWkQgNaxroEPs+MFcIfwlwCSzgC+lWL/QFO2Jizgyqk+E3I9LjpboHXUXw1htUth'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 8.2',
+        message: 'TraNzZPKmxnfERvUNgj1VwJv5KodXPrCJ6PrWrlUjBigbd7SP4GCWYay/NcRCezvfv+Ihz8HXCqgxGn2nJK8',
+        seed: 'o3F9oUO03P+8dCZlqPqVBYVUg0M=',
+        encrypted: 'Ls8VyXxaFbFHaumGs3G1eiQoT0oWKo0MgYLnkF55IlbxgSul+D8fehMOQtzAIjKETtwUoxpo7peuVko4OjQRZWQkxfYt22Rgk8Nnvh/NpCbPAKBtist+V3dvu9hVrD31BvwWsdfD8hEPPYBo6R4YY2ODHIQJaA2NqezYzx+iDuOd'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 8.3',
+        message: 'hgSsVjKMGrWtkXhh',
+        seed: '7gYgkHPMoCa7Jk5Rhb+MaLdzn4Y=',
+        encrypted: 'S8iRMKWy2rt8L8+Q610Or55oG3FGo48xc6PZz+xS6p4KQZMuZIqdaTRMUNp2P1GgPJV2ITHoBSJU3NIkjLpA/TFmd4bOBaK3tTGsnaye1YSlm2d8GortjF0V1owFVp4r54C/fbY4/Sv9KoWrJ2hg83dzOPypif/XQ9E+4I4MqYk/'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 8.4',
+        message: '/dpfv27DYanZpKxoryFqBob0OLHg5cNrlV904QfznA3dzA==',
+        seed: 'mQrVc9xIqXMjW22CVDYY8ulVEF0=',
+        encrypted: 'LkVoR9j8Nv8BR9aZNZS5OXIn1Xd1LHnQ+QT8sDnU2BL+pgWntXTdgsp4b5N1I0hDjun1tUVJhdXw4WmePnrRdaMuFfA96wQquf4d2dsbuG+MCJzLRefvDF7nyptykMprFb7UcDl4ioqT/4Pg6NYkTHEAY2Le72m29Bb7PGhDg/vQ'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 8.5',
+        message: 'Sl9JFL7iXePGk0HeBw==',
+        seed: '7MY7KPB1byL1Ksjm7BJRpuwwRxg=',
+        encrypted: 'H7k1b9XEsXltsuv30NOTzIEK32FF3vwvznFPedk4ANXirCEeqLvsyktlS5TDsYsw3Vds403JVDbvV6CUFWRZIzWaXXtBce8iwkZw8bIp02A+kfdmcbffl+cxfJdzRHbV89F9Ic+Ctbqfg98uWI02mE/RtYRGi9I7LodfMvaJU/ey'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 8.6',
+        message: 'jgfWb3uICnJWOrzT81CSvDNAn7f4jyRyvg==',
+        seed: 'OSXHGzYtQKCm3kIUVXm6Hn3UWfw=',
+        encrypted: 'Ov2cZgAUeyF5jYGMZVoPTJIS2ybQsN/cKnWUzLPSL1vx18PhEs1z/H1QnHqLr908J00TmQCflgnsS+ZHfkU/B1qjPbOChwwcNAmu85LXOGrjppa5mpS02gWJRH6VXRbJixdgKlm9c2J5/Nj7KAxEYtWQv6m/E/7VcOr96XMwosIQ'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha1', examples);
+
+      // Example 9: A 1536-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'zyzUHjTKOnKOpcuK/2TDbSe971Nk4zb9aNMSPFoZaowocBPoU9UVbVjRUZVFIPtPbXsXq7aBd2WQnFdhGWWdkCsZBu2KKxDBVcJNEkUo2rnurjeb6sZuSkEXhty4/QBi68Aw3hIZoEwqjBt90xMeTWtsruLjGl7UGsFQmy7x7iqxg2S+VoypQcJezIT/nWQ7XsGqrhAqINc/R5t4D9bakQdSEtnqwDoGdNiZ66LkMfTES2Fba6IjK9SzO67XPWJd';
+      exponent = 'AQAB';
+      d = 'GYwUHiNxWpK8z2oRmlvBE4lGjSgR9UjXJ+F7SrDrmG1vIR77U7cffMvqh+5px17mFQCMUzLetSvzkKvfv+N9cgU2gVmyY4wd4ybiHSIlHw+1hIs78VAF0qdDMPCv6RbuYszBNE0dg6cJ5gZ2JzhA9/N3QkpeCk2nXwGzH/doGc+cv90hUkPDkXwD7zgZkxLlZ7O/eu06tFfzce+KFCP0W2jG4oLsERu6KDO5h/1p+tg7wbjGE8Xh6hbBHtEl6n7B';
+      p = '/I1sBL7E65qBksp5AMvlNuLotRnezzOyRZeYxpCd9PF2230jGQ/HK4hlpxiviV8bzZFFKYAnQjtgXnCkfPWDkKjD6I/IxI6LMuPaIQ374+iB6lZ0tqNIwh6T+eVepl79';
+      q = '0gDUXniKrOpgakAdBGD4fdXBAn4S3BoNdYbok52c94m0D1GsBEKWHefSHMIeBcgxVcHyqpGTOHz9+VbLSNFTuicEBvm7ulN9SYfZ4vmULXoUy//+p0/s3ako0j4ln17h';
+      dP = '2xaAL3mi8NRfNY1p/TPkS4H66ChiLpOlQlPpl9AbB0N1naDoErSqTmyL6rIyjVQxlVpBimf/JqjFyAel2jVOBe8xzIz3WPRjcylQsD4mVyb7lOOdalcqJiRKsI23V1Kt';
+      dQ = 'oKMXz+ffFCP4em3uhFH04rSmflSX8ptPHk6DC5+t2UARZwJvVZblo5yXgX4PXxbifhnsmQLgHX6m+5qjx2Cv7h44G2neasnAdYWgatnEugC/dcitL6iYpHnoCuKU/tKh';
+      qInv = 'CyHzNcNTNC60TDqiREV4DC1lW5QBdMrjjHyKTmSTwLqf0wN0gmewg7mnpsth5C2zYrjJiW23Bk4CrVrmFYfaFbRknJBZSQn+s328tlS+tyaOyAHlqLSqORG+vYhULwW+';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 9.1',
+        message: '9zX9VbqSWSw7Urj5xPaaqhy++P6IrdCVWVQSRn+c9OwLiWxZ7aFiEOdUnIq7EM28IaEuyba1uP0vEDmetg==',
+        seed: 'jsll8TSj7Jkx6SocoNyBadXqcFw=',
+        encrypted: 'JnvNEYrKsfyLqByF1zADy4YQ+lXB2X2o1Ip8fwaJak23UaooQlW502rWXzdlPYKfGzf5e4ABlCVFsvwsVac3bKehvksXYMjgWjPlqiUmuNmOMXCI54NMdVsqWbEmMaGCwF1dQ6sXeSZPhFb1Fc5X399RLVST2re3M43Et9eNucCRrDuvU3pp/H9UnZefDv+alP2kFpvU0dGaacmeM8O1VJDVAbObHtrhGP9nk6FTJhWE06Xzn25oLj0XyM0SYfpy'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 9.2',
+        message: 'gbkGYFAVpjqr5C3fEeGXiRL1QEx0dLJtzj7Ugr+WHsyBi/QgxUZZ',
+        seed: '7LG4sl+lDNqwjlYEKGf0r1gm0Ww=',
+        encrypted: 'k6yfBnHsKay7RE7/waV0E1HWD9sOOT+/dUrPDeSXYaFIQd93cum8gnc5ZqFYTE1yuuoAEY+D81zKblN8vU2BH1WDspeD2KbZTNMb5w1vUmwQ/wnG+nzgaXlaP80FEf1fy1ZLzIDqnHjzi4ABJTnYpN32/oHpzdt/UNu7vMfl2GCXzPTsSRifuL8xi+bVoHFdUWtJrxkSWM0y3IM85utGc8A6Gbus6IzFSJX2NswMHsiQltEc4jWiZcoXZCMqaJro'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 9.3',
+        message: '/TJkKd+biQ4JtUsYuPNPHiQ=',
+        seed: '6JuwMsbOYiy9tTvJRmAU6nf3d8A=',
+        encrypted: 'gevdlQVLDIIu+a12k/Woet+0tMTOcN8t+E7UnATaWLpfwgoZ4abot6OQCyJ5bcToae5rQnktFajs61bAnGmRToE86o9pMeS47W9CGvKY1ZXJf0eJx8qmEsfvNgmEwhuT7cVAEGi1r0x4qHcbmE1TuOqK3y9qfUoLp2x14d2fZY8g3tSkYHHUbXeRtWgD2P6n8LD45Brj8JODpvlYX+d1Pqr/0r+UVjEIvuzCB7u1NfX8xwXw3en3CMYvSanJA3HT'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 9.4',
+        message: '8UWbXwyS8BoPcjouVmJITY+MCiD8KdrWrNQ7tfPv/fThtj4H/f5mKNDXTKGb8taeSgq/htKTklp5Z3L4CI4=',
+        seed: 'YG87mcC5zNdx6qKeoOTIhPMYnMw=',
+        encrypted: 'vMNflM3mbLETZiXWJblEMqNbIvPS+hGmE/8PylvVf4e5AszcHNCuvLBxXuhp0dH+OV9nkwA/XspGUFnIhmDURv9fCBhVICJVfjjAimfq2ZEmIlTxBoKXXsVjl3aFN/SXevbV9qrOt/sl3sWTcjAjH9iXivSRGaKfKeQkq4JytHVieS1clPd0uIKdCw2fGoye3fN1dNX6JI7vqcUnH8XsJXnIG91htBD6Yf425CQiHBE63bJ1ZkyAHTTKjGNR5KhY'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 9.5',
+        message: 'U+boxynW+cMZ3TF+dLDbjkzMol88gwV0bhN6xjpj7zc557WVq7lujVXlT3vUGrQzN4/7kR0=',
+        seed: '/LxCFALp7KvGCCr6QLpfJlIshA4=',
+        encrypted: 'Iyr7ySf6CML2onuH1KXLCcB9wm+uc9c6kFWIOfT9ZtKBuH7HNLziN7oWZpjtgpEGp95pQs1s3OeP7Y0uTYFCjmZJDQNiZM75KvlB0+NQVf45geFNKcu5pPZ0cwY7rseaEXn1oXycGDLyg4/X1eWbuWWdVtzooBnt7xuzrMxpfMbMenePYKBkx/b11SnGIQJi4APeWD6B4xZ7iZcfuMDhXUT//vibU9jWTdeX0Vm1bSsI6lMH6hLCQb1Y1O4nih8u'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 9.6',
+        message: 'trKOohmNDBAIvGQ=',
+        seed: 'I6reDh4Iu5uaeNIwKlL5whsuG6I=',
+        encrypted: 'Q4zH3AimjaJJ5CUF+Fc7pg4sJ3PVspD0z53/cY6EIIHDg+ZwJKDylZTqmHudJeS3OPKFlw0ZWrs6jIBU49eda5yagye6WW8SWeJxJmdHZpB9jVgv86hHYVSSmtsebRI1ssy07I9mO6nMZwqSvr2FPI2/acZDbQFvYa3YNulHMkUENCB/n9TEPewqEqlY76Ae/iZpiZteYEwlXFX7cWbeVYnjaVl7sJFowG3V2xd+BqF0DrLVyC+uym2S/O6ZMbqf'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha1', examples);
+
+      // Example 10: A 2048-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'rkXtVgHOxrjMBfgDk1xnTdvg11xMCf15UfxrDK7DE6jfOZcMUYv/ul7Wjz8NfyKkAp1BPxrgfk6+nkF3ziPn9UBLVp5O4b3PPB+wPvETgC1PhV65tRNLWnyAha3K5vovoUF+w3Y74XGwxit2Dt4jwSrZK5gIhMZB9aj6wmva1KAzgaIv4bdUiFCUyCUG1AGaU1ooav6ycbubpZLeGNz2AMKu6uVuAvfPefwUzzvcfNhP67v5UMqQMEsiGaeqBjrvosPBmA5WDNZK/neVhbYQdle5V4V+/eYBCYirfeQX/IjY84TE5ucsP5Q+DDHAxKXMNvh52KOsnX1Zhg6q2muDuw==';
+      exponent = 'AQAB';
+      d = 'BWsEIW/l81SsdyUKS2sMhSWoXFmwvYDFZFCiLV9DjllqMzqodeKR3UP0jLiLnV/A1Jn5/NHDl/mvwHDNnjmMjRnmHbfHQQprJnXfv100W4BNIBrdUC1c4t/LCRzpmXu+vlcwbzg+TViBA/A29+hdGTTRUqMj5KjbRR1vSlsbDxAswVDgL+7iuI3qStTBusyyTYQHLRTh0kpncfdAjuMFZPuG1Dk6NLzwt4hQHRkzA/E6IoSwAfD2Ser3kyjUrFxDCrRBSSCpRg7Rt7xA7GU+h20Jq8UJrkW1JRkBFqDCYQGEgphQnBw786SD5ydAVOFelwdQNumJ9gkygHtSV3UeeQ==';
+      p = '7PWuzR5VFf/6y9daKBbG6/SQGM37RjjhhdZqc5a2+AkPgBjH/ZXMNLhX3BfwzGUWuxNGq01YLK2te0EDNSOHtwM40IQEfJ2VObZJYgSz3W6kQkmSB77AH5ZCh/9jNsOYRlgzaEb1bkaGGIHBAjPSF2vxWl6W3ceAvIaKp30852k=';
+      q = 'vEbEZPxqxMp4Ow6wijyEG3cvfpsvKLq9WIroheGgxh5IWKD7JawpmZDzW+hRZMJZuhF1zdcZJwcTUYSZK2wpt0bdDSyr4UKDX30UjMFhUktKCZRtSLgoRz8c52tstohsNFwD4F9B1RtcOpCj8kBzx9dKT+JdnPIcdZYPP8OGMYM=';
+      dP = 'xzVkVx0A+xXQij3plXpQkV1xJulELaz0K8guhi5Wc/9qAI7U0uN0YX34nxehYLQ7f9qctra3QhhgmBX31FyiY8FZqjLSctEn+vS8jKLXc3jorrGbCtfaPLPeCucxSYD2K21LCoddHfA8G645zNgz72zX4tlSi/CE0flp55Tp9sE=';
+      dQ = 'Jlizf235wQML4dtoEX+p2H456itpO35tOi9wlHQT7sYULhj7jfy2rFRdfIagrUj4RXFw8O+ya8SBJsU+/R0WkgGY3CoRB9woLbaoDNMGI2C6P6E/cOQxL/GmzWuPxM2cXD2xfG1qVyEvc64p9hkye61ZsVOFhYW6Tii2CmKkXkk=';
+      qInv = 'bzhSazklCFU07z5BWoNu3ouGFYosfL/sywvYNDBP7Gg7qNT0ecQz1DQW5jJpYjzqEAd22Fr/QB0//2EO5lQRzjsTY9Y6lwnu3kJkfOpWFJPVRXCoecGGgs2XcQuWIF7DERfXO182Ij+t1ui6kN18DuYdROFjJR4gx/ZuswURfLg=';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 10.1',
+        message: 'i7pr+CpsD4bV8XVul5VocLCJU7BrTrIFvBaU7g==',
+        seed: 'R+GrcRn+5WyV7l6q2G9A0KpjvTM=',
+        encrypted: 'U+pdwIzSYPs7hYVnKH+pFVLDCy/r+6IT8K6HcC0GjRm6sH/ldFI9+0ITnWjDxa/u4L/ky3lpy/OCuATW5hOWFE4tDmB0H4mTwwFLWLmxlXqLq80jr4VPTDVvsWYqpyv8x+WGVZ3EKA0WDBJnhacj6+6+/3HxFZRECq74fRB5Ood0ojnUoEyH/hRnudr4UgjsbHJVeUqWzCkUL5qL1Bjjwf1nNEsM0IKd87K+xgJTGWKTxrNNP3XTLyE91Fxic9UFrfTM7RBXy3WPwmru+kQSVe1OZMGZ7gdefxZkYYL9tGRzm2irXa/w5j6VUgFoJPBUv008jJCpe7a2VTKE60KfzA=='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 10.2',
+        message: '5q0YHwU7WKkE8kV1EDc+Vw==',
+        seed: 'bRf1tMH/rDUdGVv3sJ0J8JpAec8=',
+        encrypted: 'orGkMKnWV+L6HCu17UP/slwFowj+kJPAEDF5X1h0QAEQgorlj7m1gc6d3dPlSa4EoJhUWb3mxiZZTnsF3EJ4sqFGXBNoQIgjyF6W3GbDowmDxjlmT8RWmjf+IeWhlbV3bu0t+NjTYa9obnUCKbvWY/FhhopQYV4MM3vsDKNf7AuxnDbrLgu8wFgvodk6rNsGEGP1nyzh7kNgXl2J7KGD0qzf6fgQEQIq07Q6PdQX2slLThHqgbGSlm6WaxgggucZZGB7T4AC82KZhEoR8q4PrqwurnD49PmAiKzc0KxVbp/MxRFSGQj60m8ExkIBRQMFd4dYsFOL+LW7FEqCjmKXlQ=='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 10.3',
+        message: 'UQos9g6Gb6I0BVPJTqOfvCVjEeg+lEVLQSQ=',
+        seed: 'OFOHUU3szHx0DdjN+druSaHL/VQ=',
+        encrypted: 'mIbD5nZKi5qE6EFI69jDsaqAUDgaePZocUwW2c/Spu3FaXnFNdne47RLhcGL6JKJkjcXEUciFtld2pjS7oNHybFN/9/4SqSNJawG99fmU5islnsc6Qkl9n3OBJt/gS2wdCmXp01E/oHb4Oej/q8uXECviI1VDdu+O8IGV6KVQ/j8KRO5vRphsqsiVuxAm719wNF3F+olxD9C7Sffhzi/SvxnZv96/whZVV7ig5IPTIpjxKc0DLr93DOezbSwUVAC+WyTK1t5Fnr2mcCtP8z98PROhacCYr8uGP40uFBYmXXoZ/+WnUjqvyEicVRs3AWmnstSblKHDINvMHvXmHgO3g=='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 10.4',
+        message: 'vN0ZDaO30wDfmgbiLKrip18QyR/2Z7fBa96LUwZKJkmpQEXJ',
+        seed: 'XKymoPdkFhqWhPhdkrbg7zfKi2U=',
+        encrypted: 'Yxjp+1wNBeUwfhaDQ26QMpOsRkI1iqoiPXFjATq6h+Lf2o5gxoYOKaHpJoYWPqC5F18ynKOxMaHt06d3Wai5e61qT49DlvKM9vOcpYES5IFg1uID2qWFbzrKX/7Vd69JlAjj39Iz4+YE2+NKnEyQgt5lUnysYzHSncgOBQig+nEi5/Mp9sylz6NNTR2kF4BUV+AIvsVJ5Hj/nhKnY8R30Vu7ePW2m9V4MPwsTtaG15vHKpXYX4gTTGsK/laozPvIVYKLszm9F5Cc8dcN4zNa4HA5CT5gbWVTZd5lULhyzW3h1EDuAxthlF9imtijU7DUCTnpajxFDSqNXu6fZ4CTyA=='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 10.5',
+        message: 'p91sfcJLRvndXx6RraTDs9+UfodyMqk=',
+        seed: 'lbyp44WYlLPdhp+n7NW7xkAb8+Q=',
+        encrypted: 'dSkIcsz9SkUFZg1lH1babaoJyhMB2JBjL2qZLz1WXO5GSv3tQO07W+k1ZxTqWqdlX0oTZsLxfHKPbyxaXR+OKEKbxOb48s/42o3A4KmAjkX9CeovpAyyts5v//XA4VnRG2jZCoX3uE4QOwnmgmZkgMZXUFwJKSWUaKMUeG106rExVzzyNL9X232eZsxnSBkuAC3A3uqTBYXwgx/c2bwz1R957S/8Frz01ZgS/OvKo/kGmw5EVobWRMJcz2O0Vu5fpv/pbxnN91H+2erzWVd1Tb9L/qUhaqGETcUHyy0IDnIuuhUDCMK1/xGTYg8XZuz0SBuvuUO9KSh38hNspJSroA=='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 10.6',
+        message: '6vGnOhsMRglTfeac2SKLvPuajKjGw++vBW/kp/RjTtALfDnsaSLXuOosBOus',
+        seed: 'n0fd9C6X7qhWqb28cU6zrCL26zI=',
+        encrypted: 'LSB6c0Mqj7TAMFGz9zsophdkCY36NMR6IJlfgRWqaBZnm1V+gtvuWEkIxuaXgtfes029Za8GPVf8p2pf0GlJL9YGjZmE0gk1BWWmLlx38jA4wSyxDGY0cJtUfEb2tKcJvYXKEi10Rl75d2LCl2Pgbbx6nnOMeL/KAQLcXnnWW5c/KCQMqrLhYaeLV9JiRX7YGV1T48eunaAhiDxtt8JK/dIyLqyXKtPDVMX87x4UbDoCkPtnrfAHBm4AQo0s7BjOWPkyhpje/vSy617HaRj94cGYy7OLevxnYmqa7+xDIr/ZDSVjSByaIh94yCcsgtG2KrkU4cafavbvMMpSYNtKRg=='
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha1', examples);
+    }
+
+    function testOAEPSHA256() {
+      var modulus, exponent, d, p, q, dP, dQ, qInv, pubkey, privateKey;
+      var examples;
+
+      // Example 1: A 1024-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'qLOyhK+OtQs4cDSoYPFGxJGfMYdjzWxVmMiuSBGh4KvEx+CwgtaTpef87Wdc9GaFEncsDLxkp0LGxjD1M8jMcvYq6DPEC/JYQumEu3i9v5fAEH1VvbZi9cTg+rmEXLUUjvc5LdOq/5OuHmtme7PUJHYW1PW6ENTP0ibeiNOfFvs=';
+      exponent = 'AQAB';
+      d = 'UzOc/befyEZqZVxzFqyoXFX9j23YmP2vEZUX709S6P2OJY35P+4YD6DkqylpPNg7FSpVPUrE0YEri5+lrw5/Vf5zBN9BVwkm8zEfFcTWWnMsSDEW7j09LQrzVJrZv3y/t4rYhPhNW+sEck3HNpsx3vN9DPU56c/N095lNynq1dE=';
+      p = '0yc35yZ//hNBstXA0VCoG1hvsxMr7S+NUmKGSpy58wrzi+RIWY1BOhcu+4AsIazxwRxSDC8mpHHcrSEurHyjnQ==';
+      q = 'zIhT0dVNpjD6wAT0cfKBx7iYLYIkpJDtvrM9Pj1cyTxHZXA9HdeRZC8fEWoN2FK+JBmyr3K/6aAw6GCwKItddw==';
+      dP = 'DhK/FxjpzvVZm6HDiC/oBGqQh07vzo8szCDk8nQfsKM6OEiuyckwX77L0tdoGZZ9RnGsxkMeQDeWjbN4eOaVwQ==';
+      dQ = 'lSl7D5Wi+mfQBwfWCd/U/AXIna/C721upVvsdx6jM3NNklHnkILs2oZu/vE8RZ4aYxOGt+NUyJn18RLKhdcVgw==';
+      qInv = 'T0VsUCSTvcDtKrdWo6btTWc1Kml9QhbpMhKxJ6Y9VBHOb6mNXb79cyY+NygUJ0OBgWbtfdY2h90qjKHS9PvY4Q==';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 1.1',
+        message: 'ZigZThIHPbA7qUzanvlTI5fVDbp5uYcASv7+NA==',
+        seed: 'GLd26iEGnWl3ajPpa61I4d2gpe8Yt3bqIQadaXdqM+k=',
+        encrypted: 'W1QN+A1CKWotV6aZW7NYnUy7SmZd34SiX0jiPiLj9+8sZW6O/L7793+IFFSO3VKbPWhrjJPyR3ZmZ+yHDCzTDkRth+s5FN3nuFtlD3XQmmh0+x60PvAUiXJnAMcwxV96wHKjsUNPSnE1fsrCPBpIO5ZRaJ1pIF6R25IeuMwDujo='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 1.2',
+        message: 'dQxAR/VH6OQUEYVlIymKybriRe+vE5f75W+d1Q==',
+        seed: 'DMdCzkqbfzL5UbyyUe/ZJf5P418Mx0LOSpt/MvlRvLI=',
+        encrypted: 'jsKSyOW1BkucnZpnt9fS72P/lamWQqexXEDPVs8uzGlFj24Rj+cqGYVlt7i9nTmOGj2YrvM8swUTJQCYIF+QBiKbkcA7WBTBXfiUlkHvpWQD0bLwOkp1CmwfpF4sq2gTsCuSaGzZAc50ZAIOvpldizU7uOCwNNGOlERcFkvhfEE='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 1.3',
+        message: '2Urggy5kRc5CMxywbVMagrHbS6rTD3RtyRbfJNTjwkUf/1mmQj6w4dAtT+ZGz2md/YGMbpewUQ==',
+        seed: 'JRTfRpV1WmeyiOr0kFw27sZv0v0lFN9GlXVaZ7KI6vQ=',
+        encrypted: 'LcQ1BhOH4Vs0XX8/QJ6q/L0vSs9BUXfA20lQ6mwAt/gvUaUOvKJWBujoxt1QgpRnU6WuH7cSCFWXuKNnrhofpFF3CBTLIUbHZFoou0A4Roi4vFGFvYYu96Boy+oWivwB9/BKs1QMQeHADgNwUgqVD15+q27yHdfIH7kGp+DiGas='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 1.4',
+        message: 'UuZQ2Y5/KgSLT4aFIVO5fgHdMW80ahn2eoU=',
+        seed: 'xENaPhoYpotoIENikKN877hds/vEQ1o+Ghimi2ggQ2I=',
+        encrypted: 'ZMkqw9CM3SuY2zPBr8/9QbgXaVon4O4AKIufl3i7RVPD07fiTOnXF0aSWKUcdXNhE6ZcXc0Ha97/S5aw6mQKYfbmjaSq/H45s2nfZYTNIa74OgsV1DTDDLSF6/3J2UKhsG0LGIFaV9cNjfucDA5KbfQbzTq8u/+WN06J6nbInrI='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 1.5',
+        message: 'jaif2eX5dKKf7/tGK0kYD2z56AI=',
+        seed: 'sxjELfO+D4P+qCP1p7R+1eQlo7WzGMQt874Pg/6oI/U=',
+        encrypted: 'NzKEr8KhWRbX/VHniUE8ap0HzdDEWOyfl7dfNHXjL4h/320dmK633rGUvlA7sE4z9yuMj/xF++9ZeBzN6oSPLhVJV/aivUfcC8J99lwwp49W7phnvkUA4WUSmUeX+XRhwj8cR27mf5lu/6kKKbgasdt4BHqXcc5jOZICnld6vdE='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 1.6',
+        message: 'JlIQUIRCcQ==',
+        seed: '5OwJgsIzbzpnf2o1YXTrDOiHq8Lk7AmCwjNvOmd/ajU=',
+        encrypted: 'nfQEzsDY2gS9UYXF85t+u0Tm7HrOmmf+LqxCD+6N4XD36NoQ96PE9Squ83PvxKy8Bj8Q0N2L8E5Z5/9AWxLPCBqOkqkqIqO7ZDQMmpHml3H1yz82rpAzAQi6acZDSFQAW8NKhg4nEEwfwKdaGQcI0JZm6FrTQUuXskOqFUT0NJc='
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha256', examples);
+
+      // Example 2: A 1025-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'AZR8f86QQl9HJ55whR8l1eYjFv6KHfGTcePmKOJgVD5JAe9ggfaMC4FBGQ0q6Nq6fRJQ7G22NulE7Dcih3x8HQpn8UsWlMXwN5RRpD5Joy3eg2cLc9qRocmbwjtDamAFXGEPC6+ZwaB5VluVo/FSZjLR1Npg8g7aJeZTxPACdm9F';
+      exponent = 'AQAB';
+      d = 'CCPyD6212okIip0AiT4h+kobEfvJPGSjvguq6pf7O5PD/3E3BMGcljwdEHqumQVHOfeeAuGG3ob4em3e/qbYzNHTyBpHv6clW+IGAaSksvCKFnteJ51xWxtFW91+qyRZQdl2i5rO+zzNpZUto87nJSW0UBZjqO4VyemS2SRi/jk=';
+      p = 'AVnb3gSjPvBvtgi4CxkPTT4ivME6yOSggQM6v6QW7bCzOKoItXMJ6lpSQOfcblQ3jGlBTDHZfdsfQG2zdpzEGkM=';
+      q = 'AStlLzBAOzi0CZX9b/QaGsyK2nA3Mja3IC05su4wz7RtsJUR9vMHzGHMIWBsGKdbimL4It8DG6DfDa/VUG9Wi9c=';
+      dP = 'Q271CN5zZRnC2kxYDZjILLdFKj+1763Ducd4mhvGWE95Wt270yQ5x0aGVS7LbCwwek069/U57sFXJIx7MfGiVQ==';
+      dQ = 'ASsVqJ89+ys5Bz5z8CvdDBp7N53UNfBc3eLv+eRilIt87GLukFDV4IFuB4WoVrSRCNy3XzaDh00cpjKaGQEwZv8=';
+      qInv = 'AnDbF9WRSwGNdhGLJDiac1Dsg2sAY6IXISNv2O222JtR5+64e2EbcTLLfqc1bCMVHB53UVB8eG2e4XlBcKjI6A==';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 2.1',
+        message: 'j/AMqmBccCgwY02abD1CxlK1jPHZL+xXC+7n',
+        seed: 'jEB7XsKJnlCZxT6M55O/lOcbF4KMQHtewomeUJnFPow=',
+        encrypted: 'AR3o2JwhHLKUfOLZ26KXD9INUK1/fWJzdZix7E545qladDYdpHRaE5zBP9nf6IPmZvBUPq75n1E4suxm+Bom7crf9be1HXCFZnmR/wo92CKg4D1zRlBwr/3Gitr3h9rU6N+tid2x9yOYj955rf3Bq4j6wmjYQpWphbhBIBMoliyJ'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 2.2',
+        message: 'LQ==',
+        seed: 'tgDPPC5QbX8Wd4yRDTqLAD7uYdW2AM88LlBtfxZ3jJE=',
+        encrypted: 'AIeYuAD2aYZYnEu1YK+INur95FfP2pTz8/k4r3xwL4bVMufgvzWFLdVK24fP96jTteLkrX6HjmebBVeUhSWG3ahebh3LH5yVS9yx+xHzM1Jxc8X1rS+kYgdCGWFbszMF/vP0ogisy5XthHqcoHNEM4Rzln7ugrXuS+dNuuPEjIAf'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 2.3',
+        message: 'dPyIxRvJD3evnV6aSnATPUtOCzTaPDfH744=',
+        seed: 'pzdoruqpH52MHtb50rY0Z/B8yuOnN2iu6qkfnYwe1vk=',
+        encrypted: 'AMkW9IJHAFs0JbfwRZhrRITtj1bQVDLcjFCwYxHMDBlSHIqpDzSAL8aMxiUq41Feo9S2O/1ZTXIiK8baJpWs9y+BPqgi1lABB6JJIvU2QZYMzWK0XgjkWk12g6HSPFhuK4yf+LQ1UYpbKVquUdZ9POOCR8S7yS+tdful6qP8Wpkm'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 2.4',
+        message: 'p+sqUDaTHSfU6JEybZlpL/rdqb9+/T405iLErcCF9yHf6IUHLHiiA7FRc5vlQPqMFToQ8Ao=',
+        seed: 'mns7DnCL2W+BkOyrT7mys4BagVaaezsOcIvZb4GQ7Ks=',
+        encrypted: 'AJ6YQ3DNjd7YXZzjHASKxPmwFbHKwoEpof+P+Li3+o6Xa95C21XyWZF0iCXc5USp5jwLt66T6G3aYQkEpoyFGvSPA3NV6tOUabopdmslYCkOwuOIsFLiuzkJc4Hu6nWXeJtTVtHn7FmzQgzQOMjuty1YConfe78YuQvyE3IAKkr2'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 2.5',
+        message: 'LvKwZvhUwz873LtZlKQ15z1sbA==',
+        seed: '6zzrvErcFrtI6IyK7A40r39Cf9PrPOu8StwWu0jojIo=',
+        encrypted: 'AMv457W0EOt8RH+LAEoMQ7dKjZamzOdwTHJepDkaGGoQHi2z8coCiVemL5XYZ+ctjPBdw3y3nlMn1sif9i3WCzY26ram8PL5eVYk7Bm3XBjv9wuhw1RZmLFzKfJS+3vi+RTFhwjyyeaJrc07f5E7Cu7CVWNh3Oe3lvSF3TB2HUI8'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 2.6',
+        message: 'in+zRMi2yyzy7x9kP5oyGPbhm7qJwA==',
+        seed: 'TEXPTVfJjj1tIJWtxRxInrUN/4RMRc9NV8mOPW0gla0=',
+        encrypted: 'AJ5iMVr3Q6ZZlqLj/x8wWewQBcUMnRoaS2lrejzqRk12Bw120fXolT6pgo20OtM6/ZpZSN7vCpmPOYgCf93MOqKpN1pqumUH33+iP1a+tos5351SidwwNb2hLy3JfhkapvjB+c9JvbIolIgr+xeWhWPmMDam/Du/y+EsBOdZrbYc'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha256', examples);
+
+      // Example 3: A 1026-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'ArWP7AOahgcApNe2Ri+T5s3UkRYd3XT06BC0DjwWUgBqXCd7J3TBEwWky6taeO+lfheobfej+jb8Sx0iSfIux8LdakYyMqzOqQbWbr6AtXBLEHKdpvgzI0q7Xv3UopLL+tM7TTP6ehS4w5e1bjrNISA0KLd836M6bacGs9iw/EPp';
+      exponent = 'AQAB';
+      d = 'FbSKW1aDqUZw4jtXGPgU+g4T+FA49QcRGCy6YVEFgfPSLH4jLvk34i5VHWi4bi+MsarYvi5Ij13379J54/Vo1Orzb4DPcUGs5g/MkRP7bEqEH9ULvHxRL/y+/yFIeqgR6zyoxiAFNGqG3oa/odipSP0/NIwi6q3zM8PObOEyCP0=';
+      p = 'Ab8B0hbXNZXPAnDCvreNQKDYRH0x2pGamD9+6ngbd9hf43Gz6Tc+e2khfTFQoC2JWN5/rZ1VUWCVi0RUEn4Ofq8=';
+      q = 'AY0zmWWBZts4KYFteylUFnWenJGYf1stiuzWOwS0i9ey/PIpu3+KbciLoT3S45rVW20aBhYHCPlwC+gLj9N0TOc=';
+      dP = 'BsCiSdIKby7nXIi0lNU/aq6ZqkJ8iMKLFjp2lEXl85DPQMJ0/W6mMppc58fOA6IVg5buKnhFeG4J4ohalyjk5Q==';
+      dQ = '0dJ8Kf7dkthsNI7dDMv6wU90bgUc4dGBHfNdYfLuHJfUvygEgC9kJxh7qOkKivRCQ7QHmwNEXmAuKfpRk+ZP6Q==';
+      qInv = 'jLL3Vr2JQbHTt3DlrTHuNzsorNpp/5tvQP5Xi58a+4WDb5Yn03rP9zwneeY0uyYBHCyPfzNhriqepl7WieNjmg==';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 3.1',
+        message: 'CHggtWno+o0=',
+        seed: 'jO1rGWKQgFeQ6QkHQBXmogsMSJSM7WsZYpCAV5DpCQc=',
+        encrypted: 'AJqBCgTJGSHjv2OR0lObiDY2gZmWdutHfVeadCdFr2W4mS3ZHwet283wbtY/bsM8w0rVxNAPh3NZNrcRt56NhoT0NzD2IK3WNy39Im/CfbicvC6Vq2PyXUh1iza+90PUM3jECPP5NsOx658MzEnYyFZFb9izZIna6YLsXwkWoHVO'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 3.2',
+        message: 'RlOsrxcZYLAfUqe+Y6OrIdw2jsQ7UNguw3geBA==',
+        seed: 'tCkdZWdVCEjMFWlnyAm6q2ylB/C0KR1lZ1UISMwVaWc=',
+        encrypted: 'ARCj8j/hSsscyXtuINlyU0HuC+d7wZc7bSekF60BJFWKeKa1p28d4KsJXmdqI22sxha7PgkI9bgpfgdBd8KHp12g5y68uXiwRyPOvv8s6YDKmJFhbW13LHbE3iZHch2YG1eHi/20M/IrsAqCuk/W5Q/dP5eSVM1hLT9LBVsX3rIH'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 3.3',
+        message: '2UzQ4I+kBO2J',
+        seed: 'zoko9gWVWCVACLrdl5T63NL9H2XOiSj2BZVYJUAIut0=',
+        encrypted: 'Anfa/o/QML7UxLCHcSUWFPUWhcp955u97b5wLqXuLnWqoeQ3POhwasFh3/ow2lkzjjIdU47jkYJEk6A0dNgYiBuDg57/KN5yS2Px/QOSV+2nYEzPgSUHGyZacrHVkj/ZVyZ+ni7Iyf/QkNTfvPGxqmZtX6cq095jgdG1ELgYsTdr'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 3.4',
+        message: 'bMZBtrYeb5Y5dNrSOpATKE7x',
+        seed: 'bil59S1oFKV9g7CQBUiI8RmluaNuKXn1LWgUpX2DsJA=',
+        encrypted: 'AalUnNYX91mP0FrqphpfhU22832WgnjDNRU1pkpSrd5eD7t7Q1YhYE+pKds6glA8i1AE/li216hJs2IbCJMddyaXrDzT8V9/UfIUaSkLfcRYBrTn9DEDOTjY1Xnn38poLOFykpZbAz5hdbOh0qG39qFgl5QZG0+aTBd1tmlMZBfO'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 3.5',
+        message: '31FRgyth9PJYkftBcvMo0u3fg3H/z9vpl5OSlfMOymkYAXz9oRU796avh1kyIw==',
+        seed: 'LXYL/jjFneNM3IuMeKOOZihKLSctdgv+OMWd40zci4w=',
+        encrypted: 'AGgQQYTuy9dW6e3SwV5UFYbEtqQD7TDtxcrMYOmYlTPgTwIFpo4GbQbtgD9BMFAW7a1lIzLxKEld49jH6m95Xgtq/BAVFl/gXin5MMbiZfRTOl38miBTg5a6IS9w6tcrWIBeY5Z5n4iCuUqF9r/m9TqvxWF0aMP2VGVKZn+LHMVj'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 3.6',
+        message: 'PDutiTxUSm1SCrAiMZGIyNUEt6eIuFCQO4WXLqoYVS4RNKetYJiCYlT/erZys9jrMVj6xtTLrvE=',
+        seed: '8XR3nF/Tz+AHuty3o2ybVb/Pvw7xdHecX9PP4Ae63Lc=',
+        encrypted: 'Aps8BQrRkPPwpNIjHw3NBznsDvp1hIHmlbG5wRERr9+Ar4ervO2GA/MMUVNijdZEtFnCGjbLwpM6RKzCk96jJX1bIgzq7hnmIzwKmq2Ue4qqO29rQL39jpCS87BBo/YKMbkYsPc2yYSDMBMOe9VDG63pvDgFGrlk/3Yfz1km3+/Y'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha256', examples);
+
+      // Example 4: A 1027-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'BRJAtswABPpI0BNGccB4x8jew7Pi8lvCVkRnM52ziFPQa4XupbLeNTv/QqwuRryX+uaslhjalTelyPVTweNXYlmR1hCNzXiF+zolQT9T78rZSMs1zZua6cHGdibRE9V93kxb6na7W7felsANBzculoWm11z50jn6FI1wkxtfP7A5';
+      exponent = 'AQAB';
+      d = 'BBH/yjt8penpvn/jioUQXjU4ltsFxXlq7NKnJRYes2UchimpuGK5BNewx7N/jLWhwrVAAQGKAKHrLK/k7k6UksNIvCvtq0ueu/Bk6O/zIrkAn47sZTkF9A34ijzcSdRWf3VifUGspiQSm0agt8aY5eZfK3uhAsdJoQE1tlQNBAE=';
+      p = 'AnRYwZ7BY2kZ5zbJryXWCaUbj1YdGca/aUPdHuGriko/IyEAvUC4jezGuiNVSLbveSoRyd6CPQp5IscJW266VwE=';
+      q = 'AhDumzOrYXFuJ9JRvUZfSzWhojLi2gCQHClL8iNQzkkNCZ9kK1N1YS22O6HyA4ZJK/BNNLPCK865CdE0QbU7UTk=';
+      dP = 'OfoCi4JuiMESG3UKiyQvqaNcW2a9/R+mN9PMSKhKT0V6GU53J+Sfe8xuWlpBJlf8RwxzIuvDdBbvRYwweowJAQ==';
+      dQ = 'AV2ZqEGVlDl5+p4b4sPBtp9DL0b9A+R9W++7v9ax0Tcdg++zMKPgIJQrL+0RXl0CviT9kskBnRzs1t1M8eVMyJk=';
+      qInv = 'AfC3AVFws/XkIiO6MDAcQabYfLtw4wy308Z9JUc9sfbL8D4/kSbj6XloJ5qGWywrQmUkz8UqaD0x7TDrmEvkEro=';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 4.1',
+        message: 'SoZglTTuQ0psvKP36WLnbUVeMmTBn2Bfbl/2E3xlxW1/s0TNUryTN089FmyfDG+cUGutGTMJctI=',
+        seed: 'HKwZzpk971X5ggP2hSiWyVzMofMcrBnOmT3vVfmCA/Y=',
+        encrypted: 'AooWJVOiXRikAgxb8XW7nkDMKIcrCgZNTV0sY352+QatjTq4go6/DtieHvIgUgb/QYBYlOPOZkdiMWXtOFdapIMRFraGeq4mKhEVmSM8G5mpVgc62nVR0jX49AXeuw7kMGxnKTV4whJanPYYQRoOb0L4Mf+8uJ5QdqBE03Ohupsp'
+    /* FIXME: could not convert 4.2', to SHA-256, message too long
+      }, {
+        title: 'RSAES-OAEP Encryption Example 4.2',
+        message: 'sK3E8/4R2lnOmSdz2QWZQ8AwRkl+6dn5oG3xFm20bZj1jSfsB0wC7ubL4kSci5/FCAxcP0QzCSUS7EaqeTdDyA==',
+        seed: '9UXViXWF49txqgy42nbFHQMq6WM=',
+        encrypted: 'AJe2mMYWVkWzA0hvv1oqRHnA7oWIm1QabwuFjWtll7E7hU60+DmvAzmagNeb2mV4yEH5DWRXFbKA03FDmS3RhsgLlJt3XK6XNw5OyXRDE2xtpITpcP/bEyOiCEeCHTsYOB3hO7SarqZlMMSkuCcfPq4XLNNm4H5mNvEBnSoortFe'
+    */
+      }, {
+        title: 'RSAES-OAEP Encryption Example 4.3',
+        message: 'v21C5wFwex0CBrDItFoccmQf8SiJIZqCveqWW155qWsNAWPtnVeOya2iDy+88eo8QInYNBm6gbDGDzYG2pk=',
+        seed: 'rZl/7vcw1up75g0NxS5y6sv90nWtmX/u9zDW6nvmDQ0=',
+        encrypted: 'AtYYko32Vmzn3ZtrsDQH9Mw/cSQk9pePdwQZJ6my7gYXWYpBdhbEN/fH7LMmvjtHnKLLTDazfF1HT0tTG6E+TY002cy+fMUvdRn0rfmFkNeHeqVOABP2EmI4eXFCBbbIlpshLxbA3vDTzPPZZqwMN+KPG4O11wmS9DcyHYtpsIOU'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 4.4',
+        message: '+y7xEvXnZuuUAZKXk0eU974vb8HFjg==',
+        seed: 'E2RU31cw9zyAen5A2MGjEqxbndMTZFTfVzD3PIB6fkA=',
+        encrypted: 'AZX8z/njjTP/ApNNF+BNGUjlczSK/7iKULnZhiAKo4LJ0XaTzTtvL9jacr+OkRxSPUCpQeK36wXdi9qjsl3SO9D7APyzN1nNE5Nu5YstiAfEMVNpdRYGdgpUasEZ4jshBRGXYW28uTMcFWRtzrlol9Lc7IhIkldTXZsR9zg11KFn'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 4.5',
+        message: 'KMzUR7uehRZtq7nlt9GtrcS5058gTpbV5EDOmtkovBwihA==',
+        seed: 'vKgFf4JLLqJX8oYUB+72PTMghoG8qAV/gksuolfyhhQ=',
+        encrypted: 'A8GIo5X2qOS6MdKjYJg+h3hi2endxxeb3F5A8v+MbC7/8WbBJnzOvKLb6YMukOfAqutJiGGzdPQM9fopdhbRwS/Ovw4ksvmNBVM+Q26CFPqvdhV8P0WxmeYTxGFGrLgma+fwxpe7L6mj300Jq6Y/5kfTEJSXNdKuLRn0JsIg8LSD'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 4.6',
+        message: '8iJCdR7GsQ==',
+        seed: 'Ln4eF/ZHtd3QM+FUcvkPaBLzrE4ufh4X9ke13dAz4VQ=',
+        encrypted: 'AM9cnO14EVBadGQYTnkkbm/vYwqmnYvnAutrc4bZR3XC0DNhhuUlzFosUSmaC1LrjKcFfcqZOwzlev5uZycR7tUlLihC6lf4o0khAjUb03Dj+ubNsDKNCOA6vP63N3p6jSVIak7EPu0KtBLuNpIyW5mdkMuwssV7CNLOQ6qG7zxZ'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha256', examples);
+
+      // Example 5: A 1028-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'Cq3z+cEl5diR8xrESOmT3v5YD4ArRfnX8iulAh6cR1drWh5oAxup205tq+TZah1vPSZyaM/0CABfEY78rbmYiNHCNEZxZrKiuEmgWoicBgrA2gxfrotV8wm6YucDdC+gMm8tELARAhSJ/0l3cBkNiV/Tn1IpPDnv1zppi9q58Q7Z';
+      exponent = 'AQAB';
+      d = 'AlbrTLpwZ/LSvlQNzf9FgqNrfTHRyQmbshS3mEhGaiaPgPWKSawEwONkiTSgIGwEU3wZsjZkOmCCcyFE33X6IXWI95RoK+iRaCdtxybFwMvbhNMbvybQpDr0lXF/fVKKz+40FWH2/zyuBcV4+EcNloL5wNBy+fYGi1bViA9oK+LF';
+      p = 'A7DTli9tF1Scv8oRKUNI3PDn45+MK8aCTyFktgbWh4YNrh5jI5PP7fUTIoIpBp4vYOSs1+YzpDYGP4I4X0iZNwc=';
+      q = 'AuTDLi9Rcmm3ByMJ8AwOMTZffOKLI2uCkS3yOavzlXLPDtYEsCmC5TVkxS1qBTl95cBSov3cFB73GJg2NGrrMx8=';
+      dP = 'AehLEZ0lFh+mewAlalvZtkXSsjLssFsBUYACmohiKtw/CbOurN5hYat83iLCrSbneX31TgcsvTsmc4ALPkM429U=';
+      dQ = '65CqGkATW0zqBxl87ciBm+Hny/8lR2YhFvRlpKn0h6sS87pP7xOCImWmUpfZi3ve2TcuP/6Bo4s+lgD+0FV1Tw==';
+      qInv = 'AS9/gTj5QEBi64WkKSRSCzj1u4hqAZb0i7jc6mD9kswCfxjngVijSlxdX4YKD2wEBxp9ATEsBlBi8etIt50cg8s=';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 5.1',
+        message: 'r3GpAeOmHTEy8Pwf20dPnqZXklf/wk0WQXAUWz296A==',
+        seed: 'RMkuKD93uUmcYD2WNmDIfS+TlGFEyS4oP3e5SZxgPZY=',
+        encrypted: 'BOGyBDRo+2G7OC79yDEzJwwLMPuhIduDVaaBdb5svHj/ZAkVlyGVnH0j+ECliT42Nhvp4kZts+9cJ0W+ui7Q9KXbjmX033MpxrvSV1Ik//kHhX6xTn51UGpaOTiqofjM3QTTi9DVzRtAarWd/c8oAldrGok1vs+tJEDbcA5KvZz7'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 5.2',
+        message: 'o7hEoII5qKxBYFrxemz9pNNQE2WFkDpBenkmh2BRmktKwzA+xz8Ph8+zI5k=',
+        seed: 'yyj1hgZZ/O7knD7q/OYlpwgDvTLLKPWGBln87uScPuo=',
+        encrypted: 'AeleoSbRCOqBTpyWGLCrZ2G3mfjCYzMvupBy+q+sOKAfBzaLCjCehEUmIZFhe+CvtmVvyKGFqOwHUWgvks9vFU7Gfi580aZm7d4FtaGGVHBO6Q32/6IS7a+KSk7L6rPWwBTI+kyxb5SW12HTEowheKkFTda06tU0l4Ji45xP2tyh'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 5.3',
+        message: 'MIsOy9LHbLd/xvcMXt0jP9LyCSnWKfAmlTu2Ko9KOjFL3hld6FtfgW2iqrB00my2rN3zI647nGeKw88S+93n',
+        seed: 'IoX0DXcEgvmp76LHLLOsVXFtwMoihfQNdwSC+anvosc=',
+        encrypted: 'Ci3TBMV4P0o59ap6Wztb9LQHfJzYSOAaYaiXjk85Q9FYhAREaeS5YXhegKbbphMIS5i1SYJShmwpYu/t8SGHiX/72v6NnRgafDKzttROuF/HJoFkTBKH6C9NKke+mxoDy/YVZ9qYzFY6PwzB4pTDwku9s5Ha4DmRBlFdA/z713a4'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 5.4',
+        message: 'FcW57hGF',
+        seed: 'SfpF06eN0Q39V3OZ0esAr37tVRNJ+kXTp43RDf1Xc5k=',
+        encrypted: 'AcMQiclY0MMdT9K4kPqZ7JYHTaSolc8B3huHcQ4U5mG11/9XjzWjTLha8Liy0w909aaPbaB7+ZQTebg7x3F4yeWFRmnAJMaIFGBW/oA952mEaJ+FR2HO0xfRPzCRCaaU7cyOxy0gnR8d9FMunt9fhbffM9TvOfR6YDE5Duz6Jg0W'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 5.5',
+        message: 'IQJuaADH+nKPyqug0ZauKNeirE/9irznlPCYX2DIpnNydzZdP+oR24kjogKa',
+        seed: '8Ch0EyNMxQNHJKCUxFhrh6/xM/zwKHQTI0zFA0ckoJQ=',
+        encrypted: 'A/gvyZ/MNHUG3JGcisvAw/h1bhviZsqIsEM5+gBpLfj7d8iq28yZa6z5cnS4cJWywHxyQMgt9BAd37im/f5WcIcB+TZS9uegFSdgaetpYf4ft/1wMlcdc1ReCrTrCKPFHLLczeifyrnJSVvQD84kQY21b4fW9uLbSiGO0Ly94il1'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 5.6',
+        message: 'VB43totsiHK4TAI=',
+        seed: '2fukXJbyHm4m0p6yzctlhb6cs0HZ+6RclvIebibSnrI=',
+        encrypted: 'AWmTYpBHOqRBGy1h5mF88hMmBVNLN++kXAqQr4PKszqorigNQZbvwbOdWYNLsXydyvKi55ds8tTvXf4rRBswyuNmbtT0t2FVCTnTjNzA1cMSahInbdKfL/1wib3CjyQmC0TbbIa3kkAdXkiYytSafDxwNyan1OWtJcMLkJ6l8WRm'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha256', examples);
+
+      // Example 6: A 1029-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'ErF/ba0uzRn/RtwT94YPCeDgz7Z3s4pSWSMFzq8CLBZtuQ0ErCnjP33RLZ+vZuCBa7Y+rSZ8x9RsF8N74hS8oqItcjpk5EQHQ2tvyWVymu/CVU83bNXc6mgpN4CmK/OdAClIWhYLu55dwJctIaUE9S5e4CiqQWMy9RCy6c/19yKv';
+      exponent = 'AQAB';
+      d = 'ApXso1YGGDaVWc7NMDqpz9r8HZ8GlZ33X/75KaqJaWG80ZDcaZftp/WWPnJNB7TcEfMGXlrpfZaDURIoC5CEuxTyoh69ToidQbnEEy7BlW/KuLsv7QV1iEk2Uixf99MyYZBIJOfK3uTguzctJFfPeOK9EoYij/g/EHMc5jyQz/P5';
+      p = 'BKbOi3NY36ab3PdCYXAFr7U4X186WKJO90oiqMBct8w469TMnZqdeJpizQ9g8MuUHTQjyWku+k/jrf8pDEdJo4s=';
+      q = 'BATJqAM3H+20xb4588ALAJ5eCKY74eQANc2spQEcxwHPfuvLmfD/4Xz9Ckv3vv0t1TaslG23l/28Sr6PKTSbke0=';
+      dP = 'A5Ycj3YKor1RVMeq/XciWzus0BOa57WUjqMxH8zYb7lcda+nZyhLmy3lWVcvFdjQRMfrg6G+X63yzDd8DYR1KUs=';
+      dQ = 'AiGX4GZ0IZaqvAP6L+605wsVy3h9YXrNMbt1x7wjStcG98SNIYLR8P+cIo3PQZZ7bAum0sCtEQobhXgx7CReLLE=';
+      qInv = 'BAHEwMU9RdvbXp2W0P7PQnXfCXS8Sgc2tKdMMmkFPvtoas4kBuIsngWN20rlQGJ64v2wgmHo5+S8vJlNqvowXEU=';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 6.1',
+        message: 'QEbKi6ozR8on9J4NgfnMHXG+m6UX1A==',
+        seed: '3Q9s/kFeiOWkaaUfu6bf1ArbQ4TdD2z+QV6I5aRppR8=',
+        encrypted: 'C3d3hdR81ybq+Wuf6QUfy2KHVuQjrsvVH2HuE2LbJT2o2ZPdrDHIoGphdGj+GWNTrcV/d8iPLJlZ0CR3O2e2b7wLUVPMorv1HidYA8B8eJxkg5FIsPuK836LchnGqQlE7ObiWOjSuIw4lZ/ULCfOYejelr6PJXSxWgQUlV78sbvP'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 6.2',
+        message: 'XMcsYCMd8Ds9QPm1eTG8MRCflyUn8osZ50gMcojLPJKyJRIhTkvmyRR5Ldq99X+qiqc=',
+        seed: 'jRS9lGoTURSPXK4u2aDGU+hevYWNFL2UahNRFI9cri4=',
+        encrypted: 'DXAHBh/uWFjxl/kIwrzm0MXeHNH5MSmoPc0mjn00UcCUFmOwTQipPmLmephH+rNOOfCQVvwP5wysU3/w2hjmk/rl6Jb4qNc+KqDiij7fKSKhPGTvY3aiXZ2LflnJ3yv4LdT9KvvWsZrHEWfsEG+fQZW4c1OMEpOMC4N44nc88yRm'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 6.3',
+        message: 'sg5lEwMJL0vMtDBwwPhtIwSTYu2WZC/FYywn20pS49gx8qsGiyOxSYecAC9r8/7ul1kRElYs',
+        seed: 'bAdbxFUg8WXAv16kxd8ZG8nvDkRsB1vEVSDxZcC/XqQ=',
+        encrypted: 'AMe1ZPYbk4lABLKDLhwJMM4AfK46Jyilp/vQ9M921AamJzanoNGdlj6ZEFkbIO68hc/Wp4Qr43iWtjcasgpLw2NS0vroRi91VI5k9BZgXtgNG7Z9FBOtPjM61Um2PWSFpAyfaZS7zoJlfRKciEa+XUKa4VGly4fYSXXAbUJV2YHc'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 6.4',
+        message: 'aE4wOMXAQfc=',
+        seed: 'O7w71mN9/hKEaQECm/WwwHEDQ5w7vDvWY33+EoRpAQI=',
+        encrypted: 'AJS/vpVJKJuLwnnzENVQChT5MCBa0mLxw/a9nt+6Zj4FL8nucIl7scjXSOkwBDPcyCWr7gqdtjjZ9z6RCQv0HfjmVKI2M6AxI2MYuzwftIQldbhCRqo8AlyK3XKjfcK+Rzvii53W8Xw4Obbsv9OCLnCrrbK8aO3XKgrHPmDthH7x'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 6.5',
+        message: 'MkiMsmLQQdbk3TX5h788ppbbHwasKaRGkw==',
+        seed: 'tGtBiT6L7zJvZ1k4OoMHHa5/yry0a0GJPovvMm9nWTg=',
+        encrypted: 'CmNUKnNQco5hWHxCISdwN5M7LbL7YJ0u7bfH82LulE32VdATd3vcJmRiFtcczNNudMlHVhl6/ZsDVY1zymLrK2kLIYWeG9Iag3rQ5xhjLAdpMYBBuwjrJ8Oqc4+2qH57bBveynuE5xRpd9p+CkkiRP7x7g4B/iAwrmFxPtrxV/q/'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 6.6',
+        message: 'ULoUvoRicgJ5wwa6',
+        seed: 'CiQDMSpB49UvBg+8E6Z95c92CacKJAMxKkHj1S8GD7w=',
+        encrypted: 'DpQAu4uQ4zbkpP/f698+a5f3MhAXCi3QTcP7vXmQVlkH0CFlCnDESNG36Jk2ybe3VmzE2deBHBKI9a5cHUzM9Lsa/AoxnbD5qd2fJt9k19dSRtDWZUR/Bn/AdVHwstzsX/vRLe6qOk9Kf01OZcvKrmlWh2IBLs8/6sEJhBWXNAKj'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha256', examples);
+
+      // Example 7: A 1030-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'MRF58Lz8m508oxXQDvMNe906LPrpkRv+3LlIs6R4LQcytqtEqkvwN0GmRNwBvsPmmwGgM+Z12KzXxJJcaxrsMRkFHf2Jdi0hXUVHX/y1n5CBSGI/NxdxVvauht16fF9D3B4fkIJUBYooSl8GwAIXk6h/GsX+/33K7mnF5Ro3ieNz';
+      exponent = 'AQAB';
+      d = 'Bwz8/y/rgnbidDLEXf7kj0m3kX1lMOHwyjRg8y4CdhdEh8VuIqRdJQDXd1SVIZ19Flqc872Swyr5qY2NycwpaACtyUoKVPtA80KRv4TujqErbxCTWcbTVCpQ+cdn9c//BaaBwuZW+3fKqttL6UaNirzU35j1jobSBT+hNJ90jiGx';
+      p = 'B0kmLBEc1HDsJWbms3MvwJMpRpqhkHHTucAZBlFMbx0muqFL6rCXHIt+YRpPeQCdb+p3aSjKJShbDeNkPRo/jHE=';
+      q = 'BrweUOlsAr9jbp7qi4mbvr92Ud533UdMPpvCO62BgrYZBMfZffvr+x4AEIh4tuZ+QVOR1nlCwrK/m0Q1+IsMsCM=';
+      dP = 'A7x+p/CqsUOrxs6LlxGGNqMBcuTP4CyPoN2jt7qvkPgJKYKYVSX0iL38tL1ybiJjmsZKMJKrf/y/HVM0z6ULW/E=';
+      dQ = 'AmKmqinCo8Z9xTRsBjga/Zh6o8yTz7/s9U/dn514fX9ZpSPTmJedoTei9jgf6UgB98lNohUY3DTLQIcMRpeZStk=';
+      qInv = 'ZJ1MF7buFyHnctA4mlWcPTzflVDUV8RrA3t0ZBsdUhZq+KITyDliBs37pEIvGNb2Hby10hTJcb9IKuuXanNwwg==';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 7.1',
+        message: 'R6rpCQ==',
+        seed: 'Q90JoH/0ysccqkYy7l4cHa7kzY9D3Qmgf/TKxxyqRjI=',
+        encrypted: 'CdXefX8LEW8SqnT1ly7/dvScQdke1rrSIBF4NcFO/G+jg0u7yjsqLLfTa8voI44Ue3W6lVuj5SkVYaP9i7VPmCWA4nFfeleuy23gbHylm4gokcCmzcAm2RLfPQYPnxIb3hoQ2C3wXo/aWoLIMFPYzI19g5uY90XMEchAci3FVC/a'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 7.2',
+        message: 'HZsuIiPZvBO/ufFiznNdtIunxo9oIqChp7auFlg05w==',
+        seed: 'Opw87HuE+b063svGc+yZ1UsivJs6nDzse4T5vTrey8Y=',
+        encrypted: 'DDar5/aikhAVropPgT3SVzSMRtdS9sEmVBEqg9my/3na0Okz51EcAy436TOZVsM0exezvKYsVbDQhtOM0Mn9r6oyBsqzUR4lx6Gt2rYDYC4X1aMsJSVcQs9pDqeAWfIAmDIIQH/3IN2uJ6u4Xl2+gFCpp8RP0F//Rj2llnEsnRsl'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 7.3',
+        message: '2Xb8',
+        seed: 'dqdeW2FXpVbPiIS7LkXCk91UXPV2p15bYVelVs+IhLs=',
+        encrypted: 'GpTkYrRFNyD9Jw1Pc1TSPSfc9Yb8k4Fw1l4kCwqPodlAboKMJe+yuXoGgVeB7Jb7JTQklGpQc1keZUzUUVZO0Q4qYUelFFe5lWM2uhq21VCbvcchrMTP6Wjts05hVgJHklLKF5cOtBGpQC0FlkwCYoXUAk//wejNycM/ZXw+ozkB'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 7.4',
+        message: '1HOGI98iOqQ4Q9+EZ1NMQdAT4MgDxiTiY2ZrI5veQKXymuuN5549qmHdA3D0m9SwE4NLmCEq72scXuNzs8s=',
+        seed: 'eGYxSmrW8rJQo1lB2yj1hktYWFl4ZjFKatbyslCjWUE=',
+        encrypted: 'G5GJEPP4ifUCg+3OHEq41DFvaucXwgdSGyuDX6/yQ1+e30d0OIjIFv4JTUXv6Oi8/uADg+EN5Ug+lEyf0RNSS4wfKgRfAaXK6x1U8dh48g/bED27ZCZ+MjhAkUcjMO0h4m3nNfLxAju7nxO2cJzNI9n1TBCMngJBco0zzhOvMZaN'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 7.5',
+        message: 'u0cjHKXqHTrUbJk0XZqKYQ==',
+        seed: 'shZu1HLVjbEMqyxrAAzM8Qp9xQmyFm7UctWNsQyrLGs=',
+        encrypted: 'HebBc/6i18c2FbG7ibWuxyQgtiN1uhtxyNsXw1Kuz8zo7RkBkt5JZEwucKyXFSwI6drZlK6QaqCRZwPQsdc2wnZlQzbkilVf1TiACqzDdpKX5i+SbCTUsOyGETV3vtxFe7/SatEKseFSLEWkIfZxAFcisIs5hWmLJdqfWQeYuMrK'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 7.6',
+        message: 'IYSCcJXTXD+G9gDo5ZdUATKW',
+        seed: 'Umc73iyhZsKqRhMawdyAjWfX07FSZzveLKFmwqpGExo=',
+        encrypted: 'DX+W3vsdnJfe63BVUFYgCAG1VmTqG/DbQ4nZgWTUGHhuijUshLtz07dHar21GJ9Ory8QQPX67PgKGnBMp0fJBnqKO3boMOEcc52HEnQxOWIW2h2rgmDtnaYtvK85pddXzhbuXkXg4DDnoMy+4XzgbLfArK12deGB0wruBbQyv3Ar'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha256', examples);
+
+      // Example 8: A 1031-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'W98OMNMh3aUUf4gkCPppGVSA34+A0/bov1gYUE82QnypsfVUC5xlqPaXTPhEeiRNkoAgG7Sfy75jeNGUTNIn4jD5bj0Q+Bnc7ydsZKALKktnAefQHeX6veOx6aDfgvRjE1nNImaWR/uxcXJGE07XtJfP/73EK1nHOpbtkBZiEt/3';
+      exponent = 'AQAB';
+      d = 'D30enlqqJf0T5KBmOuFE4NFfXNGLzbCd8sx+ZOPF6RWtYmRTBBYdCYxxW7eri9AdB+rz/tfH7QivKopi70SrFrMg4Ur3Kkj5av4mKgrkz2XmNekQeQzU7lzqdopLJjn35vZ3s/C7a+MrdXR9iQkDbwJk9Y1AHNuhMXFhV6dez2Mx';
+      p = 'CgLvhEjZ+ti70NAEyMKql1HvlyHBsNAyNqVLDflHy67VolXuno4g1JHqFyP+CUcEqXYuiK/RbrtZlEEsqWbcT58=';
+      q = 'CS02Ln7ToL/Z6f0ObAMBtt8pFZz1DMg7mwz01u6nGmHgArRuCuny3mLSW110UtSYuByaxvxYWT1MP7T11y37sKk=';
+      dP = 'B8cUEK8QOWLbNnQE43roULqk6cKd2SFFgVKUpnx9HG3tJjqgMKm2M65QMD4UA10a8BQSPrpoeCAwjY68hbaVfX0=';
+      dQ = 'rix1OAwCwBatBYkbMwHeiB8orhFxGCtrLIO+p8UV7KnKKYx7HKtYF6WXBo/IUGDeTaigFjeKrkPH+We8w3kEuQ==';
+      qInv = 'BZjRBZ462k9jIHUsCdgF/30fGuDQF67u6c76DX3X/3deRLV4Mi9kBdYhHaGVGWZqqH/cTNjIj2tuPWfpYdy7o9A=';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 8.1',
+        message: 'BQt1Xl5ogPe56daSp0w3quRJsxv+pt7/g3R6iX9sLIJbsa2/hQo8lplLXeWzPLx9SheROnln',
+        seed: 'dwb/yh7PsevuKlXlxuJM0nl6QSV3Bv/KHs+x6+4qVeU=',
+        encrypted: 'DZZvGJ61GU6OOkaPl2t8iLNAB1VwLjl3RKd/tcu19Vz9j68fjCFBQvASq9FK6Sul/N6sXIVsi4ypx/1m77bErYJqiGwkE8sQz/g4ViwQmeCvpfbCoq00B5LxklezvhnM5OeSxFtO/8AtYimLrJ3sUmDYk7xkDI20/Lb8/pyOFsjH'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 8.2',
+        message: 'TraNzZPKmxnfERvUNgj1VwJv5KodXPrCJ6PrWrlUjBigbd7SP4GCWYay/NcRCezvfv+Ihz8HXCqgxGn2nJK8',
+        seed: 'o3F9oUO03P+8dCZlqPqVBYVUg0OjcX2hQ7Tc/7x0JmU=',
+        encrypted: 'DwsnkHG2jNLgSU4LEvbkOuSaQ9+br9t3dwen8KDGmLkKVJgWGu+TNxyyo2gsBCw7S4eqEFrl49ENEhMehdjrHCBLrEBrhbHxgncKrwIXmcjX1DOCrQXEfbT4keig8TaXkasow5qby9Ded6MWcLu1dZnXPfiuiXaOYajMGJ1D3/y7'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 8.3',
+        message: 'hgSsVjKMGrWtkXhh',
+        seed: '7gYgkHPMoCa7Jk5Rhb+MaLdzn4buBiCQc8ygJrsmTlE=',
+        encrypted: 'PAKF3K/lSKcZKWQDr56LmmVqSltcaKEfS7G6+rwG239qszt8eKG6fMYJsP4h7ZfXyV1zuIZXTVhXgiRQbA9os0AhkWiMJJouhsAn60R20BOLQOtQxlXxVOvUMPGuG5EP2O+nTI0VCXky5kHNJdfJolSW+pJLVdSu4mX9Ooga1CSx'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 8.4',
+        message: '/dpfv27DYanZpKxoryFqBob0OLHg5cNrlV904QfznA3dzA==',
+        seed: 'mQrVc9xIqXMjW22CVDYY8ulVEF2ZCtVz3EipcyNbbYI=',
+        encrypted: 'LcCLDDj2S5ZOKwpvobOk6rfBWMBbxs3eWR+Edk3lKaoEAFFD5sQv0AaIs3r7yI8sOir9HvS6GKf+jc9t31zIDCIJc3sKVyrNZfEeUFSvihjbPZDo6IaZ8Jau8woE2p1z7n9rG+cbMwKuILRPSEN4hE0QSA/qz0wcye6bjb6NbK20'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 8.5',
+        message: 'Sl9JFL7iXePGk0HeBw==',
+        seed: '7MY7KPB1byL1Ksjm7BJRpuwwRxjsxjso8HVvIvUqyOY=',
+        encrypted: 'U+PBTMw6peNHnFQ3pwix/KvVeWu1nSKQGcr9QPpYNgHhuFZK6ARR7XhKxFtDoUMP+iVTXprcow4lr1Uaw4PnEx+cPe0Khl15R8GLiuh5Vm9p3lTPW1u58iP2Oa81pQZTB5AuFiD0fnFZsl+BYfjDVah3cMIu83KOBRPOdLY0j8iq'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 8.6',
+        message: 'jgfWb3uICnJWOrzT81CSvDNAn7f4jyRyvg==',
+        seed: 'OSXHGzYtQKCm3kIUVXm6Hn3UWfw5JccbNi1AoKbeQhQ=',
+        encrypted: 'WK9hbyje7E0PLeXtWaJxqD4cFkdL5x4vawlKJSOO1OKyZ6uaY8vMYBhBO47xRIqtab5Ul5UGCwdvwPR69PpARsiiSfJHVavkihXixHGgZViGTMU/7J7ftSiNT9hAwrj4JL4f1+8RhTp6WKRzsXAEKpvLK0TrzQL3ioF3QtTsiu/a'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha256', examples);
+
+      // Example 9: A 1536-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'zyzUHjTKOnKOpcuK/2TDbSe971Nk4zb9aNMSPFoZaowocBPoU9UVbVjRUZVFIPtPbXsXq7aBd2WQnFdhGWWdkCsZBu2KKxDBVcJNEkUo2rnurjeb6sZuSkEXhty4/QBi68Aw3hIZoEwqjBt90xMeTWtsruLjGl7UGsFQmy7x7iqxg2S+VoypQcJezIT/nWQ7XsGqrhAqINc/R5t4D9bakQdSEtnqwDoGdNiZ66LkMfTES2Fba6IjK9SzO67XPWJd';
+      exponent = 'AQAB';
+      d = 'GYwUHiNxWpK8z2oRmlvBE4lGjSgR9UjXJ+F7SrDrmG1vIR77U7cffMvqh+5px17mFQCMUzLetSvzkKvfv+N9cgU2gVmyY4wd4ybiHSIlHw+1hIs78VAF0qdDMPCv6RbuYszBNE0dg6cJ5gZ2JzhA9/N3QkpeCk2nXwGzH/doGc+cv90hUkPDkXwD7zgZkxLlZ7O/eu06tFfzce+KFCP0W2jG4oLsERu6KDO5h/1p+tg7wbjGE8Xh6hbBHtEl6n7B';
+      p = '/I1sBL7E65qBksp5AMvlNuLotRnezzOyRZeYxpCd9PF2230jGQ/HK4hlpxiviV8bzZFFKYAnQjtgXnCkfPWDkKjD6I/IxI6LMuPaIQ374+iB6lZ0tqNIwh6T+eVepl79';
+      q = '0gDUXniKrOpgakAdBGD4fdXBAn4S3BoNdYbok52c94m0D1GsBEKWHefSHMIeBcgxVcHyqpGTOHz9+VbLSNFTuicEBvm7ulN9SYfZ4vmULXoUy//+p0/s3ako0j4ln17h';
+      dP = '2xaAL3mi8NRfNY1p/TPkS4H66ChiLpOlQlPpl9AbB0N1naDoErSqTmyL6rIyjVQxlVpBimf/JqjFyAel2jVOBe8xzIz3WPRjcylQsD4mVyb7lOOdalcqJiRKsI23V1Kt';
+      dQ = 'oKMXz+ffFCP4em3uhFH04rSmflSX8ptPHk6DC5+t2UARZwJvVZblo5yXgX4PXxbifhnsmQLgHX6m+5qjx2Cv7h44G2neasnAdYWgatnEugC/dcitL6iYpHnoCuKU/tKh';
+      qInv = 'CyHzNcNTNC60TDqiREV4DC1lW5QBdMrjjHyKTmSTwLqf0wN0gmewg7mnpsth5C2zYrjJiW23Bk4CrVrmFYfaFbRknJBZSQn+s328tlS+tyaOyAHlqLSqORG+vYhULwW+';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 9.1',
+        message: '9zX9VbqSWSw7Urj5xPaaqhy++P6IrdCVWVQSRn+c9OwLiWxZ7aFiEOdUnIq7EM28IaEuyba1uP0vEDmetg==',
+        seed: 'jsll8TSj7Jkx6SocoNyBadXqcFyOyWXxNKPsmTHpKhw=',
+        encrypted: 'kuBqApUSIPP0yfNvL0I57K2hReD8CcPhiYZFlPPmdM0cVFQHvdPMjQ2GcEekoBMk2+JR2H3IY6QF0JcANECuoepAuEvks/XolStfJNyUVUO3vLbWGlA1JOOSPiWElIdM0hmLN5In0DizqQit7R0mzH3Y1vUGPBhzOnNgNVQgrWVvqJugjG1SPY7LaZxIjhnz3O/8EkCpxLWcyWkFLX+ujnxIKxAqjmvEztiwLLlcWbJKILOy7KE1lctyh58wYP6e'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 9.2',
+        message: 'gbkGYFAVpjqr5C3fEeGXiRL1QEx0dLJtzj7Ugr+WHsyBi/QgxUZZ',
+        seed: '7LG4sl+lDNqwjlYEKGf0r1gm0WzssbiyX6UM2rCOVgQ=',
+        encrypted: 'iNEGT/cssEWuXX1C+SWyMK1hhUjRdNz9l8FeCBhftw8I5JUY1EDXPi2hUpESGxbJOjbyricua+QVvfP/UeoPfOxCcpRSoA3DlviB0ExCJTpCb2NSv9qXtw6Z7qEqk2YyQD7mAsGb2/Y3ug3KkKrF68MAxsWFV3tmL2NV2h+yfW6qz1dVAbAIUZeRuLIbaLdY9F7O4yPC68zkaX9NcTg0tOtnAJth9fMAOFX8sVbvKBgeOuVHV1A8HcAbmqkLMIyp'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 9.3',
+        message: '/TJkKd+biQ4JtUsYuPNPHiQ=',
+        seed: '6JuwMsbOYiy9tTvJRmAU6nf3d8Dom7Ayxs5iLL21O8k=',
+        encrypted: 'JiwfWprF58xVjVRR9B9r0mhomwU5IzkxXCZDgYJwYUcacmrz+KRLKMmtCMN7DLA2lOsfK+72mU+RLmhwfAAhBYmLGR8dLLstazb5xzU9wIM9u3jAl5iyyMLSo6wk/3SH0f7vC2bnFtMkhoHsd3VSTpzl5Q+SqX/4Q1JAMGWMMiHdyjCH+WaXNdTrboPEnPVtTcBGthkkYu8r/G0IkBR6OMPCZFgl/J4uiRTGCRbZx7UC02g6+qNMQY+ksygV6R8w'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 9.4',
+        message: '8UWbXwyS8BoPcjouVmJITY+MCiD8KdrWrNQ7tfPv/fThtj4H/f5mKNDXTKGb8taeSgq/htKTklp5Z3L4CI4=',
+        seed: 'YG87mcC5zNdx6qKeoOTIhPMYnMxgbzuZwLnM13Hqop4=',
+        encrypted: 'YXo+2y1QMWzjHkLtCW6DjdJ6fS5qdm+VHALYLFhG/dI1GmOwGOiOrFqesc5KPtWE73N5nJ680e6iYQYdFIsny6a4VH9mq/2Lr6qasMgM27znPzK8l6uQ1pTcDu1fJ4gCJABshzVEXzeTWx6OyKZsOFL8eXiNCwQpyfP9eH0tRjc+F75H3dnzX6AEVff4t0yKjDqp7aRMxFZGidcMJ6KetiNXUg1dhs/lHzItdQ7oMSUAgMnHYAvJDGqy5L4F8XXM'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 9.5',
+        message: 'U+boxynW+cMZ3TF+dLDbjkzMol88gwV0bhN6xjpj7zc557WVq7lujVXlT3vUGrQzN4/7kR0=',
+        seed: '/LxCFALp7KvGCCr6QLpfJlIshA78vEIUAunsq8YIKvo=',
+        encrypted: 'fwY+yhF2kyhotPKPlHEXcTOqVRG8Kg9bDJE/cSPUOoyVoiV0j57o9xpEYtZBuM5RanPUsTDcYNvorKqP5mbN81JV3SmEkIRTL7JoHGpJAMDHFjXBfpAwgUCPhfJ2+CUCIyOoPZqlt4w+K9l+WeFZYDatr0HC1NO+stbvWq358HRdX27TexTocG5OEB4l9gqhnUYD2JHNlGidsm0vzFQJoIMaH26x9Kgosg6tZQ0t3jdoeLbTCSxOMM9dDQjjK447'
+      }, {
+        title: 'RSAES-OAEP Encryption Example 9.6',
+        message: 'trKOohmNDBAIvGQ=',
+        seed: 'I6reDh4Iu5uaeNIwKlL5whsuG6Ijqt4OHgi7m5p40jA=',
+        encrypted: 'PISd/61VECapJ7gfG4J2OroSl69kvIZD2uuqmiro3E4pmXfpdOW/q+1WCr574Pjsj/xrIUdgmNMAl8QjciO/nArYi0IFco1tCRLNZqMDGjzZifHIcDNCsvnKg/VRmkPrjXbndebLqMtw7taeVztYq1HKVAoGsdIvLkuhmsK0Iaesp+/8xka40c9hWwcXHsG+I7pevwFarxQQbuUjXSkZ2ObWgzgGSiGCw9QNUGpO0usATLSd0AFkeE+IM/KAwJCy'
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha256', examples);
+
+      // Example 10: A 2048-bit RSA Key Pair
+      // Components of the RSA Key Pair
+      modulus = 'rkXtVgHOxrjMBfgDk1xnTdvg11xMCf15UfxrDK7DE6jfOZcMUYv/ul7Wjz8NfyKkAp1BPxrgfk6+nkF3ziPn9UBLVp5O4b3PPB+wPvETgC1PhV65tRNLWnyAha3K5vovoUF+w3Y74XGwxit2Dt4jwSrZK5gIhMZB9aj6wmva1KAzgaIv4bdUiFCUyCUG1AGaU1ooav6ycbubpZLeGNz2AMKu6uVuAvfPefwUzzvcfNhP67v5UMqQMEsiGaeqBjrvosPBmA5WDNZK/neVhbYQdle5V4V+/eYBCYirfeQX/IjY84TE5ucsP5Q+DDHAxKXMNvh52KOsnX1Zhg6q2muDuw==';
+      exponent = 'AQAB';
+      d = 'BWsEIW/l81SsdyUKS2sMhSWoXFmwvYDFZFCiLV9DjllqMzqodeKR3UP0jLiLnV/A1Jn5/NHDl/mvwHDNnjmMjRnmHbfHQQprJnXfv100W4BNIBrdUC1c4t/LCRzpmXu+vlcwbzg+TViBA/A29+hdGTTRUqMj5KjbRR1vSlsbDxAswVDgL+7iuI3qStTBusyyTYQHLRTh0kpncfdAjuMFZPuG1Dk6NLzwt4hQHRkzA/E6IoSwAfD2Ser3kyjUrFxDCrRBSSCpRg7Rt7xA7GU+h20Jq8UJrkW1JRkBFqDCYQGEgphQnBw786SD5ydAVOFelwdQNumJ9gkygHtSV3UeeQ==';
+      p = '7PWuzR5VFf/6y9daKBbG6/SQGM37RjjhhdZqc5a2+AkPgBjH/ZXMNLhX3BfwzGUWuxNGq01YLK2te0EDNSOHtwM40IQEfJ2VObZJYgSz3W6kQkmSB77AH5ZCh/9jNsOYRlgzaEb1bkaGGIHBAjPSF2vxWl6W3ceAvIaKp30852k=';
+      q = 'vEbEZPxqxMp4Ow6wijyEG3cvfpsvKLq9WIroheGgxh5IWKD7JawpmZDzW+hRZMJZuhF1zdcZJwcTUYSZK2wpt0bdDSyr4UKDX30UjMFhUktKCZRtSLgoRz8c52tstohsNFwD4F9B1RtcOpCj8kBzx9dKT+JdnPIcdZYPP8OGMYM=';
+      dP = 'xzVkVx0A+xXQij3plXpQkV1xJulELaz0K8guhi5Wc/9qAI7U0uN0YX34nxehYLQ7f9qctra3QhhgmBX31FyiY8FZqjLSctEn+vS8jKLXc3jorrGbCtfaPLPeCucxSYD2K21LCoddHfA8G645zNgz72zX4tlSi/CE0flp55Tp9sE=';
+      dQ = 'Jlizf235wQML4dtoEX+p2H456itpO35tOi9wlHQT7sYULhj7jfy2rFRdfIagrUj4RXFw8O+ya8SBJsU+/R0WkgGY3CoRB9woLbaoDNMGI2C6P6E/cOQxL/GmzWuPxM2cXD2xfG1qVyEvc64p9hkye61ZsVOFhYW6Tii2CmKkXkk=';
+      qInv = 'bzhSazklCFU07z5BWoNu3ouGFYosfL/sywvYNDBP7Gg7qNT0ecQz1DQW5jJpYjzqEAd22Fr/QB0//2EO5lQRzjsTY9Y6lwnu3kJkfOpWFJPVRXCoecGGgs2XcQuWIF7DERfXO182Ij+t1ui6kN18DuYdROFjJR4gx/ZuswURfLg=';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      examples = [{
+        title: 'RSAES-OAEP Encryption Example 10.1',
+        message: 'i7pr+CpsD4bV8XVul5VocLCJU7BrTrIFvBaU7g==',
+        seed: 'R+GrcRn+5WyV7l6q2G9A0KpjvTNH4atxGf7lbJXuXqo=',
+        encrypted: 'iXCnHvFRO1zd7U4HnjDMCLRvnKZj6OVRMZv8VZCAyxdA1T4AUORzxWzAtAAA541iVjEs1n5MIrDkymBDk3cM1oha9XCGsXeazZpW2z2+4aeaM3mv/oz3QYfEGiet415sHNnikAQ9ZmYg2uzBNUOS90h0qRAWFdUV5Tyxo1HZ0slg37Ikvyu2d6tgWRAAjgiAGK7IzlU4muAfQ4GiLpvElfm+0vch7lhlrk7t5TErhEF7RWQe16lVBva7azIxlGyyrqOhYrmQ+6JQpmPnsmEKYpSxTUP2tLzoSH5e+Y0CSaD7ZB20PWILB+7PKRueJ23hlMYmnAgQBePWSUdsljXAgA=='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 10.2',
+        message: '5q0YHwU7WKkE8kV1EDc+Vw==',
+        seed: 'bRf1tMH/rDUdGVv3sJ0J8JpAec9tF/W0wf+sNR0ZW/c=',
+        encrypted: 'I3uBbIiYuvEYFA5OtRycm8zxMuuEoZMNRsPspeKZIGhcnQkqH8XEM8iJMeL6ZKA0hJm3jj4z1Xz7ra3tqMyTiw3vGKocjsYdXchK+ar3Atj/jXkdJLeIiqfTBA+orCKoPbrBXLllt4dqkhc3lbq0Z5lTBeh6caklDnmJGIMnxkiG3vON/uVpIR6LMBg+IudMCMOv2f++RpBhhrI8iJOsPbnebdMIrxviVaVxT22GUNehadT8WrHI/qKv+p1rCpD3AAyXAhJy7KKp1l+nPCy1IY1prey+YgBxCAnlHuHv2V7q1FZTRXMJe3iKubLeiX6SfAKU1sivpoqk5ntMSMgAfw=='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 10.3',
+        message: 'UQos9g6Gb6I0BVPJTqOfvCVjEeg+lEVLQSQ=',
+        seed: 'OFOHUU3szHx0DdjN+druSaHL/VQ4U4dRTezMfHQN2M0=',
+        encrypted: 'n3scq/IYyBWbaN4Xd+mKJ0bZQR10yiSYzdjV1D1K3xiH11Tvhbj59PdRQXflSxE1QMhxN0jp9/tsErIlXqSnBH2XsTX6glPwJmdgXj7gZ1Aj+wsl15PctCcZv0I/4imvBBOSEd5TRmag3oU7gmbpKQCSHi6Hp2z5H/xEHekrRZemX7Dwl6A8tzVhCBpPweKNpe34OLMrHcdyb0k/uyabEHtzoZLpiOgHRjqi7SHr2ene9PPOswH7hc87xkiKtiFOpCeCScF6asFeiUTn5sf5tuPHVGqjKskwxcm/ToW3hm7ChnQdYcPRlnHrMeLBJt6o6xdrg6+SvsnNzctLxes0gA=='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 10.4',
+        message: 'vN0ZDaO30wDfmgbiLKrip18QyR/2Z7fBa96LUwZKJkmpQEXJ',
+        seed: 'XKymoPdkFhqWhPhdkrbg7zfKi2VcrKag92QWGpaE+F0=',
+        encrypted: 'KWbozLkoxbGfY0Dixr8GE/JD+MDAXIUFzm7K5AYscTvyAh9EDkLfDc/i8Y9Cjz/GXWsrRAlzO9PmLj4rECjbaNdkyzgYUiXSVV0SWmEF62nhZcScf+5QWHgsv6syu2VXdkz9nW4O3LWir2M/HqJ6kmpKVm5o7TqeYZ7GrY25FUnFDM8DpXOZqOImHVAoh8Tim9d2V9lk2D2Av6Tdsa4SIyBDj5VcX3OVoTbqdkKj5It9ANHjXaqGwqEyj7j1cQrRzrbGVbib3qzvoFvGWoo5yzr3D8J8z/UXJ4sBkumcjrphFTDe9qQcJ5FI82ZZsChJssRcZl4ApFosoljixk0WkA=='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 10.5',
+        message: 'p91sfcJLRvndXx6RraTDs9+UfodyMqk=',
+        seed: 'lbyp44WYlLPdhp+n7NW7xkAb8+SVvKnjhZiUs92Gn6c=',
+        encrypted: 'D7UPhV1nPwixcgg47HSlk/8yDLEDSXxoyo6H7MMopUTYwCmAjtnpWp4oWGg0sACoUlzKpR3PN21a4xru1txalcOkceylsQI9AIFvLhZyS20HbvQeExT9zQGyJaDhygC/6gPifgELk7x5QUqsd+TL/MQdgBZqbLO0skLOqNG3KrTMmN0oeWgxgjMmWnyBH0qkUpV5SlRN2P3nIHd/DZrkDn/qJG0MpXh6AeNHhvSgv8gyDG2Vzdf04OgvZLJTJaTdqHuXz93t7+PQ+QfKOG0wCEf5gOaYpkFarorv9XPLhzuOauN+Dd2IPzgKH5+wjbTZlZzEn+xRyDXK7s6GL/XOZw=='
+      }, {
+        title: 'RSAES-OAEP Encryption Example 10.6',
+        message: '6vGnOhsMRglTfeac2SKLvPuajKjGw++vBW/kp/RjTtALfDnsaSLXuOosBOus',
+        seed: 'n0fd9C6X7qhWqb28cU6zrCL26zKfR930LpfuqFapvbw=',
+        encrypted: 'FO6Mv81w3SW/oVGIgdfAbIOW1eK8/UFdwryWg3ek0URFK09jNQtAaxT+66Yn5EJrTWh8fgRn1spnAOUsY5eq7iGpRsPGE86MLNonOvrBIht4Z+IDum55EgmwCrlfyiGe2fX4Xv1ifCQMSHd3OJTujAosVI3vPJaSsbTW6FqOFkM5m9uPqrdd+yhQ942wN4m4d4TG/YPx5gf62fbCRHOfvA5qSpO0XGQ45u+sWBAtOfzxmaYtf7WRAlu+JvIjTp8I2lAfVEuuW9+TJattx9RXN8jaWOBsceLIOfE6bkgad50UX5PyEtapnJOG1j0bh5PZ//oKtIASarB3PwdWM1EQTQ=='
+      }];
+      checkOAEPEncryptExamples(pubkey, privateKey, 'sha256', examples);
+    }
+
+    function _bytesToBigInteger(bytes) {
+      var buffer = UTIL.createBuffer(bytes);
+      var hex = buffer.toHex();
+      return new BigInteger(hex, 16);
+    }
+
+    function _base64ToBn(s) {
+      var decoded = UTIL.decode64(s);
+      return _bytesToBigInteger(decoded);
+    }
+
+    function checkOAEPEncryptExamples(publicKey, privateKey, md, examples) {
+      if(md === 'sha1') {
+        md = MD.sha1.create();
+      } else if(md === 'sha256') {
+        md = MD.sha256.create();
+      }
+
+      for(var i = 0; i < examples.length; ++i) {
+        var ex = examples[i];
+        it('should test ' + ex.title, function() {
+          checkOAEPEncrypt(
+            publicKey, privateKey, md, ex.message, ex.seed, ex.encrypted);
+        });
+      }
+    }
+
+    function checkOAEPEncrypt(
+      publicKey, privateKey, md, message, seed, expected) {
+      var message = UTIL.decode64(message);
+      var seed = UTIL.decode64(seed);
+      var encoded = PKCS1.encode_rsa_oaep(
+        publicKey, message, {seed: seed, md: md});
+      var ciphertext = publicKey.encrypt(encoded, null);
+      ASSERT.equal(expected, UTIL.encode64(ciphertext));
+
+      var decrypted = privateKey.decrypt(ciphertext, null);
+      var decoded = PKCS1.decode_rsa_oaep(privateKey, decrypted, {md: md});
+      ASSERT.equal(message, decoded);
+
+      // test with higher-level API, default label, and generating a seed
+      ciphertext = publicKey.encrypt(message, 'RSA-OAEP', {md: md});
+      decoded = privateKey.decrypt(ciphertext, 'RSA-OAEP', {md: md});
+      ASSERT.equal(message, decoded);
+    }
+
+    function decodeBase64PublicKey(modulus, exponent) {
+      modulus = _base64ToBn(modulus);
+      exponent = _base64ToBn(exponent);
+      return PKI.setRsaPublicKey(modulus, exponent);
+    }
+
+    function decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv) {
+      modulus = _base64ToBn(modulus);
+      exponent = _base64ToBn(exponent);
+      d = _base64ToBn(d);
+      p = _base64ToBn(p);
+      q = _base64ToBn(q);
+      dP = _base64ToBn(dP);
+      dQ = _base64ToBn(dQ);
+      qInv = _base64ToBn(qInv);
+      return PKI.setRsaPrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+    }
+
+    function makeKey() {
+      var modulus, exponent, d, p, q, dP, dQ, qInv, pubkey, privateKey;
+
+      // Example 1: A 1024-bit RSA Key Pair
+      modulus = 'qLOyhK+OtQs4cDSoYPFGxJGfMYdjzWxVmMiuSBGh4KvEx+CwgtaTpef87Wdc9GaFEncsDLxkp0LGxjD1M8jMcvYq6DPEC/JYQumEu3i9v5fAEH1VvbZi9cTg+rmEXLUUjvc5LdOq/5OuHmtme7PUJHYW1PW6ENTP0ibeiNOfFvs=';
+      exponent = 'AQAB';
+      d = 'UzOc/befyEZqZVxzFqyoXFX9j23YmP2vEZUX709S6P2OJY35P+4YD6DkqylpPNg7FSpVPUrE0YEri5+lrw5/Vf5zBN9BVwkm8zEfFcTWWnMsSDEW7j09LQrzVJrZv3y/t4rYhPhNW+sEck3HNpsx3vN9DPU56c/N095lNynq1dE=';
+      p = '0yc35yZ//hNBstXA0VCoG1hvsxMr7S+NUmKGSpy58wrzi+RIWY1BOhcu+4AsIazxwRxSDC8mpHHcrSEurHyjnQ==';
+      q = 'zIhT0dVNpjD6wAT0cfKBx7iYLYIkpJDtvrM9Pj1cyTxHZXA9HdeRZC8fEWoN2FK+JBmyr3K/6aAw6GCwKItddw==';
+      dP = 'DhK/FxjpzvVZm6HDiC/oBGqQh07vzo8szCDk8nQfsKM6OEiuyckwX77L0tdoGZZ9RnGsxkMeQDeWjbN4eOaVwQ==';
+      dQ = 'lSl7D5Wi+mfQBwfWCd/U/AXIna/C721upVvsdx6jM3NNklHnkILs2oZu/vE8RZ4aYxOGt+NUyJn18RLKhdcVgw==';
+      qInv = 'T0VsUCSTvcDtKrdWo6btTWc1Kml9QhbpMhKxJ6Y9VBHOb6mNXb79cyY+NygUJ0OBgWbtfdY2h90qjKHS9PvY4Q==';
+      pubkey = decodeBase64PublicKey(modulus, exponent);
+      privateKey = decodeBase64PrivateKey(modulus, exponent, d, p, q, dP, dQ, qInv);
+
+      return {publicKey: pubkey, privateKey: privateKey};
+    }
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/pki',
+    'forge/pkcs1',
+    'forge/md',
+    'forge/jsbn',
+    'forge/util'
+  ], function(PKI, PKCS1, MD, JSBN, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      PKI(),
+      PKCS1(),
+      MD(),
+      JSBN(),
+      UTIL()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/pki')(),
+    require('../../js/pkcs1')(),
+    require('../../js/md')(),
+    require('../../js/jsbn')(),
+    require('../../js/util')());
+}
+
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/pkcs12.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/pkcs12.js
new file mode 100644
index 0000000..bbf1eea
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/pkcs12.js
@@ -0,0 +1,650 @@
+(function() {
+
+function Tests(ASSERT, FORGE) {
+  var forge = FORGE();
+  var PKCS12 = forge.pkcs12;
+  var ASN1 = forge.asn1;
+  var PKI = forge.pki;
+  var UTIL = forge.util;
+
+  var _data;
+  describe('pkcs12', function() {
+    it('should create certificate-only p12', function() {
+      var p12Asn = PKCS12.toPkcs12Asn1(null, _data.certificate, null, {
+        useMac: false,
+        generateLocalKeyId: false
+      });
+      var p12Der = ASN1.toDer(p12Asn).getBytes();
+
+      /* The generated PKCS#12 file lacks a MAC, therefore pass -nomacver to
+        OpenSSL: openssl pkcs12 -nomacver -nodes -in pkcs12_certonly.p12 */
+      ASSERT.equal(p12Der, UTIL.decode64(_data.p12certonly));
+    });
+
+    it('should create key-only p12', function() {
+      var privateKey = PKI.privateKeyFromPem(_data.privateKey);
+      var p12Asn = PKCS12.toPkcs12Asn1(privateKey, null, null, {
+        useMac: false,
+        generateLocalKeyId: false
+      });
+      var p12Der = ASN1.toDer(p12Asn).getBytes();
+
+      /* The generated PKCS#12 file lacks a MAC, therefore pass -nomacver to
+        OpenSSL: openssl pkcs12 -nomacver -nodes -in pkcs12_keyonly.p12 */
+      ASSERT.equal(p12Der, UTIL.decode64(_data.p12keyonly));
+    });
+
+    it('should create encrypted-key-only p12', function() {
+      /* Note we need to mock the PRNG, since the PKCS#12 file uses encryption
+        which otherwise would differ each time due to the randomized IV. */
+      var oldRandomGenerate = forge.random.generate;
+      forge.random.generate = function(num) {
+        return UTIL.createBuffer().fillWithByte(0, num).getBytes();
+      };
+
+      var privateKey = PKI.privateKeyFromPem(_data.privateKey);
+      var p12Asn = PKCS12.toPkcs12Asn1(privateKey, null, 'nopass', {
+        useMac: false,
+        generateLocalKeyId: false
+      });
+      var p12Der = ASN1.toDer(p12Asn).getBytes();
+
+      // restore old random function
+      forge.random.generate = oldRandomGenerate;
+
+      /* The generated PKCS#12 file lacks a MAC, therefore pass -nomacver to
+        OpenSSL: openssl pkcs12 -nomacver -in pkcs12_enckeyonly.p12 */
+      ASSERT.equal(p12Der, UTIL.decode64(_data.p12enckeyonly));
+    });
+
+    it('should import certificate-only p12', function() {
+      var p12Der = UTIL.decode64(_data.p12certonly);
+      var p12Asn1 = ASN1.fromDer(p12Der);
+      var p12 = PKCS12.pkcs12FromAsn1(p12Asn1);
+      ASSERT.equal(p12.version, 3);
+
+      // PKCS#12 PFX has exactly one SafeContents; it is not encrypted
+      ASSERT.equal(p12.safeContents.length, 1);
+      ASSERT.equal(p12.safeContents[0].encrypted, false);
+
+      // SafeContents has one SafeBag which has one CertBag with the cert
+      ASSERT.equal(p12.safeContents[0].safeBags.length, 1);
+      ASSERT.equal(p12.safeContents[0].safeBags[0].type, PKI.oids.certBag);
+
+      // check cert's serial number
+      ASSERT.equal(
+        p12.safeContents[0].safeBags[0].cert.serialNumber,
+        '00d4541c40d835e2f3');
+    });
+
+    it('should import key-only p12', function() {
+      var p12Der = UTIL.decode64(_data.p12keyonly);
+      var p12Asn1 = ASN1.fromDer(p12Der);
+      var p12 = PKCS12.pkcs12FromAsn1(p12Asn1);
+      ASSERT.equal(p12.version, 3);
+
+      // PKCS#12 PFX has exactly one SafeContents; it is not encrypted
+      ASSERT.equal(p12.safeContents.length, 1);
+      ASSERT.equal(p12.safeContents[0].encrypted, false);
+
+      // SafeContents has one SafeBag which has one KeyBag with the key
+      ASSERT.equal(p12.safeContents[0].safeBags.length, 1);
+      ASSERT.equal(p12.safeContents[0].safeBags[0].type, PKI.oids.keyBag);
+
+      // compare the key from the PFX by comparing both primes
+      var expected = PKI.privateKeyFromPem(_data.privateKey);
+      ASSERT.deepEqual(p12.safeContents[0].safeBags[0].key.p, expected.p);
+      ASSERT.deepEqual(p12.safeContents[0].safeBags[0].key.q, expected.q);
+    });
+
+    it('should import encrypted-key-only p12', function() {
+      var p12Der = UTIL.decode64(_data.p12enckeyonly);
+      var p12Asn1 = ASN1.fromDer(p12Der);
+      var p12 = PKCS12.pkcs12FromAsn1(p12Asn1, 'nopass');
+      ASSERT.equal(p12.version, 3);
+
+      // PKCS#12 PFX has exactly one SafeContents; it is *not* encrypted,
+      // only the key itself is encrypted (shrouded)
+      ASSERT.equal(p12.safeContents.length, 1);
+      ASSERT.equal(p12.safeContents[0].encrypted, false);
+
+      // SafeContents has one SafeBag which has one shrouded KeyBag with the key
+      ASSERT.equal(p12.safeContents[0].safeBags.length, 1);
+      ASSERT.equal(
+        p12.safeContents[0].safeBags[0].type, PKI.oids.pkcs8ShroudedKeyBag);
+
+      // compare the key from the PFX by comparing both primes
+      var expected = PKI.privateKeyFromPem(_data.privateKey);
+      ASSERT.deepEqual(p12.safeContents[0].safeBags[0].key.p, expected.p);
+      ASSERT.deepEqual(p12.safeContents[0].safeBags[0].key.q, expected.q);
+    });
+
+    it('should import an encrypted-key-only p12', function() {
+      var p12Der = UTIL.decode64(_data.p12enckeyonly);
+      var p12Asn1 = ASN1.fromDer(p12Der);
+      var p12 = PKCS12.pkcs12FromAsn1(p12Asn1, 'nopass');
+      ASSERT.equal(p12.version, 3);
+
+      // PKCS#12 PFX has exactly one SafeContents; it is *not* encrypted,
+      // only the key itself is encrypted (shrouded)
+      ASSERT.equal(p12.safeContents.length, 1);
+      ASSERT.equal(p12.safeContents[0].encrypted, false);
+
+      // SafeContents has one SafeBag which has one shrouded KeyBag with the key
+      ASSERT.equal(p12.safeContents[0].safeBags.length, 1);
+      ASSERT.equal(
+        p12.safeContents[0].safeBags[0].type, PKI.oids.pkcs8ShroudedKeyBag);
+
+      // compare the key from the PFX by comparing both primes
+      var expected = PKI.privateKeyFromPem(_data.privateKey);
+      ASSERT.deepEqual(p12.safeContents[0].safeBags[0].key.p, expected.p);
+      ASSERT.deepEqual(p12.safeContents[0].safeBags[0].key.q, expected.q);
+    });
+
+    it('should import an encrypted p12 with keys and certificates', function() {
+      var p12Der = UTIL.decode64(_data.p12encmixed);
+      var p12Asn1 = ASN1.fromDer(p12Der);
+      var p12 = PKCS12.pkcs12FromAsn1(p12Asn1, '123456');
+      ASSERT.equal(p12.version, 3);
+
+      // PKCS#12 PFX has two SafeContents; first is *not* encrypted but
+      // contains two shrouded keys, second is encrypted and has six
+      // certificates
+      ASSERT.equal(p12.safeContents.length, 2);
+
+      ASSERT.equal(p12.safeContents[0].encrypted, false);
+      ASSERT.equal(p12.safeContents[0].safeBags.length, 2);
+      ASSERT.equal(p12.safeContents[0].safeBags[0].type, PKI.oids.pkcs8ShroudedKeyBag);
+      ASSERT.equal(p12.safeContents[0].safeBags[0].attributes.friendlyName.length, 1);
+      ASSERT.equal(p12.safeContents[0].safeBags[0].attributes.friendlyName[0], 'encryptionkey');
+      ASSERT.equal(p12.safeContents[0].safeBags[0].attributes.localKeyId.length, 1);
+      ASSERT.equal(p12.safeContents[0].safeBags[0].attributes.localKeyId[0], 'Time 1311855238964');
+
+      ASSERT.equal(p12.safeContents[0].safeBags[1].type, PKI.oids.pkcs8ShroudedKeyBag);
+      ASSERT.equal(p12.safeContents[0].safeBags[1].attributes.friendlyName.length, 1);
+      ASSERT.equal(p12.safeContents[0].safeBags[1].attributes.friendlyName[0], 'signaturekey');
+      ASSERT.equal(p12.safeContents[0].safeBags[1].attributes.localKeyId.length, 1);
+      ASSERT.equal(p12.safeContents[0].safeBags[1].attributes.localKeyId[0], 'Time 1311855238863');
+
+      ASSERT.equal(p12.safeContents[1].encrypted, true);
+      ASSERT.equal(p12.safeContents[1].safeBags.length, 6);
+
+      ASSERT.equal(p12.safeContents[1].safeBags[0].type, PKI.oids.certBag);
+      ASSERT.equal(p12.safeContents[1].safeBags[0].attributes.friendlyName.length, 1);
+      ASSERT.equal(p12.safeContents[1].safeBags[0].attributes.friendlyName[0], 'CN=1002753325,2.5.4.5=#130b3130303237353333323543');
+      ASSERT.equal(p12.safeContents[1].safeBags[0].attributes.localKeyId.length, 1);
+      ASSERT.equal(p12.safeContents[1].safeBags[0].attributes.localKeyId[0], 'Time 1311855238964');
+
+      ASSERT.equal(p12.safeContents[1].safeBags[1].type, PKI.oids.certBag);
+      ASSERT.equal(p12.safeContents[1].safeBags[1].attributes.friendlyName.length, 1);
+      ASSERT.equal(p12.safeContents[1].safeBags[1].attributes.friendlyName[0], 'CN=ElsterSoftTestCA,OU=CA,O=Elster,C=DE');
+
+      ASSERT.equal(p12.safeContents[1].safeBags[2].type, PKI.oids.certBag);
+      ASSERT.equal(p12.safeContents[1].safeBags[2].attributes.friendlyName.length, 1);
+      ASSERT.equal(p12.safeContents[1].safeBags[2].attributes.friendlyName[0], 'CN=ElsterRootCA,OU=RootCA,O=Elster,C=DE');
+
+      ASSERT.equal(p12.safeContents[1].safeBags[3].type, PKI.oids.certBag);
+      ASSERT.equal(p12.safeContents[1].safeBags[3].attributes.friendlyName.length, 1);
+      ASSERT.equal(p12.safeContents[1].safeBags[3].attributes.friendlyName[0], 'CN=1002753325,2.5.4.5=#130b3130303237353333323541');
+      ASSERT.equal(p12.safeContents[1].safeBags[3].attributes.localKeyId.length, 1);
+      ASSERT.equal(p12.safeContents[1].safeBags[3].attributes.localKeyId[0], 'Time 1311855238863');
+
+      ASSERT.equal(p12.safeContents[1].safeBags[4].type, PKI.oids.certBag);
+      ASSERT.equal(p12.safeContents[1].safeBags[4].attributes.friendlyName.length, 1);
+      ASSERT.equal(p12.safeContents[1].safeBags[4].attributes.friendlyName[0], 'CN=ElsterSoftTestCA,OU=CA,O=Elster,C=DE');
+
+      ASSERT.equal(p12.safeContents[1].safeBags[5].type, PKI.oids.certBag);
+      ASSERT.equal(p12.safeContents[1].safeBags[5].attributes.friendlyName.length, 1);
+      ASSERT.equal(p12.safeContents[1].safeBags[5].attributes.friendlyName[0], 'CN=ElsterRootCA,OU=RootCA,O=Elster,C=DE');
+    });
+
+    it('should get bags by friendly name', function() {
+      var p12Der = UTIL.decode64(_data.p12encmixed);
+      var p12Asn1 = ASN1.fromDer(p12Der);
+      var p12 = PKCS12.pkcs12FromAsn1(p12Asn1, '123456');
+      var bags = p12.getBags({friendlyName: 'signaturekey'});
+
+      ASSERT.equal(bags.friendlyName.length, 1);
+      ASSERT.equal(bags.friendlyName[0].attributes.friendlyName[0], 'signaturekey');
+    });
+
+    it('should get cert bags by friendly name', function() {
+      var p12Der = UTIL.decode64(_data.p12encmixed);
+      var p12Asn1 = ASN1.fromDer(p12Der);
+      var p12 = PKCS12.pkcs12FromAsn1(p12Asn1, '123456');
+      var bags = p12.getBags({
+        friendlyName: 'CN=1002753325,2.5.4.5=#130b3130303237353333323543',
+        bagType: PKI.oids.certBag
+      });
+
+      ASSERT.equal(bags.friendlyName.length, 1);
+      ASSERT.equal(bags.friendlyName[0].attributes.friendlyName[0], 'CN=1002753325,2.5.4.5=#130b3130303237353333323543');
+    });
+
+    it('should get all cert bags', function() {
+      var p12Der = UTIL.decode64(_data.p12encmixed);
+      var p12Asn1 = ASN1.fromDer(p12Der);
+      var p12 = PKCS12.pkcs12FromAsn1(p12Asn1, '123456');
+      var bags = p12.getBags({
+        bagType: PKI.oids.certBag
+      });
+
+      ASSERT.equal(bags[PKI.oids.certBag].length, 6);
+      for(var i = 0; i < bags[PKI.oids.certBag].length; ++i) {
+        ASSERT.equal(bags[PKI.oids.certBag][i].type, PKI.oids.certBag);
+      }
+    });
+
+    it('should get bags by local key ID', function() {
+      var p12Der = UTIL.decode64(_data.p12encmixed);
+      var p12Asn1 = ASN1.fromDer(p12Der);
+      var p12 = PKCS12.pkcs12FromAsn1(p12Asn1, '123456');
+      var bags = p12.getBags({localKeyId: 'Time 1311855238863'});
+
+      ASSERT.equal(bags.localKeyId.length, 2);
+      ASSERT.equal(bags.localKeyId[0].attributes.localKeyId[0], 'Time 1311855238863');
+      ASSERT.equal(bags.localKeyId[1].attributes.localKeyId[0], 'Time 1311855238863');
+    });
+
+    it('should get cert bags by local key ID', function() {
+      var p12Der = UTIL.decode64(_data.p12encmixed);
+      var p12Asn1 = ASN1.fromDer(p12Der);
+      var p12 = PKCS12.pkcs12FromAsn1(p12Asn1, '123456');
+      var bags = p12.getBags({
+        localKeyId: 'Time 1311855238863',
+        bagType: PKI.oids.certBag
+      });
+
+      ASSERT.equal(bags.localKeyId.length, 1);
+      ASSERT.equal(bags.localKeyId[0].attributes.localKeyId[0], 'Time 1311855238863');
+      ASSERT.equal(bags.localKeyId[0].type, PKI.oids.certBag);
+    });
+
+    it('should generate a PKCS#12 mac key', function() {
+      var salt = 'A15D6AA8F8DAFC352F9EE1C192F09966EB85D17B';
+      salt = UTIL.createBuffer(UTIL.hexToBytes(salt));
+      var expected = '03e46727268575c6ebd6bff828d0d09b0c914201263ca543';
+      var key = PKCS12.generateKey('123456', salt, 1, 1024, 24);
+      ASSERT.equal(key.toHex(), expected);
+    });
+  });
+
+  _data = {
+    certificate: '-----BEGIN CERTIFICATE-----\r\n' +
+      'MIIDtDCCApwCCQDUVBxA2DXi8zANBgkqhkiG9w0BAQUFADCBmzELMAkGA1UEBhMC\r\n' +
+      'REUxEjAQBgNVBAgMCUZyYW5jb25pYTEQMA4GA1UEBwwHQW5zYmFjaDEVMBMGA1UE\r\n' +
+      'CgwMU3RlZmFuIFNpZWdsMRIwEAYDVQQLDAlHZWllcmxlaW4xFjAUBgNVBAMMDUdl\r\n' +
+      'aWVybGVpbiBERVYxIzAhBgkqhkiG9w0BCQEWFHN0ZXNpZUBicm9rZW5waXBlLmRl\r\n' +
+      'MB4XDTEyMDMxODIyNTc0M1oXDTEzMDMxODIyNTc0M1owgZsxCzAJBgNVBAYTAkRF\r\n' +
+      'MRIwEAYDVQQIDAlGcmFuY29uaWExEDAOBgNVBAcMB0Fuc2JhY2gxFTATBgNVBAoM\r\n' +
+      'DFN0ZWZhbiBTaWVnbDESMBAGA1UECwwJR2VpZXJsZWluMRYwFAYDVQQDDA1HZWll\r\n' +
+      'cmxlaW4gREVWMSMwIQYJKoZIhvcNAQkBFhRzdGVzaWVAYnJva2VucGlwZS5kZTCC\r\n' +
+      'ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMsAbQ4fWevHqP1K1y/ewpMS\r\n' +
+      '3vYovBto7IsKBq0v3NmC2kPf3NhyaSKfjOOS5uAPONLffLck+iGdOLLFia6OSpM6\r\n' +
+      '0tyQIV9lHoRh7fOEYORab0Z+aBUZcEGT9yotBOraX1YbKc5f9XO+80eG4XYvb5ua\r\n' +
+      '1NHrxWqe4w2p3zGJCKO+wHpvGkbKz0nfu36jwWz5aihfHi9hp/xs8mfH86mIKiD7\r\n' +
+      'f2X2KeZ1PK9RvppA0X3lLb2VLOqMt+FHWicyZ/wjhQZ4oW55ln2yYJUQ+adlgaYn\r\n' +
+      'PrtnsxmbTxM+99oF0F2/HmGrNs8nLZSva1Vy+hmjmWz6/O8ZxhiIj7oBRqYcAocC\r\n' +
+      'AwEAATANBgkqhkiG9w0BAQUFAAOCAQEAvfvtu31GFBO5+mFjPAoR4BlzKq/H3EPO\r\n' +
+      'qS8cm/TjHgDRALwSnwKYCFs/bXqE4iOTD6otV4TusX3EPbqL2vzZQEcZn6paU/oZ\r\n' +
+      'ZVXwQqMqY5tf2teQiNxqxNmSIEPRHOr2QVBVIx2YF4Po89KGUqJ9u/3/10lDqRwp\r\n' +
+      'sReijr5UKv5aygEcnwcW8+Ne4rTx934UDsutKG20dr5trZfWQRVS9fS9CFwJehEX\r\n' +
+      'HAMUc/0++80NhfQthmWZWlWM1R3dr4TrIPtWdn5z0MtGeDvqBk7HjGrhcVS6kAsy\r\n' +
+      'Z9y/lfLPjBuxlQAHztEJCWgI4TW3/RLhgfg2gI1noM2n84Cdmisfkg==\r\n' +
+      '-----END CERTIFICATE-----\r\n',
+    privateKey: '-----BEGIN RSA PRIVATE KEY-----\r\n' +
+      'MIIEowIBAAKCAQEAywBtDh9Z68eo/UrXL97CkxLe9ii8G2jsiwoGrS/c2YLaQ9/c\r\n' +
+      '2HJpIp+M45Lm4A840t98tyT6IZ04ssWJro5KkzrS3JAhX2UehGHt84Rg5FpvRn5o\r\n' +
+      'FRlwQZP3Ki0E6tpfVhspzl/1c77zR4bhdi9vm5rU0evFap7jDanfMYkIo77Aem8a\r\n' +
+      'RsrPSd+7fqPBbPlqKF8eL2Gn/GzyZ8fzqYgqIPt/ZfYp5nU8r1G+mkDRfeUtvZUs\r\n' +
+      '6oy34UdaJzJn/COFBnihbnmWfbJglRD5p2WBpic+u2ezGZtPEz732gXQXb8eYas2\r\n' +
+      'zyctlK9rVXL6GaOZbPr87xnGGIiPugFGphwChwIDAQABAoIBAAjMA+3QvfzRsikH\r\n' +
+      'zTtt09C7yJ2yNjSZ32ZHEPMAV/m1CfBXCyL2EkhF0b0q6IZdIoFA3g6xs4UxYvuc\r\n' +
+      'Q9Mkp2ap7elQ9aFEqIXkGIOtAOXkZV4QrEH90DeHSfax7LygqfD5TF59Gg3iAHjh\r\n' +
+      'B3Qvqg58LyzJosx0BjLZYaqr3Yv67GkqyflpF/roPGdClHpahAi5PBkHiNhNTAUU\r\n' +
+      'LJRGvMegXGZkUKgGMAiGCk0N96OZwrinMKO6YKGdtgwVWC2wbJY0trElaiwXozSt\r\n' +
+      'NmP6KTQp94C7rcVO6v1lZiOfhBe5Kc8QHUU+GYydgdjqm6Rdow/yLHOALAVtXSeb\r\n' +
+      'U+tPfcECgYEA6Qi+qF+gtPincEDBxRtoKwAlRkALt8kly8bYiGcUmd116k/5bmPw\r\n' +
+      'd0tBUOQbqRa1obYC88goOVzp9LInAcBSSrexhVaPAF4nrkwYXMOq+76MiH17WUfQ\r\n' +
+      'MgVM2IB48PBjNk1s3Crj6j1cxxkctqmCnVaI9HlU2PPZ3xjaklfv/NsCgYEA3wH8\r\n' +
+      'mehUhiAp7vuhd+hfomFw74cqgHC9v0saiYGckpMafh9MJGc4U5GrN1kYeb/CFkSx\r\n' +
+      '1hOytD3YBKoaKKoYagaMQcjxf6HnEF0f/5OiQkUQpWmgC9lNnE4XTWjnwqaTS5L9\r\n' +
+      'D+H50SiI3VjHymGXTRJeKpAIwV74AxxrnVofqsUCgYAwmL1B2adm9g/c7fQ6yatg\r\n' +
+      'hEhBrSuEaTMzmsUfNPfr2m4zrffjWH4WMqBtYRSPn4fDMHTPJ+eThtfXSqutxtCi\r\n' +
+      'ekpP9ywdNIVr6LyP49Ita6Bc+mYVyU8Wj1pmL+yIumjGM0FHbL5Y4/EMKCV/xjvR\r\n' +
+      '2fD3orHaCIhf6QvzxtjqTwKBgFm6UemXKlMhI94tTsWRMNGEBU3LA9XUBvSuAkpr\r\n' +
+      'ZRUwrQssCpXnFinBxbMqXQe3mR8emrM5D8En1P/jdU0BS3t1kP9zG4AwI2lZHuPV\r\n' +
+      'ggbKBS2Y9zVtRKXsYcHawM13+nIA/WNjmAGJHrB45UJPy/HNvye+9lbfoEiYKdCR\r\n' +
+      'D4bFAoGBAIm9jcZkIwLa9kLAWH995YYYSGRY4KC29XZr2io2mog+BAjhFt1sqebt\r\n' +
+      'R8sRHNiIP2mcUECMOcaS+tcayi+8KTHWxIEed9qDmFu6XBbePfe/L6yxPSagcixH\r\n' +
+      'BK0KuK/fgTPvZCmIs8hUIC+AxhXKnqn4fIWoO54xLsALc0gEjs2d\r\n' +
+      '-----END RSA PRIVATE KEY-----\r\n',
+    p12certonly:
+      'MIIEHgIBAzCCBBcGCSqGSIb3DQEHAaCCBAgEggQEMIIEADCCA/wGCSqGSIb3DQEH\r\n' +
+      'AaCCA+0EggPpMIID5TCCA+EGCyqGSIb3DQEMCgEDoIID0DCCA8wGCiqGSIb3DQEJ\r\n' +
+      'FgGgggO8BIIDuDCCA7QwggKcAgkA1FQcQNg14vMwDQYJKoZIhvcNAQEFBQAwgZsx\r\n' +
+      'CzAJBgNVBAYTAkRFMRIwEAYDVQQIDAlGcmFuY29uaWExEDAOBgNVBAcMB0Fuc2Jh\r\n' +
+      'Y2gxFTATBgNVBAoMDFN0ZWZhbiBTaWVnbDESMBAGA1UECwwJR2VpZXJsZWluMRYw\r\n' +
+      'FAYDVQQDDA1HZWllcmxlaW4gREVWMSMwIQYJKoZIhvcNAQkBFhRzdGVzaWVAYnJv\r\n' +
+      'a2VucGlwZS5kZTAeFw0xMjAzMTgyMjU3NDNaFw0xMzAzMTgyMjU3NDNaMIGbMQsw\r\n' +
+      'CQYDVQQGEwJERTESMBAGA1UECAwJRnJhbmNvbmlhMRAwDgYDVQQHDAdBbnNiYWNo\r\n' +
+      'MRUwEwYDVQQKDAxTdGVmYW4gU2llZ2wxEjAQBgNVBAsMCUdlaWVybGVpbjEWMBQG\r\n' +
+      'A1UEAwwNR2VpZXJsZWluIERFVjEjMCEGCSqGSIb3DQEJARYUc3Rlc2llQGJyb2tl\r\n' +
+      'bnBpcGUuZGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDLAG0OH1nr\r\n' +
+      'x6j9Stcv3sKTEt72KLwbaOyLCgatL9zZgtpD39zYcmkin4zjkubgDzjS33y3JPoh\r\n' +
+      'nTiyxYmujkqTOtLckCFfZR6EYe3zhGDkWm9GfmgVGXBBk/cqLQTq2l9WGynOX/Vz\r\n' +
+      'vvNHhuF2L2+bmtTR68VqnuMNqd8xiQijvsB6bxpGys9J37t+o8Fs+WooXx4vYaf8\r\n' +
+      'bPJnx/OpiCog+39l9inmdTyvUb6aQNF95S29lSzqjLfhR1onMmf8I4UGeKFueZZ9\r\n' +
+      'smCVEPmnZYGmJz67Z7MZm08TPvfaBdBdvx5hqzbPJy2Ur2tVcvoZo5ls+vzvGcYY\r\n' +
+      'iI+6AUamHAKHAgMBAAEwDQYJKoZIhvcNAQEFBQADggEBAL377bt9RhQTufphYzwK\r\n' +
+      'EeAZcyqvx9xDzqkvHJv04x4A0QC8Ep8CmAhbP216hOIjkw+qLVeE7rF9xD26i9r8\r\n' +
+      '2UBHGZ+qWlP6GWVV8EKjKmObX9rXkIjcasTZkiBD0Rzq9kFQVSMdmBeD6PPShlKi\r\n' +
+      'fbv9/9dJQ6kcKbEXoo6+VCr+WsoBHJ8HFvPjXuK08fd+FA7LrShttHa+ba2X1kEV\r\n' +
+      'UvX0vQhcCXoRFxwDFHP9PvvNDYX0LYZlmVpVjNUd3a+E6yD7VnZ+c9DLRng76gZO\r\n' +
+      'x4xq4XFUupALMmfcv5Xyz4wbsZUAB87RCQloCOE1t/0S4YH4NoCNZ6DNp/OAnZor\r\n' +
+      'H5I=',
+    p12enckeyonly:
+      'MIIFcQIBAzCCBWoGCSqGSIb3DQEHAaCCBVsEggVXMIIFUzCCBU8GCSqGSIb3DQEH\r\n' +
+      'AaCCBUAEggU8MIIFODCCBTQGCyqGSIb3DQEMCgECoIIFIzCCBR8wSQYJKoZIhvcN\r\n' +
+      'AQUNMDwwGwYJKoZIhvcNAQUMMA4ECAAAAAAAAAAAAgIIADAdBglghkgBZQMEAQIE\r\n' +
+      'EAAAAAAAAAAAAAAAAAAAAAAEggTQQHIbPs0naCmJGgmtvFNmUlv9sHkm2A/vWHjY\r\n' +
+      'B8UavyYUz3IMtDCWZBoWHWp/izLDroCSxkabxyzqlSbYdGug1QY9y9RP6TjP6uaw\r\n' +
+      'SFurDe7beTRB3d8Oe2AMEmUQtaPE/zQI52aWse8RNh5P1I1wQEzVvk8/hf2eLdLQ\r\n' +
+      'cxUb0flz65Nkr4tVPsAmXfbepiyPm+lISi7syNfO6d7424CsGYXD3VCtDxbS5r0m\r\n' +
+      'L7OIkMfr7JhkvlrcdgrBY5r8/67MtfaJrMe0FR90UJd6ST++2FyhbilSz2BI6Twu\r\n' +
+      'wNICvkbThwY/LLxOCPKm4AgEj/81pYy6z2eWG59pD8fju4IOJUt3AGoPZoCQrbmD\r\n' +
+      'MDahpYgey6bo8ti9H08HhvP9keOgI2HUCQoObL0c2GF+fv6m/EJ59hpH9qeryfT4\r\n' +
+      'HAzSfd4h0YszF32a23+dGEgXAA492m00lZ/uM5nTF0RIQsqj5BJSxEEBpYequF4A\r\n' +
+      'MNCsjKl90HPSIndNSUfgN0us8FtmrzBNbmIATFE9juKHWag3p751ertsGv6e/Ofm\r\n' +
+      'xAhgF21j8ZhwXKjsVY4uYVFYLWkCLSD4gF8/ijWg873XZKzjPuy8w3SAAcya8vaQ\r\n' +
+      'buzzk5zgN0g5T+JxCAdP50FH68rVG2dhfY1BDFe8xY6mxSfs/wUj5EVD9jdqlYpu\r\n' +
+      '/o3IFtdksSra8eOwO2F/kw69x11wZaYpZaRzbIM2x1pDARkAtnbdvdSEXMOT7htA\r\n' +
+      'OYAJiZuhW0czOgumGGhIO8TBxwMh3/WjkUdlJ1JNhl6M+JHlmIKerCuP6kCLJsAp\r\n' +
+      '+RyRRp6pKa4t09G5rjAjCUiPqkCKRSf40ijhn+lLhj8ZHKLDyw4FCMo6NvQvFsDp\r\n' +
+      'dbCbbHzWGZXEspT56jGbuju1DQCiy+uVKYhigU1PvRXrxpCHKcv65iqnwPRgGE6X\r\n' +
+      'dPSGfjsLDbATvSrVv1DvJNTH9wyCSQt/eqBXFWkQeFEqKXij0atkdHL6GXRo57PX\r\n' +
+      'KZgeul2Xxd2J5SYNCUJf8IL4UOfHRMB4GaGGt9LTpPq2bI9fju1vVE4BjL1gSYIi\r\n' +
+      'cvynjH7mpzVwq+Cxj4uCo8aZQKWB0BI7c4cDaFmKPIFD47QFZUNgYCv9WfNljOe/\r\n' +
+      '+CqRbxNFUsXCR4hEoYmdn0EEI2b1F6Hkz/dDrLH33My4Gp14N8MVkASWtecxvbfa\r\n' +
+      'xkj5SiC5NZQ2TZtt3DX508BPFSqJRjb83I7qhNjWxqFUxS1ma9PF/AQzUgNLw+Gz\r\n' +
+      't5fpB3hD+33fWE8y4RbiUsBU+O56qaN9luOZLa/eVwo+I9F1LgXsS29iv6LvHO5m\r\n' +
+      '+IfzHM+FROS1XhzM+t8rxTK7VmBHqmPrKcjtnYXZh0eA9YIhTEeRdlEO8q4dsKFv\r\n' +
+      'sbQZ3+65DW6pbDbe/3CGqf43w5vbTvhsRSYqC9ojKjnUtoJ8gY+b7GPNUVsgxQCh\r\n' +
+      'jfqqZoVmhBihTO5hgeHJf+ilCbw5cPCEXobAxMfdPaasBV5xDBcvDDl7Sv16feYk\r\n' +
+      'OZJ6bm9wRkqbQUsWYMgYLCfs/FDe1kfkSeS8JYlmFIkHZL6K3LqkULnqPfQdnlMp\r\n' +
+      '1PYGlPTdp+6XcqNBVORyXkOXF7PyrOw7vRefEuGcBvZ4TT0jmHE3KxKEvJwbVsne\r\n' +
+      'H4/s3xo=',
+    p12keyonly:
+      'MIIFEAIBAzCCBQkGCSqGSIb3DQEHAaCCBPoEggT2MIIE8jCCBO4GCSqGSIb3DQEH\r\n' +
+      'AaCCBN8EggTbMIIE1zCCBNMGCyqGSIb3DQEMCgEBoIIEwjCCBL4CAQAwDQYJKoZI\r\n' +
+      'hvcNAQEBBQAEggSoMIIEpAIBAAKCAQEAywBtDh9Z68eo/UrXL97CkxLe9ii8G2js\r\n' +
+      'iwoGrS/c2YLaQ9/c2HJpIp+M45Lm4A840t98tyT6IZ04ssWJro5KkzrS3JAhX2Ue\r\n' +
+      'hGHt84Rg5FpvRn5oFRlwQZP3Ki0E6tpfVhspzl/1c77zR4bhdi9vm5rU0evFap7j\r\n' +
+      'DanfMYkIo77Aem8aRsrPSd+7fqPBbPlqKF8eL2Gn/GzyZ8fzqYgqIPt/ZfYp5nU8\r\n' +
+      'r1G+mkDRfeUtvZUs6oy34UdaJzJn/COFBnihbnmWfbJglRD5p2WBpic+u2ezGZtP\r\n' +
+      'Ez732gXQXb8eYas2zyctlK9rVXL6GaOZbPr87xnGGIiPugFGphwChwIDAQABAoIB\r\n' +
+      'AQAIzAPt0L380bIpB807bdPQu8idsjY0md9mRxDzAFf5tQnwVwsi9hJIRdG9KuiG\r\n' +
+      'XSKBQN4OsbOFMWL7nEPTJKdmqe3pUPWhRKiF5BiDrQDl5GVeEKxB/dA3h0n2sey8\r\n' +
+      'oKnw+UxefRoN4gB44Qd0L6oOfC8syaLMdAYy2WGqq92L+uxpKsn5aRf66DxnQpR6\r\n' +
+      'WoQIuTwZB4jYTUwFFCyURrzHoFxmZFCoBjAIhgpNDfejmcK4pzCjumChnbYMFVgt\r\n' +
+      'sGyWNLaxJWosF6M0rTZj+ik0KfeAu63FTur9ZWYjn4QXuSnPEB1FPhmMnYHY6puk\r\n' +
+      'XaMP8ixzgCwFbV0nm1PrT33BAoGBAOkIvqhfoLT4p3BAwcUbaCsAJUZAC7fJJcvG\r\n' +
+      '2IhnFJnddepP+W5j8HdLQVDkG6kWtaG2AvPIKDlc6fSyJwHAUkq3sYVWjwBeJ65M\r\n' +
+      'GFzDqvu+jIh9e1lH0DIFTNiAePDwYzZNbNwq4+o9XMcZHLapgp1WiPR5VNjz2d8Y\r\n' +
+      '2pJX7/zbAoGBAN8B/JnoVIYgKe77oXfoX6JhcO+HKoBwvb9LGomBnJKTGn4fTCRn\r\n' +
+      'OFORqzdZGHm/whZEsdYTsrQ92ASqGiiqGGoGjEHI8X+h5xBdH/+TokJFEKVpoAvZ\r\n' +
+      'TZxOF01o58Kmk0uS/Q/h+dEoiN1Yx8phl00SXiqQCMFe+AMca51aH6rFAoGAMJi9\r\n' +
+      'QdmnZvYP3O30OsmrYIRIQa0rhGkzM5rFHzT369puM63341h+FjKgbWEUj5+HwzB0\r\n' +
+      'zyfnk4bX10qrrcbQonpKT/csHTSFa+i8j+PSLWugXPpmFclPFo9aZi/siLpoxjNB\r\n' +
+      'R2y+WOPxDCglf8Y70dnw96Kx2giIX+kL88bY6k8CgYBZulHplypTISPeLU7FkTDR\r\n' +
+      'hAVNywPV1Ab0rgJKa2UVMK0LLAqV5xYpwcWzKl0Ht5kfHpqzOQ/BJ9T/43VNAUt7\r\n' +
+      'dZD/cxuAMCNpWR7j1YIGygUtmPc1bUSl7GHB2sDNd/pyAP1jY5gBiR6weOVCT8vx\r\n' +
+      'zb8nvvZW36BImCnQkQ+GxQKBgQCJvY3GZCMC2vZCwFh/feWGGEhkWOCgtvV2a9oq\r\n' +
+      'NpqIPgQI4RbdbKnm7UfLERzYiD9pnFBAjDnGkvrXGsovvCkx1sSBHnfag5hbulwW\r\n' +
+      '3j33vy+ssT0moHIsRwStCriv34Ez72QpiLPIVCAvgMYVyp6p+HyFqDueMS7AC3NI\r\n' +
+      'BI7NnQ==',
+    p12encmixed:
+      'MIIpiwIBAzCCKUUGCSqGSIb3DQEHAaCCKTYEgikyMIIpLjCCCtMGCSqGSIb3DQEH\r\n' +
+      'AaCCCsQEggrAMIIKvDCCBVsGCyqGSIb3DQEMCgECoIIE+jCCBPYwKAYKKoZIhvcN\r\n' +
+      'AQwBAzAaBBShXWqo+Nr8NS+e4cGS8Jlm64XRewICBAAEggTIMtqzJpmSCGAYKTj8\r\n' +
+      '1t3U0mGNAErZt0UA2EP9dcGvyG+0W+PMorwwduveGz5ymdqh+8mdbGOTTGKqLVmB\r\n' +
+      '9vR2826foDSgjB+x+fSX9UtSvf9xwF0J6VGPt64RP4J3c+5ntd/gleJCpeBW/div\r\n' +
+      'ieeSRAJ0JX/JthDvO1VyzBOb8w5lakK/mCvLpcbUMIMnF6M/TT1rreqtl8GSx9+n\r\n' +
+      '+ne4tBWCUYAZfYHuKd2tCpT+lpP8pmZ7BYEvgyWPmYkNTkbPMAct1nJcN3L89Rt0\r\n' +
+      'Yw2fg58pvzY0WlHK3H5rB4J7835jTnZLAYz2sjnlDXndwXtiH9AU3X3KQpSDHrkd\r\n' +
+      'ypBQfPHVwB7f+UiKYx5KYNjT1ph2y4DoBV6e4tnQs4KjixtKnom/9ipqOwjP2o6+\r\n' +
+      '4rkZf3I+g8ZrTQygbmQBCfdduNwnhImf7XJezK2RoW9/b9WoSPGSuzsVmt7h+hYs\r\n' +
+      'hGGx5mdk+iJTmst5MrdNx4byKLW+ThrFJ+eqUwm/d+ODQolu+gckOTyaUvNfjYy7\r\n' +
+      'JYi7wnzKAiOyNHwoP+Efpwb+eDffDyg0u/SuEc3qulWr61hfvjihK7w9Vo21kW3r\r\n' +
+      'a6vSL6mS9XBJjvJFdf5sEMiNTUxR7Zw4AsKUysgyiXRFM2hkxuwImFozyK+Er4OJ\r\n' +
+      'ydi3NpzcAL2+a8JzB35QztRxnypn/v/bWIyt89fW1mrstkCwvNRBaYnI4AGDN0C7\r\n' +
+      'jw5aYbOcdg3PbV69+5i4RCRkN2GU6LTovSaBvfBWxrJHav8pfinLhduOckwrWckx\r\n' +
+      'O38vc0fSuTUQaXgL8fXofX6L0139l9fN2MfndI8+39JOlzXNCsldpX+Nt2PI2Awm\r\n' +
+      'AgKEpLA3jbjazqvOmZUBxh0RVozzVu+JTbGWvkalEcmyncCuKSFZkMlP3SNrn4PS\r\n' +
+      'tpHlohTPBPHpxgJce0O6ylxgUZkUsSDatE0joWW/YJ+us0bqeGej5OLvmI9/L9iH\r\n' +
+      '2jCFIN79WVG06GsNuiKON12KPL4J/B4rv9bguQHdcPGJcVXtKv1Uzscpy1uQcqZc\r\n' +
+      'qVzl+Om4fbb0mg+vRXi9FQu//U35yK95NjF6KyniVF0riZWA6bb8dO4YdO4q9IYZ\r\n' +
+      'OFeoLQ/Zl4Zg58ytsUsqoFW6yK7itGUAV1y4BPME4pqkQAI5EVgaFnng9Gdcq9hN\r\n' +
+      '3VHHJLUiCjMLCmWrzt5dTgNCLrvF60bDnM5w9VAkR1xvNzYL/ui0j5A5fbpFc7jz\r\n' +
+      '1JcwFilP9qD94MPBOoPRNJNRxDl1bigdBtR50VTo7tNt48sSZHdVWPGMaqjDndRg\r\n' +
+      'ur3EJeQVMUvVf/5L9hDaZdqxJ9x6Va+5f4a4Or3ttOCb1qCawqutx6IcOc26EAVy\r\n' +
+      'nQ47UXQ2j/AjDoG+T8u34+TQsiVyC5X96TezAfPk5Vp8KUBjhBy15Z0YlnxXw4Up\r\n' +
+      'KzFPMfWOLTiElbJGaLtD7MXrXMQcdK9S2d/MR01zM8QuLwDH4OJfSJ53mlgsFmRG\r\n' +
+      'x7L+nZS7961GpoGHIZRRWvi7yejNpzxBUN7rIERgUqVQeh3lLDeDz8XKT83Hzd5R\r\n' +
+      '4AufZHsVg4K1xHxczD1NVoc2O/GM40vyPiK2ms1mQPiTckyF1jrsfKYDwbkzE3yi\r\n' +
+      'tJXp7Wlc5IHVQqULMU4wKQYJKoZIhvcNAQkUMRweGgBlAG4AYwByAHkAcAB0AGkA\r\n' +
+      'bwBuAGsAZQB5MCEGCSqGSIb3DQEJFTEUBBJUaW1lIDEzMTE4NTUyMzg5NjQwggVZ\r\n' +
+      'BgsqhkiG9w0BDAoBAqCCBPowggT2MCgGCiqGSIb3DQEMAQMwGgQUVHez67zL2YSj\r\n' +
+      'TqMZjS54S+FO+3wCAgQABIIEyDFgx9KJvdvBoednovcwUJeTWhvvMl6owrJ2FhVY\r\n' +
+      'FjahfYv7vLAKUeQiqnepRcATUzSHJgDDKlnW+0UDSGUqUoabbJhAqtHqrHFevGS2\r\n' +
+      'YpPNCfi7C2XTm4+F1MNmlmZhsM8gIY+2lmVpjRm+DvymKBzRuEw81xcF+RFDdOTX\r\n' +
+      '/ka6l5leRoFWTbfTnpIxA5QBVvEH52UkDw3hcrmVIct0v60IseiOqiL/4IpbpjmF\r\n' +
+      '3/rQdinE2sckujcEJD8edu1zbZzZ7KIbklWpPvcMRqCQSgrTuW1/lBuyVH3cvoFp\r\n' +
+      'FtaAw60f6X1ezKmiwA0nYIwahGVmyG4iektxO76zzBPkhL5HPD8BuJX+TRE9hYrZ\r\n' +
+      'tk161/hKFznWpPEC5ferEEEQi0vB2In1uz7L9LkpmC/to1k8MI1A/0yhY5xXUh4H\r\n' +
+      'hmp50OEsBnaXjDqPgtZeukqObjKbOSS4zL1WZ5vohJWQdF+C04d93MZoieUSz0yr\r\n' +
+      '1vSQ/JIr51NRKdffCgoZSFayv5vzFyTu9FKlUBgGEUMEzBdZ200v5ho7fHXWp1gW\r\n' +
+      'TyZK1pdVAC6tnKIgfSdkG7txHUDru120G7AdFXoFeRo7zalxGiGx5RIn3b/qfmyO\r\n' +
+      'MxcJX9wpFck9IcnN8L/S7hbxt9yAINshOyEM0rUXz0fjVZfcckKLso87YXCGJ7+X\r\n' +
+      '6HYe8bs7/uID7Yz7svD1iwnBlEPQInENZBEPuj6dtMYhMXXMHrY5nwNkXBGQioET\r\n' +
+      'O6xLjigPX7AUSuCCIRuoHGfo54HxV5uCon2/ibDuhTr46FrTKxQl2xv3M6VoWF/+\r\n' +
+      '0vLiCGKDa/aT5dZhdZ9OqC56mr6dFf8fSntMBBBxtUmcLVySa33G5UCInSrnTgu0\r\n' +
+      'fY8XGgud/V++xs5lr6jxFQjTdc0ec4muRBOflAvxGq/KKmhbO6h2sa9Ldmr9EABY\r\n' +
+      'jRaMz63WvObklE1m1IajjiceVXNLwJCwf37J7HKp1836WiWl/psIRSpsV0gdeb7y\r\n' +
+      'kEx54sEkbwtu8TNga6PbWUzwVEamFSGkAIxAnCCBj7W0okoLw+z1+FAby2lnMSSP\r\n' +
+      'F9g6aEEACt0h7fgOb6AEi9NCqfrpiZADwW1E0FRYOf8sIy/z6NPQGft3aIlUG9DA\r\n' +
+      'EZAm5IdZ0umQLMqeG30ZkC88W+ERhdIpVpwuHevdRyDwwN6SZ2+AZd5it1EOCLrC\r\n' +
+      '8CSWXyCNaSkPyoPzE0CpeayyhxYkQNg2KiLEOsOOOmSFpQx+R4QQjJL+chuX8T/D\r\n' +
+      'rxrgUgnPWPTDRI5iTexcCBlPdMbeyxfpwIWU0ZZsQxK1eBdizIzw/2JTSyHYVZuq\r\n' +
+      'nhznMaQHH0oA2PGqZw0y7Vu9iRzGU3RrEBBdGnZIwdz9agBc6BxqtLQ5tLKNLCBS\r\n' +
+      'BZjrCbWe9yBarQOFOpVPiczt/oJju/d5jC9Sj1QDppjLTiajZlcoY+mHGqcbzoe4\r\n' +
+      'wVN9+ZetkrGk4zDc8MPYMbHIxLR58P3noVZ6s84h1rhA8lKCg9vvG0jffcuveIRu\r\n' +
+      'AosyBT0v0qVRUWMIXJKpJSivKPykbQm6J+bAoK8+l3yCJ0AWpDcw5Wo5XqV/t4px\r\n' +
+      'xr95ikcr1+ANBRxa/TAl4oYuoqhlkx7Q8i/XCSudpXrswWcfR5ipc0tBzDFMMCcG\r\n' +
+      'CSqGSIb3DQEJFDEaHhgAcwBpAGcAbgBhAHQAdQByAGUAawBlAHkwIQYJKoZIhvcN\r\n' +
+      'AQkVMRQEElRpbWUgMTMxMTg1NTIzODg2MzCCHlMGCSqGSIb3DQEHBqCCHkQwgh5A\r\n' +
+      'AgEAMIIeOQYJKoZIhvcNAQcBMCgGCiqGSIb3DQEMAQYwGgQUQmWgPDEzodyfX1/t\r\n' +
+      '0lON23fzMeUCAgQAgIIeAAxfoaegDbtpKNtbR/bKgGGecS1491HJMR22X5mHI5EV\r\n' +
+      'UxPyuyM2bHky/U1eGix06P0ExQMV5kh/Eb+6vRLn+l0pTci53Ps2ITKFXvmqZ5Zx\r\n' +
+      'yjFtU3LCzN/qh5rFsLpPLdFn4oNrBveXWNPJrIj3Sf93To2GkLFQQ2aINNHe76k3\r\n' +
+      'O4jp6Kz4DKFrnyrY/fDDhHuGnkvHXBXPO+413PIV4Jgmv1zULkB94WpcJ35gsBGV\r\n' +
+      '3Njt7F0X10ZE3VN/tXlEPjaSv5k4zpG5Pe18Q4LnrPSN+XLfFLRnnYJiDlQkvO91\r\n' +
+      'AOxqlAkUq4jAGbTBUeSt+8P5HaliAPDJA43/tEWrb7fX68VpIblm4Y38AIoZOL8u\r\n' +
+      'uafg3WctcD9cy2rP6e0kblkcG4DLrwp/EezeXDxbOsdViiLU5SL1/RhO/0cqB2zw\r\n' +
+      '2scYLc6nJkxzC3iyzhukyn4834SAj+reJMzyiy9VviGQlDz4HFC+P9kYKOqbdW9N\r\n' +
+      'YYLYluHWjnNzE1enaYSPNPuR1U9UhSN/wKVwmdXsLRK0+ee7xpryxpTeUNAwacGR\r\n' +
+      'R7uWiXVBj+xQ2AG5qmW4fe1wxrZIL5bD1Z98ss3YLyUESUIv3K6MxkXZdp+gXv97\r\n' +
+      'jN6j2r536fGpA6jWehjsjk03tL5Zjv4i0PZLUFj16T1uXMzmaKplVd1HYY6bhBl6\r\n' +
+      '7lJerTtrGnPpybAeVn5KFsct0HchWIOkAeqOy3tIqi3R1msIrtR5FviFCgFYS5sV\r\n' +
+      'ly+T+rNdjQM4FrRk6y/IcCqoQTE6By8cYafRH58X07ix1+k5IFlrTbPrA8w1qQ6T\r\n' +
+      'wI5ww0hf4aE3W7brXMlmLYBfwfkTWLH/yDQsXBLsma0y1G7Ixn0BLuo6FBm3ayC2\r\n' +
+      'LEkN+iB+zEeC54oHE450Bhv1TOS80PrLzDW7wL789adWKXHgMmug9sT67gBbaFeU\r\n' +
+      'u3Z8VTISGxmbrEJQAMEoLuQiujHSfpPb5zK02+A363r+bLt1VbIs5jrYMvaB7qrk\r\n' +
+      '7nVJbVYlPscGwUQUEq4YbCjlg77rhY6d61LIcguG5snF2JTnR74Gu72JpqkrrtA9\r\n' +
+      'QHQ/njBnmIenXkqCzwcjyqiKUmPXazC/un7Hl3ZUp7BbbvfCJ1VNqtQJTdyS6kZ0\r\n' +
+      'ZIURy6R4uenoOw9BJfTzLEi+on3af1XXavb8apGlTVCxs8hL4F2IR1A3bkB8fMHv\r\n' +
+      '5oie2te80GKp+B+r0VrEdOLy6BkLgEfuwrpcsIjz+6z83UhfMhgKAcXYJVUC/mmf\r\n' +
+      'xO4hZ3AHKLCgVql8D4NoYPanQYEKx2sEoTDsCzsoh+E6OYhe4CiSBYwB4s5fKX1w\r\n' +
+      '5LVz7crf8Pg+WfffeP8Y/tDOiShz4luB7YVzw+sAy9Xil5+KmPO11yeDwIe3bdvu\r\n' +
+      'SeTAgzZ4lx7aZUpQ2cGaswo5Ix3Q7z2WkooYxCi4+XVw+BhRO0pVuyQB04v5rr1W\r\n' +
+      'EFlDAsC+RVcUw8gyM+tSxm5vqP7H6oEJT00tBYNAX/9ztDpoX4i2276s+6Iszz8B\r\n' +
+      'kqqTfasb41xzUdFf1PpSzqVGKDi4lAftfedn4JFuQHhcI4MhtxwwecKUL3uHXWiW\r\n' +
+      '3o++dAjO7ybfBm3j0WIKGVwxfON5KnVetSOofc3ZahSklUqEuyaQ/X93FT4amYMJ\r\n' +
+      'U7NwbLmrCuYe19/+0lt0ySSSBPNNJcV8r+/P0m4gR7athre9aSn/gU2rRrpYfXUS\r\n' +
+      'SIskLLPn26naLqLW5eEqF9KBg77pGXoXA4guavjUtxEeDCL0ncqAPlhNlRc7NTw5\r\n' +
+      'MGy65ozntamlGrAWK96fMesmF81ZFHyBH4XImDkIvEr62hmJUJuTh3lBhIGAmqwo\r\n' +
+      'jcYdAkibrZh3RmhYNzuSAPoBOd959fOwb9SVltDea49XAopKTodL6FPX4UQbCuES\r\n' +
+      'oml4ZBvRs+ykU+q1to+0QdoY8x0vzkEL1cOOEcbAQebK3kw3GmNZSi6+dzh+gC3C\r\n' +
+      'xrt53S6VrPlO5kpvlMjUjd5LDTIa05Kz+pIXVXUJSY5zNEWtQ54ne3TIHoqpT8oB\r\n' +
+      '8aQ+AnUKznf3Q5S3hQSA0P/zeIcbLwUvDGwk5GI+X38vNm6zbg+fhLwKi0E87fGE\r\n' +
+      '4ZM1w+D5Cfzl6AOP8QTnM9Az/g7z+nlslfh1uS4O87WNnETXyFqOKuMK5MsAYBmg\r\n' +
+      'mctsteL7lHbOcmATAX0MjGfewqvh3uVm18xg3S8RHbsQ42IC6NGDS7YfYI/ePrGu\r\n' +
+      'hdaTeUJuQVm8vSseL5vTeINLbWG7znV4avDgFDx6V+lL77relJ6dQQkRoCf/SNc4\r\n' +
+      'r3v2I1Dd7I77+AT/uBZ3laKsqQcUhcjhEb2iLzjWpZNnO54VhqILwVD8AU8QMQpu\r\n' +
+      'bjMxDXNKY9nhDWZtCoSEcbmvReo5dYXLCAjUokd2utwT8xTj+D7MADWKLTIfUO4H\r\n' +
+      '/OKq26mKCZq/6xgoLzXpiQeDxBfojJA4HWvZTmPlqH2VzIihKNFgP3QD1TH/nPCp\r\n' +
+      'LP0SULTuszYNMTbOOmPj8sYK57dVJUJc2/TiUr1rbxSEEnBL/y4BBUmWxESzNJuO\r\n' +
+      'ohJcR8rnyeklB5tnB5KzYuJqb5Do8PX7h7sGKZWOX0JAQkyq6QNSpJPR3PQ4tTSo\r\n' +
+      'vt2pn/+3Uj+9uEvMpYroJEaOMKW3kGL+PUxLg5xMmOWR86jGqHmfY/zx/sx0eiYL\r\n' +
+      'xXlD7KzaNuBLKGnTv/7fK7OzNc9bmS+pIeru8jtPIm6i6u+mQ/byIehjIPYxR1m/\r\n' +
+      'WBu7LJv4LALDHUh93Fnr92sdWkiV9yU5m5dMIgWzcT2Qis148p+y+w1teqJEnYsN\r\n' +
+      '7Ea1cRRbG/HXB4EmOuwI9oDU5so4gYqQKwv0YvL1P93AzxN0v5iA8g9JIcWD8jun\r\n' +
+      'CeyV90HiPa/shqk/xMbwQTypfFK0kGSPPeCJNBeAmLlM4RoTdGHY7pSoYyuRaRZj\r\n' +
+      'TgBfHT4WxQA5Wttp/rLlbJbyt0vabH15gyjE0WpYOItPh11ONchShJXh5/6zEyDS\r\n' +
+      'Nyn6TjFLmoDqHRSIxNraYQd2q7e11v9CJX0eoqljjst0LAWPFZ7X4m+kSQtoTdzt\r\n' +
+      'tuiPqkBY8wFokG/Mo0mDKwfTT1ZYSdygJZr8ZrNF+hXmBJN9mm4/0S+hN4Vtx/wm\r\n' +
+      'KKWeUOysbqOl3r0EHhh0Mugo2ApTABBDwzoLy7UDXfmJT8T7M0hh+ZT1Pja+G1YL\r\n' +
+      '/HrGHx8eAQQj0c4hyqePC86jgSh3iIkaBJFgEpytfJAnRZ/qr50YK5G7J6R2EjrL\r\n' +
+      '8IGcABSimIidvn0gQ0fBB//LR3con+KjugsA8cTC/cTwfSoyWr9K9FhjpmQ0rxUZ\r\n' +
+      'uE12auhTB2dNdCoOwai97jREVngGaL5GTqUqowNeUUXbedhQI5sRKphrRoinYjJ1\r\n' +
+      'uPuJDLVEsur2pkenLZLZn4l0Srgq1KAOBuZzqqDT6adxfQn3eKN6H0XHja9HMYU5\r\n' +
+      'eXNDEZyT+W6Xg4HcHtiH751LF62SR74GR1HiU3B1XXryXpxyBMGbzdypIDRR6PZb\r\n' +
+      '4o6na3Kx8nyYztI6KZ1Y4PukFqsYuCjFqjJDf9KtFM9eJyedSlsYGd2XDVMUpIlC\r\n' +
+      'NQ9VbRk+hDoH+J74upvX3cbASGkjmuu6sIKdt+quV2vdbyCKukayeWBXVP8bCW4Z\r\n' +
+      'ft0UIf8QIPmkP6GQ3F2qn/SjJI7WhrdGh04tpC0QuMdRLzJnd+R/tjA/QisCWxAW\r\n' +
+      '3WETPDphJMTqKHAUx/86VDSGV013uCrOkTXvuGJLOTl3mdbEj2+0+DLOE2APBsJc\r\n' +
+      'O0Lt5P0Oouigbj+wSVz20Fg7QhXO8Wep7S0krHAXJv3FdV0Cdn6MXaxeCBOfY4Rf\r\n' +
+      'MDUmN/xaiMk2mz7dfDRhg8OADNacg60RylM9jEQ1UclXNlzEBUseY7x3R7qqyeXz\r\n' +
+      '8zDQeCXj+PHFBm48fEvKhP5sqHNNZsB5cy53y6nVwM2Nb9XBOmVajX2kUSgQE3GQ\r\n' +
+      'HdCZE45Gx9FNP+tG6fYRnOx33ABnJdYwcN4s7xNwBXlTFp2t4CLWPDjwXUSBPudh\r\n' +
+      '2Hy/IzXic86kMmpl09WvPY89eFQ9o1laR4y7M5vnx+GMpCGkxmYZBbOZIGESVoy0\r\n' +
+      '70R7mkVJDFpPQg8FONTNzJki4ggZ2osWBy9fHbE1DvM+MqZe+4zpikjeMwoqmsK4\r\n' +
+      'flvcaautoiwLChpiG0/tjMw13JLPMW3ZMwQDfZXYta2ngT35X2iKcgZTykNSeVJe\r\n' +
+      'bB+ABC1Q9+R9/xlmlrBIUzzZHjNWr2FqDfDvbIIhURYmOqojtncOCttvEg2BUKSU\r\n' +
+      'DdHwTay9R34YmeM6GjzjAcJWY5PJUy+kYnD5Drkp0CNL3LSxoCuQEMqudFz/fMU/\r\n' +
+      'C3PogT6Ncnkr1FVu4uvs3ujG2ufu2YaGrLcYw2/N1yOZJWnnz07goD94VtyojMNc\r\n' +
+      'WTmKni3mHDobNYwHWiRW+g1vxptOH+u5efUlDuz9nnn6cOnqR73Xuht3wDOpyn/N\r\n' +
+      'VfvhZLRa3xeTTVHFqQFU+oqPjTV9H6y58zWpGhu8HOvsBcMmU/FQS6mfK7ebPGGi\r\n' +
+      'jboKbwLpHYFewi01tYgfqwn6hMMrzYPjJY1tsYJ8NIAsmRHkG70t70PVeTQ8cJoC\r\n' +
+      'Fm2IFDsZV/iTDdrBqvRcyBo0XmONAQQKr7rk/90eM4fd8nxGm/cAp/67NotSWQHa\r\n' +
+      'ZFdxhOPSBr6VBiS8SAfF1rIra8funxtQ5Yk04FPjsVotkm2nkMt4gntoM2b3w23Q\r\n' +
+      'GBaNcyPikhkQ8UC80Fbz6UzyLBKbZqCDI/GSa1A4BSvp0vy1pndHzrynyytF4t80\r\n' +
+      'r3I7e0M0SEHWYJFGmQ9szh3cXePvk0p5KJIu1BzPH6AoIK0dNRXQXAINnsxmpkeJ\r\n' +
+      '7pAkz0rIVxZ4SyH4TrZcXxnVJ0Gte9kd/95XSEZDyvT9Arhs/0jHzotxaua6wpK3\r\n' +
+      'JFF4BEmRPE7U3PsPJQN1fm6mqOdmaCE0UrnLhaMf8uMzYOoXVV8A5eRIDtgJ3X8V\r\n' +
+      'k6UkNbDt8mVlctLdkNM9tKkClaF4JnvyYYX16HS5sAJiZnM8vW46nh4KsYIVRqAf\r\n' +
+      'DaAeJzxRTSInaW52tuDqrBPVnl5XiAKbrved1fOUSSorI+SptHzaHcIH20h2DuSJ\r\n' +
+      'ryQnLseZ+F3sb7wdAUtQb6eMNvu7L3s1vBxKqKKlwAVuZEqQI/GT/5WAB34iul3U\r\n' +
+      'hAZZX0xKfweRp27xLRyUiqGFAsOaoDIwRiDhVKJZVCwIa3dSKCW8jvmC+EaeSyKG\r\n' +
+      'Wx7gGnJm9XovdI1hi/zHM60ABejiMnDeAACcvsCJqKXE/9YDFQF+yW30OSZ2AOUL\r\n' +
+      'UWnyD493R347W2oPzV1HbYLd//2gIQFFeMDv0AWfJGv4K0JkZ/pGpaPAoee6Pd1C\r\n' +
+      'OjcxbWhvbEwXDtVRFztCjgNd/rp4t+YQ9QbMczK3HplpMyYjIs0WdTU3gNWqmTEf\r\n' +
+      'guOokh7tqlOHQso0gg3ax65vc2k9V2yLJz2CDkVkATKpJOjV4sNWGPnB4129xact\r\n' +
+      'p9JfGDAWniAE4cYW/etNTXhNWJTzkSlb5Ad5JPPQ4p/lB97Xr/Krwjp1o3h2JTgC\r\n' +
+      'IBfqb9g7WQ/B8EL0AwnoHxPDTdXAHOCiUr0y1M1w36thr56AVR97/R02k2XI3dxv\r\n' +
+      'oS/bCgNtFFSao2O7uANqtU/SMHMl0BrR8dk+4924Wu0m06iNDZB8NU0jU5bqxcW6\r\n' +
+      'wzf/rjqwIndehfpH7MkeCk6rM0JiVku/EKoCfg9DOAA2rLIiyWO2+mm5UWiT60a0\r\n' +
+      'kmGwwrAxduugMnfVdb5fI8F+IyXYCH8Iwi6qpFvSLm4F/++0WP6pD1Xov6cRu9Eq\r\n' +
+      'nQ4FcCFQJ62ymKlZ0+qZ1ywftKTRwNNlPfZezkqJm17sDI02AUAjGotxrSdDfca5\r\n' +
+      'ViRxq+HJiQGVCUo4fEl4iMzSWaBLeQr9nSijB76dyq1e89NMXS0L3Uo6B7gbKm2i\r\n' +
+      'AjRTUEN2LIGM7TiRC4kZRRMrgVcBBDAtuyY/sMDZ6bUageLXlAPSGZ+VY/a+usok\r\n' +
+      'pxP+U88X7mkxuvvPIG7yKaxymdB993pRaVvLuPVcZRDmXIFrTSP5wxejRQpIvwNR\r\n' +
+      'UeYwGQs1gAuM3l6N7trX99j6WBzZr09YRVPgehh5N3s/omrEMDMcExlmAdVOYNij\r\n' +
+      'UN5NOZgPZrHTev4BtZa53FKttvGT9Ly9iLtle218tQyJRK7UQ/APZJzidpcy3p/x\r\n' +
+      'U9AgXG9+horGLG4/HAmpZh4VH+8wXpiUxsC2rXLb0cAoFg03gStLvqXU93UU6KSn\r\n' +
+      'xC0FYZZAqeFDdKbk4IMirklafEu+j45I+57RiCr7mpOyDI4o5FItWMzSxFo06ciw\r\n' +
+      'aUT4eQf+pUFrBz0yUvgJArh3+VZdRhd8vycuxrYgfp9q4H1n2hOEOi/eeQCuJH36\r\n' +
+      'RnAkToyRYwCepD3di2tf5FL2cW2DPMj69o7dIUHEn76SKVtgwmv5Q86rBWTecAn1\r\n' +
+      'qkUXMst6qxyZCqHMsrQ0Bf7lcB9nSTvPXHzbJjLg0QRYi4qZzU46Vmo5bLw0l8R/\r\n' +
+      '66Wyv+OIastQdCB6S1JtRnE2zvR7nRA/TgfmbJBklgEUY9KeyRzh1Vkp7aykuMXV\r\n' +
+      '9bsND+1swzKgqTGxCyMMqIP6OQsr9AVlO4MsR8XCTOY4F/dTaCRHWXC/uvtuar/y\r\n' +
+      '8vFQeaUPSR10+XGxYb7tnaaBqdVy9MMuwz7Y3jYgvbfxku6aXJMyWFBRqCRskOZa\r\n' +
+      'GQOMmb0j9QH/bl6goHBfCJjSSU+vkVytQf7ZtWyD+k4+R3X+nQEex0Eb+2nfzh3i\r\n' +
+      'ZHSO7cqRz12/B8CmQ75L8suRcRrqINMdAZfmARp5s0UtmHYKbOcrxd4l625rUwTJ\r\n' +
+      't0vih8+BK6k1F6oT1kCR6ZyfIHhh8dn22SYJAQFW3+WZsaPjLgkh0ihcyfhLfKMC\r\n' +
+      'K3YvF/dt9rQDorwNwp5+xiuGUrwk7SLbc7wmNCFiD5nER3AhUSuGzQLfZzjeqYgK\r\n' +
+      'Wge2QCPwtwzaHNp51c5QMvKqQfsg12P81qs3Jl/j+xKpzLh2vLYlnq8OuFd3lR6x\r\n' +
+      'q0Rya6j4o+AqW/v1CJBRhS0qXTW/bHvPm8uU16Uw9W4AGPnISbLQh5sfOKkKgNN/\r\n' +
+      'jTogehgId2rZ1VfhW7n9xvPkk2NEt+YmXHn7EuPri6GXPIDhaLWLaSpa8PYW+jxx\r\n' +
+      'T0CDjYQkT/Q/TfuX3yzGHXKhMInKxjqihd1RQ2OIBLBF8/1UFNLM82XntXt2TJXK\r\n' +
+      'kUQYAIJxH23h9ZBH2K3T2gNjOqLmiqE0C4QEW8xNO75TWiYm8j+sX2LmdYmXZP8C\r\n' +
+      'iMlyE2shMVriN3t457D8S5a1aEvATDFxM4YL5k5OsZ6HrQ6PrnzZfrWXh5OxoxAU\r\n' +
+      '+FCXxpRi6lwY3yNi3kUteexRLZGrEz2FRPemDLsevShRqnsy/0OA/05TA6JxLVpd\r\n' +
+      'Dd7ZWUBcIJZ7lQKMzfCAdWR20J7ngEuiUksQDo5h9/727aO/fbVh+aLVYY1EF+0p\r\n' +
+      '8gbM3/hyoGd8pujWqU1U7jLQACAp5zsy7xvnbiXYl42SaF1PFUP5aZrAPBcj0Fru\r\n' +
+      't8SnPjys2JE172lCkQQOBglanklkpRiBDWYxG8josUyASo7EzddOneLNoMNl8+ZO\r\n' +
+      'ZZYN6BRIioChYDsrrPZiootTU5DYC8a0/AcDsV6PQ48SlInCKtuAOi8nHJDVUzBI\r\n' +
+      'QkDd13kAeIFEMOJUV17xh7eLpbe10bv1B8zUiMbvBTzWPXZHEbuNlWiGy960J4t3\r\n' +
+      'x6NGEAfIjYg9+aMCf7uiEWd48s+nrKWymn7Ewg7llyMfK2Vsa9PVMilopGx42y51\r\n' +
+      'HMIzSV4TjOxSAJmXFZs55w57Rqjx3+LP9P7Ilpde4Lh35hD6yX5hZW+gnQs+B/j8\r\n' +
+      'DkBDeIYtMSz4tHqiK6rBUD/KnNUYYmOOGUi/bPyS4TH0ycbSFp1xx+rS/86Uh8YK\r\n' +
+      'wSOVkKvL2VhGE5G0RSSvYLUkEPcjA8K+EaHf8aCWpnGmpr3rT7F00JFhmH/kDXcU\r\n' +
+      'rtatu8Lniqm0sIV84nVEqHF9Vgz1D2d2/VYfLWlMDM5Mb8IWVUi8fjNFQf32bTCZ\r\n' +
+      'ZYTNUSushCwwpo2R8akkURsev+zstIzw73MGldj2AJ6y/0h51Z4dpQuJbwsKIw4g\r\n' +
+      '5MH42cM4PwiQ7hpqDeGLoyfeAMRFnme/HZCsgBCv247KXdpuYolORXBwjiqhlXYl\r\n' +
+      '6W5aUXp7H+Idz+ahq+nEdsGR57lX1dCC731i8x7/0fl7LEAPGCgr3A0UqTesBKqV\r\n' +
+      '5iq03xmxLhXEyv5QJVPCmG2067Wuoi9hMbXWb/IuX6TV2GACuZ54x9ftWtrPtZ7J\r\n' +
+      'bJEst/IK1SvODlNpk3Z8jcx8YFS7RzjrI3CuVrn45HXF5yHlzwiyBnaFiuBXaDFk\r\n' +
+      'kFGnTIxDrDfBsxCN7v3snuf+eW41SaXv8BHAvi4A+cv5vpSduEGY+aZWdgLDsnxw\r\n' +
+      '+zU5GUhNuT28YKEYzyTnMTdo/QL1KZkFqRDqANeRK3V24OaxHt6sbxYuRLGphytc\r\n' +
+      'uUnB6ICpHgwASejiJY/hWhm5PLI3jxdXAa7XOg7asESz1yo7FrJIwW7UlnNBOneA\r\n' +
+      'yuYFdB0usNx+E63hsw+TJ9Sg0+t+mG2+Fr1hE2qEahF2BrrB9LW0xuTXmAeW2qHp\r\n' +
+      'cOVLJigo9QsEy3Y/sPuDJC0z9MnsKefglpSZyGBxkpKtVN7ePHl/hmMBRD6W1aZ0\r\n' +
+      '8bdl0Ljj6SoT9DB8qqyUX3Km/5xSWguvp2hMa1s/J+dJAzOOGx9P94QOgggrImOR\r\n' +
+      'yhMa/3i5qA9QPzT0ivMtQwS5HaGL6Hjv6jkmK1FzfCoOE8d6+9AuhvvbfZs3c0Wf\r\n' +
+      '31F5e09s6fPqXTk3Dw6TsiED+NjtTTywPEaNgjldpPjZPBpAl6pNx/i9KghBmaCG\r\n' +
+      'LDsvFJ/BqZf1qYFKE47Ozf8jQ4b+ZgU37awZAKERnoEvPdJ3gv5H+pyjbYbacLG4\r\n' +
+      '2jF/pRzhiF0eRBuqY/5DrgMe1dkI9TNvBFzsX4YFOxZWca/kc26JhCajuH8MaTyW\r\n' +
+      'LzOeIg6QKane6HBBxRvoOBMIa40oBhffbOi5FKukKUFS3xlPL3EwdS/aZK61vCR2\r\n' +
+      'NPS7Y/d2vk80aNVRZAm2FBcmBWF6q7A825S7HqwM1izmlmqC6yWYXGofP8PuYfww\r\n' +
+      'eWW5rm+3URjcRM54K5Ob7rfKu3q7zUwUAB6R7YM9pgeDbaARyE7mB0MmpB+3UqO8\r\n' +
+      'F5heKtELqIskZGAiCKxGPKERoHPItKTV77ZCZ+ql0FjlJSrXVZ1P/9i/BiwdYmij\r\n' +
+      'vhjCEtDcHWPXXIra6Hf5hTIUJ7conZ9ldGhHliV6Rso7ST1FGIsqrgYDyt1/+Vo4\r\n' +
+      'hNBaUhWOHh65EKRblCW04v71KyaL8ms7Pevgcz4NZFtUwv3v2qI+OqdWBFFbc9Lr\r\n' +
+      'cfiyt5XbZeUD4GiI5/wDVk0b07ev7xyoedeB7GvXgmb13D1vCtHYubeDyI+V7zlM\r\n' +
+      'GXPvCkIPhj34fK6vhtHJIfHL3+8vf6emd7h4Ziakod9G0HYJTKbugpCmi6ejW8G9\r\n' +
+      'X5Kzrn9c8HD7bNCUtwNFV0unoZUN3ReVAOLNn2N0LUfHBrlq/XwseHovUbzSomYT\r\n' +
+      'Xtr/w+tiLSMSRLsJzAu0LJHgNtYPsPIavpim0OLTPg7JBmnzWoyEFCXcLvjNry6c\r\n' +
+      'yCgA4RgfmBcJzXS1Uyf/TUM9IFoeTbGo9dIziygUdWXxrUzx2Uyak53xZXEX82cB\r\n' +
+      'kC/v1+VCq668xgthc9pEEHIsqxKuRCUXj53xYThI5gSJke3XYrCdk3R8rh8FdkkQ\r\n' +
+      'E/4WFpZ8kqraFXSYlfYvGHYd31cbJoSxjTIISd5US85KaOH2n3HN0d017xfwaSqS\r\n' +
+      'I1l1iutPvcc+wxydp7On+uQAP4GiV1uPmuN0s0lu81j7ye9nS+fjxlXiukHQu1mF\r\n' +
+      'c5IdEASgborfk+mrVpl/hpeLJH4LZIGPaZgr3KDBZPDMgqDCXBphL+GjJYPXyW7I\r\n' +
+      't3QRCKMTNHCO7E3e7eet7k2ADSjrN1eZuzo7FxCU6cv+oCQUWPzaRYWb6gzr2QV4\r\n' +
+      'snvwM2sGc0Mkg1QnJAzT6zrtfVZ2uh2VwkN93u8KxwiiCRn53rHn46uW1djNHmIe\r\n' +
+      '4E2vS4IWoCmy59lGxV6UEfsjEGxC+pDv33xX69aDf8vN6VON8B4ooHwdg+GMe2Us\r\n' +
+      'N7sQkhf1ykdR0tmJnG8yr0DfGfxbcJArEv8wcZh89M0oOY7iKx/hq4n4DSVHLmDg\r\n' +
+      'obV4S2+c5aRrVFWQiw+/OjA9MCEwCQYFKw4DAhoFAAQUXolDwewLkmOH6dGcPdhJ\r\n' +
+      'JeUrAz0EFHRZbCAQ2bUo5B8DAFM8VJLi/+A2AgIEAA=='
+  };
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/forge'
+  ], function(FORGE) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      FORGE
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/forge'));
+}
+
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/pkcs7.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/pkcs7.js
new file mode 100644
index 0000000..2c4e793
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/pkcs7.js
@@ -0,0 +1,350 @@
+(function() {
+
+function Tests(ASSERT, PKCS7, PKI, AES, DES, UTIL) {
+  var _pem = {
+    p7: '-----BEGIN PKCS7-----\r\n' +
+      'MIICTgYJKoZIhvcNAQcDoIICPzCCAjsCAQAxggHGMIIBwgIBADCBqTCBmzELMAkG\r\n' +
+      'A1UEBhMCREUxEjAQBgNVBAgMCUZyYW5jb25pYTEQMA4GA1UEBwwHQW5zYmFjaDEV\r\n' +
+      'MBMGA1UECgwMU3RlZmFuIFNpZWdsMRIwEAYDVQQLDAlHZWllcmxlaW4xFjAUBgNV\r\n' +
+      'BAMMDUdlaWVybGVpbiBERVYxIzAhBgkqhkiG9w0BCQEWFHN0ZXNpZUBicm9rZW5w\r\n' +
+      'aXBlLmRlAgkA1FQcQNg14vMwDQYJKoZIhvcNAQEBBQAEggEAJhWQz5SniCd1w3A8\r\n' +
+      'uKVZEfc8Tp21I7FMfFqou+UOVsZCq7kcEa9uv2DIj3o7zD8wbLK1fuyFi4SJxTwx\r\n' +
+      'kR0a6V4bbonIpXPPJ1f615dc4LydAi2tv5w14LJ1Js5XCgGVnkAmQHDaW3EHXB7X\r\n' +
+      'T4w9PR3+tcS/5YAnWaM6Es38zCKHd7TnHpuakplIkwSK9rBFAyA1g/IyTPI+ktrE\r\n' +
+      'EHcVuJcz/7eTlF6wJEa2HL8F1TVWuL0p/0GsJP/8y0MYGdCdtr+TIVo//3YGhoBl\r\n' +
+      'N4tnheFT/jRAzfCZtflDdgAukW24CekrJ1sG2M42p5cKQ5rGFQtzNy/n8EjtUutO\r\n' +
+      'HD5YITBsBgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBBmlpfy3WrYj3uWW7+xNEiH\r\n' +
+      'gEAm2mfSF5xFPLEqqFkvKTM4w8PfhnF0ehmfQNApvoWQRQanNWLCT+Q9GHx6DCFj\r\n' +
+      'TUHl+53x88BrCl1E7FhYPs92\r\n' +
+      '-----END PKCS7-----\r\n',
+    certificate: '-----BEGIN CERTIFICATE-----\r\n' +
+      'MIIDtDCCApwCCQDUVBxA2DXi8zANBgkqhkiG9w0BAQUFADCBmzELMAkGA1UEBhMC\r\n' +
+      'REUxEjAQBgNVBAgMCUZyYW5jb25pYTEQMA4GA1UEBwwHQW5zYmFjaDEVMBMGA1UE\r\n' +
+      'CgwMU3RlZmFuIFNpZWdsMRIwEAYDVQQLDAlHZWllcmxlaW4xFjAUBgNVBAMMDUdl\r\n' +
+      'aWVybGVpbiBERVYxIzAhBgkqhkiG9w0BCQEWFHN0ZXNpZUBicm9rZW5waXBlLmRl\r\n' +
+      'MB4XDTEyMDMxODIyNTc0M1oXDTEzMDMxODIyNTc0M1owgZsxCzAJBgNVBAYTAkRF\r\n' +
+      'MRIwEAYDVQQIDAlGcmFuY29uaWExEDAOBgNVBAcMB0Fuc2JhY2gxFTATBgNVBAoM\r\n' +
+      'DFN0ZWZhbiBTaWVnbDESMBAGA1UECwwJR2VpZXJsZWluMRYwFAYDVQQDDA1HZWll\r\n' +
+      'cmxlaW4gREVWMSMwIQYJKoZIhvcNAQkBFhRzdGVzaWVAYnJva2VucGlwZS5kZTCC\r\n' +
+      'ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMsAbQ4fWevHqP1K1y/ewpMS\r\n' +
+      '3vYovBto7IsKBq0v3NmC2kPf3NhyaSKfjOOS5uAPONLffLck+iGdOLLFia6OSpM6\r\n' +
+      '0tyQIV9lHoRh7fOEYORab0Z+aBUZcEGT9yotBOraX1YbKc5f9XO+80eG4XYvb5ua\r\n' +
+      '1NHrxWqe4w2p3zGJCKO+wHpvGkbKz0nfu36jwWz5aihfHi9hp/xs8mfH86mIKiD7\r\n' +
+      'f2X2KeZ1PK9RvppA0X3lLb2VLOqMt+FHWicyZ/wjhQZ4oW55ln2yYJUQ+adlgaYn\r\n' +
+      'PrtnsxmbTxM+99oF0F2/HmGrNs8nLZSva1Vy+hmjmWz6/O8ZxhiIj7oBRqYcAocC\r\n' +
+      'AwEAATANBgkqhkiG9w0BAQUFAAOCAQEAvfvtu31GFBO5+mFjPAoR4BlzKq/H3EPO\r\n' +
+      'qS8cm/TjHgDRALwSnwKYCFs/bXqE4iOTD6otV4TusX3EPbqL2vzZQEcZn6paU/oZ\r\n' +
+      'ZVXwQqMqY5tf2teQiNxqxNmSIEPRHOr2QVBVIx2YF4Po89KGUqJ9u/3/10lDqRwp\r\n' +
+      'sReijr5UKv5aygEcnwcW8+Ne4rTx934UDsutKG20dr5trZfWQRVS9fS9CFwJehEX\r\n' +
+      'HAMUc/0++80NhfQthmWZWlWM1R3dr4TrIPtWdn5z0MtGeDvqBk7HjGrhcVS6kAsy\r\n' +
+      'Z9y/lfLPjBuxlQAHztEJCWgI4TW3/RLhgfg2gI1noM2n84Cdmisfkg==\r\n' +
+      '-----END CERTIFICATE-----\r\n',
+    privateKey: '-----BEGIN RSA PRIVATE KEY-----\r\n' +
+      'MIIEowIBAAKCAQEAywBtDh9Z68eo/UrXL97CkxLe9ii8G2jsiwoGrS/c2YLaQ9/c\r\n' +
+      '2HJpIp+M45Lm4A840t98tyT6IZ04ssWJro5KkzrS3JAhX2UehGHt84Rg5FpvRn5o\r\n' +
+      'FRlwQZP3Ki0E6tpfVhspzl/1c77zR4bhdi9vm5rU0evFap7jDanfMYkIo77Aem8a\r\n' +
+      'RsrPSd+7fqPBbPlqKF8eL2Gn/GzyZ8fzqYgqIPt/ZfYp5nU8r1G+mkDRfeUtvZUs\r\n' +
+      '6oy34UdaJzJn/COFBnihbnmWfbJglRD5p2WBpic+u2ezGZtPEz732gXQXb8eYas2\r\n' +
+      'zyctlK9rVXL6GaOZbPr87xnGGIiPugFGphwChwIDAQABAoIBAAjMA+3QvfzRsikH\r\n' +
+      'zTtt09C7yJ2yNjSZ32ZHEPMAV/m1CfBXCyL2EkhF0b0q6IZdIoFA3g6xs4UxYvuc\r\n' +
+      'Q9Mkp2ap7elQ9aFEqIXkGIOtAOXkZV4QrEH90DeHSfax7LygqfD5TF59Gg3iAHjh\r\n' +
+      'B3Qvqg58LyzJosx0BjLZYaqr3Yv67GkqyflpF/roPGdClHpahAi5PBkHiNhNTAUU\r\n' +
+      'LJRGvMegXGZkUKgGMAiGCk0N96OZwrinMKO6YKGdtgwVWC2wbJY0trElaiwXozSt\r\n' +
+      'NmP6KTQp94C7rcVO6v1lZiOfhBe5Kc8QHUU+GYydgdjqm6Rdow/yLHOALAVtXSeb\r\n' +
+      'U+tPfcECgYEA6Qi+qF+gtPincEDBxRtoKwAlRkALt8kly8bYiGcUmd116k/5bmPw\r\n' +
+      'd0tBUOQbqRa1obYC88goOVzp9LInAcBSSrexhVaPAF4nrkwYXMOq+76MiH17WUfQ\r\n' +
+      'MgVM2IB48PBjNk1s3Crj6j1cxxkctqmCnVaI9HlU2PPZ3xjaklfv/NsCgYEA3wH8\r\n' +
+      'mehUhiAp7vuhd+hfomFw74cqgHC9v0saiYGckpMafh9MJGc4U5GrN1kYeb/CFkSx\r\n' +
+      '1hOytD3YBKoaKKoYagaMQcjxf6HnEF0f/5OiQkUQpWmgC9lNnE4XTWjnwqaTS5L9\r\n' +
+      'D+H50SiI3VjHymGXTRJeKpAIwV74AxxrnVofqsUCgYAwmL1B2adm9g/c7fQ6yatg\r\n' +
+      'hEhBrSuEaTMzmsUfNPfr2m4zrffjWH4WMqBtYRSPn4fDMHTPJ+eThtfXSqutxtCi\r\n' +
+      'ekpP9ywdNIVr6LyP49Ita6Bc+mYVyU8Wj1pmL+yIumjGM0FHbL5Y4/EMKCV/xjvR\r\n' +
+      '2fD3orHaCIhf6QvzxtjqTwKBgFm6UemXKlMhI94tTsWRMNGEBU3LA9XUBvSuAkpr\r\n' +
+      'ZRUwrQssCpXnFinBxbMqXQe3mR8emrM5D8En1P/jdU0BS3t1kP9zG4AwI2lZHuPV\r\n' +
+      'ggbKBS2Y9zVtRKXsYcHawM13+nIA/WNjmAGJHrB45UJPy/HNvye+9lbfoEiYKdCR\r\n' +
+      'D4bFAoGBAIm9jcZkIwLa9kLAWH995YYYSGRY4KC29XZr2io2mog+BAjhFt1sqebt\r\n' +
+      'R8sRHNiIP2mcUECMOcaS+tcayi+8KTHWxIEed9qDmFu6XBbePfe/L6yxPSagcixH\r\n' +
+      'BK0KuK/fgTPvZCmIs8hUIC+AxhXKnqn4fIWoO54xLsALc0gEjs2d\r\n' +
+      '-----END RSA PRIVATE KEY-----\r\n',
+    encryptedData: '-----BEGIN PKCS7-----\r\n' +
+      'MIGHBgkqhkiG9w0BBwagejB4AgEAMHMGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQI\r\n' +
+      'upMFou5X3DWAUAqObuHSlewM0ZtHzWk9MAmtYb7MSb//OBMKVfLCdbmrS5BpKm9J\r\n' +
+      'gzwiDR5Od7xgfkqasLS2lOdKAvJ5jZjjTpAyrjBKpShqK9gtXDuO0zH+\r\n' +
+      '-----END PKCS7-----\r\n',
+    p7IndefiniteLength: '-----BEGIN PKCS7-----\r\n' +
+      'MIAGCSqGSIb3DQEHA6CAMIACAQAxggHGMIIBwgIBADCBqTCBmzELMAkGA1UEBhMC\r\n' +
+      'REUxEjAQBgNVBAgMCUZyYW5jb25pYTEQMA4GA1UEBwwHQW5zYmFjaDEVMBMGA1UE\r\n' +
+      'CgwMU3RlZmFuIFNpZWdsMRIwEAYDVQQLDAlHZWllcmxlaW4xFjAUBgNVBAMMDUdl\r\n' +
+      'aWVybGVpbiBERVYxIzAhBgkqhkiG9w0BCQEWFHN0ZXNpZUBicm9rZW5waXBlLmRl\r\n' +
+      'AgkA1FQcQNg14vMwDQYJKoZIhvcNAQEBBQAEggEAlWCH+E25c4jfff+m0eAxxMmE\r\n' +
+      'WWaftdsk4ZpAVAr7HsvxJ35bj1mhwTh7rBTg929JBKt6ZaQ4I800jCNxD2O40V6z\r\n' +
+      'lB7JNRqzgBwfeuU2nV6FB7v1984NBi1jQx6EfxOcusE6RL/63HqJdFbmq3Tl55gF\r\n' +
+      'dm3JdjmHbCXqwPhuwOXU4yhkpV1RJcrYhPLe3OrLAH7ZfoE0nPJPOX9HPTZ6ReES\r\n' +
+      'NToS7I9D9k7rCa8fAP7pgjO96GJGBtCHG1VXB9NX4w+xRDbgVPOeHXqqxwZhqpW2\r\n' +
+      'usBU4+B+MnFLjquOPoySXFfdJFwTP61TPClUdyIne5FFP6EYf98mdtnkjxHo1TCA\r\n' +
+      'BgkqhkiG9w0BBwEwFAYIKoZIhvcNAwcECFNtpqBmU3M9oIAESM+yyQLkreETS0Kc\r\n' +
+      'o01yl6dqqNBczH5FNTK88ypz38/jzjo47+DURlvGzjHJibiDsCz9KyiVmgbRrtvH\r\n' +
+      '08rfnMbrU+grCkkx9wQI1GnLrYhr87oAAAAAAAAAAAAA\r\n' +
+      '-----END PKCS7-----\r\n',
+    p73des: '-----BEGIN PKCS7-----\r\n' +
+      'MIICTQYJKoZIhvcNAQcDoIICPjCCAjoCAQAxggHGMIIBwgIBADCBqTCBmzELMAkG\r\n' +
+      'A1UEBhMCREUxEjAQBgNVBAgMCUZyYW5jb25pYTEQMA4GA1UEBwwHQW5zYmFjaDEV\r\n' +
+      'MBMGA1UECgwMU3RlZmFuIFNpZWdsMRIwEAYDVQQLDAlHZWllcmxlaW4xFjAUBgNV\r\n' +
+      'BAMMDUdlaWVybGVpbiBERVYxIzAhBgkqhkiG9w0BCQEWFHN0ZXNpZUBicm9rZW5w\r\n' +
+      'aXBlLmRlAgkA1FQcQNg14vMwDQYJKoZIhvcNAQEBBQAEggEAS6K+sQvdKcK6YafJ\r\n' +
+      'maDPjBzyjf5jtBgVrFgBXTCRIp/Z2zAXa70skfxhbwTgmilYTacA7jPGRrnLmvBc\r\n' +
+      'BjhyCKM3dRUyYgh1K1ka0w1prvLmRk6Onf5df1ZQn3AJMIujJZcCOhbV1ByLInve\r\n' +
+      'xn02KNHstGmdHM/JGyPCp+iYGprhUozVSpNCKS+R33EbsT0sAxamfqdAblT9+5Qj\r\n' +
+      '4CABvW11a1clPV7STwBbAKbZaLs8mDeoWP0yHvBtJ7qzZdSgJJA2oU7SDv4icwEe\r\n' +
+      'Ahccbe2HWkLRw8G5YG9XcWx5PnQQhhnXMxkLoSMIYxItyL/cRORbpDohd+otAo66\r\n' +
+      'WLH1ODBrBgkqhkiG9w0BBwEwFAYIKoZIhvcNAwcECD5EWJMv1fd7gEj1w3WM1KsM\r\n' +
+      'L8GDk9JoqA8t9v3oXCT0nAMXoNpHZMnv+0UHHVljlSXBTQxwUP5VMY/ddquJ5O3N\r\n' +
+      'rDEqqJuHB+KPIsW1kxrdplU=\r\n' +
+      '-----END PKCS7-----\r\n'
+  };
+
+  describe('pkcs7', function() {
+    it('should import message from PEM', function() {
+      var p7 = PKCS7.messageFromPem(_pem.p7);
+
+      ASSERT.equal(p7.type, PKI.oids.envelopedData);
+      ASSERT.equal(p7.version, 0);
+
+      ASSERT.equal(p7.recipients.length, 1);
+      ASSERT.equal(p7.recipients[0].version, 0);
+      ASSERT.equal(p7.recipients[0].serialNumber, '00d4541c40d835e2f3');
+
+      // Test converted RDN, which is constructed of seven parts.
+      ASSERT.equal(p7.recipients[0].issuer.length, 7);
+      ASSERT.equal(p7.recipients[0].issuer[0].type, '2.5.4.6');
+      ASSERT.equal(p7.recipients[0].issuer[0].value, 'DE');
+      ASSERT.equal(p7.recipients[0].issuer[1].type, '2.5.4.8');
+      ASSERT.equal(p7.recipients[0].issuer[1].value, 'Franconia');
+      ASSERT.equal(p7.recipients[0].issuer[2].type, '2.5.4.7');
+      ASSERT.equal(p7.recipients[0].issuer[2].value, 'Ansbach');
+      ASSERT.equal(p7.recipients[0].issuer[3].type, '2.5.4.10');
+      ASSERT.equal(p7.recipients[0].issuer[3].value, 'Stefan Siegl');
+      ASSERT.equal(p7.recipients[0].issuer[4].type, '2.5.4.11');
+      ASSERT.equal(p7.recipients[0].issuer[4].value, 'Geierlein');
+      ASSERT.equal(p7.recipients[0].issuer[5].type, '2.5.4.3');
+      ASSERT.equal(p7.recipients[0].issuer[5].value, 'Geierlein DEV');
+      ASSERT.equal(p7.recipients[0].issuer[6].type, '1.2.840.113549.1.9.1');
+      ASSERT.equal(p7.recipients[0].issuer[6].value, 'stesie@brokenpipe.de');
+
+      ASSERT.equal(p7.recipients[0].encryptedContent.algorithm, PKI.oids.rsaEncryption);
+      ASSERT.equal(p7.recipients[0].encryptedContent.content.length, 256);
+
+      ASSERT.equal(p7.encryptedContent.algorithm, PKI.oids['aes256-CBC']);
+      ASSERT.equal(p7.encryptedContent.parameter.data.length, 16);  // IV
+    });
+
+    it('should import indefinite length message from PEM', function() {
+      ASSERT.doesNotThrow(function() {
+        var p7 = PKCS7.messageFromPem(_pem.p7IndefiniteLength);
+        ASSERT.equal(p7.type, PKI.oids.envelopedData);
+        ASSERT.equal(p7.encryptedContent.parameter.toHex(), '536da6a06653733d');
+        ASSERT.equal(p7.encryptedContent.content.length(), 80);
+      });
+    });
+
+    it('should find recipient by serial number', function() {
+      var p7 = PKCS7.messageFromPem(_pem.p7);
+      var cert = PKI.certificateFromPem(_pem.certificate);
+
+      var ri = p7.findRecipient(cert);
+      ASSERT.equal(ri.serialNumber, '00d4541c40d835e2f3');
+
+      // modify certificate so it doesn't match recipient any more
+      cert.serialNumber = '1234567890abcdef42';
+      ri = p7.findRecipient(cert);
+      ASSERT.equal(ri, null);
+    });
+
+    it('should aes-decrypt message', function() {
+      var p7 = PKCS7.messageFromPem(_pem.p7);
+      var privateKey = PKI.privateKeyFromPem(_pem.privateKey);
+      p7.decrypt(p7.recipients[0], privateKey);
+
+      // symmetric key must be 32 bytes long (AES 256 key)
+      ASSERT.equal(p7.encryptedContent.key.data.length, 32);
+      ASSERT.equal(
+        p7.content,
+        'Today is Boomtime, the 9th day of Discord in the YOLD 3178\r\n');
+    });
+
+    it('should 3des-decrypt message', function() {
+      var p7 = PKCS7.messageFromPem(_pem.p73des);
+      var privateKey = PKI.privateKeyFromPem(_pem.privateKey);
+      p7.decrypt(p7.recipients[0], privateKey);
+
+      // symmetric key must be 24 bytes long (DES3 key)
+      ASSERT.equal(p7.encryptedContent.key.data.length, 24);
+      ASSERT.equal(
+        p7.content,
+        'Today is Prickle-Prickle, ' +
+        'the 16th day of Discord in the YOLD 3178\r\n');
+    });
+
+    it('should add a recipient', function() {
+      var p7 = PKCS7.createEnvelopedData();
+
+      // initially there should be no recipients
+      ASSERT.equal(p7.recipients.length, 0);
+
+      var cert = PKI.certificateFromPem(_pem.certificate);
+      p7.addRecipient(cert);
+
+      ASSERT.equal(p7.recipients.length, 1);
+      ASSERT.deepEqual(p7.recipients[0].serialNumber, cert.serialNumber);
+      ASSERT.deepEqual(p7.recipients[0].issuer, cert.subject.attributes);
+      ASSERT.deepEqual(p7.recipients[0].encryptedContent.key, cert.publicKey);
+    });
+
+    it('should aes-encrypt a message', function() {
+      var p7 = PKCS7.createEnvelopedData();
+      var cert = PKI.certificateFromPem(_pem.certificate);
+      var privateKey = PKI.privateKeyFromPem(_pem.privateKey);
+
+      p7.addRecipient(cert);
+      p7.content = UTIL.createBuffer('Just a little test');
+
+      // pre-condition, PKCS#7 module should default to AES-256-CBC
+      ASSERT.equal(p7.encryptedContent.algorithm, PKI.oids['aes256-CBC']);
+      p7.encrypt();
+
+      // since we did not provide a key, a random key should have been created
+      // automatically, AES256 requires 32 bytes of key material
+      ASSERT.equal(p7.encryptedContent.key.data.length, 32);
+
+      // furthermore an IV must be generated, AES256 has 16 byte IV
+      ASSERT.equal(p7.encryptedContent.parameter.data.length, 16);
+
+      // content is 18 bytes long, AES has 16 byte blocksize,
+      // with padding that makes 32 bytes
+      ASSERT.equal(p7.encryptedContent.content.data.length, 32);
+
+      // RSA encryption should yield 256 bytes
+      ASSERT.equal(p7.recipients[0].encryptedContent.content.length, 256);
+
+      // rewind Key & IV
+      p7.encryptedContent.key.read = 0;
+      p7.encryptedContent.parameter.read = 0;
+
+      // decryption of the asym. encrypted data should reveal the symmetric key
+      var decryptedKey = privateKey.decrypt(
+        p7.recipients[0].encryptedContent.content);
+      ASSERT.equal(decryptedKey, p7.encryptedContent.key.data);
+
+      // decryption of sym. encrypted data should reveal the content
+      var ciph = AES.createDecryptionCipher(decryptedKey);
+      ciph.start(p7.encryptedContent.parameter);
+      ciph.update(p7.encryptedContent.content);
+      ciph.finish();
+      ASSERT.equal(ciph.output, 'Just a little test');
+    });
+
+    it('should 3des-ede-encrypt a message', function() {
+      var p7 = PKCS7.createEnvelopedData();
+      var cert = PKI.certificateFromPem(_pem.certificate);
+      var privateKey = PKI.privateKeyFromPem(_pem.privateKey);
+
+      p7.addRecipient(cert);
+      p7.content = UTIL.createBuffer('Just a little test');
+      p7.encryptedContent.algorithm = PKI.oids['des-EDE3-CBC'];
+      p7.encrypt();
+
+      // since we did not provide a key, a random key should have been created
+      // automatically, 3DES-EDE requires 24 bytes of key material
+      ASSERT.equal(p7.encryptedContent.key.data.length, 24);
+
+      // furthermore an IV must be generated, DES3 has 8 byte IV
+      ASSERT.equal(p7.encryptedContent.parameter.data.length, 8);
+
+      // content is 18 bytes long, DES has 8 byte blocksize,
+      // with padding that makes 24 bytes
+      ASSERT.equal(p7.encryptedContent.content.data.length, 24);
+
+      // RSA encryption should yield 256 bytes
+      ASSERT.equal(p7.recipients[0].encryptedContent.content.length, 256);
+
+      // rewind Key & IV
+      p7.encryptedContent.key.read = 0;
+      p7.encryptedContent.parameter.read = 0;
+
+      // decryption of the asym. encrypted data should reveal the symmetric key
+      var decryptedKey = privateKey.decrypt(
+        p7.recipients[0].encryptedContent.content);
+      ASSERT.equal(decryptedKey, p7.encryptedContent.key.data);
+
+      // decryption of sym. encrypted data should reveal the content
+      var ciph = DES.createDecryptionCipher(decryptedKey);
+      ciph.start(p7.encryptedContent.parameter);
+      ciph.update(p7.encryptedContent.content);
+      ciph.finish();
+      ASSERT.equal(ciph.output, 'Just a little test');
+    });
+
+    it('should export message to PEM', function() {
+      var p7 = PKCS7.createEnvelopedData();
+      p7.addRecipient(PKI.certificateFromPem(_pem.certificate));
+      p7.content = UTIL.createBuffer('Just a little test');
+      p7.encrypt();
+
+      var pem = PKCS7.messageToPem(p7);
+
+      // convert back from PEM to new PKCS#7 object, decrypt, and test
+      p7 = PKCS7.messageFromPem(pem);
+      p7.decrypt(p7.recipients[0], PKI.privateKeyFromPem(_pem.privateKey));
+      ASSERT.equal(p7.content, 'Just a little test');
+    });
+
+    it('should decrypt encrypted data from PEM', function() {
+      var result = '1f8b08000000000000000b2e494d4bcc5308ce4c4dcfd15130b0b430d4b7343732b03437d05170cc2b4e4a4cced051b034343532d25170492d2d294ecec849cc4b0100bf52f02437000000';
+      var key = 'b96e4a4c0a3555d31e1b295647cc5cfe74081918cb7f797b';
+      key = UTIL.createBuffer(UTIL.hexToBytes(key));
+
+      ASSERT.doesNotThrow(function() {
+        var p7 = PKCS7.messageFromPem(_pem.encryptedData);
+        ASSERT.equal(p7.type, PKI.oids.encryptedData);
+        ASSERT.equal(p7.encryptedContent.algorithm, PKI.oids['des-EDE3-CBC']);
+        ASSERT.equal(p7.encryptedContent.parameter.toHex(), 'ba9305a2ee57dc35');
+        ASSERT.equal(p7.encryptedContent.content.length(), 80);
+
+        p7.decrypt(key);
+        ASSERT.equal(p7.content.toHex(), result);
+      });
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/pkcs7',
+    'forge/pki',
+    'forge/aes',
+    'forge/des',
+    'forge/util'
+  ], function(PKCS7, PKI, AES, DES, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      PKCS7(),
+      PKI(),
+      AES(),
+      DES(),
+      UTIL()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/pkcs7')(),
+    require('../../js/pki')(),
+    require('../../js/aes')(),
+    require('../../js/des')(),
+    require('../../js/util')());
+}
+
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/random.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/random.js
new file mode 100644
index 0000000..efeec2b
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/random.js
@@ -0,0 +1,70 @@
+(function() {
+
+function Tests(ASSERT, RANDOM, UTIL) {
+  var random = RANDOM();
+
+  describe('random', function() {
+    it('should generate 10 random bytes', function() {
+      random.getBytes(16);
+      random.getBytes(24);
+      random.getBytes(32);
+
+      var b = random.getBytes(10);
+      ASSERT.equal(b.length, 10);
+    });
+
+    it('should use a synchronous seed file', function() {
+      var rand = RANDOM();
+      rand.seedFileSync = function(needed) {
+        return UTIL.fillString('a', needed);
+      };
+      var b = rand.getBytes(10);
+      ASSERT.equal(UTIL.bytesToHex(b), '80a7901a239c3e606319');
+    });
+
+    it('should use an asynchronous seed file', function(done) {
+      var rand = RANDOM();
+      rand.seedFile = function(needed, callback) {
+        callback(null, UTIL.fillString('a', needed));
+      };
+      rand.getBytes(10, function(err, b) {
+        ASSERT.equal(err, null);
+        ASSERT.equal(UTIL.bytesToHex(b), '80a7901a239c3e606319');
+        done();
+      });
+    });
+
+    it('should collect some random bytes', function() {
+      var rand = RANDOM();
+      rand.seedFileSync = function(needed) {
+        return UTIL.fillString('a', needed);
+      };
+      rand.collect('bbb');
+      var b = rand.getBytes(10);
+      ASSERT.equal(UTIL.bytesToHex(b), 'ff8d213516047c94ca46');
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/random',
+    'forge/util'
+  ], function(RANDOM, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      RANDOM,
+      UTIL()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/random'),
+    require('../../js/util')());
+}
+
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/rc2.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/rc2.js
new file mode 100644
index 0000000..2acbe7b
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/rc2.js
@@ -0,0 +1,109 @@
+(function() {
+
+function Tests(ASSERT, RC2, UTIL) {
+  describe('rc2', function() {
+    it('should expand a 128-bit key', function() {
+      var key = UTIL.hexToBytes('88bca90e90875a7f0f79c384627bafb2');
+      var expect = '71ab26462f0b9333609d4476e48ab72438c2194b70a47085d84b6af1dc72119023b94fe80aee2b6b45f27f923d9be1570da3ce8b16ad7f78db166ffbc28a836a4392cf0b748085dae4b69bdc2a4679cdfc09d84317016987e0c5b765c91dc612b1f44d7921b3e2c46447508bd2ac02e119e0f42a89c719675da320cf3e8958cd';
+      ASSERT.equal(RC2.expandKey(key).toHex(), expect);
+    });
+
+    it('should expand a 40-bit key', function() {
+      var key = UTIL.hexToBytes('88bca90e90');
+      var expect = 'af136d2243b94a0878d7a604f8d6d9fd64a698fd6ebc613e641f0d1612055ef6cb55966db8f32bfd9246dae99880be8a91433adf54ea546d9daad62db7a55f6c7790aa87ba67de0e9ea9128dfc7ccdddd7c47c33d2bb7f823729977f083b5dc1f5bb09000b98e12cdaaf22f80dcc88c37d2c2fd80402f8a30a9e41d356669471';
+      ASSERT.equal(RC2.expandKey(key, 40).toHex(), expect);
+    });
+
+    it('should rc2-ecb encrypt zeros', function() {
+      var key = UTIL.hexToBytes('88bca90e90875a7f0f79c384627bafb2');
+      var input = new UTIL.createBuffer().fillWithByte(0, 8);
+      var cipher = RC2.startEncrypting(key, null, null);
+      cipher.update(input);
+      cipher.finish();
+      ASSERT.equal(cipher.output.toHex(), '2269552ab0f85ca6e35b3b2ce4e02191');
+    });
+
+    it('should rc2-ecb encrypt: vegan', function() {
+      var key = UTIL.hexToBytes('88bca90e90875a7f0f79c384627bafb2');
+      var input = new UTIL.createBuffer('vegan');
+      var cipher = RC2.startEncrypting(key, null, null);
+      cipher.update(input);
+      cipher.finish();
+      ASSERT.equal(cipher.output.toHex(), '2194adaf4d517e3a');
+    });
+
+    it('should rc2-ecb decrypt: 2194adaf4d517e3a', function() {
+      var key = UTIL.hexToBytes('88bca90e90875a7f0f79c384627bafb2');
+      var input = new UTIL.createBuffer(UTIL.hexToBytes('2194adaf4d517e3a'));
+      var cipher = RC2.startDecrypting(key, null, null);
+      cipher.update(input);
+      cipher.finish();
+      ASSERT.equal(cipher.output.getBytes(), 'vegan');
+    });
+
+    it('should rc2-cbc encrypt: revolution', function() {
+      var key = UTIL.hexToBytes('88bca90e90875a7f0f79c384627bafb2');
+      var iv = new UTIL.createBuffer(UTIL.hexToBytes('0123456789abcdef'));
+      var input = new UTIL.createBuffer('revolution');
+      var cipher = RC2.startEncrypting(key, iv, null);
+      cipher.update(input);
+      cipher.finish();
+      ASSERT.equal(cipher.output.toHex(), '50cfd16e0fd7f20b17a622eb2a469b7e');
+    });
+
+    it('should rc2-cbc decrypt: 50cfd16e0fd7f20b17a622eb2a469b7e', function() {
+      var key = UTIL.hexToBytes('88bca90e90875a7f0f79c384627bafb2');
+      var iv = new UTIL.createBuffer(UTIL.hexToBytes('0123456789abcdef'));
+      var input = new UTIL.createBuffer(
+        UTIL.hexToBytes('50cfd16e0fd7f20b17a622eb2a469b7e'));
+      var cipher = RC2.startDecrypting(key, iv, null);
+      cipher.update(input);
+      cipher.finish();
+      ASSERT.equal(cipher.output, 'revolution');
+    });
+
+    it('should rc2-cbc encrypt w/binary string iv: revolution', function() {
+      var key = UTIL.hexToBytes('88bca90e90875a7f0f79c384627bafb2');
+      var iv = UTIL.hexToBytes('0123456789abcdef');
+      var input = new UTIL.createBuffer('revolution');
+      var cipher = RC2.startEncrypting(key, iv, null);
+      cipher.update(input);
+      cipher.finish();
+      ASSERT.equal(cipher.output.toHex(), '50cfd16e0fd7f20b17a622eb2a469b7e');
+    });
+
+    it('should rc2-cbc decrypt w/binary string iv: 50cfd16e0fd7f20b17a622eb2a469b7e', function() {
+      var key = UTIL.hexToBytes('88bca90e90875a7f0f79c384627bafb2');
+      var iv = UTIL.hexToBytes('0123456789abcdef');
+      var input = new UTIL.createBuffer(
+        UTIL.hexToBytes('50cfd16e0fd7f20b17a622eb2a469b7e'));
+      var cipher = RC2.startDecrypting(key, iv, null);
+      cipher.update(input);
+      cipher.finish();
+      ASSERT.equal(cipher.output, 'revolution');
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/rc2',
+    'forge/util'
+  ], function(RC2, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      RC2(),
+      UTIL()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/rc2')(),
+    require('../../js/util')());
+}
+
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/rsa.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/rsa.js
new file mode 100644
index 0000000..434d7a3
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/rsa.js
@@ -0,0 +1,602 @@
+(function() {
+
+function Tests(ASSERT, PKI, RSA, MD, MGF, PSS, RANDOM, UTIL) {
+  var _pem = {
+    privateKey: '-----BEGIN RSA PRIVATE KEY-----\r\n' +
+      'MIICXQIBAAKBgQDL0EugUiNGMWscLAVM0VoMdhDZEJOqdsUMpx9U0YZI7szokJqQ\r\n' +
+      'NIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMGTkP3VF29vXBo+dLq5e+8VyAy\r\n' +
+      'Q3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGtvnM+z0MYDdKo80efzwIDAQAB\r\n' +
+      'AoGAIzkGONi5G+JifmXlLJdplom486p3upf4Ce2/7mqfaG9MnkyPSairKD/JXvfh\r\n' +
+      'NNWkkN8DKKDKBcVVElPgORYT0qwrWc7ueLBMUCbRXb1ZyfEulimG0R3kjUh7NYau\r\n' +
+      'DaIkVgfykXGSQMZx8FoaT6L080zd+0emKDDYRrb+/kgJNJECQQDoUZoiC2K/DWNY\r\n' +
+      'h3/ppZ0ane2y4SBmJUHJVMPQ2CEgxsrJTxet668ckNCKaOP/3VFPoWC41f17DvKq\r\n' +
+      'noYINNntAkEA4JbZBZBVUrQFhHlrpXT4jzqtO2RlKZzEq8qmFZfEErxOT1WMyyCi\r\n' +
+      'lAQ5gUKardo1Kf0omC8Xq/uO9ZYdED55KwJBALs6cJ65UFaq4oLJiQPzLd7yokuE\r\n' +
+      'dcj8g71PLBTW6jPxIiMFNA89nz3FU9wIVp+xbMNhSoMMKqIPVPC+m0Rn260CQQDA\r\n' +
+      'I83fWK/mZWUjBM33a68KumRiH238v8XyQxj7+C8i6D8G2GXvkigFAehAkb7LZZd+\r\n' +
+      'KLuGFyPlWv3fVWHf99KpAkBQFKk3MRMl6IGJZUEFQe4l5whm8LkGU4acSqv9B3xt\r\n' +
+      'qROkCrsFrMPqjuuzEmyHoQZ64r2PLJg7FOuyhBnQUOt4\r\n' +
+      '-----END RSA PRIVATE KEY-----\r\n',
+    privateKeyInfo: '-----BEGIN PRIVATE KEY-----\r\n' +
+      'MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMvQS6BSI0Yxaxws\r\n' +
+      'BUzRWgx2ENkQk6p2xQynH1TRhkjuzOiQmpA0jCiSJDoSic2dZIyUi/LjoCGeVFif\r\n' +
+      '57N5N5Tt4wZOQ/dUXb29cGj50url77xXIDJDcXMzXAji2ziFEAIXzDqarKBdDuL9\r\n' +
+      'IO7z+tepEa2+cz7PQxgN0qjzR5/PAgMBAAECgYAjOQY42Lkb4mJ+ZeUsl2mWibjz\r\n' +
+      'qne6l/gJ7b/uap9ob0yeTI9JqKsoP8le9+E01aSQ3wMooMoFxVUSU+A5FhPSrCtZ\r\n' +
+      'zu54sExQJtFdvVnJ8S6WKYbRHeSNSHs1hq4NoiRWB/KRcZJAxnHwWhpPovTzTN37\r\n' +
+      'R6YoMNhGtv7+SAk0kQJBAOhRmiILYr8NY1iHf+mlnRqd7bLhIGYlQclUw9DYISDG\r\n' +
+      'yslPF63rrxyQ0Ipo4//dUU+hYLjV/XsO8qqehgg02e0CQQDgltkFkFVStAWEeWul\r\n' +
+      'dPiPOq07ZGUpnMSryqYVl8QSvE5PVYzLIKKUBDmBQpqt2jUp/SiYLxer+471lh0Q\r\n' +
+      'PnkrAkEAuzpwnrlQVqrigsmJA/Mt3vKiS4R1yPyDvU8sFNbqM/EiIwU0Dz2fPcVT\r\n' +
+      '3AhWn7Fsw2FKgwwqog9U8L6bRGfbrQJBAMAjzd9Yr+ZlZSMEzfdrrwq6ZGIfbfy/\r\n' +
+      'xfJDGPv4LyLoPwbYZe+SKAUB6ECRvstll34ou4YXI+Va/d9VYd/30qkCQFAUqTcx\r\n' +
+      'EyXogYllQQVB7iXnCGbwuQZThpxKq/0HfG2pE6QKuwWsw+qO67MSbIehBnrivY8s\r\n' +
+      'mDsU67KEGdBQ63g=\r\n' +
+      '-----END PRIVATE KEY-----\r\n',
+    publicKey: '-----BEGIN PUBLIC KEY-----\r\n' +
+      'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDL0EugUiNGMWscLAVM0VoMdhDZ\r\n' +
+      'EJOqdsUMpx9U0YZI7szokJqQNIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMG\r\n' +
+      'TkP3VF29vXBo+dLq5e+8VyAyQ3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGt\r\n' +
+      'vnM+z0MYDdKo80efzwIDAQAB\r\n' +
+      '-----END PUBLIC KEY-----\r\n'
+  };
+  var _signature =
+    '9200ece65cdaed36bcc20b94c65af852e4f88f0b4fe5b249d54665f815992ac4' +
+    '3a1399e65d938c6a7f16dd39d971a53ca66523209dbbfbcb67afa579dbb0c220' +
+    '672813d9e6f4818f29b9becbb29da2032c5e422da97e0c39bfb7a2e7d568615a' +
+    '5073af0337ff215a8e1b2332d668691f4fb731440055420c24ac451dd3c913f4';
+
+  describe('rsa', function() {
+    it('should generate 512 bit key pair', function() {
+      var pair = RSA.generateKeyPair(512);
+      ASSERT.equal(PKI.privateKeyToPem(pair.privateKey).indexOf('-----BEGIN RSA PRIVATE KEY-----'), 0);
+      ASSERT.equal(PKI.publicKeyToPem(pair.publicKey).indexOf('-----BEGIN PUBLIC KEY-----'), 0);
+
+      // sign and verify
+      var md = MD.sha1.create();
+      md.update('0123456789abcdef');
+      var signature = pair.privateKey.sign(md);
+      ASSERT.ok(pair.publicKey.verify(md.digest().getBytes(), signature));
+    });
+
+    it('should generate the same 512 bit key pair', function() {
+      var prng = RANDOM.createInstance();
+      prng.seedFileSync = function(needed) {
+        return UTIL.fillString('a', needed);
+      };
+      var pair = RSA.generateKeyPair(512, {prng: prng});
+      var pem = {
+        privateKey: PKI.privateKeyToPem(pair.privateKey),
+        publicKey: PKI.publicKeyToPem(pair.publicKey)
+      };
+      ASSERT.equal(pem.privateKey.indexOf('-----BEGIN RSA PRIVATE KEY-----'), 0);
+      ASSERT.equal(pem.publicKey.indexOf('-----BEGIN PUBLIC KEY-----'), 0);
+
+      // sign and verify
+      var md = MD.sha1.create();
+      md.update('0123456789abcdef');
+      var signature = pair.privateKey.sign(md);
+      ASSERT.ok(pair.publicKey.verify(md.digest().getBytes(), signature));
+
+      // create same key pair by using same PRNG
+      prng = RANDOM.createInstance();
+      prng.seedFileSync = function(needed) {
+        return UTIL.fillString('a', needed);
+      };
+      var pair2 = RSA.generateKeyPair(512, {prng: prng});
+      var pem2 = {
+        privateKey: PKI.privateKeyToPem(pair2.privateKey),
+        publicKey: PKI.publicKeyToPem(pair2.publicKey)
+      };
+      ASSERT.equal(pem.privateKey, pem2.privateKey);
+      ASSERT.equal(pem.publicKey, pem2.publicKey);
+    });
+
+    it('should convert private key to/from PEM', function() {
+      var privateKey = PKI.privateKeyFromPem(_pem.privateKey);
+      ASSERT.equal(PKI.privateKeyToPem(privateKey), _pem.privateKey);
+    });
+
+    it('should convert public key to/from PEM', function() {
+      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
+      ASSERT.equal(PKI.publicKeyToPem(publicKey), _pem.publicKey);
+    });
+
+    it('should convert a PKCS#8 PrivateKeyInfo to/from PEM', function() {
+      var privateKey = PKI.privateKeyFromPem(_pem.privateKeyInfo);
+      var rsaPrivateKey = PKI.privateKeyToAsn1(privateKey);
+      var pki = PKI.wrapRsaPrivateKey(rsaPrivateKey);
+      ASSERT.equal(PKI.privateKeyInfoToPem(pki), _pem.privateKeyInfo);
+    });
+
+    (function() {
+      var algorithms = ['aes128', 'aes192', 'aes256', '3des', 'des'];
+      for(var i = 0; i < algorithms.length; ++i) {
+        var algorithm = algorithms[i];
+        it('should PKCS#8 encrypt and decrypt private key with ' + algorithm, function() {
+          var privateKey = PKI.privateKeyFromPem(_pem.privateKey);
+          var encryptedPem = PKI.encryptRsaPrivateKey(
+             privateKey, 'password', {algorithm: algorithm});
+          privateKey = PKI.decryptRsaPrivateKey(encryptedPem, 'password');
+          ASSERT.equal(PKI.privateKeyToPem(privateKey), _pem.privateKey);
+        });
+      }
+    })();
+
+    (function() {
+      var algorithms = ['aes128', 'aes192', 'aes256', '3des', 'des'];
+      for(var i = 0; i < algorithms.length; ++i) {
+        var algorithm = algorithms[i];
+        it('should legacy (OpenSSL style) encrypt and decrypt private key with ' + algorithm, function() {
+          var privateKey = PKI.privateKeyFromPem(_pem.privateKey);
+          var encryptedPem = PKI.encryptRsaPrivateKey(
+             privateKey, 'password', {algorithm: algorithm, legacy: true});
+          privateKey = PKI.decryptRsaPrivateKey(encryptedPem, 'password');
+          ASSERT.equal(PKI.privateKeyToPem(privateKey), _pem.privateKey);
+        });
+      }
+    })();
+
+    it('should verify signature', function() {
+      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
+      var md = MD.sha1.create();
+      md.update('0123456789abcdef');
+      var signature = UTIL.hexToBytes(_signature);
+      ASSERT.ok(publicKey.verify(md.digest().getBytes(), signature));
+    });
+
+    it('should sign and verify', function() {
+      var privateKey = PKI.privateKeyFromPem(_pem.privateKey);
+      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
+      var md = MD.sha1.create();
+      md.update('0123456789abcdef');
+      var signature = privateKey.sign(md);
+      ASSERT.ok(publicKey.verify(md.digest().getBytes(), signature));
+    });
+
+    it('should generate missing CRT parameters, sign, and verify', function() {
+      var privateKey = PKI.privateKeyFromPem(_pem.privateKey);
+
+      // remove dQ, dP, and qInv
+      privateKey = RSA.setPrivateKey(
+        privateKey.n, privateKey.e, privateKey.d,
+        privateKey.p, privateKey.q);
+
+      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
+      var md = MD.sha1.create();
+      md.update('0123456789abcdef');
+      var signature = privateKey.sign(md);
+      ASSERT.ok(publicKey.verify(md.digest().getBytes(), signature));
+    });
+
+    it('should sign and verify with a private key containing only e, n, and d parameters', function() {
+      var privateKey = PKI.privateKeyFromPem(_pem.privateKey);
+
+      // remove all CRT parameters from private key, so that it consists
+      // only of e, n and d (which make a perfectly valid private key, but its
+      // operations are slower)
+      privateKey = RSA.setPrivateKey(
+        privateKey.n, privateKey.e, privateKey.d);
+
+      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
+      var md = MD.sha1.create();
+      md.update('0123456789abcdef');
+      var signature = privateKey.sign(md);
+      ASSERT.ok(publicKey.verify(md.digest().getBytes(), signature));
+    });
+
+    (function() {
+      var tests = [{
+        keySize: 1024,
+        privateKeyPem: '-----BEGIN RSA PRIVATE KEY-----\r\n' +
+          'MIICWwIBAAKBgQDCjvkkLWNTeYXqEsqGiVCW/pDt3/qAodNMHcU9gOU2rxeWwiRu\r\n' +
+          'OhhLqmMxXHLi0oP5Xmg0m7zdOiLMEyzzyRzdp21aqp3k5qtuSDkZcf1prsp1jpYm\r\n' +
+          '6z9EGpaSHb64BCuUsQGmUPKutd5RERKHGZXtiRuvvIyue7ETq6VjXrOUHQIDAQAB\r\n' +
+          'AoGAOKeBjTNaVRhyEnNeXkbmHNIMSfiK7aIx8VxJ71r1ZDMgX1oxWZe5M29uaxVM\r\n' +
+          'rxg2Lgt7tLYVDSa8s0hyMptBuBdy3TJUWruDx85uwCrWnMerCt/iKVBS22fv5vm0\r\n' +
+          'LEq/4gjgIVTZwgqbVxGsBlKcY2VzxAfYqYzU8EOZBeNhZdECQQDy+PJAPcUN2xOs\r\n' +
+          '6qy66S91x6y3vMjs900OeX4+bgT4VSVKmLpqRTPizzcL07tT4+Y+pAAOX6VstZvZ\r\n' +
+          '6iFDL5rPAkEAzP1+gaRczboKoJWKJt0uEMUmztcY9NXJFDmjVLqzKwKjcAoGgIal\r\n' +
+          'h+uBFT9VJ16QajC7KxTRLlarzmMvspItUwJAeUMNhEpPwm6ID1DADDi82wdgiALM\r\n' +
+          'NJfn+UVhYD8Ac//qsKQwxUDseFH6owh1AZVIIBMxg/rwUKUCt2tGVoW3uQJAIt6M\r\n' +
+          'Aml/D8+xtxc45NuC1n9y1oRoTl1/Ut1rFyKbD5nnS0upR3uf9LruvjqDtaq0Thvz\r\n' +
+          '+qQT4RoFJ5pfprSO2QJAdMkfNWRqECfAhZyQuUrapeWU3eQ0wjvktIynCIwiBDd2\r\n' +
+          'MfjmVXzBJhMk6dtINt+vBEITVQEOdtyTgDt0y3n2Lw==\r\n' +
+          '-----END RSA PRIVATE KEY-----\r\n',
+        publicKeyPem: '-----BEGIN PUBLIC KEY-----\r\n' +
+          'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCjvkkLWNTeYXqEsqGiVCW/pDt\r\n' +
+          '3/qAodNMHcU9gOU2rxeWwiRuOhhLqmMxXHLi0oP5Xmg0m7zdOiLMEyzzyRzdp21a\r\n' +
+          'qp3k5qtuSDkZcf1prsp1jpYm6z9EGpaSHb64BCuUsQGmUPKutd5RERKHGZXtiRuv\r\n' +
+          'vIyue7ETq6VjXrOUHQIDAQAB\r\n' +
+          '-----END PUBLIC KEY-----\r\n',
+        encrypted: 'jsej3OoacmJ1VjWrlw68F+drnQORAuKAqVu6RMbz1xSXjzA355vctrJZXolRU0mvzuu/6VuNynkKGGyRJ6DHt85CvwTMChw4tOMV4Dy6bgnUt3j+DZA2sWTwFhOlpzvNQMK70QpuqrXtOZmAO59EwoDeJkW/iH6t4YzNOVYo9Jg=',
+        signature: 'GT0/3EV2zrXxPd1ydijJq3R7lkI4c0GtcprgpG04dSECv/xyXtikuzivxv7XzUdHpu6QiYmM0xE4D4i7LK3Mzy+f7aB4o/dg8XXO3htLiBzVI+ZJCRh06RdYctPtclAWmyZikZ8Etw3NnA/ldKuG4jApbwRb21UFm5gYLrJ4SP4=',
+        signaturePss: 'F4xffaANDBjhFxeSJx8ANuBbdhaWZjUHRQh4ueYQMPPCaR2mpwdqxE04sbgNgIiZzBuLIAI4HpTMMoDk3Rruhjefx3+9UhzTxgB0hRI+KzRChRs+ToltWWDZdYzt9T8hfTlELeqT4V8HgjDuteO/IAvIVlRIBwMNv53Iebu1FY4=',
+        signatureWithAbcSalt: 'GYA/Zp8G+jqG2Fu7Um+XP7Cr/yaVdzJN8lyt57Lw6gFflia2CPbOVMLyqLzD7fKoE8UD0Rc6DF8k04xhEu60sudw2nxGHeDvpL4M9du0uYra/WSr9kv7xNjAW62NyNerDngHD2J7O8gQ07TZiTXkrfS724vQab5xZL/+FhvisMY=',
+        signatureWithCustomPrng: 'LzWcUpUYK+URDp72hJbz1GVEp0rG0LHjd+Pdh2w5rfQFbUThbmXDl3X6DUT5UZr5RjUSHtc2usvH+w49XskyIJJO929sUk9EkMJMK/6QAnYYEp5BA+48pdGNNMZyjIbhyl9Y4lInzFPX8XYMM8o+tdSK+hj+dW5OPdnwWbDtR7U='
+      }, {
+        keySize: 1025,
+        privateKeyPem: '-----BEGIN RSA PRIVATE KEY-----\r\n' +
+          'MIICXgIBAAKBgQGIkej4PDlAigUh5fbbHp1WXuTHhOdQfAke+LoH0TM4uzn0QmgK\r\n' +
+          'SJqxzB1COJ5o0DwZw/NR+CNy7NUrly+vmh2YPwsaqN+AsYBF9qsF93oN8/TBtaL/\r\n' +
+          'GRoRGpDcCglkj1kZnDaWR79NsG8mC0TrvQCkcCLOP0c2Ux1hRbntOetGXwIDAQAB\r\n' +
+          'AoGBAIaJWsoX+ZcAthmT8jHOICXFh6pJBe0zVPzkSPz82Q0MPSRUzcsYbsuYJD7Z\r\n' +
+          'oJBTLQW3feANpjhwqe2ydok7y//ONm3Th53Bcu8jLfoatg4KYxNFIwXEO10mPOld\r\n' +
+          'VuDIGrBkTABe6q2P5PeUKGCKLT6i/u/2OTXTrQiJbQ0gU8thAkEBjqcFivWMXo34\r\n' +
+          'Cb9/EgfWCCtv9edRMexgvcFMysRsbHJHDK9JjRLobZltwtAv3cY7F3a/Cu1afg+g\r\n' +
+          'jAzm5E3gowJBAPwYFHTLzaZToxFKNQztWrPsXF6YfqHpPUUIpT4UzL6DhGG0M00U\r\n' +
+          'qMyhkYRRqmGOSrSovjg2hjM2643MUUWxUxUCQDPkk/khu5L3YglKzyy2rmrD1MAq\r\n' +
+          'y0v3XCR3TBq89Ows+AizrJxbkLvrk/kfBowU6M5GG9o9SWFNgXWZnFittocCQQDT\r\n' +
+          'e1P1419DUFi1UX6NuLTlybx3sxBQvf0jY6xUF1jn3ib5XBXJbTJqcIRF78iyjI9J\r\n' +
+          'XWIugDc20bTsQOJRSAA9AkEBU8kpueHBaiXTikqqlK9wvc2Lp476hgyKVmVyBGye\r\n' +
+          '9TLTWkTCzDPtManLy47YtXkXnmyazS+DlKFU61XAGEnZfg==\r\n' +
+          '-----END RSA PRIVATE KEY-----\r\n',
+        publicKeyPem: '-----BEGIN PUBLIC KEY-----\r\n' +
+          'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQGIkej4PDlAigUh5fbbHp1WXuTH\r\n' +
+          'hOdQfAke+LoH0TM4uzn0QmgKSJqxzB1COJ5o0DwZw/NR+CNy7NUrly+vmh2YPwsa\r\n' +
+          'qN+AsYBF9qsF93oN8/TBtaL/GRoRGpDcCglkj1kZnDaWR79NsG8mC0TrvQCkcCLO\r\n' +
+          'P0c2Ux1hRbntOetGXwIDAQAB\r\n' +
+          '-----END PUBLIC KEY-----\r\n',
+        encrypted: 'AOVeCUN8BOVkZvt4mxyNn/yCYE1MZ40A3e/osh6EvCBcJ09hyYbx7bzKSrdkhRnDyW0pGtgP352CollasllQZ9HlfI2Wy9zKM0aYZZn8OHBA+60Tc3xHHDGznLZqggUKuhoNpj+faVZ1uzb285eTpQQa+4mLUue2svJD4ViM8+ng',
+        signature: 'AFSx0axDYXlF2rO3ofgUhYSI8ZlIWtJUUZ62PhgdBp9O5zFqMX3DXoiov1e7NenSOz1khvTSMctFWzKP3GU3F0yewe+Yd3UAZE0dM8vAxigSSfAchUkBDmp9OFuszUie63zwWwpG+gXtvyfueZs1RniBvW1ZmXJvS+HFgX4ouzwd',
+        signaturePss: 'AQvBdhAXDpu+7RpcybMgwuTUk6w+qa08Lcq3G1xHY4kC7ZUzauZd/Jn9e0ePKApDqs7eDNAOV+dQkU2wiH/uBg6VGelzb0hFwcpSLyBW92Vw0q3GlzY7myWn8qnNzasrt110zFflWQa1GiuzH/C8f+Z82/MzlWDxloJIYbq2PRC8',
+        signatureWithAbcSalt: 'AW4bKnG/0TGvAZgqX5Dk+fXpUNgX7INFelE46d3m+spaMTG5XalY0xP1sxWfaE/+Zl3FmZcfTNtfOCo0eNRO1h1+GZZfp32ZQZmZvkdUG+dUQp318LNzgygrVf/5iIX+QKV5/soSDuAHBzS7yDfMgzJfnXNpFE/zPLOgZIoOIuLq',
+        signatureWithCustomPrng: 'AVxfCyGC/7Y3kz//eYFEuWQijjR7eR05AM36CwDlLsVkDRtXoeVzz2yTFBdP+i+QgQ73C/I3lLtvXTwfleorvIX9YncVBeGDQXssmULxzqsM3izaLfJXCRAGx9ErL1Az10+fAqPZpq954OVSDqrR/61Q7CsMY7CiQO3nfIIaxgVL'
+      }, {
+        keySize: 1031,
+        privateKeyPem: '-----BEGIN RSA PRIVATE KEY-----\r\n' +
+          'MIICXwIBAAKBgWyeKqA2oA4klYrKT9hjjutYQksJNN0cxwaQwIm9AYiLxOsYtT/C\r\n' +
+          'ovJx5Oy1EvkbYQbfvYsGISUx9bW8yasZkTHR55IbW3+UptvQjTDtdxBQTgQOpsAh\r\n' +
+          'BJtZYY3OmyH9Sj3F3oB//oyriNoj0QYyfsvlO8UsMmLzpnf6qfZBDHA/9QIDAQAB\r\n' +
+          'AoGBBj/3ne5muUmbnTfU7lOUNrCGaADonMx6G0ObAJHyk6PPOePbEgcmDyNEk+Y7\r\n' +
+          'aEAODjIzmttIbvZ39/Qb+o9nDmCSZC9VxiYPP+rjOzPglCDT5ks2Xcjwzd3If6Ya\r\n' +
+          'Uw6P31Y760OCYeTb4Ib+8zz5q51CkjkdX5Hq/Yu+lZn0Vx7BAkENo83VfL+bwxTm\r\n' +
+          'V7vR6gXqTD5IuuIGHL3uTmMNNURAP6FQDHu//duipys83iMChcOeXtboE16qYrO0\r\n' +
+          '9KC0cqL4JQJBB/aYo/auVUGZA6f50YBp0b2slGMk9TBQG0iQefuuSyH4kzKnt2e3\r\n' +
+          'Q40SBmprcM+DfttWJ11bouec++goXjz+95ECQQyiTWYRxulgKVuyqCYnvpLnTEnR\r\n' +
+          '0MoYlVTHBriVPkLErYaYCYgse+SNM1+N4p/Thv6KmkUcq/Lmuc5DSRfbl1iBAkEE\r\n' +
+          '7GKtJQvd7EO1bfpXnARQx+tWhwHHkgpFBBVHReMZ0rQEFhJ5o2c8HZEiZFNvGO2c\r\n' +
+          '1fErP14zlu2JFZ03vpCI8QJBCQz9HL28VNjafSAF2mon/SNjKablRjoGGKSoSdyA\r\n' +
+          'DHDZ/LeRsTp2dg8+bSiG1R+vPqw0f/BT+ux295Sy9ocGEM8=\r\n' +
+          '-----END RSA PRIVATE KEY-----\r\n',
+        publicKeyPem: '-----BEGIN PUBLIC KEY-----\r\n' +
+          'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgWyeKqA2oA4klYrKT9hjjutYQksJ\r\n' +
+          'NN0cxwaQwIm9AYiLxOsYtT/CovJx5Oy1EvkbYQbfvYsGISUx9bW8yasZkTHR55Ib\r\n' +
+          'W3+UptvQjTDtdxBQTgQOpsAhBJtZYY3OmyH9Sj3F3oB//oyriNoj0QYyfsvlO8Us\r\n' +
+          'MmLzpnf6qfZBDHA/9QIDAQAB\r\n' +
+          '-----END PUBLIC KEY-----\r\n',
+        encrypted: 'ShSS4/fEAkuS6XiQakhOpWp82IXaaCaDNtsndU4uokvriqgCGZyqc+IkIk3eVmZ8bn4vVIRR43ydFuvGgsptVjizOdLGZudph3TJ1clcYEMcCXk4z5HaEu0bx5SW9jmzHhE/z+WV8PB48q7y7C2qtmPmfttG2NMsNLBvkiaDopRO',
+        signature: 'Z3vYgRdezrWmdA3NC1Uz2CcHRTcE+/C2idGZA1FjUGqFztAHQ31k0QW/F5zuJdKvg8LQU45S3KxW+OQpbGPL98QbzJLhml88mFGe6OinLXJbi7UQWrtXwamc2jMdiXwovSLbXaXy6PX2QW089iC8XuAZftVi3T/IKV0458FQQprg',
+        signaturePss: 'R6QsK6b3QinIPZPamm/dP0Zndqti1TzAkFTRSZJaRSa1u2zuvZC5QHF4flDjEtHosWeDyxrBE7PHGQZ0b1bHv9qgHGsJCMwaQPj3AWj9fjYmx7b86KM2vHr8q/vqDaa9pTvVRSSwvD6fwoZPc9twQEfdjdDBAiy23yLDzk/zZiwM',
+        signatureWithAbcSalt: 'Ep9qx4/FPNcWTixWhvL2IAyJR69o5I4MIJi3cMAhDmpuTvAaL/ThQwFWkBPPOPT4Jbumnu6ELjPNjo72wa00e5k64qnZgy1pauBPMlXRlKehRc9UJZ6+xot642z8Qs+rt89OgbYTsvlyr8lzXooUHz/lPpfawYCqd7maRMs8YlYM',
+        signatureWithCustomPrng: 'NHAwyn2MdM5ez/WbDNbu2A2JNS+cRiWk/zBoh0lg3aq/RsBS0nrYr4AGiC5jt6KWVcN4AIVOomYtX2k+MhLoemN2t2rDj/+LXOeU7kgCAz0q0ED2NFQz7919JU+PuYXMy03qTMfl5jbvStdi/00eQHjJKGEH+xAgrDcED2lrhtCu'
+      }, {
+        keySize: 1032,
+        privateKeyPem: '-----BEGIN RSA PRIVATE KEY-----\r\n' +
+          'MIICYQIBAAKBggDPhzn5I3GecxWt5DKbP+VhM2AFNSOL0+VbYEOR1hnlZdLbxGK4\r\n' +
+          'cPQzMr2qT6dyttJcsgWr3xKobPkz7vsTZzQATSiekm5Js5dGpaj5lrq/x2+WTZvn\r\n' +
+          '55x9M5Y5dlpusDMKcC3KaIX/axc+MbvPFzo6Eli7JLCWdBg01eKo30knil0CAwEA\r\n' +
+          'AQKBggCNl/sjFF7SOD1jbt5kdL0hi7cI9o+xOLs1lEGmAEmc7dNnZN/ibhb/06/6\r\n' +
+          'wuxB5aEz47bg5IvLZMbG+1hNjc26D0J6Y3Ltwrg8f4ZMdDrh4v0DZ8hy/HbEpMrJ\r\n' +
+          'Td5dk3mtw9FLow10MB5udPLTDKhfDpTcWiObKm2STtFeBk3xeEECQQ6Cx6bZxQJ1\r\n' +
+          'zCxflV5Xi8BgAQaUKMqygugte+HpOLflL0j1fuZ0rPosUyDOEFkTzOsPxBYYOU8i\r\n' +
+          'Gzan1GvW3WwRAkEOTTRt849wpgC9xx2pF0IrYEVmv5gEMy3IiRfCNgEoBwpTWVf4\r\n' +
+          'QFpN3V/9GFz0WQEEYo6OTmkNcC3Of5zbHhu1jQJBBGxXAYQ2KnbP4uLL/DMBdYWO\r\n' +
+          'Knw1JvxdLPrYXVejI2MoE7xJj2QXajbirAhEMXL4rtpicj22EmoaE4H7HVgkrJEC\r\n' +
+          'QQq2V5w4AGwvW4TLHXNnYX/eB33z6ujScOuxjGNDUlBqHZja5iKkCUAjnl+UnSPF\r\n' +
+          'exaOwBrlrpiLOzRer94MylKNAkEBmI58bqfkI5OCGDArAsJ0Ih58V0l1UW35C1SX\r\n' +
+          '4yDoXSM5A/xQu2BJbXO4jPe3PnDvCVCEyKpbCK6bWbe26Y7zuw==\r\n' +
+          '-----END RSA PRIVATE KEY-----\r\n',
+        publicKeyPem: '-----BEGIN PUBLIC KEY-----\r\n' +
+          'MIGgMA0GCSqGSIb3DQEBAQUAA4GOADCBigKBggDPhzn5I3GecxWt5DKbP+VhM2AF\r\n' +
+          'NSOL0+VbYEOR1hnlZdLbxGK4cPQzMr2qT6dyttJcsgWr3xKobPkz7vsTZzQATSie\r\n' +
+          'km5Js5dGpaj5lrq/x2+WTZvn55x9M5Y5dlpusDMKcC3KaIX/axc+MbvPFzo6Eli7\r\n' +
+          'JLCWdBg01eKo30knil0CAwEAAQ==\r\n' +
+          '-----END PUBLIC KEY-----\r\n',
+        encrypted: 'pKTbv+xgXPDc+wbjsANFu1/WTcmy4aZFKXKnxddHbU5S0Dpdj2OqCACiBwu1oENPMgPAJ27XRbFtKG+eS8tX47mKP2Fo0Bi+BPFtzuQ1bj3zUzTwzjemT+PU+a4Tho/eKjPhm6xrwGAoQH2VEDEpvcYf+SRmGFJpJ/zPUrSxgffj',
+        signature: 'R9WBFprCfcIC4zY9SmBpEM0E+cr5j4gMn3Ido5mktoR9VBoJqC6eR6lubIPvZZUz9e4yUSYX0squ56Q9Y0yZFQjTHgsrlmhB2YW8kpv4h8P32Oz2TLcMJK9R2tIh9vvyxwBkd/Ml1qG60GnOFUFzxUad9VIlzaF1PFR6EfnkgBUW',
+        signaturePss: 'v9UBd4XzBxSRz8yhWKjUkFpBX4Fr2G+ImjqbePL4sAZvYw1tWL+aUQpzG8eOyMxxE703VDh9nIZULYI/uIb9HYHQoGYQ3WoUaWqtZg1x8pZP+Ad7ilUWk5ImRl57fTznNQiVdwlkS5Wgheh1yJCES570a4eujiK9OyB0ba4rKIcM',
+        signatureWithAbcSalt: 'HCm0FI1jE6wQgwwi0ZwPTkGjssxAPtRh6tWXhNd2J2IoJYj9oQMMjCEElnvQFBa/l00sIsw2YV1tKyoTABaSTGV4vlJcDF+K0g/wiAf30TRUZo72DZKDNdyffDlH0wBDkNVW+F6uqdciJqBC6zz+unNh7x+FRwYaY8xhudIPXdyP',
+        signatureWithCustomPrng: 'AGyN8xu+0yfCR1tyB9mCXcTGb2vdLnsX9ro2Qy5KV6Hw5YMVNltAt65dKR4Y8pfu6D4WUyyJRUtJ8td2ZHYzIVtWY6bG1xFt5rkjTVg4v1tzQgUQq8AHvRE2qLzwDXhazJ1e6Id2Nuxb1uInFyRC6/gLmiPga1WRDEVvFenuIA48'
+      }];
+      for(var i = 0; i < tests.length; ++i) {
+        createTests(tests[i]);
+      }
+
+      it('should ensure maximum message length for a 1024-bit key is exceeded', function() {
+        /* For PKCS#1 v1.5, the message must be padded with at least eight bytes,
+          two zero bytes and one byte telling what the block type is. This is 11
+          extra bytes are added to the message. The test uses a message of 118
+          bytes.Together with the 11 extra bytes the encryption block needs to be
+          at least 129 bytes long. This requires a key of 1025-bits. */
+        var key = PKI.publicKeyFromPem(tests[0].publicKeyPem);
+        var message = UTIL.createBuffer().fillWithByte(0, 118);
+        ASSERT.throws(function() {
+          key.encrypt(message.getBytes());
+        });
+      });
+
+      it('should ensure maximum message length for a 1025-bit key is not exceeded', function() {
+        var key = PKI.publicKeyFromPem(tests[1].publicKeyPem);
+        var message = UTIL.createBuffer().fillWithByte(0, 118);
+        ASSERT.doesNotThrow(function() {
+          key.encrypt(message.getBytes());
+        });
+      });
+
+      /**
+       * Creates RSA encryption & decryption tests.
+       *
+       * Uses different key sizes (1024, 1025, 1031, 1032). The test functions are
+       * generated from "templates" below, one for each key size to provide sensible
+       * output.
+       *
+       * Key material in was created with OpenSSL using these commands:
+       *
+       * openssl genrsa -out rsa_1024_private.pem 1024
+       * openssl rsa -in rsa_1024_private.pem -out rsa_1024_public.pem \
+       *   -outform PEM -pubout
+       * echo 'too many secrets' | openssl rsautl -encrypt \
+       *   -inkey rsa_1024_public.pem -pubin -out rsa_1024_encrypted.bin
+       *
+       * echo -n 'just testing' | openssl dgst -sha1 -binary > tosign.sha1
+       * openssl pkeyutl -sign -in tosign.sha1 -inkey rsa_1024_private.pem \
+       *   -out rsa_1024_sig.bin -pkeyopt digest:sha1
+       * openssl pkeyutl -sign -in tosign.sha1 -inkey rsa_1024_private.pem \
+       *   -out rsa_1024_sigpss.bin -pkeyopt digest:sha1 \
+       *   -pkeyopt rsa_padding_mode:pss -pkeyopt rsa_pss_saltlen:20
+       *
+       * OpenSSL commands for signature verification:
+       *
+       * openssl pkeyutl -verify -in tosign.sha1 -sigfile rsa_1024_sig.bin \
+       *   -pubin -inkey rsa_1024_public.pem -pkeyopt digest:sha1
+       * openssl pkeyutl -verify -in tosign.sha1 -sigfile rsa_1025_sigpss.bin \
+       *   -pubin -inkey rsa_1025_public.pem -pkeyopt digest:sha1 \
+       *   -pkeyopt rsa_padding_mode:pss -pkeyopt rsa_pss_saltlen:20
+       */
+      function createTests(params) {
+        var keySize = params.keySize;
+
+        it('should rsa encrypt using a ' + keySize + '-bit key', function() {
+          var message = "it need's to be about 20% cooler"; // it need's better grammar too
+
+          /* First step, do public key encryption */
+          var key = PKI.publicKeyFromPem(params.publicKeyPem);
+          var data = key.encrypt(message);
+
+          /* Second step, use private key decryption to verify successful
+            encryption. The encrypted message differs every time, since it is
+            padded with random data. Therefore just rely on the decryption
+            routine to work, which is tested seperately against an externally
+            provided encrypted message. */
+          key = PKI.privateKeyFromPem(params.privateKeyPem);
+          ASSERT.equal(key.decrypt(data), message);
+        });
+
+        it('should rsa decrypt using a ' + keySize + '-bit key', function() {
+          var data = UTIL.decode64(params.encrypted);
+          var key = PKI.privateKeyFromPem(params.privateKeyPem);
+          ASSERT.equal(key.decrypt(data), 'too many secrets\n');
+        });
+
+        it('should rsa sign using a ' + keySize + '-bit key and PKCS#1 v1.5 padding', function() {
+          var key = PKI.privateKeyFromPem(params.privateKeyPem);
+
+          var md = MD.sha1.create();
+          md.start();
+          md.update('just testing');
+
+          var signature = UTIL.decode64(params.signature);
+          ASSERT.equal(key.sign(md), signature);
+        });
+
+        it('should verify an rsa signature using a ' + keySize + '-bit key and PKCS#1 v1.5 padding', function() {
+          var signature = UTIL.decode64(params.signature);
+          var key = PKI.publicKeyFromPem(params.publicKeyPem);
+
+          var md = MD.sha1.create();
+          md.start();
+          md.update('just testing');
+
+          ASSERT.equal(key.verify(md.digest().getBytes(), signature), true);
+        });
+
+        /* Note: signatures are *not* deterministic (the point of RSASSA-PSS),
+          so they can't be compared easily -- instead they are just verified
+          using the verify() function which is tested against OpenSSL-generated
+          signatures. */
+        it('should rsa sign using a ' + keySize + '-bit key and PSS padding', function() {
+          var privateKey = PKI.privateKeyFromPem(params.privateKeyPem);
+          var publicKey = PKI.publicKeyFromPem(params.publicKeyPem);
+
+          var md = MD.sha1.create();
+          md.start();
+          md.update('just testing');
+
+          // create signature
+          var pss = PSS.create(
+            MD.sha1.create(), MGF.mgf1.create(MD.sha1.create()), 20);
+          var signature = privateKey.sign(md, pss);
+
+          // verify signature
+          md.start();
+          md.update('just testing');
+          ASSERT.equal(
+            publicKey.verify(md.digest().getBytes(), signature, pss), true);
+        });
+
+        it('should verify an rsa signature using a ' + keySize + '-bit key and PSS padding', function() {
+          var signature = UTIL.decode64(params.signaturePss);
+          var key = PKI.publicKeyFromPem(params.publicKeyPem);
+
+          var md = MD.sha1.create();
+          md.start();
+          md.update('just testing');
+
+          var pss = PSS.create(
+            MD.sha1.create(), MGF.mgf1.create(MD.sha1.create()), 20);
+          ASSERT.equal(
+            key.verify(md.digest().getBytes(), signature, pss), true);
+        });
+
+        it('should rsa sign using a ' + keySize + '-bit key and PSS padding using pss named-param API', function() {
+          var privateKey = PKI.privateKeyFromPem(params.privateKeyPem);
+          var publicKey = PKI.publicKeyFromPem(params.publicKeyPem);
+
+          var md = MD.sha1.create();
+          md.start();
+          md.update('just testing');
+
+          // create signature
+          var pss = PSS.create({
+            md: MD.sha1.create(),
+            mgf: MGF.mgf1.create(MD.sha1.create()),
+            saltLength: 20
+          });
+          var signature = privateKey.sign(md, pss);
+
+          // verify signature
+          md.start();
+          md.update('just testing');
+          ASSERT.equal(
+            publicKey.verify(md.digest().getBytes(), signature, pss), true);
+        });
+
+        it('should verify an rsa signature using a ' + keySize + '-bit key and PSS padding using pss named-param API', function() {
+          var signature = UTIL.decode64(params.signaturePss);
+          var key = PKI.publicKeyFromPem(params.publicKeyPem);
+
+          var md = MD.sha1.create();
+          md.start();
+          md.update('just testing');
+
+          var pss = PSS.create({
+            md: MD.sha1.create(),
+            mgf: MGF.mgf1.create(MD.sha1.create()),
+            saltLength: 20
+          });
+          ASSERT.equal(
+            key.verify(md.digest().getBytes(), signature, pss), true);
+        });
+
+        it('should rsa sign using a ' + keySize + '-bit key and PSS padding using salt "abc"', function() {
+          var privateKey = PKI.privateKeyFromPem(params.privateKeyPem);
+
+          var md = MD.sha1.create();
+          md.start();
+          md.update('just testing');
+
+          // create signature
+          var pss = PSS.create({
+            md: MD.sha1.create(),
+            mgf: MGF.mgf1.create(MD.sha1.create()),
+            salt: UTIL.createBuffer('abc')
+          });
+          var signature = privateKey.sign(md, pss);
+          var b64 = UTIL.encode64(signature);
+          ASSERT.equal(b64, params.signatureWithAbcSalt);
+        });
+
+        it('should verify an rsa signature using a ' + keySize + '-bit key and PSS padding using salt "abc"', function() {
+          var signature = UTIL.decode64(params.signatureWithAbcSalt);
+          var key = PKI.publicKeyFromPem(params.publicKeyPem);
+
+          var md = MD.sha1.create();
+          md.start();
+          md.update('just testing');
+
+          var pss = PSS.create({
+            md: MD.sha1.create(),
+            mgf: MGF.mgf1.create(MD.sha1.create()),
+            saltLength: 3
+          });
+          ASSERT.equal(
+            key.verify(md.digest().getBytes(), signature, pss), true);
+        });
+
+        it('should rsa sign using a ' + keySize + '-bit key and PSS padding using custom PRNG', function() {
+          var prng = RANDOM.createInstance();
+          prng.seedFileSync = function(needed) {
+            return UTIL.fillString('a', needed);
+          };
+          var privateKey = PKI.privateKeyFromPem(params.privateKeyPem);
+
+          var md = MD.sha1.create();
+          md.start();
+          md.update('just testing');
+
+          // create signature
+          var pss = PSS.create({
+            md: MD.sha1.create(),
+            mgf: MGF.mgf1.create(MD.sha1.create()),
+            saltLength: 20,
+            prng: prng
+          });
+          var signature = privateKey.sign(md, pss);
+          var b64 = UTIL.encode64(signature);
+          ASSERT.equal(b64, params.signatureWithCustomPrng);
+        });
+
+        it('should verify an rsa signature using a ' + keySize + '-bit key and PSS padding using custom PRNG', function() {
+          var prng = RANDOM.createInstance();
+          prng.seedFileSync = function(needed) {
+            return UTIL.fillString('a', needed);
+          };
+          var signature = UTIL.decode64(params.signatureWithCustomPrng);
+          var key = PKI.publicKeyFromPem(params.publicKeyPem);
+
+          var md = MD.sha1.create();
+          md.start();
+          md.update('just testing');
+
+          var pss = PSS.create({
+            md: MD.sha1.create(),
+            mgf: MGF.mgf1.create(MD.sha1.create()),
+            saltLength: 20,
+            prng: prng
+          });
+          ASSERT.equal(
+            key.verify(md.digest().getBytes(), signature, pss), true);
+        });
+      }
+    })();
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/pki',
+    'forge/rsa',
+    'forge/md',
+    'forge/mgf',
+    'forge/pss',
+    'forge/random',
+    'forge/util'
+  ], function(PKI, RSA, MD, MGF, PSS, RANDOM, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      PKI(),
+      RSA(),
+      MD(),
+      MGF(),
+      PSS(),
+      RANDOM(),
+      UTIL()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/pki')(),
+    require('../../js/rsa')(),
+    require('../../js/md')(),
+    require('../../js/mgf')(),
+    require('../../js/pss')(),
+    require('../../js/random')(),
+    require('../../js/util')());
+}
+
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/sha1.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/sha1.js
new file mode 100644
index 0000000..3ffd985
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/sha1.js
@@ -0,0 +1,75 @@
+(function() {
+
+function Tests(ASSERT, SHA1, UTIL) {
+  describe('sha1', function() {
+    it('should digest the empty string', function() {
+      var md = SHA1.create();
+      ASSERT.equal(
+        md.digest().toHex(), 'da39a3ee5e6b4b0d3255bfef95601890afd80709');
+    });
+
+    it('should digest "abc"', function() {
+      var md = SHA1.create();
+      md.update('abc');
+      ASSERT.equal(
+        md.digest().toHex(), 'a9993e364706816aba3e25717850c26c9cd0d89d');
+    });
+
+    it('should digest "The quick brown fox jumps over the lazy dog"', function() {
+      var md = SHA1.create();
+      md.update('The quick brown fox jumps over the lazy dog');
+      ASSERT.equal(
+        md.digest().toHex(), '2fd4e1c67a2d28fced849ee1bb76e7391b93eb12');
+    });
+
+    it('should digest "c\'\u00e8"', function() {
+      var md = SHA1.create();
+      md.update("c\'\u00e8", 'utf8');
+      ASSERT.equal(
+        md.digest().toHex(), '98c9a3f804daa73b68a5660d032499a447350c0d');
+    });
+
+    it('should digest "THIS IS A MESSAGE"', function() {
+      var md = SHA1.create();
+      md.start();
+      md.update('THIS IS ');
+      md.update('A MESSAGE');
+      // do twice to check continuing digest
+      ASSERT.equal(
+        md.digest().toHex(), '5f24f4d6499fd2d44df6c6e94be8b14a796c071d');
+      ASSERT.equal(
+        md.digest().toHex(), '5f24f4d6499fd2d44df6c6e94be8b14a796c071d');
+    });
+
+    it('should digest a long message', function() {
+      // Note: might be too slow on old browsers
+      var md = SHA1.create();
+      md.update(UTIL.fillString('a', 1000000));
+      ASSERT.equal(
+        md.digest().toHex(), '34aa973cd4c4daa4f61eeb2bdbad27316534016f');
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/sha1',
+    'forge/util'
+  ], function(SHA1, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      SHA1(),
+      UTIL()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/sha1')(),
+    require('../../js/util')());
+}
+
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/sha256.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/sha256.js
new file mode 100644
index 0000000..2d5eb8a
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/sha256.js
@@ -0,0 +1,81 @@
+(function() {
+
+function Tests(ASSERT, SHA256, UTIL) {
+  describe('sha256', function() {
+    it('should digest the empty string', function() {
+      var md = SHA256.create();
+      ASSERT.equal(
+        md.digest().toHex(),
+        'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855');
+    });
+
+    it('should digest "abc"', function() {
+      var md = SHA256.create();
+      md.update('abc');
+      ASSERT.equal(
+        md.digest().toHex(),
+        'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad');
+    });
+
+    it('should digest "The quick brown fox jumps over the lazy dog"', function() {
+      var md = SHA256.create();
+      md.update('The quick brown fox jumps over the lazy dog');
+      ASSERT.equal(
+        md.digest().toHex(),
+        'd7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592');
+    });
+
+    it('should digest "c\'\u00e8"', function() {
+      var md = SHA256.create();
+      md.update("c\'\u00e8", 'utf8');
+      ASSERT.equal(
+        md.digest().toHex(),
+        '1aa15c717afffd312acce2217ce1c2e5dabca53c92165999132ec9ca5decdaca');
+    });
+
+    it('should digest "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"', function() {
+      var md = SHA256.create();
+      md.start();
+      md.update('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq');
+      // do twice to check continuing digest
+      ASSERT.equal(
+        md.digest().toHex(),
+        '248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1');
+      ASSERT.equal(
+        md.digest().toHex(),
+        '248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1');
+    });
+
+    it('should digest a long message', function() {
+      // Note: might be too slow on old browsers
+      var md = SHA256.create();
+      md.update(UTIL.fillString('a', 1000000));
+      ASSERT.equal(
+        md.digest().toHex(),
+        'cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0');
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/sha256',
+    'forge/util'
+  ], function(SHA256, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      SHA256(),
+      UTIL()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/sha256')(),
+    require('../../js/util')());
+}
+
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/sha512.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/sha512.js
new file mode 100644
index 0000000..3cbc4dc
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/sha512.js
@@ -0,0 +1,174 @@
+(function() {
+
+function Tests(ASSERT, SHA512, UTIL) {
+  describe('sha512', function() {
+    it('should digest the empty string', function() {
+      var md = SHA512.create();
+      ASSERT.equal(
+        md.digest().toHex(),
+        'cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e');
+    });
+
+    it('should digest "abc"', function() {
+      var md = SHA512.create();
+      md.update('abc');
+      ASSERT.equal(
+        md.digest().toHex(),
+        'ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f');
+    });
+
+    it('should digest "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"', function() {
+      var md = SHA512.create();
+      md.update('abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu');
+      ASSERT.equal(
+        md.digest().toHex(),
+        '8e959b75dae313da8cf4f72814fc143f8f7779c6eb9f7fa17299aeadb6889018501d289e4900f7e4331b99dec4b5433ac7d329eeb6dd26545e96e55b874be909');
+    });
+
+    it('should digest "The quick brown fox jumps over the lazy dog"', function() {
+      var md = SHA512.create();
+      md.update('The quick brown fox jumps over the lazy dog');
+      ASSERT.equal(
+        md.digest().toHex(),
+        '07e547d9586f6a73f73fbac0435ed76951218fb7d0c8d788a309d785436bbb642e93a252a954f23912547d1e8a3b5ed6e1bfd7097821233fa0538f3db854fee6');
+    });
+
+    it('should digest "c\'\u00e8"', function() {
+      var md = SHA512.create();
+      md.update("c\'\u00e8", 'utf8');
+      ASSERT.equal(
+        md.digest().toHex(),
+        '9afdc0390dd91e81c63f858d1c6fcd9f949f3fc89dbdaed9e4211505bad63d8e8787797e2e9ea651285eb6954e51c4f0299837c3108cb40f1420bca1d237355c');
+    });
+
+    it('should digest "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"', function() {
+      var md = SHA512.create();
+      md.start();
+      md.update('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq');
+      // do twice to check continuing digest
+      ASSERT.equal(
+        md.digest().toHex(),
+        '204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c33596fd15c13b1b07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445');
+      ASSERT.equal(
+        md.digest().toHex(),
+        '204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c33596fd15c13b1b07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445');
+    });
+  });
+
+  SHA384 = SHA512.sha384;
+
+  describe('sha384', function() {
+    it('should digest the empty string', function() {
+      var md = SHA384.create();
+      ASSERT.equal(
+        md.digest().toHex(),
+        '38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b');
+    });
+
+    it('should digest "abc"', function() {
+      var md = SHA384.create();
+      md.update('abc');
+      ASSERT.equal(
+        md.digest().toHex(),
+        'cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7');
+    });
+
+    it('should digest "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"', function() {
+      var md = SHA384.create();
+      md.update('abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu');
+      ASSERT.equal(
+        md.digest().toHex(),
+        '09330c33f71147e83d192fc782cd1b4753111b173b3b05d22fa08086e3b0f712fcc7c71a557e2db966c3e9fa91746039');
+    });
+
+    it('should digest "The quick brown fox jumps over the lazy dog"', function() {
+      var md = SHA384.create();
+      md.update('The quick brown fox jumps over the lazy dog');
+      ASSERT.equal(
+        md.digest().toHex(),
+        'ca737f1014a48f4c0b6dd43cb177b0afd9e5169367544c494011e3317dbf9a509cb1e5dc1e85a941bbee3d7f2afbc9b1');
+    });
+
+    it('should digest "c\'\u00e8"', function() {
+      var md = SHA384.create();
+      md.update("c\'\u00e8", 'utf8');
+      ASSERT.equal(
+        md.digest().toHex(),
+        '382ec8a92d50abf57f7d0f934ff3969d6d354d30c96f1616678a920677867aba49521d2d535c0f285a3c2961c2034ea3');
+    });
+
+    it('should digest "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"', function() {
+      var md = SHA384.create();
+      md.start();
+      md.update('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq');
+      // do twice to check continuing digest
+      ASSERT.equal(
+        md.digest().toHex(),
+        '3391fdddfc8dc7393707a65b1b4709397cf8b1d162af05abfe8f450de5f36bc6b0455a8520bc4e6f5fe95b1fe3c8452b');
+      ASSERT.equal(
+        md.digest().toHex(),
+        '3391fdddfc8dc7393707a65b1b4709397cf8b1d162af05abfe8f450de5f36bc6b0455a8520bc4e6f5fe95b1fe3c8452b');
+    });
+  });
+
+  SHA256 = SHA512.sha256;
+
+  describe('sha512/256', function() {
+    it('should digest the empty string', function() {
+      var md = SHA256.create();
+      ASSERT.equal(
+        md.digest().toHex(),
+        'c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a');
+    });
+
+    it('should digest "The quick brown fox jumps over the lazy dog"', function() {
+      var md = SHA256.create();
+      md.update('The quick brown fox jumps over the lazy dog');
+      ASSERT.equal(
+        md.digest().toHex(),
+        'dd9d67b371519c339ed8dbd25af90e976a1eeefd4ad3d889005e532fc5bef04d');
+    });
+  });
+
+  SHA224 = SHA512.sha224;
+
+  describe('sha512/224', function() {
+    it('should digest the empty string', function() {
+      var md = SHA224.create();
+      ASSERT.equal(
+        md.digest().toHex(),
+        '6ed0dd02806fa89e25de060c19d3ac86cabb87d6a0ddd05c333b84f4');
+    });
+
+    it('should digest "The quick brown fox jumps over the lazy dog"', function() {
+      var md = SHA224.create();
+      md.update('The quick brown fox jumps over the lazy dog');
+      ASSERT.equal(
+        md.digest().toHex(),
+        '944cd2847fb54558d4775db0485a50003111c8e5daa63fe722c6aa37');
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/sha512',
+    'forge/util'
+  ], function(SHA512, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      SHA512(),
+      UTIL()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/sha512')(),
+    require('../../js/util')());
+}
+
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/ssh.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/ssh.js
new file mode 100644
index 0000000..c90eb26
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/ssh.js
@@ -0,0 +1,193 @@
+(function() {
+
+function Tests(ASSERT, forge) {
+
+  // Original RSA key generated by openssh
+  var keystr =
+    '-----BEGIN RSA PRIVATE KEY-----\r\n' +
+    'MIIEogIBAAKCAQEA301O+LBnd6ngw9i0Pb5iTwlQ1s37ay3DNpisjh+jPDDvaIZq\r\n' +
+    'PC+44YV7RaR1ib2wEoQfg4DwqVw5wlA2x97Txngb+sJe0yUfmM9vMTxhXWuURVR6\r\n' +
+    '6DGd8t2eOBN8qDoYlpvdVf8Go5Btrb6NCexUbZtFS1AmumK2zQKxIizgVLK8dx4G\r\n' +
+    'NYbqxibUxgYHbwRpu8ROrogVrZbMW1cOb4JE+DzG1FvfHSdzkxb9e5ARcsuLxCdP\r\n' +
+    'ItUMVY8jjgER6b5lK+Nzr57hFdr08RjWGBldNGrCFqDdm+1WkGAHTRrg/i/MD8BU\r\n' +
+    '8NCFUBpQTSrhALkGqBdGZPN/PrXonXjhcd11awIDAQABAoIBAHGMESUaJnLN2jIc\r\n' +
+    'RoLDBaBk/0tLEJaOfZ6Mgen/InUf+Q0wlGKobZ2Xz3g5SV9SKm8v6gpnjXjBIcmy\r\n' +
+    'GjkGEK/yMWAQaEF7thZxHHxv1J65bnrWm2zolgWCNcsT9aZhbFFhTmpFNO4FKhBY\r\n' +
+    'PcWW+9OESfci+Z57RbL3tHTJVwUZrthtagpFEgVceq18GLSS9N4TWJ8HX1CDxf9R\r\n' +
+    '4gzEy3+8oC0QONx+bgsWUmzNk9QNNW6yPV5NYZ3SwNLVgw3+6m8WDaqo5FWK7y2f\r\n' +
+    'pa63DUzXlg5uSYnk4OFVxI6TLpmNQcjigpXnsd1PwcynW0AM83jzIYu0F31ppU61\r\n' +
+    '8flrqfECgYEA8j/joUve1F4lTt2U34RF+59giZ1r0ME4LafNEgN/ZLMB8ghToSqr\r\n' +
+    'qzNnJnSppkAKTa2NVZGgJkvqn+qysM1HNm/3ksdUcE5yijgc3E17PNdBJwwNLZfP\r\n' +
+    'p9jB/ZPaNMduGqltYamOVkbg/qS7O4rcUHstrGGGtJ9fveH+cu87emMCgYEA6/oX\r\n' +
+    '6A2fW88hw4hZwV2pCk6gumi89vXbKbhnD6j907TW583xIqXYsQBb7x/KGyPf65+k\r\n' +
+    'Sou9MRyLhRu7qcc8INpSnFsra8ZQosP+ao8tUTq7p7N0H27qG5liTeAAksvk/xnq\r\n' +
+    '2VdL1YDRpo4tmRD7TAj8uc1sgXqdsBCPrqq4Q1kCgYAHcNjwEmGEymOA+aNh/jEc\r\n' +
+    'Gngfofs2zUiJdncBD6RxFmJ/6auP7ryZJJoNf1XaqmrmmecWcsOliX1qbg4RCi0e\r\n' +
+    'ye+jzYWVcYNpJXIVfjfD1aTFq0QYW2pgcHL88/am2l1SalPWxRt/IOw2Rh8OJCTC\r\n' +
+    'QBZWDiTSFXceYPus0hZUmwKBgCc2FYbfzJ0q3Adrvs5cy9wEmLyg7tVyoQpbs/Rs\r\n' +
+    'NlFZeWRnWixRtqIi1yPy+lhsK6cxjdE9SyDAB4cExrg9fQZQgO2uUJbGC1wgiUQX\r\n' +
+    'qoYW5lvFfARFH+2aHTWnhTDfZJvnKJkY4mcF0tCES5tlsPw/eg89zUvunglFlzqE\r\n' +
+    '771xAoGAdYRG1PIkAzjuh785rc35885dsaXChZx1+7rfZ+AclyisRsmES9UfqL6M\r\n' +
+    '+SuluaBSWJQUBS7iaHazUeXmy5t+HBjSSLuAOHoZUckwDWD3EM7GHjFlWJOCArI3\r\n' +
+    'hOYlsXSyl07rApzg/t+HxXcNpLZGJTgRwrRGF2OipUL0VPlTdRc=\r\n' +
+    '-----END RSA PRIVATE KEY-----\r\n';
+
+  var key = forge.pki.privateKeyFromPem(keystr);
+
+  describe('ssh', function() {
+    it('should convert keys to openssh public keys', function() {
+      var expect =
+        'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDfTU74sGd3qeDD2LQ9vmJPCVD' +
+        'WzftrLcM2mKyOH6M8MO9ohmo8L7jhhXtFpHWJvbAShB+DgPCpXDnCUDbH3tPGeB' +
+        'v6wl7TJR+Yz28xPGFda5RFVHroMZ3y3Z44E3yoOhiWm91V/wajkG2tvo0J7FRtm' +
+        '0VLUCa6YrbNArEiLOBUsrx3HgY1hurGJtTGBgdvBGm7xE6uiBWtlsxbVw5vgkT4' +
+        'PMbUW98dJ3OTFv17kBFyy4vEJ08i1QxVjyOOARHpvmUr43OvnuEV2vTxGNYYGV0' +
+        '0asIWoN2b7VaQYAdNGuD+L8wPwFTw0IVQGlBNKuEAuQaoF0Zk838+teideOFx3X' +
+        'Vr A comment';
+
+      ASSERT.equal(forge.ssh.publicKeyToOpenSSH(key, 'A comment'), expect);
+    });
+
+    it('should convert keys to putty unencrypted keys', function() {
+      var expect =
+        'PuTTY-User-Key-File-2: ssh-rsa\r\n' +
+        'Encryption: none\r\n' +
+        'Comment: imported-openssh-key\r\n' +
+        'Public-Lines: 6\r\n' +
+        'AAAAB3NzaC1yc2EAAAADAQABAAABAQDfTU74sGd3qeDD2LQ9vmJPCVDWzftrLcM2\r\n' +
+        'mKyOH6M8MO9ohmo8L7jhhXtFpHWJvbAShB+DgPCpXDnCUDbH3tPGeBv6wl7TJR+Y\r\n' +
+        'z28xPGFda5RFVHroMZ3y3Z44E3yoOhiWm91V/wajkG2tvo0J7FRtm0VLUCa6YrbN\r\n' +
+        'ArEiLOBUsrx3HgY1hurGJtTGBgdvBGm7xE6uiBWtlsxbVw5vgkT4PMbUW98dJ3OT\r\n' +
+        'Fv17kBFyy4vEJ08i1QxVjyOOARHpvmUr43OvnuEV2vTxGNYYGV00asIWoN2b7VaQ\r\n' +
+        'YAdNGuD+L8wPwFTw0IVQGlBNKuEAuQaoF0Zk838+teideOFx3XVr\r\n' +
+        'Private-Lines: 14\r\n' +
+        'AAABAHGMESUaJnLN2jIcRoLDBaBk/0tLEJaOfZ6Mgen/InUf+Q0wlGKobZ2Xz3g5\r\n' +
+        'SV9SKm8v6gpnjXjBIcmyGjkGEK/yMWAQaEF7thZxHHxv1J65bnrWm2zolgWCNcsT\r\n' +
+        '9aZhbFFhTmpFNO4FKhBYPcWW+9OESfci+Z57RbL3tHTJVwUZrthtagpFEgVceq18\r\n' +
+        'GLSS9N4TWJ8HX1CDxf9R4gzEy3+8oC0QONx+bgsWUmzNk9QNNW6yPV5NYZ3SwNLV\r\n' +
+        'gw3+6m8WDaqo5FWK7y2fpa63DUzXlg5uSYnk4OFVxI6TLpmNQcjigpXnsd1Pwcyn\r\n' +
+        'W0AM83jzIYu0F31ppU618flrqfEAAACBAPI/46FL3tReJU7dlN+ERfufYImda9DB\r\n' +
+        'OC2nzRIDf2SzAfIIU6Eqq6szZyZ0qaZACk2tjVWRoCZL6p/qsrDNRzZv95LHVHBO\r\n' +
+        'coo4HNxNezzXQScMDS2Xz6fYwf2T2jTHbhqpbWGpjlZG4P6kuzuK3FB7LaxhhrSf\r\n' +
+        'X73h/nLvO3pjAAAAgQDr+hfoDZ9bzyHDiFnBXakKTqC6aLz29dspuGcPqP3TtNbn\r\n' +
+        'zfEipdixAFvvH8obI9/rn6RKi70xHIuFG7upxzwg2lKcWytrxlCiw/5qjy1ROrun\r\n' +
+        's3QfbuobmWJN4ACSy+T/GerZV0vVgNGmji2ZEPtMCPy5zWyBep2wEI+uqrhDWQAA\r\n' +
+        'AIB1hEbU8iQDOO6Hvzmtzfnzzl2xpcKFnHX7ut9n4ByXKKxGyYRL1R+ovoz5K6W5\r\n' +
+        'oFJYlBQFLuJodrNR5ebLm34cGNJIu4A4ehlRyTANYPcQzsYeMWVYk4ICsjeE5iWx\r\n' +
+        'dLKXTusCnOD+34fFdw2ktkYlOBHCtEYXY6KlQvRU+VN1Fw==\r\n' +
+        'Private-MAC: 87fa1011848453317d8e41b00c927e9d17dc334e\r\n';
+
+      ASSERT.equal(forge.ssh.privateKeyToPutty(key, '', 'imported-openssh-key'), expect);
+    });
+
+    it('should convert keys to putty encrypted keys', function() {
+      var expect =
+        'PuTTY-User-Key-File-2: ssh-rsa\r\n' +
+        'Encryption: aes256-cbc\r\n' +
+        'Comment: imported-openssh-key\r\n' +
+        'Public-Lines: 6\r\n' +
+        'AAAAB3NzaC1yc2EAAAADAQABAAABAQDfTU74sGd3qeDD2LQ9vmJPCVDWzftrLcM2\r\n' +
+        'mKyOH6M8MO9ohmo8L7jhhXtFpHWJvbAShB+DgPCpXDnCUDbH3tPGeBv6wl7TJR+Y\r\n' +
+        'z28xPGFda5RFVHroMZ3y3Z44E3yoOhiWm91V/wajkG2tvo0J7FRtm0VLUCa6YrbN\r\n' +
+        'ArEiLOBUsrx3HgY1hurGJtTGBgdvBGm7xE6uiBWtlsxbVw5vgkT4PMbUW98dJ3OT\r\n' +
+        'Fv17kBFyy4vEJ08i1QxVjyOOARHpvmUr43OvnuEV2vTxGNYYGV00asIWoN2b7VaQ\r\n' +
+        'YAdNGuD+L8wPwFTw0IVQGlBNKuEAuQaoF0Zk838+teideOFx3XVr\r\n' +
+        'Private-Lines: 14\r\n' +
+        'EiVwpacmA7mhmGBTPXeIZZPkeRDtb4LOWzI+68cA5oM7UJTpBxh9zsnpAdWg2knP\r\n' +
+        'snA5gpTSq0CJV9HWb8yAY3R5izflQ493fCbJzuPFTW6RJ2y/D5dUmDWccfMczPNT\r\n' +
+        'GEDOfslCPe+Yz1h0y0y5NI5WwJowxv3nL9FTlQ9KDVrdoSizkVbTh4CJTrvqIc7c\r\n' +
+        'sI/HU25OHS8kcIZPiPL8m40ot254rkbPCWrskm9H4n+EC+BwJNtEic9ZQfGPWOVl\r\n' +
+        't8JxY35mHqGIlfhyVkdts/Rx7kwOHY+GPXDVRnHQZQOkFtVqCFGx8mL83CspIEq0\r\n' +
+        'V8LaHuvxiadA4OEeR0azuDhfVJXvrUpHd4CPjAzJu4doHm98GJAyrz3mtCyxIguH\r\n' +
+        'k8zKVJzbNawy5T43l5x9cR6VKzcl6d4V14vphiovxc8DG/J7RHBd4d1y6wxo5ZMY\r\n' +
+        'qqQ0E6VHtq6auBZjnGzx0P/1lGjpZdxUf4OVTaZ+stCxX5wAH9exF+gdZAlk20Gp\r\n' +
+        'Atg60boQuolHBiH66dQynyHp+6yuLPLKXy74EO+AEB3HvNK7aIQV8rCD7I7HEa12\r\n' +
+        'xxqIH4l0FWQKHXNIrK45uo6Hdg1siYp9zU4FFgDcNGOZJsT6+etPp1sgAeBuBR4F\r\n' +
+        'pnuX1KZzRTbG1kcRrTOjsMh0bKfZAn0+uwyuPBtwEnLziGoCXU+9+XO85zcPF2j1\r\n' +
+        'Ip5AWAOBI82SKMLu51Dpdz1rwSZOPFWnHxRxfnUiQa9Kc7qBGrMxy1UNECAwMGzp\r\n' +
+        'ljKesqZvoANo0voiodALXGs7kSpmXjcbHnUUt0SI/QHyajXabIiLHGf6lfvStYLP\r\n' +
+        'L5bLfswsMqG/2lonYWrPl9YC0WzvfRpbHgpI9G1eLjFFRlaqJ3EpZf5Dy26Z0eh0\r\n' +
+        'Private-MAC: 23aa5b6e2a411107c59e1e6c3bca06247e3c9627\r\n';
+
+      ASSERT.equal(forge.ssh.privateKeyToPutty(key, 'passphrase', 'imported-openssh-key'), expect);
+    });
+
+    it('should convert keys to openssh encrypted private keys', function() {
+      var expect =
+        '-----BEGIN RSA PRIVATE KEY-----\n' +
+        'Proc-Type: 4,ENCRYPTED\n' +
+        'DEK-Info: AES-128-CBC,2616162F269429AA628E42C3BD5A0027\n' +
+        '\n' +
+        'p8+mGWeQxZrRg6OeeFqgEX8sXGGUqWJuK4XhtgRpxAQaSg8bK6m/ahArEonjzgrO\n' +
+        'XMLow7N0aXqGJzL+n4c4EzL7e4SquzeYZLq0UCs8vbWE5GdTT6BxisWIJqzOaQW3\n' +
+        'd3OqS2lM5o47cuADMIMp015b0dJn5nwJall20GSI1XnpTUHIJ1oFv7fW/s5g39VD\n' +
+        'DSVmPzJEMhcTa8BskHrKITV6l+TuivGqrHH0LCYCfQ3IBLiRZrPINQLLkaHR6kis\n' +
+        '4qvFEMhQGAz0GrifwEob9+FPzDAHHnYTS0kG1jhZ3p92vaUi8sPxyv5ndRXOSZZg\n' +
+        'vh6Cdrk62myG/rHbsBRrrpa+Ka+BX4ofedwP3SBHPwqBpksYhEF7MxsWKhmHY+d0\n' +
+        'YINHrj0w+yfw4H3n1+0w4wajlHVUncp7RP8KKMtG3vvvfF1loWpLbyF0s6fgq7p4\n' +
+        '7kt1LcnRKB3U2IZYfMHuv94+5q0BKfGF6NmRpmgdJojyS2IXZyFaJRqrCa0mKcP9\n' +
+        'PtDZFPTEy0VeNTL8KqjweEmjK3/JtZPVgXXkPWlFMw3Hc/dtP/l6VssGLk8P/DHD\n' +
+        'yknagPsfa70iydTvDO+nrtS57DkoUqhMsU3BhCjMzZg+/bo1JgmxQUWT//PoQLSB\n' +
+        'Z7/F59AfHzZIkWgqfvGRzX3y+G1M1l11dX658iWmN0kZ5MyHU0qwU9hVI6P/fcfk\n' +
+        '6MQKc/MzPnkjw/knREtYMtHrVsKrDVDizfguGFKFC8FVhhrDOFZUnza0nh6nt1HZ\n' +
+        'Xk156MhATenWdHBW4Rn3ec1aMOD16k2SRIHd+nyJzx51H3PUdTtXBWqFNGggzoJG\n' +
+        '99ax3jD6pTLQY3BG146QKQ0csItMTIdwZPAidkzv8VVXC7HaqXk1K1pgfJT6mD4P\n' +
+        'LaNbuA9r7mNiNoPzwzk0h3BomBTMXZpAyL9Jlre9jTu6lpyN/TkOzHhs/I1/lvKQ\n' +
+        'Uki7BXv65Jq6RqkTbNc5plxBYggdzLGurr0ZIBDsoN6uXkzaM+fCMlJU8+MgAcBb\n' +
+        'x88bj8h3t4akPd/WaSsWKeOzB3Uaw3ztYCpwSVv1F+N0u6C6zGo+9VFAQZh1uKvC\n' +
+        'G9U5hvAG7WEoQ801/fvKj93lqLDhOarPJC8CcfMLwoIqj7zah7TtBYq8VsuF7Fsx\n' +
+        'dEWNFiXhtFqUhgBMt1tcOXGiXA79NucSVzUBNzrRZKlLDYm2TKKyOqEbG9vF/YxL\n' +
+        'ZG3TwmcFj0sko2K61ya7FGpKmfPQCJJoi0tIfnW4eIVcyeRpXYi5B2xqvSXcTdUx\n' +
+        '5y5Vpuu1CzrMZr50b3sSOFjcOXE5211RS8SHpOMWY+JDDB4vF4Dv94fqEIgnFtrR\n' +
+        'oQgk3DueWb1x09NcJtEZsW6lT3Jw19ursb++XSejFZ9Xu5ED8fbewgGo2w/N5j1H\n' +
+        'vQEnFkGcL1jLlLqp9PlvPIE4a///wy1y0XbnKMJs+dKxiesKVx1zZ1WDcK2Qgv4r\n' +
+        'G+RsZzHZuCjUyty1+SMVOYM6+3zW6bjXN58xI3XeSxgE/JaJKjLWBZWx5+eU7b6a\n' +
+        '04mJDMhnpdLHG97m9p90L1yuudiJfq6ngha41xxv9xLmNatfrtStCrq/DR0KHm0K\n' +
+        '-----END RSA PRIVATE KEY-----\n';
+
+      // Unable to test -- uses a random IV that I can't control
+      //ASSERT.equal(forge.ssh.rsaPrivateKeyAsOpenSSH(key, 'passphrase'), expect);
+    });
+
+    it('should convert keys to openssh unencrypted private keys', function() {
+      var expect = keystr;
+      ASSERT.equal(forge.ssh.privateKeyToOpenSSH(key, ''), expect);
+    });
+
+    it('should get an MD5 SSH fingerprint', function() {
+      var fp = forge.ssh.getPublicKeyFingerprint(key);
+      ASSERT.equal(fp.toHex(), '46549abeb89422a0955d4041ae7322ec');
+    });
+
+    it('should get a hex MD5 SSH fingerprint', function() {
+      var fp = forge.ssh.getPublicKeyFingerprint(key, {encoding: 'hex'});
+      ASSERT.equal(fp, '46549abeb89422a0955d4041ae7322ec');
+    });
+
+    it('should get a hex, colon-delimited MD5 SSH fingerprint', function() {
+      var fp = forge.ssh.getPublicKeyFingerprint(
+        key, {encoding: 'hex', delimiter: ':'});
+      ASSERT.equal(fp, '46:54:9a:be:b8:94:22:a0:95:5d:40:41:ae:73:22:ec');
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/forge'
+  ], function(forge) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      forge
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/forge'));
+}
+
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/tls.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/tls.js
new file mode 100644
index 0000000..d9ce944
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/tls.js
@@ -0,0 +1,191 @@
+(function() {
+
+function Tests(ASSERT, forge) {
+  describe('tls', function() {
+    it('should test TLS 1.0 PRF', function() {
+      // Note: This test vector is originally from:
+      // http://www.imc.org/ietf-tls/mail-archive/msg01589.html
+      // But that link is now dead.
+      var secret = forge.util.createBuffer().fillWithByte(0xAB, 48).getBytes();
+      var seed = forge.util.createBuffer().fillWithByte(0xCD, 64).getBytes();
+      var bytes = forge.tls.prf_tls1(secret, 'PRF Testvector',  seed, 104);
+      var expect =
+        'd3d4d1e349b5d515044666d51de32bab258cb521' +
+        'b6b053463e354832fd976754443bcf9a296519bc' +
+        '289abcbc1187e4ebd31e602353776c408aafb74c' +
+        'bc85eff69255f9788faa184cbb957a9819d84a5d' +
+        '7eb006eb459d3ae8de9810454b8b2d8f1afbc655' +
+        'a8c9a013';
+      ASSERT.equal(bytes.toHex(), expect);
+    });
+
+    it('should establish a TLS connection and transfer data', function(done) {
+      var end = {};
+      var data = {};
+
+      createCertificate('server', data);
+      createCertificate('client', data);
+      data.client.connection = {};
+      data.server.connection = {};
+
+      end.client = forge.tls.createConnection({
+        server: false,
+        caStore: [data.server.cert],
+        sessionCache: {},
+        cipherSuites: [
+          forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+          forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+        virtualHost: 'server',
+        verify: function(c, verified, depth, certs) {
+          data.client.connection.commonName =
+            certs[0].subject.getField('CN').value;
+          data.client.connection.certVerified = verified;
+          return true;
+        },
+        connected: function(c) {
+          c.prepare('Hello Server');
+        },
+        getCertificate: function(c, hint) {
+          return data.client.cert;
+        },
+        getPrivateKey: function(c, cert) {
+          return data.client.privateKey;
+        },
+        tlsDataReady: function(c) {
+          end.server.process(c.tlsData.getBytes());
+        },
+        dataReady: function(c) {
+          data.client.connection.data = c.data.getBytes();
+          c.close();
+        },
+        closed: function(c) {
+          ASSERT.equal(data.client.connection.commonName, 'server');
+          ASSERT.equal(data.client.connection.certVerified, true);
+          ASSERT.equal(data.client.connection.data, 'Hello Client');
+          done();
+        },
+        error: function(c, error) {
+          ASSERT.equal(error.message, undefined);
+        }
+      });
+
+      end.server = forge.tls.createConnection({
+        server: true,
+        caStore: [data.client.cert],
+        sessionCache: {},
+        cipherSuites: [
+          forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+          forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+        connected: function(c) {
+        },
+        verifyClient: true,
+        verify: function(c, verified, depth, certs) {
+          data.server.connection.commonName =
+            certs[0].subject.getField('CN').value;
+          data.server.connection.certVerified = verified;
+          return true;
+        },
+        getCertificate: function(c, hint) {
+          data.server.connection.certHint = hint[0];
+          return data.server.cert;
+        },
+        getPrivateKey: function(c, cert) {
+          return data.server.privateKey;
+        },
+        tlsDataReady: function(c) {
+          end.client.process(c.tlsData.getBytes());
+        },
+        dataReady: function(c) {
+          data.server.connection.data = c.data.getBytes();
+          c.prepare('Hello Client');
+          c.close();
+        },
+        closed: function(c) {
+          ASSERT.equal(data.server.connection.certHint, 'server');
+          ASSERT.equal(data.server.connection.commonName, 'client');
+          ASSERT.equal(data.server.connection.certVerified, true);
+          ASSERT.equal(data.server.connection.data, 'Hello Server');
+        },
+        error: function(c, error) {
+          ASSERT.equal(error.message, undefined);
+        }
+      });
+
+      end.client.handshake();
+
+      function createCertificate(cn, data) {
+        var keys = forge.pki.rsa.generateKeyPair(512);
+        var cert = forge.pki.createCertificate();
+        cert.publicKey = keys.publicKey;
+        cert.serialNumber = '01';
+        cert.validity.notBefore = new Date();
+        cert.validity.notAfter = new Date();
+        cert.validity.notAfter.setFullYear(
+          cert.validity.notBefore.getFullYear() + 1);
+        var attrs = [{
+          name: 'commonName',
+          value: cn
+        }, {
+          name: 'countryName',
+          value: 'US'
+        }, {
+          shortName: 'ST',
+          value: 'Virginia'
+        }, {
+          name: 'localityName',
+          value: 'Blacksburg'
+        }, {
+          name: 'organizationName',
+          value: 'Test'
+        }, {
+          shortName: 'OU',
+          value: 'Test'
+        }];
+        cert.setSubject(attrs);
+        cert.setIssuer(attrs);
+        cert.setExtensions([{
+          name: 'basicConstraints',
+          cA: true
+        }, {
+          name: 'keyUsage',
+          keyCertSign: true,
+          digitalSignature: true,
+          nonRepudiation: true,
+          keyEncipherment: true,
+          dataEncipherment: true
+        }, {
+          name: 'subjectAltName',
+          altNames: [{
+            type: 6, // URI
+            value: 'https://myuri.com/webid#me'
+          }]
+        }]);
+        cert.sign(keys.privateKey);
+        data[cn] = {
+          cert: forge.pki.certificateToPem(cert),
+          privateKey: forge.pki.privateKeyToPem(keys.privateKey)
+        };
+      }
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/forge'
+  ], function(forge) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      forge
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/forge'));
+}
+
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/util.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/util.js
new file mode 100644
index 0000000..57104a1
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/util.js
@@ -0,0 +1,406 @@
+(function() {
+
+function Tests(ASSERT, UTIL) {
+  // custom assertion to test array-like objects
+  function assertArrayEqual(actual, expected) {
+    ASSERT.equal(actual.length, expected.length);
+    for (var idx = 0; idx < expected.length; idx++) {
+      ASSERT.equal(actual[idx], expected[idx]);
+    }
+  }
+
+  describe('util', function() {
+    it('should put bytes into a buffer', function() {
+      var b = UTIL.createBuffer();
+      b.putByte(1);
+      b.putByte(2);
+      b.putByte(3);
+      b.putByte(4);
+      b.putInt32(4);
+      b.putByte(1);
+      b.putByte(2);
+      b.putByte(3);
+      b.putInt32(4294967295);
+      var hex = b.toHex();
+      ASSERT.equal(hex, '0102030400000004010203ffffffff');
+
+      var bytes = [];
+      while(b.length() > 0) {
+        bytes.push(b.getByte());
+      }
+      ASSERT.deepEqual(
+        bytes, [1, 2, 3, 4, 0, 0, 0, 4, 1, 2, 3, 255, 255, 255, 255]);
+    });
+
+    it('should put bytes from an Uint8Array into a buffer', function() {
+      if(typeof Uint8Array === 'undefined') {
+        return;
+      }
+      var data = [1, 2, 3, 4, 0, 0, 0, 4, 1, 2, 3, 255, 255, 255, 255];
+      var ab = new Uint8Array(data);
+      var b = UTIL.createBuffer(ab);
+      var hex = b.toHex();
+      ASSERT.equal(hex, '0102030400000004010203ffffffff');
+
+      var bytes = [];
+      while(b.length() > 0) {
+        bytes.push(b.getByte());
+      }
+      ASSERT.deepEqual(bytes, data);
+    });
+
+    it('should convert bytes from hex', function() {
+      var hex = '0102030400000004010203ffffffff';
+      var b = UTIL.createBuffer();
+      b.putBytes(UTIL.hexToBytes(hex));
+      ASSERT.equal(b.toHex(), hex);
+    });
+
+    it('should put 0 into a buffer using two\'s complement', function() {
+      var b = UTIL.createBuffer();
+      b.putSignedInt(0, 8);
+      ASSERT.equal(b.toHex(), '00');
+    });
+
+    it('should put 0 into a buffer using two\'s complement w/2 bytes', function() {
+      var b = UTIL.createBuffer();
+      b.putSignedInt(0, 16);
+      ASSERT.equal(b.toHex(), '0000');
+    });
+
+    it('should put 127 into a buffer using two\'s complement', function() {
+      var b = UTIL.createBuffer();
+      b.putSignedInt(127, 8);
+      ASSERT.equal(b.toHex(), '7f');
+    });
+
+    it('should put 127 into a buffer using two\'s complement w/2 bytes', function() {
+      var b = UTIL.createBuffer();
+      b.putSignedInt(127, 16);
+      ASSERT.equal(b.toHex(), '007f');
+    });
+
+    it('should put 128 into a buffer using two\'s complement', function() {
+      var b = UTIL.createBuffer();
+      b.putSignedInt(128, 16);
+      ASSERT.equal(b.toHex(), '0080');
+    });
+
+    it('should put 256 into a buffer using two\'s complement', function() {
+      var b = UTIL.createBuffer();
+      b.putSignedInt(256, 16);
+      ASSERT.equal(b.toHex(), '0100');
+    });
+
+    it('should put -128 into a buffer using two\'s complement', function() {
+      var b = UTIL.createBuffer();
+      b.putSignedInt(-128, 8);
+      ASSERT.equal(b.toHex(), '80');
+    });
+
+    it('should put -129 into a buffer using two\'s complement', function() {
+      var b = UTIL.createBuffer();
+      b.putSignedInt(-129, 16);
+      ASSERT.equal(b.toHex(), 'ff7f');
+    });
+
+    it('should get 0 from a buffer using two\'s complement', function() {
+      var x = 0;
+      var n = 8;
+      var b = UTIL.createBuffer();
+      b.putSignedInt(x, n);
+      ASSERT.equal(b.getSignedInt(x, n), x);
+    });
+
+    it('should get 127 from a buffer using two\'s complement', function() {
+      var x = 127;
+      var n = 8;
+      var b = UTIL.createBuffer();
+      b.putSignedInt(x, n);
+      ASSERT.equal(b.getSignedInt(n), x);
+    });
+
+    it('should get 128 from a buffer using two\'s complement', function() {
+      var x = 128;
+      var n = 16;
+      var b = UTIL.createBuffer();
+      b.putSignedInt(x, n);
+      ASSERT.equal(b.getSignedInt(n), x);
+    });
+
+    it('should get 256 from a buffer using two\'s complement', function() {
+      var x = 256;
+      var n = 16;
+      var b = UTIL.createBuffer();
+      b.putSignedInt(x, n);
+      ASSERT.equal(b.getSignedInt(n), x);
+    });
+
+    it('should get -128 from a buffer using two\'s complement', function() {
+      var x = -128;
+      var n = 8;
+      var b = UTIL.createBuffer();
+      b.putSignedInt(x, n);
+      ASSERT.equal(b.getSignedInt(n), x);
+    });
+
+    it('should get -129 from a buffer using two\'s complement', function() {
+      var x = -129;
+      var n = 16;
+      var b = UTIL.createBuffer();
+      b.putSignedInt(x, n);
+      ASSERT.equal(b.getSignedInt(n), x);
+    });
+
+    it('should base64 encode some bytes', function() {
+      var s1 = '00010203050607080A0B0C0D0F1011121415161719';
+      var s2 = 'MDAwMTAyMDMwNTA2MDcwODBBMEIwQzBEMEYxMDExMTIxNDE1MTYxNzE5';
+      ASSERT.equal(UTIL.encode64(s1), s2);
+    });
+
+    it('should base64 decode some bytes', function() {
+      var s1 = '00010203050607080A0B0C0D0F1011121415161719';
+      var s2 = 'MDAwMTAyMDMwNTA2MDcwODBBMEIwQzBEMEYxMDExMTIxNDE1MTYxNzE5';
+      ASSERT.equal(UTIL.decode64(s2), s1);
+    });
+    
+    it('should base64 encode some bytes using util.binary.base64', function() {
+      var s1 = new Uint8Array([
+        0x30, 0x30, 0x30, 0x31, 0x30, 0x32, 0x30, 0x33, 0x30,
+        0x35, 0x30, 0x36, 0x30, 0x37, 0x30, 0x38, 0x30, 0x41,
+        0x30, 0x42, 0x30, 0x43, 0x30, 0x44, 0x30, 0x46, 0x31,
+        0x30, 0x31, 0x31, 0x31, 0x32, 0x31, 0x34, 0x31, 0x35,
+        0x31, 0x36, 0x31, 0x37, 0x31, 0x39]);
+      var s2 = 'MDAwMTAyMDMwNTA2MDcwODBBMEIwQzBEMEYxMDExMTIxNDE1MTYxNzE5';
+      ASSERT.equal(UTIL.binary.base64.encode(s1), s2);
+    });
+
+    it('should base64 encode some odd-length bytes using util.binary.base64', function() {
+      var s1 = new Uint8Array([
+        0x30, 0x30, 0x30, 0x31, 0x30, 0x32, 0x30, 0x33, 0x30,
+        0x35, 0x30, 0x36, 0x30, 0x37, 0x30, 0x38, 0x30, 0x41,
+        0x30, 0x42, 0x30, 0x43, 0x30, 0x44, 0x30, 0x46, 0x31,
+        0x30, 0x31, 0x31, 0x31, 0x32, 0x31, 0x34, 0x31, 0x35,
+        0x31, 0x36, 0x31, 0x37, 0x31, 0x39, 0x31, 0x41, 0x31,
+        0x42]);
+      var s2 = 'MDAwMTAyMDMwNTA2MDcwODBBMEIwQzBEMEYxMDExMTIxNDE1MTYxNzE5MUExQg==';
+      ASSERT.equal(UTIL.binary.base64.encode(s1), s2);
+    });
+
+    it('should base64 decode some bytes using util.binary.base64', function() {
+      var s1 = new Uint8Array([
+        0x30, 0x30, 0x30, 0x31, 0x30, 0x32, 0x30, 0x33, 0x30,
+        0x35, 0x30, 0x36, 0x30, 0x37, 0x30, 0x38, 0x30, 0x41,
+        0x30, 0x42, 0x30, 0x43, 0x30, 0x44, 0x30, 0x46, 0x31,
+        0x30, 0x31, 0x31, 0x31, 0x32, 0x31, 0x34, 0x31, 0x35,
+        0x31, 0x36, 0x31, 0x37, 0x31, 0x39]);
+      var s2 = 'MDAwMTAyMDMwNTA2MDcwODBBMEIwQzBEMEYxMDExMTIxNDE1MTYxNzE5';
+      ASSERT.deepEqual(UTIL.binary.base64.decode(s2), s1);
+    });
+      
+    it('should base64 decode some odd-length bytes using util.binary.base64', function() {
+      var s1 = new Uint8Array([
+        0x30, 0x30, 0x30, 0x31, 0x30, 0x32, 0x30, 0x33, 0x30,
+        0x35, 0x30, 0x36, 0x30, 0x37, 0x30, 0x38, 0x30, 0x41,
+        0x30, 0x42, 0x30, 0x43, 0x30, 0x44, 0x30, 0x46, 0x31,
+        0x30, 0x31, 0x31, 0x31, 0x32, 0x31, 0x34, 0x31, 0x35,
+        0x31, 0x36, 0x31, 0x37, 0x31, 0x39, 0x31, 0x41, 0x31,
+        0x42]);
+      var s2 = 'MDAwMTAyMDMwNTA2MDcwODBBMEIwQzBEMEYxMDExMTIxNDE1MTYxNzE5MUExQg==';
+      assertArrayEqual(UTIL.binary.base64.decode(s2), s1);
+    });
+      
+    it('should convert IPv4 0.0.0.0 textual address to 4-byte address', function() {
+      var bytes = UTIL.bytesFromIP('0.0.0.0');
+      var b = UTIL.createBuffer().fillWithByte(0, 4);
+      ASSERT.equal(bytes, b.getBytes());
+    });
+
+    it('should convert IPv4 127.0.0.1 textual address to 4-byte address', function() {
+      var bytes = UTIL.bytesFromIP('127.0.0.1');
+      var b = UTIL.createBuffer();
+      b.putByte(127);
+      b.putByte(0);
+      b.putByte(0);
+      b.putByte(1);
+      ASSERT.equal(bytes, b.getBytes());
+    });
+
+    it('should convert IPv6 :: textual address to 16-byte address', function() {
+      var bytes = UTIL.bytesFromIP('::');
+      var b = UTIL.createBuffer().fillWithByte(0, 16);
+      ASSERT.equal(bytes, b.getBytes());
+    });
+
+    it('should convert IPv6 ::0 textual address to 16-byte address', function() {
+      var bytes = UTIL.bytesFromIP('::0');
+      var b = UTIL.createBuffer().fillWithByte(0, 16);
+      ASSERT.equal(bytes, b.getBytes());
+    });
+
+    it('should convert IPv6 0:: textual address to 16-byte address', function() {
+      var bytes = UTIL.bytesFromIP('0::');
+      var b = UTIL.createBuffer().fillWithByte(0, 16);
+      ASSERT.equal(bytes, b.getBytes());
+    });
+
+    it('should convert IPv6 ::1 textual address to 16-byte address', function() {
+      var bytes = UTIL.bytesFromIP('::1');
+      var b = UTIL.createBuffer().fillWithByte(0, 14);
+      b.putBytes(UTIL.hexToBytes('0001'));
+      ASSERT.equal(bytes, b.getBytes());
+    });
+
+    it('should convert IPv6 1:: textual address to 16-byte address', function() {
+      var bytes = UTIL.bytesFromIP('1::');
+      var b = UTIL.createBuffer();
+      b.putBytes(UTIL.hexToBytes('0001'));
+      b.fillWithByte(0, 14);
+      ASSERT.equal(bytes, b.getBytes());
+    });
+
+    it('should convert IPv6 1::1 textual address to 16-byte address', function() {
+      var bytes = UTIL.bytesFromIP('1::1');
+      var b = UTIL.createBuffer();
+      b.putBytes(UTIL.hexToBytes('0001'));
+      b.fillWithByte(0, 12);
+      b.putBytes(UTIL.hexToBytes('0001'));
+      ASSERT.equal(bytes, b.getBytes());
+    });
+
+    it('should convert IPv6 1::1:0 textual address to 16-byte address', function() {
+      var bytes = UTIL.bytesFromIP('1::1:0');
+      var b = UTIL.createBuffer();
+      b.putBytes(UTIL.hexToBytes('0001'));
+      b.fillWithByte(0, 10);
+      b.putBytes(UTIL.hexToBytes('0001'));
+      b.putBytes(UTIL.hexToBytes('0000'));
+      ASSERT.equal(bytes, b.getBytes());
+    });
+
+    it('should convert IPv6 2001:db8:0:1:1:1:1:1 textual address to 16-byte address', function() {
+      var bytes = UTIL.bytesFromIP('2001:db8:0:1:1:1:1:1');
+      var b = UTIL.createBuffer();
+      b.putBytes(UTIL.hexToBytes('2001'));
+      b.putBytes(UTIL.hexToBytes('0db8'));
+      b.putBytes(UTIL.hexToBytes('0000'));
+      b.putBytes(UTIL.hexToBytes('0001'));
+      b.putBytes(UTIL.hexToBytes('0001'));
+      b.putBytes(UTIL.hexToBytes('0001'));
+      b.putBytes(UTIL.hexToBytes('0001'));
+      b.putBytes(UTIL.hexToBytes('0001'));
+      ASSERT.equal(bytes, b.getBytes());
+    });
+
+    it('should convert IPv4 0.0.0.0 byte address to textual representation', function() {
+      var addr = '0.0.0.0';
+      var bytes = UTIL.createBuffer().fillWithByte(0, 4).getBytes();
+      var addr = UTIL.bytesToIP(bytes);
+      ASSERT.equal(addr, '0.0.0.0');
+    });
+
+    it('should convert IPv4 0.0.0.0 byte address to textual representation', function() {
+      var addr = '127.0.0.1';
+      var bytes = UTIL.bytesFromIP(addr);
+      var addr = UTIL.bytesToIP(bytes);
+      ASSERT.equal(addr, '127.0.0.1');
+    });
+
+    it('should convert IPv6 :: byte address to canonical textual representation (RFC 5952)', function() {
+      var addr = '::';
+      var bytes = UTIL.createBuffer().fillWithByte(0, 16).getBytes();
+      var addr = UTIL.bytesToIP(bytes);
+      ASSERT.equal(addr, '::');
+    });
+
+    it('should convert IPv6 ::1 byte address to canonical textual representation (RFC 5952)', function() {
+      var addr = '::1';
+      var bytes = UTIL.bytesFromIP(addr);
+      var addr = UTIL.bytesToIP(bytes);
+      ASSERT.equal(addr, '::1');
+    });
+
+    it('should convert IPv6 1:: byte address to canonical textual representation (RFC 5952)', function() {
+      var addr = '1::';
+      var bytes = UTIL.bytesFromIP(addr);
+      var addr = UTIL.bytesToIP(bytes);
+      ASSERT.equal(addr, '1::');
+    });
+
+    it('should convert IPv6 0:0:0:0:0:0:0:1 byte address to canonical textual representation (RFC 5952)', function() {
+      var addr = '0:0:0:0:0:0:0:1';
+      var bytes = UTIL.bytesFromIP(addr);
+      var addr = UTIL.bytesToIP(bytes);
+      ASSERT.equal(addr, '::1');
+    });
+
+    it('should convert IPv6 1:0:0:0:0:0:0:0 byte address to canonical textual representation (RFC 5952)', function() {
+      var addr = '1:0:0:0:0:0:0:0';
+      var bytes = UTIL.bytesFromIP(addr);
+      var addr = UTIL.bytesToIP(bytes);
+      ASSERT.equal(addr, '1::');
+    });
+
+    it('should convert IPv6 1::1 byte address to canonical textual representation (RFC 5952)', function() {
+      var addr = '1::1';
+      var bytes = UTIL.bytesFromIP(addr);
+      var addr = UTIL.bytesToIP(bytes);
+      ASSERT.equal(addr, '1::1');
+    });
+
+    it('should convert IPv6 1:0:0:0:0:0:0:1 byte address to canonical textual representation (RFC 5952)', function() {
+      var addr = '1:0:0:0:0:0:0:1';
+      var bytes = UTIL.bytesFromIP(addr);
+      var addr = UTIL.bytesToIP(bytes);
+      ASSERT.equal(addr, '1::1');
+    });
+
+    it('should convert IPv6 1:0000:0000:0000:0000:0000:0000:1 byte address to canonical textual representation (RFC 5952)', function() {
+      var addr = '1:0000:0000:0000:0000:0000:0000:1';
+      var bytes = UTIL.bytesFromIP(addr);
+      var addr = UTIL.bytesToIP(bytes);
+      ASSERT.equal(addr, '1::1');
+    });
+
+    it('should convert IPv6 1:0:0:1:1:1:0:1 byte address to canonical textual representation (RFC 5952)', function() {
+      var addr = '1:0:0:1:1:1:0:1';
+      var bytes = UTIL.bytesFromIP(addr);
+      var addr = UTIL.bytesToIP(bytes);
+      ASSERT.equal(addr, '1::1:1:1:0:1');
+    });
+
+    it('should convert IPv6 1:0:1:1:1:0:0:1 byte address to canonical textual representation (RFC 5952)', function() {
+      var addr = '1:0:1:1:1:0:0:1';
+      var bytes = UTIL.bytesFromIP(addr);
+      var addr = UTIL.bytesToIP(bytes);
+      ASSERT.equal(addr, '1:0:1:1:1::1');
+    });
+
+    it('should convert IPv6 2001:db8:0:1:1:1:1:1 byte address to canonical textual representation (RFC 5952)', function() {
+      var addr = '2001:db8:0:1:1:1:1:1';
+      var bytes = UTIL.bytesFromIP(addr);
+      var addr = UTIL.bytesToIP(bytes);
+      ASSERT.equal(addr, '2001:db8:0:1:1:1:1:1');
+    });
+  });
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/util'
+  ], function(UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      UTIL()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/util')());
+}
+
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/x509.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/x509.js
new file mode 100644
index 0000000..47a9e7f
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/test/x509.js
@@ -0,0 +1,734 @@
+(function() {
+
+function Tests(ASSERT, PKI, MD, UTIL) {
+  var _pem = {
+    privateKey: '-----BEGIN RSA PRIVATE KEY-----\r\n' +
+      'MIICXQIBAAKBgQDL0EugUiNGMWscLAVM0VoMdhDZEJOqdsUMpx9U0YZI7szokJqQ\r\n' +
+      'NIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMGTkP3VF29vXBo+dLq5e+8VyAy\r\n' +
+      'Q3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGtvnM+z0MYDdKo80efzwIDAQAB\r\n' +
+      'AoGAIzkGONi5G+JifmXlLJdplom486p3upf4Ce2/7mqfaG9MnkyPSairKD/JXvfh\r\n' +
+      'NNWkkN8DKKDKBcVVElPgORYT0qwrWc7ueLBMUCbRXb1ZyfEulimG0R3kjUh7NYau\r\n' +
+      'DaIkVgfykXGSQMZx8FoaT6L080zd+0emKDDYRrb+/kgJNJECQQDoUZoiC2K/DWNY\r\n' +
+      'h3/ppZ0ane2y4SBmJUHJVMPQ2CEgxsrJTxet668ckNCKaOP/3VFPoWC41f17DvKq\r\n' +
+      'noYINNntAkEA4JbZBZBVUrQFhHlrpXT4jzqtO2RlKZzEq8qmFZfEErxOT1WMyyCi\r\n' +
+      'lAQ5gUKardo1Kf0omC8Xq/uO9ZYdED55KwJBALs6cJ65UFaq4oLJiQPzLd7yokuE\r\n' +
+      'dcj8g71PLBTW6jPxIiMFNA89nz3FU9wIVp+xbMNhSoMMKqIPVPC+m0Rn260CQQDA\r\n' +
+      'I83fWK/mZWUjBM33a68KumRiH238v8XyQxj7+C8i6D8G2GXvkigFAehAkb7LZZd+\r\n' +
+      'KLuGFyPlWv3fVWHf99KpAkBQFKk3MRMl6IGJZUEFQe4l5whm8LkGU4acSqv9B3xt\r\n' +
+      'qROkCrsFrMPqjuuzEmyHoQZ64r2PLJg7FOuyhBnQUOt4\r\n' +
+      '-----END RSA PRIVATE KEY-----\r\n',
+    publicKey: '-----BEGIN PUBLIC KEY-----\r\n' +
+      'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDL0EugUiNGMWscLAVM0VoMdhDZ\r\n' +
+      'EJOqdsUMpx9U0YZI7szokJqQNIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMG\r\n' +
+      'TkP3VF29vXBo+dLq5e+8VyAyQ3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGt\r\n' +
+      'vnM+z0MYDdKo80efzwIDAQAB\r\n' +
+      '-----END PUBLIC KEY-----\r\n',
+    certificate: '-----BEGIN CERTIFICATE-----\r\n' +
+      'MIIDIjCCAougAwIBAgIJANE2aHSbwpaRMA0GCSqGSIb3DQEBBQUAMGoxCzAJBgNV\r\n' +
+      'BAYTAlVTMREwDwYDVQQIEwhWaXJnaW5pYTETMBEGA1UEBxMKQmxhY2tzYnVyZzEN\r\n' +
+      'MAsGA1UEChMEVGVzdDENMAsGA1UECxMEVGVzdDEVMBMGA1UEAxMMbXlzZXJ2ZXIu\r\n' +
+      'Y29tMB4XDTEwMDYxOTE3MzYyOFoXDTExMDYxOTE3MzYyOFowajELMAkGA1UEBhMC\r\n' +
+      'VVMxETAPBgNVBAgTCFZpcmdpbmlhMRMwEQYDVQQHEwpCbGFja3NidXJnMQ0wCwYD\r\n' +
+      'VQQKEwRUZXN0MQ0wCwYDVQQLEwRUZXN0MRUwEwYDVQQDEwxteXNlcnZlci5jb20w\r\n' +
+      'gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMvQS6BSI0YxaxwsBUzRWgx2ENkQ\r\n' +
+      'k6p2xQynH1TRhkjuzOiQmpA0jCiSJDoSic2dZIyUi/LjoCGeVFif57N5N5Tt4wZO\r\n' +
+      'Q/dUXb29cGj50url77xXIDJDcXMzXAji2ziFEAIXzDqarKBdDuL9IO7z+tepEa2+\r\n' +
+      'cz7PQxgN0qjzR5/PAgMBAAGjgc8wgcwwHQYDVR0OBBYEFPV1Y+DHXW6bA/r9sv1y\r\n' +
+      'NJ8jAwMAMIGcBgNVHSMEgZQwgZGAFPV1Y+DHXW6bA/r9sv1yNJ8jAwMAoW6kbDBq\r\n' +
+      'MQswCQYDVQQGEwJVUzERMA8GA1UECBMIVmlyZ2luaWExEzARBgNVBAcTCkJsYWNr\r\n' +
+      'c2J1cmcxDTALBgNVBAoTBFRlc3QxDTALBgNVBAsTBFRlc3QxFTATBgNVBAMTDG15\r\n' +
+      'c2VydmVyLmNvbYIJANE2aHSbwpaRMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEF\r\n' +
+      'BQADgYEARdH2KOlJWTC1CS2y/PAvg4uiM31PXMC1hqSdJlnLM1MY4hRfuf9VyTeX\r\n' +
+      'Y6FdybcyDLSxKn9id+g9229ci9/s9PI+QmD5vXd8yZyScLc2JkYB4GC6+9D1+/+x\r\n' +
+      's2hzMxuK6kzZlP+0l9LGcraMQPGRydjCARZZm4Uegln9rh85XFQ=\r\n' +
+      '-----END CERTIFICATE-----\r\n'
+  };
+
+  describe('x509', function() {
+    it('should convert certificate to/from PEM', function() {
+      var certificate = PKI.certificateFromPem(_pem.certificate);
+      ASSERT.equal(PKI.certificateToPem(certificate), _pem.certificate);
+    });
+
+    it('should verify self-signed certificate', function() {
+      var certificate = PKI.certificateFromPem(_pem.certificate);
+      ASSERT.ok(certificate.verify(certificate));
+    });
+
+    it('should generate and verify a self-signed certificate', function() {
+      var keys = {
+        privateKey: PKI.privateKeyFromPem(_pem.privateKey),
+        publicKey: PKI.publicKeyFromPem(_pem.publicKey)
+      };
+      var attrs = [{
+        name: 'commonName',
+        value: 'example.org'
+      }, {
+        name: 'countryName',
+        value: 'US'
+      }, {
+        shortName: 'ST',
+        value: 'Virginia'
+      }, {
+        name: 'localityName',
+        value: 'Blacksburg'
+      }, {
+        name: 'organizationName',
+        value: 'Test'
+      }, {
+        shortName: 'OU',
+        value: 'Test'
+      }];
+      var cert = createCertificate({
+        publicKey: keys.publicKey,
+        signingKey: keys.privateKey,
+        serialNumber: '01',
+        subject: attrs,
+        issuer: attrs,
+        isCA: true
+      });
+
+      var pem = PKI.certificateToPem(cert);
+      cert = PKI.certificateFromPem(pem);
+
+      // verify certificate chain
+      var caStore = PKI.createCaStore();
+      caStore.addCertificate(cert);
+      PKI.verifyCertificateChain(caStore, [cert], function(vfd, depth, chain) {
+        ASSERT.equal(vfd, true);
+        ASSERT.ok(cert.verifySubjectKeyIdentifier());
+        return true;
+      });
+    });
+
+    it('should generate and fail to verify a self-signed certificate that is not in the CA store', function() {
+      var keys = {
+        privateKey: PKI.privateKeyFromPem(_pem.privateKey),
+        publicKey: PKI.publicKeyFromPem(_pem.publicKey)
+      };
+      var attrs = [{
+        name: 'commonName',
+        value: 'example.org'
+      }, {
+        name: 'countryName',
+        value: 'US'
+      }, {
+        shortName: 'ST',
+        value: 'Virginia'
+      }, {
+        name: 'localityName',
+        value: 'Blacksburg'
+      }, {
+        name: 'organizationName',
+        value: 'Test'
+      }, {
+        shortName: 'OU',
+        value: 'Test'
+      }];
+      var cert = createCertificate({
+        publicKey: keys.publicKey,
+        signingKey: keys.privateKey,
+        serialNumber: '01',
+        subject: attrs,
+        issuer: attrs,
+        isCA: true
+      });
+
+      var pem = PKI.certificateToPem(cert);
+      cert = PKI.certificateFromPem(pem);
+
+      // verify certificate chain
+      var caStore = PKI.createCaStore();
+      PKI.verifyCertificateChain(caStore, [cert], function(vfd, depth, chain) {
+        ASSERT.equal(vfd, PKI.certificateError.unknown_ca);
+        return true;
+      });
+    });
+
+    it('should verify certificate chain ending with intermediate certificate from CA store', function() {
+      var keys = {
+        privateKey: PKI.privateKeyFromPem(_pem.privateKey),
+        publicKey: PKI.publicKeyFromPem(_pem.publicKey)
+      };
+      var entity = [{
+        name: 'commonName',
+        value: 'example.org'
+      }, {
+        name: 'countryName',
+        value: 'US'
+      }, {
+        shortName: 'ST',
+        value: 'Virginia'
+      }, {
+        name: 'localityName',
+        value: 'Blacksburg'
+      }, {
+        name: 'organizationName',
+        value: 'Test'
+      }, {
+        shortName: 'OU',
+        value: 'Test'
+      }];
+      var intermediate = [{
+        name: 'commonName',
+        value: 'intermediate'
+      }, {
+        name: 'countryName',
+        value: 'US'
+      }, {
+        shortName: 'ST',
+        value: 'Virginia'
+      }, {
+        name: 'localityName',
+        value: 'Blacksburg'
+      }, {
+        name: 'organizationName',
+        value: 'Test'
+      }, {
+        shortName: 'OU',
+        value: 'Test'
+      }];
+      var root = [{
+        name: 'commonName',
+        value: 'root'
+      }, {
+        name: 'countryName',
+        value: 'US'
+      }, {
+        shortName: 'ST',
+        value: 'Virginia'
+      }, {
+        name: 'localityName',
+        value: 'Blacksburg'
+      }, {
+        name: 'organizationName',
+        value: 'Test'
+      }, {
+        shortName: 'OU',
+        value: 'Test'
+      }];
+
+      var intermediateCert = createCertificate({
+        publicKey: keys.publicKey,
+        signingKey: keys.privateKey,
+        serialNumber: '01',
+        subject: intermediate,
+        issuer: root,
+        isCA: true
+      });
+
+      var entityCert = createCertificate({
+        publicKey: keys.publicKey,
+        signingKey: keys.privateKey,
+        serialNumber: '01',
+        subject: entity,
+        issuer: intermediate,
+        isCA: false
+      });
+
+      // verify certificate chain
+      var caStore = PKI.createCaStore();
+      caStore.addCertificate(intermediateCert);
+      var chain = [entityCert, intermediateCert];
+      PKI.verifyCertificateChain(caStore, chain, function(vfd, depth, chain) {
+        ASSERT.equal(vfd, true);
+        return true;
+      });
+    });
+
+    it('should fail to verify certificate chain ending with non-CA intermediate certificate from CA store', function() {
+      var keys = {
+        privateKey: PKI.privateKeyFromPem(_pem.privateKey),
+        publicKey: PKI.publicKeyFromPem(_pem.publicKey)
+      };
+      var entity = [{
+        name: 'commonName',
+        value: 'example.org'
+      }, {
+        name: 'countryName',
+        value: 'US'
+      }, {
+        shortName: 'ST',
+        value: 'Virginia'
+      }, {
+        name: 'localityName',
+        value: 'Blacksburg'
+      }, {
+        name: 'organizationName',
+        value: 'Test'
+      }, {
+        shortName: 'OU',
+        value: 'Test'
+      }];
+      var intermediate = [{
+        name: 'commonName',
+        value: 'intermediate'
+      }, {
+        name: 'countryName',
+        value: 'US'
+      }, {
+        shortName: 'ST',
+        value: 'Virginia'
+      }, {
+        name: 'localityName',
+        value: 'Blacksburg'
+      }, {
+        name: 'organizationName',
+        value: 'Test'
+      }, {
+        shortName: 'OU',
+        value: 'Test'
+      }];
+      var root = [{
+        name: 'commonName',
+        value: 'root'
+      }, {
+        name: 'countryName',
+        value: 'US'
+      }, {
+        shortName: 'ST',
+        value: 'Virginia'
+      }, {
+        name: 'localityName',
+        value: 'Blacksburg'
+      }, {
+        name: 'organizationName',
+        value: 'Test'
+      }, {
+        shortName: 'OU',
+        value: 'Test'
+      }];
+
+      var intermediateCert = createCertificate({
+        publicKey: keys.publicKey,
+        signingKey: keys.privateKey,
+        serialNumber: '01',
+        subject: intermediate,
+        issuer: root,
+        isCA: false
+      });
+
+      var entityCert = createCertificate({
+        publicKey: keys.publicKey,
+        signingKey: keys.privateKey,
+        serialNumber: '01',
+        subject: entity,
+        issuer: intermediate,
+        isCA: false
+      });
+
+      // verify certificate chain
+      var caStore = PKI.createCaStore();
+      caStore.addCertificate(intermediateCert);
+      var chain = [entityCert, intermediateCert];
+      PKI.verifyCertificateChain(caStore, chain, function(vfd, depth, chain) {
+        if(depth === 0) {
+          ASSERT.equal(vfd, true);
+        } else {
+          ASSERT.equal(vfd, PKI.certificateError.bad_certificate);
+        }
+        return true;
+      });
+    });
+
+    it('should verify certificate with sha1WithRSAEncryption signature', function() {
+      var certPem = '-----BEGIN CERTIFICATE-----\r\n' +
+        'MIIDZDCCAs2gAwIBAgIKQ8fjjgAAAABh3jANBgkqhkiG9w0BAQUFADBGMQswCQYD\r\n' +
+        'VQQGEwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzEiMCAGA1UEAxMZR29vZ2xlIElu\r\n' +
+        'dGVybmV0IEF1dGhvcml0eTAeFw0xMjA2MjcxMzU5MTZaFw0xMzA2MDcxOTQzMjda\r\n' +
+        'MGcxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1N\r\n' +
+        'b3VudGFpbiBWaWV3MRMwEQYDVQQKEwpHb29nbGUgSW5jMRYwFAYDVQQDEw13d3cu\r\n' +
+        'Z29vZ2xlLmRlMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCw2Hw3vNy5QMSd\r\n' +
+        '0/iMCS8lwZk9lnEk2NmrJt6vGJfRGlBprtHp5lpMFMoi+x8m8EwGVxXHGp7hLyN/\r\n' +
+        'gXuUjL7/DY9fxxx9l77D+sDZz7jfUfWmhS03Ra1FbT6myF8miVZFChJ8XgWzioJY\r\n' +
+        'gyNdRUC9149yrXdPWrSmSVaT0+tUCwIDAQABo4IBNjCCATIwHQYDVR0lBBYwFAYI\r\n' +
+        'KwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBTiQGhrO3785rMPIKZ/zQEl5RyS\r\n' +
+        '0TAfBgNVHSMEGDAWgBS/wDDr9UMRPme6npH7/Gra42sSJDBbBgNVHR8EVDBSMFCg\r\n' +
+        'TqBMhkpodHRwOi8vd3d3LmdzdGF0aWMuY29tL0dvb2dsZUludGVybmV0QXV0aG9y\r\n' +
+        'aXR5L0dvb2dsZUludGVybmV0QXV0aG9yaXR5LmNybDBmBggrBgEFBQcBAQRaMFgw\r\n' +
+        'VgYIKwYBBQUHMAKGSmh0dHA6Ly93d3cuZ3N0YXRpYy5jb20vR29vZ2xlSW50ZXJu\r\n' +
+        'ZXRBdXRob3JpdHkvR29vZ2xlSW50ZXJuZXRBdXRob3JpdHkuY3J0MAwGA1UdEwEB\r\n' +
+        '/wQCMAAwDQYJKoZIhvcNAQEFBQADgYEAVJ0qt/MBvHEPuWHeH51756qy+lBNygLA\r\n' +
+        'Xp5Gq+xHUTOzRty61BR05zv142hYAGWvpvnEOJ/DI7V3QlXK8a6dQ+du97obQJJx\r\n' +
+        '7ekqtfxVzmlSb23halYSoXmWgP8Tq0VUDsgsSLE7fS8JuO1soXUVKj1/6w189HL6\r\n' +
+        'LsngXwZSuL0=\r\n' +
+        '-----END CERTIFICATE-----\r\n';
+      var issuerPem = '-----BEGIN CERTIFICATE-----\r\n' +
+        'MIICsDCCAhmgAwIBAgIDC2dxMA0GCSqGSIb3DQEBBQUAME4xCzAJBgNVBAYTAlVT\r\n' +
+        'MRAwDgYDVQQKEwdFcXVpZmF4MS0wKwYDVQQLEyRFcXVpZmF4IFNlY3VyZSBDZXJ0\r\n' +
+        'aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDkwNjA4MjA0MzI3WhcNMTMwNjA3MTk0MzI3\r\n' +
+        'WjBGMQswCQYDVQQGEwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzEiMCAGA1UEAxMZ\r\n' +
+        'R29vZ2xlIEludGVybmV0IEF1dGhvcml0eTCBnzANBgkqhkiG9w0BAQEFAAOBjQAw\r\n' +
+        'gYkCgYEAye23pIucV+eEPkB9hPSP0XFjU5nneXQUr0SZMyCSjXvlKAy6rWxJfoNf\r\n' +
+        'NFlOCnowzdDXxFdF7dWq1nMmzq0yE7jXDx07393cCDaob1FEm8rWIFJztyaHNWrb\r\n' +
+        'qeXUWaUr/GcZOfqTGBhs3t0lig4zFEfC7wFQeeT9adGnwKziV28CAwEAAaOBozCB\r\n' +
+        'oDAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFL/AMOv1QxE+Z7qekfv8atrjaxIk\r\n' +
+        'MB8GA1UdIwQYMBaAFEjmaPkr0rKV10fYIyAQTzOYkJ/UMBIGA1UdEwEB/wQIMAYB\r\n' +
+        'Af8CAQAwOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2NybC5nZW90cnVzdC5jb20v\r\n' +
+        'Y3Jscy9zZWN1cmVjYS5jcmwwDQYJKoZIhvcNAQEFBQADgYEAuIojxkiWsRF8YHde\r\n' +
+        'BZqrocb6ghwYB8TrgbCoZutJqOkM0ymt9e8kTP3kS8p/XmOrmSfLnzYhLLkQYGfN\r\n' +
+        '0rTw8Ktx5YtaiScRhKqOv5nwnQkhClIZmloJ0pC3+gz4fniisIWvXEyZ2VxVKfml\r\n' +
+        'UUIuOss4jHg7y/j7lYe8vJD5UDI=\r\n' +
+        '-----END CERTIFICATE-----\r\n';
+      var cert = PKI.certificateFromPem(certPem, true);
+      var issuer = PKI.certificateFromPem(issuerPem);
+      ASSERT.ok(issuer.verify(cert));
+    });
+
+    it('should verify certificate with sha256WithRSAEncryption signature', function() {
+      var certPem = '-----BEGIN CERTIFICATE-----\r\n' +
+        'MIIDuzCCAqOgAwIBAgIEO5vZjDANBgkqhkiG9w0BAQsFADBGMQswCQYDVQQGEwJE\r\n' +
+        'RTEPMA0GA1UEChMGRWxzdGVyMQswCQYDVQQLEwJDQTEZMBcGA1UEAxMQRWxzdGVy\r\n' +
+        'U29mdFRlc3RDQTAeFw0xMDA5MTUwNTM4MjRaFw0xMzA5MTUwNTM4MjRaMCsxFDAS\r\n' +
+        'BgNVBAUTCzEwMDIzMTQ5OTRDMRMwEQYDVQQDEwoxMDAyMzE0OTk0MIGfMA0GCSqG\r\n' +
+        'SIb3DQEBAQUAA4GNADCBiQKBgQCLPqjbwjsugzw6+qwwm/pdzDwk7ASIsBYJ17GT\r\n' +
+        'qyT0zCnYmdDDGWsYc+xxFVVIi8xBt6Mlq8Rwj+02UJhY9qm6zRA9MqFZC3ih+HoW\r\n' +
+        'xq7H8N2d10N0rX6h5PSjkF5fU5ugncZmppsRGJ9DNXgwjpf/CsH2rqThUzK4xfrq\r\n' +
+        'jpDS/wIDAQABo4IBTjCCAUowDgYDVR0PAQH/BAQDAgUgMAwGA1UdEwEB/wQCMAAw\r\n' +
+        'HQYDVR0OBBYEFF1h7H37OQivS57GD8+nK6VsgMPTMIGXBgNVHR8EgY8wgYwwgYmg\r\n' +
+        'gYaggYOGgYBsZGFwOi8vMTkyLjE2OC42LjI0OjM4OS9sJTNkQ0ElMjBaZXJ0aWZp\r\n' +
+        'a2F0ZSxvdSUzZENBLGNuJTNkRWxzdGVyU29mdFRlc3RDQSxkYyUzZHdpZXNlbCxk\r\n' +
+        'YyUzZGVsc3RlcixkYyUzZGRlPz9iYXNlPyhvYmplY3RDbGFzcz0qKTBxBgNVHSME\r\n' +
+        'ajBogBRBILMYmlZu//pj3wjDe2UPkq7jk6FKpEgwRjELMAkGA1UEBhMCREUxDzAN\r\n' +
+        'BgNVBAoTBkVsc3RlcjEPMA0GA1UECxMGUm9vdENBMRUwEwYDVQQDEwxFbHN0ZXJS\r\n' +
+        'b290Q0GCBDuayikwDQYJKoZIhvcNAQELBQADggEBAK8Z1+/VNyU5w/EiyhFH5aRE\r\n' +
+        'Mzxo0DahqKEm4pW5haBgKubJwZGs+CrBZR70TPbZGgJd36eyMgeXb/06lBnxewii\r\n' +
+        'I/aY6wMTviQTpqFnz5m0Le8UhH+hY1bqNG/vf6J+1gbOSrZyhAUV+MDJbL/OkzX4\r\n' +
+        'voVAfUBqSODod0f5wCW2RhvBmB9E62baP6qizdxyPA4iV16H4C0etd/7coLX6NZC\r\n' +
+        'oz3Yu0IRTQCH+YrpfIbxGb0grNhtCTfFpa287fuzu8mIEvLNr8GibhBXmQg7iJ+y\r\n' +
+        'q0VIyZLY8k6jEPrUB5Iv5ouSR19Dda/2+xJPlT/bosuNcErEuk/yKAHWAzwm1wQ=\r\n' +
+        '-----END CERTIFICATE-----\r\n';
+      var issuerPem = '-----BEGIN CERTIFICATE-----\r\n' +
+        'MIIESjCCAzKgAwIBAgIEO5rKKTANBgkqhkiG9w0BAQsFADBGMQswCQYDVQQGEwJE\r\n' +
+        'RTEPMA0GA1UEChMGRWxzdGVyMQ8wDQYDVQQLEwZSb290Q0ExFTATBgNVBAMTDEVs\r\n' +
+        'c3RlclJvb3RDQTAeFw0wOTA3MjgwODE5MTFaFw0xNDA3MjgwODE5MTFaMEYxCzAJ\r\n' +
+        'BgNVBAYTAkRFMQ8wDQYDVQQKEwZFbHN0ZXIxCzAJBgNVBAsTAkNBMRkwFwYDVQQD\r\n' +
+        'ExBFbHN0ZXJTb2Z0VGVzdENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC\r\n' +
+        'AQEAv5uoKLnxXQe75iqwqgyt3H6MDAx/wvUVs26+2+yHpEUb/2gA3L8E+NChSb9E\r\n' +
+        'aNgxxoac3Yhvxzq2mPpih3vkY7Xw512Tm8l/OPbT8kbmBJmYZneFALXHytAIZiEf\r\n' +
+        'e0ZYNKAlClFIgNP5bE9UjTqVEEoSiUhpTubM6c5xEYVznnwPBoYQ0ari7RTDYnME\r\n' +
+        'HK4vMfoeBeWHYPiEygNHnGUG8d3merRC/lQASUtL6ikmLWKCKHfyit5ACzPNKAtw\r\n' +
+        'IzHAzD5ek0BpcUTci8hUsKz2ZvmoZcjPyj63veQuMYS5cTMgr3bfz9uz1xuoEDsb\r\n' +
+        'Sv9rQX9Iw3N7yMpxTDJTqGKhYwIDAQABo4IBPjCCATowDgYDVR0PAQH/BAQDAgEG\r\n' +
+        'MBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFEEgsxiaVm7/+mPfCMN7ZQ+S\r\n' +
+        'ruOTMIGXBgNVHR8EgY8wgYwwgYmggYaggYOGgYBsZGFwOi8vMTkyLjE2OC42LjI0\r\n' +
+        'OjM4OS9sJTNkQ0ElMjBaZXJ0aWZpa2F0ZSxvdSUzZFJvb3RDQSxjbiUzZEVsc3Rl\r\n' +
+        'clJvb3RDQSxkYyUzZHdpZXNlbCxkYyUzZGVsc3RlcixkYyUzZGRlPz9iYXNlPyhv\r\n' +
+        'YmplY3RDbGFzcz0qKTBbBgNVHSMEVDBSoUqkSDBGMQswCQYDVQQGEwJERTEPMA0G\r\n' +
+        'A1UEChMGRWxzdGVyMQ8wDQYDVQQLEwZSb290Q0ExFTATBgNVBAMTDEVsc3RlclJv\r\n' +
+        'b3RDQYIEO5rKADANBgkqhkiG9w0BAQsFAAOCAQEAFauDnfHSbgRmbFkpQUXM5wKi\r\n' +
+        'K5STAaVps201iAjacX5EsOs5L37VUMoT9G2DAE8Z6B1pIiR3zcd3UpiHnFlUTC0f\r\n' +
+        'ZdOCXzDkOfziKY/RzuUsLNFUhBizCIA0+XcKgm3dSA5ex8fChLJddSYheSLuPua7\r\n' +
+        'iNMuzaU2YnevbMwpdEsl55Qr/uzcc0YM/mCuM4vsNFyFml91SQyPPmdR3VvGOoGl\r\n' +
+        'qS1R0HSoPJUvr0N0kARwD7kO3ikcJ6FxBCsgVLZHGJC+q8PQNZmVMbfgjH4nAyP8\r\n' +
+        'u7Qe03v2WLW0UgKu2g0UcQXWXbovktpZoK0fUOwv3bqsZ0K1IjVvMKG8OysUvA==\r\n' +
+        '-----END CERTIFICATE-----\r\n';
+      var cert = PKI.certificateFromPem(certPem, true);
+      var issuer = PKI.certificateFromPem(issuerPem);
+      ASSERT.ok(issuer.verify(cert));
+    });
+
+    it('should import certificate with sha256 RSASSA-PSS signature', function() {
+      var certPem = '-----BEGIN CERTIFICATE-----\r\n' +
+        'MIIERzCCAvugAwIBAgIEO50CcjBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQC\r\n' +
+        'AQUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCASAwRjELMAkGA1UE\r\n' +
+        'BhMCREUxDzANBgNVBAoTBkVsc3RlcjELMAkGA1UECxMCQ0ExGTAXBgNVBAMTEEVs\r\n' +
+        'c3RlclNvZnRUZXN0Q0EwHhcNMTEwNzI4MTIxMzU3WhcNMTQwNzI4MTIxMzU3WjAr\r\n' +
+        'MRQwEgYDVQQFEwsxMDAyNzUzMzI1QzETMBEGA1UEAxMKMTAwMjc1MzMyNTCCASIw\r\n' +
+        'DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALHCogo7LVUkxWsMIc/0jgH2PCLt\r\n' +
+        'ukbATPehxWBG1XUPrz53lWgFJzlZaKLlLVVnXrfULaifuOKlZP6SM1JQlL1JuYgY\r\n' +
+        'AdgZyHjderNIk5NsSTmefwonSn/ukri5IRTH420oHtSjxk6+/DXlWnQy5OzTN6Bq\r\n' +
+        'jVJo8L+TTmf2jWuEam5cWa+YVP2k3tIqX5yMUDFjKO4znHdtIkHnBE0Kx03rWQRB\r\n' +
+        'TSYWDgDm2gttdOs9JVeuW0nnwQj27uo9gOR0iyaUjVrKLZ95p6zpXhM4uMSVRNeo\r\n' +
+        'LqkdqP2n+4pDXZVqLNgjkHQUS/xq9Q/kYgT2J7wkGfYxP9to7TG7vra1eOECAwEA\r\n' +
+        'AaOB7zCB7DAOBgNVHQ8BAf8EBAMCBSAwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU\r\n' +
+        'NDJ2BZIk6ukLqkdmttH12bu2leswOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2Ny\r\n' +
+        'bC5lbHN0ZXIuZGUvRWxzdGVyU29mdFRlc3RDQS5jcmwwcQYDVR0jBGowaIAU1R9A\r\n' +
+        'HmpdzaxK3v+ihQsEpAFgzOKhSqRIMEYxCzAJBgNVBAYTAkRFMQ8wDQYDVQQKEwZF\r\n' +
+        'bHN0ZXIxDzANBgNVBAsTBlJvb3RDQTEVMBMGA1UEAxMMRWxzdGVyUm9vdENBggQ7\r\n' +
+        'msqPMEEGCSqGSIb3DQEBCjA0oA8wDQYJYIZIAWUDBAIBBQChHDAaBgkqhkiG9w0B\r\n' +
+        'AQgwDQYJYIZIAWUDBAIBBQCiAwIBIAOCAQEAJBYNRZpe+z3yPPLV539Yci6OfjVg\r\n' +
+        'vs1e3rvSvcpFaqRJ8vZ8WNx3uuRQZ6B4Z3YEc00UJAOl3wU6KhamyryK2YvCrPg+\r\n' +
+        'TS5QDXNaO2z/rAnY1wWSlwBPlhqpMRrNv9cRXBcgK5YxprjytCVYN0UHdintgYxG\r\n' +
+        'fg7QYiFb00UXxAza1AFrpG+RqySFfO1scmu4kgpeb6A3USnQ0r6rZz6dt6NqgZZ6\r\n' +
+        'oUpDOCvnS9XSOWuvJirV8hIU0KAagguTbwfTqt9nt0wDlwZpemsJZ4Vvnvy8d9Jf\r\n' +
+        'zA68EWHbZLr2QP9hb3sHCWJgplMsTJnUwRfi2hf5KNtP8Xg5DSLMfTEbhw==\r\n' +
+        '-----END CERTIFICATE-----\r\n';
+      var cert = PKI.certificateFromPem(certPem, true);
+
+      ASSERT.equal(cert.signatureOid, PKI.oids['RSASSA-PSS']);
+      ASSERT.equal(cert.signatureParameters.hash.algorithmOid, PKI.oids['sha256']);
+      ASSERT.equal(cert.signatureParameters.mgf.algorithmOid, PKI.oids['mgf1']);
+      ASSERT.equal(cert.signatureParameters.mgf.hash.algorithmOid, PKI.oids['sha256']);
+      ASSERT.equal(cert.siginfo.algorithmOid, PKI.oids['RSASSA-PSS']);
+      ASSERT.equal(cert.siginfo.parameters.hash.algorithmOid, PKI.oids['sha256']);
+      ASSERT.equal(cert.siginfo.parameters.mgf.algorithmOid, PKI.oids['mgf1']);
+      ASSERT.equal(cert.siginfo.parameters.mgf.hash.algorithmOid, PKI.oids['sha256']);
+    });
+
+    it('should export certificate with sha256 RSASSA-PSS signature', function() {
+      var certPem = '-----BEGIN CERTIFICATE-----\r\n' +
+        'MIIERzCCAvugAwIBAgIEO50CcjBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQC\r\n' +
+        'AQUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCASAwRjELMAkGA1UE\r\n' +
+        'BhMCREUxDzANBgNVBAoTBkVsc3RlcjELMAkGA1UECxMCQ0ExGTAXBgNVBAMTEEVs\r\n' +
+        'c3RlclNvZnRUZXN0Q0EwHhcNMTEwNzI4MTIxMzU3WhcNMTQwNzI4MTIxMzU3WjAr\r\n' +
+        'MRQwEgYDVQQFEwsxMDAyNzUzMzI1QzETMBEGA1UEAxMKMTAwMjc1MzMyNTCCASIw\r\n' +
+        'DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALHCogo7LVUkxWsMIc/0jgH2PCLt\r\n' +
+        'ukbATPehxWBG1XUPrz53lWgFJzlZaKLlLVVnXrfULaifuOKlZP6SM1JQlL1JuYgY\r\n' +
+        'AdgZyHjderNIk5NsSTmefwonSn/ukri5IRTH420oHtSjxk6+/DXlWnQy5OzTN6Bq\r\n' +
+        'jVJo8L+TTmf2jWuEam5cWa+YVP2k3tIqX5yMUDFjKO4znHdtIkHnBE0Kx03rWQRB\r\n' +
+        'TSYWDgDm2gttdOs9JVeuW0nnwQj27uo9gOR0iyaUjVrKLZ95p6zpXhM4uMSVRNeo\r\n' +
+        'LqkdqP2n+4pDXZVqLNgjkHQUS/xq9Q/kYgT2J7wkGfYxP9to7TG7vra1eOECAwEA\r\n' +
+        'AaOB7zCB7DAOBgNVHQ8BAf8EBAMCBSAwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU\r\n' +
+        'NDJ2BZIk6ukLqkdmttH12bu2leswOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2Ny\r\n' +
+        'bC5lbHN0ZXIuZGUvRWxzdGVyU29mdFRlc3RDQS5jcmwwcQYDVR0jBGowaIAU1R9A\r\n' +
+        'HmpdzaxK3v+ihQsEpAFgzOKhSqRIMEYxCzAJBgNVBAYTAkRFMQ8wDQYDVQQKEwZF\r\n' +
+        'bHN0ZXIxDzANBgNVBAsTBlJvb3RDQTEVMBMGA1UEAxMMRWxzdGVyUm9vdENBggQ7\r\n' +
+        'msqPMEEGCSqGSIb3DQEBCjA0oA8wDQYJYIZIAWUDBAIBBQChHDAaBgkqhkiG9w0B\r\n' +
+        'AQgwDQYJYIZIAWUDBAIBBQCiAwIBIAOCAQEAJBYNRZpe+z3yPPLV539Yci6OfjVg\r\n' +
+        'vs1e3rvSvcpFaqRJ8vZ8WNx3uuRQZ6B4Z3YEc00UJAOl3wU6KhamyryK2YvCrPg+\r\n' +
+        'TS5QDXNaO2z/rAnY1wWSlwBPlhqpMRrNv9cRXBcgK5YxprjytCVYN0UHdintgYxG\r\n' +
+        'fg7QYiFb00UXxAza1AFrpG+RqySFfO1scmu4kgpeb6A3USnQ0r6rZz6dt6NqgZZ6\r\n' +
+        'oUpDOCvnS9XSOWuvJirV8hIU0KAagguTbwfTqt9nt0wDlwZpemsJZ4Vvnvy8d9Jf\r\n' +
+        'zA68EWHbZLr2QP9hb3sHCWJgplMsTJnUwRfi2hf5KNtP8Xg5DSLMfTEbhw==\r\n' +
+        '-----END CERTIFICATE-----\r\n';
+      var cert = PKI.certificateFromPem(certPem, true);
+      ASSERT.equal(PKI.certificateToPem(cert), certPem);
+    });
+
+    it('should verify certificate with sha256 RSASSA-PSS signature', function() {
+      var certPem = '-----BEGIN CERTIFICATE-----\r\n' +
+        'MIIERzCCAvugAwIBAgIEO50CcjBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQC\r\n' +
+        'AQUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCASAwRjELMAkGA1UE\r\n' +
+        'BhMCREUxDzANBgNVBAoTBkVsc3RlcjELMAkGA1UECxMCQ0ExGTAXBgNVBAMTEEVs\r\n' +
+        'c3RlclNvZnRUZXN0Q0EwHhcNMTEwNzI4MTIxMzU3WhcNMTQwNzI4MTIxMzU3WjAr\r\n' +
+        'MRQwEgYDVQQFEwsxMDAyNzUzMzI1QzETMBEGA1UEAxMKMTAwMjc1MzMyNTCCASIw\r\n' +
+        'DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALHCogo7LVUkxWsMIc/0jgH2PCLt\r\n' +
+        'ukbATPehxWBG1XUPrz53lWgFJzlZaKLlLVVnXrfULaifuOKlZP6SM1JQlL1JuYgY\r\n' +
+        'AdgZyHjderNIk5NsSTmefwonSn/ukri5IRTH420oHtSjxk6+/DXlWnQy5OzTN6Bq\r\n' +
+        'jVJo8L+TTmf2jWuEam5cWa+YVP2k3tIqX5yMUDFjKO4znHdtIkHnBE0Kx03rWQRB\r\n' +
+        'TSYWDgDm2gttdOs9JVeuW0nnwQj27uo9gOR0iyaUjVrKLZ95p6zpXhM4uMSVRNeo\r\n' +
+        'LqkdqP2n+4pDXZVqLNgjkHQUS/xq9Q/kYgT2J7wkGfYxP9to7TG7vra1eOECAwEA\r\n' +
+        'AaOB7zCB7DAOBgNVHQ8BAf8EBAMCBSAwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU\r\n' +
+        'NDJ2BZIk6ukLqkdmttH12bu2leswOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2Ny\r\n' +
+        'bC5lbHN0ZXIuZGUvRWxzdGVyU29mdFRlc3RDQS5jcmwwcQYDVR0jBGowaIAU1R9A\r\n' +
+        'HmpdzaxK3v+ihQsEpAFgzOKhSqRIMEYxCzAJBgNVBAYTAkRFMQ8wDQYDVQQKEwZF\r\n' +
+        'bHN0ZXIxDzANBgNVBAsTBlJvb3RDQTEVMBMGA1UEAxMMRWxzdGVyUm9vdENBggQ7\r\n' +
+        'msqPMEEGCSqGSIb3DQEBCjA0oA8wDQYJYIZIAWUDBAIBBQChHDAaBgkqhkiG9w0B\r\n' +
+        'AQgwDQYJYIZIAWUDBAIBBQCiAwIBIAOCAQEAJBYNRZpe+z3yPPLV539Yci6OfjVg\r\n' +
+        'vs1e3rvSvcpFaqRJ8vZ8WNx3uuRQZ6B4Z3YEc00UJAOl3wU6KhamyryK2YvCrPg+\r\n' +
+        'TS5QDXNaO2z/rAnY1wWSlwBPlhqpMRrNv9cRXBcgK5YxprjytCVYN0UHdintgYxG\r\n' +
+        'fg7QYiFb00UXxAza1AFrpG+RqySFfO1scmu4kgpeb6A3USnQ0r6rZz6dt6NqgZZ6\r\n' +
+        'oUpDOCvnS9XSOWuvJirV8hIU0KAagguTbwfTqt9nt0wDlwZpemsJZ4Vvnvy8d9Jf\r\n' +
+        'zA68EWHbZLr2QP9hb3sHCWJgplMsTJnUwRfi2hf5KNtP8Xg5DSLMfTEbhw==\r\n' +
+        '-----END CERTIFICATE-----\r\n';
+      var issuerPem = '-----BEGIN CERTIFICATE-----\r\n' +
+        'MIIEZDCCAxigAwIBAgIEO5rKjzBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQC\r\n' +
+        'AQUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCASAwRjELMAkGA1UE\r\n' +
+        'BhMCREUxDzANBgNVBAoTBkVsc3RlcjEPMA0GA1UECxMGUm9vdENBMRUwEwYDVQQD\r\n' +
+        'EwxFbHN0ZXJSb290Q0EwHhcNMTEwNzI4MTExNzI4WhcNMTYwNzI4MTExNzI4WjBG\r\n' +
+        'MQswCQYDVQQGEwJERTEPMA0GA1UEChMGRWxzdGVyMQswCQYDVQQLEwJDQTEZMBcG\r\n' +
+        'A1UEAxMQRWxzdGVyU29mdFRlc3RDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\r\n' +
+        'AQoCggEBAMFpz3sXnXq4ZUBdYdpG5DJVfITLXYwXPfEJzr1pLRfJ2eMPn7k3B/2g\r\n' +
+        'bvuH30zKmDRjlfV51sFw4l1l+cQugzy5FEOrfE6g7IvhpBUf9SQujVWtE3BoSRR5\r\n' +
+        'pSGbIWC7sm2SG0drpoCRpL0xmWZlAUS5mz7hBecJC/kKkKeOxUg5h492XQgWd0ow\r\n' +
+        '6GlyQBxJCmxgQBMnLS0stecs234hR5gvTHic50Ey+gRMPsTyco2Fm0FqvXtBuOsj\r\n' +
+        'zAW7Nk2hnM6awFHVMDBLm+ClE1ww0dLW0ujkdoGsTEbvmM/w8MBI6WAiWaanjK/x\r\n' +
+        'lStmQeUVXKd+AfuzT/FIPrwANcC1qGUCAwEAAaOB8TCB7jAOBgNVHQ8BAf8EBAMC\r\n' +
+        'AQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU1R9AHmpdzaxK3v+ihQsE\r\n' +
+        'pAFgzOIwNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5lbHN0ZXIuZGUvRWxz\r\n' +
+        'dGVyUm9vdENBLmNybDBxBgNVHSMEajBogBS3zfTokckL2H/fTojW02K+metEcaFK\r\n' +
+        'pEgwRjELMAkGA1UEBhMCREUxDzANBgNVBAoTBkVsc3RlcjEPMA0GA1UECxMGUm9v\r\n' +
+        'dENBMRUwEwYDVQQDEwxFbHN0ZXJSb290Q0GCBDuaylowQQYJKoZIhvcNAQEKMDSg\r\n' +
+        'DzANBglghkgBZQMEAgEFAKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgEFAKID\r\n' +
+        'AgEgA4IBAQBjT107fBmSfQNUYCpXIlaS/pogPoCahuC1g8QDtq6IEVXoEgXCzfVN\r\n' +
+        'JYHyYOQOraP4O2LEcXpo/oc0MMpVF06QLKG/KieUE0mrZnsCJ3GLSJkM8tI8bRHj\r\n' +
+        '8tAhlViMacUqnKKufCs/rIN25JB57+sCyFqjtRbSt88e+xqFJ5I79Btp/bNtoj2G\r\n' +
+        'OJGl997EPua9/yJuvdA9W67WO/KwEh+FqylB1pAapccOHqttwu4QJIc/XJfG5rrf\r\n' +
+        '8QZz8HIuOcroA4zIrprQ1nJRCuMII04ueDtBVz1eIEmqNEUkr09JdK8M0LKH0lMK\r\n' +
+        'Ysgjai/P2mPVVwhVw6dHzUVRLXh3xIQr\r\n' +
+        '-----END CERTIFICATE-----\r\n';
+      var cert = PKI.certificateFromPem(certPem, true);
+      var issuer = PKI.certificateFromPem(issuerPem);
+      ASSERT.ok(issuer.verify(cert));
+    });
+  });
+
+  describe('public key fingerprints', function() {
+    it('should get a SHA-1 RSAPublicKey fingerprint', function() {
+      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
+      var fp = PKI.getPublicKeyFingerprint(publicKey, {type: 'RSAPublicKey'});
+      ASSERT.equal(fp.toHex(), 'f57563e0c75d6e9b03fafdb2fd72349f23030300');
+    });
+
+    it('should get a SHA-1 SubjectPublicKeyInfo fingerprint', function() {
+      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
+      var fp = PKI.getPublicKeyFingerprint(
+        publicKey, {type: 'SubjectPublicKeyInfo'});
+      ASSERT.equal(fp.toHex(), '984724bc548bbc2c8acbac044bd8d518abd26dd8');
+    });
+
+    it('should get a hex SHA-1 RSAPublicKey fingerprint', function() {
+      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
+      var fp = PKI.getPublicKeyFingerprint(
+        publicKey, {type: 'RSAPublicKey', encoding: 'hex'});
+      ASSERT.equal(fp, 'f57563e0c75d6e9b03fafdb2fd72349f23030300');
+    });
+
+    it('should get a hex, colon-delimited SHA-1 RSAPublicKey fingerprint', function() {
+      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
+      var fp = PKI.getPublicKeyFingerprint(
+        publicKey, {type: 'RSAPublicKey', encoding: 'hex', delimiter: ':'});
+      ASSERT.equal(
+        fp, 'f5:75:63:e0:c7:5d:6e:9b:03:fa:fd:b2:fd:72:34:9f:23:03:03:00');
+    });
+
+    it('should get a hex, colon-delimited SHA-1 SubjectPublicKeyInfo fingerprint', function() {
+      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
+      var fp = PKI.getPublicKeyFingerprint(
+        publicKey, {
+          type: 'SubjectPublicKeyInfo',
+          encoding: 'hex',
+          delimiter: ':'
+        });
+      ASSERT.equal(
+        fp, '98:47:24:bc:54:8b:bc:2c:8a:cb:ac:04:4b:d8:d5:18:ab:d2:6d:d8');
+    });
+
+    it('should get an MD5 RSAPublicKey fingerprint', function() {
+      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
+      var fp = PKI.getPublicKeyFingerprint(
+        publicKey, {md: MD.md5.create(), type: 'RSAPublicKey'});
+      ASSERT.equal(fp.toHex(), 'c7da180cc48d31a071d31a78bc43d9d7');
+    });
+
+    it('should get an MD5 SubjectPublicKeyInfo fingerprint', function() {
+      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
+      var fp = PKI.getPublicKeyFingerprint(
+        publicKey, {md: MD.md5.create(), type: 'SubjectPublicKeyInfo'});
+      ASSERT.equal(fp.toHex(), 'e5c5ba577fe24fb8a678d8d58f539cd7');
+    });
+
+    it('should get a hex MD5 RSAPublicKey fingerprint', function() {
+      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
+      var fp = PKI.getPublicKeyFingerprint(
+        publicKey,
+        {md: MD.md5.create(), type: 'RSAPublicKey', encoding: 'hex'});
+      ASSERT.equal(fp, 'c7da180cc48d31a071d31a78bc43d9d7');
+    });
+
+    it('should get a hex, colon-delimited MD5 RSAPublicKey fingerprint', function() {
+      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
+      var fp = PKI.getPublicKeyFingerprint(
+        publicKey, {
+          md: MD.md5.create(),
+          type: 'RSAPublicKey',
+          encoding: 'hex',
+          delimiter: ':'
+        });
+      ASSERT.equal(fp, 'c7:da:18:0c:c4:8d:31:a0:71:d3:1a:78:bc:43:d9:d7');
+    });
+
+    it('should get a hex, colon-delimited MD5 SubjectPublicKeyInfo fingerprint', function() {
+      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
+      var fp = PKI.getPublicKeyFingerprint(
+        publicKey, {
+          md: MD.md5.create(),
+          type: 'SubjectPublicKeyInfo',
+          encoding: 'hex',
+          delimiter: ':'
+        });
+      ASSERT.equal(fp, 'e5:c5:ba:57:7f:e2:4f:b8:a6:78:d8:d5:8f:53:9c:d7');
+    });
+  });
+
+  function createCertificate(options) {
+    var publicKey = options.publicKey;
+    var signingKey = options.signingKey;
+    var subject = options.subject;
+    var issuer = options.issuer;
+    var isCA = options.isCA;
+    var serialNumber = options.serialNumber || '01';
+
+    var cert = PKI.createCertificate();
+    cert.publicKey = publicKey;
+    cert.serialNumber = serialNumber;
+    cert.validity.notBefore = new Date();
+    cert.validity.notAfter = new Date();
+    cert.validity.notAfter.setFullYear(
+      cert.validity.notBefore.getFullYear() + 1);
+    cert.setSubject(subject);
+    cert.setIssuer(issuer);
+    var extensions = [];
+    if(isCA) {
+      extensions.push({
+        name: 'basicConstraints',
+        cA: true
+      });
+    }
+    extensions.push({
+      name: 'keyUsage',
+      keyCertSign: true,
+      digitalSignature: true,
+      nonRepudiation: true,
+      keyEncipherment: true,
+      dataEncipherment: true
+    }, {
+      name: 'extKeyUsage',
+      serverAuth: true,
+      clientAuth: true,
+      codeSigning: true,
+      emailProtection: true,
+      timeStamping: true
+    }, {
+      name: 'nsCertType',
+      client: true,
+      server: true,
+      email: true,
+      objsign: true,
+      sslCA: true,
+      emailCA: true,
+      objCA: true
+    }, {
+      name: 'subjectAltName',
+      altNames: [{
+        type: 6, // URI
+        value: 'http://example.org/webid#me'
+      }]
+    }, {
+      name: 'subjectKeyIdentifier'
+    });
+    // FIXME: add authorityKeyIdentifier extension
+    cert.setExtensions(extensions);
+
+    cert.sign(signingKey);
+
+    return cert;
+  }
+}
+
+// check for AMD
+if(typeof define === 'function') {
+  define([
+    'forge/pki',
+    'forge/md',
+    'forge/util'
+  ], function(PKI, MD, UTIL) {
+    Tests(
+      // Global provided by test harness
+      ASSERT,
+      PKI(),
+      MD(),
+      UTIL()
+    );
+  });
+} else if(typeof module === 'object' && module.exports) {
+  // assume NodeJS
+  Tests(
+    require('assert'),
+    require('../../js/pki')(),
+    require('../../js/md')(),
+    require('../../js/util')());
+}
+
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/ui/index.html b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/ui/index.html
new file mode 100644
index 0000000..25e81b4
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/ui/index.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <link rel="stylesheet" href="mocha/mocha.css">
+    <script src="mocha/mocha.js" type="text/javascript" charset="utf-8"></script>
+    <script src="chai/chai.js" type="text/javascript" charset="utf-8"></script>
+    <script src="require.js" data-main="test" type="text/javascript" charset="utf-8"></script>
+</head>
+<body>
+    <div id="mocha"></div>
+</body>
+</html>
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/ui/require.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/ui/require.js
new file mode 100644
index 0000000..7df5d90
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/ui/require.js
@@ -0,0 +1,35 @@
+/*
+ RequireJS 2.1.5 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.
+ Available via the MIT or new BSD license.
+ see: http://github.com/jrburke/requirejs for details
+*/
+var requirejs,require,define;
+(function(aa){function I(b){return"[object Function]"===L.call(b)}function J(b){return"[object Array]"===L.call(b)}function y(b,c){if(b){var d;for(d=0;d<b.length&&(!b[d]||!c(b[d],d,b));d+=1);}}function M(b,c){if(b){var d;for(d=b.length-1;-1<d&&(!b[d]||!c(b[d],d,b));d-=1);}}function s(b,c){return ga.call(b,c)}function m(b,c){return s(b,c)&&b[c]}function G(b,c){for(var d in b)if(s(b,d)&&c(b[d],d))break}function R(b,c,d,m){c&&G(c,function(c,j){if(d||!s(b,j))m&&"string"!==typeof c?(b[j]||(b[j]={}),R(b[j],
+c,d,m)):b[j]=c});return b}function u(b,c){return function(){return c.apply(b,arguments)}}function ba(b){if(!b)return b;var c=aa;y(b.split("."),function(b){c=c[b]});return c}function B(b,c,d,m){c=Error(c+"\nhttp://requirejs.org/docs/errors.html#"+b);c.requireType=b;c.requireModules=m;d&&(c.originalError=d);return c}function ha(b){function c(a,f,b){var e,n,c,g,d,S,i,h=f&&f.split("/");e=h;var j=k.map,l=j&&j["*"];if(a&&"."===a.charAt(0))if(f){e=m(k.pkgs,f)?h=[f]:h.slice(0,h.length-1);f=a=e.concat(a.split("/"));
+for(e=0;f[e];e+=1)if(n=f[e],"."===n)f.splice(e,1),e-=1;else if(".."===n)if(1===e&&(".."===f[2]||".."===f[0]))break;else 0<e&&(f.splice(e-1,2),e-=2);e=m(k.pkgs,f=a[0]);a=a.join("/");e&&a===f+"/"+e.main&&(a=f)}else 0===a.indexOf("./")&&(a=a.substring(2));if(b&&j&&(h||l)){f=a.split("/");for(e=f.length;0<e;e-=1){c=f.slice(0,e).join("/");if(h)for(n=h.length;0<n;n-=1)if(b=m(j,h.slice(0,n).join("/")))if(b=m(b,c)){g=b;d=e;break}if(g)break;!S&&(l&&m(l,c))&&(S=m(l,c),i=e)}!g&&S&&(g=S,d=i);g&&(f.splice(0,d,
+g),a=f.join("/"))}return a}function d(a){A&&y(document.getElementsByTagName("script"),function(f){if(f.getAttribute("data-requiremodule")===a&&f.getAttribute("data-requirecontext")===i.contextName)return f.parentNode.removeChild(f),!0})}function z(a){var f=m(k.paths,a);if(f&&J(f)&&1<f.length)return d(a),f.shift(),i.require.undef(a),i.require([a]),!0}function h(a){var f,b=a?a.indexOf("!"):-1;-1<b&&(f=a.substring(0,b),a=a.substring(b+1,a.length));return[f,a]}function j(a,f,b,e){var n,C,g=null,d=f?f.name:
+null,j=a,l=!0,k="";a||(l=!1,a="_@r"+(M+=1));a=h(a);g=a[0];a=a[1];g&&(g=c(g,d,e),C=m(q,g));a&&(g?k=C&&C.normalize?C.normalize(a,function(a){return c(a,d,e)}):c(a,d,e):(k=c(a,d,e),a=h(k),g=a[0],k=a[1],b=!0,n=i.nameToUrl(k)));b=g&&!C&&!b?"_unnormalized"+(Q+=1):"";return{prefix:g,name:k,parentMap:f,unnormalized:!!b,url:n,originalName:j,isDefine:l,id:(g?g+"!"+k:k)+b}}function r(a){var f=a.id,b=m(p,f);b||(b=p[f]=new i.Module(a));return b}function t(a,f,b){var e=a.id,n=m(p,e);if(s(q,e)&&(!n||n.defineEmitComplete))"defined"===
+f&&b(q[e]);else r(a).on(f,b)}function v(a,f){var b=a.requireModules,e=!1;if(f)f(a);else if(y(b,function(f){if(f=m(p,f))f.error=a,f.events.error&&(e=!0,f.emit("error",a))}),!e)l.onError(a)}function w(){T.length&&(ia.apply(H,[H.length-1,0].concat(T)),T=[])}function x(a){delete p[a];delete V[a]}function F(a,f,b){var e=a.map.id;a.error?a.emit("error",a.error):(f[e]=!0,y(a.depMaps,function(e,c){var g=e.id,d=m(p,g);d&&(!a.depMatched[c]&&!b[g])&&(m(f,g)?(a.defineDep(c,q[g]),a.check()):F(d,f,b))}),b[e]=!0)}
+function D(){var a,f,b,e,n=(b=1E3*k.waitSeconds)&&i.startTime+b<(new Date).getTime(),c=[],g=[],h=!1,j=!0;if(!W){W=!0;G(V,function(b){a=b.map;f=a.id;if(b.enabled&&(a.isDefine||g.push(b),!b.error))if(!b.inited&&n)z(f)?h=e=!0:(c.push(f),d(f));else if(!b.inited&&(b.fetched&&a.isDefine)&&(h=!0,!a.prefix))return j=!1});if(n&&c.length)return b=B("timeout","Load timeout for modules: "+c,null,c),b.contextName=i.contextName,v(b);j&&y(g,function(a){F(a,{},{})});if((!n||e)&&h)if((A||da)&&!X)X=setTimeout(function(){X=
+0;D()},50);W=!1}}function E(a){s(q,a[0])||r(j(a[0],null,!0)).init(a[1],a[2])}function K(a){var a=a.currentTarget||a.srcElement,b=i.onScriptLoad;a.detachEvent&&!Y?a.detachEvent("onreadystatechange",b):a.removeEventListener("load",b,!1);b=i.onScriptError;(!a.detachEvent||Y)&&a.removeEventListener("error",b,!1);return{node:a,id:a&&a.getAttribute("data-requiremodule")}}function L(){var a;for(w();H.length;){a=H.shift();if(null===a[0])return v(B("mismatch","Mismatched anonymous define() module: "+a[a.length-
+1]));E(a)}}var W,Z,i,N,X,k={waitSeconds:7,baseUrl:"./",paths:{},pkgs:{},shim:{},config:{}},p={},V={},$={},H=[],q={},U={},M=1,Q=1;N={require:function(a){return a.require?a.require:a.require=i.makeRequire(a.map)},exports:function(a){a.usingExports=!0;if(a.map.isDefine)return a.exports?a.exports:a.exports=q[a.map.id]={}},module:function(a){return a.module?a.module:a.module={id:a.map.id,uri:a.map.url,config:function(){return k.config&&m(k.config,a.map.id)||{}},exports:q[a.map.id]}}};Z=function(a){this.events=
+m($,a.id)||{};this.map=a;this.shim=m(k.shim,a.id);this.depExports=[];this.depMaps=[];this.depMatched=[];this.pluginMaps={};this.depCount=0};Z.prototype={init:function(a,b,c,e){e=e||{};if(!this.inited){this.factory=b;if(c)this.on("error",c);else this.events.error&&(c=u(this,function(a){this.emit("error",a)}));this.depMaps=a&&a.slice(0);this.errback=c;this.inited=!0;this.ignore=e.ignore;e.enabled||this.enabled?this.enable():this.check()}},defineDep:function(a,b){this.depMatched[a]||(this.depMatched[a]=
+!0,this.depCount-=1,this.depExports[a]=b)},fetch:function(){if(!this.fetched){this.fetched=!0;i.startTime=(new Date).getTime();var a=this.map;if(this.shim)i.makeRequire(this.map,{enableBuildCallback:!0})(this.shim.deps||[],u(this,function(){return a.prefix?this.callPlugin():this.load()}));else return a.prefix?this.callPlugin():this.load()}},load:function(){var a=this.map.url;U[a]||(U[a]=!0,i.load(this.map.id,a))},check:function(){if(this.enabled&&!this.enabling){var a,b,c=this.map.id;b=this.depExports;
+var e=this.exports,n=this.factory;if(this.inited)if(this.error)this.emit("error",this.error);else{if(!this.defining){this.defining=!0;if(1>this.depCount&&!this.defined){if(I(n)){if(this.events.error)try{e=i.execCb(c,n,b,e)}catch(d){a=d}else e=i.execCb(c,n,b,e);this.map.isDefine&&((b=this.module)&&void 0!==b.exports&&b.exports!==this.exports?e=b.exports:void 0===e&&this.usingExports&&(e=this.exports));if(a)return a.requireMap=this.map,a.requireModules=[this.map.id],a.requireType="define",v(this.error=
+a)}else e=n;this.exports=e;if(this.map.isDefine&&!this.ignore&&(q[c]=e,l.onResourceLoad))l.onResourceLoad(i,this.map,this.depMaps);x(c);this.defined=!0}this.defining=!1;this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=!0)}}else this.fetch()}},callPlugin:function(){var a=this.map,b=a.id,d=j(a.prefix);this.depMaps.push(d);t(d,"defined",u(this,function(e){var n,d;d=this.map.name;var g=this.map.parentMap?this.map.parentMap.name:null,h=
+i.makeRequire(a.parentMap,{enableBuildCallback:!0});if(this.map.unnormalized){if(e.normalize&&(d=e.normalize(d,function(a){return c(a,g,!0)})||""),e=j(a.prefix+"!"+d,this.map.parentMap),t(e,"defined",u(this,function(a){this.init([],function(){return a},null,{enabled:!0,ignore:!0})})),d=m(p,e.id)){this.depMaps.push(e);if(this.events.error)d.on("error",u(this,function(a){this.emit("error",a)}));d.enable()}}else n=u(this,function(a){this.init([],function(){return a},null,{enabled:!0})}),n.error=u(this,
+function(a){this.inited=!0;this.error=a;a.requireModules=[b];G(p,function(a){0===a.map.id.indexOf(b+"_unnormalized")&&x(a.map.id)});v(a)}),n.fromText=u(this,function(e,c){var d=a.name,g=j(d),C=O;c&&(e=c);C&&(O=!1);r(g);s(k.config,b)&&(k.config[d]=k.config[b]);try{l.exec(e)}catch(ca){return v(B("fromtexteval","fromText eval for "+b+" failed: "+ca,ca,[b]))}C&&(O=!0);this.depMaps.push(g);i.completeLoad(d);h([d],n)}),e.load(a.name,h,n,k)}));i.enable(d,this);this.pluginMaps[d.id]=d},enable:function(){V[this.map.id]=
+this;this.enabling=this.enabled=!0;y(this.depMaps,u(this,function(a,b){var c,e;if("string"===typeof a){a=j(a,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap);this.depMaps[b]=a;if(c=m(N,a.id)){this.depExports[b]=c(this);return}this.depCount+=1;t(a,"defined",u(this,function(a){this.defineDep(b,a);this.check()}));this.errback&&t(a,"error",this.errback)}c=a.id;e=p[c];!s(N,c)&&(e&&!e.enabled)&&i.enable(a,this)}));G(this.pluginMaps,u(this,function(a){var b=m(p,a.id);b&&!b.enabled&&i.enable(a,
+this)}));this.enabling=!1;this.check()},on:function(a,b){var c=this.events[a];c||(c=this.events[a]=[]);c.push(b)},emit:function(a,b){y(this.events[a],function(a){a(b)});"error"===a&&delete this.events[a]}};i={config:k,contextName:b,registry:p,defined:q,urlFetched:U,defQueue:H,Module:Z,makeModuleMap:j,nextTick:l.nextTick,onError:v,configure:function(a){a.baseUrl&&"/"!==a.baseUrl.charAt(a.baseUrl.length-1)&&(a.baseUrl+="/");var b=k.pkgs,c=k.shim,e={paths:!0,config:!0,map:!0};G(a,function(a,b){e[b]?
+"map"===b?(k.map||(k.map={}),R(k[b],a,!0,!0)):R(k[b],a,!0):k[b]=a});a.shim&&(G(a.shim,function(a,b){J(a)&&(a={deps:a});if((a.exports||a.init)&&!a.exportsFn)a.exportsFn=i.makeShimExports(a);c[b]=a}),k.shim=c);a.packages&&(y(a.packages,function(a){a="string"===typeof a?{name:a}:a;b[a.name]={name:a.name,location:a.location||a.name,main:(a.main||"main").replace(ja,"").replace(ea,"")}}),k.pkgs=b);G(p,function(a,b){!a.inited&&!a.map.unnormalized&&(a.map=j(b))});if(a.deps||a.callback)i.require(a.deps||[],
+a.callback)},makeShimExports:function(a){return function(){var b;a.init&&(b=a.init.apply(aa,arguments));return b||a.exports&&ba(a.exports)}},makeRequire:function(a,f){function d(e,c,h){var g,k;f.enableBuildCallback&&(c&&I(c))&&(c.__requireJsBuild=!0);if("string"===typeof e){if(I(c))return v(B("requireargs","Invalid require call"),h);if(a&&s(N,e))return N[e](p[a.id]);if(l.get)return l.get(i,e,a,d);g=j(e,a,!1,!0);g=g.id;return!s(q,g)?v(B("notloaded",'Module name "'+g+'" has not been loaded yet for context: '+
+b+(a?"":". Use require([])"))):q[g]}L();i.nextTick(function(){L();k=r(j(null,a));k.skipMap=f.skipMap;k.init(e,c,h,{enabled:!0});D()});return d}f=f||{};R(d,{isBrowser:A,toUrl:function(b){var d,f=b.lastIndexOf("."),g=b.split("/")[0];if(-1!==f&&(!("."===g||".."===g)||1<f))d=b.substring(f,b.length),b=b.substring(0,f);return i.nameToUrl(c(b,a&&a.id,!0),d,!0)},defined:function(b){return s(q,j(b,a,!1,!0).id)},specified:function(b){b=j(b,a,!1,!0).id;return s(q,b)||s(p,b)}});a||(d.undef=function(b){w();var c=
+j(b,a,!0),d=m(p,b);delete q[b];delete U[c.url];delete $[b];d&&(d.events.defined&&($[b]=d.events),x(b))});return d},enable:function(a){m(p,a.id)&&r(a).enable()},completeLoad:function(a){var b,c,e=m(k.shim,a)||{},d=e.exports;for(w();H.length;){c=H.shift();if(null===c[0]){c[0]=a;if(b)break;b=!0}else c[0]===a&&(b=!0);E(c)}c=m(p,a);if(!b&&!s(q,a)&&c&&!c.inited){if(k.enforceDefine&&(!d||!ba(d)))return z(a)?void 0:v(B("nodefine","No define call for "+a,null,[a]));E([a,e.deps||[],e.exportsFn])}D()},nameToUrl:function(a,
+b,c){var e,d,h,g,j,i;if(l.jsExtRegExp.test(a))g=a+(b||"");else{e=k.paths;d=k.pkgs;g=a.split("/");for(j=g.length;0<j;j-=1)if(i=g.slice(0,j).join("/"),h=m(d,i),i=m(e,i)){J(i)&&(i=i[0]);g.splice(0,j,i);break}else if(h){a=a===h.name?h.location+"/"+h.main:h.location;g.splice(0,j,a);break}g=g.join("/");g+=b||(/\?/.test(g)||c?"":".js");g=("/"===g.charAt(0)||g.match(/^[\w\+\.\-]+:/)?"":k.baseUrl)+g}return k.urlArgs?g+((-1===g.indexOf("?")?"?":"&")+k.urlArgs):g},load:function(a,b){l.load(i,a,b)},execCb:function(a,
+b,c,d){return b.apply(d,c)},onScriptLoad:function(a){if("load"===a.type||ka.test((a.currentTarget||a.srcElement).readyState))P=null,a=K(a),i.completeLoad(a.id)},onScriptError:function(a){var b=K(a);if(!z(b.id))return v(B("scripterror","Script error",a,[b.id]))}};i.require=i.makeRequire();return i}var l,w,x,D,t,E,P,K,Q,fa,la=/(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg,ma=/[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,ea=/\.js$/,ja=/^\.\//;w=Object.prototype;var L=w.toString,ga=w.hasOwnProperty,ia=
+Array.prototype.splice,A=!!("undefined"!==typeof window&&navigator&&document),da=!A&&"undefined"!==typeof importScripts,ka=A&&"PLAYSTATION 3"===navigator.platform?/^complete$/:/^(complete|loaded)$/,Y="undefined"!==typeof opera&&"[object Opera]"===opera.toString(),F={},r={},T=[],O=!1;if("undefined"===typeof define){if("undefined"!==typeof requirejs){if(I(requirejs))return;r=requirejs;requirejs=void 0}"undefined"!==typeof require&&!I(require)&&(r=require,require=void 0);l=requirejs=function(b,c,d,z){var h,
+j="_";!J(b)&&"string"!==typeof b&&(h=b,J(c)?(b=c,c=d,d=z):b=[]);h&&h.context&&(j=h.context);(z=m(F,j))||(z=F[j]=l.s.newContext(j));h&&z.configure(h);return z.require(b,c,d)};l.config=function(b){return l(b)};l.nextTick="undefined"!==typeof setTimeout?function(b){setTimeout(b,4)}:function(b){b()};require||(require=l);l.version="2.1.5";l.jsExtRegExp=/^\/|:|\?|\.js$/;l.isBrowser=A;w=l.s={contexts:F,newContext:ha};l({});y(["toUrl","undef","defined","specified"],function(b){l[b]=function(){var c=F._;return c.require[b].apply(c,
+arguments)}});if(A&&(x=w.head=document.getElementsByTagName("head")[0],D=document.getElementsByTagName("base")[0]))x=w.head=D.parentNode;l.onError=function(b){throw b;};l.load=function(b,c,d){var l=b&&b.config||{},h;if(A)return h=l.xhtml?document.createElementNS("http://www.w3.org/1999/xhtml","html:script"):document.createElement("script"),h.type=l.scriptType||"text/javascript",h.charset="utf-8",h.async=!0,h.setAttribute("data-requirecontext",b.contextName),h.setAttribute("data-requiremodule",c),
+h.attachEvent&&!(h.attachEvent.toString&&0>h.attachEvent.toString().indexOf("[native code"))&&!Y?(O=!0,h.attachEvent("onreadystatechange",b.onScriptLoad)):(h.addEventListener("load",b.onScriptLoad,!1),h.addEventListener("error",b.onScriptError,!1)),h.src=d,K=h,D?x.insertBefore(h,D):x.appendChild(h),K=null,h;if(da)try{importScripts(d),b.completeLoad(c)}catch(j){b.onError(B("importscripts","importScripts failed for "+c+" at "+d,j,[c]))}};A&&M(document.getElementsByTagName("script"),function(b){x||(x=
+b.parentNode);if(t=b.getAttribute("data-main"))return r.baseUrl||(E=t.split("/"),Q=E.pop(),fa=E.length?E.join("/")+"/":"./",r.baseUrl=fa,t=Q),t=t.replace(ea,""),r.deps=r.deps?r.deps.concat(t):[t],!0});define=function(b,c,d){var l,h;"string"!==typeof b&&(d=c,c=b,b=null);J(c)||(d=c,c=[]);!c.length&&I(d)&&d.length&&(d.toString().replace(la,"").replace(ma,function(b,d){c.push(d)}),c=(1===d.length?["require"]:["require","exports","module"]).concat(c));if(O){if(!(l=K))P&&"interactive"===P.readyState||M(document.getElementsByTagName("script"),
+function(b){if("interactive"===b.readyState)return P=b}),l=P;l&&(b||(b=l.getAttribute("data-requiremodule")),h=F[l.getAttribute("data-requirecontext")])}(h?h.defQueue:T).push([b,c,d])};define.amd={jQuery:!0};l.exec=function(b){return eval(b)};l(r)}})(this);
\ No newline at end of file
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/ui/test.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/ui/test.js
new file mode 100644
index 0000000..e4d15d7
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/ui/test.js
@@ -0,0 +1,36 @@
+var ASSERT = chai.assert;
+mocha.setup({
+    ui: 'bdd',
+    timeout: 20000
+});
+requirejs.config({
+    paths: {
+        forge: 'forge',
+        test: 'test'
+    }
+});
+requirejs([
+    'test/util',
+    'test/md5',
+    'test/sha1',
+    'test/sha256',
+    'test/hmac',
+    'test/pbkdf2',
+    'test/mgf1',
+    'test/random',
+    'test/asn1',
+    'test/pem',
+    'test/rsa',
+    'test/kem',
+    'test/pkcs1',
+    'test/x509',
+    'test/csr',
+    'test/aes',
+    'test/rc2',
+    'test/des',
+    'test/pkcs7',
+    'test/pkcs12',
+    'test/tls'
+], function() {
+    mocha.run();
+});
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/ui/test.min.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/ui/test.min.js
new file mode 100644
index 0000000..e94e9e0
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/nodejs/ui/test.min.js
@@ -0,0 +1 @@
+(function(){function e(e){var t=e.util=e.util||{};typeof process=="undefined"||!process.nextTick?typeof setImmediate=="function"?(t.setImmediate=setImmediate,t.nextTick=function(e){return setImmediate(e)}):(t.setImmediate=function(e){setTimeout(e,0)},t.nextTick=t.setImmediate):(t.nextTick=process.nextTick,typeof setImmediate=="function"?t.setImmediate=setImmediate:t.setImmediate=t.nextTick),t.isArray=Array.isArray||function(e){return Object.prototype.toString.call(e)==="[object Array]"},t.ByteBuffer=function(e){this.data=e||"",this.read=0},t.ByteBuffer.prototype.length=function(){return this.data.length-this.read},t.ByteBuffer.prototype.isEmpty=function(){return this.length()<=0},t.ByteBuffer.prototype.putByte=function(e){return this.data+=String.fromCharCode(e),this},t.ByteBuffer.prototype.fillWithByte=function(e,t){e=String.fromCharCode(e);var n=this.data;while(t>0)t&1&&(n+=e),t>>>=1,t>0&&(e+=e);return this.data=n,this},t.ByteBuffer.prototype.putBytes=function(e){return this.data+=e,this},t.ByteBuffer.prototype.putString=function(e){return this.data+=t.encodeUtf8(e),this},t.ByteBuffer.prototype.putInt16=function(e){return this.data+=String.fromCharCode(e>>8&255)+String.fromCharCode(e&255),this},t.ByteBuffer.prototype.putInt24=function(e){return this.data+=String.fromCharCode(e>>16&255)+String.fromCharCode(e>>8&255)+String.fromCharCode(e&255),this},t.ByteBuffer.prototype.putInt32=function(e){return this.data+=String.fromCharCode(e>>24&255)+String.fromCharCode(e>>16&255)+String.fromCharCode(e>>8&255)+String.fromCharCode(e&255),this},t.ByteBuffer.prototype.putInt16Le=function(e){return this.data+=String.fromCharCode(e&255)+String.fromCharCode(e>>8&255),this},t.ByteBuffer.prototype.putInt24Le=function(e){return this.data+=String.fromCharCode(e&255)+String.fromCharCode(e>>8&255)+String.fromCharCode(e>>16&255),this},t.ByteBuffer.prototype.putInt32Le=function(e){return this.data+=String.fromCharCode(e&255)+String.fromCharCode(e>>8&255)+String.fromCharCode(e>>16&255)+String.fromCharCode(e>>24&255),this},t.ByteBuffer.prototype.putInt=function(e,t){do t-=8,this.data+=String.fromCharCode(e>>t&255);while(t>0);return this},t.ByteBuffer.prototype.putBuffer=function(e){return this.data+=e.getBytes(),this},t.ByteBuffer.prototype.getByte=function(){return this.data.charCodeAt(this.read++)},t.ByteBuffer.prototype.getInt16=function(){var e=this.data.charCodeAt(this.read)<<8^this.data.charCodeAt(this.read+1);return this.read+=2,e},t.ByteBuffer.prototype.getInt24=function(){var e=this.data.charCodeAt(this.read)<<16^this.data.charCodeAt(this.read+1)<<8^this.data.charCodeAt(this.read+2);return this.read+=3,e},t.ByteBuffer.prototype.getInt32=function(){var e=this.data.charCodeAt(this.read)<<24^this.data.charCodeAt(this.read+1)<<16^this.data.charCodeAt(this.read+2)<<8^this.data.charCodeAt(this.read+3);return this.read+=4,e},t.ByteBuffer.prototype.getInt16Le=function(){var e=this.data.charCodeAt(this.read)^this.data.charCodeAt(this.read+1)<<8;return this.read+=2,e},t.ByteBuffer.prototype.getInt24Le=function(){var e=this.data.charCodeAt(this.read)^this.data.charCodeAt(this.read+1)<<8^this.data.charCodeAt(this.read+2)<<16;return this.read+=3,e},t.ByteBuffer.prototype.getInt32Le=function(){var e=this.data.charCodeAt(this.read)^this.data.charCodeAt(this.read+1)<<8^this.data.charCodeAt(this.read+2)<<16^this.data.charCodeAt(this.read+3)<<24;return this.read+=4,e},t.ByteBuffer.prototype.getInt=function(e){var t=0;do t=(t<<8)+this.data.charCodeAt(this.read++),e-=8;while(e>0);return t},t.ByteBuffer.prototype.getBytes=function(e){var t;return e?(e=Math.min(this.length(),e),t=this.data.slice(this.read,this.read+e),this.read+=e):e===0?t="":(t=this.read===0?this.data:this.data.slice(this.read),this.clear()),t},t.ByteBuffer.prototype.bytes=function(e){return typeof e=="undefined"?this.data.slice(this.read):this.data.slice(this.read,this.read+e)},t.ByteBuffer.prototype.at=function(e){return this.data.charCodeAt(this.read+e)},t.ByteBuffer.prototype.setAt=function(e,t){return this.data=this.data.substr(0,this.read+e)+String.fromCharCode(t)+this.data.substr(this.read+e+1),this},t.ByteBuffer.prototype.last=function(){return this.data.charCodeAt(this.data.length-1)},t.ByteBuffer.prototype.copy=function(){var e=t.createBuffer(this.data);return e.read=this.read,e},t.ByteBuffer.prototype.compact=function(){return this.read>0&&(this.data=this.data.slice(this.read),this.read=0),this},t.ByteBuffer.prototype.clear=function(){return this.data="",this.read=0,this},t.ByteBuffer.prototype.truncate=function(e){var t=Math.max(0,this.length()-e);return this.data=this.data.substr(this.read,t),this.read=0,this},t.ByteBuffer.prototype.toHex=function(){var e="";for(var t=this.read;t<this.data.length;++t){var n=this.data.charCodeAt(t);n<16&&(e+="0"),e+=n.toString(16)}return e},t.ByteBuffer.prototype.toString=function(){return t.decodeUtf8(this.bytes())},t.createBuffer=function(e,n){return n=n||"raw",e!==undefined&&n==="utf8"&&(e=t.encodeUtf8(e)),new t.ByteBuffer(e)},t.fillString=function(e,t){var n="";while(t>0)t&1&&(n+=e),t>>>=1,t>0&&(e+=e);return n},t.xorBytes=function(e,t,n){var r="",i="",s="",o=0,u=0;for(;n>0;--n,++o)i=e.charCodeAt(o)^t.charCodeAt(o),u>=10&&(r+=s,s="",u=0),s+=String.fromCharCode(i),++u;return r+=s,r},t.hexToBytes=function(e){var t="",n=0;e.length&!0&&(n=1,t+=String.fromCharCode(parseInt(e[0],16)));for(;n<e.length;n+=2)t+=String.fromCharCode(parseInt(e.substr(n,2),16));return t},t.bytesToHex=function(e){return t.createBuffer(e).toHex()},t.int32ToBytes=function(e){return String.fromCharCode(e>>24&255)+String.fromCharCode(e>>16&255)+String.fromCharCode(e>>8&255)+String.fromCharCode(e&255)};var n="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",r=[62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,64,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51];t.encode64=function(e,t){var r="",i="",s,o,u,a=0;while(a<e.length)s=e.charCodeAt(a++),o=e.charCodeAt(a++),u=e.charCodeAt(a++),r+=n.charAt(s>>2),r+=n.charAt((s&3)<<4|o>>4),isNaN(o)?r+="==":(r+=n.charAt((o&15)<<2|u>>6),r+=isNaN(u)?"=":n.charAt(u&63)),t&&r.length>t&&(i+=r.substr(0,t)+"\r\n",r=r.substr(t));return i+=r,i},t.decode64=function(e){e=e.replace(/[^A-Za-z0-9\+\/\=]/g,"");var t="",n,i,s,o,u=0;while(u<e.length)n=r[e.charCodeAt(u++)-43],i=r[e.charCodeAt(u++)-43],s=r[e.charCodeAt(u++)-43],o=r[e.charCodeAt(u++)-43],t+=String.fromCharCode(n<<2|i>>4),s!==64&&(t+=String.fromCharCode((i&15)<<4|s>>2),o!==64&&(t+=String.fromCharCode((s&3)<<6|o)));return t},t.encodeUtf8=function(e){return unescape(encodeURIComponent(e))},t.decodeUtf8=function(e){return decodeURIComponent(escape(e))},t.deflate=function(e,n,r){n=t.decode64(e.deflate(t.encode64(n)).rval);if(r){var i=2,s=n.charCodeAt(1);s&32&&(i=6),n=n.substring(i,n.length-4)}return n},t.inflate=function(e,n,r){var i=e.inflate(t.encode64(n)).rval;return i===null?null:t.decode64(i)};var i=function(e,n,r){if(!e)throw{message:"WebStorage not available."};var i;r===null?i=e.removeItem(n):(r=t.encode64(JSON.stringify(r)),i=e.setItem(n,r));if(typeof i!="undefined"&&i.rval!==!0)throw i.error},s=function(e,n){if(!e)throw{message:"WebStorage not available."};var r=e.getItem(n);if(e.init)if(r.rval===null){if(r.error)throw r.error;r=null}else r=r.rval;return r!==null&&(r=JSON.parse(t.decode64(r))),r},o=function(e,t,n,r){var o=s(e,t);o===null&&(o={}),o[n]=r,i(e,t,o)},u=function(e,t,n){var r=s(e,t);return r!==null&&(r=n in r?r[n]:null),r},a=function(e,t,n){var r=s(e,t);if(r!==null&&n in r){delete r[n];var o=!0;for(var u in r){o=!1;break}o&&(r=null),i(e,t,r)}},f=function(e,t){i(e,t,null)},l=function(e,t,n){var r=null;typeof n=="undefined"&&(n=["web","flash"]);var i,s=!1,o=null;for(var u in n){i=n[u];try{if(i==="flash"||i==="both"){if(t[0]===null)throw{message:"Flash local storage not available."};r=e.apply(this,t),s=i==="flash"}if(i==="web"||i==="both")t[0]=localStorage,r=e.apply(this,t),s=!0}catch(a){o=a}if(s)break}if(!s)throw o;return r};t.setItem=function(e,t,n,r,i){l(o,arguments,i)},t.getItem=function(e,t,n,r){return l(u,arguments,r)},t.removeItem=function(e,t,n,r){l(a,arguments,r)},t.clearItems=function(e,t,n){l(f,arguments,n)},t.parseUrl=function(e){var t=/^(https?):\/\/([^:&^\/]*):?(\d*)(.*)$/g;t.lastIndex=0;var n=t.exec(e),r=n===null?null:{full:e,scheme:n[1],host:n[2],port:n[3],path:n[4]};return r&&(r.fullHost=r.host,r.port?r.port!==80&&r.scheme==="http"?r.fullHost+=":"+r.port:r.port!==443&&r.scheme==="https"&&(r.fullHost+=":"+r.port):r.scheme==="http"?r.port=80:r.scheme==="https"&&(r.port=443),r.full=r.scheme+"://"+r.fullHost),r};var c=null;t.getQueryVariables=function(e){var t=function(e){var t={},n=e.split("&");for(var r=0;r<n.length;r++){var i=n[r].indexOf("="),s,o;i>0?(s=n[r].substring(0,i),o=n[r].substring(i+1)):(s=n[r],o=null),s in t||(t[s]=[]),!(s in Object.prototype)&&o!==null&&t[s].push(unescape(o))}return t},n;return typeof e=="undefined"?(c===null&&(typeof window=="undefined"?c={}:c=t(window.location.search.substring(1))),n=c):n=t(e),n},t.parseFragment=function(e){var n=e,r="",i=e.indexOf("?");i>0&&(n=e.substring(0,i),r=e.substring(i+1));var s=n.split("/");s.length>0&&s[0]===""&&s.shift();var o=r===""?{}:t.getQueryVariables(r);return{pathString:n,queryString:r,path:s,query:o}},t.makeRequest=function(e){var n=t.parseFragment(e),r={path:n.pathString,query:n.queryString,getPath:function(e){return typeof e=="undefined"?n.path:n.path[e]},getQuery:function(e,t){var r;return typeof e=="undefined"?r=n.query:(r=n.query[e],r&&typeof t!="undefined"&&(r=r[t])),r},getQueryLast:function(e,t){var n,i=r.getQuery(e);return i?n=i[i.length-1]:n=t,n}};return r},t.makeLink=function(e,t,n){e=jQuery.isArray(e)?e.join("/"):e;var r=jQuery.param(t||{});return n=n||"",e+(r.length>0?"?"+r:"")+(n.length>0?"#"+n:"")},t.setPath=function(e,t,n){if(typeof e=="object"&&e!==null){var r=0,i=t.length;while(r<i){var s=t[r++];if(r==i)e[s]=n;else{var o=s in e;if(!o||o&&typeof e[s]!="object"||o&&e[s]===null)e[s]={};e=e[s]}}}},t.getPath=function(e,t,n){var r=0,i=t.length,s=!0;while(s&&r<i&&typeof e=="object"&&e!==null){var o=t[r++];s=o in e,s&&(e=e[o])}return s?e:n},t.deletePath=function(e,t){if(typeof e=="object"&&e!==null){var n=0,r=t.length;while(n<r){var i=t[n++];if(n==r)delete e[i];else{if(!(i in e&&typeof e[i]=="object"&&e[i]!==null))break;e=e[i]}}}},t.isEmpty=function(e){for(var t in e)if(e.hasOwnProperty(t))return!1;return!0},t.format=function(e){var t=/%./g,n,r,i=0,s=[],o=0;while(n=t.exec(e)){r=e.substring(o,t.lastIndex-2),r.length>0&&s.push(r),o=t.lastIndex;var u=n[0][1];switch(u){case"s":case"o":i<arguments.length?s.push(arguments[i++ +1]):s.push("<?>");break;case"%":s.push("%");break;default:s.push("<%"+u+"?>")}}return s.push(e.substring(o)),s.join("")},t.formatNumber=function(e,t,n,r){var i=e,s=isNaN(t=Math.abs(t))?2:t,o=n===undefined?",":n,u=r===undefined?".":r,a=i<0?"-":"",f=parseInt(i=Math.abs(+i||0).toFixed(s),10)+"",l=f.length>3?f.length%3:0;return a+(l?f.substr(0,l)+u:"")+f.substr(l).replace(/(\d{3})(?=\d)/g,"$1"+u)+(s?o+Math.abs(i-f).toFixed(s).slice(2):"")},t.formatSize=function(e){return e>=1073741824?e=t.formatNumber(e/1073741824,2,".","")+" GiB":e>=1048576?e=t.formatNumber(e/1048576,2,".","")+" MiB":e>=1024?e=t.formatNumber(e/1024,0)+" KiB":e=t.formatNumber(e,0)+" bytes",e}}var t="util";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/util",["require","module"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})})(),function(){function e(e,t){describe("util",function(){it("should put bytes into a buffer",function(){var n=t.createBuffer();n.putByte(1),n.putByte(2),n.putByte(3),n.putByte(4),n.putInt32(4),n.putByte(1),n.putByte(2),n.putByte(3),n.putInt32(4294967295);var r=n.toHex();e.equal(r,"0102030400000004010203ffffffff");var i=[];while(n.length()>0)i.push(n.getByte());e.deepEqual(i,[1,2,3,4,0,0,0,4,1,2,3,255,255,255,255])}),it("should convert bytes from hex",function(){var n="0102030400000004010203ffffffff",r=t.createBuffer();r.putBytes(t.hexToBytes(n)),e.equal(r.toHex(),n)}),it("should base64 encode some bytes",function(){var n="00010203050607080A0B0C0D0F1011121415161719",r="MDAwMTAyMDMwNTA2MDcwODBBMEIwQzBEMEYxMDExMTIxNDE1MTYxNzE5";e.equal(t.encode64(n),r)}),it("should base64 decode some bytes",function(){var n="00010203050607080A0B0C0D0F1011121415161719",r="MDAwMTAyMDMwNTA2MDcwODBBMEIwQzBEMEYxMDExMTIxNDE1MTYxNzE5";e.equal(t.decode64(r),n)})})}typeof define=="function"?define("test/util",["forge/util"],function(t){e(ASSERT,t())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/util")())}(),function(){function e(e){var t=e.md5=e.md5||{};e.md=e.md||{},e.md.algorithms=e.md.algorithms||{},e.md.md5=e.md.algorithms.md5=t;var n=null,r=null,i=null,s=null,o=!1,u=function(){n=String.fromCharCode(128),n+=e.util.fillString(String.fromCharCode(0),64),r=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,1,6,11,0,5,10,15,4,9,14,3,8,13,2,7,12,5,8,11,14,1,4,7,10,13,0,3,6,9,12,15,2,0,7,14,5,12,3,10,1,8,15,6,13,4,11,2,9],i=[7,12,17,22,7,12,17,22,7,12,17,22,7,12,17,22,5,9,14,20,5,9,14,20,5,9,14,20,5,9,14,20,4,11,16,23,4,11,16,23,4,11,16,23,4,11,16,23,6,10,15,21,6,10,15,21,6,10,15,21,6,10,15,21],s=new Array(64);for(var t=0;t<64;++t)s[t]=Math.floor(Math.abs(Math.sin(t+1))*4294967296);o=!0},a=function(e,t,n){var o,u,a,f,l,c,h,p,d=n.length();while(d>=64){u=e.h0,a=e.h1,f=e.h2,l=e.h3;for(p=0;p<16;++p)t[p]=n.getInt32Le(),c=l^a&(f^l),o=u+c+s[p]+t[p],h=i[p],u=l,l=f,f=a,a+=o<<h|o>>>32-h;for(;p<32;++p)c=f^l&(a^f),o=u+c+s[p]+t[r[p]],h=i[p],u=l,l=f,f=a,a+=o<<h|o>>>32-h;for(;p<48;++p)c=a^f^l,o=u+c+s[p]+t[r[p]],h=i[p],u=l,l=f,f=a,a+=o<<h|o>>>32-h;for(;p<64;++p)c=f^(a|~l),o=u+c+s[p]+t[r[p]],h=i[p],u=l,l=f,f=a,a+=o<<h|o>>>32-h;e.h0=e.h0+u&4294967295,e.h1=e.h1+a&4294967295,e.h2=e.h2+f&4294967295,e.h3=e.h3+l&4294967295,d-=64}};t.create=function(){o||u();var t=null,r=e.util.createBuffer(),i=new Array(16),s={algorithm:"md5",blockLength:64,digestLength:16,messageLength:0};return s.start=function(){return s.messageLength=0,r=e.util.createBuffer(),t={h0:1732584193,h1:4023233417,h2:2562383102,h3:271733878},s},s.start(),s.update=function(n,o){return o==="utf8"&&(n=e.util.encodeUtf8(n)),s.messageLength+=n.length,r.putBytes(n),a(t,i,r),(r.read>2048||r.length()===0)&&r.compact(),s},s.digest=function(){var o=s.messageLength,u=e.util.createBuffer();u.putBytes(r.bytes()),u.putBytes(n.substr(0,64-(o+8)%64)),u.putInt32Le(o<<3&4294967295),u.putInt32Le(o>>>29&255);var f={h0:t.h0,h1:t.h1,h2:t.h2,h3:t.h3};a(f,i,u);var l=e.util.createBuffer();return l.putInt32Le(f.h0),l.putInt32Le(f.h1),l.putInt32Le(f.h2),l.putInt32Le(f.h3),l},s}}var t="md5";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/md5",["require","module","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e,t,n){describe("md5",function(){it("should digest the empty string",function(){var n=t.create();e.equal(n.digest().toHex(),"d41d8cd98f00b204e9800998ecf8427e")}),it('should digest "abc"',function(){var n=t.create();n.update("abc"),e.equal(n.digest().toHex(),"900150983cd24fb0d6963f7d28e17f72")}),it('should digest "The quick brown fox jumps over the lazy dog"',function(){var n=t.create();n.update("The quick brown fox jumps over the lazy dog"),e.equal(n.digest().toHex(),"9e107d9d372bb6826bd81d3542a419d6")}),it('should digest "c\'è"',function(){var n=t.create();n.update("c'è","utf8"),e.equal(n.digest().toHex(),"8ef7c2941d78fe89f31e614437c9db59")}),it('should digest "THIS IS A MESSAGE"',function(){var n=t.create();n.start(),n.update("THIS IS "),n.update("A MESSAGE"),e.equal(n.digest().toHex(),"78eebfd9d42958e3f31244f116ab7bbe"),e.equal(n.digest().toHex(),"78eebfd9d42958e3f31244f116ab7bbe")}),it("should digest a long message",function(){var r=n.hexToBytes("0100002903018d32e9c6dc423774c4c39a5a1b78f44cc2cab5f676d39f703d29bfa27dfeb870000002002f01000200004603014c2c1e835d39da71bc0857eb04c2b50fe90dbb2a8477fe7364598d6f0575999c20a6c7248c5174da6d03ac711888f762fc4ed54f7254b32273690de849c843073d002f000b0003d20003cf0003cc308203c8308202b0a003020102020100300d06092a864886f70d0101050500308186310b3009060355040613025553311d301b060355040a13144469676974616c2042617a6161722c20496e632e31443042060355040b133b4269746d756e6b206c6f63616c686f73742d6f6e6c7920436572746966696361746573202d20417574686f72697a6174696f6e207669612042545031123010060355040313096c6f63616c686f7374301e170d3130303231343137303931395a170d3230303231333137303931395a308186310b3009060355040613025553311d301b060355040a13144469676974616c2042617a6161722c20496e632e31443042060355040b133b4269746d756e6b206c6f63616c686f73742d6f6e6c7920436572746966696361746573202d20417574686f72697a6174696f6e207669612042545031123010060355040313096c6f63616c686f737430820122300d06092a864886f70d01010105000382010f003082010a0282010100dc436f17d6909d8a9d6186ea218eb5c86b848bae02219bd56a71203daf07e81bc19e7e98134136bcb012881864bf03b3774652ad5eab85dba411a5114ffeac09babce75f31314345512cd87c91318b2e77433270a52185fc16f428c3ca412ad6e9484bc2fb87abb4e8fb71bf0f619e31a42340b35967f06c24a741a31c979c0bb8921a90a47025fbeb8adca576979e70a56830c61170c9647c18c0794d68c0df38f3aac5fc3b530e016ea5659715339f3f3c209cdee9dbe794b5af92530c5754c1d874b78974bfad994e0dfc582275e79feb522f6e4bcc2b2945baedfb0dbdaebb605f9483ff0bea29ecd5f4d6f2769965d1b3e04f8422716042680011ff676f0203010001a33f303d300c0603551d130101ff04023000300e0603551d0f0101ff0404030204f0301d0603551d250416301406082b0601050507030106082b06010505070302300d06092a864886f70d010105050003820101009c4562be3f2d8d8e388085a697f2f106eaeff4992a43f198fe3dcf15c8229cf1043f061a38204f73d86f4fb6348048cc5279ed719873aa10e3773d92b629c2c3fcce04012c81ba3b4ec451e9644ec5191078402d845e05d02c7b4d974b4588276e5037aba7ef26a8bddeb21e10698c82f425e767dc401adf722fa73ab78cfa069bd69052d7ca6a75cc9225550e315d71c5f8764362ea4dbc6ecb837a8471043c5a7f826a71af145a053090bd4bccca6a2c552841cdb1908a8352f49283d2e641acdef667c7543af441a16f8294251e2ac376fa507b53ae418dd038cd20cef1e7bfbf5ae03a7c88d93d843abaabbdc5f3431132f3e559d2dd414c3eda38a210b80e00000010000102010026a220b7be857402819b78d81080d01a682599bbd00902985cc64edf8e520e4111eb0e1729a14ffa3498ca259cc9ad6fc78fa130d968ebdb78dc0b950c0aa44355f13ba678419185d7e4608fe178ca6b2cef33e4193778d1a70fe4d0dfcb110be4bbb4dbaa712177655728f914ab4c0f6c4aef79a46b3d996c82b2ebe9ed1748eb5cace7dc44fb67e73f452a047f2ed199b3d50d5db960acf03244dc8efa4fc129faf8b65f9e52e62b5544722bd17d2358e817a777618a4265a3db277fc04851a82a91fe6cdcb8127f156e0b4a5d1f54ce2742eb70c895f5f8b85f5febe69bc73e891f9280826860a0c2ef94c7935e6215c3c4cd6b0e43e80cca396d913d36be"),i=t.create();i.update(r),e.equal(i.digest().toHex(),"d15a2da0e92c3da55dc573f885b6e653")})})}typeof define=="function"?define("test/md5",["forge/md5","forge/util"],function(t,n){e(ASSERT,t(),n())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/md5")(),require("../../js/util")())}(),function(){function e(e){var t=e.sha1=e.sha1||{};e.md=e.md||{},e.md.algorithms=e.md.algorithms||{},e.md.sha1=e.md.algorithms.sha1=t;var n=null,r=!1,i=function(){n=String.fromCharCode(128),n+=e.util.fillString(String.fromCharCode(0),64),r=!0},s=function(e,t,n){var r,i,s,o,u,a,f,l,c=n.length();while(c>=64){i=e.h0,s=e.h1,o=e.h2,u=e.h3,a=e.h4;for(l=0;l<16;++l)r=n.getInt32(),t[l]=r,f=u^s&(o^u),r=(i<<5|i>>>27)+f+a+1518500249+r,a=u,u=o,o=s<<30|s>>>2,s=i,i=r;for(;l<20;++l)r=t[l-3]^t[l-8]^t[l-14]^t[l-16],r=r<<1|r>>>31,t[l]=r,f=u^s&(o^u),r=(i<<5|i>>>27)+f+a+1518500249+r,a=u,u=o,o=s<<30|s>>>2,s=i,i=r;for(;l<32;++l)r=t[l-3]^t[l-8]^t[l-14]^t[l-16],r=r<<1|r>>>31,t[l]=r,f=s^o^u,r=(i<<5|i>>>27)+f+a+1859775393+r,a=u,u=o,o=s<<30|s>>>2,s=i,i=r;for(;l<40;++l)r=t[l-6]^t[l-16]^t[l-28]^t[l-32],r=r<<2|r>>>30,t[l]=r,f=s^o^u,r=(i<<5|i>>>27)+f+a+1859775393+r,a=u,u=o,o=s<<30|s>>>2,s=i,i=r;for(;l<60;++l)r=t[l-6]^t[l-16]^t[l-28]^t[l-32],r=r<<2|r>>>30,t[l]=r,f=s&o|u&(s^o),r=(i<<5|i>>>27)+f+a+2400959708+r,a=u,u=o,o=s<<30|s>>>2,s=i,i=r;for(;l<80;++l)r=t[l-6]^t[l-16]^t[l-28]^t[l-32],r=r<<2|r>>>30,t[l]=r,f=s^o^u,r=(i<<5|i>>>27)+f+a+3395469782+r,a=u,u=o,o=s<<30|s>>>2,s=i,i=r;e.h0+=i,e.h1+=s,e.h2+=o,e.h3+=u,e.h4+=a,c-=64}};t.create=function(){r||i();var t=null,o=e.util.createBuffer(),u=new Array(80),a={algorithm:"sha1",blockLength:64,digestLength:20,messageLength:0};return a.start=function(){return a.messageLength=0,o=e.util.createBuffer(),t={h0:1732584193,h1:4023233417,h2:2562383102,h3:271733878,h4:3285377520},a},a.start(),a.update=function(n,r){return r==="utf8"&&(n=e.util.encodeUtf8(n)),a.messageLength+=n.length,o.putBytes(n),s(t,u,o),(o.read>2048||o.length()===0)&&o.compact(),a},a.digest=function(){var r=a.messageLength,i=e.util.createBuffer();i.putBytes(o.bytes()),i.putBytes(n.substr(0,64-(r+8)%64)),i.putInt32(r>>>29&255),i.putInt32(r<<3&4294967295);var f={h0:t.h0,h1:t.h1,h2:t.h2,h3:t.h3,h4:t.h4};s(f,u,i);var l=e.util.createBuffer();return l.putInt32(f.h0),l.putInt32(f.h1),l.putInt32(f.h2),l.putInt32(f.h3),l.putInt32(f.h4),l},a}}var t="sha1";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/sha1",["require","module","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e,t,n){describe("sha1",function(){it("should digest the empty string",function(){var n=t.create();e.equal(n.digest().toHex(),"da39a3ee5e6b4b0d3255bfef95601890afd80709")}),it('should digest "abc"',function(){var n=t.create();n.update("abc"),e.equal(n.digest().toHex(),"a9993e364706816aba3e25717850c26c9cd0d89d")}),it('should digest "The quick brown fox jumps over the lazy dog"',function(){var n=t.create();n.update("The quick brown fox jumps over the lazy dog"),e.equal(n.digest().toHex(),"2fd4e1c67a2d28fced849ee1bb76e7391b93eb12")}),it('should digest "c\'è"',function(){var n=t.create();n.update("c'è","utf8"),e.equal(n.digest().toHex(),"98c9a3f804daa73b68a5660d032499a447350c0d")}),it('should digest "THIS IS A MESSAGE"',function(){var n=t.create();n.start(),n.update("THIS IS "),n.update("A MESSAGE"),e.equal(n.digest().toHex(),"5f24f4d6499fd2d44df6c6e94be8b14a796c071d"),e.equal(n.digest().toHex(),"5f24f4d6499fd2d44df6c6e94be8b14a796c071d")}),it("should digest a long message",function(){var r=t.create();r.update(n.fillString("a",1e6)),e.equal(r.digest().toHex(),"34aa973cd4c4daa4f61eeb2bdbad27316534016f")})})}typeof define=="function"?define("test/sha1",["forge/sha1","forge/util"],function(t,n){e(ASSERT,t(),n())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/sha1")(),require("../../js/util")())}(),function(){function e(e){var t=e.sha256=e.sha256||{};e.md=e.md||{},e.md.algorithms=e.md.algorithms||{},e.md.sha256=e.md.algorithms.sha256=t;var n=null,r=!1,i=null,s=function(){n=String.fromCharCode(128),n+=e.util.fillString(String.fromCharCode(0),64),i=[1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298],r=!0},o=function(e,t,n){var r,s,o,u,a,f,l,c,h,p,d,v,m,g,y,b=n.length();while(b>=64){for(l=0;l<16;++l)t[l]=n.getInt32();for(;l<64;++l)r=t[l-2],r=(r>>>17|r<<15)^(r>>>19|r<<13)^r>>>10,s=t[l-15],s=(s>>>7|s<<25)^(s>>>18|s<<14)^s>>>3,t[l]=r+t[l-7]+s+t[l-16]&4294967295;c=e.h0,h=e.h1,p=e.h2,d=e.h3,v=e.h4,m=e.h5,g=e.h6,y=e.h7;for(l=0;l<64;++l)u=(v>>>6|v<<26)^(v>>>11|v<<21)^(v>>>25|v<<7),a=g^v&(m^g),o=(c>>>2|c<<30)^(c>>>13|c<<19)^(c>>>22|c<<10),f=c&h|p&(c^h),r=y+u+a+i[l]+t[l],s=o+f,y=g,g=m,m=v,v=d+r&4294967295,d=p,p=h,h=c,c=r+s&4294967295;e.h0=e.h0+c&4294967295,e.h1=e.h1+h&4294967295,e.h2=e.h2+p&4294967295,e.h3=e.h3+d&4294967295,e.h4=e.h4+v&4294967295,e.h5=e.h5+m&4294967295,e.h6=e.h6+g&4294967295,e.h7=e.h7+y&4294967295,b-=64}};t.create=function(){r||s();var t=null,i=e.util.createBuffer(),u=new Array(64),a={algorithm:"sha256",blockLength:64,digestLength:32,messageLength:0};return a.start=function(){return a.messageLength=0,i=e.util.createBuffer(),t={h0:1779033703,h1:3144134277,h2:1013904242,h3:2773480762,h4:1359893119,h5:2600822924,h6:528734635,h7:1541459225},a},a.start(),a.update=function(n,r){return r==="utf8"&&(n=e.util.encodeUtf8(n)),a.messageLength+=n.length,i.putBytes(n),o(t,u,i),(i.read>2048||i.length()===0)&&i.compact(),a},a.digest=function(){var r=a.messageLength,s=e.util.createBuffer();s.putBytes(i.bytes()),s.putBytes(n.substr(0,64-(r+8)%64)),s.putInt32(r>>>29&255),s.putInt32(r<<3&4294967295);var f={h0:t.h0,h1:t.h1,h2:t.h2,h3:t.h3,h4:t.h4,h5:t.h5,h6:t.h6,h7:t.h7};o(f,u,s);var l=e.util.createBuffer();return l.putInt32(f.h0),l.putInt32(f.h1),l.putInt32(f.h2),l.putInt32(f.h3),l.putInt32(f.h4),l.putInt32(f.h5),l.putInt32(f.h6),l.putInt32(f.h7),l},a}}var t="sha256";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/sha256",["require","module","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e,t,n){describe("sha256",function(){it("should digest the empty string",function(){var n=t.create();e.equal(n.digest().toHex(),"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")}),it('should digest "abc"',function(){var n=t.create();n.update("abc"),e.equal(n.digest().toHex(),"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad")}),it('should digest "The quick brown fox jumps over the lazy dog"',function(){var n=t.create();n.update("The quick brown fox jumps over the lazy dog"),e.equal(n.digest().toHex(),"d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592")}),it('should digest "c\'è"',function(){var n=t.create();n.update("c'è","utf8"),e.equal(n.digest().toHex(),"1aa15c717afffd312acce2217ce1c2e5dabca53c92165999132ec9ca5decdaca")}),it('should digest "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"',function(){var n=t.create();n.start(),n.update("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"),e.equal(n.digest().toHex(),"248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1"),e.equal(n.digest().toHex(),"248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1")}),it("should digest a long message",function(){var r=t.create();r.update(n.fillString("a",1e6)),e.equal(r.digest().toHex(),"cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0")})})}typeof define=="function"?define("test/sha256",["forge/sha256","forge/util"],function(t,n){e(ASSERT,t(),n())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/sha256")(),require("../../js/util")())}(),function(){function e(e){e.md=e.md||{},e.md.algorithms={md5:e.md5,sha1:e.sha1,sha256:e.sha256},e.md.md5=e.md5,e.md.sha1=e.sha1,e.md.sha256=e.sha256}var t="md";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/md",["require","module","./md5","./sha1","./sha256"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){var t=e.hmac=e.hmac||{};t.create=function(){var t=null,n=null,r=null,i=null,s={};return s.start=function(s,o){if(s!==null)if(typeof s=="string"){s=s.toLowerCase();if(!(s in e.md.algorithms))throw'Unknown hash algorithm "'+s+'"';n=e.md.algorithms[s].create()}else n=s;if(o===null)o=t;else{if(typeof o=="string")o=e.util.createBuffer(o);else if(e.util.isArray(o)){var u=o;o=e.util.createBuffer();for(var a=0;a<u.length;++a)o.putByte(u[a])}var f=o.length();f>n.blockLength&&(n.start(),n.update(o.bytes()),o=n.digest()),r=e.util.createBuffer(),i=e.util.createBuffer(),f=o.length();for(var a=0;a<f;++a){var u=o.at(a);r.putByte(54^u),i.putByte(92^u)}if(f<n.blockLength){var u=n.blockLength-f;for(var a=0;a<u;++a)r.putByte(54),i.putByte(92)}t=o,r=r.bytes(),i=i.bytes()}n.start(),n.update(r)},s.update=function(e){n.update(e)},s.getMac=function(){var e=n.digest().bytes();return n.start(),n.update(i),n.update(e),n.digest()},s.digest=s.getMac,s}}var t="hmac";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/hmac",["require","module","./md","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e,t,n){describe("hmac",function(){it('should md5 hash "Hi There", 16-byte key',function(){var r=n.hexToBytes("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"),i=t.create();i.start("MD5",r),i.update("Hi There"),e.equal(i.digest().toHex(),"9294727a3638bb1c13f48ef8158bfc9d")}),it('should md5 hash "what do ya want for nothing?", "Jefe" key',function(){var n=t.create();n.start("MD5","Jefe"),n.update("what do ya want for nothing?"),e.equal(n.digest().toHex(),"750c783e6ab0b503eaa86e310a5db738")}),it('should md5 hash "Test Using Larger Than Block-Size Key - Hash Key First", 80-byte key',function(){var r=n.hexToBytes("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),i=t.create();i.start("MD5",r),i.update("Test Using Larger Than Block-Size Key - Hash Key First"),e.equal(i.digest().toHex(),"6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd")}),it('should sha1 hash "Hi There", 20-byte key',function(){var r=n.hexToBytes("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"),i=t.create();i.start("SHA1",r),i.update("Hi There"),e.equal(i.digest().toHex(),"b617318655057264e28bc0b6fb378c8ef146be00")}),it('should sha1 hash "what do ya want for nothing?", "Jefe" key',function(){var n=t.create();n.start("SHA1","Jefe"),n.update("what do ya want for nothing?"),e.equal(n.digest().toHex(),"effcdf6ae5eb2fa2d27416d5f184df9c259a7c79")}),it('should sha1 hash "Test Using Larger Than Block-Size Key - Hash Key First", 80-byte key',function(){var r=n.hexToBytes("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),i=t.create();i.start("SHA1",r),i.update("Test Using Larger Than Block-Size Key - Hash Key First"),e.equal(i.digest().toHex(),"aa4ae5e15272d00e95705637ce8a3b55ed402112")})})}typeof define=="function"?define("test/hmac",["forge/hmac","forge/util"],function(t,n){e(ASSERT,t(),n())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/hmac")(),require("../../js/util")())}(),function(){function e(e){var t=e.pkcs5=e.pkcs5||{};e.pbkdf2=t.pbkdf2=function(t,n,r,i,s){if(typeof s=="undefined"||s===null)s=e.md.sha1.create();var o=s.digestLength;if(i>4294967295*o)throw{message:"Derived key is too long."};var u=Math.ceil(i/o),a=i-(u-1)*o,f=e.hmac.create();f.start(s,t);var l="",c,h,p;for(var d=1;d<=u;++d){f.update(n),f.update(e.util.int32ToBytes(d)),c=p=f.digest().getBytes();for(var v=2;v<=r;++v)f.start(null,null),f.update(p),h=f.digest().getBytes(),c=e.util.xorBytes(c,h,o),p=h;l+=d<u?c:c.substr(0,a)}return l}}var t="pbkdf2";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/pbkdf2",["require","module","./hmac","./md","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e,t,n){describe("pbkdf2",function(){it("should derive a password with hmac-sha-1 c=1",function(){var r=n.bytesToHex(t("password","salt",1,20));e.equal(r,"0c60c80f961f0e71f3a9b524af6012062fe037a6")}),it("should derive a password with hmac-sha-1 c=2",function(){var r=n.bytesToHex(t("password","salt",2,20));e.equal(r,"ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957")}),it("should derive a password with hmac-sha-1 c=5 keylen=8",function(){var r=n.hexToBytes("1234567878563412"),i=n.bytesToHex(t("password",r,5,8));e.equal(i,"d1daa78615f287e6")}),it("should derive a password with hmac-sha-1 c=4096",function(){var r=n.bytesToHex(t("password","salt",4096,20));e.equal(r,"4b007901b765489abead49d926f721d065a429c1")})})}typeof define=="function"?define("test/pbkdf2",["forge/pbkdf2","forge/util"],function(t,n){e(ASSERT,t(),n())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/pbkdf2")(),require("../../js/util")())}(),function(){function e(e){e.mgf=e.mgf||{};var t=e.mgf.mgf1=e.mgf1=e.mgf1||{};t.create=function(t){var n={generate:function(n,r){var i=new e.util.ByteBuffer,s=Math.ceil(r/t.digestLength);for(var o=0;o<s;o++){var u=new e.util.ByteBuffer;u.putInt32(o),t.start(),t.update(n+u.getBytes()),i.putBuffer(t.digest())}return i.truncate(i.length()-r),i.getBytes()}};return n}}var t="mgf1";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/mgf1",["require","module","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){e.mgf=e.mgf||{},e.mgf.mgf1=e.mgf1}var t="mgf";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/mgf",["require","module","./mgf1"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e,t,n,r){describe("mgf1",function(){it("should digest the empty string",function(){var i=r.hexToBytes("032e45326fa859a72ec235acff929b15d1372e30b207255f0611b8f785d764374152e0ac009e509e7ba30cd2f1778e113b64e135cf4e2292c75efe5288edfda4"),s=r.hexToBytes("5f8de105b5e96b2e490ddecbd147dd1def7e3b8e0e6a26eb7b956ccb8b3bdc1ca975bc57c3989e8fbad31a224655d800c46954840ff32052cdf0d640562bdfadfa263cfccf3c52b29f2af4a1869959bc77f854cf15bd7a25192985a842dbff8e13efee5b7e7e55bbe4d389647c686a9a9ab3fb889b2d7767d3837eea4e0a2f04"),o=t.mgf1.create(n.sha1.create()),u=o.generate(i,s.length);e.equal(u,s)})})}typeof define=="function"?define("test/mgf1",["forge/mgf","forge/md","forge/util"],function(t,n,r){e(ASSERT,t(),n(),r())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/mgf")(),require("../../js/md")(),require("../../js/util")())}(),function(){function e(e){var t=!1,n=4,r,i,s,o,u,a=function(){t=!0,s=[0,1,2,4,8,16,32,64,128,27,54];var e=new Array(256);for(var n=0;n<128;++n)e[n]=n<<1,e[n+128]=n+128<<1^283;r=new Array(256),i=new Array(256),o=new Array(4),u=new Array(4);for(var n=0;n<4;++n)o[n]=new Array(256),u[n]=new Array(256);var a=0,f=0,l,c,h,p,d,v,m;for(var n=0;n<256;++n){p=f^f<<1^f<<2^f<<3^f<<4,p=p>>8^p&255^99,r[a]=p,i[p]=a,d=e[p],l=e[a],c=e[l],h=e[c],v=d<<24^p<<16^p<<8^(p^d),m=(l^c^h)<<24^(a^h)<<16^(a^c^h)<<8^(a^l^h);for(var g=0;g<4;++g)o[g][a]=v,u[g][p]=m,v=v<<24|v>>>8,m=m<<24|m>>>8;a===0?a=f=1:(a=l^e[e[e[l^h]]],f^=e[e[f]])}},f=function(e,t){var i=e.slice(0),o,a=1,f=i.length,l=f+6+1,c=n*l;for(var h=f;h<c;++h)o=i[h-1],h%f===0?(o=r[o>>>16&255]<<24^r[o>>>8&255]<<16^r[o&255]<<8^r[o>>>24]^s[a]<<24,a++):f>6&&h%f===4&&(o=r[o>>>24]<<24^r[o>>>16&255]<<16^r[o>>>8&255]<<8^r[o&255]),i[h]=i[h-f]^o;if(t){var p,d=u[0],v=u[1],m=u[2],g=u[3],y=i.slice(0),c=i.length;for(var h=0,b=c-n;h<c;h+=n,b-=n)if(h===0||h===c-n)y[h]=i[b],y[h+1]=i[b+3],y[h+2]=i[b+2],y[h+3]=i[b+1];else for(var w=0;w<n;++w)p=i[b+w],y[h+(3&-w)]=d[r[p>>>24]]^v[r[p>>>16&255]]^m[r[p>>>8&255]]^g[r[p&255]];i=y}return i},l=function(e,t,n,s){var a=e.length/4-1,f,l,c,h,p;s?(f=u[0],l=u[1],c=u[2],h=u[3],p=i):(f=o[0],l=o[1],c=o[2],h=o[3],p=r);var d,v,m,g,y,b,w;d=t[0]^e[0],v=t[s?3:1]^e[1],m=t[2]^e[2],g=t[s?1:3]^e[3];var E=3;for(var S=1;S<a;++S)y=f[d>>>24]^l[v>>>16&255]^c[m>>>8&255]^h[g&255]^e[++E],b=f[v>>>24]^l[m>>>16&255]^c[g>>>8&255]^h[d&255]^e[++E],w=f[m>>>24]^l[g>>>16&255]^c[d>>>8&255]^h[v&255]^e[++E],g=f[g>>>24]^l[d>>>16&255]^c[v>>>8&255]^h[m&255]^e[++E],d=y,v=b,m=w;n[0]=p[d>>>24]<<24^p[v>>>16&255]<<16^p[m>>>8&255]<<8^p[g&255]^e[++E],n[s?3:1]=p[v>>>24]<<24^p[m>>>16&255]<<16^p[g>>>8&255]<<8^p[d&255]^e[++E],n[2]=p[m>>>24]<<24^p[g>>>16&255]<<16^p[d>>>8&255]<<8^p[v&255]^e[++E],n[s?1:3]=p[g>>>24]<<24^p[d>>>16&255]<<16^p[v>>>8&255]<<8^p[m&255]^e[++E]},c=function(r,i,s,o,u){function C(){if(o)for(var e=0;e<n;++e)E[e]=b.getInt32();else for(var e=0;e<n;++e)E[e]=x[e]^b.getInt32();l(g,E,S,o);if(o){for(var e=0;e<n;++e)w.putInt32(x[e]^S[e]);x=E.slice(0)}else{for(var e=0;e<n;++e)w.putInt32(S[e]);x=S}}function k(){l(g,E,S,!1);for(var e=0;e<n;++e)E[e]=b.getInt32();for(var e=0;e<n;++e){var t=E[e]^S[e];o||(E[e]=t),w.putInt32(t)}}function L(){l(g,E,S,!1);for(var e=0;e<n;++e)E[e]=b.getInt32();for(var e=0;e<n;++e)w.putInt32(E[e]^S[e]),E[e]=S[e]}function A(){l(g,E,S,!1);for(var e=n-1;e>=0;--e){if(E[e]!==4294967295){++E[e];break}E[e]=0}for(var e=0;e<n;++e)w.putInt32(b.getInt32()^S[e])}var c=null;t||a(),u=(u||"CBC").toUpperCase();if(typeof r!="string"||r.length!==16&&r.length!==24&&r.length!==32){if(e.util.isArray(r)&&(r.length===16||r.length===24||r.length===32)){var h=r,r=e.util.createBuffer();for(var p=0;p<h.length;++p)r.putByte(h[p])}}else r=e.util.createBuffer(r);if(!e.util.isArray(r)){var h=r;r=[];var d=h.length();if(d===16||d===24||d===32){d>>>=2;for(var p=0;p<d;++p)r.push(h.getInt32())}}if(!e.util.isArray(r)||r.length!==4&&r.length!==6&&r.length!==8)return c;var v=["CFB","OFB","CTR"].indexOf(u)!==-1,m=u==="CBC",g=f(r,o&&!v),y=n<<2,b,w,E,S,x,T,N;c={output:null};if(u==="CBC")N=C;else if(u==="CFB")N=k;else if(u==="OFB")N=L;else{if(u!=="CTR")throw{message:'Unsupported block cipher mode of operation: "'+u+'"'};N=A}return c.update=function(e){T||b.putBuffer(e);while(b.length()>=y||b.length()>0&&T)N()},c.finish=function(e){var t=!0,r=b.length()%y;if(!o)if(e)t=e(y,b,o);else if(m){var i=b.length()===y?y:y-b.length();b.fillWithByte(i,i)}t&&(T=!0,c.update());if(o){m&&(t=r===0);if(t)if(e)t=e(y,w,o);else if(m){var s=w.length(),u=w.at(s-1);u>n<<2?t=!1:w.truncate(u)}}return!m&&!e&&r>0&&w.truncate(y-r),t},c.start=function(t,r){t===null&&(t=x.slice(0));if(typeof t=="string"&&t.length===16)t=e.util.createBuffer(t);else if(e.util.isArray(t)&&t.length===16){var i=t,t=e.util.createBuffer();for(var s=0;s<16;++s)t.putByte(i[s])}if(!e.util.isArray(t)){var i=t;t=new Array(4),t[0]=i.getInt32(),t[1]=i.getInt32(),t[2]=i.getInt32(),t[3]=i.getInt32()}b=e.util.createBuffer(),w=r||e.util.createBuffer(),x=t.slice(0),E=new Array(n),S=new Array(n),T=!1,c.output=w;if(["CFB","OFB","CTR"].indexOf(u)!==-1){for(var s=0;s<n;++s)E[s]=x[s];x=null}},i!==null&&c.start(i,s),c};e.aes=e.aes||{},e.aes.startEncrypting=function(e,t,n,r){return c(e,t,n,!1,r)},e.aes.createEncryptionCipher=function(e,t){return c(e,null,null,!1,t)},e.aes.startDecrypting=function(e,t,n,r){return c(e,t,n,!0,r)},e.aes.createDecryptionCipher=function(e,t){return c(e,null,null,!0,t)},e.aes._expandKey=function(e,n){return t||a(),f(e,n)},e.aes._updateBlock=l}var t="aes";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/aes",["require","module","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){var t=typeof process!="undefined"&&process.versions&&process.versions.node,n=null;!e.disableNativeCode&&t&&(n=require("crypto"));var r=e.prng=e.prng||{};r.create=function(t){function u(e){if(r.pools[0].messageLength>=32)return f(),e();var t=32-r.pools[0].messageLength<<5;r.seedFile(t,function(t,n){if(t)return e(t);r.collect(n),f(),e()})}function a(){if(r.pools[0].messageLength>=32)return f();var e=32-r.pools[0].messageLength<<5;r.collect(r.seedFileSync(e)),f()}function f(){var t=e.md.sha1.create();t.update(r.pools[0].digest().getBytes()),r.pools[0].start();var n=1;for(var i=1;i<32;++i)n=n===31?2147483648:n<<2,n%r.reseeds===0&&(t.update(r.pools[i].digest().getBytes()),r.pools[i].start());var s=t.digest().getBytes();t.start(),t.update(s);var o=t.digest().getBytes();r.key=r.plugin.formatKey(s),r.seed=r.plugin.formatSeed(o),++r.reseeds,r.generated=0,r.time=+(new Date)}function l(t){var n=e.util.createBuffer();if(typeof window!="undefined"&&window.crypto&&window.crypto.getRandomValues){var r=new Uint32Array(t/4);try{window.crypto.getRandomValues(r);for(var i=0;i<r.length;++i)n.putInt32(r[i])}catch(s){}}if(n.length()<t){var o,u,a,f=Math.floor(Math.random()*65535);while(n.length()<t){u=16807*(f&65535),o=16807*(f>>16),u+=(o&32767)<<16,u+=o>>15,u=(u&2147483647)+(u>>31),f=u&4294967295;for(var i=0;i<3;++i)a=f>>>(i<<3),a^=Math.floor(Math.random()*255),n.putByte(String.fromCharCode(a&255))}}return n.getBytes()}var r={plugin:t,key:null,seed:null,time:null,reseeds:0,generated:0},i=t.md,s=new Array(32);for(var o=0;o<32;++o)s[o]=i.create();return r.pools=s,r.pool=0,r.generate=function(t,n){function l(c){if(c)return n(c);if(f.length()>=t)return n(null,f.getBytes(t));if(r.generated>=1048576){var h=+(new Date);if(r.time===null||h-r.time>100)r.key=null}if(r.key===null)return u(l);var p=i(r.key,r.seed);r.generated+=p.length,f.putBytes(p),r.key=o(i(r.key,s(r.seed))),r.seed=a(i(r.key,r.seed)),e.util.setImmediate(l)}if(!n)return r.generateSync(t);var i=r.plugin.cipher,s=r.plugin.increment,o=r.plugin.formatKey,a=r.plugin.formatSeed,f=e.util.createBuffer();l()},r.generateSync=function(t){var n=r.plugin.cipher,i=r.plugin.increment,s=r.plugin.formatKey,o=r.plugin.formatSeed,u=e.util.createBuffer();while(u.length()<t){if(r.generated>=1048576){var f=+(new Date);if(r.time===null||f-r.time>100)r.key=null}r.key===null&&a();var l=n(r.key,r.seed);r.generated+=l.length,u.putBytes(l),r.key=s(n(r.key,i(r.seed))),r.seed=o(n(r.key,r.seed))}return u.getBytes(t)},n?(r.seedFile=function(e,t){n.randomBytes(e,function(e,n){if(e)return t(e);t(null,n.toString())})},r.seedFileSync=function(e){return n.randomBytes(e).toString()}):(r.seedFile=function(e,t){try{t(null,l(e))}catch(n){t(n)}},r.seedFileSync=l),r.collect=function(e){var t=e.length;for(var n=0;n<t;++n)r.pools[r.pool].update(e.substr(n,1)),r.pool=r.pool===31?0:r.pool+1},r.collectInt=function(e,t){var n="";for(var i=0;i<t;i+=8)n+=String.fromCharCode(e>>i&255);r.collect(n)},r.registerWorker=function(e){if(e===self)r.seedFile=function(e,t){function n(e){var r=e.data;r.forge&&r.forge.prng&&(self.removeEventListener("message",n),t(r.forge.prng.err,r.forge.prng.bytes))}self.addEventListener("message",n),self.postMessage({forge:{prng:{needed:e}}})};else{function t(t){var n=t.data;n.forge&&n.forge.prng&&r.seedFile(n.forge.prng.needed,function(t,n){e.postMessage({forge:{prng:{err:t,bytes:n}}})})}e.addEventListener("message",t)}},r}}var t="prng";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/prng",["require","module","./md","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){if(e.random&&e.random.getBytes)return;(function(t){var n={},r=new Array(4),i=e.util.createBuffer();n.formatKey=function(t){var n=e.util.createBuffer(t);return t=new Array(4),t[0]=n.getInt32(),t[1]=n.getInt32(),t[2]=n.getInt32(),t[3]=n.getInt32(),e.aes._expandKey(t,!1)},n.formatSeed=function(t){var n=e.util.createBuffer(t);return t=new Array(4),t[0]=n.getInt32(),t[1]=n.getInt32(),t[2]=n.getInt32(),t[3]=n.getInt32(),t},n.cipher=function(t,n){return e.aes._updateBlock(t,n,r,!1),i.putInt32(r[0]),i.putInt32(r[1]),i.putInt32(r[2]),i.putInt32(r[3]),i.getBytes()},n.increment=function(e){return++e[3],e},n.md=e.md.sha1;var s=e.prng.create(n),o=typeof process!="undefined"&&process.versions&&process.versions.node;if(e.disableNativeCode||!o&&(typeof window=="undefined"||!window.crypto||!window.crypto.getRandomValues)){typeof window=="undefined"||window.document===undefined,s.collectInt(+(new Date),32);if(typeof navigator!="undefined"){var u="";for(var a in navigator)try{typeof navigator[a]=="string"&&(u+=navigator[a])}catch(f){}s.collect(u),u=null}t&&(t().mousemove(function(e){s.collectInt(e.clientX,16),s.collectInt(e.clientY,16)}),t().keypress(function(e){s.collectInt(e.charCode,8)}))}if(!e.random)e.random=s;else for(var a in s)e.random[a]=s[a];e.random.getBytes=function(t,n){return e.random.generate(t,n)},e.random.getBytesSync=function(t){return e.random.generate(t)}})(typeof jQuery!="undefined"?jQuery:null)}var t="random";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/random",["require","module","./aes","./md","./prng","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e,t,n){var r=t();describe("random",function(){it("should generate 10 random bytes",function(){r.getBytes(16),r.getBytes(24),r.getBytes(32);var t=r.getBytes(10);e.equal(t.length,10)}),it("should use a synchronous seed file",function(){var r=t();r.seedFileSync=function(e){return n.fillString("a",e)};var i=r.getBytes(10);e.equal(n.bytesToHex(i),"a44857544b3df0fcac84")}),it("should use an asynchronous seed file",function(r){var i=t();i.seedFile=function(e,t){t(null,n.fillString("a",e))},i.getBytes(10,function(t,i){e.equal(t,null),e.equal(n.bytesToHex(i),"a44857544b3df0fcac84"),r()})}),it("should collect some random bytes",function(){var r=t();r.seedFileSync=function(e){return n.fillString("a",e)},r.collect("bbb");var i=r.getBytes(10);e.equal(n.bytesToHex(i),"8274fa6e0a192d670ddb")})})}typeof define=="function"?define("test/random",["forge/random","forge/util"],function(t,n){e(ASSERT,t,n())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/random"),require("../../js/util")())}(),function(){function e(e){e.pki=e.pki||{};var t=e.pki.oids=e.oids=e.oids||{};t["1.2.840.113549.1.1.1"]="rsaEncryption",t.rsaEncryption="1.2.840.113549.1.1.1",t["1.2.840.113549.1.1.4"]="md5WithRSAEncryption",t.md5WithRSAEncryption="1.2.840.113549.1.1.4",t["1.2.840.113549.1.1.5"]="sha1WithRSAEncryption",t.sha1WithRSAEncryption="1.2.840.113549.1.1.5",t["1.2.840.113549.1.1.7"]="RSAES-OAEP",t["RSAES-OAEP"]="1.2.840.113549.1.1.7",t["1.2.840.113549.1.1.8"]="mgf1",t.mgf1="1.2.840.113549.1.1.8",t["1.2.840.113549.1.1.9"]="pSpecified",t.pSpecified="1.2.840.113549.1.1.9",t["1.2.840.113549.1.1.10"]="RSASSA-PSS",t["RSASSA-PSS"]="1.2.840.113549.1.1.10",t["1.2.840.113549.1.1.11"]="sha256WithRSAEncryption",t.sha256WithRSAEncryption="1.2.840.113549.1.1.11",t["1.2.840.113549.1.1.12"]="sha384WithRSAEncryption",t.sha384WithRSAEncryption="1.2.840.113549.1.1.12",t["1.2.840.113549.1.1.13"]="sha512WithRSAEncryption",t.sha512WithRSAEncryption="1.2.840.113549.1.1.13",t["1.3.14.3.2.26"]="sha1",t.sha1="1.3.14.3.2.26",t["2.16.840.1.101.3.4.2.1"]="sha256",t.sha256="2.16.840.1.101.3.4.2.1",t["2.16.840.1.101.3.4.2.2"]="sha384",t.sha384="2.16.840.1.101.3.4.2.2",t["2.16.840.1.101.3.4.2.3"]="sha512",t.sha512="2.16.840.1.101.3.4.2.3",t["1.2.840.113549.2.5"]="md5",t.md5="1.2.840.113549.2.5",t["1.2.840.113549.1.7.1"]="data",t.data="1.2.840.113549.1.7.1",t["1.2.840.113549.1.7.2"]="signedData",t.signedData="1.2.840.113549.1.7.2",t["1.2.840.113549.1.7.3"]="envelopedData",t.envelopedData="1.2.840.113549.1.7.3",t["1.2.840.113549.1.7.4"]="signedAndEnvelopedData",t.signedAndEnvelopedData="1.2.840.113549.1.7.4",t["1.2.840.113549.1.7.5"]="digestedData",t.digestedData="1.2.840.113549.1.7.5",t["1.2.840.113549.1.7.6"]="encryptedData",t.encryptedData="1.2.840.113549.1.7.6",t["1.2.840.113549.1.9.1"]="emailAddress",t.emailAddress="1.2.840.113549.1.9.1",t["1.2.840.113549.1.9.2"]="unstructuredName",t.unstructuredName="1.2.840.113549.1.9.2",t["1.2.840.113549.1.9.3"]="contentType",t.contentType="1.2.840.113549.1.9.3",t["1.2.840.113549.1.9.4"]="messageDigest",t.messageDigest="1.2.840.113549.1.9.4",t["1.2.840.113549.1.9.5"]="signingTime",t.signingTime="1.2.840.113549.1.9.5",t["1.2.840.113549.1.9.6"]="counterSignature",t.counterSignature="1.2.840.113549.1.9.6",t["1.2.840.113549.1.9.7"]="challengePassword",t.challengePassword="1.2.840.113549.1.9.7",t["1.2.840.113549.1.9.8"]="unstructuredAddress",t.unstructuredAddress="1.2.840.113549.1.9.8",t["1.2.840.113549.1.9.20"]="friendlyName",t.friendlyName="1.2.840.113549.1.9.20",t["1.2.840.113549.1.9.21"]="localKeyId",t.localKeyId="1.2.840.113549.1.9.21",t["1.2.840.113549.1.9.22.1"]="x509Certificate",t.x509Certificate="1.2.840.113549.1.9.22.1",t["1.2.840.113549.1.12.10.1.1"]="keyBag",t.keyBag="1.2.840.113549.1.12.10.1.1",t["1.2.840.113549.1.12.10.1.2"]="pkcs8ShroudedKeyBag",t.pkcs8ShroudedKeyBag="1.2.840.113549.1.12.10.1.2",t["1.2.840.113549.1.12.10.1.3"]="certBag",t.certBag="1.2.840.113549.1.12.10.1.3",t["1.2.840.113549.1.12.10.1.4"]="crlBag",t.crlBag="1.2.840.113549.1.12.10.1.4",t["1.2.840.113549.1.12.10.1.5"]="secretBag",t.secretBag="1.2.840.113549.1.12.10.1.5",t["1.2.840.113549.1.12.10.1.6"]="safeContentsBag",t.safeContentsBag="1.2.840.113549.1.12.10.1.6",t["1.2.840.113549.1.5.13"]="pkcs5PBES2",t.pkcs5PBES2="1.2.840.113549.1.5.13",t["1.2.840.113549.1.5.12"]="pkcs5PBKDF2",t.pkcs5PBKDF2="1.2.840.113549.1.5.12",t["1.2.840.113549.1.12.1.1"]="pbeWithSHAAnd128BitRC4",t.pbeWithSHAAnd128BitRC4="1.2.840.113549.1.12.1.1",t["1.2.840.113549.1.12.1.2"]="pbeWithSHAAnd40BitRC4",t.pbeWithSHAAnd40BitRC4="1.2.840.113549.1.12.1.2",t["1.2.840.113549.1.12.1.3"]="pbeWithSHAAnd3-KeyTripleDES-CBC",t["pbeWithSHAAnd3-KeyTripleDES-CBC"]="1.2.840.113549.1.12.1.3",t["1.2.840.113549.1.12.1.4"]="pbeWithSHAAnd2-KeyTripleDES-CBC",t["pbeWithSHAAnd2-KeyTripleDES-CBC"]="1.2.840.113549.1.12.1.4",t["1.2.840.113549.1.12.1.5"]="pbeWithSHAAnd128BitRC2-CBC",t["pbeWithSHAAnd128BitRC2-CBC"]="1.2.840.113549.1.12.1.5",t["1.2.840.113549.1.12.1.6"]="pbewithSHAAnd40BitRC2-CBC",t["pbewithSHAAnd40BitRC2-CBC"]="1.2.840.113549.1.12.1.6",t["1.2.840.113549.3.7"]="des-EDE3-CBC",t["des-EDE3-CBC"]="1.2.840.113549.3.7",t["2.16.840.1.101.3.4.1.2"]="aes128-CBC",t["aes128-CBC"]="2.16.840.1.101.3.4.1.2",t["2.16.840.1.101.3.4.1.22"]="aes192-CBC",t["aes192-CBC"]="2.16.840.1.101.3.4.1.22",t["2.16.840.1.101.3.4.1.42"]="aes256-CBC",t["aes256-CBC"]="2.16.840.1.101.3.4.1.42",t["2.5.4.3"]="commonName",t.commonName="2.5.4.3",t["2.5.4.5"]="serialName",t.serialName="2.5.4.5",t["2.5.4.6"]="countryName",t.countryName="2.5.4.6",t["2.5.4.7"]="localityName",t.localityName="2.5.4.7",t["2.5.4.8"]="stateOrProvinceName",t.stateOrProvinceName="2.5.4.8",t["2.5.4.10"]="organizationName",t.organizationName="2.5.4.10",t["2.5.4.11"]="organizationalUnitName",t.organizationalUnitName="2.5.4.11",t["2.16.840.1.113730.1.1"]="nsCertType",t.nsCertType="2.16.840.1.113730.1.1",t["2.5.29.1"]="authorityKeyIdentifier",t["2.5.29.2"]="keyAttributes",t["2.5.29.3"]="certificatePolicies",t["2.5.29.4"]="keyUsageRestriction",t["2.5.29.5"]="policyMapping",t["2.5.29.6"]="subtreesConstraint",t["2.5.29.7"]="subjectAltName",t["2.5.29.8"]="issuerAltName",t["2.5.29.9"]="subjectDirectoryAttributes",t["2.5.29.10"]="basicConstraints",t["2.5.29.11"]="nameConstraints",t["2.5.29.12"]="policyConstraints",t["2.5.29.13"]="basicConstraints",t["2.5.29.14"]="subjectKeyIdentifier",t.subjectKeyIdentifier="2.5.29.14",t["2.5.29.15"]="keyUsage",t.keyUsage="2.5.29.15",t["2.5.29.16"]="privateKeyUsagePeriod",t["2.5.29.17"]="subjectAltName",t.subjectAltName="2.5.29.17",t["2.5.29.18"]="issuerAltName",t.issuerAltName="2.5.29.18",t["2.5.29.19"]="basicConstraints",t.basicConstraints="2.5.29.19",t["2.5.29.20"]="cRLNumber",t["2.5.29.21"]="cRLReason",t["2.5.29.22"]="expirationDate",t["2.5.29.23"]="instructionCode",t["2.5.29.24"]="invalidityDate",t["2.5.29.25"]="cRLDistributionPoints",t["2.5.29.26"]="issuingDistributionPoint",t["2.5.29.27"]="deltaCRLIndicator",t["2.5.29.28"]="issuingDistributionPoint",t["2.5.29.29"]="certificateIssuer",t["2.5.29.30"]="nameConstraints",t["2.5.29.31"]="cRLDistributionPoints",t["2.5.29.32"]="certificatePolicies",t["2.5.29.33"]="policyMappings",t["2.5.29.34"]="policyConstraints",t["2.5.29.35"]="authorityKeyIdentifier",t["2.5.29.36"]="policyConstraints",t["2.5.29.37"]="extKeyUsage",t.extKeyUsage="2.5.29.37",t["2.5.29.46"]="freshestCRL",t["2.5.29.54"]="inhibitAnyPolicy",t["1.3.6.1.5.5.7.3.1"]="serverAuth",t.serverAuth="1.3.6.1.5.5.7.3.1",t["1.3.6.1.5.5.7.3.2"]="clientAuth",t.clientAuth="1.3.6.1.5.5.7.3.2",t["1.3.6.1.5.5.7.3.3"]="codeSigning",t.codeSigning="1.3.6.1.5.5.7.3.3",t["1.3.6.1.5.5.7.3.4"]="emailProtection",t.emailProtection="1.3.6.1.5.5.7.3.4",t["1.3.6.1.5.5.7.3.8"]="timeStamping",t.timeStamping="1.3.6.1.5.5.7.3.8"}var t="oids";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/oids",["require","module"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){var t=e.asn1=e.asn1||{};t.Class={UNIVERSAL:0,APPLICATION:64,CONTEXT_SPECIFIC:128,PRIVATE:192},t.Type={NONE:0,BOOLEAN:1,INTEGER:2,BITSTRING:3,OCTETSTRING:4,NULL:5,OID:6,ODESC:7,EXTERNAL:8,REAL:9,ENUMERATED:10,EMBEDDED:11,UTF8:12,ROID:13,SEQUENCE:16,SET:17,PRINTABLESTRING:19,IA5STRING:22,UTCTIME:23,GENERALIZEDTIME:24,BMPSTRING:30},t.create=function(t,n,r,i){if(e.util.isArray(i)){var s=[];for(var o=0;o<i.length;++o)i[o]!==undefined&&s.push(i[o]);i=s}return{tagClass:t,type:n,constructed:r,composed:r||e.util.isArray(i),value:i}};var n=function(e){var t=e.getByte();if(t===128)return undefined;var n,r=t&128;return r?n=e.getInt((t&127)<<3):n=t,n};t.fromDer=function(r,i){i===undefined&&(i=!0),typeof r=="string"&&(r=e.util.createBuffer(r));if(r.length()<2)throw{message:"Too few bytes to parse DER.",bytes:r.length()};var s=r.getByte(),o=s&192,u=s&31,a=n(r);if(r.length()<a){if(i)throw{message:"Too few bytes to read ASN.1 value.",detail:r.length()+" < "+a};a=r.length()}var f,l=(s&32)===32,c=l;if(!c&&o===t.Class.UNIVERSAL&&u===t.Type.BITSTRING&&a>1){var h=r.read,p=r.getByte();if(p===0){s=r.getByte();var d=s&192;if(d===t.Class.UNIVERSAL||d===t.Class.CONTEXT_SPECIFIC)try{var v=n(r);c=v===a-(r.read-h),c&&(++h,--a)}catch(m){}}r.read=h}if(c){f=[];if(a===undefined)for(;;){if(r.bytes(2)===String.fromCharCode(0,0)){r.getBytes(2);break}f.push(t.fromDer(r,i))}else{var g=r.length();while(a>0)f.push(t.fromDer(r,i)),a-=g-r.length(),g=r.length()}}else{if(a===undefined)throw{message:"Non-constructed ASN.1 object of indefinite length."};if(u===t.Type.BMPSTRING){f="";for(var y=0;y<a;y+=2)f+=String.fromCharCode(r.getInt16())}else f=r.getBytes(a)}return t.create(o,u,l,f)},t.toDer=function(n){var r=e.util.createBuffer(),i=n.tagClass|n.type,s=e.util.createBuffer();if(n.composed){n.constructed?i|=32:s.putByte(0);for(var o=0;o<n.value.length;++o)n.value[o]!==undefined&&s.putBuffer(t.toDer(n.value[o]))}else if(n.type===t.Type.BMPSTRING)for(var o=0;o<n.value.length;++o)s.putInt16(n.value.charCodeAt(o));else s.putBytes(n.value);r.putByte(i);if(s.length()<=127)r.putByte(s.length()&127);else{var u=s.length(),a="";do a+=String.fromCharCode(u&255),u>>>=8;while(u>0);r.putByte(a.length|128);for(var o=a.length-1;o>=0;--o)r.putByte(a.charCodeAt(o))}return r.putBuffer(s),r},t.oidToDer=function(t){var n=t.split("."),r=e.util.createBuffer();r.putByte(40*parseInt(n[0],10)+parseInt(n[1],10));var i,s,o,u;for(var a=2;a<n.length;++a){i=!0,s=[],o=parseInt(n[a],10);do u=o&127,o>>>=7,i||(u|=128),s.push(u),i=!1;while(o>0);for(var f=s.length-1;f>=0;--f)r.putByte(s[f])}return r},t.derToOid=function(t){var n;typeof t=="string"&&(t=e.util.createBuffer(t));var r=t.getByte();n=Math.floor(r/40)+"."+r%40;var i=0;while(t.length()>0)r=t.getByte(),i<<=7,r&128?i+=r&127:(n+="."+(i+r),i=0);return n},t.utcTimeToDate=function(e){var t=new Date,n=parseInt(e.substr(0,2),10);n=n>=50?1900+n:2e3+n;var r=parseInt(e.substr(2,2),10)-1,i=parseInt(e.substr(4,2),10),s=parseInt(e.substr(6,2),10),o=parseInt(e.substr(8,2),10),u=0;if(e.length>11){var a=e.charAt(10),f=10;a!=="+"&&a!=="-"&&(u=parseInt(e.substr(10,2),10),f+=2)}t.setUTCFullYear(n,r,i),t.setUTCHours(s,o,u,0);if(f){a=e.charAt(f);if(a==="+"||a==="-"){var l=parseInt(e.substr(f+1,2),10),c=parseInt(e.substr(f+4,2),10),h=l*60+c;h*=6e4,a==="+"?t.setTime(+t-h):t.setTime(+t+h)}}return t},t.generalizedTimeToDate=function(e){var t=new Date,n=parseInt(e.substr(0,4),10),r=parseInt(e.substr(4,2),10)-1,i=parseInt(e.substr(6,2),10),s=parseInt(e.substr(8,2),10),o=parseInt(e.substr(10,2),10),u=parseInt(e.substr(12,2),10),a=0,f=0,l=!1;e.charAt(e.length-1)==="Z"&&(l=!0);var c=e.length-5,h=e.charAt(c);if(h==="+"||h==="-"){var p=parseInt(e.substr(c+1,2),10),d=parseInt(e.substr(c+4,2),10);f=p*60+d,f*=6e4,h==="+"&&(f*=-1),l=!0}return e.charAt(14)==="."&&(a=parseFloat(e.substr(14),10)*1e3),l?(t.setUTCFullYear(n,r,i),t.setUTCHours(s,o,u,a),t.setTime(+t+f)):(t.setFullYear(n,r,i),t.setHours(s,o,u,a)),t},t.dateToUtcTime=function(e){var t="",n=[];n.push((""+e.getUTCFullYear()).substr(2)),n.push(""+(e.getUTCMonth()+1)),n.push(""+e.getUTCDate()),n.push(""+e.getUTCHours()),n.push(""+e.getUTCMinutes()),n.push(""+e.getUTCSeconds());for(var r=0;r<n.length;++r)n[r].length<2&&(t+="0"),t+=n[r];return t+="Z",t},t.validate=function(n,r,i,s){var o=!1;if(n.tagClass!==r.tagClass&&typeof r.tagClass!="undefined"||n.type!==r.type&&typeof r.type!="undefined")s&&(n.tagClass!==r.tagClass&&s.push("["+r.name+"] "+'Expected tag class "'+r.tagClass+'", got "'+n.tagClass+'"'),n.type!==r.type&&s.push("["+r.name+"] "+'Expected type "'+r.type+'", got "'+n.type+'"'));else if(n.constructed===r.constructed||typeof r.constructed=="undefined"){o=!0;if(r.value&&e.util.isArray(r.value)){var u=0;for(var a=0;o&&a<r.value.length;++a)o=r.value[a].optional||!1,n.value[u]&&(o=t.validate(n.value[u],r.value[a],i,s),o?++u:r.value[a].optional&&(o=!0)),!o&&s&&s.push("["+r.name+"] "+'Tag class "'+r.tagClass+'", type "'+r.type+'" expected value length "'+r.value.length+'", got "'+n.value.length+'"')}o&&i&&(r.capture&&(i[r.capture]=n.value),r.captureAsn1&&(i[r.captureAsn1]=n))}else s&&s.push("["+r.name+"] "+'Expected constructed "'+r.constructed+'", got "'+n.constructed+'"');return o};var r=/[^\\u0000-\\u00ff]/;t.prettyPrint=function(n,i,s){var o="";i=i||0,s=s||2,i>0&&(o+="\n");var u="";for(var a=0;a<i*s;++a)u+=" ";o+=u+"Tag: ";switch(n.tagClass){case t.Class.UNIVERSAL:o+="Universal:";break;case t.Class.APPLICATION:o+="Application:";break;case t.Class.CONTEXT_SPECIFIC:o+="Context-Specific:";break;case t.Class.PRIVATE:o+="Private:"}if(n.tagClass===t.Class.UNIVERSAL){o+=n.type;switch(n.type){case t.Type.NONE:o+=" (None)";break;case t.Type.BOOLEAN:o+=" (Boolean)";break;case t.Type.BITSTRING:o+=" (Bit string)";break;case t.Type.INTEGER:o+=" (Integer)";break;case t.Type.OCTETSTRING:o+=" (Octet string)";break;case t.Type.NULL:o+=" (Null)";break;case t.Type.OID:o+=" (Object Identifier)";break;case t.Type.ODESC:o+=" (Object Descriptor)";break;case t.Type.EXTERNAL:o+=" (External or Instance of)";break;case t.Type.REAL:o+=" (Real)";break;case t.Type.ENUMERATED:o+=" (Enumerated)";break;case t.Type.EMBEDDED:o+=" (Embedded PDV)";break;case t.Type.UTF8:o+=" (UTF8)";break;case t.Type.ROID:o+=" (Relative Object Identifier)";break;case t.Type.SEQUENCE:o+=" (Sequence)";break;case t.Type.SET:o+=" (Set)";break;case t.Type.PRINTABLESTRING:o+=" (Printable String)";break;case t.Type.IA5String:o+=" (IA5String (ASCII))";break;case t.Type.UTCTIME:o+=" (UTC time)";break;case t.Type.GENERALIZEDTIME:o+=" (Generalized time)";break;case t.Type.BMPSTRING:o+=" (BMP String)"}}else o+=n.type;o+="\n",o+=u+"Constructed: "+n.constructed+"\n";if(n.composed){var f=0,l="";for(var a=0;a<n.value.length;++a)n.value[a]!==undefined&&(f+=1,l+=t.prettyPrint(n.value[a],i+1,s),a+1<n.value.length&&(l+=","));o+=u+"Sub values: "+f+l}else{o+=u+"Value: ";if(n.type===t.Type.OID){var c=t.derToOid(n.value);o+=c,e.pki&&e.pki.oids&&c in e.pki.oids&&(o+=" ("+e.pki.oids[c]+")")}else r.test(n.value)?o+="0x"+e.util.createBuffer(n.value,"utf8").toHex():n.value.length===0?o+="[null]":o+=n.value}return o}}var t="asn1";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/asn1",["require","module","./util","./oids"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e,t,n){describe("asn1",function(){it("should convert an OID to DER",function(){e.equal(t.oidToDer("1.2.840.113549").toHex(),"2a864886f70d")}),it("should convert an OID from DER",function(){var r=n.hexToBytes("2a864886f70d");e.equal(t.derToOid(r),"1.2.840.113549")}),function(){var n=[{"in":"20110223123400",out:129846444e4},{"in":"20110223123400.1",out:1298464440100},{"in":"20110223123400.123",out:1298464440123}];for(var r=0;r<n.length;++r){var i=n[r];it('should convert local generalized time "'+i.in+'" to a Date',function(){var n=t.generalizedTimeToDate(i.in),r=n.getTimezoneOffset()*6e4;e.equal(n.getTime(),i.out+r)})}}(),function(){var n=[{"in":"20110223123400Z",out:129846444e4},{"in":"20110223123400.1Z",out:1298464440100},{"in":"20110223123400.123Z",out:1298464440123},{"in":"20110223123400+0200",out:129845724e4},{"in":"20110223123400.1+0200",out:1298457240100},{"in":"20110223123400.123+0200",out:1298457240123},{"in":"20110223123400-0200",out:129847164e4},{"in":"20110223123400.1-0200",out:1298471640100},{"in":"20110223123400.123-0200",out:1298471640123}];for(var r=0;r<n.length;++r){var i=n[r];it('should convert utc generalized time "'+i.in+'" to a Date',function(){var n=t.generalizedTimeToDate(i.in);e.equal(n.getTime(),i.out)})}}(),function(){var n=[{"in":"1102231234Z",out:129846444e4},{"in":"1102231234+0200",out:129845724e4},{"in":"1102231234-0200",out:129847164e4},{"in":"110223123456Z",out:1298464496e3},{"in":"110223123456+0200",out:1298457296e3},{"in":"110223123456-0200",out:1298471696e3}];for(var r=0;r<n.length;++r){var i=n[r];it('should convert utc time "'+i.in+'" to a Date',function(){var n=t.utcTimeToDate(i.in);e.equal(n.getTime(),i.out)})}}()})}typeof define=="function"?define("test/asn1",["forge/asn1","forge/util"],function(t,n){e(ASSERT,t(),n())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/asn1")(),require("../../js/util")())}(),function(){function e(e){function n(e){var t=e.name+": ",n=[];for(var r=0;r<e.values.length;++r)n.push(e.values[r].replace(/^(\S+\r\n)/,function(e,t){return" "+t}));t+=n.join(",")+"\r\n";var i=0,s=-1;for(var r=0;r<t.length;++r,++i)if(i>65&&s!==-1){var o=t[s];o===","?(++s,t=t.substr(0,s)+"\r\n "+t.substr(s)):t=t.substr(0,s)+"\r\n"+o+t.substr(s+1),i=r-s-1,s=-1,++r}else if(t[r]===" "||t[r]==="	"||t[r]===",")s=r;return t}function r(e){return e.replace(/^\s+/,"")}var t=e.pem=e.pem||{};t.encode=function(t,r){r=r||{};var i="-----BEGIN "+t.type+"-----\r\n",s;t.procType&&(s={name:"Proc-Type",values:[String(t.procType.version),t.procType.type]},i+=n(s)),t.contentDomain&&(s={name:"Content-Domain",values:[t.contentDomain]},i+=n(s)),t.dekInfo&&(s={name:"DEK-Info",values:[t.dekInfo.algorithm]},t.dekInfo.parameters&&s.values.push(t.dekInfo.parameters),i+=n(s));if(t.headers)for(var o=0;o<t.headers.length;++o)i+=n(t.headers[o]);return t.procType&&(i+="\r\n"),i+=e.util.encode64(t.body,r.maxline||64)+"\r\n",i+="-----END "+t.type+"-----\r\n",i},t.decode=function(t){var n=[],i=/\s*-----BEGIN ([A-Z0-9- ]+)-----\r?\n([\x21-\x7e\s]+?(?:\r?\n\r?\n))?([:A-Za-z0-9+\/=\s]+?)-----END \1-----/g,s=/([\x21-\x7e]+):\s*([\x21-\x7e\s^:]+)/,o=/\r?\n/,u;for(;;){u=i.exec(t);if(!u)break;var a={type:u[1],procType:null,contentDomain:null,dekInfo:null,headers:[],body:e.util.decode64(u[3])};n.push(a);if(!u[2])continue;var f=u[2].split(o),l=0;while(u&&l<f.length){var c=f[l].replace(/\s+$/,"");for(var h=l+1;h<f.length;++h){var p=f[h];if(!/\s/.test(p[0]))break;c+=p,l=h}u=c.match(s);if(u){var d={name:u[1],values:[]},v=u[2].split(",");for(var m=0;m<v.length;++m)d.values.push(r(v[m]));if(!a.procType){if(d.name!=="Proc-Type")throw{message:'Invalid PEM formatted message. The first encapsulated header must be "Proc-Type".'};if(d.values.length!==2)throw{message:'Invalid PEM formatted message. The "Proc-Type" header must have two subfields.'};a.procType={version:v[0],type:v[1]}}else if(!a.contentDomain&&d.name==="Content-Domain")a.contentDomain=v[0]||"";else if(!a.dekInfo&&d.name==="DEK-Info"){if(d.values.length===0)throw{message:'Invalid PEM formatted message. The "DEK-Info" header must have at least one subfield.'};a.dekInfo={algorithm:v[0],parameters:v[1]||null}}else a.headers.push(d)}++l}if(a.procType==="ENCRYPTED"&&!a.dekInfo)throw{message:'Invalid PEM formatted message. The "DEK-Info" header must be present if "Proc-Type" is "ENCRYPTED".'}}if(n.length===0)throw{message:"Invalid PEM formatted message."};return n}}var t="pem";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/pem",["require","module","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e,t){var n="-----BEGIN PRIVACY-ENHANCED MESSAGE-----\r\nProc-Type: 4,ENCRYPTED\r\nContent-Domain: RFC822\r\nDEK-Info: DES-CBC,F8143EDE5960C597\r\nOriginator-ID-Symmetric: linn@zendia.enet.dec.com,,\r\nRecipient-ID-Symmetric: linn@zendia.enet.dec.com,ptf-kmc,3\r\nKey-Info: DES-ECB,RSA-MD2,9FD3AAD2F2691B9A,\r\n B70665BB9BF7CBCDA60195DB94F727D3\r\nRecipient-ID-Symmetric: pem-dev@tis.com,ptf-kmc,4\r\nKey-Info: DES-ECB,RSA-MD2,161A3F75DC82EF26,\r\n E2EF532C65CBCFF79F83A2658132DB47\r\n\r\nLLrHB0eJzyhP+/fSStdW8okeEnv47jxe7SJ/iN72ohNcUk2jHEUSoH1nvNSIWL9M\r\n8tEjmF/zxB+bATMtPjCUWbz8Lr9wloXIkjHUlBLpvXR0UrUzYbkNpk0agV2IzUpk\r\nJ6UiRRGcDSvzrsoK+oNvqu6z7Xs5Xfz5rDqUcMlK1Z6720dcBWGGsDLpTpSCnpot\r\ndXd/H5LMDWnonNvPCwQUHg==\r\n-----END PRIVACY-ENHANCED MESSAGE-----\r\n-----BEGIN PRIVACY-ENHANCED MESSAGE-----\r\nProc-Type: 4,ENCRYPTED\r\nContent-Domain: RFC822\r\nDEK-Info: DES-CBC,BFF968AA74691AC1\r\nOriginator-Certificate:\r\n MIIBlTCCAScCAWUwDQYJKoZIhvcNAQECBQAwUTELMAkGA1UEBhMCVVMxIDAeBgNV\r\n BAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMQ8wDQYDVQQLEwZCZXRhIDExDzAN\r\n BgNVBAsTBk5PVEFSWTAeFw05MTA5MDQxODM4MTdaFw05MzA5MDMxODM4MTZaMEUx\r\n CzAJBgNVBAYTAlVTMSAwHgYDVQQKExdSU0EgRGF0YSBTZWN1cml0eSwgSW5jLjEU\r\n MBIGA1UEAxMLVGVzdCBVc2VyIDEwWTAKBgRVCAEBAgICAANLADBIAkEAwHZHl7i+\r\n yJcqDtjJCowzTdBJrdAiLAnSC+CnnjOJELyuQiBgkGrgIh3j8/x0fM+YrsyF1u3F\r\n LZPVtzlndhYFJQIDAQABMA0GCSqGSIb3DQEBAgUAA1kACKr0PqphJYw1j+YPtcIq\r\n iWlFPuN5jJ79Khfg7ASFxskYkEMjRNZV/HZDZQEhtVaU7Jxfzs2wfX5byMp2X3U/\r\n 5XUXGx7qusDgHQGs7Jk9W8CW1fuSWUgN4w==\r\nKey-Info: RSA,\r\n I3rRIGXUGWAF8js5wCzRTkdhO34PTHdRZY9Tuvm03M+NM7fx6qc5udixps2Lng0+\r\n wGrtiUm/ovtKdinz6ZQ/aQ==\r\nIssuer-Certificate:\r\n MIIB3DCCAUgCAQowDQYJKoZIhvcNAQECBQAwTzELMAkGA1UEBhMCVVMxIDAeBgNV\r\n BAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMQ8wDQYDVQQLEwZCZXRhIDExDTAL\r\n BgNVBAsTBFRMQ0EwHhcNOTEwOTAxMDgwMDAwWhcNOTIwOTAxMDc1OTU5WjBRMQsw\r\n CQYDVQQGEwJVUzEgMB4GA1UEChMXUlNBIERhdGEgU2VjdXJpdHksIEluYy4xDzAN\r\n BgNVBAsTBkJldGEgMTEPMA0GA1UECxMGTk9UQVJZMHAwCgYEVQgBAQICArwDYgAw\r\n XwJYCsnp6lQCxYykNlODwutF/jMJ3kL+3PjYyHOwk+/9rLg6X65B/LD4bJHtO5XW\r\n cqAz/7R7XhjYCm0PcqbdzoACZtIlETrKrcJiDYoP+DkZ8k1gCk7hQHpbIwIDAQAB\r\n MA0GCSqGSIb3DQEBAgUAA38AAICPv4f9Gx/tY4+p+4DB7MV+tKZnvBoy8zgoMGOx\r\n dD2jMZ/3HsyWKWgSF0eH/AJB3qr9zosG47pyMnTf3aSy2nBO7CMxpUWRBcXUpE+x\r\n EREZd9++32ofGBIXaialnOgVUn0OzSYgugiQ077nJLDUj0hQehCizEs5wUJ35a5h\r\nMIC-Info: RSA-MD5,RSA,\r\n UdFJR8u/TIGhfH65ieewe2lOW4tooa3vZCvVNGBZirf/7nrgzWDABz8w9NsXSexv\r\n AjRFbHoNPzBuxwmOAFeA0HJszL4yBvhG\r\nRecipient-ID-Asymmetric:\r\n MFExCzAJBgNVBAYTAlVTMSAwHgYDVQQKExdSU0EgRGF0YSBTZWN1cml0eSwgSW5j\r\n LjEPMA0GA1UECxMGQmV0YSAxMQ8wDQYDVQQLEwZOT1RBUlk=,66\r\nKey-Info: RSA,\r\n O6BS1ww9CTyHPtS3bMLD+L0hejdvX6Qv1HK2ds2sQPEaXhX8EhvVphHYTjwekdWv\r\n 7x0Z3Jx2vTAhOYHMcqqCjA==\r\n\r\nqeWlj/YJ2Uf5ng9yznPbtD0mYloSwIuV9FRYx+gzY+8iXd/NQrXHfi6/MhPfPF3d\r\njIqCJAxvld2xgqQimUzoS1a4r7kQQ5c/Iua4LqKeq3ciFzEv/MbZhA==\r\n-----END PRIVACY-ENHANCED MESSAGE-----\r\n-----BEGIN RSA PRIVATE KEY-----\r\nMIIBPAIBAAJBALjXU+IdHkSkdBscgXf+EBoa55ruAIsU50uDFjFBkp+rWFt5AOGF\r\n9xL1/HNIby5M64BCw021nJTZKEOmXKdmzYsCAwEAAQJBAApyYRNOgf9vLAC8Q7T8\r\nbvyKuLxQ50b1D319EywFgLv1Yn0s/F9F+Rew6c04Q0pIqmuOGUM7z94ul/y5OlNJ\r\n2cECIQDveEW1ib2+787l7Y0tMeDzf/HQl4MAWdcxXWOeUFK+7QIhAMWZsukutEn9\r\n9/yqFMt8bL/dclfNn1IAgUL4+dMJ7zdXAiEAhaxGhVKxN28XuCOFhe/s2R/XdQ/O\r\nUZjU1bqCzDGcLvUCIGYmxu71Tg7SVFkyM/3eHPozKOFrU2m5CRnuTHhlMl2RAiEA\r\n0vhM5TEmmNWz0anPVabqDj9TA0z5MsDJQcn5NmO9xnw=\r\n-----END RSA PRIVATE KEY-----\r\n";describe("pem",function(){it("should decode and re-encode PEM messages",function(){var r=t.decode(n),i="";for(var s=0;s<r.length;++s)i+=t.encode(r[s]);e.equal(i,n)})})}typeof define=="function"?define("test/pem",["forge/pem"],function(t){e(ASSERT,t())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/pem")())}(),function(){function e(e){function f(e){var t=[0,4,536870912,536870916,65536,65540,536936448,536936452,512,516,536871424,536871428,66048,66052,536936960,536936964],n=[0,1,1048576,1048577,67108864,67108865,68157440,68157441,256,257,1048832,1048833,67109120,67109121,68157696,68157697],r=[0,8,2048,2056,16777216,16777224,16779264,16779272,0,8,2048,2056,16777216,16777224,16779264,16779272],i=[0,2097152,134217728,136314880,8192,2105344,134225920,136323072,131072,2228224,134348800,136445952,139264,2236416,134356992,136454144],s=[0,262144,16,262160,0,262144,16,262160,4096,266240,4112,266256,4096,266240,4112,266256],o=[0,1024,32,1056,0,1024,32,1056,33554432,33555456,33554464,33555488,33554432,33555456,33554464,33555488],u=[0,268435456,524288,268959744,2,268435458,524290,268959746,0,268435456,524288,268959744,2,268435458,524290,268959746],a=[0,65536,2048,67584,536870912,536936448,536872960,536938496,131072,196608,133120,198656,537001984,537067520,537004032,537069568],f=[0,262144,0,262144,2,262146,2,262146,33554432,33816576,33554432,33816576,33554434,33816578,33554434,33816578],l=[0,268435456,8,268435464,0,268435456,8,268435464,1024,268436480,1032,268436488,1024,268436480,1032,268436488],c=[0,32,0,32,1048576,1048608,1048576,1048608,8192,8224,8192,8224,1056768,1056800,1056768,1056800],h=[0,16777216,512,16777728,2097152,18874368,2097664,18874880,67108864,83886080,67109376,83886592,69206016,85983232,69206528,85983744],p=[0,4096,134217728,134221824,524288,528384,134742016,134746112,16,4112,134217744,134221840,524304,528400,134742032,134746128],d=[0,4,256,260,0,4,256,260,1,5,257,261,1,5,257,261],v=e.length()>8?3:1,m=[],g=[0,0,1,1,1,1,1,1,0,1,1,1,1,1,1,0],y=0,b;for(var w=0;w<v;w++){var E=e.getInt32(),S=e.getInt32();b=(E>>>4^S)&252645135,S^=b,E^=b<<4,b=(S>>>-16^E)&65535,E^=b,S^=b<<-16,b=(E>>>2^S)&858993459,S^=b,E^=b<<2,b=(S>>>-16^E)&65535,E^=b,S^=b<<-16,b=(E>>>1^S)&1431655765,S^=b,E^=b<<1,b=(S>>>8^E)&16711935,E^=b,S^=b<<8,b=(E>>>1^S)&1431655765,S^=b,E^=b<<1,b=E<<8|S>>>20&240,E=S<<24|S<<8&16711680|S>>>8&65280|S>>>24&240,S=b;for(var x=0;x<g.length;x++){g[x]?(E=E<<2|E>>>26,S=S<<2|S>>>26):(E=E<<1|E>>>27,S=S<<1|S>>>27),E&=-15,S&=-15;var T=t[E>>>28]|n[E>>>24&15]|r[E>>>20&15]|i[E>>>16&15]|s[E>>>12&15]|o[E>>>8&15]|u[E>>>4&15],N=a[S>>>28]|f[S>>>24&15]|l[S>>>20&15]|c[S>>>16&15]|h[S>>>12&15]|p[S>>>8&15]|d[S>>>4&15];b=(N>>>16^T)&65535,m[y++]=T^b,m[y++]=N^b<<16}}return m}var t=[16843776,0,65536,16843780,16842756,66564,4,65536,1024,16843776,16843780,1024,16778244,16842756,16777216,4,1028,16778240,16778240,66560,66560,16842752,16842752,16778244,65540,16777220,16777220,65540,0,1028,66564,16777216,65536,16843780,4,16842752,16843776,16777216,16777216,1024,16842756,65536,66560,16777220,1024,4,16778244,66564,16843780,65540,16842752,16778244,16777220,1028,66564,16843776,1028,16778240,16778240,0,65540,66560,0,16842756],n=[-2146402272,-2147450880,32768,1081376,1048576,32,-2146435040,-2147450848,-2147483616,-2146402272,-2146402304,-2147483648,-2147450880,1048576,32,-2146435040,1081344,1048608,-2147450848,0,-2147483648,32768,1081376,-2146435072,1048608,-2147483616,0,1081344,32800,-2146402304,-2146435072,32800,0,1081376,-2146435040,1048576,-2147450848,-2146435072,-2146402304,32768,-2146435072,-2147450880,32,-2146402272,1081376,32,32768,-2147483648,32800,-2146402304,1048576,-2147483616,1048608,-2147450848,-2147483616,1048608,1081344,0,-2147450880,32800,-2147483648,-2146435040,-2146402272,1081344],r=[520,134349312,0,134348808,134218240,0,131592,134218240,131080,134217736,134217736,131072,134349320,131080,134348800,520,134217728,8,134349312,512,131584,134348800,134348808,131592,134218248,131584,131072,134218248,8,134349320,512,134217728,134349312,134217728,131080,520,131072,134349312,134218240,0,512,131080,134349320,134218240,134217736,512,0,134348808,134218248,131072,134217728,134349320,8,131592,131584,134217736,134348800,134218248,520,134348800,131592,8,134348808,131584],i=[8396801,8321,8321,128,8396928,8388737,8388609,8193,0,8396800,8396800,8396929,129,0,8388736,8388609,1,8192,8388608,8396801,128,8388608,8193,8320,8388737,1,8320,8388736,8192,8396928,8396929,129,8388736,8388609,8396800,8396929,129,0,0,8396800,8320,8388736,8388737,1,8396801,8321,8321,128,8396929,129,1,8192,8388609,8193,8396928,8388737,8193,8320,8388608,8396801,128,8388608,8192,8396928],s=[256,34078976,34078720,1107296512,524288,256,1073741824,34078720,1074266368,524288,33554688,1074266368,1107296512,1107820544,524544,1073741824,33554432,1074266112,1074266112,0,1073742080,1107820800,1107820800,33554688,1107820544,1073742080,0,1107296256,34078976,33554432,1107296256,524544,524288,1107296512,256,33554432,1073741824,34078720,1107296512,1074266368,33554688,1073741824,1107820544,34078976,1074266368,256,33554432,1107820544,1107820800,524544,1107296256,1107820800,34078720,0,1074266112,1107296256,524544,33554688,1073742080,524288,0,1074266112,34078976,1073742080],o=[536870928,541065216,16384,541081616,541065216,16,541081616,4194304,536887296,4210704,4194304,536870928,4194320,536887296,536870912,16400,0,4194320,536887312,16384,4210688,536887312,16,541065232,541065232,0,4210704,541081600,16400,4210688,541081600,536870912,536887296,16,541065232,4210688,541081616,4194304,16400,536870928,4194304,536887296,536870912,16400,536870928,541081616,4210688,541065216,4210704,541081600,0,541065232,16,16384,541065216,4210704,16384,4194320,536887312,0,541081600,536870912,4194320,536887312],u=[2097152,69206018,67110914,0,2048,67110914,2099202,69208064,69208066,2097152,0,67108866,2,67108864,69206018,2050,67110912,2099202,2097154,67110912,67108866,69206016,69208064,2097154,69206016,2048,2050,69208066,2099200,2,67108864,2099200,67108864,2099200,2097152,67110914,67110914,69206018,69206018,2,2097154,67108864,67110912,2097152,69208064,2050,2099202,69208064,2050,67108866,69208066,69206016,2099200,0,2,69208066,0,2099202,69206016,2048,67108866,67110912,2048,2097154],a=[268439616,4096,262144,268701760,268435456,268439616,64,268435456,262208,268697600,268701760,266240,268701696,266304,4096,64,268697600,268435520,268439552,4160,266240,262208,268697664,268701696,4160,0,0,268697664,268435520,268439552,266304,262144,266304,262144,268701696,4096,64,268697664,4096,266304,268439552,64,268435520,268697600,268697664,268435456,262144,268439616,0,268701760,262208,268435520,268697600,268439552,268439616,0,268701760,266240,266240,4160,4160,262208,268435456,268701696],l=function(l,c){typeof l=="string"&&(l.length===8||l.length===24)&&(l=e.util.createBuffer(l));var h=f(l),p=1,d=0,v=0,m=0,g=0,y=!1,b=null,w=null,E=h.length===32?3:9,S;E===3?S=c?[0,32,2]:[30,-2,-2]:S=c?[0,32,2,62,30,-2,64,96,2]:[94,62,-2,32,64,2,30,-2,-2];var x=null;return x={start:function(t,n){t?(typeof t=="string"&&t.length===8&&(t=e.util.createBuffer(t)),p=1,d=t.getInt32(),m=t.getInt32()):p=0,y=!1,b=e.util.createBuffer(),w=n||e.util.createBuffer(),x.output=w},update:function(e){y||b.putBuffer(e);while(b.length()>=8){var f,l=b.getInt32(),x=b.getInt32();p===1&&(c?(l^=d,x^=m):(v=d,g=m,d=l,m=x)),f=(l>>>4^x)&252645135,x^=f,l^=f<<4,f=(l>>>16^x)&65535,x^=f,l^=f<<16,f=(x>>>2^l)&858993459,l^=f,x^=f<<2,f=(x>>>8^l)&16711935,l^=f,x^=f<<8,f=(l>>>1^x)&1431655765,x^=f,l^=f<<1,l=l<<1|l>>>31,x=x<<1|x>>>31;for(var T=0;T<E;T+=3){var N=S[T+1],C=S[T+2];for(var k=S[T];k!=N;k+=C){var L=x^h[k],A=(x>>>4|x<<28)^h[k+1];f=l,l=x,x=f^(n[L>>>24&63]|i[L>>>16&63]|o[L>>>8&63]|a[L&63]|t[A>>>24&63]|r[A>>>16&63]|s[A>>>8&63]|u[A&63])}f=l,l=x,x=f}l=l>>>1|l<<31,x=x>>>1|x<<31,f=(l>>>1^x)&1431655765,x^=f,l^=f<<1,f=(x>>>8^l)&16711935,l^=f,x^=f<<8,f=(x>>>2^l)&858993459,l^=f,x^=f<<2,f=(l>>>16^x)&65535,x^=f,l^=f<<16,f=(l>>>4^x)&252645135,x^=f,l^=f<<4,p===1&&(c?(d=l,m=x):(l^=v,x^=g)),w.putInt32(l),w.putInt32(x)}},finish:function(e){var t=!0;if(c)if(e)t=e(8,b,!c);else{var n=b.length()===8?8:8-b.length();b.fillWithByte(n,n)}t&&(y=!0,x.update());if(!c){t=b.length()===0;if(t)if(e)t=e(8,w,!c);else{var r=w.length(),i=w.at(r-1);i>r?t=!1:w.truncate(i)}}return t}},x};e.des=e.des||{},e.des.startEncrypting=function(e,t,n){var r=l(e,!0);return r.start(t,n),r},e.des.createEncryptionCipher=function(e){return l(e,!0)},e.des.startDecrypting=function(e,t,n){var r=l(e,!1);return r.start(t,n),r},e.des.createDecryptionCipher=function(e){return l(e,!1)}}var t="des";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/des",["require","module","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){function i(e,t,n){this.data=[],e!=null&&("number"==typeof e?this.fromNumber(e,t,n):t==null&&"string"!=typeof e?this.fromString(e,256):this.fromString(e,t))}function s(){return new i(null)}function o(e,t,n,r,i,s){while(--s>=0){var o=t*this.data[e++]+n.data[r]+i;i=Math.floor(o/67108864),n.data[r++]=o&67108863}return i}function u(e,t,n,r,i,s){var o=t&32767,u=t>>15;while(--s>=0){var a=this.data[e]&32767,f=this.data[e++]>>15,l=u*a+f*o;a=o*a+((l&32767)<<15)+n.data[r]+(i&1073741823),i=(a>>>30)+(l>>>15)+u*f+(i>>>30),n.data[r++]=a&1073741823}return i}function a(e,t,n,r,i,s){var o=t&16383,u=t>>14;while(--s>=0){var a=this.data[e]&16383,f=this.data[e++]>>14,l=u*a+f*o;a=o*a+((l&16383)<<14)+n.data[r]+i,i=(a>>28)+(l>>14)+u*f,n.data[r++]=a&268435455}return i}function d(e){return l.charAt(e)}function v(e,t){var n=c[e.charCodeAt(t)];return n==null?-1:n}function m(e){for(var t=this.t-1;t>=0;--t)e.data[t]=this.data[t];e.t=this.t,e.s=this.s}function g(e){this.t=1,this.s=e<0?-1:0,e>0?this.data[0]=e:e<-1?this.data[0]=e+DV:this.t=0}function y(e){var t=s();return t.fromInt(e),t}function b(e,t){var n;if(t==16)n=4;else if(t==8)n=3;else if(t==256)n=8;else if(t==2)n=1;else if(t==32)n=5;else{if(t!=4){this.fromRadix(e,t);return}n=2}this.t=0,this.s=0;var r=e.length,s=!1,o=0;while(--r>=0){var u=n==8?e[r]&255:v(e,r);if(u<0){e.charAt(r)=="-"&&(s=!0);continue}s=!1,o==0?this.data[this.t++]=u:o+n>this.DB?(this.data[this.t-1]|=(u&(1<<this.DB-o)-1)<<o,this.data[this.t++]=u>>this.DB-o):this.data[this.t-1]|=u<<o,o+=n,o>=this.DB&&(o-=this.DB)}n==8&&(e[0]&128)!=0&&(this.s=-1,o>0&&(this.data[this.t-1]|=(1<<this.DB-o)-1<<o)),this.clamp(),s&&i.ZERO.subTo(this,this)}function w(){var e=this.s&this.DM;while(this.t>0&&this.data[this.t-1]==e)--this.t}function E(e){if(this.s<0)return"-"+this.negate().toString(e);var t;if(e==16)t=4;else if(e==8)t=3;else if(e==2)t=1;else if(e==32)t=5;else{if(e!=4)return this.toRadix(e);t=2}var n=(1<<t)-1,r,i=!1,s="",o=this.t,u=this.DB-o*this.DB%t;if(o-->0){u<this.DB&&(r=this.data[o]>>u)>0&&(i=!0,s=d(r));while(o>=0)u<t?(r=(this.data[o]&(1<<u)-1)<<t-u,r|=this.data[--o]>>(u+=this.DB-t)):(r=this.data[o]>>(u-=t)&n,u<=0&&(u+=this.DB,--o)),r>0&&(i=!0),i&&(s+=d(r))}return i?s:"0"}function S(){var e=s();return i.ZERO.subTo(this,e),e}function x(){return this.s<0?this.negate():this}function T(e){var t=this.s-e.s;if(t!=0)return t;var n=this.t;t=n-e.t;if(t!=0)return this.s<0?-t:t;while(--n>=0)if((t=this.data[n]-e.data[n])!=0)return t;return 0}function N(e){var t=1,n;return(n=e>>>16)!=0&&(e=n,t+=16),(n=e>>8)!=0&&(e=n,t+=8),(n=e>>4)!=0&&(e=n,t+=4),(n=e>>2)!=0&&(e=n,t+=2),(n=e>>1)!=0&&(e=n,t+=1),t}function C(){return this.t<=0?0:this.DB*(this.t-1)+N(this.data[this.t-1]^this.s&this.DM)}function k(e,t){var n;for(n=this.t-1;n>=0;--n)t.data[n+e]=this.data[n];for(n=e-1;n>=0;--n)t.data[n]=0;t.t=this.t+e,t.s=this.s}function L(e,t){for(var n=e;n<this.t;++n)t.data[n-e]=this.data[n];t.t=Math.max(this.t-e,0),t.s=this.s}function A(e,t){var n=e%this.DB,r=this.DB-n,i=(1<<r)-1,s=Math.floor(e/this.DB),o=this.s<<n&this.DM,u;for(u=this.t-1;u>=0;--u)t.data[u+s+1]=this.data[u]>>r|o,o=(this.data[u]&i)<<n;for(u=s-1;u>=0;--u)t.data[u]=0;t.data[s]=o,t.t=this.t+s+1,t.s=this.s,t.clamp()}function O(e,t){t.s=this.s;var n=Math.floor(e/this.DB);if(n>=this.t){t.t=0;return}var r=e%this.DB,i=this.DB-r,s=(1<<r)-1;t.data[0]=this.data[n]>>r;for(var o=n+1;o<this.t;++o)t.data[o-n-1]|=(this.data[o]&s)<<i,t.data[o-n]=this.data[o]>>r;r>0&&(t.data[this.t-n-1]|=(this.s&s)<<i),t.t=this.t-n,t.clamp()}function M(e,t){var n=0,r=0,i=Math.min(e.t,this.t);while(n<i)r+=this.data[n]-e.data[n],t.data[n++]=r&this.DM,r>>=this.DB;if(e.t<this.t){r-=e.s;while(n<this.t)r+=this.data[n],t.data[n++]=r&this.DM,r>>=this.DB;r+=this.s}else{r+=this.s;while(n<e.t)r-=e.data[n],t.data[n++]=r&this.DM,r>>=this.DB;r-=e.s}t.s=r<0?-1:0,r<-1?t.data[n++]=this.DV+r:r>0&&(t.data[n++]=r),t.t=n,t.clamp()}function _(e,t){var n=this.abs(),r=e.abs(),s=n.t;t.t=s+r.t;while(--s>=0)t.data[s]=0;for(s=0;s<r.t;++s)t.data[s+n.t]=n.am(0,r.data[s],t,s,0,n.t);t.s=0,t.clamp(),this.s!=e.s&&i.ZERO.subTo(t,t)}function D(e){var t=this.abs(),n=e.t=2*t.t;while(--n>=0)e.data[n]=0;for(n=0;n<t.t-1;++n){var r=t.am(n,t.data[n],e,2*n,0,1);(e.data[n+t.t]+=t.am(n+1,2*t.data[n],e,2*n+1,r,t.t-n-1))>=t.DV&&(e.data[n+t.t]-=t.DV,e.data[n+t.t+1]=1)}e.t>0&&(e.data[e.t-1]+=t.am(n,t.data[n],e,2*n,0,1)),e.s=0,e.clamp()}function P(e,t,n){var r=e.abs();if(r.t<=0)return;var o=this.abs();if(o.t<r.t){t!=null&&t.fromInt(0),n!=null&&this.copyTo(n);return}n==null&&(n=s());var u=s(),a=this.s,f=e.s,l=this.DB-N(r.data[r.t-1]);l>0?(r.lShiftTo(l,u),o.lShiftTo(l,n)):(r.copyTo(u),o.copyTo(n));var c=u.t,h=u.data[c-1];if(h==0)return;var p=h*(1<<this.F1)+(c>1?u.data[c-2]>>this.F2:0),d=this.FV/p,v=(1<<this.F1)/p,m=1<<this.F2,g=n.t,y=g-c,b=t==null?s():t;u.dlShiftTo(y,b),n.compareTo(b)>=0&&(n.data[n.t++]=1,n.subTo(b,n)),i.ONE.dlShiftTo(c,b),b.subTo(u,u);while(u.t<c)u.data[u.t++]=0;while(--y>=0){var w=n.data[--g]==h?this.DM:Math.floor(n.data[g]*d+(n.data[g-1]+m)*v);if((n.data[g]+=u.am(0,w,n,y,0,c))<w){u.dlShiftTo(y,b),n.subTo(b,n);while(n.data[g]<--w)n.subTo(b,n)}}t!=null&&(n.drShiftTo(c,t),a!=f&&i.ZERO.subTo(t,t)),n.t=c,n.clamp(),l>0&&n.rShiftTo(l,n),a<0&&i.ZERO.subTo(n,n)}function H(e){var t=s();return this.abs().divRemTo(e,null,t),this.s<0&&t.compareTo(i.ZERO)>0&&e.subTo(t,t),t}function B(e){this.m=e}function j(e){return e.s<0||e.compareTo(this.m)>=0?e.mod(this.m):e}function F(e){return e}function I(e){e.divRemTo(this.m,null,e)}function q(e,t,n){e.multiplyTo(t,n),this.reduce(n)}function R(e,t){e.squareTo(t),this.reduce(t)}function U(){if(this.t<1)return 0;var e=this.data[0];if((e&1)==0)return 0;var t=e&3;return t=t*(2-(e&15)*t)&15,t=t*(2-(e&255)*t)&255,t=t*(2-((e&65535)*t&65535))&65535,t=t*(2-e*t%this.DV)%this.DV,t>0?this.DV-t:-t}function z(e){this.m=e,this.mp=e.invDigit(),this.mpl=this.mp&32767,this.mph=this.mp>>15,this.um=(1<<e.DB-15)-1,this.mt2=2*e.t}function W(e){var t=s();return e.abs().dlShiftTo(this.m.t,t),t.divRemTo(this.m,null,t),e.s<0&&t.compareTo(i.ZERO)>0&&this.m.subTo(t,t),t}function X(e){var t=s();return e.copyTo(t),this.reduce(t),t}function V(e){while(e.t<=this.mt2)e.data[e.t++]=0;for(var t=0;t<this.m.t;++t){var n=e.data[t]&32767,r=n*this.mpl+((n*this.mph+(e.data[t]>>15)*this.mpl&this.um)<<15)&e.DM;n=t+this.m.t,e.data[n]+=this.m.am(0,r,e,t,0,this.m.t);while(e.data[n]>=e.DV)e.data[n]-=e.DV,e.data[++n]++}e.clamp(),e.drShiftTo(this.m.t,e),e.compareTo(this.m)>=0&&e.subTo(this.m,e)}function $(e,t){e.squareTo(t),this.reduce(t)}function J(e,t,n){e.multiplyTo(t,n),this.reduce(n)}function K(){return(this.t>0?this.data[0]&1:this.s)==0}function Q(e,t){if(e>4294967295||e<1)return i.ONE;var n=s(),r=s(),o=t.convert(this),u=N(e)-1;o.copyTo(n);while(--u>=0){t.sqrTo(n,r);if((e&1<<u)>0)t.mulTo(r,o,n);else{var a=n;n=r,r=a}}return t.revert(n)}function G(e,t){var n;return e<256||t.isEven()?n=new B(t):n=new z(t),this.exp(e,n)}function Y(){var e=s();return this.copyTo(e),e}function Z(){if(this.s<0){if(this.t==1)return this.data[0]-this.DV;if(this.t==0)return-1}else{if(this.t==1)return this.data[0];if(this.t==0)return 0}return(this.data[1]&(1<<32-this.DB)-1)<<this.DB|this.data[0]}function et(){return this.t==0?this.s:this.data[0]<<24>>24}function tt(){return this.t==0?this.s:this.data[0]<<16>>16}function nt(e){return Math.floor(Math.LN2*this.DB/Math.log(e))}function rt(){return this.s<0?-1:this.t<=0||this.t==1&&this.data[0]<=0?0:1}function it(e){e==null&&(e=10);if(this.signum()==0||e<2||e>36)return"0";var t=this.chunkSize(e),n=Math.pow(e,t),r=y(n),i=s(),o=s(),u="";this.divRemTo(r,i,o);while(i.signum()>0)u=(n+o.intValue()).toString(e).substr(1)+u,i.divRemTo(r,i,o);return o.intValue().toString(e)+u}function st(e,t){this.fromInt(0),t==null&&(t=10);var n=this.chunkSize(t),r=Math.pow(t,n),s=!1,o=0,u=0;for(var a=0;a<e.length;++a){var f=v(e,a);if(f<0){e.charAt(a)=="-"&&this.signum()==0&&(s=!0);continue}u=t*u+f,++o>=n&&(this.dMultiply(r),this.dAddOffset(u,0),o=0,u=0)}o>0&&(this.dMultiply(Math.pow(t,o)),this.dAddOffset(u,0)),s&&i.ZERO.subTo(this,this)}function ot(e,t,n){if("number"==typeof t)if(e<2)this.fromInt(1);else{this.fromNumber(e,n),this.testBit(e-1)||this.bitwiseTo(i.ONE.shiftLeft(e-1),dt,this),this.isEven()&&this.dAddOffset(1,0);while(!this.isProbablePrime(t))this.dAddOffset(2,0),this.bitLength()>e&&this.subTo(i.ONE.shiftLeft(e-1),this)}else{var r=new Array,s=e&7;r.length=(e>>3)+1,t.nextBytes(r),s>0?r[0]&=(1<<s)-1:r[0]=0,this.fromString(r,256)}}function ut(){var e=this.t,t=new Array;t[0]=this.s;var n=this.DB-e*this.DB%8,r,i=0;if(e-->0){n<this.DB&&(r=this.data[e]>>n)!=(this.s&this.DM)>>n&&(t[i++]=r|this.s<<this.DB-n);while(e>=0){n<8?(r=(this.data[e]&(1<<n)-1)<<8-n,r|=this.data[--e]>>(n+=this.DB-8)):(r=this.data[e]>>(n-=8)&255,n<=0&&(n+=this.DB,--e)),(r&128)!=0&&(r|=-256),i==0&&(this.s&128)!=(r&128)&&++i;if(i>0||r!=this.s)t[i++]=r}}return t}function at(e){return this.compareTo(e)==0}function ft(e){return this.compareTo(e)<0?this:e}function lt(e){return this.compareTo(e)>0?this:e}function ct(e,t,n){var r,i,s=Math.min(e.t,this.t);for(r=0;r<s;++r)n.data[r]=t(this.data[r],e.data[r]);if(e.t<this.t){i=e.s&this.DM;for(r=s;r<this.t;++r)n.data[r]=t(this.data[r],i);n.t=this.t}else{i=this.s&this.DM;for(r=s;r<e.t;++r)n.data[r]=t(i,e.data[r]);n.t=e.t}n.s=t(this.s,e.s),n.clamp()}function ht(e,t){return e&t}function pt(e){var t=s();return this.bitwiseTo(e,ht,t),t}function dt(e,t){return e|t}function vt(e){var t=s();return this.bitwiseTo(e,dt,t),t}function mt(e,t){return e^t}function gt(e){var t=s();return this.bitwiseTo(e,mt,t),t}function yt(e,t){return e&~t}function bt(e){var t=s();return this.bitwiseTo(e,yt,t),t}function wt(){var e=s();for(var t=0;t<this.t;++t)e.data[t]=this.DM&~this.data[t];return e.t=this.t,e.s=~this.s,e}function Et(e){var t=s();return e<0?this.rShiftTo(-e,t):this.lShiftTo(e,t),t}function St(e){var t=s();return e<0?this.lShiftTo(-e,t):this.rShiftTo(e,t),t}function xt(e){if(e==0)return-1;var t=0;return(e&65535)==0&&(e>>=16,t+=16),(e&255)==0&&(e>>=8,t+=8),(e&15)==0&&(e>>=4,t+=4),(e&3)==0&&(e>>=2,t+=2),(e&1)==0&&++t,t}function Tt(){for(var e=0;e<this.t;++e)if(this.data[e]!=0)return e*this.DB+xt(this.data[e]);return this.s<0?this.t*this.DB:-1}function Nt(e){var t=0;while(e!=0)e&=e-1,++t;return t}function Ct(){var e=0,t=this.s&this.DM;for(var n=0;n<this.t;++n)e+=Nt(this.data[n]^t);return e}function kt(e){var t=Math.floor(e/this.DB);return t>=this.t?this.s!=0:(this.data[t]&1<<e%this.DB)!=0}function Lt(e,t){var n=i.ONE.shiftLeft(e);return this.bitwiseTo(n,t,n),n}function At(e){return this.changeBit(e,dt)}function Ot(e){return this.changeBit(e,yt)}function Mt(e){return this.changeBit(e,mt)}function _t(e,t){var n=0,r=0,i=Math.min(e.t,this.t);while(n<i)r+=this.data[n]+e.data[n],t.data[n++]=r&this.DM,r>>=this.DB;if(e.t<this.t){r+=e.s;while(n<this.t)r+=this.data[n],t.data[n++]=r&this.DM,r>>=this.DB;r+=this.s}else{r+=this.s;while(n<e.t)r+=e.data[n],t.data[n++]=r&this.DM,r>>=this.DB;r+=e.s}t.s=r<0?-1:0,r>0?t.data[n++]=r:r<-1&&(t.data[n++]=this.DV+r),t.t=n,t.clamp()}function Dt(e){var t=s();return this.addTo(e,t),t}function Pt(e){var t=s();return this.subTo(e,t),t}function Ht(e){var t=s();return this.multiplyTo(e,t),t}function Bt(e){var t=s();return this.divRemTo(e,t,null),t}function jt(e){var t=s();return this.divRemTo(e,null,t),t}function Ft(e){var t=s(),n=s();return this.divRemTo(e,t,n),new Array(t,n)}function It(e){this.data[this.t]=this.am(0,e-1,this,0,0,this.t),++this.t,this.clamp()}function qt(e,t){if(e==0)return;while(this.t<=t)this.data[this.t++]=0;this.data[t]+=e;while(this.data[t]>=this.DV)this.data[t]-=this.DV,++t>=this.t&&(this.data[this.t++]=0),++this.data[t]}function Rt(){}function Ut(e){return e}function zt(e,t,n){e.multiplyTo(t,n)}function Wt(e,t){e.squareTo(t)}function Xt(e){return this.exp(e,new Rt)}function Vt(e,t,n){var r=Math.min(this.t+e.t,t);n.s=0,n.t=r;while(r>0)n.data[--r]=0;var i;for(i=n.t-this.t;r<i;++r)n.data[r+this.t]=this.am(0,e.data[r],n,r,0,this.t);for(i=Math.min(e.t,t);r<i;++r)this.am(0,e.data[r],n,r,0,t-r);n.clamp()}function $t(e,t,n){--t;var r=n.t=this.t+e.t-t;n.s=0;while(--r>=0)n.data[r]=0;for(r=Math.max(t-this.t,0);r<e.t;++r)n.data[this.t+r-t]=this.am(t-r,e.data[r],n,0,0,this.t+r-t);n.clamp(),n.drShiftTo(1,n)}function Jt(e){this.r2=s(),this.q3=s(),i.ONE.dlShiftTo(2*e.t,this.r2),this.mu=this.r2.divide(e),this.m=e}function Kt(e){if(e.s<0||e.t>2*this.m.t)return e.mod(this.m);if(e.compareTo(this.m)<0)return e;var t=s();return e.copyTo(t),this.reduce(t),t}function Qt(e){return e}function Gt(e){e.drShiftTo(this.m.t-1,this.r2),e.t>this.m.t+1&&(e.t=this.m.t+1,e.clamp()),this.mu.multiplyUpperTo(this.r2,this.m.t+1,this.q3),this.m.multiplyLowerTo(this.q3,this.m.t+1,this.r2);while(e.compareTo(this.r2)<0)e.dAddOffset(1,this.m.t+1);e.subTo(this.r2,e);while(e.compareTo(this.m)>=0)e.subTo(this.m,e)}function Yt(e,t){e.squareTo(t),this.reduce(t)}function Zt(e,t,n){e.multiplyTo(t,n),this.reduce(n)}function en(e,t){var n=e.bitLength(),r,i=y(1),o;if(n<=0)return i;n<18?r=1:n<48?r=3:n<144?r=4:n<768?r=5:r=6,n<8?o=new B(t):t.isEven()?o=new Jt(t):o=new z(t);var u=new Array,a=3,f=r-1,l=(1<<r)-1;u[1]=o.convert(this);if(r>1){var c=s();o.sqrTo(u[1],c);while(a<=l)u[a]=s(),o.mulTo(c,u[a-2],u[a]),a+=2}var h=e.t-1,p,d=!0,v=s(),m;n=N(e.data[h])-1;while(h>=0){n>=f?p=e.data[h]>>n-f&l:(p=(e.data[h]&(1<<n+1)-1)<<f-n,h>0&&(p|=e.data[h-1]>>this.DB+n-f)),a=r;while((p&1)==0)p>>=1,--a;(n-=a)<0&&(n+=this.DB,--h);if(d)u[p].copyTo(i),d=!1;else{while(a>1)o.sqrTo(i,v),o.sqrTo(v,i),a-=2;a>0?o.sqrTo(i,v):(m=i,i=v,v=m),o.mulTo(v,u[p],i)}while(h>=0&&(e.data[h]&1<<n)==0)o.sqrTo(i,v),m=i,i=v,v=m,--n<0&&(n=this.DB-1,--h)}return o.revert(i)}function tn(e){var t=this.s<0?this.negate():this.clone(),n=e.s<0?e.negate():e.clone();if(t.compareTo(n)<0){var r=t;t=n,n=r}var i=t.getLowestSetBit(),s=n.getLowestSetBit();if(s<0)return t;i<s&&(s=i),s>0&&(t.rShiftTo(s,t),n.rShiftTo(s,n));while(t.signum()>0)(i=t.getLowestSetBit())>0&&t.rShiftTo(i,t),(i=n.getLowestSetBit())>0&&n.rShiftTo(i,n),t.compareTo(n)>=0?(t.subTo(n,t),t.rShiftTo(1,t)):(n.subTo(t,n),n.rShiftTo(1,n));return s>0&&n.lShiftTo(s,n),n}function nn(e){if(e<=0)return 0;var t=this.DV%e,n=this.s<0?e-1:0;if(this.t>0)if(t==0)n=this.data[0]%e;else for(var r=this.t-1;r>=0;--r)n=(t*n+this.data[r])%e;return n}function rn(e){var t=e.isEven();if(this.isEven()&&t||e.signum()==0)return i.ZERO;var n=e.clone(),r=this.clone(),s=y(1),o=y(0),u=y(0),a=y(1);while(n.signum()!=0){while(n.isEven()){n.rShiftTo(1,n);if(t){if(!s.isEven()||!o.isEven())s.addTo(this,s),o.subTo(e,o);s.rShiftTo(1,s)}else o.isEven()||o.subTo(e,o);o.rShiftTo(1,o)}while(r.isEven()){r.rShiftTo(1,r);if(t){if(!u.isEven()||!a.isEven())u.addTo(this,u),a.subTo(e,a);u.rShiftTo(1,u)}else a.isEven()||a.subTo(e,a);a.rShiftTo(1,a)}n.compareTo(r)>=0?(n.subTo(r,n),t&&s.subTo(u,s),o.subTo(a,o)):(r.subTo(n,r),t&&u.subTo(s,u),a.subTo(o,a))}return r.compareTo(i.ONE)!=0?i.ZERO:a.compareTo(e)>=0?a.subtract(e):a.signum()<0?(a.addTo(e,a),a.signum()<0?a.add(e):a):a}function un(e){var t,n=this.abs();if(n.t==1&&n.data[0]<=sn[sn.length-1]){for(t=0;t<sn.length;++t)if(n.data[0]==sn[t])return!0;return!1}if(n.isEven())return!1;t=1;while(t<sn.length){var r=sn[t],i=t+1;while(i<sn.length&&r<on)r*=sn[i++];r=n.modInt(r);while(t<i)if(r%sn[t++]==0)return!1}return n.millerRabin(e)}function an(e){var t=this.subtract(i.ONE),n=t.getLowestSetBit();if(n<=0)return!1;var r=t.shiftRight(n);e=e+1>>1,e>sn.length&&(e=sn.length);var o=s();for(var u=0;u<e;++u){o.fromInt(sn[u]);var a=o.modPow(r,this);if(a.compareTo(i.ONE)!=0&&a.compareTo(t)!=0){var f=1;while(f++<n&&a.compareTo(t)!=0){a=a.modPowInt(2,this);if(a.compareTo(i.ONE)==0)return!1}if(a.compareTo(t)!=0)return!1}}return!0}var t,n=0xdeadbeefcafe,r=(n&16777215)==15715070;typeof navigator=="undefined"?(i.prototype.am=a,t=28):r&&navigator.appName=="Microsoft Internet Explorer"?(i.prototype.am=u,t=30):r&&navigator.appName!="Netscape"?(i.prototype.am=o,t=26):(i.prototype.am=a,t=28),i.prototype.DB=t,i.prototype.DM=(1<<t)-1,i.prototype.DV=1<<t;var f=52;i.prototype.FV=Math.pow(2,f),i.prototype.F1=f-t,i.prototype.F2=2*t-f;var l="0123456789abcdefghijklmnopqrstuvwxyz",c=new Array,h,p;h="0".charCodeAt(0);for(p=0;p<=9;++p)c[h++]=p;h="a".charCodeAt(0);for(p=10;p<36;++p)c[h++]=p;h="A".charCodeAt(0);for(p=10;p<36;++p)c[h++]=p;B.prototype.convert=j,B.prototype.revert=F,B.prototype.reduce=I,B.prototype.mulTo=q,B.prototype.sqrTo=R,z.prototype.convert=W,z.prototype.revert=X,z.prototype.reduce=V,z.prototype.mulTo=J,z.prototype.sqrTo=$,i.prototype.copyTo=m,i.prototype.fromInt=g,i.prototype.fromString=b,i.prototype.clamp=w,i.prototype.dlShiftTo=k,i.prototype.drShiftTo=L,i.prototype.lShiftTo=A,i.prototype.rShiftTo=O,i.prototype.subTo=M,i.prototype.multiplyTo=_,i.prototype.squareTo=D,i.prototype.divRemTo=P,i.prototype.invDigit=U,i.prototype.isEven=K,i.prototype.exp=Q,i.prototype.toString=E,i.prototype.negate=S,i.prototype.abs=x,i.prototype.compareTo=T,i.prototype.bitLength=C,i.prototype.mod=H,i.prototype.modPowInt=G,i.ZERO=y(0),i.ONE=y(1),Rt.prototype.convert=Ut,Rt.prototype.revert=Ut,Rt.prototype.mulTo=zt,Rt.prototype.sqrTo=Wt,Jt.prototype.convert=Kt,Jt.prototype.revert=Qt,Jt.prototype.reduce=Gt,Jt.prototype.mulTo=Zt,Jt.prototype.sqrTo=Yt;var sn=[2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,503,509],on=(1<<26)/sn[sn.length-1];i.prototype.chunkSize=nt,i.prototype.toRadix=it,i.prototype.fromRadix=st,i.prototype.fromNumber=ot,i.prototype.bitwiseTo=ct,i.prototype.changeBit=Lt,i.prototype.addTo=_t,i.prototype.dMultiply=It,i.prototype.dAddOffset=qt,i.prototype.multiplyLowerTo=Vt,i.prototype.multiplyUpperTo=$t,i.prototype.modInt=nn,i.prototype.millerRabin=an,i.prototype.clone=Y,i.prototype.intValue=Z,i.prototype.byteValue=et,i.prototype.shortValue=tt,i.prototype.signum=rt,i.prototype.toByteArray=ut,i.prototype.equals=at,i.prototype.min=ft,i.prototype.max=lt,i.prototype.and=pt,i.prototype.or=vt,i.prototype.xor=gt,i.prototype.andNot=bt,i.prototype.not=wt,i.prototype.shiftLeft=Et,i.prototype.shiftRight=St,i.prototype.getLowestSetBit=Tt,i.prototype.bitCount=Ct,i.prototype.testBit=kt,i.prototype.setBit=At,i.prototype.clearBit=Ot,i.prototype.flipBit=Mt,i.prototype.add=Dt,i.prototype.subtract=Pt,i.prototype.multiply=Ht,i.prototype.divide=Bt,i.prototype.remainder=jt,i.prototype.divideAndRemainder=Ft,i.prototype.modPow=en,i.prototype.modInverse=rn,i.prototype.pow=Xt,i.prototype.gcd=tn,i.prototype.isProbablePrime=un,e.jsbn=e.jsbn||{},e.jsbn.BigInteger=i}var t="jsbn";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/jsbn",["require","module"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){var t=e.asn1,n=e.pkcs7asn1=e.pkcs7asn1||{};e.pkcs7=e.pkcs7||{},e.pkcs7.asn1=n;var r={name:"ContentInfo",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"ContentInfo.ContentType",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"contentType"},{name:"ContentInfo.content",tagClass:t.Class.CONTEXT_SPECIFIC,type:0,constructed:!0,optional:!0,captureAsn1:"content"}]};n.contentInfoValidator=r;var i={name:"EncryptedContentInfo",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"EncryptedContentInfo.contentType",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"contentType"},{name:"EncryptedContentInfo.contentEncryptionAlgorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"EncryptedContentInfo.contentEncryptionAlgorithm.algorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"encAlgorithm"},{name:"EncryptedContentInfo.contentEncryptionAlgorithm.parameter",tagClass:t.Class.UNIVERSAL,captureAsn1:"encParameter"}]},{name:"EncryptedContentInfo.encryptedContent",tagClass:t.Class.CONTEXT_SPECIFIC,type:0,capture:"encContent"}]};n.envelopedDataValidator={name:"EnvelopedData",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"EnvelopedData.Version",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"version"},{name:"EnvelopedData.RecipientInfos",tagClass:t.Class.UNIVERSAL,type:t.Type.SET,constructed:!0,captureAsn1:"recipientInfos"}].concat(i)},n.encryptedDataValidator={name:"EncryptedData",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"EncryptedData.Version",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"version"}].concat(i)};var s={name:"SignerInfo",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"SignerInfo.Version",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1},{name:"SignerInfo.IssuerAndSerialNumber",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0},{name:"SignerInfo.DigestAlgorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0},{name:"SignerInfo.AuthenticatedAttributes",tagClass:t.Class.CONTEXT_SPECIFIC,type:0,constructed:!0,optional:!0,capture:"authenticatedAttributes"},{name:"SignerInfo.DigestEncryptionAlgorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0},{name:"SignerInfo.EncryptedDigest",tagClass:t.Class.UNIVERSAL,type:t.Type.OCTETSTRING,constructed:!1,capture:"signature"},{name:"SignerInfo.UnauthenticatedAttributes",tagClass:t.Class.CONTEXT_SPECIFIC,type:1,constructed:!0,optional:!0}]};n.signedDataValidator={name:"SignedData",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"SignedData.Version",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"version"},{name:"SignedData.DigestAlgorithms",tagClass:t.Class.UNIVERSAL,type:t.Type.SET,constructed:!0,captureAsn1:"digestAlgorithms"},r,{name:"SignedData.Certificates",tagClass:t.Class.CONTEXT_SPECIFIC,type:0,optional:!0,captureAsn1:"certificates"},{name:"SignedData.CertificateRevocationLists",tagClass:t.Class.CONTEXT_SPECIFIC,type:1,optional:!0,captureAsn1:"crls"},{name:"SignedData.SignerInfos",tagClass:t.Class.UNIVERSAL,type:t.Type.SET,capture:"signerInfos",value:[s]}]},n.recipientInfoValidator={name:"RecipientInfo",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"RecipientInfo.version",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"version"},{name:"RecipientInfo.issuerAndSerial",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"RecipientInfo.issuerAndSerial.issuer",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,captureAsn1:"issuer"},{name:"RecipientInfo.issuerAndSerial.serialNumber",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"serial"}]},{name:"RecipientInfo.keyEncryptionAlgorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"RecipientInfo.keyEncryptionAlgorithm.algorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"encAlgorithm"},{name:"RecipientInfo.keyEncryptionAlgorithm.parameter",tagClass:t.Class.UNIVERSAL,constructed:!1,captureAsn1:"encParameter"}]},{name:"RecipientInfo.encryptedKey",tagClass:t.Class.UNIVERSAL,type:t.Type.OCTETSTRING,constructed:!1,capture:"encKey"}]}}var t="pkcs7asn1";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/pkcs7asn1",["require","module","./asn1","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){function f(e,t,n,r){var i=[];for(var s=0;s<e.length;s++)for(var o=0;o<e[s].safeBags.length;o++){var u=e[s].safeBags[o];if(r!==undefined&&u.type!==r)continue;u.attributes[t]!==undefined&&u.attributes[t].indexOf(n)>=0&&i.push(u)}return i}function l(e,r,s,o){r=t.fromDer(r,s);if(r.tagClass!==t.Class.UNIVERSAL||r.type!==t.Type.SEQUENCE||r.constructed!==!0)throw{message:"PKCS#12 AuthenticatedSafe expected to be a SEQUENCE OF ContentInfo"};for(var u=0;u<r.value.length;u++){var a=r.value[u],f={},l=[];if(!t.validate(a,i,f,l))throw{message:"Cannot read ContentInfo.",errors:l};var p={encrypted:!1},d=null,v=f.content.value[0];switch(t.derToOid(f.contentType)){case n.oids.data:if(v.tagClass!==t.Class.UNIVERSAL||v.type!==t.Type.OCTETSTRING)throw{message:"PKCS#12 SafeContents Data is not an OCTET STRING."};d=v.value;break;case n.oids.encryptedData:if(o===undefined)throw{message:"Found PKCS#12 Encrypted SafeContents Data but no password available."};d=c(v,o),p.encrypted=!0;break;default:throw{message:"Unsupported PKCS#12 contentType.",contentType:t.derToOid(f.contentType)}}p.safeBags=h(d,s,o),e.safeContents.push(p)}}function c(r,i){var s={},o=[];if(!t.validate(r,e.pkcs7.asn1.encryptedDataValidator,s,o))throw{message:"Cannot read EncryptedContentInfo. ",errors:o};var u=t.derToOid(s.contentType);if(u!==n.oids.data)throw{message:"PKCS#12 EncryptedContentInfo ContentType is not Data.",oid:u};u=t.derToOid(s.encAlgorithm);var a=n.pbe.getCipher(u,s.encParameter,i),f=e.util.createBuffer(s.encContent);a.update(f);if(!a.finish())throw{message:"Failed to decrypt PKCS#12 SafeContents."};return a.output.getBytes()}function h(e,r,i){e=t.fromDer(e,r);if(e.tagClass!==t.Class.UNIVERSAL||e.type!==t.Type.SEQUENCE||e.constructed!==!0)throw{message:"PKCS#12 SafeContents expected to be a SEQUENCE OF SafeBag"};var s=[];for(var u=0;u<e.value.length;u++){var f=e.value[u],l={},c=[];if(!t.validate(f,o,l,c))throw{message:"Cannot read SafeBag.",errors:c};var h={type:t.derToOid(l.bagId),attributes:p(l.bagAttributes)};s.push(h);var d,v,m=l.bagValue.value[0];switch(h.type){case n.oids.pkcs8ShroudedKeyBag:if(i===undefined)throw{message:"Found PKCS#8 ShroudedKeyBag but no password available."};m=n.decryptPrivateKeyInfo(m,i);if(m===null)throw{message:"Unable to decrypt PKCS#8 ShroudedKeyBag, wrong password?"};case n.oids.keyBag:h.key=n.privateKeyFromAsn1(m);continue;case n.oids.certBag:d=a,v=function(){if(t.derToOid(l.certId)!==n.oids.x509Certificate)throw{message:"Unsupported certificate type, only X.509 supported.",oid:t.derToOid(l.certId)};h.cert=n.certificateFromAsn1(t.fromDer(l.cert,r),!0)};break;default:throw{message:"Unsupported PKCS#12 SafeBag type.",oid:h.type}}if(d!==undefined&&!t.validate(m,d,l,c))throw{message:"Cannot read PKCS#12 "+d.name,errors:c};v()}return s}function p(e){var r={};if(e!==undefined)for(var i=0;i<e.length;++i){var s={},o=[];if(!t.validate(e[i],u,s,o))throw{message:"Cannot read PKCS#12 BagAttribute.",errors:o};var a=t.derToOid(s.oid);if(n.oids[a]===undefined)continue;r[n.oids[a]]=[];for(var f=0;f<s.values.length;++f)r[n.oids[a]].push(s.values[f].value)}return r}var t=e.asn1,n=e.pki,r=e.pkcs12=e.pkcs12||{},i={name:"ContentInfo",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"ContentInfo.contentType",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"contentType"},{name:"ContentInfo.content",tagClass:t.Class.CONTEXT_SPECIFIC,constructed:!0,captureAsn1:"content"}]},s={name:"PFX",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"PFX.version",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"version"},i,{name:"PFX.macData",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,optional:!0,captureAsn1:"mac",value:[{name:"PFX.macData.mac",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"PFX.macData.mac.digestAlgorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"PFX.macData.mac.digestAlgorithm.algorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"macAlgorithm"},{name:"PFX.macData.mac.digestAlgorithm.parameters",tagClass:t.Class.UNIVERSAL,captureAsn1:"macAlgorithmParameters"}]},{name:"PFX.macData.mac.digest",tagClass:t.Class.UNIVERSAL,type:t.Type.OCTETSTRING,constructed:!1,capture:"macDigest"}]},{name:"PFX.macData.macSalt",tagClass:t.Class.UNIVERSAL,type:t.Type.OCTETSTRING,constructed:!1,capture:"macSalt"},{name:"PFX.macData.iterations",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,optional:!0,capture:"macIterations"}]}]},o={name:"SafeBag",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"SafeBag.bagId",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"bagId"},{name:"SafeBag.bagValue",tagClass:t.Class.CONTEXT_SPECIFIC,constructed:!0,captureAsn1:"bagValue"},{name:"SafeBag.bagAttributes",tagClass:t.Class.UNIVERSAL,type:t.Type.SET,constructed:!0,optional:!0,capture:"bagAttributes"}]},u={name:"Attribute",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"Attribute.attrId",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"oid"},{name:"Attribute.attrValues",tagClass:t.Class.UNIVERSAL,type:t.Type.SET,constructed:!0,capture:"values"}]},a={name:"CertBag",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"CertBag.certId",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"certId"},{name:"CertBag.certValue",tagClass:t.Class.CONTEXT_SPECIFIC,constructed:!0,value:[{name:"CertBag.certValue[0]",tagClass:t.Class.UNIVERSAL,type:t.Class.OCTETSTRING,constructed:!1,capture:"cert"}]}]};r.pkcs12FromAsn1=function(i,o,u){typeof o=="string"?(u=o,o=!0):o===undefined&&(o=!0);var a={},c=[];if(!t.validate(i,s,a,c))throw{message:"Cannot read PKCS#12 PFX. ASN.1 object is not an PKCS#12 PFX.",errors:c};var h={version:a.version.charCodeAt(0),safeContents:[],getBags:function(t){var n={},r;return"localKeyId"in t?r=t.localKeyId:"localKeyIdHex"in t&&(r=e.util.hexToBytes(t.localKeyIdHex)),r!==undefined&&(n.localKeyId=f(h.safeContents,"localKeyId",r,t.bagType)),"friendlyName"in t&&(n.friendlyName=f(h.safeContents,"friendlyName",t.friendlyName,t.bagType)),n},getBagsByFriendlyName:function(e,t){return f(h.safeContents,"friendlyName",e,t)},getBagsByLocalKeyId:function(e,t){return f(h.safeContents,"localKeyId",e,t)}};if(a.version.charCodeAt(0)!==3)throw{message:"PKCS#12 PFX of version other than 3 not supported.",version:a.version.charCodeAt(0)};if(t.derToOid(a.contentType)!==n.oids.data)throw{message:"Only PKCS#12 PFX in password integrity mode supported.",oid:t.derToOid(a.contentType)};var p=a.content.value[0];if(p.tagClass!==t.Class.UNIVERSAL||p.type!==t.Type.OCTETSTRING)throw{message:"PKCS#12 authSafe content data is not an OCTET STRING."};if(a.mac){var d=null,v=0,m=t.derToOid(a.macAlgorithm);switch(m){case n.oids.sha1:d=e.md.sha1.create(),v=20;break;case n.oids.sha256:d=e.md.sha256.create(),v=32;break;case n.oids.sha384:d=e.md.sha384.create(),v=48;break;case n.oids.sha512:d=e.md.sha512.create(),v=64;break;case n.oids.md5:d=e.md.md5.create(),v=16}if(d===null)throw{message:"PKCS#12 uses unsupported MAC algorithm: "+m};var g=new e.util.ByteBuffer(a.macSalt),y="macIterations"in a?parseInt(e.util.bytesToHex(a.macIterations),16):1,b=r.generateKey(u||"",g,3,y,v,d),w=e.hmac.create();w.start(d,b),w.update(p.value);var E=w.getMac();if(E.getBytes()!==a.macDigest)throw{message:"PKCS#12 MAC could not be verified. Invalid password?"}}return l(h,p.value,o,u),h},r.toPkcs12Asn1=function(i,s,o,u){u=u||{},u.saltSize=u.saltSize||8,u.count=u.count||2048,u.algorithm=u.algorithm||u.encAlgorithm||"aes128","useMac"in u||(u.useMac=!0),"localKeyId"in u||(u.localKeyId=null),"generateLocalKeyId"in u||(u.generateLocalKeyId=!0);var a=u.localKeyId,f=undefined;if(a!==null)a=e.util.hexToBytes(a);else if(u.generateLocalKeyId)if(s){var l=e.util.isArray(s)?s[0]:s;typeof l=="string"&&(l=n.certificateFromPem(l));var c=e.md.sha1.create();c.update(t.toDer(n.certificateToAsn1(l)).getBytes()),a=c.digest().getBytes()}else a=e.random.getBytes(20);var h=[];a!==null&&h.push(t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.oids.localKeyId).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.SET,!0,[t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,a)])])),"friendlyName"in u&&h.push(t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.oids.friendlyName).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.SET,!0,[t.create(t.Class.UNIVERSAL,t.Type.BMPSTRING,!1,u.friendlyName)])])),h.length>0&&(f=t.create(t.Class.UNIVERSAL,t.Type.SET,!0,h));var p=[],d=[];s!==null&&(e.util.isArray(s)?d=s:d=[s]);var v=[];for(var m=0;m<d.length;++m){s=d[m],typeof s=="string"&&(s=n.certificateFromPem(s));var g=m===0?f:undefined,y=n.certificateToAsn1(s),b=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.oids.certBag).getBytes()),t.create(t.Class.CONTEXT_SPECIFIC,0,!0,[t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.oids.x509Certificate).getBytes()),t.create(t.Class.CONTEXT_SPECIFIC,0,!0,[t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,t.toDer(y).getBytes())])])]),g]);v.push(b)}if(v.length>0){var w=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,v),E=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.oids.data).getBytes()),t.create(t.Class.CONTEXT_SPECIFIC,0,!0,[t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,t.toDer(w).getBytes())])]);p.push(E)}var S=null;if(i!==null){var x=n.wrapRsaPrivateKey(n.privateKeyToAsn1(i));o===null?S=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.oids.keyBag).getBytes()),t.create(t.Class.CONTEXT_SPECIFIC,0,!0,[x]),f]):S=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.oids.pkcs8ShroudedKeyBag).getBytes()),t.create(t.Class.CONTEXT_SPECIFIC,0,!0,[n.encryptPrivateKeyInfo(x,o,u)]),f]);var T=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[S]),N=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.oids.data).getBytes()),t.create(t.Class.CONTEXT_SPECIFIC,0,!0,[t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,t.toDer(T).getBytes())])]);p.push(N)}var C=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,p),k=undefined;if(u.useMac){var c=e.md.sha1.create(),L=new e.util.ByteBuffer(e.random.getBytes(u.saltSize)),A=u.count,i=r.generateKey(o||"",L,3,A,20),O=e.hmac.create();O.start(c,i),O.update(t.toDer(C).getBytes());var M=O.getMac();k=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.oids.sha1).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.NULL,!1,"")]),t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,M.getBytes())]),t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,L.getBytes()),t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,e.util.hexToBytes(A.toString(16)))])}return t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,String.fromCharCode(3)),t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.oids.data).getBytes()),t.create(t.Class.CONTEXT_SPECIFIC,0,!0,[t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,t.toDer(C).getBytes())])]),k])},r.generateKey=function(t,n,r,i,s,o){var u,a;if(typeof o=="undefined"||o===null)o=e.md.sha1.create();var f=o.digestLength,l=o.blockLength,c=new e.util.ByteBuffer,h=new e.util.ByteBuffer;for(a=0;a<t.length;a++)h.putInt16(t.charCodeAt(a));h.putInt16(0);var p=h.length(),d=n.length(),v=new e.util.ByteBuffer;v.fillWithByte(r,l);var m=l*Math.ceil(d/l),g=new e.util.ByteBuffer;for(a=0;a<m;a++)g.putByte(n.at(a%d));var y=l*Math.ceil(p/l),b=new e.util.ByteBuffer;for(a=0;a<y;a++)b.putByte(h.at(a%p));var w=g;w.putBuffer(b);var E=Math.ceil(s/f);for(var S=1;S<=E;S++){var x=new e.util.ByteBuffer;x.putBytes(v.bytes()),x.putBytes(w.bytes());for(var T=0;T<i;T++)o.start(),o.update(x.getBytes()),x=o.digest();var N=new e.util.ByteBuffer;for(a=0;a<l;a++)N.putByte(x.at(a%f));var C=Math.ceil(d/l)+Math.ceil(p/l),k=new e.util.ByteBuffer;for(u=0;u<C;u++){var L=new e.util.ByteBuffer(w.getBytes(l)),A=511;for(a=N.length()-1;a>=0;a--)A>>=8,A+=N.at(a)+L.at(a),L.setAt(a,A&255);k.putBuffer(L)}w=k,c.putBuffer(x)}return c.truncate(c.length()-s),c}}var t="pkcs12";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/pkcs12",["require","module","./asn1","./sha1","./pkcs7asn1","./pki","./util","./random","./hmac"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){var t=e.pss=e.pss||{};t.create=function(t,n,r){var i=t.digestLength,s={};return s.verify=function(s,o,u){var a,f=u-1,l=Math.ceil(f/8);o=o.substr(-l);if(l<i+r+2)throw{message:"Inconsistent parameters to PSS signature verification."};if(o.charCodeAt(l-1)!==188)throw{message:"Encoded message does not end in 0xBC."};var c=l-i-1,h=o.substr(0,c),p=o.substr(c,i),d=65280>>8*l-f&255;if((h.charCodeAt(0)&d)!==0)throw{message:"Bits beyond keysize not zero as expected."};var v=n.generate(p,c),m="";for(a=0;a<c;a++)m+=String.fromCharCode(h.charCodeAt(a)^v.charCodeAt(a));m=String.fromCharCode(m.charCodeAt(0)&~d)+m.substr(1);var g=l-i-r-2;for(a=0;a<g;a++)if(m.charCodeAt(a)!==0)throw{message:"Leftmost octets not zero as expected"};if(m.charCodeAt(g)!==1)throw{message:"Inconsistent PSS signature, 0x01 marker not found"};var y=m.substr(-r),b=new e.util.ByteBuffer;b.fillWithByte(0,8),b.putBytes(s),b.putBytes(y),t.start(),t.update(b.getBytes());var w=t.digest().getBytes();return p===w},s.encode=function(s,o){var u,a=o-1,f=Math.ceil(a/8),l=s.digest().getBytes();if(f<i+r+2)throw{message:"Message is too long to encrypt"};var c=e.random.getBytes(r),h=new e.util.ByteBuffer;h.fillWithByte(0,8),h.putBytes(l),h.putBytes(c),t.start(),t.update(h.getBytes());var p=t.digest().getBytes(),d=new e.util.ByteBuffer;d.fillWithByte(0,f-r-i-2),d.putByte(1),d.putBytes(c);var v=d.getBytes(),m=f-i-1,g=n.generate(p,m),y="";for(u=0;u<m;u++)y+=String.fromCharCode(v.charCodeAt(u)^g.charCodeAt(u));var b=65280>>8*f-a&255;return y=String.fromCharCode(y.charCodeAt(0)&~b)+y.substr(1),y+p+String.fromCharCode(188)},s}}var t="pss";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/pss",["require","module","./random","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){var t=[217,120,249,196,25,221,181,237,40,233,253,121,74,160,216,157,198,126,55,131,43,118,83,142,98,76,100,136,68,139,251,162,23,154,89,245,135,179,79,19,97,69,109,141,9,129,125,50,189,143,64,235,134,183,123,11,240,149,33,34,92,107,78,130,84,214,101,147,206,96,178,28,115,86,192,20,167,140,241,220,18,117,202,31,59,190,228,209,66,61,212,48,163,60,182,38,111,191,14,218,70,105,7,87,39,242,29,155,188,148,67,3,248,17,199,246,144,239,62,231,6,195,213,47,200,102,30,215,8,232,234,222,128,82,238,247,132,170,114,172,53,77,106,42,150,26,210,113,90,21,73,116,75,159,208,94,4,24,164,236,194,224,65,110,15,81,203,204,36,145,175,80,161,244,112,57,153,124,58,133,35,184,180,122,252,2,54,91,37,85,151,49,45,93,250,152,227,138,146,174,5,223,41,16,103,108,186,201,211,0,230,207,225,158,168,44,99,22,1,63,88,226,137,169,13,56,52,27,171,51,255,176,187,72,12,95,185,177,205,46,197,243,219,71,229,165,156,119,10,166,32,104,254,127,193,173],n=[1,2,3,5],r=function(e,t){return e<<t&65535|(e&65535)>>16-t},i=function(e,t){return(e&65535)>>t|e<<16-t&65535};e.rc2=e.rc2||{},e.rc2.expandKey=function(n,r){typeof n=="string"&&(n=e.util.createBuffer(n)),r=r||128;var i=n,s=n.length(),o=r,u=Math.ceil(o/8),a=255>>(o&7),f;for(f=s;f<128;f++)i.putByte(t[i.at(f-1)+i.at(f-s)&255]);i.setAt(128-u,t[i.at(128-u)&a]);for(f=127-u;f>=0;f--)i.setAt(f,t[i.at(f+1)^i.at(f+u)]);return i};var s=function(t,s,o){var u=!1,a=null,f=null,l=null,c,h,p,d,v=[];t=e.rc2.expandKey(t,s);for(p=0;p<64;p++)v.push(t.getInt16Le());o?(c=function(e){for(p=0;p<4;p++)e[p]+=v[d]+(e[(p+3)%4]&e[(p+2)%4])+(~e[(p+3)%4]&e[(p+1)%4]),e[p]=r(e[p],n[p]),d++},h=function(e){for(p=0;p<4;p++)e[p]+=v[e[(p+3)%4]&63]}):(c=function(e){for(p=3;p>=0;p--)e[p]=i(e[p],n[p]),e[p]-=v[d]+(e[(p+3)%4]&e[(p+2)%4])+(~e[(p+3)%4]&e[(p+1)%4]),d--},h=function(e){for(p=3;p>=0;p--)e[p]-=v[e[(p+3)%4]&63]});var m=function(e){var t=[];for(p=0;p<4;p++){var n=a.getInt16Le();l!==null&&(o?n^=l.getInt16Le():l.putInt16Le(n)),t.push(n&65535)}d=o?0:63;for(var r=0;r<e.length;r++)for(var i=0;i<e[r][0];i++)e[r][1](t);for(p=0;p<4;p++)l!==null&&(o?l.putInt16Le(t[p]):t[p]^=l.getInt16Le()),f.putInt16Le(t[p])},g=null;return g={start:function(n,r){n&&typeof t=="string"&&n.length===8&&(n=e.util.createBuffer(n)),u=!1,a=e.util.createBuffer(),f=r||new e.util.createBuffer,l=n,g.output=f},update:function(e){u||a.putBuffer(e);while(a.length()>=8)m([[5,c],[1,h],[6,c],[1,h],[5,c]])},finish:function(e){var t=!0;if(o)if(e)t=e(8,a,!o);else{var n=a.length()===8?8:8-a.length();a.fillWithByte(n,n)}t&&(u=!0,g.update());if(!o){t=a.length()===0;if(t)if(e)t=e(8,f,!o);else{var r=f.length(),i=f.at(r-1);i>r?t=!1:f.truncate(i)}}return t}},g};e.rc2.startEncrypting=function(t,n,r){var i=e.rc2.createEncryptionCipher(t,128);return i.start(n,r),i},e.rc2.createEncryptionCipher=function(e,t){return s(e,t,!0)},e.rc2.startDecrypting=function(t,n,r){var i=e.rc2.createDecryptionCipher(t,128);return i.start(n,r),i},e.rc2.createDecryptionCipher=function(e,t){return s(e,t,!1)}}var t="rc2";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/rc2",["require","module","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){function n(e,t,n){var r="",i=Math.ceil(t/n.digestLength);for(var s=0;s<i;++s){var o=String.fromCharCode(s>>24&255,s>>16&255,s>>8&255,s&255);n.start(),n.update(e+o),r+=n.digest().getBytes()}return r.substring(0,t)}var t=e.pkcs1=e.pkcs1||{};t.encode_rsa_oaep=function(t,r,i){var s=undefined,o=undefined,u=undefined;typeof i=="string"?(s=i,o=arguments[3]||undefined,u=arguments[4]||undefined):i&&(s=i.label||undefined,o=i.seed||undefined,u=i.md||undefined),u?u.start():u=e.md.sha1.create();var a=Math.ceil(t.n.bitLength()/8),f=a-2*u.digestLength-2;if(r.length>f)throw{message:"RSAES-OAEP input message length is too long.",length:r.length,maxLength:f};s||(s=""),u.update(s,"raw");var l=u.digest(),c="",h=f-r.length;for(var p=0;p<h;p++)c+="\0";var d=l.getBytes()+c+""+r;if(!o)o=e.random.getBytes(u.digestLength);else if(o.length!==u.digestLength)throw{message:"Invalid RSAES-OAEP seed. The seed length must match the digest length.",seedLength:o.length,digestLength:u.digestLength};var v=n(o,a-u.digestLength-1,u),m=e.util.xorBytes(d,v,d.length),g=n(m,u.digestLength,u),y=e.util.xorBytes(o,g,o.length);return"\0"+y+m},t.decode_rsa_oaep=function(t,r,i){var s=undefined,o=undefined;typeof i=="string"?(s=i,o=arguments[3]||undefined):i&&(s=i.label||undefined,o=i.md||undefined);var u=Math.ceil(t.n.bitLength()/8);if(r.length!==u)throw{message:"RSAES-OAEP encoded message length is invalid.",length:r.length,expectedLength:u};o===undefined?o=e.md.sha1.create():o.start();if(u<2*o.digestLength+2)throw{message:"RSAES-OAEP key is too short for the hash function."};s||(s=""),o.update(s,"raw");var a=o.digest().getBytes(),f=r.charAt(0),l=r.substring(1,o.digestLength+1),c=r.substring(1+o.digestLength),h=n(c,o.digestLength,o),p=e.util.xorBytes(l,h,l.length),d=n(p,u-o.digestLength-1,o),v=e.util.xorBytes(c,d,c.length),m=v.substring(0,o.digestLength),g=f!=="\0";for(var y=0;y<o.digestLength;++y)g|=a.charAt(y)!==m.charAt(y);var b=1,w=o.digestLength;for(var E=o.digestLength;E<v.length;E++){var S=v.charCodeAt(E),x=S&1^1,T=b?65534:0;g|=S&T,b&=x,w+=b}if(g||v.charCodeAt(w)!==1)throw{message:"Invalid RSAES-OAEP padding."};return v.substring(w+1)}}var t="pkcs1";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/pkcs1",["require","module","./util","./random","./sha1"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){function o(t,n,r){var i=e.util.createBuffer(),s=Math.ceil(n.n.bitLength()/8);if(t.length>s-11)throw{message:"Message is too long for PKCS#1 v1.5 padding.",length:t.length,max:s-11};i.putByte(0),i.putByte(r);var o=s-3-t.length,u;if(r===0||r===1){u=r===0?0:255;for(var a=0;a<o;++a)i.putByte(u)}else for(var a=0;a<o;++a)u=Math.floor(Math.random()*255)+1,i.putByte(u);return i.putByte(0),i.putBytes(t),i}function u(t,n,r,i){var s=Math.ceil(n.n.bitLength()/8),o=e.util.createBuffer(t),u=o.getByte(),a=o.getByte();if(u!==0||r&&a!==0&&a!==1||!r&&a!=2||r&&a===0&&typeof i=="undefined")throw{message:"Encryption block is invalid."};var f=0;if(a===0){f=s-3-i;for(var l=0;l<f;++l)if(o.getByte()!==0)throw{message:"Encryption block is invalid."}}else if(a===1){f=0;while(o.length()>1){if(o.getByte()!==255){--o.read;break}++f}}else if(a===2){f=0;while(o.length()>1){if(o.getByte()===0){--o.read;break}++f}}var c=o.getByte();if(c!==0||f!==s-3-o.length())throw{message:"Encryption block is invalid."};return o.getBytes()}function a(t,n,r){function c(){h(t.pBits,function(e,n){if(e)return r(e);t.p=n,h(t.qBits,p)})}function h(e,n){function p(){var n=e-1,r=new BigInteger(e,t.rng);return r.testBit(n)||r.bitwiseTo(BigInteger.ONE.shiftLeft(n),l,r),r.dAddOffset(31-r.mod(f).byteValue(),0),r}function v(i){if(d)return;--c;var s=i.data;if(s.found){for(var a=0;a<r.length;++a)r[a].terminate();return d=!0,n(null,new BigInteger(s.prime,16))}h.bitLength()>e&&(h=p());var f=h.toString(16);i.target.postMessage({e:t.eInt,hex:f,workLoad:o}),h.dAddOffset(u,0)}var r=[];for(var i=0;i<s;++i)r[i]=new Worker(a);var c=s,h=p();for(var i=0;i<s;++i)r[i].addEventListener("message",v);var d=!1}function p(n,i){t.q=i;if(t.p.compareTo(t.q)<0){var s=t.p;t.p=t.q,t.q=s}t.p1=t.p.subtract(BigInteger.ONE),t.q1=t.q.subtract(BigInteger.ONE),t.phi=t.p1.multiply(t.q1);if(t.phi.gcd(t.e).compareTo(BigInteger.ONE)!==0){t.p=t.q=null,c();return}t.n=t.p.multiply(t.q);if(t.n.bitLength()!==t.bits){t.q=null,h(t.qBits,p);return}var o=t.e.modInverse(t.phi);t.keys={privateKey:e.pki.rsa.setPrivateKey(t.n,t.e,o,t.p,t.q,o.mod(t.p1),o.mod(t.q1),t.q.modInverse(t.p)),publicKey:e.pki.rsa.setPublicKey(t.n,t.e)},r(null,t.keys)}typeof n=="function"&&(r=n,n={});if(typeof Worker=="undefined"){function i(){if(e.pki.rsa.stepKeyPairGenerationState(t,10))return r(null,t.keys);e.util.setImmediate(i)}return i()}var s=n.workers||2,o=n.workLoad||100,u=o*30/8,a=n.workerScript||"forge/prime.worker.js",f=new BigInteger(null);f.fromInt(30);var l=function(e,t){return e|t};c()}typeof BigInteger=="undefined"&&(BigInteger=e.jsbn.BigInteger);var t=e.asn1;e.pki=e.pki||{},e.pki.rsa=e.rsa=e.rsa||{};var n=e.pki,r=[6,4,2,4,2,4,6,2],i=function(n){var r;if(n.algorithm in e.pki.oids){r=e.pki.oids[n.algorithm];var i=t.oidToDer(r).getBytes(),s=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[]),o=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[]);o.value.push(t.create(t.Class.UNIVERSAL,t.Type.OID,!1,i)),o.value.push(t.create(t.Class.UNIVERSAL,t.Type.NULL,!1,""));var u=t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,n.digest().getBytes());return s.value.push(o),s.value.push(u),t.toDer(s).getBytes()}throw{message:"Unknown message digest algorithm.",algorithm:n.algorithm}},s=function(e,t,n){var r;if(n)r=e.modPow(t.e,t.n);else{t.dP||(t.dP=t.d.mod(t.p.subtract(BigInteger.ONE))),t.dQ||(t.dQ=t.d.mod(t.q.subtract(BigInteger.ONE))),t.qInv||(t.qInv=t.q.modInverse(t.p));var i=e.mod(t.p).modPow(t.dP,t.p),s=e.mod(t.q).modPow(t.dQ,t.q);while(i.compareTo(s)<0)i=i.add(t.p);r=i.subtract(s).multiply(t.qInv).mod(t.p).multiply(t.q).add(s)}return r};n.rsa.encrypt=function(t,n,r){var i=r,u,a=Math.ceil(n.n.bitLength()/8);r!==!1&&r!==!0?(i=r===2,u=o(t,n,r)):(u=e.util.createBuffer(),u.putBytes(t));var f=new BigInteger(u.toHex(),16),l=s(f,n,i),c=l.toString(16),h=e.util.createBuffer(),p=a-Math.ceil(c.length/2);while(p>0)h.putByte(0),--p;return h.putBytes(e.util.hexToBytes(c)),h.getBytes()},n.rsa.decrypt=function(t,n,r,i){var o=Math.ceil(n.n.bitLength()/8);if(t.length!==o)throw{message:"Encrypted message length is invalid.",length:t.length,expected:o};var a=new BigInteger(e.util.createBuffer(t).toHex(),16);if(a.compareTo(n.n)>=0)throw{message:"Encrypted message is invalid."};var f=s(a,n,r),l=f.toString(16),c=e.util.createBuffer(),h=o-Math.ceil(l.length/2);while(h>0)c.putByte(0),--h;return c.putBytes(e.util.hexToBytes(l)),i!==!1?u(c.getBytes(),n,r):c.getBytes()},n.rsa.createKeyPairGenerationState=function(t,n){typeof t=="string"&&(t=parseInt(t,10)),t=t||1024;var r={nextBytes:function(t){var n=e.random.getBytes(t.length);for(var r=0;r<t.length;++r)t[r]=n.charCodeAt(r)}},i={state:0,bits:t,rng:r,eInt:n||65537,e:new BigInteger(null),p:null,q:null,qBits:t>>1,pBits:t-(t>>1),pqState:0,num:null,keys:null};return i.e.fromInt(i.eInt),i},n.rsa.stepKeyPairGenerationState=function(t,n){var i=new BigInteger(null);i.fromInt(30);var s=0,o=function(e,t){return e|t},u=+(new Date),a,f=0;while(t.keys===null&&(n<=0||f<n)){if(t.state===0){var l=t.p===null?t.pBits:t.qBits,c=l-1;t.pqState===0?(t.num=new BigInteger(l,t.rng),t.num.testBit(c)||t.num.bitwiseTo(BigInteger.ONE.shiftLeft(c),o,t.num),t.num.dAddOffset(31-t.num.mod(i).byteValue(),0),s=0,++t.pqState):t.pqState===1?t.num.bitLength()>l?t.pqState=0:t.num.isProbablePrime(1)?++t.pqState:t.num.dAddOffset(r[s++%8],0):t.pqState===2?t.pqState=t.num.subtract(BigInteger.ONE).gcd(t.e).compareTo(BigInteger.ONE)===0?3:0:t.pqState===3&&(t.pqState=0,t.num.isProbablePrime(10)&&(t.p===null?t.p=t.num:t.q=t.num,t.p!==null&&t.q!==null&&++t.state),t.num=null)}else if(t.state===1)t.p.compareTo(t.q)<0&&(t.num=t.p,t.p=t.q,t.q=t.num),++t.state;else if(t.state===2)t.p1=t.p.subtract(BigInteger.ONE),t.q1=t.q.subtract(BigInteger.ONE),t.phi=t.p1.multiply(t.q1),++t.state;else if(t.state===3)t.phi.gcd(t.e).compareTo(BigInteger.ONE)===0?++t.state:(t.p=null,t.q=null,t.state=0);else if(t.state===4)t.n=t.p.multiply(t.q),t.n.bitLength()===t.bits?++t.state:(t.q=null,t.state=0);else if(t.state===5){var h=t.e.modInverse(t.phi);t.keys={privateKey:e.pki.rsa.setPrivateKey(t.n,t.e,h,t.p,t.q,h.mod(t.p1),h.mod(t.q1),t.q.modInverse(t.p)),publicKey:e.pki.rsa.setPublicKey(t.n,t.e)}}a=+(new Date),f+=a-u,u=a}return t.keys!==null},n.rsa.generateKeyPair=function(e,t,r,i){arguments.length===1?typeof e=="object"?(r=e,e=undefined):typeof e=="function"&&(i=e,e=undefined):arguments.length===2?(typeof e=="number"?typeof t=="function"?i=t:r=t:(r=e,i=t,e=undefined),t=undefined):arguments.length===3&&(typeof t=="number"?typeof r=="function"&&(i=r,r=undefined):(i=r,r=t,t=undefined)),r=r||{},e===undefined&&(e=r.bits||1024),t===undefined&&(t=r.e||65537);var s=n.rsa.createKeyPairGenerationState(e,t);if(!i)return n.rsa.stepKeyPairGenerationState(s,0),s.keys;a(s,r,i)},n.rsa.setPublicKey=function(r,i){var s={n:r,e:i};return s.encrypt=function(t,r,i){typeof r=="string"?r=r.toUpperCase():r===undefined&&(r="RSAES-PKCS1-V1_5");if(r==="RSAES-PKCS1-V1_5")r={encode:function(e,t,n){return o(e,t,2).getBytes()}};else if(r==="RSA-OAEP"||r==="RSAES-OAEP")r={encode:function(t,n){return e.pkcs1.encode_rsa_oaep(n,t,i)}};else{if(["RAW","NONE","NULL",null].indexOf(r)===-1)throw{message:'Unsupported encryption scheme: "'+r+'".'};r={encode:function(e){return e}}}var u=r.encode(t,s,!0);return n.rsa.encrypt(u,s,!0)},s.verify=function(e,r,i){typeof i=="string"?i=i.toUpperCase():i===undefined&&(i="RSASSA-PKCS1-V1_5");if(i==="RSASSA-PKCS1-V1_5")i={verify:function(e,n){n=u(n,s,!0);var r=t.fromDer(n);return e===r.value[1].value}};else if(i==="NONE"||i==="NULL"||i===null)i={verify:function(e,t){return t=u(t,s,!0),e===t}};var o=n.rsa.decrypt(r,s,!0,!1);return i.verify(e,o,s.n.bitLength())},s},n.rsa.setPrivateKey=function(t,r,s,o,a,f,l,c){var h={n:t,e:r,d:s,p:o,q:a,dP:f,dQ:l,qInv:c};return h.decrypt=function(t,r,i){typeof r=="string"?r=r.toUpperCase():r===undefined&&(r="RSAES-PKCS1-V1_5");var s=n.rsa.decrypt(t,h,!1,!1);if(r==="RSAES-PKCS1-V1_5")r={decode:u};else if(r==="RSA-OAEP"||r==="RSAES-OAEP")r={decode:function(t,n){return e.pkcs1.decode_rsa_oaep(n,t,i)}};else{if(["RAW","NONE","NULL",null].indexOf(r)===-1)throw{message:'Unsupported encryption scheme: "'+r+'".'};r={decode:function(e){return e}}}return r.decode(s,h,!1)},h.sign=function(e,t){var r=!1;typeof t=="string"&&(t=t.toUpperCase());if(t===undefined||t==="RSASSA-PKCS1-V1_5")t={encode:i},r=1;else if(t==="NONE"||t==="NULL"||t===null)t={encode:function(){return e}},r=1;var s=t.encode(e,h.n.bitLength());return n.rsa.encrypt(s,h,r)},h}}var t="rsa";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/rsa",["require","module","./asn1","./oids","./random","./util","./jsbn","./pkcs1"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){function w(n){var r=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[]),i,s,o=n.attributes;for(var u=0;u<o.length;++u){i=o[u];var a=i.value,f=t.Type.PRINTABLESTRING;"valueTagClass"in i&&(f=i.valueTagClass,f===t.Type.UTF8&&(a=e.util.encodeUtf8(a))),s=t.create(t.Class.UNIVERSAL,t.Type.SET,!0,[t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(i.type).getBytes()),t.create(t.Class.UNIVERSAL,f,!1,a)])]),r.value.push(s)}return r}function E(e){var n=t.create(t.Class.CONTEXT_SPECIFIC,3,!0,[]),r=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[]);n.value.push(r);var i,s;for(var o=0;o<e.length;++o){i=e[o],s=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[]),r.value.push(s),s.value.push(t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(i.id).getBytes())),i.critical&&s.value.push(t.create(t.Class.UNIVERSAL,t.Type.BOOLEAN,!1,String.fromCharCode(255)));var u=i.value;typeof i.value!="string"&&(u=t.toDer(u).getBytes()),s.value.push(t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,u))}return n}function S(e,n){switch(e){case r["RSASSA-PSS"]:var i=[];return n.hash.algorithmOid!==undefined&&i.push(t.create(t.Class.CONTEXT_SPECIFIC,0,!0,[t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.hash.algorithmOid).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.NULL,!1,"")])])),n.mgf.algorithmOid!==undefined&&i.push(t.create(t.Class.CONTEXT_SPECIFIC,1,!0,[t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.mgf.algorithmOid).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.mgf.hash.algorithmOid).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.NULL,!1,"")])])])),n.saltLength!==undefined&&i.push(t.create(t.Class.CONTEXT_SPECIFIC,2,!0,[t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,String.fromCharCode(n.saltLength))])),t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,i);default:return t.create(t.Class.UNIVERSAL,t.Type.NULL,!1,"")}}function x(n){var r=t.create(t.Class.CONTEXT_SPECIFIC,0,!0,[]);if(n.attributes.length===0)return r;var i=n.attributes;for(var s=0;s<i.length;++s){var o=i[s],u=o.value,a=t.Type.UTF8;"valueTagClass"in o&&(a=o.valueTagClass),a===t.Type.UTF8&&(u=e.util.encodeUtf8(u));var f=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(o.type).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.SET,!0,[t.create(t.Class.UNIVERSAL,a,!1,u)])]);r.value.push(f)}return r}function T(e,t,n){var r=[N(e+t)];for(var i=16,s=1;i<n;++s,i+=16)r.push(N(r[s-1]+e+t));return r.join("").substr(0,n)}function N(t){return e.md.md5.create().update(t).digest().getBytes()}typeof BigInteger=="undefined"&&(BigInteger=e.jsbn.BigInteger);var t=e.asn1,n=e.pki=e.pki||{},r=n.oids;n.pbe={};var i={};i.CN=r.commonName,i.commonName="CN",i.C=r.countryName,i.countryName="C",i.L=r.localityName,i.localityName="L",i.ST=r.stateOrProvinceName,i.stateOrProvinceName="ST",i.O=r.organizationName,i.organizationName="O",i.OU=r.organizationalUnitName,i.organizationalUnitName="OU",i.E=r.emailAddress,i.emailAddress="E";var s={name:"SubjectPublicKeyInfo",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,captureAsn1:"subjectPublicKeyInfo",value:[{name:"SubjectPublicKeyInfo.AlgorithmIdentifier",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"AlgorithmIdentifier.algorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"publicKeyOid"}]},{name:"SubjectPublicKeyInfo.subjectPublicKey",tagClass:t.Class.UNIVERSAL,type:t.Type.BITSTRING,constructed:!1,value:[{name:"SubjectPublicKeyInfo.subjectPublicKey.RSAPublicKey",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,optional:!0,captureAsn1:"rsaPublicKey"}]}]},o={name:"RSAPublicKey",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"RSAPublicKey.modulus",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"publicKeyModulus"},{name:"RSAPublicKey.exponent",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"publicKeyExponent"}]},u={name:"Certificate",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"Certificate.TBSCertificate",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,captureAsn1:"tbsCertificate",value:[{name:"Certificate.TBSCertificate.version",tagClass:t.Class.CONTEXT_SPECIFIC,type:0,constructed:!0,optional:!0,value:[{name:"Certificate.TBSCertificate.version.integer",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"certVersion"}]},{name:"Certificate.TBSCertificate.serialNumber",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"certSerialNumber"},{name:"Certificate.TBSCertificate.signature",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"Certificate.TBSCertificate.signature.algorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"certinfoSignatureOid"},{name:"Certificate.TBSCertificate.signature.parameters",tagClass:t.Class.UNIVERSAL,optional:!0,captureAsn1:"certinfoSignatureParams"}]},{name:"Certificate.TBSCertificate.issuer",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,captureAsn1:"certIssuer"},{name:"Certificate.TBSCertificate.validity",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"Certificate.TBSCertificate.validity.notBefore (utc)",tagClass:t.Class.UNIVERSAL,type:t.Type.UTCTIME,constructed:!1,optional:!0,capture:"certValidity1UTCTime"},{name:"Certificate.TBSCertificate.validity.notBefore (generalized)",tagClass:t.Class.UNIVERSAL,type:t.Type.GENERALIZEDTIME,constructed:!1,optional:!0,capture:"certValidity2GeneralizedTime"},{name:"Certificate.TBSCertificate.validity.notAfter (utc)",tagClass:t.Class.UNIVERSAL,type:t.Type.UTCTIME,constructed:!1,optional:!0,capture:"certValidity3UTCTime"},{name:"Certificate.TBSCertificate.validity.notAfter (generalized)",tagClass:t.Class.UNIVERSAL,type:t.Type.GENERALIZEDTIME,constructed:!1,optional:!0,capture:"certValidity4GeneralizedTime"}]},{name:"Certificate.TBSCertificate.subject",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,captureAsn1:"certSubject"},s,{name:"Certificate.TBSCertificate.issuerUniqueID",tagClass:t.Class.CONTEXT_SPECIFIC,type:1,constructed:!0,optional:!0,value:[{name:"Certificate.TBSCertificate.issuerUniqueID.id",tagClass:t.Class.UNIVERSAL,type:t.Type.BITSTRING,constructed:!1,capture:"certIssuerUniqueId"}]},{name:"Certificate.TBSCertificate.subjectUniqueID",tagClass:t.Class.CONTEXT_SPECIFIC,type:2,constructed:!0,optional:!0,value:[{name:"Certificate.TBSCertificate.subjectUniqueID.id",tagClass:t.Class.UNIVERSAL,type:t.Type.BITSTRING,constructed:!1,capture:"certSubjectUniqueId"}]},{name:"Certificate.TBSCertificate.extensions",tagClass:t.Class.CONTEXT_SPECIFIC,type:3,constructed:!0,captureAsn1:"certExtensions",optional:!0}]},{name:"Certificate.signatureAlgorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"Certificate.signatureAlgorithm.algorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"certSignatureOid"},{name:"Certificate.TBSCertificate.signature.parameters",tagClass:t.Class.UNIVERSAL,optional:!0,captureAsn1:"certSignatureParams"}]},{name:"Certificate.signatureValue",tagClass:t.Class.UNIVERSAL,type:t.Type.BITSTRING,constructed:!1,capture:"certSignature"}]},a={name:"PrivateKeyInfo",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"PrivateKeyInfo.version",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"privateKeyVersion"},{name:"PrivateKeyInfo.privateKeyAlgorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"AlgorithmIdentifier.algorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"privateKeyOid"}]},{name:"PrivateKeyInfo",tagClass:t.Class.UNIVERSAL,type:t.Type.OCTETSTRING,constructed:!1,capture:"privateKey"}]},f={name:"RSAPrivateKey",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"RSAPrivateKey.version",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"privateKeyVersion"},{name:"RSAPrivateKey.modulus",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"privateKeyModulus"},{name:"RSAPrivateKey.publicExponent",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"privateKeyPublicExponent"},{name:"RSAPrivateKey.privateExponent",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"privateKeyPrivateExponent"},{name:"RSAPrivateKey.prime1",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"privateKeyPrime1"},{name:"RSAPrivateKey.prime2",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"privateKeyPrime2"},{name:"RSAPrivateKey.exponent1",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"privateKeyExponent1"},{name:"RSAPrivateKey.exponent2",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"privateKeyExponent2"},{name:"RSAPrivateKey.coefficient",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"privateKeyCoefficient"}]},l={name:"EncryptedPrivateKeyInfo",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"EncryptedPrivateKeyInfo.encryptionAlgorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"AlgorithmIdentifier.algorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"encryptionOid"},{name:"AlgorithmIdentifier.parameters",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,captureAsn1:"encryptionParams"}]},{name:"EncryptedPrivateKeyInfo.encryptedData",tagClass:t.Class.UNIVERSAL,type:t.Type.OCTETSTRING,constructed:!1,capture:"encryptedData"}]},c={name:"PBES2Algorithms",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"PBES2Algorithms.keyDerivationFunc",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"PBES2Algorithms.keyDerivationFunc.oid",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"kdfOid"},{name:"PBES2Algorithms.params",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"PBES2Algorithms.params.salt",tagClass:t.Class.UNIVERSAL,type:t.Type.OCTETSTRING,constructed:!1,capture:"kdfSalt"},{name:"PBES2Algorithms.params.iterationCount",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,onstructed:!0,capture:"kdfIterationCount"}]}]},{name:"PBES2Algorithms.encryptionScheme",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"PBES2Algorithms.encryptionScheme.oid",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"encOid"},{name:"PBES2Algorithms.encryptionScheme.iv",tagClass:t.Class.UNIVERSAL,type:t.Type.OCTETSTRING,constructed:!1,capture:"encIv"}]}]},h={name:"pkcs-12PbeParams",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"pkcs-12PbeParams.salt",tagClass:t.Class.UNIVERSAL,type:t.Type.OCTETSTRING,constructed:!1,capture:"salt"},{name:"pkcs-12PbeParams.iterations",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"iterations"}]},p={name:"rsapss",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"rsapss.hashAlgorithm",tagClass:t.Class.CONTEXT_SPECIFIC,type:0,constructed:!0,value:[{name:"rsapss.hashAlgorithm.AlgorithmIdentifier",tagClass:t.Class.UNIVERSAL,type:t.Class.SEQUENCE,constructed:!0,optional:!0,value:[{name:"rsapss.hashAlgorithm.AlgorithmIdentifier.algorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"hashOid"}]}]},{name:"rsapss.maskGenAlgorithm",tagClass:t.Class.CONTEXT_SPECIFIC,type:1,constructed:!0,value:[{name:"rsapss.maskGenAlgorithm.AlgorithmIdentifier",tagClass:t.Class.UNIVERSAL,type:t.Class.SEQUENCE,constructed:!0,optional:!0,value:[{name:"rsapss.maskGenAlgorithm.AlgorithmIdentifier.algorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"maskGenOid"},{name:"rsapss.maskGenAlgorithm.AlgorithmIdentifier.params",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"rsapss.maskGenAlgorithm.AlgorithmIdentifier.params.algorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"maskGenHashOid"}]}]}]},{name:"rsapss.saltLength",tagClass:t.Class.CONTEXT_SPECIFIC,type:2,optional:!0,value:[{name:"rsapss.saltLength.saltLength",tagClass:t.Class.UNIVERSAL,type:t.Class.INTEGER,constructed:!1,capture:"saltLength"}]},{name:"rsapss.trailerField",tagClass:t.Class.CONTEXT_SPECIFIC,type:3,optional:!0,value:[{name:"rsapss.trailer.trailer",tagClass:t.Class.UNIVERSAL,type:t.Class.INTEGER,constructed:!1,capture:"trailer"}]}]},d={name:"CertificationRequestInfo",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,captureAsn1:"certificationRequestInfo",value:[{name:"CertificationRequestInfo.integer",tagClass:t.Class.UNIVERSAL,type:t.Type.INTEGER,constructed:!1,capture:"certificationRequestInfoVersion"},{name:"CertificationRequestInfo.subject",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,captureAsn1:"certificationRequestInfoSubject"},s,{name:"CertificationRequestInfo.attributes",tagClass:t.Class.CONTEXT_SPECIFIC,type:0,constructed:!0,optional:!0,capture:"certificationRequestInfoAttributes",value:[{name:"CertificationRequestInfo.attributes",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"CertificationRequestInfo.attributes.type",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1},{name:"CertificationRequestInfo.attributes.value",tagClass:t.Class.UNIVERSAL,type:t.Type.SET,constructed:!0}]}]}]},v={name:"CertificationRequest",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,captureAsn1:"csr",value:[d,{name:"CertificationRequest.signatureAlgorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.SEQUENCE,constructed:!0,value:[{name:"CertificationRequest.signatureAlgorithm.algorithm",tagClass:t.Class.UNIVERSAL,type:t.Type.OID,constructed:!1,capture:"csrSignatureOid"},{name:"CertificationRequest.signatureAlgorithm.parameters",tagClass:t.Class.UNIVERSAL,optional:!0,captureAsn1:"csrSignatureParams"}]},{name:"CertificationRequest.signature",tagClass:t.Class.UNIVERSAL,type:t.Type.BITSTRING,constructed:!1,capture:"csrSignature"}]};n.RDNAttributesAsArray=function(e,n){var s=[],o,u,a;for(var f=0;f<e.value.length;++f){o=e.value[f];for(var l=0;l<o.value.length;++l)a={},u=o.value[l],a.type=t.derToOid(u.value[0].value),a.value=u.value[1].value,a.valueTagClass=u.value[1].type,a.type in r&&(a.name=r[a.type],a.name in i&&(a.shortName=i[a.name])),n&&(n.update(a.type),n.update(a.value)),s.push(a)}return s},n.CRIAttributesAsArray=function(e){var n=[];for(var s=0;s<e.length;++s){var o=e[s],u=t.derToOid(o.value[0].value),a=o.value[1].value;for(var f=0;f<a.length;++f){var l={};l.type=u,l.value=a[f].value,l.valueTagClass=a[f].type,l.type in r&&(l.name=r[l.type],l.name in i&&(l.shortName=i[l.name])),n.push(l)}}return n};var m=function(e,t){typeof t=="string"&&(t={shortName:t});var n=null,r;for(var i=0;n===null&&i<e.attributes.length;++i)r=e.attributes[i],t.type&&t.type===r.type?n=r:t.name&&t.name===r.name?n=r:t.shortName&&t.shortName===r.shortName&&(n=r);return n},g=function(n){var i=[],s,o,u;for(var a=0;a<n.value.length;++a){u=n.value[a];for(var f=0;f<u.value.length;++f){o=u.value[f],s={},s.id=t.derToOid(o.value[0].value),s.critical=!1,o.value[1].type===t.Type.BOOLEAN?(s.critical=o.value[1].value.charCodeAt(0)!==0,s.value=o.value[2].value):s.value=o.value[1].value;if(s.id in r){s.name=r[s.id];if(s.name==="keyUsage"){var l=t.fromDer(s.value),c=0,h=0;l.value.length>1&&(c=l.value.charCodeAt(1),h=l.value.length>2?l.value.charCodeAt(2):0),s.digitalSignature=(c&128)===128,s.nonRepudiation=(c&64)===64,s.keyEncipherment=(c&32)===32,s.dataEncipherment=(c&16)===16,s.keyAgreement=(c&8)===8,s.keyCertSign=(c&4)===4,s.cRLSign=(c&2)===2,s.encipherOnly=(c&1)===1,s.decipherOnly=(h&128)===128}else if(s.name==="basicConstraints"){var l=t.fromDer(s.value);l.value.length>0?s.cA=l.value[0].value.charCodeAt(0)!==0:s.cA=!1;if(l.value.length>1){var p=e.util.createBuffer(l.value[1].value);s.pathLenConstraint=p.getInt(p.length()<<3)}}else if(s.name==="extKeyUsage"){var l=t.fromDer(s.value);for(var d=0;d<l.value.length;++d){var v=t.derToOid(l.value[d].value);v in r?s[r[v]]=!0:s[v]=!0}}else if(s.name==="nsCertType"){var l=t.fromDer(s.value),c=0;l.value.length>1&&(c=l.value.charCodeAt(1)),s.client=(c&128)===128,s.server=(c&64)===64,s.email=(c&32)===32,s.objsign=(c&16)===16,s.reserved=(c&8)===8,s.sslCA=(c&4)===4,s.emailCA=(c&2)===2,s.objCA=(c&1)===1}else if(s.name==="subjectAltName"||s.name==="issuerAltName"){s.altNames=[];var m,l=t.fromDer(s.value);for(var g=0;g<l.value.length;++g){m=l.value[g];var y={type:m.type,value:m.value};s.altNames.push(y);switch(m.type){case 1:case 2:case 6:break;case 7:break;case 8:y.oid=t.derToOid(m.value);break;default:}}}else if(s.name==="subjectKeyIdentifier"){var l=t.fromDer(s.value);s.subjectKeyIdentifier=e.util.bytesToHex(l.value)}}i.push(s)}}return i};n.pemToDer=function(t){var n=e.pem.decode(t)[0];if(n.procType&&n.procType.type==="ENCRYPTED")throw{message:"Could not convert PEM to DER; PEM is encrypted."};return e.util.createBuffer(n.body)};var y=function(t){var n=t.toString(16);return n[0]>="8"&&(n="00"+n),e.util.hexToBytes(n)},b=function(e,n,i){var s={};if(e!==r["RSASSA-PSS"])return s;i&&(s={hash:{algorithmOid:r.sha1},mgf:{algorithmOid:r.mgf1,hash:{algorithmOid:r.sha1}},saltLength:20});var o={},u=[];if(!t.validate(n,p,o,u))throw{message:"Cannot read RSASSA-PSS parameter block.",errors:u};return o.hashOid!==undefined&&(s.hash=s.hash||{},s.hash.algorithmOid=t.derToOid(o.hashOid)),o.maskGenOid!==undefined&&(s.mgf=s.mgf||{},s.mgf.algorithmOid=t.derToOid(o.maskGenOid),s.mgf.hash=s.mgf.hash||{},s.mgf.hash.algorithmOid=t.derToOid(o.maskGenHashOid)),o.saltLength!==undefined&&(s.saltLength=o.saltLength.charCodeAt(0)),s};n.certificateFromPem=function(r,i,s){var o=e.pem.decode(r)[0];if(o.type!=="CERTIFICATE"&&o.type!=="X509 CERTIFICATE"&&o.type!=="TRUSTED CERTIFICATE")throw{message:'Could not convert certificate from PEM; PEM header type is not "CERTIFICATE", "X509 CERTIFICATE", or "TRUSTED CERTIFICATE".',headerType:o.type};if(o.procType&&o.procType.type==="ENCRYPTED")throw{message:"Could not convert certificate from PEM; PEM is encrypted."};var u=t.fromDer(o.body,s);return n.certificateFromAsn1(u,i)},n.certificateToPem=function(r,i){var s={type:"CERTIFICATE",body:t.toDer(n.certificateToAsn1(r)).getBytes()};return e.pem.encode(s,{maxline:i})},n.publicKeyFromPem=function(r){var i=e.pem.decode(r)[0];if(i.type!=="PUBLIC KEY"&&i.type!=="RSA PUBLIC KEY")throw{message:'Could not convert public key from PEM; PEM header type is not "PUBLIC KEY" or "RSA PUBLIC KEY".',headerType:i.type};if(i.procType&&i.procType.type==="ENCRYPTED")throw{message:"Could not convert public key from PEM; PEM is encrypted."};var s=t.fromDer(i.body);return n.publicKeyFromAsn1(s)},n.publicKeyToPem=function(r,i){var s={type:"PUBLIC KEY",body:t.toDer(n.publicKeyToAsn1(r)).getBytes()};return e.pem.encode(s,{maxline:i})},n.publicKeyToRSAPublicKeyPem=function(r,i){var s={type:"RSA PUBLIC KEY",body:t.toDer(n.publicKeyToRSAPublicKey(r)).getBytes()};return e.pem.encode(s,{maxline:i})},n.privateKeyFromPem=function(r){var i=e.pem.decode(r)[0];if(i.type!=="PRIVATE KEY"&&i.type!=="RSA PRIVATE KEY")throw{message:'Could not convert private key from PEM; PEM header type is not "PRIVATE KEY" or "RSA PRIVATE KEY".',headerType:i.type};if(i.procType&&i.procType.type==="ENCRYPTED")throw{message:"Could not convert private key from PEM; PEM is encrypted."};var s=t.fromDer(i.body);return n.privateKeyFromAsn1(s)},n.privateKeyToPem=function(r,i){var s={type:"RSA PRIVATE KEY",body:t.toDer(n.privateKeyToAsn1(r)).getBytes()};return e.pem.encode(s,{maxline:i})},n.certificationRequestFromPem=function(r,i,s){var o=e.pem.decode(r)[0];if(o.type!=="CERTIFICATE REQUEST")throw{message:'Could not convert certification request from PEM; PEM header type is not "CERTIFICATE REQUEST".',headerType:o.type};if(o.procType&&o.procType.type==="ENCRYPTED")throw{message:"Could not convert certification request from PEM; PEM is encrypted."};var u=t.fromDer(o.body,s);return n.certificationRequestFromAsn1(u,i)},n.certificationRequestToPem=function(r,i){var s={type:"CERTIFICATE REQUEST",body:t.toDer(n.certificationRequestToAsn1(r)).getBytes()};return e.pem.encode(s,{maxline:i})},n.createCertificate=function(){var s={};s.version=2,s.serialNumber="00",s.signatureOid=null,s.signature=null,s.siginfo={},s.siginfo.algorithmOid=null,s.validity={},s.validity.notBefore=new Date,s.validity.notAfter=new Date,s.issuer={},s.issuer.getField=function(e){return m(s.issuer,e)},s.issuer.addField=function(e){o([e]),s.issuer.attributes.push(e)},s.issuer.attributes=[],s.issuer.hash=null,s.subject={},s.subject.getField=function(e){return m(s.subject,e)},s.subject.addField=function(e){o([e]),s.subject.attributes.push(e)},s.subject.attributes=[],s.subject.hash=null,s.extensions=[],s.publicKey=null,s.md=null;var o=function(e){var t;for(var r=0;r<e.length;++r){t=e[r],typeof t.name=="undefined"&&(t.type&&t.type in n.oids?t.name=n.oids[t.type]:t.shortName&&t.shortName in i&&(t.name=n.oids[i[t.shortName]]));if(typeof t.type=="undefined"){if(!(t.name&&t.name in n.oids))throw{message:"Attribute type not specified.",attribute:t};t.type=n.oids[t.name]}typeof t.shortName=="undefined"&&t.name&&t.name in i&&(t.shortName=i[t.name]);if(typeof t.value=="undefined")throw{message:"Attribute value not specified.",attribute:t}}};return s.setSubject=function(e,t){o(e),s.subject.attributes=e,delete s.subject.uniqueId,t&&(s.subject.uniqueId=t),s.subject.hash=null},s.setIssuer=function(e,t){o(e),s.issuer.attributes=e,delete s.issuer.uniqueId,t&&(s.issuer.uniqueId=t),s.issuer.hash=null},s.setExtensions=function(i){var o;for(var u=0;u<i.length;++u){o=i[u],typeof o.name=="undefined"&&o.id&&o.id in n.oids&&(o.name=n.oids[o.id]);if(typeof o.id=="undefined"){if(!(o.name&&o.name in n.oids))throw{message:"Extension ID not specified.",extension:o};o.id=n.oids[o.name]}if(typeof o.value=="undefined"){if(o.name==="keyUsage"){var a=0,f=0,l=0;o.digitalSignature&&(f|=128,a=7),o.nonRepudiation&&(f|=64,a=6),o.keyEncipherment&&(f|=32,a=5),o.dataEncipherment&&(f|=16,a=4),o.keyAgreement&&(f|=8,a=3),o.keyCertSign&&(f|=4,a=2),o.cRLSign&&(f|=2,a=1),o.encipherOnly&&(f|=1,a=0),o.decipherOnly&&(l|=128,a=7);var c=String.fromCharCode(a);l!==0?c+=String.fromCharCode(f)+String.fromCharCode(l):f!==0&&(c+=String.fromCharCode(f)),o.value=t.create(t.Class.UNIVERSAL,t.Type.BITSTRING,!1,c)}else if(o.name==="basicConstraints"){o.value=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[]),o.cA&&o.value.value.push(t.create(t.Class.UNIVERSAL,t.Type.BOOLEAN,!1,String.fromCharCode(255)));if(o.pathLenConstraint){var h=o.pathLenConstraint,p=e.util.createBuffer();p.putInt(h,h.toString(2).length),o.value.value.push(t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,p.getBytes()))}}else if(o.name==="extKeyUsage"){o.value=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[]);var d=o.value.value;for(var v in o){if(o[v]!==!0)continue;v in r?d.push(t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(r[v]).getBytes())):v.indexOf(".")!==-1&&d.push(t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(v).getBytes()))}}else if(o.name==="nsCertType"){var a=0,f=0;o.client&&(f|=128,a=7),o.server&&(f|=64,a=6),o.email&&(f|=32,a=5),o.objsign&&(f|=16,a=4),o.reserved&&(f|=8,a=3),o.sslCA&&(f|=4,a=2),o.emailCA&&(f|=2,a=1),o.objCA&&(f|=1,a=0);var c=String.fromCharCode(a);f!==0&&(c+=String.fromCharCode(f)),o.value=t.create(t.Class.UNIVERSAL,t.Type.BITSTRING,!1,c)}else if(o.name==="subjectAltName"||o.name==="issuerAltName"){o.value=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[]);var m;for(var g=0;g<o.altNames.length;++g){m=o.altNames[g];var c=m.value;m.type===8&&(c=t.oidToDer(c)),o.value.value.push(t.create(t.Class.CONTEXT_SPECIFIC,m.type,!1,c))}}else if(o.name==="subjectKeyIdentifier"){var y=s.generateSubjectKeyIdentifier();o.subjectKeyIdentifier=y.toHex(),o.value=t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,y.getBytes())}if(typeof o.value=="undefined")throw{message:"Extension value not specified.",extension:o}}}s.extensions=i},s.getExtension=function(e){typeof e=="string"&&(e={name:e});var t=null,n;for(var r=0;t===null&&r<s.extensions.length;++r)n=s.extensions[r],e.id&&n.id===e.id?t=n:e.name&&n.name===e.name&&(t=n);return t},s.sign=function(i,o){s.md=o||e.md.sha1.create();var u=r[s.md.algorithm+"WithRSAEncryption"];if(!u)throw{message:"Could not compute certificate digest. Unknown message digest algorithm OID.",algorithm:s.md.algorithm};s.signatureOid=s.siginfo.algorithmOid=u,s.tbsCertificate=n.getTBSCertificate(s);var a=t.toDer(s.tbsCertificate);s.md.update(a.getBytes()),s.signature=i.sign(s.md)},s.verify=function(i){var o=!1,u=i.md;if(u===null){if(i.signatureOid in r){var a=r[i.signatureOid];switch(a){case"sha1WithRSAEncryption":u=e.md.sha1.create();break;case"md5WithRSAEncryption":u=e.md.md5.create();break;case"sha256WithRSAEncryption":u=e.md.sha256.create();break;case"RSASSA-PSS":u=e.md.sha256.create()}}if(u===null)throw{message:"Could not compute certificate digest. Unknown signature OID.",signatureOid:i.signatureOid};var f=i.tbsCertificate||n.getTBSCertificate(i),l=t.toDer(f);u.update(l.getBytes())}if(u!==null){var c=undefined;switch(i.signatureOid){case r.sha1WithRSAEncryption:c=undefined;break;case r["RSASSA-PSS"]:var h,p;h=r[i.signatureParameters.mgf.hash.algorithmOid];if(h===undefined||e.md[h]===undefined)throw{message:"Unsupported MGF hash function.",oid:i.signatureParameters.mgf.hash.algorithmOid,name:h};p=r[i.signatureParameters.mgf.algorithmOid];if(p===undefined||e.mgf[p]===undefined)throw{message:"Unsupported MGF function.",oid:i.signatureParameters.mgf.algorithmOid,name:p};p=e.mgf[p].create(e.md[h].create()),h=r[i.signatureParameters.hash.algorithmOid];if(h===undefined||e.md[h]===undefined)throw{message:"Unsupported RSASSA-PSS hash function.",oid:i.signatureParameters.hash.algorithmOid,name:h};c=e.pss.create(e.md[h].create(),p,i.signatureParameters.saltLength)}o=s.publicKey.verify(u.digest().getBytes(),i.signature,c)}return o},s.isIssuer=function(e){var t=!1,n=s.issuer,r=e.subject;if(n.hash&&r.hash)t=n.hash===r.hash;else if(n.attributes.length===r.attributes.length){t=!0;var i,o;for(var u=0;t&&u<n.attributes.length;++u){i=n.attributes[u],o=r.attributes[u];if(i.type!==o.type||i.value!==o.value)t=!1}}return t},s.generateSubjectKeyIdentifier=function(){var r=t.toDer(n.publicKeyToRSAPublicKey(s.publicKey)),i=e.md.sha1.create();return i.update(r.getBytes()),i.digest()},s.verifySubjectKeyIdentifier=function(){var t=!1,n=r.subjectKeyIdentifier;for(var i=0;i<s.extensions.length;++i){var o=s.extensions[i];if(o.id===n){var u=s.generateSubjectKeyIdentifier().getBytes();return e.util.hexToBytes(o.subjectKeyIdentifier)===u}}return!1},s},n.certificateFromAsn1=function(i,s){var o={},a=[];if(!t.validate(i,u,o,a))throw{message:"Cannot read X.509 certificate. ASN.1 object is not an X509v3 Certificate.",errors:a};if(typeof o.certSignature!="string"){var f="\0";for(var l=0;l<o.certSignature.length;++l)f+=t.toDer(o.certSignature[l]).getBytes();o.certSignature=f}var c=t.derToOid(o.publicKeyOid);if(c!==n.oids.rsaEncryption)throw{message:"Cannot read public key. OID is not RSA."};var h=n.createCertificate();h.version=o.certVersion?o.certVersion.charCodeAt(0):0;var p=e.util.createBuffer(o.certSerialNumber);h.serialNumber=p.toHex(),h.signatureOid=e.asn1.derToOid(o.certSignatureOid),h.signatureParameters=b(h.signatureOid,o.certSignatureParams,!0),h.siginfo.algorithmOid=e.asn1.derToOid(o.certinfoSignatureOid),h.siginfo.parameters=b(h.siginfo.algorithmOid,o.certinfoSignatureParams,!1);var d=e.util.createBuffer(o.certSignature);++d.read,h.signature=d.getBytes();var v=[];o.certValidity1UTCTime!==undefined&&v.push(t.utcTimeToDate(o.certValidity1UTCTime)),o.certValidity2GeneralizedTime!==undefined&&v.push(t.generalizedTimeToDate(o.certValidity2GeneralizedTime)),o.certValidity3UTCTime!==undefined&&v.push(t.utcTimeToDate(o.certValidity3UTCTime)),o.certValidity4GeneralizedTime!==undefined&&v.push(t.generalizedTimeToDate(o.certValidity4GeneralizedTime));if(v.length>2)throw{message:"Cannot read notBefore/notAfter validity times; more than two times were provided in the certificate."};if(v.length<2)throw{message:"Cannot read notBefore/notAfter validity times; they were not provided as either UTCTime or GeneralizedTime."};h.validity.notBefore=v[0],h.validity.notAfter=v[1],h.tbsCertificate=o.tbsCertificate;if(s){h.md=null;if(h.signatureOid in r){var c=r[h.signatureOid];switch(c){case"sha1WithRSAEncryption":h.md=e.md.sha1.create();break;case"md5WithRSAEncryption":h.md=e.md.md5.create();break;case"sha256WithRSAEncryption":h.md=e.md.sha256.create();break;case"RSASSA-PSS":h.md=e.md.sha256.create()}}if(h.md===null)throw{message:"Could not compute certificate digest. Unknown signature OID.",signatureOid:h.signatureOid};var y=t.toDer(h.tbsCertificate);h.md.update(y.getBytes())}var w=e.md.sha1.create();h.issuer.getField=function(e){return m(h.issuer,e)},h.issuer.addField=function(e){_fillMissingFields([e]),h.issuer.attributes.push(e)},h.issuer.attributes=n.RDNAttributesAsArray(o.certIssuer,w),o.certIssuerUniqueId&&(h.issuer.uniqueId=o.certIssuerUniqueId),h.issuer.hash=w.digest().toHex();var E=e.md.sha1.create();return h.subject.getField=function(e){return m(h.subject,e)},h.subject.addField=function(e){_fillMissingFields([e]),h.subject.attributes.push(e)},h.subject.attributes=n.RDNAttributesAsArray(o.certSubject,E),o.certSubjectUniqueId&&(h.subject.uniqueId=o.certSubjectUniqueId),h.subject.hash=E.digest().toHex(),o.certExtensions?h.extensions=g(o.certExtensions):h.extensions=[],h.publicKey=n.publicKeyFromAsn1(o.subjectPublicKeyInfo),h},n.certificationRequestFromAsn1=function(i,s){var o={},u=[];if(!t.validate(i,v,o,u))throw{message:"Cannot read PKCS#10 certificate request. ASN.1 object is not a PKCS#10 CertificationRequest.",errors:u};if(typeof o.csrSignature!="string"){var a="\0";for(var f=0;f<o.csrSignature.length;++f)a+=t.toDer(o.csrSignature[f]).getBytes();o.csrSignature=a}var l=t.derToOid(o.publicKeyOid);if(l!==n.oids.rsaEncryption)throw{message:"Cannot read public key. OID is not RSA."};var c=n.createCertificationRequest();c.version=o.csrVersion?o.csrVersion.charCodeAt(0):0,c.signatureOid=e.asn1.derToOid(o.csrSignatureOid),c.signatureParameters=b(c.signatureOid,o.csrSignatureParams,!0),c.siginfo.algorithmOid=e.asn1.derToOid(o.csrSignatureOid),c.siginfo.parameters=b(c.siginfo.algorithmOid,o.csrSignatureParams,!1);var h=e.util.createBuffer(o.csrSignature);++h.read,c.signature=h.getBytes(),c.certificationRequestInfo=o.certificationRequestInfo;if(s){c.md=null;if(c.signatureOid in r){var l=r[c.signatureOid];switch(l){case"sha1WithRSAEncryption":c.md=e.md.sha1.create();break;case"md5WithRSAEncryption":c.md=e.md.md5.create();break;case"sha256WithRSAEncryption":c.md=e.md.sha256.create();break;case"RSASSA-PSS":c.md=e.md.sha256.create()}}if(c.md===null)throw{message:"Could not compute certification request digest. Unknown signature OID.",signatureOid:c.signatureOid};var p=t.toDer(c.certificationRequestInfo);c.md.update(p.getBytes())}var d=e.md.sha1.create();return c.subject.getField=function(e){return m(c.subject,e)},c.subject.addField=function(e){_fillMissingFields([e]),c.subject.attributes.push(e)},c.subject.attributes=n.RDNAttributesAsArray(o.certificationRequestInfoSubject,d),c.subject.hash=d.digest().toHex(),c.publicKey=n.publicKeyFromAsn1(o.subjectPublicKeyInfo),c.getAttribute=function(e){return m(c.attributes,e)},c.addAttribute=function(e){_fillMissingFields([e]),c.attributes.push(e)},c.attributes=n.CRIAttributesAsArray(o.certificationRequestInfoAttributes),c},n.createCertificationRequest=function(){var s={};s.version=0,s.signatureOid=null,s.signature=null,s.siginfo={},s.siginfo.algorithmOid=null,s.subject={},s.subject.getField=function(e){return m(s.subject,e)},s.subject.addField=function(e){o([e]),s.subject.attributes.push(e)},s.subject.attributes=[],s.subject.hash=null,s.publicKey=null,s.attributes=[],s.getAttribute=function(e){return m(s.attributes,e)},s.addAttribute=function(e){o([e]),s.attributes.push(e)},s.md=null;var o=function(e){var t;for(var r=0;r<e.length;++r){t=e[r],typeof t.name=="undefined"&&(t.type&&t.type in n.oids?t.name=n.oids[t.type]:t.shortName&&t.shortName in i&&(t.name=n.oids[i[t.shortName]]));if(typeof t.type=="undefined"){if(!(t.name&&t.name in n.oids))throw{message:"Attribute type not specified.",attribute:t};t.type=n.oids[t.name]}typeof t.shortName=="undefined"&&t.name&&t.name in i&&(t.shortName=i[t.name]);if(typeof t.value=="undefined")throw{message:"Attribute value not specified.",attribute:t}}};return s.setSubject=function(e){o(e),s.subject.attributes=e,s.subject.hash=null},s.setAttributes=function(e){o(e),s.attributes=e},s.sign=function(i,o){s.md=o||e.md.sha1.create();var u=r[s.md.algorithm+"WithRSAEncryption"];if(!u)throw{message:"Could not compute certification request digest. Unknown message digest algorithm OID.",algorithm:s.md.algorithm};s.signatureOid=s.siginfo.algorithmOid=u,s.certificationRequestInfo=n.getCertificationRequestInfo(s);var a=t.toDer(s.certificationRequestInfo);s.md.update(a.getBytes()),s.signature=i.sign(s.md)},s.verify=function(){var i=!1,o=s.md;if(o===null){if(s.signatureOid in r){var u=r[s.signatureOid];switch(u){case"sha1WithRSAEncryption":o=e.md.sha1.create();break;case"md5WithRSAEncryption":o=e.md.md5.create();break;case"sha256WithRSAEncryption":o=e.md.sha256.create();break;case"RSASSA-PSS":o=e.md.sha256.create()}}if(o===null)throw{message:"Could not compute certification request digest. Unknown signature OID.",signatureOid:s.signatureOid};var a=s.certificationRequestInfo||n.getCertificationRequestInfo(s),f=t.toDer(a);o.update(f.getBytes())}if(o!==null){var l=undefined;switch(s.signatureOid){case r.sha1WithRSAEncryption:l=undefined;break;case r["RSASSA-PSS"]:var c,h;c=r[s.signatureParameters.mgf.hash.algorithmOid];if(c===undefined||e.md[c]===undefined)throw{message:"Unsupported MGF hash function.",oid:s.signatureParameters.mgf.hash.algorithmOid,name:c};h=r[s.signatureParameters.mgf.algorithmOid];if(h===undefined||e.mgf[h]===undefined)throw{message:"Unsupported MGF function.",oid:s.signatureParameters.mgf.algorithmOid,name:h};h=e.mgf[h].create(e.md[c].create()),c=r[s.signatureParameters.hash.algorithmOid];if(c===undefined||e.md[c]===undefined)throw{message:"Unsupported RSASSA-PSS hash function.",oid:s.signatureParameters.hash.algorithmOid,name:c};l=e.pss.create(e.md[c].create(),h,s.signatureParameters.saltLength)}i=s.publicKey.verify(o.digest().getBytes(),s.signature,l)}return i},s},n.getTBSCertificate=function(r){var i=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.CONTEXT_SPECIFIC,0,!0,[t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,String.fromCharCode(r.version))]),t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,e.util.hexToBytes(r.serialNumber)),t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(r.siginfo.algorithmOid).getBytes()),S(r.siginfo.algorithmOid,r.siginfo.parameters)]),w(r.issuer),t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.UTCTIME,!1,t.dateToUtcTime(r.validity.notBefore)),t.create(t.Class.UNIVERSAL,t.Type.UTCTIME,!1,t.dateToUtcTime(r.validity.notAfter))]),w(r.subject),n.publicKeyToAsn1(r.publicKey)]);return r.issuer.uniqueId&&i.value.push(t.create(t.Class.CONTEXT_SPECIFIC,1,!0,[t.create(t.Class.UNIVERSAL,t.Type.BITSTRING,!1,String.fromCharCode(0)+r.issuer.uniqueId)])),r.subject.uniqueId&&i.value.push(t.create(t.Class.CONTEXT_SPECIFIC,2,!0,[t.create(t.Class.UNIVERSAL,t.Type.BITSTRING,!1,String.fromCharCode(0)+r.subject.uniqueId)])),r.extensions.length>0&&i.value.push(E(r.extensions)),i},n.getCertificationRequestInfo=function(e){var r=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,String.fromCharCode(e.version)),w(e.subject),n.publicKeyToAsn1(e.publicKey),x(e)]);return r},n.distinguishedNameToAsn1=function(e){return w(e)},n.certificateToAsn1=function(e){var r=e.tbsCertificate||n.getTBSCertificate(e);return t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[r,t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(e.signatureOid).getBytes()),S(e.signatureOid,e.signatureParameters)]),t.create(t.Class.UNIVERSAL,t.Type.BITSTRING,!1,String.fromCharCode(0)+e.signature)])},n.certificationRequestToAsn1=function(e){var r=e.certificationRequestInfo||n.getCertificationRequestInfo(e);return t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[r,t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(e.signatureOid).getBytes()),S(e.signatureOid,e.signatureParameters)]),t.create(t.Class.UNIVERSAL,t.Type.BITSTRING,!1,String.fromCharCode(0)+e.signature)])},n.createCaStore=function(t){var r={certs:{}};r.getIssuer=function(t){var i=null;if(!t.issuer.hash){var s=e.md.sha1.create();t.issuer.attributes=n.RDNAttributesAsArray(w(t.issuer),s),t.issuer.hash=s.digest().toHex()}if(t.issuer.hash in r.certs){i=r.certs[t.issuer.hash];if(e.util.isArray(i))throw{message:"Resolving multiple issuer matches not implemented yet."}}return i},r.addCertificate=function(t){typeof t=="string"&&(t=e.pki.certificateFromPem(t));if(!t.subject.hash){var i=e.md.sha1.create();t.subject.attributes=n.RDNAttributesAsArray(w(t.subject),i),t.subject.hash=i.digest().toHex()}if(t.subject.hash in r.certs){var s=r.certs[t.subject.hash];e.util.isArray(s)||(s=[s]),s.push(t)}else r.certs[t.subject.hash]=t};if(t)for(var i=0;i<t.length;++i){var s=t[i];r.addCertificate(s)}return r},n.certificateError={bad_certificate:"forge.pki.BadCertificate",unsupported_certificate:"forge.pki.UnsupportedCertificate",certificate_revoked:"forge.pki.CertificateRevoked",certificate_expired:"forge.pki.CertificateExpired",certificate_unknown:"forge.pki.CertificateUnknown",unknown_ca:"forge.pki.UnknownCertificateAuthority"},n.verifyCertificateChain=function(t,r,i){r=r.slice(0);var s=r.slice(0),o=new Date,u=!0,a=null,f=0,l=null;do{var c=r.shift();if(o<c.validity.notBefore||o>c.validity.notAfter)a={message:"Certificate is not valid yet or has expired.",error:n.certificateError.certificate_expired,notBefore:c.validity.notBefore,notAfter:c.validity.notAfter,now:o};else{var h=!1;if(r.length>0){l=r[0];try{h=l.verify(c)}catch(p){}}else{var d=t.getIssuer(c);if(d===null)a={message:"Certificate is not trusted.",error:n.certificateError.unknown_ca};else{e.util.isArray(d)||(d=[d]);while(!h&&d.length>0){l=d.shift();try{h=l.verify(c)}catch(p){}}}}a===null&&!h&&(a={message:"Certificate signature is invalid.",error:n.certificateError.bad_certificate})}a===null&&!c.isIssuer(l)&&(a={message:"Certificate issuer is invalid.",error:n.certificateError.bad_certificate});if(a===null){var v={keyUsage:!0,basicConstraints:!0};for(var m=0;a===null&&m<c.extensions.length;++m){var g=c.extensions[m];g.critical&&!(g.name in v)&&(a={message:"Certificate has an unsupported critical extension.",error:n.certificateError.unsupported_certificate})}}if(!u||r.length===0&&!l){var y=c.getExtension("basicConstraints"),b=c.getExtension("keyUsage");b!==null&&(!b.keyCertSign||y===null)&&(a={message:"Certificate keyUsage or basicConstraints conflict or indicate that the certificate is not a CA. If the certificate is the only one in the chain or isn't the first then the certificate must be a valid CA.",error:n.certificateError.bad_certificate}),a===null&&y!==null&&!y.cA&&(a={message:"Certificate basicConstraints indicates the certificate is not a CA.",error:n.certificateError.bad_certificate})}var w=a===null?!0:a.error,E=i?i(w,f,s):w;if(E!==!0){w===!0&&(a={message:"The application rejected the certificate.",error:n.certificateError.bad_certificate});if(E||E===0)typeof E=="object"&&!e.util.isArray(E)?(E.message&&(a.message=E.message),E.error&&(a.error=E.error)):typeof E=="string"&&(a.error=E);throw a}a=null,u=!1,++f}while(r.length>0);return!0},n.publicKeyFromAsn1=function(r){var i={},u=[];if(t.validate(r,s,i,u)){var a=t.derToOid(i.publicKeyOid);if(a!==n.oids.rsaEncryption)throw{message:"Cannot read public key. Unknown OID.",oid:a};r=i.rsaPublicKey}u=[];if(!t.validate(r,o,i,u))throw{message:"Cannot read public key. ASN.1 object does not contain an RSAPublicKey.",errors:u};var f=e.util.createBuffer(i.publicKeyModulus).toHex(),l=e.util.createBuffer(i.publicKeyExponent).toHex();return n.setRsaPublicKey(new BigInteger(f,16),new BigInteger(l,16))},n.publicKeyToAsn1=n.publicKeyToSubjectPublicKeyInfo=function(e){return t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.oids.rsaEncryption).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.NULL,!1,"")]),t.create(t.Class.UNIVERSAL,t.Type.BITSTRING,!1,[n.publicKeyToRSAPublicKey(e)])])},n.publicKeyToRSAPublicKey=function(e){return t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,y(e.n)),t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,y(e.e))])},n.privateKeyFromAsn1=function(r){var i={},s=[];t.validate(r,a,i,s)&&(r=t.fromDer(e.util.createBuffer(i.privateKey))),i={},s=[];if(!t.validate(r,f,i,s))throw{message:"Cannot read private key. ASN.1 object does not contain an RSAPrivateKey.",errors:s};var o,u,l,c,h,p,d,v;return o=e.util.createBuffer(i.privateKeyModulus).toHex(),u=e.util.createBuffer(i.privateKeyPublicExponent).toHex(),l=e.util.createBuffer(i.privateKeyPrivateExponent).toHex(),c=e.util.createBuffer(i.privateKeyPrime1).toHex(),h=e.util.createBuffer(i.privateKeyPrime2).toHex(),p=e.util.createBuffer(i.privateKeyExponent1).toHex(),d=e.util.createBuffer(i.privateKeyExponent2).toHex(),v=e.util.createBuffer(i.privateKeyCoefficient).toHex(),n.setRsaPrivateKey(new BigInteger(o,16),new BigInteger(u,16),new BigInteger(l,16),new BigInteger(c,16),new BigInteger(h,16),new BigInteger(p,16),new BigInteger(d,16),new BigInteger(v,16))},n.privateKeyToAsn1=n.privateKeyToRSAPrivateKey=function(e){return t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,String.fromCharCode(0)),t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,y(e.n)),t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,y(e.e)),t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,y(e.d)),t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,y(e.p)),t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,y(e.q)),t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,y(e.dP)),t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,y(e.dQ)),t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,y(e.qInv))])},n.wrapRsaPrivateKey=function(e){return t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,"\0"),t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(r.rsaEncryption).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.NULL,!1,"")]),t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,t.toDer(e).getBytes())])},n.encryptPrivateKeyInfo=function(n,i,s){s=s||{},s.saltSize=s.saltSize||8,s.count=s.count||2048,s.algorithm=s.algorithm||"aes128";var o=e.random.getBytes(s.saltSize),u=s.count,a=e.util.createBuffer();a.putInt16(u);var f,l,c;if(s.algorithm.indexOf("aes")===0){var h;if(s.algorithm==="aes128")f=16,h=r["aes128-CBC"];else if(s.algorithm==="aes192")f=24,h=r["aes192-CBC"];else{if(s.algorithm!=="aes256")throw{message:"Cannot encrypt private key. Unknown encryption algorithm.",algorithm:s.algorithm};f=32,h=r["aes256-CBC"]}var p=e.pkcs5.pbkdf2(i,o,u,f),d=e.random.getBytes(16),v=e.aes.createEncryptionCipher(p);v.start(d),v.update(t.toDer(n)),v.finish(),c=v.output.getBytes(),l=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(r.pkcs5PBES2).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(r.pkcs5PBKDF2).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,o),t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,a.getBytes())])]),t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(h).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,d)])])])}else{if(s.algorithm!=="3des")throw{message:"Cannot encrypt private key. Unknown encryption algorithm.",algorithm:s.algorithm};f=24;var m=new e.util.ByteBuffer(o),p=e.pkcs12.generateKey(i,m,1,u,f),d=e.pkcs12.generateKey(i,m,2,u,f),v=e.des.createEncryptionCipher(p);v.start(d),v.update(t.toDer(n)),v.finish(),c=v.output.getBytes(),l=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(r["pbeWithSHAAnd3-KeyTripleDES-CBC"]).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,o),t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,a.getBytes())])])}var g=t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[l,t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,c)]);return g},n.pbe.getCipherForPBES2=function(r,i,s){var o={},u=[];if(!t.validate(i,c,o,u))throw{message:"Cannot read password-based-encryption algorithm parameters. ASN.1 object is not a supported EncryptedPrivateKeyInfo.",errors:u};r=t.derToOid(o.kdfOid);if(r!==n.oids.pkcs5PBKDF2)throw{message:"Cannot read encrypted private key. Unsupported key derivation function OID.",oid:r,supportedOids:["pkcs5PBKDF2"]};r=t.derToOid(o.encOid);if(r!==n.oids["aes128-CBC"]&&r!==n.oids["aes192-CBC"]&&r!==n.oids["aes256-CBC"])throw{message:"Cannot read encrypted private key. Unsupported encryption scheme OID.",oid:r,supportedOids:["aes128-CBC","aes192-CBC","aes256-CBC"]};var a=o.kdfSalt,f=e.util.createBuffer(o.kdfIterationCount);f=f.getInt(f.length()<<3);var l;r===n.oids["aes128-CBC"]?l=16:r===n.oids["aes192-CBC"]?l=24:r===n.oids["aes256-CBC"]&&(l=32);var h=e.pkcs5.pbkdf2(s,a,f,l),p=o.encIv,d=e.aes.createDecryptionCipher(h);return d.start(p),d},n.pbe.getCipherForPKCS12PBE=function(r,i,s){var o={},u=[];if(!t.validate(i,h,o,u))throw{message:"Cannot read password-based-encryption algorithm parameters. ASN.1 object is not a supported EncryptedPrivateKeyInfo.",errors:u};var a=e.util.createBuffer(o.salt),f=e.util.createBuffer(o.iterations);f=f.getInt(f.length()<<3);var l,c,p;switch(r){case n.oids["pbeWithSHAAnd3-KeyTripleDES-CBC"]:l=24,c=8,p=e.des.startDecrypting;break;case n.oids["pbewithSHAAnd40BitRC2-CBC"]:l=5,c=8,p=function(t,n){var r=e.rc2.createDecryptionCipher(t,40);return r.start(n,null),r};break;default:throw{message:"Cannot read PKCS #12 PBE data block. Unsupported OID.",oid:r}}var d=e.pkcs12.generateKey(s,a,1,f,l),v=e.pkcs12.generateKey(s,a,2,f,c);return p(d,v)},n.pbe.getCipher=function(e,t,r){switch(e){case n.oids.pkcs5PBES2:return n.pbe.getCipherForPBES2(e,t,r);case n.oids["pbeWithSHAAnd3-KeyTripleDES-CBC"]:case n.oids["pbewithSHAAnd40BitRC2-CBC"]:return n.pbe.getCipherForPKCS12PBE(e,t,r);default:throw{message:"Cannot read encrypted PBE data block. Unsupported OID.",oid:e,supportedOids:["pkcs5PBES2","pbeWithSHAAnd3-KeyTripleDES-CBC","pbewithSHAAnd40BitRC2-CBC"]}}},n.decryptPrivateKeyInfo=function(r,i){var s=null,o={},u=[];if(!t.validate(r,l,o,u))throw{message:"Cannot read encrypted private key. ASN.1 object is not a supported EncryptedPrivateKeyInfo.",errors:u};var a=t.derToOid(o.encryptionOid),f=n.pbe.getCipher(a,o.encryptionParams,i),c=e.util.createBuffer(o.encryptedData);return f.update(c),f.finish()&&(s=t.fromDer(f.output)),s},n.encryptedPrivateKeyToPem=function(n,r){var i={type:"ENCRYPTED PRIVATE KEY",body:t.toDer(n).getBytes()};return e.pem.encode(i,{maxline:r})},n.encryptedPrivateKeyFromPem=function(n){var r=e.pem.decode(n)[0];if(r.type!=="ENCRYPTED PRIVATE KEY")throw{message:'Could not convert encrypted private key from PEM; PEM header type is "ENCRYPTED PRIVATE KEY".',headerType:r.type};if(r.procType&&r.procType.type==="ENCRYPTED")throw{message:"Could not convert encrypted private key from PEM; PEM is encrypted."};return t.fromDer(r.body)},n.encryptRsaPrivateKey=function(r,i,s){s=s||{};if(!s.legacy){var o=n.wrapRsaPrivateKey(n.privateKeyToAsn1(r));return o=n.encryptPrivateKeyInfo(o,i,s),n.encryptedPrivateKeyToPem(o)}var u,a,f,l;switch(s.algorithm){case"aes128":u="AES-128-CBC",f=16,a=e.random.getBytes(16),l=e.aes.createEncryptionCipher;break;case"aes192":u="AES-192-CBC",f=24,a=e.random.getBytes(16),l=e.aes.createEncryptionCipher;break;case"aes256":u="AES-256-CBC",f=32,a=e.random.getBytes(16),l=e.aes.createEncryptionCipher;break;case"3des":u="DES-EDE3-CBC",f=24,a=e.random.getBytes(8),l=e.des.createEncryptionCipher;break;default:throw{message:'Could not encrypt RSA private key; unsupported encryption algorithm "'+s.algorithm+'".',algorithm:s.algorithm}}var c=T(i,a.substr(0,8),f),h=l(c);h.start(a),h.update(t.toDer(n.privateKeyToAsn1(r))),h.finish();var p={type:"RSA PRIVATE KEY",procType:{version:"4",type:"ENCRYPTED"},dekInfo:{algorithm:u,parameters:e.util.bytesToHex(a).toUpperCase()},body:h.output.getBytes()};return e.pem.encode(p)},n.decryptRsaPrivateKey=function(r,i){var s=null,o=e.pem.decode(r)[0];if(o.type!=="ENCRYPTED PRIVATE KEY"&&o.type!=="PRIVATE KEY"&&o.type!=="RSA PRIVATE KEY")throw{message:'Could not convert private key from PEM; PEM header type is not "ENCRYPTED PRIVATE KEY", "PRIVATE KEY", or "RSA PRIVATE KEY".',headerType:o.type};if(o.procType&&o.procType.type==="ENCRYPTED"){var u,a;switch(o.dekInfo.algorithm){case"DES-EDE3-CBC":u=24,a=e.des.createDecryptionCipher;break;case"AES-128-CBC":u=16,a=e.aes.createDecryptionCipher;break;case"AES-192-CBC":u=24,a=e.aes.createDecryptionCipher;break;case"AES-256-CBC":u=32,a=e.aes.createDecryptionCipher;break;case"RC2-40-CBC":u=5,a=function(t){return e.rc2.createDecryptionCipher(t,40)};break;case"RC2-64-CBC":u=8,a=function(t){return e.rc2.createDecryptionCipher(t,64)};break;case"RC2-128-CBC":u=16,a=function(t){return e.rc2.createDecryptionCipher(t,128)};break;default:throw{message:'Could not decrypt private key; unsupported encryption algorithm "'+o.dekInfo.algorithm+'".',algorithm:o.dekInfo.algorithm}}var f=e.util.hexToBytes(o.dekInfo.parameters),l=T(i,f.substr(0,8),u),c=a(l);c.start(f),c.update(e.util.createBuffer(o.body));if(!c.finish())return s;s=c.output.getBytes()}else s=o.body;return o.type==="ENCRYPTED PRIVATE KEY"?s=n.decryptPrivateKeyInfo(t.fromDer(s),i):s=t.fromDer(s),s!==null&&(s=n.privateKeyFromAsn1(s)),s},n.setRsaPublicKey=n.rsa.setPublicKey,n.setRsaPrivateKey=n.rsa.setPrivateKey}var t="pki";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/pki",["require","module","./aes","./asn1","./des","./jsbn","./md","./mgf","./oids","./pem","./pbkdf2","./pkcs12","./pss","./random","./rc2","./rsa","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e,t,n,r,i,s,o){var u={privateKey:"-----BEGIN RSA PRIVATE KEY-----\r\nMIICXQIBAAKBgQDL0EugUiNGMWscLAVM0VoMdhDZEJOqdsUMpx9U0YZI7szokJqQ\r\nNIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMGTkP3VF29vXBo+dLq5e+8VyAy\r\nQ3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGtvnM+z0MYDdKo80efzwIDAQAB\r\nAoGAIzkGONi5G+JifmXlLJdplom486p3upf4Ce2/7mqfaG9MnkyPSairKD/JXvfh\r\nNNWkkN8DKKDKBcVVElPgORYT0qwrWc7ueLBMUCbRXb1ZyfEulimG0R3kjUh7NYau\r\nDaIkVgfykXGSQMZx8FoaT6L080zd+0emKDDYRrb+/kgJNJECQQDoUZoiC2K/DWNY\r\nh3/ppZ0ane2y4SBmJUHJVMPQ2CEgxsrJTxet668ckNCKaOP/3VFPoWC41f17DvKq\r\nnoYINNntAkEA4JbZBZBVUrQFhHlrpXT4jzqtO2RlKZzEq8qmFZfEErxOT1WMyyCi\r\nlAQ5gUKardo1Kf0omC8Xq/uO9ZYdED55KwJBALs6cJ65UFaq4oLJiQPzLd7yokuE\r\ndcj8g71PLBTW6jPxIiMFNA89nz3FU9wIVp+xbMNhSoMMKqIPVPC+m0Rn260CQQDA\r\nI83fWK/mZWUjBM33a68KumRiH238v8XyQxj7+C8i6D8G2GXvkigFAehAkb7LZZd+\r\nKLuGFyPlWv3fVWHf99KpAkBQFKk3MRMl6IGJZUEFQe4l5whm8LkGU4acSqv9B3xt\r\nqROkCrsFrMPqjuuzEmyHoQZ64r2PLJg7FOuyhBnQUOt4\r\n-----END RSA PRIVATE KEY-----\r\n",publicKey:"-----BEGIN PUBLIC KEY-----\r\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDL0EugUiNGMWscLAVM0VoMdhDZ\r\nEJOqdsUMpx9U0YZI7szokJqQNIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMG\r\nTkP3VF29vXBo+dLq5e+8VyAyQ3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGt\r\nvnM+z0MYDdKo80efzwIDAQAB\r\n-----END PUBLIC KEY-----\r\n"},a="9200ece65cdaed36bcc20b94c65af852e4f88f0b4fe5b249d54665f815992ac43a1399e65d938c6a7f16dd39d971a53ca66523209dbbfbcb67afa579dbb0c220672813d9e6f4818f29b9becbb29da2032c5e422da97e0c39bfb7a2e7d568615a5073af0337ff215a8e1b2332d668691f4fb731440055420c24ac451dd3c913f4";describe("rsa",function(){it("should generate 512 bit key pair",function(){var i=n.generateKeyPair(512);e.equal(t.privateKeyToPem(i.privateKey).indexOf("-----BEGIN RSA PRIVATE KEY-----"),0),e.equal(t.publicKeyToPem(i.publicKey).indexOf("-----BEGIN PUBLIC KEY-----"),0);var s=r.sha1.create();s.update("0123456789abcdef");var o=i.privateKey.sign(s);e.ok(i.publicKey.verify(s.digest().getBytes(),o))}),it("should convert private key to/from PEM",function(){var n=t.privateKeyFromPem(u.privateKey);e.equal(t.privateKeyToPem(n),u.privateKey)}),it("should convert public key to/from PEM",function(){var n=t.publicKeyFromPem(u.publicKey);e.equal(t.publicKeyToPem(n),u.publicKey)}),function(){var n=["aes128","aes192","aes256","3des"];for(var r=0;r<n.length;++r){var i=n[r];it("should PKCS#8 encrypt and decrypt private key with "+i,function(){var n=t.privateKeyFromPem(u.privateKey),r=t.encryptRsaPrivateKey(n,"password",{algorithm:i}),n=t.decryptRsaPrivateKey(r,"password");e.equal(t.privateKeyToPem(n),u.privateKey)})}}(),function(){var n=["aes128","aes192","aes256","3des"];for(var r=0;r<n.length;++r){var i=n[r];it("should legacy (OpenSSL style) encrypt and decrypt private key with "+i,function(){var n=t.privateKeyFromPem(u.privateKey),r=t.encryptRsaPrivateKey(n,"password",{algorithm:i,legacy:!0}),n=t.decryptRsaPrivateKey(r,"password");e.equal(t.privateKeyToPem(n),u.privateKey)})}}(),it("should verify signature",function(){var n=t.publicKeyFromPem(u.publicKey),i=r.sha1.create();i.update("0123456789abcdef");var s=o.hexToBytes(a);e.ok(n.verify(i.digest().getBytes(),s))}),it("should sign and verify",function(){var n=t.privateKeyFromPem(u.privateKey),i=t.publicKeyFromPem(u.publicKey),s=r.sha1.create();s.update("0123456789abcdef");var o=n.sign(s);e.ok(i.verify(s.digest().getBytes(),o))}),function(){function a(n){var u=n.keySize;it("should rsa encrypt using a "+u+"-bit key",function(){var r="it need's to be about 20% cooler",i=t.publicKeyFromPem(n.publicKeyPem),s=i.encrypt(r);i=t.privateKeyFromPem(n.privateKeyPem),e.equal(i.decrypt(s),r)}),it("should rsa decrypt using a "+u+"-bit key",function(){var r=o.decode64(n.encrypted),i=t.privateKeyFromPem(n.privateKeyPem);e.equal(i.decrypt(r),"too many secrets\n")}),it("should rsa sign using a "+u+"-bit key and PKCS#1 v1.5 padding",function(){var i=t.privateKeyFromPem(n.privateKeyPem),s=r.sha1.create();s.start(),s.update("just testing");var u=o.decode64(n.signature);e.equal(i.sign(s),u)}),it("should verify an rsa signature using a "+u+"-bit key and PKCS#1 v1.5 padding",function(){var i=o.decode64(n.signature),s=t.publicKeyFromPem(n.publicKeyPem),u=r.sha1.create();u.start(),u.update("just testing"),e.equal(s.verify(u.digest().getBytes(),i),!0)}),it("should rsa sign using a "+u+"-bit key and PSS padding",function(){var o=t.privateKeyFromPem(n.privateKeyPem),u=t.publicKeyFromPem(n.publicKeyPem),a=r.sha1.create();a.start(),a.update("just testing");var f=s.create(r.sha1.create(),i.mgf1.create(r.sha1.create()),20),l=o.sign(a,f);a.start(),a.update("just testing"),e.equal(u.verify(a.digest().getBytes(),l,f),!0)}),it("should verify an rsa signature using a "+u+"-bit key and PSS padding",function(){var u=o.decode64(n.signaturePss),a=t.publicKeyFromPem(n.publicKeyPem),f=r.sha1.create();f.start(),f.update("just testing");var l=s.create(r.sha1.create(),i.mgf1.create(r.sha1.create()),20);e.equal(a.verify(f.digest().getBytes(),u,l),!0)})}var n=[{keySize:1024,privateKeyPem:"-----BEGIN RSA PRIVATE KEY-----\r\nMIICWwIBAAKBgQDCjvkkLWNTeYXqEsqGiVCW/pDt3/qAodNMHcU9gOU2rxeWwiRu\r\nOhhLqmMxXHLi0oP5Xmg0m7zdOiLMEyzzyRzdp21aqp3k5qtuSDkZcf1prsp1jpYm\r\n6z9EGpaSHb64BCuUsQGmUPKutd5RERKHGZXtiRuvvIyue7ETq6VjXrOUHQIDAQAB\r\nAoGAOKeBjTNaVRhyEnNeXkbmHNIMSfiK7aIx8VxJ71r1ZDMgX1oxWZe5M29uaxVM\r\nrxg2Lgt7tLYVDSa8s0hyMptBuBdy3TJUWruDx85uwCrWnMerCt/iKVBS22fv5vm0\r\nLEq/4gjgIVTZwgqbVxGsBlKcY2VzxAfYqYzU8EOZBeNhZdECQQDy+PJAPcUN2xOs\r\n6qy66S91x6y3vMjs900OeX4+bgT4VSVKmLpqRTPizzcL07tT4+Y+pAAOX6VstZvZ\r\n6iFDL5rPAkEAzP1+gaRczboKoJWKJt0uEMUmztcY9NXJFDmjVLqzKwKjcAoGgIal\r\nh+uBFT9VJ16QajC7KxTRLlarzmMvspItUwJAeUMNhEpPwm6ID1DADDi82wdgiALM\r\nNJfn+UVhYD8Ac//qsKQwxUDseFH6owh1AZVIIBMxg/rwUKUCt2tGVoW3uQJAIt6M\r\nAml/D8+xtxc45NuC1n9y1oRoTl1/Ut1rFyKbD5nnS0upR3uf9LruvjqDtaq0Thvz\r\n+qQT4RoFJ5pfprSO2QJAdMkfNWRqECfAhZyQuUrapeWU3eQ0wjvktIynCIwiBDd2\r\nMfjmVXzBJhMk6dtINt+vBEITVQEOdtyTgDt0y3n2Lw==\r\n-----END RSA PRIVATE KEY-----\r\n",publicKeyPem:"-----BEGIN PUBLIC KEY-----\r\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCjvkkLWNTeYXqEsqGiVCW/pDt\r\n3/qAodNMHcU9gOU2rxeWwiRuOhhLqmMxXHLi0oP5Xmg0m7zdOiLMEyzzyRzdp21a\r\nqp3k5qtuSDkZcf1prsp1jpYm6z9EGpaSHb64BCuUsQGmUPKutd5RERKHGZXtiRuv\r\nvIyue7ETq6VjXrOUHQIDAQAB\r\n-----END PUBLIC KEY-----\r\n",encrypted:"jsej3OoacmJ1VjWrlw68F+drnQORAuKAqVu6RMbz1xSXjzA355vctrJZXolRU0mvzuu/6VuNynkKGGyRJ6DHt85CvwTMChw4tOMV4Dy6bgnUt3j+DZA2sWTwFhOlpzvNQMK70QpuqrXtOZmAO59EwoDeJkW/iH6t4YzNOVYo9Jg=",signature:"GT0/3EV2zrXxPd1ydijJq3R7lkI4c0GtcprgpG04dSECv/xyXtikuzivxv7XzUdHpu6QiYmM0xE4D4i7LK3Mzy+f7aB4o/dg8XXO3htLiBzVI+ZJCRh06RdYctPtclAWmyZikZ8Etw3NnA/ldKuG4jApbwRb21UFm5gYLrJ4SP4=",signaturePss:"F4xffaANDBjhFxeSJx8ANuBbdhaWZjUHRQh4ueYQMPPCaR2mpwdqxE04sbgNgIiZzBuLIAI4HpTMMoDk3Rruhjefx3+9UhzTxgB0hRI+KzRChRs+ToltWWDZdYzt9T8hfTlELeqT4V8HgjDuteO/IAvIVlRIBwMNv53Iebu1FY4="},{keySize:1025,privateKeyPem:"-----BEGIN RSA PRIVATE KEY-----\r\nMIICXgIBAAKBgQGIkej4PDlAigUh5fbbHp1WXuTHhOdQfAke+LoH0TM4uzn0QmgK\r\nSJqxzB1COJ5o0DwZw/NR+CNy7NUrly+vmh2YPwsaqN+AsYBF9qsF93oN8/TBtaL/\r\nGRoRGpDcCglkj1kZnDaWR79NsG8mC0TrvQCkcCLOP0c2Ux1hRbntOetGXwIDAQAB\r\nAoGBAIaJWsoX+ZcAthmT8jHOICXFh6pJBe0zVPzkSPz82Q0MPSRUzcsYbsuYJD7Z\r\noJBTLQW3feANpjhwqe2ydok7y//ONm3Th53Bcu8jLfoatg4KYxNFIwXEO10mPOld\r\nVuDIGrBkTABe6q2P5PeUKGCKLT6i/u/2OTXTrQiJbQ0gU8thAkEBjqcFivWMXo34\r\nCb9/EgfWCCtv9edRMexgvcFMysRsbHJHDK9JjRLobZltwtAv3cY7F3a/Cu1afg+g\r\njAzm5E3gowJBAPwYFHTLzaZToxFKNQztWrPsXF6YfqHpPUUIpT4UzL6DhGG0M00U\r\nqMyhkYRRqmGOSrSovjg2hjM2643MUUWxUxUCQDPkk/khu5L3YglKzyy2rmrD1MAq\r\ny0v3XCR3TBq89Ows+AizrJxbkLvrk/kfBowU6M5GG9o9SWFNgXWZnFittocCQQDT\r\ne1P1419DUFi1UX6NuLTlybx3sxBQvf0jY6xUF1jn3ib5XBXJbTJqcIRF78iyjI9J\r\nXWIugDc20bTsQOJRSAA9AkEBU8kpueHBaiXTikqqlK9wvc2Lp476hgyKVmVyBGye\r\n9TLTWkTCzDPtManLy47YtXkXnmyazS+DlKFU61XAGEnZfg==\r\n-----END RSA PRIVATE KEY-----\r\n",publicKeyPem:"-----BEGIN PUBLIC KEY-----\r\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQGIkej4PDlAigUh5fbbHp1WXuTH\r\nhOdQfAke+LoH0TM4uzn0QmgKSJqxzB1COJ5o0DwZw/NR+CNy7NUrly+vmh2YPwsa\r\nqN+AsYBF9qsF93oN8/TBtaL/GRoRGpDcCglkj1kZnDaWR79NsG8mC0TrvQCkcCLO\r\nP0c2Ux1hRbntOetGXwIDAQAB\r\n-----END PUBLIC KEY-----\r\n",encrypted:"AOVeCUN8BOVkZvt4mxyNn/yCYE1MZ40A3e/osh6EvCBcJ09hyYbx7bzKSrdkhRnDyW0pGtgP352CollasllQZ9HlfI2Wy9zKM0aYZZn8OHBA+60Tc3xHHDGznLZqggUKuhoNpj+faVZ1uzb285eTpQQa+4mLUue2svJD4ViM8+ng",signature:"AFSx0axDYXlF2rO3ofgUhYSI8ZlIWtJUUZ62PhgdBp9O5zFqMX3DXoiov1e7NenSOz1khvTSMctFWzKP3GU3F0yewe+Yd3UAZE0dM8vAxigSSfAchUkBDmp9OFuszUie63zwWwpG+gXtvyfueZs1RniBvW1ZmXJvS+HFgX4ouzwd",signaturePss:"AQvBdhAXDpu+7RpcybMgwuTUk6w+qa08Lcq3G1xHY4kC7ZUzauZd/Jn9e0ePKApDqs7eDNAOV+dQkU2wiH/uBg6VGelzb0hFwcpSLyBW92Vw0q3GlzY7myWn8qnNzasrt110zFflWQa1GiuzH/C8f+Z82/MzlWDxloJIYbq2PRC8"},{keySize:1031,privateKeyPem:"-----BEGIN RSA PRIVATE KEY-----\r\nMIICXwIBAAKBgWyeKqA2oA4klYrKT9hjjutYQksJNN0cxwaQwIm9AYiLxOsYtT/C\r\novJx5Oy1EvkbYQbfvYsGISUx9bW8yasZkTHR55IbW3+UptvQjTDtdxBQTgQOpsAh\r\nBJtZYY3OmyH9Sj3F3oB//oyriNoj0QYyfsvlO8UsMmLzpnf6qfZBDHA/9QIDAQAB\r\nAoGBBj/3ne5muUmbnTfU7lOUNrCGaADonMx6G0ObAJHyk6PPOePbEgcmDyNEk+Y7\r\naEAODjIzmttIbvZ39/Qb+o9nDmCSZC9VxiYPP+rjOzPglCDT5ks2Xcjwzd3If6Ya\r\nUw6P31Y760OCYeTb4Ib+8zz5q51CkjkdX5Hq/Yu+lZn0Vx7BAkENo83VfL+bwxTm\r\nV7vR6gXqTD5IuuIGHL3uTmMNNURAP6FQDHu//duipys83iMChcOeXtboE16qYrO0\r\n9KC0cqL4JQJBB/aYo/auVUGZA6f50YBp0b2slGMk9TBQG0iQefuuSyH4kzKnt2e3\r\nQ40SBmprcM+DfttWJ11bouec++goXjz+95ECQQyiTWYRxulgKVuyqCYnvpLnTEnR\r\n0MoYlVTHBriVPkLErYaYCYgse+SNM1+N4p/Thv6KmkUcq/Lmuc5DSRfbl1iBAkEE\r\n7GKtJQvd7EO1bfpXnARQx+tWhwHHkgpFBBVHReMZ0rQEFhJ5o2c8HZEiZFNvGO2c\r\n1fErP14zlu2JFZ03vpCI8QJBCQz9HL28VNjafSAF2mon/SNjKablRjoGGKSoSdyA\r\nDHDZ/LeRsTp2dg8+bSiG1R+vPqw0f/BT+ux295Sy9ocGEM8=\r\n-----END RSA PRIVATE KEY-----\r\n",publicKeyPem:"-----BEGIN PUBLIC KEY-----\r\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgWyeKqA2oA4klYrKT9hjjutYQksJ\r\nNN0cxwaQwIm9AYiLxOsYtT/CovJx5Oy1EvkbYQbfvYsGISUx9bW8yasZkTHR55Ib\r\nW3+UptvQjTDtdxBQTgQOpsAhBJtZYY3OmyH9Sj3F3oB//oyriNoj0QYyfsvlO8Us\r\nMmLzpnf6qfZBDHA/9QIDAQAB\r\n-----END PUBLIC KEY-----\r\n",encrypted:"ShSS4/fEAkuS6XiQakhOpWp82IXaaCaDNtsndU4uokvriqgCGZyqc+IkIk3eVmZ8bn4vVIRR43ydFuvGgsptVjizOdLGZudph3TJ1clcYEMcCXk4z5HaEu0bx5SW9jmzHhE/z+WV8PB48q7y7C2qtmPmfttG2NMsNLBvkiaDopRO",signature:"Z3vYgRdezrWmdA3NC1Uz2CcHRTcE+/C2idGZA1FjUGqFztAHQ31k0QW/F5zuJdKvg8LQU45S3KxW+OQpbGPL98QbzJLhml88mFGe6OinLXJbi7UQWrtXwamc2jMdiXwovSLbXaXy6PX2QW089iC8XuAZftVi3T/IKV0458FQQprg",signaturePss:"R6QsK6b3QinIPZPamm/dP0Zndqti1TzAkFTRSZJaRSa1u2zuvZC5QHF4flDjEtHosWeDyxrBE7PHGQZ0b1bHv9qgHGsJCMwaQPj3AWj9fjYmx7b86KM2vHr8q/vqDaa9pTvVRSSwvD6fwoZPc9twQEfdjdDBAiy23yLDzk/zZiwM"},{keySize:1032,privateKeyPem:"-----BEGIN RSA PRIVATE KEY-----\r\nMIICYQIBAAKBggDPhzn5I3GecxWt5DKbP+VhM2AFNSOL0+VbYEOR1hnlZdLbxGK4\r\ncPQzMr2qT6dyttJcsgWr3xKobPkz7vsTZzQATSiekm5Js5dGpaj5lrq/x2+WTZvn\r\n55x9M5Y5dlpusDMKcC3KaIX/axc+MbvPFzo6Eli7JLCWdBg01eKo30knil0CAwEA\r\nAQKBggCNl/sjFF7SOD1jbt5kdL0hi7cI9o+xOLs1lEGmAEmc7dNnZN/ibhb/06/6\r\nwuxB5aEz47bg5IvLZMbG+1hNjc26D0J6Y3Ltwrg8f4ZMdDrh4v0DZ8hy/HbEpMrJ\r\nTd5dk3mtw9FLow10MB5udPLTDKhfDpTcWiObKm2STtFeBk3xeEECQQ6Cx6bZxQJ1\r\nzCxflV5Xi8BgAQaUKMqygugte+HpOLflL0j1fuZ0rPosUyDOEFkTzOsPxBYYOU8i\r\nGzan1GvW3WwRAkEOTTRt849wpgC9xx2pF0IrYEVmv5gEMy3IiRfCNgEoBwpTWVf4\r\nQFpN3V/9GFz0WQEEYo6OTmkNcC3Of5zbHhu1jQJBBGxXAYQ2KnbP4uLL/DMBdYWO\r\nKnw1JvxdLPrYXVejI2MoE7xJj2QXajbirAhEMXL4rtpicj22EmoaE4H7HVgkrJEC\r\nQQq2V5w4AGwvW4TLHXNnYX/eB33z6ujScOuxjGNDUlBqHZja5iKkCUAjnl+UnSPF\r\nexaOwBrlrpiLOzRer94MylKNAkEBmI58bqfkI5OCGDArAsJ0Ih58V0l1UW35C1SX\r\n4yDoXSM5A/xQu2BJbXO4jPe3PnDvCVCEyKpbCK6bWbe26Y7zuw==\r\n-----END RSA PRIVATE KEY-----\r\n",publicKeyPem:"-----BEGIN PUBLIC KEY-----\r\nMIGgMA0GCSqGSIb3DQEBAQUAA4GOADCBigKBggDPhzn5I3GecxWt5DKbP+VhM2AF\r\nNSOL0+VbYEOR1hnlZdLbxGK4cPQzMr2qT6dyttJcsgWr3xKobPkz7vsTZzQATSie\r\nkm5Js5dGpaj5lrq/x2+WTZvn55x9M5Y5dlpusDMKcC3KaIX/axc+MbvPFzo6Eli7\r\nJLCWdBg01eKo30knil0CAwEAAQ==\r\n-----END PUBLIC KEY-----\r\n",encrypted:"pKTbv+xgXPDc+wbjsANFu1/WTcmy4aZFKXKnxddHbU5S0Dpdj2OqCACiBwu1oENPMgPAJ27XRbFtKG+eS8tX47mKP2Fo0Bi+BPFtzuQ1bj3zUzTwzjemT+PU+a4Tho/eKjPhm6xrwGAoQH2VEDEpvcYf+SRmGFJpJ/zPUrSxgffj",signature:"R9WBFprCfcIC4zY9SmBpEM0E+cr5j4gMn3Ido5mktoR9VBoJqC6eR6lubIPvZZUz9e4yUSYX0squ56Q9Y0yZFQjTHgsrlmhB2YW8kpv4h8P32Oz2TLcMJK9R2tIh9vvyxwBkd/Ml1qG60GnOFUFzxUad9VIlzaF1PFR6EfnkgBUW",signaturePss:"v9UBd4XzBxSRz8yhWKjUkFpBX4Fr2G+ImjqbePL4sAZvYw1tWL+aUQpzG8eOyMxxE703VDh9nIZULYI/uIb9HYHQoGYQ3WoUaWqtZg1x8pZP+Ad7ilUWk5ImRl57fTznNQiVdwlkS5Wgheh1yJCES570a4eujiK9OyB0ba4rKIcM"}];for(var u=0;u<n.length;++u)a(n[u]);it("should ensure maximum message length for a 1024-bit key is exceeded",function(){var r=t.publicKeyFromPem(n[0].publicKeyPem),i=o.createBuffer().fillWithByte(0,118);e.throws(function(){r.encrypt(i.getBytes())})}),it("should ensure maximum message length for a 1025-bit key is not exceeded",function(){var r=t.publicKeyFromPem(n[1].publicKeyPem),i=o.createBuffer().fillWithByte(0,118);e.doesNotThrow(function(){r.encrypt(i.getBytes())})})}()})}typeof define=="function"?define("test/rsa",["forge/pki","forge/rsa","forge/md","forge/mgf","forge/pss","forge/util"],function(t,n,r,i,s,o){e(ASSERT,t(),n(),r(),i(),s(),o())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/pki")(),require("../../js/rsa")(),require("../../js/md")(),require("../../js/mgf")(),require("../../js/pss")(),require("../../js/util")())}(),function(){function e(e,t,n,r,i){describe("pkcs1",function(){function s(){var e,t,n,r,i,s,o,u,a,l,p;e="qLOyhK+OtQs4cDSoYPFGxJGfMYdjzWxVmMiuSBGh4KvEx+CwgtaTpef87Wdc9GaFEncsDLxkp0LGxjD1M8jMcvYq6DPEC/JYQumEu3i9v5fAEH1VvbZi9cTg+rmEXLUUjvc5LdOq/5OuHmtme7PUJHYW1PW6ENTP0ibeiNOfFvs=",t="AQAB",n="UzOc/befyEZqZVxzFqyoXFX9j23YmP2vEZUX709S6P2OJY35P+4YD6DkqylpPNg7FSpVPUrE0YEri5+lrw5/Vf5zBN9BVwkm8zEfFcTWWnMsSDEW7j09LQrzVJrZv3y/t4rYhPhNW+sEck3HNpsx3vN9DPU56c/N095lNynq1dE=",r="0yc35yZ//hNBstXA0VCoG1hvsxMr7S+NUmKGSpy58wrzi+RIWY1BOhcu+4AsIazxwRxSDC8mpHHcrSEurHyjnQ==",i="zIhT0dVNpjD6wAT0cfKBx7iYLYIkpJDtvrM9Pj1cyTxHZXA9HdeRZC8fEWoN2FK+JBmyr3K/6aAw6GCwKItddw==",s="DhK/FxjpzvVZm6HDiC/oBGqQh07vzo8szCDk8nQfsKM6OEiuyckwX77L0tdoGZZ9RnGsxkMeQDeWjbN4eOaVwQ==",o="lSl7D5Wi+mfQBwfWCd/U/AXIna/C721upVvsdx6jM3NNklHnkILs2oZu/vE8RZ4aYxOGt+NUyJn18RLKhdcVgw==",u="T0VsUCSTvcDtKrdWo6btTWc1Kml9QhbpMhKxJ6Y9VBHOb6mNXb79cyY+NygUJ0OBgWbtfdY2h90qjKHS9PvY4Q==",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 1.1",message:"ZigZThIHPbA7qUzanvlTI5fVDbp5uYcASv7+NA==",seed:"GLd26iEGnWl3ajPpa61I4d2gpe8=",encrypted:"NU/me0oSbV01/jbHd3kaP3uhPe9ITi05CK/3IvrUaPshaW3pXQvpEcLTF0+K/MIBA197bY5pQC3lRRYYwhpTX6nXv8W43Z/CQ/jPkn2zEyLW6IHqqRqZYXDmV6BaJmQm2YyIAD+Ed8EicJSg2foejEAkMJzh7My1IQA11HrHLoo="},{title:"RSAES-OAEP Encryption Example 1.2",message:"dQxAR/VH6OQUEYVlIymKybriRe+vE5f75W+d1Q==",seed:"DMdCzkqbfzL5UbyyUe/ZJf5P418=",encrypted:"ZA2xrMWOBWj+VAfl+bcB3/jDyR5xbFNvx/zsbLW3HBFlmI1KJ54Vd9cw/Hopky4/AMgVFSNtjY4xAXp6Cd9DUtkEzet5qlg63MMeppikwFKD2rqQib5UkfZ8Gk7kjcdLu+ZkOu+EZnm0yzlaNS1e0RWRLfaW/+BwKTKUbXFJK0Q="},{title:"RSAES-OAEP Encryption Example 1.3",message:"2Urggy5kRc5CMxywbVMagrHbS6rTD3RtyRbfJNTjwkUf/1mmQj6w4dAtT+ZGz2md/YGMbpewUQ==",seed:"JRTfRpV1WmeyiOr0kFw27sZv0v0=",encrypted:"Qjc27QNfYCavJ2w1wLN0GzZeX3bKCRtOjCni8L7+5gNZWqgyLWAtLmJeleuBsvHJck6CLsp224YYzwnFNDUDpDYINbWQO8Y344efsF4O8yaF1a7FBnzXzJb+SyZwturDBmsfz1aGtoWJqvt9YpsC2PhiXKODNiTUgA+wgbHPlOs="},{title:"RSAES-OAEP Encryption Example 1.4",message:"UuZQ2Y5/KgSLT4aFIVO5fgHdMW80ahn2eoU=",seed:"xENaPhoYpotoIENikKN877hds/s=",encrypted:"RerUylUeZiyYAPGsqCg7BSXmq64wvktKunYvpA/T044iq+/Gl5T267vAXduxEhYkfS9BL9D7qHxuOs2IiBNkb9DkjnhSBPnD9z1tgjlWJyLd3Ydx/sSLg6Me5vWSxM/UvIgXTzsToRKq47n3uA4PxvclW6iA3H2AIeIq1qhfB1U="},{title:"RSAES-OAEP Encryption Example 1.5",message:"jaif2eX5dKKf7/tGK0kYD2z56AI=",seed:"sxjELfO+D4P+qCP1p7R+1eQlo7U=",encrypted:"NvbjTZSo002qy6M6ITnQCthak0WoYFHnMHFiAFa5IOIZAFhVohOg8jiXzc1zG0UlfHd/6QggK+/dC1g4axJE6gz1OaBdXRAynaROEwMP12Dc1kTP7yCU0ZENP0M+HHxt0YvB8t9/ZD1mL7ndN+rZBZGQ9PpmyjnoacTrRJy9xDk="},{title:"RSAES-OAEP Encryption Example 1.6",message:"JlIQUIRCcQ==",seed:"5OwJgsIzbzpnf2o1YXTrDOiHq8I=",encrypted:"Qs7iYXsezqTbP0gpOG+9Ydr78DjhgNg3yWNm3yTAl7SrD6xr31kNghyfEGQuaBrQW414s3jA9Gzi+tY/dOCtPfBrB11+tfVjb41AO5BZynYbXGK7UqpFAC6nC6rOCN7SQ7nYy9YqaK3iZYMrVlZOQ6b6Qu0ZmgmXaXQt8VOeglU="}],f(a,l,"sha1",p),e="AZR8f86QQl9HJ55whR8l1eYjFv6KHfGTcePmKOJgVD5JAe9ggfaMC4FBGQ0q6Nq6fRJQ7G22NulE7Dcih3x8HQpn8UsWlMXwN5RRpD5Joy3eg2cLc9qRocmbwjtDamAFXGEPC6+ZwaB5VluVo/FSZjLR1Npg8g7aJeZTxPACdm9F",t="AQAB",n="CCPyD6212okIip0AiT4h+kobEfvJPGSjvguq6pf7O5PD/3E3BMGcljwdEHqumQVHOfeeAuGG3ob4em3e/qbYzNHTyBpHv6clW+IGAaSksvCKFnteJ51xWxtFW91+qyRZQdl2i5rO+zzNpZUto87nJSW0UBZjqO4VyemS2SRi/jk=",r="AVnb3gSjPvBvtgi4CxkPTT4ivME6yOSggQM6v6QW7bCzOKoItXMJ6lpSQOfcblQ3jGlBTDHZfdsfQG2zdpzEGkM=",i="AStlLzBAOzi0CZX9b/QaGsyK2nA3Mja3IC05su4wz7RtsJUR9vMHzGHMIWBsGKdbimL4It8DG6DfDa/VUG9Wi9c=",s="Q271CN5zZRnC2kxYDZjILLdFKj+1763Ducd4mhvGWE95Wt270yQ5x0aGVS7LbCwwek069/U57sFXJIx7MfGiVQ==",o="ASsVqJ89+ys5Bz5z8CvdDBp7N53UNfBc3eLv+eRilIt87GLukFDV4IFuB4WoVrSRCNy3XzaDh00cpjKaGQEwZv8=",u="AnDbF9WRSwGNdhGLJDiac1Dsg2sAY6IXISNv2O222JtR5+64e2EbcTLLfqc1bCMVHB53UVB8eG2e4XlBcKjI6A==",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 2.1",message:"j/AMqmBccCgwY02abD1CxlK1jPHZL+xXC+7n",seed:"jEB7XsKJnlCZxT6M55O/lOcbF4I=",encrypted:"AYGviSK5/LTXnZLr4ZgVmS/AwUOdi81JE5ig9K06Mppb2ThVYNtTJoPIt9oE5LEq7Wqs30ccNMnNqJGt3MLfNFZlOqY4Lprlm1RFUlfrCZ1WK74QRT8rbRPFnALhDx+Ku12g0FcJMtrPLQkB23KdD+/MBU5wlo6lQMgbBLyu/nIO"},{title:"RSAES-OAEP Encryption Example 2.2",message:"LQ==",seed:"tgDPPC5QbX8Wd4yRDTqLAD7uYdU=",encrypted:"AYdZ/x32OyeSQQViMUQWqK6vKsY0tG+UCrgtZNvxZe7jMBHadJ1Lq24vzRgSnJ5JJ32EUxErQpoiKoRxsHCZOZjnWIYcTT9tdJ2RxCkNMyx6SrP36jX/OgfUl8lV/w/8lQBrYsbSloENm/qwJBlseTQBLC35eO8pmrojmUDLoQJF"},{title:"RSAES-OAEP Encryption Example 2.3",message:"dPyIxRvJD3evnV6aSnATPUtOCzTaPDfH744=",seed:"pzdoruqpH52MHtb50rY0Z/B8yuM=",encrypted:"AYgCurBMYDJegcSWIxHyvnwq3OkwQaAHGciPlXV18sefG3vIztEVxwazEcCKLZhso7apM2sUfCnG8ilAnd7GUb0f3VoLf2EMmTf9tKOnYjZLizIGtOpIX9CY0I9j1KqLsml9Ant1DDLX906vUYDS6bZrF8svpVUjvCgNoQ0UviBT"},{title:"RSAES-OAEP Encryption Example 2.4",message:"p+sqUDaTHSfU6JEybZlpL/rdqb9+/T405iLErcCF9yHf6IUHLHiiA7FRc5vlQPqMFToQ8Ao=",seed:"mns7DnCL2W+BkOyrT7mys4BagVY=",encrypted:"AKRXjLwXYximOPun0B3xV0avRNT2zZbX58SVy/QlsJxknTK/iG2kj7r5iaIRcYfK+x+1gDF2kOPM1EaSC3r4KzHbWATYfQFRSsv6kVbngvhn9r7ZRJ4OmiwJvOzGqgh2NpZeNLPsdm8v4uQwGKL93rFAYWoOnYLlMxAk7gZS/HZB"},{title:"RSAES-OAEP Encryption Example 2.5",message:"LvKwZvhUwz873LtZlKQ15z1sbA==",seed:"6zzrvErcFrtI6IyK7A40r39Cf9M=",encrypted:"AOvF9f2nfP2tPINkGpAl531y2Kb7M6gQ9ZUPjXTHPo2THoY02GqxJGJWrge2AFtxt/L7mDUSGDMc5puP+9ydoIu8nHBPh23rnfn8LsBlyth/kJCweswXqn+ZeyespIgG6Jf3cdlRQf5FJtilMBtnhifvq3B/1A++vW55KiVhPnrs"},{title:"RSAES-OAEP Encryption Example 2.6",message:"in+zRMi2yyzy7x9kP5oyGPbhm7qJwA==",seed:"TEXPTVfJjj1tIJWtxRxInrUN/4Q=",encrypted:"AQg57CDCe5BS5Vvvubd+b8JukHXXpUN4xkar31HkRb1XFd6BeJ9W8YA9kXB2Sp6Ty3h5hpQCPuc5POBLxdj4xaUsFx1Dg346ymL2CesKpf+wlg7wQZjddU9X9/vmq/dlzxGLTKRDsjtaqyZvlSMmrEWBEAZEMl+LchrNXQT/FO86"}],f(a,l,"sha1",p),e="ArWP7AOahgcApNe2Ri+T5s3UkRYd3XT06BC0DjwWUgBqXCd7J3TBEwWky6taeO+lfheobfej+jb8Sx0iSfIux8LdakYyMqzOqQbWbr6AtXBLEHKdpvgzI0q7Xv3UopLL+tM7TTP6ehS4w5e1bjrNISA0KLd836M6bacGs9iw/EPp",t="AQAB",n="FbSKW1aDqUZw4jtXGPgU+g4T+FA49QcRGCy6YVEFgfPSLH4jLvk34i5VHWi4bi+MsarYvi5Ij13379J54/Vo1Orzb4DPcUGs5g/MkRP7bEqEH9ULvHxRL/y+/yFIeqgR6zyoxiAFNGqG3oa/odipSP0/NIwi6q3zM8PObOEyCP0=",r="Ab8B0hbXNZXPAnDCvreNQKDYRH0x2pGamD9+6ngbd9hf43Gz6Tc+e2khfTFQoC2JWN5/rZ1VUWCVi0RUEn4Ofq8=",i="AY0zmWWBZts4KYFteylUFnWenJGYf1stiuzWOwS0i9ey/PIpu3+KbciLoT3S45rVW20aBhYHCPlwC+gLj9N0TOc=",s="BsCiSdIKby7nXIi0lNU/aq6ZqkJ8iMKLFjp2lEXl85DPQMJ0/W6mMppc58fOA6IVg5buKnhFeG4J4ohalyjk5Q==",o="0dJ8Kf7dkthsNI7dDMv6wU90bgUc4dGBHfNdYfLuHJfUvygEgC9kJxh7qOkKivRCQ7QHmwNEXmAuKfpRk+ZP6Q==",u="jLL3Vr2JQbHTt3DlrTHuNzsorNpp/5tvQP5Xi58a+4WDb5Yn03rP9zwneeY0uyYBHCyPfzNhriqepl7WieNjmg==",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 3.1",message:"CHggtWno+o0=",seed:"jO1rGWKQgFeQ6QkHQBXmogsMSJQ=",encrypted:"AmoEhdlq69lrQ4IIUJm5Yuaivew9kMjbYl4UNy3oXi1be6q2XI+vkbtVBPtJWvzlyYiz9qUuIOHWy9NWbFzR8rgxi7VCzA6iXEqrmTKvogdg6t3seEOWoH6g7yTU5vTTflBSp6MeFGqkgKERu+kmQBMH4A9BADOEK22C/lzk366A"},{title:"RSAES-OAEP Encryption Example 3.2",message:"RlOsrxcZYLAfUqe+Y6OrIdw2jsQ7UNguw3geBA==",seed:"tCkdZWdVCEjMFWlnyAm6q2ylB/A=",encrypted:"Ak24nHgCmJvgeDhHhjCElBvyCddhmH44+Xy19vG8iNpypQtz668RyHnE+V3ze4ULj2XXYi4lsbiJ6A/oC6yiBp1uDh2CmVP8RZBp3pjql5i0UeVX6Zq/j+PZzPkJbrvz5SVdO04cbS7K3wZ6NZ7qhkBazUfV4WVRfMr9R9bb7kv1"},{title:"RSAES-OAEP Encryption Example 3.3",message:"2UzQ4I+kBO2J",seed:"zoko9gWVWCVACLrdl5T63NL9H2U=",encrypted:"Ajm85oEDJEFSiHfW0ci7KKo7yX8d9YRWNhiZV5doOETKhmZHMvS+16CqsIOqq/tyOPWC4wlYwgJOROVwQ7l5UP1UPal3yQzd5TN9YYRC+Z5g13g6tZzm3Z1pxHrR6WK+wi0FiVz/jT9k7VJh2SsmeFEDk0hJkLo/fwaBiub/zoo6"},{title:"RSAES-OAEP Encryption Example 3.4",message:"bMZBtrYeb5Y5dNrSOpATKE7x",seed:"bil59S1oFKV9g7CQBUiI8RmluaM=",encrypted:"AplMYq/Xb0mLof0s9kKFf8qB9Dc8sI8cuu5vAlw7UStCw+h3kRNHZkgDnb4Ek/kkYpL6wolQYA58DzLt+cgbnexFw73gzI2IR1kBaZB7fcWZHOspuwcU1hPZbfDxLsXY01B8jueueN2D8hb6Yd4QA2OspIp+kUrp9C3fvpQ7Cdmg"},{title:"RSAES-OAEP Encryption Example 3.5",message:"31FRgyth9PJYkftBcvMo0u3fg3H/z9vpl5OSlfMOymkYAXz9oRU796avh1kyIw==",seed:"LXYL/jjFneNM3IuMeKOOZihKLSc=",encrypted:"AWIEL/aWlZKmFnAxgRojmDTOY4q/VP7IuZR4Eir+LuZ/jFsYsDOYBb/bxaTmcgs3xZz7qUJGTFl/9TKhGYIVRf0uWbEU5h2vcYIFKfUCnPUklUMnw07F5vW6fvzE3pQ6uK1O14exRUMp9w23mKOo9NkvgnTispSK3mJ86O4z5Dxg"},{title:"RSAES-OAEP Encryption Example 3.6",message:"PDutiTxUSm1SCrAiMZGIyNUEt6eIuFCQO4WXLqoYVS4RNKetYJiCYlT/erZys9jrMVj6xtTLrvE=",seed:"8XR3nF/Tz+AHuty3o2ybVb/Pvw4=",encrypted:"ABEgUeddBklDvER4B15DSC/VnO4Ged5ok+7DqUPapJC5aRyT38BGS2YjufPb0+cAgyZPA0s3T3QWThoAdjcl5XR0S6C524NDTzHflvbiom9tjro0i9RobCI4rAfDeqw3hdHH7qL4Gf2RSReY7Y6c715Dt4Gw4CduN8Q/+UktAFcw"}],f(a,l,"sha1",p),e="BRJAtswABPpI0BNGccB4x8jew7Pi8lvCVkRnM52ziFPQa4XupbLeNTv/QqwuRryX+uaslhjalTelyPVTweNXYlmR1hCNzXiF+zolQT9T78rZSMs1zZua6cHGdibRE9V93kxb6na7W7felsANBzculoWm11z50jn6FI1wkxtfP7A5",t="AQAB",n="BBH/yjt8penpvn/jioUQXjU4ltsFxXlq7NKnJRYes2UchimpuGK5BNewx7N/jLWhwrVAAQGKAKHrLK/k7k6UksNIvCvtq0ueu/Bk6O/zIrkAn47sZTkF9A34ijzcSdRWf3VifUGspiQSm0agt8aY5eZfK3uhAsdJoQE1tlQNBAE=",r="AnRYwZ7BY2kZ5zbJryXWCaUbj1YdGca/aUPdHuGriko/IyEAvUC4jezGuiNVSLbveSoRyd6CPQp5IscJW266VwE=",i="AhDumzOrYXFuJ9JRvUZfSzWhojLi2gCQHClL8iNQzkkNCZ9kK1N1YS22O6HyA4ZJK/BNNLPCK865CdE0QbU7UTk=",s="OfoCi4JuiMESG3UKiyQvqaNcW2a9/R+mN9PMSKhKT0V6GU53J+Sfe8xuWlpBJlf8RwxzIuvDdBbvRYwweowJAQ==",o="AV2ZqEGVlDl5+p4b4sPBtp9DL0b9A+R9W++7v9ax0Tcdg++zMKPgIJQrL+0RXl0CviT9kskBnRzs1t1M8eVMyJk=",u="AfC3AVFws/XkIiO6MDAcQabYfLtw4wy308Z9JUc9sfbL8D4/kSbj6XloJ5qGWywrQmUkz8UqaD0x7TDrmEvkEro=",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 4.1",message:"SoZglTTuQ0psvKP36WLnbUVeMmTBn2Bfbl/2E3xlxW1/s0TNUryTN089FmyfDG+cUGutGTMJctI=",seed:"HKwZzpk971X5ggP2hSiWyVzMofM=",encrypted:"BMzhlhSEXglBUqP+GOVOMzDETl77xkrhaIbLGGkBTMV4Gx+PngRThNARKhNcoNEunIio5AY0Ft6q44RPYNbpb+FVFF9FJbmjRDHKN2YYD3DhWl5djosaUW/4cGCfE/iWk1ztGIJ5pY7RPQcRQnfXXGVoYH4KsJL9gDoiPkqO4LGo"},{title:"RSAES-OAEP Encryption Example 4.2",message:"sK3E8/4R2lnOmSdz2QWZQ8AwRkl+6dn5oG3xFm20bZj1jSfsB0wC7ubL4kSci5/FCAxcP0QzCSUS7EaqeTdDyA==",seed:"9UXViXWF49txqgy42nbFHQMq6WM=",encrypted:"AJe2mMYWVkWzA0hvv1oqRHnA7oWIm1QabwuFjWtll7E7hU60+DmvAzmagNeb2mV4yEH5DWRXFbKA03FDmS3RhsgLlJt3XK6XNw5OyXRDE2xtpITpcP/bEyOiCEeCHTsYOB3hO7SarqZlMMSkuCcfPq4XLNNm4H5mNvEBnSoortFe"},{title:"RSAES-OAEP Encryption Example 4.3",message:"v21C5wFwex0CBrDItFoccmQf8SiJIZqCveqWW155qWsNAWPtnVeOya2iDy+88eo8QInYNBm6gbDGDzYG2pk=",seed:"rZl/7vcw1up75g0NxS5y6sv90nU=",encrypted:"AwH5NenEery0isu+CYldn1lxrxSDnaT/lUF+5FPR/XcxkHK7cpfhtV11Yc2dG7JMGpo3xhmGQwgkKASHnYbr0AHc5Rg5deFQaYm3DlqDQ0FU1cv9aiR4fmDrDGWNKsGTMC0RksbmItShKtS1OSO8okbfMcY5XjdwLGp4rggfudBl"},{title:"RSAES-OAEP Encryption Example 4.4",message:"+y7xEvXnZuuUAZKXk0eU974vb8HFjg==",seed:"E2RU31cw9zyAen5A2MGjEqxbndM=",encrypted:"AtEQrTCvtye+tpHdDPF9CvGh5/oMwEDsGkuiakLFnQp5ai4iyPNXzMmLZRms62gulF5iy3NGFKUpQHzUUr7j5E/s6EI8wZ5VVIuLmUuEnH7N5JM+dgN+HQzkQnWwhxDGjkMBMLkpcw7XfgmwFWQsVZPwTk/7lBB5gQKo6W/9/hHk"},{title:"RSAES-OAEP Encryption Example 4.5",message:"KMzUR7uehRZtq7nlt9GtrcS5058gTpbV5EDOmtkovBwihA==",seed:"vKgFf4JLLqJX8oYUB+72PTMghoE=",encrypted:"ANu4p0OdkO/ZGaN3xU+uj+EexYw7hYNi4jrRuKRDEHmQZrmTR6pSVpHSrcWNmwbjTyiMFwOQxfDhHAqjZFlZ8Y7nno8r6NesXCPQYfGN10uMXypY/LXrDFT5nwGoMkdWgpJTZYM0CUjXqMl8Ss0emNHincMg6XomBTKoqnp1ih7C"},{title:"RSAES-OAEP Encryption Example 4.6",message:"8iJCdR7GsQ==",seed:"Ln4eF/ZHtd3QM+FUcvkPaBLzrE4=",encrypted:"AKX/pHaMi77K7i23fo8u7JlZWTNUVSCDXlun25ST0+F83e/mpfVnYkRxkI204tg6D77mBgj8hASVA7IjSgfcg7J7IoR62JIP9C9nTvebdigLACM9K1G4yycDqdQr+8glDJbsMsBR5X8bS6Uo24nDfkxU4n5uZKxpY1roh9lUFhmp"}],f(a,l,"sha1",p),e="Cq3z+cEl5diR8xrESOmT3v5YD4ArRfnX8iulAh6cR1drWh5oAxup205tq+TZah1vPSZyaM/0CABfEY78rbmYiNHCNEZxZrKiuEmgWoicBgrA2gxfrotV8wm6YucDdC+gMm8tELARAhSJ/0l3cBkNiV/Tn1IpPDnv1zppi9q58Q7Z",t="AQAB",n="AlbrTLpwZ/LSvlQNzf9FgqNrfTHRyQmbshS3mEhGaiaPgPWKSawEwONkiTSgIGwEU3wZsjZkOmCCcyFE33X6IXWI95RoK+iRaCdtxybFwMvbhNMbvybQpDr0lXF/fVKKz+40FWH2/zyuBcV4+EcNloL5wNBy+fYGi1bViA9oK+LF",r="A7DTli9tF1Scv8oRKUNI3PDn45+MK8aCTyFktgbWh4YNrh5jI5PP7fUTIoIpBp4vYOSs1+YzpDYGP4I4X0iZNwc=",i="AuTDLi9Rcmm3ByMJ8AwOMTZffOKLI2uCkS3yOavzlXLPDtYEsCmC5TVkxS1qBTl95cBSov3cFB73GJg2NGrrMx8=",s="AehLEZ0lFh+mewAlalvZtkXSsjLssFsBUYACmohiKtw/CbOurN5hYat83iLCrSbneX31TgcsvTsmc4ALPkM429U=",o="65CqGkATW0zqBxl87ciBm+Hny/8lR2YhFvRlpKn0h6sS87pP7xOCImWmUpfZi3ve2TcuP/6Bo4s+lgD+0FV1Tw==",u="AS9/gTj5QEBi64WkKSRSCzj1u4hqAZb0i7jc6mD9kswCfxjngVijSlxdX4YKD2wEBxp9ATEsBlBi8etIt50cg8s=",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 5.1",message:"r3GpAeOmHTEy8Pwf20dPnqZXklf/wk0WQXAUWz296A==",seed:"RMkuKD93uUmcYD2WNmDIfS+TlGE=",encrypted:"A2BGpKR9ntO6mokTnBBQOOt0krBaXWi/1TrM/0WX96aGUbR7SkYn2Sfkhe7XtFZkIOi0CYeeXWBuriUdIqXfeZ95IL/BF7mSVypTsSYxRrzqAzhcxehTyaEByMPhvaMaUZgHSWxsteXvtAiCOjUrj6BmH7Zk763Vk965n/9e0ADl"},{title:"RSAES-OAEP Encryption Example 5.2",message:"o7hEoII5qKxBYFrxemz9pNNQE2WFkDpBenkmh2BRmktKwzA+xz8Ph8+zI5k=",seed:"yyj1hgZZ/O7knD7q/OYlpwgDvTI=",encrypted:"A9brZU7c5hW8WfRVJl7U5aGCI8u5vk5AabRzgE1d6W9U3KqmA9BJxdlKoUcN/NIlQGa3x7Yf8fb2dw4yFcUTmf1ONOxQgrxI8ImECtBDVK5m3A8b0Y5GGjPMEli0Q6KDem3yZ1mqIwIzSYb4c4DJzJ1Tvp+ZYF0smpfaewkVpKet"},{title:"RSAES-OAEP Encryption Example 5.3",message:"MIsOy9LHbLd/xvcMXt0jP9LyCSnWKfAmlTu2Ko9KOjFL3hld6FtfgW2iqrB00my2rN3zI647nGeKw88S+93n",seed:"IoX0DXcEgvmp76LHLLOsVXFtwMo=",encrypted:"B3CVIYFkn5+fB/9ib/OiLDXEYkQ9kF1Fap/Qv/Q8rCynqfVU6UeLmsw6yDiwIED/0+GEfeLkJTkp+d2e5ARDJamwXKu4CLLuhA004V0QWj8feydpWhoHotc/4I7KqjycnU1aif+JDVRyfXrkDA7BqN2GFl2O4sY2gUEBaki1W2ln"},{title:"RSAES-OAEP Encryption Example 5.4",message:"FcW57hGF",seed:"SfpF06eN0Q39V3OZ0esAr37tVRM=",encrypted:"CBK3Z2jry2QtBAJY5fREGgGFIb2WaH5sXomfzWwXWI/1moLMiuA6S0WzEpmvF4jDKffc0oX4z0ztgmBrl2EmcaRb7coTNEIUTRYX0RT4AoV/D51zl1HFej+e5ACRLGHi5pkr4DGkPdSPproU7vfEIrXtxOevoE/dOPQC0ci7cZq/"},{title:"RSAES-OAEP Encryption Example 5.5",message:"IQJuaADH+nKPyqug0ZauKNeirE/9irznlPCYX2DIpnNydzZdP+oR24kjogKa",seed:"8Ch0EyNMxQNHJKCUxFhrh6/xM/w=",encrypted:"B7YOFOyVS/0p5g0AR+eJ9R1XGGxjWJkDMGeTztP2gkHHQ1KaumpjdPkuGeAWPvozaX4Zb3Zh36qkeqxr3l5R3rUHxyxYmiyhaT2WsUYDgSSbLNuerER2nySJxdPS+Z8O48fuW/ZKWsecQr1DPxSb6MtZVINhZAWVUTyXr3vCUJcj"},{title:"RSAES-OAEP Encryption Example 5.6",message:"VB43totsiHK4TAI=",seed:"2fukXJbyHm4m0p6yzctlhb6cs0E=",encrypted:"CMNtTdozQjsu1oMNhfZBG6Hc9HCh+uDr7+58CJ8lbO90y5bqacOPYPOavuRBKby0yS3n95diOyAHTj2cKJlwHtkHHh76C92E1MPlEwMC2PAkC6ukuEpxzAMvIjWl/w+uJ3w+j5ESvvRMmuINF1/JpAWL/JMLoxsC4uT0REg3EPJK"}],f(a,l,"sha1",p),e="ErF/ba0uzRn/RtwT94YPCeDgz7Z3s4pSWSMFzq8CLBZtuQ0ErCnjP33RLZ+vZuCBa7Y+rSZ8x9RsF8N74hS8oqItcjpk5EQHQ2tvyWVymu/CVU83bNXc6mgpN4CmK/OdAClIWhYLu55dwJctIaUE9S5e4CiqQWMy9RCy6c/19yKv",t="AQAB",n="ApXso1YGGDaVWc7NMDqpz9r8HZ8GlZ33X/75KaqJaWG80ZDcaZftp/WWPnJNB7TcEfMGXlrpfZaDURIoC5CEuxTyoh69ToidQbnEEy7BlW/KuLsv7QV1iEk2Uixf99MyYZBIJOfK3uTguzctJFfPeOK9EoYij/g/EHMc5jyQz/P5",r="BKbOi3NY36ab3PdCYXAFr7U4X186WKJO90oiqMBct8w469TMnZqdeJpizQ9g8MuUHTQjyWku+k/jrf8pDEdJo4s=",i="BATJqAM3H+20xb4588ALAJ5eCKY74eQANc2spQEcxwHPfuvLmfD/4Xz9Ckv3vv0t1TaslG23l/28Sr6PKTSbke0=",s="A5Ycj3YKor1RVMeq/XciWzus0BOa57WUjqMxH8zYb7lcda+nZyhLmy3lWVcvFdjQRMfrg6G+X63yzDd8DYR1KUs=",o="AiGX4GZ0IZaqvAP6L+605wsVy3h9YXrNMbt1x7wjStcG98SNIYLR8P+cIo3PQZZ7bAum0sCtEQobhXgx7CReLLE=",u="BAHEwMU9RdvbXp2W0P7PQnXfCXS8Sgc2tKdMMmkFPvtoas4kBuIsngWN20rlQGJ64v2wgmHo5+S8vJlNqvowXEU=",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 6.1",message:"QEbKi6ozR8on9J4NgfnMHXG+m6UX1A==",seed:"3Q9s/kFeiOWkaaUfu6bf1ArbQ4Q=",encrypted:"BjDuvNKFbCT3mIBuQfnmc0Xtqc7aOGrMn6yuoe7tBqzlg3CXGNnRafrfQU1cdvkploM+8wW3Wx5LlfZiog+u3DuuDEgnqL+KiO29V+wgOieoQfAuQ6YVurGoysBwHeNN6972KgiAibVew26nUi/T7I0GtqBz5t+DMVO8Cu/ZO9Gj"},{title:"RSAES-OAEP Encryption Example 6.2",message:"XMcsYCMd8Ds9QPm1eTG8MRCflyUn8osZ50gMcojLPJKyJRIhTkvmyRR5Ldq99X+qiqc=",seed:"jRS9lGoTURSPXK4u2aDGU+hevYU=",encrypted:"Drw3N2FzpP0vicxVwspismsR1Rw8fOSeiEX3TnYHMXxDa8jSO5Zn3+udCHI0tHvGg3F1rlwFWfa4HX0iQW0+UPSsUz2PCBLy2555H+nHdayLatD1Na2c6yOkoCAUxYqz+NMWFJmiYPOTSOcUriodNEMgj9i3Isz9+zk+mAEfmeY/"},{title:"RSAES-OAEP Encryption Example 6.3",message:"sg5lEwMJL0vMtDBwwPhtIwSTYu2WZC/FYywn20pS49gx8qsGiyOxSYecAC9r8/7ul1kRElYs",seed:"bAdbxFUg8WXAv16kxd8ZG8nvDkQ=",encrypted:"Cpi/EJNhk5RDbPaNjzji8Vj96OpU80NfI5uNBrgyGEQgJHau7ZYAlJJIDOOo1wVJjEyMaPAVAdyB22CPYAhzUMjDsL0unvaoFFi3yAG4ny5P6Z1JALpqS15althl3Gdsd1WSh5QTDWKAqBYKGQ8t8+p8+aoCcdiOnmkF7PHFFS1l"},{title:"RSAES-OAEP Encryption Example 6.4",message:"aE4wOMXAQfc=",seed:"O7w71mN9/hKEaQECm/WwwHEDQ5w=",encrypted:"AI56Z8rPtcTiS+x97hSRF/GVmM6MRYCP74jGCP+c1uaVJjuaPArUuLpMlSOOlqhCK4U1YpyNU4I3RHmtE/o5l0skL5p1nur5yDrVqMoYlAoBYrp1WHbfJj9L1QxlJcVgkCZ8Hw4JzgiZoM81nogSCr2b+JNEWzyud9Ngc1mumlL4"},{title:"RSAES-OAEP Encryption Example 6.5",message:"MkiMsmLQQdbk3TX5h788ppbbHwasKaRGkw==",seed:"tGtBiT6L7zJvZ1k4OoMHHa5/yrw=",encrypted:"AAA0dEFse2i9+WHDhXN5RNfx9AyzlTQ8aTzAtP5jsx/t8erurJzMBnizHcMuCXdIlRTE8JCF9imKllPwGupARf9YLuiHviauV1tz7vfzd0kh43Wj0ZrdoMoxqhhJiHwfQsrJZ396L06SP25ahos4wITvGHWU3J9/BI/qLgKVU4Sr"},{title:"RSAES-OAEP Encryption Example 6.6",message:"ULoUvoRicgJ5wwa6",seed:"CiQDMSpB49UvBg+8E6Z95c92Cac=",encrypted:"CgJt2l/IeF972b91Mntj6F4sD97l2ttl69ysmuHelcksZyq0M6p6jmnOam2Il/rErEpU3oQa5eW7znaHh515Y0zqejBoQGXHFNUkCbkoJWu/U+q81SMetyWVBFNzmb0pFktybTOkbacBNgpBaKCRzKty1Epi/tJGwP/qWxNIq1Rw"}],f(a,l,"sha1",p),e="MRF58Lz8m508oxXQDvMNe906LPrpkRv+3LlIs6R4LQcytqtEqkvwN0GmRNwBvsPmmwGgM+Z12KzXxJJcaxrsMRkFHf2Jdi0hXUVHX/y1n5CBSGI/NxdxVvauht16fF9D3B4fkIJUBYooSl8GwAIXk6h/GsX+/33K7mnF5Ro3ieNz",t="AQAB",n="Bwz8/y/rgnbidDLEXf7kj0m3kX1lMOHwyjRg8y4CdhdEh8VuIqRdJQDXd1SVIZ19Flqc872Swyr5qY2NycwpaACtyUoKVPtA80KRv4TujqErbxCTWcbTVCpQ+cdn9c//BaaBwuZW+3fKqttL6UaNirzU35j1jobSBT+hNJ90jiGx",r="B0kmLBEc1HDsJWbms3MvwJMpRpqhkHHTucAZBlFMbx0muqFL6rCXHIt+YRpPeQCdb+p3aSjKJShbDeNkPRo/jHE=",i="BrweUOlsAr9jbp7qi4mbvr92Ud533UdMPpvCO62BgrYZBMfZffvr+x4AEIh4tuZ+QVOR1nlCwrK/m0Q1+IsMsCM=",s="A7x+p/CqsUOrxs6LlxGGNqMBcuTP4CyPoN2jt7qvkPgJKYKYVSX0iL38tL1ybiJjmsZKMJKrf/y/HVM0z6ULW/E=",o="AmKmqinCo8Z9xTRsBjga/Zh6o8yTz7/s9U/dn514fX9ZpSPTmJedoTei9jgf6UgB98lNohUY3DTLQIcMRpeZStk=",u="ZJ1MF7buFyHnctA4mlWcPTzflVDUV8RrA3t0ZBsdUhZq+KITyDliBs37pEIvGNb2Hby10hTJcb9IKuuXanNwwg==",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 7.1",message:"R6rpCQ==",seed:"Q90JoH/0ysccqkYy7l4cHa7kzY8=",encrypted:"FojkzneUu6bLcBQWns1VnO3iowtWpSto2f4Yzxlz75eyoDFTlRx1X2KUqkmtvbVYRatodfs5hsk+z5J5YoQNKC+eVM6LaQ98DLi71zRA2VcdGxbNkmD56rR4PMSC5SI9xglzhxeD7Cewrg/UdzLLwoahc/ySsA+0umgkZHzZPIXB"},{title:"RSAES-OAEP Encryption Example 7.2",message:"HZsuIiPZvBO/ufFiznNdtIunxo9oIqChp7auFlg05w==",seed:"Opw87HuE+b063svGc+yZ1UsivJs=",encrypted:"EFLtOXsuAeHQ7hxQvyQ2P5XlBPSgNDSgj9giV07WuXNu27XzkNsQMhR5qKE5NQ4r1Jd8N3jvMx8+eK4RiyaEUfIKLwHUcfXVPFZpNxcbLbwtS95FmleZ8DctZXQjmyMj0kXQu4HChrY8iaNhAXM35JAviKRn9MfyRL/Vq0ZDf/O2"},{title:"RSAES-OAEP Encryption Example 7.3",message:"2Xb8",seed:"dqdeW2FXpVbPiIS7LkXCk91UXPU=",encrypted:"IVXNhD/ySk7outt2lCYAKKSQgTuos2mky/EG7BSOUphwf1llvn0QHBBJ6oWEwkzWNFWtnBBNaGKC0/uAOkwRwcLpuRxxeIAdG2ZA8AP1co3wB7ikzMkrzgXkGicnjXyFAYxSQUMTpQd3iQAdTwGRC3Kq0F0iCqFKWHM6dIm8VFVr"},{title:"RSAES-OAEP Encryption Example 7.4",message:"1HOGI98iOqQ4Q9+EZ1NMQdAT4MgDxiTiY2ZrI5veQKXymuuN5549qmHdA3D0m9SwE4NLmCEq72scXuNzs8s=",seed:"eGYxSmrW8rJQo1lB2yj1hktYWFk=",encrypted:"CrFMNzrrfUMo0KqtjAlNiLnrCYuV8hBUopCCUivnwnoxKHi2N5F+PYGebDxWjbXYQ4ArBtUdnpiivgv0DAMUI7AO37/4Mg77kXG9IERlOky5xRIvbGXoPNouw8EmAnqcGla6h00P6iPzgLgs8kC4z1QABHWMTHfZNBV6dPP8Er+s"},{title:"RSAES-OAEP Encryption Example 7.5",message:"u0cjHKXqHTrUbJk0XZqKYQ==",seed:"shZu1HLVjbEMqyxrAAzM8Qp9xQk=",encrypted:"AoOHoxgndDR5i02X9GAGjfUpj6ulBBuhF2Ghy3MWskGEEU7FACV+JYntO2B6HrvpemzC4CvxtoH0IxKjO3p32OeFXEpt4D48BGQ/eGuRomSg1oBeLOqR5oF363pk2SVeTyfnE7fM7ADcIA69IcLqK7iQ/q5JQt+UHcP5eJDtNHR4"},{title:"RSAES-OAEP Encryption Example 7.6",message:"IYSCcJXTXD+G9gDo5ZdUATKW",seed:"Umc73iyhZsKqRhMawdyAjWfX07E=",encrypted:"FMZ4qUrWBSXvOelZsvO6XAl6lP+RK2fbrOgFNcGHq9R9B1QgsYchUrugj3/DHzE7v5JzyRL8TAFJqbDPt5gH40brMyBpYRvsD/m80Wjx98M+dzE86kVLlOJUnuzwAuKs9/by0oRdT+CqsuWpLd9oxICuESR5NdH2JXSEIhauZ0EV"}],f(a,l,"sha1",p),e="W98OMNMh3aUUf4gkCPppGVSA34+A0/bov1gYUE82QnypsfVUC5xlqPaXTPhEeiRNkoAgG7Sfy75jeNGUTNIn4jD5bj0Q+Bnc7ydsZKALKktnAefQHeX6veOx6aDfgvRjE1nNImaWR/uxcXJGE07XtJfP/73EK1nHOpbtkBZiEt/3",t="AQAB",n="D30enlqqJf0T5KBmOuFE4NFfXNGLzbCd8sx+ZOPF6RWtYmRTBBYdCYxxW7eri9AdB+rz/tfH7QivKopi70SrFrMg4Ur3Kkj5av4mKgrkz2XmNekQeQzU7lzqdopLJjn35vZ3s/C7a+MrdXR9iQkDbwJk9Y1AHNuhMXFhV6dez2Mx",r="CgLvhEjZ+ti70NAEyMKql1HvlyHBsNAyNqVLDflHy67VolXuno4g1JHqFyP+CUcEqXYuiK/RbrtZlEEsqWbcT58=",i="CS02Ln7ToL/Z6f0ObAMBtt8pFZz1DMg7mwz01u6nGmHgArRuCuny3mLSW110UtSYuByaxvxYWT1MP7T11y37sKk=",s="B8cUEK8QOWLbNnQE43roULqk6cKd2SFFgVKUpnx9HG3tJjqgMKm2M65QMD4UA10a8BQSPrpoeCAwjY68hbaVfX0=",o="rix1OAwCwBatBYkbMwHeiB8orhFxGCtrLIO+p8UV7KnKKYx7HKtYF6WXBo/IUGDeTaigFjeKrkPH+We8w3kEuQ==",u="BZjRBZ462k9jIHUsCdgF/30fGuDQF67u6c76DX3X/3deRLV4Mi9kBdYhHaGVGWZqqH/cTNjIj2tuPWfpYdy7o9A=",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 8.1",message:"BQt1Xl5ogPe56daSp0w3quRJsxv+pt7/g3R6iX9sLIJbsa2/hQo8lplLXeWzPLx9SheROnln",seed:"dwb/yh7PsevuKlXlxuJM0nl6QSU=",encrypted:"CbNoPYousPspW2LtH7kpC3FEV7eCUxn0ZHhyr4ibMECUcgIK0SkSvxmxHUgZ9JYUgk/9hNCcChfn0XMJ0SkZeQQQqimVaZ9qhtvjJCtazCOvRWkQgNaxroEPs+MFcIfwlwCSzgC+lWL/QFO2Jizgyqk+E3I9LjpboHXUXw1htUth"},{title:"RSAES-OAEP Encryption Example 8.2",message:"TraNzZPKmxnfERvUNgj1VwJv5KodXPrCJ6PrWrlUjBigbd7SP4GCWYay/NcRCezvfv+Ihz8HXCqgxGn2nJK8",seed:"o3F9oUO03P+8dCZlqPqVBYVUg0M=",encrypted:"Ls8VyXxaFbFHaumGs3G1eiQoT0oWKo0MgYLnkF55IlbxgSul+D8fehMOQtzAIjKETtwUoxpo7peuVko4OjQRZWQkxfYt22Rgk8Nnvh/NpCbPAKBtist+V3dvu9hVrD31BvwWsdfD8hEPPYBo6R4YY2ODHIQJaA2NqezYzx+iDuOd"},{title:"RSAES-OAEP Encryption Example 8.3",message:"hgSsVjKMGrWtkXhh",seed:"7gYgkHPMoCa7Jk5Rhb+MaLdzn4Y=",encrypted:"S8iRMKWy2rt8L8+Q610Or55oG3FGo48xc6PZz+xS6p4KQZMuZIqdaTRMUNp2P1GgPJV2ITHoBSJU3NIkjLpA/TFmd4bOBaK3tTGsnaye1YSlm2d8GortjF0V1owFVp4r54C/fbY4/Sv9KoWrJ2hg83dzOPypif/XQ9E+4I4MqYk/"},{title:"RSAES-OAEP Encryption Example 8.4",message:"/dpfv27DYanZpKxoryFqBob0OLHg5cNrlV904QfznA3dzA==",seed:"mQrVc9xIqXMjW22CVDYY8ulVEF0=",encrypted:"LkVoR9j8Nv8BR9aZNZS5OXIn1Xd1LHnQ+QT8sDnU2BL+pgWntXTdgsp4b5N1I0hDjun1tUVJhdXw4WmePnrRdaMuFfA96wQquf4d2dsbuG+MCJzLRefvDF7nyptykMprFb7UcDl4ioqT/4Pg6NYkTHEAY2Le72m29Bb7PGhDg/vQ"},{title:"RSAES-OAEP Encryption Example 8.5",message:"Sl9JFL7iXePGk0HeBw==",seed:"7MY7KPB1byL1Ksjm7BJRpuwwRxg=",encrypted:"H7k1b9XEsXltsuv30NOTzIEK32FF3vwvznFPedk4ANXirCEeqLvsyktlS5TDsYsw3Vds403JVDbvV6CUFWRZIzWaXXtBce8iwkZw8bIp02A+kfdmcbffl+cxfJdzRHbV89F9Ic+Ctbqfg98uWI02mE/RtYRGi9I7LodfMvaJU/ey"},{title:"RSAES-OAEP Encryption Example 8.6",message:"jgfWb3uICnJWOrzT81CSvDNAn7f4jyRyvg==",seed:"OSXHGzYtQKCm3kIUVXm6Hn3UWfw=",encrypted:"Ov2cZgAUeyF5jYGMZVoPTJIS2ybQsN/cKnWUzLPSL1vx18PhEs1z/H1QnHqLr908J00TmQCflgnsS+ZHfkU/B1qjPbOChwwcNAmu85LXOGrjppa5mpS02gWJRH6VXRbJixdgKlm9c2J5/Nj7KAxEYtWQv6m/E/7VcOr96XMwosIQ"}],f(a,l,"sha1",p),e="zyzUHjTKOnKOpcuK/2TDbSe971Nk4zb9aNMSPFoZaowocBPoU9UVbVjRUZVFIPtPbXsXq7aBd2WQnFdhGWWdkCsZBu2KKxDBVcJNEkUo2rnurjeb6sZuSkEXhty4/QBi68Aw3hIZoEwqjBt90xMeTWtsruLjGl7UGsFQmy7x7iqxg2S+VoypQcJezIT/nWQ7XsGqrhAqINc/R5t4D9bakQdSEtnqwDoGdNiZ66LkMfTES2Fba6IjK9SzO67XPWJd",t="AQAB",n="GYwUHiNxWpK8z2oRmlvBE4lGjSgR9UjXJ+F7SrDrmG1vIR77U7cffMvqh+5px17mFQCMUzLetSvzkKvfv+N9cgU2gVmyY4wd4ybiHSIlHw+1hIs78VAF0qdDMPCv6RbuYszBNE0dg6cJ5gZ2JzhA9/N3QkpeCk2nXwGzH/doGc+cv90hUkPDkXwD7zgZkxLlZ7O/eu06tFfzce+KFCP0W2jG4oLsERu6KDO5h/1p+tg7wbjGE8Xh6hbBHtEl6n7B",r="/I1sBL7E65qBksp5AMvlNuLotRnezzOyRZeYxpCd9PF2230jGQ/HK4hlpxiviV8bzZFFKYAnQjtgXnCkfPWDkKjD6I/IxI6LMuPaIQ374+iB6lZ0tqNIwh6T+eVepl79",i="0gDUXniKrOpgakAdBGD4fdXBAn4S3BoNdYbok52c94m0D1GsBEKWHefSHMIeBcgxVcHyqpGTOHz9+VbLSNFTuicEBvm7ulN9SYfZ4vmULXoUy//+p0/s3ako0j4ln17h",s="2xaAL3mi8NRfNY1p/TPkS4H66ChiLpOlQlPpl9AbB0N1naDoErSqTmyL6rIyjVQxlVpBimf/JqjFyAel2jVOBe8xzIz3WPRjcylQsD4mVyb7lOOdalcqJiRKsI23V1Kt",o="oKMXz+ffFCP4em3uhFH04rSmflSX8ptPHk6DC5+t2UARZwJvVZblo5yXgX4PXxbifhnsmQLgHX6m+5qjx2Cv7h44G2neasnAdYWgatnEugC/dcitL6iYpHnoCuKU/tKh",u="CyHzNcNTNC60TDqiREV4DC1lW5QBdMrjjHyKTmSTwLqf0wN0gmewg7mnpsth5C2zYrjJiW23Bk4CrVrmFYfaFbRknJBZSQn+s328tlS+tyaOyAHlqLSqORG+vYhULwW+",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 9.1",message:"9zX9VbqSWSw7Urj5xPaaqhy++P6IrdCVWVQSRn+c9OwLiWxZ7aFiEOdUnIq7EM28IaEuyba1uP0vEDmetg==",seed:"jsll8TSj7Jkx6SocoNyBadXqcFw=",encrypted:"JnvNEYrKsfyLqByF1zADy4YQ+lXB2X2o1Ip8fwaJak23UaooQlW502rWXzdlPYKfGzf5e4ABlCVFsvwsVac3bKehvksXYMjgWjPlqiUmuNmOMXCI54NMdVsqWbEmMaGCwF1dQ6sXeSZPhFb1Fc5X399RLVST2re3M43Et9eNucCRrDuvU3pp/H9UnZefDv+alP2kFpvU0dGaacmeM8O1VJDVAbObHtrhGP9nk6FTJhWE06Xzn25oLj0XyM0SYfpy"},{title:"RSAES-OAEP Encryption Example 9.2",message:"gbkGYFAVpjqr5C3fEeGXiRL1QEx0dLJtzj7Ugr+WHsyBi/QgxUZZ",seed:"7LG4sl+lDNqwjlYEKGf0r1gm0Ww=",encrypted:"k6yfBnHsKay7RE7/waV0E1HWD9sOOT+/dUrPDeSXYaFIQd93cum8gnc5ZqFYTE1yuuoAEY+D81zKblN8vU2BH1WDspeD2KbZTNMb5w1vUmwQ/wnG+nzgaXlaP80FEf1fy1ZLzIDqnHjzi4ABJTnYpN32/oHpzdt/UNu7vMfl2GCXzPTsSRifuL8xi+bVoHFdUWtJrxkSWM0y3IM85utGc8A6Gbus6IzFSJX2NswMHsiQltEc4jWiZcoXZCMqaJro"},{title:"RSAES-OAEP Encryption Example 9.3",message:"/TJkKd+biQ4JtUsYuPNPHiQ=",seed:"6JuwMsbOYiy9tTvJRmAU6nf3d8A=",encrypted:"gevdlQVLDIIu+a12k/Woet+0tMTOcN8t+E7UnATaWLpfwgoZ4abot6OQCyJ5bcToae5rQnktFajs61bAnGmRToE86o9pMeS47W9CGvKY1ZXJf0eJx8qmEsfvNgmEwhuT7cVAEGi1r0x4qHcbmE1TuOqK3y9qfUoLp2x14d2fZY8g3tSkYHHUbXeRtWgD2P6n8LD45Brj8JODpvlYX+d1Pqr/0r+UVjEIvuzCB7u1NfX8xwXw3en3CMYvSanJA3HT"},{title:"RSAES-OAEP Encryption Example 9.4",message:"8UWbXwyS8BoPcjouVmJITY+MCiD8KdrWrNQ7tfPv/fThtj4H/f5mKNDXTKGb8taeSgq/htKTklp5Z3L4CI4=",seed:"YG87mcC5zNdx6qKeoOTIhPMYnMw=",encrypted:"vMNflM3mbLETZiXWJblEMqNbIvPS+hGmE/8PylvVf4e5AszcHNCuvLBxXuhp0dH+OV9nkwA/XspGUFnIhmDURv9fCBhVICJVfjjAimfq2ZEmIlTxBoKXXsVjl3aFN/SXevbV9qrOt/sl3sWTcjAjH9iXivSRGaKfKeQkq4JytHVieS1clPd0uIKdCw2fGoye3fN1dNX6JI7vqcUnH8XsJXnIG91htBD6Yf425CQiHBE63bJ1ZkyAHTTKjGNR5KhY"},{title:"RSAES-OAEP Encryption Example 9.5",message:"U+boxynW+cMZ3TF+dLDbjkzMol88gwV0bhN6xjpj7zc557WVq7lujVXlT3vUGrQzN4/7kR0=",seed:"/LxCFALp7KvGCCr6QLpfJlIshA4=",encrypted:"Iyr7ySf6CML2onuH1KXLCcB9wm+uc9c6kFWIOfT9ZtKBuH7HNLziN7oWZpjtgpEGp95pQs1s3OeP7Y0uTYFCjmZJDQNiZM75KvlB0+NQVf45geFNKcu5pPZ0cwY7rseaEXn1oXycGDLyg4/X1eWbuWWdVtzooBnt7xuzrMxpfMbMenePYKBkx/b11SnGIQJi4APeWD6B4xZ7iZcfuMDhXUT//vibU9jWTdeX0Vm1bSsI6lMH6hLCQb1Y1O4nih8u"},{title:"RSAES-OAEP Encryption Example 9.6",message:"trKOohmNDBAIvGQ=",seed:"I6reDh4Iu5uaeNIwKlL5whsuG6I=",encrypted:"Q4zH3AimjaJJ5CUF+Fc7pg4sJ3PVspD0z53/cY6EIIHDg+ZwJKDylZTqmHudJeS3OPKFlw0ZWrs6jIBU49eda5yagye6WW8SWeJxJmdHZpB9jVgv86hHYVSSmtsebRI1ssy07I9mO6nMZwqSvr2FPI2/acZDbQFvYa3YNulHMkUENCB/n9TEPewqEqlY76Ae/iZpiZteYEwlXFX7cWbeVYnjaVl7sJFowG3V2xd+BqF0DrLVyC+uym2S/O6ZMbqf"}],f(a,l,"sha1",p),e="rkXtVgHOxrjMBfgDk1xnTdvg11xMCf15UfxrDK7DE6jfOZcMUYv/ul7Wjz8NfyKkAp1BPxrgfk6+nkF3ziPn9UBLVp5O4b3PPB+wPvETgC1PhV65tRNLWnyAha3K5vovoUF+w3Y74XGwxit2Dt4jwSrZK5gIhMZB9aj6wmva1KAzgaIv4bdUiFCUyCUG1AGaU1ooav6ycbubpZLeGNz2AMKu6uVuAvfPefwUzzvcfNhP67v5UMqQMEsiGaeqBjrvosPBmA5WDNZK/neVhbYQdle5V4V+/eYBCYirfeQX/IjY84TE5ucsP5Q+DDHAxKXMNvh52KOsnX1Zhg6q2muDuw==",t="AQAB",n="BWsEIW/l81SsdyUKS2sMhSWoXFmwvYDFZFCiLV9DjllqMzqodeKR3UP0jLiLnV/A1Jn5/NHDl/mvwHDNnjmMjRnmHbfHQQprJnXfv100W4BNIBrdUC1c4t/LCRzpmXu+vlcwbzg+TViBA/A29+hdGTTRUqMj5KjbRR1vSlsbDxAswVDgL+7iuI3qStTBusyyTYQHLRTh0kpncfdAjuMFZPuG1Dk6NLzwt4hQHRkzA/E6IoSwAfD2Ser3kyjUrFxDCrRBSSCpRg7Rt7xA7GU+h20Jq8UJrkW1JRkBFqDCYQGEgphQnBw786SD5ydAVOFelwdQNumJ9gkygHtSV3UeeQ==",r="7PWuzR5VFf/6y9daKBbG6/SQGM37RjjhhdZqc5a2+AkPgBjH/ZXMNLhX3BfwzGUWuxNGq01YLK2te0EDNSOHtwM40IQEfJ2VObZJYgSz3W6kQkmSB77AH5ZCh/9jNsOYRlgzaEb1bkaGGIHBAjPSF2vxWl6W3ceAvIaKp30852k=",i="vEbEZPxqxMp4Ow6wijyEG3cvfpsvKLq9WIroheGgxh5IWKD7JawpmZDzW+hRZMJZuhF1zdcZJwcTUYSZK2wpt0bdDSyr4UKDX30UjMFhUktKCZRtSLgoRz8c52tstohsNFwD4F9B1RtcOpCj8kBzx9dKT+JdnPIcdZYPP8OGMYM=",s="xzVkVx0A+xXQij3plXpQkV1xJulELaz0K8guhi5Wc/9qAI7U0uN0YX34nxehYLQ7f9qctra3QhhgmBX31FyiY8FZqjLSctEn+vS8jKLXc3jorrGbCtfaPLPeCucxSYD2K21LCoddHfA8G645zNgz72zX4tlSi/CE0flp55Tp9sE=",o="Jlizf235wQML4dtoEX+p2H456itpO35tOi9wlHQT7sYULhj7jfy2rFRdfIagrUj4RXFw8O+ya8SBJsU+/R0WkgGY3CoRB9woLbaoDNMGI2C6P6E/cOQxL/GmzWuPxM2cXD2xfG1qVyEvc64p9hkye61ZsVOFhYW6Tii2CmKkXkk=",u="bzhSazklCFU07z5BWoNu3ouGFYosfL/sywvYNDBP7Gg7qNT0ecQz1DQW5jJpYjzqEAd22Fr/QB0//2EO5lQRzjsTY9Y6lwnu3kJkfOpWFJPVRXCoecGGgs2XcQuWIF7DERfXO182Ij+t1ui6kN18DuYdROFjJR4gx/ZuswURfLg=",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 10.1",message:"i7pr+CpsD4bV8XVul5VocLCJU7BrTrIFvBaU7g==",seed:"R+GrcRn+5WyV7l6q2G9A0KpjvTM=",encrypted:"U+pdwIzSYPs7hYVnKH+pFVLDCy/r+6IT8K6HcC0GjRm6sH/ldFI9+0ITnWjDxa/u4L/ky3lpy/OCuATW5hOWFE4tDmB0H4mTwwFLWLmxlXqLq80jr4VPTDVvsWYqpyv8x+WGVZ3EKA0WDBJnhacj6+6+/3HxFZRECq74fRB5Ood0ojnUoEyH/hRnudr4UgjsbHJVeUqWzCkUL5qL1Bjjwf1nNEsM0IKd87K+xgJTGWKTxrNNP3XTLyE91Fxic9UFrfTM7RBXy3WPwmru+kQSVe1OZMGZ7gdefxZkYYL9tGRzm2irXa/w5j6VUgFoJPBUv008jJCpe7a2VTKE60KfzA=="},{title:"RSAES-OAEP Encryption Example 10.2",message:"5q0YHwU7WKkE8kV1EDc+Vw==",seed:"bRf1tMH/rDUdGVv3sJ0J8JpAec8=",encrypted:"orGkMKnWV+L6HCu17UP/slwFowj+kJPAEDF5X1h0QAEQgorlj7m1gc6d3dPlSa4EoJhUWb3mxiZZTnsF3EJ4sqFGXBNoQIgjyF6W3GbDowmDxjlmT8RWmjf+IeWhlbV3bu0t+NjTYa9obnUCKbvWY/FhhopQYV4MM3vsDKNf7AuxnDbrLgu8wFgvodk6rNsGEGP1nyzh7kNgXl2J7KGD0qzf6fgQEQIq07Q6PdQX2slLThHqgbGSlm6WaxgggucZZGB7T4AC82KZhEoR8q4PrqwurnD49PmAiKzc0KxVbp/MxRFSGQj60m8ExkIBRQMFd4dYsFOL+LW7FEqCjmKXlQ=="},{title:"RSAES-OAEP Encryption Example 10.3",message:"UQos9g6Gb6I0BVPJTqOfvCVjEeg+lEVLQSQ=",seed:"OFOHUU3szHx0DdjN+druSaHL/VQ=",encrypted:"mIbD5nZKi5qE6EFI69jDsaqAUDgaePZocUwW2c/Spu3FaXnFNdne47RLhcGL6JKJkjcXEUciFtld2pjS7oNHybFN/9/4SqSNJawG99fmU5islnsc6Qkl9n3OBJt/gS2wdCmXp01E/oHb4Oej/q8uXECviI1VDdu+O8IGV6KVQ/j8KRO5vRphsqsiVuxAm719wNF3F+olxD9C7Sffhzi/SvxnZv96/whZVV7ig5IPTIpjxKc0DLr93DOezbSwUVAC+WyTK1t5Fnr2mcCtP8z98PROhacCYr8uGP40uFBYmXXoZ/+WnUjqvyEicVRs3AWmnstSblKHDINvMHvXmHgO3g=="},{title:"RSAES-OAEP Encryption Example 10.4",message:"vN0ZDaO30wDfmgbiLKrip18QyR/2Z7fBa96LUwZKJkmpQEXJ",seed:"XKymoPdkFhqWhPhdkrbg7zfKi2U=",encrypted:"Yxjp+1wNBeUwfhaDQ26QMpOsRkI1iqoiPXFjATq6h+Lf2o5gxoYOKaHpJoYWPqC5F18ynKOxMaHt06d3Wai5e61qT49DlvKM9vOcpYES5IFg1uID2qWFbzrKX/7Vd69JlAjj39Iz4+YE2+NKnEyQgt5lUnysYzHSncgOBQig+nEi5/Mp9sylz6NNTR2kF4BUV+AIvsVJ5Hj/nhKnY8R30Vu7ePW2m9V4MPwsTtaG15vHKpXYX4gTTGsK/laozPvIVYKLszm9F5Cc8dcN4zNa4HA5CT5gbWVTZd5lULhyzW3h1EDuAxthlF9imtijU7DUCTnpajxFDSqNXu6fZ4CTyA=="},{title:"RSAES-OAEP Encryption Example 10.5",message:"p91sfcJLRvndXx6RraTDs9+UfodyMqk=",seed:"lbyp44WYlLPdhp+n7NW7xkAb8+Q=",encrypted:"dSkIcsz9SkUFZg1lH1babaoJyhMB2JBjL2qZLz1WXO5GSv3tQO07W+k1ZxTqWqdlX0oTZsLxfHKPbyxaXR+OKEKbxOb48s/42o3A4KmAjkX9CeovpAyyts5v//XA4VnRG2jZCoX3uE4QOwnmgmZkgMZXUFwJKSWUaKMUeG106rExVzzyNL9X232eZsxnSBkuAC3A3uqTBYXwgx/c2bwz1R957S/8Frz01ZgS/OvKo/kGmw5EVobWRMJcz2O0Vu5fpv/pbxnN91H+2erzWVd1Tb9L/qUhaqGETcUHyy0IDnIuuhUDCMK1/xGTYg8XZuz0SBuvuUO9KSh38hNspJSroA=="},{title:"RSAES-OAEP Encryption Example 10.6",message:"6vGnOhsMRglTfeac2SKLvPuajKjGw++vBW/kp/RjTtALfDnsaSLXuOosBOus",seed:"n0fd9C6X7qhWqb28cU6zrCL26zI=",encrypted:"LSB6c0Mqj7TAMFGz9zsophdkCY36NMR6IJlfgRWqaBZnm1V+gtvuWEkIxuaXgtfes029Za8GPVf8p2pf0GlJL9YGjZmE0gk1BWWmLlx38jA4wSyxDGY0cJtUfEb2tKcJvYXKEi10Rl75d2LCl2Pgbbx6nnOMeL/KAQLcXnnWW5c/KCQMqrLhYaeLV9JiRX7YGV1T48eunaAhiDxtt8JK/dIyLqyXKtPDVMX87x4UbDoCkPtnrfAHBm4AQo0s7BjOWPkyhpje/vSy617HaRj94cGYy7OLevxnYmqa7+xDIr/ZDSVjSByaIh94yCcsgtG2KrkU4cafavbvMMpSYNtKRg=="}],f(a,l,"sha1",p)}function o(){var e,t,n,r,i,s,o,u,a,l,p;e="qLOyhK+OtQs4cDSoYPFGxJGfMYdjzWxVmMiuSBGh4KvEx+CwgtaTpef87Wdc9GaFEncsDLxkp0LGxjD1M8jMcvYq6DPEC/JYQumEu3i9v5fAEH1VvbZi9cTg+rmEXLUUjvc5LdOq/5OuHmtme7PUJHYW1PW6ENTP0ibeiNOfFvs=",t="AQAB",n="UzOc/befyEZqZVxzFqyoXFX9j23YmP2vEZUX709S6P2OJY35P+4YD6DkqylpPNg7FSpVPUrE0YEri5+lrw5/Vf5zBN9BVwkm8zEfFcTWWnMsSDEW7j09LQrzVJrZv3y/t4rYhPhNW+sEck3HNpsx3vN9DPU56c/N095lNynq1dE=",r="0yc35yZ//hNBstXA0VCoG1hvsxMr7S+NUmKGSpy58wrzi+RIWY1BOhcu+4AsIazxwRxSDC8mpHHcrSEurHyjnQ==",i="zIhT0dVNpjD6wAT0cfKBx7iYLYIkpJDtvrM9Pj1cyTxHZXA9HdeRZC8fEWoN2FK+JBmyr3K/6aAw6GCwKItddw==",s="DhK/FxjpzvVZm6HDiC/oBGqQh07vzo8szCDk8nQfsKM6OEiuyckwX77L0tdoGZZ9RnGsxkMeQDeWjbN4eOaVwQ==",o="lSl7D5Wi+mfQBwfWCd/U/AXIna/C721upVvsdx6jM3NNklHnkILs2oZu/vE8RZ4aYxOGt+NUyJn18RLKhdcVgw==",u="T0VsUCSTvcDtKrdWo6btTWc1Kml9QhbpMhKxJ6Y9VBHOb6mNXb79cyY+NygUJ0OBgWbtfdY2h90qjKHS9PvY4Q==",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 1.1",message:"ZigZThIHPbA7qUzanvlTI5fVDbp5uYcASv7+NA==",seed:"GLd26iEGnWl3ajPpa61I4d2gpe8Yt3bqIQadaXdqM+k=",encrypted:"W1QN+A1CKWotV6aZW7NYnUy7SmZd34SiX0jiPiLj9+8sZW6O/L7793+IFFSO3VKbPWhrjJPyR3ZmZ+yHDCzTDkRth+s5FN3nuFtlD3XQmmh0+x60PvAUiXJnAMcwxV96wHKjsUNPSnE1fsrCPBpIO5ZRaJ1pIF6R25IeuMwDujo="},{title:"RSAES-OAEP Encryption Example 1.2",message:"dQxAR/VH6OQUEYVlIymKybriRe+vE5f75W+d1Q==",seed:"DMdCzkqbfzL5UbyyUe/ZJf5P418Mx0LOSpt/MvlRvLI=",encrypted:"jsKSyOW1BkucnZpnt9fS72P/lamWQqexXEDPVs8uzGlFj24Rj+cqGYVlt7i9nTmOGj2YrvM8swUTJQCYIF+QBiKbkcA7WBTBXfiUlkHvpWQD0bLwOkp1CmwfpF4sq2gTsCuSaGzZAc50ZAIOvpldizU7uOCwNNGOlERcFkvhfEE="},{title:"RSAES-OAEP Encryption Example 1.3",message:"2Urggy5kRc5CMxywbVMagrHbS6rTD3RtyRbfJNTjwkUf/1mmQj6w4dAtT+ZGz2md/YGMbpewUQ==",seed:"JRTfRpV1WmeyiOr0kFw27sZv0v0lFN9GlXVaZ7KI6vQ=",encrypted:"LcQ1BhOH4Vs0XX8/QJ6q/L0vSs9BUXfA20lQ6mwAt/gvUaUOvKJWBujoxt1QgpRnU6WuH7cSCFWXuKNnrhofpFF3CBTLIUbHZFoou0A4Roi4vFGFvYYu96Boy+oWivwB9/BKs1QMQeHADgNwUgqVD15+q27yHdfIH7kGp+DiGas="},{title:"RSAES-OAEP Encryption Example 1.4",message:"UuZQ2Y5/KgSLT4aFIVO5fgHdMW80ahn2eoU=",seed:"xENaPhoYpotoIENikKN877hds/vEQ1o+Ghimi2ggQ2I=",encrypted:"ZMkqw9CM3SuY2zPBr8/9QbgXaVon4O4AKIufl3i7RVPD07fiTOnXF0aSWKUcdXNhE6ZcXc0Ha97/S5aw6mQKYfbmjaSq/H45s2nfZYTNIa74OgsV1DTDDLSF6/3J2UKhsG0LGIFaV9cNjfucDA5KbfQbzTq8u/+WN06J6nbInrI="},{title:"RSAES-OAEP Encryption Example 1.5",message:"jaif2eX5dKKf7/tGK0kYD2z56AI=",seed:"sxjELfO+D4P+qCP1p7R+1eQlo7WzGMQt874Pg/6oI/U=",encrypted:"NzKEr8KhWRbX/VHniUE8ap0HzdDEWOyfl7dfNHXjL4h/320dmK633rGUvlA7sE4z9yuMj/xF++9ZeBzN6oSPLhVJV/aivUfcC8J99lwwp49W7phnvkUA4WUSmUeX+XRhwj8cR27mf5lu/6kKKbgasdt4BHqXcc5jOZICnld6vdE="},{title:"RSAES-OAEP Encryption Example 1.6",message:"JlIQUIRCcQ==",seed:"5OwJgsIzbzpnf2o1YXTrDOiHq8Lk7AmCwjNvOmd/ajU=",encrypted:"nfQEzsDY2gS9UYXF85t+u0Tm7HrOmmf+LqxCD+6N4XD36NoQ96PE9Squ83PvxKy8Bj8Q0N2L8E5Z5/9AWxLPCBqOkqkqIqO7ZDQMmpHml3H1yz82rpAzAQi6acZDSFQAW8NKhg4nEEwfwKdaGQcI0JZm6FrTQUuXskOqFUT0NJc="}],f(a,l,"sha256",p),e="AZR8f86QQl9HJ55whR8l1eYjFv6KHfGTcePmKOJgVD5JAe9ggfaMC4FBGQ0q6Nq6fRJQ7G22NulE7Dcih3x8HQpn8UsWlMXwN5RRpD5Joy3eg2cLc9qRocmbwjtDamAFXGEPC6+ZwaB5VluVo/FSZjLR1Npg8g7aJeZTxPACdm9F",t="AQAB",n="CCPyD6212okIip0AiT4h+kobEfvJPGSjvguq6pf7O5PD/3E3BMGcljwdEHqumQVHOfeeAuGG3ob4em3e/qbYzNHTyBpHv6clW+IGAaSksvCKFnteJ51xWxtFW91+qyRZQdl2i5rO+zzNpZUto87nJSW0UBZjqO4VyemS2SRi/jk=",r="AVnb3gSjPvBvtgi4CxkPTT4ivME6yOSggQM6v6QW7bCzOKoItXMJ6lpSQOfcblQ3jGlBTDHZfdsfQG2zdpzEGkM=",i="AStlLzBAOzi0CZX9b/QaGsyK2nA3Mja3IC05su4wz7RtsJUR9vMHzGHMIWBsGKdbimL4It8DG6DfDa/VUG9Wi9c=",s="Q271CN5zZRnC2kxYDZjILLdFKj+1763Ducd4mhvGWE95Wt270yQ5x0aGVS7LbCwwek069/U57sFXJIx7MfGiVQ==",o="ASsVqJ89+ys5Bz5z8CvdDBp7N53UNfBc3eLv+eRilIt87GLukFDV4IFuB4WoVrSRCNy3XzaDh00cpjKaGQEwZv8=",u="AnDbF9WRSwGNdhGLJDiac1Dsg2sAY6IXISNv2O222JtR5+64e2EbcTLLfqc1bCMVHB53UVB8eG2e4XlBcKjI6A==",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 2.1",message:"j/AMqmBccCgwY02abD1CxlK1jPHZL+xXC+7n",seed:"jEB7XsKJnlCZxT6M55O/lOcbF4KMQHtewomeUJnFPow=",encrypted:"AR3o2JwhHLKUfOLZ26KXD9INUK1/fWJzdZix7E545qladDYdpHRaE5zBP9nf6IPmZvBUPq75n1E4suxm+Bom7crf9be1HXCFZnmR/wo92CKg4D1zRlBwr/3Gitr3h9rU6N+tid2x9yOYj955rf3Bq4j6wmjYQpWphbhBIBMoliyJ"},{title:"RSAES-OAEP Encryption Example 2.2",message:"LQ==",seed:"tgDPPC5QbX8Wd4yRDTqLAD7uYdW2AM88LlBtfxZ3jJE=",encrypted:"AIeYuAD2aYZYnEu1YK+INur95FfP2pTz8/k4r3xwL4bVMufgvzWFLdVK24fP96jTteLkrX6HjmebBVeUhSWG3ahebh3LH5yVS9yx+xHzM1Jxc8X1rS+kYgdCGWFbszMF/vP0ogisy5XthHqcoHNEM4Rzln7ugrXuS+dNuuPEjIAf"},{title:"RSAES-OAEP Encryption Example 2.3",message:"dPyIxRvJD3evnV6aSnATPUtOCzTaPDfH744=",seed:"pzdoruqpH52MHtb50rY0Z/B8yuOnN2iu6qkfnYwe1vk=",encrypted:"AMkW9IJHAFs0JbfwRZhrRITtj1bQVDLcjFCwYxHMDBlSHIqpDzSAL8aMxiUq41Feo9S2O/1ZTXIiK8baJpWs9y+BPqgi1lABB6JJIvU2QZYMzWK0XgjkWk12g6HSPFhuK4yf+LQ1UYpbKVquUdZ9POOCR8S7yS+tdful6qP8Wpkm"},{title:"RSAES-OAEP Encryption Example 2.4",message:"p+sqUDaTHSfU6JEybZlpL/rdqb9+/T405iLErcCF9yHf6IUHLHiiA7FRc5vlQPqMFToQ8Ao=",seed:"mns7DnCL2W+BkOyrT7mys4BagVaaezsOcIvZb4GQ7Ks=",encrypted:"AJ6YQ3DNjd7YXZzjHASKxPmwFbHKwoEpof+P+Li3+o6Xa95C21XyWZF0iCXc5USp5jwLt66T6G3aYQkEpoyFGvSPA3NV6tOUabopdmslYCkOwuOIsFLiuzkJc4Hu6nWXeJtTVtHn7FmzQgzQOMjuty1YConfe78YuQvyE3IAKkr2"},{title:"RSAES-OAEP Encryption Example 2.5",message:"LvKwZvhUwz873LtZlKQ15z1sbA==",seed:"6zzrvErcFrtI6IyK7A40r39Cf9PrPOu8StwWu0jojIo=",encrypted:"AMv457W0EOt8RH+LAEoMQ7dKjZamzOdwTHJepDkaGGoQHi2z8coCiVemL5XYZ+ctjPBdw3y3nlMn1sif9i3WCzY26ram8PL5eVYk7Bm3XBjv9wuhw1RZmLFzKfJS+3vi+RTFhwjyyeaJrc07f5E7Cu7CVWNh3Oe3lvSF3TB2HUI8"},{title:"RSAES-OAEP Encryption Example 2.6",message:"in+zRMi2yyzy7x9kP5oyGPbhm7qJwA==",seed:"TEXPTVfJjj1tIJWtxRxInrUN/4RMRc9NV8mOPW0gla0=",encrypted:"AJ5iMVr3Q6ZZlqLj/x8wWewQBcUMnRoaS2lrejzqRk12Bw120fXolT6pgo20OtM6/ZpZSN7vCpmPOYgCf93MOqKpN1pqumUH33+iP1a+tos5351SidwwNb2hLy3JfhkapvjB+c9JvbIolIgr+xeWhWPmMDam/Du/y+EsBOdZrbYc"}],f(a,l,"sha256",p),e="ArWP7AOahgcApNe2Ri+T5s3UkRYd3XT06BC0DjwWUgBqXCd7J3TBEwWky6taeO+lfheobfej+jb8Sx0iSfIux8LdakYyMqzOqQbWbr6AtXBLEHKdpvgzI0q7Xv3UopLL+tM7TTP6ehS4w5e1bjrNISA0KLd836M6bacGs9iw/EPp",t="AQAB",n="FbSKW1aDqUZw4jtXGPgU+g4T+FA49QcRGCy6YVEFgfPSLH4jLvk34i5VHWi4bi+MsarYvi5Ij13379J54/Vo1Orzb4DPcUGs5g/MkRP7bEqEH9ULvHxRL/y+/yFIeqgR6zyoxiAFNGqG3oa/odipSP0/NIwi6q3zM8PObOEyCP0=",r="Ab8B0hbXNZXPAnDCvreNQKDYRH0x2pGamD9+6ngbd9hf43Gz6Tc+e2khfTFQoC2JWN5/rZ1VUWCVi0RUEn4Ofq8=",i="AY0zmWWBZts4KYFteylUFnWenJGYf1stiuzWOwS0i9ey/PIpu3+KbciLoT3S45rVW20aBhYHCPlwC+gLj9N0TOc=",s="BsCiSdIKby7nXIi0lNU/aq6ZqkJ8iMKLFjp2lEXl85DPQMJ0/W6mMppc58fOA6IVg5buKnhFeG4J4ohalyjk5Q==",o="0dJ8Kf7dkthsNI7dDMv6wU90bgUc4dGBHfNdYfLuHJfUvygEgC9kJxh7qOkKivRCQ7QHmwNEXmAuKfpRk+ZP6Q==",u="jLL3Vr2JQbHTt3DlrTHuNzsorNpp/5tvQP5Xi58a+4WDb5Yn03rP9zwneeY0uyYBHCyPfzNhriqepl7WieNjmg==",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 3.1",message:"CHggtWno+o0=",seed:"jO1rGWKQgFeQ6QkHQBXmogsMSJSM7WsZYpCAV5DpCQc=",encrypted:"AJqBCgTJGSHjv2OR0lObiDY2gZmWdutHfVeadCdFr2W4mS3ZHwet283wbtY/bsM8w0rVxNAPh3NZNrcRt56NhoT0NzD2IK3WNy39Im/CfbicvC6Vq2PyXUh1iza+90PUM3jECPP5NsOx658MzEnYyFZFb9izZIna6YLsXwkWoHVO"},{title:"RSAES-OAEP Encryption Example 3.2",message:"RlOsrxcZYLAfUqe+Y6OrIdw2jsQ7UNguw3geBA==",seed:"tCkdZWdVCEjMFWlnyAm6q2ylB/C0KR1lZ1UISMwVaWc=",encrypted:"ARCj8j/hSsscyXtuINlyU0HuC+d7wZc7bSekF60BJFWKeKa1p28d4KsJXmdqI22sxha7PgkI9bgpfgdBd8KHp12g5y68uXiwRyPOvv8s6YDKmJFhbW13LHbE3iZHch2YG1eHi/20M/IrsAqCuk/W5Q/dP5eSVM1hLT9LBVsX3rIH"},{title:"RSAES-OAEP Encryption Example 3.3",message:"2UzQ4I+kBO2J",seed:"zoko9gWVWCVACLrdl5T63NL9H2XOiSj2BZVYJUAIut0=",encrypted:"Anfa/o/QML7UxLCHcSUWFPUWhcp955u97b5wLqXuLnWqoeQ3POhwasFh3/ow2lkzjjIdU47jkYJEk6A0dNgYiBuDg57/KN5yS2Px/QOSV+2nYEzPgSUHGyZacrHVkj/ZVyZ+ni7Iyf/QkNTfvPGxqmZtX6cq095jgdG1ELgYsTdr"},{title:"RSAES-OAEP Encryption Example 3.4",message:"bMZBtrYeb5Y5dNrSOpATKE7x",seed:"bil59S1oFKV9g7CQBUiI8RmluaNuKXn1LWgUpX2DsJA=",encrypted:"AalUnNYX91mP0FrqphpfhU22832WgnjDNRU1pkpSrd5eD7t7Q1YhYE+pKds6glA8i1AE/li216hJs2IbCJMddyaXrDzT8V9/UfIUaSkLfcRYBrTn9DEDOTjY1Xnn38poLOFykpZbAz5hdbOh0qG39qFgl5QZG0+aTBd1tmlMZBfO"},{title:"RSAES-OAEP Encryption Example 3.5",message:"31FRgyth9PJYkftBcvMo0u3fg3H/z9vpl5OSlfMOymkYAXz9oRU796avh1kyIw==",seed:"LXYL/jjFneNM3IuMeKOOZihKLSctdgv+OMWd40zci4w=",encrypted:"AGgQQYTuy9dW6e3SwV5UFYbEtqQD7TDtxcrMYOmYlTPgTwIFpo4GbQbtgD9BMFAW7a1lIzLxKEld49jH6m95Xgtq/BAVFl/gXin5MMbiZfRTOl38miBTg5a6IS9w6tcrWIBeY5Z5n4iCuUqF9r/m9TqvxWF0aMP2VGVKZn+LHMVj"},{title:"RSAES-OAEP Encryption Example 3.6",message:"PDutiTxUSm1SCrAiMZGIyNUEt6eIuFCQO4WXLqoYVS4RNKetYJiCYlT/erZys9jrMVj6xtTLrvE=",seed:"8XR3nF/Tz+AHuty3o2ybVb/Pvw7xdHecX9PP4Ae63Lc=",encrypted:"Aps8BQrRkPPwpNIjHw3NBznsDvp1hIHmlbG5wRERr9+Ar4ervO2GA/MMUVNijdZEtFnCGjbLwpM6RKzCk96jJX1bIgzq7hnmIzwKmq2Ue4qqO29rQL39jpCS87BBo/YKMbkYsPc2yYSDMBMOe9VDG63pvDgFGrlk/3Yfz1km3+/Y"}],f(a,l,"sha256",p),e="BRJAtswABPpI0BNGccB4x8jew7Pi8lvCVkRnM52ziFPQa4XupbLeNTv/QqwuRryX+uaslhjalTelyPVTweNXYlmR1hCNzXiF+zolQT9T78rZSMs1zZua6cHGdibRE9V93kxb6na7W7felsANBzculoWm11z50jn6FI1wkxtfP7A5",t="AQAB",n="BBH/yjt8penpvn/jioUQXjU4ltsFxXlq7NKnJRYes2UchimpuGK5BNewx7N/jLWhwrVAAQGKAKHrLK/k7k6UksNIvCvtq0ueu/Bk6O/zIrkAn47sZTkF9A34ijzcSdRWf3VifUGspiQSm0agt8aY5eZfK3uhAsdJoQE1tlQNBAE=",r="AnRYwZ7BY2kZ5zbJryXWCaUbj1YdGca/aUPdHuGriko/IyEAvUC4jezGuiNVSLbveSoRyd6CPQp5IscJW266VwE=",i="AhDumzOrYXFuJ9JRvUZfSzWhojLi2gCQHClL8iNQzkkNCZ9kK1N1YS22O6HyA4ZJK/BNNLPCK865CdE0QbU7UTk=",s="OfoCi4JuiMESG3UKiyQvqaNcW2a9/R+mN9PMSKhKT0V6GU53J+Sfe8xuWlpBJlf8RwxzIuvDdBbvRYwweowJAQ==",o="AV2ZqEGVlDl5+p4b4sPBtp9DL0b9A+R9W++7v9ax0Tcdg++zMKPgIJQrL+0RXl0CviT9kskBnRzs1t1M8eVMyJk=",u="AfC3AVFws/XkIiO6MDAcQabYfLtw4wy308Z9JUc9sfbL8D4/kSbj6XloJ5qGWywrQmUkz8UqaD0x7TDrmEvkEro=",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 4.1",message:"SoZglTTuQ0psvKP36WLnbUVeMmTBn2Bfbl/2E3xlxW1/s0TNUryTN089FmyfDG+cUGutGTMJctI=",seed:"HKwZzpk971X5ggP2hSiWyVzMofMcrBnOmT3vVfmCA/Y=",encrypted:"AooWJVOiXRikAgxb8XW7nkDMKIcrCgZNTV0sY352+QatjTq4go6/DtieHvIgUgb/QYBYlOPOZkdiMWXtOFdapIMRFraGeq4mKhEVmSM8G5mpVgc62nVR0jX49AXeuw7kMGxnKTV4whJanPYYQRoOb0L4Mf+8uJ5QdqBE03Ohupsp"},{title:"RSAES-OAEP Encryption Example 4.3",message:"v21C5wFwex0CBrDItFoccmQf8SiJIZqCveqWW155qWsNAWPtnVeOya2iDy+88eo8QInYNBm6gbDGDzYG2pk=",seed:"rZl/7vcw1up75g0NxS5y6sv90nWtmX/u9zDW6nvmDQ0=",encrypted:"AtYYko32Vmzn3ZtrsDQH9Mw/cSQk9pePdwQZJ6my7gYXWYpBdhbEN/fH7LMmvjtHnKLLTDazfF1HT0tTG6E+TY002cy+fMUvdRn0rfmFkNeHeqVOABP2EmI4eXFCBbbIlpshLxbA3vDTzPPZZqwMN+KPG4O11wmS9DcyHYtpsIOU"},{title:"RSAES-OAEP Encryption Example 4.4",message:"+y7xEvXnZuuUAZKXk0eU974vb8HFjg==",seed:"E2RU31cw9zyAen5A2MGjEqxbndMTZFTfVzD3PIB6fkA=",encrypted:"AZX8z/njjTP/ApNNF+BNGUjlczSK/7iKULnZhiAKo4LJ0XaTzTtvL9jacr+OkRxSPUCpQeK36wXdi9qjsl3SO9D7APyzN1nNE5Nu5YstiAfEMVNpdRYGdgpUasEZ4jshBRGXYW28uTMcFWRtzrlol9Lc7IhIkldTXZsR9zg11KFn"},{title:"RSAES-OAEP Encryption Example 4.5",message:"KMzUR7uehRZtq7nlt9GtrcS5058gTpbV5EDOmtkovBwihA==",seed:"vKgFf4JLLqJX8oYUB+72PTMghoG8qAV/gksuolfyhhQ=",encrypted:"A8GIo5X2qOS6MdKjYJg+h3hi2endxxeb3F5A8v+MbC7/8WbBJnzOvKLb6YMukOfAqutJiGGzdPQM9fopdhbRwS/Ovw4ksvmNBVM+Q26CFPqvdhV8P0WxmeYTxGFGrLgma+fwxpe7L6mj300Jq6Y/5kfTEJSXNdKuLRn0JsIg8LSD"},{title:"RSAES-OAEP Encryption Example 4.6",message:"8iJCdR7GsQ==",seed:"Ln4eF/ZHtd3QM+FUcvkPaBLzrE4ufh4X9ke13dAz4VQ=",encrypted:"AM9cnO14EVBadGQYTnkkbm/vYwqmnYvnAutrc4bZR3XC0DNhhuUlzFosUSmaC1LrjKcFfcqZOwzlev5uZycR7tUlLihC6lf4o0khAjUb03Dj+ubNsDKNCOA6vP63N3p6jSVIak7EPu0KtBLuNpIyW5mdkMuwssV7CNLOQ6qG7zxZ"}],f(a,l,"sha256",p),e="Cq3z+cEl5diR8xrESOmT3v5YD4ArRfnX8iulAh6cR1drWh5oAxup205tq+TZah1vPSZyaM/0CABfEY78rbmYiNHCNEZxZrKiuEmgWoicBgrA2gxfrotV8wm6YucDdC+gMm8tELARAhSJ/0l3cBkNiV/Tn1IpPDnv1zppi9q58Q7Z",t="AQAB",n="AlbrTLpwZ/LSvlQNzf9FgqNrfTHRyQmbshS3mEhGaiaPgPWKSawEwONkiTSgIGwEU3wZsjZkOmCCcyFE33X6IXWI95RoK+iRaCdtxybFwMvbhNMbvybQpDr0lXF/fVKKz+40FWH2/zyuBcV4+EcNloL5wNBy+fYGi1bViA9oK+LF",r="A7DTli9tF1Scv8oRKUNI3PDn45+MK8aCTyFktgbWh4YNrh5jI5PP7fUTIoIpBp4vYOSs1+YzpDYGP4I4X0iZNwc=",i="AuTDLi9Rcmm3ByMJ8AwOMTZffOKLI2uCkS3yOavzlXLPDtYEsCmC5TVkxS1qBTl95cBSov3cFB73GJg2NGrrMx8=",s="AehLEZ0lFh+mewAlalvZtkXSsjLssFsBUYACmohiKtw/CbOurN5hYat83iLCrSbneX31TgcsvTsmc4ALPkM429U=",o="65CqGkATW0zqBxl87ciBm+Hny/8lR2YhFvRlpKn0h6sS87pP7xOCImWmUpfZi3ve2TcuP/6Bo4s+lgD+0FV1Tw==",u="AS9/gTj5QEBi64WkKSRSCzj1u4hqAZb0i7jc6mD9kswCfxjngVijSlxdX4YKD2wEBxp9ATEsBlBi8etIt50cg8s=",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 5.1",message:"r3GpAeOmHTEy8Pwf20dPnqZXklf/wk0WQXAUWz296A==",seed:"RMkuKD93uUmcYD2WNmDIfS+TlGFEyS4oP3e5SZxgPZY=",encrypted:"BOGyBDRo+2G7OC79yDEzJwwLMPuhIduDVaaBdb5svHj/ZAkVlyGVnH0j+ECliT42Nhvp4kZts+9cJ0W+ui7Q9KXbjmX033MpxrvSV1Ik//kHhX6xTn51UGpaOTiqofjM3QTTi9DVzRtAarWd/c8oAldrGok1vs+tJEDbcA5KvZz7"},{title:"RSAES-OAEP Encryption Example 5.2",message:"o7hEoII5qKxBYFrxemz9pNNQE2WFkDpBenkmh2BRmktKwzA+xz8Ph8+zI5k=",seed:"yyj1hgZZ/O7knD7q/OYlpwgDvTLLKPWGBln87uScPuo=",encrypted:"AeleoSbRCOqBTpyWGLCrZ2G3mfjCYzMvupBy+q+sOKAfBzaLCjCehEUmIZFhe+CvtmVvyKGFqOwHUWgvks9vFU7Gfi580aZm7d4FtaGGVHBO6Q32/6IS7a+KSk7L6rPWwBTI+kyxb5SW12HTEowheKkFTda06tU0l4Ji45xP2tyh"},{title:"RSAES-OAEP Encryption Example 5.3",message:"MIsOy9LHbLd/xvcMXt0jP9LyCSnWKfAmlTu2Ko9KOjFL3hld6FtfgW2iqrB00my2rN3zI647nGeKw88S+93n",seed:"IoX0DXcEgvmp76LHLLOsVXFtwMoihfQNdwSC+anvosc=",encrypted:"Ci3TBMV4P0o59ap6Wztb9LQHfJzYSOAaYaiXjk85Q9FYhAREaeS5YXhegKbbphMIS5i1SYJShmwpYu/t8SGHiX/72v6NnRgafDKzttROuF/HJoFkTBKH6C9NKke+mxoDy/YVZ9qYzFY6PwzB4pTDwku9s5Ha4DmRBlFdA/z713a4"},{title:"RSAES-OAEP Encryption Example 5.4",message:"FcW57hGF",seed:"SfpF06eN0Q39V3OZ0esAr37tVRNJ+kXTp43RDf1Xc5k=",encrypted:"AcMQiclY0MMdT9K4kPqZ7JYHTaSolc8B3huHcQ4U5mG11/9XjzWjTLha8Liy0w909aaPbaB7+ZQTebg7x3F4yeWFRmnAJMaIFGBW/oA952mEaJ+FR2HO0xfRPzCRCaaU7cyOxy0gnR8d9FMunt9fhbffM9TvOfR6YDE5Duz6Jg0W"},{title:"RSAES-OAEP Encryption Example 5.5",message:"IQJuaADH+nKPyqug0ZauKNeirE/9irznlPCYX2DIpnNydzZdP+oR24kjogKa",seed:"8Ch0EyNMxQNHJKCUxFhrh6/xM/zwKHQTI0zFA0ckoJQ=",encrypted:"A/gvyZ/MNHUG3JGcisvAw/h1bhviZsqIsEM5+gBpLfj7d8iq28yZa6z5cnS4cJWywHxyQMgt9BAd37im/f5WcIcB+TZS9uegFSdgaetpYf4ft/1wMlcdc1ReCrTrCKPFHLLczeifyrnJSVvQD84kQY21b4fW9uLbSiGO0Ly94il1"},{title:"RSAES-OAEP Encryption Example 5.6",message:"VB43totsiHK4TAI=",seed:"2fukXJbyHm4m0p6yzctlhb6cs0HZ+6RclvIebibSnrI=",encrypted:"AWmTYpBHOqRBGy1h5mF88hMmBVNLN++kXAqQr4PKszqorigNQZbvwbOdWYNLsXydyvKi55ds8tTvXf4rRBswyuNmbtT0t2FVCTnTjNzA1cMSahInbdKfL/1wib3CjyQmC0TbbIa3kkAdXkiYytSafDxwNyan1OWtJcMLkJ6l8WRm"}],f(a,l,"sha256",p),e="ErF/ba0uzRn/RtwT94YPCeDgz7Z3s4pSWSMFzq8CLBZtuQ0ErCnjP33RLZ+vZuCBa7Y+rSZ8x9RsF8N74hS8oqItcjpk5EQHQ2tvyWVymu/CVU83bNXc6mgpN4CmK/OdAClIWhYLu55dwJctIaUE9S5e4CiqQWMy9RCy6c/19yKv",t="AQAB",n="ApXso1YGGDaVWc7NMDqpz9r8HZ8GlZ33X/75KaqJaWG80ZDcaZftp/WWPnJNB7TcEfMGXlrpfZaDURIoC5CEuxTyoh69ToidQbnEEy7BlW/KuLsv7QV1iEk2Uixf99MyYZBIJOfK3uTguzctJFfPeOK9EoYij/g/EHMc5jyQz/P5",r="BKbOi3NY36ab3PdCYXAFr7U4X186WKJO90oiqMBct8w469TMnZqdeJpizQ9g8MuUHTQjyWku+k/jrf8pDEdJo4s=",i="BATJqAM3H+20xb4588ALAJ5eCKY74eQANc2spQEcxwHPfuvLmfD/4Xz9Ckv3vv0t1TaslG23l/28Sr6PKTSbke0=",s="A5Ycj3YKor1RVMeq/XciWzus0BOa57WUjqMxH8zYb7lcda+nZyhLmy3lWVcvFdjQRMfrg6G+X63yzDd8DYR1KUs=",o="AiGX4GZ0IZaqvAP6L+605wsVy3h9YXrNMbt1x7wjStcG98SNIYLR8P+cIo3PQZZ7bAum0sCtEQobhXgx7CReLLE=",u="BAHEwMU9RdvbXp2W0P7PQnXfCXS8Sgc2tKdMMmkFPvtoas4kBuIsngWN20rlQGJ64v2wgmHo5+S8vJlNqvowXEU=",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 6.1",message:"QEbKi6ozR8on9J4NgfnMHXG+m6UX1A==",seed:"3Q9s/kFeiOWkaaUfu6bf1ArbQ4TdD2z+QV6I5aRppR8=",encrypted:"C3d3hdR81ybq+Wuf6QUfy2KHVuQjrsvVH2HuE2LbJT2o2ZPdrDHIoGphdGj+GWNTrcV/d8iPLJlZ0CR3O2e2b7wLUVPMorv1HidYA8B8eJxkg5FIsPuK836LchnGqQlE7ObiWOjSuIw4lZ/ULCfOYejelr6PJXSxWgQUlV78sbvP"},{title:"RSAES-OAEP Encryption Example 6.2",message:"XMcsYCMd8Ds9QPm1eTG8MRCflyUn8osZ50gMcojLPJKyJRIhTkvmyRR5Ldq99X+qiqc=",seed:"jRS9lGoTURSPXK4u2aDGU+hevYWNFL2UahNRFI9cri4=",encrypted:"DXAHBh/uWFjxl/kIwrzm0MXeHNH5MSmoPc0mjn00UcCUFmOwTQipPmLmephH+rNOOfCQVvwP5wysU3/w2hjmk/rl6Jb4qNc+KqDiij7fKSKhPGTvY3aiXZ2LflnJ3yv4LdT9KvvWsZrHEWfsEG+fQZW4c1OMEpOMC4N44nc88yRm"},{title:"RSAES-OAEP Encryption Example 6.3",message:"sg5lEwMJL0vMtDBwwPhtIwSTYu2WZC/FYywn20pS49gx8qsGiyOxSYecAC9r8/7ul1kRElYs",seed:"bAdbxFUg8WXAv16kxd8ZG8nvDkRsB1vEVSDxZcC/XqQ=",encrypted:"AMe1ZPYbk4lABLKDLhwJMM4AfK46Jyilp/vQ9M921AamJzanoNGdlj6ZEFkbIO68hc/Wp4Qr43iWtjcasgpLw2NS0vroRi91VI5k9BZgXtgNG7Z9FBOtPjM61Um2PWSFpAyfaZS7zoJlfRKciEa+XUKa4VGly4fYSXXAbUJV2YHc"},{title:"RSAES-OAEP Encryption Example 6.4",message:"aE4wOMXAQfc=",seed:"O7w71mN9/hKEaQECm/WwwHEDQ5w7vDvWY33+EoRpAQI=",encrypted:"AJS/vpVJKJuLwnnzENVQChT5MCBa0mLxw/a9nt+6Zj4FL8nucIl7scjXSOkwBDPcyCWr7gqdtjjZ9z6RCQv0HfjmVKI2M6AxI2MYuzwftIQldbhCRqo8AlyK3XKjfcK+Rzvii53W8Xw4Obbsv9OCLnCrrbK8aO3XKgrHPmDthH7x"},{title:"RSAES-OAEP Encryption Example 6.5",message:"MkiMsmLQQdbk3TX5h788ppbbHwasKaRGkw==",seed:"tGtBiT6L7zJvZ1k4OoMHHa5/yry0a0GJPovvMm9nWTg=",encrypted:"CmNUKnNQco5hWHxCISdwN5M7LbL7YJ0u7bfH82LulE32VdATd3vcJmRiFtcczNNudMlHVhl6/ZsDVY1zymLrK2kLIYWeG9Iag3rQ5xhjLAdpMYBBuwjrJ8Oqc4+2qH57bBveynuE5xRpd9p+CkkiRP7x7g4B/iAwrmFxPtrxV/q/"},{title:"RSAES-OAEP Encryption Example 6.6",message:"ULoUvoRicgJ5wwa6",seed:"CiQDMSpB49UvBg+8E6Z95c92CacKJAMxKkHj1S8GD7w=",encrypted:"DpQAu4uQ4zbkpP/f698+a5f3MhAXCi3QTcP7vXmQVlkH0CFlCnDESNG36Jk2ybe3VmzE2deBHBKI9a5cHUzM9Lsa/AoxnbD5qd2fJt9k19dSRtDWZUR/Bn/AdVHwstzsX/vRLe6qOk9Kf01OZcvKrmlWh2IBLs8/6sEJhBWXNAKj"}],f(a,l,"sha256",p),e="MRF58Lz8m508oxXQDvMNe906LPrpkRv+3LlIs6R4LQcytqtEqkvwN0GmRNwBvsPmmwGgM+Z12KzXxJJcaxrsMRkFHf2Jdi0hXUVHX/y1n5CBSGI/NxdxVvauht16fF9D3B4fkIJUBYooSl8GwAIXk6h/GsX+/33K7mnF5Ro3ieNz",t="AQAB",n="Bwz8/y/rgnbidDLEXf7kj0m3kX1lMOHwyjRg8y4CdhdEh8VuIqRdJQDXd1SVIZ19Flqc872Swyr5qY2NycwpaACtyUoKVPtA80KRv4TujqErbxCTWcbTVCpQ+cdn9c//BaaBwuZW+3fKqttL6UaNirzU35j1jobSBT+hNJ90jiGx",r="B0kmLBEc1HDsJWbms3MvwJMpRpqhkHHTucAZBlFMbx0muqFL6rCXHIt+YRpPeQCdb+p3aSjKJShbDeNkPRo/jHE=",i="BrweUOlsAr9jbp7qi4mbvr92Ud533UdMPpvCO62BgrYZBMfZffvr+x4AEIh4tuZ+QVOR1nlCwrK/m0Q1+IsMsCM=",s="A7x+p/CqsUOrxs6LlxGGNqMBcuTP4CyPoN2jt7qvkPgJKYKYVSX0iL38tL1ybiJjmsZKMJKrf/y/HVM0z6ULW/E=",o="AmKmqinCo8Z9xTRsBjga/Zh6o8yTz7/s9U/dn514fX9ZpSPTmJedoTei9jgf6UgB98lNohUY3DTLQIcMRpeZStk=",u="ZJ1MF7buFyHnctA4mlWcPTzflVDUV8RrA3t0ZBsdUhZq+KITyDliBs37pEIvGNb2Hby10hTJcb9IKuuXanNwwg==",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 7.1",message:"R6rpCQ==",seed:"Q90JoH/0ysccqkYy7l4cHa7kzY9D3Qmgf/TKxxyqRjI=",encrypted:"CdXefX8LEW8SqnT1ly7/dvScQdke1rrSIBF4NcFO/G+jg0u7yjsqLLfTa8voI44Ue3W6lVuj5SkVYaP9i7VPmCWA4nFfeleuy23gbHylm4gokcCmzcAm2RLfPQYPnxIb3hoQ2C3wXo/aWoLIMFPYzI19g5uY90XMEchAci3FVC/a"},{title:"RSAES-OAEP Encryption Example 7.2",message:"HZsuIiPZvBO/ufFiznNdtIunxo9oIqChp7auFlg05w==",seed:"Opw87HuE+b063svGc+yZ1UsivJs6nDzse4T5vTrey8Y=",encrypted:"DDar5/aikhAVropPgT3SVzSMRtdS9sEmVBEqg9my/3na0Okz51EcAy436TOZVsM0exezvKYsVbDQhtOM0Mn9r6oyBsqzUR4lx6Gt2rYDYC4X1aMsJSVcQs9pDqeAWfIAmDIIQH/3IN2uJ6u4Xl2+gFCpp8RP0F//Rj2llnEsnRsl"},{title:"RSAES-OAEP Encryption Example 7.3",message:"2Xb8",seed:"dqdeW2FXpVbPiIS7LkXCk91UXPV2p15bYVelVs+IhLs=",encrypted:"GpTkYrRFNyD9Jw1Pc1TSPSfc9Yb8k4Fw1l4kCwqPodlAboKMJe+yuXoGgVeB7Jb7JTQklGpQc1keZUzUUVZO0Q4qYUelFFe5lWM2uhq21VCbvcchrMTP6Wjts05hVgJHklLKF5cOtBGpQC0FlkwCYoXUAk//wejNycM/ZXw+ozkB"},{title:"RSAES-OAEP Encryption Example 7.4",message:"1HOGI98iOqQ4Q9+EZ1NMQdAT4MgDxiTiY2ZrI5veQKXymuuN5549qmHdA3D0m9SwE4NLmCEq72scXuNzs8s=",seed:"eGYxSmrW8rJQo1lB2yj1hktYWFl4ZjFKatbyslCjWUE=",encrypted:"G5GJEPP4ifUCg+3OHEq41DFvaucXwgdSGyuDX6/yQ1+e30d0OIjIFv4JTUXv6Oi8/uADg+EN5Ug+lEyf0RNSS4wfKgRfAaXK6x1U8dh48g/bED27ZCZ+MjhAkUcjMO0h4m3nNfLxAju7nxO2cJzNI9n1TBCMngJBco0zzhOvMZaN"},{title:"RSAES-OAEP Encryption Example 7.5",message:"u0cjHKXqHTrUbJk0XZqKYQ==",seed:"shZu1HLVjbEMqyxrAAzM8Qp9xQmyFm7UctWNsQyrLGs=",encrypted:"HebBc/6i18c2FbG7ibWuxyQgtiN1uhtxyNsXw1Kuz8zo7RkBkt5JZEwucKyXFSwI6drZlK6QaqCRZwPQsdc2wnZlQzbkilVf1TiACqzDdpKX5i+SbCTUsOyGETV3vtxFe7/SatEKseFSLEWkIfZxAFcisIs5hWmLJdqfWQeYuMrK"},{title:"RSAES-OAEP Encryption Example 7.6",message:"IYSCcJXTXD+G9gDo5ZdUATKW",seed:"Umc73iyhZsKqRhMawdyAjWfX07FSZzveLKFmwqpGExo=",encrypted:"DX+W3vsdnJfe63BVUFYgCAG1VmTqG/DbQ4nZgWTUGHhuijUshLtz07dHar21GJ9Ory8QQPX67PgKGnBMp0fJBnqKO3boMOEcc52HEnQxOWIW2h2rgmDtnaYtvK85pddXzhbuXkXg4DDnoMy+4XzgbLfArK12deGB0wruBbQyv3Ar"}],f(a,l,"sha256",p),e="W98OMNMh3aUUf4gkCPppGVSA34+A0/bov1gYUE82QnypsfVUC5xlqPaXTPhEeiRNkoAgG7Sfy75jeNGUTNIn4jD5bj0Q+Bnc7ydsZKALKktnAefQHeX6veOx6aDfgvRjE1nNImaWR/uxcXJGE07XtJfP/73EK1nHOpbtkBZiEt/3",t="AQAB",n="D30enlqqJf0T5KBmOuFE4NFfXNGLzbCd8sx+ZOPF6RWtYmRTBBYdCYxxW7eri9AdB+rz/tfH7QivKopi70SrFrMg4Ur3Kkj5av4mKgrkz2XmNekQeQzU7lzqdopLJjn35vZ3s/C7a+MrdXR9iQkDbwJk9Y1AHNuhMXFhV6dez2Mx",r="CgLvhEjZ+ti70NAEyMKql1HvlyHBsNAyNqVLDflHy67VolXuno4g1JHqFyP+CUcEqXYuiK/RbrtZlEEsqWbcT58=",i="CS02Ln7ToL/Z6f0ObAMBtt8pFZz1DMg7mwz01u6nGmHgArRuCuny3mLSW110UtSYuByaxvxYWT1MP7T11y37sKk=",s="B8cUEK8QOWLbNnQE43roULqk6cKd2SFFgVKUpnx9HG3tJjqgMKm2M65QMD4UA10a8BQSPrpoeCAwjY68hbaVfX0=",o="rix1OAwCwBatBYkbMwHeiB8orhFxGCtrLIO+p8UV7KnKKYx7HKtYF6WXBo/IUGDeTaigFjeKrkPH+We8w3kEuQ==",u="BZjRBZ462k9jIHUsCdgF/30fGuDQF67u6c76DX3X/3deRLV4Mi9kBdYhHaGVGWZqqH/cTNjIj2tuPWfpYdy7o9A=",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 8.1",message:"BQt1Xl5ogPe56daSp0w3quRJsxv+pt7/g3R6iX9sLIJbsa2/hQo8lplLXeWzPLx9SheROnln",seed:"dwb/yh7PsevuKlXlxuJM0nl6QSV3Bv/KHs+x6+4qVeU=",encrypted:"DZZvGJ61GU6OOkaPl2t8iLNAB1VwLjl3RKd/tcu19Vz9j68fjCFBQvASq9FK6Sul/N6sXIVsi4ypx/1m77bErYJqiGwkE8sQz/g4ViwQmeCvpfbCoq00B5LxklezvhnM5OeSxFtO/8AtYimLrJ3sUmDYk7xkDI20/Lb8/pyOFsjH"},{title:"RSAES-OAEP Encryption Example 8.2",message:"TraNzZPKmxnfERvUNgj1VwJv5KodXPrCJ6PrWrlUjBigbd7SP4GCWYay/NcRCezvfv+Ihz8HXCqgxGn2nJK8",seed:"o3F9oUO03P+8dCZlqPqVBYVUg0OjcX2hQ7Tc/7x0JmU=",encrypted:"DwsnkHG2jNLgSU4LEvbkOuSaQ9+br9t3dwen8KDGmLkKVJgWGu+TNxyyo2gsBCw7S4eqEFrl49ENEhMehdjrHCBLrEBrhbHxgncKrwIXmcjX1DOCrQXEfbT4keig8TaXkasow5qby9Ded6MWcLu1dZnXPfiuiXaOYajMGJ1D3/y7"},{title:"RSAES-OAEP Encryption Example 8.3",message:"hgSsVjKMGrWtkXhh",seed:"7gYgkHPMoCa7Jk5Rhb+MaLdzn4buBiCQc8ygJrsmTlE=",encrypted:"PAKF3K/lSKcZKWQDr56LmmVqSltcaKEfS7G6+rwG239qszt8eKG6fMYJsP4h7ZfXyV1zuIZXTVhXgiRQbA9os0AhkWiMJJouhsAn60R20BOLQOtQxlXxVOvUMPGuG5EP2O+nTI0VCXky5kHNJdfJolSW+pJLVdSu4mX9Ooga1CSx"},{title:"RSAES-OAEP Encryption Example 8.4",message:"/dpfv27DYanZpKxoryFqBob0OLHg5cNrlV904QfznA3dzA==",seed:"mQrVc9xIqXMjW22CVDYY8ulVEF2ZCtVz3EipcyNbbYI=",encrypted:"LcCLDDj2S5ZOKwpvobOk6rfBWMBbxs3eWR+Edk3lKaoEAFFD5sQv0AaIs3r7yI8sOir9HvS6GKf+jc9t31zIDCIJc3sKVyrNZfEeUFSvihjbPZDo6IaZ8Jau8woE2p1z7n9rG+cbMwKuILRPSEN4hE0QSA/qz0wcye6bjb6NbK20"},{title:"RSAES-OAEP Encryption Example 8.5",message:"Sl9JFL7iXePGk0HeBw==",seed:"7MY7KPB1byL1Ksjm7BJRpuwwRxjsxjso8HVvIvUqyOY=",encrypted:"U+PBTMw6peNHnFQ3pwix/KvVeWu1nSKQGcr9QPpYNgHhuFZK6ARR7XhKxFtDoUMP+iVTXprcow4lr1Uaw4PnEx+cPe0Khl15R8GLiuh5Vm9p3lTPW1u58iP2Oa81pQZTB5AuFiD0fnFZsl+BYfjDVah3cMIu83KOBRPOdLY0j8iq"},{title:"RSAES-OAEP Encryption Example 8.6",message:"jgfWb3uICnJWOrzT81CSvDNAn7f4jyRyvg==",seed:"OSXHGzYtQKCm3kIUVXm6Hn3UWfw5JccbNi1AoKbeQhQ=",encrypted:"WK9hbyje7E0PLeXtWaJxqD4cFkdL5x4vawlKJSOO1OKyZ6uaY8vMYBhBO47xRIqtab5Ul5UGCwdvwPR69PpARsiiSfJHVavkihXixHGgZViGTMU/7J7ftSiNT9hAwrj4JL4f1+8RhTp6WKRzsXAEKpvLK0TrzQL3ioF3QtTsiu/a"}],f(a,l,"sha256",p),e="zyzUHjTKOnKOpcuK/2TDbSe971Nk4zb9aNMSPFoZaowocBPoU9UVbVjRUZVFIPtPbXsXq7aBd2WQnFdhGWWdkCsZBu2KKxDBVcJNEkUo2rnurjeb6sZuSkEXhty4/QBi68Aw3hIZoEwqjBt90xMeTWtsruLjGl7UGsFQmy7x7iqxg2S+VoypQcJezIT/nWQ7XsGqrhAqINc/R5t4D9bakQdSEtnqwDoGdNiZ66LkMfTES2Fba6IjK9SzO67XPWJd",t="AQAB",n="GYwUHiNxWpK8z2oRmlvBE4lGjSgR9UjXJ+F7SrDrmG1vIR77U7cffMvqh+5px17mFQCMUzLetSvzkKvfv+N9cgU2gVmyY4wd4ybiHSIlHw+1hIs78VAF0qdDMPCv6RbuYszBNE0dg6cJ5gZ2JzhA9/N3QkpeCk2nXwGzH/doGc+cv90hUkPDkXwD7zgZkxLlZ7O/eu06tFfzce+KFCP0W2jG4oLsERu6KDO5h/1p+tg7wbjGE8Xh6hbBHtEl6n7B",r="/I1sBL7E65qBksp5AMvlNuLotRnezzOyRZeYxpCd9PF2230jGQ/HK4hlpxiviV8bzZFFKYAnQjtgXnCkfPWDkKjD6I/IxI6LMuPaIQ374+iB6lZ0tqNIwh6T+eVepl79",i="0gDUXniKrOpgakAdBGD4fdXBAn4S3BoNdYbok52c94m0D1GsBEKWHefSHMIeBcgxVcHyqpGTOHz9+VbLSNFTuicEBvm7ulN9SYfZ4vmULXoUy//+p0/s3ako0j4ln17h",s="2xaAL3mi8NRfNY1p/TPkS4H66ChiLpOlQlPpl9AbB0N1naDoErSqTmyL6rIyjVQxlVpBimf/JqjFyAel2jVOBe8xzIz3WPRjcylQsD4mVyb7lOOdalcqJiRKsI23V1Kt",o="oKMXz+ffFCP4em3uhFH04rSmflSX8ptPHk6DC5+t2UARZwJvVZblo5yXgX4PXxbifhnsmQLgHX6m+5qjx2Cv7h44G2neasnAdYWgatnEugC/dcitL6iYpHnoCuKU/tKh",u="CyHzNcNTNC60TDqiREV4DC1lW5QBdMrjjHyKTmSTwLqf0wN0gmewg7mnpsth5C2zYrjJiW23Bk4CrVrmFYfaFbRknJBZSQn+s328tlS+tyaOyAHlqLSqORG+vYhULwW+",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 9.1",message:"9zX9VbqSWSw7Urj5xPaaqhy++P6IrdCVWVQSRn+c9OwLiWxZ7aFiEOdUnIq7EM28IaEuyba1uP0vEDmetg==",seed:"jsll8TSj7Jkx6SocoNyBadXqcFyOyWXxNKPsmTHpKhw=",encrypted:"kuBqApUSIPP0yfNvL0I57K2hReD8CcPhiYZFlPPmdM0cVFQHvdPMjQ2GcEekoBMk2+JR2H3IY6QF0JcANECuoepAuEvks/XolStfJNyUVUO3vLbWGlA1JOOSPiWElIdM0hmLN5In0DizqQit7R0mzH3Y1vUGPBhzOnNgNVQgrWVvqJugjG1SPY7LaZxIjhnz3O/8EkCpxLWcyWkFLX+ujnxIKxAqjmvEztiwLLlcWbJKILOy7KE1lctyh58wYP6e"},{title:"RSAES-OAEP Encryption Example 9.2",message:"gbkGYFAVpjqr5C3fEeGXiRL1QEx0dLJtzj7Ugr+WHsyBi/QgxUZZ",seed:"7LG4sl+lDNqwjlYEKGf0r1gm0WzssbiyX6UM2rCOVgQ=",encrypted:"iNEGT/cssEWuXX1C+SWyMK1hhUjRdNz9l8FeCBhftw8I5JUY1EDXPi2hUpESGxbJOjbyricua+QVvfP/UeoPfOxCcpRSoA3DlviB0ExCJTpCb2NSv9qXtw6Z7qEqk2YyQD7mAsGb2/Y3ug3KkKrF68MAxsWFV3tmL2NV2h+yfW6qz1dVAbAIUZeRuLIbaLdY9F7O4yPC68zkaX9NcTg0tOtnAJth9fMAOFX8sVbvKBgeOuVHV1A8HcAbmqkLMIyp"},{title:"RSAES-OAEP Encryption Example 9.3",message:"/TJkKd+biQ4JtUsYuPNPHiQ=",seed:"6JuwMsbOYiy9tTvJRmAU6nf3d8Dom7Ayxs5iLL21O8k=",encrypted:"JiwfWprF58xVjVRR9B9r0mhomwU5IzkxXCZDgYJwYUcacmrz+KRLKMmtCMN7DLA2lOsfK+72mU+RLmhwfAAhBYmLGR8dLLstazb5xzU9wIM9u3jAl5iyyMLSo6wk/3SH0f7vC2bnFtMkhoHsd3VSTpzl5Q+SqX/4Q1JAMGWMMiHdyjCH+WaXNdTrboPEnPVtTcBGthkkYu8r/G0IkBR6OMPCZFgl/J4uiRTGCRbZx7UC02g6+qNMQY+ksygV6R8w"},{title:"RSAES-OAEP Encryption Example 9.4",message:"8UWbXwyS8BoPcjouVmJITY+MCiD8KdrWrNQ7tfPv/fThtj4H/f5mKNDXTKGb8taeSgq/htKTklp5Z3L4CI4=",seed:"YG87mcC5zNdx6qKeoOTIhPMYnMxgbzuZwLnM13Hqop4=",encrypted:"YXo+2y1QMWzjHkLtCW6DjdJ6fS5qdm+VHALYLFhG/dI1GmOwGOiOrFqesc5KPtWE73N5nJ680e6iYQYdFIsny6a4VH9mq/2Lr6qasMgM27znPzK8l6uQ1pTcDu1fJ4gCJABshzVEXzeTWx6OyKZsOFL8eXiNCwQpyfP9eH0tRjc+F75H3dnzX6AEVff4t0yKjDqp7aRMxFZGidcMJ6KetiNXUg1dhs/lHzItdQ7oMSUAgMnHYAvJDGqy5L4F8XXM"},{title:"RSAES-OAEP Encryption Example 9.5",message:"U+boxynW+cMZ3TF+dLDbjkzMol88gwV0bhN6xjpj7zc557WVq7lujVXlT3vUGrQzN4/7kR0=",seed:"/LxCFALp7KvGCCr6QLpfJlIshA78vEIUAunsq8YIKvo=",encrypted:"fwY+yhF2kyhotPKPlHEXcTOqVRG8Kg9bDJE/cSPUOoyVoiV0j57o9xpEYtZBuM5RanPUsTDcYNvorKqP5mbN81JV3SmEkIRTL7JoHGpJAMDHFjXBfpAwgUCPhfJ2+CUCIyOoPZqlt4w+K9l+WeFZYDatr0HC1NO+stbvWq358HRdX27TexTocG5OEB4l9gqhnUYD2JHNlGidsm0vzFQJoIMaH26x9Kgosg6tZQ0t3jdoeLbTCSxOMM9dDQjjK447"},{title:"RSAES-OAEP Encryption Example 9.6",message:"trKOohmNDBAIvGQ=",seed:"I6reDh4Iu5uaeNIwKlL5whsuG6Ijqt4OHgi7m5p40jA=",encrypted:"PISd/61VECapJ7gfG4J2OroSl69kvIZD2uuqmiro3E4pmXfpdOW/q+1WCr574Pjsj/xrIUdgmNMAl8QjciO/nArYi0IFco1tCRLNZqMDGjzZifHIcDNCsvnKg/VRmkPrjXbndebLqMtw7taeVztYq1HKVAoGsdIvLkuhmsK0Iaesp+/8xka40c9hWwcXHsG+I7pevwFarxQQbuUjXSkZ2ObWgzgGSiGCw9QNUGpO0usATLSd0AFkeE+IM/KAwJCy"}],f(a,l,"sha256",p),e="rkXtVgHOxrjMBfgDk1xnTdvg11xMCf15UfxrDK7DE6jfOZcMUYv/ul7Wjz8NfyKkAp1BPxrgfk6+nkF3ziPn9UBLVp5O4b3PPB+wPvETgC1PhV65tRNLWnyAha3K5vovoUF+w3Y74XGwxit2Dt4jwSrZK5gIhMZB9aj6wmva1KAzgaIv4bdUiFCUyCUG1AGaU1ooav6ycbubpZLeGNz2AMKu6uVuAvfPefwUzzvcfNhP67v5UMqQMEsiGaeqBjrvosPBmA5WDNZK/neVhbYQdle5V4V+/eYBCYirfeQX/IjY84TE5ucsP5Q+DDHAxKXMNvh52KOsnX1Zhg6q2muDuw==",t="AQAB",n="BWsEIW/l81SsdyUKS2sMhSWoXFmwvYDFZFCiLV9DjllqMzqodeKR3UP0jLiLnV/A1Jn5/NHDl/mvwHDNnjmMjRnmHbfHQQprJnXfv100W4BNIBrdUC1c4t/LCRzpmXu+vlcwbzg+TViBA/A29+hdGTTRUqMj5KjbRR1vSlsbDxAswVDgL+7iuI3qStTBusyyTYQHLRTh0kpncfdAjuMFZPuG1Dk6NLzwt4hQHRkzA/E6IoSwAfD2Ser3kyjUrFxDCrRBSSCpRg7Rt7xA7GU+h20Jq8UJrkW1JRkBFqDCYQGEgphQnBw786SD5ydAVOFelwdQNumJ9gkygHtSV3UeeQ==",r="7PWuzR5VFf/6y9daKBbG6/SQGM37RjjhhdZqc5a2+AkPgBjH/ZXMNLhX3BfwzGUWuxNGq01YLK2te0EDNSOHtwM40IQEfJ2VObZJYgSz3W6kQkmSB77AH5ZCh/9jNsOYRlgzaEb1bkaGGIHBAjPSF2vxWl6W3ceAvIaKp30852k=",i="vEbEZPxqxMp4Ow6wijyEG3cvfpsvKLq9WIroheGgxh5IWKD7JawpmZDzW+hRZMJZuhF1zdcZJwcTUYSZK2wpt0bdDSyr4UKDX30UjMFhUktKCZRtSLgoRz8c52tstohsNFwD4F9B1RtcOpCj8kBzx9dKT+JdnPIcdZYPP8OGMYM=",s="xzVkVx0A+xXQij3plXpQkV1xJulELaz0K8guhi5Wc/9qAI7U0uN0YX34nxehYLQ7f9qctra3QhhgmBX31FyiY8FZqjLSctEn+vS8jKLXc3jorrGbCtfaPLPeCucxSYD2K21LCoddHfA8G645zNgz72zX4tlSi/CE0flp55Tp9sE=",o="Jlizf235wQML4dtoEX+p2H456itpO35tOi9wlHQT7sYULhj7jfy2rFRdfIagrUj4RXFw8O+ya8SBJsU+/R0WkgGY3CoRB9woLbaoDNMGI2C6P6E/cOQxL/GmzWuPxM2cXD2xfG1qVyEvc64p9hkye61ZsVOFhYW6Tii2CmKkXkk=",u="bzhSazklCFU07z5BWoNu3ouGFYosfL/sywvYNDBP7Gg7qNT0ecQz1DQW5jJpYjzqEAd22Fr/QB0//2EO5lQRzjsTY9Y6lwnu3kJkfOpWFJPVRXCoecGGgs2XcQuWIF7DERfXO182Ij+t1ui6kN18DuYdROFjJR4gx/ZuswURfLg=",a=c(e,t),l=h(e,t,n,r,i,s,o,u),p=[{title:"RSAES-OAEP Encryption Example 10.1",message:"i7pr+CpsD4bV8XVul5VocLCJU7BrTrIFvBaU7g==",seed:"R+GrcRn+5WyV7l6q2G9A0KpjvTNH4atxGf7lbJXuXqo=",encrypted:"iXCnHvFRO1zd7U4HnjDMCLRvnKZj6OVRMZv8VZCAyxdA1T4AUORzxWzAtAAA541iVjEs1n5MIrDkymBDk3cM1oha9XCGsXeazZpW2z2+4aeaM3mv/oz3QYfEGiet415sHNnikAQ9ZmYg2uzBNUOS90h0qRAWFdUV5Tyxo1HZ0slg37Ikvyu2d6tgWRAAjgiAGK7IzlU4muAfQ4GiLpvElfm+0vch7lhlrk7t5TErhEF7RWQe16lVBva7azIxlGyyrqOhYrmQ+6JQpmPnsmEKYpSxTUP2tLzoSH5e+Y0CSaD7ZB20PWILB+7PKRueJ23hlMYmnAgQBePWSUdsljXAgA=="},{title:"RSAES-OAEP Encryption Example 10.2",message:"5q0YHwU7WKkE8kV1EDc+Vw==",seed:"bRf1tMH/rDUdGVv3sJ0J8JpAec9tF/W0wf+sNR0ZW/c=",encrypted:"I3uBbIiYuvEYFA5OtRycm8zxMuuEoZMNRsPspeKZIGhcnQkqH8XEM8iJMeL6ZKA0hJm3jj4z1Xz7ra3tqMyTiw3vGKocjsYdXchK+ar3Atj/jXkdJLeIiqfTBA+orCKoPbrBXLllt4dqkhc3lbq0Z5lTBeh6caklDnmJGIMnxkiG3vON/uVpIR6LMBg+IudMCMOv2f++RpBhhrI8iJOsPbnebdMIrxviVaVxT22GUNehadT8WrHI/qKv+p1rCpD3AAyXAhJy7KKp1l+nPCy1IY1prey+YgBxCAnlHuHv2V7q1FZTRXMJe3iKubLeiX6SfAKU1sivpoqk5ntMSMgAfw=="},{title:"RSAES-OAEP Encryption Example 10.3",message:"UQos9g6Gb6I0BVPJTqOfvCVjEeg+lEVLQSQ=",seed:"OFOHUU3szHx0DdjN+druSaHL/VQ4U4dRTezMfHQN2M0=",encrypted:"n3scq/IYyBWbaN4Xd+mKJ0bZQR10yiSYzdjV1D1K3xiH11Tvhbj59PdRQXflSxE1QMhxN0jp9/tsErIlXqSnBH2XsTX6glPwJmdgXj7gZ1Aj+wsl15PctCcZv0I/4imvBBOSEd5TRmag3oU7gmbpKQCSHi6Hp2z5H/xEHekrRZemX7Dwl6A8tzVhCBpPweKNpe34OLMrHcdyb0k/uyabEHtzoZLpiOgHRjqi7SHr2ene9PPOswH7hc87xkiKtiFOpCeCScF6asFeiUTn5sf5tuPHVGqjKskwxcm/ToW3hm7ChnQdYcPRlnHrMeLBJt6o6xdrg6+SvsnNzctLxes0gA=="},{title:"RSAES-OAEP Encryption Example 10.4",message:"vN0ZDaO30wDfmgbiLKrip18QyR/2Z7fBa96LUwZKJkmpQEXJ",seed:"XKymoPdkFhqWhPhdkrbg7zfKi2VcrKag92QWGpaE+F0=",encrypted:"KWbozLkoxbGfY0Dixr8GE/JD+MDAXIUFzm7K5AYscTvyAh9EDkLfDc/i8Y9Cjz/GXWsrRAlzO9PmLj4rECjbaNdkyzgYUiXSVV0SWmEF62nhZcScf+5QWHgsv6syu2VXdkz9nW4O3LWir2M/HqJ6kmpKVm5o7TqeYZ7GrY25FUnFDM8DpXOZqOImHVAoh8Tim9d2V9lk2D2Av6Tdsa4SIyBDj5VcX3OVoTbqdkKj5It9ANHjXaqGwqEyj7j1cQrRzrbGVbib3qzvoFvGWoo5yzr3D8J8z/UXJ4sBkumcjrphFTDe9qQcJ5FI82ZZsChJssRcZl4ApFosoljixk0WkA=="},{title:"RSAES-OAEP Encryption Example 10.5",message:"p91sfcJLRvndXx6RraTDs9+UfodyMqk=",seed:"lbyp44WYlLPdhp+n7NW7xkAb8+SVvKnjhZiUs92Gn6c=",encrypted:"D7UPhV1nPwixcgg47HSlk/8yDLEDSXxoyo6H7MMopUTYwCmAjtnpWp4oWGg0sACoUlzKpR3PN21a4xru1txalcOkceylsQI9AIFvLhZyS20HbvQeExT9zQGyJaDhygC/6gPifgELk7x5QUqsd+TL/MQdgBZqbLO0skLOqNG3KrTMmN0oeWgxgjMmWnyBH0qkUpV5SlRN2P3nIHd/DZrkDn/qJG0MpXh6AeNHhvSgv8gyDG2Vzdf04OgvZLJTJaTdqHuXz93t7+PQ+QfKOG0wCEf5gOaYpkFarorv9XPLhzuOauN+Dd2IPzgKH5+wjbTZlZzEn+xRyDXK7s6GL/XOZw=="},{title:"RSAES-OAEP Encryption Example 10.6",message:"6vGnOhsMRglTfeac2SKLvPuajKjGw++vBW/kp/RjTtALfDnsaSLXuOosBOus",seed:"n0fd9C6X7qhWqb28cU6zrCL26zKfR930LpfuqFapvbw=",encrypted:"FO6Mv81w3SW/oVGIgdfAbIOW1eK8/UFdwryWg3ek0URFK09jNQtAaxT+66Yn5EJrTWh8fgRn1spnAOUsY5eq7iGpRsPGE86MLNonOvrBIht4Z+IDum55EgmwCrlfyiGe2fX4Xv1ifCQMSHd3OJTujAosVI3vPJaSsbTW6FqOFkM5m9uPqrdd+yhQ942wN4m4d4TG/YPx5gf62fbCRHOfvA5qSpO0XGQ45u+sWBAtOfzxmaYtf7WRAlu+JvIjTp8I2lAfVEuuW9+TJattx9RXN8jaWOBsceLIOfE6bkgad50UX5PyEtapnJOG1j0bh5PZ//oKtIASarB3PwdWM1EQTQ=="}],f(a,l,"sha256",p)}function u(e){var t=i.createBuffer(e),n=t.toHex();return new BigInteger(n,16)}function a(e){var t=i.decode64(e);return u(t)}function f(e,t,n,i){n==="sha1"?n=r.sha1.create():n==="sha256"&&(n=r.sha256.create());for(var s=0;s<i.length;++s){var o=i[s];it("should test "+o.title,function(){l(e,t,n,o.message,o.seed,o.encrypted)})}}function l(t,r,s,o,u,a){var o=i.decode64(o),u=i.decode64(u),f=n.encode_rsa_oaep(t,o,{seed:u,md:s}),l=t.encrypt(f,null);e.equal(a,i.encode64(l));var c=r.decrypt(l,null),h=n.decode_rsa_oaep(r,c,{md:s});e.equal(o,h),l=t.encrypt(o,"RSA-OAEP",{md:s}),h=r.decrypt(l,"RSA-OAEP",{md:s}),e.equal(o,h)}function c(e,n){return e=a(e),n=a(n),t.setRsaPublicKey(e,n)}function h(e,n,r,i,s,o,u,f){return e=a(e),n=a(n),r=a(r),i=a(i),s=a(s),o=a(o),u=a(u),f=a(f),t.setRsaPrivateKey(e,n,r,i,s,o,u,f)}function p(){var e,t,n,r,i,s,o,u,a,f;return e="qLOyhK+OtQs4cDSoYPFGxJGfMYdjzWxVmMiuSBGh4KvEx+CwgtaTpef87Wdc9GaFEncsDLxkp0LGxjD1M8jMcvYq6DPEC/JYQumEu3i9v5fAEH1VvbZi9cTg+rmEXLUUjvc5LdOq/5OuHmtme7PUJHYW1PW6ENTP0ibeiNOfFvs=",t="AQAB",n="UzOc/befyEZqZVxzFqyoXFX9j23YmP2vEZUX709S6P2OJY35P+4YD6DkqylpPNg7FSpVPUrE0YEri5+lrw5/Vf5zBN9BVwkm8zEfFcTWWnMsSDEW7j09LQrzVJrZv3y/t4rYhPhNW+sEck3HNpsx3vN9DPU56c/N095lNynq1dE=",r="0yc35yZ//hNBstXA0VCoG1hvsxMr7S+NUmKGSpy58wrzi+RIWY1BOhcu+4AsIazxwRxSDC8mpHHcrSEurHyjnQ==",i="zIhT0dVNpjD6wAT0cfKBx7iYLYIkpJDtvrM9Pj1cyTxHZXA9HdeRZC8fEWoN2FK+JBmyr3K/6aAw6GCwKItddw==",s="DhK/FxjpzvVZm6HDiC/oBGqQh07vzo8szCDk8nQfsKM6OEiuyckwX77L0tdoGZZ9RnGsxkMeQDeWjbN4eOaVwQ==",o="lSl7D5Wi+mfQBwfWCd/U/AXIna/C721upVvsdx6jM3NNklHnkILs2oZu/vE8RZ4aYxOGt+NUyJn18RLKhdcVgw==",u="T0VsUCSTvcDtKrdWo6btTWc1Kml9QhbpMhKxJ6Y9VBHOb6mNXb79cyY+NygUJ0OBgWbtfdY2h90qjKHS9PvY4Q==",a=c(e,t),f=h(e,t,n,r,i,s,o,u),{publicKey:a,privateKey:f}}it("should detect invalid RSAES-OAEP padding",function(){var t=p(),r=i.decode64("JRTfRpV1WmeyiOr0kFw27sZv0v0="),s=n.encode_rsa_oaep(t.publicKey,"datadatadatadata",{seed:r}),o=t.publicKey.encrypt(s,null),u=o.length*8;u/=8;for(var a=8;a<u;++a){var f=a/8,l=a%8,c=o.substring(0,f),h=1<<l;c+=String.fromCharCode(o.charCodeAt(f)^h),c+=o.substring(f+1);try{var d=t.privateKey.decrypt(c,null);throw n.decode_rsa_oaep(t.privateKey,d),{message:"Expected an exception."}}catch(v){e.equal(v.message,"Invalid RSAES-OAEP padding.")}}}),it("should detect leading zero bytes",function(){var t=p(),r=i.fillString("\0",80),s=n.encode_rsa_oaep(t.publicKey,r),o=t.publicKey.encrypt(s,null),u=t.privateKey.decrypt(o,null),a=n.decode_rsa_oaep(t.privateKey,u);e.equal(r,a)}),s(),o()})}typeof define=="function"?define("test/pkcs1",["forge/pki","forge/pkcs1","forge/md","forge/util"],function(t,n,r,i){e(ASSERT,t(),n(),r(),i())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/pki")(),require("../../js/pkcs1")(),require("../../js/md")(),require("../../js/util")())}(),function(){function e(e,t,n,r){var i={privateKey:"-----BEGIN RSA PRIVATE KEY-----\r\nMIICXQIBAAKBgQDL0EugUiNGMWscLAVM0VoMdhDZEJOqdsUMpx9U0YZI7szokJqQ\r\nNIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMGTkP3VF29vXBo+dLq5e+8VyAy\r\nQ3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGtvnM+z0MYDdKo80efzwIDAQAB\r\nAoGAIzkGONi5G+JifmXlLJdplom486p3upf4Ce2/7mqfaG9MnkyPSairKD/JXvfh\r\nNNWkkN8DKKDKBcVVElPgORYT0qwrWc7ueLBMUCbRXb1ZyfEulimG0R3kjUh7NYau\r\nDaIkVgfykXGSQMZx8FoaT6L080zd+0emKDDYRrb+/kgJNJECQQDoUZoiC2K/DWNY\r\nh3/ppZ0ane2y4SBmJUHJVMPQ2CEgxsrJTxet668ckNCKaOP/3VFPoWC41f17DvKq\r\nnoYINNntAkEA4JbZBZBVUrQFhHlrpXT4jzqtO2RlKZzEq8qmFZfEErxOT1WMyyCi\r\nlAQ5gUKardo1Kf0omC8Xq/uO9ZYdED55KwJBALs6cJ65UFaq4oLJiQPzLd7yokuE\r\ndcj8g71PLBTW6jPxIiMFNA89nz3FU9wIVp+xbMNhSoMMKqIPVPC+m0Rn260CQQDA\r\nI83fWK/mZWUjBM33a68KumRiH238v8XyQxj7+C8i6D8G2GXvkigFAehAkb7LZZd+\r\nKLuGFyPlWv3fVWHf99KpAkBQFKk3MRMl6IGJZUEFQe4l5whm8LkGU4acSqv9B3xt\r\nqROkCrsFrMPqjuuzEmyHoQZ64r2PLJg7FOuyhBnQUOt4\r\n-----END RSA PRIVATE KEY-----\r\n",publicKey:"-----BEGIN PUBLIC KEY-----\r\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDL0EugUiNGMWscLAVM0VoMdhDZ\r\nEJOqdsUMpx9U0YZI7szokJqQNIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMG\r\nTkP3VF29vXBo+dLq5e+8VyAyQ3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGt\r\nvnM+z0MYDdKo80efzwIDAQAB\r\n-----END PUBLIC KEY-----\r\n",certificate:"-----BEGIN CERTIFICATE-----\r\nMIIDIjCCAougAwIBAgIJANE2aHSbwpaRMA0GCSqGSIb3DQEBBQUAMGoxCzAJBgNV\r\nBAYTAlVTMREwDwYDVQQIEwhWaXJnaW5pYTETMBEGA1UEBxMKQmxhY2tzYnVyZzEN\r\nMAsGA1UEChMEVGVzdDENMAsGA1UECxMEVGVzdDEVMBMGA1UEAxMMbXlzZXJ2ZXIu\r\nY29tMB4XDTEwMDYxOTE3MzYyOFoXDTExMDYxOTE3MzYyOFowajELMAkGA1UEBhMC\r\nVVMxETAPBgNVBAgTCFZpcmdpbmlhMRMwEQYDVQQHEwpCbGFja3NidXJnMQ0wCwYD\r\nVQQKEwRUZXN0MQ0wCwYDVQQLEwRUZXN0MRUwEwYDVQQDEwxteXNlcnZlci5jb20w\r\ngZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMvQS6BSI0YxaxwsBUzRWgx2ENkQ\r\nk6p2xQynH1TRhkjuzOiQmpA0jCiSJDoSic2dZIyUi/LjoCGeVFif57N5N5Tt4wZO\r\nQ/dUXb29cGj50url77xXIDJDcXMzXAji2ziFEAIXzDqarKBdDuL9IO7z+tepEa2+\r\ncz7PQxgN0qjzR5/PAgMBAAGjgc8wgcwwHQYDVR0OBBYEFPV1Y+DHXW6bA/r9sv1y\r\nNJ8jAwMAMIGcBgNVHSMEgZQwgZGAFPV1Y+DHXW6bA/r9sv1yNJ8jAwMAoW6kbDBq\r\nMQswCQYDVQQGEwJVUzERMA8GA1UECBMIVmlyZ2luaWExEzARBgNVBAcTCkJsYWNr\r\nc2J1cmcxDTALBgNVBAoTBFRlc3QxDTALBgNVBAsTBFRlc3QxFTATBgNVBAMTDG15\r\nc2VydmVyLmNvbYIJANE2aHSbwpaRMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEF\r\nBQADgYEARdH2KOlJWTC1CS2y/PAvg4uiM31PXMC1hqSdJlnLM1MY4hRfuf9VyTeX\r\nY6FdybcyDLSxKn9id+g9229ci9/s9PI+QmD5vXd8yZyScLc2JkYB4GC6+9D1+/+x\r\ns2hzMxuK6kzZlP+0l9LGcraMQPGRydjCARZZm4Uegln9rh85XFQ=\r\n-----END CERTIFICATE-----\r\n"};describe("x509",function(){it("should convert certificate to/from PEM",function(){var n=t.certificateFromPem(i.certificate);e.equal(t.certificateToPem(n),i.certificate)}),it("should verify self-signed certificate",function(){var n=t.certificateFromPem(i.certificate);e.ok(n.verify(n))}),it("should generate a self-signed certificate",function(){var n={privateKey:t.privateKeyFromPem(i.privateKey),publicKey:t.publicKeyFromPem(i.publicKey)},r=t.createCertificate();r.publicKey=n.publicKey,r.serialNumber="01",r.validity.notBefore=new Date,r.validity.notAfter=new Date,r.validity.notAfter.setFullYear(r.validity.notBefore.getFullYear()+1);var s=[{name:"commonName",value:"example.org"},{name:"countryName",value:"US"},{shortName:"ST",value:"Virginia"},{name:"localityName",value:"Blacksburg"},{name:"organizationName",value:"Test"},{shortName:"OU",value:"Test"}];r.setSubject(s),r.setIssuer(s),r.setExtensions([{name:"basicConstraints",cA:!0},{name:"keyUsage",keyCertSign:!0,digitalSignature:!0,nonRepudiation:!0,keyEncipherment:!0,dataEncipherment:!0},{name:"extKeyUsage",serverAuth:!0,clientAuth:!0,codeSigning:!0,emailProtection:!0,timeStamping:!0},{name:"nsCertType",client:!0,server:!0,email:!0,objsign:!0,sslCA:!0,emailCA:!0,objCA:!0},{name:"subjectAltName",altNames:[{type:6,value:"http://example.org/webid#me"}]},{name:"subjectKeyIdentifier"}]),r.sign(n.privateKey);var o=t.certificateToPem(r);r=t.certificateFromPem(o);var u=t.createCaStore();u.addCertificate(r),t.verifyCertificateChain(u,[r],function(t,n,i){return e.equal(t,!0),e.ok(r.verifySubjectKeyIdentifier()),!0})}),it("should verify certificate with sha1WithRSAEncryption signature",function(){var n="-----BEGIN CERTIFICATE-----\r\nMIIDZDCCAs2gAwIBAgIKQ8fjjgAAAABh3jANBgkqhkiG9w0BAQUFADBGMQswCQYD\r\nVQQGEwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzEiMCAGA1UEAxMZR29vZ2xlIElu\r\ndGVybmV0IEF1dGhvcml0eTAeFw0xMjA2MjcxMzU5MTZaFw0xMzA2MDcxOTQzMjda\r\nMGcxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1N\r\nb3VudGFpbiBWaWV3MRMwEQYDVQQKEwpHb29nbGUgSW5jMRYwFAYDVQQDEw13d3cu\r\nZ29vZ2xlLmRlMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCw2Hw3vNy5QMSd\r\n0/iMCS8lwZk9lnEk2NmrJt6vGJfRGlBprtHp5lpMFMoi+x8m8EwGVxXHGp7hLyN/\r\ngXuUjL7/DY9fxxx9l77D+sDZz7jfUfWmhS03Ra1FbT6myF8miVZFChJ8XgWzioJY\r\ngyNdRUC9149yrXdPWrSmSVaT0+tUCwIDAQABo4IBNjCCATIwHQYDVR0lBBYwFAYI\r\nKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBTiQGhrO3785rMPIKZ/zQEl5RyS\r\n0TAfBgNVHSMEGDAWgBS/wDDr9UMRPme6npH7/Gra42sSJDBbBgNVHR8EVDBSMFCg\r\nTqBMhkpodHRwOi8vd3d3LmdzdGF0aWMuY29tL0dvb2dsZUludGVybmV0QXV0aG9y\r\naXR5L0dvb2dsZUludGVybmV0QXV0aG9yaXR5LmNybDBmBggrBgEFBQcBAQRaMFgw\r\nVgYIKwYBBQUHMAKGSmh0dHA6Ly93d3cuZ3N0YXRpYy5jb20vR29vZ2xlSW50ZXJu\r\nZXRBdXRob3JpdHkvR29vZ2xlSW50ZXJuZXRBdXRob3JpdHkuY3J0MAwGA1UdEwEB\r\n/wQCMAAwDQYJKoZIhvcNAQEFBQADgYEAVJ0qt/MBvHEPuWHeH51756qy+lBNygLA\r\nXp5Gq+xHUTOzRty61BR05zv142hYAGWvpvnEOJ/DI7V3QlXK8a6dQ+du97obQJJx\r\n7ekqtfxVzmlSb23halYSoXmWgP8Tq0VUDsgsSLE7fS8JuO1soXUVKj1/6w189HL6\r\nLsngXwZSuL0=\r\n-----END CERTIFICATE-----\r\n",r="-----BEGIN CERTIFICATE-----\r\nMIICsDCCAhmgAwIBAgIDC2dxMA0GCSqGSIb3DQEBBQUAME4xCzAJBgNVBAYTAlVT\r\nMRAwDgYDVQQKEwdFcXVpZmF4MS0wKwYDVQQLEyRFcXVpZmF4IFNlY3VyZSBDZXJ0\r\naWZpY2F0ZSBBdXRob3JpdHkwHhcNMDkwNjA4MjA0MzI3WhcNMTMwNjA3MTk0MzI3\r\nWjBGMQswCQYDVQQGEwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzEiMCAGA1UEAxMZ\r\nR29vZ2xlIEludGVybmV0IEF1dGhvcml0eTCBnzANBgkqhkiG9w0BAQEFAAOBjQAw\r\ngYkCgYEAye23pIucV+eEPkB9hPSP0XFjU5nneXQUr0SZMyCSjXvlKAy6rWxJfoNf\r\nNFlOCnowzdDXxFdF7dWq1nMmzq0yE7jXDx07393cCDaob1FEm8rWIFJztyaHNWrb\r\nqeXUWaUr/GcZOfqTGBhs3t0lig4zFEfC7wFQeeT9adGnwKziV28CAwEAAaOBozCB\r\noDAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFL/AMOv1QxE+Z7qekfv8atrjaxIk\r\nMB8GA1UdIwQYMBaAFEjmaPkr0rKV10fYIyAQTzOYkJ/UMBIGA1UdEwEB/wQIMAYB\r\nAf8CAQAwOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2NybC5nZW90cnVzdC5jb20v\r\nY3Jscy9zZWN1cmVjYS5jcmwwDQYJKoZIhvcNAQEFBQADgYEAuIojxkiWsRF8YHde\r\nBZqrocb6ghwYB8TrgbCoZutJqOkM0ymt9e8kTP3kS8p/XmOrmSfLnzYhLLkQYGfN\r\n0rTw8Ktx5YtaiScRhKqOv5nwnQkhClIZmloJ0pC3+gz4fniisIWvXEyZ2VxVKfml\r\nUUIuOss4jHg7y/j7lYe8vJD5UDI=\r\n-----END CERTIFICATE-----\r\n",i=t.certificateFromPem(n,!0),s=t.certificateFromPem(r);e.ok(s.verify(i))}),it("should verify certificate with sha256WithRSAEncryption signature",function(){var n="-----BEGIN CERTIFICATE-----\r\nMIIDuzCCAqOgAwIBAgIEO5vZjDANBgkqhkiG9w0BAQsFADBGMQswCQYDVQQGEwJE\r\nRTEPMA0GA1UEChMGRWxzdGVyMQswCQYDVQQLEwJDQTEZMBcGA1UEAxMQRWxzdGVy\r\nU29mdFRlc3RDQTAeFw0xMDA5MTUwNTM4MjRaFw0xMzA5MTUwNTM4MjRaMCsxFDAS\r\nBgNVBAUTCzEwMDIzMTQ5OTRDMRMwEQYDVQQDEwoxMDAyMzE0OTk0MIGfMA0GCSqG\r\nSIb3DQEBAQUAA4GNADCBiQKBgQCLPqjbwjsugzw6+qwwm/pdzDwk7ASIsBYJ17GT\r\nqyT0zCnYmdDDGWsYc+xxFVVIi8xBt6Mlq8Rwj+02UJhY9qm6zRA9MqFZC3ih+HoW\r\nxq7H8N2d10N0rX6h5PSjkF5fU5ugncZmppsRGJ9DNXgwjpf/CsH2rqThUzK4xfrq\r\njpDS/wIDAQABo4IBTjCCAUowDgYDVR0PAQH/BAQDAgUgMAwGA1UdEwEB/wQCMAAw\r\nHQYDVR0OBBYEFF1h7H37OQivS57GD8+nK6VsgMPTMIGXBgNVHR8EgY8wgYwwgYmg\r\ngYaggYOGgYBsZGFwOi8vMTkyLjE2OC42LjI0OjM4OS9sJTNkQ0ElMjBaZXJ0aWZp\r\na2F0ZSxvdSUzZENBLGNuJTNkRWxzdGVyU29mdFRlc3RDQSxkYyUzZHdpZXNlbCxk\r\nYyUzZGVsc3RlcixkYyUzZGRlPz9iYXNlPyhvYmplY3RDbGFzcz0qKTBxBgNVHSME\r\najBogBRBILMYmlZu//pj3wjDe2UPkq7jk6FKpEgwRjELMAkGA1UEBhMCREUxDzAN\r\nBgNVBAoTBkVsc3RlcjEPMA0GA1UECxMGUm9vdENBMRUwEwYDVQQDEwxFbHN0ZXJS\r\nb290Q0GCBDuayikwDQYJKoZIhvcNAQELBQADggEBAK8Z1+/VNyU5w/EiyhFH5aRE\r\nMzxo0DahqKEm4pW5haBgKubJwZGs+CrBZR70TPbZGgJd36eyMgeXb/06lBnxewii\r\nI/aY6wMTviQTpqFnz5m0Le8UhH+hY1bqNG/vf6J+1gbOSrZyhAUV+MDJbL/OkzX4\r\nvoVAfUBqSODod0f5wCW2RhvBmB9E62baP6qizdxyPA4iV16H4C0etd/7coLX6NZC\r\noz3Yu0IRTQCH+YrpfIbxGb0grNhtCTfFpa287fuzu8mIEvLNr8GibhBXmQg7iJ+y\r\nq0VIyZLY8k6jEPrUB5Iv5ouSR19Dda/2+xJPlT/bosuNcErEuk/yKAHWAzwm1wQ=\r\n-----END CERTIFICATE-----\r\n",r="-----BEGIN CERTIFICATE-----\r\nMIIESjCCAzKgAwIBAgIEO5rKKTANBgkqhkiG9w0BAQsFADBGMQswCQYDVQQGEwJE\r\nRTEPMA0GA1UEChMGRWxzdGVyMQ8wDQYDVQQLEwZSb290Q0ExFTATBgNVBAMTDEVs\r\nc3RlclJvb3RDQTAeFw0wOTA3MjgwODE5MTFaFw0xNDA3MjgwODE5MTFaMEYxCzAJ\r\nBgNVBAYTAkRFMQ8wDQYDVQQKEwZFbHN0ZXIxCzAJBgNVBAsTAkNBMRkwFwYDVQQD\r\nExBFbHN0ZXJTb2Z0VGVzdENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC\r\nAQEAv5uoKLnxXQe75iqwqgyt3H6MDAx/wvUVs26+2+yHpEUb/2gA3L8E+NChSb9E\r\naNgxxoac3Yhvxzq2mPpih3vkY7Xw512Tm8l/OPbT8kbmBJmYZneFALXHytAIZiEf\r\ne0ZYNKAlClFIgNP5bE9UjTqVEEoSiUhpTubM6c5xEYVznnwPBoYQ0ari7RTDYnME\r\nHK4vMfoeBeWHYPiEygNHnGUG8d3merRC/lQASUtL6ikmLWKCKHfyit5ACzPNKAtw\r\nIzHAzD5ek0BpcUTci8hUsKz2ZvmoZcjPyj63veQuMYS5cTMgr3bfz9uz1xuoEDsb\r\nSv9rQX9Iw3N7yMpxTDJTqGKhYwIDAQABo4IBPjCCATowDgYDVR0PAQH/BAQDAgEG\r\nMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFEEgsxiaVm7/+mPfCMN7ZQ+S\r\nruOTMIGXBgNVHR8EgY8wgYwwgYmggYaggYOGgYBsZGFwOi8vMTkyLjE2OC42LjI0\r\nOjM4OS9sJTNkQ0ElMjBaZXJ0aWZpa2F0ZSxvdSUzZFJvb3RDQSxjbiUzZEVsc3Rl\r\nclJvb3RDQSxkYyUzZHdpZXNlbCxkYyUzZGVsc3RlcixkYyUzZGRlPz9iYXNlPyhv\r\nYmplY3RDbGFzcz0qKTBbBgNVHSMEVDBSoUqkSDBGMQswCQYDVQQGEwJERTEPMA0G\r\nA1UEChMGRWxzdGVyMQ8wDQYDVQQLEwZSb290Q0ExFTATBgNVBAMTDEVsc3RlclJv\r\nb3RDQYIEO5rKADANBgkqhkiG9w0BAQsFAAOCAQEAFauDnfHSbgRmbFkpQUXM5wKi\r\nK5STAaVps201iAjacX5EsOs5L37VUMoT9G2DAE8Z6B1pIiR3zcd3UpiHnFlUTC0f\r\nZdOCXzDkOfziKY/RzuUsLNFUhBizCIA0+XcKgm3dSA5ex8fChLJddSYheSLuPua7\r\niNMuzaU2YnevbMwpdEsl55Qr/uzcc0YM/mCuM4vsNFyFml91SQyPPmdR3VvGOoGl\r\nqS1R0HSoPJUvr0N0kARwD7kO3ikcJ6FxBCsgVLZHGJC+q8PQNZmVMbfgjH4nAyP8\r\nu7Qe03v2WLW0UgKu2g0UcQXWXbovktpZoK0fUOwv3bqsZ0K1IjVvMKG8OysUvA==\r\n-----END CERTIFICATE-----\r\n",i=t.certificateFromPem(n,!0),s=t.certificateFromPem(r);e.ok(s.verify(i))}),it("should import certificate with sha256 RSASSA-PSS signature",function(){var n="-----BEGIN CERTIFICATE-----\r\nMIIERzCCAvugAwIBAgIEO50CcjBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQC\r\nAQUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCASAwRjELMAkGA1UE\r\nBhMCREUxDzANBgNVBAoTBkVsc3RlcjELMAkGA1UECxMCQ0ExGTAXBgNVBAMTEEVs\r\nc3RlclNvZnRUZXN0Q0EwHhcNMTEwNzI4MTIxMzU3WhcNMTQwNzI4MTIxMzU3WjAr\r\nMRQwEgYDVQQFEwsxMDAyNzUzMzI1QzETMBEGA1UEAxMKMTAwMjc1MzMyNTCCASIw\r\nDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALHCogo7LVUkxWsMIc/0jgH2PCLt\r\nukbATPehxWBG1XUPrz53lWgFJzlZaKLlLVVnXrfULaifuOKlZP6SM1JQlL1JuYgY\r\nAdgZyHjderNIk5NsSTmefwonSn/ukri5IRTH420oHtSjxk6+/DXlWnQy5OzTN6Bq\r\njVJo8L+TTmf2jWuEam5cWa+YVP2k3tIqX5yMUDFjKO4znHdtIkHnBE0Kx03rWQRB\r\nTSYWDgDm2gttdOs9JVeuW0nnwQj27uo9gOR0iyaUjVrKLZ95p6zpXhM4uMSVRNeo\r\nLqkdqP2n+4pDXZVqLNgjkHQUS/xq9Q/kYgT2J7wkGfYxP9to7TG7vra1eOECAwEA\r\nAaOB7zCB7DAOBgNVHQ8BAf8EBAMCBSAwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU\r\nNDJ2BZIk6ukLqkdmttH12bu2leswOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2Ny\r\nbC5lbHN0ZXIuZGUvRWxzdGVyU29mdFRlc3RDQS5jcmwwcQYDVR0jBGowaIAU1R9A\r\nHmpdzaxK3v+ihQsEpAFgzOKhSqRIMEYxCzAJBgNVBAYTAkRFMQ8wDQYDVQQKEwZF\r\nbHN0ZXIxDzANBgNVBAsTBlJvb3RDQTEVMBMGA1UEAxMMRWxzdGVyUm9vdENBggQ7\r\nmsqPMEEGCSqGSIb3DQEBCjA0oA8wDQYJYIZIAWUDBAIBBQChHDAaBgkqhkiG9w0B\r\nAQgwDQYJYIZIAWUDBAIBBQCiAwIBIAOCAQEAJBYNRZpe+z3yPPLV539Yci6OfjVg\r\nvs1e3rvSvcpFaqRJ8vZ8WNx3uuRQZ6B4Z3YEc00UJAOl3wU6KhamyryK2YvCrPg+\r\nTS5QDXNaO2z/rAnY1wWSlwBPlhqpMRrNv9cRXBcgK5YxprjytCVYN0UHdintgYxG\r\nfg7QYiFb00UXxAza1AFrpG+RqySFfO1scmu4kgpeb6A3USnQ0r6rZz6dt6NqgZZ6\r\noUpDOCvnS9XSOWuvJirV8hIU0KAagguTbwfTqt9nt0wDlwZpemsJZ4Vvnvy8d9Jf\r\nzA68EWHbZLr2QP9hb3sHCWJgplMsTJnUwRfi2hf5KNtP8Xg5DSLMfTEbhw==\r\n-----END CERTIFICATE-----\r\n",r=t.certificateFromPem(n,!0);e.equal(r.signatureOid,t.oids["RSASSA-PSS"]),e.equal(r.signatureParameters.hash.algorithmOid,t.oids.sha256),e.equal(r.signatureParameters.mgf.algorithmOid,t.oids.mgf1),e.equal(r.signatureParameters.mgf.hash.algorithmOid,t.oids.sha256),e.equal(r.siginfo.algorithmOid,t.oids["RSASSA-PSS"]),e.equal(r.siginfo.parameters.hash.algorithmOid,t.oids.sha256),e.equal(r.siginfo.parameters.mgf.algorithmOid,t.oids.mgf1),e.equal(r.siginfo.parameters.mgf.hash.algorithmOid,t.oids.sha256)}),it("should export certificate with sha256 RSASSA-PSS signature",function(){var n="-----BEGIN CERTIFICATE-----\r\nMIIERzCCAvugAwIBAgIEO50CcjBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQC\r\nAQUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCASAwRjELMAkGA1UE\r\nBhMCREUxDzANBgNVBAoTBkVsc3RlcjELMAkGA1UECxMCQ0ExGTAXBgNVBAMTEEVs\r\nc3RlclNvZnRUZXN0Q0EwHhcNMTEwNzI4MTIxMzU3WhcNMTQwNzI4MTIxMzU3WjAr\r\nMRQwEgYDVQQFEwsxMDAyNzUzMzI1QzETMBEGA1UEAxMKMTAwMjc1MzMyNTCCASIw\r\nDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALHCogo7LVUkxWsMIc/0jgH2PCLt\r\nukbATPehxWBG1XUPrz53lWgFJzlZaKLlLVVnXrfULaifuOKlZP6SM1JQlL1JuYgY\r\nAdgZyHjderNIk5NsSTmefwonSn/ukri5IRTH420oHtSjxk6+/DXlWnQy5OzTN6Bq\r\njVJo8L+TTmf2jWuEam5cWa+YVP2k3tIqX5yMUDFjKO4znHdtIkHnBE0Kx03rWQRB\r\nTSYWDgDm2gttdOs9JVeuW0nnwQj27uo9gOR0iyaUjVrKLZ95p6zpXhM4uMSVRNeo\r\nLqkdqP2n+4pDXZVqLNgjkHQUS/xq9Q/kYgT2J7wkGfYxP9to7TG7vra1eOECAwEA\r\nAaOB7zCB7DAOBgNVHQ8BAf8EBAMCBSAwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU\r\nNDJ2BZIk6ukLqkdmttH12bu2leswOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2Ny\r\nbC5lbHN0ZXIuZGUvRWxzdGVyU29mdFRlc3RDQS5jcmwwcQYDVR0jBGowaIAU1R9A\r\nHmpdzaxK3v+ihQsEpAFgzOKhSqRIMEYxCzAJBgNVBAYTAkRFMQ8wDQYDVQQKEwZF\r\nbHN0ZXIxDzANBgNVBAsTBlJvb3RDQTEVMBMGA1UEAxMMRWxzdGVyUm9vdENBggQ7\r\nmsqPMEEGCSqGSIb3DQEBCjA0oA8wDQYJYIZIAWUDBAIBBQChHDAaBgkqhkiG9w0B\r\nAQgwDQYJYIZIAWUDBAIBBQCiAwIBIAOCAQEAJBYNRZpe+z3yPPLV539Yci6OfjVg\r\nvs1e3rvSvcpFaqRJ8vZ8WNx3uuRQZ6B4Z3YEc00UJAOl3wU6KhamyryK2YvCrPg+\r\nTS5QDXNaO2z/rAnY1wWSlwBPlhqpMRrNv9cRXBcgK5YxprjytCVYN0UHdintgYxG\r\nfg7QYiFb00UXxAza1AFrpG+RqySFfO1scmu4kgpeb6A3USnQ0r6rZz6dt6NqgZZ6\r\noUpDOCvnS9XSOWuvJirV8hIU0KAagguTbwfTqt9nt0wDlwZpemsJZ4Vvnvy8d9Jf\r\nzA68EWHbZLr2QP9hb3sHCWJgplMsTJnUwRfi2hf5KNtP8Xg5DSLMfTEbhw==\r\n-----END CERTIFICATE-----\r\n",r=t.certificateFromPem(n,!0);e.equal(t.certificateToPem(r),n)}),it("should verify certificate with sha256 RSASSA-PSS signature",function(){var n="-----BEGIN CERTIFICATE-----\r\nMIIERzCCAvugAwIBAgIEO50CcjBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQC\r\nAQUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCASAwRjELMAkGA1UE\r\nBhMCREUxDzANBgNVBAoTBkVsc3RlcjELMAkGA1UECxMCQ0ExGTAXBgNVBAMTEEVs\r\nc3RlclNvZnRUZXN0Q0EwHhcNMTEwNzI4MTIxMzU3WhcNMTQwNzI4MTIxMzU3WjAr\r\nMRQwEgYDVQQFEwsxMDAyNzUzMzI1QzETMBEGA1UEAxMKMTAwMjc1MzMyNTCCASIw\r\nDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALHCogo7LVUkxWsMIc/0jgH2PCLt\r\nukbATPehxWBG1XUPrz53lWgFJzlZaKLlLVVnXrfULaifuOKlZP6SM1JQlL1JuYgY\r\nAdgZyHjderNIk5NsSTmefwonSn/ukri5IRTH420oHtSjxk6+/DXlWnQy5OzTN6Bq\r\njVJo8L+TTmf2jWuEam5cWa+YVP2k3tIqX5yMUDFjKO4znHdtIkHnBE0Kx03rWQRB\r\nTSYWDgDm2gttdOs9JVeuW0nnwQj27uo9gOR0iyaUjVrKLZ95p6zpXhM4uMSVRNeo\r\nLqkdqP2n+4pDXZVqLNgjkHQUS/xq9Q/kYgT2J7wkGfYxP9to7TG7vra1eOECAwEA\r\nAaOB7zCB7DAOBgNVHQ8BAf8EBAMCBSAwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU\r\nNDJ2BZIk6ukLqkdmttH12bu2leswOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2Ny\r\nbC5lbHN0ZXIuZGUvRWxzdGVyU29mdFRlc3RDQS5jcmwwcQYDVR0jBGowaIAU1R9A\r\nHmpdzaxK3v+ihQsEpAFgzOKhSqRIMEYxCzAJBgNVBAYTAkRFMQ8wDQYDVQQKEwZF\r\nbHN0ZXIxDzANBgNVBAsTBlJvb3RDQTEVMBMGA1UEAxMMRWxzdGVyUm9vdENBggQ7\r\nmsqPMEEGCSqGSIb3DQEBCjA0oA8wDQYJYIZIAWUDBAIBBQChHDAaBgkqhkiG9w0B\r\nAQgwDQYJYIZIAWUDBAIBBQCiAwIBIAOCAQEAJBYNRZpe+z3yPPLV539Yci6OfjVg\r\nvs1e3rvSvcpFaqRJ8vZ8WNx3uuRQZ6B4Z3YEc00UJAOl3wU6KhamyryK2YvCrPg+\r\nTS5QDXNaO2z/rAnY1wWSlwBPlhqpMRrNv9cRXBcgK5YxprjytCVYN0UHdintgYxG\r\nfg7QYiFb00UXxAza1AFrpG+RqySFfO1scmu4kgpeb6A3USnQ0r6rZz6dt6NqgZZ6\r\noUpDOCvnS9XSOWuvJirV8hIU0KAagguTbwfTqt9nt0wDlwZpemsJZ4Vvnvy8d9Jf\r\nzA68EWHbZLr2QP9hb3sHCWJgplMsTJnUwRfi2hf5KNtP8Xg5DSLMfTEbhw==\r\n-----END CERTIFICATE-----\r\n",r="-----BEGIN CERTIFICATE-----\r\nMIIEZDCCAxigAwIBAgIEO5rKjzBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQC\r\nAQUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCASAwRjELMAkGA1UE\r\nBhMCREUxDzANBgNVBAoTBkVsc3RlcjEPMA0GA1UECxMGUm9vdENBMRUwEwYDVQQD\r\nEwxFbHN0ZXJSb290Q0EwHhcNMTEwNzI4MTExNzI4WhcNMTYwNzI4MTExNzI4WjBG\r\nMQswCQYDVQQGEwJERTEPMA0GA1UEChMGRWxzdGVyMQswCQYDVQQLEwJDQTEZMBcG\r\nA1UEAxMQRWxzdGVyU29mdFRlc3RDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\r\nAQoCggEBAMFpz3sXnXq4ZUBdYdpG5DJVfITLXYwXPfEJzr1pLRfJ2eMPn7k3B/2g\r\nbvuH30zKmDRjlfV51sFw4l1l+cQugzy5FEOrfE6g7IvhpBUf9SQujVWtE3BoSRR5\r\npSGbIWC7sm2SG0drpoCRpL0xmWZlAUS5mz7hBecJC/kKkKeOxUg5h492XQgWd0ow\r\n6GlyQBxJCmxgQBMnLS0stecs234hR5gvTHic50Ey+gRMPsTyco2Fm0FqvXtBuOsj\r\nzAW7Nk2hnM6awFHVMDBLm+ClE1ww0dLW0ujkdoGsTEbvmM/w8MBI6WAiWaanjK/x\r\nlStmQeUVXKd+AfuzT/FIPrwANcC1qGUCAwEAAaOB8TCB7jAOBgNVHQ8BAf8EBAMC\r\nAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU1R9AHmpdzaxK3v+ihQsE\r\npAFgzOIwNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5lbHN0ZXIuZGUvRWxz\r\ndGVyUm9vdENBLmNybDBxBgNVHSMEajBogBS3zfTokckL2H/fTojW02K+metEcaFK\r\npEgwRjELMAkGA1UEBhMCREUxDzANBgNVBAoTBkVsc3RlcjEPMA0GA1UECxMGUm9v\r\ndENBMRUwEwYDVQQDEwxFbHN0ZXJSb290Q0GCBDuaylowQQYJKoZIhvcNAQEKMDSg\r\nDzANBglghkgBZQMEAgEFAKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgEFAKID\r\nAgEgA4IBAQBjT107fBmSfQNUYCpXIlaS/pogPoCahuC1g8QDtq6IEVXoEgXCzfVN\r\nJYHyYOQOraP4O2LEcXpo/oc0MMpVF06QLKG/KieUE0mrZnsCJ3GLSJkM8tI8bRHj\r\n8tAhlViMacUqnKKufCs/rIN25JB57+sCyFqjtRbSt88e+xqFJ5I79Btp/bNtoj2G\r\nOJGl997EPua9/yJuvdA9W67WO/KwEh+FqylB1pAapccOHqttwu4QJIc/XJfG5rrf\r\n8QZz8HIuOcroA4zIrprQ1nJRCuMII04ueDtBVz1eIEmqNEUkr09JdK8M0LKH0lMK\r\nYsgjai/P2mPVVwhVw6dHzUVRLXh3xIQr\r\n-----END CERTIFICATE-----\r\n",i=t.certificateFromPem(n,!0),s=t.certificateFromPem(r);e.ok(s.verify(i))})})}typeof define=="function"?define("test/x509",["forge/pki","forge/md","forge/util"],function(t,n,r){e(ASSERT,t(),n(),r())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/pki")(),require("../../js/md")(),require("../../js/util")())}(),function(){function e(e,t){var n={privateKey:"-----BEGIN RSA PRIVATE KEY-----\r\nMIICXQIBAAKBgQDL0EugUiNGMWscLAVM0VoMdhDZEJOqdsUMpx9U0YZI7szokJqQ\r\nNIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMGTkP3VF29vXBo+dLq5e+8VyAy\r\nQ3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGtvnM+z0MYDdKo80efzwIDAQAB\r\nAoGAIzkGONi5G+JifmXlLJdplom486p3upf4Ce2/7mqfaG9MnkyPSairKD/JXvfh\r\nNNWkkN8DKKDKBcVVElPgORYT0qwrWc7ueLBMUCbRXb1ZyfEulimG0R3kjUh7NYau\r\nDaIkVgfykXGSQMZx8FoaT6L080zd+0emKDDYRrb+/kgJNJECQQDoUZoiC2K/DWNY\r\nh3/ppZ0ane2y4SBmJUHJVMPQ2CEgxsrJTxet668ckNCKaOP/3VFPoWC41f17DvKq\r\nnoYINNntAkEA4JbZBZBVUrQFhHlrpXT4jzqtO2RlKZzEq8qmFZfEErxOT1WMyyCi\r\nlAQ5gUKardo1Kf0omC8Xq/uO9ZYdED55KwJBALs6cJ65UFaq4oLJiQPzLd7yokuE\r\ndcj8g71PLBTW6jPxIiMFNA89nz3FU9wIVp+xbMNhSoMMKqIPVPC+m0Rn260CQQDA\r\nI83fWK/mZWUjBM33a68KumRiH238v8XyQxj7+C8i6D8G2GXvkigFAehAkb7LZZd+\r\nKLuGFyPlWv3fVWHf99KpAkBQFKk3MRMl6IGJZUEFQe4l5whm8LkGU4acSqv9B3xt\r\nqROkCrsFrMPqjuuzEmyHoQZ64r2PLJg7FOuyhBnQUOt4\r\n-----END RSA PRIVATE KEY-----\r\n",publicKey:"-----BEGIN PUBLIC KEY-----\r\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDL0EugUiNGMWscLAVM0VoMdhDZ\r\nEJOqdsUMpx9U0YZI7szokJqQNIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMG\r\nTkP3VF29vXBo+dLq5e+8VyAyQ3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGt\r\nvnM+z0MYDdKo80efzwIDAQAB\r\n-----END PUBLIC KEY-----\r\n"};describe("csr",function(){it("should generate a certification request",function(){var r={privateKey:t.privateKeyFromPem(n.privateKey),publicKey:t.publicKeyFromPem(n.publicKey)},i=t.createCertificationRequest();i.publicKey=r.publicKey,i.setSubject([{name:"commonName",value:"example.org"},{name:"countryName",value:"US"},{shortName:"ST",value:"Virginia"},{name:"localityName",value:"Blacksburg"},{name:"organizationName",value:"Test"},{shortName:"OU",value:"Test"}]),i.setAttributes([{name:"challengePassword",value:"password"},{name:"unstructuredName",value:"My company"}]),i.sign(r.privateKey);var s=t.certificationRequestToPem(i);i=t.certificationRequestFromPem(s),e.ok(i.verify())})})}typeof define=="function"?define("test/csr",["forge/pki"],function(t){e(ASSERT,t())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/pki")())}(),function(){function e(e,t,n){describe("aes",function(){it("should encrypt a single block with a 128-bit key",function(){var r=[66051,67438087,134810123,202182159],i=[1122867,1146447479,2291772091,3437096703],s=[],o=t._expandKey(r,!1);t._updateBlock(o,i,s,!1);var u=n.createBuffer();u.putInt32(s[0]),u.putInt32(s[1]),u.putInt32(s[2]),u.putInt32(s[3]),e.equal(u.toHex(),"69c4e0d86a7b0430d8cdb78070b4c55a")}),it("should decrypt a single block with a 128-bit key",function(){var r=[66051,67438087,134810123,202182159],i=[1774510296,1786446896,3637360512,1890895194],s=[],o=t._expandKey(r,!0);t._updateBlock(o,i,s,!0);var u=n.createBuffer();u.putInt32(s[0]),u.putInt32(s[1]),u.putInt32(s[2]),u.putInt32(s[3]),e.equal(u.toHex(),"00112233445566778899aabbccddeeff")}),it("should encrypt a single block with a 192-bit key",function(){var r=[66051,67438087,134810123,202182159,269554195,336926231],i=[1122867,1146447479,2291772091,3437096703],s=[],o=t._expandKey(r,!1);t._updateBlock(o,i,s,!1);var u=n.createBuffer();u.putInt32(s[0]),u.putInt32(s[1]),u.putInt32(s[2]),u.putInt32(s[3]),e.equal(u.toHex(),"dda97ca4864cdfe06eaf70a0ec0d7191")}),it("should decrypt a single block with a 192-bit key",function(){var r=[66051,67438087,134810123,202182159,269554195,336926231],i=[3718872228,2253184992,1856991392,3960304017],s=[],o=t._expandKey(r,!0);t._updateBlock(o,i,s,!0);var u=n.createBuffer();u.putInt32(s[0]),u.putInt32(s[1]),u.putInt32(s[2]),u.putInt32(s[3]),e.equal(u.toHex(),"00112233445566778899aabbccddeeff")}),it("should encrypt a single block with a 256-bit key",function(){var r=[66051,67438087,134810123,202182159,269554195,336926231,404298267,471670303],i=[1122867,1146447479,2291772091,3437096703],s=[],o=t._expandKey(r,!1);t._updateBlock(o,i,s,!1);var u=n.createBuffer();u.putInt32(s[0]),u.putInt32(s[1]),u.putInt32(s[2]),u.putInt32(s[3]),e.equal(u.toHex(),"8ea2b7ca516745bfeafc49904b496089")}),it("should decrypt a single block with a 256-bit key",function(){var r=[66051,67438087,134810123,202182159,269554195,336926231,404298267,471670303],i=[2393028554,1365722559,3942402448,1263100041],s=[],o=t._expandKey(r,!0);t._updateBlock(o,i,s,!0);var u=n.createBuffer();u.putInt32(s[0]),u.putInt32(s[1]),u.putInt32(s[2]),u.putInt32(s[3]),e.equal(u.toHex(),"00112233445566778899aabbccddeeff")}),function(){var r=["06a9214036b8a15b512e03d534120006","c286696d887c9aa0611bbb3e2025a45a","6c3ea0477630ce21a2ce334aa746c2cd","56e47a38c5598974bc46903dba290349"],i=["3dafba429d9eb430b422da802c9fac41","562e17996d093d28ddb3ba695a2e6f58","c782dc4c098c66cbd9cd27d825682c81","8ce82eefbea0da3c44699ed7db51b7d9"],s=["Single block msg","000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","This is a 48-byte message (exactly 3 AES blocks)","a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedf"],o=["e353779c1079aeb82708942dbe77181a","d296cd94c2cccf8a3a863028b5e1dc0a7586602d253cfff91b8266bea6d61ab1","d0a02b3836451753d493665d33f0e8862dea54cdb293abc7506939276772f8d5021c19216bad525c8579695d83ba2684","c30e32ffedc0774e6aff6af0869f71aa0f3af07a9a31a9c684db207eb0ef8e4e35907aa632c3ffdf868bb7b29d3d46ad83ce9f9a102ee99d49a53e87f4c3da55"];for(var u=0;u<r.length;++u)(function(u){var a=n.hexToBytes(r[u]),f=n.hexToBytes(i[u]),l=u&1?n.hexToBytes(s[u]):s[u],c=n.hexToBytes(o[u]);it("should aes-128-cbc encrypt: "+s[u],function(){var r=t.createEncryptionCipher(a,"CBC");r.start(f),r.update(n.createBuffer(l)),r.finish(function(){return!0}),e.equal(r.output.toHex(),o[u])}),it("should aes-128-cbc decrypt: "+o[u],function(){var r=t.createDecryptionCipher(a,"CBC");r.start(f),r.update(n.createBuffer(c)),r.finish(function(){return!0});var i=u&1?r.output.toHex():r.output.bytes();e.equal(i,s[u])})})(u)}(),function(){var r=["00000000000000000000000000000000","2b7e151628aed2a6abf7158809cf4f3c","2b7e151628aed2a6abf7158809cf4f3c","2b7e151628aed2a6abf7158809cf4f3c","2b7e151628aed2a6abf7158809cf4f3c","00000000000000000000000000000000"],i=["80000000000000000000000000000000","000102030405060708090a0b0c0d0e0f","3B3FD92EB72DAD20333449F8E83CFB4A","C8A64537A0B3A93FCDE3CDAD9F1CE58B","26751F67A3CBB140B1808CF187A4F4DF","60f9ff04fac1a25657bf5b36b5efaf75"],s=["00000000000000000000000000000000","6bc1bee22e409f96e93d7e117393172a","ae2d8a571e03ac9c9eb76fac45af8e51","30c81c46a35ce411e5fbc1191a0a52ef","f69f2445df4f9b17ad2b417be66c3710","This is a 48-byte message (exactly 3 AES blocks)"],o=["3ad78e726c1ec02b7ebfe92b23d9ec34","3b3fd92eb72dad20333449f8e83cfb4a","c8a64537a0b3a93fcde3cdad9f1ce58b","26751f67a3cbb140b1808cf187a4f4df","c04b05357c5d1c0eeac4c66f9ff7f2e6","52396a2ba1ba420c5e5b699a814944d8f4e7fbf984a038319fbc0b4ee45cfa6f07b2564beab5b5e92dbd44cb345f49b4"];for(var u=0;u<r.length;++u)(function(u){var a=n.hexToBytes(r[u]),f=n.hexToBytes(i[u]),l=u!==5?n.hexToBytes(s[u]):s[u],c=n.hexToBytes(o[u]);it("should aes-128-cfb encrypt: "+s[u],function(){var r=t.createEncryptionCipher(a,"CFB");r.start(f),r.update(n.createBuffer(l)),r.finish(),e.equal(r.output.toHex(),o[u])}),it("should aes-128-cfb decrypt: "+o[u],function(){var r=t.createDecryptionCipher(a,"CFB");r.start(f),r.update(n.createBuffer(c)),r.finish();var i=u!==5?r.output.toHex():r.output.getBytes();e.equal(i,s[u])})})(u)}(),function(){var r=["00000000000000000000000000000000","00000000000000000000000000000000"],i=["80000000000000000000000000000000","c8ca0d6a35dbeac776e911ee16bea7d3"],s=["00000000000000000000000000000000","This is a 48-byte message (exactly 3 AES blocks)"],o=["3ad78e726c1ec02b7ebfe92b23d9ec34","39c0190727a76b2a90963426f63689cfcdb8a2be8e20c5e877a81a724e3611f62ecc386f2e941b2441c838906002be19"];for(var u=0;u<r.length;++u)(function(u){var a=n.hexToBytes(r[u]),f=n.hexToBytes(i[u]),l=u!==1?n.hexToBytes(s[u]):s[u],c=n.hexToBytes(o[u]);it("should aes-128-ofb encrypt: "+s[u],function(){var r=t.createEncryptionCipher(a,"OFB");r.start(f),r.update(n.createBuffer(l)),r.finish(),e.equal(r.output.toHex(),o[u])}),it("should aes-128-ofb decrypt: "+o[u],function(){var r=t.createDecryptionCipher(a,"OFB");r.start(f),r.update(n.createBuffer(c)),r.finish();var i=u!==1?r.output.toHex():r.output.getBytes();e.equal(i,s[u])})})(u)}(),function(){var r=["2b7e151628aed2a6abf7158809cf4f3c","00000000000000000000000000000000"],i=["f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff","650cdb80ff9fc758342d2bd99ee2abcf"],s=["6bc1bee22e409f96e93d7e117393172a","This is a 48-byte message (exactly 3 AES blocks)"],o=["874d6191b620e3261bef6864990db6ce","5ede11d00e9a76ec1d5e7e811ea3dd1ce09ee941210f825d35718d3282796f1c07c3f1cb424f2b365766ab5229f5b5a4"];for(var u=0;u<r.length;++u)(function(u){var a=n.hexToBytes(r[u]),f=n.hexToBytes(i[u]),l=u!==1?n.hexToBytes(s[u]):s[u],c=n.hexToBytes(o[u]);it("should aes-128-ctr encrypt: "+s[u],function(){var r=t.createEncryptionCipher(a,"CTR");r.start(f),r.update(n.createBuffer(l)),r.finish(),e.equal(r.output.toHex(),o[u])}),it("should aes-128-ctr decrypt: "+o[u],function(){var r=t.createDecryptionCipher(a,"CTR");r.start(f),r.update(n.createBuffer(c)),r.finish();var i=u!==1?r.output.toHex():r.output.getBytes();e.equal(i,s[u])})})(u)}(),function(){var r=["861009ec4d599fab1f40abc76e6f89880cff5833c79c548c99f9045f191cd90b"],i=["d927ad81199aa7dcadfdb4e47b6dc694"],s=["MY-DATA-AND-HERE-IS-MORE-DATA"],o=["80eb666a9fc9e263faf71e87ffc94451d7d8df7cfcf2606470351dd5ac"];for(var u=0;u<r.length;++u)(function(u){var a=n.hexToBytes(r[u]),f=n.hexToBytes(i[u]),l=s[u],c=n.hexToBytes(o[u]);it("should aes-256-cfb encrypt: "+s[u],function(){var r=t.createEncryptionCipher(a,"CFB");r.start(f),r.update(n.createBuffer(l)),r.finish(),e.equal(r.output.toHex(),o[u])}),it("should aes-256-cfb decrypt: "+o[u],function(){var r=t.createDecryptionCipher(a,"CFB");r.start(f),r.update(n.createBuffer(c)),r.finish();var i=r.output.getBytes();e.equal(i,s[u])})})(u)}()})}typeof define=="function"?define("test/aes",["forge/aes","forge/util"],function(t,n){e(ASSERT,t(),n())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/aes")(),require("../../js/util")())}(),function(){function e(e,t,n){describe("rc2",function(){it("should expand a 128-bit key",function(){var r=n.hexToBytes("88bca90e90875a7f0f79c384627bafb2"),i="71ab26462f0b9333609d4476e48ab72438c2194b70a47085d84b6af1dc72119023b94fe80aee2b6b45f27f923d9be1570da3ce8b16ad7f78db166ffbc28a836a4392cf0b748085dae4b69bdc2a4679cdfc09d84317016987e0c5b765c91dc612b1f44d7921b3e2c46447508bd2ac02e119e0f42a89c719675da320cf3e8958cd";e.equal(t.expandKey(r).toHex(),i)}),it("should expand a 40-bit key",function(){var r=n.hexToBytes("88bca90e90"),i="af136d2243b94a0878d7a604f8d6d9fd64a698fd6ebc613e641f0d1612055ef6cb55966db8f32bfd9246dae99880be8a91433adf54ea546d9daad62db7a55f6c7790aa87ba67de0e9ea9128dfc7ccdddd7c47c33d2bb7f823729977f083b5dc1f5bb09000b98e12cdaaf22f80dcc88c37d2c2fd80402f8a30a9e41d356669471";e.equal(t.expandKey(r,40).toHex(),i)}),it("should rc2-ecb encrypt zeros",function(){var r=n.hexToBytes("88bca90e90875a7f0f79c384627bafb2"),i=(new n.createBuffer).fillWithByte(0,8),s=t.startEncrypting(r,null,null);s.update(i),s.finish(),e.equal(s.output.toHex(),"2269552ab0f85ca6e35b3b2ce4e02191")}),it("should rc2-ecb encrypt: vegan",function(){var r=n.hexToBytes("88bca90e90875a7f0f79c384627bafb2"),i=new n.createBuffer("vegan"),s=t.startEncrypting(r,null,null);s.update(i),s.finish(),e.equal(s.output.toHex(),"2194adaf4d517e3a")}),it("should rc2-ecb decrypt: 2194adaf4d517e3a",function(){var r=n.hexToBytes("88bca90e90875a7f0f79c384627bafb2"),i=new n.createBuffer(n.hexToBytes("2194adaf4d517e3a")),s=t.startDecrypting(r,null,null);s.update(i),s.finish(),e.equal(s.output.getBytes(),"vegan")}),it("should rc2-cbc encrypt: revolution",function(){var r=n.hexToBytes("88bca90e90875a7f0f79c384627bafb2"),i=new n.createBuffer(n.hexToBytes("0123456789abcdef")),s=new n.createBuffer("revolution"),o=t.startEncrypting(r,i,null);o.update(s),o.finish(),e.equal(o.output.toHex(),"50cfd16e0fd7f20b17a622eb2a469b7e")}),it("should rc2-cbc decrypt: 50cfd16e0fd7f20b17a622eb2a469b7e",function(){var r=n.hexToBytes("88bca90e90875a7f0f79c384627bafb2"),i=new n.createBuffer(n.hexToBytes("0123456789abcdef")),s=new n.createBuffer(n.hexToBytes("50cfd16e0fd7f20b17a622eb2a469b7e")),o=t.startDecrypting(r,i,null);o.update(s),o.finish(),e.equal(o.output,"revolution")})})}typeof define=="function"?define("test/rc2",["forge/rc2","forge/util"],function(t,n){e(ASSERT,t(),n())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/rc2")(),require("../../js/util")())}(),function(){function e(e,t,n){describe("des",function(){it("should des-ecb encrypt: foobar",function(){var r=new n.createBuffer(n.hexToBytes("a1c06b381adf3651")),i=t.startEncrypting(r,null,null);i.update(n.createBuffer("foobar")),i.finish(),e.equal(i.output.toHex(),"b705ffcf3dff06b3")}),it("should des-ecb decrypt: b705ffcf3dff06b3",function(){var r=new n.createBuffer(n.hexToBytes("a1c06b381adf3651")),i=t.startDecrypting(r,null,null);i.update(n.createBuffer(n.hexToBytes("b705ffcf3dff06b3"))),i.finish(),e.equal(i.output.getBytes(),"foobar")}),it("should des-cbc encrypt: foobar",function(){var r=new n.createBuffer(n.hexToBytes("a1c06b381adf3651")),i=new n.createBuffer(n.hexToBytes("818bcf76efc59662")),s=t.startEncrypting(r,i,null);s.update(n.createBuffer("foobar")),s.finish(),e.equal(s.output.toHex(),"3261e5839a990454")}),it("should des-cbc decrypt: 3261e5839a990454",function(){var r=new n.createBuffer(n.hexToBytes("a1c06b381adf3651")),i=new n.createBuffer(n.hexToBytes("818bcf76efc59662")),s=t.startDecrypting(r,i,null);s.update(n.createBuffer(n.hexToBytes("3261e5839a990454"))),s.finish(),e.equal(s.output.getBytes(),"foobar")}),it("should 3des-ecb encrypt: foobar",function(){var r=new n.createBuffer(n.hexToBytes("a1c06b381adf36517e84575552777779da5e3d9f994b05b5")),i=t.startEncrypting(r,null,null);i.update(n.createBuffer("foobar")),i.finish(),e.equal(i.output.toHex(),"fce8b1ee8c6440d1")}),it("should 3des-ecb decrypt: fce8b1ee8c6440d1",function(){var r=new n.createBuffer(n.hexToBytes("a1c06b381adf36517e84575552777779da5e3d9f994b05b5")),i=t.startDecrypting(r,null,null);i.update(n.createBuffer(n.hexToBytes("fce8b1ee8c6440d1"))),i.finish(),e.equal(i.output.getBytes(),"foobar")}),it('should 3des-cbc encrypt "foobar", restart, and encrypt "foobar,,"',function(){var r=new n.createBuffer(n.hexToBytes("a1c06b381adf36517e84575552777779da5e3d9f994b05b5")),i=new n.createBuffer(n.hexToBytes("818bcf76efc59662")),s=t.startEncrypting(r,i.copy(),null);s.update(n.createBuffer("foobar")),s.finish(),e.equal(s.output.toHex(),"209225f7687ca0b2"),s.start(i.copy()),s.update(n.createBuffer("foobar,,")),s.finish(),e.equal(s.output.toHex(),"57156174c48dfc37293831bf192a6742")}),it('should 3des-cbc decrypt "209225f7687ca0b2", restart, and decrypt "57156174c48dfc37293831bf192a6742,,"',function(){var r=new n.createBuffer(n.hexToBytes("a1c06b381adf36517e84575552777779da5e3d9f994b05b5")),i=new n.createBuffer(n.hexToBytes("818bcf76efc59662")),s=t.startDecrypting(r,i.copy(),null);s.update(n.createBuffer(n.hexToBytes("209225f7687ca0b2"))),s.finish(),e.equal(s.output.getBytes(),"foobar"),s.start(i.copy()),s.update(n.createBuffer(n.hexToBytes("57156174c48dfc37293831bf192a6742"))),s.finish(),e.equal(s.output.getBytes(),"foobar,,")})})}typeof define=="function"?define("test/des",["forge/des","forge/util"],function(t,n){e(ASSERT,t(),n())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/des")(),require("../../js/util")())}(),function(){function e(e){var t=e.asn1,n=e.pkcs7=e.pkcs7||{};n.messageFromPem=function(r){var i=e.pem.decode(r)[0];if(i.type!=="PKCS7")throw{message:'Could not convert PKCS#7 message from PEM; PEM header type is not "PKCS#7".',headerType:i.type};if(i.procType&&i.procType.type==="ENCRYPTED")throw{message:"Could not convert PKCS#7 message from PEM; PEM is encrypted."};var s=t.fromDer(i.body);return n.messageFromAsn1(s)},n.messageToPem=function(n,r){var i={type:"PKCS7",body:t.toDer(n.toAsn1()).getBytes()};return e.pem.encode(i,{maxline:r})},n.messageFromAsn1=function(r){var i={},s=[];if(!t.validate(r,n.asn1.contentInfoValidator,i,s))throw{message:"Cannot read PKCS#7 message. ASN.1 object is not an PKCS#7 ContentInfo.",errors:s};var o=t.derToOid(i.contentType),u;switch(o){case e.pki.oids.envelopedData:u=n.createEnvelopedData();break;case e.pki.oids.encryptedData:u=n.createEncryptedData();break;case e.pki.oids.signedData:u=n.createSignedData();break;default:throw{message:"Cannot read PKCS#7 message. ContentType with OID "+o+" is not (yet) supported."}}return u.fromAsn1(i.content.value[0]),u};var r=function(r){var i={},s=[];if(!t.validate(r,n.asn1.recipientInfoValidator,i,s))throw{message:"Cannot read PKCS#7 message. ASN.1 object is not an PKCS#7 EnvelopedData.",errors:s};return{version:i.version.charCodeAt(0),issuer:e.pki.RDNAttributesAsArray(i.issuer),serialNumber:e.util.createBuffer(i.serial).toHex(),encContent:{algorithm:t.derToOid(i.encAlgorithm),parameter:i.encParameter.value,content:i.encKey}}},i=function(n){return t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,String.fromCharCode(n.version)),t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[e.pki.distinguishedNameToAsn1({attributes:n.issuer}),t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,e.util.hexToBytes(n.serialNumber))]),t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.encContent.algorithm).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.NULL,!1,"")]),t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,n.encContent.content)])},s=function(e){var t=[];for(var n=0;n<e.length;n++)t.push(r(e[n]));return t},o=function(e){var t=[];for(var n=0;n<e.length;n++)t.push(i(e[n]));return t},u=function(n){return[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(e.pki.oids.data).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(n.algorithm).getBytes()),t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,n.parameter.getBytes())]),t.create(t.Class.CONTEXT_SPECIFIC,0,!0,[t.create(t.Class.UNIVERSAL,t.Type.OCTETSTRING,!1,n.content.getBytes())])]},a=function(n,r,i){var s={},o=[];if(!t.validate(r,i,s,o))throw{message:"Cannot read PKCS#7 message. ASN.1 object is not an PKCS#7 EnvelopedData.",errors:o};var u=t.derToOid(s.contentType);if(u!==e.pki.oids.data)throw{message:"Unsupported PKCS#7 message. Only contentType Data supported within EnvelopedData."};if(s.encContent){var a="";if(e.util.isArray(s.encContent))for(var f=0;f<s.encContent.length;++f){if(s.encContent[f].type!==t.Type.OCTETSTRING)throw{message:"Malformed PKCS#7 message, expecting encrypted content constructed of only OCTET STRING objects."};a+=s.encContent[f].value}else a=s.encContent;n.encContent={algorithm:t.derToOid(s.encAlgorithm),parameter:e.util.createBuffer(s.encParameter.value),content:e.util.createBuffer(a)}}if(s.content){var a="";if(e.util.isArray(s.content))for(var f=0;f<s.content.length;++f){if(s.content[f].type!==t.Type.OCTETSTRING)throw{message:"Malformed PKCS#7 message, expecting content constructed of only OCTET STRING objects."};a+=s.content[f].value}else a=s.content;n.content=e.util.createBuffer(a)}return n.version=s.version.charCodeAt(0),n.rawCapture=s,s},f=function(t){if(t.encContent.key===undefined)throw{message:"Symmetric key not available."};if(t.content===undefined){var n;switch(t.encContent.algorithm){case e.pki.oids["aes128-CBC"]:case e.pki.oids["aes192-CBC"]:case e.pki.oids["aes256-CBC"]:n=e.aes.createDecryptionCipher(t.encContent.key);break;case e.pki.oids["des-EDE3-CBC"]:n=e.des.createDecryptionCipher(t.encContent.key);break;default:throw{message:"Unsupported symmetric cipher, OID "+t.encContent.algorithm}}n.start(t.encContent.parameter),n.update(t.encContent.content);if(!n.finish())throw{message:"Symmetric decryption failed."};t.content=n.output}};n.createSignedData=function(){var t=null;return t={type:e.pki.oids.signedData,version:1,fromAsn1:function(e){a(t,e,n.asn1.signedDataValidator)}},t},n.createEncryptedData=function(){var t=null;return t={type:e.pki.oids.encryptedData,version:0,encContent:{algorithm:e.pki.oids["aes256-CBC"]},fromAsn1:function(e){a(t,e,n.asn1.encryptedDataValidator)},decrypt:function(e){e!==undefined&&(t.encContent.key=e),f(t)}},t},n.createEnvelopedData=function(){var r=null;return r={type:e.pki.oids.envelopedData,version:0,recipients:[],encContent:{algorithm:e.pki.oids["aes256-CBC"]},fromAsn1:function(e){var t=a(r,e,n.asn1.envelopedDataValidator);r.recipients=s(t.recipientInfos.value)},toAsn1:function(){return t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.OID,!1,t.oidToDer(r.type).getBytes()),t.create(t.Class.CONTEXT_SPECIFIC,0,!0,[t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,[t.create(t.Class.UNIVERSAL,t.Type.INTEGER,!1,String.fromCharCode(r.version)),t.create(t.Class.UNIVERSAL,t.Type.SET,!0,o(r.recipients)),t.create(t.Class.UNIVERSAL,t.Type.SEQUENCE,!0,u(r.encContent))])])])},findRecipient:function(e){var t=e.issuer.attributes;for(var n=0;n<r.recipients.length;++n){var i=r.recipients[n],s=i.issuer;if(i.serialNumber!==e.serialNumber)continue;if(s.length!==t.length)continue;var o=!0;for(var u=0;u<t.length;++u)if(s[u].type!==t[u].type||s[u].value!==t[u].value){o=!1;break}if(o)return i}return null},decrypt:function(t,n){if(r.encContent.key===undefined&&t!==undefined&&n!==undefined)switch(t.encContent.algorithm){case e.pki.oids.rsaEncryption:var i=n.decrypt(t.encContent.content);r.encContent.key=e.util.createBuffer(i);break;default:throw{message:"Unsupported asymmetric cipher, OID "+t.encContent.algorithm}}f(r)},addRecipient:function(t){r.recipients.push({version:0,issuer:t.subject.attributes,serialNumber:t.serialNumber,encContent:{algorithm:e.pki.oids.rsaEncryption,key:t.publicKey}})},encrypt:function(t,n){if(r.encContent.content===undefined){n=n||r.encContent.algorithm,t=t||r.encContent.key;var i,s,o;switch(n){case e.pki.oids["aes128-CBC"]:i=16,s=16,o=e.aes.createEncryptionCipher;break;case e.pki.oids["aes192-CBC"]:i=24,s=16,o=e.aes.createEncryptionCipher;break;case e.pki.oids["aes256-CBC"]:i=32,s=16,o=e.aes.createEncryptionCipher;break;case e.pki.oids["des-EDE3-CBC"]:i=24,s=8,o=e.des.createEncryptionCipher;break;default:throw{message:"Unsupported symmetric cipher, OID "+n}}if(t===undefined)t=e.util.createBuffer(e.random.getBytes(i));else if(t.length()!=i)throw{message:"Symmetric key has wrong length, got "+t.length()+" bytes, expected "+i};r.encContent.algorithm=n,r.encContent.key=t,r.encContent.parameter=e.util.createBuffer(e.random.getBytes(s));var u=o(t);u.start(r.encContent.parameter.copy()),u.update(r.content);if(!u.finish())throw{message:"Symmetric encryption failed."};r.encContent.content=u.output}for(var a=0;a<r.recipients.length;a++){var f=r.recipients[a];if(f.encContent.content!==undefined)continue;switch(f.encContent.algorithm){case e.pki.oids.rsaEncryption:f.encContent.content=f.encContent.key.encrypt(r.encContent.key.data);break;default:throw{message:"Unsupported asymmetric cipher, OID "+f.encContent.algorithm}}}}},r}}var t="pkcs7";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/pkcs7",["require","module","./aes","./asn1","./des","./pem","./pkcs7asn1","./pki","./random","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e,t,n,r,i,s){var o={p7:"-----BEGIN PKCS7-----\r\nMIICTgYJKoZIhvcNAQcDoIICPzCCAjsCAQAxggHGMIIBwgIBADCBqTCBmzELMAkG\r\nA1UEBhMCREUxEjAQBgNVBAgMCUZyYW5jb25pYTEQMA4GA1UEBwwHQW5zYmFjaDEV\r\nMBMGA1UECgwMU3RlZmFuIFNpZWdsMRIwEAYDVQQLDAlHZWllcmxlaW4xFjAUBgNV\r\nBAMMDUdlaWVybGVpbiBERVYxIzAhBgkqhkiG9w0BCQEWFHN0ZXNpZUBicm9rZW5w\r\naXBlLmRlAgkA1FQcQNg14vMwDQYJKoZIhvcNAQEBBQAEggEAJhWQz5SniCd1w3A8\r\nuKVZEfc8Tp21I7FMfFqou+UOVsZCq7kcEa9uv2DIj3o7zD8wbLK1fuyFi4SJxTwx\r\nkR0a6V4bbonIpXPPJ1f615dc4LydAi2tv5w14LJ1Js5XCgGVnkAmQHDaW3EHXB7X\r\nT4w9PR3+tcS/5YAnWaM6Es38zCKHd7TnHpuakplIkwSK9rBFAyA1g/IyTPI+ktrE\r\nEHcVuJcz/7eTlF6wJEa2HL8F1TVWuL0p/0GsJP/8y0MYGdCdtr+TIVo//3YGhoBl\r\nN4tnheFT/jRAzfCZtflDdgAukW24CekrJ1sG2M42p5cKQ5rGFQtzNy/n8EjtUutO\r\nHD5YITBsBgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBBmlpfy3WrYj3uWW7+xNEiH\r\ngEAm2mfSF5xFPLEqqFkvKTM4w8PfhnF0ehmfQNApvoWQRQanNWLCT+Q9GHx6DCFj\r\nTUHl+53x88BrCl1E7FhYPs92\r\n-----END PKCS7-----\r\n",certificate:"-----BEGIN CERTIFICATE-----\r\nMIIDtDCCApwCCQDUVBxA2DXi8zANBgkqhkiG9w0BAQUFADCBmzELMAkGA1UEBhMC\r\nREUxEjAQBgNVBAgMCUZyYW5jb25pYTEQMA4GA1UEBwwHQW5zYmFjaDEVMBMGA1UE\r\nCgwMU3RlZmFuIFNpZWdsMRIwEAYDVQQLDAlHZWllcmxlaW4xFjAUBgNVBAMMDUdl\r\naWVybGVpbiBERVYxIzAhBgkqhkiG9w0BCQEWFHN0ZXNpZUBicm9rZW5waXBlLmRl\r\nMB4XDTEyMDMxODIyNTc0M1oXDTEzMDMxODIyNTc0M1owgZsxCzAJBgNVBAYTAkRF\r\nMRIwEAYDVQQIDAlGcmFuY29uaWExEDAOBgNVBAcMB0Fuc2JhY2gxFTATBgNVBAoM\r\nDFN0ZWZhbiBTaWVnbDESMBAGA1UECwwJR2VpZXJsZWluMRYwFAYDVQQDDA1HZWll\r\ncmxlaW4gREVWMSMwIQYJKoZIhvcNAQkBFhRzdGVzaWVAYnJva2VucGlwZS5kZTCC\r\nASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMsAbQ4fWevHqP1K1y/ewpMS\r\n3vYovBto7IsKBq0v3NmC2kPf3NhyaSKfjOOS5uAPONLffLck+iGdOLLFia6OSpM6\r\n0tyQIV9lHoRh7fOEYORab0Z+aBUZcEGT9yotBOraX1YbKc5f9XO+80eG4XYvb5ua\r\n1NHrxWqe4w2p3zGJCKO+wHpvGkbKz0nfu36jwWz5aihfHi9hp/xs8mfH86mIKiD7\r\nf2X2KeZ1PK9RvppA0X3lLb2VLOqMt+FHWicyZ/wjhQZ4oW55ln2yYJUQ+adlgaYn\r\nPrtnsxmbTxM+99oF0F2/HmGrNs8nLZSva1Vy+hmjmWz6/O8ZxhiIj7oBRqYcAocC\r\nAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAvfvtu31GFBO5+mFjPAoR4BlzKq/H3EPO\r\nqS8cm/TjHgDRALwSnwKYCFs/bXqE4iOTD6otV4TusX3EPbqL2vzZQEcZn6paU/oZ\r\nZVXwQqMqY5tf2teQiNxqxNmSIEPRHOr2QVBVIx2YF4Po89KGUqJ9u/3/10lDqRwp\r\nsReijr5UKv5aygEcnwcW8+Ne4rTx934UDsutKG20dr5trZfWQRVS9fS9CFwJehEX\r\nHAMUc/0++80NhfQthmWZWlWM1R3dr4TrIPtWdn5z0MtGeDvqBk7HjGrhcVS6kAsy\r\nZ9y/lfLPjBuxlQAHztEJCWgI4TW3/RLhgfg2gI1noM2n84Cdmisfkg==\r\n-----END CERTIFICATE-----\r\n",privateKey:"-----BEGIN RSA PRIVATE KEY-----\r\nMIIEowIBAAKCAQEAywBtDh9Z68eo/UrXL97CkxLe9ii8G2jsiwoGrS/c2YLaQ9/c\r\n2HJpIp+M45Lm4A840t98tyT6IZ04ssWJro5KkzrS3JAhX2UehGHt84Rg5FpvRn5o\r\nFRlwQZP3Ki0E6tpfVhspzl/1c77zR4bhdi9vm5rU0evFap7jDanfMYkIo77Aem8a\r\nRsrPSd+7fqPBbPlqKF8eL2Gn/GzyZ8fzqYgqIPt/ZfYp5nU8r1G+mkDRfeUtvZUs\r\n6oy34UdaJzJn/COFBnihbnmWfbJglRD5p2WBpic+u2ezGZtPEz732gXQXb8eYas2\r\nzyctlK9rVXL6GaOZbPr87xnGGIiPugFGphwChwIDAQABAoIBAAjMA+3QvfzRsikH\r\nzTtt09C7yJ2yNjSZ32ZHEPMAV/m1CfBXCyL2EkhF0b0q6IZdIoFA3g6xs4UxYvuc\r\nQ9Mkp2ap7elQ9aFEqIXkGIOtAOXkZV4QrEH90DeHSfax7LygqfD5TF59Gg3iAHjh\r\nB3Qvqg58LyzJosx0BjLZYaqr3Yv67GkqyflpF/roPGdClHpahAi5PBkHiNhNTAUU\r\nLJRGvMegXGZkUKgGMAiGCk0N96OZwrinMKO6YKGdtgwVWC2wbJY0trElaiwXozSt\r\nNmP6KTQp94C7rcVO6v1lZiOfhBe5Kc8QHUU+GYydgdjqm6Rdow/yLHOALAVtXSeb\r\nU+tPfcECgYEA6Qi+qF+gtPincEDBxRtoKwAlRkALt8kly8bYiGcUmd116k/5bmPw\r\nd0tBUOQbqRa1obYC88goOVzp9LInAcBSSrexhVaPAF4nrkwYXMOq+76MiH17WUfQ\r\nMgVM2IB48PBjNk1s3Crj6j1cxxkctqmCnVaI9HlU2PPZ3xjaklfv/NsCgYEA3wH8\r\nmehUhiAp7vuhd+hfomFw74cqgHC9v0saiYGckpMafh9MJGc4U5GrN1kYeb/CFkSx\r\n1hOytD3YBKoaKKoYagaMQcjxf6HnEF0f/5OiQkUQpWmgC9lNnE4XTWjnwqaTS5L9\r\nD+H50SiI3VjHymGXTRJeKpAIwV74AxxrnVofqsUCgYAwmL1B2adm9g/c7fQ6yatg\r\nhEhBrSuEaTMzmsUfNPfr2m4zrffjWH4WMqBtYRSPn4fDMHTPJ+eThtfXSqutxtCi\r\nekpP9ywdNIVr6LyP49Ita6Bc+mYVyU8Wj1pmL+yIumjGM0FHbL5Y4/EMKCV/xjvR\r\n2fD3orHaCIhf6QvzxtjqTwKBgFm6UemXKlMhI94tTsWRMNGEBU3LA9XUBvSuAkpr\r\nZRUwrQssCpXnFinBxbMqXQe3mR8emrM5D8En1P/jdU0BS3t1kP9zG4AwI2lZHuPV\r\nggbKBS2Y9zVtRKXsYcHawM13+nIA/WNjmAGJHrB45UJPy/HNvye+9lbfoEiYKdCR\r\nD4bFAoGBAIm9jcZkIwLa9kLAWH995YYYSGRY4KC29XZr2io2mog+BAjhFt1sqebt\r\nR8sRHNiIP2mcUECMOcaS+tcayi+8KTHWxIEed9qDmFu6XBbePfe/L6yxPSagcixH\r\nBK0KuK/fgTPvZCmIs8hUIC+AxhXKnqn4fIWoO54xLsALc0gEjs2d\r\n-----END RSA PRIVATE KEY-----\r\n",encryptedData:"-----BEGIN PKCS7-----\r\nMIGHBgkqhkiG9w0BBwagejB4AgEAMHMGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQI\r\nupMFou5X3DWAUAqObuHSlewM0ZtHzWk9MAmtYb7MSb//OBMKVfLCdbmrS5BpKm9J\r\ngzwiDR5Od7xgfkqasLS2lOdKAvJ5jZjjTpAyrjBKpShqK9gtXDuO0zH+\r\n-----END PKCS7-----\r\n",p7IndefiniteLength:"-----BEGIN PKCS7-----\r\nMIAGCSqGSIb3DQEHA6CAMIACAQAxggHGMIIBwgIBADCBqTCBmzELMAkGA1UEBhMC\r\nREUxEjAQBgNVBAgMCUZyYW5jb25pYTEQMA4GA1UEBwwHQW5zYmFjaDEVMBMGA1UE\r\nCgwMU3RlZmFuIFNpZWdsMRIwEAYDVQQLDAlHZWllcmxlaW4xFjAUBgNVBAMMDUdl\r\naWVybGVpbiBERVYxIzAhBgkqhkiG9w0BCQEWFHN0ZXNpZUBicm9rZW5waXBlLmRl\r\nAgkA1FQcQNg14vMwDQYJKoZIhvcNAQEBBQAEggEAlWCH+E25c4jfff+m0eAxxMmE\r\nWWaftdsk4ZpAVAr7HsvxJ35bj1mhwTh7rBTg929JBKt6ZaQ4I800jCNxD2O40V6z\r\nlB7JNRqzgBwfeuU2nV6FB7v1984NBi1jQx6EfxOcusE6RL/63HqJdFbmq3Tl55gF\r\ndm3JdjmHbCXqwPhuwOXU4yhkpV1RJcrYhPLe3OrLAH7ZfoE0nPJPOX9HPTZ6ReES\r\nNToS7I9D9k7rCa8fAP7pgjO96GJGBtCHG1VXB9NX4w+xRDbgVPOeHXqqxwZhqpW2\r\nusBU4+B+MnFLjquOPoySXFfdJFwTP61TPClUdyIne5FFP6EYf98mdtnkjxHo1TCA\r\nBgkqhkiG9w0BBwEwFAYIKoZIhvcNAwcECFNtpqBmU3M9oIAESM+yyQLkreETS0Kc\r\no01yl6dqqNBczH5FNTK88ypz38/jzjo47+DURlvGzjHJibiDsCz9KyiVmgbRrtvH\r\n08rfnMbrU+grCkkx9wQI1GnLrYhr87oAAAAAAAAAAAAA\r\n-----END PKCS7-----\r\n",p73des:"-----BEGIN PKCS7-----\r\nMIICTQYJKoZIhvcNAQcDoIICPjCCAjoCAQAxggHGMIIBwgIBADCBqTCBmzELMAkG\r\nA1UEBhMCREUxEjAQBgNVBAgMCUZyYW5jb25pYTEQMA4GA1UEBwwHQW5zYmFjaDEV\r\nMBMGA1UECgwMU3RlZmFuIFNpZWdsMRIwEAYDVQQLDAlHZWllcmxlaW4xFjAUBgNV\r\nBAMMDUdlaWVybGVpbiBERVYxIzAhBgkqhkiG9w0BCQEWFHN0ZXNpZUBicm9rZW5w\r\naXBlLmRlAgkA1FQcQNg14vMwDQYJKoZIhvcNAQEBBQAEggEAS6K+sQvdKcK6YafJ\r\nmaDPjBzyjf5jtBgVrFgBXTCRIp/Z2zAXa70skfxhbwTgmilYTacA7jPGRrnLmvBc\r\nBjhyCKM3dRUyYgh1K1ka0w1prvLmRk6Onf5df1ZQn3AJMIujJZcCOhbV1ByLInve\r\nxn02KNHstGmdHM/JGyPCp+iYGprhUozVSpNCKS+R33EbsT0sAxamfqdAblT9+5Qj\r\n4CABvW11a1clPV7STwBbAKbZaLs8mDeoWP0yHvBtJ7qzZdSgJJA2oU7SDv4icwEe\r\nAhccbe2HWkLRw8G5YG9XcWx5PnQQhhnXMxkLoSMIYxItyL/cRORbpDohd+otAo66\r\nWLH1ODBrBgkqhkiG9w0BBwEwFAYIKoZIhvcNAwcECD5EWJMv1fd7gEj1w3WM1KsM\r\nL8GDk9JoqA8t9v3oXCT0nAMXoNpHZMnv+0UHHVljlSXBTQxwUP5VMY/ddquJ5O3N\r\nrDEqqJuHB+KPIsW1kxrdplU=\r\n-----END PKCS7-----\r\n"};describe("pkcs7",function(){it("should import message from PEM",function(){var r=t.messageFromPem(o.p7);e.equal(r.type,n.oids.envelopedData),e.equal(r.version,0),e.equal(r.recipients.length,1),e.equal(r.recipients[0].version,0),e.equal(r.recipients[0].serialNumber,"00d4541c40d835e2f3"),e.equal(r.recipients[0].issuer.length,7),e.equal(r.recipients[0].issuer[0].type,"2.5.4.6"),e.equal(r.recipients[0].issuer[0].value,"DE"),e.equal(r.recipients[0].issuer[1].type,"2.5.4.8"),e.equal(r.recipients[0].issuer[1].value,"Franconia"),e.equal(r.recipients[0].issuer[2].type,"2.5.4.7"),e.equal(r.recipients[0].issuer[2].value,"Ansbach"),e.equal(r.recipients[0].issuer[3].type,"2.5.4.10"),e.equal(r.recipients[0].issuer[3].value,"Stefan Siegl"),e.equal(r.recipients[0].issuer[4].type,"2.5.4.11"),e.equal(r.recipients[0].issuer[4].value,"Geierlein"),e.equal(r.recipients[0].issuer[5].type,"2.5.4.3"),e.equal(r.recipients[0].issuer[5].value,"Geierlein DEV"),e.equal(r.recipients[0].issuer[6].type,"1.2.840.113549.1.9.1"),e.equal(r.recipients[0].issuer[6].value,"stesie@brokenpipe.de"),e.equal(r.recipients[0].encContent.algorithm,n.oids.rsaEncryption),e.equal(r.recipients[0].encContent.content.length,256),e.equal(r.encContent.algorithm,n.oids["aes256-CBC"]),e.equal(r.encContent.parameter.data.length,16)}),it("should import indefinite length message from PEM",function(){e.doesNotThrow(function(){var r=t.messageFromPem(o.p7IndefiniteLength);e.equal(r.type,n.oids.envelopedData),e.equal(r.encContent.parameter.toHex(),"536da6a06653733d"),e.equal(r.encContent.content.length(),80)})}),it("should find recipient by serial number",function(){var r=t.messageFromPem(o.p7),i=n.certificateFromPem(o.certificate),s=r.findRecipient(i);e.equal(s.serialNumber,"00d4541c40d835e2f3"),i.serialNumber="1234567890abcdef42",s=r.findRecipient(i),e.equal(s,null)}),it("should aes-decrypt message",function(){var r=t.messageFromPem(o.p7),i=n.privateKeyFromPem(o.privateKey);r.decrypt(r.recipients[0],i),e.equal(r.encContent.key.data.length,32),e.equal(r.content,"Today is Boomtime, the 9th day of Discord in the YOLD 3178\r\n")}),it("should 3des-decrypt message",function(){var r=t.messageFromPem(o.p73des),i=n.privateKeyFromPem(o.privateKey);r.decrypt(r.recipients[0],i),e.equal(r.encContent.key.data.length,24),e.equal(r.content,"Today is Prickle-Prickle, the 16th day of Discord in the YOLD 3178\r\n")}),it("should add a recipient",function(){var r=t.createEnvelopedData();e.equal(r.recipients.length,0);var i=n.certificateFromPem(o.certificate);r.addRecipient(i),e.equal(r.recipients.length,1),e.deepEqual(r.recipients[0].serialNumber,i.serialNumber),e.deepEqual(r.recipients[0].issuer,i.subject.attributes),e.deepEqual(r.recipients[0].encContent.key,i.publicKey)}),it("should aes-encrypt a message",function(){var i=t.createEnvelopedData(),u=n.certificateFromPem(o.certificate),a=n.privateKeyFromPem(o.privateKey);i.addRecipient(u),i.content=s.createBuffer("Just a little test"),e.equal(i.encContent.algorithm,n.oids["aes256-CBC"]),i.encrypt(),e.equal(i.encContent.key.data.length,32),e.equal(i.encContent.parameter.data.length,16),e.equal(i.encContent.content.data.length,32),e.equal(i.recipients[0].encContent.content.length,256),i.encContent.key.read=0,i.encContent.parameter.read=0;var f=a.decrypt(i.recipients[0].encContent.content);e.equal(f,i.encContent.key.data);var l=r.createDecryptionCipher(f);l.start(i.encContent.parameter),l.update(i.encContent.content),l.finish(),e.equal(l.output,"Just a little test")}),it("should 3des-ede-encrypt a message",function(){var r=t.createEnvelopedData(),u=n.certificateFromPem(o.certificate),a=n.privateKeyFromPem(o.privateKey);r.addRecipient(u),r.content=s.createBuffer("Just a little test"),r.encContent.algorithm=n.oids["des-EDE3-CBC"],r.encrypt(),e.equal(r.encContent.key.data.length,24),e.equal(r.encContent.parameter.data.length,8),e.equal(r.encContent.content.data.length,24),e.equal(r.recipients[0].encContent.content.length,256),r.encContent.key.read=0,r.encContent.parameter.read=0;var f=a.decrypt(r.recipients[0].encContent.content);e.equal(f,r.encContent.key.data);var l=i.createDecryptionCipher(f);l.start(r.encContent.parameter),l.update(r.encContent.content),l.finish(),e.equal(l.output,"Just a little test")}),it("should export message to PEM",function(){var r=t.createEnvelopedData();r.addRecipient(n.certificateFromPem(o.certificate)),r.content=s.createBuffer("Just a little test"),r.encrypt();var i=t.messageToPem(r);r=t.messageFromPem(i),r.decrypt(r.recipients[0],n.privateKeyFromPem(o.privateKey)),e.equal(r.content,"Just a little test")}),it("should decrypt encrypted data from PEM",function(){var r="1f8b08000000000000000b2e494d4bcc5308ce4c4dcfd15130b0b430d4b7343732b03437d05170cc2b4e4a4cced051b034343532d25170492d2d294ecec849cc4b0100bf52f02437000000",i="b96e4a4c0a3555d31e1b295647cc5cfe74081918cb7f797b";i=s.createBuffer(s.hexToBytes(i)),e.doesNotThrow(function(){var u=t.messageFromPem(o.encryptedData);e.equal(u.type,n.oids.encryptedData),e.equal(u.encContent.algorithm,n.oids["des-EDE3-CBC"]),e.equal(u.encContent.parameter.toHex(),"ba9305a2ee57dc35"),e.equal(u.encContent.content.length(),80),u.decrypt(i),e.equal(u.content.getBytes(),s.hexToBytes(r))})})})}typeof define=="function"?define("test/pkcs7",["forge/pkcs7","forge/pki","forge/aes","forge/des","forge/util"],function(t,n,r,i,s){e(ASSERT,t(),n(),r(),i(),s())}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/pkcs7")(),require("../../js/pki")(),require("../../js/aes")(),require("../../js/des")(),require("../../js/util")())}(),function(){function e(e){var t=function(t,n,r,i){var s=e.util.createBuffer(),o=t.length>>1,u=o+(t.length&1),a=t.substr(0,u),f=t.substr(o,u),l=e.util.createBuffer(),c=e.hmac.create();r=n+r;var h=Math.ceil(i/16),p=Math.ceil(i/20);c.start("MD5",a);var d=e.util.createBuffer();l.putBytes(r);for(var v=0;v<h;++v)c.start(null,null),c.update(l.getBytes()),l.putBuffer(c.digest()),c.start(null,null),c.update(l.bytes()+r),d.putBuffer(c.digest());c.start("SHA1",f);var m=e.util.createBuffer();l.clear(),l.putBytes(r);for(var v=0;v<p;++v)c.start(null,null),c.update(l.getBytes()),l.putBuffer(c.digest()),c.start(null,null),c.update(l.bytes()+r),m.putBuffer(c.digest());return s.putBytes(e.util.xorBytes(d.getBytes(),m.getBytes(),i)),s},n=function(e,t,n,r){},r=function(t,n,r){var i=e.hmac.create();i.start("SHA1",t);var s=e.util.createBuffer();return s.putInt32(n[0]),s.putInt32(n[1]),s.putByte(r.type),s.putByte(r.version.major),s.putByte(r.version.minor),s.putInt16(r.length),s.putBytes(r.fragment.bytes()),i.update(s.getBytes()),i.digest().getBytes()},i=function(t,n,r){var i=!1;try{var s=t.deflate(n.fragment.getBytes());n.fragment=e.util.createBuffer(s),n.length=s.length,i=!0}catch(o){}return i},s=function(t,n,r){var i=!1;try{var s=t.inflate(n.fragment.getBytes());n.fragment=e.util.createBuffer(s),n.length=s.length,i=!0}catch(o){}return i},o=function(t,n){var r=0;switch(n){case 1:r=t.getByte();break;case 2:r=t.getInt16();break;case 3:r=t.getInt24();break;case 4:r=t.getInt32()}return e.util.createBuffer(t.getBytes(r))},u=function(e,t,n){e.putInt(n.length(),t<<3),e.putBuffer(n)},a={};a.Version={major:3,minor:1},a.MaxFragment=15360,a.ConnectionEnd={server:0,client:1},a.PRFAlgorithm={tls_prf_sha256:0},a.BulkCipherAlgorithm={none:null,rc4:0,des3:1,aes:2},a.CipherType={stream:0,block:1,aead:2},a.MACAlgorithm={none:null,hmac_md5:0,hmac_sha1:1,hmac_sha256:2,hmac_sha384:3,hmac_sha512:4},a.CompressionMethod={none:0,deflate:1},a.ContentType={change_cipher_spec:20,alert:21,handshake:22,application_data:23},a.HandshakeType={hello_request:0,client_hello:1,server_hello:2,certificate:11,server_key_exchange:12,certificate_request:13,server_hello_done:14,certificate_verify:15,client_key_exchange:16,finished:20},a.Alert={},a.Alert.Level={warning:1,fatal:2},a.Alert.Description={close_notify:0,unexpected_message:10,bad_record_mac:20,decryption_failed:21,record_overflow:22,decompression_failure:30,handshake_failure:40,bad_certificate:42,unsupported_certificate:43,certificate_revoked:44,certificate_expired:45,certificate_unknown:46,illegal_parameter:47,unknown_ca:48,access_denied:49,decode_error:50,decrypt_error:51,export_restriction:60,protocol_version:70,insufficient_security:71,internal_error:80,user_canceled:90,no_renegotiation:100},a.CipherSuites={},a.getCipherSuite=function(e){var t=null;for(var n in a.CipherSuites){var r=a.CipherSuites[n];if(r.id[0]===e.charCodeAt(0)&&r.id[1]===e.charCodeAt(1)){t=r;break}}return t},a.handleUnexpected=function(e,t){var n=!e.open&&e.entity===a.ConnectionEnd.client;n||e.error(e,{message:"Unexpected message. Received TLS record out of order.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.unexpected_message}})},a.handleHelloRequest=function(e,t,n){!e.handshaking&&e.handshakes>0&&(a.queue(e,a.createAlert({level:a.Alert.Level.warning,description:a.Alert.Description.no_renegotiation})),a.flush(e)),e.process()},a.parseHelloMessage=function(t,n,r){var i=null,s=t.entity===a.ConnectionEnd.client;if(r<38)t.error(t,{message:s?"Invalid ServerHello message. Message too short.":"Invalid ClientHello message. Message too short.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.illegal_parameter}});else{var u=n.fragment,f=u.length();i={version:{major:u.getByte(),minor:u.getByte()},random:e.util.createBuffer(u.getBytes(32)),session_id:o(u,1),extensions:[]},s?(i.cipher_suite=u.getBytes(2),i.compression_method=u.getByte()):(i.cipher_suites=o(u,2),i.compression_methods=o(u,1)),f=r-(f-u.length());if(f>0){var l=o(u,2);while(l.length()>0)i.extensions.push({type:[l.getByte(),l.getByte()],data:o(l,2)});if(!s)for(var c=0;c<i.extensions.length;++c){var h=i.extensions[c];if(h.type[0]===0&&h.type[1]===0){var p=o(h.data,2);while(p.length()>0){var d=p.getByte();if(d!==0)break;t.session.serverNameList.push(o(p,2).getBytes())}}}}(i.version.major!==a.Version.major||i.version.minor!==a.Version.minor)&&t.error(t,{message:"Incompatible TLS version.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.protocol_version}});if(s)t.session.cipherSuite=a.getCipherSuite(i.cipher_suite);else{var v=e.util.createBuffer(i.cipher_suites.bytes());while(v.length()>0){t.session.cipherSuite=a.getCipherSuite(v.getBytes(2));if(t.session.cipherSuite!==null)break}}if(t.session.cipherSuite===null)return t.error(t,{message:"No cipher suites in common.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.handshake_failure},cipherSuite:e.util.bytesToHex(i.cipher_suite)});s?t.session.compressionMethod=i.compression_method:t.session.compressionMethod=a.CompressionMethod.none}return i},a.createSecurityParameters=function(e,t){var n=e.entity===a.ConnectionEnd.client,r=t.random.bytes(),i=n?e.session.sp.client_random:r,s=n?r:a.createRandom().getBytes();e.session.sp={entity:e.entity,prf_algorithm:a.PRFAlgorithm.tls_prf_sha256,bulk_cipher_algorithm:null,cipher_type:null,enc_key_length:null,block_length:null,fixed_iv_length:null,record_iv_length:null,mac_algorithm:null,mac_length:null,mac_key_length:null,compression_algorithm:e.session.compressionMethod,pre_master_secret:null,master_secret:null,client_random:i,server_random:s}},a.handleServerHello=function(e,t,n){var r=a.parseHelloMessage(e,t,n);if(!e.fail){var i=r.session_id.bytes();i===e.session.id?(e.expect=d,e.session.resuming=!0,e.session.sp.server_random=r.random.bytes()):(e.expect=l,e.session.resuming=!1,a.createSecurityParameters(e,r)),e.session.id=i,e.process()}},a.handleClientHello=function(t,n,r){var i=a.parseHelloMessage(t,n,r);if(!t.fail){var s=i.session_id.bytes(),o=null;t.sessionCache&&(o=t.sessionCache.getSession(s),o===null&&(s="")),s.length===0&&(s=e.random.getBytes(32)),t.session.id=s,t.session.clientHelloVersion=i.version,t.session.sp=o?o.sp:{},o!==null?(t.expect=S,t.session.resuming=!0,t.session.sp.client_random=i.random.bytes()):(t.expect=t.verifyClient!==!1?b:w,t.session.resuming=!1,a.createSecurityParameters(t,i)),t.open=!0,a.queue(t,a.createRecord({type:a.ContentType.handshake,data:a.createServerHello(t)})),t.session.resuming?(a.queue(t,a.createRecord({type:a.ContentType.change_cipher_spec,data:a.createChangeCipherSpec()})),t.state.pending=a.createConnectionState(t),t.state.current.write=t.state.pending.write,a.queue(t,a.createRecord({type:a.ContentType.handshake,data:a.createFinished(t)}))):(a.queue(t,a.createRecord({type:a.ContentType.handshake,data:a.createCertificate(t)})),t.fail||(a.queue(t,a.createRecord({type:a.ContentType.handshake,data:a.createServerKeyExchange(t)})),t.verifyClient!==!1&&a.queue(t,a.createRecord({type:a.ContentType.handshake,data:a.createCertificateRequest(t)})),a.queue(t,a.createRecord({type:a.ContentType.handshake,data:a.createServerHelloDone(t)})))),a.flush(t),t.process()}},a.handleCertificate=function(t,n,r){if(r<3)t.error(t,{message:"Invalid Certificate message. Message too short.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.illegal_parameter}});else{var i=n.fragment,s={certificate_list:o(i,3)},u,f,l=[];try{while(s.certificate_list.length()>0)u=o(s.certificate_list,3),f=e.asn1.fromDer(u),u=e.pki.certificateFromAsn1(f,!0),l.push(u)}catch(h){t.error(t,{message:"Could not parse certificate list.",cause:h,send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.bad_certificate}})}if(!t.fail){var p=t.entity===a.ConnectionEnd.client;!p&&t.verifyClient!==!0||l.length!==0?l.length===0?t.expect=p?c:w:(p?t.session.serverCertificate=l[0]:t.session.clientCertificate=l[0],a.verifyCertificateChain(t,l)&&(t.expect=p?c:w)):t.error(t,{message:p?"No server certificate provided.":"No client certificate provided.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.illegal_parameter}}),t.process()}}},a.handleServerKeyExchange=function(e,t,n){n>0?e.error(e,{message:"Invalid key parameters. Only RSA is supported.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.unsupported_certificate}}):(e.expect=h,e.process())},a.handleClientKeyExchange=function(t,n,r){if(r<48)t.error(t,{message:"Invalid key parameters. Only RSA is supported.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.unsupported_certificate}});else{var i=n.fragment,s={enc_pre_master_secret:o(i,2).getBytes()},u=null;if(t.getPrivateKey)try{u=t.getPrivateKey(t,t.session.serverCertificate),u=e.pki.privateKeyFromPem(u)}catch(f){t.error(t,{message:"Could not get private key.",cause:f,send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.internal_error}})}if(u===null)t.error(t,{message:"No private key set.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.internal_error}});else try{var l=t.session.sp;l.pre_master_secret=u.decrypt(s.enc_pre_master_secret);var c=t.session.clientHelloVersion;if(c.major!==l.pre_master_secret.charCodeAt(0)||c.minor!==l.pre_master_secret.charCodeAt(1))throw{message:"TLS version rollback attack detected."}}catch(f){l.pre_master_secret=e.random.getBytes(48)}}t.fail||(t.expect=S,t.session.clientCertificate!==null&&(t.expect=E),t.process())},a.handleCertificateRequest=function(e,t,n){if(n<3)e.error(e,{message:"Invalid CertificateRequest. Message too short.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.illegal_parameter}});else{var r=t.fragment,i={certificate_types:o(r,1),certificate_authorities:o(r,2)};e.session.certificateRequest=i,e.expect=p,e.process()}},a.handleCertificateVerify=function(t,n,r){if(r<2)t.error(t,{message:"Invalid CertificateVerify. Message too short.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.illegal_parameter}});else{var i=n.fragment;i.read-=4;var s=i.bytes();i.read+=4;var u={signature:o(i,2).getBytes()},f=e.util.createBuffer();f.putBuffer(t.session.md5.digest()),f.putBuffer(t.session.sha1.digest()),f=f.getBytes();try{var l=t.session.clientCertificate;if(!l.publicKey.verify(f,u.signature,"NONE"))throw{message:"CertificateVerify signature does not match."};t.session.md5.update(s),t.session.sha1.update(s)}catch(c){t.error(t,{message:"Bad signature in CertificateVerify.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.handshake_failure}})}t.fail||(t.expect=S,t.process())}},a.handleServerHelloDone=function(t,n,r){if(r>0)t.error(t,{message:"Invalid ServerHelloDone message. Invalid length.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.record_overflow}});else if(t.serverCertificate===null){var i={message:"No server certificate provided. Not enough security.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.insufficient_security}},s=t.verify(t,i.alert.description,depth,[]);if(s===!0)i=null;else{if(s||s===0)typeof s=="object"&&!e.util.isArray(s)?(s.message&&(i.message=s.message),s.alert&&(i.alert.description=s.alert)):typeof s=="number"&&(i.alert.description=s);t.error(t,i)}}!t.fail&&t.session.certificateRequest!==null&&(n=a.createRecord({type:a.ContentType.handshake,data:a.createCertificate(t)}),a.queue(t,n));if(!t.fail){n=a.createRecord({type:a.ContentType.handshake,data:a.createClientKeyExchange(t)}),a.queue(t,n),t.expect=g;var o=function(e,t){e.session.certificateRequest!==null&&e.session.clientCertificate!==null&&a.queue(e,a.createRecord({type:a.ContentType.handshake,data:a.createCertificateVerify(e,t)})),a.queue(e,a.createRecord({type:a.ContentType.change_cipher_spec,data:a.createChangeCipherSpec()})),e.state.pending=a.createConnectionState(e),e.state.current.write=e.state.pending.write,a.queue(e,a.createRecord({type:a.ContentType.handshake,data:a.createFinished(e)})),e.expect=d,a.flush(e),e.process()};t.session.certificateRequest===null||t.session.clientCertificate===null?o(t,null):a.getClientSignature(t,o)}},a.handleChangeCipherSpec=function(e,t){if(t.fragment.getByte()!==1)e.error(e,{message:"Invalid ChangeCipherSpec message received.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.illegal_parameter}});else{var n=e.entity===a.ConnectionEnd.client;if(e.session.resuming&&n||!e.session.resuming&&!n)e.state.pending=a.createConnectionState(e);e.state.current.read=e.state.pending.read;if(!e.session.resuming&&n||e.session.resuming&&!n)e.state.pending=null;e.expect=n?v:x,e.process()}},a.handleFinished=function(n,r,i){var s=r.fragment;s.read-=4;var o=s.bytes();s.read+=4;var u=r.fragment.getBytes();s=e.util.createBuffer(),s.putBuffer(n.session.md5.digest()),s.putBuffer(n.session.sha1.digest());var f=n.entity===a.ConnectionEnd.client,l=f?"server finished":"client finished",c=n.session.sp,h=12,p=t;s=p(c.master_secret,l,s.getBytes(),h);if(s.getBytes()!==u)n.error(n,{message:"Invalid verify_data in Finished message.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.decrypt_error}});else{n.session.md5.update(o),n.session.sha1.update(o);if(n.session.resuming&&f||!n.session.resuming&&!f)a.queue(n,a.createRecord({type:a.ContentType.change_cipher_spec,data:a.createChangeCipherSpec()})),n.state.current.write=n.state.pending.write,n.state.pending=null,a.queue(n,a.createRecord({type:a.ContentType.handshake,data:a.createFinished(n)}));n.expect=f?m:T,n.handshaking=!1,++n.handshakes,n.peerCertificate=f?n.session.serverCertificate:n.session.clientCertificate,n.sessionCache?(n.session={id:n.session.id,sp:n.session.sp},n.session.sp.keys=null):n.session=null,a.flush(n),n.isConnected=!0,n.connected(n),n.process()}},a.handleAlert=function(e,t){var n=t.fragment,r={level:n.getByte(),description:n.getByte()},i;switch(r.description){case a.Alert.Description.close_notify:i="Connection closed.";break;case a.Alert.Description.unexpected_message:i="Unexpected message.";break;case a.Alert.Description.bad_record_mac:i="Bad record MAC.";break;case a.Alert.Description.decryption_failed:i="Decryption failed.";break;case a.Alert.Description.record_overflow:i="Record overflow.";break;case a.Alert.Description.decompression_failure:i="Decompression failed.";break;case a.Alert.Description.handshake_failure:i="Handshake failure.";break;case a.Alert.Description.bad_certificate:i="Bad certificate.";break;case a.Alert.Description.unsupported_certificate:i="Unsupported certificate.";break;case a.Alert.Description.certificate_revoked:i="Certificate revoked.";break;case a.Alert.Description.certificate_expired:i="Certificate expired.";break;case a.Alert.Description.certificate_unknown:i="Certificate unknown.";break;case a.Alert.Description.illegal_parameter:i="Illegal parameter.";break;case a.Alert.Description.unknown_ca:i="Unknown certificate authority.";break;case a.Alert.Description.access_denied:i="Access denied.";break;case a.Alert.Description.decode_error:i="Decode error.";break;case a.Alert.Description.decrypt_error:i="Decrypt error.";break;case a.Alert.Description.export_restriction:i="Export restriction.";break;case a.Alert.Description.protocol_version:i="Unsupported protocol version.";break;case a.Alert.Description.insufficient_security:i="Insufficient security.";break;case a.Alert.Description.internal_error:i="Internal error.";break;case a.Alert.Description.user_canceled:i="User canceled.";break;case a.Alert.Description.no_renegotiation:i="Renegotiation not supported.";break;default:i="Unknown error."}r.description===a.Alert.Description.close_notify?e.close():(e.error(e,{message:i,send:!1,origin:e.entity===a.ConnectionEnd.client?"server":"client",alert:r}),e.process())},a.handleHandshake=function(t,n){var r=n.fragment,i=r.getByte(),s=r.getInt24();if(s>r.length())t.fragmented=n,n.fragment=e.util.createBuffer(),r.read-=4,t.process();else{t.fragmented=null,r.read-=4;var o=r.bytes(s+4);r.read+=4,i in I[t.entity][t.expect]?(t.entity===a.ConnectionEnd.server&&!t.open&&!t.fail&&(t.handshaking=!0,t.session={serverNameList:[],cipherSuite:null,compressionMethod:null,serverCertificate:null,clientCertificate:null,md5:e.md.md5.create(),sha1:e.md.sha1.create()}),i!==a.HandshakeType.hello_request&&i!==a.HandshakeType.certificate_verify&&i!==a.HandshakeType.finished&&(t.session.md5.update(o),t.session.sha1.update(o)),I[t.entity][t.expect][i](t,n,s)):a.handleUnexpected(t,n)}},a.handleApplicationData=function(e,t){e.data.putBuffer(t.fragment),e.dataReady(e),e.process()};var f=0,l=1,c=2,h=3,p=4,d=5,v=6,m=7,g=8,y=0,b=1,w=2,E=3,S=4,x=5,T=6,N=7,C=a.handleUnexpected,k=a.handleChangeCipherSpec,L=a.handleAlert,A=a.handleHandshake,O=a.handleApplicationData,M=[];M[a.ConnectionEnd.client]=[[C,L,A,C],[C,L,A,C],[C,L,A,C],[C,L,A,C],[C,L,A,C],[k,L,C,C],[C,L,A,C],[C,L,A,O],[C,L,A,C]],M[a.ConnectionEnd.server]=[[C,L,A,C],[C,L,A,C],[C,L,A,C],[C,L,A,C],[k,L,C,C],[C,L,A,C],[C,L,A,O],[C,L,A,C]];var _=a.handleHelloRequest,D=a.handleServerHello,P=a.handleCertificate,H=a.handleServerKeyExchange,B=a.handleCertificateRequest,j=a.handleServerHelloDone,F=a.handleFinished,I=[];I[a.ConnectionEnd.client]=[[C,C,D,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C],[_,C,C,C,C,C,C,C,C,C,C,P,H,B,j,C,C,C,C,C,C],[_,C,C,C,C,C,C,C,C,C,C,C,H,B,j,C,C,C,C,C,C],[_,C,C,C,C,C,C,C,C,C,C,C,C,B,j,C,C,C,C,C,C],[_,C,C,C,C,C,C,C,C,C,C,C,C,C,j,C,C,C,C,C,C],[_,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C],[_,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,F],[_,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C],[_,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C]];var q=a.handleClientHello,R=a.handleClientKeyExchange,U=a.handleCertificateVerify;I[a.ConnectionEnd.server]=[[C,q,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C],[C,C,C,C,C,C,C,C,C,C,C,P,C,C,C,C,C,C,C,C,C],[C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,R,C,C,C,C],[C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,U,C,C,C,C,C],[C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C],[C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,F],[C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C],[C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C]],a.generateKeys=function(e,n){var r=t,i=n.client_random+n.server_random;e.session.resuming||(n.master_secret=r(n.pre_master_secret,"master secret",i,48).bytes(),n.pre_master_secret=null),i=n.server_random+n.client_random;var s=2*n.mac_key_length+2*n.enc_key_length+2*n.fixed_iv_length,o=r(n.master_secret,"key expansion",i,s);return{client_write_MAC_key:o.getBytes(n.mac_key_length),server_write_MAC_key:o.getBytes(n.mac_key_length),client_write_key:o.getBytes(n.enc_key_length),server_write_key:o.getBytes(n.enc_key_length),client_write_IV:o.getBytes(n.fixed_iv_length),server_write_IV:o.getBytes(n.fixed_iv_length)}},a.createConnectionState=function(e){var t=e.entity===a.ConnectionEnd.client,n=function(){var e={sequenceNumber:[0,0],macKey:null,macLength:0,macFunction:null,cipherState:null,cipherFunction:function(e){return!0},compressionState:null,compressFunction:function(e){return!0},updateSequenceNumber:function(){e.sequenceNumber[1]===4294967295?(e.sequenceNumber[1]=0,++e.sequenceNumber[0]):++e.sequenceNumber[1]}};return e},r={read:n(),write:n()};r.read.update=function(e,t){return r.read.cipherFunction(t,r.read)?r.read.compressFunction(e,t,r.read)||e.error(e,{message:"Could not decompress record.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.decompression_failure}}):e.error(e,{message:"Could not decrypt record or bad MAC.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.bad_record_mac}}),!e.fail},r.write.update=function(e,t){return r.write.compressFunction(e,t,r.write)?r.write.cipherFunction(t,r.write)||e.error(e,{message:"Could not encrypt record.",send:!1,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.internal_error}}):e.error(e,{message:"Could not compress record.",send:!1,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.internal_error}}),!e.fail};if(e.session){var o=e.session.sp;e.session.cipherSuite.initSecurityParameters(o),o.keys=a.generateKeys(e,o),r.read.macKey=t?o.keys.server_write_MAC_key:o.keys.client_write_MAC_key,r.write.macKey=t?o.keys.client_write_MAC_key:o.keys.server_write_MAC_key,e.session.cipherSuite.initConnectionState(r,e,o);switch(o.compression_algorithm){case a.CompressionMethod.none:break;case a.CompressionMethod.deflate:r.read.compressFunction=s,r.write.compressFunction=i;break;default:throw{message:"Unsupported compression algorithm."}}}return r},a.createRandom=function(){var t=new Date,n=+t+t.getTimezoneOffset()*6e4,r=e.util.createBuffer();return r.putInt32(n),r.putBytes(e.random.getBytes(28)),r},a.createRecord=function(e){if(!e.data)return null;var t={type:e.type,version:{major:a.Version.major,minor:a.Version.minor},length:e.data.length(),fragment:e.data};return t},a.createAlert=function(t){var n=e.util.createBuffer();return n.putByte(t.level),n.putByte(t.description),a.createRecord({type:a.ContentType.alert,data:n})},a.createClientHello=function(t){var n=e.util.createBuffer();for(var r=0;r<t.cipherSuites.length;++r){var i=t.cipherSuites[r];n.putByte(i.id[0]),n.putByte(i.id[1])}var s=n.length(),o=e.util.createBuffer();o.putByte(a.CompressionMethod.none);var f=o.length(),l=e.util.createBuffer();if(t.virtualHost){var c=e.util.createBuffer();c.putByte(0),c.putByte(0);var h=e.util.createBuffer();h.putByte(0),u(h,2,e.util.createBuffer(t.virtualHost));var p=e.util.createBuffer();u(p,2,h),u(c,2,p),l.putBuffer(c)}var d=l.length();d>0&&(d+=2);var v=t.session.id,m=v.length+1+2+4+28+2+s+1+f+d,g=e.util.createBuffer();return g.putByte(a.HandshakeType.client_hello),g.putInt24(m),g.putByte(a.Version.major),g.putByte(a.Version.minor),g.putBytes(t.session.sp.client_random),u(g,1,e.util.createBuffer(v)),u(g,2,n),u(g,1,o),d>0&&u(g,2,l),g},a.createServerHello=function(t){var n=t.session.id,r=n.length+1+2+4+28+2+1,i=e.util.createBuffer();return i.putByte(a.HandshakeType.server_hello),i.putInt24(r),i.putByte(a.Version.major),i.putByte(a.Version.minor),i.putBytes(t.session.sp.server_random),u(i,1,e.util.createBuffer(n)),i.putByte(t.session.cipherSuite.id[0]),i.putByte(t.session.cipherSuite.id[1]),i.putByte(t.session.compressionMethod),i},a.createCertificate=function(t){var n=t.entity===a.ConnectionEnd.client,r=null;t.getCertificate&&(r=t.getCertificate(t,n?t.session.certificateRequest:t.session.serverNameList));var i=e.util.createBuffer();if(r!==null)try{e.util.isArray(r)||(r=[r]);var s=null;for(var o=0;o<r.length;++o){var f=e.pem.decode(r[o])[0];if(f.type!=="CERTIFICATE"&&f.type!=="X509 CERTIFICATE"&&f.type!=="TRUSTED CERTIFICATE")throw{message:'Could not convert certificate from PEM; PEM header type is not "CERTIFICATE", "X509 CERTIFICATE", or "TRUSTED CERTIFICATE".',headerType:f.type};if(f.procType&&f.procType.type==="ENCRYPTED")throw{message:"Could not convert certificate from PEM; PEM is encrypted."};var l=e.util.createBuffer(f.body);s===null&&(s=e.asn1.fromDer(l.bytes(),!1));var c=e.util.createBuffer();u(c,3,l),i.putBuffer(c)}r=e.pki.certificateFromAsn1(s),n?t.session.clientCertificate=r:t.session.serverCertificate=r}catch(h){return t.error(t,{message:"Could not send certificate list.",cause:h,send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.bad_certificate}})}var p=3+i.length(),d=e.util.createBuffer();return d.putByte(a.HandshakeType.certificate),d.putInt24(p),u(d,3,i),d},a.createClientKeyExchange=function(t){var n=e.util.createBuffer();n.putByte(a.Version.major),n.putByte(a.Version.minor),n.putBytes(e.random.getBytes(46));var r=t.session.sp;r.pre_master_secret=n.getBytes();var i=t.session.serverCertificate.publicKey;n=i.encrypt(r.pre_master_secret);var s=n.length+2,o=e.util.createBuffer();return o.putByte(a.HandshakeType.client_key_exchange),o.putInt24(s),o.putInt16(n.length),o.putBytes(n),o},a.createServerKeyExchange=function(t){var n=0,r=e.util.createBuffer();return n>0&&(r.putByte(a.HandshakeType.server_key_exchange),r.putInt24(n)),r},a.getClientSignature=function(t,n){var r=e.util.createBuffer();r.putBuffer(t.session.md5.digest()),r.putBuffer(t.session.sha1.digest()),r=r.getBytes(),t.getSignature=t.getSignature||function(t,n,r){var i=null;if(t.getPrivateKey)try{i=t.getPrivateKey(t,t.session.clientCertificate),i=e.pki.privateKeyFromPem(i)}catch(s){t.error(t,{message:"Could not get private key.",cause:s,send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.internal_error}})}i===null?t.error(t,{message:"No private key set.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.internal_error}}):n=i.sign(n,null),r(t,n)},t.getSignature(t,r,n)},a.createCertificateVerify=function(t,n){var r=n.length+2,i=e.util.createBuffer();return i.putByte(a.HandshakeType.certificate_verify),i.putInt24(r),i.putInt16(n.length),i.putBytes(n),i},a.createCertificateRequest=function(t){var n=e.util.createBuffer();n.putByte(1);var r=e.util.createBuffer();for(var i in t.caStore.certs){var s=t.caStore.certs[i],o=e.pki.distinguishedNameToAsn1(s.subject);r.putBuffer(e.asn1.toDer(o))}var f=1+n.length()+2+r.length(),l=e.util.createBuffer();return l.putByte(a.HandshakeType.certificate_request),l.putInt24(f),u(l,1,n),u(l,2,r),l},a.createServerHelloDone=function(t){var n=e.util.createBuffer();return n.putByte(a.HandshakeType.server_hello_done),n.putInt24(0),n},a.createChangeCipherSpec=function(){var t=e.util.createBuffer();return t.putByte(1),t},a.createFinished=function(n){var r=e.util.createBuffer();r.putBuffer(n.session.md5.digest()),r.putBuffer(n.session.sha1.digest());var i=n.entity===a.ConnectionEnd.client,s=n.session.sp,o=12,u=t,f=i?"client finished":"server finished";r=u(s.master_secret,f,r.getBytes(),o);var l=e.util.createBuffer();return l.putByte(a.HandshakeType.finished),l.putInt24(r.length()),l.putBuffer(r),l},a.queue=function(t,n){if(!n)return;if(n.type===a.ContentType.handshake){var r=n.fragment.bytes();t.session.md5.update(r),t.session.sha1.update(r),r=null}var i;if(n.fragment.length()<=a.MaxFragment)i=[n];else{i=[];var s=n.fragment.bytes();while(s.length>a.MaxFragment)i.push(a.createRecord({type:n.type,data:e.util.createBuffer(s.slice(0,a.MaxFragment))})),s=s.slice(a.MaxFragment);s.length>0&&i.push(a.createRecord({type:n.type,data:e.util.createBuffer(s)}))}for(var o=0;o<i.length&&!t.fail;++o){var u=i[o],f=t.state.current.write;f.update(t,u)&&t.records.push(u)}},a.flush=function(e){for(var t=0;t<e.records.length;++t){var n=e.records[t];e.tlsData.putByte(n.type),e.tlsData.putByte(n.version.major),e.tlsData.putByte(n.version.minor),e.tlsData.putInt16(n.fragment.length()),e.tlsData.putBuffer(e.records[t].fragment)}return e.records=[],e.tlsDataReady(e)};var z=function(t){switch(t){case!0:return!0;case e.pki.certificateError.bad_certificate:return a.Alert.Description.bad_certificate;case e.pki.certificateError.unsupported_certificate:return a.Alert.Description.unsupported_certificate;case e.pki.certificateError.certificate_revoked:return a.Alert.Description.certificate_revoked;case e.pki.certificateError.certificate_expired:return a.Alert.Description.certificate_expired;case e.pki.certificateError.certificate_unknown:return a.Alert.Description.certificate_unknown;case e.pki.certificateError.unknown_ca:return a.Alert.Description.unknown_ca;default:return a.Alert.Description.bad_certificate}},W=function(t){switch(t){case!0:return!0;case a.Alert.Description.bad_certificate:return e.pki.certificateError.bad_certificate;case a.Alert.Description.unsupported_certificate:return e.pki.certificateError.unsupported_certificate;case a.Alert.Description.certificate_revoked:return e.pki.certificateError.certificate_revoked;case a.Alert.Description.certificate_expired:return e.pki.certificateError.certificate_expired;case a.Alert.Description.certificate_unknown:return e.pki.certificateError.certificate_unknown;case a.Alert.Description.unknown_ca:return e.pki.certificateError.unknown_ca;default:return e.pki.certificateError.bad_certificate}};a.verifyCertificateChain=function(t,n){try{e.pki.verifyCertificateChain(t.caStore,n,function(r,i,s){var o=z(r),u=t.verify(t,r,i,s);if(u!==!0){if(typeof u=="object"&&!e.util.isArray(u)){var f={message:"The application rejected the certificate.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.bad_certificate}};throw u.message&&(f.message=u.message),u.alert&&(f.alert.description=u.alert),f}u!==r&&(u=W(u))}return u})}catch(r){if(typeof r!="object"||e.util.isArray(r))r={send:!0,alert:{level:a.Alert.Level.fatal,description:z(r)}};"send"in r||(r.send=!0),"alert"in r||(r.alert={level:a.Alert.Level.fatal,description:z(r.error)}),t.error(t,r)}return!t.fail},a.createSessionCache=function(t,n){var r=null;if(t&&t.getSession&&t.setSession&&t.order)r=t;else{r={},r.cache=t||{},r.capacity=Math.max(n||100,1),r.order=[];for(var i in t)r.order.length<=n?r.order.push(i):delete t[i];r.getSession=function(t){var n=null,i=null;t?i=e.util.bytesToHex(t):r.order.length>0&&(i=r.order[0]);if(i!==null&&i in r.cache){n=r.cache[i],delete r.cache[i];for(var s in r.order)if(r.order[s]===i){r.order.splice(s,1);break}}return n},r.setSession=function(t,n){if(r.order.length===r.capacity){var i=r.order.shift();delete r.cache[i]}var i=e.util.bytesToHex(t);r.order.push(i),r.cache[i]=n}}return r},a.createConnection=function(t){var n=null;t.caStore?e.util.isArray(t.caStore)?n=e.pki.createCaStore(t.caStore):n=t.caStore:n=e.pki.createCaStore();var r=t.cipherSuites||null;if(r===null){r=[];for(var i in a.CipherSuites)r.push(a.CipherSuites[i])}var s=t.server||!1?a.ConnectionEnd.server:a.ConnectionEnd.client,o=t.sessionCache?a.createSessionCache(t.sessionCache):null,u={entity:s,sessionId:t.sessionId,caStore:n,sessionCache:o,cipherSuites:r,connected:t.connected,virtualHost:t.virtualHost||null,verifyClient:t.verifyClient||!1,verify:t.verify||function(e,t,n,r){return t},getCertificate:t.getCertificate||null,getPrivateKey:t.getPrivateKey||null,getSignature:t.getSignature||null,input:e.util.createBuffer(),tlsData:e.util.createBuffer(),data:e.util.createBuffer(),tlsDataReady:t.tlsDataReady,dataReady:t.dataReady,closed:t.closed,error:function(e,n){n.origin=n.origin||(e.entity===a.ConnectionEnd.client?"client":"server"),n.send&&(a.queue(e,a.createAlert(n.alert)),a.flush(e));var r=n.fatal!==!1;r&&(e.fail=!0),t.error(e,n),r&&e.close(!1)},deflate:t.deflate||null,inflate:t.inflate||null};u.reset=function(e){u.record=null,u.session=null,u.peerCertificate=null,u.state={pending:null,current:null},u.expect=u.entity===a.ConnectionEnd.client?f:y,u.fragmented=null,u.records=[],u.open=!1,u.handshakes=0,u.handshaking=!1,u.isConnected=!1,u.fail=!e&&typeof e!="undefined",u.input.clear(),u.tlsData.clear(),u.data.clear(),u.state.current=a.createConnectionState(u)},u.reset();var l=function(e,t){var n=t.type-a.ContentType.change_cipher_spec,r=M[e.entity][e.expect];n in r?r[n](e,t):a.handleUnexpected(e,t)},c=function(t){var n=0,r=t.input,i=r.length();return i<5?n=5-i:(t.record={type:r.getByte(),version:{major:r.getByte(),minor:r.getByte()},length:r.getInt16(),fragment:e.util.createBuffer(),ready:!1},(t.record.version.major!==a.Version.major||t.record.version.minor!==a.Version.minor)&&t.error(t,{message:"Incompatible TLS version.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.protocol_version}})),n},h=function(e){var t=0,n=e.input,r=n.length();if(r<e.record.length)t=e.record.length-r;else{e.record.fragment.putBytes(n.getBytes(e.record.length));var i=e.state.current.read;i.update(e,e.record)&&(e.fragmented!==null&&(e.fragmented.type===e.record.type?(e.fragmented.fragment.putBuffer(e.record.fragment),e.record=e.fragmented):e.error(e,{message:"Invalid fragmented record.",send:!0,alert:{level:a.Alert.Level.fatal,description:a.Alert.Description.unexpected_message}})),e.record.ready=!0)}return t};return u.handshake=function(t){if(u.entity!==a.ConnectionEnd.client)u.error(u,{message:"Cannot initiate handshake as a server.",fatal:!1});else if(u.handshaking)u.error(u,{message:"Handshake already in progress.",fatal:!1});else{u.fail&&!u.open&&u.handshakes===0&&(u.fail=!1),u.handshaking=!0,t=t||"";var n=null;t.length>0&&(u.sessionCache&&(n=u.sessionCache.getSession(t)),n===null&&(t="")),t.length===0&&u.sessionCache&&(n=u.sessionCache.getSession(),n!==null&&(t=n.id)),u.session={id:t,cipherSuite:null,compressionMethod:null,serverCertificate:null,certificateRequest:null,clientCertificate:null,sp:n?n.sp:{},md5:e.md.md5.create(),sha1:e.md.sha1.create()},u.session.sp.client_random=a.createRandom().getBytes(),u.open=!0,a.queue(u,a.createRecord({type:a.ContentType.handshake,data:a.createClientHello(u)})),a.flush(u)}},u.process=function(e){var t=0;return e&&u.input.putBytes(e),u.fail||(u.record!==null&&u.record.ready&&u.record.fragment.isEmpty()&&(u.record=null),u.record===null&&(t=c(u)),!u.fail&&u.record!==null&&!u.record.ready&&(t=h(u)),!u.fail&&u.record!==null&&u.record.ready&&l(u,u.record)),t},u.prepare=function(t){return a.queue(u,a.createRecord({type:a.ContentType.application_data,data:e.util.createBuffer(t)})),a.flush(u)},u.close=function(e){!u.fail&&u.sessionCache&&u.session&&u.sessionCache.setSession(u.session.id,u.session);if(u.open){u.open=!1,u.input.clear();if(u.isConnected||u.handshaking)u.isConnected=u.handshaking=!1,a.queue(u,a.createAlert({level:a.Alert.Level.warning,description:a.Alert.Description.close_notify})),a.flush(u);u.closed(u)}u.reset(e)},u},e.tls=e.tls||{};for(var X in a)typeof a[X]!="function"&&(e.tls[X]=a[X]);e.tls.prf_tls1=t,e.tls.hmac_sha1=r,e.tls.createSessionCache=a.createSessionCache,e.tls.createConnection=a.createConnection}var t="tls";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/tls",["require","module","./asn1","./hmac","./md","./pem","./pki","./random","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){function n(n,i,s){var u=i.entity===e.tls.ConnectionEnd.client;n.read.cipherState={init:!1,cipher:e.aes.createDecryptionCipher(u?s.keys.server_write_key:s.keys.client_write_key),iv:u?s.keys.server_write_IV:s.keys.client_write_IV},n.write.cipherState={init:!1,cipher:e.aes.createEncryptionCipher(u?s.keys.client_write_key:s.keys.server_write_key),iv:u?s.keys.client_write_IV:s.keys.server_write_IV},n.read.cipherFunction=o,n.write.cipherFunction=r,n.read.macLength=n.write.macLength=s.mac_length,n.read.macFunction=n.write.macFunction=t.hmac_sha1}function r(t,n){var r=!1,s=n.macFunction(n.macKey,n.sequenceNumber,t);t.fragment.putBytes(s),n.updateSequenceNumber();var o;t.version.minor>1?o=e.random.getBytes(16):o=n.cipherState.init?null:n.cipherState.iv,n.cipherState.init=!0;var u=n.cipherState.cipher;return u.start(o),t.version.minor>1&&u.output.putBytes(o),u.update(t.fragment),u.finish(i)&&(t.fragment=u.output,t.length=t.fragment.length(),r=!0),r}function i(e,t,n){if(!n){var r=e-t.length()%e;t.fillWithByte(r-1,r)}return!0}function s(e,t,n){var r=!0;if(n){var i=t.length(),s=t.last();for(var o=i-1-s;o<i-1;++o)r=r&&t.at(o)==s;r&&t.truncate(s+1)}return r}function o(t,n){var r=!1,i=n.cipherState.init?null:n.cipherState.iv;n.cipherState.init=!0;var o=n.cipherState.cipher;o.start(i),o.update(t.fragment),r=o.finish(s);var u=n.macLength,a="";for(var f=0;f<u;++f)a+=String.fromCharCode(0);var l=o.output.length();l>=u?(t.fragment=o.output.getBytes(l-u),a=o.output.getBytes(u)):t.fragment=o.output.getBytes(),t.fragment=e.util.createBuffer(t.fragment),t.length=t.fragment.length();var c=n.macFunction(n.macKey,n.sequenceNumber,t);return n.updateSequenceNumber(),r=c===a&&r,r}var t=e.tls;t.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA={id:[0,47],name:"TLS_RSA_WITH_AES_128_CBC_SHA",initSecurityParameters:function(e){e.bulk_cipher_algorithm=t.BulkCipherAlgorithm.aes,e.cipher_type=t.CipherType.block,e.enc_key_length=16,e.block_length=16,e.fixed_iv_length=16,e.record_iv_length=16,e.mac_algorithm=t.MACAlgorithm.hmac_sha1,e.mac_length=20,e.mac_key_length=20},initConnectionState:n},t.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA={id:[0,53],name:"TLS_RSA_WITH_AES_256_CBC_SHA",initSecurityParameters:function(e){e.bulk_cipher_algorithm=t.BulkCipherAlgorithm.aes,e.cipher_type=t.CipherType.block,e.enc_key_length=32,e.block_length=16,e.fixed_iv_length=16,e.record_iv_length=16,e.mac_algorithm=t.MACAlgorithm.hmac_sha1,e.mac_length=20,e.mac_key_length=20},initConnectionState:n}}var t="aesCipherSuites";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/aesCipherSuites",["require","module","./aes","./tls"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){e.debug=e.debug||{},e.debug.storage={},e.debug.get=function(t,n){var r;return typeof t=="undefined"?r=e.debug.storage:t in e.debug.storage&&(typeof n=="undefined"?r=e.debug.storage[t]:r=e.debug.storage[t][n]),r},e.debug.set=function(t,n,r){t in e.debug.storage||(e.debug.storage[t]={}),e.debug.storage[t][n]=r},e.debug.clear=function(t,n){typeof t=="undefined"?e.debug.storage={}:t in e.debug.storage&&(typeof n=="undefined"?delete e.debug.storage[t]:delete e.debug.storage[t][n])}}var t="debug";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/debug",["require","module"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){e.log=e.log||{},e.log.levels=["none","error","warning","info","debug","verbose","max"];var t={},n=[],r=null;e.log.LEVEL_LOCKED=2,e.log.NO_LEVEL_CHECK=4,e.log.INTERPOLATE=8;for(var i=0;i<e.log.levels.length;++i){var s=e.log.levels[i];t[s]={index:i,name:s.toUpperCase()}}e.log.logMessage=function(r){var i=t[r.level].index;for(var s=0;s<n.length;++s){var o=n[s];if(o.flags&e.log.NO_LEVEL_CHECK)o.f(r);else{var u=t[o.level].index;i<=u&&o.f(o,r)}}},e.log.prepareStandard=function(e){"standard"in e||(e.standard=t[e.level].name+" ["+e.category+"] "+e.message)},e.log.prepareFull=function(t){if(!("full"in t)){var n=[t.message];n=n.concat([]||t.arguments),t.full=e.util.format.apply(this,n)}},e.log.prepareStandardFull=function(t){"standardFull"in t||(e.log.prepareStandard(t),t.standardFull=t.standard)};var o=["error","warning","info","debug","verbose"];for(var i=0;i<o.length;++i)(function(t){e.log[t]=function(n,r){var i=Array.prototype.slice.call(arguments).slice(2),s={timestamp:new Date,level:t,category:n,message:r,arguments:i};e.log.logMessage(s)}})(o[i]);e.log.makeLogger=function(t){var n={flags:0,f:t};return e.log.setLevel(n,"none"),n},e.log.setLevel=function(t,n){var r=!1;if(t&&!(t.flags&e.log.LEVEL_LOCKED))for(var i=0;i<e.log.levels.length;++i){var s=e.log.levels[i];if(n==s){t.level=n,r=!0;break}}return r},e.log.lock=function(t,n){typeof n=="undefined"||n?t.flags|=e.log.LEVEL_LOCKED:t.flags&=~e.log.LEVEL_LOCKED},e.log.addLogger=function(e){n.push(e)};if(typeof console!="undefined"&&"log"in console){var u;if(console.error&&console.warn&&console.info&&console.debug){var a={error:console.error,warning:console.warn,info:console.info,debug:console.debug,verbose:console.debug},f=function(t,n){e.log.prepareStandard(n);var r=a[n.level],i=[n.standard];i=i.concat(n.arguments.slice()),r.apply(console,i)};u=e.log.makeLogger(f)}else{var f=function(t,n){e.log.prepareStandardFull(n),console.log(n.standardFull)};u=e.log.makeLogger(f)}e.log.setLevel(u,"debug"),e.log.addLogger(u),r=u}else console={log:function(){}};if(r!==null){var l=e.util.getQueryVariables();"console.level"in l&&e.log.setLevel(r,l["console.level"].slice(-1)[0]);if("console.lock"in l){var c=l["console.lock"].slice(-1)[0];c=="true"&&e.log.lock(r)}}e.log.consoleLogger=r}var t="log";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/log",["require","module","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e){var t="forge.task",n=0,r={},i=0;e.debug.set(t,"tasks",r);var s={};e.debug.set(t,"queues",s);var o="?",u=30,a=20,f="ready",l="running",c="blocked",h="sleeping",p="done",d="error",v="stop",m="start",g="block",y="unblock",b="sleep",w="wakeup",E="cancel",S="fail",x={};x[f]={},x[f][v]=f,x[f][m]=l,x[f][E]=p,x[f][S]=d,x[l]={},x[l][v]=f,x[l][m]=l,x[l][g]=c,x[l][y]=l,x[l][b]=h,x[l][w]=l,x[l][E]=p,x[l][S]=d,x[c]={},x[c][v]=c,x[c][m]=c,x[c][g]=c,x[c][y]=c,x[c][b]=c,x[c][w]=c,x[c][E]=p,x[c][S]=d,x[h]={},x[h][v]=h,x[h][m]=h,x[h][g]=h,x[h][y]=h,x[h][b]=h,x[h][w]=h,x[h][E]=p,x[h][S]=d,x[p]={},x[p][v]=p,x[p][m]=p,x[p][g]=p,x[p][y]=p,x[p][b]=p,x[p][w]=p,x[p][E]=p,x[p][S]=d,x[d]={},x[d][v]=d,x[d][m]=d,x[d][g]=d,x[d][y]=d,x[d][b]=d,x[d][w]=d,x[d][E]=d,x[d][S]=d;var T=function(s){this.id=-1,this.name=s.name||o,this.parent=s.parent||null,this.run=s.run,this.subtasks=[],this.error=!1,this.state=f,this.blocks=0,this.timeoutId=null,this.swapTime=null,this.userData=null,this.id=i++,r[this.id]=this,n>=1&&e.log.verbose(t,"[%s][%s] init",this.id,this.name,this)};T.prototype.debug=function(n){n=n||"",e.log.debug(t,n,"[%s][%s] task:",this.id,this.name,this,"subtasks:",this.subtasks.length,"queue:",s)},T.prototype.next=function(e,t){typeof e=="function"&&(t=e,e=this.name);var n=new T({run:t,name:e,parent:this});return n.state=l,n.type=this.type,n.successCallback=this.successCallback||null,n.failureCallback=this.failureCallback||null,this.subtasks.push(n),this},T.prototype.parallel=function(t,n){return e.util.isArray(t)&&(n=t,t=this.name),this.next(t,function(r){var i=r;i.block(n.length);var s=function(t,r){e.task.start({type:t,run:function(e){n[r](e)},success:function(e){i.unblock()},failure:function(e){i.unblock()}})};for(var o=0;o<n.length;o++){var u=t+"__parallel-"+r.id+"-"+o,a=o;s(u,a)}})},T.prototype.stop=function(){this.state=x[this.state][v]},T.prototype.start=function(){this.error=!1,this.state=x[this.state][m],this.state===l&&(this.start=new Date,this.run(this),C(this,0))},T.prototype.block=function(e){e=typeof e=="undefined"?1:e,this.blocks+=e,this.blocks>0&&(this.state=x[this.state][g])},T.prototype.unblock=function(e){return e=typeof e=="undefined"?1:e,this.blocks-=e,this.blocks===0&&this.state!==p&&(this.state=l,C(this,0)),this.blocks},T.prototype.sleep=function(e){e=typeof e=="undefined"?0:e,this.state=x[this.state][b];var t=this;this.timeoutId=setTimeout(function(){t.timeoutId=null,t.state=l,C(t,0)},e)},T.prototype.wait=function(e){e.wait(this)},T.prototype.wakeup=function(){this.state===h&&(cancelTimeout(this.timeoutId),this.timeoutId=null,this.state=l,C(this,0))},T.prototype.cancel=function(){this.state=x[this.state][E],this.permitsNeeded=0,this.timeoutId!==null&&(cancelTimeout(this.timeoutId),this.timeoutId=null),this.subtasks=[]},T.prototype.fail=function(e){this.error=!0,k(this,!0);if(e)e.error=this.error,e.swapTime=this.swapTime,e.userData=this.userData,C(e,0);else{if(this.parent!==null){var t=this.parent;while(t.parent!==null)t.error=this.error,t.swapTime=this.swapTime,t.userData=this.userData,t=t.parent;k(t,!0)}this.failureCallback&&this.failureCallback(this)}};var N=function(e){e.error=!1,e.state=x[e.state][m],setTimeout(function(){e.state===l&&(e.swapTime=+(new Date),e.run(e),C(e,0))},0)},C=function(e,t){var n=t>u||+(new Date)-e.swapTime>a,r=function(t){t++;if(e.state===l){n&&(e.swapTime=+(new Date));if(e.subtasks.length>0){var r=e.subtasks.shift();r.error=e.error,r.swapTime=e.swapTime,r.userData=e.userData,r.run(r),r.error||C(r,t)}else k(e),e.error||e.parent!==null&&(e.parent.error=e.error,e.parent.swapTime=e.swapTime,e.parent.userData=e.userData,C(e.parent,t))}};n?setTimeout(r,0):r(t)},k=function(i,o){i.state=p,delete r[i.id],n>=1&&e.log.verbose(t,"[%s][%s] finish",i.id,i.name,i),i.parent===null&&(i.type in s?s[i.type].length===0?e.log.error(t,"[%s][%s] task queue empty [%s]",i.id,i.name,i.type):s[i.type][0]!==i?e.log.error(t,"[%s][%s] task not first in queue [%s]",i.id,i.name,i.type):(s[i.type].shift(),s[i.type].length===0?(n>=1&&e.log.verbose(t,"[%s][%s] delete queue [%s]",i.id,i.name,i.type),delete s[i.type]):(n>=1&&e.log.verbose(t,"[%s][%s] queue start next [%s] remain:%s",i.id,i.name,i.type,s[i.type].length),s[i.type][0].start())):e.log.error(t,"[%s][%s] task queue missing [%s]",i.id,i.name,i.type),o||(i.error&&i.failureCallback?i.failureCallback(i):!i.error&&i.successCallback&&i.successCallback(i)))};e.task=e.task||{},e.task.start=function(r){var i=new T({run:r.run,name:r.name||o});i.type=r.type,i.successCallback=r.success||null,i.failureCallback=r.failure||null,i.type in s?s[r.type].push(i):(n>=1&&e.log.verbose(t,"[%s][%s] create queue [%s]",i.id,i.name,i.type),s[i.type]=[i],N(i))},e.task.cancel=function(e){e in s&&(s[e]=[s[e][0]])},e.task.createCondition=function(){var e={tasks:{}};return e.wait=function(t){t.id in e.tasks||(t.block(),e.tasks[t.id]=t)},e.notify=function(){var t=e.tasks;e.tasks={};for(var n in t)t[n].unblock()},e}}var t="task";if(typeof define!="function"){if(typeof module!="object"||!module.exports)return typeof forge=="undefined"&&(forge={}),e(forge);var n=!0;define=function(e,t){t(require,module)}}var r,i=function(n,i){i.exports=function(i){var s=r.map(function(e){return n(e)}).concat(e);i=i||{},i.defined=i.defined||{};if(i.defined[t])return i[t];i.defined[t]=!0;for(var o=0;o<s.length;++o)s[o](i);return i[t]}},s=define;define=function(e,t){return r=typeof e=="string"?t.slice(2):e.slice(2),n?(delete define,s.apply(null,Array.prototype.slice.call(arguments,0))):(define=s,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/task",["require","module","./debug","./log","./util"],function(){i.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){var e="forge";if(typeof define!="function"){if(typeof module!="object"||!module.exports){typeof forge=="undefined"&&(forge={disableNativeCode:!1});return}var t=!0;define=function(e,t){t(require,module)}}var n,r=function(t,r){r.exports=function(r){var i=n.map(function(e){return t(e)});r=r||{},r.defined=r.defined||{};if(r.defined[e])return r[e];r.defined[e]=!0;for(var s=0;s<i.length;++s)i[s](r);return r},r.exports.disableNativeCode=!1,r.exports(r.exports)},i=define;define=function(e,r){return n=typeof e=="string"?r.slice(2):e.slice(2),t?(delete define,i.apply(null,Array.prototype.slice.call(arguments,0))):(define=i,define.apply(null,Array.prototype.slice.call(arguments,0)))},define("forge/forge",["require","module","./aes","./aesCipherSuites","./asn1","./debug","./des","./hmac","./log","./pbkdf2","./pem","./pkcs7","./pkcs1","./pkcs12","./pki","./prng","./pss","./random","./rc2","./task","./tls","./util","./md","./mgf1"],function(){r.apply(null,Array.prototype.slice.call(arguments,0))})}(),function(){function e(e,t){describe("tls",function(){it("should test TLS 1.0 PRF",function(){var n=t.util.createBuffer().fillWithByte(171,48).getBytes(),r=t.util.createBuffer().fillWithByte(205,64).getBytes(),i=t.tls.prf_tls1(n,"PRF Testvector",r,104),s="d3d4d1e349b5d515044666d51de32bab258cb521b6b053463e354832fd976754443bcf9a296519bc289abcbc1187e4ebd31e602353776c408aafb74cbc85eff69255f9788faa184cbb957a9819d84a5d7eb006eb459d3ae8de9810454b8b2d8f1afbc655a8c9a013";e.equal(i.toHex(),s)}),it("should establish a TLS connection and transfer data",function(n){function s(e,n){var r=t.pki.rsa.generateKeyPair(512),i=t.pki.createCertificate();i.publicKey=r.publicKey,i.serialNumber="01",i.validity.notBefore=new Date,i.validity.notAfter=new Date,i.validity.notAfter.setFullYear(i.validity.notBefore.getFullYear()+1);var s=[{name:"commonName",value:e},{name:"countryName",value:"US"},{shortName:"ST",value:"Virginia"},{name:"localityName",value:"Blacksburg"},{name:"organizationName",value:"Test"},{shortName:"OU",value:"Test"}];i.setSubject(s),i.setIssuer(s),i.setExtensions([{name:"basicConstraints",cA:!0},{name:"keyUsage",keyCertSign:!0,digitalSignature:!0,nonRepudiation:!0,keyEncipherment:!0,dataEncipherment:!0},{name:"subjectAltName",altNames:[{type:6,value:"https://myuri.com/webid#me"}]}]),i.sign(r.privateKey),n[e]={cert:t.pki.certificateToPem(i),privateKey:t.pki.privateKeyToPem(r.privateKey)}}var r={},i={};s("server",i),s("client",i),i.client.connection={},i.server.connection={},r.client=t.tls.createConnection({server:!1,caStore:[i.server.cert],sessionCache:{},cipherSuites:[t.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,t.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],virtualHost:"server",verify:function(e,t,n,r){return i.client.connection.commonName=r[0].subject.getField("CN").value,i.client.connection.certVerified=t,!0},connected:function(e){e.prepare("Hello Server")},getCertificate:function(e,t){return i.client.cert},getPrivateKey:function(e,t){return i.client.privateKey},tlsDataReady:function(e){r.server.process(e.tlsData.getBytes())},dataReady:function(e){i.client.connection.data=e.data.getBytes(),e.close()},closed:function(t){e.equal(i.client.connection.commonName,"server"),e.equal(i.client.connection.certVerified,!0),e.equal(i.client.connection.data,"Hello Client"),n()},error:function(t,n){e.equal(n.message,undefined)}}),r.server=t.tls.createConnection({server:!0,caStore:[i.client.cert],sessionCache:{},cipherSuites:[t.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,t.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],connected:function(e){},verifyClient:!0,verify:function(e,t,n,r){return i.server.connection.commonName=r[0].subject.getField("CN").value,i.server.connection.certVerified=t,!0},getCertificate:function(e,t){return i.server.connection.certHint=t[0],i.server.cert},getPrivateKey:function(e,t){return i.server.privateKey},tlsDataReady:function(e){r.client.process(e.tlsData.getBytes())},dataReady:function(e){i.server.connection.data=e.data.getBytes(),e.prepare("Hello Client"),e.close()},closed:function(t){e.equal(i.server.connection.certHint,"server"),e.equal(i.server.connection.commonName,"client"),e.equal(i.server.connection.certVerified,!0),e.equal(i.server.connection.data,"Hello Server")},error:function(t,n){e.equal(n.message,undefined)}}),r.client.handshake()})})}typeof define=="function"?define("test/tls",["forge/forge"],function(t){e(ASSERT,t)}):typeof module=="object"&&module.exports&&e(require("assert"),require("../../js/forge"))}();var ASSERT=chai.assert;mocha.setup({ui:"bdd"}),requirejs.config({paths:{forge:"forge",test:"test"}}),requirejs(["test/util","test/md5","test/sha1","test/sha256","test/hmac","test/pbkdf2","test/mgf1","test/random","test/asn1","test/pem","test/rsa","test/pkcs1","test/x509","test/csr","test/aes","test/rc2","test/des","test/pkcs7","test/tls"],function(){mocha.run()}),define("ui/test.js",function(){});
\ No newline at end of file
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/package.json b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/package.json
new file mode 100644
index 0000000..9de0a11
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/package.json
@@ -0,0 +1,113 @@
+{
+  "name": "node-forge",
+  "version": "0.6.21",
+  "description": "JavaScript implementations of network transports, cryptography, ciphers, PKI, message digests, and various utilities.",
+  "homepage": "http://github.com/digitalbazaar/forge",
+  "author": {
+    "name": "Digital Bazaar, Inc.",
+    "email": "support@digitalbazaar.com",
+    "url": "http://digitalbazaar.com/"
+  },
+  "contributors": [
+    {
+      "name": "Dave Longley",
+      "email": "dlongley@digitalbazaar.com"
+    },
+    {
+      "name": "Stefan Siegl",
+      "email": "stesie@brokenpipe.de"
+    },
+    {
+      "name": "Christoph Dorn",
+      "email": "christoph@christophdorn.com"
+    }
+  ],
+  "devDependencies": {
+    "almond": "~0.2.6",
+    "jscs": "^1.8.1",
+    "requirejs": "~2.1.8"
+  },
+  "repository": {
+    "type": "git",
+    "url": "http://github.com/digitalbazaar/forge"
+  },
+  "bugs": {
+    "url": "https://github.com/digitalbazaar/forge/issues",
+    "email": "support@digitalbazaar.com"
+  },
+  "licenses": [
+    {
+      "type": "BSD",
+      "url": "https://github.com/digitalbazaar/forge/raw/master/LICENSE"
+    }
+  ],
+  "main": "js/forge.js",
+  "engines": {
+    "node": "*"
+  },
+  "keywords": [
+    "aes",
+    "asn",
+    "asn.1",
+    "cbc",
+    "crypto",
+    "cryptography",
+    "csr",
+    "des",
+    "gcm",
+    "hmac",
+    "http",
+    "https",
+    "md5",
+    "network",
+    "pkcs",
+    "pki",
+    "prng",
+    "rc2",
+    "rsa",
+    "sha1",
+    "sha256",
+    "sha384",
+    "sha512",
+    "ssh",
+    "tls",
+    "x.509",
+    "x509"
+  ],
+  "scripts": {
+    "bundle": "r.js -o minify.js optimize=none out=js/forge.bundle.js",
+    "minify": "r.js -o minify.js",
+    "jscs": "jscs *.js js/*.js minify.js nodejs/*.js nodejs/test/*.js nodejs/ui/*.js tests/*.js",
+    "jshint": "jshint *.js js/*.js minify.js nodejs/*.js nodejs/test/*.js nodejs/ui/*.js tests/*.js"
+  },
+  "gitHead": "875d46d0ef05f1b2a8a93b533a882c3a862f7fcf",
+  "_id": "node-forge@0.6.21",
+  "_shasum": "7dadde911be009c7aae9150e780aea21d4f8bd09",
+  "_from": "node-forge@^0.6.12",
+  "_npmVersion": "1.4.28",
+  "_npmUser": {
+    "name": "dlongley",
+    "email": "dlongley@digitalbazaar.com"
+  },
+  "maintainers": [
+    {
+      "name": "davidlehn",
+      "email": "dil@lehn.org"
+    },
+    {
+      "name": "dlongley",
+      "email": "dlongley@digitalbazaar.com"
+    },
+    {
+      "name": "msporny",
+      "email": "msporny@digitalbazaar.com"
+    }
+  ],
+  "dist": {
+    "shasum": "7dadde911be009c7aae9150e780aea21d4f8bd09",
+    "tarball": "http://registry.npmjs.org/node-forge/-/node-forge-0.6.21.tgz"
+  },
+  "directories": {},
+  "_resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.6.21.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/setup/configure.ac b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/setup/configure.ac
new file mode 100644
index 0000000..0d94441
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/setup/configure.ac
@@ -0,0 +1,202 @@
+# Configure script for Digital Bazaar Bitmunk product line
+# Usage: Run ./configure once 
+# Author: Manu Sporny
+
+AC_PREREQ([2.60])
+AC_INIT([forge],[0.1.0],[support@digitalbazaar.com])
+AC_CONFIG_AUX_DIR(setup)
+
+# Setup standard build environment variables
+# FIXME: allow changing via configure option
+FORGE_DIR=`(cd ${srcdir} && pwd)`
+AC_SUBST(FORGE_DIR)
+DATE_YMD=`date +%Y%m%d`
+PACKAGE_DATE_VERSION=${PACKAGE_VERSION}-${DATE_YMD}
+AC_SUBST(DATE_RFC_2822)
+AC_SUBST(PACKAGE_DATE_VERSION)
+
+dnl ----------------- docs -----------------
+
+AC_ARG_ENABLE([docs],
+   AS_HELP_STRING([--enable-docs], [build documentation [no]]),
+   [ 
+      case "${enableval}" in
+         yes) BUILD_DOCS=yes ;;
+         no) BUILD_DOCS=no ;;
+         *) AC_MSG_ERROR(bad value ${enableval} for --enable-docs) ;;
+      esac
+   ], [BUILD_DOCS=no]) dnl Default value
+
+AC_SUBST(BUILD_DOCS)
+
+dnl ----------------- tests -----------------
+
+AC_ARG_ENABLE([tests],
+   AC_HELP_STRING([--disable-tests], [disable building test apps [no]]),
+   [
+   case "${enableval}" in
+      yes) BUILD_TESTS=yes ;;
+      no)  BUILD_TESTS=no ;;
+      *)   AC_MSG_ERROR(bad value ${enableval} for --disable-tests) ;;
+   esac
+   ], [BUILD_TESTS=no]) dnl Default value
+
+AC_SUBST(BUILD_TESTS)
+
+dnl ----------------- build flash -----------------
+
+AC_ARG_ENABLE([flash],
+   AC_HELP_STRING([--disable-flash], [disable building Flash [no]]),
+   [
+   case "${enableval}" in
+      yes) BUILD_FLASH=yes ;;
+      no)  BUILD_FLASH=no ;;
+      *)   AC_MSG_ERROR(bad value ${enableval} for --disable-flash) ;;
+   esac
+   ], [BUILD_FLASH=yes]) dnl Default value
+
+AC_ARG_ENABLE([pre-built-flash],
+   AC_HELP_STRING([--disable-pre-built-flash],
+      [disable use of pre-built Flash [no]]),
+   [
+   case "${enableval}" in
+      yes) USE_PRE_BUILT_FLASH=yes ;;
+      no)  USE_PRE_BUILT_FLASH=no ;;
+      *)   AC_MSG_ERROR(bad value ${enableval} for --disable-flash) ;;
+   esac
+   ], [USE_PRE_BUILT_FLASH=yes]) dnl Default value
+
+AC_SUBST(BUILD_FLASH)
+AC_SUBST(USE_PRE_BUILT_FLASH)
+
+dnl ----------------- mxmlc -----------------
+
+AC_ARG_WITH([mxmlc],
+   AC_HELP_STRING([--with-mxmlc=PATH],
+      [use PATH for mxmlc]),
+   [
+      case "${withval}" in
+         yes|no) AC_MSG_ERROR(bad value ${withval} for --with-mxmlc) ;;
+         *)      MXMLC="${withval}" ;;
+      esac
+      if test "x$MXMLC" = x -o ! -x "$MXMLC"; then
+         AC_MSG_ERROR([mxmlc not found at "$MXMLC"])
+      fi
+   ])
+
+if test "$BUILD_FLASH" = "yes" ; then
+   dnl Need to try to find mxmlc
+   if test "x$MXMLC" = x; then
+      AC_CHECK_PROGS(MXMLC, mxmlc /usr/lib/flex3/bin/mxmlc,, $PATH /)
+   fi
+   dnl Check that mxmlc was found
+   if test "x$MXMLC" = x; then
+      if test "$USE_PRE_BUILT_FLASH" = "yes"; then
+         dnl Check pre-built SWF is present
+         if test -r "$FORGE_DIR/swf/SocketPool.swf"; then
+            AC_MSG_NOTICE([Using pre-built Flash])
+            BUILD_FLASH=no
+         else
+            AC_MSG_ERROR([mxmlc and pre-built Flash not found])
+         fi
+      else
+         AC_MSG_ERROR([mxmlc not found, try --with-mxmlc])
+      fi
+   fi
+fi
+
+AC_SUBST(MXMLC)
+
+dnl ----------------- mxmlc debug -----------------
+
+AC_ARG_ENABLE([mxmlc-debug],
+   AC_HELP_STRING([--enable-mxmlc-debug], [enable mxmlc debug mode [no]]),
+   [
+   case "${enableval}" in
+      yes) MXMLC_DEBUG_MODE=yes ;;
+      no)  MXMLC_DEBUG_MODE=no ;;
+      *)   AC_MSG_ERROR(bad value ${enableval} for --enable-mxmlc-debug) ;;
+   esac
+   ], [MXMLC_DEBUG_MODE=no]) dnl Default value
+
+AC_SUBST(MXMLC_DEBUG_MODE)
+
+dnl ----------------- end of options -----------------
+
+echo -e "\n--------- Configuring Build Environment -----------"
+
+PKG_PROG_PKG_CONFIG
+
+# Checking for standard build tools
+AC_PROG_CPP
+AC_PROG_INSTALL
+AS_PATH_PYTHON([2.7])
+
+if test "x$PYTHON" != x; then
+   save_CPPFLAGS="$CPPFLAGS"
+   AC_CHECK_PROGS([PYTHON_CONFIG], [python-config]) 
+   if test "x$PYTHON_CONFIG" != x; then
+      CPPFLAGS="$CPPFLAGS `$PYTHON_CONFIG --cflags`"
+   fi
+   AC_CHECK_HEADERS([Python.h],
+      [BUILD_PYTHON_MODULES=yes],
+      [BUILD_PYTHON_MODULES=no
+      AC_MSG_WARN([Python.h not found, SSL bindings will not be build.])])
+   CPPFLAGS="$save_CPPFLAGS"
+else
+   AC_MSG_WARN([Need at least Python 2.7 to build SSL bindings.])
+fi
+
+AC_SUBST(BUILD_PYTHON_MODULES)
+
+dnl ----------------------------------
+
+dnl NOTE:
+dnl This code was used previously to autogenerate the .gitignore file but due
+dnl to the current more common use of just the js files, it's likely people
+dnl who checkout the code will never run the build scripts. The files are now
+dnl just hardcoded into .gitignore and should be updated by hand as needed.
+dnl
+dnl # Generating files
+dnl AC_CONFIG_FILES([
+dnl    .gitignore
+dnl    Makefile
+dnl ])
+dnl
+dnl # add newlines to internal output file list
+dnl CONFIGURE_GENERATED_FILES="`echo $ac_config_files | tr ' ' '\n'`"
+dnl AC_SUBST(CONFIGURE_GENERATED_FILES)
+
+AC_OUTPUT
+
+# Dump the build configuration
+
+echo -e "\n--------- Forge Build Environment -----------"
+echo "Forge Version     : $PACKAGE_NAME $PACKAGE_DATE_VERSION"
+
+if test "x$BUILD_FLASH" = "xyes" ; then
+   echo "Adobe Flash       : Flash building enabled"
+   echo "MXMLC             : $MXMLC"
+   echo "MXMLC Debug flags : $MXMLC_DEBUG_MODE"
+else
+   echo "Adobe Flash       : using pre-built Flash"
+fi
+
+if test "x$BUILD_PYTHON_MODULES" = "xyes" ; then
+   echo "Python            : $PYTHON (version $PYTHON_VERSION)"
+else
+   echo "Python            : development environment not found"
+fi
+
+if test "x$BUILD_DOCS" = "xyes"; then
+   echo "Documentation     : enabled"
+else
+   echo "Documentation     : disabled (use --enable-docs to enable)"
+fi
+
+if test "x$BUILD_TESTS" = "xyes"; then
+   echo "Tests             : enabled"
+else
+   echo "Tests             : disabled (use --enable-tests to enable)"
+fi
+
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/setup/install-sh b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/setup/install-sh
new file mode 100755
index 0000000..d4744f0
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/setup/install-sh
@@ -0,0 +1,269 @@
+#!/bin/sh
+#
+# install - install a program, script, or datafile
+#
+# This originates from X11R5 (mit/util/scripts/install.sh), which was
+# later released in X11R6 (xc/config/util/install.sh) with the
+# following copyright and license.
+#
+# Copyright (C) 1994 X Consortium
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC-
+# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+# Except as contained in this notice, the name of the X Consortium shall not
+# be used in advertising or otherwise to promote the sale, use or other deal-
+# ings in this Software without prior written authorization from the X Consor-
+# tium.
+#
+#
+# FSF changes to this file are in the public domain.
+#
+# Calling this script install-sh is preferred over install.sh, to prevent
+# `make' implicit rules from creating a file called install from it
+# when there is no Makefile.
+#
+# This script is compatible with the BSD install script, but was written
+# from scratch.  It can only install one file at a time, a restriction
+# shared with many OS's install programs.
+
+
+# set DOITPROG to echo to test this script
+
+# Don't use :- since 4.3BSD and earlier shells don't like it.
+doit="${DOITPROG-}"
+
+
+# put in absolute paths if you don't have them in your path; or use env. vars.
+
+mvprog="${MVPROG-mv}"
+cpprog="${CPPROG-cp}"
+chmodprog="${CHMODPROG-chmod}"
+chownprog="${CHOWNPROG-chown}"
+chgrpprog="${CHGRPPROG-chgrp}"
+stripprog="${STRIPPROG-strip}"
+rmprog="${RMPROG-rm}"
+mkdirprog="${MKDIRPROG-mkdir}"
+
+transformbasename=""
+transform_arg=""
+instcmd="$mvprog"
+chmodcmd="$chmodprog 0755"
+chowncmd=""
+chgrpcmd=""
+stripcmd=""
+rmcmd="$rmprog -f"
+mvcmd="$mvprog"
+src=""
+dst=""
+dir_arg=""
+
+while [ x"$1" != x ]; do
+    case $1 in
+	-c) instcmd="$cpprog"
+	    shift
+	    continue;;
+
+	-d) dir_arg=true
+	    shift
+	    continue;;
+
+	-m) chmodcmd="$chmodprog $2"
+	    shift
+	    shift
+	    continue;;
+
+	-o) chowncmd="$chownprog $2"
+	    shift
+	    shift
+	    continue;;
+
+	-g) chgrpcmd="$chgrpprog $2"
+	    shift
+	    shift
+	    continue;;
+
+	-s) stripcmd="$stripprog"
+	    shift
+	    continue;;
+
+	-t=*) transformarg=`echo $1 | sed 's/-t=//'`
+	    shift
+	    continue;;
+
+	-b=*) transformbasename=`echo $1 | sed 's/-b=//'`
+	    shift
+	    continue;;
+
+	*)  if [ x"$src" = x ]
+	    then
+		src=$1
+	    else
+		# this colon is to work around a 386BSD /bin/sh bug
+		:
+		dst=$1
+	    fi
+	    shift
+	    continue;;
+    esac
+done
+
+if [ x"$src" = x ]
+then
+	echo "install:	no input file specified"
+	exit 1
+else
+	true
+fi
+
+if [ x"$dir_arg" != x ]; then
+	dst=$src
+	src=""
+	
+	if [ -d $dst ]; then
+		instcmd=:
+		chmodcmd=""
+	else
+		instcmd=mkdir
+	fi
+else
+
+# Waiting for this to be detected by the "$instcmd $src $dsttmp" command
+# might cause directories to be created, which would be especially bad 
+# if $src (and thus $dsttmp) contains '*'.
+
+	if [ -f $src -o -d $src ]
+	then
+		true
+	else
+		echo "install:  $src does not exist"
+		exit 1
+	fi
+	
+	if [ x"$dst" = x ]
+	then
+		echo "install:	no destination specified"
+		exit 1
+	else
+		true
+	fi
+
+# If destination is a directory, append the input filename; if your system
+# does not like double slashes in filenames, you may need to add some logic
+
+	if [ -d $dst ]
+	then
+		dst="$dst"/`basename $src`
+	else
+		true
+	fi
+fi
+
+## this sed command emulates the dirname command
+dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'`
+
+# Make sure that the destination directory exists.
+#  this part is taken from Noah Friedman's mkinstalldirs script
+
+# Skip lots of stat calls in the usual case.
+if [ ! -d "$dstdir" ]; then
+defaultIFS=' 	
+'
+IFS="${IFS-${defaultIFS}}"
+
+oIFS="${IFS}"
+# Some sh's can't handle IFS=/ for some reason.
+IFS='%'
+set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'`
+IFS="${oIFS}"
+
+pathcomp=''
+
+while [ $# -ne 0 ] ; do
+	pathcomp="${pathcomp}${1}"
+	shift
+
+	if [ ! -d "${pathcomp}" ] ;
+        then
+		$mkdirprog "${pathcomp}"
+	else
+		true
+	fi
+
+	pathcomp="${pathcomp}/"
+done
+fi
+
+if [ x"$dir_arg" != x ]
+then
+	$doit $instcmd $dst &&
+
+	if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else true ; fi &&
+	if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else true ; fi &&
+	if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else true ; fi &&
+	if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else true ; fi
+else
+
+# If we're going to rename the final executable, determine the name now.
+
+	if [ x"$transformarg" = x ] 
+	then
+		dstfile=`basename $dst`
+	else
+		dstfile=`basename $dst $transformbasename | 
+			sed $transformarg`$transformbasename
+	fi
+
+# don't allow the sed command to completely eliminate the filename
+
+	if [ x"$dstfile" = x ] 
+	then
+		dstfile=`basename $dst`
+	else
+		true
+	fi
+
+# Make a temp file name in the proper directory.
+
+	dsttmp=$dstdir/#inst.$$#
+
+# Move or copy the file name to the temp name
+
+	$doit $instcmd $src $dsttmp &&
+
+	trap "rm -f ${dsttmp}" 0 &&
+
+# and set any options; do chmod last to preserve setuid bits
+
+# If any of these fail, we abort the whole thing.  If we want to
+# ignore errors from any of these, just make sure not to ignore
+# errors from the above "$doit $instcmd $src $dsttmp" command.
+
+	if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else true;fi &&
+	if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else true;fi &&
+	if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else true;fi &&
+	if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else true;fi &&
+
+# Now rename the file to the real destination.
+
+	$doit $rmcmd -f $dstdir/$dstfile &&
+	$doit $mvcmd $dsttmp $dstdir/$dstfile 
+
+fi &&
+
+
+exit 0
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/setup/m4/as-python.m4 b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/setup/m4/as-python.m4
new file mode 100644
index 0000000..84d4e36
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/setup/m4/as-python.m4
@@ -0,0 +1,156 @@
+## ------------------------
+## Python file handling
+## From Andrew Dalke
+## Updated by James Henstridge
+## Updated by Andy Wingo to loop through possible pythons
+## ------------------------
+
+# AS_PATH_PYTHON([MINIMUM-VERSION])
+
+# Adds support for distributing Python modules and packages.  To
+# install modules, copy them to $(pythondir), using the python_PYTHON
+# automake variable.  To install a package with the same name as the
+# automake package, install to $(pkgpythondir), or use the
+# pkgpython_PYTHON automake variable.
+
+# The variables $(pyexecdir) and $(pkgpyexecdir) are provided as
+# locations to install python extension modules (shared libraries).
+# Another macro is required to find the appropriate flags to compile
+# extension modules.
+
+# If your package is configured with a different prefix to python,
+# users will have to add the install directory to the PYTHONPATH
+# environment variable, or create a .pth file (see the python
+# documentation for details).
+
+# If the MINIMUM-VERSION argument is passed, AS_PATH_PYTHON will
+# cause an error if the version of python installed on the system
+# doesn't meet the requirement.  MINIMUM-VERSION should consist of
+# numbers and dots only.
+
+# Updated to loop over all possible python binaries by Andy Wingo
+# <wingo@pobox.com>
+# Updated to only warn and unset PYTHON if no good one is found
+
+AC_DEFUN([AS_PATH_PYTHON],
+ [
+  dnl Find a version of Python.  I could check for python versions 1.4
+  dnl or earlier, but the default installation locations changed from
+  dnl $prefix/lib/site-python in 1.4 to $prefix/lib/python1.5/site-packages
+  dnl in 1.5, and I don't want to maintain that logic.
+
+  dnl should we do the version check?
+  PYTHON_CANDIDATES="$PYTHON python python2 \
+                     python2.7 python2.6 pyton2.5 python2.4 python2.3 \
+                     python2.2 python2.1 python2.0 \
+                     python1.6 python1.5"
+  dnl Declare PYTHON as a special var
+  AC_ARG_VAR([PYTHON], [path to Python interpreter])
+  ifelse([$1],[],
+         [AC_PATH_PROG(PYTHON, $PYTHON_CANDIDATES)],
+         [
+     AC_MSG_NOTICE(Looking for Python version >= $1)
+    changequote(<<, >>)dnl
+    prog="
+import sys, string
+minver = '$1'
+# split string by '.' and convert to numeric
+minver_info = map(string.atoi, string.split(minver, '.'))
+# we can now do comparisons on the two lists:
+if sys.version_info >= tuple(minver_info):
+    sys.exit(0)
+else:
+    sys.exit(1)"
+    changequote([, ])dnl
+
+    python_good=false
+    for python_candidate in $PYTHON_CANDIDATES; do
+      unset PYTHON
+      AC_PATH_PROG(PYTHON, $python_candidate) 1> /dev/null 2> /dev/null
+
+      if test "x$PYTHON" = "x"; then continue; fi
+
+      if $PYTHON -c "$prog" 1>&AC_FD_CC 2>&AC_FD_CC; then
+        AC_MSG_CHECKING(["$PYTHON":])
+        AC_MSG_RESULT([okay])
+        python_good=true
+        break;
+      else
+        dnl clear the cache val
+        unset ac_cv_path_PYTHON
+      fi
+    done
+  ])
+
+  if test "$python_good" != "true"; then
+    AC_MSG_WARN([No suitable version of python found])
+    PYTHON=
+  else
+
+  AC_MSG_CHECKING([local Python configuration])
+
+  dnl Query Python for its version number.  Getting [:3] seems to be
+  dnl the best way to do this; it's what "site.py" does in the standard
+  dnl library.  Need to change quote character because of [:3]
+
+  AC_SUBST(PYTHON_VERSION)
+  changequote(<<, >>)dnl
+  PYTHON_VERSION=`$PYTHON -c "import sys; print sys.version[:3]"`
+  changequote([, ])dnl
+
+
+  dnl Use the values of $prefix and $exec_prefix for the corresponding
+  dnl values of PYTHON_PREFIX and PYTHON_EXEC_PREFIX.  These are made
+  dnl distinct variables so they can be overridden if need be.  However,
+  dnl general consensus is that you shouldn't need this ability.
+
+  AC_SUBST(PYTHON_PREFIX)
+  PYTHON_PREFIX='${prefix}'
+
+  AC_SUBST(PYTHON_EXEC_PREFIX)
+  PYTHON_EXEC_PREFIX='${exec_prefix}'
+
+  dnl At times (like when building shared libraries) you may want
+  dnl to know which OS platform Python thinks this is.
+
+  AC_SUBST(PYTHON_PLATFORM)
+  PYTHON_PLATFORM=`$PYTHON -c "import sys; print sys.platform"`
+
+
+  dnl Set up 4 directories:
+
+  dnl pythondir -- where to install python scripts.  This is the
+  dnl   site-packages directory, not the python standard library
+  dnl   directory like in previous automake betas.  This behaviour
+  dnl   is more consistent with lispdir.m4 for example.
+  dnl
+  dnl Also, if the package prefix isn't the same as python's prefix,
+  dnl then the old $(pythondir) was pretty useless.
+
+  AC_SUBST(pythondir)
+  pythondir=$PYTHON_PREFIX"/lib/python"$PYTHON_VERSION/site-packages
+
+  dnl pkgpythondir -- $PACKAGE directory under pythondir.  Was
+  dnl   PYTHON_SITE_PACKAGE in previous betas, but this naming is
+  dnl   more consistent with the rest of automake.
+  dnl   Maybe this should be put in python.am?
+
+  AC_SUBST(pkgpythondir)
+  pkgpythondir=\${pythondir}/$PACKAGE
+
+  dnl pyexecdir -- directory for installing python extension modules
+  dnl   (shared libraries)  Was PYTHON_SITE_EXEC in previous betas.
+
+  AC_SUBST(pyexecdir)
+  pyexecdir=$PYTHON_EXEC_PREFIX"/lib/python"$PYTHON_VERSION/site-packages
+
+  dnl pkgpyexecdir -- $(pyexecdir)/$(PACKAGE)
+  dnl   Maybe this should be put in python.am?
+
+  AC_SUBST(pkgpyexecdir)
+  pkgpyexecdir=\${pyexecdir}/$PACKAGE
+
+  AC_MSG_RESULT([looks good])
+
+  fi
+])
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/start.frag b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/start.frag
new file mode 100644
index 0000000..dad9d0f
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/start.frag
@@ -0,0 +1,7 @@
+(function(root, factory) {
+  if(typeof define === 'function' && define.amd) {
+    define([], factory);
+  } else {
+    root.forge = factory();
+  }
+})(this, function() {
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/aes-speed.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/aes-speed.js
new file mode 100644
index 0000000..6c9922a
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/aes-speed.js
@@ -0,0 +1,54 @@
+var forge = require('../js/forge');
+
+aes_128('GCM');
+
+function aes_128(mode) {
+  var size = 4096;
+  var key = forge.random.getBytes(16);
+  var iv = forge.random.getBytes(mode === 'GCM' ? 12 : 16);
+  var plain = forge.util.createBuffer().fillWithByte(0, size);
+
+  // run for 5 seconds
+  var start = Date.now();
+
+  var now;
+  var totalEncrypt = 0;
+  var totalDecrypt = 0;
+  var count = 0;
+  var passed = 0;
+  while(passed < 5000) {
+    var input = forge.util.createBuffer(plain);
+
+    // encrypt, only measuring update() and finish()
+    var cipher = forge.aes.createEncryptionCipher(key, mode);
+    cipher.start(iv);
+    now = Date.now();
+    cipher.update(input);
+    cipher.finish();
+    totalEncrypt += Date.now() - now;
+
+    var ciphertext = cipher.output;
+    var tag = cipher.tag;
+
+    // decrypt, only measuring update() and finish()
+    cipher = forge.aes.createDecryptionCipher(key, mode);
+    cipher.start(iv, {tag: tag});
+    now = Date.now();
+    cipher.update(ciphertext);
+    if(!cipher.finish()) {
+      throw new Error('Decryption error.');
+    }
+    totalDecrypt += Date.now() - now;
+
+    ++count;
+    passed = Date.now() - start;
+  }
+
+  count = count * size / 1000;
+  totalEncrypt /= 1000;
+  totalDecrypt /= 1000;
+
+  console.log('times in 1000s of bytes/sec processed.');
+  console.log('encrypt: ' + (count / totalEncrypt) + ' k/sec');
+  console.log('decrypt: ' + (count / totalDecrypt) + ' k/sec');
+}
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/common.html b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/common.html
new file mode 100644
index 0000000..0fb4705
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/common.html
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+   <head>
+      <title>Forge Common Test</title>
+      <link type="text/css" rel="stylesheet" media="all" href="screen.css" />
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
+      <script type="text/javascript" src="forge/debug.js"></script>
+      <script type="text/javascript" src="forge/util.js"></script>
+      <script type="text/javascript" src="forge/log.js"></script>
+      <script type="text/javascript" src="forge/task.js"></script>
+      <script type="text/javascript" src="forge/md5.js"></script>
+      <script type="text/javascript" src="forge/sha1.js"></script>
+      <script type="text/javascript" src="forge/sha256.js"></script>
+      <script type="text/javascript" src="forge/hmac.js"></script>
+      <script type="text/javascript" src="forge/pbkdf2.js"></script>
+      <script type="text/javascript" src="forge/aes.js"></script>
+      <script type="text/javascript" src="forge/pem.js"></script>
+      <script type="text/javascript" src="forge/asn1.js"></script>
+      <script type="text/javascript" src="forge/jsbn.js"></script>
+      <script type="text/javascript" src="forge/prng.js"></script>
+      <script type="text/javascript" src="forge/random.js"></script>
+      <script type="text/javascript" src="forge/oids.js"></script>
+      <script type="text/javascript" src="forge/rsa.js"></script>
+      <script type="text/javascript" src="forge/pbe.js"></script>
+      <script type="text/javascript" src="forge/x509.js"></script>
+      <script type="text/javascript" src="forge/pki.js"></script>
+      <script type="text/javascript" src="forge/tls.js"></script>
+      <script type="text/javascript" src="forge/aesCipherSuites.js"></script>
+      <script type="text/javascript" src="common.js"></script>
+
+      <link type="text/css" rel="stylesheet" media="all" href="screen.css" />
+      <style type="text/css">
+         .ready { color: inherit; background: inherit; }
+         .testing { color: black; background: yellow; }
+         .pass{ color: white; background: green; }
+         .fail{ color: white; background: red; }
+      </style>
+   </head>
+<body>
+<div class="nav"><a href="index.html">Forge Tests</a> / Common</div>
+
+<div class="header">
+   <h1>Common Tests</h1>
+</div>
+
+<div class="content">
+
+<fieldset class="section">
+   <ul>
+      <li>Test various Forge components.</li>
+      <li>See JavaScript console for more detailed output.</li>
+   </ul>
+</fieldset>
+
+<fieldset class="section">
+<legend>Control</legend>
+   <button id="start">Start</button>
+   <button id="reset">Reset</button>
+   <br/>
+   <input id="scroll" type="checkbox" />Scroll Tests
+   <br/>
+   <button id="keygen">Generate RSA key pair</button>
+   <button id="certgen">Generate RSA certificate</button>
+   <input id="bits" value="1024"/>bits
+</fieldset>
+
+<fieldset class="section">
+<legend>Progress</legend>
+Status: <span id="status">?</span><br/>
+Pass: <span id="pass">?</span>/<span id="total">?</span><br/>
+Fail: <span id="fail">?</span>
+</fieldset>
+
+<fieldset class="section">
+<legend>Tests</legend>
+<div id="tests"></div>
+</fieldset>
+
+</div>
+
+</body>
+</html>
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/common.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/common.js
new file mode 100644
index 0000000..57dfbc4
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/common.js
@@ -0,0 +1,2199 @@
+/**
+ * Forge Common Tests
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2009-2012 Digital Bazaar, Inc. All rights reserved.
+ */
+jQuery(function($)
+{
+   // logging category
+   var cat = 'forge.tests.common';
+
+   // local alias
+   var forge = window.forge;
+
+   var tests = [];
+   var passed = 0;
+   var failed = 0;
+
+   var init = function()
+   {
+      passed = failed = 0;
+      $('.ready,.testing,.pass,.fail')
+         .removeClass('ready testing pass fail');
+      $('#status')
+         .text('Ready.')
+         .addClass('ready');
+      $('#total').text(tests.length);
+      $('#pass').text(passed);
+      $('#fail').text(failed);
+      $('.expect').empty();
+      $('.result').empty();
+      $('.time').empty();
+      $('.timePer').empty();
+      $('#start').attr('disabled', '');
+   };
+
+   var start = function()
+   {
+      $('#start').attr('disabled', 'true');
+      // meta! use tasks to run the task tests
+      forge.task.start({
+         type: 'test',
+         run: function(task) {
+            task.next('starting', function(task) {
+               forge.log.debug(cat, 'start');
+               $('#status')
+                  .text('Testing...')
+                  .addClass('testing')
+                  .removeClass('idle');
+            });
+            $.each(tests, function(i, test) {
+               task.next('test', function(task) {
+                  var title = $('li:first', test.container);
+                  if($('#scroll:checked').length === 1)
+                  {
+                     $('html,body').animate({scrollTop: title.offset().top});
+                  }
+                  title.addClass('testing');
+                  test.run(task, test);
+               });
+               task.next('test', function(task) {
+                  $('li:first', test.container).removeClass('testing');
+               });
+            });
+            task.next('success', function(task) {
+               forge.log.debug(cat, 'done');
+               if(failed === 0) {
+                  $('#status')
+                     .text('PASS')
+                     .addClass('pass')
+                     .removeClass('testing');
+               } else {
+                  // FIXME: should just be hitting failure() below
+                  $('#status')
+                     .text('FAIL')
+                     .addClass('fail')
+                     .removeClass('testing');
+               }
+            });
+         },
+         failure: function() {
+            $('#status')
+               .text('FAIL')
+               .addClass('fail')
+               .removeClass('testing');
+         }
+      });
+   };
+
+   $('#start').click(function() {
+      start();
+   });
+
+   $('#reset').click(function() {
+      init();
+   });
+
+   $('#keygen').click(function() {
+      var bits = $('#bits')[0].value;
+      var keys = forge.pki.rsa.generateKeyPair(bits);
+      forge.log.debug(cat, 'generating ' + bits + '-bit RSA key-pair...');
+      setTimeout(function()
+      {
+         forge.log.debug(cat, 'private key:', keys.privateKey);
+         forge.log.debug(cat, forge.pki.privateKeyToPem(keys.privateKey));
+         forge.log.debug(cat, 'public key:', keys.publicKey);
+         forge.log.debug(cat, forge.pki.publicKeyToPem(keys.publicKey));
+
+         forge.log.debug(cat, 'testing sign/verify...');
+         setTimeout(function()
+         {
+            // do sign/verify test
+            try
+            {
+               var md = forge.md.sha1.create();
+               md.update('foo');
+               var signature = keys.privateKey.sign(md);
+               keys.publicKey.verify(md.digest().getBytes(), signature);
+               forge.log.debug(cat, 'sign/verify success');
+            }
+            catch(ex)
+            {
+               forge.log.error(cat, 'sign/verify failure', ex);
+            }
+         }, 0);
+      }, 0);
+   });
+
+   $('#certgen').click(function() {
+      var bits = $('#bits')[0].value;
+      forge.log.debug(cat, 'generating ' + bits +
+         '-bit RSA key-pair and certificate...');
+      setTimeout(function()
+      {
+         try
+         {
+            var keys = forge.pki.rsa.generateKeyPair(bits);
+            var cert = forge.pki.createCertificate();
+            cert.serialNumber = '01';
+            cert.validity.notBefore = new Date();
+            cert.validity.notAfter = new Date();
+            cert.validity.notAfter.setFullYear(
+               cert.validity.notBefore.getFullYear() + 1);
+            var attrs = [{
+               name: 'commonName',
+               value: 'mycert'
+            }, {
+               name: 'countryName',
+               value: 'US'
+            }, {
+               shortName: 'ST',
+               value: 'Virginia'
+            }, {
+               name: 'localityName',
+               value: 'Blacksburg'
+            }, {
+               name: 'organizationName',
+               value: 'Test'
+            }, {
+               shortName: 'OU',
+               value: 'Test'
+            }];
+            cert.setSubject(attrs);
+            cert.setIssuer(attrs);
+            cert.setExtensions([{
+               name: 'basicConstraints',
+               cA: true
+            }, {
+               name: 'keyUsage',
+               keyCertSign: true
+            }, {
+               name: 'subjectAltName',
+               altNames: [{
+                  type: 6, // URI
+                  value: 'http://localhost/dataspace/person/myname#this'
+               }]
+            }]);
+            // FIXME: add subjectKeyIdentifier extension
+            // FIXME: add authorityKeyIdentifier extension
+            cert.publicKey = keys.publicKey;
+
+            // self-sign certificate
+            cert.sign(keys.privateKey);
+
+            forge.log.debug(cat, 'certificate:', cert);
+            //forge.log.debug(cat,
+            //   forge.asn1.prettyPrint(forge.pki.certificateToAsn1(cert)));
+            forge.log.debug(cat, forge.pki.certificateToPem(cert));
+
+            // verify certificate
+            forge.log.debug(cat, 'verified', cert.verify(cert));
+         }
+         catch(ex)
+         {
+            forge.log.error(cat, ex, ex.message ? ex.message : '');
+         }
+      }, 0);
+   });
+
+   var addTest = function(name, run)
+   {
+      var container = $('<ul><li>Test ' + name + '</li><ul/></ul>');
+      var expect = $('<li>Expect: <span class="expect"/></li>');
+      var result = $('<li>Result: <span class="result"/></li>');
+      var time = $('<li>Time: <span class="time"/></li>');
+      var timePer = $('<li>Time Per Iteration: <span class="timePer"/></li>');
+      $('ul', container)
+         .append(expect)
+         .append(result)
+         .append(time)
+         .append(timePer);
+      $('#tests').append(container);
+      var test = {
+         container: container,
+         startTime: null,
+         run: function(task, test) {
+            test.startTime = new Date();
+            run(task, test);
+         },
+         expect: $('span', expect),
+         result: $('span', result),
+         check: function() {
+            var e = test.expect.text();
+            var r = test.result.text();
+            (e == r) ? test.pass() : test.fail();
+         },
+         pass: function(iterations) {
+            var dt = new Date() - test.startTime;
+            if(!iterations)
+            {
+               iterations = 1;
+            }
+            var dti = (dt / iterations);
+            passed += 1;
+            $('#pass').text(passed);
+            $('li:first', container).addClass('pass');
+            $('span.time', container).html(dt + 'ms');
+            $('span.timePer', container).html(dti + 'ms');
+         },
+         fail: function(iterations) {
+            var dt = new Date() - test.startTime;
+            if(!iterations)
+            {
+               iterations = 1;
+            }
+            var dti = (dt / iterations);
+            failed += 1;
+            $('#fail').text(failed);
+            $('li:first', container).addClass('fail');
+            $('span.time', container).html(dt + 'ms');
+            $('span.timePer', container).html(dti + 'ms');
+         }
+      };
+      tests.push(test);
+   };
+
+   addTest('buffer put bytes', function(task, test)
+   {
+      ba = forge.util.createBuffer();
+      ba.putByte(1);
+      ba.putByte(2);
+      ba.putByte(3);
+      ba.putByte(4);
+      ba.putInt32(4);
+      ba.putByte(1);
+      ba.putByte(2);
+      ba.putByte(3);
+      ba.putInt32(4294967295);
+      var hex = ba.toHex();
+      var bytes = [];
+      while(ba.length() > 0)
+      {
+         bytes.push(ba.getByte());
+      }
+      var expect = [1, 2, 3, 4, 0, 0, 0, 4, 1, 2, 3, 255, 255, 255, 255];
+      var exHex = '0102030400000004010203ffffffff';
+      test.expect.html(exHex);
+      test.result.html(hex);
+
+      test.check();
+   });
+
+   addTest('buffer from hex', function(task, test)
+   {
+      var exHex = '0102030400000004010203ffffffff';
+      test.expect.html(exHex);
+
+      var buf = forge.util.createBuffer();
+      buf.putBytes(forge.util.hexToBytes(exHex));
+      test.result.html(buf.toHex());
+
+      test.check();
+   });
+
+   addTest('base64 encode', function(task, test)
+   {
+      var s1 = '00010203050607080A0B0C0D0F1011121415161719';
+      var s2 = 'MDAwMTAyMDMwNTA2MDcwODBBMEIwQzBEMEYxMDExMTIxNDE1MTYxNzE5';
+      test.expect.html(s2);
+
+      var out = forge.util.encode64(s1);
+      test.result.html(out);
+
+      test.check();
+   });
+
+   addTest('base64 decode', function(task, test)
+   {
+      var s1 = '00010203050607080A0B0C0D0F1011121415161719';
+      var s2 = 'MDAwMTAyMDMwNTA2MDcwODBBMEIwQzBEMEYxMDExMTIxNDE1MTYxNzE5';
+      test.expect.html(s1);
+
+      var out = forge.util.decode64(s2);
+      test.result.html(out);
+
+      test.check();
+   });
+
+   addTest('md5 empty', function(task, test)
+   {
+      var expect = 'd41d8cd98f00b204e9800998ecf8427e';
+      test.expect.html(expect);
+      var md = forge.md.md5.create();
+      test.result.html(md.digest().toHex());
+      test.check();
+   });
+
+   addTest('md5 "abc"', function(task, test)
+   {
+      var expect = '900150983cd24fb0d6963f7d28e17f72';
+      test.expect.html(expect);
+      var md = forge.md.md5.create();
+      md.update('abc');
+      test.result.html(md.digest().toHex());
+      test.check();
+   });
+
+   addTest('md5 "The quick brown fox jumps over the lazy dog"',
+      function(task, test)
+   {
+      var expect = '9e107d9d372bb6826bd81d3542a419d6';
+      test.expect.html(expect);
+      var md = forge.md.md5.create();
+      md.start();
+      md.update('The quick brown fox jumps over the lazy dog');
+      test.result.html(md.digest().toHex());
+      test.check();
+   });
+
+   // c'è
+   addTest('md5 "c\'\u00e8"', function(task, test)
+   {
+      var expect = '8ef7c2941d78fe89f31e614437c9db59';
+      test.expect.html(expect);
+      var md = forge.md.md5.create();
+      md.update("c'\u00e8", 'utf8');
+      test.result.html(md.digest().toHex());
+      test.check();
+   });
+
+   addTest('md5 "THIS IS A MESSAGE"',
+   function(task, test)
+   {
+      var expect = '78eebfd9d42958e3f31244f116ab7bbe';
+      test.expect.html(expect);
+      var md = forge.md.md5.create();
+      md.start();
+      md.update('THIS IS ');
+      md.update('A MESSAGE');
+      // do twice to check continuing digest
+      test.result.html(md.digest().toHex());
+      test.result.html(md.digest().toHex());
+      test.check();
+   });
+
+   addTest('md5 long message',
+   function(task, test)
+   {
+      var input = forge.util.createBuffer();
+      input.putBytes(forge.util.hexToBytes(
+         '0100002903018d32e9c6dc423774c4c39a5a1b78f44cc2cab5f676d39' +
+         'f703d29bfa27dfeb870000002002f0100'));
+      input.putBytes(forge.util.hexToBytes(
+         '0200004603014c2c1e835d39da71bc0857eb04c2b50fe90dbb2a8477f' +
+         'e7364598d6f0575999c20a6c7248c5174da6d03ac711888f762fc4ed5' +
+         '4f7254b32273690de849c843073d002f00'));
+      input.putBytes(forge.util.hexToBytes(
+         '0b0003d20003cf0003cc308203c8308202b0a003020102020100300d0' +
+         '6092a864886f70d0101050500308186310b3009060355040613025553' +
+         '311d301b060355040a13144469676974616c2042617a6161722c20496' +
+         'e632e31443042060355040b133b4269746d756e6b206c6f63616c686f' +
+         '73742d6f6e6c7920436572746966696361746573202d20417574686f7' +
+         '2697a6174696f6e207669612042545031123010060355040313096c6f' +
+         '63616c686f7374301e170d3130303231343137303931395a170d32303' +
+         '03231333137303931395a308186310b3009060355040613025553311d' +
+         '301b060355040a13144469676974616c2042617a6161722c20496e632' +
+         'e31443042060355040b133b4269746d756e6b206c6f63616c686f7374' +
+         '2d6f6e6c7920436572746966696361746573202d20417574686f72697' +
+         'a6174696f6e207669612042545031123010060355040313096c6f6361' +
+         '6c686f737430820122300d06092a864886f70d01010105000382010f0' +
+         '03082010a0282010100dc436f17d6909d8a9d6186ea218eb5c86b848b' +
+         'ae02219bd56a71203daf07e81bc19e7e98134136bcb012881864bf03b' +
+         '3774652ad5eab85dba411a5114ffeac09babce75f31314345512cd87c' +
+         '91318b2e77433270a52185fc16f428c3ca412ad6e9484bc2fb87abb4e' +
+         '8fb71bf0f619e31a42340b35967f06c24a741a31c979c0bb8921a90a4' +
+         '7025fbeb8adca576979e70a56830c61170c9647c18c0794d68c0df38f' +
+         '3aac5fc3b530e016ea5659715339f3f3c209cdee9dbe794b5af92530c' +
+         '5754c1d874b78974bfad994e0dfc582275e79feb522f6e4bcc2b2945b' +
+         'aedfb0dbdaebb605f9483ff0bea29ecd5f4d6f2769965d1b3e04f8422' +
+         '716042680011ff676f0203010001a33f303d300c0603551d130101ff0' +
+         '4023000300e0603551d0f0101ff0404030204f0301d0603551d250416' +
+         '301406082b0601050507030106082b06010505070302300d06092a864' +
+         '886f70d010105050003820101009c4562be3f2d8d8e388085a697f2f1' +
+         '06eaeff4992a43f198fe3dcf15c8229cf1043f061a38204f73d86f4fb' +
+         '6348048cc5279ed719873aa10e3773d92b629c2c3fcce04012c81ba3b' +
+         '4ec451e9644ec5191078402d845e05d02c7b4d974b4588276e5037aba' +
+         '7ef26a8bddeb21e10698c82f425e767dc401adf722fa73ab78cfa069b' +
+         'd69052d7ca6a75cc9225550e315d71c5f8764362ea4dbc6ecb837a847' +
+         '1043c5a7f826a71af145a053090bd4bccca6a2c552841cdb1908a8352' +
+         'f49283d2e641acdef667c7543af441a16f8294251e2ac376fa507b53a' +
+         'e418dd038cd20cef1e7bfbf5ae03a7c88d93d843abaabbdc5f3431132' +
+         'f3e559d2dd414c3eda38a210b8'));
+      input.putBytes(forge.util.hexToBytes('0e000000'));
+      input.putBytes(forge.util.hexToBytes(
+         '10000102010026a220b7be857402819b78d81080d01a682599bbd0090' +
+         '2985cc64edf8e520e4111eb0e1729a14ffa3498ca259cc9ad6fc78fa1' +
+         '30d968ebdb78dc0b950c0aa44355f13ba678419185d7e4608fe178ca6' +
+         'b2cef33e4193778d1a70fe4d0dfcb110be4bbb4dbaa712177655728f9' +
+         '14ab4c0f6c4aef79a46b3d996c82b2ebe9ed1748eb5cace7dc44fb67e' +
+         '73f452a047f2ed199b3d50d5db960acf03244dc8efa4fc129faf8b65f' +
+         '9e52e62b5544722bd17d2358e817a777618a4265a3db277fc04851a82' +
+         'a91fe6cdcb8127f156e0b4a5d1f54ce2742eb70c895f5f8b85f5febe6' +
+         '9bc73e891f9280826860a0c2ef94c7935e6215c3c4cd6b0e43e80cca3' +
+         '96d913d36be'));
+
+      var expect = 'd15a2da0e92c3da55dc573f885b6e653';
+      test.expect.html(expect);
+
+      var md = forge.md.md5.create();
+      md.start();
+      md.update(input.getBytes());
+      test.result.html(md.digest().toHex());
+
+      test.check();
+   });
+
+   addTest('sha-1 empty', function(task, test)
+   {
+      var expect = 'da39a3ee5e6b4b0d3255bfef95601890afd80709';
+      test.expect.html(expect);
+      var md = forge.md.sha1.create();
+      test.result.html(md.digest().toHex());
+      test.check();
+   });
+
+   addTest('sha-1 "abc"', function(task, test)
+   {
+      var expect = 'a9993e364706816aba3e25717850c26c9cd0d89d';
+      test.expect.html(expect);
+      var md = forge.md.sha1.create();
+      md.update('abc');
+      test.result.html(md.digest().toHex());
+      test.check();
+   });
+
+   addTest('sha-1 "The quick brown fox jumps over the lazy dog"',
+      function(task, test)
+   {
+      var expect = '2fd4e1c67a2d28fced849ee1bb76e7391b93eb12';
+      test.expect.html(expect);
+      var md = forge.md.sha1.create();
+      md.start();
+      md.update('The quick brown fox jumps over the lazy dog');
+      test.result.html(md.digest().toHex());
+      test.check();
+   });
+
+   // c'è
+   addTest('sha-1 "c\'\u00e8"', function(task, test)
+   {
+      var expect = '98c9a3f804daa73b68a5660d032499a447350c0d';
+      test.expect.html(expect);
+      var md = forge.md.sha1.create();
+      md.update("c'\u00e8", 'utf8');
+      test.result.html(md.digest().toHex());
+      test.check();
+   });
+
+   addTest('sha-1 "THIS IS A MESSAGE"',
+   function(task, test)
+   {
+      var expect = '5f24f4d6499fd2d44df6c6e94be8b14a796c071d';
+      test.expect.html(expect);
+      var md = forge.md.sha1.create();
+      md.start();
+      md.update('THIS IS ');
+      md.update('A MESSAGE');
+      // do twice to check continuing digest
+      test.result.html(md.digest().toHex());
+      test.result.html(md.digest().toHex());
+      test.check();
+   });
+
+   // other browsers too slow for this test
+   if($.browser.webkit)
+   {
+      addTest('sha-1 long message',
+      function(task, test)
+      {
+         var expect = '34aa973cd4c4daa4f61eeb2bdbad27316534016f';
+         test.expect.html(expect);
+         var md = forge.md.sha1.create();
+         md.start();
+         md.update(forge.util.fillString('a', 1000000));
+         // do twice to check continuing digest
+         test.result.html(md.digest().toHex());
+         test.result.html(md.digest().toHex());
+         test.check();
+      });
+   }
+
+   addTest('sha-256 "abc"', function(task, test)
+   {
+      var expect =
+         'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad';
+      test.expect.html(expect);
+      var md = forge.md.sha256.create();
+      md.update('abc');
+      test.result.html(md.digest().toHex());
+      test.check();
+   });
+
+   // c'è
+   addTest('sha-256 "c\'\u00e8"', function(task, test)
+   {
+      var expect =
+         '1aa15c717afffd312acce2217ce1c2e5dabca53c92165999132ec9ca5decdaca';
+      test.expect.html(expect);
+      var md = forge.md.sha256.create();
+      md.update("c'\u00e8", 'utf8');
+      test.result.html(md.digest().toHex());
+      test.check();
+   });
+
+   addTest('sha-256 "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"',
+   function(task, test)
+   {
+      var expect =
+         '248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1';
+      test.expect.html(expect);
+      var md = forge.md.sha256.create();
+      md.start();
+      md.update('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq');
+      test.result.html(md.digest().toHex());
+      test.check();
+   });
+
+   // other browsers too slow for this test
+   if($.browser.webkit)
+   {
+      addTest('sha-256 long message',
+      function(task, test)
+      {
+         var expect =
+            'cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0';
+         test.expect.html(expect);
+         var md = forge.md.sha256.create();
+         md.start();
+         md.update(forge.util.fillString('a', 1000000));
+         // do twice to check continuing digest
+         test.result.html(md.digest().toHex());
+         test.result.html(md.digest().toHex());
+         test.check();
+      });
+   }
+
+   addTest('hmac md5 "Hi There", 16-byte key', function(task, test)
+   {
+      var expect = '9294727a3638bb1c13f48ef8158bfc9d';
+      test.expect.html(expect);
+      var key = forge.util.hexToBytes(
+         '0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b');
+      var hmac = forge.hmac.create();
+      hmac.start('MD5', key);
+      hmac.update('Hi There');
+      test.result.html(hmac.digest().toHex());
+      test.check();
+   });
+
+   addTest('hmac md5 "what do ya want for nothing?", "Jefe" key',
+      function(task, test)
+   {
+      var expect = '750c783e6ab0b503eaa86e310a5db738';
+      test.expect.html(expect);
+      var hmac = forge.hmac.create();
+      hmac.start('MD5', 'Jefe');
+      hmac.update('what do ya want for nothing?');
+      test.result.html(hmac.digest().toHex());
+      test.check();
+   });
+
+   addTest('hmac md5 "Test Using Larger Than Block-Size Key - ' +
+      'Hash Key First", 80-byte key', function(task, test)
+   {
+      var expect = '6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd';
+      test.expect.html(expect);
+      var key = forge.util.hexToBytes(
+         'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
+         'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
+         'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
+         'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
+      var hmac = forge.hmac.create();
+      hmac.start('MD5', key);
+      hmac.update('Test Using Larger Than Block-Size Key - Hash Key First');
+      test.result.html(hmac.digest().toHex());
+      test.check();
+   });
+
+   addTest('hmac sha-1 "Hi There", 20-byte key', function(task, test)
+   {
+      var expect = 'b617318655057264e28bc0b6fb378c8ef146be00';
+      test.expect.html(expect);
+      var key = forge.util.hexToBytes(
+         '0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b');
+      var hmac = forge.hmac.create();
+      hmac.start('SHA1', key);
+      hmac.update('Hi There');
+      test.result.html(hmac.digest().toHex());
+      test.check();
+   });
+
+   addTest('hmac sha-1 "what do ya want for nothing?", "Jefe" key',
+      function(task, test)
+   {
+      var expect = 'effcdf6ae5eb2fa2d27416d5f184df9c259a7c79';
+      test.expect.html(expect);
+      var hmac = forge.hmac.create();
+      hmac.start('SHA1', 'Jefe');
+      hmac.update('what do ya want for nothing?');
+      test.result.html(hmac.digest().toHex());
+      test.check();
+   });
+
+   addTest('hmac sha-1 "Test Using Larger Than Block-Size Key - ' +
+      'Hash Key First", 80-byte key', function(task, test)
+   {
+      var expect = 'aa4ae5e15272d00e95705637ce8a3b55ed402112';
+      test.expect.html(expect);
+      var key = forge.util.hexToBytes(
+         'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
+         'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
+         'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
+         'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
+      var hmac = forge.hmac.create();
+      hmac.start('SHA1', key);
+      hmac.update('Test Using Larger Than Block-Size Key - Hash Key First');
+      test.result.html(hmac.digest().toHex());
+      test.check();
+   });
+
+   addTest('pbkdf2 hmac-sha-1 c=1', function(task, test)
+   {
+      var expect = '0c60c80f961f0e71f3a9b524af6012062fe037a6';
+      var dk = forge.pkcs5.pbkdf2('password', 'salt', 1, 20);
+      test.expect.html(expect);
+      test.result.html(forge.util.bytesToHex(dk));
+      test.check();
+   });
+
+   addTest('pbkdf2 hmac-sha-1 c=2', function(task, test)
+   {
+      var expect = 'ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957';
+      var dk = forge.pkcs5.pbkdf2('password', 'salt', 2, 20);
+      test.expect.html(expect);
+      test.result.html(forge.util.bytesToHex(dk));
+      test.check();
+   });
+
+   addTest('pbkdf2 hmac-sha-1 c=2', function(task, test)
+   {
+      var expect = 'ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957';
+      var dk = forge.pkcs5.pbkdf2('password', 'salt', 2, 20);
+      test.expect.html(expect);
+      test.result.html(forge.util.bytesToHex(dk));
+      test.check();
+   });
+
+   addTest('pbkdf2 hmac-sha-1 c=5 keylen=8', function(task, test)
+   {
+      var expect = 'd1daa78615f287e6';
+      var salt = forge.util.hexToBytes('1234567878563412');
+      var dk = forge.pkcs5.pbkdf2('password', salt, 5, 8);
+      test.expect.html(expect);
+      test.result.html(forge.util.bytesToHex(dk));
+      test.check();
+   });
+
+   // other browsers too slow for this test
+   if($.browser.webkit)
+   {
+      addTest('pbkdf2 hmac-sha-1 c=4096', function(task, test)
+      {
+         var expect = '4b007901b765489abead49d926f721d065a429c1';
+         var dk = forge.pkcs5.pbkdf2('password', 'salt', 4096, 20);
+         test.expect.html(expect);
+         test.result.html(forge.util.bytesToHex(dk));
+         test.check();
+      });
+   }
+
+   /* too slow for javascript
+   addTest('pbkdf2 hmac-sha-1 c=16777216', function(task, test)
+   {
+      var expect = 'eefe3d61cd4da4e4e9945b3d6ba2158c2634e984';
+      var dk = forge.pkcs5.pbkdf2('password', 'salt', 16777216, 20);
+      test.expect.html(expect);
+      test.result.html(forge.util.bytesToHex(dk));
+      test.check();
+   });*/
+
+   addTest('aes-128 encrypt', function(task, test)
+   {
+      var block = [];
+      block.push(0x00112233);
+      block.push(0x44556677);
+      block.push(0x8899aabb);
+      block.push(0xccddeeff);
+      var plain = block;
+
+      var key = [];
+      key.push(0x00010203);
+      key.push(0x04050607);
+      key.push(0x08090a0b);
+      key.push(0x0c0d0e0f);
+
+      var expect = [];
+      expect.push(0x69c4e0d8);
+      expect.push(0x6a7b0430);
+      expect.push(0xd8cdb780);
+      expect.push(0x70b4c55a);
+
+      test.expect.html('69c4e0d86a7b0430d8cdb78070b4c55a');
+
+      var output = [];
+      var w = forge.aes._expandKey(key, false);
+      forge.aes._updateBlock(w, block, output, false);
+
+      var out = forge.util.createBuffer();
+      out.putInt32(output[0]);
+      out.putInt32(output[1]);
+      out.putInt32(output[2]);
+      out.putInt32(output[3]);
+      test.result.html(out.toHex());
+
+      test.check();
+   });
+
+   addTest('aes-128 decrypt', function(task, test)
+   {
+      var block = [];
+      block.push(0x69c4e0d8);
+      block.push(0x6a7b0430);
+      block.push(0xd8cdb780);
+      block.push(0x70b4c55a);
+
+      var key = [];
+      key.push(0x00010203);
+      key.push(0x04050607);
+      key.push(0x08090a0b);
+      key.push(0x0c0d0e0f);
+
+      var expect = [];
+      expect.push(0x00112233);
+      expect.push(0x44556677);
+      expect.push(0x8899aabb);
+      expect.push(0xccddeeff);
+
+      test.expect.html('00112233445566778899aabbccddeeff');
+
+      var output = [];
+      w = forge.aes._expandKey(key, true);
+      forge.aes._updateBlock(w, block, output, true);
+
+      var out = forge.util.createBuffer();
+      out.putInt32(output[0]);
+      out.putInt32(output[1]);
+      out.putInt32(output[2]);
+      out.putInt32(output[3]);
+      test.result.html(out.toHex());
+
+      test.check();
+   });
+
+   addTest('aes-192 encrypt', function(task, test)
+   {
+      var block = [];
+      block.push(0x00112233);
+      block.push(0x44556677);
+      block.push(0x8899aabb);
+      block.push(0xccddeeff);
+      var plain = block;
+
+      var key = [];
+      key.push(0x00010203);
+      key.push(0x04050607);
+      key.push(0x08090a0b);
+      key.push(0x0c0d0e0f);
+      key.push(0x10111213);
+      key.push(0x14151617);
+
+      var expect = [];
+      expect.push(0xdda97ca4);
+      expect.push(0x864cdfe0);
+      expect.push(0x6eaf70a0);
+      expect.push(0xec0d7191);
+
+      test.expect.html('dda97ca4864cdfe06eaf70a0ec0d7191');
+
+      var output = [];
+      var w = forge.aes._expandKey(key, false);
+      forge.aes._updateBlock(w, block, output, false);
+
+      var out = forge.util.createBuffer();
+      out.putInt32(output[0]);
+      out.putInt32(output[1]);
+      out.putInt32(output[2]);
+      out.putInt32(output[3]);
+      test.result.html(out.toHex());
+
+      test.check();
+   });
+
+   addTest('aes-192 decrypt', function(task, test)
+   {
+      var block = [];
+      block.push(0xdda97ca4);
+      block.push(0x864cdfe0);
+      block.push(0x6eaf70a0);
+      block.push(0xec0d7191);
+
+      var key = [];
+      key.push(0x00010203);
+      key.push(0x04050607);
+      key.push(0x08090a0b);
+      key.push(0x0c0d0e0f);
+      key.push(0x10111213);
+      key.push(0x14151617);
+
+      var expect = [];
+      expect.push(0x00112233);
+      expect.push(0x44556677);
+      expect.push(0x8899aabb);
+      expect.push(0xccddeeff);
+
+      test.expect.html('00112233445566778899aabbccddeeff');
+
+      var output = [];
+      w = forge.aes._expandKey(key, true);
+      forge.aes._updateBlock(w, block, output, true);
+
+      var out = forge.util.createBuffer();
+      out.putInt32(output[0]);
+      out.putInt32(output[1]);
+      out.putInt32(output[2]);
+      out.putInt32(output[3]);
+      test.result.html(out.toHex());
+
+      test.check();
+   });
+
+   addTest('aes-256 encrypt', function(task, test)
+   {
+      var block = [];
+      block.push(0x00112233);
+      block.push(0x44556677);
+      block.push(0x8899aabb);
+      block.push(0xccddeeff);
+      var plain = block;
+
+      var key = [];
+      key.push(0x00010203);
+      key.push(0x04050607);
+      key.push(0x08090a0b);
+      key.push(0x0c0d0e0f);
+      key.push(0x10111213);
+      key.push(0x14151617);
+      key.push(0x18191a1b);
+      key.push(0x1c1d1e1f);
+
+      var expect = [];
+      expect.push(0x8ea2b7ca);
+      expect.push(0x516745bf);
+      expect.push(0xeafc4990);
+      expect.push(0x4b496089);
+
+      test.expect.html('8ea2b7ca516745bfeafc49904b496089');
+
+      var output = [];
+      var w = forge.aes._expandKey(key, false);
+      forge.aes._updateBlock(w, block, output, false);
+
+      var out = forge.util.createBuffer();
+      out.putInt32(output[0]);
+      out.putInt32(output[1]);
+      out.putInt32(output[2]);
+      out.putInt32(output[3]);
+      test.result.html(out.toHex());
+
+      test.check();
+   });
+
+   addTest('aes-256 decrypt', function(task, test)
+   {
+      var block = [];
+      block.push(0x8ea2b7ca);
+      block.push(0x516745bf);
+      block.push(0xeafc4990);
+      block.push(0x4b496089);
+
+      var key = [];
+      key.push(0x00010203);
+      key.push(0x04050607);
+      key.push(0x08090a0b);
+      key.push(0x0c0d0e0f);
+      key.push(0x10111213);
+      key.push(0x14151617);
+      key.push(0x18191a1b);
+      key.push(0x1c1d1e1f);
+
+      var expect = [];
+      expect.push(0x00112233);
+      expect.push(0x44556677);
+      expect.push(0x8899aabb);
+      expect.push(0xccddeeff);
+
+      test.expect.html('00112233445566778899aabbccddeeff');
+
+      var output = [];
+      w = forge.aes._expandKey(key, true);
+      forge.aes._updateBlock(w, block, output, true);
+
+      var out = forge.util.createBuffer();
+      out.putInt32(output[0]);
+      out.putInt32(output[1]);
+      out.putInt32(output[2]);
+      out.putInt32(output[3]);
+      test.result.html(out.toHex());
+
+      test.check();
+   });
+
+   (function()
+   {
+      var keys = [
+         '06a9214036b8a15b512e03d534120006',
+         'c286696d887c9aa0611bbb3e2025a45a',
+         '6c3ea0477630ce21a2ce334aa746c2cd',
+         '56e47a38c5598974bc46903dba290349'
+      ];
+
+      var ivs = [
+         '3dafba429d9eb430b422da802c9fac41',
+         '562e17996d093d28ddb3ba695a2e6f58',
+         'c782dc4c098c66cbd9cd27d825682c81',
+         '8ce82eefbea0da3c44699ed7db51b7d9'
+      ];
+
+      var inputs = [
+         'Single block msg',
+         '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f',
+         'This is a 48-byte message (exactly 3 AES blocks)',
+         'a0a1a2a3a4a5a6a7a8a9aaabacadaeaf' +
+            'b0b1b2b3b4b5b6b7b8b9babbbcbdbebf' +
+            'c0c1c2c3c4c5c6c7c8c9cacbcccdcecf' +
+            'd0d1d2d3d4d5d6d7d8d9dadbdcdddedf'
+      ];
+
+      var outputs = [
+         'e353779c1079aeb82708942dbe77181a',
+         'd296cd94c2cccf8a3a863028b5e1dc0a7586602d253cfff91b8266bea6d61ab1',
+         'd0a02b3836451753d493665d33f0e886' +
+            '2dea54cdb293abc7506939276772f8d5' +
+            '021c19216bad525c8579695d83ba2684',
+         'c30e32ffedc0774e6aff6af0869f71aa' +
+            '0f3af07a9a31a9c684db207eb0ef8e4e' +
+            '35907aa632c3ffdf868bb7b29d3d46ad' +
+            '83ce9f9a102ee99d49a53e87f4c3da55'
+      ];
+
+      for(var i = 0; i < keys.length; ++i)
+      {
+         (function(i)
+         {
+            var key = forge.util.hexToBytes(keys[i]);
+            var iv = forge.util.hexToBytes(ivs[i]);
+            var input = (i & 1) ? forge.util.hexToBytes(inputs[i]) : inputs[i];
+            var output = forge.util.hexToBytes(outputs[i]);
+
+            addTest('aes-128 cbc encrypt', function(task, test)
+            {
+               // encrypt w/no padding
+               test.expect.html(outputs[i]);
+               var cipher = forge.aes.createEncryptionCipher(key);
+               cipher.start(iv);
+               cipher.update(forge.util.createBuffer(input));
+               cipher.finish(function(){return true;});
+               test.result.html(cipher.output.toHex());
+               test.check();
+            });
+
+            addTest('aes-128 cbc decrypt', function(task, test)
+            {
+               // decrypt w/no padding
+               test.expect.html(inputs[i]);
+               var cipher = forge.aes.createDecryptionCipher(key);
+               cipher.start(iv);
+               cipher.update(forge.util.createBuffer(output));
+               cipher.finish(function(){return true;});
+               var out = (i & 1) ?
+                  cipher.output.toHex() : cipher.output.bytes();
+               test.result.html(out);
+               test.check();
+            });
+         })(i);
+      }
+   })();
+
+   (function()
+   {
+      var keys = [
+         '00000000000000000000000000000000',
+         '2b7e151628aed2a6abf7158809cf4f3c',
+         '2b7e151628aed2a6abf7158809cf4f3c',
+         '2b7e151628aed2a6abf7158809cf4f3c',
+         '2b7e151628aed2a6abf7158809cf4f3c',
+         '00000000000000000000000000000000'
+      ];
+
+      var ivs = [
+         '80000000000000000000000000000000',
+         '000102030405060708090a0b0c0d0e0f',
+         '3B3FD92EB72DAD20333449F8E83CFB4A',
+         'C8A64537A0B3A93FCDE3CDAD9F1CE58B',
+         '26751F67A3CBB140B1808CF187A4F4DF',
+         '60f9ff04fac1a25657bf5b36b5efaf75'
+      ];
+
+      var inputs = [
+         '00000000000000000000000000000000',
+         '6bc1bee22e409f96e93d7e117393172a',
+         'ae2d8a571e03ac9c9eb76fac45af8e51',
+         '30c81c46a35ce411e5fbc1191a0a52ef',
+         'f69f2445df4f9b17ad2b417be66c3710',
+         'This is a 48-byte message (exactly 3 AES blocks)'
+      ];
+
+      var outputs = [
+         '3ad78e726c1ec02b7ebfe92b23d9ec34',
+         '3b3fd92eb72dad20333449f8e83cfb4a',
+         'c8a64537a0b3a93fcde3cdad9f1ce58b',
+         '26751f67a3cbb140b1808cf187a4f4df',
+         'c04b05357c5d1c0eeac4c66f9ff7f2e6',
+         '52396a2ba1ba420c5e5b699a814944d8' +
+           'f4e7fbf984a038319fbc0b4ee45cfa6f' +
+           '07b2564beab5b5e92dbd44cb345f49b4'
+      ];
+
+      for(var i = 0; i < keys.length; ++i)
+      {
+         (function(i)
+         {
+            var key = forge.util.hexToBytes(keys[i]);
+            var iv = forge.util.hexToBytes(ivs[i]);
+            var input = (i !== 5) ?
+               forge.util.hexToBytes(inputs[i]) : inputs[i];
+            var output = forge.util.hexToBytes(outputs[i]);
+
+            addTest('aes-128 cfb encrypt', function(task, test)
+            {
+               // encrypt
+               test.expect.html(outputs[i]);
+               var cipher = forge.aes.createEncryptionCipher(key, 'CFB');
+               cipher.start(iv);
+               cipher.update(forge.util.createBuffer(input));
+               cipher.finish();
+               test.result.html(cipher.output.toHex());
+               test.check();
+            });
+
+            addTest('aes-128 cfb decrypt', function(task, test)
+            {
+               // decrypt
+               test.expect.html(inputs[i]);
+               var cipher = forge.aes.createDecryptionCipher(key, 'CFB');
+               cipher.start(iv);
+               cipher.update(forge.util.createBuffer(output));
+               cipher.finish();
+               var out = (i !== 5) ?
+                 cipher.output.toHex() : cipher.output.getBytes();
+               test.result.html(out);
+               test.check();
+            });
+         })(i);
+      }
+   })();
+
+   (function()
+   {
+      var keys = [
+         '861009ec4d599fab1f40abc76e6f89880cff5833c79c548c99f9045f191cd90b'
+      ];
+
+      var ivs = [
+         'd927ad81199aa7dcadfdb4e47b6dc694'
+      ];
+
+      var inputs = [
+         'MY-DATA-AND-HERE-IS-MORE-DATA'
+      ];
+
+      var outputs = [
+         '80eb666a9fc9e263faf71e87ffc94451d7d8df7cfcf2606470351dd5ac'
+      ];
+
+      for(var i = 0; i < keys.length; ++i)
+      {
+         (function(i)
+         {
+            var key = forge.util.hexToBytes(keys[i]);
+            var iv = forge.util.hexToBytes(ivs[i]);
+            var input = inputs[i];
+            var output = forge.util.hexToBytes(outputs[i]);
+
+            addTest('aes-256 cfb encrypt', function(task, test)
+            {
+               // encrypt
+               test.expect.html(outputs[i]);
+               var cipher = forge.aes.createEncryptionCipher(key, 'CFB');
+               cipher.start(iv);
+               cipher.update(forge.util.createBuffer(input));
+               cipher.finish();
+               test.result.html(cipher.output.toHex());
+               test.check();
+            });
+
+            addTest('aes-256 cfb decrypt', function(task, test)
+            {
+               // decrypt
+               test.expect.html(inputs[i]);
+               var cipher = forge.aes.createDecryptionCipher(key, 'CFB');
+               cipher.start(iv);
+               cipher.update(forge.util.createBuffer(output));
+               cipher.finish();
+               var out = cipher.output.getBytes();
+               test.result.html(out);
+               test.check();
+            });
+         })(i);
+      }
+   })();
+
+   (function()
+   {
+      var keys = [
+         '00000000000000000000000000000000',
+         '00000000000000000000000000000000'
+      ];
+
+      var ivs = [
+         '80000000000000000000000000000000',
+         'c8ca0d6a35dbeac776e911ee16bea7d3'
+      ];
+
+      var inputs = [
+         '00000000000000000000000000000000',
+         'This is a 48-byte message (exactly 3 AES blocks)'
+      ];
+
+      var outputs = [
+         '3ad78e726c1ec02b7ebfe92b23d9ec34',
+         '39c0190727a76b2a90963426f63689cf' +
+           'cdb8a2be8e20c5e877a81a724e3611f6' +
+           '2ecc386f2e941b2441c838906002be19'
+      ];
+
+      for(var i = 0; i < keys.length; ++i)
+      {
+         (function(i)
+         {
+            var key = forge.util.hexToBytes(keys[i]);
+            var iv = forge.util.hexToBytes(ivs[i]);
+            var input = (i !== 1) ?
+               forge.util.hexToBytes(inputs[i]) : inputs[i];
+            var output = forge.util.hexToBytes(outputs[i]);
+
+            addTest('aes-128 ofb encrypt', function(task, test)
+            {
+               // encrypt w/no padding
+               test.expect.html(outputs[i]);
+               var cipher = forge.aes.createEncryptionCipher(key, 'OFB');
+               cipher.start(iv);
+               cipher.update(forge.util.createBuffer(input));
+               cipher.finish(function(){return true;});
+               test.result.html(cipher.output.toHex());
+               test.check();
+            });
+
+            addTest('aes-128 ofb decrypt', function(task, test)
+            {
+               // decrypt w/no padding
+               test.expect.html(inputs[i]);
+               var cipher = forge.aes.createDecryptionCipher(key, 'OFB');
+               cipher.start(iv);
+               cipher.update(forge.util.createBuffer(output));
+               cipher.finish(function(){return true;});
+               var out = (i !== 1) ?
+                 cipher.output.toHex() : cipher.output.getBytes();
+               test.result.html(out);
+               test.check();
+            });
+         })(i);
+      }
+   })();
+
+   (function()
+   {
+      var keys = [
+         '00000000000000000000000000000000',
+         '2b7e151628aed2a6abf7158809cf4f3c'
+      ];
+
+      var ivs = [
+         '650cdb80ff9fc758342d2bd99ee2abcf',
+         'f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff'
+      ];
+
+      var inputs = [
+         'This is a 48-byte message (exactly 3 AES blocks)',
+         '6bc1bee22e409f96e93d7e117393172a'
+      ];
+
+      var outputs = [
+         '5ede11d00e9a76ec1d5e7e811ea3dd1c' +
+           'e09ee941210f825d35718d3282796f1c' +
+           '07c3f1cb424f2b365766ab5229f5b5a4',
+         '874d6191b620e3261bef6864990db6ce'
+      ];
+
+      for(var i = 0; i < keys.length; ++i)
+      {
+         (function(i)
+         {
+            var key = forge.util.hexToBytes(keys[i]);
+            var iv = forge.util.hexToBytes(ivs[i]);
+            var input = (i !== 0) ?
+               forge.util.hexToBytes(inputs[i]) : inputs[i];
+            var output = forge.util.hexToBytes(outputs[i]);
+
+            addTest('aes-128 ctr encrypt', function(task, test)
+            {
+               // encrypt w/no padding
+               test.expect.html(outputs[i]);
+               var cipher = forge.aes.createEncryptionCipher(key, 'CTR');
+               cipher.start(iv);
+               cipher.update(forge.util.createBuffer(input));
+               cipher.finish(function(){return true;});
+               test.result.html(cipher.output.toHex());
+               test.check();
+            });
+
+            addTest('aes-128 ctr decrypt', function(task, test)
+            {
+               // decrypt w/no padding
+               test.expect.html(inputs[i]);
+               var cipher = forge.aes.createDecryptionCipher(key, 'CTR');
+               cipher.start(iv);
+               cipher.update(forge.util.createBuffer(output));
+               cipher.finish(function(){return true;});
+               var out = (i !== 0) ?
+                 cipher.output.toHex() : cipher.output.getBytes();
+               test.result.html(out);
+               test.check();
+            });
+         })(i);
+      }
+   })();
+
+   addTest('private key encryption', function(task, test)
+   {
+      var _privateKey =
+         '-----BEGIN RSA PRIVATE KEY-----\r\n' +
+         'MIICXQIBAAKBgQDL0EugUiNGMWscLAVM0VoMdhDZEJOqdsUMpx9U0YZI7szokJqQ\r\n' +
+         'NIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMGTkP3VF29vXBo+dLq5e+8VyAy\r\n' +
+         'Q3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGtvnM+z0MYDdKo80efzwIDAQAB\r\n' +
+         'AoGAIzkGONi5G+JifmXlLJdplom486p3upf4Ce2/7mqfaG9MnkyPSairKD/JXvfh\r\n' +
+         'NNWkkN8DKKDKBcVVElPgORYT0qwrWc7ueLBMUCbRXb1ZyfEulimG0R3kjUh7NYau\r\n' +
+         'DaIkVgfykXGSQMZx8FoaT6L080zd+0emKDDYRrb+/kgJNJECQQDoUZoiC2K/DWNY\r\n' +
+         'h3/ppZ0ane2y4SBmJUHJVMPQ2CEgxsrJTxet668ckNCKaOP/3VFPoWC41f17DvKq\r\n' +
+         'noYINNntAkEA4JbZBZBVUrQFhHlrpXT4jzqtO2RlKZzEq8qmFZfEErxOT1WMyyCi\r\n' +
+         'lAQ5gUKardo1Kf0omC8Xq/uO9ZYdED55KwJBALs6cJ65UFaq4oLJiQPzLd7yokuE\r\n' +
+         'dcj8g71PLBTW6jPxIiMFNA89nz3FU9wIVp+xbMNhSoMMKqIPVPC+m0Rn260CQQDA\r\n' +
+         'I83fWK/mZWUjBM33a68KumRiH238v8XyQxj7+C8i6D8G2GXvkigFAehAkb7LZZd+\r\n' +
+         'KLuGFyPlWv3fVWHf99KpAkBQFKk3MRMl6IGJZUEFQe4l5whm8LkGU4acSqv9B3xt\r\n' +
+         'qROkCrsFrMPqjuuzEmyHoQZ64r2PLJg7FOuyhBnQUOt4\r\n' +
+         '-----END RSA PRIVATE KEY-----';
+      var pk = forge.pki.privateKeyFromPem(_privateKey);
+      var pem1 = forge.pki.privateKeyToPem(pk);
+      var pem2 = forge.pki.encryptRsaPrivateKey(
+         pk, 'password', {'encAlg': 'aes128'});
+      var privateKey = forge.pki.decryptRsaPrivateKey(pem2, 'password');
+      var pem3 = forge.pki.privateKeyToPem(privateKey);
+      if(pem1 === pem3)
+      {
+         test.pass();
+      }
+      else
+      {
+         test.fail();
+      }
+   });
+
+   addTest('random', function(task, test)
+   {
+     forge.random.getBytes(16);
+     forge.random.getBytes(24);
+     forge.random.getBytes(32);
+
+      var b = forge.random.getBytes(10);
+      test.result.html(forge.util.bytesToHex(b));
+      if(b.length === 10)
+      {
+         test.pass();
+      }
+      else
+      {
+         test.fail();
+      }
+   });
+
+   addTest('asn.1 oid => der', function(task, test)
+   {
+      test.expect.html('2a864886f70d');
+      test.result.html(forge.asn1.oidToDer('1.2.840.113549').toHex());
+      test.check();
+   });
+
+   addTest('asn.1 der => oid', function(task, test)
+   {
+      var der = '2a864886f70d';
+      test.expect.html('1.2.840.113549');
+      test.result.html(forge.asn1.derToOid(forge.util.hexToBytes(der)));
+      test.check();
+   });
+
+   (function()
+   {
+      var _privateKey =
+      '-----BEGIN RSA PRIVATE KEY-----\r\n' +
+      'MIICXQIBAAKBgQDL0EugUiNGMWscLAVM0VoMdhDZEJOqdsUMpx9U0YZI7szokJqQ\r\n' +
+      'NIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMGTkP3VF29vXBo+dLq5e+8VyAy\r\n' +
+      'Q3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGtvnM+z0MYDdKo80efzwIDAQAB\r\n' +
+      'AoGAIzkGONi5G+JifmXlLJdplom486p3upf4Ce2/7mqfaG9MnkyPSairKD/JXvfh\r\n' +
+      'NNWkkN8DKKDKBcVVElPgORYT0qwrWc7ueLBMUCbRXb1ZyfEulimG0R3kjUh7NYau\r\n' +
+      'DaIkVgfykXGSQMZx8FoaT6L080zd+0emKDDYRrb+/kgJNJECQQDoUZoiC2K/DWNY\r\n' +
+      'h3/ppZ0ane2y4SBmJUHJVMPQ2CEgxsrJTxet668ckNCKaOP/3VFPoWC41f17DvKq\r\n' +
+      'noYINNntAkEA4JbZBZBVUrQFhHlrpXT4jzqtO2RlKZzEq8qmFZfEErxOT1WMyyCi\r\n' +
+      'lAQ5gUKardo1Kf0omC8Xq/uO9ZYdED55KwJBALs6cJ65UFaq4oLJiQPzLd7yokuE\r\n' +
+      'dcj8g71PLBTW6jPxIiMFNA89nz3FU9wIVp+xbMNhSoMMKqIPVPC+m0Rn260CQQDA\r\n' +
+      'I83fWK/mZWUjBM33a68KumRiH238v8XyQxj7+C8i6D8G2GXvkigFAehAkb7LZZd+\r\n' +
+      'KLuGFyPlWv3fVWHf99KpAkBQFKk3MRMl6IGJZUEFQe4l5whm8LkGU4acSqv9B3xt\r\n' +
+      'qROkCrsFrMPqjuuzEmyHoQZ64r2PLJg7FOuyhBnQUOt4\r\n' +
+      '-----END RSA PRIVATE KEY-----\r\n';
+
+      var _publicKey =
+      '-----BEGIN PUBLIC KEY-----\r\n' +
+      'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDL0EugUiNGMWscLAVM0VoMdhDZ\r\n' +
+      'EJOqdsUMpx9U0YZI7szokJqQNIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMG\r\n' +
+      'TkP3VF29vXBo+dLq5e+8VyAyQ3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGt\r\n' +
+      'vnM+z0MYDdKo80efzwIDAQAB\r\n' +
+      '-----END PUBLIC KEY-----\r\n';
+
+      var _certificate =
+      '-----BEGIN CERTIFICATE-----\r\n' +
+      'MIIDIjCCAougAwIBAgIJANE2aHSbwpaRMA0GCSqGSIb3DQEBBQUAMGoxCzAJBgNV\r\n' +
+      'BAYTAlVTMREwDwYDVQQIEwhWaXJnaW5pYTETMBEGA1UEBxMKQmxhY2tzYnVyZzEN\r\n' +
+      'MAsGA1UEChMEVGVzdDENMAsGA1UECxMEVGVzdDEVMBMGA1UEAxMMbXlzZXJ2ZXIu\r\n' +
+      'Y29tMB4XDTEwMDYxOTE3MzYyOFoXDTExMDYxOTE3MzYyOFowajELMAkGA1UEBhMC\r\n' +
+      'VVMxETAPBgNVBAgTCFZpcmdpbmlhMRMwEQYDVQQHEwpCbGFja3NidXJnMQ0wCwYD\r\n' +
+      'VQQKEwRUZXN0MQ0wCwYDVQQLEwRUZXN0MRUwEwYDVQQDEwxteXNlcnZlci5jb20w\r\n' +
+      'gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMvQS6BSI0YxaxwsBUzRWgx2ENkQ\r\n' +
+      'k6p2xQynH1TRhkjuzOiQmpA0jCiSJDoSic2dZIyUi/LjoCGeVFif57N5N5Tt4wZO\r\n' +
+      'Q/dUXb29cGj50url77xXIDJDcXMzXAji2ziFEAIXzDqarKBdDuL9IO7z+tepEa2+\r\n' +
+      'cz7PQxgN0qjzR5/PAgMBAAGjgc8wgcwwHQYDVR0OBBYEFPV1Y+DHXW6bA/r9sv1y\r\n' +
+      'NJ8jAwMAMIGcBgNVHSMEgZQwgZGAFPV1Y+DHXW6bA/r9sv1yNJ8jAwMAoW6kbDBq\r\n' +
+      'MQswCQYDVQQGEwJVUzERMA8GA1UECBMIVmlyZ2luaWExEzARBgNVBAcTCkJsYWNr\r\n' +
+      'c2J1cmcxDTALBgNVBAoTBFRlc3QxDTALBgNVBAsTBFRlc3QxFTATBgNVBAMTDG15\r\n' +
+      'c2VydmVyLmNvbYIJANE2aHSbwpaRMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEF\r\n' +
+      'BQADgYEARdH2KOlJWTC1CS2y/PAvg4uiM31PXMC1hqSdJlnLM1MY4hRfuf9VyTeX\r\n' +
+      'Y6FdybcyDLSxKn9id+g9229ci9/s9PI+QmD5vXd8yZyScLc2JkYB4GC6+9D1+/+x\r\n' +
+      's2hzMxuK6kzZlP+0l9LGcraMQPGRydjCARZZm4Uegln9rh85XFQ=\r\n' +
+      '-----END CERTIFICATE-----\r\n';
+
+      var _signature =
+         '9200ece65cdaed36bcc20b94c65af852e4f88f0b4fe5b249d54665f815992ac4' +
+         '3a1399e65d938c6a7f16dd39d971a53ca66523209dbbfbcb67afa579dbb0c220' +
+         '672813d9e6f4818f29b9becbb29da2032c5e422da97e0c39bfb7a2e7d568615a' +
+         '5073af0337ff215a8e1b2332d668691f4fb731440055420c24ac451dd3c913f4';
+
+      addTest('private key from pem/to pem', function(task, test)
+      {
+         try
+         {
+            // convert from pem
+            var key = forge.pki.privateKeyFromPem(_privateKey);
+            //forge.log.debug(cat, 'privateKey', key);
+
+            // convert back to pem
+            var pem = forge.pki.privateKeyToPem(key);
+            test.expect.html(_privateKey);
+            test.result.html(pem);
+            test.check();
+         }
+         catch(ex)
+         {
+            forge.log.error('test', ex);
+            test.fail();
+         }
+      });
+
+      addTest('public key from pem/to pem', function(task, test)
+      {
+         try
+         {
+            // convert from pem
+            var key = forge.pki.publicKeyFromPem(_publicKey);
+            //forge.log.debug(cat, 'publicKey', key);
+
+            // convert back to pem
+            var pem = forge.pki.publicKeyToPem(key);
+            test.expect.html(_publicKey);
+            test.result.html(pem);
+            test.check();
+         }
+         catch(ex)
+         {
+            forge.log.error('test', ex);
+            test.fail();
+         }
+      });
+
+      addTest('certificate key from pem/to pem', function(task, test)
+      {
+         try
+         {
+            var cert = forge.pki.certificateFromPem(_certificate);
+            /*
+            forge.log.debug(cat, 'cert', cert);
+            forge.log.debug(cat, 'CN', cert.subject.getField('CN').value);
+            forge.log.debug(cat, 'C',
+               cert.subject.getField({shortName: 'C'}).value);
+            forge.log.debug(cat, 'stateOrProvinceName',
+               cert.subject.getField({name: 'stateOrProvinceName'}).value);
+            forge.log.debug(cat, '2.5.4.7',
+               cert.subject.getField({type: '2.5.4.7'}).value);
+            */
+            // convert back to pem
+            var pem = forge.pki.certificateToPem(cert);
+            test.expect.html(_certificate);
+            test.result.html(pem);
+            test.check();
+         }
+         catch(ex)
+         {
+            forge.log.error('test', ex);
+            test.fail();
+         }
+      });
+
+      addTest('verify signature', function(task, test)
+      {
+         try
+         {
+            var key = forge.pki.publicKeyFromPem(_publicKey);
+            var md = forge.md.sha1.create();
+            md.update('0123456789abcdef');
+            var signature = forge.util.hexToBytes(_signature);
+            var success = key.verify(md.digest().getBytes(), signature);
+            if(success)
+            {
+               test.pass();
+            }
+            else
+            {
+               test.fail();
+            }
+         }
+         catch(ex)
+         {
+            forge.log.error('test', ex);
+            test.fail();
+         }
+      });
+
+      addTest('sign and verify', function(task, test)
+      {
+         try
+         {
+            var privateKey = forge.pki.privateKeyFromPem(_privateKey);
+            var publicKey = forge.pki.publicKeyFromPem(_publicKey);
+
+            // do sign
+            var md = forge.md.sha1.create();
+            md.update('0123456789abcdef');
+            var st = +new Date();
+            var signature = privateKey.sign(md);
+            var et = +new Date();
+            //forge.log.debug(cat, 'sign time', (et - st) + 'ms');
+
+            // do verify
+            st = +new Date();
+            var success = publicKey.verify(md.digest().getBytes(), signature);
+            et = +new Date();
+            //forge.log.debug(cat, 'verify time', (et - st) + 'ms');
+            if(success)
+            {
+               test.pass();
+            }
+            else
+            {
+               test.fail();
+            }
+         }
+         catch(ex)
+         {
+            forge.log.error('test', ex);
+            test.fail();
+         }
+      });
+
+      addTest('certificate verify', function(task, test)
+      {
+         try
+         {
+            var cert = forge.pki.certificateFromPem(_certificate, true);
+            //forge.log.debug(cat, 'cert', cert);
+            var success = cert.verify(cert);
+            if(success)
+            {
+               test.pass();
+            }
+            else
+            {
+               test.fail();
+            }
+         }
+         catch(ex)
+         {
+            forge.log.error('test', ex);
+            test.fail();
+         }
+      });
+   })();
+
+   addTest('TLS prf', function(task, test)
+   {
+      // Note: This test vector is originally from:
+      // http://www.imc.org/ietf-tls/mail-archive/msg01589.html
+      // But that link is now dead.
+      var secret = forge.util.createBuffer();
+      for(var i = 0; i < 48; ++i)
+      {
+         secret.putByte(0xAB);
+      }
+      secret = secret.getBytes();
+      var seed = forge.util.createBuffer();
+      for(var i = 0; i < 64; ++i)
+      {
+         seed.putByte(0xCD);
+      }
+      seed = seed.getBytes();
+
+      var bytes = forge.tls.prf_tls1(secret, 'PRF Testvector',  seed, 104);
+      var expect =
+         'd3d4d1e349b5d515044666d51de32bab258cb521' +
+         'b6b053463e354832fd976754443bcf9a296519bc' +
+         '289abcbc1187e4ebd31e602353776c408aafb74c' +
+         'bc85eff69255f9788faa184cbb957a9819d84a5d' +
+         '7eb006eb459d3ae8de9810454b8b2d8f1afbc655' +
+         'a8c9a013';
+      test.expect.html(expect);
+      test.result.html(bytes.toHex());
+      test.check();
+   });
+
+   // function to create certificate
+   var createCert = function(keys, cn, data)
+   {
+      var cert = forge.pki.createCertificate();
+      cert.serialNumber = '01';
+      cert.validity.notBefore = new Date();
+      cert.validity.notAfter = new Date();
+      cert.validity.notAfter.setFullYear(
+         cert.validity.notBefore.getFullYear() + 1);
+      var attrs = [{
+         name: 'commonName',
+         value: cn
+      }, {
+         name: 'countryName',
+         value: 'US'
+      }, {
+         shortName: 'ST',
+         value: 'Virginia'
+      }, {
+         name: 'localityName',
+         value: 'Blacksburg'
+      }, {
+         name: 'organizationName',
+         value: 'Test'
+      }, {
+         shortName: 'OU',
+         value: 'Test'
+      }];
+      cert.setSubject(attrs);
+      cert.setIssuer(attrs);
+      cert.setExtensions([{
+         name: 'basicConstraints',
+         cA: true
+      }, {
+         name: 'keyUsage',
+         keyCertSign: true,
+         digitalSignature: true,
+         nonRepudiation: true,
+         keyEncipherment: true,
+         dataEncipherment: true
+      }, {
+         name: 'subjectAltName',
+         altNames: [{
+            type: 6, // URI
+            value: 'http://myuri.com/webid#me'
+         }]
+      }]);
+      // FIXME: add subjectKeyIdentifier extension
+      // FIXME: add authorityKeyIdentifier extension
+      cert.publicKey = keys.publicKey;
+
+      // self-sign certificate
+      cert.sign(keys.privateKey);
+
+      // save data
+      data[cn] = {
+         cert: forge.pki.certificateToPem(cert),
+         privateKey: forge.pki.privateKeyToPem(keys.privateKey)
+      };
+   };
+
+   var generateCert = function(task, test, cn, data)
+   {
+      task.block();
+
+      // create key-generation state and function to step algorithm
+      test.result.html(
+         'Generating 512-bit key-pair and certificate for \"' + cn + '\".');
+      var state = forge.pki.rsa.createKeyPairGenerationState(512);
+      var kgTime = +new Date();
+      var step = function()
+      {
+         // step key-generation
+         if(!forge.pki.rsa.stepKeyPairGenerationState(state, 1000))
+         {
+            test.result.html(test.result.html() + '.');
+            setTimeout(step, 1);
+         }
+         // key-generation complete
+         else
+         {
+            kgTime = +new Date() - kgTime;
+            forge.log.debug(cat, 'Total key-gen time', kgTime + 'ms');
+            try
+            {
+               createCert(state.keys, cn, data);
+               test.result.html(
+                  test.result.html() + 'done. Time=' + kgTime + 'ms. ');
+               task.unblock();
+            }
+            catch(ex)
+            {
+               forge.log.error(cat, ex, ex.message ? ex.message : '');
+               test.result.html(ex.message);
+               test.fail();
+               task.fail();
+            }
+         }
+      };
+
+      // run key-gen algorithm
+      setTimeout(step, 0);
+   };
+
+   var clientSessionCache1 = forge.tls.createSessionCache();
+   var serverSessionCache1 = forge.tls.createSessionCache();
+   addTest('TLS connection, w/o client-certificate', function(task, test)
+   {
+      var data = {};
+
+      task.next('generate server certifcate', function(task)
+      {
+         generateCert(task, test, 'server', data);
+      });
+
+      task.next('starttls', function(task)
+      {
+         test.result.html(test.result.html() + 'Starting TLS...');
+
+         var end =
+         {
+            client: null,
+            server: null
+         };
+         var success = false;
+
+         // create client
+         end.client = forge.tls.createConnection(
+         {
+            server: false,
+            caStore: [data.server.cert],
+            sessionCache: clientSessionCache1,
+            // optional cipher suites in order of preference
+            cipherSuites: [
+               forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+               forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+            virtualHost: 'server',
+            verify: function(c, verified, depth, certs)
+            {
+               test.result.html(test.result.html() +
+                  'Client verifying certificate w/CN: \"' +
+                  certs[0].subject.getField('CN').value +
+                  '\", verified: ' + verified + '...');
+               if(verified !== true)
+               {
+                  test.fail();
+                  task.fail();
+               }
+               return verified;
+            },
+            connected: function(c)
+            {
+               test.result.html(test.result.html() + 'Client connected...');
+
+               // send message to server
+               setTimeout(function()
+               {
+                  c.prepare('Hello Server');
+               }, 1);
+            },
+            tlsDataReady: function(c)
+            {
+               // send TLS data to server
+               end.server.process(c.tlsData.getBytes());
+            },
+            dataReady: function(c)
+            {
+               var response = c.data.getBytes();
+               test.result.html(test.result.html() +
+                  'Client received \"' + response + '\"');
+               success = (response === 'Hello Client');
+               c.close();
+            },
+            closed: function(c)
+            {
+               test.result.html(test.result.html() + 'Client disconnected.');
+               test.result.html(success ? 'Success' : 'Failure');
+               if(success)
+               {
+                  test.expect.html('Success');
+                  task.unblock();
+                  test.pass();
+               }
+               else
+               {
+                  console.log('closed fail');
+                  test.fail();
+                  task.fail();
+               }
+            },
+            error: function(c, error)
+            {
+               test.result.html(test.result.html() + 'Error: ' + error.message);
+               test.fail();
+               task.fail();
+            }
+         });
+
+         // create server
+         end.server = forge.tls.createConnection(
+         {
+            server: true,
+            sessionCache: serverSessionCache1,
+            // optional cipher suites in order of preference
+            cipherSuites: [
+               forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+               forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+            connected: function(c)
+            {
+               test.result.html(test.result.html() + 'Server connected...');
+            },
+            getCertificate: function(c, hint)
+            {
+               test.result.html(test.result.html() +
+                  'Server getting certificate for \"' + hint[0] + '\"...');
+               return data.server.cert;
+            },
+            getPrivateKey: function(c, cert)
+            {
+               return data.server.privateKey;
+            },
+            tlsDataReady: function(c)
+            {
+               // send TLS data to client
+               end.client.process(c.tlsData.getBytes());
+            },
+            dataReady: function(c)
+            {
+               test.result.html(test.result.html() +
+                  'Server received \"' + c.data.getBytes() + '\"');
+
+               // send response
+               c.prepare('Hello Client');
+               c.close();
+            },
+            closed: function(c)
+            {
+               test.result.html(test.result.html() + 'Server disconnected.');
+            },
+            error: function(c, error)
+            {
+               test.result.html(test.result.html() + 'Error: ' + error.message);
+               test.fail();
+               task.fail();
+            }
+         });
+
+         // start handshake
+         task.block();
+         end.client.handshake();
+      });
+   });
+
+   var clientSessionCache2 = forge.tls.createSessionCache();
+   var serverSessionCache2 = forge.tls.createSessionCache();
+   addTest('TLS connection, w/optional client-certificate', function(task, test)
+   {
+      var data = {};
+
+      task.next('generate server certifcate', function(task)
+      {
+         generateCert(task, test, 'server', data);
+      });
+
+      // client-cert generated but not sent in this test
+      task.next('generate client certifcate', function(task)
+      {
+         generateCert(task, test, 'client', data);
+      });
+
+      task.next('starttls', function(task)
+      {
+         test.result.html(test.result.html() + 'Starting TLS...');
+
+         var end =
+         {
+            client: null,
+            server: null
+         };
+         var success = false;
+
+         // create client
+         end.client = forge.tls.createConnection(
+         {
+            server: false,
+            caStore: [data.server.cert],
+            sessionCache: clientSessionCache2,
+            // supported cipher suites in order of preference
+            cipherSuites: [
+               forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+               forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+            virtualHost: 'server',
+            verify: function(c, verified, depth, certs)
+            {
+               test.result.html(test.result.html() +
+                  'Client verifying certificate w/CN: \"' +
+                  certs[0].subject.getField('CN').value +
+                  '\", verified: ' + verified + '...');
+               if(verified !== true)
+               {
+                  test.fail();
+                  task.fail();
+               }
+               return verified;
+            },
+            connected: function(c)
+            {
+               test.result.html(test.result.html() + 'Client connected...');
+
+               // send message to server
+               setTimeout(function()
+               {
+                  c.prepare('Hello Server');
+               }, 1);
+            },
+            tlsDataReady: function(c)
+            {
+               // send TLS data to server
+               end.server.process(c.tlsData.getBytes());
+            },
+            dataReady: function(c)
+            {
+               var response = c.data.getBytes();
+               test.result.html(test.result.html() +
+                  'Client received \"' + response + '\"');
+               success = (response === 'Hello Client');
+               c.close();
+            },
+            closed: function(c)
+            {
+               test.result.html(test.result.html() + 'Client disconnected.');
+               test.result.html(success ? 'Success' : 'Failure');
+               if(success)
+               {
+                  test.expect.html('Success');
+                  task.unblock();
+                  test.pass();
+               }
+               else
+               {
+                  console.log('closed fail');
+                  test.fail();
+                  task.fail();
+               }
+            },
+            error: function(c, error)
+            {
+               test.result.html(test.result.html() + 'Error: ' + error.message);
+               test.fail();
+               task.fail();
+            }
+         });
+
+         // create server
+         end.server = forge.tls.createConnection(
+         {
+            server: true,
+            caStore: [data.client.cert],
+            sessionCache: serverSessionCache2,
+            // supported cipher suites in order of preference
+            cipherSuites: [
+               forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+               forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+            connected: function(c)
+            {
+               test.result.html(test.result.html() + 'Server connected...');
+            },
+            verifyClient: 'optional',
+            verify: function(c, verified, depth, certs)
+            {
+               test.result.html(test.result.html() +
+                  'Server verifying certificate w/CN: \"' +
+                  certs[0].subject.getField('CN').value +
+                  '\", verified: ' + verified + '...');
+               if(verified !== true)
+               {
+                  test.fail();
+                  task.fail();
+               }
+               return verified;
+            },
+            getCertificate: function(c, hint)
+            {
+               test.result.html(test.result.html() +
+                  'Server getting certificate for \"' + hint[0] + '\"...');
+               return data.server.cert;
+            },
+            getPrivateKey: function(c, cert)
+            {
+               return data.server.privateKey;
+            },
+            tlsDataReady: function(c)
+            {
+               // send TLS data to client
+               end.client.process(c.tlsData.getBytes());
+            },
+            dataReady: function(c)
+            {
+               test.result.html(test.result.html() +
+                  'Server received \"' + c.data.getBytes() + '\"');
+
+               // send response
+               c.prepare('Hello Client');
+               c.close();
+            },
+            closed: function(c)
+            {
+               test.result.html(test.result.html() + 'Server disconnected.');
+            },
+            error: function(c, error)
+            {
+               test.result.html(test.result.html() + 'Error: ' + error.message);
+               test.fail();
+               task.fail();
+            }
+         });
+
+         // start handshake
+         task.block();
+         end.client.handshake();
+      });
+   });
+
+   var clientSessionCache3 = forge.tls.createSessionCache();
+   var serverSessionCache3 = forge.tls.createSessionCache();
+   addTest('TLS connection, w/client-certificate', function(task, test)
+   {
+      var data = {};
+
+      task.next('generate server certifcate', function(task)
+      {
+         generateCert(task, test, 'server', data);
+      });
+
+      task.next('generate client certifcate', function(task)
+      {
+         generateCert(task, test, 'client', data);
+      });
+
+      task.next('starttls', function(task)
+      {
+         test.result.html(test.result.html() + 'Starting TLS...');
+
+         var end =
+         {
+            client: null,
+            server: null
+         };
+         var success = false;
+
+         // create client
+         end.client = forge.tls.createConnection(
+         {
+            server: false,
+            caStore: [data.server.cert],
+            sessionCache: clientSessionCache3,
+            // supported cipher suites in order of preference
+            cipherSuites: [
+               forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+               forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+            virtualHost: 'server',
+            verify: function(c, verified, depth, certs)
+            {
+               test.result.html(test.result.html() +
+                  'Client verifying certificate w/CN: \"' +
+                  certs[0].subject.getField('CN').value +
+                  '\", verified: ' + verified + '...');
+               if(verified !== true)
+               {
+                  test.fail();
+                  task.fail();
+               }
+               return verified;
+            },
+            connected: function(c)
+            {
+               test.result.html(test.result.html() + 'Client connected...');
+
+               // send message to server
+               setTimeout(function()
+               {
+                  c.prepare('Hello Server');
+               }, 1);
+            },
+            getCertificate: function(c, hint)
+            {
+               test.result.html(test.result.html() +
+                  'Client getting certificate ...');
+               return data.client.cert;
+            },
+            getPrivateKey: function(c, cert)
+            {
+               return data.client.privateKey;
+            },
+            tlsDataReady: function(c)
+            {
+               // send TLS data to server
+               end.server.process(c.tlsData.getBytes());
+            },
+            dataReady: function(c)
+            {
+               var response = c.data.getBytes();
+               test.result.html(test.result.html() +
+                  'Client received \"' + response + '\"');
+               success = (response === 'Hello Client');
+               c.close();
+            },
+            closed: function(c)
+            {
+               test.result.html(test.result.html() + 'Client disconnected.');
+               test.result.html(success ? 'Success' : 'Failure');
+               if(success)
+               {
+                  test.expect.html('Success');
+                  task.unblock();
+                  test.pass();
+               }
+               else
+               {
+                  console.log('closed fail');
+                  test.fail();
+                  task.fail();
+               }
+            },
+            error: function(c, error)
+            {
+               test.result.html(test.result.html() + 'Error: ' + error.message);
+               test.fail();
+               task.fail();
+            }
+         });
+
+         // create server
+         end.server = forge.tls.createConnection(
+         {
+            server: true,
+            caStore: [data.client.cert],
+            sessionCache: serverSessionCache3,
+            // supported cipher suites in order of preference
+            cipherSuites: [
+               forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+               forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+            connected: function(c)
+            {
+               test.result.html(test.result.html() + 'Server connected...');
+            },
+            verifyClient: true, // use 'optional' to request but not require
+            verify: function(c, verified, depth, certs)
+            {
+               test.result.html(test.result.html() +
+                  'Server verifying certificate w/CN: \"' +
+                  certs[0].subject.getField('CN').value +
+                  '\", verified: ' + verified + '...');
+               if(verified !== true)
+               {
+                  test.fail();
+                  task.fail();
+               }
+               return verified;
+            },
+            getCertificate: function(c, hint)
+            {
+               test.result.html(test.result.html() +
+                  'Server getting certificate for \"' + hint[0] + '\"...');
+               return data.server.cert;
+            },
+            getPrivateKey: function(c, cert)
+            {
+               return data.server.privateKey;
+            },
+            tlsDataReady: function(c)
+            {
+               // send TLS data to client
+               end.client.process(c.tlsData.getBytes());
+            },
+            dataReady: function(c)
+            {
+               test.result.html(test.result.html() +
+                  'Server received \"' + c.data.getBytes() + '\"');
+
+               // send response
+               c.prepare('Hello Client');
+               c.close();
+            },
+            closed: function(c)
+            {
+               test.result.html(test.result.html() + 'Server disconnected.');
+            },
+            error: function(c, error)
+            {
+               test.result.html(test.result.html() + 'Error: ' + error.message);
+               test.fail();
+               task.fail();
+            }
+         });
+
+         // start handshake
+         task.block();
+         end.client.handshake();
+      });
+   });
+
+   init();
+});
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/favicon.ico b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/favicon.ico
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/favicon.ico
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/flash/Test.as b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/flash/Test.as
new file mode 100644
index 0000000..7c03727
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/flash/Test.as
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2010 Digital Bazaar, Inc. All rights reserved.
+ * 
+ * @author Dave Longley
+ */
+package
+{
+   import flash.display.Sprite;
+   
+   public class Test extends Sprite
+   {
+      import flash.events.*;
+      import flash.net.*;
+      
+      import flash.external.ExternalInterface;
+      import flash.system.Security;
+      
+      public function Test()
+      {
+         try
+         {
+            // FIXME: replace 'localhost' with cross-domain host to hit
+            var xhost:String = "localhost";
+            Security.loadPolicyFile("xmlsocket://" + xhost + ":80");
+            
+            var loader:URLLoader = new URLLoader();
+            loader.addEventListener(
+               Event.COMPLETE, completeHandler);
+            loader.addEventListener(
+               Event.OPEN, openHandler);
+            loader.addEventListener(
+               ProgressEvent.PROGRESS, progressHandler);
+            loader.addEventListener(
+               SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
+            loader.addEventListener(
+               HTTPStatusEvent.HTTP_STATUS, httpStatusHandler);
+            loader.addEventListener(
+               IOErrorEvent.IO_ERROR, ioErrorHandler);
+            
+            var request:URLRequest = new URLRequest(
+               "http://" + xhost + "/index.html");
+            loader.load(request);
+         }
+         catch(e:Error)
+         {
+            log("error=" + e.errorID + "," + e.name + "," + e.message);
+            throw e;
+         }
+      }
+      
+      private function log(obj:Object):void
+      {
+         if(obj is String)
+         {
+            var str:String = obj as String;
+            ExternalInterface.call("console.log", "Test", str);
+         }
+         else if(obj is Error)
+         {
+            var e:Error = obj as Error;
+            log("error=" + e.errorID + "," + e.name + "," + e.message);
+         }
+      }
+      
+      private function completeHandler(event:Event):void
+      {
+         var loader:URLLoader = URLLoader(event.target);
+         log("complete: " + loader.data);
+      }
+
+      private function openHandler(event:Event):void
+      {
+         log("open: " + event);
+      }
+
+      private function progressHandler(event:ProgressEvent):void
+      {
+         log("progress:" + event.bytesLoaded + " total: " + event.bytesTotal);
+      }
+
+      private function securityErrorHandler(event:SecurityErrorEvent):void
+      {
+         log("securityError: " + event);
+      }
+
+      private function httpStatusHandler(event:HTTPStatusEvent):void
+      {
+         log("httpStatus: " + event);
+      }
+
+      private function ioErrorHandler(event:IOErrorEvent):void
+      {
+         log("ioError: " + event);
+      }
+   }
+}
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/flash/build-flash.xml b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/flash/build-flash.xml
new file mode 100644
index 0000000..f037c58
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/flash/build-flash.xml
@@ -0,0 +1,7 @@
+<flex-config>
+   <compiler>
+      <source-path>
+         <path-element>.</path-element>
+      </source-path>
+   </compiler>
+</flex-config>
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/flash/index.html b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/flash/index.html
new file mode 100644
index 0000000..26a10b8
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/flash/index.html
@@ -0,0 +1,27 @@
+<html>
+   <head>
+      <link type="text/css" rel="stylesheet" media="all" href="screen.css" />
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js"></script>
+      
+      <script type="text/javascript">
+      //<![CDATA[
+      swfobject.embedSWF(
+         'Test.swf', 'test', '0', '0', '9.0.0',
+         false, {}, {allowscriptaccess: 'always'}, {});
+      //]]>
+      </script>
+   </head>
+   <body>
+      <div class="header">
+         <h1>Flash Cross-Domain URLLoader Test</h1>
+      </div>
+
+      <div class="content">
+
+      <div id="test">
+         <p>Could not load the flash test.</p>
+      </div>
+
+      </div>
+   </body>
+</html>
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/forge_ssl/forge/__init__.py b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/forge_ssl/forge/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/forge_ssl/forge/__init__.py
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/forge_ssl/forge/_ssl.c b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/forge_ssl/forge/_ssl.c
new file mode 100644
index 0000000..bdef8c9
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/forge_ssl/forge/_ssl.c
@@ -0,0 +1,1770 @@
+/* SSL socket module
+
+   SSL support based on patches by Brian E Gallew and Laszlo Kovacs.
+   Re-worked a bit by Bill Janssen to add server-side support and
+   certificate decoding.  Chris Stawarz contributed some non-blocking
+   patches.
+
+   This module is imported by ssl.py. It should *not* be used
+   directly.
+
+   XXX should partial writes be enabled, SSL_MODE_ENABLE_PARTIAL_WRITE?
+
+   XXX integrate several "shutdown modes" as suggested in
+       http://bugs.python.org/issue8108#msg102867 ?
+*/
+
+#include "Python.h"
+
+#ifdef WITH_THREAD
+#include "pythread.h"
+#define PySSL_BEGIN_ALLOW_THREADS { \
+            PyThreadState *_save = NULL;  \
+            if (_ssl_locks_count>0) {_save = PyEval_SaveThread();}
+#define PySSL_BLOCK_THREADS     if (_ssl_locks_count>0){PyEval_RestoreThread(_save)};
+#define PySSL_UNBLOCK_THREADS   if (_ssl_locks_count>0){_save = PyEval_SaveThread()};
+#define PySSL_END_ALLOW_THREADS if (_ssl_locks_count>0){PyEval_RestoreThread(_save);} \
+         }
+
+#else   /* no WITH_THREAD */
+
+#define PySSL_BEGIN_ALLOW_THREADS
+#define PySSL_BLOCK_THREADS
+#define PySSL_UNBLOCK_THREADS
+#define PySSL_END_ALLOW_THREADS
+
+#endif
+
+enum py_ssl_error {
+    /* these mirror ssl.h */
+    PY_SSL_ERROR_NONE,
+    PY_SSL_ERROR_SSL,
+    PY_SSL_ERROR_WANT_READ,
+    PY_SSL_ERROR_WANT_WRITE,
+    PY_SSL_ERROR_WANT_X509_LOOKUP,
+    PY_SSL_ERROR_SYSCALL,     /* look at error stack/return value/errno */
+    PY_SSL_ERROR_ZERO_RETURN,
+    PY_SSL_ERROR_WANT_CONNECT,
+    /* start of non ssl.h errorcodes */
+    PY_SSL_ERROR_EOF,         /* special case of SSL_ERROR_SYSCALL */
+    PY_SSL_ERROR_INVALID_ERROR_CODE
+};
+
+enum py_ssl_server_or_client {
+    PY_SSL_CLIENT,
+    PY_SSL_SERVER
+};
+
+enum py_ssl_cert_requirements {
+    PY_SSL_CERT_NONE,
+    PY_SSL_CERT_OPTIONAL,
+    PY_SSL_CERT_REQUIRED
+};
+
+enum py_ssl_version {
+    PY_SSL_VERSION_SSL2,
+    PY_SSL_VERSION_SSL3,
+    PY_SSL_VERSION_SSL23,
+    PY_SSL_VERSION_TLS1
+};
+
+enum py_ssl_sess_cache_mode {
+    PY_SSL_SESS_CACHE_OFF,
+    PY_SSL_SESS_CACHE_CLIENT,
+    PY_SSL_SESS_CACHE_SERVER,
+    PY_SSL_SESS_CACHE_BOTH
+};
+
+/* Include symbols from _socket module */
+#include "socketmodule.h"
+
+#if defined(HAVE_POLL_H)
+#include <poll.h>
+#elif defined(HAVE_SYS_POLL_H)
+#include <sys/poll.h>
+#endif
+
+/* Include OpenSSL header files */
+#include "openssl/rsa.h"
+#include "openssl/crypto.h"
+#include "openssl/x509.h"
+#include "openssl/x509v3.h"
+#include "openssl/pem.h"
+#include "openssl/ssl.h"
+#include "openssl/err.h"
+#include "openssl/rand.h"
+
+/* SSL error object */
+static PyObject *PySSLErrorObject;
+
+#ifdef WITH_THREAD
+
+/* serves as a flag to see whether we've initialized the SSL thread support. */
+/* 0 means no, greater than 0 means yes */
+
+static unsigned int _ssl_locks_count = 0;
+
+#endif /* def WITH_THREAD */
+
+/* SSL socket object */
+
+#define X509_NAME_MAXLEN 256
+
+/* RAND_* APIs got added to OpenSSL in 0.9.5 */
+#if OPENSSL_VERSION_NUMBER >= 0x0090500fL
+# define HAVE_OPENSSL_RAND 1
+#else
+# undef HAVE_OPENSSL_RAND
+#endif
+
+typedef struct {
+    PyObject_HEAD
+    PySocketSockObject *Socket;         /* Socket on which we're layered */
+    int                 inherited;
+    SSL_CTX*            ctx;
+    SSL*                ssl;
+    X509*               peer_cert;
+    char                server[X509_NAME_MAXLEN];
+    char                issuer[X509_NAME_MAXLEN];
+    int                 shutdown_seen_zero;
+
+} PySSLObject;
+
+static PyTypeObject PySSL_Type;
+static PyObject *PySSL_SSLwrite(PySSLObject *self, PyObject *args);
+static PyObject *PySSL_SSLread(PySSLObject *self, PyObject *args);
+static int check_socket_and_wait_for_timeout(PySocketSockObject *s,
+                                             int writing);
+static PyObject *PySSL_peercert(PySSLObject *self, PyObject *args);
+static PyObject *PySSL_cipher(PySSLObject *self);
+
+#define PySSLObject_Check(v)    (Py_TYPE(v) == &PySSL_Type)
+
+typedef enum {
+    SOCKET_IS_NONBLOCKING,
+    SOCKET_IS_BLOCKING,
+    SOCKET_HAS_TIMED_OUT,
+    SOCKET_HAS_BEEN_CLOSED,
+    SOCKET_TOO_LARGE_FOR_SELECT,
+    SOCKET_OPERATION_OK
+} timeout_state;
+
+/* Wrap error strings with filename and line # */
+#define STRINGIFY1(x) #x
+#define STRINGIFY2(x) STRINGIFY1(x)
+#define ERRSTR1(x,y,z) (x ":" y ": " z)
+#define ERRSTR(x) ERRSTR1("_ssl.c", STRINGIFY2(__LINE__), x)
+
+/* XXX It might be helpful to augment the error message generated
+   below with the name of the SSL function that generated the error.
+   I expect it's obvious most of the time.
+*/
+
+static PyObject *
+PySSL_SetError(PySSLObject *obj, int ret, char *filename, int lineno)
+{
+    PyObject *v;
+    char buf[2048];
+    char *errstr;
+    int err;
+    enum py_ssl_error p = PY_SSL_ERROR_NONE;
+
+    assert(ret <= 0);
+
+    if (obj->ssl != NULL) {
+        err = SSL_get_error(obj->ssl, ret);
+
+        switch (err) {
+        case SSL_ERROR_ZERO_RETURN:
+            errstr = "TLS/SSL connection has been closed";
+            p = PY_SSL_ERROR_ZERO_RETURN;
+            break;
+        case SSL_ERROR_WANT_READ:
+            errstr = "The operation did not complete (read)";
+            p = PY_SSL_ERROR_WANT_READ;
+            break;
+        case SSL_ERROR_WANT_WRITE:
+            p = PY_SSL_ERROR_WANT_WRITE;
+            errstr = "The operation did not complete (write)";
+            break;
+        case SSL_ERROR_WANT_X509_LOOKUP:
+            p = PY_SSL_ERROR_WANT_X509_LOOKUP;
+            errstr = "The operation did not complete (X509 lookup)";
+            break;
+        case SSL_ERROR_WANT_CONNECT:
+            p = PY_SSL_ERROR_WANT_CONNECT;
+            errstr = "The operation did not complete (connect)";
+            break;
+        case SSL_ERROR_SYSCALL:
+        {
+            unsigned long e = ERR_get_error();
+            if (e == 0) {
+                if (ret == 0 || !obj->Socket) {
+                    p = PY_SSL_ERROR_EOF;
+                    errstr = "EOF occurred in violation of protocol";
+                } else if (ret == -1) {
+                    /* underlying BIO reported an I/O error */
+                    ERR_clear_error();
+                    return obj->Socket->errorhandler();
+                } else { /* possible? */
+                    p = PY_SSL_ERROR_SYSCALL;
+                    errstr = "Some I/O error occurred";
+                }
+            } else {
+                p = PY_SSL_ERROR_SYSCALL;
+                /* XXX Protected by global interpreter lock */
+                errstr = ERR_error_string(e, NULL);
+            }
+            break;
+        }
+        case SSL_ERROR_SSL:
+        {
+            unsigned long e = ERR_get_error();
+            p = PY_SSL_ERROR_SSL;
+            if (e != 0)
+                /* XXX Protected by global interpreter lock */
+                errstr = ERR_error_string(e, NULL);
+            else {              /* possible? */
+                errstr = "A failure in the SSL library occurred";
+            }
+            break;
+        }
+        default:
+            p = PY_SSL_ERROR_INVALID_ERROR_CODE;
+            errstr = "Invalid error code";
+        }
+    } else {
+        errstr = ERR_error_string(ERR_peek_last_error(), NULL);
+    }
+    PyOS_snprintf(buf, sizeof(buf), "_ssl.c:%d: %s", lineno, errstr);
+    ERR_clear_error();
+    v = Py_BuildValue("(is)", p, buf);
+    if (v != NULL) {
+        PyErr_SetObject(PySSLErrorObject, v);
+        Py_DECREF(v);
+    }
+    return NULL;
+}
+
+static PyObject *
+_setSSLError (char *errstr, int errcode, char *filename, int lineno) {
+
+    char buf[2048];
+    PyObject *v;
+
+    if (errstr == NULL) {
+        errcode = ERR_peek_last_error();
+        errstr = ERR_error_string(errcode, NULL);
+    }
+    PyOS_snprintf(buf, sizeof(buf), "_ssl.c:%d: %s", lineno, errstr);
+    ERR_clear_error();
+    v = Py_BuildValue("(is)", errcode, buf);
+    if (v != NULL) {
+        PyErr_SetObject(PySSLErrorObject, v);
+        Py_DECREF(v);
+    }
+    return NULL;
+}
+
+static PySSLObject *
+newPySSLObject(PySSLObject *ssl_object, PySocketSockObject *Sock,
+               char *key_file, char *cert_file,
+               enum py_ssl_server_or_client socket_type,
+               enum py_ssl_cert_requirements certreq,
+               enum py_ssl_version proto_version,
+               enum py_ssl_sess_cache_mode cache_mode,
+               char *sess_id_ctx,
+               char *cacerts_file)
+{
+    PySSLObject *self;
+    char *errstr = NULL;
+    int ret;
+    int verification_mode;
+    int sess_cache_mode;
+
+    self = PyObject_New(PySSLObject, &PySSL_Type); /* Create new object */
+    if (self == NULL)
+        return NULL;
+    memset(self->server, '\0', sizeof(char) * X509_NAME_MAXLEN);
+    memset(self->issuer, '\0', sizeof(char) * X509_NAME_MAXLEN);
+    self->peer_cert = NULL;
+    self->inherited = 0;
+    self->ssl = NULL;
+    self->ctx = NULL;
+    self->Socket = NULL;
+
+    /* Make sure the SSL error state is initialized */
+    (void) ERR_get_state();
+    ERR_clear_error();
+
+    if ((key_file && !cert_file) || (!key_file && cert_file)) {
+        errstr = ERRSTR("Both the key & certificate files "
+                        "must be specified");
+        goto fail;
+    }
+
+    if ((socket_type == PY_SSL_SERVER) && (ssl_object == NULL) &&
+        ((key_file == NULL) || (cert_file == NULL))) {
+        errstr = ERRSTR("Both the key & certificate files "
+                        "must be specified for server-side operation");
+        goto fail;
+    }
+
+    if (ssl_object != NULL) {
+        self->inherited = 1;
+        self->ctx = ssl_object->ctx;
+    } else {
+        self->inherited = 0;
+
+        PySSL_BEGIN_ALLOW_THREADS
+        if (proto_version == PY_SSL_VERSION_TLS1)
+            self->ctx = SSL_CTX_new(TLSv1_method()); /* Set up context */
+        else if (proto_version == PY_SSL_VERSION_SSL3)
+            self->ctx = SSL_CTX_new(SSLv3_method()); /* Set up context */
+        else if (proto_version == PY_SSL_VERSION_SSL2)
+            self->ctx = SSL_CTX_new(SSLv2_method()); /* Set up context */
+        else if (proto_version == PY_SSL_VERSION_SSL23)
+            self->ctx = SSL_CTX_new(SSLv23_method()); /* Set up context */
+        PySSL_END_ALLOW_THREADS
+    }
+
+    if (self->ctx == NULL) {
+        errstr = ERRSTR("Invalid SSL protocol variant specified.");
+        goto fail;
+    }
+
+    if (self->inherited == 0 && certreq != PY_SSL_CERT_NONE) {
+        if (cacerts_file == NULL) {
+            errstr = ERRSTR("No root certificates specified for "
+                            "verification of other-side certificates.");
+            goto fail;
+        } else {
+            PySSL_BEGIN_ALLOW_THREADS
+            ret = SSL_CTX_load_verify_locations(self->ctx,
+                                                cacerts_file,
+                                                NULL);
+            PySSL_END_ALLOW_THREADS
+            if (ret != 1) {
+                _setSSLError(NULL, 0, __FILE__, __LINE__);
+                goto fail;
+            }
+        }
+    }
+    if (self->inherited == 0 && key_file) {
+        PySSL_BEGIN_ALLOW_THREADS
+        ret = SSL_CTX_use_PrivateKey_file(self->ctx, key_file,
+                                          SSL_FILETYPE_PEM);
+        PySSL_END_ALLOW_THREADS
+        if (ret != 1) {
+            _setSSLError(NULL, ret, __FILE__, __LINE__);
+            goto fail;
+        }
+
+        PySSL_BEGIN_ALLOW_THREADS
+        ret = SSL_CTX_use_certificate_chain_file(self->ctx,
+                                                 cert_file);
+        PySSL_END_ALLOW_THREADS
+        if (ret != 1) {
+            /*
+            fprintf(stderr, "ret is %d, errcode is %lu, %lu, with file \"%s\"\n",
+                ret, ERR_peek_error(), ERR_peek_last_error(), cert_file);
+                */
+            if (ERR_peek_last_error() != 0) {
+                _setSSLError(NULL, ret, __FILE__, __LINE__);
+                goto fail;
+            }
+        }
+    }
+
+    if (self->inherited == 0) {
+        /* ssl compatibility */
+        SSL_CTX_set_options(self->ctx, SSL_OP_ALL);
+
+        /* session cache mode */
+        PySSL_BEGIN_ALLOW_THREADS
+        sess_cache_mode = SSL_SESS_CACHE_SERVER;
+        if (cache_mode == PY_SSL_SESS_CACHE_OFF)
+            sess_cache_mode = SSL_SESS_CACHE_OFF;
+        else if (cache_mode == PY_SSL_SESS_CACHE_CLIENT)
+            sess_cache_mode = SSL_SESS_CACHE_CLIENT;
+        else if (cache_mode == PY_SSL_SESS_CACHE_SERVER)
+            sess_cache_mode = SSL_SESS_CACHE_SERVER;
+        else if (cache_mode == PY_SSL_SESS_CACHE_BOTH)
+            sess_cache_mode = SSL_SESS_CACHE_BOTH;
+        SSL_CTX_set_session_cache_mode(self->ctx, sess_cache_mode);
+
+        /* session id context */
+        if (sess_id_ctx != NULL)
+           SSL_CTX_set_session_id_context(self->ctx,
+                                          (const unsigned char*)sess_id_ctx,
+                                          strlen(sess_id_ctx));
+        PySSL_END_ALLOW_THREADS
+
+        verification_mode = SSL_VERIFY_NONE;
+        if (certreq == PY_SSL_CERT_OPTIONAL)
+            verification_mode = SSL_VERIFY_PEER;
+        else if (certreq == PY_SSL_CERT_REQUIRED)
+            verification_mode = (SSL_VERIFY_PEER |
+                                 SSL_VERIFY_FAIL_IF_NO_PEER_CERT);
+        SSL_CTX_set_verify(self->ctx, verification_mode,
+                           NULL); /* set verify lvl */
+    }
+
+    self->ssl = SSL_new(self->ctx); /* New ssl struct */
+    SSL_set_fd(self->ssl, Sock->sock_fd);       /* Set the socket for SSL */
+#ifdef SSL_MODE_AUTO_RETRY
+    SSL_set_mode(self->ssl, SSL_MODE_AUTO_RETRY);
+#endif
+
+    /* If the socket is in non-blocking mode or timeout mode, set the BIO
+     * to non-blocking mode (blocking is the default)
+     */
+    if (Sock->sock_timeout >= 0.0) {
+        /* Set both the read and write BIO's to non-blocking mode */
+        BIO_set_nbio(SSL_get_rbio(self->ssl), 1);
+        BIO_set_nbio(SSL_get_wbio(self->ssl), 1);
+    }
+
+    PySSL_BEGIN_ALLOW_THREADS
+    if (socket_type == PY_SSL_CLIENT)
+        SSL_set_connect_state(self->ssl);
+    else
+        SSL_set_accept_state(self->ssl);
+    PySSL_END_ALLOW_THREADS
+
+    self->Socket = Sock;
+    Py_INCREF(self->Socket);
+    return self;
+ fail:
+    if (errstr)
+        PyErr_SetString(PySSLErrorObject, errstr);
+    Py_DECREF(self);
+    return NULL;
+}
+
+static PyObject *
+PySSL_sslwrap(PyObject *self, PyObject *args)
+{
+    PySocketSockObject *Sock;
+    int server_side = 0;
+    int verification_mode = PY_SSL_CERT_NONE;
+    int protocol = PY_SSL_VERSION_SSL23;
+    int sess_cache_mode = PY_SSL_SESS_CACHE_SERVER;
+    char *sess_id_ctx = NULL;
+    char *key_file = NULL;
+    char *cert_file = NULL;
+    char *cacerts_file = NULL;
+
+    if (!PyArg_ParseTuple(args, "O!i|zziiizz:sslwrap",
+                          PySocketModule.Sock_Type,
+                          &Sock,
+                          &server_side,
+                          &key_file, &cert_file,
+                          &verification_mode, &protocol,
+                          &sess_cache_mode, &sess_id_ctx,
+                          &cacerts_file))
+        return NULL;
+
+    /*
+    fprintf(stderr,
+        "server_side is %d, keyfile %p, certfile %p, verify_mode %d, "
+        "protocol %d, sess_cache_mode %d, sess_id_ctx %p, certs %p\n",
+        server_side, key_file, cert_file, verification_mode,
+        protocol, sess_cache_mode, sess_id_ctx, cacerts_file);
+     */
+
+    return (PyObject *) newPySSLObject(NULL, Sock, key_file, cert_file,
+                                       server_side, verification_mode,
+                                       protocol, sess_cache_mode, sess_id_ctx,
+                                       cacerts_file);
+}
+
+PyDoc_STRVAR(sslwrap_doc,
+"sslwrap(socket, server_side, [keyfile, certfile, certs_mode, protocol,\n"
+"                              sess_cache_mode, sess_id_ctx, cacertsfile]) -> sslobject");
+
+/* SSL object methods */
+
+static PyObject *
+PySSL_SSLwrap_accepted(PySSLObject *self, PyObject *args)
+{
+    PySocketSockObject *Sock;
+
+    if (!PyArg_ParseTuple(args, "O!:sslwrap",
+                          PySocketModule.Sock_Type,
+                          &Sock))
+        return NULL;
+
+    return (PyObject *) newPySSLObject(self, Sock, NULL, NULL,
+                                       PY_SSL_SERVER, 0, 0, 0, NULL, NULL);
+}
+
+PyDoc_STRVAR(PySSL_SSLwrap_accepted_doc,
+"wrap_accepted(socket) -> sslobject");
+
+static PyObject *PySSL_SSLdo_handshake(PySSLObject *self)
+{
+    int ret;
+    int err;
+    int sockstate, nonblocking;
+
+    /* just in case the blocking state of the socket has been changed */
+    nonblocking = (self->Socket->sock_timeout >= 0.0);
+    BIO_set_nbio(SSL_get_rbio(self->ssl), nonblocking);
+    BIO_set_nbio(SSL_get_wbio(self->ssl), nonblocking);
+
+    /* Actually negotiate SSL connection */
+    /* XXX If SSL_do_handshake() returns 0, it's also a failure. */
+    sockstate = 0;
+    do {
+        PySSL_BEGIN_ALLOW_THREADS
+        ret = SSL_do_handshake(self->ssl);
+        err = SSL_get_error(self->ssl, ret);
+        PySSL_END_ALLOW_THREADS
+        if(PyErr_CheckSignals()) {
+            return NULL;
+        }
+        if (err == SSL_ERROR_WANT_READ) {
+            sockstate = check_socket_and_wait_for_timeout(self->Socket, 0);
+        } else if (err == SSL_ERROR_WANT_WRITE) {
+            sockstate = check_socket_and_wait_for_timeout(self->Socket, 1);
+        } else {
+            sockstate = SOCKET_OPERATION_OK;
+        }
+        if (sockstate == SOCKET_HAS_TIMED_OUT) {
+            PyErr_SetString(PySSLErrorObject,
+                            ERRSTR("The handshake operation timed out"));
+            return NULL;
+        } else if (sockstate == SOCKET_HAS_BEEN_CLOSED) {
+            PyErr_SetString(PySSLErrorObject,
+                            ERRSTR("Underlying socket has been closed."));
+            return NULL;
+        } else if (sockstate == SOCKET_TOO_LARGE_FOR_SELECT) {
+            PyErr_SetString(PySSLErrorObject,
+                            ERRSTR("Underlying socket too large for select()."));
+            return NULL;
+        } else if (sockstate == SOCKET_IS_NONBLOCKING) {
+            break;
+        }
+    } while (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE);
+    if (ret < 1)
+        return PySSL_SetError(self, ret, __FILE__, __LINE__);
+
+    if (self->peer_cert)
+        X509_free (self->peer_cert);
+    PySSL_BEGIN_ALLOW_THREADS
+    if ((self->peer_cert = SSL_get_peer_certificate(self->ssl))) {
+        X509_NAME_oneline(X509_get_subject_name(self->peer_cert),
+                          self->server, X509_NAME_MAXLEN);
+        X509_NAME_oneline(X509_get_issuer_name(self->peer_cert),
+                          self->issuer, X509_NAME_MAXLEN);
+    }
+    PySSL_END_ALLOW_THREADS
+
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+static PyObject *
+PySSL_server(PySSLObject *self)
+{
+    return PyString_FromString(self->server);
+}
+
+static PyObject *
+PySSL_issuer(PySSLObject *self)
+{
+    return PyString_FromString(self->issuer);
+}
+
+static PyObject *
+_create_tuple_for_attribute (ASN1_OBJECT *name, ASN1_STRING *value) {
+
+    char namebuf[X509_NAME_MAXLEN];
+    int buflen;
+    PyObject *name_obj;
+    PyObject *value_obj;
+    PyObject *attr;
+    unsigned char *valuebuf = NULL;
+
+    buflen = OBJ_obj2txt(namebuf, sizeof(namebuf), name, 0);
+    if (buflen < 0) {
+        _setSSLError(NULL, 0, __FILE__, __LINE__);
+        goto fail;
+    }
+    name_obj = PyString_FromStringAndSize(namebuf, buflen);
+    if (name_obj == NULL)
+        goto fail;
+
+    buflen = ASN1_STRING_to_UTF8(&valuebuf, value);
+    if (buflen < 0) {
+        _setSSLError(NULL, 0, __FILE__, __LINE__);
+        Py_DECREF(name_obj);
+        goto fail;
+    }
+    value_obj = PyUnicode_DecodeUTF8((char *) valuebuf,
+                                     buflen, "strict");
+    OPENSSL_free(valuebuf);
+    if (value_obj == NULL) {
+        Py_DECREF(name_obj);
+        goto fail;
+    }
+    attr = PyTuple_New(2);
+    if (attr == NULL) {
+        Py_DECREF(name_obj);
+        Py_DECREF(value_obj);
+        goto fail;
+    }
+    PyTuple_SET_ITEM(attr, 0, name_obj);
+    PyTuple_SET_ITEM(attr, 1, value_obj);
+    return attr;
+
+  fail:
+    return NULL;
+}
+
+static PyObject *
+_create_tuple_for_X509_NAME (X509_NAME *xname)
+{
+    PyObject *dn = NULL;    /* tuple which represents the "distinguished name" */
+    PyObject *rdn = NULL;   /* tuple to hold a "relative distinguished name" */
+    PyObject *rdnt;
+    PyObject *attr = NULL;   /* tuple to hold an attribute */
+    int entry_count = X509_NAME_entry_count(xname);
+    X509_NAME_ENTRY *entry;
+    ASN1_OBJECT *name;
+    ASN1_STRING *value;
+    int index_counter;
+    int rdn_level = -1;
+    int retcode;
+
+    dn = PyList_New(0);
+    if (dn == NULL)
+        return NULL;
+    /* now create another tuple to hold the top-level RDN */
+    rdn = PyList_New(0);
+    if (rdn == NULL)
+        goto fail0;
+
+    for (index_counter = 0;
+         index_counter < entry_count;
+         index_counter++)
+    {
+        entry = X509_NAME_get_entry(xname, index_counter);
+
+        /* check to see if we've gotten to a new RDN */
+        if (rdn_level >= 0) {
+            if (rdn_level != entry->set) {
+                /* yes, new RDN */
+                /* add old RDN to DN */
+                rdnt = PyList_AsTuple(rdn);
+                Py_DECREF(rdn);
+                if (rdnt == NULL)
+                    goto fail0;
+                retcode = PyList_Append(dn, rdnt);
+                Py_DECREF(rdnt);
+                if (retcode < 0)
+                    goto fail0;
+                /* create new RDN */
+                rdn = PyList_New(0);
+                if (rdn == NULL)
+                    goto fail0;
+            }
+        }
+        rdn_level = entry->set;
+
+        /* now add this attribute to the current RDN */
+        name = X509_NAME_ENTRY_get_object(entry);
+        value = X509_NAME_ENTRY_get_data(entry);
+        attr = _create_tuple_for_attribute(name, value);
+        /*
+        fprintf(stderr, "RDN level %d, attribute %s: %s\n",
+            entry->set,
+            PyString_AS_STRING(PyTuple_GET_ITEM(attr, 0)),
+            PyString_AS_STRING(PyTuple_GET_ITEM(attr, 1)));
+        */
+        if (attr == NULL)
+            goto fail1;
+        retcode = PyList_Append(rdn, attr);
+        Py_DECREF(attr);
+        if (retcode < 0)
+            goto fail1;
+    }
+    /* now, there's typically a dangling RDN */
+    if ((rdn != NULL) && (PyList_Size(rdn) > 0)) {
+        rdnt = PyList_AsTuple(rdn);
+        Py_DECREF(rdn);
+        if (rdnt == NULL)
+            goto fail0;
+        retcode = PyList_Append(dn, rdnt);
+        Py_DECREF(rdnt);
+        if (retcode < 0)
+            goto fail0;
+    }
+
+    /* convert list to tuple */
+    rdnt = PyList_AsTuple(dn);
+    Py_DECREF(dn);
+    if (rdnt == NULL)
+        return NULL;
+    return rdnt;
+
+  fail1:
+    Py_XDECREF(rdn);
+
+  fail0:
+    Py_XDECREF(dn);
+    return NULL;
+}
+
+static PyObject *
+_get_peer_alt_names (X509 *certificate) {
+
+    /* this code follows the procedure outlined in
+       OpenSSL's crypto/x509v3/v3_prn.c:X509v3_EXT_print()
+       function to extract the STACK_OF(GENERAL_NAME),
+       then iterates through the stack to add the
+       names. */
+
+    int i, j;
+    PyObject *peer_alt_names = Py_None;
+    PyObject *v, *t;
+    X509_EXTENSION *ext = NULL;
+    GENERAL_NAMES *names = NULL;
+    GENERAL_NAME *name;
+    X509V3_EXT_METHOD *method;
+    BIO *biobuf = NULL;
+    char buf[2048];
+    char *vptr;
+    int len;
+    const unsigned char *p;
+
+    if (certificate == NULL)
+        return peer_alt_names;
+
+    /* get a memory buffer */
+    biobuf = BIO_new(BIO_s_mem());
+
+    i = 0;
+    while ((i = X509_get_ext_by_NID(
+                    certificate, NID_subject_alt_name, i)) >= 0) {
+
+        if (peer_alt_names == Py_None) {
+            peer_alt_names = PyList_New(0);
+            if (peer_alt_names == NULL)
+                goto fail;
+        }
+
+        /* now decode the altName */
+        ext = X509_get_ext(certificate, i);
+        if(!(method = X509V3_EXT_get(ext))) {
+            PyErr_SetString(PySSLErrorObject,
+                            ERRSTR("No method for internalizing subjectAltName!"));
+            goto fail;
+        }
+
+        p = ext->value->data;
+        if (method->it)
+            names = (GENERAL_NAMES*) (ASN1_item_d2i(NULL,
+                                                    &p,
+                                                    ext->value->length,
+                                                    ASN1_ITEM_ptr(method->it)));
+        else
+            names = (GENERAL_NAMES*) (method->d2i(NULL,
+                                                  &p,
+                                                  ext->value->length));
+
+        for(j = 0; j < sk_GENERAL_NAME_num(names); j++) {
+
+            /* get a rendering of each name in the set of names */
+
+            name = sk_GENERAL_NAME_value(names, j);
+            if (name->type == GEN_DIRNAME) {
+
+                /* we special-case DirName as a tuple of tuples of attributes */
+
+                t = PyTuple_New(2);
+                if (t == NULL) {
+                    goto fail;
+                }
+
+                v = PyString_FromString("DirName");
+                if (v == NULL) {
+                    Py_DECREF(t);
+                    goto fail;
+                }
+                PyTuple_SET_ITEM(t, 0, v);
+
+                v = _create_tuple_for_X509_NAME (name->d.dirn);
+                if (v == NULL) {
+                    Py_DECREF(t);
+                    goto fail;
+                }
+                PyTuple_SET_ITEM(t, 1, v);
+
+            } else {
+
+                /* for everything else, we use the OpenSSL print form */
+
+                (void) BIO_reset(biobuf);
+                GENERAL_NAME_print(biobuf, name);
+                len = BIO_gets(biobuf, buf, sizeof(buf)-1);
+                if (len < 0) {
+                    _setSSLError(NULL, 0, __FILE__, __LINE__);
+                    goto fail;
+                }
+                vptr = strchr(buf, ':');
+                if (vptr == NULL)
+                    goto fail;
+                t = PyTuple_New(2);
+                if (t == NULL)
+                    goto fail;
+                v = PyString_FromStringAndSize(buf, (vptr - buf));
+                if (v == NULL) {
+                    Py_DECREF(t);
+                    goto fail;
+                }
+                PyTuple_SET_ITEM(t, 0, v);
+                v = PyString_FromStringAndSize((vptr + 1), (len - (vptr - buf + 1)));
+                if (v == NULL) {
+                    Py_DECREF(t);
+                    goto fail;
+                }
+                PyTuple_SET_ITEM(t, 1, v);
+            }
+
+            /* and add that rendering to the list */
+
+            if (PyList_Append(peer_alt_names, t) < 0) {
+                Py_DECREF(t);
+                goto fail;
+            }
+            Py_DECREF(t);
+        }
+    }
+    BIO_free(biobuf);
+    if (peer_alt_names != Py_None) {
+        v = PyList_AsTuple(peer_alt_names);
+        Py_DECREF(peer_alt_names);
+        return v;
+    } else {
+        return peer_alt_names;
+    }
+
+
+  fail:
+    if (biobuf != NULL)
+        BIO_free(biobuf);
+
+    if (peer_alt_names != Py_None) {
+        Py_XDECREF(peer_alt_names);
+    }
+
+    return NULL;
+}
+
+static PyObject *
+_decode_certificate (X509 *certificate, int verbose) {
+
+    PyObject *retval = NULL;
+    BIO *biobuf = NULL;
+    PyObject *peer;
+    PyObject *peer_alt_names = NULL;
+    PyObject *issuer;
+    PyObject *version;
+    PyObject *sn_obj;
+    ASN1_INTEGER *serialNumber;
+    char buf[2048];
+    int len;
+    ASN1_TIME *notBefore, *notAfter;
+    PyObject *pnotBefore, *pnotAfter;
+
+    retval = PyDict_New();
+    if (retval == NULL)
+        return NULL;
+
+    peer = _create_tuple_for_X509_NAME(
+        X509_get_subject_name(certificate));
+    if (peer == NULL)
+        goto fail0;
+    if (PyDict_SetItemString(retval, (const char *) "subject", peer) < 0) {
+        Py_DECREF(peer);
+        goto fail0;
+    }
+    Py_DECREF(peer);
+
+    if (verbose) {
+        issuer = _create_tuple_for_X509_NAME(
+            X509_get_issuer_name(certificate));
+        if (issuer == NULL)
+            goto fail0;
+        if (PyDict_SetItemString(retval, (const char *)"issuer", issuer) < 0) {
+            Py_DECREF(issuer);
+            goto fail0;
+        }
+        Py_DECREF(issuer);
+
+        version = PyInt_FromLong(X509_get_version(certificate) + 1);
+        if (PyDict_SetItemString(retval, "version", version) < 0) {
+            Py_DECREF(version);
+            goto fail0;
+        }
+        Py_DECREF(version);
+    }
+
+    /* get a memory buffer */
+    biobuf = BIO_new(BIO_s_mem());
+
+    if (verbose) {
+
+        (void) BIO_reset(biobuf);
+        serialNumber = X509_get_serialNumber(certificate);
+        /* should not exceed 20 octets, 160 bits, so buf is big enough */
+        i2a_ASN1_INTEGER(biobuf, serialNumber);
+        len = BIO_gets(biobuf, buf, sizeof(buf)-1);
+        if (len < 0) {
+            _setSSLError(NULL, 0, __FILE__, __LINE__);
+            goto fail1;
+        }
+        sn_obj = PyString_FromStringAndSize(buf, len);
+        if (sn_obj == NULL)
+            goto fail1;
+        if (PyDict_SetItemString(retval, "serialNumber", sn_obj) < 0) {
+            Py_DECREF(sn_obj);
+            goto fail1;
+        }
+        Py_DECREF(sn_obj);
+
+        (void) BIO_reset(biobuf);
+        notBefore = X509_get_notBefore(certificate);
+        ASN1_TIME_print(biobuf, notBefore);
+        len = BIO_gets(biobuf, buf, sizeof(buf)-1);
+        if (len < 0) {
+            _setSSLError(NULL, 0, __FILE__, __LINE__);
+            goto fail1;
+        }
+        pnotBefore = PyString_FromStringAndSize(buf, len);
+        if (pnotBefore == NULL)
+            goto fail1;
+        if (PyDict_SetItemString(retval, "notBefore", pnotBefore) < 0) {
+            Py_DECREF(pnotBefore);
+            goto fail1;
+        }
+        Py_DECREF(pnotBefore);
+    }
+
+    (void) BIO_reset(biobuf);
+    notAfter = X509_get_notAfter(certificate);
+    ASN1_TIME_print(biobuf, notAfter);
+    len = BIO_gets(biobuf, buf, sizeof(buf)-1);
+    if (len < 0) {
+        _setSSLError(NULL, 0, __FILE__, __LINE__);
+        goto fail1;
+    }
+    pnotAfter = PyString_FromStringAndSize(buf, len);
+    if (pnotAfter == NULL)
+        goto fail1;
+    if (PyDict_SetItemString(retval, "notAfter", pnotAfter) < 0) {
+        Py_DECREF(pnotAfter);
+        goto fail1;
+    }
+    Py_DECREF(pnotAfter);
+
+    /* Now look for subjectAltName */
+
+    peer_alt_names = _get_peer_alt_names(certificate);
+    if (peer_alt_names == NULL)
+        goto fail1;
+    else if (peer_alt_names != Py_None) {
+        if (PyDict_SetItemString(retval, "subjectAltName",
+                                 peer_alt_names) < 0) {
+            Py_DECREF(peer_alt_names);
+            goto fail1;
+        }
+        Py_DECREF(peer_alt_names);
+    }
+
+    BIO_free(biobuf);
+    return retval;
+
+  fail1:
+    if (biobuf != NULL)
+        BIO_free(biobuf);
+  fail0:
+    Py_XDECREF(retval);
+    return NULL;
+}
+
+
+static PyObject *
+PySSL_test_decode_certificate (PyObject *mod, PyObject *args) {
+
+    PyObject *retval = NULL;
+    char *filename = NULL;
+    X509 *x=NULL;
+    BIO *cert;
+    int verbose = 1;
+
+    if (!PyArg_ParseTuple(args, "s|i:test_decode_certificate", &filename, &verbose))
+        return NULL;
+
+    if ((cert=BIO_new(BIO_s_file())) == NULL) {
+        PyErr_SetString(PySSLErrorObject, "Can't malloc memory to read file");
+        goto fail0;
+    }
+
+    if (BIO_read_filename(cert,filename) <= 0) {
+        PyErr_SetString(PySSLErrorObject, "Can't open file");
+        goto fail0;
+    }
+
+    x = PEM_read_bio_X509_AUX(cert,NULL, NULL, NULL);
+    if (x == NULL) {
+        PyErr_SetString(PySSLErrorObject, "Error decoding PEM-encoded file");
+        goto fail0;
+    }
+
+    retval = _decode_certificate(x, verbose);
+
+  fail0:
+
+    if (cert != NULL) BIO_free(cert);
+    return retval;
+}
+
+
+static PyObject *
+PySSL_peercert(PySSLObject *self, PyObject *args)
+{
+    PyObject *retval = NULL;
+    int len;
+    int verification;
+    PyObject *binary_mode = Py_None;
+
+    if (!PyArg_ParseTuple(args, "|O:peer_certificate", &binary_mode))
+        return NULL;
+
+    if (!self->peer_cert)
+        Py_RETURN_NONE;
+
+    if (PyObject_IsTrue(binary_mode)) {
+        /* return cert in DER-encoded format */
+
+        unsigned char *bytes_buf = NULL;
+
+        bytes_buf = NULL;
+        len = i2d_X509(self->peer_cert, &bytes_buf);
+        if (len < 0) {
+            PySSL_SetError(self, len, __FILE__, __LINE__);
+            return NULL;
+        }
+        retval = PyString_FromStringAndSize((const char *) bytes_buf, len);
+        OPENSSL_free(bytes_buf);
+        return retval;
+
+    } else {
+
+        verification = SSL_CTX_get_verify_mode(self->ctx);
+        if ((verification & SSL_VERIFY_PEER) == 0)
+            return PyDict_New();
+        else
+            return _decode_certificate (self->peer_cert, 0);
+    }
+}
+
+PyDoc_STRVAR(PySSL_peercert_doc,
+"peer_certificate([der=False]) -> certificate\n\
+\n\
+Returns the certificate for the peer.  If no certificate was provided,\n\
+returns None.  If a certificate was provided, but not validated, returns\n\
+an empty dictionary.  Otherwise returns a dict containing information\n\
+about the peer certificate.\n\
+\n\
+If the optional argument is True, returns a DER-encoded copy of the\n\
+peer certificate, or None if no certificate was provided.  This will\n\
+return the certificate even if it wasn't validated.");
+
+static PyObject *PySSL_cipher (PySSLObject *self) {
+
+    PyObject *retval, *v;
+    SSL_CIPHER *current;
+    char *cipher_name;
+    char *cipher_protocol;
+
+    if (self->ssl == NULL)
+        return Py_None;
+    current = SSL_get_current_cipher(self->ssl);
+    if (current == NULL)
+        return Py_None;
+
+    retval = PyTuple_New(3);
+    if (retval == NULL)
+        return NULL;
+
+    cipher_name = (char *) SSL_CIPHER_get_name(current);
+    if (cipher_name == NULL) {
+        PyTuple_SET_ITEM(retval, 0, Py_None);
+    } else {
+        v = PyString_FromString(cipher_name);
+        if (v == NULL)
+            goto fail0;
+        PyTuple_SET_ITEM(retval, 0, v);
+    }
+    cipher_protocol = SSL_CIPHER_get_version(current);
+    if (cipher_protocol == NULL) {
+        PyTuple_SET_ITEM(retval, 1, Py_None);
+    } else {
+        v = PyString_FromString(cipher_protocol);
+        if (v == NULL)
+            goto fail0;
+        PyTuple_SET_ITEM(retval, 1, v);
+    }
+    v = PyInt_FromLong(SSL_CIPHER_get_bits(current, NULL));
+    if (v == NULL)
+        goto fail0;
+    PyTuple_SET_ITEM(retval, 2, v);
+    return retval;
+
+  fail0:
+    Py_DECREF(retval);
+    return NULL;
+}
+
+static void PySSL_dealloc(PySSLObject *self)
+{
+    if (self->peer_cert)        /* Possible not to have one? */
+        X509_free (self->peer_cert);
+    if (self->ssl)
+        SSL_free(self->ssl);
+    if (self->ctx && self->inherited == 0)
+        SSL_CTX_free(self->ctx);
+    Py_XDECREF(self->Socket);
+    PyObject_Del(self);
+}
+
+/* If the socket has a timeout, do a select()/poll() on the socket.
+   The argument writing indicates the direction.
+   Returns one of the possibilities in the timeout_state enum (above).
+ */
+
+static int
+check_socket_and_wait_for_timeout(PySocketSockObject *s, int writing)
+{
+    fd_set fds;
+    struct timeval tv;
+    int rc;
+
+    /* Nothing to do unless we're in timeout mode (not non-blocking) */
+    if (s->sock_timeout < 0.0)
+        return SOCKET_IS_BLOCKING;
+    else if (s->sock_timeout == 0.0)
+        return SOCKET_IS_NONBLOCKING;
+
+    /* Guard against closed socket */
+    if (s->sock_fd < 0)
+        return SOCKET_HAS_BEEN_CLOSED;
+
+    /* Prefer poll, if available, since you can poll() any fd
+     * which can't be done with select(). */
+#ifdef HAVE_POLL
+    {
+        struct pollfd pollfd;
+        int timeout;
+
+        pollfd.fd = s->sock_fd;
+        pollfd.events = writing ? POLLOUT : POLLIN;
+
+        /* s->sock_timeout is in seconds, timeout in ms */
+        timeout = (int)(s->sock_timeout * 1000 + 0.5);
+        PySSL_BEGIN_ALLOW_THREADS
+        rc = poll(&pollfd, 1, timeout);
+        PySSL_END_ALLOW_THREADS
+
+        goto normal_return;
+    }
+#endif
+
+    /* Guard against socket too large for select*/
+#ifndef Py_SOCKET_FD_CAN_BE_GE_FD_SETSIZE
+    if (s->sock_fd >= FD_SETSIZE)
+        return SOCKET_TOO_LARGE_FOR_SELECT;
+#endif
+
+    /* Construct the arguments to select */
+    tv.tv_sec = (int)s->sock_timeout;
+    tv.tv_usec = (int)((s->sock_timeout - tv.tv_sec) * 1e6);
+    FD_ZERO(&fds);
+    FD_SET(s->sock_fd, &fds);
+
+    /* See if the socket is ready */
+    PySSL_BEGIN_ALLOW_THREADS
+    if (writing)
+        rc = select(s->sock_fd+1, NULL, &fds, NULL, &tv);
+    else
+        rc = select(s->sock_fd+1, &fds, NULL, NULL, &tv);
+    PySSL_END_ALLOW_THREADS
+
+#ifdef HAVE_POLL
+normal_return:
+#endif
+    /* Return SOCKET_TIMED_OUT on timeout, SOCKET_OPERATION_OK otherwise
+       (when we are able to write or when there's something to read) */
+    return rc == 0 ? SOCKET_HAS_TIMED_OUT : SOCKET_OPERATION_OK;
+}
+
+static PyObject *PySSL_SSLwrite(PySSLObject *self, PyObject *args)
+{
+    char *data;
+    int len;
+    int count;
+    int sockstate;
+    int err;
+    int nonblocking;
+
+    if (!PyArg_ParseTuple(args, "s#:write", &data, &count))
+        return NULL;
+
+    /* just in case the blocking state of the socket has been changed */
+    nonblocking = (self->Socket->sock_timeout >= 0.0);
+    BIO_set_nbio(SSL_get_rbio(self->ssl), nonblocking);
+    BIO_set_nbio(SSL_get_wbio(self->ssl), nonblocking);
+
+    sockstate = check_socket_and_wait_for_timeout(self->Socket, 1);
+    if (sockstate == SOCKET_HAS_TIMED_OUT) {
+        PyErr_SetString(PySSLErrorObject,
+                        "The write operation timed out");
+        return NULL;
+    } else if (sockstate == SOCKET_HAS_BEEN_CLOSED) {
+        PyErr_SetString(PySSLErrorObject,
+                        "Underlying socket has been closed.");
+        return NULL;
+    } else if (sockstate == SOCKET_TOO_LARGE_FOR_SELECT) {
+        PyErr_SetString(PySSLErrorObject,
+                        "Underlying socket too large for select().");
+        return NULL;
+    }
+    do {
+        err = 0;
+        PySSL_BEGIN_ALLOW_THREADS
+        len = SSL_write(self->ssl, data, count);
+        err = SSL_get_error(self->ssl, len);
+        PySSL_END_ALLOW_THREADS
+        if(PyErr_CheckSignals()) {
+            return NULL;
+        }
+        if (err == SSL_ERROR_WANT_READ) {
+            sockstate = check_socket_and_wait_for_timeout(self->Socket, 0);
+        } else if (err == SSL_ERROR_WANT_WRITE) {
+            sockstate = check_socket_and_wait_for_timeout(self->Socket, 1);
+        } else {
+            sockstate = SOCKET_OPERATION_OK;
+        }
+        if (sockstate == SOCKET_HAS_TIMED_OUT) {
+            PyErr_SetString(PySSLErrorObject,
+                            "The write operation timed out");
+            return NULL;
+        } else if (sockstate == SOCKET_HAS_BEEN_CLOSED) {
+            PyErr_SetString(PySSLErrorObject,
+                            "Underlying socket has been closed.");
+            return NULL;
+        } else if (sockstate == SOCKET_IS_NONBLOCKING) {
+            break;
+        }
+    } while (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE);
+    if (len > 0)
+        return PyInt_FromLong(len);
+    else
+        return PySSL_SetError(self, len, __FILE__, __LINE__);
+}
+
+PyDoc_STRVAR(PySSL_SSLwrite_doc,
+"write(s) -> len\n\
+\n\
+Writes the string s into the SSL object.  Returns the number\n\
+of bytes written.");
+
+static PyObject *PySSL_SSLpending(PySSLObject *self)
+{
+    int count = 0;
+
+    PySSL_BEGIN_ALLOW_THREADS
+    count = SSL_pending(self->ssl);
+    PySSL_END_ALLOW_THREADS
+    if (count < 0)
+        return PySSL_SetError(self, count, __FILE__, __LINE__);
+    else
+        return PyInt_FromLong(count);
+}
+
+PyDoc_STRVAR(PySSL_SSLpending_doc,
+"pending() -> count\n\
+\n\
+Returns the number of already decrypted bytes available for read,\n\
+pending on the connection.\n");
+
+static PyObject *PySSL_SSLread(PySSLObject *self, PyObject *args)
+{
+    PyObject *buf;
+    int count = 0;
+    int len = 1024;
+    int sockstate;
+    int err;
+    int nonblocking;
+
+    if (!PyArg_ParseTuple(args, "|i:read", &len))
+        return NULL;
+
+    if (!(buf = PyString_FromStringAndSize((char *) 0, len)))
+        return NULL;
+
+    /* just in case the blocking state of the socket has been changed */
+    nonblocking = (self->Socket->sock_timeout >= 0.0);
+    BIO_set_nbio(SSL_get_rbio(self->ssl), nonblocking);
+    BIO_set_nbio(SSL_get_wbio(self->ssl), nonblocking);
+
+    /* first check if there are bytes ready to be read */
+    PySSL_BEGIN_ALLOW_THREADS
+    count = SSL_pending(self->ssl);
+    PySSL_END_ALLOW_THREADS
+
+    if (!count) {
+        sockstate = check_socket_and_wait_for_timeout(self->Socket, 0);
+        if (sockstate == SOCKET_HAS_TIMED_OUT) {
+            PyErr_SetString(PySSLErrorObject,
+                            "The read operation timed out");
+            Py_DECREF(buf);
+            return NULL;
+        } else if (sockstate == SOCKET_TOO_LARGE_FOR_SELECT) {
+            PyErr_SetString(PySSLErrorObject,
+                            "Underlying socket too large for select().");
+            Py_DECREF(buf);
+            return NULL;
+        } else if (sockstate == SOCKET_HAS_BEEN_CLOSED) {
+            if (SSL_get_shutdown(self->ssl) !=
+                SSL_RECEIVED_SHUTDOWN)
+            {
+                Py_DECREF(buf);
+                PyErr_SetString(PySSLErrorObject,
+                                "Socket closed without SSL shutdown handshake");
+                return NULL;
+            } else {
+                /* should contain a zero-length string */
+                _PyString_Resize(&buf, 0);
+                return buf;
+            }
+        }
+    }
+    do {
+        err = 0;
+        PySSL_BEGIN_ALLOW_THREADS
+        count = SSL_read(self->ssl, PyString_AsString(buf), len);
+        err = SSL_get_error(self->ssl, count);
+        PySSL_END_ALLOW_THREADS
+        if(PyErr_CheckSignals()) {
+            Py_DECREF(buf);
+            return NULL;
+        }
+        if (err == SSL_ERROR_WANT_READ) {
+            sockstate = check_socket_and_wait_for_timeout(self->Socket, 0);
+        } else if (err == SSL_ERROR_WANT_WRITE) {
+            sockstate = check_socket_and_wait_for_timeout(self->Socket, 1);
+        } else if ((err == SSL_ERROR_ZERO_RETURN) &&
+                   (SSL_get_shutdown(self->ssl) ==
+                    SSL_RECEIVED_SHUTDOWN))
+        {
+            _PyString_Resize(&buf, 0);
+            return buf;
+        } else {
+            sockstate = SOCKET_OPERATION_OK;
+        }
+        if (sockstate == SOCKET_HAS_TIMED_OUT) {
+            PyErr_SetString(PySSLErrorObject,
+                            "The read operation timed out");
+            Py_DECREF(buf);
+            return NULL;
+        } else if (sockstate == SOCKET_IS_NONBLOCKING) {
+            break;
+        }
+    } while (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE);
+    if (count <= 0) {
+        Py_DECREF(buf);
+        return PySSL_SetError(self, count, __FILE__, __LINE__);
+    }
+    if (count != len)
+        _PyString_Resize(&buf, count);
+    return buf;
+}
+
+PyDoc_STRVAR(PySSL_SSLread_doc,
+"read([len]) -> string\n\
+\n\
+Read up to len bytes from the SSL socket.");
+
+static PyObject *PySSL_SSLshutdown(PySSLObject *self)
+{
+    int err, ssl_err, sockstate, nonblocking;
+    int zeros = 0;
+
+    /* Guard against closed socket */
+    if (self->Socket->sock_fd < 0) {
+        PyErr_SetString(PySSLErrorObject,
+                        "Underlying socket has been closed.");
+        return NULL;
+    }
+
+    /* Just in case the blocking state of the socket has been changed */
+    nonblocking = (self->Socket->sock_timeout >= 0.0);
+    BIO_set_nbio(SSL_get_rbio(self->ssl), nonblocking);
+    BIO_set_nbio(SSL_get_wbio(self->ssl), nonblocking);
+
+    while (1) {
+        PySSL_BEGIN_ALLOW_THREADS
+        /* Disable read-ahead so that unwrap can work correctly.
+         * Otherwise OpenSSL might read in too much data,
+         * eating clear text data that happens to be
+         * transmitted after the SSL shutdown.
+         * Should be safe to call repeatedly everytime this
+         * function is used and the shutdown_seen_zero != 0
+         * condition is met.
+         */
+        if (self->shutdown_seen_zero)
+            SSL_set_read_ahead(self->ssl, 0);
+        err = SSL_shutdown(self->ssl);
+        PySSL_END_ALLOW_THREADS
+        /* If err == 1, a secure shutdown with SSL_shutdown() is complete */
+        if (err > 0)
+            break;
+        if (err == 0) {
+            /* Don't loop endlessly; instead preserve legacy
+               behaviour of trying SSL_shutdown() only twice.
+               This looks necessary for OpenSSL < 0.9.8m */
+            if (++zeros > 1)
+                break;
+            /* Shutdown was sent, now try receiving */
+            self->shutdown_seen_zero = 1;
+            continue;
+        }
+
+        /* Possibly retry shutdown until timeout or failure */
+        ssl_err = SSL_get_error(self->ssl, err);
+        if (ssl_err == SSL_ERROR_WANT_READ)
+            sockstate = check_socket_and_wait_for_timeout(self->Socket, 0);
+        else if (ssl_err == SSL_ERROR_WANT_WRITE)
+            sockstate = check_socket_and_wait_for_timeout(self->Socket, 1);
+        else
+            break;
+        if (sockstate == SOCKET_HAS_TIMED_OUT) {
+            if (ssl_err == SSL_ERROR_WANT_READ)
+                PyErr_SetString(PySSLErrorObject,
+                                "The read operation timed out");
+            else
+                PyErr_SetString(PySSLErrorObject,
+                                "The write operation timed out");
+            return NULL;
+        }
+        else if (sockstate == SOCKET_TOO_LARGE_FOR_SELECT) {
+            PyErr_SetString(PySSLErrorObject,
+                            "Underlying socket too large for select().");
+            return NULL;
+        }
+        else if (sockstate != SOCKET_OPERATION_OK)
+            /* Retain the SSL error code */
+            break;
+    }
+
+    if (err < 0)
+        return PySSL_SetError(self, err, __FILE__, __LINE__);
+    else {
+        Py_INCREF(self->Socket);
+        return (PyObject *) (self->Socket);
+    }
+}
+
+PyDoc_STRVAR(PySSL_SSLshutdown_doc,
+"shutdown(s) -> socket\n\
+\n\
+Does the SSL shutdown handshake with the remote end, and returns\n\
+the underlying socket object.");
+
+static PyMethodDef PySSLMethods[] = {
+    {"wrap_accepted", (PyCFunction)PySSL_SSLwrap_accepted, METH_VARARGS,
+     PySSL_SSLwrap_accepted_doc},
+    {"do_handshake", (PyCFunction)PySSL_SSLdo_handshake, METH_NOARGS},
+    {"write", (PyCFunction)PySSL_SSLwrite, METH_VARARGS,
+     PySSL_SSLwrite_doc},
+    {"read", (PyCFunction)PySSL_SSLread, METH_VARARGS,
+     PySSL_SSLread_doc},
+    {"pending", (PyCFunction)PySSL_SSLpending, METH_NOARGS,
+     PySSL_SSLpending_doc},
+    {"server", (PyCFunction)PySSL_server, METH_NOARGS},
+    {"issuer", (PyCFunction)PySSL_issuer, METH_NOARGS},
+    {"peer_certificate", (PyCFunction)PySSL_peercert, METH_VARARGS,
+     PySSL_peercert_doc},
+    {"cipher", (PyCFunction)PySSL_cipher, METH_NOARGS},
+    {"shutdown", (PyCFunction)PySSL_SSLshutdown, METH_NOARGS,
+     PySSL_SSLshutdown_doc},
+    {NULL, NULL}
+};
+
+static PyObject *PySSL_getattr(PySSLObject *self, char *name)
+{
+    return Py_FindMethod(PySSLMethods, (PyObject *)self, name);
+}
+
+static PyTypeObject PySSL_Type = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "ssl.SSLContext",                   /*tp_name*/
+    sizeof(PySSLObject),                /*tp_basicsize*/
+    0,                                  /*tp_itemsize*/
+    /* methods */
+    (destructor)PySSL_dealloc,          /*tp_dealloc*/
+    0,                                  /*tp_print*/
+    (getattrfunc)PySSL_getattr,         /*tp_getattr*/
+    0,                                  /*tp_setattr*/
+    0,                                  /*tp_compare*/
+    0,                                  /*tp_repr*/
+    0,                                  /*tp_as_number*/
+    0,                                  /*tp_as_sequence*/
+    0,                                  /*tp_as_mapping*/
+    0,                                  /*tp_hash*/
+};
+
+#ifdef HAVE_OPENSSL_RAND
+
+/* helper routines for seeding the SSL PRNG */
+static PyObject *
+PySSL_RAND_add(PyObject *self, PyObject *args)
+{
+    char *buf;
+    int len;
+    double entropy;
+
+    if (!PyArg_ParseTuple(args, "s#d:RAND_add", &buf, &len, &entropy))
+        return NULL;
+    RAND_add(buf, len, entropy);
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+PyDoc_STRVAR(PySSL_RAND_add_doc,
+"RAND_add(string, entropy)\n\
+\n\
+Mix string into the OpenSSL PRNG state.  entropy (a float) is a lower\n\
+bound on the entropy contained in string.  See RFC 1750.");
+
+static PyObject *
+PySSL_RAND_status(PyObject *self)
+{
+    return PyInt_FromLong(RAND_status());
+}
+
+PyDoc_STRVAR(PySSL_RAND_status_doc,
+"RAND_status() -> 0 or 1\n\
+\n\
+Returns 1 if the OpenSSL PRNG has been seeded with enough data and 0 if not.\n\
+It is necessary to seed the PRNG with RAND_add() on some platforms before\n\
+using the ssl() function.");
+
+static PyObject *
+PySSL_RAND_egd(PyObject *self, PyObject *arg)
+{
+    int bytes;
+
+    if (!PyString_Check(arg))
+        return PyErr_Format(PyExc_TypeError,
+                            "RAND_egd() expected string, found %s",
+                            Py_TYPE(arg)->tp_name);
+    bytes = RAND_egd(PyString_AS_STRING(arg));
+    if (bytes == -1) {
+        PyErr_SetString(PySSLErrorObject,
+                        "EGD connection failed or EGD did not return "
+                        "enough data to seed the PRNG");
+        return NULL;
+    }
+    return PyInt_FromLong(bytes);
+}
+
+PyDoc_STRVAR(PySSL_RAND_egd_doc,
+"RAND_egd(path) -> bytes\n\
+\n\
+Queries the entropy gather daemon (EGD) on the socket named by 'path'.\n\
+Returns number of bytes read.  Raises SSLError if connection to EGD\n\
+fails or if it does provide enough data to seed PRNG.");
+
+#endif
+
+/* List of functions exported by this module. */
+
+static PyMethodDef PySSL_methods[] = {
+    {"sslwrap",             PySSL_sslwrap,
+     METH_VARARGS, sslwrap_doc},
+    {"_test_decode_cert",   PySSL_test_decode_certificate,
+     METH_VARARGS},
+#ifdef HAVE_OPENSSL_RAND
+    {"RAND_add",            PySSL_RAND_add, METH_VARARGS,
+     PySSL_RAND_add_doc},
+    {"RAND_egd",            PySSL_RAND_egd, METH_O,
+     PySSL_RAND_egd_doc},
+    {"RAND_status",         (PyCFunction)PySSL_RAND_status, METH_NOARGS,
+     PySSL_RAND_status_doc},
+#endif
+    {NULL,                  NULL}            /* Sentinel */
+};
+
+
+#ifdef WITH_THREAD
+
+/* an implementation of OpenSSL threading operations in terms
+   of the Python C thread library */
+
+static PyThread_type_lock *_ssl_locks = NULL;
+
+static unsigned long _ssl_thread_id_function (void) {
+    return PyThread_get_thread_ident();
+}
+
+static void _ssl_thread_locking_function (int mode, int n, const char *file, int line) {
+    /* this function is needed to perform locking on shared data
+       structures. (Note that OpenSSL uses a number of global data
+       structures that will be implicitly shared whenever multiple threads
+       use OpenSSL.) Multi-threaded applications will crash at random if
+       it is not set.
+
+       locking_function() must be able to handle up to CRYPTO_num_locks()
+       different mutex locks. It sets the n-th lock if mode & CRYPTO_LOCK, and
+       releases it otherwise.
+
+       file and line are the file number of the function setting the
+       lock. They can be useful for debugging.
+    */
+
+    if ((_ssl_locks == NULL) ||
+        (n < 0) || ((unsigned)n >= _ssl_locks_count))
+        return;
+
+    if (mode & CRYPTO_LOCK) {
+        PyThread_acquire_lock(_ssl_locks[n], 1);
+    } else {
+        PyThread_release_lock(_ssl_locks[n]);
+    }
+}
+
+static int _setup_ssl_threads(void) {
+
+    unsigned int i;
+
+    if (_ssl_locks == NULL) {
+        _ssl_locks_count = CRYPTO_num_locks();
+        _ssl_locks = (PyThread_type_lock *)
+            malloc(sizeof(PyThread_type_lock) * _ssl_locks_count);
+        if (_ssl_locks == NULL)
+            return 0;
+        memset(_ssl_locks, 0, sizeof(PyThread_type_lock) * _ssl_locks_count);
+        for (i = 0;  i < _ssl_locks_count;  i++) {
+            _ssl_locks[i] = PyThread_allocate_lock();
+            if (_ssl_locks[i] == NULL) {
+                unsigned int j;
+                for (j = 0;  j < i;  j++) {
+                    PyThread_free_lock(_ssl_locks[j]);
+                }
+                free(_ssl_locks);
+                return 0;
+            }
+        }
+        CRYPTO_set_locking_callback(_ssl_thread_locking_function);
+        CRYPTO_set_id_callback(_ssl_thread_id_function);
+    }
+    return 1;
+}
+
+#endif  /* def HAVE_THREAD */
+
+PyDoc_STRVAR(module_doc,
+"Implementation module for SSL socket operations.  See the socket module\n\
+for documentation.");
+
+PyMODINIT_FUNC
+init_forge_ssl(void)
+{
+    PyObject *m, *d;
+
+    Py_TYPE(&PySSL_Type) = &PyType_Type;
+
+    m = Py_InitModule3("_forge_ssl", PySSL_methods, module_doc);
+    if (m == NULL)
+        return;
+    d = PyModule_GetDict(m);
+
+    /* Load _socket module and its C API */
+    if (PySocketModule_ImportModuleAndAPI())
+        return;
+
+    /* Init OpenSSL */
+    SSL_load_error_strings();
+    SSL_library_init();
+#ifdef WITH_THREAD
+    /* note that this will start threading if not already started */
+    if (!_setup_ssl_threads()) {
+        return;
+    }
+#endif
+    OpenSSL_add_all_algorithms();
+
+    /* Add symbols to module dict */
+    PySSLErrorObject = PyErr_NewException("ssl.SSLError",
+                                          PySocketModule.error,
+                                          NULL);
+    if (PySSLErrorObject == NULL)
+        return;
+    if (PyDict_SetItemString(d, "SSLError", PySSLErrorObject) != 0)
+        return;
+    if (PyDict_SetItemString(d, "SSLType",
+                             (PyObject *)&PySSL_Type) != 0)
+        return;
+    PyModule_AddIntConstant(m, "SSL_ERROR_ZERO_RETURN",
+                            PY_SSL_ERROR_ZERO_RETURN);
+    PyModule_AddIntConstant(m, "SSL_ERROR_WANT_READ",
+                            PY_SSL_ERROR_WANT_READ);
+    PyModule_AddIntConstant(m, "SSL_ERROR_WANT_WRITE",
+                            PY_SSL_ERROR_WANT_WRITE);
+    PyModule_AddIntConstant(m, "SSL_ERROR_WANT_X509_LOOKUP",
+                            PY_SSL_ERROR_WANT_X509_LOOKUP);
+    PyModule_AddIntConstant(m, "SSL_ERROR_SYSCALL",
+                            PY_SSL_ERROR_SYSCALL);
+    PyModule_AddIntConstant(m, "SSL_ERROR_SSL",
+                            PY_SSL_ERROR_SSL);
+    PyModule_AddIntConstant(m, "SSL_ERROR_WANT_CONNECT",
+                            PY_SSL_ERROR_WANT_CONNECT);
+    /* non ssl.h errorcodes */
+    PyModule_AddIntConstant(m, "SSL_ERROR_EOF",
+                            PY_SSL_ERROR_EOF);
+    PyModule_AddIntConstant(m, "SSL_ERROR_INVALID_ERROR_CODE",
+                            PY_SSL_ERROR_INVALID_ERROR_CODE);
+    /* cert requirements */
+    PyModule_AddIntConstant(m, "CERT_NONE",
+                            PY_SSL_CERT_NONE);
+    PyModule_AddIntConstant(m, "CERT_OPTIONAL",
+                            PY_SSL_CERT_OPTIONAL);
+    PyModule_AddIntConstant(m, "CERT_REQUIRED",
+                            PY_SSL_CERT_REQUIRED);
+
+    /* protocol versions */
+    PyModule_AddIntConstant(m, "PROTOCOL_SSLv2",
+                            PY_SSL_VERSION_SSL2);
+    PyModule_AddIntConstant(m, "PROTOCOL_SSLv3",
+                            PY_SSL_VERSION_SSL3);
+    PyModule_AddIntConstant(m, "PROTOCOL_SSLv23",
+                            PY_SSL_VERSION_SSL23);
+    PyModule_AddIntConstant(m, "PROTOCOL_TLSv1",
+                            PY_SSL_VERSION_TLS1);
+
+    /* session cache modes */
+    PyModule_AddIntConstant(m, "SESS_CACHE_OFF",
+                            PY_SSL_SESS_CACHE_OFF);
+    PyModule_AddIntConstant(m, "SESS_CACHE_CLIENT",
+                            PY_SSL_SESS_CACHE_CLIENT);
+    PyModule_AddIntConstant(m, "SESS_CACHE_SERVER",
+                            PY_SSL_SESS_CACHE_SERVER);
+    PyModule_AddIntConstant(m, "SESS_CACHE_BOTH",
+                            PY_SSL_SESS_CACHE_BOTH);
+}
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/forge_ssl/forge/socketmodule.h b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/forge_ssl/forge/socketmodule.h
new file mode 100644
index 0000000..a4415b5
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/forge_ssl/forge/socketmodule.h
@@ -0,0 +1,268 @@
+/* Socket module header file */
+
+/* Includes needed for the sockaddr_* symbols below */
+#ifndef MS_WINDOWS
+#ifdef __VMS
+#   include <socket.h>
+# else
+#   include <sys/socket.h>
+# endif
+# include <netinet/in.h>
+# if !(defined(__BEOS__) || defined(__CYGWIN__) || (defined(PYOS_OS2) && defined(PYCC_VACPP)))
+#  include <netinet/tcp.h>
+# endif
+
+#else /* MS_WINDOWS */
+# include <winsock2.h>
+# include <ws2tcpip.h>
+/* VC6 is shipped with old platform headers, and does not have MSTcpIP.h
+ * Separate SDKs have all the functions we want, but older ones don't have
+ * any version information.
+ * I use SIO_GET_MULTICAST_FILTER to detect a decent SDK.
+ */
+# ifdef SIO_GET_MULTICAST_FILTER
+#  include <MSTcpIP.h> /* for SIO_RCVALL */
+#  define HAVE_ADDRINFO
+#  define HAVE_SOCKADDR_STORAGE
+#  define HAVE_GETADDRINFO
+#  define HAVE_GETNAMEINFO
+#  define ENABLE_IPV6
+# else
+typedef int socklen_t;
+# endif /* IPPROTO_IPV6 */
+#endif /* MS_WINDOWS */
+
+#ifdef HAVE_SYS_UN_H
+# include <sys/un.h>
+#else
+# undef AF_UNIX
+#endif
+
+#ifdef HAVE_LINUX_NETLINK_H
+# ifdef HAVE_ASM_TYPES_H
+#  include <asm/types.h>
+# endif
+# include <linux/netlink.h>
+#else
+#  undef AF_NETLINK
+#endif
+
+#ifdef HAVE_BLUETOOTH_BLUETOOTH_H
+/*
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/rfcomm.h>
+#include <bluetooth/l2cap.h>
+#include <bluetooth/sco.h>
+#include <bluetooth/hci.h>
+*/
+#endif
+
+#ifdef HAVE_BLUETOOTH_H
+#include <bluetooth.h>
+#endif
+
+#ifdef HAVE_NETPACKET_PACKET_H
+# include <sys/ioctl.h>
+# include <net/if.h>
+# include <netpacket/packet.h>
+#endif
+
+#ifdef HAVE_LINUX_TIPC_H
+# include <linux/tipc.h>
+#endif
+
+#ifndef Py__SOCKET_H
+#define Py__SOCKET_H
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Python module and C API name */
+#define PySocket_MODULE_NAME    "_socket"
+#define PySocket_CAPI_NAME      "CAPI"
+
+/* Abstract the socket file descriptor type */
+#ifdef MS_WINDOWS
+typedef SOCKET SOCKET_T;
+#       ifdef MS_WIN64
+#               define SIZEOF_SOCKET_T 8
+#       else
+#               define SIZEOF_SOCKET_T 4
+#       endif
+#else
+typedef int SOCKET_T;
+#       define SIZEOF_SOCKET_T SIZEOF_INT
+#endif
+
+/* Socket address */
+typedef union sock_addr {
+    struct sockaddr_in in;
+#ifdef AF_UNIX
+    struct sockaddr_un un;
+#endif
+#ifdef AF_NETLINK
+    struct sockaddr_nl nl;
+#endif
+#ifdef ENABLE_IPV6
+    struct sockaddr_in6 in6;
+    struct sockaddr_storage storage;
+#endif
+#ifdef HAVE_BLUETOOTH_BLUETOOTH_H
+/*
+    struct sockaddr_l2 bt_l2;
+    struct sockaddr_rc bt_rc;
+    struct sockaddr_sco bt_sco;
+    struct sockaddr_hci bt_hci;
+*/
+#endif
+#ifdef HAVE_NETPACKET_PACKET_H
+    struct sockaddr_ll ll;
+#endif
+} sock_addr_t;
+
+/* The object holding a socket.  It holds some extra information,
+   like the address family, which is used to decode socket address
+   arguments properly. */
+
+typedef struct {
+    PyObject_HEAD
+    SOCKET_T sock_fd;           /* Socket file descriptor */
+    int sock_family;            /* Address family, e.g., AF_INET */
+    int sock_type;              /* Socket type, e.g., SOCK_STREAM */
+    int sock_proto;             /* Protocol type, usually 0 */
+    PyObject *(*errorhandler)(void); /* Error handler; checks
+                                        errno, returns NULL and
+                                        sets a Python exception */
+    double sock_timeout;                 /* Operation timeout in seconds;
+                                        0.0 means non-blocking */
+} PySocketSockObject;
+
+/* --- C API ----------------------------------------------------*/
+
+/* Short explanation of what this C API export mechanism does
+   and how it works:
+
+    The _ssl module needs access to the type object defined in
+    the _socket module. Since cross-DLL linking introduces a lot of
+    problems on many platforms, the "trick" is to wrap the
+    C API of a module in a struct which then gets exported to
+    other modules via a PyCObject.
+
+    The code in socketmodule.c defines this struct (which currently
+    only contains the type object reference, but could very
+    well also include other C APIs needed by other modules)
+    and exports it as PyCObject via the module dictionary
+    under the name "CAPI".
+
+    Other modules can now include the socketmodule.h file
+    which defines the needed C APIs to import and set up
+    a static copy of this struct in the importing module.
+
+    After initialization, the importing module can then
+    access the C APIs from the _socket module by simply
+    referring to the static struct, e.g.
+
+    Load _socket module and its C API; this sets up the global
+    PySocketModule:
+
+    if (PySocketModule_ImportModuleAndAPI())
+        return;
+
+
+    Now use the C API as if it were defined in the using
+    module:
+
+    if (!PyArg_ParseTuple(args, "O!|zz:ssl",
+
+                          PySocketModule.Sock_Type,
+
+                          (PyObject*)&Sock,
+                          &key_file, &cert_file))
+        return NULL;
+
+    Support could easily be extended to export more C APIs/symbols
+    this way. Currently, only the type object is exported,
+    other candidates would be socket constructors and socket
+    access functions.
+
+*/
+
+/* C API for usage by other Python modules */
+typedef struct {
+    PyTypeObject *Sock_Type;
+    PyObject *error;
+} PySocketModule_APIObject;
+
+/* XXX The net effect of the following appears to be to define a function
+   XXX named PySocketModule_APIObject in _ssl.c.  It's unclear why it isn't
+   XXX defined there directly.
+
+   >>> It's defined here because other modules might also want to use
+   >>> the C API.
+
+*/
+#ifndef PySocket_BUILDING_SOCKET
+
+/* --- C API ----------------------------------------------------*/
+
+/* Interfacestructure to C API for other modules.
+   Call PySocketModule_ImportModuleAndAPI() to initialize this
+   structure. After that usage is simple:
+
+   if (!PyArg_ParseTuple(args, "O!|zz:ssl",
+                         &PySocketModule.Sock_Type, (PyObject*)&Sock,
+                         &key_file, &cert_file))
+     return NULL;
+   ...
+*/
+
+static
+PySocketModule_APIObject PySocketModule;
+
+/* You *must* call this before using any of the functions in
+   PySocketModule and check its outcome; otherwise all accesses will
+   result in a segfault. Returns 0 on success. */
+
+#ifndef DPRINTF
+# define DPRINTF if (0) printf
+#endif
+
+static
+int PySocketModule_ImportModuleAndAPI(void)
+{
+    PyObject *mod = 0, *v = 0;
+    char *apimodule = PySocket_MODULE_NAME;
+    char *apiname = PySocket_CAPI_NAME;
+    void *api;
+
+    DPRINTF("Importing the %s C API...\n", apimodule);
+    mod = PyImport_ImportModuleNoBlock(apimodule);
+    if (mod == NULL)
+        goto onError;
+    DPRINTF(" %s package found\n", apimodule);
+    v = PyObject_GetAttrString(mod, apiname);
+    if (v == NULL)
+        goto onError;
+    Py_DECREF(mod);
+    DPRINTF(" API object %s found\n", apiname);
+    api = PyCObject_AsVoidPtr(v);
+    if (api == NULL)
+        goto onError;
+    Py_DECREF(v);
+    memcpy(&PySocketModule, api, sizeof(PySocketModule));
+    DPRINTF(" API object loaded and initialized.\n");
+    return 0;
+
+ onError:
+    DPRINTF(" not found.\n");
+    Py_XDECREF(mod);
+    Py_XDECREF(v);
+    return -1;
+}
+
+#endif /* !PySocket_BUILDING_SOCKET */
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* !Py__SOCKET_H */
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/forge_ssl/forge/ssl.py b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/forge_ssl/forge/ssl.py
new file mode 100644
index 0000000..aa9fc14
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/forge_ssl/forge/ssl.py
@@ -0,0 +1,486 @@
+# Wrapper module for _ssl, providing some additional facilities
+# implemented in Python.  Written by Bill Janssen.
+
+"""\
+This module provides some more Pythonic support for SSL.
+
+Object types:
+
+  SSLSocket -- subtype of socket.socket which does SSL over the socket
+
+Exceptions:
+
+  SSLError -- exception raised for I/O errors
+
+Functions:
+
+  cert_time_to_seconds -- convert time string used for certificate
+                          notBefore and notAfter functions to integer
+                          seconds past the Epoch (the time values
+                          returned from time.time())
+
+  fetch_server_certificate (HOST, PORT) -- fetch the certificate provided
+                          by the server running on HOST at port PORT.  No
+                          validation of the certificate is performed.
+
+Integer constants:
+
+SSL_ERROR_ZERO_RETURN
+SSL_ERROR_WANT_READ
+SSL_ERROR_WANT_WRITE
+SSL_ERROR_WANT_X509_LOOKUP
+SSL_ERROR_SYSCALL
+SSL_ERROR_SSL
+SSL_ERROR_WANT_CONNECT
+
+SSL_ERROR_EOF
+SSL_ERROR_INVALID_ERROR_CODE
+
+The following group define certificate requirements that one side is
+allowing/requiring from the other side:
+
+CERT_NONE - no certificates from the other side are required (or will
+            be looked at if provided)
+CERT_OPTIONAL - certificates are not required, but if provided will be
+                validated, and if validation fails, the connection will
+                also fail
+CERT_REQUIRED - certificates are required, and will be validated, and
+                if validation fails, the connection will also fail
+
+The following constants identify various SSL protocol variants:
+
+PROTOCOL_SSLv2
+PROTOCOL_SSLv3
+PROTOCOL_SSLv23
+PROTOCOL_TLSv1
+
+The following constants identify various SSL session caching modes:
+
+SESS_CACHE_OFF
+SESS_CACHE_CLIENT
+SESS_CACHE_SERVER
+SESS_CACHE_BOTH
+"""
+
+import textwrap
+
+import _forge_ssl             # if we can't import it, let the error propagate
+
+from _forge_ssl import SSLError
+from _forge_ssl import CERT_NONE, CERT_OPTIONAL, CERT_REQUIRED
+from _forge_ssl import PROTOCOL_SSLv2, PROTOCOL_SSLv3, PROTOCOL_SSLv23, PROTOCOL_TLSv1
+from _forge_ssl import SESS_CACHE_OFF, SESS_CACHE_CLIENT, SESS_CACHE_SERVER, SESS_CACHE_BOTH
+from _forge_ssl import RAND_status, RAND_egd, RAND_add
+from _forge_ssl import \
+     SSL_ERROR_ZERO_RETURN, \
+     SSL_ERROR_WANT_READ, \
+     SSL_ERROR_WANT_WRITE, \
+     SSL_ERROR_WANT_X509_LOOKUP, \
+     SSL_ERROR_SYSCALL, \
+     SSL_ERROR_SSL, \
+     SSL_ERROR_WANT_CONNECT, \
+     SSL_ERROR_EOF, \
+     SSL_ERROR_INVALID_ERROR_CODE
+
+from socket import socket, _fileobject, _delegate_methods
+from socket import error as socket_error
+from socket import getnameinfo as _getnameinfo
+import base64        # for DER-to-PEM translation
+import errno
+
+class SSLSocket(socket):
+
+    """This class implements a subtype of socket.socket that wraps
+    the underlying OS socket in an SSL context when necessary, and
+    provides read and write methods over that channel."""
+
+    def __init__(self, parent_socket, sock, keyfile=None, certfile=None,
+                 server_side=False, cert_reqs=CERT_NONE,
+                 ssl_version=PROTOCOL_SSLv23,
+                 sess_cache_mode=SESS_CACHE_SERVER,
+                 sess_id_ctx=None,
+                 ca_certs=None,
+                 do_handshake_on_connect=True,
+                 suppress_ragged_eofs=True):
+        socket.__init__(self, _sock=sock._sock)
+        # The initializer for socket overrides the methods send(), recv(), etc.
+        # in the instancce, which we don't need -- but we want to provide the
+        # methods defined in SSLSocket.
+        for attr in _delegate_methods:
+            try:
+                delattr(self, attr)
+            except AttributeError:
+                pass
+
+        if certfile and not keyfile:
+            keyfile = certfile
+
+        create = True
+        connected = False
+        if not server_side:
+            # see if it's connected
+            try:
+                socket.getpeername(self)
+                connected = True
+            except socket_error, e:
+                if e.errno != errno.ENOTCONN:
+                    raise
+                # no, no connection yet
+                self._sslobj = None
+                create = False
+        if create:
+            # yes, create the SSL object
+            if parent_socket == None:
+                self._sslobj = _forge_ssl.sslwrap(
+                                                 self._sock,
+                                                 server_side,
+                                                 keyfile, certfile,
+                                                 cert_reqs, ssl_version,
+                                                 sess_cache_mode, sess_id_ctx,
+                                                 ca_certs)
+            else:
+                self._sslobj = parent_socket._sslobj.wrap_accepted(self._sock)
+
+        if connected and do_handshake_on_connect:
+            self.do_handshake()
+        self.keyfile = keyfile
+        self.certfile = certfile
+        self.cert_reqs = cert_reqs
+        self.ssl_version = ssl_version
+        self.sess_cache_mode = sess_cache_mode
+        self.sess_id_ctx = sess_id_ctx
+        self.ca_certs = ca_certs
+        self.do_handshake_on_connect = do_handshake_on_connect
+        self.suppress_ragged_eofs = suppress_ragged_eofs
+        self._makefile_refs = 0
+
+    def read(self, len=1024):
+
+        """Read up to LEN bytes and return them.
+        Return zero-length string on EOF."""
+
+        try:
+            return self._sslobj.read(len)
+        except SSLError, x:
+            if x.args[0] == SSL_ERROR_EOF and self.suppress_ragged_eofs:
+                return ''
+            else:
+                raise
+
+    def write(self, data):
+
+        """Write DATA to the underlying SSL channel.  Returns
+        number of bytes of DATA actually transmitted."""
+
+        return self._sslobj.write(data)
+
+    def getpeercert(self, binary_form=False):
+
+        """Returns a formatted version of the data in the
+        certificate provided by the other end of the SSL channel.
+        Return None if no certificate was provided, {} if a
+        certificate was provided, but not validated."""
+
+        return self._sslobj.peer_certificate(binary_form)
+
+    def cipher(self):
+
+        if not self._sslobj:
+            return None
+        else:
+            return self._sslobj.cipher()
+
+    def send(self, data, flags=0):
+        if self._sslobj:
+            if flags != 0:
+                raise ValueError(
+                    "non-zero flags not allowed in calls to send() on %s" %
+                    self.__class__)
+            while True:
+                try:
+                    v = self._sslobj.write(data)
+                except SSLError, x:
+                    if x.args[0] == SSL_ERROR_WANT_READ:
+                        return 0
+                    elif x.args[0] == SSL_ERROR_WANT_WRITE:
+                        return 0
+                    else:
+                        raise
+                else:
+                    return v
+        else:
+            return socket.send(self, data, flags)
+
+    def sendto(self, data, addr, flags=0):
+        if self._sslobj:
+            raise ValueError("sendto not allowed on instances of %s" %
+                             self.__class__)
+        else:
+            return socket.sendto(self, data, addr, flags)
+
+    def sendall(self, data, flags=0):
+        if self._sslobj:
+            if flags != 0:
+                raise ValueError(
+                    "non-zero flags not allowed in calls to sendall() on %s" %
+                    self.__class__)
+            amount = len(data)
+            count = 0
+            while (count < amount):
+                v = self.send(data[count:])
+                count += v
+            return amount
+        else:
+            return socket.sendall(self, data, flags)
+
+    def recv(self, buflen=1024, flags=0):
+        if self._sslobj:
+            if flags != 0:
+                raise ValueError(
+                    "non-zero flags not allowed in calls to recv() on %s" %
+                    self.__class__)
+            return self.read(buflen)
+        else:
+            return socket.recv(self, buflen, flags)
+
+    def recv_into(self, buffer, nbytes=None, flags=0):
+        if buffer and (nbytes is None):
+            nbytes = len(buffer)
+        elif nbytes is None:
+            nbytes = 1024
+        if self._sslobj:
+            if flags != 0:
+                raise ValueError(
+                  "non-zero flags not allowed in calls to recv_into() on %s" %
+                  self.__class__)
+            tmp_buffer = self.read(nbytes)
+            v = len(tmp_buffer)
+            buffer[:v] = tmp_buffer
+            return v
+        else:
+            return socket.recv_into(self, buffer, nbytes, flags)
+
+    def recvfrom(self, addr, buflen=1024, flags=0):
+        if self._sslobj:
+            raise ValueError("recvfrom not allowed on instances of %s" %
+                             self.__class__)
+        else:
+            return socket.recvfrom(self, addr, buflen, flags)
+
+    def recvfrom_into(self, buffer, nbytes=None, flags=0):
+        if self._sslobj:
+            raise ValueError("recvfrom_into not allowed on instances of %s" %
+                             self.__class__)
+        else:
+            return socket.recvfrom_into(self, buffer, nbytes, flags)
+
+    def pending(self):
+        if self._sslobj:
+            return self._sslobj.pending()
+        else:
+            return 0
+
+    def unwrap(self):
+        if self._sslobj:
+            try:
+                # if connected then shutdown
+                self.getpeername()
+                s = self._sslobj.shutdown()
+            except:
+                s = self._sock
+            self._sslobj = None
+            return s
+        else:
+            raise ValueError("No SSL wrapper around " + str(self))
+
+    def shutdown(self, how):
+        self._sslobj = None
+        socket.shutdown(self, how)
+
+    def close(self):
+        if self._makefile_refs < 1:
+            if self._sslobj:
+                self.unwrap()
+            socket.close(self)
+        else:
+            self._makefile_refs -= 1
+
+    def do_handshake(self):
+
+        """Perform a TLS/SSL handshake."""
+
+        self._sslobj.do_handshake()
+
+    def connect(self, addr):
+
+        """Connects to remote ADDR, and then wraps the connection in
+        an SSL channel."""
+
+        # Here we assume that the socket is client-side, and not
+        # connected at the time of the call.  We connect it, then wrap it.
+        if self._sslobj:
+            raise ValueError("attempt to connect already-connected SSLSocket!")
+        socket.connect(self, addr)
+        self._sslobj = _forge_ssl.sslwrap(self._sock, False,
+                                          self.keyfile, self.certfile,
+                                          self.cert_reqs, self.ssl_version,
+                                          self.sess_cache_mode,
+                                          self.sess_id_ctx,
+                                          self.ca_certs)
+        if self.do_handshake_on_connect:
+            self.do_handshake()
+
+    def accept(self):
+
+        """Accepts a new connection from a remote client, and returns
+        a tuple containing that new connection wrapped with a server-side
+        SSL channel, and the address of the remote client."""
+
+        newsock, addr = socket.accept(self)
+        return (SSLSocket(self,
+                          newsock,
+                          keyfile=self.keyfile,
+                          certfile=self.certfile,
+                          server_side=True,
+                          cert_reqs=self.cert_reqs,
+                          ssl_version=self.ssl_version,
+                          sess_cache_mode=self.sess_cache_mode,
+                          sess_id_ctx=self.sess_id_ctx,
+                          ca_certs=self.ca_certs,
+                          do_handshake_on_connect=self.do_handshake_on_connect,
+                          suppress_ragged_eofs=self.suppress_ragged_eofs),
+                addr)
+
+    def makefile(self, mode='r', bufsize=-1):
+
+        """Make and return a file-like object that
+        works with the SSL connection.  Just use the code
+        from the socket module."""
+
+        self._makefile_refs += 1
+        # close=True so as to decrement the reference count when done with
+        # the file-like object.
+        return _fileobject(self, mode, bufsize, close=True)
+
+
+
+def wrap_socket(sock, parent_socket=None, keyfile=None, certfile=None,
+                server_side=False, cert_reqs=CERT_NONE,
+                ssl_version=PROTOCOL_SSLv23,
+                sess_cache_mode=SESS_CACHE_SERVER,
+                sess_id_ctx=None,
+                ca_certs=None,
+                do_handshake_on_connect=True,
+                suppress_ragged_eofs=True):
+
+    return SSLSocket(parent_socket,
+                     sock, keyfile=keyfile, certfile=certfile,
+                     server_side=server_side, cert_reqs=cert_reqs,
+                     ssl_version=ssl_version,
+                     sess_cache_mode=sess_cache_mode,
+                     sess_id_ctx=sess_id_ctx,
+                     ca_certs=ca_certs,
+                     do_handshake_on_connect=do_handshake_on_connect,
+                     suppress_ragged_eofs=suppress_ragged_eofs)
+
+
+# some utility functions
+
+def cert_time_to_seconds(cert_time):
+
+    """Takes a date-time string in standard ASN1_print form
+    ("MON DAY 24HOUR:MINUTE:SEC YEAR TIMEZONE") and return
+    a Python time value in seconds past the epoch."""
+
+    import time
+    return time.mktime(time.strptime(cert_time, "%b %d %H:%M:%S %Y GMT"))
+
+PEM_HEADER = "-----BEGIN CERTIFICATE-----"
+PEM_FOOTER = "-----END CERTIFICATE-----"
+
+def DER_cert_to_PEM_cert(der_cert_bytes):
+
+    """Takes a certificate in binary DER format and returns the
+    PEM version of it as a string."""
+
+    if hasattr(base64, 'standard_b64encode'):
+        # preferred because older API gets line-length wrong
+        f = base64.standard_b64encode(der_cert_bytes)
+        return (PEM_HEADER + '\n' +
+                textwrap.fill(f, 64) + '\n' +
+                PEM_FOOTER + '\n')
+    else:
+        return (PEM_HEADER + '\n' +
+                base64.encodestring(der_cert_bytes) +
+                PEM_FOOTER + '\n')
+
+def PEM_cert_to_DER_cert(pem_cert_string):
+
+    """Takes a certificate in ASCII PEM format and returns the
+    DER-encoded version of it as a byte sequence"""
+
+    if not pem_cert_string.startswith(PEM_HEADER):
+        raise ValueError("Invalid PEM encoding; must start with %s"
+                         % PEM_HEADER)
+    if not pem_cert_string.strip().endswith(PEM_FOOTER):
+        raise ValueError("Invalid PEM encoding; must end with %s"
+                         % PEM_FOOTER)
+    d = pem_cert_string.strip()[len(PEM_HEADER):-len(PEM_FOOTER)]
+    return base64.decodestring(d)
+
+def get_server_certificate(addr, ssl_version=PROTOCOL_SSLv3, ca_certs=None):
+
+    """Retrieve the certificate from the server at the specified address,
+    and return it as a PEM-encoded string.
+    If 'ca_certs' is specified, validate the server cert against it.
+    If 'ssl_version' is specified, use it in the connection attempt."""
+
+    host, port = addr
+    if (ca_certs is not None):
+        cert_reqs = CERT_REQUIRED
+    else:
+        cert_reqs = CERT_NONE
+    s = wrap_socket(socket(), ssl_version=ssl_version,
+                    cert_reqs=cert_reqs, ca_certs=ca_certs)
+    s.connect(addr)
+    dercert = s.getpeercert(True)
+    s.close()
+    return DER_cert_to_PEM_cert(dercert)
+
+def get_protocol_name(protocol_code):
+    if protocol_code == PROTOCOL_TLSv1:
+        return "TLSv1"
+    elif protocol_code == PROTOCOL_SSLv23:
+        return "SSLv23"
+    elif protocol_code == PROTOCOL_SSLv2:
+        return "SSLv2"
+    elif protocol_code == PROTOCOL_SSLv3:
+        return "SSLv3"
+    else:
+        return "<unknown>"
+
+
+# a replacement for the old socket.ssl function
+
+def sslwrap_simple(sock, keyfile=None, certfile=None):
+
+    """A replacement for the old socket.ssl function.  Designed
+    for compability with Python 2.5 and earlier.  Will disappear in
+    Python 3.0."""
+
+    if hasattr(sock, "_sock"):
+        sock = sock._sock
+
+    ssl_sock = _forge_ssl.sslwrap(sock, 0, keyfile, certfile,
+                                  CERT_NONE, PROTOCOL_SSLv23,
+                                  SESS_CACHE_SERVER, None, None)
+    try:
+        sock.getpeername()
+    except:
+        # no, no connection yet
+        pass
+    else:
+        # yes, do the handshake
+        ssl_sock.do_handshake()
+
+    return ssl_sock
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/forge_ssl/setup.py b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/forge_ssl/setup.py
new file mode 100644
index 0000000..350ae37
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/forge_ssl/setup.py
@@ -0,0 +1,12 @@
+#!/usr/bin/env python
+
+from distutils.core import setup, Extension
+
+ssl = Extension('_forge_ssl',
+        sources = ['forge/_ssl.c'])
+
+setup (name = 'Forge SSL',
+        version = '1.0',
+        description = 'Python SSL with session cache support.',
+        ext_modules = [ssl],
+        py_modules = ['forge.ssl'])
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/form.html b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/form.html
new file mode 100644
index 0000000..cfe9f94
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/form.html
@@ -0,0 +1,150 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+   <head>
+      <title>Forge Form Test</title>
+      <link type="text/css" rel="stylesheet" media="all" href="screen.css" />
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
+      <script type="text/javascript" src="forge/debug.js"></script>
+      <script type="text/javascript" src="forge/util.js"></script>
+      <script type="text/javascript" src="forge/log.js"></script>
+      <script type="text/javascript" src="forge/form.js"></script>
+      <script type="text/javascript" src="form.js"></script>
+
+      <link type="text/css" rel="stylesheet" media="all" href="screen.css" />
+   </head>
+<body>
+<div class="nav"><a href="index.html">Forge Tests</a> / Form</div>
+
+<div class="header">
+   <h1>Form Tests</h1>
+</div>
+
+<div class="content">
+
+<form id="form-1" class="ajax standard" method="post" action="">
+   <fieldset>
+      <legend>Text</legend>
+
+      <p>
+         <input name="text1" type="text" value="value1" />
+         <input name="text2.sub1" type="text" value="value2" />
+         <input name="text2.sub2[]" type="text" value="value3" />
+         <input name="text2.sub2[]" type="text" value="value4" />
+         <input name="text2.sub3[0]" type="text" value="value5" />
+         <input name="text2.sub4[0][0]" type="text" value="value6" />
+         <input name="text2.sub4[0][]" type="text" value="value7" />
+         <input name="text2.sub5[foo][]" type="text" value="value8" />
+         <input name="text2.sub5[dotted.name]" type="text" value="value9" />
+         <input name="text2.sub6[]" type="text" value="value10" />
+         <input name="text2.sub7[].@" type="text" value="value11" />
+         <input name="text2.sub7[].@" type="text" value="value12" />
+         <input name="text2.sub8[][].@" type="text" value="value13" />
+      </p>
+
+      <p>
+         <label>username <input name="username" type="text" value="username" /></label>
+         <label>password <input name="password" type="password" value="password" /></label>
+      </p>
+
+      <p>
+         <label>password1.1 <input name="password1" type="password" value="password" /></label>
+         <label>password1.2 <input name="password1" type="password" value="password" /></label>
+      </p>
+   </fieldset>
+
+   <fieldset>
+      <legend>Checkbox</legend>
+
+      <p>
+         <label><input name="checkbox1" type="checkbox" value="c1" /> C1</label>
+         <label><input name="checkbox1" type="checkbox" value="c2" /> C1</label>
+         <label><input name="checkbox1" type="checkbox" value="c3" /> C1</label>
+      </p>
+
+      <p>
+         <label><input name="checkbox2[]" type="checkbox" value="c1" /> C2[]</label>
+         <label><input name="checkbox2[]" type="checkbox" value="c2" /> C2[]</label>
+         <label><input name="checkbox2[3]" type="checkbox" value="c3" /> C2[3]</label>
+         <label><input name="checkbox2[]" type="checkbox" value="c4" /> C2[]</label>
+         <label><input name="checkbox2.sub1" type="checkbox" value="c4" /> C2.sub1</label>
+      </p>
+
+      <p>
+         <label><input name="checkbox3.sub1" type="checkbox" value="c1" /> C3.s1</label>
+         <label><input name="checkbox3.sub2" type="checkbox" value="c2" /> C3.s2</label>
+         <label><input name="checkbox3.sub2" type="checkbox" value="c3" /> C3.s2</label>
+         <label><input name="checkbox3[]" type="checkbox" value="c4" /> C3[]</label>
+      </p>
+   </fieldset>
+
+   <fieldset>
+      <legend>Radio</legend>
+
+      <p>
+         <label><input name="radio1" type="radio" value="r1" /> R1</label>
+         <label><input name="radio1" type="radio" value="r2" /> R1</label>
+         <label><input name="radio1" type="radio" value="r3" /> R1</label>
+         <label><input name="radio1" type="radio" value="r4" /> R1</label>
+      </p>
+
+      <p>
+         <label><input name="radio2.sub1" type="radio" value="r1" /> R2.s1</label>
+         <label><input name="radio2.sub1" type="radio" value="r2" /> R2.s1</label>
+         <label><input name="radio2.sub2" type="radio" value="r3" /> R2.s2</label>
+         <label><input name="radio2.sub2" type="radio" value="r4" /> R2.s2</label>
+      </p>
+   </fieldset>
+
+   <fieldset>
+      <legend>Select</legend>
+      <p>
+         <select name="select1">
+            <option value="select1">Select 1</option>
+            <option value="select2">Select 2</option>
+            <option value="select3">Select 3</option>
+         </select>
+      </p>
+
+      <p>
+         <select name="select2" multiple="multiple">
+            <option value="select1">Select 1</option>
+            <option value="select2">Select 2</option>
+            <option value="select3">Select 3</option>
+         </select>
+      </p>
+   </fieldset>
+
+   <fieldset>
+      <legend>Text Area</legend>
+
+      <textarea name="textarea">Test text.</textarea>
+   </fieldset>
+
+   <fieldset>
+      <legend>Buttons</legend>
+
+      <p>
+         <button name="button1" type="submit" value="submit">Submit Form</button>
+         <button name="button2" type="reset" value="reset">Reset Form</button>
+      </p>
+   </fieldset>
+
+   <fieldset>
+      <legend>Input Buttons</legend>
+      
+      <p>
+         <input name="submit" type="submit" value="Submit Form" />
+         <input name="reset" type="reset" value="Reset Form" />
+      </p>
+   </fieldset>
+</form>
+
+<p>Result:</p>
+<div id="result"></div>
+
+</div>
+
+</body>
+</html>
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/form.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/form.js
new file mode 100644
index 0000000..abfbab0
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/form.js
@@ -0,0 +1,40 @@
+/**
+ * Forge Form Tests
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2011 Digital Bazaar, Inc. All rights reserved.
+ */
+(function($) {
+$(document).ready(function()
+{
+   // logging category
+   var cat = 'forge.tests.form';
+   
+   // local alias
+   var forge = window.forge;
+   
+   $('form.ajax').each(function(i, form)
+   {
+      $(form).submit(function()
+      {
+         try
+         {
+            var f = forge.form.serialize($(this));
+            forge.log.debug(cat, 'result:', JSON.stringify(f));
+            $('#result').html(JSON.stringify(f));
+            
+            /* dictionary test
+            var f = forge.form.serialize($(this), '.', {'username':'user'});
+            forge.log.debug(cat, 'result:', JSON.stringify(f));
+            */
+         }
+         catch(e)
+         {
+            console.log('exception', e.stack);
+         }
+         return false;
+      });
+   });
+});
+})(jQuery);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/heartbleed.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/heartbleed.js
new file mode 100644
index 0000000..b38869a
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/heartbleed.js
@@ -0,0 +1,55 @@
+var forge = require('../js/forge');
+var net = require('net');
+
+var socket = new net.Socket();
+
+var client = forge.tls.createConnection({
+  server: false,
+  verify: function(connection, verified, depth, certs) {
+    // skip verification for testing
+    return true;
+  },
+  connected: function(connection) {
+    // heartbleeds 2k
+    console.log('[tls] connected');
+    connection.prepareHeartbeatRequest('', 2048);
+    setTimeout(function() {
+      client.close();
+    }, 1000);
+  },
+  tlsDataReady: function(connection) {
+    // encrypted data is ready to be sent to the server
+    var data = connection.tlsData.getBytes();
+    socket.write(data, 'binary');
+  },
+  dataReady: function(connection) {
+    // clear data from the server is ready
+    var data = connection.data.getBytes();
+    console.log('[tls] received from the server: ' + data);
+  },
+  heartbeatReceived: function(c, payload) {
+    console.log('Heartbleed:\n' + payload.toHex());
+    client.close();
+  },
+  closed: function() {
+    console.log('[tls] disconnected');
+    socket.end();
+  },
+  error: function(connection, error) {
+    console.log('[tls] error', error);
+  }
+});
+
+socket.on('connect', function() {
+  console.log('[socket] connected');
+  client.handshake();
+});
+socket.on('data', function(data) {
+  client.process(data.toString('binary'));
+});
+socket.on('end', function() {
+  console.log('[socket] disconnected');
+});
+
+// connect
+socket.connect(443, 'yahoo.com');
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/http.html b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/http.html
new file mode 100644
index 0000000..3bdf941
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/http.html
@@ -0,0 +1,229 @@
+<html>
+   <head>
+      <link type="text/css" rel="stylesheet" media="all" href="screen.css" />
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js"></script>
+      <script type="text/javascript" src="forge/util.js"></script>
+      <script type="text/javascript" src="forge/socket.js"></script>
+      <script type="text/javascript" src="forge/http.js"></script>
+      <script type="text/javascript" src="forge/log.js"></script>
+      
+      <script type="text/javascript">
+      //<![CDATA[
+      // logging category
+      var cat = 'forge.tests.http';
+
+      window.forge.socketPool =
+      {
+         ready: function()
+         {
+            forge.log.debug(cat, 'SocketPool ready.');
+         }
+      };
+
+      swfobject.embedSWF(
+         'forge/SocketPool.swf', 'socketPool', '0', '0', '9.0.0',
+         false, {}, {allowscriptaccess: 'always'}, {});
+      
+      // local aliases
+      var net = window.forge.net;
+      var http = window.forge.http;
+      var util = window.forge.util;
+
+      var client;
+      
+      function client_init()
+      {
+         try
+         {
+            var sp = net.createSocketPool({
+               flashId: 'socketPool',
+               policyPort: 19945,
+               msie: false
+            });
+            client = http.createClient({
+               //url: 'http://' + window.location.host,
+               socketPool: sp,
+               connections: 10
+            });
+            
+            document.getElementById('feedback').innerHTML =
+               'HTTP client created';
+         }
+         catch(ex)
+         {
+            forge.log.error(cat, ex);
+         }
+         return false;
+      }
+      
+      function client_cleanup()
+      {
+         var sp = client.socketPool;
+         client.destroy();
+         sp.destroy();
+         document.getElementById('feedback').innerHTML =
+            'HTTP client cleaned up';
+         return false;
+      }
+
+      function client_send()
+      {
+         var request = http.createRequest({
+            method: 'GET',
+            path: '/'
+            //body: 'echo=foo',
+            //headers: [{'Content-Type': 'application/x-www-form-urlencoded'}]
+         });
+         
+         client.send({
+            request: request,
+            connected: function(e)
+            {
+               forge.log.debug(cat, 'connected', e);
+            },
+            headerReady: function(e)
+            {
+               forge.log.debug(cat, 'header ready', e);
+            },
+            bodyReady: function(e)
+            {
+               forge.log.debug(cat, 'body ready', e);
+            },
+            error: function(e)
+            {
+               forge.log.error(cat, 'error', e);
+            }
+         });
+         document.getElementById('feedback').innerHTML =
+            'HTTP request sent';
+         return false;
+      }
+
+      function client_send_10()
+      {
+         for(var i = 0; i < 10; ++i)
+         {
+            client_send();
+         }
+         return false;
+      }
+
+      function client_stress()
+      {
+         for(var i = 0; i < 10; ++i)
+         {
+            setTimeout(function()
+            {
+               for(var i = 0; i < 10; ++i)
+               {
+                  client_send();
+               }
+            }, 0);
+         }
+         return false;
+      }
+      
+      function client_cookies()
+      {
+         var cookie =
+         {
+            name: 'test-cookie',
+            value: 'test-value',
+            maxAge: -1,
+            secure: false,
+            path: '/'
+         };
+         client.setCookie(cookie);
+         forge.log.debug(cat, 'cookie', client.getCookie('test-cookie'));
+      }
+
+      function client_clear_cookies()
+      {
+         client.clearCookies();
+      }
+
+      function request_add_cookies()
+      {
+         var cookie1 =
+         {
+            name: 'test-cookie1',
+            value: 'test-value1',
+            maxAge: -1,
+            secure: false,
+            path: '/'
+         };
+         var cookie2 =
+         {
+            name: 'test-cookie2',
+            value: 'test-value2',
+            maxAge: -1,
+            secure: false,
+            path: '/'
+         };
+         var request = http.createRequest({
+            method: 'GET',
+            path: '/'
+         });
+         request.addCookie(cookie1);
+         request.addCookie(cookie2);
+         forge.log.debug(cat, 'request', request.toString());
+      }
+
+      function response_get_cookies()
+      {
+         var response = http.createResponse();
+         response.appendField('Set-Cookie',
+            'test-cookie1=test-value1; max-age=0; path=/; secure');
+         response.appendField('Set-Cookie',
+            'test-cookie2=test-value2; ' +
+            'expires=Thu, 21-Aug-2008 23:47:25 GMT; path=/');
+         var cookies = response.getCookies();
+         forge.log.debug(cat, 'cookies', cookies);
+      }
+      
+      //]]>
+      </script>
+   </head>
+   <body>
+      <div class="nav"><a href="index.html">Forge Tests</a> / HTTP</div>
+
+      <div class="header">
+         <h1>HTTP Test</h1>
+      </div>
+
+      <div class="content">
+
+      <div id="socketPool">
+         <p>Could not load the flash SocketPool.</p>
+      </div>
+
+      <fieldset class="section">
+         <ul>
+            <li>Use the controls below to test the HTTP client.</li>
+            <li>You currently need a JavaScript console to view the output.</li>
+         </ul>
+      </fieldset>
+
+      <fieldset class="section">
+      <legend>Controls</legend>
+         <button id="init" onclick="javascript:return client_init();">init</button>
+         <button id="cleanup" onclick="javascript:return client_cleanup();">cleanup</button>
+         <button id="send" onclick="javascript:return client_send();">send</button>
+         <button id="send10" onclick="javascript:return client_send_10();">send 10</button>
+         <button id="stress" onclick="javascript:return client_stress();">stress</button>
+         <button id="client_cookies" onclick="javascript:return client_cookies();">cookies</button>
+         <button id="clear_cookies" onclick="javascript:return client_clear_cookies();">clear cookies</button>
+         <button id="add_cookies" onclick="javascript:return request_add_cookies();">add cookies</button>
+         <button id="get_cookies" onclick="javascript:return response_get_cookies();">get cookies</button>
+      </fieldset>
+
+      <fieldset class="section">
+      <legend>Feedback</legend>
+      <p>Feedback from the flash SocketPool:</p>
+      <div id="feedback">
+      None
+      </div>
+
+      </div>
+   </body>
+</html>
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/index.html b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/index.html
new file mode 100644
index 0000000..2e27c8a
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/index.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+   <meta name="author" content="Digital Bazaar, Inc.; http://digitalbazaar.com/" />
+   <meta name="description" content="Forge Tests" />
+   <link type="text/css" rel="stylesheet" media="all" href="screen.css" />
+   <title>Forge Tests</title>
+</head>
+
+<body>
+  <div class="header">
+     <h1>Forge Tests</h1>
+  </div>
+  <div class="content">
+     <fieldset class="section">
+       <ul>
+         <li>These are various tests of the Forge JavaScript libraries.</li>
+         <li>Please see the code and documentation for details.</li>
+       </ul>
+     </fieldset>
+     <fieldset class="section">
+        <legend>Tests</legend>
+        <ul>
+           <li><a href="common.html">Common</a>
+              <sup>1,2</sup>
+           </li>
+           <li><a href="form.html">Form</a>
+              <sup>1,2</sup>
+           </li>
+           <li><a href="performance.html">Performance</a>
+              <sup>1,2</sup>
+           </li>
+           <li><a href="tasks.html">Tasks</a>
+              <sup>1,2</sup>
+           </li>
+           <li><a href="socketPool.html">SocketPool</a>
+              <sup>1</sup>
+           </li>
+           <li><a href="http.html">HTTP</a>
+              <sup>1</sup>
+           </li>
+           <li><a href="tls.html">TLS</a>
+              <sup>2</sup>
+           </li>
+           <li><a href="xhr.html">XHR</a>
+              <sup>1,2</sup>
+           </li>
+           <li><a href="webid.html">Web ID</a>
+              <sup>1,2</sup>
+           </li>
+        </ul>
+        <div>
+           <span><sup>1</sup>: Test works over HTTP</span><br/>
+           <span><sup>2</sup>: Test works over HTTPS</span>
+        </div>
+     </fieldset>
+  </div>
+  <div class="footer">
+     Copyright &copy; 2010 <a href="http://digitalbazaar.com/">Digital Bazaar, Inc.</a>
+  </div>
+</body>
+</html>
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/keygen.html b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/keygen.html
new file mode 100644
index 0000000..22e2432
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/keygen.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="utf-8" />
+  <script type="text/javascript" src="forge/util.js"></script>
+  <script type="text/javascript" src="forge/sha256.js"></script>
+  <script type="text/javascript" src="forge/cipher.js"></script>
+  <script type="text/javascript" src="forge/cipherModes.js"></script>
+  <script type="text/javascript" src="forge/aes.js"></script>
+  <script type="text/javascript" src="forge/prng.js"></script>
+  <script type="text/javascript" src="forge/random.js"></script>
+  <script type="text/javascript" src="forge/jsbn.js"></script>
+  <script type="text/javascript" src="forge/asn1.js"></script>
+  <script type="text/javascript" src="forge/pem.js"></script>
+  <script type="text/javascript" src="forge/rsa.js"></script>
+</head>
+
+<body>
+
+<script type="text/javascript">
+
+function async() {
+  var bits = 2048;
+  console.log('Generating ' + bits + '-bit key-pair...');
+  var st = +new Date();
+  forge.pki.rsa.generateKeyPair({
+    bits: bits,
+    workers: -1,
+    /*workLoad: 100,*/
+    workerScript: 'forge/prime.worker.js'
+  }, function(err, keypair) {
+    var et = +new Date();
+    console.log('Key-pair created in ' + (et - st) + 'ms.');
+    //console.log('private', forge.pki.privateKeyToPem(keypair.privateKey));
+    //console.log('public', forge.pki.publicKeyToPem(keypair.publicKey));
+  });
+}
+
+function sync() {
+  var bits = 2048;
+  console.log('Generating ' + bits + '-bit key-pair...');
+  var st = +new Date();
+  var keypair = forge.pki.rsa.generateKeyPair(bits);
+  var et = +new Date();
+  console.log('Key-pair created in ' + (et - st) + 'ms.');
+  //console.log('private', forge.pki.privateKeyToPem(keypair.privateKey));
+  //console.log('public', forge.pki.publicKeyToPem(keypair.publicKey));
+}
+
+async();
+//sync();
+
+</script>
+
+</body>
+</html>
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/login.css b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/login.css
new file mode 100644
index 0000000..3a1cb05
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/login.css
@@ -0,0 +1,26 @@
+/* WebID CSS */
+
+* {
+margin: 0;
+padding: 0;
+}
+
+p {
+margin: 10px 0;
+}
+
+body {
+margin: 10px;
+font-family: helvetica,arial,sans-serif;
+font-size: 14px;
+}
+
+#header {
+padding: 5px;
+background-color: #ddf;
+border: 2px solid #000;
+}
+
+#header h1 {
+font-size: 20px;
+}
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/loginDemo.html b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/loginDemo.html
new file mode 100644
index 0000000..a1aecd4
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/loginDemo.html
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+   <head>
+      <title>Web ID Login</title>
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js"></script>
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
+      <script type="text/javascript" src="forge/debug.js"></script>
+      <script type="text/javascript" src="forge/util.js"></script>
+      <script type="text/javascript" src="forge/log.js"></script>
+      <script type="text/javascript" src="forge/task.js"></script>
+      <script type="text/javascript" src="forge/socket.js"></script>
+      <script type="text/javascript" src="forge/md5.js"></script>
+      <script type="text/javascript" src="forge/sha1.js"></script>
+      <script type="text/javascript" src="forge/hmac.js"></script>
+      <script type="text/javascript" src="forge/aes.js"></script>
+      <script type="text/javascript" src="forge/pem.js"></script>
+      <script type="text/javascript" src="forge/asn1.js"></script>
+      <script type="text/javascript" src="forge/jsbn.js"></script>
+      <script type="text/javascript" src="forge/prng.js"></script>
+      <script type="text/javascript" src="forge/random.js"></script>
+      <script type="text/javascript" src="forge/oids.js"></script>
+      <script type="text/javascript" src="forge/rsa.js"></script>
+      <script type="text/javascript" src="forge/pbe.js"></script>
+      <script type="text/javascript" src="forge/x509.js"></script>
+      <script type="text/javascript" src="forge/pki.js"></script>
+      <script type="text/javascript" src="forge/tls.js"></script>
+      <script type="text/javascript" src="forge/aesCipherSuites.js"></script>
+      <script type="text/javascript" src="forge/http.js"></script>
+      <script type="text/javascript" src="forge/xhr.js"></script>
+      <script type="text/javascript" src="loginDemo.js"></script>
+      
+      <link type="text/css" rel="stylesheet" media="all" href="login.css" />
+   </head>
+<body>
+
+<div id="header">
+   <h1>Web ID Login</h1>
+</div>
+
+<div id="content">
+   <p>Please select a Web ID to log in to <span id="domain">this website</span>.</p>
+
+   <div id="webids"></div>
+
+   <p>Do not select an identity if you do not trust this website.</p>
+
+   <!-- div used to hold the flash socket pool implementation -->
+   <div id="socketPool">
+      <p>Could not load the flash SocketPool.</p>
+   </div>
+</div>
+
+</body>
+</html>
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/loginDemo.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/loginDemo.js
new file mode 100644
index 0000000..859e1f0
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/loginDemo.js
@@ -0,0 +1,149 @@
+/**
+ * Forge Web ID Tests
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010 Digital Bazaar, Inc. All rights reserved.
+ */
+(function($)
+{
+   // load flash socket pool
+   window.forge.socketPool = {};
+   window.forge.socketPool.ready = function()
+   {
+      // init page
+      init($);
+   };
+   swfobject.embedSWF(
+      'forge/SocketPool.swf', 'socketPool', '0', '0', '9.0.0',
+      false, {}, {allowscriptaccess: 'always'}, {});
+})(jQuery);
+
+var init = function($)
+{
+   // logging category
+   var cat = 'forge.tests.loginDemo';
+   
+   // local alias
+   var forge = window.forge;
+   try
+   {
+      // get query variables
+      var query = forge.util.getQueryVariables();
+      var domain = query.domain || '';
+      var auth = query.auth || '';
+      var redirect = query.redirect || '';
+      var pport = query.pport || 843;
+      redirect = 'https://' + domain + '/' + redirect;
+      if(domain)
+      {
+         $('#domain').html('`' + domain + '`');
+      } 
+      
+      // for chosen webid
+      var chosen = null;
+      
+      // init forge xhr
+      forge.xhr.init({
+         flashId: 'socketPool',
+         msie: $.browser.msie,
+         url: 'https://' + domain,
+         policyPort: pport,
+         connections: 1,
+         caCerts: [],
+         verify: function(c, verified, depth, certs)
+         {
+            // don't care about cert verification for test
+            return true;
+         },
+         getCertificate: function(c)
+         {
+            forge.log.debug(cat, 'using cert', chosen.certificate);
+            return chosen.certificate;
+         },
+         getPrivateKey: function(c)
+         {
+            //forge.log.debug(cat, 'using private key', chosen.privateKey);
+            return chosen.privateKey;
+         }
+      });
+      
+      // get flash API
+      var flashApi = document.getElementById('socketPool');
+      
+      // get web ids collection
+      var webids = forge.util.getItem(
+         flashApi, 'forge.test.webid', 'webids');
+      webids = webids || {};
+      
+      var id = 0;
+      var list = $('<ul/>');
+      for(var key in webids)
+      {
+         (function(webid)
+         {
+            var cert = forge.pki.certificateFromPem(webid.certificate);
+            var item = $('<li/>');
+            var button = $('<button>');
+            button.attr('id', '' + (webid + id++));
+            button.html('Choose');
+            button.click(function()
+            {
+               button.attr('disabled', 'disabled');
+               
+               // set chosen webid
+               chosen = webid;
+               
+               // do webid call
+               $.ajax(
+               {
+                  type: 'GET',
+                  url: '/' + auth,
+                  success: function(data, textStatus, xhr)
+                  {
+                     if(data !== '')
+                     {
+                        forge.log.debug(cat, 'authentication completed');
+                        forge.log.debug(cat, data);
+                        window.name = data;
+                     }
+                     else
+                     {
+                        forge.log.debug(cat, 'authentication failed');
+                        window.name = '';
+                     }
+                     window.location = redirect;
+                  },
+                  error: function(xhr, textStatus, errorThrown)
+                  {
+                     forge.log.error(cat, 'authentication failed');
+                  },
+                  xhr: forge.xhr.create
+               });
+            });
+            item.append(button);
+            item.append(' ' + key + '<br/>');
+            
+            // display certificate attributes
+            var attr;
+            for(var n = 0; n < cert.subject.attributes.length; ++n)
+            {
+               attr = cert.subject.attributes[n];
+               item.append(attr.name + ': ' + attr.value + '<br/>');
+            }
+            
+            list.append(item);
+         })(webids[key]);
+      }
+      if(list.html() === '<ul/>')
+      {
+         list.append('None');
+      }
+      
+      $('#webids').append(list);
+   }
+   catch(ex)
+   {
+      forge.log.error(cat, ex);
+   }
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/nodejs-create-cert.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/nodejs-create-cert.js
new file mode 100644
index 0000000..d1666eb
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/nodejs-create-cert.js
@@ -0,0 +1,110 @@
+var forge = require('../js/forge');
+
+console.log('Generating 1024-bit key-pair...');
+var keys = forge.pki.rsa.generateKeyPair(1024);
+console.log('Key-pair created.');
+
+console.log('Creating self-signed certificate...');
+var cert = forge.pki.createCertificate();
+cert.publicKey = keys.publicKey;
+cert.serialNumber = '01';
+cert.validity.notBefore = new Date();
+cert.validity.notAfter = new Date();
+cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1);
+var attrs = [{
+  name: 'commonName',
+  value: 'example.org'
+}, {
+  name: 'countryName',
+  value: 'US'
+}, {
+  shortName: 'ST',
+  value: 'Virginia'
+}, {
+  name: 'localityName',
+  value: 'Blacksburg'
+}, {
+  name: 'organizationName',
+  value: 'Test'
+}, {
+  shortName: 'OU',
+  value: 'Test'
+}];
+cert.setSubject(attrs);
+cert.setIssuer(attrs);
+cert.setExtensions([{
+  name: 'basicConstraints',
+  cA: true/*,
+  pathLenConstraint: 4*/
+}, {
+  name: 'keyUsage',
+  keyCertSign: true,
+  digitalSignature: true,
+  nonRepudiation: true,
+  keyEncipherment: true,
+  dataEncipherment: true
+}, {
+  name: 'extKeyUsage',
+  serverAuth: true,
+  clientAuth: true,
+  codeSigning: true,
+  emailProtection: true,
+  timeStamping: true
+}, {
+  name: 'nsCertType',
+  client: true,
+  server: true,
+  email: true,
+  objsign: true,
+  sslCA: true,
+  emailCA: true,
+  objCA: true
+}, {
+  name: 'subjectAltName',
+  altNames: [{
+    type: 6, // URI
+    value: 'http://example.org/webid#me'
+  }, {
+    type: 7, // IP
+    ip: '127.0.0.1'
+  }]
+}, {
+  name: 'subjectKeyIdentifier'
+}]);
+// FIXME: add authorityKeyIdentifier extension
+
+// self-sign certificate
+cert.sign(keys.privateKey/*, forge.md.sha256.create()*/);
+console.log('Certificate created.');
+
+// PEM-format keys and cert
+var pem = {
+  privateKey: forge.pki.privateKeyToPem(keys.privateKey),
+  publicKey: forge.pki.publicKeyToPem(keys.publicKey),
+  certificate: forge.pki.certificateToPem(cert)
+};
+
+console.log('\nKey-Pair:');
+console.log(pem.privateKey);
+console.log(pem.publicKey);
+
+console.log('\nCertificate:');
+console.log(pem.certificate);
+
+// verify certificate
+var caStore = forge.pki.createCaStore();
+caStore.addCertificate(cert);
+try {
+  forge.pki.verifyCertificateChain(caStore, [cert],
+    function(vfd, depth, chain) {
+      if(vfd === true) {
+        console.log('SubjectKeyIdentifier verified: ' +
+          cert.verifySubjectKeyIdentifier());
+        console.log('Certificate verified.');
+      }
+      return true;
+  });
+} catch(ex) {
+  console.log('Certificate verification failure: ' +
+    JSON.stringify(ex, null, 2));
+}
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/nodejs-create-csr.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/nodejs-create-csr.js
new file mode 100644
index 0000000..1cb335f
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/nodejs-create-csr.js
@@ -0,0 +1,66 @@
+var forge = require('../js/forge');
+
+console.log('Generating 1024-bit key-pair...');
+var keys = forge.pki.rsa.generateKeyPair(1024);
+console.log('Key-pair created.');
+
+console.log('Creating certification request (CSR) ...');
+var csr = forge.pki.createCertificationRequest();
+csr.publicKey = keys.publicKey;
+csr.setSubject([{
+  name: 'commonName',
+  value: 'example.org'
+}, {
+  name: 'countryName',
+  value: 'US'
+}, {
+  shortName: 'ST',
+  value: 'Virginia'
+}, {
+  name: 'localityName',
+  value: 'Blacksburg'
+}, {
+  name: 'organizationName',
+  value: 'Test'
+}, {
+  shortName: 'OU',
+  value: 'Test'
+}]);
+// add optional attributes
+csr.setAttributes([{
+  name: 'challengePassword',
+  value: 'password'
+}, {
+  name: 'unstructuredName',
+  value: 'My company'
+}]);
+
+// sign certification request
+csr.sign(keys.privateKey/*, forge.md.sha256.create()*/);
+console.log('Certification request (CSR) created.');
+
+// PEM-format keys and csr
+var pem = {
+  privateKey: forge.pki.privateKeyToPem(keys.privateKey),
+  publicKey: forge.pki.publicKeyToPem(keys.publicKey),
+  csr: forge.pki.certificationRequestToPem(csr)
+};
+
+console.log('\nKey-Pair:');
+console.log(pem.privateKey);
+console.log(pem.publicKey);
+
+console.log('\nCertification Request (CSR):');
+console.log(pem.csr);
+
+// verify certification request
+try {
+  if(csr.verify()) {
+    console.log('Certification request (CSR) verified.');
+  } else {
+    throw new Error('Signature not verified.');
+  }
+} catch(err) {
+  console.log('Certification request (CSR) verification failure: ' +
+    JSON.stringify(err, null, 2));
+}
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/nodejs-create-pkcs12.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/nodejs-create-pkcs12.js
new file mode 100644
index 0000000..e52eefa
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/nodejs-create-pkcs12.js
@@ -0,0 +1,160 @@
+var forge = require('../js/forge');
+
+try {
+  // generate a keypair
+  console.log('Generating 1024-bit key-pair...');
+  var keys = forge.pki.rsa.generateKeyPair(1024);
+  console.log('Key-pair created.');
+
+  // create a certificate
+  console.log('Creating self-signed certificate...');
+  var cert = forge.pki.createCertificate();
+  cert.publicKey = keys.publicKey;
+  cert.serialNumber = '01';
+  cert.validity.notBefore = new Date();
+  cert.validity.notAfter = new Date();
+  cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1);
+  var attrs = [{
+    name: 'commonName',
+    value: 'example.org'
+  }, {
+    name: 'countryName',
+    value: 'US'
+  }, {
+    shortName: 'ST',
+    value: 'Virginia'
+  }, {
+    name: 'localityName',
+    value: 'Blacksburg'
+  }, {
+    name: 'organizationName',
+    value: 'Test'
+  }, {
+    shortName: 'OU',
+    value: 'Test'
+  }];
+  cert.setSubject(attrs);
+  cert.setIssuer(attrs);
+  cert.setExtensions([{
+    name: 'basicConstraints',
+    cA: true
+  }, {
+    name: 'keyUsage',
+    keyCertSign: true,
+    digitalSignature: true,
+    nonRepudiation: true,
+    keyEncipherment: true,
+    dataEncipherment: true
+  }, {
+    name: 'subjectAltName',
+    altNames: [{
+      type: 6, // URI
+      value: 'http://example.org/webid#me'
+    }]
+  }]);
+
+  // self-sign certificate
+  cert.sign(keys.privateKey);
+  console.log('Certificate created.');
+
+  // create PKCS12
+  console.log('\nCreating PKCS#12...');
+  var password = 'password';
+  var newPkcs12Asn1 = forge.pkcs12.toPkcs12Asn1(
+    keys.privateKey, [cert], password,
+    {generateLocalKeyId: true, friendlyName: 'test'});
+  var newPkcs12Der = forge.asn1.toDer(newPkcs12Asn1).getBytes();
+
+  console.log('\nBase64-encoded new PKCS#12:');
+  console.log(forge.util.encode64(newPkcs12Der));
+
+  // create CA store (w/own certificate in this example)
+  var caStore = forge.pki.createCaStore([cert]);
+
+  console.log('\nLoading new PKCS#12 to confirm...');
+  loadPkcs12(newPkcs12Der, password, caStore);
+} catch(ex) {
+  if(ex.stack) {
+    console.log(ex.stack);
+  } else {
+    console.log('Error', ex);
+  }
+}
+
+function loadPkcs12(pkcs12Der, password, caStore) {
+  var pkcs12Asn1 = forge.asn1.fromDer(pkcs12Der);
+  var pkcs12 = forge.pkcs12.pkcs12FromAsn1(pkcs12Asn1, false, password);
+
+  // load keypair and cert chain from safe content(s) and map to key ID
+  var map = {};
+  for(var sci = 0; sci < pkcs12.safeContents.length; ++sci) {
+    var safeContents = pkcs12.safeContents[sci];
+    console.log('safeContents ' + (sci + 1));
+
+    for(var sbi = 0; sbi < safeContents.safeBags.length; ++sbi) {
+      var safeBag = safeContents.safeBags[sbi];
+      console.log('safeBag.type: ' + safeBag.type);
+
+      var localKeyId = null;
+      if(safeBag.attributes.localKeyId) {
+        localKeyId = forge.util.bytesToHex(
+          safeBag.attributes.localKeyId[0]);
+        console.log('localKeyId: ' + localKeyId);
+        if(!(localKeyId in map)) {
+          map[localKeyId] = {
+            privateKey: null,
+            certChain: []
+          };
+        }
+      } else {
+        // no local key ID, skip bag
+        continue;
+      }
+
+      // this bag has a private key
+      if(safeBag.type === forge.pki.oids.pkcs8ShroudedKeyBag) {
+        console.log('found private key');
+        map[localKeyId].privateKey = safeBag.key;
+      } else if(safeBag.type === forge.pki.oids.certBag) {
+        // this bag has a certificate
+        console.log('found certificate');
+        map[localKeyId].certChain.push(safeBag.cert);
+      }
+    }
+  }
+
+  console.log('\nPKCS#12 Info:');
+
+  for(var localKeyId in map) {
+    var entry = map[localKeyId];
+    console.log('\nLocal Key ID: ' + localKeyId);
+    if(entry.privateKey) {
+      var privateKeyP12Pem = forge.pki.privateKeyToPem(entry.privateKey);
+      var encryptedPrivateKeyP12Pem = forge.pki.encryptRsaPrivateKey(
+        entry.privateKey, password);
+
+      console.log('\nPrivate Key:');
+      console.log(privateKeyP12Pem);
+      console.log('Encrypted Private Key (password: "' + password + '"):');
+      console.log(encryptedPrivateKeyP12Pem);
+    } else {
+      console.log('');
+    }
+    if(entry.certChain.length > 0) {
+      console.log('Certificate chain:');
+      var certChain = entry.certChain;
+      for(var i = 0; i < certChain.length; ++i) {
+        var certP12Pem = forge.pki.certificateToPem(certChain[i]);
+        console.log(certP12Pem);
+      }
+
+      var chainVerified = false;
+      try {
+        chainVerified = forge.pki.verifyCertificateChain(caStore, certChain);
+      } catch(ex) {
+        chainVerified = ex;
+      }
+      console.log('Certificate chain verified: ', chainVerified);
+    }
+  }
+}
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/nodejs-imap.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/nodejs-imap.js
new file mode 100644
index 0000000..ba024ef
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/nodejs-imap.js
@@ -0,0 +1,46 @@
+var forge = require('../js/forge');
+var net = require('net');
+
+var socket = new net.Socket();
+
+var client = forge.tls.createConnection({
+  server: false,
+  verify: function(connection, verified, depth, certs) {
+    // skip verification for testing
+    return true;
+  },
+  connected: function(connection) {
+    console.log('[tls] connected');
+  },
+  tlsDataReady: function(connection) {
+    // encrypted data is ready to be sent to the server
+    var data = connection.tlsData.getBytes();
+    socket.write(data, 'binary');
+  },
+  dataReady: function(connection) {
+    // clear data from the server is ready
+    var data = connection.data.getBytes();
+    console.log('[tls] received from the server: ' + data);
+    client.close();
+  },
+  closed: function() {
+    console.log('[tls] disconnected');
+  },
+  error: function(connection, error) {
+    console.log('[tls] error', error);
+  }
+});
+
+socket.on('connect', function() {
+  console.log('[socket] connected');
+  client.handshake();
+});
+socket.on('data', function(data) {
+  client.process(data.toString('binary'));
+});
+socket.on('end', function() {
+  console.log('[socket] disconnected');
+});
+
+// connect to gmail's imap server
+socket.connect(993, 'imap.gmail.com');
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/nodejs-tls.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/nodejs-tls.js
new file mode 100644
index 0000000..5be6acd
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/nodejs-tls.js
@@ -0,0 +1,189 @@
+var forge = require('../js/forge');
+
+// function to create certificate
+var createCert = function(cn, data) {
+  console.log(
+    'Generating 512-bit key-pair and certificate for \"' + cn + '\".');
+  var keys = forge.pki.rsa.generateKeyPair(512);
+  console.log('key-pair created.');
+
+  var cert = forge.pki.createCertificate();
+  cert.serialNumber = '01';
+  cert.validity.notBefore = new Date();
+  cert.validity.notAfter = new Date();
+  cert.validity.notAfter.setFullYear(
+    cert.validity.notBefore.getFullYear() + 1);
+  var attrs = [{
+    name: 'commonName',
+    value: cn
+  }, {
+    name: 'countryName',
+    value: 'US'
+  }, {
+    shortName: 'ST',
+    value: 'Virginia'
+  }, {
+    name: 'localityName',
+    value: 'Blacksburg'
+  }, {
+    name: 'organizationName',
+    value: 'Test'
+  }, {
+    shortName: 'OU',
+    value: 'Test'
+  }];
+  cert.setSubject(attrs);
+  cert.setIssuer(attrs);
+  cert.setExtensions([{
+    name: 'basicConstraints',
+    cA: true
+  }, {
+    name: 'keyUsage',
+    keyCertSign: true,
+    digitalSignature: true,
+    nonRepudiation: true,
+    keyEncipherment: true,
+    dataEncipherment: true
+  }, {
+    name: 'subjectAltName',
+    altNames: [{
+      type: 6, // URI
+      value: 'http://myuri.com/webid#me'
+    }]
+  }]);
+  // FIXME: add subjectKeyIdentifier extension
+  // FIXME: add authorityKeyIdentifier extension
+  cert.publicKey = keys.publicKey;
+
+  // self-sign certificate
+  cert.sign(keys.privateKey);
+
+  // save data
+  data[cn] = {
+    cert: forge.pki.certificateToPem(cert),
+    privateKey: forge.pki.privateKeyToPem(keys.privateKey)
+  };
+
+  console.log('certificate created for \"' + cn + '\": \n' + data[cn].cert);
+};
+
+var end = {};
+var data = {};
+
+// create certificate for server and client
+createCert('server', data);
+createCert('client', data);
+
+var success = false;
+
+// create TLS client
+end.client = forge.tls.createConnection({
+  server: false,
+  caStore: [data.server.cert],
+  sessionCache: {},
+  // supported cipher suites in order of preference
+  cipherSuites: [
+    forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+    forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+  virtualHost: 'server',
+  verify: function(c, verified, depth, certs) {
+    console.log(
+      'TLS Client verifying certificate w/CN: \"' +
+      certs[0].subject.getField('CN').value +
+      '\", verified: ' + verified + '...');
+    return verified;
+  },
+  connected: function(c) {
+    console.log('Client connected...');
+
+    // send message to server
+    setTimeout(function() {
+      c.prepareHeartbeatRequest('heartbeat');
+      c.prepare('Hello Server');
+    }, 1);
+  },
+  getCertificate: function(c, hint) {
+    console.log('Client getting certificate ...');
+    return data.client.cert;
+  },
+  getPrivateKey: function(c, cert) {
+    return data.client.privateKey;
+  },
+  tlsDataReady: function(c) {
+    // send TLS data to server
+    end.server.process(c.tlsData.getBytes());
+  },
+  dataReady: function(c) {
+    var response = c.data.getBytes();
+    console.log('Client received \"' + response + '\"');
+    success = (response === 'Hello Client');
+    c.close();
+  },
+  heartbeatReceived: function(c, payload) {
+    console.log('Client received heartbeat: ' + payload.getBytes());
+  },
+  closed: function(c) {
+    console.log('Client disconnected.');
+    if(success) {
+      console.log('PASS');
+    } else {
+      console.log('FAIL');
+    }
+  },
+  error: function(c, error) {
+    console.log('Client error: ' + error.message);
+  }
+});
+
+// create TLS server
+end.server = forge.tls.createConnection({
+  server: true,
+  caStore: [data.client.cert],
+  sessionCache: {},
+  // supported cipher suites in order of preference
+  cipherSuites: [
+    forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+    forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+  connected: function(c) {
+    console.log('Server connected');
+    c.prepareHeartbeatRequest('heartbeat');
+  },
+  verifyClient: true,
+  verify: function(c, verified, depth, certs) {
+    console.log(
+      'Server verifying certificate w/CN: \"' +
+      certs[0].subject.getField('CN').value +
+      '\", verified: ' + verified + '...');
+    return verified;
+  },
+  getCertificate: function(c, hint) {
+    console.log('Server getting certificate for \"' + hint[0] + '\"...');
+    return data.server.cert;
+  },
+  getPrivateKey: function(c, cert) {
+    return data.server.privateKey;
+  },
+  tlsDataReady: function(c) {
+    // send TLS data to client
+    end.client.process(c.tlsData.getBytes());
+  },
+  dataReady: function(c) {
+    console.log('Server received \"' + c.data.getBytes() + '\"');
+
+    // send response
+    c.prepare('Hello Client');
+    c.close();
+  },
+  heartbeatReceived: function(c, payload) {
+    console.log('Server received heartbeat: ' + payload.getBytes());
+  },
+  closed: function(c) {
+    console.log('Server disconnected.');
+  },
+  error: function(c, error) {
+    console.log('Server error: ' + error.message);
+  }
+});
+
+console.log('created TLS client and server, doing handshake...');
+end.client.handshake();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/nodejs-ws-webid.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/nodejs-ws-webid.js
new file mode 100644
index 0000000..fae0b82
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/nodejs-ws-webid.js
@@ -0,0 +1,491 @@
+var forge = require('../js/forge');
+var fs = require('fs');
+var http = require('http');
+//var rdf = require('./rdflib');
+var sys = require('sys');
+var urllib = require('url');
+var ws = require('./ws');
+
+// remove xmlns from input
+var normalizeNs = function(input, ns) {
+  var rval = null;
+
+  // primitive
+  if(typeof input === 'string' ||
+    typeof input === 'number' ||
+    typeof input === 'boolean') {
+    rval = input;
+  }
+  // array
+  else if(forge.util.isArray(input)) {
+    rval = [];
+    for(var i = 0; i < input.length; ++i) {
+      rval.push(normalizeNs(input[i], ns));
+    }
+  }
+  // object
+  else {
+    if('@' in input) {
+      // copy namespace map
+      var newNs = {};
+      for(var key in ns) {
+        newNs[key] = ns[key];
+      }
+      ns = newNs;
+
+      // update namespace map
+      for(var key in input['@']) {
+        if(key.indexOf('xmlns:') === 0) {
+          ns[key.substr(6)] = input['@'][key];
+        }
+      }
+    }
+
+    rval = {};
+    for(var key in input) {
+      if(key.indexOf('xmlns:') !== 0) {
+        var value = input[key];
+        var colon = key.indexOf(':');
+        if(colon !== -1) {
+          var prefix = key.substr(0, colon);
+          if(prefix in ns) {
+            key = ns[prefix] + key.substr(colon + 1);
+          }
+        }
+        rval[key] = normalizeNs(value, ns);
+      }
+    }
+  }
+
+  return rval;
+};
+
+// gets public key from WebID rdf
+var getPublicKey = function(data, uri, callback) {
+  // FIXME: use RDF library to simplify code below
+  //var kb = new rdf.RDFParser(rdf.IndexedFormula(), uri).loadBuf(data);
+  //var CERT = rdf.Namespace('http://www.w3.org/ns/auth/cert#');
+  //var RSA  = rdf.Namespace('http://www.w3.org/ns/auth/rsa#');
+  var RDF = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
+  var CERT = 'http://www.w3.org/ns/auth/cert#';
+  var RSA  = 'http://www.w3.org/ns/auth/rsa#';
+  var desc = RDF + 'Description';
+  var about = RDF + 'about';
+  var type = RDF + 'type';
+  var resource = RDF + 'resource';
+  var publicKey = RSA + 'RSAPublicKey';
+  var modulus = RSA + 'modulus';
+  var exponent = RSA + 'public_exponent';
+  var identity = CERT + 'identity';
+  var hex = CERT + 'hex';
+  var decimal = CERT + 'decimal';
+
+  // gets a resource identifer from a node
+  var getResource = function(node, key) {
+    var rval = null;
+
+    // special case 'about'
+    if(key === about) {
+      if('@' in node && about in node['@']) {
+        rval = node['@'][about];
+      }
+    }
+    // any other resource
+    else if(
+       key in node &&
+       typeof node[key] === 'object' && !forge.util.isArray(node[key]) &&
+       '@' in node[key] && resource in node[key]['@']) {
+      rval = node[key]['@'][resource];
+    }
+
+    return rval;
+  };
+
+  // parse XML
+  uri = urllib.parse(uri);
+  var xml2js = require('./xml2js');
+  var parser = new xml2js.Parser();
+  parser.addListener('end', function(result) {
+    // normalize namespaces
+    result = normalizeNs(result, {});
+
+    // find grab all public keys whose identity matches hash from uri
+    var keys = [];
+    if(desc in result) {
+      // normalize RDF descriptions to array
+      if(!forge.util.isArray(result[desc])) {
+        desc = [result[desc]];
+      }
+      else {
+        desc = result[desc];
+      }
+
+      // collect properties for all resources
+      var graph = {};
+      for(var i = 0; i < desc.length; ++i) {
+        var node = desc[i];
+        var res = {};
+        for(var key in node) {
+          var obj = getResource(node, key);
+          res[key] = (obj === null) ? node[key] : obj;
+        }
+        graph[getResource(node, about) || ''] = res;
+      }
+
+      // for every public key w/identity that matches the uri hash
+      // save the public key modulus and exponent
+      for(var r in graph) {
+        var props = graph[r];
+        if(identity in props &&
+          type in props &&
+          props[type] === publicKey &&
+          props[identity] === uri.hash &&
+          modulus in props &&
+          exponent in props &&
+          props[modulus] in graph &&
+          props[exponent] in graph &&
+          hex in graph[props[modulus]] &&
+          decimal in graph[props[exponent]]) {
+          keys.push({
+            modulus: graph[props[modulus]][hex],
+            exponent: graph[props[exponent]][decimal]
+          });
+        }
+      }
+    }
+
+    sys.log('Public keys from RDF: ' + JSON.stringify(keys));
+    callback(keys);
+  });
+  parser.parseString(data);
+};
+
+// compares two public keys for equality
+var comparePublicKeys = function(key1, key2) {
+  return key1.modulus === key2.modulus && key1.exponent === key2.exponent;
+};
+
+// gets the RDF data from a URL
+var fetchUrl = function(url, callback, redirects) {
+  // allow 3 redirects by default
+  if(typeof(redirects) === 'undefined') {
+    redirects = 3;
+  }
+
+  sys.log('Fetching URL: \"' + url + '\"');
+
+  // parse URL
+  url = forge.util.parseUrl(url);
+  var client = http.createClient(
+    url.port, url.fullHost, url.scheme === 'https');
+  var request = client.request('GET', url.path, {
+    'Host': url.host,
+    'Accept': 'application/rdf+xml'
+  });
+  request.addListener('response', function(response) {
+    var body = '';
+
+    // error, return empty body
+    if(response.statusCode >= 400) {
+       callback(body);
+    }
+    // follow redirect
+    else if(response.statusCode === 302) {
+      if(redirects > 0) {
+        // follow redirect
+        fetchUrl(response.headers.location, callback, --redirects);
+      }
+      else {
+        // return empty body
+        callback(body);
+      }
+    }
+    // handle data
+    else {
+      response.setEncoding('utf8');
+      response.addListener('data', function(chunk) {
+        body += chunk;
+      });
+      response.addListener('end', function() {
+        callback(body);
+      });
+    }
+  });
+  request.end();
+};
+
+// does WebID authentication
+var authenticateWebId = function(c, state) {
+  var auth = false;
+
+  // get client-certificate
+  var cert = c.peerCertificate;
+
+  // get public key from certificate
+  var publicKey = {
+    modulus: cert.publicKey.n.toString(16).toLowerCase(),
+    exponent: cert.publicKey.e.toString(10)
+  };
+
+  sys.log(
+    'Server verifying certificate w/CN: \"' +
+    cert.subject.getField('CN').value + '\"\n' +
+    'Public Key: ' + JSON.stringify(publicKey));
+
+  // build queue of subject alternative names to authenticate with
+  var altNames = [];
+  var ext = cert.getExtension({name: 'subjectAltName'});
+  if(ext !== null && ext.altNames) {
+    for(var i = 0; i < ext.altNames.length; ++i) {
+      var altName = ext.altNames[i];
+      if(altName.type === 6) {
+        altNames.push(altName.value);
+      }
+    }
+  }
+
+  // create authentication processor
+  var authNext = function() {
+    if(!auth) {
+      // no more alt names, auth failed
+      if(altNames.length === 0) {
+        sys.log('WebID authentication FAILED.');
+        c.prepare(JSON.stringify({
+          success: false,
+          error: 'Not Authenticated'
+        }));
+        c.close();
+      }
+      // try next alt name
+      else {
+        // fetch URL
+        var url = altNames.shift();
+        fetchUrl(url, function(body) {
+          // get public key
+          getPublicKey(body, url, function(keys) {
+            // compare public keys from RDF until one matches
+            for(var i = 0; !auth && i < keys.length; ++i) {
+              auth = comparePublicKeys(keys[i], publicKey);
+            }
+            if(auth) {
+              // send authenticated notice to client
+              sys.log('WebID authentication PASSED.');
+              state.authenticated = true;
+              c.prepare(JSON.stringify({
+                success: true,
+                cert: forge.pki.certificateToPem(cert),
+                webID: url,
+                rdf: forge.util.encode64(body)
+              }));
+            }
+            else {
+              // try next alt name
+              authNext();
+            }
+          });
+        });
+      }
+    }
+  };
+
+  // do auth
+  authNext();
+};
+
+// creates credentials (private key + certificate)
+var createCredentials = function(cn, credentials) {
+  sys.log('Generating 512-bit key-pair and certificate for \"' + cn + '\".');
+  var keys = forge.pki.rsa.generateKeyPair(512);
+  sys.log('key-pair created.');
+
+  var cert = forge.pki.createCertificate();
+  cert.serialNumber = '01';
+  cert.validity.notBefore = new Date();
+  cert.validity.notAfter = new Date();
+  cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1);
+  var attrs = [{
+    name: 'commonName',
+    value: cn
+  }, {
+    name: 'countryName',
+    value: 'US'
+  }, {
+    shortName: 'ST',
+    value: 'Virginia'
+  }, {
+    name: 'localityName',
+    value: 'Blacksburg'
+  }, {
+    name: 'organizationName',
+    value: 'Test'
+  }, {
+    shortName: 'OU',
+    value: 'Test'
+  }];
+  cert.setSubject(attrs);
+  cert.setIssuer(attrs);
+  cert.setExtensions([{
+    name: 'basicConstraints',
+    cA: true
+  }, {
+    name: 'keyUsage',
+    keyCertSign: true,
+    digitalSignature: true,
+    nonRepudiation: true,
+    keyEncipherment: true,
+    dataEncipherment: true
+  }, {
+    name: 'subjectAltName',
+    altNames: [{
+      type: 6, // URI
+      value: 'http://myuri.com/webid#me'
+    }]
+  }]);
+  // FIXME: add subjectKeyIdentifier extension
+  // FIXME: add authorityKeyIdentifier extension
+  cert.publicKey = keys.publicKey;
+
+  // self-sign certificate
+  cert.sign(keys.privateKey);
+
+  // save credentials
+  credentials.key = forge.pki.privateKeyToPem(keys.privateKey);
+  credentials.cert = forge.pki.certificateToPem(cert);
+
+  sys.log('Certificate created for \"' + cn + '\": \n' + credentials.cert);
+};
+
+// initialize credentials
+var credentials = {
+  key: null,
+  cert: null
+};
+
+// read private key from file
+var readPrivateKey = function(filename) {
+  credentials.key = fs.readFileSync(filename);
+  // try to parse from PEM as test
+  forge.pki.privateKeyFromPem(credentials.key);
+};
+
+// read certificate from file
+var readCertificate = function(filename) {
+  credentials.cert = fs.readFileSync(filename);
+  // try to parse from PEM as test
+  forge.pki.certificateFromPem(credentials.cert);
+};
+
+// parse command line options
+var opts = require('opts');
+var options = [
+{ short       : 'v'
+, long        : 'version'
+, description : 'Show version and exit'
+, callback    : function() { console.log('v1.0'); process.exit(1); }
+},
+{ short       : 'p'
+, long        : 'port'
+, description : 'The port to listen for WebSocket connections on'
+, value       : true
+},
+{ long        : 'key'
+, description : 'The server private key file to use in PEM format'
+, value       : true
+, callback    : readPrivateKey
+},
+{ long        : 'cert'
+, description : 'The server certificate file to use in PEM format'
+, value       : true
+, callback    : readCertificate
+}
+];
+opts.parse(options, true);
+
+// create credentials for server
+if(credentials.key === null || credentials.cert === null) {
+  createCredentials('server', credentials);
+}
+
+// function to create TLS server connection
+var createTls = function(websocket) {
+  var state = {
+    authenticated: false
+  };
+  return forge.tls.createConnection({
+    server: true,
+    caStore: [],
+    sessionCache: {},
+    // supported cipher suites in order of preference
+    cipherSuites: [
+       forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+       forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+    connected: function(c) {
+      sys.log('Server connected');
+
+      // do WebID authentication
+      try {
+        authenticateWebId(c, state);
+      }
+      catch(ex) {
+        c.close();
+      }
+    },
+    verifyClient: true,
+    verify: function(c, verified, depth, certs) {
+      // accept certs w/unknown-CA (48)
+      if(verified === 48) {
+        verified = true;
+      }
+      return verified;
+    },
+    getCertificate: function(c, hint) {
+       sys.log('Server using certificate for \"' + hint[0] + '\"');
+       return credentials.cert;
+    },
+    getPrivateKey: function(c, cert) {
+       return credentials.key;
+    },
+    tlsDataReady: function(c) {
+       // send base64-encoded TLS data over websocket
+       websocket.write(forge.util.encode64(c.tlsData.getBytes()));
+    },
+    dataReady: function(c) {
+      // ignore any data until connection is authenticated
+      if(state.authenticated) {
+        sys.log('Server received \"' + c.data.getBytes() + '\"');
+      }
+    },
+    closed: function(c) {
+      sys.log('Server disconnected');
+      websocket.end();
+    },
+    error: function(c, error) {
+      sys.log('Server error: ' + error.message);
+    }
+  });
+};
+
+// create websocket server
+var port = opts.get('port') || 8080;
+ws.createServer(function(websocket) {
+  // create TLS server connection
+  var tls = createTls(websocket);
+
+  websocket.addListener('connect', function(resource) {
+    sys.log('WebSocket connected: ' + resource);
+
+    // close connection after 30 second timeout
+    setTimeout(websocket.end, 30 * 1000);
+  });
+
+  websocket.addListener('data', function(data) {
+    // base64-decode data and process it
+    tls.process(forge.util.decode64(data));
+  });
+
+  websocket.addListener('close', function() {
+    sys.log('WebSocket closed');
+  });
+}).listen(port);
+
+sys.log('WebSocket WebID server running on port ' + port);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/nodejs-ws.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/nodejs-ws.js
new file mode 100644
index 0000000..164962d
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/nodejs-ws.js
@@ -0,0 +1,166 @@
+var sys = require('sys');
+var ws = require('./ws');
+var forge = require('../js/forge');
+
+// function to create certificate
+var createCert = function(cn, data)
+{
+   sys.puts(
+      'Generating 512-bit key-pair and certificate for \"' + cn + '\".');
+   var keys = forge.pki.rsa.generateKeyPair(512);
+   sys.puts('key-pair created.');
+
+   var cert = forge.pki.createCertificate();
+   cert.serialNumber = '01';
+   cert.validity.notBefore = new Date();
+   cert.validity.notAfter = new Date();
+   cert.validity.notAfter.setFullYear(
+      cert.validity.notBefore.getFullYear() + 1);
+   var attrs = [{
+      name: 'commonName',
+      value: cn
+   }, {
+      name: 'countryName',
+      value: 'US'
+   }, {
+      shortName: 'ST',
+      value: 'Virginia'
+   }, {
+      name: 'localityName',
+      value: 'Blacksburg'
+   }, {
+      name: 'organizationName',
+      value: 'Test'
+   }, {
+      shortName: 'OU',
+      value: 'Test'
+   }];
+   cert.setSubject(attrs);
+   cert.setIssuer(attrs);
+   cert.setExtensions([{
+      name: 'basicConstraints',
+      cA: true
+   }, {
+      name: 'keyUsage',
+      keyCertSign: true,
+      digitalSignature: true,
+      nonRepudiation: true,
+      keyEncipherment: true,
+      dataEncipherment: true
+   }, {
+      name: 'subjectAltName',
+      altNames: [{
+         type: 6, // URI
+         value: 'http://myuri.com/webid#me'
+      }]
+   }]);
+   // FIXME: add subjectKeyIdentifier extension
+   // FIXME: add authorityKeyIdentifier extension
+   cert.publicKey = keys.publicKey;
+   
+   // self-sign certificate
+   cert.sign(keys.privateKey);
+   
+   // save data
+   data[cn] = {
+      cert: forge.pki.certificateToPem(cert),
+      privateKey: forge.pki.privateKeyToPem(keys.privateKey)
+   };
+   
+   sys.puts('certificate created for \"' + cn + '\": \n' + data[cn].cert);
+};
+
+var data = {};
+
+// create certificate for server
+createCert('server', data);
+
+// function to create TLS server connection
+var createTls = function(websocket)
+{
+   return forge.tls.createConnection(
+   {
+      server: true,
+      caStore: [],
+      sessionCache: {},
+      // supported cipher suites in order of preference
+      cipherSuites: [
+         forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+         forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+      connected: function(c)
+      {
+         sys.puts('Server connected');
+      },
+      verifyClient: true,
+      verify: function(c, verified, depth, certs)
+      {
+         sys.puts(
+            'Server verifying certificate w/CN: \"' +
+            certs[0].subject.getField('CN').value +
+            '\", verified: ' + verified + '...');
+         
+         // accept any certificate (could actually do WebID authorization from
+         // here within the protocol)
+         return true;
+      },
+      getCertificate: function(c, hint)
+      {
+         sys.puts('Server getting certificate for \"' + hint[0] + '\"...');
+         return data.server.cert;
+      },
+      getPrivateKey: function(c, cert)
+      {
+         return data.server.privateKey;
+      },
+      tlsDataReady: function(c)
+      {
+         // send base64-encoded TLS data over websocket
+         websocket.write(forge.util.encode64(c.tlsData.getBytes()));
+      },
+      dataReady: function(c)
+      {
+         sys.puts('Server received \"' + c.data.getBytes() + '\"');
+         
+         // send response
+         c.prepare('Hello Client');
+      },
+      closed: function(c)
+      {
+         sys.puts('Server disconnected.');
+         websocket.end();
+      },
+      error: function(c, error)
+      {
+         sys.puts('Server error: ' + error.message);
+      }
+   });
+};
+
+// create websocket server
+var port = 8080;
+ws.createServer(function(websocket)
+{
+   // create TLS server connection
+   var tls = createTls(websocket);
+   
+   websocket.addListener('connect', function(resource)
+   {
+      sys.puts('connected: ' + resource);
+      
+      // close connection after 10 seconds
+      setTimeout(websocket.end, 10 * 1000);
+   });
+   
+   websocket.addListener('data', function(data)
+   {
+      // base64-decode data and process it
+      tls.process(forge.util.decode64(data));
+   });
+   
+   websocket.addListener('close', function()
+   {
+      sys.puts('closed');
+   });
+}).listen(port);
+
+sys.puts('server running on port ' + port);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/performance.html b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/performance.html
new file mode 100644
index 0000000..9acbcc5
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/performance.html
@@ -0,0 +1,550 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+   <head>
+      <title>Forge Performance Tests</title>
+      <link type="text/css" rel="stylesheet" media="all" href="screen.css" />
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
+      <script type="text/javascript" src="forge/debug.js"></script>
+      <script type="text/javascript" src="forge/util.js"></script>
+      <script type="text/javascript" src="forge/log.js"></script>
+      <script type="text/javascript" src="forge/task.js"></script>
+      <script type="text/javascript" src="forge/md5.js"></script>
+      <script type="text/javascript" src="forge/sha1.js"></script>
+      <script type="text/javascript" src="forge/sha256.js"></script>
+      <script type="text/javascript" src="forge/hmac.js"></script>
+      <script type="text/javascript" src="forge/aes.js"></script>
+      <script type="text/javascript" src="forge/pem.js"></script>
+      <script type="text/javascript" src="forge/asn1.js"></script>
+      <script type="text/javascript" src="forge/jsbn.js"></script>
+      <script type="text/javascript" src="forge/prng.js"></script>
+      <script type="text/javascript" src="forge/random.js"></script>
+      <script type="text/javascript" src="forge/oids.js"></script>
+      <script type="text/javascript" src="forge/rsa.js"></script>
+      <script type="text/javascript" src="forge/pbe.js"></script>
+      <script type="text/javascript" src="forge/x509.js"></script>
+      <script type="text/javascript" src="forge/pki.js"></script>
+      <script type="text/javascript" src="forge/tls.js"></script>
+      <script type="text/javascript" src="forge/aesCipherSuites.js"></script>
+
+      <script type="text/javascript">
+      //<![CDATA[
+      // logging category
+      var cat = 'forge.tests.performance';
+
+      var test_random = function()
+      {
+         forge.log.debug(cat, 'painting canvas...');
+         setTimeout(function()
+         {
+	         var canvas = document.getElementById("canvas");
+	         var ctx = canvas.getContext("2d");
+	         var imgData = ctx.createImageData(canvas.width, canvas.height);
+
+	         // generate random bytes
+	         var bytes = forge.random.getBytes(canvas.width * canvas.height * 3);
+	         var n = 0;
+	         for(var x = 0; x < imgData.width; x++)
+	         {
+	            for(var y = 0; y < imgData.height; y++)
+	            {
+	               // index of the pixel in the array
+	               var idx = (x + y * imgData.width) * 4;
+
+	               // set values
+	               imgData.data[idx + 0] = bytes.charCodeAt(n++); // Red
+	               imgData.data[idx + 1] = bytes.charCodeAt(n++); // Green
+	               imgData.data[idx + 2] = bytes.charCodeAt(n++); // Blue
+	               imgData.data[idx + 3] = 255;                   // Alpha
+	            }
+	         }
+
+	         ctx.putImageData(imgData, 0, 0);
+	         forge.log.debug(cat, 'done');
+         }, 0);
+      };
+
+      var canvas_clear = function()
+      {
+         var canvas = document.getElementById("canvas");
+         var ctx = canvas.getContext("2d");
+         ctx.clearRect(0, 0, canvas.width, canvas.height);
+      };
+
+      var test_buffer_fill = function()
+      {
+         forge.log.debug(cat,
+            'buffer fill optimized vs. slow running for about 5 seconds...');
+
+         setTimeout(function()
+         {
+	         // run slow fill for 2.5 seconds
+	         var st, et;
+	         var b = '';
+	         var passed = 0;
+	         while(passed < 2500)
+	         {
+	            st = +new Date();
+	            b += 'b';
+	            et = +new Date();
+	            passed += (et - st);
+	         }
+
+	         // do optimized fill
+	         var buf = forge.util.createBuffer();
+	         st = +new Date();
+	         buf.fillWithByte('b'.charCodeAt(0), b.length);
+	         et = +new Date();
+
+	         forge.log.debug(cat, 'fill times', (et - st) + ' < ' + passed);
+         });
+      };
+
+      var test_buffer_xor = function()
+      {
+         forge.log.debug(cat,
+            'buffer xor optimized vs. slow running for about 5 seconds...');
+
+         setTimeout(function()
+         {
+	         // run slow xor for 2.5 seconds
+	         var st, et;
+	         var output = forge.util.createBuffer();
+	         var passed = 0;
+	         while(passed < 2500)
+	         {
+	            st = +new Date();
+	            output.putByte(0x01 ^ 0x02);
+	            et = +new Date();
+	            passed += (et - st);
+	         }
+
+	         // do optimized xor
+	         var count = output.length();
+	         var b1 = forge.util.createBuffer();
+	         b1.fillWithByte(0x01, count);
+	         var b2 = forge.util.createBuffer();
+	         b2.fillWithByte(0x02, count);
+
+	         st = +new Date();
+	         output = forge.util.xorBytes(b1.getBytes(), b2.getBytes(), count);
+	         et = +new Date();
+
+	         forge.log.debug(cat, 'xor times', (et - st) + ' < ' + passed);
+         });
+      };
+
+      // TODO: follow the same format as the hash tests
+      var test_base64_encode = function()
+      {
+         forge.log.debug(cat, 'base64 encode running for about 5 seconds...');
+
+         setTimeout(function()
+         {
+	         // get starting time to make test run for only 5 seconds
+	         var start = +new Date();
+
+	         // build data to encode
+	         var str = '';
+	         for(var i = 0; i < 100; i++)
+	         {
+	            str += '00010203050607080A0B0C0D0F1011121415161719';
+	         }
+
+	         // keep encoding for 5 seconds, keep track of total and count
+	         var total = 0, count = 0, st, et;
+	         var passed = 0;
+	         while(passed < 5000)
+	         {
+	            st = +new Date();
+	            forge.util.encode64(str);
+	            et = +new Date();
+	            ++count;
+	            total += (et - st);
+	            passed = +new Date() - start;
+	         }
+
+	         total /= 1000;
+	         var kb = 4200/1024;
+	         forge.log.debug(cat,
+	            'encode:', (count*kb/total) + ' KiB/s');
+         });
+      };
+
+      // TODO: follow the same format as the hash tests
+      var test_base64_decode = function()
+      {
+         forge.log.debug(cat, 'base64 decode running for about 5 seconds...');
+
+         setTimeout(function()
+         {
+	         // get starting time to make test run for only 5 seconds
+	         var start = +new Date();
+
+	         // build data to decode
+	         var str = '';
+	         for(var i = 0; i < 100; i++)
+	         {
+	            str +=
+		            'MDAwMTAyMDMwNTA2MDcwODBBMEIwQzBEMEYxMDExMTIxNDE1MTYxNzE5';
+	         }
+
+	         // keep encoding for 5 seconds, keep track of total and count
+	         var total = 0, count = 0, st, et;
+	         var passed = 0;
+	         while(passed < 5000)
+	         {
+	            st = +new Date();
+	            forge.util.decode64(str);
+	            et = +new Date();
+	            ++count;
+	            total += (et - st);
+	            passed = +new Date() - start;
+	         }
+
+	         total /= 1000;
+	         var kb = 5600/1024;
+	         forge.log.debug(cat,
+	            'decode:', (count*kb/total) + ' KiB/s');
+         });
+      };
+
+      var test_md5 = function()
+      {
+         // create input data
+         var input = ['0123456789abcdef', '', '', '', ''];
+         for(var i = 0; i < 4; ++i)
+         {
+            input[1] += input[0];
+         }
+         for(var i = 0; i < 4; ++i)
+         {
+            input[2] += input[1];
+         }
+         for(var i = 0; i < 4; ++i)
+         {
+            input[3] += input[2];
+         }
+         for(var i = 0; i < 8; ++i)
+         {
+            input[4] += input[3];
+         }
+
+         var md = forge.md.md5.create();
+
+         forge.log.debug(cat, 'md5 times in 1000s of bytes/sec processed:');
+
+         var st, et;
+         var output =
+            ['  16 bytes: ',
+             '  64 bytes: ',
+             ' 256 bytes: ',
+             '1024 bytes: ',
+             '8192 bytes: '];
+         var s = [16, 64, 256, 1024, 8192];
+         var t = [0, 0, 0, 0, 0];
+         for(var n = 0; n < 5; ++n)
+         {
+            var f = function(n)
+            {
+               setTimeout(function()
+               {
+                  // run for 2 seconds each
+                  var count = 0;
+                  while(t[n] < 2000)
+                  {
+                     md.start();
+                     st = +new Date();
+                     md.update(input[n]);
+                     md.digest();
+                     et = +new Date();
+                     t[n] = t[n] + (et - st);
+                     ++count;
+                  }
+                  t[n] /= 1000;
+                  forge.log.debug(cat,
+                     output[n], (count*s[n]/t[n]/1000) + 'k/sec');
+               }, 0);
+            }(n);
+         }
+      };
+
+      var test_sha1 = function()
+      {
+         // create input data
+         var input = ['0123456789abcdef', '', '', '', ''];
+         for(var i = 0; i < 4; ++i)
+         {
+            input[1] += input[0];
+         }
+         for(var i = 0; i < 4; ++i)
+         {
+            input[2] += input[1];
+         }
+         for(var i = 0; i < 4; ++i)
+         {
+            input[3] += input[2];
+         }
+         for(var i = 0; i < 8; ++i)
+         {
+            input[4] += input[3];
+         }
+
+         var md = forge.md.sha1.create();
+
+         forge.log.debug(cat, 'sha-1 times in 1000s of bytes/sec processed:');
+
+         var st, et;
+         var output =
+            ['  16 bytes: ',
+             '  64 bytes: ',
+             ' 256 bytes: ',
+             '1024 bytes: ',
+             '8192 bytes: '];
+         var s = [16, 64, 256, 1024, 8192];
+         var t = [0, 0, 0, 0, 0];
+         for(var n = 0; n < 5; ++n)
+         {
+            var f = function(n)
+            {
+               setTimeout(function()
+               {
+                  // run for 2 seconds each
+                  var count = 0;
+                  while(t[n] < 2000)
+                  {
+                     md.start();
+                     st = +new Date();
+                     md.update(input[n]);
+                     md.digest();
+                     et = +new Date();
+                     t[n] = t[n] + (et - st);
+                     ++count;
+                  }
+                  t[n] /= 1000;
+                  forge.log.debug(cat,
+                     output[n], (count*s[n]/t[n]/1000) + 'k/sec');
+               }, 0);
+            }(n);
+         }
+      };
+
+      var test_sha256 = function()
+      {
+         // create input data
+         var input = ['0123456789abcdef', '', '', '', ''];
+         for(var i = 0; i < 4; ++i)
+         {
+            input[1] += input[0];
+         }
+         for(var i = 0; i < 4; ++i)
+         {
+            input[2] += input[1];
+         }
+         for(var i = 0; i < 4; ++i)
+         {
+            input[3] += input[2];
+         }
+         for(var i = 0; i < 8; ++i)
+         {
+            input[4] += input[3];
+         }
+
+         var md = forge.md.sha256.create();
+
+         forge.log.debug(cat, 'sha-256 times in 1000s of bytes/sec processed:');
+
+         var st, et;
+         var output =
+            ['  16 bytes: ',
+             '  64 bytes: ',
+             ' 256 bytes: ',
+             '1024 bytes: ',
+             '8192 bytes: '];
+         var s = [16, 64, 256, 1024, 8192];
+         var t = [0, 0, 0, 0, 0];
+         for(var n = 0; n < 5; ++n)
+         {
+            var f = function(n)
+            {
+               setTimeout(function()
+               {
+                  // run for 2 seconds each
+                  var count = 0;
+                  while(t[n] < 2000)
+                  {
+                     md.start();
+                     st = +new Date();
+                     md.update(input[n]);
+                     md.digest();
+                     et = +new Date();
+                     t[n] = t[n] + (et - st);
+                     ++count;
+                  }
+                  t[n] /= 1000;
+                  forge.log.debug(cat,
+                     output[n], (count*s[n]/t[n]/1000) + 'k/sec');
+               }, 0);
+            }(n);
+         }
+      };
+
+      // TODO: follow the same format as the hash tests
+      var aes_128 = function()
+      {
+         forge.log.debug(cat, 'running AES-128 for 5 seconds...');
+
+         var block = [];
+         block.push(0x00112233);
+         block.push(0x44556677);
+         block.push(0x8899aabb);
+         block.push(0xccddeeff);
+
+         var key = [];
+         key.push(0x00010203);
+         key.push(0x04050607);
+         key.push(0x08090a0b);
+         key.push(0x0c0d0e0f);
+
+         setTimeout(function()
+         {
+            // run for 5 seconds
+            var start = +new Date();
+	         var now;
+	         var totalEncrypt = 0;
+	         var totalDecrypt = 0;
+	         var count = 0;
+            var passed = 0;
+	         while(passed < 5000)
+	         {
+	            var output = [];
+	            var w = forge.aes._expandKey(key, false);
+	            now = +new Date();
+	            forge.aes._updateBlock(w, block, output, false);
+	            totalEncrypt += +new Date() - now;
+
+	            block = output;
+	            output = [];
+	            w = forge.aes._expandKey(key, true);
+	            now = +new Date();
+	            forge.aes._updateBlock(w, block, output, true);
+	            totalDecrypt += +new Date() - now;
+
+               ++count;
+	            passed = +new Date() - start;
+	         }
+
+	         count = count * 16 / 1000;
+	         totalEncrypt /= 1000;
+	         totalDecrypt /= 1000;
+	         forge.log.debug(cat, 'times in 1000s of bytes/sec processed.');
+	         forge.log.debug(cat,
+	   	      'encrypt: ' + (count*16 / totalEncrypt) + ' k/sec');
+	         forge.log.debug(cat,
+	   	      'decrypt: ' + (count*16 / totalDecrypt) + ' k/sec');
+         }, 0);
+      };
+
+      // TODO: follow the same format as the hash tests
+      var aes_128_cbc = function()
+      {
+         forge.log.debug(cat, 'running AES-128 CBC for 5 seconds...');
+
+         var key = forge.random.getBytes(16);
+         var iv = forge.random.getBytes(16);
+         var plain = forge.random.getBytes(16);
+
+         setTimeout(function()
+         {
+            // run for 5 seconds
+            var start = +new Date();
+	         var now;
+	         var totalEncrypt = 0;
+	         var totalDecrypt = 0;
+	         var cipher;
+	         var count = 0;
+	         var passed = 0;
+	         while(passed < 5000)
+	         {
+               var input = forge.util.createBuffer(plain);
+
+               // encrypt, only measuring update() and finish()
+               cipher = forge.aes.startEncrypting(key, iv);
+               now = +new Date();
+               cipher.update(input);
+               cipher.finish();
+               totalEncrypt += +new Date() - now;
+
+               // decrypt, only measuring update() and finish()
+               var ct = cipher.output;
+               cipher = forge.aes.startDecrypting(key, iv);
+               now = +new Date();
+               cipher.update(ct);
+               cipher.finish();
+               totalDecrypt += +new Date() - now;
+
+               ++count;
+               passed = +new Date() - start;
+	         }
+
+	         // 32 bytes encrypted because of 16 bytes of padding
+	         count = count * 32 / 1000;
+            totalEncrypt /= 1000;
+            totalDecrypt /= 1000;
+            forge.log.debug(cat, 'times in 1000s of bytes/sec processed.');
+            forge.log.debug(cat,
+               'encrypt: ' + (count / totalEncrypt) + ' k/sec');
+            forge.log.debug(cat,
+               'decrypt: ' + (count / totalDecrypt) + ' k/sec');
+         }, 0);
+      };
+
+      //]]>
+      </script>
+   </head>
+   <body>
+      <div class="nav"><a href="index.html">Forge Tests</a> / Performance</div>
+
+      <div class="header">
+         <h1>Performance Tests</h1>
+      </div>
+
+      <div class="content">
+
+         <fieldset class="section">
+            <ul>
+               <li>Use the controls below to run Forge performance tests.</li>
+               <li>You currently need a JavaScript console to view the output.</li>
+            </ul>
+         </fieldset>
+
+         <fieldset class="section">
+            <legend>Tests</legend>
+
+         <div id="random_controls">
+            <button id="random" onclick="javascript:return test_random();">paint random</button>
+            <button id="clear" onclick="javascript:return canvas_clear();">clear</button>
+         </div>
+         <canvas id="canvas" width="100" height="100"></canvas>
+         <div id="buffer_controls">
+            <button id="buffer_fill" onclick="javascript:return test_buffer_fill();">buffer fill</button>
+            <button id="buffer_xor" onclick="javascript:return test_buffer_xor();">buffer xor</button>
+         </div>
+         <div id="base64_controls">
+            <button id="base64_encode" onclick="javascript:return test_base64_encode();">base64 encode</button>
+            <button id="base64_decode" onclick="javascript:return test_base64_decode();">base64 decode</button>
+         </div>
+         <div id="hash_controls">
+            <button id="md5" onclick="javascript:return test_md5();">md5</button>
+            <button id="sha1" onclick="javascript:return test_sha1();">sha1</button>
+            <button id="sha256" onclick="javascript:return test_sha256();">sha256</button>
+         </div>
+         <div id="aes_controls">
+            <button id="aes_128" onclick="javascript:return aes_128();">aes-128</button>
+            <button id="aes_128_cbc" onclick="javascript:return aes_128_cbc();">aes-128 cbc</button>
+         </div>
+         </fieldset>
+      </div>
+   </body>
+</html>
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/policyserver.py b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/policyserver.py
new file mode 100755
index 0000000..bda8afe
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/policyserver.py
@@ -0,0 +1,112 @@
+#!/usr/bin/env python
+
+"""
+Flash Socket Policy Server.
+
+- Starts Flash socket policy file server.
+- Defaults to port 843.
+- NOTE: Most operating systems require administrative privileges to use
+  ports under 1024.
+
+  $ ./policyserver.py [options]
+"""
+
+"""
+Also consider Adobe's solutions:
+http://www.adobe.com/devnet/flashplayer/articles/socket_policy_files.html
+"""
+
+from multiprocessing import Process
+from optparse import OptionParser
+import SocketServer
+import logging
+
+# Set address reuse for all TCPServers
+SocketServer.TCPServer.allow_reuse_address = True
+
+# Static socket policy file string.
+# NOTE: This format is very strict. Edit with care.
+socket_policy_file = """\
+<?xml version="1.0"?>\
+<!DOCTYPE cross-domain-policy\
+ SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">\
+<cross-domain-policy>\
+<allow-access-from domain="*" to-ports="*"/>\
+</cross-domain-policy>\0"""
+
+
+class PolicyHandler(SocketServer.BaseRequestHandler):
+    """
+    The RequestHandler class for our server.
+
+    Returns a policy file when requested.
+    """
+
+    def handle(self):
+        """Send policy string if proper request string is received."""
+        # get some data
+        # TODO: make this more robust (while loop, etc)
+        self.data = self.request.recv(1024).rstrip('\0')
+        logging.debug("%s wrote:%s" % (self.client_address[0], repr(self.data)))
+        # if policy file request, send the file.
+        if self.data == "<policy-file-request/>":
+            logging.info("Policy server request from %s." % (self.client_address[0]))
+            self.request.send(socket_policy_file)
+        else:
+            logging.info("Policy server received junk from %s: \"%s\"" % \
+                    (self.client_address[0], repr(self.data)))
+
+
+class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
+    def serve_forever(self):
+        """Handle one request at a time until shutdown or keyboard interrupt."""
+        try:
+           SocketServer.BaseServer.serve_forever(self)
+        except KeyboardInterrupt:
+           return
+
+
+def main():
+    """Run socket policy file servers."""
+    usage = "Usage: %prog [options]"
+    parser = OptionParser(usage=usage)
+    parser.add_option("", "--host", dest="host", metavar="HOST",
+            default="localhost", help="bind to HOST")
+    parser.add_option("-p", "--port", dest="port", metavar="PORT",
+            default=843, type="int", help="serve on PORT")
+    parser.add_option("-d", "--debug", dest="debug", action="store_true",
+            default=False, help="debugging output")
+    parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
+            default=False, help="verbose output")
+    (options, args) = parser.parse_args()
+
+    # setup logging
+    if options.debug:
+        lvl = logging.DEBUG
+    elif options.verbose:
+        lvl = logging.INFO
+    else:
+        lvl = logging.WARNING
+    logging.basicConfig(level=lvl, format="%(levelname)-8s %(message)s")
+
+    # log basic info
+    logging.info("Flash Socket Policy Server. Use ctrl-c to exit.")
+    
+    # create policy server
+    logging.info("Socket policy serving on %s:%d." % (options.host, options.port))
+    policyd = ThreadedTCPServer((options.host, options.port), PolicyHandler)
+
+    # start server
+    policy_p = Process(target=policyd.serve_forever)
+    policy_p.start()
+
+    while policy_p.is_alive():
+        try:
+            policy_p.join(1)
+        except KeyboardInterrupt:
+            logging.info("Stopping test server...")
+
+
+if __name__ == "__main__":
+    main()
+
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/result.txt b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/result.txt
new file mode 100644
index 0000000..7cb007c
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/result.txt
@@ -0,0 +1 @@
+expected result
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/screen.css b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/screen.css
new file mode 100644
index 0000000..365a39f
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/screen.css
@@ -0,0 +1,61 @@
+/* CSS for Forge tests */
+body {
+   background: white;
+   color: black;
+   margin: 0;
+   padding: 0;
+}
+.warning {
+   border: thin solid red;
+   background: #7FF;
+}
+div.nav {
+   background: white;
+   border-bottom: thin solid black;
+   padding: .5em;
+   padding-top: .2em;
+   padding-bottom: .2em;
+}
+div.header {
+   border-bottom: thin solid black;
+   padding: .5em;
+}
+div.content {
+   padding: .5em;
+   background: #DDD;
+}
+div.footer {
+   border-top: thin solid black;
+   font-size: 80%;
+   padding: .5em;
+}
+canvas {
+   background: black;
+}
+table, td, th {
+   border: thin solid black;
+   border-collapse: collapse;
+}
+td, th {
+   padding: .2em;
+}
+span.sp-state-on {
+   font-weight: bold;
+}
+table#feedback {
+   width: 100%;
+}
+table#feedback th, table#feedback td {
+   width: 33%;
+}
+fieldset.section {
+   margin: .5em;
+   border: 2px solid black;
+   background: #FFF;
+}
+fieldset.section legend {
+   padding: .2em;
+   border: 2px solid black;
+   background: #DDF;
+   font-weight: bold;
+}
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/server.crt b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/server.crt
new file mode 100644
index 0000000..6952426
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/server.crt
@@ -0,0 +1,26 @@
+-----BEGIN CERTIFICATE-----
+MIIEaDCCA1CgAwIBAgIJAJuj0AjEWncuMA0GCSqGSIb3DQEBBQUAMH8xCzAJBgNV
+BAYTAlVTMREwDwYDVQQIEwhWaXJnaW5pYTETMBEGA1UEBxMKQmxhY2tzYnVyZzEd
+MBsGA1UEChMURGlnaXRhbCBCYXphYXIsIEluYy4xGjAYBgNVBAsTEUZvcmdlIFRl
+c3QgU2VydmVyMQ0wCwYDVQQDEwR0ZXN0MB4XDTEwMDcxMzE3MjAzN1oXDTMwMDcw
+ODE3MjAzN1owfzELMAkGA1UEBhMCVVMxETAPBgNVBAgTCFZpcmdpbmlhMRMwEQYD
+VQQHEwpCbGFja3NidXJnMR0wGwYDVQQKExREaWdpdGFsIEJhemFhciwgSW5jLjEa
+MBgGA1UECxMRRm9yZ2UgVGVzdCBTZXJ2ZXIxDTALBgNVBAMTBHRlc3QwggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCm/FobjqK8CVP/Xbnpyhf1tpoyaiFf
+ShUOmlWqL5rLe0Q0dDR/Zur+sLMUv/1T4wOfFkjjxvZ0Sk5NIjK3Wy2UA41a+M3J
+RTbCFrg4ujsovFaD4CDmV7Rek0qJB3m5Gp7hgu5vfL/v+WrwxnQObNq+IrTMSA15
+cO4LzNIPj9K1LN2dB+ucT7xTQFHAfvLLgLlCLiberoabF4rEhgTMTbmMtFVKSt+P
+xgQIYPnhw1WuAvE9hFesRQFdfARLqIZk92FeHkgtHv9BAunktJemcidbowTCTBaM
+/njcgi1Tei/LFkph/FCVyGER0pekJNHX626bAQSLo/srsWfmcll9rK6bAgMBAAGj
+geYwgeMwHQYDVR0OBBYEFCau5k6jxezjULlLuo/liswJlBF8MIGzBgNVHSMEgasw
+gaiAFCau5k6jxezjULlLuo/liswJlBF8oYGEpIGBMH8xCzAJBgNVBAYTAlVTMREw
+DwYDVQQIEwhWaXJnaW5pYTETMBEGA1UEBxMKQmxhY2tzYnVyZzEdMBsGA1UEChMU
+RGlnaXRhbCBCYXphYXIsIEluYy4xGjAYBgNVBAsTEUZvcmdlIFRlc3QgU2VydmVy
+MQ0wCwYDVQQDEwR0ZXN0ggkAm6PQCMRady4wDAYDVR0TBAUwAwEB/zANBgkqhkiG
+9w0BAQUFAAOCAQEAnP/2mzFWaoGx6+KAfY8pcgnF48IoyKPx5cAQyzpMo+uRwrln
+INcDGwNx6p6rkjFbK27TME9ReCk+xQuVGaKOtqErKECXWDtD+0M35noyaOwWIFu2
+7gPZ0uGJ1n9ZMe/S9yZmmusaIrc66rX4o+fslUlH0g3SrH7yf83M8aOC2pEyCsG0
+mNNfwSFWfmu+1GMRHXJQ/qT8qBX8ZPhzRY2BAS6vr+eh3gwXR6yXLA8Xm1+e+iDU
+gGTQoYkixDIL2nhvd4AFFlE977BiE+0sMS1eJKUUbQ36MLAWb5oOZKHrphEvqMKA
+eGDO3qoDqB5TkZC3x38DXBDvAZ01d9s0fvveag==
+-----END CERTIFICATE-----
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/server.key b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/server.key
new file mode 100644
index 0000000..4024097
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/server.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEApvxaG46ivAlT/1256coX9baaMmohX0oVDppVqi+ay3tENHQ0
+f2bq/rCzFL/9U+MDnxZI48b2dEpOTSIyt1stlAONWvjNyUU2wha4OLo7KLxWg+Ag
+5le0XpNKiQd5uRqe4YLub3y/7/lq8MZ0DmzaviK0zEgNeXDuC8zSD4/StSzdnQfr
+nE+8U0BRwH7yy4C5Qi4m3q6GmxeKxIYEzE25jLRVSkrfj8YECGD54cNVrgLxPYRX
+rEUBXXwES6iGZPdhXh5ILR7/QQLp5LSXpnInW6MEwkwWjP543IItU3ovyxZKYfxQ
+lchhEdKXpCTR1+tumwEEi6P7K7Fn5nJZfayumwIDAQABAoIBAFGPbEuNbXq+a6KN
+GuNP7Ef9em8pW0d5nbNWOoU3XzoH6RZds86ObDUeBTobVBaHCRvI/K0UXwgJyxjt
+nSvlguuKmJ5Ya9rkzYwbILvEamTJKNCcxjT7nYOcGYm4dwGsOPIYy3D006LYhh04
+MTNig6zessQcZUhtmjd1QRyMuPP4PaWVO79ic01jxZR/ip6tN/FjCYclPRi/FRi8
+bQVuGEVLW2qHgQbDKPpcXFyFjIqt7c9dL97/3eeIDp+SgdQ6bPi80J7v9p2MRyBP
+7OPhX8ZDsAiZr4G4N0EbEzmWWpVSjAI3Nlmk8SLT4lu42KKyoZLtuKPjEOVI3/TR
+0ktsc/ECgYEA27AHLnsv07Yqe7Z2bmv+GP8PKlwrPSHwqU/3Z5/1V590N+jo15N4
+lb7gvBUwwvXIxQQQVYJqRimqNQYVfT6+xRtQdtdqInxv2hvhc/cKPEiIHNpRh7OI
+w7I59yNMlCnqLeRBiCOmd7ruCWoMGw+VLhsyArwUTXuqUK2oYN9qWm8CgYEAwpZF
+XNm8xCFa+YeqP+WQzwK/0yUxHmYZs7ofh9ZIgHtqwHNKO/OA8+nGsZBaIl5xiyT4
+uZ/qZi2EkYzOmx0iSathiWQpSyc9kB+nOTdMHyhBOj8CgbTRRXIMjDQ6bz78Z09F
+Nxenhwk2gSVr3oB2FG/BWc1rlmVlmGJIIX3QtJUCgYBfLhLOdpywExqw4srI6Iz8
+c3U0mx44rD3CfVzpTopTXkhR+Nz4mXIDHuHrWxr3PNmxUiNpiMlWgLK3ql0hGFA6
+wazI8GeRbWxgiPfS8FNE7v/Z0FTGgGhesRcgFfEVuFs3as9hlmCHOzvqZEG+b6/o
+e+vc93OsZknSDosG/YTsjQKBgHrb7HGinLftI4a3rLvpU1QRNVK4gdnit0muM6hN
+mLtesVlPschGh935ddW5Ad//Z4tmTZDOMm5PQQuxLuXrMDH5fn0D+7qSzSEJi0jp
+7Csj/IMtM4T3yMYjK17+vwJsb2s/NsGBMupk28ARA5mZ3HQs15S+ybZM0Se0rjxP
+Nw49AoGBAKrLTOtZta0DSGt7tURwQK1mERuGM8ZZdXjvIVTJIIknohD2u3/T+O4+
+ekFTUd6GQKOFd/hZ52t4wcRs7c7KE1Xen7vRHc8c6c3TkF9ldpLmK2AWT8WifQO6
+9Fjx2Wf8HM+CbrokQYH/OHSV9Xft8BRTOPHGUJlp1UsYikSwp4fW
+-----END RSA PRIVATE KEY-----
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/server.py b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/server.py
new file mode 100755
index 0000000..b5a5f06
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/server.py
@@ -0,0 +1,184 @@
+#!/usr/bin/env python
+
+"""
+SSL server for Forge tests.
+
+- The server changes to the directory of the server script.
+- SSL uses "server.key" and "server.crt".
+- Sever performs basic static file serving.
+- Starts Flash cross domain policy file server.
+- Defaults to HTTP/HTTPS port 19400.
+- Defaults to Flash socket policy port 19945.
+
+  $ ./server.py [options]
+
+If you just need a simple HTTP server, also consider:
+  $ python -m SimpleHTTPServer 19400
+"""
+
+from multiprocessing import Process
+from optparse import OptionParser
+import SimpleHTTPServer
+import SocketServer
+import os
+import sys
+import time
+
+# Try to import special Forge SSL module with session cache support
+# Using the built directory directly
+python_version = "python" + sys.version[:3]
+sys.path.insert(0, os.path.join(
+    os.path.dirname(os.path.realpath(__file__)),
+    "..", "dist", "forge_ssl", "lib", python_version, "site-packages"))
+try:
+    from forge import ssl
+    have_ssl_sessions = True
+    have_ssl = True
+except ImportError:
+    have_ssl_sessions = False
+    try:
+        import ssl
+        have_ssl = True
+    except ImportError:
+        have_ssl = False
+
+# Set address reuse for all TCPServers
+SocketServer.TCPServer.allow_reuse_address = True
+
+# The policy file
+# NOTE: This format is very strict. Edit with care.
+policy_file = """\
+<?xml version="1.0"?>\
+<!DOCTYPE cross-domain-policy\
+ SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">\
+<cross-domain-policy>\
+<allow-access-from domain="*" to-ports="*"/>\
+</cross-domain-policy>\0"""
+
+
+class PolicyHandler(SocketServer.BaseRequestHandler):
+    """
+    The RequestHandler class for our server.
+
+    Returns a policy file when requested.
+    """
+
+    def handle(self):
+        # get some data
+        # TODO: make this more robust (while loop, etc)
+        self.data = self.request.recv(1024).rstrip('\0')
+        #print "%s wrote:" % self.client_address[0]
+        #print repr(self.data)
+        # if policy file request, send the file.
+        if self.data == "<policy-file-request/>":
+            print "Policy server request from %s." % (self.client_address[0])
+            self.request.send(policy_file)
+        else:
+            print "Policy server received junk from %s: \"%s\"" % \
+                    (self.client_address[0], repr(self.data))
+
+
+def create_policy_server(options):
+    """Start a policy server"""
+    print "Policy serving from %d." % (options.policy_port)
+    policyd = SocketServer.TCPServer((options.host, options.policy_port), PolicyHandler)
+    return policyd
+
+
+class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
+    pass
+
+
+def create_http_server(options, script_dir):
+    """Start a static file server"""
+    # use UTF-8 encoding for javascript files
+    m = SimpleHTTPServer.SimpleHTTPRequestHandler.extensions_map
+    m['.js'] = 'application/javascript;charset=UTF-8'
+
+    Handler = SimpleHTTPServer.SimpleHTTPRequestHandler
+#    httpd = SocketServer.TCPServer((options.host, options.port), Handler)
+    httpd = ThreadedTCPServer((options.host, options.port), Handler)
+    if options.tls:
+        if not have_ssl:
+            raise Exception("SSL support from Python 2.7 or later is required.")
+
+        # setup session args if we session support
+        sess_args = {}
+        if have_ssl_sessions:
+            sess_args["sess_id_ctx"] = "forgetest"
+        else:
+            print "Forge SSL with session cache not available, using standard version."
+
+        httpd.socket = ssl.wrap_socket(
+            httpd.socket,
+            keyfile="server.key",
+            certfile="server.crt",
+            server_side=True,
+            **sess_args)
+
+    print "Serving from \"%s\"." % (script_dir)
+    print "%s://%s:%d/" % \
+            (("https" if options.tls else "http"),
+            httpd.server_address[0],
+            options.port)
+    return httpd
+
+
+def serve_forever(server):
+    """Serve until shutdown or keyboard interrupt."""
+    try:
+       server.serve_forever()
+    except KeyboardInterrupt:
+       return
+
+
+def main():
+    """Start static file and policy servers"""
+    usage = "Usage: %prog [options]"
+    parser = OptionParser(usage=usage)
+    parser.add_option("", "--host", dest="host", metavar="HOST",
+            default="localhost", help="bind to HOST")
+    parser.add_option("-p", "--port", dest="port", type="int",
+            help="serve on PORT", metavar="PORT", default=19400)
+    parser.add_option("-P", "--policy-port", dest="policy_port", type="int",
+            help="serve policy file on PORT", metavar="PORT", default=19945)
+    parser.add_option("", "--tls", dest="tls", action="store_true",
+            help="serve HTTPS", default=False)
+    (options, args) = parser.parse_args()
+
+    # Change to script dir so SSL and test files are in current dir.
+    script_dir = os.path.dirname(os.path.realpath(__file__))
+    os.chdir(script_dir)
+
+    print "Forge Test Server. Use ctrl-c to exit."
+    
+    # create policy and http servers
+    httpd = create_http_server(options, script_dir)
+    policyd = create_policy_server(options)
+
+    # start servers
+    server_p = Process(target=serve_forever, args=(httpd,))
+    policy_p = Process(target=serve_forever, args=(policyd,))
+    server_p.start()
+    policy_p.start()
+
+    processes = [server_p, policy_p]
+
+    while len(processes) > 0:
+        try:
+            for p in processes:
+               if p.is_alive():
+                  p.join(1)
+               else:
+                  processes.remove(p)
+        except KeyboardInterrupt:
+            print "\nStopping test server..."
+            # processes each receive interrupt
+            # so no need to shutdown
+            #httpd.shutdown();
+            #policyd.shutdown();
+
+
+if __name__ == "__main__":
+    main()
+
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/socketPool.html b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/socketPool.html
new file mode 100644
index 0000000..33a095f
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/socketPool.html
@@ -0,0 +1,299 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+   <head>
+      <link type="text/css" rel="stylesheet" media="all" href="screen.css" />
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js"></script>
+      <script type="text/javascript" src="forge/util.js"></script>
+      <script type="text/javascript" src="forge/socket.js"></script>
+      <script type="text/javascript" src="forge/log.js"></script>
+      
+      <script type="text/javascript">
+      //<![CDATA[
+      // logging category
+      var cat = 'forge.tests.socketPool';
+
+      // feedback types
+      var ERROR = '';
+      var USER = 'user';
+      var SOCKETPOOL = 'socketpool'
+      var SOCKET = 'socket'
+
+      function addFeedback(type, text)
+      {
+          var row = $('<tr/>')
+             .append($('<td/>').html('&nbsp;'))
+             .append($('<td/>').html('&nbsp;'))
+             .append($('<td/>').html('&nbsp;'));
+          switch(type)
+          {
+             case USER:
+                row.children().eq(0).html(text);
+                break;
+             case SOCKETPOOL:
+                row.children().eq(1).html(text);
+                break;
+             case SOCKET:
+                row.children().eq(2).html(text);
+                break;
+             default:
+                var msg = 'ERROR: bad feedback type:' + type;
+                row.children().eq(1).html(msg);
+                forge.log.error(cat, msg);
+          }
+          $('#feedback').append(row);
+          forge.log.debug(cat, '[' + type + ']', text);
+      };
+
+      function _setState(stateSel)
+      {
+         $('.sp-control').filter(stateSel).removeAttr('disabled');
+         $('.sp-control').filter(':not(' + stateSel + ')').attr('disabled', 'disabled');
+         $('.sp-state').filter(stateSel).addClass('sp-state-on');
+         $('.sp-state').filter(':not(' + stateSel + ')').removeClass('sp-state-on');
+      };
+
+      function setState(state)
+      {
+         switch(state)
+         {
+            case 'ready':
+               _setState('.sp-ready');
+               break;
+            case 'initialized':
+               _setState('.sp-ready,.sp-initialized');
+               break;
+            case 'created':
+               _setState('.sp-ready,.sp-initialized,.sp-created');
+               break;
+            case 'connected':
+               _setState('.sp-ready,.sp-initialized,.sp-created,.sp-connected');
+               break;
+            default:
+               addFeedback(ERROR, 'ERROR: bad state: ' + state);
+         };
+      };
+
+      window.forge.socketPool =
+      {
+         ready: function()
+         {
+            $(document).ready(function() {
+               addFeedback(SOCKETPOOL, 'Ready');
+               setState('ready');
+            });
+         }
+      };
+      
+      swfobject.embedSWF(
+         "forge/SocketPool.swf", "socketPool", "0", "0", "9.0.0",
+         false, {}, {allowscriptaccess: 'always'}, {});
+      
+      // local alias
+      var net = window.forge.net;
+
+      // socket to use
+      var socket;
+
+      $(document).ready(function() {
+         addFeedback(USER, 'Ready');
+         $('#host').val(window.location.hostname);
+         $('#port').val(window.location.port);
+      });
+
+      function sp_init()
+      {
+         net.createSocketPool({
+            flashId: 'socketPool',
+            policyPort: parseInt($('#policyPort').val()),
+            msie: false
+         });
+         addFeedback(SOCKETPOOL, 'Initialized');
+         setState('initialized');
+         return false;
+      };
+      
+      function sp_cleanup()
+      {
+         net.destroySocketPool({flashId: 'socketPool'});
+         addFeedback(SOCKETPOOL, 'Cleaned up');
+         setState('ready');
+         return false;
+      };
+
+      function sp_create()
+      {
+         socket = net.createSocket({
+            flashId: 'socketPool',
+            connected: function(e)
+            {
+               forge.log.debug(cat, 'connected', e);
+               addFeedback(SOCKET, 'Connected');
+            },
+            closed: function(e)
+            {
+               forge.log.debug(cat, 'closed', e);
+               addFeedback(SOCKET, 'Closed. Type: ' + e.type);
+               setState('created');
+            },
+            data: function(e)
+            {
+               forge.log.debug(cat, 'data received', e);
+               forge.log.debug(cat, 'bytes available', socket.bytesAvailable());
+               addFeedback(SOCKET,
+                  'Data available: ' +
+                  socket.bytesAvailable() +' bytes');
+               var bytes = socket.receive(e.bytesAvailable);
+               forge.log.debug(cat, 'bytes received', bytes);
+            },
+            error: function(e)
+            {
+               forge.log.error(cat, 'error', e);
+               addFeedback(SOCKET, 'Error: ' + e);
+            }
+         });
+         addFeedback(SOCKETPOOL, 'Created socket');
+         setState('created');
+         return false;
+      };
+      
+      function sp_destroy()
+      {
+         socket.destroy();
+         addFeedback(USER, 'Request socket destroy');
+         setState('initialized');
+         return false;
+      };
+
+      function sp_connect()
+      {
+         socket.connect({
+            host: $('#host').val(),
+            port: parseInt($('#port').val()),
+            policyPort: parseInt($('#policyPort').val())
+         });
+         addFeedback(USER, 'Request socket connect');
+         setState('connected');
+      };
+
+      function sp_isConnected()
+      {
+         var connected = socket.isConnected();
+         addFeedback(USER, 'Socket connected check: ' + connected);
+      };
+
+      function sp_send()
+      {
+         socket.send('GET ' + $('#path').val() + ' HTTP/1.0\r\n\r\n');
+         addFeedback(USER, 'Send GET request');
+      };
+
+      function sp_close()
+      {
+         socket.close();
+         addFeedback(USER, 'Requst socket close');
+         setState('created');
+      };
+      //]]>
+      </script>
+   </head>
+   <body>
+      <div class="nav"><a href="index.html">Forge Tests</a> / SocketPool</div>
+
+      <div class="header">
+         <h1>SocketPool Test</h1>
+      </div>
+
+      <div class="content">
+         <!-- div used to hold the flash socket pool implemenation -->
+         <div id="socketPool">
+            <p>Could not load the flash SocketPool.</p>
+         </div>
+   
+         <fieldset class="section">
+            <ul>
+               <li>This page tests a single socket connection to the local test server.</li>
+               <li>Note that the selected server must serve a Flash cross-domain policy file on the selected policy port.</li>
+               <li>Additional output available in the JavaScript console.</li>
+            </ul>
+         </fieldset>
+   
+         <fieldset class="section">
+            <legend>State</legend>
+            <p>State:
+               <span class="sp-state sp-ready">Ready</span> &raquo;
+               <span class="sp-state sp-initialized">Initialized</span> &raquo;
+               <span class="sp-state sp-created">Created</span> &raquo;
+               <span class="sp-state sp-connected">Connected</span>
+            </p>
+         </fieldset>
+   
+         <fieldset class="section">
+            <legend>Controls</legend>
+            <div id="controls">
+               <table>
+                  <tr><th>Action</th><th>Description</th></tr>
+                  <tr>
+                     <td><button id="init" disabled="disabled" class="sp-control sp-ready"
+                        onclick="javascript:return sp_init();">init</button></td>
+                     <td>Initialize SocketPool system.</td>
+                  </tr>
+                  <tr>
+                     <td><button id="cleanup" disabled="disabled" class="sp-control sp-initialized"
+                        onclick="javascript:return sp_cleanup();">cleanup</button></td>
+                     <td>Cleanup SocketPool system.</td>
+                  </tr>
+                  <tr>
+                     <td><button id="create" disabled="disabled" class="sp-control sp-initialized"
+                        onclick="javascript:return sp_create();">create socket</button></td>
+                     <td>Create a new test socket.</td>
+                  </tr>
+                  <tr>
+                     <td><button id="destroy" disabled="disabled" class="sp-control sp-created"
+                        onclick="javascript:return sp_destroy();">destroy socket</button></td>
+                     <td>Destroy the test socket.</td>
+                  </tr>
+                  <tr>
+                     <td><button id="connect" disabled="disabled" class="sp-control sp-created"
+                        onclick="javascript:return sp_connect();">connect</button></td>
+                     <td>Connect the socket to
+                        host: <input id="host"/>
+                        port: <input id="port"/>
+                        policy port: <input id="policyPort" value="19945"/>
+                     </td>
+                  </tr>
+                  <tr>
+                     <td><button id="isConnected" disabled="disabled" class="sp-control sp-created"
+                        onclick="javascript:return sp_isConnected();">is connected</button></td>
+                     <td>Check if socket is connected.</td>
+                  </tr>
+                  <tr>
+                     <td><button id="send" disabled="disabled" class="sp-control sp-connected"
+                        onclick="javascript:return sp_send();">send</button></td>
+                     <td>Send a GET request for
+                        path: <input id="path" value="/"/>
+                     </td>
+                  </tr>
+                  <tr>
+                     <td><button id="close" disabled="disabled" class="sp-control sp-connected"
+                        onclick="javascript:return sp_close();">close</button></td>
+                     <td>Close the test socket.</td>
+                  </tr>
+               </table>
+            </div>
+         </fieldset>
+   
+         <fieldset class="section">
+            <legend>Feedback</legend>
+            <table id="feedback">
+               <tr>
+                  <th>User</th>
+                  <th>SocketPool</th>
+                  <th>Socket</th>
+               </tr>
+            </table>
+         </fieldset>
+      </div>
+   </body>
+</html>
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/tasks.html b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/tasks.html
new file mode 100644
index 0000000..eba0173
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/tasks.html
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+   <head>
+      <title>Forge Tasks Test</title>
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
+      <script type="text/javascript" src="forge/debug.js"></script>
+      <script type="text/javascript" src="forge/util.js"></script>
+      <script type="text/javascript" src="forge/log.js"></script>
+      <script type="text/javascript" src="forge/task.js"></script>
+      <script type="text/javascript" src="tasks.js"></script>
+      <link type="text/css" rel="stylesheet" media="all" href="screen.css" />
+      <style type="text/css">
+         .ready { color: inherit; background: inherit; }
+         .testing { color: black; background: yellow; }
+         .pass{ color: white; background: green; }
+         .fail{ color: white; background: red; }
+      </style>
+   </head>
+<body>
+<div class="nav"><a href="index.html">Forge Tests</a> / Tasks</div>
+
+<div class="header">
+   <h1>Task Tests</h1>
+</div>
+
+<div class="content">
+
+<fieldset class="section">
+<legend>Control</legend>
+   <button id="start">Start</button>
+   <button id="reset">Reset</button>
+   <br/>
+   <input id="scroll" type="checkbox" checked="checked" />Scroll Tests
+</fieldset>
+
+<fieldset class="section">
+<legend>Progress</legend>
+Status: <span id="status">?</span><br/>
+Pass: <span id="pass">?</span>/<span id="total">?</span><br/>
+Fail: <span id="fail">?</span>
+</fieldset>
+
+<fieldset class="section">
+<legend>Tests</legend>
+<div id="tests"></div>
+</fieldset>
+
+</div>
+
+</body>
+</html>
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/tasks.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/tasks.js
new file mode 100644
index 0000000..dd3ffde
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/tasks.js
@@ -0,0 +1,378 @@
+/**
+ * Forge Tasks Test
+ *
+ * @author David I. Lehn <dlehn@digitalbazaar.com>
+ *
+ * Copyright (c) 2009-2010 Digital Bazaar, Inc. All rights reserved.
+ */
+jQuery(function($)
+{
+   var cat = 'forge.tests.tasks';
+
+   var tests = [];
+   var passed = 0;
+   var failed = 0;
+   
+   var init = function() {
+      passed = failed = 0;
+      $('.ready,.testing,.pass,.fail')
+         .removeClass('ready testing pass fail');
+      $('#status')
+         .text('Ready.')
+         .addClass('ready');
+      $('#total').text(tests.length);
+      $('#pass').text(passed);
+      $('#fail').text(failed);
+      $('.expect').empty();
+      $('.result').empty();
+      $('.time').empty();
+      $('#start').removeAttr('disabled');
+   };
+
+   var start = function()
+   {
+      $('#start').attr('disabled', 'disabled');
+      // meta! use tasks to run the task tests
+      forge.task.start({
+         type: 'test',
+         run: function(task) {
+            task.next('starting', function(task) {
+               forge.log.debug(cat, 'start');
+               $('#status')
+                  .text('Testing...')
+                  .addClass('testing')
+                  .removeClass('idle');
+            });
+            $.each(tests, function(i, test) {
+               task.next('test', function(task) {
+                  var title = $('li:first', test.container);
+                  if($('#scroll:checked').length === 1)
+                  {
+                     $('html,body').animate({scrollTop: title.offset().top});
+                  }
+                  title.addClass('testing');
+                  test.run(task, test);
+               });
+               task.next('test', function(task) {
+                  $('li:first', test.container).removeClass('testing');
+               });
+            });
+            task.next('success', function(task) {
+               forge.log.debug(cat, 'done');
+               if(failed === 0) {
+                  $('#status')
+                     .text('PASS')
+                     .addClass('pass')
+                     .removeClass('testing');
+               } else {
+                  // FIXME: should just be hitting failure() below
+                  $('#status')
+                     .text('FAIL')
+                     .addClass('fail')
+                     .removeClass('testing');
+               }
+            });
+         },
+         failure: function() {
+            $('#status')
+               .text('FAIL')
+               .addClass('fail')
+               .removeClass('testing');
+         }
+      });
+   };
+
+   $('#start').click(function() {
+      start();
+   });
+   
+   $('#reset').click(function() {
+      init();
+   });
+   
+   var addTest = function(name, run)
+   {
+      var container = $('<ul><li>Test ' + name + '</li><ul/></ul>');
+      var expect = $('<li>Expect: <span class="expect"/></li>');
+      var result = $('<li>Result: <span class="result"/></li>');
+      var time = $('<li>Time: <span class="time"/></li>');
+      $('ul', container).append(expect).append(result).append(time);
+      $('#tests').append(container);
+      var test = {
+         container: container,
+         startTime: null,
+         run: function(task, test) {
+            test.startTime = new Date();
+            run(task, test);
+         },
+         expect: $('span', expect),
+         result: $('span', result),
+         check: function() {
+            var e = test.expect.text();
+            var r = test.result.text();
+            (e == r) ? test.pass() : test.fail();
+         },
+         pass: function() {
+            passed += 1;
+            $('#pass').text(passed);
+            $('li:first', container).addClass('pass');
+            var dt = new Date() - test.startTime;
+            $('span.time', container).html(dt);
+         },
+         fail: function() {
+            failed += 1;
+            $('#fail').text(failed);
+            $('li:first', container).addClass('fail');
+            var dt = new Date() - test.startTime;
+            $('span.time', container).html(dt);
+         }
+      };
+      tests.push(test);
+   };
+
+   addTest('pass', function(task, test) {
+      test.pass();
+   });
+
+   addTest('check', function(task, test) {
+      test.check();
+   });
+
+   addTest('task 1', function(task, test) {
+      task.next(function(task) {
+         test.pass();
+      });
+   });
+
+   addTest('task check()', function(task, test) {
+      test.expect.append('check');
+      task.next(function(task) {
+         test.result.append('check');
+      });
+      task.next(function(task) {
+         test.check();
+      });
+   });
+
+   addTest('serial 20', function(task, test) {
+      // total
+      var n = 20;
+      // counter used in the tasks
+      var taskn = 0;
+      for(var i = 0; i < n; ++i) {
+         test.expect.append(i + ' ');
+         task.next(function(task) {
+            test.result.append(taskn++ + ' ');
+         });
+      }
+      task.next(function(task) {
+         test.check();
+      });
+   });
+
+   addTest('ajax block', function(task, test) {
+      test.expect.append('.');
+      task.next(function(task) {
+         task.parent.block();
+         $.ajax({
+            type: 'GET',
+            url: 'tasks.html',
+            success: function() {
+               test.result.append('.');
+               task.parent.unblock();
+            }
+         });
+      });
+      task.next(function(task) {
+         test.check();
+      });
+   });
+
+   addTest('serial ajax', function(task, test) {
+      var n = 10;
+      for(var i = 0; i < n; ++i)
+      {
+         test.expect.append(i + ' ');
+      }
+      task.next(function(task) {
+         // create parallel functions
+         task.parent.block(n);
+         for(var i = 0; i < n; ++i)
+         {
+            // pass value into closure
+            (function(i)
+            {
+               // serial tasks
+               task.next(function(ajaxTask)
+               {
+                  $.ajax({
+                     type: 'GET',
+                     url: 'tasks.html',
+                     success: function() {
+                        // results use top level task
+                        test.result.append(i + ' ');
+                        task.parent.unblock();
+                     }
+                  });
+               });
+            })(i);
+         }
+      });
+      task.next(function(task) {
+         test.check();
+      });
+   });
+
+   addTest('parallel ajax', function(task, test) {
+      task.next(function(task) {
+         var n = 10;
+         // create parallel functions
+         var tasks = [];
+         for(var i = 0; i < n; ++i)
+         {
+            // pass value into closure
+            (function(i)
+            {
+               tasks.push(function(ajaxTask)
+               {
+                  $.ajax({
+                     type: 'GET',
+                     url: 'tasks.html',
+                     success: function() {
+                        // results use top level task
+                        test.result.append(i + ' ');
+                     }
+                  });
+               });
+            })(i);
+         }
+         // launch in parallel
+         task.parallel(tasks);
+      });
+      task.next(function(task) {
+         test.pass();
+      });
+   });
+
+   addTest('linear empty tasks rate', function(task, test) {
+      test.expect.append('-');
+      // total
+      var n = 100;
+      var start = new Date();
+      for(var i = 0; i < n; ++i) {
+         // empty task
+         task.next(function(task) {});
+      }
+      task.next(function(task) {
+         var dt = (new Date() - start) / 1000;
+         var res = $('<ul/>')
+            .append('<li>Tasks: ' + n + '</li>')
+            .append('<li>Time: ' + dt + 's</li>')
+            .append('<li>Rate: ' + n/dt + ' tasks/s</li>')
+            .append('<li>Task Time: ' + 1000*dt/n + ' ms/tasks</li>');
+         test.result.html(res);
+         test.pass();
+      });
+   });
+
+   addTest('sleep', function(task, test) {
+      test.expect.append('-');
+      var st = 1000;
+      var start = new Date();
+      task.next(function(task) {
+         task.sleep(st);
+      });
+      task.next(function(task) {
+         var dt = new Date() - start;
+         var res = $('<ul/>')
+            .append('<li>Sleep Time : ' + st + 'ms</li>')
+            .append('<li>Real Time: ' + dt + 'ms</li>')
+            .append('<li>Diff: ' + (dt-st) + 'ms</li>');
+         test.result.html(res);
+         test.pass();
+      });
+   });
+   
+   addTest('serial 20 + sleep', function(task, test) {
+      // total
+      var n = 20;
+      // counter used in the tasks
+      var taskn = 0;
+      for(var i = 0; i < n; ++i) {
+         test.expect.append(i + ' ');
+         task.next(function(task) {
+            task.sleep(20);
+            test.result.append(taskn++ + ' ');
+         });
+      }
+      task.next(function(task) {
+         test.check();
+      });
+   });
+   
+   addTest('concurrent tasks', function(task, test)
+   {
+      var colors = [
+         'red',
+         'green',
+         'blue',
+         'black',
+         'purple',
+         'goldenrod',
+         'maroon',
+         'gray',
+         'teal',
+         'magenta'
+      ];
+      var count = colors.length;
+      task.next(function(task)
+      {
+         var main = task;
+         task.block(count);
+         
+         var tasks = [];
+         for(var i = 0; i < count; ++i)
+         {
+            var makefunction = function(index)
+            {
+               return function(task)
+               {
+                  // total
+                  var n = 20;
+                  // counter used in the tasks
+                  var taskn = 0;
+                  for(var j = 0; j < n; j++)
+                  {
+                     task.next(function(task)
+                     {
+                        test.result.append(
+                           '<span style=\"color:' + colors[index] + ';\">' +
+                           taskn++ + '</span> ');
+                     });
+                  }
+                  task.next(function(task)
+                  {
+                     main.unblock();
+                  });
+               };
+            };
+            tasks.push(
+            {
+               type: 'concurrent' + i,
+               run: makefunction(i)
+            });
+         }
+         
+         for(var i = 0; i < count; ++i)
+         {
+            forge.task.start(tasks[i]);
+         }
+      });
+      
+      task.next(function(task) {
+         test.pass();
+      });
+   });
+
+   init();
+});
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/tls.html b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/tls.html
new file mode 100644
index 0000000..92501b8
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/tls.html
@@ -0,0 +1,426 @@
+<html>
+   <head>
+      <link type="text/css" rel="stylesheet" media="all" href="screen.css" />
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js"></script>
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
+      <script type="text/javascript" src="forge/debug.js"></script>
+      <script type="text/javascript" src="forge/util.js"></script>
+      <script type="text/javascript" src="forge/log.js"></script>
+      <script type="text/javascript" src="forge/socket.js"></script>
+      <script type="text/javascript" src="forge/md5.js"></script>
+      <script type="text/javascript" src="forge/sha1.js"></script>
+      <script type="text/javascript" src="forge/hmac.js"></script>
+      <script type="text/javascript" src="forge/aes.js"></script>
+      <script type="text/javascript" src="forge/pem.js"></script>
+      <script type="text/javascript" src="forge/asn1.js"></script>
+      <script type="text/javascript" src="forge/jsbn.js"></script>
+      <script type="text/javascript" src="forge/prng.js"></script>
+      <script type="text/javascript" src="forge/random.js"></script>
+      <script type="text/javascript" src="forge/oids.js"></script>
+      <script type="text/javascript" src="forge/rsa.js"></script>
+      <script type="text/javascript" src="forge/pbe.js"></script>
+      <script type="text/javascript" src="forge/x509.js"></script>
+      <script type="text/javascript" src="forge/pki.js"></script>
+      <script type="text/javascript" src="forge/tls.js"></script>
+      <script type="text/javascript" src="forge/aesCipherSuites.js"></script>
+      <script type="text/javascript" src="forge/tlssocket.js"></script>
+      <script type="text/javascript" src="forge/http.js"></script>
+      <script type="text/javascript" src="ws-webid.js"></script>
+      
+      <script type="text/javascript">
+      //<![CDATA[
+      // logging category
+      var cat = 'forge.tests.tls';
+
+      swfobject.embedSWF(
+         'forge/SocketPool.swf', 'socketPool', '0', '0', '9.0.0',
+         false, {}, {allowscriptaccess: 'always'}, {});
+      
+      // CA certificate for test server
+      var certificatePem =
+         '-----BEGIN CERTIFICATE-----\r\n' +
+         'MIIEaDCCA1CgAwIBAgIJAJuj0AjEWncuMA0GCSqGSIb3DQEBBQUAMH8xCzAJBgNV\r\n' +
+         'BAYTAlVTMREwDwYDVQQIEwhWaXJnaW5pYTETMBEGA1UEBxMKQmxhY2tzYnVyZzEd\r\n' +
+         'MBsGA1UEChMURGlnaXRhbCBCYXphYXIsIEluYy4xGjAYBgNVBAsTEUZvcmdlIFRl\r\n' +
+         'c3QgU2VydmVyMQ0wCwYDVQQDEwR0ZXN0MB4XDTEwMDcxMzE3MjAzN1oXDTMwMDcw\r\n' +
+         'ODE3MjAzN1owfzELMAkGA1UEBhMCVVMxETAPBgNVBAgTCFZpcmdpbmlhMRMwEQYD\r\n' +
+         'VQQHEwpCbGFja3NidXJnMR0wGwYDVQQKExREaWdpdGFsIEJhemFhciwgSW5jLjEa\r\n' +
+         'MBgGA1UECxMRRm9yZ2UgVGVzdCBTZXJ2ZXIxDTALBgNVBAMTBHRlc3QwggEiMA0G\r\n' +
+         'CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCm/FobjqK8CVP/Xbnpyhf1tpoyaiFf\r\n' +
+         'ShUOmlWqL5rLe0Q0dDR/Zur+sLMUv/1T4wOfFkjjxvZ0Sk5NIjK3Wy2UA41a+M3J\r\n' +
+         'RTbCFrg4ujsovFaD4CDmV7Rek0qJB3m5Gp7hgu5vfL/v+WrwxnQObNq+IrTMSA15\r\n' +
+         'cO4LzNIPj9K1LN2dB+ucT7xTQFHAfvLLgLlCLiberoabF4rEhgTMTbmMtFVKSt+P\r\n' +
+         'xgQIYPnhw1WuAvE9hFesRQFdfARLqIZk92FeHkgtHv9BAunktJemcidbowTCTBaM\r\n' +
+         '/njcgi1Tei/LFkph/FCVyGER0pekJNHX626bAQSLo/srsWfmcll9rK6bAgMBAAGj\r\n' +
+         'geYwgeMwHQYDVR0OBBYEFCau5k6jxezjULlLuo/liswJlBF8MIGzBgNVHSMEgasw\r\n' +
+         'gaiAFCau5k6jxezjULlLuo/liswJlBF8oYGEpIGBMH8xCzAJBgNVBAYTAlVTMREw\r\n' +
+         'DwYDVQQIEwhWaXJnaW5pYTETMBEGA1UEBxMKQmxhY2tzYnVyZzEdMBsGA1UEChMU\r\n' +
+         'RGlnaXRhbCBCYXphYXIsIEluYy4xGjAYBgNVBAsTEUZvcmdlIFRlc3QgU2VydmVy\r\n' +
+         'MQ0wCwYDVQQDEwR0ZXN0ggkAm6PQCMRady4wDAYDVR0TBAUwAwEB/zANBgkqhkiG\r\n' +
+         '9w0BAQUFAAOCAQEAnP/2mzFWaoGx6+KAfY8pcgnF48IoyKPx5cAQyzpMo+uRwrln\r\n' +
+         'INcDGwNx6p6rkjFbK27TME9ReCk+xQuVGaKOtqErKECXWDtD+0M35noyaOwWIFu2\r\n' +
+         '7gPZ0uGJ1n9ZMe/S9yZmmusaIrc66rX4o+fslUlH0g3SrH7yf83M8aOC2pEyCsG0\r\n' +
+         'mNNfwSFWfmu+1GMRHXJQ/qT8qBX8ZPhzRY2BAS6vr+eh3gwXR6yXLA8Xm1+e+iDU\r\n' +
+         'gGTQoYkixDIL2nhvd4AFFlE977BiE+0sMS1eJKUUbQ36MLAWb5oOZKHrphEvqMKA\r\n' +
+         'eGDO3qoDqB5TkZC3x38DXBDvAZ01d9s0fvveag==\r\n' +
+         '-----END CERTIFICATE-----';
+      
+      // local aliases
+      var net = window.forge.net;
+      var tls = window.forge.tls;
+      var http = window.forge.http;
+      var util = window.forge.util;
+
+      var client;
+      
+      function client_init(primed)
+      {
+         try
+         {
+            var sp = net.createSocketPool({
+               flashId: 'socketPool',
+               policyPort: 19945,
+               msie: false
+            });
+            client = http.createClient({
+               //url: 'https://localhost:4433',
+               url: 'https://' + window.location.host,
+               socketPool: sp,
+               connections: 10,
+               caCerts: [certificatePem],
+               // optional cipher suites in order of preference
+               cipherSuites: [
+                  tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+                  tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+               verify: function(c, verified, depth, certs)
+               {
+                  forge.log.debug(cat,
+                     'TLS certificate ' + depth + ' verified', verified);
+                  // Note: change to always true to test verifying without cert
+                  //return verified;
+                  // FIXME: temporarily accept any cert to allow hitting any bpe
+                  if(verified !== true)
+                  {
+                     forge.log.warning(cat, 
+                        'Certificate NOT verified. Ignored for test.');
+                  }
+                  return true;
+               },
+               primeTlsSockets: primed
+            });
+            document.getElementById('feedback').innerHTML =
+               'http client created';
+         }
+         catch(ex)
+         {
+            forge.log.error(cat, ex);
+         }
+         
+         return false;
+      }
+      
+      function client_cleanup()
+      {
+         var sp = client.socketPool;
+         client.destroy();
+         sp.destroy();
+         document.getElementById('feedback').innerHTML =
+            'http client cleaned up';
+         return false;
+      }
+
+      function client_send()
+      {
+         /*
+         var request = http.createRequest({
+            method: 'POST',
+            path: '/',
+            body: 'echo=foo',
+            headers: [{'Content-Type': 'application/x-www-form-urlencoded'}]
+         });
+         */
+         var request = http.createRequest({
+            method: 'GET',
+            path: '/'
+         });
+         
+         client.send({
+            request: request,
+            connected: function(e)
+            {
+               forge.log.debug(cat, 'connected', e);
+            },
+            headerReady: function(e)
+            {
+               forge.log.debug(cat, 'header ready', e);
+            },
+            bodyReady: function(e)
+            {
+               forge.log.debug(cat, 'body ready', e);
+
+               // FIXME: current test server doesn't seem to handle keep-alive
+               // correctly, so close connection 
+               e.socket.close();
+            },
+            error: function(e)
+            {
+               forge.log.error(cat, 'error', e);
+            }
+         });
+         document.getElementById('feedback').innerHTML =
+            'http request sent';
+         return false;
+      }
+
+      function client_send_10()
+      {
+         for(var i = 0; i < 10; ++i)
+         {
+            client_send();
+         }
+         return false;
+      }
+
+      function client_stress()
+      {
+         for(var i = 0; i < 10; ++i)
+         {
+            setTimeout(function()
+            {
+               for(var i = 0; i < 10; ++i)
+               {
+                  client_send();
+               }
+            }, 0);
+         }
+         return false;
+      }
+
+      function client_cookies()
+      {
+         var cookie =
+         {
+            name: 'test-cookie',
+            value: 'test-value',
+            maxAge: -1,
+            secure: true,
+            path: '/'
+         };
+         client.setCookie(cookie);
+         forge.log.debug(cat, 'cookie', client.getCookie('test-cookie'));
+      }
+
+      function client_clear_cookies()
+      {
+         client.clearCookies();
+      }
+      
+      function websocket_test()
+      {
+         // create certificate
+         var cn = 'client';
+         console.log(
+            'Generating 512-bit key-pair and certificate for \"' + cn + '\".');
+         var keys = forge.pki.rsa.generateKeyPair(512);
+         console.log('key-pair created.');
+
+         var cert = forge.pki.createCertificate();
+         cert.serialNumber = '01';
+         cert.validity.notBefore = new Date();
+         cert.validity.notAfter = new Date();
+         cert.validity.notAfter.setFullYear(
+            cert.validity.notBefore.getFullYear() + 1);
+         var attrs = [{
+            name: 'commonName',
+            value: cn
+         }, {
+            name: 'countryName',
+            value: 'US'
+         }, {
+            shortName: 'ST',
+            value: 'Virginia'
+         }, {
+            name: 'localityName',
+            value: 'Blacksburg'
+         }, {
+            name: 'organizationName',
+            value: 'Test'
+         }, {
+            shortName: 'OU',
+            value: 'Test'
+         }];
+         cert.setSubject(attrs);
+         cert.setIssuer(attrs);
+         cert.setExtensions([{
+            name: 'basicConstraints',
+            cA: true
+         }, {
+            name: 'keyUsage',
+            keyCertSign: true,
+            digitalSignature: true,
+            nonRepudiation: true,
+            keyEncipherment: true,
+            dataEncipherment: true
+         }, {
+            name: 'subjectAltName',
+            altNames: [{
+               type: 6, // URI
+               value: 'http://myuri.com/webid#me'
+            }]
+         }]);
+         // FIXME: add subjectKeyIdentifier extension
+         // FIXME: add authorityKeyIdentifier extension
+         cert.publicKey = keys.publicKey;
+         
+         // self-sign certificate
+         cert.sign(keys.privateKey);
+         
+         // save cert and private key in PEM format
+         cert = forge.pki.certificateToPem(cert);
+         privateKey = forge.pki.privateKeyToPem(keys.privateKey);
+         console.log('certificate created for \"' + cn + '\": \n' + cert);
+
+         // create websocket
+         var ws = new WebSocket('ws://localhost:8080');
+         console.log('created websocket', ws);
+
+         // create TLS client
+         var success = false;
+         var tls = forge.tls.createConnection(
+         {
+            server: false,
+            caStore: [],
+            sessionCache: {},
+            // supported cipher suites in order of preference
+            cipherSuites: [
+               forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+               forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+            virtualHost: 'server',
+            verify: function(c, verified, depth, certs)
+            {
+               console.log(
+                  'TLS Client verifying certificate w/CN: \"' +
+                  certs[0].subject.getField('CN').value +
+                  '\", verified: ' + verified + '...');
+               // accept any certificate from the server for this test
+               return true;
+            },
+            connected: function(c)
+            {
+               console.log('Client connected...');
+               
+               // send message to server
+               setTimeout(function()
+               {
+                  c.prepare('Hello Server');
+               }, 1);
+            },
+            getCertificate: function(c, hint)
+            {
+               console.log('Client getting certificate ...');
+               return cert;
+            },
+            getPrivateKey: function(c, cert)
+            {
+               return privateKey;
+            },
+            tlsDataReady: function(c)
+            {
+               // send base64-encoded TLS data to server
+               ws.send(forge.util.encode64(c.tlsData.getBytes()));
+            },
+            dataReady: function(c)
+            {
+               var response = c.data.getBytes();
+               console.log('Client received \"' + response + '\"');
+               success = (response === 'Hello Client');
+               c.close();
+            },
+            closed: function(c)
+            {
+               console.log('Client disconnected.');
+               if(success)
+               {
+                  console.log('PASS');
+               }
+               else
+               {
+                  console.log('FAIL');
+               }
+            },
+            error: function(c, error)
+            {
+               console.log('Client error: ' + error.message);
+            }
+         });
+
+         ws.onopen = function(evt)
+         {
+            console.log('websocket connected');
+
+            // do TLS handshake
+            tls.handshake();
+         };
+         ws.onmessage = function(evt)
+         {
+            // base64-decode data and process it
+            tls.process(forge.util.decode64(evt.data));
+         };
+         ws.onclose = function(evt)
+         {
+            console.log('websocket closed');
+         };
+      }
+      
+      //]]>
+      </script>
+   </head>
+   <body>
+      <div class="nav"><a href="index.html">Forge Tests</a> / TLS</div>
+
+      <div class="header">
+         <h1>TLS Test</h1>
+      </div>
+
+      <div class="content">
+
+      <!-- div used to hold the flash socket pool implemenation -->
+      <div id="socketPool">
+         <p>Could not load the flash SocketPool.</p>
+      </div>
+
+      <fieldset class="section">
+         <ul>
+           <li>Use the controls below to test the HTTP client over TLS.</li>
+           <li>You currently need a JavaScript console to view the output.</li>
+           <li>This test connects to a TLS server so you must have one running. The easiest way to run this test is to start the test server with --tls and load this page over HTTPS.</li>
+         </ul>
+      </fieldset>
+
+      <fieldset class="section">
+      <legend>Controls</legend>
+      <div id="controls">
+         <button id="init" onclick="javascript:return client_init(false);">init</button>
+         <button id="init_primed" onclick="javascript:return client_init(true);">init primed</button>
+         <button id="cleanup" onclick="javascript:return client_cleanup();">cleanup</button>
+         <button id="send" onclick="javascript:return client_send();">send</button>
+         <button id="send10" onclick="javascript:return client_send_10();">send 10</button>
+         <button id="stress" onclick="javascript:return client_stress();">stress</button>
+         <button id="client_cookies" onclick="javascript:return client_cookies();">cookies</button>
+         <button id="clear_cookies" onclick="javascript:return client_clear_cookies();">clear cookies</button>
+         <button id="websocket" onclick="javascript:return websocket_test();">websocket test</button>
+         <button id="websocket-webid" onclick="javascript:return websocket_webid('localhost', 8080);">websocket webid test</button>
+      </div>
+      </fieldset>
+
+      <fieldset class="section">
+      <legend>Feedback</legend>
+      <p>Feedback from the flash SocketPool:</p>
+      <div id="feedback">
+      None
+      </div>
+      </fieldset>
+
+      </div>
+   </body>
+</html>
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/webid.html b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/webid.html
new file mode 100644
index 0000000..8c8d795
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/webid.html
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+   <head>
+      <title>Web ID Test</title>
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js"></script>
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
+      <script type="text/javascript" src="forge/debug.js"></script>
+      <script type="text/javascript" src="forge/util.js"></script>
+      <script type="text/javascript" src="forge/log.js"></script>
+      <script type="text/javascript" src="forge/task.js"></script>
+      <script type="text/javascript" src="forge/socket.js"></script>
+      <script type="text/javascript" src="forge/md5.js"></script>
+      <script type="text/javascript" src="forge/sha1.js"></script>
+      <script type="text/javascript" src="forge/hmac.js"></script>
+      <script type="text/javascript" src="forge/aes.js"></script>
+      <script type="text/javascript" src="forge/pem.js"></script>
+      <script type="text/javascript" src="forge/asn1.js"></script>
+      <script type="text/javascript" src="forge/jsbn.js"></script>
+      <script type="text/javascript" src="forge/prng.js"></script>
+      <script type="text/javascript" src="forge/random.js"></script>
+      <script type="text/javascript" src="forge/oids.js"></script>
+      <script type="text/javascript" src="forge/rsa.js"></script>
+      <script type="text/javascript" src="forge/pbe.js"></script>
+      <script type="text/javascript" src="forge/x509.js"></script>
+      <script type="text/javascript" src="forge/pki.js"></script>
+      <script type="text/javascript" src="forge/tls.js"></script>
+      <script type="text/javascript" src="forge/aesCipherSuites.js"></script>
+      <script type="text/javascript" src="forge/tlssocket.js"></script>
+      <script type="text/javascript" src="forge/http.js"></script>
+      <script type="text/javascript" src="forge/xhr.js"></script>
+      <script type="text/javascript" src="webid.js"></script>
+
+      <link type="text/css" rel="stylesheet" media="all" href="screen.css" />
+      <style type="text/css">
+         .ready { color: inherit; background: inherit; }
+         .testing { color: black; background: yellow; }
+         .pass{ color: white; background: green; }
+         .fail{ color: white; background: red; }
+      </style>
+   </head>
+<body>
+<div class="nav"><a href="index.html">Forge Tests</a> / Web ID</div>
+
+<div class="header">
+   <h1>Web ID Tests</h1>
+</div>
+
+<div class="content">
+
+<!-- div used to hold the flash socket pool implementation -->
+<div id="socketPool">
+   <p>Could not load the flash SocketPool.</p>
+</div>
+
+<fieldset class="section">
+   <ul>
+     <li>Use the controls below to test Web ID.</li>
+     <li>Use 512 bits or less on slower JavaScript implementations.</li>
+   </ul>
+</fieldset>
+
+<fieldset class="section">
+<legend>Control</legend>
+   <table>
+      <tr>
+         <td rowspan="3"><button id="create">Create Web ID</button></td>
+         <td>Bits</td>
+         <td><input id="bits" size=8 value="1024"/></td>
+      </tr>
+      <tr>
+         <td>URI</td>
+         <td><input id="uri" size=60 value="http://localhost/dataspace/person/myname#this"/></td>
+      </tr>
+      <tr>
+         <td>Common Name</td>
+         <td><input id="commonName" size=20 value="mycert"/></td>
+      </tr>
+      <tr>
+         <td><button id="show">Show Web IDs</button></td>
+         <td>&nbsp;</td>
+         <td>&nbsp;</td>
+      </tr>
+      <tr>
+         <td><button id="clear">Delete Web IDs</button></td>
+         <td>&nbsp;</td>
+         <td>&nbsp;</td>
+      </tr>
+      <tr>
+         <td><button id="authenticate">Authenticate using Web ID</button></td>
+         <td>URI</td>
+         <td><input id="webid" size=60 value="http://localhost/dataspace/person/myname#this"/></td>
+      </tr>
+   </table>
+</fieldset>
+
+<fieldset class="section">
+<legend>Progress</legend>
+   <div id="progress"></div>
+</fieldset>
+
+<fieldset class="section">
+<legend>Available Web IDs</legend>
+<div id="webids"></div>
+</fieldset>
+
+</div>
+</body>
+</html>
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/webid.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/webid.js
new file mode 100644
index 0000000..7c07ab9
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/webid.js
@@ -0,0 +1,313 @@
+/**
+ * Forge Web ID Tests
+ *
+ * @author Dave Longley
+ *
+ * Copyright (c) 2010 Digital Bazaar, Inc. All rights reserved.
+ */
+(function($)
+{
+   // load flash socket pool
+   window.forge.socketPool = {};
+   window.forge.socketPool.ready = function()
+   {
+      // init forge xhr
+      forge.xhr.init({
+         flashId: 'socketPool',
+         policyPort: 19945,
+         msie: $.browser.msie,
+         connections: 10,
+         caCerts: [],
+         verify: function(c, verified, depth, certs)
+         {
+            // don't care about cert verification for test
+            return true;
+         }
+      });
+   };
+   swfobject.embedSWF(
+      'forge/SocketPool.swf', 'socketPool', '0', '0', '9.0.0',
+      false, {}, {allowscriptaccess: 'always'}, {});
+})(jQuery);
+
+jQuery(function($)
+{
+   var cat = 'forge.tests.webid';
+   
+   // local alias
+   var forge = window.forge;
+
+   $('#create').click(function()
+   {
+      var bits = $('#bits')[0].value;
+      var uri = $('#uri')[0].value;
+      var commonName = $('#commonName')[0].value;
+      forge.log.debug(cat, 'generating ' + bits +
+         '-bit RSA key-pair and certificate...');
+      
+      // function to create cert
+      var createCert = function(keys)
+      {
+         try
+         {
+            var cert = forge.pki.createCertificate();
+            cert.serialNumber = '01';
+            cert.validity.notBefore = new Date();
+            cert.validity.notAfter = new Date();
+            cert.validity.notAfter.setFullYear(
+               cert.validity.notBefore.getFullYear() + 1);
+            var attrs = [{
+               name: 'commonName',
+               value: commonName
+            }, {
+               name: 'countryName',
+               value: 'US'
+            }, {
+               shortName: 'ST',
+               value: 'Virginia'
+            }, {
+               name: 'localityName',
+               value: 'Blacksburg'
+            }, {
+               name: 'organizationName',
+               value: 'Test'
+            }, {
+               shortName: 'OU',
+               value: 'Test'
+            }];
+            cert.setSubject(attrs);
+            cert.setIssuer(attrs);
+            cert.setExtensions([{
+               name: 'basicConstraints',
+               cA: true
+            }, {
+               name: 'keyUsage',
+               keyCertSign: true,
+               digitalSignature: true,
+               nonRepudiation: true,
+               keyEncipherment: true,
+               dataEncipherment: true
+            }, {
+               name: 'subjectAltName',
+               altNames: [{
+                  type: 6, // URI
+                  value: uri
+               }]
+            }]);
+            // FIXME: add subjectKeyIdentifier extension
+            // FIXME: add authorityKeyIdentifier extension
+            cert.publicKey = keys.publicKey;
+            
+            // self-sign certificate
+            cert.sign(keys.privateKey);
+            
+            // verify certificate
+            forge.log.debug('verified', cert.verify(cert));
+            
+            forge.log.debug(cat, 'certificate:', cert);
+            //forge.log.debug(cat, 
+            //   forge.asn1.prettyPrint(forge.pki.certificateToAsn1(cert)));
+            var keyPem = forge.pki.privateKeyToPem(keys.privateKey);
+            var certPem = forge.pki.certificateToPem(cert);
+            forge.log.debug(cat, keyPem);
+            forge.log.debug(cat, certPem);
+            
+            forge.log.debug(cat, 'storing certificate and private key...');
+            try
+            {
+               // get flash API
+               var flashApi = document.getElementById('socketPool');
+               
+               // get web ids collection
+               var webids = forge.util.getItem(
+                  flashApi, 'forge.test.webid', 'webids');
+               webids = webids || {};
+               
+               // add web id
+               webids[uri] = {
+                  certificate: certPem,
+                  privateKey: keyPem
+               };
+               
+               // update web ids collection
+               forge.util.setItem(
+                  flashApi, 'forge.test.webid', 'webids', webids);
+               
+               forge.log.debug(cat, 'certificate and private key stored');
+               $('#show').click();
+            }
+            catch(ex)
+            {
+               forge.log.error(cat, ex);
+            }
+         }
+         catch(ex)
+         {
+            forge.log.error(cat, ex, ex.message ? ex.message : '');
+         }
+      };
+      
+      // create key-generation state and function to step algorithm
+      var progress = $('#progress');
+      progress.html('Generating ' + bits + '-bit key-pair.');
+      var state = forge.pki.rsa.createKeyPairGenerationState(bits);
+      var kgTime = +new Date();
+      var step = function()
+      {
+         // step key-generation
+         if(!forge.pki.rsa.stepKeyPairGenerationState(state, 1000))
+         {
+            progress.html(progress.html() + '.');
+            setTimeout(step, 1);
+         }
+         // key-generation complete
+         else
+         {
+            kgTime = +new Date() - kgTime;
+            forge.log.debug(cat, 'Total key-gen time', kgTime + 'ms');
+            createCert(state.keys);
+            progress.html(progress.html() + 'done. Time=' + kgTime + 'ms');
+         }
+      };
+      
+      // run key-gen algorithm
+      setTimeout(step, 0);
+   });
+
+   $('#show').click(function()
+   {  
+      forge.log.debug(cat, 'get stored web IDs...');
+      try
+      {
+         // get flash API
+         var flashApi = document.getElementById('socketPool');
+         
+         // get web ids collection
+         var webids = forge.util.getItem(
+            flashApi, 'forge.test.webid', 'webids');
+         webids = webids || {};
+         
+         var html = '<ul>';
+         var webid, cert;
+         for(var key in webids)
+         {
+            webid = webids[key];
+            cert = forge.pki.certificateFromPem(webid.certificate);
+            html += '<li><p>' + key + '</p>';
+            
+            var attr;
+            for(var n = 0; n < cert.subject.attributes.length; ++n)
+            {
+               attr = cert.subject.attributes[n];
+               html += attr.name + ': ' + attr.value + '<br/>';
+            }
+            
+            //html += '<p>' + webid.certificate + '</p></li>';
+            html += '</li>';
+         }
+         if(html === '<ul>')
+         {
+            html = 'None';
+         }
+         else
+         {
+            html += '</ul>';
+         }
+         
+         $('#webids').html(html);
+         
+         forge.log.debug(cat, 'Web IDs retrieved');
+      }
+      catch(ex)
+      {
+         forge.log.error(cat, ex);
+      }
+   });
+   
+   $('#clear').click(function()
+   {  
+      forge.log.debug(cat, 'clearing all web IDs...');
+      try
+      {
+         // get flash API
+         var flashApi = document.getElementById('socketPool');
+         forge.util.clearItems(flashApi, 'forge.test.webid');
+         $('#webids').html('None');
+         forge.log.debug(cat, 'Web IDs cleared');
+      }
+      catch(ex)
+      {
+         forge.log.error(cat, ex);
+      }
+   });
+   
+   $('#authenticate').click(function()
+   {
+      forge.log.debug(cat, 'doing Web ID authentication...');
+      
+      try
+      {
+         // get flash API
+         var flashApi = document.getElementById('socketPool');
+         
+         // get web ids collection
+         var webids = forge.util.getItem(
+            flashApi, 'forge.test.webid', 'webids');
+         webids = webids || {};
+         
+         var uri = $('#webid')[0].value;
+         var webid = webids[uri];
+         
+         $.ajax(
+         {
+            type: 'GET',
+            url: '/',
+            success: function(data, textStatus, xhr)
+            {
+               if(data !== '')
+               {
+                  forge.log.debug(cat, 'authentication completed');
+                  forge.log.debug(cat, data);
+               }
+               else
+               {
+                  forge.log.error(cat, 'authentication failed');
+               }
+            },
+            error: function(xhr, textStatus, errorThrown)
+            {
+               forge.log.error(cat, 'authentication failed');
+            },
+            xhr: function()
+            {
+               return forge.xhr.create({
+                  // FIXME: change URL
+                  url: 'https://localhost:4433',
+                  connections: 10,
+                  caCerts: [],
+                  verify: function(c, verified, depth, certs)
+                  {
+                     // don't care about cert verification for test
+                     return true;
+                  },
+                  getCertificate: function(c)
+                  {
+                     //forge.log.debug(cat, 'using cert', webid.certificate);
+                     return webid.certificate;
+                  },
+                  getPrivateKey: function(c)
+                  {
+                     //forge.log.debug(cat,
+                     //   'using private key', webid.privateKey);
+                     return webid.privateKey;
+                  }
+               });
+            }
+         });      
+      }
+      catch(ex)
+      {
+         forge.log.error(cat, ex);
+      }
+   });
+});
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/ws-webid.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/ws-webid.js
new file mode 100644
index 0000000..2ce5816
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/ws-webid.js
@@ -0,0 +1,132 @@
+var websocket_webid = function(host, port)
+{
+   var cat = 'ws';
+   
+   // TODO: get private key and certificate from local storage
+   var privateKey =
+   '-----BEGIN RSA PRIVATE KEY-----\r\n' +
+'MIICXAIBAAKBgQCTmE8QLARsC57Z1OrOaLM6AS3fn70N7BvlU7z7yw8UpcJA/jOl\r\n' +
+'NWu7eS9uzuckdVZ9FE0+x3DRvhtDI6K+18dcrUWtl5ADWXcs1QS3/7bGh7IybFyY\r\n' +
+'0xT4VzLHcx6K4PNmfkjAQdyOz/EsuRqZ/ngIQ2tdHdkkzdQPECbTvFeG2wIDAQAB\r\n' +
+'AoGAds3l7l2QHaxo7GzfqNBMXEdwto2tLxS8C6eQ+pkkBXm72HcF+Vj75AcTMD2p\r\n' +
+'fwZYXQxHdV4yqRI+fZeku7uTA/3yBAAvNobbEN5jtHnq0ZTO/HO8HuHkKrCvD8c3\r\n' +
+'0rJV6lNIuaARI9jZFf6HVchW3PMjKUpYhTs/sFhRxmsMpTkCQQDu8TPzXRmN1aw8\r\n' +
+'tSI2Nyn8QUy9bw/12tlVaZIhrcVCiJl7JHGqSCowTqZlwmJIjd4W0zWjTvS7tEeO\r\n' +
+'FaZHtP8lAkEAniGvm8S9zyzmhWRRIuU6EE2dtTbeAa5aSOK3nBaaNu2cHUxWle+J\r\n' +
+'8lE4uequ9wqDG1AfOLobPmHReccmOI6N/wJAIP/I1/RkohT/a4bsiaZGsyLlkUf0\r\n' +
+'YVTvLP+ege44zv6Ei+A1nnnG8dL64hTdc/27zVUwFDTEUeQM+c99nmudzQJBAApY\r\n' +
+'qeTHOqQTjAGuTqC53tKyQV9Z96yke8PJEbpkwDJX2Z8RH5kv0xbHua5wbII9bdab\r\n' +
+'p29OvfmW7N3K6fVJXoECQHK8FDC0i8v1Ui8FoBmt+Z1c1+/9TCEE0abUQ6rfOUbm\r\n' +
+'XHMMac/n4qDs0OoCjR4u46dpoK+WN7zcg56tToFPVow=\r\n' +
+'-----END RSA PRIVATE KEY-----';
+  var certificate =
+  '-----BEGIN CERTIFICATE-----\r\n' +
+'MIICgDCCAemgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMRMwEQYDVQQDEwpKb2hu\r\n' +
+'IFNtaXRoMRMwEQYDVQQHEwpCbGFja3NidXJnMREwDwYDVQQIEwhWaXJnaW5pYTEL\r\n' +
+'MAkGA1UEBhMCVVMxDDAKBgNVBAoTA0ZvbzAeFw0xMDExMjYxNzUxMzJaFw0xMTEx\r\n' +
+'MjYxNzUxMzJaMFgxEzARBgNVBAMTCkpvaG4gU21pdGgxEzARBgNVBAcTCkJsYWNr\r\n' +
+'c2J1cmcxETAPBgNVBAgTCFZpcmdpbmlhMQswCQYDVQQGEwJVUzEMMAoGA1UEChMD\r\n' +
+'Rm9vMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCTmE8QLARsC57Z1OrOaLM6\r\n' +
+'AS3fn70N7BvlU7z7yw8UpcJA/jOlNWu7eS9uzuckdVZ9FE0+x3DRvhtDI6K+18dc\r\n' +
+'rUWtl5ADWXcs1QS3/7bGh7IybFyY0xT4VzLHcx6K4PNmfkjAQdyOz/EsuRqZ/ngI\r\n' +
+'Q2tdHdkkzdQPECbTvFeG2wIDAQABo1owWDAMBgNVHRMEBTADAQH/MAsGA1UdDwQE\r\n' +
+'AwIC9DA7BgNVHREENDAyhjBodHRwOi8vd2ViaWQuZGlnaXRhbGJhemFhci5jb20v\r\n' +
+'aWRzLzE1MzQ1NzI2NDcjbWUwDQYJKoZIhvcNAQEFBQADgYEAPNm8albI4w6anynw\r\n' +
+'XE/+00sCVks9BbgTcIpRqZPGqSuTRwoYW35isNLDqFqIUdVREMvFrEn3nOlOyKi0\r\n' +
+'29G8JtLHFSXZsqf38Zou/bGAhtEH1AVEbM2bRtEnG8IW24jL8hiciz4htxmjnkHN\r\n' +
+'JnQ8SQtUSWplGnz0vMFEOv6JbnI=\r\n' +
+'-----END CERTIFICATE-----';
+
+   // create websocket
+   var ws = new WebSocket('ws://' + host + ':' + port);
+   forge.log.debug(cat, 'Created WebSocket', ws);
+
+   // create TLS client
+   var success = false;
+   var tls = forge.tls.createConnection(
+   {
+      server: false,
+      caStore: [],
+      sessionCache: {},
+      // supported cipher suites in order of preference
+      cipherSuites: [
+         forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+         forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+      virtualHost: host,
+      verify: function(c, verified, depth, certs)
+      {
+         forge.log.debug(cat, 
+            'TLS Client verifying certificate w/CN: \"' +
+            certs[0].subject.getField('CN').value + '\"');
+         // accept any certificate from the server for this test
+         return true;
+      },
+      connected: function(c)
+      {
+         forge.log.debug(cat, 'Client connected');
+      },
+      getCertificate: function(c, hint)
+      {
+         forge.log.debug(cat, 'Client using client-certificate');
+         return certificate;
+      },
+      getPrivateKey: function(c, cert)
+      {
+         return privateKey;
+      },
+      tlsDataReady: function(c)
+      {
+         // send base64-encoded TLS data to server
+         ws.send(forge.util.encode64(c.tlsData.getBytes()));
+      },
+      dataReady: function(c)
+      {
+         var response = c.data.getBytes();
+         forge.log.debug(cat, 'Client received \"' + response + '\"');
+         try
+         {
+            response = JSON.parse(response);
+            success = response.success;
+            
+            // TODO: call window.authenticate on response json, just like
+            // w/flash version
+         }
+         catch(ex) {}
+         c.close();
+      },
+      closed: function(c)
+      {
+         forge.log.debug(cat, 'Client disconnected');
+         if(success)
+         {
+            forge.log.debug(cat, 'PASS');
+         }
+         else
+         {
+            forge.log.debug(cat, 'FAIL');
+         }
+      },
+      error: function(c, error)
+      {
+         forge.log.debug(cat, 'Client error: ' + error.message);
+      }
+   });
+
+   ws.onopen = function(evt)
+   {
+      forge.log.debug(cat, 'WebSocket connected');
+
+      // do TLS handshake
+      tls.handshake();
+   };
+   ws.onmessage = function(evt)
+   {
+      // base64-decode data and process it
+      tls.process(forge.util.decode64(evt.data));
+   };
+   ws.onclose = function(evt)
+   {
+      forge.log.debug(cat, 'WebSocket closed');
+   };
+};
+
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/ws.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/ws.js
new file mode 100644
index 0000000..ba0b39d
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/ws.js
@@ -0,0 +1,237 @@
+// Github: http://github.com/ncr/node.ws.js
+// Compatible with node v0.1.91
+// Author: Jacek Becela
+// Contributors:
+//   Michael Stillwell  http://github.com/ithinkihaveacat
+//   Nick Chapman       http://github.com/nchapman
+//   Dmitriy Shalashov  http://github.com/skaurus
+//   Johan Dahlberg
+//   Andreas Kompanez
+//   Samuel Cyprian		http://github.com/samcyp
+// License: MIT
+// Based on: http://github.com/Guille/node.websocket.js
+
+function nano(template, data) {
+  return template.replace(/\{([\w\.]*)}/g, function (str, key) {
+    var keys = key.split("."), value = data[keys.shift()];
+    keys.forEach(function (key) { value = value[key];});
+    return value;
+  });
+}
+
+function pack(num) {
+  var result = '';
+  result += String.fromCharCode(num >> 24 & 0xFF);
+  result += String.fromCharCode(num >> 16 & 0xFF);
+  result += String.fromCharCode(num >> 8 & 0xFF);
+  result += String.fromCharCode(num & 0xFF);
+  return result;
+}
+
+var sys  = require("sys"),
+  net    = require("net"),
+  crypto = require("crypto"),
+  requiredHeaders = {
+    'get': /^GET (\/[^\s]*)/,
+    'upgrade': /^WebSocket$/,
+    'connection': /^Upgrade$/,
+    'host': /^(.+)$/,
+    'origin': /^(.+)$/
+  },
+  handshakeTemplate75 = [
+    'HTTP/1.1 101 Web Socket Protocol Handshake', 
+    'Upgrade: WebSocket', 
+    'Connection: Upgrade',
+    'WebSocket-Origin: {origin}',
+    'WebSocket-Location: {protocol}://{host}{resource}',
+    '',
+    ''
+  ].join("\r\n"),
+  handshakeTemplate76 = [
+    'HTTP/1.1 101 WebSocket Protocol Handshake', // note a diff here
+    'Upgrade: WebSocket',
+    'Connection: Upgrade',
+    'Sec-WebSocket-Origin: {origin}',
+    'Sec-WebSocket-Location: {protocol}://{host}{resource}',
+    '',
+    '{data}'
+  ].join("\r\n"),
+  flashPolicy = '<cross-domain-policy><allow-access-from domain="*" to-ports="*" /></cross-domain-policy>';
+
+
+
+exports.createSecureServer = function (websocketListener, credentials, options) {
+	if (!options) options = {};
+	options.secure = credentials;
+	return this.createServer(websocketListener, options);
+};
+
+exports.createServer = function (websocketListener, options) {
+  if (!options) options = {};
+  if (!options.flashPolicy) options.flashPolicy = flashPolicy;
+  // The value should be a crypto credentials
+  if (!options.secure) options.secure = null;
+
+  return net.createServer(function (socket) {
+	//Secure WebSockets
+	var wsProtocol = 'ws';
+	if(options.secure) {
+	  wsProtocol = 'wss';
+	  socket.setSecure(options.secure);
+	}
+    socket.setTimeout(0);
+    socket.setNoDelay(true);
+    socket.setKeepAlive(true, 0);
+
+    var emitter = new process.EventEmitter(),
+      handshaked = false,
+      buffer = "";
+      
+    function handle(data) {
+      buffer += data;
+      
+      var chunks = buffer.split("\ufffd"),
+        count = chunks.length - 1; // last is "" or a partial packet
+        
+      for(var i = 0; i < count; i++) {
+        var chunk = chunks[i];
+        if(chunk[0] == "\u0000") {
+          emitter.emit("data", chunk.slice(1));
+        } else {
+          socket.end();
+          return;
+        }
+      }
+      
+      buffer = chunks[count];
+    }
+
+    function handshake(data) {
+      var _headers = data.split("\r\n");
+
+      if ( /<policy-file-request.*>/.exec(_headers[0]) ) {
+        socket.write( options.flashPolicy );
+        socket.end();
+        return;
+      }
+
+      // go to more convenient hash form
+      var headers = {}, upgradeHead, len = _headers.length;
+      if ( _headers[0].match(/^GET /) ) {
+        headers["get"] = _headers[0];
+      } else {
+        socket.end();
+        return;
+      }
+      if ( _headers[ _headers.length - 1 ] ) {
+        upgradeHead = _headers[ _headers.length - 1 ];
+        len--;
+      }
+      while (--len) { // _headers[0] will be skipped
+        var header = _headers[len];
+        if (!header) continue;
+
+        var split = header.split(": ", 2); // second parameter actually seems to not work in node
+        headers[ split[0].toLowerCase() ] = split[1];
+      }
+
+      // check if we have all needed headers and fetch data from them
+      var data = {}, match;
+      for (var header in requiredHeaders) {
+        //           regexp                          actual header value
+        if ( match = requiredHeaders[ header ].exec( headers[header] ) ) {
+          data[header] = match;
+        } else {
+          socket.end();
+          return;
+        }
+      }
+
+      // draft auto-sensing
+      if ( headers["sec-websocket-key1"] && headers["sec-websocket-key2"] && upgradeHead ) { // 76
+        var strkey1 = headers["sec-websocket-key1"]
+          , strkey2 = headers["sec-websocket-key2"]
+
+          , numkey1 = parseInt(strkey1.replace(/[^\d]/g, ""), 10)
+          , numkey2 = parseInt(strkey2.replace(/[^\d]/g, ""), 10)
+
+          , spaces1 = strkey1.replace(/[^\ ]/g, "").length
+          , spaces2 = strkey2.replace(/[^\ ]/g, "").length;
+
+        if (spaces1 == 0 || spaces2 == 0 || numkey1 % spaces1 != 0 || numkey2 % spaces2 != 0) {
+          socket.end();
+          return;
+        }
+
+        var hash = crypto.createHash("md5")
+        , key1 = pack(parseInt(numkey1/spaces1))
+        , key2 = pack(parseInt(numkey2/spaces2));
+        
+        hash.update(key1);
+        hash.update(key2);
+        hash.update(upgradeHead);
+
+        socket.write(nano(handshakeTemplate76, {
+          protocol: wsProtocol,
+          resource: data.get[1],
+          host:     data.host[1],
+          origin:   data.origin[1],
+          data:     hash.digest("binary")
+        }), "binary");
+
+      } else { // 75
+        socket.write(nano(handshakeTemplate75, {
+          protocol: wsProtocol,
+          resource: data.get[1],
+          host:     data.host[1],
+          origin:   data.origin[1]
+        }));
+
+      }
+
+      handshaked = true;
+      emitter.emit("connect", data.get[1]);
+    }
+
+    socket.addListener("data", function (data) {
+      if(handshaked) {
+        handle(data.toString("utf8"));
+      } else {
+        handshake(data.toString("binary")); // because of draft76 handshakes
+      }
+    }).addListener("end", function () {
+      socket.end();
+    }).addListener("close", function () {
+      if (handshaked) { // don't emit close from policy-requests
+        emitter.emit("close");
+      }
+    }).addListener("error", function (exception) {
+      if (emitter.listeners("error").length > 0) {
+        emitter.emit("error", exception);
+      } else {
+        throw exception;
+      }
+    });
+
+    emitter.remoteAddress = socket.remoteAddress;
+    
+    emitter.write = function (data) {
+      try {
+        socket.write('\u0000', 'binary');
+        socket.write(data, 'utf8');
+        socket.write('\uffff', 'binary');
+      } catch(e) { 
+        // Socket not open for writing, 
+        // should get "close" event just before.
+        socket.end();
+      }
+    };
+    
+    emitter.end = function () {
+      socket.end();
+    };
+    
+    websocketListener(emitter); // emits: "connect", "data", "close", provides: write(data), end()
+  });
+};
+
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/xhr.html b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/xhr.html
new file mode 100644
index 0000000..aaa721c
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/xhr.html
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+   <head>
+      <title>Forge XmlHttpRequest Test</title>
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js"></script>
+      <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
+      <script type="text/javascript" src="forge/debug.js"></script>
+      <script type="text/javascript" src="forge/util.js"></script>
+      <script type="text/javascript" src="forge/log.js"></script>
+      <script type="text/javascript" src="forge/task.js"></script>
+      <script type="text/javascript" src="forge/socket.js"></script>
+      <script type="text/javascript" src="forge/md5.js"></script>
+      <script type="text/javascript" src="forge/sha1.js"></script>
+      <script type="text/javascript" src="forge/hmac.js"></script>
+      <script type="text/javascript" src="forge/aes.js"></script>
+      <script type="text/javascript" src="forge/pem.js"></script>
+      <script type="text/javascript" src="forge/asn1.js"></script>
+      <script type="text/javascript" src="forge/jsbn.js"></script>
+      <script type="text/javascript" src="forge/prng.js"></script>
+      <script type="text/javascript" src="forge/random.js"></script>
+      <script type="text/javascript" src="forge/oids.js"></script>
+      <script type="text/javascript" src="forge/rsa.js"></script>
+      <script type="text/javascript" src="forge/pbe.js"></script>
+      <script type="text/javascript" src="forge/x509.js"></script>
+      <script type="text/javascript" src="forge/pki.js"></script>
+      <script type="text/javascript" src="forge/tls.js"></script>
+      <script type="text/javascript" src="forge/aesCipherSuites.js"></script>
+      <script type="text/javascript" src="forge/tlssocket.js"></script>
+      <script type="text/javascript" src="forge/http.js"></script>
+      <script type="text/javascript" src="forge/xhr.js"></script>
+      <script type="text/javascript" src="xhr.js"></script>
+      
+      <link type="text/css" rel="stylesheet" media="all" href="screen.css" />
+      <style type="text/css">
+         .ready { color: inherit; background: inherit; }
+         .testing { color: black; background: yellow; }
+         .pass{ color: white; background: green; }
+         .fail{ color: white; background: red; }
+      </style>
+   </head>
+<body>
+<div class="nav"><a href="index.html">Forge Tests</a> / XHR</div>
+
+<div class="header">
+   <h1>XmlHttpRequest Tests</h1>
+</div>
+
+<div class="content">
+
+<!-- div used to hold the flash socket pool implemenation -->
+<div id="socketPool">
+   <p>Could not load the flash SocketPool.</p>
+</div>
+
+<fieldset class="section">
+   <ul>
+     <li>Use the controls below to test the XHR wrapper.</li>
+   </ul>
+</fieldset>
+
+<fieldset class="section">
+<legend>Control</legend>
+   <button id="start">Start</button>
+   <button id="reset">Reset</button>
+   <br/>
+   <input id="scroll" type="checkbox" checked="checked" />Scroll Tests
+   <br/>
+   <button id="stress">Stress</button>
+</fieldset>
+
+<fieldset class="section">
+<legend>Progress</legend>
+Status: <span id="status">?</span><br/>
+Pass: <span id="pass">?</span>/<span id="total">?</span><br/>
+Fail: <span id="fail">?</span>
+</fieldset>
+
+<fieldset class="section">
+<legend>Tests</legend>
+<div id="tests"></div>
+</fieldset>
+
+</div>
+</body>
+</html>
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/xhr.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/xhr.js
new file mode 100644
index 0000000..78f91ad
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/node-forge/tests/xhr.js
@@ -0,0 +1,590 @@
+/**
+ * Forge XmlHttpRequest Test
+ *
+ * @author Dave Longley
+ * @author David I. Lehn <dlehn@digitalbazaar.com>
+ *
+ * Copyright (c) 2009-2010 Digital Bazaar, Inc. All rights reserved.
+ */
+(function($)
+{
+   // load flash socket pool
+   window.forge.socketPool = {};
+   window.forge.socketPool.ready = function()
+   {
+      // init forge xhr
+      forge.xhr.init({
+         flashId: 'socketPool',
+         policyPort: 19945,
+         msie: $.browser.msie,
+         connections: 10,
+         caCerts: [],
+         verify: function(c, verified, depth, certs)
+         {
+            // don't care about cert verification for test
+            return true;
+         }
+      });
+   };
+   swfobject.embedSWF(
+      'forge/SocketPool.swf', 'socketPool', '0', '0', '9.0.0',
+      false, {}, {allowscriptaccess: 'always'}, {});
+})(jQuery);
+
+jQuery(function($)
+{
+   var cat = 'forge.tests.xhr';
+
+   var tests = [];
+   var passed = 0;
+   var failed = 0;
+
+   var init = function() {
+      passed = failed = 0;
+      $('.ready,.testing,.pass,.fail')
+         .removeClass('ready testing pass fail');
+      $('#status')
+         .text('Ready.')
+         .addClass('ready');
+      $('#total').text(tests.length);
+      $('#pass').text(passed);
+      $('#fail').text(failed);
+      $('.expect').empty();
+      $('.result').empty();
+      $('.time').empty();
+      $('.timePer').empty();
+      $('#start').removeAttr('disabled');
+   };
+
+   var start = function()
+   {
+      $('#start').attr('disabled', 'disabled');
+      // meta! use tasks to run the task tests
+      forge.task.start({
+         type: 'test',
+         run: function(task) {
+            task.next('starting', function(task) {
+               forge.log.debug(cat, 'start');
+               $('#status')
+                  .text('Testing...')
+                  .addClass('testing')
+                  .removeClass('idle');
+            });
+            $.each(tests, function(i, test) {
+               task.next('test', function(task) {
+                  var title = $('li:first', test.container);
+                  if($('#scroll:checked').length === 1)
+                  {
+                     $('html,body').animate({scrollTop: title.offset().top});
+                  }
+                  title.addClass('testing');
+                  test.run(task, test);
+               });
+               task.next('test', function(task) {
+                  $('li:first', test.container).removeClass('testing');
+               });
+            });
+            task.next('success', function(task) {
+               forge.log.debug(cat, 'done');
+               if(failed === 0) {
+                  $('#status')
+                     .text('PASS')
+                     .addClass('pass')
+                     .removeClass('testing');
+               } else {
+                  // FIXME: should just be hitting failure() below
+                  $('#status')
+                     .text('FAIL')
+                     .addClass('fail')
+                     .removeClass('testing');
+               }
+            });
+         },
+         failure: function() {
+            $('#status')
+               .text('FAIL')
+               .addClass('fail')
+               .removeClass('testing');
+         }
+      });
+   };
+
+   $('#start').click(function() {
+      start();
+   });
+
+   $('#reset').click(function() {
+      init();
+   });
+
+   var stressStats =
+   {
+      sent: 0,
+      success: 0,
+      error: 0
+   };
+   var stressStatsMessage = function()
+   {
+      return 'received:' + (stressStats.success + stressStats.error) + '/' +
+         stressStats.sent + ' errors:' + stressStats.error;
+   };
+
+   $('#stress').click(function() {
+      for(var i = 1; i <= 100; ++i)
+      {
+         (function(seqnum)
+         {
+            setTimeout(function()
+            {
+               $.ajax(
+               {
+                  type: 'GET',
+                  url: '/result.txt?seq=' + seqnum,
+                  beforeSend: function(xhr)
+                  {
+                     ++stressStats.sent;
+                     xhr.setRequestHeader('Connection', 'close');
+                  },
+                  success: function(data, textStatus, xhr)
+                  {
+                     ++stressStats.success;
+                     forge.log.debug(cat, 'xhr connection completed' +
+                        ' seq:' + seqnum +
+                        ' datalen:' + data.length + ' ' +
+                        stressStatsMessage());
+                  },
+                  error: function(xhr, textStatus, errorThrown)
+                  {
+                     ++stressStats.error;
+                     forge.log.error(cat, 'xhr connection failed' +
+                        ' seq:' + seqnum + ' ' +
+                        stressStatsMessage(), arguments);
+                  },
+                  xhr: forge.xhr.create
+               });
+            }, 0);
+         })(i);
+      }
+      return false;
+   });
+
+   /**
+    * Creates a simple XMLHttpRequest wrapper. For testing.
+    */
+   var createWrapper = function()
+   {
+      var UNSENT = 0;
+      var OPENED = 1;
+      var HEADERS_RECEIVED = 2;
+      var LOADING = 3;
+      var DONE = 4;
+
+      var toWrap = new XMLHttpRequest();
+
+      // create xhr wrapper object
+      var xhr =
+      {
+         // FIXME: an EventListener
+         onreadystatechange: null,
+         // FIXME: readonly
+         readyState: UNSENT,
+         // FIXME: a string
+         responseText: null,
+         // FIXME: a document
+         responseXML: null,
+         // FIXME: readonly, returns the HTTP status code
+         status: 0,
+         // FIXME: readonly, returns the HTTP status message
+         statusText: null,
+
+         // FIXME: async, user, and password are optional
+         open: function(method, url, async, user, password)
+         {
+            toWrap.open(method, url, async, user, password);
+         },
+
+         setRequestHeader: function(header, value)
+         {
+            toWrap.setRequestHeader(header, value);
+         },
+
+         // FIXME: data can be a string or a document
+         send: function(data)
+         {
+            toWrap.send(data);
+         },
+
+         abort: function()
+         {
+            toWrap.abort();
+            toWrap.onreadystatechange = null;
+            toWrap = null;
+         },
+
+         // FIXME: return all response headers as a string
+         getAllResponseHeaders: function()
+         {
+            return toWrap.getAllResponseHeaders();
+         },
+
+         // FIXME: return header field value
+         getResponseHeader: function(header)
+         {
+            return toWrap.getResponseHeader(header);
+         }
+      };
+
+      toWrap.onreadystatechange = function()
+      {
+         // copy attributes
+         xhr.readyState = toWrap.readyState;
+         xhr.responseText = toWrap.responseText;
+         xhr.responseXML = toWrap.responseXML;
+
+         if(toWrap.readyState == HEADERS_RECEIVED)
+         {
+            xhr.status = toWrap.status;
+            xhr.statusText = toWrap.statusText;
+         }
+
+         if(xhr.onreadystatechange)
+         {
+            //forge.log.debug(cat, 'wrapper orsc', toWrap);
+            xhr.onreadystatechange();
+         }
+      };
+
+      return xhr;
+   };
+
+   var addTest = function(name, run)
+   {
+      var container = $('<ul><li>Test ' + name + '</li><ul/></ul>');
+      var expect = $('<li>Expect: <span class="expect"/></li>');
+      var result = $('<li>Result: <span class="result"/></li>');
+      var time = $('<li>Time: <span class="time"/></li>');
+      var timePer = $('<li>Time Per Iteration: <span class="timePer"/></li>');
+      $('ul', container)
+         .append(expect)
+         .append(result)
+         .append(time)
+         .append(timePer);
+      $('#tests').append(container);
+      var test = {
+         container: container,
+         startTime: null,
+         run: function(task, test) {
+            test.startTime = new Date();
+            run(task, test);
+         },
+         expect: $('span', expect),
+         result: $('span', result),
+         check: function() {
+            var e = test.expect.text();
+            var r = test.result.text();
+            (e == r) ? test.pass() : test.fail();
+         },
+         pass: function(iterations) {
+            var dt = new Date() - test.startTime;
+            if(!iterations)
+            {
+               iterations = 1;
+            }
+            var dti = (dt / iterations);
+            passed += 1;
+            $('#pass').text(passed);
+            $('li:first', container).addClass('pass');
+            $('span.time', container).html(dt + 'ms');
+            $('span.timePer', container).html(dti + 'ms');
+         },
+         fail: function(iterations) {
+            var dt = new Date() - test.startTime;
+            if(!iterations)
+            {
+               iterations = 1;
+            }
+            var dti = (dt / iterations);
+            failed += 1;
+            $('#fail').text(failed);
+            $('li:first', container).addClass('fail');
+            $('span.time', container).html(dt + 'ms');
+            $('span.timePer', container).html(dti + 'ms');
+         }
+      };
+      tests.push(test);
+   };
+
+   addTest('builtin xhr', function(task, test)
+   {
+      task.block();
+
+      $.ajax(
+      {
+         type: 'GET',
+         url: '/result.txt',
+         success: function(data)
+         {
+            test.expect.html('expected result');
+            test.result.html(data);
+            task.unblock();
+         },
+         error: function()
+         {
+            task.fail();
+         }
+      });
+
+      task.next(function(task)
+      {
+         test.pass();
+      });
+   });
+
+   addTest('builtin xhr (10 serial)', function(task, test)
+   {
+      var N = 10;
+      for(var i = 0; i < N; i++)
+      {
+         task.next(function(task)
+         {
+            task.parent.block();
+
+            $.ajax(
+            {
+               type: 'GET',
+               url: '/result.txt',
+               success: function(data, textStatus)
+               {
+                  test.result.append('.');
+                  task.parent.unblock();
+               },
+               error: function(xhr, textStatus, errorThrown)
+               {
+                  task.fail(N);
+               }
+            });
+         });
+      }
+
+      task.next(function(task)
+      {
+         test.pass(N);
+      });
+   });
+
+   addTest('builtin xhr (10 parallel)', function(task, test)
+   {
+      var N = 10;
+      task.block(N);
+      for(var i = 0; i < N; i++)
+      {
+         $.ajax(
+         {
+            type: 'GET',
+            url: '/result.txt',
+            success: function(data, textStatus)
+            {
+               test.result.append('.');
+               task.unblock();
+            },
+            error: function(xhr, textStatus, errorThrown)
+            {
+               task.fail(N);
+            }
+         });
+      }
+
+      task.next(function(task)
+      {
+         test.pass(N);
+      });
+   });
+
+   // test only works with non-IE
+   if(!$.browser.msie)
+   {
+      addTest('generic wrapper xhr', function(task, test)
+      {
+         task.block();
+
+         $.ajax(
+         {
+            type: 'GET',
+            url: '/result.txt',
+            success: function(data)
+            {
+               test.expect.html('expected result');
+               test.result.html(data);
+               task.unblock();
+            },
+            error: function()
+            {
+               task.fail();
+            },
+            xhr: createWrapper
+         });
+
+         task.next(function(task)
+         {
+            test.pass();
+         });
+      });
+
+      addTest('generic wrapper xhr (10 serial)', function(task, test)
+      {
+         var N = 10;
+         for(var i = 0; i < N; i++)
+         {
+            task.next(function(task)
+            {
+               task.parent.block();
+
+               $.ajax(
+               {
+                  type: 'GET',
+                  url: '/result.txt',
+                  success: function(data, textStatus)
+                  {
+                     test.result.append('.');
+                     task.parent.unblock();
+                  },
+                  error: function(xhr, textStatus, errorThrown)
+                  {
+                     task.fail(N);
+                  },
+                  xhr: createWrapper
+               });
+            });
+         }
+
+         task.next(function(task)
+         {
+            test.pass(N);
+         });
+      });
+
+      addTest('generic wrapper xhr (10 parallel)', function(task, test)
+      {
+         var N = 10;
+         task.block(N);
+         for(var i = 0; i < N; i++)
+         {
+            $.ajax(
+            {
+               type: 'GET',
+               url: '/result.txt',
+               success: function(data, textStatus)
+               {
+                  test.result.append('.');
+                  task.unblock();
+               },
+               error: function(xhr, textStatus, errorThrown)
+               {
+                  task.fail(N);
+               },
+               xhr: createWrapper
+            });
+         }
+
+         task.next(function(task)
+         {
+            test.pass(N);
+         });
+      });
+   }
+
+   for(var i = 0; i < 3; i++) {
+   addTest('TLS xhr ' + i, function(task, test)
+   {
+      task.block();
+
+      $.ajax(
+      {
+         type: 'GET',
+         url: '/result.txt',
+         success: function(data, textStatus, xhr)
+         {
+            test.expect.html('expected result');
+            test.result.html(data);
+            task.unblock();
+         },
+         error: function(xhr, textStatus, errorThrown)
+         {
+            task.fail();
+         },
+         xhr: forge.xhr.create
+      });
+
+      task.next(function(task)
+      {
+         test.pass();
+      });
+   });
+   }
+
+   addTest('TLS xhr (10 serial)', function(task, test)
+   {
+      var N = 10;
+      for(var i = 0; i < N; i++)
+      {
+         task.next(function(task)
+         {
+            task.parent.block();
+
+            $.ajax(
+            {
+               type: 'GET',
+               url: '/result.txt',
+               success: function(data, textStatus, xhr)
+               {
+                  test.result.append('.');
+                  task.parent.unblock();
+               },
+               error: function(xhr, textStatus, errorThrown)
+               {
+                  task.fail(N);
+               },
+               xhr: forge.xhr.create
+            });
+         });
+      }
+
+      task.next(function(task)
+      {
+         test.pass(N);
+      });
+   });
+
+   addTest('TLS xhr (10 parallel) ' +
+      '(hit "Reset" then "Start" to speed up - uses SSL session cache)',
+      function(task, test)
+   {
+      var N = 10;
+      task.block(N);
+      for(var i = 0; i < N; i++)
+      {
+         $.ajax(
+         {
+            type: 'GET',
+            url: '/result.txt',
+            success: function(data, textStatus, xhr)
+            {
+               test.result.append('.');
+               task.unblock();
+            },
+            error: function(xhr, textStatus, errorThrown)
+            {
+               task.fail(N);
+            },
+            xhr: forge.xhr.create
+         });
+      }
+
+      task.next(function(task)
+      {
+         test.pass(N);
+      });
+   });
+
+   init();
+});
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/.npmignore b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/.npmignore
new file mode 100644
index 0000000..13abef4
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/.npmignore
@@ -0,0 +1,3 @@
+node_modules
+node_modules/*
+npm_debug.log
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/.travis.yml b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/.travis.yml
new file mode 100644
index 0000000..dad2273
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/.travis.yml
@@ -0,0 +1,4 @@
+language: node_js
+node_js:
+  - 0.8
+  - "0.10"
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/LICENCE b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/LICENCE
new file mode 100644
index 0000000..171dd97
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/LICENCE
@@ -0,0 +1,22 @@
+Copyright (c) 2011 Dominic Tarr
+
+Permission is hereby granted, free of charge, 
+to any person obtaining a copy of this software and 
+associated documentation files (the "Software"), to 
+deal in the Software without restriction, including 
+without limitation the rights to use, copy, modify, 
+merge, publish, distribute, sublicense, and/or sell 
+copies of the Software, and to permit persons to whom 
+the Software is furnished to do so, 
+subject to the following conditions:
+
+The above copyright notice and this permission notice 
+shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 
+ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/examples/pretty.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/examples/pretty.js
new file mode 100644
index 0000000..2e89131
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/examples/pretty.js
@@ -0,0 +1,26 @@
+
+var inspect = require('util').inspect
+var es      = require('event-stream')     //load event-stream
+var split   = require('../')
+
+if(!module.parent) {
+  es.pipe(                            //pipe joins streams together
+    process.openStdin(),              //open stdin
+    split(),                       //split stream to break on newlines
+    es.map(function (data, callback) {//turn this async function into a stream
+      var j 
+      try {
+        j = JSON.parse(data)          //try to parse input into json
+      } catch (err) {
+        return callback(null, data)   //if it fails just pass it anyway
+      }
+      callback(null, inspect(j))      //render it nicely
+    }),
+    process.stdout                    // pipe it to stdout !
+    )
+  }
+  
+// run this
+// 
+// curl -sS registry.npmjs.org/event-stream | node pretty.js 
+//
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/index.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/index.js
new file mode 100644
index 0000000..ca57e0f
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/index.js
@@ -0,0 +1,59 @@
+//filter will reemit the data if cb(err,pass) pass is truthy
+
+// reduce is more tricky
+// maybe we want to group the reductions or emit progress updates occasionally
+// the most basic reduce just emits one 'data' event after it has recieved 'end'
+
+
+var through = require('through')
+var Decoder = require('string_decoder').StringDecoder
+
+module.exports = split
+
+//TODO pass in a function to map across the lines.
+
+function split (matcher, mapper) {
+  var decoder = new Decoder()
+  var soFar = ''
+  if('function' === typeof matcher)
+    mapper = matcher, matcher = null
+  if (!matcher)
+    matcher = /\r?\n/
+
+  function emit(stream, piece) {
+    if(mapper) {
+      try {
+        piece = mapper(piece)
+      }
+      catch (err) {
+        return stream.emit('error', err)
+      }
+      if('undefined' !== typeof piece)
+        stream.queue(piece)
+    }
+    else
+      stream.queue(piece)
+  }
+
+  function next (stream, buffer) { 
+    var pieces = (soFar + buffer).split(matcher)
+    soFar = pieces.pop()
+
+    for (var i = 0; i < pieces.length; i++) {
+      var piece = pieces[i]
+      emit(stream, piece)
+    }
+  }
+
+  return through(function (b) {
+    next(this, decoder.write(b))
+  },
+  function () {
+    if(decoder.end) 
+      next(this, decoder.end())
+    if(soFar != null)
+      emit(this, soFar)
+    this.queue(null)
+  })
+}
+
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/node_modules/through/.travis.yml b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/node_modules/through/.travis.yml
new file mode 100644
index 0000000..c693a93
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/node_modules/through/.travis.yml
@@ -0,0 +1,5 @@
+language: node_js
+node_js:
+  - 0.6
+  - 0.8
+  - "0.10"
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/node_modules/through/LICENSE.APACHE2 b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/node_modules/through/LICENSE.APACHE2
new file mode 100644
index 0000000..6366c04
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/node_modules/through/LICENSE.APACHE2
@@ -0,0 +1,15 @@
+Apache License, Version 2.0
+
+Copyright (c) 2011 Dominic Tarr
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/node_modules/through/LICENSE.MIT b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/node_modules/through/LICENSE.MIT
new file mode 100644
index 0000000..6eafbd7
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/node_modules/through/LICENSE.MIT
@@ -0,0 +1,24 @@
+The MIT License
+
+Copyright (c) 2011 Dominic Tarr
+
+Permission is hereby granted, free of charge, 
+to any person obtaining a copy of this software and 
+associated documentation files (the "Software"), to 
+deal in the Software without restriction, including 
+without limitation the rights to use, copy, modify, 
+merge, publish, distribute, sublicense, and/or sell 
+copies of the Software, and to permit persons to whom 
+the Software is furnished to do so, 
+subject to the following conditions:
+
+The above copyright notice and this permission notice 
+shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 
+ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/node_modules/through/index.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/node_modules/through/index.js
new file mode 100644
index 0000000..7b935bf
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/node_modules/through/index.js
@@ -0,0 +1,108 @@
+var Stream = require('stream')
+
+// through
+//
+// a stream that does nothing but re-emit the input.
+// useful for aggregating a series of changing but not ending streams into one stream)
+
+exports = module.exports = through
+through.through = through
+
+//create a readable writable stream.
+
+function through (write, end, opts) {
+  write = write || function (data) { this.queue(data) }
+  end = end || function () { this.queue(null) }
+
+  var ended = false, destroyed = false, buffer = [], _ended = false
+  var stream = new Stream()
+  stream.readable = stream.writable = true
+  stream.paused = false
+
+//  stream.autoPause   = !(opts && opts.autoPause   === false)
+  stream.autoDestroy = !(opts && opts.autoDestroy === false)
+
+  stream.write = function (data) {
+    write.call(this, data)
+    return !stream.paused
+  }
+
+  function drain() {
+    while(buffer.length && !stream.paused) {
+      var data = buffer.shift()
+      if(null === data)
+        return stream.emit('end')
+      else
+        stream.emit('data', data)
+    }
+  }
+
+  stream.queue = stream.push = function (data) {
+//    console.error(ended)
+    if(_ended) return stream
+    if(data == null) _ended = true
+    buffer.push(data)
+    drain()
+    return stream
+  }
+
+  //this will be registered as the first 'end' listener
+  //must call destroy next tick, to make sure we're after any
+  //stream piped from here.
+  //this is only a problem if end is not emitted synchronously.
+  //a nicer way to do this is to make sure this is the last listener for 'end'
+
+  stream.on('end', function () {
+    stream.readable = false
+    if(!stream.writable && stream.autoDestroy)
+      process.nextTick(function () {
+        stream.destroy()
+      })
+  })
+
+  function _end () {
+    stream.writable = false
+    end.call(stream)
+    if(!stream.readable && stream.autoDestroy)
+      stream.destroy()
+  }
+
+  stream.end = function (data) {
+    if(ended) return
+    ended = true
+    if(arguments.length) stream.write(data)
+    _end() // will emit or queue
+    return stream
+  }
+
+  stream.destroy = function () {
+    if(destroyed) return
+    destroyed = true
+    ended = true
+    buffer.length = 0
+    stream.writable = stream.readable = false
+    stream.emit('close')
+    return stream
+  }
+
+  stream.pause = function () {
+    if(stream.paused) return
+    stream.paused = true
+    return stream
+  }
+
+  stream.resume = function () {
+    if(stream.paused) {
+      stream.paused = false
+      stream.emit('resume')
+    }
+    drain()
+    //may have become paused again,
+    //as drain emits 'data'.
+    if(!stream.paused)
+      stream.emit('drain')
+    return stream
+  }
+  return stream
+}
+
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/node_modules/through/package.json b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/node_modules/through/package.json
new file mode 100644
index 0000000..fe9fbdb
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/node_modules/through/package.json
@@ -0,0 +1,65 @@
+{
+  "name": "through",
+  "version": "2.3.6",
+  "description": "simplified stream construction",
+  "main": "index.js",
+  "scripts": {
+    "test": "set -e; for t in test/*.js; do node $t; done"
+  },
+  "devDependencies": {
+    "stream-spec": "~0.3.5",
+    "tape": "~2.3.2",
+    "from": "~0.1.3"
+  },
+  "keywords": [
+    "stream",
+    "streams",
+    "user-streams",
+    "pipe"
+  ],
+  "author": {
+    "name": "Dominic Tarr",
+    "email": "dominic.tarr@gmail.com",
+    "url": "dominictarr.com"
+  },
+  "license": "MIT",
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/dominictarr/through.git"
+  },
+  "homepage": "http://github.com/dominictarr/through",
+  "testling": {
+    "browsers": [
+      "ie/8..latest",
+      "ff/15..latest",
+      "chrome/20..latest",
+      "safari/5.1..latest"
+    ],
+    "files": "test/*.js"
+  },
+  "gitHead": "19ed9b7e84efe7c3e3c8be80f29390b1620e13c0",
+  "bugs": {
+    "url": "https://github.com/dominictarr/through/issues"
+  },
+  "_id": "through@2.3.6",
+  "_shasum": "26681c0f524671021d4e29df7c36bce2d0ecf2e8",
+  "_from": "through@2",
+  "_npmVersion": "1.4.26",
+  "_npmUser": {
+    "name": "dominictarr",
+    "email": "dominic.tarr@gmail.com"
+  },
+  "maintainers": [
+    {
+      "name": "dominictarr",
+      "email": "dominic.tarr@gmail.com"
+    }
+  ],
+  "dist": {
+    "shasum": "26681c0f524671021d4e29df7c36bce2d0ecf2e8",
+    "tarball": "http://registry.npmjs.org/through/-/through-2.3.6.tgz"
+  },
+  "directories": {},
+  "_resolved": "https://registry.npmjs.org/through/-/through-2.3.6.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/node_modules/through/readme.markdown b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/node_modules/through/readme.markdown
new file mode 100644
index 0000000..cb34c81
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/node_modules/through/readme.markdown
@@ -0,0 +1,64 @@
+#through
+
+[![build status](https://secure.travis-ci.org/dominictarr/through.png)](http://travis-ci.org/dominictarr/through)
+[![testling badge](https://ci.testling.com/dominictarr/through.png)](https://ci.testling.com/dominictarr/through)
+
+Easy way to create a `Stream` that is both `readable` and `writable`. 
+
+* Pass in optional `write` and `end` methods.
+* `through` takes care of pause/resume logic if you use `this.queue(data)` instead of `this.emit('data', data)`.
+* Use `this.pause()` and `this.resume()` to manage flow.
+* Check `this.paused` to see current flow state. (`write` always returns `!this.paused`).
+
+This function is the basis for most of the synchronous streams in 
+[event-stream](http://github.com/dominictarr/event-stream).
+
+``` js
+var through = require('through')
+
+through(function write(data) {
+    this.queue(data) //data *must* not be null
+  },
+  function end () { //optional
+    this.queue(null)
+  })
+```
+
+Or, can also be used _without_ buffering on pause, use `this.emit('data', data)`,
+and this.emit('end')
+
+``` js
+var through = require('through')
+
+through(function write(data) {
+    this.emit('data', data)
+    //this.pause() 
+  },
+  function end () { //optional
+    this.emit('end')
+  })
+```
+
+## Extended Options
+
+You will probably not need these 99% of the time.
+
+### autoDestroy=false
+
+By default, `through` emits close when the writable
+and readable side of the stream has ended.
+If that is not desired, set `autoDestroy=false`.
+
+``` js
+var through = require('through')
+
+//like this
+var ts = through(write, end, {autoDestroy: false})
+//or like this
+var ts = through(write, end)
+ts.autoDestroy = false
+```
+
+## License
+
+MIT / Apache2
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/node_modules/through/test/async.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/node_modules/through/test/async.js
new file mode 100644
index 0000000..46bdbae
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/node_modules/through/test/async.js
@@ -0,0 +1,28 @@
+var from = require('from')
+var through = require('../')
+
+var tape = require('tape')
+
+tape('simple async example', function (t) {
+ 
+  var n = 0, expected = [1,2,3,4,5], actual = []
+  from(expected)
+  .pipe(through(function(data) {
+    this.pause()
+    n ++
+    setTimeout(function(){
+      console.log('pushing data', data)
+      this.push(data)
+      this.resume()
+    }.bind(this), 300)
+  })).pipe(through(function(data) {
+    console.log('pushing data second time', data);
+    this.push(data)
+  })).on('data', function (d) {
+    actual.push(d)
+  }).on('end', function() {
+    t.deepEqual(actual, expected)
+    t.end()
+  })
+
+})
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/node_modules/through/test/auto-destroy.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/node_modules/through/test/auto-destroy.js
new file mode 100644
index 0000000..9a8fd00
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/node_modules/through/test/auto-destroy.js
@@ -0,0 +1,30 @@
+var test = require('tape')
+var through = require('../')
+
+// must emit end before close.
+
+test('end before close', function (assert) {
+  var ts = through()
+  ts.autoDestroy = false
+  var ended = false, closed = false
+
+  ts.on('end', function () {
+    assert.ok(!closed)
+    ended = true
+  })
+  ts.on('close', function () {
+    assert.ok(ended)
+    closed = true
+  })
+
+  ts.write(1)
+  ts.write(2)
+  ts.write(3)
+  ts.end()
+  assert.ok(ended)
+  assert.notOk(closed)
+  ts.destroy()
+  assert.ok(closed)
+  assert.end()
+})
+
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/node_modules/through/test/buffering.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/node_modules/through/test/buffering.js
new file mode 100644
index 0000000..b0084bf
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/node_modules/through/test/buffering.js
@@ -0,0 +1,71 @@
+var test = require('tape')
+var through = require('../')
+
+// must emit end before close.
+
+test('buffering', function(assert) {
+  var ts = through(function (data) {
+    this.queue(data)
+  }, function () {
+    this.queue(null)
+  })
+
+  var ended = false,  actual = []
+
+  ts.on('data', actual.push.bind(actual))
+  ts.on('end', function () {
+    ended = true
+  })
+
+  ts.write(1)
+  ts.write(2)
+  ts.write(3)
+  assert.deepEqual(actual, [1, 2, 3])
+  ts.pause()
+  ts.write(4)
+  ts.write(5)
+  ts.write(6)
+  assert.deepEqual(actual, [1, 2, 3])
+  ts.resume()
+  assert.deepEqual(actual, [1, 2, 3, 4, 5, 6])
+  ts.pause()
+  ts.end()
+  assert.ok(!ended)
+  ts.resume()
+  assert.ok(ended)
+  assert.end()
+})
+
+test('buffering has data in queue, when ends', function (assert) {
+
+  /*
+   * If stream ends while paused with data in the queue,
+   * stream should still emit end after all data is written
+   * on resume.
+   */
+
+  var ts = through(function (data) {
+    this.queue(data)
+  }, function () {
+    this.queue(null)
+  })
+
+  var ended = false,  actual = []
+
+  ts.on('data', actual.push.bind(actual))
+  ts.on('end', function () {
+    ended = true
+  })
+
+  ts.pause()
+  ts.write(1)
+  ts.write(2)
+  ts.write(3)
+  ts.end()
+  assert.deepEqual(actual, [], 'no data written yet, still paused')
+  assert.ok(!ended, 'end not emitted yet, still paused')
+  ts.resume()
+  assert.deepEqual(actual, [1, 2, 3], 'resumed, all data should be delivered')
+  assert.ok(ended, 'end should be emitted once all data was delivered')
+  assert.end();
+})
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/node_modules/through/test/end.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/node_modules/through/test/end.js
new file mode 100644
index 0000000..fa113f5
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/node_modules/through/test/end.js
@@ -0,0 +1,45 @@
+var test = require('tape')
+var through = require('../')
+
+// must emit end before close.
+
+test('end before close', function (assert) {
+  var ts = through()
+  var ended = false, closed = false
+
+  ts.on('end', function () {
+    assert.ok(!closed)
+    ended = true
+  })
+  ts.on('close', function () {
+    assert.ok(ended)
+    closed = true
+  })
+
+  ts.write(1)
+  ts.write(2)
+  ts.write(3)
+  ts.end()
+  assert.ok(ended)
+  assert.ok(closed)
+  assert.end()
+})
+
+test('end only once', function (t) {
+
+  var ts = through()
+  var ended = false, closed = false
+
+  ts.on('end', function () {
+    t.equal(ended, false)
+    ended = true
+  })
+
+  ts.queue(null)
+  ts.queue(null)
+  ts.queue(null)
+
+  ts.resume()
+
+  t.end()
+})
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/node_modules/through/test/index.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/node_modules/through/test/index.js
new file mode 100644
index 0000000..8d1517e
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/node_modules/through/test/index.js
@@ -0,0 +1,117 @@
+
+var test = require('tape')
+var spec = require('stream-spec')
+var through = require('../')
+
+/*
+  I'm using these two functions, and not streams and pipe
+  so there is less to break. if this test fails it must be
+  the implementation of _through_
+*/
+
+function write(array, stream) {
+  array = array.slice()
+  function next() {
+    while(array.length)
+      if(stream.write(array.shift()) === false)
+        return stream.once('drain', next)
+    
+    stream.end()
+  }
+
+  next()
+}
+
+function read(stream, callback) {
+  var actual = []
+  stream.on('data', function (data) {
+    actual.push(data)
+  })
+  stream.once('end', function () {
+    callback(null, actual)
+  })
+  stream.once('error', function (err) {
+    callback(err)
+  })
+}
+
+test('simple defaults', function(assert) {
+
+  var l = 1000
+    , expected = []
+
+  while(l--) expected.push(l * Math.random())
+
+  var t = through()
+  var s = spec(t).through().pausable()
+
+  read(t, function (err, actual) {
+    assert.ifError(err)
+    assert.deepEqual(actual, expected)
+    assert.end()
+  })
+
+  t.on('close', s.validate)
+
+  write(expected, t)
+});
+
+test('simple functions', function(assert) {
+
+  var l = 1000
+    , expected = [] 
+
+  while(l--) expected.push(l * Math.random())
+
+  var t = through(function (data) {
+      this.emit('data', data*2)
+    }) 
+  var s = spec(t).through().pausable()
+      
+
+  read(t, function (err, actual) {
+    assert.ifError(err)
+    assert.deepEqual(actual, expected.map(function (data) {
+      return data*2
+    }))
+    assert.end()
+  })
+
+  t.on('close', s.validate)
+
+  write(expected, t)
+})
+
+test('pauses', function(assert) {
+
+  var l = 1000
+    , expected = [] 
+
+  while(l--) expected.push(l) //Math.random())
+
+  var t = through()    
+ 
+  var s = spec(t)
+      .through()
+      .pausable()
+
+  t.on('data', function () {
+    if(Math.random() > 0.1) return
+    t.pause()
+    process.nextTick(function () {
+      t.resume()
+    })
+  })
+
+  read(t, function (err, actual) {
+    assert.ifError(err)
+    assert.deepEqual(actual, expected)
+  })
+
+  t.on('close', function () {
+    s.validate()
+    assert.end()
+  })
+
+  write(expected, t)
+})
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/package.json b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/package.json
new file mode 100644
index 0000000..c726916
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/package.json
@@ -0,0 +1,39 @@
+{
+  "name": "split",
+  "version": "0.2.10",
+  "description": "split a Text Stream into a Line Stream",
+  "homepage": "http://github.com/dominictarr/split",
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/dominictarr/split.git"
+  },
+  "dependencies": {
+    "through": "2"
+  },
+  "devDependencies": {
+    "asynct": "*",
+    "it-is": "1",
+    "ubelt": "~2.9",
+    "stream-spec": "~0.2",
+    "event-stream": "~3.0.2"
+  },
+  "scripts": {
+    "test": "asynct test/"
+  },
+  "author": {
+    "name": "Dominic Tarr",
+    "email": "dominic.tarr@gmail.com",
+    "url": "http://bit.ly/dominictarr"
+  },
+  "optionalDependencies": {},
+  "engines": {
+    "node": "*"
+  },
+  "readme": "# Split (matcher)\n\n[![build status](https://secure.travis-ci.org/dominictarr/split.png)](http://travis-ci.org/dominictarr/split)\n\nBreak up a stream and reassemble it so that each line is a chunk. matcher may be a `String`, or a `RegExp` \n\nExample, read every line in a file ...\n\n``` js\n  fs.createReadStream(file)\n    .pipe(split())\n    .on('data', function (line) {\n      //each chunk now is a seperate line!\n    })\n\n```\n\n`split` takes the same arguments as `string.split` except it defaults to '/\\r?\\n/' instead of ',', and the optional `limit` paremeter is ignored.\n[String#split](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/split)\n\n# NDJ - Newline Delimited Json\n\n`split` accepts a function which transforms each line.\n\n``` js\nfs.createReadStream(file)\n  .pipe(split(JSON.parse))\n  .on('data', function (obj) {\n    //each chunk now is a a js object\n  })\n  .on('error', function (err) {\n    //syntax errors will land here\n    //note, this ends the stream.\n  })\n```\n\n# License\n\nMIT\n",
+  "readmeFilename": "readme.markdown",
+  "bugs": {
+    "url": "https://github.com/dominictarr/split/issues"
+  },
+  "_id": "split@0.2.10",
+  "_from": "split@~0.2.10"
+}
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/readme.markdown b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/readme.markdown
new file mode 100644
index 0000000..55b8ba8
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/readme.markdown
@@ -0,0 +1,39 @@
+# Split (matcher)
+
+[![build status](https://secure.travis-ci.org/dominictarr/split.png)](http://travis-ci.org/dominictarr/split)
+
+Break up a stream and reassemble it so that each line is a chunk. matcher may be a `String`, or a `RegExp` 
+
+Example, read every line in a file ...
+
+``` js
+  fs.createReadStream(file)
+    .pipe(split())
+    .on('data', function (line) {
+      //each chunk now is a seperate line!
+    })
+
+```
+
+`split` takes the same arguments as `string.split` except it defaults to '/\r?\n/' instead of ',', and the optional `limit` paremeter is ignored.
+[String#split](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/split)
+
+# NDJ - Newline Delimited Json
+
+`split` accepts a function which transforms each line.
+
+``` js
+fs.createReadStream(file)
+  .pipe(split(JSON.parse))
+  .on('data', function (obj) {
+    //each chunk now is a a js object
+  })
+  .on('error', function (err) {
+    //syntax errors will land here
+    //note, this ends the stream.
+  })
+```
+
+# License
+
+MIT
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/test/partitioned_unicode.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/test/partitioned_unicode.js
new file mode 100644
index 0000000..aff3d5d
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/test/partitioned_unicode.js
@@ -0,0 +1,34 @@
+var it = require('it-is').style('colour')
+  , split = require('..')
+
+exports ['split data with partitioned unicode character'] = function (test) {
+  var s = split(/,/g)
+    , caughtError = false
+    , rows = []
+
+  s.on('error', function (err) {
+    caughtError = true
+  })
+ 
+  s.on('data', function (row) { rows.push(row) })
+
+  var x = 'テスト試験今日とても,よい天気で'
+  unicodeData = new Buffer(x);
+
+  // partition of 日
+  piece1 = unicodeData.slice(0, 20);
+  piece2 = unicodeData.slice(20, unicodeData.length);
+
+  s.write(piece1);
+  s.write(piece2);
+
+  s.end()
+
+  it(caughtError).equal(false)
+
+  it(rows).deepEqual(['テスト試験今日とても', 'よい天気で']);
+
+  it(rows).deepEqual(x.split(','))
+
+  test.done()
+}
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/test/split.asynct.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/test/split.asynct.js
new file mode 100644
index 0000000..fb15b28
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/test/split.asynct.js
@@ -0,0 +1,85 @@
+var es = require('event-stream')
+  , it = require('it-is').style('colour')
+  , d = require('ubelt')
+  , split = require('..')
+  , join = require('path').join
+  , fs = require('fs')
+  , Stream = require('stream').Stream
+  , spec = require('stream-spec')
+
+exports ['split() works like String#split'] = function (test) {
+  var readme = join(__filename)
+    , expected = fs.readFileSync(readme, 'utf-8').split('\n')
+    , cs = split()
+    , actual = []
+    , ended = false
+    , x = spec(cs).through()
+
+  var a = new Stream ()
+  
+  a.write = function (l) {
+    actual.push(l.trim())
+  }
+  a.end = function () {
+
+      ended = true
+      expected.forEach(function (v,k) {
+        //String.split will append an empty string ''
+        //if the string ends in a split pattern.
+        //es.split doesn't which was breaking this test.
+        //clearly, appending the empty string is correct.
+        //tests are passing though. which is the current job.
+        if(v)
+          it(actual[k]).like(v)
+      })
+      //give the stream time to close
+      process.nextTick(function () {
+        test.done()
+        x.validate()
+      })
+  }
+  a.writable = true
+  
+  fs.createReadStream(readme, {flags: 'r'}).pipe(cs)
+  cs.pipe(a) 
+  
+}
+
+exports ['split() takes mapper function'] = function (test) {
+  var readme = join(__filename)
+    , expected = fs.readFileSync(readme, 'utf-8').split('\n')
+    , cs = split(function (line) { return line.toUpperCase() })
+    , actual = []
+    , ended = false
+    , x = spec(cs).through()
+
+  var a = new Stream ()
+  
+  a.write = function (l) {
+    actual.push(l.trim())
+  }
+  a.end = function () {
+
+      ended = true
+      expected.forEach(function (v,k) {
+        //String.split will append an empty string ''
+        //if the string ends in a split pattern.
+        //es.split doesn't which was breaking this test.
+        //clearly, appending the empty string is correct.
+        //tests are passing though. which is the current job.
+        if(v)
+          it(actual[k]).equal(v.trim().toUpperCase())
+      })
+      //give the stream time to close
+      process.nextTick(function () {
+        test.done()
+        x.validate()
+      })
+  }
+  a.writable = true
+  
+  fs.createReadStream(readme, {flags: 'r'}).pipe(cs)
+  cs.pipe(a) 
+  
+}
+
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/test/try_catch.asynct.js b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/test/try_catch.asynct.js
new file mode 100644
index 0000000..39e49f7
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/node_modules/split/test/try_catch.asynct.js
@@ -0,0 +1,51 @@
+var it = require('it-is').style('colour')
+  , split = require('..')
+
+exports ['emit mapper exceptions as error events'] = function (test) {
+  var s = split(JSON.parse)
+    , caughtError = false
+    , rows = []
+ 
+  s.on('error', function (err) {
+    caughtError = true
+  })
+ 
+  s.on('data', function (row) { rows.push(row) })
+
+  s.write('{"a":1}\n{"')
+  it(caughtError).equal(false)
+  it(rows).deepEqual([ { a: 1 } ])
+
+  s.write('b":2}\n{"c":}\n')
+  it(caughtError).equal(true)
+  it(rows).deepEqual([ { a: 1 }, { b: 2 } ])
+
+  s.end()
+  test.done()
+}
+
+exports ['mapper error events on trailing chunks'] = function (test) {
+  var s = split(JSON.parse)
+    , caughtError = false
+    , rows = []
+ 
+  s.on('error', function (err) {
+    caughtError = true
+  })
+ 
+  s.on('data', function (row) { rows.push(row) })
+
+  s.write('{"a":1}\n{"')
+  it(caughtError).equal(false)
+  it(rows).deepEqual([ { a: 1 } ])
+
+  s.write('b":2}\n{"c":}')
+  it(caughtError).equal(false)
+  it(rows).deepEqual([ { a: 1 }, { b: 2 } ])
+
+  s.end()
+  it(caughtError).equal(true)
+  it(rows).deepEqual([ { a: 1 }, { b: 2 } ])
+
+  test.done()
+}
diff --git a/node_modules/node-firefox-forward-ports/node_modules/adbkit/package.json b/node_modules/node-firefox-forward-ports/node_modules/adbkit/package.json
new file mode 100644
index 0000000..275ef4e
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/adbkit/package.json
@@ -0,0 +1,88 @@
+{
+  "name": "adbkit",
+  "version": "2.1.6",
+  "description": "A pure Node.js client for the Android Debug Bridge.",
+  "keywords": [
+    "adb",
+    "adbkit",
+    "android",
+    "logcat",
+    "monkey"
+  ],
+  "bugs": {
+    "url": "https://github.com/CyberAgent/adbkit/issues"
+  },
+  "license": "Apache-2.0",
+  "author": {
+    "name": "CyberAgent, Inc.",
+    "email": "npm@cyberagent.co.jp",
+    "url": "http://www.cyberagent.co.jp/"
+  },
+  "main": "./index",
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/CyberAgent/adbkit.git"
+  },
+  "scripts": {
+    "postpublish": "grunt clean",
+    "prepublish": "grunt test coffee",
+    "test": "grunt test"
+  },
+  "dependencies": {
+    "adbkit-logcat": "~1.0.3",
+    "adbkit-monkey": "~1.0.1",
+    "bluebird": "~1.1.0",
+    "commander": "^2.3.0",
+    "debug": "~0.7.4",
+    "node-forge": "^0.6.12",
+    "split": "~0.2.10"
+  },
+  "devDependencies": {
+    "bench": "~0.3.5",
+    "chai": "~1.9.0",
+    "coffee-script": "~1.7.1",
+    "grunt": "~0.4.2",
+    "grunt-cli": "~0.1.13",
+    "grunt-coffeelint": "0.0.8",
+    "grunt-contrib-clean": "~0.5.0",
+    "grunt-contrib-coffee": "~0.10.0",
+    "grunt-contrib-watch": "~0.5.3",
+    "grunt-exec": "~0.4.3",
+    "grunt-jsonlint": "~1.0.4",
+    "grunt-notify": "~0.2.16",
+    "mocha": "~1.17.1",
+    "sinon": "~1.8.1",
+    "sinon-chai": "~2.5.0",
+    "coffeelint": "~1.0.8"
+  },
+  "engines": {
+    "node": ">= 0.10.4"
+  },
+  "gitHead": "c74e6a5617fb046cfa93708e5b2be0289cde20e6",
+  "homepage": "https://github.com/CyberAgent/adbkit",
+  "_id": "adbkit@2.1.6",
+  "_shasum": "10f016de49483c255b549a1d28aec79882178f2a",
+  "_from": "adbkit@^2.1.6",
+  "_npmVersion": "1.4.28",
+  "_npmUser": {
+    "name": "sorccu",
+    "email": "simo@shoqolate.com"
+  },
+  "maintainers": [
+    {
+      "name": "sorccu",
+      "email": "simo@shoqolate.com"
+    },
+    {
+      "name": "cyberagent",
+      "email": "npm@cyberagent.co.jp"
+    }
+  ],
+  "dist": {
+    "shasum": "10f016de49483c255b549a1d28aec79882178f2a",
+    "tarball": "http://registry.npmjs.org/adbkit/-/adbkit-2.1.6.tgz"
+  },
+  "directories": {},
+  "_resolved": "https://registry.npmjs.org/adbkit/-/adbkit-2.1.6.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/.npmignore b/node_modules/node-firefox-forward-ports/node_modules/portfinder/.npmignore
new file mode 100644
index 0000000..5171c54
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/.npmignore
@@ -0,0 +1,2 @@
+node_modules
+npm-debug.log
\ No newline at end of file
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/.travis.yml b/node_modules/node-firefox-forward-ports/node_modules/portfinder/.travis.yml
new file mode 100644
index 0000000..62b5273
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/.travis.yml
@@ -0,0 +1,21 @@
+language: node_js
+node_js:
+  - "0.8"
+  - "0.10"
+  - "0.11"
+  
+before_install:
+  - travis_retry npm install -g npm
+  - travis_retry npm install
+
+script:
+  - npm test
+
+matrix:
+  allow_failures:
+    - node_js: "0.11"
+
+notifications:
+  email:
+    - travis@nodejitsu.com
+  irc: "irc.freenode.org#nodejitsu"
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/LICENSE b/node_modules/node-firefox-forward-ports/node_modules/portfinder/LICENSE
new file mode 100644
index 0000000..d26f4a2
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/LICENSE
@@ -0,0 +1,22 @@
+node-portfinder
+
+Copyright (c) 2012 Charlie Robbins
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/README.md b/node_modules/node-firefox-forward-ports/node_modules/portfinder/README.md
new file mode 100644
index 0000000..229ccf0
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/README.md
@@ -0,0 +1,38 @@
+# node-portfinder [![Build Status](https://api.travis-ci.org/indexzero/node-portfinder.svg)](https://travis-ci.org/indexzero/node-portfinder)
+
+## Installation
+
+### Installing npm (node package manager)
+``` bash
+  curl http://npmjs.org/install.sh | sh
+```
+
+### Installing node-portfinder
+``` bash
+  $ [sudo] npm install portfinder
+```
+
+## Usage
+The `portfinder` module has a simple interface:
+
+``` js
+  var portfinder = require('portfinder');
+
+  portfinder.getPort(function (err, port) {
+    //
+    // `port` is guaranteed to be a free port
+    // in this scope.
+    //
+  });
+```
+
+By default `portfinder` will start searching from `8000`. To change this simply set `portfinder.basePort`.
+
+## Run Tests
+``` bash
+  $ npm test
+```
+
+#### Author: [Charlie Robbins][0]
+#### License: MIT/X11
+[0]: http://nodejitsu.com
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/lib/portfinder.js b/node_modules/node-firefox-forward-ports/node_modules/portfinder/lib/portfinder.js
new file mode 100644
index 0000000..3da2562
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/lib/portfinder.js
@@ -0,0 +1,218 @@
+/*
+ * portfinder.js: A simple tool to find an open port on the current machine.
+ *
+ * (C) 2011, Charlie Robbins
+ *
+ */
+ 
+var fs = require('fs'),
+    net = require('net'),
+    path = require('path'),
+    async = require('async'),
+    mkdirp = require('mkdirp').mkdirp;
+
+//
+// ### @basePort {Number}
+// The lowest port to begin any port search from
+//
+exports.basePort = 8000;
+
+//
+// ### @basePath {string}
+// Default path to begin any socket search from
+//
+exports.basePath = '/tmp/portfinder'
+
+//
+// ### function getPort (options, callback)
+// #### @options {Object} Settings to use when finding the necessary port
+// #### @callback {function} Continuation to respond to when complete.
+// Responds with a unbound port on the current machine.
+//
+exports.getPort = function (options, callback) {
+  if (!callback) {
+    callback = options;
+    options = {}; 
+  }
+  
+  options.port   = options.port   || exports.basePort;
+  options.host   = options.host   || null;
+  options.server = options.server || net.createServer(function () {
+    //
+    // Create an empty listener for the port testing server.
+    //
+  });
+  
+  function onListen () {
+    options.server.removeListener('error', onError);
+    options.server.close();
+    callback(null, options.port)
+  }
+  
+  function onError (err) {
+    options.server.removeListener('listening', onListen);
+
+    if (err.code !== 'EADDRINUSE' && err.code !== 'EACCES') {
+      return callback(err);
+    }
+
+    exports.getPort({
+      port: exports.nextPort(options.port),
+      host: options.host,
+      server: options.server
+    }, callback);
+  }
+
+  options.server.once('error', onError);
+  options.server.once('listening', onListen);
+  options.server.listen(options.port, options.host);
+};
+
+//
+// ### function getPorts (count, options, callback)
+// #### @count {Number} The number of ports to find
+// #### @options {Object} Settings to use when finding the necessary port
+// #### @callback {function} Continuation to respond to when complete.
+// Responds with an array of unbound ports on the current machine.
+//
+exports.getPorts = function (count, options, callback) {
+  if (!callback) {
+    callback = options;
+    options = {};
+  }
+
+  var lastPort = null;
+  async.timesSeries(count, function(index, asyncCallback) {
+    if (lastPort) {
+      options.port = exports.nextPort(lastPort);
+    }
+
+    exports.getPort(options, function (err, port) {
+      if (err) {
+        asyncCallback(err);
+      } else {
+        lastPort = port;
+        asyncCallback(null, port);
+      }
+    });
+  }, callback);
+};
+
+//
+// ### function getSocket (options, callback)
+// #### @options {Object} Settings to use when finding the necessary port
+// #### @callback {function} Continuation to respond to when complete.
+// Responds with a unbound socket using the specified directory and base
+// name on the current machine.
+//
+exports.getSocket = function (options, callback) {
+  if (!callback) {
+    callback = options;
+    options = {};
+  }
+
+  options.mod  = options.mod    || 0755;
+  options.path = options.path   || exports.basePath + '.sock';
+  
+  //
+  // Tests the specified socket
+  //
+  function testSocket () {
+    fs.stat(options.path, function (err) {
+      //
+      // If file we're checking doesn't exist (thus, stating it emits ENOENT),
+      // we should be OK with listening on this socket.
+      //
+      if (err) {
+        if (err.code == 'ENOENT') {
+          callback(null, options.path);
+        }
+        else {
+          callback(err);
+        }
+      }
+      else {
+        //
+        // This file exists, so it isn't possible to listen on it. Lets try
+        // next socket.
+        //
+        options.path = exports.nextSocket(options.path);
+        exports.getSocket(options, callback);
+      }
+    });
+  }
+  
+  //
+  // Create the target `dir` then test connection
+  // against the socket.
+  //
+  function createAndTestSocket (dir) {
+    mkdirp(dir, options.mod, function (err) {
+      if (err) {
+        return callback(err);
+      }
+      
+      options.exists = true;
+      testSocket();
+    });
+  }
+  
+  //
+  // Check if the parent directory of the target
+  // socket path exists. If it does, test connection
+  // against the socket. Otherwise, create the directory
+  // then test connection. 
+  //
+  function checkAndTestSocket () {
+    var dir = path.dirname(options.path);
+    
+    fs.stat(dir, function (err, stats) {
+      if (err || !stats.isDirectory()) {
+        return createAndTestSocket(dir);
+      }
+
+      options.exists = true;
+      testSocket();
+    });
+  }
+  
+  //
+  // If it has been explicitly stated that the 
+  // target `options.path` already exists, then 
+  // simply test the socket.
+  //
+  return options.exists 
+    ? testSocket()
+    : checkAndTestSocket();
+};
+
+//
+// ### function nextPort (port)
+// #### @port {Number} Port to increment from.
+// Gets the next port in sequence from the 
+// specified `port`.
+//
+exports.nextPort = function (port) {
+  return port + 1;
+};
+
+//
+// ### function nextSocket (socketPath)
+// #### @socketPath {string} Path to increment from
+// Gets the next socket path in sequence from the 
+// specified `socketPath`.
+//
+exports.nextSocket = function (socketPath) {
+  var dir = path.dirname(socketPath),
+      name = path.basename(socketPath, '.sock'),
+      match = name.match(/^([a-zA-z]+)(\d*)$/i),
+      index = parseInt(match[2]),
+      base = match[1];
+  
+  if (isNaN(index)) {
+    index = 0;
+  }
+  
+  index += 1;
+  return path.join(dir, base + index + '.sock');
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/.bin/mkdirp b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/.bin/mkdirp
new file mode 120000
index 0000000..017896c
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/.bin/mkdirp
@@ -0,0 +1 @@
+../mkdirp/bin/cmd.js
\ No newline at end of file
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/async/.travis.yml b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/async/.travis.yml
new file mode 100644
index 0000000..6e5919d
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/async/.travis.yml
@@ -0,0 +1,3 @@
+language: node_js
+node_js:
+  - "0.10"
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/async/LICENSE b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/async/LICENSE
new file mode 100644
index 0000000..8f29698
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/async/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2010-2014 Caolan McMahon
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/async/README.md b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/async/README.md
new file mode 100644
index 0000000..0bea531
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/async/README.md
@@ -0,0 +1,1646 @@
+# Async.js
+
+[![Build Status via Travis CI](https://travis-ci.org/caolan/async.svg?branch=master)](https://travis-ci.org/caolan/async)
+
+
+Async is a utility module which provides straight-forward, powerful functions
+for working with asynchronous JavaScript. Although originally designed for
+use with [Node.js](http://nodejs.org), it can also be used directly in the
+browser. Also supports [component](https://github.com/component/component).
+
+Async provides around 20 functions that include the usual 'functional'
+suspects (`map`, `reduce`, `filter`, `each`…) as well as some common patterns
+for asynchronous control flow (`parallel`, `series`, `waterfall`…). All these
+functions assume you follow the Node.js convention of providing a single
+callback as the last argument of your `async` function.
+
+
+## Quick Examples
+
+```javascript
+async.map(['file1','file2','file3'], fs.stat, function(err, results){
+    // results is now an array of stats for each file
+});
+
+async.filter(['file1','file2','file3'], fs.exists, function(results){
+    // results now equals an array of the existing files
+});
+
+async.parallel([
+    function(){ ... },
+    function(){ ... }
+], callback);
+
+async.series([
+    function(){ ... },
+    function(){ ... }
+]);
+```
+
+There are many more functions available so take a look at the docs below for a
+full list. This module aims to be comprehensive, so if you feel anything is
+missing please create a GitHub issue for it.
+
+## Common Pitfalls
+
+### Binding a context to an iterator
+
+This section is really about `bind`, not about `async`. If you are wondering how to
+make `async` execute your iterators in a given context, or are confused as to why
+a method of another library isn't working as an iterator, study this example:
+
+```js
+// Here is a simple object with an (unnecessarily roundabout) squaring method
+var AsyncSquaringLibrary = {
+  squareExponent: 2,
+  square: function(number, callback){ 
+    var result = Math.pow(number, this.squareExponent);
+    setTimeout(function(){
+      callback(null, result);
+    }, 200);
+  }
+};
+
+async.map([1, 2, 3], AsyncSquaringLibrary.square, function(err, result){
+  // result is [NaN, NaN, NaN]
+  // This fails because the `this.squareExponent` expression in the square
+  // function is not evaluated in the context of AsyncSquaringLibrary, and is
+  // therefore undefined.
+});
+
+async.map([1, 2, 3], AsyncSquaringLibrary.square.bind(AsyncSquaringLibrary), function(err, result){
+  // result is [1, 4, 9]
+  // With the help of bind we can attach a context to the iterator before
+  // passing it to async. Now the square function will be executed in its 
+  // 'home' AsyncSquaringLibrary context and the value of `this.squareExponent`
+  // will be as expected.
+});
+```
+
+## Download
+
+The source is available for download from
+[GitHub](http://github.com/caolan/async).
+Alternatively, you can install using Node Package Manager (`npm`):
+
+    npm install async
+
+__Development:__ [async.js](https://github.com/caolan/async/raw/master/lib/async.js) - 29.6kb Uncompressed
+
+## In the Browser
+
+So far it's been tested in IE6, IE7, IE8, FF3.6 and Chrome 5. 
+
+Usage:
+
+```html
+<script type="text/javascript" src="async.js"></script>
+<script type="text/javascript">
+
+    async.map(data, asyncProcess, function(err, results){
+        alert(results);
+    });
+
+</script>
+```
+
+## Documentation
+
+### Collections
+
+* [`each`](#each)
+* [`eachSeries`](#eachSeries)
+* [`eachLimit`](#eachLimit)
+* [`map`](#map)
+* [`mapSeries`](#mapSeries)
+* [`mapLimit`](#mapLimit)
+* [`filter`](#filter)
+* [`filterSeries`](#filterSeries)
+* [`reject`](#reject)
+* [`rejectSeries`](#rejectSeries)
+* [`reduce`](#reduce)
+* [`reduceRight`](#reduceRight)
+* [`detect`](#detect)
+* [`detectSeries`](#detectSeries)
+* [`sortBy`](#sortBy)
+* [`some`](#some)
+* [`every`](#every)
+* [`concat`](#concat)
+* [`concatSeries`](#concatSeries)
+
+### Control Flow
+
+* [`series`](#seriestasks-callback)
+* [`parallel`](#parallel)
+* [`parallelLimit`](#parallellimittasks-limit-callback)
+* [`whilst`](#whilst)
+* [`doWhilst`](#doWhilst)
+* [`until`](#until)
+* [`doUntil`](#doUntil)
+* [`forever`](#forever)
+* [`waterfall`](#waterfall)
+* [`compose`](#compose)
+* [`seq`](#seq)
+* [`applyEach`](#applyEach)
+* [`applyEachSeries`](#applyEachSeries)
+* [`queue`](#queue)
+* [`priorityQueue`](#priorityQueue)
+* [`cargo`](#cargo)
+* [`auto`](#auto)
+* [`retry`](#retry)
+* [`iterator`](#iterator)
+* [`apply`](#apply)
+* [`nextTick`](#nextTick)
+* [`times`](#times)
+* [`timesSeries`](#timesSeries)
+
+### Utils
+
+* [`memoize`](#memoize)
+* [`unmemoize`](#unmemoize)
+* [`log`](#log)
+* [`dir`](#dir)
+* [`noConflict`](#noConflict)
+
+
+## Collections
+
+<a name="forEach" />
+<a name="each" />
+### each(arr, iterator, callback)
+
+Applies the function `iterator` to each item in `arr`, in parallel.
+The `iterator` is called with an item from the list, and a callback for when it
+has finished. If the `iterator` passes an error to its `callback`, the main
+`callback` (for the `each` function) is immediately called with the error.
+
+Note, that since this function applies `iterator` to each item in parallel,
+there is no guarantee that the iterator functions will complete in order.
+
+__Arguments__
+
+* `arr` - An array to iterate over.
+* `iterator(item, callback)` - A function to apply to each item in `arr`.
+  The iterator is passed a `callback(err)` which must be called once it has 
+  completed. If no error has occured, the `callback` should be run without 
+  arguments or with an explicit `null` argument.
+* `callback(err)` - A callback which is called when all `iterator` functions
+  have finished, or an error occurs.
+
+__Examples__
+
+
+```js
+// assuming openFiles is an array of file names and saveFile is a function
+// to save the modified contents of that file:
+
+async.each(openFiles, saveFile, function(err){
+    // if any of the saves produced an error, err would equal that error
+});
+```
+
+```js
+// assuming openFiles is an array of file names 
+
+async.each(openFiles, function( file, callback) {
+  
+  // Perform operation on file here.
+  console.log('Processing file ' + file);
+  
+  if( file.length > 32 ) {
+    console.log('This file name is too long');
+    callback('File name too long');
+  } else {
+    // Do work to process file here
+    console.log('File processed');
+    callback();
+  }
+}, function(err){
+    // if any of the file processing produced an error, err would equal that error
+    if( err ) {
+      // One of the iterations produced an error.
+      // All processing will now stop.
+      console.log('A file failed to process');
+    } else {
+      console.log('All files have been processed successfully');
+    }
+});
+```
+
+---------------------------------------
+
+<a name="forEachSeries" />
+<a name="eachSeries" />
+### eachSeries(arr, iterator, callback)
+
+The same as [`each`](#each), only `iterator` is applied to each item in `arr` in
+series. The next `iterator` is only called once the current one has completed. 
+This means the `iterator` functions will complete in order.
+
+
+---------------------------------------
+
+<a name="forEachLimit" />
+<a name="eachLimit" />
+### eachLimit(arr, limit, iterator, callback)
+
+The same as [`each`](#each), only no more than `limit` `iterator`s will be simultaneously 
+running at any time.
+
+Note that the items in `arr` are not processed in batches, so there is no guarantee that 
+the first `limit` `iterator` functions will complete before any others are started.
+
+__Arguments__
+
+* `arr` - An array to iterate over.
+* `limit` - The maximum number of `iterator`s to run at any time.
+* `iterator(item, callback)` - A function to apply to each item in `arr`.
+  The iterator is passed a `callback(err)` which must be called once it has 
+  completed. If no error has occured, the callback should be run without 
+  arguments or with an explicit `null` argument.
+* `callback(err)` - A callback which is called when all `iterator` functions
+  have finished, or an error occurs.
+
+__Example__
+
+```js
+// Assume documents is an array of JSON objects and requestApi is a
+// function that interacts with a rate-limited REST api.
+
+async.eachLimit(documents, 20, requestApi, function(err){
+    // if any of the saves produced an error, err would equal that error
+});
+```
+
+---------------------------------------
+
+<a name="map" />
+### map(arr, iterator, callback)
+
+Produces a new array of values by mapping each value in `arr` through
+the `iterator` function. The `iterator` is called with an item from `arr` and a
+callback for when it has finished processing. Each of these callback takes 2 arguments: 
+an `error`, and the transformed item from `arr`. If `iterator` passes an error to this 
+callback, the main `callback` (for the `map` function) is immediately called with the error.
+
+Note, that since this function applies the `iterator` to each item in parallel,
+there is no guarantee that the `iterator` functions will complete in order. 
+However, the results array will be in the same order as the original `arr`.
+
+__Arguments__
+
+* `arr` - An array to iterate over.
+* `iterator(item, callback)` - A function to apply to each item in `arr`.
+  The iterator is passed a `callback(err, transformed)` which must be called once 
+  it has completed with an error (which can be `null`) and a transformed item.
+* `callback(err, results)` - A callback which is called when all `iterator`
+  functions have finished, or an error occurs. Results is an array of the
+  transformed items from the `arr`.
+
+__Example__
+
+```js
+async.map(['file1','file2','file3'], fs.stat, function(err, results){
+    // results is now an array of stats for each file
+});
+```
+
+---------------------------------------
+
+<a name="mapSeries" />
+### mapSeries(arr, iterator, callback)
+
+The same as [`map`](#map), only the `iterator` is applied to each item in `arr` in
+series. The next `iterator` is only called once the current one has completed. 
+The results array will be in the same order as the original.
+
+
+---------------------------------------
+
+<a name="mapLimit" />
+### mapLimit(arr, limit, iterator, callback)
+
+The same as [`map`](#map), only no more than `limit` `iterator`s will be simultaneously 
+running at any time.
+
+Note that the items are not processed in batches, so there is no guarantee that 
+the first `limit` `iterator` functions will complete before any others are started.
+
+__Arguments__
+
+* `arr` - An array to iterate over.
+* `limit` - The maximum number of `iterator`s to run at any time.
+* `iterator(item, callback)` - A function to apply to each item in `arr`.
+  The iterator is passed a `callback(err, transformed)` which must be called once 
+  it has completed with an error (which can be `null`) and a transformed item.
+* `callback(err, results)` - A callback which is called when all `iterator`
+  calls have finished, or an error occurs. The result is an array of the
+  transformed items from the original `arr`.
+
+__Example__
+
+```js
+async.mapLimit(['file1','file2','file3'], 1, fs.stat, function(err, results){
+    // results is now an array of stats for each file
+});
+```
+
+---------------------------------------
+
+<a name="select" />
+<a name="filter" />
+### filter(arr, iterator, callback)
+
+__Alias:__ `select`
+
+Returns a new array of all the values in `arr` which pass an async truth test.
+_The callback for each `iterator` call only accepts a single argument of `true` or
+`false`; it does not accept an error argument first!_ This is in-line with the
+way node libraries work with truth tests like `fs.exists`. This operation is
+performed in parallel, but the results array will be in the same order as the
+original.
+
+__Arguments__
+
+* `arr` - An array to iterate over.
+* `iterator(item, callback)` - A truth test to apply to each item in `arr`.
+  The `iterator` is passed a `callback(truthValue)`, which must be called with a 
+  boolean argument once it has completed.
+* `callback(results)` - A callback which is called after all the `iterator`
+  functions have finished.
+
+__Example__
+
+```js
+async.filter(['file1','file2','file3'], fs.exists, function(results){
+    // results now equals an array of the existing files
+});
+```
+
+---------------------------------------
+
+<a name="selectSeries" />
+<a name="filterSeries" />
+### filterSeries(arr, iterator, callback)
+
+__Alias:__ `selectSeries`
+
+The same as [`filter`](#filter) only the `iterator` is applied to each item in `arr` in
+series. The next `iterator` is only called once the current one has completed. 
+The results array will be in the same order as the original.
+
+---------------------------------------
+
+<a name="reject" />
+### reject(arr, iterator, callback)
+
+The opposite of [`filter`](#filter). Removes values that pass an `async` truth test.
+
+---------------------------------------
+
+<a name="rejectSeries" />
+### rejectSeries(arr, iterator, callback)
+
+The same as [`reject`](#reject), only the `iterator` is applied to each item in `arr`
+in series.
+
+
+---------------------------------------
+
+<a name="reduce" />
+### reduce(arr, memo, iterator, callback)
+
+__Aliases:__ `inject`, `foldl`
+
+Reduces `arr` into a single value using an async `iterator` to return
+each successive step. `memo` is the initial state of the reduction. 
+This function only operates in series. 
+
+For performance reasons, it may make sense to split a call to this function into 
+a parallel map, and then use the normal `Array.prototype.reduce` on the results. 
+This function is for situations where each step in the reduction needs to be async; 
+if you can get the data before reducing it, then it's probably a good idea to do so.
+
+__Arguments__
+
+* `arr` - An array to iterate over.
+* `memo` - The initial state of the reduction.
+* `iterator(memo, item, callback)` - A function applied to each item in the
+  array to produce the next step in the reduction. The `iterator` is passed a
+  `callback(err, reduction)` which accepts an optional error as its first 
+  argument, and the state of the reduction as the second. If an error is 
+  passed to the callback, the reduction is stopped and the main `callback` is 
+  immediately called with the error.
+* `callback(err, result)` - A callback which is called after all the `iterator`
+  functions have finished. Result is the reduced value.
+
+__Example__
+
+```js
+async.reduce([1,2,3], 0, function(memo, item, callback){
+    // pointless async:
+    process.nextTick(function(){
+        callback(null, memo + item)
+    });
+}, function(err, result){
+    // result is now equal to the last value of memo, which is 6
+});
+```
+
+---------------------------------------
+
+<a name="reduceRight" />
+### reduceRight(arr, memo, iterator, callback)
+
+__Alias:__ `foldr`
+
+Same as [`reduce`](#reduce), only operates on `arr` in reverse order.
+
+
+---------------------------------------
+
+<a name="detect" />
+### detect(arr, iterator, callback)
+
+Returns the first value in `arr` that passes an async truth test. The
+`iterator` is applied in parallel, meaning the first iterator to return `true` will
+fire the detect `callback` with that result. That means the result might not be
+the first item in the original `arr` (in terms of order) that passes the test.
+
+If order within the original `arr` is important, then look at [`detectSeries`](#detectSeries).
+
+__Arguments__
+
+* `arr` - An array to iterate over.
+* `iterator(item, callback)` - A truth test to apply to each item in `arr`.
+  The iterator is passed a `callback(truthValue)` which must be called with a 
+  boolean argument once it has completed.
+* `callback(result)` - A callback which is called as soon as any iterator returns
+  `true`, or after all the `iterator` functions have finished. Result will be
+  the first item in the array that passes the truth test (iterator) or the
+  value `undefined` if none passed.
+
+__Example__
+
+```js
+async.detect(['file1','file2','file3'], fs.exists, function(result){
+    // result now equals the first file in the list that exists
+});
+```
+
+---------------------------------------
+
+<a name="detectSeries" />
+### detectSeries(arr, iterator, callback)
+
+The same as [`detect`](#detect), only the `iterator` is applied to each item in `arr`
+in series. This means the result is always the first in the original `arr` (in
+terms of array order) that passes the truth test.
+
+
+---------------------------------------
+
+<a name="sortBy" />
+### sortBy(arr, iterator, callback)
+
+Sorts a list by the results of running each `arr` value through an async `iterator`.
+
+__Arguments__
+
+* `arr` - An array to iterate over.
+* `iterator(item, callback)` - A function to apply to each item in `arr`.
+  The iterator is passed a `callback(err, sortValue)` which must be called once it
+  has completed with an error (which can be `null`) and a value to use as the sort
+  criteria.
+* `callback(err, results)` - A callback which is called after all the `iterator`
+  functions have finished, or an error occurs. Results is the items from
+  the original `arr` sorted by the values returned by the `iterator` calls.
+
+__Example__
+
+```js
+async.sortBy(['file1','file2','file3'], function(file, callback){
+    fs.stat(file, function(err, stats){
+        callback(err, stats.mtime);
+    });
+}, function(err, results){
+    // results is now the original array of files sorted by
+    // modified date
+});
+```
+
+__Sort Order__
+
+By modifying the callback parameter the sorting order can be influenced:
+
+```js
+//ascending order
+async.sortBy([1,9,3,5], function(x, callback){
+    callback(err, x);
+}, function(err,result){
+    //result callback
+} );
+
+//descending order
+async.sortBy([1,9,3,5], function(x, callback){
+    callback(err, x*-1);    //<- x*-1 instead of x, turns the order around
+}, function(err,result){
+    //result callback
+} );
+```
+
+---------------------------------------
+
+<a name="some" />
+### some(arr, iterator, callback)
+
+__Alias:__ `any`
+
+Returns `true` if at least one element in the `arr` satisfies an async test.
+_The callback for each iterator call only accepts a single argument of `true` or
+`false`; it does not accept an error argument first!_ This is in-line with the
+way node libraries work with truth tests like `fs.exists`. Once any iterator
+call returns `true`, the main `callback` is immediately called.
+
+__Arguments__
+
+* `arr` - An array to iterate over.
+* `iterator(item, callback)` - A truth test to apply to each item in the array
+  in parallel. The iterator is passed a callback(truthValue) which must be 
+  called with a boolean argument once it has completed.
+* `callback(result)` - A callback which is called as soon as any iterator returns
+  `true`, or after all the iterator functions have finished. Result will be
+  either `true` or `false` depending on the values of the async tests.
+
+__Example__
+
+```js
+async.some(['file1','file2','file3'], fs.exists, function(result){
+    // if result is true then at least one of the files exists
+});
+```
+
+---------------------------------------
+
+<a name="every" />
+### every(arr, iterator, callback)
+
+__Alias:__ `all`
+
+Returns `true` if every element in `arr` satisfies an async test.
+_The callback for each `iterator` call only accepts a single argument of `true` or
+`false`; it does not accept an error argument first!_ This is in-line with the
+way node libraries work with truth tests like `fs.exists`.
+
+__Arguments__
+
+* `arr` - An array to iterate over.
+* `iterator(item, callback)` - A truth test to apply to each item in the array
+  in parallel. The iterator is passed a callback(truthValue) which must be 
+  called with a  boolean argument once it has completed.
+* `callback(result)` - A callback which is called after all the `iterator`
+  functions have finished. Result will be either `true` or `false` depending on
+  the values of the async tests.
+
+__Example__
+
+```js
+async.every(['file1','file2','file3'], fs.exists, function(result){
+    // if result is true then every file exists
+});
+```
+
+---------------------------------------
+
+<a name="concat" />
+### concat(arr, iterator, callback)
+
+Applies `iterator` to each item in `arr`, concatenating the results. Returns the
+concatenated list. The `iterator`s are called in parallel, and the results are
+concatenated as they return. There is no guarantee that the results array will
+be returned in the original order of `arr` passed to the `iterator` function.
+
+__Arguments__
+
+* `arr` - An array to iterate over.
+* `iterator(item, callback)` - A function to apply to each item in `arr`.
+  The iterator is passed a `callback(err, results)` which must be called once it 
+  has completed with an error (which can be `null`) and an array of results.
+* `callback(err, results)` - A callback which is called after all the `iterator`
+  functions have finished, or an error occurs. Results is an array containing
+  the concatenated results of the `iterator` function.
+
+__Example__
+
+```js
+async.concat(['dir1','dir2','dir3'], fs.readdir, function(err, files){
+    // files is now a list of filenames that exist in the 3 directories
+});
+```
+
+---------------------------------------
+
+<a name="concatSeries" />
+### concatSeries(arr, iterator, callback)
+
+Same as [`concat`](#concat), but executes in series instead of parallel.
+
+
+## Control Flow
+
+<a name="series" />
+### series(tasks, [callback])
+
+Run the functions in the `tasks` array in series, each one running once the previous
+function has completed. If any functions in the series pass an error to its
+callback, no more functions are run, and `callback` is immediately called with the value of the error. 
+Otherwise, `callback` receives an array of results when `tasks` have completed.
+
+It is also possible to use an object instead of an array. Each property will be
+run as a function, and the results will be passed to the final `callback` as an object
+instead of an array. This can be a more readable way of handling results from
+[`series`](#series).
+
+**Note** that while many implementations preserve the order of object properties, the
+[ECMAScript Language Specifcation](http://www.ecma-international.org/ecma-262/5.1/#sec-8.6) 
+explicitly states that
+
+> The mechanics and order of enumerating the properties is not specified.
+
+So if you rely on the order in which your series of functions are executed, and want
+this to work on all platforms, consider using an array. 
+
+__Arguments__
+
+* `tasks` - An array or object containing functions to run, each function is passed
+  a `callback(err, result)` it must call on completion with an error `err` (which can
+  be `null`) and an optional `result` value.
+* `callback(err, results)` - An optional callback to run once all the functions
+  have completed. This function gets a results array (or object) containing all 
+  the result arguments passed to the `task` callbacks.
+
+__Example__
+
+```js
+async.series([
+    function(callback){
+        // do some stuff ...
+        callback(null, 'one');
+    },
+    function(callback){
+        // do some more stuff ...
+        callback(null, 'two');
+    }
+],
+// optional callback
+function(err, results){
+    // results is now equal to ['one', 'two']
+});
+
+
+// an example using an object instead of an array
+async.series({
+    one: function(callback){
+        setTimeout(function(){
+            callback(null, 1);
+        }, 200);
+    },
+    two: function(callback){
+        setTimeout(function(){
+            callback(null, 2);
+        }, 100);
+    }
+},
+function(err, results) {
+    // results is now equal to: {one: 1, two: 2}
+});
+```
+
+---------------------------------------
+
+<a name="parallel" />
+### parallel(tasks, [callback])
+
+Run the `tasks` array of functions in parallel, without waiting until the previous
+function has completed. If any of the functions pass an error to its
+callback, the main `callback` is immediately called with the value of the error.
+Once the `tasks` have completed, the results are passed to the final `callback` as an
+array.
+
+It is also possible to use an object instead of an array. Each property will be
+run as a function and the results will be passed to the final `callback` as an object
+instead of an array. This can be a more readable way of handling results from
+[`parallel`](#parallel).
+
+
+__Arguments__
+
+* `tasks` - An array or object containing functions to run. Each function is passed 
+  a `callback(err, result)` which it must call on completion with an error `err` 
+  (which can be `null`) and an optional `result` value.
+* `callback(err, results)` - An optional callback to run once all the functions
+  have completed. This function gets a results array (or object) containing all 
+  the result arguments passed to the task callbacks.
+
+__Example__
+
+```js
+async.parallel([
+    function(callback){
+        setTimeout(function(){
+            callback(null, 'one');
+        }, 200);
+    },
+    function(callback){
+        setTimeout(function(){
+            callback(null, 'two');
+        }, 100);
+    }
+],
+// optional callback
+function(err, results){
+    // the results array will equal ['one','two'] even though
+    // the second function had a shorter timeout.
+});
+
+
+// an example using an object instead of an array
+async.parallel({
+    one: function(callback){
+        setTimeout(function(){
+            callback(null, 1);
+        }, 200);
+    },
+    two: function(callback){
+        setTimeout(function(){
+            callback(null, 2);
+        }, 100);
+    }
+},
+function(err, results) {
+    // results is now equals to: {one: 1, two: 2}
+});
+```
+
+---------------------------------------
+
+<a name="parallelLimit" />
+### parallelLimit(tasks, limit, [callback])
+
+The same as [`parallel`](#parallel), only `tasks` are executed in parallel 
+with a maximum of `limit` tasks executing at any time.
+
+Note that the `tasks` are not executed in batches, so there is no guarantee that 
+the first `limit` tasks will complete before any others are started.
+
+__Arguments__
+
+* `tasks` - An array or object containing functions to run, each function is passed 
+  a `callback(err, result)` it must call on completion with an error `err` (which can
+  be `null`) and an optional `result` value.
+* `limit` - The maximum number of `tasks` to run at any time.
+* `callback(err, results)` - An optional callback to run once all the functions
+  have completed. This function gets a results array (or object) containing all 
+  the result arguments passed to the `task` callbacks.
+
+---------------------------------------
+
+<a name="whilst" />
+### whilst(test, fn, callback)
+
+Repeatedly call `fn`, while `test` returns `true`. Calls `callback` when stopped,
+or an error occurs.
+
+__Arguments__
+
+* `test()` - synchronous truth test to perform before each execution of `fn`.
+* `fn(callback)` - A function which is called each time `test` passes. The function is
+  passed a `callback(err)`, which must be called once it has completed with an 
+  optional `err` argument.
+* `callback(err)` - A callback which is called after the test fails and repeated
+  execution of `fn` has stopped.
+
+__Example__
+
+```js
+var count = 0;
+
+async.whilst(
+    function () { return count < 5; },
+    function (callback) {
+        count++;
+        setTimeout(callback, 1000);
+    },
+    function (err) {
+        // 5 seconds have passed
+    }
+);
+```
+
+---------------------------------------
+
+<a name="doWhilst" />
+### doWhilst(fn, test, callback)
+
+The post-check version of [`whilst`](#whilst). To reflect the difference in 
+the order of operations, the arguments `test` and `fn` are switched. 
+
+`doWhilst` is to `whilst` as `do while` is to `while` in plain JavaScript.
+
+---------------------------------------
+
+<a name="until" />
+### until(test, fn, callback)
+
+Repeatedly call `fn` until `test` returns `true`. Calls `callback` when stopped,
+or an error occurs.
+
+The inverse of [`whilst`](#whilst).
+
+---------------------------------------
+
+<a name="doUntil" />
+### doUntil(fn, test, callback)
+
+Like [`doWhilst`](#doWhilst), except the `test` is inverted. Note the argument ordering differs from `until`.
+
+---------------------------------------
+
+<a name="forever" />
+### forever(fn, errback)
+
+Calls the asynchronous function `fn` with a callback parameter that allows it to
+call itself again, in series, indefinitely.
+
+If an error is passed to the callback then `errback` is called with the
+error, and execution stops, otherwise it will never be called.
+
+```js
+async.forever(
+    function(next) {
+        // next is suitable for passing to things that need a callback(err [, whatever]);
+        // it will result in this function being called again.
+    },
+    function(err) {
+        // if next is called with a value in its first parameter, it will appear
+        // in here as 'err', and execution will stop.
+    }
+);
+```
+
+---------------------------------------
+
+<a name="waterfall" />
+### waterfall(tasks, [callback])
+
+Runs the `tasks` array of functions in series, each passing their results to the next in
+the array. However, if any of the `tasks` pass an error to their own callback, the
+next function is not executed, and the main `callback` is immediately called with
+the error.
+
+__Arguments__
+
+* `tasks` - An array of functions to run, each function is passed a 
+  `callback(err, result1, result2, ...)` it must call on completion. The first
+  argument is an error (which can be `null`) and any further arguments will be 
+  passed as arguments in order to the next task.
+* `callback(err, [results])` - An optional callback to run once all the functions
+  have completed. This will be passed the results of the last task's callback.
+
+
+
+__Example__
+
+```js
+async.waterfall([
+    function(callback){
+        callback(null, 'one', 'two');
+    },
+    function(arg1, arg2, callback){
+      // arg1 now equals 'one' and arg2 now equals 'two'
+        callback(null, 'three');
+    },
+    function(arg1, callback){
+        // arg1 now equals 'three'
+        callback(null, 'done');
+    }
+], function (err, result) {
+   // result now equals 'done'    
+});
+```
+
+---------------------------------------
+<a name="compose" />
+### compose(fn1, fn2...)
+
+Creates a function which is a composition of the passed asynchronous
+functions. Each function consumes the return value of the function that
+follows. Composing functions `f()`, `g()`, and `h()` would produce the result of
+`f(g(h()))`, only this version uses callbacks to obtain the return values.
+
+Each function is executed with the `this` binding of the composed function.
+
+__Arguments__
+
+* `functions...` - the asynchronous functions to compose
+
+
+__Example__
+
+```js
+function add1(n, callback) {
+    setTimeout(function () {
+        callback(null, n + 1);
+    }, 10);
+}
+
+function mul3(n, callback) {
+    setTimeout(function () {
+        callback(null, n * 3);
+    }, 10);
+}
+
+var add1mul3 = async.compose(mul3, add1);
+
+add1mul3(4, function (err, result) {
+   // result now equals 15
+});
+```
+
+---------------------------------------
+<a name="seq" />
+### seq(fn1, fn2...)
+
+Version of the compose function that is more natural to read.
+Each following function consumes the return value of the latter function. 
+
+Each function is executed with the `this` binding of the composed function.
+
+__Arguments__
+
+* functions... - the asynchronous functions to compose
+
+
+__Example__
+
+```js
+// Requires lodash (or underscore), express3 and dresende's orm2.
+// Part of an app, that fetches cats of the logged user.
+// This example uses `seq` function to avoid overnesting and error 
+// handling clutter.
+app.get('/cats', function(request, response) {
+  function handleError(err, data, callback) {
+    if (err) {
+      console.error(err);
+      response.json({ status: 'error', message: err.message });
+    }
+    else {
+      callback(data);
+    }
+  }
+  var User = request.models.User;
+  async.seq(
+    _.bind(User.get, User),  // 'User.get' has signature (id, callback(err, data))
+    handleError,
+    function(user, fn) {
+      user.getCats(fn);      // 'getCats' has signature (callback(err, data))
+    },
+    handleError,
+    function(cats) {
+      response.json({ status: 'ok', message: 'Cats found', data: cats });
+    }
+  )(req.session.user_id);
+  }
+});
+```
+
+---------------------------------------
+<a name="applyEach" />
+### applyEach(fns, args..., callback)
+
+Applies the provided arguments to each function in the array, calling 
+`callback` after all functions have completed. If you only provide the first
+argument, then it will return a function which lets you pass in the
+arguments as if it were a single function call.
+
+__Arguments__
+
+* `fns` - the asynchronous functions to all call with the same arguments
+* `args...` - any number of separate arguments to pass to the function
+* `callback` - the final argument should be the callback, called when all
+  functions have completed processing
+
+
+__Example__
+
+```js
+async.applyEach([enableSearch, updateSchema], 'bucket', callback);
+
+// partial application example:
+async.each(
+    buckets,
+    async.applyEach([enableSearch, updateSchema]),
+    callback
+);
+```
+
+---------------------------------------
+
+<a name="applyEachSeries" />
+### applyEachSeries(arr, iterator, callback)
+
+The same as [`applyEach`](#applyEach) only the functions are applied in series.
+
+---------------------------------------
+
+<a name="queue" />
+### queue(worker, concurrency)
+
+Creates a `queue` object with the specified `concurrency`. Tasks added to the
+`queue` are processed in parallel (up to the `concurrency` limit). If all
+`worker`s are in progress, the task is queued until one becomes available. 
+Once a `worker` completes a `task`, that `task`'s callback is called.
+
+__Arguments__
+
+* `worker(task, callback)` - An asynchronous function for processing a queued
+  task, which must call its `callback(err)` argument when finished, with an 
+  optional `error` as an argument.
+* `concurrency` - An `integer` for determining how many `worker` functions should be
+  run in parallel.
+
+__Queue objects__
+
+The `queue` object returned by this function has the following properties and
+methods:
+
+* `length()` - a function returning the number of items waiting to be processed.
+* `started` - a function returning whether or not any items have been pushed and processed by the queue
+* `running()` - a function returning the number of items currently being processed.
+* `idle()` - a function returning false if there are items waiting or being processed, or true if not.
+* `concurrency` - an integer for determining how many `worker` functions should be
+  run in parallel. This property can be changed after a `queue` is created to
+  alter the concurrency on-the-fly.
+* `push(task, [callback])` - add a new task to the `queue`. Calls `callback` once 
+  the `worker` has finished processing the task. Instead of a single task, a `tasks` array
+  can be submitted. The respective callback is used for every task in the list.
+* `unshift(task, [callback])` - add a new task to the front of the `queue`.
+* `saturated` - a callback that is called when the `queue` length hits the `concurrency` limit, 
+   and further tasks will be queued.
+* `empty` - a callback that is called when the last item from the `queue` is given to a `worker`.
+* `drain` - a callback that is called when the last item from the `queue` has returned from the `worker`.
+* `paused` - a boolean for determining whether the queue is in a paused state
+* `pause()` - a function that pauses the processing of tasks until `resume()` is called.
+* `resume()` - a function that resumes the processing of queued tasks when the queue is paused.
+* `kill()` - a function that empties remaining tasks from the queue forcing it to go idle.
+
+__Example__
+
+```js
+// create a queue object with concurrency 2
+
+var q = async.queue(function (task, callback) {
+    console.log('hello ' + task.name);
+    callback();
+}, 2);
+
+
+// assign a callback
+q.drain = function() {
+    console.log('all items have been processed');
+}
+
+// add some items to the queue
+
+q.push({name: 'foo'}, function (err) {
+    console.log('finished processing foo');
+});
+q.push({name: 'bar'}, function (err) {
+    console.log('finished processing bar');
+});
+
+// add some items to the queue (batch-wise)
+
+q.push([{name: 'baz'},{name: 'bay'},{name: 'bax'}], function (err) {
+    console.log('finished processing bar');
+});
+
+// add some items to the front of the queue
+
+q.unshift({name: 'bar'}, function (err) {
+    console.log('finished processing bar');
+});
+```
+
+
+---------------------------------------
+
+<a name="priorityQueue" />
+### priorityQueue(worker, concurrency)
+
+The same as [`queue`](#queue) only tasks are assigned a priority and completed in ascending priority order. There are two differences between `queue` and `priorityQueue` objects:
+
+* `push(task, priority, [callback])` - `priority` should be a number. If an array of
+  `tasks` is given, all tasks will be assigned the same priority.
+* The `unshift` method was removed.
+
+---------------------------------------
+
+<a name="cargo" />
+### cargo(worker, [payload])
+
+Creates a `cargo` object with the specified payload. Tasks added to the
+cargo will be processed altogether (up to the `payload` limit). If the
+`worker` is in progress, the task is queued until it becomes available. Once
+the `worker` has completed some tasks, each callback of those tasks is called.
+Check out [this animation](https://camo.githubusercontent.com/6bbd36f4cf5b35a0f11a96dcd2e97711ffc2fb37/68747470733a2f2f662e636c6f75642e6769746875622e636f6d2f6173736574732f313637363837312f36383130382f62626330636662302d356632392d313165322d393734662d3333393763363464633835382e676966) for how `cargo` and `queue` work.
+
+While [queue](#queue) passes only one task to one of a group of workers
+at a time, cargo passes an array of tasks to a single worker, repeating
+when the worker is finished.
+
+__Arguments__
+
+* `worker(tasks, callback)` - An asynchronous function for processing an array of
+  queued tasks, which must call its `callback(err)` argument when finished, with 
+  an optional `err` argument.
+* `payload` - An optional `integer` for determining how many tasks should be
+  processed per round; if omitted, the default is unlimited.
+
+__Cargo objects__
+
+The `cargo` object returned by this function has the following properties and
+methods:
+
+* `length()` - A function returning the number of items waiting to be processed.
+* `payload` - An `integer` for determining how many tasks should be
+  process per round. This property can be changed after a `cargo` is created to
+  alter the payload on-the-fly.
+* `push(task, [callback])` - Adds `task` to the `queue`. The callback is called
+  once the `worker` has finished processing the task. Instead of a single task, an array of `tasks` 
+  can be submitted. The respective callback is used for every task in the list.
+* `saturated` - A callback that is called when the `queue.length()` hits the concurrency and further tasks will be queued.
+* `empty` - A callback that is called when the last item from the `queue` is given to a `worker`.
+* `drain` - A callback that is called when the last item from the `queue` has returned from the `worker`.
+
+__Example__
+
+```js
+// create a cargo object with payload 2
+
+var cargo = async.cargo(function (tasks, callback) {
+    for(var i=0; i<tasks.length; i++){
+      console.log('hello ' + tasks[i].name);
+    }
+    callback();
+}, 2);
+
+
+// add some items
+
+cargo.push({name: 'foo'}, function (err) {
+    console.log('finished processing foo');
+});
+cargo.push({name: 'bar'}, function (err) {
+    console.log('finished processing bar');
+});
+cargo.push({name: 'baz'}, function (err) {
+    console.log('finished processing baz');
+});
+```
+
+---------------------------------------
+
+<a name="auto" />
+### auto(tasks, [callback])
+
+Determines the best order for running the functions in `tasks`, based on their 
+requirements. Each function can optionally depend on other functions being completed 
+first, and each function is run as soon as its requirements are satisfied. 
+
+If any of the functions pass an error to their callback, it will not 
+complete (so any other functions depending on it will not run), and the main 
+`callback` is immediately called with the error. Functions also receive an 
+object containing the results of functions which have completed so far.
+
+Note, all functions are called with a `results` object as a second argument, 
+so it is unsafe to pass functions in the `tasks` object which cannot handle the
+extra argument. 
+
+For example, this snippet of code:
+
+```js
+async.auto({
+  readData: async.apply(fs.readFile, 'data.txt', 'utf-8')
+}, callback);
+```
+
+will have the effect of calling `readFile` with the results object as the last
+argument, which will fail:
+
+```js
+fs.readFile('data.txt', 'utf-8', cb, {});
+```
+
+Instead, wrap the call to `readFile` in a function which does not forward the 
+`results` object:
+
+```js
+async.auto({
+  readData: function(cb, results){
+    fs.readFile('data.txt', 'utf-8', cb);
+  }
+}, callback);
+```
+
+__Arguments__
+
+* `tasks` - An object. Each of its properties is either a function or an array of
+  requirements, with the function itself the last item in the array. The object's key
+  of a property serves as the name of the task defined by that property,
+  i.e. can be used when specifying requirements for other tasks.
+  The function receives two arguments: (1) a `callback(err, result)` which must be 
+  called when finished, passing an `error` (which can be `null`) and the result of 
+  the function's execution, and (2) a `results` object, containing the results of
+  the previously executed functions.
+* `callback(err, results)` - An optional callback which is called when all the
+  tasks have been completed. It receives the `err` argument if any `tasks` 
+  pass an error to their callback. Results are always returned; however, if 
+  an error occurs, no further `tasks` will be performed, and the results
+  object will only contain partial results.
+
+
+__Example__
+
+```js
+async.auto({
+    get_data: function(callback){
+        console.log('in get_data');
+        // async code to get some data
+        callback(null, 'data', 'converted to array');
+    },
+    make_folder: function(callback){
+        console.log('in make_folder');
+        // async code to create a directory to store a file in
+        // this is run at the same time as getting the data
+        callback(null, 'folder');
+    },
+    write_file: ['get_data', 'make_folder', function(callback, results){
+        console.log('in write_file', JSON.stringify(results));
+        // once there is some data and the directory exists,
+        // write the data to a file in the directory
+        callback(null, 'filename');
+    }],
+    email_link: ['write_file', function(callback, results){
+        console.log('in email_link', JSON.stringify(results));
+        // once the file is written let's email a link to it...
+        // results.write_file contains the filename returned by write_file.
+        callback(null, {'file':results.write_file, 'email':'user@example.com'});
+    }]
+}, function(err, results) {
+    console.log('err = ', err);
+    console.log('results = ', results);
+});
+```
+
+This is a fairly trivial example, but to do this using the basic parallel and
+series functions would look like this:
+
+```js
+async.parallel([
+    function(callback){
+        console.log('in get_data');
+        // async code to get some data
+        callback(null, 'data', 'converted to array');
+    },
+    function(callback){
+        console.log('in make_folder');
+        // async code to create a directory to store a file in
+        // this is run at the same time as getting the data
+        callback(null, 'folder');
+    }
+],
+function(err, results){
+    async.series([
+        function(callback){
+            console.log('in write_file', JSON.stringify(results));
+            // once there is some data and the directory exists,
+            // write the data to a file in the directory
+            results.push('filename');
+            callback(null);
+        },
+        function(callback){
+            console.log('in email_link', JSON.stringify(results));
+            // once the file is written let's email a link to it...
+            callback(null, {'file':results.pop(), 'email':'user@example.com'});
+        }
+    ]);
+});
+```
+
+For a complicated series of `async` tasks, using the [`auto`](#auto) function makes adding
+new tasks much easier (and the code more readable).
+
+
+---------------------------------------
+
+<a name="retry" />
+### retry([times = 5], task, [callback])
+
+Attempts to get a successful response from `task` no more than `times` times before
+returning an error. If the task is successful, the `callback` will be passed the result
+of the successfull task. If all attemps fail, the callback will be passed the error and
+result (if any) of the final attempt.
+
+__Arguments__
+
+* `times` - An integer indicating how many times to attempt the `task` before giving up. Defaults to 5.
+* `task(callback, results)` - A function which receives two arguments: (1) a `callback(err, result)`
+  which must be called when finished, passing `err` (which can be `null`) and the `result` of 
+  the function's execution, and (2) a `results` object, containing the results of
+  the previously executed functions (if nested inside another control flow).
+* `callback(err, results)` - An optional callback which is called when the
+  task has succeeded, or after the final failed attempt. It receives the `err` and `result` arguments of the last attempt at completing the `task`.
+
+The [`retry`](#retry) function can be used as a stand-alone control flow by passing a
+callback, as shown below:
+
+```js
+async.retry(3, apiMethod, function(err, result) {
+    // do something with the result
+});
+```
+
+It can also be embeded within other control flow functions to retry individual methods
+that are not as reliable, like this:
+
+```js
+async.auto({
+    users: api.getUsers.bind(api),
+    payments: async.retry(3, api.getPayments.bind(api))
+}, function(err, results) {
+  // do something with the results
+});
+```
+
+
+---------------------------------------
+
+<a name="iterator" />
+### iterator(tasks)
+
+Creates an iterator function which calls the next function in the `tasks` array,
+returning a continuation to call the next one after that. It's also possible to
+“peek” at the next iterator with `iterator.next()`.
+
+This function is used internally by the `async` module, but can be useful when
+you want to manually control the flow of functions in series.
+
+__Arguments__
+
+* `tasks` - An array of functions to run.
+
+__Example__
+
+```js
+var iterator = async.iterator([
+    function(){ sys.p('one'); },
+    function(){ sys.p('two'); },
+    function(){ sys.p('three'); }
+]);
+
+node> var iterator2 = iterator();
+'one'
+node> var iterator3 = iterator2();
+'two'
+node> iterator3();
+'three'
+node> var nextfn = iterator2.next();
+node> nextfn();
+'three'
+```
+
+---------------------------------------
+
+<a name="apply" />
+### apply(function, arguments..)
+
+Creates a continuation function with some arguments already applied. 
+
+Useful as a shorthand when combined with other control flow functions. Any arguments
+passed to the returned function are added to the arguments originally passed
+to apply.
+
+__Arguments__
+
+* `function` - The function you want to eventually apply all arguments to.
+* `arguments...` - Any number of arguments to automatically apply when the
+  continuation is called.
+
+__Example__
+
+```js
+// using apply
+
+async.parallel([
+    async.apply(fs.writeFile, 'testfile1', 'test1'),
+    async.apply(fs.writeFile, 'testfile2', 'test2'),
+]);
+
+
+// the same process without using apply
+
+async.parallel([
+    function(callback){
+        fs.writeFile('testfile1', 'test1', callback);
+    },
+    function(callback){
+        fs.writeFile('testfile2', 'test2', callback);
+    }
+]);
+```
+
+It's possible to pass any number of additional arguments when calling the
+continuation:
+
+```js
+node> var fn = async.apply(sys.puts, 'one');
+node> fn('two', 'three');
+one
+two
+three
+```
+
+---------------------------------------
+
+<a name="nextTick" />
+### nextTick(callback)
+
+Calls `callback` on a later loop around the event loop. In Node.js this just
+calls `process.nextTick`; in the browser it falls back to `setImmediate(callback)`
+if available, otherwise `setTimeout(callback, 0)`, which means other higher priority
+events may precede the execution of `callback`.
+
+This is used internally for browser-compatibility purposes.
+
+__Arguments__
+
+* `callback` - The function to call on a later loop around the event loop.
+
+__Example__
+
+```js
+var call_order = [];
+async.nextTick(function(){
+    call_order.push('two');
+    // call_order now equals ['one','two']
+});
+call_order.push('one')
+```
+
+<a name="times" />
+### times(n, callback)
+
+Calls the `callback` function `n` times, and accumulates results in the same manner
+you would use with [`map`](#map).
+
+__Arguments__
+
+* `n` - The number of times to run the function.
+* `callback` - The function to call `n` times.
+
+__Example__
+
+```js
+// Pretend this is some complicated async factory
+var createUser = function(id, callback) {
+  callback(null, {
+    id: 'user' + id
+  })
+}
+// generate 5 users
+async.times(5, function(n, next){
+    createUser(n, function(err, user) {
+      next(err, user)
+    })
+}, function(err, users) {
+  // we should now have 5 users
+});
+```
+
+<a name="timesSeries" />
+### timesSeries(n, callback)
+
+The same as [`times`](#times), only the iterator is applied to each item in `arr` in
+series. The next `iterator` is only called once the current one has completed. 
+The results array will be in the same order as the original.
+
+
+## Utils
+
+<a name="memoize" />
+### memoize(fn, [hasher])
+
+Caches the results of an `async` function. When creating a hash to store function
+results against, the callback is omitted from the hash and an optional hash
+function can be used.
+
+The cache of results is exposed as the `memo` property of the function returned
+by `memoize`.
+
+__Arguments__
+
+* `fn` - The function to proxy and cache results from.
+* `hasher` - Tn optional function for generating a custom hash for storing
+  results. It has all the arguments applied to it apart from the callback, and
+  must be synchronous.
+
+__Example__
+
+```js
+var slow_fn = function (name, callback) {
+    // do something
+    callback(null, result);
+};
+var fn = async.memoize(slow_fn);
+
+// fn can now be used as if it were slow_fn
+fn('some name', function () {
+    // callback
+});
+```
+
+<a name="unmemoize" />
+### unmemoize(fn)
+
+Undoes a [`memoize`](#memoize)d function, reverting it to the original, unmemoized
+form. Handy for testing.
+
+__Arguments__
+
+* `fn` - the memoized function
+
+<a name="log" />
+### log(function, arguments)
+
+Logs the result of an `async` function to the `console`. Only works in Node.js or
+in browsers that support `console.log` and `console.error` (such as FF and Chrome).
+If multiple arguments are returned from the async function, `console.log` is
+called on each argument in order.
+
+__Arguments__
+
+* `function` - The function you want to eventually apply all arguments to.
+* `arguments...` - Any number of arguments to apply to the function.
+
+__Example__
+
+```js
+var hello = function(name, callback){
+    setTimeout(function(){
+        callback(null, 'hello ' + name);
+    }, 1000);
+};
+```
+```js
+node> async.log(hello, 'world');
+'hello world'
+```
+
+---------------------------------------
+
+<a name="dir" />
+### dir(function, arguments)
+
+Logs the result of an `async` function to the `console` using `console.dir` to
+display the properties of the resulting object. Only works in Node.js or
+in browsers that support `console.dir` and `console.error` (such as FF and Chrome).
+If multiple arguments are returned from the async function, `console.dir` is
+called on each argument in order.
+
+__Arguments__
+
+* `function` - The function you want to eventually apply all arguments to.
+* `arguments...` - Any number of arguments to apply to the function.
+
+__Example__
+
+```js
+var hello = function(name, callback){
+    setTimeout(function(){
+        callback(null, {hello: name});
+    }, 1000);
+};
+```
+```js
+node> async.dir(hello, 'world');
+{hello: 'world'}
+```
+
+---------------------------------------
+
+<a name="noConflict" />
+### noConflict()
+
+Changes the value of `async` back to its original value, returning a reference to the
+`async` object.
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/async/component.json b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/async/component.json
new file mode 100644
index 0000000..bbb0115
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/async/component.json
@@ -0,0 +1,11 @@
+{
+  "name": "async",
+  "repo": "caolan/async",
+  "description": "Higher-order functions and common patterns for asynchronous code",
+  "version": "0.1.23",
+  "keywords": [],
+  "dependencies": {},
+  "development": {},
+  "main": "lib/async.js",
+  "scripts": [ "lib/async.js" ]
+}
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/async/lib/async.js b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/async/lib/async.js
new file mode 100755
index 0000000..01e8afc
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/async/lib/async.js
@@ -0,0 +1,1123 @@
+/*!
+ * async
+ * https://github.com/caolan/async
+ *
+ * Copyright 2010-2014 Caolan McMahon
+ * Released under the MIT license
+ */
+/*jshint onevar: false, indent:4 */
+/*global setImmediate: false, setTimeout: false, console: false */
+(function () {
+
+    var async = {};
+
+    // global on the server, window in the browser
+    var root, previous_async;
+
+    root = this;
+    if (root != null) {
+      previous_async = root.async;
+    }
+
+    async.noConflict = function () {
+        root.async = previous_async;
+        return async;
+    };
+
+    function only_once(fn) {
+        var called = false;
+        return function() {
+            if (called) throw new Error("Callback was already called.");
+            called = true;
+            fn.apply(root, arguments);
+        }
+    }
+
+    //// cross-browser compatiblity functions ////
+
+    var _toString = Object.prototype.toString;
+
+    var _isArray = Array.isArray || function (obj) {
+        return _toString.call(obj) === '[object Array]';
+    };
+
+    var _each = function (arr, iterator) {
+        if (arr.forEach) {
+            return arr.forEach(iterator);
+        }
+        for (var i = 0; i < arr.length; i += 1) {
+            iterator(arr[i], i, arr);
+        }
+    };
+
+    var _map = function (arr, iterator) {
+        if (arr.map) {
+            return arr.map(iterator);
+        }
+        var results = [];
+        _each(arr, function (x, i, a) {
+            results.push(iterator(x, i, a));
+        });
+        return results;
+    };
+
+    var _reduce = function (arr, iterator, memo) {
+        if (arr.reduce) {
+            return arr.reduce(iterator, memo);
+        }
+        _each(arr, function (x, i, a) {
+            memo = iterator(memo, x, i, a);
+        });
+        return memo;
+    };
+
+    var _keys = function (obj) {
+        if (Object.keys) {
+            return Object.keys(obj);
+        }
+        var keys = [];
+        for (var k in obj) {
+            if (obj.hasOwnProperty(k)) {
+                keys.push(k);
+            }
+        }
+        return keys;
+    };
+
+    //// exported async module functions ////
+
+    //// nextTick implementation with browser-compatible fallback ////
+    if (typeof process === 'undefined' || !(process.nextTick)) {
+        if (typeof setImmediate === 'function') {
+            async.nextTick = function (fn) {
+                // not a direct alias for IE10 compatibility
+                setImmediate(fn);
+            };
+            async.setImmediate = async.nextTick;
+        }
+        else {
+            async.nextTick = function (fn) {
+                setTimeout(fn, 0);
+            };
+            async.setImmediate = async.nextTick;
+        }
+    }
+    else {
+        async.nextTick = process.nextTick;
+        if (typeof setImmediate !== 'undefined') {
+            async.setImmediate = function (fn) {
+              // not a direct alias for IE10 compatibility
+              setImmediate(fn);
+            };
+        }
+        else {
+            async.setImmediate = async.nextTick;
+        }
+    }
+
+    async.each = function (arr, iterator, callback) {
+        callback = callback || function () {};
+        if (!arr.length) {
+            return callback();
+        }
+        var completed = 0;
+        _each(arr, function (x) {
+            iterator(x, only_once(done) );
+        });
+        function done(err) {
+          if (err) {
+              callback(err);
+              callback = function () {};
+          }
+          else {
+              completed += 1;
+              if (completed >= arr.length) {
+                  callback();
+              }
+          }
+        }
+    };
+    async.forEach = async.each;
+
+    async.eachSeries = function (arr, iterator, callback) {
+        callback = callback || function () {};
+        if (!arr.length) {
+            return callback();
+        }
+        var completed = 0;
+        var iterate = function () {
+            iterator(arr[completed], function (err) {
+                if (err) {
+                    callback(err);
+                    callback = function () {};
+                }
+                else {
+                    completed += 1;
+                    if (completed >= arr.length) {
+                        callback();
+                    }
+                    else {
+                        iterate();
+                    }
+                }
+            });
+        };
+        iterate();
+    };
+    async.forEachSeries = async.eachSeries;
+
+    async.eachLimit = function (arr, limit, iterator, callback) {
+        var fn = _eachLimit(limit);
+        fn.apply(null, [arr, iterator, callback]);
+    };
+    async.forEachLimit = async.eachLimit;
+
+    var _eachLimit = function (limit) {
+
+        return function (arr, iterator, callback) {
+            callback = callback || function () {};
+            if (!arr.length || limit <= 0) {
+                return callback();
+            }
+            var completed = 0;
+            var started = 0;
+            var running = 0;
+
+            (function replenish () {
+                if (completed >= arr.length) {
+                    return callback();
+                }
+
+                while (running < limit && started < arr.length) {
+                    started += 1;
+                    running += 1;
+                    iterator(arr[started - 1], function (err) {
+                        if (err) {
+                            callback(err);
+                            callback = function () {};
+                        }
+                        else {
+                            completed += 1;
+                            running -= 1;
+                            if (completed >= arr.length) {
+                                callback();
+                            }
+                            else {
+                                replenish();
+                            }
+                        }
+                    });
+                }
+            })();
+        };
+    };
+
+
+    var doParallel = function (fn) {
+        return function () {
+            var args = Array.prototype.slice.call(arguments);
+            return fn.apply(null, [async.each].concat(args));
+        };
+    };
+    var doParallelLimit = function(limit, fn) {
+        return function () {
+            var args = Array.prototype.slice.call(arguments);
+            return fn.apply(null, [_eachLimit(limit)].concat(args));
+        };
+    };
+    var doSeries = function (fn) {
+        return function () {
+            var args = Array.prototype.slice.call(arguments);
+            return fn.apply(null, [async.eachSeries].concat(args));
+        };
+    };
+
+
+    var _asyncMap = function (eachfn, arr, iterator, callback) {
+        arr = _map(arr, function (x, i) {
+            return {index: i, value: x};
+        });
+        if (!callback) {
+            eachfn(arr, function (x, callback) {
+                iterator(x.value, function (err) {
+                    callback(err);
+                });
+            });
+        } else {
+            var results = [];
+            eachfn(arr, function (x, callback) {
+                iterator(x.value, function (err, v) {
+                    results[x.index] = v;
+                    callback(err);
+                });
+            }, function (err) {
+                callback(err, results);
+            });
+        }
+    };
+    async.map = doParallel(_asyncMap);
+    async.mapSeries = doSeries(_asyncMap);
+    async.mapLimit = function (arr, limit, iterator, callback) {
+        return _mapLimit(limit)(arr, iterator, callback);
+    };
+
+    var _mapLimit = function(limit) {
+        return doParallelLimit(limit, _asyncMap);
+    };
+
+    // reduce only has a series version, as doing reduce in parallel won't
+    // work in many situations.
+    async.reduce = function (arr, memo, iterator, callback) {
+        async.eachSeries(arr, function (x, callback) {
+            iterator(memo, x, function (err, v) {
+                memo = v;
+                callback(err);
+            });
+        }, function (err) {
+            callback(err, memo);
+        });
+    };
+    // inject alias
+    async.inject = async.reduce;
+    // foldl alias
+    async.foldl = async.reduce;
+
+    async.reduceRight = function (arr, memo, iterator, callback) {
+        var reversed = _map(arr, function (x) {
+            return x;
+        }).reverse();
+        async.reduce(reversed, memo, iterator, callback);
+    };
+    // foldr alias
+    async.foldr = async.reduceRight;
+
+    var _filter = function (eachfn, arr, iterator, callback) {
+        var results = [];
+        arr = _map(arr, function (x, i) {
+            return {index: i, value: x};
+        });
+        eachfn(arr, function (x, callback) {
+            iterator(x.value, function (v) {
+                if (v) {
+                    results.push(x);
+                }
+                callback();
+            });
+        }, function (err) {
+            callback(_map(results.sort(function (a, b) {
+                return a.index - b.index;
+            }), function (x) {
+                return x.value;
+            }));
+        });
+    };
+    async.filter = doParallel(_filter);
+    async.filterSeries = doSeries(_filter);
+    // select alias
+    async.select = async.filter;
+    async.selectSeries = async.filterSeries;
+
+    var _reject = function (eachfn, arr, iterator, callback) {
+        var results = [];
+        arr = _map(arr, function (x, i) {
+            return {index: i, value: x};
+        });
+        eachfn(arr, function (x, callback) {
+            iterator(x.value, function (v) {
+                if (!v) {
+                    results.push(x);
+                }
+                callback();
+            });
+        }, function (err) {
+            callback(_map(results.sort(function (a, b) {
+                return a.index - b.index;
+            }), function (x) {
+                return x.value;
+            }));
+        });
+    };
+    async.reject = doParallel(_reject);
+    async.rejectSeries = doSeries(_reject);
+
+    var _detect = function (eachfn, arr, iterator, main_callback) {
+        eachfn(arr, function (x, callback) {
+            iterator(x, function (result) {
+                if (result) {
+                    main_callback(x);
+                    main_callback = function () {};
+                }
+                else {
+                    callback();
+                }
+            });
+        }, function (err) {
+            main_callback();
+        });
+    };
+    async.detect = doParallel(_detect);
+    async.detectSeries = doSeries(_detect);
+
+    async.some = function (arr, iterator, main_callback) {
+        async.each(arr, function (x, callback) {
+            iterator(x, function (v) {
+                if (v) {
+                    main_callback(true);
+                    main_callback = function () {};
+                }
+                callback();
+            });
+        }, function (err) {
+            main_callback(false);
+        });
+    };
+    // any alias
+    async.any = async.some;
+
+    async.every = function (arr, iterator, main_callback) {
+        async.each(arr, function (x, callback) {
+            iterator(x, function (v) {
+                if (!v) {
+                    main_callback(false);
+                    main_callback = function () {};
+                }
+                callback();
+            });
+        }, function (err) {
+            main_callback(true);
+        });
+    };
+    // all alias
+    async.all = async.every;
+
+    async.sortBy = function (arr, iterator, callback) {
+        async.map(arr, function (x, callback) {
+            iterator(x, function (err, criteria) {
+                if (err) {
+                    callback(err);
+                }
+                else {
+                    callback(null, {value: x, criteria: criteria});
+                }
+            });
+        }, function (err, results) {
+            if (err) {
+                return callback(err);
+            }
+            else {
+                var fn = function (left, right) {
+                    var a = left.criteria, b = right.criteria;
+                    return a < b ? -1 : a > b ? 1 : 0;
+                };
+                callback(null, _map(results.sort(fn), function (x) {
+                    return x.value;
+                }));
+            }
+        });
+    };
+
+    async.auto = function (tasks, callback) {
+        callback = callback || function () {};
+        var keys = _keys(tasks);
+        var remainingTasks = keys.length
+        if (!remainingTasks) {
+            return callback();
+        }
+
+        var results = {};
+
+        var listeners = [];
+        var addListener = function (fn) {
+            listeners.unshift(fn);
+        };
+        var removeListener = function (fn) {
+            for (var i = 0; i < listeners.length; i += 1) {
+                if (listeners[i] === fn) {
+                    listeners.splice(i, 1);
+                    return;
+                }
+            }
+        };
+        var taskComplete = function () {
+            remainingTasks--
+            _each(listeners.slice(0), function (fn) {
+                fn();
+            });
+        };
+
+        addListener(function () {
+            if (!remainingTasks) {
+                var theCallback = callback;
+                // prevent final callback from calling itself if it errors
+                callback = function () {};
+
+                theCallback(null, results);
+            }
+        });
+
+        _each(keys, function (k) {
+            var task = _isArray(tasks[k]) ? tasks[k]: [tasks[k]];
+            var taskCallback = function (err) {
+                var args = Array.prototype.slice.call(arguments, 1);
+                if (args.length <= 1) {
+                    args = args[0];
+                }
+                if (err) {
+                    var safeResults = {};
+                    _each(_keys(results), function(rkey) {
+                        safeResults[rkey] = results[rkey];
+                    });
+                    safeResults[k] = args;
+                    callback(err, safeResults);
+                    // stop subsequent errors hitting callback multiple times
+                    callback = function () {};
+                }
+                else {
+                    results[k] = args;
+                    async.setImmediate(taskComplete);
+                }
+            };
+            var requires = task.slice(0, Math.abs(task.length - 1)) || [];
+            var ready = function () {
+                return _reduce(requires, function (a, x) {
+                    return (a && results.hasOwnProperty(x));
+                }, true) && !results.hasOwnProperty(k);
+            };
+            if (ready()) {
+                task[task.length - 1](taskCallback, results);
+            }
+            else {
+                var listener = function () {
+                    if (ready()) {
+                        removeListener(listener);
+                        task[task.length - 1](taskCallback, results);
+                    }
+                };
+                addListener(listener);
+            }
+        });
+    };
+
+    async.retry = function(times, task, callback) {
+        var DEFAULT_TIMES = 5;
+        var attempts = [];
+        // Use defaults if times not passed
+        if (typeof times === 'function') {
+            callback = task;
+            task = times;
+            times = DEFAULT_TIMES;
+        }
+        // Make sure times is a number
+        times = parseInt(times, 10) || DEFAULT_TIMES;
+        var wrappedTask = function(wrappedCallback, wrappedResults) {
+            var retryAttempt = function(task, finalAttempt) {
+                return function(seriesCallback) {
+                    task(function(err, result){
+                        seriesCallback(!err || finalAttempt, {err: err, result: result});
+                    }, wrappedResults);
+                };
+            };
+            while (times) {
+                attempts.push(retryAttempt(task, !(times-=1)));
+            }
+            async.series(attempts, function(done, data){
+                data = data[data.length - 1];
+                (wrappedCallback || callback)(data.err, data.result);
+            });
+        }
+        // If a callback is passed, run this as a controll flow
+        return callback ? wrappedTask() : wrappedTask
+    };
+
+    async.waterfall = function (tasks, callback) {
+        callback = callback || function () {};
+        if (!_isArray(tasks)) {
+          var err = new Error('First argument to waterfall must be an array of functions');
+          return callback(err);
+        }
+        if (!tasks.length) {
+            return callback();
+        }
+        var wrapIterator = function (iterator) {
+            return function (err) {
+                if (err) {
+                    callback.apply(null, arguments);
+                    callback = function () {};
+                }
+                else {
+                    var args = Array.prototype.slice.call(arguments, 1);
+                    var next = iterator.next();
+                    if (next) {
+                        args.push(wrapIterator(next));
+                    }
+                    else {
+                        args.push(callback);
+                    }
+                    async.setImmediate(function () {
+                        iterator.apply(null, args);
+                    });
+                }
+            };
+        };
+        wrapIterator(async.iterator(tasks))();
+    };
+
+    var _parallel = function(eachfn, tasks, callback) {
+        callback = callback || function () {};
+        if (_isArray(tasks)) {
+            eachfn.map(tasks, function (fn, callback) {
+                if (fn) {
+                    fn(function (err) {
+                        var args = Array.prototype.slice.call(arguments, 1);
+                        if (args.length <= 1) {
+                            args = args[0];
+                        }
+                        callback.call(null, err, args);
+                    });
+                }
+            }, callback);
+        }
+        else {
+            var results = {};
+            eachfn.each(_keys(tasks), function (k, callback) {
+                tasks[k](function (err) {
+                    var args = Array.prototype.slice.call(arguments, 1);
+                    if (args.length <= 1) {
+                        args = args[0];
+                    }
+                    results[k] = args;
+                    callback(err);
+                });
+            }, function (err) {
+                callback(err, results);
+            });
+        }
+    };
+
+    async.parallel = function (tasks, callback) {
+        _parallel({ map: async.map, each: async.each }, tasks, callback);
+    };
+
+    async.parallelLimit = function(tasks, limit, callback) {
+        _parallel({ map: _mapLimit(limit), each: _eachLimit(limit) }, tasks, callback);
+    };
+
+    async.series = function (tasks, callback) {
+        callback = callback || function () {};
+        if (_isArray(tasks)) {
+            async.mapSeries(tasks, function (fn, callback) {
+                if (fn) {
+                    fn(function (err) {
+                        var args = Array.prototype.slice.call(arguments, 1);
+                        if (args.length <= 1) {
+                            args = args[0];
+                        }
+                        callback.call(null, err, args);
+                    });
+                }
+            }, callback);
+        }
+        else {
+            var results = {};
+            async.eachSeries(_keys(tasks), function (k, callback) {
+                tasks[k](function (err) {
+                    var args = Array.prototype.slice.call(arguments, 1);
+                    if (args.length <= 1) {
+                        args = args[0];
+                    }
+                    results[k] = args;
+                    callback(err);
+                });
+            }, function (err) {
+                callback(err, results);
+            });
+        }
+    };
+
+    async.iterator = function (tasks) {
+        var makeCallback = function (index) {
+            var fn = function () {
+                if (tasks.length) {
+                    tasks[index].apply(null, arguments);
+                }
+                return fn.next();
+            };
+            fn.next = function () {
+                return (index < tasks.length - 1) ? makeCallback(index + 1): null;
+            };
+            return fn;
+        };
+        return makeCallback(0);
+    };
+
+    async.apply = function (fn) {
+        var args = Array.prototype.slice.call(arguments, 1);
+        return function () {
+            return fn.apply(
+                null, args.concat(Array.prototype.slice.call(arguments))
+            );
+        };
+    };
+
+    var _concat = function (eachfn, arr, fn, callback) {
+        var r = [];
+        eachfn(arr, function (x, cb) {
+            fn(x, function (err, y) {
+                r = r.concat(y || []);
+                cb(err);
+            });
+        }, function (err) {
+            callback(err, r);
+        });
+    };
+    async.concat = doParallel(_concat);
+    async.concatSeries = doSeries(_concat);
+
+    async.whilst = function (test, iterator, callback) {
+        if (test()) {
+            iterator(function (err) {
+                if (err) {
+                    return callback(err);
+                }
+                async.whilst(test, iterator, callback);
+            });
+        }
+        else {
+            callback();
+        }
+    };
+
+    async.doWhilst = function (iterator, test, callback) {
+        iterator(function (err) {
+            if (err) {
+                return callback(err);
+            }
+            var args = Array.prototype.slice.call(arguments, 1);
+            if (test.apply(null, args)) {
+                async.doWhilst(iterator, test, callback);
+            }
+            else {
+                callback();
+            }
+        });
+    };
+
+    async.until = function (test, iterator, callback) {
+        if (!test()) {
+            iterator(function (err) {
+                if (err) {
+                    return callback(err);
+                }
+                async.until(test, iterator, callback);
+            });
+        }
+        else {
+            callback();
+        }
+    };
+
+    async.doUntil = function (iterator, test, callback) {
+        iterator(function (err) {
+            if (err) {
+                return callback(err);
+            }
+            var args = Array.prototype.slice.call(arguments, 1);
+            if (!test.apply(null, args)) {
+                async.doUntil(iterator, test, callback);
+            }
+            else {
+                callback();
+            }
+        });
+    };
+
+    async.queue = function (worker, concurrency) {
+        if (concurrency === undefined) {
+            concurrency = 1;
+        }
+        function _insert(q, data, pos, callback) {
+          if (!q.started){
+            q.started = true;
+          }
+          if (!_isArray(data)) {
+              data = [data];
+          }
+          if(data.length == 0) {
+             // call drain immediately if there are no tasks
+             return async.setImmediate(function() {
+                 if (q.drain) {
+                     q.drain();
+                 }
+             });
+          }
+          _each(data, function(task) {
+              var item = {
+                  data: task,
+                  callback: typeof callback === 'function' ? callback : null
+              };
+
+              if (pos) {
+                q.tasks.unshift(item);
+              } else {
+                q.tasks.push(item);
+              }
+
+              if (q.saturated && q.tasks.length === q.concurrency) {
+                  q.saturated();
+              }
+              async.setImmediate(q.process);
+          });
+        }
+
+        var workers = 0;
+        var q = {
+            tasks: [],
+            concurrency: concurrency,
+            saturated: null,
+            empty: null,
+            drain: null,
+            started: false,
+            paused: false,
+            push: function (data, callback) {
+              _insert(q, data, false, callback);
+            },
+            kill: function () {
+              q.drain = null;
+              q.tasks = [];
+            },
+            unshift: function (data, callback) {
+              _insert(q, data, true, callback);
+            },
+            process: function () {
+                if (!q.paused && workers < q.concurrency && q.tasks.length) {
+                    var task = q.tasks.shift();
+                    if (q.empty && q.tasks.length === 0) {
+                        q.empty();
+                    }
+                    workers += 1;
+                    var next = function () {
+                        workers -= 1;
+                        if (task.callback) {
+                            task.callback.apply(task, arguments);
+                        }
+                        if (q.drain && q.tasks.length + workers === 0) {
+                            q.drain();
+                        }
+                        q.process();
+                    };
+                    var cb = only_once(next);
+                    worker(task.data, cb);
+                }
+            },
+            length: function () {
+                return q.tasks.length;
+            },
+            running: function () {
+                return workers;
+            },
+            idle: function() {
+                return q.tasks.length + workers === 0;
+            },
+            pause: function () {
+                if (q.paused === true) { return; }
+                q.paused = true;
+                q.process();
+            },
+            resume: function () {
+                if (q.paused === false) { return; }
+                q.paused = false;
+                q.process();
+            }
+        };
+        return q;
+    };
+    
+    async.priorityQueue = function (worker, concurrency) {
+        
+        function _compareTasks(a, b){
+          return a.priority - b.priority;
+        };
+        
+        function _binarySearch(sequence, item, compare) {
+          var beg = -1,
+              end = sequence.length - 1;
+          while (beg < end) {
+            var mid = beg + ((end - beg + 1) >>> 1);
+            if (compare(item, sequence[mid]) >= 0) {
+              beg = mid;
+            } else {
+              end = mid - 1;
+            }
+          }
+          return beg;
+        }
+        
+        function _insert(q, data, priority, callback) {
+          if (!q.started){
+            q.started = true;
+          }
+          if (!_isArray(data)) {
+              data = [data];
+          }
+          if(data.length == 0) {
+             // call drain immediately if there are no tasks
+             return async.setImmediate(function() {
+                 if (q.drain) {
+                     q.drain();
+                 }
+             });
+          }
+          _each(data, function(task) {
+              var item = {
+                  data: task,
+                  priority: priority,
+                  callback: typeof callback === 'function' ? callback : null
+              };
+              
+              q.tasks.splice(_binarySearch(q.tasks, item, _compareTasks) + 1, 0, item);
+
+              if (q.saturated && q.tasks.length === q.concurrency) {
+                  q.saturated();
+              }
+              async.setImmediate(q.process);
+          });
+        }
+        
+        // Start with a normal queue
+        var q = async.queue(worker, concurrency);
+        
+        // Override push to accept second parameter representing priority
+        q.push = function (data, priority, callback) {
+          _insert(q, data, priority, callback);
+        };
+        
+        // Remove unshift function
+        delete q.unshift;
+
+        return q;
+    };
+
+    async.cargo = function (worker, payload) {
+        var working     = false,
+            tasks       = [];
+
+        var cargo = {
+            tasks: tasks,
+            payload: payload,
+            saturated: null,
+            empty: null,
+            drain: null,
+            drained: true,
+            push: function (data, callback) {
+                if (!_isArray(data)) {
+                    data = [data];
+                }
+                _each(data, function(task) {
+                    tasks.push({
+                        data: task,
+                        callback: typeof callback === 'function' ? callback : null
+                    });
+                    cargo.drained = false;
+                    if (cargo.saturated && tasks.length === payload) {
+                        cargo.saturated();
+                    }
+                });
+                async.setImmediate(cargo.process);
+            },
+            process: function process() {
+                if (working) return;
+                if (tasks.length === 0) {
+                    if(cargo.drain && !cargo.drained) cargo.drain();
+                    cargo.drained = true;
+                    return;
+                }
+
+                var ts = typeof payload === 'number'
+                            ? tasks.splice(0, payload)
+                            : tasks.splice(0, tasks.length);
+
+                var ds = _map(ts, function (task) {
+                    return task.data;
+                });
+
+                if(cargo.empty) cargo.empty();
+                working = true;
+                worker(ds, function () {
+                    working = false;
+
+                    var args = arguments;
+                    _each(ts, function (data) {
+                        if (data.callback) {
+                            data.callback.apply(null, args);
+                        }
+                    });
+
+                    process();
+                });
+            },
+            length: function () {
+                return tasks.length;
+            },
+            running: function () {
+                return working;
+            }
+        };
+        return cargo;
+    };
+
+    var _console_fn = function (name) {
+        return function (fn) {
+            var args = Array.prototype.slice.call(arguments, 1);
+            fn.apply(null, args.concat([function (err) {
+                var args = Array.prototype.slice.call(arguments, 1);
+                if (typeof console !== 'undefined') {
+                    if (err) {
+                        if (console.error) {
+                            console.error(err);
+                        }
+                    }
+                    else if (console[name]) {
+                        _each(args, function (x) {
+                            console[name](x);
+                        });
+                    }
+                }
+            }]));
+        };
+    };
+    async.log = _console_fn('log');
+    async.dir = _console_fn('dir');
+    /*async.info = _console_fn('info');
+    async.warn = _console_fn('warn');
+    async.error = _console_fn('error');*/
+
+    async.memoize = function (fn, hasher) {
+        var memo = {};
+        var queues = {};
+        hasher = hasher || function (x) {
+            return x;
+        };
+        var memoized = function () {
+            var args = Array.prototype.slice.call(arguments);
+            var callback = args.pop();
+            var key = hasher.apply(null, args);
+            if (key in memo) {
+                async.nextTick(function () {
+                    callback.apply(null, memo[key]);
+                });
+            }
+            else if (key in queues) {
+                queues[key].push(callback);
+            }
+            else {
+                queues[key] = [callback];
+                fn.apply(null, args.concat([function () {
+                    memo[key] = arguments;
+                    var q = queues[key];
+                    delete queues[key];
+                    for (var i = 0, l = q.length; i < l; i++) {
+                      q[i].apply(null, arguments);
+                    }
+                }]));
+            }
+        };
+        memoized.memo = memo;
+        memoized.unmemoized = fn;
+        return memoized;
+    };
+
+    async.unmemoize = function (fn) {
+      return function () {
+        return (fn.unmemoized || fn).apply(null, arguments);
+      };
+    };
+
+    async.times = function (count, iterator, callback) {
+        var counter = [];
+        for (var i = 0; i < count; i++) {
+            counter.push(i);
+        }
+        return async.map(counter, iterator, callback);
+    };
+
+    async.timesSeries = function (count, iterator, callback) {
+        var counter = [];
+        for (var i = 0; i < count; i++) {
+            counter.push(i);
+        }
+        return async.mapSeries(counter, iterator, callback);
+    };
+
+    async.seq = function (/* functions... */) {
+        var fns = arguments;
+        return function () {
+            var that = this;
+            var args = Array.prototype.slice.call(arguments);
+            var callback = args.pop();
+            async.reduce(fns, args, function (newargs, fn, cb) {
+                fn.apply(that, newargs.concat([function () {
+                    var err = arguments[0];
+                    var nextargs = Array.prototype.slice.call(arguments, 1);
+                    cb(err, nextargs);
+                }]))
+            },
+            function (err, results) {
+                callback.apply(that, [err].concat(results));
+            });
+        };
+    };
+
+    async.compose = function (/* functions... */) {
+      return async.seq.apply(null, Array.prototype.reverse.call(arguments));
+    };
+
+    var _applyEach = function (eachfn, fns /*args...*/) {
+        var go = function () {
+            var that = this;
+            var args = Array.prototype.slice.call(arguments);
+            var callback = args.pop();
+            return eachfn(fns, function (fn, cb) {
+                fn.apply(that, args.concat([cb]));
+            },
+            callback);
+        };
+        if (arguments.length > 2) {
+            var args = Array.prototype.slice.call(arguments, 2);
+            return go.apply(this, args);
+        }
+        else {
+            return go;
+        }
+    };
+    async.applyEach = doParallel(_applyEach);
+    async.applyEachSeries = doSeries(_applyEach);
+
+    async.forever = function (fn, callback) {
+        function next(err) {
+            if (err) {
+                if (callback) {
+                    return callback(err);
+                }
+                throw err;
+            }
+            fn(next);
+        }
+        next();
+    };
+
+    // Node.js
+    if (typeof module !== 'undefined' && module.exports) {
+        module.exports = async;
+    }
+    // AMD / RequireJS
+    else if (typeof define !== 'undefined' && define.amd) {
+        define([], function () {
+            return async;
+        });
+    }
+    // included directly via <script> tag
+    else {
+        root.async = async;
+    }
+
+}());
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/async/package.json b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/async/package.json
new file mode 100644
index 0000000..6aa6f14
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/async/package.json
@@ -0,0 +1,59 @@
+{
+  "name": "async",
+  "description": "Higher-order functions and common patterns for asynchronous code",
+  "main": "./lib/async",
+  "author": {
+    "name": "Caolan McMahon"
+  },
+  "version": "0.9.0",
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/caolan/async.git"
+  },
+  "bugs": {
+    "url": "https://github.com/caolan/async/issues"
+  },
+  "licenses": [
+    {
+      "type": "MIT",
+      "url": "https://github.com/caolan/async/raw/master/LICENSE"
+    }
+  ],
+  "devDependencies": {
+    "nodeunit": ">0.0.0",
+    "uglify-js": "1.2.x",
+    "nodelint": ">0.0.0"
+  },
+  "jam": {
+    "main": "lib/async.js",
+    "include": [
+      "lib/async.js",
+      "README.md",
+      "LICENSE"
+    ]
+  },
+  "scripts": {
+    "test": "nodeunit test/test-async.js"
+  },
+  "homepage": "https://github.com/caolan/async",
+  "_id": "async@0.9.0",
+  "dist": {
+    "shasum": "ac3613b1da9bed1b47510bb4651b8931e47146c7",
+    "tarball": "http://registry.npmjs.org/async/-/async-0.9.0.tgz"
+  },
+  "_from": "async@0.9.0",
+  "_npmVersion": "1.4.3",
+  "_npmUser": {
+    "name": "caolan",
+    "email": "caolan.mcmahon@gmail.com"
+  },
+  "maintainers": [
+    {
+      "name": "caolan",
+      "email": "caolan@caolanmcmahon.com"
+    }
+  ],
+  "directories": {},
+  "_shasum": "ac3613b1da9bed1b47510bb4651b8931e47146c7",
+  "_resolved": "https://registry.npmjs.org/async/-/async-0.9.0.tgz"
+}
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/.npmignore b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/.npmignore
new file mode 100644
index 0000000..9303c34
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/.npmignore
@@ -0,0 +1,2 @@
+node_modules/
+npm-debug.log
\ No newline at end of file
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/.travis.yml b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/.travis.yml
new file mode 100644
index 0000000..c693a93
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/.travis.yml
@@ -0,0 +1,5 @@
+language: node_js
+node_js:
+  - 0.6
+  - 0.8
+  - "0.10"
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/LICENSE b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/LICENSE
new file mode 100644
index 0000000..432d1ae
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/LICENSE
@@ -0,0 +1,21 @@
+Copyright 2010 James Halliday (mail@substack.net)
+
+This project is free software released under the MIT/X11 license:
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/bin/cmd.js b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/bin/cmd.js
new file mode 100755
index 0000000..d95de15
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/bin/cmd.js
@@ -0,0 +1,33 @@
+#!/usr/bin/env node
+
+var mkdirp = require('../');
+var minimist = require('minimist');
+var fs = require('fs');
+
+var argv = minimist(process.argv.slice(2), {
+    alias: { m: 'mode', h: 'help' },
+    string: [ 'mode' ]
+});
+if (argv.help) {
+    fs.createReadStream(__dirname + '/usage.txt').pipe(process.stdout);
+    return;
+}
+
+var paths = argv._.slice();
+var mode = argv.mode ? parseInt(argv.mode, 8) : undefined;
+
+(function next () {
+    if (paths.length === 0) return;
+    var p = paths.shift();
+    
+    if (mode === undefined) mkdirp(p, cb)
+    else mkdirp(p, mode, cb)
+    
+    function cb (err) {
+        if (err) {
+            console.error(err.message);
+            process.exit(1);
+        }
+        else next();
+    }
+})();
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/bin/usage.txt b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/bin/usage.txt
new file mode 100644
index 0000000..f952aa2
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/bin/usage.txt
@@ -0,0 +1,12 @@
+usage: mkdirp [DIR1,DIR2..] {OPTIONS}
+
+  Create each supplied directory including any necessary parent directories that
+  don't yet exist.
+  
+  If the directory already exists, do nothing.
+
+OPTIONS are:
+
+  -m, --mode   If a directory needs to be created, set the mode as an octal
+               permission string.
+
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/examples/pow.js b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/examples/pow.js
new file mode 100644
index 0000000..e692421
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/examples/pow.js
@@ -0,0 +1,6 @@
+var mkdirp = require('mkdirp');
+
+mkdirp('/tmp/foo/bar/baz', function (err) {
+    if (err) console.error(err)
+    else console.log('pow!')
+});
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/index.js b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/index.js
new file mode 100644
index 0000000..a1742b2
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/index.js
@@ -0,0 +1,97 @@
+var path = require('path');
+var fs = require('fs');
+
+module.exports = mkdirP.mkdirp = mkdirP.mkdirP = mkdirP;
+
+function mkdirP (p, opts, f, made) {
+    if (typeof opts === 'function') {
+        f = opts;
+        opts = {};
+    }
+    else if (!opts || typeof opts !== 'object') {
+        opts = { mode: opts };
+    }
+    
+    var mode = opts.mode;
+    var xfs = opts.fs || fs;
+    
+    if (mode === undefined) {
+        mode = 0777 & (~process.umask());
+    }
+    if (!made) made = null;
+    
+    var cb = f || function () {};
+    p = path.resolve(p);
+    
+    xfs.mkdir(p, mode, function (er) {
+        if (!er) {
+            made = made || p;
+            return cb(null, made);
+        }
+        switch (er.code) {
+            case 'ENOENT':
+                mkdirP(path.dirname(p), opts, function (er, made) {
+                    if (er) cb(er, made);
+                    else mkdirP(p, opts, cb, made);
+                });
+                break;
+
+            // In the case of any other error, just see if there's a dir
+            // there already.  If so, then hooray!  If not, then something
+            // is borked.
+            default:
+                xfs.stat(p, function (er2, stat) {
+                    // if the stat fails, then that's super weird.
+                    // let the original error be the failure reason.
+                    if (er2 || !stat.isDirectory()) cb(er, made)
+                    else cb(null, made);
+                });
+                break;
+        }
+    });
+}
+
+mkdirP.sync = function sync (p, opts, made) {
+    if (!opts || typeof opts !== 'object') {
+        opts = { mode: opts };
+    }
+    
+    var mode = opts.mode;
+    var xfs = opts.fs || fs;
+    
+    if (mode === undefined) {
+        mode = 0777 & (~process.umask());
+    }
+    if (!made) made = null;
+
+    p = path.resolve(p);
+
+    try {
+        xfs.mkdirSync(p, mode);
+        made = made || p;
+    }
+    catch (err0) {
+        switch (err0.code) {
+            case 'ENOENT' :
+                made = sync(path.dirname(p), opts, made);
+                sync(p, opts, made);
+                break;
+
+            // In the case of any other error, just see if there's a dir
+            // there already.  If so, then hooray!  If not, then something
+            // is borked.
+            default:
+                var stat;
+                try {
+                    stat = xfs.statSync(p);
+                }
+                catch (err1) {
+                    throw err0;
+                }
+                if (!stat.isDirectory()) throw err0;
+                break;
+        }
+    }
+
+    return made;
+};
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/node_modules/minimist/.travis.yml b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/node_modules/minimist/.travis.yml
new file mode 100644
index 0000000..cc4dba2
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/node_modules/minimist/.travis.yml
@@ -0,0 +1,4 @@
+language: node_js
+node_js:
+  - "0.8"
+  - "0.10"
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/node_modules/minimist/LICENSE b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/node_modules/minimist/LICENSE
new file mode 100644
index 0000000..ee27ba4
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/node_modules/minimist/LICENSE
@@ -0,0 +1,18 @@
+This software is released under the MIT license:
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/node_modules/minimist/example/parse.js b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/node_modules/minimist/example/parse.js
new file mode 100644
index 0000000..abff3e8
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/node_modules/minimist/example/parse.js
@@ -0,0 +1,2 @@
+var argv = require('../')(process.argv.slice(2));
+console.dir(argv);
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/node_modules/minimist/index.js b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/node_modules/minimist/index.js
new file mode 100644
index 0000000..584f551
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/node_modules/minimist/index.js
@@ -0,0 +1,187 @@
+module.exports = function (args, opts) {
+    if (!opts) opts = {};
+    
+    var flags = { bools : {}, strings : {} };
+    
+    [].concat(opts['boolean']).filter(Boolean).forEach(function (key) {
+        flags.bools[key] = true;
+    });
+    
+    [].concat(opts.string).filter(Boolean).forEach(function (key) {
+        flags.strings[key] = true;
+    });
+    
+    var aliases = {};
+    Object.keys(opts.alias || {}).forEach(function (key) {
+        aliases[key] = [].concat(opts.alias[key]);
+        aliases[key].forEach(function (x) {
+            aliases[x] = [key].concat(aliases[key].filter(function (y) {
+                return x !== y;
+            }));
+        });
+    });
+    
+    var defaults = opts['default'] || {};
+    
+    var argv = { _ : [] };
+    Object.keys(flags.bools).forEach(function (key) {
+        setArg(key, defaults[key] === undefined ? false : defaults[key]);
+    });
+    
+    var notFlags = [];
+
+    if (args.indexOf('--') !== -1) {
+        notFlags = args.slice(args.indexOf('--')+1);
+        args = args.slice(0, args.indexOf('--'));
+    }
+
+    function setArg (key, val) {
+        var value = !flags.strings[key] && isNumber(val)
+            ? Number(val) : val
+        ;
+        setKey(argv, key.split('.'), value);
+        
+        (aliases[key] || []).forEach(function (x) {
+            setKey(argv, x.split('.'), value);
+        });
+    }
+    
+    for (var i = 0; i < args.length; i++) {
+        var arg = args[i];
+        
+        if (/^--.+=/.test(arg)) {
+            // Using [\s\S] instead of . because js doesn't support the
+            // 'dotall' regex modifier. See:
+            // http://stackoverflow.com/a/1068308/13216
+            var m = arg.match(/^--([^=]+)=([\s\S]*)$/);
+            setArg(m[1], m[2]);
+        }
+        else if (/^--no-.+/.test(arg)) {
+            var key = arg.match(/^--no-(.+)/)[1];
+            setArg(key, false);
+        }
+        else if (/^--.+/.test(arg)) {
+            var key = arg.match(/^--(.+)/)[1];
+            var next = args[i + 1];
+            if (next !== undefined && !/^-/.test(next)
+            && !flags.bools[key]
+            && (aliases[key] ? !flags.bools[aliases[key]] : true)) {
+                setArg(key, next);
+                i++;
+            }
+            else if (/^(true|false)$/.test(next)) {
+                setArg(key, next === 'true');
+                i++;
+            }
+            else {
+                setArg(key, flags.strings[key] ? '' : true);
+            }
+        }
+        else if (/^-[^-]+/.test(arg)) {
+            var letters = arg.slice(1,-1).split('');
+            
+            var broken = false;
+            for (var j = 0; j < letters.length; j++) {
+                var next = arg.slice(j+2);
+                
+                if (next === '-') {
+                    setArg(letters[j], next)
+                    continue;
+                }
+                
+                if (/[A-Za-z]/.test(letters[j])
+                && /-?\d+(\.\d*)?(e-?\d+)?$/.test(next)) {
+                    setArg(letters[j], next);
+                    broken = true;
+                    break;
+                }
+                
+                if (letters[j+1] && letters[j+1].match(/\W/)) {
+                    setArg(letters[j], arg.slice(j+2));
+                    broken = true;
+                    break;
+                }
+                else {
+                    setArg(letters[j], flags.strings[letters[j]] ? '' : true);
+                }
+            }
+            
+            var key = arg.slice(-1)[0];
+            if (!broken && key !== '-') {
+                if (args[i+1] && !/^(-|--)[^-]/.test(args[i+1])
+                && !flags.bools[key]
+                && (aliases[key] ? !flags.bools[aliases[key]] : true)) {
+                    setArg(key, args[i+1]);
+                    i++;
+                }
+                else if (args[i+1] && /true|false/.test(args[i+1])) {
+                    setArg(key, args[i+1] === 'true');
+                    i++;
+                }
+                else {
+                    setArg(key, flags.strings[key] ? '' : true);
+                }
+            }
+        }
+        else {
+            argv._.push(
+                flags.strings['_'] || !isNumber(arg) ? arg : Number(arg)
+            );
+        }
+    }
+    
+    Object.keys(defaults).forEach(function (key) {
+        if (!hasKey(argv, key.split('.'))) {
+            setKey(argv, key.split('.'), defaults[key]);
+            
+            (aliases[key] || []).forEach(function (x) {
+                setKey(argv, x.split('.'), defaults[key]);
+            });
+        }
+    });
+    
+    notFlags.forEach(function(key) {
+        argv._.push(key);
+    });
+
+    return argv;
+};
+
+function hasKey (obj, keys) {
+    var o = obj;
+    keys.slice(0,-1).forEach(function (key) {
+        o = (o[key] || {});
+    });
+
+    var key = keys[keys.length - 1];
+    return key in o;
+}
+
+function setKey (obj, keys, value) {
+    var o = obj;
+    keys.slice(0,-1).forEach(function (key) {
+        if (o[key] === undefined) o[key] = {};
+        o = o[key];
+    });
+    
+    var key = keys[keys.length - 1];
+    if (o[key] === undefined || typeof o[key] === 'boolean') {
+        o[key] = value;
+    }
+    else if (Array.isArray(o[key])) {
+        o[key].push(value);
+    }
+    else {
+        o[key] = [ o[key], value ];
+    }
+}
+
+function isNumber (x) {
+    if (typeof x === 'number') return true;
+    if (/^0x[0-9a-f]+$/i.test(x)) return true;
+    return /^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(e[-+]?\d+)?$/.test(x);
+}
+
+function longest (xs) {
+    return Math.max.apply(null, xs.map(function (x) { return x.length }));
+}
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/node_modules/minimist/package.json b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/node_modules/minimist/package.json
new file mode 100644
index 0000000..7cd80f4
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/node_modules/minimist/package.json
@@ -0,0 +1,66 @@
+{
+  "name": "minimist",
+  "version": "0.0.8",
+  "description": "parse argument options",
+  "main": "index.js",
+  "devDependencies": {
+    "tape": "~1.0.4",
+    "tap": "~0.4.0"
+  },
+  "scripts": {
+    "test": "tap test/*.js"
+  },
+  "testling": {
+    "files": "test/*.js",
+    "browsers": [
+      "ie/6..latest",
+      "ff/5",
+      "firefox/latest",
+      "chrome/10",
+      "chrome/latest",
+      "safari/5.1",
+      "safari/latest",
+      "opera/12"
+    ]
+  },
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/substack/minimist.git"
+  },
+  "homepage": "https://github.com/substack/minimist",
+  "keywords": [
+    "argv",
+    "getopt",
+    "parser",
+    "optimist"
+  ],
+  "author": {
+    "name": "James Halliday",
+    "email": "mail@substack.net",
+    "url": "http://substack.net"
+  },
+  "license": "MIT",
+  "bugs": {
+    "url": "https://github.com/substack/minimist/issues"
+  },
+  "_id": "minimist@0.0.8",
+  "dist": {
+    "shasum": "857fcabfc3397d2625b8228262e86aa7a011b05d",
+    "tarball": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz"
+  },
+  "_from": "minimist@0.0.8",
+  "_npmVersion": "1.4.3",
+  "_npmUser": {
+    "name": "substack",
+    "email": "mail@substack.net"
+  },
+  "maintainers": [
+    {
+      "name": "substack",
+      "email": "mail@substack.net"
+    }
+  ],
+  "directories": {},
+  "_shasum": "857fcabfc3397d2625b8228262e86aa7a011b05d",
+  "_resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz"
+}
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/node_modules/minimist/readme.markdown b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/node_modules/minimist/readme.markdown
new file mode 100644
index 0000000..c256353
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/node_modules/minimist/readme.markdown
@@ -0,0 +1,73 @@
+# minimist
+
+parse argument options
+
+This module is the guts of optimist's argument parser without all the
+fanciful decoration.
+
+[![browser support](https://ci.testling.com/substack/minimist.png)](http://ci.testling.com/substack/minimist)
+
+[![build status](https://secure.travis-ci.org/substack/minimist.png)](http://travis-ci.org/substack/minimist)
+
+# example
+
+``` js
+var argv = require('minimist')(process.argv.slice(2));
+console.dir(argv);
+```
+
+```
+$ node example/parse.js -a beep -b boop
+{ _: [], a: 'beep', b: 'boop' }
+```
+
+```
+$ node example/parse.js -x 3 -y 4 -n5 -abc --beep=boop foo bar baz
+{ _: [ 'foo', 'bar', 'baz' ],
+  x: 3,
+  y: 4,
+  n: 5,
+  a: true,
+  b: true,
+  c: true,
+  beep: 'boop' }
+```
+
+# methods
+
+``` js
+var parseArgs = require('minimist')
+```
+
+## var argv = parseArgs(args, opts={})
+
+Return an argument object `argv` populated with the array arguments from `args`.
+
+`argv._` contains all the arguments that didn't have an option associated with
+them.
+
+Numeric-looking arguments will be returned as numbers unless `opts.string` or
+`opts.boolean` is set for that argument name.
+
+Any arguments after `'--'` will not be parsed and will end up in `argv._`.
+
+options can be:
+
+* `opts.string` - a string or array of strings argument names to always treat as
+strings
+* `opts.boolean` - a string or array of strings to always treat as booleans
+* `opts.alias` - an object mapping string names to strings or arrays of string
+argument names to use as aliases
+* `opts.default` - an object mapping string argument names to default values
+
+# install
+
+With [npm](https://npmjs.org) do:
+
+```
+npm install minimist
+```
+
+# license
+
+MIT
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/node_modules/minimist/test/dash.js b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/node_modules/minimist/test/dash.js
new file mode 100644
index 0000000..8b034b9
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/node_modules/minimist/test/dash.js
@@ -0,0 +1,24 @@
+var parse = require('../');
+var test = require('tape');
+
+test('-', function (t) {
+    t.plan(5);
+    t.deepEqual(parse([ '-n', '-' ]), { n: '-', _: [] });
+    t.deepEqual(parse([ '-' ]), { _: [ '-' ] });
+    t.deepEqual(parse([ '-f-' ]), { f: '-', _: [] });
+    t.deepEqual(
+        parse([ '-b', '-' ], { boolean: 'b' }),
+        { b: true, _: [ '-' ] }
+    );
+    t.deepEqual(
+        parse([ '-s', '-' ], { string: 's' }),
+        { s: '-', _: [] }
+    );
+});
+
+test('-a -- b', function (t) {
+    t.plan(3);
+    t.deepEqual(parse([ '-a', '--', 'b' ]), { a: true, _: [ 'b' ] });
+    t.deepEqual(parse([ '--a', '--', 'b' ]), { a: true, _: [ 'b' ] });
+    t.deepEqual(parse([ '--a', '--', 'b' ]), { a: true, _: [ 'b' ] });
+});
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/node_modules/minimist/test/default_bool.js b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/node_modules/minimist/test/default_bool.js
new file mode 100644
index 0000000..f0041ee
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/node_modules/minimist/test/default_bool.js
@@ -0,0 +1,20 @@
+var test = require('tape');
+var parse = require('../');
+
+test('boolean default true', function (t) {
+    var argv = parse([], {
+        boolean: 'sometrue',
+        default: { sometrue: true }
+    });
+    t.equal(argv.sometrue, true);
+    t.end();
+});
+
+test('boolean default false', function (t) {
+    var argv = parse([], {
+        boolean: 'somefalse',
+        default: { somefalse: false }
+    });
+    t.equal(argv.somefalse, false);
+    t.end();
+});
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/node_modules/minimist/test/dotted.js b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/node_modules/minimist/test/dotted.js
new file mode 100644
index 0000000..ef0ae34
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/node_modules/minimist/test/dotted.js
@@ -0,0 +1,16 @@
+var parse = require('../');
+var test = require('tape');
+
+test('dotted alias', function (t) {
+    var argv = parse(['--a.b', '22'], {default: {'a.b': 11}, alias: {'a.b': 'aa.bb'}});
+    t.equal(argv.a.b, 22);
+    t.equal(argv.aa.bb, 22);
+    t.end();
+});
+
+test('dotted default', function (t) {
+    var argv = parse('', {default: {'a.b': 11}, alias: {'a.b': 'aa.bb'}});
+    t.equal(argv.a.b, 11);
+    t.equal(argv.aa.bb, 11);
+    t.end();
+});
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/node_modules/minimist/test/long.js b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/node_modules/minimist/test/long.js
new file mode 100644
index 0000000..5d3a1e0
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/node_modules/minimist/test/long.js
@@ -0,0 +1,31 @@
+var test = require('tape');
+var parse = require('../');
+
+test('long opts', function (t) {
+    t.deepEqual(
+        parse([ '--bool' ]),
+        { bool : true, _ : [] },
+        'long boolean'
+    );
+    t.deepEqual(
+        parse([ '--pow', 'xixxle' ]),
+        { pow : 'xixxle', _ : [] },
+        'long capture sp'
+    );
+    t.deepEqual(
+        parse([ '--pow=xixxle' ]),
+        { pow : 'xixxle', _ : [] },
+        'long capture eq'
+    );
+    t.deepEqual(
+        parse([ '--host', 'localhost', '--port', '555' ]),
+        { host : 'localhost', port : 555, _ : [] },
+        'long captures sp'
+    );
+    t.deepEqual(
+        parse([ '--host=localhost', '--port=555' ]),
+        { host : 'localhost', port : 555, _ : [] },
+        'long captures eq'
+    );
+    t.end();
+});
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/node_modules/minimist/test/parse.js b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/node_modules/minimist/test/parse.js
new file mode 100644
index 0000000..8a90646
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/node_modules/minimist/test/parse.js
@@ -0,0 +1,318 @@
+var parse = require('../');
+var test = require('tape');
+
+test('parse args', function (t) {
+    t.deepEqual(
+        parse([ '--no-moo' ]),
+        { moo : false, _ : [] },
+        'no'
+    );
+    t.deepEqual(
+        parse([ '-v', 'a', '-v', 'b', '-v', 'c' ]),
+        { v : ['a','b','c'], _ : [] },
+        'multi'
+    );
+    t.end();
+});
+ 
+test('comprehensive', function (t) {
+    t.deepEqual(
+        parse([
+            '--name=meowmers', 'bare', '-cats', 'woo',
+            '-h', 'awesome', '--multi=quux',
+            '--key', 'value',
+            '-b', '--bool', '--no-meep', '--multi=baz',
+            '--', '--not-a-flag', 'eek'
+        ]),
+        {
+            c : true,
+            a : true,
+            t : true,
+            s : 'woo',
+            h : 'awesome',
+            b : true,
+            bool : true,
+            key : 'value',
+            multi : [ 'quux', 'baz' ],
+            meep : false,
+            name : 'meowmers',
+            _ : [ 'bare', '--not-a-flag', 'eek' ]
+        }
+    );
+    t.end();
+});
+
+test('nums', function (t) {
+    var argv = parse([
+        '-x', '1234',
+        '-y', '5.67',
+        '-z', '1e7',
+        '-w', '10f',
+        '--hex', '0xdeadbeef',
+        '789'
+    ]);
+    t.deepEqual(argv, {
+        x : 1234,
+        y : 5.67,
+        z : 1e7,
+        w : '10f',
+        hex : 0xdeadbeef,
+        _ : [ 789 ]
+    });
+    t.deepEqual(typeof argv.x, 'number');
+    t.deepEqual(typeof argv.y, 'number');
+    t.deepEqual(typeof argv.z, 'number');
+    t.deepEqual(typeof argv.w, 'string');
+    t.deepEqual(typeof argv.hex, 'number');
+    t.deepEqual(typeof argv._[0], 'number');
+    t.end();
+});
+
+test('flag boolean', function (t) {
+    var argv = parse([ '-t', 'moo' ], { boolean: 't' });
+    t.deepEqual(argv, { t : true, _ : [ 'moo' ] });
+    t.deepEqual(typeof argv.t, 'boolean');
+    t.end();
+});
+
+test('flag boolean value', function (t) {
+    var argv = parse(['--verbose', 'false', 'moo', '-t', 'true'], {
+        boolean: [ 't', 'verbose' ],
+        default: { verbose: true }
+    });
+    
+    t.deepEqual(argv, {
+        verbose: false,
+        t: true,
+        _: ['moo']
+    });
+    
+    t.deepEqual(typeof argv.verbose, 'boolean');
+    t.deepEqual(typeof argv.t, 'boolean');
+    t.end();
+});
+
+test('flag boolean default false', function (t) {
+    var argv = parse(['moo'], {
+        boolean: ['t', 'verbose'],
+        default: { verbose: false, t: false }
+    });
+    
+    t.deepEqual(argv, {
+        verbose: false,
+        t: false,
+        _: ['moo']
+    });
+    
+    t.deepEqual(typeof argv.verbose, 'boolean');
+    t.deepEqual(typeof argv.t, 'boolean');
+    t.end();
+
+});
+
+test('boolean groups', function (t) {
+    var argv = parse([ '-x', '-z', 'one', 'two', 'three' ], {
+        boolean: ['x','y','z']
+    });
+    
+    t.deepEqual(argv, {
+        x : true,
+        y : false,
+        z : true,
+        _ : [ 'one', 'two', 'three' ]
+    });
+    
+    t.deepEqual(typeof argv.x, 'boolean');
+    t.deepEqual(typeof argv.y, 'boolean');
+    t.deepEqual(typeof argv.z, 'boolean');
+    t.end();
+});
+
+test('newlines in params' , function (t) {
+    var args = parse([ '-s', "X\nX" ])
+    t.deepEqual(args, { _ : [], s : "X\nX" });
+    
+    // reproduce in bash:
+    // VALUE="new
+    // line"
+    // node program.js --s="$VALUE"
+    args = parse([ "--s=X\nX" ])
+    t.deepEqual(args, { _ : [], s : "X\nX" });
+    t.end();
+});
+
+test('strings' , function (t) {
+    var s = parse([ '-s', '0001234' ], { string: 's' }).s;
+    t.equal(s, '0001234');
+    t.equal(typeof s, 'string');
+    
+    var x = parse([ '-x', '56' ], { string: 'x' }).x;
+    t.equal(x, '56');
+    t.equal(typeof x, 'string');
+    t.end();
+});
+
+test('stringArgs', function (t) {
+    var s = parse([ '  ', '  ' ], { string: '_' })._;
+    t.same(s.length, 2);
+    t.same(typeof s[0], 'string');
+    t.same(s[0], '  ');
+    t.same(typeof s[1], 'string');
+    t.same(s[1], '  ');
+    t.end();
+});
+
+test('empty strings', function(t) {
+    var s = parse([ '-s' ], { string: 's' }).s;
+    t.equal(s, '');
+    t.equal(typeof s, 'string');
+
+    var str = parse([ '--str' ], { string: 'str' }).str;
+    t.equal(str, '');
+    t.equal(typeof str, 'string');
+
+    var letters = parse([ '-art' ], {
+        string: [ 'a', 't' ]
+    });
+
+    t.equal(letters.a, '');
+    t.equal(letters.r, true);
+    t.equal(letters.t, '');
+
+    t.end();
+});
+
+
+test('slashBreak', function (t) {
+    t.same(
+        parse([ '-I/foo/bar/baz' ]),
+        { I : '/foo/bar/baz', _ : [] }
+    );
+    t.same(
+        parse([ '-xyz/foo/bar/baz' ]),
+        { x : true, y : true, z : '/foo/bar/baz', _ : [] }
+    );
+    t.end();
+});
+
+test('alias', function (t) {
+    var argv = parse([ '-f', '11', '--zoom', '55' ], {
+        alias: { z: 'zoom' }
+    });
+    t.equal(argv.zoom, 55);
+    t.equal(argv.z, argv.zoom);
+    t.equal(argv.f, 11);
+    t.end();
+});
+
+test('multiAlias', function (t) {
+    var argv = parse([ '-f', '11', '--zoom', '55' ], {
+        alias: { z: [ 'zm', 'zoom' ] }
+    });
+    t.equal(argv.zoom, 55);
+    t.equal(argv.z, argv.zoom);
+    t.equal(argv.z, argv.zm);
+    t.equal(argv.f, 11);
+    t.end();
+});
+
+test('nested dotted objects', function (t) {
+    var argv = parse([
+        '--foo.bar', '3', '--foo.baz', '4',
+        '--foo.quux.quibble', '5', '--foo.quux.o_O',
+        '--beep.boop'
+    ]);
+    
+    t.same(argv.foo, {
+        bar : 3,
+        baz : 4,
+        quux : {
+            quibble : 5,
+            o_O : true
+        }
+    });
+    t.same(argv.beep, { boop : true });
+    t.end();
+});
+
+test('boolean and alias with chainable api', function (t) {
+    var aliased = [ '-h', 'derp' ];
+    var regular = [ '--herp',  'derp' ];
+    var opts = {
+        herp: { alias: 'h', boolean: true }
+    };
+    var aliasedArgv = parse(aliased, {
+        boolean: 'herp',
+        alias: { h: 'herp' }
+    });
+    var propertyArgv = parse(regular, {
+        boolean: 'herp',
+        alias: { h: 'herp' }
+    });
+    var expected = {
+        herp: true,
+        h: true,
+        '_': [ 'derp' ]
+    };
+    
+    t.same(aliasedArgv, expected);
+    t.same(propertyArgv, expected); 
+    t.end();
+});
+
+test('boolean and alias with options hash', function (t) {
+    var aliased = [ '-h', 'derp' ];
+    var regular = [ '--herp', 'derp' ];
+    var opts = {
+        alias: { 'h': 'herp' },
+        boolean: 'herp'
+    };
+    var aliasedArgv = parse(aliased, opts);
+    var propertyArgv = parse(regular, opts);
+    var expected = {
+        herp: true,
+        h: true,
+        '_': [ 'derp' ]
+    };
+    t.same(aliasedArgv, expected);
+    t.same(propertyArgv, expected);
+    t.end();
+});
+
+test('boolean and alias using explicit true', function (t) {
+    var aliased = [ '-h', 'true' ];
+    var regular = [ '--herp',  'true' ];
+    var opts = {
+        alias: { h: 'herp' },
+        boolean: 'h'
+    };
+    var aliasedArgv = parse(aliased, opts);
+    var propertyArgv = parse(regular, opts);
+    var expected = {
+        herp: true,
+        h: true,
+        '_': [ ]
+    };
+
+    t.same(aliasedArgv, expected);
+    t.same(propertyArgv, expected); 
+    t.end();
+});
+
+// regression, see https://github.com/substack/node-optimist/issues/71
+test('boolean and --x=true', function(t) {
+    var parsed = parse(['--boool', '--other=true'], {
+        boolean: 'boool'
+    });
+
+    t.same(parsed.boool, true);
+    t.same(parsed.other, 'true');
+
+    parsed = parse(['--boool', '--other=false'], {
+        boolean: 'boool'
+    });
+    
+    t.same(parsed.boool, true);
+    t.same(parsed.other, 'false');
+    t.end();
+});
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/node_modules/minimist/test/parse_modified.js b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/node_modules/minimist/test/parse_modified.js
new file mode 100644
index 0000000..21851b0
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/node_modules/minimist/test/parse_modified.js
@@ -0,0 +1,9 @@
+var parse = require('../');
+var test = require('tape');
+
+test('parse with modifier functions' , function (t) {
+    t.plan(1);
+    
+    var argv = parse([ '-b', '123' ], { boolean: 'b' });
+    t.deepEqual(argv, { b: true, _: ['123'] });
+});
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/node_modules/minimist/test/short.js b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/node_modules/minimist/test/short.js
new file mode 100644
index 0000000..d513a1c
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/node_modules/minimist/test/short.js
@@ -0,0 +1,67 @@
+var parse = require('../');
+var test = require('tape');
+
+test('numeric short args', function (t) {
+    t.plan(2);
+    t.deepEqual(parse([ '-n123' ]), { n: 123, _: [] });
+    t.deepEqual(
+        parse([ '-123', '456' ]),
+        { 1: true, 2: true, 3: 456, _: [] }
+    );
+});
+
+test('short', function (t) {
+    t.deepEqual(
+        parse([ '-b' ]),
+        { b : true, _ : [] },
+        'short boolean'
+    );
+    t.deepEqual(
+        parse([ 'foo', 'bar', 'baz' ]),
+        { _ : [ 'foo', 'bar', 'baz' ] },
+        'bare'
+    );
+    t.deepEqual(
+        parse([ '-cats' ]),
+        { c : true, a : true, t : true, s : true, _ : [] },
+        'group'
+    );
+    t.deepEqual(
+        parse([ '-cats', 'meow' ]),
+        { c : true, a : true, t : true, s : 'meow', _ : [] },
+        'short group next'
+    );
+    t.deepEqual(
+        parse([ '-h', 'localhost' ]),
+        { h : 'localhost', _ : [] },
+        'short capture'
+    );
+    t.deepEqual(
+        parse([ '-h', 'localhost', '-p', '555' ]),
+        { h : 'localhost', p : 555, _ : [] },
+        'short captures'
+    );
+    t.end();
+});
+ 
+test('mixed short bool and capture', function (t) {
+    t.same(
+        parse([ '-h', 'localhost', '-fp', '555', 'script.js' ]),
+        {
+            f : true, p : 555, h : 'localhost',
+            _ : [ 'script.js' ]
+        }
+    );
+    t.end();
+});
+ 
+test('short and long', function (t) {
+    t.deepEqual(
+        parse([ '-h', 'localhost', '-fp', '555', 'script.js' ]),
+        {
+            f : true, p : 555, h : 'localhost',
+            _ : [ 'script.js' ]
+        }
+    );
+    t.end();
+});
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/node_modules/minimist/test/whitespace.js b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/node_modules/minimist/test/whitespace.js
new file mode 100644
index 0000000..8a52a58
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/node_modules/minimist/test/whitespace.js
@@ -0,0 +1,8 @@
+var parse = require('../');
+var test = require('tape');
+
+test('whitespace should be whitespace' , function (t) {
+    t.plan(1);
+    var x = parse([ '-x', '\t' ]).x;
+    t.equal(x, '\t');
+});
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/package.json b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/package.json
new file mode 100644
index 0000000..1220910
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/package.json
@@ -0,0 +1,59 @@
+{
+  "name": "mkdirp",
+  "description": "Recursively mkdir, like `mkdir -p`",
+  "version": "0.5.0",
+  "author": {
+    "name": "James Halliday",
+    "email": "mail@substack.net",
+    "url": "http://substack.net"
+  },
+  "main": "./index",
+  "keywords": [
+    "mkdir",
+    "directory"
+  ],
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/substack/node-mkdirp.git"
+  },
+  "scripts": {
+    "test": "tap test/*.js"
+  },
+  "dependencies": {
+    "minimist": "0.0.8"
+  },
+  "devDependencies": {
+    "tap": "~0.4.0",
+    "mock-fs": "~2.2.0"
+  },
+  "bin": {
+    "mkdirp": "bin/cmd.js"
+  },
+  "license": "MIT",
+  "bugs": {
+    "url": "https://github.com/substack/node-mkdirp/issues"
+  },
+  "homepage": "https://github.com/substack/node-mkdirp",
+  "_id": "mkdirp@0.5.0",
+  "dist": {
+    "shasum": "1d73076a6df986cd9344e15e71fcc05a4c9abf12",
+    "tarball": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.0.tgz"
+  },
+  "_from": "mkdirp@0.5.x",
+  "_npmVersion": "1.4.3",
+  "_npmUser": {
+    "name": "substack",
+    "email": "mail@substack.net"
+  },
+  "maintainers": [
+    {
+      "name": "substack",
+      "email": "mail@substack.net"
+    }
+  ],
+  "directories": {},
+  "_shasum": "1d73076a6df986cd9344e15e71fcc05a4c9abf12",
+  "_resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.0.tgz",
+  "readme": "# mkdirp\n\nLike `mkdir -p`, but in node.js!\n\n[![build status](https://secure.travis-ci.org/substack/node-mkdirp.png)](http://travis-ci.org/substack/node-mkdirp)\n\n# example\n\n## pow.js\n\n```js\nvar mkdirp = require('mkdirp');\n    \nmkdirp('/tmp/foo/bar/baz', function (err) {\n    if (err) console.error(err)\n    else console.log('pow!')\n});\n```\n\nOutput\n\n```\npow!\n```\n\nAnd now /tmp/foo/bar/baz exists, huzzah!\n\n# methods\n\n```js\nvar mkdirp = require('mkdirp');\n```\n\n## mkdirp(dir, opts, cb)\n\nCreate a new directory and any necessary subdirectories at `dir` with octal\npermission string `opts.mode`. If `opts` is a non-object, it will be treated as\nthe `opts.mode`.\n\nIf `opts.mode` isn't specified, it defaults to `0777 & (~process.umask())`.\n\n`cb(err, made)` fires with the error or the first directory `made`\nthat had to be created, if any.\n\nYou can optionally pass in an alternate `fs` implementation by passing in\n`opts.fs`. Your implementation should have `opts.fs.mkdir(path, mode, cb)` and\n`opts.fs.stat(path, cb)`.\n\n## mkdirp.sync(dir, opts)\n\nSynchronously create a new directory and any necessary subdirectories at `dir`\nwith octal permission string `opts.mode`. If `opts` is a non-object, it will be\ntreated as the `opts.mode`.\n\nIf `opts.mode` isn't specified, it defaults to `0777 & (~process.umask())`.\n\nReturns the first directory that had to be created, if any.\n\nYou can optionally pass in an alternate `fs` implementation by passing in\n`opts.fs`. Your implementation should have `opts.fs.mkdirSync(path, mode)` and\n`opts.fs.statSync(path)`.\n\n# usage\n\nThis package also ships with a `mkdirp` command.\n\n```\nusage: mkdirp [DIR1,DIR2..] {OPTIONS}\n\n  Create each supplied directory including any necessary parent directories that\n  don't yet exist.\n  \n  If the directory already exists, do nothing.\n\nOPTIONS are:\n\n  -m, --mode   If a directory needs to be created, set the mode as an octal\n               permission string.\n\n```\n\n# install\n\nWith [npm](http://npmjs.org) do:\n\n```\nnpm install mkdirp\n```\n\nto get the library, or\n\n```\nnpm install -g mkdirp\n```\n\nto get the command.\n\n# license\n\nMIT\n",
+  "readmeFilename": "readme.markdown"
+}
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/readme.markdown b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/readme.markdown
new file mode 100644
index 0000000..3cc1315
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/readme.markdown
@@ -0,0 +1,100 @@
+# mkdirp
+
+Like `mkdir -p`, but in node.js!
+
+[![build status](https://secure.travis-ci.org/substack/node-mkdirp.png)](http://travis-ci.org/substack/node-mkdirp)
+
+# example
+
+## pow.js
+
+```js
+var mkdirp = require('mkdirp');
+    
+mkdirp('/tmp/foo/bar/baz', function (err) {
+    if (err) console.error(err)
+    else console.log('pow!')
+});
+```
+
+Output
+
+```
+pow!
+```
+
+And now /tmp/foo/bar/baz exists, huzzah!
+
+# methods
+
+```js
+var mkdirp = require('mkdirp');
+```
+
+## mkdirp(dir, opts, cb)
+
+Create a new directory and any necessary subdirectories at `dir` with octal
+permission string `opts.mode`. If `opts` is a non-object, it will be treated as
+the `opts.mode`.
+
+If `opts.mode` isn't specified, it defaults to `0777 & (~process.umask())`.
+
+`cb(err, made)` fires with the error or the first directory `made`
+that had to be created, if any.
+
+You can optionally pass in an alternate `fs` implementation by passing in
+`opts.fs`. Your implementation should have `opts.fs.mkdir(path, mode, cb)` and
+`opts.fs.stat(path, cb)`.
+
+## mkdirp.sync(dir, opts)
+
+Synchronously create a new directory and any necessary subdirectories at `dir`
+with octal permission string `opts.mode`. If `opts` is a non-object, it will be
+treated as the `opts.mode`.
+
+If `opts.mode` isn't specified, it defaults to `0777 & (~process.umask())`.
+
+Returns the first directory that had to be created, if any.
+
+You can optionally pass in an alternate `fs` implementation by passing in
+`opts.fs`. Your implementation should have `opts.fs.mkdirSync(path, mode)` and
+`opts.fs.statSync(path)`.
+
+# usage
+
+This package also ships with a `mkdirp` command.
+
+```
+usage: mkdirp [DIR1,DIR2..] {OPTIONS}
+
+  Create each supplied directory including any necessary parent directories that
+  don't yet exist.
+  
+  If the directory already exists, do nothing.
+
+OPTIONS are:
+
+  -m, --mode   If a directory needs to be created, set the mode as an octal
+               permission string.
+
+```
+
+# install
+
+With [npm](http://npmjs.org) do:
+
+```
+npm install mkdirp
+```
+
+to get the library, or
+
+```
+npm install -g mkdirp
+```
+
+to get the command.
+
+# license
+
+MIT
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/test/chmod.js b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/test/chmod.js
new file mode 100644
index 0000000..520dcb8
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/test/chmod.js
@@ -0,0 +1,38 @@
+var mkdirp = require('../').mkdirp;
+var path = require('path');
+var fs = require('fs');
+var test = require('tap').test;
+
+var ps = [ '', 'tmp' ];
+
+for (var i = 0; i < 25; i++) {
+    var dir = Math.floor(Math.random() * Math.pow(16,4)).toString(16);
+    ps.push(dir);
+}
+
+var file = ps.join('/');
+
+test('chmod-pre', function (t) {
+    var mode = 0744
+    mkdirp(file, mode, function (er) {
+        t.ifError(er, 'should not error');
+        fs.stat(file, function (er, stat) {
+            t.ifError(er, 'should exist');
+            t.ok(stat && stat.isDirectory(), 'should be directory');
+            t.equal(stat && stat.mode & 0777, mode, 'should be 0744');
+            t.end();
+        });
+    });
+});
+
+test('chmod', function (t) {
+    var mode = 0755
+    mkdirp(file, mode, function (er) {
+        t.ifError(er, 'should not error');
+        fs.stat(file, function (er, stat) {
+            t.ifError(er, 'should exist');
+            t.ok(stat && stat.isDirectory(), 'should be directory');
+            t.end();
+        });
+    });
+});
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/test/clobber.js b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/test/clobber.js
new file mode 100644
index 0000000..0eb7099
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/test/clobber.js
@@ -0,0 +1,37 @@
+var mkdirp = require('../').mkdirp;
+var path = require('path');
+var fs = require('fs');
+var test = require('tap').test;
+
+var ps = [ '', 'tmp' ];
+
+for (var i = 0; i < 25; i++) {
+    var dir = Math.floor(Math.random() * Math.pow(16,4)).toString(16);
+    ps.push(dir);
+}
+
+var file = ps.join('/');
+
+// a file in the way
+var itw = ps.slice(0, 3).join('/');
+
+
+test('clobber-pre', function (t) {
+    console.error("about to write to "+itw)
+    fs.writeFileSync(itw, 'I AM IN THE WAY, THE TRUTH, AND THE LIGHT.');
+
+    fs.stat(itw, function (er, stat) {
+        t.ifError(er)
+        t.ok(stat && stat.isFile(), 'should be file')
+        t.end()
+    })
+})
+
+test('clobber', function (t) {
+    t.plan(2);
+    mkdirp(file, 0755, function (err) {
+        t.ok(err);
+        t.equal(err.code, 'ENOTDIR');
+        t.end();
+    });
+});
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/test/mkdirp.js b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/test/mkdirp.js
new file mode 100644
index 0000000..3b624dd
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/test/mkdirp.js
@@ -0,0 +1,26 @@
+var mkdirp = require('../');
+var path = require('path');
+var fs = require('fs');
+var exists = fs.exists || path.exists;
+var test = require('tap').test;
+
+test('woo', function (t) {
+    t.plan(5);
+    var x = Math.floor(Math.random() * Math.pow(16,4)).toString(16);
+    var y = Math.floor(Math.random() * Math.pow(16,4)).toString(16);
+    var z = Math.floor(Math.random() * Math.pow(16,4)).toString(16);
+    
+    var file = '/tmp/' + [x,y,z].join('/');
+    
+    mkdirp(file, 0755, function (err) {
+        t.ifError(err);
+        exists(file, function (ex) {
+            t.ok(ex, 'file created');
+            fs.stat(file, function (err, stat) {
+                t.ifError(err);
+                t.equal(stat.mode & 0777, 0755);
+                t.ok(stat.isDirectory(), 'target not a directory');
+            })
+        })
+    });
+});
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/test/opts_fs.js b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/test/opts_fs.js
new file mode 100644
index 0000000..f1fbeca
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/test/opts_fs.js
@@ -0,0 +1,27 @@
+var mkdirp = require('../');
+var path = require('path');
+var test = require('tap').test;
+var mockfs = require('mock-fs');
+
+test('opts.fs', function (t) {
+    t.plan(5);
+    
+    var x = Math.floor(Math.random() * Math.pow(16,4)).toString(16);
+    var y = Math.floor(Math.random() * Math.pow(16,4)).toString(16);
+    var z = Math.floor(Math.random() * Math.pow(16,4)).toString(16);
+    
+    var file = '/beep/boop/' + [x,y,z].join('/');
+    var xfs = mockfs.fs();
+    
+    mkdirp(file, { fs: xfs, mode: 0755 }, function (err) {
+        t.ifError(err);
+        xfs.exists(file, function (ex) {
+            t.ok(ex, 'created file');
+            xfs.stat(file, function (err, stat) {
+                t.ifError(err);
+                t.equal(stat.mode & 0777, 0755);
+                t.ok(stat.isDirectory(), 'target not a directory');
+            });
+        });
+    });
+});
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/test/opts_fs_sync.js b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/test/opts_fs_sync.js
new file mode 100644
index 0000000..224b506
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/test/opts_fs_sync.js
@@ -0,0 +1,25 @@
+var mkdirp = require('../');
+var path = require('path');
+var test = require('tap').test;
+var mockfs = require('mock-fs');
+
+test('opts.fs sync', function (t) {
+    t.plan(4);
+    
+    var x = Math.floor(Math.random() * Math.pow(16,4)).toString(16);
+    var y = Math.floor(Math.random() * Math.pow(16,4)).toString(16);
+    var z = Math.floor(Math.random() * Math.pow(16,4)).toString(16);
+    
+    var file = '/beep/boop/' + [x,y,z].join('/');
+    var xfs = mockfs.fs();
+    
+    mkdirp.sync(file, { fs: xfs, mode: 0755 });
+    xfs.exists(file, function (ex) {
+        t.ok(ex, 'created file');
+        xfs.stat(file, function (err, stat) {
+            t.ifError(err);
+            t.equal(stat.mode & 0777, 0755);
+            t.ok(stat.isDirectory(), 'target not a directory');
+        });
+    });
+});
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/test/perm.js b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/test/perm.js
new file mode 100644
index 0000000..2c97590
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/test/perm.js
@@ -0,0 +1,30 @@
+var mkdirp = require('../');
+var path = require('path');
+var fs = require('fs');
+var exists = fs.exists || path.exists;
+var test = require('tap').test;
+
+test('async perm', function (t) {
+    t.plan(5);
+    var file = '/tmp/' + (Math.random() * (1<<30)).toString(16);
+    
+    mkdirp(file, 0755, function (err) {
+        t.ifError(err);
+        exists(file, function (ex) {
+            t.ok(ex, 'file created');
+            fs.stat(file, function (err, stat) {
+                t.ifError(err);
+                t.equal(stat.mode & 0777, 0755);
+                t.ok(stat.isDirectory(), 'target not a directory');
+            })
+        })
+    });
+});
+
+test('async root perm', function (t) {
+    mkdirp('/tmp', 0755, function (err) {
+        if (err) t.fail(err);
+        t.end();
+    });
+    t.end();
+});
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/test/perm_sync.js b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/test/perm_sync.js
new file mode 100644
index 0000000..327e54b
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/test/perm_sync.js
@@ -0,0 +1,34 @@
+var mkdirp = require('../');
+var path = require('path');
+var fs = require('fs');
+var exists = fs.exists || path.exists;
+var test = require('tap').test;
+
+test('sync perm', function (t) {
+    t.plan(4);
+    var file = '/tmp/' + (Math.random() * (1<<30)).toString(16) + '.json';
+    
+    mkdirp.sync(file, 0755);
+    exists(file, function (ex) {
+        t.ok(ex, 'file created');
+        fs.stat(file, function (err, stat) {
+            t.ifError(err);
+            t.equal(stat.mode & 0777, 0755);
+            t.ok(stat.isDirectory(), 'target not a directory');
+        });
+    });
+});
+
+test('sync root perm', function (t) {
+    t.plan(3);
+    
+    var file = '/tmp';
+    mkdirp.sync(file, 0755);
+    exists(file, function (ex) {
+        t.ok(ex, 'file created');
+        fs.stat(file, function (err, stat) {
+            t.ifError(err);
+            t.ok(stat.isDirectory(), 'target not a directory');
+        })
+    });
+});
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/test/race.js b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/test/race.js
new file mode 100644
index 0000000..7c295f4
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/test/race.js
@@ -0,0 +1,40 @@
+var mkdirp = require('../').mkdirp;
+var path = require('path');
+var fs = require('fs');
+var exists = fs.exists || path.exists;
+var test = require('tap').test;
+
+test('race', function (t) {
+    t.plan(6);
+    var ps = [ '', 'tmp' ];
+    
+    for (var i = 0; i < 25; i++) {
+        var dir = Math.floor(Math.random() * Math.pow(16,4)).toString(16);
+        ps.push(dir);
+    }
+    var file = ps.join('/');
+    
+    var res = 2;
+    mk(file, function () {
+        if (--res === 0) t.end();
+    });
+    
+    mk(file, function () {
+        if (--res === 0) t.end();
+    });
+    
+    function mk (file, cb) {
+        mkdirp(file, 0755, function (err) {
+            t.ifError(err);
+            exists(file, function (ex) {
+                t.ok(ex, 'file created');
+                fs.stat(file, function (err, stat) {
+                    t.ifError(err);
+                    t.equal(stat.mode & 0777, 0755);
+                    t.ok(stat.isDirectory(), 'target not a directory');
+                    if (cb) cb();
+                });
+            })
+        });
+    }
+});
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/test/rel.js b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/test/rel.js
new file mode 100644
index 0000000..d1f175c
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/test/rel.js
@@ -0,0 +1,30 @@
+var mkdirp = require('../');
+var path = require('path');
+var fs = require('fs');
+var exists = fs.exists || path.exists;
+var test = require('tap').test;
+
+test('rel', function (t) {
+    t.plan(5);
+    var x = Math.floor(Math.random() * Math.pow(16,4)).toString(16);
+    var y = Math.floor(Math.random() * Math.pow(16,4)).toString(16);
+    var z = Math.floor(Math.random() * Math.pow(16,4)).toString(16);
+    
+    var cwd = process.cwd();
+    process.chdir('/tmp');
+    
+    var file = [x,y,z].join('/');
+    
+    mkdirp(file, 0755, function (err) {
+        t.ifError(err);
+        exists(file, function (ex) {
+            t.ok(ex, 'file created');
+            fs.stat(file, function (err, stat) {
+                t.ifError(err);
+                process.chdir(cwd);
+                t.equal(stat.mode & 0777, 0755);
+                t.ok(stat.isDirectory(), 'target not a directory');
+            })
+        })
+    });
+});
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/test/return.js b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/test/return.js
new file mode 100644
index 0000000..bce68e5
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/test/return.js
@@ -0,0 +1,25 @@
+var mkdirp = require('../');
+var path = require('path');
+var fs = require('fs');
+var test = require('tap').test;
+
+test('return value', function (t) {
+    t.plan(4);
+    var x = Math.floor(Math.random() * Math.pow(16,4)).toString(16);
+    var y = Math.floor(Math.random() * Math.pow(16,4)).toString(16);
+    var z = Math.floor(Math.random() * Math.pow(16,4)).toString(16);
+
+    var file = '/tmp/' + [x,y,z].join('/');
+
+    // should return the first dir created.
+    // By this point, it would be profoundly surprising if /tmp didn't
+    // already exist, since every other test makes things in there.
+    mkdirp(file, function (err, made) {
+        t.ifError(err);
+        t.equal(made, '/tmp/' + x);
+        mkdirp(file, function (err, made) {
+          t.ifError(err);
+          t.equal(made, null);
+        });
+    });
+});
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/test/return_sync.js b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/test/return_sync.js
new file mode 100644
index 0000000..7c222d3
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/test/return_sync.js
@@ -0,0 +1,24 @@
+var mkdirp = require('../');
+var path = require('path');
+var fs = require('fs');
+var test = require('tap').test;
+
+test('return value', function (t) {
+    t.plan(2);
+    var x = Math.floor(Math.random() * Math.pow(16,4)).toString(16);
+    var y = Math.floor(Math.random() * Math.pow(16,4)).toString(16);
+    var z = Math.floor(Math.random() * Math.pow(16,4)).toString(16);
+
+    var file = '/tmp/' + [x,y,z].join('/');
+
+    // should return the first dir created.
+    // By this point, it would be profoundly surprising if /tmp didn't
+    // already exist, since every other test makes things in there.
+    // Note that this will throw on failure, which will fail the test.
+    var made = mkdirp.sync(file);
+    t.equal(made, '/tmp/' + x);
+
+    // making the same file again should have no effect.
+    made = mkdirp.sync(file);
+    t.equal(made, null);
+});
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/test/root.js b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/test/root.js
new file mode 100644
index 0000000..97ad7a2
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/test/root.js
@@ -0,0 +1,18 @@
+var mkdirp = require('../');
+var path = require('path');
+var fs = require('fs');
+var test = require('tap').test;
+
+test('root', function (t) {
+    // '/' on unix, 'c:/' on windows.
+    var file = path.resolve('/');
+
+    mkdirp(file, 0755, function (err) {
+        if (err) throw err
+        fs.stat(file, function (er, stat) {
+            if (er) throw er
+            t.ok(stat.isDirectory(), 'target is a directory');
+            t.end();
+        })
+    });
+});
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/test/sync.js b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/test/sync.js
new file mode 100644
index 0000000..88fa432
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/test/sync.js
@@ -0,0 +1,30 @@
+var mkdirp = require('../');
+var path = require('path');
+var fs = require('fs');
+var exists = fs.exists || path.exists;
+var test = require('tap').test;
+
+test('sync', function (t) {
+    t.plan(4);
+    var x = Math.floor(Math.random() * Math.pow(16,4)).toString(16);
+    var y = Math.floor(Math.random() * Math.pow(16,4)).toString(16);
+    var z = Math.floor(Math.random() * Math.pow(16,4)).toString(16);
+
+    var file = '/tmp/' + [x,y,z].join('/');
+
+    try {
+        mkdirp.sync(file, 0755);
+    } catch (err) {
+        t.fail(err);
+        return t.end();
+    }
+
+    exists(file, function (ex) {
+        t.ok(ex, 'file created');
+        fs.stat(file, function (err, stat) {
+            t.ifError(err);
+            t.equal(stat.mode & 0777, 0755);
+            t.ok(stat.isDirectory(), 'target not a directory');
+        });
+    });
+});
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/test/umask.js b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/test/umask.js
new file mode 100644
index 0000000..82c393a
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/test/umask.js
@@ -0,0 +1,26 @@
+var mkdirp = require('../');
+var path = require('path');
+var fs = require('fs');
+var exists = fs.exists || path.exists;
+var test = require('tap').test;
+
+test('implicit mode from umask', function (t) {
+    t.plan(5);
+    var x = Math.floor(Math.random() * Math.pow(16,4)).toString(16);
+    var y = Math.floor(Math.random() * Math.pow(16,4)).toString(16);
+    var z = Math.floor(Math.random() * Math.pow(16,4)).toString(16);
+    
+    var file = '/tmp/' + [x,y,z].join('/');
+    
+    mkdirp(file, function (err) {
+        t.ifError(err);
+        exists(file, function (ex) {
+            t.ok(ex, 'file created');
+            fs.stat(file, function (err, stat) {
+                t.ifError(err);
+                t.equal(stat.mode & 0777, 0777 & (~process.umask()));
+                t.ok(stat.isDirectory(), 'target not a directory');
+            });
+        })
+    });
+});
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/test/umask_sync.js b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/test/umask_sync.js
new file mode 100644
index 0000000..e537fbe
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/node_modules/mkdirp/test/umask_sync.js
@@ -0,0 +1,30 @@
+var mkdirp = require('../');
+var path = require('path');
+var fs = require('fs');
+var exists = fs.exists || path.exists;
+var test = require('tap').test;
+
+test('umask sync modes', function (t) {
+    t.plan(4);
+    var x = Math.floor(Math.random() * Math.pow(16,4)).toString(16);
+    var y = Math.floor(Math.random() * Math.pow(16,4)).toString(16);
+    var z = Math.floor(Math.random() * Math.pow(16,4)).toString(16);
+
+    var file = '/tmp/' + [x,y,z].join('/');
+
+    try {
+        mkdirp.sync(file);
+    } catch (err) {
+        t.fail(err);
+        return t.end();
+    }
+
+    exists(file, function (ex) {
+        t.ok(ex, 'file created');
+        fs.stat(file, function (err, stat) {
+            t.ifError(err);
+            t.equal(stat.mode & 0777, (0777 & (~process.umask())));
+            t.ok(stat.isDirectory(), 'target not a directory');
+        });
+    });
+});
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/package.json b/node_modules/node-firefox-forward-ports/node_modules/portfinder/package.json
new file mode 100644
index 0000000..4014659
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/package.json
@@ -0,0 +1,60 @@
+{
+  "name": "portfinder",
+  "version": "0.4.0",
+  "description": "A simple tool to find an open port on the current machine",
+  "author": {
+    "name": "Charlie Robbins",
+    "email": "charlie.robbins@gmail.com"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git@github.com:indexzero/node-portfinder.git"
+  },
+  "keywords": [
+    "http",
+    "ports",
+    "utilities"
+  ],
+  "dependencies": {
+    "async": "0.9.0",
+    "mkdirp": "0.5.x"
+  },
+  "devDependencies": {
+    "vows": "0.8.0"
+  },
+  "main": "./lib/portfinder",
+  "scripts": {
+    "test": "vows test/*-test.js --spec"
+  },
+  "engines": {
+    "node": ">= 0.8.0"
+  },
+  "license": "MIT/X11",
+  "gitHead": "8c3f20bf1d5ec399262c592f61129a65727004a9",
+  "bugs": {
+    "url": "https://github.com/indexzero/node-portfinder/issues"
+  },
+  "homepage": "https://github.com/indexzero/node-portfinder",
+  "_id": "portfinder@0.4.0",
+  "_shasum": "a3ffadffafe4fb98e0601a85eda27c27ce84ca1e",
+  "_from": "portfinder@^0.4.0",
+  "_npmVersion": "2.2.0",
+  "_nodeVersion": "0.10.33",
+  "_npmUser": {
+    "name": "indexzero",
+    "email": "charlie.robbins@gmail.com"
+  },
+  "maintainers": [
+    {
+      "name": "indexzero",
+      "email": "charlie.robbins@gmail.com"
+    }
+  ],
+  "dist": {
+    "shasum": "a3ffadffafe4fb98e0601a85eda27c27ce84ca1e",
+    "tarball": "http://registry.npmjs.org/portfinder/-/portfinder-0.4.0.tgz"
+  },
+  "directories": {},
+  "_resolved": "https://registry.npmjs.org/portfinder/-/portfinder-0.4.0.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/test/fixtures/.gitkeep b/node_modules/node-firefox-forward-ports/node_modules/portfinder/test/fixtures/.gitkeep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/test/fixtures/.gitkeep
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/test/port-finder-multiple-test.js b/node_modules/node-firefox-forward-ports/node_modules/portfinder/test/port-finder-multiple-test.js
new file mode 100644
index 0000000..ede487e
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/test/port-finder-multiple-test.js
@@ -0,0 +1,67 @@
+/*
+ * portfinder-test.js: Tests for the `portfinder` module.
+ *
+ * (C) 2011, Charlie Robbins
+ *
+ */
+
+var vows = require('vows'),
+    assert = require('assert'),
+    async = require('async'),
+    http = require('http'),
+    portfinder = require('../lib/portfinder');
+
+var servers = [];
+
+function createServers (callback) {
+  var base = 8000;
+
+  async.whilst(
+    function () { return base < 8005 },
+    function (next) {
+      var server = http.createServer(function () { });
+      server.listen(base, next);
+      base++;
+      servers.push(server);
+    }, callback);
+}
+
+vows.describe('portfinder').addBatch({
+  "When using portfinder module": {
+    "with 5 existing servers": {
+      topic: function () {
+        createServers(this.callback);
+      },
+      "the getPorts() method with an argument of 3": {
+        topic: function () {
+          portfinder.getPorts(3, this.callback);
+        },
+        "should respond with the first three available ports (8005, 8006, 8007)": function (err, ports) {
+          assert.isTrue(!err);
+          assert.deepEqual(ports, [8005, 8006, 8007]);
+        }
+      }
+    }
+  }
+}).addBatch({
+  "When using portfinder module": {
+    "with no existing servers": {
+      topic: function () {
+        servers.forEach(function (server) {
+          server.close();
+        });
+
+        return null;
+      },
+      "the getPorts() method with an argument of 3": {
+        topic: function () {
+          portfinder.getPorts(3, this.callback);
+        },
+        "should respond with the first three available ports (8000, 8001, 80072": function (err, ports) {
+          assert.isTrue(!err);
+          assert.deepEqual(ports, [8000, 8001, 8002]);
+        }
+      }
+    }
+  }
+}).export(module);
\ No newline at end of file
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/test/port-finder-socket-test.js b/node_modules/node-firefox-forward-ports/node_modules/portfinder/test/port-finder-socket-test.js
new file mode 100644
index 0000000..91c840c
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/test/port-finder-socket-test.js
@@ -0,0 +1,92 @@
+/*
+ * portfinder-test.js: Tests for the `portfinder` module.
+ *
+ * (C) 2011, Charlie Robbins
+ *
+ */
+
+var assert = require('assert'),
+    exec = require('child_process').exec,
+    net = require('net'),
+    path = require('path'),
+    async = require('async'),
+    vows = require('vows'),
+    portfinder = require('../lib/portfinder');
+
+var servers = [],
+    socketDir = path.join(__dirname, 'fixtures'),
+    badDir = path.join(__dirname, 'bad-dir');
+
+function createServers (callback) {
+  var base = 0;
+  
+  async.whilst(
+    function () { return base < 5 },
+    function (next) {
+      var server = net.createServer(function () { }),
+          name = base === 0 ? 'test.sock' : 'test' + base + '.sock';
+      
+      server.listen(path.join(socketDir, name), next);
+      base++;
+      servers.push(server);
+    }, callback);
+}
+
+vows.describe('portfinder').addBatch({
+  "When using portfinder module": {
+    "with 5 existing servers": {
+      topic: function () {
+        createServers(this.callback);
+      },
+      "the getPort() method": {
+        topic: function () {
+          portfinder.getSocket({
+            path: path.join(socketDir, 'test.sock')
+          }, this.callback);
+        },
+        "should respond with the first free socket (test5.sock)": function (err, socket) {
+          assert.isTrue(!err);
+          assert.equal(socket, path.join(socketDir, 'test5.sock'));
+        }
+      }
+    }
+  }
+}).addBatch({
+  "When using portfinder module": {
+    "with no existing servers": {
+      "the getSocket() method": {
+        "with a directory that doesnt exist": {
+          topic: function () {
+            var that = this;
+            exec('rm -rf ' + badDir, function () {
+              portfinder.getSocket({
+                path: path.join(badDir, 'test.sock')
+              }, that.callback);
+            });
+          },
+          "should respond with the first free socket (test.sock)": function (err, socket) {
+            assert.isTrue(!err);
+            assert.equal(socket, path.join(badDir, 'test.sock'));
+          }
+        },
+        "with a directory that exists": {
+          topic: function () {
+            portfinder.getSocket({
+              path: path.join(socketDir, 'exists.sock')
+            }, this.callback);
+          },
+          "should respond with the first free socket (exists.sock)": function (err, socket) {
+            assert.isTrue(!err);
+            assert.equal(socket, path.join(socketDir, 'exists.sock'));
+          }
+        }
+      }
+    }
+  }
+}).addBatch({
+  "When the tests are over": {
+    "necessary cleanup should take place": function () {
+      exec('rm -rf ' + badDir + ' ' + path.join(socketDir, '*'), function () { });
+    }
+  }
+}).export(module);
\ No newline at end of file
diff --git a/node_modules/node-firefox-forward-ports/node_modules/portfinder/test/port-finder-test.js b/node_modules/node-firefox-forward-ports/node_modules/portfinder/test/port-finder-test.js
new file mode 100644
index 0000000..c10ded9
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/node_modules/portfinder/test/port-finder-test.js
@@ -0,0 +1,67 @@
+/*
+ * portfinder-test.js: Tests for the `portfinder` module.
+ *
+ * (C) 2011, Charlie Robbins
+ *
+ */
+
+var vows = require('vows'),
+    assert = require('assert'),
+    async = require('async'),
+    http = require('http'),
+    portfinder = require('../lib/portfinder');
+
+var servers = [];
+
+function createServers (callback) {
+  var base = 8000;
+  
+  async.whilst(
+    function () { return base < 8005 },
+    function (next) {
+      var server = http.createServer(function () { });
+      server.listen(base, next);
+      base++;
+      servers.push(server);
+    }, callback);
+}
+
+vows.describe('portfinder').addBatch({
+  "When using portfinder module": {
+    "with 5 existing servers": {
+      topic: function () {
+        createServers(this.callback);
+      },
+      "the getPort() method": {
+        topic: function () {
+          portfinder.getPort(this.callback);
+        },
+        "should respond with the first free port (8005)": function (err, port) {
+          assert.isTrue(!err);
+          assert.equal(port, 8005);
+        }
+      }
+    }
+  }
+}).addBatch({
+  "When using portfinder module": {
+    "with no existing servers": {
+      topic: function () {
+        servers.forEach(function (server) {
+          server.close();
+        });
+        
+        return null;
+      },
+      "the getPort() method": {
+        topic: function () {
+          portfinder.getPort(this.callback);
+        },
+        "should respond with the first free port (8000)": function (err, port) {
+          assert.isTrue(!err);
+          assert.equal(port, 8000);
+        }
+      }
+    }
+  }
+}).export(module);
\ No newline at end of file
diff --git a/node_modules/node-firefox-forward-ports/package.json b/node_modules/node-firefox-forward-ports/package.json
new file mode 100644
index 0000000..6696fd7
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/package.json
@@ -0,0 +1,72 @@
+{
+  "name": "node-firefox-forward-ports",
+  "version": "1.0.0",
+  "description": "Forward local ports to remote debugging interfaces on connected Firefox OS devices",
+  "main": "index.js",
+  "dependencies": {
+    "adbkit": "^2.1.6",
+    "es6-promise": "^2.0.1",
+    "portfinder": "^0.4.0"
+  },
+  "devDependencies": {
+    "gulp": "^3.8.10",
+    "mockery": "^1.4.0",
+    "node-firefox-build-tools": "^0.1.0",
+    "nodemock": "^0.3.4"
+  },
+  "scripts": {
+    "gulp": "gulp",
+    "test": "gulp test"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/mozilla/node-firefox-forward-ports.git"
+  },
+  "keywords": [
+    "firefox",
+    "developer tools",
+    "b2g",
+    "firefox os",
+    "firefoxos",
+    "fxos",
+    "ports"
+  ],
+  "author": {
+    "name": "Mozilla",
+    "url": "https://mozilla.org/"
+  },
+  "contributors": [
+    {
+      "name": "Les Orchard",
+      "email": "me@lmorchard.com",
+      "url": "http://lmorchard.com"
+    }
+  ],
+  "license": "Apache 2.0",
+  "bugs": {
+    "url": "https://github.com/mozilla/node-firefox-forward-ports/issues"
+  },
+  "homepage": "https://github.com/mozilla/node-firefox-forward-ports",
+  "gitHead": "d91f23606909e9a84334a668374ee8560dbfcf46",
+  "_id": "node-firefox-forward-ports@1.0.0",
+  "_shasum": "3999dd188b739dfaff009252e02672d72085d3bb",
+  "_from": "node-firefox-forward-ports@",
+  "_npmVersion": "2.1.9",
+  "_nodeVersion": "0.10.33",
+  "_npmUser": {
+    "name": "tofumatt",
+    "email": "matt@lonelyvegan.com"
+  },
+  "maintainers": [
+    {
+      "name": "tofumatt",
+      "email": "matt@lonelyvegan.com"
+    }
+  ],
+  "dist": {
+    "shasum": "3999dd188b739dfaff009252e02672d72085d3bb",
+    "tarball": "http://registry.npmjs.org/node-firefox-forward-ports/-/node-firefox-forward-ports-1.0.0.tgz"
+  },
+  "directories": {},
+  "_resolved": "https://registry.npmjs.org/node-firefox-forward-ports/-/node-firefox-forward-ports-1.0.0.tgz"
+}
diff --git a/node_modules/node-firefox-forward-ports/tests/unit/test.index.js b/node_modules/node-firefox-forward-ports/tests/unit/test.index.js
new file mode 100644
index 0000000..4a3c742
--- /dev/null
+++ b/node_modules/node-firefox-forward-ports/tests/unit/test.index.js
@@ -0,0 +1,179 @@
+'use strict';
+
+/* global -Promise */
+var Promise = require('es6-promise').Promise;
+var mockery = require('mockery');
+var nodemock = require('nodemock');
+
+var forwardPorts = require('../../index');
+
+// nodemock expects an empty function to indicate a callback parameter
+var CALLBACK_TYPE = function() {};
+
+module.exports = {
+
+  'forwardPorts() should yield an error if the devices option is missing': function(test) {
+    forwardPorts().then(function(results) {
+      test.ok(false);
+      test.done();
+    }).catch(function(err) {
+      test.done();
+    });
+  },
+
+  'forwardPorts() should yield an error if the devices option is not an array': function(test) {
+    forwardPorts({
+      devices: 'LOL NOT AN ARRAY'
+    }).then(function(results) {
+      test.ok(false);
+      test.done();
+    }).catch(function(err) {
+      test.done();
+    });
+  },
+
+  // Note: These tests are mostly identical, except for whether forwarded
+  // ports already exist. In either case, whether ports are quietly reused or
+  // new ones forwarded, the end result returned to the developer is the same.
+
+  'forwardPorts({ devices: devices }) should forward a port for a device not yet forwarded':
+    commonForwardTest(false, true),
+
+  'forwardPorts({ devices: devices }) should skip forwarding for devices that have forwarded ports':
+    commonForwardTest(true, true),
+
+  'forwardPorts(devices) should forward a port for a device not yet forwarded':
+    commonForwardTest(false, false),
+
+  'forwardPorts(devices) should skip forwarding for devices that have forwarded ports':
+    commonForwardTest(true, false),
+
+  tearDown: function(done) {
+    mockery.disable();
+    done();
+  }
+
+};
+
+function commonForwardTest(detectForwardedPorts, useOptions) {
+  return function(test) {
+
+    var port = 9999;
+    var deviceId = '8675309';
+    var localAddress = 'tcp:'+port;
+    var remoteAddress = 'localfilesystem:/data/local/debugger-socket';
+    var devices = [ { id: deviceId } ];
+
+    var mocked;
+
+    // Vary the test on whether existing forwarded ports should be detected
+    if (detectForwardedPorts) {
+
+      // Report existing port forwards for the device.
+      mocked = nodemock
+        .mock('listForwards')
+        .returns(new Promise(function(resolve, reject) {
+          resolve([
+
+            // This is a Firefox remote debugging socket, using test values.
+            {
+              serial: deviceId,
+              local: localAddress,
+              remote: remoteAddress
+            },
+
+            // Note: This is a socket we're not interested in, so it shouldn't
+            // end up in results.
+            {
+              serial: deviceId,
+              local: 'tcp:2112',
+              remote: 'localfilesystem:/data/local/some-other-socket'
+            }
+
+          ]);
+        }));
+
+      // Any call to portfinder.getPort() or client.forward() is a failure,
+      // because port forwarding should be skipped.
+      mocked.mock('getPort').fail();
+      mocked.mock('forward').fail();
+
+    } else {
+
+      // Report no existing port forwards for the device.
+      mocked = nodemock
+        .mock('listForwards')
+        .returns(new Promise(function(resolve, reject) {
+          resolve([]);
+        }));
+
+      // portfinder.getPort() should be called to discover a free port.
+      mocked.mock('getPort')
+        .takes({ host: '127.0.0.1' }, CALLBACK_TYPE)
+        .calls(1, [null, port]);
+
+      // client.forward() should be called to create the port forward.
+      mocked.mock('forward')
+        .takes(deviceId, localAddress, remoteAddress)
+        .returns(new Promise(function(resolve, reject) { resolve(); }));
+
+    }
+
+    mockery.registerMock('portfinder', {
+      getPort: mocked.getPort
+    });
+
+    mocked.mock('createClient').returns({
+      listForwards: mocked.listForwards,
+      forward: mocked.forward
+    });
+
+    mockery.registerMock('adbkit', {
+      createClient: mocked.createClient
+    });
+
+    // Enable mocks on a clear import cache
+    mockery.enable({
+      warnOnReplace: false,
+      warnOnUnregistered: false,
+      useCleanCache: true
+    });
+
+    // Require a freshly imported forwardPorts for this test
+    var forwardPortsWithMocks = require('../../index');
+
+    // forwardPorts() can accept a simple device list, or a full options object.
+    var forwardPortsPromise = (useOptions) ?
+      forwardPortsWithMocks({ devices: devices }) :
+      forwardPortsWithMocks(devices);
+
+    forwardPortsPromise.catch(function(err) {
+      test.ifError(err);
+      test.done();
+    }).then(function(results) {
+
+      // Ensure all the mocks were called, and with the expected parameters
+      test.ok(mocked.assert());
+
+      // Result should be an array of one device.
+      test.ok(Array.isArray(results));
+      test.equal(results.length, 1);
+
+      // The single device should list device and port details.
+      var result = results[0];
+      test.equal(result.id, deviceId);
+
+      // There should only be one port - the other one should have been ignored.
+      test.equal(result.ports.length, 1);
+      test.deepEqual(result.ports[0], {
+        port: port,
+        local: localAddress,
+        remote: remoteAddress
+      });
+
+      test.done();
+
+    });
+
+  };
+}
diff --git a/node_modules/node-firefox-install-app/.npmignore b/node_modules/node-firefox-install-app/.npmignore
new file mode 100644
index 0000000..fd4f2b0
--- /dev/null
+++ b/node_modules/node-firefox-install-app/.npmignore
@@ -0,0 +1,2 @@
+node_modules
+.DS_Store
diff --git a/node_modules/node-firefox-install-app/LICENSE b/node_modules/node-firefox-install-app/LICENSE
new file mode 100644
index 0000000..a7c6b5e
--- /dev/null
+++ b/node_modules/node-firefox-install-app/LICENSE
@@ -0,0 +1,13 @@
+Copyright 2015 Mozilla
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/node_modules/node-firefox-install-app/README.md b/node_modules/node-firefox-install-app/README.md
new file mode 100644
index 0000000..a50fab5
--- /dev/null
+++ b/node_modules/node-firefox-install-app/README.md
@@ -0,0 +1,112 @@
+# node-firefox-install-app [![Build Status](https://secure.travis-ci.org/mozilla/node-firefox-install-app.png?branch=master)](http://travis-ci.org/mozilla/node-firefox-install-app)
+
+> Install an app on a runtime.
+
+This is part of the [node-firefox](https://github.com/mozilla/node-firefox) project.
+
+**NOTE**
+
+*This is a work in progress. Things will probably be missing and broken while we move from `fxos-deploy` to `node-firefox-install-app`. Please have a look at the [existing issues](https://github.com/mozilla/node-firefox-install-app/issues), and/or [file more](https://github.com/mozilla/node-firefox-install-app/issues/new) if you find any! :-)*
+
+## Installation
+
+### From git
+
+```bash
+git clone https://github.com/mozilla/node-firefox-install-app.git
+cd node-firefox-install-app
+npm install
+```
+
+If you want to update later on:
+
+```bash
+cd node-firefox-install-app
+git pull origin master
+npm install
+```
+
+### npm
+
+<!--
+```bash
+npm install node-firefox-install-app
+```
+-->
+
+This module is not on npm yet.
+
+## Usage
+
+```javascript
+installApp(options) // returns a Promise
+```
+
+where `options` is a plain `Object` which must contain the following:
+
+* `appPath`: path to the app folder
+* `client`: the remote client where we want to install this app
+
+If no `options` are provided, or if `options` is an empty `Object` (`{}`), then `installApp` will fail (how can you install *you don't know what app exactly* in *you don't know where*?)
+
+
+### Installing a packaged app on a simulator
+
+This is done by passing the path to the app folder:
+
+```javascript
+var startSimulator = require('node-firefox-start-simulator');
+var connect = require('node-firefox-connect');
+var installApp = require('node-firefox-install-app');
+
+startSimulator().then(function(simulator) {
+  connect(simulator.port).then(function(client) {
+    installApp({
+      appPath: appPath,
+      client: client
+    }).then(function(appId) {
+      console.log('App was installed with appId = ', appId);
+    }, function(error) {
+      console.error('App could not be installed: ', error);
+    });
+  });
+});
+
+
+
+```
+
+You can have a look at the `examples` folder for a complete example.
+
+## Running the tests
+
+After installing, you can simply run the following from the module folder:
+
+```bash
+npm test
+```
+
+To add a new unit test file, create a new file in the `tests/unit` folder. Any file that matches `test.*.js` will be run as a test by the appropriate test runner, based on the folder location.
+
+We use `gulp` behind the scenes to run the test; if you don't have it installed globally you can use `npm gulp` from inside the project's root folder to run `gulp`.
+
+### Code quality and style
+
+Because we have multiple contributors working on our projects, we value consistent code styles. It makes it easier to read code written by many people! :-)
+
+Our tests include unit tests as well as code quality ("linting") tests that make sure our test pass a style guide and [JSHint](http://jshint.com/). Instead of submitting code with the wrong indentation or a different style, run the tests and you will be told where your code quality/style differs from ours and instructions on how to fix it.
+
+## History
+
+This is based on initial work on [fxos-deploy](https://github.com/nicola/fxos-deploy) by Nicola Greco.
+
+## License
+
+This program is free software; it is distributed under an
+[Apache License](https://github.com/mozilla/node-firefox-install-app/blob/master/LICENSE).
+
+## Copyright
+
+Copyright (c) 2015 [Mozilla](https://mozilla.org)
+([Contributors](https://github.com/mozilla/node-firefox-install-app/graphs/contributors)).
+
diff --git a/node_modules/node-firefox-install-app/examples/sampleApp/app.js b/node_modules/node-firefox-install-app/examples/sampleApp/app.js
new file mode 100644
index 0000000..dc7e45e
--- /dev/null
+++ b/node_modules/node-firefox-install-app/examples/sampleApp/app.js
@@ -0,0 +1,3 @@
+window.addEventListener("load", function() {
+  console.log("Hello World!");
+});
diff --git a/node_modules/node-firefox-install-app/examples/sampleApp/icons/icon128x128.png b/node_modules/node-firefox-install-app/examples/sampleApp/icons/icon128x128.png
new file mode 100644
index 0000000..7db00d7
--- /dev/null
+++ b/node_modules/node-firefox-install-app/examples/sampleApp/icons/icon128x128.png
Binary files differ
diff --git a/node_modules/node-firefox-install-app/examples/sampleApp/icons/icon16x16.png b/node_modules/node-firefox-install-app/examples/sampleApp/icons/icon16x16.png
new file mode 100644
index 0000000..1e751ce
--- /dev/null
+++ b/node_modules/node-firefox-install-app/examples/sampleApp/icons/icon16x16.png
Binary files differ
diff --git a/node_modules/node-firefox-install-app/examples/sampleApp/icons/icon48x48.png b/node_modules/node-firefox-install-app/examples/sampleApp/icons/icon48x48.png
new file mode 100644
index 0000000..acc33ff
--- /dev/null
+++ b/node_modules/node-firefox-install-app/examples/sampleApp/icons/icon48x48.png
Binary files differ
diff --git a/node_modules/node-firefox-install-app/examples/sampleApp/icons/icon60x60.png b/node_modules/node-firefox-install-app/examples/sampleApp/icons/icon60x60.png
new file mode 100644
index 0000000..bc14314
--- /dev/null
+++ b/node_modules/node-firefox-install-app/examples/sampleApp/icons/icon60x60.png
Binary files differ
diff --git a/node_modules/node-firefox-install-app/examples/sampleApp/index.html b/node_modules/node-firefox-install-app/examples/sampleApp/index.html
new file mode 100644
index 0000000..d6bed3f
--- /dev/null
+++ b/node_modules/node-firefox-install-app/examples/sampleApp/index.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1">
+
+    <title>node-firefox-install-app</title>
+
+    <style>
+      body {
+        border: 1px solid black;
+      }
+    </style>
+
+    <!-- Inline scripts are forbidden in Firefox OS apps (CSP restrictions),
+         so we use a script file. -->
+    <script src="app.js" defer></script>
+
+  </head>
+
+  <body>
+    <!-- This code is in the public domain. Enjoy! -->
+    <h1><tt>node-firefox-install-app</tt></h1>
+    <p>This is a simple test app for demonstrating how to use this module.</p>
+  </body>
+
+</html>
diff --git a/node_modules/node-firefox-install-app/examples/sampleApp/manifest.webapp b/node_modules/node-firefox-install-app/examples/sampleApp/manifest.webapp
new file mode 100644
index 0000000..1d19874
--- /dev/null
+++ b/node_modules/node-firefox-install-app/examples/sampleApp/manifest.webapp
@@ -0,0 +1,11 @@
+{
+  "name": "node-firefox-install-app",
+  "description": "A test app for this module",
+  "launch_path": "/index.html",
+  "icons": {
+    "16": "/icons/icon16x16.png",
+    "48": "/icons/icon48x48.png",
+    "60": "/icons/icon60x60.png",
+    "128": "/icons/icon128x128.png"
+  }
+}
diff --git a/node_modules/node-firefox-install-app/examples/usage.js b/node_modules/node-firefox-install-app/examples/usage.js
new file mode 100644
index 0000000..88fc1b7
--- /dev/null
+++ b/node_modules/node-firefox-install-app/examples/usage.js
@@ -0,0 +1,25 @@
+'use strict';
+
+var path = require('path');
+var startSimulator = require('node-firefox-start-simulator');
+var connect = require('node-firefox-connect');
+var installApp = require('..');
+var appPath = path.join(__dirname, 'sampleApp');
+
+// This example will start a Firefox OS simulator
+// and install the sample app on it
+// You will need to have at least a simulator
+// already installed!
+
+startSimulator().then(function(simulator) {
+  connect(simulator.port).then(function(client) {
+    installApp({
+      appPath: appPath,
+      client: client
+    }).then(function(appId) {
+      console.log('App was installed with appId = ', appId);
+    }, function(error) {
+      console.error('App could not be installed: ', error);
+    });
+  });
+});
diff --git a/node_modules/node-firefox-install-app/gulpfile.js b/node_modules/node-firefox-install-app/gulpfile.js
new file mode 100644
index 0000000..75941db
--- /dev/null
+++ b/node_modules/node-firefox-install-app/gulpfile.js
@@ -0,0 +1,4 @@
+var gulp = require('gulp');
+var buildTools = require('node-firefox-build-tools');
+
+buildTools.loadGulpTasks(gulp);
diff --git a/node_modules/node-firefox-install-app/index.js b/node_modules/node-firefox-install-app/index.js
new file mode 100644
index 0000000..4c735b4
--- /dev/null
+++ b/node_modules/node-firefox-install-app/index.js
@@ -0,0 +1,95 @@
+'use strict';
+
+// See https://github.com/jshint/jshint/issues/1747 for context
+/* global -Promise */
+var Promise = require('es6-Promise').Promise;
+var fs = require('fs');
+var uuid = require('node-uuid');
+var zipFolder = require('zip-folder');
+var temporary = require('temporary');
+
+module.exports = function(options) {
+
+  options = options || {};
+
+  var appPath = options.appPath;
+  var client = options.client;
+
+  return new Promise(function(resolve, reject) {
+
+    if (appPath === undefined || client === undefined) {
+      return reject(new Error('App path and client are required to install an app'));
+    }
+
+    // TODO: we can also install hosted apps using the client!
+    // TODO: do we need to uninstall the app? (it's an unexpected side effect!)
+    // TODO: optionally launch app
+
+    Promise.all([ zipApp(appPath), getWebAppsActor(client) ]).then(function(results) {
+
+      var packagedAppPath = results[0];
+      var webAppsActor = results[1];
+      var appId = uuid.v1();
+
+      installApp(webAppsActor, packagedAppPath, appId).then(function(result) {
+        resolve(result);
+        deleteFile(packagedAppPath);
+      }, function(err) {
+        reject(err);
+        deleteFile(packagedAppPath);
+      });
+
+    });
+
+  });
+
+};
+
+
+function zipApp(appPath) {
+
+  var zipPath = new temporary.File().path;
+
+  return new Promise(function(resolve, reject) {
+    zipFolder(appPath, zipPath, function(err) {
+      if (err) {
+        reject(err);
+      } else {
+        resolve(zipPath);
+      }
+    });
+  });
+
+}
+
+
+function getWebAppsActor(client) {
+  return new Promise(function(resolve, reject) {
+
+    client.getWebapps(function(err, webAppsActor) {
+      if (err) {
+        return reject(err);
+      }
+      resolve(webAppsActor);
+    });
+
+  });
+}
+
+
+function installApp(webAppsActor, packagedAppPath, appId) {
+  return new Promise(function(resolve, reject) {
+    webAppsActor.installPackaged(packagedAppPath, appId, function(err, actualAppId) {
+      if (err) {
+        return reject(err);
+      }
+      resolve(actualAppId);
+    });
+  });
+}
+
+
+function deleteFile(filePath) {
+  fs.unlinkSync(filePath);
+}
+
diff --git a/node_modules/node-firefox-install-app/node_modules/.bin/uuid b/node_modules/node-firefox-install-app/node_modules/.bin/uuid
new file mode 120000
index 0000000..80eb14a
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/.bin/uuid
@@ -0,0 +1 @@
+../node-uuid/bin/uuid
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/es6-promise/.release.json b/node_modules/node-firefox-install-app/node_modules/es6-promise/.release.json
new file mode 100644
index 0000000..dee8cbc
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/es6-promise/.release.json
@@ -0,0 +1,17 @@
+{
+  "non-interactive": true,
+  "dry-run": false,
+  "verbose": false,
+  "force": false,
+  "pkgFiles": ["package.json", "bower.json"],
+  "increment": "patch",
+  "commitMessage": "Release %s",
+  "tagName": "%s",
+  "tagAnnotation": "Release %s",
+  "buildCommand": "npm run-script build-all",
+  "distRepo": "git@github.com:components/rsvp.js.git",
+  "distStageDir": "tmp/stage",
+  "distBase": "dist",
+  "distFiles": ["**/*", "../package.json", "../bower.json"],
+  "publish": false
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/es6-promise/Brocfile.js b/node_modules/node-firefox-install-app/node_modules/es6-promise/Brocfile.js
new file mode 100644
index 0000000..d34f458
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/es6-promise/Brocfile.js
@@ -0,0 +1,75 @@
+/* jshint node:true, undef:true, unused:true */
+var AMDFormatter     = require('es6-module-transpiler-amd-formatter');
+var closureCompiler  = require('broccoli-closure-compiler');
+var compileModules   = require('broccoli-compile-modules');
+var mergeTrees       = require('broccoli-merge-trees');
+var moveFile         = require('broccoli-file-mover');
+var es3Recast        = require('broccoli-es3-safe-recast');
+var concat           = require('broccoli-concat');
+var replace          = require('broccoli-string-replace');
+var calculateVersion = require('./lib/calculateVersion');
+var path             = require('path');
+var trees            = [];
+var env              = process.env.EMBER_ENV || 'development';
+
+var bundle = compileModules('lib', {
+  inputFiles: ['es6-promise.umd.js'],
+  output: '/es6-promise.js',
+  formatter: 'bundle',
+});
+
+trees.push(bundle);
+trees.push(compileModules('lib', {
+  inputFiles: ['**/*.js'],
+  output: '/amd/',
+  formatter: new AMDFormatter()
+}));
+
+if (env === 'production') {
+  trees.push(closureCompiler(moveFile(bundle, {
+    srcFile: 'es6-promise.js',
+    destFile: 'es6-promise.min.js'
+  }), {
+    compilation_level: 'ADVANCED_OPTIMIZATIONS',
+    externs: ['node'],
+  }));
+}
+
+var distTree = mergeTrees(trees.concat('config'));
+var distTrees = [];
+
+distTrees.push(concat(distTree, {
+  inputFiles: [
+    'versionTemplate.txt',
+    'es6-promise.js'
+  ],
+  outputFile: '/es6-promise.js'
+}));
+
+if (env === 'production') {
+  distTrees.push(concat(distTree, {
+    inputFiles: [
+      'versionTemplate.txt',
+      'es6-promise.min.js'
+    ],
+    outputFile: '/es6-promise.min.js'
+  }));
+}
+
+if (env !== 'development') {
+  distTrees = distTrees.map(es3Recast);
+}
+
+distTree = mergeTrees(distTrees);
+var distTree = replace(distTree, {
+  files: [
+    'es6-promise.js',
+    'es6-promise.min.js'
+  ],
+  pattern: {
+    match: /VERSION_PLACEHOLDER_STRING/g,
+    replacement: calculateVersion()
+  }
+});
+
+module.exports = distTree;
diff --git a/node_modules/node-firefox-install-app/node_modules/es6-promise/CHANGELOG.md b/node_modules/node-firefox-install-app/node_modules/es6-promise/CHANGELOG.md
new file mode 100644
index 0000000..e06b496
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/es6-promise/CHANGELOG.md
@@ -0,0 +1,9 @@
+# Master
+
+# 2.0.0
+
+* re-sync with RSVP. Many large performance improvements and bugfixes.
+
+# 1.0.0
+
+* first subset of RSVP
diff --git a/node_modules/node-firefox-install-app/node_modules/es6-promise/LICENSE b/node_modules/node-firefox-install-app/node_modules/es6-promise/LICENSE
new file mode 100644
index 0000000..954ec59
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/es6-promise/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/node_modules/node-firefox-install-app/node_modules/es6-promise/README.md b/node_modules/node-firefox-install-app/node_modules/es6-promise/README.md
new file mode 100644
index 0000000..b974ce4
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/es6-promise/README.md
@@ -0,0 +1,58 @@
+# ES6-Promise (subset of [rsvp.js](https://github.com/tildeio/rsvp.js))
+
+This is a polyfill of the [ES6 Promise](http://people.mozilla.org/~jorendorff/es6-draft.html#sec-promise-constructor). The implementation is a subset of [rsvp.js](https://github.com/tildeio/rsvp.js), if you're wanting extra features and more debugging options, check out the [full library](https://github.com/tildeio/rsvp.js).
+
+For API details and how to use promises, see the <a href="http://www.html5rocks.com/en/tutorials/es6/promises/">JavaScript Promises HTML5Rocks article</a>.
+
+## Downloads
+
+* [es6-promise](https://es6-promises.s3.amazonaws.com/es6-promise-2.0.1.js)
+* [es6-promise-min](https://es6-promises.s3.amazonaws.com/es6-promise-2.0.1.min.js) (~2.2k gzipped)
+
+## Node.js
+
+To install:
+
+```sh
+npm install es6-promise
+```
+
+To use:
+
+```js
+var Promise = require('es6-promise').Promise;
+```
+
+## Usage in IE<9
+
+`catch` is a reserved word in IE<9, meaning `promise.catch(func)` throws a syntax error. To work around this, you can use a string to access the property as shown in the following example.
+
+However, please remember that such technique is already provided by most common minifiers, making the resulting code safe for old browsers and production:
+
+```js
+promise['catch'](function(err) {
+  // ...
+});
+```
+
+Or use `.then` instead:
+
+```js
+promise.then(undefined, function(err) {
+  // ...
+});
+```
+
+## Auto-polyfill
+
+To polyfill the global environment (either in Node or in the browser via CommonJS) use the following code snippet:
+
+```js
+require('es6-promise').polyfill();
+```
+
+Notice that we don't assign the result of `polyfill()` to any variable. The `polyfill()` method will patch the global environment (in this case to the `Promise` name) when called.
+
+## Building & Testing
+
+* `npm run build-all && npm test` - Run Mocha tests through Node and PhantomJS.
diff --git a/node_modules/node-firefox-install-app/node_modules/es6-promise/bin/publish_to_s3.js b/node_modules/node-firefox-install-app/node_modules/es6-promise/bin/publish_to_s3.js
new file mode 100755
index 0000000..7daf49a
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/es6-promise/bin/publish_to_s3.js
@@ -0,0 +1,28 @@
+#!/usr/bin/env node
+
+// To invoke this from the commandline you need the following to env vars to exist:
+//
+// S3_BUCKET_NAME
+// TRAVIS_BRANCH
+// TRAVIS_TAG
+// TRAVIS_COMMIT
+// S3_SECRET_ACCESS_KEY
+// S3_ACCESS_KEY_ID
+//
+// Once you have those you execute with the following:
+//
+// ```sh
+// ./bin/publish_to_s3.js
+// ```
+var S3Publisher = require('ember-publisher');
+var configPath = require('path').join(__dirname, '../config/s3ProjectConfig.js');
+publisher = new S3Publisher({ projectConfigPath: configPath });
+
+// Always use wildcard section of project config.
+// This is useful when the including library does not
+// require channels (like in ember.js / ember-data).
+publisher.currentBranch = function() {
+  return (process.env.TRAVIS_BRANCH === 'master') ? 'wildcard' : 'no-op';
+};
+publisher.publish();
+
diff --git a/node_modules/node-firefox-install-app/node_modules/es6-promise/bower.json b/node_modules/node-firefox-install-app/node_modules/es6-promise/bower.json
new file mode 100644
index 0000000..84ec5ad
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/es6-promise/bower.json
@@ -0,0 +1,29 @@
+{
+  "name": "es6-promise",
+  "namespace": "Promise",
+  "version": "2.0.1",
+  "description": "A polyfill for ES6-style Promises, tracking rsvp",
+  "authors": [
+    "Stefan Penner <stefan.penner@gmail.com>"
+  ],
+  "main": "dist/es6-promise.js",
+  "keywords": [
+    "promise"
+  ],
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/jakearchibald/ES6-Promises.git"
+  },
+  "bugs": {
+    "url": "https://github.com/jakearchibald/ES6-Promises/issues"
+  },
+  "license": "MIT",
+  "ignore": [
+    "node_modules",
+    "bower_components",
+    "test",
+    "tests",
+    "vendor",
+    "tasks"
+  ]
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/es6-promise/config/s3ProjectConfig.js b/node_modules/node-firefox-install-app/node_modules/es6-promise/config/s3ProjectConfig.js
new file mode 100644
index 0000000..5f3349a
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/es6-promise/config/s3ProjectConfig.js
@@ -0,0 +1,26 @@
+/*
+ * Using wildcard because es6-promise does not currently have a
+ * channel system in place.
+ */
+module.exports = function(revision,tag,date){
+  return {
+    'es6-promise.js':
+      { contentType: 'text/javascript',
+        destinations: {
+          wildcard: [
+            'es6-promise-latest.js',
+            'es6-promise-' + revision + '.js'
+          ]
+        }
+      },
+    'es6-promise.min.js':
+      { contentType: 'text/javascript',
+        destinations: {
+          wildcard: [
+            'es6-promise-latest.min.js',
+            'es6-promise-' + revision + '.min.js'
+          ]
+        }
+      }
+  }
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/es6-promise/config/versionTemplate.txt b/node_modules/node-firefox-install-app/node_modules/es6-promise/config/versionTemplate.txt
new file mode 100644
index 0000000..999a5fc
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/es6-promise/config/versionTemplate.txt
@@ -0,0 +1,7 @@
+/*!
+ * @overview es6-promise - a tiny implementation of Promises/A+.
+ * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald)
+ * @license   Licensed under MIT license
+ *            See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE
+ * @version   VERSION_PLACEHOLDER_STRING
+ */
diff --git a/node_modules/node-firefox-install-app/node_modules/es6-promise/dist/es6-promise.js b/node_modules/node-firefox-install-app/node_modules/es6-promise/dist/es6-promise.js
new file mode 100644
index 0000000..30a6d81
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/es6-promise/dist/es6-promise.js
@@ -0,0 +1,960 @@
+/*!
+ * @overview es6-promise - a tiny implementation of Promises/A+.
+ * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald)
+ * @license   Licensed under MIT license
+ *            See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE
+ * @version   2.0.1
+ */
+
+(function() {
+    "use strict";
+
+    function $$utils$$objectOrFunction(x) {
+      return typeof x === 'function' || (typeof x === 'object' && x !== null);
+    }
+
+    function $$utils$$isFunction(x) {
+      return typeof x === 'function';
+    }
+
+    function $$utils$$isMaybeThenable(x) {
+      return typeof x === 'object' && x !== null;
+    }
+
+    var $$utils$$_isArray;
+
+    if (!Array.isArray) {
+      $$utils$$_isArray = function (x) {
+        return Object.prototype.toString.call(x) === '[object Array]';
+      };
+    } else {
+      $$utils$$_isArray = Array.isArray;
+    }
+
+    var $$utils$$isArray = $$utils$$_isArray;
+    var $$utils$$now = Date.now || function() { return new Date().getTime(); };
+    function $$utils$$F() { }
+
+    var $$utils$$o_create = (Object.create || function (o) {
+      if (arguments.length > 1) {
+        throw new Error('Second argument not supported');
+      }
+      if (typeof o !== 'object') {
+        throw new TypeError('Argument must be an object');
+      }
+      $$utils$$F.prototype = o;
+      return new $$utils$$F();
+    });
+
+    var $$asap$$len = 0;
+
+    var $$asap$$default = function asap(callback, arg) {
+      $$asap$$queue[$$asap$$len] = callback;
+      $$asap$$queue[$$asap$$len + 1] = arg;
+      $$asap$$len += 2;
+      if ($$asap$$len === 2) {
+        // If len is 1, that means that we need to schedule an async flush.
+        // If additional callbacks are queued before the queue is flushed, they
+        // will be processed by this flush that we are scheduling.
+        $$asap$$scheduleFlush();
+      }
+    };
+
+    var $$asap$$browserGlobal = (typeof window !== 'undefined') ? window : {};
+    var $$asap$$BrowserMutationObserver = $$asap$$browserGlobal.MutationObserver || $$asap$$browserGlobal.WebKitMutationObserver;
+
+    // test for web worker but not in IE10
+    var $$asap$$isWorker = typeof Uint8ClampedArray !== 'undefined' &&
+      typeof importScripts !== 'undefined' &&
+      typeof MessageChannel !== 'undefined';
+
+    // node
+    function $$asap$$useNextTick() {
+      return function() {
+        process.nextTick($$asap$$flush);
+      };
+    }
+
+    function $$asap$$useMutationObserver() {
+      var iterations = 0;
+      var observer = new $$asap$$BrowserMutationObserver($$asap$$flush);
+      var node = document.createTextNode('');
+      observer.observe(node, { characterData: true });
+
+      return function() {
+        node.data = (iterations = ++iterations % 2);
+      };
+    }
+
+    // web worker
+    function $$asap$$useMessageChannel() {
+      var channel = new MessageChannel();
+      channel.port1.onmessage = $$asap$$flush;
+      return function () {
+        channel.port2.postMessage(0);
+      };
+    }
+
+    function $$asap$$useSetTimeout() {
+      return function() {
+        setTimeout($$asap$$flush, 1);
+      };
+    }
+
+    var $$asap$$queue = new Array(1000);
+
+    function $$asap$$flush() {
+      for (var i = 0; i < $$asap$$len; i+=2) {
+        var callback = $$asap$$queue[i];
+        var arg = $$asap$$queue[i+1];
+
+        callback(arg);
+
+        $$asap$$queue[i] = undefined;
+        $$asap$$queue[i+1] = undefined;
+      }
+
+      $$asap$$len = 0;
+    }
+
+    var $$asap$$scheduleFlush;
+
+    // Decide what async method to use to triggering processing of queued callbacks:
+    if (typeof process !== 'undefined' && {}.toString.call(process) === '[object process]') {
+      $$asap$$scheduleFlush = $$asap$$useNextTick();
+    } else if ($$asap$$BrowserMutationObserver) {
+      $$asap$$scheduleFlush = $$asap$$useMutationObserver();
+    } else if ($$asap$$isWorker) {
+      $$asap$$scheduleFlush = $$asap$$useMessageChannel();
+    } else {
+      $$asap$$scheduleFlush = $$asap$$useSetTimeout();
+    }
+
+    function $$$internal$$noop() {}
+    var $$$internal$$PENDING   = void 0;
+    var $$$internal$$FULFILLED = 1;
+    var $$$internal$$REJECTED  = 2;
+    var $$$internal$$GET_THEN_ERROR = new $$$internal$$ErrorObject();
+
+    function $$$internal$$selfFullfillment() {
+      return new TypeError("You cannot resolve a promise with itself");
+    }
+
+    function $$$internal$$cannotReturnOwn() {
+      return new TypeError('A promises callback cannot return that same promise.')
+    }
+
+    function $$$internal$$getThen(promise) {
+      try {
+        return promise.then;
+      } catch(error) {
+        $$$internal$$GET_THEN_ERROR.error = error;
+        return $$$internal$$GET_THEN_ERROR;
+      }
+    }
+
+    function $$$internal$$tryThen(then, value, fulfillmentHandler, rejectionHandler) {
+      try {
+        then.call(value, fulfillmentHandler, rejectionHandler);
+      } catch(e) {
+        return e;
+      }
+    }
+
+    function $$$internal$$handleForeignThenable(promise, thenable, then) {
+       $$asap$$default(function(promise) {
+        var sealed = false;
+        var error = $$$internal$$tryThen(then, thenable, function(value) {
+          if (sealed) { return; }
+          sealed = true;
+          if (thenable !== value) {
+            $$$internal$$resolve(promise, value);
+          } else {
+            $$$internal$$fulfill(promise, value);
+          }
+        }, function(reason) {
+          if (sealed) { return; }
+          sealed = true;
+
+          $$$internal$$reject(promise, reason);
+        }, 'Settle: ' + (promise._label || ' unknown promise'));
+
+        if (!sealed && error) {
+          sealed = true;
+          $$$internal$$reject(promise, error);
+        }
+      }, promise);
+    }
+
+    function $$$internal$$handleOwnThenable(promise, thenable) {
+      if (thenable._state === $$$internal$$FULFILLED) {
+        $$$internal$$fulfill(promise, thenable._result);
+      } else if (promise._state === $$$internal$$REJECTED) {
+        $$$internal$$reject(promise, thenable._result);
+      } else {
+        $$$internal$$subscribe(thenable, undefined, function(value) {
+          $$$internal$$resolve(promise, value);
+        }, function(reason) {
+          $$$internal$$reject(promise, reason);
+        });
+      }
+    }
+
+    function $$$internal$$handleMaybeThenable(promise, maybeThenable) {
+      if (maybeThenable.constructor === promise.constructor) {
+        $$$internal$$handleOwnThenable(promise, maybeThenable);
+      } else {
+        var then = $$$internal$$getThen(maybeThenable);
+
+        if (then === $$$internal$$GET_THEN_ERROR) {
+          $$$internal$$reject(promise, $$$internal$$GET_THEN_ERROR.error);
+        } else if (then === undefined) {
+          $$$internal$$fulfill(promise, maybeThenable);
+        } else if ($$utils$$isFunction(then)) {
+          $$$internal$$handleForeignThenable(promise, maybeThenable, then);
+        } else {
+          $$$internal$$fulfill(promise, maybeThenable);
+        }
+      }
+    }
+
+    function $$$internal$$resolve(promise, value) {
+      if (promise === value) {
+        $$$internal$$reject(promise, $$$internal$$selfFullfillment());
+      } else if ($$utils$$objectOrFunction(value)) {
+        $$$internal$$handleMaybeThenable(promise, value);
+      } else {
+        $$$internal$$fulfill(promise, value);
+      }
+    }
+
+    function $$$internal$$publishRejection(promise) {
+      if (promise._onerror) {
+        promise._onerror(promise._result);
+      }
+
+      $$$internal$$publish(promise);
+    }
+
+    function $$$internal$$fulfill(promise, value) {
+      if (promise._state !== $$$internal$$PENDING) { return; }
+
+      promise._result = value;
+      promise._state = $$$internal$$FULFILLED;
+
+      if (promise._subscribers.length === 0) {
+      } else {
+        $$asap$$default($$$internal$$publish, promise);
+      }
+    }
+
+    function $$$internal$$reject(promise, reason) {
+      if (promise._state !== $$$internal$$PENDING) { return; }
+      promise._state = $$$internal$$REJECTED;
+      promise._result = reason;
+
+      $$asap$$default($$$internal$$publishRejection, promise);
+    }
+
+    function $$$internal$$subscribe(parent, child, onFulfillment, onRejection) {
+      var subscribers = parent._subscribers;
+      var length = subscribers.length;
+
+      parent._onerror = null;
+
+      subscribers[length] = child;
+      subscribers[length + $$$internal$$FULFILLED] = onFulfillment;
+      subscribers[length + $$$internal$$REJECTED]  = onRejection;
+
+      if (length === 0 && parent._state) {
+        $$asap$$default($$$internal$$publish, parent);
+      }
+    }
+
+    function $$$internal$$publish(promise) {
+      var subscribers = promise._subscribers;
+      var settled = promise._state;
+
+      if (subscribers.length === 0) { return; }
+
+      var child, callback, detail = promise._result;
+
+      for (var i = 0; i < subscribers.length; i += 3) {
+        child = subscribers[i];
+        callback = subscribers[i + settled];
+
+        if (child) {
+          $$$internal$$invokeCallback(settled, child, callback, detail);
+        } else {
+          callback(detail);
+        }
+      }
+
+      promise._subscribers.length = 0;
+    }
+
+    function $$$internal$$ErrorObject() {
+      this.error = null;
+    }
+
+    var $$$internal$$TRY_CATCH_ERROR = new $$$internal$$ErrorObject();
+
+    function $$$internal$$tryCatch(callback, detail) {
+      try {
+        return callback(detail);
+      } catch(e) {
+        $$$internal$$TRY_CATCH_ERROR.error = e;
+        return $$$internal$$TRY_CATCH_ERROR;
+      }
+    }
+
+    function $$$internal$$invokeCallback(settled, promise, callback, detail) {
+      var hasCallback = $$utils$$isFunction(callback),
+          value, error, succeeded, failed;
+
+      if (hasCallback) {
+        value = $$$internal$$tryCatch(callback, detail);
+
+        if (value === $$$internal$$TRY_CATCH_ERROR) {
+          failed = true;
+          error = value.error;
+          value = null;
+        } else {
+          succeeded = true;
+        }
+
+        if (promise === value) {
+          $$$internal$$reject(promise, $$$internal$$cannotReturnOwn());
+          return;
+        }
+
+      } else {
+        value = detail;
+        succeeded = true;
+      }
+
+      if (promise._state !== $$$internal$$PENDING) {
+        // noop
+      } else if (hasCallback && succeeded) {
+        $$$internal$$resolve(promise, value);
+      } else if (failed) {
+        $$$internal$$reject(promise, error);
+      } else if (settled === $$$internal$$FULFILLED) {
+        $$$internal$$fulfill(promise, value);
+      } else if (settled === $$$internal$$REJECTED) {
+        $$$internal$$reject(promise, value);
+      }
+    }
+
+    function $$$internal$$initializePromise(promise, resolver) {
+      try {
+        resolver(function resolvePromise(value){
+          $$$internal$$resolve(promise, value);
+        }, function rejectPromise(reason) {
+          $$$internal$$reject(promise, reason);
+        });
+      } catch(e) {
+        $$$internal$$reject(promise, e);
+      }
+    }
+
+    function $$$enumerator$$makeSettledResult(state, position, value) {
+      if (state === $$$internal$$FULFILLED) {
+        return {
+          state: 'fulfilled',
+          value: value
+        };
+      } else {
+        return {
+          state: 'rejected',
+          reason: value
+        };
+      }
+    }
+
+    function $$$enumerator$$Enumerator(Constructor, input, abortOnReject, label) {
+      this._instanceConstructor = Constructor;
+      this.promise = new Constructor($$$internal$$noop, label);
+      this._abortOnReject = abortOnReject;
+
+      if (this._validateInput(input)) {
+        this._input     = input;
+        this.length     = input.length;
+        this._remaining = input.length;
+
+        this._init();
+
+        if (this.length === 0) {
+          $$$internal$$fulfill(this.promise, this._result);
+        } else {
+          this.length = this.length || 0;
+          this._enumerate();
+          if (this._remaining === 0) {
+            $$$internal$$fulfill(this.promise, this._result);
+          }
+        }
+      } else {
+        $$$internal$$reject(this.promise, this._validationError());
+      }
+    }
+
+    $$$enumerator$$Enumerator.prototype._validateInput = function(input) {
+      return $$utils$$isArray(input);
+    };
+
+    $$$enumerator$$Enumerator.prototype._validationError = function() {
+      return new Error('Array Methods must be provided an Array');
+    };
+
+    $$$enumerator$$Enumerator.prototype._init = function() {
+      this._result = new Array(this.length);
+    };
+
+    var $$$enumerator$$default = $$$enumerator$$Enumerator;
+
+    $$$enumerator$$Enumerator.prototype._enumerate = function() {
+      var length  = this.length;
+      var promise = this.promise;
+      var input   = this._input;
+
+      for (var i = 0; promise._state === $$$internal$$PENDING && i < length; i++) {
+        this._eachEntry(input[i], i);
+      }
+    };
+
+    $$$enumerator$$Enumerator.prototype._eachEntry = function(entry, i) {
+      var c = this._instanceConstructor;
+      if ($$utils$$isMaybeThenable(entry)) {
+        if (entry.constructor === c && entry._state !== $$$internal$$PENDING) {
+          entry._onerror = null;
+          this._settledAt(entry._state, i, entry._result);
+        } else {
+          this._willSettleAt(c.resolve(entry), i);
+        }
+      } else {
+        this._remaining--;
+        this._result[i] = this._makeResult($$$internal$$FULFILLED, i, entry);
+      }
+    };
+
+    $$$enumerator$$Enumerator.prototype._settledAt = function(state, i, value) {
+      var promise = this.promise;
+
+      if (promise._state === $$$internal$$PENDING) {
+        this._remaining--;
+
+        if (this._abortOnReject && state === $$$internal$$REJECTED) {
+          $$$internal$$reject(promise, value);
+        } else {
+          this._result[i] = this._makeResult(state, i, value);
+        }
+      }
+
+      if (this._remaining === 0) {
+        $$$internal$$fulfill(promise, this._result);
+      }
+    };
+
+    $$$enumerator$$Enumerator.prototype._makeResult = function(state, i, value) {
+      return value;
+    };
+
+    $$$enumerator$$Enumerator.prototype._willSettleAt = function(promise, i) {
+      var enumerator = this;
+
+      $$$internal$$subscribe(promise, undefined, function(value) {
+        enumerator._settledAt($$$internal$$FULFILLED, i, value);
+      }, function(reason) {
+        enumerator._settledAt($$$internal$$REJECTED, i, reason);
+      });
+    };
+
+    var $$promise$all$$default = function all(entries, label) {
+      return new $$$enumerator$$default(this, entries, true /* abort on reject */, label).promise;
+    };
+
+    var $$promise$race$$default = function race(entries, label) {
+      /*jshint validthis:true */
+      var Constructor = this;
+
+      var promise = new Constructor($$$internal$$noop, label);
+
+      if (!$$utils$$isArray(entries)) {
+        $$$internal$$reject(promise, new TypeError('You must pass an array to race.'));
+        return promise;
+      }
+
+      var length = entries.length;
+
+      function onFulfillment(value) {
+        $$$internal$$resolve(promise, value);
+      }
+
+      function onRejection(reason) {
+        $$$internal$$reject(promise, reason);
+      }
+
+      for (var i = 0; promise._state === $$$internal$$PENDING && i < length; i++) {
+        $$$internal$$subscribe(Constructor.resolve(entries[i]), undefined, onFulfillment, onRejection);
+      }
+
+      return promise;
+    };
+
+    var $$promise$resolve$$default = function resolve(object, label) {
+      /*jshint validthis:true */
+      var Constructor = this;
+
+      if (object && typeof object === 'object' && object.constructor === Constructor) {
+        return object;
+      }
+
+      var promise = new Constructor($$$internal$$noop, label);
+      $$$internal$$resolve(promise, object);
+      return promise;
+    };
+
+    var $$promise$reject$$default = function reject(reason, label) {
+      /*jshint validthis:true */
+      var Constructor = this;
+      var promise = new Constructor($$$internal$$noop, label);
+      $$$internal$$reject(promise, reason);
+      return promise;
+    };
+
+    var $$es6$promise$promise$$counter = 0;
+
+    function $$es6$promise$promise$$needsResolver() {
+      throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
+    }
+
+    function $$es6$promise$promise$$needsNew() {
+      throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");
+    }
+
+    var $$es6$promise$promise$$default = $$es6$promise$promise$$Promise;
+
+    /**
+      Promise objects represent the eventual result of an asynchronous operation. The
+      primary way of interacting with a promise is through its `then` method, which
+      registers callbacks to receive either a promise’s eventual value or the reason
+      why the promise cannot be fulfilled.
+
+      Terminology
+      -----------
+
+      - `promise` is an object or function with a `then` method whose behavior conforms to this specification.
+      - `thenable` is an object or function that defines a `then` method.
+      - `value` is any legal JavaScript value (including undefined, a thenable, or a promise).
+      - `exception` is a value that is thrown using the throw statement.
+      - `reason` is a value that indicates why a promise was rejected.
+      - `settled` the final resting state of a promise, fulfilled or rejected.
+
+      A promise can be in one of three states: pending, fulfilled, or rejected.
+
+      Promises that are fulfilled have a fulfillment value and are in the fulfilled
+      state.  Promises that are rejected have a rejection reason and are in the
+      rejected state.  A fulfillment value is never a thenable.
+
+      Promises can also be said to *resolve* a value.  If this value is also a
+      promise, then the original promise's settled state will match the value's
+      settled state.  So a promise that *resolves* a promise that rejects will
+      itself reject, and a promise that *resolves* a promise that fulfills will
+      itself fulfill.
+
+
+      Basic Usage:
+      ------------
+
+      ```js
+      var promise = new Promise(function(resolve, reject) {
+        // on success
+        resolve(value);
+
+        // on failure
+        reject(reason);
+      });
+
+      promise.then(function(value) {
+        // on fulfillment
+      }, function(reason) {
+        // on rejection
+      });
+      ```
+
+      Advanced Usage:
+      ---------------
+
+      Promises shine when abstracting away asynchronous interactions such as
+      `XMLHttpRequest`s.
+
+      ```js
+      function getJSON(url) {
+        return new Promise(function(resolve, reject){
+          var xhr = new XMLHttpRequest();
+
+          xhr.open('GET', url);
+          xhr.onreadystatechange = handler;
+          xhr.responseType = 'json';
+          xhr.setRequestHeader('Accept', 'application/json');
+          xhr.send();
+
+          function handler() {
+            if (this.readyState === this.DONE) {
+              if (this.status === 200) {
+                resolve(this.response);
+              } else {
+                reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']'));
+              }
+            }
+          };
+        });
+      }
+
+      getJSON('/posts.json').then(function(json) {
+        // on fulfillment
+      }, function(reason) {
+        // on rejection
+      });
+      ```
+
+      Unlike callbacks, promises are great composable primitives.
+
+      ```js
+      Promise.all([
+        getJSON('/posts'),
+        getJSON('/comments')
+      ]).then(function(values){
+        values[0] // => postsJSON
+        values[1] // => commentsJSON
+
+        return values;
+      });
+      ```
+
+      @class Promise
+      @param {function} resolver
+      Useful for tooling.
+      @constructor
+    */
+    function $$es6$promise$promise$$Promise(resolver) {
+      this._id = $$es6$promise$promise$$counter++;
+      this._state = undefined;
+      this._result = undefined;
+      this._subscribers = [];
+
+      if ($$$internal$$noop !== resolver) {
+        if (!$$utils$$isFunction(resolver)) {
+          $$es6$promise$promise$$needsResolver();
+        }
+
+        if (!(this instanceof $$es6$promise$promise$$Promise)) {
+          $$es6$promise$promise$$needsNew();
+        }
+
+        $$$internal$$initializePromise(this, resolver);
+      }
+    }
+
+    $$es6$promise$promise$$Promise.all = $$promise$all$$default;
+    $$es6$promise$promise$$Promise.race = $$promise$race$$default;
+    $$es6$promise$promise$$Promise.resolve = $$promise$resolve$$default;
+    $$es6$promise$promise$$Promise.reject = $$promise$reject$$default;
+
+    $$es6$promise$promise$$Promise.prototype = {
+      constructor: $$es6$promise$promise$$Promise,
+
+    /**
+      The primary way of interacting with a promise is through its `then` method,
+      which registers callbacks to receive either a promise's eventual value or the
+      reason why the promise cannot be fulfilled.
+
+      ```js
+      findUser().then(function(user){
+        // user is available
+      }, function(reason){
+        // user is unavailable, and you are given the reason why
+      });
+      ```
+
+      Chaining
+      --------
+
+      The return value of `then` is itself a promise.  This second, 'downstream'
+      promise is resolved with the return value of the first promise's fulfillment
+      or rejection handler, or rejected if the handler throws an exception.
+
+      ```js
+      findUser().then(function (user) {
+        return user.name;
+      }, function (reason) {
+        return 'default name';
+      }).then(function (userName) {
+        // If `findUser` fulfilled, `userName` will be the user's name, otherwise it
+        // will be `'default name'`
+      });
+
+      findUser().then(function (user) {
+        throw new Error('Found user, but still unhappy');
+      }, function (reason) {
+        throw new Error('`findUser` rejected and we're unhappy');
+      }).then(function (value) {
+        // never reached
+      }, function (reason) {
+        // if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'.
+        // If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'.
+      });
+      ```
+      If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream.
+
+      ```js
+      findUser().then(function (user) {
+        throw new PedagogicalException('Upstream error');
+      }).then(function (value) {
+        // never reached
+      }).then(function (value) {
+        // never reached
+      }, function (reason) {
+        // The `PedgagocialException` is propagated all the way down to here
+      });
+      ```
+
+      Assimilation
+      ------------
+
+      Sometimes the value you want to propagate to a downstream promise can only be
+      retrieved asynchronously. This can be achieved by returning a promise in the
+      fulfillment or rejection handler. The downstream promise will then be pending
+      until the returned promise is settled. This is called *assimilation*.
+
+      ```js
+      findUser().then(function (user) {
+        return findCommentsByAuthor(user);
+      }).then(function (comments) {
+        // The user's comments are now available
+      });
+      ```
+
+      If the assimliated promise rejects, then the downstream promise will also reject.
+
+      ```js
+      findUser().then(function (user) {
+        return findCommentsByAuthor(user);
+      }).then(function (comments) {
+        // If `findCommentsByAuthor` fulfills, we'll have the value here
+      }, function (reason) {
+        // If `findCommentsByAuthor` rejects, we'll have the reason here
+      });
+      ```
+
+      Simple Example
+      --------------
+
+      Synchronous Example
+
+      ```javascript
+      var result;
+
+      try {
+        result = findResult();
+        // success
+      } catch(reason) {
+        // failure
+      }
+      ```
+
+      Errback Example
+
+      ```js
+      findResult(function(result, err){
+        if (err) {
+          // failure
+        } else {
+          // success
+        }
+      });
+      ```
+
+      Promise Example;
+
+      ```javascript
+      findResult().then(function(result){
+        // success
+      }, function(reason){
+        // failure
+      });
+      ```
+
+      Advanced Example
+      --------------
+
+      Synchronous Example
+
+      ```javascript
+      var author, books;
+
+      try {
+        author = findAuthor();
+        books  = findBooksByAuthor(author);
+        // success
+      } catch(reason) {
+        // failure
+      }
+      ```
+
+      Errback Example
+
+      ```js
+
+      function foundBooks(books) {
+
+      }
+
+      function failure(reason) {
+
+      }
+
+      findAuthor(function(author, err){
+        if (err) {
+          failure(err);
+          // failure
+        } else {
+          try {
+            findBoooksByAuthor(author, function(books, err) {
+              if (err) {
+                failure(err);
+              } else {
+                try {
+                  foundBooks(books);
+                } catch(reason) {
+                  failure(reason);
+                }
+              }
+            });
+          } catch(error) {
+            failure(err);
+          }
+          // success
+        }
+      });
+      ```
+
+      Promise Example;
+
+      ```javascript
+      findAuthor().
+        then(findBooksByAuthor).
+        then(function(books){
+          // found books
+      }).catch(function(reason){
+        // something went wrong
+      });
+      ```
+
+      @method then
+      @param {Function} onFulfilled
+      @param {Function} onRejected
+      Useful for tooling.
+      @return {Promise}
+    */
+      then: function(onFulfillment, onRejection) {
+        var parent = this;
+        var state = parent._state;
+
+        if (state === $$$internal$$FULFILLED && !onFulfillment || state === $$$internal$$REJECTED && !onRejection) {
+          return this;
+        }
+
+        var child = new this.constructor($$$internal$$noop);
+        var result = parent._result;
+
+        if (state) {
+          var callback = arguments[state - 1];
+          $$asap$$default(function(){
+            $$$internal$$invokeCallback(state, child, callback, result);
+          });
+        } else {
+          $$$internal$$subscribe(parent, child, onFulfillment, onRejection);
+        }
+
+        return child;
+      },
+
+    /**
+      `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same
+      as the catch block of a try/catch statement.
+
+      ```js
+      function findAuthor(){
+        throw new Error('couldn't find that author');
+      }
+
+      // synchronous
+      try {
+        findAuthor();
+      } catch(reason) {
+        // something went wrong
+      }
+
+      // async with promises
+      findAuthor().catch(function(reason){
+        // something went wrong
+      });
+      ```
+
+      @method catch
+      @param {Function} onRejection
+      Useful for tooling.
+      @return {Promise}
+    */
+      'catch': function(onRejection) {
+        return this.then(null, onRejection);
+      }
+    };
+
+    var $$es6$promise$polyfill$$default = function polyfill() {
+      var local;
+
+      if (typeof global !== 'undefined') {
+        local = global;
+      } else if (typeof window !== 'undefined' && window.document) {
+        local = window;
+      } else {
+        local = self;
+      }
+
+      var es6PromiseSupport =
+        "Promise" in local &&
+        // Some of these methods are missing from
+        // Firefox/Chrome experimental implementations
+        "resolve" in local.Promise &&
+        "reject" in local.Promise &&
+        "all" in local.Promise &&
+        "race" in local.Promise &&
+        // Older version of the spec had a resolver object
+        // as the arg rather than a function
+        (function() {
+          var resolve;
+          new local.Promise(function(r) { resolve = r; });
+          return $$utils$$isFunction(resolve);
+        }());
+
+      if (!es6PromiseSupport) {
+        local.Promise = $$es6$promise$promise$$default;
+      }
+    };
+
+    var es6$promise$umd$$ES6Promise = {
+      'Promise': $$es6$promise$promise$$default,
+      'polyfill': $$es6$promise$polyfill$$default
+    };
+
+    /* global define:true module:true window: true */
+    if (typeof define === 'function' && define['amd']) {
+      define(function() { return es6$promise$umd$$ES6Promise; });
+    } else if (typeof module !== 'undefined' && module['exports']) {
+      module['exports'] = es6$promise$umd$$ES6Promise;
+    } else if (typeof this !== 'undefined') {
+      this['ES6Promise'] = es6$promise$umd$$ES6Promise;
+    }
+}).call(this);
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/es6-promise/dist/es6-promise.min.js b/node_modules/node-firefox-install-app/node_modules/es6-promise/dist/es6-promise.min.js
new file mode 100644
index 0000000..6e0c99f
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/es6-promise/dist/es6-promise.min.js
@@ -0,0 +1,18 @@
+/*!
+ * @overview es6-promise - a tiny implementation of Promises/A+.
+ * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald)
+ * @license   Licensed under MIT license
+ *            See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE
+ * @version   2.0.1
+ */
+
+(function(){function r(a,b){n[l]=a;n[l+1]=b;l+=2;2===l&&A()}function s(a){return"function"===typeof a}function F(){return function(){process.nextTick(t)}}function G(){var a=0,b=new B(t),c=document.createTextNode("");b.observe(c,{characterData:!0});return function(){c.data=a=++a%2}}function H(){var a=new MessageChannel;a.port1.onmessage=t;return function(){a.port2.postMessage(0)}}function I(){return function(){setTimeout(t,1)}}function t(){for(var a=0;a<l;a+=2)(0,n[a])(n[a+1]),n[a]=void 0,n[a+1]=void 0;
+l=0}function p(){}function J(a,b,c,d){try{a.call(b,c,d)}catch(e){return e}}function K(a,b,c){r(function(a){var e=!1,f=J(c,b,function(c){e||(e=!0,b!==c?q(a,c):m(a,c))},function(b){e||(e=!0,g(a,b))});!e&&f&&(e=!0,g(a,f))},a)}function L(a,b){1===b.a?m(a,b.b):2===a.a?g(a,b.b):u(b,void 0,function(b){q(a,b)},function(b){g(a,b)})}function q(a,b){if(a===b)g(a,new TypeError("You cannot resolve a promise with itself"));else if("function"===typeof b||"object"===typeof b&&null!==b)if(b.constructor===a.constructor)L(a,
+b);else{var c;try{c=b.then}catch(d){v.error=d,c=v}c===v?g(a,v.error):void 0===c?m(a,b):s(c)?K(a,b,c):m(a,b)}else m(a,b)}function M(a){a.f&&a.f(a.b);x(a)}function m(a,b){void 0===a.a&&(a.b=b,a.a=1,0!==a.e.length&&r(x,a))}function g(a,b){void 0===a.a&&(a.a=2,a.b=b,r(M,a))}function u(a,b,c,d){var e=a.e,f=e.length;a.f=null;e[f]=b;e[f+1]=c;e[f+2]=d;0===f&&a.a&&r(x,a)}function x(a){var b=a.e,c=a.a;if(0!==b.length){for(var d,e,f=a.b,g=0;g<b.length;g+=3)d=b[g],e=b[g+c],d?C(c,d,e,f):e(f);a.e.length=0}}function D(){this.error=
+null}function C(a,b,c,d){var e=s(c),f,k,h,l;if(e){try{f=c(d)}catch(n){y.error=n,f=y}f===y?(l=!0,k=f.error,f=null):h=!0;if(b===f){g(b,new TypeError("A promises callback cannot return that same promise."));return}}else f=d,h=!0;void 0===b.a&&(e&&h?q(b,f):l?g(b,k):1===a?m(b,f):2===a&&g(b,f))}function N(a,b){try{b(function(b){q(a,b)},function(b){g(a,b)})}catch(c){g(a,c)}}function k(a,b,c,d){this.n=a;this.c=new a(p,d);this.i=c;this.o(b)?(this.m=b,this.d=this.length=b.length,this.l(),0===this.length?m(this.c,
+this.b):(this.length=this.length||0,this.k(),0===this.d&&m(this.c,this.b))):g(this.c,this.p())}function h(a){O++;this.b=this.a=void 0;this.e=[];if(p!==a){if(!s(a))throw new TypeError("You must pass a resolver function as the first argument to the promise constructor");if(!(this instanceof h))throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");N(this,a)}}var E=Array.isArray?Array.isArray:function(a){return"[object Array]"===
+Object.prototype.toString.call(a)},l=0,w="undefined"!==typeof window?window:{},B=w.MutationObserver||w.WebKitMutationObserver,w="undefined"!==typeof Uint8ClampedArray&&"undefined"!==typeof importScripts&&"undefined"!==typeof MessageChannel,n=Array(1E3),A;A="undefined"!==typeof process&&"[object process]"==={}.toString.call(process)?F():B?G():w?H():I();var v=new D,y=new D;k.prototype.o=function(a){return E(a)};k.prototype.p=function(){return Error("Array Methods must be provided an Array")};k.prototype.l=
+function(){this.b=Array(this.length)};k.prototype.k=function(){for(var a=this.length,b=this.c,c=this.m,d=0;void 0===b.a&&d<a;d++)this.j(c[d],d)};k.prototype.j=function(a,b){var c=this.n;"object"===typeof a&&null!==a?a.constructor===c&&void 0!==a.a?(a.f=null,this.g(a.a,b,a.b)):this.q(c.resolve(a),b):(this.d--,this.b[b]=this.h(a))};k.prototype.g=function(a,b,c){var d=this.c;void 0===d.a&&(this.d--,this.i&&2===a?g(d,c):this.b[b]=this.h(c));0===this.d&&m(d,this.b)};k.prototype.h=function(a){return a};
+k.prototype.q=function(a,b){var c=this;u(a,void 0,function(a){c.g(1,b,a)},function(a){c.g(2,b,a)})};var O=0;h.all=function(a,b){return(new k(this,a,!0,b)).c};h.race=function(a,b){function c(a){q(e,a)}function d(a){g(e,a)}var e=new this(p,b);if(!E(a))return (g(e,new TypeError("You must pass an array to race.")), e);for(var f=a.length,h=0;void 0===e.a&&h<f;h++)u(this.resolve(a[h]),void 0,c,d);return e};h.resolve=function(a,b){if(a&&"object"===typeof a&&a.constructor===this)return a;var c=new this(p,b);
+q(c,a);return c};h.reject=function(a,b){var c=new this(p,b);g(c,a);return c};h.prototype={constructor:h,then:function(a,b){var c=this.a;if(1===c&&!a||2===c&&!b)return this;var d=new this.constructor(p),e=this.b;if(c){var f=arguments[c-1];r(function(){C(c,d,f,e)})}else u(this,d,a,b);return d},"catch":function(a){return this.then(null,a)}};var z={Promise:h,polyfill:function(){var a;a="undefined"!==typeof global?global:"undefined"!==typeof window&&window.document?window:self;"Promise"in a&&"resolve"in
+a.Promise&&"reject"in a.Promise&&"all"in a.Promise&&"race"in a.Promise&&function(){var b;new a.Promise(function(a){b=a});return s(b)}()||(a.Promise=h)}};"function"===typeof define&&define.amd?define(function(){return z}):"undefined"!==typeof module&&module.exports?module.exports=z:"undefined"!==typeof this&&(this.ES6Promise=z)}).call(this);
diff --git a/node_modules/node-firefox-install-app/node_modules/es6-promise/lib/calculateVersion.js b/node_modules/node-firefox-install-app/node_modules/es6-promise/lib/calculateVersion.js
new file mode 100644
index 0000000..018e364
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/es6-promise/lib/calculateVersion.js
@@ -0,0 +1,36 @@
+'use strict';
+
+var fs   = require('fs');
+var path = require('path');
+
+module.exports = function () {
+  var packageVersion = require('../package.json').version;
+  var output         = [packageVersion];
+  var gitPath        = path.join(__dirname,'..','.git');
+  var headFilePath   = path.join(gitPath, 'HEAD');
+
+  if (packageVersion.indexOf('+') > -1) {
+    try {
+      if (fs.existsSync(headFilePath)) {
+        var headFile = fs.readFileSync(headFilePath, {encoding: 'utf8'});
+        var branchName = headFile.split('/').slice(-1)[0].trim();
+        var refPath = headFile.split(' ')[1];
+        var branchSHA;
+
+        if (refPath) {
+          var branchPath = path.join(gitPath, refPath.trim());
+          branchSHA  = fs.readFileSync(branchPath);
+        } else {
+          branchSHA = branchName;
+        }
+
+        output.push(branchSHA.slice(0,10));
+      }
+    } catch (err) {
+      console.error(err.stack);
+    }
+    return output.join('.');
+  } else {
+    return packageVersion;
+  }
+};
diff --git a/node_modules/node-firefox-install-app/node_modules/es6-promise/lib/es6-promise.umd.js b/node_modules/node-firefox-install-app/node_modules/es6-promise/lib/es6-promise.umd.js
new file mode 100644
index 0000000..cd01553
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/es6-promise/lib/es6-promise.umd.js
@@ -0,0 +1,16 @@
+import Promise from './es6-promise/promise';
+import polyfill from './es6-promise/polyfill';
+
+var ES6Promise = {
+  'Promise': Promise,
+  'polyfill': polyfill
+};
+
+/* global define:true module:true window: true */
+if (typeof define === 'function' && define['amd']) {
+  define(function() { return ES6Promise; });
+} else if (typeof module !== 'undefined' && module['exports']) {
+  module['exports'] = ES6Promise;
+} else if (typeof this !== 'undefined') {
+  this['ES6Promise'] = ES6Promise;
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/es6-promise/lib/es6-promise/-internal.js b/node_modules/node-firefox-install-app/node_modules/es6-promise/lib/es6-promise/-internal.js
new file mode 100644
index 0000000..afef22e
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/es6-promise/lib/es6-promise/-internal.js
@@ -0,0 +1,251 @@
+import {
+  objectOrFunction,
+  isFunction
+} from './utils';
+
+import asap from './asap';
+
+function noop() {}
+
+var PENDING   = void 0;
+var FULFILLED = 1;
+var REJECTED  = 2;
+
+var GET_THEN_ERROR = new ErrorObject();
+
+function selfFullfillment() {
+  return new TypeError("You cannot resolve a promise with itself");
+}
+
+function cannotReturnOwn() {
+  return new TypeError('A promises callback cannot return that same promise.')
+}
+
+function getThen(promise) {
+  try {
+    return promise.then;
+  } catch(error) {
+    GET_THEN_ERROR.error = error;
+    return GET_THEN_ERROR;
+  }
+}
+
+function tryThen(then, value, fulfillmentHandler, rejectionHandler) {
+  try {
+    then.call(value, fulfillmentHandler, rejectionHandler);
+  } catch(e) {
+    return e;
+  }
+}
+
+function handleForeignThenable(promise, thenable, then) {
+   asap(function(promise) {
+    var sealed = false;
+    var error = tryThen(then, thenable, function(value) {
+      if (sealed) { return; }
+      sealed = true;
+      if (thenable !== value) {
+        resolve(promise, value);
+      } else {
+        fulfill(promise, value);
+      }
+    }, function(reason) {
+      if (sealed) { return; }
+      sealed = true;
+
+      reject(promise, reason);
+    }, 'Settle: ' + (promise._label || ' unknown promise'));
+
+    if (!sealed && error) {
+      sealed = true;
+      reject(promise, error);
+    }
+  }, promise);
+}
+
+function handleOwnThenable(promise, thenable) {
+  if (thenable._state === FULFILLED) {
+    fulfill(promise, thenable._result);
+  } else if (promise._state === REJECTED) {
+    reject(promise, thenable._result);
+  } else {
+    subscribe(thenable, undefined, function(value) {
+      resolve(promise, value);
+    }, function(reason) {
+      reject(promise, reason);
+    });
+  }
+}
+
+function handleMaybeThenable(promise, maybeThenable) {
+  if (maybeThenable.constructor === promise.constructor) {
+    handleOwnThenable(promise, maybeThenable);
+  } else {
+    var then = getThen(maybeThenable);
+
+    if (then === GET_THEN_ERROR) {
+      reject(promise, GET_THEN_ERROR.error);
+    } else if (then === undefined) {
+      fulfill(promise, maybeThenable);
+    } else if (isFunction(then)) {
+      handleForeignThenable(promise, maybeThenable, then);
+    } else {
+      fulfill(promise, maybeThenable);
+    }
+  }
+}
+
+function resolve(promise, value) {
+  if (promise === value) {
+    reject(promise, selfFullfillment());
+  } else if (objectOrFunction(value)) {
+    handleMaybeThenable(promise, value);
+  } else {
+    fulfill(promise, value);
+  }
+}
+
+function publishRejection(promise) {
+  if (promise._onerror) {
+    promise._onerror(promise._result);
+  }
+
+  publish(promise);
+}
+
+function fulfill(promise, value) {
+  if (promise._state !== PENDING) { return; }
+
+  promise._result = value;
+  promise._state = FULFILLED;
+
+  if (promise._subscribers.length === 0) {
+  } else {
+    asap(publish, promise);
+  }
+}
+
+function reject(promise, reason) {
+  if (promise._state !== PENDING) { return; }
+  promise._state = REJECTED;
+  promise._result = reason;
+
+  asap(publishRejection, promise);
+}
+
+function subscribe(parent, child, onFulfillment, onRejection) {
+  var subscribers = parent._subscribers;
+  var length = subscribers.length;
+
+  parent._onerror = null;
+
+  subscribers[length] = child;
+  subscribers[length + FULFILLED] = onFulfillment;
+  subscribers[length + REJECTED]  = onRejection;
+
+  if (length === 0 && parent._state) {
+    asap(publish, parent);
+  }
+}
+
+function publish(promise) {
+  var subscribers = promise._subscribers;
+  var settled = promise._state;
+
+  if (subscribers.length === 0) { return; }
+
+  var child, callback, detail = promise._result;
+
+  for (var i = 0; i < subscribers.length; i += 3) {
+    child = subscribers[i];
+    callback = subscribers[i + settled];
+
+    if (child) {
+      invokeCallback(settled, child, callback, detail);
+    } else {
+      callback(detail);
+    }
+  }
+
+  promise._subscribers.length = 0;
+}
+
+function ErrorObject() {
+  this.error = null;
+}
+
+var TRY_CATCH_ERROR = new ErrorObject();
+
+function tryCatch(callback, detail) {
+  try {
+    return callback(detail);
+  } catch(e) {
+    TRY_CATCH_ERROR.error = e;
+    return TRY_CATCH_ERROR;
+  }
+}
+
+function invokeCallback(settled, promise, callback, detail) {
+  var hasCallback = isFunction(callback),
+      value, error, succeeded, failed;
+
+  if (hasCallback) {
+    value = tryCatch(callback, detail);
+
+    if (value === TRY_CATCH_ERROR) {
+      failed = true;
+      error = value.error;
+      value = null;
+    } else {
+      succeeded = true;
+    }
+
+    if (promise === value) {
+      reject(promise, cannotReturnOwn());
+      return;
+    }
+
+  } else {
+    value = detail;
+    succeeded = true;
+  }
+
+  if (promise._state !== PENDING) {
+    // noop
+  } else if (hasCallback && succeeded) {
+    resolve(promise, value);
+  } else if (failed) {
+    reject(promise, error);
+  } else if (settled === FULFILLED) {
+    fulfill(promise, value);
+  } else if (settled === REJECTED) {
+    reject(promise, value);
+  }
+}
+
+function initializePromise(promise, resolver) {
+  try {
+    resolver(function resolvePromise(value){
+      resolve(promise, value);
+    }, function rejectPromise(reason) {
+      reject(promise, reason);
+    });
+  } catch(e) {
+    reject(promise, e);
+  }
+}
+
+export {
+  noop,
+  resolve,
+  reject,
+  fulfill,
+  subscribe,
+  publish,
+  publishRejection,
+  initializePromise,
+  invokeCallback,
+  FULFILLED,
+  REJECTED,
+  PENDING
+};
diff --git a/node_modules/node-firefox-install-app/node_modules/es6-promise/lib/es6-promise/asap.js b/node_modules/node-firefox-install-app/node_modules/es6-promise/lib/es6-promise/asap.js
new file mode 100644
index 0000000..4872589
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/es6-promise/lib/es6-promise/asap.js
@@ -0,0 +1,82 @@
+var len = 0;
+
+export default function asap(callback, arg) {
+  queue[len] = callback;
+  queue[len + 1] = arg;
+  len += 2;
+  if (len === 2) {
+    // If len is 1, that means that we need to schedule an async flush.
+    // If additional callbacks are queued before the queue is flushed, they
+    // will be processed by this flush that we are scheduling.
+    scheduleFlush();
+  }
+}
+
+var browserGlobal = (typeof window !== 'undefined') ? window : {};
+var BrowserMutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver;
+
+// test for web worker but not in IE10
+var isWorker = typeof Uint8ClampedArray !== 'undefined' &&
+  typeof importScripts !== 'undefined' &&
+  typeof MessageChannel !== 'undefined';
+
+// node
+function useNextTick() {
+  return function() {
+    process.nextTick(flush);
+  };
+}
+
+function useMutationObserver() {
+  var iterations = 0;
+  var observer = new BrowserMutationObserver(flush);
+  var node = document.createTextNode('');
+  observer.observe(node, { characterData: true });
+
+  return function() {
+    node.data = (iterations = ++iterations % 2);
+  };
+}
+
+// web worker
+function useMessageChannel() {
+  var channel = new MessageChannel();
+  channel.port1.onmessage = flush;
+  return function () {
+    channel.port2.postMessage(0);
+  };
+}
+
+function useSetTimeout() {
+  return function() {
+    setTimeout(flush, 1);
+  };
+}
+
+var queue = new Array(1000);
+function flush() {
+  for (var i = 0; i < len; i+=2) {
+    var callback = queue[i];
+    var arg = queue[i+1];
+
+    callback(arg);
+
+    queue[i] = undefined;
+    queue[i+1] = undefined;
+  }
+
+  len = 0;
+}
+
+var scheduleFlush;
+
+// Decide what async method to use to triggering processing of queued callbacks:
+if (typeof process !== 'undefined' && {}.toString.call(process) === '[object process]') {
+  scheduleFlush = useNextTick();
+} else if (BrowserMutationObserver) {
+  scheduleFlush = useMutationObserver();
+} else if (isWorker) {
+  scheduleFlush = useMessageChannel();
+} else {
+  scheduleFlush = useSetTimeout();
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/es6-promise/lib/es6-promise/enumerator.js b/node_modules/node-firefox-install-app/node_modules/es6-promise/lib/es6-promise/enumerator.js
new file mode 100644
index 0000000..7d3f803
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/es6-promise/lib/es6-promise/enumerator.js
@@ -0,0 +1,125 @@
+import {
+  isArray,
+  isMaybeThenable
+} from './utils';
+
+import {
+  noop,
+  reject,
+  fulfill,
+  subscribe,
+  FULFILLED,
+  REJECTED,
+  PENDING
+} from './-internal';
+
+export function makeSettledResult(state, position, value) {
+  if (state === FULFILLED) {
+    return {
+      state: 'fulfilled',
+      value: value
+    };
+  } else {
+    return {
+      state: 'rejected',
+      reason: value
+    };
+  }
+}
+
+function Enumerator(Constructor, input, abortOnReject, label) {
+  this._instanceConstructor = Constructor;
+  this.promise = new Constructor(noop, label);
+  this._abortOnReject = abortOnReject;
+
+  if (this._validateInput(input)) {
+    this._input     = input;
+    this.length     = input.length;
+    this._remaining = input.length;
+
+    this._init();
+
+    if (this.length === 0) {
+      fulfill(this.promise, this._result);
+    } else {
+      this.length = this.length || 0;
+      this._enumerate();
+      if (this._remaining === 0) {
+        fulfill(this.promise, this._result);
+      }
+    }
+  } else {
+    reject(this.promise, this._validationError());
+  }
+}
+
+Enumerator.prototype._validateInput = function(input) {
+  return isArray(input);
+};
+
+Enumerator.prototype._validationError = function() {
+  return new Error('Array Methods must be provided an Array');
+};
+
+Enumerator.prototype._init = function() {
+  this._result = new Array(this.length);
+};
+
+export default Enumerator;
+
+Enumerator.prototype._enumerate = function() {
+  var length  = this.length;
+  var promise = this.promise;
+  var input   = this._input;
+
+  for (var i = 0; promise._state === PENDING && i < length; i++) {
+    this._eachEntry(input[i], i);
+  }
+};
+
+Enumerator.prototype._eachEntry = function(entry, i) {
+  var c = this._instanceConstructor;
+  if (isMaybeThenable(entry)) {
+    if (entry.constructor === c && entry._state !== PENDING) {
+      entry._onerror = null;
+      this._settledAt(entry._state, i, entry._result);
+    } else {
+      this._willSettleAt(c.resolve(entry), i);
+    }
+  } else {
+    this._remaining--;
+    this._result[i] = this._makeResult(FULFILLED, i, entry);
+  }
+};
+
+Enumerator.prototype._settledAt = function(state, i, value) {
+  var promise = this.promise;
+
+  if (promise._state === PENDING) {
+    this._remaining--;
+
+    if (this._abortOnReject && state === REJECTED) {
+      reject(promise, value);
+    } else {
+      this._result[i] = this._makeResult(state, i, value);
+    }
+  }
+
+  if (this._remaining === 0) {
+    fulfill(promise, this._result);
+  }
+};
+
+Enumerator.prototype._makeResult = function(state, i, value) {
+  return value;
+};
+
+Enumerator.prototype._willSettleAt = function(promise, i) {
+  var enumerator = this;
+
+  subscribe(promise, undefined, function(value) {
+    enumerator._settledAt(FULFILLED, i, value);
+  }, function(reason) {
+    enumerator._settledAt(REJECTED, i, reason);
+  });
+};
diff --git a/node_modules/node-firefox-install-app/node_modules/es6-promise/lib/es6-promise/polyfill.js b/node_modules/node-firefox-install-app/node_modules/es6-promise/lib/es6-promise/polyfill.js
new file mode 100644
index 0000000..e5b0165
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/es6-promise/lib/es6-promise/polyfill.js
@@ -0,0 +1,35 @@
+/*global self*/
+import { default as RSVPPromise } from "./promise";
+import { isFunction } from "./utils";
+
+export default function polyfill() {
+  var local;
+
+  if (typeof global !== 'undefined') {
+    local = global;
+  } else if (typeof window !== 'undefined' && window.document) {
+    local = window;
+  } else {
+    local = self;
+  }
+
+  var es6PromiseSupport =
+    "Promise" in local &&
+    // Some of these methods are missing from
+    // Firefox/Chrome experimental implementations
+    "resolve" in local.Promise &&
+    "reject" in local.Promise &&
+    "all" in local.Promise &&
+    "race" in local.Promise &&
+    // Older version of the spec had a resolver object
+    // as the arg rather than a function
+    (function() {
+      var resolve;
+      new local.Promise(function(r) { resolve = r; });
+      return isFunction(resolve);
+    }());
+
+  if (!es6PromiseSupport) {
+    local.Promise = RSVPPromise;
+  }
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/es6-promise/lib/es6-promise/promise.js b/node_modules/node-firefox-install-app/node_modules/es6-promise/lib/es6-promise/promise.js
new file mode 100644
index 0000000..5e865a7
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/es6-promise/lib/es6-promise/promise.js
@@ -0,0 +1,409 @@
+import {
+  isFunction,
+  now
+} from './utils';
+
+import {
+  noop,
+  subscribe,
+  initializePromise,
+  invokeCallback,
+  FULFILLED,
+  REJECTED
+} from './-internal';
+
+import asap from './asap';
+
+import all from './promise/all';
+import race from './promise/race';
+import Resolve from './promise/resolve';
+import Reject from './promise/reject';
+
+var counter = 0;
+
+function needsResolver() {
+  throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
+}
+
+function needsNew() {
+  throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");
+}
+
+export default Promise;
+/**
+  Promise objects represent the eventual result of an asynchronous operation. The
+  primary way of interacting with a promise is through its `then` method, which
+  registers callbacks to receive either a promise’s eventual value or the reason
+  why the promise cannot be fulfilled.
+
+  Terminology
+  -----------
+
+  - `promise` is an object or function with a `then` method whose behavior conforms to this specification.
+  - `thenable` is an object or function that defines a `then` method.
+  - `value` is any legal JavaScript value (including undefined, a thenable, or a promise).
+  - `exception` is a value that is thrown using the throw statement.
+  - `reason` is a value that indicates why a promise was rejected.
+  - `settled` the final resting state of a promise, fulfilled or rejected.
+
+  A promise can be in one of three states: pending, fulfilled, or rejected.
+
+  Promises that are fulfilled have a fulfillment value and are in the fulfilled
+  state.  Promises that are rejected have a rejection reason and are in the
+  rejected state.  A fulfillment value is never a thenable.
+
+  Promises can also be said to *resolve* a value.  If this value is also a
+  promise, then the original promise's settled state will match the value's
+  settled state.  So a promise that *resolves* a promise that rejects will
+  itself reject, and a promise that *resolves* a promise that fulfills will
+  itself fulfill.
+
+
+  Basic Usage:
+  ------------
+
+  ```js
+  var promise = new Promise(function(resolve, reject) {
+    // on success
+    resolve(value);
+
+    // on failure
+    reject(reason);
+  });
+
+  promise.then(function(value) {
+    // on fulfillment
+  }, function(reason) {
+    // on rejection
+  });
+  ```
+
+  Advanced Usage:
+  ---------------
+
+  Promises shine when abstracting away asynchronous interactions such as
+  `XMLHttpRequest`s.
+
+  ```js
+  function getJSON(url) {
+    return new Promise(function(resolve, reject){
+      var xhr = new XMLHttpRequest();
+
+      xhr.open('GET', url);
+      xhr.onreadystatechange = handler;
+      xhr.responseType = 'json';
+      xhr.setRequestHeader('Accept', 'application/json');
+      xhr.send();
+
+      function handler() {
+        if (this.readyState === this.DONE) {
+          if (this.status === 200) {
+            resolve(this.response);
+          } else {
+            reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']'));
+          }
+        }
+      };
+    });
+  }
+
+  getJSON('/posts.json').then(function(json) {
+    // on fulfillment
+  }, function(reason) {
+    // on rejection
+  });
+  ```
+
+  Unlike callbacks, promises are great composable primitives.
+
+  ```js
+  Promise.all([
+    getJSON('/posts'),
+    getJSON('/comments')
+  ]).then(function(values){
+    values[0] // => postsJSON
+    values[1] // => commentsJSON
+
+    return values;
+  });
+  ```
+
+  @class Promise
+  @param {function} resolver
+  Useful for tooling.
+  @constructor
+*/
+function Promise(resolver) {
+  this._id = counter++;
+  this._state = undefined;
+  this._result = undefined;
+  this._subscribers = [];
+
+  if (noop !== resolver) {
+    if (!isFunction(resolver)) {
+      needsResolver();
+    }
+
+    if (!(this instanceof Promise)) {
+      needsNew();
+    }
+
+    initializePromise(this, resolver);
+  }
+}
+
+Promise.all = all;
+Promise.race = race;
+Promise.resolve = Resolve;
+Promise.reject = Reject;
+
+Promise.prototype = {
+  constructor: Promise,
+
+/**
+  The primary way of interacting with a promise is through its `then` method,
+  which registers callbacks to receive either a promise's eventual value or the
+  reason why the promise cannot be fulfilled.
+
+  ```js
+  findUser().then(function(user){
+    // user is available
+  }, function(reason){
+    // user is unavailable, and you are given the reason why
+  });
+  ```
+
+  Chaining
+  --------
+
+  The return value of `then` is itself a promise.  This second, 'downstream'
+  promise is resolved with the return value of the first promise's fulfillment
+  or rejection handler, or rejected if the handler throws an exception.
+
+  ```js
+  findUser().then(function (user) {
+    return user.name;
+  }, function (reason) {
+    return 'default name';
+  }).then(function (userName) {
+    // If `findUser` fulfilled, `userName` will be the user's name, otherwise it
+    // will be `'default name'`
+  });
+
+  findUser().then(function (user) {
+    throw new Error('Found user, but still unhappy');
+  }, function (reason) {
+    throw new Error('`findUser` rejected and we're unhappy');
+  }).then(function (value) {
+    // never reached
+  }, function (reason) {
+    // if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'.
+    // If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'.
+  });
+  ```
+  If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream.
+
+  ```js
+  findUser().then(function (user) {
+    throw new PedagogicalException('Upstream error');
+  }).then(function (value) {
+    // never reached
+  }).then(function (value) {
+    // never reached
+  }, function (reason) {
+    // The `PedgagocialException` is propagated all the way down to here
+  });
+  ```
+
+  Assimilation
+  ------------
+
+  Sometimes the value you want to propagate to a downstream promise can only be
+  retrieved asynchronously. This can be achieved by returning a promise in the
+  fulfillment or rejection handler. The downstream promise will then be pending
+  until the returned promise is settled. This is called *assimilation*.
+
+  ```js
+  findUser().then(function (user) {
+    return findCommentsByAuthor(user);
+  }).then(function (comments) {
+    // The user's comments are now available
+  });
+  ```
+
+  If the assimliated promise rejects, then the downstream promise will also reject.
+
+  ```js
+  findUser().then(function (user) {
+    return findCommentsByAuthor(user);
+  }).then(function (comments) {
+    // If `findCommentsByAuthor` fulfills, we'll have the value here
+  }, function (reason) {
+    // If `findCommentsByAuthor` rejects, we'll have the reason here
+  });
+  ```
+
+  Simple Example
+  --------------
+
+  Synchronous Example
+
+  ```javascript
+  var result;
+
+  try {
+    result = findResult();
+    // success
+  } catch(reason) {
+    // failure
+  }
+  ```
+
+  Errback Example
+
+  ```js
+  findResult(function(result, err){
+    if (err) {
+      // failure
+    } else {
+      // success
+    }
+  });
+  ```
+
+  Promise Example;
+
+  ```javascript
+  findResult().then(function(result){
+    // success
+  }, function(reason){
+    // failure
+  });
+  ```
+
+  Advanced Example
+  --------------
+
+  Synchronous Example
+
+  ```javascript
+  var author, books;
+
+  try {
+    author = findAuthor();
+    books  = findBooksByAuthor(author);
+    // success
+  } catch(reason) {
+    // failure
+  }
+  ```
+
+  Errback Example
+
+  ```js
+
+  function foundBooks(books) {
+
+  }
+
+  function failure(reason) {
+
+  }
+
+  findAuthor(function(author, err){
+    if (err) {
+      failure(err);
+      // failure
+    } else {
+      try {
+        findBoooksByAuthor(author, function(books, err) {
+          if (err) {
+            failure(err);
+          } else {
+            try {
+              foundBooks(books);
+            } catch(reason) {
+              failure(reason);
+            }
+          }
+        });
+      } catch(error) {
+        failure(err);
+      }
+      // success
+    }
+  });
+  ```
+
+  Promise Example;
+
+  ```javascript
+  findAuthor().
+    then(findBooksByAuthor).
+    then(function(books){
+      // found books
+  }).catch(function(reason){
+    // something went wrong
+  });
+  ```
+
+  @method then
+  @param {Function} onFulfilled
+  @param {Function} onRejected
+  Useful for tooling.
+  @return {Promise}
+*/
+  then: function(onFulfillment, onRejection) {
+    var parent = this;
+    var state = parent._state;
+
+    if (state === FULFILLED && !onFulfillment || state === REJECTED && !onRejection) {
+      return this;
+    }
+
+    var child = new this.constructor(noop);
+    var result = parent._result;
+
+    if (state) {
+      var callback = arguments[state - 1];
+      asap(function(){
+        invokeCallback(state, child, callback, result);
+      });
+    } else {
+      subscribe(parent, child, onFulfillment, onRejection);
+    }
+
+    return child;
+  },
+
+/**
+  `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same
+  as the catch block of a try/catch statement.
+
+  ```js
+  function findAuthor(){
+    throw new Error('couldn't find that author');
+  }
+
+  // synchronous
+  try {
+    findAuthor();
+  } catch(reason) {
+    // something went wrong
+  }
+
+  // async with promises
+  findAuthor().catch(function(reason){
+    // something went wrong
+  });
+  ```
+
+  @method catch
+  @param {Function} onRejection
+  Useful for tooling.
+  @return {Promise}
+*/
+  'catch': function(onRejection) {
+    return this.then(null, onRejection);
+  }
+};
diff --git a/node_modules/node-firefox-install-app/node_modules/es6-promise/lib/es6-promise/promise/all.js b/node_modules/node-firefox-install-app/node_modules/es6-promise/lib/es6-promise/promise/all.js
new file mode 100644
index 0000000..8db7e71
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/es6-promise/lib/es6-promise/promise/all.js
@@ -0,0 +1,52 @@
+import Enumerator from '../enumerator';
+
+/**
+  `Promise.all` accepts an array of promises, and returns a new promise which
+  is fulfilled with an array of fulfillment values for the passed promises, or
+  rejected with the reason of the first passed promise to be rejected. It casts all
+  elements of the passed iterable to promises as it runs this algorithm.
+
+  Example:
+
+  ```javascript
+  var promise1 = resolve(1);
+  var promise2 = resolve(2);
+  var promise3 = resolve(3);
+  var promises = [ promise1, promise2, promise3 ];
+
+  Promise.all(promises).then(function(array){
+    // The array here would be [ 1, 2, 3 ];
+  });
+  ```
+
+  If any of the `promises` given to `all` are rejected, the first promise
+  that is rejected will be given as an argument to the returned promises's
+  rejection handler. For example:
+
+  Example:
+
+  ```javascript
+  var promise1 = resolve(1);
+  var promise2 = reject(new Error("2"));
+  var promise3 = reject(new Error("3"));
+  var promises = [ promise1, promise2, promise3 ];
+
+  Promise.all(promises).then(function(array){
+    // Code here never runs because there are rejected promises!
+  }, function(error) {
+    // error.message === "2"
+  });
+  ```
+
+  @method all
+  @static
+  @param {Array} entries array of promises
+  @param {String} label optional string for labeling the promise.
+  Useful for tooling.
+  @return {Promise} promise that is fulfilled when all `promises` have been
+  fulfilled, or rejected if any of them become rejected.
+  @static
+*/
+export default function all(entries, label) {
+  return new Enumerator(this, entries, true /* abort on reject */, label).promise;
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/es6-promise/lib/es6-promise/promise/race.js b/node_modules/node-firefox-install-app/node_modules/es6-promise/lib/es6-promise/promise/race.js
new file mode 100644
index 0000000..7daa28a
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/es6-promise/lib/es6-promise/promise/race.js
@@ -0,0 +1,105 @@
+import {
+  isArray
+} from "../utils";
+
+import {
+  noop,
+  resolve,
+  reject,
+  subscribe,
+  PENDING
+} from '../-internal';
+
+/**
+  `Promise.race` returns a new promise which is settled in the same way as the
+  first passed promise to settle.
+
+  Example:
+
+  ```javascript
+  var promise1 = new Promise(function(resolve, reject){
+    setTimeout(function(){
+      resolve('promise 1');
+    }, 200);
+  });
+
+  var promise2 = new Promise(function(resolve, reject){
+    setTimeout(function(){
+      resolve('promise 2');
+    }, 100);
+  });
+
+  Promise.race([promise1, promise2]).then(function(result){
+    // result === 'promise 2' because it was resolved before promise1
+    // was resolved.
+  });
+  ```
+
+  `Promise.race` is deterministic in that only the state of the first
+  settled promise matters. For example, even if other promises given to the
+  `promises` array argument are resolved, but the first settled promise has
+  become rejected before the other promises became fulfilled, the returned
+  promise will become rejected:
+
+  ```javascript
+  var promise1 = new Promise(function(resolve, reject){
+    setTimeout(function(){
+      resolve('promise 1');
+    }, 200);
+  });
+
+  var promise2 = new Promise(function(resolve, reject){
+    setTimeout(function(){
+      reject(new Error('promise 2'));
+    }, 100);
+  });
+
+  Promise.race([promise1, promise2]).then(function(result){
+    // Code here never runs
+  }, function(reason){
+    // reason.message === 'promise 2' because promise 2 became rejected before
+    // promise 1 became fulfilled
+  });
+  ```
+
+  An example real-world use case is implementing timeouts:
+
+  ```javascript
+  Promise.race([ajax('foo.json'), timeout(5000)])
+  ```
+
+  @method race
+  @static
+  @param {Array} promises array of promises to observe
+  @param {String} label optional string for describing the promise returned.
+  Useful for tooling.
+  @return {Promise} a promise which settles in the same way as the first passed
+  promise to settle.
+*/
+export default function race(entries, label) {
+  /*jshint validthis:true */
+  var Constructor = this;
+
+  var promise = new Constructor(noop, label);
+
+  if (!isArray(entries)) {
+    reject(promise, new TypeError('You must pass an array to race.'));
+    return promise;
+  }
+
+  var length = entries.length;
+
+  function onFulfillment(value) {
+    resolve(promise, value);
+  }
+
+  function onRejection(reason) {
+    reject(promise, reason);
+  }
+
+  for (var i = 0; promise._state === PENDING && i < length; i++) {
+    subscribe(Constructor.resolve(entries[i]), undefined, onFulfillment, onRejection);
+  }
+
+  return promise;
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/es6-promise/lib/es6-promise/promise/reject.js b/node_modules/node-firefox-install-app/node_modules/es6-promise/lib/es6-promise/promise/reject.js
new file mode 100644
index 0000000..518eff7
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/es6-promise/lib/es6-promise/promise/reject.js
@@ -0,0 +1,47 @@
+import {
+  noop,
+  reject as _reject
+} from '../-internal';
+
+/**
+  `Promise.reject` returns a promise rejected with the passed `reason`.
+  It is shorthand for the following:
+
+  ```javascript
+  var promise = new Promise(function(resolve, reject){
+    reject(new Error('WHOOPS'));
+  });
+
+  promise.then(function(value){
+    // Code here doesn't run because the promise is rejected!
+  }, function(reason){
+    // reason.message === 'WHOOPS'
+  });
+  ```
+
+  Instead of writing the above, your code now simply becomes the following:
+
+  ```javascript
+  var promise = Promise.reject(new Error('WHOOPS'));
+
+  promise.then(function(value){
+    // Code here doesn't run because the promise is rejected!
+  }, function(reason){
+    // reason.message === 'WHOOPS'
+  });
+  ```
+
+  @method reject
+  @static
+  @param {Any} reason value that the returned promise will be rejected with.
+  @param {String} label optional string for identifying the returned promise.
+  Useful for tooling.
+  @return {Promise} a promise rejected with the given `reason`.
+*/
+export default function reject(reason, label) {
+  /*jshint validthis:true */
+  var Constructor = this;
+  var promise = new Constructor(noop, label);
+  _reject(promise, reason);
+  return promise;
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/es6-promise/lib/es6-promise/promise/resolve.js b/node_modules/node-firefox-install-app/node_modules/es6-promise/lib/es6-promise/promise/resolve.js
new file mode 100644
index 0000000..1b3c533
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/es6-promise/lib/es6-promise/promise/resolve.js
@@ -0,0 +1,49 @@
+import {
+  noop,
+  resolve as _resolve
+} from '../-internal';
+
+/**
+  `Promise.resolve` returns a promise that will become resolved with the
+  passed `value`. It is shorthand for the following:
+
+  ```javascript
+  var promise = new Promise(function(resolve, reject){
+    resolve(1);
+  });
+
+  promise.then(function(value){
+    // value === 1
+  });
+  ```
+
+  Instead of writing the above, your code now simply becomes the following:
+
+  ```javascript
+  var promise = Promise.resolve(1);
+
+  promise.then(function(value){
+    // value === 1
+  });
+  ```
+
+  @method resolve
+  @static
+  @param {Any} value value that the returned promise will be resolved with
+  @param {String} label optional string for identifying the returned promise.
+  Useful for tooling.
+  @return {Promise} a promise that will become fulfilled with the given
+  `value`
+*/
+export default function resolve(object, label) {
+  /*jshint validthis:true */
+  var Constructor = this;
+
+  if (object && typeof object === 'object' && object.constructor === Constructor) {
+    return object;
+  }
+
+  var promise = new Constructor(noop, label);
+  _resolve(promise, object);
+  return promise;
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/es6-promise/lib/es6-promise/utils.js b/node_modules/node-firefox-install-app/node_modules/es6-promise/lib/es6-promise/utils.js
new file mode 100644
index 0000000..6b49bbf
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/es6-promise/lib/es6-promise/utils.js
@@ -0,0 +1,39 @@
+export function objectOrFunction(x) {
+  return typeof x === 'function' || (typeof x === 'object' && x !== null);
+}
+
+export function isFunction(x) {
+  return typeof x === 'function';
+}
+
+export function isMaybeThenable(x) {
+  return typeof x === 'object' && x !== null;
+}
+
+var _isArray;
+if (!Array.isArray) {
+  _isArray = function (x) {
+    return Object.prototype.toString.call(x) === '[object Array]';
+  };
+} else {
+  _isArray = Array.isArray;
+}
+
+export var isArray = _isArray;
+
+// Date.now is not available in browsers < IE9
+// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now#Compatibility
+export var now = Date.now || function() { return new Date().getTime(); };
+
+function F() { }
+
+export var o_create = (Object.create || function (o) {
+  if (arguments.length > 1) {
+    throw new Error('Second argument not supported');
+  }
+  if (typeof o !== 'object') {
+    throw new TypeError('Argument must be an object');
+  }
+  F.prototype = o;
+  return new F();
+});
diff --git a/node_modules/node-firefox-install-app/node_modules/es6-promise/package.json b/node_modules/node-firefox-install-app/node_modules/es6-promise/package.json
new file mode 100644
index 0000000..28494ce
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/es6-promise/package.json
@@ -0,0 +1,81 @@
+{
+  "name": "es6-promise",
+  "namespace": "es6-promise",
+  "version": "2.0.1",
+  "description": "A lightweight library that provides tools for organizing asynchronous code",
+  "main": "dist/es6-promise.js",
+  "directories": {
+    "lib": "lib"
+  },
+  "devDependencies": {
+    "bower": "^1.3.9",
+    "brfs": "0.0.8",
+    "broccoli-closure-compiler": "^0.2.0",
+    "broccoli-compile-modules": "git+https://github.com/eventualbuddha/broccoli-compile-modules",
+    "broccoli-concat": "0.0.7",
+    "broccoli-es3-safe-recast": "0.0.8",
+    "broccoli-file-mover": "^0.4.0",
+    "broccoli-jshint": "^0.5.1",
+    "broccoli-merge-trees": "^0.1.4",
+    "broccoli-static-compiler": "^0.1.4",
+    "broccoli-string-replace": "0.0.1",
+    "browserify": "^4.2.0",
+    "ember-cli": "0.0.40",
+    "ember-publisher": "0.0.7",
+    "es6-module-transpiler-amd-formatter": "0.0.1",
+    "express": "^4.5.0",
+    "jshint": "~0.9.1",
+    "mkdirp": "^0.5.0",
+    "mocha": "^1.20.1",
+    "promises-aplus-tests": "git://github.com/stefanpenner/promises-tests.git",
+    "release-it": "0.0.10",
+    "testem": "^0.6.17",
+    "json3": "^3.3.2"
+  },
+  "scripts": {
+    "test": "testem ci -R dot",
+    "test-server": "testem",
+    "lint": "jshint lib",
+    "prepublish": "ember build --environment production",
+    "aplus": "browserify test/main.js",
+    "build-all": "ember build --environment production && browserify ./test/main.js -o tmp/test-bundle.js",
+    "dry-run-release": "ember build --environment production && release-it --dry-run --non-interactive"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/jakearchibald/ES6-Promises.git"
+  },
+  "bugs": {
+    "url": "https://github.com/jakearchibald/ES6-Promises/issues"
+  },
+  "keywords": [
+    "promises",
+    "futures"
+  ],
+  "author": {
+    "name": "Yehuda Katz, Tom Dale, Stefan Penner and contributors",
+    "url": "Conversion to ES6 API by Jake Archibald"
+  },
+  "license": "MIT",
+  "homepage": "https://github.com/jakearchibald/ES6-Promises",
+  "_id": "es6-promise@2.0.1",
+  "dist": {
+    "shasum": "ccc4963e679f0ca9fb187c777b9e583d3c7573c2",
+    "tarball": "http://registry.npmjs.org/es6-promise/-/es6-promise-2.0.1.tgz"
+  },
+  "_from": "es6-promise@^2.0.1",
+  "_npmVersion": "1.3.24",
+  "_npmUser": {
+    "name": "jaffathecake",
+    "email": "jaffathecake@gmail.com"
+  },
+  "maintainers": [
+    {
+      "name": "jaffathecake",
+      "email": "jaffathecake@gmail.com"
+    }
+  ],
+  "_shasum": "ccc4963e679f0ca9fb187c777b9e583d3c7573c2",
+  "_resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-2.0.1.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/es6-promise/server/.jshintrc b/node_modules/node-firefox-install-app/node_modules/es6-promise/server/.jshintrc
new file mode 100644
index 0000000..c1f2978
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/es6-promise/server/.jshintrc
@@ -0,0 +1,3 @@
+{
+  "node": true
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/es6-promise/server/index.js b/node_modules/node-firefox-install-app/node_modules/es6-promise/server/index.js
new file mode 100644
index 0000000..8a54d36
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/es6-promise/server/index.js
@@ -0,0 +1,6 @@
+module.exports = function(app) {
+  app.use(require('express').static(__dirname + '/../'));
+  app.get('/', function(req, res) {
+    res.redirect('/test/');
+  })
+};
diff --git a/node_modules/node-firefox-install-app/node_modules/es6-promise/testem.json b/node_modules/node-firefox-install-app/node_modules/es6-promise/testem.json
new file mode 100644
index 0000000..7494912
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/es6-promise/testem.json
@@ -0,0 +1,11 @@
+{
+  "test_page": "test/index.html",
+  "parallel": 5,
+  "before_tests": "npm run build-all",
+  "launchers": {
+    "Mocha": {
+      "command": "./node_modules/.bin/mocha test/main.js"
+    }
+  },
+  "launch_in_ci":  ["PhantomJS", "Mocha"]
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/node-uuid/.npmignore b/node_modules/node-firefox-install-app/node_modules/node-uuid/.npmignore
new file mode 100644
index 0000000..fd4f2b0
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/node-uuid/.npmignore
@@ -0,0 +1,2 @@
+node_modules
+.DS_Store
diff --git a/node_modules/node-firefox-install-app/node_modules/node-uuid/LICENSE.md b/node_modules/node-firefox-install-app/node_modules/node-uuid/LICENSE.md
new file mode 100644
index 0000000..652609b
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/node-uuid/LICENSE.md
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c)  2010-2012 Robert Kieffer 
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/node_modules/node-firefox-install-app/node_modules/node-uuid/README.md b/node_modules/node-firefox-install-app/node_modules/node-uuid/README.md
new file mode 100644
index 0000000..b7d04c9
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/node-uuid/README.md
@@ -0,0 +1,243 @@
+# node-uuid
+
+Simple, fast generation of [RFC4122](http://www.ietf.org/rfc/rfc4122.txt) UUIDS.
+
+Features:
+
+* Generate RFC4122 version 1 or version 4 UUIDs
+* Runs in node.js and all browsers.
+* Registered as a [ComponentJS](https://github.com/component/component) [component](https://github.com/component/component/wiki/Components) ('broofa/node-uuid').
+* Cryptographically strong random # generation on supporting platforms
+* 1.1K minified and gzip'ed  (Want something smaller?  Check this [crazy shit](https://gist.github.com/982883) out! )
+* [Annotated source code](http://broofa.github.com/node-uuid/docs/uuid.html)
+* Comes with a Command Line Interface for generating uuids on the command line
+
+## Getting Started
+
+Install it in your browser:
+
+```html
+<script src="uuid.js"></script>
+```
+
+Or in node.js:
+
+```
+npm install node-uuid
+```
+
+```javascript
+var uuid = require('node-uuid');
+```
+
+Then create some ids ...
+
+```javascript
+// Generate a v1 (time-based) id
+uuid.v1(); // -> '6c84fb90-12c4-11e1-840d-7b25c5ee775a'
+
+// Generate a v4 (random) id
+uuid.v4(); // -> '110ec58a-a0f2-4ac4-8393-c866d813b8d1'
+```
+
+## API
+
+### uuid.v1([`options` [, `buffer` [, `offset`]]])
+
+Generate and return a RFC4122 v1 (timestamp-based) UUID.
+
+* `options` - (Object) Optional uuid state to apply. Properties may include:
+
+  * `node` - (Array) Node id as Array of 6 bytes (per 4.1.6). Default: Randomly generated ID.  See note 1.
+  * `clockseq` - (Number between 0 - 0x3fff) RFC clock sequence.  Default: An internally maintained clockseq is used.
+  * `msecs` - (Number | Date) Time in milliseconds since unix Epoch.  Default: The current time is used.
+  * `nsecs` - (Number between 0-9999) additional time, in 100-nanosecond units. Ignored if `msecs` is unspecified. Default: internal uuid counter is used, as per 4.2.1.2.
+
+* `buffer` - (Array | Buffer) Array or buffer where UUID bytes are to be written.
+* `offset` - (Number) Starting index in `buffer` at which to begin writing.
+
+Returns `buffer`, if specified, otherwise the string form of the UUID
+
+Notes:
+
+1. The randomly generated node id is only guaranteed to stay constant for the lifetime of the current JS runtime. (Future versions of this module may use persistent storage mechanisms to extend this guarantee.)
+
+Example: Generate string UUID with fully-specified options
+
+```javascript
+uuid.v1({
+  node: [0x01, 0x23, 0x45, 0x67, 0x89, 0xab],
+  clockseq: 0x1234,
+  msecs: new Date('2011-11-01').getTime(),
+  nsecs: 5678
+});   // -> "710b962e-041c-11e1-9234-0123456789ab"
+```
+
+Example: In-place generation of two binary IDs
+
+```javascript
+// Generate two ids in an array
+var arr = new Array(32); // -> []
+uuid.v1(null, arr, 0);   // -> [02 a2 ce 90 14 32 11 e1 85 58 0b 48 8e 4f c1 15]
+uuid.v1(null, arr, 16);  // -> [02 a2 ce 90 14 32 11 e1 85 58 0b 48 8e 4f c1 15 02 a3 1c b0 14 32 11 e1 85 58 0b 48 8e 4f c1 15]
+
+// Optionally use uuid.unparse() to get stringify the ids
+uuid.unparse(buffer);    // -> '02a2ce90-1432-11e1-8558-0b488e4fc115'
+uuid.unparse(buffer, 16) // -> '02a31cb0-1432-11e1-8558-0b488e4fc115'
+```
+
+### uuid.v4([`options` [, `buffer` [, `offset`]]])
+
+Generate and return a RFC4122 v4 UUID.
+
+* `options` - (Object) Optional uuid state to apply. Properties may include:
+
+  * `random` - (Number[16]) Array of 16 numbers (0-255) to use in place of randomly generated values
+  * `rng` - (Function) Random # generator to use.  Set to one of the built-in generators - `uuid.mathRNG` (all platforms), `uuid.nodeRNG` (node.js only), `uuid.whatwgRNG` (WebKit only) - or a custom function that returns an array[16] of byte values.
+
+* `buffer` - (Array | Buffer) Array or buffer where UUID bytes are to be written.
+* `offset` - (Number) Starting index in `buffer` at which to begin writing.
+
+Returns `buffer`, if specified, otherwise the string form of the UUID
+
+Example: Generate string UUID with fully-specified options
+
+```javascript
+uuid.v4({
+  random: [
+    0x10, 0x91, 0x56, 0xbe, 0xc4, 0xfb, 0xc1, 0xea,
+    0x71, 0xb4, 0xef, 0xe1, 0x67, 0x1c, 0x58, 0x36
+  ]
+});
+// -> "109156be-c4fb-41ea-b1b4-efe1671c5836"
+```
+
+Example: Generate two IDs in a single buffer
+
+```javascript
+var buffer = new Array(32); // (or 'new Buffer' in node.js)
+uuid.v4(null, buffer, 0);
+uuid.v4(null, buffer, 16);
+```
+
+### uuid.parse(id[, buffer[, offset]])
+### uuid.unparse(buffer[, offset])
+
+Parse and unparse UUIDs
+
+  * `id` - (String) UUID(-like) string
+  * `buffer` - (Array | Buffer) Array or buffer where UUID bytes are to be written. Default: A new Array or Buffer is used
+  * `offset` - (Number) Starting index in `buffer` at which to begin writing. Default: 0
+
+Example parsing and unparsing a UUID string
+
+```javascript
+var bytes = uuid.parse('797ff043-11eb-11e1-80d6-510998755d10'); // -> <Buffer 79 7f f0 43 11 eb 11 e1 80 d6 51 09 98 75 5d 10>
+var string = uuid.unparse(bytes); // -> '797ff043-11eb-11e1-80d6-510998755d10'
+```
+
+### uuid.noConflict()
+
+(Browsers only) Set `uuid` property back to it's previous value.
+
+Returns the node-uuid object.
+
+Example:
+
+```javascript
+var myUuid = uuid.noConflict();
+myUuid.v1(); // -> '6c84fb90-12c4-11e1-840d-7b25c5ee775a'
+```
+
+## Deprecated APIs
+
+Support for the following v1.2 APIs is available in v1.3, but is deprecated and will be removed in the next major version.
+
+### uuid([format [, buffer [, offset]]])
+
+uuid() has become uuid.v4(), and the `format` argument is now implicit in the `buffer` argument. (i.e. if you specify a buffer, the format is assumed to be binary).
+
+### uuid.BufferClass
+
+The class of container created when generating binary uuid data if no buffer argument is specified.  This is expected to go away, with no replacement API.
+
+## Command Line Interface
+
+To use the executable, it's probably best to install this library globally.
+
+`npm install -g node-uuid`
+
+Usage:
+
+```
+USAGE: uuid [version] [options]
+
+
+options:
+
+--help                     Display this message and exit
+```
+
+`version` must be an RFC4122 version that is supported by this library, which is currently version 1 and version 4 (denoted by "v1" and "v4", respectively). `version` defaults to version 4 when not supplied.
+
+### Examples
+
+```
+> uuid
+3a91f950-dec8-4688-ba14-5b7bbfc7a563
+```
+
+```
+> uuid v1
+9d0b43e0-7696-11e3-964b-250efa37a98e
+```
+
+```
+> uuid v4
+6790ac7c-24ac-4f98-8464-42f6d98a53ae
+```
+
+## Testing
+
+In node.js
+
+```
+npm test
+```
+
+In Browser
+
+```
+open test/test.html
+```
+
+### Benchmarking
+
+Requires node.js
+
+```
+npm install uuid uuid-js
+node benchmark/benchmark.js
+```
+
+For a more complete discussion of node-uuid performance, please see the `benchmark/README.md` file, and the [benchmark wiki](https://github.com/broofa/node-uuid/wiki/Benchmark)
+
+For browser performance [checkout the JSPerf tests](http://jsperf.com/node-uuid-performance).
+
+## Release notes
+
+### 1.4.0
+
+* Improved module context detection
+* Removed public RNG functions
+
+### 1.3.2
+
+* Improve tests and handling of v1() options (Issue #24)
+* Expose RNG option to allow for perf testing with different generators
+
+### 1.3.0
+
+* Support for version 1 ids, thanks to [@ctavan](https://github.com/ctavan)!
+* Support for node.js crypto API
+* De-emphasizing performance in favor of a) cryptographic quality PRNGs where available and b) more manageable code
diff --git a/node_modules/node-firefox-install-app/node_modules/node-uuid/benchmark/README.md b/node_modules/node-firefox-install-app/node_modules/node-uuid/benchmark/README.md
new file mode 100644
index 0000000..aaeb2ea
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/node-uuid/benchmark/README.md
@@ -0,0 +1,53 @@
+# node-uuid Benchmarks
+
+### Results
+
+To see the results of our benchmarks visit https://github.com/broofa/node-uuid/wiki/Benchmark
+
+### Run them yourself
+
+node-uuid comes with some benchmarks to measure performance of generating UUIDs. These can be run using node.js. node-uuid is being benchmarked against some other uuid modules, that are available through npm namely `uuid` and `uuid-js`.
+
+To prepare and run the benchmark issue;
+
+```
+npm install uuid uuid-js
+node benchmark/benchmark.js
+```
+
+You'll see an output like this one:
+
+```
+# v4
+nodeuuid.v4(): 854700 uuids/second
+nodeuuid.v4('binary'): 788643 uuids/second
+nodeuuid.v4('binary', buffer): 1336898 uuids/second
+uuid(): 479386 uuids/second
+uuid('binary'): 582072 uuids/second
+uuidjs.create(4): 312304 uuids/second
+
+# v1
+nodeuuid.v1(): 938086 uuids/second
+nodeuuid.v1('binary'): 683060 uuids/second
+nodeuuid.v1('binary', buffer): 1644736 uuids/second
+uuidjs.create(1): 190621 uuids/second
+```
+
+* The `uuid()` entries are for Nikhil Marathe's [uuid module](https://bitbucket.org/nikhilm/uuidjs) which is a wrapper around the native libuuid library.
+* The `uuidjs()` entries are for Patrick Negri's [uuid-js module](https://github.com/pnegri/uuid-js) which is a pure javascript implementation based on [UUID.js](https://github.com/LiosK/UUID.js) by LiosK.
+
+If you want to get more reliable results you can run the benchmark multiple times and write the output into a log file:
+
+```
+for i in {0..9}; do node benchmark/benchmark.js >> benchmark/bench_0.4.12.log; done;
+```
+
+If you're interested in how performance varies between different node versions, you can issue the above command multiple times.
+
+You can then use the shell script `bench.sh` provided in this directory to calculate the averages over all benchmark runs and draw a nice plot:
+
+```
+(cd benchmark/ && ./bench.sh)
+```
+
+This assumes you have [gnuplot](http://www.gnuplot.info/) and [ImageMagick](http://www.imagemagick.org/) installed. You'll find a nice `bench.png` graph in the `benchmark/` directory then.
diff --git a/node_modules/node-firefox-install-app/node_modules/node-uuid/benchmark/bench.gnu b/node_modules/node-firefox-install-app/node_modules/node-uuid/benchmark/bench.gnu
new file mode 100644
index 0000000..a342fbb
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/node-uuid/benchmark/bench.gnu
@@ -0,0 +1,174 @@
+#!/opt/local/bin/gnuplot -persist
+#
+#    
+#    	G N U P L O T
+#    	Version 4.4 patchlevel 3
+#    	last modified March 2011
+#    	System: Darwin 10.8.0
+#    
+#    	Copyright (C) 1986-1993, 1998, 2004, 2007-2010
+#    	Thomas Williams, Colin Kelley and many others
+#    
+#    	gnuplot home:     http://www.gnuplot.info
+#    	faq, bugs, etc:   type "help seeking-assistance"
+#    	immediate help:   type "help"
+#    	plot window:      hit 'h'
+set terminal postscript eps noenhanced defaultplex \
+ leveldefault color colortext \
+ solid linewidth 1.2 butt noclip \
+ palfuncparam 2000,0.003 \
+ "Helvetica" 14 
+set output 'bench.eps'
+unset clip points
+set clip one
+unset clip two
+set bar 1.000000 front
+set border 31 front linetype -1 linewidth 1.000
+set xdata
+set ydata
+set zdata
+set x2data
+set y2data
+set timefmt x "%d/%m/%y,%H:%M"
+set timefmt y "%d/%m/%y,%H:%M"
+set timefmt z "%d/%m/%y,%H:%M"
+set timefmt x2 "%d/%m/%y,%H:%M"
+set timefmt y2 "%d/%m/%y,%H:%M"
+set timefmt cb "%d/%m/%y,%H:%M"
+set boxwidth
+set style fill  empty border
+set style rectangle back fc lt -3 fillstyle   solid 1.00 border lt -1
+set style circle radius graph 0.02, first 0, 0 
+set dummy x,y
+set format x "% g"
+set format y "% g"
+set format x2 "% g"
+set format y2 "% g"
+set format z "% g"
+set format cb "% g"
+set angles radians
+unset grid
+set key title ""
+set key outside left top horizontal Right noreverse enhanced autotitles columnhead nobox
+set key noinvert samplen 4 spacing 1 width 0 height 0 
+set key maxcolumns 2 maxrows 0
+unset label
+unset arrow
+set style increment default
+unset style line
+set style line 1  linetype 1 linewidth 2.000 pointtype 1 pointsize default pointinterval 0
+unset style arrow
+set style histogram clustered gap 2 title  offset character 0, 0, 0
+unset logscale
+set offsets graph 0.05, 0.15, 0, 0
+set pointsize 1.5
+set pointintervalbox 1
+set encoding default
+unset polar
+unset parametric
+unset decimalsign
+set view 60, 30, 1, 1
+set samples 100, 100
+set isosamples 10, 10
+set surface
+unset contour
+set clabel '%8.3g'
+set mapping cartesian
+set datafile separator whitespace
+unset hidden3d
+set cntrparam order 4
+set cntrparam linear
+set cntrparam levels auto 5
+set cntrparam points 5
+set size ratio 0 1,1
+set origin 0,0
+set style data points
+set style function lines
+set xzeroaxis linetype -2 linewidth 1.000
+set yzeroaxis linetype -2 linewidth 1.000
+set zzeroaxis linetype -2 linewidth 1.000
+set x2zeroaxis linetype -2 linewidth 1.000
+set y2zeroaxis linetype -2 linewidth 1.000
+set ticslevel 0.5
+set mxtics default
+set mytics default
+set mztics default
+set mx2tics default
+set my2tics default
+set mcbtics default
+set xtics border in scale 1,0.5 mirror norotate  offset character 0, 0, 0
+set xtics  norangelimit
+set xtics   ()
+set ytics border in scale 1,0.5 mirror norotate  offset character 0, 0, 0
+set ytics autofreq  norangelimit
+set ztics border in scale 1,0.5 nomirror norotate  offset character 0, 0, 0
+set ztics autofreq  norangelimit
+set nox2tics
+set noy2tics
+set cbtics border in scale 1,0.5 mirror norotate  offset character 0, 0, 0
+set cbtics autofreq  norangelimit
+set title "" 
+set title  offset character 0, 0, 0 font "" norotate
+set timestamp bottom 
+set timestamp "" 
+set timestamp  offset character 0, 0, 0 font "" norotate
+set rrange [ * : * ] noreverse nowriteback  # (currently [8.98847e+307:-8.98847e+307] )
+set autoscale rfixmin
+set autoscale rfixmax
+set trange [ * : * ] noreverse nowriteback  # (currently [-5.00000:5.00000] )
+set autoscale tfixmin
+set autoscale tfixmax
+set urange [ * : * ] noreverse nowriteback  # (currently [-10.0000:10.0000] )
+set autoscale ufixmin
+set autoscale ufixmax
+set vrange [ * : * ] noreverse nowriteback  # (currently [-10.0000:10.0000] )
+set autoscale vfixmin
+set autoscale vfixmax
+set xlabel "" 
+set xlabel  offset character 0, 0, 0 font "" textcolor lt -1 norotate
+set x2label "" 
+set x2label  offset character 0, 0, 0 font "" textcolor lt -1 norotate
+set xrange [ * : * ] noreverse nowriteback  # (currently [-0.150000:3.15000] )
+set autoscale xfixmin
+set autoscale xfixmax
+set x2range [ * : * ] noreverse nowriteback  # (currently [0.00000:3.00000] )
+set autoscale x2fixmin
+set autoscale x2fixmax
+set ylabel "" 
+set ylabel  offset character 0, 0, 0 font "" textcolor lt -1 rotate by -270
+set y2label "" 
+set y2label  offset character 0, 0, 0 font "" textcolor lt -1 rotate by -270
+set yrange [ 0.00000 : 1.90000e+06 ] noreverse nowriteback  # (currently [:] )
+set autoscale yfixmin
+set autoscale yfixmax
+set y2range [ * : * ] noreverse nowriteback  # (currently [0.00000:1.90000e+06] )
+set autoscale y2fixmin
+set autoscale y2fixmax
+set zlabel "" 
+set zlabel  offset character 0, 0, 0 font "" textcolor lt -1 norotate
+set zrange [ * : * ] noreverse nowriteback  # (currently [-10.0000:10.0000] )
+set autoscale zfixmin
+set autoscale zfixmax
+set cblabel "" 
+set cblabel  offset character 0, 0, 0 font "" textcolor lt -1 rotate by -270
+set cbrange [ * : * ] noreverse nowriteback  # (currently [8.98847e+307:-8.98847e+307] )
+set autoscale cbfixmin
+set autoscale cbfixmax
+set zero 1e-08
+set lmargin  -1
+set bmargin  -1
+set rmargin  -1
+set tmargin  -1
+set pm3d explicit at s
+set pm3d scansautomatic
+set pm3d interpolate 1,1 flush begin noftriangles nohidden3d corners2color mean
+set palette positive nops_allcF maxcolors 0 gamma 1.5 color model RGB 
+set palette rgbformulae 7, 5, 15
+set colorbox default
+set colorbox vertical origin screen 0.9, 0.2, 0 size screen 0.05, 0.6, 0 front bdefault
+set loadpath 
+set fontpath 
+set fit noerrorvariables
+GNUTERM = "aqua"
+plot 'bench_results.txt' using 2:xticlabel(1) w lp lw 2, '' using 3:xticlabel(1) w lp lw 2, '' using 4:xticlabel(1) w lp lw 2, '' using 5:xticlabel(1) w lp lw 2, '' using 6:xticlabel(1) w lp lw 2, '' using 7:xticlabel(1) w lp lw 2, '' using 8:xticlabel(1) w lp lw 2, '' using 9:xticlabel(1) w lp lw 2
+#    EOF
diff --git a/node_modules/node-firefox-install-app/node_modules/node-uuid/benchmark/bench.sh b/node_modules/node-firefox-install-app/node_modules/node-uuid/benchmark/bench.sh
new file mode 100755
index 0000000..d870a0c
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/node-uuid/benchmark/bench.sh
@@ -0,0 +1,34 @@
+#!/bin/bash
+
+# for a given node version run:
+# for i in {0..9}; do node benchmark.js >> bench_0.6.2.log; done;
+
+PATTERNS=('nodeuuid.v1()' "nodeuuid.v1('binary'," 'nodeuuid.v4()' "nodeuuid.v4('binary'," "uuid()" "uuid('binary')" 'uuidjs.create(1)' 'uuidjs.create(4)' '140byte')
+FILES=(node_uuid_v1_string node_uuid_v1_buf node_uuid_v4_string node_uuid_v4_buf libuuid_v4_string libuuid_v4_binary uuidjs_v1_string uuidjs_v4_string 140byte_es)
+INDICES=(2 3 2 3 2 2 2 2 2)
+VERSIONS=$( ls bench_*.log | sed -e 's/^bench_\([0-9\.]*\)\.log/\1/' | tr "\\n" " " )
+TMPJOIN="tmp_join"
+OUTPUT="bench_results.txt"
+
+for I in ${!FILES[*]}; do
+  F=${FILES[$I]}
+  P=${PATTERNS[$I]}
+  INDEX=${INDICES[$I]}
+  echo "version $F" > $F
+  for V in $VERSIONS; do
+    (VAL=$( grep "$P" bench_$V.log | LC_ALL=en_US awk '{ sum += $'$INDEX' } END { print sum/NR }' ); echo $V $VAL) >> $F
+  done
+  if [ $I == 0 ]; then
+    cat $F > $TMPJOIN
+  else
+    join $TMPJOIN $F > $OUTPUT
+    cp $OUTPUT $TMPJOIN
+  fi
+  rm $F
+done
+
+rm $TMPJOIN
+
+gnuplot bench.gnu
+convert -density 200 -resize 800x560 -flatten bench.eps bench.png
+rm bench.eps
diff --git a/node_modules/node-firefox-install-app/node_modules/node-uuid/benchmark/benchmark-native.c b/node_modules/node-firefox-install-app/node_modules/node-uuid/benchmark/benchmark-native.c
new file mode 100644
index 0000000..dbfc75f
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/node-uuid/benchmark/benchmark-native.c
@@ -0,0 +1,34 @@
+/*
+Test performance of native C UUID generation
+
+To Compile: cc -luuid benchmark-native.c -o benchmark-native
+*/
+
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <uuid/uuid.h>
+
+int main() {
+  uuid_t myid;
+  char buf[36+1];
+  int i;
+  struct timeval t;
+  double start, finish;
+
+  gettimeofday(&t, NULL);
+  start = t.tv_sec + t.tv_usec/1e6;
+
+  int n = 2e5;
+  for (i = 0; i < n; i++) {
+    uuid_generate(myid);
+    uuid_unparse(myid, buf);
+  }
+
+  gettimeofday(&t, NULL);
+  finish = t.tv_sec + t.tv_usec/1e6;
+  double dur = finish - start;
+
+  printf("%d uuids/sec", (int)(n/dur));
+  return 0;
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/node-uuid/benchmark/benchmark.js b/node_modules/node-firefox-install-app/node_modules/node-uuid/benchmark/benchmark.js
new file mode 100644
index 0000000..40e6efb
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/node-uuid/benchmark/benchmark.js
@@ -0,0 +1,84 @@
+try {
+  var nodeuuid = require('../uuid');
+} catch (e) {
+  console.error('node-uuid require failed - skipping tests');
+}
+
+try {
+  var uuid = require('uuid');
+} catch (e) {
+  console.error('uuid require failed - skipping tests');
+}
+
+try {
+  var uuidjs = require('uuid-js');
+} catch (e) {
+  console.error('uuid-js require failed - skipping tests');
+}
+
+var N = 5e5;
+
+function rate(msg, t) {
+  console.log(msg + ': ' +
+    (N / (Date.now() - t) * 1e3 | 0) +
+    ' uuids/second');
+}
+
+console.log('# v4');
+
+// node-uuid - string form
+if (nodeuuid) {
+  for (var i = 0, t = Date.now(); i < N; i++) nodeuuid.v4();
+  rate('nodeuuid.v4() - using node.js crypto RNG', t);
+
+  for (var i = 0, t = Date.now(); i < N; i++) nodeuuid.v4({rng: nodeuuid.mathRNG});
+  rate('nodeuuid.v4() - using Math.random() RNG', t);
+
+  for (var i = 0, t = Date.now(); i < N; i++) nodeuuid.v4('binary');
+  rate('nodeuuid.v4(\'binary\')', t);
+
+  var buffer = new nodeuuid.BufferClass(16);
+  for (var i = 0, t = Date.now(); i < N; i++) nodeuuid.v4('binary', buffer);
+  rate('nodeuuid.v4(\'binary\', buffer)', t);
+}
+
+// libuuid - string form
+if (uuid) {
+  for (var i = 0, t = Date.now(); i < N; i++) uuid();
+  rate('uuid()', t);
+
+  for (var i = 0, t = Date.now(); i < N; i++) uuid('binary');
+  rate('uuid(\'binary\')', t);
+}
+
+// uuid-js - string form
+if (uuidjs) {
+  for (var i = 0, t = Date.now(); i < N; i++) uuidjs.create(4);
+  rate('uuidjs.create(4)', t);
+}
+
+// 140byte.es
+for (var i = 0, t = Date.now(); i < N; i++) 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g,function(s,r){r=Math.random()*16|0;return (s=='x'?r:r&0x3|0x8).toString(16)});
+rate('140byte.es_v4', t);
+
+console.log('');
+console.log('# v1');
+
+// node-uuid - v1 string form
+if (nodeuuid) {
+  for (var i = 0, t = Date.now(); i < N; i++) nodeuuid.v1();
+  rate('nodeuuid.v1()', t);
+
+  for (var i = 0, t = Date.now(); i < N; i++) nodeuuid.v1('binary');
+  rate('nodeuuid.v1(\'binary\')', t);
+
+  var buffer = new nodeuuid.BufferClass(16);
+  for (var i = 0, t = Date.now(); i < N; i++) nodeuuid.v1('binary', buffer);
+  rate('nodeuuid.v1(\'binary\', buffer)', t);
+}
+
+// uuid-js - v1 string form
+if (uuidjs) {
+  for (var i = 0, t = Date.now(); i < N; i++) uuidjs.create(1);
+  rate('uuidjs.create(1)', t);
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/node-uuid/bin/uuid b/node_modules/node-firefox-install-app/node_modules/node-uuid/bin/uuid
new file mode 100755
index 0000000..f732e99
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/node-uuid/bin/uuid
@@ -0,0 +1,26 @@
+#!/usr/bin/env node
+
+var path = require('path');
+var uuid = require(path.join(__dirname, '..'));
+
+var arg = process.argv[2];
+
+if ('--help' === arg) {
+  console.log('\n  USAGE: uuid [version] [options]\n\n');
+  console.log('  options:\n');
+  console.log('  --help                     Display this message and exit\n');
+  process.exit(0);
+}
+
+if (null == arg) {
+  console.log(uuid());
+  process.exit(0);
+}
+
+if ('v1' !== arg && 'v4' !== arg) {
+  console.error('Version must be RFC4122 version 1 or version 4, denoted as "v1" or "v4"');
+  process.exit(1);
+}
+
+console.log(uuid[arg]());
+process.exit(0);
diff --git a/node_modules/node-firefox-install-app/node_modules/node-uuid/bower.json b/node_modules/node-firefox-install-app/node_modules/node-uuid/bower.json
new file mode 100644
index 0000000..1656dc8
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/node-uuid/bower.json
@@ -0,0 +1,23 @@
+{
+  "name": "node-uuid",
+  "version": "1.4.3",
+  "homepage": "https://github.com/broofa/node-uuid",
+  "authors": [
+    "Robert Kieffer <robert@broofa.com>"
+  ],
+  "description": "Rigorous implementation of RFC4122 (v1 and v4) UUIDs.",
+  "main": "uuid.js",
+  "keywords": [
+    "uuid",
+    "gid",
+    "rfc4122"
+  ],
+  "license": "MIT",
+  "ignore": [
+    "**/.*",
+    "node_modules",
+    "bower_components",
+    "test",
+    "tests"
+  ]
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/node-uuid/component.json b/node_modules/node-firefox-install-app/node_modules/node-uuid/component.json
new file mode 100644
index 0000000..149f84b
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/node-uuid/component.json
@@ -0,0 +1,18 @@
+{
+  "name": "node-uuid",
+  "repo": "broofa/node-uuid",
+  "description": "Rigorous implementation of RFC4122 (v1 and v4) UUIDs.",
+  "version": "1.4.3",
+  "author": "Robert Kieffer <robert@broofa.com>",
+  "contributors": [
+    {"name": "Christoph Tavan <dev@tavan.de>", "github": "https://github.com/ctavan"}
+  ],
+  "keywords": ["uuid", "guid", "rfc4122"],
+  "dependencies": {},
+  "development": {},
+  "main": "uuid.js",
+  "scripts": [
+    "uuid.js"
+  ],
+  "license": "MIT"
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/node-uuid/package.json b/node_modules/node-firefox-install-app/node_modules/node-uuid/package.json
new file mode 100644
index 0000000..ac71465
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/node-uuid/package.json
@@ -0,0 +1,65 @@
+{
+  "name": "node-uuid",
+  "description": "Rigorous implementation of RFC4122 (v1 and v4) UUIDs.",
+  "url": "http://github.com/broofa/node-uuid",
+  "keywords": [
+    "uuid",
+    "guid",
+    "rfc4122"
+  ],
+  "author": {
+    "name": "Robert Kieffer",
+    "email": "robert@broofa.com"
+  },
+  "contributors": [
+    {
+      "name": "Christoph Tavan",
+      "email": "dev@tavan.de"
+    }
+  ],
+  "bin": {
+    "uuid": "./bin/uuid"
+  },
+  "scripts": {
+    "test": "node test/test.js"
+  },
+  "lib": ".",
+  "main": "./uuid.js",
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/broofa/node-uuid.git"
+  },
+  "version": "1.4.3",
+  "licenses": [
+    {
+      "type": "MIT",
+      "url": "https://raw.github.com/broofa/node-uuid/master/LICENSE.md"
+    }
+  ],
+  "gitHead": "886463c660a095dfebfa69603921a8d156fdb12c",
+  "bugs": {
+    "url": "https://github.com/broofa/node-uuid/issues"
+  },
+  "homepage": "https://github.com/broofa/node-uuid",
+  "_id": "node-uuid@1.4.3",
+  "_shasum": "319bb7a56e7cb63f00b5c0cd7851cd4b4ddf1df9",
+  "_from": "node-uuid@^1.4.2",
+  "_npmVersion": "1.4.28",
+  "_npmUser": {
+    "name": "broofa",
+    "email": "robert@broofa.com"
+  },
+  "maintainers": [
+    {
+      "name": "broofa",
+      "email": "robert@broofa.com"
+    }
+  ],
+  "dist": {
+    "shasum": "319bb7a56e7cb63f00b5c0cd7851cd4b4ddf1df9",
+    "tarball": "http://registry.npmjs.org/node-uuid/-/node-uuid-1.4.3.tgz"
+  },
+  "directories": {},
+  "_resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.3.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/node-uuid/test/compare_v1.js b/node_modules/node-firefox-install-app/node_modules/node-uuid/test/compare_v1.js
new file mode 100644
index 0000000..05af822
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/node-uuid/test/compare_v1.js
@@ -0,0 +1,63 @@
+var assert = require('assert'),
+    nodeuuid = require('../uuid'),
+    uuidjs = require('uuid-js'),
+    libuuid = require('uuid').generate,
+    util = require('util'),
+    exec = require('child_process').exec,
+    os = require('os');
+
+// On Mac Os X / macports there's only the ossp-uuid package that provides uuid
+// On Linux there's uuid-runtime which provides uuidgen
+var uuidCmd = os.type() === 'Darwin' ? 'uuid -1' : 'uuidgen -t';
+
+function compare(ids) {
+  console.log(ids);
+  for (var i = 0; i < ids.length; i++) {
+    var id = ids[i].split('-');
+    id = [id[2], id[1], id[0]].join('');
+    ids[i] = id;
+  }
+  var sorted = ([].concat(ids)).sort();
+
+  if (sorted.toString() !== ids.toString()) {
+    console.log('Warning: sorted !== ids');
+  } else {
+    console.log('everything in order!');
+  }
+}
+
+// Test time order of v1 uuids
+var ids = [];
+while (ids.length < 10e3) ids.push(nodeuuid.v1());
+
+var max = 10;
+console.log('node-uuid:');
+ids = [];
+for (var i = 0; i < max; i++) ids.push(nodeuuid.v1());
+compare(ids);
+
+console.log('');
+console.log('uuidjs:');
+ids = [];
+for (var i = 0; i < max; i++) ids.push(uuidjs.create(1).toString());
+compare(ids);
+
+console.log('');
+console.log('libuuid:');
+ids = [];
+var count = 0;
+var last = function() {
+  compare(ids);
+}
+var cb = function(err, stdout, stderr) {
+  ids.push(stdout.substring(0, stdout.length-1));
+  count++;
+  if (count < max) {
+    return next();
+  }
+  last();
+};
+var next = function() {
+  exec(uuidCmd, cb);
+};
+next();
diff --git a/node_modules/node-firefox-install-app/node_modules/node-uuid/test/test.html b/node_modules/node-firefox-install-app/node_modules/node-uuid/test/test.html
new file mode 100644
index 0000000..d80326e
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/node-uuid/test/test.html
@@ -0,0 +1,17 @@
+<html>
+  <head>
+    <style>
+      div {
+        font-family: monospace;
+        font-size: 8pt;
+      }
+      div.log {color: #444;}
+      div.warn {color: #550;}
+      div.error {color: #800; font-weight: bold;}
+    </style>
+    <script src="../uuid.js"></script>
+  </head>
+  <body>
+    <script src="./test.js"></script>
+  </body>
+</html>
diff --git a/node_modules/node-firefox-install-app/node_modules/node-uuid/test/test.js b/node_modules/node-firefox-install-app/node_modules/node-uuid/test/test.js
new file mode 100644
index 0000000..2469225
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/node-uuid/test/test.js
@@ -0,0 +1,228 @@
+if (!this.uuid) {
+  // node.js
+  uuid = require('../uuid');
+}
+
+//
+// x-platform log/assert shims
+//
+
+function _log(msg, type) {
+  type = type || 'log';
+
+  if (typeof(document) != 'undefined') {
+    document.write('<div class="' + type + '">' + msg.replace(/\n/g, '<br />') + '</div>');
+  }
+  if (typeof(console) != 'undefined') {
+    var color = {
+      log: '\033[39m',
+      warn: '\033[33m',
+      error: '\033[31m'
+    };
+    console[type](color[type] + msg + color.log);
+  }
+}
+
+function log(msg) {_log(msg, 'log');}
+function warn(msg) {_log(msg, 'warn');}
+function error(msg) {_log(msg, 'error');}
+
+function assert(res, msg) {
+  if (!res) {
+    error('FAIL: ' + msg);
+  } else {
+    log('Pass: ' + msg);
+  }
+}
+
+//
+// Unit tests
+//
+
+// Verify ordering of v1 ids created with explicit times
+var TIME = 1321644961388; // 2011-11-18 11:36:01.388-08:00
+
+function compare(name, ids) {
+  ids = ids.map(function(id) {
+    return id.split('-').reverse().join('-');
+  }).sort();
+  var sorted = ([].concat(ids)).sort();
+
+  assert(sorted.toString() == ids.toString(), name + ' have expected order');
+}
+
+// Verify ordering of v1 ids created using default behavior
+compare('uuids with current time', [
+  uuid.v1(),
+  uuid.v1(),
+  uuid.v1(),
+  uuid.v1(),
+  uuid.v1()
+]);
+
+// Verify ordering of v1 ids created with explicit times
+compare('uuids with time option', [
+  uuid.v1({msecs: TIME - 10*3600*1000}),
+  uuid.v1({msecs: TIME - 1}),
+  uuid.v1({msecs: TIME}),
+  uuid.v1({msecs: TIME + 1}),
+  uuid.v1({msecs: TIME + 28*24*3600*1000})
+]);
+
+assert(
+  uuid.v1({msecs: TIME}) != uuid.v1({msecs: TIME}),
+  'IDs created at same msec are different'
+);
+
+// Verify throw if too many ids created
+var thrown = false;
+try {
+  uuid.v1({msecs: TIME, nsecs: 10000});
+} catch (e) {
+  thrown = true;
+}
+assert(thrown, 'Exception thrown when > 10K ids created in 1 ms');
+
+// Verify clock regression bumps clockseq
+var uidt = uuid.v1({msecs: TIME});
+var uidtb = uuid.v1({msecs: TIME - 1});
+assert(
+  parseInt(uidtb.split('-')[3], 16) - parseInt(uidt.split('-')[3], 16) === 1,
+  'Clock regression by msec increments the clockseq'
+);
+
+// Verify clock regression bumps clockseq
+var uidtn = uuid.v1({msecs: TIME, nsecs: 10});
+var uidtnb = uuid.v1({msecs: TIME, nsecs: 9});
+assert(
+  parseInt(uidtnb.split('-')[3], 16) - parseInt(uidtn.split('-')[3], 16) === 1,
+  'Clock regression by nsec increments the clockseq'
+);
+
+// Verify explicit options produce expected id
+var id = uuid.v1({
+  msecs: 1321651533573,
+  nsecs: 5432,
+  clockseq: 0x385c,
+  node: [ 0x61, 0xcd, 0x3c, 0xbb, 0x32, 0x10 ]
+});
+assert(id == 'd9428888-122b-11e1-b85c-61cd3cbb3210', 'Explicit options produce expected id');
+
+// Verify adjacent ids across a msec boundary are 1 time unit apart
+var u0 = uuid.v1({msecs: TIME, nsecs: 9999});
+var u1 = uuid.v1({msecs: TIME + 1, nsecs: 0});
+
+var before = u0.split('-')[0], after = u1.split('-')[0];
+var dt = parseInt(after, 16) - parseInt(before, 16);
+assert(dt === 1, 'Ids spanning 1ms boundary are 100ns apart');
+
+//
+// Test parse/unparse
+//
+
+id = '00112233445566778899aabbccddeeff';
+assert(uuid.unparse(uuid.parse(id.substr(0,10))) ==
+  '00112233-4400-0000-0000-000000000000', 'Short parse');
+assert(uuid.unparse(uuid.parse('(this is the uuid -> ' + id + id)) ==
+  '00112233-4455-6677-8899-aabbccddeeff', 'Dirty parse');
+
+//
+// Perf tests
+//
+
+var generators = {
+  v1: uuid.v1,
+  v4: uuid.v4
+};
+
+var UUID_FORMAT = {
+  v1: /[0-9a-f]{8}-[0-9a-f]{4}-1[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/i,
+  v4: /[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/i
+};
+
+var N = 1e4;
+
+// Get %'age an actual value differs from the ideal value
+function divergence(actual, ideal) {
+  return Math.round(100*100*(actual - ideal)/ideal)/100;
+}
+
+function rate(msg, t) {
+  log(msg + ': ' + (N / (Date.now() - t) * 1e3 | 0) + ' uuids\/second');
+}
+
+for (var version in generators) {
+  var counts = {}, max = 0;
+  var generator = generators[version];
+  var format = UUID_FORMAT[version];
+
+  log('\nSanity check ' + N + ' ' + version + ' uuids');
+  for (var i = 0, ok = 0; i < N; i++) {
+    id = generator();
+    if (!format.test(id)) {
+      throw Error(id + ' is not a valid UUID string');
+    }
+
+    if (id != uuid.unparse(uuid.parse(id))) {
+      assert(fail, id + ' is not a valid id');
+    }
+
+    // Count digits for our randomness check
+    if (version == 'v4') {
+      var digits = id.replace(/-/g, '').split('');
+      for (var j = digits.length-1; j >= 0; j--) {
+        var c = digits[j];
+        max = Math.max(max, counts[c] = (counts[c] || 0) + 1);
+      }
+    }
+  }
+
+  // Check randomness for v4 UUIDs
+  if (version == 'v4') {
+    // Limit that we get worried about randomness. (Purely empirical choice, this!)
+    var limit = 2*100*Math.sqrt(1/N);
+
+    log('\nChecking v4 randomness.  Distribution of Hex Digits (% deviation from ideal)');
+
+    for (var i = 0; i < 16; i++) {
+      var c = i.toString(16);
+      var bar = '', n = counts[c], p = Math.round(n/max*100|0);
+
+      // 1-3,5-8, and D-F: 1:16 odds over 30 digits
+      var ideal = N*30/16;
+      if (i == 4) {
+        // 4: 1:1 odds on 1 digit, plus 1:16 odds on 30 digits
+        ideal = N*(1 + 30/16);
+      } else if (i >= 8 && i <= 11) {
+        // 8-B: 1:4 odds on 1 digit, plus 1:16 odds on 30 digits
+        ideal = N*(1/4 + 30/16);
+      } else {
+        // Otherwise: 1:16 odds on 30 digits
+        ideal = N*30/16;
+      }
+      var d = divergence(n, ideal);
+
+      // Draw bar using UTF squares (just for grins)
+      var s = n/max*50 | 0;
+      while (s--) bar += '=';
+
+      assert(Math.abs(d) < limit, c + ' |' + bar + '| ' + counts[c] + ' (' + d + '% < ' + limit + '%)');
+    }
+  }
+}
+
+// Perf tests
+for (var version in generators) {
+  log('\nPerformance testing ' + version + ' UUIDs');
+  var generator = generators[version];
+  var buf = new uuid.BufferClass(16);
+
+  for (var i = 0, t = Date.now(); i < N; i++) generator();
+  rate('uuid.' + version + '()', t);
+
+  for (var i = 0, t = Date.now(); i < N; i++) generator('binary');
+  rate('uuid.' + version + '(\'binary\')', t);
+
+  for (var i = 0, t = Date.now(); i < N; i++) generator('binary', buf);
+  rate('uuid.' + version + '(\'binary\', buffer)', t);
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/node-uuid/uuid.js b/node_modules/node-firefox-install-app/node_modules/node-uuid/uuid.js
new file mode 100644
index 0000000..0a61769
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/node-uuid/uuid.js
@@ -0,0 +1,247 @@
+//     uuid.js
+//
+//     Copyright (c) 2010-2012 Robert Kieffer
+//     MIT License - http://opensource.org/licenses/mit-license.php
+
+(function() {
+  var _global = this;
+
+  // Unique ID creation requires a high quality random # generator.  We feature
+  // detect to determine the best RNG source, normalizing to a function that
+  // returns 128-bits of randomness, since that's what's usually required
+  var _rng;
+
+  // Node.js crypto-based RNG - http://nodejs.org/docs/v0.6.2/api/crypto.html
+  //
+  // Moderately fast, high quality
+  if (typeof(_global.require) == 'function') {
+    try {
+      var _rb = _global.require('crypto').randomBytes;
+      _rng = _rb && function() {return _rb(16);};
+    } catch(e) {}
+  }
+
+  if (!_rng && _global.crypto && crypto.getRandomValues) {
+    // WHATWG crypto-based RNG - http://wiki.whatwg.org/wiki/Crypto
+    //
+    // Moderately fast, high quality
+    var _rnds8 = new Uint8Array(16);
+    _rng = function whatwgRNG() {
+      crypto.getRandomValues(_rnds8);
+      return _rnds8;
+    };
+  }
+
+  if (!_rng) {
+    // Math.random()-based (RNG)
+    //
+    // If all else fails, use Math.random().  It's fast, but is of unspecified
+    // quality.
+    var  _rnds = new Array(16);
+    _rng = function() {
+      for (var i = 0, r; i < 16; i++) {
+        if ((i & 0x03) === 0) r = Math.random() * 0x100000000;
+        _rnds[i] = r >>> ((i & 0x03) << 3) & 0xff;
+      }
+
+      return _rnds;
+    };
+  }
+
+  // Buffer class to use
+  var BufferClass = typeof(_global.Buffer) == 'function' ? _global.Buffer : Array;
+
+  // Maps for number <-> hex string conversion
+  var _byteToHex = [];
+  var _hexToByte = {};
+  for (var i = 0; i < 256; i++) {
+    _byteToHex[i] = (i + 0x100).toString(16).substr(1);
+    _hexToByte[_byteToHex[i]] = i;
+  }
+
+  // **`parse()` - Parse a UUID into it's component bytes**
+  function parse(s, buf, offset) {
+    var i = (buf && offset) || 0, ii = 0;
+
+    buf = buf || [];
+    s.toLowerCase().replace(/[0-9a-f]{2}/g, function(oct) {
+      if (ii < 16) { // Don't overflow!
+        buf[i + ii++] = _hexToByte[oct];
+      }
+    });
+
+    // Zero out remaining bytes if string was short
+    while (ii < 16) {
+      buf[i + ii++] = 0;
+    }
+
+    return buf;
+  }
+
+  // **`unparse()` - Convert UUID byte array (ala parse()) into a string**
+  function unparse(buf, offset) {
+    var i = offset || 0, bth = _byteToHex;
+    return  bth[buf[i++]] + bth[buf[i++]] +
+            bth[buf[i++]] + bth[buf[i++]] + '-' +
+            bth[buf[i++]] + bth[buf[i++]] + '-' +
+            bth[buf[i++]] + bth[buf[i++]] + '-' +
+            bth[buf[i++]] + bth[buf[i++]] + '-' +
+            bth[buf[i++]] + bth[buf[i++]] +
+            bth[buf[i++]] + bth[buf[i++]] +
+            bth[buf[i++]] + bth[buf[i++]];
+  }
+
+  // **`v1()` - Generate time-based UUID**
+  //
+  // Inspired by https://github.com/LiosK/UUID.js
+  // and http://docs.python.org/library/uuid.html
+
+  // random #'s we need to init node and clockseq
+  var _seedBytes = _rng();
+
+  // Per 4.5, create and 48-bit node id, (47 random bits + multicast bit = 1)
+  var _nodeId = [
+    _seedBytes[0] | 0x01,
+    _seedBytes[1], _seedBytes[2], _seedBytes[3], _seedBytes[4], _seedBytes[5]
+  ];
+
+  // Per 4.2.2, randomize (14 bit) clockseq
+  var _clockseq = (_seedBytes[6] << 8 | _seedBytes[7]) & 0x3fff;
+
+  // Previous uuid creation time
+  var _lastMSecs = 0, _lastNSecs = 0;
+
+  // See https://github.com/broofa/node-uuid for API details
+  function v1(options, buf, offset) {
+    var i = buf && offset || 0;
+    var b = buf || [];
+
+    options = options || {};
+
+    var clockseq = options.clockseq != null ? options.clockseq : _clockseq;
+
+    // UUID timestamps are 100 nano-second units since the Gregorian epoch,
+    // (1582-10-15 00:00).  JSNumbers aren't precise enough for this, so
+    // time is handled internally as 'msecs' (integer milliseconds) and 'nsecs'
+    // (100-nanoseconds offset from msecs) since unix epoch, 1970-01-01 00:00.
+    var msecs = options.msecs != null ? options.msecs : new Date().getTime();
+
+    // Per 4.2.1.2, use count of uuid's generated during the current clock
+    // cycle to simulate higher resolution clock
+    var nsecs = options.nsecs != null ? options.nsecs : _lastNSecs + 1;
+
+    // Time since last uuid creation (in msecs)
+    var dt = (msecs - _lastMSecs) + (nsecs - _lastNSecs)/10000;
+
+    // Per 4.2.1.2, Bump clockseq on clock regression
+    if (dt < 0 && options.clockseq == null) {
+      clockseq = clockseq + 1 & 0x3fff;
+    }
+
+    // Reset nsecs if clock regresses (new clockseq) or we've moved onto a new
+    // time interval
+    if ((dt < 0 || msecs > _lastMSecs) && options.nsecs == null) {
+      nsecs = 0;
+    }
+
+    // Per 4.2.1.2 Throw error if too many uuids are requested
+    if (nsecs >= 10000) {
+      throw new Error('uuid.v1(): Can\'t create more than 10M uuids/sec');
+    }
+
+    _lastMSecs = msecs;
+    _lastNSecs = nsecs;
+    _clockseq = clockseq;
+
+    // Per 4.1.4 - Convert from unix epoch to Gregorian epoch
+    msecs += 12219292800000;
+
+    // `time_low`
+    var tl = ((msecs & 0xfffffff) * 10000 + nsecs) % 0x100000000;
+    b[i++] = tl >>> 24 & 0xff;
+    b[i++] = tl >>> 16 & 0xff;
+    b[i++] = tl >>> 8 & 0xff;
+    b[i++] = tl & 0xff;
+
+    // `time_mid`
+    var tmh = (msecs / 0x100000000 * 10000) & 0xfffffff;
+    b[i++] = tmh >>> 8 & 0xff;
+    b[i++] = tmh & 0xff;
+
+    // `time_high_and_version`
+    b[i++] = tmh >>> 24 & 0xf | 0x10; // include version
+    b[i++] = tmh >>> 16 & 0xff;
+
+    // `clock_seq_hi_and_reserved` (Per 4.2.2 - include variant)
+    b[i++] = clockseq >>> 8 | 0x80;
+
+    // `clock_seq_low`
+    b[i++] = clockseq & 0xff;
+
+    // `node`
+    var node = options.node || _nodeId;
+    for (var n = 0; n < 6; n++) {
+      b[i + n] = node[n];
+    }
+
+    return buf ? buf : unparse(b);
+  }
+
+  // **`v4()` - Generate random UUID**
+
+  // See https://github.com/broofa/node-uuid for API details
+  function v4(options, buf, offset) {
+    // Deprecated - 'format' argument, as supported in v1.2
+    var i = buf && offset || 0;
+
+    if (typeof(options) == 'string') {
+      buf = options == 'binary' ? new BufferClass(16) : null;
+      options = null;
+    }
+    options = options || {};
+
+    var rnds = options.random || (options.rng || _rng)();
+
+    // Per 4.4, set bits for version and `clock_seq_hi_and_reserved`
+    rnds[6] = (rnds[6] & 0x0f) | 0x40;
+    rnds[8] = (rnds[8] & 0x3f) | 0x80;
+
+    // Copy bytes to buffer, if provided
+    if (buf) {
+      for (var ii = 0; ii < 16; ii++) {
+        buf[i + ii] = rnds[ii];
+      }
+    }
+
+    return buf || unparse(rnds);
+  }
+
+  // Export public API
+  var uuid = v4;
+  uuid.v1 = v1;
+  uuid.v4 = v4;
+  uuid.parse = parse;
+  uuid.unparse = unparse;
+  uuid.BufferClass = BufferClass;
+
+  if (typeof(module) != 'undefined' && module.exports) {
+    // Publish as node.js module
+    module.exports = uuid;
+  } else  if (typeof define === 'function' && define.amd) {
+    // Publish as AMD module
+    define(function() {return uuid;});
+ 
+
+  } else {
+    // Publish as global (in browsers)
+    var _previousRoot = _global.uuid;
+
+    // **`noConflict()` - (browser only) to reset global 'uuid' var**
+    uuid.noConflict = function() {
+      _global.uuid = _previousRoot;
+      return uuid;
+    };
+
+    _global.uuid = uuid;
+  }
+}).call(this);
diff --git a/node_modules/node-firefox-install-app/node_modules/temporary/.npmignore b/node_modules/node-firefox-install-app/node_modules/temporary/.npmignore
new file mode 100644
index 0000000..3b3a32f
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/temporary/.npmignore
@@ -0,0 +1,22 @@
+tmp
+node_modules
+*._
+*.tmp
+.monitor
+*.diff
+*.err
+*.orig
+*.log
+*.rej
+*.swo
+*.swp
+*.vi
+*~
+.DS_Store
+Thumbs.db
+.cache
+.project
+.settings
+.tmproj
+*.esproj
+nbproject
diff --git a/node_modules/node-firefox-install-app/node_modules/temporary/.travis.yml b/node_modules/node-firefox-install-app/node_modules/temporary/.travis.yml
new file mode 100644
index 0000000..8e3af8f
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/temporary/.travis.yml
@@ -0,0 +1,6 @@
+language: node_js
+node_js:
+  - 0.6
+  - 0.8
+  - 0.10
+  - 0.11
diff --git a/node_modules/node-firefox-install-app/node_modules/temporary/History.md b/node_modules/node-firefox-install-app/node_modules/temporary/History.md
new file mode 100644
index 0000000..318dafa
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/temporary/History.md
@@ -0,0 +1,24 @@
+
+0.0.8 / 2014-01-05
+==================
+
+  * Fix Node 0.11 error when path is frozen.
+
+0.0.7 / 2013-09-25
+==================
+
+  * Normalize windows paths correctly
+  * On Windows lib/detector.js not adding trailing slash when needed.
+
+0.0.6 / 2013-09-13
+==================
+
+  * Export Dir and File in index
+  * Test on Node 0.10 as well
+  * Get tests passing on OSX
+  * Update Readme.md
+
+0.0.5 / 2012-11-12
+==================
+
+  * Solved process.env issue in detector on certain version so linux.
diff --git a/node_modules/node-firefox-install-app/node_modules/temporary/Makefile b/node_modules/node-firefox-install-app/node_modules/temporary/Makefile
new file mode 100644
index 0000000..98957d6
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/temporary/Makefile
@@ -0,0 +1,11 @@
+TESTS = test/*.test.js
+
+test:
+	@NODE_ENV=test ./node_modules/.bin/mocha \
+	--reporter spec \
+	$(TESTS)
+
+clean:
+	rm -f examples/tmp/*
+
+.PHONY: test clean
diff --git a/node_modules/node-firefox-install-app/node_modules/temporary/Readme.md b/node_modules/node-firefox-install-app/node_modules/temporary/Readme.md
new file mode 100644
index 0000000..b5c78f3
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/temporary/Readme.md
@@ -0,0 +1,81 @@
+[![Build Status](https://secure.travis-ci.org/vesln/temporary.png)](http://travis-ci.org/vesln/temporary)
+
+# temporary - The lord of tmp.
+
+## Intro
+
+Temporary provides an easy way to create temporary files and directories.
+It will create a temporary file/directory with a unique name.
+
+## Features
+
+- Generates unique name.
+- Auto-discovers tmp dir.
+
+## Installation
+
+	$ npm install temporary
+
+## Usage
+
+	var tmp = require('temporary');
+	var file = new tmp.File();
+	var dir = new tmp.Dir();
+	
+	console.log(file.path); // path.
+	console.log(dir.path); // path.
+	
+	file.unlink();
+	dir.rmdir();
+
+## Methods
+
+### File
+
+- File.readFile
+- File.readFileSync
+- File.writeFile
+- File.writeFileSync
+- File.open
+- File.openSync
+- File.close
+- File.closeSync
+- File.unlink
+- File.unlinkSync
+
+### Dir
+
+- Dir.rmdir
+- Dir.rmdirSync
+
+## Tests
+
+	$ make test
+
+## Contribution
+
+Bug fixes and features are welcomed.
+
+## License
+
+MIT License
+
+Copyright (C) 2012 Veselin Todorov
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/node_modules/node-firefox-install-app/node_modules/temporary/examples/dir.js b/node_modules/node-firefox-install-app/node_modules/temporary/examples/dir.js
new file mode 100644
index 0000000..5182b83
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/temporary/examples/dir.js
@@ -0,0 +1,18 @@
+/**
+ * Temporary - The lord of tmp.
+ * 
+ * Author: Veselin Todorov <hi@vesln.com>
+ * Licensed under the MIT License.
+ */
+
+var Tempdir = require('../lib/dir');
+var dir = new Tempdir('foo') // name - optional
+
+console.log(dir.path); // path.
+
+/**
+ * You can also use:
+ * 
+ * dir.rmdir
+ * dir.rmdirSync
+ */
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/temporary/examples/file.js b/node_modules/node-firefox-install-app/node_modules/temporary/examples/file.js
new file mode 100644
index 0000000..6a3fe31
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/temporary/examples/file.js
@@ -0,0 +1,26 @@
+/**
+ * Temporary - The lord of tmp.
+ * 
+ * Author: Veselin Todorov <hi@vesln.com>
+ * Licensed under the MIT License.
+ */
+
+var Tempfile = require('../lib/file');
+var file = new Tempfile('foo') // name - optional
+
+console.log(file.path); // file path.
+
+/**
+ * You can also use:
+ * 
+ * file.readFile
+ * file.readFileSync
+ * file.writeFile
+ * file.writeFileSync
+ * file.open
+ * file.openSync
+ * file.close
+ * file.closeSync
+ * file.unlink
+ * file.unlinkSync
+ */
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/temporary/index.js b/node_modules/node-firefox-install-app/node_modules/temporary/index.js
new file mode 100644
index 0000000..a519266
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/temporary/index.js
@@ -0,0 +1,26 @@
+/**
+ * Temporary - The lord of tmp.
+ * 
+ * Author: Veselin Todorov <hi@vesln.com>
+ * Licensed under the MIT License.
+ */
+
+/**
+ * Dependencies.
+ */
+var package = require('package')(module);
+
+/**
+ * Version.
+ */
+module.exports.version = package.version;
+
+/**
+ * Exporting the temp file
+ */
+module.exports.File = require('./lib/file');
+
+/**
+ * Exporting the temp directory.
+ */
+module.exports.Dir = require('./lib/dir');
diff --git a/node_modules/node-firefox-install-app/node_modules/temporary/lib/base.js b/node_modules/node-firefox-install-app/node_modules/temporary/lib/base.js
new file mode 100644
index 0000000..a3e95c8
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/temporary/lib/base.js
@@ -0,0 +1,80 @@
+/**
+ * Temporary - The lord of tmp.
+ *
+ * Author: Veselin Todorov <hi@vesln.com>
+ * Licensed under the MIT License.
+ */
+
+/**
+ * Dependencies.
+ */
+var fs = require('fs');
+var path = require('path');
+var generator = require('./generator');
+var detector = require('./detector');
+
+/**
+ * Base constructor.
+ *
+ * @param {String|null} name
+ */
+function Base(name) {
+  this.init(name);
+};
+
+/**
+ * Initializes the class.
+ *
+ * @param {String|null} name
+ */
+Base.prototype.init = function(name) {
+  var filename = generator.build(name);
+  this.create(filename);
+  this.path = filename;
+};
+
+/**
+ * Converts the arguments object to array and
+ * append `this.path` as first element.
+ *
+ * @returns {Array}
+ */
+Base.prototype.prepareArgs = function(args) {
+  args = Array.prototype.slice.call(args);
+  args.unshift(this.path);
+  return args;
+};
+
+/**
+ * Renames the dir/file.
+ *
+ * @param {String} name
+ * @param {Function} cb Callback.
+ */
+Base.prototype.rename = function(name, cb) {
+  var self = this;
+  var args = arguments;
+  var tmp = path.normalize(path.dirname(self.path) + '/' + name);
+
+  fs.rename(this.path, tmp, function(err) {
+    self.path = tmp;
+    if (args.length === 2) cb(err);
+  });
+};
+
+/**
+ * Renames the dir/file sync.
+ *
+ * @param {String} name
+ */
+Base.prototype.renameSync = function(name) {
+  var tmp = path.normalize(path.dirname(this.path) + '/' + name);
+  var result = fs.renameSync(this.path, tmp);
+  this.path = tmp;
+  return result;
+};
+
+/**
+ * Exporting the lib.
+ */
+module.exports = Base;
diff --git a/node_modules/node-firefox-install-app/node_modules/temporary/lib/detector.js b/node_modules/node-firefox-install-app/node_modules/temporary/lib/detector.js
new file mode 100644
index 0000000..acbef82
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/temporary/lib/detector.js
@@ -0,0 +1,66 @@
+/**
+ * Temporary - The lord of tmp.
+ * 
+ * Author: Veselin Todorov <hi@vesln.com>
+ * Licensed under the MIT License.
+ */
+ 
+/**
+ * Detection stolen from NPM (https://github.com/isaacs/npm/)
+ * 
+ * Copyright 2009, 2010, 2011 Isaac Z. Schlueter (the "Author")
+ * MIT License (https://github.com/isaacs/npm/blob/master/LICENSE)
+ */ 
+
+/**
+ * Detector namespace.
+ * 
+ * @type {Object}
+ */
+var detector = module.exports;
+
+var normalize = function(path) {
+  var last = path[path.length - 1];
+  
+  if (detector.platform() !== "win32") {
+    if (last !== '/') {
+      path += '/';
+    }
+  } else {
+    //This is fine b/c Windows will 
+    //correctly resolve filepaths with additional slashes
+    //and it is not correct to assume that on Windows the value
+    //of path will be a string that terminates in '\'.
+    //
+    //See: http://stackoverflow.com/questions/4158597/extra-slashes-in-path-variable-of-file-copy-or-directory-createdirectory-met
+    //
+    path += '/';
+  }
+  
+  return path;
+}
+
+/**
+ * Returns tmp dir. Thank you npm.
+ * 
+ * @returns {String} tmp dir.
+ */
+detector.tmp = function() {
+  var temp = process.env.TMPDIR
+      || process.env.TMP
+      || process.env.TEMP
+      || (detector.platform() === "win32" ? "c:\\windows\\temp\\" : "/tmp/")
+  
+  return normalize(temp);
+};
+
+/**
+ * Returns the platform. Allows Tests to verify all behaviors.
+ *
+ * @returns {String} platform.
+ */
+detector.platform = function() {
+  return process.platform;
+};
+
+detector._normalize = normalize;
diff --git a/node_modules/node-firefox-install-app/node_modules/temporary/lib/dir.js b/node_modules/node-firefox-install-app/node_modules/temporary/lib/dir.js
new file mode 100644
index 0000000..73acecc
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/temporary/lib/dir.js
@@ -0,0 +1,57 @@
+/**
+ * Temporary - The lord of tmp.
+ *
+ * Author: Veselin Todorov <hi@vesln.com>
+ * Licensed under the MIT License.
+ */
+
+/**
+ * Dependencies.
+ */
+var fs = require('fs');
+var path = require('path');
+var generator = require('./generator');
+var detector = require('./detector');
+var Base = require('./base');
+
+/**
+ * Dir constructor.
+ *
+ * @param {String|null} name
+ */
+function Dir(name) {
+  this.init(name);
+};
+
+/**
+ * Dir extends from tmp.
+ */
+Dir.prototype.__proto__ = Base.prototype;
+
+/**
+ * Creates new file.
+ *
+ * @param {String} dirname
+ */
+Dir.prototype.create = function(dirname) {
+  return fs.mkdirSync(path.normalize(dirname), 0777);
+};
+
+/**
+ * Asynchronous dir.
+ */
+Dir.prototype.rmdir = function() {
+  fs.rmdir.apply(fs, this.prepareArgs(arguments));
+};
+
+/**
+ * Synchronous rmdir.
+ */
+Dir.prototype.rmdirSync = function() {
+  return fs.rmdirSync.apply(fs, this.prepareArgs(arguments));
+};
+
+/**
+ * Exporting the lib.
+ */
+module.exports = Dir;
diff --git a/node_modules/node-firefox-install-app/node_modules/temporary/lib/file.js b/node_modules/node-firefox-install-app/node_modules/temporary/lib/file.js
new file mode 100644
index 0000000..49de026
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/temporary/lib/file.js
@@ -0,0 +1,113 @@
+/**
+ * Temporary - The lord of tmp.
+ *
+ * Author: Veselin Todorov <hi@vesln.com>
+ * Licensed under the MIT License.
+ */
+
+/**
+ * Dependencies.
+ */
+var fs = require('fs');
+var path = require('path');
+var generator = require('./generator');
+var detector = require('./detector');
+var Base = require('./base');
+
+/**
+ * File constructor.
+ *
+ * @param {String|null} name
+ */
+function File(name) {
+  this.init(name);
+};
+
+/**
+ * File extends from tmp.
+ */
+File.prototype.__proto__ = Base.prototype;
+
+/**
+ * Creates new file.
+ *
+ * @param {String} filename
+ */
+File.prototype.create = function(filename) {
+  return fs.writeFileSync(path.normalize(filename), '');
+};
+
+/**
+ * Asynchronously reads the entire contents of a file.
+ */
+File.prototype.readFile = function() {
+  fs.readFile.apply(fs, this.prepareArgs(arguments));
+};
+
+/**
+ * Synchronous read.
+ */
+File.prototype.readFileSync = function() {
+  return fs.readFileSync.apply(fs, this.prepareArgs(arguments));
+};
+
+/**
+ * Asynchronously writes data to a file.
+ */
+File.prototype.writeFile = function() {
+  fs.writeFile.apply(fs, this.prepareArgs(arguments));
+};
+
+/**
+ * Synchronous writes data to a file.
+ */
+File.prototype.writeFileSync = function() {
+  return fs.writeFileSync.apply(fs, this.prepareArgs(arguments));
+};
+
+/**
+ * Asynchronous file open.
+ */
+File.prototype.open = function() {
+  fs.open.apply(fs, this.prepareArgs(arguments));
+};
+
+/**
+ * Synchronous open.
+ */
+File.prototype.openSync = function() {
+  return fs.openSync.apply(fs, this.prepareArgs(arguments));
+};
+
+/**
+ * Asynchronous close.
+ */
+File.prototype.close = function() {
+  fs.close.apply(fs, Array.prototype.slice.call(arguments));
+};
+
+/**
+ * Synchronous close.
+ */
+File.prototype.closeSync = function() {
+  return fs.closeSync.apply(fs, Array.prototype.slice.call(arguments));
+};
+
+/**
+ * Asynchronous unlink.
+ */
+File.prototype.unlink = function() {
+  fs.unlink.apply(fs, this.prepareArgs(arguments));
+};
+
+/**
+ * Synchronous unlink.
+ */
+File.prototype.unlinkSync = function() {
+  return fs.unlinkSync.apply(fs, this.prepareArgs(arguments));
+};
+
+/**
+ * Exporting the lib.
+ */
+module.exports = File;
diff --git a/node_modules/node-firefox-install-app/node_modules/temporary/lib/generator.js b/node_modules/node-firefox-install-app/node_modules/temporary/lib/generator.js
new file mode 100644
index 0000000..a68f0ba
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/temporary/lib/generator.js
@@ -0,0 +1,48 @@
+/**
+ * Temporary - The lord of tmp.
+ *
+ * Author: Veselin Todorov <hi@vesln.com>
+ * Licensed under the MIT License.
+ */
+
+/**
+ * Dependencies.
+ */
+var fs = require('fs');
+var path = require('path');
+var detector = require('./detector');
+var existsSync = fs.existsSync || path.existsSync;
+
+/**
+ * Generator namespace.
+ *
+ * @type {Object}
+ */
+var generator = module.exports;
+
+/**
+ * Generates random name.
+ *
+ * @returns {String}
+ */
+generator.name = function() {
+  var id = null;
+  var tmp = detector.tmp();
+  do {
+    id = Date.now() + Math.random();
+  } while(existsSync(tmp + '/' + id));
+
+  return id + '';
+};
+
+/**
+ * Buld a full name. (tmp dir + name).
+ *
+ * @param {String} name
+ * @returns {String}
+ */
+generator.build = function(name) {
+  var filename = detector.tmp();
+  if (name) filename += name + '.';
+  return filename + this.name();
+};
diff --git a/node_modules/node-firefox-install-app/node_modules/temporary/node_modules/package/.npmignore b/node_modules/node-firefox-install-app/node_modules/temporary/node_modules/package/.npmignore
new file mode 100644
index 0000000..3b3a32f
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/temporary/node_modules/package/.npmignore
@@ -0,0 +1,22 @@
+tmp
+node_modules
+*._
+*.tmp
+.monitor
+*.diff
+*.err
+*.orig
+*.log
+*.rej
+*.swo
+*.swp
+*.vi
+*~
+.DS_Store
+Thumbs.db
+.cache
+.project
+.settings
+.tmproj
+*.esproj
+nbproject
diff --git a/node_modules/node-firefox-install-app/node_modules/temporary/node_modules/package/.travis.yml b/node_modules/node-firefox-install-app/node_modules/temporary/node_modules/package/.travis.yml
new file mode 100644
index 0000000..b8e1f17
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/temporary/node_modules/package/.travis.yml
@@ -0,0 +1,3 @@
+language: node_js
+node_js:
+  - 0.6
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/temporary/node_modules/package/History.md b/node_modules/node-firefox-install-app/node_modules/temporary/node_modules/package/History.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/temporary/node_modules/package/History.md
diff --git a/node_modules/node-firefox-install-app/node_modules/temporary/node_modules/package/Makefile b/node_modules/node-firefox-install-app/node_modules/temporary/node_modules/package/Makefile
new file mode 100644
index 0000000..277485c
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/temporary/node_modules/package/Makefile
@@ -0,0 +1,12 @@
+TESTS = $(shell find test -iname \*.test.js)
+
+test:
+	@NODE_ENV=test ./node_modules/.bin/mocha \
+	--require should \
+	--reporter spec \
+	$(TESTS)
+
+clean:
+	rm -f examples/tmp/*
+
+.PHONY: test clean
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/temporary/node_modules/package/Readme.md b/node_modules/node-firefox-install-app/node_modules/temporary/node_modules/package/Readme.md
new file mode 100644
index 0000000..c531965
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/temporary/node_modules/package/Readme.md
@@ -0,0 +1,54 @@
+[![Build Status](https://secure.travis-ci.org/vesln/package.png)](http://travis-ci.org/vesln/package)
+
+# package - Easy package.json exports.
+
+## Intro
+
+This module provides an easy and simple way to export package.json data.
+
+## Installation
+
+	$ npm install package
+
+## Usage
+
+	var package = require('package')(module); // contains package.json data.
+	var yourAwesomeModule = {};
+	yourAwesomeModule.version = package.version;
+
+## Tests
+
+	$ make test
+
+## Contribution
+
+Bug fixes and features are welcomed.
+
+## Other similar modules
+
+- pkginfo (https://github.com/indexzero/node-pkginfo) by indexzero.
+- JSON.parse + fs.readFile
+
+## License
+
+MIT License
+
+Copyright (C) 2012 Veselin Todorov
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/temporary/node_modules/package/examples/custom_path.js b/node_modules/node-firefox-install-app/node_modules/temporary/node_modules/package/examples/custom_path.js
new file mode 100644
index 0000000..a03c0cc
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/temporary/node_modules/package/examples/custom_path.js
@@ -0,0 +1,14 @@
+/**
+ * package - Easy package.json exports.
+ * 
+ * Author: Veselin Todorov <hi@vesln.com>
+ * Licensed under the MIT License.
+ */
+
+/**
+ * Dependencies.
+ */
+
+var package = require('../')(__dirname + '/..'); // parent dir.
+
+console.log(package); // This will contain the package.json data.
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/temporary/node_modules/package/examples/module.js b/node_modules/node-firefox-install-app/node_modules/temporary/node_modules/package/examples/module.js
new file mode 100644
index 0000000..c479069
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/temporary/node_modules/package/examples/module.js
@@ -0,0 +1,14 @@
+/**
+ * package - Easy package.json exports.
+ * 
+ * Author: Veselin Todorov <hi@vesln.com>
+ * Licensed under the MIT License.
+ */
+
+/**
+ * Dependencies.
+ */
+
+var package = require('../')(module);
+
+console.log(package); // This will contain the package.json data.
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/temporary/node_modules/package/lib/package.js b/node_modules/node-firefox-install-app/node_modules/temporary/node_modules/package/lib/package.js
new file mode 100644
index 0000000..348db0c
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/temporary/node_modules/package/lib/package.js
@@ -0,0 +1,64 @@
+/**
+ * package - Easy package.json exports.
+ * 
+ * Author: Veselin Todorov <hi@vesln.com>
+ * Licensed under the MIT License.
+ */
+
+/**
+ * Dependencies.
+ */
+var fs = require('fs');
+var path = require('path');
+var exists = fs.existsSync || path.existsSync;
+
+/**
+ * Package.
+ * 
+ * @param {String|null} location
+ * @returns {Object} package.json data
+ */
+var package = function(location) {
+  if (location === Object(location)) {
+    location = package.discover(location);
+  }
+  return package.read(path.normalize(location + '/package.json'));
+};
+
+/**
+ * Reads and parses a package.json file.
+ * 
+ * @param {String} file
+ * @returns {Object} package.json data
+ */
+package.read = function(file) {
+  var data = fs.readFileSync(file, 'utf8');
+  return JSON.parse(data);
+};
+
+/**
+ * Makes an atempt to find package.json file.
+ * 
+ * @returns {Object} package.json data
+ */
+package.discover = function(module) {
+  var location = path.dirname(module.filename);
+  var found = null;
+  
+  while (!found) {
+    if (exists(location + '/package.json')) {
+      found = location;
+    } else if (location !== '/') {
+      location = path.dirname(location);
+    } else {
+      throw new Error('package.json can not be located');
+    }
+  }
+  
+  return found;
+};
+
+/**
+ * Exporting the lib.
+ */
+module.exports = package;
diff --git a/node_modules/node-firefox-install-app/node_modules/temporary/node_modules/package/package.json b/node_modules/node-firefox-install-app/node_modules/temporary/node_modules/package/package.json
new file mode 100644
index 0000000..10df33f
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/temporary/node_modules/package/package.json
@@ -0,0 +1,35 @@
+{
+  "name": "package",
+  "version": "1.0.1",
+  "description": "Easy package.json exports.",
+  "keywords": [
+    "package.json"
+  ],
+  "author": {
+    "name": "Veselin Todorov",
+    "email": "hi@vesln.com"
+  },
+  "devDependencies": {
+    "mocha": "0.3.3",
+    "should": "0.3.2"
+  },
+  "repository": {
+    "type": "git",
+    "url": "http://github.com/vesln/package.git"
+  },
+  "homepage": "http://github.com/vesln/package",
+  "scripts": {
+    "test": "make test"
+  },
+  "main": "./lib/package",
+  "engines": {
+    "node": ">= 0.6.0"
+  },
+  "readme": "[![Build Status](https://secure.travis-ci.org/vesln/package.png)](http://travis-ci.org/vesln/package)\n\n# package - Easy package.json exports.\n\n## Intro\n\nThis module provides an easy and simple way to export package.json data.\n\n## Installation\n\n\t$ npm install package\n\n## Usage\n\n\tvar package = require('package')(module); // contains package.json data.\n\tvar yourAwesomeModule = {};\n\tyourAwesomeModule.version = package.version;\n\n## Tests\n\n\t$ make test\n\n## Contribution\n\nBug fixes and features are welcomed.\n\n## Other similar modules\n\n- pkginfo (https://github.com/indexzero/node-pkginfo) by indexzero.\n- JSON.parse + fs.readFile\n\n## License\n\nMIT License\n\nCopyright (C) 2012 Veselin Todorov\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies\nof the Software, and to permit persons to whom the Software is furnished to do\nso, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.",
+  "readmeFilename": "Readme.md",
+  "bugs": {
+    "url": "https://github.com/vesln/package/issues"
+  },
+  "_id": "package@1.0.1",
+  "_from": "package@>= 1.0.0 < 1.2.0"
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/temporary/node_modules/package/test/index.test.js b/node_modules/node-firefox-install-app/node_modules/temporary/node_modules/package/test/index.test.js
new file mode 100644
index 0000000..c43364b
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/temporary/node_modules/package/test/index.test.js
@@ -0,0 +1,42 @@
+/**
+ * package - Easy package.json exports.
+ * 
+ * Author: Veselin Todorov <hi@vesln.com>
+ * Licensed under the MIT License.
+ */
+
+/**
+ * Dependencies.
+ */ 
+var package = require('../');
+
+describe('package', function() {
+  describe('read', function() {
+    it('should read and parse .json file', function() {
+      var result = package.read(__dirname + '/support/package.json');
+      result.should.eql({
+        name: 'test-package-json-file',
+        version: '0.0.1',
+        private: true 
+      });
+    });
+  });
+  
+  it('should read and parse given .json file', function() {
+    var result = package(__dirname + '/support');
+    result.should.eql({
+      name: 'test-package-json-file',
+      version: '0.0.1',
+      private: true 
+    });
+  });
+  
+  it('should autodiscover, read and parse package.json', function() {
+    var result = package(module);
+    result.should.eql({
+      name: 'test-package-json-file',
+      version: '0.0.1',
+      private: true 
+    });
+  });
+});
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/temporary/node_modules/package/test/nested/two/nested.test.js b/node_modules/node-firefox-install-app/node_modules/temporary/node_modules/package/test/nested/two/nested.test.js
new file mode 100644
index 0000000..99c012e
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/temporary/node_modules/package/test/nested/two/nested.test.js
@@ -0,0 +1,22 @@
+/**
+ * package - Easy package.json exports.
+ * 
+ * Author: Veselin Todorov <hi@vesln.com>
+ * Licensed under the MIT License.
+ */
+ 
+/**
+ * Dependencies.
+ */ 
+var package = require('../../../');
+
+describe('nested package json', function() {
+  it('should autodiscover, read and parse package.json', function() {
+    var result = package(module);
+    result.should.eql({
+      name: 'test-package-json-file',
+      version: '0.0.1',
+      private: true 
+    });
+  });
+});
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/temporary/node_modules/package/test/package.json b/node_modules/node-firefox-install-app/node_modules/temporary/node_modules/package/test/package.json
new file mode 100644
index 0000000..ed5e671
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/temporary/node_modules/package/test/package.json
@@ -0,0 +1,5 @@
+{
+    "name": "test-package-json-file"
+  , "version": "0.0.1"
+  , "private": true
+}
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/temporary/node_modules/package/test/support/package.json b/node_modules/node-firefox-install-app/node_modules/temporary/node_modules/package/test/support/package.json
new file mode 100644
index 0000000..ed5e671
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/temporary/node_modules/package/test/support/package.json
@@ -0,0 +1,5 @@
+{
+    "name": "test-package-json-file"
+  , "version": "0.0.1"
+  , "private": true
+}
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/temporary/package.json b/node_modules/node-firefox-install-app/node_modules/temporary/package.json
new file mode 100644
index 0000000..b16ea2f
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/temporary/package.json
@@ -0,0 +1,60 @@
+{
+  "name": "temporary",
+  "version": "0.0.8",
+  "description": "The lord of tmp.",
+  "keywords": [
+    "tmp",
+    "temp",
+    "tempfile",
+    "tempdirectory"
+  ],
+  "author": {
+    "name": "Veselin Todorov",
+    "email": "hi@vesln.com"
+  },
+  "dependencies": {
+    "package": ">= 1.0.0 < 1.2.0"
+  },
+  "devDependencies": {
+    "mocha": "1.2.x",
+    "chai": "*",
+    "sinon": "1.2.0"
+  },
+  "repository": {
+    "type": "git",
+    "url": "http://github.com/vesln/temporary.git"
+  },
+  "homepage": "http://github.com/vesln/temporary",
+  "scripts": {
+    "test": "make test"
+  },
+  "main": "index",
+  "engines": {
+    "node": ">= 0.6.0"
+  },
+  "readme": "[![Build Status](https://secure.travis-ci.org/vesln/temporary.png)](http://travis-ci.org/vesln/temporary)\n\n# temporary - The lord of tmp.\n\n## Intro\n\nTemporary provides an easy way to create temporary files and directories.\nIt will create a temporary file/directory with a unique name.\n\n## Features\n\n- Generates unique name.\n- Auto-discovers tmp dir.\n\n## Installation\n\n\t$ npm install temporary\n\n## Usage\n\n\tvar tmp = require('temporary');\n\tvar file = new tmp.File();\n\tvar dir = new tmp.Dir();\n\t\n\tconsole.log(file.path); // path.\n\tconsole.log(dir.path); // path.\n\t\n\tfile.unlink();\n\tdir.rmdir();\n\n## Methods\n\n### File\n\n- File.readFile\n- File.readFileSync\n- File.writeFile\n- File.writeFileSync\n- File.open\n- File.openSync\n- File.close\n- File.closeSync\n- File.unlink\n- File.unlinkSync\n\n### Dir\n\n- Dir.rmdir\n- Dir.rmdirSync\n\n## Tests\n\n\t$ make test\n\n## Contribution\n\nBug fixes and features are welcomed.\n\n## License\n\nMIT License\n\nCopyright (C) 2012 Veselin Todorov\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies\nof the Software, and to permit persons to whom the Software is furnished to do\nso, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n",
+  "readmeFilename": "Readme.md",
+  "bugs": {
+    "url": "https://github.com/vesln/temporary/issues"
+  },
+  "_id": "temporary@0.0.8",
+  "dist": {
+    "shasum": "a18a981d28ba8ca36027fb3c30538c3ecb740ac0",
+    "tarball": "http://registry.npmjs.org/temporary/-/temporary-0.0.8.tgz"
+  },
+  "_from": "temporary@0.0.8",
+  "_npmVersion": "1.2.30",
+  "_npmUser": {
+    "name": "vesln",
+    "email": "hi@vesln.com"
+  },
+  "maintainers": [
+    {
+      "name": "vesln",
+      "email": "hi@vesln.com"
+    }
+  ],
+  "directories": {},
+  "_shasum": "a18a981d28ba8ca36027fb3c30538c3ecb740ac0",
+  "_resolved": "https://registry.npmjs.org/temporary/-/temporary-0.0.8.tgz"
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/temporary/test/base.test.js b/node_modules/node-firefox-install-app/node_modules/temporary/test/base.test.js
new file mode 100644
index 0000000..2fdea28
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/temporary/test/base.test.js
@@ -0,0 +1,57 @@
+/**
+ * Temporary - The lord of tmp.
+ *
+ * Author: Veselin Todorov <hi@vesln.com>
+ * Licensed under the MIT License.
+ */
+
+/**
+ * Dependencies.
+ */
+var path = require('path');
+var fs = require('fs');
+var existsSync = fs.existsSync || path.existsSync;
+
+var Base = require('../lib/base');
+var generator = require('../lib/generator');
+var should = require('chai').should();
+
+Base.prototype.create = function() {};
+
+describe('Base', function() {
+  describe('rename', function() {
+    it('should rename the directory', function(done) {
+      var tmp = new Base;
+      tmp.path = generator.build();
+      fs.mkdirSync(path.normalize(tmp.path), 0777);
+      existsSync(tmp.path).should.be.ok;
+      tmp.rename('foo', function(err) {
+        existsSync(tmp.path).should.be.ok;
+        done();
+      });
+    });
+  });
+
+  describe('renameSync', function() {
+    it('should rename the directory', function() {
+      var tmp = new Base('foo');
+      tmp.path = generator.build();
+      fs.mkdirSync(path.normalize(tmp.path), 0777);
+      var oldPath = tmp.path;
+      existsSync(tmp.path).should.be.ok;
+      tmp.renameSync('foo3');
+      existsSync(tmp.path).should.be.ok;
+      path.should.not.eql(oldPath);
+    });
+  });
+
+  describe('prepareArgs', function() {
+    it('should convert object to array and append path as first element', function() {
+      var tmp = new Base('foo');
+      var args = { 0: 'foo' };
+      args.length = 1;
+      tmp.path = generator.build();
+      tmp.prepareArgs(args).should.eql([tmp.path, 'foo']);
+    });
+  });
+});
diff --git a/node_modules/node-firefox-install-app/node_modules/temporary/test/detector.test.js b/node_modules/node-firefox-install-app/node_modules/temporary/test/detector.test.js
new file mode 100644
index 0000000..13c4cb7
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/temporary/test/detector.test.js
@@ -0,0 +1,42 @@
+/**
+ * Temporary - The lord of tmp.
+ *
+ * Author: Veselin Todorov <hi@vesln.com>
+ * Licensed under the MIT License.
+ */
+
+/**
+ * Dependencies.
+ */
+var detector = require('../lib/detector');
+var should = require('chai').should();
+
+describe('detector', function() {
+  describe('tmp', function() {
+    it('should return the tmp dir of the system', function() {
+      var tmp =  process.env.TMPDIR
+          || process.env.TMP
+          || process.env.TEMP
+          || (process.platform === "win32" ? "c:\\windows\\temp\\" : "/tmp/");
+      detector.tmp().should.eql(tmp);
+    });
+    it('should normalize windows paths correctly', function () {
+      var platform_noConflict = detector.platform;
+      
+      detector.platform = function() {
+        return 'win32';
+      };
+      detector._normalize('c:\\windows\\foo\\bar\\')
+          .should.eql('c:\\windows\\foo\\bar\\/');
+      detector._normalize('c:/windows/foo/bar/')
+         .should.eql('c:/windows/foo/bar//');
+      detector._normalize('c:/windows/foo/bar')
+         .should.eql('c:/windows/foo/bar/');
+      detector._normalize('c:\\windows\\foo\\bar')
+         .should.eql('c:\\windows\\foo\\bar/');
+      detector.platform = platform_noConflict;
+    });
+
+  });
+});
+
diff --git a/node_modules/node-firefox-install-app/node_modules/temporary/test/dir.test.js b/node_modules/node-firefox-install-app/node_modules/temporary/test/dir.test.js
new file mode 100644
index 0000000..8875a88
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/temporary/test/dir.test.js
@@ -0,0 +1,44 @@
+/**
+ * Temporary - The lord of tmp.
+ *
+ * Author: Veselin Todorov <hi@vesln.com>
+ * Licensed under the MIT License.
+ */
+
+/**
+ * Dependencies.
+ */
+var fs = require('fs');
+var path = require('path');
+var existsSync = fs.existsSync || path.existsSync;
+
+var Tempdir = require('../lib/dir');
+var sinon = require('sinon');
+var should = require('chai').should();
+
+describe('Tempdir', function() {
+  it('should create file', function() {
+    var tmp = new Tempdir('foo');
+    existsSync(tmp.path).should.be.ok;
+  });
+
+  describe('rmdir', function() {
+    it('should remove the directory', function() {
+      var tmp = new Tempdir('foo');
+      sinon.spy(fs, 'rmdir');
+      tmp.rmdir();
+      fs.rmdir.getCall(0).args[0].should.eql(tmp.path);
+      fs.rmdir.restore();
+    });
+  });
+
+  describe('rmdirSync', function() {
+    it('should remove the directory', function() {
+      var tmp = new Tempdir('foo');
+      sinon.spy(fs, 'rmdirSync');
+      tmp.rmdirSync();
+      fs.rmdirSync.getCall(0).args[0].should.eql(tmp.path);
+      fs.rmdirSync.restore();
+    });
+  });
+});
diff --git a/node_modules/node-firefox-install-app/node_modules/temporary/test/file.test.js b/node_modules/node-firefox-install-app/node_modules/temporary/test/file.test.js
new file mode 100644
index 0000000..9927e31
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/temporary/test/file.test.js
@@ -0,0 +1,126 @@
+/**
+ * Temporary - The lord of tmp.
+ *
+ * Author: Veselin Todorov <hi@vesln.com>
+ * Licensed under the MIT License.
+ */
+
+/**
+ * Dependencies.
+ */
+var path = require('path');
+var fs = require('fs');
+var existsSync = fs.existsSync || path.existsSync;
+
+var Tempfile = require('../lib/file');
+var sinon = require('sinon');
+var should = require('chai').should();
+
+describe('Tempfile', function() {
+  it('should create file', function() {
+    var tmp = new Tempfile('foo');
+    existsSync(tmp.path).should.be.ok;
+  });
+
+  describe('readFile', function() {
+    it('should call fs.readfile', function() {
+      sinon.spy(fs, 'readFile');
+      var tmp = new Tempfile;
+      tmp.readFile();
+      fs.readFile.getCall(0).args[0].should.eql(tmp.path);
+      fs.readFile.restore();
+    });
+  });
+
+  describe('readFileSync', function() {
+    it('should call fs.readfileSync', function() {
+      sinon.spy(fs, 'readFileSync');
+      var tmp = new Tempfile;
+      tmp.readFileSync();
+      fs.readFileSync.getCall(0).args[0].should.eql(tmp.path);
+      fs.readFileSync.restore();
+    });
+  });
+
+  describe('writeFile', function() {
+    it('should call fs.readfile', function() {
+      sinon.spy(fs, 'writeFile');
+      var tmp = new Tempfile;
+      tmp.writeFile();
+      fs.writeFile.getCall(0).args[0].should.eql(tmp.path);
+      fs.writeFile.restore();
+    });
+  });
+
+  describe('writeFileSync', function() {
+    it('should call fs.writeFileSync', function() {
+      sinon.spy(fs, 'writeFileSync');
+      var tmp = new Tempfile;
+      tmp.writeFileSync();
+      fs.writeFileSync.getCall(0).args[0].should.eql(tmp.path);
+      fs.writeFileSync.restore();
+    });
+  });
+
+  describe('open', function() {
+    it('should call fs.open', function() {
+      sinon.spy(fs, 'open');
+      var tmp = new Tempfile;
+      tmp.open('r');
+      fs.open.getCall(0).args[0].should.eql(tmp.path);
+      fs.open.restore();
+    });
+  });
+
+  describe('openSync', function() {
+    it('should call fs.openSync', function() {
+      sinon.spy(fs, 'openSync');
+      var tmp = new Tempfile;
+      tmp.openSync('r');
+      fs.openSync.getCall(0).args[0].should.eql(tmp.path);
+      fs.openSync.restore();
+    });
+  });
+
+  describe('close', function() {
+    it('should call fs.close', function() {
+      sinon.spy(fs, 'close');
+      var tmp = new Tempfile;
+      var fd = tmp.openSync('r');
+      tmp.close(fd);
+      fs.close.getCall(0).args[0].should.eql(fd);
+      fs.close.restore();
+    });
+  });
+
+  describe('closeSync', function() {
+    it('should call fs.closeSync', function() {
+      sinon.spy(fs, 'closeSync');
+      var tmp = new Tempfile;
+      var fd = tmp.openSync('r');
+      tmp.closeSync(fd);
+      fs.closeSync.getCall(0).args[0].should.eql(fd);
+      fs.closeSync.restore();
+    });
+  });
+
+  describe('unlink', function() {
+    it('should call fs.unlink', function() {
+      sinon.spy(fs, 'unlink');
+      var tmp = new Tempfile;
+      tmp.unlink();
+      fs.unlink.getCall(0).args[0].should.eql(tmp.path);
+      fs.unlink.restore();
+    });
+  });
+
+  describe('unlinkSync', function() {
+    it('should call fs.readfileSync', function() {
+      sinon.spy(fs, 'unlinkSync');
+      var tmp = new Tempfile;
+      tmp.unlinkSync();
+      fs.unlinkSync.getCall(0).args[0].should.eql(tmp.path);
+      fs.unlinkSync.restore();
+    });
+  });
+});
diff --git a/node_modules/node-firefox-install-app/node_modules/temporary/test/generator.test.js b/node_modules/node-firefox-install-app/node_modules/temporary/test/generator.test.js
new file mode 100644
index 0000000..a1024f7
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/temporary/test/generator.test.js
@@ -0,0 +1,29 @@
+/**
+ * Temporary - The lord of tmp.
+ *
+ * Author: Veselin Todorov <hi@vesln.com>
+ * Licensed under the MIT License.
+ */
+
+/**
+ * Dependencies.
+ */
+var generator = require('../lib/generator');
+var detector = require('../lib/detector');
+var should = require('chai').should();
+
+describe('generator', function() {
+  describe('name', function() {
+    it('should unique generate name', function() {
+      generator.name().should.be.ok;
+    });
+  });
+
+  describe('build', function() {
+    it('should build full names', function() {
+      var tmp = detector.tmp();
+      generator.build().indexOf(tmp).should.equal(0);
+      generator.build('foo').indexOf(tmp + 'foo.').should.equal(0);
+    });
+  });
+});
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/.npmignore b/node_modules/node-firefox-install-app/node_modules/zip-folder/.npmignore
new file mode 100644
index 0000000..9daa824
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/.npmignore
@@ -0,0 +1,2 @@
+.DS_Store
+node_modules
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/README.md b/node_modules/node-firefox-install-app/node_modules/zip-folder/README.md
new file mode 100644
index 0000000..20c3844
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/README.md
@@ -0,0 +1,22 @@
+# node-zip-folder
+
+> zips a folder and calls your callback when it's done
+> AKA: something that maybe already exists in npmjs.org, but that I couldn't find.
+
+## Usage
+
+```javascript
+var zipFolder = require('zip-folder');
+
+zipFolder('/path/to/the/folder', '/path/to/archive.zip', function(err) {
+	if(err) {
+		console.log('oh no!', err);
+	} else {
+		console.log('EXCELLENT');
+	}
+});
+```
+
+## Tests
+
+Tests are in `tests.js` and built with nodeunit. Run `npm test` to run the tests.
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/index.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/index.js
new file mode 100644
index 0000000..61b8c81
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/index.js
@@ -0,0 +1,25 @@
+var fs = require('fs');
+var archiver = require('archiver');
+
+function zipFolder(srcFolder, zipFilePath, callback) {
+	var output = fs.createWriteStream(zipFilePath);
+	var zipArchive = archiver('zip');
+
+	output.on('close', function() {
+		callback();
+	});
+
+	zipArchive.pipe(output);
+
+	zipArchive.bulk([
+		{ cwd: srcFolder, src: ['**/*'], expand: true }
+	]);
+
+	zipArchive.finalize(function(err, bytes) {
+		if(err) {
+			callback(err);
+		}
+	});
+}
+
+module.exports = zipFolder;
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/LICENSE-MIT b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/LICENSE-MIT
new file mode 100644
index 0000000..bc56a8a
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/LICENSE-MIT
@@ -0,0 +1,22 @@
+Copyright (c) 2012-2014 Chris Talkington, contributors.

+

+Permission is hereby granted, free of charge, to any person

+obtaining a copy of this software and associated documentation

+files (the "Software"), to deal in the Software without

+restriction, including without limitation the rights to use,

+copy, modify, merge, publish, distribute, sublicense, and/or sell

+copies of the Software, and to permit persons to whom the

+Software is furnished to do so, subject to the following

+conditions:

+

+The above copyright notice and this permission notice shall be

+included in all copies or substantial portions of the Software.

+

+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,

+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES

+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND

+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT

+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,

+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING

+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR

+OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/README.md b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/README.md
new file mode 100644
index 0000000..5ef812f
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/README.md
@@ -0,0 +1,166 @@
+# Archiver v0.11.0 [![Build Status](https://travis-ci.org/ctalkington/node-archiver.svg?branch=master)](https://travis-ci.org/ctalkington/node-archiver)

+

+a streaming interface for archive generation

+

+[![NPM](https://nodei.co/npm/archiver.png)](https://nodei.co/npm/archiver/)

+

+## Install

+

+```bash

+npm install archiver --save

+```

+

+You can also use `npm install https://github.com/ctalkington/node-archiver/archive/master.tar.gz` to test upcoming versions.

+

+## Archiver

+

+#### create(format, options)

+

+Creates an Archiver instance based on the format (zip, tar, etc) passed. Parameters can be passed directly to `Archiver` constructor for convenience.

+

+#### registerFormat(format, module)

+

+Registers an archive format. Format modules are essentially transform streams with a few required methods. They will be further documented once a formal spec is in place.

+

+### Instance Methods

+

+Inherits [Transform Stream](http://nodejs.org/api/stream.html#stream_class_stream_transform) methods.

+

+#### append(input, data)

+

+Appends an input source (text string, buffer, or stream) to the instance. When the instance has received, processed, and emitted the input, the `entry` event is fired.

+

+Replaced `#addFile` in v0.5.

+

+```js

+archive.append('string', { name:'string.txt' });

+archive.append(new Buffer('string'), { name:'buffer.txt' });

+archive.append(fs.createReadStream('mydir/file.txt'), { name:'stream.txt' });

+archive.append(null, { name:'dir/' });

+```

+

+#### bulk(mappings)

+

+Appends multiple entries from passed array of src-dest mappings. A lazystream wrapper is used to prevent issues with open file limits.

+

+Globbing patterns are supported through use of the [file-utils](https://github.com/SBoudrias/file-utils) package. Please note that multiple src files to single dest file (ie concat) is not supported.

+

+The `data` property can be set (per src-dest mapping) to define data for matched entries.

+

+```js

+archive.bulk([

+  { src: ['mydir/**'], data: { date: new Date() } },

+  { expand: true, cwd: 'mydir', src: ['**'], dest: 'newdir' }

+]);

+```

+

+For more detail on this feature, please see [BULK.md](https://github.com/ctalkington/node-archiver/blob/master/BULK.md).

+

+#### file(filepath, data)

+

+Appends a file given its filepath using a lazystream wrapper to prevent issues with open file limits. When the instance has received, processed, and emitted the file, the `entry` event is fired.

+

+```js

+archive.file('mydir/file.txt', { name:'file.txt' });

+```

+

+#### finalize()

+

+Finalizes the instance and prevents further appending to the archive structure (queue will continue til drained). The `end`, `close` or `finish` events on the destination stream may fire right after calling this method so you should set listeners beforehand to properly detect stream completion.

+

+*You must call this method to get a valid archive and end the instance stream.*

+

+#### pointer()

+

+Returns the current byte length emitted by archiver. Use this in your end callback to log generated size.

+

+## Events

+

+Inherits [Transform Stream](http://nodejs.org/api/stream.html#stream_class_stream_transform) events.

+

+#### entry

+

+Fired when the input has been received, processed, and emitted. Passes entry data as first argument.

+

+## Zip

+

+### Options

+

+#### comment `string`

+

+Sets the zip comment.

+

+#### forceUTC `boolean`

+

+If true, forces the entry date to UTC. Helps with testing across timezones.

+

+#### store `boolean`

+

+If true, all entry contents will be archived without compression by default.

+

+#### zlib `object`

+

+Passed to node's [zlib](http://nodejs.org/api/zlib.html#zlib_options) module to control compression. Options may vary by node version.

+

+### Entry Data

+

+#### name `string` `required`

+

+Sets the entry name including internal path.

+

+#### date `string|Date`

+

+Sets the entry date. This can be any valid date string or instance. Defaults to current time in locale.

+

+#### store `boolean`

+

+If true, entry contents will be archived without compression.

+

+#### comment `string`

+

+Sets the entry comment.

+

+#### mode `number`

+

+Sets the entry permissions. Defaults to octal 0755 (directory) or 0644 (file).

+

+## Tar

+

+### Options

+

+#### gzip `boolean`

+

+Compresses the tar archive using gzip, default is false.

+

+#### gzipOptions `object`

+

+Passed to node's [zlib](http://nodejs.org/api/zlib.html#zlib_options) module to control compression. Options may vary by node version.

+

+### Entry Data

+

+#### name `string` `required`

+

+Sets the entry name including internal path.

+

+#### date `string|Date`

+

+Sets the entry date. This can be any valid date string or instance. Defaults to current time in locale.

+

+#### mode `number`

+

+Sets the entry permissions. Defaults to octal 0755 (directory) or 0644 (file).

+

+## Libraries

+

+Archiver makes use of several libraries/modules to avoid duplication of efforts.

+

+- [zip-stream](https://npmjs.org/package/zip-stream)

+- [tar-stream](https://npmjs.org/package/tar-stream)

+

+## Things of Interest

+

+- [Source Docs (coming soon)](https://docsrc.com/archiver/)

+- [Examples](https://github.com/ctalkington/node-archiver/blob/master/examples)

+- [Changelog](https://github.com/ctalkington/node-archiver/releases)

+- [Contributing](https://github.com/ctalkington/node-archiver/blob/master/CONTRIBUTING.md)

+- [MIT License](https://github.com/ctalkington/node-archiver/blob/master/LICENSE-MIT)
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/lib/archiver.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/lib/archiver.js
new file mode 100644
index 0000000..bc6b750
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/lib/archiver.js
@@ -0,0 +1,42 @@
+/**

+ * node-archiver

+ *

+ * Copyright (c) 2012-2014 Chris Talkington, contributors.

+ * Licensed under the MIT license.

+ * https://github.com/ctalkington/node-archiver/blob/master/LICENSE-MIT

+ */

+var ArchiverCore = require('./modules/core');

+var formatModules = {};

+

+var archiver = module.exports = function(format, options) {

+  return archiver.create(format, options);

+};

+

+archiver.create = function(format, options) {

+  if (formatModules[format]) {

+    var inst = new ArchiverCore(options);

+    inst.setModule(new formatModules[format](options));

+

+    return inst;

+  } else {

+    throw new Error('unknown format: ' + format);

+  }

+};

+

+archiver.registerFormat = function(format, module) {

+  if (module && typeof module === 'function' && typeof module.prototype.append === 'function') {

+    formatModules[format] = module;

+

+    // backwards compat

+    var compatName = 'create' + format.charAt(0).toUpperCase() + format.slice(1);

+    archiver[compatName] = function(options) {

+      return archiver.create(format, options);

+    };

+  } else {

+    throw new Error('format module invalid: ' + format);

+  }

+};

+

+archiver.registerFormat('zip', require('./modules/zip'));

+archiver.registerFormat('tar', require('./modules/tar'));

+archiver.registerFormat('json', require('./modules/json'));
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/lib/modules/core/index.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/lib/modules/core/index.js
new file mode 100644
index 0000000..a314ff6
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/lib/modules/core/index.js
@@ -0,0 +1,319 @@
+/**

+ * node-archiver

+ *

+ * Copyright (c) 2012-2014 Chris Talkington, contributors.

+ * Licensed under the MIT license.

+ * https://github.com/ctalkington/node-archiver/blob/master/LICENSE-MIT

+ */

+var fs = require('fs');

+var inherits = require('util').inherits;

+var Transform = require('readable-stream').Transform;

+

+var async = require('async');

+

+var util = require('../../util');

+

+var Archiver = module.exports = function(options) {

+  options = this.options = util.defaults(options, {

+    highWaterMark: 1024 * 1024

+  });

+

+  Transform.call(this, options);

+

+  this._entries = [];

+  this._module = false;

+  this._pointer = 0;

+

+  this._queue = async.queue(this._onQueueTask.bind(this), 1);

+  this._queue.drain = this._onQueueDrain.bind(this);

+

+  this._state = {

+    finalize: false,

+    finalized: false,

+    modulePiped: false

+  };

+};

+

+inherits(Archiver, Transform);

+

+Archiver.prototype._append = function(filepath, data) {

+  data = data || {};

+

+  if (!data.name) {

+    data.name = filepath;

+  }

+

+  data.sourcePath = filepath;

+

+  this._queue.push({

+    data: data,

+    source: null,

+    deferredStat: true,

+    filepath: filepath

+  });

+};

+

+Archiver.prototype._moduleAppend = function(source, data, callback) {

+  this._module.append(source, data, callback);

+};

+

+Archiver.prototype._moduleFinalize = function() {

+  this._state.finalized = true;

+

+  if (typeof this._module.finalize === 'function') {

+    this._module.finalize();

+  } else if (typeof this._module.end === 'function') {

+    this._module.end();

+  } else {

+    this.emit('error', new Error('format module missing finalize and end method'));

+  }

+};

+

+Archiver.prototype._moduleSupports = function(key) {

+  this._module.supports = util.defaults(this._module.supports, {

+    directory: false

+  });

+

+  return this._module.supports[key];

+};

+

+Archiver.prototype._normalizeEntryData = function(data, stats) {

+  stats = stats || false;

+  data = util.defaults(data, {

+    type: 'file',

+    name: null,

+    date: null,

+    mode: null,

+    sourcePath: null

+  });

+

+  var isDir = data.type === 'directory';

+

+  if (data.name) {

+    data.name = util.sanitizePath(data.name);

+

+    if (data.name.slice(-1) === '/') {

+      isDir = true;

+      data.type = 'directory';

+    } else if (isDir) {

+      data.name += '/';

+    }

+  }

+

+  if (typeof data.mode === 'number') {

+    data.mode &= 0777;

+  } else if (stats) {

+    data.mode = stats.mode & 0777;

+  } else {

+    data.mode = isDir ? 0755 : 0644;

+  }

+

+  if (stats && data.date === null) {

+    data.date = stats.mtime;

+  }

+

+  data.date = util.dateify(data.date);

+

+  data._stats = stats;

+

+  return data;

+};

+

+Archiver.prototype._onModuleError = function(err) {

+  this.emit('error', err);

+};

+

+Archiver.prototype._onQueueDrain = function() {

+  if (this._state.finalize && !this._state.finalized && this._queue.idle()) {

+    this._moduleFinalize();

+  }

+};

+

+Archiver.prototype._onQueueTask = function(task, callback) {

+  var afterAppend = function(err, entry) {

+    if (err) {

+      this.emit('error', err);

+      callback();

+      return;

+    }

+

+    entry = entry || task.data;

+

+    this.emit('entry', entry);

+    this._entries.push(entry);

+

+    callback();

+  }.bind(this);

+

+  var afterStat = function(err, stats) {

+    if (err) {

+      this.emit('error', err);

+      callback();

+      return;

+    }

+

+    task = this._updateQueueTaskWithStats(task, stats);

+

+    if (task.source !== null) {

+      this._moduleAppend(task.source, task.data, afterAppend);

+    } else {

+      this.emit('error', new Error('unsupported entry: ' + task.filepath));

+      callback();

+      return;

+    }

+  }.bind(this);

+

+  if (task.deferredStat) {

+    fs.stat(task.filepath, afterStat);

+  } else {

+    this._moduleAppend(task.source, task.data, afterAppend);

+  }

+};

+

+Archiver.prototype._updateQueueTaskWithStats = function(task, stats) {

+  if (stats.isFile()) {

+    task.data.type = 'file';

+    task.data.sourceType = 'stream';

+    task.source = util.lazyReadStream(task.filepath);

+  } else if (stats.isDirectory() && this._moduleSupports('directory')) {

+    task.data.name = util.trailingSlashIt(task.data.name);

+    task.data.type = 'directory';

+    task.data.sourcePath = util.trailingSlashIt(task.filepath);

+    task.data.sourceType = 'buffer';

+    task.source = new Buffer(0);

+  } else {

+    return task;

+  }

+

+  task.data = this._normalizeEntryData(task.data, stats);

+  return task;

+};

+

+Archiver.prototype._pipeModuleOutput = function() {

+  this._module.on('error', this._onModuleError.bind(this));

+  this._module.pipe(this);

+

+  this._state.modulePiped = true;

+};

+

+Archiver.prototype._processFile = function(source, data, callback) {

+  this.emit('error', new Error('method not implemented'));

+};

+

+Archiver.prototype._transform = function(chunk, encoding, callback) {

+  if (chunk) {

+    this._pointer += chunk.length;

+  }

+

+  callback(null, chunk);

+};

+

+Archiver.prototype.append = function(source, data) {

+  if (this._state.finalize) {

+    this.emit('error', new Error('unable to append after calling finalize.'));

+    return this;

+  }

+

+  data = this._normalizeEntryData(data);

+

+  if (typeof data.name !== 'string' || data.name.length === 0) {

+    this.emit('error', new Error('entry name must be a non-empty string value'));

+    return this;

+  }

+

+  if (data.type === 'directory' && !this._moduleSupports('directory')) {

+    this.emit('error', new Error('entries of "directory" type not currently supported by this module'));

+    return this;

+  }

+

+  source = util.normalizeInputSource(source);

+

+  if (Buffer.isBuffer(source)) {

+    data.sourceType = 'buffer';

+  } else if (util.isStream(source)) {

+    data.sourceType = 'stream';

+  } else {

+    this.emit('error', new Error('input source must be valid Stream or Buffer instance'));

+    return this;

+  }

+

+  this._queue.push({

+    data: data,

+    source: source

+  });

+

+  return this;

+};

+

+Archiver.prototype.bulk = function(mappings) {

+  if (this._state.finalize) {

+    this.emit('error', new Error('unable to append after calling finalize.'));

+    return this;

+  }

+

+  if (!Array.isArray(mappings)) {

+    mappings = [mappings];

+  }

+

+  var self = this;

+  var files = util.file.normalizeFilesArray(mappings);

+

+  files.forEach(function(file){

+    var isExpandedPair = file.orig.expand || false;

+    var fileData = file.data || {};

+

+    file.src.forEach(function(filepath) {

+      var data = util._.extend({}, fileData);

+      var name = isExpandedPair ? file.dest : util.unixifyPath(file.dest || '', filepath);

+

+      if (name === '.') {

+        return;

+      }

+

+      data.name = name;

+      self._append(filepath, data);

+    });

+  });

+

+  return this;

+};

+

+Archiver.prototype.file = function(filepath, data) {

+  if (this._state.finalize) {

+    this.emit('error', new Error('unable to append after calling finalize.'));

+    return this;

+  }

+

+  if (typeof filepath !== 'string' || filepath.length === 0) {

+    this.emit('error', new Error('filepath must be a non-empty string value'));

+    return this;

+  }

+

+  this._append(filepath, data);

+

+  return this;

+};

+

+Archiver.prototype.finalize = function() {

+  this._state.finalize = true;

+

+  if (this._queue.idle()) {

+    this._moduleFinalize();

+  }

+

+  return this;

+};

+

+Archiver.prototype.setModule = function(module) {

+  if (this._state.modulePiped) {

+    this.emit('error', new Error('format module already set'));

+    return;

+  }

+

+  this._module = module;

+  this._pipeModuleOutput();

+};

+

+Archiver.prototype.pointer = function() {

+  return this._pointer;

+};
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/lib/modules/json/index.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/lib/modules/json/index.js
new file mode 100644
index 0000000..001c449
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/lib/modules/json/index.js
@@ -0,0 +1,71 @@
+/**

+ * node-archiver

+ *

+ * Copyright (c) 2012-2014 Chris Talkington, contributors.

+ * Licensed under the MIT license.

+ * https://github.com/ctalkington/node-archiver/blob/master/LICENSE-MIT

+ */

+var inherits = require('util').inherits;

+var Transform = require('readable-stream').Transform;

+

+var crc32 = require('buffer-crc32');

+var util = require('../../util');

+

+var Json = module.exports = function(options) {

+  options = this.options = util.defaults(options, {});

+

+  Transform.call(this, options);

+

+  this.supports = {

+    directory: true

+  };

+

+  this.files = [];

+};

+

+inherits(Json, Transform);

+

+Json.prototype._transform = function(chunk, encoding, callback) {

+  callback(null, chunk);

+};

+

+Json.prototype._writeStringified = function() {

+  var fileString = JSON.stringify(this.files);

+  this.write(fileString);

+};

+

+Json.prototype.append = function(source, data, callback) {

+  var self = this;

+

+  data.crc32 = 0;

+

+  function onend(err, sourceBuffer) {

+    if (err) {

+      callback(err);

+      return;

+    }

+

+    data.size = sourceBuffer.length || 0;

+    data.crc32 = crc32.unsigned(sourceBuffer);

+

+    self.files.push(data);

+

+    callback(null, data);

+  }

+

+  if (data.sourceType === 'buffer') {

+    onend(null, source);

+  } else if (data.sourceType === 'stream') {

+    util.collectStream(source, onend);

+  }

+};

+

+Json.prototype.finalize = function(callback) {

+  callback = callback || function() {};

+

+  this._writeStringified();

+

+  this.end();

+

+  callback();

+};
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/lib/modules/tar/index.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/lib/modules/tar/index.js
new file mode 100644
index 0000000..5fba091
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/lib/modules/tar/index.js
@@ -0,0 +1,84 @@
+/**

+ * node-archiver

+ *

+ * Copyright (c) 2012-2014 Chris Talkington, contributors.

+ * Licensed under the MIT license.

+ * https://github.com/ctalkington/node-archiver/blob/master/LICENSE-MIT

+ */

+var zlib = require('zlib');

+

+var engine = require('tar-stream');

+var util = require('../../util');

+

+var Tar = module.exports = function(options) {

+  options = this.options = util.defaults(options, {

+    gzip: false

+  });

+

+  if (typeof options.gzipOptions !== 'object') {

+    options.gzipOptions = {};

+  }

+

+  this.supports = {

+    directory: true

+  };

+

+  this.engine = engine.pack(options);

+  this.compressor = false;

+

+  if (options.gzip) {

+    this.compressor = zlib.createGzip(options.gzipOptions);

+    this.compressor.on('error', this._onCompressorError.bind(this));

+  }

+};

+

+Tar.prototype._onCompressorError = function(err) {

+  this.engine.emit('error', err);

+};

+

+Tar.prototype.append = function(source, data, callback) {

+  var self = this;

+

+  data.mtime = data.date;

+

+  function append(err, sourceBuffer) {

+    if (err) {

+      callback(err);

+      return;

+    }

+

+    self.engine.entry(data, sourceBuffer, function(err) {

+      callback(err, data);

+    });

+  }

+

+  if (data.sourceType === 'buffer') {

+    append(null, source);

+  } else if (data.sourceType === 'stream' && data._stats) {

+    data.size = data._stats.size;

+

+    var entry = self.engine.entry(data, function(err) {

+      callback(err, data);

+    });

+

+    source.pipe(entry);

+  } else if (data.sourceType === 'stream') {

+    util.collectStream(source, append);

+  }

+};

+

+Tar.prototype.finalize = function() {

+  this.engine.finalize();

+};

+

+Tar.prototype.on = function() {

+  return this.engine.on.apply(this.engine, arguments);

+};

+

+Tar.prototype.pipe = function(destination, options) {

+  if (this.compressor) {

+    return this.engine.pipe.apply(this.engine, [this.compressor]).pipe(destination, options);

+  } else {

+    return this.engine.pipe.apply(this.engine, arguments);

+  }

+};
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/lib/modules/zip/index.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/lib/modules/zip/index.js
new file mode 100644
index 0000000..28c13d3
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/lib/modules/zip/index.js
@@ -0,0 +1,39 @@
+/**

+ * node-archiver

+ *

+ * Copyright (c) 2012-2014 Chris Talkington, contributors.

+ * Licensed under the MIT license.

+ * https://github.com/ctalkington/node-archiver/blob/master/LICENSE-MIT

+ */

+var engine = require('zip-stream');

+var util = require('../../util');

+

+var Zip = module.exports = function(options) {

+  options = this.options = util.defaults(options, {

+    comment: '',

+    forceUTC: false,

+    store: false

+  });

+

+  this.supports = {

+    directory: true

+  };

+

+  this.engine = new engine(options);

+};

+

+Zip.prototype.append = function(source, data, callback) {

+  this.engine.entry(source, data, callback);

+};

+

+Zip.prototype.finalize = function() {

+  this.engine.finalize();

+};

+

+Zip.prototype.on = function() {

+  return this.engine.on.apply(this.engine, arguments);

+};

+

+Zip.prototype.pipe = function() {

+  return this.engine.pipe.apply(this.engine, arguments);

+};
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/lib/util/file.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/lib/util/file.js
new file mode 100644
index 0000000..1ecaae3
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/lib/util/file.js
@@ -0,0 +1,206 @@
+/**

+ * node-archiver

+ *

+ * Copyright (c) 2012-2014 Chris Talkington, contributors.

+ * Licensed under the MIT license.

+ * https://github.com/ctalkington/node-archiver/blob/master/LICENSE-MIT

+ */

+var fs = require('fs');

+var path = require('path');

+

+var _ = require('lodash');

+var glob = require('glob');

+

+var file = module.exports = {};

+

+var pathSeparatorRe = /[\/\\]/g;

+

+// Process specified wildcard glob patterns or filenames against a

+// callback, excluding and uniquing files in the result set.

+var processPatterns = function(patterns, fn) {

+  // Filepaths to return.

+  var result = [];

+  // Iterate over flattened patterns array.

+  _.flatten(patterns).forEach(function(pattern) {

+    // If the first character is ! it should be omitted

+    var exclusion = pattern.indexOf('!') === 0;

+    // If the pattern is an exclusion, remove the !

+    if (exclusion) { pattern = pattern.slice(1); }

+    // Find all matching files for this pattern.

+    var matches = fn(pattern);

+    if (exclusion) {

+      // If an exclusion, remove matching files.

+      result = _.difference(result, matches);

+    } else {

+      // Otherwise add matching files.

+      result = _.union(result, matches);

+    }

+  });

+  return result;

+};

+

+// True if the file path exists.

+file.exists = function() {

+  var filepath = path.join.apply(path, arguments);

+  return fs.existsSync(filepath);

+};

+

+// Return an array of all file paths that match the given wildcard patterns.

+file.expand = function() {

+  var args = _.toArray(arguments);

+  // If the first argument is an options object, save those options to pass

+  // into the File.prototype.glob.sync method.

+  var options = _.isPlainObject(args[0]) ? args.shift() : {};

+  // Use the first argument if it's an Array, otherwise convert the arguments

+  // object to an array and use that.

+  var patterns = Array.isArray(args[0]) ? args[0] : args;

+  // Return empty set if there are no patterns or filepaths.

+  if (patterns.length === 0) { return []; }

+  // Return all matching filepaths.

+  var matches = processPatterns(patterns, function(pattern) {

+    // Find all matching files for this pattern.

+    return glob.sync(pattern, options);

+  });

+  // Filter result set?

+  if (options.filter) {

+    matches = matches.filter(function(filepath) {

+      filepath = path.join(options.cwd || '', filepath);

+      try {

+        if (typeof options.filter === 'function') {

+          return options.filter(filepath);

+        } else {

+          // If the file is of the right type and exists, this should work.

+          return fs.statSync(filepath)[options.filter]();

+        }

+      } catch(e) {

+        // Otherwise, it's probably not the right type.

+        return false;

+      }

+    });

+  }

+  return matches;

+};

+

+// Build a multi task "files" object dynamically.

+file.expandMapping = function(patterns, destBase, options) {

+  options = _.defaults({}, options, {

+    rename: function(destBase, destPath) {

+      return path.join(destBase || '', destPath);

+    }

+  });

+  var files = [];

+  var fileByDest = {};

+  // Find all files matching pattern, using passed-in options.

+  file.expand(options, patterns).forEach(function(src) {

+    var destPath = src;

+    // Flatten?

+    if (options.flatten) {

+      destPath = path.basename(destPath);

+    }

+    // Change the extension?

+    if (options.ext) {

+      destPath = destPath.replace(/(\.[^\/]*)?$/, options.ext);

+    }

+    // Generate destination filename.

+    var dest = options.rename(destBase, destPath, options);

+    // Prepend cwd to src path if necessary.

+    if (options.cwd) { src = path.join(options.cwd, src); }

+    // Normalize filepaths to be unix-style.

+    dest = dest.replace(pathSeparatorRe, '/');

+    src = src.replace(pathSeparatorRe, '/');

+    // Map correct src path to dest path.

+    if (fileByDest[dest]) {

+      // If dest already exists, push this src onto that dest's src array.

+      fileByDest[dest].src.push(src);

+    } else {

+      // Otherwise create a new src-dest file mapping object.

+      files.push({

+        src: [src],

+        dest: dest,

+      });

+      // And store a reference for later use.

+      fileByDest[dest] = files[files.length - 1];

+    }

+  });

+  return files;

+};

+

+// reusing bits of grunt's multi-task source normalization

+file.normalizeFilesArray = function(data) {

+  var files = [];

+

+  data.forEach(function(obj) {

+    var prop;

+    if ('src' in obj || 'dest' in obj) {

+      files.push(obj);

+    }

+  });

+

+  if (files.length === 0) {

+    return [];

+  }

+

+  files = _(files).chain().forEach(function(obj) {

+    if (!('src' in obj) || !obj.src) { return; }

+    // Normalize .src properties to flattened array.

+    if (Array.isArray(obj.src)) {

+      obj.src = _.flatten(obj.src);

+    } else {

+      obj.src = [obj.src];

+    }

+  }).map(function(obj) {

+    // Build options object, removing unwanted properties.

+    var expandOptions = _.extend({}, obj);

+    delete expandOptions.src;

+    delete expandOptions.dest;

+

+    // Expand file mappings.

+    if (obj.expand) {

+      return file.expandMapping(obj.src, obj.dest, expandOptions).map(function(mapObj) {

+        // Copy obj properties to result.

+        var result = _.extend({}, obj);

+        // Make a clone of the orig obj available.

+        result.orig = _.extend({}, obj);

+        // Set .src and .dest, processing both as templates.

+        result.src = mapObj.src;

+        result.dest = mapObj.dest;

+        // Remove unwanted properties.

+        ['expand', 'cwd', 'flatten', 'rename', 'ext'].forEach(function(prop) {

+          delete result[prop];

+        });

+        return result;

+      });

+    }

+

+    // Copy obj properties to result, adding an .orig property.

+    var result = _.extend({}, obj);

+    // Make a clone of the orig obj available.

+    result.orig = _.extend({}, obj);

+

+    if ('src' in result) {

+      // Expose an expand-on-demand getter method as .src.

+      Object.defineProperty(result, 'src', {

+        enumerable: true,

+        get: function fn() {

+          var src;

+          if (!('result' in fn)) {

+            src = obj.src;

+            // If src is an array, flatten it. Otherwise, make it into an array.

+            src = Array.isArray(src) ? _.flatten(src) : [src];

+            // Expand src files, memoizing result.

+            fn.result = file.expand(expandOptions, src);

+          }

+          return fn.result;

+        }

+      });

+    }

+

+    if ('dest' in result) {

+      result.dest = obj.dest;

+    }

+

+    return result;

+  }).flatten().value();

+

+  return files;

+};
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/lib/util/index.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/lib/util/index.js
new file mode 100644
index 0000000..0646151
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/lib/util/index.js
@@ -0,0 +1,103 @@
+/**

+ * node-archiver

+ *

+ * Copyright (c) 2012-2014 Chris Talkington, contributors.

+ * Licensed under the MIT license.

+ * https://github.com/ctalkington/node-archiver/blob/master/LICENSE-MIT

+ */

+var fs = require('fs');

+var path = require('path');

+

+var Stream = require('stream').Stream;

+var PassThrough = require('readable-stream').PassThrough;

+

+var util = module.exports = {};

+

+util._ = require('lodash');

+util.lazystream = require('lazystream');

+util.file = require('./file');

+

+util.collectStream = function(source, callback) {

+  var collection = [];

+  var size = 0;

+

+  source.on('error', callback);

+

+  source.on('data', function(chunk) {

+    collection.push(chunk);

+    size += chunk.length;

+  });

+

+  source.on('end', function() {

+    var buf = new Buffer(size, 'utf8');

+    var offset = 0;

+

+    collection.forEach(function(data) {

+      data.copy(buf, offset);

+      offset += data.length;

+    });

+

+    callback(null, buf);

+  });

+};

+

+util.dateify = function(dateish) {

+  dateish = dateish || new Date();

+

+  if (dateish instanceof Date) {

+    dateish = dateish;

+  } else if (typeof dateish === 'string') {

+    dateish = new Date(dateish);

+  } else {

+    dateish = new Date();

+  }

+

+  return dateish;

+};

+

+// this is slightly different from lodash version

+util.defaults = function(object, source, guard) {

+  var args = arguments;

+  args[0] = args[0] || {};

+

+  return util._.defaults.apply(util._, args);

+};

+

+util.isStream = function(source) {

+  return source instanceof Stream;

+};

+

+util.lazyReadStream = function(filepath) {

+  return new util.lazystream.Readable(function() {

+    return fs.createReadStream(filepath);

+  });

+};

+

+util.normalizeInputSource = function(source) {

+  if (source === null) {

+    return new Buffer(0);

+  } else if (typeof source === 'string') {

+    return new Buffer(source);

+  } else if (util.isStream(source) && !source._readableState) {

+    var normalized = new PassThrough();

+    source.pipe(normalized);

+

+    return normalized;

+  }

+

+  return source;

+};

+

+util.sanitizePath = function() {

+  var filepath = path.join.apply(path, arguments);

+  return filepath.replace(/\\/g, '/').replace(/:/g, '').replace(/^\/+/, '');

+};

+

+util.trailingSlashIt = function(str) {

+  return str.slice(-1) !== '/' ? str + '/' : str;

+};

+

+util.unixifyPath = function() {

+  var filepath = path.join.apply(path, arguments);

+  return filepath.replace(/\\/g, '/');

+};
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/async/.travis.yml b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/async/.travis.yml
new file mode 100644
index 0000000..6e5919d
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/async/.travis.yml
@@ -0,0 +1,3 @@
+language: node_js
+node_js:
+  - "0.10"
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/async/LICENSE b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/async/LICENSE
new file mode 100644
index 0000000..8f29698
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/async/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2010-2014 Caolan McMahon
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/async/README.md b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/async/README.md
new file mode 100644
index 0000000..0bea531
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/async/README.md
@@ -0,0 +1,1646 @@
+# Async.js
+
+[![Build Status via Travis CI](https://travis-ci.org/caolan/async.svg?branch=master)](https://travis-ci.org/caolan/async)
+
+
+Async is a utility module which provides straight-forward, powerful functions
+for working with asynchronous JavaScript. Although originally designed for
+use with [Node.js](http://nodejs.org), it can also be used directly in the
+browser. Also supports [component](https://github.com/component/component).
+
+Async provides around 20 functions that include the usual 'functional'
+suspects (`map`, `reduce`, `filter`, `each`…) as well as some common patterns
+for asynchronous control flow (`parallel`, `series`, `waterfall`…). All these
+functions assume you follow the Node.js convention of providing a single
+callback as the last argument of your `async` function.
+
+
+## Quick Examples
+
+```javascript
+async.map(['file1','file2','file3'], fs.stat, function(err, results){
+    // results is now an array of stats for each file
+});
+
+async.filter(['file1','file2','file3'], fs.exists, function(results){
+    // results now equals an array of the existing files
+});
+
+async.parallel([
+    function(){ ... },
+    function(){ ... }
+], callback);
+
+async.series([
+    function(){ ... },
+    function(){ ... }
+]);
+```
+
+There are many more functions available so take a look at the docs below for a
+full list. This module aims to be comprehensive, so if you feel anything is
+missing please create a GitHub issue for it.
+
+## Common Pitfalls
+
+### Binding a context to an iterator
+
+This section is really about `bind`, not about `async`. If you are wondering how to
+make `async` execute your iterators in a given context, or are confused as to why
+a method of another library isn't working as an iterator, study this example:
+
+```js
+// Here is a simple object with an (unnecessarily roundabout) squaring method
+var AsyncSquaringLibrary = {
+  squareExponent: 2,
+  square: function(number, callback){ 
+    var result = Math.pow(number, this.squareExponent);
+    setTimeout(function(){
+      callback(null, result);
+    }, 200);
+  }
+};
+
+async.map([1, 2, 3], AsyncSquaringLibrary.square, function(err, result){
+  // result is [NaN, NaN, NaN]
+  // This fails because the `this.squareExponent` expression in the square
+  // function is not evaluated in the context of AsyncSquaringLibrary, and is
+  // therefore undefined.
+});
+
+async.map([1, 2, 3], AsyncSquaringLibrary.square.bind(AsyncSquaringLibrary), function(err, result){
+  // result is [1, 4, 9]
+  // With the help of bind we can attach a context to the iterator before
+  // passing it to async. Now the square function will be executed in its 
+  // 'home' AsyncSquaringLibrary context and the value of `this.squareExponent`
+  // will be as expected.
+});
+```
+
+## Download
+
+The source is available for download from
+[GitHub](http://github.com/caolan/async).
+Alternatively, you can install using Node Package Manager (`npm`):
+
+    npm install async
+
+__Development:__ [async.js](https://github.com/caolan/async/raw/master/lib/async.js) - 29.6kb Uncompressed
+
+## In the Browser
+
+So far it's been tested in IE6, IE7, IE8, FF3.6 and Chrome 5. 
+
+Usage:
+
+```html
+<script type="text/javascript" src="async.js"></script>
+<script type="text/javascript">
+
+    async.map(data, asyncProcess, function(err, results){
+        alert(results);
+    });
+
+</script>
+```
+
+## Documentation
+
+### Collections
+
+* [`each`](#each)
+* [`eachSeries`](#eachSeries)
+* [`eachLimit`](#eachLimit)
+* [`map`](#map)
+* [`mapSeries`](#mapSeries)
+* [`mapLimit`](#mapLimit)
+* [`filter`](#filter)
+* [`filterSeries`](#filterSeries)
+* [`reject`](#reject)
+* [`rejectSeries`](#rejectSeries)
+* [`reduce`](#reduce)
+* [`reduceRight`](#reduceRight)
+* [`detect`](#detect)
+* [`detectSeries`](#detectSeries)
+* [`sortBy`](#sortBy)
+* [`some`](#some)
+* [`every`](#every)
+* [`concat`](#concat)
+* [`concatSeries`](#concatSeries)
+
+### Control Flow
+
+* [`series`](#seriestasks-callback)
+* [`parallel`](#parallel)
+* [`parallelLimit`](#parallellimittasks-limit-callback)
+* [`whilst`](#whilst)
+* [`doWhilst`](#doWhilst)
+* [`until`](#until)
+* [`doUntil`](#doUntil)
+* [`forever`](#forever)
+* [`waterfall`](#waterfall)
+* [`compose`](#compose)
+* [`seq`](#seq)
+* [`applyEach`](#applyEach)
+* [`applyEachSeries`](#applyEachSeries)
+* [`queue`](#queue)
+* [`priorityQueue`](#priorityQueue)
+* [`cargo`](#cargo)
+* [`auto`](#auto)
+* [`retry`](#retry)
+* [`iterator`](#iterator)
+* [`apply`](#apply)
+* [`nextTick`](#nextTick)
+* [`times`](#times)
+* [`timesSeries`](#timesSeries)
+
+### Utils
+
+* [`memoize`](#memoize)
+* [`unmemoize`](#unmemoize)
+* [`log`](#log)
+* [`dir`](#dir)
+* [`noConflict`](#noConflict)
+
+
+## Collections
+
+<a name="forEach" />
+<a name="each" />
+### each(arr, iterator, callback)
+
+Applies the function `iterator` to each item in `arr`, in parallel.
+The `iterator` is called with an item from the list, and a callback for when it
+has finished. If the `iterator` passes an error to its `callback`, the main
+`callback` (for the `each` function) is immediately called with the error.
+
+Note, that since this function applies `iterator` to each item in parallel,
+there is no guarantee that the iterator functions will complete in order.
+
+__Arguments__
+
+* `arr` - An array to iterate over.
+* `iterator(item, callback)` - A function to apply to each item in `arr`.
+  The iterator is passed a `callback(err)` which must be called once it has 
+  completed. If no error has occured, the `callback` should be run without 
+  arguments or with an explicit `null` argument.
+* `callback(err)` - A callback which is called when all `iterator` functions
+  have finished, or an error occurs.
+
+__Examples__
+
+
+```js
+// assuming openFiles is an array of file names and saveFile is a function
+// to save the modified contents of that file:
+
+async.each(openFiles, saveFile, function(err){
+    // if any of the saves produced an error, err would equal that error
+});
+```
+
+```js
+// assuming openFiles is an array of file names 
+
+async.each(openFiles, function( file, callback) {
+  
+  // Perform operation on file here.
+  console.log('Processing file ' + file);
+  
+  if( file.length > 32 ) {
+    console.log('This file name is too long');
+    callback('File name too long');
+  } else {
+    // Do work to process file here
+    console.log('File processed');
+    callback();
+  }
+}, function(err){
+    // if any of the file processing produced an error, err would equal that error
+    if( err ) {
+      // One of the iterations produced an error.
+      // All processing will now stop.
+      console.log('A file failed to process');
+    } else {
+      console.log('All files have been processed successfully');
+    }
+});
+```
+
+---------------------------------------
+
+<a name="forEachSeries" />
+<a name="eachSeries" />
+### eachSeries(arr, iterator, callback)
+
+The same as [`each`](#each), only `iterator` is applied to each item in `arr` in
+series. The next `iterator` is only called once the current one has completed. 
+This means the `iterator` functions will complete in order.
+
+
+---------------------------------------
+
+<a name="forEachLimit" />
+<a name="eachLimit" />
+### eachLimit(arr, limit, iterator, callback)
+
+The same as [`each`](#each), only no more than `limit` `iterator`s will be simultaneously 
+running at any time.
+
+Note that the items in `arr` are not processed in batches, so there is no guarantee that 
+the first `limit` `iterator` functions will complete before any others are started.
+
+__Arguments__
+
+* `arr` - An array to iterate over.
+* `limit` - The maximum number of `iterator`s to run at any time.
+* `iterator(item, callback)` - A function to apply to each item in `arr`.
+  The iterator is passed a `callback(err)` which must be called once it has 
+  completed. If no error has occured, the callback should be run without 
+  arguments or with an explicit `null` argument.
+* `callback(err)` - A callback which is called when all `iterator` functions
+  have finished, or an error occurs.
+
+__Example__
+
+```js
+// Assume documents is an array of JSON objects and requestApi is a
+// function that interacts with a rate-limited REST api.
+
+async.eachLimit(documents, 20, requestApi, function(err){
+    // if any of the saves produced an error, err would equal that error
+});
+```
+
+---------------------------------------
+
+<a name="map" />
+### map(arr, iterator, callback)
+
+Produces a new array of values by mapping each value in `arr` through
+the `iterator` function. The `iterator` is called with an item from `arr` and a
+callback for when it has finished processing. Each of these callback takes 2 arguments: 
+an `error`, and the transformed item from `arr`. If `iterator` passes an error to this 
+callback, the main `callback` (for the `map` function) is immediately called with the error.
+
+Note, that since this function applies the `iterator` to each item in parallel,
+there is no guarantee that the `iterator` functions will complete in order. 
+However, the results array will be in the same order as the original `arr`.
+
+__Arguments__
+
+* `arr` - An array to iterate over.
+* `iterator(item, callback)` - A function to apply to each item in `arr`.
+  The iterator is passed a `callback(err, transformed)` which must be called once 
+  it has completed with an error (which can be `null`) and a transformed item.
+* `callback(err, results)` - A callback which is called when all `iterator`
+  functions have finished, or an error occurs. Results is an array of the
+  transformed items from the `arr`.
+
+__Example__
+
+```js
+async.map(['file1','file2','file3'], fs.stat, function(err, results){
+    // results is now an array of stats for each file
+});
+```
+
+---------------------------------------
+
+<a name="mapSeries" />
+### mapSeries(arr, iterator, callback)
+
+The same as [`map`](#map), only the `iterator` is applied to each item in `arr` in
+series. The next `iterator` is only called once the current one has completed. 
+The results array will be in the same order as the original.
+
+
+---------------------------------------
+
+<a name="mapLimit" />
+### mapLimit(arr, limit, iterator, callback)
+
+The same as [`map`](#map), only no more than `limit` `iterator`s will be simultaneously 
+running at any time.
+
+Note that the items are not processed in batches, so there is no guarantee that 
+the first `limit` `iterator` functions will complete before any others are started.
+
+__Arguments__
+
+* `arr` - An array to iterate over.
+* `limit` - The maximum number of `iterator`s to run at any time.
+* `iterator(item, callback)` - A function to apply to each item in `arr`.
+  The iterator is passed a `callback(err, transformed)` which must be called once 
+  it has completed with an error (which can be `null`) and a transformed item.
+* `callback(err, results)` - A callback which is called when all `iterator`
+  calls have finished, or an error occurs. The result is an array of the
+  transformed items from the original `arr`.
+
+__Example__
+
+```js
+async.mapLimit(['file1','file2','file3'], 1, fs.stat, function(err, results){
+    // results is now an array of stats for each file
+});
+```
+
+---------------------------------------
+
+<a name="select" />
+<a name="filter" />
+### filter(arr, iterator, callback)
+
+__Alias:__ `select`
+
+Returns a new array of all the values in `arr` which pass an async truth test.
+_The callback for each `iterator` call only accepts a single argument of `true` or
+`false`; it does not accept an error argument first!_ This is in-line with the
+way node libraries work with truth tests like `fs.exists`. This operation is
+performed in parallel, but the results array will be in the same order as the
+original.
+
+__Arguments__
+
+* `arr` - An array to iterate over.
+* `iterator(item, callback)` - A truth test to apply to each item in `arr`.
+  The `iterator` is passed a `callback(truthValue)`, which must be called with a 
+  boolean argument once it has completed.
+* `callback(results)` - A callback which is called after all the `iterator`
+  functions have finished.
+
+__Example__
+
+```js
+async.filter(['file1','file2','file3'], fs.exists, function(results){
+    // results now equals an array of the existing files
+});
+```
+
+---------------------------------------
+
+<a name="selectSeries" />
+<a name="filterSeries" />
+### filterSeries(arr, iterator, callback)
+
+__Alias:__ `selectSeries`
+
+The same as [`filter`](#filter) only the `iterator` is applied to each item in `arr` in
+series. The next `iterator` is only called once the current one has completed. 
+The results array will be in the same order as the original.
+
+---------------------------------------
+
+<a name="reject" />
+### reject(arr, iterator, callback)
+
+The opposite of [`filter`](#filter). Removes values that pass an `async` truth test.
+
+---------------------------------------
+
+<a name="rejectSeries" />
+### rejectSeries(arr, iterator, callback)
+
+The same as [`reject`](#reject), only the `iterator` is applied to each item in `arr`
+in series.
+
+
+---------------------------------------
+
+<a name="reduce" />
+### reduce(arr, memo, iterator, callback)
+
+__Aliases:__ `inject`, `foldl`
+
+Reduces `arr` into a single value using an async `iterator` to return
+each successive step. `memo` is the initial state of the reduction. 
+This function only operates in series. 
+
+For performance reasons, it may make sense to split a call to this function into 
+a parallel map, and then use the normal `Array.prototype.reduce` on the results. 
+This function is for situations where each step in the reduction needs to be async; 
+if you can get the data before reducing it, then it's probably a good idea to do so.
+
+__Arguments__
+
+* `arr` - An array to iterate over.
+* `memo` - The initial state of the reduction.
+* `iterator(memo, item, callback)` - A function applied to each item in the
+  array to produce the next step in the reduction. The `iterator` is passed a
+  `callback(err, reduction)` which accepts an optional error as its first 
+  argument, and the state of the reduction as the second. If an error is 
+  passed to the callback, the reduction is stopped and the main `callback` is 
+  immediately called with the error.
+* `callback(err, result)` - A callback which is called after all the `iterator`
+  functions have finished. Result is the reduced value.
+
+__Example__
+
+```js
+async.reduce([1,2,3], 0, function(memo, item, callback){
+    // pointless async:
+    process.nextTick(function(){
+        callback(null, memo + item)
+    });
+}, function(err, result){
+    // result is now equal to the last value of memo, which is 6
+});
+```
+
+---------------------------------------
+
+<a name="reduceRight" />
+### reduceRight(arr, memo, iterator, callback)
+
+__Alias:__ `foldr`
+
+Same as [`reduce`](#reduce), only operates on `arr` in reverse order.
+
+
+---------------------------------------
+
+<a name="detect" />
+### detect(arr, iterator, callback)
+
+Returns the first value in `arr` that passes an async truth test. The
+`iterator` is applied in parallel, meaning the first iterator to return `true` will
+fire the detect `callback` with that result. That means the result might not be
+the first item in the original `arr` (in terms of order) that passes the test.
+
+If order within the original `arr` is important, then look at [`detectSeries`](#detectSeries).
+
+__Arguments__
+
+* `arr` - An array to iterate over.
+* `iterator(item, callback)` - A truth test to apply to each item in `arr`.
+  The iterator is passed a `callback(truthValue)` which must be called with a 
+  boolean argument once it has completed.
+* `callback(result)` - A callback which is called as soon as any iterator returns
+  `true`, or after all the `iterator` functions have finished. Result will be
+  the first item in the array that passes the truth test (iterator) or the
+  value `undefined` if none passed.
+
+__Example__
+
+```js
+async.detect(['file1','file2','file3'], fs.exists, function(result){
+    // result now equals the first file in the list that exists
+});
+```
+
+---------------------------------------
+
+<a name="detectSeries" />
+### detectSeries(arr, iterator, callback)
+
+The same as [`detect`](#detect), only the `iterator` is applied to each item in `arr`
+in series. This means the result is always the first in the original `arr` (in
+terms of array order) that passes the truth test.
+
+
+---------------------------------------
+
+<a name="sortBy" />
+### sortBy(arr, iterator, callback)
+
+Sorts a list by the results of running each `arr` value through an async `iterator`.
+
+__Arguments__
+
+* `arr` - An array to iterate over.
+* `iterator(item, callback)` - A function to apply to each item in `arr`.
+  The iterator is passed a `callback(err, sortValue)` which must be called once it
+  has completed with an error (which can be `null`) and a value to use as the sort
+  criteria.
+* `callback(err, results)` - A callback which is called after all the `iterator`
+  functions have finished, or an error occurs. Results is the items from
+  the original `arr` sorted by the values returned by the `iterator` calls.
+
+__Example__
+
+```js
+async.sortBy(['file1','file2','file3'], function(file, callback){
+    fs.stat(file, function(err, stats){
+        callback(err, stats.mtime);
+    });
+}, function(err, results){
+    // results is now the original array of files sorted by
+    // modified date
+});
+```
+
+__Sort Order__
+
+By modifying the callback parameter the sorting order can be influenced:
+
+```js
+//ascending order
+async.sortBy([1,9,3,5], function(x, callback){
+    callback(err, x);
+}, function(err,result){
+    //result callback
+} );
+
+//descending order
+async.sortBy([1,9,3,5], function(x, callback){
+    callback(err, x*-1);    //<- x*-1 instead of x, turns the order around
+}, function(err,result){
+    //result callback
+} );
+```
+
+---------------------------------------
+
+<a name="some" />
+### some(arr, iterator, callback)
+
+__Alias:__ `any`
+
+Returns `true` if at least one element in the `arr` satisfies an async test.
+_The callback for each iterator call only accepts a single argument of `true` or
+`false`; it does not accept an error argument first!_ This is in-line with the
+way node libraries work with truth tests like `fs.exists`. Once any iterator
+call returns `true`, the main `callback` is immediately called.
+
+__Arguments__
+
+* `arr` - An array to iterate over.
+* `iterator(item, callback)` - A truth test to apply to each item in the array
+  in parallel. The iterator is passed a callback(truthValue) which must be 
+  called with a boolean argument once it has completed.
+* `callback(result)` - A callback which is called as soon as any iterator returns
+  `true`, or after all the iterator functions have finished. Result will be
+  either `true` or `false` depending on the values of the async tests.
+
+__Example__
+
+```js
+async.some(['file1','file2','file3'], fs.exists, function(result){
+    // if result is true then at least one of the files exists
+});
+```
+
+---------------------------------------
+
+<a name="every" />
+### every(arr, iterator, callback)
+
+__Alias:__ `all`
+
+Returns `true` if every element in `arr` satisfies an async test.
+_The callback for each `iterator` call only accepts a single argument of `true` or
+`false`; it does not accept an error argument first!_ This is in-line with the
+way node libraries work with truth tests like `fs.exists`.
+
+__Arguments__
+
+* `arr` - An array to iterate over.
+* `iterator(item, callback)` - A truth test to apply to each item in the array
+  in parallel. The iterator is passed a callback(truthValue) which must be 
+  called with a  boolean argument once it has completed.
+* `callback(result)` - A callback which is called after all the `iterator`
+  functions have finished. Result will be either `true` or `false` depending on
+  the values of the async tests.
+
+__Example__
+
+```js
+async.every(['file1','file2','file3'], fs.exists, function(result){
+    // if result is true then every file exists
+});
+```
+
+---------------------------------------
+
+<a name="concat" />
+### concat(arr, iterator, callback)
+
+Applies `iterator` to each item in `arr`, concatenating the results. Returns the
+concatenated list. The `iterator`s are called in parallel, and the results are
+concatenated as they return. There is no guarantee that the results array will
+be returned in the original order of `arr` passed to the `iterator` function.
+
+__Arguments__
+
+* `arr` - An array to iterate over.
+* `iterator(item, callback)` - A function to apply to each item in `arr`.
+  The iterator is passed a `callback(err, results)` which must be called once it 
+  has completed with an error (which can be `null`) and an array of results.
+* `callback(err, results)` - A callback which is called after all the `iterator`
+  functions have finished, or an error occurs. Results is an array containing
+  the concatenated results of the `iterator` function.
+
+__Example__
+
+```js
+async.concat(['dir1','dir2','dir3'], fs.readdir, function(err, files){
+    // files is now a list of filenames that exist in the 3 directories
+});
+```
+
+---------------------------------------
+
+<a name="concatSeries" />
+### concatSeries(arr, iterator, callback)
+
+Same as [`concat`](#concat), but executes in series instead of parallel.
+
+
+## Control Flow
+
+<a name="series" />
+### series(tasks, [callback])
+
+Run the functions in the `tasks` array in series, each one running once the previous
+function has completed. If any functions in the series pass an error to its
+callback, no more functions are run, and `callback` is immediately called with the value of the error. 
+Otherwise, `callback` receives an array of results when `tasks` have completed.
+
+It is also possible to use an object instead of an array. Each property will be
+run as a function, and the results will be passed to the final `callback` as an object
+instead of an array. This can be a more readable way of handling results from
+[`series`](#series).
+
+**Note** that while many implementations preserve the order of object properties, the
+[ECMAScript Language Specifcation](http://www.ecma-international.org/ecma-262/5.1/#sec-8.6) 
+explicitly states that
+
+> The mechanics and order of enumerating the properties is not specified.
+
+So if you rely on the order in which your series of functions are executed, and want
+this to work on all platforms, consider using an array. 
+
+__Arguments__
+
+* `tasks` - An array or object containing functions to run, each function is passed
+  a `callback(err, result)` it must call on completion with an error `err` (which can
+  be `null`) and an optional `result` value.
+* `callback(err, results)` - An optional callback to run once all the functions
+  have completed. This function gets a results array (or object) containing all 
+  the result arguments passed to the `task` callbacks.
+
+__Example__
+
+```js
+async.series([
+    function(callback){
+        // do some stuff ...
+        callback(null, 'one');
+    },
+    function(callback){
+        // do some more stuff ...
+        callback(null, 'two');
+    }
+],
+// optional callback
+function(err, results){
+    // results is now equal to ['one', 'two']
+});
+
+
+// an example using an object instead of an array
+async.series({
+    one: function(callback){
+        setTimeout(function(){
+            callback(null, 1);
+        }, 200);
+    },
+    two: function(callback){
+        setTimeout(function(){
+            callback(null, 2);
+        }, 100);
+    }
+},
+function(err, results) {
+    // results is now equal to: {one: 1, two: 2}
+});
+```
+
+---------------------------------------
+
+<a name="parallel" />
+### parallel(tasks, [callback])
+
+Run the `tasks` array of functions in parallel, without waiting until the previous
+function has completed. If any of the functions pass an error to its
+callback, the main `callback` is immediately called with the value of the error.
+Once the `tasks` have completed, the results are passed to the final `callback` as an
+array.
+
+It is also possible to use an object instead of an array. Each property will be
+run as a function and the results will be passed to the final `callback` as an object
+instead of an array. This can be a more readable way of handling results from
+[`parallel`](#parallel).
+
+
+__Arguments__
+
+* `tasks` - An array or object containing functions to run. Each function is passed 
+  a `callback(err, result)` which it must call on completion with an error `err` 
+  (which can be `null`) and an optional `result` value.
+* `callback(err, results)` - An optional callback to run once all the functions
+  have completed. This function gets a results array (or object) containing all 
+  the result arguments passed to the task callbacks.
+
+__Example__
+
+```js
+async.parallel([
+    function(callback){
+        setTimeout(function(){
+            callback(null, 'one');
+        }, 200);
+    },
+    function(callback){
+        setTimeout(function(){
+            callback(null, 'two');
+        }, 100);
+    }
+],
+// optional callback
+function(err, results){
+    // the results array will equal ['one','two'] even though
+    // the second function had a shorter timeout.
+});
+
+
+// an example using an object instead of an array
+async.parallel({
+    one: function(callback){
+        setTimeout(function(){
+            callback(null, 1);
+        }, 200);
+    },
+    two: function(callback){
+        setTimeout(function(){
+            callback(null, 2);
+        }, 100);
+    }
+},
+function(err, results) {
+    // results is now equals to: {one: 1, two: 2}
+});
+```
+
+---------------------------------------
+
+<a name="parallelLimit" />
+### parallelLimit(tasks, limit, [callback])
+
+The same as [`parallel`](#parallel), only `tasks` are executed in parallel 
+with a maximum of `limit` tasks executing at any time.
+
+Note that the `tasks` are not executed in batches, so there is no guarantee that 
+the first `limit` tasks will complete before any others are started.
+
+__Arguments__
+
+* `tasks` - An array or object containing functions to run, each function is passed 
+  a `callback(err, result)` it must call on completion with an error `err` (which can
+  be `null`) and an optional `result` value.
+* `limit` - The maximum number of `tasks` to run at any time.
+* `callback(err, results)` - An optional callback to run once all the functions
+  have completed. This function gets a results array (or object) containing all 
+  the result arguments passed to the `task` callbacks.
+
+---------------------------------------
+
+<a name="whilst" />
+### whilst(test, fn, callback)
+
+Repeatedly call `fn`, while `test` returns `true`. Calls `callback` when stopped,
+or an error occurs.
+
+__Arguments__
+
+* `test()` - synchronous truth test to perform before each execution of `fn`.
+* `fn(callback)` - A function which is called each time `test` passes. The function is
+  passed a `callback(err)`, which must be called once it has completed with an 
+  optional `err` argument.
+* `callback(err)` - A callback which is called after the test fails and repeated
+  execution of `fn` has stopped.
+
+__Example__
+
+```js
+var count = 0;
+
+async.whilst(
+    function () { return count < 5; },
+    function (callback) {
+        count++;
+        setTimeout(callback, 1000);
+    },
+    function (err) {
+        // 5 seconds have passed
+    }
+);
+```
+
+---------------------------------------
+
+<a name="doWhilst" />
+### doWhilst(fn, test, callback)
+
+The post-check version of [`whilst`](#whilst). To reflect the difference in 
+the order of operations, the arguments `test` and `fn` are switched. 
+
+`doWhilst` is to `whilst` as `do while` is to `while` in plain JavaScript.
+
+---------------------------------------
+
+<a name="until" />
+### until(test, fn, callback)
+
+Repeatedly call `fn` until `test` returns `true`. Calls `callback` when stopped,
+or an error occurs.
+
+The inverse of [`whilst`](#whilst).
+
+---------------------------------------
+
+<a name="doUntil" />
+### doUntil(fn, test, callback)
+
+Like [`doWhilst`](#doWhilst), except the `test` is inverted. Note the argument ordering differs from `until`.
+
+---------------------------------------
+
+<a name="forever" />
+### forever(fn, errback)
+
+Calls the asynchronous function `fn` with a callback parameter that allows it to
+call itself again, in series, indefinitely.
+
+If an error is passed to the callback then `errback` is called with the
+error, and execution stops, otherwise it will never be called.
+
+```js
+async.forever(
+    function(next) {
+        // next is suitable for passing to things that need a callback(err [, whatever]);
+        // it will result in this function being called again.
+    },
+    function(err) {
+        // if next is called with a value in its first parameter, it will appear
+        // in here as 'err', and execution will stop.
+    }
+);
+```
+
+---------------------------------------
+
+<a name="waterfall" />
+### waterfall(tasks, [callback])
+
+Runs the `tasks` array of functions in series, each passing their results to the next in
+the array. However, if any of the `tasks` pass an error to their own callback, the
+next function is not executed, and the main `callback` is immediately called with
+the error.
+
+__Arguments__
+
+* `tasks` - An array of functions to run, each function is passed a 
+  `callback(err, result1, result2, ...)` it must call on completion. The first
+  argument is an error (which can be `null`) and any further arguments will be 
+  passed as arguments in order to the next task.
+* `callback(err, [results])` - An optional callback to run once all the functions
+  have completed. This will be passed the results of the last task's callback.
+
+
+
+__Example__
+
+```js
+async.waterfall([
+    function(callback){
+        callback(null, 'one', 'two');
+    },
+    function(arg1, arg2, callback){
+      // arg1 now equals 'one' and arg2 now equals 'two'
+        callback(null, 'three');
+    },
+    function(arg1, callback){
+        // arg1 now equals 'three'
+        callback(null, 'done');
+    }
+], function (err, result) {
+   // result now equals 'done'    
+});
+```
+
+---------------------------------------
+<a name="compose" />
+### compose(fn1, fn2...)
+
+Creates a function which is a composition of the passed asynchronous
+functions. Each function consumes the return value of the function that
+follows. Composing functions `f()`, `g()`, and `h()` would produce the result of
+`f(g(h()))`, only this version uses callbacks to obtain the return values.
+
+Each function is executed with the `this` binding of the composed function.
+
+__Arguments__
+
+* `functions...` - the asynchronous functions to compose
+
+
+__Example__
+
+```js
+function add1(n, callback) {
+    setTimeout(function () {
+        callback(null, n + 1);
+    }, 10);
+}
+
+function mul3(n, callback) {
+    setTimeout(function () {
+        callback(null, n * 3);
+    }, 10);
+}
+
+var add1mul3 = async.compose(mul3, add1);
+
+add1mul3(4, function (err, result) {
+   // result now equals 15
+});
+```
+
+---------------------------------------
+<a name="seq" />
+### seq(fn1, fn2...)
+
+Version of the compose function that is more natural to read.
+Each following function consumes the return value of the latter function. 
+
+Each function is executed with the `this` binding of the composed function.
+
+__Arguments__
+
+* functions... - the asynchronous functions to compose
+
+
+__Example__
+
+```js
+// Requires lodash (or underscore), express3 and dresende's orm2.
+// Part of an app, that fetches cats of the logged user.
+// This example uses `seq` function to avoid overnesting and error 
+// handling clutter.
+app.get('/cats', function(request, response) {
+  function handleError(err, data, callback) {
+    if (err) {
+      console.error(err);
+      response.json({ status: 'error', message: err.message });
+    }
+    else {
+      callback(data);
+    }
+  }
+  var User = request.models.User;
+  async.seq(
+    _.bind(User.get, User),  // 'User.get' has signature (id, callback(err, data))
+    handleError,
+    function(user, fn) {
+      user.getCats(fn);      // 'getCats' has signature (callback(err, data))
+    },
+    handleError,
+    function(cats) {
+      response.json({ status: 'ok', message: 'Cats found', data: cats });
+    }
+  )(req.session.user_id);
+  }
+});
+```
+
+---------------------------------------
+<a name="applyEach" />
+### applyEach(fns, args..., callback)
+
+Applies the provided arguments to each function in the array, calling 
+`callback` after all functions have completed. If you only provide the first
+argument, then it will return a function which lets you pass in the
+arguments as if it were a single function call.
+
+__Arguments__
+
+* `fns` - the asynchronous functions to all call with the same arguments
+* `args...` - any number of separate arguments to pass to the function
+* `callback` - the final argument should be the callback, called when all
+  functions have completed processing
+
+
+__Example__
+
+```js
+async.applyEach([enableSearch, updateSchema], 'bucket', callback);
+
+// partial application example:
+async.each(
+    buckets,
+    async.applyEach([enableSearch, updateSchema]),
+    callback
+);
+```
+
+---------------------------------------
+
+<a name="applyEachSeries" />
+### applyEachSeries(arr, iterator, callback)
+
+The same as [`applyEach`](#applyEach) only the functions are applied in series.
+
+---------------------------------------
+
+<a name="queue" />
+### queue(worker, concurrency)
+
+Creates a `queue` object with the specified `concurrency`. Tasks added to the
+`queue` are processed in parallel (up to the `concurrency` limit). If all
+`worker`s are in progress, the task is queued until one becomes available. 
+Once a `worker` completes a `task`, that `task`'s callback is called.
+
+__Arguments__
+
+* `worker(task, callback)` - An asynchronous function for processing a queued
+  task, which must call its `callback(err)` argument when finished, with an 
+  optional `error` as an argument.
+* `concurrency` - An `integer` for determining how many `worker` functions should be
+  run in parallel.
+
+__Queue objects__
+
+The `queue` object returned by this function has the following properties and
+methods:
+
+* `length()` - a function returning the number of items waiting to be processed.
+* `started` - a function returning whether or not any items have been pushed and processed by the queue
+* `running()` - a function returning the number of items currently being processed.
+* `idle()` - a function returning false if there are items waiting or being processed, or true if not.
+* `concurrency` - an integer for determining how many `worker` functions should be
+  run in parallel. This property can be changed after a `queue` is created to
+  alter the concurrency on-the-fly.
+* `push(task, [callback])` - add a new task to the `queue`. Calls `callback` once 
+  the `worker` has finished processing the task. Instead of a single task, a `tasks` array
+  can be submitted. The respective callback is used for every task in the list.
+* `unshift(task, [callback])` - add a new task to the front of the `queue`.
+* `saturated` - a callback that is called when the `queue` length hits the `concurrency` limit, 
+   and further tasks will be queued.
+* `empty` - a callback that is called when the last item from the `queue` is given to a `worker`.
+* `drain` - a callback that is called when the last item from the `queue` has returned from the `worker`.
+* `paused` - a boolean for determining whether the queue is in a paused state
+* `pause()` - a function that pauses the processing of tasks until `resume()` is called.
+* `resume()` - a function that resumes the processing of queued tasks when the queue is paused.
+* `kill()` - a function that empties remaining tasks from the queue forcing it to go idle.
+
+__Example__
+
+```js
+// create a queue object with concurrency 2
+
+var q = async.queue(function (task, callback) {
+    console.log('hello ' + task.name);
+    callback();
+}, 2);
+
+
+// assign a callback
+q.drain = function() {
+    console.log('all items have been processed');
+}
+
+// add some items to the queue
+
+q.push({name: 'foo'}, function (err) {
+    console.log('finished processing foo');
+});
+q.push({name: 'bar'}, function (err) {
+    console.log('finished processing bar');
+});
+
+// add some items to the queue (batch-wise)
+
+q.push([{name: 'baz'},{name: 'bay'},{name: 'bax'}], function (err) {
+    console.log('finished processing bar');
+});
+
+// add some items to the front of the queue
+
+q.unshift({name: 'bar'}, function (err) {
+    console.log('finished processing bar');
+});
+```
+
+
+---------------------------------------
+
+<a name="priorityQueue" />
+### priorityQueue(worker, concurrency)
+
+The same as [`queue`](#queue) only tasks are assigned a priority and completed in ascending priority order. There are two differences between `queue` and `priorityQueue` objects:
+
+* `push(task, priority, [callback])` - `priority` should be a number. If an array of
+  `tasks` is given, all tasks will be assigned the same priority.
+* The `unshift` method was removed.
+
+---------------------------------------
+
+<a name="cargo" />
+### cargo(worker, [payload])
+
+Creates a `cargo` object with the specified payload. Tasks added to the
+cargo will be processed altogether (up to the `payload` limit). If the
+`worker` is in progress, the task is queued until it becomes available. Once
+the `worker` has completed some tasks, each callback of those tasks is called.
+Check out [this animation](https://camo.githubusercontent.com/6bbd36f4cf5b35a0f11a96dcd2e97711ffc2fb37/68747470733a2f2f662e636c6f75642e6769746875622e636f6d2f6173736574732f313637363837312f36383130382f62626330636662302d356632392d313165322d393734662d3333393763363464633835382e676966) for how `cargo` and `queue` work.
+
+While [queue](#queue) passes only one task to one of a group of workers
+at a time, cargo passes an array of tasks to a single worker, repeating
+when the worker is finished.
+
+__Arguments__
+
+* `worker(tasks, callback)` - An asynchronous function for processing an array of
+  queued tasks, which must call its `callback(err)` argument when finished, with 
+  an optional `err` argument.
+* `payload` - An optional `integer` for determining how many tasks should be
+  processed per round; if omitted, the default is unlimited.
+
+__Cargo objects__
+
+The `cargo` object returned by this function has the following properties and
+methods:
+
+* `length()` - A function returning the number of items waiting to be processed.
+* `payload` - An `integer` for determining how many tasks should be
+  process per round. This property can be changed after a `cargo` is created to
+  alter the payload on-the-fly.
+* `push(task, [callback])` - Adds `task` to the `queue`. The callback is called
+  once the `worker` has finished processing the task. Instead of a single task, an array of `tasks` 
+  can be submitted. The respective callback is used for every task in the list.
+* `saturated` - A callback that is called when the `queue.length()` hits the concurrency and further tasks will be queued.
+* `empty` - A callback that is called when the last item from the `queue` is given to a `worker`.
+* `drain` - A callback that is called when the last item from the `queue` has returned from the `worker`.
+
+__Example__
+
+```js
+// create a cargo object with payload 2
+
+var cargo = async.cargo(function (tasks, callback) {
+    for(var i=0; i<tasks.length; i++){
+      console.log('hello ' + tasks[i].name);
+    }
+    callback();
+}, 2);
+
+
+// add some items
+
+cargo.push({name: 'foo'}, function (err) {
+    console.log('finished processing foo');
+});
+cargo.push({name: 'bar'}, function (err) {
+    console.log('finished processing bar');
+});
+cargo.push({name: 'baz'}, function (err) {
+    console.log('finished processing baz');
+});
+```
+
+---------------------------------------
+
+<a name="auto" />
+### auto(tasks, [callback])
+
+Determines the best order for running the functions in `tasks`, based on their 
+requirements. Each function can optionally depend on other functions being completed 
+first, and each function is run as soon as its requirements are satisfied. 
+
+If any of the functions pass an error to their callback, it will not 
+complete (so any other functions depending on it will not run), and the main 
+`callback` is immediately called with the error. Functions also receive an 
+object containing the results of functions which have completed so far.
+
+Note, all functions are called with a `results` object as a second argument, 
+so it is unsafe to pass functions in the `tasks` object which cannot handle the
+extra argument. 
+
+For example, this snippet of code:
+
+```js
+async.auto({
+  readData: async.apply(fs.readFile, 'data.txt', 'utf-8')
+}, callback);
+```
+
+will have the effect of calling `readFile` with the results object as the last
+argument, which will fail:
+
+```js
+fs.readFile('data.txt', 'utf-8', cb, {});
+```
+
+Instead, wrap the call to `readFile` in a function which does not forward the 
+`results` object:
+
+```js
+async.auto({
+  readData: function(cb, results){
+    fs.readFile('data.txt', 'utf-8', cb);
+  }
+}, callback);
+```
+
+__Arguments__
+
+* `tasks` - An object. Each of its properties is either a function or an array of
+  requirements, with the function itself the last item in the array. The object's key
+  of a property serves as the name of the task defined by that property,
+  i.e. can be used when specifying requirements for other tasks.
+  The function receives two arguments: (1) a `callback(err, result)` which must be 
+  called when finished, passing an `error` (which can be `null`) and the result of 
+  the function's execution, and (2) a `results` object, containing the results of
+  the previously executed functions.
+* `callback(err, results)` - An optional callback which is called when all the
+  tasks have been completed. It receives the `err` argument if any `tasks` 
+  pass an error to their callback. Results are always returned; however, if 
+  an error occurs, no further `tasks` will be performed, and the results
+  object will only contain partial results.
+
+
+__Example__
+
+```js
+async.auto({
+    get_data: function(callback){
+        console.log('in get_data');
+        // async code to get some data
+        callback(null, 'data', 'converted to array');
+    },
+    make_folder: function(callback){
+        console.log('in make_folder');
+        // async code to create a directory to store a file in
+        // this is run at the same time as getting the data
+        callback(null, 'folder');
+    },
+    write_file: ['get_data', 'make_folder', function(callback, results){
+        console.log('in write_file', JSON.stringify(results));
+        // once there is some data and the directory exists,
+        // write the data to a file in the directory
+        callback(null, 'filename');
+    }],
+    email_link: ['write_file', function(callback, results){
+        console.log('in email_link', JSON.stringify(results));
+        // once the file is written let's email a link to it...
+        // results.write_file contains the filename returned by write_file.
+        callback(null, {'file':results.write_file, 'email':'user@example.com'});
+    }]
+}, function(err, results) {
+    console.log('err = ', err);
+    console.log('results = ', results);
+});
+```
+
+This is a fairly trivial example, but to do this using the basic parallel and
+series functions would look like this:
+
+```js
+async.parallel([
+    function(callback){
+        console.log('in get_data');
+        // async code to get some data
+        callback(null, 'data', 'converted to array');
+    },
+    function(callback){
+        console.log('in make_folder');
+        // async code to create a directory to store a file in
+        // this is run at the same time as getting the data
+        callback(null, 'folder');
+    }
+],
+function(err, results){
+    async.series([
+        function(callback){
+            console.log('in write_file', JSON.stringify(results));
+            // once there is some data and the directory exists,
+            // write the data to a file in the directory
+            results.push('filename');
+            callback(null);
+        },
+        function(callback){
+            console.log('in email_link', JSON.stringify(results));
+            // once the file is written let's email a link to it...
+            callback(null, {'file':results.pop(), 'email':'user@example.com'});
+        }
+    ]);
+});
+```
+
+For a complicated series of `async` tasks, using the [`auto`](#auto) function makes adding
+new tasks much easier (and the code more readable).
+
+
+---------------------------------------
+
+<a name="retry" />
+### retry([times = 5], task, [callback])
+
+Attempts to get a successful response from `task` no more than `times` times before
+returning an error. If the task is successful, the `callback` will be passed the result
+of the successfull task. If all attemps fail, the callback will be passed the error and
+result (if any) of the final attempt.
+
+__Arguments__
+
+* `times` - An integer indicating how many times to attempt the `task` before giving up. Defaults to 5.
+* `task(callback, results)` - A function which receives two arguments: (1) a `callback(err, result)`
+  which must be called when finished, passing `err` (which can be `null`) and the `result` of 
+  the function's execution, and (2) a `results` object, containing the results of
+  the previously executed functions (if nested inside another control flow).
+* `callback(err, results)` - An optional callback which is called when the
+  task has succeeded, or after the final failed attempt. It receives the `err` and `result` arguments of the last attempt at completing the `task`.
+
+The [`retry`](#retry) function can be used as a stand-alone control flow by passing a
+callback, as shown below:
+
+```js
+async.retry(3, apiMethod, function(err, result) {
+    // do something with the result
+});
+```
+
+It can also be embeded within other control flow functions to retry individual methods
+that are not as reliable, like this:
+
+```js
+async.auto({
+    users: api.getUsers.bind(api),
+    payments: async.retry(3, api.getPayments.bind(api))
+}, function(err, results) {
+  // do something with the results
+});
+```
+
+
+---------------------------------------
+
+<a name="iterator" />
+### iterator(tasks)
+
+Creates an iterator function which calls the next function in the `tasks` array,
+returning a continuation to call the next one after that. It's also possible to
+“peek” at the next iterator with `iterator.next()`.
+
+This function is used internally by the `async` module, but can be useful when
+you want to manually control the flow of functions in series.
+
+__Arguments__
+
+* `tasks` - An array of functions to run.
+
+__Example__
+
+```js
+var iterator = async.iterator([
+    function(){ sys.p('one'); },
+    function(){ sys.p('two'); },
+    function(){ sys.p('three'); }
+]);
+
+node> var iterator2 = iterator();
+'one'
+node> var iterator3 = iterator2();
+'two'
+node> iterator3();
+'three'
+node> var nextfn = iterator2.next();
+node> nextfn();
+'three'
+```
+
+---------------------------------------
+
+<a name="apply" />
+### apply(function, arguments..)
+
+Creates a continuation function with some arguments already applied. 
+
+Useful as a shorthand when combined with other control flow functions. Any arguments
+passed to the returned function are added to the arguments originally passed
+to apply.
+
+__Arguments__
+
+* `function` - The function you want to eventually apply all arguments to.
+* `arguments...` - Any number of arguments to automatically apply when the
+  continuation is called.
+
+__Example__
+
+```js
+// using apply
+
+async.parallel([
+    async.apply(fs.writeFile, 'testfile1', 'test1'),
+    async.apply(fs.writeFile, 'testfile2', 'test2'),
+]);
+
+
+// the same process without using apply
+
+async.parallel([
+    function(callback){
+        fs.writeFile('testfile1', 'test1', callback);
+    },
+    function(callback){
+        fs.writeFile('testfile2', 'test2', callback);
+    }
+]);
+```
+
+It's possible to pass any number of additional arguments when calling the
+continuation:
+
+```js
+node> var fn = async.apply(sys.puts, 'one');
+node> fn('two', 'three');
+one
+two
+three
+```
+
+---------------------------------------
+
+<a name="nextTick" />
+### nextTick(callback)
+
+Calls `callback` on a later loop around the event loop. In Node.js this just
+calls `process.nextTick`; in the browser it falls back to `setImmediate(callback)`
+if available, otherwise `setTimeout(callback, 0)`, which means other higher priority
+events may precede the execution of `callback`.
+
+This is used internally for browser-compatibility purposes.
+
+__Arguments__
+
+* `callback` - The function to call on a later loop around the event loop.
+
+__Example__
+
+```js
+var call_order = [];
+async.nextTick(function(){
+    call_order.push('two');
+    // call_order now equals ['one','two']
+});
+call_order.push('one')
+```
+
+<a name="times" />
+### times(n, callback)
+
+Calls the `callback` function `n` times, and accumulates results in the same manner
+you would use with [`map`](#map).
+
+__Arguments__
+
+* `n` - The number of times to run the function.
+* `callback` - The function to call `n` times.
+
+__Example__
+
+```js
+// Pretend this is some complicated async factory
+var createUser = function(id, callback) {
+  callback(null, {
+    id: 'user' + id
+  })
+}
+// generate 5 users
+async.times(5, function(n, next){
+    createUser(n, function(err, user) {
+      next(err, user)
+    })
+}, function(err, users) {
+  // we should now have 5 users
+});
+```
+
+<a name="timesSeries" />
+### timesSeries(n, callback)
+
+The same as [`times`](#times), only the iterator is applied to each item in `arr` in
+series. The next `iterator` is only called once the current one has completed. 
+The results array will be in the same order as the original.
+
+
+## Utils
+
+<a name="memoize" />
+### memoize(fn, [hasher])
+
+Caches the results of an `async` function. When creating a hash to store function
+results against, the callback is omitted from the hash and an optional hash
+function can be used.
+
+The cache of results is exposed as the `memo` property of the function returned
+by `memoize`.
+
+__Arguments__
+
+* `fn` - The function to proxy and cache results from.
+* `hasher` - Tn optional function for generating a custom hash for storing
+  results. It has all the arguments applied to it apart from the callback, and
+  must be synchronous.
+
+__Example__
+
+```js
+var slow_fn = function (name, callback) {
+    // do something
+    callback(null, result);
+};
+var fn = async.memoize(slow_fn);
+
+// fn can now be used as if it were slow_fn
+fn('some name', function () {
+    // callback
+});
+```
+
+<a name="unmemoize" />
+### unmemoize(fn)
+
+Undoes a [`memoize`](#memoize)d function, reverting it to the original, unmemoized
+form. Handy for testing.
+
+__Arguments__
+
+* `fn` - the memoized function
+
+<a name="log" />
+### log(function, arguments)
+
+Logs the result of an `async` function to the `console`. Only works in Node.js or
+in browsers that support `console.log` and `console.error` (such as FF and Chrome).
+If multiple arguments are returned from the async function, `console.log` is
+called on each argument in order.
+
+__Arguments__
+
+* `function` - The function you want to eventually apply all arguments to.
+* `arguments...` - Any number of arguments to apply to the function.
+
+__Example__
+
+```js
+var hello = function(name, callback){
+    setTimeout(function(){
+        callback(null, 'hello ' + name);
+    }, 1000);
+};
+```
+```js
+node> async.log(hello, 'world');
+'hello world'
+```
+
+---------------------------------------
+
+<a name="dir" />
+### dir(function, arguments)
+
+Logs the result of an `async` function to the `console` using `console.dir` to
+display the properties of the resulting object. Only works in Node.js or
+in browsers that support `console.dir` and `console.error` (such as FF and Chrome).
+If multiple arguments are returned from the async function, `console.dir` is
+called on each argument in order.
+
+__Arguments__
+
+* `function` - The function you want to eventually apply all arguments to.
+* `arguments...` - Any number of arguments to apply to the function.
+
+__Example__
+
+```js
+var hello = function(name, callback){
+    setTimeout(function(){
+        callback(null, {hello: name});
+    }, 1000);
+};
+```
+```js
+node> async.dir(hello, 'world');
+{hello: 'world'}
+```
+
+---------------------------------------
+
+<a name="noConflict" />
+### noConflict()
+
+Changes the value of `async` back to its original value, returning a reference to the
+`async` object.
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/async/component.json b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/async/component.json
new file mode 100644
index 0000000..bbb0115
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/async/component.json
@@ -0,0 +1,11 @@
+{
+  "name": "async",
+  "repo": "caolan/async",
+  "description": "Higher-order functions and common patterns for asynchronous code",
+  "version": "0.1.23",
+  "keywords": [],
+  "dependencies": {},
+  "development": {},
+  "main": "lib/async.js",
+  "scripts": [ "lib/async.js" ]
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/async/lib/async.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/async/lib/async.js
new file mode 100755
index 0000000..01e8afc
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/async/lib/async.js
@@ -0,0 +1,1123 @@
+/*!
+ * async
+ * https://github.com/caolan/async
+ *
+ * Copyright 2010-2014 Caolan McMahon
+ * Released under the MIT license
+ */
+/*jshint onevar: false, indent:4 */
+/*global setImmediate: false, setTimeout: false, console: false */
+(function () {
+
+    var async = {};
+
+    // global on the server, window in the browser
+    var root, previous_async;
+
+    root = this;
+    if (root != null) {
+      previous_async = root.async;
+    }
+
+    async.noConflict = function () {
+        root.async = previous_async;
+        return async;
+    };
+
+    function only_once(fn) {
+        var called = false;
+        return function() {
+            if (called) throw new Error("Callback was already called.");
+            called = true;
+            fn.apply(root, arguments);
+        }
+    }
+
+    //// cross-browser compatiblity functions ////
+
+    var _toString = Object.prototype.toString;
+
+    var _isArray = Array.isArray || function (obj) {
+        return _toString.call(obj) === '[object Array]';
+    };
+
+    var _each = function (arr, iterator) {
+        if (arr.forEach) {
+            return arr.forEach(iterator);
+        }
+        for (var i = 0; i < arr.length; i += 1) {
+            iterator(arr[i], i, arr);
+        }
+    };
+
+    var _map = function (arr, iterator) {
+        if (arr.map) {
+            return arr.map(iterator);
+        }
+        var results = [];
+        _each(arr, function (x, i, a) {
+            results.push(iterator(x, i, a));
+        });
+        return results;
+    };
+
+    var _reduce = function (arr, iterator, memo) {
+        if (arr.reduce) {
+            return arr.reduce(iterator, memo);
+        }
+        _each(arr, function (x, i, a) {
+            memo = iterator(memo, x, i, a);
+        });
+        return memo;
+    };
+
+    var _keys = function (obj) {
+        if (Object.keys) {
+            return Object.keys(obj);
+        }
+        var keys = [];
+        for (var k in obj) {
+            if (obj.hasOwnProperty(k)) {
+                keys.push(k);
+            }
+        }
+        return keys;
+    };
+
+    //// exported async module functions ////
+
+    //// nextTick implementation with browser-compatible fallback ////
+    if (typeof process === 'undefined' || !(process.nextTick)) {
+        if (typeof setImmediate === 'function') {
+            async.nextTick = function (fn) {
+                // not a direct alias for IE10 compatibility
+                setImmediate(fn);
+            };
+            async.setImmediate = async.nextTick;
+        }
+        else {
+            async.nextTick = function (fn) {
+                setTimeout(fn, 0);
+            };
+            async.setImmediate = async.nextTick;
+        }
+    }
+    else {
+        async.nextTick = process.nextTick;
+        if (typeof setImmediate !== 'undefined') {
+            async.setImmediate = function (fn) {
+              // not a direct alias for IE10 compatibility
+              setImmediate(fn);
+            };
+        }
+        else {
+            async.setImmediate = async.nextTick;
+        }
+    }
+
+    async.each = function (arr, iterator, callback) {
+        callback = callback || function () {};
+        if (!arr.length) {
+            return callback();
+        }
+        var completed = 0;
+        _each(arr, function (x) {
+            iterator(x, only_once(done) );
+        });
+        function done(err) {
+          if (err) {
+              callback(err);
+              callback = function () {};
+          }
+          else {
+              completed += 1;
+              if (completed >= arr.length) {
+                  callback();
+              }
+          }
+        }
+    };
+    async.forEach = async.each;
+
+    async.eachSeries = function (arr, iterator, callback) {
+        callback = callback || function () {};
+        if (!arr.length) {
+            return callback();
+        }
+        var completed = 0;
+        var iterate = function () {
+            iterator(arr[completed], function (err) {
+                if (err) {
+                    callback(err);
+                    callback = function () {};
+                }
+                else {
+                    completed += 1;
+                    if (completed >= arr.length) {
+                        callback();
+                    }
+                    else {
+                        iterate();
+                    }
+                }
+            });
+        };
+        iterate();
+    };
+    async.forEachSeries = async.eachSeries;
+
+    async.eachLimit = function (arr, limit, iterator, callback) {
+        var fn = _eachLimit(limit);
+        fn.apply(null, [arr, iterator, callback]);
+    };
+    async.forEachLimit = async.eachLimit;
+
+    var _eachLimit = function (limit) {
+
+        return function (arr, iterator, callback) {
+            callback = callback || function () {};
+            if (!arr.length || limit <= 0) {
+                return callback();
+            }
+            var completed = 0;
+            var started = 0;
+            var running = 0;
+
+            (function replenish () {
+                if (completed >= arr.length) {
+                    return callback();
+                }
+
+                while (running < limit && started < arr.length) {
+                    started += 1;
+                    running += 1;
+                    iterator(arr[started - 1], function (err) {
+                        if (err) {
+                            callback(err);
+                            callback = function () {};
+                        }
+                        else {
+                            completed += 1;
+                            running -= 1;
+                            if (completed >= arr.length) {
+                                callback();
+                            }
+                            else {
+                                replenish();
+                            }
+                        }
+                    });
+                }
+            })();
+        };
+    };
+
+
+    var doParallel = function (fn) {
+        return function () {
+            var args = Array.prototype.slice.call(arguments);
+            return fn.apply(null, [async.each].concat(args));
+        };
+    };
+    var doParallelLimit = function(limit, fn) {
+        return function () {
+            var args = Array.prototype.slice.call(arguments);
+            return fn.apply(null, [_eachLimit(limit)].concat(args));
+        };
+    };
+    var doSeries = function (fn) {
+        return function () {
+            var args = Array.prototype.slice.call(arguments);
+            return fn.apply(null, [async.eachSeries].concat(args));
+        };
+    };
+
+
+    var _asyncMap = function (eachfn, arr, iterator, callback) {
+        arr = _map(arr, function (x, i) {
+            return {index: i, value: x};
+        });
+        if (!callback) {
+            eachfn(arr, function (x, callback) {
+                iterator(x.value, function (err) {
+                    callback(err);
+                });
+            });
+        } else {
+            var results = [];
+            eachfn(arr, function (x, callback) {
+                iterator(x.value, function (err, v) {
+                    results[x.index] = v;
+                    callback(err);
+                });
+            }, function (err) {
+                callback(err, results);
+            });
+        }
+    };
+    async.map = doParallel(_asyncMap);
+    async.mapSeries = doSeries(_asyncMap);
+    async.mapLimit = function (arr, limit, iterator, callback) {
+        return _mapLimit(limit)(arr, iterator, callback);
+    };
+
+    var _mapLimit = function(limit) {
+        return doParallelLimit(limit, _asyncMap);
+    };
+
+    // reduce only has a series version, as doing reduce in parallel won't
+    // work in many situations.
+    async.reduce = function (arr, memo, iterator, callback) {
+        async.eachSeries(arr, function (x, callback) {
+            iterator(memo, x, function (err, v) {
+                memo = v;
+                callback(err);
+            });
+        }, function (err) {
+            callback(err, memo);
+        });
+    };
+    // inject alias
+    async.inject = async.reduce;
+    // foldl alias
+    async.foldl = async.reduce;
+
+    async.reduceRight = function (arr, memo, iterator, callback) {
+        var reversed = _map(arr, function (x) {
+            return x;
+        }).reverse();
+        async.reduce(reversed, memo, iterator, callback);
+    };
+    // foldr alias
+    async.foldr = async.reduceRight;
+
+    var _filter = function (eachfn, arr, iterator, callback) {
+        var results = [];
+        arr = _map(arr, function (x, i) {
+            return {index: i, value: x};
+        });
+        eachfn(arr, function (x, callback) {
+            iterator(x.value, function (v) {
+                if (v) {
+                    results.push(x);
+                }
+                callback();
+            });
+        }, function (err) {
+            callback(_map(results.sort(function (a, b) {
+                return a.index - b.index;
+            }), function (x) {
+                return x.value;
+            }));
+        });
+    };
+    async.filter = doParallel(_filter);
+    async.filterSeries = doSeries(_filter);
+    // select alias
+    async.select = async.filter;
+    async.selectSeries = async.filterSeries;
+
+    var _reject = function (eachfn, arr, iterator, callback) {
+        var results = [];
+        arr = _map(arr, function (x, i) {
+            return {index: i, value: x};
+        });
+        eachfn(arr, function (x, callback) {
+            iterator(x.value, function (v) {
+                if (!v) {
+                    results.push(x);
+                }
+                callback();
+            });
+        }, function (err) {
+            callback(_map(results.sort(function (a, b) {
+                return a.index - b.index;
+            }), function (x) {
+                return x.value;
+            }));
+        });
+    };
+    async.reject = doParallel(_reject);
+    async.rejectSeries = doSeries(_reject);
+
+    var _detect = function (eachfn, arr, iterator, main_callback) {
+        eachfn(arr, function (x, callback) {
+            iterator(x, function (result) {
+                if (result) {
+                    main_callback(x);
+                    main_callback = function () {};
+                }
+                else {
+                    callback();
+                }
+            });
+        }, function (err) {
+            main_callback();
+        });
+    };
+    async.detect = doParallel(_detect);
+    async.detectSeries = doSeries(_detect);
+
+    async.some = function (arr, iterator, main_callback) {
+        async.each(arr, function (x, callback) {
+            iterator(x, function (v) {
+                if (v) {
+                    main_callback(true);
+                    main_callback = function () {};
+                }
+                callback();
+            });
+        }, function (err) {
+            main_callback(false);
+        });
+    };
+    // any alias
+    async.any = async.some;
+
+    async.every = function (arr, iterator, main_callback) {
+        async.each(arr, function (x, callback) {
+            iterator(x, function (v) {
+                if (!v) {
+                    main_callback(false);
+                    main_callback = function () {};
+                }
+                callback();
+            });
+        }, function (err) {
+            main_callback(true);
+        });
+    };
+    // all alias
+    async.all = async.every;
+
+    async.sortBy = function (arr, iterator, callback) {
+        async.map(arr, function (x, callback) {
+            iterator(x, function (err, criteria) {
+                if (err) {
+                    callback(err);
+                }
+                else {
+                    callback(null, {value: x, criteria: criteria});
+                }
+            });
+        }, function (err, results) {
+            if (err) {
+                return callback(err);
+            }
+            else {
+                var fn = function (left, right) {
+                    var a = left.criteria, b = right.criteria;
+                    return a < b ? -1 : a > b ? 1 : 0;
+                };
+                callback(null, _map(results.sort(fn), function (x) {
+                    return x.value;
+                }));
+            }
+        });
+    };
+
+    async.auto = function (tasks, callback) {
+        callback = callback || function () {};
+        var keys = _keys(tasks);
+        var remainingTasks = keys.length
+        if (!remainingTasks) {
+            return callback();
+        }
+
+        var results = {};
+
+        var listeners = [];
+        var addListener = function (fn) {
+            listeners.unshift(fn);
+        };
+        var removeListener = function (fn) {
+            for (var i = 0; i < listeners.length; i += 1) {
+                if (listeners[i] === fn) {
+                    listeners.splice(i, 1);
+                    return;
+                }
+            }
+        };
+        var taskComplete = function () {
+            remainingTasks--
+            _each(listeners.slice(0), function (fn) {
+                fn();
+            });
+        };
+
+        addListener(function () {
+            if (!remainingTasks) {
+                var theCallback = callback;
+                // prevent final callback from calling itself if it errors
+                callback = function () {};
+
+                theCallback(null, results);
+            }
+        });
+
+        _each(keys, function (k) {
+            var task = _isArray(tasks[k]) ? tasks[k]: [tasks[k]];
+            var taskCallback = function (err) {
+                var args = Array.prototype.slice.call(arguments, 1);
+                if (args.length <= 1) {
+                    args = args[0];
+                }
+                if (err) {
+                    var safeResults = {};
+                    _each(_keys(results), function(rkey) {
+                        safeResults[rkey] = results[rkey];
+                    });
+                    safeResults[k] = args;
+                    callback(err, safeResults);
+                    // stop subsequent errors hitting callback multiple times
+                    callback = function () {};
+                }
+                else {
+                    results[k] = args;
+                    async.setImmediate(taskComplete);
+                }
+            };
+            var requires = task.slice(0, Math.abs(task.length - 1)) || [];
+            var ready = function () {
+                return _reduce(requires, function (a, x) {
+                    return (a && results.hasOwnProperty(x));
+                }, true) && !results.hasOwnProperty(k);
+            };
+            if (ready()) {
+                task[task.length - 1](taskCallback, results);
+            }
+            else {
+                var listener = function () {
+                    if (ready()) {
+                        removeListener(listener);
+                        task[task.length - 1](taskCallback, results);
+                    }
+                };
+                addListener(listener);
+            }
+        });
+    };
+
+    async.retry = function(times, task, callback) {
+        var DEFAULT_TIMES = 5;
+        var attempts = [];
+        // Use defaults if times not passed
+        if (typeof times === 'function') {
+            callback = task;
+            task = times;
+            times = DEFAULT_TIMES;
+        }
+        // Make sure times is a number
+        times = parseInt(times, 10) || DEFAULT_TIMES;
+        var wrappedTask = function(wrappedCallback, wrappedResults) {
+            var retryAttempt = function(task, finalAttempt) {
+                return function(seriesCallback) {
+                    task(function(err, result){
+                        seriesCallback(!err || finalAttempt, {err: err, result: result});
+                    }, wrappedResults);
+                };
+            };
+            while (times) {
+                attempts.push(retryAttempt(task, !(times-=1)));
+            }
+            async.series(attempts, function(done, data){
+                data = data[data.length - 1];
+                (wrappedCallback || callback)(data.err, data.result);
+            });
+        }
+        // If a callback is passed, run this as a controll flow
+        return callback ? wrappedTask() : wrappedTask
+    };
+
+    async.waterfall = function (tasks, callback) {
+        callback = callback || function () {};
+        if (!_isArray(tasks)) {
+          var err = new Error('First argument to waterfall must be an array of functions');
+          return callback(err);
+        }
+        if (!tasks.length) {
+            return callback();
+        }
+        var wrapIterator = function (iterator) {
+            return function (err) {
+                if (err) {
+                    callback.apply(null, arguments);
+                    callback = function () {};
+                }
+                else {
+                    var args = Array.prototype.slice.call(arguments, 1);
+                    var next = iterator.next();
+                    if (next) {
+                        args.push(wrapIterator(next));
+                    }
+                    else {
+                        args.push(callback);
+                    }
+                    async.setImmediate(function () {
+                        iterator.apply(null, args);
+                    });
+                }
+            };
+        };
+        wrapIterator(async.iterator(tasks))();
+    };
+
+    var _parallel = function(eachfn, tasks, callback) {
+        callback = callback || function () {};
+        if (_isArray(tasks)) {
+            eachfn.map(tasks, function (fn, callback) {
+                if (fn) {
+                    fn(function (err) {
+                        var args = Array.prototype.slice.call(arguments, 1);
+                        if (args.length <= 1) {
+                            args = args[0];
+                        }
+                        callback.call(null, err, args);
+                    });
+                }
+            }, callback);
+        }
+        else {
+            var results = {};
+            eachfn.each(_keys(tasks), function (k, callback) {
+                tasks[k](function (err) {
+                    var args = Array.prototype.slice.call(arguments, 1);
+                    if (args.length <= 1) {
+                        args = args[0];
+                    }
+                    results[k] = args;
+                    callback(err);
+                });
+            }, function (err) {
+                callback(err, results);
+            });
+        }
+    };
+
+    async.parallel = function (tasks, callback) {
+        _parallel({ map: async.map, each: async.each }, tasks, callback);
+    };
+
+    async.parallelLimit = function(tasks, limit, callback) {
+        _parallel({ map: _mapLimit(limit), each: _eachLimit(limit) }, tasks, callback);
+    };
+
+    async.series = function (tasks, callback) {
+        callback = callback || function () {};
+        if (_isArray(tasks)) {
+            async.mapSeries(tasks, function (fn, callback) {
+                if (fn) {
+                    fn(function (err) {
+                        var args = Array.prototype.slice.call(arguments, 1);
+                        if (args.length <= 1) {
+                            args = args[0];
+                        }
+                        callback.call(null, err, args);
+                    });
+                }
+            }, callback);
+        }
+        else {
+            var results = {};
+            async.eachSeries(_keys(tasks), function (k, callback) {
+                tasks[k](function (err) {
+                    var args = Array.prototype.slice.call(arguments, 1);
+                    if (args.length <= 1) {
+                        args = args[0];
+                    }
+                    results[k] = args;
+                    callback(err);
+                });
+            }, function (err) {
+                callback(err, results);
+            });
+        }
+    };
+
+    async.iterator = function (tasks) {
+        var makeCallback = function (index) {
+            var fn = function () {
+                if (tasks.length) {
+                    tasks[index].apply(null, arguments);
+                }
+                return fn.next();
+            };
+            fn.next = function () {
+                return (index < tasks.length - 1) ? makeCallback(index + 1): null;
+            };
+            return fn;
+        };
+        return makeCallback(0);
+    };
+
+    async.apply = function (fn) {
+        var args = Array.prototype.slice.call(arguments, 1);
+        return function () {
+            return fn.apply(
+                null, args.concat(Array.prototype.slice.call(arguments))
+            );
+        };
+    };
+
+    var _concat = function (eachfn, arr, fn, callback) {
+        var r = [];
+        eachfn(arr, function (x, cb) {
+            fn(x, function (err, y) {
+                r = r.concat(y || []);
+                cb(err);
+            });
+        }, function (err) {
+            callback(err, r);
+        });
+    };
+    async.concat = doParallel(_concat);
+    async.concatSeries = doSeries(_concat);
+
+    async.whilst = function (test, iterator, callback) {
+        if (test()) {
+            iterator(function (err) {
+                if (err) {
+                    return callback(err);
+                }
+                async.whilst(test, iterator, callback);
+            });
+        }
+        else {
+            callback();
+        }
+    };
+
+    async.doWhilst = function (iterator, test, callback) {
+        iterator(function (err) {
+            if (err) {
+                return callback(err);
+            }
+            var args = Array.prototype.slice.call(arguments, 1);
+            if (test.apply(null, args)) {
+                async.doWhilst(iterator, test, callback);
+            }
+            else {
+                callback();
+            }
+        });
+    };
+
+    async.until = function (test, iterator, callback) {
+        if (!test()) {
+            iterator(function (err) {
+                if (err) {
+                    return callback(err);
+                }
+                async.until(test, iterator, callback);
+            });
+        }
+        else {
+            callback();
+        }
+    };
+
+    async.doUntil = function (iterator, test, callback) {
+        iterator(function (err) {
+            if (err) {
+                return callback(err);
+            }
+            var args = Array.prototype.slice.call(arguments, 1);
+            if (!test.apply(null, args)) {
+                async.doUntil(iterator, test, callback);
+            }
+            else {
+                callback();
+            }
+        });
+    };
+
+    async.queue = function (worker, concurrency) {
+        if (concurrency === undefined) {
+            concurrency = 1;
+        }
+        function _insert(q, data, pos, callback) {
+          if (!q.started){
+            q.started = true;
+          }
+          if (!_isArray(data)) {
+              data = [data];
+          }
+          if(data.length == 0) {
+             // call drain immediately if there are no tasks
+             return async.setImmediate(function() {
+                 if (q.drain) {
+                     q.drain();
+                 }
+             });
+          }
+          _each(data, function(task) {
+              var item = {
+                  data: task,
+                  callback: typeof callback === 'function' ? callback : null
+              };
+
+              if (pos) {
+                q.tasks.unshift(item);
+              } else {
+                q.tasks.push(item);
+              }
+
+              if (q.saturated && q.tasks.length === q.concurrency) {
+                  q.saturated();
+              }
+              async.setImmediate(q.process);
+          });
+        }
+
+        var workers = 0;
+        var q = {
+            tasks: [],
+            concurrency: concurrency,
+            saturated: null,
+            empty: null,
+            drain: null,
+            started: false,
+            paused: false,
+            push: function (data, callback) {
+              _insert(q, data, false, callback);
+            },
+            kill: function () {
+              q.drain = null;
+              q.tasks = [];
+            },
+            unshift: function (data, callback) {
+              _insert(q, data, true, callback);
+            },
+            process: function () {
+                if (!q.paused && workers < q.concurrency && q.tasks.length) {
+                    var task = q.tasks.shift();
+                    if (q.empty && q.tasks.length === 0) {
+                        q.empty();
+                    }
+                    workers += 1;
+                    var next = function () {
+                        workers -= 1;
+                        if (task.callback) {
+                            task.callback.apply(task, arguments);
+                        }
+                        if (q.drain && q.tasks.length + workers === 0) {
+                            q.drain();
+                        }
+                        q.process();
+                    };
+                    var cb = only_once(next);
+                    worker(task.data, cb);
+                }
+            },
+            length: function () {
+                return q.tasks.length;
+            },
+            running: function () {
+                return workers;
+            },
+            idle: function() {
+                return q.tasks.length + workers === 0;
+            },
+            pause: function () {
+                if (q.paused === true) { return; }
+                q.paused = true;
+                q.process();
+            },
+            resume: function () {
+                if (q.paused === false) { return; }
+                q.paused = false;
+                q.process();
+            }
+        };
+        return q;
+    };
+    
+    async.priorityQueue = function (worker, concurrency) {
+        
+        function _compareTasks(a, b){
+          return a.priority - b.priority;
+        };
+        
+        function _binarySearch(sequence, item, compare) {
+          var beg = -1,
+              end = sequence.length - 1;
+          while (beg < end) {
+            var mid = beg + ((end - beg + 1) >>> 1);
+            if (compare(item, sequence[mid]) >= 0) {
+              beg = mid;
+            } else {
+              end = mid - 1;
+            }
+          }
+          return beg;
+        }
+        
+        function _insert(q, data, priority, callback) {
+          if (!q.started){
+            q.started = true;
+          }
+          if (!_isArray(data)) {
+              data = [data];
+          }
+          if(data.length == 0) {
+             // call drain immediately if there are no tasks
+             return async.setImmediate(function() {
+                 if (q.drain) {
+                     q.drain();
+                 }
+             });
+          }
+          _each(data, function(task) {
+              var item = {
+                  data: task,
+                  priority: priority,
+                  callback: typeof callback === 'function' ? callback : null
+              };
+              
+              q.tasks.splice(_binarySearch(q.tasks, item, _compareTasks) + 1, 0, item);
+
+              if (q.saturated && q.tasks.length === q.concurrency) {
+                  q.saturated();
+              }
+              async.setImmediate(q.process);
+          });
+        }
+        
+        // Start with a normal queue
+        var q = async.queue(worker, concurrency);
+        
+        // Override push to accept second parameter representing priority
+        q.push = function (data, priority, callback) {
+          _insert(q, data, priority, callback);
+        };
+        
+        // Remove unshift function
+        delete q.unshift;
+
+        return q;
+    };
+
+    async.cargo = function (worker, payload) {
+        var working     = false,
+            tasks       = [];
+
+        var cargo = {
+            tasks: tasks,
+            payload: payload,
+            saturated: null,
+            empty: null,
+            drain: null,
+            drained: true,
+            push: function (data, callback) {
+                if (!_isArray(data)) {
+                    data = [data];
+                }
+                _each(data, function(task) {
+                    tasks.push({
+                        data: task,
+                        callback: typeof callback === 'function' ? callback : null
+                    });
+                    cargo.drained = false;
+                    if (cargo.saturated && tasks.length === payload) {
+                        cargo.saturated();
+                    }
+                });
+                async.setImmediate(cargo.process);
+            },
+            process: function process() {
+                if (working) return;
+                if (tasks.length === 0) {
+                    if(cargo.drain && !cargo.drained) cargo.drain();
+                    cargo.drained = true;
+                    return;
+                }
+
+                var ts = typeof payload === 'number'
+                            ? tasks.splice(0, payload)
+                            : tasks.splice(0, tasks.length);
+
+                var ds = _map(ts, function (task) {
+                    return task.data;
+                });
+
+                if(cargo.empty) cargo.empty();
+                working = true;
+                worker(ds, function () {
+                    working = false;
+
+                    var args = arguments;
+                    _each(ts, function (data) {
+                        if (data.callback) {
+                            data.callback.apply(null, args);
+                        }
+                    });
+
+                    process();
+                });
+            },
+            length: function () {
+                return tasks.length;
+            },
+            running: function () {
+                return working;
+            }
+        };
+        return cargo;
+    };
+
+    var _console_fn = function (name) {
+        return function (fn) {
+            var args = Array.prototype.slice.call(arguments, 1);
+            fn.apply(null, args.concat([function (err) {
+                var args = Array.prototype.slice.call(arguments, 1);
+                if (typeof console !== 'undefined') {
+                    if (err) {
+                        if (console.error) {
+                            console.error(err);
+                        }
+                    }
+                    else if (console[name]) {
+                        _each(args, function (x) {
+                            console[name](x);
+                        });
+                    }
+                }
+            }]));
+        };
+    };
+    async.log = _console_fn('log');
+    async.dir = _console_fn('dir');
+    /*async.info = _console_fn('info');
+    async.warn = _console_fn('warn');
+    async.error = _console_fn('error');*/
+
+    async.memoize = function (fn, hasher) {
+        var memo = {};
+        var queues = {};
+        hasher = hasher || function (x) {
+            return x;
+        };
+        var memoized = function () {
+            var args = Array.prototype.slice.call(arguments);
+            var callback = args.pop();
+            var key = hasher.apply(null, args);
+            if (key in memo) {
+                async.nextTick(function () {
+                    callback.apply(null, memo[key]);
+                });
+            }
+            else if (key in queues) {
+                queues[key].push(callback);
+            }
+            else {
+                queues[key] = [callback];
+                fn.apply(null, args.concat([function () {
+                    memo[key] = arguments;
+                    var q = queues[key];
+                    delete queues[key];
+                    for (var i = 0, l = q.length; i < l; i++) {
+                      q[i].apply(null, arguments);
+                    }
+                }]));
+            }
+        };
+        memoized.memo = memo;
+        memoized.unmemoized = fn;
+        return memoized;
+    };
+
+    async.unmemoize = function (fn) {
+      return function () {
+        return (fn.unmemoized || fn).apply(null, arguments);
+      };
+    };
+
+    async.times = function (count, iterator, callback) {
+        var counter = [];
+        for (var i = 0; i < count; i++) {
+            counter.push(i);
+        }
+        return async.map(counter, iterator, callback);
+    };
+
+    async.timesSeries = function (count, iterator, callback) {
+        var counter = [];
+        for (var i = 0; i < count; i++) {
+            counter.push(i);
+        }
+        return async.mapSeries(counter, iterator, callback);
+    };
+
+    async.seq = function (/* functions... */) {
+        var fns = arguments;
+        return function () {
+            var that = this;
+            var args = Array.prototype.slice.call(arguments);
+            var callback = args.pop();
+            async.reduce(fns, args, function (newargs, fn, cb) {
+                fn.apply(that, newargs.concat([function () {
+                    var err = arguments[0];
+                    var nextargs = Array.prototype.slice.call(arguments, 1);
+                    cb(err, nextargs);
+                }]))
+            },
+            function (err, results) {
+                callback.apply(that, [err].concat(results));
+            });
+        };
+    };
+
+    async.compose = function (/* functions... */) {
+      return async.seq.apply(null, Array.prototype.reverse.call(arguments));
+    };
+
+    var _applyEach = function (eachfn, fns /*args...*/) {
+        var go = function () {
+            var that = this;
+            var args = Array.prototype.slice.call(arguments);
+            var callback = args.pop();
+            return eachfn(fns, function (fn, cb) {
+                fn.apply(that, args.concat([cb]));
+            },
+            callback);
+        };
+        if (arguments.length > 2) {
+            var args = Array.prototype.slice.call(arguments, 2);
+            return go.apply(this, args);
+        }
+        else {
+            return go;
+        }
+    };
+    async.applyEach = doParallel(_applyEach);
+    async.applyEachSeries = doSeries(_applyEach);
+
+    async.forever = function (fn, callback) {
+        function next(err) {
+            if (err) {
+                if (callback) {
+                    return callback(err);
+                }
+                throw err;
+            }
+            fn(next);
+        }
+        next();
+    };
+
+    // Node.js
+    if (typeof module !== 'undefined' && module.exports) {
+        module.exports = async;
+    }
+    // AMD / RequireJS
+    else if (typeof define !== 'undefined' && define.amd) {
+        define([], function () {
+            return async;
+        });
+    }
+    // included directly via <script> tag
+    else {
+        root.async = async;
+    }
+
+}());
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/async/package.json b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/async/package.json
new file mode 100644
index 0000000..fbdc082
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/async/package.json
@@ -0,0 +1,43 @@
+{
+  "name": "async",
+  "description": "Higher-order functions and common patterns for asynchronous code",
+  "main": "./lib/async",
+  "author": {
+    "name": "Caolan McMahon"
+  },
+  "version": "0.9.0",
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/caolan/async.git"
+  },
+  "bugs": {
+    "url": "https://github.com/caolan/async/issues"
+  },
+  "licenses": [
+    {
+      "type": "MIT",
+      "url": "https://github.com/caolan/async/raw/master/LICENSE"
+    }
+  ],
+  "devDependencies": {
+    "nodeunit": ">0.0.0",
+    "uglify-js": "1.2.x",
+    "nodelint": ">0.0.0"
+  },
+  "jam": {
+    "main": "lib/async.js",
+    "include": [
+      "lib/async.js",
+      "README.md",
+      "LICENSE"
+    ]
+  },
+  "scripts": {
+    "test": "nodeunit test/test-async.js"
+  },
+  "readme": "# Async.js\n\n[![Build Status via Travis CI](https://travis-ci.org/caolan/async.svg?branch=master)](https://travis-ci.org/caolan/async)\n\n\nAsync is a utility module which provides straight-forward, powerful functions\nfor working with asynchronous JavaScript. Although originally designed for\nuse with [Node.js](http://nodejs.org), it can also be used directly in the\nbrowser. Also supports [component](https://github.com/component/component).\n\nAsync provides around 20 functions that include the usual 'functional'\nsuspects (`map`, `reduce`, `filter`, `each`…) as well as some common patterns\nfor asynchronous control flow (`parallel`, `series`, `waterfall`…). All these\nfunctions assume you follow the Node.js convention of providing a single\ncallback as the last argument of your `async` function.\n\n\n## Quick Examples\n\n```javascript\nasync.map(['file1','file2','file3'], fs.stat, function(err, results){\n    // results is now an array of stats for each file\n});\n\nasync.filter(['file1','file2','file3'], fs.exists, function(results){\n    // results now equals an array of the existing files\n});\n\nasync.parallel([\n    function(){ ... },\n    function(){ ... }\n], callback);\n\nasync.series([\n    function(){ ... },\n    function(){ ... }\n]);\n```\n\nThere are many more functions available so take a look at the docs below for a\nfull list. This module aims to be comprehensive, so if you feel anything is\nmissing please create a GitHub issue for it.\n\n## Common Pitfalls\n\n### Binding a context to an iterator\n\nThis section is really about `bind`, not about `async`. If you are wondering how to\nmake `async` execute your iterators in a given context, or are confused as to why\na method of another library isn't working as an iterator, study this example:\n\n```js\n// Here is a simple object with an (unnecessarily roundabout) squaring method\nvar AsyncSquaringLibrary = {\n  squareExponent: 2,\n  square: function(number, callback){ \n    var result = Math.pow(number, this.squareExponent);\n    setTimeout(function(){\n      callback(null, result);\n    }, 200);\n  }\n};\n\nasync.map([1, 2, 3], AsyncSquaringLibrary.square, function(err, result){\n  // result is [NaN, NaN, NaN]\n  // This fails because the `this.squareExponent` expression in the square\n  // function is not evaluated in the context of AsyncSquaringLibrary, and is\n  // therefore undefined.\n});\n\nasync.map([1, 2, 3], AsyncSquaringLibrary.square.bind(AsyncSquaringLibrary), function(err, result){\n  // result is [1, 4, 9]\n  // With the help of bind we can attach a context to the iterator before\n  // passing it to async. Now the square function will be executed in its \n  // 'home' AsyncSquaringLibrary context and the value of `this.squareExponent`\n  // will be as expected.\n});\n```\n\n## Download\n\nThe source is available for download from\n[GitHub](http://github.com/caolan/async).\nAlternatively, you can install using Node Package Manager (`npm`):\n\n    npm install async\n\n__Development:__ [async.js](https://github.com/caolan/async/raw/master/lib/async.js) - 29.6kb Uncompressed\n\n## In the Browser\n\nSo far it's been tested in IE6, IE7, IE8, FF3.6 and Chrome 5. \n\nUsage:\n\n```html\n<script type=\"text/javascript\" src=\"async.js\"></script>\n<script type=\"text/javascript\">\n\n    async.map(data, asyncProcess, function(err, results){\n        alert(results);\n    });\n\n</script>\n```\n\n## Documentation\n\n### Collections\n\n* [`each`](#each)\n* [`eachSeries`](#eachSeries)\n* [`eachLimit`](#eachLimit)\n* [`map`](#map)\n* [`mapSeries`](#mapSeries)\n* [`mapLimit`](#mapLimit)\n* [`filter`](#filter)\n* [`filterSeries`](#filterSeries)\n* [`reject`](#reject)\n* [`rejectSeries`](#rejectSeries)\n* [`reduce`](#reduce)\n* [`reduceRight`](#reduceRight)\n* [`detect`](#detect)\n* [`detectSeries`](#detectSeries)\n* [`sortBy`](#sortBy)\n* [`some`](#some)\n* [`every`](#every)\n* [`concat`](#concat)\n* [`concatSeries`](#concatSeries)\n\n### Control Flow\n\n* [`series`](#seriestasks-callback)\n* [`parallel`](#parallel)\n* [`parallelLimit`](#parallellimittasks-limit-callback)\n* [`whilst`](#whilst)\n* [`doWhilst`](#doWhilst)\n* [`until`](#until)\n* [`doUntil`](#doUntil)\n* [`forever`](#forever)\n* [`waterfall`](#waterfall)\n* [`compose`](#compose)\n* [`seq`](#seq)\n* [`applyEach`](#applyEach)\n* [`applyEachSeries`](#applyEachSeries)\n* [`queue`](#queue)\n* [`priorityQueue`](#priorityQueue)\n* [`cargo`](#cargo)\n* [`auto`](#auto)\n* [`retry`](#retry)\n* [`iterator`](#iterator)\n* [`apply`](#apply)\n* [`nextTick`](#nextTick)\n* [`times`](#times)\n* [`timesSeries`](#timesSeries)\n\n### Utils\n\n* [`memoize`](#memoize)\n* [`unmemoize`](#unmemoize)\n* [`log`](#log)\n* [`dir`](#dir)\n* [`noConflict`](#noConflict)\n\n\n## Collections\n\n<a name=\"forEach\" />\n<a name=\"each\" />\n### each(arr, iterator, callback)\n\nApplies the function `iterator` to each item in `arr`, in parallel.\nThe `iterator` is called with an item from the list, and a callback for when it\nhas finished. If the `iterator` passes an error to its `callback`, the main\n`callback` (for the `each` function) is immediately called with the error.\n\nNote, that since this function applies `iterator` to each item in parallel,\nthere is no guarantee that the iterator functions will complete in order.\n\n__Arguments__\n\n* `arr` - An array to iterate over.\n* `iterator(item, callback)` - A function to apply to each item in `arr`.\n  The iterator is passed a `callback(err)` which must be called once it has \n  completed. If no error has occured, the `callback` should be run without \n  arguments or with an explicit `null` argument.\n* `callback(err)` - A callback which is called when all `iterator` functions\n  have finished, or an error occurs.\n\n__Examples__\n\n\n```js\n// assuming openFiles is an array of file names and saveFile is a function\n// to save the modified contents of that file:\n\nasync.each(openFiles, saveFile, function(err){\n    // if any of the saves produced an error, err would equal that error\n});\n```\n\n```js\n// assuming openFiles is an array of file names \n\nasync.each(openFiles, function( file, callback) {\n  \n  // Perform operation on file here.\n  console.log('Processing file ' + file);\n  \n  if( file.length > 32 ) {\n    console.log('This file name is too long');\n    callback('File name too long');\n  } else {\n    // Do work to process file here\n    console.log('File processed');\n    callback();\n  }\n}, function(err){\n    // if any of the file processing produced an error, err would equal that error\n    if( err ) {\n      // One of the iterations produced an error.\n      // All processing will now stop.\n      console.log('A file failed to process');\n    } else {\n      console.log('All files have been processed successfully');\n    }\n});\n```\n\n---------------------------------------\n\n<a name=\"forEachSeries\" />\n<a name=\"eachSeries\" />\n### eachSeries(arr, iterator, callback)\n\nThe same as [`each`](#each), only `iterator` is applied to each item in `arr` in\nseries. The next `iterator` is only called once the current one has completed. \nThis means the `iterator` functions will complete in order.\n\n\n---------------------------------------\n\n<a name=\"forEachLimit\" />\n<a name=\"eachLimit\" />\n### eachLimit(arr, limit, iterator, callback)\n\nThe same as [`each`](#each), only no more than `limit` `iterator`s will be simultaneously \nrunning at any time.\n\nNote that the items in `arr` are not processed in batches, so there is no guarantee that \nthe first `limit` `iterator` functions will complete before any others are started.\n\n__Arguments__\n\n* `arr` - An array to iterate over.\n* `limit` - The maximum number of `iterator`s to run at any time.\n* `iterator(item, callback)` - A function to apply to each item in `arr`.\n  The iterator is passed a `callback(err)` which must be called once it has \n  completed. If no error has occured, the callback should be run without \n  arguments or with an explicit `null` argument.\n* `callback(err)` - A callback which is called when all `iterator` functions\n  have finished, or an error occurs.\n\n__Example__\n\n```js\n// Assume documents is an array of JSON objects and requestApi is a\n// function that interacts with a rate-limited REST api.\n\nasync.eachLimit(documents, 20, requestApi, function(err){\n    // if any of the saves produced an error, err would equal that error\n});\n```\n\n---------------------------------------\n\n<a name=\"map\" />\n### map(arr, iterator, callback)\n\nProduces a new array of values by mapping each value in `arr` through\nthe `iterator` function. The `iterator` is called with an item from `arr` and a\ncallback for when it has finished processing. Each of these callback takes 2 arguments: \nan `error`, and the transformed item from `arr`. If `iterator` passes an error to this \ncallback, the main `callback` (for the `map` function) is immediately called with the error.\n\nNote, that since this function applies the `iterator` to each item in parallel,\nthere is no guarantee that the `iterator` functions will complete in order. \nHowever, the results array will be in the same order as the original `arr`.\n\n__Arguments__\n\n* `arr` - An array to iterate over.\n* `iterator(item, callback)` - A function to apply to each item in `arr`.\n  The iterator is passed a `callback(err, transformed)` which must be called once \n  it has completed with an error (which can be `null`) and a transformed item.\n* `callback(err, results)` - A callback which is called when all `iterator`\n  functions have finished, or an error occurs. Results is an array of the\n  transformed items from the `arr`.\n\n__Example__\n\n```js\nasync.map(['file1','file2','file3'], fs.stat, function(err, results){\n    // results is now an array of stats for each file\n});\n```\n\n---------------------------------------\n\n<a name=\"mapSeries\" />\n### mapSeries(arr, iterator, callback)\n\nThe same as [`map`](#map), only the `iterator` is applied to each item in `arr` in\nseries. The next `iterator` is only called once the current one has completed. \nThe results array will be in the same order as the original.\n\n\n---------------------------------------\n\n<a name=\"mapLimit\" />\n### mapLimit(arr, limit, iterator, callback)\n\nThe same as [`map`](#map), only no more than `limit` `iterator`s will be simultaneously \nrunning at any time.\n\nNote that the items are not processed in batches, so there is no guarantee that \nthe first `limit` `iterator` functions will complete before any others are started.\n\n__Arguments__\n\n* `arr` - An array to iterate over.\n* `limit` - The maximum number of `iterator`s to run at any time.\n* `iterator(item, callback)` - A function to apply to each item in `arr`.\n  The iterator is passed a `callback(err, transformed)` which must be called once \n  it has completed with an error (which can be `null`) and a transformed item.\n* `callback(err, results)` - A callback which is called when all `iterator`\n  calls have finished, or an error occurs. The result is an array of the\n  transformed items from the original `arr`.\n\n__Example__\n\n```js\nasync.mapLimit(['file1','file2','file3'], 1, fs.stat, function(err, results){\n    // results is now an array of stats for each file\n});\n```\n\n---------------------------------------\n\n<a name=\"select\" />\n<a name=\"filter\" />\n### filter(arr, iterator, callback)\n\n__Alias:__ `select`\n\nReturns a new array of all the values in `arr` which pass an async truth test.\n_The callback for each `iterator` call only accepts a single argument of `true` or\n`false`; it does not accept an error argument first!_ This is in-line with the\nway node libraries work with truth tests like `fs.exists`. This operation is\nperformed in parallel, but the results array will be in the same order as the\noriginal.\n\n__Arguments__\n\n* `arr` - An array to iterate over.\n* `iterator(item, callback)` - A truth test to apply to each item in `arr`.\n  The `iterator` is passed a `callback(truthValue)`, which must be called with a \n  boolean argument once it has completed.\n* `callback(results)` - A callback which is called after all the `iterator`\n  functions have finished.\n\n__Example__\n\n```js\nasync.filter(['file1','file2','file3'], fs.exists, function(results){\n    // results now equals an array of the existing files\n});\n```\n\n---------------------------------------\n\n<a name=\"selectSeries\" />\n<a name=\"filterSeries\" />\n### filterSeries(arr, iterator, callback)\n\n__Alias:__ `selectSeries`\n\nThe same as [`filter`](#filter) only the `iterator` is applied to each item in `arr` in\nseries. The next `iterator` is only called once the current one has completed. \nThe results array will be in the same order as the original.\n\n---------------------------------------\n\n<a name=\"reject\" />\n### reject(arr, iterator, callback)\n\nThe opposite of [`filter`](#filter). Removes values that pass an `async` truth test.\n\n---------------------------------------\n\n<a name=\"rejectSeries\" />\n### rejectSeries(arr, iterator, callback)\n\nThe same as [`reject`](#reject), only the `iterator` is applied to each item in `arr`\nin series.\n\n\n---------------------------------------\n\n<a name=\"reduce\" />\n### reduce(arr, memo, iterator, callback)\n\n__Aliases:__ `inject`, `foldl`\n\nReduces `arr` into a single value using an async `iterator` to return\neach successive step. `memo` is the initial state of the reduction. \nThis function only operates in series. \n\nFor performance reasons, it may make sense to split a call to this function into \na parallel map, and then use the normal `Array.prototype.reduce` on the results. \nThis function is for situations where each step in the reduction needs to be async; \nif you can get the data before reducing it, then it's probably a good idea to do so.\n\n__Arguments__\n\n* `arr` - An array to iterate over.\n* `memo` - The initial state of the reduction.\n* `iterator(memo, item, callback)` - A function applied to each item in the\n  array to produce the next step in the reduction. The `iterator` is passed a\n  `callback(err, reduction)` which accepts an optional error as its first \n  argument, and the state of the reduction as the second. If an error is \n  passed to the callback, the reduction is stopped and the main `callback` is \n  immediately called with the error.\n* `callback(err, result)` - A callback which is called after all the `iterator`\n  functions have finished. Result is the reduced value.\n\n__Example__\n\n```js\nasync.reduce([1,2,3], 0, function(memo, item, callback){\n    // pointless async:\n    process.nextTick(function(){\n        callback(null, memo + item)\n    });\n}, function(err, result){\n    // result is now equal to the last value of memo, which is 6\n});\n```\n\n---------------------------------------\n\n<a name=\"reduceRight\" />\n### reduceRight(arr, memo, iterator, callback)\n\n__Alias:__ `foldr`\n\nSame as [`reduce`](#reduce), only operates on `arr` in reverse order.\n\n\n---------------------------------------\n\n<a name=\"detect\" />\n### detect(arr, iterator, callback)\n\nReturns the first value in `arr` that passes an async truth test. The\n`iterator` is applied in parallel, meaning the first iterator to return `true` will\nfire the detect `callback` with that result. That means the result might not be\nthe first item in the original `arr` (in terms of order) that passes the test.\n\nIf order within the original `arr` is important, then look at [`detectSeries`](#detectSeries).\n\n__Arguments__\n\n* `arr` - An array to iterate over.\n* `iterator(item, callback)` - A truth test to apply to each item in `arr`.\n  The iterator is passed a `callback(truthValue)` which must be called with a \n  boolean argument once it has completed.\n* `callback(result)` - A callback which is called as soon as any iterator returns\n  `true`, or after all the `iterator` functions have finished. Result will be\n  the first item in the array that passes the truth test (iterator) or the\n  value `undefined` if none passed.\n\n__Example__\n\n```js\nasync.detect(['file1','file2','file3'], fs.exists, function(result){\n    // result now equals the first file in the list that exists\n});\n```\n\n---------------------------------------\n\n<a name=\"detectSeries\" />\n### detectSeries(arr, iterator, callback)\n\nThe same as [`detect`](#detect), only the `iterator` is applied to each item in `arr`\nin series. This means the result is always the first in the original `arr` (in\nterms of array order) that passes the truth test.\n\n\n---------------------------------------\n\n<a name=\"sortBy\" />\n### sortBy(arr, iterator, callback)\n\nSorts a list by the results of running each `arr` value through an async `iterator`.\n\n__Arguments__\n\n* `arr` - An array to iterate over.\n* `iterator(item, callback)` - A function to apply to each item in `arr`.\n  The iterator is passed a `callback(err, sortValue)` which must be called once it\n  has completed with an error (which can be `null`) and a value to use as the sort\n  criteria.\n* `callback(err, results)` - A callback which is called after all the `iterator`\n  functions have finished, or an error occurs. Results is the items from\n  the original `arr` sorted by the values returned by the `iterator` calls.\n\n__Example__\n\n```js\nasync.sortBy(['file1','file2','file3'], function(file, callback){\n    fs.stat(file, function(err, stats){\n        callback(err, stats.mtime);\n    });\n}, function(err, results){\n    // results is now the original array of files sorted by\n    // modified date\n});\n```\n\n__Sort Order__\n\nBy modifying the callback parameter the sorting order can be influenced:\n\n```js\n//ascending order\nasync.sortBy([1,9,3,5], function(x, callback){\n    callback(err, x);\n}, function(err,result){\n    //result callback\n} );\n\n//descending order\nasync.sortBy([1,9,3,5], function(x, callback){\n    callback(err, x*-1);    //<- x*-1 instead of x, turns the order around\n}, function(err,result){\n    //result callback\n} );\n```\n\n---------------------------------------\n\n<a name=\"some\" />\n### some(arr, iterator, callback)\n\n__Alias:__ `any`\n\nReturns `true` if at least one element in the `arr` satisfies an async test.\n_The callback for each iterator call only accepts a single argument of `true` or\n`false`; it does not accept an error argument first!_ This is in-line with the\nway node libraries work with truth tests like `fs.exists`. Once any iterator\ncall returns `true`, the main `callback` is immediately called.\n\n__Arguments__\n\n* `arr` - An array to iterate over.\n* `iterator(item, callback)` - A truth test to apply to each item in the array\n  in parallel. The iterator is passed a callback(truthValue) which must be \n  called with a boolean argument once it has completed.\n* `callback(result)` - A callback which is called as soon as any iterator returns\n  `true`, or after all the iterator functions have finished. Result will be\n  either `true` or `false` depending on the values of the async tests.\n\n__Example__\n\n```js\nasync.some(['file1','file2','file3'], fs.exists, function(result){\n    // if result is true then at least one of the files exists\n});\n```\n\n---------------------------------------\n\n<a name=\"every\" />\n### every(arr, iterator, callback)\n\n__Alias:__ `all`\n\nReturns `true` if every element in `arr` satisfies an async test.\n_The callback for each `iterator` call only accepts a single argument of `true` or\n`false`; it does not accept an error argument first!_ This is in-line with the\nway node libraries work with truth tests like `fs.exists`.\n\n__Arguments__\n\n* `arr` - An array to iterate over.\n* `iterator(item, callback)` - A truth test to apply to each item in the array\n  in parallel. The iterator is passed a callback(truthValue) which must be \n  called with a  boolean argument once it has completed.\n* `callback(result)` - A callback which is called after all the `iterator`\n  functions have finished. Result will be either `true` or `false` depending on\n  the values of the async tests.\n\n__Example__\n\n```js\nasync.every(['file1','file2','file3'], fs.exists, function(result){\n    // if result is true then every file exists\n});\n```\n\n---------------------------------------\n\n<a name=\"concat\" />\n### concat(arr, iterator, callback)\n\nApplies `iterator` to each item in `arr`, concatenating the results. Returns the\nconcatenated list. The `iterator`s are called in parallel, and the results are\nconcatenated as they return. There is no guarantee that the results array will\nbe returned in the original order of `arr` passed to the `iterator` function.\n\n__Arguments__\n\n* `arr` - An array to iterate over.\n* `iterator(item, callback)` - A function to apply to each item in `arr`.\n  The iterator is passed a `callback(err, results)` which must be called once it \n  has completed with an error (which can be `null`) and an array of results.\n* `callback(err, results)` - A callback which is called after all the `iterator`\n  functions have finished, or an error occurs. Results is an array containing\n  the concatenated results of the `iterator` function.\n\n__Example__\n\n```js\nasync.concat(['dir1','dir2','dir3'], fs.readdir, function(err, files){\n    // files is now a list of filenames that exist in the 3 directories\n});\n```\n\n---------------------------------------\n\n<a name=\"concatSeries\" />\n### concatSeries(arr, iterator, callback)\n\nSame as [`concat`](#concat), but executes in series instead of parallel.\n\n\n## Control Flow\n\n<a name=\"series\" />\n### series(tasks, [callback])\n\nRun the functions in the `tasks` array in series, each one running once the previous\nfunction has completed. If any functions in the series pass an error to its\ncallback, no more functions are run, and `callback` is immediately called with the value of the error. \nOtherwise, `callback` receives an array of results when `tasks` have completed.\n\nIt is also possible to use an object instead of an array. Each property will be\nrun as a function, and the results will be passed to the final `callback` as an object\ninstead of an array. This can be a more readable way of handling results from\n[`series`](#series).\n\n**Note** that while many implementations preserve the order of object properties, the\n[ECMAScript Language Specifcation](http://www.ecma-international.org/ecma-262/5.1/#sec-8.6) \nexplicitly states that\n\n> The mechanics and order of enumerating the properties is not specified.\n\nSo if you rely on the order in which your series of functions are executed, and want\nthis to work on all platforms, consider using an array. \n\n__Arguments__\n\n* `tasks` - An array or object containing functions to run, each function is passed\n  a `callback(err, result)` it must call on completion with an error `err` (which can\n  be `null`) and an optional `result` value.\n* `callback(err, results)` - An optional callback to run once all the functions\n  have completed. This function gets a results array (or object) containing all \n  the result arguments passed to the `task` callbacks.\n\n__Example__\n\n```js\nasync.series([\n    function(callback){\n        // do some stuff ...\n        callback(null, 'one');\n    },\n    function(callback){\n        // do some more stuff ...\n        callback(null, 'two');\n    }\n],\n// optional callback\nfunction(err, results){\n    // results is now equal to ['one', 'two']\n});\n\n\n// an example using an object instead of an array\nasync.series({\n    one: function(callback){\n        setTimeout(function(){\n            callback(null, 1);\n        }, 200);\n    },\n    two: function(callback){\n        setTimeout(function(){\n            callback(null, 2);\n        }, 100);\n    }\n},\nfunction(err, results) {\n    // results is now equal to: {one: 1, two: 2}\n});\n```\n\n---------------------------------------\n\n<a name=\"parallel\" />\n### parallel(tasks, [callback])\n\nRun the `tasks` array of functions in parallel, without waiting until the previous\nfunction has completed. If any of the functions pass an error to its\ncallback, the main `callback` is immediately called with the value of the error.\nOnce the `tasks` have completed, the results are passed to the final `callback` as an\narray.\n\nIt is also possible to use an object instead of an array. Each property will be\nrun as a function and the results will be passed to the final `callback` as an object\ninstead of an array. This can be a more readable way of handling results from\n[`parallel`](#parallel).\n\n\n__Arguments__\n\n* `tasks` - An array or object containing functions to run. Each function is passed \n  a `callback(err, result)` which it must call on completion with an error `err` \n  (which can be `null`) and an optional `result` value.\n* `callback(err, results)` - An optional callback to run once all the functions\n  have completed. This function gets a results array (or object) containing all \n  the result arguments passed to the task callbacks.\n\n__Example__\n\n```js\nasync.parallel([\n    function(callback){\n        setTimeout(function(){\n            callback(null, 'one');\n        }, 200);\n    },\n    function(callback){\n        setTimeout(function(){\n            callback(null, 'two');\n        }, 100);\n    }\n],\n// optional callback\nfunction(err, results){\n    // the results array will equal ['one','two'] even though\n    // the second function had a shorter timeout.\n});\n\n\n// an example using an object instead of an array\nasync.parallel({\n    one: function(callback){\n        setTimeout(function(){\n            callback(null, 1);\n        }, 200);\n    },\n    two: function(callback){\n        setTimeout(function(){\n            callback(null, 2);\n        }, 100);\n    }\n},\nfunction(err, results) {\n    // results is now equals to: {one: 1, two: 2}\n});\n```\n\n---------------------------------------\n\n<a name=\"parallelLimit\" />\n### parallelLimit(tasks, limit, [callback])\n\nThe same as [`parallel`](#parallel), only `tasks` are executed in parallel \nwith a maximum of `limit` tasks executing at any time.\n\nNote that the `tasks` are not executed in batches, so there is no guarantee that \nthe first `limit` tasks will complete before any others are started.\n\n__Arguments__\n\n* `tasks` - An array or object containing functions to run, each function is passed \n  a `callback(err, result)` it must call on completion with an error `err` (which can\n  be `null`) and an optional `result` value.\n* `limit` - The maximum number of `tasks` to run at any time.\n* `callback(err, results)` - An optional callback to run once all the functions\n  have completed. This function gets a results array (or object) containing all \n  the result arguments passed to the `task` callbacks.\n\n---------------------------------------\n\n<a name=\"whilst\" />\n### whilst(test, fn, callback)\n\nRepeatedly call `fn`, while `test` returns `true`. Calls `callback` when stopped,\nor an error occurs.\n\n__Arguments__\n\n* `test()` - synchronous truth test to perform before each execution of `fn`.\n* `fn(callback)` - A function which is called each time `test` passes. The function is\n  passed a `callback(err)`, which must be called once it has completed with an \n  optional `err` argument.\n* `callback(err)` - A callback which is called after the test fails and repeated\n  execution of `fn` has stopped.\n\n__Example__\n\n```js\nvar count = 0;\n\nasync.whilst(\n    function () { return count < 5; },\n    function (callback) {\n        count++;\n        setTimeout(callback, 1000);\n    },\n    function (err) {\n        // 5 seconds have passed\n    }\n);\n```\n\n---------------------------------------\n\n<a name=\"doWhilst\" />\n### doWhilst(fn, test, callback)\n\nThe post-check version of [`whilst`](#whilst). To reflect the difference in \nthe order of operations, the arguments `test` and `fn` are switched. \n\n`doWhilst` is to `whilst` as `do while` is to `while` in plain JavaScript.\n\n---------------------------------------\n\n<a name=\"until\" />\n### until(test, fn, callback)\n\nRepeatedly call `fn` until `test` returns `true`. Calls `callback` when stopped,\nor an error occurs.\n\nThe inverse of [`whilst`](#whilst).\n\n---------------------------------------\n\n<a name=\"doUntil\" />\n### doUntil(fn, test, callback)\n\nLike [`doWhilst`](#doWhilst), except the `test` is inverted. Note the argument ordering differs from `until`.\n\n---------------------------------------\n\n<a name=\"forever\" />\n### forever(fn, errback)\n\nCalls the asynchronous function `fn` with a callback parameter that allows it to\ncall itself again, in series, indefinitely.\n\nIf an error is passed to the callback then `errback` is called with the\nerror, and execution stops, otherwise it will never be called.\n\n```js\nasync.forever(\n    function(next) {\n        // next is suitable for passing to things that need a callback(err [, whatever]);\n        // it will result in this function being called again.\n    },\n    function(err) {\n        // if next is called with a value in its first parameter, it will appear\n        // in here as 'err', and execution will stop.\n    }\n);\n```\n\n---------------------------------------\n\n<a name=\"waterfall\" />\n### waterfall(tasks, [callback])\n\nRuns the `tasks` array of functions in series, each passing their results to the next in\nthe array. However, if any of the `tasks` pass an error to their own callback, the\nnext function is not executed, and the main `callback` is immediately called with\nthe error.\n\n__Arguments__\n\n* `tasks` - An array of functions to run, each function is passed a \n  `callback(err, result1, result2, ...)` it must call on completion. The first\n  argument is an error (which can be `null`) and any further arguments will be \n  passed as arguments in order to the next task.\n* `callback(err, [results])` - An optional callback to run once all the functions\n  have completed. This will be passed the results of the last task's callback.\n\n\n\n__Example__\n\n```js\nasync.waterfall([\n    function(callback){\n        callback(null, 'one', 'two');\n    },\n    function(arg1, arg2, callback){\n      // arg1 now equals 'one' and arg2 now equals 'two'\n        callback(null, 'three');\n    },\n    function(arg1, callback){\n        // arg1 now equals 'three'\n        callback(null, 'done');\n    }\n], function (err, result) {\n   // result now equals 'done'    \n});\n```\n\n---------------------------------------\n<a name=\"compose\" />\n### compose(fn1, fn2...)\n\nCreates a function which is a composition of the passed asynchronous\nfunctions. Each function consumes the return value of the function that\nfollows. Composing functions `f()`, `g()`, and `h()` would produce the result of\n`f(g(h()))`, only this version uses callbacks to obtain the return values.\n\nEach function is executed with the `this` binding of the composed function.\n\n__Arguments__\n\n* `functions...` - the asynchronous functions to compose\n\n\n__Example__\n\n```js\nfunction add1(n, callback) {\n    setTimeout(function () {\n        callback(null, n + 1);\n    }, 10);\n}\n\nfunction mul3(n, callback) {\n    setTimeout(function () {\n        callback(null, n * 3);\n    }, 10);\n}\n\nvar add1mul3 = async.compose(mul3, add1);\n\nadd1mul3(4, function (err, result) {\n   // result now equals 15\n});\n```\n\n---------------------------------------\n<a name=\"seq\" />\n### seq(fn1, fn2...)\n\nVersion of the compose function that is more natural to read.\nEach following function consumes the return value of the latter function. \n\nEach function is executed with the `this` binding of the composed function.\n\n__Arguments__\n\n* functions... - the asynchronous functions to compose\n\n\n__Example__\n\n```js\n// Requires lodash (or underscore), express3 and dresende's orm2.\n// Part of an app, that fetches cats of the logged user.\n// This example uses `seq` function to avoid overnesting and error \n// handling clutter.\napp.get('/cats', function(request, response) {\n  function handleError(err, data, callback) {\n    if (err) {\n      console.error(err);\n      response.json({ status: 'error', message: err.message });\n    }\n    else {\n      callback(data);\n    }\n  }\n  var User = request.models.User;\n  async.seq(\n    _.bind(User.get, User),  // 'User.get' has signature (id, callback(err, data))\n    handleError,\n    function(user, fn) {\n      user.getCats(fn);      // 'getCats' has signature (callback(err, data))\n    },\n    handleError,\n    function(cats) {\n      response.json({ status: 'ok', message: 'Cats found', data: cats });\n    }\n  )(req.session.user_id);\n  }\n});\n```\n\n---------------------------------------\n<a name=\"applyEach\" />\n### applyEach(fns, args..., callback)\n\nApplies the provided arguments to each function in the array, calling \n`callback` after all functions have completed. If you only provide the first\nargument, then it will return a function which lets you pass in the\narguments as if it were a single function call.\n\n__Arguments__\n\n* `fns` - the asynchronous functions to all call with the same arguments\n* `args...` - any number of separate arguments to pass to the function\n* `callback` - the final argument should be the callback, called when all\n  functions have completed processing\n\n\n__Example__\n\n```js\nasync.applyEach([enableSearch, updateSchema], 'bucket', callback);\n\n// partial application example:\nasync.each(\n    buckets,\n    async.applyEach([enableSearch, updateSchema]),\n    callback\n);\n```\n\n---------------------------------------\n\n<a name=\"applyEachSeries\" />\n### applyEachSeries(arr, iterator, callback)\n\nThe same as [`applyEach`](#applyEach) only the functions are applied in series.\n\n---------------------------------------\n\n<a name=\"queue\" />\n### queue(worker, concurrency)\n\nCreates a `queue` object with the specified `concurrency`. Tasks added to the\n`queue` are processed in parallel (up to the `concurrency` limit). If all\n`worker`s are in progress, the task is queued until one becomes available. \nOnce a `worker` completes a `task`, that `task`'s callback is called.\n\n__Arguments__\n\n* `worker(task, callback)` - An asynchronous function for processing a queued\n  task, which must call its `callback(err)` argument when finished, with an \n  optional `error` as an argument.\n* `concurrency` - An `integer` for determining how many `worker` functions should be\n  run in parallel.\n\n__Queue objects__\n\nThe `queue` object returned by this function has the following properties and\nmethods:\n\n* `length()` - a function returning the number of items waiting to be processed.\n* `started` - a function returning whether or not any items have been pushed and processed by the queue\n* `running()` - a function returning the number of items currently being processed.\n* `idle()` - a function returning false if there are items waiting or being processed, or true if not.\n* `concurrency` - an integer for determining how many `worker` functions should be\n  run in parallel. This property can be changed after a `queue` is created to\n  alter the concurrency on-the-fly.\n* `push(task, [callback])` - add a new task to the `queue`. Calls `callback` once \n  the `worker` has finished processing the task. Instead of a single task, a `tasks` array\n  can be submitted. The respective callback is used for every task in the list.\n* `unshift(task, [callback])` - add a new task to the front of the `queue`.\n* `saturated` - a callback that is called when the `queue` length hits the `concurrency` limit, \n   and further tasks will be queued.\n* `empty` - a callback that is called when the last item from the `queue` is given to a `worker`.\n* `drain` - a callback that is called when the last item from the `queue` has returned from the `worker`.\n* `paused` - a boolean for determining whether the queue is in a paused state\n* `pause()` - a function that pauses the processing of tasks until `resume()` is called.\n* `resume()` - a function that resumes the processing of queued tasks when the queue is paused.\n* `kill()` - a function that empties remaining tasks from the queue forcing it to go idle.\n\n__Example__\n\n```js\n// create a queue object with concurrency 2\n\nvar q = async.queue(function (task, callback) {\n    console.log('hello ' + task.name);\n    callback();\n}, 2);\n\n\n// assign a callback\nq.drain = function() {\n    console.log('all items have been processed');\n}\n\n// add some items to the queue\n\nq.push({name: 'foo'}, function (err) {\n    console.log('finished processing foo');\n});\nq.push({name: 'bar'}, function (err) {\n    console.log('finished processing bar');\n});\n\n// add some items to the queue (batch-wise)\n\nq.push([{name: 'baz'},{name: 'bay'},{name: 'bax'}], function (err) {\n    console.log('finished processing bar');\n});\n\n// add some items to the front of the queue\n\nq.unshift({name: 'bar'}, function (err) {\n    console.log('finished processing bar');\n});\n```\n\n\n---------------------------------------\n\n<a name=\"priorityQueue\" />\n### priorityQueue(worker, concurrency)\n\nThe same as [`queue`](#queue) only tasks are assigned a priority and completed in ascending priority order. There are two differences between `queue` and `priorityQueue` objects:\n\n* `push(task, priority, [callback])` - `priority` should be a number. If an array of\n  `tasks` is given, all tasks will be assigned the same priority.\n* The `unshift` method was removed.\n\n---------------------------------------\n\n<a name=\"cargo\" />\n### cargo(worker, [payload])\n\nCreates a `cargo` object with the specified payload. Tasks added to the\ncargo will be processed altogether (up to the `payload` limit). If the\n`worker` is in progress, the task is queued until it becomes available. Once\nthe `worker` has completed some tasks, each callback of those tasks is called.\nCheck out [this animation](https://camo.githubusercontent.com/6bbd36f4cf5b35a0f11a96dcd2e97711ffc2fb37/68747470733a2f2f662e636c6f75642e6769746875622e636f6d2f6173736574732f313637363837312f36383130382f62626330636662302d356632392d313165322d393734662d3333393763363464633835382e676966) for how `cargo` and `queue` work.\n\nWhile [queue](#queue) passes only one task to one of a group of workers\nat a time, cargo passes an array of tasks to a single worker, repeating\nwhen the worker is finished.\n\n__Arguments__\n\n* `worker(tasks, callback)` - An asynchronous function for processing an array of\n  queued tasks, which must call its `callback(err)` argument when finished, with \n  an optional `err` argument.\n* `payload` - An optional `integer` for determining how many tasks should be\n  processed per round; if omitted, the default is unlimited.\n\n__Cargo objects__\n\nThe `cargo` object returned by this function has the following properties and\nmethods:\n\n* `length()` - A function returning the number of items waiting to be processed.\n* `payload` - An `integer` for determining how many tasks should be\n  process per round. This property can be changed after a `cargo` is created to\n  alter the payload on-the-fly.\n* `push(task, [callback])` - Adds `task` to the `queue`. The callback is called\n  once the `worker` has finished processing the task. Instead of a single task, an array of `tasks` \n  can be submitted. The respective callback is used for every task in the list.\n* `saturated` - A callback that is called when the `queue.length()` hits the concurrency and further tasks will be queued.\n* `empty` - A callback that is called when the last item from the `queue` is given to a `worker`.\n* `drain` - A callback that is called when the last item from the `queue` has returned from the `worker`.\n\n__Example__\n\n```js\n// create a cargo object with payload 2\n\nvar cargo = async.cargo(function (tasks, callback) {\n    for(var i=0; i<tasks.length; i++){\n      console.log('hello ' + tasks[i].name);\n    }\n    callback();\n}, 2);\n\n\n// add some items\n\ncargo.push({name: 'foo'}, function (err) {\n    console.log('finished processing foo');\n});\ncargo.push({name: 'bar'}, function (err) {\n    console.log('finished processing bar');\n});\ncargo.push({name: 'baz'}, function (err) {\n    console.log('finished processing baz');\n});\n```\n\n---------------------------------------\n\n<a name=\"auto\" />\n### auto(tasks, [callback])\n\nDetermines the best order for running the functions in `tasks`, based on their \nrequirements. Each function can optionally depend on other functions being completed \nfirst, and each function is run as soon as its requirements are satisfied. \n\nIf any of the functions pass an error to their callback, it will not \ncomplete (so any other functions depending on it will not run), and the main \n`callback` is immediately called with the error. Functions also receive an \nobject containing the results of functions which have completed so far.\n\nNote, all functions are called with a `results` object as a second argument, \nso it is unsafe to pass functions in the `tasks` object which cannot handle the\nextra argument. \n\nFor example, this snippet of code:\n\n```js\nasync.auto({\n  readData: async.apply(fs.readFile, 'data.txt', 'utf-8')\n}, callback);\n```\n\nwill have the effect of calling `readFile` with the results object as the last\nargument, which will fail:\n\n```js\nfs.readFile('data.txt', 'utf-8', cb, {});\n```\n\nInstead, wrap the call to `readFile` in a function which does not forward the \n`results` object:\n\n```js\nasync.auto({\n  readData: function(cb, results){\n    fs.readFile('data.txt', 'utf-8', cb);\n  }\n}, callback);\n```\n\n__Arguments__\n\n* `tasks` - An object. Each of its properties is either a function or an array of\n  requirements, with the function itself the last item in the array. The object's key\n  of a property serves as the name of the task defined by that property,\n  i.e. can be used when specifying requirements for other tasks.\n  The function receives two arguments: (1) a `callback(err, result)` which must be \n  called when finished, passing an `error` (which can be `null`) and the result of \n  the function's execution, and (2) a `results` object, containing the results of\n  the previously executed functions.\n* `callback(err, results)` - An optional callback which is called when all the\n  tasks have been completed. It receives the `err` argument if any `tasks` \n  pass an error to their callback. Results are always returned; however, if \n  an error occurs, no further `tasks` will be performed, and the results\n  object will only contain partial results.\n\n\n__Example__\n\n```js\nasync.auto({\n    get_data: function(callback){\n        console.log('in get_data');\n        // async code to get some data\n        callback(null, 'data', 'converted to array');\n    },\n    make_folder: function(callback){\n        console.log('in make_folder');\n        // async code to create a directory to store a file in\n        // this is run at the same time as getting the data\n        callback(null, 'folder');\n    },\n    write_file: ['get_data', 'make_folder', function(callback, results){\n        console.log('in write_file', JSON.stringify(results));\n        // once there is some data and the directory exists,\n        // write the data to a file in the directory\n        callback(null, 'filename');\n    }],\n    email_link: ['write_file', function(callback, results){\n        console.log('in email_link', JSON.stringify(results));\n        // once the file is written let's email a link to it...\n        // results.write_file contains the filename returned by write_file.\n        callback(null, {'file':results.write_file, 'email':'user@example.com'});\n    }]\n}, function(err, results) {\n    console.log('err = ', err);\n    console.log('results = ', results);\n});\n```\n\nThis is a fairly trivial example, but to do this using the basic parallel and\nseries functions would look like this:\n\n```js\nasync.parallel([\n    function(callback){\n        console.log('in get_data');\n        // async code to get some data\n        callback(null, 'data', 'converted to array');\n    },\n    function(callback){\n        console.log('in make_folder');\n        // async code to create a directory to store a file in\n        // this is run at the same time as getting the data\n        callback(null, 'folder');\n    }\n],\nfunction(err, results){\n    async.series([\n        function(callback){\n            console.log('in write_file', JSON.stringify(results));\n            // once there is some data and the directory exists,\n            // write the data to a file in the directory\n            results.push('filename');\n            callback(null);\n        },\n        function(callback){\n            console.log('in email_link', JSON.stringify(results));\n            // once the file is written let's email a link to it...\n            callback(null, {'file':results.pop(), 'email':'user@example.com'});\n        }\n    ]);\n});\n```\n\nFor a complicated series of `async` tasks, using the [`auto`](#auto) function makes adding\nnew tasks much easier (and the code more readable).\n\n\n---------------------------------------\n\n<a name=\"retry\" />\n### retry([times = 5], task, [callback])\n\nAttempts to get a successful response from `task` no more than `times` times before\nreturning an error. If the task is successful, the `callback` will be passed the result\nof the successfull task. If all attemps fail, the callback will be passed the error and\nresult (if any) of the final attempt.\n\n__Arguments__\n\n* `times` - An integer indicating how many times to attempt the `task` before giving up. Defaults to 5.\n* `task(callback, results)` - A function which receives two arguments: (1) a `callback(err, result)`\n  which must be called when finished, passing `err` (which can be `null`) and the `result` of \n  the function's execution, and (2) a `results` object, containing the results of\n  the previously executed functions (if nested inside another control flow).\n* `callback(err, results)` - An optional callback which is called when the\n  task has succeeded, or after the final failed attempt. It receives the `err` and `result` arguments of the last attempt at completing the `task`.\n\nThe [`retry`](#retry) function can be used as a stand-alone control flow by passing a\ncallback, as shown below:\n\n```js\nasync.retry(3, apiMethod, function(err, result) {\n    // do something with the result\n});\n```\n\nIt can also be embeded within other control flow functions to retry individual methods\nthat are not as reliable, like this:\n\n```js\nasync.auto({\n    users: api.getUsers.bind(api),\n    payments: async.retry(3, api.getPayments.bind(api))\n}, function(err, results) {\n  // do something with the results\n});\n```\n\n\n---------------------------------------\n\n<a name=\"iterator\" />\n### iterator(tasks)\n\nCreates an iterator function which calls the next function in the `tasks` array,\nreturning a continuation to call the next one after that. It's also possible to\n“peek” at the next iterator with `iterator.next()`.\n\nThis function is used internally by the `async` module, but can be useful when\nyou want to manually control the flow of functions in series.\n\n__Arguments__\n\n* `tasks` - An array of functions to run.\n\n__Example__\n\n```js\nvar iterator = async.iterator([\n    function(){ sys.p('one'); },\n    function(){ sys.p('two'); },\n    function(){ sys.p('three'); }\n]);\n\nnode> var iterator2 = iterator();\n'one'\nnode> var iterator3 = iterator2();\n'two'\nnode> iterator3();\n'three'\nnode> var nextfn = iterator2.next();\nnode> nextfn();\n'three'\n```\n\n---------------------------------------\n\n<a name=\"apply\" />\n### apply(function, arguments..)\n\nCreates a continuation function with some arguments already applied. \n\nUseful as a shorthand when combined with other control flow functions. Any arguments\npassed to the returned function are added to the arguments originally passed\nto apply.\n\n__Arguments__\n\n* `function` - The function you want to eventually apply all arguments to.\n* `arguments...` - Any number of arguments to automatically apply when the\n  continuation is called.\n\n__Example__\n\n```js\n// using apply\n\nasync.parallel([\n    async.apply(fs.writeFile, 'testfile1', 'test1'),\n    async.apply(fs.writeFile, 'testfile2', 'test2'),\n]);\n\n\n// the same process without using apply\n\nasync.parallel([\n    function(callback){\n        fs.writeFile('testfile1', 'test1', callback);\n    },\n    function(callback){\n        fs.writeFile('testfile2', 'test2', callback);\n    }\n]);\n```\n\nIt's possible to pass any number of additional arguments when calling the\ncontinuation:\n\n```js\nnode> var fn = async.apply(sys.puts, 'one');\nnode> fn('two', 'three');\none\ntwo\nthree\n```\n\n---------------------------------------\n\n<a name=\"nextTick\" />\n### nextTick(callback)\n\nCalls `callback` on a later loop around the event loop. In Node.js this just\ncalls `process.nextTick`; in the browser it falls back to `setImmediate(callback)`\nif available, otherwise `setTimeout(callback, 0)`, which means other higher priority\nevents may precede the execution of `callback`.\n\nThis is used internally for browser-compatibility purposes.\n\n__Arguments__\n\n* `callback` - The function to call on a later loop around the event loop.\n\n__Example__\n\n```js\nvar call_order = [];\nasync.nextTick(function(){\n    call_order.push('two');\n    // call_order now equals ['one','two']\n});\ncall_order.push('one')\n```\n\n<a name=\"times\" />\n### times(n, callback)\n\nCalls the `callback` function `n` times, and accumulates results in the same manner\nyou would use with [`map`](#map).\n\n__Arguments__\n\n* `n` - The number of times to run the function.\n* `callback` - The function to call `n` times.\n\n__Example__\n\n```js\n// Pretend this is some complicated async factory\nvar createUser = function(id, callback) {\n  callback(null, {\n    id: 'user' + id\n  })\n}\n// generate 5 users\nasync.times(5, function(n, next){\n    createUser(n, function(err, user) {\n      next(err, user)\n    })\n}, function(err, users) {\n  // we should now have 5 users\n});\n```\n\n<a name=\"timesSeries\" />\n### timesSeries(n, callback)\n\nThe same as [`times`](#times), only the iterator is applied to each item in `arr` in\nseries. The next `iterator` is only called once the current one has completed. \nThe results array will be in the same order as the original.\n\n\n## Utils\n\n<a name=\"memoize\" />\n### memoize(fn, [hasher])\n\nCaches the results of an `async` function. When creating a hash to store function\nresults against, the callback is omitted from the hash and an optional hash\nfunction can be used.\n\nThe cache of results is exposed as the `memo` property of the function returned\nby `memoize`.\n\n__Arguments__\n\n* `fn` - The function to proxy and cache results from.\n* `hasher` - Tn optional function for generating a custom hash for storing\n  results. It has all the arguments applied to it apart from the callback, and\n  must be synchronous.\n\n__Example__\n\n```js\nvar slow_fn = function (name, callback) {\n    // do something\n    callback(null, result);\n};\nvar fn = async.memoize(slow_fn);\n\n// fn can now be used as if it were slow_fn\nfn('some name', function () {\n    // callback\n});\n```\n\n<a name=\"unmemoize\" />\n### unmemoize(fn)\n\nUndoes a [`memoize`](#memoize)d function, reverting it to the original, unmemoized\nform. Handy for testing.\n\n__Arguments__\n\n* `fn` - the memoized function\n\n<a name=\"log\" />\n### log(function, arguments)\n\nLogs the result of an `async` function to the `console`. Only works in Node.js or\nin browsers that support `console.log` and `console.error` (such as FF and Chrome).\nIf multiple arguments are returned from the async function, `console.log` is\ncalled on each argument in order.\n\n__Arguments__\n\n* `function` - The function you want to eventually apply all arguments to.\n* `arguments...` - Any number of arguments to apply to the function.\n\n__Example__\n\n```js\nvar hello = function(name, callback){\n    setTimeout(function(){\n        callback(null, 'hello ' + name);\n    }, 1000);\n};\n```\n```js\nnode> async.log(hello, 'world');\n'hello world'\n```\n\n---------------------------------------\n\n<a name=\"dir\" />\n### dir(function, arguments)\n\nLogs the result of an `async` function to the `console` using `console.dir` to\ndisplay the properties of the resulting object. Only works in Node.js or\nin browsers that support `console.dir` and `console.error` (such as FF and Chrome).\nIf multiple arguments are returned from the async function, `console.dir` is\ncalled on each argument in order.\n\n__Arguments__\n\n* `function` - The function you want to eventually apply all arguments to.\n* `arguments...` - Any number of arguments to apply to the function.\n\n__Example__\n\n```js\nvar hello = function(name, callback){\n    setTimeout(function(){\n        callback(null, {hello: name});\n    }, 1000);\n};\n```\n```js\nnode> async.dir(hello, 'world');\n{hello: 'world'}\n```\n\n---------------------------------------\n\n<a name=\"noConflict\" />\n### noConflict()\n\nChanges the value of `async` back to its original value, returning a reference to the\n`async` object.\n",
+  "readmeFilename": "README.md",
+  "homepage": "https://github.com/caolan/async",
+  "_id": "async@0.9.0",
+  "_from": "async@~0.9.0"
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/buffer-crc32/.npmignore b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/buffer-crc32/.npmignore
new file mode 100644
index 0000000..b512c09
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/buffer-crc32/.npmignore
@@ -0,0 +1 @@
+node_modules
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/buffer-crc32/.travis.yml b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/buffer-crc32/.travis.yml
new file mode 100644
index 0000000..7a902e8
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/buffer-crc32/.travis.yml
@@ -0,0 +1,8 @@
+language: node_js
+node_js:
+  - 0.6
+  - 0.8
+notifications:
+  email:
+    recipients:
+      - brianloveswords@gmail.com
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/buffer-crc32/LICENSE b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/buffer-crc32/LICENSE
new file mode 100644
index 0000000..4cef10e
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/buffer-crc32/LICENSE
@@ -0,0 +1,19 @@
+The MIT License
+
+Copyright (c) 2013 Brian J. Brennan
+
+Permission is hereby granted, free of charge, to any person obtaining a copy 
+of this software and associated documentation files (the "Software"), to deal in 
+the Software without restriction, including without limitation the rights to use, 
+copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the 
+Software, and to permit persons to whom the Software is furnished to do so, 
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all 
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
+INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 
+PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/buffer-crc32/README.md b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/buffer-crc32/README.md
new file mode 100644
index 0000000..0d9d8b8
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/buffer-crc32/README.md
@@ -0,0 +1,47 @@
+# buffer-crc32
+
+[![Build Status](https://secure.travis-ci.org/brianloveswords/buffer-crc32.png?branch=master)](http://travis-ci.org/brianloveswords/buffer-crc32)
+
+crc32 that works with binary data and fancy character sets, outputs
+buffer, signed or unsigned data and has tests.
+
+Derived from the sample CRC implementation in the PNG specification: http://www.w3.org/TR/PNG/#D-CRCAppendix
+
+# install
+```
+npm install buffer-crc32
+```
+
+# example
+```js
+var crc32 = require('buffer-crc32');
+// works with buffers
+var buf = Buffer([0x00, 0x73, 0x75, 0x70, 0x20, 0x62, 0x72, 0x6f, 0x00])
+crc32(buf) // -> <Buffer 94 5a ab 4a>
+
+// has convenience methods for getting signed or unsigned ints
+crc32.signed(buf) // -> -1805997238
+crc32.unsigned(buf) // -> 2488970058
+
+// will cast to buffer if given a string, so you can
+// directly use foreign characters safely
+crc32('自動販売機') // -> <Buffer cb 03 1a c5>
+
+// and works in append mode too
+var partialCrc = crc32('hey');
+var partialCrc = crc32(' ', partialCrc);
+var partialCrc = crc32('sup', partialCrc);
+var partialCrc = crc32(' ', partialCrc);
+var finalCrc = crc32('bros', partialCrc); // -> <Buffer 47 fa 55 70>
+```
+
+# tests
+This was tested against the output of zlib's crc32 method. You can run
+the tests with`npm test` (requires tap)
+
+# see also
+https://github.com/alexgorbatchev/node-crc, `crc.buffer.crc32` also
+supports buffer inputs and return unsigned ints (thanks @tjholowaychuk).
+
+# license
+MIT/X11
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/buffer-crc32/index.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/buffer-crc32/index.js
new file mode 100644
index 0000000..8694c63
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/buffer-crc32/index.js
@@ -0,0 +1,91 @@
+var Buffer = require('buffer').Buffer;
+
+var CRC_TABLE = [
+  0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419,
+  0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4,
+  0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07,
+  0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
+  0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856,
+  0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
+  0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4,
+  0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
+  0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3,
+  0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a,
+  0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599,
+  0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
+  0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190,
+  0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f,
+  0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e,
+  0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
+  0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed,
+  0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
+  0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3,
+  0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
+  0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a,
+  0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5,
+  0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010,
+  0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
+  0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17,
+  0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6,
+  0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615,
+  0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
+  0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344,
+  0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
+  0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a,
+  0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
+  0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1,
+  0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c,
+  0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef,
+  0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
+  0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe,
+  0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31,
+  0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c,
+  0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
+  0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b,
+  0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
+  0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1,
+  0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
+  0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278,
+  0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7,
+  0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66,
+  0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
+  0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605,
+  0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8,
+  0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b,
+  0x2d02ef8d
+];
+
+if (typeof Int32Array !== 'undefined')
+  CRC_TABLE = new Int32Array(CRC_TABLE);
+
+function bufferizeInt(num) {
+  var tmp = Buffer(4);
+  tmp.writeInt32BE(num, 0);
+  return tmp;
+}
+
+function _crc32(buf, previous) {
+  if (!Buffer.isBuffer(buf)) {
+    buf = Buffer(buf);
+  }
+  if (Buffer.isBuffer(previous)) {
+    previous = previous.readUInt32BE(0);
+  }
+  var crc = ~~previous ^ -1;
+  for (var n = 0; n < buf.length; n++) {
+    crc = CRC_TABLE[(crc ^ buf[n]) & 0xff] ^ (crc >>> 8);
+  }
+  return (crc ^ -1);
+}
+
+function crc32() {
+  return bufferizeInt(_crc32.apply(null, arguments));
+}
+crc32.signed = function () {
+  return _crc32.apply(null, arguments);
+};
+crc32.unsigned = function () {
+  return _crc32.apply(null, arguments) >>> 0;
+};
+
+module.exports = crc32;
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/buffer-crc32/package.json b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/buffer-crc32/package.json
new file mode 100644
index 0000000..7dc1e3c
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/buffer-crc32/package.json
@@ -0,0 +1,65 @@
+{
+  "author": {
+    "name": "Brian J. Brennan",
+    "email": "brianloveswords@gmail.com",
+    "url": "http://bjb.io"
+  },
+  "name": "buffer-crc32",
+  "description": "A pure javascript CRC32 algorithm that plays nice with binary data",
+  "version": "0.2.5",
+  "licenses": [
+    {
+      "type": "MIT",
+      "url": "https://github.com/brianloveswords/buffer-crc32/raw/master/LICENSE"
+    }
+  ],
+  "contributors": [
+    {
+      "name": "Vladimir Kuznetsov"
+    }
+  ],
+  "homepage": "https://github.com/brianloveswords/buffer-crc32",
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/brianloveswords/buffer-crc32.git"
+  },
+  "main": "index.js",
+  "scripts": {
+    "test": "tap tests/*.test.js"
+  },
+  "dependencies": {},
+  "devDependencies": {
+    "tap": "~0.2.5"
+  },
+  "optionalDependencies": {},
+  "engines": {
+    "node": "*"
+  },
+  "license": "MIT",
+  "gitHead": "beb976670f2ea6414e4cce4764d0213e5f9d7cbc",
+  "bugs": {
+    "url": "https://github.com/brianloveswords/buffer-crc32/issues"
+  },
+  "_id": "buffer-crc32@0.2.5",
+  "_shasum": "db003ac2671e62ebd6ece78ea2c2e1b405736e91",
+  "_from": "buffer-crc32@~0.2.1",
+  "_npmVersion": "2.1.11",
+  "_nodeVersion": "0.10.33",
+  "_npmUser": {
+    "name": "brianloveswords",
+    "email": "brianloveswords@gmail.com"
+  },
+  "maintainers": [
+    {
+      "name": "brianloveswords",
+      "email": "brian@nyhacker.org"
+    }
+  ],
+  "dist": {
+    "shasum": "db003ac2671e62ebd6ece78ea2c2e1b405736e91",
+    "tarball": "http://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.5.tgz"
+  },
+  "directories": {},
+  "_resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.5.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/buffer-crc32/tests/crc.test.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/buffer-crc32/tests/crc.test.js
new file mode 100644
index 0000000..bb0f9ef
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/buffer-crc32/tests/crc.test.js
@@ -0,0 +1,89 @@
+var crc32 = require('..');
+var test = require('tap').test;
+
+test('simple crc32 is no problem', function (t) {
+  var input = Buffer('hey sup bros');
+  var expected = Buffer([0x47, 0xfa, 0x55, 0x70]);
+  t.same(crc32(input), expected);
+  t.end();
+});
+
+test('another simple one', function (t) {
+  var input = Buffer('IEND');
+  var expected = Buffer([0xae, 0x42, 0x60, 0x82]);
+  t.same(crc32(input), expected);
+  t.end();
+});
+
+test('slightly more complex', function (t) {
+  var input = Buffer([0x00, 0x00, 0x00]);
+  var expected = Buffer([0xff, 0x41, 0xd9, 0x12]);
+  t.same(crc32(input), expected);
+  t.end();
+});
+
+test('complex crc32 gets calculated like a champ', function (t) {
+  var input = Buffer('शीर्षक');
+  var expected = Buffer([0x17, 0xb8, 0xaf, 0xf1]);
+  t.same(crc32(input), expected);
+  t.end();
+});
+
+test('casts to buffer if necessary', function (t) {
+  var input = 'शीर्षक';
+  var expected = Buffer([0x17, 0xb8, 0xaf, 0xf1]);
+  t.same(crc32(input), expected);
+  t.end();
+});
+
+test('can do signed', function (t) {
+  var input = 'ham sandwich';
+  var expected = -1891873021;
+  t.same(crc32.signed(input), expected);
+  t.end();
+});
+
+test('can do unsigned', function (t) {
+  var input = 'bear sandwich';
+  var expected = 3711466352;
+  t.same(crc32.unsigned(input), expected);
+  t.end();
+});
+
+
+test('simple crc32 in append mode', function (t) {
+  var input = [Buffer('hey'), Buffer(' '), Buffer('sup'), Buffer(' '), Buffer('bros')];
+  var expected = Buffer([0x47, 0xfa, 0x55, 0x70]);
+  for (var crc = 0, i = 0; i < input.length; i++) {
+    crc = crc32(input[i], crc);
+  }
+  t.same(crc, expected);
+  t.end();
+});
+
+
+test('can do signed in append mode', function (t) {
+  var input1 = 'ham';
+  var input2 = ' ';
+  var input3 = 'sandwich';
+  var expected = -1891873021;
+
+  var crc = crc32.signed(input1);
+  crc = crc32.signed(input2, crc);
+  crc = crc32.signed(input3, crc);
+
+  t.same(crc, expected);
+  t.end();
+});
+
+test('can do unsigned in append mode', function (t) {
+  var input1 = 'bear san';
+  var input2 = 'dwich';
+  var expected = 3711466352;
+
+  var crc = crc32.unsigned(input1);
+  crc = crc32.unsigned(input2, crc);
+  t.same(crc, expected);
+  t.end();
+});
+
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/.npmignore b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/.npmignore
new file mode 100644
index 0000000..2af4b71
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/.npmignore
@@ -0,0 +1,2 @@
+.*.swp
+test/a/
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/.travis.yml b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/.travis.yml
new file mode 100644
index 0000000..baa0031
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/.travis.yml
@@ -0,0 +1,3 @@
+language: node_js
+node_js:
+  - 0.8
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/LICENSE b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/LICENSE
new file mode 100644
index 0000000..0c44ae7
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) Isaac Z. Schlueter ("Author")
+All rights reserved.
+
+The BSD License
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/README.md b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/README.md
new file mode 100644
index 0000000..cc69164
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/README.md
@@ -0,0 +1,250 @@
+# Glob
+
+Match files using the patterns the shell uses, like stars and stuff.
+
+This is a glob implementation in JavaScript.  It uses the `minimatch`
+library to do its matching.
+
+## Attention: node-glob users!
+
+The API has changed dramatically between 2.x and 3.x. This library is
+now 100% JavaScript, and the integer flags have been replaced with an
+options object.
+
+Also, there's an event emitter class, proper tests, and all the other
+things you've come to expect from node modules.
+
+And best of all, no compilation!
+
+## Usage
+
+```javascript
+var glob = require("glob")
+
+// options is optional
+glob("**/*.js", options, function (er, files) {
+  // files is an array of filenames.
+  // If the `nonull` option is set, and nothing
+  // was found, then files is ["**/*.js"]
+  // er is an error object or null.
+})
+```
+
+## Features
+
+Please see the [minimatch
+documentation](https://github.com/isaacs/minimatch) for more details.
+
+Supports these glob features:
+
+* Brace Expansion
+* Extended glob matching
+* "Globstar" `**` matching
+
+See:
+
+* `man sh`
+* `man bash`
+* `man 3 fnmatch`
+* `man 5 gitignore`
+* [minimatch documentation](https://github.com/isaacs/minimatch)
+
+## glob(pattern, [options], cb)
+
+* `pattern` {String} Pattern to be matched
+* `options` {Object}
+* `cb` {Function}
+  * `err` {Error | null}
+  * `matches` {Array<String>} filenames found matching the pattern
+
+Perform an asynchronous glob search.
+
+## glob.sync(pattern, [options])
+
+* `pattern` {String} Pattern to be matched
+* `options` {Object}
+* return: {Array<String>} filenames found matching the pattern
+
+Perform a synchronous glob search.
+
+## Class: glob.Glob
+
+Create a Glob object by instanting the `glob.Glob` class.
+
+```javascript
+var Glob = require("glob").Glob
+var mg = new Glob(pattern, options, cb)
+```
+
+It's an EventEmitter, and starts walking the filesystem to find matches
+immediately.
+
+### new glob.Glob(pattern, [options], [cb])
+
+* `pattern` {String} pattern to search for
+* `options` {Object}
+* `cb` {Function} Called when an error occurs, or matches are found
+  * `err` {Error | null}
+  * `matches` {Array<String>} filenames found matching the pattern
+
+Note that if the `sync` flag is set in the options, then matches will
+be immediately available on the `g.found` member.
+
+### Properties
+
+* `minimatch` The minimatch object that the glob uses.
+* `options` The options object passed in.
+* `error` The error encountered.  When an error is encountered, the
+  glob object is in an undefined state, and should be discarded.
+* `aborted` Boolean which is set to true when calling `abort()`.  There
+  is no way at this time to continue a glob search after aborting, but
+  you can re-use the statCache to avoid having to duplicate syscalls.
+* `statCache` Collection of all the stat results the glob search
+  performed.
+* `cache` Convenience object.  Each field has the following possible
+  values:
+  * `false` - Path does not exist
+  * `true` - Path exists
+  * `1` - Path exists, and is not a directory
+  * `2` - Path exists, and is a directory
+  * `[file, entries, ...]` - Path exists, is a directory, and the
+    array value is the results of `fs.readdir`
+
+### Events
+
+* `end` When the matching is finished, this is emitted with all the
+  matches found.  If the `nonull` option is set, and no match was found,
+  then the `matches` list contains the original pattern.  The matches
+  are sorted, unless the `nosort` flag is set.
+* `match` Every time a match is found, this is emitted with the matched.
+* `error` Emitted when an unexpected error is encountered, or whenever
+  any fs error occurs if `options.strict` is set.
+* `abort` When `abort()` is called, this event is raised.
+
+### Methods
+
+* `abort` Stop the search.
+
+### Options
+
+All the options that can be passed to Minimatch can also be passed to
+Glob to change pattern matching behavior.  Also, some have been added,
+or have glob-specific ramifications.
+
+All options are false by default, unless otherwise noted.
+
+All options are added to the glob object, as well.
+
+* `cwd` The current working directory in which to search.  Defaults
+  to `process.cwd()`.
+* `root` The place where patterns starting with `/` will be mounted
+  onto.  Defaults to `path.resolve(options.cwd, "/")` (`/` on Unix
+  systems, and `C:\` or some such on Windows.)
+* `dot` Include `.dot` files in normal matches and `globstar` matches.
+  Note that an explicit dot in a portion of the pattern will always
+  match dot files.
+* `nomount` By default, a pattern starting with a forward-slash will be
+  "mounted" onto the root setting, so that a valid filesystem path is
+  returned.  Set this flag to disable that behavior.
+* `mark` Add a `/` character to directory matches.  Note that this
+  requires additional stat calls.
+* `nosort` Don't sort the results.
+* `stat` Set to true to stat *all* results.  This reduces performance
+  somewhat, and is completely unnecessary, unless `readdir` is presumed
+  to be an untrustworthy indicator of file existence.  It will cause
+  ELOOP to be triggered one level sooner in the case of cyclical
+  symbolic links.
+* `silent` When an unusual error is encountered
+  when attempting to read a directory, a warning will be printed to
+  stderr.  Set the `silent` option to true to suppress these warnings.
+* `strict` When an unusual error is encountered
+  when attempting to read a directory, the process will just continue on
+  in search of other matches.  Set the `strict` option to raise an error
+  in these cases.
+* `cache` See `cache` property above.  Pass in a previously generated
+  cache object to save some fs calls.
+* `statCache` A cache of results of filesystem information, to prevent
+  unnecessary stat calls.  While it should not normally be necessary to
+  set this, you may pass the statCache from one glob() call to the
+  options object of another, if you know that the filesystem will not
+  change between calls.  (See "Race Conditions" below.)
+* `sync` Perform a synchronous glob search.
+* `nounique` In some cases, brace-expanded patterns can result in the
+  same file showing up multiple times in the result set.  By default,
+  this implementation prevents duplicates in the result set.
+  Set this flag to disable that behavior.
+* `nonull` Set to never return an empty set, instead returning a set
+  containing the pattern itself.  This is the default in glob(3).
+* `nocase` Perform a case-insensitive match.  Note that case-insensitive
+  filesystems will sometimes result in glob returning results that are
+  case-insensitively matched anyway, since readdir and stat will not
+  raise an error.
+* `debug` Set to enable debug logging in minimatch and glob.
+* `globDebug` Set to enable debug logging in glob, but not minimatch.
+
+## Comparisons to other fnmatch/glob implementations
+
+While strict compliance with the existing standards is a worthwhile
+goal, some discrepancies exist between node-glob and other
+implementations, and are intentional.
+
+If the pattern starts with a `!` character, then it is negated.  Set the
+`nonegate` flag to suppress this behavior, and treat leading `!`
+characters normally.  This is perhaps relevant if you wish to start the
+pattern with a negative extglob pattern like `!(a|B)`.  Multiple `!`
+characters at the start of a pattern will negate the pattern multiple
+times.
+
+If a pattern starts with `#`, then it is treated as a comment, and
+will not match anything.  Use `\#` to match a literal `#` at the
+start of a line, or set the `nocomment` flag to suppress this behavior.
+
+The double-star character `**` is supported by default, unless the
+`noglobstar` flag is set.  This is supported in the manner of bsdglob
+and bash 4.1, where `**` only has special significance if it is the only
+thing in a path part.  That is, `a/**/b` will match `a/x/y/b`, but
+`a/**b` will not.
+
+If an escaped pattern has no matches, and the `nonull` flag is set,
+then glob returns the pattern as-provided, rather than
+interpreting the character escapes.  For example,
+`glob.match([], "\\*a\\?")` will return `"\\*a\\?"` rather than
+`"*a?"`.  This is akin to setting the `nullglob` option in bash, except
+that it does not resolve escaped pattern characters.
+
+If brace expansion is not disabled, then it is performed before any
+other interpretation of the glob pattern.  Thus, a pattern like
+`+(a|{b),c)}`, which would not be valid in bash or zsh, is expanded
+**first** into the set of `+(a|b)` and `+(a|c)`, and those patterns are
+checked for validity.  Since those two are valid, matching proceeds.
+
+## Windows
+
+**Please only use forward-slashes in glob expressions.**
+
+Though windows uses either `/` or `\` as its path separator, only `/`
+characters are used by this glob implementation.  You must use
+forward-slashes **only** in glob expressions.  Back-slashes will always
+be interpreted as escape characters, not path separators.
+
+Results from absolute patterns such as `/foo/*` are mounted onto the
+root setting using `path.join`.  On windows, this will by default result
+in `/foo/*` matching `C:\foo\bar.txt`.
+
+## Race Conditions
+
+Glob searching, by its very nature, is susceptible to race conditions,
+since it relies on directory walking and such.
+
+As a result, it is possible that a file that exists when glob looks for
+it may have been deleted or modified by the time it returns the result.
+
+As part of its internal implementation, this program caches all stat
+and readdir calls that it makes, in order to cut down on system
+overhead.  However, this also makes it even more susceptible to races,
+especially if the cache or statCache objects are reused between glob
+calls.
+
+Users are thus advised not to use a glob result as a guarantee of
+filesystem state in the face of rapid changes.  For the vast majority
+of operations, this is never a problem.
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/examples/g.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/examples/g.js
new file mode 100644
index 0000000..be122df
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/examples/g.js
@@ -0,0 +1,9 @@
+var Glob = require("../").Glob
+
+var pattern = "test/a/**/[cg]/../[cg]"
+console.log(pattern)
+
+var mg = new Glob(pattern, {mark: true, sync:true}, function (er, matches) {
+  console.log("matches", matches)
+})
+console.log("after")
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/examples/usr-local.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/examples/usr-local.js
new file mode 100644
index 0000000..327a425
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/examples/usr-local.js
@@ -0,0 +1,9 @@
+var Glob = require("../").Glob
+
+var pattern = "{./*/*,/*,/usr/local/*}"
+console.log(pattern)
+
+var mg = new Glob(pattern, {mark: true}, function (er, matches) {
+  console.log("matches", matches)
+})
+console.log("after")
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/glob.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/glob.js
new file mode 100644
index 0000000..f646c44
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/glob.js
@@ -0,0 +1,728 @@
+// Approach:
+//
+// 1. Get the minimatch set
+// 2. For each pattern in the set, PROCESS(pattern)
+// 3. Store matches per-set, then uniq them
+//
+// PROCESS(pattern)
+// Get the first [n] items from pattern that are all strings
+// Join these together.  This is PREFIX.
+//   If there is no more remaining, then stat(PREFIX) and
+//   add to matches if it succeeds.  END.
+// readdir(PREFIX) as ENTRIES
+//   If fails, END
+//   If pattern[n] is GLOBSTAR
+//     // handle the case where the globstar match is empty
+//     // by pruning it out, and testing the resulting pattern
+//     PROCESS(pattern[0..n] + pattern[n+1 .. $])
+//     // handle other cases.
+//     for ENTRY in ENTRIES (not dotfiles)
+//       // attach globstar + tail onto the entry
+//       PROCESS(pattern[0..n] + ENTRY + pattern[n .. $])
+//
+//   else // not globstar
+//     for ENTRY in ENTRIES (not dotfiles, unless pattern[n] is dot)
+//       Test ENTRY against pattern[n]
+//       If fails, continue
+//       If passes, PROCESS(pattern[0..n] + item + pattern[n+1 .. $])
+//
+// Caveat:
+//   Cache all stats and readdirs results to minimize syscall.  Since all
+//   we ever care about is existence and directory-ness, we can just keep
+//   `true` for files, and [children,...] for directories, or `false` for
+//   things that don't exist.
+
+
+
+module.exports = glob
+
+var fs = require("fs")
+, minimatch = require("minimatch")
+, Minimatch = minimatch.Minimatch
+, inherits = require("inherits")
+, EE = require("events").EventEmitter
+, path = require("path")
+, isDir = {}
+, assert = require("assert").ok
+
+function glob (pattern, options, cb) {
+  if (typeof options === "function") cb = options, options = {}
+  if (!options) options = {}
+
+  if (typeof options === "number") {
+    deprecated()
+    return
+  }
+
+  var g = new Glob(pattern, options, cb)
+  return g.sync ? g.found : g
+}
+
+glob.fnmatch = deprecated
+
+function deprecated () {
+  throw new Error("glob's interface has changed. Please see the docs.")
+}
+
+glob.sync = globSync
+function globSync (pattern, options) {
+  if (typeof options === "number") {
+    deprecated()
+    return
+  }
+
+  options = options || {}
+  options.sync = true
+  return glob(pattern, options)
+}
+
+this._processingEmitQueue = false
+
+glob.Glob = Glob
+inherits(Glob, EE)
+function Glob (pattern, options, cb) {
+  if (!(this instanceof Glob)) {
+    return new Glob(pattern, options, cb)
+  }
+
+  if (typeof options === "function") {
+    cb = options
+    options = null
+  }
+
+  if (typeof cb === "function") {
+    this.on("error", cb)
+    this.on("end", function (matches) {
+      cb(null, matches)
+    })
+  }
+
+  options = options || {}
+
+  this._endEmitted = false
+  this.EOF = {}
+  this._emitQueue = []
+
+  this.paused = false
+  this._processingEmitQueue = false
+
+  this.maxDepth = options.maxDepth || 1000
+  this.maxLength = options.maxLength || Infinity
+  this.cache = options.cache || {}
+  this.statCache = options.statCache || {}
+
+  this.changedCwd = false
+  var cwd = process.cwd()
+  if (!options.hasOwnProperty("cwd")) this.cwd = cwd
+  else {
+    this.cwd = options.cwd
+    this.changedCwd = path.resolve(options.cwd) !== cwd
+  }
+
+  this.root = options.root || path.resolve(this.cwd, "/")
+  this.root = path.resolve(this.root)
+  if (process.platform === "win32")
+    this.root = this.root.replace(/\\/g, "/")
+
+  this.nomount = !!options.nomount
+
+  if (!pattern) {
+    throw new Error("must provide pattern")
+  }
+
+  // base-matching: just use globstar for that.
+  if (options.matchBase && -1 === pattern.indexOf("/")) {
+    if (options.noglobstar) {
+      throw new Error("base matching requires globstar")
+    }
+    pattern = "**/" + pattern
+  }
+
+  this.strict = options.strict !== false
+  this.dot = !!options.dot
+  this.mark = !!options.mark
+  this.sync = !!options.sync
+  this.nounique = !!options.nounique
+  this.nonull = !!options.nonull
+  this.nosort = !!options.nosort
+  this.nocase = !!options.nocase
+  this.stat = !!options.stat
+
+  this.debug = !!options.debug || !!options.globDebug
+  if (this.debug)
+    this.log = console.error
+
+  this.silent = !!options.silent
+
+  var mm = this.minimatch = new Minimatch(pattern, options)
+  this.options = mm.options
+  pattern = this.pattern = mm.pattern
+
+  this.error = null
+  this.aborted = false
+
+  // list of all the patterns that ** has resolved do, so
+  // we can avoid visiting multiple times.
+  this._globstars = {}
+
+  EE.call(this)
+
+  // process each pattern in the minimatch set
+  var n = this.minimatch.set.length
+
+  // The matches are stored as {<filename>: true,...} so that
+  // duplicates are automagically pruned.
+  // Later, we do an Object.keys() on these.
+  // Keep them as a list so we can fill in when nonull is set.
+  this.matches = new Array(n)
+
+  this.minimatch.set.forEach(iterator.bind(this))
+  function iterator (pattern, i, set) {
+    this._process(pattern, 0, i, function (er) {
+      if (er) this.emit("error", er)
+      if (-- n <= 0) this._finish()
+    })
+  }
+}
+
+Glob.prototype.log = function () {}
+
+Glob.prototype._finish = function () {
+  assert(this instanceof Glob)
+
+  var nou = this.nounique
+  , all = nou ? [] : {}
+
+  for (var i = 0, l = this.matches.length; i < l; i ++) {
+    var matches = this.matches[i]
+    this.log("matches[%d] =", i, matches)
+    // do like the shell, and spit out the literal glob
+    if (!matches) {
+      if (this.nonull) {
+        var literal = this.minimatch.globSet[i]
+        if (nou) all.push(literal)
+        else all[literal] = true
+      }
+    } else {
+      // had matches
+      var m = Object.keys(matches)
+      if (nou) all.push.apply(all, m)
+      else m.forEach(function (m) {
+        all[m] = true
+      })
+    }
+  }
+
+  if (!nou) all = Object.keys(all)
+
+  if (!this.nosort) {
+    all = all.sort(this.nocase ? alphasorti : alphasort)
+  }
+
+  if (this.mark) {
+    // at *some* point we statted all of these
+    all = all.map(this._mark, this)
+  }
+
+  this.log("emitting end", all)
+
+  this.EOF = this.found = all
+  this.emitMatch(this.EOF)
+}
+
+function alphasorti (a, b) {
+  a = a.toLowerCase()
+  b = b.toLowerCase()
+  return alphasort(a, b)
+}
+
+function alphasort (a, b) {
+  return a > b ? 1 : a < b ? -1 : 0
+}
+
+Glob.prototype._mark = function (p) {
+  var c = this.cache[p]
+  var m = p
+  if (c) {
+    var isDir = c === 2 || Array.isArray(c)
+    var slash = p.slice(-1) === '/'
+
+    if (isDir && !slash)
+      m += '/'
+    else if (!isDir && slash)
+      m = m.slice(0, -1)
+
+    if (m !== p) {
+      this.statCache[m] = this.statCache[p]
+      this.cache[m] = this.cache[p]
+    }
+  }
+
+  return m
+}
+
+Glob.prototype.abort = function () {
+  this.aborted = true
+  this.emit("abort")
+}
+
+Glob.prototype.pause = function () {
+  if (this.paused) return
+  if (this.sync)
+    this.emit("error", new Error("Can't pause/resume sync glob"))
+  this.paused = true
+  this.emit("pause")
+}
+
+Glob.prototype.resume = function () {
+  if (!this.paused) return
+  if (this.sync)
+    this.emit("error", new Error("Can't pause/resume sync glob"))
+  this.paused = false
+  this.emit("resume")
+  this._processEmitQueue()
+  //process.nextTick(this.emit.bind(this, "resume"))
+}
+
+Glob.prototype.emitMatch = function (m) {
+  this.log('emitMatch', m)
+  this._emitQueue.push(m)
+  this._processEmitQueue()
+}
+
+Glob.prototype._processEmitQueue = function (m) {
+  this.log("pEQ paused=%j processing=%j m=%j", this.paused,
+           this._processingEmitQueue, m)
+  var done = false
+  while (!this._processingEmitQueue &&
+         !this.paused) {
+    this._processingEmitQueue = true
+    var m = this._emitQueue.shift()
+    this.log(">processEmitQueue", m === this.EOF ? ":EOF:" : m)
+    if (!m) {
+      this.log(">processEmitQueue, falsey m")
+      this._processingEmitQueue = false
+      break
+    }
+
+    if (m === this.EOF || !(this.mark && !this.stat)) {
+      this.log("peq: unmarked, or eof")
+      next.call(this, 0, false)
+    } else if (this.statCache[m]) {
+      var sc = this.statCache[m]
+      var exists
+      if (sc)
+        exists = sc.isDirectory() ? 2 : 1
+      this.log("peq: stat cached")
+      next.call(this, exists, exists === 2)
+    } else {
+      this.log("peq: _stat, then next")
+      this._stat(m, next)
+    }
+
+    function next(exists, isDir) {
+      this.log("next", m, exists, isDir)
+      var ev = m === this.EOF ? "end" : "match"
+
+      // "end" can only happen once.
+      assert(!this._endEmitted)
+      if (ev === "end")
+        this._endEmitted = true
+
+      if (exists) {
+        // Doesn't mean it necessarily doesn't exist, it's possible
+        // we just didn't check because we don't care that much, or
+        // this is EOF anyway.
+        if (isDir && !m.match(/\/$/)) {
+          m = m + "/"
+        } else if (!isDir && m.match(/\/$/)) {
+          m = m.replace(/\/+$/, "")
+        }
+      }
+      this.log("emit", ev, m)
+      this.emit(ev, m)
+      this._processingEmitQueue = false
+      if (done && m !== this.EOF && !this.paused)
+        this._processEmitQueue()
+    }
+  }
+  done = true
+}
+
+Glob.prototype._process = function (pattern, depth, index, cb_) {
+  assert(this instanceof Glob)
+
+  var cb = function cb (er, res) {
+    assert(this instanceof Glob)
+    if (this.paused) {
+      if (!this._processQueue) {
+        this._processQueue = []
+        this.once("resume", function () {
+          var q = this._processQueue
+          this._processQueue = null
+          q.forEach(function (cb) { cb() })
+        })
+      }
+      this._processQueue.push(cb_.bind(this, er, res))
+    } else {
+      cb_.call(this, er, res)
+    }
+  }.bind(this)
+
+  if (this.aborted) return cb()
+
+  if (depth > this.maxDepth) return cb()
+
+  // Get the first [n] parts of pattern that are all strings.
+  var n = 0
+  while (typeof pattern[n] === "string") {
+    n ++
+  }
+  // now n is the index of the first one that is *not* a string.
+
+  // see if there's anything else
+  var prefix
+  switch (n) {
+    // if not, then this is rather simple
+    case pattern.length:
+      prefix = pattern.join("/")
+      this._stat(prefix, function (exists, isDir) {
+        // either it's there, or it isn't.
+        // nothing more to do, either way.
+        if (exists) {
+          if (prefix && isAbsolute(prefix) && !this.nomount) {
+            if (prefix.charAt(0) === "/") {
+              prefix = path.join(this.root, prefix)
+            } else {
+              prefix = path.resolve(this.root, prefix)
+            }
+          }
+
+          if (process.platform === "win32")
+            prefix = prefix.replace(/\\/g, "/")
+
+          this.matches[index] = this.matches[index] || {}
+          this.matches[index][prefix] = true
+          this.emitMatch(prefix)
+        }
+        return cb()
+      })
+      return
+
+    case 0:
+      // pattern *starts* with some non-trivial item.
+      // going to readdir(cwd), but not include the prefix in matches.
+      prefix = null
+      break
+
+    default:
+      // pattern has some string bits in the front.
+      // whatever it starts with, whether that's "absolute" like /foo/bar,
+      // or "relative" like "../baz"
+      prefix = pattern.slice(0, n)
+      prefix = prefix.join("/")
+      break
+  }
+
+  // get the list of entries.
+  var read
+  if (prefix === null) read = "."
+  else if (isAbsolute(prefix) || isAbsolute(pattern.join("/"))) {
+    if (!prefix || !isAbsolute(prefix)) {
+      prefix = path.join("/", prefix)
+    }
+    read = prefix = path.resolve(prefix)
+
+    // if (process.platform === "win32")
+    //   read = prefix = prefix.replace(/^[a-zA-Z]:|\\/g, "/")
+
+    this.log('absolute: ', prefix, this.root, pattern, read)
+  } else {
+    read = prefix
+  }
+
+  this.log('readdir(%j)', read, this.cwd, this.root)
+
+  return this._readdir(read, function (er, entries) {
+    if (er) {
+      // not a directory!
+      // this means that, whatever else comes after this, it can never match
+      return cb()
+    }
+
+    // globstar is special
+    if (pattern[n] === minimatch.GLOBSTAR) {
+      // test without the globstar, and with every child both below
+      // and replacing the globstar.
+      var s = [ pattern.slice(0, n).concat(pattern.slice(n + 1)) ]
+      entries.forEach(function (e) {
+        if (e.charAt(0) === "." && !this.dot) return
+        // instead of the globstar
+        s.push(pattern.slice(0, n).concat(e).concat(pattern.slice(n + 1)))
+        // below the globstar
+        s.push(pattern.slice(0, n).concat(e).concat(pattern.slice(n)))
+      }, this)
+
+      s = s.filter(function (pattern) {
+        var key = gsKey(pattern)
+        var seen = !this._globstars[key]
+        this._globstars[key] = true
+        return seen
+      }, this)
+
+      if (!s.length)
+        return cb()
+
+      // now asyncForEach over this
+      var l = s.length
+      , errState = null
+      s.forEach(function (gsPattern) {
+        this._process(gsPattern, depth + 1, index, function (er) {
+          if (errState) return
+          if (er) return cb(errState = er)
+          if (--l <= 0) return cb()
+        })
+      }, this)
+
+      return
+    }
+
+    // not a globstar
+    // It will only match dot entries if it starts with a dot, or if
+    // dot is set.  Stuff like @(.foo|.bar) isn't allowed.
+    var pn = pattern[n]
+    var rawGlob = pattern[n]._glob
+    , dotOk = this.dot || rawGlob.charAt(0) === "."
+
+    entries = entries.filter(function (e) {
+      return (e.charAt(0) !== "." || dotOk) &&
+             e.match(pattern[n])
+    })
+
+    // If n === pattern.length - 1, then there's no need for the extra stat
+    // *unless* the user has specified "mark" or "stat" explicitly.
+    // We know that they exist, since the readdir returned them.
+    if (n === pattern.length - 1 &&
+        !this.mark &&
+        !this.stat) {
+      entries.forEach(function (e) {
+        if (prefix) {
+          if (prefix !== "/") e = prefix + "/" + e
+          else e = prefix + e
+        }
+        if (e.charAt(0) === "/" && !this.nomount) {
+          e = path.join(this.root, e)
+        }
+
+        if (process.platform === "win32")
+          e = e.replace(/\\/g, "/")
+
+        this.matches[index] = this.matches[index] || {}
+        this.matches[index][e] = true
+        this.emitMatch(e)
+      }, this)
+      return cb.call(this)
+    }
+
+
+    // now test all the remaining entries as stand-ins for that part
+    // of the pattern.
+    var l = entries.length
+    , errState = null
+    if (l === 0) return cb() // no matches possible
+    entries.forEach(function (e) {
+      var p = pattern.slice(0, n).concat(e).concat(pattern.slice(n + 1))
+      this._process(p, depth + 1, index, function (er) {
+        if (errState) return
+        if (er) return cb(errState = er)
+        if (--l === 0) return cb.call(this)
+      })
+    }, this)
+  })
+
+}
+
+function gsKey (pattern) {
+  return '**' + pattern.map(function (p) {
+    return (p === minimatch.GLOBSTAR) ? '**' : (''+p)
+  }).join('/')
+}
+
+Glob.prototype._stat = function (f, cb) {
+  assert(this instanceof Glob)
+  var abs = f
+  if (f.charAt(0) === "/") {
+    abs = path.join(this.root, f)
+  } else if (this.changedCwd) {
+    abs = path.resolve(this.cwd, f)
+  }
+
+  if (f.length > this.maxLength) {
+    var er = new Error("Path name too long")
+    er.code = "ENAMETOOLONG"
+    er.path = f
+    return this._afterStat(f, abs, cb, er)
+  }
+
+  this.log('stat', [this.cwd, f, '=', abs])
+
+  if (!this.stat && this.cache.hasOwnProperty(f)) {
+    var exists = this.cache[f]
+    , isDir = exists && (Array.isArray(exists) || exists === 2)
+    if (this.sync) return cb.call(this, !!exists, isDir)
+    return process.nextTick(cb.bind(this, !!exists, isDir))
+  }
+
+  var stat = this.statCache[abs]
+  if (this.sync || stat) {
+    var er
+    try {
+      stat = fs.statSync(abs)
+    } catch (e) {
+      er = e
+    }
+    this._afterStat(f, abs, cb, er, stat)
+  } else {
+    fs.stat(abs, this._afterStat.bind(this, f, abs, cb))
+  }
+}
+
+Glob.prototype._afterStat = function (f, abs, cb, er, stat) {
+  var exists
+  assert(this instanceof Glob)
+
+  if (abs.slice(-1) === "/" && stat && !stat.isDirectory()) {
+    this.log("should be ENOTDIR, fake it")
+
+    er = new Error("ENOTDIR, not a directory '" + abs + "'")
+    er.path = abs
+    er.code = "ENOTDIR"
+    stat = null
+  }
+
+  var emit = !this.statCache[abs]
+  this.statCache[abs] = stat
+
+  if (er || !stat) {
+    exists = false
+  } else {
+    exists = stat.isDirectory() ? 2 : 1
+    if (emit)
+      this.emit('stat', f, stat)
+  }
+  this.cache[f] = this.cache[f] || exists
+  cb.call(this, !!exists, exists === 2)
+}
+
+Glob.prototype._readdir = function (f, cb) {
+  assert(this instanceof Glob)
+  var abs = f
+  if (f.charAt(0) === "/") {
+    abs = path.join(this.root, f)
+  } else if (isAbsolute(f)) {
+    abs = f
+  } else if (this.changedCwd) {
+    abs = path.resolve(this.cwd, f)
+  }
+
+  if (f.length > this.maxLength) {
+    var er = new Error("Path name too long")
+    er.code = "ENAMETOOLONG"
+    er.path = f
+    return this._afterReaddir(f, abs, cb, er)
+  }
+
+  this.log('readdir', [this.cwd, f, abs])
+  if (this.cache.hasOwnProperty(f)) {
+    var c = this.cache[f]
+    if (Array.isArray(c)) {
+      if (this.sync) return cb.call(this, null, c)
+      return process.nextTick(cb.bind(this, null, c))
+    }
+
+    if (!c || c === 1) {
+      // either ENOENT or ENOTDIR
+      var code = c ? "ENOTDIR" : "ENOENT"
+      , er = new Error((c ? "Not a directory" : "Not found") + ": " + f)
+      er.path = f
+      er.code = code
+      this.log(f, er)
+      if (this.sync) return cb.call(this, er)
+      return process.nextTick(cb.bind(this, er))
+    }
+
+    // at this point, c === 2, meaning it's a dir, but we haven't
+    // had to read it yet, or c === true, meaning it's *something*
+    // but we don't have any idea what.  Need to read it, either way.
+  }
+
+  if (this.sync) {
+    var er, entries
+    try {
+      entries = fs.readdirSync(abs)
+    } catch (e) {
+      er = e
+    }
+    return this._afterReaddir(f, abs, cb, er, entries)
+  }
+
+  fs.readdir(abs, this._afterReaddir.bind(this, f, abs, cb))
+}
+
+Glob.prototype._afterReaddir = function (f, abs, cb, er, entries) {
+  assert(this instanceof Glob)
+  if (entries && !er) {
+    this.cache[f] = entries
+    // if we haven't asked to stat everything for suresies, then just
+    // assume that everything in there exists, so we can avoid
+    // having to stat it a second time.  This also gets us one step
+    // further into ELOOP territory.
+    if (!this.mark && !this.stat) {
+      entries.forEach(function (e) {
+        if (f === "/") e = f + e
+        else e = f + "/" + e
+        this.cache[e] = true
+      }, this)
+    }
+
+    return cb.call(this, er, entries)
+  }
+
+  // now handle errors, and cache the information
+  if (er) switch (er.code) {
+    case "ENOTDIR": // totally normal. means it *does* exist.
+      this.cache[f] = 1
+      return cb.call(this, er)
+    case "ENOENT": // not terribly unusual
+    case "ELOOP":
+    case "ENAMETOOLONG":
+    case "UNKNOWN":
+      this.cache[f] = false
+      return cb.call(this, er)
+    default: // some unusual error.  Treat as failure.
+      this.cache[f] = false
+      if (this.strict) this.emit("error", er)
+      if (!this.silent) console.error("glob error", er)
+      return cb.call(this, er)
+  }
+}
+
+var isAbsolute = process.platform === "win32" ? absWin : absUnix
+
+function absWin (p) {
+  if (absUnix(p)) return true
+  // pull off the device/UNC bit from a windows path.
+  // from node's lib/path.js
+  var splitDeviceRe =
+      /^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/]+)?([\\\/])?([\s\S]*?)$/
+    , result = splitDeviceRe.exec(p)
+    , device = result[1] || ''
+    , isUnc = device && device.charAt(1) !== ':'
+    , isAbsolute = !!result[2] || isUnc // UNC paths are always absolute
+
+  return isAbsolute
+}
+
+function absUnix (p) {
+  return p.charAt(0) === "/" || p === ""
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/inherits/LICENSE b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/inherits/LICENSE
new file mode 100644
index 0000000..dea3013
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/inherits/LICENSE
@@ -0,0 +1,16 @@
+The ISC License
+
+Copyright (c) Isaac Z. Schlueter
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.
+
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/inherits/README.md b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/inherits/README.md
new file mode 100644
index 0000000..b1c5665
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/inherits/README.md
@@ -0,0 +1,42 @@
+Browser-friendly inheritance fully compatible with standard node.js
+[inherits](http://nodejs.org/api/util.html#util_util_inherits_constructor_superconstructor).
+
+This package exports standard `inherits` from node.js `util` module in
+node environment, but also provides alternative browser-friendly
+implementation through [browser
+field](https://gist.github.com/shtylman/4339901). Alternative
+implementation is a literal copy of standard one located in standalone
+module to avoid requiring of `util`. It also has a shim for old
+browsers with no `Object.create` support.
+
+While keeping you sure you are using standard `inherits`
+implementation in node.js environment, it allows bundlers such as
+[browserify](https://github.com/substack/node-browserify) to not
+include full `util` package to your client code if all you need is
+just `inherits` function. It worth, because browser shim for `util`
+package is large and `inherits` is often the single function you need
+from it.
+
+It's recommended to use this package instead of
+`require('util').inherits` for any code that has chances to be used
+not only in node.js but in browser too.
+
+## usage
+
+```js
+var inherits = require('inherits');
+// then use exactly as the standard one
+```
+
+## note on version ~1.0
+
+Version ~1.0 had completely different motivation and is not compatible
+neither with 2.0 nor with standard node.js `inherits`.
+
+If you are using version ~1.0 and planning to switch to ~2.0, be
+careful:
+
+* new version uses `super_` instead of `super` for referencing
+  superclass
+* new version overwrites current prototype while old one preserves any
+  existing fields on it
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/inherits/inherits.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/inherits/inherits.js
new file mode 100644
index 0000000..29f5e24
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/inherits/inherits.js
@@ -0,0 +1 @@
+module.exports = require('util').inherits
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/inherits/inherits_browser.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/inherits/inherits_browser.js
new file mode 100644
index 0000000..c1e78a7
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/inherits/inherits_browser.js
@@ -0,0 +1,23 @@
+if (typeof Object.create === 'function') {
+  // implementation from standard node.js 'util' module
+  module.exports = function inherits(ctor, superCtor) {
+    ctor.super_ = superCtor
+    ctor.prototype = Object.create(superCtor.prototype, {
+      constructor: {
+        value: ctor,
+        enumerable: false,
+        writable: true,
+        configurable: true
+      }
+    });
+  };
+} else {
+  // old school shim for old browsers
+  module.exports = function inherits(ctor, superCtor) {
+    ctor.super_ = superCtor
+    var TempCtor = function () {}
+    TempCtor.prototype = superCtor.prototype
+    ctor.prototype = new TempCtor()
+    ctor.prototype.constructor = ctor
+  }
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/inherits/package.json b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/inherits/package.json
new file mode 100644
index 0000000..5bf0db5
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/inherits/package.json
@@ -0,0 +1,33 @@
+{
+  "name": "inherits",
+  "description": "Browser-friendly inheritance fully compatible with standard node.js inherits()",
+  "version": "2.0.1",
+  "keywords": [
+    "inheritance",
+    "class",
+    "klass",
+    "oop",
+    "object-oriented",
+    "inherits",
+    "browser",
+    "browserify"
+  ],
+  "main": "./inherits.js",
+  "browser": "./inherits_browser.js",
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/isaacs/inherits"
+  },
+  "license": "ISC",
+  "scripts": {
+    "test": "node test"
+  },
+  "readme": "Browser-friendly inheritance fully compatible with standard node.js\n[inherits](http://nodejs.org/api/util.html#util_util_inherits_constructor_superconstructor).\n\nThis package exports standard `inherits` from node.js `util` module in\nnode environment, but also provides alternative browser-friendly\nimplementation through [browser\nfield](https://gist.github.com/shtylman/4339901). Alternative\nimplementation is a literal copy of standard one located in standalone\nmodule to avoid requiring of `util`. It also has a shim for old\nbrowsers with no `Object.create` support.\n\nWhile keeping you sure you are using standard `inherits`\nimplementation in node.js environment, it allows bundlers such as\n[browserify](https://github.com/substack/node-browserify) to not\ninclude full `util` package to your client code if all you need is\njust `inherits` function. It worth, because browser shim for `util`\npackage is large and `inherits` is often the single function you need\nfrom it.\n\nIt's recommended to use this package instead of\n`require('util').inherits` for any code that has chances to be used\nnot only in node.js but in browser too.\n\n## usage\n\n```js\nvar inherits = require('inherits');\n// then use exactly as the standard one\n```\n\n## note on version ~1.0\n\nVersion ~1.0 had completely different motivation and is not compatible\nneither with 2.0 nor with standard node.js `inherits`.\n\nIf you are using version ~1.0 and planning to switch to ~2.0, be\ncareful:\n\n* new version uses `super_` instead of `super` for referencing\n  superclass\n* new version overwrites current prototype while old one preserves any\n  existing fields on it\n",
+  "readmeFilename": "README.md",
+  "bugs": {
+    "url": "https://github.com/isaacs/inherits/issues"
+  },
+  "homepage": "https://github.com/isaacs/inherits",
+  "_id": "inherits@2.0.1",
+  "_from": "inherits@2"
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/inherits/test.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/inherits/test.js
new file mode 100644
index 0000000..fc53012
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/inherits/test.js
@@ -0,0 +1,25 @@
+var inherits = require('./inherits.js')
+var assert = require('assert')
+
+function test(c) {
+  assert(c.constructor === Child)
+  assert(c.constructor.super_ === Parent)
+  assert(Object.getPrototypeOf(c) === Child.prototype)
+  assert(Object.getPrototypeOf(Object.getPrototypeOf(c)) === Parent.prototype)
+  assert(c instanceof Child)
+  assert(c instanceof Parent)
+}
+
+function Child() {
+  Parent.call(this)
+  test(this)
+}
+
+function Parent() {}
+
+inherits(Child, Parent)
+
+var c = new Child
+test(c)
+
+console.log('ok')
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/.npmignore b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/.npmignore
new file mode 100644
index 0000000..3c3629e
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/.npmignore
@@ -0,0 +1 @@
+node_modules
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/LICENSE b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/LICENSE
new file mode 100644
index 0000000..05a4010
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/LICENSE
@@ -0,0 +1,23 @@
+Copyright 2009, 2010, 2011 Isaac Z. Schlueter.
+All rights reserved.
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/README.md b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/README.md
new file mode 100644
index 0000000..5b3967e
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/README.md
@@ -0,0 +1,218 @@
+# minimatch
+
+A minimal matching utility.
+
+[![Build Status](https://secure.travis-ci.org/isaacs/minimatch.png)](http://travis-ci.org/isaacs/minimatch)
+
+
+This is the matching library used internally by npm.
+
+Eventually, it will replace the C binding in node-glob.
+
+It works by converting glob expressions into JavaScript `RegExp`
+objects.
+
+## Usage
+
+```javascript
+var minimatch = require("minimatch")
+
+minimatch("bar.foo", "*.foo") // true!
+minimatch("bar.foo", "*.bar") // false!
+minimatch("bar.foo", "*.+(bar|foo)", { debug: true }) // true, and noisy!
+```
+
+## Features
+
+Supports these glob features:
+
+* Brace Expansion
+* Extended glob matching
+* "Globstar" `**` matching
+
+See:
+
+* `man sh`
+* `man bash`
+* `man 3 fnmatch`
+* `man 5 gitignore`
+
+## Minimatch Class
+
+Create a minimatch object by instanting the `minimatch.Minimatch` class.
+
+```javascript
+var Minimatch = require("minimatch").Minimatch
+var mm = new Minimatch(pattern, options)
+```
+
+### Properties
+
+* `pattern` The original pattern the minimatch object represents.
+* `options` The options supplied to the constructor.
+* `set` A 2-dimensional array of regexp or string expressions.
+  Each row in the
+  array corresponds to a brace-expanded pattern.  Each item in the row
+  corresponds to a single path-part.  For example, the pattern
+  `{a,b/c}/d` would expand to a set of patterns like:
+
+        [ [ a, d ]
+        , [ b, c, d ] ]
+
+    If a portion of the pattern doesn't have any "magic" in it
+    (that is, it's something like `"foo"` rather than `fo*o?`), then it
+    will be left as a string rather than converted to a regular
+    expression.
+
+* `regexp` Created by the `makeRe` method.  A single regular expression
+  expressing the entire pattern.  This is useful in cases where you wish
+  to use the pattern somewhat like `fnmatch(3)` with `FNM_PATH` enabled.
+* `negate` True if the pattern is negated.
+* `comment` True if the pattern is a comment.
+* `empty` True if the pattern is `""`.
+
+### Methods
+
+* `makeRe` Generate the `regexp` member if necessary, and return it.
+  Will return `false` if the pattern is invalid.
+* `match(fname)` Return true if the filename matches the pattern, or
+  false otherwise.
+* `matchOne(fileArray, patternArray, partial)` Take a `/`-split
+  filename, and match it against a single row in the `regExpSet`.  This
+  method is mainly for internal use, but is exposed so that it can be
+  used by a glob-walker that needs to avoid excessive filesystem calls.
+
+All other methods are internal, and will be called as necessary.
+
+## Functions
+
+The top-level exported function has a `cache` property, which is an LRU
+cache set to store 100 items.  So, calling these methods repeatedly
+with the same pattern and options will use the same Minimatch object,
+saving the cost of parsing it multiple times.
+
+### minimatch(path, pattern, options)
+
+Main export.  Tests a path against the pattern using the options.
+
+```javascript
+var isJS = minimatch(file, "*.js", { matchBase: true })
+```
+
+### minimatch.filter(pattern, options)
+
+Returns a function that tests its
+supplied argument, suitable for use with `Array.filter`.  Example:
+
+```javascript
+var javascripts = fileList.filter(minimatch.filter("*.js", {matchBase: true}))
+```
+
+### minimatch.match(list, pattern, options)
+
+Match against the list of
+files, in the style of fnmatch or glob.  If nothing is matched, and
+options.nonull is set, then return a list containing the pattern itself.
+
+```javascript
+var javascripts = minimatch.match(fileList, "*.js", {matchBase: true}))
+```
+
+### minimatch.makeRe(pattern, options)
+
+Make a regular expression object from the pattern.
+
+## Options
+
+All options are `false` by default.
+
+### debug
+
+Dump a ton of stuff to stderr.
+
+### nobrace
+
+Do not expand `{a,b}` and `{1..3}` brace sets.
+
+### noglobstar
+
+Disable `**` matching against multiple folder names.
+
+### dot
+
+Allow patterns to match filenames starting with a period, even if
+the pattern does not explicitly have a period in that spot.
+
+Note that by default, `a/**/b` will **not** match `a/.d/b`, unless `dot`
+is set.
+
+### noext
+
+Disable "extglob" style patterns like `+(a|b)`.
+
+### nocase
+
+Perform a case-insensitive match.
+
+### nonull
+
+When a match is not found by `minimatch.match`, return a list containing
+the pattern itself if this option is set.  When not set, an empty list
+is returned if there are no matches.
+
+### matchBase
+
+If set, then patterns without slashes will be matched
+against the basename of the path if it contains slashes.  For example,
+`a?b` would match the path `/xyz/123/acb`, but not `/xyz/acb/123`.
+
+### nocomment
+
+Suppress the behavior of treating `#` at the start of a pattern as a
+comment.
+
+### nonegate
+
+Suppress the behavior of treating a leading `!` character as negation.
+
+### flipNegate
+
+Returns from negate expressions the same as if they were not negated.
+(Ie, true on a hit, false on a miss.)
+
+
+## Comparisons to other fnmatch/glob implementations
+
+While strict compliance with the existing standards is a worthwhile
+goal, some discrepancies exist between minimatch and other
+implementations, and are intentional.
+
+If the pattern starts with a `!` character, then it is negated.  Set the
+`nonegate` flag to suppress this behavior, and treat leading `!`
+characters normally.  This is perhaps relevant if you wish to start the
+pattern with a negative extglob pattern like `!(a|B)`.  Multiple `!`
+characters at the start of a pattern will negate the pattern multiple
+times.
+
+If a pattern starts with `#`, then it is treated as a comment, and
+will not match anything.  Use `\#` to match a literal `#` at the
+start of a line, or set the `nocomment` flag to suppress this behavior.
+
+The double-star character `**` is supported by default, unless the
+`noglobstar` flag is set.  This is supported in the manner of bsdglob
+and bash 4.1, where `**` only has special significance if it is the only
+thing in a path part.  That is, `a/**/b` will match `a/x/y/b`, but
+`a/**b` will not.
+
+If an escaped pattern has no matches, and the `nonull` flag is set,
+then minimatch.match returns the pattern as-provided, rather than
+interpreting the character escapes.  For example,
+`minimatch.match([], "\\*a\\?")` will return `"\\*a\\?"` rather than
+`"*a?"`.  This is akin to setting the `nullglob` option in bash, except
+that it does not resolve escaped pattern characters.
+
+If brace expansion is not disabled, then it is performed before any
+other interpretation of the glob pattern.  Thus, a pattern like
+`+(a|{b),c)}`, which would not be valid in bash or zsh, is expanded
+**first** into the set of `+(a|b)` and `+(a|c)`, and those patterns are
+checked for validity.  Since those two are valid, matching proceeds.
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/minimatch.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/minimatch.js
new file mode 100644
index 0000000..4539678
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/minimatch.js
@@ -0,0 +1,1061 @@
+;(function (require, exports, module, platform) {
+
+if (module) module.exports = minimatch
+else exports.minimatch = minimatch
+
+if (!require) {
+  require = function (id) {
+    switch (id) {
+      case "sigmund": return function sigmund (obj) {
+        return JSON.stringify(obj)
+      }
+      case "path": return { basename: function (f) {
+        f = f.split(/[\/\\]/)
+        var e = f.pop()
+        if (!e) e = f.pop()
+        return e
+      }}
+      case "lru-cache": return function LRUCache () {
+        // not quite an LRU, but still space-limited.
+        var cache = {}
+        var cnt = 0
+        this.set = function (k, v) {
+          cnt ++
+          if (cnt >= 100) cache = {}
+          cache[k] = v
+        }
+        this.get = function (k) { return cache[k] }
+      }
+    }
+  }
+}
+
+minimatch.Minimatch = Minimatch
+
+var LRU = require("lru-cache")
+  , cache = minimatch.cache = new LRU({max: 100})
+  , GLOBSTAR = minimatch.GLOBSTAR = Minimatch.GLOBSTAR = {}
+  , sigmund = require("sigmund")
+
+var path = require("path")
+  // any single thing other than /
+  // don't need to escape / when using new RegExp()
+  , qmark = "[^/]"
+
+  // * => any number of characters
+  , star = qmark + "*?"
+
+  // ** when dots are allowed.  Anything goes, except .. and .
+  // not (^ or / followed by one or two dots followed by $ or /),
+  // followed by anything, any number of times.
+  , twoStarDot = "(?:(?!(?:\\\/|^)(?:\\.{1,2})($|\\\/)).)*?"
+
+  // not a ^ or / followed by a dot,
+  // followed by anything, any number of times.
+  , twoStarNoDot = "(?:(?!(?:\\\/|^)\\.).)*?"
+
+  // characters that need to be escaped in RegExp.
+  , reSpecials = charSet("().*{}+?[]^$\\!")
+
+// "abc" -> { a:true, b:true, c:true }
+function charSet (s) {
+  return s.split("").reduce(function (set, c) {
+    set[c] = true
+    return set
+  }, {})
+}
+
+// normalizes slashes.
+var slashSplit = /\/+/
+
+minimatch.filter = filter
+function filter (pattern, options) {
+  options = options || {}
+  return function (p, i, list) {
+    return minimatch(p, pattern, options)
+  }
+}
+
+function ext (a, b) {
+  a = a || {}
+  b = b || {}
+  var t = {}
+  Object.keys(b).forEach(function (k) {
+    t[k] = b[k]
+  })
+  Object.keys(a).forEach(function (k) {
+    t[k] = a[k]
+  })
+  return t
+}
+
+minimatch.defaults = function (def) {
+  if (!def || !Object.keys(def).length) return minimatch
+
+  var orig = minimatch
+
+  var m = function minimatch (p, pattern, options) {
+    return orig.minimatch(p, pattern, ext(def, options))
+  }
+
+  m.Minimatch = function Minimatch (pattern, options) {
+    return new orig.Minimatch(pattern, ext(def, options))
+  }
+
+  return m
+}
+
+Minimatch.defaults = function (def) {
+  if (!def || !Object.keys(def).length) return Minimatch
+  return minimatch.defaults(def).Minimatch
+}
+
+
+function minimatch (p, pattern, options) {
+  if (typeof pattern !== "string") {
+    throw new TypeError("glob pattern string required")
+  }
+
+  if (!options) options = {}
+
+  // shortcut: comments match nothing.
+  if (!options.nocomment && pattern.charAt(0) === "#") {
+    return false
+  }
+
+  // "" only matches ""
+  if (pattern.trim() === "") return p === ""
+
+  return new Minimatch(pattern, options).match(p)
+}
+
+function Minimatch (pattern, options) {
+  if (!(this instanceof Minimatch)) {
+    return new Minimatch(pattern, options, cache)
+  }
+
+  if (typeof pattern !== "string") {
+    throw new TypeError("glob pattern string required")
+  }
+
+  if (!options) options = {}
+  pattern = pattern.trim()
+
+  // windows: need to use /, not \
+  // On other platforms, \ is a valid (albeit bad) filename char.
+  if (platform === "win32") {
+    pattern = pattern.split("\\").join("/")
+  }
+
+  // lru storage.
+  // these things aren't particularly big, but walking down the string
+  // and turning it into a regexp can get pretty costly.
+  var cacheKey = pattern + "\n" + sigmund(options)
+  var cached = minimatch.cache.get(cacheKey)
+  if (cached) return cached
+  minimatch.cache.set(cacheKey, this)
+
+  this.options = options
+  this.set = []
+  this.pattern = pattern
+  this.regexp = null
+  this.negate = false
+  this.comment = false
+  this.empty = false
+
+  // make the set of regexps etc.
+  this.make()
+}
+
+Minimatch.prototype.debug = function() {}
+
+Minimatch.prototype.make = make
+function make () {
+  // don't do it more than once.
+  if (this._made) return
+
+  var pattern = this.pattern
+  var options = this.options
+
+  // empty patterns and comments match nothing.
+  if (!options.nocomment && pattern.charAt(0) === "#") {
+    this.comment = true
+    return
+  }
+  if (!pattern) {
+    this.empty = true
+    return
+  }
+
+  // step 1: figure out negation, etc.
+  this.parseNegate()
+
+  // step 2: expand braces
+  var set = this.globSet = this.braceExpand()
+
+  if (options.debug) this.debug = console.error
+
+  this.debug(this.pattern, set)
+
+  // step 3: now we have a set, so turn each one into a series of path-portion
+  // matching patterns.
+  // These will be regexps, except in the case of "**", which is
+  // set to the GLOBSTAR object for globstar behavior,
+  // and will not contain any / characters
+  set = this.globParts = set.map(function (s) {
+    return s.split(slashSplit)
+  })
+
+  this.debug(this.pattern, set)
+
+  // glob --> regexps
+  set = set.map(function (s, si, set) {
+    return s.map(this.parse, this)
+  }, this)
+
+  this.debug(this.pattern, set)
+
+  // filter out everything that didn't compile properly.
+  set = set.filter(function (s) {
+    return -1 === s.indexOf(false)
+  })
+
+  this.debug(this.pattern, set)
+
+  this.set = set
+}
+
+Minimatch.prototype.parseNegate = parseNegate
+function parseNegate () {
+  var pattern = this.pattern
+    , negate = false
+    , options = this.options
+    , negateOffset = 0
+
+  if (options.nonegate) return
+
+  for ( var i = 0, l = pattern.length
+      ; i < l && pattern.charAt(i) === "!"
+      ; i ++) {
+    negate = !negate
+    negateOffset ++
+  }
+
+  if (negateOffset) this.pattern = pattern.substr(negateOffset)
+  this.negate = negate
+}
+
+// Brace expansion:
+// a{b,c}d -> abd acd
+// a{b,}c -> abc ac
+// a{0..3}d -> a0d a1d a2d a3d
+// a{b,c{d,e}f}g -> abg acdfg acefg
+// a{b,c}d{e,f}g -> abdeg acdeg abdeg abdfg
+//
+// Invalid sets are not expanded.
+// a{2..}b -> a{2..}b
+// a{b}c -> a{b}c
+minimatch.braceExpand = function (pattern, options) {
+  return new Minimatch(pattern, options).braceExpand()
+}
+
+Minimatch.prototype.braceExpand = braceExpand
+function braceExpand (pattern, options) {
+  options = options || this.options
+  pattern = typeof pattern === "undefined"
+    ? this.pattern : pattern
+
+  if (typeof pattern === "undefined") {
+    throw new Error("undefined pattern")
+  }
+
+  if (options.nobrace ||
+      !pattern.match(/\{.*\}/)) {
+    // shortcut. no need to expand.
+    return [pattern]
+  }
+
+  var escaping = false
+
+  // examples and comments refer to this crazy pattern:
+  // a{b,c{d,e},{f,g}h}x{y,z}
+  // expected:
+  // abxy
+  // abxz
+  // acdxy
+  // acdxz
+  // acexy
+  // acexz
+  // afhxy
+  // afhxz
+  // aghxy
+  // aghxz
+
+  // everything before the first \{ is just a prefix.
+  // So, we pluck that off, and work with the rest,
+  // and then prepend it to everything we find.
+  if (pattern.charAt(0) !== "{") {
+    this.debug(pattern)
+    var prefix = null
+    for (var i = 0, l = pattern.length; i < l; i ++) {
+      var c = pattern.charAt(i)
+      this.debug(i, c)
+      if (c === "\\") {
+        escaping = !escaping
+      } else if (c === "{" && !escaping) {
+        prefix = pattern.substr(0, i)
+        break
+      }
+    }
+
+    // actually no sets, all { were escaped.
+    if (prefix === null) {
+      this.debug("no sets")
+      return [pattern]
+    }
+
+   var tail = braceExpand.call(this, pattern.substr(i), options)
+    return tail.map(function (t) {
+      return prefix + t
+    })
+  }
+
+  // now we have something like:
+  // {b,c{d,e},{f,g}h}x{y,z}
+  // walk through the set, expanding each part, until
+  // the set ends.  then, we'll expand the suffix.
+  // If the set only has a single member, then'll put the {} back
+
+  // first, handle numeric sets, since they're easier
+  var numset = pattern.match(/^\{(-?[0-9]+)\.\.(-?[0-9]+)\}/)
+  if (numset) {
+    this.debug("numset", numset[1], numset[2])
+    var suf = braceExpand.call(this, pattern.substr(numset[0].length), options)
+      , start = +numset[1]
+      , end = +numset[2]
+      , inc = start > end ? -1 : 1
+      , set = []
+    for (var i = start; i != (end + inc); i += inc) {
+      // append all the suffixes
+      for (var ii = 0, ll = suf.length; ii < ll; ii ++) {
+        set.push(i + suf[ii])
+      }
+    }
+    return set
+  }
+
+  // ok, walk through the set
+  // We hope, somewhat optimistically, that there
+  // will be a } at the end.
+  // If the closing brace isn't found, then the pattern is
+  // interpreted as braceExpand("\\" + pattern) so that
+  // the leading \{ will be interpreted literally.
+  var i = 1 // skip the \{
+    , depth = 1
+    , set = []
+    , member = ""
+    , sawEnd = false
+    , escaping = false
+
+  function addMember () {
+    set.push(member)
+    member = ""
+  }
+
+  this.debug("Entering for")
+  FOR: for (i = 1, l = pattern.length; i < l; i ++) {
+    var c = pattern.charAt(i)
+    this.debug("", i, c)
+
+    if (escaping) {
+      escaping = false
+      member += "\\" + c
+    } else {
+      switch (c) {
+        case "\\":
+          escaping = true
+          continue
+
+        case "{":
+          depth ++
+          member += "{"
+          continue
+
+        case "}":
+          depth --
+          // if this closes the actual set, then we're done
+          if (depth === 0) {
+            addMember()
+            // pluck off the close-brace
+            i ++
+            break FOR
+          } else {
+            member += c
+            continue
+          }
+
+        case ",":
+          if (depth === 1) {
+            addMember()
+          } else {
+            member += c
+          }
+          continue
+
+        default:
+          member += c
+          continue
+      } // switch
+    } // else
+  } // for
+
+  // now we've either finished the set, and the suffix is
+  // pattern.substr(i), or we have *not* closed the set,
+  // and need to escape the leading brace
+  if (depth !== 0) {
+    this.debug("didn't close", pattern)
+    return braceExpand.call(this, "\\" + pattern, options)
+  }
+
+  // x{y,z} -> ["xy", "xz"]
+  this.debug("set", set)
+  this.debug("suffix", pattern.substr(i))
+  var suf = braceExpand.call(this, pattern.substr(i), options)
+  // ["b", "c{d,e}","{f,g}h"] ->
+  //   [["b"], ["cd", "ce"], ["fh", "gh"]]
+  var addBraces = set.length === 1
+  this.debug("set pre-expanded", set)
+  set = set.map(function (p) {
+    return braceExpand.call(this, p, options)
+  }, this)
+  this.debug("set expanded", set)
+
+
+  // [["b"], ["cd", "ce"], ["fh", "gh"]] ->
+  //   ["b", "cd", "ce", "fh", "gh"]
+  set = set.reduce(function (l, r) {
+    return l.concat(r)
+  })
+
+  if (addBraces) {
+    set = set.map(function (s) {
+      return "{" + s + "}"
+    })
+  }
+
+  // now attach the suffixes.
+  var ret = []
+  for (var i = 0, l = set.length; i < l; i ++) {
+    for (var ii = 0, ll = suf.length; ii < ll; ii ++) {
+      ret.push(set[i] + suf[ii])
+    }
+  }
+  return ret
+}
+
+// parse a component of the expanded set.
+// At this point, no pattern may contain "/" in it
+// so we're going to return a 2d array, where each entry is the full
+// pattern, split on '/', and then turned into a regular expression.
+// A regexp is made at the end which joins each array with an
+// escaped /, and another full one which joins each regexp with |.
+//
+// Following the lead of Bash 4.1, note that "**" only has special meaning
+// when it is the *only* thing in a path portion.  Otherwise, any series
+// of * is equivalent to a single *.  Globstar behavior is enabled by
+// default, and can be disabled by setting options.noglobstar.
+Minimatch.prototype.parse = parse
+var SUBPARSE = {}
+function parse (pattern, isSub) {
+  var options = this.options
+
+  // shortcuts
+  if (!options.noglobstar && pattern === "**") return GLOBSTAR
+  if (pattern === "") return ""
+
+  var re = ""
+    , hasMagic = !!options.nocase
+    , escaping = false
+    // ? => one single character
+    , patternListStack = []
+    , plType
+    , stateChar
+    , inClass = false
+    , reClassStart = -1
+    , classStart = -1
+    // . and .. never match anything that doesn't start with .,
+    // even when options.dot is set.
+    , patternStart = pattern.charAt(0) === "." ? "" // anything
+      // not (start or / followed by . or .. followed by / or end)
+      : options.dot ? "(?!(?:^|\\\/)\\.{1,2}(?:$|\\\/))"
+      : "(?!\\.)"
+    , self = this
+
+  function clearStateChar () {
+    if (stateChar) {
+      // we had some state-tracking character
+      // that wasn't consumed by this pass.
+      switch (stateChar) {
+        case "*":
+          re += star
+          hasMagic = true
+          break
+        case "?":
+          re += qmark
+          hasMagic = true
+          break
+        default:
+          re += "\\"+stateChar
+          break
+      }
+      self.debug('clearStateChar %j %j', stateChar, re)
+      stateChar = false
+    }
+  }
+
+  for ( var i = 0, len = pattern.length, c
+      ; (i < len) && (c = pattern.charAt(i))
+      ; i ++ ) {
+
+    this.debug("%s\t%s %s %j", pattern, i, re, c)
+
+    // skip over any that are escaped.
+    if (escaping && reSpecials[c]) {
+      re += "\\" + c
+      escaping = false
+      continue
+    }
+
+    SWITCH: switch (c) {
+      case "/":
+        // completely not allowed, even escaped.
+        // Should already be path-split by now.
+        return false
+
+      case "\\":
+        clearStateChar()
+        escaping = true
+        continue
+
+      // the various stateChar values
+      // for the "extglob" stuff.
+      case "?":
+      case "*":
+      case "+":
+      case "@":
+      case "!":
+        this.debug("%s\t%s %s %j <-- stateChar", pattern, i, re, c)
+
+        // all of those are literals inside a class, except that
+        // the glob [!a] means [^a] in regexp
+        if (inClass) {
+          this.debug('  in class')
+          if (c === "!" && i === classStart + 1) c = "^"
+          re += c
+          continue
+        }
+
+        // if we already have a stateChar, then it means
+        // that there was something like ** or +? in there.
+        // Handle the stateChar, then proceed with this one.
+        self.debug('call clearStateChar %j', stateChar)
+        clearStateChar()
+        stateChar = c
+        // if extglob is disabled, then +(asdf|foo) isn't a thing.
+        // just clear the statechar *now*, rather than even diving into
+        // the patternList stuff.
+        if (options.noext) clearStateChar()
+        continue
+
+      case "(":
+        if (inClass) {
+          re += "("
+          continue
+        }
+
+        if (!stateChar) {
+          re += "\\("
+          continue
+        }
+
+        plType = stateChar
+        patternListStack.push({ type: plType
+                              , start: i - 1
+                              , reStart: re.length })
+        // negation is (?:(?!js)[^/]*)
+        re += stateChar === "!" ? "(?:(?!" : "(?:"
+        this.debug('plType %j %j', stateChar, re)
+        stateChar = false
+        continue
+
+      case ")":
+        if (inClass || !patternListStack.length) {
+          re += "\\)"
+          continue
+        }
+
+        clearStateChar()
+        hasMagic = true
+        re += ")"
+        plType = patternListStack.pop().type
+        // negation is (?:(?!js)[^/]*)
+        // The others are (?:<pattern>)<type>
+        switch (plType) {
+          case "!":
+            re += "[^/]*?)"
+            break
+          case "?":
+          case "+":
+          case "*": re += plType
+          case "@": break // the default anyway
+        }
+        continue
+
+      case "|":
+        if (inClass || !patternListStack.length || escaping) {
+          re += "\\|"
+          escaping = false
+          continue
+        }
+
+        clearStateChar()
+        re += "|"
+        continue
+
+      // these are mostly the same in regexp and glob
+      case "[":
+        // swallow any state-tracking char before the [
+        clearStateChar()
+
+        if (inClass) {
+          re += "\\" + c
+          continue
+        }
+
+        inClass = true
+        classStart = i
+        reClassStart = re.length
+        re += c
+        continue
+
+      case "]":
+        //  a right bracket shall lose its special
+        //  meaning and represent itself in
+        //  a bracket expression if it occurs
+        //  first in the list.  -- POSIX.2 2.8.3.2
+        if (i === classStart + 1 || !inClass) {
+          re += "\\" + c
+          escaping = false
+          continue
+        }
+
+        // finish up the class.
+        hasMagic = true
+        inClass = false
+        re += c
+        continue
+
+      default:
+        // swallow any state char that wasn't consumed
+        clearStateChar()
+
+        if (escaping) {
+          // no need
+          escaping = false
+        } else if (reSpecials[c]
+                   && !(c === "^" && inClass)) {
+          re += "\\"
+        }
+
+        re += c
+
+    } // switch
+  } // for
+
+
+  // handle the case where we left a class open.
+  // "[abc" is valid, equivalent to "\[abc"
+  if (inClass) {
+    // split where the last [ was, and escape it
+    // this is a huge pita.  We now have to re-walk
+    // the contents of the would-be class to re-translate
+    // any characters that were passed through as-is
+    var cs = pattern.substr(classStart + 1)
+      , sp = this.parse(cs, SUBPARSE)
+    re = re.substr(0, reClassStart) + "\\[" + sp[0]
+    hasMagic = hasMagic || sp[1]
+  }
+
+  // handle the case where we had a +( thing at the *end*
+  // of the pattern.
+  // each pattern list stack adds 3 chars, and we need to go through
+  // and escape any | chars that were passed through as-is for the regexp.
+  // Go through and escape them, taking care not to double-escape any
+  // | chars that were already escaped.
+  var pl
+  while (pl = patternListStack.pop()) {
+    var tail = re.slice(pl.reStart + 3)
+    // maybe some even number of \, then maybe 1 \, followed by a |
+    tail = tail.replace(/((?:\\{2})*)(\\?)\|/g, function (_, $1, $2) {
+      if (!$2) {
+        // the | isn't already escaped, so escape it.
+        $2 = "\\"
+      }
+
+      // need to escape all those slashes *again*, without escaping the
+      // one that we need for escaping the | character.  As it works out,
+      // escaping an even number of slashes can be done by simply repeating
+      // it exactly after itself.  That's why this trick works.
+      //
+      // I am sorry that you have to see this.
+      return $1 + $1 + $2 + "|"
+    })
+
+    this.debug("tail=%j\n   %s", tail, tail)
+    var t = pl.type === "*" ? star
+          : pl.type === "?" ? qmark
+          : "\\" + pl.type
+
+    hasMagic = true
+    re = re.slice(0, pl.reStart)
+       + t + "\\("
+       + tail
+  }
+
+  // handle trailing things that only matter at the very end.
+  clearStateChar()
+  if (escaping) {
+    // trailing \\
+    re += "\\\\"
+  }
+
+  // only need to apply the nodot start if the re starts with
+  // something that could conceivably capture a dot
+  var addPatternStart = false
+  switch (re.charAt(0)) {
+    case ".":
+    case "[":
+    case "(": addPatternStart = true
+  }
+
+  // if the re is not "" at this point, then we need to make sure
+  // it doesn't match against an empty path part.
+  // Otherwise a/* will match a/, which it should not.
+  if (re !== "" && hasMagic) re = "(?=.)" + re
+
+  if (addPatternStart) re = patternStart + re
+
+  // parsing just a piece of a larger pattern.
+  if (isSub === SUBPARSE) {
+    return [ re, hasMagic ]
+  }
+
+  // skip the regexp for non-magical patterns
+  // unescape anything in it, though, so that it'll be
+  // an exact match against a file etc.
+  if (!hasMagic) {
+    return globUnescape(pattern)
+  }
+
+  var flags = options.nocase ? "i" : ""
+    , regExp = new RegExp("^" + re + "$", flags)
+
+  regExp._glob = pattern
+  regExp._src = re
+
+  return regExp
+}
+
+minimatch.makeRe = function (pattern, options) {
+  return new Minimatch(pattern, options || {}).makeRe()
+}
+
+Minimatch.prototype.makeRe = makeRe
+function makeRe () {
+  if (this.regexp || this.regexp === false) return this.regexp
+
+  // at this point, this.set is a 2d array of partial
+  // pattern strings, or "**".
+  //
+  // It's better to use .match().  This function shouldn't
+  // be used, really, but it's pretty convenient sometimes,
+  // when you just want to work with a regex.
+  var set = this.set
+
+  if (!set.length) return this.regexp = false
+  var options = this.options
+
+  var twoStar = options.noglobstar ? star
+      : options.dot ? twoStarDot
+      : twoStarNoDot
+    , flags = options.nocase ? "i" : ""
+
+  var re = set.map(function (pattern) {
+    return pattern.map(function (p) {
+      return (p === GLOBSTAR) ? twoStar
+           : (typeof p === "string") ? regExpEscape(p)
+           : p._src
+    }).join("\\\/")
+  }).join("|")
+
+  // must match entire pattern
+  // ending in a * or ** will make it less strict.
+  re = "^(?:" + re + ")$"
+
+  // can match anything, as long as it's not this.
+  if (this.negate) re = "^(?!" + re + ").*$"
+
+  try {
+    return this.regexp = new RegExp(re, flags)
+  } catch (ex) {
+    return this.regexp = false
+  }
+}
+
+minimatch.match = function (list, pattern, options) {
+  options = options || {}
+  var mm = new Minimatch(pattern, options)
+  list = list.filter(function (f) {
+    return mm.match(f)
+  })
+  if (mm.options.nonull && !list.length) {
+    list.push(pattern)
+  }
+  return list
+}
+
+Minimatch.prototype.match = match
+function match (f, partial) {
+  this.debug("match", f, this.pattern)
+  // short-circuit in the case of busted things.
+  // comments, etc.
+  if (this.comment) return false
+  if (this.empty) return f === ""
+
+  if (f === "/" && partial) return true
+
+  var options = this.options
+
+  // windows: need to use /, not \
+  // On other platforms, \ is a valid (albeit bad) filename char.
+  if (platform === "win32") {
+    f = f.split("\\").join("/")
+  }
+
+  // treat the test path as a set of pathparts.
+  f = f.split(slashSplit)
+  this.debug(this.pattern, "split", f)
+
+  // just ONE of the pattern sets in this.set needs to match
+  // in order for it to be valid.  If negating, then just one
+  // match means that we have failed.
+  // Either way, return on the first hit.
+
+  var set = this.set
+  this.debug(this.pattern, "set", set)
+
+  // Find the basename of the path by looking for the last non-empty segment
+  var filename;
+  for (var i = f.length - 1; i >= 0; i--) {
+    filename = f[i]
+    if (filename) break
+  }
+
+  for (var i = 0, l = set.length; i < l; i ++) {
+    var pattern = set[i], file = f
+    if (options.matchBase && pattern.length === 1) {
+      file = [filename]
+    }
+    var hit = this.matchOne(file, pattern, partial)
+    if (hit) {
+      if (options.flipNegate) return true
+      return !this.negate
+    }
+  }
+
+  // didn't get any hits.  this is success if it's a negative
+  // pattern, failure otherwise.
+  if (options.flipNegate) return false
+  return this.negate
+}
+
+// set partial to true to test if, for example,
+// "/a/b" matches the start of "/*/b/*/d"
+// Partial means, if you run out of file before you run
+// out of pattern, then that's fine, as long as all
+// the parts match.
+Minimatch.prototype.matchOne = function (file, pattern, partial) {
+  var options = this.options
+
+  this.debug("matchOne",
+              { "this": this
+              , file: file
+              , pattern: pattern })
+
+  this.debug("matchOne", file.length, pattern.length)
+
+  for ( var fi = 0
+          , pi = 0
+          , fl = file.length
+          , pl = pattern.length
+      ; (fi < fl) && (pi < pl)
+      ; fi ++, pi ++ ) {
+
+    this.debug("matchOne loop")
+    var p = pattern[pi]
+      , f = file[fi]
+
+    this.debug(pattern, p, f)
+
+    // should be impossible.
+    // some invalid regexp stuff in the set.
+    if (p === false) return false
+
+    if (p === GLOBSTAR) {
+      this.debug('GLOBSTAR', [pattern, p, f])
+
+      // "**"
+      // a/**/b/**/c would match the following:
+      // a/b/x/y/z/c
+      // a/x/y/z/b/c
+      // a/b/x/b/x/c
+      // a/b/c
+      // To do this, take the rest of the pattern after
+      // the **, and see if it would match the file remainder.
+      // If so, return success.
+      // If not, the ** "swallows" a segment, and try again.
+      // This is recursively awful.
+      //
+      // a/**/b/**/c matching a/b/x/y/z/c
+      // - a matches a
+      // - doublestar
+      //   - matchOne(b/x/y/z/c, b/**/c)
+      //     - b matches b
+      //     - doublestar
+      //       - matchOne(x/y/z/c, c) -> no
+      //       - matchOne(y/z/c, c) -> no
+      //       - matchOne(z/c, c) -> no
+      //       - matchOne(c, c) yes, hit
+      var fr = fi
+        , pr = pi + 1
+      if (pr === pl) {
+        this.debug('** at the end')
+        // a ** at the end will just swallow the rest.
+        // We have found a match.
+        // however, it will not swallow /.x, unless
+        // options.dot is set.
+        // . and .. are *never* matched by **, for explosively
+        // exponential reasons.
+        for ( ; fi < fl; fi ++) {
+          if (file[fi] === "." || file[fi] === ".." ||
+              (!options.dot && file[fi].charAt(0) === ".")) return false
+        }
+        return true
+      }
+
+      // ok, let's see if we can swallow whatever we can.
+      WHILE: while (fr < fl) {
+        var swallowee = file[fr]
+
+        this.debug('\nglobstar while',
+                    file, fr, pattern, pr, swallowee)
+
+        // XXX remove this slice.  Just pass the start index.
+        if (this.matchOne(file.slice(fr), pattern.slice(pr), partial)) {
+          this.debug('globstar found match!', fr, fl, swallowee)
+          // found a match.
+          return true
+        } else {
+          // can't swallow "." or ".." ever.
+          // can only swallow ".foo" when explicitly asked.
+          if (swallowee === "." || swallowee === ".." ||
+              (!options.dot && swallowee.charAt(0) === ".")) {
+            this.debug("dot detected!", file, fr, pattern, pr)
+            break WHILE
+          }
+
+          // ** swallows a segment, and continue.
+          this.debug('globstar swallow a segment, and continue')
+          fr ++
+        }
+      }
+      // no match was found.
+      // However, in partial mode, we can't say this is necessarily over.
+      // If there's more *pattern* left, then
+      if (partial) {
+        // ran out of file
+        this.debug("\n>>> no match, partial?", file, fr, pattern, pr)
+        if (fr === fl) return true
+      }
+      return false
+    }
+
+    // something other than **
+    // non-magic patterns just have to match exactly
+    // patterns with magic have been turned into regexps.
+    var hit
+    if (typeof p === "string") {
+      if (options.nocase) {
+        hit = f.toLowerCase() === p.toLowerCase()
+      } else {
+        hit = f === p
+      }
+      this.debug("string match", p, f, hit)
+    } else {
+      hit = f.match(p)
+      this.debug("pattern match", p, f, hit)
+    }
+
+    if (!hit) return false
+  }
+
+  // Note: ending in / means that we'll get a final ""
+  // at the end of the pattern.  This can only match a
+  // corresponding "" at the end of the file.
+  // If the file ends in /, then it can only match a
+  // a pattern that ends in /, unless the pattern just
+  // doesn't have any more for it. But, a/b/ should *not*
+  // match "a/b/*", even though "" matches against the
+  // [^/]*? pattern, except in partial mode, where it might
+  // simply not be reached yet.
+  // However, a/b/ should still satisfy a/*
+
+  // now either we fell off the end of the pattern, or we're done.
+  if (fi === fl && pi === pl) {
+    // ran out of pattern and filename at the same time.
+    // an exact hit!
+    return true
+  } else if (fi === fl) {
+    // ran out of file, but still had pattern left.
+    // this is ok if we're doing the match as part of
+    // a glob fs traversal.
+    return partial
+  } else if (pi === pl) {
+    // ran out of pattern, still have file left.
+    // this is only acceptable if we're on the very last
+    // empty segment of a file with a trailing slash.
+    // a/* should match a/b/
+    var emptyFileEnd = (fi === fl - 1) && (file[fi] === "")
+    return emptyFileEnd
+  }
+
+  // should be unreachable.
+  throw new Error("wtf?")
+}
+
+
+// replace stuff like \* with *
+function globUnescape (s) {
+  return s.replace(/\\(.)/g, "$1")
+}
+
+
+function regExpEscape (s) {
+  return s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&")
+}
+
+})( typeof require === "function" ? require : null,
+    this,
+    typeof module === "object" ? module : null,
+    typeof process === "object" ? process.platform : "win32"
+  )
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/node_modules/lru-cache/.npmignore b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/node_modules/lru-cache/.npmignore
new file mode 100644
index 0000000..07e6e47
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/node_modules/lru-cache/.npmignore
@@ -0,0 +1 @@
+/node_modules
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/node_modules/lru-cache/CONTRIBUTORS b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/node_modules/lru-cache/CONTRIBUTORS
new file mode 100644
index 0000000..4a0bc50
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/node_modules/lru-cache/CONTRIBUTORS
@@ -0,0 +1,14 @@
+# Authors, sorted by whether or not they are me
+Isaac Z. Schlueter <i@izs.me>
+Brian Cottingham <spiffytech@gmail.com>
+Carlos Brito Lage <carlos@carloslage.net>
+Jesse Dailey <jesse.dailey@gmail.com>
+Kevin O'Hara <kevinohara80@gmail.com>
+Marco Rogers <marco.rogers@gmail.com>
+Mark Cavage <mcavage@gmail.com>
+Marko Mikulicic <marko.mikulicic@isti.cnr.it>
+Nathan Rajlich <nathan@tootallnate.net>
+Satheesh Natesan <snateshan@myspace-inc.com>
+Trent Mick <trentm@gmail.com>
+ashleybrener <ashley@starlogik.com>
+n4kz <n4kz@n4kz.com>
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/node_modules/lru-cache/LICENSE b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/node_modules/lru-cache/LICENSE
new file mode 100644
index 0000000..05a4010
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/node_modules/lru-cache/LICENSE
@@ -0,0 +1,23 @@
+Copyright 2009, 2010, 2011 Isaac Z. Schlueter.
+All rights reserved.
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/node_modules/lru-cache/README.md b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/node_modules/lru-cache/README.md
new file mode 100644
index 0000000..03ee0f9
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/node_modules/lru-cache/README.md
@@ -0,0 +1,97 @@
+# lru cache
+
+A cache object that deletes the least-recently-used items.
+
+## Usage:
+
+```javascript
+var LRU = require("lru-cache")
+  , options = { max: 500
+              , length: function (n) { return n * 2 }
+              , dispose: function (key, n) { n.close() }
+              , maxAge: 1000 * 60 * 60 }
+  , cache = LRU(options)
+  , otherCache = LRU(50) // sets just the max size
+
+cache.set("key", "value")
+cache.get("key") // "value"
+
+cache.reset()    // empty the cache
+```
+
+If you put more stuff in it, then items will fall out.
+
+If you try to put an oversized thing in it, then it'll fall out right
+away.
+
+## Options
+
+* `max` The maximum size of the cache, checked by applying the length
+  function to all values in the cache.  Not setting this is kind of
+  silly, since that's the whole purpose of this lib, but it defaults
+  to `Infinity`.
+* `maxAge` Maximum age in ms.  Items are not pro-actively pruned out
+  as they age, but if you try to get an item that is too old, it'll
+  drop it and return undefined instead of giving it to you.
+* `length` Function that is used to calculate the length of stored
+  items.  If you're storing strings or buffers, then you probably want
+  to do something like `function(n){return n.length}`.  The default is
+  `function(n){return 1}`, which is fine if you want to store `n`
+  like-sized things.
+* `dispose` Function that is called on items when they are dropped
+  from the cache.  This can be handy if you want to close file
+  descriptors or do other cleanup tasks when items are no longer
+  accessible.  Called with `key, value`.  It's called *before*
+  actually removing the item from the internal cache, so if you want
+  to immediately put it back in, you'll have to do that in a
+  `nextTick` or `setTimeout` callback or it won't do anything.
+* `stale` By default, if you set a `maxAge`, it'll only actually pull
+  stale items out of the cache when you `get(key)`.  (That is, it's
+  not pre-emptively doing a `setTimeout` or anything.)  If you set
+  `stale:true`, it'll return the stale value before deleting it.  If
+  you don't set this, then it'll return `undefined` when you try to
+  get a stale entry, as if it had already been deleted.
+
+## API
+
+* `set(key, value)`
+* `get(key) => value`
+
+    Both of these will update the "recently used"-ness of the key.
+    They do what you think.
+
+* `peek(key)`
+
+    Returns the key value (or `undefined` if not found) without
+    updating the "recently used"-ness of the key.
+
+    (If you find yourself using this a lot, you *might* be using the
+    wrong sort of data structure, but there are some use cases where
+    it's handy.)
+
+* `del(key)`
+
+    Deletes a key out of the cache.
+
+* `reset()`
+
+    Clear the cache entirely, throwing away all values.
+
+* `has(key)`
+
+    Check if a key is in the cache, without updating the recent-ness
+    or deleting it for being stale.
+
+* `forEach(function(value,key,cache), [thisp])`
+
+    Just like `Array.prototype.forEach`.  Iterates over all the keys
+    in the cache, in order of recent-ness.  (Ie, more recently used
+    items are iterated over first.)
+
+* `keys()`
+
+    Return an array of the keys in the cache.
+
+* `values()`
+
+    Return an array of the values in the cache.
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/node_modules/lru-cache/lib/lru-cache.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/node_modules/lru-cache/lib/lru-cache.js
new file mode 100644
index 0000000..d1d1381
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/node_modules/lru-cache/lib/lru-cache.js
@@ -0,0 +1,252 @@
+;(function () { // closure for web browsers
+
+if (typeof module === 'object' && module.exports) {
+  module.exports = LRUCache
+} else {
+  // just set the global for non-node platforms.
+  this.LRUCache = LRUCache
+}
+
+function hOP (obj, key) {
+  return Object.prototype.hasOwnProperty.call(obj, key)
+}
+
+function naiveLength () { return 1 }
+
+function LRUCache (options) {
+  if (!(this instanceof LRUCache))
+    return new LRUCache(options)
+
+  if (typeof options === 'number')
+    options = { max: options }
+
+  if (!options)
+    options = {}
+
+  this._max = options.max
+  // Kind of weird to have a default max of Infinity, but oh well.
+  if (!this._max || !(typeof this._max === "number") || this._max <= 0 )
+    this._max = Infinity
+
+  this._lengthCalculator = options.length || naiveLength
+  if (typeof this._lengthCalculator !== "function")
+    this._lengthCalculator = naiveLength
+
+  this._allowStale = options.stale || false
+  this._maxAge = options.maxAge || null
+  this._dispose = options.dispose
+  this.reset()
+}
+
+// resize the cache when the max changes.
+Object.defineProperty(LRUCache.prototype, "max",
+  { set : function (mL) {
+      if (!mL || !(typeof mL === "number") || mL <= 0 ) mL = Infinity
+      this._max = mL
+      if (this._length > this._max) trim(this)
+    }
+  , get : function () { return this._max }
+  , enumerable : true
+  })
+
+// resize the cache when the lengthCalculator changes.
+Object.defineProperty(LRUCache.prototype, "lengthCalculator",
+  { set : function (lC) {
+      if (typeof lC !== "function") {
+        this._lengthCalculator = naiveLength
+        this._length = this._itemCount
+        for (var key in this._cache) {
+          this._cache[key].length = 1
+        }
+      } else {
+        this._lengthCalculator = lC
+        this._length = 0
+        for (var key in this._cache) {
+          this._cache[key].length = this._lengthCalculator(this._cache[key].value)
+          this._length += this._cache[key].length
+        }
+      }
+
+      if (this._length > this._max) trim(this)
+    }
+  , get : function () { return this._lengthCalculator }
+  , enumerable : true
+  })
+
+Object.defineProperty(LRUCache.prototype, "length",
+  { get : function () { return this._length }
+  , enumerable : true
+  })
+
+
+Object.defineProperty(LRUCache.prototype, "itemCount",
+  { get : function () { return this._itemCount }
+  , enumerable : true
+  })
+
+LRUCache.prototype.forEach = function (fn, thisp) {
+  thisp = thisp || this
+  var i = 0;
+  for (var k = this._mru - 1; k >= 0 && i < this._itemCount; k--) if (this._lruList[k]) {
+    i++
+    var hit = this._lruList[k]
+    if (this._maxAge && (Date.now() - hit.now > this._maxAge)) {
+      del(this, hit)
+      if (!this._allowStale) hit = undefined
+    }
+    if (hit) {
+      fn.call(thisp, hit.value, hit.key, this)
+    }
+  }
+}
+
+LRUCache.prototype.keys = function () {
+  var keys = new Array(this._itemCount)
+  var i = 0
+  for (var k = this._mru - 1; k >= 0 && i < this._itemCount; k--) if (this._lruList[k]) {
+    var hit = this._lruList[k]
+    keys[i++] = hit.key
+  }
+  return keys
+}
+
+LRUCache.prototype.values = function () {
+  var values = new Array(this._itemCount)
+  var i = 0
+  for (var k = this._mru - 1; k >= 0 && i < this._itemCount; k--) if (this._lruList[k]) {
+    var hit = this._lruList[k]
+    values[i++] = hit.value
+  }
+  return values
+}
+
+LRUCache.prototype.reset = function () {
+  if (this._dispose && this._cache) {
+    for (var k in this._cache) {
+      this._dispose(k, this._cache[k].value)
+    }
+  }
+
+  this._cache = Object.create(null) // hash of items by key
+  this._lruList = Object.create(null) // list of items in order of use recency
+  this._mru = 0 // most recently used
+  this._lru = 0 // least recently used
+  this._length = 0 // number of items in the list
+  this._itemCount = 0
+}
+
+// Provided for debugging/dev purposes only. No promises whatsoever that
+// this API stays stable.
+LRUCache.prototype.dump = function () {
+  return this._cache
+}
+
+LRUCache.prototype.dumpLru = function () {
+  return this._lruList
+}
+
+LRUCache.prototype.set = function (key, value) {
+  if (hOP(this._cache, key)) {
+    // dispose of the old one before overwriting
+    if (this._dispose) this._dispose(key, this._cache[key].value)
+    if (this._maxAge) this._cache[key].now = Date.now()
+    this._cache[key].value = value
+    this.get(key)
+    return true
+  }
+
+  var len = this._lengthCalculator(value)
+  var age = this._maxAge ? Date.now() : 0
+  var hit = new Entry(key, value, this._mru++, len, age)
+
+  // oversized objects fall out of cache automatically.
+  if (hit.length > this._max) {
+    if (this._dispose) this._dispose(key, value)
+    return false
+  }
+
+  this._length += hit.length
+  this._lruList[hit.lu] = this._cache[key] = hit
+  this._itemCount ++
+
+  if (this._length > this._max) trim(this)
+  return true
+}
+
+LRUCache.prototype.has = function (key) {
+  if (!hOP(this._cache, key)) return false
+  var hit = this._cache[key]
+  if (this._maxAge && (Date.now() - hit.now > this._maxAge)) {
+    return false
+  }
+  return true
+}
+
+LRUCache.prototype.get = function (key) {
+  return get(this, key, true)
+}
+
+LRUCache.prototype.peek = function (key) {
+  return get(this, key, false)
+}
+
+LRUCache.prototype.pop = function () {
+  var hit = this._lruList[this._lru]
+  del(this, hit)
+  return hit || null
+}
+
+LRUCache.prototype.del = function (key) {
+  del(this, this._cache[key])
+}
+
+function get (self, key, doUse) {
+  var hit = self._cache[key]
+  if (hit) {
+    if (self._maxAge && (Date.now() - hit.now > self._maxAge)) {
+      del(self, hit)
+      if (!self._allowStale) hit = undefined
+    } else {
+      if (doUse) use(self, hit)
+    }
+    if (hit) hit = hit.value
+  }
+  return hit
+}
+
+function use (self, hit) {
+  shiftLU(self, hit)
+  hit.lu = self._mru ++
+  self._lruList[hit.lu] = hit
+}
+
+function trim (self) {
+  while (self._lru < self._mru && self._length > self._max)
+    del(self, self._lruList[self._lru])
+}
+
+function shiftLU (self, hit) {
+  delete self._lruList[ hit.lu ]
+  while (self._lru < self._mru && !self._lruList[self._lru]) self._lru ++
+}
+
+function del (self, hit) {
+  if (hit) {
+    if (self._dispose) self._dispose(hit.key, hit.value)
+    self._length -= hit.length
+    self._itemCount --
+    delete self._cache[ hit.key ]
+    shiftLU(self, hit)
+  }
+}
+
+// classy, since V8 prefers predictable objects.
+function Entry (key, value, lu, length, now) {
+  this.key = key
+  this.value = value
+  this.lu = lu
+  this.length = length
+  this.now = now
+}
+
+})()
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/node_modules/lru-cache/package.json b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/node_modules/lru-cache/package.json
new file mode 100644
index 0000000..4472725
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/node_modules/lru-cache/package.json
@@ -0,0 +1,33 @@
+{
+  "name": "lru-cache",
+  "description": "A cache object that deletes the least-recently-used items.",
+  "version": "2.5.0",
+  "author": {
+    "name": "Isaac Z. Schlueter",
+    "email": "i@izs.me"
+  },
+  "scripts": {
+    "test": "tap test --gc"
+  },
+  "main": "lib/lru-cache.js",
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/isaacs/node-lru-cache.git"
+  },
+  "devDependencies": {
+    "tap": "",
+    "weak": ""
+  },
+  "license": {
+    "type": "MIT",
+    "url": "http://github.com/isaacs/node-lru-cache/raw/master/LICENSE"
+  },
+  "readme": "# lru cache\n\nA cache object that deletes the least-recently-used items.\n\n## Usage:\n\n```javascript\nvar LRU = require(\"lru-cache\")\n  , options = { max: 500\n              , length: function (n) { return n * 2 }\n              , dispose: function (key, n) { n.close() }\n              , maxAge: 1000 * 60 * 60 }\n  , cache = LRU(options)\n  , otherCache = LRU(50) // sets just the max size\n\ncache.set(\"key\", \"value\")\ncache.get(\"key\") // \"value\"\n\ncache.reset()    // empty the cache\n```\n\nIf you put more stuff in it, then items will fall out.\n\nIf you try to put an oversized thing in it, then it'll fall out right\naway.\n\n## Options\n\n* `max` The maximum size of the cache, checked by applying the length\n  function to all values in the cache.  Not setting this is kind of\n  silly, since that's the whole purpose of this lib, but it defaults\n  to `Infinity`.\n* `maxAge` Maximum age in ms.  Items are not pro-actively pruned out\n  as they age, but if you try to get an item that is too old, it'll\n  drop it and return undefined instead of giving it to you.\n* `length` Function that is used to calculate the length of stored\n  items.  If you're storing strings or buffers, then you probably want\n  to do something like `function(n){return n.length}`.  The default is\n  `function(n){return 1}`, which is fine if you want to store `n`\n  like-sized things.\n* `dispose` Function that is called on items when they are dropped\n  from the cache.  This can be handy if you want to close file\n  descriptors or do other cleanup tasks when items are no longer\n  accessible.  Called with `key, value`.  It's called *before*\n  actually removing the item from the internal cache, so if you want\n  to immediately put it back in, you'll have to do that in a\n  `nextTick` or `setTimeout` callback or it won't do anything.\n* `stale` By default, if you set a `maxAge`, it'll only actually pull\n  stale items out of the cache when you `get(key)`.  (That is, it's\n  not pre-emptively doing a `setTimeout` or anything.)  If you set\n  `stale:true`, it'll return the stale value before deleting it.  If\n  you don't set this, then it'll return `undefined` when you try to\n  get a stale entry, as if it had already been deleted.\n\n## API\n\n* `set(key, value)`\n* `get(key) => value`\n\n    Both of these will update the \"recently used\"-ness of the key.\n    They do what you think.\n\n* `peek(key)`\n\n    Returns the key value (or `undefined` if not found) without\n    updating the \"recently used\"-ness of the key.\n\n    (If you find yourself using this a lot, you *might* be using the\n    wrong sort of data structure, but there are some use cases where\n    it's handy.)\n\n* `del(key)`\n\n    Deletes a key out of the cache.\n\n* `reset()`\n\n    Clear the cache entirely, throwing away all values.\n\n* `has(key)`\n\n    Check if a key is in the cache, without updating the recent-ness\n    or deleting it for being stale.\n\n* `forEach(function(value,key,cache), [thisp])`\n\n    Just like `Array.prototype.forEach`.  Iterates over all the keys\n    in the cache, in order of recent-ness.  (Ie, more recently used\n    items are iterated over first.)\n\n* `keys()`\n\n    Return an array of the keys in the cache.\n\n* `values()`\n\n    Return an array of the values in the cache.\n",
+  "readmeFilename": "README.md",
+  "bugs": {
+    "url": "https://github.com/isaacs/node-lru-cache/issues"
+  },
+  "homepage": "https://github.com/isaacs/node-lru-cache",
+  "_id": "lru-cache@2.5.0",
+  "_from": "lru-cache@2"
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/node_modules/lru-cache/test/basic.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/node_modules/lru-cache/test/basic.js
new file mode 100644
index 0000000..f72697c
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/node_modules/lru-cache/test/basic.js
@@ -0,0 +1,369 @@
+var test = require("tap").test
+  , LRU = require("../")
+
+test("basic", function (t) {
+  var cache = new LRU({max: 10})
+  cache.set("key", "value")
+  t.equal(cache.get("key"), "value")
+  t.equal(cache.get("nada"), undefined)
+  t.equal(cache.length, 1)
+  t.equal(cache.max, 10)
+  t.end()
+})
+
+test("least recently set", function (t) {
+  var cache = new LRU(2)
+  cache.set("a", "A")
+  cache.set("b", "B")
+  cache.set("c", "C")
+  t.equal(cache.get("c"), "C")
+  t.equal(cache.get("b"), "B")
+  t.equal(cache.get("a"), undefined)
+  t.end()
+})
+
+test("lru recently gotten", function (t) {
+  var cache = new LRU(2)
+  cache.set("a", "A")
+  cache.set("b", "B")
+  cache.get("a")
+  cache.set("c", "C")
+  t.equal(cache.get("c"), "C")
+  t.equal(cache.get("b"), undefined)
+  t.equal(cache.get("a"), "A")
+  t.end()
+})
+
+test("del", function (t) {
+  var cache = new LRU(2)
+  cache.set("a", "A")
+  cache.del("a")
+  t.equal(cache.get("a"), undefined)
+  t.end()
+})
+
+test("max", function (t) {
+  var cache = new LRU(3)
+
+  // test changing the max, verify that the LRU items get dropped.
+  cache.max = 100
+  for (var i = 0; i < 100; i ++) cache.set(i, i)
+  t.equal(cache.length, 100)
+  for (var i = 0; i < 100; i ++) {
+    t.equal(cache.get(i), i)
+  }
+  cache.max = 3
+  t.equal(cache.length, 3)
+  for (var i = 0; i < 97; i ++) {
+    t.equal(cache.get(i), undefined)
+  }
+  for (var i = 98; i < 100; i ++) {
+    t.equal(cache.get(i), i)
+  }
+
+  // now remove the max restriction, and try again.
+  cache.max = "hello"
+  for (var i = 0; i < 100; i ++) cache.set(i, i)
+  t.equal(cache.length, 100)
+  for (var i = 0; i < 100; i ++) {
+    t.equal(cache.get(i), i)
+  }
+  // should trigger an immediate resize
+  cache.max = 3
+  t.equal(cache.length, 3)
+  for (var i = 0; i < 97; i ++) {
+    t.equal(cache.get(i), undefined)
+  }
+  for (var i = 98; i < 100; i ++) {
+    t.equal(cache.get(i), i)
+  }
+  t.end()
+})
+
+test("reset", function (t) {
+  var cache = new LRU(10)
+  cache.set("a", "A")
+  cache.set("b", "B")
+  cache.reset()
+  t.equal(cache.length, 0)
+  t.equal(cache.max, 10)
+  t.equal(cache.get("a"), undefined)
+  t.equal(cache.get("b"), undefined)
+  t.end()
+})
+
+
+// Note: `<cache>.dump()` is a debugging tool only. No guarantees are made
+// about the format/layout of the response.
+test("dump", function (t) {
+  var cache = new LRU(10)
+  var d = cache.dump();
+  t.equal(Object.keys(d).length, 0, "nothing in dump for empty cache")
+  cache.set("a", "A")
+  var d = cache.dump()  // { a: { key: "a", value: "A", lu: 0 } }
+  t.ok(d.a)
+  t.equal(d.a.key, "a")
+  t.equal(d.a.value, "A")
+  t.equal(d.a.lu, 0)
+
+  cache.set("b", "B")
+  cache.get("b")
+  d = cache.dump()
+  t.ok(d.b)
+  t.equal(d.b.key, "b")
+  t.equal(d.b.value, "B")
+  t.equal(d.b.lu, 2)
+
+  t.end()
+})
+
+
+test("basic with weighed length", function (t) {
+  var cache = new LRU({
+    max: 100,
+    length: function (item) { return item.size }
+  })
+  cache.set("key", {val: "value", size: 50})
+  t.equal(cache.get("key").val, "value")
+  t.equal(cache.get("nada"), undefined)
+  t.equal(cache.lengthCalculator(cache.get("key")), 50)
+  t.equal(cache.length, 50)
+  t.equal(cache.max, 100)
+  t.end()
+})
+
+
+test("weighed length item too large", function (t) {
+  var cache = new LRU({
+    max: 10,
+    length: function (item) { return item.size }
+  })
+  t.equal(cache.max, 10)
+
+  // should fall out immediately
+  cache.set("key", {val: "value", size: 50})
+
+  t.equal(cache.length, 0)
+  t.equal(cache.get("key"), undefined)
+  t.end()
+})
+
+test("least recently set with weighed length", function (t) {
+  var cache = new LRU({
+    max:8,
+    length: function (item) { return item.length }
+  })
+  cache.set("a", "A")
+  cache.set("b", "BB")
+  cache.set("c", "CCC")
+  cache.set("d", "DDDD")
+  t.equal(cache.get("d"), "DDDD")
+  t.equal(cache.get("c"), "CCC")
+  t.equal(cache.get("b"), undefined)
+  t.equal(cache.get("a"), undefined)
+  t.end()
+})
+
+test("lru recently gotten with weighed length", function (t) {
+  var cache = new LRU({
+    max: 8,
+    length: function (item) { return item.length }
+  })
+  cache.set("a", "A")
+  cache.set("b", "BB")
+  cache.set("c", "CCC")
+  cache.get("a")
+  cache.get("b")
+  cache.set("d", "DDDD")
+  t.equal(cache.get("c"), undefined)
+  t.equal(cache.get("d"), "DDDD")
+  t.equal(cache.get("b"), "BB")
+  t.equal(cache.get("a"), "A")
+  t.end()
+})
+
+test("set returns proper booleans", function(t) {
+  var cache = new LRU({
+    max: 5,
+    length: function (item) { return item.length }
+  })
+
+  t.equal(cache.set("a", "A"), true)
+
+  // should return false for max exceeded
+  t.equal(cache.set("b", "donuts"), false)
+
+  t.equal(cache.set("b", "B"), true)
+  t.equal(cache.set("c", "CCCC"), true)
+  t.end()
+})
+
+test("drop the old items", function(t) {
+  var cache = new LRU({
+    max: 5,
+    maxAge: 50
+  })
+
+  cache.set("a", "A")
+
+  setTimeout(function () {
+    cache.set("b", "b")
+    t.equal(cache.get("a"), "A")
+  }, 25)
+
+  setTimeout(function () {
+    cache.set("c", "C")
+    // timed out
+    t.notOk(cache.get("a"))
+  }, 60)
+
+  setTimeout(function () {
+    t.notOk(cache.get("b"))
+    t.equal(cache.get("c"), "C")
+  }, 90)
+
+  setTimeout(function () {
+    t.notOk(cache.get("c"))
+    t.end()
+  }, 155)
+})
+
+test("disposal function", function(t) {
+  var disposed = false
+  var cache = new LRU({
+    max: 1,
+    dispose: function (k, n) {
+      disposed = n
+    }
+  })
+
+  cache.set(1, 1)
+  cache.set(2, 2)
+  t.equal(disposed, 1)
+  cache.set(3, 3)
+  t.equal(disposed, 2)
+  cache.reset()
+  t.equal(disposed, 3)
+  t.end()
+})
+
+test("disposal function on too big of item", function(t) {
+  var disposed = false
+  var cache = new LRU({
+    max: 1,
+    length: function (k) {
+      return k.length
+    },
+    dispose: function (k, n) {
+      disposed = n
+    }
+  })
+  var obj = [ 1, 2 ]
+
+  t.equal(disposed, false)
+  cache.set("obj", obj)
+  t.equal(disposed, obj)
+  t.end()
+})
+
+test("has()", function(t) {
+  var cache = new LRU({
+    max: 1,
+    maxAge: 10
+  })
+
+  cache.set('foo', 'bar')
+  t.equal(cache.has('foo'), true)
+  cache.set('blu', 'baz')
+  t.equal(cache.has('foo'), false)
+  t.equal(cache.has('blu'), true)
+  setTimeout(function() {
+    t.equal(cache.has('blu'), false)
+    t.end()
+  }, 15)
+})
+
+test("stale", function(t) {
+  var cache = new LRU({
+    maxAge: 10,
+    stale: true
+  })
+
+  cache.set('foo', 'bar')
+  t.equal(cache.get('foo'), 'bar')
+  t.equal(cache.has('foo'), true)
+  setTimeout(function() {
+    t.equal(cache.has('foo'), false)
+    t.equal(cache.get('foo'), 'bar')
+    t.equal(cache.get('foo'), undefined)
+    t.end()
+  }, 15)
+})
+
+test("lru update via set", function(t) {
+  var cache = LRU({ max: 2 });
+
+  cache.set('foo', 1);
+  cache.set('bar', 2);
+  cache.del('bar');
+  cache.set('baz', 3);
+  cache.set('qux', 4);
+
+  t.equal(cache.get('foo'), undefined)
+  t.equal(cache.get('bar'), undefined)
+  t.equal(cache.get('baz'), 3)
+  t.equal(cache.get('qux'), 4)
+  t.end()
+})
+
+test("least recently set w/ peek", function (t) {
+  var cache = new LRU(2)
+  cache.set("a", "A")
+  cache.set("b", "B")
+  t.equal(cache.peek("a"), "A")
+  cache.set("c", "C")
+  t.equal(cache.get("c"), "C")
+  t.equal(cache.get("b"), "B")
+  t.equal(cache.get("a"), undefined)
+  t.end()
+})
+
+test("pop the least used item", function (t) {
+  var cache = new LRU(3)
+  , last
+
+  cache.set("a", "A")
+  cache.set("b", "B")
+  cache.set("c", "C")
+
+  t.equal(cache.length, 3)
+  t.equal(cache.max, 3)
+
+  // Ensure we pop a, c, b
+  cache.get("b", "B")
+
+  last = cache.pop()
+  t.equal(last.key, "a")
+  t.equal(last.value, "A")
+  t.equal(cache.length, 2)
+  t.equal(cache.max, 3)
+
+  last = cache.pop()
+  t.equal(last.key, "c")
+  t.equal(last.value, "C")
+  t.equal(cache.length, 1)
+  t.equal(cache.max, 3)
+
+  last = cache.pop()
+  t.equal(last.key, "b")
+  t.equal(last.value, "B")
+  t.equal(cache.length, 0)
+  t.equal(cache.max, 3)
+
+  last = cache.pop()
+  t.equal(last, null)
+  t.equal(cache.length, 0)
+  t.equal(cache.max, 3)
+
+  t.end()
+})
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/node_modules/lru-cache/test/foreach.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/node_modules/lru-cache/test/foreach.js
new file mode 100644
index 0000000..eefb80d
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/node_modules/lru-cache/test/foreach.js
@@ -0,0 +1,52 @@
+var test = require('tap').test
+var LRU = require('../')
+
+test('forEach', function (t) {
+  var l = new LRU(5)
+  for (var i = 0; i < 10; i ++) {
+    l.set(i.toString(), i.toString(2))
+  }
+
+  var i = 9
+  l.forEach(function (val, key, cache) {
+    t.equal(cache, l)
+    t.equal(key, i.toString())
+    t.equal(val, i.toString(2))
+    i -= 1
+  })
+
+  // get in order of most recently used
+  l.get(6)
+  l.get(8)
+
+  var order = [ 8, 6, 9, 7, 5 ]
+  var i = 0
+
+  l.forEach(function (val, key, cache) {
+    var j = order[i ++]
+    t.equal(cache, l)
+    t.equal(key, j.toString())
+    t.equal(val, j.toString(2))
+  })
+
+  t.end()
+})
+
+test('keys() and values()', function (t) {
+  var l = new LRU(5)
+  for (var i = 0; i < 10; i ++) {
+    l.set(i.toString(), i.toString(2))
+  }
+
+  t.similar(l.keys(), ['9', '8', '7', '6', '5'])
+  t.similar(l.values(), ['1001', '1000', '111', '110', '101'])
+
+  // get in order of most recently used
+  l.get(6)
+  l.get(8)
+
+  t.similar(l.keys(), ['8', '6', '9', '7', '5'])
+  t.similar(l.values(), ['1000', '110', '1001', '111', '101'])
+
+  t.end()
+})
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/node_modules/lru-cache/test/memory-leak.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/node_modules/lru-cache/test/memory-leak.js
new file mode 100644
index 0000000..7af45b0
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/node_modules/lru-cache/test/memory-leak.js
@@ -0,0 +1,50 @@
+#!/usr/bin/env node --expose_gc
+
+var weak = require('weak');
+var test = require('tap').test
+var LRU = require('../')
+var l = new LRU({ max: 10 })
+var refs = 0
+function X() {
+  refs ++
+  weak(this, deref)
+}
+
+function deref() {
+  refs --
+}
+
+test('no leaks', function (t) {
+  // fill up the cache
+  for (var i = 0; i < 100; i++) {
+    l.set(i, new X);
+    // throw some gets in there, too.
+    if (i % 2 === 0)
+      l.get(i / 2)
+  }
+
+  gc()
+
+  var start = process.memoryUsage()
+
+  // capture the memory
+  var startRefs = refs
+
+  // do it again, but more
+  for (var i = 0; i < 10000; i++) {
+    l.set(i, new X);
+    // throw some gets in there, too.
+    if (i % 2 === 0)
+      l.get(i / 2)
+  }
+
+  gc()
+
+  var end = process.memoryUsage()
+  t.equal(refs, startRefs, 'no leaky refs')
+
+  console.error('start: %j\n' +
+                'end:   %j', start, end);
+  t.pass();
+  t.end();
+})
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/node_modules/sigmund/LICENSE b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/node_modules/sigmund/LICENSE
new file mode 100644
index 0000000..0c44ae7
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/node_modules/sigmund/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) Isaac Z. Schlueter ("Author")
+All rights reserved.
+
+The BSD License
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/node_modules/sigmund/README.md b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/node_modules/sigmund/README.md
new file mode 100644
index 0000000..7e36512
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/node_modules/sigmund/README.md
@@ -0,0 +1,53 @@
+# sigmund
+
+Quick and dirty signatures for Objects.
+
+This is like a much faster `deepEquals` comparison, which returns a
+string key suitable for caches and the like.
+
+## Usage
+
+```javascript
+function doSomething (someObj) {
+  var key = sigmund(someObj, maxDepth) // max depth defaults to 10
+  var cached = cache.get(key)
+  if (cached) return cached)
+
+  var result = expensiveCalculation(someObj)
+  cache.set(key, result)
+  return result
+}
+```
+
+The resulting key will be as unique and reproducible as calling
+`JSON.stringify` or `util.inspect` on the object, but is much faster.
+In order to achieve this speed, some differences are glossed over.
+For example, the object `{0:'foo'}` will be treated identically to the
+array `['foo']`.
+
+Also, just as there is no way to summon the soul from the scribblings
+of a cocain-addled psychoanalyst, there is no way to revive the object
+from the signature string that sigmund gives you.  In fact, it's
+barely even readable.
+
+As with `sys.inspect` and `JSON.stringify`, larger objects will
+produce larger signature strings.
+
+Because sigmund is a bit less strict than the more thorough
+alternatives, the strings will be shorter, and also there is a
+slightly higher chance for collisions.  For example, these objects
+have the same signature:
+
+    var obj1 = {a:'b',c:/def/,g:['h','i',{j:'',k:'l'}]}
+    var obj2 = {a:'b',c:'/def/',g:['h','i','{jkl']}
+
+Like a good Freudian, sigmund is most effective when you already have
+some understanding of what you're looking for.  It can help you help
+yourself, but you must be willing to do some work as well.
+
+Cycles are handled, and cyclical objects are silently omitted (though
+the key is included in the signature output.)
+
+The second argument is the maximum depth, which defaults to 10,
+because that is the maximum object traversal depth covered by most
+insurance carriers.
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/node_modules/sigmund/bench.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/node_modules/sigmund/bench.js
new file mode 100644
index 0000000..5acfd6d
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/node_modules/sigmund/bench.js
@@ -0,0 +1,283 @@
+// different ways to id objects
+// use a req/res pair, since it's crazy deep and cyclical
+
+// sparseFE10 and sigmund are usually pretty close, which is to be expected,
+// since they are essentially the same algorithm, except that sigmund handles
+// regular expression objects properly.
+
+
+var http = require('http')
+var util = require('util')
+var sigmund = require('./sigmund.js')
+var sreq, sres, creq, cres, test
+
+http.createServer(function (q, s) {
+  sreq = q
+  sres = s
+  sres.end('ok')
+  this.close(function () { setTimeout(function () {
+    start()
+  }, 200) })
+}).listen(1337, function () {
+  creq = http.get({ port: 1337 })
+  creq.on('response', function (s) { cres = s })
+})
+
+function start () {
+  test = [sreq, sres, creq, cres]
+  // test = sreq
+  // sreq.sres = sres
+  // sreq.creq = creq
+  // sreq.cres = cres
+
+  for (var i in exports.compare) {
+    console.log(i)
+    var hash = exports.compare[i]()
+    console.log(hash)
+    console.log(hash.length)
+    console.log('')
+  }
+
+  require('bench').runMain()
+}
+
+function customWs (obj, md, d) {
+  d = d || 0
+  var to = typeof obj
+  if (to === 'undefined' || to === 'function' || to === null) return ''
+  if (d > md || !obj || to !== 'object') return ('' + obj).replace(/[\n ]+/g, '')
+
+  if (Array.isArray(obj)) {
+    return obj.map(function (i, _, __) {
+      return customWs(i, md, d + 1)
+    }).reduce(function (a, b) { return a + b }, '')
+  }
+
+  var keys = Object.keys(obj)
+  return keys.map(function (k, _, __) {
+    return k + ':' + customWs(obj[k], md, d + 1)
+  }).reduce(function (a, b) { return a + b }, '')
+}
+
+function custom (obj, md, d) {
+  d = d || 0
+  var to = typeof obj
+  if (to === 'undefined' || to === 'function' || to === null) return ''
+  if (d > md || !obj || to !== 'object') return '' + obj
+
+  if (Array.isArray(obj)) {
+    return obj.map(function (i, _, __) {
+      return custom(i, md, d + 1)
+    }).reduce(function (a, b) { return a + b }, '')
+  }
+
+  var keys = Object.keys(obj)
+  return keys.map(function (k, _, __) {
+    return k + ':' + custom(obj[k], md, d + 1)
+  }).reduce(function (a, b) { return a + b }, '')
+}
+
+function sparseFE2 (obj, maxDepth) {
+  var seen = []
+  var soFar = ''
+  function ch (v, depth) {
+    if (depth > maxDepth) return
+    if (typeof v === 'function' || typeof v === 'undefined') return
+    if (typeof v !== 'object' || !v) {
+      soFar += v
+      return
+    }
+    if (seen.indexOf(v) !== -1 || depth === maxDepth) return
+    seen.push(v)
+    soFar += '{'
+    Object.keys(v).forEach(function (k, _, __) {
+      // pseudo-private values.  skip those.
+      if (k.charAt(0) === '_') return
+      var to = typeof v[k]
+      if (to === 'function' || to === 'undefined') return
+      soFar += k + ':'
+      ch(v[k], depth + 1)
+    })
+    soFar += '}'
+  }
+  ch(obj, 0)
+  return soFar
+}
+
+function sparseFE (obj, maxDepth) {
+  var seen = []
+  var soFar = ''
+  function ch (v, depth) {
+    if (depth > maxDepth) return
+    if (typeof v === 'function' || typeof v === 'undefined') return
+    if (typeof v !== 'object' || !v) {
+      soFar += v
+      return
+    }
+    if (seen.indexOf(v) !== -1 || depth === maxDepth) return
+    seen.push(v)
+    soFar += '{'
+    Object.keys(v).forEach(function (k, _, __) {
+      // pseudo-private values.  skip those.
+      if (k.charAt(0) === '_') return
+      var to = typeof v[k]
+      if (to === 'function' || to === 'undefined') return
+      soFar += k
+      ch(v[k], depth + 1)
+    })
+  }
+  ch(obj, 0)
+  return soFar
+}
+
+function sparse (obj, maxDepth) {
+  var seen = []
+  var soFar = ''
+  function ch (v, depth) {
+    if (depth > maxDepth) return
+    if (typeof v === 'function' || typeof v === 'undefined') return
+    if (typeof v !== 'object' || !v) {
+      soFar += v
+      return
+    }
+    if (seen.indexOf(v) !== -1 || depth === maxDepth) return
+    seen.push(v)
+    soFar += '{'
+    for (var k in v) {
+      // pseudo-private values.  skip those.
+      if (k.charAt(0) === '_') continue
+      var to = typeof v[k]
+      if (to === 'function' || to === 'undefined') continue
+      soFar += k
+      ch(v[k], depth + 1)
+    }
+  }
+  ch(obj, 0)
+  return soFar
+}
+
+function noCommas (obj, maxDepth) {
+  var seen = []
+  var soFar = ''
+  function ch (v, depth) {
+    if (depth > maxDepth) return
+    if (typeof v === 'function' || typeof v === 'undefined') return
+    if (typeof v !== 'object' || !v) {
+      soFar += v
+      return
+    }
+    if (seen.indexOf(v) !== -1 || depth === maxDepth) return
+    seen.push(v)
+    soFar += '{'
+    for (var k in v) {
+      // pseudo-private values.  skip those.
+      if (k.charAt(0) === '_') continue
+      var to = typeof v[k]
+      if (to === 'function' || to === 'undefined') continue
+      soFar += k + ':'
+      ch(v[k], depth + 1)
+    }
+    soFar += '}'
+  }
+  ch(obj, 0)
+  return soFar
+}
+
+
+function flatten (obj, maxDepth) {
+  var seen = []
+  var soFar = ''
+  function ch (v, depth) {
+    if (depth > maxDepth) return
+    if (typeof v === 'function' || typeof v === 'undefined') return
+    if (typeof v !== 'object' || !v) {
+      soFar += v
+      return
+    }
+    if (seen.indexOf(v) !== -1 || depth === maxDepth) return
+    seen.push(v)
+    soFar += '{'
+    for (var k in v) {
+      // pseudo-private values.  skip those.
+      if (k.charAt(0) === '_') continue
+      var to = typeof v[k]
+      if (to === 'function' || to === 'undefined') continue
+      soFar += k + ':'
+      ch(v[k], depth + 1)
+      soFar += ','
+    }
+    soFar += '}'
+  }
+  ch(obj, 0)
+  return soFar
+}
+
+exports.compare =
+{
+  // 'custom 2': function () {
+  //   return custom(test, 2, 0)
+  // },
+  // 'customWs 2': function () {
+  //   return customWs(test, 2, 0)
+  // },
+  'JSON.stringify (guarded)': function () {
+    var seen = []
+    return JSON.stringify(test, function (k, v) {
+      if (typeof v !== 'object' || !v) return v
+      if (seen.indexOf(v) !== -1) return undefined
+      seen.push(v)
+      return v
+    })
+  },
+
+  'flatten 10': function () {
+    return flatten(test, 10)
+  },
+
+  // 'flattenFE 10': function () {
+  //   return flattenFE(test, 10)
+  // },
+
+  'noCommas 10': function () {
+    return noCommas(test, 10)
+  },
+
+  'sparse 10': function () {
+    return sparse(test, 10)
+  },
+
+  'sparseFE 10': function () {
+    return sparseFE(test, 10)
+  },
+
+  'sparseFE2 10': function () {
+    return sparseFE2(test, 10)
+  },
+
+  sigmund: function() {
+    return sigmund(test, 10)
+  },
+
+
+  // 'util.inspect 1': function () {
+  //   return util.inspect(test, false, 1, false)
+  // },
+  // 'util.inspect undefined': function () {
+  //   util.inspect(test)
+  // },
+  // 'util.inspect 2': function () {
+  //   util.inspect(test, false, 2, false)
+  // },
+  // 'util.inspect 3': function () {
+  //   util.inspect(test, false, 3, false)
+  // },
+  // 'util.inspect 4': function () {
+  //   util.inspect(test, false, 4, false)
+  // },
+  // 'util.inspect Infinity': function () {
+  //   util.inspect(test, false, Infinity, false)
+  // }
+}
+
+/** results
+**/
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/node_modules/sigmund/package.json b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/node_modules/sigmund/package.json
new file mode 100644
index 0000000..cb7e2bd
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/node_modules/sigmund/package.json
@@ -0,0 +1,42 @@
+{
+  "name": "sigmund",
+  "version": "1.0.0",
+  "description": "Quick and dirty signatures for Objects.",
+  "main": "sigmund.js",
+  "directories": {
+    "test": "test"
+  },
+  "dependencies": {},
+  "devDependencies": {
+    "tap": "~0.3.0"
+  },
+  "scripts": {
+    "test": "tap test/*.js",
+    "bench": "node bench.js"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/isaacs/sigmund"
+  },
+  "keywords": [
+    "object",
+    "signature",
+    "key",
+    "data",
+    "psychoanalysis"
+  ],
+  "author": {
+    "name": "Isaac Z. Schlueter",
+    "email": "i@izs.me",
+    "url": "http://blog.izs.me/"
+  },
+  "license": "BSD",
+  "readme": "# sigmund\n\nQuick and dirty signatures for Objects.\n\nThis is like a much faster `deepEquals` comparison, which returns a\nstring key suitable for caches and the like.\n\n## Usage\n\n```javascript\nfunction doSomething (someObj) {\n  var key = sigmund(someObj, maxDepth) // max depth defaults to 10\n  var cached = cache.get(key)\n  if (cached) return cached)\n\n  var result = expensiveCalculation(someObj)\n  cache.set(key, result)\n  return result\n}\n```\n\nThe resulting key will be as unique and reproducible as calling\n`JSON.stringify` or `util.inspect` on the object, but is much faster.\nIn order to achieve this speed, some differences are glossed over.\nFor example, the object `{0:'foo'}` will be treated identically to the\narray `['foo']`.\n\nAlso, just as there is no way to summon the soul from the scribblings\nof a cocain-addled psychoanalyst, there is no way to revive the object\nfrom the signature string that sigmund gives you.  In fact, it's\nbarely even readable.\n\nAs with `sys.inspect` and `JSON.stringify`, larger objects will\nproduce larger signature strings.\n\nBecause sigmund is a bit less strict than the more thorough\nalternatives, the strings will be shorter, and also there is a\nslightly higher chance for collisions.  For example, these objects\nhave the same signature:\n\n    var obj1 = {a:'b',c:/def/,g:['h','i',{j:'',k:'l'}]}\n    var obj2 = {a:'b',c:'/def/',g:['h','i','{jkl']}\n\nLike a good Freudian, sigmund is most effective when you already have\nsome understanding of what you're looking for.  It can help you help\nyourself, but you must be willing to do some work as well.\n\nCycles are handled, and cyclical objects are silently omitted (though\nthe key is included in the signature output.)\n\nThe second argument is the maximum depth, which defaults to 10,\nbecause that is the maximum object traversal depth covered by most\ninsurance carriers.\n",
+  "readmeFilename": "README.md",
+  "bugs": {
+    "url": "https://github.com/isaacs/sigmund/issues"
+  },
+  "homepage": "https://github.com/isaacs/sigmund",
+  "_id": "sigmund@1.0.0",
+  "_from": "sigmund@~1.0.0"
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/node_modules/sigmund/sigmund.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/node_modules/sigmund/sigmund.js
new file mode 100644
index 0000000..82c7ab8
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/node_modules/sigmund/sigmund.js
@@ -0,0 +1,39 @@
+module.exports = sigmund
+function sigmund (subject, maxSessions) {
+    maxSessions = maxSessions || 10;
+    var notes = [];
+    var analysis = '';
+    var RE = RegExp;
+
+    function psychoAnalyze (subject, session) {
+        if (session > maxSessions) return;
+
+        if (typeof subject === 'function' ||
+            typeof subject === 'undefined') {
+            return;
+        }
+
+        if (typeof subject !== 'object' || !subject ||
+            (subject instanceof RE)) {
+            analysis += subject;
+            return;
+        }
+
+        if (notes.indexOf(subject) !== -1 || session === maxSessions) return;
+
+        notes.push(subject);
+        analysis += '{';
+        Object.keys(subject).forEach(function (issue, _, __) {
+            // pseudo-private values.  skip those.
+            if (issue.charAt(0) === '_') return;
+            var to = typeof subject[issue];
+            if (to === 'function' || to === 'undefined') return;
+            analysis += issue;
+            psychoAnalyze(subject[issue], session + 1);
+        });
+    }
+    psychoAnalyze(subject, 0);
+    return analysis;
+}
+
+// vim: set softtabstop=4 shiftwidth=4:
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/node_modules/sigmund/test/basic.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/node_modules/sigmund/test/basic.js
new file mode 100644
index 0000000..50c53a1
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/node_modules/sigmund/test/basic.js
@@ -0,0 +1,24 @@
+var test = require('tap').test
+var sigmund = require('../sigmund.js')
+
+
+// occasionally there are duplicates
+// that's an acceptable edge-case.  JSON.stringify and util.inspect
+// have some collision potential as well, though less, and collision
+// detection is expensive.
+var hash = '{abc/def/g{0h1i2{jkl'
+var obj1 = {a:'b',c:/def/,g:['h','i',{j:'',k:'l'}]}
+var obj2 = {a:'b',c:'/def/',g:['h','i','{jkl']}
+
+var obj3 = JSON.parse(JSON.stringify(obj1))
+obj3.c = /def/
+obj3.g[2].cycle = obj3
+var cycleHash = '{abc/def/g{0h1i2{jklcycle'
+
+test('basic', function (t) {
+    t.equal(sigmund(obj1), hash)
+    t.equal(sigmund(obj2), hash)
+    t.equal(sigmund(obj3), cycleHash)
+    t.end()
+})
+
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/package.json b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/package.json
new file mode 100644
index 0000000..3b95796
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/package.json
@@ -0,0 +1,58 @@
+{
+  "author": {
+    "name": "Isaac Z. Schlueter",
+    "email": "i@izs.me",
+    "url": "http://blog.izs.me"
+  },
+  "name": "minimatch",
+  "description": "a glob matcher in javascript",
+  "version": "0.3.0",
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/isaacs/minimatch.git"
+  },
+  "main": "minimatch.js",
+  "scripts": {
+    "test": "tap test/*.js"
+  },
+  "engines": {
+    "node": "*"
+  },
+  "dependencies": {
+    "lru-cache": "2",
+    "sigmund": "~1.0.0"
+  },
+  "devDependencies": {
+    "tap": ""
+  },
+  "license": {
+    "type": "MIT",
+    "url": "http://github.com/isaacs/minimatch/raw/master/LICENSE"
+  },
+  "bugs": {
+    "url": "https://github.com/isaacs/minimatch/issues"
+  },
+  "homepage": "https://github.com/isaacs/minimatch",
+  "_id": "minimatch@0.3.0",
+  "_shasum": "275d8edaac4f1bb3326472089e7949c8394699dd",
+  "_from": "minimatch@0.3",
+  "_npmVersion": "1.4.10",
+  "_npmUser": {
+    "name": "isaacs",
+    "email": "i@izs.me"
+  },
+  "maintainers": [
+    {
+      "name": "isaacs",
+      "email": "i@izs.me"
+    }
+  ],
+  "dist": {
+    "shasum": "275d8edaac4f1bb3326472089e7949c8394699dd",
+    "tarball": "http://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz"
+  },
+  "directories": {},
+  "_resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz",
+  "readme": "# minimatch\n\nA minimal matching utility.\n\n[![Build Status](https://secure.travis-ci.org/isaacs/minimatch.png)](http://travis-ci.org/isaacs/minimatch)\n\n\nThis is the matching library used internally by npm.\n\nEventually, it will replace the C binding in node-glob.\n\nIt works by converting glob expressions into JavaScript `RegExp`\nobjects.\n\n## Usage\n\n```javascript\nvar minimatch = require(\"minimatch\")\n\nminimatch(\"bar.foo\", \"*.foo\") // true!\nminimatch(\"bar.foo\", \"*.bar\") // false!\nminimatch(\"bar.foo\", \"*.+(bar|foo)\", { debug: true }) // true, and noisy!\n```\n\n## Features\n\nSupports these glob features:\n\n* Brace Expansion\n* Extended glob matching\n* \"Globstar\" `**` matching\n\nSee:\n\n* `man sh`\n* `man bash`\n* `man 3 fnmatch`\n* `man 5 gitignore`\n\n## Minimatch Class\n\nCreate a minimatch object by instanting the `minimatch.Minimatch` class.\n\n```javascript\nvar Minimatch = require(\"minimatch\").Minimatch\nvar mm = new Minimatch(pattern, options)\n```\n\n### Properties\n\n* `pattern` The original pattern the minimatch object represents.\n* `options` The options supplied to the constructor.\n* `set` A 2-dimensional array of regexp or string expressions.\n  Each row in the\n  array corresponds to a brace-expanded pattern.  Each item in the row\n  corresponds to a single path-part.  For example, the pattern\n  `{a,b/c}/d` would expand to a set of patterns like:\n\n        [ [ a, d ]\n        , [ b, c, d ] ]\n\n    If a portion of the pattern doesn't have any \"magic\" in it\n    (that is, it's something like `\"foo\"` rather than `fo*o?`), then it\n    will be left as a string rather than converted to a regular\n    expression.\n\n* `regexp` Created by the `makeRe` method.  A single regular expression\n  expressing the entire pattern.  This is useful in cases where you wish\n  to use the pattern somewhat like `fnmatch(3)` with `FNM_PATH` enabled.\n* `negate` True if the pattern is negated.\n* `comment` True if the pattern is a comment.\n* `empty` True if the pattern is `\"\"`.\n\n### Methods\n\n* `makeRe` Generate the `regexp` member if necessary, and return it.\n  Will return `false` if the pattern is invalid.\n* `match(fname)` Return true if the filename matches the pattern, or\n  false otherwise.\n* `matchOne(fileArray, patternArray, partial)` Take a `/`-split\n  filename, and match it against a single row in the `regExpSet`.  This\n  method is mainly for internal use, but is exposed so that it can be\n  used by a glob-walker that needs to avoid excessive filesystem calls.\n\nAll other methods are internal, and will be called as necessary.\n\n## Functions\n\nThe top-level exported function has a `cache` property, which is an LRU\ncache set to store 100 items.  So, calling these methods repeatedly\nwith the same pattern and options will use the same Minimatch object,\nsaving the cost of parsing it multiple times.\n\n### minimatch(path, pattern, options)\n\nMain export.  Tests a path against the pattern using the options.\n\n```javascript\nvar isJS = minimatch(file, \"*.js\", { matchBase: true })\n```\n\n### minimatch.filter(pattern, options)\n\nReturns a function that tests its\nsupplied argument, suitable for use with `Array.filter`.  Example:\n\n```javascript\nvar javascripts = fileList.filter(minimatch.filter(\"*.js\", {matchBase: true}))\n```\n\n### minimatch.match(list, pattern, options)\n\nMatch against the list of\nfiles, in the style of fnmatch or glob.  If nothing is matched, and\noptions.nonull is set, then return a list containing the pattern itself.\n\n```javascript\nvar javascripts = minimatch.match(fileList, \"*.js\", {matchBase: true}))\n```\n\n### minimatch.makeRe(pattern, options)\n\nMake a regular expression object from the pattern.\n\n## Options\n\nAll options are `false` by default.\n\n### debug\n\nDump a ton of stuff to stderr.\n\n### nobrace\n\nDo not expand `{a,b}` and `{1..3}` brace sets.\n\n### noglobstar\n\nDisable `**` matching against multiple folder names.\n\n### dot\n\nAllow patterns to match filenames starting with a period, even if\nthe pattern does not explicitly have a period in that spot.\n\nNote that by default, `a/**/b` will **not** match `a/.d/b`, unless `dot`\nis set.\n\n### noext\n\nDisable \"extglob\" style patterns like `+(a|b)`.\n\n### nocase\n\nPerform a case-insensitive match.\n\n### nonull\n\nWhen a match is not found by `minimatch.match`, return a list containing\nthe pattern itself if this option is set.  When not set, an empty list\nis returned if there are no matches.\n\n### matchBase\n\nIf set, then patterns without slashes will be matched\nagainst the basename of the path if it contains slashes.  For example,\n`a?b` would match the path `/xyz/123/acb`, but not `/xyz/acb/123`.\n\n### nocomment\n\nSuppress the behavior of treating `#` at the start of a pattern as a\ncomment.\n\n### nonegate\n\nSuppress the behavior of treating a leading `!` character as negation.\n\n### flipNegate\n\nReturns from negate expressions the same as if they were not negated.\n(Ie, true on a hit, false on a miss.)\n\n\n## Comparisons to other fnmatch/glob implementations\n\nWhile strict compliance with the existing standards is a worthwhile\ngoal, some discrepancies exist between minimatch and other\nimplementations, and are intentional.\n\nIf the pattern starts with a `!` character, then it is negated.  Set the\n`nonegate` flag to suppress this behavior, and treat leading `!`\ncharacters normally.  This is perhaps relevant if you wish to start the\npattern with a negative extglob pattern like `!(a|B)`.  Multiple `!`\ncharacters at the start of a pattern will negate the pattern multiple\ntimes.\n\nIf a pattern starts with `#`, then it is treated as a comment, and\nwill not match anything.  Use `\\#` to match a literal `#` at the\nstart of a line, or set the `nocomment` flag to suppress this behavior.\n\nThe double-star character `**` is supported by default, unless the\n`noglobstar` flag is set.  This is supported in the manner of bsdglob\nand bash 4.1, where `**` only has special significance if it is the only\nthing in a path part.  That is, `a/**/b` will match `a/x/y/b`, but\n`a/**b` will not.\n\nIf an escaped pattern has no matches, and the `nonull` flag is set,\nthen minimatch.match returns the pattern as-provided, rather than\ninterpreting the character escapes.  For example,\n`minimatch.match([], \"\\\\*a\\\\?\")` will return `\"\\\\*a\\\\?\"` rather than\n`\"*a?\"`.  This is akin to setting the `nullglob` option in bash, except\nthat it does not resolve escaped pattern characters.\n\nIf brace expansion is not disabled, then it is performed before any\nother interpretation of the glob pattern.  Thus, a pattern like\n`+(a|{b),c)}`, which would not be valid in bash or zsh, is expanded\n**first** into the set of `+(a|b)` and `+(a|c)`, and those patterns are\nchecked for validity.  Since those two are valid, matching proceeds.\n",
+  "readmeFilename": "README.md"
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/test/basic.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/test/basic.js
new file mode 100644
index 0000000..ae7ac73
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/test/basic.js
@@ -0,0 +1,399 @@
+// http://www.bashcookbook.com/bashinfo/source/bash-1.14.7/tests/glob-test
+//
+// TODO: Some of these tests do very bad things with backslashes, and will
+// most likely fail badly on windows.  They should probably be skipped.
+
+var tap = require("tap")
+  , globalBefore = Object.keys(global)
+  , mm = require("../")
+  , files = [ "a", "b", "c", "d", "abc"
+            , "abd", "abe", "bb", "bcd"
+            , "ca", "cb", "dd", "de"
+            , "bdir/", "bdir/cfile"]
+  , next = files.concat([ "a-b", "aXb"
+                        , ".x", ".y" ])
+
+
+var patterns =
+  [ "http://www.bashcookbook.com/bashinfo/source/bash-1.14.7/tests/glob-test"
+  , ["a*", ["a", "abc", "abd", "abe"]]
+  , ["X*", ["X*"], {nonull: true}]
+
+  // allow null glob expansion
+  , ["X*", []]
+
+  // isaacs: Slightly different than bash/sh/ksh
+  // \\* is not un-escaped to literal "*" in a failed match,
+  // but it does make it get treated as a literal star
+  , ["\\*", ["\\*"], {nonull: true}]
+  , ["\\**", ["\\**"], {nonull: true}]
+  , ["\\*\\*", ["\\*\\*"], {nonull: true}]
+
+  , ["b*/", ["bdir/"]]
+  , ["c*", ["c", "ca", "cb"]]
+  , ["**", files]
+
+  , ["\\.\\./*/", ["\\.\\./*/"], {nonull: true}]
+  , ["s/\\..*//", ["s/\\..*//"], {nonull: true}]
+
+  , "legendary larry crashes bashes"
+  , ["/^root:/{s/^[^:]*:[^:]*:\([^:]*\).*$/\\1/"
+    , ["/^root:/{s/^[^:]*:[^:]*:\([^:]*\).*$/\\1/"], {nonull: true}]
+  , ["/^root:/{s/^[^:]*:[^:]*:\([^:]*\).*$/\1/"
+    , ["/^root:/{s/^[^:]*:[^:]*:\([^:]*\).*$/\1/"], {nonull: true}]
+
+  , "character classes"
+  , ["[a-c]b*", ["abc", "abd", "abe", "bb", "cb"]]
+  , ["[a-y]*[^c]", ["abd", "abe", "bb", "bcd",
+     "bdir/", "ca", "cb", "dd", "de"]]
+  , ["a*[^c]", ["abd", "abe"]]
+  , function () { files.push("a-b", "aXb") }
+  , ["a[X-]b", ["a-b", "aXb"]]
+  , function () { files.push(".x", ".y") }
+  , ["[^a-c]*", ["d", "dd", "de"]]
+  , function () { files.push("a*b/", "a*b/ooo") }
+  , ["a\\*b/*", ["a*b/ooo"]]
+  , ["a\\*?/*", ["a*b/ooo"]]
+  , ["*\\\\!*", [], {null: true}, ["echo !7"]]
+  , ["*\\!*", ["echo !7"], null, ["echo !7"]]
+  , ["*.\\*", ["r.*"], null, ["r.*"]]
+  , ["a[b]c", ["abc"]]
+  , ["a[\\b]c", ["abc"]]
+  , ["a?c", ["abc"]]
+  , ["a\\*c", [], {null: true}, ["abc"]]
+  , ["", [""], { null: true }, [""]]
+
+  , "http://www.opensource.apple.com/source/bash/bash-23/" +
+    "bash/tests/glob-test"
+  , function () { files.push("man/", "man/man1/", "man/man1/bash.1") }
+  , ["*/man*/bash.*", ["man/man1/bash.1"]]
+  , ["man/man1/bash.1", ["man/man1/bash.1"]]
+  , ["a***c", ["abc"], null, ["abc"]]
+  , ["a*****?c", ["abc"], null, ["abc"]]
+  , ["?*****??", ["abc"], null, ["abc"]]
+  , ["*****??", ["abc"], null, ["abc"]]
+  , ["?*****?c", ["abc"], null, ["abc"]]
+  , ["?***?****c", ["abc"], null, ["abc"]]
+  , ["?***?****?", ["abc"], null, ["abc"]]
+  , ["?***?****", ["abc"], null, ["abc"]]
+  , ["*******c", ["abc"], null, ["abc"]]
+  , ["*******?", ["abc"], null, ["abc"]]
+  , ["a*cd**?**??k", ["abcdecdhjk"], null, ["abcdecdhjk"]]
+  , ["a**?**cd**?**??k", ["abcdecdhjk"], null, ["abcdecdhjk"]]
+  , ["a**?**cd**?**??k***", ["abcdecdhjk"], null, ["abcdecdhjk"]]
+  , ["a**?**cd**?**??***k", ["abcdecdhjk"], null, ["abcdecdhjk"]]
+  , ["a**?**cd**?**??***k**", ["abcdecdhjk"], null, ["abcdecdhjk"]]
+  , ["a****c**?**??*****", ["abcdecdhjk"], null, ["abcdecdhjk"]]
+  , ["[-abc]", ["-"], null, ["-"]]
+  , ["[abc-]", ["-"], null, ["-"]]
+  , ["\\", ["\\"], null, ["\\"]]
+  , ["[\\\\]", ["\\"], null, ["\\"]]
+  , ["[[]", ["["], null, ["["]]
+  , ["[", ["["], null, ["["]]
+  , ["[*", ["[abc"], null, ["[abc"]]
+  , "a right bracket shall lose its special meaning and\n" +
+    "represent itself in a bracket expression if it occurs\n" +
+    "first in the list.  -- POSIX.2 2.8.3.2"
+  , ["[]]", ["]"], null, ["]"]]
+  , ["[]-]", ["]"], null, ["]"]]
+  , ["[a-\z]", ["p"], null, ["p"]]
+  , ["??**********?****?", [], { null: true }, ["abc"]]
+  , ["??**********?****c", [], { null: true }, ["abc"]]
+  , ["?************c****?****", [], { null: true }, ["abc"]]
+  , ["*c*?**", [], { null: true }, ["abc"]]
+  , ["a*****c*?**", [], { null: true }, ["abc"]]
+  , ["a********???*******", [], { null: true }, ["abc"]]
+  , ["[]", [], { null: true }, ["a"]]
+  , ["[abc", [], { null: true }, ["["]]
+
+  , "nocase tests"
+  , ["XYZ", ["xYz"], { nocase: true, null: true }
+    , ["xYz", "ABC", "IjK"]]
+  , ["ab*", ["ABC"], { nocase: true, null: true }
+    , ["xYz", "ABC", "IjK"]]
+  , ["[ia]?[ck]", ["ABC", "IjK"], { nocase: true, null: true }
+    , ["xYz", "ABC", "IjK"]]
+
+  // [ pattern, [matches], MM opts, files, TAP opts]
+  , "onestar/twostar"
+  , ["{/*,*}", [], {null: true}, ["/asdf/asdf/asdf"]]
+  , ["{/?,*}", ["/a", "bb"], {null: true}
+    , ["/a", "/b/b", "/a/b/c", "bb"]]
+
+  , "dots should not match unless requested"
+  , ["**", ["a/b"], {}, ["a/b", "a/.d", ".a/.d"]]
+
+  // .. and . can only match patterns starting with .,
+  // even when options.dot is set.
+  , function () {
+      files = ["a/./b", "a/../b", "a/c/b", "a/.d/b"]
+    }
+  , ["a/*/b", ["a/c/b", "a/.d/b"], {dot: true}]
+  , ["a/.*/b", ["a/./b", "a/../b", "a/.d/b"], {dot: true}]
+  , ["a/*/b", ["a/c/b"], {dot:false}]
+  , ["a/.*/b", ["a/./b", "a/../b", "a/.d/b"], {dot: false}]
+
+
+  // this also tests that changing the options needs
+  // to change the cache key, even if the pattern is
+  // the same!
+  , ["**", ["a/b","a/.d",".a/.d"], { dot: true }
+    , [ ".a/.d", "a/.d", "a/b"]]
+
+  , "paren sets cannot contain slashes"
+  , ["*(a/b)", ["*(a/b)"], {nonull: true}, ["a/b"]]
+
+  // brace sets trump all else.
+  //
+  // invalid glob pattern.  fails on bash4 and bsdglob.
+  // however, in this implementation, it's easier just
+  // to do the intuitive thing, and let brace-expansion
+  // actually come before parsing any extglob patterns,
+  // like the documentation seems to say.
+  //
+  // XXX: if anyone complains about this, either fix it
+  // or tell them to grow up and stop complaining.
+  //
+  // bash/bsdglob says this:
+  // , ["*(a|{b),c)}", ["*(a|{b),c)}"], {}, ["a", "ab", "ac", "ad"]]
+  // but we do this instead:
+  , ["*(a|{b),c)}", ["a", "ab", "ac"], {}, ["a", "ab", "ac", "ad"]]
+
+  // test partial parsing in the presence of comment/negation chars
+  , ["[!a*", ["[!ab"], {}, ["[!ab", "[ab"]]
+  , ["[#a*", ["[#ab"], {}, ["[#ab", "[ab"]]
+
+  // like: {a,b|c\\,d\\\|e} except it's unclosed, so it has to be escaped.
+  , ["+(a|*\\|c\\\\|d\\\\\\|e\\\\\\\\|f\\\\\\\\\\|g"
+    , ["+(a|b\\|c\\\\|d\\\\|e\\\\\\\\|f\\\\\\\\|g"]
+    , {}
+    , ["+(a|b\\|c\\\\|d\\\\|e\\\\\\\\|f\\\\\\\\|g", "a", "b\\c"]]
+
+
+  // crazy nested {,,} and *(||) tests.
+  , function () {
+      files = [ "a", "b", "c", "d"
+              , "ab", "ac", "ad"
+              , "bc", "cb"
+              , "bc,d", "c,db", "c,d"
+              , "d)", "(b|c", "*(b|c"
+              , "b|c", "b|cc", "cb|c"
+              , "x(a|b|c)", "x(a|c)"
+              , "(a|b|c)", "(a|c)"]
+    }
+  , ["*(a|{b,c})", ["a", "b", "c", "ab", "ac"]]
+  , ["{a,*(b|c,d)}", ["a","(b|c", "*(b|c", "d)"]]
+  // a
+  // *(b|c)
+  // *(b|d)
+  , ["{a,*(b|{c,d})}", ["a","b", "bc", "cb", "c", "d"]]
+  , ["*(a|{b|c,c})", ["a", "b", "c", "ab", "ac", "bc", "cb"]]
+
+
+  // test various flag settings.
+  , [ "*(a|{b|c,c})", ["x(a|b|c)", "x(a|c)", "(a|b|c)", "(a|c)"]
+    , { noext: true } ]
+  , ["a?b", ["x/y/acb", "acb/"], {matchBase: true}
+    , ["x/y/acb", "acb/", "acb/d/e", "x/y/acb/d"] ]
+  , ["#*", ["#a", "#b"], {nocomment: true}, ["#a", "#b", "c#d"]]
+
+
+  // begin channelling Boole and deMorgan...
+  , "negation tests"
+  , function () {
+      files = ["d", "e", "!ab", "!abc", "a!b", "\\!a"]
+    }
+
+  // anything that is NOT a* matches.
+  , ["!a*", ["\\!a", "d", "e", "!ab", "!abc"]]
+
+  // anything that IS !a* matches.
+  , ["!a*", ["!ab", "!abc"], {nonegate: true}]
+
+  // anything that IS a* matches
+  , ["!!a*", ["a!b"]]
+
+  // anything that is NOT !a* matches
+  , ["!\\!a*", ["a!b", "d", "e", "\\!a"]]
+
+  // negation nestled within a pattern
+  , function () {
+      files = [ "foo.js"
+              , "foo.bar"
+              // can't match this one without negative lookbehind.
+              , "foo.js.js"
+              , "blar.js"
+              , "foo."
+              , "boo.js.boo" ]
+    }
+  , ["*.!(js)", ["foo.bar", "foo.", "boo.js.boo"] ]
+
+  // https://github.com/isaacs/minimatch/issues/5
+  , function () {
+      files = [ 'a/b/.x/c'
+              , 'a/b/.x/c/d'
+              , 'a/b/.x/c/d/e'
+              , 'a/b/.x'
+              , 'a/b/.x/'
+              , 'a/.x/b'
+              , '.x'
+              , '.x/'
+              , '.x/a'
+              , '.x/a/b'
+              , 'a/.x/b/.x/c'
+              , '.x/.x' ]
+  }
+  , ["**/.x/**", [ '.x/'
+                 , '.x/a'
+                 , '.x/a/b'
+                 , 'a/.x/b'
+                 , 'a/b/.x/'
+                 , 'a/b/.x/c'
+                 , 'a/b/.x/c/d'
+                 , 'a/b/.x/c/d/e' ] ]
+
+  ]
+
+var regexps =
+  [ '/^(?:(?=.)a[^/]*?)$/',
+    '/^(?:(?=.)X[^/]*?)$/',
+    '/^(?:(?=.)X[^/]*?)$/',
+    '/^(?:\\*)$/',
+    '/^(?:(?=.)\\*[^/]*?)$/',
+    '/^(?:\\*\\*)$/',
+    '/^(?:(?=.)b[^/]*?\\/)$/',
+    '/^(?:(?=.)c[^/]*?)$/',
+    '/^(?:(?:(?!(?:\\/|^)\\.).)*?)$/',
+    '/^(?:\\.\\.\\/(?!\\.)(?=.)[^/]*?\\/)$/',
+    '/^(?:s\\/(?=.)\\.\\.[^/]*?\\/)$/',
+    '/^(?:\\/\\^root:\\/\\{s\\/(?=.)\\^[^:][^/]*?:[^:][^/]*?:\\([^:]\\)[^/]*?\\.[^/]*?\\$\\/1\\/)$/',
+    '/^(?:\\/\\^root:\\/\\{s\\/(?=.)\\^[^:][^/]*?:[^:][^/]*?:\\([^:]\\)[^/]*?\\.[^/]*?\\$\\/\u0001\\/)$/',
+    '/^(?:(?!\\.)(?=.)[a-c]b[^/]*?)$/',
+    '/^(?:(?!\\.)(?=.)[a-y][^/]*?[^c])$/',
+    '/^(?:(?=.)a[^/]*?[^c])$/',
+    '/^(?:(?=.)a[X-]b)$/',
+    '/^(?:(?!\\.)(?=.)[^a-c][^/]*?)$/',
+    '/^(?:a\\*b\\/(?!\\.)(?=.)[^/]*?)$/',
+    '/^(?:(?=.)a\\*[^/]\\/(?!\\.)(?=.)[^/]*?)$/',
+    '/^(?:(?!\\.)(?=.)[^/]*?\\\\\\![^/]*?)$/',
+    '/^(?:(?!\\.)(?=.)[^/]*?\\![^/]*?)$/',
+    '/^(?:(?!\\.)(?=.)[^/]*?\\.\\*)$/',
+    '/^(?:(?=.)a[b]c)$/',
+    '/^(?:(?=.)a[b]c)$/',
+    '/^(?:(?=.)a[^/]c)$/',
+    '/^(?:a\\*c)$/',
+    'false',
+    '/^(?:(?!\\.)(?=.)[^/]*?\\/(?=.)man[^/]*?\\/(?=.)bash\\.[^/]*?)$/',
+    '/^(?:man\\/man1\\/bash\\.1)$/',
+    '/^(?:(?=.)a[^/]*?[^/]*?[^/]*?c)$/',
+    '/^(?:(?=.)a[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]c)$/',
+    '/^(?:(?!\\.)(?=.)[^/][^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/][^/])$/',
+    '/^(?:(?!\\.)(?=.)[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/][^/])$/',
+    '/^(?:(?!\\.)(?=.)[^/][^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]c)$/',
+    '/^(?:(?!\\.)(?=.)[^/][^/]*?[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/]*?[^/]*?c)$/',
+    '/^(?:(?!\\.)(?=.)[^/][^/]*?[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/]*?[^/]*?[^/])$/',
+    '/^(?:(?!\\.)(?=.)[^/][^/]*?[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/]*?[^/]*?)$/',
+    '/^(?:(?!\\.)(?=.)[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?c)$/',
+    '/^(?:(?!\\.)(?=.)[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/])$/',
+    '/^(?:(?=.)a[^/]*?cd[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/][^/]k)$/',
+    '/^(?:(?=.)a[^/]*?[^/]*?[^/][^/]*?[^/]*?cd[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/][^/]k)$/',
+    '/^(?:(?=.)a[^/]*?[^/]*?[^/][^/]*?[^/]*?cd[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/][^/]k[^/]*?[^/]*?[^/]*?)$/',
+    '/^(?:(?=.)a[^/]*?[^/]*?[^/][^/]*?[^/]*?cd[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/][^/][^/]*?[^/]*?[^/]*?k)$/',
+    '/^(?:(?=.)a[^/]*?[^/]*?[^/][^/]*?[^/]*?cd[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/][^/][^/]*?[^/]*?[^/]*?k[^/]*?[^/]*?)$/',
+    '/^(?:(?=.)a[^/]*?[^/]*?[^/]*?[^/]*?c[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/][^/][^/]*?[^/]*?[^/]*?[^/]*?[^/]*?)$/',
+    '/^(?:(?!\\.)(?=.)[-abc])$/',
+    '/^(?:(?!\\.)(?=.)[abc-])$/',
+    '/^(?:\\\\)$/',
+    '/^(?:(?!\\.)(?=.)[\\\\])$/',
+    '/^(?:(?!\\.)(?=.)[\\[])$/',
+    '/^(?:\\[)$/',
+    '/^(?:(?=.)\\[(?!\\.)(?=.)[^/]*?)$/',
+    '/^(?:(?!\\.)(?=.)[\\]])$/',
+    '/^(?:(?!\\.)(?=.)[\\]-])$/',
+    '/^(?:(?!\\.)(?=.)[a-z])$/',
+    '/^(?:(?!\\.)(?=.)[^/][^/][^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/]*?[^/]*?[^/])$/',
+    '/^(?:(?!\\.)(?=.)[^/][^/][^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/]*?[^/]*?c)$/',
+    '/^(?:(?!\\.)(?=.)[^/][^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?c[^/]*?[^/]*?[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/]*?[^/]*?)$/',
+    '/^(?:(?!\\.)(?=.)[^/]*?c[^/]*?[^/][^/]*?[^/]*?)$/',
+    '/^(?:(?=.)a[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?c[^/]*?[^/][^/]*?[^/]*?)$/',
+    '/^(?:(?=.)a[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/][^/][^/][^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?)$/',
+    '/^(?:\\[\\])$/',
+    '/^(?:\\[abc)$/',
+    '/^(?:(?=.)XYZ)$/i',
+    '/^(?:(?=.)ab[^/]*?)$/i',
+    '/^(?:(?!\\.)(?=.)[ia][^/][ck])$/i',
+    '/^(?:\\/(?!\\.)(?=.)[^/]*?|(?!\\.)(?=.)[^/]*?)$/',
+    '/^(?:\\/(?!\\.)(?=.)[^/]|(?!\\.)(?=.)[^/]*?)$/',
+    '/^(?:(?:(?!(?:\\/|^)\\.).)*?)$/',
+    '/^(?:a\\/(?!(?:^|\\/)\\.{1,2}(?:$|\\/))(?=.)[^/]*?\\/b)$/',
+    '/^(?:a\\/(?=.)\\.[^/]*?\\/b)$/',
+    '/^(?:a\\/(?!\\.)(?=.)[^/]*?\\/b)$/',
+    '/^(?:a\\/(?=.)\\.[^/]*?\\/b)$/',
+    '/^(?:(?:(?!(?:\\/|^)(?:\\.{1,2})($|\\/)).)*?)$/',
+    '/^(?:(?!\\.)(?=.)[^/]*?\\(a\\/b\\))$/',
+    '/^(?:(?!\\.)(?=.)(?:a|b)*|(?!\\.)(?=.)(?:a|c)*)$/',
+    '/^(?:(?=.)\\[(?=.)\\!a[^/]*?)$/',
+    '/^(?:(?=.)\\[(?=.)#a[^/]*?)$/',
+    '/^(?:(?=.)\\+\\(a\\|[^/]*?\\|c\\\\\\\\\\|d\\\\\\\\\\|e\\\\\\\\\\\\\\\\\\|f\\\\\\\\\\\\\\\\\\|g)$/',
+    '/^(?:(?!\\.)(?=.)(?:a|b)*|(?!\\.)(?=.)(?:a|c)*)$/',
+    '/^(?:a|(?!\\.)(?=.)[^/]*?\\(b\\|c|d\\))$/',
+    '/^(?:a|(?!\\.)(?=.)(?:b|c)*|(?!\\.)(?=.)(?:b|d)*)$/',
+    '/^(?:(?!\\.)(?=.)(?:a|b|c)*|(?!\\.)(?=.)(?:a|c)*)$/',
+    '/^(?:(?!\\.)(?=.)[^/]*?\\(a\\|b\\|c\\)|(?!\\.)(?=.)[^/]*?\\(a\\|c\\))$/',
+    '/^(?:(?=.)a[^/]b)$/',
+    '/^(?:(?=.)#[^/]*?)$/',
+    '/^(?!^(?:(?=.)a[^/]*?)$).*$/',
+    '/^(?:(?=.)\\!a[^/]*?)$/',
+    '/^(?:(?=.)a[^/]*?)$/',
+    '/^(?!^(?:(?=.)\\!a[^/]*?)$).*$/',
+    '/^(?:(?!\\.)(?=.)[^/]*?\\.(?:(?!js)[^/]*?))$/',
+    '/^(?:(?:(?!(?:\\/|^)\\.).)*?\\/\\.x\\/(?:(?!(?:\\/|^)\\.).)*?)$/' ]
+var re = 0;
+
+tap.test("basic tests", function (t) {
+  var start = Date.now()
+
+  // [ pattern, [matches], MM opts, files, TAP opts]
+  patterns.forEach(function (c) {
+    if (typeof c === "function") return c()
+    if (typeof c === "string") return t.comment(c)
+
+    var pattern = c[0]
+      , expect = c[1].sort(alpha)
+      , options = c[2] || {}
+      , f = c[3] || files
+      , tapOpts = c[4] || {}
+
+    // options.debug = true
+    var m = new mm.Minimatch(pattern, options)
+    var r = m.makeRe()
+    var expectRe = regexps[re++]
+    tapOpts.re = String(r) || JSON.stringify(r)
+    tapOpts.files = JSON.stringify(f)
+    tapOpts.pattern = pattern
+    tapOpts.set = m.set
+    tapOpts.negated = m.negate
+
+    var actual = mm.match(f, pattern, options)
+    actual.sort(alpha)
+
+    t.equivalent( actual, expect
+                , JSON.stringify(pattern) + " " + JSON.stringify(expect)
+                , tapOpts )
+
+    t.equal(tapOpts.re, expectRe, tapOpts)
+  })
+
+  t.comment("time=" + (Date.now() - start) + "ms")
+  t.end()
+})
+
+tap.test("global leak test", function (t) {
+  var globalAfter = Object.keys(global)
+  t.equivalent(globalAfter, globalBefore, "no new globals, please")
+  t.end()
+})
+
+function alpha (a, b) {
+  return a > b ? 1 : -1
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/test/brace-expand.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/test/brace-expand.js
new file mode 100644
index 0000000..7ee278a
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/test/brace-expand.js
@@ -0,0 +1,33 @@
+var tap = require("tap")
+  , minimatch = require("../")
+
+tap.test("brace expansion", function (t) {
+  // [ pattern, [expanded] ]
+  ; [ [ "a{b,c{d,e},{f,g}h}x{y,z}"
+      , [ "abxy"
+        , "abxz"
+        , "acdxy"
+        , "acdxz"
+        , "acexy"
+        , "acexz"
+        , "afhxy"
+        , "afhxz"
+        , "aghxy"
+        , "aghxz" ] ]
+    , [ "a{1..5}b"
+      , [ "a1b"
+        , "a2b"
+        , "a3b"
+        , "a4b"
+        , "a5b" ] ]
+    , [ "a{b}c", ["a{b}c"] ]
+  ].forEach(function (tc) {
+    var p = tc[0]
+      , expect = tc[1]
+    t.equivalent(minimatch.braceExpand(p), expect, p)
+  })
+  console.error("ending")
+  t.end()
+})
+
+
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/test/caching.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/test/caching.js
new file mode 100644
index 0000000..0fec4b0
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/test/caching.js
@@ -0,0 +1,14 @@
+var Minimatch = require("../minimatch.js").Minimatch
+var tap = require("tap")
+tap.test("cache test", function (t) {
+  var mm1 = new Minimatch("a?b")
+  var mm2 = new Minimatch("a?b")
+  t.equal(mm1, mm2, "should get the same object")
+  // the lru should drop it after 100 entries
+  for (var i = 0; i < 100; i ++) {
+    new Minimatch("a"+i)
+  }
+  mm2 = new Minimatch("a?b")
+  t.notEqual(mm1, mm2, "cache should have dropped")
+  t.end()
+})
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/test/defaults.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/test/defaults.js
new file mode 100644
index 0000000..75e0571
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/test/defaults.js
@@ -0,0 +1,274 @@
+// http://www.bashcookbook.com/bashinfo/source/bash-1.14.7/tests/glob-test
+//
+// TODO: Some of these tests do very bad things with backslashes, and will
+// most likely fail badly on windows.  They should probably be skipped.
+
+var tap = require("tap")
+  , globalBefore = Object.keys(global)
+  , mm = require("../")
+  , files = [ "a", "b", "c", "d", "abc"
+            , "abd", "abe", "bb", "bcd"
+            , "ca", "cb", "dd", "de"
+            , "bdir/", "bdir/cfile"]
+  , next = files.concat([ "a-b", "aXb"
+                        , ".x", ".y" ])
+
+tap.test("basic tests", function (t) {
+  var start = Date.now()
+
+  // [ pattern, [matches], MM opts, files, TAP opts]
+  ; [ "http://www.bashcookbook.com/bashinfo" +
+      "/source/bash-1.14.7/tests/glob-test"
+    , ["a*", ["a", "abc", "abd", "abe"]]
+    , ["X*", ["X*"], {nonull: true}]
+
+    // allow null glob expansion
+    , ["X*", []]
+
+    // isaacs: Slightly different than bash/sh/ksh
+    // \\* is not un-escaped to literal "*" in a failed match,
+    // but it does make it get treated as a literal star
+    , ["\\*", ["\\*"], {nonull: true}]
+    , ["\\**", ["\\**"], {nonull: true}]
+    , ["\\*\\*", ["\\*\\*"], {nonull: true}]
+
+    , ["b*/", ["bdir/"]]
+    , ["c*", ["c", "ca", "cb"]]
+    , ["**", files]
+
+    , ["\\.\\./*/", ["\\.\\./*/"], {nonull: true}]
+    , ["s/\\..*//", ["s/\\..*//"], {nonull: true}]
+
+    , "legendary larry crashes bashes"
+    , ["/^root:/{s/^[^:]*:[^:]*:\([^:]*\).*$/\\1/"
+      , ["/^root:/{s/^[^:]*:[^:]*:\([^:]*\).*$/\\1/"], {nonull: true}]
+    , ["/^root:/{s/^[^:]*:[^:]*:\([^:]*\).*$/\1/"
+      , ["/^root:/{s/^[^:]*:[^:]*:\([^:]*\).*$/\1/"], {nonull: true}]
+
+    , "character classes"
+    , ["[a-c]b*", ["abc", "abd", "abe", "bb", "cb"]]
+    , ["[a-y]*[^c]", ["abd", "abe", "bb", "bcd",
+       "bdir/", "ca", "cb", "dd", "de"]]
+    , ["a*[^c]", ["abd", "abe"]]
+    , function () { files.push("a-b", "aXb") }
+    , ["a[X-]b", ["a-b", "aXb"]]
+    , function () { files.push(".x", ".y") }
+    , ["[^a-c]*", ["d", "dd", "de"]]
+    , function () { files.push("a*b/", "a*b/ooo") }
+    , ["a\\*b/*", ["a*b/ooo"]]
+    , ["a\\*?/*", ["a*b/ooo"]]
+    , ["*\\\\!*", [], {null: true}, ["echo !7"]]
+    , ["*\\!*", ["echo !7"], null, ["echo !7"]]
+    , ["*.\\*", ["r.*"], null, ["r.*"]]
+    , ["a[b]c", ["abc"]]
+    , ["a[\\b]c", ["abc"]]
+    , ["a?c", ["abc"]]
+    , ["a\\*c", [], {null: true}, ["abc"]]
+    , ["", [""], { null: true }, [""]]
+
+    , "http://www.opensource.apple.com/source/bash/bash-23/" +
+      "bash/tests/glob-test"
+    , function () { files.push("man/", "man/man1/", "man/man1/bash.1") }
+    , ["*/man*/bash.*", ["man/man1/bash.1"]]
+    , ["man/man1/bash.1", ["man/man1/bash.1"]]
+    , ["a***c", ["abc"], null, ["abc"]]
+    , ["a*****?c", ["abc"], null, ["abc"]]
+    , ["?*****??", ["abc"], null, ["abc"]]
+    , ["*****??", ["abc"], null, ["abc"]]
+    , ["?*****?c", ["abc"], null, ["abc"]]
+    , ["?***?****c", ["abc"], null, ["abc"]]
+    , ["?***?****?", ["abc"], null, ["abc"]]
+    , ["?***?****", ["abc"], null, ["abc"]]
+    , ["*******c", ["abc"], null, ["abc"]]
+    , ["*******?", ["abc"], null, ["abc"]]
+    , ["a*cd**?**??k", ["abcdecdhjk"], null, ["abcdecdhjk"]]
+    , ["a**?**cd**?**??k", ["abcdecdhjk"], null, ["abcdecdhjk"]]
+    , ["a**?**cd**?**??k***", ["abcdecdhjk"], null, ["abcdecdhjk"]]
+    , ["a**?**cd**?**??***k", ["abcdecdhjk"], null, ["abcdecdhjk"]]
+    , ["a**?**cd**?**??***k**", ["abcdecdhjk"], null, ["abcdecdhjk"]]
+    , ["a****c**?**??*****", ["abcdecdhjk"], null, ["abcdecdhjk"]]
+    , ["[-abc]", ["-"], null, ["-"]]
+    , ["[abc-]", ["-"], null, ["-"]]
+    , ["\\", ["\\"], null, ["\\"]]
+    , ["[\\\\]", ["\\"], null, ["\\"]]
+    , ["[[]", ["["], null, ["["]]
+    , ["[", ["["], null, ["["]]
+    , ["[*", ["[abc"], null, ["[abc"]]
+    , "a right bracket shall lose its special meaning and\n" +
+      "represent itself in a bracket expression if it occurs\n" +
+      "first in the list.  -- POSIX.2 2.8.3.2"
+    , ["[]]", ["]"], null, ["]"]]
+    , ["[]-]", ["]"], null, ["]"]]
+    , ["[a-\z]", ["p"], null, ["p"]]
+    , ["??**********?****?", [], { null: true }, ["abc"]]
+    , ["??**********?****c", [], { null: true }, ["abc"]]
+    , ["?************c****?****", [], { null: true }, ["abc"]]
+    , ["*c*?**", [], { null: true }, ["abc"]]
+    , ["a*****c*?**", [], { null: true }, ["abc"]]
+    , ["a********???*******", [], { null: true }, ["abc"]]
+    , ["[]", [], { null: true }, ["a"]]
+    , ["[abc", [], { null: true }, ["["]]
+
+    , "nocase tests"
+    , ["XYZ", ["xYz"], { nocase: true, null: true }
+      , ["xYz", "ABC", "IjK"]]
+    , ["ab*", ["ABC"], { nocase: true, null: true }
+      , ["xYz", "ABC", "IjK"]]
+    , ["[ia]?[ck]", ["ABC", "IjK"], { nocase: true, null: true }
+      , ["xYz", "ABC", "IjK"]]
+
+    // [ pattern, [matches], MM opts, files, TAP opts]
+    , "onestar/twostar"
+    , ["{/*,*}", [], {null: true}, ["/asdf/asdf/asdf"]]
+    , ["{/?,*}", ["/a", "bb"], {null: true}
+      , ["/a", "/b/b", "/a/b/c", "bb"]]
+
+    , "dots should not match unless requested"
+    , ["**", ["a/b"], {}, ["a/b", "a/.d", ".a/.d"]]
+
+    // .. and . can only match patterns starting with .,
+    // even when options.dot is set.
+    , function () {
+        files = ["a/./b", "a/../b", "a/c/b", "a/.d/b"]
+      }
+    , ["a/*/b", ["a/c/b", "a/.d/b"], {dot: true}]
+    , ["a/.*/b", ["a/./b", "a/../b", "a/.d/b"], {dot: true}]
+    , ["a/*/b", ["a/c/b"], {dot:false}]
+    , ["a/.*/b", ["a/./b", "a/../b", "a/.d/b"], {dot: false}]
+
+
+    // this also tests that changing the options needs
+    // to change the cache key, even if the pattern is
+    // the same!
+    , ["**", ["a/b","a/.d",".a/.d"], { dot: true }
+      , [ ".a/.d", "a/.d", "a/b"]]
+
+    , "paren sets cannot contain slashes"
+    , ["*(a/b)", ["*(a/b)"], {nonull: true}, ["a/b"]]
+
+    // brace sets trump all else.
+    //
+    // invalid glob pattern.  fails on bash4 and bsdglob.
+    // however, in this implementation, it's easier just
+    // to do the intuitive thing, and let brace-expansion
+    // actually come before parsing any extglob patterns,
+    // like the documentation seems to say.
+    //
+    // XXX: if anyone complains about this, either fix it
+    // or tell them to grow up and stop complaining.
+    //
+    // bash/bsdglob says this:
+    // , ["*(a|{b),c)}", ["*(a|{b),c)}"], {}, ["a", "ab", "ac", "ad"]]
+    // but we do this instead:
+    , ["*(a|{b),c)}", ["a", "ab", "ac"], {}, ["a", "ab", "ac", "ad"]]
+
+    // test partial parsing in the presence of comment/negation chars
+    , ["[!a*", ["[!ab"], {}, ["[!ab", "[ab"]]
+    , ["[#a*", ["[#ab"], {}, ["[#ab", "[ab"]]
+
+    // like: {a,b|c\\,d\\\|e} except it's unclosed, so it has to be escaped.
+    , ["+(a|*\\|c\\\\|d\\\\\\|e\\\\\\\\|f\\\\\\\\\\|g"
+      , ["+(a|b\\|c\\\\|d\\\\|e\\\\\\\\|f\\\\\\\\|g"]
+      , {}
+      , ["+(a|b\\|c\\\\|d\\\\|e\\\\\\\\|f\\\\\\\\|g", "a", "b\\c"]]
+
+
+    // crazy nested {,,} and *(||) tests.
+    , function () {
+        files = [ "a", "b", "c", "d"
+                , "ab", "ac", "ad"
+                , "bc", "cb"
+                , "bc,d", "c,db", "c,d"
+                , "d)", "(b|c", "*(b|c"
+                , "b|c", "b|cc", "cb|c"
+                , "x(a|b|c)", "x(a|c)"
+                , "(a|b|c)", "(a|c)"]
+      }
+    , ["*(a|{b,c})", ["a", "b", "c", "ab", "ac"]]
+    , ["{a,*(b|c,d)}", ["a","(b|c", "*(b|c", "d)"]]
+    // a
+    // *(b|c)
+    // *(b|d)
+    , ["{a,*(b|{c,d})}", ["a","b", "bc", "cb", "c", "d"]]
+    , ["*(a|{b|c,c})", ["a", "b", "c", "ab", "ac", "bc", "cb"]]
+
+
+    // test various flag settings.
+    , [ "*(a|{b|c,c})", ["x(a|b|c)", "x(a|c)", "(a|b|c)", "(a|c)"]
+      , { noext: true } ]
+    , ["a?b", ["x/y/acb", "acb/"], {matchBase: true}
+      , ["x/y/acb", "acb/", "acb/d/e", "x/y/acb/d"] ]
+    , ["#*", ["#a", "#b"], {nocomment: true}, ["#a", "#b", "c#d"]]
+
+
+    // begin channelling Boole and deMorgan...
+    , "negation tests"
+    , function () {
+        files = ["d", "e", "!ab", "!abc", "a!b", "\\!a"]
+      }
+
+    // anything that is NOT a* matches.
+    , ["!a*", ["\\!a", "d", "e", "!ab", "!abc"]]
+
+    // anything that IS !a* matches.
+    , ["!a*", ["!ab", "!abc"], {nonegate: true}]
+
+    // anything that IS a* matches
+    , ["!!a*", ["a!b"]]
+
+    // anything that is NOT !a* matches
+    , ["!\\!a*", ["a!b", "d", "e", "\\!a"]]
+
+    // negation nestled within a pattern
+    , function () {
+        files = [ "foo.js"
+                , "foo.bar"
+                // can't match this one without negative lookbehind.
+                , "foo.js.js"
+                , "blar.js"
+                , "foo."
+                , "boo.js.boo" ]
+      }
+    , ["*.!(js)", ["foo.bar", "foo.", "boo.js.boo"] ]
+
+    ].forEach(function (c) {
+      if (typeof c === "function") return c()
+      if (typeof c === "string") return t.comment(c)
+
+      var pattern = c[0]
+        , expect = c[1].sort(alpha)
+        , options = c[2]
+        , f = c[3] || files
+        , tapOpts = c[4] || {}
+
+      // options.debug = true
+      var Class = mm.defaults(options).Minimatch
+      var m = new Class(pattern, {})
+      var r = m.makeRe()
+      tapOpts.re = String(r) || JSON.stringify(r)
+      tapOpts.files = JSON.stringify(f)
+      tapOpts.pattern = pattern
+      tapOpts.set = m.set
+      tapOpts.negated = m.negate
+
+      var actual = mm.match(f, pattern, options)
+      actual.sort(alpha)
+
+      t.equivalent( actual, expect
+                  , JSON.stringify(pattern) + " " + JSON.stringify(expect)
+                  , tapOpts )
+    })
+
+  t.comment("time=" + (Date.now() - start) + "ms")
+  t.end()
+})
+
+tap.test("global leak test", function (t) {
+  var globalAfter = Object.keys(global)
+  t.equivalent(globalAfter, globalBefore, "no new globals, please")
+  t.end()
+})
+
+function alpha (a, b) {
+  return a > b ? 1 : -1
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/test/extglob-ending-with-state-char.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/test/extglob-ending-with-state-char.js
new file mode 100644
index 0000000..6676e26
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/node_modules/minimatch/test/extglob-ending-with-state-char.js
@@ -0,0 +1,8 @@
+var test = require('tap').test
+var minimatch = require('../')
+
+test('extglob ending with statechar', function(t) {
+  t.notOk(minimatch('ax', 'a?(b*)'))
+  t.ok(minimatch('ax', '?(a*|b)'))
+  t.end()
+})
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/package.json b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/package.json
new file mode 100644
index 0000000..a814d01
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/package.json
@@ -0,0 +1,40 @@
+{
+  "author": {
+    "name": "Isaac Z. Schlueter",
+    "email": "i@izs.me",
+    "url": "http://blog.izs.me/"
+  },
+  "name": "glob",
+  "description": "a little globber",
+  "version": "3.2.11",
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/isaacs/node-glob.git"
+  },
+  "main": "glob.js",
+  "engines": {
+    "node": "*"
+  },
+  "dependencies": {
+    "inherits": "2",
+    "minimatch": "0.3"
+  },
+  "devDependencies": {
+    "tap": "~0.4.0",
+    "mkdirp": "0",
+    "rimraf": "1"
+  },
+  "scripts": {
+    "test": "tap test/*.js",
+    "test-regen": "TEST_REGEN=1 node test/00-setup.js"
+  },
+  "license": "BSD",
+  "readme": "# Glob\n\nMatch files using the patterns the shell uses, like stars and stuff.\n\nThis is a glob implementation in JavaScript.  It uses the `minimatch`\nlibrary to do its matching.\n\n## Attention: node-glob users!\n\nThe API has changed dramatically between 2.x and 3.x. This library is\nnow 100% JavaScript, and the integer flags have been replaced with an\noptions object.\n\nAlso, there's an event emitter class, proper tests, and all the other\nthings you've come to expect from node modules.\n\nAnd best of all, no compilation!\n\n## Usage\n\n```javascript\nvar glob = require(\"glob\")\n\n// options is optional\nglob(\"**/*.js\", options, function (er, files) {\n  // files is an array of filenames.\n  // If the `nonull` option is set, and nothing\n  // was found, then files is [\"**/*.js\"]\n  // er is an error object or null.\n})\n```\n\n## Features\n\nPlease see the [minimatch\ndocumentation](https://github.com/isaacs/minimatch) for more details.\n\nSupports these glob features:\n\n* Brace Expansion\n* Extended glob matching\n* \"Globstar\" `**` matching\n\nSee:\n\n* `man sh`\n* `man bash`\n* `man 3 fnmatch`\n* `man 5 gitignore`\n* [minimatch documentation](https://github.com/isaacs/minimatch)\n\n## glob(pattern, [options], cb)\n\n* `pattern` {String} Pattern to be matched\n* `options` {Object}\n* `cb` {Function}\n  * `err` {Error | null}\n  * `matches` {Array<String>} filenames found matching the pattern\n\nPerform an asynchronous glob search.\n\n## glob.sync(pattern, [options])\n\n* `pattern` {String} Pattern to be matched\n* `options` {Object}\n* return: {Array<String>} filenames found matching the pattern\n\nPerform a synchronous glob search.\n\n## Class: glob.Glob\n\nCreate a Glob object by instanting the `glob.Glob` class.\n\n```javascript\nvar Glob = require(\"glob\").Glob\nvar mg = new Glob(pattern, options, cb)\n```\n\nIt's an EventEmitter, and starts walking the filesystem to find matches\nimmediately.\n\n### new glob.Glob(pattern, [options], [cb])\n\n* `pattern` {String} pattern to search for\n* `options` {Object}\n* `cb` {Function} Called when an error occurs, or matches are found\n  * `err` {Error | null}\n  * `matches` {Array<String>} filenames found matching the pattern\n\nNote that if the `sync` flag is set in the options, then matches will\nbe immediately available on the `g.found` member.\n\n### Properties\n\n* `minimatch` The minimatch object that the glob uses.\n* `options` The options object passed in.\n* `error` The error encountered.  When an error is encountered, the\n  glob object is in an undefined state, and should be discarded.\n* `aborted` Boolean which is set to true when calling `abort()`.  There\n  is no way at this time to continue a glob search after aborting, but\n  you can re-use the statCache to avoid having to duplicate syscalls.\n* `statCache` Collection of all the stat results the glob search\n  performed.\n* `cache` Convenience object.  Each field has the following possible\n  values:\n  * `false` - Path does not exist\n  * `true` - Path exists\n  * `1` - Path exists, and is not a directory\n  * `2` - Path exists, and is a directory\n  * `[file, entries, ...]` - Path exists, is a directory, and the\n    array value is the results of `fs.readdir`\n\n### Events\n\n* `end` When the matching is finished, this is emitted with all the\n  matches found.  If the `nonull` option is set, and no match was found,\n  then the `matches` list contains the original pattern.  The matches\n  are sorted, unless the `nosort` flag is set.\n* `match` Every time a match is found, this is emitted with the matched.\n* `error` Emitted when an unexpected error is encountered, or whenever\n  any fs error occurs if `options.strict` is set.\n* `abort` When `abort()` is called, this event is raised.\n\n### Methods\n\n* `abort` Stop the search.\n\n### Options\n\nAll the options that can be passed to Minimatch can also be passed to\nGlob to change pattern matching behavior.  Also, some have been added,\nor have glob-specific ramifications.\n\nAll options are false by default, unless otherwise noted.\n\nAll options are added to the glob object, as well.\n\n* `cwd` The current working directory in which to search.  Defaults\n  to `process.cwd()`.\n* `root` The place where patterns starting with `/` will be mounted\n  onto.  Defaults to `path.resolve(options.cwd, \"/\")` (`/` on Unix\n  systems, and `C:\\` or some such on Windows.)\n* `dot` Include `.dot` files in normal matches and `globstar` matches.\n  Note that an explicit dot in a portion of the pattern will always\n  match dot files.\n* `nomount` By default, a pattern starting with a forward-slash will be\n  \"mounted\" onto the root setting, so that a valid filesystem path is\n  returned.  Set this flag to disable that behavior.\n* `mark` Add a `/` character to directory matches.  Note that this\n  requires additional stat calls.\n* `nosort` Don't sort the results.\n* `stat` Set to true to stat *all* results.  This reduces performance\n  somewhat, and is completely unnecessary, unless `readdir` is presumed\n  to be an untrustworthy indicator of file existence.  It will cause\n  ELOOP to be triggered one level sooner in the case of cyclical\n  symbolic links.\n* `silent` When an unusual error is encountered\n  when attempting to read a directory, a warning will be printed to\n  stderr.  Set the `silent` option to true to suppress these warnings.\n* `strict` When an unusual error is encountered\n  when attempting to read a directory, the process will just continue on\n  in search of other matches.  Set the `strict` option to raise an error\n  in these cases.\n* `cache` See `cache` property above.  Pass in a previously generated\n  cache object to save some fs calls.\n* `statCache` A cache of results of filesystem information, to prevent\n  unnecessary stat calls.  While it should not normally be necessary to\n  set this, you may pass the statCache from one glob() call to the\n  options object of another, if you know that the filesystem will not\n  change between calls.  (See \"Race Conditions\" below.)\n* `sync` Perform a synchronous glob search.\n* `nounique` In some cases, brace-expanded patterns can result in the\n  same file showing up multiple times in the result set.  By default,\n  this implementation prevents duplicates in the result set.\n  Set this flag to disable that behavior.\n* `nonull` Set to never return an empty set, instead returning a set\n  containing the pattern itself.  This is the default in glob(3).\n* `nocase` Perform a case-insensitive match.  Note that case-insensitive\n  filesystems will sometimes result in glob returning results that are\n  case-insensitively matched anyway, since readdir and stat will not\n  raise an error.\n* `debug` Set to enable debug logging in minimatch and glob.\n* `globDebug` Set to enable debug logging in glob, but not minimatch.\n\n## Comparisons to other fnmatch/glob implementations\n\nWhile strict compliance with the existing standards is a worthwhile\ngoal, some discrepancies exist between node-glob and other\nimplementations, and are intentional.\n\nIf the pattern starts with a `!` character, then it is negated.  Set the\n`nonegate` flag to suppress this behavior, and treat leading `!`\ncharacters normally.  This is perhaps relevant if you wish to start the\npattern with a negative extglob pattern like `!(a|B)`.  Multiple `!`\ncharacters at the start of a pattern will negate the pattern multiple\ntimes.\n\nIf a pattern starts with `#`, then it is treated as a comment, and\nwill not match anything.  Use `\\#` to match a literal `#` at the\nstart of a line, or set the `nocomment` flag to suppress this behavior.\n\nThe double-star character `**` is supported by default, unless the\n`noglobstar` flag is set.  This is supported in the manner of bsdglob\nand bash 4.1, where `**` only has special significance if it is the only\nthing in a path part.  That is, `a/**/b` will match `a/x/y/b`, but\n`a/**b` will not.\n\nIf an escaped pattern has no matches, and the `nonull` flag is set,\nthen glob returns the pattern as-provided, rather than\ninterpreting the character escapes.  For example,\n`glob.match([], \"\\\\*a\\\\?\")` will return `\"\\\\*a\\\\?\"` rather than\n`\"*a?\"`.  This is akin to setting the `nullglob` option in bash, except\nthat it does not resolve escaped pattern characters.\n\nIf brace expansion is not disabled, then it is performed before any\nother interpretation of the glob pattern.  Thus, a pattern like\n`+(a|{b),c)}`, which would not be valid in bash or zsh, is expanded\n**first** into the set of `+(a|b)` and `+(a|c)`, and those patterns are\nchecked for validity.  Since those two are valid, matching proceeds.\n\n## Windows\n\n**Please only use forward-slashes in glob expressions.**\n\nThough windows uses either `/` or `\\` as its path separator, only `/`\ncharacters are used by this glob implementation.  You must use\nforward-slashes **only** in glob expressions.  Back-slashes will always\nbe interpreted as escape characters, not path separators.\n\nResults from absolute patterns such as `/foo/*` are mounted onto the\nroot setting using `path.join`.  On windows, this will by default result\nin `/foo/*` matching `C:\\foo\\bar.txt`.\n\n## Race Conditions\n\nGlob searching, by its very nature, is susceptible to race conditions,\nsince it relies on directory walking and such.\n\nAs a result, it is possible that a file that exists when glob looks for\nit may have been deleted or modified by the time it returns the result.\n\nAs part of its internal implementation, this program caches all stat\nand readdir calls that it makes, in order to cut down on system\noverhead.  However, this also makes it even more susceptible to races,\nespecially if the cache or statCache objects are reused between glob\ncalls.\n\nUsers are thus advised not to use a glob result as a guarantee of\nfilesystem state in the face of rapid changes.  For the vast majority\nof operations, this is never a problem.\n",
+  "readmeFilename": "README.md",
+  "bugs": {
+    "url": "https://github.com/isaacs/node-glob/issues"
+  },
+  "homepage": "https://github.com/isaacs/node-glob",
+  "_id": "glob@3.2.11",
+  "_from": "glob@~3.2.6"
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/test/00-setup.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/test/00-setup.js
new file mode 100644
index 0000000..245afaf
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/test/00-setup.js
@@ -0,0 +1,176 @@
+// just a little pre-run script to set up the fixtures.
+// zz-finish cleans it up
+
+var mkdirp = require("mkdirp")
+var path = require("path")
+var i = 0
+var tap = require("tap")
+var fs = require("fs")
+var rimraf = require("rimraf")
+
+var files =
+[ "a/.abcdef/x/y/z/a"
+, "a/abcdef/g/h"
+, "a/abcfed/g/h"
+, "a/b/c/d"
+, "a/bc/e/f"
+, "a/c/d/c/b"
+, "a/cb/e/f"
+]
+
+var symlinkTo = path.resolve(__dirname, "a/symlink/a/b/c")
+var symlinkFrom = "../.."
+
+files = files.map(function (f) {
+  return path.resolve(__dirname, f)
+})
+
+tap.test("remove fixtures", function (t) {
+  rimraf(path.resolve(__dirname, "a"), function (er) {
+    t.ifError(er, "remove fixtures")
+    t.end()
+  })
+})
+
+files.forEach(function (f) {
+  tap.test(f, function (t) {
+    var d = path.dirname(f)
+    mkdirp(d, 0755, function (er) {
+      if (er) {
+        t.fail(er)
+        return t.bailout()
+      }
+      fs.writeFile(f, "i like tests", function (er) {
+        t.ifError(er, "make file")
+        t.end()
+      })
+    })
+  })
+})
+
+if (process.platform !== "win32") {
+  tap.test("symlinky", function (t) {
+    var d = path.dirname(symlinkTo)
+    console.error("mkdirp", d)
+    mkdirp(d, 0755, function (er) {
+      t.ifError(er)
+      fs.symlink(symlinkFrom, symlinkTo, "dir", function (er) {
+        t.ifError(er, "make symlink")
+        t.end()
+      })
+    })
+  })
+}
+
+;["foo","bar","baz","asdf","quux","qwer","rewq"].forEach(function (w) {
+  w = "/tmp/glob-test/" + w
+  tap.test("create " + w, function (t) {
+    mkdirp(w, function (er) {
+      if (er)
+        throw er
+      t.pass(w)
+      t.end()
+    })
+  })
+})
+
+
+// generate the bash pattern test-fixtures if possible
+if (process.platform === "win32" || !process.env.TEST_REGEN) {
+  console.error("Windows, or TEST_REGEN unset.  Using cached fixtures.")
+  return
+}
+
+var spawn = require("child_process").spawn;
+var globs =
+  // put more patterns here.
+  // anything that would be directly in / should be in /tmp/glob-test
+  ["test/a/*/+(c|g)/./d"
+  ,"test/a/**/[cg]/../[cg]"
+  ,"test/a/{b,c,d,e,f}/**/g"
+  ,"test/a/b/**"
+  ,"test/**/g"
+  ,"test/a/abc{fed,def}/g/h"
+  ,"test/a/abc{fed/g,def}/**/"
+  ,"test/a/abc{fed/g,def}/**///**/"
+  ,"test/**/a/**/"
+  ,"test/+(a|b|c)/a{/,bc*}/**"
+  ,"test/*/*/*/f"
+  ,"test/**/f"
+  ,"test/a/symlink/a/b/c/a/b/c/a/b/c//a/b/c////a/b/c/**/b/c/**"
+  ,"{./*/*,/tmp/glob-test/*}"
+  ,"{/tmp/glob-test/*,*}" // evil owl face!  how you taunt me!
+  ,"test/a/!(symlink)/**"
+  ]
+var bashOutput = {}
+var fs = require("fs")
+
+globs.forEach(function (pattern) {
+  tap.test("generate fixture " + pattern, function (t) {
+    var cmd = "shopt -s globstar && " +
+              "shopt -s extglob && " +
+              "shopt -s nullglob && " +
+              // "shopt >&2; " +
+              "eval \'for i in " + pattern + "; do echo $i; done\'"
+    var cp = spawn("bash", ["-c", cmd], { cwd: path.dirname(__dirname) })
+    var out = []
+    cp.stdout.on("data", function (c) {
+      out.push(c)
+    })
+    cp.stderr.pipe(process.stderr)
+    cp.on("close", function (code) {
+      out = flatten(out)
+      if (!out)
+        out = []
+      else
+        out = cleanResults(out.split(/\r*\n/))
+
+      bashOutput[pattern] = out
+      t.notOk(code, "bash test should finish nicely")
+      t.end()
+    })
+  })
+})
+
+tap.test("save fixtures", function (t) {
+  var fname = path.resolve(__dirname, "bash-results.json")
+  var data = JSON.stringify(bashOutput, null, 2) + "\n"
+  fs.writeFile(fname, data, function (er) {
+    t.ifError(er)
+    t.end()
+  })
+})
+
+function cleanResults (m) {
+  // normalize discrepancies in ordering, duplication,
+  // and ending slashes.
+  return m.map(function (m) {
+    return m.replace(/\/+/g, "/").replace(/\/$/, "")
+  }).sort(alphasort).reduce(function (set, f) {
+    if (f !== set[set.length - 1]) set.push(f)
+    return set
+  }, []).sort(alphasort).map(function (f) {
+    // de-windows
+    return (process.platform !== 'win32') ? f
+           : f.replace(/^[a-zA-Z]:\\\\/, '/').replace(/\\/g, '/')
+  })
+}
+
+function flatten (chunks) {
+  var s = 0
+  chunks.forEach(function (c) { s += c.length })
+  var out = new Buffer(s)
+  s = 0
+  chunks.forEach(function (c) {
+    c.copy(out, s)
+    s += c.length
+  })
+
+  return out.toString().trim()
+}
+
+function alphasort (a, b) {
+  a = a.toLowerCase()
+  b = b.toLowerCase()
+  return a > b ? 1 : a < b ? -1 : 0
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/test/bash-comparison.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/test/bash-comparison.js
new file mode 100644
index 0000000..239ed1a
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/test/bash-comparison.js
@@ -0,0 +1,63 @@
+// basic test
+// show that it does the same thing by default as the shell.
+var tap = require("tap")
+, child_process = require("child_process")
+, bashResults = require("./bash-results.json")
+, globs = Object.keys(bashResults)
+, glob = require("../")
+, path = require("path")
+
+// run from the root of the project
+// this is usually where you're at anyway, but be sure.
+process.chdir(path.resolve(__dirname, ".."))
+
+function alphasort (a, b) {
+  a = a.toLowerCase()
+  b = b.toLowerCase()
+  return a > b ? 1 : a < b ? -1 : 0
+}
+
+globs.forEach(function (pattern) {
+  var expect = bashResults[pattern]
+  // anything regarding the symlink thing will fail on windows, so just skip it
+  if (process.platform === "win32" &&
+      expect.some(function (m) {
+        return /\/symlink\//.test(m)
+      }))
+    return
+
+  tap.test(pattern, function (t) {
+    glob(pattern, function (er, matches) {
+      if (er)
+        throw er
+
+      // sort and unmark, just to match the shell results
+      matches = cleanResults(matches)
+
+      t.deepEqual(matches, expect, pattern)
+      t.end()
+    })
+  })
+
+  tap.test(pattern + " sync", function (t) {
+    var matches = cleanResults(glob.sync(pattern))
+
+    t.deepEqual(matches, expect, "should match shell")
+    t.end()
+  })
+})
+
+function cleanResults (m) {
+  // normalize discrepancies in ordering, duplication,
+  // and ending slashes.
+  return m.map(function (m) {
+    return m.replace(/\/+/g, "/").replace(/\/$/, "")
+  }).sort(alphasort).reduce(function (set, f) {
+    if (f !== set[set.length - 1]) set.push(f)
+    return set
+  }, []).sort(alphasort).map(function (f) {
+    // de-windows
+    return (process.platform !== 'win32') ? f
+           : f.replace(/^[a-zA-Z]:[\/\\]+/, '/').replace(/[\\\/]+/g, '/')
+  })
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/test/bash-results.json b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/test/bash-results.json
new file mode 100644
index 0000000..8051c72
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/test/bash-results.json
@@ -0,0 +1,351 @@
+{
+  "test/a/*/+(c|g)/./d": [
+    "test/a/b/c/./d"
+  ],
+  "test/a/**/[cg]/../[cg]": [
+    "test/a/abcdef/g/../g",
+    "test/a/abcfed/g/../g",
+    "test/a/b/c/../c",
+    "test/a/c/../c",
+    "test/a/c/d/c/../c",
+    "test/a/symlink/a/b/c/../c",
+    "test/a/symlink/a/b/c/a/b/c/../c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/../c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/../c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/../c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/../c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/../c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/../c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/../c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/../c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/../c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/../c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/../c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/../c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/../c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/../c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/../c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/../c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/../c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/../c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/../c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/../c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/../c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/../c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/../c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/../c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/../c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/../c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/../c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/../c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/../c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/../c"
+  ],
+  "test/a/{b,c,d,e,f}/**/g": [],
+  "test/a/b/**": [
+    "test/a/b",
+    "test/a/b/c",
+    "test/a/b/c/d"
+  ],
+  "test/**/g": [
+    "test/a/abcdef/g",
+    "test/a/abcfed/g"
+  ],
+  "test/a/abc{fed,def}/g/h": [
+    "test/a/abcdef/g/h",
+    "test/a/abcfed/g/h"
+  ],
+  "test/a/abc{fed/g,def}/**/": [
+    "test/a/abcdef",
+    "test/a/abcdef/g",
+    "test/a/abcfed/g"
+  ],
+  "test/a/abc{fed/g,def}/**///**/": [
+    "test/a/abcdef",
+    "test/a/abcdef/g",
+    "test/a/abcfed/g"
+  ],
+  "test/**/a/**/": [
+    "test/a",
+    "test/a/abcdef",
+    "test/a/abcdef/g",
+    "test/a/abcfed",
+    "test/a/abcfed/g",
+    "test/a/b",
+    "test/a/b/c",
+    "test/a/bc",
+    "test/a/bc/e",
+    "test/a/c",
+    "test/a/c/d",
+    "test/a/c/d/c",
+    "test/a/cb",
+    "test/a/cb/e",
+    "test/a/symlink",
+    "test/a/symlink/a",
+    "test/a/symlink/a/b",
+    "test/a/symlink/a/b/c",
+    "test/a/symlink/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b"
+  ],
+  "test/+(a|b|c)/a{/,bc*}/**": [
+    "test/a/abcdef",
+    "test/a/abcdef/g",
+    "test/a/abcdef/g/h",
+    "test/a/abcfed",
+    "test/a/abcfed/g",
+    "test/a/abcfed/g/h"
+  ],
+  "test/*/*/*/f": [
+    "test/a/bc/e/f",
+    "test/a/cb/e/f"
+  ],
+  "test/**/f": [
+    "test/a/bc/e/f",
+    "test/a/cb/e/f"
+  ],
+  "test/a/symlink/a/b/c/a/b/c/a/b/c//a/b/c////a/b/c/**/b/c/**": [
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b",
+    "test/a/symlink/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c/a/b/c"
+  ],
+  "{./*/*,/tmp/glob-test/*}": [
+    "./examples/g.js",
+    "./examples/usr-local.js",
+    "./node_modules/inherits",
+    "./node_modules/minimatch",
+    "./node_modules/mkdirp",
+    "./node_modules/rimraf",
+    "./node_modules/tap",
+    "./test/00-setup.js",
+    "./test/a",
+    "./test/bash-comparison.js",
+    "./test/bash-results.json",
+    "./test/cwd-test.js",
+    "./test/globstar-match.js",
+    "./test/mark.js",
+    "./test/new-glob-optional-options.js",
+    "./test/nocase-nomagic.js",
+    "./test/pause-resume.js",
+    "./test/readme-issue.js",
+    "./test/root-nomount.js",
+    "./test/root.js",
+    "./test/stat.js",
+    "./test/zz-cleanup.js",
+    "/tmp/glob-test/asdf",
+    "/tmp/glob-test/bar",
+    "/tmp/glob-test/baz",
+    "/tmp/glob-test/foo",
+    "/tmp/glob-test/quux",
+    "/tmp/glob-test/qwer",
+    "/tmp/glob-test/rewq"
+  ],
+  "{/tmp/glob-test/*,*}": [
+    "/tmp/glob-test/asdf",
+    "/tmp/glob-test/bar",
+    "/tmp/glob-test/baz",
+    "/tmp/glob-test/foo",
+    "/tmp/glob-test/quux",
+    "/tmp/glob-test/qwer",
+    "/tmp/glob-test/rewq",
+    "examples",
+    "glob.js",
+    "LICENSE",
+    "node_modules",
+    "package.json",
+    "README.md",
+    "test"
+  ],
+  "test/a/!(symlink)/**": [
+    "test/a/abcdef",
+    "test/a/abcdef/g",
+    "test/a/abcdef/g/h",
+    "test/a/abcfed",
+    "test/a/abcfed/g",
+    "test/a/abcfed/g/h",
+    "test/a/b",
+    "test/a/b/c",
+    "test/a/b/c/d",
+    "test/a/bc",
+    "test/a/bc/e",
+    "test/a/bc/e/f",
+    "test/a/c",
+    "test/a/c/d",
+    "test/a/c/d/c",
+    "test/a/c/d/c/b",
+    "test/a/cb",
+    "test/a/cb/e",
+    "test/a/cb/e/f"
+  ]
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/test/cwd-test.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/test/cwd-test.js
new file mode 100644
index 0000000..352c27e
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/test/cwd-test.js
@@ -0,0 +1,55 @@
+var tap = require("tap")
+
+var origCwd = process.cwd()
+process.chdir(__dirname)
+
+tap.test("changing cwd and searching for **/d", function (t) {
+  var glob = require('../')
+  var path = require('path')
+  t.test('.', function (t) {
+    glob('**/d', function (er, matches) {
+      t.ifError(er)
+      t.like(matches, [ 'a/b/c/d', 'a/c/d' ])
+      t.end()
+    })
+  })
+
+  t.test('a', function (t) {
+    glob('**/d', {cwd:path.resolve('a')}, function (er, matches) {
+      t.ifError(er)
+      t.like(matches, [ 'b/c/d', 'c/d' ])
+      t.end()
+    })
+  })
+
+  t.test('a/b', function (t) {
+    glob('**/d', {cwd:path.resolve('a/b')}, function (er, matches) {
+      t.ifError(er)
+      t.like(matches, [ 'c/d' ])
+      t.end()
+    })
+  })
+
+  t.test('a/b/', function (t) {
+    glob('**/d', {cwd:path.resolve('a/b/')}, function (er, matches) {
+      t.ifError(er)
+      t.like(matches, [ 'c/d' ])
+      t.end()
+    })
+  })
+
+  t.test('.', function (t) {
+    glob('**/d', {cwd: process.cwd()}, function (er, matches) {
+      t.ifError(er)
+      t.like(matches, [ 'a/b/c/d', 'a/c/d' ])
+      t.end()
+    })
+  })
+
+  t.test('cd -', function (t) {
+    process.chdir(origCwd)
+    t.end()
+  })
+
+  t.end()
+})
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/test/globstar-match.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/test/globstar-match.js
new file mode 100644
index 0000000..9b234fa
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/test/globstar-match.js
@@ -0,0 +1,19 @@
+var Glob = require("../glob.js").Glob
+var test = require('tap').test
+
+test('globstar should not have dupe matches', function(t) {
+  var pattern = 'a/**/[gh]'
+  var g = new Glob(pattern, { cwd: __dirname })
+  var matches = []
+  g.on('match', function(m) {
+    console.error('match %j', m)
+    matches.push(m)
+  })
+  g.on('end', function(set) {
+    console.error('set', set)
+    matches = matches.sort()
+    set = set.sort()
+    t.same(matches, set, 'should have same set of matches')
+    t.end()
+  })
+})
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/test/mark.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/test/mark.js
new file mode 100644
index 0000000..bf411c0
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/test/mark.js
@@ -0,0 +1,118 @@
+var test = require("tap").test
+var glob = require('../')
+process.chdir(__dirname)
+
+// expose timing issues
+var lag = 5
+glob.Glob.prototype._stat = function(o) { return function(f, cb) {
+  var args = arguments
+  setTimeout(function() {
+    o.call(this, f, cb)
+  }.bind(this), lag += 5)
+}}(glob.Glob.prototype._stat)
+
+
+test("mark, with **", function (t) {
+  glob("a/*b*/**", {mark: true}, function (er, results) {
+    if (er)
+      throw er
+    var expect =
+      [ 'a/abcdef/',
+        'a/abcdef/g/',
+        'a/abcdef/g/h',
+        'a/abcfed/',
+        'a/abcfed/g/',
+        'a/abcfed/g/h',
+        'a/b/',
+        'a/b/c/',
+        'a/b/c/d',
+        'a/bc/',
+        'a/bc/e/',
+        'a/bc/e/f',
+        'a/cb/',
+        'a/cb/e/',
+        'a/cb/e/f' ]
+
+    t.same(results, expect)
+    t.end()
+  })
+})
+
+test("mark, no / on pattern", function (t) {
+  glob("a/*", {mark: true}, function (er, results) {
+    if (er)
+      throw er
+    var expect = [ 'a/abcdef/',
+                   'a/abcfed/',
+                   'a/b/',
+                   'a/bc/',
+                   'a/c/',
+                   'a/cb/' ]
+
+    if (process.platform !== "win32")
+      expect.push('a/symlink/')
+
+    t.same(results, expect)
+    t.end()
+  }).on('match', function(m) {
+    t.similar(m, /\/$/)
+  })
+})
+
+test("mark=false, no / on pattern", function (t) {
+  glob("a/*", function (er, results) {
+    if (er)
+      throw er
+    var expect = [ 'a/abcdef',
+                   'a/abcfed',
+                   'a/b',
+                   'a/bc',
+                   'a/c',
+                   'a/cb' ]
+
+    if (process.platform !== "win32")
+      expect.push('a/symlink')
+    t.same(results, expect)
+    t.end()
+  }).on('match', function(m) {
+    t.similar(m, /[^\/]$/)
+  })
+})
+
+test("mark=true, / on pattern", function (t) {
+  glob("a/*/", {mark: true}, function (er, results) {
+    if (er)
+      throw er
+    var expect = [ 'a/abcdef/',
+                    'a/abcfed/',
+                    'a/b/',
+                    'a/bc/',
+                    'a/c/',
+                    'a/cb/' ]
+    if (process.platform !== "win32")
+      expect.push('a/symlink/')
+    t.same(results, expect)
+    t.end()
+  }).on('match', function(m) {
+    t.similar(m, /\/$/)
+  })
+})
+
+test("mark=false, / on pattern", function (t) {
+  glob("a/*/", function (er, results) {
+    if (er)
+      throw er
+    var expect = [ 'a/abcdef/',
+                   'a/abcfed/',
+                   'a/b/',
+                   'a/bc/',
+                   'a/c/',
+                   'a/cb/' ]
+    if (process.platform !== "win32")
+      expect.push('a/symlink/')
+    t.same(results, expect)
+    t.end()
+  }).on('match', function(m) {
+    t.similar(m, /\/$/)
+  })
+})
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/test/new-glob-optional-options.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/test/new-glob-optional-options.js
new file mode 100644
index 0000000..3e7dc5a
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/test/new-glob-optional-options.js
@@ -0,0 +1,10 @@
+var Glob = require('../glob.js').Glob;
+var test = require('tap').test;
+
+test('new glob, with cb, and no options', function (t) {
+  new Glob(__filename, function(er, results) {
+    if (er) throw er;
+    t.same(results, [__filename]);
+    t.end();
+  });
+});
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/test/nocase-nomagic.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/test/nocase-nomagic.js
new file mode 100644
index 0000000..2503f23
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/test/nocase-nomagic.js
@@ -0,0 +1,113 @@
+var fs = require('fs');
+var test = require('tap').test;
+var glob = require('../');
+
+test('mock fs', function(t) {
+  var stat = fs.stat
+  var statSync = fs.statSync
+  var readdir = fs.readdir
+  var readdirSync = fs.readdirSync
+
+  function fakeStat(path) {
+    var ret
+    switch (path.toLowerCase()) {
+      case '/tmp': case '/tmp/':
+        ret = { isDirectory: function() { return true } }
+        break
+      case '/tmp/a':
+        ret = { isDirectory: function() { return false } }
+        break
+    }
+    return ret
+  }
+
+  fs.stat = function(path, cb) {
+    var f = fakeStat(path);
+    if (f) {
+      process.nextTick(function() {
+        cb(null, f)
+      })
+    } else {
+      stat.call(fs, path, cb)
+    }
+  }
+
+  fs.statSync = function(path) {
+    return fakeStat(path) || statSync.call(fs, path)
+  }
+
+  function fakeReaddir(path) {
+    var ret
+    switch (path.toLowerCase()) {
+      case '/tmp': case '/tmp/':
+        ret = [ 'a', 'A' ]
+        break
+      case '/':
+        ret = ['tmp', 'tMp', 'tMP', 'TMP']
+    }
+    return ret
+  }
+
+  fs.readdir = function(path, cb) {
+    var f = fakeReaddir(path)
+    if (f)
+      process.nextTick(function() {
+        cb(null, f)
+      })
+    else
+      readdir.call(fs, path, cb)
+  }
+
+  fs.readdirSync = function(path) {
+    return fakeReaddir(path) || readdirSync.call(fs, path)
+  }
+
+  t.pass('mocked')
+  t.end()
+})
+
+test('nocase, nomagic', function(t) {
+  var n = 2
+  var want = [ '/TMP/A',
+               '/TMP/a',
+               '/tMP/A',
+               '/tMP/a',
+               '/tMp/A',
+               '/tMp/a',
+               '/tmp/A',
+               '/tmp/a' ]
+  glob('/tmp/a', { nocase: true }, function(er, res) {
+    if (er)
+      throw er
+    t.same(res.sort(), want)
+    if (--n === 0) t.end()
+  })
+  glob('/tmp/A', { nocase: true }, function(er, res) {
+    if (er)
+      throw er
+    t.same(res.sort(), want)
+    if (--n === 0) t.end()
+  })
+})
+
+test('nocase, with some magic', function(t) {
+  t.plan(2)
+  var want = [ '/TMP/A',
+               '/TMP/a',
+               '/tMP/A',
+               '/tMP/a',
+               '/tMp/A',
+               '/tMp/a',
+               '/tmp/A',
+               '/tmp/a' ]
+  glob('/tmp/*', { nocase: true }, function(er, res) {
+    if (er)
+      throw er
+    t.same(res.sort(), want)
+  })
+  glob('/tmp/*', { nocase: true }, function(er, res) {
+    if (er)
+      throw er
+    t.same(res.sort(), want)
+  })
+})
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/test/pause-resume.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/test/pause-resume.js
new file mode 100644
index 0000000..e1ffbab
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/test/pause-resume.js
@@ -0,0 +1,73 @@
+// show that no match events happen while paused.
+var tap = require("tap")
+, child_process = require("child_process")
+// just some gnarly pattern with lots of matches
+, pattern = "test/a/!(symlink)/**"
+, bashResults = require("./bash-results.json")
+, patterns = Object.keys(bashResults)
+, glob = require("../")
+, Glob = glob.Glob
+, path = require("path")
+
+// run from the root of the project
+// this is usually where you're at anyway, but be sure.
+process.chdir(path.resolve(__dirname, ".."))
+
+function alphasort (a, b) {
+  a = a.toLowerCase()
+  b = b.toLowerCase()
+  return a > b ? 1 : a < b ? -1 : 0
+}
+
+function cleanResults (m) {
+  // normalize discrepancies in ordering, duplication,
+  // and ending slashes.
+  return m.map(function (m) {
+    return m.replace(/\/+/g, "/").replace(/\/$/, "")
+  }).sort(alphasort).reduce(function (set, f) {
+    if (f !== set[set.length - 1]) set.push(f)
+    return set
+  }, []).sort(alphasort).map(function (f) {
+    // de-windows
+    return (process.platform !== 'win32') ? f
+           : f.replace(/^[a-zA-Z]:\\\\/, '/').replace(/\\/g, '/')
+  })
+}
+
+var globResults = []
+tap.test("use a Glob object, and pause/resume it", function (t) {
+  var g = new Glob(pattern)
+  , paused = false
+  , res = []
+  , expect = bashResults[pattern]
+
+  g.on("pause", function () {
+    console.error("pause")
+  })
+
+  g.on("resume", function () {
+    console.error("resume")
+  })
+
+  g.on("match", function (m) {
+    t.notOk(g.paused, "must not be paused")
+    globResults.push(m)
+    g.pause()
+    t.ok(g.paused, "must be paused")
+    setTimeout(g.resume.bind(g), 10)
+  })
+
+  g.on("end", function (matches) {
+    t.pass("reached glob end")
+    globResults = cleanResults(globResults)
+    matches = cleanResults(matches)
+    t.deepEqual(matches, globResults,
+      "end event matches should be the same as match events")
+
+    t.deepEqual(matches, expect,
+      "glob matches should be the same as bash results")
+
+    t.end()
+  })
+})
+
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/test/readme-issue.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/test/readme-issue.js
new file mode 100644
index 0000000..0b4e0be
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/test/readme-issue.js
@@ -0,0 +1,36 @@
+var test = require("tap").test
+var glob = require("../")
+
+var mkdirp = require("mkdirp")
+var fs = require("fs")
+var rimraf = require("rimraf")
+var dir = __dirname + "/package"
+
+test("setup", function (t) {
+  mkdirp.sync(dir)
+  fs.writeFileSync(dir + "/package.json", "{}", "ascii")
+  fs.writeFileSync(dir + "/README", "x", "ascii")
+  t.pass("setup done")
+  t.end()
+})
+
+test("glob", function (t) {
+  var opt = {
+    cwd: dir,
+    nocase: true,
+    mark: true
+  }
+
+  glob("README?(.*)", opt, function (er, files) {
+    if (er)
+      throw er
+    t.same(files, ["README"])
+    t.end()
+  })
+})
+
+test("cleanup", function (t) {
+  rimraf.sync(dir)
+  t.pass("clean")
+  t.end()
+})
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/test/root-nomount.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/test/root-nomount.js
new file mode 100644
index 0000000..3ac5979
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/test/root-nomount.js
@@ -0,0 +1,39 @@
+var tap = require("tap")
+
+var origCwd = process.cwd()
+process.chdir(__dirname)
+
+tap.test("changing root and searching for /b*/**", function (t) {
+  var glob = require('../')
+  var path = require('path')
+  t.test('.', function (t) {
+    glob('/b*/**', { globDebug: true, root: '.', nomount: true }, function (er, matches) {
+      t.ifError(er)
+      t.like(matches, [])
+      t.end()
+    })
+  })
+
+  t.test('a', function (t) {
+    glob('/b*/**', { globDebug: true, root: path.resolve('a'), nomount: true }, function (er, matches) {
+      t.ifError(er)
+      t.like(matches, [ '/b', '/b/c', '/b/c/d', '/bc', '/bc/e', '/bc/e/f' ])
+      t.end()
+    })
+  })
+
+  t.test('root=a, cwd=a/b', function (t) {
+    glob('/b*/**', { globDebug: true, root: 'a', cwd: path.resolve('a/b'), nomount: true }, function (er, matches) {
+      t.ifError(er)
+      t.like(matches, [ '/b', '/b/c', '/b/c/d', '/bc', '/bc/e', '/bc/e/f' ])
+      t.end()
+    })
+  })
+
+  t.test('cd -', function (t) {
+    process.chdir(origCwd)
+    t.end()
+  })
+
+  t.end()
+})
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/test/root.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/test/root.js
new file mode 100644
index 0000000..95c23f9
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/test/root.js
@@ -0,0 +1,46 @@
+var t = require("tap")
+
+var origCwd = process.cwd()
+process.chdir(__dirname)
+
+var glob = require('../')
+var path = require('path')
+
+t.test('.', function (t) {
+  glob('/b*/**', { globDebug: true, root: '.' }, function (er, matches) {
+    t.ifError(er)
+    t.like(matches, [])
+    t.end()
+  })
+})
+
+
+t.test('a', function (t) {
+  console.error("root=" + path.resolve('a'))
+  glob('/b*/**', { globDebug: true, root: path.resolve('a') }, function (er, matches) {
+    t.ifError(er)
+    var wanted = [
+        '/b', '/b/c', '/b/c/d', '/bc', '/bc/e', '/bc/e/f'
+      ].map(function (m) {
+        return path.join(path.resolve('a'), m).replace(/\\/g, '/')
+      })
+
+    t.like(matches, wanted)
+    t.end()
+  })
+})
+
+t.test('root=a, cwd=a/b', function (t) {
+  glob('/b*/**', { globDebug: true, root: 'a', cwd: path.resolve('a/b') }, function (er, matches) {
+    t.ifError(er)
+    t.like(matches, [ '/b', '/b/c', '/b/c/d', '/bc', '/bc/e', '/bc/e/f' ].map(function (m) {
+      return path.join(path.resolve('a'), m).replace(/\\/g, '/')
+    }))
+    t.end()
+  })
+})
+
+t.test('cd -', function (t) {
+  process.chdir(origCwd)
+  t.end()
+})
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/test/stat.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/test/stat.js
new file mode 100644
index 0000000..6291711
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/test/stat.js
@@ -0,0 +1,32 @@
+var glob = require('../')
+var test = require('tap').test
+var path = require('path')
+
+test('stat all the things', function(t) {
+  var g = new glob.Glob('a/*abc*/**', { stat: true, cwd: __dirname })
+  var matches = []
+  g.on('match', function(m) {
+    matches.push(m)
+  })
+  var stats = []
+  g.on('stat', function(m) {
+    stats.push(m)
+  })
+  g.on('end', function(eof) {
+    stats = stats.sort()
+    matches = matches.sort()
+    eof = eof.sort()
+    t.same(stats, matches)
+    t.same(eof, matches)
+    var cache = Object.keys(this.statCache)
+    t.same(cache.map(function (f) {
+      return path.relative(__dirname, f)
+    }).sort(), matches)
+
+    cache.forEach(function(c) {
+      t.equal(typeof this.statCache[c], 'object')
+    }, this)
+
+    t.end()
+  })
+})
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/test/zz-cleanup.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/test/zz-cleanup.js
new file mode 100644
index 0000000..e085f0f
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/glob/test/zz-cleanup.js
@@ -0,0 +1,11 @@
+// remove the fixtures
+var tap = require("tap")
+, rimraf = require("rimraf")
+, path = require("path")
+
+tap.test("cleanup fixtures", function (t) {
+  rimraf(path.resolve(__dirname, "a"), function (er) {
+    t.ifError(er, "removed")
+    t.end()
+  })
+})
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lazystream/.npmignore b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lazystream/.npmignore
new file mode 100644
index 0000000..8030a49
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lazystream/.npmignore
@@ -0,0 +1,3 @@
+npm-debug.log
+node_modules/
+test/tmp/
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lazystream/.travis.yml b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lazystream/.travis.yml
new file mode 100644
index 0000000..5c11909
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lazystream/.travis.yml
@@ -0,0 +1,5 @@
+language: node_js
+node_js:
+  - "0.10"
+  - "0.8"
+# - "0.6"
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lazystream/LICENSE-MIT b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lazystream/LICENSE-MIT
new file mode 100644
index 0000000..982db13
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lazystream/LICENSE-MIT
@@ -0,0 +1,23 @@
+Copyright (c) 2013 J. Pommerening, contributors.
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lazystream/README.md b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lazystream/README.md
new file mode 100644
index 0000000..3af9caf
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lazystream/README.md
@@ -0,0 +1,100 @@
+# Lazy Streams
+
+> *Create streams lazily when they are read from or written to.*  
+> `lazystream: 0.0.2` [![Build Status](https://travis-ci.org/jpommerening/node-lazystream.png?branch=master)](https://travis-ci.org/jpommerening/node-lazystream)  
+
+## Why?
+
+Sometimes you feel the itch to open *all the files* at once. You want to pass a bunch of streams around, so the consumer does not need to worry where the data comes from.
+From a software design point-of-view this sounds entirely reasonable. Then there is that neat little function `fs.createReadStream()` that opens a file and gives you a nice `fs.ReadStream` to pass around, so you use what the mighty creator deities of node bestowed upon you.
+
+> `Error: EMFILE, too many open files`  
+> ─ *node*
+
+This package provides two classes based on the node's new streams API (or `readable-stream` if you are using node a node version earlier than 0.10):
+
+## Class: lazystream.Readable
+
+A wrapper for readable streams. Extends [`stream.PassThrough`](http://nodejs.org/api/stream.html#stream_class_stream_passthrough).
+
+### new lazystream.Readable(fn [, options])
+
+* `fn` *{Function}*  
+  The function that the lazy stream will call to obtain the stream to actually read from.
+* `options` *{Object}*  
+  Options for the underlying `PassThrough` stream, accessible by `fn`.
+
+Creates a new readable stream. Once the stream is accessed (for example when you call its `read()` method, or attach a `data`-event listener) the `fn` function is called with the outer `lazystream.Readable` instance bound to `this`.
+
+If you pass an `options` object to the constuctor, you can access it in your `fn` function.
+
+```javascript
+new lazystream.Readable(function (options) {
+  return fs.createReadStream('/dev/urandom');
+});
+```
+
+## Class: lazystream.Writable
+
+A wrapper for writable streams. Extends [`stream.PassThrough`](http://nodejs.org/api/stream.html#stream_class_stream_passthrough).
+
+### new lazystream.Writable(fn [, options])
+
+* `fn` *{Function}*  
+  The function that the lazy stream will call to obtain the stream to actually write to.
+* `options` *{Object}*  
+  Options for the underlying `PassThrough` stream, accessible by `fn`.
+
+Creates a new writable stream. Just like the one above but for writable streams.
+
+```javascript
+new lazystream.Writable(function () {
+  return fs.createWriteStream('/dev/null');
+});
+```
+
+## Install
+
+```console
+$ npm install lazystream --save
+npm http GET https://registry.npmjs.org/readable-stream
+npm http 200 https://registry.npmjs.org/readable-stream
+npm http GET https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.2.tgz
+npm http 200 https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.2.tgz
+lazystream@0.0.2 node_modules/lazystream
+└── readable-stream@1.0.2
+```
+
+## Contributing
+
+Fork it, branch it, send me a pull request. We'll work out the rest together.
+
+## Credits
+
+[Chris Talkington](https://github.com/ctalkington) and his [node-archiver](https://github.com/ctalkington/node-archiver) for providing a use-case.
+
+## [License](LICENSE-MIT)
+
+Copyright (c) 2013 J. Pommerening, contributors.
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lazystream/lib/lazystream.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lazystream/lib/lazystream.js
new file mode 100644
index 0000000..c6fbb39
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lazystream/lib/lazystream.js
@@ -0,0 +1,52 @@
+
+var util = require('util');
+var PassThrough = require('stream').PassThrough || require('readable-stream/passthrough');
+
+module.exports = {
+  Readable: Readable,
+  Writable: Writable
+};
+
+util.inherits(Readable, PassThrough);
+util.inherits(Writable, PassThrough);
+
+// Patch the given method of instance so that the callback
+// is executed once, before the actual method is called the
+// first time.
+function beforeFirstCall(instance, method, callback) {
+  instance[method] = function() {
+    delete instance[method];
+    callback.apply(this, arguments);
+    return this[method].apply(this, arguments);
+  };
+}
+
+function Readable(fn, options) {
+  if (!(this instanceof Readable))
+    return new Readable(fn, options);
+
+  PassThrough.call(this, options);
+
+  beforeFirstCall(this, '_read', function() {
+    var source = fn.call(this, options);
+    var that = this;
+    source.pipe(this);
+  });
+
+  this.emit('readable');
+}
+
+function Writable(fn, options) {
+  if (!(this instanceof Writable))
+    return new Writable(fn, options);
+
+  PassThrough.call(this, options);
+
+  beforeFirstCall(this, '_write', function() {
+    var destination = fn.call(this, options);
+    this.pipe(destination);
+  });
+
+  this.emit('writable');
+}
+
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lazystream/package.json b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lazystream/package.json
new file mode 100644
index 0000000..57168ae
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lazystream/package.json
@@ -0,0 +1,43 @@
+{
+  "name": "lazystream",
+  "version": "0.1.0",
+  "description": "Open Node Streams on demand.",
+  "homepage": "https://github.com/jpommerening/node-lazystream",
+  "author": {
+    "name": "J. Pommerening"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/jpommerening/node-lazystream.git"
+  },
+  "bugs": {
+    "url": "https://github.com/jpommerening/node-lazystream/issues"
+  },
+  "licenses": [
+    {
+      "type": "MIT",
+      "url": "https://github.com/jpommerening/node-lazystream/blob/master/LICENSE-MIT"
+    }
+  ],
+  "main": "lib/lazystream.js",
+  "engines": {
+    "node": ">= 0.6.3"
+  },
+  "scripts": {
+    "test": "nodeunit test/readable_test.js test/writable_test.js test/pipe_test.js test/fs_test.js"
+  },
+  "dependencies": {
+    "readable-stream": "~1.0.2"
+  },
+  "devDependencies": {
+    "nodeunit": "~0.7.4"
+  },
+  "keywords": [
+    "streams",
+    "stream"
+  ],
+  "readme": "# Lazy Streams\n\n> *Create streams lazily when they are read from or written to.*  \n> `lazystream: 0.0.2` [![Build Status](https://travis-ci.org/jpommerening/node-lazystream.png?branch=master)](https://travis-ci.org/jpommerening/node-lazystream)  \n\n## Why?\n\nSometimes you feel the itch to open *all the files* at once. You want to pass a bunch of streams around, so the consumer does not need to worry where the data comes from.\nFrom a software design point-of-view this sounds entirely reasonable. Then there is that neat little function `fs.createReadStream()` that opens a file and gives you a nice `fs.ReadStream` to pass around, so you use what the mighty creator deities of node bestowed upon you.\n\n> `Error: EMFILE, too many open files`  \n> ─ *node*\n\nThis package provides two classes based on the node's new streams API (or `readable-stream` if you are using node a node version earlier than 0.10):\n\n## Class: lazystream.Readable\n\nA wrapper for readable streams. Extends [`stream.PassThrough`](http://nodejs.org/api/stream.html#stream_class_stream_passthrough).\n\n### new lazystream.Readable(fn [, options])\n\n* `fn` *{Function}*  \n  The function that the lazy stream will call to obtain the stream to actually read from.\n* `options` *{Object}*  \n  Options for the underlying `PassThrough` stream, accessible by `fn`.\n\nCreates a new readable stream. Once the stream is accessed (for example when you call its `read()` method, or attach a `data`-event listener) the `fn` function is called with the outer `lazystream.Readable` instance bound to `this`.\n\nIf you pass an `options` object to the constuctor, you can access it in your `fn` function.\n\n```javascript\nnew lazystream.Readable(function (options) {\n  return fs.createReadStream('/dev/urandom');\n});\n```\n\n## Class: lazystream.Writable\n\nA wrapper for writable streams. Extends [`stream.PassThrough`](http://nodejs.org/api/stream.html#stream_class_stream_passthrough).\n\n### new lazystream.Writable(fn [, options])\n\n* `fn` *{Function}*  \n  The function that the lazy stream will call to obtain the stream to actually write to.\n* `options` *{Object}*  \n  Options for the underlying `PassThrough` stream, accessible by `fn`.\n\nCreates a new writable stream. Just like the one above but for writable streams.\n\n```javascript\nnew lazystream.Writable(function () {\n  return fs.createWriteStream('/dev/null');\n});\n```\n\n## Install\n\n```console\n$ npm install lazystream --save\nnpm http GET https://registry.npmjs.org/readable-stream\nnpm http 200 https://registry.npmjs.org/readable-stream\nnpm http GET https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.2.tgz\nnpm http 200 https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.2.tgz\nlazystream@0.0.2 node_modules/lazystream\n└── readable-stream@1.0.2\n```\n\n## Contributing\n\nFork it, branch it, send me a pull request. We'll work out the rest together.\n\n## Credits\n\n[Chris Talkington](https://github.com/ctalkington) and his [node-archiver](https://github.com/ctalkington/node-archiver) for providing a use-case.\n\n## [License](LICENSE-MIT)\n\nCopyright (c) 2013 J. Pommerening, contributors.\n\nPermission is hereby granted, free of charge, to any person\nobtaining a copy of this software and associated documentation\nfiles (the \"Software\"), to deal in the Software without\nrestriction, including without limitation the rights to use,\ncopy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the\nSoftware is furnished to do so, subject to the following\nconditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\nOF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\nHOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\nWHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\nFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\nOTHER DEALINGS IN THE SOFTWARE.\n\n",
+  "readmeFilename": "README.md",
+  "_id": "lazystream@0.1.0",
+  "_from": "lazystream@~0.1.0"
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lazystream/test/data.md b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lazystream/test/data.md
new file mode 100644
index 0000000..fc48222
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lazystream/test/data.md
@@ -0,0 +1,13 @@
+> Never mind, hey, this is really exciting, so much to find out about, so much to
+> look forward to, I'm quite dizzy with anticipation . . . Or is it the wind?
+> 
+> There really is a lot of that now, isn't there? And wow! Hey! What's this thing
+> suddenly coming toward me very fast? Very, very fast. So big and flat and round,
+> it needs a big wide-sounding name like . . . ow . . . ound . . . round . . .
+> ground! That's it! That's a good name- ground!
+>
+> I wonder if it will be friends with me?
+>
+> Hello Ground!
+
+And the rest, after a sudden wet thud, was silence.
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lazystream/test/fs_test.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lazystream/test/fs_test.js
new file mode 100644
index 0000000..149b1c4
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lazystream/test/fs_test.js
@@ -0,0 +1,69 @@
+
+var stream = require('../lib/lazystream');
+var fs = require('fs');
+var tmpDir = 'test/tmp/';
+var readFile = 'test/data.md';
+var writeFile = tmpDir + 'data.md';
+
+exports.fs = {
+  readwrite: function(test) {
+    var readfd, writefd;
+
+    var readable = new stream.Readable(function() {
+       return fs.createReadStream(readFile)
+        .on('open', function(fd) {
+          readfd = fd;
+        })
+        .on('close', function() {
+           readfd = undefined;
+           step();
+        });
+    });
+
+    var writable = new stream.Writable(function() {
+      return fs.createWriteStream(writeFile)
+        .on('open', function(fd) {
+          writefd = fd;
+        })
+        .on('close', function() {
+          writefd = undefined;
+           step();
+        });
+    });
+
+    test.expect(3);
+
+    test.equal(readfd, undefined, 'Input file should not be opened until read');
+    test.equal(writefd, undefined, 'Output file should not be opened until write');
+
+    if (!fs.existsSync(tmpDir)) {
+      fs.mkdirSync(tmpDir);
+    }
+    if (fs.existsSync(writeFile)) {
+      fs.unlinkSync(writeFile);
+    }
+
+    readable.on('end', function() { step(); });
+    writable.on('end', function() { step(); });
+
+    var steps = 0;
+    function step() {
+      steps += 1;
+      if (steps == 4) {
+        var input = fs.readFileSync(readFile);
+        var output = fs.readFileSync(writeFile);
+
+        test.ok(input >= output && input <= output, 'Should be equal');
+
+        fs.unlinkSync(writeFile);
+        fs.rmdirSync(tmpDir);
+
+        test.done();
+      }
+    };
+
+    readable.pipe(writable);
+  }
+};
+
+
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lazystream/test/helper.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lazystream/test/helper.js
new file mode 100644
index 0000000..9d41191
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lazystream/test/helper.js
@@ -0,0 +1,39 @@
+
+var _Readable = require('readable-stream/readable');
+var _Writable = require('readable-stream/writable');
+var util = require('util');
+
+module.exports = {
+  DummyReadable: DummyReadable,
+  DummyWritable: DummyWritable
+};
+
+function DummyReadable(strings) {
+  _Readable.call(this);
+  this.strings = strings;
+  this.emit('readable');
+}
+
+util.inherits(DummyReadable, _Readable);
+
+DummyReadable.prototype._read = function _read(n) {
+  if (this.strings.length) {
+    this.push(new Buffer(this.strings.shift()));
+  } else {
+    this.push(null);
+  }
+};
+
+function DummyWritable(strings) {
+  _Writable.call(this);
+  this.strings = strings;
+  this.emit('writable');
+}
+
+util.inherits(DummyWritable, _Writable);
+
+DummyWritable.prototype._write = function _write(chunk, encoding, callback) {
+  this.strings.push(chunk.toString());
+  if (callback) callback();
+};
+
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lazystream/test/pipe_test.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lazystream/test/pipe_test.js
new file mode 100644
index 0000000..7129e35
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lazystream/test/pipe_test.js
@@ -0,0 +1,36 @@
+
+var stream = require('../lib/lazystream');
+var helper = require('./helper');
+
+exports.pipe = {
+  readwrite: function(test) {
+    var expected = [ 'line1\n', 'line2\n' ];
+    var actual = [];
+    var readableInstantiated = false;
+    var writableInstantiated = false;
+
+    test.expect(3);
+
+    var readable = new stream.Readable(function() {
+      readableInstantiated = true;
+      return new helper.DummyReadable([].concat(expected));
+    });
+
+    var writable = new stream.Writable(function() {
+      writableInstantiated = true;
+      return new helper.DummyWritable(actual);
+    });
+
+    test.equal(readableInstantiated, false, 'DummyReadable should only be instantiated when it is needed');
+    test.equal(writableInstantiated, false, 'DummyWritable should only be instantiated when it is needed');
+
+    writable.on('end', function() {
+      test.equal(actual.join(''), expected.join(''), 'Piping on demand streams should keep data intact');
+      test.done();
+    });
+    
+    readable.pipe(writable);
+  }
+};
+
+
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lazystream/test/readable_test.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lazystream/test/readable_test.js
new file mode 100644
index 0000000..12eb05a
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lazystream/test/readable_test.js
@@ -0,0 +1,88 @@
+
+var Readable = require('../lib/lazystream').Readable;
+var DummyReadable = require('./helper').DummyReadable;
+
+exports.readable = {
+  dummy: function(test) {
+    var expected = [ 'line1\n', 'line2\n' ];
+    var actual = [];
+
+    test.expect(1);
+
+    new DummyReadable([].concat(expected))
+      .on('data', function(chunk) {
+        actual.push(chunk.toString());
+      })
+      .on('end', function() {
+        test.equal(actual.join(''), expected.join(''), 'DummyReadable should produce the data it was created with');
+        test.done();
+      });
+  },
+  options: function(test) {
+    test.expect(3);
+
+    var readable = new Readable(function(options) {
+       test.ok(this instanceof Readable, "Readable should bind itself to callback's this");
+       test.equal(options.encoding, "utf-8", "Readable should make options accessible to callback");
+       this.ok = true;
+       return new DummyReadable(["test"]);
+    }, {encoding: "utf-8"});
+
+    readable.read(4);
+
+    test.ok(readable.ok);
+
+    test.done();
+  },
+  streams2: function(test) {
+    var expected = [ 'line1\n', 'line2\n' ];
+    var actual = [];
+    var instantiated = false;
+
+    test.expect(2);
+
+    var readable = new Readable(function() {
+      instantiated = true;
+      return new DummyReadable([].concat(expected));
+    });
+
+    test.equal(instantiated, false, 'DummyReadable should only be instantiated when it is needed');
+
+    readable.on('readable', function() {
+      var chunk = readable.read();
+      actual.push(chunk.toString());
+    });
+    readable.on('end', function() {
+      test.equal(actual.join(''), expected.join(''), 'Readable should not change the data of the underlying stream');
+      test.done();
+    });
+
+    readable.read(0);
+  },
+  resume: function(test) {
+    var expected = [ 'line1\n', 'line2\n' ];
+    var actual = [];
+    var instantiated = false;
+
+    test.expect(2);
+
+    var readable = new Readable(function() {
+      instantiated = true;
+      return new DummyReadable([].concat(expected));
+    });
+
+    readable.pause();
+
+    readable.on('data', function(chunk) {
+      actual.push(chunk.toString());
+    });
+    readable.on('end', function() {
+      test.equal(actual.join(''), expected.join(''), 'Readable should not change the data of the underlying stream');
+      test.done();
+    });
+
+    test.equal(instantiated, false, 'DummyReadable should only be instantiated when it is needed');
+    
+    readable.resume();
+  }
+};
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lazystream/test/writable_test.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lazystream/test/writable_test.js
new file mode 100644
index 0000000..a663845
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lazystream/test/writable_test.js
@@ -0,0 +1,59 @@
+
+var Writable = require('../lib/lazystream').Writable;
+var DummyWritable = require('./helper').DummyWritable;
+
+exports.writable = {
+  options: function(test) {
+    test.expect(3);
+
+    var writable = new Writable(function(options) {
+       test.ok(this instanceof Writable, "Writable should bind itself to callback's this");
+       test.equal(options.encoding, "utf-8", "Writable should make options accessible to callback");
+       this.ok = true;
+       return new DummyWritable([]);
+    }, {encoding: "utf-8"});
+
+    writable.write("test");
+
+    test.ok(writable.ok);
+
+    test.done();
+  },
+  dummy: function(test) {
+    var expected = [ 'line1\n', 'line2\n' ];
+    var actual = [];
+    
+    test.expect(0);
+
+    var dummy = new DummyWritable(actual);
+
+    expected.forEach(function(item) {
+      dummy.write(new Buffer(item));
+    });
+    test.done();
+  },
+  streams2: function(test) {
+    var expected = [ 'line1\n', 'line2\n' ];
+    var actual = [];
+    var instantiated = false;
+
+    test.expect(2);
+
+    var writable = new Writable(function() {
+      instantiated = true;
+      return new DummyWritable(actual);
+    });
+
+    test.equal(instantiated, false, 'DummyWritable should only be instantiated when it is needed');
+
+    writable.on('end', function() {
+      test.equal(actual.join(''), expected.join(''), 'Writable should not change the data of the underlying stream');
+      test.done();
+    });
+
+    expected.forEach(function(item) {
+      writable.write(new Buffer(item));
+    });
+    writable.end();
+  }
+};
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lodash/LICENSE.txt b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lodash/LICENSE.txt
new file mode 100644
index 0000000..49869bb
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lodash/LICENSE.txt
@@ -0,0 +1,22 @@
+Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
+Based on Underscore.js 1.5.2, copyright 2009-2013 Jeremy Ashkenas,
+DocumentCloud and Investigative Reporters & Editors <http://underscorejs.org/>
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lodash/README.md b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lodash/README.md
new file mode 100644
index 0000000..6f9598e
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lodash/README.md
@@ -0,0 +1,163 @@
+# Lo-Dash v2.4.1
+A utility library delivering consistency, [customization](http://lodash.com/custom-builds), [performance](http://lodash.com/benchmarks), & [extras](http://lodash.com/#features).
+
+## Download
+
+Check out our [wiki]([https://github.com/lodash/lodash/wiki/build-differences]) for details over the differences between builds.
+
+* Modern builds perfect for newer browsers/environments:<br>
+[Development](https://raw.github.com/lodash/lodash/2.4.1/dist/lodash.js) &
+[Production](https://raw.github.com/lodash/lodash/2.4.1/dist/lodash.min.js)
+
+* Compatibility builds for older environment support too:<br>
+[Development](https://raw.github.com/lodash/lodash/2.4.1/dist/lodash.compat.js) &
+[Production](https://raw.github.com/lodash/lodash/2.4.1/dist/lodash.compat.min.js)
+
+* Underscore builds to use as a drop-in replacement:<br>
+[Development](https://raw.github.com/lodash/lodash/2.4.1/dist/lodash.underscore.js) &
+[Production](https://raw.github.com/lodash/lodash/2.4.1/dist/lodash.underscore.min.js)
+
+CDN copies are available on [cdnjs](http://cdnjs.com/libraries/lodash.js/) & [jsDelivr](http://www.jsdelivr.com/#!lodash). For smaller file sizes, create [custom builds](http://lodash.com/custom-builds) with only the features needed.
+
+Love modules? We’ve got you covered with [lodash-amd](https://npmjs.org/package/lodash-amd), [lodash-es6](https://github.com/lodash/lodash-es6), [lodash-node](https://npmjs.org/package/lodash-node), & [npm packages](https://npmjs.org/browse/keyword/lodash-modularized) per method.
+
+## Dive in
+
+There’s plenty of **[documentation](http://lodash.com/docs)**, [unit tests](http://lodash.com/tests), & [benchmarks](http://lodash.com/benchmarks).<br>
+Check out <a href="http://devdocs.io/lodash/">DevDocs</a> as a fast, organized, & searchable interface for our documentation.
+
+The full changelog for this release is available on our [wiki](https://github.com/lodash/lodash/wiki/Changelog).<br>
+A list of upcoming features is available on our [roadmap](https://github.com/lodash/lodash/wiki/Roadmap).
+
+## Features *not* in Underscore
+
+ * AMD loader support ([curl](https://github.com/cujojs/curl), [dojo](http://dojotoolkit.org/), [requirejs](http://requirejs.org/), etc.)
+ * [_(…)](http://lodash.com/docs#_) supports intuitive chaining
+ * [_.at](http://lodash.com/docs#at) for cherry-picking collection values
+ * [_.bindKey](http://lodash.com/docs#bindKey) for binding [*“lazy”*](http://michaux.ca/articles/lazy-function-definition-pattern) defined methods
+ * [_.clone](http://lodash.com/docs#clone) supports shallow cloning of `Date` & `RegExp` objects
+ * [_.cloneDeep](http://lodash.com/docs#cloneDeep) for deep cloning arrays & objects
+ * [_.constant](http://lodash.com/docs#constant) & [_.property](http://lodash.com/docs#property) function generators for composing functions
+ * [_.contains](http://lodash.com/docs#contains) accepts a `fromIndex`
+ * [_.create](http://lodash.com/docs#create) for easier object inheritance
+ * [_.createCallback](http://lodash.com/docs#createCallback) for extending callbacks in methods & mixins
+ * [_.curry](http://lodash.com/docs#curry) for creating [curried](http://hughfdjackson.com/javascript/2013/07/06/why-curry-helps/) functions
+ * [_.debounce](http://lodash.com/docs#debounce) & [_.throttle](http://lodash.com/docs#throttle) accept additional `options` for more control
+ * [_.findIndex](http://lodash.com/docs#findIndex) & [_.findKey](http://lodash.com/docs#findKey) for finding indexes & keys
+ * [_.forEach](http://lodash.com/docs#forEach) is chainable & supports exiting early
+ * [_.forIn](http://lodash.com/docs#forIn) for iterating own & inherited properties
+ * [_.forOwn](http://lodash.com/docs#forOwn) for iterating own properties
+ * [_.isPlainObject](http://lodash.com/docs#isPlainObject) for checking if values are created by `Object`
+ * [_.mapValues](http://lodash.com/docs#mapValues) for [mapping](http://lodash.com/docs#map) values to an object
+ * [_.memoize](http://lodash.com/docs#memoize) exposes the `cache` of memoized functions
+ * [_.merge](http://lodash.com/docs#merge) for a deep [_.extend](http://lodash.com/docs#extend)
+ * [_.noop](http://lodash.com/docs#noop) for function placeholders
+ * [_.now](http://lodash.com/docs#now) as a cross-browser `Date.now` alternative
+ * [_.parseInt](http://lodash.com/docs#parseInt) for consistent behavior
+ * [_.pull](http://lodash.com/docs#pull) & [_.remove](http://lodash.com/docs#remove) for mutating arrays
+ * [_.random](http://lodash.com/docs#random) supports returning floating-point numbers
+ * [_.runInContext](http://lodash.com/docs#runInContext) for easier mocking
+ * [_.sortBy](http://lodash.com/docs#sortBy) supports sorting by multiple properties
+ * [_.support](http://lodash.com/docs#support) for flagging environment features
+ * [_.template](http://lodash.com/docs#template) supports [*“imports”*](http://lodash.com/docs#templateSettings_imports) options & [ES6 template delimiters](http://people.mozilla.org/~jorendorff/es6-draft.html#sec-literals-string-literals)
+ * [_.transform](http://lodash.com/docs#transform) as a powerful alternative to [_.reduce](http://lodash.com/docs#reduce) for transforming objects
+ * [_.where](http://lodash.com/docs#where) supports deep object comparisons
+ * [_.xor](http://lodash.com/docs#xor) as a companion to [_.difference](http://lodash.com/docs#difference), [_.intersection](http://lodash.com/docs#intersection), & [_.union](http://lodash.com/docs#union)
+ * [_.zip](http://lodash.com/docs#zip) is capable of unzipping values
+ * [_.omit](http://lodash.com/docs#omit), [_.pick](http://lodash.com/docs#pick), &
+   [more](http://lodash.com/docs "_.assign, _.clone, _.cloneDeep, _.first, _.initial, _.isEqual, _.last, _.merge, _.rest") accept callbacks
+ * [_.contains](http://lodash.com/docs#contains), [_.toArray](http://lodash.com/docs#toArray), &
+   [more](http://lodash.com/docs "_.at, _.countBy, _.every, _.filter, _.find, _.forEach, _.forEachRight, _.groupBy, _.invoke, _.map, _.max, _.min, _.pluck, _.reduce, _.reduceRight, _.reject, _.shuffle, _.size, _.some, _.sortBy, _.where") accept strings
+ * [_.filter](http://lodash.com/docs#filter), [_.map](http://lodash.com/docs#map), &
+   [more](http://lodash.com/docs "_.countBy, _.every, _.find, _.findKey, _.findLast, _.findLastIndex, _.findLastKey, _.first, _.groupBy, _.initial, _.last, _.max, _.min, _.reject, _.rest, _.some, _.sortBy, _.sortedIndex, _.uniq") support *“_.pluck”* & *“_.where”* shorthands
+ * [_.findLast](http://lodash.com/docs#findLast), [_.findLastIndex](http://lodash.com/docs#findLastIndex), &
+   [more](http://lodash.com/docs "_.findLastKey, _.forEachRight, _.forInRight, _.forOwnRight, _.partialRight") right-associative methods
+
+## Resources
+
+ * Podcasts
+  - [JavaScript Jabber](http://javascriptjabber.com/079-jsj-lo-dash-with-john-david-dalton/)
+
+ * Posts
+  - [Say “Hello” to Lo-Dash](http://kitcambridge.be/blog/say-hello-to-lo-dash/)
+  - [Custom builds in Lo-Dash 2.0](http://kitcambridge.be/blog/custom-builds-in-lo-dash-2-dot-0/)
+
+ * Videos
+  - [Introduction](https://vimeo.com/44154599)
+  - [Origins](https://vimeo.com/44154600)
+  - [Optimizations & builds](https://vimeo.com/44154601)
+  - [Native method use](https://vimeo.com/48576012)
+  - [Testing](https://vimeo.com/45865290)
+  - [CascadiaJS ’12](http://www.youtube.com/watch?v=dpPy4f_SeEk)
+
+ A list of other community created podcasts, posts, & videos is available on our [wiki](https://github.com/lodash/lodash/wiki/Resources).
+
+## Support
+
+Tested in Chrome 5~31, Firefox 2~25, IE 6-11, Opera 9.25~17, Safari 3-7, Node.js 0.6.21~0.10.22, Narwhal 0.3.2, PhantomJS 1.9.2, RingoJS 0.9, & Rhino 1.7RC5.<br>
+Automated browser test results [are available](https://saucelabs.com/u/lodash) as well as [Travis CI](https://travis-ci.org/) builds for [lodash](https://travis-ci.org/lodash/lodash/), [lodash-cli](https://travis-ci.org/lodash/lodash-cli/), [lodash-amd](https://travis-ci.org/lodash/lodash-amd/), [lodash-node](https://travis-ci.org/lodash/lodash-node/), & [grunt-lodash](https://travis-ci.org/lodash/grunt-lodash).
+
+Special thanks to [Sauce Labs](https://saucelabs.com/) for providing automated browser testing.<br>
+[![Sauce Labs](http://lodash.com/_img/sauce.png)](https://saucelabs.com/ "Sauce Labs: Selenium Testing & More")
+
+## Installation & usage
+
+In browsers:
+
+```html
+<script src="lodash.js"></script>
+```
+
+Using [`npm`](http://npmjs.org/):
+
+```bash
+npm i --save lodash
+
+{sudo} npm i -g lodash
+npm ln lodash
+```
+
+In [Node.js](http://nodejs.org/) & [Ringo](http://ringojs.org/):
+
+```js
+var _ = require('lodash');
+// or as Underscore
+var _ = require('lodash/dist/lodash.underscore');
+```
+
+**Notes:**
+ * Don’t assign values to [special variable](http://nodejs.org/api/repl.html#repl_repl_features) `_` when in the REPL
+ * If Lo-Dash is installed globally, run [`npm ln lodash`](http://blog.nodejs.org/2011/03/23/npm-1-0-global-vs-local-installation/) in your project’s root directory *before* requiring it
+
+In [Rhino](http://www.mozilla.org/rhino/):
+
+```js
+load('lodash.js');
+```
+
+In an AMD loader:
+
+```js
+require({
+  'packages': [
+    { 'name': 'lodash', 'location': 'path/to/lodash', 'main': 'lodash' }
+  ]
+},
+['lodash'], function(_) {
+  console.log(_.VERSION);
+});
+```
+
+## Author
+
+| [![twitter/jdalton](http://gravatar.com/avatar/299a3d891ff1920b69c364d061007043?s=70)](https://twitter.com/jdalton "Follow @jdalton on Twitter") |
+|---|
+| [John-David Dalton](http://allyoucanleet.com/) |
+
+## Contributors
+
+| [![twitter/blainebublitz](http://gravatar.com/avatar/ac1c67fd906c9fecd823ce302283b4c1?s=70)](https://twitter.com/blainebublitz "Follow @BlaineBublitz on Twitter") | [![twitter/kitcambridge](http://gravatar.com/avatar/6662a1d02f351b5ef2f8b4d815804661?s=70)](https://twitter.com/kitcambridge "Follow @kitcambridge on Twitter") | [![twitter/mathias](http://gravatar.com/avatar/24e08a9ea84deb17ae121074d0f17125?s=70)](https://twitter.com/mathias "Follow @mathias on Twitter") |
+|---|---|---|
+| [Blaine Bublitz](http://www.iceddev.com/) | [Kit Cambridge](http://kitcambridge.be/) | [Mathias Bynens](http://mathiasbynens.be/) |
+
+[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/lodash/lodash/trend.png)](https://bitdeli.com/free "Bitdeli Badge")
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lodash/dist/lodash.compat.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lodash/dist/lodash.compat.js
new file mode 100644
index 0000000..23798ba
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lodash/dist/lodash.compat.js
@@ -0,0 +1,7157 @@
+/**
+ * @license
+ * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
+ * Build: `lodash -o ./dist/lodash.compat.js`
+ * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
+ * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
+ * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Available under MIT license <http://lodash.com/license>
+ */
+;(function() {
+
+  /** Used as a safe reference for `undefined` in pre ES5 environments */
+  var undefined;
+
+  /** Used to pool arrays and objects used internally */
+  var arrayPool = [],
+      objectPool = [];
+
+  /** Used to generate unique IDs */
+  var idCounter = 0;
+
+  /** Used internally to indicate various things */
+  var indicatorObject = {};
+
+  /** Used to prefix keys to avoid issues with `__proto__` and properties on `Object.prototype` */
+  var keyPrefix = +new Date + '';
+
+  /** Used as the size when optimizations are enabled for large arrays */
+  var largeArraySize = 75;
+
+  /** Used as the max size of the `arrayPool` and `objectPool` */
+  var maxPoolSize = 40;
+
+  /** Used to detect and test whitespace */
+  var whitespace = (
+    // whitespace
+    ' \t\x0B\f\xA0\ufeff' +
+
+    // line terminators
+    '\n\r\u2028\u2029' +
+
+    // unicode category "Zs" space separators
+    '\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000'
+  );
+
+  /** Used to match empty string literals in compiled template source */
+  var reEmptyStringLeading = /\b__p \+= '';/g,
+      reEmptyStringMiddle = /\b(__p \+=) '' \+/g,
+      reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g;
+
+  /**
+   * Used to match ES6 template delimiters
+   * http://people.mozilla.org/~jorendorff/es6-draft.html#sec-literals-string-literals
+   */
+  var reEsTemplate = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g;
+
+  /** Used to match regexp flags from their coerced string values */
+  var reFlags = /\w*$/;
+
+  /** Used to detected named functions */
+  var reFuncName = /^\s*function[ \n\r\t]+\w/;
+
+  /** Used to match "interpolate" template delimiters */
+  var reInterpolate = /<%=([\s\S]+?)%>/g;
+
+  /** Used to match leading whitespace and zeros to be removed */
+  var reLeadingSpacesAndZeros = RegExp('^[' + whitespace + ']*0+(?=.$)');
+
+  /** Used to ensure capturing order of template delimiters */
+  var reNoMatch = /($^)/;
+
+  /** Used to detect functions containing a `this` reference */
+  var reThis = /\bthis\b/;
+
+  /** Used to match unescaped characters in compiled string literals */
+  var reUnescapedString = /['\n\r\t\u2028\u2029\\]/g;
+
+  /** Used to assign default `context` object properties */
+  var contextProps = [
+    'Array', 'Boolean', 'Date', 'Error', 'Function', 'Math', 'Number', 'Object',
+    'RegExp', 'String', '_', 'attachEvent', 'clearTimeout', 'isFinite', 'isNaN',
+    'parseInt', 'setTimeout'
+  ];
+
+  /** Used to fix the JScript [[DontEnum]] bug */
+  var shadowedProps = [
+    'constructor', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable',
+    'toLocaleString', 'toString', 'valueOf'
+  ];
+
+  /** Used to make template sourceURLs easier to identify */
+  var templateCounter = 0;
+
+  /** `Object#toString` result shortcuts */
+  var argsClass = '[object Arguments]',
+      arrayClass = '[object Array]',
+      boolClass = '[object Boolean]',
+      dateClass = '[object Date]',
+      errorClass = '[object Error]',
+      funcClass = '[object Function]',
+      numberClass = '[object Number]',
+      objectClass = '[object Object]',
+      regexpClass = '[object RegExp]',
+      stringClass = '[object String]';
+
+  /** Used to identify object classifications that `_.clone` supports */
+  var cloneableClasses = {};
+  cloneableClasses[funcClass] = false;
+  cloneableClasses[argsClass] = cloneableClasses[arrayClass] =
+  cloneableClasses[boolClass] = cloneableClasses[dateClass] =
+  cloneableClasses[numberClass] = cloneableClasses[objectClass] =
+  cloneableClasses[regexpClass] = cloneableClasses[stringClass] = true;
+
+  /** Used as an internal `_.debounce` options object */
+  var debounceOptions = {
+    'leading': false,
+    'maxWait': 0,
+    'trailing': false
+  };
+
+  /** Used as the property descriptor for `__bindData__` */
+  var descriptor = {
+    'configurable': false,
+    'enumerable': false,
+    'value': null,
+    'writable': false
+  };
+
+  /** Used as the data object for `iteratorTemplate` */
+  var iteratorData = {
+    'args': '',
+    'array': null,
+    'bottom': '',
+    'firstArg': '',
+    'init': '',
+    'keys': null,
+    'loop': '',
+    'shadowedProps': null,
+    'support': null,
+    'top': '',
+    'useHas': false
+  };
+
+  /** Used to determine if values are of the language type Object */
+  var objectTypes = {
+    'boolean': false,
+    'function': true,
+    'object': true,
+    'number': false,
+    'string': false,
+    'undefined': false
+  };
+
+  /** Used to escape characters for inclusion in compiled string literals */
+  var stringEscapes = {
+    '\\': '\\',
+    "'": "'",
+    '\n': 'n',
+    '\r': 'r',
+    '\t': 't',
+    '\u2028': 'u2028',
+    '\u2029': 'u2029'
+  };
+
+  /** Used as a reference to the global object */
+  var root = (objectTypes[typeof window] && window) || this;
+
+  /** Detect free variable `exports` */
+  var freeExports = objectTypes[typeof exports] && exports && !exports.nodeType && exports;
+
+  /** Detect free variable `module` */
+  var freeModule = objectTypes[typeof module] && module && !module.nodeType && module;
+
+  /** Detect the popular CommonJS extension `module.exports` */
+  var moduleExports = freeModule && freeModule.exports === freeExports && freeExports;
+
+  /** Detect free variable `global` from Node.js or Browserified code and use it as `root` */
+  var freeGlobal = objectTypes[typeof global] && global;
+  if (freeGlobal && (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal)) {
+    root = freeGlobal;
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * The base implementation of `_.indexOf` without support for binary searches
+   * or `fromIndex` constraints.
+   *
+   * @private
+   * @param {Array} array The array to search.
+   * @param {*} value The value to search for.
+   * @param {number} [fromIndex=0] The index to search from.
+   * @returns {number} Returns the index of the matched value or `-1`.
+   */
+  function baseIndexOf(array, value, fromIndex) {
+    var index = (fromIndex || 0) - 1,
+        length = array ? array.length : 0;
+
+    while (++index < length) {
+      if (array[index] === value) {
+        return index;
+      }
+    }
+    return -1;
+  }
+
+  /**
+   * An implementation of `_.contains` for cache objects that mimics the return
+   * signature of `_.indexOf` by returning `0` if the value is found, else `-1`.
+   *
+   * @private
+   * @param {Object} cache The cache object to inspect.
+   * @param {*} value The value to search for.
+   * @returns {number} Returns `0` if `value` is found, else `-1`.
+   */
+  function cacheIndexOf(cache, value) {
+    var type = typeof value;
+    cache = cache.cache;
+
+    if (type == 'boolean' || value == null) {
+      return cache[value] ? 0 : -1;
+    }
+    if (type != 'number' && type != 'string') {
+      type = 'object';
+    }
+    var key = type == 'number' ? value : keyPrefix + value;
+    cache = (cache = cache[type]) && cache[key];
+
+    return type == 'object'
+      ? (cache && baseIndexOf(cache, value) > -1 ? 0 : -1)
+      : (cache ? 0 : -1);
+  }
+
+  /**
+   * Adds a given value to the corresponding cache object.
+   *
+   * @private
+   * @param {*} value The value to add to the cache.
+   */
+  function cachePush(value) {
+    var cache = this.cache,
+        type = typeof value;
+
+    if (type == 'boolean' || value == null) {
+      cache[value] = true;
+    } else {
+      if (type != 'number' && type != 'string') {
+        type = 'object';
+      }
+      var key = type == 'number' ? value : keyPrefix + value,
+          typeCache = cache[type] || (cache[type] = {});
+
+      if (type == 'object') {
+        (typeCache[key] || (typeCache[key] = [])).push(value);
+      } else {
+        typeCache[key] = true;
+      }
+    }
+  }
+
+  /**
+   * Used by `_.max` and `_.min` as the default callback when a given
+   * collection is a string value.
+   *
+   * @private
+   * @param {string} value The character to inspect.
+   * @returns {number} Returns the code unit of given character.
+   */
+  function charAtCallback(value) {
+    return value.charCodeAt(0);
+  }
+
+  /**
+   * Used by `sortBy` to compare transformed `collection` elements, stable sorting
+   * them in ascending order.
+   *
+   * @private
+   * @param {Object} a The object to compare to `b`.
+   * @param {Object} b The object to compare to `a`.
+   * @returns {number} Returns the sort order indicator of `1` or `-1`.
+   */
+  function compareAscending(a, b) {
+    var ac = a.criteria,
+        bc = b.criteria,
+        index = -1,
+        length = ac.length;
+
+    while (++index < length) {
+      var value = ac[index],
+          other = bc[index];
+
+      if (value !== other) {
+        if (value > other || typeof value == 'undefined') {
+          return 1;
+        }
+        if (value < other || typeof other == 'undefined') {
+          return -1;
+        }
+      }
+    }
+    // Fixes an `Array#sort` bug in the JS engine embedded in Adobe applications
+    // that causes it, under certain circumstances, to return the same value for
+    // `a` and `b`. See https://github.com/jashkenas/underscore/pull/1247
+    //
+    // This also ensures a stable sort in V8 and other engines.
+    // See http://code.google.com/p/v8/issues/detail?id=90
+    return a.index - b.index;
+  }
+
+  /**
+   * Creates a cache object to optimize linear searches of large arrays.
+   *
+   * @private
+   * @param {Array} [array=[]] The array to search.
+   * @returns {null|Object} Returns the cache object or `null` if caching should not be used.
+   */
+  function createCache(array) {
+    var index = -1,
+        length = array.length,
+        first = array[0],
+        mid = array[(length / 2) | 0],
+        last = array[length - 1];
+
+    if (first && typeof first == 'object' &&
+        mid && typeof mid == 'object' && last && typeof last == 'object') {
+      return false;
+    }
+    var cache = getObject();
+    cache['false'] = cache['null'] = cache['true'] = cache['undefined'] = false;
+
+    var result = getObject();
+    result.array = array;
+    result.cache = cache;
+    result.push = cachePush;
+
+    while (++index < length) {
+      result.push(array[index]);
+    }
+    return result;
+  }
+
+  /**
+   * Used by `template` to escape characters for inclusion in compiled
+   * string literals.
+   *
+   * @private
+   * @param {string} match The matched character to escape.
+   * @returns {string} Returns the escaped character.
+   */
+  function escapeStringChar(match) {
+    return '\\' + stringEscapes[match];
+  }
+
+  /**
+   * Gets an array from the array pool or creates a new one if the pool is empty.
+   *
+   * @private
+   * @returns {Array} The array from the pool.
+   */
+  function getArray() {
+    return arrayPool.pop() || [];
+  }
+
+  /**
+   * Gets an object from the object pool or creates a new one if the pool is empty.
+   *
+   * @private
+   * @returns {Object} The object from the pool.
+   */
+  function getObject() {
+    return objectPool.pop() || {
+      'array': null,
+      'cache': null,
+      'criteria': null,
+      'false': false,
+      'index': 0,
+      'null': false,
+      'number': null,
+      'object': null,
+      'push': null,
+      'string': null,
+      'true': false,
+      'undefined': false,
+      'value': null
+    };
+  }
+
+  /**
+   * Checks if `value` is a DOM node in IE < 9.
+   *
+   * @private
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if the `value` is a DOM node, else `false`.
+   */
+  function isNode(value) {
+    // IE < 9 presents DOM nodes as `Object` objects except they have `toString`
+    // methods that are `typeof` "string" and still can coerce nodes to strings
+    return typeof value.toString != 'function' && typeof (value + '') == 'string';
+  }
+
+  /**
+   * Releases the given array back to the array pool.
+   *
+   * @private
+   * @param {Array} [array] The array to release.
+   */
+  function releaseArray(array) {
+    array.length = 0;
+    if (arrayPool.length < maxPoolSize) {
+      arrayPool.push(array);
+    }
+  }
+
+  /**
+   * Releases the given object back to the object pool.
+   *
+   * @private
+   * @param {Object} [object] The object to release.
+   */
+  function releaseObject(object) {
+    var cache = object.cache;
+    if (cache) {
+      releaseObject(cache);
+    }
+    object.array = object.cache = object.criteria = object.object = object.number = object.string = object.value = null;
+    if (objectPool.length < maxPoolSize) {
+      objectPool.push(object);
+    }
+  }
+
+  /**
+   * Slices the `collection` from the `start` index up to, but not including,
+   * the `end` index.
+   *
+   * Note: This function is used instead of `Array#slice` to support node lists
+   * in IE < 9 and to ensure dense arrays are returned.
+   *
+   * @private
+   * @param {Array|Object|string} collection The collection to slice.
+   * @param {number} start The start index.
+   * @param {number} end The end index.
+   * @returns {Array} Returns the new array.
+   */
+  function slice(array, start, end) {
+    start || (start = 0);
+    if (typeof end == 'undefined') {
+      end = array ? array.length : 0;
+    }
+    var index = -1,
+        length = end - start || 0,
+        result = Array(length < 0 ? 0 : length);
+
+    while (++index < length) {
+      result[index] = array[start + index];
+    }
+    return result;
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Create a new `lodash` function using the given context object.
+   *
+   * @static
+   * @memberOf _
+   * @category Utilities
+   * @param {Object} [context=root] The context object.
+   * @returns {Function} Returns the `lodash` function.
+   */
+  function runInContext(context) {
+    // Avoid issues with some ES3 environments that attempt to use values, named
+    // after built-in constructors like `Object`, for the creation of literals.
+    // ES5 clears this up by stating that literals must use built-in constructors.
+    // See http://es5.github.io/#x11.1.5.
+    context = context ? _.defaults(root.Object(), context, _.pick(root, contextProps)) : root;
+
+    /** Native constructor references */
+    var Array = context.Array,
+        Boolean = context.Boolean,
+        Date = context.Date,
+        Error = context.Error,
+        Function = context.Function,
+        Math = context.Math,
+        Number = context.Number,
+        Object = context.Object,
+        RegExp = context.RegExp,
+        String = context.String,
+        TypeError = context.TypeError;
+
+    /**
+     * Used for `Array` method references.
+     *
+     * Normally `Array.prototype` would suffice, however, using an array literal
+     * avoids issues in Narwhal.
+     */
+    var arrayRef = [];
+
+    /** Used for native method references */
+    var errorProto = Error.prototype,
+        objectProto = Object.prototype,
+        stringProto = String.prototype;
+
+    /** Used to restore the original `_` reference in `noConflict` */
+    var oldDash = context._;
+
+    /** Used to resolve the internal [[Class]] of values */
+    var toString = objectProto.toString;
+
+    /** Used to detect if a method is native */
+    var reNative = RegExp('^' +
+      String(toString)
+        .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
+        .replace(/toString| for [^\]]+/g, '.*?') + '$'
+    );
+
+    /** Native method shortcuts */
+    var ceil = Math.ceil,
+        clearTimeout = context.clearTimeout,
+        floor = Math.floor,
+        fnToString = Function.prototype.toString,
+        getPrototypeOf = isNative(getPrototypeOf = Object.getPrototypeOf) && getPrototypeOf,
+        hasOwnProperty = objectProto.hasOwnProperty,
+        push = arrayRef.push,
+        propertyIsEnumerable = objectProto.propertyIsEnumerable,
+        setTimeout = context.setTimeout,
+        splice = arrayRef.splice,
+        unshift = arrayRef.unshift;
+
+    /** Used to set meta data on functions */
+    var defineProperty = (function() {
+      // IE 8 only accepts DOM elements
+      try {
+        var o = {},
+            func = isNative(func = Object.defineProperty) && func,
+            result = func(o, o, o) && func;
+      } catch(e) { }
+      return result;
+    }());
+
+    /* Native method shortcuts for methods with the same name as other `lodash` methods */
+    var nativeCreate = isNative(nativeCreate = Object.create) && nativeCreate,
+        nativeIsArray = isNative(nativeIsArray = Array.isArray) && nativeIsArray,
+        nativeIsFinite = context.isFinite,
+        nativeIsNaN = context.isNaN,
+        nativeKeys = isNative(nativeKeys = Object.keys) && nativeKeys,
+        nativeMax = Math.max,
+        nativeMin = Math.min,
+        nativeParseInt = context.parseInt,
+        nativeRandom = Math.random;
+
+    /** Used to lookup a built-in constructor by [[Class]] */
+    var ctorByClass = {};
+    ctorByClass[arrayClass] = Array;
+    ctorByClass[boolClass] = Boolean;
+    ctorByClass[dateClass] = Date;
+    ctorByClass[funcClass] = Function;
+    ctorByClass[objectClass] = Object;
+    ctorByClass[numberClass] = Number;
+    ctorByClass[regexpClass] = RegExp;
+    ctorByClass[stringClass] = String;
+
+    /** Used to avoid iterating non-enumerable properties in IE < 9 */
+    var nonEnumProps = {};
+    nonEnumProps[arrayClass] = nonEnumProps[dateClass] = nonEnumProps[numberClass] = { 'constructor': true, 'toLocaleString': true, 'toString': true, 'valueOf': true };
+    nonEnumProps[boolClass] = nonEnumProps[stringClass] = { 'constructor': true, 'toString': true, 'valueOf': true };
+    nonEnumProps[errorClass] = nonEnumProps[funcClass] = nonEnumProps[regexpClass] = { 'constructor': true, 'toString': true };
+    nonEnumProps[objectClass] = { 'constructor': true };
+
+    (function() {
+      var length = shadowedProps.length;
+      while (length--) {
+        var key = shadowedProps[length];
+        for (var className in nonEnumProps) {
+          if (hasOwnProperty.call(nonEnumProps, className) && !hasOwnProperty.call(nonEnumProps[className], key)) {
+            nonEnumProps[className][key] = false;
+          }
+        }
+      }
+    }());
+
+    /*--------------------------------------------------------------------------*/
+
+    /**
+     * Creates a `lodash` object which wraps the given value to enable intuitive
+     * method chaining.
+     *
+     * In addition to Lo-Dash methods, wrappers also have the following `Array` methods:
+     * `concat`, `join`, `pop`, `push`, `reverse`, `shift`, `slice`, `sort`, `splice`,
+     * and `unshift`
+     *
+     * Chaining is supported in custom builds as long as the `value` method is
+     * implicitly or explicitly included in the build.
+     *
+     * The chainable wrapper functions are:
+     * `after`, `assign`, `bind`, `bindAll`, `bindKey`, `chain`, `compact`,
+     * `compose`, `concat`, `countBy`, `create`, `createCallback`, `curry`,
+     * `debounce`, `defaults`, `defer`, `delay`, `difference`, `filter`, `flatten`,
+     * `forEach`, `forEachRight`, `forIn`, `forInRight`, `forOwn`, `forOwnRight`,
+     * `functions`, `groupBy`, `indexBy`, `initial`, `intersection`, `invert`,
+     * `invoke`, `keys`, `map`, `max`, `memoize`, `merge`, `min`, `object`, `omit`,
+     * `once`, `pairs`, `partial`, `partialRight`, `pick`, `pluck`, `pull`, `push`,
+     * `range`, `reject`, `remove`, `rest`, `reverse`, `shuffle`, `slice`, `sort`,
+     * `sortBy`, `splice`, `tap`, `throttle`, `times`, `toArray`, `transform`,
+     * `union`, `uniq`, `unshift`, `unzip`, `values`, `where`, `without`, `wrap`,
+     * and `zip`
+     *
+     * The non-chainable wrapper functions are:
+     * `clone`, `cloneDeep`, `contains`, `escape`, `every`, `find`, `findIndex`,
+     * `findKey`, `findLast`, `findLastIndex`, `findLastKey`, `has`, `identity`,
+     * `indexOf`, `isArguments`, `isArray`, `isBoolean`, `isDate`, `isElement`,
+     * `isEmpty`, `isEqual`, `isFinite`, `isFunction`, `isNaN`, `isNull`, `isNumber`,
+     * `isObject`, `isPlainObject`, `isRegExp`, `isString`, `isUndefined`, `join`,
+     * `lastIndexOf`, `mixin`, `noConflict`, `parseInt`, `pop`, `random`, `reduce`,
+     * `reduceRight`, `result`, `shift`, `size`, `some`, `sortedIndex`, `runInContext`,
+     * `template`, `unescape`, `uniqueId`, and `value`
+     *
+     * The wrapper functions `first` and `last` return wrapped values when `n` is
+     * provided, otherwise they return unwrapped values.
+     *
+     * Explicit chaining can be enabled by using the `_.chain` method.
+     *
+     * @name _
+     * @constructor
+     * @category Chaining
+     * @param {*} value The value to wrap in a `lodash` instance.
+     * @returns {Object} Returns a `lodash` instance.
+     * @example
+     *
+     * var wrapped = _([1, 2, 3]);
+     *
+     * // returns an unwrapped value
+     * wrapped.reduce(function(sum, num) {
+     *   return sum + num;
+     * });
+     * // => 6
+     *
+     * // returns a wrapped value
+     * var squares = wrapped.map(function(num) {
+     *   return num * num;
+     * });
+     *
+     * _.isArray(squares);
+     * // => false
+     *
+     * _.isArray(squares.value());
+     * // => true
+     */
+    function lodash(value) {
+      // don't wrap if already wrapped, even if wrapped by a different `lodash` constructor
+      return (value && typeof value == 'object' && !isArray(value) && hasOwnProperty.call(value, '__wrapped__'))
+       ? value
+       : new lodashWrapper(value);
+    }
+
+    /**
+     * A fast path for creating `lodash` wrapper objects.
+     *
+     * @private
+     * @param {*} value The value to wrap in a `lodash` instance.
+     * @param {boolean} chainAll A flag to enable chaining for all methods
+     * @returns {Object} Returns a `lodash` instance.
+     */
+    function lodashWrapper(value, chainAll) {
+      this.__chain__ = !!chainAll;
+      this.__wrapped__ = value;
+    }
+    // ensure `new lodashWrapper` is an instance of `lodash`
+    lodashWrapper.prototype = lodash.prototype;
+
+    /**
+     * An object used to flag environments features.
+     *
+     * @static
+     * @memberOf _
+     * @type Object
+     */
+    var support = lodash.support = {};
+
+    (function() {
+      var ctor = function() { this.x = 1; },
+          object = { '0': 1, 'length': 1 },
+          props = [];
+
+      ctor.prototype = { 'valueOf': 1, 'y': 1 };
+      for (var key in new ctor) { props.push(key); }
+      for (key in arguments) { }
+
+      /**
+       * Detect if an `arguments` object's [[Class]] is resolvable (all but Firefox < 4, IE < 9).
+       *
+       * @memberOf _.support
+       * @type boolean
+       */
+      support.argsClass = toString.call(arguments) == argsClass;
+
+      /**
+       * Detect if `arguments` objects are `Object` objects (all but Narwhal and Opera < 10.5).
+       *
+       * @memberOf _.support
+       * @type boolean
+       */
+      support.argsObject = arguments.constructor == Object && !(arguments instanceof Array);
+
+      /**
+       * Detect if `name` or `message` properties of `Error.prototype` are
+       * enumerable by default. (IE < 9, Safari < 5.1)
+       *
+       * @memberOf _.support
+       * @type boolean
+       */
+      support.enumErrorProps = propertyIsEnumerable.call(errorProto, 'message') || propertyIsEnumerable.call(errorProto, 'name');
+
+      /**
+       * Detect if `prototype` properties are enumerable by default.
+       *
+       * Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1
+       * (if the prototype or a property on the prototype has been set)
+       * incorrectly sets a function's `prototype` property [[Enumerable]]
+       * value to `true`.
+       *
+       * @memberOf _.support
+       * @type boolean
+       */
+      support.enumPrototypes = propertyIsEnumerable.call(ctor, 'prototype');
+
+      /**
+       * Detect if functions can be decompiled by `Function#toString`
+       * (all but PS3 and older Opera mobile browsers & avoided in Windows 8 apps).
+       *
+       * @memberOf _.support
+       * @type boolean
+       */
+      support.funcDecomp = !isNative(context.WinRTError) && reThis.test(runInContext);
+
+      /**
+       * Detect if `Function#name` is supported (all but IE).
+       *
+       * @memberOf _.support
+       * @type boolean
+       */
+      support.funcNames = typeof Function.name == 'string';
+
+      /**
+       * Detect if `arguments` object indexes are non-enumerable
+       * (Firefox < 4, IE < 9, PhantomJS, Safari < 5.1).
+       *
+       * @memberOf _.support
+       * @type boolean
+       */
+      support.nonEnumArgs = key != 0;
+
+      /**
+       * Detect if properties shadowing those on `Object.prototype` are non-enumerable.
+       *
+       * In IE < 9 an objects own properties, shadowing non-enumerable ones, are
+       * made non-enumerable as well (a.k.a the JScript [[DontEnum]] bug).
+       *
+       * @memberOf _.support
+       * @type boolean
+       */
+      support.nonEnumShadows = !/valueOf/.test(props);
+
+      /**
+       * Detect if own properties are iterated after inherited properties (all but IE < 9).
+       *
+       * @memberOf _.support
+       * @type boolean
+       */
+      support.ownLast = props[0] != 'x';
+
+      /**
+       * Detect if `Array#shift` and `Array#splice` augment array-like objects correctly.
+       *
+       * Firefox < 10, IE compatibility mode, and IE < 9 have buggy Array `shift()`
+       * and `splice()` functions that fail to remove the last element, `value[0]`,
+       * of array-like objects even though the `length` property is set to `0`.
+       * The `shift()` method is buggy in IE 8 compatibility mode, while `splice()`
+       * is buggy regardless of mode in IE < 9 and buggy in compatibility mode in IE 9.
+       *
+       * @memberOf _.support
+       * @type boolean
+       */
+      support.spliceObjects = (arrayRef.splice.call(object, 0, 1), !object[0]);
+
+      /**
+       * Detect lack of support for accessing string characters by index.
+       *
+       * IE < 8 can't access characters by index and IE 8 can only access
+       * characters by index on string literals.
+       *
+       * @memberOf _.support
+       * @type boolean
+       */
+      support.unindexedChars = ('x'[0] + Object('x')[0]) != 'xx';
+
+      /**
+       * Detect if a DOM node's [[Class]] is resolvable (all but IE < 9)
+       * and that the JS engine errors when attempting to coerce an object to
+       * a string without a `toString` function.
+       *
+       * @memberOf _.support
+       * @type boolean
+       */
+      try {
+        support.nodeClass = !(toString.call(document) == objectClass && !({ 'toString': 0 } + ''));
+      } catch(e) {
+        support.nodeClass = true;
+      }
+    }(1));
+
+    /**
+     * By default, the template delimiters used by Lo-Dash are similar to those in
+     * embedded Ruby (ERB). Change the following template settings to use alternative
+     * delimiters.
+     *
+     * @static
+     * @memberOf _
+     * @type Object
+     */
+    lodash.templateSettings = {
+
+      /**
+       * Used to detect `data` property values to be HTML-escaped.
+       *
+       * @memberOf _.templateSettings
+       * @type RegExp
+       */
+      'escape': /<%-([\s\S]+?)%>/g,
+
+      /**
+       * Used to detect code to be evaluated.
+       *
+       * @memberOf _.templateSettings
+       * @type RegExp
+       */
+      'evaluate': /<%([\s\S]+?)%>/g,
+
+      /**
+       * Used to detect `data` property values to inject.
+       *
+       * @memberOf _.templateSettings
+       * @type RegExp
+       */
+      'interpolate': reInterpolate,
+
+      /**
+       * Used to reference the data object in the template text.
+       *
+       * @memberOf _.templateSettings
+       * @type string
+       */
+      'variable': '',
+
+      /**
+       * Used to import variables into the compiled template.
+       *
+       * @memberOf _.templateSettings
+       * @type Object
+       */
+      'imports': {
+
+        /**
+         * A reference to the `lodash` function.
+         *
+         * @memberOf _.templateSettings.imports
+         * @type Function
+         */
+        '_': lodash
+      }
+    };
+
+    /*--------------------------------------------------------------------------*/
+
+    /**
+     * The template used to create iterator functions.
+     *
+     * @private
+     * @param {Object} data The data object used to populate the text.
+     * @returns {string} Returns the interpolated text.
+     */
+    var iteratorTemplate = function(obj) {
+
+      var __p = 'var index, iterable = ' +
+      (obj.firstArg) +
+      ', result = ' +
+      (obj.init) +
+      ';\nif (!iterable) return result;\n' +
+      (obj.top) +
+      ';';
+       if (obj.array) {
+      __p += '\nvar length = iterable.length; index = -1;\nif (' +
+      (obj.array) +
+      ') {  ';
+       if (support.unindexedChars) {
+      __p += '\n  if (isString(iterable)) {\n    iterable = iterable.split(\'\')\n  }  ';
+       }
+      __p += '\n  while (++index < length) {\n    ' +
+      (obj.loop) +
+      ';\n  }\n}\nelse {  ';
+       } else if (support.nonEnumArgs) {
+      __p += '\n  var length = iterable.length; index = -1;\n  if (length && isArguments(iterable)) {\n    while (++index < length) {\n      index += \'\';\n      ' +
+      (obj.loop) +
+      ';\n    }\n  } else {  ';
+       }
+
+       if (support.enumPrototypes) {
+      __p += '\n  var skipProto = typeof iterable == \'function\';\n  ';
+       }
+
+       if (support.enumErrorProps) {
+      __p += '\n  var skipErrorProps = iterable === errorProto || iterable instanceof Error;\n  ';
+       }
+
+          var conditions = [];    if (support.enumPrototypes) { conditions.push('!(skipProto && index == "prototype")'); }    if (support.enumErrorProps)  { conditions.push('!(skipErrorProps && (index == "message" || index == "name"))'); }
+
+       if (obj.useHas && obj.keys) {
+      __p += '\n  var ownIndex = -1,\n      ownProps = objectTypes[typeof iterable] && keys(iterable),\n      length = ownProps ? ownProps.length : 0;\n\n  while (++ownIndex < length) {\n    index = ownProps[ownIndex];\n';
+          if (conditions.length) {
+      __p += '    if (' +
+      (conditions.join(' && ')) +
+      ') {\n  ';
+       }
+      __p +=
+      (obj.loop) +
+      ';    ';
+       if (conditions.length) {
+      __p += '\n    }';
+       }
+      __p += '\n  }  ';
+       } else {
+      __p += '\n  for (index in iterable) {\n';
+          if (obj.useHas) { conditions.push("hasOwnProperty.call(iterable, index)"); }    if (conditions.length) {
+      __p += '    if (' +
+      (conditions.join(' && ')) +
+      ') {\n  ';
+       }
+      __p +=
+      (obj.loop) +
+      ';    ';
+       if (conditions.length) {
+      __p += '\n    }';
+       }
+      __p += '\n  }    ';
+       if (support.nonEnumShadows) {
+      __p += '\n\n  if (iterable !== objectProto) {\n    var ctor = iterable.constructor,\n        isProto = iterable === (ctor && ctor.prototype),\n        className = iterable === stringProto ? stringClass : iterable === errorProto ? errorClass : toString.call(iterable),\n        nonEnum = nonEnumProps[className];\n      ';
+       for (k = 0; k < 7; k++) {
+      __p += '\n    index = \'' +
+      (obj.shadowedProps[k]) +
+      '\';\n    if ((!(isProto && nonEnum[index]) && hasOwnProperty.call(iterable, index))';
+              if (!obj.useHas) {
+      __p += ' || (!nonEnum[index] && iterable[index] !== objectProto[index])';
+       }
+      __p += ') {\n      ' +
+      (obj.loop) +
+      ';\n    }      ';
+       }
+      __p += '\n  }    ';
+       }
+
+       }
+
+       if (obj.array || support.nonEnumArgs) {
+      __p += '\n}';
+       }
+      __p +=
+      (obj.bottom) +
+      ';\nreturn result';
+
+      return __p
+    };
+
+    /*--------------------------------------------------------------------------*/
+
+    /**
+     * The base implementation of `_.bind` that creates the bound function and
+     * sets its meta data.
+     *
+     * @private
+     * @param {Array} bindData The bind data array.
+     * @returns {Function} Returns the new bound function.
+     */
+    function baseBind(bindData) {
+      var func = bindData[0],
+          partialArgs = bindData[2],
+          thisArg = bindData[4];
+
+      function bound() {
+        // `Function#bind` spec
+        // http://es5.github.io/#x15.3.4.5
+        if (partialArgs) {
+          // avoid `arguments` object deoptimizations by using `slice` instead
+          // of `Array.prototype.slice.call` and not assigning `arguments` to a
+          // variable as a ternary expression
+          var args = slice(partialArgs);
+          push.apply(args, arguments);
+        }
+        // mimic the constructor's `return` behavior
+        // http://es5.github.io/#x13.2.2
+        if (this instanceof bound) {
+          // ensure `new bound` is an instance of `func`
+          var thisBinding = baseCreate(func.prototype),
+              result = func.apply(thisBinding, args || arguments);
+          return isObject(result) ? result : thisBinding;
+        }
+        return func.apply(thisArg, args || arguments);
+      }
+      setBindData(bound, bindData);
+      return bound;
+    }
+
+    /**
+     * The base implementation of `_.clone` without argument juggling or support
+     * for `thisArg` binding.
+     *
+     * @private
+     * @param {*} value The value to clone.
+     * @param {boolean} [isDeep=false] Specify a deep clone.
+     * @param {Function} [callback] The function to customize cloning values.
+     * @param {Array} [stackA=[]] Tracks traversed source objects.
+     * @param {Array} [stackB=[]] Associates clones with source counterparts.
+     * @returns {*} Returns the cloned value.
+     */
+    function baseClone(value, isDeep, callback, stackA, stackB) {
+      if (callback) {
+        var result = callback(value);
+        if (typeof result != 'undefined') {
+          return result;
+        }
+      }
+      // inspect [[Class]]
+      var isObj = isObject(value);
+      if (isObj) {
+        var className = toString.call(value);
+        if (!cloneableClasses[className] || (!support.nodeClass && isNode(value))) {
+          return value;
+        }
+        var ctor = ctorByClass[className];
+        switch (className) {
+          case boolClass:
+          case dateClass:
+            return new ctor(+value);
+
+          case numberClass:
+          case stringClass:
+            return new ctor(value);
+
+          case regexpClass:
+            result = ctor(value.source, reFlags.exec(value));
+            result.lastIndex = value.lastIndex;
+            return result;
+        }
+      } else {
+        return value;
+      }
+      var isArr = isArray(value);
+      if (isDeep) {
+        // check for circular references and return corresponding clone
+        var initedStack = !stackA;
+        stackA || (stackA = getArray());
+        stackB || (stackB = getArray());
+
+        var length = stackA.length;
+        while (length--) {
+          if (stackA[length] == value) {
+            return stackB[length];
+          }
+        }
+        result = isArr ? ctor(value.length) : {};
+      }
+      else {
+        result = isArr ? slice(value) : assign({}, value);
+      }
+      // add array properties assigned by `RegExp#exec`
+      if (isArr) {
+        if (hasOwnProperty.call(value, 'index')) {
+          result.index = value.index;
+        }
+        if (hasOwnProperty.call(value, 'input')) {
+          result.input = value.input;
+        }
+      }
+      // exit for shallow clone
+      if (!isDeep) {
+        return result;
+      }
+      // add the source value to the stack of traversed objects
+      // and associate it with its clone
+      stackA.push(value);
+      stackB.push(result);
+
+      // recursively populate clone (susceptible to call stack limits)
+      (isArr ? baseEach : forOwn)(value, function(objValue, key) {
+        result[key] = baseClone(objValue, isDeep, callback, stackA, stackB);
+      });
+
+      if (initedStack) {
+        releaseArray(stackA);
+        releaseArray(stackB);
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.create` without support for assigning
+     * properties to the created object.
+     *
+     * @private
+     * @param {Object} prototype The object to inherit from.
+     * @returns {Object} Returns the new object.
+     */
+    function baseCreate(prototype, properties) {
+      return isObject(prototype) ? nativeCreate(prototype) : {};
+    }
+    // fallback for browsers without `Object.create`
+    if (!nativeCreate) {
+      baseCreate = (function() {
+        function Object() {}
+        return function(prototype) {
+          if (isObject(prototype)) {
+            Object.prototype = prototype;
+            var result = new Object;
+            Object.prototype = null;
+          }
+          return result || context.Object();
+        };
+      }());
+    }
+
+    /**
+     * The base implementation of `_.createCallback` without support for creating
+     * "_.pluck" or "_.where" style callbacks.
+     *
+     * @private
+     * @param {*} [func=identity] The value to convert to a callback.
+     * @param {*} [thisArg] The `this` binding of the created callback.
+     * @param {number} [argCount] The number of arguments the callback accepts.
+     * @returns {Function} Returns a callback function.
+     */
+    function baseCreateCallback(func, thisArg, argCount) {
+      if (typeof func != 'function') {
+        return identity;
+      }
+      // exit early for no `thisArg` or already bound by `Function#bind`
+      if (typeof thisArg == 'undefined' || !('prototype' in func)) {
+        return func;
+      }
+      var bindData = func.__bindData__;
+      if (typeof bindData == 'undefined') {
+        if (support.funcNames) {
+          bindData = !func.name;
+        }
+        bindData = bindData || !support.funcDecomp;
+        if (!bindData) {
+          var source = fnToString.call(func);
+          if (!support.funcNames) {
+            bindData = !reFuncName.test(source);
+          }
+          if (!bindData) {
+            // checks if `func` references the `this` keyword and stores the result
+            bindData = reThis.test(source);
+            setBindData(func, bindData);
+          }
+        }
+      }
+      // exit early if there are no `this` references or `func` is bound
+      if (bindData === false || (bindData !== true && bindData[1] & 1)) {
+        return func;
+      }
+      switch (argCount) {
+        case 1: return function(value) {
+          return func.call(thisArg, value);
+        };
+        case 2: return function(a, b) {
+          return func.call(thisArg, a, b);
+        };
+        case 3: return function(value, index, collection) {
+          return func.call(thisArg, value, index, collection);
+        };
+        case 4: return function(accumulator, value, index, collection) {
+          return func.call(thisArg, accumulator, value, index, collection);
+        };
+      }
+      return bind(func, thisArg);
+    }
+
+    /**
+     * The base implementation of `createWrapper` that creates the wrapper and
+     * sets its meta data.
+     *
+     * @private
+     * @param {Array} bindData The bind data array.
+     * @returns {Function} Returns the new function.
+     */
+    function baseCreateWrapper(bindData) {
+      var func = bindData[0],
+          bitmask = bindData[1],
+          partialArgs = bindData[2],
+          partialRightArgs = bindData[3],
+          thisArg = bindData[4],
+          arity = bindData[5];
+
+      var isBind = bitmask & 1,
+          isBindKey = bitmask & 2,
+          isCurry = bitmask & 4,
+          isCurryBound = bitmask & 8,
+          key = func;
+
+      function bound() {
+        var thisBinding = isBind ? thisArg : this;
+        if (partialArgs) {
+          var args = slice(partialArgs);
+          push.apply(args, arguments);
+        }
+        if (partialRightArgs || isCurry) {
+          args || (args = slice(arguments));
+          if (partialRightArgs) {
+            push.apply(args, partialRightArgs);
+          }
+          if (isCurry && args.length < arity) {
+            bitmask |= 16 & ~32;
+            return baseCreateWrapper([func, (isCurryBound ? bitmask : bitmask & ~3), args, null, thisArg, arity]);
+          }
+        }
+        args || (args = arguments);
+        if (isBindKey) {
+          func = thisBinding[key];
+        }
+        if (this instanceof bound) {
+          thisBinding = baseCreate(func.prototype);
+          var result = func.apply(thisBinding, args);
+          return isObject(result) ? result : thisBinding;
+        }
+        return func.apply(thisBinding, args);
+      }
+      setBindData(bound, bindData);
+      return bound;
+    }
+
+    /**
+     * The base implementation of `_.difference` that accepts a single array
+     * of values to exclude.
+     *
+     * @private
+     * @param {Array} array The array to process.
+     * @param {Array} [values] The array of values to exclude.
+     * @returns {Array} Returns a new array of filtered values.
+     */
+    function baseDifference(array, values) {
+      var index = -1,
+          indexOf = getIndexOf(),
+          length = array ? array.length : 0,
+          isLarge = length >= largeArraySize && indexOf === baseIndexOf,
+          result = [];
+
+      if (isLarge) {
+        var cache = createCache(values);
+        if (cache) {
+          indexOf = cacheIndexOf;
+          values = cache;
+        } else {
+          isLarge = false;
+        }
+      }
+      while (++index < length) {
+        var value = array[index];
+        if (indexOf(values, value) < 0) {
+          result.push(value);
+        }
+      }
+      if (isLarge) {
+        releaseObject(values);
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.flatten` without support for callback
+     * shorthands or `thisArg` binding.
+     *
+     * @private
+     * @param {Array} array The array to flatten.
+     * @param {boolean} [isShallow=false] A flag to restrict flattening to a single level.
+     * @param {boolean} [isStrict=false] A flag to restrict flattening to arrays and `arguments` objects.
+     * @param {number} [fromIndex=0] The index to start from.
+     * @returns {Array} Returns a new flattened array.
+     */
+    function baseFlatten(array, isShallow, isStrict, fromIndex) {
+      var index = (fromIndex || 0) - 1,
+          length = array ? array.length : 0,
+          result = [];
+
+      while (++index < length) {
+        var value = array[index];
+
+        if (value && typeof value == 'object' && typeof value.length == 'number'
+            && (isArray(value) || isArguments(value))) {
+          // recursively flatten arrays (susceptible to call stack limits)
+          if (!isShallow) {
+            value = baseFlatten(value, isShallow, isStrict);
+          }
+          var valIndex = -1,
+              valLength = value.length,
+              resIndex = result.length;
+
+          result.length += valLength;
+          while (++valIndex < valLength) {
+            result[resIndex++] = value[valIndex];
+          }
+        } else if (!isStrict) {
+          result.push(value);
+        }
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.isEqual`, without support for `thisArg` binding,
+     * that allows partial "_.where" style comparisons.
+     *
+     * @private
+     * @param {*} a The value to compare.
+     * @param {*} b The other value to compare.
+     * @param {Function} [callback] The function to customize comparing values.
+     * @param {Function} [isWhere=false] A flag to indicate performing partial comparisons.
+     * @param {Array} [stackA=[]] Tracks traversed `a` objects.
+     * @param {Array} [stackB=[]] Tracks traversed `b` objects.
+     * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
+     */
+    function baseIsEqual(a, b, callback, isWhere, stackA, stackB) {
+      // used to indicate that when comparing objects, `a` has at least the properties of `b`
+      if (callback) {
+        var result = callback(a, b);
+        if (typeof result != 'undefined') {
+          return !!result;
+        }
+      }
+      // exit early for identical values
+      if (a === b) {
+        // treat `+0` vs. `-0` as not equal
+        return a !== 0 || (1 / a == 1 / b);
+      }
+      var type = typeof a,
+          otherType = typeof b;
+
+      // exit early for unlike primitive values
+      if (a === a &&
+          !(a && objectTypes[type]) &&
+          !(b && objectTypes[otherType])) {
+        return false;
+      }
+      // exit early for `null` and `undefined` avoiding ES3's Function#call behavior
+      // http://es5.github.io/#x15.3.4.4
+      if (a == null || b == null) {
+        return a === b;
+      }
+      // compare [[Class]] names
+      var className = toString.call(a),
+          otherClass = toString.call(b);
+
+      if (className == argsClass) {
+        className = objectClass;
+      }
+      if (otherClass == argsClass) {
+        otherClass = objectClass;
+      }
+      if (className != otherClass) {
+        return false;
+      }
+      switch (className) {
+        case boolClass:
+        case dateClass:
+          // coerce dates and booleans to numbers, dates to milliseconds and booleans
+          // to `1` or `0` treating invalid dates coerced to `NaN` as not equal
+          return +a == +b;
+
+        case numberClass:
+          // treat `NaN` vs. `NaN` as equal
+          return (a != +a)
+            ? b != +b
+            // but treat `+0` vs. `-0` as not equal
+            : (a == 0 ? (1 / a == 1 / b) : a == +b);
+
+        case regexpClass:
+        case stringClass:
+          // coerce regexes to strings (http://es5.github.io/#x15.10.6.4)
+          // treat string primitives and their corresponding object instances as equal
+          return a == String(b);
+      }
+      var isArr = className == arrayClass;
+      if (!isArr) {
+        // unwrap any `lodash` wrapped values
+        var aWrapped = hasOwnProperty.call(a, '__wrapped__'),
+            bWrapped = hasOwnProperty.call(b, '__wrapped__');
+
+        if (aWrapped || bWrapped) {
+          return baseIsEqual(aWrapped ? a.__wrapped__ : a, bWrapped ? b.__wrapped__ : b, callback, isWhere, stackA, stackB);
+        }
+        // exit for functions and DOM nodes
+        if (className != objectClass || (!support.nodeClass && (isNode(a) || isNode(b)))) {
+          return false;
+        }
+        // in older versions of Opera, `arguments` objects have `Array` constructors
+        var ctorA = !support.argsObject && isArguments(a) ? Object : a.constructor,
+            ctorB = !support.argsObject && isArguments(b) ? Object : b.constructor;
+
+        // non `Object` object instances with different constructors are not equal
+        if (ctorA != ctorB &&
+              !(isFunction(ctorA) && ctorA instanceof ctorA && isFunction(ctorB) && ctorB instanceof ctorB) &&
+              ('constructor' in a && 'constructor' in b)
+            ) {
+          return false;
+        }
+      }
+      // assume cyclic structures are equal
+      // the algorithm for detecting cyclic structures is adapted from ES 5.1
+      // section 15.12.3, abstract operation `JO` (http://es5.github.io/#x15.12.3)
+      var initedStack = !stackA;
+      stackA || (stackA = getArray());
+      stackB || (stackB = getArray());
+
+      var length = stackA.length;
+      while (length--) {
+        if (stackA[length] == a) {
+          return stackB[length] == b;
+        }
+      }
+      var size = 0;
+      result = true;
+
+      // add `a` and `b` to the stack of traversed objects
+      stackA.push(a);
+      stackB.push(b);
+
+      // recursively compare objects and arrays (susceptible to call stack limits)
+      if (isArr) {
+        // compare lengths to determine if a deep comparison is necessary
+        length = a.length;
+        size = b.length;
+        result = size == length;
+
+        if (result || isWhere) {
+          // deep compare the contents, ignoring non-numeric properties
+          while (size--) {
+            var index = length,
+                value = b[size];
+
+            if (isWhere) {
+              while (index--) {
+                if ((result = baseIsEqual(a[index], value, callback, isWhere, stackA, stackB))) {
+                  break;
+                }
+              }
+            } else if (!(result = baseIsEqual(a[size], value, callback, isWhere, stackA, stackB))) {
+              break;
+            }
+          }
+        }
+      }
+      else {
+        // deep compare objects using `forIn`, instead of `forOwn`, to avoid `Object.keys`
+        // which, in this case, is more costly
+        forIn(b, function(value, key, b) {
+          if (hasOwnProperty.call(b, key)) {
+            // count the number of properties.
+            size++;
+            // deep compare each property value.
+            return (result = hasOwnProperty.call(a, key) && baseIsEqual(a[key], value, callback, isWhere, stackA, stackB));
+          }
+        });
+
+        if (result && !isWhere) {
+          // ensure both objects have the same number of properties
+          forIn(a, function(value, key, a) {
+            if (hasOwnProperty.call(a, key)) {
+              // `size` will be `-1` if `a` has more properties than `b`
+              return (result = --size > -1);
+            }
+          });
+        }
+      }
+      stackA.pop();
+      stackB.pop();
+
+      if (initedStack) {
+        releaseArray(stackA);
+        releaseArray(stackB);
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.merge` without argument juggling or support
+     * for `thisArg` binding.
+     *
+     * @private
+     * @param {Object} object The destination object.
+     * @param {Object} source The source object.
+     * @param {Function} [callback] The function to customize merging properties.
+     * @param {Array} [stackA=[]] Tracks traversed source objects.
+     * @param {Array} [stackB=[]] Associates values with source counterparts.
+     */
+    function baseMerge(object, source, callback, stackA, stackB) {
+      (isArray(source) ? forEach : forOwn)(source, function(source, key) {
+        var found,
+            isArr,
+            result = source,
+            value = object[key];
+
+        if (source && ((isArr = isArray(source)) || isPlainObject(source))) {
+          // avoid merging previously merged cyclic sources
+          var stackLength = stackA.length;
+          while (stackLength--) {
+            if ((found = stackA[stackLength] == source)) {
+              value = stackB[stackLength];
+              break;
+            }
+          }
+          if (!found) {
+            var isShallow;
+            if (callback) {
+              result = callback(value, source);
+              if ((isShallow = typeof result != 'undefined')) {
+                value = result;
+              }
+            }
+            if (!isShallow) {
+              value = isArr
+                ? (isArray(value) ? value : [])
+                : (isPlainObject(value) ? value : {});
+            }
+            // add `source` and associated `value` to the stack of traversed objects
+            stackA.push(source);
+            stackB.push(value);
+
+            // recursively merge objects and arrays (susceptible to call stack limits)
+            if (!isShallow) {
+              baseMerge(value, source, callback, stackA, stackB);
+            }
+          }
+        }
+        else {
+          if (callback) {
+            result = callback(value, source);
+            if (typeof result == 'undefined') {
+              result = source;
+            }
+          }
+          if (typeof result != 'undefined') {
+            value = result;
+          }
+        }
+        object[key] = value;
+      });
+    }
+
+    /**
+     * The base implementation of `_.random` without argument juggling or support
+     * for returning floating-point numbers.
+     *
+     * @private
+     * @param {number} min The minimum possible value.
+     * @param {number} max The maximum possible value.
+     * @returns {number} Returns a random number.
+     */
+    function baseRandom(min, max) {
+      return min + floor(nativeRandom() * (max - min + 1));
+    }
+
+    /**
+     * The base implementation of `_.uniq` without support for callback shorthands
+     * or `thisArg` binding.
+     *
+     * @private
+     * @param {Array} array The array to process.
+     * @param {boolean} [isSorted=false] A flag to indicate that `array` is sorted.
+     * @param {Function} [callback] The function called per iteration.
+     * @returns {Array} Returns a duplicate-value-free array.
+     */
+    function baseUniq(array, isSorted, callback) {
+      var index = -1,
+          indexOf = getIndexOf(),
+          length = array ? array.length : 0,
+          result = [];
+
+      var isLarge = !isSorted && length >= largeArraySize && indexOf === baseIndexOf,
+          seen = (callback || isLarge) ? getArray() : result;
+
+      if (isLarge) {
+        var cache = createCache(seen);
+        indexOf = cacheIndexOf;
+        seen = cache;
+      }
+      while (++index < length) {
+        var value = array[index],
+            computed = callback ? callback(value, index, array) : value;
+
+        if (isSorted
+              ? !index || seen[seen.length - 1] !== computed
+              : indexOf(seen, computed) < 0
+            ) {
+          if (callback || isLarge) {
+            seen.push(computed);
+          }
+          result.push(value);
+        }
+      }
+      if (isLarge) {
+        releaseArray(seen.array);
+        releaseObject(seen);
+      } else if (callback) {
+        releaseArray(seen);
+      }
+      return result;
+    }
+
+    /**
+     * Creates a function that aggregates a collection, creating an object composed
+     * of keys generated from the results of running each element of the collection
+     * through a callback. The given `setter` function sets the keys and values
+     * of the composed object.
+     *
+     * @private
+     * @param {Function} setter The setter function.
+     * @returns {Function} Returns the new aggregator function.
+     */
+    function createAggregator(setter) {
+      return function(collection, callback, thisArg) {
+        var result = {};
+        callback = lodash.createCallback(callback, thisArg, 3);
+
+        if (isArray(collection)) {
+          var index = -1,
+              length = collection.length;
+
+          while (++index < length) {
+            var value = collection[index];
+            setter(result, value, callback(value, index, collection), collection);
+          }
+        } else {
+          baseEach(collection, function(value, key, collection) {
+            setter(result, value, callback(value, key, collection), collection);
+          });
+        }
+        return result;
+      };
+    }
+
+    /**
+     * Creates a function that, when called, either curries or invokes `func`
+     * with an optional `this` binding and partially applied arguments.
+     *
+     * @private
+     * @param {Function|string} func The function or method name to reference.
+     * @param {number} bitmask The bitmask of method flags to compose.
+     *  The bitmask may be composed of the following flags:
+     *  1 - `_.bind`
+     *  2 - `_.bindKey`
+     *  4 - `_.curry`
+     *  8 - `_.curry` (bound)
+     *  16 - `_.partial`
+     *  32 - `_.partialRight`
+     * @param {Array} [partialArgs] An array of arguments to prepend to those
+     *  provided to the new function.
+     * @param {Array} [partialRightArgs] An array of arguments to append to those
+     *  provided to the new function.
+     * @param {*} [thisArg] The `this` binding of `func`.
+     * @param {number} [arity] The arity of `func`.
+     * @returns {Function} Returns the new function.
+     */
+    function createWrapper(func, bitmask, partialArgs, partialRightArgs, thisArg, arity) {
+      var isBind = bitmask & 1,
+          isBindKey = bitmask & 2,
+          isCurry = bitmask & 4,
+          isCurryBound = bitmask & 8,
+          isPartial = bitmask & 16,
+          isPartialRight = bitmask & 32;
+
+      if (!isBindKey && !isFunction(func)) {
+        throw new TypeError;
+      }
+      if (isPartial && !partialArgs.length) {
+        bitmask &= ~16;
+        isPartial = partialArgs = false;
+      }
+      if (isPartialRight && !partialRightArgs.length) {
+        bitmask &= ~32;
+        isPartialRight = partialRightArgs = false;
+      }
+      var bindData = func && func.__bindData__;
+      if (bindData && bindData !== true) {
+        // clone `bindData`
+        bindData = slice(bindData);
+        if (bindData[2]) {
+          bindData[2] = slice(bindData[2]);
+        }
+        if (bindData[3]) {
+          bindData[3] = slice(bindData[3]);
+        }
+        // set `thisBinding` is not previously bound
+        if (isBind && !(bindData[1] & 1)) {
+          bindData[4] = thisArg;
+        }
+        // set if previously bound but not currently (subsequent curried functions)
+        if (!isBind && bindData[1] & 1) {
+          bitmask |= 8;
+        }
+        // set curried arity if not yet set
+        if (isCurry && !(bindData[1] & 4)) {
+          bindData[5] = arity;
+        }
+        // append partial left arguments
+        if (isPartial) {
+          push.apply(bindData[2] || (bindData[2] = []), partialArgs);
+        }
+        // append partial right arguments
+        if (isPartialRight) {
+          unshift.apply(bindData[3] || (bindData[3] = []), partialRightArgs);
+        }
+        // merge flags
+        bindData[1] |= bitmask;
+        return createWrapper.apply(null, bindData);
+      }
+      // fast path for `_.bind`
+      var creater = (bitmask == 1 || bitmask === 17) ? baseBind : baseCreateWrapper;
+      return creater([func, bitmask, partialArgs, partialRightArgs, thisArg, arity]);
+    }
+
+    /**
+     * Creates compiled iteration functions.
+     *
+     * @private
+     * @param {...Object} [options] The compile options object(s).
+     * @param {string} [options.array] Code to determine if the iterable is an array or array-like.
+     * @param {boolean} [options.useHas] Specify using `hasOwnProperty` checks in the object loop.
+     * @param {Function} [options.keys] A reference to `_.keys` for use in own property iteration.
+     * @param {string} [options.args] A comma separated string of iteration function arguments.
+     * @param {string} [options.top] Code to execute before the iteration branches.
+     * @param {string} [options.loop] Code to execute in the object loop.
+     * @param {string} [options.bottom] Code to execute after the iteration branches.
+     * @returns {Function} Returns the compiled function.
+     */
+    function createIterator() {
+      // data properties
+      iteratorData.shadowedProps = shadowedProps;
+
+      // iterator options
+      iteratorData.array = iteratorData.bottom = iteratorData.loop = iteratorData.top = '';
+      iteratorData.init = 'iterable';
+      iteratorData.useHas = true;
+
+      // merge options into a template data object
+      for (var object, index = 0; object = arguments[index]; index++) {
+        for (var key in object) {
+          iteratorData[key] = object[key];
+        }
+      }
+      var args = iteratorData.args;
+      iteratorData.firstArg = /^[^,]+/.exec(args)[0];
+
+      // create the function factory
+      var factory = Function(
+          'baseCreateCallback, errorClass, errorProto, hasOwnProperty, ' +
+          'indicatorObject, isArguments, isArray, isString, keys, objectProto, ' +
+          'objectTypes, nonEnumProps, stringClass, stringProto, toString',
+        'return function(' + args + ') {\n' + iteratorTemplate(iteratorData) + '\n}'
+      );
+
+      // return the compiled function
+      return factory(
+        baseCreateCallback, errorClass, errorProto, hasOwnProperty,
+        indicatorObject, isArguments, isArray, isString, iteratorData.keys, objectProto,
+        objectTypes, nonEnumProps, stringClass, stringProto, toString
+      );
+    }
+
+    /**
+     * Used by `escape` to convert characters to HTML entities.
+     *
+     * @private
+     * @param {string} match The matched character to escape.
+     * @returns {string} Returns the escaped character.
+     */
+    function escapeHtmlChar(match) {
+      return htmlEscapes[match];
+    }
+
+    /**
+     * Gets the appropriate "indexOf" function. If the `_.indexOf` method is
+     * customized, this method returns the custom method, otherwise it returns
+     * the `baseIndexOf` function.
+     *
+     * @private
+     * @returns {Function} Returns the "indexOf" function.
+     */
+    function getIndexOf() {
+      var result = (result = lodash.indexOf) === indexOf ? baseIndexOf : result;
+      return result;
+    }
+
+    /**
+     * Checks if `value` is a native function.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if the `value` is a native function, else `false`.
+     */
+    function isNative(value) {
+      return typeof value == 'function' && reNative.test(value);
+    }
+
+    /**
+     * Sets `this` binding data on a given function.
+     *
+     * @private
+     * @param {Function} func The function to set data on.
+     * @param {Array} value The data array to set.
+     */
+    var setBindData = !defineProperty ? noop : function(func, value) {
+      descriptor.value = value;
+      defineProperty(func, '__bindData__', descriptor);
+    };
+
+    /**
+     * A fallback implementation of `isPlainObject` which checks if a given value
+     * is an object created by the `Object` constructor, assuming objects created
+     * by the `Object` constructor have no inherited enumerable properties and that
+     * there are no `Object.prototype` extensions.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a plain object, else `false`.
+     */
+    function shimIsPlainObject(value) {
+      var ctor,
+          result;
+
+      // avoid non Object objects, `arguments` objects, and DOM elements
+      if (!(value && toString.call(value) == objectClass) ||
+          (ctor = value.constructor, isFunction(ctor) && !(ctor instanceof ctor)) ||
+          (!support.argsClass && isArguments(value)) ||
+          (!support.nodeClass && isNode(value))) {
+        return false;
+      }
+      // IE < 9 iterates inherited properties before own properties. If the first
+      // iterated property is an object's own property then there are no inherited
+      // enumerable properties.
+      if (support.ownLast) {
+        forIn(value, function(value, key, object) {
+          result = hasOwnProperty.call(object, key);
+          return false;
+        });
+        return result !== false;
+      }
+      // In most environments an object's own properties are iterated before
+      // its inherited properties. If the last iterated property is an object's
+      // own property then there are no inherited enumerable properties.
+      forIn(value, function(value, key) {
+        result = key;
+      });
+      return typeof result == 'undefined' || hasOwnProperty.call(value, result);
+    }
+
+    /**
+     * Used by `unescape` to convert HTML entities to characters.
+     *
+     * @private
+     * @param {string} match The matched character to unescape.
+     * @returns {string} Returns the unescaped character.
+     */
+    function unescapeHtmlChar(match) {
+      return htmlUnescapes[match];
+    }
+
+    /*--------------------------------------------------------------------------*/
+
+    /**
+     * Checks if `value` is an `arguments` object.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if the `value` is an `arguments` object, else `false`.
+     * @example
+     *
+     * (function() { return _.isArguments(arguments); })(1, 2, 3);
+     * // => true
+     *
+     * _.isArguments([1, 2, 3]);
+     * // => false
+     */
+    function isArguments(value) {
+      return value && typeof value == 'object' && typeof value.length == 'number' &&
+        toString.call(value) == argsClass || false;
+    }
+    // fallback for browsers that can't detect `arguments` objects by [[Class]]
+    if (!support.argsClass) {
+      isArguments = function(value) {
+        return value && typeof value == 'object' && typeof value.length == 'number' &&
+          hasOwnProperty.call(value, 'callee') && !propertyIsEnumerable.call(value, 'callee') || false;
+      };
+    }
+
+    /**
+     * Checks if `value` is an array.
+     *
+     * @static
+     * @memberOf _
+     * @type Function
+     * @category Objects
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if the `value` is an array, else `false`.
+     * @example
+     *
+     * (function() { return _.isArray(arguments); })();
+     * // => false
+     *
+     * _.isArray([1, 2, 3]);
+     * // => true
+     */
+    var isArray = nativeIsArray || function(value) {
+      return value && typeof value == 'object' && typeof value.length == 'number' &&
+        toString.call(value) == arrayClass || false;
+    };
+
+    /**
+     * A fallback implementation of `Object.keys` which produces an array of the
+     * given object's own enumerable property names.
+     *
+     * @private
+     * @type Function
+     * @param {Object} object The object to inspect.
+     * @returns {Array} Returns an array of property names.
+     */
+    var shimKeys = createIterator({
+      'args': 'object',
+      'init': '[]',
+      'top': 'if (!(objectTypes[typeof object])) return result',
+      'loop': 'result.push(index)'
+    });
+
+    /**
+     * Creates an array composed of the own enumerable property names of an object.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Object} object The object to inspect.
+     * @returns {Array} Returns an array of property names.
+     * @example
+     *
+     * _.keys({ 'one': 1, 'two': 2, 'three': 3 });
+     * // => ['one', 'two', 'three'] (property order is not guaranteed across environments)
+     */
+    var keys = !nativeKeys ? shimKeys : function(object) {
+      if (!isObject(object)) {
+        return [];
+      }
+      if ((support.enumPrototypes && typeof object == 'function') ||
+          (support.nonEnumArgs && object.length && isArguments(object))) {
+        return shimKeys(object);
+      }
+      return nativeKeys(object);
+    };
+
+    /** Reusable iterator options shared by `each`, `forIn`, and `forOwn` */
+    var eachIteratorOptions = {
+      'args': 'collection, callback, thisArg',
+      'top': "callback = callback && typeof thisArg == 'undefined' ? callback : baseCreateCallback(callback, thisArg, 3)",
+      'array': "typeof length == 'number'",
+      'keys': keys,
+      'loop': 'if (callback(iterable[index], index, collection) === false) return result'
+    };
+
+    /** Reusable iterator options for `assign` and `defaults` */
+    var defaultsIteratorOptions = {
+      'args': 'object, source, guard',
+      'top':
+        'var args = arguments,\n' +
+        '    argsIndex = 0,\n' +
+        "    argsLength = typeof guard == 'number' ? 2 : args.length;\n" +
+        'while (++argsIndex < argsLength) {\n' +
+        '  iterable = args[argsIndex];\n' +
+        '  if (iterable && objectTypes[typeof iterable]) {',
+      'keys': keys,
+      'loop': "if (typeof result[index] == 'undefined') result[index] = iterable[index]",
+      'bottom': '  }\n}'
+    };
+
+    /** Reusable iterator options for `forIn` and `forOwn` */
+    var forOwnIteratorOptions = {
+      'top': 'if (!objectTypes[typeof iterable]) return result;\n' + eachIteratorOptions.top,
+      'array': false
+    };
+
+    /**
+     * Used to convert characters to HTML entities:
+     *
+     * Though the `>` character is escaped for symmetry, characters like `>` and `/`
+     * don't require escaping in HTML and have no special meaning unless they're part
+     * of a tag or an unquoted attribute value.
+     * http://mathiasbynens.be/notes/ambiguous-ampersands (under "semi-related fun fact")
+     */
+    var htmlEscapes = {
+      '&': '&amp;',
+      '<': '&lt;',
+      '>': '&gt;',
+      '"': '&quot;',
+      "'": '&#39;'
+    };
+
+    /** Used to convert HTML entities to characters */
+    var htmlUnescapes = invert(htmlEscapes);
+
+    /** Used to match HTML entities and HTML characters */
+    var reEscapedHtml = RegExp('(' + keys(htmlUnescapes).join('|') + ')', 'g'),
+        reUnescapedHtml = RegExp('[' + keys(htmlEscapes).join('') + ']', 'g');
+
+    /**
+     * A function compiled to iterate `arguments` objects, arrays, objects, and
+     * strings consistenly across environments, executing the callback for each
+     * element in the collection. The callback is bound to `thisArg` and invoked
+     * with three arguments; (value, index|key, collection). Callbacks may exit
+     * iteration early by explicitly returning `false`.
+     *
+     * @private
+     * @type Function
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function} [callback=identity] The function called per iteration.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Array|Object|string} Returns `collection`.
+     */
+    var baseEach = createIterator(eachIteratorOptions);
+
+    /*--------------------------------------------------------------------------*/
+
+    /**
+     * Assigns own enumerable properties of source object(s) to the destination
+     * object. Subsequent sources will overwrite property assignments of previous
+     * sources. If a callback is provided it will be executed to produce the
+     * assigned values. The callback is bound to `thisArg` and invoked with two
+     * arguments; (objectValue, sourceValue).
+     *
+     * @static
+     * @memberOf _
+     * @type Function
+     * @alias extend
+     * @category Objects
+     * @param {Object} object The destination object.
+     * @param {...Object} [source] The source objects.
+     * @param {Function} [callback] The function to customize assigning values.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Object} Returns the destination object.
+     * @example
+     *
+     * _.assign({ 'name': 'fred' }, { 'employer': 'slate' });
+     * // => { 'name': 'fred', 'employer': 'slate' }
+     *
+     * var defaults = _.partialRight(_.assign, function(a, b) {
+     *   return typeof a == 'undefined' ? b : a;
+     * });
+     *
+     * var object = { 'name': 'barney' };
+     * defaults(object, { 'name': 'fred', 'employer': 'slate' });
+     * // => { 'name': 'barney', 'employer': 'slate' }
+     */
+    var assign = createIterator(defaultsIteratorOptions, {
+      'top':
+        defaultsIteratorOptions.top.replace(';',
+          ';\n' +
+          "if (argsLength > 3 && typeof args[argsLength - 2] == 'function') {\n" +
+          '  var callback = baseCreateCallback(args[--argsLength - 1], args[argsLength--], 2);\n' +
+          "} else if (argsLength > 2 && typeof args[argsLength - 1] == 'function') {\n" +
+          '  callback = args[--argsLength];\n' +
+          '}'
+        ),
+      'loop': 'result[index] = callback ? callback(result[index], iterable[index]) : iterable[index]'
+    });
+
+    /**
+     * Creates a clone of `value`. If `isDeep` is `true` nested objects will also
+     * be cloned, otherwise they will be assigned by reference. If a callback
+     * is provided it will be executed to produce the cloned values. If the
+     * callback returns `undefined` cloning will be handled by the method instead.
+     * The callback is bound to `thisArg` and invoked with one argument; (value).
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to clone.
+     * @param {boolean} [isDeep=false] Specify a deep clone.
+     * @param {Function} [callback] The function to customize cloning values.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {*} Returns the cloned value.
+     * @example
+     *
+     * var characters = [
+     *   { 'name': 'barney', 'age': 36 },
+     *   { 'name': 'fred',   'age': 40 }
+     * ];
+     *
+     * var shallow = _.clone(characters);
+     * shallow[0] === characters[0];
+     * // => true
+     *
+     * var deep = _.clone(characters, true);
+     * deep[0] === characters[0];
+     * // => false
+     *
+     * _.mixin({
+     *   'clone': _.partialRight(_.clone, function(value) {
+     *     return _.isElement(value) ? value.cloneNode(false) : undefined;
+     *   })
+     * });
+     *
+     * var clone = _.clone(document.body);
+     * clone.childNodes.length;
+     * // => 0
+     */
+    function clone(value, isDeep, callback, thisArg) {
+      // allows working with "Collections" methods without using their `index`
+      // and `collection` arguments for `isDeep` and `callback`
+      if (typeof isDeep != 'boolean' && isDeep != null) {
+        thisArg = callback;
+        callback = isDeep;
+        isDeep = false;
+      }
+      return baseClone(value, isDeep, typeof callback == 'function' && baseCreateCallback(callback, thisArg, 1));
+    }
+
+    /**
+     * Creates a deep clone of `value`. If a callback is provided it will be
+     * executed to produce the cloned values. If the callback returns `undefined`
+     * cloning will be handled by the method instead. The callback is bound to
+     * `thisArg` and invoked with one argument; (value).
+     *
+     * Note: This method is loosely based on the structured clone algorithm. Functions
+     * and DOM nodes are **not** cloned. The enumerable properties of `arguments` objects and
+     * objects created by constructors other than `Object` are cloned to plain `Object` objects.
+     * See http://www.w3.org/TR/html5/infrastructure.html#internal-structured-cloning-algorithm.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to deep clone.
+     * @param {Function} [callback] The function to customize cloning values.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {*} Returns the deep cloned value.
+     * @example
+     *
+     * var characters = [
+     *   { 'name': 'barney', 'age': 36 },
+     *   { 'name': 'fred',   'age': 40 }
+     * ];
+     *
+     * var deep = _.cloneDeep(characters);
+     * deep[0] === characters[0];
+     * // => false
+     *
+     * var view = {
+     *   'label': 'docs',
+     *   'node': element
+     * };
+     *
+     * var clone = _.cloneDeep(view, function(value) {
+     *   return _.isElement(value) ? value.cloneNode(true) : undefined;
+     * });
+     *
+     * clone.node == view.node;
+     * // => false
+     */
+    function cloneDeep(value, callback, thisArg) {
+      return baseClone(value, true, typeof callback == 'function' && baseCreateCallback(callback, thisArg, 1));
+    }
+
+    /**
+     * Creates an object that inherits from the given `prototype` object. If a
+     * `properties` object is provided its own enumerable properties are assigned
+     * to the created object.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Object} prototype The object to inherit from.
+     * @param {Object} [properties] The properties to assign to the object.
+     * @returns {Object} Returns the new object.
+     * @example
+     *
+     * function Shape() {
+     *   this.x = 0;
+     *   this.y = 0;
+     * }
+     *
+     * function Circle() {
+     *   Shape.call(this);
+     * }
+     *
+     * Circle.prototype = _.create(Shape.prototype, { 'constructor': Circle });
+     *
+     * var circle = new Circle;
+     * circle instanceof Circle;
+     * // => true
+     *
+     * circle instanceof Shape;
+     * // => true
+     */
+    function create(prototype, properties) {
+      var result = baseCreate(prototype);
+      return properties ? assign(result, properties) : result;
+    }
+
+    /**
+     * Assigns own enumerable properties of source object(s) to the destination
+     * object for all destination properties that resolve to `undefined`. Once a
+     * property is set, additional defaults of the same property will be ignored.
+     *
+     * @static
+     * @memberOf _
+     * @type Function
+     * @category Objects
+     * @param {Object} object The destination object.
+     * @param {...Object} [source] The source objects.
+     * @param- {Object} [guard] Allows working with `_.reduce` without using its
+     *  `key` and `object` arguments as sources.
+     * @returns {Object} Returns the destination object.
+     * @example
+     *
+     * var object = { 'name': 'barney' };
+     * _.defaults(object, { 'name': 'fred', 'employer': 'slate' });
+     * // => { 'name': 'barney', 'employer': 'slate' }
+     */
+    var defaults = createIterator(defaultsIteratorOptions);
+
+    /**
+     * This method is like `_.findIndex` except that it returns the key of the
+     * first element that passes the callback check, instead of the element itself.
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Object} object The object to search.
+     * @param {Function|Object|string} [callback=identity] The function called per
+     *  iteration. If a property name or object is provided it will be used to
+     *  create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {string|undefined} Returns the key of the found element, else `undefined`.
+     * @example
+     *
+     * var characters = {
+     *   'barney': {  'age': 36, 'blocked': false },
+     *   'fred': {    'age': 40, 'blocked': true },
+     *   'pebbles': { 'age': 1,  'blocked': false }
+     * };
+     *
+     * _.findKey(characters, function(chr) {
+     *   return chr.age < 40;
+     * });
+     * // => 'barney' (property order is not guaranteed across environments)
+     *
+     * // using "_.where" callback shorthand
+     * _.findKey(characters, { 'age': 1 });
+     * // => 'pebbles'
+     *
+     * // using "_.pluck" callback shorthand
+     * _.findKey(characters, 'blocked');
+     * // => 'fred'
+     */
+    function findKey(object, callback, thisArg) {
+      var result;
+      callback = lodash.createCallback(callback, thisArg, 3);
+      forOwn(object, function(value, key, object) {
+        if (callback(value, key, object)) {
+          result = key;
+          return false;
+        }
+      });
+      return result;
+    }
+
+    /**
+     * This method is like `_.findKey` except that it iterates over elements
+     * of a `collection` in the opposite order.
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Object} object The object to search.
+     * @param {Function|Object|string} [callback=identity] The function called per
+     *  iteration. If a property name or object is provided it will be used to
+     *  create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {string|undefined} Returns the key of the found element, else `undefined`.
+     * @example
+     *
+     * var characters = {
+     *   'barney': {  'age': 36, 'blocked': true },
+     *   'fred': {    'age': 40, 'blocked': false },
+     *   'pebbles': { 'age': 1,  'blocked': true }
+     * };
+     *
+     * _.findLastKey(characters, function(chr) {
+     *   return chr.age < 40;
+     * });
+     * // => returns `pebbles`, assuming `_.findKey` returns `barney`
+     *
+     * // using "_.where" callback shorthand
+     * _.findLastKey(characters, { 'age': 40 });
+     * // => 'fred'
+     *
+     * // using "_.pluck" callback shorthand
+     * _.findLastKey(characters, 'blocked');
+     * // => 'pebbles'
+     */
+    function findLastKey(object, callback, thisArg) {
+      var result;
+      callback = lodash.createCallback(callback, thisArg, 3);
+      forOwnRight(object, function(value, key, object) {
+        if (callback(value, key, object)) {
+          result = key;
+          return false;
+        }
+      });
+      return result;
+    }
+
+    /**
+     * Iterates over own and inherited enumerable properties of an object,
+     * executing the callback for each property. The callback is bound to `thisArg`
+     * and invoked with three arguments; (value, key, object). Callbacks may exit
+     * iteration early by explicitly returning `false`.
+     *
+     * @static
+     * @memberOf _
+     * @type Function
+     * @category Objects
+     * @param {Object} object The object to iterate over.
+     * @param {Function} [callback=identity] The function called per iteration.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Object} Returns `object`.
+     * @example
+     *
+     * function Shape() {
+     *   this.x = 0;
+     *   this.y = 0;
+     * }
+     *
+     * Shape.prototype.move = function(x, y) {
+     *   this.x += x;
+     *   this.y += y;
+     * };
+     *
+     * _.forIn(new Shape, function(value, key) {
+     *   console.log(key);
+     * });
+     * // => logs 'x', 'y', and 'move' (property order is not guaranteed across environments)
+     */
+    var forIn = createIterator(eachIteratorOptions, forOwnIteratorOptions, {
+      'useHas': false
+    });
+
+    /**
+     * This method is like `_.forIn` except that it iterates over elements
+     * of a `collection` in the opposite order.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Object} object The object to iterate over.
+     * @param {Function} [callback=identity] The function called per iteration.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Object} Returns `object`.
+     * @example
+     *
+     * function Shape() {
+     *   this.x = 0;
+     *   this.y = 0;
+     * }
+     *
+     * Shape.prototype.move = function(x, y) {
+     *   this.x += x;
+     *   this.y += y;
+     * };
+     *
+     * _.forInRight(new Shape, function(value, key) {
+     *   console.log(key);
+     * });
+     * // => logs 'move', 'y', and 'x' assuming `_.forIn ` logs 'x', 'y', and 'move'
+     */
+    function forInRight(object, callback, thisArg) {
+      var pairs = [];
+
+      forIn(object, function(value, key) {
+        pairs.push(key, value);
+      });
+
+      var length = pairs.length;
+      callback = baseCreateCallback(callback, thisArg, 3);
+      while (length--) {
+        if (callback(pairs[length--], pairs[length], object) === false) {
+          break;
+        }
+      }
+      return object;
+    }
+
+    /**
+     * Iterates over own enumerable properties of an object, executing the callback
+     * for each property. The callback is bound to `thisArg` and invoked with three
+     * arguments; (value, key, object). Callbacks may exit iteration early by
+     * explicitly returning `false`.
+     *
+     * @static
+     * @memberOf _
+     * @type Function
+     * @category Objects
+     * @param {Object} object The object to iterate over.
+     * @param {Function} [callback=identity] The function called per iteration.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Object} Returns `object`.
+     * @example
+     *
+     * _.forOwn({ '0': 'zero', '1': 'one', 'length': 2 }, function(num, key) {
+     *   console.log(key);
+     * });
+     * // => logs '0', '1', and 'length' (property order is not guaranteed across environments)
+     */
+    var forOwn = createIterator(eachIteratorOptions, forOwnIteratorOptions);
+
+    /**
+     * This method is like `_.forOwn` except that it iterates over elements
+     * of a `collection` in the opposite order.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Object} object The object to iterate over.
+     * @param {Function} [callback=identity] The function called per iteration.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Object} Returns `object`.
+     * @example
+     *
+     * _.forOwnRight({ '0': 'zero', '1': 'one', 'length': 2 }, function(num, key) {
+     *   console.log(key);
+     * });
+     * // => logs 'length', '1', and '0' assuming `_.forOwn` logs '0', '1', and 'length'
+     */
+    function forOwnRight(object, callback, thisArg) {
+      var props = keys(object),
+          length = props.length;
+
+      callback = baseCreateCallback(callback, thisArg, 3);
+      while (length--) {
+        var key = props[length];
+        if (callback(object[key], key, object) === false) {
+          break;
+        }
+      }
+      return object;
+    }
+
+    /**
+     * Creates a sorted array of property names of all enumerable properties,
+     * own and inherited, of `object` that have function values.
+     *
+     * @static
+     * @memberOf _
+     * @alias methods
+     * @category Objects
+     * @param {Object} object The object to inspect.
+     * @returns {Array} Returns an array of property names that have function values.
+     * @example
+     *
+     * _.functions(_);
+     * // => ['all', 'any', 'bind', 'bindAll', 'clone', 'compact', 'compose', ...]
+     */
+    function functions(object) {
+      var result = [];
+      forIn(object, function(value, key) {
+        if (isFunction(value)) {
+          result.push(key);
+        }
+      });
+      return result.sort();
+    }
+
+    /**
+     * Checks if the specified property name exists as a direct property of `object`,
+     * instead of an inherited property.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Object} object The object to inspect.
+     * @param {string} key The name of the property to check.
+     * @returns {boolean} Returns `true` if key is a direct property, else `false`.
+     * @example
+     *
+     * _.has({ 'a': 1, 'b': 2, 'c': 3 }, 'b');
+     * // => true
+     */
+    function has(object, key) {
+      return object ? hasOwnProperty.call(object, key) : false;
+    }
+
+    /**
+     * Creates an object composed of the inverted keys and values of the given object.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Object} object The object to invert.
+     * @returns {Object} Returns the created inverted object.
+     * @example
+     *
+     * _.invert({ 'first': 'fred', 'second': 'barney' });
+     * // => { 'fred': 'first', 'barney': 'second' }
+     */
+    function invert(object) {
+      var index = -1,
+          props = keys(object),
+          length = props.length,
+          result = {};
+
+      while (++index < length) {
+        var key = props[index];
+        result[object[key]] = key;
+      }
+      return result;
+    }
+
+    /**
+     * Checks if `value` is a boolean value.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if the `value` is a boolean value, else `false`.
+     * @example
+     *
+     * _.isBoolean(null);
+     * // => false
+     */
+    function isBoolean(value) {
+      return value === true || value === false ||
+        value && typeof value == 'object' && toString.call(value) == boolClass || false;
+    }
+
+    /**
+     * Checks if `value` is a date.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if the `value` is a date, else `false`.
+     * @example
+     *
+     * _.isDate(new Date);
+     * // => true
+     */
+    function isDate(value) {
+      return value && typeof value == 'object' && toString.call(value) == dateClass || false;
+    }
+
+    /**
+     * Checks if `value` is a DOM element.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if the `value` is a DOM element, else `false`.
+     * @example
+     *
+     * _.isElement(document.body);
+     * // => true
+     */
+    function isElement(value) {
+      return value && value.nodeType === 1 || false;
+    }
+
+    /**
+     * Checks if `value` is empty. Arrays, strings, or `arguments` objects with a
+     * length of `0` and objects with no own enumerable properties are considered
+     * "empty".
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Array|Object|string} value The value to inspect.
+     * @returns {boolean} Returns `true` if the `value` is empty, else `false`.
+     * @example
+     *
+     * _.isEmpty([1, 2, 3]);
+     * // => false
+     *
+     * _.isEmpty({});
+     * // => true
+     *
+     * _.isEmpty('');
+     * // => true
+     */
+    function isEmpty(value) {
+      var result = true;
+      if (!value) {
+        return result;
+      }
+      var className = toString.call(value),
+          length = value.length;
+
+      if ((className == arrayClass || className == stringClass ||
+          (support.argsClass ? className == argsClass : isArguments(value))) ||
+          (className == objectClass && typeof length == 'number' && isFunction(value.splice))) {
+        return !length;
+      }
+      forOwn(value, function() {
+        return (result = false);
+      });
+      return result;
+    }
+
+    /**
+     * Performs a deep comparison between two values to determine if they are
+     * equivalent to each other. If a callback is provided it will be executed
+     * to compare values. If the callback returns `undefined` comparisons will
+     * be handled by the method instead. The callback is bound to `thisArg` and
+     * invoked with two arguments; (a, b).
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} a The value to compare.
+     * @param {*} b The other value to compare.
+     * @param {Function} [callback] The function to customize comparing values.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
+     * @example
+     *
+     * var object = { 'name': 'fred' };
+     * var copy = { 'name': 'fred' };
+     *
+     * object == copy;
+     * // => false
+     *
+     * _.isEqual(object, copy);
+     * // => true
+     *
+     * var words = ['hello', 'goodbye'];
+     * var otherWords = ['hi', 'goodbye'];
+     *
+     * _.isEqual(words, otherWords, function(a, b) {
+     *   var reGreet = /^(?:hello|hi)$/i,
+     *       aGreet = _.isString(a) && reGreet.test(a),
+     *       bGreet = _.isString(b) && reGreet.test(b);
+     *
+     *   return (aGreet || bGreet) ? (aGreet == bGreet) : undefined;
+     * });
+     * // => true
+     */
+    function isEqual(a, b, callback, thisArg) {
+      return baseIsEqual(a, b, typeof callback == 'function' && baseCreateCallback(callback, thisArg, 2));
+    }
+
+    /**
+     * Checks if `value` is, or can be coerced to, a finite number.
+     *
+     * Note: This is not the same as native `isFinite` which will return true for
+     * booleans and empty strings. See http://es5.github.io/#x15.1.2.5.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if the `value` is finite, else `false`.
+     * @example
+     *
+     * _.isFinite(-101);
+     * // => true
+     *
+     * _.isFinite('10');
+     * // => true
+     *
+     * _.isFinite(true);
+     * // => false
+     *
+     * _.isFinite('');
+     * // => false
+     *
+     * _.isFinite(Infinity);
+     * // => false
+     */
+    function isFinite(value) {
+      return nativeIsFinite(value) && !nativeIsNaN(parseFloat(value));
+    }
+
+    /**
+     * Checks if `value` is a function.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if the `value` is a function, else `false`.
+     * @example
+     *
+     * _.isFunction(_);
+     * // => true
+     */
+    function isFunction(value) {
+      return typeof value == 'function';
+    }
+    // fallback for older versions of Chrome and Safari
+    if (isFunction(/x/)) {
+      isFunction = function(value) {
+        return typeof value == 'function' && toString.call(value) == funcClass;
+      };
+    }
+
+    /**
+     * Checks if `value` is the language type of Object.
+     * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if the `value` is an object, else `false`.
+     * @example
+     *
+     * _.isObject({});
+     * // => true
+     *
+     * _.isObject([1, 2, 3]);
+     * // => true
+     *
+     * _.isObject(1);
+     * // => false
+     */
+    function isObject(value) {
+      // check if the value is the ECMAScript language type of Object
+      // http://es5.github.io/#x8
+      // and avoid a V8 bug
+      // http://code.google.com/p/v8/issues/detail?id=2291
+      return !!(value && objectTypes[typeof value]);
+    }
+
+    /**
+     * Checks if `value` is `NaN`.
+     *
+     * Note: This is not the same as native `isNaN` which will return `true` for
+     * `undefined` and other non-numeric values. See http://es5.github.io/#x15.1.2.4.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if the `value` is `NaN`, else `false`.
+     * @example
+     *
+     * _.isNaN(NaN);
+     * // => true
+     *
+     * _.isNaN(new Number(NaN));
+     * // => true
+     *
+     * isNaN(undefined);
+     * // => true
+     *
+     * _.isNaN(undefined);
+     * // => false
+     */
+    function isNaN(value) {
+      // `NaN` as a primitive is the only value that is not equal to itself
+      // (perform the [[Class]] check first to avoid errors with some host objects in IE)
+      return isNumber(value) && value != +value;
+    }
+
+    /**
+     * Checks if `value` is `null`.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if the `value` is `null`, else `false`.
+     * @example
+     *
+     * _.isNull(null);
+     * // => true
+     *
+     * _.isNull(undefined);
+     * // => false
+     */
+    function isNull(value) {
+      return value === null;
+    }
+
+    /**
+     * Checks if `value` is a number.
+     *
+     * Note: `NaN` is considered a number. See http://es5.github.io/#x8.5.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if the `value` is a number, else `false`.
+     * @example
+     *
+     * _.isNumber(8.4 * 5);
+     * // => true
+     */
+    function isNumber(value) {
+      return typeof value == 'number' ||
+        value && typeof value == 'object' && toString.call(value) == numberClass || false;
+    }
+
+    /**
+     * Checks if `value` is an object created by the `Object` constructor.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a plain object, else `false`.
+     * @example
+     *
+     * function Shape() {
+     *   this.x = 0;
+     *   this.y = 0;
+     * }
+     *
+     * _.isPlainObject(new Shape);
+     * // => false
+     *
+     * _.isPlainObject([1, 2, 3]);
+     * // => false
+     *
+     * _.isPlainObject({ 'x': 0, 'y': 0 });
+     * // => true
+     */
+    var isPlainObject = !getPrototypeOf ? shimIsPlainObject : function(value) {
+      if (!(value && toString.call(value) == objectClass) || (!support.argsClass && isArguments(value))) {
+        return false;
+      }
+      var valueOf = value.valueOf,
+          objProto = isNative(valueOf) && (objProto = getPrototypeOf(valueOf)) && getPrototypeOf(objProto);
+
+      return objProto
+        ? (value == objProto || getPrototypeOf(value) == objProto)
+        : shimIsPlainObject(value);
+    };
+
+    /**
+     * Checks if `value` is a regular expression.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if the `value` is a regular expression, else `false`.
+     * @example
+     *
+     * _.isRegExp(/fred/);
+     * // => true
+     */
+    function isRegExp(value) {
+      return value && objectTypes[typeof value] && toString.call(value) == regexpClass || false;
+    }
+
+    /**
+     * Checks if `value` is a string.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if the `value` is a string, else `false`.
+     * @example
+     *
+     * _.isString('fred');
+     * // => true
+     */
+    function isString(value) {
+      return typeof value == 'string' ||
+        value && typeof value == 'object' && toString.call(value) == stringClass || false;
+    }
+
+    /**
+     * Checks if `value` is `undefined`.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if the `value` is `undefined`, else `false`.
+     * @example
+     *
+     * _.isUndefined(void 0);
+     * // => true
+     */
+    function isUndefined(value) {
+      return typeof value == 'undefined';
+    }
+
+    /**
+     * Creates an object with the same keys as `object` and values generated by
+     * running each own enumerable property of `object` through the callback.
+     * The callback is bound to `thisArg` and invoked with three arguments;
+     * (value, key, object).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Object} object The object to iterate over.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Array} Returns a new object with values of the results of each `callback` execution.
+     * @example
+     *
+     * _.mapValues({ 'a': 1, 'b': 2, 'c': 3} , function(num) { return num * 3; });
+     * // => { 'a': 3, 'b': 6, 'c': 9 }
+     *
+     * var characters = {
+     *   'fred': { 'name': 'fred', 'age': 40 },
+     *   'pebbles': { 'name': 'pebbles', 'age': 1 }
+     * };
+     *
+     * // using "_.pluck" callback shorthand
+     * _.mapValues(characters, 'age');
+     * // => { 'fred': 40, 'pebbles': 1 }
+     */
+    function mapValues(object, callback, thisArg) {
+      var result = {};
+      callback = lodash.createCallback(callback, thisArg, 3);
+
+      forOwn(object, function(value, key, object) {
+        result[key] = callback(value, key, object);
+      });
+      return result;
+    }
+
+    /**
+     * Recursively merges own enumerable properties of the source object(s), that
+     * don't resolve to `undefined` into the destination object. Subsequent sources
+     * will overwrite property assignments of previous sources. If a callback is
+     * provided it will be executed to produce the merged values of the destination
+     * and source properties. If the callback returns `undefined` merging will
+     * be handled by the method instead. The callback is bound to `thisArg` and
+     * invoked with two arguments; (objectValue, sourceValue).
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Object} object The destination object.
+     * @param {...Object} [source] The source objects.
+     * @param {Function} [callback] The function to customize merging properties.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Object} Returns the destination object.
+     * @example
+     *
+     * var names = {
+     *   'characters': [
+     *     { 'name': 'barney' },
+     *     { 'name': 'fred' }
+     *   ]
+     * };
+     *
+     * var ages = {
+     *   'characters': [
+     *     { 'age': 36 },
+     *     { 'age': 40 }
+     *   ]
+     * };
+     *
+     * _.merge(names, ages);
+     * // => { 'characters': [{ 'name': 'barney', 'age': 36 }, { 'name': 'fred', 'age': 40 }] }
+     *
+     * var food = {
+     *   'fruits': ['apple'],
+     *   'vegetables': ['beet']
+     * };
+     *
+     * var otherFood = {
+     *   'fruits': ['banana'],
+     *   'vegetables': ['carrot']
+     * };
+     *
+     * _.merge(food, otherFood, function(a, b) {
+     *   return _.isArray(a) ? a.concat(b) : undefined;
+     * });
+     * // => { 'fruits': ['apple', 'banana'], 'vegetables': ['beet', 'carrot] }
+     */
+    function merge(object) {
+      var args = arguments,
+          length = 2;
+
+      if (!isObject(object)) {
+        return object;
+      }
+      // allows working with `_.reduce` and `_.reduceRight` without using
+      // their `index` and `collection` arguments
+      if (typeof args[2] != 'number') {
+        length = args.length;
+      }
+      if (length > 3 && typeof args[length - 2] == 'function') {
+        var callback = baseCreateCallback(args[--length - 1], args[length--], 2);
+      } else if (length > 2 && typeof args[length - 1] == 'function') {
+        callback = args[--length];
+      }
+      var sources = slice(arguments, 1, length),
+          index = -1,
+          stackA = getArray(),
+          stackB = getArray();
+
+      while (++index < length) {
+        baseMerge(object, sources[index], callback, stackA, stackB);
+      }
+      releaseArray(stackA);
+      releaseArray(stackB);
+      return object;
+    }
+
+    /**
+     * Creates a shallow clone of `object` excluding the specified properties.
+     * Property names may be specified as individual arguments or as arrays of
+     * property names. If a callback is provided it will be executed for each
+     * property of `object` omitting the properties the callback returns truey
+     * for. The callback is bound to `thisArg` and invoked with three arguments;
+     * (value, key, object).
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Object} object The source object.
+     * @param {Function|...string|string[]} [callback] The properties to omit or the
+     *  function called per iteration.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Object} Returns an object without the omitted properties.
+     * @example
+     *
+     * _.omit({ 'name': 'fred', 'age': 40 }, 'age');
+     * // => { 'name': 'fred' }
+     *
+     * _.omit({ 'name': 'fred', 'age': 40 }, function(value) {
+     *   return typeof value == 'number';
+     * });
+     * // => { 'name': 'fred' }
+     */
+    function omit(object, callback, thisArg) {
+      var result = {};
+      if (typeof callback != 'function') {
+        var props = [];
+        forIn(object, function(value, key) {
+          props.push(key);
+        });
+        props = baseDifference(props, baseFlatten(arguments, true, false, 1));
+
+        var index = -1,
+            length = props.length;
+
+        while (++index < length) {
+          var key = props[index];
+          result[key] = object[key];
+        }
+      } else {
+        callback = lodash.createCallback(callback, thisArg, 3);
+        forIn(object, function(value, key, object) {
+          if (!callback(value, key, object)) {
+            result[key] = value;
+          }
+        });
+      }
+      return result;
+    }
+
+    /**
+     * Creates a two dimensional array of an object's key-value pairs,
+     * i.e. `[[key1, value1], [key2, value2]]`.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Object} object The object to inspect.
+     * @returns {Array} Returns new array of key-value pairs.
+     * @example
+     *
+     * _.pairs({ 'barney': 36, 'fred': 40 });
+     * // => [['barney', 36], ['fred', 40]] (property order is not guaranteed across environments)
+     */
+    function pairs(object) {
+      var index = -1,
+          props = keys(object),
+          length = props.length,
+          result = Array(length);
+
+      while (++index < length) {
+        var key = props[index];
+        result[index] = [key, object[key]];
+      }
+      return result;
+    }
+
+    /**
+     * Creates a shallow clone of `object` composed of the specified properties.
+     * Property names may be specified as individual arguments or as arrays of
+     * property names. If a callback is provided it will be executed for each
+     * property of `object` picking the properties the callback returns truey
+     * for. The callback is bound to `thisArg` and invoked with three arguments;
+     * (value, key, object).
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Object} object The source object.
+     * @param {Function|...string|string[]} [callback] The function called per
+     *  iteration or property names to pick, specified as individual property
+     *  names or arrays of property names.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Object} Returns an object composed of the picked properties.
+     * @example
+     *
+     * _.pick({ 'name': 'fred', '_userid': 'fred1' }, 'name');
+     * // => { 'name': 'fred' }
+     *
+     * _.pick({ 'name': 'fred', '_userid': 'fred1' }, function(value, key) {
+     *   return key.charAt(0) != '_';
+     * });
+     * // => { 'name': 'fred' }
+     */
+    function pick(object, callback, thisArg) {
+      var result = {};
+      if (typeof callback != 'function') {
+        var index = -1,
+            props = baseFlatten(arguments, true, false, 1),
+            length = isObject(object) ? props.length : 0;
+
+        while (++index < length) {
+          var key = props[index];
+          if (key in object) {
+            result[key] = object[key];
+          }
+        }
+      } else {
+        callback = lodash.createCallback(callback, thisArg, 3);
+        forIn(object, function(value, key, object) {
+          if (callback(value, key, object)) {
+            result[key] = value;
+          }
+        });
+      }
+      return result;
+    }
+
+    /**
+     * An alternative to `_.reduce` this method transforms `object` to a new
+     * `accumulator` object which is the result of running each of its own
+     * enumerable properties through a callback, with each callback execution
+     * potentially mutating the `accumulator` object. The callback is bound to
+     * `thisArg` and invoked with four arguments; (accumulator, value, key, object).
+     * Callbacks may exit iteration early by explicitly returning `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Array|Object} object The object to iterate over.
+     * @param {Function} [callback=identity] The function called per iteration.
+     * @param {*} [accumulator] The custom accumulator value.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {*} Returns the accumulated value.
+     * @example
+     *
+     * var squares = _.transform([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], function(result, num) {
+     *   num *= num;
+     *   if (num % 2) {
+     *     return result.push(num) < 3;
+     *   }
+     * });
+     * // => [1, 9, 25]
+     *
+     * var mapped = _.transform({ 'a': 1, 'b': 2, 'c': 3 }, function(result, num, key) {
+     *   result[key] = num * 3;
+     * });
+     * // => { 'a': 3, 'b': 6, 'c': 9 }
+     */
+    function transform(object, callback, accumulator, thisArg) {
+      var isArr = isArray(object);
+      if (accumulator == null) {
+        if (isArr) {
+          accumulator = [];
+        } else {
+          var ctor = object && object.constructor,
+              proto = ctor && ctor.prototype;
+
+          accumulator = baseCreate(proto);
+        }
+      }
+      if (callback) {
+        callback = lodash.createCallback(callback, thisArg, 4);
+        (isArr ? baseEach : forOwn)(object, function(value, index, object) {
+          return callback(accumulator, value, index, object);
+        });
+      }
+      return accumulator;
+    }
+
+    /**
+     * Creates an array composed of the own enumerable property values of `object`.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Object} object The object to inspect.
+     * @returns {Array} Returns an array of property values.
+     * @example
+     *
+     * _.values({ 'one': 1, 'two': 2, 'three': 3 });
+     * // => [1, 2, 3] (property order is not guaranteed across environments)
+     */
+    function values(object) {
+      var index = -1,
+          props = keys(object),
+          length = props.length,
+          result = Array(length);
+
+      while (++index < length) {
+        result[index] = object[props[index]];
+      }
+      return result;
+    }
+
+    /*--------------------------------------------------------------------------*/
+
+    /**
+     * Creates an array of elements from the specified indexes, or keys, of the
+     * `collection`. Indexes may be specified as individual arguments or as arrays
+     * of indexes.
+     *
+     * @static
+     * @memberOf _
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {...(number|number[]|string|string[])} [index] The indexes of `collection`
+     *   to retrieve, specified as individual indexes or arrays of indexes.
+     * @returns {Array} Returns a new array of elements corresponding to the
+     *  provided indexes.
+     * @example
+     *
+     * _.at(['a', 'b', 'c', 'd', 'e'], [0, 2, 4]);
+     * // => ['a', 'c', 'e']
+     *
+     * _.at(['fred', 'barney', 'pebbles'], 0, 2);
+     * // => ['fred', 'pebbles']
+     */
+    function at(collection) {
+      var args = arguments,
+          index = -1,
+          props = baseFlatten(args, true, false, 1),
+          length = (args[2] && args[2][args[1]] === collection) ? 1 : props.length,
+          result = Array(length);
+
+      if (support.unindexedChars && isString(collection)) {
+        collection = collection.split('');
+      }
+      while(++index < length) {
+        result[index] = collection[props[index]];
+      }
+      return result;
+    }
+
+    /**
+     * Checks if a given value is present in a collection using strict equality
+     * for comparisons, i.e. `===`. If `fromIndex` is negative, it is used as the
+     * offset from the end of the collection.
+     *
+     * @static
+     * @memberOf _
+     * @alias include
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {*} target The value to check for.
+     * @param {number} [fromIndex=0] The index to search from.
+     * @returns {boolean} Returns `true` if the `target` element is found, else `false`.
+     * @example
+     *
+     * _.contains([1, 2, 3], 1);
+     * // => true
+     *
+     * _.contains([1, 2, 3], 1, 2);
+     * // => false
+     *
+     * _.contains({ 'name': 'fred', 'age': 40 }, 'fred');
+     * // => true
+     *
+     * _.contains('pebbles', 'eb');
+     * // => true
+     */
+    function contains(collection, target, fromIndex) {
+      var index = -1,
+          indexOf = getIndexOf(),
+          length = collection ? collection.length : 0,
+          result = false;
+
+      fromIndex = (fromIndex < 0 ? nativeMax(0, length + fromIndex) : fromIndex) || 0;
+      if (isArray(collection)) {
+        result = indexOf(collection, target, fromIndex) > -1;
+      } else if (typeof length == 'number') {
+        result = (isString(collection) ? collection.indexOf(target, fromIndex) : indexOf(collection, target, fromIndex)) > -1;
+      } else {
+        baseEach(collection, function(value) {
+          if (++index >= fromIndex) {
+            return !(result = value === target);
+          }
+        });
+      }
+      return result;
+    }
+
+    /**
+     * Creates an object composed of keys generated from the results of running
+     * each element of `collection` through the callback. The corresponding value
+     * of each key is the number of times the key was returned by the callback.
+     * The callback is bound to `thisArg` and invoked with three arguments;
+     * (value, index|key, collection).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Object} Returns the composed aggregate object.
+     * @example
+     *
+     * _.countBy([4.3, 6.1, 6.4], function(num) { return Math.floor(num); });
+     * // => { '4': 1, '6': 2 }
+     *
+     * _.countBy([4.3, 6.1, 6.4], function(num) { return this.floor(num); }, Math);
+     * // => { '4': 1, '6': 2 }
+     *
+     * _.countBy(['one', 'two', 'three'], 'length');
+     * // => { '3': 2, '5': 1 }
+     */
+    var countBy = createAggregator(function(result, value, key) {
+      (hasOwnProperty.call(result, key) ? result[key]++ : result[key] = 1);
+    });
+
+    /**
+     * Checks if the given callback returns truey value for **all** elements of
+     * a collection. The callback is bound to `thisArg` and invoked with three
+     * arguments; (value, index|key, collection).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @alias all
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {boolean} Returns `true` if all elements passed the callback check,
+     *  else `false`.
+     * @example
+     *
+     * _.every([true, 1, null, 'yes']);
+     * // => false
+     *
+     * var characters = [
+     *   { 'name': 'barney', 'age': 36 },
+     *   { 'name': 'fred',   'age': 40 }
+     * ];
+     *
+     * // using "_.pluck" callback shorthand
+     * _.every(characters, 'age');
+     * // => true
+     *
+     * // using "_.where" callback shorthand
+     * _.every(characters, { 'age': 36 });
+     * // => false
+     */
+    function every(collection, callback, thisArg) {
+      var result = true;
+      callback = lodash.createCallback(callback, thisArg, 3);
+
+      if (isArray(collection)) {
+        var index = -1,
+            length = collection.length;
+
+        while (++index < length) {
+          if (!(result = !!callback(collection[index], index, collection))) {
+            break;
+          }
+        }
+      } else {
+        baseEach(collection, function(value, index, collection) {
+          return (result = !!callback(value, index, collection));
+        });
+      }
+      return result;
+    }
+
+    /**
+     * Iterates over elements of a collection, returning an array of all elements
+     * the callback returns truey for. The callback is bound to `thisArg` and
+     * invoked with three arguments; (value, index|key, collection).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @alias select
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Array} Returns a new array of elements that passed the callback check.
+     * @example
+     *
+     * var evens = _.filter([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; });
+     * // => [2, 4, 6]
+     *
+     * var characters = [
+     *   { 'name': 'barney', 'age': 36, 'blocked': false },
+     *   { 'name': 'fred',   'age': 40, 'blocked': true }
+     * ];
+     *
+     * // using "_.pluck" callback shorthand
+     * _.filter(characters, 'blocked');
+     * // => [{ 'name': 'fred', 'age': 40, 'blocked': true }]
+     *
+     * // using "_.where" callback shorthand
+     * _.filter(characters, { 'age': 36 });
+     * // => [{ 'name': 'barney', 'age': 36, 'blocked': false }]
+     */
+    function filter(collection, callback, thisArg) {
+      var result = [];
+      callback = lodash.createCallback(callback, thisArg, 3);
+
+      if (isArray(collection)) {
+        var index = -1,
+            length = collection.length;
+
+        while (++index < length) {
+          var value = collection[index];
+          if (callback(value, index, collection)) {
+            result.push(value);
+          }
+        }
+      } else {
+        baseEach(collection, function(value, index, collection) {
+          if (callback(value, index, collection)) {
+            result.push(value);
+          }
+        });
+      }
+      return result;
+    }
+
+    /**
+     * Iterates over elements of a collection, returning the first element that
+     * the callback returns truey for. The callback is bound to `thisArg` and
+     * invoked with three arguments; (value, index|key, collection).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @alias detect, findWhere
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {*} Returns the found element, else `undefined`.
+     * @example
+     *
+     * var characters = [
+     *   { 'name': 'barney',  'age': 36, 'blocked': false },
+     *   { 'name': 'fred',    'age': 40, 'blocked': true },
+     *   { 'name': 'pebbles', 'age': 1,  'blocked': false }
+     * ];
+     *
+     * _.find(characters, function(chr) {
+     *   return chr.age < 40;
+     * });
+     * // => { 'name': 'barney', 'age': 36, 'blocked': false }
+     *
+     * // using "_.where" callback shorthand
+     * _.find(characters, { 'age': 1 });
+     * // =>  { 'name': 'pebbles', 'age': 1, 'blocked': false }
+     *
+     * // using "_.pluck" callback shorthand
+     * _.find(characters, 'blocked');
+     * // => { 'name': 'fred', 'age': 40, 'blocked': true }
+     */
+    function find(collection, callback, thisArg) {
+      callback = lodash.createCallback(callback, thisArg, 3);
+
+      if (isArray(collection)) {
+        var index = -1,
+            length = collection.length;
+
+        while (++index < length) {
+          var value = collection[index];
+          if (callback(value, index, collection)) {
+            return value;
+          }
+        }
+      } else {
+        var result;
+        baseEach(collection, function(value, index, collection) {
+          if (callback(value, index, collection)) {
+            result = value;
+            return false;
+          }
+        });
+        return result;
+      }
+    }
+
+    /**
+     * This method is like `_.find` except that it iterates over elements
+     * of a `collection` from right to left.
+     *
+     * @static
+     * @memberOf _
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {*} Returns the found element, else `undefined`.
+     * @example
+     *
+     * _.findLast([1, 2, 3, 4], function(num) {
+     *   return num % 2 == 1;
+     * });
+     * // => 3
+     */
+    function findLast(collection, callback, thisArg) {
+      var result;
+      callback = lodash.createCallback(callback, thisArg, 3);
+      forEachRight(collection, function(value, index, collection) {
+        if (callback(value, index, collection)) {
+          result = value;
+          return false;
+        }
+      });
+      return result;
+    }
+
+    /**
+     * Iterates over elements of a collection, executing the callback for each
+     * element. The callback is bound to `thisArg` and invoked with three arguments;
+     * (value, index|key, collection). Callbacks may exit iteration early by
+     * explicitly returning `false`.
+     *
+     * Note: As with other "Collections" methods, objects with a `length` property
+     * are iterated like arrays. To avoid this behavior `_.forIn` or `_.forOwn`
+     * may be used for object iteration.
+     *
+     * @static
+     * @memberOf _
+     * @alias each
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function} [callback=identity] The function called per iteration.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Array|Object|string} Returns `collection`.
+     * @example
+     *
+     * _([1, 2, 3]).forEach(function(num) { console.log(num); }).join(',');
+     * // => logs each number and returns '1,2,3'
+     *
+     * _.forEach({ 'one': 1, 'two': 2, 'three': 3 }, function(num) { console.log(num); });
+     * // => logs each number and returns the object (property order is not guaranteed across environments)
+     */
+    function forEach(collection, callback, thisArg) {
+      if (callback && typeof thisArg == 'undefined' && isArray(collection)) {
+        var index = -1,
+            length = collection.length;
+
+        while (++index < length) {
+          if (callback(collection[index], index, collection) === false) {
+            break;
+          }
+        }
+      } else {
+        baseEach(collection, callback, thisArg);
+      }
+      return collection;
+    }
+
+    /**
+     * This method is like `_.forEach` except that it iterates over elements
+     * of a `collection` from right to left.
+     *
+     * @static
+     * @memberOf _
+     * @alias eachRight
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function} [callback=identity] The function called per iteration.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Array|Object|string} Returns `collection`.
+     * @example
+     *
+     * _([1, 2, 3]).forEachRight(function(num) { console.log(num); }).join(',');
+     * // => logs each number from right to left and returns '3,2,1'
+     */
+    function forEachRight(collection, callback, thisArg) {
+      var iterable = collection,
+          length = collection ? collection.length : 0;
+
+      callback = callback && typeof thisArg == 'undefined' ? callback : baseCreateCallback(callback, thisArg, 3);
+      if (isArray(collection)) {
+        while (length--) {
+          if (callback(collection[length], length, collection) === false) {
+            break;
+          }
+        }
+      } else {
+        if (typeof length != 'number') {
+          var props = keys(collection);
+          length = props.length;
+        } else if (support.unindexedChars && isString(collection)) {
+          iterable = collection.split('');
+        }
+        baseEach(collection, function(value, key, collection) {
+          key = props ? props[--length] : --length;
+          return callback(iterable[key], key, collection);
+        });
+      }
+      return collection;
+    }
+
+    /**
+     * Creates an object composed of keys generated from the results of running
+     * each element of a collection through the callback. The corresponding value
+     * of each key is an array of the elements responsible for generating the key.
+     * The callback is bound to `thisArg` and invoked with three arguments;
+     * (value, index|key, collection).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`
+     *
+     * @static
+     * @memberOf _
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Object} Returns the composed aggregate object.
+     * @example
+     *
+     * _.groupBy([4.2, 6.1, 6.4], function(num) { return Math.floor(num); });
+     * // => { '4': [4.2], '6': [6.1, 6.4] }
+     *
+     * _.groupBy([4.2, 6.1, 6.4], function(num) { return this.floor(num); }, Math);
+     * // => { '4': [4.2], '6': [6.1, 6.4] }
+     *
+     * // using "_.pluck" callback shorthand
+     * _.groupBy(['one', 'two', 'three'], 'length');
+     * // => { '3': ['one', 'two'], '5': ['three'] }
+     */
+    var groupBy = createAggregator(function(result, value, key) {
+      (hasOwnProperty.call(result, key) ? result[key] : result[key] = []).push(value);
+    });
+
+    /**
+     * Creates an object composed of keys generated from the results of running
+     * each element of the collection through the given callback. The corresponding
+     * value of each key is the last element responsible for generating the key.
+     * The callback is bound to `thisArg` and invoked with three arguments;
+     * (value, index|key, collection).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Object} Returns the composed aggregate object.
+     * @example
+     *
+     * var keys = [
+     *   { 'dir': 'left', 'code': 97 },
+     *   { 'dir': 'right', 'code': 100 }
+     * ];
+     *
+     * _.indexBy(keys, 'dir');
+     * // => { 'left': { 'dir': 'left', 'code': 97 }, 'right': { 'dir': 'right', 'code': 100 } }
+     *
+     * _.indexBy(keys, function(key) { return String.fromCharCode(key.code); });
+     * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } }
+     *
+     * _.indexBy(characters, function(key) { this.fromCharCode(key.code); }, String);
+     * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } }
+     */
+    var indexBy = createAggregator(function(result, value, key) {
+      result[key] = value;
+    });
+
+    /**
+     * Invokes the method named by `methodName` on each element in the `collection`
+     * returning an array of the results of each invoked method. Additional arguments
+     * will be provided to each invoked method. If `methodName` is a function it
+     * will be invoked for, and `this` bound to, each element in the `collection`.
+     *
+     * @static
+     * @memberOf _
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|string} methodName The name of the method to invoke or
+     *  the function invoked per iteration.
+     * @param {...*} [arg] Arguments to invoke the method with.
+     * @returns {Array} Returns a new array of the results of each invoked method.
+     * @example
+     *
+     * _.invoke([[5, 1, 7], [3, 2, 1]], 'sort');
+     * // => [[1, 5, 7], [1, 2, 3]]
+     *
+     * _.invoke([123, 456], String.prototype.split, '');
+     * // => [['1', '2', '3'], ['4', '5', '6']]
+     */
+    function invoke(collection, methodName) {
+      var args = slice(arguments, 2),
+          index = -1,
+          isFunc = typeof methodName == 'function',
+          length = collection ? collection.length : 0,
+          result = Array(typeof length == 'number' ? length : 0);
+
+      forEach(collection, function(value) {
+        result[++index] = (isFunc ? methodName : value[methodName]).apply(value, args);
+      });
+      return result;
+    }
+
+    /**
+     * Creates an array of values by running each element in the collection
+     * through the callback. The callback is bound to `thisArg` and invoked with
+     * three arguments; (value, index|key, collection).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @alias collect
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Array} Returns a new array of the results of each `callback` execution.
+     * @example
+     *
+     * _.map([1, 2, 3], function(num) { return num * 3; });
+     * // => [3, 6, 9]
+     *
+     * _.map({ 'one': 1, 'two': 2, 'three': 3 }, function(num) { return num * 3; });
+     * // => [3, 6, 9] (property order is not guaranteed across environments)
+     *
+     * var characters = [
+     *   { 'name': 'barney', 'age': 36 },
+     *   { 'name': 'fred',   'age': 40 }
+     * ];
+     *
+     * // using "_.pluck" callback shorthand
+     * _.map(characters, 'name');
+     * // => ['barney', 'fred']
+     */
+    function map(collection, callback, thisArg) {
+      var index = -1,
+          length = collection ? collection.length : 0,
+          result = Array(typeof length == 'number' ? length : 0);
+
+      callback = lodash.createCallback(callback, thisArg, 3);
+      if (isArray(collection)) {
+        while (++index < length) {
+          result[index] = callback(collection[index], index, collection);
+        }
+      } else {
+        baseEach(collection, function(value, key, collection) {
+          result[++index] = callback(value, key, collection);
+        });
+      }
+      return result;
+    }
+
+    /**
+     * Retrieves the maximum value of a collection. If the collection is empty or
+     * falsey `-Infinity` is returned. If a callback is provided it will be executed
+     * for each value in the collection to generate the criterion by which the value
+     * is ranked. The callback is bound to `thisArg` and invoked with three
+     * arguments; (value, index, collection).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {*} Returns the maximum value.
+     * @example
+     *
+     * _.max([4, 2, 8, 6]);
+     * // => 8
+     *
+     * var characters = [
+     *   { 'name': 'barney', 'age': 36 },
+     *   { 'name': 'fred',   'age': 40 }
+     * ];
+     *
+     * _.max(characters, function(chr) { return chr.age; });
+     * // => { 'name': 'fred', 'age': 40 };
+     *
+     * // using "_.pluck" callback shorthand
+     * _.max(characters, 'age');
+     * // => { 'name': 'fred', 'age': 40 };
+     */
+    function max(collection, callback, thisArg) {
+      var computed = -Infinity,
+          result = computed;
+
+      // allows working with functions like `_.map` without using
+      // their `index` argument as a callback
+      if (typeof callback != 'function' && thisArg && thisArg[callback] === collection) {
+        callback = null;
+      }
+      if (callback == null && isArray(collection)) {
+        var index = -1,
+            length = collection.length;
+
+        while (++index < length) {
+          var value = collection[index];
+          if (value > result) {
+            result = value;
+          }
+        }
+      } else {
+        callback = (callback == null && isString(collection))
+          ? charAtCallback
+          : lodash.createCallback(callback, thisArg, 3);
+
+        baseEach(collection, function(value, index, collection) {
+          var current = callback(value, index, collection);
+          if (current > computed) {
+            computed = current;
+            result = value;
+          }
+        });
+      }
+      return result;
+    }
+
+    /**
+     * Retrieves the minimum value of a collection. If the collection is empty or
+     * falsey `Infinity` is returned. If a callback is provided it will be executed
+     * for each value in the collection to generate the criterion by which the value
+     * is ranked. The callback is bound to `thisArg` and invoked with three
+     * arguments; (value, index, collection).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {*} Returns the minimum value.
+     * @example
+     *
+     * _.min([4, 2, 8, 6]);
+     * // => 2
+     *
+     * var characters = [
+     *   { 'name': 'barney', 'age': 36 },
+     *   { 'name': 'fred',   'age': 40 }
+     * ];
+     *
+     * _.min(characters, function(chr) { return chr.age; });
+     * // => { 'name': 'barney', 'age': 36 };
+     *
+     * // using "_.pluck" callback shorthand
+     * _.min(characters, 'age');
+     * // => { 'name': 'barney', 'age': 36 };
+     */
+    function min(collection, callback, thisArg) {
+      var computed = Infinity,
+          result = computed;
+
+      // allows working with functions like `_.map` without using
+      // their `index` argument as a callback
+      if (typeof callback != 'function' && thisArg && thisArg[callback] === collection) {
+        callback = null;
+      }
+      if (callback == null && isArray(collection)) {
+        var index = -1,
+            length = collection.length;
+
+        while (++index < length) {
+          var value = collection[index];
+          if (value < result) {
+            result = value;
+          }
+        }
+      } else {
+        callback = (callback == null && isString(collection))
+          ? charAtCallback
+          : lodash.createCallback(callback, thisArg, 3);
+
+        baseEach(collection, function(value, index, collection) {
+          var current = callback(value, index, collection);
+          if (current < computed) {
+            computed = current;
+            result = value;
+          }
+        });
+      }
+      return result;
+    }
+
+    /**
+     * Retrieves the value of a specified property from all elements in the collection.
+     *
+     * @static
+     * @memberOf _
+     * @type Function
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {string} property The name of the property to pluck.
+     * @returns {Array} Returns a new array of property values.
+     * @example
+     *
+     * var characters = [
+     *   { 'name': 'barney', 'age': 36 },
+     *   { 'name': 'fred',   'age': 40 }
+     * ];
+     *
+     * _.pluck(characters, 'name');
+     * // => ['barney', 'fred']
+     */
+    var pluck = map;
+
+    /**
+     * Reduces a collection to a value which is the accumulated result of running
+     * each element in the collection through the callback, where each successive
+     * callback execution consumes the return value of the previous execution. If
+     * `accumulator` is not provided the first element of the collection will be
+     * used as the initial `accumulator` value. The callback is bound to `thisArg`
+     * and invoked with four arguments; (accumulator, value, index|key, collection).
+     *
+     * @static
+     * @memberOf _
+     * @alias foldl, inject
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function} [callback=identity] The function called per iteration.
+     * @param {*} [accumulator] Initial value of the accumulator.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {*} Returns the accumulated value.
+     * @example
+     *
+     * var sum = _.reduce([1, 2, 3], function(sum, num) {
+     *   return sum + num;
+     * });
+     * // => 6
+     *
+     * var mapped = _.reduce({ 'a': 1, 'b': 2, 'c': 3 }, function(result, num, key) {
+     *   result[key] = num * 3;
+     *   return result;
+     * }, {});
+     * // => { 'a': 3, 'b': 6, 'c': 9 }
+     */
+    function reduce(collection, callback, accumulator, thisArg) {
+      var noaccum = arguments.length < 3;
+      callback = lodash.createCallback(callback, thisArg, 4);
+
+      if (isArray(collection)) {
+        var index = -1,
+            length = collection.length;
+
+        if (noaccum) {
+          accumulator = collection[++index];
+        }
+        while (++index < length) {
+          accumulator = callback(accumulator, collection[index], index, collection);
+        }
+      } else {
+        baseEach(collection, function(value, index, collection) {
+          accumulator = noaccum
+            ? (noaccum = false, value)
+            : callback(accumulator, value, index, collection)
+        });
+      }
+      return accumulator;
+    }
+
+    /**
+     * This method is like `_.reduce` except that it iterates over elements
+     * of a `collection` from right to left.
+     *
+     * @static
+     * @memberOf _
+     * @alias foldr
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function} [callback=identity] The function called per iteration.
+     * @param {*} [accumulator] Initial value of the accumulator.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {*} Returns the accumulated value.
+     * @example
+     *
+     * var list = [[0, 1], [2, 3], [4, 5]];
+     * var flat = _.reduceRight(list, function(a, b) { return a.concat(b); }, []);
+     * // => [4, 5, 2, 3, 0, 1]
+     */
+    function reduceRight(collection, callback, accumulator, thisArg) {
+      var noaccum = arguments.length < 3;
+      callback = lodash.createCallback(callback, thisArg, 4);
+      forEachRight(collection, function(value, index, collection) {
+        accumulator = noaccum
+          ? (noaccum = false, value)
+          : callback(accumulator, value, index, collection);
+      });
+      return accumulator;
+    }
+
+    /**
+     * The opposite of `_.filter` this method returns the elements of a
+     * collection that the callback does **not** return truey for.
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Array} Returns a new array of elements that failed the callback check.
+     * @example
+     *
+     * var odds = _.reject([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; });
+     * // => [1, 3, 5]
+     *
+     * var characters = [
+     *   { 'name': 'barney', 'age': 36, 'blocked': false },
+     *   { 'name': 'fred',   'age': 40, 'blocked': true }
+     * ];
+     *
+     * // using "_.pluck" callback shorthand
+     * _.reject(characters, 'blocked');
+     * // => [{ 'name': 'barney', 'age': 36, 'blocked': false }]
+     *
+     * // using "_.where" callback shorthand
+     * _.reject(characters, { 'age': 36 });
+     * // => [{ 'name': 'fred', 'age': 40, 'blocked': true }]
+     */
+    function reject(collection, callback, thisArg) {
+      callback = lodash.createCallback(callback, thisArg, 3);
+      return filter(collection, function(value, index, collection) {
+        return !callback(value, index, collection);
+      });
+    }
+
+    /**
+     * Retrieves a random element or `n` random elements from a collection.
+     *
+     * @static
+     * @memberOf _
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to sample.
+     * @param {number} [n] The number of elements to sample.
+     * @param- {Object} [guard] Allows working with functions like `_.map`
+     *  without using their `index` arguments as `n`.
+     * @returns {Array} Returns the random sample(s) of `collection`.
+     * @example
+     *
+     * _.sample([1, 2, 3, 4]);
+     * // => 2
+     *
+     * _.sample([1, 2, 3, 4], 2);
+     * // => [3, 1]
+     */
+    function sample(collection, n, guard) {
+      if (collection && typeof collection.length != 'number') {
+        collection = values(collection);
+      } else if (support.unindexedChars && isString(collection)) {
+        collection = collection.split('');
+      }
+      if (n == null || guard) {
+        return collection ? collection[baseRandom(0, collection.length - 1)] : undefined;
+      }
+      var result = shuffle(collection);
+      result.length = nativeMin(nativeMax(0, n), result.length);
+      return result;
+    }
+
+    /**
+     * Creates an array of shuffled values, using a version of the Fisher-Yates
+     * shuffle. See http://en.wikipedia.org/wiki/Fisher-Yates_shuffle.
+     *
+     * @static
+     * @memberOf _
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to shuffle.
+     * @returns {Array} Returns a new shuffled collection.
+     * @example
+     *
+     * _.shuffle([1, 2, 3, 4, 5, 6]);
+     * // => [4, 1, 6, 3, 5, 2]
+     */
+    function shuffle(collection) {
+      var index = -1,
+          length = collection ? collection.length : 0,
+          result = Array(typeof length == 'number' ? length : 0);
+
+      forEach(collection, function(value) {
+        var rand = baseRandom(0, ++index);
+        result[index] = result[rand];
+        result[rand] = value;
+      });
+      return result;
+    }
+
+    /**
+     * Gets the size of the `collection` by returning `collection.length` for arrays
+     * and array-like objects or the number of own enumerable properties for objects.
+     *
+     * @static
+     * @memberOf _
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to inspect.
+     * @returns {number} Returns `collection.length` or number of own enumerable properties.
+     * @example
+     *
+     * _.size([1, 2]);
+     * // => 2
+     *
+     * _.size({ 'one': 1, 'two': 2, 'three': 3 });
+     * // => 3
+     *
+     * _.size('pebbles');
+     * // => 7
+     */
+    function size(collection) {
+      var length = collection ? collection.length : 0;
+      return typeof length == 'number' ? length : keys(collection).length;
+    }
+
+    /**
+     * Checks if the callback returns a truey value for **any** element of a
+     * collection. The function returns as soon as it finds a passing value and
+     * does not iterate over the entire collection. The callback is bound to
+     * `thisArg` and invoked with three arguments; (value, index|key, collection).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @alias any
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {boolean} Returns `true` if any element passed the callback check,
+     *  else `false`.
+     * @example
+     *
+     * _.some([null, 0, 'yes', false], Boolean);
+     * // => true
+     *
+     * var characters = [
+     *   { 'name': 'barney', 'age': 36, 'blocked': false },
+     *   { 'name': 'fred',   'age': 40, 'blocked': true }
+     * ];
+     *
+     * // using "_.pluck" callback shorthand
+     * _.some(characters, 'blocked');
+     * // => true
+     *
+     * // using "_.where" callback shorthand
+     * _.some(characters, { 'age': 1 });
+     * // => false
+     */
+    function some(collection, callback, thisArg) {
+      var result;
+      callback = lodash.createCallback(callback, thisArg, 3);
+
+      if (isArray(collection)) {
+        var index = -1,
+            length = collection.length;
+
+        while (++index < length) {
+          if ((result = callback(collection[index], index, collection))) {
+            break;
+          }
+        }
+      } else {
+        baseEach(collection, function(value, index, collection) {
+          return !(result = callback(value, index, collection));
+        });
+      }
+      return !!result;
+    }
+
+    /**
+     * Creates an array of elements, sorted in ascending order by the results of
+     * running each element in a collection through the callback. This method
+     * performs a stable sort, that is, it will preserve the original sort order
+     * of equal elements. The callback is bound to `thisArg` and invoked with
+     * three arguments; (value, index|key, collection).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an array of property names is provided for `callback` the collection
+     * will be sorted by each property value.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Array|Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Array} Returns a new array of sorted elements.
+     * @example
+     *
+     * _.sortBy([1, 2, 3], function(num) { return Math.sin(num); });
+     * // => [3, 1, 2]
+     *
+     * _.sortBy([1, 2, 3], function(num) { return this.sin(num); }, Math);
+     * // => [3, 1, 2]
+     *
+     * var characters = [
+     *   { 'name': 'barney',  'age': 36 },
+     *   { 'name': 'fred',    'age': 40 },
+     *   { 'name': 'barney',  'age': 26 },
+     *   { 'name': 'fred',    'age': 30 }
+     * ];
+     *
+     * // using "_.pluck" callback shorthand
+     * _.map(_.sortBy(characters, 'age'), _.values);
+     * // => [['barney', 26], ['fred', 30], ['barney', 36], ['fred', 40]]
+     *
+     * // sorting by multiple properties
+     * _.map(_.sortBy(characters, ['name', 'age']), _.values);
+     * // = > [['barney', 26], ['barney', 36], ['fred', 30], ['fred', 40]]
+     */
+    function sortBy(collection, callback, thisArg) {
+      var index = -1,
+          isArr = isArray(callback),
+          length = collection ? collection.length : 0,
+          result = Array(typeof length == 'number' ? length : 0);
+
+      if (!isArr) {
+        callback = lodash.createCallback(callback, thisArg, 3);
+      }
+      forEach(collection, function(value, key, collection) {
+        var object = result[++index] = getObject();
+        if (isArr) {
+          object.criteria = map(callback, function(key) { return value[key]; });
+        } else {
+          (object.criteria = getArray())[0] = callback(value, key, collection);
+        }
+        object.index = index;
+        object.value = value;
+      });
+
+      length = result.length;
+      result.sort(compareAscending);
+      while (length--) {
+        var object = result[length];
+        result[length] = object.value;
+        if (!isArr) {
+          releaseArray(object.criteria);
+        }
+        releaseObject(object);
+      }
+      return result;
+    }
+
+    /**
+     * Converts the `collection` to an array.
+     *
+     * @static
+     * @memberOf _
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to convert.
+     * @returns {Array} Returns the new converted array.
+     * @example
+     *
+     * (function() { return _.toArray(arguments).slice(1); })(1, 2, 3, 4);
+     * // => [2, 3, 4]
+     */
+    function toArray(collection) {
+      if (collection && typeof collection.length == 'number') {
+        return (support.unindexedChars && isString(collection))
+          ? collection.split('')
+          : slice(collection);
+      }
+      return values(collection);
+    }
+
+    /**
+     * Performs a deep comparison of each element in a `collection` to the given
+     * `properties` object, returning an array of all elements that have equivalent
+     * property values.
+     *
+     * @static
+     * @memberOf _
+     * @type Function
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Object} props The object of property values to filter by.
+     * @returns {Array} Returns a new array of elements that have the given properties.
+     * @example
+     *
+     * var characters = [
+     *   { 'name': 'barney', 'age': 36, 'pets': ['hoppy'] },
+     *   { 'name': 'fred',   'age': 40, 'pets': ['baby puss', 'dino'] }
+     * ];
+     *
+     * _.where(characters, { 'age': 36 });
+     * // => [{ 'name': 'barney', 'age': 36, 'pets': ['hoppy'] }]
+     *
+     * _.where(characters, { 'pets': ['dino'] });
+     * // => [{ 'name': 'fred', 'age': 40, 'pets': ['baby puss', 'dino'] }]
+     */
+    var where = filter;
+
+    /*--------------------------------------------------------------------------*/
+
+    /**
+     * Creates an array with all falsey values removed. The values `false`, `null`,
+     * `0`, `""`, `undefined`, and `NaN` are all falsey.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {Array} array The array to compact.
+     * @returns {Array} Returns a new array of filtered values.
+     * @example
+     *
+     * _.compact([0, 1, false, 2, '', 3]);
+     * // => [1, 2, 3]
+     */
+    function compact(array) {
+      var index = -1,
+          length = array ? array.length : 0,
+          result = [];
+
+      while (++index < length) {
+        var value = array[index];
+        if (value) {
+          result.push(value);
+        }
+      }
+      return result;
+    }
+
+    /**
+     * Creates an array excluding all values of the provided arrays using strict
+     * equality for comparisons, i.e. `===`.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {Array} array The array to process.
+     * @param {...Array} [values] The arrays of values to exclude.
+     * @returns {Array} Returns a new array of filtered values.
+     * @example
+     *
+     * _.difference([1, 2, 3, 4, 5], [5, 2, 10]);
+     * // => [1, 3, 4]
+     */
+    function difference(array) {
+      return baseDifference(array, baseFlatten(arguments, true, true, 1));
+    }
+
+    /**
+     * This method is like `_.find` except that it returns the index of the first
+     * element that passes the callback check, instead of the element itself.
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {Array} array The array to search.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {number} Returns the index of the found element, else `-1`.
+     * @example
+     *
+     * var characters = [
+     *   { 'name': 'barney',  'age': 36, 'blocked': false },
+     *   { 'name': 'fred',    'age': 40, 'blocked': true },
+     *   { 'name': 'pebbles', 'age': 1,  'blocked': false }
+     * ];
+     *
+     * _.findIndex(characters, function(chr) {
+     *   return chr.age < 20;
+     * });
+     * // => 2
+     *
+     * // using "_.where" callback shorthand
+     * _.findIndex(characters, { 'age': 36 });
+     * // => 0
+     *
+     * // using "_.pluck" callback shorthand
+     * _.findIndex(characters, 'blocked');
+     * // => 1
+     */
+    function findIndex(array, callback, thisArg) {
+      var index = -1,
+          length = array ? array.length : 0;
+
+      callback = lodash.createCallback(callback, thisArg, 3);
+      while (++index < length) {
+        if (callback(array[index], index, array)) {
+          return index;
+        }
+      }
+      return -1;
+    }
+
+    /**
+     * This method is like `_.findIndex` except that it iterates over elements
+     * of a `collection` from right to left.
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {Array} array The array to search.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {number} Returns the index of the found element, else `-1`.
+     * @example
+     *
+     * var characters = [
+     *   { 'name': 'barney',  'age': 36, 'blocked': true },
+     *   { 'name': 'fred',    'age': 40, 'blocked': false },
+     *   { 'name': 'pebbles', 'age': 1,  'blocked': true }
+     * ];
+     *
+     * _.findLastIndex(characters, function(chr) {
+     *   return chr.age > 30;
+     * });
+     * // => 1
+     *
+     * // using "_.where" callback shorthand
+     * _.findLastIndex(characters, { 'age': 36 });
+     * // => 0
+     *
+     * // using "_.pluck" callback shorthand
+     * _.findLastIndex(characters, 'blocked');
+     * // => 2
+     */
+    function findLastIndex(array, callback, thisArg) {
+      var length = array ? array.length : 0;
+      callback = lodash.createCallback(callback, thisArg, 3);
+      while (length--) {
+        if (callback(array[length], length, array)) {
+          return length;
+        }
+      }
+      return -1;
+    }
+
+    /**
+     * Gets the first element or first `n` elements of an array. If a callback
+     * is provided elements at the beginning of the array are returned as long
+     * as the callback returns truey. The callback is bound to `thisArg` and
+     * invoked with three arguments; (value, index, array).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @alias head, take
+     * @category Arrays
+     * @param {Array} array The array to query.
+     * @param {Function|Object|number|string} [callback] The function called
+     *  per element or the number of elements to return. If a property name or
+     *  object is provided it will be used to create a "_.pluck" or "_.where"
+     *  style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {*} Returns the first element(s) of `array`.
+     * @example
+     *
+     * _.first([1, 2, 3]);
+     * // => 1
+     *
+     * _.first([1, 2, 3], 2);
+     * // => [1, 2]
+     *
+     * _.first([1, 2, 3], function(num) {
+     *   return num < 3;
+     * });
+     * // => [1, 2]
+     *
+     * var characters = [
+     *   { 'name': 'barney',  'blocked': true,  'employer': 'slate' },
+     *   { 'name': 'fred',    'blocked': false, 'employer': 'slate' },
+     *   { 'name': 'pebbles', 'blocked': true,  'employer': 'na' }
+     * ];
+     *
+     * // using "_.pluck" callback shorthand
+     * _.first(characters, 'blocked');
+     * // => [{ 'name': 'barney', 'blocked': true, 'employer': 'slate' }]
+     *
+     * // using "_.where" callback shorthand
+     * _.pluck(_.first(characters, { 'employer': 'slate' }), 'name');
+     * // => ['barney', 'fred']
+     */
+    function first(array, callback, thisArg) {
+      var n = 0,
+          length = array ? array.length : 0;
+
+      if (typeof callback != 'number' && callback != null) {
+        var index = -1;
+        callback = lodash.createCallback(callback, thisArg, 3);
+        while (++index < length && callback(array[index], index, array)) {
+          n++;
+        }
+      } else {
+        n = callback;
+        if (n == null || thisArg) {
+          return array ? array[0] : undefined;
+        }
+      }
+      return slice(array, 0, nativeMin(nativeMax(0, n), length));
+    }
+
+    /**
+     * Flattens a nested array (the nesting can be to any depth). If `isShallow`
+     * is truey, the array will only be flattened a single level. If a callback
+     * is provided each element of the array is passed through the callback before
+     * flattening. The callback is bound to `thisArg` and invoked with three
+     * arguments; (value, index, array).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {Array} array The array to flatten.
+     * @param {boolean} [isShallow=false] A flag to restrict flattening to a single level.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Array} Returns a new flattened array.
+     * @example
+     *
+     * _.flatten([1, [2], [3, [[4]]]]);
+     * // => [1, 2, 3, 4];
+     *
+     * _.flatten([1, [2], [3, [[4]]]], true);
+     * // => [1, 2, 3, [[4]]];
+     *
+     * var characters = [
+     *   { 'name': 'barney', 'age': 30, 'pets': ['hoppy'] },
+     *   { 'name': 'fred',   'age': 40, 'pets': ['baby puss', 'dino'] }
+     * ];
+     *
+     * // using "_.pluck" callback shorthand
+     * _.flatten(characters, 'pets');
+     * // => ['hoppy', 'baby puss', 'dino']
+     */
+    function flatten(array, isShallow, callback, thisArg) {
+      // juggle arguments
+      if (typeof isShallow != 'boolean' && isShallow != null) {
+        thisArg = callback;
+        callback = (typeof isShallow != 'function' && thisArg && thisArg[isShallow] === array) ? null : isShallow;
+        isShallow = false;
+      }
+      if (callback != null) {
+        array = map(array, callback, thisArg);
+      }
+      return baseFlatten(array, isShallow);
+    }
+
+    /**
+     * Gets the index at which the first occurrence of `value` is found using
+     * strict equality for comparisons, i.e. `===`. If the array is already sorted
+     * providing `true` for `fromIndex` will run a faster binary search.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {Array} array The array to search.
+     * @param {*} value The value to search for.
+     * @param {boolean|number} [fromIndex=0] The index to search from or `true`
+     *  to perform a binary search on a sorted array.
+     * @returns {number} Returns the index of the matched value or `-1`.
+     * @example
+     *
+     * _.indexOf([1, 2, 3, 1, 2, 3], 2);
+     * // => 1
+     *
+     * _.indexOf([1, 2, 3, 1, 2, 3], 2, 3);
+     * // => 4
+     *
+     * _.indexOf([1, 1, 2, 2, 3, 3], 2, true);
+     * // => 2
+     */
+    function indexOf(array, value, fromIndex) {
+      if (typeof fromIndex == 'number') {
+        var length = array ? array.length : 0;
+        fromIndex = (fromIndex < 0 ? nativeMax(0, length + fromIndex) : fromIndex || 0);
+      } else if (fromIndex) {
+        var index = sortedIndex(array, value);
+        return array[index] === value ? index : -1;
+      }
+      return baseIndexOf(array, value, fromIndex);
+    }
+
+    /**
+     * Gets all but the last element or last `n` elements of an array. If a
+     * callback is provided elements at the end of the array are excluded from
+     * the result as long as the callback returns truey. The callback is bound
+     * to `thisArg` and invoked with three arguments; (value, index, array).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {Array} array The array to query.
+     * @param {Function|Object|number|string} [callback=1] The function called
+     *  per element or the number of elements to exclude. If a property name or
+     *  object is provided it will be used to create a "_.pluck" or "_.where"
+     *  style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Array} Returns a slice of `array`.
+     * @example
+     *
+     * _.initial([1, 2, 3]);
+     * // => [1, 2]
+     *
+     * _.initial([1, 2, 3], 2);
+     * // => [1]
+     *
+     * _.initial([1, 2, 3], function(num) {
+     *   return num > 1;
+     * });
+     * // => [1]
+     *
+     * var characters = [
+     *   { 'name': 'barney',  'blocked': false, 'employer': 'slate' },
+     *   { 'name': 'fred',    'blocked': true,  'employer': 'slate' },
+     *   { 'name': 'pebbles', 'blocked': true,  'employer': 'na' }
+     * ];
+     *
+     * // using "_.pluck" callback shorthand
+     * _.initial(characters, 'blocked');
+     * // => [{ 'name': 'barney',  'blocked': false, 'employer': 'slate' }]
+     *
+     * // using "_.where" callback shorthand
+     * _.pluck(_.initial(characters, { 'employer': 'na' }), 'name');
+     * // => ['barney', 'fred']
+     */
+    function initial(array, callback, thisArg) {
+      var n = 0,
+          length = array ? array.length : 0;
+
+      if (typeof callback != 'number' && callback != null) {
+        var index = length;
+        callback = lodash.createCallback(callback, thisArg, 3);
+        while (index-- && callback(array[index], index, array)) {
+          n++;
+        }
+      } else {
+        n = (callback == null || thisArg) ? 1 : callback || n;
+      }
+      return slice(array, 0, nativeMin(nativeMax(0, length - n), length));
+    }
+
+    /**
+     * Creates an array of unique values present in all provided arrays using
+     * strict equality for comparisons, i.e. `===`.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {...Array} [array] The arrays to inspect.
+     * @returns {Array} Returns an array of shared values.
+     * @example
+     *
+     * _.intersection([1, 2, 3], [5, 2, 1, 4], [2, 1]);
+     * // => [1, 2]
+     */
+    function intersection() {
+      var args = [],
+          argsIndex = -1,
+          argsLength = arguments.length,
+          caches = getArray(),
+          indexOf = getIndexOf(),
+          trustIndexOf = indexOf === baseIndexOf,
+          seen = getArray();
+
+      while (++argsIndex < argsLength) {
+        var value = arguments[argsIndex];
+        if (isArray(value) || isArguments(value)) {
+          args.push(value);
+          caches.push(trustIndexOf && value.length >= largeArraySize &&
+            createCache(argsIndex ? args[argsIndex] : seen));
+        }
+      }
+      var array = args[0],
+          index = -1,
+          length = array ? array.length : 0,
+          result = [];
+
+      outer:
+      while (++index < length) {
+        var cache = caches[0];
+        value = array[index];
+
+        if ((cache ? cacheIndexOf(cache, value) : indexOf(seen, value)) < 0) {
+          argsIndex = argsLength;
+          (cache || seen).push(value);
+          while (--argsIndex) {
+            cache = caches[argsIndex];
+            if ((cache ? cacheIndexOf(cache, value) : indexOf(args[argsIndex], value)) < 0) {
+              continue outer;
+            }
+          }
+          result.push(value);
+        }
+      }
+      while (argsLength--) {
+        cache = caches[argsLength];
+        if (cache) {
+          releaseObject(cache);
+        }
+      }
+      releaseArray(caches);
+      releaseArray(seen);
+      return result;
+    }
+
+    /**
+     * Gets the last element or last `n` elements of an array. If a callback is
+     * provided elements at the end of the array are returned as long as the
+     * callback returns truey. The callback is bound to `thisArg` and invoked
+     * with three arguments; (value, index, array).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {Array} array The array to query.
+     * @param {Function|Object|number|string} [callback] The function called
+     *  per element or the number of elements to return. If a property name or
+     *  object is provided it will be used to create a "_.pluck" or "_.where"
+     *  style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {*} Returns the last element(s) of `array`.
+     * @example
+     *
+     * _.last([1, 2, 3]);
+     * // => 3
+     *
+     * _.last([1, 2, 3], 2);
+     * // => [2, 3]
+     *
+     * _.last([1, 2, 3], function(num) {
+     *   return num > 1;
+     * });
+     * // => [2, 3]
+     *
+     * var characters = [
+     *   { 'name': 'barney',  'blocked': false, 'employer': 'slate' },
+     *   { 'name': 'fred',    'blocked': true,  'employer': 'slate' },
+     *   { 'name': 'pebbles', 'blocked': true,  'employer': 'na' }
+     * ];
+     *
+     * // using "_.pluck" callback shorthand
+     * _.pluck(_.last(characters, 'blocked'), 'name');
+     * // => ['fred', 'pebbles']
+     *
+     * // using "_.where" callback shorthand
+     * _.last(characters, { 'employer': 'na' });
+     * // => [{ 'name': 'pebbles', 'blocked': true, 'employer': 'na' }]
+     */
+    function last(array, callback, thisArg) {
+      var n = 0,
+          length = array ? array.length : 0;
+
+      if (typeof callback != 'number' && callback != null) {
+        var index = length;
+        callback = lodash.createCallback(callback, thisArg, 3);
+        while (index-- && callback(array[index], index, array)) {
+          n++;
+        }
+      } else {
+        n = callback;
+        if (n == null || thisArg) {
+          return array ? array[length - 1] : undefined;
+        }
+      }
+      return slice(array, nativeMax(0, length - n));
+    }
+
+    /**
+     * Gets the index at which the last occurrence of `value` is found using strict
+     * equality for comparisons, i.e. `===`. If `fromIndex` is negative, it is used
+     * as the offset from the end of the collection.
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {Array} array The array to search.
+     * @param {*} value The value to search for.
+     * @param {number} [fromIndex=array.length-1] The index to search from.
+     * @returns {number} Returns the index of the matched value or `-1`.
+     * @example
+     *
+     * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2);
+     * // => 4
+     *
+     * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2, 3);
+     * // => 1
+     */
+    function lastIndexOf(array, value, fromIndex) {
+      var index = array ? array.length : 0;
+      if (typeof fromIndex == 'number') {
+        index = (fromIndex < 0 ? nativeMax(0, index + fromIndex) : nativeMin(fromIndex, index - 1)) + 1;
+      }
+      while (index--) {
+        if (array[index] === value) {
+          return index;
+        }
+      }
+      return -1;
+    }
+
+    /**
+     * Removes all provided values from the given array using strict equality for
+     * comparisons, i.e. `===`.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {Array} array The array to modify.
+     * @param {...*} [value] The values to remove.
+     * @returns {Array} Returns `array`.
+     * @example
+     *
+     * var array = [1, 2, 3, 1, 2, 3];
+     * _.pull(array, 2, 3);
+     * console.log(array);
+     * // => [1, 1]
+     */
+    function pull(array) {
+      var args = arguments,
+          argsIndex = 0,
+          argsLength = args.length,
+          length = array ? array.length : 0;
+
+      while (++argsIndex < argsLength) {
+        var index = -1,
+            value = args[argsIndex];
+        while (++index < length) {
+          if (array[index] === value) {
+            splice.call(array, index--, 1);
+            length--;
+          }
+        }
+      }
+      return array;
+    }
+
+    /**
+     * Creates an array of numbers (positive and/or negative) progressing from
+     * `start` up to but not including `end`. If `start` is less than `stop` a
+     * zero-length range is created unless a negative `step` is specified.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {number} [start=0] The start of the range.
+     * @param {number} end The end of the range.
+     * @param {number} [step=1] The value to increment or decrement by.
+     * @returns {Array} Returns a new range array.
+     * @example
+     *
+     * _.range(4);
+     * // => [0, 1, 2, 3]
+     *
+     * _.range(1, 5);
+     * // => [1, 2, 3, 4]
+     *
+     * _.range(0, 20, 5);
+     * // => [0, 5, 10, 15]
+     *
+     * _.range(0, -4, -1);
+     * // => [0, -1, -2, -3]
+     *
+     * _.range(1, 4, 0);
+     * // => [1, 1, 1]
+     *
+     * _.range(0);
+     * // => []
+     */
+    function range(start, end, step) {
+      start = +start || 0;
+      step = typeof step == 'number' ? step : (+step || 1);
+
+      if (end == null) {
+        end = start;
+        start = 0;
+      }
+      // use `Array(length)` so engines like Chakra and V8 avoid slower modes
+      // http://youtu.be/XAqIpGU8ZZk#t=17m25s
+      var index = -1,
+          length = nativeMax(0, ceil((end - start) / (step || 1))),
+          result = Array(length);
+
+      while (++index < length) {
+        result[index] = start;
+        start += step;
+      }
+      return result;
+    }
+
+    /**
+     * Removes all elements from an array that the callback returns truey for
+     * and returns an array of removed elements. The callback is bound to `thisArg`
+     * and invoked with three arguments; (value, index, array).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {Array} array The array to modify.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Array} Returns a new array of removed elements.
+     * @example
+     *
+     * var array = [1, 2, 3, 4, 5, 6];
+     * var evens = _.remove(array, function(num) { return num % 2 == 0; });
+     *
+     * console.log(array);
+     * // => [1, 3, 5]
+     *
+     * console.log(evens);
+     * // => [2, 4, 6]
+     */
+    function remove(array, callback, thisArg) {
+      var index = -1,
+          length = array ? array.length : 0,
+          result = [];
+
+      callback = lodash.createCallback(callback, thisArg, 3);
+      while (++index < length) {
+        var value = array[index];
+        if (callback(value, index, array)) {
+          result.push(value);
+          splice.call(array, index--, 1);
+          length--;
+        }
+      }
+      return result;
+    }
+
+    /**
+     * The opposite of `_.initial` this method gets all but the first element or
+     * first `n` elements of an array. If a callback function is provided elements
+     * at the beginning of the array are excluded from the result as long as the
+     * callback returns truey. The callback is bound to `thisArg` and invoked
+     * with three arguments; (value, index, array).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @alias drop, tail
+     * @category Arrays
+     * @param {Array} array The array to query.
+     * @param {Function|Object|number|string} [callback=1] The function called
+     *  per element or the number of elements to exclude. If a property name or
+     *  object is provided it will be used to create a "_.pluck" or "_.where"
+     *  style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Array} Returns a slice of `array`.
+     * @example
+     *
+     * _.rest([1, 2, 3]);
+     * // => [2, 3]
+     *
+     * _.rest([1, 2, 3], 2);
+     * // => [3]
+     *
+     * _.rest([1, 2, 3], function(num) {
+     *   return num < 3;
+     * });
+     * // => [3]
+     *
+     * var characters = [
+     *   { 'name': 'barney',  'blocked': true,  'employer': 'slate' },
+     *   { 'name': 'fred',    'blocked': false,  'employer': 'slate' },
+     *   { 'name': 'pebbles', 'blocked': true, 'employer': 'na' }
+     * ];
+     *
+     * // using "_.pluck" callback shorthand
+     * _.pluck(_.rest(characters, 'blocked'), 'name');
+     * // => ['fred', 'pebbles']
+     *
+     * // using "_.where" callback shorthand
+     * _.rest(characters, { 'employer': 'slate' });
+     * // => [{ 'name': 'pebbles', 'blocked': true, 'employer': 'na' }]
+     */
+    function rest(array, callback, thisArg) {
+      if (typeof callback != 'number' && callback != null) {
+        var n = 0,
+            index = -1,
+            length = array ? array.length : 0;
+
+        callback = lodash.createCallback(callback, thisArg, 3);
+        while (++index < length && callback(array[index], index, array)) {
+          n++;
+        }
+      } else {
+        n = (callback == null || thisArg) ? 1 : nativeMax(0, callback);
+      }
+      return slice(array, n);
+    }
+
+    /**
+     * Uses a binary search to determine the smallest index at which a value
+     * should be inserted into a given sorted array in order to maintain the sort
+     * order of the array. If a callback is provided it will be executed for
+     * `value` and each element of `array` to compute their sort ranking. The
+     * callback is bound to `thisArg` and invoked with one argument; (value).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {Array} array The array to inspect.
+     * @param {*} value The value to evaluate.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {number} Returns the index at which `value` should be inserted
+     *  into `array`.
+     * @example
+     *
+     * _.sortedIndex([20, 30, 50], 40);
+     * // => 2
+     *
+     * // using "_.pluck" callback shorthand
+     * _.sortedIndex([{ 'x': 20 }, { 'x': 30 }, { 'x': 50 }], { 'x': 40 }, 'x');
+     * // => 2
+     *
+     * var dict = {
+     *   'wordToNumber': { 'twenty': 20, 'thirty': 30, 'fourty': 40, 'fifty': 50 }
+     * };
+     *
+     * _.sortedIndex(['twenty', 'thirty', 'fifty'], 'fourty', function(word) {
+     *   return dict.wordToNumber[word];
+     * });
+     * // => 2
+     *
+     * _.sortedIndex(['twenty', 'thirty', 'fifty'], 'fourty', function(word) {
+     *   return this.wordToNumber[word];
+     * }, dict);
+     * // => 2
+     */
+    function sortedIndex(array, value, callback, thisArg) {
+      var low = 0,
+          high = array ? array.length : low;
+
+      // explicitly reference `identity` for better inlining in Firefox
+      callback = callback ? lodash.createCallback(callback, thisArg, 1) : identity;
+      value = callback(value);
+
+      while (low < high) {
+        var mid = (low + high) >>> 1;
+        (callback(array[mid]) < value)
+          ? low = mid + 1
+          : high = mid;
+      }
+      return low;
+    }
+
+    /**
+     * Creates an array of unique values, in order, of the provided arrays using
+     * strict equality for comparisons, i.e. `===`.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {...Array} [array] The arrays to inspect.
+     * @returns {Array} Returns an array of combined values.
+     * @example
+     *
+     * _.union([1, 2, 3], [5, 2, 1, 4], [2, 1]);
+     * // => [1, 2, 3, 5, 4]
+     */
+    function union() {
+      return baseUniq(baseFlatten(arguments, true, true));
+    }
+
+    /**
+     * Creates a duplicate-value-free version of an array using strict equality
+     * for comparisons, i.e. `===`. If the array is sorted, providing
+     * `true` for `isSorted` will use a faster algorithm. If a callback is provided
+     * each element of `array` is passed through the callback before uniqueness
+     * is computed. The callback is bound to `thisArg` and invoked with three
+     * arguments; (value, index, array).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @alias unique
+     * @category Arrays
+     * @param {Array} array The array to process.
+     * @param {boolean} [isSorted=false] A flag to indicate that `array` is sorted.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Array} Returns a duplicate-value-free array.
+     * @example
+     *
+     * _.uniq([1, 2, 1, 3, 1]);
+     * // => [1, 2, 3]
+     *
+     * _.uniq([1, 1, 2, 2, 3], true);
+     * // => [1, 2, 3]
+     *
+     * _.uniq(['A', 'b', 'C', 'a', 'B', 'c'], function(letter) { return letter.toLowerCase(); });
+     * // => ['A', 'b', 'C']
+     *
+     * _.uniq([1, 2.5, 3, 1.5, 2, 3.5], function(num) { return this.floor(num); }, Math);
+     * // => [1, 2.5, 3]
+     *
+     * // using "_.pluck" callback shorthand
+     * _.uniq([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x');
+     * // => [{ 'x': 1 }, { 'x': 2 }]
+     */
+    function uniq(array, isSorted, callback, thisArg) {
+      // juggle arguments
+      if (typeof isSorted != 'boolean' && isSorted != null) {
+        thisArg = callback;
+        callback = (typeof isSorted != 'function' && thisArg && thisArg[isSorted] === array) ? null : isSorted;
+        isSorted = false;
+      }
+      if (callback != null) {
+        callback = lodash.createCallback(callback, thisArg, 3);
+      }
+      return baseUniq(array, isSorted, callback);
+    }
+
+    /**
+     * Creates an array excluding all provided values using strict equality for
+     * comparisons, i.e. `===`.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {Array} array The array to filter.
+     * @param {...*} [value] The values to exclude.
+     * @returns {Array} Returns a new array of filtered values.
+     * @example
+     *
+     * _.without([1, 2, 1, 0, 3, 1, 4], 0, 1);
+     * // => [2, 3, 4]
+     */
+    function without(array) {
+      return baseDifference(array, slice(arguments, 1));
+    }
+
+    /**
+     * Creates an array that is the symmetric difference of the provided arrays.
+     * See http://en.wikipedia.org/wiki/Symmetric_difference.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {...Array} [array] The arrays to inspect.
+     * @returns {Array} Returns an array of values.
+     * @example
+     *
+     * _.xor([1, 2, 3], [5, 2, 1, 4]);
+     * // => [3, 5, 4]
+     *
+     * _.xor([1, 2, 5], [2, 3, 5], [3, 4, 5]);
+     * // => [1, 4, 5]
+     */
+    function xor() {
+      var index = -1,
+          length = arguments.length;
+
+      while (++index < length) {
+        var array = arguments[index];
+        if (isArray(array) || isArguments(array)) {
+          var result = result
+            ? baseUniq(baseDifference(result, array).concat(baseDifference(array, result)))
+            : array;
+        }
+      }
+      return result || [];
+    }
+
+    /**
+     * Creates an array of grouped elements, the first of which contains the first
+     * elements of the given arrays, the second of which contains the second
+     * elements of the given arrays, and so on.
+     *
+     * @static
+     * @memberOf _
+     * @alias unzip
+     * @category Arrays
+     * @param {...Array} [array] Arrays to process.
+     * @returns {Array} Returns a new array of grouped elements.
+     * @example
+     *
+     * _.zip(['fred', 'barney'], [30, 40], [true, false]);
+     * // => [['fred', 30, true], ['barney', 40, false]]
+     */
+    function zip() {
+      var array = arguments.length > 1 ? arguments : arguments[0],
+          index = -1,
+          length = array ? max(pluck(array, 'length')) : 0,
+          result = Array(length < 0 ? 0 : length);
+
+      while (++index < length) {
+        result[index] = pluck(array, index);
+      }
+      return result;
+    }
+
+    /**
+     * Creates an object composed from arrays of `keys` and `values`. Provide
+     * either a single two dimensional array, i.e. `[[key1, value1], [key2, value2]]`
+     * or two arrays, one of `keys` and one of corresponding `values`.
+     *
+     * @static
+     * @memberOf _
+     * @alias object
+     * @category Arrays
+     * @param {Array} keys The array of keys.
+     * @param {Array} [values=[]] The array of values.
+     * @returns {Object} Returns an object composed of the given keys and
+     *  corresponding values.
+     * @example
+     *
+     * _.zipObject(['fred', 'barney'], [30, 40]);
+     * // => { 'fred': 30, 'barney': 40 }
+     */
+    function zipObject(keys, values) {
+      var index = -1,
+          length = keys ? keys.length : 0,
+          result = {};
+
+      if (!values && length && !isArray(keys[0])) {
+        values = [];
+      }
+      while (++index < length) {
+        var key = keys[index];
+        if (values) {
+          result[key] = values[index];
+        } else if (key) {
+          result[key[0]] = key[1];
+        }
+      }
+      return result;
+    }
+
+    /*--------------------------------------------------------------------------*/
+
+    /**
+     * Creates a function that executes `func`, with  the `this` binding and
+     * arguments of the created function, only after being called `n` times.
+     *
+     * @static
+     * @memberOf _
+     * @category Functions
+     * @param {number} n The number of times the function must be called before
+     *  `func` is executed.
+     * @param {Function} func The function to restrict.
+     * @returns {Function} Returns the new restricted function.
+     * @example
+     *
+     * var saves = ['profile', 'settings'];
+     *
+     * var done = _.after(saves.length, function() {
+     *   console.log('Done saving!');
+     * });
+     *
+     * _.forEach(saves, function(type) {
+     *   asyncSave({ 'type': type, 'complete': done });
+     * });
+     * // => logs 'Done saving!', after all saves have completed
+     */
+    function after(n, func) {
+      if (!isFunction(func)) {
+        throw new TypeError;
+      }
+      return function() {
+        if (--n < 1) {
+          return func.apply(this, arguments);
+        }
+      };
+    }
+
+    /**
+     * Creates a function that, when called, invokes `func` with the `this`
+     * binding of `thisArg` and prepends any additional `bind` arguments to those
+     * provided to the bound function.
+     *
+     * @static
+     * @memberOf _
+     * @category Functions
+     * @param {Function} func The function to bind.
+     * @param {*} [thisArg] The `this` binding of `func`.
+     * @param {...*} [arg] Arguments to be partially applied.
+     * @returns {Function} Returns the new bound function.
+     * @example
+     *
+     * var func = function(greeting) {
+     *   return greeting + ' ' + this.name;
+     * };
+     *
+     * func = _.bind(func, { 'name': 'fred' }, 'hi');
+     * func();
+     * // => 'hi fred'
+     */
+    function bind(func, thisArg) {
+      return arguments.length > 2
+        ? createWrapper(func, 17, slice(arguments, 2), null, thisArg)
+        : createWrapper(func, 1, null, null, thisArg);
+    }
+
+    /**
+     * Binds methods of an object to the object itself, overwriting the existing
+     * method. Method names may be specified as individual arguments or as arrays
+     * of method names. If no method names are provided all the function properties
+     * of `object` will be bound.
+     *
+     * @static
+     * @memberOf _
+     * @category Functions
+     * @param {Object} object The object to bind and assign the bound methods to.
+     * @param {...string} [methodName] The object method names to
+     *  bind, specified as individual method names or arrays of method names.
+     * @returns {Object} Returns `object`.
+     * @example
+     *
+     * var view = {
+     *   'label': 'docs',
+     *   'onClick': function() { console.log('clicked ' + this.label); }
+     * };
+     *
+     * _.bindAll(view);
+     * jQuery('#docs').on('click', view.onClick);
+     * // => logs 'clicked docs', when the button is clicked
+     */
+    function bindAll(object) {
+      var funcs = arguments.length > 1 ? baseFlatten(arguments, true, false, 1) : functions(object),
+          index = -1,
+          length = funcs.length;
+
+      while (++index < length) {
+        var key = funcs[index];
+        object[key] = createWrapper(object[key], 1, null, null, object);
+      }
+      return object;
+    }
+
+    /**
+     * Creates a function that, when called, invokes the method at `object[key]`
+     * and prepends any additional `bindKey` arguments to those provided to the bound
+     * function. This method differs from `_.bind` by allowing bound functions to
+     * reference methods that will be redefined or don't yet exist.
+     * See http://michaux.ca/articles/lazy-function-definition-pattern.
+     *
+     * @static
+     * @memberOf _
+     * @category Functions
+     * @param {Object} object The object the method belongs to.
+     * @param {string} key The key of the method.
+     * @param {...*} [arg] Arguments to be partially applied.
+     * @returns {Function} Returns the new bound function.
+     * @example
+     *
+     * var object = {
+     *   'name': 'fred',
+     *   'greet': function(greeting) {
+     *     return greeting + ' ' + this.name;
+     *   }
+     * };
+     *
+     * var func = _.bindKey(object, 'greet', 'hi');
+     * func();
+     * // => 'hi fred'
+     *
+     * object.greet = function(greeting) {
+     *   return greeting + 'ya ' + this.name + '!';
+     * };
+     *
+     * func();
+     * // => 'hiya fred!'
+     */
+    function bindKey(object, key) {
+      return arguments.length > 2
+        ? createWrapper(key, 19, slice(arguments, 2), null, object)
+        : createWrapper(key, 3, null, null, object);
+    }
+
+    /**
+     * Creates a function that is the composition of the provided functions,
+     * where each function consumes the return value of the function that follows.
+     * For example, composing the functions `f()`, `g()`, and `h()` produces `f(g(h()))`.
+     * Each function is executed with the `this` binding of the composed function.
+     *
+     * @static
+     * @memberOf _
+     * @category Functions
+     * @param {...Function} [func] Functions to compose.
+     * @returns {Function} Returns the new composed function.
+     * @example
+     *
+     * var realNameMap = {
+     *   'pebbles': 'penelope'
+     * };
+     *
+     * var format = function(name) {
+     *   name = realNameMap[name.toLowerCase()] || name;
+     *   return name.charAt(0).toUpperCase() + name.slice(1).toLowerCase();
+     * };
+     *
+     * var greet = function(formatted) {
+     *   return 'Hiya ' + formatted + '!';
+     * };
+     *
+     * var welcome = _.compose(greet, format);
+     * welcome('pebbles');
+     * // => 'Hiya Penelope!'
+     */
+    function compose() {
+      var funcs = arguments,
+          length = funcs.length;
+
+      while (length--) {
+        if (!isFunction(funcs[length])) {
+          throw new TypeError;
+        }
+      }
+      return function() {
+        var args = arguments,
+            length = funcs.length;
+
+        while (length--) {
+          args = [funcs[length].apply(this, args)];
+        }
+        return args[0];
+      };
+    }
+
+    /**
+     * Creates a function which accepts one or more arguments of `func` that when
+     * invoked either executes `func` returning its result, if all `func` arguments
+     * have been provided, or returns a function that accepts one or more of the
+     * remaining `func` arguments, and so on. The arity of `func` can be specified
+     * if `func.length` is not sufficient.
+     *
+     * @static
+     * @memberOf _
+     * @category Functions
+     * @param {Function} func The function to curry.
+     * @param {number} [arity=func.length] The arity of `func`.
+     * @returns {Function} Returns the new curried function.
+     * @example
+     *
+     * var curried = _.curry(function(a, b, c) {
+     *   console.log(a + b + c);
+     * });
+     *
+     * curried(1)(2)(3);
+     * // => 6
+     *
+     * curried(1, 2)(3);
+     * // => 6
+     *
+     * curried(1, 2, 3);
+     * // => 6
+     */
+    function curry(func, arity) {
+      arity = typeof arity == 'number' ? arity : (+arity || func.length);
+      return createWrapper(func, 4, null, null, null, arity);
+    }
+
+    /**
+     * Creates a function that will delay the execution of `func` until after
+     * `wait` milliseconds have elapsed since the last time it was invoked.
+     * Provide an options object to indicate that `func` should be invoked on
+     * the leading and/or trailing edge of the `wait` timeout. Subsequent calls
+     * to the debounced function will return the result of the last `func` call.
+     *
+     * Note: If `leading` and `trailing` options are `true` `func` will be called
+     * on the trailing edge of the timeout only if the the debounced function is
+     * invoked more than once during the `wait` timeout.
+     *
+     * @static
+     * @memberOf _
+     * @category Functions
+     * @param {Function} func The function to debounce.
+     * @param {number} wait The number of milliseconds to delay.
+     * @param {Object} [options] The options object.
+     * @param {boolean} [options.leading=false] Specify execution on the leading edge of the timeout.
+     * @param {number} [options.maxWait] The maximum time `func` is allowed to be delayed before it's called.
+     * @param {boolean} [options.trailing=true] Specify execution on the trailing edge of the timeout.
+     * @returns {Function} Returns the new debounced function.
+     * @example
+     *
+     * // avoid costly calculations while the window size is in flux
+     * var lazyLayout = _.debounce(calculateLayout, 150);
+     * jQuery(window).on('resize', lazyLayout);
+     *
+     * // execute `sendMail` when the click event is fired, debouncing subsequent calls
+     * jQuery('#postbox').on('click', _.debounce(sendMail, 300, {
+     *   'leading': true,
+     *   'trailing': false
+     * });
+     *
+     * // ensure `batchLog` is executed once after 1 second of debounced calls
+     * var source = new EventSource('/stream');
+     * source.addEventListener('message', _.debounce(batchLog, 250, {
+     *   'maxWait': 1000
+     * }, false);
+     */
+    function debounce(func, wait, options) {
+      var args,
+          maxTimeoutId,
+          result,
+          stamp,
+          thisArg,
+          timeoutId,
+          trailingCall,
+          lastCalled = 0,
+          maxWait = false,
+          trailing = true;
+
+      if (!isFunction(func)) {
+        throw new TypeError;
+      }
+      wait = nativeMax(0, wait) || 0;
+      if (options === true) {
+        var leading = true;
+        trailing = false;
+      } else if (isObject(options)) {
+        leading = options.leading;
+        maxWait = 'maxWait' in options && (nativeMax(wait, options.maxWait) || 0);
+        trailing = 'trailing' in options ? options.trailing : trailing;
+      }
+      var delayed = function() {
+        var remaining = wait - (now() - stamp);
+        if (remaining <= 0) {
+          if (maxTimeoutId) {
+            clearTimeout(maxTimeoutId);
+          }
+          var isCalled = trailingCall;
+          maxTimeoutId = timeoutId = trailingCall = undefined;
+          if (isCalled) {
+            lastCalled = now();
+            result = func.apply(thisArg, args);
+            if (!timeoutId && !maxTimeoutId) {
+              args = thisArg = null;
+            }
+          }
+        } else {
+          timeoutId = setTimeout(delayed, remaining);
+        }
+      };
+
+      var maxDelayed = function() {
+        if (timeoutId) {
+          clearTimeout(timeoutId);
+        }
+        maxTimeoutId = timeoutId = trailingCall = undefined;
+        if (trailing || (maxWait !== wait)) {
+          lastCalled = now();
+          result = func.apply(thisArg, args);
+          if (!timeoutId && !maxTimeoutId) {
+            args = thisArg = null;
+          }
+        }
+      };
+
+      return function() {
+        args = arguments;
+        stamp = now();
+        thisArg = this;
+        trailingCall = trailing && (timeoutId || !leading);
+
+        if (maxWait === false) {
+          var leadingCall = leading && !timeoutId;
+        } else {
+          if (!maxTimeoutId && !leading) {
+            lastCalled = stamp;
+          }
+          var remaining = maxWait - (stamp - lastCalled),
+              isCalled = remaining <= 0;
+
+          if (isCalled) {
+            if (maxTimeoutId) {
+              maxTimeoutId = clearTimeout(maxTimeoutId);
+            }
+            lastCalled = stamp;
+            result = func.apply(thisArg, args);
+          }
+          else if (!maxTimeoutId) {
+            maxTimeoutId = setTimeout(maxDelayed, remaining);
+          }
+        }
+        if (isCalled && timeoutId) {
+          timeoutId = clearTimeout(timeoutId);
+        }
+        else if (!timeoutId && wait !== maxWait) {
+          timeoutId = setTimeout(delayed, wait);
+        }
+        if (leadingCall) {
+          isCalled = true;
+          result = func.apply(thisArg, args);
+        }
+        if (isCalled && !timeoutId && !maxTimeoutId) {
+          args = thisArg = null;
+        }
+        return result;
+      };
+    }
+
+    /**
+     * Defers executing the `func` function until the current call stack has cleared.
+     * Additional arguments will be provided to `func` when it is invoked.
+     *
+     * @static
+     * @memberOf _
+     * @category Functions
+     * @param {Function} func The function to defer.
+     * @param {...*} [arg] Arguments to invoke the function with.
+     * @returns {number} Returns the timer id.
+     * @example
+     *
+     * _.defer(function(text) { console.log(text); }, 'deferred');
+     * // logs 'deferred' after one or more milliseconds
+     */
+    function defer(func) {
+      if (!isFunction(func)) {
+        throw new TypeError;
+      }
+      var args = slice(arguments, 1);
+      return setTimeout(function() { func.apply(undefined, args); }, 1);
+    }
+
+    /**
+     * Executes the `func` function after `wait` milliseconds. Additional arguments
+     * will be provided to `func` when it is invoked.
+     *
+     * @static
+     * @memberOf _
+     * @category Functions
+     * @param {Function} func The function to delay.
+     * @param {number} wait The number of milliseconds to delay execution.
+     * @param {...*} [arg] Arguments to invoke the function with.
+     * @returns {number} Returns the timer id.
+     * @example
+     *
+     * _.delay(function(text) { console.log(text); }, 1000, 'later');
+     * // => logs 'later' after one second
+     */
+    function delay(func, wait) {
+      if (!isFunction(func)) {
+        throw new TypeError;
+      }
+      var args = slice(arguments, 2);
+      return setTimeout(function() { func.apply(undefined, args); }, wait);
+    }
+
+    /**
+     * Creates a function that memoizes the result of `func`. If `resolver` is
+     * provided it will be used to determine the cache key for storing the result
+     * based on the arguments provided to the memoized function. By default, the
+     * first argument provided to the memoized function is used as the cache key.
+     * The `func` is executed with the `this` binding of the memoized function.
+     * The result cache is exposed as the `cache` property on the memoized function.
+     *
+     * @static
+     * @memberOf _
+     * @category Functions
+     * @param {Function} func The function to have its output memoized.
+     * @param {Function} [resolver] A function used to resolve the cache key.
+     * @returns {Function} Returns the new memoizing function.
+     * @example
+     *
+     * var fibonacci = _.memoize(function(n) {
+     *   return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);
+     * });
+     *
+     * fibonacci(9)
+     * // => 34
+     *
+     * var data = {
+     *   'fred': { 'name': 'fred', 'age': 40 },
+     *   'pebbles': { 'name': 'pebbles', 'age': 1 }
+     * };
+     *
+     * // modifying the result cache
+     * var get = _.memoize(function(name) { return data[name]; }, _.identity);
+     * get('pebbles');
+     * // => { 'name': 'pebbles', 'age': 1 }
+     *
+     * get.cache.pebbles.name = 'penelope';
+     * get('pebbles');
+     * // => { 'name': 'penelope', 'age': 1 }
+     */
+    function memoize(func, resolver) {
+      if (!isFunction(func)) {
+        throw new TypeError;
+      }
+      var memoized = function() {
+        var cache = memoized.cache,
+            key = resolver ? resolver.apply(this, arguments) : keyPrefix + arguments[0];
+
+        return hasOwnProperty.call(cache, key)
+          ? cache[key]
+          : (cache[key] = func.apply(this, arguments));
+      }
+      memoized.cache = {};
+      return memoized;
+    }
+
+    /**
+     * Creates a function that is restricted to execute `func` once. Repeat calls to
+     * the function will return the value of the first call. The `func` is executed
+     * with the `this` binding of the created function.
+     *
+     * @static
+     * @memberOf _
+     * @category Functions
+     * @param {Function} func The function to restrict.
+     * @returns {Function} Returns the new restricted function.
+     * @example
+     *
+     * var initialize = _.once(createApplication);
+     * initialize();
+     * initialize();
+     * // `initialize` executes `createApplication` once
+     */
+    function once(func) {
+      var ran,
+          result;
+
+      if (!isFunction(func)) {
+        throw new TypeError;
+      }
+      return function() {
+        if (ran) {
+          return result;
+        }
+        ran = true;
+        result = func.apply(this, arguments);
+
+        // clear the `func` variable so the function may be garbage collected
+        func = null;
+        return result;
+      };
+    }
+
+    /**
+     * Creates a function that, when called, invokes `func` with any additional
+     * `partial` arguments prepended to those provided to the new function. This
+     * method is similar to `_.bind` except it does **not** alter the `this` binding.
+     *
+     * @static
+     * @memberOf _
+     * @category Functions
+     * @param {Function} func The function to partially apply arguments to.
+     * @param {...*} [arg] Arguments to be partially applied.
+     * @returns {Function} Returns the new partially applied function.
+     * @example
+     *
+     * var greet = function(greeting, name) { return greeting + ' ' + name; };
+     * var hi = _.partial(greet, 'hi');
+     * hi('fred');
+     * // => 'hi fred'
+     */
+    function partial(func) {
+      return createWrapper(func, 16, slice(arguments, 1));
+    }
+
+    /**
+     * This method is like `_.partial` except that `partial` arguments are
+     * appended to those provided to the new function.
+     *
+     * @static
+     * @memberOf _
+     * @category Functions
+     * @param {Function} func The function to partially apply arguments to.
+     * @param {...*} [arg] Arguments to be partially applied.
+     * @returns {Function} Returns the new partially applied function.
+     * @example
+     *
+     * var defaultsDeep = _.partialRight(_.merge, _.defaults);
+     *
+     * var options = {
+     *   'variable': 'data',
+     *   'imports': { 'jq': $ }
+     * };
+     *
+     * defaultsDeep(options, _.templateSettings);
+     *
+     * options.variable
+     * // => 'data'
+     *
+     * options.imports
+     * // => { '_': _, 'jq': $ }
+     */
+    function partialRight(func) {
+      return createWrapper(func, 32, null, slice(arguments, 1));
+    }
+
+    /**
+     * Creates a function that, when executed, will only call the `func` function
+     * at most once per every `wait` milliseconds. Provide an options object to
+     * indicate that `func` should be invoked on the leading and/or trailing edge
+     * of the `wait` timeout. Subsequent calls to the throttled function will
+     * return the result of the last `func` call.
+     *
+     * Note: If `leading` and `trailing` options are `true` `func` will be called
+     * on the trailing edge of the timeout only if the the throttled function is
+     * invoked more than once during the `wait` timeout.
+     *
+     * @static
+     * @memberOf _
+     * @category Functions
+     * @param {Function} func The function to throttle.
+     * @param {number} wait The number of milliseconds to throttle executions to.
+     * @param {Object} [options] The options object.
+     * @param {boolean} [options.leading=true] Specify execution on the leading edge of the timeout.
+     * @param {boolean} [options.trailing=true] Specify execution on the trailing edge of the timeout.
+     * @returns {Function} Returns the new throttled function.
+     * @example
+     *
+     * // avoid excessively updating the position while scrolling
+     * var throttled = _.throttle(updatePosition, 100);
+     * jQuery(window).on('scroll', throttled);
+     *
+     * // execute `renewToken` when the click event is fired, but not more than once every 5 minutes
+     * jQuery('.interactive').on('click', _.throttle(renewToken, 300000, {
+     *   'trailing': false
+     * }));
+     */
+    function throttle(func, wait, options) {
+      var leading = true,
+          trailing = true;
+
+      if (!isFunction(func)) {
+        throw new TypeError;
+      }
+      if (options === false) {
+        leading = false;
+      } else if (isObject(options)) {
+        leading = 'leading' in options ? options.leading : leading;
+        trailing = 'trailing' in options ? options.trailing : trailing;
+      }
+      debounceOptions.leading = leading;
+      debounceOptions.maxWait = wait;
+      debounceOptions.trailing = trailing;
+
+      return debounce(func, wait, debounceOptions);
+    }
+
+    /**
+     * Creates a function that provides `value` to the wrapper function as its
+     * first argument. Additional arguments provided to the function are appended
+     * to those provided to the wrapper function. The wrapper is executed with
+     * the `this` binding of the created function.
+     *
+     * @static
+     * @memberOf _
+     * @category Functions
+     * @param {*} value The value to wrap.
+     * @param {Function} wrapper The wrapper function.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var p = _.wrap(_.escape, function(func, text) {
+     *   return '<p>' + func(text) + '</p>';
+     * });
+     *
+     * p('Fred, Wilma, & Pebbles');
+     * // => '<p>Fred, Wilma, &amp; Pebbles</p>'
+     */
+    function wrap(value, wrapper) {
+      return createWrapper(wrapper, 16, [value]);
+    }
+
+    /*--------------------------------------------------------------------------*/
+
+    /**
+     * Creates a function that returns `value`.
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @param {*} value The value to return from the new function.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var object = { 'name': 'fred' };
+     * var getter = _.constant(object);
+     * getter() === object;
+     * // => true
+     */
+    function constant(value) {
+      return function() {
+        return value;
+      };
+    }
+
+    /**
+     * Produces a callback bound to an optional `thisArg`. If `func` is a property
+     * name the created callback will return the property value for a given element.
+     * If `func` is an object the created callback will return `true` for elements
+     * that contain the equivalent object properties, otherwise it will return `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @param {*} [func=identity] The value to convert to a callback.
+     * @param {*} [thisArg] The `this` binding of the created callback.
+     * @param {number} [argCount] The number of arguments the callback accepts.
+     * @returns {Function} Returns a callback function.
+     * @example
+     *
+     * var characters = [
+     *   { 'name': 'barney', 'age': 36 },
+     *   { 'name': 'fred',   'age': 40 }
+     * ];
+     *
+     * // wrap to create custom callback shorthands
+     * _.createCallback = _.wrap(_.createCallback, function(func, callback, thisArg) {
+     *   var match = /^(.+?)__([gl]t)(.+)$/.exec(callback);
+     *   return !match ? func(callback, thisArg) : function(object) {
+     *     return match[2] == 'gt' ? object[match[1]] > match[3] : object[match[1]] < match[3];
+     *   };
+     * });
+     *
+     * _.filter(characters, 'age__gt38');
+     * // => [{ 'name': 'fred', 'age': 40 }]
+     */
+    function createCallback(func, thisArg, argCount) {
+      var type = typeof func;
+      if (func == null || type == 'function') {
+        return baseCreateCallback(func, thisArg, argCount);
+      }
+      // handle "_.pluck" style callback shorthands
+      if (type != 'object') {
+        return property(func);
+      }
+      var props = keys(func),
+          key = props[0],
+          a = func[key];
+
+      // handle "_.where" style callback shorthands
+      if (props.length == 1 && a === a && !isObject(a)) {
+        // fast path the common case of providing an object with a single
+        // property containing a primitive value
+        return function(object) {
+          var b = object[key];
+          return a === b && (a !== 0 || (1 / a == 1 / b));
+        };
+      }
+      return function(object) {
+        var length = props.length,
+            result = false;
+
+        while (length--) {
+          if (!(result = baseIsEqual(object[props[length]], func[props[length]], null, true))) {
+            break;
+          }
+        }
+        return result;
+      };
+    }
+
+    /**
+     * Converts the characters `&`, `<`, `>`, `"`, and `'` in `string` to their
+     * corresponding HTML entities.
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @param {string} string The string to escape.
+     * @returns {string} Returns the escaped string.
+     * @example
+     *
+     * _.escape('Fred, Wilma, & Pebbles');
+     * // => 'Fred, Wilma, &amp; Pebbles'
+     */
+    function escape(string) {
+      return string == null ? '' : String(string).replace(reUnescapedHtml, escapeHtmlChar);
+    }
+
+    /**
+     * This method returns the first argument provided to it.
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @param {*} value Any value.
+     * @returns {*} Returns `value`.
+     * @example
+     *
+     * var object = { 'name': 'fred' };
+     * _.identity(object) === object;
+     * // => true
+     */
+    function identity(value) {
+      return value;
+    }
+
+    /**
+     * Adds function properties of a source object to the destination object.
+     * If `object` is a function methods will be added to its prototype as well.
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @param {Function|Object} [object=lodash] object The destination object.
+     * @param {Object} source The object of functions to add.
+     * @param {Object} [options] The options object.
+     * @param {boolean} [options.chain=true] Specify whether the functions added are chainable.
+     * @example
+     *
+     * function capitalize(string) {
+     *   return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();
+     * }
+     *
+     * _.mixin({ 'capitalize': capitalize });
+     * _.capitalize('fred');
+     * // => 'Fred'
+     *
+     * _('fred').capitalize().value();
+     * // => 'Fred'
+     *
+     * _.mixin({ 'capitalize': capitalize }, { 'chain': false });
+     * _('fred').capitalize();
+     * // => 'Fred'
+     */
+    function mixin(object, source, options) {
+      var chain = true,
+          methodNames = source && functions(source);
+
+      if (!source || (!options && !methodNames.length)) {
+        if (options == null) {
+          options = source;
+        }
+        ctor = lodashWrapper;
+        source = object;
+        object = lodash;
+        methodNames = functions(source);
+      }
+      if (options === false) {
+        chain = false;
+      } else if (isObject(options) && 'chain' in options) {
+        chain = options.chain;
+      }
+      var ctor = object,
+          isFunc = isFunction(ctor);
+
+      forEach(methodNames, function(methodName) {
+        var func = object[methodName] = source[methodName];
+        if (isFunc) {
+          ctor.prototype[methodName] = function() {
+            var chainAll = this.__chain__,
+                value = this.__wrapped__,
+                args = [value];
+
+            push.apply(args, arguments);
+            var result = func.apply(object, args);
+            if (chain || chainAll) {
+              if (value === result && isObject(result)) {
+                return this;
+              }
+              result = new ctor(result);
+              result.__chain__ = chainAll;
+            }
+            return result;
+          };
+        }
+      });
+    }
+
+    /**
+     * Reverts the '_' variable to its previous value and returns a reference to
+     * the `lodash` function.
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @returns {Function} Returns the `lodash` function.
+     * @example
+     *
+     * var lodash = _.noConflict();
+     */
+    function noConflict() {
+      context._ = oldDash;
+      return this;
+    }
+
+    /**
+     * A no-operation function.
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @example
+     *
+     * var object = { 'name': 'fred' };
+     * _.noop(object) === undefined;
+     * // => true
+     */
+    function noop() {
+      // no operation performed
+    }
+
+    /**
+     * Gets the number of milliseconds that have elapsed since the Unix epoch
+     * (1 January 1970 00:00:00 UTC).
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @example
+     *
+     * var stamp = _.now();
+     * _.defer(function() { console.log(_.now() - stamp); });
+     * // => logs the number of milliseconds it took for the deferred function to be called
+     */
+    var now = isNative(now = Date.now) && now || function() {
+      return new Date().getTime();
+    };
+
+    /**
+     * Converts the given value into an integer of the specified radix.
+     * If `radix` is `undefined` or `0` a `radix` of `10` is used unless the
+     * `value` is a hexadecimal, in which case a `radix` of `16` is used.
+     *
+     * Note: This method avoids differences in native ES3 and ES5 `parseInt`
+     * implementations. See http://es5.github.io/#E.
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @param {string} value The value to parse.
+     * @param {number} [radix] The radix used to interpret the value to parse.
+     * @returns {number} Returns the new integer value.
+     * @example
+     *
+     * _.parseInt('08');
+     * // => 8
+     */
+    var parseInt = nativeParseInt(whitespace + '08') == 8 ? nativeParseInt : function(value, radix) {
+      // Firefox < 21 and Opera < 15 follow the ES3 specified implementation of `parseInt`
+      return nativeParseInt(isString(value) ? value.replace(reLeadingSpacesAndZeros, '') : value, radix || 0);
+    };
+
+    /**
+     * Creates a "_.pluck" style function, which returns the `key` value of a
+     * given object.
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @param {string} key The name of the property to retrieve.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var characters = [
+     *   { 'name': 'fred',   'age': 40 },
+     *   { 'name': 'barney', 'age': 36 }
+     * ];
+     *
+     * var getName = _.property('name');
+     *
+     * _.map(characters, getName);
+     * // => ['barney', 'fred']
+     *
+     * _.sortBy(characters, getName);
+     * // => [{ 'name': 'barney', 'age': 36 }, { 'name': 'fred',   'age': 40 }]
+     */
+    function property(key) {
+      return function(object) {
+        return object[key];
+      };
+    }
+
+    /**
+     * Produces a random number between `min` and `max` (inclusive). If only one
+     * argument is provided a number between `0` and the given number will be
+     * returned. If `floating` is truey or either `min` or `max` are floats a
+     * floating-point number will be returned instead of an integer.
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @param {number} [min=0] The minimum possible value.
+     * @param {number} [max=1] The maximum possible value.
+     * @param {boolean} [floating=false] Specify returning a floating-point number.
+     * @returns {number} Returns a random number.
+     * @example
+     *
+     * _.random(0, 5);
+     * // => an integer between 0 and 5
+     *
+     * _.random(5);
+     * // => also an integer between 0 and 5
+     *
+     * _.random(5, true);
+     * // => a floating-point number between 0 and 5
+     *
+     * _.random(1.2, 5.2);
+     * // => a floating-point number between 1.2 and 5.2
+     */
+    function random(min, max, floating) {
+      var noMin = min == null,
+          noMax = max == null;
+
+      if (floating == null) {
+        if (typeof min == 'boolean' && noMax) {
+          floating = min;
+          min = 1;
+        }
+        else if (!noMax && typeof max == 'boolean') {
+          floating = max;
+          noMax = true;
+        }
+      }
+      if (noMin && noMax) {
+        max = 1;
+      }
+      min = +min || 0;
+      if (noMax) {
+        max = min;
+        min = 0;
+      } else {
+        max = +max || 0;
+      }
+      if (floating || min % 1 || max % 1) {
+        var rand = nativeRandom();
+        return nativeMin(min + (rand * (max - min + parseFloat('1e-' + ((rand +'').length - 1)))), max);
+      }
+      return baseRandom(min, max);
+    }
+
+    /**
+     * Resolves the value of property `key` on `object`. If `key` is a function
+     * it will be invoked with the `this` binding of `object` and its result returned,
+     * else the property value is returned. If `object` is falsey then `undefined`
+     * is returned.
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @param {Object} object The object to inspect.
+     * @param {string} key The name of the property to resolve.
+     * @returns {*} Returns the resolved value.
+     * @example
+     *
+     * var object = {
+     *   'cheese': 'crumpets',
+     *   'stuff': function() {
+     *     return 'nonsense';
+     *   }
+     * };
+     *
+     * _.result(object, 'cheese');
+     * // => 'crumpets'
+     *
+     * _.result(object, 'stuff');
+     * // => 'nonsense'
+     */
+    function result(object, key) {
+      if (object) {
+        var value = object[key];
+        return isFunction(value) ? object[key]() : value;
+      }
+    }
+
+    /**
+     * A micro-templating method that handles arbitrary delimiters, preserves
+     * whitespace, and correctly escapes quotes within interpolated code.
+     *
+     * Note: In the development build, `_.template` utilizes sourceURLs for easier
+     * debugging. See http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl
+     *
+     * For more information on precompiling templates see:
+     * http://lodash.com/custom-builds
+     *
+     * For more information on Chrome extension sandboxes see:
+     * http://developer.chrome.com/stable/extensions/sandboxingEval.html
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @param {string} text The template text.
+     * @param {Object} data The data object used to populate the text.
+     * @param {Object} [options] The options object.
+     * @param {RegExp} [options.escape] The "escape" delimiter.
+     * @param {RegExp} [options.evaluate] The "evaluate" delimiter.
+     * @param {Object} [options.imports] An object to import into the template as local variables.
+     * @param {RegExp} [options.interpolate] The "interpolate" delimiter.
+     * @param {string} [sourceURL] The sourceURL of the template's compiled source.
+     * @param {string} [variable] The data object variable name.
+     * @returns {Function|string} Returns a compiled function when no `data` object
+     *  is given, else it returns the interpolated text.
+     * @example
+     *
+     * // using the "interpolate" delimiter to create a compiled template
+     * var compiled = _.template('hello <%= name %>');
+     * compiled({ 'name': 'fred' });
+     * // => 'hello fred'
+     *
+     * // using the "escape" delimiter to escape HTML in data property values
+     * _.template('<b><%- value %></b>', { 'value': '<script>' });
+     * // => '<b>&lt;script&gt;</b>'
+     *
+     * // using the "evaluate" delimiter to generate HTML
+     * var list = '<% _.forEach(people, function(name) { %><li><%- name %></li><% }); %>';
+     * _.template(list, { 'people': ['fred', 'barney'] });
+     * // => '<li>fred</li><li>barney</li>'
+     *
+     * // using the ES6 delimiter as an alternative to the default "interpolate" delimiter
+     * _.template('hello ${ name }', { 'name': 'pebbles' });
+     * // => 'hello pebbles'
+     *
+     * // using the internal `print` function in "evaluate" delimiters
+     * _.template('<% print("hello " + name); %>!', { 'name': 'barney' });
+     * // => 'hello barney!'
+     *
+     * // using a custom template delimiters
+     * _.templateSettings = {
+     *   'interpolate': /{{([\s\S]+?)}}/g
+     * };
+     *
+     * _.template('hello {{ name }}!', { 'name': 'mustache' });
+     * // => 'hello mustache!'
+     *
+     * // using the `imports` option to import jQuery
+     * var list = '<% jq.each(people, function(name) { %><li><%- name %></li><% }); %>';
+     * _.template(list, { 'people': ['fred', 'barney'] }, { 'imports': { 'jq': jQuery } });
+     * // => '<li>fred</li><li>barney</li>'
+     *
+     * // using the `sourceURL` option to specify a custom sourceURL for the template
+     * var compiled = _.template('hello <%= name %>', null, { 'sourceURL': '/basic/greeting.jst' });
+     * compiled(data);
+     * // => find the source of "greeting.jst" under the Sources tab or Resources panel of the web inspector
+     *
+     * // using the `variable` option to ensure a with-statement isn't used in the compiled template
+     * var compiled = _.template('hi <%= data.name %>!', null, { 'variable': 'data' });
+     * compiled.source;
+     * // => function(data) {
+     *   var __t, __p = '', __e = _.escape;
+     *   __p += 'hi ' + ((__t = ( data.name )) == null ? '' : __t) + '!';
+     *   return __p;
+     * }
+     *
+     * // using the `source` property to inline compiled templates for meaningful
+     * // line numbers in error messages and a stack trace
+     * fs.writeFileSync(path.join(cwd, 'jst.js'), '\
+     *   var JST = {\
+     *     "main": ' + _.template(mainText).source + '\
+     *   };\
+     * ');
+     */
+    function template(text, data, options) {
+      // based on John Resig's `tmpl` implementation
+      // http://ejohn.org/blog/javascript-micro-templating/
+      // and Laura Doktorova's doT.js
+      // https://github.com/olado/doT
+      var settings = lodash.templateSettings;
+      text = String(text || '');
+
+      // avoid missing dependencies when `iteratorTemplate` is not defined
+      options = defaults({}, options, settings);
+
+      var imports = defaults({}, options.imports, settings.imports),
+          importsKeys = keys(imports),
+          importsValues = values(imports);
+
+      var isEvaluating,
+          index = 0,
+          interpolate = options.interpolate || reNoMatch,
+          source = "__p += '";
+
+      // compile the regexp to match each delimiter
+      var reDelimiters = RegExp(
+        (options.escape || reNoMatch).source + '|' +
+        interpolate.source + '|' +
+        (interpolate === reInterpolate ? reEsTemplate : reNoMatch).source + '|' +
+        (options.evaluate || reNoMatch).source + '|$'
+      , 'g');
+
+      text.replace(reDelimiters, function(match, escapeValue, interpolateValue, esTemplateValue, evaluateValue, offset) {
+        interpolateValue || (interpolateValue = esTemplateValue);
+
+        // escape characters that cannot be included in string literals
+        source += text.slice(index, offset).replace(reUnescapedString, escapeStringChar);
+
+        // replace delimiters with snippets
+        if (escapeValue) {
+          source += "' +\n__e(" + escapeValue + ") +\n'";
+        }
+        if (evaluateValue) {
+          isEvaluating = true;
+          source += "';\n" + evaluateValue + ";\n__p += '";
+        }
+        if (interpolateValue) {
+          source += "' +\n((__t = (" + interpolateValue + ")) == null ? '' : __t) +\n'";
+        }
+        index = offset + match.length;
+
+        // the JS engine embedded in Adobe products requires returning the `match`
+        // string in order to produce the correct `offset` value
+        return match;
+      });
+
+      source += "';\n";
+
+      // if `variable` is not specified, wrap a with-statement around the generated
+      // code to add the data object to the top of the scope chain
+      var variable = options.variable,
+          hasVariable = variable;
+
+      if (!hasVariable) {
+        variable = 'obj';
+        source = 'with (' + variable + ') {\n' + source + '\n}\n';
+      }
+      // cleanup code by stripping empty strings
+      source = (isEvaluating ? source.replace(reEmptyStringLeading, '') : source)
+        .replace(reEmptyStringMiddle, '$1')
+        .replace(reEmptyStringTrailing, '$1;');
+
+      // frame code as the function body
+      source = 'function(' + variable + ') {\n' +
+        (hasVariable ? '' : variable + ' || (' + variable + ' = {});\n') +
+        "var __t, __p = '', __e = _.escape" +
+        (isEvaluating
+          ? ', __j = Array.prototype.join;\n' +
+            "function print() { __p += __j.call(arguments, '') }\n"
+          : ';\n'
+        ) +
+        source +
+        'return __p\n}';
+
+      // Use a sourceURL for easier debugging.
+      // http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl
+      var sourceURL = '\n/*\n//# sourceURL=' + (options.sourceURL || '/lodash/template/source[' + (templateCounter++) + ']') + '\n*/';
+
+      try {
+        var result = Function(importsKeys, 'return ' + source + sourceURL).apply(undefined, importsValues);
+      } catch(e) {
+        e.source = source;
+        throw e;
+      }
+      if (data) {
+        return result(data);
+      }
+      // provide the compiled function's source by its `toString` method, in
+      // supported environments, or the `source` property as a convenience for
+      // inlining compiled templates during the build process
+      result.source = source;
+      return result;
+    }
+
+    /**
+     * Executes the callback `n` times, returning an array of the results
+     * of each callback execution. The callback is bound to `thisArg` and invoked
+     * with one argument; (index).
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @param {number} n The number of times to execute the callback.
+     * @param {Function} callback The function called per iteration.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Array} Returns an array of the results of each `callback` execution.
+     * @example
+     *
+     * var diceRolls = _.times(3, _.partial(_.random, 1, 6));
+     * // => [3, 6, 4]
+     *
+     * _.times(3, function(n) { mage.castSpell(n); });
+     * // => calls `mage.castSpell(n)` three times, passing `n` of `0`, `1`, and `2` respectively
+     *
+     * _.times(3, function(n) { this.cast(n); }, mage);
+     * // => also calls `mage.castSpell(n)` three times
+     */
+    function times(n, callback, thisArg) {
+      n = (n = +n) > -1 ? n : 0;
+      var index = -1,
+          result = Array(n);
+
+      callback = baseCreateCallback(callback, thisArg, 1);
+      while (++index < n) {
+        result[index] = callback(index);
+      }
+      return result;
+    }
+
+    /**
+     * The inverse of `_.escape` this method converts the HTML entities
+     * `&amp;`, `&lt;`, `&gt;`, `&quot;`, and `&#39;` in `string` to their
+     * corresponding characters.
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @param {string} string The string to unescape.
+     * @returns {string} Returns the unescaped string.
+     * @example
+     *
+     * _.unescape('Fred, Barney &amp; Pebbles');
+     * // => 'Fred, Barney & Pebbles'
+     */
+    function unescape(string) {
+      return string == null ? '' : String(string).replace(reEscapedHtml, unescapeHtmlChar);
+    }
+
+    /**
+     * Generates a unique ID. If `prefix` is provided the ID will be appended to it.
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @param {string} [prefix] The value to prefix the ID with.
+     * @returns {string} Returns the unique ID.
+     * @example
+     *
+     * _.uniqueId('contact_');
+     * // => 'contact_104'
+     *
+     * _.uniqueId();
+     * // => '105'
+     */
+    function uniqueId(prefix) {
+      var id = ++idCounter;
+      return String(prefix == null ? '' : prefix) + id;
+    }
+
+    /*--------------------------------------------------------------------------*/
+
+    /**
+     * Creates a `lodash` object that wraps the given value with explicit
+     * method chaining enabled.
+     *
+     * @static
+     * @memberOf _
+     * @category Chaining
+     * @param {*} value The value to wrap.
+     * @returns {Object} Returns the wrapper object.
+     * @example
+     *
+     * var characters = [
+     *   { 'name': 'barney',  'age': 36 },
+     *   { 'name': 'fred',    'age': 40 },
+     *   { 'name': 'pebbles', 'age': 1 }
+     * ];
+     *
+     * var youngest = _.chain(characters)
+     *     .sortBy('age')
+     *     .map(function(chr) { return chr.name + ' is ' + chr.age; })
+     *     .first()
+     *     .value();
+     * // => 'pebbles is 1'
+     */
+    function chain(value) {
+      value = new lodashWrapper(value);
+      value.__chain__ = true;
+      return value;
+    }
+
+    /**
+     * Invokes `interceptor` with the `value` as the first argument and then
+     * returns `value`. The purpose of this method is to "tap into" a method
+     * chain in order to perform operations on intermediate results within
+     * the chain.
+     *
+     * @static
+     * @memberOf _
+     * @category Chaining
+     * @param {*} value The value to provide to `interceptor`.
+     * @param {Function} interceptor The function to invoke.
+     * @returns {*} Returns `value`.
+     * @example
+     *
+     * _([1, 2, 3, 4])
+     *  .tap(function(array) { array.pop(); })
+     *  .reverse()
+     *  .value();
+     * // => [3, 2, 1]
+     */
+    function tap(value, interceptor) {
+      interceptor(value);
+      return value;
+    }
+
+    /**
+     * Enables explicit method chaining on the wrapper object.
+     *
+     * @name chain
+     * @memberOf _
+     * @category Chaining
+     * @returns {*} Returns the wrapper object.
+     * @example
+     *
+     * var characters = [
+     *   { 'name': 'barney', 'age': 36 },
+     *   { 'name': 'fred',   'age': 40 }
+     * ];
+     *
+     * // without explicit chaining
+     * _(characters).first();
+     * // => { 'name': 'barney', 'age': 36 }
+     *
+     * // with explicit chaining
+     * _(characters).chain()
+     *   .first()
+     *   .pick('age')
+     *   .value();
+     * // => { 'age': 36 }
+     */
+    function wrapperChain() {
+      this.__chain__ = true;
+      return this;
+    }
+
+    /**
+     * Produces the `toString` result of the wrapped value.
+     *
+     * @name toString
+     * @memberOf _
+     * @category Chaining
+     * @returns {string} Returns the string result.
+     * @example
+     *
+     * _([1, 2, 3]).toString();
+     * // => '1,2,3'
+     */
+    function wrapperToString() {
+      return String(this.__wrapped__);
+    }
+
+    /**
+     * Extracts the wrapped value.
+     *
+     * @name valueOf
+     * @memberOf _
+     * @alias value
+     * @category Chaining
+     * @returns {*} Returns the wrapped value.
+     * @example
+     *
+     * _([1, 2, 3]).valueOf();
+     * // => [1, 2, 3]
+     */
+    function wrapperValueOf() {
+      return this.__wrapped__;
+    }
+
+    /*--------------------------------------------------------------------------*/
+
+    // add functions that return wrapped values when chaining
+    lodash.after = after;
+    lodash.assign = assign;
+    lodash.at = at;
+    lodash.bind = bind;
+    lodash.bindAll = bindAll;
+    lodash.bindKey = bindKey;
+    lodash.chain = chain;
+    lodash.compact = compact;
+    lodash.compose = compose;
+    lodash.constant = constant;
+    lodash.countBy = countBy;
+    lodash.create = create;
+    lodash.createCallback = createCallback;
+    lodash.curry = curry;
+    lodash.debounce = debounce;
+    lodash.defaults = defaults;
+    lodash.defer = defer;
+    lodash.delay = delay;
+    lodash.difference = difference;
+    lodash.filter = filter;
+    lodash.flatten = flatten;
+    lodash.forEach = forEach;
+    lodash.forEachRight = forEachRight;
+    lodash.forIn = forIn;
+    lodash.forInRight = forInRight;
+    lodash.forOwn = forOwn;
+    lodash.forOwnRight = forOwnRight;
+    lodash.functions = functions;
+    lodash.groupBy = groupBy;
+    lodash.indexBy = indexBy;
+    lodash.initial = initial;
+    lodash.intersection = intersection;
+    lodash.invert = invert;
+    lodash.invoke = invoke;
+    lodash.keys = keys;
+    lodash.map = map;
+    lodash.mapValues = mapValues;
+    lodash.max = max;
+    lodash.memoize = memoize;
+    lodash.merge = merge;
+    lodash.min = min;
+    lodash.omit = omit;
+    lodash.once = once;
+    lodash.pairs = pairs;
+    lodash.partial = partial;
+    lodash.partialRight = partialRight;
+    lodash.pick = pick;
+    lodash.pluck = pluck;
+    lodash.property = property;
+    lodash.pull = pull;
+    lodash.range = range;
+    lodash.reject = reject;
+    lodash.remove = remove;
+    lodash.rest = rest;
+    lodash.shuffle = shuffle;
+    lodash.sortBy = sortBy;
+    lodash.tap = tap;
+    lodash.throttle = throttle;
+    lodash.times = times;
+    lodash.toArray = toArray;
+    lodash.transform = transform;
+    lodash.union = union;
+    lodash.uniq = uniq;
+    lodash.values = values;
+    lodash.where = where;
+    lodash.without = without;
+    lodash.wrap = wrap;
+    lodash.xor = xor;
+    lodash.zip = zip;
+    lodash.zipObject = zipObject;
+
+    // add aliases
+    lodash.collect = map;
+    lodash.drop = rest;
+    lodash.each = forEach;
+    lodash.eachRight = forEachRight;
+    lodash.extend = assign;
+    lodash.methods = functions;
+    lodash.object = zipObject;
+    lodash.select = filter;
+    lodash.tail = rest;
+    lodash.unique = uniq;
+    lodash.unzip = zip;
+
+    // add functions to `lodash.prototype`
+    mixin(lodash);
+
+    /*--------------------------------------------------------------------------*/
+
+    // add functions that return unwrapped values when chaining
+    lodash.clone = clone;
+    lodash.cloneDeep = cloneDeep;
+    lodash.contains = contains;
+    lodash.escape = escape;
+    lodash.every = every;
+    lodash.find = find;
+    lodash.findIndex = findIndex;
+    lodash.findKey = findKey;
+    lodash.findLast = findLast;
+    lodash.findLastIndex = findLastIndex;
+    lodash.findLastKey = findLastKey;
+    lodash.has = has;
+    lodash.identity = identity;
+    lodash.indexOf = indexOf;
+    lodash.isArguments = isArguments;
+    lodash.isArray = isArray;
+    lodash.isBoolean = isBoolean;
+    lodash.isDate = isDate;
+    lodash.isElement = isElement;
+    lodash.isEmpty = isEmpty;
+    lodash.isEqual = isEqual;
+    lodash.isFinite = isFinite;
+    lodash.isFunction = isFunction;
+    lodash.isNaN = isNaN;
+    lodash.isNull = isNull;
+    lodash.isNumber = isNumber;
+    lodash.isObject = isObject;
+    lodash.isPlainObject = isPlainObject;
+    lodash.isRegExp = isRegExp;
+    lodash.isString = isString;
+    lodash.isUndefined = isUndefined;
+    lodash.lastIndexOf = lastIndexOf;
+    lodash.mixin = mixin;
+    lodash.noConflict = noConflict;
+    lodash.noop = noop;
+    lodash.now = now;
+    lodash.parseInt = parseInt;
+    lodash.random = random;
+    lodash.reduce = reduce;
+    lodash.reduceRight = reduceRight;
+    lodash.result = result;
+    lodash.runInContext = runInContext;
+    lodash.size = size;
+    lodash.some = some;
+    lodash.sortedIndex = sortedIndex;
+    lodash.template = template;
+    lodash.unescape = unescape;
+    lodash.uniqueId = uniqueId;
+
+    // add aliases
+    lodash.all = every;
+    lodash.any = some;
+    lodash.detect = find;
+    lodash.findWhere = find;
+    lodash.foldl = reduce;
+    lodash.foldr = reduceRight;
+    lodash.include = contains;
+    lodash.inject = reduce;
+
+    mixin(function() {
+      var source = {}
+      forOwn(lodash, function(func, methodName) {
+        if (!lodash.prototype[methodName]) {
+          source[methodName] = func;
+        }
+      });
+      return source;
+    }(), false);
+
+    /*--------------------------------------------------------------------------*/
+
+    // add functions capable of returning wrapped and unwrapped values when chaining
+    lodash.first = first;
+    lodash.last = last;
+    lodash.sample = sample;
+
+    // add aliases
+    lodash.take = first;
+    lodash.head = first;
+
+    forOwn(lodash, function(func, methodName) {
+      var callbackable = methodName !== 'sample';
+      if (!lodash.prototype[methodName]) {
+        lodash.prototype[methodName]= function(n, guard) {
+          var chainAll = this.__chain__,
+              result = func(this.__wrapped__, n, guard);
+
+          return !chainAll && (n == null || (guard && !(callbackable && typeof n == 'function')))
+            ? result
+            : new lodashWrapper(result, chainAll);
+        };
+      }
+    });
+
+    /*--------------------------------------------------------------------------*/
+
+    /**
+     * The semantic version number.
+     *
+     * @static
+     * @memberOf _
+     * @type string
+     */
+    lodash.VERSION = '2.4.1';
+
+    // add "Chaining" functions to the wrapper
+    lodash.prototype.chain = wrapperChain;
+    lodash.prototype.toString = wrapperToString;
+    lodash.prototype.value = wrapperValueOf;
+    lodash.prototype.valueOf = wrapperValueOf;
+
+    // add `Array` functions that return unwrapped values
+    baseEach(['join', 'pop', 'shift'], function(methodName) {
+      var func = arrayRef[methodName];
+      lodash.prototype[methodName] = function() {
+        var chainAll = this.__chain__,
+            result = func.apply(this.__wrapped__, arguments);
+
+        return chainAll
+          ? new lodashWrapper(result, chainAll)
+          : result;
+      };
+    });
+
+    // add `Array` functions that return the existing wrapped value
+    baseEach(['push', 'reverse', 'sort', 'unshift'], function(methodName) {
+      var func = arrayRef[methodName];
+      lodash.prototype[methodName] = function() {
+        func.apply(this.__wrapped__, arguments);
+        return this;
+      };
+    });
+
+    // add `Array` functions that return new wrapped values
+    baseEach(['concat', 'slice', 'splice'], function(methodName) {
+      var func = arrayRef[methodName];
+      lodash.prototype[methodName] = function() {
+        return new lodashWrapper(func.apply(this.__wrapped__, arguments), this.__chain__);
+      };
+    });
+
+    // avoid array-like object bugs with `Array#shift` and `Array#splice`
+    // in IE < 9, Firefox < 10, Narwhal, and RingoJS
+    if (!support.spliceObjects) {
+      baseEach(['pop', 'shift', 'splice'], function(methodName) {
+        var func = arrayRef[methodName],
+            isSplice = methodName == 'splice';
+
+        lodash.prototype[methodName] = function() {
+          var chainAll = this.__chain__,
+              value = this.__wrapped__,
+              result = func.apply(value, arguments);
+
+          if (value.length === 0) {
+            delete value[0];
+          }
+          return (chainAll || isSplice)
+            ? new lodashWrapper(result, chainAll)
+            : result;
+        };
+      });
+    }
+
+    return lodash;
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  // expose Lo-Dash
+  var _ = runInContext();
+
+  // some AMD build optimizers like r.js check for condition patterns like the following:
+  if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) {
+    // Expose Lo-Dash to the global object even when an AMD loader is present in
+    // case Lo-Dash is loaded with a RequireJS shim config.
+    // See http://requirejs.org/docs/api.html#config-shim
+    root._ = _;
+
+    // define as an anonymous module so, through path mapping, it can be
+    // referenced as the "underscore" module
+    define(function() {
+      return _;
+    });
+  }
+  // check for `exports` after `define` in case a build optimizer adds an `exports` object
+  else if (freeExports && freeModule) {
+    // in Node.js or RingoJS
+    if (moduleExports) {
+      (freeModule.exports = _)._ = _;
+    }
+    // in Narwhal or Rhino -require
+    else {
+      freeExports._ = _;
+    }
+  }
+  else {
+    // in a browser or Rhino
+    root._ = _;
+  }
+}.call(this));
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lodash/dist/lodash.compat.min.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lodash/dist/lodash.compat.min.js
new file mode 100644
index 0000000..d03b6ba
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lodash/dist/lodash.compat.min.js
@@ -0,0 +1,61 @@
+/**
+ * @license
+ * Lo-Dash 2.4.1 (Custom Build) lodash.com/license | Underscore.js 1.5.2 underscorejs.org/LICENSE
+ * Build: `lodash -o ./dist/lodash.compat.js`
+ */
+;(function(){function n(n,t,e){e=(e||0)-1;for(var r=n?n.length:0;++e<r;)if(n[e]===t)return e;return-1}function t(t,e){var r=typeof e;if(t=t.l,"boolean"==r||null==e)return t[e]?0:-1;"number"!=r&&"string"!=r&&(r="object");var u="number"==r?e:b+e;return t=(t=t[r])&&t[u],"object"==r?t&&-1<n(t,e)?0:-1:t?0:-1}function e(n){var t=this.l,e=typeof n;if("boolean"==e||null==n)t[n]=true;else{"number"!=e&&"string"!=e&&(e="object");var r="number"==e?n:b+n,t=t[e]||(t[e]={});"object"==e?(t[r]||(t[r]=[])).push(n):t[r]=true
+}}function r(n){return n.charCodeAt(0)}function u(n,t){for(var e=n.m,r=t.m,u=-1,o=e.length;++u<o;){var a=e[u],i=r[u];if(a!==i){if(a>i||typeof a=="undefined")return 1;if(a<i||typeof i=="undefined")return-1}}return n.n-t.n}function o(n){var t=-1,r=n.length,u=n[0],o=n[r/2|0],a=n[r-1];if(u&&typeof u=="object"&&o&&typeof o=="object"&&a&&typeof a=="object")return false;for(u=l(),u["false"]=u["null"]=u["true"]=u.undefined=false,o=l(),o.k=n,o.l=u,o.push=e;++t<r;)o.push(n[t]);return o}function a(n){return"\\"+Y[n]
+}function i(){return v.pop()||[]}function l(){return y.pop()||{k:null,l:null,m:null,"false":false,n:0,"null":false,number:null,object:null,push:null,string:null,"true":false,undefined:false,o:null}}function f(n){return typeof n.toString!="function"&&typeof(n+"")=="string"}function c(n){n.length=0,v.length<w&&v.push(n)}function p(n){var t=n.l;t&&p(t),n.k=n.l=n.m=n.object=n.number=n.string=n.o=null,y.length<w&&y.push(n)}function s(n,t,e){t||(t=0),typeof e=="undefined"&&(e=n?n.length:0);var r=-1;e=e-t||0;for(var u=Array(0>e?0:e);++r<e;)u[r]=n[t+r];
+return u}function g(e){function v(n){return n&&typeof n=="object"&&!qe(n)&&we.call(n,"__wrapped__")?n:new y(n)}function y(n,t){this.__chain__=!!t,this.__wrapped__=n}function w(n){function t(){if(r){var n=s(r);je.apply(n,arguments)}if(this instanceof t){var o=nt(e.prototype),n=e.apply(o,n||arguments);return xt(n)?n:o}return e.apply(u,n||arguments)}var e=n[0],r=n[2],u=n[4];return ze(t,n),t}function Y(n,t,e,r,u){if(e){var o=e(n);if(typeof o!="undefined")return o}if(!xt(n))return n;var a=he.call(n);if(!V[a]||!Le.nodeClass&&f(n))return n;
+var l=Te[a];switch(a){case L:case z:return new l(+n);case W:case M:return new l(n);case J:return o=l(n.source,S.exec(n)),o.lastIndex=n.lastIndex,o}if(a=qe(n),t){var p=!r;r||(r=i()),u||(u=i());for(var g=r.length;g--;)if(r[g]==n)return u[g];o=a?l(n.length):{}}else o=a?s(n):Ye({},n);return a&&(we.call(n,"index")&&(o.index=n.index),we.call(n,"input")&&(o.input=n.input)),t?(r.push(n),u.push(o),(a?Xe:tr)(n,function(n,a){o[a]=Y(n,t,e,r,u)}),p&&(c(r),c(u)),o):o}function nt(n){return xt(n)?Se(n):{}}function tt(n,t,e){if(typeof n!="function")return Ht;
+if(typeof t=="undefined"||!("prototype"in n))return n;var r=n.__bindData__;if(typeof r=="undefined"&&(Le.funcNames&&(r=!n.name),r=r||!Le.funcDecomp,!r)){var u=be.call(n);Le.funcNames||(r=!A.test(u)),r||(r=B.test(u),ze(n,r))}if(false===r||true!==r&&1&r[1])return n;switch(e){case 1:return function(e){return n.call(t,e)};case 2:return function(e,r){return n.call(t,e,r)};case 3:return function(e,r,u){return n.call(t,e,r,u)};case 4:return function(e,r,u,o){return n.call(t,e,r,u,o)}}return Mt(n,t)}function et(n){function t(){var n=l?a:this;
+if(u){var h=s(u);je.apply(h,arguments)}return(o||c)&&(h||(h=s(arguments)),o&&je.apply(h,o),c&&h.length<i)?(r|=16,et([e,p?r:-4&r,h,null,a,i])):(h||(h=arguments),f&&(e=n[g]),this instanceof t?(n=nt(e.prototype),h=e.apply(n,h),xt(h)?h:n):e.apply(n,h))}var e=n[0],r=n[1],u=n[2],o=n[3],a=n[4],i=n[5],l=1&r,f=2&r,c=4&r,p=8&r,g=e;return ze(t,n),t}function rt(e,r){var u=-1,a=ht(),i=e?e.length:0,l=i>=_&&a===n,f=[];if(l){var c=o(r);c?(a=t,r=c):l=false}for(;++u<i;)c=e[u],0>a(r,c)&&f.push(c);return l&&p(r),f}function ot(n,t,e,r){r=(r||0)-1;
+for(var u=n?n.length:0,o=[];++r<u;){var a=n[r];if(a&&typeof a=="object"&&typeof a.length=="number"&&(qe(a)||dt(a))){t||(a=ot(a,t,e));var i=-1,l=a.length,f=o.length;for(o.length+=l;++i<l;)o[f++]=a[i]}else e||o.push(a)}return o}function at(n,t,e,r,u,o){if(e){var a=e(n,t);if(typeof a!="undefined")return!!a}if(n===t)return 0!==n||1/n==1/t;if(n===n&&!(n&&X[typeof n]||t&&X[typeof t]))return false;if(null==n||null==t)return n===t;var l=he.call(n),p=he.call(t);if(l==T&&(l=G),p==T&&(p=G),l!=p)return false;switch(l){case L:case z:return+n==+t;
+case W:return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case J:case M:return n==ie(t)}if(p=l==$,!p){var s=we.call(n,"__wrapped__"),g=we.call(t,"__wrapped__");if(s||g)return at(s?n.__wrapped__:n,g?t.__wrapped__:t,e,r,u,o);if(l!=G||!Le.nodeClass&&(f(n)||f(t)))return false;if(l=!Le.argsObject&&dt(n)?oe:n.constructor,s=!Le.argsObject&&dt(t)?oe:t.constructor,l!=s&&!(jt(l)&&l instanceof l&&jt(s)&&s instanceof s)&&"constructor"in n&&"constructor"in t)return false}for(l=!u,u||(u=i()),o||(o=i()),s=u.length;s--;)if(u[s]==n)return o[s]==t;
+var h=0,a=true;if(u.push(n),o.push(t),p){if(s=n.length,h=t.length,(a=h==s)||r)for(;h--;)if(p=s,g=t[h],r)for(;p--&&!(a=at(n[p],g,e,r,u,o)););else if(!(a=at(n[h],g,e,r,u,o)))break}else nr(t,function(t,i,l){return we.call(l,i)?(h++,a=we.call(n,i)&&at(n[i],t,e,r,u,o)):void 0}),a&&!r&&nr(n,function(n,t,e){return we.call(e,t)?a=-1<--h:void 0});return u.pop(),o.pop(),l&&(c(u),c(o)),a}function it(n,t,e,r,u){(qe(t)?Dt:tr)(t,function(t,o){var a,i,l=t,f=n[o];if(t&&((i=qe(t))||er(t))){for(l=r.length;l--;)if(a=r[l]==t){f=u[l];
+break}if(!a){var c;e&&(l=e(f,t),c=typeof l!="undefined")&&(f=l),c||(f=i?qe(f)?f:[]:er(f)?f:{}),r.push(t),u.push(f),c||it(f,t,e,r,u)}}else e&&(l=e(f,t),typeof l=="undefined"&&(l=t)),typeof l!="undefined"&&(f=l);n[o]=f})}function lt(n,t){return n+de(Fe()*(t-n+1))}function ft(e,r,u){var a=-1,l=ht(),f=e?e.length:0,s=[],g=!r&&f>=_&&l===n,h=u||g?i():s;for(g&&(h=o(h),l=t);++a<f;){var v=e[a],y=u?u(v,a,e):v;(r?!a||h[h.length-1]!==y:0>l(h,y))&&((u||g)&&h.push(y),s.push(v))}return g?(c(h.k),p(h)):u&&c(h),s}function ct(n){return function(t,e,r){var u={};
+if(e=v.createCallback(e,r,3),qe(t)){r=-1;for(var o=t.length;++r<o;){var a=t[r];n(u,a,e(a,r,t),t)}}else Xe(t,function(t,r,o){n(u,t,e(t,r,o),o)});return u}}function pt(n,t,e,r,u,o){var a=1&t,i=4&t,l=16&t,f=32&t;if(!(2&t||jt(n)))throw new le;l&&!e.length&&(t&=-17,l=e=false),f&&!r.length&&(t&=-33,f=r=false);var c=n&&n.__bindData__;return c&&true!==c?(c=s(c),c[2]&&(c[2]=s(c[2])),c[3]&&(c[3]=s(c[3])),!a||1&c[1]||(c[4]=u),!a&&1&c[1]&&(t|=8),!i||4&c[1]||(c[5]=o),l&&je.apply(c[2]||(c[2]=[]),e),f&&Ee.apply(c[3]||(c[3]=[]),r),c[1]|=t,pt.apply(null,c)):(1==t||17===t?w:et)([n,t,e,r,u,o])
+}function st(){Q.h=F,Q.b=Q.c=Q.g=Q.i="",Q.e="t",Q.j=true;for(var n,t=0;n=arguments[t];t++)for(var e in n)Q[e]=n[e];t=Q.a,Q.d=/^[^,]+/.exec(t)[0],n=ee,t="return function("+t+"){",e=Q;var r="var n,t="+e.d+",E="+e.e+";if(!t)return E;"+e.i+";";e.b?(r+="var u=t.length;n=-1;if("+e.b+"){",Le.unindexedChars&&(r+="if(s(t)){t=t.split('')}"),r+="while(++n<u){"+e.g+";}}else{"):Le.nonEnumArgs&&(r+="var u=t.length;n=-1;if(u&&p(t)){while(++n<u){n+='';"+e.g+";}}else{"),Le.enumPrototypes&&(r+="var G=typeof t=='function';"),Le.enumErrorProps&&(r+="var F=t===k||t instanceof Error;");
+var u=[];if(Le.enumPrototypes&&u.push('!(G&&n=="prototype")'),Le.enumErrorProps&&u.push('!(F&&(n=="message"||n=="name"))'),e.j&&e.f)r+="var C=-1,D=B[typeof t]&&v(t),u=D?D.length:0;while(++C<u){n=D[C];",u.length&&(r+="if("+u.join("&&")+"){"),r+=e.g+";",u.length&&(r+="}"),r+="}";else if(r+="for(n in t){",e.j&&u.push("m.call(t, n)"),u.length&&(r+="if("+u.join("&&")+"){"),r+=e.g+";",u.length&&(r+="}"),r+="}",Le.nonEnumShadows){for(r+="if(t!==A){var i=t.constructor,r=t===(i&&i.prototype),f=t===J?I:t===k?j:L.call(t),x=y[f];",k=0;7>k;k++)r+="n='"+e.h[k]+"';if((!(r&&x[n])&&m.call(t,n))",e.j||(r+="||(!x[n]&&t[n]!==A[n])"),r+="){"+e.g+"}";
+r+="}"}return(e.b||Le.nonEnumArgs)&&(r+="}"),r+=e.c+";return E",n("d,j,k,m,o,p,q,s,v,A,B,y,I,J,L",t+r+"}")(tt,q,ce,we,d,dt,qe,kt,Q.f,pe,X,$e,M,se,he)}function gt(n){return Ve[n]}function ht(){var t=(t=v.indexOf)===zt?n:t;return t}function vt(n){return typeof n=="function"&&ve.test(n)}function yt(n){var t,e;return!n||he.call(n)!=G||(t=n.constructor,jt(t)&&!(t instanceof t))||!Le.argsClass&&dt(n)||!Le.nodeClass&&f(n)?false:Le.ownLast?(nr(n,function(n,t,r){return e=we.call(r,t),false}),false!==e):(nr(n,function(n,t){e=t
+}),typeof e=="undefined"||we.call(n,e))}function mt(n){return He[n]}function dt(n){return n&&typeof n=="object"&&typeof n.length=="number"&&he.call(n)==T||false}function bt(n,t,e){var r=We(n),u=r.length;for(t=tt(t,e,3);u--&&(e=r[u],false!==t(n[e],e,n)););return n}function _t(n){var t=[];return nr(n,function(n,e){jt(n)&&t.push(e)}),t.sort()}function wt(n){for(var t=-1,e=We(n),r=e.length,u={};++t<r;){var o=e[t];u[n[o]]=o}return u}function jt(n){return typeof n=="function"}function xt(n){return!(!n||!X[typeof n])
+}function Ct(n){return typeof n=="number"||n&&typeof n=="object"&&he.call(n)==W||false}function kt(n){return typeof n=="string"||n&&typeof n=="object"&&he.call(n)==M||false}function Et(n){for(var t=-1,e=We(n),r=e.length,u=Zt(r);++t<r;)u[t]=n[e[t]];return u}function Ot(n,t,e){var r=-1,u=ht(),o=n?n.length:0,a=false;return e=(0>e?Be(0,o+e):e)||0,qe(n)?a=-1<u(n,t,e):typeof o=="number"?a=-1<(kt(n)?n.indexOf(t,e):u(n,t,e)):Xe(n,function(n){return++r<e?void 0:!(a=n===t)}),a}function St(n,t,e){var r=true;if(t=v.createCallback(t,e,3),qe(n)){e=-1;
+for(var u=n.length;++e<u&&(r=!!t(n[e],e,n)););}else Xe(n,function(n,e,u){return r=!!t(n,e,u)});return r}function At(n,t,e){var r=[];if(t=v.createCallback(t,e,3),qe(n)){e=-1;for(var u=n.length;++e<u;){var o=n[e];t(o,e,n)&&r.push(o)}}else Xe(n,function(n,e,u){t(n,e,u)&&r.push(n)});return r}function It(n,t,e){if(t=v.createCallback(t,e,3),!qe(n)){var r;return Xe(n,function(n,e,u){return t(n,e,u)?(r=n,false):void 0}),r}e=-1;for(var u=n.length;++e<u;){var o=n[e];if(t(o,e,n))return o}}function Dt(n,t,e){if(t&&typeof e=="undefined"&&qe(n)){e=-1;
+for(var r=n.length;++e<r&&false!==t(n[e],e,n););}else Xe(n,t,e);return n}function Nt(n,t,e){var r=n,u=n?n.length:0;if(t=t&&typeof e=="undefined"?t:tt(t,e,3),qe(n))for(;u--&&false!==t(n[u],u,n););else{if(typeof u!="number")var o=We(n),u=o.length;else Le.unindexedChars&&kt(n)&&(r=n.split(""));Xe(n,function(n,e,a){return e=o?o[--u]:--u,t(r[e],e,a)})}return n}function Bt(n,t,e){var r=-1,u=n?n.length:0,o=Zt(typeof u=="number"?u:0);if(t=v.createCallback(t,e,3),qe(n))for(;++r<u;)o[r]=t(n[r],r,n);else Xe(n,function(n,e,u){o[++r]=t(n,e,u)
+});return o}function Pt(n,t,e){var u=-1/0,o=u;if(typeof t!="function"&&e&&e[t]===n&&(t=null),null==t&&qe(n)){e=-1;for(var a=n.length;++e<a;){var i=n[e];i>o&&(o=i)}}else t=null==t&&kt(n)?r:v.createCallback(t,e,3),Xe(n,function(n,e,r){e=t(n,e,r),e>u&&(u=e,o=n)});return o}function Rt(n,t,e,r){var u=3>arguments.length;if(t=v.createCallback(t,r,4),qe(n)){var o=-1,a=n.length;for(u&&(e=n[++o]);++o<a;)e=t(e,n[o],o,n)}else Xe(n,function(n,r,o){e=u?(u=false,n):t(e,n,r,o)});return e}function Ft(n,t,e,r){var u=3>arguments.length;
+return t=v.createCallback(t,r,4),Nt(n,function(n,r,o){e=u?(u=false,n):t(e,n,r,o)}),e}function Tt(n){var t=-1,e=n?n.length:0,r=Zt(typeof e=="number"?e:0);return Dt(n,function(n){var e=lt(0,++t);r[t]=r[e],r[e]=n}),r}function $t(n,t,e){var r;if(t=v.createCallback(t,e,3),qe(n)){e=-1;for(var u=n.length;++e<u&&!(r=t(n[e],e,n)););}else Xe(n,function(n,e,u){return!(r=t(n,e,u))});return!!r}function Lt(n,t,e){var r=0,u=n?n.length:0;if(typeof t!="number"&&null!=t){var o=-1;for(t=v.createCallback(t,e,3);++o<u&&t(n[o],o,n);)r++
+}else if(r=t,null==r||e)return n?n[0]:h;return s(n,0,Pe(Be(0,r),u))}function zt(t,e,r){if(typeof r=="number"){var u=t?t.length:0;r=0>r?Be(0,u+r):r||0}else if(r)return r=Kt(t,e),t[r]===e?r:-1;return n(t,e,r)}function qt(n,t,e){if(typeof t!="number"&&null!=t){var r=0,u=-1,o=n?n.length:0;for(t=v.createCallback(t,e,3);++u<o&&t(n[u],u,n);)r++}else r=null==t||e?1:Be(0,t);return s(n,r)}function Kt(n,t,e,r){var u=0,o=n?n.length:u;for(e=e?v.createCallback(e,r,1):Ht,t=e(t);u<o;)r=u+o>>>1,e(n[r])<t?u=r+1:o=r;
+return u}function Wt(n,t,e,r){return typeof t!="boolean"&&null!=t&&(r=e,e=typeof t!="function"&&r&&r[t]===n?null:t,t=false),null!=e&&(e=v.createCallback(e,r,3)),ft(n,t,e)}function Gt(){for(var n=1<arguments.length?arguments:arguments[0],t=-1,e=n?Pt(ar(n,"length")):0,r=Zt(0>e?0:e);++t<e;)r[t]=ar(n,t);return r}function Jt(n,t){var e=-1,r=n?n.length:0,u={};for(t||!r||qe(n[0])||(t=[]);++e<r;){var o=n[e];t?u[o]=t[e]:o&&(u[o[0]]=o[1])}return u}function Mt(n,t){return 2<arguments.length?pt(n,17,s(arguments,2),null,t):pt(n,1,null,null,t)
+}function Vt(n,t,e){var r,u,o,a,i,l,f,c=0,p=false,s=true;if(!jt(n))throw new le;if(t=Be(0,t)||0,true===e)var g=true,s=false;else xt(e)&&(g=e.leading,p="maxWait"in e&&(Be(t,e.maxWait)||0),s="trailing"in e?e.trailing:s);var v=function(){var e=t-(ir()-a);0<e?l=Ce(v,e):(u&&me(u),e=f,u=l=f=h,e&&(c=ir(),o=n.apply(i,r),l||u||(r=i=null)))},y=function(){l&&me(l),u=l=f=h,(s||p!==t)&&(c=ir(),o=n.apply(i,r),l||u||(r=i=null))};return function(){if(r=arguments,a=ir(),i=this,f=s&&(l||!g),false===p)var e=g&&!l;else{u||g||(c=a);
+var h=p-(a-c),m=0>=h;m?(u&&(u=me(u)),c=a,o=n.apply(i,r)):u||(u=Ce(y,h))}return m&&l?l=me(l):l||t===p||(l=Ce(v,t)),e&&(m=true,o=n.apply(i,r)),!m||l||u||(r=i=null),o}}function Ht(n){return n}function Ut(n,t,e){var r=true,u=t&&_t(t);t&&(e||u.length)||(null==e&&(e=t),o=y,t=n,n=v,u=_t(t)),false===e?r=false:xt(e)&&"chain"in e&&(r=e.chain);var o=n,a=jt(o);Dt(u,function(e){var u=n[e]=t[e];a&&(o.prototype[e]=function(){var t=this.__chain__,e=this.__wrapped__,a=[e];if(je.apply(a,arguments),a=u.apply(n,a),r||t){if(e===a&&xt(a))return this;
+a=new o(a),a.__chain__=t}return a})})}function Qt(){}function Xt(n){return function(t){return t[n]}}function Yt(){return this.__wrapped__}e=e?ut.defaults(Z.Object(),e,ut.pick(Z,R)):Z;var Zt=e.Array,ne=e.Boolean,te=e.Date,ee=e.Function,re=e.Math,ue=e.Number,oe=e.Object,ae=e.RegExp,ie=e.String,le=e.TypeError,fe=[],ce=e.Error.prototype,pe=oe.prototype,se=ie.prototype,ge=e._,he=pe.toString,ve=ae("^"+ie(he).replace(/[.*+?^${}()|[\]\\]/g,"\\$&").replace(/toString| for [^\]]+/g,".*?")+"$"),ye=re.ceil,me=e.clearTimeout,de=re.floor,be=ee.prototype.toString,_e=vt(_e=oe.getPrototypeOf)&&_e,we=pe.hasOwnProperty,je=fe.push,xe=pe.propertyIsEnumerable,Ce=e.setTimeout,ke=fe.splice,Ee=fe.unshift,Oe=function(){try{var n={},t=vt(t=oe.defineProperty)&&t,e=t(n,n,n)&&t
+}catch(r){}return e}(),Se=vt(Se=oe.create)&&Se,Ae=vt(Ae=Zt.isArray)&&Ae,Ie=e.isFinite,De=e.isNaN,Ne=vt(Ne=oe.keys)&&Ne,Be=re.max,Pe=re.min,Re=e.parseInt,Fe=re.random,Te={};Te[$]=Zt,Te[L]=ne,Te[z]=te,Te[K]=ee,Te[G]=oe,Te[W]=ue,Te[J]=ae,Te[M]=ie;var $e={};$e[$]=$e[z]=$e[W]={constructor:true,toLocaleString:true,toString:true,valueOf:true},$e[L]=$e[M]={constructor:true,toString:true,valueOf:true},$e[q]=$e[K]=$e[J]={constructor:true,toString:true},$e[G]={constructor:true},function(){for(var n=F.length;n--;){var t,e=F[n];
+for(t in $e)we.call($e,t)&&!we.call($e[t],e)&&($e[t][e]=false)}}(),y.prototype=v.prototype;var Le=v.support={};!function(){var n=function(){this.x=1},t={0:1,length:1},r=[];n.prototype={valueOf:1,y:1};for(var u in new n)r.push(u);for(u in arguments);Le.argsClass=he.call(arguments)==T,Le.argsObject=arguments.constructor==oe&&!(arguments instanceof Zt),Le.enumErrorProps=xe.call(ce,"message")||xe.call(ce,"name"),Le.enumPrototypes=xe.call(n,"prototype"),Le.funcDecomp=!vt(e.WinRTError)&&B.test(g),Le.funcNames=typeof ee.name=="string",Le.nonEnumArgs=0!=u,Le.nonEnumShadows=!/valueOf/.test(r),Le.ownLast="x"!=r[0],Le.spliceObjects=(fe.splice.call(t,0,1),!t[0]),Le.unindexedChars="xx"!="x"[0]+oe("x")[0];
+try{Le.nodeClass=!(he.call(document)==G&&!({toString:0}+""))}catch(o){Le.nodeClass=true}}(1),v.templateSettings={escape:/<%-([\s\S]+?)%>/g,evaluate:/<%([\s\S]+?)%>/g,interpolate:I,variable:"",imports:{_:v}},Se||(nt=function(){function n(){}return function(t){if(xt(t)){n.prototype=t;var r=new n;n.prototype=null}return r||e.Object()}}());var ze=Oe?function(n,t){U.value=t,Oe(n,"__bindData__",U)}:Qt;Le.argsClass||(dt=function(n){return n&&typeof n=="object"&&typeof n.length=="number"&&we.call(n,"callee")&&!xe.call(n,"callee")||false
+});var qe=Ae||function(n){return n&&typeof n=="object"&&typeof n.length=="number"&&he.call(n)==$||false},Ke=st({a:"z",e:"[]",i:"if(!(B[typeof z]))return E",g:"E.push(n)"}),We=Ne?function(n){return xt(n)?Le.enumPrototypes&&typeof n=="function"||Le.nonEnumArgs&&n.length&&dt(n)?Ke(n):Ne(n):[]}:Ke,Ge={a:"g,e,K",i:"e=e&&typeof K=='undefined'?e:d(e,K,3)",b:"typeof u=='number'",v:We,g:"if(e(t[n],n,g)===false)return E"},Je={a:"z,H,l",i:"var a=arguments,b=0,c=typeof l=='number'?2:a.length;while(++b<c){t=a[b];if(t&&B[typeof t]){",v:We,g:"if(typeof E[n]=='undefined')E[n]=t[n]",c:"}}"},Me={i:"if(!B[typeof t])return E;"+Ge.i,b:false},Ve={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"},He=wt(Ve),Ue=ae("("+We(He).join("|")+")","g"),Qe=ae("["+We(Ve).join("")+"]","g"),Xe=st(Ge),Ye=st(Je,{i:Je.i.replace(";",";if(c>3&&typeof a[c-2]=='function'){var e=d(a[--c-1],a[c--],2)}else if(c>2&&typeof a[c-1]=='function'){e=a[--c]}"),g:"E[n]=e?e(E[n],t[n]):t[n]"}),Ze=st(Je),nr=st(Ge,Me,{j:false}),tr=st(Ge,Me);
+jt(/x/)&&(jt=function(n){return typeof n=="function"&&he.call(n)==K});var er=_e?function(n){if(!n||he.call(n)!=G||!Le.argsClass&&dt(n))return false;var t=n.valueOf,e=vt(t)&&(e=_e(t))&&_e(e);return e?n==e||_e(n)==e:yt(n)}:yt,rr=ct(function(n,t,e){we.call(n,e)?n[e]++:n[e]=1}),ur=ct(function(n,t,e){(we.call(n,e)?n[e]:n[e]=[]).push(t)}),or=ct(function(n,t,e){n[e]=t}),ar=Bt,ir=vt(ir=te.now)&&ir||function(){return(new te).getTime()},lr=8==Re(j+"08")?Re:function(n,t){return Re(kt(n)?n.replace(D,""):n,t||0)};
+return v.after=function(n,t){if(!jt(t))throw new le;return function(){return 1>--n?t.apply(this,arguments):void 0}},v.assign=Ye,v.at=function(n){var t=arguments,e=-1,r=ot(t,true,false,1),t=t[2]&&t[2][t[1]]===n?1:r.length,u=Zt(t);for(Le.unindexedChars&&kt(n)&&(n=n.split(""));++e<t;)u[e]=n[r[e]];return u},v.bind=Mt,v.bindAll=function(n){for(var t=1<arguments.length?ot(arguments,true,false,1):_t(n),e=-1,r=t.length;++e<r;){var u=t[e];n[u]=pt(n[u],1,null,null,n)}return n},v.bindKey=function(n,t){return 2<arguments.length?pt(t,19,s(arguments,2),null,n):pt(t,3,null,null,n)
+},v.chain=function(n){return n=new y(n),n.__chain__=true,n},v.compact=function(n){for(var t=-1,e=n?n.length:0,r=[];++t<e;){var u=n[t];u&&r.push(u)}return r},v.compose=function(){for(var n=arguments,t=n.length;t--;)if(!jt(n[t]))throw new le;return function(){for(var t=arguments,e=n.length;e--;)t=[n[e].apply(this,t)];return t[0]}},v.constant=function(n){return function(){return n}},v.countBy=rr,v.create=function(n,t){var e=nt(n);return t?Ye(e,t):e},v.createCallback=function(n,t,e){var r=typeof n;if(null==n||"function"==r)return tt(n,t,e);
+if("object"!=r)return Xt(n);var u=We(n),o=u[0],a=n[o];return 1!=u.length||a!==a||xt(a)?function(t){for(var e=u.length,r=false;e--&&(r=at(t[u[e]],n[u[e]],null,true)););return r}:function(n){return n=n[o],a===n&&(0!==a||1/a==1/n)}},v.curry=function(n,t){return t=typeof t=="number"?t:+t||n.length,pt(n,4,null,null,null,t)},v.debounce=Vt,v.defaults=Ze,v.defer=function(n){if(!jt(n))throw new le;var t=s(arguments,1);return Ce(function(){n.apply(h,t)},1)},v.delay=function(n,t){if(!jt(n))throw new le;var e=s(arguments,2);
+return Ce(function(){n.apply(h,e)},t)},v.difference=function(n){return rt(n,ot(arguments,true,true,1))},v.filter=At,v.flatten=function(n,t,e,r){return typeof t!="boolean"&&null!=t&&(r=e,e=typeof t!="function"&&r&&r[t]===n?null:t,t=false),null!=e&&(n=Bt(n,e,r)),ot(n,t)},v.forEach=Dt,v.forEachRight=Nt,v.forIn=nr,v.forInRight=function(n,t,e){var r=[];nr(n,function(n,t){r.push(t,n)});var u=r.length;for(t=tt(t,e,3);u--&&false!==t(r[u--],r[u],n););return n},v.forOwn=tr,v.forOwnRight=bt,v.functions=_t,v.groupBy=ur,v.indexBy=or,v.initial=function(n,t,e){var r=0,u=n?n.length:0;
+if(typeof t!="number"&&null!=t){var o=u;for(t=v.createCallback(t,e,3);o--&&t(n[o],o,n);)r++}else r=null==t||e?1:t||r;return s(n,0,Pe(Be(0,u-r),u))},v.intersection=function(){for(var e=[],r=-1,u=arguments.length,a=i(),l=ht(),f=l===n,s=i();++r<u;){var g=arguments[r];(qe(g)||dt(g))&&(e.push(g),a.push(f&&g.length>=_&&o(r?e[r]:s)))}var f=e[0],h=-1,v=f?f.length:0,y=[];n:for(;++h<v;){var m=a[0],g=f[h];if(0>(m?t(m,g):l(s,g))){for(r=u,(m||s).push(g);--r;)if(m=a[r],0>(m?t(m,g):l(e[r],g)))continue n;y.push(g)
+}}for(;u--;)(m=a[u])&&p(m);return c(a),c(s),y},v.invert=wt,v.invoke=function(n,t){var e=s(arguments,2),r=-1,u=typeof t=="function",o=n?n.length:0,a=Zt(typeof o=="number"?o:0);return Dt(n,function(n){a[++r]=(u?t:n[t]).apply(n,e)}),a},v.keys=We,v.map=Bt,v.mapValues=function(n,t,e){var r={};return t=v.createCallback(t,e,3),tr(n,function(n,e,u){r[e]=t(n,e,u)}),r},v.max=Pt,v.memoize=function(n,t){if(!jt(n))throw new le;var e=function(){var r=e.cache,u=t?t.apply(this,arguments):b+arguments[0];return we.call(r,u)?r[u]:r[u]=n.apply(this,arguments)
+};return e.cache={},e},v.merge=function(n){var t=arguments,e=2;if(!xt(n))return n;if("number"!=typeof t[2]&&(e=t.length),3<e&&"function"==typeof t[e-2])var r=tt(t[--e-1],t[e--],2);else 2<e&&"function"==typeof t[e-1]&&(r=t[--e]);for(var t=s(arguments,1,e),u=-1,o=i(),a=i();++u<e;)it(n,t[u],r,o,a);return c(o),c(a),n},v.min=function(n,t,e){var u=1/0,o=u;if(typeof t!="function"&&e&&e[t]===n&&(t=null),null==t&&qe(n)){e=-1;for(var a=n.length;++e<a;){var i=n[e];i<o&&(o=i)}}else t=null==t&&kt(n)?r:v.createCallback(t,e,3),Xe(n,function(n,e,r){e=t(n,e,r),e<u&&(u=e,o=n)
+});return o},v.omit=function(n,t,e){var r={};if(typeof t!="function"){var u=[];nr(n,function(n,t){u.push(t)});for(var u=rt(u,ot(arguments,true,false,1)),o=-1,a=u.length;++o<a;){var i=u[o];r[i]=n[i]}}else t=v.createCallback(t,e,3),nr(n,function(n,e,u){t(n,e,u)||(r[e]=n)});return r},v.once=function(n){var t,e;if(!jt(n))throw new le;return function(){return t?e:(t=true,e=n.apply(this,arguments),n=null,e)}},v.pairs=function(n){for(var t=-1,e=We(n),r=e.length,u=Zt(r);++t<r;){var o=e[t];u[t]=[o,n[o]]}return u
+},v.partial=function(n){return pt(n,16,s(arguments,1))},v.partialRight=function(n){return pt(n,32,null,s(arguments,1))},v.pick=function(n,t,e){var r={};if(typeof t!="function")for(var u=-1,o=ot(arguments,true,false,1),a=xt(n)?o.length:0;++u<a;){var i=o[u];i in n&&(r[i]=n[i])}else t=v.createCallback(t,e,3),nr(n,function(n,e,u){t(n,e,u)&&(r[e]=n)});return r},v.pluck=ar,v.property=Xt,v.pull=function(n){for(var t=arguments,e=0,r=t.length,u=n?n.length:0;++e<r;)for(var o=-1,a=t[e];++o<u;)n[o]===a&&(ke.call(n,o--,1),u--);
+return n},v.range=function(n,t,e){n=+n||0,e=typeof e=="number"?e:+e||1,null==t&&(t=n,n=0);var r=-1;t=Be(0,ye((t-n)/(e||1)));for(var u=Zt(t);++r<t;)u[r]=n,n+=e;return u},v.reject=function(n,t,e){return t=v.createCallback(t,e,3),At(n,function(n,e,r){return!t(n,e,r)})},v.remove=function(n,t,e){var r=-1,u=n?n.length:0,o=[];for(t=v.createCallback(t,e,3);++r<u;)e=n[r],t(e,r,n)&&(o.push(e),ke.call(n,r--,1),u--);return o},v.rest=qt,v.shuffle=Tt,v.sortBy=function(n,t,e){var r=-1,o=qe(t),a=n?n.length:0,f=Zt(typeof a=="number"?a:0);
+for(o||(t=v.createCallback(t,e,3)),Dt(n,function(n,e,u){var a=f[++r]=l();o?a.m=Bt(t,function(t){return n[t]}):(a.m=i())[0]=t(n,e,u),a.n=r,a.o=n}),a=f.length,f.sort(u);a--;)n=f[a],f[a]=n.o,o||c(n.m),p(n);return f},v.tap=function(n,t){return t(n),n},v.throttle=function(n,t,e){var r=true,u=true;if(!jt(n))throw new le;return false===e?r=false:xt(e)&&(r="leading"in e?e.leading:r,u="trailing"in e?e.trailing:u),H.leading=r,H.maxWait=t,H.trailing=u,Vt(n,t,H)},v.times=function(n,t,e){n=-1<(n=+n)?n:0;var r=-1,u=Zt(n);
+for(t=tt(t,e,1);++r<n;)u[r]=t(r);return u},v.toArray=function(n){return n&&typeof n.length=="number"?Le.unindexedChars&&kt(n)?n.split(""):s(n):Et(n)},v.transform=function(n,t,e,r){var u=qe(n);if(null==e)if(u)e=[];else{var o=n&&n.constructor;e=nt(o&&o.prototype)}return t&&(t=v.createCallback(t,r,4),(u?Xe:tr)(n,function(n,r,u){return t(e,n,r,u)})),e},v.union=function(){return ft(ot(arguments,true,true))},v.uniq=Wt,v.values=Et,v.where=At,v.without=function(n){return rt(n,s(arguments,1))},v.wrap=function(n,t){return pt(t,16,[n])
+},v.xor=function(){for(var n=-1,t=arguments.length;++n<t;){var e=arguments[n];if(qe(e)||dt(e))var r=r?ft(rt(r,e).concat(rt(e,r))):e}return r||[]},v.zip=Gt,v.zipObject=Jt,v.collect=Bt,v.drop=qt,v.each=Dt,v.eachRight=Nt,v.extend=Ye,v.methods=_t,v.object=Jt,v.select=At,v.tail=qt,v.unique=Wt,v.unzip=Gt,Ut(v),v.clone=function(n,t,e,r){return typeof t!="boolean"&&null!=t&&(r=e,e=t,t=false),Y(n,t,typeof e=="function"&&tt(e,r,1))},v.cloneDeep=function(n,t,e){return Y(n,true,typeof t=="function"&&tt(t,e,1))},v.contains=Ot,v.escape=function(n){return null==n?"":ie(n).replace(Qe,gt)
+},v.every=St,v.find=It,v.findIndex=function(n,t,e){var r=-1,u=n?n.length:0;for(t=v.createCallback(t,e,3);++r<u;)if(t(n[r],r,n))return r;return-1},v.findKey=function(n,t,e){var r;return t=v.createCallback(t,e,3),tr(n,function(n,e,u){return t(n,e,u)?(r=e,false):void 0}),r},v.findLast=function(n,t,e){var r;return t=v.createCallback(t,e,3),Nt(n,function(n,e,u){return t(n,e,u)?(r=n,false):void 0}),r},v.findLastIndex=function(n,t,e){var r=n?n.length:0;for(t=v.createCallback(t,e,3);r--;)if(t(n[r],r,n))return r;
+return-1},v.findLastKey=function(n,t,e){var r;return t=v.createCallback(t,e,3),bt(n,function(n,e,u){return t(n,e,u)?(r=e,false):void 0}),r},v.has=function(n,t){return n?we.call(n,t):false},v.identity=Ht,v.indexOf=zt,v.isArguments=dt,v.isArray=qe,v.isBoolean=function(n){return true===n||false===n||n&&typeof n=="object"&&he.call(n)==L||false},v.isDate=function(n){return n&&typeof n=="object"&&he.call(n)==z||false},v.isElement=function(n){return n&&1===n.nodeType||false},v.isEmpty=function(n){var t=true;if(!n)return t;var e=he.call(n),r=n.length;
+return e==$||e==M||(Le.argsClass?e==T:dt(n))||e==G&&typeof r=="number"&&jt(n.splice)?!r:(tr(n,function(){return t=false}),t)},v.isEqual=function(n,t,e,r){return at(n,t,typeof e=="function"&&tt(e,r,2))},v.isFinite=function(n){return Ie(n)&&!De(parseFloat(n))},v.isFunction=jt,v.isNaN=function(n){return Ct(n)&&n!=+n},v.isNull=function(n){return null===n},v.isNumber=Ct,v.isObject=xt,v.isPlainObject=er,v.isRegExp=function(n){return n&&X[typeof n]&&he.call(n)==J||false},v.isString=kt,v.isUndefined=function(n){return typeof n=="undefined"
+},v.lastIndexOf=function(n,t,e){var r=n?n.length:0;for(typeof e=="number"&&(r=(0>e?Be(0,r+e):Pe(e,r-1))+1);r--;)if(n[r]===t)return r;return-1},v.mixin=Ut,v.noConflict=function(){return e._=ge,this},v.noop=Qt,v.now=ir,v.parseInt=lr,v.random=function(n,t,e){var r=null==n,u=null==t;return null==e&&(typeof n=="boolean"&&u?(e=n,n=1):u||typeof t!="boolean"||(e=t,u=true)),r&&u&&(t=1),n=+n||0,u?(t=n,n=0):t=+t||0,e||n%1||t%1?(e=Fe(),Pe(n+e*(t-n+parseFloat("1e-"+((e+"").length-1))),t)):lt(n,t)},v.reduce=Rt,v.reduceRight=Ft,v.result=function(n,t){if(n){var e=n[t];
+return jt(e)?n[t]():e}},v.runInContext=g,v.size=function(n){var t=n?n.length:0;return typeof t=="number"?t:We(n).length},v.some=$t,v.sortedIndex=Kt,v.template=function(n,t,e){var r=v.templateSettings;n=ie(n||""),e=Ze({},e,r);var u,o=Ze({},e.imports,r.imports),r=We(o),o=Et(o),i=0,l=e.interpolate||N,f="__p+='",l=ae((e.escape||N).source+"|"+l.source+"|"+(l===I?O:N).source+"|"+(e.evaluate||N).source+"|$","g");n.replace(l,function(t,e,r,o,l,c){return r||(r=o),f+=n.slice(i,c).replace(P,a),e&&(f+="'+__e("+e+")+'"),l&&(u=true,f+="';"+l+";\n__p+='"),r&&(f+="'+((__t=("+r+"))==null?'':__t)+'"),i=c+t.length,t
+}),f+="';",l=e=e.variable,l||(e="obj",f="with("+e+"){"+f+"}"),f=(u?f.replace(x,""):f).replace(C,"$1").replace(E,"$1;"),f="function("+e+"){"+(l?"":e+"||("+e+"={});")+"var __t,__p='',__e=_.escape"+(u?",__j=Array.prototype.join;function print(){__p+=__j.call(arguments,'')}":";")+f+"return __p}";try{var c=ee(r,"return "+f).apply(h,o)}catch(p){throw p.source=f,p}return t?c(t):(c.source=f,c)},v.unescape=function(n){return null==n?"":ie(n).replace(Ue,mt)},v.uniqueId=function(n){var t=++m;return ie(null==n?"":n)+t
+},v.all=St,v.any=$t,v.detect=It,v.findWhere=It,v.foldl=Rt,v.foldr=Ft,v.include=Ot,v.inject=Rt,Ut(function(){var n={};return tr(v,function(t,e){v.prototype[e]||(n[e]=t)}),n}(),false),v.first=Lt,v.last=function(n,t,e){var r=0,u=n?n.length:0;if(typeof t!="number"&&null!=t){var o=u;for(t=v.createCallback(t,e,3);o--&&t(n[o],o,n);)r++}else if(r=t,null==r||e)return n?n[u-1]:h;return s(n,Be(0,u-r))},v.sample=function(n,t,e){return n&&typeof n.length!="number"?n=Et(n):Le.unindexedChars&&kt(n)&&(n=n.split("")),null==t||e?n?n[lt(0,n.length-1)]:h:(n=Tt(n),n.length=Pe(Be(0,t),n.length),n)
+},v.take=Lt,v.head=Lt,tr(v,function(n,t){var e="sample"!==t;v.prototype[t]||(v.prototype[t]=function(t,r){var u=this.__chain__,o=n(this.__wrapped__,t,r);return u||null!=t&&(!r||e&&typeof t=="function")?new y(o,u):o})}),v.VERSION="2.4.1",v.prototype.chain=function(){return this.__chain__=true,this},v.prototype.toString=function(){return ie(this.__wrapped__)},v.prototype.value=Yt,v.prototype.valueOf=Yt,Xe(["join","pop","shift"],function(n){var t=fe[n];v.prototype[n]=function(){var n=this.__chain__,e=t.apply(this.__wrapped__,arguments);
+return n?new y(e,n):e}}),Xe(["push","reverse","sort","unshift"],function(n){var t=fe[n];v.prototype[n]=function(){return t.apply(this.__wrapped__,arguments),this}}),Xe(["concat","slice","splice"],function(n){var t=fe[n];v.prototype[n]=function(){return new y(t.apply(this.__wrapped__,arguments),this.__chain__)}}),Le.spliceObjects||Xe(["pop","shift","splice"],function(n){var t=fe[n],e="splice"==n;v.prototype[n]=function(){var n=this.__chain__,r=this.__wrapped__,u=t.apply(r,arguments);return 0===r.length&&delete r[0],n||e?new y(u,n):u
+}}),v}var h,v=[],y=[],m=0,d={},b=+new Date+"",_=75,w=40,j=" \t\x0B\f\xa0\ufeff\n\r\u2028\u2029\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000",x=/\b__p\+='';/g,C=/\b(__p\+=)''\+/g,E=/(__e\(.*?\)|\b__t\))\+'';/g,O=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,S=/\w*$/,A=/^\s*function[ \n\r\t]+\w/,I=/<%=([\s\S]+?)%>/g,D=RegExp("^["+j+"]*0+(?=.$)"),N=/($^)/,B=/\bthis\b/,P=/['\n\r\t\u2028\u2029\\]/g,R="Array Boolean Date Error Function Math Number Object RegExp String _ attachEvent clearTimeout isFinite isNaN parseInt setTimeout".split(" "),F="constructor hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString valueOf".split(" "),T="[object Arguments]",$="[object Array]",L="[object Boolean]",z="[object Date]",q="[object Error]",K="[object Function]",W="[object Number]",G="[object Object]",J="[object RegExp]",M="[object String]",V={};
+V[K]=false,V[T]=V[$]=V[L]=V[z]=V[W]=V[G]=V[J]=V[M]=true;var H={leading:false,maxWait:0,trailing:false},U={configurable:false,enumerable:false,value:null,writable:false},Q={a:"",b:null,c:"",d:"",e:"",v:null,g:"",h:null,support:null,i:"",j:false},X={"boolean":false,"function":true,object:true,number:false,string:false,undefined:false},Y={"\\":"\\","'":"'","\n":"n","\r":"r","\t":"t","\u2028":"u2028","\u2029":"u2029"},Z=X[typeof window]&&window||this,nt=X[typeof exports]&&exports&&!exports.nodeType&&exports,tt=X[typeof module]&&module&&!module.nodeType&&module,et=tt&&tt.exports===nt&&nt,rt=X[typeof global]&&global;
+!rt||rt.global!==rt&&rt.window!==rt||(Z=rt);var ut=g();typeof define=="function"&&typeof define.amd=="object"&&define.amd?(Z._=ut, define(function(){return ut})):nt&&tt?et?(tt.exports=ut)._=ut:nt._=ut:Z._=ut}).call(this);
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lodash/dist/lodash.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lodash/dist/lodash.js
new file mode 100644
index 0000000..d653e5a
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lodash/dist/lodash.js
@@ -0,0 +1,6785 @@
+/**
+ * @license
+ * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
+ * Build: `lodash modern -o ./dist/lodash.js`
+ * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
+ * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
+ * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Available under MIT license <http://lodash.com/license>
+ */
+;(function() {
+
+  /** Used as a safe reference for `undefined` in pre ES5 environments */
+  var undefined;
+
+  /** Used to pool arrays and objects used internally */
+  var arrayPool = [],
+      objectPool = [];
+
+  /** Used to generate unique IDs */
+  var idCounter = 0;
+
+  /** Used to prefix keys to avoid issues with `__proto__` and properties on `Object.prototype` */
+  var keyPrefix = +new Date + '';
+
+  /** Used as the size when optimizations are enabled for large arrays */
+  var largeArraySize = 75;
+
+  /** Used as the max size of the `arrayPool` and `objectPool` */
+  var maxPoolSize = 40;
+
+  /** Used to detect and test whitespace */
+  var whitespace = (
+    // whitespace
+    ' \t\x0B\f\xA0\ufeff' +
+
+    // line terminators
+    '\n\r\u2028\u2029' +
+
+    // unicode category "Zs" space separators
+    '\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000'
+  );
+
+  /** Used to match empty string literals in compiled template source */
+  var reEmptyStringLeading = /\b__p \+= '';/g,
+      reEmptyStringMiddle = /\b(__p \+=) '' \+/g,
+      reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g;
+
+  /**
+   * Used to match ES6 template delimiters
+   * http://people.mozilla.org/~jorendorff/es6-draft.html#sec-literals-string-literals
+   */
+  var reEsTemplate = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g;
+
+  /** Used to match regexp flags from their coerced string values */
+  var reFlags = /\w*$/;
+
+  /** Used to detected named functions */
+  var reFuncName = /^\s*function[ \n\r\t]+\w/;
+
+  /** Used to match "interpolate" template delimiters */
+  var reInterpolate = /<%=([\s\S]+?)%>/g;
+
+  /** Used to match leading whitespace and zeros to be removed */
+  var reLeadingSpacesAndZeros = RegExp('^[' + whitespace + ']*0+(?=.$)');
+
+  /** Used to ensure capturing order of template delimiters */
+  var reNoMatch = /($^)/;
+
+  /** Used to detect functions containing a `this` reference */
+  var reThis = /\bthis\b/;
+
+  /** Used to match unescaped characters in compiled string literals */
+  var reUnescapedString = /['\n\r\t\u2028\u2029\\]/g;
+
+  /** Used to assign default `context` object properties */
+  var contextProps = [
+    'Array', 'Boolean', 'Date', 'Function', 'Math', 'Number', 'Object',
+    'RegExp', 'String', '_', 'attachEvent', 'clearTimeout', 'isFinite', 'isNaN',
+    'parseInt', 'setTimeout'
+  ];
+
+  /** Used to make template sourceURLs easier to identify */
+  var templateCounter = 0;
+
+  /** `Object#toString` result shortcuts */
+  var argsClass = '[object Arguments]',
+      arrayClass = '[object Array]',
+      boolClass = '[object Boolean]',
+      dateClass = '[object Date]',
+      funcClass = '[object Function]',
+      numberClass = '[object Number]',
+      objectClass = '[object Object]',
+      regexpClass = '[object RegExp]',
+      stringClass = '[object String]';
+
+  /** Used to identify object classifications that `_.clone` supports */
+  var cloneableClasses = {};
+  cloneableClasses[funcClass] = false;
+  cloneableClasses[argsClass] = cloneableClasses[arrayClass] =
+  cloneableClasses[boolClass] = cloneableClasses[dateClass] =
+  cloneableClasses[numberClass] = cloneableClasses[objectClass] =
+  cloneableClasses[regexpClass] = cloneableClasses[stringClass] = true;
+
+  /** Used as an internal `_.debounce` options object */
+  var debounceOptions = {
+    'leading': false,
+    'maxWait': 0,
+    'trailing': false
+  };
+
+  /** Used as the property descriptor for `__bindData__` */
+  var descriptor = {
+    'configurable': false,
+    'enumerable': false,
+    'value': null,
+    'writable': false
+  };
+
+  /** Used to determine if values are of the language type Object */
+  var objectTypes = {
+    'boolean': false,
+    'function': true,
+    'object': true,
+    'number': false,
+    'string': false,
+    'undefined': false
+  };
+
+  /** Used to escape characters for inclusion in compiled string literals */
+  var stringEscapes = {
+    '\\': '\\',
+    "'": "'",
+    '\n': 'n',
+    '\r': 'r',
+    '\t': 't',
+    '\u2028': 'u2028',
+    '\u2029': 'u2029'
+  };
+
+  /** Used as a reference to the global object */
+  var root = (objectTypes[typeof window] && window) || this;
+
+  /** Detect free variable `exports` */
+  var freeExports = objectTypes[typeof exports] && exports && !exports.nodeType && exports;
+
+  /** Detect free variable `module` */
+  var freeModule = objectTypes[typeof module] && module && !module.nodeType && module;
+
+  /** Detect the popular CommonJS extension `module.exports` */
+  var moduleExports = freeModule && freeModule.exports === freeExports && freeExports;
+
+  /** Detect free variable `global` from Node.js or Browserified code and use it as `root` */
+  var freeGlobal = objectTypes[typeof global] && global;
+  if (freeGlobal && (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal)) {
+    root = freeGlobal;
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * The base implementation of `_.indexOf` without support for binary searches
+   * or `fromIndex` constraints.
+   *
+   * @private
+   * @param {Array} array The array to search.
+   * @param {*} value The value to search for.
+   * @param {number} [fromIndex=0] The index to search from.
+   * @returns {number} Returns the index of the matched value or `-1`.
+   */
+  function baseIndexOf(array, value, fromIndex) {
+    var index = (fromIndex || 0) - 1,
+        length = array ? array.length : 0;
+
+    while (++index < length) {
+      if (array[index] === value) {
+        return index;
+      }
+    }
+    return -1;
+  }
+
+  /**
+   * An implementation of `_.contains` for cache objects that mimics the return
+   * signature of `_.indexOf` by returning `0` if the value is found, else `-1`.
+   *
+   * @private
+   * @param {Object} cache The cache object to inspect.
+   * @param {*} value The value to search for.
+   * @returns {number} Returns `0` if `value` is found, else `-1`.
+   */
+  function cacheIndexOf(cache, value) {
+    var type = typeof value;
+    cache = cache.cache;
+
+    if (type == 'boolean' || value == null) {
+      return cache[value] ? 0 : -1;
+    }
+    if (type != 'number' && type != 'string') {
+      type = 'object';
+    }
+    var key = type == 'number' ? value : keyPrefix + value;
+    cache = (cache = cache[type]) && cache[key];
+
+    return type == 'object'
+      ? (cache && baseIndexOf(cache, value) > -1 ? 0 : -1)
+      : (cache ? 0 : -1);
+  }
+
+  /**
+   * Adds a given value to the corresponding cache object.
+   *
+   * @private
+   * @param {*} value The value to add to the cache.
+   */
+  function cachePush(value) {
+    var cache = this.cache,
+        type = typeof value;
+
+    if (type == 'boolean' || value == null) {
+      cache[value] = true;
+    } else {
+      if (type != 'number' && type != 'string') {
+        type = 'object';
+      }
+      var key = type == 'number' ? value : keyPrefix + value,
+          typeCache = cache[type] || (cache[type] = {});
+
+      if (type == 'object') {
+        (typeCache[key] || (typeCache[key] = [])).push(value);
+      } else {
+        typeCache[key] = true;
+      }
+    }
+  }
+
+  /**
+   * Used by `_.max` and `_.min` as the default callback when a given
+   * collection is a string value.
+   *
+   * @private
+   * @param {string} value The character to inspect.
+   * @returns {number} Returns the code unit of given character.
+   */
+  function charAtCallback(value) {
+    return value.charCodeAt(0);
+  }
+
+  /**
+   * Used by `sortBy` to compare transformed `collection` elements, stable sorting
+   * them in ascending order.
+   *
+   * @private
+   * @param {Object} a The object to compare to `b`.
+   * @param {Object} b The object to compare to `a`.
+   * @returns {number} Returns the sort order indicator of `1` or `-1`.
+   */
+  function compareAscending(a, b) {
+    var ac = a.criteria,
+        bc = b.criteria,
+        index = -1,
+        length = ac.length;
+
+    while (++index < length) {
+      var value = ac[index],
+          other = bc[index];
+
+      if (value !== other) {
+        if (value > other || typeof value == 'undefined') {
+          return 1;
+        }
+        if (value < other || typeof other == 'undefined') {
+          return -1;
+        }
+      }
+    }
+    // Fixes an `Array#sort` bug in the JS engine embedded in Adobe applications
+    // that causes it, under certain circumstances, to return the same value for
+    // `a` and `b`. See https://github.com/jashkenas/underscore/pull/1247
+    //
+    // This also ensures a stable sort in V8 and other engines.
+    // See http://code.google.com/p/v8/issues/detail?id=90
+    return a.index - b.index;
+  }
+
+  /**
+   * Creates a cache object to optimize linear searches of large arrays.
+   *
+   * @private
+   * @param {Array} [array=[]] The array to search.
+   * @returns {null|Object} Returns the cache object or `null` if caching should not be used.
+   */
+  function createCache(array) {
+    var index = -1,
+        length = array.length,
+        first = array[0],
+        mid = array[(length / 2) | 0],
+        last = array[length - 1];
+
+    if (first && typeof first == 'object' &&
+        mid && typeof mid == 'object' && last && typeof last == 'object') {
+      return false;
+    }
+    var cache = getObject();
+    cache['false'] = cache['null'] = cache['true'] = cache['undefined'] = false;
+
+    var result = getObject();
+    result.array = array;
+    result.cache = cache;
+    result.push = cachePush;
+
+    while (++index < length) {
+      result.push(array[index]);
+    }
+    return result;
+  }
+
+  /**
+   * Used by `template` to escape characters for inclusion in compiled
+   * string literals.
+   *
+   * @private
+   * @param {string} match The matched character to escape.
+   * @returns {string} Returns the escaped character.
+   */
+  function escapeStringChar(match) {
+    return '\\' + stringEscapes[match];
+  }
+
+  /**
+   * Gets an array from the array pool or creates a new one if the pool is empty.
+   *
+   * @private
+   * @returns {Array} The array from the pool.
+   */
+  function getArray() {
+    return arrayPool.pop() || [];
+  }
+
+  /**
+   * Gets an object from the object pool or creates a new one if the pool is empty.
+   *
+   * @private
+   * @returns {Object} The object from the pool.
+   */
+  function getObject() {
+    return objectPool.pop() || {
+      'array': null,
+      'cache': null,
+      'criteria': null,
+      'false': false,
+      'index': 0,
+      'null': false,
+      'number': null,
+      'object': null,
+      'push': null,
+      'string': null,
+      'true': false,
+      'undefined': false,
+      'value': null
+    };
+  }
+
+  /**
+   * Releases the given array back to the array pool.
+   *
+   * @private
+   * @param {Array} [array] The array to release.
+   */
+  function releaseArray(array) {
+    array.length = 0;
+    if (arrayPool.length < maxPoolSize) {
+      arrayPool.push(array);
+    }
+  }
+
+  /**
+   * Releases the given object back to the object pool.
+   *
+   * @private
+   * @param {Object} [object] The object to release.
+   */
+  function releaseObject(object) {
+    var cache = object.cache;
+    if (cache) {
+      releaseObject(cache);
+    }
+    object.array = object.cache = object.criteria = object.object = object.number = object.string = object.value = null;
+    if (objectPool.length < maxPoolSize) {
+      objectPool.push(object);
+    }
+  }
+
+  /**
+   * Slices the `collection` from the `start` index up to, but not including,
+   * the `end` index.
+   *
+   * Note: This function is used instead of `Array#slice` to support node lists
+   * in IE < 9 and to ensure dense arrays are returned.
+   *
+   * @private
+   * @param {Array|Object|string} collection The collection to slice.
+   * @param {number} start The start index.
+   * @param {number} end The end index.
+   * @returns {Array} Returns the new array.
+   */
+  function slice(array, start, end) {
+    start || (start = 0);
+    if (typeof end == 'undefined') {
+      end = array ? array.length : 0;
+    }
+    var index = -1,
+        length = end - start || 0,
+        result = Array(length < 0 ? 0 : length);
+
+    while (++index < length) {
+      result[index] = array[start + index];
+    }
+    return result;
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Create a new `lodash` function using the given context object.
+   *
+   * @static
+   * @memberOf _
+   * @category Utilities
+   * @param {Object} [context=root] The context object.
+   * @returns {Function} Returns the `lodash` function.
+   */
+  function runInContext(context) {
+    // Avoid issues with some ES3 environments that attempt to use values, named
+    // after built-in constructors like `Object`, for the creation of literals.
+    // ES5 clears this up by stating that literals must use built-in constructors.
+    // See http://es5.github.io/#x11.1.5.
+    context = context ? _.defaults(root.Object(), context, _.pick(root, contextProps)) : root;
+
+    /** Native constructor references */
+    var Array = context.Array,
+        Boolean = context.Boolean,
+        Date = context.Date,
+        Function = context.Function,
+        Math = context.Math,
+        Number = context.Number,
+        Object = context.Object,
+        RegExp = context.RegExp,
+        String = context.String,
+        TypeError = context.TypeError;
+
+    /**
+     * Used for `Array` method references.
+     *
+     * Normally `Array.prototype` would suffice, however, using an array literal
+     * avoids issues in Narwhal.
+     */
+    var arrayRef = [];
+
+    /** Used for native method references */
+    var objectProto = Object.prototype;
+
+    /** Used to restore the original `_` reference in `noConflict` */
+    var oldDash = context._;
+
+    /** Used to resolve the internal [[Class]] of values */
+    var toString = objectProto.toString;
+
+    /** Used to detect if a method is native */
+    var reNative = RegExp('^' +
+      String(toString)
+        .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
+        .replace(/toString| for [^\]]+/g, '.*?') + '$'
+    );
+
+    /** Native method shortcuts */
+    var ceil = Math.ceil,
+        clearTimeout = context.clearTimeout,
+        floor = Math.floor,
+        fnToString = Function.prototype.toString,
+        getPrototypeOf = isNative(getPrototypeOf = Object.getPrototypeOf) && getPrototypeOf,
+        hasOwnProperty = objectProto.hasOwnProperty,
+        push = arrayRef.push,
+        setTimeout = context.setTimeout,
+        splice = arrayRef.splice,
+        unshift = arrayRef.unshift;
+
+    /** Used to set meta data on functions */
+    var defineProperty = (function() {
+      // IE 8 only accepts DOM elements
+      try {
+        var o = {},
+            func = isNative(func = Object.defineProperty) && func,
+            result = func(o, o, o) && func;
+      } catch(e) { }
+      return result;
+    }());
+
+    /* Native method shortcuts for methods with the same name as other `lodash` methods */
+    var nativeCreate = isNative(nativeCreate = Object.create) && nativeCreate,
+        nativeIsArray = isNative(nativeIsArray = Array.isArray) && nativeIsArray,
+        nativeIsFinite = context.isFinite,
+        nativeIsNaN = context.isNaN,
+        nativeKeys = isNative(nativeKeys = Object.keys) && nativeKeys,
+        nativeMax = Math.max,
+        nativeMin = Math.min,
+        nativeParseInt = context.parseInt,
+        nativeRandom = Math.random;
+
+    /** Used to lookup a built-in constructor by [[Class]] */
+    var ctorByClass = {};
+    ctorByClass[arrayClass] = Array;
+    ctorByClass[boolClass] = Boolean;
+    ctorByClass[dateClass] = Date;
+    ctorByClass[funcClass] = Function;
+    ctorByClass[objectClass] = Object;
+    ctorByClass[numberClass] = Number;
+    ctorByClass[regexpClass] = RegExp;
+    ctorByClass[stringClass] = String;
+
+    /*--------------------------------------------------------------------------*/
+
+    /**
+     * Creates a `lodash` object which wraps the given value to enable intuitive
+     * method chaining.
+     *
+     * In addition to Lo-Dash methods, wrappers also have the following `Array` methods:
+     * `concat`, `join`, `pop`, `push`, `reverse`, `shift`, `slice`, `sort`, `splice`,
+     * and `unshift`
+     *
+     * Chaining is supported in custom builds as long as the `value` method is
+     * implicitly or explicitly included in the build.
+     *
+     * The chainable wrapper functions are:
+     * `after`, `assign`, `bind`, `bindAll`, `bindKey`, `chain`, `compact`,
+     * `compose`, `concat`, `countBy`, `create`, `createCallback`, `curry`,
+     * `debounce`, `defaults`, `defer`, `delay`, `difference`, `filter`, `flatten`,
+     * `forEach`, `forEachRight`, `forIn`, `forInRight`, `forOwn`, `forOwnRight`,
+     * `functions`, `groupBy`, `indexBy`, `initial`, `intersection`, `invert`,
+     * `invoke`, `keys`, `map`, `max`, `memoize`, `merge`, `min`, `object`, `omit`,
+     * `once`, `pairs`, `partial`, `partialRight`, `pick`, `pluck`, `pull`, `push`,
+     * `range`, `reject`, `remove`, `rest`, `reverse`, `shuffle`, `slice`, `sort`,
+     * `sortBy`, `splice`, `tap`, `throttle`, `times`, `toArray`, `transform`,
+     * `union`, `uniq`, `unshift`, `unzip`, `values`, `where`, `without`, `wrap`,
+     * and `zip`
+     *
+     * The non-chainable wrapper functions are:
+     * `clone`, `cloneDeep`, `contains`, `escape`, `every`, `find`, `findIndex`,
+     * `findKey`, `findLast`, `findLastIndex`, `findLastKey`, `has`, `identity`,
+     * `indexOf`, `isArguments`, `isArray`, `isBoolean`, `isDate`, `isElement`,
+     * `isEmpty`, `isEqual`, `isFinite`, `isFunction`, `isNaN`, `isNull`, `isNumber`,
+     * `isObject`, `isPlainObject`, `isRegExp`, `isString`, `isUndefined`, `join`,
+     * `lastIndexOf`, `mixin`, `noConflict`, `parseInt`, `pop`, `random`, `reduce`,
+     * `reduceRight`, `result`, `shift`, `size`, `some`, `sortedIndex`, `runInContext`,
+     * `template`, `unescape`, `uniqueId`, and `value`
+     *
+     * The wrapper functions `first` and `last` return wrapped values when `n` is
+     * provided, otherwise they return unwrapped values.
+     *
+     * Explicit chaining can be enabled by using the `_.chain` method.
+     *
+     * @name _
+     * @constructor
+     * @category Chaining
+     * @param {*} value The value to wrap in a `lodash` instance.
+     * @returns {Object} Returns a `lodash` instance.
+     * @example
+     *
+     * var wrapped = _([1, 2, 3]);
+     *
+     * // returns an unwrapped value
+     * wrapped.reduce(function(sum, num) {
+     *   return sum + num;
+     * });
+     * // => 6
+     *
+     * // returns a wrapped value
+     * var squares = wrapped.map(function(num) {
+     *   return num * num;
+     * });
+     *
+     * _.isArray(squares);
+     * // => false
+     *
+     * _.isArray(squares.value());
+     * // => true
+     */
+    function lodash(value) {
+      // don't wrap if already wrapped, even if wrapped by a different `lodash` constructor
+      return (value && typeof value == 'object' && !isArray(value) && hasOwnProperty.call(value, '__wrapped__'))
+       ? value
+       : new lodashWrapper(value);
+    }
+
+    /**
+     * A fast path for creating `lodash` wrapper objects.
+     *
+     * @private
+     * @param {*} value The value to wrap in a `lodash` instance.
+     * @param {boolean} chainAll A flag to enable chaining for all methods
+     * @returns {Object} Returns a `lodash` instance.
+     */
+    function lodashWrapper(value, chainAll) {
+      this.__chain__ = !!chainAll;
+      this.__wrapped__ = value;
+    }
+    // ensure `new lodashWrapper` is an instance of `lodash`
+    lodashWrapper.prototype = lodash.prototype;
+
+    /**
+     * An object used to flag environments features.
+     *
+     * @static
+     * @memberOf _
+     * @type Object
+     */
+    var support = lodash.support = {};
+
+    /**
+     * Detect if functions can be decompiled by `Function#toString`
+     * (all but PS3 and older Opera mobile browsers & avoided in Windows 8 apps).
+     *
+     * @memberOf _.support
+     * @type boolean
+     */
+    support.funcDecomp = !isNative(context.WinRTError) && reThis.test(runInContext);
+
+    /**
+     * Detect if `Function#name` is supported (all but IE).
+     *
+     * @memberOf _.support
+     * @type boolean
+     */
+    support.funcNames = typeof Function.name == 'string';
+
+    /**
+     * By default, the template delimiters used by Lo-Dash are similar to those in
+     * embedded Ruby (ERB). Change the following template settings to use alternative
+     * delimiters.
+     *
+     * @static
+     * @memberOf _
+     * @type Object
+     */
+    lodash.templateSettings = {
+
+      /**
+       * Used to detect `data` property values to be HTML-escaped.
+       *
+       * @memberOf _.templateSettings
+       * @type RegExp
+       */
+      'escape': /<%-([\s\S]+?)%>/g,
+
+      /**
+       * Used to detect code to be evaluated.
+       *
+       * @memberOf _.templateSettings
+       * @type RegExp
+       */
+      'evaluate': /<%([\s\S]+?)%>/g,
+
+      /**
+       * Used to detect `data` property values to inject.
+       *
+       * @memberOf _.templateSettings
+       * @type RegExp
+       */
+      'interpolate': reInterpolate,
+
+      /**
+       * Used to reference the data object in the template text.
+       *
+       * @memberOf _.templateSettings
+       * @type string
+       */
+      'variable': '',
+
+      /**
+       * Used to import variables into the compiled template.
+       *
+       * @memberOf _.templateSettings
+       * @type Object
+       */
+      'imports': {
+
+        /**
+         * A reference to the `lodash` function.
+         *
+         * @memberOf _.templateSettings.imports
+         * @type Function
+         */
+        '_': lodash
+      }
+    };
+
+    /*--------------------------------------------------------------------------*/
+
+    /**
+     * The base implementation of `_.bind` that creates the bound function and
+     * sets its meta data.
+     *
+     * @private
+     * @param {Array} bindData The bind data array.
+     * @returns {Function} Returns the new bound function.
+     */
+    function baseBind(bindData) {
+      var func = bindData[0],
+          partialArgs = bindData[2],
+          thisArg = bindData[4];
+
+      function bound() {
+        // `Function#bind` spec
+        // http://es5.github.io/#x15.3.4.5
+        if (partialArgs) {
+          // avoid `arguments` object deoptimizations by using `slice` instead
+          // of `Array.prototype.slice.call` and not assigning `arguments` to a
+          // variable as a ternary expression
+          var args = slice(partialArgs);
+          push.apply(args, arguments);
+        }
+        // mimic the constructor's `return` behavior
+        // http://es5.github.io/#x13.2.2
+        if (this instanceof bound) {
+          // ensure `new bound` is an instance of `func`
+          var thisBinding = baseCreate(func.prototype),
+              result = func.apply(thisBinding, args || arguments);
+          return isObject(result) ? result : thisBinding;
+        }
+        return func.apply(thisArg, args || arguments);
+      }
+      setBindData(bound, bindData);
+      return bound;
+    }
+
+    /**
+     * The base implementation of `_.clone` without argument juggling or support
+     * for `thisArg` binding.
+     *
+     * @private
+     * @param {*} value The value to clone.
+     * @param {boolean} [isDeep=false] Specify a deep clone.
+     * @param {Function} [callback] The function to customize cloning values.
+     * @param {Array} [stackA=[]] Tracks traversed source objects.
+     * @param {Array} [stackB=[]] Associates clones with source counterparts.
+     * @returns {*} Returns the cloned value.
+     */
+    function baseClone(value, isDeep, callback, stackA, stackB) {
+      if (callback) {
+        var result = callback(value);
+        if (typeof result != 'undefined') {
+          return result;
+        }
+      }
+      // inspect [[Class]]
+      var isObj = isObject(value);
+      if (isObj) {
+        var className = toString.call(value);
+        if (!cloneableClasses[className]) {
+          return value;
+        }
+        var ctor = ctorByClass[className];
+        switch (className) {
+          case boolClass:
+          case dateClass:
+            return new ctor(+value);
+
+          case numberClass:
+          case stringClass:
+            return new ctor(value);
+
+          case regexpClass:
+            result = ctor(value.source, reFlags.exec(value));
+            result.lastIndex = value.lastIndex;
+            return result;
+        }
+      } else {
+        return value;
+      }
+      var isArr = isArray(value);
+      if (isDeep) {
+        // check for circular references and return corresponding clone
+        var initedStack = !stackA;
+        stackA || (stackA = getArray());
+        stackB || (stackB = getArray());
+
+        var length = stackA.length;
+        while (length--) {
+          if (stackA[length] == value) {
+            return stackB[length];
+          }
+        }
+        result = isArr ? ctor(value.length) : {};
+      }
+      else {
+        result = isArr ? slice(value) : assign({}, value);
+      }
+      // add array properties assigned by `RegExp#exec`
+      if (isArr) {
+        if (hasOwnProperty.call(value, 'index')) {
+          result.index = value.index;
+        }
+        if (hasOwnProperty.call(value, 'input')) {
+          result.input = value.input;
+        }
+      }
+      // exit for shallow clone
+      if (!isDeep) {
+        return result;
+      }
+      // add the source value to the stack of traversed objects
+      // and associate it with its clone
+      stackA.push(value);
+      stackB.push(result);
+
+      // recursively populate clone (susceptible to call stack limits)
+      (isArr ? forEach : forOwn)(value, function(objValue, key) {
+        result[key] = baseClone(objValue, isDeep, callback, stackA, stackB);
+      });
+
+      if (initedStack) {
+        releaseArray(stackA);
+        releaseArray(stackB);
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.create` without support for assigning
+     * properties to the created object.
+     *
+     * @private
+     * @param {Object} prototype The object to inherit from.
+     * @returns {Object} Returns the new object.
+     */
+    function baseCreate(prototype, properties) {
+      return isObject(prototype) ? nativeCreate(prototype) : {};
+    }
+    // fallback for browsers without `Object.create`
+    if (!nativeCreate) {
+      baseCreate = (function() {
+        function Object() {}
+        return function(prototype) {
+          if (isObject(prototype)) {
+            Object.prototype = prototype;
+            var result = new Object;
+            Object.prototype = null;
+          }
+          return result || context.Object();
+        };
+      }());
+    }
+
+    /**
+     * The base implementation of `_.createCallback` without support for creating
+     * "_.pluck" or "_.where" style callbacks.
+     *
+     * @private
+     * @param {*} [func=identity] The value to convert to a callback.
+     * @param {*} [thisArg] The `this` binding of the created callback.
+     * @param {number} [argCount] The number of arguments the callback accepts.
+     * @returns {Function} Returns a callback function.
+     */
+    function baseCreateCallback(func, thisArg, argCount) {
+      if (typeof func != 'function') {
+        return identity;
+      }
+      // exit early for no `thisArg` or already bound by `Function#bind`
+      if (typeof thisArg == 'undefined' || !('prototype' in func)) {
+        return func;
+      }
+      var bindData = func.__bindData__;
+      if (typeof bindData == 'undefined') {
+        if (support.funcNames) {
+          bindData = !func.name;
+        }
+        bindData = bindData || !support.funcDecomp;
+        if (!bindData) {
+          var source = fnToString.call(func);
+          if (!support.funcNames) {
+            bindData = !reFuncName.test(source);
+          }
+          if (!bindData) {
+            // checks if `func` references the `this` keyword and stores the result
+            bindData = reThis.test(source);
+            setBindData(func, bindData);
+          }
+        }
+      }
+      // exit early if there are no `this` references or `func` is bound
+      if (bindData === false || (bindData !== true && bindData[1] & 1)) {
+        return func;
+      }
+      switch (argCount) {
+        case 1: return function(value) {
+          return func.call(thisArg, value);
+        };
+        case 2: return function(a, b) {
+          return func.call(thisArg, a, b);
+        };
+        case 3: return function(value, index, collection) {
+          return func.call(thisArg, value, index, collection);
+        };
+        case 4: return function(accumulator, value, index, collection) {
+          return func.call(thisArg, accumulator, value, index, collection);
+        };
+      }
+      return bind(func, thisArg);
+    }
+
+    /**
+     * The base implementation of `createWrapper` that creates the wrapper and
+     * sets its meta data.
+     *
+     * @private
+     * @param {Array} bindData The bind data array.
+     * @returns {Function} Returns the new function.
+     */
+    function baseCreateWrapper(bindData) {
+      var func = bindData[0],
+          bitmask = bindData[1],
+          partialArgs = bindData[2],
+          partialRightArgs = bindData[3],
+          thisArg = bindData[4],
+          arity = bindData[5];
+
+      var isBind = bitmask & 1,
+          isBindKey = bitmask & 2,
+          isCurry = bitmask & 4,
+          isCurryBound = bitmask & 8,
+          key = func;
+
+      function bound() {
+        var thisBinding = isBind ? thisArg : this;
+        if (partialArgs) {
+          var args = slice(partialArgs);
+          push.apply(args, arguments);
+        }
+        if (partialRightArgs || isCurry) {
+          args || (args = slice(arguments));
+          if (partialRightArgs) {
+            push.apply(args, partialRightArgs);
+          }
+          if (isCurry && args.length < arity) {
+            bitmask |= 16 & ~32;
+            return baseCreateWrapper([func, (isCurryBound ? bitmask : bitmask & ~3), args, null, thisArg, arity]);
+          }
+        }
+        args || (args = arguments);
+        if (isBindKey) {
+          func = thisBinding[key];
+        }
+        if (this instanceof bound) {
+          thisBinding = baseCreate(func.prototype);
+          var result = func.apply(thisBinding, args);
+          return isObject(result) ? result : thisBinding;
+        }
+        return func.apply(thisBinding, args);
+      }
+      setBindData(bound, bindData);
+      return bound;
+    }
+
+    /**
+     * The base implementation of `_.difference` that accepts a single array
+     * of values to exclude.
+     *
+     * @private
+     * @param {Array} array The array to process.
+     * @param {Array} [values] The array of values to exclude.
+     * @returns {Array} Returns a new array of filtered values.
+     */
+    function baseDifference(array, values) {
+      var index = -1,
+          indexOf = getIndexOf(),
+          length = array ? array.length : 0,
+          isLarge = length >= largeArraySize && indexOf === baseIndexOf,
+          result = [];
+
+      if (isLarge) {
+        var cache = createCache(values);
+        if (cache) {
+          indexOf = cacheIndexOf;
+          values = cache;
+        } else {
+          isLarge = false;
+        }
+      }
+      while (++index < length) {
+        var value = array[index];
+        if (indexOf(values, value) < 0) {
+          result.push(value);
+        }
+      }
+      if (isLarge) {
+        releaseObject(values);
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.flatten` without support for callback
+     * shorthands or `thisArg` binding.
+     *
+     * @private
+     * @param {Array} array The array to flatten.
+     * @param {boolean} [isShallow=false] A flag to restrict flattening to a single level.
+     * @param {boolean} [isStrict=false] A flag to restrict flattening to arrays and `arguments` objects.
+     * @param {number} [fromIndex=0] The index to start from.
+     * @returns {Array} Returns a new flattened array.
+     */
+    function baseFlatten(array, isShallow, isStrict, fromIndex) {
+      var index = (fromIndex || 0) - 1,
+          length = array ? array.length : 0,
+          result = [];
+
+      while (++index < length) {
+        var value = array[index];
+
+        if (value && typeof value == 'object' && typeof value.length == 'number'
+            && (isArray(value) || isArguments(value))) {
+          // recursively flatten arrays (susceptible to call stack limits)
+          if (!isShallow) {
+            value = baseFlatten(value, isShallow, isStrict);
+          }
+          var valIndex = -1,
+              valLength = value.length,
+              resIndex = result.length;
+
+          result.length += valLength;
+          while (++valIndex < valLength) {
+            result[resIndex++] = value[valIndex];
+          }
+        } else if (!isStrict) {
+          result.push(value);
+        }
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.isEqual`, without support for `thisArg` binding,
+     * that allows partial "_.where" style comparisons.
+     *
+     * @private
+     * @param {*} a The value to compare.
+     * @param {*} b The other value to compare.
+     * @param {Function} [callback] The function to customize comparing values.
+     * @param {Function} [isWhere=false] A flag to indicate performing partial comparisons.
+     * @param {Array} [stackA=[]] Tracks traversed `a` objects.
+     * @param {Array} [stackB=[]] Tracks traversed `b` objects.
+     * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
+     */
+    function baseIsEqual(a, b, callback, isWhere, stackA, stackB) {
+      // used to indicate that when comparing objects, `a` has at least the properties of `b`
+      if (callback) {
+        var result = callback(a, b);
+        if (typeof result != 'undefined') {
+          return !!result;
+        }
+      }
+      // exit early for identical values
+      if (a === b) {
+        // treat `+0` vs. `-0` as not equal
+        return a !== 0 || (1 / a == 1 / b);
+      }
+      var type = typeof a,
+          otherType = typeof b;
+
+      // exit early for unlike primitive values
+      if (a === a &&
+          !(a && objectTypes[type]) &&
+          !(b && objectTypes[otherType])) {
+        return false;
+      }
+      // exit early for `null` and `undefined` avoiding ES3's Function#call behavior
+      // http://es5.github.io/#x15.3.4.4
+      if (a == null || b == null) {
+        return a === b;
+      }
+      // compare [[Class]] names
+      var className = toString.call(a),
+          otherClass = toString.call(b);
+
+      if (className == argsClass) {
+        className = objectClass;
+      }
+      if (otherClass == argsClass) {
+        otherClass = objectClass;
+      }
+      if (className != otherClass) {
+        return false;
+      }
+      switch (className) {
+        case boolClass:
+        case dateClass:
+          // coerce dates and booleans to numbers, dates to milliseconds and booleans
+          // to `1` or `0` treating invalid dates coerced to `NaN` as not equal
+          return +a == +b;
+
+        case numberClass:
+          // treat `NaN` vs. `NaN` as equal
+          return (a != +a)
+            ? b != +b
+            // but treat `+0` vs. `-0` as not equal
+            : (a == 0 ? (1 / a == 1 / b) : a == +b);
+
+        case regexpClass:
+        case stringClass:
+          // coerce regexes to strings (http://es5.github.io/#x15.10.6.4)
+          // treat string primitives and their corresponding object instances as equal
+          return a == String(b);
+      }
+      var isArr = className == arrayClass;
+      if (!isArr) {
+        // unwrap any `lodash` wrapped values
+        var aWrapped = hasOwnProperty.call(a, '__wrapped__'),
+            bWrapped = hasOwnProperty.call(b, '__wrapped__');
+
+        if (aWrapped || bWrapped) {
+          return baseIsEqual(aWrapped ? a.__wrapped__ : a, bWrapped ? b.__wrapped__ : b, callback, isWhere, stackA, stackB);
+        }
+        // exit for functions and DOM nodes
+        if (className != objectClass) {
+          return false;
+        }
+        // in older versions of Opera, `arguments` objects have `Array` constructors
+        var ctorA = a.constructor,
+            ctorB = b.constructor;
+
+        // non `Object` object instances with different constructors are not equal
+        if (ctorA != ctorB &&
+              !(isFunction(ctorA) && ctorA instanceof ctorA && isFunction(ctorB) && ctorB instanceof ctorB) &&
+              ('constructor' in a && 'constructor' in b)
+            ) {
+          return false;
+        }
+      }
+      // assume cyclic structures are equal
+      // the algorithm for detecting cyclic structures is adapted from ES 5.1
+      // section 15.12.3, abstract operation `JO` (http://es5.github.io/#x15.12.3)
+      var initedStack = !stackA;
+      stackA || (stackA = getArray());
+      stackB || (stackB = getArray());
+
+      var length = stackA.length;
+      while (length--) {
+        if (stackA[length] == a) {
+          return stackB[length] == b;
+        }
+      }
+      var size = 0;
+      result = true;
+
+      // add `a` and `b` to the stack of traversed objects
+      stackA.push(a);
+      stackB.push(b);
+
+      // recursively compare objects and arrays (susceptible to call stack limits)
+      if (isArr) {
+        // compare lengths to determine if a deep comparison is necessary
+        length = a.length;
+        size = b.length;
+        result = size == length;
+
+        if (result || isWhere) {
+          // deep compare the contents, ignoring non-numeric properties
+          while (size--) {
+            var index = length,
+                value = b[size];
+
+            if (isWhere) {
+              while (index--) {
+                if ((result = baseIsEqual(a[index], value, callback, isWhere, stackA, stackB))) {
+                  break;
+                }
+              }
+            } else if (!(result = baseIsEqual(a[size], value, callback, isWhere, stackA, stackB))) {
+              break;
+            }
+          }
+        }
+      }
+      else {
+        // deep compare objects using `forIn`, instead of `forOwn`, to avoid `Object.keys`
+        // which, in this case, is more costly
+        forIn(b, function(value, key, b) {
+          if (hasOwnProperty.call(b, key)) {
+            // count the number of properties.
+            size++;
+            // deep compare each property value.
+            return (result = hasOwnProperty.call(a, key) && baseIsEqual(a[key], value, callback, isWhere, stackA, stackB));
+          }
+        });
+
+        if (result && !isWhere) {
+          // ensure both objects have the same number of properties
+          forIn(a, function(value, key, a) {
+            if (hasOwnProperty.call(a, key)) {
+              // `size` will be `-1` if `a` has more properties than `b`
+              return (result = --size > -1);
+            }
+          });
+        }
+      }
+      stackA.pop();
+      stackB.pop();
+
+      if (initedStack) {
+        releaseArray(stackA);
+        releaseArray(stackB);
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.merge` without argument juggling or support
+     * for `thisArg` binding.
+     *
+     * @private
+     * @param {Object} object The destination object.
+     * @param {Object} source The source object.
+     * @param {Function} [callback] The function to customize merging properties.
+     * @param {Array} [stackA=[]] Tracks traversed source objects.
+     * @param {Array} [stackB=[]] Associates values with source counterparts.
+     */
+    function baseMerge(object, source, callback, stackA, stackB) {
+      (isArray(source) ? forEach : forOwn)(source, function(source, key) {
+        var found,
+            isArr,
+            result = source,
+            value = object[key];
+
+        if (source && ((isArr = isArray(source)) || isPlainObject(source))) {
+          // avoid merging previously merged cyclic sources
+          var stackLength = stackA.length;
+          while (stackLength--) {
+            if ((found = stackA[stackLength] == source)) {
+              value = stackB[stackLength];
+              break;
+            }
+          }
+          if (!found) {
+            var isShallow;
+            if (callback) {
+              result = callback(value, source);
+              if ((isShallow = typeof result != 'undefined')) {
+                value = result;
+              }
+            }
+            if (!isShallow) {
+              value = isArr
+                ? (isArray(value) ? value : [])
+                : (isPlainObject(value) ? value : {});
+            }
+            // add `source` and associated `value` to the stack of traversed objects
+            stackA.push(source);
+            stackB.push(value);
+
+            // recursively merge objects and arrays (susceptible to call stack limits)
+            if (!isShallow) {
+              baseMerge(value, source, callback, stackA, stackB);
+            }
+          }
+        }
+        else {
+          if (callback) {
+            result = callback(value, source);
+            if (typeof result == 'undefined') {
+              result = source;
+            }
+          }
+          if (typeof result != 'undefined') {
+            value = result;
+          }
+        }
+        object[key] = value;
+      });
+    }
+
+    /**
+     * The base implementation of `_.random` without argument juggling or support
+     * for returning floating-point numbers.
+     *
+     * @private
+     * @param {number} min The minimum possible value.
+     * @param {number} max The maximum possible value.
+     * @returns {number} Returns a random number.
+     */
+    function baseRandom(min, max) {
+      return min + floor(nativeRandom() * (max - min + 1));
+    }
+
+    /**
+     * The base implementation of `_.uniq` without support for callback shorthands
+     * or `thisArg` binding.
+     *
+     * @private
+     * @param {Array} array The array to process.
+     * @param {boolean} [isSorted=false] A flag to indicate that `array` is sorted.
+     * @param {Function} [callback] The function called per iteration.
+     * @returns {Array} Returns a duplicate-value-free array.
+     */
+    function baseUniq(array, isSorted, callback) {
+      var index = -1,
+          indexOf = getIndexOf(),
+          length = array ? array.length : 0,
+          result = [];
+
+      var isLarge = !isSorted && length >= largeArraySize && indexOf === baseIndexOf,
+          seen = (callback || isLarge) ? getArray() : result;
+
+      if (isLarge) {
+        var cache = createCache(seen);
+        indexOf = cacheIndexOf;
+        seen = cache;
+      }
+      while (++index < length) {
+        var value = array[index],
+            computed = callback ? callback(value, index, array) : value;
+
+        if (isSorted
+              ? !index || seen[seen.length - 1] !== computed
+              : indexOf(seen, computed) < 0
+            ) {
+          if (callback || isLarge) {
+            seen.push(computed);
+          }
+          result.push(value);
+        }
+      }
+      if (isLarge) {
+        releaseArray(seen.array);
+        releaseObject(seen);
+      } else if (callback) {
+        releaseArray(seen);
+      }
+      return result;
+    }
+
+    /**
+     * Creates a function that aggregates a collection, creating an object composed
+     * of keys generated from the results of running each element of the collection
+     * through a callback. The given `setter` function sets the keys and values
+     * of the composed object.
+     *
+     * @private
+     * @param {Function} setter The setter function.
+     * @returns {Function} Returns the new aggregator function.
+     */
+    function createAggregator(setter) {
+      return function(collection, callback, thisArg) {
+        var result = {};
+        callback = lodash.createCallback(callback, thisArg, 3);
+
+        var index = -1,
+            length = collection ? collection.length : 0;
+
+        if (typeof length == 'number') {
+          while (++index < length) {
+            var value = collection[index];
+            setter(result, value, callback(value, index, collection), collection);
+          }
+        } else {
+          forOwn(collection, function(value, key, collection) {
+            setter(result, value, callback(value, key, collection), collection);
+          });
+        }
+        return result;
+      };
+    }
+
+    /**
+     * Creates a function that, when called, either curries or invokes `func`
+     * with an optional `this` binding and partially applied arguments.
+     *
+     * @private
+     * @param {Function|string} func The function or method name to reference.
+     * @param {number} bitmask The bitmask of method flags to compose.
+     *  The bitmask may be composed of the following flags:
+     *  1 - `_.bind`
+     *  2 - `_.bindKey`
+     *  4 - `_.curry`
+     *  8 - `_.curry` (bound)
+     *  16 - `_.partial`
+     *  32 - `_.partialRight`
+     * @param {Array} [partialArgs] An array of arguments to prepend to those
+     *  provided to the new function.
+     * @param {Array} [partialRightArgs] An array of arguments to append to those
+     *  provided to the new function.
+     * @param {*} [thisArg] The `this` binding of `func`.
+     * @param {number} [arity] The arity of `func`.
+     * @returns {Function} Returns the new function.
+     */
+    function createWrapper(func, bitmask, partialArgs, partialRightArgs, thisArg, arity) {
+      var isBind = bitmask & 1,
+          isBindKey = bitmask & 2,
+          isCurry = bitmask & 4,
+          isCurryBound = bitmask & 8,
+          isPartial = bitmask & 16,
+          isPartialRight = bitmask & 32;
+
+      if (!isBindKey && !isFunction(func)) {
+        throw new TypeError;
+      }
+      if (isPartial && !partialArgs.length) {
+        bitmask &= ~16;
+        isPartial = partialArgs = false;
+      }
+      if (isPartialRight && !partialRightArgs.length) {
+        bitmask &= ~32;
+        isPartialRight = partialRightArgs = false;
+      }
+      var bindData = func && func.__bindData__;
+      if (bindData && bindData !== true) {
+        // clone `bindData`
+        bindData = slice(bindData);
+        if (bindData[2]) {
+          bindData[2] = slice(bindData[2]);
+        }
+        if (bindData[3]) {
+          bindData[3] = slice(bindData[3]);
+        }
+        // set `thisBinding` is not previously bound
+        if (isBind && !(bindData[1] & 1)) {
+          bindData[4] = thisArg;
+        }
+        // set if previously bound but not currently (subsequent curried functions)
+        if (!isBind && bindData[1] & 1) {
+          bitmask |= 8;
+        }
+        // set curried arity if not yet set
+        if (isCurry && !(bindData[1] & 4)) {
+          bindData[5] = arity;
+        }
+        // append partial left arguments
+        if (isPartial) {
+          push.apply(bindData[2] || (bindData[2] = []), partialArgs);
+        }
+        // append partial right arguments
+        if (isPartialRight) {
+          unshift.apply(bindData[3] || (bindData[3] = []), partialRightArgs);
+        }
+        // merge flags
+        bindData[1] |= bitmask;
+        return createWrapper.apply(null, bindData);
+      }
+      // fast path for `_.bind`
+      var creater = (bitmask == 1 || bitmask === 17) ? baseBind : baseCreateWrapper;
+      return creater([func, bitmask, partialArgs, partialRightArgs, thisArg, arity]);
+    }
+
+    /**
+     * Used by `escape` to convert characters to HTML entities.
+     *
+     * @private
+     * @param {string} match The matched character to escape.
+     * @returns {string} Returns the escaped character.
+     */
+    function escapeHtmlChar(match) {
+      return htmlEscapes[match];
+    }
+
+    /**
+     * Gets the appropriate "indexOf" function. If the `_.indexOf` method is
+     * customized, this method returns the custom method, otherwise it returns
+     * the `baseIndexOf` function.
+     *
+     * @private
+     * @returns {Function} Returns the "indexOf" function.
+     */
+    function getIndexOf() {
+      var result = (result = lodash.indexOf) === indexOf ? baseIndexOf : result;
+      return result;
+    }
+
+    /**
+     * Checks if `value` is a native function.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if the `value` is a native function, else `false`.
+     */
+    function isNative(value) {
+      return typeof value == 'function' && reNative.test(value);
+    }
+
+    /**
+     * Sets `this` binding data on a given function.
+     *
+     * @private
+     * @param {Function} func The function to set data on.
+     * @param {Array} value The data array to set.
+     */
+    var setBindData = !defineProperty ? noop : function(func, value) {
+      descriptor.value = value;
+      defineProperty(func, '__bindData__', descriptor);
+    };
+
+    /**
+     * A fallback implementation of `isPlainObject` which checks if a given value
+     * is an object created by the `Object` constructor, assuming objects created
+     * by the `Object` constructor have no inherited enumerable properties and that
+     * there are no `Object.prototype` extensions.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a plain object, else `false`.
+     */
+    function shimIsPlainObject(value) {
+      var ctor,
+          result;
+
+      // avoid non Object objects, `arguments` objects, and DOM elements
+      if (!(value && toString.call(value) == objectClass) ||
+          (ctor = value.constructor, isFunction(ctor) && !(ctor instanceof ctor))) {
+        return false;
+      }
+      // In most environments an object's own properties are iterated before
+      // its inherited properties. If the last iterated property is an object's
+      // own property then there are no inherited enumerable properties.
+      forIn(value, function(value, key) {
+        result = key;
+      });
+      return typeof result == 'undefined' || hasOwnProperty.call(value, result);
+    }
+
+    /**
+     * Used by `unescape` to convert HTML entities to characters.
+     *
+     * @private
+     * @param {string} match The matched character to unescape.
+     * @returns {string} Returns the unescaped character.
+     */
+    function unescapeHtmlChar(match) {
+      return htmlUnescapes[match];
+    }
+
+    /*--------------------------------------------------------------------------*/
+
+    /**
+     * Checks if `value` is an `arguments` object.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if the `value` is an `arguments` object, else `false`.
+     * @example
+     *
+     * (function() { return _.isArguments(arguments); })(1, 2, 3);
+     * // => true
+     *
+     * _.isArguments([1, 2, 3]);
+     * // => false
+     */
+    function isArguments(value) {
+      return value && typeof value == 'object' && typeof value.length == 'number' &&
+        toString.call(value) == argsClass || false;
+    }
+
+    /**
+     * Checks if `value` is an array.
+     *
+     * @static
+     * @memberOf _
+     * @type Function
+     * @category Objects
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if the `value` is an array, else `false`.
+     * @example
+     *
+     * (function() { return _.isArray(arguments); })();
+     * // => false
+     *
+     * _.isArray([1, 2, 3]);
+     * // => true
+     */
+    var isArray = nativeIsArray || function(value) {
+      return value && typeof value == 'object' && typeof value.length == 'number' &&
+        toString.call(value) == arrayClass || false;
+    };
+
+    /**
+     * A fallback implementation of `Object.keys` which produces an array of the
+     * given object's own enumerable property names.
+     *
+     * @private
+     * @type Function
+     * @param {Object} object The object to inspect.
+     * @returns {Array} Returns an array of property names.
+     */
+    var shimKeys = function(object) {
+      var index, iterable = object, result = [];
+      if (!iterable) return result;
+      if (!(objectTypes[typeof object])) return result;
+        for (index in iterable) {
+          if (hasOwnProperty.call(iterable, index)) {
+            result.push(index);
+          }
+        }
+      return result
+    };
+
+    /**
+     * Creates an array composed of the own enumerable property names of an object.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Object} object The object to inspect.
+     * @returns {Array} Returns an array of property names.
+     * @example
+     *
+     * _.keys({ 'one': 1, 'two': 2, 'three': 3 });
+     * // => ['one', 'two', 'three'] (property order is not guaranteed across environments)
+     */
+    var keys = !nativeKeys ? shimKeys : function(object) {
+      if (!isObject(object)) {
+        return [];
+      }
+      return nativeKeys(object);
+    };
+
+    /**
+     * Used to convert characters to HTML entities:
+     *
+     * Though the `>` character is escaped for symmetry, characters like `>` and `/`
+     * don't require escaping in HTML and have no special meaning unless they're part
+     * of a tag or an unquoted attribute value.
+     * http://mathiasbynens.be/notes/ambiguous-ampersands (under "semi-related fun fact")
+     */
+    var htmlEscapes = {
+      '&': '&amp;',
+      '<': '&lt;',
+      '>': '&gt;',
+      '"': '&quot;',
+      "'": '&#39;'
+    };
+
+    /** Used to convert HTML entities to characters */
+    var htmlUnescapes = invert(htmlEscapes);
+
+    /** Used to match HTML entities and HTML characters */
+    var reEscapedHtml = RegExp('(' + keys(htmlUnescapes).join('|') + ')', 'g'),
+        reUnescapedHtml = RegExp('[' + keys(htmlEscapes).join('') + ']', 'g');
+
+    /*--------------------------------------------------------------------------*/
+
+    /**
+     * Assigns own enumerable properties of source object(s) to the destination
+     * object. Subsequent sources will overwrite property assignments of previous
+     * sources. If a callback is provided it will be executed to produce the
+     * assigned values. The callback is bound to `thisArg` and invoked with two
+     * arguments; (objectValue, sourceValue).
+     *
+     * @static
+     * @memberOf _
+     * @type Function
+     * @alias extend
+     * @category Objects
+     * @param {Object} object The destination object.
+     * @param {...Object} [source] The source objects.
+     * @param {Function} [callback] The function to customize assigning values.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Object} Returns the destination object.
+     * @example
+     *
+     * _.assign({ 'name': 'fred' }, { 'employer': 'slate' });
+     * // => { 'name': 'fred', 'employer': 'slate' }
+     *
+     * var defaults = _.partialRight(_.assign, function(a, b) {
+     *   return typeof a == 'undefined' ? b : a;
+     * });
+     *
+     * var object = { 'name': 'barney' };
+     * defaults(object, { 'name': 'fred', 'employer': 'slate' });
+     * // => { 'name': 'barney', 'employer': 'slate' }
+     */
+    var assign = function(object, source, guard) {
+      var index, iterable = object, result = iterable;
+      if (!iterable) return result;
+      var args = arguments,
+          argsIndex = 0,
+          argsLength = typeof guard == 'number' ? 2 : args.length;
+      if (argsLength > 3 && typeof args[argsLength - 2] == 'function') {
+        var callback = baseCreateCallback(args[--argsLength - 1], args[argsLength--], 2);
+      } else if (argsLength > 2 && typeof args[argsLength - 1] == 'function') {
+        callback = args[--argsLength];
+      }
+      while (++argsIndex < argsLength) {
+        iterable = args[argsIndex];
+        if (iterable && objectTypes[typeof iterable]) {
+        var ownIndex = -1,
+            ownProps = objectTypes[typeof iterable] && keys(iterable),
+            length = ownProps ? ownProps.length : 0;
+
+        while (++ownIndex < length) {
+          index = ownProps[ownIndex];
+          result[index] = callback ? callback(result[index], iterable[index]) : iterable[index];
+        }
+        }
+      }
+      return result
+    };
+
+    /**
+     * Creates a clone of `value`. If `isDeep` is `true` nested objects will also
+     * be cloned, otherwise they will be assigned by reference. If a callback
+     * is provided it will be executed to produce the cloned values. If the
+     * callback returns `undefined` cloning will be handled by the method instead.
+     * The callback is bound to `thisArg` and invoked with one argument; (value).
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to clone.
+     * @param {boolean} [isDeep=false] Specify a deep clone.
+     * @param {Function} [callback] The function to customize cloning values.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {*} Returns the cloned value.
+     * @example
+     *
+     * var characters = [
+     *   { 'name': 'barney', 'age': 36 },
+     *   { 'name': 'fred',   'age': 40 }
+     * ];
+     *
+     * var shallow = _.clone(characters);
+     * shallow[0] === characters[0];
+     * // => true
+     *
+     * var deep = _.clone(characters, true);
+     * deep[0] === characters[0];
+     * // => false
+     *
+     * _.mixin({
+     *   'clone': _.partialRight(_.clone, function(value) {
+     *     return _.isElement(value) ? value.cloneNode(false) : undefined;
+     *   })
+     * });
+     *
+     * var clone = _.clone(document.body);
+     * clone.childNodes.length;
+     * // => 0
+     */
+    function clone(value, isDeep, callback, thisArg) {
+      // allows working with "Collections" methods without using their `index`
+      // and `collection` arguments for `isDeep` and `callback`
+      if (typeof isDeep != 'boolean' && isDeep != null) {
+        thisArg = callback;
+        callback = isDeep;
+        isDeep = false;
+      }
+      return baseClone(value, isDeep, typeof callback == 'function' && baseCreateCallback(callback, thisArg, 1));
+    }
+
+    /**
+     * Creates a deep clone of `value`. If a callback is provided it will be
+     * executed to produce the cloned values. If the callback returns `undefined`
+     * cloning will be handled by the method instead. The callback is bound to
+     * `thisArg` and invoked with one argument; (value).
+     *
+     * Note: This method is loosely based on the structured clone algorithm. Functions
+     * and DOM nodes are **not** cloned. The enumerable properties of `arguments` objects and
+     * objects created by constructors other than `Object` are cloned to plain `Object` objects.
+     * See http://www.w3.org/TR/html5/infrastructure.html#internal-structured-cloning-algorithm.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to deep clone.
+     * @param {Function} [callback] The function to customize cloning values.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {*} Returns the deep cloned value.
+     * @example
+     *
+     * var characters = [
+     *   { 'name': 'barney', 'age': 36 },
+     *   { 'name': 'fred',   'age': 40 }
+     * ];
+     *
+     * var deep = _.cloneDeep(characters);
+     * deep[0] === characters[0];
+     * // => false
+     *
+     * var view = {
+     *   'label': 'docs',
+     *   'node': element
+     * };
+     *
+     * var clone = _.cloneDeep(view, function(value) {
+     *   return _.isElement(value) ? value.cloneNode(true) : undefined;
+     * });
+     *
+     * clone.node == view.node;
+     * // => false
+     */
+    function cloneDeep(value, callback, thisArg) {
+      return baseClone(value, true, typeof callback == 'function' && baseCreateCallback(callback, thisArg, 1));
+    }
+
+    /**
+     * Creates an object that inherits from the given `prototype` object. If a
+     * `properties` object is provided its own enumerable properties are assigned
+     * to the created object.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Object} prototype The object to inherit from.
+     * @param {Object} [properties] The properties to assign to the object.
+     * @returns {Object} Returns the new object.
+     * @example
+     *
+     * function Shape() {
+     *   this.x = 0;
+     *   this.y = 0;
+     * }
+     *
+     * function Circle() {
+     *   Shape.call(this);
+     * }
+     *
+     * Circle.prototype = _.create(Shape.prototype, { 'constructor': Circle });
+     *
+     * var circle = new Circle;
+     * circle instanceof Circle;
+     * // => true
+     *
+     * circle instanceof Shape;
+     * // => true
+     */
+    function create(prototype, properties) {
+      var result = baseCreate(prototype);
+      return properties ? assign(result, properties) : result;
+    }
+
+    /**
+     * Assigns own enumerable properties of source object(s) to the destination
+     * object for all destination properties that resolve to `undefined`. Once a
+     * property is set, additional defaults of the same property will be ignored.
+     *
+     * @static
+     * @memberOf _
+     * @type Function
+     * @category Objects
+     * @param {Object} object The destination object.
+     * @param {...Object} [source] The source objects.
+     * @param- {Object} [guard] Allows working with `_.reduce` without using its
+     *  `key` and `object` arguments as sources.
+     * @returns {Object} Returns the destination object.
+     * @example
+     *
+     * var object = { 'name': 'barney' };
+     * _.defaults(object, { 'name': 'fred', 'employer': 'slate' });
+     * // => { 'name': 'barney', 'employer': 'slate' }
+     */
+    var defaults = function(object, source, guard) {
+      var index, iterable = object, result = iterable;
+      if (!iterable) return result;
+      var args = arguments,
+          argsIndex = 0,
+          argsLength = typeof guard == 'number' ? 2 : args.length;
+      while (++argsIndex < argsLength) {
+        iterable = args[argsIndex];
+        if (iterable && objectTypes[typeof iterable]) {
+        var ownIndex = -1,
+            ownProps = objectTypes[typeof iterable] && keys(iterable),
+            length = ownProps ? ownProps.length : 0;
+
+        while (++ownIndex < length) {
+          index = ownProps[ownIndex];
+          if (typeof result[index] == 'undefined') result[index] = iterable[index];
+        }
+        }
+      }
+      return result
+    };
+
+    /**
+     * This method is like `_.findIndex` except that it returns the key of the
+     * first element that passes the callback check, instead of the element itself.
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Object} object The object to search.
+     * @param {Function|Object|string} [callback=identity] The function called per
+     *  iteration. If a property name or object is provided it will be used to
+     *  create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {string|undefined} Returns the key of the found element, else `undefined`.
+     * @example
+     *
+     * var characters = {
+     *   'barney': {  'age': 36, 'blocked': false },
+     *   'fred': {    'age': 40, 'blocked': true },
+     *   'pebbles': { 'age': 1,  'blocked': false }
+     * };
+     *
+     * _.findKey(characters, function(chr) {
+     *   return chr.age < 40;
+     * });
+     * // => 'barney' (property order is not guaranteed across environments)
+     *
+     * // using "_.where" callback shorthand
+     * _.findKey(characters, { 'age': 1 });
+     * // => 'pebbles'
+     *
+     * // using "_.pluck" callback shorthand
+     * _.findKey(characters, 'blocked');
+     * // => 'fred'
+     */
+    function findKey(object, callback, thisArg) {
+      var result;
+      callback = lodash.createCallback(callback, thisArg, 3);
+      forOwn(object, function(value, key, object) {
+        if (callback(value, key, object)) {
+          result = key;
+          return false;
+        }
+      });
+      return result;
+    }
+
+    /**
+     * This method is like `_.findKey` except that it iterates over elements
+     * of a `collection` in the opposite order.
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Object} object The object to search.
+     * @param {Function|Object|string} [callback=identity] The function called per
+     *  iteration. If a property name or object is provided it will be used to
+     *  create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {string|undefined} Returns the key of the found element, else `undefined`.
+     * @example
+     *
+     * var characters = {
+     *   'barney': {  'age': 36, 'blocked': true },
+     *   'fred': {    'age': 40, 'blocked': false },
+     *   'pebbles': { 'age': 1,  'blocked': true }
+     * };
+     *
+     * _.findLastKey(characters, function(chr) {
+     *   return chr.age < 40;
+     * });
+     * // => returns `pebbles`, assuming `_.findKey` returns `barney`
+     *
+     * // using "_.where" callback shorthand
+     * _.findLastKey(characters, { 'age': 40 });
+     * // => 'fred'
+     *
+     * // using "_.pluck" callback shorthand
+     * _.findLastKey(characters, 'blocked');
+     * // => 'pebbles'
+     */
+    function findLastKey(object, callback, thisArg) {
+      var result;
+      callback = lodash.createCallback(callback, thisArg, 3);
+      forOwnRight(object, function(value, key, object) {
+        if (callback(value, key, object)) {
+          result = key;
+          return false;
+        }
+      });
+      return result;
+    }
+
+    /**
+     * Iterates over own and inherited enumerable properties of an object,
+     * executing the callback for each property. The callback is bound to `thisArg`
+     * and invoked with three arguments; (value, key, object). Callbacks may exit
+     * iteration early by explicitly returning `false`.
+     *
+     * @static
+     * @memberOf _
+     * @type Function
+     * @category Objects
+     * @param {Object} object The object to iterate over.
+     * @param {Function} [callback=identity] The function called per iteration.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Object} Returns `object`.
+     * @example
+     *
+     * function Shape() {
+     *   this.x = 0;
+     *   this.y = 0;
+     * }
+     *
+     * Shape.prototype.move = function(x, y) {
+     *   this.x += x;
+     *   this.y += y;
+     * };
+     *
+     * _.forIn(new Shape, function(value, key) {
+     *   console.log(key);
+     * });
+     * // => logs 'x', 'y', and 'move' (property order is not guaranteed across environments)
+     */
+    var forIn = function(collection, callback, thisArg) {
+      var index, iterable = collection, result = iterable;
+      if (!iterable) return result;
+      if (!objectTypes[typeof iterable]) return result;
+      callback = callback && typeof thisArg == 'undefined' ? callback : baseCreateCallback(callback, thisArg, 3);
+        for (index in iterable) {
+          if (callback(iterable[index], index, collection) === false) return result;
+        }
+      return result
+    };
+
+    /**
+     * This method is like `_.forIn` except that it iterates over elements
+     * of a `collection` in the opposite order.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Object} object The object to iterate over.
+     * @param {Function} [callback=identity] The function called per iteration.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Object} Returns `object`.
+     * @example
+     *
+     * function Shape() {
+     *   this.x = 0;
+     *   this.y = 0;
+     * }
+     *
+     * Shape.prototype.move = function(x, y) {
+     *   this.x += x;
+     *   this.y += y;
+     * };
+     *
+     * _.forInRight(new Shape, function(value, key) {
+     *   console.log(key);
+     * });
+     * // => logs 'move', 'y', and 'x' assuming `_.forIn ` logs 'x', 'y', and 'move'
+     */
+    function forInRight(object, callback, thisArg) {
+      var pairs = [];
+
+      forIn(object, function(value, key) {
+        pairs.push(key, value);
+      });
+
+      var length = pairs.length;
+      callback = baseCreateCallback(callback, thisArg, 3);
+      while (length--) {
+        if (callback(pairs[length--], pairs[length], object) === false) {
+          break;
+        }
+      }
+      return object;
+    }
+
+    /**
+     * Iterates over own enumerable properties of an object, executing the callback
+     * for each property. The callback is bound to `thisArg` and invoked with three
+     * arguments; (value, key, object). Callbacks may exit iteration early by
+     * explicitly returning `false`.
+     *
+     * @static
+     * @memberOf _
+     * @type Function
+     * @category Objects
+     * @param {Object} object The object to iterate over.
+     * @param {Function} [callback=identity] The function called per iteration.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Object} Returns `object`.
+     * @example
+     *
+     * _.forOwn({ '0': 'zero', '1': 'one', 'length': 2 }, function(num, key) {
+     *   console.log(key);
+     * });
+     * // => logs '0', '1', and 'length' (property order is not guaranteed across environments)
+     */
+    var forOwn = function(collection, callback, thisArg) {
+      var index, iterable = collection, result = iterable;
+      if (!iterable) return result;
+      if (!objectTypes[typeof iterable]) return result;
+      callback = callback && typeof thisArg == 'undefined' ? callback : baseCreateCallback(callback, thisArg, 3);
+        var ownIndex = -1,
+            ownProps = objectTypes[typeof iterable] && keys(iterable),
+            length = ownProps ? ownProps.length : 0;
+
+        while (++ownIndex < length) {
+          index = ownProps[ownIndex];
+          if (callback(iterable[index], index, collection) === false) return result;
+        }
+      return result
+    };
+
+    /**
+     * This method is like `_.forOwn` except that it iterates over elements
+     * of a `collection` in the opposite order.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Object} object The object to iterate over.
+     * @param {Function} [callback=identity] The function called per iteration.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Object} Returns `object`.
+     * @example
+     *
+     * _.forOwnRight({ '0': 'zero', '1': 'one', 'length': 2 }, function(num, key) {
+     *   console.log(key);
+     * });
+     * // => logs 'length', '1', and '0' assuming `_.forOwn` logs '0', '1', and 'length'
+     */
+    function forOwnRight(object, callback, thisArg) {
+      var props = keys(object),
+          length = props.length;
+
+      callback = baseCreateCallback(callback, thisArg, 3);
+      while (length--) {
+        var key = props[length];
+        if (callback(object[key], key, object) === false) {
+          break;
+        }
+      }
+      return object;
+    }
+
+    /**
+     * Creates a sorted array of property names of all enumerable properties,
+     * own and inherited, of `object` that have function values.
+     *
+     * @static
+     * @memberOf _
+     * @alias methods
+     * @category Objects
+     * @param {Object} object The object to inspect.
+     * @returns {Array} Returns an array of property names that have function values.
+     * @example
+     *
+     * _.functions(_);
+     * // => ['all', 'any', 'bind', 'bindAll', 'clone', 'compact', 'compose', ...]
+     */
+    function functions(object) {
+      var result = [];
+      forIn(object, function(value, key) {
+        if (isFunction(value)) {
+          result.push(key);
+        }
+      });
+      return result.sort();
+    }
+
+    /**
+     * Checks if the specified property name exists as a direct property of `object`,
+     * instead of an inherited property.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Object} object The object to inspect.
+     * @param {string} key The name of the property to check.
+     * @returns {boolean} Returns `true` if key is a direct property, else `false`.
+     * @example
+     *
+     * _.has({ 'a': 1, 'b': 2, 'c': 3 }, 'b');
+     * // => true
+     */
+    function has(object, key) {
+      return object ? hasOwnProperty.call(object, key) : false;
+    }
+
+    /**
+     * Creates an object composed of the inverted keys and values of the given object.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Object} object The object to invert.
+     * @returns {Object} Returns the created inverted object.
+     * @example
+     *
+     * _.invert({ 'first': 'fred', 'second': 'barney' });
+     * // => { 'fred': 'first', 'barney': 'second' }
+     */
+    function invert(object) {
+      var index = -1,
+          props = keys(object),
+          length = props.length,
+          result = {};
+
+      while (++index < length) {
+        var key = props[index];
+        result[object[key]] = key;
+      }
+      return result;
+    }
+
+    /**
+     * Checks if `value` is a boolean value.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if the `value` is a boolean value, else `false`.
+     * @example
+     *
+     * _.isBoolean(null);
+     * // => false
+     */
+    function isBoolean(value) {
+      return value === true || value === false ||
+        value && typeof value == 'object' && toString.call(value) == boolClass || false;
+    }
+
+    /**
+     * Checks if `value` is a date.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if the `value` is a date, else `false`.
+     * @example
+     *
+     * _.isDate(new Date);
+     * // => true
+     */
+    function isDate(value) {
+      return value && typeof value == 'object' && toString.call(value) == dateClass || false;
+    }
+
+    /**
+     * Checks if `value` is a DOM element.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if the `value` is a DOM element, else `false`.
+     * @example
+     *
+     * _.isElement(document.body);
+     * // => true
+     */
+    function isElement(value) {
+      return value && value.nodeType === 1 || false;
+    }
+
+    /**
+     * Checks if `value` is empty. Arrays, strings, or `arguments` objects with a
+     * length of `0` and objects with no own enumerable properties are considered
+     * "empty".
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Array|Object|string} value The value to inspect.
+     * @returns {boolean} Returns `true` if the `value` is empty, else `false`.
+     * @example
+     *
+     * _.isEmpty([1, 2, 3]);
+     * // => false
+     *
+     * _.isEmpty({});
+     * // => true
+     *
+     * _.isEmpty('');
+     * // => true
+     */
+    function isEmpty(value) {
+      var result = true;
+      if (!value) {
+        return result;
+      }
+      var className = toString.call(value),
+          length = value.length;
+
+      if ((className == arrayClass || className == stringClass || className == argsClass ) ||
+          (className == objectClass && typeof length == 'number' && isFunction(value.splice))) {
+        return !length;
+      }
+      forOwn(value, function() {
+        return (result = false);
+      });
+      return result;
+    }
+
+    /**
+     * Performs a deep comparison between two values to determine if they are
+     * equivalent to each other. If a callback is provided it will be executed
+     * to compare values. If the callback returns `undefined` comparisons will
+     * be handled by the method instead. The callback is bound to `thisArg` and
+     * invoked with two arguments; (a, b).
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} a The value to compare.
+     * @param {*} b The other value to compare.
+     * @param {Function} [callback] The function to customize comparing values.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
+     * @example
+     *
+     * var object = { 'name': 'fred' };
+     * var copy = { 'name': 'fred' };
+     *
+     * object == copy;
+     * // => false
+     *
+     * _.isEqual(object, copy);
+     * // => true
+     *
+     * var words = ['hello', 'goodbye'];
+     * var otherWords = ['hi', 'goodbye'];
+     *
+     * _.isEqual(words, otherWords, function(a, b) {
+     *   var reGreet = /^(?:hello|hi)$/i,
+     *       aGreet = _.isString(a) && reGreet.test(a),
+     *       bGreet = _.isString(b) && reGreet.test(b);
+     *
+     *   return (aGreet || bGreet) ? (aGreet == bGreet) : undefined;
+     * });
+     * // => true
+     */
+    function isEqual(a, b, callback, thisArg) {
+      return baseIsEqual(a, b, typeof callback == 'function' && baseCreateCallback(callback, thisArg, 2));
+    }
+
+    /**
+     * Checks if `value` is, or can be coerced to, a finite number.
+     *
+     * Note: This is not the same as native `isFinite` which will return true for
+     * booleans and empty strings. See http://es5.github.io/#x15.1.2.5.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if the `value` is finite, else `false`.
+     * @example
+     *
+     * _.isFinite(-101);
+     * // => true
+     *
+     * _.isFinite('10');
+     * // => true
+     *
+     * _.isFinite(true);
+     * // => false
+     *
+     * _.isFinite('');
+     * // => false
+     *
+     * _.isFinite(Infinity);
+     * // => false
+     */
+    function isFinite(value) {
+      return nativeIsFinite(value) && !nativeIsNaN(parseFloat(value));
+    }
+
+    /**
+     * Checks if `value` is a function.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if the `value` is a function, else `false`.
+     * @example
+     *
+     * _.isFunction(_);
+     * // => true
+     */
+    function isFunction(value) {
+      return typeof value == 'function';
+    }
+
+    /**
+     * Checks if `value` is the language type of Object.
+     * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if the `value` is an object, else `false`.
+     * @example
+     *
+     * _.isObject({});
+     * // => true
+     *
+     * _.isObject([1, 2, 3]);
+     * // => true
+     *
+     * _.isObject(1);
+     * // => false
+     */
+    function isObject(value) {
+      // check if the value is the ECMAScript language type of Object
+      // http://es5.github.io/#x8
+      // and avoid a V8 bug
+      // http://code.google.com/p/v8/issues/detail?id=2291
+      return !!(value && objectTypes[typeof value]);
+    }
+
+    /**
+     * Checks if `value` is `NaN`.
+     *
+     * Note: This is not the same as native `isNaN` which will return `true` for
+     * `undefined` and other non-numeric values. See http://es5.github.io/#x15.1.2.4.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if the `value` is `NaN`, else `false`.
+     * @example
+     *
+     * _.isNaN(NaN);
+     * // => true
+     *
+     * _.isNaN(new Number(NaN));
+     * // => true
+     *
+     * isNaN(undefined);
+     * // => true
+     *
+     * _.isNaN(undefined);
+     * // => false
+     */
+    function isNaN(value) {
+      // `NaN` as a primitive is the only value that is not equal to itself
+      // (perform the [[Class]] check first to avoid errors with some host objects in IE)
+      return isNumber(value) && value != +value;
+    }
+
+    /**
+     * Checks if `value` is `null`.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if the `value` is `null`, else `false`.
+     * @example
+     *
+     * _.isNull(null);
+     * // => true
+     *
+     * _.isNull(undefined);
+     * // => false
+     */
+    function isNull(value) {
+      return value === null;
+    }
+
+    /**
+     * Checks if `value` is a number.
+     *
+     * Note: `NaN` is considered a number. See http://es5.github.io/#x8.5.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if the `value` is a number, else `false`.
+     * @example
+     *
+     * _.isNumber(8.4 * 5);
+     * // => true
+     */
+    function isNumber(value) {
+      return typeof value == 'number' ||
+        value && typeof value == 'object' && toString.call(value) == numberClass || false;
+    }
+
+    /**
+     * Checks if `value` is an object created by the `Object` constructor.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a plain object, else `false`.
+     * @example
+     *
+     * function Shape() {
+     *   this.x = 0;
+     *   this.y = 0;
+     * }
+     *
+     * _.isPlainObject(new Shape);
+     * // => false
+     *
+     * _.isPlainObject([1, 2, 3]);
+     * // => false
+     *
+     * _.isPlainObject({ 'x': 0, 'y': 0 });
+     * // => true
+     */
+    var isPlainObject = !getPrototypeOf ? shimIsPlainObject : function(value) {
+      if (!(value && toString.call(value) == objectClass)) {
+        return false;
+      }
+      var valueOf = value.valueOf,
+          objProto = isNative(valueOf) && (objProto = getPrototypeOf(valueOf)) && getPrototypeOf(objProto);
+
+      return objProto
+        ? (value == objProto || getPrototypeOf(value) == objProto)
+        : shimIsPlainObject(value);
+    };
+
+    /**
+     * Checks if `value` is a regular expression.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if the `value` is a regular expression, else `false`.
+     * @example
+     *
+     * _.isRegExp(/fred/);
+     * // => true
+     */
+    function isRegExp(value) {
+      return value && typeof value == 'object' && toString.call(value) == regexpClass || false;
+    }
+
+    /**
+     * Checks if `value` is a string.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if the `value` is a string, else `false`.
+     * @example
+     *
+     * _.isString('fred');
+     * // => true
+     */
+    function isString(value) {
+      return typeof value == 'string' ||
+        value && typeof value == 'object' && toString.call(value) == stringClass || false;
+    }
+
+    /**
+     * Checks if `value` is `undefined`.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if the `value` is `undefined`, else `false`.
+     * @example
+     *
+     * _.isUndefined(void 0);
+     * // => true
+     */
+    function isUndefined(value) {
+      return typeof value == 'undefined';
+    }
+
+    /**
+     * Creates an object with the same keys as `object` and values generated by
+     * running each own enumerable property of `object` through the callback.
+     * The callback is bound to `thisArg` and invoked with three arguments;
+     * (value, key, object).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Object} object The object to iterate over.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Array} Returns a new object with values of the results of each `callback` execution.
+     * @example
+     *
+     * _.mapValues({ 'a': 1, 'b': 2, 'c': 3} , function(num) { return num * 3; });
+     * // => { 'a': 3, 'b': 6, 'c': 9 }
+     *
+     * var characters = {
+     *   'fred': { 'name': 'fred', 'age': 40 },
+     *   'pebbles': { 'name': 'pebbles', 'age': 1 }
+     * };
+     *
+     * // using "_.pluck" callback shorthand
+     * _.mapValues(characters, 'age');
+     * // => { 'fred': 40, 'pebbles': 1 }
+     */
+    function mapValues(object, callback, thisArg) {
+      var result = {};
+      callback = lodash.createCallback(callback, thisArg, 3);
+
+      forOwn(object, function(value, key, object) {
+        result[key] = callback(value, key, object);
+      });
+      return result;
+    }
+
+    /**
+     * Recursively merges own enumerable properties of the source object(s), that
+     * don't resolve to `undefined` into the destination object. Subsequent sources
+     * will overwrite property assignments of previous sources. If a callback is
+     * provided it will be executed to produce the merged values of the destination
+     * and source properties. If the callback returns `undefined` merging will
+     * be handled by the method instead. The callback is bound to `thisArg` and
+     * invoked with two arguments; (objectValue, sourceValue).
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Object} object The destination object.
+     * @param {...Object} [source] The source objects.
+     * @param {Function} [callback] The function to customize merging properties.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Object} Returns the destination object.
+     * @example
+     *
+     * var names = {
+     *   'characters': [
+     *     { 'name': 'barney' },
+     *     { 'name': 'fred' }
+     *   ]
+     * };
+     *
+     * var ages = {
+     *   'characters': [
+     *     { 'age': 36 },
+     *     { 'age': 40 }
+     *   ]
+     * };
+     *
+     * _.merge(names, ages);
+     * // => { 'characters': [{ 'name': 'barney', 'age': 36 }, { 'name': 'fred', 'age': 40 }] }
+     *
+     * var food = {
+     *   'fruits': ['apple'],
+     *   'vegetables': ['beet']
+     * };
+     *
+     * var otherFood = {
+     *   'fruits': ['banana'],
+     *   'vegetables': ['carrot']
+     * };
+     *
+     * _.merge(food, otherFood, function(a, b) {
+     *   return _.isArray(a) ? a.concat(b) : undefined;
+     * });
+     * // => { 'fruits': ['apple', 'banana'], 'vegetables': ['beet', 'carrot] }
+     */
+    function merge(object) {
+      var args = arguments,
+          length = 2;
+
+      if (!isObject(object)) {
+        return object;
+      }
+      // allows working with `_.reduce` and `_.reduceRight` without using
+      // their `index` and `collection` arguments
+      if (typeof args[2] != 'number') {
+        length = args.length;
+      }
+      if (length > 3 && typeof args[length - 2] == 'function') {
+        var callback = baseCreateCallback(args[--length - 1], args[length--], 2);
+      } else if (length > 2 && typeof args[length - 1] == 'function') {
+        callback = args[--length];
+      }
+      var sources = slice(arguments, 1, length),
+          index = -1,
+          stackA = getArray(),
+          stackB = getArray();
+
+      while (++index < length) {
+        baseMerge(object, sources[index], callback, stackA, stackB);
+      }
+      releaseArray(stackA);
+      releaseArray(stackB);
+      return object;
+    }
+
+    /**
+     * Creates a shallow clone of `object` excluding the specified properties.
+     * Property names may be specified as individual arguments or as arrays of
+     * property names. If a callback is provided it will be executed for each
+     * property of `object` omitting the properties the callback returns truey
+     * for. The callback is bound to `thisArg` and invoked with three arguments;
+     * (value, key, object).
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Object} object The source object.
+     * @param {Function|...string|string[]} [callback] The properties to omit or the
+     *  function called per iteration.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Object} Returns an object without the omitted properties.
+     * @example
+     *
+     * _.omit({ 'name': 'fred', 'age': 40 }, 'age');
+     * // => { 'name': 'fred' }
+     *
+     * _.omit({ 'name': 'fred', 'age': 40 }, function(value) {
+     *   return typeof value == 'number';
+     * });
+     * // => { 'name': 'fred' }
+     */
+    function omit(object, callback, thisArg) {
+      var result = {};
+      if (typeof callback != 'function') {
+        var props = [];
+        forIn(object, function(value, key) {
+          props.push(key);
+        });
+        props = baseDifference(props, baseFlatten(arguments, true, false, 1));
+
+        var index = -1,
+            length = props.length;
+
+        while (++index < length) {
+          var key = props[index];
+          result[key] = object[key];
+        }
+      } else {
+        callback = lodash.createCallback(callback, thisArg, 3);
+        forIn(object, function(value, key, object) {
+          if (!callback(value, key, object)) {
+            result[key] = value;
+          }
+        });
+      }
+      return result;
+    }
+
+    /**
+     * Creates a two dimensional array of an object's key-value pairs,
+     * i.e. `[[key1, value1], [key2, value2]]`.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Object} object The object to inspect.
+     * @returns {Array} Returns new array of key-value pairs.
+     * @example
+     *
+     * _.pairs({ 'barney': 36, 'fred': 40 });
+     * // => [['barney', 36], ['fred', 40]] (property order is not guaranteed across environments)
+     */
+    function pairs(object) {
+      var index = -1,
+          props = keys(object),
+          length = props.length,
+          result = Array(length);
+
+      while (++index < length) {
+        var key = props[index];
+        result[index] = [key, object[key]];
+      }
+      return result;
+    }
+
+    /**
+     * Creates a shallow clone of `object` composed of the specified properties.
+     * Property names may be specified as individual arguments or as arrays of
+     * property names. If a callback is provided it will be executed for each
+     * property of `object` picking the properties the callback returns truey
+     * for. The callback is bound to `thisArg` and invoked with three arguments;
+     * (value, key, object).
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Object} object The source object.
+     * @param {Function|...string|string[]} [callback] The function called per
+     *  iteration or property names to pick, specified as individual property
+     *  names or arrays of property names.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Object} Returns an object composed of the picked properties.
+     * @example
+     *
+     * _.pick({ 'name': 'fred', '_userid': 'fred1' }, 'name');
+     * // => { 'name': 'fred' }
+     *
+     * _.pick({ 'name': 'fred', '_userid': 'fred1' }, function(value, key) {
+     *   return key.charAt(0) != '_';
+     * });
+     * // => { 'name': 'fred' }
+     */
+    function pick(object, callback, thisArg) {
+      var result = {};
+      if (typeof callback != 'function') {
+        var index = -1,
+            props = baseFlatten(arguments, true, false, 1),
+            length = isObject(object) ? props.length : 0;
+
+        while (++index < length) {
+          var key = props[index];
+          if (key in object) {
+            result[key] = object[key];
+          }
+        }
+      } else {
+        callback = lodash.createCallback(callback, thisArg, 3);
+        forIn(object, function(value, key, object) {
+          if (callback(value, key, object)) {
+            result[key] = value;
+          }
+        });
+      }
+      return result;
+    }
+
+    /**
+     * An alternative to `_.reduce` this method transforms `object` to a new
+     * `accumulator` object which is the result of running each of its own
+     * enumerable properties through a callback, with each callback execution
+     * potentially mutating the `accumulator` object. The callback is bound to
+     * `thisArg` and invoked with four arguments; (accumulator, value, key, object).
+     * Callbacks may exit iteration early by explicitly returning `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Array|Object} object The object to iterate over.
+     * @param {Function} [callback=identity] The function called per iteration.
+     * @param {*} [accumulator] The custom accumulator value.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {*} Returns the accumulated value.
+     * @example
+     *
+     * var squares = _.transform([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], function(result, num) {
+     *   num *= num;
+     *   if (num % 2) {
+     *     return result.push(num) < 3;
+     *   }
+     * });
+     * // => [1, 9, 25]
+     *
+     * var mapped = _.transform({ 'a': 1, 'b': 2, 'c': 3 }, function(result, num, key) {
+     *   result[key] = num * 3;
+     * });
+     * // => { 'a': 3, 'b': 6, 'c': 9 }
+     */
+    function transform(object, callback, accumulator, thisArg) {
+      var isArr = isArray(object);
+      if (accumulator == null) {
+        if (isArr) {
+          accumulator = [];
+        } else {
+          var ctor = object && object.constructor,
+              proto = ctor && ctor.prototype;
+
+          accumulator = baseCreate(proto);
+        }
+      }
+      if (callback) {
+        callback = lodash.createCallback(callback, thisArg, 4);
+        (isArr ? forEach : forOwn)(object, function(value, index, object) {
+          return callback(accumulator, value, index, object);
+        });
+      }
+      return accumulator;
+    }
+
+    /**
+     * Creates an array composed of the own enumerable property values of `object`.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Object} object The object to inspect.
+     * @returns {Array} Returns an array of property values.
+     * @example
+     *
+     * _.values({ 'one': 1, 'two': 2, 'three': 3 });
+     * // => [1, 2, 3] (property order is not guaranteed across environments)
+     */
+    function values(object) {
+      var index = -1,
+          props = keys(object),
+          length = props.length,
+          result = Array(length);
+
+      while (++index < length) {
+        result[index] = object[props[index]];
+      }
+      return result;
+    }
+
+    /*--------------------------------------------------------------------------*/
+
+    /**
+     * Creates an array of elements from the specified indexes, or keys, of the
+     * `collection`. Indexes may be specified as individual arguments or as arrays
+     * of indexes.
+     *
+     * @static
+     * @memberOf _
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {...(number|number[]|string|string[])} [index] The indexes of `collection`
+     *   to retrieve, specified as individual indexes or arrays of indexes.
+     * @returns {Array} Returns a new array of elements corresponding to the
+     *  provided indexes.
+     * @example
+     *
+     * _.at(['a', 'b', 'c', 'd', 'e'], [0, 2, 4]);
+     * // => ['a', 'c', 'e']
+     *
+     * _.at(['fred', 'barney', 'pebbles'], 0, 2);
+     * // => ['fred', 'pebbles']
+     */
+    function at(collection) {
+      var args = arguments,
+          index = -1,
+          props = baseFlatten(args, true, false, 1),
+          length = (args[2] && args[2][args[1]] === collection) ? 1 : props.length,
+          result = Array(length);
+
+      while(++index < length) {
+        result[index] = collection[props[index]];
+      }
+      return result;
+    }
+
+    /**
+     * Checks if a given value is present in a collection using strict equality
+     * for comparisons, i.e. `===`. If `fromIndex` is negative, it is used as the
+     * offset from the end of the collection.
+     *
+     * @static
+     * @memberOf _
+     * @alias include
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {*} target The value to check for.
+     * @param {number} [fromIndex=0] The index to search from.
+     * @returns {boolean} Returns `true` if the `target` element is found, else `false`.
+     * @example
+     *
+     * _.contains([1, 2, 3], 1);
+     * // => true
+     *
+     * _.contains([1, 2, 3], 1, 2);
+     * // => false
+     *
+     * _.contains({ 'name': 'fred', 'age': 40 }, 'fred');
+     * // => true
+     *
+     * _.contains('pebbles', 'eb');
+     * // => true
+     */
+    function contains(collection, target, fromIndex) {
+      var index = -1,
+          indexOf = getIndexOf(),
+          length = collection ? collection.length : 0,
+          result = false;
+
+      fromIndex = (fromIndex < 0 ? nativeMax(0, length + fromIndex) : fromIndex) || 0;
+      if (isArray(collection)) {
+        result = indexOf(collection, target, fromIndex) > -1;
+      } else if (typeof length == 'number') {
+        result = (isString(collection) ? collection.indexOf(target, fromIndex) : indexOf(collection, target, fromIndex)) > -1;
+      } else {
+        forOwn(collection, function(value) {
+          if (++index >= fromIndex) {
+            return !(result = value === target);
+          }
+        });
+      }
+      return result;
+    }
+
+    /**
+     * Creates an object composed of keys generated from the results of running
+     * each element of `collection` through the callback. The corresponding value
+     * of each key is the number of times the key was returned by the callback.
+     * The callback is bound to `thisArg` and invoked with three arguments;
+     * (value, index|key, collection).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Object} Returns the composed aggregate object.
+     * @example
+     *
+     * _.countBy([4.3, 6.1, 6.4], function(num) { return Math.floor(num); });
+     * // => { '4': 1, '6': 2 }
+     *
+     * _.countBy([4.3, 6.1, 6.4], function(num) { return this.floor(num); }, Math);
+     * // => { '4': 1, '6': 2 }
+     *
+     * _.countBy(['one', 'two', 'three'], 'length');
+     * // => { '3': 2, '5': 1 }
+     */
+    var countBy = createAggregator(function(result, value, key) {
+      (hasOwnProperty.call(result, key) ? result[key]++ : result[key] = 1);
+    });
+
+    /**
+     * Checks if the given callback returns truey value for **all** elements of
+     * a collection. The callback is bound to `thisArg` and invoked with three
+     * arguments; (value, index|key, collection).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @alias all
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {boolean} Returns `true` if all elements passed the callback check,
+     *  else `false`.
+     * @example
+     *
+     * _.every([true, 1, null, 'yes']);
+     * // => false
+     *
+     * var characters = [
+     *   { 'name': 'barney', 'age': 36 },
+     *   { 'name': 'fred',   'age': 40 }
+     * ];
+     *
+     * // using "_.pluck" callback shorthand
+     * _.every(characters, 'age');
+     * // => true
+     *
+     * // using "_.where" callback shorthand
+     * _.every(characters, { 'age': 36 });
+     * // => false
+     */
+    function every(collection, callback, thisArg) {
+      var result = true;
+      callback = lodash.createCallback(callback, thisArg, 3);
+
+      var index = -1,
+          length = collection ? collection.length : 0;
+
+      if (typeof length == 'number') {
+        while (++index < length) {
+          if (!(result = !!callback(collection[index], index, collection))) {
+            break;
+          }
+        }
+      } else {
+        forOwn(collection, function(value, index, collection) {
+          return (result = !!callback(value, index, collection));
+        });
+      }
+      return result;
+    }
+
+    /**
+     * Iterates over elements of a collection, returning an array of all elements
+     * the callback returns truey for. The callback is bound to `thisArg` and
+     * invoked with three arguments; (value, index|key, collection).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @alias select
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Array} Returns a new array of elements that passed the callback check.
+     * @example
+     *
+     * var evens = _.filter([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; });
+     * // => [2, 4, 6]
+     *
+     * var characters = [
+     *   { 'name': 'barney', 'age': 36, 'blocked': false },
+     *   { 'name': 'fred',   'age': 40, 'blocked': true }
+     * ];
+     *
+     * // using "_.pluck" callback shorthand
+     * _.filter(characters, 'blocked');
+     * // => [{ 'name': 'fred', 'age': 40, 'blocked': true }]
+     *
+     * // using "_.where" callback shorthand
+     * _.filter(characters, { 'age': 36 });
+     * // => [{ 'name': 'barney', 'age': 36, 'blocked': false }]
+     */
+    function filter(collection, callback, thisArg) {
+      var result = [];
+      callback = lodash.createCallback(callback, thisArg, 3);
+
+      var index = -1,
+          length = collection ? collection.length : 0;
+
+      if (typeof length == 'number') {
+        while (++index < length) {
+          var value = collection[index];
+          if (callback(value, index, collection)) {
+            result.push(value);
+          }
+        }
+      } else {
+        forOwn(collection, function(value, index, collection) {
+          if (callback(value, index, collection)) {
+            result.push(value);
+          }
+        });
+      }
+      return result;
+    }
+
+    /**
+     * Iterates over elements of a collection, returning the first element that
+     * the callback returns truey for. The callback is bound to `thisArg` and
+     * invoked with three arguments; (value, index|key, collection).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @alias detect, findWhere
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {*} Returns the found element, else `undefined`.
+     * @example
+     *
+     * var characters = [
+     *   { 'name': 'barney',  'age': 36, 'blocked': false },
+     *   { 'name': 'fred',    'age': 40, 'blocked': true },
+     *   { 'name': 'pebbles', 'age': 1,  'blocked': false }
+     * ];
+     *
+     * _.find(characters, function(chr) {
+     *   return chr.age < 40;
+     * });
+     * // => { 'name': 'barney', 'age': 36, 'blocked': false }
+     *
+     * // using "_.where" callback shorthand
+     * _.find(characters, { 'age': 1 });
+     * // =>  { 'name': 'pebbles', 'age': 1, 'blocked': false }
+     *
+     * // using "_.pluck" callback shorthand
+     * _.find(characters, 'blocked');
+     * // => { 'name': 'fred', 'age': 40, 'blocked': true }
+     */
+    function find(collection, callback, thisArg) {
+      callback = lodash.createCallback(callback, thisArg, 3);
+
+      var index = -1,
+          length = collection ? collection.length : 0;
+
+      if (typeof length == 'number') {
+        while (++index < length) {
+          var value = collection[index];
+          if (callback(value, index, collection)) {
+            return value;
+          }
+        }
+      } else {
+        var result;
+        forOwn(collection, function(value, index, collection) {
+          if (callback(value, index, collection)) {
+            result = value;
+            return false;
+          }
+        });
+        return result;
+      }
+    }
+
+    /**
+     * This method is like `_.find` except that it iterates over elements
+     * of a `collection` from right to left.
+     *
+     * @static
+     * @memberOf _
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {*} Returns the found element, else `undefined`.
+     * @example
+     *
+     * _.findLast([1, 2, 3, 4], function(num) {
+     *   return num % 2 == 1;
+     * });
+     * // => 3
+     */
+    function findLast(collection, callback, thisArg) {
+      var result;
+      callback = lodash.createCallback(callback, thisArg, 3);
+      forEachRight(collection, function(value, index, collection) {
+        if (callback(value, index, collection)) {
+          result = value;
+          return false;
+        }
+      });
+      return result;
+    }
+
+    /**
+     * Iterates over elements of a collection, executing the callback for each
+     * element. The callback is bound to `thisArg` and invoked with three arguments;
+     * (value, index|key, collection). Callbacks may exit iteration early by
+     * explicitly returning `false`.
+     *
+     * Note: As with other "Collections" methods, objects with a `length` property
+     * are iterated like arrays. To avoid this behavior `_.forIn` or `_.forOwn`
+     * may be used for object iteration.
+     *
+     * @static
+     * @memberOf _
+     * @alias each
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function} [callback=identity] The function called per iteration.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Array|Object|string} Returns `collection`.
+     * @example
+     *
+     * _([1, 2, 3]).forEach(function(num) { console.log(num); }).join(',');
+     * // => logs each number and returns '1,2,3'
+     *
+     * _.forEach({ 'one': 1, 'two': 2, 'three': 3 }, function(num) { console.log(num); });
+     * // => logs each number and returns the object (property order is not guaranteed across environments)
+     */
+    function forEach(collection, callback, thisArg) {
+      var index = -1,
+          length = collection ? collection.length : 0;
+
+      callback = callback && typeof thisArg == 'undefined' ? callback : baseCreateCallback(callback, thisArg, 3);
+      if (typeof length == 'number') {
+        while (++index < length) {
+          if (callback(collection[index], index, collection) === false) {
+            break;
+          }
+        }
+      } else {
+        forOwn(collection, callback);
+      }
+      return collection;
+    }
+
+    /**
+     * This method is like `_.forEach` except that it iterates over elements
+     * of a `collection` from right to left.
+     *
+     * @static
+     * @memberOf _
+     * @alias eachRight
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function} [callback=identity] The function called per iteration.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Array|Object|string} Returns `collection`.
+     * @example
+     *
+     * _([1, 2, 3]).forEachRight(function(num) { console.log(num); }).join(',');
+     * // => logs each number from right to left and returns '3,2,1'
+     */
+    function forEachRight(collection, callback, thisArg) {
+      var length = collection ? collection.length : 0;
+      callback = callback && typeof thisArg == 'undefined' ? callback : baseCreateCallback(callback, thisArg, 3);
+      if (typeof length == 'number') {
+        while (length--) {
+          if (callback(collection[length], length, collection) === false) {
+            break;
+          }
+        }
+      } else {
+        var props = keys(collection);
+        length = props.length;
+        forOwn(collection, function(value, key, collection) {
+          key = props ? props[--length] : --length;
+          return callback(collection[key], key, collection);
+        });
+      }
+      return collection;
+    }
+
+    /**
+     * Creates an object composed of keys generated from the results of running
+     * each element of a collection through the callback. The corresponding value
+     * of each key is an array of the elements responsible for generating the key.
+     * The callback is bound to `thisArg` and invoked with three arguments;
+     * (value, index|key, collection).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`
+     *
+     * @static
+     * @memberOf _
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Object} Returns the composed aggregate object.
+     * @example
+     *
+     * _.groupBy([4.2, 6.1, 6.4], function(num) { return Math.floor(num); });
+     * // => { '4': [4.2], '6': [6.1, 6.4] }
+     *
+     * _.groupBy([4.2, 6.1, 6.4], function(num) { return this.floor(num); }, Math);
+     * // => { '4': [4.2], '6': [6.1, 6.4] }
+     *
+     * // using "_.pluck" callback shorthand
+     * _.groupBy(['one', 'two', 'three'], 'length');
+     * // => { '3': ['one', 'two'], '5': ['three'] }
+     */
+    var groupBy = createAggregator(function(result, value, key) {
+      (hasOwnProperty.call(result, key) ? result[key] : result[key] = []).push(value);
+    });
+
+    /**
+     * Creates an object composed of keys generated from the results of running
+     * each element of the collection through the given callback. The corresponding
+     * value of each key is the last element responsible for generating the key.
+     * The callback is bound to `thisArg` and invoked with three arguments;
+     * (value, index|key, collection).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Object} Returns the composed aggregate object.
+     * @example
+     *
+     * var keys = [
+     *   { 'dir': 'left', 'code': 97 },
+     *   { 'dir': 'right', 'code': 100 }
+     * ];
+     *
+     * _.indexBy(keys, 'dir');
+     * // => { 'left': { 'dir': 'left', 'code': 97 }, 'right': { 'dir': 'right', 'code': 100 } }
+     *
+     * _.indexBy(keys, function(key) { return String.fromCharCode(key.code); });
+     * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } }
+     *
+     * _.indexBy(characters, function(key) { this.fromCharCode(key.code); }, String);
+     * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } }
+     */
+    var indexBy = createAggregator(function(result, value, key) {
+      result[key] = value;
+    });
+
+    /**
+     * Invokes the method named by `methodName` on each element in the `collection`
+     * returning an array of the results of each invoked method. Additional arguments
+     * will be provided to each invoked method. If `methodName` is a function it
+     * will be invoked for, and `this` bound to, each element in the `collection`.
+     *
+     * @static
+     * @memberOf _
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|string} methodName The name of the method to invoke or
+     *  the function invoked per iteration.
+     * @param {...*} [arg] Arguments to invoke the method with.
+     * @returns {Array} Returns a new array of the results of each invoked method.
+     * @example
+     *
+     * _.invoke([[5, 1, 7], [3, 2, 1]], 'sort');
+     * // => [[1, 5, 7], [1, 2, 3]]
+     *
+     * _.invoke([123, 456], String.prototype.split, '');
+     * // => [['1', '2', '3'], ['4', '5', '6']]
+     */
+    function invoke(collection, methodName) {
+      var args = slice(arguments, 2),
+          index = -1,
+          isFunc = typeof methodName == 'function',
+          length = collection ? collection.length : 0,
+          result = Array(typeof length == 'number' ? length : 0);
+
+      forEach(collection, function(value) {
+        result[++index] = (isFunc ? methodName : value[methodName]).apply(value, args);
+      });
+      return result;
+    }
+
+    /**
+     * Creates an array of values by running each element in the collection
+     * through the callback. The callback is bound to `thisArg` and invoked with
+     * three arguments; (value, index|key, collection).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @alias collect
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Array} Returns a new array of the results of each `callback` execution.
+     * @example
+     *
+     * _.map([1, 2, 3], function(num) { return num * 3; });
+     * // => [3, 6, 9]
+     *
+     * _.map({ 'one': 1, 'two': 2, 'three': 3 }, function(num) { return num * 3; });
+     * // => [3, 6, 9] (property order is not guaranteed across environments)
+     *
+     * var characters = [
+     *   { 'name': 'barney', 'age': 36 },
+     *   { 'name': 'fred',   'age': 40 }
+     * ];
+     *
+     * // using "_.pluck" callback shorthand
+     * _.map(characters, 'name');
+     * // => ['barney', 'fred']
+     */
+    function map(collection, callback, thisArg) {
+      var index = -1,
+          length = collection ? collection.length : 0;
+
+      callback = lodash.createCallback(callback, thisArg, 3);
+      if (typeof length == 'number') {
+        var result = Array(length);
+        while (++index < length) {
+          result[index] = callback(collection[index], index, collection);
+        }
+      } else {
+        result = [];
+        forOwn(collection, function(value, key, collection) {
+          result[++index] = callback(value, key, collection);
+        });
+      }
+      return result;
+    }
+
+    /**
+     * Retrieves the maximum value of a collection. If the collection is empty or
+     * falsey `-Infinity` is returned. If a callback is provided it will be executed
+     * for each value in the collection to generate the criterion by which the value
+     * is ranked. The callback is bound to `thisArg` and invoked with three
+     * arguments; (value, index, collection).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {*} Returns the maximum value.
+     * @example
+     *
+     * _.max([4, 2, 8, 6]);
+     * // => 8
+     *
+     * var characters = [
+     *   { 'name': 'barney', 'age': 36 },
+     *   { 'name': 'fred',   'age': 40 }
+     * ];
+     *
+     * _.max(characters, function(chr) { return chr.age; });
+     * // => { 'name': 'fred', 'age': 40 };
+     *
+     * // using "_.pluck" callback shorthand
+     * _.max(characters, 'age');
+     * // => { 'name': 'fred', 'age': 40 };
+     */
+    function max(collection, callback, thisArg) {
+      var computed = -Infinity,
+          result = computed;
+
+      // allows working with functions like `_.map` without using
+      // their `index` argument as a callback
+      if (typeof callback != 'function' && thisArg && thisArg[callback] === collection) {
+        callback = null;
+      }
+      if (callback == null && isArray(collection)) {
+        var index = -1,
+            length = collection.length;
+
+        while (++index < length) {
+          var value = collection[index];
+          if (value > result) {
+            result = value;
+          }
+        }
+      } else {
+        callback = (callback == null && isString(collection))
+          ? charAtCallback
+          : lodash.createCallback(callback, thisArg, 3);
+
+        forEach(collection, function(value, index, collection) {
+          var current = callback(value, index, collection);
+          if (current > computed) {
+            computed = current;
+            result = value;
+          }
+        });
+      }
+      return result;
+    }
+
+    /**
+     * Retrieves the minimum value of a collection. If the collection is empty or
+     * falsey `Infinity` is returned. If a callback is provided it will be executed
+     * for each value in the collection to generate the criterion by which the value
+     * is ranked. The callback is bound to `thisArg` and invoked with three
+     * arguments; (value, index, collection).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {*} Returns the minimum value.
+     * @example
+     *
+     * _.min([4, 2, 8, 6]);
+     * // => 2
+     *
+     * var characters = [
+     *   { 'name': 'barney', 'age': 36 },
+     *   { 'name': 'fred',   'age': 40 }
+     * ];
+     *
+     * _.min(characters, function(chr) { return chr.age; });
+     * // => { 'name': 'barney', 'age': 36 };
+     *
+     * // using "_.pluck" callback shorthand
+     * _.min(characters, 'age');
+     * // => { 'name': 'barney', 'age': 36 };
+     */
+    function min(collection, callback, thisArg) {
+      var computed = Infinity,
+          result = computed;
+
+      // allows working with functions like `_.map` without using
+      // their `index` argument as a callback
+      if (typeof callback != 'function' && thisArg && thisArg[callback] === collection) {
+        callback = null;
+      }
+      if (callback == null && isArray(collection)) {
+        var index = -1,
+            length = collection.length;
+
+        while (++index < length) {
+          var value = collection[index];
+          if (value < result) {
+            result = value;
+          }
+        }
+      } else {
+        callback = (callback == null && isString(collection))
+          ? charAtCallback
+          : lodash.createCallback(callback, thisArg, 3);
+
+        forEach(collection, function(value, index, collection) {
+          var current = callback(value, index, collection);
+          if (current < computed) {
+            computed = current;
+            result = value;
+          }
+        });
+      }
+      return result;
+    }
+
+    /**
+     * Retrieves the value of a specified property from all elements in the collection.
+     *
+     * @static
+     * @memberOf _
+     * @type Function
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {string} property The name of the property to pluck.
+     * @returns {Array} Returns a new array of property values.
+     * @example
+     *
+     * var characters = [
+     *   { 'name': 'barney', 'age': 36 },
+     *   { 'name': 'fred',   'age': 40 }
+     * ];
+     *
+     * _.pluck(characters, 'name');
+     * // => ['barney', 'fred']
+     */
+    var pluck = map;
+
+    /**
+     * Reduces a collection to a value which is the accumulated result of running
+     * each element in the collection through the callback, where each successive
+     * callback execution consumes the return value of the previous execution. If
+     * `accumulator` is not provided the first element of the collection will be
+     * used as the initial `accumulator` value. The callback is bound to `thisArg`
+     * and invoked with four arguments; (accumulator, value, index|key, collection).
+     *
+     * @static
+     * @memberOf _
+     * @alias foldl, inject
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function} [callback=identity] The function called per iteration.
+     * @param {*} [accumulator] Initial value of the accumulator.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {*} Returns the accumulated value.
+     * @example
+     *
+     * var sum = _.reduce([1, 2, 3], function(sum, num) {
+     *   return sum + num;
+     * });
+     * // => 6
+     *
+     * var mapped = _.reduce({ 'a': 1, 'b': 2, 'c': 3 }, function(result, num, key) {
+     *   result[key] = num * 3;
+     *   return result;
+     * }, {});
+     * // => { 'a': 3, 'b': 6, 'c': 9 }
+     */
+    function reduce(collection, callback, accumulator, thisArg) {
+      if (!collection) return accumulator;
+      var noaccum = arguments.length < 3;
+      callback = lodash.createCallback(callback, thisArg, 4);
+
+      var index = -1,
+          length = collection.length;
+
+      if (typeof length == 'number') {
+        if (noaccum) {
+          accumulator = collection[++index];
+        }
+        while (++index < length) {
+          accumulator = callback(accumulator, collection[index], index, collection);
+        }
+      } else {
+        forOwn(collection, function(value, index, collection) {
+          accumulator = noaccum
+            ? (noaccum = false, value)
+            : callback(accumulator, value, index, collection)
+        });
+      }
+      return accumulator;
+    }
+
+    /**
+     * This method is like `_.reduce` except that it iterates over elements
+     * of a `collection` from right to left.
+     *
+     * @static
+     * @memberOf _
+     * @alias foldr
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function} [callback=identity] The function called per iteration.
+     * @param {*} [accumulator] Initial value of the accumulator.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {*} Returns the accumulated value.
+     * @example
+     *
+     * var list = [[0, 1], [2, 3], [4, 5]];
+     * var flat = _.reduceRight(list, function(a, b) { return a.concat(b); }, []);
+     * // => [4, 5, 2, 3, 0, 1]
+     */
+    function reduceRight(collection, callback, accumulator, thisArg) {
+      var noaccum = arguments.length < 3;
+      callback = lodash.createCallback(callback, thisArg, 4);
+      forEachRight(collection, function(value, index, collection) {
+        accumulator = noaccum
+          ? (noaccum = false, value)
+          : callback(accumulator, value, index, collection);
+      });
+      return accumulator;
+    }
+
+    /**
+     * The opposite of `_.filter` this method returns the elements of a
+     * collection that the callback does **not** return truey for.
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Array} Returns a new array of elements that failed the callback check.
+     * @example
+     *
+     * var odds = _.reject([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; });
+     * // => [1, 3, 5]
+     *
+     * var characters = [
+     *   { 'name': 'barney', 'age': 36, 'blocked': false },
+     *   { 'name': 'fred',   'age': 40, 'blocked': true }
+     * ];
+     *
+     * // using "_.pluck" callback shorthand
+     * _.reject(characters, 'blocked');
+     * // => [{ 'name': 'barney', 'age': 36, 'blocked': false }]
+     *
+     * // using "_.where" callback shorthand
+     * _.reject(characters, { 'age': 36 });
+     * // => [{ 'name': 'fred', 'age': 40, 'blocked': true }]
+     */
+    function reject(collection, callback, thisArg) {
+      callback = lodash.createCallback(callback, thisArg, 3);
+      return filter(collection, function(value, index, collection) {
+        return !callback(value, index, collection);
+      });
+    }
+
+    /**
+     * Retrieves a random element or `n` random elements from a collection.
+     *
+     * @static
+     * @memberOf _
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to sample.
+     * @param {number} [n] The number of elements to sample.
+     * @param- {Object} [guard] Allows working with functions like `_.map`
+     *  without using their `index` arguments as `n`.
+     * @returns {Array} Returns the random sample(s) of `collection`.
+     * @example
+     *
+     * _.sample([1, 2, 3, 4]);
+     * // => 2
+     *
+     * _.sample([1, 2, 3, 4], 2);
+     * // => [3, 1]
+     */
+    function sample(collection, n, guard) {
+      if (collection && typeof collection.length != 'number') {
+        collection = values(collection);
+      }
+      if (n == null || guard) {
+        return collection ? collection[baseRandom(0, collection.length - 1)] : undefined;
+      }
+      var result = shuffle(collection);
+      result.length = nativeMin(nativeMax(0, n), result.length);
+      return result;
+    }
+
+    /**
+     * Creates an array of shuffled values, using a version of the Fisher-Yates
+     * shuffle. See http://en.wikipedia.org/wiki/Fisher-Yates_shuffle.
+     *
+     * @static
+     * @memberOf _
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to shuffle.
+     * @returns {Array} Returns a new shuffled collection.
+     * @example
+     *
+     * _.shuffle([1, 2, 3, 4, 5, 6]);
+     * // => [4, 1, 6, 3, 5, 2]
+     */
+    function shuffle(collection) {
+      var index = -1,
+          length = collection ? collection.length : 0,
+          result = Array(typeof length == 'number' ? length : 0);
+
+      forEach(collection, function(value) {
+        var rand = baseRandom(0, ++index);
+        result[index] = result[rand];
+        result[rand] = value;
+      });
+      return result;
+    }
+
+    /**
+     * Gets the size of the `collection` by returning `collection.length` for arrays
+     * and array-like objects or the number of own enumerable properties for objects.
+     *
+     * @static
+     * @memberOf _
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to inspect.
+     * @returns {number} Returns `collection.length` or number of own enumerable properties.
+     * @example
+     *
+     * _.size([1, 2]);
+     * // => 2
+     *
+     * _.size({ 'one': 1, 'two': 2, 'three': 3 });
+     * // => 3
+     *
+     * _.size('pebbles');
+     * // => 7
+     */
+    function size(collection) {
+      var length = collection ? collection.length : 0;
+      return typeof length == 'number' ? length : keys(collection).length;
+    }
+
+    /**
+     * Checks if the callback returns a truey value for **any** element of a
+     * collection. The function returns as soon as it finds a passing value and
+     * does not iterate over the entire collection. The callback is bound to
+     * `thisArg` and invoked with three arguments; (value, index|key, collection).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @alias any
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {boolean} Returns `true` if any element passed the callback check,
+     *  else `false`.
+     * @example
+     *
+     * _.some([null, 0, 'yes', false], Boolean);
+     * // => true
+     *
+     * var characters = [
+     *   { 'name': 'barney', 'age': 36, 'blocked': false },
+     *   { 'name': 'fred',   'age': 40, 'blocked': true }
+     * ];
+     *
+     * // using "_.pluck" callback shorthand
+     * _.some(characters, 'blocked');
+     * // => true
+     *
+     * // using "_.where" callback shorthand
+     * _.some(characters, { 'age': 1 });
+     * // => false
+     */
+    function some(collection, callback, thisArg) {
+      var result;
+      callback = lodash.createCallback(callback, thisArg, 3);
+
+      var index = -1,
+          length = collection ? collection.length : 0;
+
+      if (typeof length == 'number') {
+        while (++index < length) {
+          if ((result = callback(collection[index], index, collection))) {
+            break;
+          }
+        }
+      } else {
+        forOwn(collection, function(value, index, collection) {
+          return !(result = callback(value, index, collection));
+        });
+      }
+      return !!result;
+    }
+
+    /**
+     * Creates an array of elements, sorted in ascending order by the results of
+     * running each element in a collection through the callback. This method
+     * performs a stable sort, that is, it will preserve the original sort order
+     * of equal elements. The callback is bound to `thisArg` and invoked with
+     * three arguments; (value, index|key, collection).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an array of property names is provided for `callback` the collection
+     * will be sorted by each property value.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Array|Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Array} Returns a new array of sorted elements.
+     * @example
+     *
+     * _.sortBy([1, 2, 3], function(num) { return Math.sin(num); });
+     * // => [3, 1, 2]
+     *
+     * _.sortBy([1, 2, 3], function(num) { return this.sin(num); }, Math);
+     * // => [3, 1, 2]
+     *
+     * var characters = [
+     *   { 'name': 'barney',  'age': 36 },
+     *   { 'name': 'fred',    'age': 40 },
+     *   { 'name': 'barney',  'age': 26 },
+     *   { 'name': 'fred',    'age': 30 }
+     * ];
+     *
+     * // using "_.pluck" callback shorthand
+     * _.map(_.sortBy(characters, 'age'), _.values);
+     * // => [['barney', 26], ['fred', 30], ['barney', 36], ['fred', 40]]
+     *
+     * // sorting by multiple properties
+     * _.map(_.sortBy(characters, ['name', 'age']), _.values);
+     * // = > [['barney', 26], ['barney', 36], ['fred', 30], ['fred', 40]]
+     */
+    function sortBy(collection, callback, thisArg) {
+      var index = -1,
+          isArr = isArray(callback),
+          length = collection ? collection.length : 0,
+          result = Array(typeof length == 'number' ? length : 0);
+
+      if (!isArr) {
+        callback = lodash.createCallback(callback, thisArg, 3);
+      }
+      forEach(collection, function(value, key, collection) {
+        var object = result[++index] = getObject();
+        if (isArr) {
+          object.criteria = map(callback, function(key) { return value[key]; });
+        } else {
+          (object.criteria = getArray())[0] = callback(value, key, collection);
+        }
+        object.index = index;
+        object.value = value;
+      });
+
+      length = result.length;
+      result.sort(compareAscending);
+      while (length--) {
+        var object = result[length];
+        result[length] = object.value;
+        if (!isArr) {
+          releaseArray(object.criteria);
+        }
+        releaseObject(object);
+      }
+      return result;
+    }
+
+    /**
+     * Converts the `collection` to an array.
+     *
+     * @static
+     * @memberOf _
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to convert.
+     * @returns {Array} Returns the new converted array.
+     * @example
+     *
+     * (function() { return _.toArray(arguments).slice(1); })(1, 2, 3, 4);
+     * // => [2, 3, 4]
+     */
+    function toArray(collection) {
+      if (collection && typeof collection.length == 'number') {
+        return slice(collection);
+      }
+      return values(collection);
+    }
+
+    /**
+     * Performs a deep comparison of each element in a `collection` to the given
+     * `properties` object, returning an array of all elements that have equivalent
+     * property values.
+     *
+     * @static
+     * @memberOf _
+     * @type Function
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Object} props The object of property values to filter by.
+     * @returns {Array} Returns a new array of elements that have the given properties.
+     * @example
+     *
+     * var characters = [
+     *   { 'name': 'barney', 'age': 36, 'pets': ['hoppy'] },
+     *   { 'name': 'fred',   'age': 40, 'pets': ['baby puss', 'dino'] }
+     * ];
+     *
+     * _.where(characters, { 'age': 36 });
+     * // => [{ 'name': 'barney', 'age': 36, 'pets': ['hoppy'] }]
+     *
+     * _.where(characters, { 'pets': ['dino'] });
+     * // => [{ 'name': 'fred', 'age': 40, 'pets': ['baby puss', 'dino'] }]
+     */
+    var where = filter;
+
+    /*--------------------------------------------------------------------------*/
+
+    /**
+     * Creates an array with all falsey values removed. The values `false`, `null`,
+     * `0`, `""`, `undefined`, and `NaN` are all falsey.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {Array} array The array to compact.
+     * @returns {Array} Returns a new array of filtered values.
+     * @example
+     *
+     * _.compact([0, 1, false, 2, '', 3]);
+     * // => [1, 2, 3]
+     */
+    function compact(array) {
+      var index = -1,
+          length = array ? array.length : 0,
+          result = [];
+
+      while (++index < length) {
+        var value = array[index];
+        if (value) {
+          result.push(value);
+        }
+      }
+      return result;
+    }
+
+    /**
+     * Creates an array excluding all values of the provided arrays using strict
+     * equality for comparisons, i.e. `===`.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {Array} array The array to process.
+     * @param {...Array} [values] The arrays of values to exclude.
+     * @returns {Array} Returns a new array of filtered values.
+     * @example
+     *
+     * _.difference([1, 2, 3, 4, 5], [5, 2, 10]);
+     * // => [1, 3, 4]
+     */
+    function difference(array) {
+      return baseDifference(array, baseFlatten(arguments, true, true, 1));
+    }
+
+    /**
+     * This method is like `_.find` except that it returns the index of the first
+     * element that passes the callback check, instead of the element itself.
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {Array} array The array to search.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {number} Returns the index of the found element, else `-1`.
+     * @example
+     *
+     * var characters = [
+     *   { 'name': 'barney',  'age': 36, 'blocked': false },
+     *   { 'name': 'fred',    'age': 40, 'blocked': true },
+     *   { 'name': 'pebbles', 'age': 1,  'blocked': false }
+     * ];
+     *
+     * _.findIndex(characters, function(chr) {
+     *   return chr.age < 20;
+     * });
+     * // => 2
+     *
+     * // using "_.where" callback shorthand
+     * _.findIndex(characters, { 'age': 36 });
+     * // => 0
+     *
+     * // using "_.pluck" callback shorthand
+     * _.findIndex(characters, 'blocked');
+     * // => 1
+     */
+    function findIndex(array, callback, thisArg) {
+      var index = -1,
+          length = array ? array.length : 0;
+
+      callback = lodash.createCallback(callback, thisArg, 3);
+      while (++index < length) {
+        if (callback(array[index], index, array)) {
+          return index;
+        }
+      }
+      return -1;
+    }
+
+    /**
+     * This method is like `_.findIndex` except that it iterates over elements
+     * of a `collection` from right to left.
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {Array} array The array to search.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {number} Returns the index of the found element, else `-1`.
+     * @example
+     *
+     * var characters = [
+     *   { 'name': 'barney',  'age': 36, 'blocked': true },
+     *   { 'name': 'fred',    'age': 40, 'blocked': false },
+     *   { 'name': 'pebbles', 'age': 1,  'blocked': true }
+     * ];
+     *
+     * _.findLastIndex(characters, function(chr) {
+     *   return chr.age > 30;
+     * });
+     * // => 1
+     *
+     * // using "_.where" callback shorthand
+     * _.findLastIndex(characters, { 'age': 36 });
+     * // => 0
+     *
+     * // using "_.pluck" callback shorthand
+     * _.findLastIndex(characters, 'blocked');
+     * // => 2
+     */
+    function findLastIndex(array, callback, thisArg) {
+      var length = array ? array.length : 0;
+      callback = lodash.createCallback(callback, thisArg, 3);
+      while (length--) {
+        if (callback(array[length], length, array)) {
+          return length;
+        }
+      }
+      return -1;
+    }
+
+    /**
+     * Gets the first element or first `n` elements of an array. If a callback
+     * is provided elements at the beginning of the array are returned as long
+     * as the callback returns truey. The callback is bound to `thisArg` and
+     * invoked with three arguments; (value, index, array).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @alias head, take
+     * @category Arrays
+     * @param {Array} array The array to query.
+     * @param {Function|Object|number|string} [callback] The function called
+     *  per element or the number of elements to return. If a property name or
+     *  object is provided it will be used to create a "_.pluck" or "_.where"
+     *  style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {*} Returns the first element(s) of `array`.
+     * @example
+     *
+     * _.first([1, 2, 3]);
+     * // => 1
+     *
+     * _.first([1, 2, 3], 2);
+     * // => [1, 2]
+     *
+     * _.first([1, 2, 3], function(num) {
+     *   return num < 3;
+     * });
+     * // => [1, 2]
+     *
+     * var characters = [
+     *   { 'name': 'barney',  'blocked': true,  'employer': 'slate' },
+     *   { 'name': 'fred',    'blocked': false, 'employer': 'slate' },
+     *   { 'name': 'pebbles', 'blocked': true,  'employer': 'na' }
+     * ];
+     *
+     * // using "_.pluck" callback shorthand
+     * _.first(characters, 'blocked');
+     * // => [{ 'name': 'barney', 'blocked': true, 'employer': 'slate' }]
+     *
+     * // using "_.where" callback shorthand
+     * _.pluck(_.first(characters, { 'employer': 'slate' }), 'name');
+     * // => ['barney', 'fred']
+     */
+    function first(array, callback, thisArg) {
+      var n = 0,
+          length = array ? array.length : 0;
+
+      if (typeof callback != 'number' && callback != null) {
+        var index = -1;
+        callback = lodash.createCallback(callback, thisArg, 3);
+        while (++index < length && callback(array[index], index, array)) {
+          n++;
+        }
+      } else {
+        n = callback;
+        if (n == null || thisArg) {
+          return array ? array[0] : undefined;
+        }
+      }
+      return slice(array, 0, nativeMin(nativeMax(0, n), length));
+    }
+
+    /**
+     * Flattens a nested array (the nesting can be to any depth). If `isShallow`
+     * is truey, the array will only be flattened a single level. If a callback
+     * is provided each element of the array is passed through the callback before
+     * flattening. The callback is bound to `thisArg` and invoked with three
+     * arguments; (value, index, array).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {Array} array The array to flatten.
+     * @param {boolean} [isShallow=false] A flag to restrict flattening to a single level.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Array} Returns a new flattened array.
+     * @example
+     *
+     * _.flatten([1, [2], [3, [[4]]]]);
+     * // => [1, 2, 3, 4];
+     *
+     * _.flatten([1, [2], [3, [[4]]]], true);
+     * // => [1, 2, 3, [[4]]];
+     *
+     * var characters = [
+     *   { 'name': 'barney', 'age': 30, 'pets': ['hoppy'] },
+     *   { 'name': 'fred',   'age': 40, 'pets': ['baby puss', 'dino'] }
+     * ];
+     *
+     * // using "_.pluck" callback shorthand
+     * _.flatten(characters, 'pets');
+     * // => ['hoppy', 'baby puss', 'dino']
+     */
+    function flatten(array, isShallow, callback, thisArg) {
+      // juggle arguments
+      if (typeof isShallow != 'boolean' && isShallow != null) {
+        thisArg = callback;
+        callback = (typeof isShallow != 'function' && thisArg && thisArg[isShallow] === array) ? null : isShallow;
+        isShallow = false;
+      }
+      if (callback != null) {
+        array = map(array, callback, thisArg);
+      }
+      return baseFlatten(array, isShallow);
+    }
+
+    /**
+     * Gets the index at which the first occurrence of `value` is found using
+     * strict equality for comparisons, i.e. `===`. If the array is already sorted
+     * providing `true` for `fromIndex` will run a faster binary search.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {Array} array The array to search.
+     * @param {*} value The value to search for.
+     * @param {boolean|number} [fromIndex=0] The index to search from or `true`
+     *  to perform a binary search on a sorted array.
+     * @returns {number} Returns the index of the matched value or `-1`.
+     * @example
+     *
+     * _.indexOf([1, 2, 3, 1, 2, 3], 2);
+     * // => 1
+     *
+     * _.indexOf([1, 2, 3, 1, 2, 3], 2, 3);
+     * // => 4
+     *
+     * _.indexOf([1, 1, 2, 2, 3, 3], 2, true);
+     * // => 2
+     */
+    function indexOf(array, value, fromIndex) {
+      if (typeof fromIndex == 'number') {
+        var length = array ? array.length : 0;
+        fromIndex = (fromIndex < 0 ? nativeMax(0, length + fromIndex) : fromIndex || 0);
+      } else if (fromIndex) {
+        var index = sortedIndex(array, value);
+        return array[index] === value ? index : -1;
+      }
+      return baseIndexOf(array, value, fromIndex);
+    }
+
+    /**
+     * Gets all but the last element or last `n` elements of an array. If a
+     * callback is provided elements at the end of the array are excluded from
+     * the result as long as the callback returns truey. The callback is bound
+     * to `thisArg` and invoked with three arguments; (value, index, array).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {Array} array The array to query.
+     * @param {Function|Object|number|string} [callback=1] The function called
+     *  per element or the number of elements to exclude. If a property name or
+     *  object is provided it will be used to create a "_.pluck" or "_.where"
+     *  style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Array} Returns a slice of `array`.
+     * @example
+     *
+     * _.initial([1, 2, 3]);
+     * // => [1, 2]
+     *
+     * _.initial([1, 2, 3], 2);
+     * // => [1]
+     *
+     * _.initial([1, 2, 3], function(num) {
+     *   return num > 1;
+     * });
+     * // => [1]
+     *
+     * var characters = [
+     *   { 'name': 'barney',  'blocked': false, 'employer': 'slate' },
+     *   { 'name': 'fred',    'blocked': true,  'employer': 'slate' },
+     *   { 'name': 'pebbles', 'blocked': true,  'employer': 'na' }
+     * ];
+     *
+     * // using "_.pluck" callback shorthand
+     * _.initial(characters, 'blocked');
+     * // => [{ 'name': 'barney',  'blocked': false, 'employer': 'slate' }]
+     *
+     * // using "_.where" callback shorthand
+     * _.pluck(_.initial(characters, { 'employer': 'na' }), 'name');
+     * // => ['barney', 'fred']
+     */
+    function initial(array, callback, thisArg) {
+      var n = 0,
+          length = array ? array.length : 0;
+
+      if (typeof callback != 'number' && callback != null) {
+        var index = length;
+        callback = lodash.createCallback(callback, thisArg, 3);
+        while (index-- && callback(array[index], index, array)) {
+          n++;
+        }
+      } else {
+        n = (callback == null || thisArg) ? 1 : callback || n;
+      }
+      return slice(array, 0, nativeMin(nativeMax(0, length - n), length));
+    }
+
+    /**
+     * Creates an array of unique values present in all provided arrays using
+     * strict equality for comparisons, i.e. `===`.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {...Array} [array] The arrays to inspect.
+     * @returns {Array} Returns an array of shared values.
+     * @example
+     *
+     * _.intersection([1, 2, 3], [5, 2, 1, 4], [2, 1]);
+     * // => [1, 2]
+     */
+    function intersection() {
+      var args = [],
+          argsIndex = -1,
+          argsLength = arguments.length,
+          caches = getArray(),
+          indexOf = getIndexOf(),
+          trustIndexOf = indexOf === baseIndexOf,
+          seen = getArray();
+
+      while (++argsIndex < argsLength) {
+        var value = arguments[argsIndex];
+        if (isArray(value) || isArguments(value)) {
+          args.push(value);
+          caches.push(trustIndexOf && value.length >= largeArraySize &&
+            createCache(argsIndex ? args[argsIndex] : seen));
+        }
+      }
+      var array = args[0],
+          index = -1,
+          length = array ? array.length : 0,
+          result = [];
+
+      outer:
+      while (++index < length) {
+        var cache = caches[0];
+        value = array[index];
+
+        if ((cache ? cacheIndexOf(cache, value) : indexOf(seen, value)) < 0) {
+          argsIndex = argsLength;
+          (cache || seen).push(value);
+          while (--argsIndex) {
+            cache = caches[argsIndex];
+            if ((cache ? cacheIndexOf(cache, value) : indexOf(args[argsIndex], value)) < 0) {
+              continue outer;
+            }
+          }
+          result.push(value);
+        }
+      }
+      while (argsLength--) {
+        cache = caches[argsLength];
+        if (cache) {
+          releaseObject(cache);
+        }
+      }
+      releaseArray(caches);
+      releaseArray(seen);
+      return result;
+    }
+
+    /**
+     * Gets the last element or last `n` elements of an array. If a callback is
+     * provided elements at the end of the array are returned as long as the
+     * callback returns truey. The callback is bound to `thisArg` and invoked
+     * with three arguments; (value, index, array).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {Array} array The array to query.
+     * @param {Function|Object|number|string} [callback] The function called
+     *  per element or the number of elements to return. If a property name or
+     *  object is provided it will be used to create a "_.pluck" or "_.where"
+     *  style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {*} Returns the last element(s) of `array`.
+     * @example
+     *
+     * _.last([1, 2, 3]);
+     * // => 3
+     *
+     * _.last([1, 2, 3], 2);
+     * // => [2, 3]
+     *
+     * _.last([1, 2, 3], function(num) {
+     *   return num > 1;
+     * });
+     * // => [2, 3]
+     *
+     * var characters = [
+     *   { 'name': 'barney',  'blocked': false, 'employer': 'slate' },
+     *   { 'name': 'fred',    'blocked': true,  'employer': 'slate' },
+     *   { 'name': 'pebbles', 'blocked': true,  'employer': 'na' }
+     * ];
+     *
+     * // using "_.pluck" callback shorthand
+     * _.pluck(_.last(characters, 'blocked'), 'name');
+     * // => ['fred', 'pebbles']
+     *
+     * // using "_.where" callback shorthand
+     * _.last(characters, { 'employer': 'na' });
+     * // => [{ 'name': 'pebbles', 'blocked': true, 'employer': 'na' }]
+     */
+    function last(array, callback, thisArg) {
+      var n = 0,
+          length = array ? array.length : 0;
+
+      if (typeof callback != 'number' && callback != null) {
+        var index = length;
+        callback = lodash.createCallback(callback, thisArg, 3);
+        while (index-- && callback(array[index], index, array)) {
+          n++;
+        }
+      } else {
+        n = callback;
+        if (n == null || thisArg) {
+          return array ? array[length - 1] : undefined;
+        }
+      }
+      return slice(array, nativeMax(0, length - n));
+    }
+
+    /**
+     * Gets the index at which the last occurrence of `value` is found using strict
+     * equality for comparisons, i.e. `===`. If `fromIndex` is negative, it is used
+     * as the offset from the end of the collection.
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {Array} array The array to search.
+     * @param {*} value The value to search for.
+     * @param {number} [fromIndex=array.length-1] The index to search from.
+     * @returns {number} Returns the index of the matched value or `-1`.
+     * @example
+     *
+     * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2);
+     * // => 4
+     *
+     * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2, 3);
+     * // => 1
+     */
+    function lastIndexOf(array, value, fromIndex) {
+      var index = array ? array.length : 0;
+      if (typeof fromIndex == 'number') {
+        index = (fromIndex < 0 ? nativeMax(0, index + fromIndex) : nativeMin(fromIndex, index - 1)) + 1;
+      }
+      while (index--) {
+        if (array[index] === value) {
+          return index;
+        }
+      }
+      return -1;
+    }
+
+    /**
+     * Removes all provided values from the given array using strict equality for
+     * comparisons, i.e. `===`.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {Array} array The array to modify.
+     * @param {...*} [value] The values to remove.
+     * @returns {Array} Returns `array`.
+     * @example
+     *
+     * var array = [1, 2, 3, 1, 2, 3];
+     * _.pull(array, 2, 3);
+     * console.log(array);
+     * // => [1, 1]
+     */
+    function pull(array) {
+      var args = arguments,
+          argsIndex = 0,
+          argsLength = args.length,
+          length = array ? array.length : 0;
+
+      while (++argsIndex < argsLength) {
+        var index = -1,
+            value = args[argsIndex];
+        while (++index < length) {
+          if (array[index] === value) {
+            splice.call(array, index--, 1);
+            length--;
+          }
+        }
+      }
+      return array;
+    }
+
+    /**
+     * Creates an array of numbers (positive and/or negative) progressing from
+     * `start` up to but not including `end`. If `start` is less than `stop` a
+     * zero-length range is created unless a negative `step` is specified.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {number} [start=0] The start of the range.
+     * @param {number} end The end of the range.
+     * @param {number} [step=1] The value to increment or decrement by.
+     * @returns {Array} Returns a new range array.
+     * @example
+     *
+     * _.range(4);
+     * // => [0, 1, 2, 3]
+     *
+     * _.range(1, 5);
+     * // => [1, 2, 3, 4]
+     *
+     * _.range(0, 20, 5);
+     * // => [0, 5, 10, 15]
+     *
+     * _.range(0, -4, -1);
+     * // => [0, -1, -2, -3]
+     *
+     * _.range(1, 4, 0);
+     * // => [1, 1, 1]
+     *
+     * _.range(0);
+     * // => []
+     */
+    function range(start, end, step) {
+      start = +start || 0;
+      step = typeof step == 'number' ? step : (+step || 1);
+
+      if (end == null) {
+        end = start;
+        start = 0;
+      }
+      // use `Array(length)` so engines like Chakra and V8 avoid slower modes
+      // http://youtu.be/XAqIpGU8ZZk#t=17m25s
+      var index = -1,
+          length = nativeMax(0, ceil((end - start) / (step || 1))),
+          result = Array(length);
+
+      while (++index < length) {
+        result[index] = start;
+        start += step;
+      }
+      return result;
+    }
+
+    /**
+     * Removes all elements from an array that the callback returns truey for
+     * and returns an array of removed elements. The callback is bound to `thisArg`
+     * and invoked with three arguments; (value, index, array).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {Array} array The array to modify.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Array} Returns a new array of removed elements.
+     * @example
+     *
+     * var array = [1, 2, 3, 4, 5, 6];
+     * var evens = _.remove(array, function(num) { return num % 2 == 0; });
+     *
+     * console.log(array);
+     * // => [1, 3, 5]
+     *
+     * console.log(evens);
+     * // => [2, 4, 6]
+     */
+    function remove(array, callback, thisArg) {
+      var index = -1,
+          length = array ? array.length : 0,
+          result = [];
+
+      callback = lodash.createCallback(callback, thisArg, 3);
+      while (++index < length) {
+        var value = array[index];
+        if (callback(value, index, array)) {
+          result.push(value);
+          splice.call(array, index--, 1);
+          length--;
+        }
+      }
+      return result;
+    }
+
+    /**
+     * The opposite of `_.initial` this method gets all but the first element or
+     * first `n` elements of an array. If a callback function is provided elements
+     * at the beginning of the array are excluded from the result as long as the
+     * callback returns truey. The callback is bound to `thisArg` and invoked
+     * with three arguments; (value, index, array).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @alias drop, tail
+     * @category Arrays
+     * @param {Array} array The array to query.
+     * @param {Function|Object|number|string} [callback=1] The function called
+     *  per element or the number of elements to exclude. If a property name or
+     *  object is provided it will be used to create a "_.pluck" or "_.where"
+     *  style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Array} Returns a slice of `array`.
+     * @example
+     *
+     * _.rest([1, 2, 3]);
+     * // => [2, 3]
+     *
+     * _.rest([1, 2, 3], 2);
+     * // => [3]
+     *
+     * _.rest([1, 2, 3], function(num) {
+     *   return num < 3;
+     * });
+     * // => [3]
+     *
+     * var characters = [
+     *   { 'name': 'barney',  'blocked': true,  'employer': 'slate' },
+     *   { 'name': 'fred',    'blocked': false,  'employer': 'slate' },
+     *   { 'name': 'pebbles', 'blocked': true, 'employer': 'na' }
+     * ];
+     *
+     * // using "_.pluck" callback shorthand
+     * _.pluck(_.rest(characters, 'blocked'), 'name');
+     * // => ['fred', 'pebbles']
+     *
+     * // using "_.where" callback shorthand
+     * _.rest(characters, { 'employer': 'slate' });
+     * // => [{ 'name': 'pebbles', 'blocked': true, 'employer': 'na' }]
+     */
+    function rest(array, callback, thisArg) {
+      if (typeof callback != 'number' && callback != null) {
+        var n = 0,
+            index = -1,
+            length = array ? array.length : 0;
+
+        callback = lodash.createCallback(callback, thisArg, 3);
+        while (++index < length && callback(array[index], index, array)) {
+          n++;
+        }
+      } else {
+        n = (callback == null || thisArg) ? 1 : nativeMax(0, callback);
+      }
+      return slice(array, n);
+    }
+
+    /**
+     * Uses a binary search to determine the smallest index at which a value
+     * should be inserted into a given sorted array in order to maintain the sort
+     * order of the array. If a callback is provided it will be executed for
+     * `value` and each element of `array` to compute their sort ranking. The
+     * callback is bound to `thisArg` and invoked with one argument; (value).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {Array} array The array to inspect.
+     * @param {*} value The value to evaluate.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {number} Returns the index at which `value` should be inserted
+     *  into `array`.
+     * @example
+     *
+     * _.sortedIndex([20, 30, 50], 40);
+     * // => 2
+     *
+     * // using "_.pluck" callback shorthand
+     * _.sortedIndex([{ 'x': 20 }, { 'x': 30 }, { 'x': 50 }], { 'x': 40 }, 'x');
+     * // => 2
+     *
+     * var dict = {
+     *   'wordToNumber': { 'twenty': 20, 'thirty': 30, 'fourty': 40, 'fifty': 50 }
+     * };
+     *
+     * _.sortedIndex(['twenty', 'thirty', 'fifty'], 'fourty', function(word) {
+     *   return dict.wordToNumber[word];
+     * });
+     * // => 2
+     *
+     * _.sortedIndex(['twenty', 'thirty', 'fifty'], 'fourty', function(word) {
+     *   return this.wordToNumber[word];
+     * }, dict);
+     * // => 2
+     */
+    function sortedIndex(array, value, callback, thisArg) {
+      var low = 0,
+          high = array ? array.length : low;
+
+      // explicitly reference `identity` for better inlining in Firefox
+      callback = callback ? lodash.createCallback(callback, thisArg, 1) : identity;
+      value = callback(value);
+
+      while (low < high) {
+        var mid = (low + high) >>> 1;
+        (callback(array[mid]) < value)
+          ? low = mid + 1
+          : high = mid;
+      }
+      return low;
+    }
+
+    /**
+     * Creates an array of unique values, in order, of the provided arrays using
+     * strict equality for comparisons, i.e. `===`.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {...Array} [array] The arrays to inspect.
+     * @returns {Array} Returns an array of combined values.
+     * @example
+     *
+     * _.union([1, 2, 3], [5, 2, 1, 4], [2, 1]);
+     * // => [1, 2, 3, 5, 4]
+     */
+    function union() {
+      return baseUniq(baseFlatten(arguments, true, true));
+    }
+
+    /**
+     * Creates a duplicate-value-free version of an array using strict equality
+     * for comparisons, i.e. `===`. If the array is sorted, providing
+     * `true` for `isSorted` will use a faster algorithm. If a callback is provided
+     * each element of `array` is passed through the callback before uniqueness
+     * is computed. The callback is bound to `thisArg` and invoked with three
+     * arguments; (value, index, array).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @alias unique
+     * @category Arrays
+     * @param {Array} array The array to process.
+     * @param {boolean} [isSorted=false] A flag to indicate that `array` is sorted.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Array} Returns a duplicate-value-free array.
+     * @example
+     *
+     * _.uniq([1, 2, 1, 3, 1]);
+     * // => [1, 2, 3]
+     *
+     * _.uniq([1, 1, 2, 2, 3], true);
+     * // => [1, 2, 3]
+     *
+     * _.uniq(['A', 'b', 'C', 'a', 'B', 'c'], function(letter) { return letter.toLowerCase(); });
+     * // => ['A', 'b', 'C']
+     *
+     * _.uniq([1, 2.5, 3, 1.5, 2, 3.5], function(num) { return this.floor(num); }, Math);
+     * // => [1, 2.5, 3]
+     *
+     * // using "_.pluck" callback shorthand
+     * _.uniq([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x');
+     * // => [{ 'x': 1 }, { 'x': 2 }]
+     */
+    function uniq(array, isSorted, callback, thisArg) {
+      // juggle arguments
+      if (typeof isSorted != 'boolean' && isSorted != null) {
+        thisArg = callback;
+        callback = (typeof isSorted != 'function' && thisArg && thisArg[isSorted] === array) ? null : isSorted;
+        isSorted = false;
+      }
+      if (callback != null) {
+        callback = lodash.createCallback(callback, thisArg, 3);
+      }
+      return baseUniq(array, isSorted, callback);
+    }
+
+    /**
+     * Creates an array excluding all provided values using strict equality for
+     * comparisons, i.e. `===`.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {Array} array The array to filter.
+     * @param {...*} [value] The values to exclude.
+     * @returns {Array} Returns a new array of filtered values.
+     * @example
+     *
+     * _.without([1, 2, 1, 0, 3, 1, 4], 0, 1);
+     * // => [2, 3, 4]
+     */
+    function without(array) {
+      return baseDifference(array, slice(arguments, 1));
+    }
+
+    /**
+     * Creates an array that is the symmetric difference of the provided arrays.
+     * See http://en.wikipedia.org/wiki/Symmetric_difference.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {...Array} [array] The arrays to inspect.
+     * @returns {Array} Returns an array of values.
+     * @example
+     *
+     * _.xor([1, 2, 3], [5, 2, 1, 4]);
+     * // => [3, 5, 4]
+     *
+     * _.xor([1, 2, 5], [2, 3, 5], [3, 4, 5]);
+     * // => [1, 4, 5]
+     */
+    function xor() {
+      var index = -1,
+          length = arguments.length;
+
+      while (++index < length) {
+        var array = arguments[index];
+        if (isArray(array) || isArguments(array)) {
+          var result = result
+            ? baseUniq(baseDifference(result, array).concat(baseDifference(array, result)))
+            : array;
+        }
+      }
+      return result || [];
+    }
+
+    /**
+     * Creates an array of grouped elements, the first of which contains the first
+     * elements of the given arrays, the second of which contains the second
+     * elements of the given arrays, and so on.
+     *
+     * @static
+     * @memberOf _
+     * @alias unzip
+     * @category Arrays
+     * @param {...Array} [array] Arrays to process.
+     * @returns {Array} Returns a new array of grouped elements.
+     * @example
+     *
+     * _.zip(['fred', 'barney'], [30, 40], [true, false]);
+     * // => [['fred', 30, true], ['barney', 40, false]]
+     */
+    function zip() {
+      var array = arguments.length > 1 ? arguments : arguments[0],
+          index = -1,
+          length = array ? max(pluck(array, 'length')) : 0,
+          result = Array(length < 0 ? 0 : length);
+
+      while (++index < length) {
+        result[index] = pluck(array, index);
+      }
+      return result;
+    }
+
+    /**
+     * Creates an object composed from arrays of `keys` and `values`. Provide
+     * either a single two dimensional array, i.e. `[[key1, value1], [key2, value2]]`
+     * or two arrays, one of `keys` and one of corresponding `values`.
+     *
+     * @static
+     * @memberOf _
+     * @alias object
+     * @category Arrays
+     * @param {Array} keys The array of keys.
+     * @param {Array} [values=[]] The array of values.
+     * @returns {Object} Returns an object composed of the given keys and
+     *  corresponding values.
+     * @example
+     *
+     * _.zipObject(['fred', 'barney'], [30, 40]);
+     * // => { 'fred': 30, 'barney': 40 }
+     */
+    function zipObject(keys, values) {
+      var index = -1,
+          length = keys ? keys.length : 0,
+          result = {};
+
+      if (!values && length && !isArray(keys[0])) {
+        values = [];
+      }
+      while (++index < length) {
+        var key = keys[index];
+        if (values) {
+          result[key] = values[index];
+        } else if (key) {
+          result[key[0]] = key[1];
+        }
+      }
+      return result;
+    }
+
+    /*--------------------------------------------------------------------------*/
+
+    /**
+     * Creates a function that executes `func`, with  the `this` binding and
+     * arguments of the created function, only after being called `n` times.
+     *
+     * @static
+     * @memberOf _
+     * @category Functions
+     * @param {number} n The number of times the function must be called before
+     *  `func` is executed.
+     * @param {Function} func The function to restrict.
+     * @returns {Function} Returns the new restricted function.
+     * @example
+     *
+     * var saves = ['profile', 'settings'];
+     *
+     * var done = _.after(saves.length, function() {
+     *   console.log('Done saving!');
+     * });
+     *
+     * _.forEach(saves, function(type) {
+     *   asyncSave({ 'type': type, 'complete': done });
+     * });
+     * // => logs 'Done saving!', after all saves have completed
+     */
+    function after(n, func) {
+      if (!isFunction(func)) {
+        throw new TypeError;
+      }
+      return function() {
+        if (--n < 1) {
+          return func.apply(this, arguments);
+        }
+      };
+    }
+
+    /**
+     * Creates a function that, when called, invokes `func` with the `this`
+     * binding of `thisArg` and prepends any additional `bind` arguments to those
+     * provided to the bound function.
+     *
+     * @static
+     * @memberOf _
+     * @category Functions
+     * @param {Function} func The function to bind.
+     * @param {*} [thisArg] The `this` binding of `func`.
+     * @param {...*} [arg] Arguments to be partially applied.
+     * @returns {Function} Returns the new bound function.
+     * @example
+     *
+     * var func = function(greeting) {
+     *   return greeting + ' ' + this.name;
+     * };
+     *
+     * func = _.bind(func, { 'name': 'fred' }, 'hi');
+     * func();
+     * // => 'hi fred'
+     */
+    function bind(func, thisArg) {
+      return arguments.length > 2
+        ? createWrapper(func, 17, slice(arguments, 2), null, thisArg)
+        : createWrapper(func, 1, null, null, thisArg);
+    }
+
+    /**
+     * Binds methods of an object to the object itself, overwriting the existing
+     * method. Method names may be specified as individual arguments or as arrays
+     * of method names. If no method names are provided all the function properties
+     * of `object` will be bound.
+     *
+     * @static
+     * @memberOf _
+     * @category Functions
+     * @param {Object} object The object to bind and assign the bound methods to.
+     * @param {...string} [methodName] The object method names to
+     *  bind, specified as individual method names or arrays of method names.
+     * @returns {Object} Returns `object`.
+     * @example
+     *
+     * var view = {
+     *   'label': 'docs',
+     *   'onClick': function() { console.log('clicked ' + this.label); }
+     * };
+     *
+     * _.bindAll(view);
+     * jQuery('#docs').on('click', view.onClick);
+     * // => logs 'clicked docs', when the button is clicked
+     */
+    function bindAll(object) {
+      var funcs = arguments.length > 1 ? baseFlatten(arguments, true, false, 1) : functions(object),
+          index = -1,
+          length = funcs.length;
+
+      while (++index < length) {
+        var key = funcs[index];
+        object[key] = createWrapper(object[key], 1, null, null, object);
+      }
+      return object;
+    }
+
+    /**
+     * Creates a function that, when called, invokes the method at `object[key]`
+     * and prepends any additional `bindKey` arguments to those provided to the bound
+     * function. This method differs from `_.bind` by allowing bound functions to
+     * reference methods that will be redefined or don't yet exist.
+     * See http://michaux.ca/articles/lazy-function-definition-pattern.
+     *
+     * @static
+     * @memberOf _
+     * @category Functions
+     * @param {Object} object The object the method belongs to.
+     * @param {string} key The key of the method.
+     * @param {...*} [arg] Arguments to be partially applied.
+     * @returns {Function} Returns the new bound function.
+     * @example
+     *
+     * var object = {
+     *   'name': 'fred',
+     *   'greet': function(greeting) {
+     *     return greeting + ' ' + this.name;
+     *   }
+     * };
+     *
+     * var func = _.bindKey(object, 'greet', 'hi');
+     * func();
+     * // => 'hi fred'
+     *
+     * object.greet = function(greeting) {
+     *   return greeting + 'ya ' + this.name + '!';
+     * };
+     *
+     * func();
+     * // => 'hiya fred!'
+     */
+    function bindKey(object, key) {
+      return arguments.length > 2
+        ? createWrapper(key, 19, slice(arguments, 2), null, object)
+        : createWrapper(key, 3, null, null, object);
+    }
+
+    /**
+     * Creates a function that is the composition of the provided functions,
+     * where each function consumes the return value of the function that follows.
+     * For example, composing the functions `f()`, `g()`, and `h()` produces `f(g(h()))`.
+     * Each function is executed with the `this` binding of the composed function.
+     *
+     * @static
+     * @memberOf _
+     * @category Functions
+     * @param {...Function} [func] Functions to compose.
+     * @returns {Function} Returns the new composed function.
+     * @example
+     *
+     * var realNameMap = {
+     *   'pebbles': 'penelope'
+     * };
+     *
+     * var format = function(name) {
+     *   name = realNameMap[name.toLowerCase()] || name;
+     *   return name.charAt(0).toUpperCase() + name.slice(1).toLowerCase();
+     * };
+     *
+     * var greet = function(formatted) {
+     *   return 'Hiya ' + formatted + '!';
+     * };
+     *
+     * var welcome = _.compose(greet, format);
+     * welcome('pebbles');
+     * // => 'Hiya Penelope!'
+     */
+    function compose() {
+      var funcs = arguments,
+          length = funcs.length;
+
+      while (length--) {
+        if (!isFunction(funcs[length])) {
+          throw new TypeError;
+        }
+      }
+      return function() {
+        var args = arguments,
+            length = funcs.length;
+
+        while (length--) {
+          args = [funcs[length].apply(this, args)];
+        }
+        return args[0];
+      };
+    }
+
+    /**
+     * Creates a function which accepts one or more arguments of `func` that when
+     * invoked either executes `func` returning its result, if all `func` arguments
+     * have been provided, or returns a function that accepts one or more of the
+     * remaining `func` arguments, and so on. The arity of `func` can be specified
+     * if `func.length` is not sufficient.
+     *
+     * @static
+     * @memberOf _
+     * @category Functions
+     * @param {Function} func The function to curry.
+     * @param {number} [arity=func.length] The arity of `func`.
+     * @returns {Function} Returns the new curried function.
+     * @example
+     *
+     * var curried = _.curry(function(a, b, c) {
+     *   console.log(a + b + c);
+     * });
+     *
+     * curried(1)(2)(3);
+     * // => 6
+     *
+     * curried(1, 2)(3);
+     * // => 6
+     *
+     * curried(1, 2, 3);
+     * // => 6
+     */
+    function curry(func, arity) {
+      arity = typeof arity == 'number' ? arity : (+arity || func.length);
+      return createWrapper(func, 4, null, null, null, arity);
+    }
+
+    /**
+     * Creates a function that will delay the execution of `func` until after
+     * `wait` milliseconds have elapsed since the last time it was invoked.
+     * Provide an options object to indicate that `func` should be invoked on
+     * the leading and/or trailing edge of the `wait` timeout. Subsequent calls
+     * to the debounced function will return the result of the last `func` call.
+     *
+     * Note: If `leading` and `trailing` options are `true` `func` will be called
+     * on the trailing edge of the timeout only if the the debounced function is
+     * invoked more than once during the `wait` timeout.
+     *
+     * @static
+     * @memberOf _
+     * @category Functions
+     * @param {Function} func The function to debounce.
+     * @param {number} wait The number of milliseconds to delay.
+     * @param {Object} [options] The options object.
+     * @param {boolean} [options.leading=false] Specify execution on the leading edge of the timeout.
+     * @param {number} [options.maxWait] The maximum time `func` is allowed to be delayed before it's called.
+     * @param {boolean} [options.trailing=true] Specify execution on the trailing edge of the timeout.
+     * @returns {Function} Returns the new debounced function.
+     * @example
+     *
+     * // avoid costly calculations while the window size is in flux
+     * var lazyLayout = _.debounce(calculateLayout, 150);
+     * jQuery(window).on('resize', lazyLayout);
+     *
+     * // execute `sendMail` when the click event is fired, debouncing subsequent calls
+     * jQuery('#postbox').on('click', _.debounce(sendMail, 300, {
+     *   'leading': true,
+     *   'trailing': false
+     * });
+     *
+     * // ensure `batchLog` is executed once after 1 second of debounced calls
+     * var source = new EventSource('/stream');
+     * source.addEventListener('message', _.debounce(batchLog, 250, {
+     *   'maxWait': 1000
+     * }, false);
+     */
+    function debounce(func, wait, options) {
+      var args,
+          maxTimeoutId,
+          result,
+          stamp,
+          thisArg,
+          timeoutId,
+          trailingCall,
+          lastCalled = 0,
+          maxWait = false,
+          trailing = true;
+
+      if (!isFunction(func)) {
+        throw new TypeError;
+      }
+      wait = nativeMax(0, wait) || 0;
+      if (options === true) {
+        var leading = true;
+        trailing = false;
+      } else if (isObject(options)) {
+        leading = options.leading;
+        maxWait = 'maxWait' in options && (nativeMax(wait, options.maxWait) || 0);
+        trailing = 'trailing' in options ? options.trailing : trailing;
+      }
+      var delayed = function() {
+        var remaining = wait - (now() - stamp);
+        if (remaining <= 0) {
+          if (maxTimeoutId) {
+            clearTimeout(maxTimeoutId);
+          }
+          var isCalled = trailingCall;
+          maxTimeoutId = timeoutId = trailingCall = undefined;
+          if (isCalled) {
+            lastCalled = now();
+            result = func.apply(thisArg, args);
+            if (!timeoutId && !maxTimeoutId) {
+              args = thisArg = null;
+            }
+          }
+        } else {
+          timeoutId = setTimeout(delayed, remaining);
+        }
+      };
+
+      var maxDelayed = function() {
+        if (timeoutId) {
+          clearTimeout(timeoutId);
+        }
+        maxTimeoutId = timeoutId = trailingCall = undefined;
+        if (trailing || (maxWait !== wait)) {
+          lastCalled = now();
+          result = func.apply(thisArg, args);
+          if (!timeoutId && !maxTimeoutId) {
+            args = thisArg = null;
+          }
+        }
+      };
+
+      return function() {
+        args = arguments;
+        stamp = now();
+        thisArg = this;
+        trailingCall = trailing && (timeoutId || !leading);
+
+        if (maxWait === false) {
+          var leadingCall = leading && !timeoutId;
+        } else {
+          if (!maxTimeoutId && !leading) {
+            lastCalled = stamp;
+          }
+          var remaining = maxWait - (stamp - lastCalled),
+              isCalled = remaining <= 0;
+
+          if (isCalled) {
+            if (maxTimeoutId) {
+              maxTimeoutId = clearTimeout(maxTimeoutId);
+            }
+            lastCalled = stamp;
+            result = func.apply(thisArg, args);
+          }
+          else if (!maxTimeoutId) {
+            maxTimeoutId = setTimeout(maxDelayed, remaining);
+          }
+        }
+        if (isCalled && timeoutId) {
+          timeoutId = clearTimeout(timeoutId);
+        }
+        else if (!timeoutId && wait !== maxWait) {
+          timeoutId = setTimeout(delayed, wait);
+        }
+        if (leadingCall) {
+          isCalled = true;
+          result = func.apply(thisArg, args);
+        }
+        if (isCalled && !timeoutId && !maxTimeoutId) {
+          args = thisArg = null;
+        }
+        return result;
+      };
+    }
+
+    /**
+     * Defers executing the `func` function until the current call stack has cleared.
+     * Additional arguments will be provided to `func` when it is invoked.
+     *
+     * @static
+     * @memberOf _
+     * @category Functions
+     * @param {Function} func The function to defer.
+     * @param {...*} [arg] Arguments to invoke the function with.
+     * @returns {number} Returns the timer id.
+     * @example
+     *
+     * _.defer(function(text) { console.log(text); }, 'deferred');
+     * // logs 'deferred' after one or more milliseconds
+     */
+    function defer(func) {
+      if (!isFunction(func)) {
+        throw new TypeError;
+      }
+      var args = slice(arguments, 1);
+      return setTimeout(function() { func.apply(undefined, args); }, 1);
+    }
+
+    /**
+     * Executes the `func` function after `wait` milliseconds. Additional arguments
+     * will be provided to `func` when it is invoked.
+     *
+     * @static
+     * @memberOf _
+     * @category Functions
+     * @param {Function} func The function to delay.
+     * @param {number} wait The number of milliseconds to delay execution.
+     * @param {...*} [arg] Arguments to invoke the function with.
+     * @returns {number} Returns the timer id.
+     * @example
+     *
+     * _.delay(function(text) { console.log(text); }, 1000, 'later');
+     * // => logs 'later' after one second
+     */
+    function delay(func, wait) {
+      if (!isFunction(func)) {
+        throw new TypeError;
+      }
+      var args = slice(arguments, 2);
+      return setTimeout(function() { func.apply(undefined, args); }, wait);
+    }
+
+    /**
+     * Creates a function that memoizes the result of `func`. If `resolver` is
+     * provided it will be used to determine the cache key for storing the result
+     * based on the arguments provided to the memoized function. By default, the
+     * first argument provided to the memoized function is used as the cache key.
+     * The `func` is executed with the `this` binding of the memoized function.
+     * The result cache is exposed as the `cache` property on the memoized function.
+     *
+     * @static
+     * @memberOf _
+     * @category Functions
+     * @param {Function} func The function to have its output memoized.
+     * @param {Function} [resolver] A function used to resolve the cache key.
+     * @returns {Function} Returns the new memoizing function.
+     * @example
+     *
+     * var fibonacci = _.memoize(function(n) {
+     *   return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);
+     * });
+     *
+     * fibonacci(9)
+     * // => 34
+     *
+     * var data = {
+     *   'fred': { 'name': 'fred', 'age': 40 },
+     *   'pebbles': { 'name': 'pebbles', 'age': 1 }
+     * };
+     *
+     * // modifying the result cache
+     * var get = _.memoize(function(name) { return data[name]; }, _.identity);
+     * get('pebbles');
+     * // => { 'name': 'pebbles', 'age': 1 }
+     *
+     * get.cache.pebbles.name = 'penelope';
+     * get('pebbles');
+     * // => { 'name': 'penelope', 'age': 1 }
+     */
+    function memoize(func, resolver) {
+      if (!isFunction(func)) {
+        throw new TypeError;
+      }
+      var memoized = function() {
+        var cache = memoized.cache,
+            key = resolver ? resolver.apply(this, arguments) : keyPrefix + arguments[0];
+
+        return hasOwnProperty.call(cache, key)
+          ? cache[key]
+          : (cache[key] = func.apply(this, arguments));
+      }
+      memoized.cache = {};
+      return memoized;
+    }
+
+    /**
+     * Creates a function that is restricted to execute `func` once. Repeat calls to
+     * the function will return the value of the first call. The `func` is executed
+     * with the `this` binding of the created function.
+     *
+     * @static
+     * @memberOf _
+     * @category Functions
+     * @param {Function} func The function to restrict.
+     * @returns {Function} Returns the new restricted function.
+     * @example
+     *
+     * var initialize = _.once(createApplication);
+     * initialize();
+     * initialize();
+     * // `initialize` executes `createApplication` once
+     */
+    function once(func) {
+      var ran,
+          result;
+
+      if (!isFunction(func)) {
+        throw new TypeError;
+      }
+      return function() {
+        if (ran) {
+          return result;
+        }
+        ran = true;
+        result = func.apply(this, arguments);
+
+        // clear the `func` variable so the function may be garbage collected
+        func = null;
+        return result;
+      };
+    }
+
+    /**
+     * Creates a function that, when called, invokes `func` with any additional
+     * `partial` arguments prepended to those provided to the new function. This
+     * method is similar to `_.bind` except it does **not** alter the `this` binding.
+     *
+     * @static
+     * @memberOf _
+     * @category Functions
+     * @param {Function} func The function to partially apply arguments to.
+     * @param {...*} [arg] Arguments to be partially applied.
+     * @returns {Function} Returns the new partially applied function.
+     * @example
+     *
+     * var greet = function(greeting, name) { return greeting + ' ' + name; };
+     * var hi = _.partial(greet, 'hi');
+     * hi('fred');
+     * // => 'hi fred'
+     */
+    function partial(func) {
+      return createWrapper(func, 16, slice(arguments, 1));
+    }
+
+    /**
+     * This method is like `_.partial` except that `partial` arguments are
+     * appended to those provided to the new function.
+     *
+     * @static
+     * @memberOf _
+     * @category Functions
+     * @param {Function} func The function to partially apply arguments to.
+     * @param {...*} [arg] Arguments to be partially applied.
+     * @returns {Function} Returns the new partially applied function.
+     * @example
+     *
+     * var defaultsDeep = _.partialRight(_.merge, _.defaults);
+     *
+     * var options = {
+     *   'variable': 'data',
+     *   'imports': { 'jq': $ }
+     * };
+     *
+     * defaultsDeep(options, _.templateSettings);
+     *
+     * options.variable
+     * // => 'data'
+     *
+     * options.imports
+     * // => { '_': _, 'jq': $ }
+     */
+    function partialRight(func) {
+      return createWrapper(func, 32, null, slice(arguments, 1));
+    }
+
+    /**
+     * Creates a function that, when executed, will only call the `func` function
+     * at most once per every `wait` milliseconds. Provide an options object to
+     * indicate that `func` should be invoked on the leading and/or trailing edge
+     * of the `wait` timeout. Subsequent calls to the throttled function will
+     * return the result of the last `func` call.
+     *
+     * Note: If `leading` and `trailing` options are `true` `func` will be called
+     * on the trailing edge of the timeout only if the the throttled function is
+     * invoked more than once during the `wait` timeout.
+     *
+     * @static
+     * @memberOf _
+     * @category Functions
+     * @param {Function} func The function to throttle.
+     * @param {number} wait The number of milliseconds to throttle executions to.
+     * @param {Object} [options] The options object.
+     * @param {boolean} [options.leading=true] Specify execution on the leading edge of the timeout.
+     * @param {boolean} [options.trailing=true] Specify execution on the trailing edge of the timeout.
+     * @returns {Function} Returns the new throttled function.
+     * @example
+     *
+     * // avoid excessively updating the position while scrolling
+     * var throttled = _.throttle(updatePosition, 100);
+     * jQuery(window).on('scroll', throttled);
+     *
+     * // execute `renewToken` when the click event is fired, but not more than once every 5 minutes
+     * jQuery('.interactive').on('click', _.throttle(renewToken, 300000, {
+     *   'trailing': false
+     * }));
+     */
+    function throttle(func, wait, options) {
+      var leading = true,
+          trailing = true;
+
+      if (!isFunction(func)) {
+        throw new TypeError;
+      }
+      if (options === false) {
+        leading = false;
+      } else if (isObject(options)) {
+        leading = 'leading' in options ? options.leading : leading;
+        trailing = 'trailing' in options ? options.trailing : trailing;
+      }
+      debounceOptions.leading = leading;
+      debounceOptions.maxWait = wait;
+      debounceOptions.trailing = trailing;
+
+      return debounce(func, wait, debounceOptions);
+    }
+
+    /**
+     * Creates a function that provides `value` to the wrapper function as its
+     * first argument. Additional arguments provided to the function are appended
+     * to those provided to the wrapper function. The wrapper is executed with
+     * the `this` binding of the created function.
+     *
+     * @static
+     * @memberOf _
+     * @category Functions
+     * @param {*} value The value to wrap.
+     * @param {Function} wrapper The wrapper function.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var p = _.wrap(_.escape, function(func, text) {
+     *   return '<p>' + func(text) + '</p>';
+     * });
+     *
+     * p('Fred, Wilma, & Pebbles');
+     * // => '<p>Fred, Wilma, &amp; Pebbles</p>'
+     */
+    function wrap(value, wrapper) {
+      return createWrapper(wrapper, 16, [value]);
+    }
+
+    /*--------------------------------------------------------------------------*/
+
+    /**
+     * Creates a function that returns `value`.
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @param {*} value The value to return from the new function.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var object = { 'name': 'fred' };
+     * var getter = _.constant(object);
+     * getter() === object;
+     * // => true
+     */
+    function constant(value) {
+      return function() {
+        return value;
+      };
+    }
+
+    /**
+     * Produces a callback bound to an optional `thisArg`. If `func` is a property
+     * name the created callback will return the property value for a given element.
+     * If `func` is an object the created callback will return `true` for elements
+     * that contain the equivalent object properties, otherwise it will return `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @param {*} [func=identity] The value to convert to a callback.
+     * @param {*} [thisArg] The `this` binding of the created callback.
+     * @param {number} [argCount] The number of arguments the callback accepts.
+     * @returns {Function} Returns a callback function.
+     * @example
+     *
+     * var characters = [
+     *   { 'name': 'barney', 'age': 36 },
+     *   { 'name': 'fred',   'age': 40 }
+     * ];
+     *
+     * // wrap to create custom callback shorthands
+     * _.createCallback = _.wrap(_.createCallback, function(func, callback, thisArg) {
+     *   var match = /^(.+?)__([gl]t)(.+)$/.exec(callback);
+     *   return !match ? func(callback, thisArg) : function(object) {
+     *     return match[2] == 'gt' ? object[match[1]] > match[3] : object[match[1]] < match[3];
+     *   };
+     * });
+     *
+     * _.filter(characters, 'age__gt38');
+     * // => [{ 'name': 'fred', 'age': 40 }]
+     */
+    function createCallback(func, thisArg, argCount) {
+      var type = typeof func;
+      if (func == null || type == 'function') {
+        return baseCreateCallback(func, thisArg, argCount);
+      }
+      // handle "_.pluck" style callback shorthands
+      if (type != 'object') {
+        return property(func);
+      }
+      var props = keys(func),
+          key = props[0],
+          a = func[key];
+
+      // handle "_.where" style callback shorthands
+      if (props.length == 1 && a === a && !isObject(a)) {
+        // fast path the common case of providing an object with a single
+        // property containing a primitive value
+        return function(object) {
+          var b = object[key];
+          return a === b && (a !== 0 || (1 / a == 1 / b));
+        };
+      }
+      return function(object) {
+        var length = props.length,
+            result = false;
+
+        while (length--) {
+          if (!(result = baseIsEqual(object[props[length]], func[props[length]], null, true))) {
+            break;
+          }
+        }
+        return result;
+      };
+    }
+
+    /**
+     * Converts the characters `&`, `<`, `>`, `"`, and `'` in `string` to their
+     * corresponding HTML entities.
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @param {string} string The string to escape.
+     * @returns {string} Returns the escaped string.
+     * @example
+     *
+     * _.escape('Fred, Wilma, & Pebbles');
+     * // => 'Fred, Wilma, &amp; Pebbles'
+     */
+    function escape(string) {
+      return string == null ? '' : String(string).replace(reUnescapedHtml, escapeHtmlChar);
+    }
+
+    /**
+     * This method returns the first argument provided to it.
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @param {*} value Any value.
+     * @returns {*} Returns `value`.
+     * @example
+     *
+     * var object = { 'name': 'fred' };
+     * _.identity(object) === object;
+     * // => true
+     */
+    function identity(value) {
+      return value;
+    }
+
+    /**
+     * Adds function properties of a source object to the destination object.
+     * If `object` is a function methods will be added to its prototype as well.
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @param {Function|Object} [object=lodash] object The destination object.
+     * @param {Object} source The object of functions to add.
+     * @param {Object} [options] The options object.
+     * @param {boolean} [options.chain=true] Specify whether the functions added are chainable.
+     * @example
+     *
+     * function capitalize(string) {
+     *   return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();
+     * }
+     *
+     * _.mixin({ 'capitalize': capitalize });
+     * _.capitalize('fred');
+     * // => 'Fred'
+     *
+     * _('fred').capitalize().value();
+     * // => 'Fred'
+     *
+     * _.mixin({ 'capitalize': capitalize }, { 'chain': false });
+     * _('fred').capitalize();
+     * // => 'Fred'
+     */
+    function mixin(object, source, options) {
+      var chain = true,
+          methodNames = source && functions(source);
+
+      if (!source || (!options && !methodNames.length)) {
+        if (options == null) {
+          options = source;
+        }
+        ctor = lodashWrapper;
+        source = object;
+        object = lodash;
+        methodNames = functions(source);
+      }
+      if (options === false) {
+        chain = false;
+      } else if (isObject(options) && 'chain' in options) {
+        chain = options.chain;
+      }
+      var ctor = object,
+          isFunc = isFunction(ctor);
+
+      forEach(methodNames, function(methodName) {
+        var func = object[methodName] = source[methodName];
+        if (isFunc) {
+          ctor.prototype[methodName] = function() {
+            var chainAll = this.__chain__,
+                value = this.__wrapped__,
+                args = [value];
+
+            push.apply(args, arguments);
+            var result = func.apply(object, args);
+            if (chain || chainAll) {
+              if (value === result && isObject(result)) {
+                return this;
+              }
+              result = new ctor(result);
+              result.__chain__ = chainAll;
+            }
+            return result;
+          };
+        }
+      });
+    }
+
+    /**
+     * Reverts the '_' variable to its previous value and returns a reference to
+     * the `lodash` function.
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @returns {Function} Returns the `lodash` function.
+     * @example
+     *
+     * var lodash = _.noConflict();
+     */
+    function noConflict() {
+      context._ = oldDash;
+      return this;
+    }
+
+    /**
+     * A no-operation function.
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @example
+     *
+     * var object = { 'name': 'fred' };
+     * _.noop(object) === undefined;
+     * // => true
+     */
+    function noop() {
+      // no operation performed
+    }
+
+    /**
+     * Gets the number of milliseconds that have elapsed since the Unix epoch
+     * (1 January 1970 00:00:00 UTC).
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @example
+     *
+     * var stamp = _.now();
+     * _.defer(function() { console.log(_.now() - stamp); });
+     * // => logs the number of milliseconds it took for the deferred function to be called
+     */
+    var now = isNative(now = Date.now) && now || function() {
+      return new Date().getTime();
+    };
+
+    /**
+     * Converts the given value into an integer of the specified radix.
+     * If `radix` is `undefined` or `0` a `radix` of `10` is used unless the
+     * `value` is a hexadecimal, in which case a `radix` of `16` is used.
+     *
+     * Note: This method avoids differences in native ES3 and ES5 `parseInt`
+     * implementations. See http://es5.github.io/#E.
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @param {string} value The value to parse.
+     * @param {number} [radix] The radix used to interpret the value to parse.
+     * @returns {number} Returns the new integer value.
+     * @example
+     *
+     * _.parseInt('08');
+     * // => 8
+     */
+    var parseInt = nativeParseInt(whitespace + '08') == 8 ? nativeParseInt : function(value, radix) {
+      // Firefox < 21 and Opera < 15 follow the ES3 specified implementation of `parseInt`
+      return nativeParseInt(isString(value) ? value.replace(reLeadingSpacesAndZeros, '') : value, radix || 0);
+    };
+
+    /**
+     * Creates a "_.pluck" style function, which returns the `key` value of a
+     * given object.
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @param {string} key The name of the property to retrieve.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var characters = [
+     *   { 'name': 'fred',   'age': 40 },
+     *   { 'name': 'barney', 'age': 36 }
+     * ];
+     *
+     * var getName = _.property('name');
+     *
+     * _.map(characters, getName);
+     * // => ['barney', 'fred']
+     *
+     * _.sortBy(characters, getName);
+     * // => [{ 'name': 'barney', 'age': 36 }, { 'name': 'fred',   'age': 40 }]
+     */
+    function property(key) {
+      return function(object) {
+        return object[key];
+      };
+    }
+
+    /**
+     * Produces a random number between `min` and `max` (inclusive). If only one
+     * argument is provided a number between `0` and the given number will be
+     * returned. If `floating` is truey or either `min` or `max` are floats a
+     * floating-point number will be returned instead of an integer.
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @param {number} [min=0] The minimum possible value.
+     * @param {number} [max=1] The maximum possible value.
+     * @param {boolean} [floating=false] Specify returning a floating-point number.
+     * @returns {number} Returns a random number.
+     * @example
+     *
+     * _.random(0, 5);
+     * // => an integer between 0 and 5
+     *
+     * _.random(5);
+     * // => also an integer between 0 and 5
+     *
+     * _.random(5, true);
+     * // => a floating-point number between 0 and 5
+     *
+     * _.random(1.2, 5.2);
+     * // => a floating-point number between 1.2 and 5.2
+     */
+    function random(min, max, floating) {
+      var noMin = min == null,
+          noMax = max == null;
+
+      if (floating == null) {
+        if (typeof min == 'boolean' && noMax) {
+          floating = min;
+          min = 1;
+        }
+        else if (!noMax && typeof max == 'boolean') {
+          floating = max;
+          noMax = true;
+        }
+      }
+      if (noMin && noMax) {
+        max = 1;
+      }
+      min = +min || 0;
+      if (noMax) {
+        max = min;
+        min = 0;
+      } else {
+        max = +max || 0;
+      }
+      if (floating || min % 1 || max % 1) {
+        var rand = nativeRandom();
+        return nativeMin(min + (rand * (max - min + parseFloat('1e-' + ((rand +'').length - 1)))), max);
+      }
+      return baseRandom(min, max);
+    }
+
+    /**
+     * Resolves the value of property `key` on `object`. If `key` is a function
+     * it will be invoked with the `this` binding of `object` and its result returned,
+     * else the property value is returned. If `object` is falsey then `undefined`
+     * is returned.
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @param {Object} object The object to inspect.
+     * @param {string} key The name of the property to resolve.
+     * @returns {*} Returns the resolved value.
+     * @example
+     *
+     * var object = {
+     *   'cheese': 'crumpets',
+     *   'stuff': function() {
+     *     return 'nonsense';
+     *   }
+     * };
+     *
+     * _.result(object, 'cheese');
+     * // => 'crumpets'
+     *
+     * _.result(object, 'stuff');
+     * // => 'nonsense'
+     */
+    function result(object, key) {
+      if (object) {
+        var value = object[key];
+        return isFunction(value) ? object[key]() : value;
+      }
+    }
+
+    /**
+     * A micro-templating method that handles arbitrary delimiters, preserves
+     * whitespace, and correctly escapes quotes within interpolated code.
+     *
+     * Note: In the development build, `_.template` utilizes sourceURLs for easier
+     * debugging. See http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl
+     *
+     * For more information on precompiling templates see:
+     * http://lodash.com/custom-builds
+     *
+     * For more information on Chrome extension sandboxes see:
+     * http://developer.chrome.com/stable/extensions/sandboxingEval.html
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @param {string} text The template text.
+     * @param {Object} data The data object used to populate the text.
+     * @param {Object} [options] The options object.
+     * @param {RegExp} [options.escape] The "escape" delimiter.
+     * @param {RegExp} [options.evaluate] The "evaluate" delimiter.
+     * @param {Object} [options.imports] An object to import into the template as local variables.
+     * @param {RegExp} [options.interpolate] The "interpolate" delimiter.
+     * @param {string} [sourceURL] The sourceURL of the template's compiled source.
+     * @param {string} [variable] The data object variable name.
+     * @returns {Function|string} Returns a compiled function when no `data` object
+     *  is given, else it returns the interpolated text.
+     * @example
+     *
+     * // using the "interpolate" delimiter to create a compiled template
+     * var compiled = _.template('hello <%= name %>');
+     * compiled({ 'name': 'fred' });
+     * // => 'hello fred'
+     *
+     * // using the "escape" delimiter to escape HTML in data property values
+     * _.template('<b><%- value %></b>', { 'value': '<script>' });
+     * // => '<b>&lt;script&gt;</b>'
+     *
+     * // using the "evaluate" delimiter to generate HTML
+     * var list = '<% _.forEach(people, function(name) { %><li><%- name %></li><% }); %>';
+     * _.template(list, { 'people': ['fred', 'barney'] });
+     * // => '<li>fred</li><li>barney</li>'
+     *
+     * // using the ES6 delimiter as an alternative to the default "interpolate" delimiter
+     * _.template('hello ${ name }', { 'name': 'pebbles' });
+     * // => 'hello pebbles'
+     *
+     * // using the internal `print` function in "evaluate" delimiters
+     * _.template('<% print("hello " + name); %>!', { 'name': 'barney' });
+     * // => 'hello barney!'
+     *
+     * // using a custom template delimiters
+     * _.templateSettings = {
+     *   'interpolate': /{{([\s\S]+?)}}/g
+     * };
+     *
+     * _.template('hello {{ name }}!', { 'name': 'mustache' });
+     * // => 'hello mustache!'
+     *
+     * // using the `imports` option to import jQuery
+     * var list = '<% jq.each(people, function(name) { %><li><%- name %></li><% }); %>';
+     * _.template(list, { 'people': ['fred', 'barney'] }, { 'imports': { 'jq': jQuery } });
+     * // => '<li>fred</li><li>barney</li>'
+     *
+     * // using the `sourceURL` option to specify a custom sourceURL for the template
+     * var compiled = _.template('hello <%= name %>', null, { 'sourceURL': '/basic/greeting.jst' });
+     * compiled(data);
+     * // => find the source of "greeting.jst" under the Sources tab or Resources panel of the web inspector
+     *
+     * // using the `variable` option to ensure a with-statement isn't used in the compiled template
+     * var compiled = _.template('hi <%= data.name %>!', null, { 'variable': 'data' });
+     * compiled.source;
+     * // => function(data) {
+     *   var __t, __p = '', __e = _.escape;
+     *   __p += 'hi ' + ((__t = ( data.name )) == null ? '' : __t) + '!';
+     *   return __p;
+     * }
+     *
+     * // using the `source` property to inline compiled templates for meaningful
+     * // line numbers in error messages and a stack trace
+     * fs.writeFileSync(path.join(cwd, 'jst.js'), '\
+     *   var JST = {\
+     *     "main": ' + _.template(mainText).source + '\
+     *   };\
+     * ');
+     */
+    function template(text, data, options) {
+      // based on John Resig's `tmpl` implementation
+      // http://ejohn.org/blog/javascript-micro-templating/
+      // and Laura Doktorova's doT.js
+      // https://github.com/olado/doT
+      var settings = lodash.templateSettings;
+      text = String(text || '');
+
+      // avoid missing dependencies when `iteratorTemplate` is not defined
+      options = defaults({}, options, settings);
+
+      var imports = defaults({}, options.imports, settings.imports),
+          importsKeys = keys(imports),
+          importsValues = values(imports);
+
+      var isEvaluating,
+          index = 0,
+          interpolate = options.interpolate || reNoMatch,
+          source = "__p += '";
+
+      // compile the regexp to match each delimiter
+      var reDelimiters = RegExp(
+        (options.escape || reNoMatch).source + '|' +
+        interpolate.source + '|' +
+        (interpolate === reInterpolate ? reEsTemplate : reNoMatch).source + '|' +
+        (options.evaluate || reNoMatch).source + '|$'
+      , 'g');
+
+      text.replace(reDelimiters, function(match, escapeValue, interpolateValue, esTemplateValue, evaluateValue, offset) {
+        interpolateValue || (interpolateValue = esTemplateValue);
+
+        // escape characters that cannot be included in string literals
+        source += text.slice(index, offset).replace(reUnescapedString, escapeStringChar);
+
+        // replace delimiters with snippets
+        if (escapeValue) {
+          source += "' +\n__e(" + escapeValue + ") +\n'";
+        }
+        if (evaluateValue) {
+          isEvaluating = true;
+          source += "';\n" + evaluateValue + ";\n__p += '";
+        }
+        if (interpolateValue) {
+          source += "' +\n((__t = (" + interpolateValue + ")) == null ? '' : __t) +\n'";
+        }
+        index = offset + match.length;
+
+        // the JS engine embedded in Adobe products requires returning the `match`
+        // string in order to produce the correct `offset` value
+        return match;
+      });
+
+      source += "';\n";
+
+      // if `variable` is not specified, wrap a with-statement around the generated
+      // code to add the data object to the top of the scope chain
+      var variable = options.variable,
+          hasVariable = variable;
+
+      if (!hasVariable) {
+        variable = 'obj';
+        source = 'with (' + variable + ') {\n' + source + '\n}\n';
+      }
+      // cleanup code by stripping empty strings
+      source = (isEvaluating ? source.replace(reEmptyStringLeading, '') : source)
+        .replace(reEmptyStringMiddle, '$1')
+        .replace(reEmptyStringTrailing, '$1;');
+
+      // frame code as the function body
+      source = 'function(' + variable + ') {\n' +
+        (hasVariable ? '' : variable + ' || (' + variable + ' = {});\n') +
+        "var __t, __p = '', __e = _.escape" +
+        (isEvaluating
+          ? ', __j = Array.prototype.join;\n' +
+            "function print() { __p += __j.call(arguments, '') }\n"
+          : ';\n'
+        ) +
+        source +
+        'return __p\n}';
+
+      // Use a sourceURL for easier debugging.
+      // http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl
+      var sourceURL = '\n/*\n//# sourceURL=' + (options.sourceURL || '/lodash/template/source[' + (templateCounter++) + ']') + '\n*/';
+
+      try {
+        var result = Function(importsKeys, 'return ' + source + sourceURL).apply(undefined, importsValues);
+      } catch(e) {
+        e.source = source;
+        throw e;
+      }
+      if (data) {
+        return result(data);
+      }
+      // provide the compiled function's source by its `toString` method, in
+      // supported environments, or the `source` property as a convenience for
+      // inlining compiled templates during the build process
+      result.source = source;
+      return result;
+    }
+
+    /**
+     * Executes the callback `n` times, returning an array of the results
+     * of each callback execution. The callback is bound to `thisArg` and invoked
+     * with one argument; (index).
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @param {number} n The number of times to execute the callback.
+     * @param {Function} callback The function called per iteration.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Array} Returns an array of the results of each `callback` execution.
+     * @example
+     *
+     * var diceRolls = _.times(3, _.partial(_.random, 1, 6));
+     * // => [3, 6, 4]
+     *
+     * _.times(3, function(n) { mage.castSpell(n); });
+     * // => calls `mage.castSpell(n)` three times, passing `n` of `0`, `1`, and `2` respectively
+     *
+     * _.times(3, function(n) { this.cast(n); }, mage);
+     * // => also calls `mage.castSpell(n)` three times
+     */
+    function times(n, callback, thisArg) {
+      n = (n = +n) > -1 ? n : 0;
+      var index = -1,
+          result = Array(n);
+
+      callback = baseCreateCallback(callback, thisArg, 1);
+      while (++index < n) {
+        result[index] = callback(index);
+      }
+      return result;
+    }
+
+    /**
+     * The inverse of `_.escape` this method converts the HTML entities
+     * `&amp;`, `&lt;`, `&gt;`, `&quot;`, and `&#39;` in `string` to their
+     * corresponding characters.
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @param {string} string The string to unescape.
+     * @returns {string} Returns the unescaped string.
+     * @example
+     *
+     * _.unescape('Fred, Barney &amp; Pebbles');
+     * // => 'Fred, Barney & Pebbles'
+     */
+    function unescape(string) {
+      return string == null ? '' : String(string).replace(reEscapedHtml, unescapeHtmlChar);
+    }
+
+    /**
+     * Generates a unique ID. If `prefix` is provided the ID will be appended to it.
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @param {string} [prefix] The value to prefix the ID with.
+     * @returns {string} Returns the unique ID.
+     * @example
+     *
+     * _.uniqueId('contact_');
+     * // => 'contact_104'
+     *
+     * _.uniqueId();
+     * // => '105'
+     */
+    function uniqueId(prefix) {
+      var id = ++idCounter;
+      return String(prefix == null ? '' : prefix) + id;
+    }
+
+    /*--------------------------------------------------------------------------*/
+
+    /**
+     * Creates a `lodash` object that wraps the given value with explicit
+     * method chaining enabled.
+     *
+     * @static
+     * @memberOf _
+     * @category Chaining
+     * @param {*} value The value to wrap.
+     * @returns {Object} Returns the wrapper object.
+     * @example
+     *
+     * var characters = [
+     *   { 'name': 'barney',  'age': 36 },
+     *   { 'name': 'fred',    'age': 40 },
+     *   { 'name': 'pebbles', 'age': 1 }
+     * ];
+     *
+     * var youngest = _.chain(characters)
+     *     .sortBy('age')
+     *     .map(function(chr) { return chr.name + ' is ' + chr.age; })
+     *     .first()
+     *     .value();
+     * // => 'pebbles is 1'
+     */
+    function chain(value) {
+      value = new lodashWrapper(value);
+      value.__chain__ = true;
+      return value;
+    }
+
+    /**
+     * Invokes `interceptor` with the `value` as the first argument and then
+     * returns `value`. The purpose of this method is to "tap into" a method
+     * chain in order to perform operations on intermediate results within
+     * the chain.
+     *
+     * @static
+     * @memberOf _
+     * @category Chaining
+     * @param {*} value The value to provide to `interceptor`.
+     * @param {Function} interceptor The function to invoke.
+     * @returns {*} Returns `value`.
+     * @example
+     *
+     * _([1, 2, 3, 4])
+     *  .tap(function(array) { array.pop(); })
+     *  .reverse()
+     *  .value();
+     * // => [3, 2, 1]
+     */
+    function tap(value, interceptor) {
+      interceptor(value);
+      return value;
+    }
+
+    /**
+     * Enables explicit method chaining on the wrapper object.
+     *
+     * @name chain
+     * @memberOf _
+     * @category Chaining
+     * @returns {*} Returns the wrapper object.
+     * @example
+     *
+     * var characters = [
+     *   { 'name': 'barney', 'age': 36 },
+     *   { 'name': 'fred',   'age': 40 }
+     * ];
+     *
+     * // without explicit chaining
+     * _(characters).first();
+     * // => { 'name': 'barney', 'age': 36 }
+     *
+     * // with explicit chaining
+     * _(characters).chain()
+     *   .first()
+     *   .pick('age')
+     *   .value();
+     * // => { 'age': 36 }
+     */
+    function wrapperChain() {
+      this.__chain__ = true;
+      return this;
+    }
+
+    /**
+     * Produces the `toString` result of the wrapped value.
+     *
+     * @name toString
+     * @memberOf _
+     * @category Chaining
+     * @returns {string} Returns the string result.
+     * @example
+     *
+     * _([1, 2, 3]).toString();
+     * // => '1,2,3'
+     */
+    function wrapperToString() {
+      return String(this.__wrapped__);
+    }
+
+    /**
+     * Extracts the wrapped value.
+     *
+     * @name valueOf
+     * @memberOf _
+     * @alias value
+     * @category Chaining
+     * @returns {*} Returns the wrapped value.
+     * @example
+     *
+     * _([1, 2, 3]).valueOf();
+     * // => [1, 2, 3]
+     */
+    function wrapperValueOf() {
+      return this.__wrapped__;
+    }
+
+    /*--------------------------------------------------------------------------*/
+
+    // add functions that return wrapped values when chaining
+    lodash.after = after;
+    lodash.assign = assign;
+    lodash.at = at;
+    lodash.bind = bind;
+    lodash.bindAll = bindAll;
+    lodash.bindKey = bindKey;
+    lodash.chain = chain;
+    lodash.compact = compact;
+    lodash.compose = compose;
+    lodash.constant = constant;
+    lodash.countBy = countBy;
+    lodash.create = create;
+    lodash.createCallback = createCallback;
+    lodash.curry = curry;
+    lodash.debounce = debounce;
+    lodash.defaults = defaults;
+    lodash.defer = defer;
+    lodash.delay = delay;
+    lodash.difference = difference;
+    lodash.filter = filter;
+    lodash.flatten = flatten;
+    lodash.forEach = forEach;
+    lodash.forEachRight = forEachRight;
+    lodash.forIn = forIn;
+    lodash.forInRight = forInRight;
+    lodash.forOwn = forOwn;
+    lodash.forOwnRight = forOwnRight;
+    lodash.functions = functions;
+    lodash.groupBy = groupBy;
+    lodash.indexBy = indexBy;
+    lodash.initial = initial;
+    lodash.intersection = intersection;
+    lodash.invert = invert;
+    lodash.invoke = invoke;
+    lodash.keys = keys;
+    lodash.map = map;
+    lodash.mapValues = mapValues;
+    lodash.max = max;
+    lodash.memoize = memoize;
+    lodash.merge = merge;
+    lodash.min = min;
+    lodash.omit = omit;
+    lodash.once = once;
+    lodash.pairs = pairs;
+    lodash.partial = partial;
+    lodash.partialRight = partialRight;
+    lodash.pick = pick;
+    lodash.pluck = pluck;
+    lodash.property = property;
+    lodash.pull = pull;
+    lodash.range = range;
+    lodash.reject = reject;
+    lodash.remove = remove;
+    lodash.rest = rest;
+    lodash.shuffle = shuffle;
+    lodash.sortBy = sortBy;
+    lodash.tap = tap;
+    lodash.throttle = throttle;
+    lodash.times = times;
+    lodash.toArray = toArray;
+    lodash.transform = transform;
+    lodash.union = union;
+    lodash.uniq = uniq;
+    lodash.values = values;
+    lodash.where = where;
+    lodash.without = without;
+    lodash.wrap = wrap;
+    lodash.xor = xor;
+    lodash.zip = zip;
+    lodash.zipObject = zipObject;
+
+    // add aliases
+    lodash.collect = map;
+    lodash.drop = rest;
+    lodash.each = forEach;
+    lodash.eachRight = forEachRight;
+    lodash.extend = assign;
+    lodash.methods = functions;
+    lodash.object = zipObject;
+    lodash.select = filter;
+    lodash.tail = rest;
+    lodash.unique = uniq;
+    lodash.unzip = zip;
+
+    // add functions to `lodash.prototype`
+    mixin(lodash);
+
+    /*--------------------------------------------------------------------------*/
+
+    // add functions that return unwrapped values when chaining
+    lodash.clone = clone;
+    lodash.cloneDeep = cloneDeep;
+    lodash.contains = contains;
+    lodash.escape = escape;
+    lodash.every = every;
+    lodash.find = find;
+    lodash.findIndex = findIndex;
+    lodash.findKey = findKey;
+    lodash.findLast = findLast;
+    lodash.findLastIndex = findLastIndex;
+    lodash.findLastKey = findLastKey;
+    lodash.has = has;
+    lodash.identity = identity;
+    lodash.indexOf = indexOf;
+    lodash.isArguments = isArguments;
+    lodash.isArray = isArray;
+    lodash.isBoolean = isBoolean;
+    lodash.isDate = isDate;
+    lodash.isElement = isElement;
+    lodash.isEmpty = isEmpty;
+    lodash.isEqual = isEqual;
+    lodash.isFinite = isFinite;
+    lodash.isFunction = isFunction;
+    lodash.isNaN = isNaN;
+    lodash.isNull = isNull;
+    lodash.isNumber = isNumber;
+    lodash.isObject = isObject;
+    lodash.isPlainObject = isPlainObject;
+    lodash.isRegExp = isRegExp;
+    lodash.isString = isString;
+    lodash.isUndefined = isUndefined;
+    lodash.lastIndexOf = lastIndexOf;
+    lodash.mixin = mixin;
+    lodash.noConflict = noConflict;
+    lodash.noop = noop;
+    lodash.now = now;
+    lodash.parseInt = parseInt;
+    lodash.random = random;
+    lodash.reduce = reduce;
+    lodash.reduceRight = reduceRight;
+    lodash.result = result;
+    lodash.runInContext = runInContext;
+    lodash.size = size;
+    lodash.some = some;
+    lodash.sortedIndex = sortedIndex;
+    lodash.template = template;
+    lodash.unescape = unescape;
+    lodash.uniqueId = uniqueId;
+
+    // add aliases
+    lodash.all = every;
+    lodash.any = some;
+    lodash.detect = find;
+    lodash.findWhere = find;
+    lodash.foldl = reduce;
+    lodash.foldr = reduceRight;
+    lodash.include = contains;
+    lodash.inject = reduce;
+
+    mixin(function() {
+      var source = {}
+      forOwn(lodash, function(func, methodName) {
+        if (!lodash.prototype[methodName]) {
+          source[methodName] = func;
+        }
+      });
+      return source;
+    }(), false);
+
+    /*--------------------------------------------------------------------------*/
+
+    // add functions capable of returning wrapped and unwrapped values when chaining
+    lodash.first = first;
+    lodash.last = last;
+    lodash.sample = sample;
+
+    // add aliases
+    lodash.take = first;
+    lodash.head = first;
+
+    forOwn(lodash, function(func, methodName) {
+      var callbackable = methodName !== 'sample';
+      if (!lodash.prototype[methodName]) {
+        lodash.prototype[methodName]= function(n, guard) {
+          var chainAll = this.__chain__,
+              result = func(this.__wrapped__, n, guard);
+
+          return !chainAll && (n == null || (guard && !(callbackable && typeof n == 'function')))
+            ? result
+            : new lodashWrapper(result, chainAll);
+        };
+      }
+    });
+
+    /*--------------------------------------------------------------------------*/
+
+    /**
+     * The semantic version number.
+     *
+     * @static
+     * @memberOf _
+     * @type string
+     */
+    lodash.VERSION = '2.4.1';
+
+    // add "Chaining" functions to the wrapper
+    lodash.prototype.chain = wrapperChain;
+    lodash.prototype.toString = wrapperToString;
+    lodash.prototype.value = wrapperValueOf;
+    lodash.prototype.valueOf = wrapperValueOf;
+
+    // add `Array` functions that return unwrapped values
+    forEach(['join', 'pop', 'shift'], function(methodName) {
+      var func = arrayRef[methodName];
+      lodash.prototype[methodName] = function() {
+        var chainAll = this.__chain__,
+            result = func.apply(this.__wrapped__, arguments);
+
+        return chainAll
+          ? new lodashWrapper(result, chainAll)
+          : result;
+      };
+    });
+
+    // add `Array` functions that return the existing wrapped value
+    forEach(['push', 'reverse', 'sort', 'unshift'], function(methodName) {
+      var func = arrayRef[methodName];
+      lodash.prototype[methodName] = function() {
+        func.apply(this.__wrapped__, arguments);
+        return this;
+      };
+    });
+
+    // add `Array` functions that return new wrapped values
+    forEach(['concat', 'slice', 'splice'], function(methodName) {
+      var func = arrayRef[methodName];
+      lodash.prototype[methodName] = function() {
+        return new lodashWrapper(func.apply(this.__wrapped__, arguments), this.__chain__);
+      };
+    });
+
+    return lodash;
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  // expose Lo-Dash
+  var _ = runInContext();
+
+  // some AMD build optimizers like r.js check for condition patterns like the following:
+  if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) {
+    // Expose Lo-Dash to the global object even when an AMD loader is present in
+    // case Lo-Dash is loaded with a RequireJS shim config.
+    // See http://requirejs.org/docs/api.html#config-shim
+    root._ = _;
+
+    // define as an anonymous module so, through path mapping, it can be
+    // referenced as the "underscore" module
+    define(function() {
+      return _;
+    });
+  }
+  // check for `exports` after `define` in case a build optimizer adds an `exports` object
+  else if (freeExports && freeModule) {
+    // in Node.js or RingoJS
+    if (moduleExports) {
+      (freeModule.exports = _)._ = _;
+    }
+    // in Narwhal or Rhino -require
+    else {
+      freeExports._ = _;
+    }
+  }
+  else {
+    // in a browser or Rhino
+    root._ = _;
+  }
+}.call(this));
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lodash/dist/lodash.min.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lodash/dist/lodash.min.js
new file mode 100644
index 0000000..85a9626
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lodash/dist/lodash.min.js
@@ -0,0 +1,56 @@
+/**
+ * @license
+ * Lo-Dash 2.4.1 (Custom Build) lodash.com/license | Underscore.js 1.5.2 underscorejs.org/LICENSE
+ * Build: `lodash modern -o ./dist/lodash.js`
+ */
+;(function(){function n(n,t,e){e=(e||0)-1;for(var r=n?n.length:0;++e<r;)if(n[e]===t)return e;return-1}function t(t,e){var r=typeof e;if(t=t.l,"boolean"==r||null==e)return t[e]?0:-1;"number"!=r&&"string"!=r&&(r="object");var u="number"==r?e:m+e;return t=(t=t[r])&&t[u],"object"==r?t&&-1<n(t,e)?0:-1:t?0:-1}function e(n){var t=this.l,e=typeof n;if("boolean"==e||null==n)t[n]=true;else{"number"!=e&&"string"!=e&&(e="object");var r="number"==e?n:m+n,t=t[e]||(t[e]={});"object"==e?(t[r]||(t[r]=[])).push(n):t[r]=true
+}}function r(n){return n.charCodeAt(0)}function u(n,t){for(var e=n.m,r=t.m,u=-1,o=e.length;++u<o;){var i=e[u],a=r[u];if(i!==a){if(i>a||typeof i=="undefined")return 1;if(i<a||typeof a=="undefined")return-1}}return n.n-t.n}function o(n){var t=-1,r=n.length,u=n[0],o=n[r/2|0],i=n[r-1];if(u&&typeof u=="object"&&o&&typeof o=="object"&&i&&typeof i=="object")return false;for(u=f(),u["false"]=u["null"]=u["true"]=u.undefined=false,o=f(),o.k=n,o.l=u,o.push=e;++t<r;)o.push(n[t]);return o}function i(n){return"\\"+U[n]
+}function a(){return h.pop()||[]}function f(){return g.pop()||{k:null,l:null,m:null,"false":false,n:0,"null":false,number:null,object:null,push:null,string:null,"true":false,undefined:false,o:null}}function l(n){n.length=0,h.length<_&&h.push(n)}function c(n){var t=n.l;t&&c(t),n.k=n.l=n.m=n.object=n.number=n.string=n.o=null,g.length<_&&g.push(n)}function p(n,t,e){t||(t=0),typeof e=="undefined"&&(e=n?n.length:0);var r=-1;e=e-t||0;for(var u=Array(0>e?0:e);++r<e;)u[r]=n[t+r];return u}function s(e){function h(n,t,e){if(!n||!V[typeof n])return n;
+t=t&&typeof e=="undefined"?t:tt(t,e,3);for(var r=-1,u=V[typeof n]&&Fe(n),o=u?u.length:0;++r<o&&(e=u[r],false!==t(n[e],e,n)););return n}function g(n,t,e){var r;if(!n||!V[typeof n])return n;t=t&&typeof e=="undefined"?t:tt(t,e,3);for(r in n)if(false===t(n[r],r,n))break;return n}function _(n,t,e){var r,u=n,o=u;if(!u)return o;for(var i=arguments,a=0,f=typeof e=="number"?2:i.length;++a<f;)if((u=i[a])&&V[typeof u])for(var l=-1,c=V[typeof u]&&Fe(u),p=c?c.length:0;++l<p;)r=c[l],"undefined"==typeof o[r]&&(o[r]=u[r]);
+return o}function U(n,t,e){var r,u=n,o=u;if(!u)return o;var i=arguments,a=0,f=typeof e=="number"?2:i.length;if(3<f&&"function"==typeof i[f-2])var l=tt(i[--f-1],i[f--],2);else 2<f&&"function"==typeof i[f-1]&&(l=i[--f]);for(;++a<f;)if((u=i[a])&&V[typeof u])for(var c=-1,p=V[typeof u]&&Fe(u),s=p?p.length:0;++c<s;)r=p[c],o[r]=l?l(o[r],u[r]):u[r];return o}function H(n){var t,e=[];if(!n||!V[typeof n])return e;for(t in n)me.call(n,t)&&e.push(t);return e}function J(n){return n&&typeof n=="object"&&!Te(n)&&me.call(n,"__wrapped__")?n:new Q(n)
+}function Q(n,t){this.__chain__=!!t,this.__wrapped__=n}function X(n){function t(){if(r){var n=p(r);be.apply(n,arguments)}if(this instanceof t){var o=nt(e.prototype),n=e.apply(o,n||arguments);return wt(n)?n:o}return e.apply(u,n||arguments)}var e=n[0],r=n[2],u=n[4];return $e(t,n),t}function Z(n,t,e,r,u){if(e){var o=e(n);if(typeof o!="undefined")return o}if(!wt(n))return n;var i=ce.call(n);if(!K[i])return n;var f=Ae[i];switch(i){case T:case F:return new f(+n);case W:case P:return new f(n);case z:return o=f(n.source,C.exec(n)),o.lastIndex=n.lastIndex,o
+}if(i=Te(n),t){var c=!r;r||(r=a()),u||(u=a());for(var s=r.length;s--;)if(r[s]==n)return u[s];o=i?f(n.length):{}}else o=i?p(n):U({},n);return i&&(me.call(n,"index")&&(o.index=n.index),me.call(n,"input")&&(o.input=n.input)),t?(r.push(n),u.push(o),(i?St:h)(n,function(n,i){o[i]=Z(n,t,e,r,u)}),c&&(l(r),l(u)),o):o}function nt(n){return wt(n)?ke(n):{}}function tt(n,t,e){if(typeof n!="function")return Ut;if(typeof t=="undefined"||!("prototype"in n))return n;var r=n.__bindData__;if(typeof r=="undefined"&&(De.funcNames&&(r=!n.name),r=r||!De.funcDecomp,!r)){var u=ge.call(n);
+De.funcNames||(r=!O.test(u)),r||(r=E.test(u),$e(n,r))}if(false===r||true!==r&&1&r[1])return n;switch(e){case 1:return function(e){return n.call(t,e)};case 2:return function(e,r){return n.call(t,e,r)};case 3:return function(e,r,u){return n.call(t,e,r,u)};case 4:return function(e,r,u,o){return n.call(t,e,r,u,o)}}return Mt(n,t)}function et(n){function t(){var n=f?i:this;if(u){var h=p(u);be.apply(h,arguments)}return(o||c)&&(h||(h=p(arguments)),o&&be.apply(h,o),c&&h.length<a)?(r|=16,et([e,s?r:-4&r,h,null,i,a])):(h||(h=arguments),l&&(e=n[v]),this instanceof t?(n=nt(e.prototype),h=e.apply(n,h),wt(h)?h:n):e.apply(n,h))
+}var e=n[0],r=n[1],u=n[2],o=n[3],i=n[4],a=n[5],f=1&r,l=2&r,c=4&r,s=8&r,v=e;return $e(t,n),t}function rt(e,r){var u=-1,i=st(),a=e?e.length:0,f=a>=b&&i===n,l=[];if(f){var p=o(r);p?(i=t,r=p):f=false}for(;++u<a;)p=e[u],0>i(r,p)&&l.push(p);return f&&c(r),l}function ut(n,t,e,r){r=(r||0)-1;for(var u=n?n.length:0,o=[];++r<u;){var i=n[r];if(i&&typeof i=="object"&&typeof i.length=="number"&&(Te(i)||yt(i))){t||(i=ut(i,t,e));var a=-1,f=i.length,l=o.length;for(o.length+=f;++a<f;)o[l++]=i[a]}else e||o.push(i)}return o
+}function ot(n,t,e,r,u,o){if(e){var i=e(n,t);if(typeof i!="undefined")return!!i}if(n===t)return 0!==n||1/n==1/t;if(n===n&&!(n&&V[typeof n]||t&&V[typeof t]))return false;if(null==n||null==t)return n===t;var f=ce.call(n),c=ce.call(t);if(f==D&&(f=q),c==D&&(c=q),f!=c)return false;switch(f){case T:case F:return+n==+t;case W:return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case z:case P:return n==oe(t)}if(c=f==$,!c){var p=me.call(n,"__wrapped__"),s=me.call(t,"__wrapped__");if(p||s)return ot(p?n.__wrapped__:n,s?t.__wrapped__:t,e,r,u,o);
+if(f!=q)return false;if(f=n.constructor,p=t.constructor,f!=p&&!(dt(f)&&f instanceof f&&dt(p)&&p instanceof p)&&"constructor"in n&&"constructor"in t)return false}for(f=!u,u||(u=a()),o||(o=a()),p=u.length;p--;)if(u[p]==n)return o[p]==t;var v=0,i=true;if(u.push(n),o.push(t),c){if(p=n.length,v=t.length,(i=v==p)||r)for(;v--;)if(c=p,s=t[v],r)for(;c--&&!(i=ot(n[c],s,e,r,u,o)););else if(!(i=ot(n[v],s,e,r,u,o)))break}else g(t,function(t,a,f){return me.call(f,a)?(v++,i=me.call(n,a)&&ot(n[a],t,e,r,u,o)):void 0}),i&&!r&&g(n,function(n,t,e){return me.call(e,t)?i=-1<--v:void 0
+});return u.pop(),o.pop(),f&&(l(u),l(o)),i}function it(n,t,e,r,u){(Te(t)?St:h)(t,function(t,o){var i,a,f=t,l=n[o];if(t&&((a=Te(t))||Pe(t))){for(f=r.length;f--;)if(i=r[f]==t){l=u[f];break}if(!i){var c;e&&(f=e(l,t),c=typeof f!="undefined")&&(l=f),c||(l=a?Te(l)?l:[]:Pe(l)?l:{}),r.push(t),u.push(l),c||it(l,t,e,r,u)}}else e&&(f=e(l,t),typeof f=="undefined"&&(f=t)),typeof f!="undefined"&&(l=f);n[o]=l})}function at(n,t){return n+he(Re()*(t-n+1))}function ft(e,r,u){var i=-1,f=st(),p=e?e.length:0,s=[],v=!r&&p>=b&&f===n,h=u||v?a():s;
+for(v&&(h=o(h),f=t);++i<p;){var g=e[i],y=u?u(g,i,e):g;(r?!i||h[h.length-1]!==y:0>f(h,y))&&((u||v)&&h.push(y),s.push(g))}return v?(l(h.k),c(h)):u&&l(h),s}function lt(n){return function(t,e,r){var u={};e=J.createCallback(e,r,3),r=-1;var o=t?t.length:0;if(typeof o=="number")for(;++r<o;){var i=t[r];n(u,i,e(i,r,t),t)}else h(t,function(t,r,o){n(u,t,e(t,r,o),o)});return u}}function ct(n,t,e,r,u,o){var i=1&t,a=4&t,f=16&t,l=32&t;if(!(2&t||dt(n)))throw new ie;f&&!e.length&&(t&=-17,f=e=false),l&&!r.length&&(t&=-33,l=r=false);
+var c=n&&n.__bindData__;return c&&true!==c?(c=p(c),c[2]&&(c[2]=p(c[2])),c[3]&&(c[3]=p(c[3])),!i||1&c[1]||(c[4]=u),!i&&1&c[1]&&(t|=8),!a||4&c[1]||(c[5]=o),f&&be.apply(c[2]||(c[2]=[]),e),l&&we.apply(c[3]||(c[3]=[]),r),c[1]|=t,ct.apply(null,c)):(1==t||17===t?X:et)([n,t,e,r,u,o])}function pt(n){return Be[n]}function st(){var t=(t=J.indexOf)===Wt?n:t;return t}function vt(n){return typeof n=="function"&&pe.test(n)}function ht(n){var t,e;return n&&ce.call(n)==q&&(t=n.constructor,!dt(t)||t instanceof t)?(g(n,function(n,t){e=t
+}),typeof e=="undefined"||me.call(n,e)):false}function gt(n){return We[n]}function yt(n){return n&&typeof n=="object"&&typeof n.length=="number"&&ce.call(n)==D||false}function mt(n,t,e){var r=Fe(n),u=r.length;for(t=tt(t,e,3);u--&&(e=r[u],false!==t(n[e],e,n)););return n}function bt(n){var t=[];return g(n,function(n,e){dt(n)&&t.push(e)}),t.sort()}function _t(n){for(var t=-1,e=Fe(n),r=e.length,u={};++t<r;){var o=e[t];u[n[o]]=o}return u}function dt(n){return typeof n=="function"}function wt(n){return!(!n||!V[typeof n])
+}function jt(n){return typeof n=="number"||n&&typeof n=="object"&&ce.call(n)==W||false}function kt(n){return typeof n=="string"||n&&typeof n=="object"&&ce.call(n)==P||false}function xt(n){for(var t=-1,e=Fe(n),r=e.length,u=Xt(r);++t<r;)u[t]=n[e[t]];return u}function Ct(n,t,e){var r=-1,u=st(),o=n?n.length:0,i=false;return e=(0>e?Ie(0,o+e):e)||0,Te(n)?i=-1<u(n,t,e):typeof o=="number"?i=-1<(kt(n)?n.indexOf(t,e):u(n,t,e)):h(n,function(n){return++r<e?void 0:!(i=n===t)}),i}function Ot(n,t,e){var r=true;t=J.createCallback(t,e,3),e=-1;
+var u=n?n.length:0;if(typeof u=="number")for(;++e<u&&(r=!!t(n[e],e,n)););else h(n,function(n,e,u){return r=!!t(n,e,u)});return r}function Nt(n,t,e){var r=[];t=J.createCallback(t,e,3),e=-1;var u=n?n.length:0;if(typeof u=="number")for(;++e<u;){var o=n[e];t(o,e,n)&&r.push(o)}else h(n,function(n,e,u){t(n,e,u)&&r.push(n)});return r}function It(n,t,e){t=J.createCallback(t,e,3),e=-1;var r=n?n.length:0;if(typeof r!="number"){var u;return h(n,function(n,e,r){return t(n,e,r)?(u=n,false):void 0}),u}for(;++e<r;){var o=n[e];
+if(t(o,e,n))return o}}function St(n,t,e){var r=-1,u=n?n.length:0;if(t=t&&typeof e=="undefined"?t:tt(t,e,3),typeof u=="number")for(;++r<u&&false!==t(n[r],r,n););else h(n,t);return n}function Et(n,t,e){var r=n?n.length:0;if(t=t&&typeof e=="undefined"?t:tt(t,e,3),typeof r=="number")for(;r--&&false!==t(n[r],r,n););else{var u=Fe(n),r=u.length;h(n,function(n,e,o){return e=u?u[--r]:--r,t(o[e],e,o)})}return n}function Rt(n,t,e){var r=-1,u=n?n.length:0;if(t=J.createCallback(t,e,3),typeof u=="number")for(var o=Xt(u);++r<u;)o[r]=t(n[r],r,n);
+else o=[],h(n,function(n,e,u){o[++r]=t(n,e,u)});return o}function At(n,t,e){var u=-1/0,o=u;if(typeof t!="function"&&e&&e[t]===n&&(t=null),null==t&&Te(n)){e=-1;for(var i=n.length;++e<i;){var a=n[e];a>o&&(o=a)}}else t=null==t&&kt(n)?r:J.createCallback(t,e,3),St(n,function(n,e,r){e=t(n,e,r),e>u&&(u=e,o=n)});return o}function Dt(n,t,e,r){if(!n)return e;var u=3>arguments.length;t=J.createCallback(t,r,4);var o=-1,i=n.length;if(typeof i=="number")for(u&&(e=n[++o]);++o<i;)e=t(e,n[o],o,n);else h(n,function(n,r,o){e=u?(u=false,n):t(e,n,r,o)
+});return e}function $t(n,t,e,r){var u=3>arguments.length;return t=J.createCallback(t,r,4),Et(n,function(n,r,o){e=u?(u=false,n):t(e,n,r,o)}),e}function Tt(n){var t=-1,e=n?n.length:0,r=Xt(typeof e=="number"?e:0);return St(n,function(n){var e=at(0,++t);r[t]=r[e],r[e]=n}),r}function Ft(n,t,e){var r;t=J.createCallback(t,e,3),e=-1;var u=n?n.length:0;if(typeof u=="number")for(;++e<u&&!(r=t(n[e],e,n)););else h(n,function(n,e,u){return!(r=t(n,e,u))});return!!r}function Bt(n,t,e){var r=0,u=n?n.length:0;if(typeof t!="number"&&null!=t){var o=-1;
+for(t=J.createCallback(t,e,3);++o<u&&t(n[o],o,n);)r++}else if(r=t,null==r||e)return n?n[0]:v;return p(n,0,Se(Ie(0,r),u))}function Wt(t,e,r){if(typeof r=="number"){var u=t?t.length:0;r=0>r?Ie(0,u+r):r||0}else if(r)return r=zt(t,e),t[r]===e?r:-1;return n(t,e,r)}function qt(n,t,e){if(typeof t!="number"&&null!=t){var r=0,u=-1,o=n?n.length:0;for(t=J.createCallback(t,e,3);++u<o&&t(n[u],u,n);)r++}else r=null==t||e?1:Ie(0,t);return p(n,r)}function zt(n,t,e,r){var u=0,o=n?n.length:u;for(e=e?J.createCallback(e,r,1):Ut,t=e(t);u<o;)r=u+o>>>1,e(n[r])<t?u=r+1:o=r;
+return u}function Pt(n,t,e,r){return typeof t!="boolean"&&null!=t&&(r=e,e=typeof t!="function"&&r&&r[t]===n?null:t,t=false),null!=e&&(e=J.createCallback(e,r,3)),ft(n,t,e)}function Kt(){for(var n=1<arguments.length?arguments:arguments[0],t=-1,e=n?At(Ve(n,"length")):0,r=Xt(0>e?0:e);++t<e;)r[t]=Ve(n,t);return r}function Lt(n,t){var e=-1,r=n?n.length:0,u={};for(t||!r||Te(n[0])||(t=[]);++e<r;){var o=n[e];t?u[o]=t[e]:o&&(u[o[0]]=o[1])}return u}function Mt(n,t){return 2<arguments.length?ct(n,17,p(arguments,2),null,t):ct(n,1,null,null,t)
+}function Vt(n,t,e){function r(){c&&ve(c),i=c=p=v,(g||h!==t)&&(s=Ue(),a=n.apply(l,o),c||i||(o=l=null))}function u(){var e=t-(Ue()-f);0<e?c=_e(u,e):(i&&ve(i),e=p,i=c=p=v,e&&(s=Ue(),a=n.apply(l,o),c||i||(o=l=null)))}var o,i,a,f,l,c,p,s=0,h=false,g=true;if(!dt(n))throw new ie;if(t=Ie(0,t)||0,true===e)var y=true,g=false;else wt(e)&&(y=e.leading,h="maxWait"in e&&(Ie(t,e.maxWait)||0),g="trailing"in e?e.trailing:g);return function(){if(o=arguments,f=Ue(),l=this,p=g&&(c||!y),false===h)var e=y&&!c;else{i||y||(s=f);var v=h-(f-s),m=0>=v;
+m?(i&&(i=ve(i)),s=f,a=n.apply(l,o)):i||(i=_e(r,v))}return m&&c?c=ve(c):c||t===h||(c=_e(u,t)),e&&(m=true,a=n.apply(l,o)),!m||c||i||(o=l=null),a}}function Ut(n){return n}function Gt(n,t,e){var r=true,u=t&&bt(t);t&&(e||u.length)||(null==e&&(e=t),o=Q,t=n,n=J,u=bt(t)),false===e?r=false:wt(e)&&"chain"in e&&(r=e.chain);var o=n,i=dt(o);St(u,function(e){var u=n[e]=t[e];i&&(o.prototype[e]=function(){var t=this.__chain__,e=this.__wrapped__,i=[e];if(be.apply(i,arguments),i=u.apply(n,i),r||t){if(e===i&&wt(i))return this;
+i=new o(i),i.__chain__=t}return i})})}function Ht(){}function Jt(n){return function(t){return t[n]}}function Qt(){return this.__wrapped__}e=e?Y.defaults(G.Object(),e,Y.pick(G,A)):G;var Xt=e.Array,Yt=e.Boolean,Zt=e.Date,ne=e.Function,te=e.Math,ee=e.Number,re=e.Object,ue=e.RegExp,oe=e.String,ie=e.TypeError,ae=[],fe=re.prototype,le=e._,ce=fe.toString,pe=ue("^"+oe(ce).replace(/[.*+?^${}()|[\]\\]/g,"\\$&").replace(/toString| for [^\]]+/g,".*?")+"$"),se=te.ceil,ve=e.clearTimeout,he=te.floor,ge=ne.prototype.toString,ye=vt(ye=re.getPrototypeOf)&&ye,me=fe.hasOwnProperty,be=ae.push,_e=e.setTimeout,de=ae.splice,we=ae.unshift,je=function(){try{var n={},t=vt(t=re.defineProperty)&&t,e=t(n,n,n)&&t
+}catch(r){}return e}(),ke=vt(ke=re.create)&&ke,xe=vt(xe=Xt.isArray)&&xe,Ce=e.isFinite,Oe=e.isNaN,Ne=vt(Ne=re.keys)&&Ne,Ie=te.max,Se=te.min,Ee=e.parseInt,Re=te.random,Ae={};Ae[$]=Xt,Ae[T]=Yt,Ae[F]=Zt,Ae[B]=ne,Ae[q]=re,Ae[W]=ee,Ae[z]=ue,Ae[P]=oe,Q.prototype=J.prototype;var De=J.support={};De.funcDecomp=!vt(e.a)&&E.test(s),De.funcNames=typeof ne.name=="string",J.templateSettings={escape:/<%-([\s\S]+?)%>/g,evaluate:/<%([\s\S]+?)%>/g,interpolate:N,variable:"",imports:{_:J}},ke||(nt=function(){function n(){}return function(t){if(wt(t)){n.prototype=t;
+var r=new n;n.prototype=null}return r||e.Object()}}());var $e=je?function(n,t){M.value=t,je(n,"__bindData__",M)}:Ht,Te=xe||function(n){return n&&typeof n=="object"&&typeof n.length=="number"&&ce.call(n)==$||false},Fe=Ne?function(n){return wt(n)?Ne(n):[]}:H,Be={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"},We=_t(Be),qe=ue("("+Fe(We).join("|")+")","g"),ze=ue("["+Fe(Be).join("")+"]","g"),Pe=ye?function(n){if(!n||ce.call(n)!=q)return false;var t=n.valueOf,e=vt(t)&&(e=ye(t))&&ye(e);return e?n==e||ye(n)==e:ht(n)
+}:ht,Ke=lt(function(n,t,e){me.call(n,e)?n[e]++:n[e]=1}),Le=lt(function(n,t,e){(me.call(n,e)?n[e]:n[e]=[]).push(t)}),Me=lt(function(n,t,e){n[e]=t}),Ve=Rt,Ue=vt(Ue=Zt.now)&&Ue||function(){return(new Zt).getTime()},Ge=8==Ee(d+"08")?Ee:function(n,t){return Ee(kt(n)?n.replace(I,""):n,t||0)};return J.after=function(n,t){if(!dt(t))throw new ie;return function(){return 1>--n?t.apply(this,arguments):void 0}},J.assign=U,J.at=function(n){for(var t=arguments,e=-1,r=ut(t,true,false,1),t=t[2]&&t[2][t[1]]===n?1:r.length,u=Xt(t);++e<t;)u[e]=n[r[e]];
+return u},J.bind=Mt,J.bindAll=function(n){for(var t=1<arguments.length?ut(arguments,true,false,1):bt(n),e=-1,r=t.length;++e<r;){var u=t[e];n[u]=ct(n[u],1,null,null,n)}return n},J.bindKey=function(n,t){return 2<arguments.length?ct(t,19,p(arguments,2),null,n):ct(t,3,null,null,n)},J.chain=function(n){return n=new Q(n),n.__chain__=true,n},J.compact=function(n){for(var t=-1,e=n?n.length:0,r=[];++t<e;){var u=n[t];u&&r.push(u)}return r},J.compose=function(){for(var n=arguments,t=n.length;t--;)if(!dt(n[t]))throw new ie;
+return function(){for(var t=arguments,e=n.length;e--;)t=[n[e].apply(this,t)];return t[0]}},J.constant=function(n){return function(){return n}},J.countBy=Ke,J.create=function(n,t){var e=nt(n);return t?U(e,t):e},J.createCallback=function(n,t,e){var r=typeof n;if(null==n||"function"==r)return tt(n,t,e);if("object"!=r)return Jt(n);var u=Fe(n),o=u[0],i=n[o];return 1!=u.length||i!==i||wt(i)?function(t){for(var e=u.length,r=false;e--&&(r=ot(t[u[e]],n[u[e]],null,true)););return r}:function(n){return n=n[o],i===n&&(0!==i||1/i==1/n)
+}},J.curry=function(n,t){return t=typeof t=="number"?t:+t||n.length,ct(n,4,null,null,null,t)},J.debounce=Vt,J.defaults=_,J.defer=function(n){if(!dt(n))throw new ie;var t=p(arguments,1);return _e(function(){n.apply(v,t)},1)},J.delay=function(n,t){if(!dt(n))throw new ie;var e=p(arguments,2);return _e(function(){n.apply(v,e)},t)},J.difference=function(n){return rt(n,ut(arguments,true,true,1))},J.filter=Nt,J.flatten=function(n,t,e,r){return typeof t!="boolean"&&null!=t&&(r=e,e=typeof t!="function"&&r&&r[t]===n?null:t,t=false),null!=e&&(n=Rt(n,e,r)),ut(n,t)
+},J.forEach=St,J.forEachRight=Et,J.forIn=g,J.forInRight=function(n,t,e){var r=[];g(n,function(n,t){r.push(t,n)});var u=r.length;for(t=tt(t,e,3);u--&&false!==t(r[u--],r[u],n););return n},J.forOwn=h,J.forOwnRight=mt,J.functions=bt,J.groupBy=Le,J.indexBy=Me,J.initial=function(n,t,e){var r=0,u=n?n.length:0;if(typeof t!="number"&&null!=t){var o=u;for(t=J.createCallback(t,e,3);o--&&t(n[o],o,n);)r++}else r=null==t||e?1:t||r;return p(n,0,Se(Ie(0,u-r),u))},J.intersection=function(){for(var e=[],r=-1,u=arguments.length,i=a(),f=st(),p=f===n,s=a();++r<u;){var v=arguments[r];
+(Te(v)||yt(v))&&(e.push(v),i.push(p&&v.length>=b&&o(r?e[r]:s)))}var p=e[0],h=-1,g=p?p.length:0,y=[];n:for(;++h<g;){var m=i[0],v=p[h];if(0>(m?t(m,v):f(s,v))){for(r=u,(m||s).push(v);--r;)if(m=i[r],0>(m?t(m,v):f(e[r],v)))continue n;y.push(v)}}for(;u--;)(m=i[u])&&c(m);return l(i),l(s),y},J.invert=_t,J.invoke=function(n,t){var e=p(arguments,2),r=-1,u=typeof t=="function",o=n?n.length:0,i=Xt(typeof o=="number"?o:0);return St(n,function(n){i[++r]=(u?t:n[t]).apply(n,e)}),i},J.keys=Fe,J.map=Rt,J.mapValues=function(n,t,e){var r={};
+return t=J.createCallback(t,e,3),h(n,function(n,e,u){r[e]=t(n,e,u)}),r},J.max=At,J.memoize=function(n,t){function e(){var r=e.cache,u=t?t.apply(this,arguments):m+arguments[0];return me.call(r,u)?r[u]:r[u]=n.apply(this,arguments)}if(!dt(n))throw new ie;return e.cache={},e},J.merge=function(n){var t=arguments,e=2;if(!wt(n))return n;if("number"!=typeof t[2]&&(e=t.length),3<e&&"function"==typeof t[e-2])var r=tt(t[--e-1],t[e--],2);else 2<e&&"function"==typeof t[e-1]&&(r=t[--e]);for(var t=p(arguments,1,e),u=-1,o=a(),i=a();++u<e;)it(n,t[u],r,o,i);
+return l(o),l(i),n},J.min=function(n,t,e){var u=1/0,o=u;if(typeof t!="function"&&e&&e[t]===n&&(t=null),null==t&&Te(n)){e=-1;for(var i=n.length;++e<i;){var a=n[e];a<o&&(o=a)}}else t=null==t&&kt(n)?r:J.createCallback(t,e,3),St(n,function(n,e,r){e=t(n,e,r),e<u&&(u=e,o=n)});return o},J.omit=function(n,t,e){var r={};if(typeof t!="function"){var u=[];g(n,function(n,t){u.push(t)});for(var u=rt(u,ut(arguments,true,false,1)),o=-1,i=u.length;++o<i;){var a=u[o];r[a]=n[a]}}else t=J.createCallback(t,e,3),g(n,function(n,e,u){t(n,e,u)||(r[e]=n)
+});return r},J.once=function(n){var t,e;if(!dt(n))throw new ie;return function(){return t?e:(t=true,e=n.apply(this,arguments),n=null,e)}},J.pairs=function(n){for(var t=-1,e=Fe(n),r=e.length,u=Xt(r);++t<r;){var o=e[t];u[t]=[o,n[o]]}return u},J.partial=function(n){return ct(n,16,p(arguments,1))},J.partialRight=function(n){return ct(n,32,null,p(arguments,1))},J.pick=function(n,t,e){var r={};if(typeof t!="function")for(var u=-1,o=ut(arguments,true,false,1),i=wt(n)?o.length:0;++u<i;){var a=o[u];a in n&&(r[a]=n[a])
+}else t=J.createCallback(t,e,3),g(n,function(n,e,u){t(n,e,u)&&(r[e]=n)});return r},J.pluck=Ve,J.property=Jt,J.pull=function(n){for(var t=arguments,e=0,r=t.length,u=n?n.length:0;++e<r;)for(var o=-1,i=t[e];++o<u;)n[o]===i&&(de.call(n,o--,1),u--);return n},J.range=function(n,t,e){n=+n||0,e=typeof e=="number"?e:+e||1,null==t&&(t=n,n=0);var r=-1;t=Ie(0,se((t-n)/(e||1)));for(var u=Xt(t);++r<t;)u[r]=n,n+=e;return u},J.reject=function(n,t,e){return t=J.createCallback(t,e,3),Nt(n,function(n,e,r){return!t(n,e,r)
+})},J.remove=function(n,t,e){var r=-1,u=n?n.length:0,o=[];for(t=J.createCallback(t,e,3);++r<u;)e=n[r],t(e,r,n)&&(o.push(e),de.call(n,r--,1),u--);return o},J.rest=qt,J.shuffle=Tt,J.sortBy=function(n,t,e){var r=-1,o=Te(t),i=n?n.length:0,p=Xt(typeof i=="number"?i:0);for(o||(t=J.createCallback(t,e,3)),St(n,function(n,e,u){var i=p[++r]=f();o?i.m=Rt(t,function(t){return n[t]}):(i.m=a())[0]=t(n,e,u),i.n=r,i.o=n}),i=p.length,p.sort(u);i--;)n=p[i],p[i]=n.o,o||l(n.m),c(n);return p},J.tap=function(n,t){return t(n),n
+},J.throttle=function(n,t,e){var r=true,u=true;if(!dt(n))throw new ie;return false===e?r=false:wt(e)&&(r="leading"in e?e.leading:r,u="trailing"in e?e.trailing:u),L.leading=r,L.maxWait=t,L.trailing=u,Vt(n,t,L)},J.times=function(n,t,e){n=-1<(n=+n)?n:0;var r=-1,u=Xt(n);for(t=tt(t,e,1);++r<n;)u[r]=t(r);return u},J.toArray=function(n){return n&&typeof n.length=="number"?p(n):xt(n)},J.transform=function(n,t,e,r){var u=Te(n);if(null==e)if(u)e=[];else{var o=n&&n.constructor;e=nt(o&&o.prototype)}return t&&(t=J.createCallback(t,r,4),(u?St:h)(n,function(n,r,u){return t(e,n,r,u)
+})),e},J.union=function(){return ft(ut(arguments,true,true))},J.uniq=Pt,J.values=xt,J.where=Nt,J.without=function(n){return rt(n,p(arguments,1))},J.wrap=function(n,t){return ct(t,16,[n])},J.xor=function(){for(var n=-1,t=arguments.length;++n<t;){var e=arguments[n];if(Te(e)||yt(e))var r=r?ft(rt(r,e).concat(rt(e,r))):e}return r||[]},J.zip=Kt,J.zipObject=Lt,J.collect=Rt,J.drop=qt,J.each=St,J.eachRight=Et,J.extend=U,J.methods=bt,J.object=Lt,J.select=Nt,J.tail=qt,J.unique=Pt,J.unzip=Kt,Gt(J),J.clone=function(n,t,e,r){return typeof t!="boolean"&&null!=t&&(r=e,e=t,t=false),Z(n,t,typeof e=="function"&&tt(e,r,1))
+},J.cloneDeep=function(n,t,e){return Z(n,true,typeof t=="function"&&tt(t,e,1))},J.contains=Ct,J.escape=function(n){return null==n?"":oe(n).replace(ze,pt)},J.every=Ot,J.find=It,J.findIndex=function(n,t,e){var r=-1,u=n?n.length:0;for(t=J.createCallback(t,e,3);++r<u;)if(t(n[r],r,n))return r;return-1},J.findKey=function(n,t,e){var r;return t=J.createCallback(t,e,3),h(n,function(n,e,u){return t(n,e,u)?(r=e,false):void 0}),r},J.findLast=function(n,t,e){var r;return t=J.createCallback(t,e,3),Et(n,function(n,e,u){return t(n,e,u)?(r=n,false):void 0
+}),r},J.findLastIndex=function(n,t,e){var r=n?n.length:0;for(t=J.createCallback(t,e,3);r--;)if(t(n[r],r,n))return r;return-1},J.findLastKey=function(n,t,e){var r;return t=J.createCallback(t,e,3),mt(n,function(n,e,u){return t(n,e,u)?(r=e,false):void 0}),r},J.has=function(n,t){return n?me.call(n,t):false},J.identity=Ut,J.indexOf=Wt,J.isArguments=yt,J.isArray=Te,J.isBoolean=function(n){return true===n||false===n||n&&typeof n=="object"&&ce.call(n)==T||false},J.isDate=function(n){return n&&typeof n=="object"&&ce.call(n)==F||false
+},J.isElement=function(n){return n&&1===n.nodeType||false},J.isEmpty=function(n){var t=true;if(!n)return t;var e=ce.call(n),r=n.length;return e==$||e==P||e==D||e==q&&typeof r=="number"&&dt(n.splice)?!r:(h(n,function(){return t=false}),t)},J.isEqual=function(n,t,e,r){return ot(n,t,typeof e=="function"&&tt(e,r,2))},J.isFinite=function(n){return Ce(n)&&!Oe(parseFloat(n))},J.isFunction=dt,J.isNaN=function(n){return jt(n)&&n!=+n},J.isNull=function(n){return null===n},J.isNumber=jt,J.isObject=wt,J.isPlainObject=Pe,J.isRegExp=function(n){return n&&typeof n=="object"&&ce.call(n)==z||false
+},J.isString=kt,J.isUndefined=function(n){return typeof n=="undefined"},J.lastIndexOf=function(n,t,e){var r=n?n.length:0;for(typeof e=="number"&&(r=(0>e?Ie(0,r+e):Se(e,r-1))+1);r--;)if(n[r]===t)return r;return-1},J.mixin=Gt,J.noConflict=function(){return e._=le,this},J.noop=Ht,J.now=Ue,J.parseInt=Ge,J.random=function(n,t,e){var r=null==n,u=null==t;return null==e&&(typeof n=="boolean"&&u?(e=n,n=1):u||typeof t!="boolean"||(e=t,u=true)),r&&u&&(t=1),n=+n||0,u?(t=n,n=0):t=+t||0,e||n%1||t%1?(e=Re(),Se(n+e*(t-n+parseFloat("1e-"+((e+"").length-1))),t)):at(n,t)
+},J.reduce=Dt,J.reduceRight=$t,J.result=function(n,t){if(n){var e=n[t];return dt(e)?n[t]():e}},J.runInContext=s,J.size=function(n){var t=n?n.length:0;return typeof t=="number"?t:Fe(n).length},J.some=Ft,J.sortedIndex=zt,J.template=function(n,t,e){var r=J.templateSettings;n=oe(n||""),e=_({},e,r);var u,o=_({},e.imports,r.imports),r=Fe(o),o=xt(o),a=0,f=e.interpolate||S,l="__p+='",f=ue((e.escape||S).source+"|"+f.source+"|"+(f===N?x:S).source+"|"+(e.evaluate||S).source+"|$","g");n.replace(f,function(t,e,r,o,f,c){return r||(r=o),l+=n.slice(a,c).replace(R,i),e&&(l+="'+__e("+e+")+'"),f&&(u=true,l+="';"+f+";\n__p+='"),r&&(l+="'+((__t=("+r+"))==null?'':__t)+'"),a=c+t.length,t
+}),l+="';",f=e=e.variable,f||(e="obj",l="with("+e+"){"+l+"}"),l=(u?l.replace(w,""):l).replace(j,"$1").replace(k,"$1;"),l="function("+e+"){"+(f?"":e+"||("+e+"={});")+"var __t,__p='',__e=_.escape"+(u?",__j=Array.prototype.join;function print(){__p+=__j.call(arguments,'')}":";")+l+"return __p}";try{var c=ne(r,"return "+l).apply(v,o)}catch(p){throw p.source=l,p}return t?c(t):(c.source=l,c)},J.unescape=function(n){return null==n?"":oe(n).replace(qe,gt)},J.uniqueId=function(n){var t=++y;return oe(null==n?"":n)+t
+},J.all=Ot,J.any=Ft,J.detect=It,J.findWhere=It,J.foldl=Dt,J.foldr=$t,J.include=Ct,J.inject=Dt,Gt(function(){var n={};return h(J,function(t,e){J.prototype[e]||(n[e]=t)}),n}(),false),J.first=Bt,J.last=function(n,t,e){var r=0,u=n?n.length:0;if(typeof t!="number"&&null!=t){var o=u;for(t=J.createCallback(t,e,3);o--&&t(n[o],o,n);)r++}else if(r=t,null==r||e)return n?n[u-1]:v;return p(n,Ie(0,u-r))},J.sample=function(n,t,e){return n&&typeof n.length!="number"&&(n=xt(n)),null==t||e?n?n[at(0,n.length-1)]:v:(n=Tt(n),n.length=Se(Ie(0,t),n.length),n)
+},J.take=Bt,J.head=Bt,h(J,function(n,t){var e="sample"!==t;J.prototype[t]||(J.prototype[t]=function(t,r){var u=this.__chain__,o=n(this.__wrapped__,t,r);return u||null!=t&&(!r||e&&typeof t=="function")?new Q(o,u):o})}),J.VERSION="2.4.1",J.prototype.chain=function(){return this.__chain__=true,this},J.prototype.toString=function(){return oe(this.__wrapped__)},J.prototype.value=Qt,J.prototype.valueOf=Qt,St(["join","pop","shift"],function(n){var t=ae[n];J.prototype[n]=function(){var n=this.__chain__,e=t.apply(this.__wrapped__,arguments);
+return n?new Q(e,n):e}}),St(["push","reverse","sort","unshift"],function(n){var t=ae[n];J.prototype[n]=function(){return t.apply(this.__wrapped__,arguments),this}}),St(["concat","slice","splice"],function(n){var t=ae[n];J.prototype[n]=function(){return new Q(t.apply(this.__wrapped__,arguments),this.__chain__)}}),J}var v,h=[],g=[],y=0,m=+new Date+"",b=75,_=40,d=" \t\x0B\f\xa0\ufeff\n\r\u2028\u2029\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000",w=/\b__p\+='';/g,j=/\b(__p\+=)''\+/g,k=/(__e\(.*?\)|\b__t\))\+'';/g,x=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,C=/\w*$/,O=/^\s*function[ \n\r\t]+\w/,N=/<%=([\s\S]+?)%>/g,I=RegExp("^["+d+"]*0+(?=.$)"),S=/($^)/,E=/\bthis\b/,R=/['\n\r\t\u2028\u2029\\]/g,A="Array Boolean Date Function Math Number Object RegExp String _ attachEvent clearTimeout isFinite isNaN parseInt setTimeout".split(" "),D="[object Arguments]",$="[object Array]",T="[object Boolean]",F="[object Date]",B="[object Function]",W="[object Number]",q="[object Object]",z="[object RegExp]",P="[object String]",K={};
+K[B]=false,K[D]=K[$]=K[T]=K[F]=K[W]=K[q]=K[z]=K[P]=true;var L={leading:false,maxWait:0,trailing:false},M={configurable:false,enumerable:false,value:null,writable:false},V={"boolean":false,"function":true,object:true,number:false,string:false,undefined:false},U={"\\":"\\","'":"'","\n":"n","\r":"r","\t":"t","\u2028":"u2028","\u2029":"u2029"},G=V[typeof window]&&window||this,H=V[typeof exports]&&exports&&!exports.nodeType&&exports,J=V[typeof module]&&module&&!module.nodeType&&module,Q=J&&J.exports===H&&H,X=V[typeof global]&&global;!X||X.global!==X&&X.window!==X||(G=X);
+var Y=s();typeof define=="function"&&typeof define.amd=="object"&&define.amd?(G._=Y, define(function(){return Y})):H&&J?Q?(J.exports=Y)._=Y:H._=Y:G._=Y}).call(this);
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lodash/dist/lodash.underscore.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lodash/dist/lodash.underscore.js
new file mode 100644
index 0000000..0c84471
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lodash/dist/lodash.underscore.js
@@ -0,0 +1,4979 @@
+/**
+ * @license
+ * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
+ * Build: `lodash underscore exports="amd,commonjs,global,node" -o ./dist/lodash.underscore.js`
+ * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
+ * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
+ * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Available under MIT license <http://lodash.com/license>
+ */
+;(function() {
+
+  /** Used as a safe reference for `undefined` in pre ES5 environments */
+  var undefined;
+
+  /** Used to generate unique IDs */
+  var idCounter = 0;
+
+  /** Used internally to indicate various things */
+  var indicatorObject = {};
+
+  /** Used to prefix keys to avoid issues with `__proto__` and properties on `Object.prototype` */
+  var keyPrefix = +new Date + '';
+
+  /** Used to match "interpolate" template delimiters */
+  var reInterpolate = /<%=([\s\S]+?)%>/g;
+
+  /** Used to ensure capturing order of template delimiters */
+  var reNoMatch = /($^)/;
+
+  /** Used to match unescaped characters in compiled string literals */
+  var reUnescapedString = /['\n\r\t\u2028\u2029\\]/g;
+
+  /** `Object#toString` result shortcuts */
+  var argsClass = '[object Arguments]',
+      arrayClass = '[object Array]',
+      boolClass = '[object Boolean]',
+      dateClass = '[object Date]',
+      funcClass = '[object Function]',
+      numberClass = '[object Number]',
+      objectClass = '[object Object]',
+      regexpClass = '[object RegExp]',
+      stringClass = '[object String]';
+
+  /** Used to determine if values are of the language type Object */
+  var objectTypes = {
+    'boolean': false,
+    'function': true,
+    'object': true,
+    'number': false,
+    'string': false,
+    'undefined': false
+  };
+
+  /** Used to escape characters for inclusion in compiled string literals */
+  var stringEscapes = {
+    '\\': '\\',
+    "'": "'",
+    '\n': 'n',
+    '\r': 'r',
+    '\t': 't',
+    '\u2028': 'u2028',
+    '\u2029': 'u2029'
+  };
+
+  /** Used as a reference to the global object */
+  var root = (objectTypes[typeof window] && window) || this;
+
+  /** Detect free variable `exports` */
+  var freeExports = objectTypes[typeof exports] && exports && !exports.nodeType && exports;
+
+  /** Detect free variable `module` */
+  var freeModule = objectTypes[typeof module] && module && !module.nodeType && module;
+
+  /** Detect the popular CommonJS extension `module.exports` */
+  var moduleExports = freeModule && freeModule.exports === freeExports && freeExports;
+
+  /** Detect free variable `global` from Node.js or Browserified code and use it as `root` */
+  var freeGlobal = objectTypes[typeof global] && global;
+  if (freeGlobal && (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal)) {
+    root = freeGlobal;
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * The base implementation of `_.indexOf` without support for binary searches
+   * or `fromIndex` constraints.
+   *
+   * @private
+   * @param {Array} array The array to search.
+   * @param {*} value The value to search for.
+   * @param {number} [fromIndex=0] The index to search from.
+   * @returns {number} Returns the index of the matched value or `-1`.
+   */
+  function baseIndexOf(array, value, fromIndex) {
+    var index = (fromIndex || 0) - 1,
+        length = array ? array.length : 0;
+
+    while (++index < length) {
+      if (array[index] === value) {
+        return index;
+      }
+    }
+    return -1;
+  }
+
+  /**
+   * Used by `sortBy` to compare transformed `collection` elements, stable sorting
+   * them in ascending order.
+   *
+   * @private
+   * @param {Object} a The object to compare to `b`.
+   * @param {Object} b The object to compare to `a`.
+   * @returns {number} Returns the sort order indicator of `1` or `-1`.
+   */
+  function compareAscending(a, b) {
+    var ac = a.criteria,
+        bc = b.criteria,
+        index = -1,
+        length = ac.length;
+
+    while (++index < length) {
+      var value = ac[index],
+          other = bc[index];
+
+      if (value !== other) {
+        if (value > other || typeof value == 'undefined') {
+          return 1;
+        }
+        if (value < other || typeof other == 'undefined') {
+          return -1;
+        }
+      }
+    }
+    // Fixes an `Array#sort` bug in the JS engine embedded in Adobe applications
+    // that causes it, under certain circumstances, to return the same value for
+    // `a` and `b`. See https://github.com/jashkenas/underscore/pull/1247
+    //
+    // This also ensures a stable sort in V8 and other engines.
+    // See http://code.google.com/p/v8/issues/detail?id=90
+    return a.index - b.index;
+  }
+
+  /**
+   * Used by `template` to escape characters for inclusion in compiled
+   * string literals.
+   *
+   * @private
+   * @param {string} match The matched character to escape.
+   * @returns {string} Returns the escaped character.
+   */
+  function escapeStringChar(match) {
+    return '\\' + stringEscapes[match];
+  }
+
+  /**
+   * Slices the `collection` from the `start` index up to, but not including,
+   * the `end` index.
+   *
+   * Note: This function is used instead of `Array#slice` to support node lists
+   * in IE < 9 and to ensure dense arrays are returned.
+   *
+   * @private
+   * @param {Array|Object|string} collection The collection to slice.
+   * @param {number} start The start index.
+   * @param {number} end The end index.
+   * @returns {Array} Returns the new array.
+   */
+  function slice(array, start, end) {
+    start || (start = 0);
+    if (typeof end == 'undefined') {
+      end = array ? array.length : 0;
+    }
+    var index = -1,
+        length = end - start || 0,
+        result = Array(length < 0 ? 0 : length);
+
+    while (++index < length) {
+      result[index] = array[start + index];
+    }
+    return result;
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Used for `Array` method references.
+   *
+   * Normally `Array.prototype` would suffice, however, using an array literal
+   * avoids issues in Narwhal.
+   */
+  var arrayRef = [];
+
+  /** Used for native method references */
+  var objectProto = Object.prototype;
+
+  /** Used to restore the original `_` reference in `noConflict` */
+  var oldDash = root._;
+
+  /** Used to resolve the internal [[Class]] of values */
+  var toString = objectProto.toString;
+
+  /** Used to detect if a method is native */
+  var reNative = RegExp('^' +
+    String(toString)
+      .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
+      .replace(/toString| for [^\]]+/g, '.*?') + '$'
+  );
+
+  /** Native method shortcuts */
+  var ceil = Math.ceil,
+      floor = Math.floor,
+      hasOwnProperty = objectProto.hasOwnProperty,
+      push = arrayRef.push,
+      propertyIsEnumerable = objectProto.propertyIsEnumerable;
+
+  /* Native method shortcuts for methods with the same name as other `lodash` methods */
+  var nativeCreate = isNative(nativeCreate = Object.create) && nativeCreate,
+      nativeIsArray = isNative(nativeIsArray = Array.isArray) && nativeIsArray,
+      nativeIsFinite = root.isFinite,
+      nativeIsNaN = root.isNaN,
+      nativeKeys = isNative(nativeKeys = Object.keys) && nativeKeys,
+      nativeMax = Math.max,
+      nativeMin = Math.min,
+      nativeRandom = Math.random;
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Creates a `lodash` object which wraps the given value to enable intuitive
+   * method chaining.
+   *
+   * In addition to Lo-Dash methods, wrappers also have the following `Array` methods:
+   * `concat`, `join`, `pop`, `push`, `reverse`, `shift`, `slice`, `sort`, `splice`,
+   * and `unshift`
+   *
+   * Chaining is supported in custom builds as long as the `value` method is
+   * implicitly or explicitly included in the build.
+   *
+   * The chainable wrapper functions are:
+   * `after`, `assign`, `bind`, `bindAll`, `bindKey`, `chain`, `compact`,
+   * `compose`, `concat`, `countBy`, `create`, `createCallback`, `curry`,
+   * `debounce`, `defaults`, `defer`, `delay`, `difference`, `filter`, `flatten`,
+   * `forEach`, `forEachRight`, `forIn`, `forInRight`, `forOwn`, `forOwnRight`,
+   * `functions`, `groupBy`, `indexBy`, `initial`, `intersection`, `invert`,
+   * `invoke`, `keys`, `map`, `max`, `memoize`, `merge`, `min`, `object`, `omit`,
+   * `once`, `pairs`, `partial`, `partialRight`, `pick`, `pluck`, `pull`, `push`,
+   * `range`, `reject`, `remove`, `rest`, `reverse`, `shuffle`, `slice`, `sort`,
+   * `sortBy`, `splice`, `tap`, `throttle`, `times`, `toArray`, `transform`,
+   * `union`, `uniq`, `unshift`, `unzip`, `values`, `where`, `without`, `wrap`,
+   * and `zip`
+   *
+   * The non-chainable wrapper functions are:
+   * `clone`, `cloneDeep`, `contains`, `escape`, `every`, `find`, `findIndex`,
+   * `findKey`, `findLast`, `findLastIndex`, `findLastKey`, `has`, `identity`,
+   * `indexOf`, `isArguments`, `isArray`, `isBoolean`, `isDate`, `isElement`,
+   * `isEmpty`, `isEqual`, `isFinite`, `isFunction`, `isNaN`, `isNull`, `isNumber`,
+   * `isObject`, `isPlainObject`, `isRegExp`, `isString`, `isUndefined`, `join`,
+   * `lastIndexOf`, `mixin`, `noConflict`, `parseInt`, `pop`, `random`, `reduce`,
+   * `reduceRight`, `result`, `shift`, `size`, `some`, `sortedIndex`, `runInContext`,
+   * `template`, `unescape`, `uniqueId`, and `value`
+   *
+   * The wrapper functions `first` and `last` return wrapped values when `n` is
+   * provided, otherwise they return unwrapped values.
+   *
+   * Explicit chaining can be enabled by using the `_.chain` method.
+   *
+   * @name _
+   * @constructor
+   * @category Chaining
+   * @param {*} value The value to wrap in a `lodash` instance.
+   * @returns {Object} Returns a `lodash` instance.
+   * @example
+   *
+   * var wrapped = _([1, 2, 3]);
+   *
+   * // returns an unwrapped value
+   * wrapped.reduce(function(sum, num) {
+   *   return sum + num;
+   * });
+   * // => 6
+   *
+   * // returns a wrapped value
+   * var squares = wrapped.map(function(num) {
+   *   return num * num;
+   * });
+   *
+   * _.isArray(squares);
+   * // => false
+   *
+   * _.isArray(squares.value());
+   * // => true
+   */
+  function lodash(value) {
+    return (value instanceof lodash)
+      ? value
+      : new lodashWrapper(value);
+  }
+
+  /**
+   * A fast path for creating `lodash` wrapper objects.
+   *
+   * @private
+   * @param {*} value The value to wrap in a `lodash` instance.
+   * @param {boolean} chainAll A flag to enable chaining for all methods
+   * @returns {Object} Returns a `lodash` instance.
+   */
+  function lodashWrapper(value, chainAll) {
+    this.__chain__ = !!chainAll;
+    this.__wrapped__ = value;
+  }
+  // ensure `new lodashWrapper` is an instance of `lodash`
+  lodashWrapper.prototype = lodash.prototype;
+
+  /**
+   * An object used to flag environments features.
+   *
+   * @static
+   * @memberOf _
+   * @type Object
+   */
+  var support = {};
+
+  (function() {
+    var object = { '0': 1, 'length': 1 };
+
+    /**
+     * Detect if `Array#shift` and `Array#splice` augment array-like objects correctly.
+     *
+     * Firefox < 10, IE compatibility mode, and IE < 9 have buggy Array `shift()`
+     * and `splice()` functions that fail to remove the last element, `value[0]`,
+     * of array-like objects even though the `length` property is set to `0`.
+     * The `shift()` method is buggy in IE 8 compatibility mode, while `splice()`
+     * is buggy regardless of mode in IE < 9 and buggy in compatibility mode in IE 9.
+     *
+     * @memberOf _.support
+     * @type boolean
+     */
+    support.spliceObjects = (arrayRef.splice.call(object, 0, 1), !object[0]);
+  }(1));
+
+  /**
+   * By default, the template delimiters used by Lo-Dash are similar to those in
+   * embedded Ruby (ERB). Change the following template settings to use alternative
+   * delimiters.
+   *
+   * @static
+   * @memberOf _
+   * @type Object
+   */
+  lodash.templateSettings = {
+
+    /**
+     * Used to detect `data` property values to be HTML-escaped.
+     *
+     * @memberOf _.templateSettings
+     * @type RegExp
+     */
+    'escape': /<%-([\s\S]+?)%>/g,
+
+    /**
+     * Used to detect code to be evaluated.
+     *
+     * @memberOf _.templateSettings
+     * @type RegExp
+     */
+    'evaluate': /<%([\s\S]+?)%>/g,
+
+    /**
+     * Used to detect `data` property values to inject.
+     *
+     * @memberOf _.templateSettings
+     * @type RegExp
+     */
+    'interpolate': reInterpolate,
+
+    /**
+     * Used to reference the data object in the template text.
+     *
+     * @memberOf _.templateSettings
+     * @type string
+     */
+    'variable': ''
+  };
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * The base implementation of `_.bind` that creates the bound function and
+   * sets its meta data.
+   *
+   * @private
+   * @param {Array} bindData The bind data array.
+   * @returns {Function} Returns the new bound function.
+   */
+  function baseBind(bindData) {
+    var func = bindData[0],
+        partialArgs = bindData[2],
+        thisArg = bindData[4];
+
+    function bound() {
+      // `Function#bind` spec
+      // http://es5.github.io/#x15.3.4.5
+      if (partialArgs) {
+        // avoid `arguments` object deoptimizations by using `slice` instead
+        // of `Array.prototype.slice.call` and not assigning `arguments` to a
+        // variable as a ternary expression
+        var args = slice(partialArgs);
+        push.apply(args, arguments);
+      }
+      // mimic the constructor's `return` behavior
+      // http://es5.github.io/#x13.2.2
+      if (this instanceof bound) {
+        // ensure `new bound` is an instance of `func`
+        var thisBinding = baseCreate(func.prototype),
+            result = func.apply(thisBinding, args || arguments);
+        return isObject(result) ? result : thisBinding;
+      }
+      return func.apply(thisArg, args || arguments);
+    }
+    return bound;
+  }
+
+  /**
+   * The base implementation of `_.create` without support for assigning
+   * properties to the created object.
+   *
+   * @private
+   * @param {Object} prototype The object to inherit from.
+   * @returns {Object} Returns the new object.
+   */
+  function baseCreate(prototype, properties) {
+    return isObject(prototype) ? nativeCreate(prototype) : {};
+  }
+  // fallback for browsers without `Object.create`
+  if (!nativeCreate) {
+    baseCreate = (function() {
+      function Object() {}
+      return function(prototype) {
+        if (isObject(prototype)) {
+          Object.prototype = prototype;
+          var result = new Object;
+          Object.prototype = null;
+        }
+        return result || root.Object();
+      };
+    }());
+  }
+
+  /**
+   * The base implementation of `_.createCallback` without support for creating
+   * "_.pluck" or "_.where" style callbacks.
+   *
+   * @private
+   * @param {*} [func=identity] The value to convert to a callback.
+   * @param {*} [thisArg] The `this` binding of the created callback.
+   * @param {number} [argCount] The number of arguments the callback accepts.
+   * @returns {Function} Returns a callback function.
+   */
+  function baseCreateCallback(func, thisArg, argCount) {
+    if (typeof func != 'function') {
+      return identity;
+    }
+    // exit early for no `thisArg` or already bound by `Function#bind`
+    if (typeof thisArg == 'undefined' || !('prototype' in func)) {
+      return func;
+    }
+    switch (argCount) {
+      case 1: return function(value) {
+        return func.call(thisArg, value);
+      };
+      case 2: return function(a, b) {
+        return func.call(thisArg, a, b);
+      };
+      case 3: return function(value, index, collection) {
+        return func.call(thisArg, value, index, collection);
+      };
+      case 4: return function(accumulator, value, index, collection) {
+        return func.call(thisArg, accumulator, value, index, collection);
+      };
+    }
+    return bind(func, thisArg);
+  }
+
+  /**
+   * The base implementation of `createWrapper` that creates the wrapper and
+   * sets its meta data.
+   *
+   * @private
+   * @param {Array} bindData The bind data array.
+   * @returns {Function} Returns the new function.
+   */
+  function baseCreateWrapper(bindData) {
+    var func = bindData[0],
+        bitmask = bindData[1],
+        partialArgs = bindData[2],
+        partialRightArgs = bindData[3],
+        thisArg = bindData[4],
+        arity = bindData[5];
+
+    var isBind = bitmask & 1,
+        isBindKey = bitmask & 2,
+        isCurry = bitmask & 4,
+        isCurryBound = bitmask & 8,
+        key = func;
+
+    function bound() {
+      var thisBinding = isBind ? thisArg : this;
+      if (partialArgs) {
+        var args = slice(partialArgs);
+        push.apply(args, arguments);
+      }
+      if (partialRightArgs || isCurry) {
+        args || (args = slice(arguments));
+        if (partialRightArgs) {
+          push.apply(args, partialRightArgs);
+        }
+        if (isCurry && args.length < arity) {
+          bitmask |= 16 & ~32;
+          return baseCreateWrapper([func, (isCurryBound ? bitmask : bitmask & ~3), args, null, thisArg, arity]);
+        }
+      }
+      args || (args = arguments);
+      if (isBindKey) {
+        func = thisBinding[key];
+      }
+      if (this instanceof bound) {
+        thisBinding = baseCreate(func.prototype);
+        var result = func.apply(thisBinding, args);
+        return isObject(result) ? result : thisBinding;
+      }
+      return func.apply(thisBinding, args);
+    }
+    return bound;
+  }
+
+  /**
+   * The base implementation of `_.difference` that accepts a single array
+   * of values to exclude.
+   *
+   * @private
+   * @param {Array} array The array to process.
+   * @param {Array} [values] The array of values to exclude.
+   * @returns {Array} Returns a new array of filtered values.
+   */
+  function baseDifference(array, values) {
+    var index = -1,
+        indexOf = getIndexOf(),
+        length = array ? array.length : 0,
+        result = [];
+
+    while (++index < length) {
+      var value = array[index];
+      if (indexOf(values, value) < 0) {
+        result.push(value);
+      }
+    }
+    return result;
+  }
+
+  /**
+   * The base implementation of `_.flatten` without support for callback
+   * shorthands or `thisArg` binding.
+   *
+   * @private
+   * @param {Array} array The array to flatten.
+   * @param {boolean} [isShallow=false] A flag to restrict flattening to a single level.
+   * @param {boolean} [isStrict=false] A flag to restrict flattening to arrays and `arguments` objects.
+   * @param {number} [fromIndex=0] The index to start from.
+   * @returns {Array} Returns a new flattened array.
+   */
+  function baseFlatten(array, isShallow, isStrict, fromIndex) {
+    var index = (fromIndex || 0) - 1,
+        length = array ? array.length : 0,
+        result = [];
+
+    while (++index < length) {
+      var value = array[index];
+
+      if (value && typeof value == 'object' && typeof value.length == 'number'
+          && (isArray(value) || isArguments(value))) {
+        // recursively flatten arrays (susceptible to call stack limits)
+        if (!isShallow) {
+          value = baseFlatten(value, isShallow, isStrict);
+        }
+        var valIndex = -1,
+            valLength = value.length,
+            resIndex = result.length;
+
+        result.length += valLength;
+        while (++valIndex < valLength) {
+          result[resIndex++] = value[valIndex];
+        }
+      } else if (!isStrict) {
+        result.push(value);
+      }
+    }
+    return result;
+  }
+
+  /**
+   * The base implementation of `_.isEqual`, without support for `thisArg` binding,
+   * that allows partial "_.where" style comparisons.
+   *
+   * @private
+   * @param {*} a The value to compare.
+   * @param {*} b The other value to compare.
+   * @param {Function} [callback] The function to customize comparing values.
+   * @param {Function} [isWhere=false] A flag to indicate performing partial comparisons.
+   * @param {Array} [stackA=[]] Tracks traversed `a` objects.
+   * @param {Array} [stackB=[]] Tracks traversed `b` objects.
+   * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
+   */
+  function baseIsEqual(a, b, stackA, stackB) {
+    if (a === b) {
+      return a !== 0 || (1 / a == 1 / b);
+    }
+    var type = typeof a,
+        otherType = typeof b;
+
+    if (a === a &&
+        !(a && objectTypes[type]) &&
+        !(b && objectTypes[otherType])) {
+      return false;
+    }
+    if (a == null || b == null) {
+      return a === b;
+    }
+    var className = toString.call(a),
+        otherClass = toString.call(b);
+
+    if (className != otherClass) {
+      return false;
+    }
+    switch (className) {
+      case boolClass:
+      case dateClass:
+        return +a == +b;
+
+      case numberClass:
+        return a != +a
+          ? b != +b
+          : (a == 0 ? (1 / a == 1 / b) : a == +b);
+
+      case regexpClass:
+      case stringClass:
+        return a == String(b);
+    }
+    var isArr = className == arrayClass;
+    if (!isArr) {
+      var aWrapped = a instanceof lodash,
+          bWrapped = b instanceof lodash;
+
+      if (aWrapped || bWrapped) {
+        return baseIsEqual(aWrapped ? a.__wrapped__ : a, bWrapped ? b.__wrapped__ : b, stackA, stackB);
+      }
+      if (className != objectClass) {
+        return false;
+      }
+      var ctorA = a.constructor,
+          ctorB = b.constructor;
+
+      if (ctorA != ctorB &&
+            !(isFunction(ctorA) && ctorA instanceof ctorA && isFunction(ctorB) && ctorB instanceof ctorB) &&
+            ('constructor' in a && 'constructor' in b)
+          ) {
+        return false;
+      }
+    }
+    stackA || (stackA = []);
+    stackB || (stackB = []);
+
+    var length = stackA.length;
+    while (length--) {
+      if (stackA[length] == a) {
+        return stackB[length] == b;
+      }
+    }
+    var result = true,
+        size = 0;
+
+    stackA.push(a);
+    stackB.push(b);
+
+    if (isArr) {
+      size = b.length;
+      result = size == a.length;
+
+      if (result) {
+        while (size--) {
+          if (!(result = baseIsEqual(a[size], b[size], stackA, stackB))) {
+            break;
+          }
+        }
+      }
+    }
+    else {
+      forIn(b, function(value, key, b) {
+        if (hasOwnProperty.call(b, key)) {
+          size++;
+          return !(result = hasOwnProperty.call(a, key) && baseIsEqual(a[key], value, stackA, stackB)) && indicatorObject;
+        }
+      });
+
+      if (result) {
+        forIn(a, function(value, key, a) {
+          if (hasOwnProperty.call(a, key)) {
+            return !(result = --size > -1) && indicatorObject;
+          }
+        });
+      }
+    }
+    stackA.pop();
+    stackB.pop();
+    return result;
+  }
+
+  /**
+   * The base implementation of `_.random` without argument juggling or support
+   * for returning floating-point numbers.
+   *
+   * @private
+   * @param {number} min The minimum possible value.
+   * @param {number} max The maximum possible value.
+   * @returns {number} Returns a random number.
+   */
+  function baseRandom(min, max) {
+    return min + floor(nativeRandom() * (max - min + 1));
+  }
+
+  /**
+   * The base implementation of `_.uniq` without support for callback shorthands
+   * or `thisArg` binding.
+   *
+   * @private
+   * @param {Array} array The array to process.
+   * @param {boolean} [isSorted=false] A flag to indicate that `array` is sorted.
+   * @param {Function} [callback] The function called per iteration.
+   * @returns {Array} Returns a duplicate-value-free array.
+   */
+  function baseUniq(array, isSorted, callback) {
+    var index = -1,
+        indexOf = getIndexOf(),
+        length = array ? array.length : 0,
+        result = [],
+        seen = callback ? [] : result;
+
+    while (++index < length) {
+      var value = array[index],
+          computed = callback ? callback(value, index, array) : value;
+
+      if (isSorted
+            ? !index || seen[seen.length - 1] !== computed
+            : indexOf(seen, computed) < 0
+          ) {
+        if (callback) {
+          seen.push(computed);
+        }
+        result.push(value);
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Creates a function that aggregates a collection, creating an object composed
+   * of keys generated from the results of running each element of the collection
+   * through a callback. The given `setter` function sets the keys and values
+   * of the composed object.
+   *
+   * @private
+   * @param {Function} setter The setter function.
+   * @returns {Function} Returns the new aggregator function.
+   */
+  function createAggregator(setter) {
+    return function(collection, callback, thisArg) {
+      var result = {};
+      callback = createCallback(callback, thisArg, 3);
+
+      var index = -1,
+          length = collection ? collection.length : 0;
+
+      if (typeof length == 'number') {
+        while (++index < length) {
+          var value = collection[index];
+          setter(result, value, callback(value, index, collection), collection);
+        }
+      } else {
+        forOwn(collection, function(value, key, collection) {
+          setter(result, value, callback(value, key, collection), collection);
+        });
+      }
+      return result;
+    };
+  }
+
+  /**
+   * Creates a function that, when called, either curries or invokes `func`
+   * with an optional `this` binding and partially applied arguments.
+   *
+   * @private
+   * @param {Function|string} func The function or method name to reference.
+   * @param {number} bitmask The bitmask of method flags to compose.
+   *  The bitmask may be composed of the following flags:
+   *  1 - `_.bind`
+   *  2 - `_.bindKey`
+   *  4 - `_.curry`
+   *  8 - `_.curry` (bound)
+   *  16 - `_.partial`
+   *  32 - `_.partialRight`
+   * @param {Array} [partialArgs] An array of arguments to prepend to those
+   *  provided to the new function.
+   * @param {Array} [partialRightArgs] An array of arguments to append to those
+   *  provided to the new function.
+   * @param {*} [thisArg] The `this` binding of `func`.
+   * @param {number} [arity] The arity of `func`.
+   * @returns {Function} Returns the new function.
+   */
+  function createWrapper(func, bitmask, partialArgs, partialRightArgs, thisArg, arity) {
+    var isBind = bitmask & 1,
+        isBindKey = bitmask & 2,
+        isCurry = bitmask & 4,
+        isCurryBound = bitmask & 8,
+        isPartial = bitmask & 16,
+        isPartialRight = bitmask & 32;
+
+    if (!isBindKey && !isFunction(func)) {
+      throw new TypeError;
+    }
+    if (isPartial && !partialArgs.length) {
+      bitmask &= ~16;
+      isPartial = partialArgs = false;
+    }
+    if (isPartialRight && !partialRightArgs.length) {
+      bitmask &= ~32;
+      isPartialRight = partialRightArgs = false;
+    }
+    // fast path for `_.bind`
+    var creater = (bitmask == 1 || bitmask === 17) ? baseBind : baseCreateWrapper;
+    return creater([func, bitmask, partialArgs, partialRightArgs, thisArg, arity]);
+  }
+
+  /**
+   * Used by `escape` to convert characters to HTML entities.
+   *
+   * @private
+   * @param {string} match The matched character to escape.
+   * @returns {string} Returns the escaped character.
+   */
+  function escapeHtmlChar(match) {
+    return htmlEscapes[match];
+  }
+
+  /**
+   * Gets the appropriate "indexOf" function. If the `_.indexOf` method is
+   * customized, this method returns the custom method, otherwise it returns
+   * the `baseIndexOf` function.
+   *
+   * @private
+   * @returns {Function} Returns the "indexOf" function.
+   */
+  function getIndexOf() {
+    var result = (result = lodash.indexOf) === indexOf ? baseIndexOf : result;
+    return result;
+  }
+
+  /**
+   * Checks if `value` is a native function.
+   *
+   * @private
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if the `value` is a native function, else `false`.
+   */
+  function isNative(value) {
+    return typeof value == 'function' && reNative.test(value);
+  }
+
+  /**
+   * Used by `unescape` to convert HTML entities to characters.
+   *
+   * @private
+   * @param {string} match The matched character to unescape.
+   * @returns {string} Returns the unescaped character.
+   */
+  function unescapeHtmlChar(match) {
+    return htmlUnescapes[match];
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Checks if `value` is an `arguments` object.
+   *
+   * @static
+   * @memberOf _
+   * @category Objects
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if the `value` is an `arguments` object, else `false`.
+   * @example
+   *
+   * (function() { return _.isArguments(arguments); })(1, 2, 3);
+   * // => true
+   *
+   * _.isArguments([1, 2, 3]);
+   * // => false
+   */
+  function isArguments(value) {
+    return value && typeof value == 'object' && typeof value.length == 'number' &&
+      toString.call(value) == argsClass || false;
+  }
+  // fallback for browsers that can't detect `arguments` objects by [[Class]]
+  if (!isArguments(arguments)) {
+    isArguments = function(value) {
+      return value && typeof value == 'object' && typeof value.length == 'number' &&
+        hasOwnProperty.call(value, 'callee') && !propertyIsEnumerable.call(value, 'callee') || false;
+    };
+  }
+
+  /**
+   * Checks if `value` is an array.
+   *
+   * @static
+   * @memberOf _
+   * @type Function
+   * @category Objects
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if the `value` is an array, else `false`.
+   * @example
+   *
+   * (function() { return _.isArray(arguments); })();
+   * // => false
+   *
+   * _.isArray([1, 2, 3]);
+   * // => true
+   */
+  var isArray = nativeIsArray || function(value) {
+    return value && typeof value == 'object' && typeof value.length == 'number' &&
+      toString.call(value) == arrayClass || false;
+  };
+
+  /**
+   * A fallback implementation of `Object.keys` which produces an array of the
+   * given object's own enumerable property names.
+   *
+   * @private
+   * @type Function
+   * @param {Object} object The object to inspect.
+   * @returns {Array} Returns an array of property names.
+   */
+  var shimKeys = function(object) {
+    var index, iterable = object, result = [];
+    if (!iterable) return result;
+    if (!(objectTypes[typeof object])) return result;
+      for (index in iterable) {
+        if (hasOwnProperty.call(iterable, index)) {
+          result.push(index);
+        }
+      }
+    return result
+  };
+
+  /**
+   * Creates an array composed of the own enumerable property names of an object.
+   *
+   * @static
+   * @memberOf _
+   * @category Objects
+   * @param {Object} object The object to inspect.
+   * @returns {Array} Returns an array of property names.
+   * @example
+   *
+   * _.keys({ 'one': 1, 'two': 2, 'three': 3 });
+   * // => ['one', 'two', 'three'] (property order is not guaranteed across environments)
+   */
+  var keys = !nativeKeys ? shimKeys : function(object) {
+    if (!isObject(object)) {
+      return [];
+    }
+    return nativeKeys(object);
+  };
+
+  /**
+   * Used to convert characters to HTML entities:
+   *
+   * Though the `>` character is escaped for symmetry, characters like `>` and `/`
+   * don't require escaping in HTML and have no special meaning unless they're part
+   * of a tag or an unquoted attribute value.
+   * http://mathiasbynens.be/notes/ambiguous-ampersands (under "semi-related fun fact")
+   */
+  var htmlEscapes = {
+    '&': '&amp;',
+    '<': '&lt;',
+    '>': '&gt;',
+    '"': '&quot;',
+    "'": '&#x27;'
+  };
+
+  /** Used to convert HTML entities to characters */
+  var htmlUnescapes = invert(htmlEscapes);
+
+  /** Used to match HTML entities and HTML characters */
+  var reEscapedHtml = RegExp('(' + keys(htmlUnescapes).join('|') + ')', 'g'),
+      reUnescapedHtml = RegExp('[' + keys(htmlEscapes).join('') + ']', 'g');
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Assigns own enumerable properties of source object(s) to the destination
+   * object. Subsequent sources will overwrite property assignments of previous
+   * sources. If a callback is provided it will be executed to produce the
+   * assigned values. The callback is bound to `thisArg` and invoked with two
+   * arguments; (objectValue, sourceValue).
+   *
+   * @static
+   * @memberOf _
+   * @type Function
+   * @alias extend
+   * @category Objects
+   * @param {Object} object The destination object.
+   * @param {...Object} [source] The source objects.
+   * @param {Function} [callback] The function to customize assigning values.
+   * @param {*} [thisArg] The `this` binding of `callback`.
+   * @returns {Object} Returns the destination object.
+   * @example
+   *
+   * _.assign({ 'name': 'fred' }, { 'employer': 'slate' });
+   * // => { 'name': 'fred', 'employer': 'slate' }
+   *
+   * var defaults = _.partialRight(_.assign, function(a, b) {
+   *   return typeof a == 'undefined' ? b : a;
+   * });
+   *
+   * var object = { 'name': 'barney' };
+   * defaults(object, { 'name': 'fred', 'employer': 'slate' });
+   * // => { 'name': 'barney', 'employer': 'slate' }
+   */
+  function assign(object) {
+    if (!object) {
+      return object;
+    }
+    for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) {
+      var iterable = arguments[argsIndex];
+      if (iterable) {
+        for (var key in iterable) {
+          object[key] = iterable[key];
+        }
+      }
+    }
+    return object;
+  }
+
+  /**
+   * Creates a clone of `value`. If `isDeep` is `true` nested objects will also
+   * be cloned, otherwise they will be assigned by reference. If a callback
+   * is provided it will be executed to produce the cloned values. If the
+   * callback returns `undefined` cloning will be handled by the method instead.
+   * The callback is bound to `thisArg` and invoked with one argument; (value).
+   *
+   * @static
+   * @memberOf _
+   * @category Objects
+   * @param {*} value The value to clone.
+   * @param {boolean} [isDeep=false] Specify a deep clone.
+   * @param {Function} [callback] The function to customize cloning values.
+   * @param {*} [thisArg] The `this` binding of `callback`.
+   * @returns {*} Returns the cloned value.
+   * @example
+   *
+   * var characters = [
+   *   { 'name': 'barney', 'age': 36 },
+   *   { 'name': 'fred',   'age': 40 }
+   * ];
+   *
+   * var shallow = _.clone(characters);
+   * shallow[0] === characters[0];
+   * // => true
+   *
+   * var deep = _.clone(characters, true);
+   * deep[0] === characters[0];
+   * // => false
+   *
+   * _.mixin({
+   *   'clone': _.partialRight(_.clone, function(value) {
+   *     return _.isElement(value) ? value.cloneNode(false) : undefined;
+   *   })
+   * });
+   *
+   * var clone = _.clone(document.body);
+   * clone.childNodes.length;
+   * // => 0
+   */
+  function clone(value) {
+    return isObject(value)
+      ? (isArray(value) ? slice(value) : assign({}, value))
+      : value;
+  }
+
+  /**
+   * Assigns own enumerable properties of source object(s) to the destination
+   * object for all destination properties that resolve to `undefined`. Once a
+   * property is set, additional defaults of the same property will be ignored.
+   *
+   * @static
+   * @memberOf _
+   * @type Function
+   * @category Objects
+   * @param {Object} object The destination object.
+   * @param {...Object} [source] The source objects.
+   * @param- {Object} [guard] Allows working with `_.reduce` without using its
+   *  `key` and `object` arguments as sources.
+   * @returns {Object} Returns the destination object.
+   * @example
+   *
+   * var object = { 'name': 'barney' };
+   * _.defaults(object, { 'name': 'fred', 'employer': 'slate' });
+   * // => { 'name': 'barney', 'employer': 'slate' }
+   */
+  function defaults(object) {
+    if (!object) {
+      return object;
+    }
+    for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) {
+      var iterable = arguments[argsIndex];
+      if (iterable) {
+        for (var key in iterable) {
+          if (typeof object[key] == 'undefined') {
+            object[key] = iterable[key];
+          }
+        }
+      }
+    }
+    return object;
+  }
+
+  /**
+   * Iterates over own and inherited enumerable properties of an object,
+   * executing the callback for each property. The callback is bound to `thisArg`
+   * and invoked with three arguments; (value, key, object). Callbacks may exit
+   * iteration early by explicitly returning `false`.
+   *
+   * @static
+   * @memberOf _
+   * @type Function
+   * @category Objects
+   * @param {Object} object The object to iterate over.
+   * @param {Function} [callback=identity] The function called per iteration.
+   * @param {*} [thisArg] The `this` binding of `callback`.
+   * @returns {Object} Returns `object`.
+   * @example
+   *
+   * function Shape() {
+   *   this.x = 0;
+   *   this.y = 0;
+   * }
+   *
+   * Shape.prototype.move = function(x, y) {
+   *   this.x += x;
+   *   this.y += y;
+   * };
+   *
+   * _.forIn(new Shape, function(value, key) {
+   *   console.log(key);
+   * });
+   * // => logs 'x', 'y', and 'move' (property order is not guaranteed across environments)
+   */
+  var forIn = function(collection, callback) {
+    var index, iterable = collection, result = iterable;
+    if (!iterable) return result;
+    if (!objectTypes[typeof iterable]) return result;
+      for (index in iterable) {
+        if (callback(iterable[index], index, collection) === indicatorObject) return result;
+      }
+    return result
+  };
+
+  /**
+   * Iterates over own enumerable properties of an object, executing the callback
+   * for each property. The callback is bound to `thisArg` and invoked with three
+   * arguments; (value, key, object). Callbacks may exit iteration early by
+   * explicitly returning `false`.
+   *
+   * @static
+   * @memberOf _
+   * @type Function
+   * @category Objects
+   * @param {Object} object The object to iterate over.
+   * @param {Function} [callback=identity] The function called per iteration.
+   * @param {*} [thisArg] The `this` binding of `callback`.
+   * @returns {Object} Returns `object`.
+   * @example
+   *
+   * _.forOwn({ '0': 'zero', '1': 'one', 'length': 2 }, function(num, key) {
+   *   console.log(key);
+   * });
+   * // => logs '0', '1', and 'length' (property order is not guaranteed across environments)
+   */
+  var forOwn = function(collection, callback) {
+    var index, iterable = collection, result = iterable;
+    if (!iterable) return result;
+    if (!objectTypes[typeof iterable]) return result;
+      for (index in iterable) {
+        if (hasOwnProperty.call(iterable, index)) {
+          if (callback(iterable[index], index, collection) === indicatorObject) return result;
+        }
+      }
+    return result
+  };
+
+  /**
+   * Creates a sorted array of property names of all enumerable properties,
+   * own and inherited, of `object` that have function values.
+   *
+   * @static
+   * @memberOf _
+   * @alias methods
+   * @category Objects
+   * @param {Object} object The object to inspect.
+   * @returns {Array} Returns an array of property names that have function values.
+   * @example
+   *
+   * _.functions(_);
+   * // => ['all', 'any', 'bind', 'bindAll', 'clone', 'compact', 'compose', ...]
+   */
+  function functions(object) {
+    var result = [];
+    forIn(object, function(value, key) {
+      if (isFunction(value)) {
+        result.push(key);
+      }
+    });
+    return result.sort();
+  }
+
+  /**
+   * Checks if the specified property name exists as a direct property of `object`,
+   * instead of an inherited property.
+   *
+   * @static
+   * @memberOf _
+   * @category Objects
+   * @param {Object} object The object to inspect.
+   * @param {string} key The name of the property to check.
+   * @returns {boolean} Returns `true` if key is a direct property, else `false`.
+   * @example
+   *
+   * _.has({ 'a': 1, 'b': 2, 'c': 3 }, 'b');
+   * // => true
+   */
+  function has(object, key) {
+    return object ? hasOwnProperty.call(object, key) : false;
+  }
+
+  /**
+   * Creates an object composed of the inverted keys and values of the given object.
+   *
+   * @static
+   * @memberOf _
+   * @category Objects
+   * @param {Object} object The object to invert.
+   * @returns {Object} Returns the created inverted object.
+   * @example
+   *
+   * _.invert({ 'first': 'fred', 'second': 'barney' });
+   * // => { 'fred': 'first', 'barney': 'second' }
+   */
+  function invert(object) {
+    var index = -1,
+        props = keys(object),
+        length = props.length,
+        result = {};
+
+    while (++index < length) {
+      var key = props[index];
+      result[object[key]] = key;
+    }
+    return result;
+  }
+
+  /**
+   * Checks if `value` is a boolean value.
+   *
+   * @static
+   * @memberOf _
+   * @category Objects
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if the `value` is a boolean value, else `false`.
+   * @example
+   *
+   * _.isBoolean(null);
+   * // => false
+   */
+  function isBoolean(value) {
+    return value === true || value === false ||
+      value && typeof value == 'object' && toString.call(value) == boolClass || false;
+  }
+
+  /**
+   * Checks if `value` is a date.
+   *
+   * @static
+   * @memberOf _
+   * @category Objects
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if the `value` is a date, else `false`.
+   * @example
+   *
+   * _.isDate(new Date);
+   * // => true
+   */
+  function isDate(value) {
+    return value && typeof value == 'object' && toString.call(value) == dateClass || false;
+  }
+
+  /**
+   * Checks if `value` is a DOM element.
+   *
+   * @static
+   * @memberOf _
+   * @category Objects
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if the `value` is a DOM element, else `false`.
+   * @example
+   *
+   * _.isElement(document.body);
+   * // => true
+   */
+  function isElement(value) {
+    return value && value.nodeType === 1 || false;
+  }
+
+  /**
+   * Checks if `value` is empty. Arrays, strings, or `arguments` objects with a
+   * length of `0` and objects with no own enumerable properties are considered
+   * "empty".
+   *
+   * @static
+   * @memberOf _
+   * @category Objects
+   * @param {Array|Object|string} value The value to inspect.
+   * @returns {boolean} Returns `true` if the `value` is empty, else `false`.
+   * @example
+   *
+   * _.isEmpty([1, 2, 3]);
+   * // => false
+   *
+   * _.isEmpty({});
+   * // => true
+   *
+   * _.isEmpty('');
+   * // => true
+   */
+  function isEmpty(value) {
+    if (!value) {
+      return true;
+    }
+    if (isArray(value) || isString(value)) {
+      return !value.length;
+    }
+    for (var key in value) {
+      if (hasOwnProperty.call(value, key)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Performs a deep comparison between two values to determine if they are
+   * equivalent to each other. If a callback is provided it will be executed
+   * to compare values. If the callback returns `undefined` comparisons will
+   * be handled by the method instead. The callback is bound to `thisArg` and
+   * invoked with two arguments; (a, b).
+   *
+   * @static
+   * @memberOf _
+   * @category Objects
+   * @param {*} a The value to compare.
+   * @param {*} b The other value to compare.
+   * @param {Function} [callback] The function to customize comparing values.
+   * @param {*} [thisArg] The `this` binding of `callback`.
+   * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
+   * @example
+   *
+   * var object = { 'name': 'fred' };
+   * var copy = { 'name': 'fred' };
+   *
+   * object == copy;
+   * // => false
+   *
+   * _.isEqual(object, copy);
+   * // => true
+   *
+   * var words = ['hello', 'goodbye'];
+   * var otherWords = ['hi', 'goodbye'];
+   *
+   * _.isEqual(words, otherWords, function(a, b) {
+   *   var reGreet = /^(?:hello|hi)$/i,
+   *       aGreet = _.isString(a) && reGreet.test(a),
+   *       bGreet = _.isString(b) && reGreet.test(b);
+   *
+   *   return (aGreet || bGreet) ? (aGreet == bGreet) : undefined;
+   * });
+   * // => true
+   */
+  function isEqual(a, b) {
+    return baseIsEqual(a, b);
+  }
+
+  /**
+   * Checks if `value` is, or can be coerced to, a finite number.
+   *
+   * Note: This is not the same as native `isFinite` which will return true for
+   * booleans and empty strings. See http://es5.github.io/#x15.1.2.5.
+   *
+   * @static
+   * @memberOf _
+   * @category Objects
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if the `value` is finite, else `false`.
+   * @example
+   *
+   * _.isFinite(-101);
+   * // => true
+   *
+   * _.isFinite('10');
+   * // => true
+   *
+   * _.isFinite(true);
+   * // => false
+   *
+   * _.isFinite('');
+   * // => false
+   *
+   * _.isFinite(Infinity);
+   * // => false
+   */
+  function isFinite(value) {
+    return nativeIsFinite(value) && !nativeIsNaN(parseFloat(value));
+  }
+
+  /**
+   * Checks if `value` is a function.
+   *
+   * @static
+   * @memberOf _
+   * @category Objects
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if the `value` is a function, else `false`.
+   * @example
+   *
+   * _.isFunction(_);
+   * // => true
+   */
+  function isFunction(value) {
+    return typeof value == 'function';
+  }
+  // fallback for older versions of Chrome and Safari
+  if (isFunction(/x/)) {
+    isFunction = function(value) {
+      return typeof value == 'function' && toString.call(value) == funcClass;
+    };
+  }
+
+  /**
+   * Checks if `value` is the language type of Object.
+   * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
+   *
+   * @static
+   * @memberOf _
+   * @category Objects
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if the `value` is an object, else `false`.
+   * @example
+   *
+   * _.isObject({});
+   * // => true
+   *
+   * _.isObject([1, 2, 3]);
+   * // => true
+   *
+   * _.isObject(1);
+   * // => false
+   */
+  function isObject(value) {
+    // check if the value is the ECMAScript language type of Object
+    // http://es5.github.io/#x8
+    // and avoid a V8 bug
+    // http://code.google.com/p/v8/issues/detail?id=2291
+    return !!(value && objectTypes[typeof value]);
+  }
+
+  /**
+   * Checks if `value` is `NaN`.
+   *
+   * Note: This is not the same as native `isNaN` which will return `true` for
+   * `undefined` and other non-numeric values. See http://es5.github.io/#x15.1.2.4.
+   *
+   * @static
+   * @memberOf _
+   * @category Objects
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if the `value` is `NaN`, else `false`.
+   * @example
+   *
+   * _.isNaN(NaN);
+   * // => true
+   *
+   * _.isNaN(new Number(NaN));
+   * // => true
+   *
+   * isNaN(undefined);
+   * // => true
+   *
+   * _.isNaN(undefined);
+   * // => false
+   */
+  function isNaN(value) {
+    // `NaN` as a primitive is the only value that is not equal to itself
+    // (perform the [[Class]] check first to avoid errors with some host objects in IE)
+    return isNumber(value) && value != +value;
+  }
+
+  /**
+   * Checks if `value` is `null`.
+   *
+   * @static
+   * @memberOf _
+   * @category Objects
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if the `value` is `null`, else `false`.
+   * @example
+   *
+   * _.isNull(null);
+   * // => true
+   *
+   * _.isNull(undefined);
+   * // => false
+   */
+  function isNull(value) {
+    return value === null;
+  }
+
+  /**
+   * Checks if `value` is a number.
+   *
+   * Note: `NaN` is considered a number. See http://es5.github.io/#x8.5.
+   *
+   * @static
+   * @memberOf _
+   * @category Objects
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if the `value` is a number, else `false`.
+   * @example
+   *
+   * _.isNumber(8.4 * 5);
+   * // => true
+   */
+  function isNumber(value) {
+    return typeof value == 'number' ||
+      value && typeof value == 'object' && toString.call(value) == numberClass || false;
+  }
+
+  /**
+   * Checks if `value` is a regular expression.
+   *
+   * @static
+   * @memberOf _
+   * @category Objects
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if the `value` is a regular expression, else `false`.
+   * @example
+   *
+   * _.isRegExp(/fred/);
+   * // => true
+   */
+  function isRegExp(value) {
+    return value && objectTypes[typeof value] && toString.call(value) == regexpClass || false;
+  }
+
+  /**
+   * Checks if `value` is a string.
+   *
+   * @static
+   * @memberOf _
+   * @category Objects
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if the `value` is a string, else `false`.
+   * @example
+   *
+   * _.isString('fred');
+   * // => true
+   */
+  function isString(value) {
+    return typeof value == 'string' ||
+      value && typeof value == 'object' && toString.call(value) == stringClass || false;
+  }
+
+  /**
+   * Checks if `value` is `undefined`.
+   *
+   * @static
+   * @memberOf _
+   * @category Objects
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if the `value` is `undefined`, else `false`.
+   * @example
+   *
+   * _.isUndefined(void 0);
+   * // => true
+   */
+  function isUndefined(value) {
+    return typeof value == 'undefined';
+  }
+
+  /**
+   * Creates a shallow clone of `object` excluding the specified properties.
+   * Property names may be specified as individual arguments or as arrays of
+   * property names. If a callback is provided it will be executed for each
+   * property of `object` omitting the properties the callback returns truey
+   * for. The callback is bound to `thisArg` and invoked with three arguments;
+   * (value, key, object).
+   *
+   * @static
+   * @memberOf _
+   * @category Objects
+   * @param {Object} object The source object.
+   * @param {Function|...string|string[]} [callback] The properties to omit or the
+   *  function called per iteration.
+   * @param {*} [thisArg] The `this` binding of `callback`.
+   * @returns {Object} Returns an object without the omitted properties.
+   * @example
+   *
+   * _.omit({ 'name': 'fred', 'age': 40 }, 'age');
+   * // => { 'name': 'fred' }
+   *
+   * _.omit({ 'name': 'fred', 'age': 40 }, function(value) {
+   *   return typeof value == 'number';
+   * });
+   * // => { 'name': 'fred' }
+   */
+  function omit(object) {
+    var props = [];
+    forIn(object, function(value, key) {
+      props.push(key);
+    });
+    props = baseDifference(props, baseFlatten(arguments, true, false, 1));
+
+    var index = -1,
+        length = props.length,
+        result = {};
+
+    while (++index < length) {
+      var key = props[index];
+      result[key] = object[key];
+    }
+    return result;
+  }
+
+  /**
+   * Creates a two dimensional array of an object's key-value pairs,
+   * i.e. `[[key1, value1], [key2, value2]]`.
+   *
+   * @static
+   * @memberOf _
+   * @category Objects
+   * @param {Object} object The object to inspect.
+   * @returns {Array} Returns new array of key-value pairs.
+   * @example
+   *
+   * _.pairs({ 'barney': 36, 'fred': 40 });
+   * // => [['barney', 36], ['fred', 40]] (property order is not guaranteed across environments)
+   */
+  function pairs(object) {
+    var index = -1,
+        props = keys(object),
+        length = props.length,
+        result = Array(length);
+
+    while (++index < length) {
+      var key = props[index];
+      result[index] = [key, object[key]];
+    }
+    return result;
+  }
+
+  /**
+   * Creates a shallow clone of `object` composed of the specified properties.
+   * Property names may be specified as individual arguments or as arrays of
+   * property names. If a callback is provided it will be executed for each
+   * property of `object` picking the properties the callback returns truey
+   * for. The callback is bound to `thisArg` and invoked with three arguments;
+   * (value, key, object).
+   *
+   * @static
+   * @memberOf _
+   * @category Objects
+   * @param {Object} object The source object.
+   * @param {Function|...string|string[]} [callback] The function called per
+   *  iteration or property names to pick, specified as individual property
+   *  names or arrays of property names.
+   * @param {*} [thisArg] The `this` binding of `callback`.
+   * @returns {Object} Returns an object composed of the picked properties.
+   * @example
+   *
+   * _.pick({ 'name': 'fred', '_userid': 'fred1' }, 'name');
+   * // => { 'name': 'fred' }
+   *
+   * _.pick({ 'name': 'fred', '_userid': 'fred1' }, function(value, key) {
+   *   return key.charAt(0) != '_';
+   * });
+   * // => { 'name': 'fred' }
+   */
+  function pick(object) {
+    var index = -1,
+        props = baseFlatten(arguments, true, false, 1),
+        length = props.length,
+        result = {};
+
+    while (++index < length) {
+      var key = props[index];
+      if (key in object) {
+        result[key] = object[key];
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Creates an array composed of the own enumerable property values of `object`.
+   *
+   * @static
+   * @memberOf _
+   * @category Objects
+   * @param {Object} object The object to inspect.
+   * @returns {Array} Returns an array of property values.
+   * @example
+   *
+   * _.values({ 'one': 1, 'two': 2, 'three': 3 });
+   * // => [1, 2, 3] (property order is not guaranteed across environments)
+   */
+  function values(object) {
+    var index = -1,
+        props = keys(object),
+        length = props.length,
+        result = Array(length);
+
+    while (++index < length) {
+      result[index] = object[props[index]];
+    }
+    return result;
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Checks if a given value is present in a collection using strict equality
+   * for comparisons, i.e. `===`. If `fromIndex` is negative, it is used as the
+   * offset from the end of the collection.
+   *
+   * @static
+   * @memberOf _
+   * @alias include
+   * @category Collections
+   * @param {Array|Object|string} collection The collection to iterate over.
+   * @param {*} target The value to check for.
+   * @param {number} [fromIndex=0] The index to search from.
+   * @returns {boolean} Returns `true` if the `target` element is found, else `false`.
+   * @example
+   *
+   * _.contains([1, 2, 3], 1);
+   * // => true
+   *
+   * _.contains([1, 2, 3], 1, 2);
+   * // => false
+   *
+   * _.contains({ 'name': 'fred', 'age': 40 }, 'fred');
+   * // => true
+   *
+   * _.contains('pebbles', 'eb');
+   * // => true
+   */
+  function contains(collection, target) {
+    var indexOf = getIndexOf(),
+        length = collection ? collection.length : 0,
+        result = false;
+    if (length && typeof length == 'number') {
+      result = indexOf(collection, target) > -1;
+    } else {
+      forOwn(collection, function(value) {
+        return (result = value === target) && indicatorObject;
+      });
+    }
+    return result;
+  }
+
+  /**
+   * Creates an object composed of keys generated from the results of running
+   * each element of `collection` through the callback. The corresponding value
+   * of each key is the number of times the key was returned by the callback.
+   * The callback is bound to `thisArg` and invoked with three arguments;
+   * (value, index|key, collection).
+   *
+   * If a property name is provided for `callback` the created "_.pluck" style
+   * callback will return the property value of the given element.
+   *
+   * If an object is provided for `callback` the created "_.where" style callback
+   * will return `true` for elements that have the properties of the given object,
+   * else `false`.
+   *
+   * @static
+   * @memberOf _
+   * @category Collections
+   * @param {Array|Object|string} collection The collection to iterate over.
+   * @param {Function|Object|string} [callback=identity] The function called
+   *  per iteration. If a property name or object is provided it will be used
+   *  to create a "_.pluck" or "_.where" style callback, respectively.
+   * @param {*} [thisArg] The `this` binding of `callback`.
+   * @returns {Object} Returns the composed aggregate object.
+   * @example
+   *
+   * _.countBy([4.3, 6.1, 6.4], function(num) { return Math.floor(num); });
+   * // => { '4': 1, '6': 2 }
+   *
+   * _.countBy([4.3, 6.1, 6.4], function(num) { return this.floor(num); }, Math);
+   * // => { '4': 1, '6': 2 }
+   *
+   * _.countBy(['one', 'two', 'three'], 'length');
+   * // => { '3': 2, '5': 1 }
+   */
+  var countBy = createAggregator(function(result, value, key) {
+    (hasOwnProperty.call(result, key) ? result[key]++ : result[key] = 1);
+  });
+
+  /**
+   * Checks if the given callback returns truey value for **all** elements of
+   * a collection. The callback is bound to `thisArg` and invoked with three
+   * arguments; (value, index|key, collection).
+   *
+   * If a property name is provided for `callback` the created "_.pluck" style
+   * callback will return the property value of the given element.
+   *
+   * If an object is provided for `callback` the created "_.where" style callback
+   * will return `true` for elements that have the properties of the given object,
+   * else `false`.
+   *
+   * @static
+   * @memberOf _
+   * @alias all
+   * @category Collections
+   * @param {Array|Object|string} collection The collection to iterate over.
+   * @param {Function|Object|string} [callback=identity] The function called
+   *  per iteration. If a property name or object is provided it will be used
+   *  to create a "_.pluck" or "_.where" style callback, respectively.
+   * @param {*} [thisArg] The `this` binding of `callback`.
+   * @returns {boolean} Returns `true` if all elements passed the callback check,
+   *  else `false`.
+   * @example
+   *
+   * _.every([true, 1, null, 'yes']);
+   * // => false
+   *
+   * var characters = [
+   *   { 'name': 'barney', 'age': 36 },
+   *   { 'name': 'fred',   'age': 40 }
+   * ];
+   *
+   * // using "_.pluck" callback shorthand
+   * _.every(characters, 'age');
+   * // => true
+   *
+   * // using "_.where" callback shorthand
+   * _.every(characters, { 'age': 36 });
+   * // => false
+   */
+  function every(collection, callback, thisArg) {
+    var result = true;
+    callback = createCallback(callback, thisArg, 3);
+
+    var index = -1,
+        length = collection ? collection.length : 0;
+
+    if (typeof length == 'number') {
+      while (++index < length) {
+        if (!(result = !!callback(collection[index], index, collection))) {
+          break;
+        }
+      }
+    } else {
+      forOwn(collection, function(value, index, collection) {
+        return !(result = !!callback(value, index, collection)) && indicatorObject;
+      });
+    }
+    return result;
+  }
+
+  /**
+   * Iterates over elements of a collection, returning an array of all elements
+   * the callback returns truey for. The callback is bound to `thisArg` and
+   * invoked with three arguments; (value, index|key, collection).
+   *
+   * If a property name is provided for `callback` the created "_.pluck" style
+   * callback will return the property value of the given element.
+   *
+   * If an object is provided for `callback` the created "_.where" style callback
+   * will return `true` for elements that have the properties of the given object,
+   * else `false`.
+   *
+   * @static
+   * @memberOf _
+   * @alias select
+   * @category Collections
+   * @param {Array|Object|string} collection The collection to iterate over.
+   * @param {Function|Object|string} [callback=identity] The function called
+   *  per iteration. If a property name or object is provided it will be used
+   *  to create a "_.pluck" or "_.where" style callback, respectively.
+   * @param {*} [thisArg] The `this` binding of `callback`.
+   * @returns {Array} Returns a new array of elements that passed the callback check.
+   * @example
+   *
+   * var evens = _.filter([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; });
+   * // => [2, 4, 6]
+   *
+   * var characters = [
+   *   { 'name': 'barney', 'age': 36, 'blocked': false },
+   *   { 'name': 'fred',   'age': 40, 'blocked': true }
+   * ];
+   *
+   * // using "_.pluck" callback shorthand
+   * _.filter(characters, 'blocked');
+   * // => [{ 'name': 'fred', 'age': 40, 'blocked': true }]
+   *
+   * // using "_.where" callback shorthand
+   * _.filter(characters, { 'age': 36 });
+   * // => [{ 'name': 'barney', 'age': 36, 'blocked': false }]
+   */
+  function filter(collection, callback, thisArg) {
+    var result = [];
+    callback = createCallback(callback, thisArg, 3);
+
+    var index = -1,
+        length = collection ? collection.length : 0;
+
+    if (typeof length == 'number') {
+      while (++index < length) {
+        var value = collection[index];
+        if (callback(value, index, collection)) {
+          result.push(value);
+        }
+      }
+    } else {
+      forOwn(collection, function(value, index, collection) {
+        if (callback(value, index, collection)) {
+          result.push(value);
+        }
+      });
+    }
+    return result;
+  }
+
+  /**
+   * Iterates over elements of a collection, returning the first element that
+   * the callback returns truey for. The callback is bound to `thisArg` and
+   * invoked with three arguments; (value, index|key, collection).
+   *
+   * If a property name is provided for `callback` the created "_.pluck" style
+   * callback will return the property value of the given element.
+   *
+   * If an object is provided for `callback` the created "_.where" style callback
+   * will return `true` for elements that have the properties of the given object,
+   * else `false`.
+   *
+   * @static
+   * @memberOf _
+   * @alias detect, findWhere
+   * @category Collections
+   * @param {Array|Object|string} collection The collection to iterate over.
+   * @param {Function|Object|string} [callback=identity] The function called
+   *  per iteration. If a property name or object is provided it will be used
+   *  to create a "_.pluck" or "_.where" style callback, respectively.
+   * @param {*} [thisArg] The `this` binding of `callback`.
+   * @returns {*} Returns the found element, else `undefined`.
+   * @example
+   *
+   * var characters = [
+   *   { 'name': 'barney',  'age': 36, 'blocked': false },
+   *   { 'name': 'fred',    'age': 40, 'blocked': true },
+   *   { 'name': 'pebbles', 'age': 1,  'blocked': false }
+   * ];
+   *
+   * _.find(characters, function(chr) {
+   *   return chr.age < 40;
+   * });
+   * // => { 'name': 'barney', 'age': 36, 'blocked': false }
+   *
+   * // using "_.where" callback shorthand
+   * _.find(characters, { 'age': 1 });
+   * // =>  { 'name': 'pebbles', 'age': 1, 'blocked': false }
+   *
+   * // using "_.pluck" callback shorthand
+   * _.find(characters, 'blocked');
+   * // => { 'name': 'fred', 'age': 40, 'blocked': true }
+   */
+  function find(collection, callback, thisArg) {
+    callback = createCallback(callback, thisArg, 3);
+
+    var index = -1,
+        length = collection ? collection.length : 0;
+
+    if (typeof length == 'number') {
+      while (++index < length) {
+        var value = collection[index];
+        if (callback(value, index, collection)) {
+          return value;
+        }
+      }
+    } else {
+      var result;
+      forOwn(collection, function(value, index, collection) {
+        if (callback(value, index, collection)) {
+          result = value;
+          return indicatorObject;
+        }
+      });
+      return result;
+    }
+  }
+
+  /**
+   * Examines each element in a `collection`, returning the first that
+   * has the given properties. When checking `properties`, this method
+   * performs a deep comparison between values to determine if they are
+   * equivalent to each other.
+   *
+   * @static
+   * @memberOf _
+   * @category Collections
+   * @param {Array|Object|string} collection The collection to iterate over.
+   * @param {Object} properties The object of property values to filter by.
+   * @returns {*} Returns the found element, else `undefined`.
+   * @example
+   *
+   * var food = [
+   *   { 'name': 'apple',  'organic': false, 'type': 'fruit' },
+   *   { 'name': 'banana', 'organic': true,  'type': 'fruit' },
+   *   { 'name': 'beet',   'organic': false, 'type': 'vegetable' }
+   * ];
+   *
+   * _.findWhere(food, { 'type': 'vegetable' });
+   * // => { 'name': 'beet', 'organic': false, 'type': 'vegetable' }
+   */
+  function findWhere(object, properties) {
+    return where(object, properties, true);
+  }
+
+  /**
+   * Iterates over elements of a collection, executing the callback for each
+   * element. The callback is bound to `thisArg` and invoked with three arguments;
+   * (value, index|key, collection). Callbacks may exit iteration early by
+   * explicitly returning `false`.
+   *
+   * Note: As with other "Collections" methods, objects with a `length` property
+   * are iterated like arrays. To avoid this behavior `_.forIn` or `_.forOwn`
+   * may be used for object iteration.
+   *
+   * @static
+   * @memberOf _
+   * @alias each
+   * @category Collections
+   * @param {Array|Object|string} collection The collection to iterate over.
+   * @param {Function} [callback=identity] The function called per iteration.
+   * @param {*} [thisArg] The `this` binding of `callback`.
+   * @returns {Array|Object|string} Returns `collection`.
+   * @example
+   *
+   * _([1, 2, 3]).forEach(function(num) { console.log(num); }).join(',');
+   * // => logs each number and returns '1,2,3'
+   *
+   * _.forEach({ 'one': 1, 'two': 2, 'three': 3 }, function(num) { console.log(num); });
+   * // => logs each number and returns the object (property order is not guaranteed across environments)
+   */
+  function forEach(collection, callback, thisArg) {
+    var index = -1,
+        length = collection ? collection.length : 0;
+
+    callback = callback && typeof thisArg == 'undefined' ? callback : baseCreateCallback(callback, thisArg, 3);
+    if (typeof length == 'number') {
+      while (++index < length) {
+        if (callback(collection[index], index, collection) === indicatorObject) {
+          break;
+        }
+      }
+    } else {
+      forOwn(collection, callback);
+    }
+  }
+
+  /**
+   * This method is like `_.forEach` except that it iterates over elements
+   * of a `collection` from right to left.
+   *
+   * @static
+   * @memberOf _
+   * @alias eachRight
+   * @category Collections
+   * @param {Array|Object|string} collection The collection to iterate over.
+   * @param {Function} [callback=identity] The function called per iteration.
+   * @param {*} [thisArg] The `this` binding of `callback`.
+   * @returns {Array|Object|string} Returns `collection`.
+   * @example
+   *
+   * _([1, 2, 3]).forEachRight(function(num) { console.log(num); }).join(',');
+   * // => logs each number from right to left and returns '3,2,1'
+   */
+  function forEachRight(collection, callback) {
+    var length = collection ? collection.length : 0;
+    if (typeof length == 'number') {
+      while (length--) {
+        if (callback(collection[length], length, collection) === false) {
+          break;
+        }
+      }
+    } else {
+      var props = keys(collection);
+      length = props.length;
+      forOwn(collection, function(value, key, collection) {
+        key = props ? props[--length] : --length;
+        return callback(collection[key], key, collection) === false && indicatorObject;
+      });
+    }
+  }
+
+  /**
+   * Creates an object composed of keys generated from the results of running
+   * each element of a collection through the callback. The corresponding value
+   * of each key is an array of the elements responsible for generating the key.
+   * The callback is bound to `thisArg` and invoked with three arguments;
+   * (value, index|key, collection).
+   *
+   * If a property name is provided for `callback` the created "_.pluck" style
+   * callback will return the property value of the given element.
+   *
+   * If an object is provided for `callback` the created "_.where" style callback
+   * will return `true` for elements that have the properties of the given object,
+   * else `false`
+   *
+   * @static
+   * @memberOf _
+   * @category Collections
+   * @param {Array|Object|string} collection The collection to iterate over.
+   * @param {Function|Object|string} [callback=identity] The function called
+   *  per iteration. If a property name or object is provided it will be used
+   *  to create a "_.pluck" or "_.where" style callback, respectively.
+   * @param {*} [thisArg] The `this` binding of `callback`.
+   * @returns {Object} Returns the composed aggregate object.
+   * @example
+   *
+   * _.groupBy([4.2, 6.1, 6.4], function(num) { return Math.floor(num); });
+   * // => { '4': [4.2], '6': [6.1, 6.4] }
+   *
+   * _.groupBy([4.2, 6.1, 6.4], function(num) { return this.floor(num); }, Math);
+   * // => { '4': [4.2], '6': [6.1, 6.4] }
+   *
+   * // using "_.pluck" callback shorthand
+   * _.groupBy(['one', 'two', 'three'], 'length');
+   * // => { '3': ['one', 'two'], '5': ['three'] }
+   */
+  var groupBy = createAggregator(function(result, value, key) {
+    (hasOwnProperty.call(result, key) ? result[key] : result[key] = []).push(value);
+  });
+
+  /**
+   * Creates an object composed of keys generated from the results of running
+   * each element of the collection through the given callback. The corresponding
+   * value of each key is the last element responsible for generating the key.
+   * The callback is bound to `thisArg` and invoked with three arguments;
+   * (value, index|key, collection).
+   *
+   * If a property name is provided for `callback` the created "_.pluck" style
+   * callback will return the property value of the given element.
+   *
+   * If an object is provided for `callback` the created "_.where" style callback
+   * will return `true` for elements that have the properties of the given object,
+   * else `false`.
+   *
+   * @static
+   * @memberOf _
+   * @category Collections
+   * @param {Array|Object|string} collection The collection to iterate over.
+   * @param {Function|Object|string} [callback=identity] The function called
+   *  per iteration. If a property name or object is provided it will be used
+   *  to create a "_.pluck" or "_.where" style callback, respectively.
+   * @param {*} [thisArg] The `this` binding of `callback`.
+   * @returns {Object} Returns the composed aggregate object.
+   * @example
+   *
+   * var keys = [
+   *   { 'dir': 'left', 'code': 97 },
+   *   { 'dir': 'right', 'code': 100 }
+   * ];
+   *
+   * _.indexBy(keys, 'dir');
+   * // => { 'left': { 'dir': 'left', 'code': 97 }, 'right': { 'dir': 'right', 'code': 100 } }
+   *
+   * _.indexBy(keys, function(key) { return String.fromCharCode(key.code); });
+   * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } }
+   *
+   * _.indexBy(characters, function(key) { this.fromCharCode(key.code); }, String);
+   * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } }
+   */
+  var indexBy = createAggregator(function(result, value, key) {
+    result[key] = value;
+  });
+
+  /**
+   * Invokes the method named by `methodName` on each element in the `collection`
+   * returning an array of the results of each invoked method. Additional arguments
+   * will be provided to each invoked method. If `methodName` is a function it
+   * will be invoked for, and `this` bound to, each element in the `collection`.
+   *
+   * @static
+   * @memberOf _
+   * @category Collections
+   * @param {Array|Object|string} collection The collection to iterate over.
+   * @param {Function|string} methodName The name of the method to invoke or
+   *  the function invoked per iteration.
+   * @param {...*} [arg] Arguments to invoke the method with.
+   * @returns {Array} Returns a new array of the results of each invoked method.
+   * @example
+   *
+   * _.invoke([[5, 1, 7], [3, 2, 1]], 'sort');
+   * // => [[1, 5, 7], [1, 2, 3]]
+   *
+   * _.invoke([123, 456], String.prototype.split, '');
+   * // => [['1', '2', '3'], ['4', '5', '6']]
+   */
+  function invoke(collection, methodName) {
+    var args = slice(arguments, 2),
+        index = -1,
+        isFunc = typeof methodName == 'function',
+        length = collection ? collection.length : 0,
+        result = Array(typeof length == 'number' ? length : 0);
+
+    forEach(collection, function(value) {
+      result[++index] = (isFunc ? methodName : value[methodName]).apply(value, args);
+    });
+    return result;
+  }
+
+  /**
+   * Creates an array of values by running each element in the collection
+   * through the callback. The callback is bound to `thisArg` and invoked with
+   * three arguments; (value, index|key, collection).
+   *
+   * If a property name is provided for `callback` the created "_.pluck" style
+   * callback will return the property value of the given element.
+   *
+   * If an object is provided for `callback` the created "_.where" style callback
+   * will return `true` for elements that have the properties of the given object,
+   * else `false`.
+   *
+   * @static
+   * @memberOf _
+   * @alias collect
+   * @category Collections
+   * @param {Array|Object|string} collection The collection to iterate over.
+   * @param {Function|Object|string} [callback=identity] The function called
+   *  per iteration. If a property name or object is provided it will be used
+   *  to create a "_.pluck" or "_.where" style callback, respectively.
+   * @param {*} [thisArg] The `this` binding of `callback`.
+   * @returns {Array} Returns a new array of the results of each `callback` execution.
+   * @example
+   *
+   * _.map([1, 2, 3], function(num) { return num * 3; });
+   * // => [3, 6, 9]
+   *
+   * _.map({ 'one': 1, 'two': 2, 'three': 3 }, function(num) { return num * 3; });
+   * // => [3, 6, 9] (property order is not guaranteed across environments)
+   *
+   * var characters = [
+   *   { 'name': 'barney', 'age': 36 },
+   *   { 'name': 'fred',   'age': 40 }
+   * ];
+   *
+   * // using "_.pluck" callback shorthand
+   * _.map(characters, 'name');
+   * // => ['barney', 'fred']
+   */
+  function map(collection, callback, thisArg) {
+    var index = -1,
+        length = collection ? collection.length : 0;
+
+    callback = createCallback(callback, thisArg, 3);
+    if (typeof length == 'number') {
+      var result = Array(length);
+      while (++index < length) {
+        result[index] = callback(collection[index], index, collection);
+      }
+    } else {
+      result = [];
+      forOwn(collection, function(value, key, collection) {
+        result[++index] = callback(value, key, collection);
+      });
+    }
+    return result;
+  }
+
+  /**
+   * Retrieves the maximum value of a collection. If the collection is empty or
+   * falsey `-Infinity` is returned. If a callback is provided it will be executed
+   * for each value in the collection to generate the criterion by which the value
+   * is ranked. The callback is bound to `thisArg` and invoked with three
+   * arguments; (value, index, collection).
+   *
+   * If a property name is provided for `callback` the created "_.pluck" style
+   * callback will return the property value of the given element.
+   *
+   * If an object is provided for `callback` the created "_.where" style callback
+   * will return `true` for elements that have the properties of the given object,
+   * else `false`.
+   *
+   * @static
+   * @memberOf _
+   * @category Collections
+   * @param {Array|Object|string} collection The collection to iterate over.
+   * @param {Function|Object|string} [callback=identity] The function called
+   *  per iteration. If a property name or object is provided it will be used
+   *  to create a "_.pluck" or "_.where" style callback, respectively.
+   * @param {*} [thisArg] The `this` binding of `callback`.
+   * @returns {*} Returns the maximum value.
+   * @example
+   *
+   * _.max([4, 2, 8, 6]);
+   * // => 8
+   *
+   * var characters = [
+   *   { 'name': 'barney', 'age': 36 },
+   *   { 'name': 'fred',   'age': 40 }
+   * ];
+   *
+   * _.max(characters, function(chr) { return chr.age; });
+   * // => { 'name': 'fred', 'age': 40 };
+   *
+   * // using "_.pluck" callback shorthand
+   * _.max(characters, 'age');
+   * // => { 'name': 'fred', 'age': 40 };
+   */
+  function max(collection, callback, thisArg) {
+    var computed = -Infinity,
+        result = computed;
+
+    // allows working with functions like `_.map` without using
+    // their `index` argument as a callback
+    if (typeof callback != 'function' && thisArg && thisArg[callback] === collection) {
+      callback = null;
+    }
+    var index = -1,
+        length = collection ? collection.length : 0;
+
+    if (callback == null && typeof length == 'number') {
+      while (++index < length) {
+        var value = collection[index];
+        if (value > result) {
+          result = value;
+        }
+      }
+    } else {
+      callback = createCallback(callback, thisArg, 3);
+
+      forEach(collection, function(value, index, collection) {
+        var current = callback(value, index, collection);
+        if (current > computed) {
+          computed = current;
+          result = value;
+        }
+      });
+    }
+    return result;
+  }
+
+  /**
+   * Retrieves the minimum value of a collection. If the collection is empty or
+   * falsey `Infinity` is returned. If a callback is provided it will be executed
+   * for each value in the collection to generate the criterion by which the value
+   * is ranked. The callback is bound to `thisArg` and invoked with three
+   * arguments; (value, index, collection).
+   *
+   * If a property name is provided for `callback` the created "_.pluck" style
+   * callback will return the property value of the given element.
+   *
+   * If an object is provided for `callback` the created "_.where" style callback
+   * will return `true` for elements that have the properties of the given object,
+   * else `false`.
+   *
+   * @static
+   * @memberOf _
+   * @category Collections
+   * @param {Array|Object|string} collection The collection to iterate over.
+   * @param {Function|Object|string} [callback=identity] The function called
+   *  per iteration. If a property name or object is provided it will be used
+   *  to create a "_.pluck" or "_.where" style callback, respectively.
+   * @param {*} [thisArg] The `this` binding of `callback`.
+   * @returns {*} Returns the minimum value.
+   * @example
+   *
+   * _.min([4, 2, 8, 6]);
+   * // => 2
+   *
+   * var characters = [
+   *   { 'name': 'barney', 'age': 36 },
+   *   { 'name': 'fred',   'age': 40 }
+   * ];
+   *
+   * _.min(characters, function(chr) { return chr.age; });
+   * // => { 'name': 'barney', 'age': 36 };
+   *
+   * // using "_.pluck" callback shorthand
+   * _.min(characters, 'age');
+   * // => { 'name': 'barney', 'age': 36 };
+   */
+  function min(collection, callback, thisArg) {
+    var computed = Infinity,
+        result = computed;
+
+    // allows working with functions like `_.map` without using
+    // their `index` argument as a callback
+    if (typeof callback != 'function' && thisArg && thisArg[callback] === collection) {
+      callback = null;
+    }
+    var index = -1,
+        length = collection ? collection.length : 0;
+
+    if (callback == null && typeof length == 'number') {
+      while (++index < length) {
+        var value = collection[index];
+        if (value < result) {
+          result = value;
+        }
+      }
+    } else {
+      callback = createCallback(callback, thisArg, 3);
+
+      forEach(collection, function(value, index, collection) {
+        var current = callback(value, index, collection);
+        if (current < computed) {
+          computed = current;
+          result = value;
+        }
+      });
+    }
+    return result;
+  }
+
+  /**
+   * Retrieves the value of a specified property from all elements in the collection.
+   *
+   * @static
+   * @memberOf _
+   * @type Function
+   * @category Collections
+   * @param {Array|Object|string} collection The collection to iterate over.
+   * @param {string} property The name of the property to pluck.
+   * @returns {Array} Returns a new array of property values.
+   * @example
+   *
+   * var characters = [
+   *   { 'name': 'barney', 'age': 36 },
+   *   { 'name': 'fred',   'age': 40 }
+   * ];
+   *
+   * _.pluck(characters, 'name');
+   * // => ['barney', 'fred']
+   */
+  var pluck = map;
+
+  /**
+   * Reduces a collection to a value which is the accumulated result of running
+   * each element in the collection through the callback, where each successive
+   * callback execution consumes the return value of the previous execution. If
+   * `accumulator` is not provided the first element of the collection will be
+   * used as the initial `accumulator` value. The callback is bound to `thisArg`
+   * and invoked with four arguments; (accumulator, value, index|key, collection).
+   *
+   * @static
+   * @memberOf _
+   * @alias foldl, inject
+   * @category Collections
+   * @param {Array|Object|string} collection The collection to iterate over.
+   * @param {Function} [callback=identity] The function called per iteration.
+   * @param {*} [accumulator] Initial value of the accumulator.
+   * @param {*} [thisArg] The `this` binding of `callback`.
+   * @returns {*} Returns the accumulated value.
+   * @example
+   *
+   * var sum = _.reduce([1, 2, 3], function(sum, num) {
+   *   return sum + num;
+   * });
+   * // => 6
+   *
+   * var mapped = _.reduce({ 'a': 1, 'b': 2, 'c': 3 }, function(result, num, key) {
+   *   result[key] = num * 3;
+   *   return result;
+   * }, {});
+   * // => { 'a': 3, 'b': 6, 'c': 9 }
+   */
+  function reduce(collection, callback, accumulator, thisArg) {
+    if (!collection) return accumulator;
+    var noaccum = arguments.length < 3;
+    callback = createCallback(callback, thisArg, 4);
+
+    var index = -1,
+        length = collection.length;
+
+    if (typeof length == 'number') {
+      if (noaccum) {
+        accumulator = collection[++index];
+      }
+      while (++index < length) {
+        accumulator = callback(accumulator, collection[index], index, collection);
+      }
+    } else {
+      forOwn(collection, function(value, index, collection) {
+        accumulator = noaccum
+          ? (noaccum = false, value)
+          : callback(accumulator, value, index, collection)
+      });
+    }
+    return accumulator;
+  }
+
+  /**
+   * This method is like `_.reduce` except that it iterates over elements
+   * of a `collection` from right to left.
+   *
+   * @static
+   * @memberOf _
+   * @alias foldr
+   * @category Collections
+   * @param {Array|Object|string} collection The collection to iterate over.
+   * @param {Function} [callback=identity] The function called per iteration.
+   * @param {*} [accumulator] Initial value of the accumulator.
+   * @param {*} [thisArg] The `this` binding of `callback`.
+   * @returns {*} Returns the accumulated value.
+   * @example
+   *
+   * var list = [[0, 1], [2, 3], [4, 5]];
+   * var flat = _.reduceRight(list, function(a, b) { return a.concat(b); }, []);
+   * // => [4, 5, 2, 3, 0, 1]
+   */
+  function reduceRight(collection, callback, accumulator, thisArg) {
+    var noaccum = arguments.length < 3;
+    callback = createCallback(callback, thisArg, 4);
+    forEachRight(collection, function(value, index, collection) {
+      accumulator = noaccum
+        ? (noaccum = false, value)
+        : callback(accumulator, value, index, collection);
+    });
+    return accumulator;
+  }
+
+  /**
+   * The opposite of `_.filter` this method returns the elements of a
+   * collection that the callback does **not** return truey for.
+   *
+   * If a property name is provided for `callback` the created "_.pluck" style
+   * callback will return the property value of the given element.
+   *
+   * If an object is provided for `callback` the created "_.where" style callback
+   * will return `true` for elements that have the properties of the given object,
+   * else `false`.
+   *
+   * @static
+   * @memberOf _
+   * @category Collections
+   * @param {Array|Object|string} collection The collection to iterate over.
+   * @param {Function|Object|string} [callback=identity] The function called
+   *  per iteration. If a property name or object is provided it will be used
+   *  to create a "_.pluck" or "_.where" style callback, respectively.
+   * @param {*} [thisArg] The `this` binding of `callback`.
+   * @returns {Array} Returns a new array of elements that failed the callback check.
+   * @example
+   *
+   * var odds = _.reject([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; });
+   * // => [1, 3, 5]
+   *
+   * var characters = [
+   *   { 'name': 'barney', 'age': 36, 'blocked': false },
+   *   { 'name': 'fred',   'age': 40, 'blocked': true }
+   * ];
+   *
+   * // using "_.pluck" callback shorthand
+   * _.reject(characters, 'blocked');
+   * // => [{ 'name': 'barney', 'age': 36, 'blocked': false }]
+   *
+   * // using "_.where" callback shorthand
+   * _.reject(characters, { 'age': 36 });
+   * // => [{ 'name': 'fred', 'age': 40, 'blocked': true }]
+   */
+  function reject(collection, callback, thisArg) {
+    callback = createCallback(callback, thisArg, 3);
+    return filter(collection, function(value, index, collection) {
+      return !callback(value, index, collection);
+    });
+  }
+
+  /**
+   * Retrieves a random element or `n` random elements from a collection.
+   *
+   * @static
+   * @memberOf _
+   * @category Collections
+   * @param {Array|Object|string} collection The collection to sample.
+   * @param {number} [n] The number of elements to sample.
+   * @param- {Object} [guard] Allows working with functions like `_.map`
+   *  without using their `index` arguments as `n`.
+   * @returns {Array} Returns the random sample(s) of `collection`.
+   * @example
+   *
+   * _.sample([1, 2, 3, 4]);
+   * // => 2
+   *
+   * _.sample([1, 2, 3, 4], 2);
+   * // => [3, 1]
+   */
+  function sample(collection, n, guard) {
+    if (collection && typeof collection.length != 'number') {
+      collection = values(collection);
+    }
+    if (n == null || guard) {
+      return collection ? collection[baseRandom(0, collection.length - 1)] : undefined;
+    }
+    var result = shuffle(collection);
+    result.length = nativeMin(nativeMax(0, n), result.length);
+    return result;
+  }
+
+  /**
+   * Creates an array of shuffled values, using a version of the Fisher-Yates
+   * shuffle. See http://en.wikipedia.org/wiki/Fisher-Yates_shuffle.
+   *
+   * @static
+   * @memberOf _
+   * @category Collections
+   * @param {Array|Object|string} collection The collection to shuffle.
+   * @returns {Array} Returns a new shuffled collection.
+   * @example
+   *
+   * _.shuffle([1, 2, 3, 4, 5, 6]);
+   * // => [4, 1, 6, 3, 5, 2]
+   */
+  function shuffle(collection) {
+    var index = -1,
+        length = collection ? collection.length : 0,
+        result = Array(typeof length == 'number' ? length : 0);
+
+    forEach(collection, function(value) {
+      var rand = baseRandom(0, ++index);
+      result[index] = result[rand];
+      result[rand] = value;
+    });
+    return result;
+  }
+
+  /**
+   * Gets the size of the `collection` by returning `collection.length` for arrays
+   * and array-like objects or the number of own enumerable properties for objects.
+   *
+   * @static
+   * @memberOf _
+   * @category Collections
+   * @param {Array|Object|string} collection The collection to inspect.
+   * @returns {number} Returns `collection.length` or number of own enumerable properties.
+   * @example
+   *
+   * _.size([1, 2]);
+   * // => 2
+   *
+   * _.size({ 'one': 1, 'two': 2, 'three': 3 });
+   * // => 3
+   *
+   * _.size('pebbles');
+   * // => 7
+   */
+  function size(collection) {
+    var length = collection ? collection.length : 0;
+    return typeof length == 'number' ? length : keys(collection).length;
+  }
+
+  /**
+   * Checks if the callback returns a truey value for **any** element of a
+   * collection. The function returns as soon as it finds a passing value and
+   * does not iterate over the entire collection. The callback is bound to
+   * `thisArg` and invoked with three arguments; (value, index|key, collection).
+   *
+   * If a property name is provided for `callback` the created "_.pluck" style
+   * callback will return the property value of the given element.
+   *
+   * If an object is provided for `callback` the created "_.where" style callback
+   * will return `true` for elements that have the properties of the given object,
+   * else `false`.
+   *
+   * @static
+   * @memberOf _
+   * @alias any
+   * @category Collections
+   * @param {Array|Object|string} collection The collection to iterate over.
+   * @param {Function|Object|string} [callback=identity] The function called
+   *  per iteration. If a property name or object is provided it will be used
+   *  to create a "_.pluck" or "_.where" style callback, respectively.
+   * @param {*} [thisArg] The `this` binding of `callback`.
+   * @returns {boolean} Returns `true` if any element passed the callback check,
+   *  else `false`.
+   * @example
+   *
+   * _.some([null, 0, 'yes', false], Boolean);
+   * // => true
+   *
+   * var characters = [
+   *   { 'name': 'barney', 'age': 36, 'blocked': false },
+   *   { 'name': 'fred',   'age': 40, 'blocked': true }
+   * ];
+   *
+   * // using "_.pluck" callback shorthand
+   * _.some(characters, 'blocked');
+   * // => true
+   *
+   * // using "_.where" callback shorthand
+   * _.some(characters, { 'age': 1 });
+   * // => false
+   */
+  function some(collection, callback, thisArg) {
+    var result;
+    callback = createCallback(callback, thisArg, 3);
+
+    var index = -1,
+        length = collection ? collection.length : 0;
+
+    if (typeof length == 'number') {
+      while (++index < length) {
+        if ((result = callback(collection[index], index, collection))) {
+          break;
+        }
+      }
+    } else {
+      forOwn(collection, function(value, index, collection) {
+        return (result = callback(value, index, collection)) && indicatorObject;
+      });
+    }
+    return !!result;
+  }
+
+  /**
+   * Creates an array of elements, sorted in ascending order by the results of
+   * running each element in a collection through the callback. This method
+   * performs a stable sort, that is, it will preserve the original sort order
+   * of equal elements. The callback is bound to `thisArg` and invoked with
+   * three arguments; (value, index|key, collection).
+   *
+   * If a property name is provided for `callback` the created "_.pluck" style
+   * callback will return the property value of the given element.
+   *
+   * If an array of property names is provided for `callback` the collection
+   * will be sorted by each property value.
+   *
+   * If an object is provided for `callback` the created "_.where" style callback
+   * will return `true` for elements that have the properties of the given object,
+   * else `false`.
+   *
+   * @static
+   * @memberOf _
+   * @category Collections
+   * @param {Array|Object|string} collection The collection to iterate over.
+   * @param {Array|Function|Object|string} [callback=identity] The function called
+   *  per iteration. If a property name or object is provided it will be used
+   *  to create a "_.pluck" or "_.where" style callback, respectively.
+   * @param {*} [thisArg] The `this` binding of `callback`.
+   * @returns {Array} Returns a new array of sorted elements.
+   * @example
+   *
+   * _.sortBy([1, 2, 3], function(num) { return Math.sin(num); });
+   * // => [3, 1, 2]
+   *
+   * _.sortBy([1, 2, 3], function(num) { return this.sin(num); }, Math);
+   * // => [3, 1, 2]
+   *
+   * var characters = [
+   *   { 'name': 'barney',  'age': 36 },
+   *   { 'name': 'fred',    'age': 40 },
+   *   { 'name': 'barney',  'age': 26 },
+   *   { 'name': 'fred',    'age': 30 }
+   * ];
+   *
+   * // using "_.pluck" callback shorthand
+   * _.map(_.sortBy(characters, 'age'), _.values);
+   * // => [['barney', 26], ['fred', 30], ['barney', 36], ['fred', 40]]
+   *
+   * // sorting by multiple properties
+   * _.map(_.sortBy(characters, ['name', 'age']), _.values);
+   * // = > [['barney', 26], ['barney', 36], ['fred', 30], ['fred', 40]]
+   */
+  function sortBy(collection, callback, thisArg) {
+    var index = -1,
+        length = collection ? collection.length : 0,
+        result = Array(typeof length == 'number' ? length : 0);
+
+    callback = createCallback(callback, thisArg, 3);
+    forEach(collection, function(value, key, collection) {
+      result[++index] = {
+        'criteria': [callback(value, key, collection)],
+        'index': index,
+        'value': value
+      };
+    });
+
+    length = result.length;
+    result.sort(compareAscending);
+    while (length--) {
+      result[length] = result[length].value;
+    }
+    return result;
+  }
+
+  /**
+   * Converts the `collection` to an array.
+   *
+   * @static
+   * @memberOf _
+   * @category Collections
+   * @param {Array|Object|string} collection The collection to convert.
+   * @returns {Array} Returns the new converted array.
+   * @example
+   *
+   * (function() { return _.toArray(arguments).slice(1); })(1, 2, 3, 4);
+   * // => [2, 3, 4]
+   */
+  function toArray(collection) {
+    if (isArray(collection)) {
+      return slice(collection);
+    }
+    if (collection && typeof collection.length == 'number') {
+      return map(collection);
+    }
+    return values(collection);
+  }
+
+  /**
+   * Performs a deep comparison of each element in a `collection` to the given
+   * `properties` object, returning an array of all elements that have equivalent
+   * property values.
+   *
+   * @static
+   * @memberOf _
+   * @type Function
+   * @category Collections
+   * @param {Array|Object|string} collection The collection to iterate over.
+   * @param {Object} props The object of property values to filter by.
+   * @returns {Array} Returns a new array of elements that have the given properties.
+   * @example
+   *
+   * var characters = [
+   *   { 'name': 'barney', 'age': 36, 'pets': ['hoppy'] },
+   *   { 'name': 'fred',   'age': 40, 'pets': ['baby puss', 'dino'] }
+   * ];
+   *
+   * _.where(characters, { 'age': 36 });
+   * // => [{ 'name': 'barney', 'age': 36, 'pets': ['hoppy'] }]
+   *
+   * _.where(characters, { 'pets': ['dino'] });
+   * // => [{ 'name': 'fred', 'age': 40, 'pets': ['baby puss', 'dino'] }]
+   */
+  function where(collection, properties, first) {
+    return (first && isEmpty(properties))
+      ? undefined
+      : (first ? find : filter)(collection, properties);
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Creates an array with all falsey values removed. The values `false`, `null`,
+   * `0`, `""`, `undefined`, and `NaN` are all falsey.
+   *
+   * @static
+   * @memberOf _
+   * @category Arrays
+   * @param {Array} array The array to compact.
+   * @returns {Array} Returns a new array of filtered values.
+   * @example
+   *
+   * _.compact([0, 1, false, 2, '', 3]);
+   * // => [1, 2, 3]
+   */
+  function compact(array) {
+    var index = -1,
+        length = array ? array.length : 0,
+        result = [];
+
+    while (++index < length) {
+      var value = array[index];
+      if (value) {
+        result.push(value);
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Creates an array excluding all values of the provided arrays using strict
+   * equality for comparisons, i.e. `===`.
+   *
+   * @static
+   * @memberOf _
+   * @category Arrays
+   * @param {Array} array The array to process.
+   * @param {...Array} [values] The arrays of values to exclude.
+   * @returns {Array} Returns a new array of filtered values.
+   * @example
+   *
+   * _.difference([1, 2, 3, 4, 5], [5, 2, 10]);
+   * // => [1, 3, 4]
+   */
+  function difference(array) {
+    return baseDifference(array, baseFlatten(arguments, true, true, 1));
+  }
+
+  /**
+   * Gets the first element or first `n` elements of an array. If a callback
+   * is provided elements at the beginning of the array are returned as long
+   * as the callback returns truey. The callback is bound to `thisArg` and
+   * invoked with three arguments; (value, index, array).
+   *
+   * If a property name is provided for `callback` the created "_.pluck" style
+   * callback will return the property value of the given element.
+   *
+   * If an object is provided for `callback` the created "_.where" style callback
+   * will return `true` for elements that have the properties of the given object,
+   * else `false`.
+   *
+   * @static
+   * @memberOf _
+   * @alias head, take
+   * @category Arrays
+   * @param {Array} array The array to query.
+   * @param {Function|Object|number|string} [callback] The function called
+   *  per element or the number of elements to return. If a property name or
+   *  object is provided it will be used to create a "_.pluck" or "_.where"
+   *  style callback, respectively.
+   * @param {*} [thisArg] The `this` binding of `callback`.
+   * @returns {*} Returns the first element(s) of `array`.
+   * @example
+   *
+   * _.first([1, 2, 3]);
+   * // => 1
+   *
+   * _.first([1, 2, 3], 2);
+   * // => [1, 2]
+   *
+   * _.first([1, 2, 3], function(num) {
+   *   return num < 3;
+   * });
+   * // => [1, 2]
+   *
+   * var characters = [
+   *   { 'name': 'barney',  'blocked': true,  'employer': 'slate' },
+   *   { 'name': 'fred',    'blocked': false, 'employer': 'slate' },
+   *   { 'name': 'pebbles', 'blocked': true,  'employer': 'na' }
+   * ];
+   *
+   * // using "_.pluck" callback shorthand
+   * _.first(characters, 'blocked');
+   * // => [{ 'name': 'barney', 'blocked': true, 'employer': 'slate' }]
+   *
+   * // using "_.where" callback shorthand
+   * _.pluck(_.first(characters, { 'employer': 'slate' }), 'name');
+   * // => ['barney', 'fred']
+   */
+  function first(array, callback, thisArg) {
+    var n = 0,
+        length = array ? array.length : 0;
+
+    if (typeof callback != 'number' && callback != null) {
+      var index = -1;
+      callback = createCallback(callback, thisArg, 3);
+      while (++index < length && callback(array[index], index, array)) {
+        n++;
+      }
+    } else {
+      n = callback;
+      if (n == null || thisArg) {
+        return array ? array[0] : undefined;
+      }
+    }
+    return slice(array, 0, nativeMin(nativeMax(0, n), length));
+  }
+
+  /**
+   * Flattens a nested array (the nesting can be to any depth). If `isShallow`
+   * is truey, the array will only be flattened a single level. If a callback
+   * is provided each element of the array is passed through the callback before
+   * flattening. The callback is bound to `thisArg` and invoked with three
+   * arguments; (value, index, array).
+   *
+   * If a property name is provided for `callback` the created "_.pluck" style
+   * callback will return the property value of the given element.
+   *
+   * If an object is provided for `callback` the created "_.where" style callback
+   * will return `true` for elements that have the properties of the given object,
+   * else `false`.
+   *
+   * @static
+   * @memberOf _
+   * @category Arrays
+   * @param {Array} array The array to flatten.
+   * @param {boolean} [isShallow=false] A flag to restrict flattening to a single level.
+   * @param {Function|Object|string} [callback=identity] The function called
+   *  per iteration. If a property name or object is provided it will be used
+   *  to create a "_.pluck" or "_.where" style callback, respectively.
+   * @param {*} [thisArg] The `this` binding of `callback`.
+   * @returns {Array} Returns a new flattened array.
+   * @example
+   *
+   * _.flatten([1, [2], [3, [[4]]]]);
+   * // => [1, 2, 3, 4];
+   *
+   * _.flatten([1, [2], [3, [[4]]]], true);
+   * // => [1, 2, 3, [[4]]];
+   *
+   * var characters = [
+   *   { 'name': 'barney', 'age': 30, 'pets': ['hoppy'] },
+   *   { 'name': 'fred',   'age': 40, 'pets': ['baby puss', 'dino'] }
+   * ];
+   *
+   * // using "_.pluck" callback shorthand
+   * _.flatten(characters, 'pets');
+   * // => ['hoppy', 'baby puss', 'dino']
+   */
+  function flatten(array, isShallow) {
+    return baseFlatten(array, isShallow);
+  }
+
+  /**
+   * Gets the index at which the first occurrence of `value` is found using
+   * strict equality for comparisons, i.e. `===`. If the array is already sorted
+   * providing `true` for `fromIndex` will run a faster binary search.
+   *
+   * @static
+   * @memberOf _
+   * @category Arrays
+   * @param {Array} array The array to search.
+   * @param {*} value The value to search for.
+   * @param {boolean|number} [fromIndex=0] The index to search from or `true`
+   *  to perform a binary search on a sorted array.
+   * @returns {number} Returns the index of the matched value or `-1`.
+   * @example
+   *
+   * _.indexOf([1, 2, 3, 1, 2, 3], 2);
+   * // => 1
+   *
+   * _.indexOf([1, 2, 3, 1, 2, 3], 2, 3);
+   * // => 4
+   *
+   * _.indexOf([1, 1, 2, 2, 3, 3], 2, true);
+   * // => 2
+   */
+  function indexOf(array, value, fromIndex) {
+    if (typeof fromIndex == 'number') {
+      var length = array ? array.length : 0;
+      fromIndex = (fromIndex < 0 ? nativeMax(0, length + fromIndex) : fromIndex || 0);
+    } else if (fromIndex) {
+      var index = sortedIndex(array, value);
+      return array[index] === value ? index : -1;
+    }
+    return baseIndexOf(array, value, fromIndex);
+  }
+
+  /**
+   * Gets all but the last element or last `n` elements of an array. If a
+   * callback is provided elements at the end of the array are excluded from
+   * the result as long as the callback returns truey. The callback is bound
+   * to `thisArg` and invoked with three arguments; (value, index, array).
+   *
+   * If a property name is provided for `callback` the created "_.pluck" style
+   * callback will return the property value of the given element.
+   *
+   * If an object is provided for `callback` the created "_.where" style callback
+   * will return `true` for elements that have the properties of the given object,
+   * else `false`.
+   *
+   * @static
+   * @memberOf _
+   * @category Arrays
+   * @param {Array} array The array to query.
+   * @param {Function|Object|number|string} [callback=1] The function called
+   *  per element or the number of elements to exclude. If a property name or
+   *  object is provided it will be used to create a "_.pluck" or "_.where"
+   *  style callback, respectively.
+   * @param {*} [thisArg] The `this` binding of `callback`.
+   * @returns {Array} Returns a slice of `array`.
+   * @example
+   *
+   * _.initial([1, 2, 3]);
+   * // => [1, 2]
+   *
+   * _.initial([1, 2, 3], 2);
+   * // => [1]
+   *
+   * _.initial([1, 2, 3], function(num) {
+   *   return num > 1;
+   * });
+   * // => [1]
+   *
+   * var characters = [
+   *   { 'name': 'barney',  'blocked': false, 'employer': 'slate' },
+   *   { 'name': 'fred',    'blocked': true,  'employer': 'slate' },
+   *   { 'name': 'pebbles', 'blocked': true,  'employer': 'na' }
+   * ];
+   *
+   * // using "_.pluck" callback shorthand
+   * _.initial(characters, 'blocked');
+   * // => [{ 'name': 'barney',  'blocked': false, 'employer': 'slate' }]
+   *
+   * // using "_.where" callback shorthand
+   * _.pluck(_.initial(characters, { 'employer': 'na' }), 'name');
+   * // => ['barney', 'fred']
+   */
+  function initial(array, callback, thisArg) {
+    var n = 0,
+        length = array ? array.length : 0;
+
+    if (typeof callback != 'number' && callback != null) {
+      var index = length;
+      callback = createCallback(callback, thisArg, 3);
+      while (index-- && callback(array[index], index, array)) {
+        n++;
+      }
+    } else {
+      n = (callback == null || thisArg) ? 1 : callback || n;
+    }
+    return slice(array, 0, nativeMin(nativeMax(0, length - n), length));
+  }
+
+  /**
+   * Creates an array of unique values present in all provided arrays using
+   * strict equality for comparisons, i.e. `===`.
+   *
+   * @static
+   * @memberOf _
+   * @category Arrays
+   * @param {...Array} [array] The arrays to inspect.
+   * @returns {Array} Returns an array of shared values.
+   * @example
+   *
+   * _.intersection([1, 2, 3], [5, 2, 1, 4], [2, 1]);
+   * // => [1, 2]
+   */
+  function intersection() {
+    var args = [],
+        argsIndex = -1,
+        argsLength = arguments.length;
+
+    while (++argsIndex < argsLength) {
+      var value = arguments[argsIndex];
+       if (isArray(value) || isArguments(value)) {
+         args.push(value);
+       }
+    }
+    var array = args[0],
+        index = -1,
+        indexOf = getIndexOf(),
+        length = array ? array.length : 0,
+        result = [];
+
+    outer:
+    while (++index < length) {
+      value = array[index];
+      if (indexOf(result, value) < 0) {
+        var argsIndex = argsLength;
+        while (--argsIndex) {
+          if (indexOf(args[argsIndex], value) < 0) {
+            continue outer;
+          }
+        }
+        result.push(value);
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Gets the last element or last `n` elements of an array. If a callback is
+   * provided elements at the end of the array are returned as long as the
+   * callback returns truey. The callback is bound to `thisArg` and invoked
+   * with three arguments; (value, index, array).
+   *
+   * If a property name is provided for `callback` the created "_.pluck" style
+   * callback will return the property value of the given element.
+   *
+   * If an object is provided for `callback` the created "_.where" style callback
+   * will return `true` for elements that have the properties of the given object,
+   * else `false`.
+   *
+   * @static
+   * @memberOf _
+   * @category Arrays
+   * @param {Array} array The array to query.
+   * @param {Function|Object|number|string} [callback] The function called
+   *  per element or the number of elements to return. If a property name or
+   *  object is provided it will be used to create a "_.pluck" or "_.where"
+   *  style callback, respectively.
+   * @param {*} [thisArg] The `this` binding of `callback`.
+   * @returns {*} Returns the last element(s) of `array`.
+   * @example
+   *
+   * _.last([1, 2, 3]);
+   * // => 3
+   *
+   * _.last([1, 2, 3], 2);
+   * // => [2, 3]
+   *
+   * _.last([1, 2, 3], function(num) {
+   *   return num > 1;
+   * });
+   * // => [2, 3]
+   *
+   * var characters = [
+   *   { 'name': 'barney',  'blocked': false, 'employer': 'slate' },
+   *   { 'name': 'fred',    'blocked': true,  'employer': 'slate' },
+   *   { 'name': 'pebbles', 'blocked': true,  'employer': 'na' }
+   * ];
+   *
+   * // using "_.pluck" callback shorthand
+   * _.pluck(_.last(characters, 'blocked'), 'name');
+   * // => ['fred', 'pebbles']
+   *
+   * // using "_.where" callback shorthand
+   * _.last(characters, { 'employer': 'na' });
+   * // => [{ 'name': 'pebbles', 'blocked': true, 'employer': 'na' }]
+   */
+  function last(array, callback, thisArg) {
+    var n = 0,
+        length = array ? array.length : 0;
+
+    if (typeof callback != 'number' && callback != null) {
+      var index = length;
+      callback = createCallback(callback, thisArg, 3);
+      while (index-- && callback(array[index], index, array)) {
+        n++;
+      }
+    } else {
+      n = callback;
+      if (n == null || thisArg) {
+        return array ? array[length - 1] : undefined;
+      }
+    }
+    return slice(array, nativeMax(0, length - n));
+  }
+
+  /**
+   * Gets the index at which the last occurrence of `value` is found using strict
+   * equality for comparisons, i.e. `===`. If `fromIndex` is negative, it is used
+   * as the offset from the end of the collection.
+   *
+   * If a property name is provided for `callback` the created "_.pluck" style
+   * callback will return the property value of the given element.
+   *
+   * If an object is provided for `callback` the created "_.where" style callback
+   * will return `true` for elements that have the properties of the given object,
+   * else `false`.
+   *
+   * @static
+   * @memberOf _
+   * @category Arrays
+   * @param {Array} array The array to search.
+   * @param {*} value The value to search for.
+   * @param {number} [fromIndex=array.length-1] The index to search from.
+   * @returns {number} Returns the index of the matched value or `-1`.
+   * @example
+   *
+   * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2);
+   * // => 4
+   *
+   * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2, 3);
+   * // => 1
+   */
+  function lastIndexOf(array, value, fromIndex) {
+    var index = array ? array.length : 0;
+    if (typeof fromIndex == 'number') {
+      index = (fromIndex < 0 ? nativeMax(0, index + fromIndex) : nativeMin(fromIndex, index - 1)) + 1;
+    }
+    while (index--) {
+      if (array[index] === value) {
+        return index;
+      }
+    }
+    return -1;
+  }
+
+  /**
+   * Creates an array of numbers (positive and/or negative) progressing from
+   * `start` up to but not including `end`. If `start` is less than `stop` a
+   * zero-length range is created unless a negative `step` is specified.
+   *
+   * @static
+   * @memberOf _
+   * @category Arrays
+   * @param {number} [start=0] The start of the range.
+   * @param {number} end The end of the range.
+   * @param {number} [step=1] The value to increment or decrement by.
+   * @returns {Array} Returns a new range array.
+   * @example
+   *
+   * _.range(4);
+   * // => [0, 1, 2, 3]
+   *
+   * _.range(1, 5);
+   * // => [1, 2, 3, 4]
+   *
+   * _.range(0, 20, 5);
+   * // => [0, 5, 10, 15]
+   *
+   * _.range(0, -4, -1);
+   * // => [0, -1, -2, -3]
+   *
+   * _.range(1, 4, 0);
+   * // => [1, 1, 1]
+   *
+   * _.range(0);
+   * // => []
+   */
+  function range(start, end, step) {
+    start = +start || 0;
+    step =  (+step || 1);
+
+    if (end == null) {
+      end = start;
+      start = 0;
+    }
+    // use `Array(length)` so engines like Chakra and V8 avoid slower modes
+    // http://youtu.be/XAqIpGU8ZZk#t=17m25s
+    var index = -1,
+        length = nativeMax(0, ceil((end - start) / step)),
+        result = Array(length);
+
+    while (++index < length) {
+      result[index] = start;
+      start += step;
+    }
+    return result;
+  }
+
+  /**
+   * The opposite of `_.initial` this method gets all but the first element or
+   * first `n` elements of an array. If a callback function is provided elements
+   * at the beginning of the array are excluded from the result as long as the
+   * callback returns truey. The callback is bound to `thisArg` and invoked
+   * with three arguments; (value, index, array).
+   *
+   * If a property name is provided for `callback` the created "_.pluck" style
+   * callback will return the property value of the given element.
+   *
+   * If an object is provided for `callback` the created "_.where" style callback
+   * will return `true` for elements that have the properties of the given object,
+   * else `false`.
+   *
+   * @static
+   * @memberOf _
+   * @alias drop, tail
+   * @category Arrays
+   * @param {Array} array The array to query.
+   * @param {Function|Object|number|string} [callback=1] The function called
+   *  per element or the number of elements to exclude. If a property name or
+   *  object is provided it will be used to create a "_.pluck" or "_.where"
+   *  style callback, respectively.
+   * @param {*} [thisArg] The `this` binding of `callback`.
+   * @returns {Array} Returns a slice of `array`.
+   * @example
+   *
+   * _.rest([1, 2, 3]);
+   * // => [2, 3]
+   *
+   * _.rest([1, 2, 3], 2);
+   * // => [3]
+   *
+   * _.rest([1, 2, 3], function(num) {
+   *   return num < 3;
+   * });
+   * // => [3]
+   *
+   * var characters = [
+   *   { 'name': 'barney',  'blocked': true,  'employer': 'slate' },
+   *   { 'name': 'fred',    'blocked': false,  'employer': 'slate' },
+   *   { 'name': 'pebbles', 'blocked': true, 'employer': 'na' }
+   * ];
+   *
+   * // using "_.pluck" callback shorthand
+   * _.pluck(_.rest(characters, 'blocked'), 'name');
+   * // => ['fred', 'pebbles']
+   *
+   * // using "_.where" callback shorthand
+   * _.rest(characters, { 'employer': 'slate' });
+   * // => [{ 'name': 'pebbles', 'blocked': true, 'employer': 'na' }]
+   */
+  function rest(array, callback, thisArg) {
+    if (typeof callback != 'number' && callback != null) {
+      var n = 0,
+          index = -1,
+          length = array ? array.length : 0;
+
+      callback = createCallback(callback, thisArg, 3);
+      while (++index < length && callback(array[index], index, array)) {
+        n++;
+      }
+    } else {
+      n = (callback == null || thisArg) ? 1 : nativeMax(0, callback);
+    }
+    return slice(array, n);
+  }
+
+  /**
+   * Uses a binary search to determine the smallest index at which a value
+   * should be inserted into a given sorted array in order to maintain the sort
+   * order of the array. If a callback is provided it will be executed for
+   * `value` and each element of `array` to compute their sort ranking. The
+   * callback is bound to `thisArg` and invoked with one argument; (value).
+   *
+   * If a property name is provided for `callback` the created "_.pluck" style
+   * callback will return the property value of the given element.
+   *
+   * If an object is provided for `callback` the created "_.where" style callback
+   * will return `true` for elements that have the properties of the given object,
+   * else `false`.
+   *
+   * @static
+   * @memberOf _
+   * @category Arrays
+   * @param {Array} array The array to inspect.
+   * @param {*} value The value to evaluate.
+   * @param {Function|Object|string} [callback=identity] The function called
+   *  per iteration. If a property name or object is provided it will be used
+   *  to create a "_.pluck" or "_.where" style callback, respectively.
+   * @param {*} [thisArg] The `this` binding of `callback`.
+   * @returns {number} Returns the index at which `value` should be inserted
+   *  into `array`.
+   * @example
+   *
+   * _.sortedIndex([20, 30, 50], 40);
+   * // => 2
+   *
+   * // using "_.pluck" callback shorthand
+   * _.sortedIndex([{ 'x': 20 }, { 'x': 30 }, { 'x': 50 }], { 'x': 40 }, 'x');
+   * // => 2
+   *
+   * var dict = {
+   *   'wordToNumber': { 'twenty': 20, 'thirty': 30, 'fourty': 40, 'fifty': 50 }
+   * };
+   *
+   * _.sortedIndex(['twenty', 'thirty', 'fifty'], 'fourty', function(word) {
+   *   return dict.wordToNumber[word];
+   * });
+   * // => 2
+   *
+   * _.sortedIndex(['twenty', 'thirty', 'fifty'], 'fourty', function(word) {
+   *   return this.wordToNumber[word];
+   * }, dict);
+   * // => 2
+   */
+  function sortedIndex(array, value, callback, thisArg) {
+    var low = 0,
+        high = array ? array.length : low;
+
+    // explicitly reference `identity` for better inlining in Firefox
+    callback = callback ? createCallback(callback, thisArg, 1) : identity;
+    value = callback(value);
+
+    while (low < high) {
+      var mid = (low + high) >>> 1;
+      (callback(array[mid]) < value)
+        ? low = mid + 1
+        : high = mid;
+    }
+    return low;
+  }
+
+  /**
+   * Creates an array of unique values, in order, of the provided arrays using
+   * strict equality for comparisons, i.e. `===`.
+   *
+   * @static
+   * @memberOf _
+   * @category Arrays
+   * @param {...Array} [array] The arrays to inspect.
+   * @returns {Array} Returns an array of combined values.
+   * @example
+   *
+   * _.union([1, 2, 3], [5, 2, 1, 4], [2, 1]);
+   * // => [1, 2, 3, 5, 4]
+   */
+  function union() {
+    return baseUniq(baseFlatten(arguments, true, true));
+  }
+
+  /**
+   * Creates a duplicate-value-free version of an array using strict equality
+   * for comparisons, i.e. `===`. If the array is sorted, providing
+   * `true` for `isSorted` will use a faster algorithm. If a callback is provided
+   * each element of `array` is passed through the callback before uniqueness
+   * is computed. The callback is bound to `thisArg` and invoked with three
+   * arguments; (value, index, array).
+   *
+   * If a property name is provided for `callback` the created "_.pluck" style
+   * callback will return the property value of the given element.
+   *
+   * If an object is provided for `callback` the created "_.where" style callback
+   * will return `true` for elements that have the properties of the given object,
+   * else `false`.
+   *
+   * @static
+   * @memberOf _
+   * @alias unique
+   * @category Arrays
+   * @param {Array} array The array to process.
+   * @param {boolean} [isSorted=false] A flag to indicate that `array` is sorted.
+   * @param {Function|Object|string} [callback=identity] The function called
+   *  per iteration. If a property name or object is provided it will be used
+   *  to create a "_.pluck" or "_.where" style callback, respectively.
+   * @param {*} [thisArg] The `this` binding of `callback`.
+   * @returns {Array} Returns a duplicate-value-free array.
+   * @example
+   *
+   * _.uniq([1, 2, 1, 3, 1]);
+   * // => [1, 2, 3]
+   *
+   * _.uniq([1, 1, 2, 2, 3], true);
+   * // => [1, 2, 3]
+   *
+   * _.uniq(['A', 'b', 'C', 'a', 'B', 'c'], function(letter) { return letter.toLowerCase(); });
+   * // => ['A', 'b', 'C']
+   *
+   * _.uniq([1, 2.5, 3, 1.5, 2, 3.5], function(num) { return this.floor(num); }, Math);
+   * // => [1, 2.5, 3]
+   *
+   * // using "_.pluck" callback shorthand
+   * _.uniq([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x');
+   * // => [{ 'x': 1 }, { 'x': 2 }]
+   */
+  function uniq(array, isSorted, callback, thisArg) {
+    // juggle arguments
+    if (typeof isSorted != 'boolean' && isSorted != null) {
+      thisArg = callback;
+      callback = (typeof isSorted != 'function' && thisArg && thisArg[isSorted] === array) ? null : isSorted;
+      isSorted = false;
+    }
+    if (callback != null) {
+      callback = createCallback(callback, thisArg, 3);
+    }
+    return baseUniq(array, isSorted, callback);
+  }
+
+  /**
+   * Creates an array excluding all provided values using strict equality for
+   * comparisons, i.e. `===`.
+   *
+   * @static
+   * @memberOf _
+   * @category Arrays
+   * @param {Array} array The array to filter.
+   * @param {...*} [value] The values to exclude.
+   * @returns {Array} Returns a new array of filtered values.
+   * @example
+   *
+   * _.without([1, 2, 1, 0, 3, 1, 4], 0, 1);
+   * // => [2, 3, 4]
+   */
+  function without(array) {
+    return baseDifference(array, slice(arguments, 1));
+  }
+
+  /**
+   * Creates an array of grouped elements, the first of which contains the first
+   * elements of the given arrays, the second of which contains the second
+   * elements of the given arrays, and so on.
+   *
+   * @static
+   * @memberOf _
+   * @alias unzip
+   * @category Arrays
+   * @param {...Array} [array] Arrays to process.
+   * @returns {Array} Returns a new array of grouped elements.
+   * @example
+   *
+   * _.zip(['fred', 'barney'], [30, 40], [true, false]);
+   * // => [['fred', 30, true], ['barney', 40, false]]
+   */
+  function zip() {
+    var index = -1,
+        length = max(pluck(arguments, 'length')),
+        result = Array(length < 0 ? 0 : length);
+
+    while (++index < length) {
+      result[index] = pluck(arguments, index);
+    }
+    return result;
+  }
+
+  /**
+   * Creates an object composed from arrays of `keys` and `values`. Provide
+   * either a single two dimensional array, i.e. `[[key1, value1], [key2, value2]]`
+   * or two arrays, one of `keys` and one of corresponding `values`.
+   *
+   * @static
+   * @memberOf _
+   * @alias object
+   * @category Arrays
+   * @param {Array} keys The array of keys.
+   * @param {Array} [values=[]] The array of values.
+   * @returns {Object} Returns an object composed of the given keys and
+   *  corresponding values.
+   * @example
+   *
+   * _.zipObject(['fred', 'barney'], [30, 40]);
+   * // => { 'fred': 30, 'barney': 40 }
+   */
+  function zipObject(keys, values) {
+    var index = -1,
+        length = keys ? keys.length : 0,
+        result = {};
+
+    if (!values && length && !isArray(keys[0])) {
+      values = [];
+    }
+    while (++index < length) {
+      var key = keys[index];
+      if (values) {
+        result[key] = values[index];
+      } else if (key) {
+        result[key[0]] = key[1];
+      }
+    }
+    return result;
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Creates a function that executes `func`, with  the `this` binding and
+   * arguments of the created function, only after being called `n` times.
+   *
+   * @static
+   * @memberOf _
+   * @category Functions
+   * @param {number} n The number of times the function must be called before
+   *  `func` is executed.
+   * @param {Function} func The function to restrict.
+   * @returns {Function} Returns the new restricted function.
+   * @example
+   *
+   * var saves = ['profile', 'settings'];
+   *
+   * var done = _.after(saves.length, function() {
+   *   console.log('Done saving!');
+   * });
+   *
+   * _.forEach(saves, function(type) {
+   *   asyncSave({ 'type': type, 'complete': done });
+   * });
+   * // => logs 'Done saving!', after all saves have completed
+   */
+  function after(n, func) {
+    if (!isFunction(func)) {
+      throw new TypeError;
+    }
+    return function() {
+      if (--n < 1) {
+        return func.apply(this, arguments);
+      }
+    };
+  }
+
+  /**
+   * Creates a function that, when called, invokes `func` with the `this`
+   * binding of `thisArg` and prepends any additional `bind` arguments to those
+   * provided to the bound function.
+   *
+   * @static
+   * @memberOf _
+   * @category Functions
+   * @param {Function} func The function to bind.
+   * @param {*} [thisArg] The `this` binding of `func`.
+   * @param {...*} [arg] Arguments to be partially applied.
+   * @returns {Function} Returns the new bound function.
+   * @example
+   *
+   * var func = function(greeting) {
+   *   return greeting + ' ' + this.name;
+   * };
+   *
+   * func = _.bind(func, { 'name': 'fred' }, 'hi');
+   * func();
+   * // => 'hi fred'
+   */
+  function bind(func, thisArg) {
+    return arguments.length > 2
+      ? createWrapper(func, 17, slice(arguments, 2), null, thisArg)
+      : createWrapper(func, 1, null, null, thisArg);
+  }
+
+  /**
+   * Binds methods of an object to the object itself, overwriting the existing
+   * method. Method names may be specified as individual arguments or as arrays
+   * of method names. If no method names are provided all the function properties
+   * of `object` will be bound.
+   *
+   * @static
+   * @memberOf _
+   * @category Functions
+   * @param {Object} object The object to bind and assign the bound methods to.
+   * @param {...string} [methodName] The object method names to
+   *  bind, specified as individual method names or arrays of method names.
+   * @returns {Object} Returns `object`.
+   * @example
+   *
+   * var view = {
+   *   'label': 'docs',
+   *   'onClick': function() { console.log('clicked ' + this.label); }
+   * };
+   *
+   * _.bindAll(view);
+   * jQuery('#docs').on('click', view.onClick);
+   * // => logs 'clicked docs', when the button is clicked
+   */
+  function bindAll(object) {
+    var funcs = arguments.length > 1 ? baseFlatten(arguments, true, false, 1) : functions(object),
+        index = -1,
+        length = funcs.length;
+
+    while (++index < length) {
+      var key = funcs[index];
+      object[key] = createWrapper(object[key], 1, null, null, object);
+    }
+    return object;
+  }
+
+  /**
+   * Creates a function that is the composition of the provided functions,
+   * where each function consumes the return value of the function that follows.
+   * For example, composing the functions `f()`, `g()`, and `h()` produces `f(g(h()))`.
+   * Each function is executed with the `this` binding of the composed function.
+   *
+   * @static
+   * @memberOf _
+   * @category Functions
+   * @param {...Function} [func] Functions to compose.
+   * @returns {Function} Returns the new composed function.
+   * @example
+   *
+   * var realNameMap = {
+   *   'pebbles': 'penelope'
+   * };
+   *
+   * var format = function(name) {
+   *   name = realNameMap[name.toLowerCase()] || name;
+   *   return name.charAt(0).toUpperCase() + name.slice(1).toLowerCase();
+   * };
+   *
+   * var greet = function(formatted) {
+   *   return 'Hiya ' + formatted + '!';
+   * };
+   *
+   * var welcome = _.compose(greet, format);
+   * welcome('pebbles');
+   * // => 'Hiya Penelope!'
+   */
+  function compose() {
+    var funcs = arguments,
+        length = funcs.length;
+
+    while (length--) {
+      if (!isFunction(funcs[length])) {
+        throw new TypeError;
+      }
+    }
+    return function() {
+      var args = arguments,
+          length = funcs.length;
+
+      while (length--) {
+        args = [funcs[length].apply(this, args)];
+      }
+      return args[0];
+    };
+  }
+
+  /**
+   * Creates a function that will delay the execution of `func` until after
+   * `wait` milliseconds have elapsed since the last time it was invoked.
+   * Provide an options object to indicate that `func` should be invoked on
+   * the leading and/or trailing edge of the `wait` timeout. Subsequent calls
+   * to the debounced function will return the result of the last `func` call.
+   *
+   * Note: If `leading` and `trailing` options are `true` `func` will be called
+   * on the trailing edge of the timeout only if the the debounced function is
+   * invoked more than once during the `wait` timeout.
+   *
+   * @static
+   * @memberOf _
+   * @category Functions
+   * @param {Function} func The function to debounce.
+   * @param {number} wait The number of milliseconds to delay.
+   * @param {Object} [options] The options object.
+   * @param {boolean} [options.leading=false] Specify execution on the leading edge of the timeout.
+   * @param {number} [options.maxWait] The maximum time `func` is allowed to be delayed before it's called.
+   * @param {boolean} [options.trailing=true] Specify execution on the trailing edge of the timeout.
+   * @returns {Function} Returns the new debounced function.
+   * @example
+   *
+   * // avoid costly calculations while the window size is in flux
+   * var lazyLayout = _.debounce(calculateLayout, 150);
+   * jQuery(window).on('resize', lazyLayout);
+   *
+   * // execute `sendMail` when the click event is fired, debouncing subsequent calls
+   * jQuery('#postbox').on('click', _.debounce(sendMail, 300, {
+   *   'leading': true,
+   *   'trailing': false
+   * });
+   *
+   * // ensure `batchLog` is executed once after 1 second of debounced calls
+   * var source = new EventSource('/stream');
+   * source.addEventListener('message', _.debounce(batchLog, 250, {
+   *   'maxWait': 1000
+   * }, false);
+   */
+  function debounce(func, wait, options) {
+    var args,
+        maxTimeoutId,
+        result,
+        stamp,
+        thisArg,
+        timeoutId,
+        trailingCall,
+        lastCalled = 0,
+        maxWait = false,
+        trailing = true;
+
+    if (!isFunction(func)) {
+      throw new TypeError;
+    }
+    wait = nativeMax(0, wait) || 0;
+    if (options === true) {
+      var leading = true;
+      trailing = false;
+    } else if (isObject(options)) {
+      leading = options.leading;
+      maxWait = 'maxWait' in options && (nativeMax(wait, options.maxWait) || 0);
+      trailing = 'trailing' in options ? options.trailing : trailing;
+    }
+    var delayed = function() {
+      var remaining = wait - (now() - stamp);
+      if (remaining <= 0) {
+        if (maxTimeoutId) {
+          clearTimeout(maxTimeoutId);
+        }
+        var isCalled = trailingCall;
+        maxTimeoutId = timeoutId = trailingCall = undefined;
+        if (isCalled) {
+          lastCalled = now();
+          result = func.apply(thisArg, args);
+          if (!timeoutId && !maxTimeoutId) {
+            args = thisArg = null;
+          }
+        }
+      } else {
+        timeoutId = setTimeout(delayed, remaining);
+      }
+    };
+
+    var maxDelayed = function() {
+      if (timeoutId) {
+        clearTimeout(timeoutId);
+      }
+      maxTimeoutId = timeoutId = trailingCall = undefined;
+      if (trailing || (maxWait !== wait)) {
+        lastCalled = now();
+        result = func.apply(thisArg, args);
+        if (!timeoutId && !maxTimeoutId) {
+          args = thisArg = null;
+        }
+      }
+    };
+
+    return function() {
+      args = arguments;
+      stamp = now();
+      thisArg = this;
+      trailingCall = trailing && (timeoutId || !leading);
+
+      if (maxWait === false) {
+        var leadingCall = leading && !timeoutId;
+      } else {
+        if (!maxTimeoutId && !leading) {
+          lastCalled = stamp;
+        }
+        var remaining = maxWait - (stamp - lastCalled),
+            isCalled = remaining <= 0;
+
+        if (isCalled) {
+          if (maxTimeoutId) {
+            maxTimeoutId = clearTimeout(maxTimeoutId);
+          }
+          lastCalled = stamp;
+          result = func.apply(thisArg, args);
+        }
+        else if (!maxTimeoutId) {
+          maxTimeoutId = setTimeout(maxDelayed, remaining);
+        }
+      }
+      if (isCalled && timeoutId) {
+        timeoutId = clearTimeout(timeoutId);
+      }
+      else if (!timeoutId && wait !== maxWait) {
+        timeoutId = setTimeout(delayed, wait);
+      }
+      if (leadingCall) {
+        isCalled = true;
+        result = func.apply(thisArg, args);
+      }
+      if (isCalled && !timeoutId && !maxTimeoutId) {
+        args = thisArg = null;
+      }
+      return result;
+    };
+  }
+
+  /**
+   * Defers executing the `func` function until the current call stack has cleared.
+   * Additional arguments will be provided to `func` when it is invoked.
+   *
+   * @static
+   * @memberOf _
+   * @category Functions
+   * @param {Function} func The function to defer.
+   * @param {...*} [arg] Arguments to invoke the function with.
+   * @returns {number} Returns the timer id.
+   * @example
+   *
+   * _.defer(function(text) { console.log(text); }, 'deferred');
+   * // logs 'deferred' after one or more milliseconds
+   */
+  function defer(func) {
+    if (!isFunction(func)) {
+      throw new TypeError;
+    }
+    var args = slice(arguments, 1);
+    return setTimeout(function() { func.apply(undefined, args); }, 1);
+  }
+
+  /**
+   * Executes the `func` function after `wait` milliseconds. Additional arguments
+   * will be provided to `func` when it is invoked.
+   *
+   * @static
+   * @memberOf _
+   * @category Functions
+   * @param {Function} func The function to delay.
+   * @param {number} wait The number of milliseconds to delay execution.
+   * @param {...*} [arg] Arguments to invoke the function with.
+   * @returns {number} Returns the timer id.
+   * @example
+   *
+   * _.delay(function(text) { console.log(text); }, 1000, 'later');
+   * // => logs 'later' after one second
+   */
+  function delay(func, wait) {
+    if (!isFunction(func)) {
+      throw new TypeError;
+    }
+    var args = slice(arguments, 2);
+    return setTimeout(function() { func.apply(undefined, args); }, wait);
+  }
+
+  /**
+   * Creates a function that memoizes the result of `func`. If `resolver` is
+   * provided it will be used to determine the cache key for storing the result
+   * based on the arguments provided to the memoized function. By default, the
+   * first argument provided to the memoized function is used as the cache key.
+   * The `func` is executed with the `this` binding of the memoized function.
+   * The result cache is exposed as the `cache` property on the memoized function.
+   *
+   * @static
+   * @memberOf _
+   * @category Functions
+   * @param {Function} func The function to have its output memoized.
+   * @param {Function} [resolver] A function used to resolve the cache key.
+   * @returns {Function} Returns the new memoizing function.
+   * @example
+   *
+   * var fibonacci = _.memoize(function(n) {
+   *   return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);
+   * });
+   *
+   * fibonacci(9)
+   * // => 34
+   *
+   * var data = {
+   *   'fred': { 'name': 'fred', 'age': 40 },
+   *   'pebbles': { 'name': 'pebbles', 'age': 1 }
+   * };
+   *
+   * // modifying the result cache
+   * var get = _.memoize(function(name) { return data[name]; }, _.identity);
+   * get('pebbles');
+   * // => { 'name': 'pebbles', 'age': 1 }
+   *
+   * get.cache.pebbles.name = 'penelope';
+   * get('pebbles');
+   * // => { 'name': 'penelope', 'age': 1 }
+   */
+  function memoize(func, resolver) {
+    var cache = {};
+    return function() {
+      var key = resolver ? resolver.apply(this, arguments) : keyPrefix + arguments[0];
+      return hasOwnProperty.call(cache, key)
+        ? cache[key]
+        : (cache[key] = func.apply(this, arguments));
+    };
+  }
+
+  /**
+   * Creates a function that is restricted to execute `func` once. Repeat calls to
+   * the function will return the value of the first call. The `func` is executed
+   * with the `this` binding of the created function.
+   *
+   * @static
+   * @memberOf _
+   * @category Functions
+   * @param {Function} func The function to restrict.
+   * @returns {Function} Returns the new restricted function.
+   * @example
+   *
+   * var initialize = _.once(createApplication);
+   * initialize();
+   * initialize();
+   * // `initialize` executes `createApplication` once
+   */
+  function once(func) {
+    var ran,
+        result;
+
+    if (!isFunction(func)) {
+      throw new TypeError;
+    }
+    return function() {
+      if (ran) {
+        return result;
+      }
+      ran = true;
+      result = func.apply(this, arguments);
+
+      // clear the `func` variable so the function may be garbage collected
+      func = null;
+      return result;
+    };
+  }
+
+  /**
+   * Creates a function that, when called, invokes `func` with any additional
+   * `partial` arguments prepended to those provided to the new function. This
+   * method is similar to `_.bind` except it does **not** alter the `this` binding.
+   *
+   * @static
+   * @memberOf _
+   * @category Functions
+   * @param {Function} func The function to partially apply arguments to.
+   * @param {...*} [arg] Arguments to be partially applied.
+   * @returns {Function} Returns the new partially applied function.
+   * @example
+   *
+   * var greet = function(greeting, name) { return greeting + ' ' + name; };
+   * var hi = _.partial(greet, 'hi');
+   * hi('fred');
+   * // => 'hi fred'
+   */
+  function partial(func) {
+    return createWrapper(func, 16, slice(arguments, 1));
+  }
+
+  /**
+   * Creates a function that, when executed, will only call the `func` function
+   * at most once per every `wait` milliseconds. Provide an options object to
+   * indicate that `func` should be invoked on the leading and/or trailing edge
+   * of the `wait` timeout. Subsequent calls to the throttled function will
+   * return the result of the last `func` call.
+   *
+   * Note: If `leading` and `trailing` options are `true` `func` will be called
+   * on the trailing edge of the timeout only if the the throttled function is
+   * invoked more than once during the `wait` timeout.
+   *
+   * @static
+   * @memberOf _
+   * @category Functions
+   * @param {Function} func The function to throttle.
+   * @param {number} wait The number of milliseconds to throttle executions to.
+   * @param {Object} [options] The options object.
+   * @param {boolean} [options.leading=true] Specify execution on the leading edge of the timeout.
+   * @param {boolean} [options.trailing=true] Specify execution on the trailing edge of the timeout.
+   * @returns {Function} Returns the new throttled function.
+   * @example
+   *
+   * // avoid excessively updating the position while scrolling
+   * var throttled = _.throttle(updatePosition, 100);
+   * jQuery(window).on('scroll', throttled);
+   *
+   * // execute `renewToken` when the click event is fired, but not more than once every 5 minutes
+   * jQuery('.interactive').on('click', _.throttle(renewToken, 300000, {
+   *   'trailing': false
+   * }));
+   */
+  function throttle(func, wait, options) {
+    var leading = true,
+        trailing = true;
+
+    if (!isFunction(func)) {
+      throw new TypeError;
+    }
+    if (options === false) {
+      leading = false;
+    } else if (isObject(options)) {
+      leading = 'leading' in options ? options.leading : leading;
+      trailing = 'trailing' in options ? options.trailing : trailing;
+    }
+    options = {};
+    options.leading = leading;
+    options.maxWait = wait;
+    options.trailing = trailing;
+
+    return debounce(func, wait, options);
+  }
+
+  /**
+   * Creates a function that provides `value` to the wrapper function as its
+   * first argument. Additional arguments provided to the function are appended
+   * to those provided to the wrapper function. The wrapper is executed with
+   * the `this` binding of the created function.
+   *
+   * @static
+   * @memberOf _
+   * @category Functions
+   * @param {*} value The value to wrap.
+   * @param {Function} wrapper The wrapper function.
+   * @returns {Function} Returns the new function.
+   * @example
+   *
+   * var p = _.wrap(_.escape, function(func, text) {
+   *   return '<p>' + func(text) + '</p>';
+   * });
+   *
+   * p('Fred, Wilma, & Pebbles');
+   * // => '<p>Fred, Wilma, &amp; Pebbles</p>'
+   */
+  function wrap(value, wrapper) {
+    return createWrapper(wrapper, 16, [value]);
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Produces a callback bound to an optional `thisArg`. If `func` is a property
+   * name the created callback will return the property value for a given element.
+   * If `func` is an object the created callback will return `true` for elements
+   * that contain the equivalent object properties, otherwise it will return `false`.
+   *
+   * @static
+   * @memberOf _
+   * @category Utilities
+   * @param {*} [func=identity] The value to convert to a callback.
+   * @param {*} [thisArg] The `this` binding of the created callback.
+   * @param {number} [argCount] The number of arguments the callback accepts.
+   * @returns {Function} Returns a callback function.
+   * @example
+   *
+   * var characters = [
+   *   { 'name': 'barney', 'age': 36 },
+   *   { 'name': 'fred',   'age': 40 }
+   * ];
+   *
+   * // wrap to create custom callback shorthands
+   * _.createCallback = _.wrap(_.createCallback, function(func, callback, thisArg) {
+   *   var match = /^(.+?)__([gl]t)(.+)$/.exec(callback);
+   *   return !match ? func(callback, thisArg) : function(object) {
+   *     return match[2] == 'gt' ? object[match[1]] > match[3] : object[match[1]] < match[3];
+   *   };
+   * });
+   *
+   * _.filter(characters, 'age__gt38');
+   * // => [{ 'name': 'fred', 'age': 40 }]
+   */
+  function createCallback(func, thisArg, argCount) {
+    var type = typeof func;
+    if (func == null || type == 'function') {
+      return baseCreateCallback(func, thisArg, argCount);
+    }
+    // handle "_.pluck" style callback shorthands
+    if (type != 'object') {
+      return property(func);
+    }
+    var props = keys(func);
+    return function(object) {
+      var length = props.length,
+          result = false;
+
+      while (length--) {
+        if (!(result = object[props[length]] === func[props[length]])) {
+          break;
+        }
+      }
+      return result;
+    };
+  }
+
+  /**
+   * Converts the characters `&`, `<`, `>`, `"`, and `'` in `string` to their
+   * corresponding HTML entities.
+   *
+   * @static
+   * @memberOf _
+   * @category Utilities
+   * @param {string} string The string to escape.
+   * @returns {string} Returns the escaped string.
+   * @example
+   *
+   * _.escape('Fred, Wilma, & Pebbles');
+   * // => 'Fred, Wilma, &amp; Pebbles'
+   */
+  function escape(string) {
+    return string == null ? '' : String(string).replace(reUnescapedHtml, escapeHtmlChar);
+  }
+
+  /**
+   * This method returns the first argument provided to it.
+   *
+   * @static
+   * @memberOf _
+   * @category Utilities
+   * @param {*} value Any value.
+   * @returns {*} Returns `value`.
+   * @example
+   *
+   * var object = { 'name': 'fred' };
+   * _.identity(object) === object;
+   * // => true
+   */
+  function identity(value) {
+    return value;
+  }
+
+  /**
+   * Adds function properties of a source object to the destination object.
+   * If `object` is a function methods will be added to its prototype as well.
+   *
+   * @static
+   * @memberOf _
+   * @category Utilities
+   * @param {Function|Object} [object=lodash] object The destination object.
+   * @param {Object} source The object of functions to add.
+   * @param {Object} [options] The options object.
+   * @param {boolean} [options.chain=true] Specify whether the functions added are chainable.
+   * @example
+   *
+   * function capitalize(string) {
+   *   return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();
+   * }
+   *
+   * _.mixin({ 'capitalize': capitalize });
+   * _.capitalize('fred');
+   * // => 'Fred'
+   *
+   * _('fred').capitalize().value();
+   * // => 'Fred'
+   *
+   * _.mixin({ 'capitalize': capitalize }, { 'chain': false });
+   * _('fred').capitalize();
+   * // => 'Fred'
+   */
+  function mixin(object) {
+    forEach(functions(object), function(methodName) {
+      var func = lodash[methodName] = object[methodName];
+
+      lodash.prototype[methodName] = function() {
+        var args = [this.__wrapped__];
+        push.apply(args, arguments);
+
+        var result = func.apply(lodash, args);
+        return this.__chain__
+          ? new lodashWrapper(result, true)
+          : result;
+      };
+    });
+  }
+
+  /**
+   * Reverts the '_' variable to its previous value and returns a reference to
+   * the `lodash` function.
+   *
+   * @static
+   * @memberOf _
+   * @category Utilities
+   * @returns {Function} Returns the `lodash` function.
+   * @example
+   *
+   * var lodash = _.noConflict();
+   */
+  function noConflict() {
+    root._ = oldDash;
+    return this;
+  }
+
+  /**
+   * A no-operation function.
+   *
+   * @static
+   * @memberOf _
+   * @category Utilities
+   * @example
+   *
+   * var object = { 'name': 'fred' };
+   * _.noop(object) === undefined;
+   * // => true
+   */
+  function noop() {
+    // no operation performed
+  }
+
+  /**
+   * Gets the number of milliseconds that have elapsed since the Unix epoch
+   * (1 January 1970 00:00:00 UTC).
+   *
+   * @static
+   * @memberOf _
+   * @category Utilities
+   * @example
+   *
+   * var stamp = _.now();
+   * _.defer(function() { console.log(_.now() - stamp); });
+   * // => logs the number of milliseconds it took for the deferred function to be called
+   */
+  var now = isNative(now = Date.now) && now || function() {
+    return new Date().getTime();
+  };
+
+  /**
+   * Creates a "_.pluck" style function, which returns the `key` value of a
+   * given object.
+   *
+   * @static
+   * @memberOf _
+   * @category Utilities
+   * @param {string} key The name of the property to retrieve.
+   * @returns {Function} Returns the new function.
+   * @example
+   *
+   * var characters = [
+   *   { 'name': 'fred',   'age': 40 },
+   *   { 'name': 'barney', 'age': 36 }
+   * ];
+   *
+   * var getName = _.property('name');
+   *
+   * _.map(characters, getName);
+   * // => ['barney', 'fred']
+   *
+   * _.sortBy(characters, getName);
+   * // => [{ 'name': 'barney', 'age': 36 }, { 'name': 'fred',   'age': 40 }]
+   */
+  function property(key) {
+    return function(object) {
+      return object[key];
+    };
+  }
+
+  /**
+   * Produces a random number between `min` and `max` (inclusive). If only one
+   * argument is provided a number between `0` and the given number will be
+   * returned. If `floating` is truey or either `min` or `max` are floats a
+   * floating-point number will be returned instead of an integer.
+   *
+   * @static
+   * @memberOf _
+   * @category Utilities
+   * @param {number} [min=0] The minimum possible value.
+   * @param {number} [max=1] The maximum possible value.
+   * @param {boolean} [floating=false] Specify returning a floating-point number.
+   * @returns {number} Returns a random number.
+   * @example
+   *
+   * _.random(0, 5);
+   * // => an integer between 0 and 5
+   *
+   * _.random(5);
+   * // => also an integer between 0 and 5
+   *
+   * _.random(5, true);
+   * // => a floating-point number between 0 and 5
+   *
+   * _.random(1.2, 5.2);
+   * // => a floating-point number between 1.2 and 5.2
+   */
+  function random(min, max) {
+    if (min == null && max == null) {
+      max = 1;
+    }
+    min = +min || 0;
+    if (max == null) {
+      max = min;
+      min = 0;
+    } else {
+      max = +max || 0;
+    }
+    return min + floor(nativeRandom() * (max - min + 1));
+  }
+
+  /**
+   * Resolves the value of property `key` on `object`. If `key` is a function
+   * it will be invoked with the `this` binding of `object` and its result returned,
+   * else the property value is returned. If `object` is falsey then `undefined`
+   * is returned.
+   *
+   * @static
+   * @memberOf _
+   * @category Utilities
+   * @param {Object} object The object to inspect.
+   * @param {string} key The name of the property to resolve.
+   * @returns {*} Returns the resolved value.
+   * @example
+   *
+   * var object = {
+   *   'cheese': 'crumpets',
+   *   'stuff': function() {
+   *     return 'nonsense';
+   *   }
+   * };
+   *
+   * _.result(object, 'cheese');
+   * // => 'crumpets'
+   *
+   * _.result(object, 'stuff');
+   * // => 'nonsense'
+   */
+  function result(object, key) {
+    if (object) {
+      var value = object[key];
+      return isFunction(value) ? object[key]() : value;
+    }
+  }
+
+  /**
+   * A micro-templating method that handles arbitrary delimiters, preserves
+   * whitespace, and correctly escapes quotes within interpolated code.
+   *
+   * Note: In the development build, `_.template` utilizes sourceURLs for easier
+   * debugging. See http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl
+   *
+   * For more information on precompiling templates see:
+   * http://lodash.com/custom-builds
+   *
+   * For more information on Chrome extension sandboxes see:
+   * http://developer.chrome.com/stable/extensions/sandboxingEval.html
+   *
+   * @static
+   * @memberOf _
+   * @category Utilities
+   * @param {string} text The template text.
+   * @param {Object} data The data object used to populate the text.
+   * @param {Object} [options] The options object.
+   * @param {RegExp} [options.escape] The "escape" delimiter.
+   * @param {RegExp} [options.evaluate] The "evaluate" delimiter.
+   * @param {Object} [options.imports] An object to import into the template as local variables.
+   * @param {RegExp} [options.interpolate] The "interpolate" delimiter.
+   * @param {string} [sourceURL] The sourceURL of the template's compiled source.
+   * @param {string} [variable] The data object variable name.
+   * @returns {Function|string} Returns a compiled function when no `data` object
+   *  is given, else it returns the interpolated text.
+   * @example
+   *
+   * // using the "interpolate" delimiter to create a compiled template
+   * var compiled = _.template('hello <%= name %>');
+   * compiled({ 'name': 'fred' });
+   * // => 'hello fred'
+   *
+   * // using the "escape" delimiter to escape HTML in data property values
+   * _.template('<b><%- value %></b>', { 'value': '<script>' });
+   * // => '<b>&lt;script&gt;</b>'
+   *
+   * // using the "evaluate" delimiter to generate HTML
+   * var list = '<% _.forEach(people, function(name) { %><li><%- name %></li><% }); %>';
+   * _.template(list, { 'people': ['fred', 'barney'] });
+   * // => '<li>fred</li><li>barney</li>'
+   *
+   * // using the ES6 delimiter as an alternative to the default "interpolate" delimiter
+   * _.template('hello ${ name }', { 'name': 'pebbles' });
+   * // => 'hello pebbles'
+   *
+   * // using the internal `print` function in "evaluate" delimiters
+   * _.template('<% print("hello " + name); %>!', { 'name': 'barney' });
+   * // => 'hello barney!'
+   *
+   * // using a custom template delimiters
+   * _.templateSettings = {
+   *   'interpolate': /{{([\s\S]+?)}}/g
+   * };
+   *
+   * _.template('hello {{ name }}!', { 'name': 'mustache' });
+   * // => 'hello mustache!'
+   *
+   * // using the `imports` option to import jQuery
+   * var list = '<% jq.each(people, function(name) { %><li><%- name %></li><% }); %>';
+   * _.template(list, { 'people': ['fred', 'barney'] }, { 'imports': { 'jq': jQuery } });
+   * // => '<li>fred</li><li>barney</li>'
+   *
+   * // using the `sourceURL` option to specify a custom sourceURL for the template
+   * var compiled = _.template('hello <%= name %>', null, { 'sourceURL': '/basic/greeting.jst' });
+   * compiled(data);
+   * // => find the source of "greeting.jst" under the Sources tab or Resources panel of the web inspector
+   *
+   * // using the `variable` option to ensure a with-statement isn't used in the compiled template
+   * var compiled = _.template('hi <%= data.name %>!', null, { 'variable': 'data' });
+   * compiled.source;
+   * // => function(data) {
+   *   var __t, __p = '', __e = _.escape;
+   *   __p += 'hi ' + ((__t = ( data.name )) == null ? '' : __t) + '!';
+   *   return __p;
+   * }
+   *
+   * // using the `source` property to inline compiled templates for meaningful
+   * // line numbers in error messages and a stack trace
+   * fs.writeFileSync(path.join(cwd, 'jst.js'), '\
+   *   var JST = {\
+   *     "main": ' + _.template(mainText).source + '\
+   *   };\
+   * ');
+   */
+  function template(text, data, options) {
+    var _ = lodash,
+        settings = _.templateSettings;
+
+    text = String(text || '');
+    options = defaults({}, options, settings);
+
+    var index = 0,
+        source = "__p += '",
+        variable = options.variable;
+
+    var reDelimiters = RegExp(
+      (options.escape || reNoMatch).source + '|' +
+      (options.interpolate || reNoMatch).source + '|' +
+      (options.evaluate || reNoMatch).source + '|$'
+    , 'g');
+
+    text.replace(reDelimiters, function(match, escapeValue, interpolateValue, evaluateValue, offset) {
+      source += text.slice(index, offset).replace(reUnescapedString, escapeStringChar);
+      if (escapeValue) {
+        source += "' +\n_.escape(" + escapeValue + ") +\n'";
+      }
+      if (evaluateValue) {
+        source += "';\n" + evaluateValue + ";\n__p += '";
+      }
+      if (interpolateValue) {
+        source += "' +\n((__t = (" + interpolateValue + ")) == null ? '' : __t) +\n'";
+      }
+      index = offset + match.length;
+      return match;
+    });
+
+    source += "';\n";
+    if (!variable) {
+      variable = 'obj';
+      source = 'with (' + variable + ' || {}) {\n' + source + '\n}\n';
+    }
+    source = 'function(' + variable + ') {\n' +
+      "var __t, __p = '', __j = Array.prototype.join;\n" +
+      "function print() { __p += __j.call(arguments, '') }\n" +
+      source +
+      'return __p\n}';
+
+    try {
+      var result = Function('_', 'return ' + source)(_);
+    } catch(e) {
+      e.source = source;
+      throw e;
+    }
+    if (data) {
+      return result(data);
+    }
+    result.source = source;
+    return result;
+  }
+
+  /**
+   * Executes the callback `n` times, returning an array of the results
+   * of each callback execution. The callback is bound to `thisArg` and invoked
+   * with one argument; (index).
+   *
+   * @static
+   * @memberOf _
+   * @category Utilities
+   * @param {number} n The number of times to execute the callback.
+   * @param {Function} callback The function called per iteration.
+   * @param {*} [thisArg] The `this` binding of `callback`.
+   * @returns {Array} Returns an array of the results of each `callback` execution.
+   * @example
+   *
+   * var diceRolls = _.times(3, _.partial(_.random, 1, 6));
+   * // => [3, 6, 4]
+   *
+   * _.times(3, function(n) { mage.castSpell(n); });
+   * // => calls `mage.castSpell(n)` three times, passing `n` of `0`, `1`, and `2` respectively
+   *
+   * _.times(3, function(n) { this.cast(n); }, mage);
+   * // => also calls `mage.castSpell(n)` three times
+   */
+  function times(n, callback, thisArg) {
+    n = (n = +n) > -1 ? n : 0;
+    var index = -1,
+        result = Array(n);
+
+    callback = baseCreateCallback(callback, thisArg, 1);
+    while (++index < n) {
+      result[index] = callback(index);
+    }
+    return result;
+  }
+
+  /**
+   * The inverse of `_.escape` this method converts the HTML entities
+   * `&amp;`, `&lt;`, `&gt;`, `&quot;`, and `&#39;` in `string` to their
+   * corresponding characters.
+   *
+   * @static
+   * @memberOf _
+   * @category Utilities
+   * @param {string} string The string to unescape.
+   * @returns {string} Returns the unescaped string.
+   * @example
+   *
+   * _.unescape('Fred, Barney &amp; Pebbles');
+   * // => 'Fred, Barney & Pebbles'
+   */
+  function unescape(string) {
+    return string == null ? '' : String(string).replace(reEscapedHtml, unescapeHtmlChar);
+  }
+
+  /**
+   * Generates a unique ID. If `prefix` is provided the ID will be appended to it.
+   *
+   * @static
+   * @memberOf _
+   * @category Utilities
+   * @param {string} [prefix] The value to prefix the ID with.
+   * @returns {string} Returns the unique ID.
+   * @example
+   *
+   * _.uniqueId('contact_');
+   * // => 'contact_104'
+   *
+   * _.uniqueId();
+   * // => '105'
+   */
+  function uniqueId(prefix) {
+    var id = ++idCounter + '';
+    return prefix ? prefix + id : id;
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Creates a `lodash` object that wraps the given value with explicit
+   * method chaining enabled.
+   *
+   * @static
+   * @memberOf _
+   * @category Chaining
+   * @param {*} value The value to wrap.
+   * @returns {Object} Returns the wrapper object.
+   * @example
+   *
+   * var characters = [
+   *   { 'name': 'barney',  'age': 36 },
+   *   { 'name': 'fred',    'age': 40 },
+   *   { 'name': 'pebbles', 'age': 1 }
+   * ];
+   *
+   * var youngest = _.chain(characters)
+   *     .sortBy('age')
+   *     .map(function(chr) { return chr.name + ' is ' + chr.age; })
+   *     .first()
+   *     .value();
+   * // => 'pebbles is 1'
+   */
+  function chain(value) {
+    value = new lodashWrapper(value);
+    value.__chain__ = true;
+    return value;
+  }
+
+  /**
+   * Invokes `interceptor` with the `value` as the first argument and then
+   * returns `value`. The purpose of this method is to "tap into" a method
+   * chain in order to perform operations on intermediate results within
+   * the chain.
+   *
+   * @static
+   * @memberOf _
+   * @category Chaining
+   * @param {*} value The value to provide to `interceptor`.
+   * @param {Function} interceptor The function to invoke.
+   * @returns {*} Returns `value`.
+   * @example
+   *
+   * _([1, 2, 3, 4])
+   *  .tap(function(array) { array.pop(); })
+   *  .reverse()
+   *  .value();
+   * // => [3, 2, 1]
+   */
+  function tap(value, interceptor) {
+    interceptor(value);
+    return value;
+  }
+
+  /**
+   * Enables explicit method chaining on the wrapper object.
+   *
+   * @name chain
+   * @memberOf _
+   * @category Chaining
+   * @returns {*} Returns the wrapper object.
+   * @example
+   *
+   * var characters = [
+   *   { 'name': 'barney', 'age': 36 },
+   *   { 'name': 'fred',   'age': 40 }
+   * ];
+   *
+   * // without explicit chaining
+   * _(characters).first();
+   * // => { 'name': 'barney', 'age': 36 }
+   *
+   * // with explicit chaining
+   * _(characters).chain()
+   *   .first()
+   *   .pick('age')
+   *   .value();
+   * // => { 'age': 36 }
+   */
+  function wrapperChain() {
+    this.__chain__ = true;
+    return this;
+  }
+
+  /**
+   * Extracts the wrapped value.
+   *
+   * @name valueOf
+   * @memberOf _
+   * @alias value
+   * @category Chaining
+   * @returns {*} Returns the wrapped value.
+   * @example
+   *
+   * _([1, 2, 3]).valueOf();
+   * // => [1, 2, 3]
+   */
+  function wrapperValueOf() {
+    return this.__wrapped__;
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  // add functions that return wrapped values when chaining
+  lodash.after = after;
+  lodash.bind = bind;
+  lodash.bindAll = bindAll;
+  lodash.chain = chain;
+  lodash.compact = compact;
+  lodash.compose = compose;
+  lodash.countBy = countBy;
+  lodash.debounce = debounce;
+  lodash.defaults = defaults;
+  lodash.defer = defer;
+  lodash.delay = delay;
+  lodash.difference = difference;
+  lodash.filter = filter;
+  lodash.flatten = flatten;
+  lodash.forEach = forEach;
+  lodash.functions = functions;
+  lodash.groupBy = groupBy;
+  lodash.indexBy = indexBy;
+  lodash.initial = initial;
+  lodash.intersection = intersection;
+  lodash.invert = invert;
+  lodash.invoke = invoke;
+  lodash.keys = keys;
+  lodash.map = map;
+  lodash.max = max;
+  lodash.memoize = memoize;
+  lodash.min = min;
+  lodash.omit = omit;
+  lodash.once = once;
+  lodash.pairs = pairs;
+  lodash.partial = partial;
+  lodash.pick = pick;
+  lodash.pluck = pluck;
+  lodash.range = range;
+  lodash.reject = reject;
+  lodash.rest = rest;
+  lodash.shuffle = shuffle;
+  lodash.sortBy = sortBy;
+  lodash.tap = tap;
+  lodash.throttle = throttle;
+  lodash.times = times;
+  lodash.toArray = toArray;
+  lodash.union = union;
+  lodash.uniq = uniq;
+  lodash.values = values;
+  lodash.where = where;
+  lodash.without = without;
+  lodash.wrap = wrap;
+  lodash.zip = zip;
+
+  // add aliases
+  lodash.collect = map;
+  lodash.drop = rest;
+  lodash.each = forEach;
+  lodash.extend = assign;
+  lodash.methods = functions;
+  lodash.object = zipObject;
+  lodash.select = filter;
+  lodash.tail = rest;
+  lodash.unique = uniq;
+
+  /*--------------------------------------------------------------------------*/
+
+  // add functions that return unwrapped values when chaining
+  lodash.clone = clone;
+  lodash.contains = contains;
+  lodash.escape = escape;
+  lodash.every = every;
+  lodash.find = find;
+  lodash.has = has;
+  lodash.identity = identity;
+  lodash.indexOf = indexOf;
+  lodash.isArguments = isArguments;
+  lodash.isArray = isArray;
+  lodash.isBoolean = isBoolean;
+  lodash.isDate = isDate;
+  lodash.isElement = isElement;
+  lodash.isEmpty = isEmpty;
+  lodash.isEqual = isEqual;
+  lodash.isFinite = isFinite;
+  lodash.isFunction = isFunction;
+  lodash.isNaN = isNaN;
+  lodash.isNull = isNull;
+  lodash.isNumber = isNumber;
+  lodash.isObject = isObject;
+  lodash.isRegExp = isRegExp;
+  lodash.isString = isString;
+  lodash.isUndefined = isUndefined;
+  lodash.lastIndexOf = lastIndexOf;
+  lodash.mixin = mixin;
+  lodash.noConflict = noConflict;
+  lodash.random = random;
+  lodash.reduce = reduce;
+  lodash.reduceRight = reduceRight;
+  lodash.result = result;
+  lodash.size = size;
+  lodash.some = some;
+  lodash.sortedIndex = sortedIndex;
+  lodash.template = template;
+  lodash.unescape = unescape;
+  lodash.uniqueId = uniqueId;
+
+  // add aliases
+  lodash.all = every;
+  lodash.any = some;
+  lodash.detect = find;
+  lodash.findWhere = findWhere;
+  lodash.foldl = reduce;
+  lodash.foldr = reduceRight;
+  lodash.include = contains;
+  lodash.inject = reduce;
+
+  /*--------------------------------------------------------------------------*/
+
+  // add functions capable of returning wrapped and unwrapped values when chaining
+  lodash.first = first;
+  lodash.last = last;
+  lodash.sample = sample;
+
+  // add aliases
+  lodash.take = first;
+  lodash.head = first;
+
+  /*--------------------------------------------------------------------------*/
+
+  // add functions to `lodash.prototype`
+  mixin(lodash);
+
+  /**
+   * The semantic version number.
+   *
+   * @static
+   * @memberOf _
+   * @type string
+   */
+  lodash.VERSION = '2.4.1';
+
+  // add "Chaining" functions to the wrapper
+  lodash.prototype.chain = wrapperChain;
+  lodash.prototype.value = wrapperValueOf;
+
+    // add `Array` mutator functions to the wrapper
+    forEach(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(methodName) {
+      var func = arrayRef[methodName];
+      lodash.prototype[methodName] = function() {
+        var value = this.__wrapped__;
+        func.apply(value, arguments);
+
+        // avoid array-like object bugs with `Array#shift` and `Array#splice`
+        // in Firefox < 10 and IE < 9
+        if (!support.spliceObjects && value.length === 0) {
+          delete value[0];
+        }
+        return this;
+      };
+    });
+
+    // add `Array` accessor functions to the wrapper
+    forEach(['concat', 'join', 'slice'], function(methodName) {
+      var func = arrayRef[methodName];
+      lodash.prototype[methodName] = function() {
+        var value = this.__wrapped__,
+            result = func.apply(value, arguments);
+
+        if (this.__chain__) {
+          result = new lodashWrapper(result);
+          result.__chain__ = true;
+        }
+        return result;
+      };
+    });
+
+  /*--------------------------------------------------------------------------*/
+
+  // some AMD build optimizers like r.js check for condition patterns like the following:
+  if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) {
+    // Expose Lo-Dash to the global object even when an AMD loader is present in
+    // case Lo-Dash is loaded with a RequireJS shim config.
+    // See http://requirejs.org/docs/api.html#config-shim
+    root._ = lodash;
+
+    // define as an anonymous module so, through path mapping, it can be
+    // referenced as the "underscore" module
+    define(function() {
+      return lodash;
+    });
+  }
+  // check for `exports` after `define` in case a build optimizer adds an `exports` object
+  else if (freeExports && freeModule) {
+    // in Node.js or RingoJS
+    if (moduleExports) {
+      (freeModule.exports = lodash)._ = lodash;
+    }
+    // in Narwhal or Rhino -require
+    else {
+      freeExports._ = lodash;
+    }
+  }
+  else {
+    // in a browser or Rhino
+    root._ = lodash;
+  }
+}.call(this));
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lodash/dist/lodash.underscore.min.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lodash/dist/lodash.underscore.min.js
new file mode 100644
index 0000000..e659124
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lodash/dist/lodash.underscore.min.js
@@ -0,0 +1,39 @@
+/**
+ * @license
+ * Lo-Dash 2.4.1 (Custom Build) lodash.com/license | Underscore.js 1.5.2 underscorejs.org/LICENSE
+ * Build: `lodash underscore exports="amd,commonjs,global,node" -o ./dist/lodash.underscore.js`
+ */
+;(function(){function n(n,r,t){t=(t||0)-1;for(var e=n?n.length:0;++t<e;)if(n[t]===r)return t;return-1}function r(n,r){for(var t=n.m,e=r.m,u=-1,o=t.length;++u<o;){var i=t[u],f=e[u];if(i!==f){if(i>f||typeof i=="undefined")return 1;if(i<f||typeof f=="undefined")return-1}}return n.n-r.n}function t(n){return"\\"+yr[n]}function e(n,r,t){r||(r=0),typeof t=="undefined"&&(t=n?n.length:0);var e=-1;t=t-r||0;for(var u=Array(0>t?0:t);++e<t;)u[e]=n[r+e];return u}function u(n){return n instanceof u?n:new o(n)}function o(n,r){this.__chain__=!!r,this.__wrapped__=n
+}function i(n){function r(){if(u){var n=e(u);Rr.apply(n,arguments)}if(this instanceof r){var i=f(t.prototype),n=t.apply(i,n||arguments);return O(n)?n:i}return t.apply(o,n||arguments)}var t=n[0],u=n[2],o=n[4];return r}function f(n){return O(n)?Br(n):{}}function a(n,r,t){if(typeof n!="function")return Y;if(typeof r=="undefined"||!("prototype"in n))return n;switch(t){case 1:return function(t){return n.call(r,t)};case 2:return function(t,e){return n.call(r,t,e)};case 3:return function(t,e,u){return n.call(r,t,e,u)
+};case 4:return function(t,e,u,o){return n.call(r,t,e,u,o)}}return L(n,r)}function l(n){function r(){var n=p?a:this;if(o){var y=e(o);Rr.apply(y,arguments)}return(i||g)&&(y||(y=e(arguments)),i&&Rr.apply(y,i),g&&y.length<c)?(u|=16,l([t,h?u:-4&u,y,null,a,c])):(y||(y=arguments),s&&(t=n[v]),this instanceof r?(n=f(t.prototype),y=t.apply(n,y),O(y)?y:n):t.apply(n,y))}var t=n[0],u=n[1],o=n[2],i=n[3],a=n[4],c=n[5],p=1&u,s=2&u,g=4&u,h=8&u,v=t;return r}function c(n,r){for(var t=-1,e=m(),u=n?n.length:0,o=[];++t<u;){var i=n[t];
+0>e(r,i)&&o.push(i)}return o}function p(n,r,t,e){e=(e||0)-1;for(var u=n?n.length:0,o=[];++e<u;){var i=n[e];if(i&&typeof i=="object"&&typeof i.length=="number"&&(Cr(i)||b(i))){r||(i=p(i,r,t));var f=-1,a=i.length,l=o.length;for(o.length+=a;++f<a;)o[l++]=i[f]}else t||o.push(i)}return o}function s(n,r,t,e){if(n===r)return 0!==n||1/n==1/r;if(n===n&&!(n&&vr[typeof n]||r&&vr[typeof r]))return false;if(null==n||null==r)return n===r;var o=Er.call(n),i=Er.call(r);if(o!=i)return false;switch(o){case lr:case cr:return+n==+r;
+case pr:return n!=+n?r!=+r:0==n?1/n==1/r:n==+r;case gr:case hr:return n==r+""}if(i=o==ar,!i){var f=n instanceof u,a=r instanceof u;if(f||a)return s(f?n.__wrapped__:n,a?r.__wrapped__:r,t,e);if(o!=sr)return false;if(o=n.constructor,f=r.constructor,o!=f&&!(A(o)&&o instanceof o&&A(f)&&f instanceof f)&&"constructor"in n&&"constructor"in r)return false}for(t||(t=[]),e||(e=[]),o=t.length;o--;)if(t[o]==n)return e[o]==r;var l=true,c=0;if(t.push(n),e.push(r),i){if(c=r.length,l=c==n.length)for(;c--&&(l=s(n[c],r[c],t,e)););}else Kr(r,function(r,u,o){return Nr.call(o,u)?(c++,!(l=Nr.call(n,u)&&s(n[u],r,t,e))&&er):void 0
+}),l&&Kr(n,function(n,r,t){return Nr.call(t,r)?!(l=-1<--c)&&er:void 0});return t.pop(),e.pop(),l}function g(n,r,t){for(var e=-1,u=m(),o=n?n.length:0,i=[],f=t?[]:i;++e<o;){var a=n[e],l=t?t(a,e,n):a;(r?!e||f[f.length-1]!==l:0>u(f,l))&&(t&&f.push(l),i.push(a))}return i}function h(n){return function(r,t,e){var u={};t=X(t,e,3),e=-1;var o=r?r.length:0;if(typeof o=="number")for(;++e<o;){var i=r[e];n(u,i,t(i,e,r),r)}else Lr(r,function(r,e,o){n(u,r,t(r,e,o),o)});return u}}function v(n,r,t,e,u,o){var f=16&r,a=32&r;
+if(!(2&r||A(n)))throw new TypeError;return f&&!t.length&&(r&=-17,t=false),a&&!e.length&&(r&=-33,e=false),(1==r||17===r?i:l)([n,r,t,e,u,o])}function y(n){return Vr[n]}function m(){var r=(r=u.indexOf)===G?n:r;return r}function _(n){return typeof n=="function"&&Ar.test(n)}function d(n){return Gr[n]}function b(n){return n&&typeof n=="object"&&typeof n.length=="number"&&Er.call(n)==fr||false}function w(n){if(!n)return n;for(var r=1,t=arguments.length;r<t;r++){var e=arguments[r];if(e)for(var u in e)n[u]=e[u]}return n
+}function j(n){if(!n)return n;for(var r=1,t=arguments.length;r<t;r++){var e=arguments[r];if(e)for(var u in e)"undefined"==typeof n[u]&&(n[u]=e[u])}return n}function x(n){var r=[];return Kr(n,function(n,t){A(n)&&r.push(t)}),r.sort()}function T(n){for(var r=-1,t=Ur(n),e=t.length,u={};++r<e;){var o=t[r];u[n[o]]=o}return u}function E(n){if(!n)return true;if(Cr(n)||N(n))return!n.length;for(var r in n)if(Nr.call(n,r))return false;return true}function A(n){return typeof n=="function"}function O(n){return!(!n||!vr[typeof n])
+}function S(n){return typeof n=="number"||n&&typeof n=="object"&&Er.call(n)==pr||false}function N(n){return typeof n=="string"||n&&typeof n=="object"&&Er.call(n)==hr||false}function R(n){for(var r=-1,t=Ur(n),e=t.length,u=Array(e);++r<e;)u[r]=n[t[r]];return u}function k(n,r){var t=m(),e=n?n.length:0,u=false;return e&&typeof e=="number"?u=-1<t(n,r):Lr(n,function(n){return(u=n===r)&&er}),u}function B(n,r,t){var e=true;r=X(r,t,3),t=-1;var u=n?n.length:0;if(typeof u=="number")for(;++t<u&&(e=!!r(n[t],t,n)););else Lr(n,function(n,t,u){return!(e=!!r(n,t,u))&&er
+});return e}function F(n,r,t){var e=[];r=X(r,t,3),t=-1;var u=n?n.length:0;if(typeof u=="number")for(;++t<u;){var o=n[t];r(o,t,n)&&e.push(o)}else Lr(n,function(n,t,u){r(n,t,u)&&e.push(n)});return e}function q(n,r,t){r=X(r,t,3),t=-1;var e=n?n.length:0;if(typeof e!="number"){var u;return Lr(n,function(n,t,e){return r(n,t,e)?(u=n,er):void 0}),u}for(;++t<e;){var o=n[t];if(r(o,t,n))return o}}function D(n,r,t){var e=-1,u=n?n.length:0;if(r=r&&typeof t=="undefined"?r:a(r,t,3),typeof u=="number")for(;++e<u&&r(n[e],e,n)!==er;);else Lr(n,r)
+}function I(n,r){var t=n?n.length:0;if(typeof t=="number")for(;t--&&false!==r(n[t],t,n););else{var e=Ur(n),t=e.length;Lr(n,function(n,u,o){return u=e?e[--t]:--t,false===r(o[u],u,o)&&er})}}function M(n,r,t){var e=-1,u=n?n.length:0;if(r=X(r,t,3),typeof u=="number")for(var o=Array(u);++e<u;)o[e]=r(n[e],e,n);else o=[],Lr(n,function(n,t,u){o[++e]=r(n,t,u)});return o}function $(n,r,t){var e=-1/0,u=e;typeof r!="function"&&t&&t[r]===n&&(r=null);var o=-1,i=n?n.length:0;if(null==r&&typeof i=="number")for(;++o<i;)t=n[o],t>u&&(u=t);
+else r=X(r,t,3),D(n,function(n,t,o){t=r(n,t,o),t>e&&(e=t,u=n)});return u}function W(n,r,t,e){if(!n)return t;var u=3>arguments.length;r=X(r,e,4);var o=-1,i=n.length;if(typeof i=="number")for(u&&(t=n[++o]);++o<i;)t=r(t,n[o],o,n);else Lr(n,function(n,e,o){t=u?(u=false,n):r(t,n,e,o)});return t}function z(n,r,t,e){var u=3>arguments.length;return r=X(r,e,4),I(n,function(n,e,o){t=u?(u=false,n):r(t,n,e,o)}),t}function C(n){var r=-1,t=n?n.length:0,e=Array(typeof t=="number"?t:0);return D(n,function(n){var t;t=++r,t=0+Sr(Wr()*(t-0+1)),e[r]=e[t],e[t]=n
+}),e}function P(n,r,t){var e;r=X(r,t,3),t=-1;var u=n?n.length:0;if(typeof u=="number")for(;++t<u&&!(e=r(n[t],t,n)););else Lr(n,function(n,t,u){return(e=r(n,t,u))&&er});return!!e}function U(n,r,t){return t&&E(r)?rr:(t?q:F)(n,r)}function V(n,r,t){var u=0,o=n?n.length:0;if(typeof r!="number"&&null!=r){var i=-1;for(r=X(r,t,3);++i<o&&r(n[i],i,n);)u++}else if(u=r,null==u||t)return n?n[0]:rr;return e(n,0,$r(Mr(0,u),o))}function G(r,t,e){if(typeof e=="number"){var u=r?r.length:0;e=0>e?Mr(0,u+e):e||0}else if(e)return e=J(r,t),r[e]===t?e:-1;
+return n(r,t,e)}function H(n,r,t){if(typeof r!="number"&&null!=r){var u=0,o=-1,i=n?n.length:0;for(r=X(r,t,3);++o<i&&r(n[o],o,n);)u++}else u=null==r||t?1:Mr(0,r);return e(n,u)}function J(n,r,t,e){var u=0,o=n?n.length:u;for(t=t?X(t,e,1):Y,r=t(r);u<o;)e=u+o>>>1,t(n[e])<r?u=e+1:o=e;return u}function K(n,r,t,e){return typeof r!="boolean"&&null!=r&&(e=t,t=typeof r!="function"&&e&&e[r]===n?null:r,r=false),null!=t&&(t=X(t,e,3)),g(n,r,t)}function L(n,r){return 2<arguments.length?v(n,17,e(arguments,2),null,r):v(n,1,null,null,r)
+}function Q(n,r,t){var e,u,o,i,f,a,l,c=0,p=false,s=true;if(!A(n))throw new TypeError;if(r=Mr(0,r)||0,true===t)var g=true,s=false;else O(t)&&(g=t.leading,p="maxWait"in t&&(Mr(r,t.maxWait)||0),s="trailing"in t?t.trailing:s);var h=function(){var t=r-(nt()-i);0<t?a=setTimeout(h,t):(u&&clearTimeout(u),t=l,u=a=l=rr,t&&(c=nt(),o=n.apply(f,e),a||u||(e=f=null)))},v=function(){a&&clearTimeout(a),u=a=l=rr,(s||p!==r)&&(c=nt(),o=n.apply(f,e),a||u||(e=f=null))};return function(){if(e=arguments,i=nt(),f=this,l=s&&(a||!g),false===p)var t=g&&!a;
+else{u||g||(c=i);var y=p-(i-c),m=0>=y;m?(u&&(u=clearTimeout(u)),c=i,o=n.apply(f,e)):u||(u=setTimeout(v,y))}return m&&a?a=clearTimeout(a):a||r===p||(a=setTimeout(h,r)),t&&(m=true,o=n.apply(f,e)),!m||a||u||(e=f=null),o}}function X(n,r,t){var e=typeof n;if(null==n||"function"==e)return a(n,r,t);if("object"!=e)return nr(n);var u=Ur(n);return function(r){for(var t=u.length,e=false;t--&&(e=r[u[t]]===n[u[t]]););return e}}function Y(n){return n}function Z(n){D(x(n),function(r){var t=u[r]=n[r];u.prototype[r]=function(){var n=[this.__wrapped__];
+return Rr.apply(n,arguments),n=t.apply(u,n),this.__chain__?new o(n,true):n}})}function nr(n){return function(r){return r[n]}}var rr,tr=0,er={},ur=+new Date+"",or=/($^)/,ir=/['\n\r\t\u2028\u2029\\]/g,fr="[object Arguments]",ar="[object Array]",lr="[object Boolean]",cr="[object Date]",pr="[object Number]",sr="[object Object]",gr="[object RegExp]",hr="[object String]",vr={"boolean":false,"function":true,object:true,number:false,string:false,undefined:false},yr={"\\":"\\","'":"'","\n":"n","\r":"r","\t":"t","\u2028":"u2028","\u2029":"u2029"},mr=vr[typeof window]&&window||this,_r=vr[typeof exports]&&exports&&!exports.nodeType&&exports,dr=vr[typeof module]&&module&&!module.nodeType&&module,br=dr&&dr.exports===_r&&_r,wr=vr[typeof global]&&global;
+!wr||wr.global!==wr&&wr.window!==wr||(mr=wr);var jr=[],xr=Object.prototype,Tr=mr._,Er=xr.toString,Ar=RegExp("^"+(Er+"").replace(/[.*+?^${}()|[\]\\]/g,"\\$&").replace(/toString| for [^\]]+/g,".*?")+"$"),Or=Math.ceil,Sr=Math.floor,Nr=xr.hasOwnProperty,Rr=jr.push,kr=xr.propertyIsEnumerable,Br=_(Br=Object.create)&&Br,Fr=_(Fr=Array.isArray)&&Fr,qr=mr.isFinite,Dr=mr.isNaN,Ir=_(Ir=Object.keys)&&Ir,Mr=Math.max,$r=Math.min,Wr=Math.random;o.prototype=u.prototype;var zr={};!function(){var n={0:1,length:1};zr.spliceObjects=(jr.splice.call(n,0,1),!n[0])
+}(1),u.templateSettings={escape:/<%-([\s\S]+?)%>/g,evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,variable:""},Br||(f=function(){function n(){}return function(r){if(O(r)){n.prototype=r;var t=new n;n.prototype=null}return t||mr.Object()}}()),b(arguments)||(b=function(n){return n&&typeof n=="object"&&typeof n.length=="number"&&Nr.call(n,"callee")&&!kr.call(n,"callee")||false});var Cr=Fr||function(n){return n&&typeof n=="object"&&typeof n.length=="number"&&Er.call(n)==ar||false},Pr=function(n){var r,t=[];
+if(!n||!vr[typeof n])return t;for(r in n)Nr.call(n,r)&&t.push(r);return t},Ur=Ir?function(n){return O(n)?Ir(n):[]}:Pr,Vr={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;"},Gr=T(Vr),Hr=RegExp("("+Ur(Gr).join("|")+")","g"),Jr=RegExp("["+Ur(Vr).join("")+"]","g"),Kr=function(n,r){var t;if(!n||!vr[typeof n])return n;for(t in n)if(r(n[t],t,n)===er)break;return n},Lr=function(n,r){var t;if(!n||!vr[typeof n])return n;for(t in n)if(Nr.call(n,t)&&r(n[t],t,n)===er)break;return n};A(/x/)&&(A=function(n){return typeof n=="function"&&"[object Function]"==Er.call(n)
+});var Qr=h(function(n,r,t){Nr.call(n,t)?n[t]++:n[t]=1}),Xr=h(function(n,r,t){(Nr.call(n,t)?n[t]:n[t]=[]).push(r)}),Yr=h(function(n,r,t){n[t]=r}),Zr=M,nt=_(nt=Date.now)&&nt||function(){return(new Date).getTime()};u.after=function(n,r){if(!A(r))throw new TypeError;return function(){return 1>--n?r.apply(this,arguments):void 0}},u.bind=L,u.bindAll=function(n){for(var r=1<arguments.length?p(arguments,true,false,1):x(n),t=-1,e=r.length;++t<e;){var u=r[t];n[u]=v(n[u],1,null,null,n)}return n},u.chain=function(n){return n=new o(n),n.__chain__=true,n
+},u.compact=function(n){for(var r=-1,t=n?n.length:0,e=[];++r<t;){var u=n[r];u&&e.push(u)}return e},u.compose=function(){for(var n=arguments,r=n.length;r--;)if(!A(n[r]))throw new TypeError;return function(){for(var r=arguments,t=n.length;t--;)r=[n[t].apply(this,r)];return r[0]}},u.countBy=Qr,u.debounce=Q,u.defaults=j,u.defer=function(n){if(!A(n))throw new TypeError;var r=e(arguments,1);return setTimeout(function(){n.apply(rr,r)},1)},u.delay=function(n,r){if(!A(n))throw new TypeError;var t=e(arguments,2);
+return setTimeout(function(){n.apply(rr,t)},r)},u.difference=function(n){return c(n,p(arguments,true,true,1))},u.filter=F,u.flatten=function(n,r){return p(n,r)},u.forEach=D,u.functions=x,u.groupBy=Xr,u.indexBy=Yr,u.initial=function(n,r,t){var u=0,o=n?n.length:0;if(typeof r!="number"&&null!=r){var i=o;for(r=X(r,t,3);i--&&r(n[i],i,n);)u++}else u=null==r||t?1:r||u;return e(n,0,$r(Mr(0,o-u),o))},u.intersection=function(){for(var n=[],r=-1,t=arguments.length;++r<t;){var e=arguments[r];(Cr(e)||b(e))&&n.push(e)
+}var u=n[0],o=-1,i=m(),f=u?u.length:0,a=[];n:for(;++o<f;)if(e=u[o],0>i(a,e)){for(r=t;--r;)if(0>i(n[r],e))continue n;a.push(e)}return a},u.invert=T,u.invoke=function(n,r){var t=e(arguments,2),u=-1,o=typeof r=="function",i=n?n.length:0,f=Array(typeof i=="number"?i:0);return D(n,function(n){f[++u]=(o?r:n[r]).apply(n,t)}),f},u.keys=Ur,u.map=M,u.max=$,u.memoize=function(n,r){var t={};return function(){var e=r?r.apply(this,arguments):ur+arguments[0];return Nr.call(t,e)?t[e]:t[e]=n.apply(this,arguments)
+}},u.min=function(n,r,t){var e=1/0,u=e;typeof r!="function"&&t&&t[r]===n&&(r=null);var o=-1,i=n?n.length:0;if(null==r&&typeof i=="number")for(;++o<i;)t=n[o],t<u&&(u=t);else r=X(r,t,3),D(n,function(n,t,o){t=r(n,t,o),t<e&&(e=t,u=n)});return u},u.omit=function(n){var r=[];Kr(n,function(n,t){r.push(t)});for(var r=c(r,p(arguments,true,false,1)),t=-1,e=r.length,u={};++t<e;){var o=r[t];u[o]=n[o]}return u},u.once=function(n){var r,t;if(!A(n))throw new TypeError;return function(){return r?t:(r=true,t=n.apply(this,arguments),n=null,t)
+}},u.pairs=function(n){for(var r=-1,t=Ur(n),e=t.length,u=Array(e);++r<e;){var o=t[r];u[r]=[o,n[o]]}return u},u.partial=function(n){return v(n,16,e(arguments,1))},u.pick=function(n){for(var r=-1,t=p(arguments,true,false,1),e=t.length,u={};++r<e;){var o=t[r];o in n&&(u[o]=n[o])}return u},u.pluck=Zr,u.range=function(n,r,t){n=+n||0,t=+t||1,null==r&&(r=n,n=0);var e=-1;r=Mr(0,Or((r-n)/t));for(var u=Array(r);++e<r;)u[e]=n,n+=t;return u},u.reject=function(n,r,t){return r=X(r,t,3),F(n,function(n,t,e){return!r(n,t,e)
+})},u.rest=H,u.shuffle=C,u.sortBy=function(n,t,e){var u=-1,o=n?n.length:0,i=Array(typeof o=="number"?o:0);for(t=X(t,e,3),D(n,function(n,r,e){i[++u]={m:[t(n,r,e)],n:u,o:n}}),o=i.length,i.sort(r);o--;)i[o]=i[o].o;return i},u.tap=function(n,r){return r(n),n},u.throttle=function(n,r,t){var e=true,u=true;if(!A(n))throw new TypeError;return false===t?e=false:O(t)&&(e="leading"in t?t.leading:e,u="trailing"in t?t.trailing:u),t={},t.leading=e,t.maxWait=r,t.trailing=u,Q(n,r,t)},u.times=function(n,r,t){n=-1<(n=+n)?n:0;
+var e=-1,u=Array(n);for(r=a(r,t,1);++e<n;)u[e]=r(e);return u},u.toArray=function(n){return Cr(n)?e(n):n&&typeof n.length=="number"?M(n):R(n)},u.union=function(){return g(p(arguments,true,true))},u.uniq=K,u.values=R,u.where=U,u.without=function(n){return c(n,e(arguments,1))},u.wrap=function(n,r){return v(r,16,[n])},u.zip=function(){for(var n=-1,r=$(Zr(arguments,"length")),t=Array(0>r?0:r);++n<r;)t[n]=Zr(arguments,n);return t},u.collect=M,u.drop=H,u.each=D,u.extend=w,u.methods=x,u.object=function(n,r){var t=-1,e=n?n.length:0,u={};
+for(r||!e||Cr(n[0])||(r=[]);++t<e;){var o=n[t];r?u[o]=r[t]:o&&(u[o[0]]=o[1])}return u},u.select=F,u.tail=H,u.unique=K,u.clone=function(n){return O(n)?Cr(n)?e(n):w({},n):n},u.contains=k,u.escape=function(n){return null==n?"":(n+"").replace(Jr,y)},u.every=B,u.find=q,u.has=function(n,r){return n?Nr.call(n,r):false},u.identity=Y,u.indexOf=G,u.isArguments=b,u.isArray=Cr,u.isBoolean=function(n){return true===n||false===n||n&&typeof n=="object"&&Er.call(n)==lr||false},u.isDate=function(n){return n&&typeof n=="object"&&Er.call(n)==cr||false
+},u.isElement=function(n){return n&&1===n.nodeType||false},u.isEmpty=E,u.isEqual=function(n,r){return s(n,r)},u.isFinite=function(n){return qr(n)&&!Dr(parseFloat(n))},u.isFunction=A,u.isNaN=function(n){return S(n)&&n!=+n},u.isNull=function(n){return null===n},u.isNumber=S,u.isObject=O,u.isRegExp=function(n){return n&&vr[typeof n]&&Er.call(n)==gr||false},u.isString=N,u.isUndefined=function(n){return typeof n=="undefined"},u.lastIndexOf=function(n,r,t){var e=n?n.length:0;for(typeof t=="number"&&(e=(0>t?Mr(0,e+t):$r(t,e-1))+1);e--;)if(n[e]===r)return e;
+return-1},u.mixin=Z,u.noConflict=function(){return mr._=Tr,this},u.random=function(n,r){return null==n&&null==r&&(r=1),n=+n||0,null==r?(r=n,n=0):r=+r||0,n+Sr(Wr()*(r-n+1))},u.reduce=W,u.reduceRight=z,u.result=function(n,r){if(n){var t=n[r];return A(t)?n[r]():t}},u.size=function(n){var r=n?n.length:0;return typeof r=="number"?r:Ur(n).length},u.some=P,u.sortedIndex=J,u.template=function(n,r,e){var o=u,i=o.templateSettings;n=(n||"")+"",e=j({},e,i);var f=0,a="__p+='",i=e.variable;n.replace(RegExp((e.escape||or).source+"|"+(e.interpolate||or).source+"|"+(e.evaluate||or).source+"|$","g"),function(r,e,u,o,i){return a+=n.slice(f,i).replace(ir,t),e&&(a+="'+_.escape("+e+")+'"),o&&(a+="';"+o+";\n__p+='"),u&&(a+="'+((__t=("+u+"))==null?'':__t)+'"),f=i+r.length,r
+}),a+="';",i||(i="obj",a="with("+i+"||{}){"+a+"}"),a="function("+i+"){var __t,__p='',__j=Array.prototype.join;function print(){__p+=__j.call(arguments,'')}"+a+"return __p}";try{var l=Function("_","return "+a)(o)}catch(c){throw c.source=a,c}return r?l(r):(l.source=a,l)},u.unescape=function(n){return null==n?"":(n+"").replace(Hr,d)},u.uniqueId=function(n){var r=++tr+"";return n?n+r:r},u.all=B,u.any=P,u.detect=q,u.findWhere=function(n,r){return U(n,r,true)},u.foldl=W,u.foldr=z,u.include=k,u.inject=W,u.first=V,u.last=function(n,r,t){var u=0,o=n?n.length:0;
+if(typeof r!="number"&&null!=r){var i=o;for(r=X(r,t,3);i--&&r(n[i],i,n);)u++}else if(u=r,null==u||t)return n?n[o-1]:rr;return e(n,Mr(0,o-u))},u.sample=function(n,r,t){return n&&typeof n.length!="number"&&(n=R(n)),null==r||t?n?n[0+Sr(Wr()*(n.length-1-0+1))]:rr:(n=C(n),n.length=$r(Mr(0,r),n.length),n)},u.take=V,u.head=V,Z(u),u.VERSION="2.4.1",u.prototype.chain=function(){return this.__chain__=true,this},u.prototype.value=function(){return this.__wrapped__},D("pop push reverse shift sort splice unshift".split(" "),function(n){var r=jr[n];
+u.prototype[n]=function(){var n=this.__wrapped__;return r.apply(n,arguments),zr.spliceObjects||0!==n.length||delete n[0],this}}),D(["concat","join","slice"],function(n){var r=jr[n];u.prototype[n]=function(){var n=r.apply(this.__wrapped__,arguments);return this.__chain__&&(n=new o(n),n.__chain__=true),n}}),typeof define=="function"&&typeof define.amd=="object"&&define.amd?(mr._=u, define(function(){return u})):_r&&dr?br?(dr.exports=u)._=u:_r._=u:mr._=u}).call(this);
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lodash/lodash.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lodash/lodash.js
new file mode 100644
index 0000000..5b37903
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lodash/lodash.js
@@ -0,0 +1,7179 @@
+/**
+ * @license
+ * Lo-Dash 2.4.1 <http://lodash.com/>
+ * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
+ * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
+ * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Available under MIT license <http://lodash.com/license>
+ */
+;(function() {
+
+  /** Used as a safe reference for `undefined` in pre ES5 environments */
+  var undefined;
+
+  /** Used to pool arrays and objects used internally */
+  var arrayPool = [],
+      objectPool = [];
+
+  /** Used to generate unique IDs */
+  var idCounter = 0;
+
+  /** Used internally to indicate various things */
+  var indicatorObject = {};
+
+  /** Used to prefix keys to avoid issues with `__proto__` and properties on `Object.prototype` */
+  var keyPrefix = +new Date + '';
+
+  /** Used as the size when optimizations are enabled for large arrays */
+  var largeArraySize = 75;
+
+  /** Used as the max size of the `arrayPool` and `objectPool` */
+  var maxPoolSize = 40;
+
+  /** Used to detect and test whitespace */
+  var whitespace = (
+    // whitespace
+    ' \t\x0B\f\xA0\ufeff' +
+
+    // line terminators
+    '\n\r\u2028\u2029' +
+
+    // unicode category "Zs" space separators
+    '\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000'
+  );
+
+  /** Used to match empty string literals in compiled template source */
+  var reEmptyStringLeading = /\b__p \+= '';/g,
+      reEmptyStringMiddle = /\b(__p \+=) '' \+/g,
+      reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g;
+
+  /**
+   * Used to match ES6 template delimiters
+   * http://people.mozilla.org/~jorendorff/es6-draft.html#sec-literals-string-literals
+   */
+  var reEsTemplate = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g;
+
+  /** Used to match regexp flags from their coerced string values */
+  var reFlags = /\w*$/;
+
+  /** Used to detected named functions */
+  var reFuncName = /^\s*function[ \n\r\t]+\w/;
+
+  /** Used to match "interpolate" template delimiters */
+  var reInterpolate = /<%=([\s\S]+?)%>/g;
+
+  /** Used to match leading whitespace and zeros to be removed */
+  var reLeadingSpacesAndZeros = RegExp('^[' + whitespace + ']*0+(?=.$)');
+
+  /** Used to ensure capturing order of template delimiters */
+  var reNoMatch = /($^)/;
+
+  /** Used to detect functions containing a `this` reference */
+  var reThis = /\bthis\b/;
+
+  /** Used to match unescaped characters in compiled string literals */
+  var reUnescapedString = /['\n\r\t\u2028\u2029\\]/g;
+
+  /** Used to assign default `context` object properties */
+  var contextProps = [
+    'Array', 'Boolean', 'Date', 'Error', 'Function', 'Math', 'Number', 'Object',
+    'RegExp', 'String', '_', 'attachEvent', 'clearTimeout', 'isFinite', 'isNaN',
+    'parseInt', 'setTimeout'
+  ];
+
+  /** Used to fix the JScript [[DontEnum]] bug */
+  var shadowedProps = [
+    'constructor', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable',
+    'toLocaleString', 'toString', 'valueOf'
+  ];
+
+  /** Used to make template sourceURLs easier to identify */
+  var templateCounter = 0;
+
+  /** `Object#toString` result shortcuts */
+  var argsClass = '[object Arguments]',
+      arrayClass = '[object Array]',
+      boolClass = '[object Boolean]',
+      dateClass = '[object Date]',
+      errorClass = '[object Error]',
+      funcClass = '[object Function]',
+      numberClass = '[object Number]',
+      objectClass = '[object Object]',
+      regexpClass = '[object RegExp]',
+      stringClass = '[object String]';
+
+  /** Used to identify object classifications that `_.clone` supports */
+  var cloneableClasses = {};
+  cloneableClasses[funcClass] = false;
+  cloneableClasses[argsClass] = cloneableClasses[arrayClass] =
+  cloneableClasses[boolClass] = cloneableClasses[dateClass] =
+  cloneableClasses[numberClass] = cloneableClasses[objectClass] =
+  cloneableClasses[regexpClass] = cloneableClasses[stringClass] = true;
+
+  /** Used as an internal `_.debounce` options object */
+  var debounceOptions = {
+    'leading': false,
+    'maxWait': 0,
+    'trailing': false
+  };
+
+  /** Used as the property descriptor for `__bindData__` */
+  var descriptor = {
+    'configurable': false,
+    'enumerable': false,
+    'value': null,
+    'writable': false
+  };
+
+  /** Used as the data object for `iteratorTemplate` */
+  var iteratorData = {
+    'args': '',
+    'array': null,
+    'bottom': '',
+    'firstArg': '',
+    'init': '',
+    'keys': null,
+    'loop': '',
+    'shadowedProps': null,
+    'support': null,
+    'top': '',
+    'useHas': false
+  };
+
+  /** Used to determine if values are of the language type Object */
+  var objectTypes = {
+    'boolean': false,
+    'function': true,
+    'object': true,
+    'number': false,
+    'string': false,
+    'undefined': false
+  };
+
+  /** Used to escape characters for inclusion in compiled string literals */
+  var stringEscapes = {
+    '\\': '\\',
+    "'": "'",
+    '\n': 'n',
+    '\r': 'r',
+    '\t': 't',
+    '\u2028': 'u2028',
+    '\u2029': 'u2029'
+  };
+
+  /** Used as a reference to the global object */
+  var root = (objectTypes[typeof window] && window) || this;
+
+  /** Detect free variable `exports` */
+  var freeExports = objectTypes[typeof exports] && exports && !exports.nodeType && exports;
+
+  /** Detect free variable `module` */
+  var freeModule = objectTypes[typeof module] && module && !module.nodeType && module;
+
+  /** Detect the popular CommonJS extension `module.exports` */
+  var moduleExports = freeModule && freeModule.exports === freeExports && freeExports;
+
+  /** Detect free variable `global` from Node.js or Browserified code and use it as `root` */
+  var freeGlobal = objectTypes[typeof global] && global;
+  if (freeGlobal && (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal)) {
+    root = freeGlobal;
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * The base implementation of `_.indexOf` without support for binary searches
+   * or `fromIndex` constraints.
+   *
+   * @private
+   * @param {Array} array The array to search.
+   * @param {*} value The value to search for.
+   * @param {number} [fromIndex=0] The index to search from.
+   * @returns {number} Returns the index of the matched value or `-1`.
+   */
+  function baseIndexOf(array, value, fromIndex) {
+    var index = (fromIndex || 0) - 1,
+        length = array ? array.length : 0;
+
+    while (++index < length) {
+      if (array[index] === value) {
+        return index;
+      }
+    }
+    return -1;
+  }
+
+  /**
+   * An implementation of `_.contains` for cache objects that mimics the return
+   * signature of `_.indexOf` by returning `0` if the value is found, else `-1`.
+   *
+   * @private
+   * @param {Object} cache The cache object to inspect.
+   * @param {*} value The value to search for.
+   * @returns {number} Returns `0` if `value` is found, else `-1`.
+   */
+  function cacheIndexOf(cache, value) {
+    var type = typeof value;
+    cache = cache.cache;
+
+    if (type == 'boolean' || value == null) {
+      return cache[value] ? 0 : -1;
+    }
+    if (type != 'number' && type != 'string') {
+      type = 'object';
+    }
+    var key = type == 'number' ? value : keyPrefix + value;
+    cache = (cache = cache[type]) && cache[key];
+
+    return type == 'object'
+      ? (cache && baseIndexOf(cache, value) > -1 ? 0 : -1)
+      : (cache ? 0 : -1);
+  }
+
+  /**
+   * Adds a given value to the corresponding cache object.
+   *
+   * @private
+   * @param {*} value The value to add to the cache.
+   */
+  function cachePush(value) {
+    var cache = this.cache,
+        type = typeof value;
+
+    if (type == 'boolean' || value == null) {
+      cache[value] = true;
+    } else {
+      if (type != 'number' && type != 'string') {
+        type = 'object';
+      }
+      var key = type == 'number' ? value : keyPrefix + value,
+          typeCache = cache[type] || (cache[type] = {});
+
+      if (type == 'object') {
+        (typeCache[key] || (typeCache[key] = [])).push(value);
+      } else {
+        typeCache[key] = true;
+      }
+    }
+  }
+
+  /**
+   * Used by `_.max` and `_.min` as the default callback when a given
+   * collection is a string value.
+   *
+   * @private
+   * @param {string} value The character to inspect.
+   * @returns {number} Returns the code unit of given character.
+   */
+  function charAtCallback(value) {
+    return value.charCodeAt(0);
+  }
+
+  /**
+   * Used by `sortBy` to compare transformed `collection` elements, stable sorting
+   * them in ascending order.
+   *
+   * @private
+   * @param {Object} a The object to compare to `b`.
+   * @param {Object} b The object to compare to `a`.
+   * @returns {number} Returns the sort order indicator of `1` or `-1`.
+   */
+  function compareAscending(a, b) {
+    var ac = a.criteria,
+        bc = b.criteria,
+        index = -1,
+        length = ac.length;
+
+    while (++index < length) {
+      var value = ac[index],
+          other = bc[index];
+
+      if (value !== other) {
+        if (value > other || typeof value == 'undefined') {
+          return 1;
+        }
+        if (value < other || typeof other == 'undefined') {
+          return -1;
+        }
+      }
+    }
+    // Fixes an `Array#sort` bug in the JS engine embedded in Adobe applications
+    // that causes it, under certain circumstances, to return the same value for
+    // `a` and `b`. See https://github.com/jashkenas/underscore/pull/1247
+    //
+    // This also ensures a stable sort in V8 and other engines.
+    // See http://code.google.com/p/v8/issues/detail?id=90
+    return a.index - b.index;
+  }
+
+  /**
+   * Creates a cache object to optimize linear searches of large arrays.
+   *
+   * @private
+   * @param {Array} [array=[]] The array to search.
+   * @returns {null|Object} Returns the cache object or `null` if caching should not be used.
+   */
+  function createCache(array) {
+    var index = -1,
+        length = array.length,
+        first = array[0],
+        mid = array[(length / 2) | 0],
+        last = array[length - 1];
+
+    if (first && typeof first == 'object' &&
+        mid && typeof mid == 'object' && last && typeof last == 'object') {
+      return false;
+    }
+    var cache = getObject();
+    cache['false'] = cache['null'] = cache['true'] = cache['undefined'] = false;
+
+    var result = getObject();
+    result.array = array;
+    result.cache = cache;
+    result.push = cachePush;
+
+    while (++index < length) {
+      result.push(array[index]);
+    }
+    return result;
+  }
+
+  /**
+   * Used by `template` to escape characters for inclusion in compiled
+   * string literals.
+   *
+   * @private
+   * @param {string} match The matched character to escape.
+   * @returns {string} Returns the escaped character.
+   */
+  function escapeStringChar(match) {
+    return '\\' + stringEscapes[match];
+  }
+
+  /**
+   * Gets an array from the array pool or creates a new one if the pool is empty.
+   *
+   * @private
+   * @returns {Array} The array from the pool.
+   */
+  function getArray() {
+    return arrayPool.pop() || [];
+  }
+
+  /**
+   * Gets an object from the object pool or creates a new one if the pool is empty.
+   *
+   * @private
+   * @returns {Object} The object from the pool.
+   */
+  function getObject() {
+    return objectPool.pop() || {
+      'array': null,
+      'cache': null,
+      'criteria': null,
+      'false': false,
+      'index': 0,
+      'null': false,
+      'number': null,
+      'object': null,
+      'push': null,
+      'string': null,
+      'true': false,
+      'undefined': false,
+      'value': null
+    };
+  }
+
+  /**
+   * Checks if `value` is a DOM node in IE < 9.
+   *
+   * @private
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if the `value` is a DOM node, else `false`.
+   */
+  function isNode(value) {
+    // IE < 9 presents DOM nodes as `Object` objects except they have `toString`
+    // methods that are `typeof` "string" and still can coerce nodes to strings
+    return typeof value.toString != 'function' && typeof (value + '') == 'string';
+  }
+
+  /**
+   * Releases the given array back to the array pool.
+   *
+   * @private
+   * @param {Array} [array] The array to release.
+   */
+  function releaseArray(array) {
+    array.length = 0;
+    if (arrayPool.length < maxPoolSize) {
+      arrayPool.push(array);
+    }
+  }
+
+  /**
+   * Releases the given object back to the object pool.
+   *
+   * @private
+   * @param {Object} [object] The object to release.
+   */
+  function releaseObject(object) {
+    var cache = object.cache;
+    if (cache) {
+      releaseObject(cache);
+    }
+    object.array = object.cache = object.criteria = object.object = object.number = object.string = object.value = null;
+    if (objectPool.length < maxPoolSize) {
+      objectPool.push(object);
+    }
+  }
+
+  /**
+   * Slices the `collection` from the `start` index up to, but not including,
+   * the `end` index.
+   *
+   * Note: This function is used instead of `Array#slice` to support node lists
+   * in IE < 9 and to ensure dense arrays are returned.
+   *
+   * @private
+   * @param {Array|Object|string} collection The collection to slice.
+   * @param {number} start The start index.
+   * @param {number} end The end index.
+   * @returns {Array} Returns the new array.
+   */
+  function slice(array, start, end) {
+    start || (start = 0);
+    if (typeof end == 'undefined') {
+      end = array ? array.length : 0;
+    }
+    var index = -1,
+        length = end - start || 0,
+        result = Array(length < 0 ? 0 : length);
+
+    while (++index < length) {
+      result[index] = array[start + index];
+    }
+    return result;
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Create a new `lodash` function using the given context object.
+   *
+   * @static
+   * @memberOf _
+   * @category Utilities
+   * @param {Object} [context=root] The context object.
+   * @returns {Function} Returns the `lodash` function.
+   */
+  function runInContext(context) {
+    // Avoid issues with some ES3 environments that attempt to use values, named
+    // after built-in constructors like `Object`, for the creation of literals.
+    // ES5 clears this up by stating that literals must use built-in constructors.
+    // See http://es5.github.io/#x11.1.5.
+    context = context ? _.defaults(root.Object(), context, _.pick(root, contextProps)) : root;
+
+    /** Native constructor references */
+    var Array = context.Array,
+        Boolean = context.Boolean,
+        Date = context.Date,
+        Error = context.Error,
+        Function = context.Function,
+        Math = context.Math,
+        Number = context.Number,
+        Object = context.Object,
+        RegExp = context.RegExp,
+        String = context.String,
+        TypeError = context.TypeError;
+
+    /**
+     * Used for `Array` method references.
+     *
+     * Normally `Array.prototype` would suffice, however, using an array literal
+     * avoids issues in Narwhal.
+     */
+    var arrayRef = [];
+
+    /** Used for native method references */
+    var errorProto = Error.prototype,
+        objectProto = Object.prototype,
+        stringProto = String.prototype;
+
+    /** Used to restore the original `_` reference in `noConflict` */
+    var oldDash = context._;
+
+    /** Used to resolve the internal [[Class]] of values */
+    var toString = objectProto.toString;
+
+    /** Used to detect if a method is native */
+    var reNative = RegExp('^' +
+      String(toString)
+        .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
+        .replace(/toString| for [^\]]+/g, '.*?') + '$'
+    );
+
+    /** Native method shortcuts */
+    var ceil = Math.ceil,
+        clearTimeout = context.clearTimeout,
+        floor = Math.floor,
+        fnToString = Function.prototype.toString,
+        getPrototypeOf = isNative(getPrototypeOf = Object.getPrototypeOf) && getPrototypeOf,
+        hasOwnProperty = objectProto.hasOwnProperty,
+        push = arrayRef.push,
+        propertyIsEnumerable = objectProto.propertyIsEnumerable,
+        setTimeout = context.setTimeout,
+        splice = arrayRef.splice,
+        unshift = arrayRef.unshift;
+
+    /** Used to set meta data on functions */
+    var defineProperty = (function() {
+      // IE 8 only accepts DOM elements
+      try {
+        var o = {},
+            func = isNative(func = Object.defineProperty) && func,
+            result = func(o, o, o) && func;
+      } catch(e) { }
+      return result;
+    }());
+
+    /* Native method shortcuts for methods with the same name as other `lodash` methods */
+    var nativeCreate = isNative(nativeCreate = Object.create) && nativeCreate,
+        nativeIsArray = isNative(nativeIsArray = Array.isArray) && nativeIsArray,
+        nativeIsFinite = context.isFinite,
+        nativeIsNaN = context.isNaN,
+        nativeKeys = isNative(nativeKeys = Object.keys) && nativeKeys,
+        nativeMax = Math.max,
+        nativeMin = Math.min,
+        nativeParseInt = context.parseInt,
+        nativeRandom = Math.random;
+
+    /** Used to lookup a built-in constructor by [[Class]] */
+    var ctorByClass = {};
+    ctorByClass[arrayClass] = Array;
+    ctorByClass[boolClass] = Boolean;
+    ctorByClass[dateClass] = Date;
+    ctorByClass[funcClass] = Function;
+    ctorByClass[objectClass] = Object;
+    ctorByClass[numberClass] = Number;
+    ctorByClass[regexpClass] = RegExp;
+    ctorByClass[stringClass] = String;
+
+    /** Used to avoid iterating non-enumerable properties in IE < 9 */
+    var nonEnumProps = {};
+    nonEnumProps[arrayClass] = nonEnumProps[dateClass] = nonEnumProps[numberClass] = { 'constructor': true, 'toLocaleString': true, 'toString': true, 'valueOf': true };
+    nonEnumProps[boolClass] = nonEnumProps[stringClass] = { 'constructor': true, 'toString': true, 'valueOf': true };
+    nonEnumProps[errorClass] = nonEnumProps[funcClass] = nonEnumProps[regexpClass] = { 'constructor': true, 'toString': true };
+    nonEnumProps[objectClass] = { 'constructor': true };
+
+    (function() {
+      var length = shadowedProps.length;
+      while (length--) {
+        var key = shadowedProps[length];
+        for (var className in nonEnumProps) {
+          if (hasOwnProperty.call(nonEnumProps, className) && !hasOwnProperty.call(nonEnumProps[className], key)) {
+            nonEnumProps[className][key] = false;
+          }
+        }
+      }
+    }());
+
+    /*--------------------------------------------------------------------------*/
+
+    /**
+     * Creates a `lodash` object which wraps the given value to enable intuitive
+     * method chaining.
+     *
+     * In addition to Lo-Dash methods, wrappers also have the following `Array` methods:
+     * `concat`, `join`, `pop`, `push`, `reverse`, `shift`, `slice`, `sort`, `splice`,
+     * and `unshift`
+     *
+     * Chaining is supported in custom builds as long as the `value` method is
+     * implicitly or explicitly included in the build.
+     *
+     * The chainable wrapper functions are:
+     * `after`, `assign`, `bind`, `bindAll`, `bindKey`, `chain`, `compact`,
+     * `compose`, `concat`, `countBy`, `create`, `createCallback`, `curry`,
+     * `debounce`, `defaults`, `defer`, `delay`, `difference`, `filter`, `flatten`,
+     * `forEach`, `forEachRight`, `forIn`, `forInRight`, `forOwn`, `forOwnRight`,
+     * `functions`, `groupBy`, `indexBy`, `initial`, `intersection`, `invert`,
+     * `invoke`, `keys`, `map`, `max`, `memoize`, `merge`, `min`, `object`, `omit`,
+     * `once`, `pairs`, `partial`, `partialRight`, `pick`, `pluck`, `pull`, `push`,
+     * `range`, `reject`, `remove`, `rest`, `reverse`, `shuffle`, `slice`, `sort`,
+     * `sortBy`, `splice`, `tap`, `throttle`, `times`, `toArray`, `transform`,
+     * `union`, `uniq`, `unshift`, `unzip`, `values`, `where`, `without`, `wrap`,
+     * and `zip`
+     *
+     * The non-chainable wrapper functions are:
+     * `clone`, `cloneDeep`, `contains`, `escape`, `every`, `find`, `findIndex`,
+     * `findKey`, `findLast`, `findLastIndex`, `findLastKey`, `has`, `identity`,
+     * `indexOf`, `isArguments`, `isArray`, `isBoolean`, `isDate`, `isElement`,
+     * `isEmpty`, `isEqual`, `isFinite`, `isFunction`, `isNaN`, `isNull`, `isNumber`,
+     * `isObject`, `isPlainObject`, `isRegExp`, `isString`, `isUndefined`, `join`,
+     * `lastIndexOf`, `mixin`, `noConflict`, `parseInt`, `pop`, `random`, `reduce`,
+     * `reduceRight`, `result`, `shift`, `size`, `some`, `sortedIndex`, `runInContext`,
+     * `template`, `unescape`, `uniqueId`, and `value`
+     *
+     * The wrapper functions `first` and `last` return wrapped values when `n` is
+     * provided, otherwise they return unwrapped values.
+     *
+     * Explicit chaining can be enabled by using the `_.chain` method.
+     *
+     * @name _
+     * @constructor
+     * @category Chaining
+     * @param {*} value The value to wrap in a `lodash` instance.
+     * @returns {Object} Returns a `lodash` instance.
+     * @example
+     *
+     * var wrapped = _([1, 2, 3]);
+     *
+     * // returns an unwrapped value
+     * wrapped.reduce(function(sum, num) {
+     *   return sum + num;
+     * });
+     * // => 6
+     *
+     * // returns a wrapped value
+     * var squares = wrapped.map(function(num) {
+     *   return num * num;
+     * });
+     *
+     * _.isArray(squares);
+     * // => false
+     *
+     * _.isArray(squares.value());
+     * // => true
+     */
+    function lodash(value) {
+      // don't wrap if already wrapped, even if wrapped by a different `lodash` constructor
+      return (value && typeof value == 'object' && !isArray(value) && hasOwnProperty.call(value, '__wrapped__'))
+       ? value
+       : new lodashWrapper(value);
+    }
+
+    /**
+     * A fast path for creating `lodash` wrapper objects.
+     *
+     * @private
+     * @param {*} value The value to wrap in a `lodash` instance.
+     * @param {boolean} chainAll A flag to enable chaining for all methods
+     * @returns {Object} Returns a `lodash` instance.
+     */
+    function lodashWrapper(value, chainAll) {
+      this.__chain__ = !!chainAll;
+      this.__wrapped__ = value;
+    }
+    // ensure `new lodashWrapper` is an instance of `lodash`
+    lodashWrapper.prototype = lodash.prototype;
+
+    /**
+     * An object used to flag environments features.
+     *
+     * @static
+     * @memberOf _
+     * @type Object
+     */
+    var support = lodash.support = {};
+
+    (function() {
+      var ctor = function() { this.x = 1; },
+          object = { '0': 1, 'length': 1 },
+          props = [];
+
+      ctor.prototype = { 'valueOf': 1, 'y': 1 };
+      for (var key in new ctor) { props.push(key); }
+      for (key in arguments) { }
+
+      /**
+       * Detect if an `arguments` object's [[Class]] is resolvable (all but Firefox < 4, IE < 9).
+       *
+       * @memberOf _.support
+       * @type boolean
+       */
+      support.argsClass = toString.call(arguments) == argsClass;
+
+      /**
+       * Detect if `arguments` objects are `Object` objects (all but Narwhal and Opera < 10.5).
+       *
+       * @memberOf _.support
+       * @type boolean
+       */
+      support.argsObject = arguments.constructor == Object && !(arguments instanceof Array);
+
+      /**
+       * Detect if `name` or `message` properties of `Error.prototype` are
+       * enumerable by default. (IE < 9, Safari < 5.1)
+       *
+       * @memberOf _.support
+       * @type boolean
+       */
+      support.enumErrorProps = propertyIsEnumerable.call(errorProto, 'message') || propertyIsEnumerable.call(errorProto, 'name');
+
+      /**
+       * Detect if `prototype` properties are enumerable by default.
+       *
+       * Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1
+       * (if the prototype or a property on the prototype has been set)
+       * incorrectly sets a function's `prototype` property [[Enumerable]]
+       * value to `true`.
+       *
+       * @memberOf _.support
+       * @type boolean
+       */
+      support.enumPrototypes = propertyIsEnumerable.call(ctor, 'prototype');
+
+      /**
+       * Detect if functions can be decompiled by `Function#toString`
+       * (all but PS3 and older Opera mobile browsers & avoided in Windows 8 apps).
+       *
+       * @memberOf _.support
+       * @type boolean
+       */
+      support.funcDecomp = !isNative(context.WinRTError) && reThis.test(runInContext);
+
+      /**
+       * Detect if `Function#name` is supported (all but IE).
+       *
+       * @memberOf _.support
+       * @type boolean
+       */
+      support.funcNames = typeof Function.name == 'string';
+
+      /**
+       * Detect if `arguments` object indexes are non-enumerable
+       * (Firefox < 4, IE < 9, PhantomJS, Safari < 5.1).
+       *
+       * @memberOf _.support
+       * @type boolean
+       */
+      support.nonEnumArgs = key != 0;
+
+      /**
+       * Detect if properties shadowing those on `Object.prototype` are non-enumerable.
+       *
+       * In IE < 9 an objects own properties, shadowing non-enumerable ones, are
+       * made non-enumerable as well (a.k.a the JScript [[DontEnum]] bug).
+       *
+       * @memberOf _.support
+       * @type boolean
+       */
+      support.nonEnumShadows = !/valueOf/.test(props);
+
+      /**
+       * Detect if own properties are iterated after inherited properties (all but IE < 9).
+       *
+       * @memberOf _.support
+       * @type boolean
+       */
+      support.ownLast = props[0] != 'x';
+
+      /**
+       * Detect if `Array#shift` and `Array#splice` augment array-like objects correctly.
+       *
+       * Firefox < 10, IE compatibility mode, and IE < 9 have buggy Array `shift()`
+       * and `splice()` functions that fail to remove the last element, `value[0]`,
+       * of array-like objects even though the `length` property is set to `0`.
+       * The `shift()` method is buggy in IE 8 compatibility mode, while `splice()`
+       * is buggy regardless of mode in IE < 9 and buggy in compatibility mode in IE 9.
+       *
+       * @memberOf _.support
+       * @type boolean
+       */
+      support.spliceObjects = (arrayRef.splice.call(object, 0, 1), !object[0]);
+
+      /**
+       * Detect lack of support for accessing string characters by index.
+       *
+       * IE < 8 can't access characters by index and IE 8 can only access
+       * characters by index on string literals.
+       *
+       * @memberOf _.support
+       * @type boolean
+       */
+      support.unindexedChars = ('x'[0] + Object('x')[0]) != 'xx';
+
+      /**
+       * Detect if a DOM node's [[Class]] is resolvable (all but IE < 9)
+       * and that the JS engine errors when attempting to coerce an object to
+       * a string without a `toString` function.
+       *
+       * @memberOf _.support
+       * @type boolean
+       */
+      try {
+        support.nodeClass = !(toString.call(document) == objectClass && !({ 'toString': 0 } + ''));
+      } catch(e) {
+        support.nodeClass = true;
+      }
+    }(1));
+
+    /**
+     * By default, the template delimiters used by Lo-Dash are similar to those in
+     * embedded Ruby (ERB). Change the following template settings to use alternative
+     * delimiters.
+     *
+     * @static
+     * @memberOf _
+     * @type Object
+     */
+    lodash.templateSettings = {
+
+      /**
+       * Used to detect `data` property values to be HTML-escaped.
+       *
+       * @memberOf _.templateSettings
+       * @type RegExp
+       */
+      'escape': /<%-([\s\S]+?)%>/g,
+
+      /**
+       * Used to detect code to be evaluated.
+       *
+       * @memberOf _.templateSettings
+       * @type RegExp
+       */
+      'evaluate': /<%([\s\S]+?)%>/g,
+
+      /**
+       * Used to detect `data` property values to inject.
+       *
+       * @memberOf _.templateSettings
+       * @type RegExp
+       */
+      'interpolate': reInterpolate,
+
+      /**
+       * Used to reference the data object in the template text.
+       *
+       * @memberOf _.templateSettings
+       * @type string
+       */
+      'variable': '',
+
+      /**
+       * Used to import variables into the compiled template.
+       *
+       * @memberOf _.templateSettings
+       * @type Object
+       */
+      'imports': {
+
+        /**
+         * A reference to the `lodash` function.
+         *
+         * @memberOf _.templateSettings.imports
+         * @type Function
+         */
+        '_': lodash
+      }
+    };
+
+    /*--------------------------------------------------------------------------*/
+
+    /**
+     * The template used to create iterator functions.
+     *
+     * @private
+     * @param {Object} data The data object used to populate the text.
+     * @returns {string} Returns the interpolated text.
+     */
+    var iteratorTemplate = template(
+      // the `iterable` may be reassigned by the `top` snippet
+      'var index, iterable = <%= firstArg %>, ' +
+      // assign the `result` variable an initial value
+      'result = <%= init %>;\n' +
+      // exit early if the first argument is falsey
+      'if (!iterable) return result;\n' +
+      // add code before the iteration branches
+      '<%= top %>;' +
+
+      // array-like iteration:
+      '<% if (array) { %>\n' +
+      'var length = iterable.length; index = -1;\n' +
+      'if (<%= array %>) {' +
+
+      // add support for accessing string characters by index if needed
+      '  <% if (support.unindexedChars) { %>\n' +
+      '  if (isString(iterable)) {\n' +
+      "    iterable = iterable.split('')\n" +
+      '  }' +
+      '  <% } %>\n' +
+
+      // iterate over the array-like value
+      '  while (++index < length) {\n' +
+      '    <%= loop %>;\n' +
+      '  }\n' +
+      '}\n' +
+      'else {' +
+
+      // object iteration:
+      // add support for iterating over `arguments` objects if needed
+      '  <% } else if (support.nonEnumArgs) { %>\n' +
+      '  var length = iterable.length; index = -1;\n' +
+      '  if (length && isArguments(iterable)) {\n' +
+      '    while (++index < length) {\n' +
+      "      index += '';\n" +
+      '      <%= loop %>;\n' +
+      '    }\n' +
+      '  } else {' +
+      '  <% } %>' +
+
+      // avoid iterating over `prototype` properties in older Firefox, Opera, and Safari
+      '  <% if (support.enumPrototypes) { %>\n' +
+      "  var skipProto = typeof iterable == 'function';\n" +
+      '  <% } %>' +
+
+      // avoid iterating over `Error.prototype` properties in older IE and Safari
+      '  <% if (support.enumErrorProps) { %>\n' +
+      '  var skipErrorProps = iterable === errorProto || iterable instanceof Error;\n' +
+      '  <% } %>' +
+
+      // define conditions used in the loop
+      '  <%' +
+      '    var conditions = [];' +
+      '    if (support.enumPrototypes) { conditions.push(\'!(skipProto && index == "prototype")\'); }' +
+      '    if (support.enumErrorProps)  { conditions.push(\'!(skipErrorProps && (index == "message" || index == "name"))\'); }' +
+      '  %>' +
+
+      // iterate own properties using `Object.keys`
+      '  <% if (useHas && keys) { %>\n' +
+      '  var ownIndex = -1,\n' +
+      '      ownProps = objectTypes[typeof iterable] && keys(iterable),\n' +
+      '      length = ownProps ? ownProps.length : 0;\n\n' +
+      '  while (++ownIndex < length) {\n' +
+      '    index = ownProps[ownIndex];\n<%' +
+      "    if (conditions.length) { %>    if (<%= conditions.join(' && ') %>) {\n  <% } %>" +
+      '    <%= loop %>;' +
+      '    <% if (conditions.length) { %>\n    }<% } %>\n' +
+      '  }' +
+
+      // else using a for-in loop
+      '  <% } else { %>\n' +
+      '  for (index in iterable) {\n<%' +
+      '    if (useHas) { conditions.push("hasOwnProperty.call(iterable, index)"); }' +
+      "    if (conditions.length) { %>    if (<%= conditions.join(' && ') %>) {\n  <% } %>" +
+      '    <%= loop %>;' +
+      '    <% if (conditions.length) { %>\n    }<% } %>\n' +
+      '  }' +
+
+      // Because IE < 9 can't set the `[[Enumerable]]` attribute of an
+      // existing property and the `constructor` property of a prototype
+      // defaults to non-enumerable, Lo-Dash skips the `constructor`
+      // property when it infers it's iterating over a `prototype` object.
+      '    <% if (support.nonEnumShadows) { %>\n\n' +
+      '  if (iterable !== objectProto) {\n' +
+      "    var ctor = iterable.constructor,\n" +
+      '        isProto = iterable === (ctor && ctor.prototype),\n' +
+      '        className = iterable === stringProto ? stringClass : iterable === errorProto ? errorClass : toString.call(iterable),\n' +
+      '        nonEnum = nonEnumProps[className];\n' +
+      '      <% for (k = 0; k < 7; k++) { %>\n' +
+      "    index = '<%= shadowedProps[k] %>';\n" +
+      '    if ((!(isProto && nonEnum[index]) && hasOwnProperty.call(iterable, index))<%' +
+      '        if (!useHas) { %> || (!nonEnum[index] && iterable[index] !== objectProto[index])<% }' +
+      '      %>) {\n' +
+      '      <%= loop %>;\n' +
+      '    }' +
+      '      <% } %>\n' +
+      '  }' +
+      '    <% } %>' +
+      '  <% } %>' +
+      '  <% if (array || support.nonEnumArgs) { %>\n}<% } %>\n' +
+
+      // add code to the bottom of the iteration function
+      '<%= bottom %>;\n' +
+      // finally, return the `result`
+      'return result'
+    );
+
+    /*--------------------------------------------------------------------------*/
+
+    /**
+     * The base implementation of `_.bind` that creates the bound function and
+     * sets its meta data.
+     *
+     * @private
+     * @param {Array} bindData The bind data array.
+     * @returns {Function} Returns the new bound function.
+     */
+    function baseBind(bindData) {
+      var func = bindData[0],
+          partialArgs = bindData[2],
+          thisArg = bindData[4];
+
+      function bound() {
+        // `Function#bind` spec
+        // http://es5.github.io/#x15.3.4.5
+        if (partialArgs) {
+          // avoid `arguments` object deoptimizations by using `slice` instead
+          // of `Array.prototype.slice.call` and not assigning `arguments` to a
+          // variable as a ternary expression
+          var args = slice(partialArgs);
+          push.apply(args, arguments);
+        }
+        // mimic the constructor's `return` behavior
+        // http://es5.github.io/#x13.2.2
+        if (this instanceof bound) {
+          // ensure `new bound` is an instance of `func`
+          var thisBinding = baseCreate(func.prototype),
+              result = func.apply(thisBinding, args || arguments);
+          return isObject(result) ? result : thisBinding;
+        }
+        return func.apply(thisArg, args || arguments);
+      }
+      setBindData(bound, bindData);
+      return bound;
+    }
+
+    /**
+     * The base implementation of `_.clone` without argument juggling or support
+     * for `thisArg` binding.
+     *
+     * @private
+     * @param {*} value The value to clone.
+     * @param {boolean} [isDeep=false] Specify a deep clone.
+     * @param {Function} [callback] The function to customize cloning values.
+     * @param {Array} [stackA=[]] Tracks traversed source objects.
+     * @param {Array} [stackB=[]] Associates clones with source counterparts.
+     * @returns {*} Returns the cloned value.
+     */
+    function baseClone(value, isDeep, callback, stackA, stackB) {
+      if (callback) {
+        var result = callback(value);
+        if (typeof result != 'undefined') {
+          return result;
+        }
+      }
+      // inspect [[Class]]
+      var isObj = isObject(value);
+      if (isObj) {
+        var className = toString.call(value);
+        if (!cloneableClasses[className] || (!support.nodeClass && isNode(value))) {
+          return value;
+        }
+        var ctor = ctorByClass[className];
+        switch (className) {
+          case boolClass:
+          case dateClass:
+            return new ctor(+value);
+
+          case numberClass:
+          case stringClass:
+            return new ctor(value);
+
+          case regexpClass:
+            result = ctor(value.source, reFlags.exec(value));
+            result.lastIndex = value.lastIndex;
+            return result;
+        }
+      } else {
+        return value;
+      }
+      var isArr = isArray(value);
+      if (isDeep) {
+        // check for circular references and return corresponding clone
+        var initedStack = !stackA;
+        stackA || (stackA = getArray());
+        stackB || (stackB = getArray());
+
+        var length = stackA.length;
+        while (length--) {
+          if (stackA[length] == value) {
+            return stackB[length];
+          }
+        }
+        result = isArr ? ctor(value.length) : {};
+      }
+      else {
+        result = isArr ? slice(value) : assign({}, value);
+      }
+      // add array properties assigned by `RegExp#exec`
+      if (isArr) {
+        if (hasOwnProperty.call(value, 'index')) {
+          result.index = value.index;
+        }
+        if (hasOwnProperty.call(value, 'input')) {
+          result.input = value.input;
+        }
+      }
+      // exit for shallow clone
+      if (!isDeep) {
+        return result;
+      }
+      // add the source value to the stack of traversed objects
+      // and associate it with its clone
+      stackA.push(value);
+      stackB.push(result);
+
+      // recursively populate clone (susceptible to call stack limits)
+      (isArr ? baseEach : forOwn)(value, function(objValue, key) {
+        result[key] = baseClone(objValue, isDeep, callback, stackA, stackB);
+      });
+
+      if (initedStack) {
+        releaseArray(stackA);
+        releaseArray(stackB);
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.create` without support for assigning
+     * properties to the created object.
+     *
+     * @private
+     * @param {Object} prototype The object to inherit from.
+     * @returns {Object} Returns the new object.
+     */
+    function baseCreate(prototype, properties) {
+      return isObject(prototype) ? nativeCreate(prototype) : {};
+    }
+    // fallback for browsers without `Object.create`
+    if (!nativeCreate) {
+      baseCreate = (function() {
+        function Object() {}
+        return function(prototype) {
+          if (isObject(prototype)) {
+            Object.prototype = prototype;
+            var result = new Object;
+            Object.prototype = null;
+          }
+          return result || context.Object();
+        };
+      }());
+    }
+
+    /**
+     * The base implementation of `_.createCallback` without support for creating
+     * "_.pluck" or "_.where" style callbacks.
+     *
+     * @private
+     * @param {*} [func=identity] The value to convert to a callback.
+     * @param {*} [thisArg] The `this` binding of the created callback.
+     * @param {number} [argCount] The number of arguments the callback accepts.
+     * @returns {Function} Returns a callback function.
+     */
+    function baseCreateCallback(func, thisArg, argCount) {
+      if (typeof func != 'function') {
+        return identity;
+      }
+      // exit early for no `thisArg` or already bound by `Function#bind`
+      if (typeof thisArg == 'undefined' || !('prototype' in func)) {
+        return func;
+      }
+      var bindData = func.__bindData__;
+      if (typeof bindData == 'undefined') {
+        if (support.funcNames) {
+          bindData = !func.name;
+        }
+        bindData = bindData || !support.funcDecomp;
+        if (!bindData) {
+          var source = fnToString.call(func);
+          if (!support.funcNames) {
+            bindData = !reFuncName.test(source);
+          }
+          if (!bindData) {
+            // checks if `func` references the `this` keyword and stores the result
+            bindData = reThis.test(source);
+            setBindData(func, bindData);
+          }
+        }
+      }
+      // exit early if there are no `this` references or `func` is bound
+      if (bindData === false || (bindData !== true && bindData[1] & 1)) {
+        return func;
+      }
+      switch (argCount) {
+        case 1: return function(value) {
+          return func.call(thisArg, value);
+        };
+        case 2: return function(a, b) {
+          return func.call(thisArg, a, b);
+        };
+        case 3: return function(value, index, collection) {
+          return func.call(thisArg, value, index, collection);
+        };
+        case 4: return function(accumulator, value, index, collection) {
+          return func.call(thisArg, accumulator, value, index, collection);
+        };
+      }
+      return bind(func, thisArg);
+    }
+
+    /**
+     * The base implementation of `createWrapper` that creates the wrapper and
+     * sets its meta data.
+     *
+     * @private
+     * @param {Array} bindData The bind data array.
+     * @returns {Function} Returns the new function.
+     */
+    function baseCreateWrapper(bindData) {
+      var func = bindData[0],
+          bitmask = bindData[1],
+          partialArgs = bindData[2],
+          partialRightArgs = bindData[3],
+          thisArg = bindData[4],
+          arity = bindData[5];
+
+      var isBind = bitmask & 1,
+          isBindKey = bitmask & 2,
+          isCurry = bitmask & 4,
+          isCurryBound = bitmask & 8,
+          key = func;
+
+      function bound() {
+        var thisBinding = isBind ? thisArg : this;
+        if (partialArgs) {
+          var args = slice(partialArgs);
+          push.apply(args, arguments);
+        }
+        if (partialRightArgs || isCurry) {
+          args || (args = slice(arguments));
+          if (partialRightArgs) {
+            push.apply(args, partialRightArgs);
+          }
+          if (isCurry && args.length < arity) {
+            bitmask |= 16 & ~32;
+            return baseCreateWrapper([func, (isCurryBound ? bitmask : bitmask & ~3), args, null, thisArg, arity]);
+          }
+        }
+        args || (args = arguments);
+        if (isBindKey) {
+          func = thisBinding[key];
+        }
+        if (this instanceof bound) {
+          thisBinding = baseCreate(func.prototype);
+          var result = func.apply(thisBinding, args);
+          return isObject(result) ? result : thisBinding;
+        }
+        return func.apply(thisBinding, args);
+      }
+      setBindData(bound, bindData);
+      return bound;
+    }
+
+    /**
+     * The base implementation of `_.difference` that accepts a single array
+     * of values to exclude.
+     *
+     * @private
+     * @param {Array} array The array to process.
+     * @param {Array} [values] The array of values to exclude.
+     * @returns {Array} Returns a new array of filtered values.
+     */
+    function baseDifference(array, values) {
+      var index = -1,
+          indexOf = getIndexOf(),
+          length = array ? array.length : 0,
+          isLarge = length >= largeArraySize && indexOf === baseIndexOf,
+          result = [];
+
+      if (isLarge) {
+        var cache = createCache(values);
+        if (cache) {
+          indexOf = cacheIndexOf;
+          values = cache;
+        } else {
+          isLarge = false;
+        }
+      }
+      while (++index < length) {
+        var value = array[index];
+        if (indexOf(values, value) < 0) {
+          result.push(value);
+        }
+      }
+      if (isLarge) {
+        releaseObject(values);
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.flatten` without support for callback
+     * shorthands or `thisArg` binding.
+     *
+     * @private
+     * @param {Array} array The array to flatten.
+     * @param {boolean} [isShallow=false] A flag to restrict flattening to a single level.
+     * @param {boolean} [isStrict=false] A flag to restrict flattening to arrays and `arguments` objects.
+     * @param {number} [fromIndex=0] The index to start from.
+     * @returns {Array} Returns a new flattened array.
+     */
+    function baseFlatten(array, isShallow, isStrict, fromIndex) {
+      var index = (fromIndex || 0) - 1,
+          length = array ? array.length : 0,
+          result = [];
+
+      while (++index < length) {
+        var value = array[index];
+
+        if (value && typeof value == 'object' && typeof value.length == 'number'
+            && (isArray(value) || isArguments(value))) {
+          // recursively flatten arrays (susceptible to call stack limits)
+          if (!isShallow) {
+            value = baseFlatten(value, isShallow, isStrict);
+          }
+          var valIndex = -1,
+              valLength = value.length,
+              resIndex = result.length;
+
+          result.length += valLength;
+          while (++valIndex < valLength) {
+            result[resIndex++] = value[valIndex];
+          }
+        } else if (!isStrict) {
+          result.push(value);
+        }
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.isEqual`, without support for `thisArg` binding,
+     * that allows partial "_.where" style comparisons.
+     *
+     * @private
+     * @param {*} a The value to compare.
+     * @param {*} b The other value to compare.
+     * @param {Function} [callback] The function to customize comparing values.
+     * @param {Function} [isWhere=false] A flag to indicate performing partial comparisons.
+     * @param {Array} [stackA=[]] Tracks traversed `a` objects.
+     * @param {Array} [stackB=[]] Tracks traversed `b` objects.
+     * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
+     */
+    function baseIsEqual(a, b, callback, isWhere, stackA, stackB) {
+      // used to indicate that when comparing objects, `a` has at least the properties of `b`
+      if (callback) {
+        var result = callback(a, b);
+        if (typeof result != 'undefined') {
+          return !!result;
+        }
+      }
+      // exit early for identical values
+      if (a === b) {
+        // treat `+0` vs. `-0` as not equal
+        return a !== 0 || (1 / a == 1 / b);
+      }
+      var type = typeof a,
+          otherType = typeof b;
+
+      // exit early for unlike primitive values
+      if (a === a &&
+          !(a && objectTypes[type]) &&
+          !(b && objectTypes[otherType])) {
+        return false;
+      }
+      // exit early for `null` and `undefined` avoiding ES3's Function#call behavior
+      // http://es5.github.io/#x15.3.4.4
+      if (a == null || b == null) {
+        return a === b;
+      }
+      // compare [[Class]] names
+      var className = toString.call(a),
+          otherClass = toString.call(b);
+
+      if (className == argsClass) {
+        className = objectClass;
+      }
+      if (otherClass == argsClass) {
+        otherClass = objectClass;
+      }
+      if (className != otherClass) {
+        return false;
+      }
+      switch (className) {
+        case boolClass:
+        case dateClass:
+          // coerce dates and booleans to numbers, dates to milliseconds and booleans
+          // to `1` or `0` treating invalid dates coerced to `NaN` as not equal
+          return +a == +b;
+
+        case numberClass:
+          // treat `NaN` vs. `NaN` as equal
+          return (a != +a)
+            ? b != +b
+            // but treat `+0` vs. `-0` as not equal
+            : (a == 0 ? (1 / a == 1 / b) : a == +b);
+
+        case regexpClass:
+        case stringClass:
+          // coerce regexes to strings (http://es5.github.io/#x15.10.6.4)
+          // treat string primitives and their corresponding object instances as equal
+          return a == String(b);
+      }
+      var isArr = className == arrayClass;
+      if (!isArr) {
+        // unwrap any `lodash` wrapped values
+        var aWrapped = hasOwnProperty.call(a, '__wrapped__'),
+            bWrapped = hasOwnProperty.call(b, '__wrapped__');
+
+        if (aWrapped || bWrapped) {
+          return baseIsEqual(aWrapped ? a.__wrapped__ : a, bWrapped ? b.__wrapped__ : b, callback, isWhere, stackA, stackB);
+        }
+        // exit for functions and DOM nodes
+        if (className != objectClass || (!support.nodeClass && (isNode(a) || isNode(b)))) {
+          return false;
+        }
+        // in older versions of Opera, `arguments` objects have `Array` constructors
+        var ctorA = !support.argsObject && isArguments(a) ? Object : a.constructor,
+            ctorB = !support.argsObject && isArguments(b) ? Object : b.constructor;
+
+        // non `Object` object instances with different constructors are not equal
+        if (ctorA != ctorB &&
+              !(isFunction(ctorA) && ctorA instanceof ctorA && isFunction(ctorB) && ctorB instanceof ctorB) &&
+              ('constructor' in a && 'constructor' in b)
+            ) {
+          return false;
+        }
+      }
+      // assume cyclic structures are equal
+      // the algorithm for detecting cyclic structures is adapted from ES 5.1
+      // section 15.12.3, abstract operation `JO` (http://es5.github.io/#x15.12.3)
+      var initedStack = !stackA;
+      stackA || (stackA = getArray());
+      stackB || (stackB = getArray());
+
+      var length = stackA.length;
+      while (length--) {
+        if (stackA[length] == a) {
+          return stackB[length] == b;
+        }
+      }
+      var size = 0;
+      result = true;
+
+      // add `a` and `b` to the stack of traversed objects
+      stackA.push(a);
+      stackB.push(b);
+
+      // recursively compare objects and arrays (susceptible to call stack limits)
+      if (isArr) {
+        // compare lengths to determine if a deep comparison is necessary
+        length = a.length;
+        size = b.length;
+        result = size == length;
+
+        if (result || isWhere) {
+          // deep compare the contents, ignoring non-numeric properties
+          while (size--) {
+            var index = length,
+                value = b[size];
+
+            if (isWhere) {
+              while (index--) {
+                if ((result = baseIsEqual(a[index], value, callback, isWhere, stackA, stackB))) {
+                  break;
+                }
+              }
+            } else if (!(result = baseIsEqual(a[size], value, callback, isWhere, stackA, stackB))) {
+              break;
+            }
+          }
+        }
+      }
+      else {
+        // deep compare objects using `forIn`, instead of `forOwn`, to avoid `Object.keys`
+        // which, in this case, is more costly
+        forIn(b, function(value, key, b) {
+          if (hasOwnProperty.call(b, key)) {
+            // count the number of properties.
+            size++;
+            // deep compare each property value.
+            return (result = hasOwnProperty.call(a, key) && baseIsEqual(a[key], value, callback, isWhere, stackA, stackB));
+          }
+        });
+
+        if (result && !isWhere) {
+          // ensure both objects have the same number of properties
+          forIn(a, function(value, key, a) {
+            if (hasOwnProperty.call(a, key)) {
+              // `size` will be `-1` if `a` has more properties than `b`
+              return (result = --size > -1);
+            }
+          });
+        }
+      }
+      stackA.pop();
+      stackB.pop();
+
+      if (initedStack) {
+        releaseArray(stackA);
+        releaseArray(stackB);
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.merge` without argument juggling or support
+     * for `thisArg` binding.
+     *
+     * @private
+     * @param {Object} object The destination object.
+     * @param {Object} source The source object.
+     * @param {Function} [callback] The function to customize merging properties.
+     * @param {Array} [stackA=[]] Tracks traversed source objects.
+     * @param {Array} [stackB=[]] Associates values with source counterparts.
+     */
+    function baseMerge(object, source, callback, stackA, stackB) {
+      (isArray(source) ? forEach : forOwn)(source, function(source, key) {
+        var found,
+            isArr,
+            result = source,
+            value = object[key];
+
+        if (source && ((isArr = isArray(source)) || isPlainObject(source))) {
+          // avoid merging previously merged cyclic sources
+          var stackLength = stackA.length;
+          while (stackLength--) {
+            if ((found = stackA[stackLength] == source)) {
+              value = stackB[stackLength];
+              break;
+            }
+          }
+          if (!found) {
+            var isShallow;
+            if (callback) {
+              result = callback(value, source);
+              if ((isShallow = typeof result != 'undefined')) {
+                value = result;
+              }
+            }
+            if (!isShallow) {
+              value = isArr
+                ? (isArray(value) ? value : [])
+                : (isPlainObject(value) ? value : {});
+            }
+            // add `source` and associated `value` to the stack of traversed objects
+            stackA.push(source);
+            stackB.push(value);
+
+            // recursively merge objects and arrays (susceptible to call stack limits)
+            if (!isShallow) {
+              baseMerge(value, source, callback, stackA, stackB);
+            }
+          }
+        }
+        else {
+          if (callback) {
+            result = callback(value, source);
+            if (typeof result == 'undefined') {
+              result = source;
+            }
+          }
+          if (typeof result != 'undefined') {
+            value = result;
+          }
+        }
+        object[key] = value;
+      });
+    }
+
+    /**
+     * The base implementation of `_.random` without argument juggling or support
+     * for returning floating-point numbers.
+     *
+     * @private
+     * @param {number} min The minimum possible value.
+     * @param {number} max The maximum possible value.
+     * @returns {number} Returns a random number.
+     */
+    function baseRandom(min, max) {
+      return min + floor(nativeRandom() * (max - min + 1));
+    }
+
+    /**
+     * The base implementation of `_.uniq` without support for callback shorthands
+     * or `thisArg` binding.
+     *
+     * @private
+     * @param {Array} array The array to process.
+     * @param {boolean} [isSorted=false] A flag to indicate that `array` is sorted.
+     * @param {Function} [callback] The function called per iteration.
+     * @returns {Array} Returns a duplicate-value-free array.
+     */
+    function baseUniq(array, isSorted, callback) {
+      var index = -1,
+          indexOf = getIndexOf(),
+          length = array ? array.length : 0,
+          result = [];
+
+      var isLarge = !isSorted && length >= largeArraySize && indexOf === baseIndexOf,
+          seen = (callback || isLarge) ? getArray() : result;
+
+      if (isLarge) {
+        var cache = createCache(seen);
+        indexOf = cacheIndexOf;
+        seen = cache;
+      }
+      while (++index < length) {
+        var value = array[index],
+            computed = callback ? callback(value, index, array) : value;
+
+        if (isSorted
+              ? !index || seen[seen.length - 1] !== computed
+              : indexOf(seen, computed) < 0
+            ) {
+          if (callback || isLarge) {
+            seen.push(computed);
+          }
+          result.push(value);
+        }
+      }
+      if (isLarge) {
+        releaseArray(seen.array);
+        releaseObject(seen);
+      } else if (callback) {
+        releaseArray(seen);
+      }
+      return result;
+    }
+
+    /**
+     * Creates a function that aggregates a collection, creating an object composed
+     * of keys generated from the results of running each element of the collection
+     * through a callback. The given `setter` function sets the keys and values
+     * of the composed object.
+     *
+     * @private
+     * @param {Function} setter The setter function.
+     * @returns {Function} Returns the new aggregator function.
+     */
+    function createAggregator(setter) {
+      return function(collection, callback, thisArg) {
+        var result = {};
+        callback = lodash.createCallback(callback, thisArg, 3);
+
+        if (isArray(collection)) {
+          var index = -1,
+              length = collection.length;
+
+          while (++index < length) {
+            var value = collection[index];
+            setter(result, value, callback(value, index, collection), collection);
+          }
+        } else {
+          baseEach(collection, function(value, key, collection) {
+            setter(result, value, callback(value, key, collection), collection);
+          });
+        }
+        return result;
+      };
+    }
+
+    /**
+     * Creates a function that, when called, either curries or invokes `func`
+     * with an optional `this` binding and partially applied arguments.
+     *
+     * @private
+     * @param {Function|string} func The function or method name to reference.
+     * @param {number} bitmask The bitmask of method flags to compose.
+     *  The bitmask may be composed of the following flags:
+     *  1 - `_.bind`
+     *  2 - `_.bindKey`
+     *  4 - `_.curry`
+     *  8 - `_.curry` (bound)
+     *  16 - `_.partial`
+     *  32 - `_.partialRight`
+     * @param {Array} [partialArgs] An array of arguments to prepend to those
+     *  provided to the new function.
+     * @param {Array} [partialRightArgs] An array of arguments to append to those
+     *  provided to the new function.
+     * @param {*} [thisArg] The `this` binding of `func`.
+     * @param {number} [arity] The arity of `func`.
+     * @returns {Function} Returns the new function.
+     */
+    function createWrapper(func, bitmask, partialArgs, partialRightArgs, thisArg, arity) {
+      var isBind = bitmask & 1,
+          isBindKey = bitmask & 2,
+          isCurry = bitmask & 4,
+          isCurryBound = bitmask & 8,
+          isPartial = bitmask & 16,
+          isPartialRight = bitmask & 32;
+
+      if (!isBindKey && !isFunction(func)) {
+        throw new TypeError;
+      }
+      if (isPartial && !partialArgs.length) {
+        bitmask &= ~16;
+        isPartial = partialArgs = false;
+      }
+      if (isPartialRight && !partialRightArgs.length) {
+        bitmask &= ~32;
+        isPartialRight = partialRightArgs = false;
+      }
+      var bindData = func && func.__bindData__;
+      if (bindData && bindData !== true) {
+        // clone `bindData`
+        bindData = slice(bindData);
+        if (bindData[2]) {
+          bindData[2] = slice(bindData[2]);
+        }
+        if (bindData[3]) {
+          bindData[3] = slice(bindData[3]);
+        }
+        // set `thisBinding` is not previously bound
+        if (isBind && !(bindData[1] & 1)) {
+          bindData[4] = thisArg;
+        }
+        // set if previously bound but not currently (subsequent curried functions)
+        if (!isBind && bindData[1] & 1) {
+          bitmask |= 8;
+        }
+        // set curried arity if not yet set
+        if (isCurry && !(bindData[1] & 4)) {
+          bindData[5] = arity;
+        }
+        // append partial left arguments
+        if (isPartial) {
+          push.apply(bindData[2] || (bindData[2] = []), partialArgs);
+        }
+        // append partial right arguments
+        if (isPartialRight) {
+          unshift.apply(bindData[3] || (bindData[3] = []), partialRightArgs);
+        }
+        // merge flags
+        bindData[1] |= bitmask;
+        return createWrapper.apply(null, bindData);
+      }
+      // fast path for `_.bind`
+      var creater = (bitmask == 1 || bitmask === 17) ? baseBind : baseCreateWrapper;
+      return creater([func, bitmask, partialArgs, partialRightArgs, thisArg, arity]);
+    }
+
+    /**
+     * Creates compiled iteration functions.
+     *
+     * @private
+     * @param {...Object} [options] The compile options object(s).
+     * @param {string} [options.array] Code to determine if the iterable is an array or array-like.
+     * @param {boolean} [options.useHas] Specify using `hasOwnProperty` checks in the object loop.
+     * @param {Function} [options.keys] A reference to `_.keys` for use in own property iteration.
+     * @param {string} [options.args] A comma separated string of iteration function arguments.
+     * @param {string} [options.top] Code to execute before the iteration branches.
+     * @param {string} [options.loop] Code to execute in the object loop.
+     * @param {string} [options.bottom] Code to execute after the iteration branches.
+     * @returns {Function} Returns the compiled function.
+     */
+    function createIterator() {
+      // data properties
+      iteratorData.shadowedProps = shadowedProps;
+      iteratorData.support = support;
+
+      // iterator options
+      iteratorData.array = iteratorData.bottom = iteratorData.loop = iteratorData.top = '';
+      iteratorData.init = 'iterable';
+      iteratorData.useHas = true;
+
+      // merge options into a template data object
+      for (var object, index = 0; object = arguments[index]; index++) {
+        for (var key in object) {
+          iteratorData[key] = object[key];
+        }
+      }
+      var args = iteratorData.args;
+      iteratorData.firstArg = /^[^,]+/.exec(args)[0];
+
+      // create the function factory
+      var factory = Function(
+          'baseCreateCallback, errorClass, errorProto, hasOwnProperty, ' +
+          'indicatorObject, isArguments, isArray, isString, keys, objectProto, ' +
+          'objectTypes, nonEnumProps, stringClass, stringProto, toString',
+        'return function(' + args + ') {\n' + iteratorTemplate(iteratorData) + '\n}'
+      );
+
+      // return the compiled function
+      return factory(
+        baseCreateCallback, errorClass, errorProto, hasOwnProperty,
+        indicatorObject, isArguments, isArray, isString, iteratorData.keys, objectProto,
+        objectTypes, nonEnumProps, stringClass, stringProto, toString
+      );
+    }
+
+    /**
+     * Used by `escape` to convert characters to HTML entities.
+     *
+     * @private
+     * @param {string} match The matched character to escape.
+     * @returns {string} Returns the escaped character.
+     */
+    function escapeHtmlChar(match) {
+      return htmlEscapes[match];
+    }
+
+    /**
+     * Gets the appropriate "indexOf" function. If the `_.indexOf` method is
+     * customized, this method returns the custom method, otherwise it returns
+     * the `baseIndexOf` function.
+     *
+     * @private
+     * @returns {Function} Returns the "indexOf" function.
+     */
+    function getIndexOf() {
+      var result = (result = lodash.indexOf) === indexOf ? baseIndexOf : result;
+      return result;
+    }
+
+    /**
+     * Checks if `value` is a native function.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if the `value` is a native function, else `false`.
+     */
+    function isNative(value) {
+      return typeof value == 'function' && reNative.test(value);
+    }
+
+    /**
+     * Sets `this` binding data on a given function.
+     *
+     * @private
+     * @param {Function} func The function to set data on.
+     * @param {Array} value The data array to set.
+     */
+    var setBindData = !defineProperty ? noop : function(func, value) {
+      descriptor.value = value;
+      defineProperty(func, '__bindData__', descriptor);
+    };
+
+    /**
+     * A fallback implementation of `isPlainObject` which checks if a given value
+     * is an object created by the `Object` constructor, assuming objects created
+     * by the `Object` constructor have no inherited enumerable properties and that
+     * there are no `Object.prototype` extensions.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a plain object, else `false`.
+     */
+    function shimIsPlainObject(value) {
+      var ctor,
+          result;
+
+      // avoid non Object objects, `arguments` objects, and DOM elements
+      if (!(value && toString.call(value) == objectClass) ||
+          (ctor = value.constructor, isFunction(ctor) && !(ctor instanceof ctor)) ||
+          (!support.argsClass && isArguments(value)) ||
+          (!support.nodeClass && isNode(value))) {
+        return false;
+      }
+      // IE < 9 iterates inherited properties before own properties. If the first
+      // iterated property is an object's own property then there are no inherited
+      // enumerable properties.
+      if (support.ownLast) {
+        forIn(value, function(value, key, object) {
+          result = hasOwnProperty.call(object, key);
+          return false;
+        });
+        return result !== false;
+      }
+      // In most environments an object's own properties are iterated before
+      // its inherited properties. If the last iterated property is an object's
+      // own property then there are no inherited enumerable properties.
+      forIn(value, function(value, key) {
+        result = key;
+      });
+      return typeof result == 'undefined' || hasOwnProperty.call(value, result);
+    }
+
+    /**
+     * Used by `unescape` to convert HTML entities to characters.
+     *
+     * @private
+     * @param {string} match The matched character to unescape.
+     * @returns {string} Returns the unescaped character.
+     */
+    function unescapeHtmlChar(match) {
+      return htmlUnescapes[match];
+    }
+
+    /*--------------------------------------------------------------------------*/
+
+    /**
+     * Checks if `value` is an `arguments` object.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if the `value` is an `arguments` object, else `false`.
+     * @example
+     *
+     * (function() { return _.isArguments(arguments); })(1, 2, 3);
+     * // => true
+     *
+     * _.isArguments([1, 2, 3]);
+     * // => false
+     */
+    function isArguments(value) {
+      return value && typeof value == 'object' && typeof value.length == 'number' &&
+        toString.call(value) == argsClass || false;
+    }
+    // fallback for browsers that can't detect `arguments` objects by [[Class]]
+    if (!support.argsClass) {
+      isArguments = function(value) {
+        return value && typeof value == 'object' && typeof value.length == 'number' &&
+          hasOwnProperty.call(value, 'callee') && !propertyIsEnumerable.call(value, 'callee') || false;
+      };
+    }
+
+    /**
+     * Checks if `value` is an array.
+     *
+     * @static
+     * @memberOf _
+     * @type Function
+     * @category Objects
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if the `value` is an array, else `false`.
+     * @example
+     *
+     * (function() { return _.isArray(arguments); })();
+     * // => false
+     *
+     * _.isArray([1, 2, 3]);
+     * // => true
+     */
+    var isArray = nativeIsArray || function(value) {
+      return value && typeof value == 'object' && typeof value.length == 'number' &&
+        toString.call(value) == arrayClass || false;
+    };
+
+    /**
+     * A fallback implementation of `Object.keys` which produces an array of the
+     * given object's own enumerable property names.
+     *
+     * @private
+     * @type Function
+     * @param {Object} object The object to inspect.
+     * @returns {Array} Returns an array of property names.
+     */
+    var shimKeys = createIterator({
+      'args': 'object',
+      'init': '[]',
+      'top': 'if (!(objectTypes[typeof object])) return result',
+      'loop': 'result.push(index)'
+    });
+
+    /**
+     * Creates an array composed of the own enumerable property names of an object.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Object} object The object to inspect.
+     * @returns {Array} Returns an array of property names.
+     * @example
+     *
+     * _.keys({ 'one': 1, 'two': 2, 'three': 3 });
+     * // => ['one', 'two', 'three'] (property order is not guaranteed across environments)
+     */
+    var keys = !nativeKeys ? shimKeys : function(object) {
+      if (!isObject(object)) {
+        return [];
+      }
+      if ((support.enumPrototypes && typeof object == 'function') ||
+          (support.nonEnumArgs && object.length && isArguments(object))) {
+        return shimKeys(object);
+      }
+      return nativeKeys(object);
+    };
+
+    /** Reusable iterator options shared by `each`, `forIn`, and `forOwn` */
+    var eachIteratorOptions = {
+      'args': 'collection, callback, thisArg',
+      'top': "callback = callback && typeof thisArg == 'undefined' ? callback : baseCreateCallback(callback, thisArg, 3)",
+      'array': "typeof length == 'number'",
+      'keys': keys,
+      'loop': 'if (callback(iterable[index], index, collection) === false) return result'
+    };
+
+    /** Reusable iterator options for `assign` and `defaults` */
+    var defaultsIteratorOptions = {
+      'args': 'object, source, guard',
+      'top':
+        'var args = arguments,\n' +
+        '    argsIndex = 0,\n' +
+        "    argsLength = typeof guard == 'number' ? 2 : args.length;\n" +
+        'while (++argsIndex < argsLength) {\n' +
+        '  iterable = args[argsIndex];\n' +
+        '  if (iterable && objectTypes[typeof iterable]) {',
+      'keys': keys,
+      'loop': "if (typeof result[index] == 'undefined') result[index] = iterable[index]",
+      'bottom': '  }\n}'
+    };
+
+    /** Reusable iterator options for `forIn` and `forOwn` */
+    var forOwnIteratorOptions = {
+      'top': 'if (!objectTypes[typeof iterable]) return result;\n' + eachIteratorOptions.top,
+      'array': false
+    };
+
+    /**
+     * Used to convert characters to HTML entities:
+     *
+     * Though the `>` character is escaped for symmetry, characters like `>` and `/`
+     * don't require escaping in HTML and have no special meaning unless they're part
+     * of a tag or an unquoted attribute value.
+     * http://mathiasbynens.be/notes/ambiguous-ampersands (under "semi-related fun fact")
+     */
+    var htmlEscapes = {
+      '&': '&amp;',
+      '<': '&lt;',
+      '>': '&gt;',
+      '"': '&quot;',
+      "'": '&#39;'
+    };
+
+    /** Used to convert HTML entities to characters */
+    var htmlUnescapes = invert(htmlEscapes);
+
+    /** Used to match HTML entities and HTML characters */
+    var reEscapedHtml = RegExp('(' + keys(htmlUnescapes).join('|') + ')', 'g'),
+        reUnescapedHtml = RegExp('[' + keys(htmlEscapes).join('') + ']', 'g');
+
+    /**
+     * A function compiled to iterate `arguments` objects, arrays, objects, and
+     * strings consistenly across environments, executing the callback for each
+     * element in the collection. The callback is bound to `thisArg` and invoked
+     * with three arguments; (value, index|key, collection). Callbacks may exit
+     * iteration early by explicitly returning `false`.
+     *
+     * @private
+     * @type Function
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function} [callback=identity] The function called per iteration.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Array|Object|string} Returns `collection`.
+     */
+    var baseEach = createIterator(eachIteratorOptions);
+
+    /*--------------------------------------------------------------------------*/
+
+    /**
+     * Assigns own enumerable properties of source object(s) to the destination
+     * object. Subsequent sources will overwrite property assignments of previous
+     * sources. If a callback is provided it will be executed to produce the
+     * assigned values. The callback is bound to `thisArg` and invoked with two
+     * arguments; (objectValue, sourceValue).
+     *
+     * @static
+     * @memberOf _
+     * @type Function
+     * @alias extend
+     * @category Objects
+     * @param {Object} object The destination object.
+     * @param {...Object} [source] The source objects.
+     * @param {Function} [callback] The function to customize assigning values.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Object} Returns the destination object.
+     * @example
+     *
+     * _.assign({ 'name': 'fred' }, { 'employer': 'slate' });
+     * // => { 'name': 'fred', 'employer': 'slate' }
+     *
+     * var defaults = _.partialRight(_.assign, function(a, b) {
+     *   return typeof a == 'undefined' ? b : a;
+     * });
+     *
+     * var object = { 'name': 'barney' };
+     * defaults(object, { 'name': 'fred', 'employer': 'slate' });
+     * // => { 'name': 'barney', 'employer': 'slate' }
+     */
+    var assign = createIterator(defaultsIteratorOptions, {
+      'top':
+        defaultsIteratorOptions.top.replace(';',
+          ';\n' +
+          "if (argsLength > 3 && typeof args[argsLength - 2] == 'function') {\n" +
+          '  var callback = baseCreateCallback(args[--argsLength - 1], args[argsLength--], 2);\n' +
+          "} else if (argsLength > 2 && typeof args[argsLength - 1] == 'function') {\n" +
+          '  callback = args[--argsLength];\n' +
+          '}'
+        ),
+      'loop': 'result[index] = callback ? callback(result[index], iterable[index]) : iterable[index]'
+    });
+
+    /**
+     * Creates a clone of `value`. If `isDeep` is `true` nested objects will also
+     * be cloned, otherwise they will be assigned by reference. If a callback
+     * is provided it will be executed to produce the cloned values. If the
+     * callback returns `undefined` cloning will be handled by the method instead.
+     * The callback is bound to `thisArg` and invoked with one argument; (value).
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to clone.
+     * @param {boolean} [isDeep=false] Specify a deep clone.
+     * @param {Function} [callback] The function to customize cloning values.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {*} Returns the cloned value.
+     * @example
+     *
+     * var characters = [
+     *   { 'name': 'barney', 'age': 36 },
+     *   { 'name': 'fred',   'age': 40 }
+     * ];
+     *
+     * var shallow = _.clone(characters);
+     * shallow[0] === characters[0];
+     * // => true
+     *
+     * var deep = _.clone(characters, true);
+     * deep[0] === characters[0];
+     * // => false
+     *
+     * _.mixin({
+     *   'clone': _.partialRight(_.clone, function(value) {
+     *     return _.isElement(value) ? value.cloneNode(false) : undefined;
+     *   })
+     * });
+     *
+     * var clone = _.clone(document.body);
+     * clone.childNodes.length;
+     * // => 0
+     */
+    function clone(value, isDeep, callback, thisArg) {
+      // allows working with "Collections" methods without using their `index`
+      // and `collection` arguments for `isDeep` and `callback`
+      if (typeof isDeep != 'boolean' && isDeep != null) {
+        thisArg = callback;
+        callback = isDeep;
+        isDeep = false;
+      }
+      return baseClone(value, isDeep, typeof callback == 'function' && baseCreateCallback(callback, thisArg, 1));
+    }
+
+    /**
+     * Creates a deep clone of `value`. If a callback is provided it will be
+     * executed to produce the cloned values. If the callback returns `undefined`
+     * cloning will be handled by the method instead. The callback is bound to
+     * `thisArg` and invoked with one argument; (value).
+     *
+     * Note: This method is loosely based on the structured clone algorithm. Functions
+     * and DOM nodes are **not** cloned. The enumerable properties of `arguments` objects and
+     * objects created by constructors other than `Object` are cloned to plain `Object` objects.
+     * See http://www.w3.org/TR/html5/infrastructure.html#internal-structured-cloning-algorithm.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to deep clone.
+     * @param {Function} [callback] The function to customize cloning values.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {*} Returns the deep cloned value.
+     * @example
+     *
+     * var characters = [
+     *   { 'name': 'barney', 'age': 36 },
+     *   { 'name': 'fred',   'age': 40 }
+     * ];
+     *
+     * var deep = _.cloneDeep(characters);
+     * deep[0] === characters[0];
+     * // => false
+     *
+     * var view = {
+     *   'label': 'docs',
+     *   'node': element
+     * };
+     *
+     * var clone = _.cloneDeep(view, function(value) {
+     *   return _.isElement(value) ? value.cloneNode(true) : undefined;
+     * });
+     *
+     * clone.node == view.node;
+     * // => false
+     */
+    function cloneDeep(value, callback, thisArg) {
+      return baseClone(value, true, typeof callback == 'function' && baseCreateCallback(callback, thisArg, 1));
+    }
+
+    /**
+     * Creates an object that inherits from the given `prototype` object. If a
+     * `properties` object is provided its own enumerable properties are assigned
+     * to the created object.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Object} prototype The object to inherit from.
+     * @param {Object} [properties] The properties to assign to the object.
+     * @returns {Object} Returns the new object.
+     * @example
+     *
+     * function Shape() {
+     *   this.x = 0;
+     *   this.y = 0;
+     * }
+     *
+     * function Circle() {
+     *   Shape.call(this);
+     * }
+     *
+     * Circle.prototype = _.create(Shape.prototype, { 'constructor': Circle });
+     *
+     * var circle = new Circle;
+     * circle instanceof Circle;
+     * // => true
+     *
+     * circle instanceof Shape;
+     * // => true
+     */
+    function create(prototype, properties) {
+      var result = baseCreate(prototype);
+      return properties ? assign(result, properties) : result;
+    }
+
+    /**
+     * Assigns own enumerable properties of source object(s) to the destination
+     * object for all destination properties that resolve to `undefined`. Once a
+     * property is set, additional defaults of the same property will be ignored.
+     *
+     * @static
+     * @memberOf _
+     * @type Function
+     * @category Objects
+     * @param {Object} object The destination object.
+     * @param {...Object} [source] The source objects.
+     * @param- {Object} [guard] Allows working with `_.reduce` without using its
+     *  `key` and `object` arguments as sources.
+     * @returns {Object} Returns the destination object.
+     * @example
+     *
+     * var object = { 'name': 'barney' };
+     * _.defaults(object, { 'name': 'fred', 'employer': 'slate' });
+     * // => { 'name': 'barney', 'employer': 'slate' }
+     */
+    var defaults = createIterator(defaultsIteratorOptions);
+
+    /**
+     * This method is like `_.findIndex` except that it returns the key of the
+     * first element that passes the callback check, instead of the element itself.
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Object} object The object to search.
+     * @param {Function|Object|string} [callback=identity] The function called per
+     *  iteration. If a property name or object is provided it will be used to
+     *  create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {string|undefined} Returns the key of the found element, else `undefined`.
+     * @example
+     *
+     * var characters = {
+     *   'barney': {  'age': 36, 'blocked': false },
+     *   'fred': {    'age': 40, 'blocked': true },
+     *   'pebbles': { 'age': 1,  'blocked': false }
+     * };
+     *
+     * _.findKey(characters, function(chr) {
+     *   return chr.age < 40;
+     * });
+     * // => 'barney' (property order is not guaranteed across environments)
+     *
+     * // using "_.where" callback shorthand
+     * _.findKey(characters, { 'age': 1 });
+     * // => 'pebbles'
+     *
+     * // using "_.pluck" callback shorthand
+     * _.findKey(characters, 'blocked');
+     * // => 'fred'
+     */
+    function findKey(object, callback, thisArg) {
+      var result;
+      callback = lodash.createCallback(callback, thisArg, 3);
+      forOwn(object, function(value, key, object) {
+        if (callback(value, key, object)) {
+          result = key;
+          return false;
+        }
+      });
+      return result;
+    }
+
+    /**
+     * This method is like `_.findKey` except that it iterates over elements
+     * of a `collection` in the opposite order.
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Object} object The object to search.
+     * @param {Function|Object|string} [callback=identity] The function called per
+     *  iteration. If a property name or object is provided it will be used to
+     *  create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {string|undefined} Returns the key of the found element, else `undefined`.
+     * @example
+     *
+     * var characters = {
+     *   'barney': {  'age': 36, 'blocked': true },
+     *   'fred': {    'age': 40, 'blocked': false },
+     *   'pebbles': { 'age': 1,  'blocked': true }
+     * };
+     *
+     * _.findLastKey(characters, function(chr) {
+     *   return chr.age < 40;
+     * });
+     * // => returns `pebbles`, assuming `_.findKey` returns `barney`
+     *
+     * // using "_.where" callback shorthand
+     * _.findLastKey(characters, { 'age': 40 });
+     * // => 'fred'
+     *
+     * // using "_.pluck" callback shorthand
+     * _.findLastKey(characters, 'blocked');
+     * // => 'pebbles'
+     */
+    function findLastKey(object, callback, thisArg) {
+      var result;
+      callback = lodash.createCallback(callback, thisArg, 3);
+      forOwnRight(object, function(value, key, object) {
+        if (callback(value, key, object)) {
+          result = key;
+          return false;
+        }
+      });
+      return result;
+    }
+
+    /**
+     * Iterates over own and inherited enumerable properties of an object,
+     * executing the callback for each property. The callback is bound to `thisArg`
+     * and invoked with three arguments; (value, key, object). Callbacks may exit
+     * iteration early by explicitly returning `false`.
+     *
+     * @static
+     * @memberOf _
+     * @type Function
+     * @category Objects
+     * @param {Object} object The object to iterate over.
+     * @param {Function} [callback=identity] The function called per iteration.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Object} Returns `object`.
+     * @example
+     *
+     * function Shape() {
+     *   this.x = 0;
+     *   this.y = 0;
+     * }
+     *
+     * Shape.prototype.move = function(x, y) {
+     *   this.x += x;
+     *   this.y += y;
+     * };
+     *
+     * _.forIn(new Shape, function(value, key) {
+     *   console.log(key);
+     * });
+     * // => logs 'x', 'y', and 'move' (property order is not guaranteed across environments)
+     */
+    var forIn = createIterator(eachIteratorOptions, forOwnIteratorOptions, {
+      'useHas': false
+    });
+
+    /**
+     * This method is like `_.forIn` except that it iterates over elements
+     * of a `collection` in the opposite order.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Object} object The object to iterate over.
+     * @param {Function} [callback=identity] The function called per iteration.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Object} Returns `object`.
+     * @example
+     *
+     * function Shape() {
+     *   this.x = 0;
+     *   this.y = 0;
+     * }
+     *
+     * Shape.prototype.move = function(x, y) {
+     *   this.x += x;
+     *   this.y += y;
+     * };
+     *
+     * _.forInRight(new Shape, function(value, key) {
+     *   console.log(key);
+     * });
+     * // => logs 'move', 'y', and 'x' assuming `_.forIn ` logs 'x', 'y', and 'move'
+     */
+    function forInRight(object, callback, thisArg) {
+      var pairs = [];
+
+      forIn(object, function(value, key) {
+        pairs.push(key, value);
+      });
+
+      var length = pairs.length;
+      callback = baseCreateCallback(callback, thisArg, 3);
+      while (length--) {
+        if (callback(pairs[length--], pairs[length], object) === false) {
+          break;
+        }
+      }
+      return object;
+    }
+
+    /**
+     * Iterates over own enumerable properties of an object, executing the callback
+     * for each property. The callback is bound to `thisArg` and invoked with three
+     * arguments; (value, key, object). Callbacks may exit iteration early by
+     * explicitly returning `false`.
+     *
+     * @static
+     * @memberOf _
+     * @type Function
+     * @category Objects
+     * @param {Object} object The object to iterate over.
+     * @param {Function} [callback=identity] The function called per iteration.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Object} Returns `object`.
+     * @example
+     *
+     * _.forOwn({ '0': 'zero', '1': 'one', 'length': 2 }, function(num, key) {
+     *   console.log(key);
+     * });
+     * // => logs '0', '1', and 'length' (property order is not guaranteed across environments)
+     */
+    var forOwn = createIterator(eachIteratorOptions, forOwnIteratorOptions);
+
+    /**
+     * This method is like `_.forOwn` except that it iterates over elements
+     * of a `collection` in the opposite order.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Object} object The object to iterate over.
+     * @param {Function} [callback=identity] The function called per iteration.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Object} Returns `object`.
+     * @example
+     *
+     * _.forOwnRight({ '0': 'zero', '1': 'one', 'length': 2 }, function(num, key) {
+     *   console.log(key);
+     * });
+     * // => logs 'length', '1', and '0' assuming `_.forOwn` logs '0', '1', and 'length'
+     */
+    function forOwnRight(object, callback, thisArg) {
+      var props = keys(object),
+          length = props.length;
+
+      callback = baseCreateCallback(callback, thisArg, 3);
+      while (length--) {
+        var key = props[length];
+        if (callback(object[key], key, object) === false) {
+          break;
+        }
+      }
+      return object;
+    }
+
+    /**
+     * Creates a sorted array of property names of all enumerable properties,
+     * own and inherited, of `object` that have function values.
+     *
+     * @static
+     * @memberOf _
+     * @alias methods
+     * @category Objects
+     * @param {Object} object The object to inspect.
+     * @returns {Array} Returns an array of property names that have function values.
+     * @example
+     *
+     * _.functions(_);
+     * // => ['all', 'any', 'bind', 'bindAll', 'clone', 'compact', 'compose', ...]
+     */
+    function functions(object) {
+      var result = [];
+      forIn(object, function(value, key) {
+        if (isFunction(value)) {
+          result.push(key);
+        }
+      });
+      return result.sort();
+    }
+
+    /**
+     * Checks if the specified property name exists as a direct property of `object`,
+     * instead of an inherited property.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Object} object The object to inspect.
+     * @param {string} key The name of the property to check.
+     * @returns {boolean} Returns `true` if key is a direct property, else `false`.
+     * @example
+     *
+     * _.has({ 'a': 1, 'b': 2, 'c': 3 }, 'b');
+     * // => true
+     */
+    function has(object, key) {
+      return object ? hasOwnProperty.call(object, key) : false;
+    }
+
+    /**
+     * Creates an object composed of the inverted keys and values of the given object.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Object} object The object to invert.
+     * @returns {Object} Returns the created inverted object.
+     * @example
+     *
+     * _.invert({ 'first': 'fred', 'second': 'barney' });
+     * // => { 'fred': 'first', 'barney': 'second' }
+     */
+    function invert(object) {
+      var index = -1,
+          props = keys(object),
+          length = props.length,
+          result = {};
+
+      while (++index < length) {
+        var key = props[index];
+        result[object[key]] = key;
+      }
+      return result;
+    }
+
+    /**
+     * Checks if `value` is a boolean value.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if the `value` is a boolean value, else `false`.
+     * @example
+     *
+     * _.isBoolean(null);
+     * // => false
+     */
+    function isBoolean(value) {
+      return value === true || value === false ||
+        value && typeof value == 'object' && toString.call(value) == boolClass || false;
+    }
+
+    /**
+     * Checks if `value` is a date.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if the `value` is a date, else `false`.
+     * @example
+     *
+     * _.isDate(new Date);
+     * // => true
+     */
+    function isDate(value) {
+      return value && typeof value == 'object' && toString.call(value) == dateClass || false;
+    }
+
+    /**
+     * Checks if `value` is a DOM element.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if the `value` is a DOM element, else `false`.
+     * @example
+     *
+     * _.isElement(document.body);
+     * // => true
+     */
+    function isElement(value) {
+      return value && value.nodeType === 1 || false;
+    }
+
+    /**
+     * Checks if `value` is empty. Arrays, strings, or `arguments` objects with a
+     * length of `0` and objects with no own enumerable properties are considered
+     * "empty".
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Array|Object|string} value The value to inspect.
+     * @returns {boolean} Returns `true` if the `value` is empty, else `false`.
+     * @example
+     *
+     * _.isEmpty([1, 2, 3]);
+     * // => false
+     *
+     * _.isEmpty({});
+     * // => true
+     *
+     * _.isEmpty('');
+     * // => true
+     */
+    function isEmpty(value) {
+      var result = true;
+      if (!value) {
+        return result;
+      }
+      var className = toString.call(value),
+          length = value.length;
+
+      if ((className == arrayClass || className == stringClass ||
+          (support.argsClass ? className == argsClass : isArguments(value))) ||
+          (className == objectClass && typeof length == 'number' && isFunction(value.splice))) {
+        return !length;
+      }
+      forOwn(value, function() {
+        return (result = false);
+      });
+      return result;
+    }
+
+    /**
+     * Performs a deep comparison between two values to determine if they are
+     * equivalent to each other. If a callback is provided it will be executed
+     * to compare values. If the callback returns `undefined` comparisons will
+     * be handled by the method instead. The callback is bound to `thisArg` and
+     * invoked with two arguments; (a, b).
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} a The value to compare.
+     * @param {*} b The other value to compare.
+     * @param {Function} [callback] The function to customize comparing values.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
+     * @example
+     *
+     * var object = { 'name': 'fred' };
+     * var copy = { 'name': 'fred' };
+     *
+     * object == copy;
+     * // => false
+     *
+     * _.isEqual(object, copy);
+     * // => true
+     *
+     * var words = ['hello', 'goodbye'];
+     * var otherWords = ['hi', 'goodbye'];
+     *
+     * _.isEqual(words, otherWords, function(a, b) {
+     *   var reGreet = /^(?:hello|hi)$/i,
+     *       aGreet = _.isString(a) && reGreet.test(a),
+     *       bGreet = _.isString(b) && reGreet.test(b);
+     *
+     *   return (aGreet || bGreet) ? (aGreet == bGreet) : undefined;
+     * });
+     * // => true
+     */
+    function isEqual(a, b, callback, thisArg) {
+      return baseIsEqual(a, b, typeof callback == 'function' && baseCreateCallback(callback, thisArg, 2));
+    }
+
+    /**
+     * Checks if `value` is, or can be coerced to, a finite number.
+     *
+     * Note: This is not the same as native `isFinite` which will return true for
+     * booleans and empty strings. See http://es5.github.io/#x15.1.2.5.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if the `value` is finite, else `false`.
+     * @example
+     *
+     * _.isFinite(-101);
+     * // => true
+     *
+     * _.isFinite('10');
+     * // => true
+     *
+     * _.isFinite(true);
+     * // => false
+     *
+     * _.isFinite('');
+     * // => false
+     *
+     * _.isFinite(Infinity);
+     * // => false
+     */
+    function isFinite(value) {
+      return nativeIsFinite(value) && !nativeIsNaN(parseFloat(value));
+    }
+
+    /**
+     * Checks if `value` is a function.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if the `value` is a function, else `false`.
+     * @example
+     *
+     * _.isFunction(_);
+     * // => true
+     */
+    function isFunction(value) {
+      return typeof value == 'function';
+    }
+    // fallback for older versions of Chrome and Safari
+    if (isFunction(/x/)) {
+      isFunction = function(value) {
+        return typeof value == 'function' && toString.call(value) == funcClass;
+      };
+    }
+
+    /**
+     * Checks if `value` is the language type of Object.
+     * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if the `value` is an object, else `false`.
+     * @example
+     *
+     * _.isObject({});
+     * // => true
+     *
+     * _.isObject([1, 2, 3]);
+     * // => true
+     *
+     * _.isObject(1);
+     * // => false
+     */
+    function isObject(value) {
+      // check if the value is the ECMAScript language type of Object
+      // http://es5.github.io/#x8
+      // and avoid a V8 bug
+      // http://code.google.com/p/v8/issues/detail?id=2291
+      return !!(value && objectTypes[typeof value]);
+    }
+
+    /**
+     * Checks if `value` is `NaN`.
+     *
+     * Note: This is not the same as native `isNaN` which will return `true` for
+     * `undefined` and other non-numeric values. See http://es5.github.io/#x15.1.2.4.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if the `value` is `NaN`, else `false`.
+     * @example
+     *
+     * _.isNaN(NaN);
+     * // => true
+     *
+     * _.isNaN(new Number(NaN));
+     * // => true
+     *
+     * isNaN(undefined);
+     * // => true
+     *
+     * _.isNaN(undefined);
+     * // => false
+     */
+    function isNaN(value) {
+      // `NaN` as a primitive is the only value that is not equal to itself
+      // (perform the [[Class]] check first to avoid errors with some host objects in IE)
+      return isNumber(value) && value != +value;
+    }
+
+    /**
+     * Checks if `value` is `null`.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if the `value` is `null`, else `false`.
+     * @example
+     *
+     * _.isNull(null);
+     * // => true
+     *
+     * _.isNull(undefined);
+     * // => false
+     */
+    function isNull(value) {
+      return value === null;
+    }
+
+    /**
+     * Checks if `value` is a number.
+     *
+     * Note: `NaN` is considered a number. See http://es5.github.io/#x8.5.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if the `value` is a number, else `false`.
+     * @example
+     *
+     * _.isNumber(8.4 * 5);
+     * // => true
+     */
+    function isNumber(value) {
+      return typeof value == 'number' ||
+        value && typeof value == 'object' && toString.call(value) == numberClass || false;
+    }
+
+    /**
+     * Checks if `value` is an object created by the `Object` constructor.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a plain object, else `false`.
+     * @example
+     *
+     * function Shape() {
+     *   this.x = 0;
+     *   this.y = 0;
+     * }
+     *
+     * _.isPlainObject(new Shape);
+     * // => false
+     *
+     * _.isPlainObject([1, 2, 3]);
+     * // => false
+     *
+     * _.isPlainObject({ 'x': 0, 'y': 0 });
+     * // => true
+     */
+    var isPlainObject = !getPrototypeOf ? shimIsPlainObject : function(value) {
+      if (!(value && toString.call(value) == objectClass) || (!support.argsClass && isArguments(value))) {
+        return false;
+      }
+      var valueOf = value.valueOf,
+          objProto = isNative(valueOf) && (objProto = getPrototypeOf(valueOf)) && getPrototypeOf(objProto);
+
+      return objProto
+        ? (value == objProto || getPrototypeOf(value) == objProto)
+        : shimIsPlainObject(value);
+    };
+
+    /**
+     * Checks if `value` is a regular expression.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if the `value` is a regular expression, else `false`.
+     * @example
+     *
+     * _.isRegExp(/fred/);
+     * // => true
+     */
+    function isRegExp(value) {
+      return value && objectTypes[typeof value] && toString.call(value) == regexpClass || false;
+    }
+
+    /**
+     * Checks if `value` is a string.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if the `value` is a string, else `false`.
+     * @example
+     *
+     * _.isString('fred');
+     * // => true
+     */
+    function isString(value) {
+      return typeof value == 'string' ||
+        value && typeof value == 'object' && toString.call(value) == stringClass || false;
+    }
+
+    /**
+     * Checks if `value` is `undefined`.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if the `value` is `undefined`, else `false`.
+     * @example
+     *
+     * _.isUndefined(void 0);
+     * // => true
+     */
+    function isUndefined(value) {
+      return typeof value == 'undefined';
+    }
+
+    /**
+     * Creates an object with the same keys as `object` and values generated by
+     * running each own enumerable property of `object` through the callback.
+     * The callback is bound to `thisArg` and invoked with three arguments;
+     * (value, key, object).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Object} object The object to iterate over.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Array} Returns a new object with values of the results of each `callback` execution.
+     * @example
+     *
+     * _.mapValues({ 'a': 1, 'b': 2, 'c': 3} , function(num) { return num * 3; });
+     * // => { 'a': 3, 'b': 6, 'c': 9 }
+     *
+     * var characters = {
+     *   'fred': { 'name': 'fred', 'age': 40 },
+     *   'pebbles': { 'name': 'pebbles', 'age': 1 }
+     * };
+     *
+     * // using "_.pluck" callback shorthand
+     * _.mapValues(characters, 'age');
+     * // => { 'fred': 40, 'pebbles': 1 }
+     */
+    function mapValues(object, callback, thisArg) {
+      var result = {};
+      callback = lodash.createCallback(callback, thisArg, 3);
+
+      forOwn(object, function(value, key, object) {
+        result[key] = callback(value, key, object);
+      });
+      return result;
+    }
+
+    /**
+     * Recursively merges own enumerable properties of the source object(s), that
+     * don't resolve to `undefined` into the destination object. Subsequent sources
+     * will overwrite property assignments of previous sources. If a callback is
+     * provided it will be executed to produce the merged values of the destination
+     * and source properties. If the callback returns `undefined` merging will
+     * be handled by the method instead. The callback is bound to `thisArg` and
+     * invoked with two arguments; (objectValue, sourceValue).
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Object} object The destination object.
+     * @param {...Object} [source] The source objects.
+     * @param {Function} [callback] The function to customize merging properties.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Object} Returns the destination object.
+     * @example
+     *
+     * var names = {
+     *   'characters': [
+     *     { 'name': 'barney' },
+     *     { 'name': 'fred' }
+     *   ]
+     * };
+     *
+     * var ages = {
+     *   'characters': [
+     *     { 'age': 36 },
+     *     { 'age': 40 }
+     *   ]
+     * };
+     *
+     * _.merge(names, ages);
+     * // => { 'characters': [{ 'name': 'barney', 'age': 36 }, { 'name': 'fred', 'age': 40 }] }
+     *
+     * var food = {
+     *   'fruits': ['apple'],
+     *   'vegetables': ['beet']
+     * };
+     *
+     * var otherFood = {
+     *   'fruits': ['banana'],
+     *   'vegetables': ['carrot']
+     * };
+     *
+     * _.merge(food, otherFood, function(a, b) {
+     *   return _.isArray(a) ? a.concat(b) : undefined;
+     * });
+     * // => { 'fruits': ['apple', 'banana'], 'vegetables': ['beet', 'carrot] }
+     */
+    function merge(object) {
+      var args = arguments,
+          length = 2;
+
+      if (!isObject(object)) {
+        return object;
+      }
+      // allows working with `_.reduce` and `_.reduceRight` without using
+      // their `index` and `collection` arguments
+      if (typeof args[2] != 'number') {
+        length = args.length;
+      }
+      if (length > 3 && typeof args[length - 2] == 'function') {
+        var callback = baseCreateCallback(args[--length - 1], args[length--], 2);
+      } else if (length > 2 && typeof args[length - 1] == 'function') {
+        callback = args[--length];
+      }
+      var sources = slice(arguments, 1, length),
+          index = -1,
+          stackA = getArray(),
+          stackB = getArray();
+
+      while (++index < length) {
+        baseMerge(object, sources[index], callback, stackA, stackB);
+      }
+      releaseArray(stackA);
+      releaseArray(stackB);
+      return object;
+    }
+
+    /**
+     * Creates a shallow clone of `object` excluding the specified properties.
+     * Property names may be specified as individual arguments or as arrays of
+     * property names. If a callback is provided it will be executed for each
+     * property of `object` omitting the properties the callback returns truey
+     * for. The callback is bound to `thisArg` and invoked with three arguments;
+     * (value, key, object).
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Object} object The source object.
+     * @param {Function|...string|string[]} [callback] The properties to omit or the
+     *  function called per iteration.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Object} Returns an object without the omitted properties.
+     * @example
+     *
+     * _.omit({ 'name': 'fred', 'age': 40 }, 'age');
+     * // => { 'name': 'fred' }
+     *
+     * _.omit({ 'name': 'fred', 'age': 40 }, function(value) {
+     *   return typeof value == 'number';
+     * });
+     * // => { 'name': 'fred' }
+     */
+    function omit(object, callback, thisArg) {
+      var result = {};
+      if (typeof callback != 'function') {
+        var props = [];
+        forIn(object, function(value, key) {
+          props.push(key);
+        });
+        props = baseDifference(props, baseFlatten(arguments, true, false, 1));
+
+        var index = -1,
+            length = props.length;
+
+        while (++index < length) {
+          var key = props[index];
+          result[key] = object[key];
+        }
+      } else {
+        callback = lodash.createCallback(callback, thisArg, 3);
+        forIn(object, function(value, key, object) {
+          if (!callback(value, key, object)) {
+            result[key] = value;
+          }
+        });
+      }
+      return result;
+    }
+
+    /**
+     * Creates a two dimensional array of an object's key-value pairs,
+     * i.e. `[[key1, value1], [key2, value2]]`.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Object} object The object to inspect.
+     * @returns {Array} Returns new array of key-value pairs.
+     * @example
+     *
+     * _.pairs({ 'barney': 36, 'fred': 40 });
+     * // => [['barney', 36], ['fred', 40]] (property order is not guaranteed across environments)
+     */
+    function pairs(object) {
+      var index = -1,
+          props = keys(object),
+          length = props.length,
+          result = Array(length);
+
+      while (++index < length) {
+        var key = props[index];
+        result[index] = [key, object[key]];
+      }
+      return result;
+    }
+
+    /**
+     * Creates a shallow clone of `object` composed of the specified properties.
+     * Property names may be specified as individual arguments or as arrays of
+     * property names. If a callback is provided it will be executed for each
+     * property of `object` picking the properties the callback returns truey
+     * for. The callback is bound to `thisArg` and invoked with three arguments;
+     * (value, key, object).
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Object} object The source object.
+     * @param {Function|...string|string[]} [callback] The function called per
+     *  iteration or property names to pick, specified as individual property
+     *  names or arrays of property names.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Object} Returns an object composed of the picked properties.
+     * @example
+     *
+     * _.pick({ 'name': 'fred', '_userid': 'fred1' }, 'name');
+     * // => { 'name': 'fred' }
+     *
+     * _.pick({ 'name': 'fred', '_userid': 'fred1' }, function(value, key) {
+     *   return key.charAt(0) != '_';
+     * });
+     * // => { 'name': 'fred' }
+     */
+    function pick(object, callback, thisArg) {
+      var result = {};
+      if (typeof callback != 'function') {
+        var index = -1,
+            props = baseFlatten(arguments, true, false, 1),
+            length = isObject(object) ? props.length : 0;
+
+        while (++index < length) {
+          var key = props[index];
+          if (key in object) {
+            result[key] = object[key];
+          }
+        }
+      } else {
+        callback = lodash.createCallback(callback, thisArg, 3);
+        forIn(object, function(value, key, object) {
+          if (callback(value, key, object)) {
+            result[key] = value;
+          }
+        });
+      }
+      return result;
+    }
+
+    /**
+     * An alternative to `_.reduce` this method transforms `object` to a new
+     * `accumulator` object which is the result of running each of its own
+     * enumerable properties through a callback, with each callback execution
+     * potentially mutating the `accumulator` object. The callback is bound to
+     * `thisArg` and invoked with four arguments; (accumulator, value, key, object).
+     * Callbacks may exit iteration early by explicitly returning `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Array|Object} object The object to iterate over.
+     * @param {Function} [callback=identity] The function called per iteration.
+     * @param {*} [accumulator] The custom accumulator value.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {*} Returns the accumulated value.
+     * @example
+     *
+     * var squares = _.transform([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], function(result, num) {
+     *   num *= num;
+     *   if (num % 2) {
+     *     return result.push(num) < 3;
+     *   }
+     * });
+     * // => [1, 9, 25]
+     *
+     * var mapped = _.transform({ 'a': 1, 'b': 2, 'c': 3 }, function(result, num, key) {
+     *   result[key] = num * 3;
+     * });
+     * // => { 'a': 3, 'b': 6, 'c': 9 }
+     */
+    function transform(object, callback, accumulator, thisArg) {
+      var isArr = isArray(object);
+      if (accumulator == null) {
+        if (isArr) {
+          accumulator = [];
+        } else {
+          var ctor = object && object.constructor,
+              proto = ctor && ctor.prototype;
+
+          accumulator = baseCreate(proto);
+        }
+      }
+      if (callback) {
+        callback = lodash.createCallback(callback, thisArg, 4);
+        (isArr ? baseEach : forOwn)(object, function(value, index, object) {
+          return callback(accumulator, value, index, object);
+        });
+      }
+      return accumulator;
+    }
+
+    /**
+     * Creates an array composed of the own enumerable property values of `object`.
+     *
+     * @static
+     * @memberOf _
+     * @category Objects
+     * @param {Object} object The object to inspect.
+     * @returns {Array} Returns an array of property values.
+     * @example
+     *
+     * _.values({ 'one': 1, 'two': 2, 'three': 3 });
+     * // => [1, 2, 3] (property order is not guaranteed across environments)
+     */
+    function values(object) {
+      var index = -1,
+          props = keys(object),
+          length = props.length,
+          result = Array(length);
+
+      while (++index < length) {
+        result[index] = object[props[index]];
+      }
+      return result;
+    }
+
+    /*--------------------------------------------------------------------------*/
+
+    /**
+     * Creates an array of elements from the specified indexes, or keys, of the
+     * `collection`. Indexes may be specified as individual arguments or as arrays
+     * of indexes.
+     *
+     * @static
+     * @memberOf _
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {...(number|number[]|string|string[])} [index] The indexes of `collection`
+     *   to retrieve, specified as individual indexes or arrays of indexes.
+     * @returns {Array} Returns a new array of elements corresponding to the
+     *  provided indexes.
+     * @example
+     *
+     * _.at(['a', 'b', 'c', 'd', 'e'], [0, 2, 4]);
+     * // => ['a', 'c', 'e']
+     *
+     * _.at(['fred', 'barney', 'pebbles'], 0, 2);
+     * // => ['fred', 'pebbles']
+     */
+    function at(collection) {
+      var args = arguments,
+          index = -1,
+          props = baseFlatten(args, true, false, 1),
+          length = (args[2] && args[2][args[1]] === collection) ? 1 : props.length,
+          result = Array(length);
+
+      if (support.unindexedChars && isString(collection)) {
+        collection = collection.split('');
+      }
+      while(++index < length) {
+        result[index] = collection[props[index]];
+      }
+      return result;
+    }
+
+    /**
+     * Checks if a given value is present in a collection using strict equality
+     * for comparisons, i.e. `===`. If `fromIndex` is negative, it is used as the
+     * offset from the end of the collection.
+     *
+     * @static
+     * @memberOf _
+     * @alias include
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {*} target The value to check for.
+     * @param {number} [fromIndex=0] The index to search from.
+     * @returns {boolean} Returns `true` if the `target` element is found, else `false`.
+     * @example
+     *
+     * _.contains([1, 2, 3], 1);
+     * // => true
+     *
+     * _.contains([1, 2, 3], 1, 2);
+     * // => false
+     *
+     * _.contains({ 'name': 'fred', 'age': 40 }, 'fred');
+     * // => true
+     *
+     * _.contains('pebbles', 'eb');
+     * // => true
+     */
+    function contains(collection, target, fromIndex) {
+      var index = -1,
+          indexOf = getIndexOf(),
+          length = collection ? collection.length : 0,
+          result = false;
+
+      fromIndex = (fromIndex < 0 ? nativeMax(0, length + fromIndex) : fromIndex) || 0;
+      if (isArray(collection)) {
+        result = indexOf(collection, target, fromIndex) > -1;
+      } else if (typeof length == 'number') {
+        result = (isString(collection) ? collection.indexOf(target, fromIndex) : indexOf(collection, target, fromIndex)) > -1;
+      } else {
+        baseEach(collection, function(value) {
+          if (++index >= fromIndex) {
+            return !(result = value === target);
+          }
+        });
+      }
+      return result;
+    }
+
+    /**
+     * Creates an object composed of keys generated from the results of running
+     * each element of `collection` through the callback. The corresponding value
+     * of each key is the number of times the key was returned by the callback.
+     * The callback is bound to `thisArg` and invoked with three arguments;
+     * (value, index|key, collection).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Object} Returns the composed aggregate object.
+     * @example
+     *
+     * _.countBy([4.3, 6.1, 6.4], function(num) { return Math.floor(num); });
+     * // => { '4': 1, '6': 2 }
+     *
+     * _.countBy([4.3, 6.1, 6.4], function(num) { return this.floor(num); }, Math);
+     * // => { '4': 1, '6': 2 }
+     *
+     * _.countBy(['one', 'two', 'three'], 'length');
+     * // => { '3': 2, '5': 1 }
+     */
+    var countBy = createAggregator(function(result, value, key) {
+      (hasOwnProperty.call(result, key) ? result[key]++ : result[key] = 1);
+    });
+
+    /**
+     * Checks if the given callback returns truey value for **all** elements of
+     * a collection. The callback is bound to `thisArg` and invoked with three
+     * arguments; (value, index|key, collection).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @alias all
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {boolean} Returns `true` if all elements passed the callback check,
+     *  else `false`.
+     * @example
+     *
+     * _.every([true, 1, null, 'yes']);
+     * // => false
+     *
+     * var characters = [
+     *   { 'name': 'barney', 'age': 36 },
+     *   { 'name': 'fred',   'age': 40 }
+     * ];
+     *
+     * // using "_.pluck" callback shorthand
+     * _.every(characters, 'age');
+     * // => true
+     *
+     * // using "_.where" callback shorthand
+     * _.every(characters, { 'age': 36 });
+     * // => false
+     */
+    function every(collection, callback, thisArg) {
+      var result = true;
+      callback = lodash.createCallback(callback, thisArg, 3);
+
+      if (isArray(collection)) {
+        var index = -1,
+            length = collection.length;
+
+        while (++index < length) {
+          if (!(result = !!callback(collection[index], index, collection))) {
+            break;
+          }
+        }
+      } else {
+        baseEach(collection, function(value, index, collection) {
+          return (result = !!callback(value, index, collection));
+        });
+      }
+      return result;
+    }
+
+    /**
+     * Iterates over elements of a collection, returning an array of all elements
+     * the callback returns truey for. The callback is bound to `thisArg` and
+     * invoked with three arguments; (value, index|key, collection).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @alias select
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Array} Returns a new array of elements that passed the callback check.
+     * @example
+     *
+     * var evens = _.filter([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; });
+     * // => [2, 4, 6]
+     *
+     * var characters = [
+     *   { 'name': 'barney', 'age': 36, 'blocked': false },
+     *   { 'name': 'fred',   'age': 40, 'blocked': true }
+     * ];
+     *
+     * // using "_.pluck" callback shorthand
+     * _.filter(characters, 'blocked');
+     * // => [{ 'name': 'fred', 'age': 40, 'blocked': true }]
+     *
+     * // using "_.where" callback shorthand
+     * _.filter(characters, { 'age': 36 });
+     * // => [{ 'name': 'barney', 'age': 36, 'blocked': false }]
+     */
+    function filter(collection, callback, thisArg) {
+      var result = [];
+      callback = lodash.createCallback(callback, thisArg, 3);
+
+      if (isArray(collection)) {
+        var index = -1,
+            length = collection.length;
+
+        while (++index < length) {
+          var value = collection[index];
+          if (callback(value, index, collection)) {
+            result.push(value);
+          }
+        }
+      } else {
+        baseEach(collection, function(value, index, collection) {
+          if (callback(value, index, collection)) {
+            result.push(value);
+          }
+        });
+      }
+      return result;
+    }
+
+    /**
+     * Iterates over elements of a collection, returning the first element that
+     * the callback returns truey for. The callback is bound to `thisArg` and
+     * invoked with three arguments; (value, index|key, collection).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @alias detect, findWhere
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {*} Returns the found element, else `undefined`.
+     * @example
+     *
+     * var characters = [
+     *   { 'name': 'barney',  'age': 36, 'blocked': false },
+     *   { 'name': 'fred',    'age': 40, 'blocked': true },
+     *   { 'name': 'pebbles', 'age': 1,  'blocked': false }
+     * ];
+     *
+     * _.find(characters, function(chr) {
+     *   return chr.age < 40;
+     * });
+     * // => { 'name': 'barney', 'age': 36, 'blocked': false }
+     *
+     * // using "_.where" callback shorthand
+     * _.find(characters, { 'age': 1 });
+     * // =>  { 'name': 'pebbles', 'age': 1, 'blocked': false }
+     *
+     * // using "_.pluck" callback shorthand
+     * _.find(characters, 'blocked');
+     * // => { 'name': 'fred', 'age': 40, 'blocked': true }
+     */
+    function find(collection, callback, thisArg) {
+      callback = lodash.createCallback(callback, thisArg, 3);
+
+      if (isArray(collection)) {
+        var index = -1,
+            length = collection.length;
+
+        while (++index < length) {
+          var value = collection[index];
+          if (callback(value, index, collection)) {
+            return value;
+          }
+        }
+      } else {
+        var result;
+        baseEach(collection, function(value, index, collection) {
+          if (callback(value, index, collection)) {
+            result = value;
+            return false;
+          }
+        });
+        return result;
+      }
+    }
+
+    /**
+     * This method is like `_.find` except that it iterates over elements
+     * of a `collection` from right to left.
+     *
+     * @static
+     * @memberOf _
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {*} Returns the found element, else `undefined`.
+     * @example
+     *
+     * _.findLast([1, 2, 3, 4], function(num) {
+     *   return num % 2 == 1;
+     * });
+     * // => 3
+     */
+    function findLast(collection, callback, thisArg) {
+      var result;
+      callback = lodash.createCallback(callback, thisArg, 3);
+      forEachRight(collection, function(value, index, collection) {
+        if (callback(value, index, collection)) {
+          result = value;
+          return false;
+        }
+      });
+      return result;
+    }
+
+    /**
+     * Iterates over elements of a collection, executing the callback for each
+     * element. The callback is bound to `thisArg` and invoked with three arguments;
+     * (value, index|key, collection). Callbacks may exit iteration early by
+     * explicitly returning `false`.
+     *
+     * Note: As with other "Collections" methods, objects with a `length` property
+     * are iterated like arrays. To avoid this behavior `_.forIn` or `_.forOwn`
+     * may be used for object iteration.
+     *
+     * @static
+     * @memberOf _
+     * @alias each
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function} [callback=identity] The function called per iteration.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Array|Object|string} Returns `collection`.
+     * @example
+     *
+     * _([1, 2, 3]).forEach(function(num) { console.log(num); }).join(',');
+     * // => logs each number and returns '1,2,3'
+     *
+     * _.forEach({ 'one': 1, 'two': 2, 'three': 3 }, function(num) { console.log(num); });
+     * // => logs each number and returns the object (property order is not guaranteed across environments)
+     */
+    function forEach(collection, callback, thisArg) {
+      if (callback && typeof thisArg == 'undefined' && isArray(collection)) {
+        var index = -1,
+            length = collection.length;
+
+        while (++index < length) {
+          if (callback(collection[index], index, collection) === false) {
+            break;
+          }
+        }
+      } else {
+        baseEach(collection, callback, thisArg);
+      }
+      return collection;
+    }
+
+    /**
+     * This method is like `_.forEach` except that it iterates over elements
+     * of a `collection` from right to left.
+     *
+     * @static
+     * @memberOf _
+     * @alias eachRight
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function} [callback=identity] The function called per iteration.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Array|Object|string} Returns `collection`.
+     * @example
+     *
+     * _([1, 2, 3]).forEachRight(function(num) { console.log(num); }).join(',');
+     * // => logs each number from right to left and returns '3,2,1'
+     */
+    function forEachRight(collection, callback, thisArg) {
+      var iterable = collection,
+          length = collection ? collection.length : 0;
+
+      callback = callback && typeof thisArg == 'undefined' ? callback : baseCreateCallback(callback, thisArg, 3);
+      if (isArray(collection)) {
+        while (length--) {
+          if (callback(collection[length], length, collection) === false) {
+            break;
+          }
+        }
+      } else {
+        if (typeof length != 'number') {
+          var props = keys(collection);
+          length = props.length;
+        } else if (support.unindexedChars && isString(collection)) {
+          iterable = collection.split('');
+        }
+        baseEach(collection, function(value, key, collection) {
+          key = props ? props[--length] : --length;
+          return callback(iterable[key], key, collection);
+        });
+      }
+      return collection;
+    }
+
+    /**
+     * Creates an object composed of keys generated from the results of running
+     * each element of a collection through the callback. The corresponding value
+     * of each key is an array of the elements responsible for generating the key.
+     * The callback is bound to `thisArg` and invoked with three arguments;
+     * (value, index|key, collection).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`
+     *
+     * @static
+     * @memberOf _
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Object} Returns the composed aggregate object.
+     * @example
+     *
+     * _.groupBy([4.2, 6.1, 6.4], function(num) { return Math.floor(num); });
+     * // => { '4': [4.2], '6': [6.1, 6.4] }
+     *
+     * _.groupBy([4.2, 6.1, 6.4], function(num) { return this.floor(num); }, Math);
+     * // => { '4': [4.2], '6': [6.1, 6.4] }
+     *
+     * // using "_.pluck" callback shorthand
+     * _.groupBy(['one', 'two', 'three'], 'length');
+     * // => { '3': ['one', 'two'], '5': ['three'] }
+     */
+    var groupBy = createAggregator(function(result, value, key) {
+      (hasOwnProperty.call(result, key) ? result[key] : result[key] = []).push(value);
+    });
+
+    /**
+     * Creates an object composed of keys generated from the results of running
+     * each element of the collection through the given callback. The corresponding
+     * value of each key is the last element responsible for generating the key.
+     * The callback is bound to `thisArg` and invoked with three arguments;
+     * (value, index|key, collection).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Object} Returns the composed aggregate object.
+     * @example
+     *
+     * var keys = [
+     *   { 'dir': 'left', 'code': 97 },
+     *   { 'dir': 'right', 'code': 100 }
+     * ];
+     *
+     * _.indexBy(keys, 'dir');
+     * // => { 'left': { 'dir': 'left', 'code': 97 }, 'right': { 'dir': 'right', 'code': 100 } }
+     *
+     * _.indexBy(keys, function(key) { return String.fromCharCode(key.code); });
+     * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } }
+     *
+     * _.indexBy(characters, function(key) { this.fromCharCode(key.code); }, String);
+     * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } }
+     */
+    var indexBy = createAggregator(function(result, value, key) {
+      result[key] = value;
+    });
+
+    /**
+     * Invokes the method named by `methodName` on each element in the `collection`
+     * returning an array of the results of each invoked method. Additional arguments
+     * will be provided to each invoked method. If `methodName` is a function it
+     * will be invoked for, and `this` bound to, each element in the `collection`.
+     *
+     * @static
+     * @memberOf _
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|string} methodName The name of the method to invoke or
+     *  the function invoked per iteration.
+     * @param {...*} [arg] Arguments to invoke the method with.
+     * @returns {Array} Returns a new array of the results of each invoked method.
+     * @example
+     *
+     * _.invoke([[5, 1, 7], [3, 2, 1]], 'sort');
+     * // => [[1, 5, 7], [1, 2, 3]]
+     *
+     * _.invoke([123, 456], String.prototype.split, '');
+     * // => [['1', '2', '3'], ['4', '5', '6']]
+     */
+    function invoke(collection, methodName) {
+      var args = slice(arguments, 2),
+          index = -1,
+          isFunc = typeof methodName == 'function',
+          length = collection ? collection.length : 0,
+          result = Array(typeof length == 'number' ? length : 0);
+
+      forEach(collection, function(value) {
+        result[++index] = (isFunc ? methodName : value[methodName]).apply(value, args);
+      });
+      return result;
+    }
+
+    /**
+     * Creates an array of values by running each element in the collection
+     * through the callback. The callback is bound to `thisArg` and invoked with
+     * three arguments; (value, index|key, collection).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @alias collect
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Array} Returns a new array of the results of each `callback` execution.
+     * @example
+     *
+     * _.map([1, 2, 3], function(num) { return num * 3; });
+     * // => [3, 6, 9]
+     *
+     * _.map({ 'one': 1, 'two': 2, 'three': 3 }, function(num) { return num * 3; });
+     * // => [3, 6, 9] (property order is not guaranteed across environments)
+     *
+     * var characters = [
+     *   { 'name': 'barney', 'age': 36 },
+     *   { 'name': 'fred',   'age': 40 }
+     * ];
+     *
+     * // using "_.pluck" callback shorthand
+     * _.map(characters, 'name');
+     * // => ['barney', 'fred']
+     */
+    function map(collection, callback, thisArg) {
+      var index = -1,
+          length = collection ? collection.length : 0,
+          result = Array(typeof length == 'number' ? length : 0);
+
+      callback = lodash.createCallback(callback, thisArg, 3);
+      if (isArray(collection)) {
+        while (++index < length) {
+          result[index] = callback(collection[index], index, collection);
+        }
+      } else {
+        baseEach(collection, function(value, key, collection) {
+          result[++index] = callback(value, key, collection);
+        });
+      }
+      return result;
+    }
+
+    /**
+     * Retrieves the maximum value of a collection. If the collection is empty or
+     * falsey `-Infinity` is returned. If a callback is provided it will be executed
+     * for each value in the collection to generate the criterion by which the value
+     * is ranked. The callback is bound to `thisArg` and invoked with three
+     * arguments; (value, index, collection).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {*} Returns the maximum value.
+     * @example
+     *
+     * _.max([4, 2, 8, 6]);
+     * // => 8
+     *
+     * var characters = [
+     *   { 'name': 'barney', 'age': 36 },
+     *   { 'name': 'fred',   'age': 40 }
+     * ];
+     *
+     * _.max(characters, function(chr) { return chr.age; });
+     * // => { 'name': 'fred', 'age': 40 };
+     *
+     * // using "_.pluck" callback shorthand
+     * _.max(characters, 'age');
+     * // => { 'name': 'fred', 'age': 40 };
+     */
+    function max(collection, callback, thisArg) {
+      var computed = -Infinity,
+          result = computed;
+
+      // allows working with functions like `_.map` without using
+      // their `index` argument as a callback
+      if (typeof callback != 'function' && thisArg && thisArg[callback] === collection) {
+        callback = null;
+      }
+      if (callback == null && isArray(collection)) {
+        var index = -1,
+            length = collection.length;
+
+        while (++index < length) {
+          var value = collection[index];
+          if (value > result) {
+            result = value;
+          }
+        }
+      } else {
+        callback = (callback == null && isString(collection))
+          ? charAtCallback
+          : lodash.createCallback(callback, thisArg, 3);
+
+        baseEach(collection, function(value, index, collection) {
+          var current = callback(value, index, collection);
+          if (current > computed) {
+            computed = current;
+            result = value;
+          }
+        });
+      }
+      return result;
+    }
+
+    /**
+     * Retrieves the minimum value of a collection. If the collection is empty or
+     * falsey `Infinity` is returned. If a callback is provided it will be executed
+     * for each value in the collection to generate the criterion by which the value
+     * is ranked. The callback is bound to `thisArg` and invoked with three
+     * arguments; (value, index, collection).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {*} Returns the minimum value.
+     * @example
+     *
+     * _.min([4, 2, 8, 6]);
+     * // => 2
+     *
+     * var characters = [
+     *   { 'name': 'barney', 'age': 36 },
+     *   { 'name': 'fred',   'age': 40 }
+     * ];
+     *
+     * _.min(characters, function(chr) { return chr.age; });
+     * // => { 'name': 'barney', 'age': 36 };
+     *
+     * // using "_.pluck" callback shorthand
+     * _.min(characters, 'age');
+     * // => { 'name': 'barney', 'age': 36 };
+     */
+    function min(collection, callback, thisArg) {
+      var computed = Infinity,
+          result = computed;
+
+      // allows working with functions like `_.map` without using
+      // their `index` argument as a callback
+      if (typeof callback != 'function' && thisArg && thisArg[callback] === collection) {
+        callback = null;
+      }
+      if (callback == null && isArray(collection)) {
+        var index = -1,
+            length = collection.length;
+
+        while (++index < length) {
+          var value = collection[index];
+          if (value < result) {
+            result = value;
+          }
+        }
+      } else {
+        callback = (callback == null && isString(collection))
+          ? charAtCallback
+          : lodash.createCallback(callback, thisArg, 3);
+
+        baseEach(collection, function(value, index, collection) {
+          var current = callback(value, index, collection);
+          if (current < computed) {
+            computed = current;
+            result = value;
+          }
+        });
+      }
+      return result;
+    }
+
+    /**
+     * Retrieves the value of a specified property from all elements in the collection.
+     *
+     * @static
+     * @memberOf _
+     * @type Function
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {string} property The name of the property to pluck.
+     * @returns {Array} Returns a new array of property values.
+     * @example
+     *
+     * var characters = [
+     *   { 'name': 'barney', 'age': 36 },
+     *   { 'name': 'fred',   'age': 40 }
+     * ];
+     *
+     * _.pluck(characters, 'name');
+     * // => ['barney', 'fred']
+     */
+    var pluck = map;
+
+    /**
+     * Reduces a collection to a value which is the accumulated result of running
+     * each element in the collection through the callback, where each successive
+     * callback execution consumes the return value of the previous execution. If
+     * `accumulator` is not provided the first element of the collection will be
+     * used as the initial `accumulator` value. The callback is bound to `thisArg`
+     * and invoked with four arguments; (accumulator, value, index|key, collection).
+     *
+     * @static
+     * @memberOf _
+     * @alias foldl, inject
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function} [callback=identity] The function called per iteration.
+     * @param {*} [accumulator] Initial value of the accumulator.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {*} Returns the accumulated value.
+     * @example
+     *
+     * var sum = _.reduce([1, 2, 3], function(sum, num) {
+     *   return sum + num;
+     * });
+     * // => 6
+     *
+     * var mapped = _.reduce({ 'a': 1, 'b': 2, 'c': 3 }, function(result, num, key) {
+     *   result[key] = num * 3;
+     *   return result;
+     * }, {});
+     * // => { 'a': 3, 'b': 6, 'c': 9 }
+     */
+    function reduce(collection, callback, accumulator, thisArg) {
+      var noaccum = arguments.length < 3;
+      callback = lodash.createCallback(callback, thisArg, 4);
+
+      if (isArray(collection)) {
+        var index = -1,
+            length = collection.length;
+
+        if (noaccum) {
+          accumulator = collection[++index];
+        }
+        while (++index < length) {
+          accumulator = callback(accumulator, collection[index], index, collection);
+        }
+      } else {
+        baseEach(collection, function(value, index, collection) {
+          accumulator = noaccum
+            ? (noaccum = false, value)
+            : callback(accumulator, value, index, collection)
+        });
+      }
+      return accumulator;
+    }
+
+    /**
+     * This method is like `_.reduce` except that it iterates over elements
+     * of a `collection` from right to left.
+     *
+     * @static
+     * @memberOf _
+     * @alias foldr
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function} [callback=identity] The function called per iteration.
+     * @param {*} [accumulator] Initial value of the accumulator.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {*} Returns the accumulated value.
+     * @example
+     *
+     * var list = [[0, 1], [2, 3], [4, 5]];
+     * var flat = _.reduceRight(list, function(a, b) { return a.concat(b); }, []);
+     * // => [4, 5, 2, 3, 0, 1]
+     */
+    function reduceRight(collection, callback, accumulator, thisArg) {
+      var noaccum = arguments.length < 3;
+      callback = lodash.createCallback(callback, thisArg, 4);
+      forEachRight(collection, function(value, index, collection) {
+        accumulator = noaccum
+          ? (noaccum = false, value)
+          : callback(accumulator, value, index, collection);
+      });
+      return accumulator;
+    }
+
+    /**
+     * The opposite of `_.filter` this method returns the elements of a
+     * collection that the callback does **not** return truey for.
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Array} Returns a new array of elements that failed the callback check.
+     * @example
+     *
+     * var odds = _.reject([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; });
+     * // => [1, 3, 5]
+     *
+     * var characters = [
+     *   { 'name': 'barney', 'age': 36, 'blocked': false },
+     *   { 'name': 'fred',   'age': 40, 'blocked': true }
+     * ];
+     *
+     * // using "_.pluck" callback shorthand
+     * _.reject(characters, 'blocked');
+     * // => [{ 'name': 'barney', 'age': 36, 'blocked': false }]
+     *
+     * // using "_.where" callback shorthand
+     * _.reject(characters, { 'age': 36 });
+     * // => [{ 'name': 'fred', 'age': 40, 'blocked': true }]
+     */
+    function reject(collection, callback, thisArg) {
+      callback = lodash.createCallback(callback, thisArg, 3);
+      return filter(collection, function(value, index, collection) {
+        return !callback(value, index, collection);
+      });
+    }
+
+    /**
+     * Retrieves a random element or `n` random elements from a collection.
+     *
+     * @static
+     * @memberOf _
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to sample.
+     * @param {number} [n] The number of elements to sample.
+     * @param- {Object} [guard] Allows working with functions like `_.map`
+     *  without using their `index` arguments as `n`.
+     * @returns {Array} Returns the random sample(s) of `collection`.
+     * @example
+     *
+     * _.sample([1, 2, 3, 4]);
+     * // => 2
+     *
+     * _.sample([1, 2, 3, 4], 2);
+     * // => [3, 1]
+     */
+    function sample(collection, n, guard) {
+      if (collection && typeof collection.length != 'number') {
+        collection = values(collection);
+      } else if (support.unindexedChars && isString(collection)) {
+        collection = collection.split('');
+      }
+      if (n == null || guard) {
+        return collection ? collection[baseRandom(0, collection.length - 1)] : undefined;
+      }
+      var result = shuffle(collection);
+      result.length = nativeMin(nativeMax(0, n), result.length);
+      return result;
+    }
+
+    /**
+     * Creates an array of shuffled values, using a version of the Fisher-Yates
+     * shuffle. See http://en.wikipedia.org/wiki/Fisher-Yates_shuffle.
+     *
+     * @static
+     * @memberOf _
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to shuffle.
+     * @returns {Array} Returns a new shuffled collection.
+     * @example
+     *
+     * _.shuffle([1, 2, 3, 4, 5, 6]);
+     * // => [4, 1, 6, 3, 5, 2]
+     */
+    function shuffle(collection) {
+      var index = -1,
+          length = collection ? collection.length : 0,
+          result = Array(typeof length == 'number' ? length : 0);
+
+      forEach(collection, function(value) {
+        var rand = baseRandom(0, ++index);
+        result[index] = result[rand];
+        result[rand] = value;
+      });
+      return result;
+    }
+
+    /**
+     * Gets the size of the `collection` by returning `collection.length` for arrays
+     * and array-like objects or the number of own enumerable properties for objects.
+     *
+     * @static
+     * @memberOf _
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to inspect.
+     * @returns {number} Returns `collection.length` or number of own enumerable properties.
+     * @example
+     *
+     * _.size([1, 2]);
+     * // => 2
+     *
+     * _.size({ 'one': 1, 'two': 2, 'three': 3 });
+     * // => 3
+     *
+     * _.size('pebbles');
+     * // => 7
+     */
+    function size(collection) {
+      var length = collection ? collection.length : 0;
+      return typeof length == 'number' ? length : keys(collection).length;
+    }
+
+    /**
+     * Checks if the callback returns a truey value for **any** element of a
+     * collection. The function returns as soon as it finds a passing value and
+     * does not iterate over the entire collection. The callback is bound to
+     * `thisArg` and invoked with three arguments; (value, index|key, collection).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @alias any
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {boolean} Returns `true` if any element passed the callback check,
+     *  else `false`.
+     * @example
+     *
+     * _.some([null, 0, 'yes', false], Boolean);
+     * // => true
+     *
+     * var characters = [
+     *   { 'name': 'barney', 'age': 36, 'blocked': false },
+     *   { 'name': 'fred',   'age': 40, 'blocked': true }
+     * ];
+     *
+     * // using "_.pluck" callback shorthand
+     * _.some(characters, 'blocked');
+     * // => true
+     *
+     * // using "_.where" callback shorthand
+     * _.some(characters, { 'age': 1 });
+     * // => false
+     */
+    function some(collection, callback, thisArg) {
+      var result;
+      callback = lodash.createCallback(callback, thisArg, 3);
+
+      if (isArray(collection)) {
+        var index = -1,
+            length = collection.length;
+
+        while (++index < length) {
+          if ((result = callback(collection[index], index, collection))) {
+            break;
+          }
+        }
+      } else {
+        baseEach(collection, function(value, index, collection) {
+          return !(result = callback(value, index, collection));
+        });
+      }
+      return !!result;
+    }
+
+    /**
+     * Creates an array of elements, sorted in ascending order by the results of
+     * running each element in a collection through the callback. This method
+     * performs a stable sort, that is, it will preserve the original sort order
+     * of equal elements. The callback is bound to `thisArg` and invoked with
+     * three arguments; (value, index|key, collection).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an array of property names is provided for `callback` the collection
+     * will be sorted by each property value.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Array|Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Array} Returns a new array of sorted elements.
+     * @example
+     *
+     * _.sortBy([1, 2, 3], function(num) { return Math.sin(num); });
+     * // => [3, 1, 2]
+     *
+     * _.sortBy([1, 2, 3], function(num) { return this.sin(num); }, Math);
+     * // => [3, 1, 2]
+     *
+     * var characters = [
+     *   { 'name': 'barney',  'age': 36 },
+     *   { 'name': 'fred',    'age': 40 },
+     *   { 'name': 'barney',  'age': 26 },
+     *   { 'name': 'fred',    'age': 30 }
+     * ];
+     *
+     * // using "_.pluck" callback shorthand
+     * _.map(_.sortBy(characters, 'age'), _.values);
+     * // => [['barney', 26], ['fred', 30], ['barney', 36], ['fred', 40]]
+     *
+     * // sorting by multiple properties
+     * _.map(_.sortBy(characters, ['name', 'age']), _.values);
+     * // = > [['barney', 26], ['barney', 36], ['fred', 30], ['fred', 40]]
+     */
+    function sortBy(collection, callback, thisArg) {
+      var index = -1,
+          isArr = isArray(callback),
+          length = collection ? collection.length : 0,
+          result = Array(typeof length == 'number' ? length : 0);
+
+      if (!isArr) {
+        callback = lodash.createCallback(callback, thisArg, 3);
+      }
+      forEach(collection, function(value, key, collection) {
+        var object = result[++index] = getObject();
+        if (isArr) {
+          object.criteria = map(callback, function(key) { return value[key]; });
+        } else {
+          (object.criteria = getArray())[0] = callback(value, key, collection);
+        }
+        object.index = index;
+        object.value = value;
+      });
+
+      length = result.length;
+      result.sort(compareAscending);
+      while (length--) {
+        var object = result[length];
+        result[length] = object.value;
+        if (!isArr) {
+          releaseArray(object.criteria);
+        }
+        releaseObject(object);
+      }
+      return result;
+    }
+
+    /**
+     * Converts the `collection` to an array.
+     *
+     * @static
+     * @memberOf _
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to convert.
+     * @returns {Array} Returns the new converted array.
+     * @example
+     *
+     * (function() { return _.toArray(arguments).slice(1); })(1, 2, 3, 4);
+     * // => [2, 3, 4]
+     */
+    function toArray(collection) {
+      if (collection && typeof collection.length == 'number') {
+        return (support.unindexedChars && isString(collection))
+          ? collection.split('')
+          : slice(collection);
+      }
+      return values(collection);
+    }
+
+    /**
+     * Performs a deep comparison of each element in a `collection` to the given
+     * `properties` object, returning an array of all elements that have equivalent
+     * property values.
+     *
+     * @static
+     * @memberOf _
+     * @type Function
+     * @category Collections
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Object} props The object of property values to filter by.
+     * @returns {Array} Returns a new array of elements that have the given properties.
+     * @example
+     *
+     * var characters = [
+     *   { 'name': 'barney', 'age': 36, 'pets': ['hoppy'] },
+     *   { 'name': 'fred',   'age': 40, 'pets': ['baby puss', 'dino'] }
+     * ];
+     *
+     * _.where(characters, { 'age': 36 });
+     * // => [{ 'name': 'barney', 'age': 36, 'pets': ['hoppy'] }]
+     *
+     * _.where(characters, { 'pets': ['dino'] });
+     * // => [{ 'name': 'fred', 'age': 40, 'pets': ['baby puss', 'dino'] }]
+     */
+    var where = filter;
+
+    /*--------------------------------------------------------------------------*/
+
+    /**
+     * Creates an array with all falsey values removed. The values `false`, `null`,
+     * `0`, `""`, `undefined`, and `NaN` are all falsey.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {Array} array The array to compact.
+     * @returns {Array} Returns a new array of filtered values.
+     * @example
+     *
+     * _.compact([0, 1, false, 2, '', 3]);
+     * // => [1, 2, 3]
+     */
+    function compact(array) {
+      var index = -1,
+          length = array ? array.length : 0,
+          result = [];
+
+      while (++index < length) {
+        var value = array[index];
+        if (value) {
+          result.push(value);
+        }
+      }
+      return result;
+    }
+
+    /**
+     * Creates an array excluding all values of the provided arrays using strict
+     * equality for comparisons, i.e. `===`.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {Array} array The array to process.
+     * @param {...Array} [values] The arrays of values to exclude.
+     * @returns {Array} Returns a new array of filtered values.
+     * @example
+     *
+     * _.difference([1, 2, 3, 4, 5], [5, 2, 10]);
+     * // => [1, 3, 4]
+     */
+    function difference(array) {
+      return baseDifference(array, baseFlatten(arguments, true, true, 1));
+    }
+
+    /**
+     * This method is like `_.find` except that it returns the index of the first
+     * element that passes the callback check, instead of the element itself.
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {Array} array The array to search.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {number} Returns the index of the found element, else `-1`.
+     * @example
+     *
+     * var characters = [
+     *   { 'name': 'barney',  'age': 36, 'blocked': false },
+     *   { 'name': 'fred',    'age': 40, 'blocked': true },
+     *   { 'name': 'pebbles', 'age': 1,  'blocked': false }
+     * ];
+     *
+     * _.findIndex(characters, function(chr) {
+     *   return chr.age < 20;
+     * });
+     * // => 2
+     *
+     * // using "_.where" callback shorthand
+     * _.findIndex(characters, { 'age': 36 });
+     * // => 0
+     *
+     * // using "_.pluck" callback shorthand
+     * _.findIndex(characters, 'blocked');
+     * // => 1
+     */
+    function findIndex(array, callback, thisArg) {
+      var index = -1,
+          length = array ? array.length : 0;
+
+      callback = lodash.createCallback(callback, thisArg, 3);
+      while (++index < length) {
+        if (callback(array[index], index, array)) {
+          return index;
+        }
+      }
+      return -1;
+    }
+
+    /**
+     * This method is like `_.findIndex` except that it iterates over elements
+     * of a `collection` from right to left.
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {Array} array The array to search.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {number} Returns the index of the found element, else `-1`.
+     * @example
+     *
+     * var characters = [
+     *   { 'name': 'barney',  'age': 36, 'blocked': true },
+     *   { 'name': 'fred',    'age': 40, 'blocked': false },
+     *   { 'name': 'pebbles', 'age': 1,  'blocked': true }
+     * ];
+     *
+     * _.findLastIndex(characters, function(chr) {
+     *   return chr.age > 30;
+     * });
+     * // => 1
+     *
+     * // using "_.where" callback shorthand
+     * _.findLastIndex(characters, { 'age': 36 });
+     * // => 0
+     *
+     * // using "_.pluck" callback shorthand
+     * _.findLastIndex(characters, 'blocked');
+     * // => 2
+     */
+    function findLastIndex(array, callback, thisArg) {
+      var length = array ? array.length : 0;
+      callback = lodash.createCallback(callback, thisArg, 3);
+      while (length--) {
+        if (callback(array[length], length, array)) {
+          return length;
+        }
+      }
+      return -1;
+    }
+
+    /**
+     * Gets the first element or first `n` elements of an array. If a callback
+     * is provided elements at the beginning of the array are returned as long
+     * as the callback returns truey. The callback is bound to `thisArg` and
+     * invoked with three arguments; (value, index, array).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @alias head, take
+     * @category Arrays
+     * @param {Array} array The array to query.
+     * @param {Function|Object|number|string} [callback] The function called
+     *  per element or the number of elements to return. If a property name or
+     *  object is provided it will be used to create a "_.pluck" or "_.where"
+     *  style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {*} Returns the first element(s) of `array`.
+     * @example
+     *
+     * _.first([1, 2, 3]);
+     * // => 1
+     *
+     * _.first([1, 2, 3], 2);
+     * // => [1, 2]
+     *
+     * _.first([1, 2, 3], function(num) {
+     *   return num < 3;
+     * });
+     * // => [1, 2]
+     *
+     * var characters = [
+     *   { 'name': 'barney',  'blocked': true,  'employer': 'slate' },
+     *   { 'name': 'fred',    'blocked': false, 'employer': 'slate' },
+     *   { 'name': 'pebbles', 'blocked': true,  'employer': 'na' }
+     * ];
+     *
+     * // using "_.pluck" callback shorthand
+     * _.first(characters, 'blocked');
+     * // => [{ 'name': 'barney', 'blocked': true, 'employer': 'slate' }]
+     *
+     * // using "_.where" callback shorthand
+     * _.pluck(_.first(characters, { 'employer': 'slate' }), 'name');
+     * // => ['barney', 'fred']
+     */
+    function first(array, callback, thisArg) {
+      var n = 0,
+          length = array ? array.length : 0;
+
+      if (typeof callback != 'number' && callback != null) {
+        var index = -1;
+        callback = lodash.createCallback(callback, thisArg, 3);
+        while (++index < length && callback(array[index], index, array)) {
+          n++;
+        }
+      } else {
+        n = callback;
+        if (n == null || thisArg) {
+          return array ? array[0] : undefined;
+        }
+      }
+      return slice(array, 0, nativeMin(nativeMax(0, n), length));
+    }
+
+    /**
+     * Flattens a nested array (the nesting can be to any depth). If `isShallow`
+     * is truey, the array will only be flattened a single level. If a callback
+     * is provided each element of the array is passed through the callback before
+     * flattening. The callback is bound to `thisArg` and invoked with three
+     * arguments; (value, index, array).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {Array} array The array to flatten.
+     * @param {boolean} [isShallow=false] A flag to restrict flattening to a single level.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Array} Returns a new flattened array.
+     * @example
+     *
+     * _.flatten([1, [2], [3, [[4]]]]);
+     * // => [1, 2, 3, 4];
+     *
+     * _.flatten([1, [2], [3, [[4]]]], true);
+     * // => [1, 2, 3, [[4]]];
+     *
+     * var characters = [
+     *   { 'name': 'barney', 'age': 30, 'pets': ['hoppy'] },
+     *   { 'name': 'fred',   'age': 40, 'pets': ['baby puss', 'dino'] }
+     * ];
+     *
+     * // using "_.pluck" callback shorthand
+     * _.flatten(characters, 'pets');
+     * // => ['hoppy', 'baby puss', 'dino']
+     */
+    function flatten(array, isShallow, callback, thisArg) {
+      // juggle arguments
+      if (typeof isShallow != 'boolean' && isShallow != null) {
+        thisArg = callback;
+        callback = (typeof isShallow != 'function' && thisArg && thisArg[isShallow] === array) ? null : isShallow;
+        isShallow = false;
+      }
+      if (callback != null) {
+        array = map(array, callback, thisArg);
+      }
+      return baseFlatten(array, isShallow);
+    }
+
+    /**
+     * Gets the index at which the first occurrence of `value` is found using
+     * strict equality for comparisons, i.e. `===`. If the array is already sorted
+     * providing `true` for `fromIndex` will run a faster binary search.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {Array} array The array to search.
+     * @param {*} value The value to search for.
+     * @param {boolean|number} [fromIndex=0] The index to search from or `true`
+     *  to perform a binary search on a sorted array.
+     * @returns {number} Returns the index of the matched value or `-1`.
+     * @example
+     *
+     * _.indexOf([1, 2, 3, 1, 2, 3], 2);
+     * // => 1
+     *
+     * _.indexOf([1, 2, 3, 1, 2, 3], 2, 3);
+     * // => 4
+     *
+     * _.indexOf([1, 1, 2, 2, 3, 3], 2, true);
+     * // => 2
+     */
+    function indexOf(array, value, fromIndex) {
+      if (typeof fromIndex == 'number') {
+        var length = array ? array.length : 0;
+        fromIndex = (fromIndex < 0 ? nativeMax(0, length + fromIndex) : fromIndex || 0);
+      } else if (fromIndex) {
+        var index = sortedIndex(array, value);
+        return array[index] === value ? index : -1;
+      }
+      return baseIndexOf(array, value, fromIndex);
+    }
+
+    /**
+     * Gets all but the last element or last `n` elements of an array. If a
+     * callback is provided elements at the end of the array are excluded from
+     * the result as long as the callback returns truey. The callback is bound
+     * to `thisArg` and invoked with three arguments; (value, index, array).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {Array} array The array to query.
+     * @param {Function|Object|number|string} [callback=1] The function called
+     *  per element or the number of elements to exclude. If a property name or
+     *  object is provided it will be used to create a "_.pluck" or "_.where"
+     *  style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Array} Returns a slice of `array`.
+     * @example
+     *
+     * _.initial([1, 2, 3]);
+     * // => [1, 2]
+     *
+     * _.initial([1, 2, 3], 2);
+     * // => [1]
+     *
+     * _.initial([1, 2, 3], function(num) {
+     *   return num > 1;
+     * });
+     * // => [1]
+     *
+     * var characters = [
+     *   { 'name': 'barney',  'blocked': false, 'employer': 'slate' },
+     *   { 'name': 'fred',    'blocked': true,  'employer': 'slate' },
+     *   { 'name': 'pebbles', 'blocked': true,  'employer': 'na' }
+     * ];
+     *
+     * // using "_.pluck" callback shorthand
+     * _.initial(characters, 'blocked');
+     * // => [{ 'name': 'barney',  'blocked': false, 'employer': 'slate' }]
+     *
+     * // using "_.where" callback shorthand
+     * _.pluck(_.initial(characters, { 'employer': 'na' }), 'name');
+     * // => ['barney', 'fred']
+     */
+    function initial(array, callback, thisArg) {
+      var n = 0,
+          length = array ? array.length : 0;
+
+      if (typeof callback != 'number' && callback != null) {
+        var index = length;
+        callback = lodash.createCallback(callback, thisArg, 3);
+        while (index-- && callback(array[index], index, array)) {
+          n++;
+        }
+      } else {
+        n = (callback == null || thisArg) ? 1 : callback || n;
+      }
+      return slice(array, 0, nativeMin(nativeMax(0, length - n), length));
+    }
+
+    /**
+     * Creates an array of unique values present in all provided arrays using
+     * strict equality for comparisons, i.e. `===`.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {...Array} [array] The arrays to inspect.
+     * @returns {Array} Returns an array of shared values.
+     * @example
+     *
+     * _.intersection([1, 2, 3], [5, 2, 1, 4], [2, 1]);
+     * // => [1, 2]
+     */
+    function intersection() {
+      var args = [],
+          argsIndex = -1,
+          argsLength = arguments.length,
+          caches = getArray(),
+          indexOf = getIndexOf(),
+          trustIndexOf = indexOf === baseIndexOf,
+          seen = getArray();
+
+      while (++argsIndex < argsLength) {
+        var value = arguments[argsIndex];
+        if (isArray(value) || isArguments(value)) {
+          args.push(value);
+          caches.push(trustIndexOf && value.length >= largeArraySize &&
+            createCache(argsIndex ? args[argsIndex] : seen));
+        }
+      }
+      var array = args[0],
+          index = -1,
+          length = array ? array.length : 0,
+          result = [];
+
+      outer:
+      while (++index < length) {
+        var cache = caches[0];
+        value = array[index];
+
+        if ((cache ? cacheIndexOf(cache, value) : indexOf(seen, value)) < 0) {
+          argsIndex = argsLength;
+          (cache || seen).push(value);
+          while (--argsIndex) {
+            cache = caches[argsIndex];
+            if ((cache ? cacheIndexOf(cache, value) : indexOf(args[argsIndex], value)) < 0) {
+              continue outer;
+            }
+          }
+          result.push(value);
+        }
+      }
+      while (argsLength--) {
+        cache = caches[argsLength];
+        if (cache) {
+          releaseObject(cache);
+        }
+      }
+      releaseArray(caches);
+      releaseArray(seen);
+      return result;
+    }
+
+    /**
+     * Gets the last element or last `n` elements of an array. If a callback is
+     * provided elements at the end of the array are returned as long as the
+     * callback returns truey. The callback is bound to `thisArg` and invoked
+     * with three arguments; (value, index, array).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {Array} array The array to query.
+     * @param {Function|Object|number|string} [callback] The function called
+     *  per element or the number of elements to return. If a property name or
+     *  object is provided it will be used to create a "_.pluck" or "_.where"
+     *  style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {*} Returns the last element(s) of `array`.
+     * @example
+     *
+     * _.last([1, 2, 3]);
+     * // => 3
+     *
+     * _.last([1, 2, 3], 2);
+     * // => [2, 3]
+     *
+     * _.last([1, 2, 3], function(num) {
+     *   return num > 1;
+     * });
+     * // => [2, 3]
+     *
+     * var characters = [
+     *   { 'name': 'barney',  'blocked': false, 'employer': 'slate' },
+     *   { 'name': 'fred',    'blocked': true,  'employer': 'slate' },
+     *   { 'name': 'pebbles', 'blocked': true,  'employer': 'na' }
+     * ];
+     *
+     * // using "_.pluck" callback shorthand
+     * _.pluck(_.last(characters, 'blocked'), 'name');
+     * // => ['fred', 'pebbles']
+     *
+     * // using "_.where" callback shorthand
+     * _.last(characters, { 'employer': 'na' });
+     * // => [{ 'name': 'pebbles', 'blocked': true, 'employer': 'na' }]
+     */
+    function last(array, callback, thisArg) {
+      var n = 0,
+          length = array ? array.length : 0;
+
+      if (typeof callback != 'number' && callback != null) {
+        var index = length;
+        callback = lodash.createCallback(callback, thisArg, 3);
+        while (index-- && callback(array[index], index, array)) {
+          n++;
+        }
+      } else {
+        n = callback;
+        if (n == null || thisArg) {
+          return array ? array[length - 1] : undefined;
+        }
+      }
+      return slice(array, nativeMax(0, length - n));
+    }
+
+    /**
+     * Gets the index at which the last occurrence of `value` is found using strict
+     * equality for comparisons, i.e. `===`. If `fromIndex` is negative, it is used
+     * as the offset from the end of the collection.
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {Array} array The array to search.
+     * @param {*} value The value to search for.
+     * @param {number} [fromIndex=array.length-1] The index to search from.
+     * @returns {number} Returns the index of the matched value or `-1`.
+     * @example
+     *
+     * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2);
+     * // => 4
+     *
+     * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2, 3);
+     * // => 1
+     */
+    function lastIndexOf(array, value, fromIndex) {
+      var index = array ? array.length : 0;
+      if (typeof fromIndex == 'number') {
+        index = (fromIndex < 0 ? nativeMax(0, index + fromIndex) : nativeMin(fromIndex, index - 1)) + 1;
+      }
+      while (index--) {
+        if (array[index] === value) {
+          return index;
+        }
+      }
+      return -1;
+    }
+
+    /**
+     * Removes all provided values from the given array using strict equality for
+     * comparisons, i.e. `===`.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {Array} array The array to modify.
+     * @param {...*} [value] The values to remove.
+     * @returns {Array} Returns `array`.
+     * @example
+     *
+     * var array = [1, 2, 3, 1, 2, 3];
+     * _.pull(array, 2, 3);
+     * console.log(array);
+     * // => [1, 1]
+     */
+    function pull(array) {
+      var args = arguments,
+          argsIndex = 0,
+          argsLength = args.length,
+          length = array ? array.length : 0;
+
+      while (++argsIndex < argsLength) {
+        var index = -1,
+            value = args[argsIndex];
+        while (++index < length) {
+          if (array[index] === value) {
+            splice.call(array, index--, 1);
+            length--;
+          }
+        }
+      }
+      return array;
+    }
+
+    /**
+     * Creates an array of numbers (positive and/or negative) progressing from
+     * `start` up to but not including `end`. If `start` is less than `stop` a
+     * zero-length range is created unless a negative `step` is specified.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {number} [start=0] The start of the range.
+     * @param {number} end The end of the range.
+     * @param {number} [step=1] The value to increment or decrement by.
+     * @returns {Array} Returns a new range array.
+     * @example
+     *
+     * _.range(4);
+     * // => [0, 1, 2, 3]
+     *
+     * _.range(1, 5);
+     * // => [1, 2, 3, 4]
+     *
+     * _.range(0, 20, 5);
+     * // => [0, 5, 10, 15]
+     *
+     * _.range(0, -4, -1);
+     * // => [0, -1, -2, -3]
+     *
+     * _.range(1, 4, 0);
+     * // => [1, 1, 1]
+     *
+     * _.range(0);
+     * // => []
+     */
+    function range(start, end, step) {
+      start = +start || 0;
+      step = typeof step == 'number' ? step : (+step || 1);
+
+      if (end == null) {
+        end = start;
+        start = 0;
+      }
+      // use `Array(length)` so engines like Chakra and V8 avoid slower modes
+      // http://youtu.be/XAqIpGU8ZZk#t=17m25s
+      var index = -1,
+          length = nativeMax(0, ceil((end - start) / (step || 1))),
+          result = Array(length);
+
+      while (++index < length) {
+        result[index] = start;
+        start += step;
+      }
+      return result;
+    }
+
+    /**
+     * Removes all elements from an array that the callback returns truey for
+     * and returns an array of removed elements. The callback is bound to `thisArg`
+     * and invoked with three arguments; (value, index, array).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {Array} array The array to modify.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Array} Returns a new array of removed elements.
+     * @example
+     *
+     * var array = [1, 2, 3, 4, 5, 6];
+     * var evens = _.remove(array, function(num) { return num % 2 == 0; });
+     *
+     * console.log(array);
+     * // => [1, 3, 5]
+     *
+     * console.log(evens);
+     * // => [2, 4, 6]
+     */
+    function remove(array, callback, thisArg) {
+      var index = -1,
+          length = array ? array.length : 0,
+          result = [];
+
+      callback = lodash.createCallback(callback, thisArg, 3);
+      while (++index < length) {
+        var value = array[index];
+        if (callback(value, index, array)) {
+          result.push(value);
+          splice.call(array, index--, 1);
+          length--;
+        }
+      }
+      return result;
+    }
+
+    /**
+     * The opposite of `_.initial` this method gets all but the first element or
+     * first `n` elements of an array. If a callback function is provided elements
+     * at the beginning of the array are excluded from the result as long as the
+     * callback returns truey. The callback is bound to `thisArg` and invoked
+     * with three arguments; (value, index, array).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @alias drop, tail
+     * @category Arrays
+     * @param {Array} array The array to query.
+     * @param {Function|Object|number|string} [callback=1] The function called
+     *  per element or the number of elements to exclude. If a property name or
+     *  object is provided it will be used to create a "_.pluck" or "_.where"
+     *  style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Array} Returns a slice of `array`.
+     * @example
+     *
+     * _.rest([1, 2, 3]);
+     * // => [2, 3]
+     *
+     * _.rest([1, 2, 3], 2);
+     * // => [3]
+     *
+     * _.rest([1, 2, 3], function(num) {
+     *   return num < 3;
+     * });
+     * // => [3]
+     *
+     * var characters = [
+     *   { 'name': 'barney',  'blocked': true,  'employer': 'slate' },
+     *   { 'name': 'fred',    'blocked': false,  'employer': 'slate' },
+     *   { 'name': 'pebbles', 'blocked': true, 'employer': 'na' }
+     * ];
+     *
+     * // using "_.pluck" callback shorthand
+     * _.pluck(_.rest(characters, 'blocked'), 'name');
+     * // => ['fred', 'pebbles']
+     *
+     * // using "_.where" callback shorthand
+     * _.rest(characters, { 'employer': 'slate' });
+     * // => [{ 'name': 'pebbles', 'blocked': true, 'employer': 'na' }]
+     */
+    function rest(array, callback, thisArg) {
+      if (typeof callback != 'number' && callback != null) {
+        var n = 0,
+            index = -1,
+            length = array ? array.length : 0;
+
+        callback = lodash.createCallback(callback, thisArg, 3);
+        while (++index < length && callback(array[index], index, array)) {
+          n++;
+        }
+      } else {
+        n = (callback == null || thisArg) ? 1 : nativeMax(0, callback);
+      }
+      return slice(array, n);
+    }
+
+    /**
+     * Uses a binary search to determine the smallest index at which a value
+     * should be inserted into a given sorted array in order to maintain the sort
+     * order of the array. If a callback is provided it will be executed for
+     * `value` and each element of `array` to compute their sort ranking. The
+     * callback is bound to `thisArg` and invoked with one argument; (value).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {Array} array The array to inspect.
+     * @param {*} value The value to evaluate.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {number} Returns the index at which `value` should be inserted
+     *  into `array`.
+     * @example
+     *
+     * _.sortedIndex([20, 30, 50], 40);
+     * // => 2
+     *
+     * // using "_.pluck" callback shorthand
+     * _.sortedIndex([{ 'x': 20 }, { 'x': 30 }, { 'x': 50 }], { 'x': 40 }, 'x');
+     * // => 2
+     *
+     * var dict = {
+     *   'wordToNumber': { 'twenty': 20, 'thirty': 30, 'fourty': 40, 'fifty': 50 }
+     * };
+     *
+     * _.sortedIndex(['twenty', 'thirty', 'fifty'], 'fourty', function(word) {
+     *   return dict.wordToNumber[word];
+     * });
+     * // => 2
+     *
+     * _.sortedIndex(['twenty', 'thirty', 'fifty'], 'fourty', function(word) {
+     *   return this.wordToNumber[word];
+     * }, dict);
+     * // => 2
+     */
+    function sortedIndex(array, value, callback, thisArg) {
+      var low = 0,
+          high = array ? array.length : low;
+
+      // explicitly reference `identity` for better inlining in Firefox
+      callback = callback ? lodash.createCallback(callback, thisArg, 1) : identity;
+      value = callback(value);
+
+      while (low < high) {
+        var mid = (low + high) >>> 1;
+        (callback(array[mid]) < value)
+          ? low = mid + 1
+          : high = mid;
+      }
+      return low;
+    }
+
+    /**
+     * Creates an array of unique values, in order, of the provided arrays using
+     * strict equality for comparisons, i.e. `===`.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {...Array} [array] The arrays to inspect.
+     * @returns {Array} Returns an array of combined values.
+     * @example
+     *
+     * _.union([1, 2, 3], [5, 2, 1, 4], [2, 1]);
+     * // => [1, 2, 3, 5, 4]
+     */
+    function union() {
+      return baseUniq(baseFlatten(arguments, true, true));
+    }
+
+    /**
+     * Creates a duplicate-value-free version of an array using strict equality
+     * for comparisons, i.e. `===`. If the array is sorted, providing
+     * `true` for `isSorted` will use a faster algorithm. If a callback is provided
+     * each element of `array` is passed through the callback before uniqueness
+     * is computed. The callback is bound to `thisArg` and invoked with three
+     * arguments; (value, index, array).
+     *
+     * If a property name is provided for `callback` the created "_.pluck" style
+     * callback will return the property value of the given element.
+     *
+     * If an object is provided for `callback` the created "_.where" style callback
+     * will return `true` for elements that have the properties of the given object,
+     * else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @alias unique
+     * @category Arrays
+     * @param {Array} array The array to process.
+     * @param {boolean} [isSorted=false] A flag to indicate that `array` is sorted.
+     * @param {Function|Object|string} [callback=identity] The function called
+     *  per iteration. If a property name or object is provided it will be used
+     *  to create a "_.pluck" or "_.where" style callback, respectively.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Array} Returns a duplicate-value-free array.
+     * @example
+     *
+     * _.uniq([1, 2, 1, 3, 1]);
+     * // => [1, 2, 3]
+     *
+     * _.uniq([1, 1, 2, 2, 3], true);
+     * // => [1, 2, 3]
+     *
+     * _.uniq(['A', 'b', 'C', 'a', 'B', 'c'], function(letter) { return letter.toLowerCase(); });
+     * // => ['A', 'b', 'C']
+     *
+     * _.uniq([1, 2.5, 3, 1.5, 2, 3.5], function(num) { return this.floor(num); }, Math);
+     * // => [1, 2.5, 3]
+     *
+     * // using "_.pluck" callback shorthand
+     * _.uniq([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x');
+     * // => [{ 'x': 1 }, { 'x': 2 }]
+     */
+    function uniq(array, isSorted, callback, thisArg) {
+      // juggle arguments
+      if (typeof isSorted != 'boolean' && isSorted != null) {
+        thisArg = callback;
+        callback = (typeof isSorted != 'function' && thisArg && thisArg[isSorted] === array) ? null : isSorted;
+        isSorted = false;
+      }
+      if (callback != null) {
+        callback = lodash.createCallback(callback, thisArg, 3);
+      }
+      return baseUniq(array, isSorted, callback);
+    }
+
+    /**
+     * Creates an array excluding all provided values using strict equality for
+     * comparisons, i.e. `===`.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {Array} array The array to filter.
+     * @param {...*} [value] The values to exclude.
+     * @returns {Array} Returns a new array of filtered values.
+     * @example
+     *
+     * _.without([1, 2, 1, 0, 3, 1, 4], 0, 1);
+     * // => [2, 3, 4]
+     */
+    function without(array) {
+      return baseDifference(array, slice(arguments, 1));
+    }
+
+    /**
+     * Creates an array that is the symmetric difference of the provided arrays.
+     * See http://en.wikipedia.org/wiki/Symmetric_difference.
+     *
+     * @static
+     * @memberOf _
+     * @category Arrays
+     * @param {...Array} [array] The arrays to inspect.
+     * @returns {Array} Returns an array of values.
+     * @example
+     *
+     * _.xor([1, 2, 3], [5, 2, 1, 4]);
+     * // => [3, 5, 4]
+     *
+     * _.xor([1, 2, 5], [2, 3, 5], [3, 4, 5]);
+     * // => [1, 4, 5]
+     */
+    function xor() {
+      var index = -1,
+          length = arguments.length;
+
+      while (++index < length) {
+        var array = arguments[index];
+        if (isArray(array) || isArguments(array)) {
+          var result = result
+            ? baseUniq(baseDifference(result, array).concat(baseDifference(array, result)))
+            : array;
+        }
+      }
+      return result || [];
+    }
+
+    /**
+     * Creates an array of grouped elements, the first of which contains the first
+     * elements of the given arrays, the second of which contains the second
+     * elements of the given arrays, and so on.
+     *
+     * @static
+     * @memberOf _
+     * @alias unzip
+     * @category Arrays
+     * @param {...Array} [array] Arrays to process.
+     * @returns {Array} Returns a new array of grouped elements.
+     * @example
+     *
+     * _.zip(['fred', 'barney'], [30, 40], [true, false]);
+     * // => [['fred', 30, true], ['barney', 40, false]]
+     */
+    function zip() {
+      var array = arguments.length > 1 ? arguments : arguments[0],
+          index = -1,
+          length = array ? max(pluck(array, 'length')) : 0,
+          result = Array(length < 0 ? 0 : length);
+
+      while (++index < length) {
+        result[index] = pluck(array, index);
+      }
+      return result;
+    }
+
+    /**
+     * Creates an object composed from arrays of `keys` and `values`. Provide
+     * either a single two dimensional array, i.e. `[[key1, value1], [key2, value2]]`
+     * or two arrays, one of `keys` and one of corresponding `values`.
+     *
+     * @static
+     * @memberOf _
+     * @alias object
+     * @category Arrays
+     * @param {Array} keys The array of keys.
+     * @param {Array} [values=[]] The array of values.
+     * @returns {Object} Returns an object composed of the given keys and
+     *  corresponding values.
+     * @example
+     *
+     * _.zipObject(['fred', 'barney'], [30, 40]);
+     * // => { 'fred': 30, 'barney': 40 }
+     */
+    function zipObject(keys, values) {
+      var index = -1,
+          length = keys ? keys.length : 0,
+          result = {};
+
+      if (!values && length && !isArray(keys[0])) {
+        values = [];
+      }
+      while (++index < length) {
+        var key = keys[index];
+        if (values) {
+          result[key] = values[index];
+        } else if (key) {
+          result[key[0]] = key[1];
+        }
+      }
+      return result;
+    }
+
+    /*--------------------------------------------------------------------------*/
+
+    /**
+     * Creates a function that executes `func`, with  the `this` binding and
+     * arguments of the created function, only after being called `n` times.
+     *
+     * @static
+     * @memberOf _
+     * @category Functions
+     * @param {number} n The number of times the function must be called before
+     *  `func` is executed.
+     * @param {Function} func The function to restrict.
+     * @returns {Function} Returns the new restricted function.
+     * @example
+     *
+     * var saves = ['profile', 'settings'];
+     *
+     * var done = _.after(saves.length, function() {
+     *   console.log('Done saving!');
+     * });
+     *
+     * _.forEach(saves, function(type) {
+     *   asyncSave({ 'type': type, 'complete': done });
+     * });
+     * // => logs 'Done saving!', after all saves have completed
+     */
+    function after(n, func) {
+      if (!isFunction(func)) {
+        throw new TypeError;
+      }
+      return function() {
+        if (--n < 1) {
+          return func.apply(this, arguments);
+        }
+      };
+    }
+
+    /**
+     * Creates a function that, when called, invokes `func` with the `this`
+     * binding of `thisArg` and prepends any additional `bind` arguments to those
+     * provided to the bound function.
+     *
+     * @static
+     * @memberOf _
+     * @category Functions
+     * @param {Function} func The function to bind.
+     * @param {*} [thisArg] The `this` binding of `func`.
+     * @param {...*} [arg] Arguments to be partially applied.
+     * @returns {Function} Returns the new bound function.
+     * @example
+     *
+     * var func = function(greeting) {
+     *   return greeting + ' ' + this.name;
+     * };
+     *
+     * func = _.bind(func, { 'name': 'fred' }, 'hi');
+     * func();
+     * // => 'hi fred'
+     */
+    function bind(func, thisArg) {
+      return arguments.length > 2
+        ? createWrapper(func, 17, slice(arguments, 2), null, thisArg)
+        : createWrapper(func, 1, null, null, thisArg);
+    }
+
+    /**
+     * Binds methods of an object to the object itself, overwriting the existing
+     * method. Method names may be specified as individual arguments or as arrays
+     * of method names. If no method names are provided all the function properties
+     * of `object` will be bound.
+     *
+     * @static
+     * @memberOf _
+     * @category Functions
+     * @param {Object} object The object to bind and assign the bound methods to.
+     * @param {...string} [methodName] The object method names to
+     *  bind, specified as individual method names or arrays of method names.
+     * @returns {Object} Returns `object`.
+     * @example
+     *
+     * var view = {
+     *   'label': 'docs',
+     *   'onClick': function() { console.log('clicked ' + this.label); }
+     * };
+     *
+     * _.bindAll(view);
+     * jQuery('#docs').on('click', view.onClick);
+     * // => logs 'clicked docs', when the button is clicked
+     */
+    function bindAll(object) {
+      var funcs = arguments.length > 1 ? baseFlatten(arguments, true, false, 1) : functions(object),
+          index = -1,
+          length = funcs.length;
+
+      while (++index < length) {
+        var key = funcs[index];
+        object[key] = createWrapper(object[key], 1, null, null, object);
+      }
+      return object;
+    }
+
+    /**
+     * Creates a function that, when called, invokes the method at `object[key]`
+     * and prepends any additional `bindKey` arguments to those provided to the bound
+     * function. This method differs from `_.bind` by allowing bound functions to
+     * reference methods that will be redefined or don't yet exist.
+     * See http://michaux.ca/articles/lazy-function-definition-pattern.
+     *
+     * @static
+     * @memberOf _
+     * @category Functions
+     * @param {Object} object The object the method belongs to.
+     * @param {string} key The key of the method.
+     * @param {...*} [arg] Arguments to be partially applied.
+     * @returns {Function} Returns the new bound function.
+     * @example
+     *
+     * var object = {
+     *   'name': 'fred',
+     *   'greet': function(greeting) {
+     *     return greeting + ' ' + this.name;
+     *   }
+     * };
+     *
+     * var func = _.bindKey(object, 'greet', 'hi');
+     * func();
+     * // => 'hi fred'
+     *
+     * object.greet = function(greeting) {
+     *   return greeting + 'ya ' + this.name + '!';
+     * };
+     *
+     * func();
+     * // => 'hiya fred!'
+     */
+    function bindKey(object, key) {
+      return arguments.length > 2
+        ? createWrapper(key, 19, slice(arguments, 2), null, object)
+        : createWrapper(key, 3, null, null, object);
+    }
+
+    /**
+     * Creates a function that is the composition of the provided functions,
+     * where each function consumes the return value of the function that follows.
+     * For example, composing the functions `f()`, `g()`, and `h()` produces `f(g(h()))`.
+     * Each function is executed with the `this` binding of the composed function.
+     *
+     * @static
+     * @memberOf _
+     * @category Functions
+     * @param {...Function} [func] Functions to compose.
+     * @returns {Function} Returns the new composed function.
+     * @example
+     *
+     * var realNameMap = {
+     *   'pebbles': 'penelope'
+     * };
+     *
+     * var format = function(name) {
+     *   name = realNameMap[name.toLowerCase()] || name;
+     *   return name.charAt(0).toUpperCase() + name.slice(1).toLowerCase();
+     * };
+     *
+     * var greet = function(formatted) {
+     *   return 'Hiya ' + formatted + '!';
+     * };
+     *
+     * var welcome = _.compose(greet, format);
+     * welcome('pebbles');
+     * // => 'Hiya Penelope!'
+     */
+    function compose() {
+      var funcs = arguments,
+          length = funcs.length;
+
+      while (length--) {
+        if (!isFunction(funcs[length])) {
+          throw new TypeError;
+        }
+      }
+      return function() {
+        var args = arguments,
+            length = funcs.length;
+
+        while (length--) {
+          args = [funcs[length].apply(this, args)];
+        }
+        return args[0];
+      };
+    }
+
+    /**
+     * Creates a function which accepts one or more arguments of `func` that when
+     * invoked either executes `func` returning its result, if all `func` arguments
+     * have been provided, or returns a function that accepts one or more of the
+     * remaining `func` arguments, and so on. The arity of `func` can be specified
+     * if `func.length` is not sufficient.
+     *
+     * @static
+     * @memberOf _
+     * @category Functions
+     * @param {Function} func The function to curry.
+     * @param {number} [arity=func.length] The arity of `func`.
+     * @returns {Function} Returns the new curried function.
+     * @example
+     *
+     * var curried = _.curry(function(a, b, c) {
+     *   console.log(a + b + c);
+     * });
+     *
+     * curried(1)(2)(3);
+     * // => 6
+     *
+     * curried(1, 2)(3);
+     * // => 6
+     *
+     * curried(1, 2, 3);
+     * // => 6
+     */
+    function curry(func, arity) {
+      arity = typeof arity == 'number' ? arity : (+arity || func.length);
+      return createWrapper(func, 4, null, null, null, arity);
+    }
+
+    /**
+     * Creates a function that will delay the execution of `func` until after
+     * `wait` milliseconds have elapsed since the last time it was invoked.
+     * Provide an options object to indicate that `func` should be invoked on
+     * the leading and/or trailing edge of the `wait` timeout. Subsequent calls
+     * to the debounced function will return the result of the last `func` call.
+     *
+     * Note: If `leading` and `trailing` options are `true` `func` will be called
+     * on the trailing edge of the timeout only if the the debounced function is
+     * invoked more than once during the `wait` timeout.
+     *
+     * @static
+     * @memberOf _
+     * @category Functions
+     * @param {Function} func The function to debounce.
+     * @param {number} wait The number of milliseconds to delay.
+     * @param {Object} [options] The options object.
+     * @param {boolean} [options.leading=false] Specify execution on the leading edge of the timeout.
+     * @param {number} [options.maxWait] The maximum time `func` is allowed to be delayed before it's called.
+     * @param {boolean} [options.trailing=true] Specify execution on the trailing edge of the timeout.
+     * @returns {Function} Returns the new debounced function.
+     * @example
+     *
+     * // avoid costly calculations while the window size is in flux
+     * var lazyLayout = _.debounce(calculateLayout, 150);
+     * jQuery(window).on('resize', lazyLayout);
+     *
+     * // execute `sendMail` when the click event is fired, debouncing subsequent calls
+     * jQuery('#postbox').on('click', _.debounce(sendMail, 300, {
+     *   'leading': true,
+     *   'trailing': false
+     * });
+     *
+     * // ensure `batchLog` is executed once after 1 second of debounced calls
+     * var source = new EventSource('/stream');
+     * source.addEventListener('message', _.debounce(batchLog, 250, {
+     *   'maxWait': 1000
+     * }, false);
+     */
+    function debounce(func, wait, options) {
+      var args,
+          maxTimeoutId,
+          result,
+          stamp,
+          thisArg,
+          timeoutId,
+          trailingCall,
+          lastCalled = 0,
+          maxWait = false,
+          trailing = true;
+
+      if (!isFunction(func)) {
+        throw new TypeError;
+      }
+      wait = nativeMax(0, wait) || 0;
+      if (options === true) {
+        var leading = true;
+        trailing = false;
+      } else if (isObject(options)) {
+        leading = options.leading;
+        maxWait = 'maxWait' in options && (nativeMax(wait, options.maxWait) || 0);
+        trailing = 'trailing' in options ? options.trailing : trailing;
+      }
+      var delayed = function() {
+        var remaining = wait - (now() - stamp);
+        if (remaining <= 0) {
+          if (maxTimeoutId) {
+            clearTimeout(maxTimeoutId);
+          }
+          var isCalled = trailingCall;
+          maxTimeoutId = timeoutId = trailingCall = undefined;
+          if (isCalled) {
+            lastCalled = now();
+            result = func.apply(thisArg, args);
+            if (!timeoutId && !maxTimeoutId) {
+              args = thisArg = null;
+            }
+          }
+        } else {
+          timeoutId = setTimeout(delayed, remaining);
+        }
+      };
+
+      var maxDelayed = function() {
+        if (timeoutId) {
+          clearTimeout(timeoutId);
+        }
+        maxTimeoutId = timeoutId = trailingCall = undefined;
+        if (trailing || (maxWait !== wait)) {
+          lastCalled = now();
+          result = func.apply(thisArg, args);
+          if (!timeoutId && !maxTimeoutId) {
+            args = thisArg = null;
+          }
+        }
+      };
+
+      return function() {
+        args = arguments;
+        stamp = now();
+        thisArg = this;
+        trailingCall = trailing && (timeoutId || !leading);
+
+        if (maxWait === false) {
+          var leadingCall = leading && !timeoutId;
+        } else {
+          if (!maxTimeoutId && !leading) {
+            lastCalled = stamp;
+          }
+          var remaining = maxWait - (stamp - lastCalled),
+              isCalled = remaining <= 0;
+
+          if (isCalled) {
+            if (maxTimeoutId) {
+              maxTimeoutId = clearTimeout(maxTimeoutId);
+            }
+            lastCalled = stamp;
+            result = func.apply(thisArg, args);
+          }
+          else if (!maxTimeoutId) {
+            maxTimeoutId = setTimeout(maxDelayed, remaining);
+          }
+        }
+        if (isCalled && timeoutId) {
+          timeoutId = clearTimeout(timeoutId);
+        }
+        else if (!timeoutId && wait !== maxWait) {
+          timeoutId = setTimeout(delayed, wait);
+        }
+        if (leadingCall) {
+          isCalled = true;
+          result = func.apply(thisArg, args);
+        }
+        if (isCalled && !timeoutId && !maxTimeoutId) {
+          args = thisArg = null;
+        }
+        return result;
+      };
+    }
+
+    /**
+     * Defers executing the `func` function until the current call stack has cleared.
+     * Additional arguments will be provided to `func` when it is invoked.
+     *
+     * @static
+     * @memberOf _
+     * @category Functions
+     * @param {Function} func The function to defer.
+     * @param {...*} [arg] Arguments to invoke the function with.
+     * @returns {number} Returns the timer id.
+     * @example
+     *
+     * _.defer(function(text) { console.log(text); }, 'deferred');
+     * // logs 'deferred' after one or more milliseconds
+     */
+    function defer(func) {
+      if (!isFunction(func)) {
+        throw new TypeError;
+      }
+      var args = slice(arguments, 1);
+      return setTimeout(function() { func.apply(undefined, args); }, 1);
+    }
+
+    /**
+     * Executes the `func` function after `wait` milliseconds. Additional arguments
+     * will be provided to `func` when it is invoked.
+     *
+     * @static
+     * @memberOf _
+     * @category Functions
+     * @param {Function} func The function to delay.
+     * @param {number} wait The number of milliseconds to delay execution.
+     * @param {...*} [arg] Arguments to invoke the function with.
+     * @returns {number} Returns the timer id.
+     * @example
+     *
+     * _.delay(function(text) { console.log(text); }, 1000, 'later');
+     * // => logs 'later' after one second
+     */
+    function delay(func, wait) {
+      if (!isFunction(func)) {
+        throw new TypeError;
+      }
+      var args = slice(arguments, 2);
+      return setTimeout(function() { func.apply(undefined, args); }, wait);
+    }
+
+    /**
+     * Creates a function that memoizes the result of `func`. If `resolver` is
+     * provided it will be used to determine the cache key for storing the result
+     * based on the arguments provided to the memoized function. By default, the
+     * first argument provided to the memoized function is used as the cache key.
+     * The `func` is executed with the `this` binding of the memoized function.
+     * The result cache is exposed as the `cache` property on the memoized function.
+     *
+     * @static
+     * @memberOf _
+     * @category Functions
+     * @param {Function} func The function to have its output memoized.
+     * @param {Function} [resolver] A function used to resolve the cache key.
+     * @returns {Function} Returns the new memoizing function.
+     * @example
+     *
+     * var fibonacci = _.memoize(function(n) {
+     *   return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);
+     * });
+     *
+     * fibonacci(9)
+     * // => 34
+     *
+     * var data = {
+     *   'fred': { 'name': 'fred', 'age': 40 },
+     *   'pebbles': { 'name': 'pebbles', 'age': 1 }
+     * };
+     *
+     * // modifying the result cache
+     * var get = _.memoize(function(name) { return data[name]; }, _.identity);
+     * get('pebbles');
+     * // => { 'name': 'pebbles', 'age': 1 }
+     *
+     * get.cache.pebbles.name = 'penelope';
+     * get('pebbles');
+     * // => { 'name': 'penelope', 'age': 1 }
+     */
+    function memoize(func, resolver) {
+      if (!isFunction(func)) {
+        throw new TypeError;
+      }
+      var memoized = function() {
+        var cache = memoized.cache,
+            key = resolver ? resolver.apply(this, arguments) : keyPrefix + arguments[0];
+
+        return hasOwnProperty.call(cache, key)
+          ? cache[key]
+          : (cache[key] = func.apply(this, arguments));
+      }
+      memoized.cache = {};
+      return memoized;
+    }
+
+    /**
+     * Creates a function that is restricted to execute `func` once. Repeat calls to
+     * the function will return the value of the first call. The `func` is executed
+     * with the `this` binding of the created function.
+     *
+     * @static
+     * @memberOf _
+     * @category Functions
+     * @param {Function} func The function to restrict.
+     * @returns {Function} Returns the new restricted function.
+     * @example
+     *
+     * var initialize = _.once(createApplication);
+     * initialize();
+     * initialize();
+     * // `initialize` executes `createApplication` once
+     */
+    function once(func) {
+      var ran,
+          result;
+
+      if (!isFunction(func)) {
+        throw new TypeError;
+      }
+      return function() {
+        if (ran) {
+          return result;
+        }
+        ran = true;
+        result = func.apply(this, arguments);
+
+        // clear the `func` variable so the function may be garbage collected
+        func = null;
+        return result;
+      };
+    }
+
+    /**
+     * Creates a function that, when called, invokes `func` with any additional
+     * `partial` arguments prepended to those provided to the new function. This
+     * method is similar to `_.bind` except it does **not** alter the `this` binding.
+     *
+     * @static
+     * @memberOf _
+     * @category Functions
+     * @param {Function} func The function to partially apply arguments to.
+     * @param {...*} [arg] Arguments to be partially applied.
+     * @returns {Function} Returns the new partially applied function.
+     * @example
+     *
+     * var greet = function(greeting, name) { return greeting + ' ' + name; };
+     * var hi = _.partial(greet, 'hi');
+     * hi('fred');
+     * // => 'hi fred'
+     */
+    function partial(func) {
+      return createWrapper(func, 16, slice(arguments, 1));
+    }
+
+    /**
+     * This method is like `_.partial` except that `partial` arguments are
+     * appended to those provided to the new function.
+     *
+     * @static
+     * @memberOf _
+     * @category Functions
+     * @param {Function} func The function to partially apply arguments to.
+     * @param {...*} [arg] Arguments to be partially applied.
+     * @returns {Function} Returns the new partially applied function.
+     * @example
+     *
+     * var defaultsDeep = _.partialRight(_.merge, _.defaults);
+     *
+     * var options = {
+     *   'variable': 'data',
+     *   'imports': { 'jq': $ }
+     * };
+     *
+     * defaultsDeep(options, _.templateSettings);
+     *
+     * options.variable
+     * // => 'data'
+     *
+     * options.imports
+     * // => { '_': _, 'jq': $ }
+     */
+    function partialRight(func) {
+      return createWrapper(func, 32, null, slice(arguments, 1));
+    }
+
+    /**
+     * Creates a function that, when executed, will only call the `func` function
+     * at most once per every `wait` milliseconds. Provide an options object to
+     * indicate that `func` should be invoked on the leading and/or trailing edge
+     * of the `wait` timeout. Subsequent calls to the throttled function will
+     * return the result of the last `func` call.
+     *
+     * Note: If `leading` and `trailing` options are `true` `func` will be called
+     * on the trailing edge of the timeout only if the the throttled function is
+     * invoked more than once during the `wait` timeout.
+     *
+     * @static
+     * @memberOf _
+     * @category Functions
+     * @param {Function} func The function to throttle.
+     * @param {number} wait The number of milliseconds to throttle executions to.
+     * @param {Object} [options] The options object.
+     * @param {boolean} [options.leading=true] Specify execution on the leading edge of the timeout.
+     * @param {boolean} [options.trailing=true] Specify execution on the trailing edge of the timeout.
+     * @returns {Function} Returns the new throttled function.
+     * @example
+     *
+     * // avoid excessively updating the position while scrolling
+     * var throttled = _.throttle(updatePosition, 100);
+     * jQuery(window).on('scroll', throttled);
+     *
+     * // execute `renewToken` when the click event is fired, but not more than once every 5 minutes
+     * jQuery('.interactive').on('click', _.throttle(renewToken, 300000, {
+     *   'trailing': false
+     * }));
+     */
+    function throttle(func, wait, options) {
+      var leading = true,
+          trailing = true;
+
+      if (!isFunction(func)) {
+        throw new TypeError;
+      }
+      if (options === false) {
+        leading = false;
+      } else if (isObject(options)) {
+        leading = 'leading' in options ? options.leading : leading;
+        trailing = 'trailing' in options ? options.trailing : trailing;
+      }
+      debounceOptions.leading = leading;
+      debounceOptions.maxWait = wait;
+      debounceOptions.trailing = trailing;
+
+      return debounce(func, wait, debounceOptions);
+    }
+
+    /**
+     * Creates a function that provides `value` to the wrapper function as its
+     * first argument. Additional arguments provided to the function are appended
+     * to those provided to the wrapper function. The wrapper is executed with
+     * the `this` binding of the created function.
+     *
+     * @static
+     * @memberOf _
+     * @category Functions
+     * @param {*} value The value to wrap.
+     * @param {Function} wrapper The wrapper function.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var p = _.wrap(_.escape, function(func, text) {
+     *   return '<p>' + func(text) + '</p>';
+     * });
+     *
+     * p('Fred, Wilma, & Pebbles');
+     * // => '<p>Fred, Wilma, &amp; Pebbles</p>'
+     */
+    function wrap(value, wrapper) {
+      return createWrapper(wrapper, 16, [value]);
+    }
+
+    /*--------------------------------------------------------------------------*/
+
+    /**
+     * Creates a function that returns `value`.
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @param {*} value The value to return from the new function.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var object = { 'name': 'fred' };
+     * var getter = _.constant(object);
+     * getter() === object;
+     * // => true
+     */
+    function constant(value) {
+      return function() {
+        return value;
+      };
+    }
+
+    /**
+     * Produces a callback bound to an optional `thisArg`. If `func` is a property
+     * name the created callback will return the property value for a given element.
+     * If `func` is an object the created callback will return `true` for elements
+     * that contain the equivalent object properties, otherwise it will return `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @param {*} [func=identity] The value to convert to a callback.
+     * @param {*} [thisArg] The `this` binding of the created callback.
+     * @param {number} [argCount] The number of arguments the callback accepts.
+     * @returns {Function} Returns a callback function.
+     * @example
+     *
+     * var characters = [
+     *   { 'name': 'barney', 'age': 36 },
+     *   { 'name': 'fred',   'age': 40 }
+     * ];
+     *
+     * // wrap to create custom callback shorthands
+     * _.createCallback = _.wrap(_.createCallback, function(func, callback, thisArg) {
+     *   var match = /^(.+?)__([gl]t)(.+)$/.exec(callback);
+     *   return !match ? func(callback, thisArg) : function(object) {
+     *     return match[2] == 'gt' ? object[match[1]] > match[3] : object[match[1]] < match[3];
+     *   };
+     * });
+     *
+     * _.filter(characters, 'age__gt38');
+     * // => [{ 'name': 'fred', 'age': 40 }]
+     */
+    function createCallback(func, thisArg, argCount) {
+      var type = typeof func;
+      if (func == null || type == 'function') {
+        return baseCreateCallback(func, thisArg, argCount);
+      }
+      // handle "_.pluck" style callback shorthands
+      if (type != 'object') {
+        return property(func);
+      }
+      var props = keys(func),
+          key = props[0],
+          a = func[key];
+
+      // handle "_.where" style callback shorthands
+      if (props.length == 1 && a === a && !isObject(a)) {
+        // fast path the common case of providing an object with a single
+        // property containing a primitive value
+        return function(object) {
+          var b = object[key];
+          return a === b && (a !== 0 || (1 / a == 1 / b));
+        };
+      }
+      return function(object) {
+        var length = props.length,
+            result = false;
+
+        while (length--) {
+          if (!(result = baseIsEqual(object[props[length]], func[props[length]], null, true))) {
+            break;
+          }
+        }
+        return result;
+      };
+    }
+
+    /**
+     * Converts the characters `&`, `<`, `>`, `"`, and `'` in `string` to their
+     * corresponding HTML entities.
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @param {string} string The string to escape.
+     * @returns {string} Returns the escaped string.
+     * @example
+     *
+     * _.escape('Fred, Wilma, & Pebbles');
+     * // => 'Fred, Wilma, &amp; Pebbles'
+     */
+    function escape(string) {
+      return string == null ? '' : String(string).replace(reUnescapedHtml, escapeHtmlChar);
+    }
+
+    /**
+     * This method returns the first argument provided to it.
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @param {*} value Any value.
+     * @returns {*} Returns `value`.
+     * @example
+     *
+     * var object = { 'name': 'fred' };
+     * _.identity(object) === object;
+     * // => true
+     */
+    function identity(value) {
+      return value;
+    }
+
+    /**
+     * Adds function properties of a source object to the destination object.
+     * If `object` is a function methods will be added to its prototype as well.
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @param {Function|Object} [object=lodash] object The destination object.
+     * @param {Object} source The object of functions to add.
+     * @param {Object} [options] The options object.
+     * @param {boolean} [options.chain=true] Specify whether the functions added are chainable.
+     * @example
+     *
+     * function capitalize(string) {
+     *   return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();
+     * }
+     *
+     * _.mixin({ 'capitalize': capitalize });
+     * _.capitalize('fred');
+     * // => 'Fred'
+     *
+     * _('fred').capitalize().value();
+     * // => 'Fred'
+     *
+     * _.mixin({ 'capitalize': capitalize }, { 'chain': false });
+     * _('fred').capitalize();
+     * // => 'Fred'
+     */
+    function mixin(object, source, options) {
+      var chain = true,
+          methodNames = source && functions(source);
+
+      if (!source || (!options && !methodNames.length)) {
+        if (options == null) {
+          options = source;
+        }
+        ctor = lodashWrapper;
+        source = object;
+        object = lodash;
+        methodNames = functions(source);
+      }
+      if (options === false) {
+        chain = false;
+      } else if (isObject(options) && 'chain' in options) {
+        chain = options.chain;
+      }
+      var ctor = object,
+          isFunc = isFunction(ctor);
+
+      forEach(methodNames, function(methodName) {
+        var func = object[methodName] = source[methodName];
+        if (isFunc) {
+          ctor.prototype[methodName] = function() {
+            var chainAll = this.__chain__,
+                value = this.__wrapped__,
+                args = [value];
+
+            push.apply(args, arguments);
+            var result = func.apply(object, args);
+            if (chain || chainAll) {
+              if (value === result && isObject(result)) {
+                return this;
+              }
+              result = new ctor(result);
+              result.__chain__ = chainAll;
+            }
+            return result;
+          };
+        }
+      });
+    }
+
+    /**
+     * Reverts the '_' variable to its previous value and returns a reference to
+     * the `lodash` function.
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @returns {Function} Returns the `lodash` function.
+     * @example
+     *
+     * var lodash = _.noConflict();
+     */
+    function noConflict() {
+      context._ = oldDash;
+      return this;
+    }
+
+    /**
+     * A no-operation function.
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @example
+     *
+     * var object = { 'name': 'fred' };
+     * _.noop(object) === undefined;
+     * // => true
+     */
+    function noop() {
+      // no operation performed
+    }
+
+    /**
+     * Gets the number of milliseconds that have elapsed since the Unix epoch
+     * (1 January 1970 00:00:00 UTC).
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @example
+     *
+     * var stamp = _.now();
+     * _.defer(function() { console.log(_.now() - stamp); });
+     * // => logs the number of milliseconds it took for the deferred function to be called
+     */
+    var now = isNative(now = Date.now) && now || function() {
+      return new Date().getTime();
+    };
+
+    /**
+     * Converts the given value into an integer of the specified radix.
+     * If `radix` is `undefined` or `0` a `radix` of `10` is used unless the
+     * `value` is a hexadecimal, in which case a `radix` of `16` is used.
+     *
+     * Note: This method avoids differences in native ES3 and ES5 `parseInt`
+     * implementations. See http://es5.github.io/#E.
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @param {string} value The value to parse.
+     * @param {number} [radix] The radix used to interpret the value to parse.
+     * @returns {number} Returns the new integer value.
+     * @example
+     *
+     * _.parseInt('08');
+     * // => 8
+     */
+    var parseInt = nativeParseInt(whitespace + '08') == 8 ? nativeParseInt : function(value, radix) {
+      // Firefox < 21 and Opera < 15 follow the ES3 specified implementation of `parseInt`
+      return nativeParseInt(isString(value) ? value.replace(reLeadingSpacesAndZeros, '') : value, radix || 0);
+    };
+
+    /**
+     * Creates a "_.pluck" style function, which returns the `key` value of a
+     * given object.
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @param {string} key The name of the property to retrieve.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var characters = [
+     *   { 'name': 'fred',   'age': 40 },
+     *   { 'name': 'barney', 'age': 36 }
+     * ];
+     *
+     * var getName = _.property('name');
+     *
+     * _.map(characters, getName);
+     * // => ['barney', 'fred']
+     *
+     * _.sortBy(characters, getName);
+     * // => [{ 'name': 'barney', 'age': 36 }, { 'name': 'fred',   'age': 40 }]
+     */
+    function property(key) {
+      return function(object) {
+        return object[key];
+      };
+    }
+
+    /**
+     * Produces a random number between `min` and `max` (inclusive). If only one
+     * argument is provided a number between `0` and the given number will be
+     * returned. If `floating` is truey or either `min` or `max` are floats a
+     * floating-point number will be returned instead of an integer.
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @param {number} [min=0] The minimum possible value.
+     * @param {number} [max=1] The maximum possible value.
+     * @param {boolean} [floating=false] Specify returning a floating-point number.
+     * @returns {number} Returns a random number.
+     * @example
+     *
+     * _.random(0, 5);
+     * // => an integer between 0 and 5
+     *
+     * _.random(5);
+     * // => also an integer between 0 and 5
+     *
+     * _.random(5, true);
+     * // => a floating-point number between 0 and 5
+     *
+     * _.random(1.2, 5.2);
+     * // => a floating-point number between 1.2 and 5.2
+     */
+    function random(min, max, floating) {
+      var noMin = min == null,
+          noMax = max == null;
+
+      if (floating == null) {
+        if (typeof min == 'boolean' && noMax) {
+          floating = min;
+          min = 1;
+        }
+        else if (!noMax && typeof max == 'boolean') {
+          floating = max;
+          noMax = true;
+        }
+      }
+      if (noMin && noMax) {
+        max = 1;
+      }
+      min = +min || 0;
+      if (noMax) {
+        max = min;
+        min = 0;
+      } else {
+        max = +max || 0;
+      }
+      if (floating || min % 1 || max % 1) {
+        var rand = nativeRandom();
+        return nativeMin(min + (rand * (max - min + parseFloat('1e-' + ((rand +'').length - 1)))), max);
+      }
+      return baseRandom(min, max);
+    }
+
+    /**
+     * Resolves the value of property `key` on `object`. If `key` is a function
+     * it will be invoked with the `this` binding of `object` and its result returned,
+     * else the property value is returned. If `object` is falsey then `undefined`
+     * is returned.
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @param {Object} object The object to inspect.
+     * @param {string} key The name of the property to resolve.
+     * @returns {*} Returns the resolved value.
+     * @example
+     *
+     * var object = {
+     *   'cheese': 'crumpets',
+     *   'stuff': function() {
+     *     return 'nonsense';
+     *   }
+     * };
+     *
+     * _.result(object, 'cheese');
+     * // => 'crumpets'
+     *
+     * _.result(object, 'stuff');
+     * // => 'nonsense'
+     */
+    function result(object, key) {
+      if (object) {
+        var value = object[key];
+        return isFunction(value) ? object[key]() : value;
+      }
+    }
+
+    /**
+     * A micro-templating method that handles arbitrary delimiters, preserves
+     * whitespace, and correctly escapes quotes within interpolated code.
+     *
+     * Note: In the development build, `_.template` utilizes sourceURLs for easier
+     * debugging. See http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl
+     *
+     * For more information on precompiling templates see:
+     * http://lodash.com/custom-builds
+     *
+     * For more information on Chrome extension sandboxes see:
+     * http://developer.chrome.com/stable/extensions/sandboxingEval.html
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @param {string} text The template text.
+     * @param {Object} data The data object used to populate the text.
+     * @param {Object} [options] The options object.
+     * @param {RegExp} [options.escape] The "escape" delimiter.
+     * @param {RegExp} [options.evaluate] The "evaluate" delimiter.
+     * @param {Object} [options.imports] An object to import into the template as local variables.
+     * @param {RegExp} [options.interpolate] The "interpolate" delimiter.
+     * @param {string} [sourceURL] The sourceURL of the template's compiled source.
+     * @param {string} [variable] The data object variable name.
+     * @returns {Function|string} Returns a compiled function when no `data` object
+     *  is given, else it returns the interpolated text.
+     * @example
+     *
+     * // using the "interpolate" delimiter to create a compiled template
+     * var compiled = _.template('hello <%= name %>');
+     * compiled({ 'name': 'fred' });
+     * // => 'hello fred'
+     *
+     * // using the "escape" delimiter to escape HTML in data property values
+     * _.template('<b><%- value %></b>', { 'value': '<script>' });
+     * // => '<b>&lt;script&gt;</b>'
+     *
+     * // using the "evaluate" delimiter to generate HTML
+     * var list = '<% _.forEach(people, function(name) { %><li><%- name %></li><% }); %>';
+     * _.template(list, { 'people': ['fred', 'barney'] });
+     * // => '<li>fred</li><li>barney</li>'
+     *
+     * // using the ES6 delimiter as an alternative to the default "interpolate" delimiter
+     * _.template('hello ${ name }', { 'name': 'pebbles' });
+     * // => 'hello pebbles'
+     *
+     * // using the internal `print` function in "evaluate" delimiters
+     * _.template('<% print("hello " + name); %>!', { 'name': 'barney' });
+     * // => 'hello barney!'
+     *
+     * // using a custom template delimiters
+     * _.templateSettings = {
+     *   'interpolate': /{{([\s\S]+?)}}/g
+     * };
+     *
+     * _.template('hello {{ name }}!', { 'name': 'mustache' });
+     * // => 'hello mustache!'
+     *
+     * // using the `imports` option to import jQuery
+     * var list = '<% jq.each(people, function(name) { %><li><%- name %></li><% }); %>';
+     * _.template(list, { 'people': ['fred', 'barney'] }, { 'imports': { 'jq': jQuery } });
+     * // => '<li>fred</li><li>barney</li>'
+     *
+     * // using the `sourceURL` option to specify a custom sourceURL for the template
+     * var compiled = _.template('hello <%= name %>', null, { 'sourceURL': '/basic/greeting.jst' });
+     * compiled(data);
+     * // => find the source of "greeting.jst" under the Sources tab or Resources panel of the web inspector
+     *
+     * // using the `variable` option to ensure a with-statement isn't used in the compiled template
+     * var compiled = _.template('hi <%= data.name %>!', null, { 'variable': 'data' });
+     * compiled.source;
+     * // => function(data) {
+     *   var __t, __p = '', __e = _.escape;
+     *   __p += 'hi ' + ((__t = ( data.name )) == null ? '' : __t) + '!';
+     *   return __p;
+     * }
+     *
+     * // using the `source` property to inline compiled templates for meaningful
+     * // line numbers in error messages and a stack trace
+     * fs.writeFileSync(path.join(cwd, 'jst.js'), '\
+     *   var JST = {\
+     *     "main": ' + _.template(mainText).source + '\
+     *   };\
+     * ');
+     */
+    function template(text, data, options) {
+      // based on John Resig's `tmpl` implementation
+      // http://ejohn.org/blog/javascript-micro-templating/
+      // and Laura Doktorova's doT.js
+      // https://github.com/olado/doT
+      var settings = lodash.templateSettings;
+      text = String(text || '');
+
+      // avoid missing dependencies when `iteratorTemplate` is not defined
+      options = iteratorTemplate ? defaults({}, options, settings) : settings;
+
+      var imports = iteratorTemplate && defaults({}, options.imports, settings.imports),
+          importsKeys = iteratorTemplate ? keys(imports) : ['_'],
+          importsValues = iteratorTemplate ? values(imports) : [lodash];
+
+      var isEvaluating,
+          index = 0,
+          interpolate = options.interpolate || reNoMatch,
+          source = "__p += '";
+
+      // compile the regexp to match each delimiter
+      var reDelimiters = RegExp(
+        (options.escape || reNoMatch).source + '|' +
+        interpolate.source + '|' +
+        (interpolate === reInterpolate ? reEsTemplate : reNoMatch).source + '|' +
+        (options.evaluate || reNoMatch).source + '|$'
+      , 'g');
+
+      text.replace(reDelimiters, function(match, escapeValue, interpolateValue, esTemplateValue, evaluateValue, offset) {
+        interpolateValue || (interpolateValue = esTemplateValue);
+
+        // escape characters that cannot be included in string literals
+        source += text.slice(index, offset).replace(reUnescapedString, escapeStringChar);
+
+        // replace delimiters with snippets
+        if (escapeValue) {
+          source += "' +\n__e(" + escapeValue + ") +\n'";
+        }
+        if (evaluateValue) {
+          isEvaluating = true;
+          source += "';\n" + evaluateValue + ";\n__p += '";
+        }
+        if (interpolateValue) {
+          source += "' +\n((__t = (" + interpolateValue + ")) == null ? '' : __t) +\n'";
+        }
+        index = offset + match.length;
+
+        // the JS engine embedded in Adobe products requires returning the `match`
+        // string in order to produce the correct `offset` value
+        return match;
+      });
+
+      source += "';\n";
+
+      // if `variable` is not specified, wrap a with-statement around the generated
+      // code to add the data object to the top of the scope chain
+      var variable = options.variable,
+          hasVariable = variable;
+
+      if (!hasVariable) {
+        variable = 'obj';
+        source = 'with (' + variable + ') {\n' + source + '\n}\n';
+      }
+      // cleanup code by stripping empty strings
+      source = (isEvaluating ? source.replace(reEmptyStringLeading, '') : source)
+        .replace(reEmptyStringMiddle, '$1')
+        .replace(reEmptyStringTrailing, '$1;');
+
+      // frame code as the function body
+      source = 'function(' + variable + ') {\n' +
+        (hasVariable ? '' : variable + ' || (' + variable + ' = {});\n') +
+        "var __t, __p = '', __e = _.escape" +
+        (isEvaluating
+          ? ', __j = Array.prototype.join;\n' +
+            "function print() { __p += __j.call(arguments, '') }\n"
+          : ';\n'
+        ) +
+        source +
+        'return __p\n}';
+
+      // Use a sourceURL for easier debugging.
+      // http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl
+      var sourceURL = '\n/*\n//# sourceURL=' + (options.sourceURL || '/lodash/template/source[' + (templateCounter++) + ']') + '\n*/';
+
+      try {
+        var result = Function(importsKeys, 'return ' + source + sourceURL).apply(undefined, importsValues);
+      } catch(e) {
+        e.source = source;
+        throw e;
+      }
+      if (data) {
+        return result(data);
+      }
+      // provide the compiled function's source by its `toString` method, in
+      // supported environments, or the `source` property as a convenience for
+      // inlining compiled templates during the build process
+      result.source = source;
+      return result;
+    }
+
+    /**
+     * Executes the callback `n` times, returning an array of the results
+     * of each callback execution. The callback is bound to `thisArg` and invoked
+     * with one argument; (index).
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @param {number} n The number of times to execute the callback.
+     * @param {Function} callback The function called per iteration.
+     * @param {*} [thisArg] The `this` binding of `callback`.
+     * @returns {Array} Returns an array of the results of each `callback` execution.
+     * @example
+     *
+     * var diceRolls = _.times(3, _.partial(_.random, 1, 6));
+     * // => [3, 6, 4]
+     *
+     * _.times(3, function(n) { mage.castSpell(n); });
+     * // => calls `mage.castSpell(n)` three times, passing `n` of `0`, `1`, and `2` respectively
+     *
+     * _.times(3, function(n) { this.cast(n); }, mage);
+     * // => also calls `mage.castSpell(n)` three times
+     */
+    function times(n, callback, thisArg) {
+      n = (n = +n) > -1 ? n : 0;
+      var index = -1,
+          result = Array(n);
+
+      callback = baseCreateCallback(callback, thisArg, 1);
+      while (++index < n) {
+        result[index] = callback(index);
+      }
+      return result;
+    }
+
+    /**
+     * The inverse of `_.escape` this method converts the HTML entities
+     * `&amp;`, `&lt;`, `&gt;`, `&quot;`, and `&#39;` in `string` to their
+     * corresponding characters.
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @param {string} string The string to unescape.
+     * @returns {string} Returns the unescaped string.
+     * @example
+     *
+     * _.unescape('Fred, Barney &amp; Pebbles');
+     * // => 'Fred, Barney & Pebbles'
+     */
+    function unescape(string) {
+      return string == null ? '' : String(string).replace(reEscapedHtml, unescapeHtmlChar);
+    }
+
+    /**
+     * Generates a unique ID. If `prefix` is provided the ID will be appended to it.
+     *
+     * @static
+     * @memberOf _
+     * @category Utilities
+     * @param {string} [prefix] The value to prefix the ID with.
+     * @returns {string} Returns the unique ID.
+     * @example
+     *
+     * _.uniqueId('contact_');
+     * // => 'contact_104'
+     *
+     * _.uniqueId();
+     * // => '105'
+     */
+    function uniqueId(prefix) {
+      var id = ++idCounter;
+      return String(prefix == null ? '' : prefix) + id;
+    }
+
+    /*--------------------------------------------------------------------------*/
+
+    /**
+     * Creates a `lodash` object that wraps the given value with explicit
+     * method chaining enabled.
+     *
+     * @static
+     * @memberOf _
+     * @category Chaining
+     * @param {*} value The value to wrap.
+     * @returns {Object} Returns the wrapper object.
+     * @example
+     *
+     * var characters = [
+     *   { 'name': 'barney',  'age': 36 },
+     *   { 'name': 'fred',    'age': 40 },
+     *   { 'name': 'pebbles', 'age': 1 }
+     * ];
+     *
+     * var youngest = _.chain(characters)
+     *     .sortBy('age')
+     *     .map(function(chr) { return chr.name + ' is ' + chr.age; })
+     *     .first()
+     *     .value();
+     * // => 'pebbles is 1'
+     */
+    function chain(value) {
+      value = new lodashWrapper(value);
+      value.__chain__ = true;
+      return value;
+    }
+
+    /**
+     * Invokes `interceptor` with the `value` as the first argument and then
+     * returns `value`. The purpose of this method is to "tap into" a method
+     * chain in order to perform operations on intermediate results within
+     * the chain.
+     *
+     * @static
+     * @memberOf _
+     * @category Chaining
+     * @param {*} value The value to provide to `interceptor`.
+     * @param {Function} interceptor The function to invoke.
+     * @returns {*} Returns `value`.
+     * @example
+     *
+     * _([1, 2, 3, 4])
+     *  .tap(function(array) { array.pop(); })
+     *  .reverse()
+     *  .value();
+     * // => [3, 2, 1]
+     */
+    function tap(value, interceptor) {
+      interceptor(value);
+      return value;
+    }
+
+    /**
+     * Enables explicit method chaining on the wrapper object.
+     *
+     * @name chain
+     * @memberOf _
+     * @category Chaining
+     * @returns {*} Returns the wrapper object.
+     * @example
+     *
+     * var characters = [
+     *   { 'name': 'barney', 'age': 36 },
+     *   { 'name': 'fred',   'age': 40 }
+     * ];
+     *
+     * // without explicit chaining
+     * _(characters).first();
+     * // => { 'name': 'barney', 'age': 36 }
+     *
+     * // with explicit chaining
+     * _(characters).chain()
+     *   .first()
+     *   .pick('age')
+     *   .value();
+     * // => { 'age': 36 }
+     */
+    function wrapperChain() {
+      this.__chain__ = true;
+      return this;
+    }
+
+    /**
+     * Produces the `toString` result of the wrapped value.
+     *
+     * @name toString
+     * @memberOf _
+     * @category Chaining
+     * @returns {string} Returns the string result.
+     * @example
+     *
+     * _([1, 2, 3]).toString();
+     * // => '1,2,3'
+     */
+    function wrapperToString() {
+      return String(this.__wrapped__);
+    }
+
+    /**
+     * Extracts the wrapped value.
+     *
+     * @name valueOf
+     * @memberOf _
+     * @alias value
+     * @category Chaining
+     * @returns {*} Returns the wrapped value.
+     * @example
+     *
+     * _([1, 2, 3]).valueOf();
+     * // => [1, 2, 3]
+     */
+    function wrapperValueOf() {
+      return this.__wrapped__;
+    }
+
+    /*--------------------------------------------------------------------------*/
+
+    // add functions that return wrapped values when chaining
+    lodash.after = after;
+    lodash.assign = assign;
+    lodash.at = at;
+    lodash.bind = bind;
+    lodash.bindAll = bindAll;
+    lodash.bindKey = bindKey;
+    lodash.chain = chain;
+    lodash.compact = compact;
+    lodash.compose = compose;
+    lodash.constant = constant;
+    lodash.countBy = countBy;
+    lodash.create = create;
+    lodash.createCallback = createCallback;
+    lodash.curry = curry;
+    lodash.debounce = debounce;
+    lodash.defaults = defaults;
+    lodash.defer = defer;
+    lodash.delay = delay;
+    lodash.difference = difference;
+    lodash.filter = filter;
+    lodash.flatten = flatten;
+    lodash.forEach = forEach;
+    lodash.forEachRight = forEachRight;
+    lodash.forIn = forIn;
+    lodash.forInRight = forInRight;
+    lodash.forOwn = forOwn;
+    lodash.forOwnRight = forOwnRight;
+    lodash.functions = functions;
+    lodash.groupBy = groupBy;
+    lodash.indexBy = indexBy;
+    lodash.initial = initial;
+    lodash.intersection = intersection;
+    lodash.invert = invert;
+    lodash.invoke = invoke;
+    lodash.keys = keys;
+    lodash.map = map;
+    lodash.mapValues = mapValues;
+    lodash.max = max;
+    lodash.memoize = memoize;
+    lodash.merge = merge;
+    lodash.min = min;
+    lodash.omit = omit;
+    lodash.once = once;
+    lodash.pairs = pairs;
+    lodash.partial = partial;
+    lodash.partialRight = partialRight;
+    lodash.pick = pick;
+    lodash.pluck = pluck;
+    lodash.property = property;
+    lodash.pull = pull;
+    lodash.range = range;
+    lodash.reject = reject;
+    lodash.remove = remove;
+    lodash.rest = rest;
+    lodash.shuffle = shuffle;
+    lodash.sortBy = sortBy;
+    lodash.tap = tap;
+    lodash.throttle = throttle;
+    lodash.times = times;
+    lodash.toArray = toArray;
+    lodash.transform = transform;
+    lodash.union = union;
+    lodash.uniq = uniq;
+    lodash.values = values;
+    lodash.where = where;
+    lodash.without = without;
+    lodash.wrap = wrap;
+    lodash.xor = xor;
+    lodash.zip = zip;
+    lodash.zipObject = zipObject;
+
+    // add aliases
+    lodash.collect = map;
+    lodash.drop = rest;
+    lodash.each = forEach;
+    lodash.eachRight = forEachRight;
+    lodash.extend = assign;
+    lodash.methods = functions;
+    lodash.object = zipObject;
+    lodash.select = filter;
+    lodash.tail = rest;
+    lodash.unique = uniq;
+    lodash.unzip = zip;
+
+    // add functions to `lodash.prototype`
+    mixin(lodash);
+
+    /*--------------------------------------------------------------------------*/
+
+    // add functions that return unwrapped values when chaining
+    lodash.clone = clone;
+    lodash.cloneDeep = cloneDeep;
+    lodash.contains = contains;
+    lodash.escape = escape;
+    lodash.every = every;
+    lodash.find = find;
+    lodash.findIndex = findIndex;
+    lodash.findKey = findKey;
+    lodash.findLast = findLast;
+    lodash.findLastIndex = findLastIndex;
+    lodash.findLastKey = findLastKey;
+    lodash.has = has;
+    lodash.identity = identity;
+    lodash.indexOf = indexOf;
+    lodash.isArguments = isArguments;
+    lodash.isArray = isArray;
+    lodash.isBoolean = isBoolean;
+    lodash.isDate = isDate;
+    lodash.isElement = isElement;
+    lodash.isEmpty = isEmpty;
+    lodash.isEqual = isEqual;
+    lodash.isFinite = isFinite;
+    lodash.isFunction = isFunction;
+    lodash.isNaN = isNaN;
+    lodash.isNull = isNull;
+    lodash.isNumber = isNumber;
+    lodash.isObject = isObject;
+    lodash.isPlainObject = isPlainObject;
+    lodash.isRegExp = isRegExp;
+    lodash.isString = isString;
+    lodash.isUndefined = isUndefined;
+    lodash.lastIndexOf = lastIndexOf;
+    lodash.mixin = mixin;
+    lodash.noConflict = noConflict;
+    lodash.noop = noop;
+    lodash.now = now;
+    lodash.parseInt = parseInt;
+    lodash.random = random;
+    lodash.reduce = reduce;
+    lodash.reduceRight = reduceRight;
+    lodash.result = result;
+    lodash.runInContext = runInContext;
+    lodash.size = size;
+    lodash.some = some;
+    lodash.sortedIndex = sortedIndex;
+    lodash.template = template;
+    lodash.unescape = unescape;
+    lodash.uniqueId = uniqueId;
+
+    // add aliases
+    lodash.all = every;
+    lodash.any = some;
+    lodash.detect = find;
+    lodash.findWhere = find;
+    lodash.foldl = reduce;
+    lodash.foldr = reduceRight;
+    lodash.include = contains;
+    lodash.inject = reduce;
+
+    mixin(function() {
+      var source = {}
+      forOwn(lodash, function(func, methodName) {
+        if (!lodash.prototype[methodName]) {
+          source[methodName] = func;
+        }
+      });
+      return source;
+    }(), false);
+
+    /*--------------------------------------------------------------------------*/
+
+    // add functions capable of returning wrapped and unwrapped values when chaining
+    lodash.first = first;
+    lodash.last = last;
+    lodash.sample = sample;
+
+    // add aliases
+    lodash.take = first;
+    lodash.head = first;
+
+    forOwn(lodash, function(func, methodName) {
+      var callbackable = methodName !== 'sample';
+      if (!lodash.prototype[methodName]) {
+        lodash.prototype[methodName]= function(n, guard) {
+          var chainAll = this.__chain__,
+              result = func(this.__wrapped__, n, guard);
+
+          return !chainAll && (n == null || (guard && !(callbackable && typeof n == 'function')))
+            ? result
+            : new lodashWrapper(result, chainAll);
+        };
+      }
+    });
+
+    /*--------------------------------------------------------------------------*/
+
+    /**
+     * The semantic version number.
+     *
+     * @static
+     * @memberOf _
+     * @type string
+     */
+    lodash.VERSION = '2.4.1';
+
+    // add "Chaining" functions to the wrapper
+    lodash.prototype.chain = wrapperChain;
+    lodash.prototype.toString = wrapperToString;
+    lodash.prototype.value = wrapperValueOf;
+    lodash.prototype.valueOf = wrapperValueOf;
+
+    // add `Array` functions that return unwrapped values
+    baseEach(['join', 'pop', 'shift'], function(methodName) {
+      var func = arrayRef[methodName];
+      lodash.prototype[methodName] = function() {
+        var chainAll = this.__chain__,
+            result = func.apply(this.__wrapped__, arguments);
+
+        return chainAll
+          ? new lodashWrapper(result, chainAll)
+          : result;
+      };
+    });
+
+    // add `Array` functions that return the existing wrapped value
+    baseEach(['push', 'reverse', 'sort', 'unshift'], function(methodName) {
+      var func = arrayRef[methodName];
+      lodash.prototype[methodName] = function() {
+        func.apply(this.__wrapped__, arguments);
+        return this;
+      };
+    });
+
+    // add `Array` functions that return new wrapped values
+    baseEach(['concat', 'slice', 'splice'], function(methodName) {
+      var func = arrayRef[methodName];
+      lodash.prototype[methodName] = function() {
+        return new lodashWrapper(func.apply(this.__wrapped__, arguments), this.__chain__);
+      };
+    });
+
+    // avoid array-like object bugs with `Array#shift` and `Array#splice`
+    // in IE < 9, Firefox < 10, Narwhal, and RingoJS
+    if (!support.spliceObjects) {
+      baseEach(['pop', 'shift', 'splice'], function(methodName) {
+        var func = arrayRef[methodName],
+            isSplice = methodName == 'splice';
+
+        lodash.prototype[methodName] = function() {
+          var chainAll = this.__chain__,
+              value = this.__wrapped__,
+              result = func.apply(value, arguments);
+
+          if (value.length === 0) {
+            delete value[0];
+          }
+          return (chainAll || isSplice)
+            ? new lodashWrapper(result, chainAll)
+            : result;
+        };
+      });
+    }
+
+    // add pseudo private property to be used and removed during the build process
+    lodash._baseEach = baseEach;
+    lodash._iteratorTemplate = iteratorTemplate;
+    lodash._shimKeys = shimKeys;
+
+    return lodash;
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  // expose Lo-Dash
+  var _ = runInContext();
+
+  // some AMD build optimizers like r.js check for condition patterns like the following:
+  if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) {
+    // Expose Lo-Dash to the global object even when an AMD loader is present in
+    // case Lo-Dash is loaded with a RequireJS shim config.
+    // See http://requirejs.org/docs/api.html#config-shim
+    root._ = _;
+
+    // define as an anonymous module so, through path mapping, it can be
+    // referenced as the "underscore" module
+    define(function() {
+      return _;
+    });
+  }
+  // check for `exports` after `define` in case a build optimizer adds an `exports` object
+  else if (freeExports && freeModule) {
+    // in Node.js or RingoJS
+    if (moduleExports) {
+      (freeModule.exports = _)._ = _;
+    }
+    // in Narwhal or Rhino -require
+    else {
+      freeExports._ = _;
+    }
+  }
+  else {
+    // in a browser or Rhino
+    root._ = _;
+  }
+}.call(this));
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lodash/package.json b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lodash/package.json
new file mode 100644
index 0000000..691ec59
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/lodash/package.json
@@ -0,0 +1,103 @@
+{
+  "name": "lodash",
+  "version": "2.4.1",
+  "description": "A utility library delivering consistency, customization, performance, & extras.",
+  "homepage": "http://lodash.com/",
+  "license": "MIT",
+  "main": "dist/lodash.js",
+  "keywords": [
+    "amd",
+    "browser",
+    "client",
+    "customize",
+    "functional",
+    "server",
+    "util"
+  ],
+  "author": {
+    "name": "John-David Dalton",
+    "email": "john.david.dalton@gmail.com",
+    "url": "http://allyoucanleet.com/"
+  },
+  "contributors": [
+    {
+      "name": "John-David Dalton",
+      "email": "john.david.dalton@gmail.com",
+      "url": "http://allyoucanleet.com/"
+    },
+    {
+      "name": "Blaine Bublitz",
+      "email": "blaine@iceddev.com",
+      "url": "http://www.iceddev.com/"
+    },
+    {
+      "name": "Kit Cambridge",
+      "email": "github@kitcambridge.be",
+      "url": "http://kitcambridge.be/"
+    },
+    {
+      "name": "Mathias Bynens",
+      "email": "mathias@qiwi.be",
+      "url": "http://mathiasbynens.be/"
+    }
+  ],
+  "bugs": {
+    "url": "https://github.com/lodash/lodash/issues"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/lodash/lodash.git"
+  },
+  "engines": [
+    "node",
+    "rhino"
+  ],
+  "files": [
+    "LICENSE.txt",
+    "lodash.js",
+    "dist/lodash.js",
+    "dist/lodash.min.js",
+    "dist/lodash.compat.js",
+    "dist/lodash.compat.min.js",
+    "dist/lodash.underscore.js",
+    "dist/lodash.underscore.min.js"
+  ],
+  "jam": {
+    "main": "dist/lodash.compat.js",
+    "include": [
+      "LICENSE.txt",
+      "dist/lodash.js",
+      "dist/lodash.min.js",
+      "dist/lodash.compat.js",
+      "dist/lodash.compat.min.js",
+      "dist/lodash.underscore.js",
+      "dist/lodash.underscore.min.js"
+    ]
+  },
+  "volo": {
+    "type": "directory",
+    "ignore": [
+      ".*",
+      "*.custom.*",
+      "*.min.*",
+      "*.template.*",
+      "*.map",
+      "*.md",
+      "lodash.js",
+      "index.js",
+      "bower.json",
+      "component.json",
+      "doc",
+      "modularize",
+      "node_modules",
+      "perf",
+      "test",
+      "vendor"
+    ]
+  },
+  "readme": "# Lo-Dash v2.4.1\nA utility library delivering consistency, [customization](http://lodash.com/custom-builds), [performance](http://lodash.com/benchmarks), & [extras](http://lodash.com/#features).\n\n## Download\n\nCheck out our [wiki]([https://github.com/lodash/lodash/wiki/build-differences]) for details over the differences between builds.\n\n* Modern builds perfect for newer browsers/environments:<br>\n[Development](https://raw.github.com/lodash/lodash/2.4.1/dist/lodash.js) &\n[Production](https://raw.github.com/lodash/lodash/2.4.1/dist/lodash.min.js)\n\n* Compatibility builds for older environment support too:<br>\n[Development](https://raw.github.com/lodash/lodash/2.4.1/dist/lodash.compat.js) &\n[Production](https://raw.github.com/lodash/lodash/2.4.1/dist/lodash.compat.min.js)\n\n* Underscore builds to use as a drop-in replacement:<br>\n[Development](https://raw.github.com/lodash/lodash/2.4.1/dist/lodash.underscore.js) &\n[Production](https://raw.github.com/lodash/lodash/2.4.1/dist/lodash.underscore.min.js)\n\nCDN copies are available on [cdnjs](http://cdnjs.com/libraries/lodash.js/) & [jsDelivr](http://www.jsdelivr.com/#!lodash). For smaller file sizes, create [custom builds](http://lodash.com/custom-builds) with only the features needed.\n\nLove modules? We’ve got you covered with [lodash-amd](https://npmjs.org/package/lodash-amd), [lodash-es6](https://github.com/lodash/lodash-es6), [lodash-node](https://npmjs.org/package/lodash-node), & [npm packages](https://npmjs.org/browse/keyword/lodash-modularized) per method.\n\n## Dive in\n\nThere’s plenty of **[documentation](http://lodash.com/docs)**, [unit tests](http://lodash.com/tests), & [benchmarks](http://lodash.com/benchmarks).<br>\nCheck out <a href=\"http://devdocs.io/lodash/\">DevDocs</a> as a fast, organized, & searchable interface for our documentation.\n\nThe full changelog for this release is available on our [wiki](https://github.com/lodash/lodash/wiki/Changelog).<br>\nA list of upcoming features is available on our [roadmap](https://github.com/lodash/lodash/wiki/Roadmap).\n\n## Features *not* in Underscore\n\n * AMD loader support ([curl](https://github.com/cujojs/curl), [dojo](http://dojotoolkit.org/), [requirejs](http://requirejs.org/), etc.)\n * [_(…)](http://lodash.com/docs#_) supports intuitive chaining\n * [_.at](http://lodash.com/docs#at) for cherry-picking collection values\n * [_.bindKey](http://lodash.com/docs#bindKey) for binding [*“lazy”*](http://michaux.ca/articles/lazy-function-definition-pattern) defined methods\n * [_.clone](http://lodash.com/docs#clone) supports shallow cloning of `Date` & `RegExp` objects\n * [_.cloneDeep](http://lodash.com/docs#cloneDeep) for deep cloning arrays & objects\n * [_.constant](http://lodash.com/docs#constant) & [_.property](http://lodash.com/docs#property) function generators for composing functions\n * [_.contains](http://lodash.com/docs#contains) accepts a `fromIndex`\n * [_.create](http://lodash.com/docs#create) for easier object inheritance\n * [_.createCallback](http://lodash.com/docs#createCallback) for extending callbacks in methods & mixins\n * [_.curry](http://lodash.com/docs#curry) for creating [curried](http://hughfdjackson.com/javascript/2013/07/06/why-curry-helps/) functions\n * [_.debounce](http://lodash.com/docs#debounce) & [_.throttle](http://lodash.com/docs#throttle) accept additional `options` for more control\n * [_.findIndex](http://lodash.com/docs#findIndex) & [_.findKey](http://lodash.com/docs#findKey) for finding indexes & keys\n * [_.forEach](http://lodash.com/docs#forEach) is chainable & supports exiting early\n * [_.forIn](http://lodash.com/docs#forIn) for iterating own & inherited properties\n * [_.forOwn](http://lodash.com/docs#forOwn) for iterating own properties\n * [_.isPlainObject](http://lodash.com/docs#isPlainObject) for checking if values are created by `Object`\n * [_.mapValues](http://lodash.com/docs#mapValues) for [mapping](http://lodash.com/docs#map) values to an object\n * [_.memoize](http://lodash.com/docs#memoize) exposes the `cache` of memoized functions\n * [_.merge](http://lodash.com/docs#merge) for a deep [_.extend](http://lodash.com/docs#extend)\n * [_.noop](http://lodash.com/docs#noop) for function placeholders\n * [_.now](http://lodash.com/docs#now) as a cross-browser `Date.now` alternative\n * [_.parseInt](http://lodash.com/docs#parseInt) for consistent behavior\n * [_.pull](http://lodash.com/docs#pull) & [_.remove](http://lodash.com/docs#remove) for mutating arrays\n * [_.random](http://lodash.com/docs#random) supports returning floating-point numbers\n * [_.runInContext](http://lodash.com/docs#runInContext) for easier mocking\n * [_.sortBy](http://lodash.com/docs#sortBy) supports sorting by multiple properties\n * [_.support](http://lodash.com/docs#support) for flagging environment features\n * [_.template](http://lodash.com/docs#template) supports [*“imports”*](http://lodash.com/docs#templateSettings_imports) options & [ES6 template delimiters](http://people.mozilla.org/~jorendorff/es6-draft.html#sec-literals-string-literals)\n * [_.transform](http://lodash.com/docs#transform) as a powerful alternative to [_.reduce](http://lodash.com/docs#reduce) for transforming objects\n * [_.where](http://lodash.com/docs#where) supports deep object comparisons\n * [_.xor](http://lodash.com/docs#xor) as a companion to [_.difference](http://lodash.com/docs#difference), [_.intersection](http://lodash.com/docs#intersection), & [_.union](http://lodash.com/docs#union)\n * [_.zip](http://lodash.com/docs#zip) is capable of unzipping values\n * [_.omit](http://lodash.com/docs#omit), [_.pick](http://lodash.com/docs#pick), &\n   [more](http://lodash.com/docs \"_.assign, _.clone, _.cloneDeep, _.first, _.initial, _.isEqual, _.last, _.merge, _.rest\") accept callbacks\n * [_.contains](http://lodash.com/docs#contains), [_.toArray](http://lodash.com/docs#toArray), &\n   [more](http://lodash.com/docs \"_.at, _.countBy, _.every, _.filter, _.find, _.forEach, _.forEachRight, _.groupBy, _.invoke, _.map, _.max, _.min, _.pluck, _.reduce, _.reduceRight, _.reject, _.shuffle, _.size, _.some, _.sortBy, _.where\") accept strings\n * [_.filter](http://lodash.com/docs#filter), [_.map](http://lodash.com/docs#map), &\n   [more](http://lodash.com/docs \"_.countBy, _.every, _.find, _.findKey, _.findLast, _.findLastIndex, _.findLastKey, _.first, _.groupBy, _.initial, _.last, _.max, _.min, _.reject, _.rest, _.some, _.sortBy, _.sortedIndex, _.uniq\") support *“_.pluck”* & *“_.where”* shorthands\n * [_.findLast](http://lodash.com/docs#findLast), [_.findLastIndex](http://lodash.com/docs#findLastIndex), &\n   [more](http://lodash.com/docs \"_.findLastKey, _.forEachRight, _.forInRight, _.forOwnRight, _.partialRight\") right-associative methods\n\n## Resources\n\n * Podcasts\n  - [JavaScript Jabber](http://javascriptjabber.com/079-jsj-lo-dash-with-john-david-dalton/)\n\n * Posts\n  - [Say “Hello” to Lo-Dash](http://kitcambridge.be/blog/say-hello-to-lo-dash/)\n  - [Custom builds in Lo-Dash 2.0](http://kitcambridge.be/blog/custom-builds-in-lo-dash-2-dot-0/)\n\n * Videos\n  - [Introduction](https://vimeo.com/44154599)\n  - [Origins](https://vimeo.com/44154600)\n  - [Optimizations & builds](https://vimeo.com/44154601)\n  - [Native method use](https://vimeo.com/48576012)\n  - [Testing](https://vimeo.com/45865290)\n  - [CascadiaJS ’12](http://www.youtube.com/watch?v=dpPy4f_SeEk)\n\n A list of other community created podcasts, posts, & videos is available on our [wiki](https://github.com/lodash/lodash/wiki/Resources).\n\n## Support\n\nTested in Chrome 5~31, Firefox 2~25, IE 6-11, Opera 9.25~17, Safari 3-7, Node.js 0.6.21~0.10.22, Narwhal 0.3.2, PhantomJS 1.9.2, RingoJS 0.9, & Rhino 1.7RC5.<br>\nAutomated browser test results [are available](https://saucelabs.com/u/lodash) as well as [Travis CI](https://travis-ci.org/) builds for [lodash](https://travis-ci.org/lodash/lodash/), [lodash-cli](https://travis-ci.org/lodash/lodash-cli/), [lodash-amd](https://travis-ci.org/lodash/lodash-amd/), [lodash-node](https://travis-ci.org/lodash/lodash-node/), & [grunt-lodash](https://travis-ci.org/lodash/grunt-lodash).\n\nSpecial thanks to [Sauce Labs](https://saucelabs.com/) for providing automated browser testing.<br>\n[![Sauce Labs](http://lodash.com/_img/sauce.png)](https://saucelabs.com/ \"Sauce Labs: Selenium Testing & More\")\n\n## Installation & usage\n\nIn browsers:\n\n```html\n<script src=\"lodash.js\"></script>\n```\n\nUsing [`npm`](http://npmjs.org/):\n\n```bash\nnpm i --save lodash\n\n{sudo} npm i -g lodash\nnpm ln lodash\n```\n\nIn [Node.js](http://nodejs.org/) & [Ringo](http://ringojs.org/):\n\n```js\nvar _ = require('lodash');\n// or as Underscore\nvar _ = require('lodash/dist/lodash.underscore');\n```\n\n**Notes:**\n * Don’t assign values to [special variable](http://nodejs.org/api/repl.html#repl_repl_features) `_` when in the REPL\n * If Lo-Dash is installed globally, run [`npm ln lodash`](http://blog.nodejs.org/2011/03/23/npm-1-0-global-vs-local-installation/) in your project’s root directory *before* requiring it\n\nIn [Rhino](http://www.mozilla.org/rhino/):\n\n```js\nload('lodash.js');\n```\n\nIn an AMD loader:\n\n```js\nrequire({\n  'packages': [\n    { 'name': 'lodash', 'location': 'path/to/lodash', 'main': 'lodash' }\n  ]\n},\n['lodash'], function(_) {\n  console.log(_.VERSION);\n});\n```\n\n## Author\n\n| [![twitter/jdalton](http://gravatar.com/avatar/299a3d891ff1920b69c364d061007043?s=70)](https://twitter.com/jdalton \"Follow @jdalton on Twitter\") |\n|---|\n| [John-David Dalton](http://allyoucanleet.com/) |\n\n## Contributors\n\n| [![twitter/blainebublitz](http://gravatar.com/avatar/ac1c67fd906c9fecd823ce302283b4c1?s=70)](https://twitter.com/blainebublitz \"Follow @BlaineBublitz on Twitter\") | [![twitter/kitcambridge](http://gravatar.com/avatar/6662a1d02f351b5ef2f8b4d815804661?s=70)](https://twitter.com/kitcambridge \"Follow @kitcambridge on Twitter\") | [![twitter/mathias](http://gravatar.com/avatar/24e08a9ea84deb17ae121074d0f17125?s=70)](https://twitter.com/mathias \"Follow @mathias on Twitter\") |\n|---|---|---|\n| [Blaine Bublitz](http://www.iceddev.com/) | [Kit Cambridge](http://kitcambridge.be/) | [Mathias Bynens](http://mathiasbynens.be/) |\n\n[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/lodash/lodash/trend.png)](https://bitdeli.com/free \"Bitdeli Badge\")\n",
+  "readmeFilename": "README.md",
+  "_id": "lodash@2.4.1",
+  "_from": "lodash@~2.4.1",
+  "scripts": {}
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/.npmignore b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/.npmignore
new file mode 100644
index 0000000..38344f8
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/.npmignore
@@ -0,0 +1,5 @@
+build/
+test/
+examples/
+fs.js
+zlib.js
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/LICENSE b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/LICENSE
new file mode 100644
index 0000000..e3d4e69
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/LICENSE
@@ -0,0 +1,18 @@
+Copyright Joyent, Inc. and other Node contributors. All rights reserved.
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to
+deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+sell copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+IN THE SOFTWARE.
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/README.md b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/README.md
new file mode 100644
index 0000000..3fb3e80
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/README.md
@@ -0,0 +1,15 @@
+# readable-stream
+
+***Node-core streams for userland***
+
+[![NPM](https://nodei.co/npm/readable-stream.png?downloads=true&downloadRank=true)](https://nodei.co/npm/readable-stream/)
+[![NPM](https://nodei.co/npm-dl/readable-stream.png?&months=6&height=3)](https://nodei.co/npm/readable-stream/)
+
+This package is a mirror of the Streams2 and Streams3 implementations in Node-core.
+
+If you want to guarantee a stable streams base, regardless of what version of Node you, or the users of your libraries are using, use **readable-stream** *only* and avoid the *"stream"* module in Node-core.
+
+**readable-stream** comes in two major versions, v1.0.x and v1.1.x. The former tracks the Streams2 implementation in Node 0.10, including bug-fixes and minor improvements as they are added. The latter tracks Streams3 as it develops in Node 0.11; we will likely see a v1.2.x branch for Node 0.12.
+
+**readable-stream** uses proper patch-level versioning so if you pin to `"~1.0.0"` you’ll get the latest Node 0.10 Streams2 implementation, including any fixes and minor non-breaking improvements. The patch-level versions of 1.0.x and 1.1.x should mirror the patch-level versions of Node-core releases. You should prefer the **1.0.x** releases for now and when you’re ready to start using Streams3, pin to `"~1.1.0"`
+
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/duplex.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/duplex.js
new file mode 100644
index 0000000..ca807af
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/duplex.js
@@ -0,0 +1 @@
+module.exports = require("./lib/_stream_duplex.js")
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/lib/_stream_duplex.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/lib/_stream_duplex.js
new file mode 100644
index 0000000..b513d61
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/lib/_stream_duplex.js
@@ -0,0 +1,89 @@
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// a duplex stream is just a stream that is both readable and writable.
+// Since JS doesn't have multiple prototypal inheritance, this class
+// prototypally inherits from Readable, and then parasitically from
+// Writable.
+
+module.exports = Duplex;
+
+/*<replacement>*/
+var objectKeys = Object.keys || function (obj) {
+  var keys = [];
+  for (var key in obj) keys.push(key);
+  return keys;
+}
+/*</replacement>*/
+
+
+/*<replacement>*/
+var util = require('core-util-is');
+util.inherits = require('inherits');
+/*</replacement>*/
+
+var Readable = require('./_stream_readable');
+var Writable = require('./_stream_writable');
+
+util.inherits(Duplex, Readable);
+
+forEach(objectKeys(Writable.prototype), function(method) {
+  if (!Duplex.prototype[method])
+    Duplex.prototype[method] = Writable.prototype[method];
+});
+
+function Duplex(options) {
+  if (!(this instanceof Duplex))
+    return new Duplex(options);
+
+  Readable.call(this, options);
+  Writable.call(this, options);
+
+  if (options && options.readable === false)
+    this.readable = false;
+
+  if (options && options.writable === false)
+    this.writable = false;
+
+  this.allowHalfOpen = true;
+  if (options && options.allowHalfOpen === false)
+    this.allowHalfOpen = false;
+
+  this.once('end', onend);
+}
+
+// the no-half-open enforcer
+function onend() {
+  // if we allow half-open state, or if the writable side ended,
+  // then we're ok.
+  if (this.allowHalfOpen || this._writableState.ended)
+    return;
+
+  // no more data can be written.
+  // But allow more writes to happen in this tick.
+  process.nextTick(this.end.bind(this));
+}
+
+function forEach (xs, f) {
+  for (var i = 0, l = xs.length; i < l; i++) {
+    f(xs[i], i);
+  }
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/lib/_stream_passthrough.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/lib/_stream_passthrough.js
new file mode 100644
index 0000000..895ca50
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/lib/_stream_passthrough.js
@@ -0,0 +1,46 @@
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// a passthrough stream.
+// basically just the most minimal sort of Transform stream.
+// Every written chunk gets output as-is.
+
+module.exports = PassThrough;
+
+var Transform = require('./_stream_transform');
+
+/*<replacement>*/
+var util = require('core-util-is');
+util.inherits = require('inherits');
+/*</replacement>*/
+
+util.inherits(PassThrough, Transform);
+
+function PassThrough(options) {
+  if (!(this instanceof PassThrough))
+    return new PassThrough(options);
+
+  Transform.call(this, options);
+}
+
+PassThrough.prototype._transform = function(chunk, encoding, cb) {
+  cb(null, chunk);
+};
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/lib/_stream_readable.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/lib/_stream_readable.js
new file mode 100644
index 0000000..6307220
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/lib/_stream_readable.js
@@ -0,0 +1,982 @@
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+module.exports = Readable;
+
+/*<replacement>*/
+var isArray = require('isarray');
+/*</replacement>*/
+
+
+/*<replacement>*/
+var Buffer = require('buffer').Buffer;
+/*</replacement>*/
+
+Readable.ReadableState = ReadableState;
+
+var EE = require('events').EventEmitter;
+
+/*<replacement>*/
+if (!EE.listenerCount) EE.listenerCount = function(emitter, type) {
+  return emitter.listeners(type).length;
+};
+/*</replacement>*/
+
+var Stream = require('stream');
+
+/*<replacement>*/
+var util = require('core-util-is');
+util.inherits = require('inherits');
+/*</replacement>*/
+
+var StringDecoder;
+
+util.inherits(Readable, Stream);
+
+function ReadableState(options, stream) {
+  options = options || {};
+
+  // the point at which it stops calling _read() to fill the buffer
+  // Note: 0 is a valid value, means "don't call _read preemptively ever"
+  var hwm = options.highWaterMark;
+  this.highWaterMark = (hwm || hwm === 0) ? hwm : 16 * 1024;
+
+  // cast to ints.
+  this.highWaterMark = ~~this.highWaterMark;
+
+  this.buffer = [];
+  this.length = 0;
+  this.pipes = null;
+  this.pipesCount = 0;
+  this.flowing = false;
+  this.ended = false;
+  this.endEmitted = false;
+  this.reading = false;
+
+  // In streams that never have any data, and do push(null) right away,
+  // the consumer can miss the 'end' event if they do some I/O before
+  // consuming the stream.  So, we don't emit('end') until some reading
+  // happens.
+  this.calledRead = false;
+
+  // a flag to be able to tell if the onwrite cb is called immediately,
+  // or on a later tick.  We set this to true at first, becuase any
+  // actions that shouldn't happen until "later" should generally also
+  // not happen before the first write call.
+  this.sync = true;
+
+  // whenever we return null, then we set a flag to say
+  // that we're awaiting a 'readable' event emission.
+  this.needReadable = false;
+  this.emittedReadable = false;
+  this.readableListening = false;
+
+
+  // object stream flag. Used to make read(n) ignore n and to
+  // make all the buffer merging and length checks go away
+  this.objectMode = !!options.objectMode;
+
+  // Crypto is kind of old and crusty.  Historically, its default string
+  // encoding is 'binary' so we have to make this configurable.
+  // Everything else in the universe uses 'utf8', though.
+  this.defaultEncoding = options.defaultEncoding || 'utf8';
+
+  // when piping, we only care about 'readable' events that happen
+  // after read()ing all the bytes and not getting any pushback.
+  this.ranOut = false;
+
+  // the number of writers that are awaiting a drain event in .pipe()s
+  this.awaitDrain = 0;
+
+  // if true, a maybeReadMore has been scheduled
+  this.readingMore = false;
+
+  this.decoder = null;
+  this.encoding = null;
+  if (options.encoding) {
+    if (!StringDecoder)
+      StringDecoder = require('string_decoder/').StringDecoder;
+    this.decoder = new StringDecoder(options.encoding);
+    this.encoding = options.encoding;
+  }
+}
+
+function Readable(options) {
+  if (!(this instanceof Readable))
+    return new Readable(options);
+
+  this._readableState = new ReadableState(options, this);
+
+  // legacy
+  this.readable = true;
+
+  Stream.call(this);
+}
+
+// Manually shove something into the read() buffer.
+// This returns true if the highWaterMark has not been hit yet,
+// similar to how Writable.write() returns true if you should
+// write() some more.
+Readable.prototype.push = function(chunk, encoding) {
+  var state = this._readableState;
+
+  if (typeof chunk === 'string' && !state.objectMode) {
+    encoding = encoding || state.defaultEncoding;
+    if (encoding !== state.encoding) {
+      chunk = new Buffer(chunk, encoding);
+      encoding = '';
+    }
+  }
+
+  return readableAddChunk(this, state, chunk, encoding, false);
+};
+
+// Unshift should *always* be something directly out of read()
+Readable.prototype.unshift = function(chunk) {
+  var state = this._readableState;
+  return readableAddChunk(this, state, chunk, '', true);
+};
+
+function readableAddChunk(stream, state, chunk, encoding, addToFront) {
+  var er = chunkInvalid(state, chunk);
+  if (er) {
+    stream.emit('error', er);
+  } else if (chunk === null || chunk === undefined) {
+    state.reading = false;
+    if (!state.ended)
+      onEofChunk(stream, state);
+  } else if (state.objectMode || chunk && chunk.length > 0) {
+    if (state.ended && !addToFront) {
+      var e = new Error('stream.push() after EOF');
+      stream.emit('error', e);
+    } else if (state.endEmitted && addToFront) {
+      var e = new Error('stream.unshift() after end event');
+      stream.emit('error', e);
+    } else {
+      if (state.decoder && !addToFront && !encoding)
+        chunk = state.decoder.write(chunk);
+
+      // update the buffer info.
+      state.length += state.objectMode ? 1 : chunk.length;
+      if (addToFront) {
+        state.buffer.unshift(chunk);
+      } else {
+        state.reading = false;
+        state.buffer.push(chunk);
+      }
+
+      if (state.needReadable)
+        emitReadable(stream);
+
+      maybeReadMore(stream, state);
+    }
+  } else if (!addToFront) {
+    state.reading = false;
+  }
+
+  return needMoreData(state);
+}
+
+
+
+// if it's past the high water mark, we can push in some more.
+// Also, if we have no data yet, we can stand some
+// more bytes.  This is to work around cases where hwm=0,
+// such as the repl.  Also, if the push() triggered a
+// readable event, and the user called read(largeNumber) such that
+// needReadable was set, then we ought to push more, so that another
+// 'readable' event will be triggered.
+function needMoreData(state) {
+  return !state.ended &&
+         (state.needReadable ||
+          state.length < state.highWaterMark ||
+          state.length === 0);
+}
+
+// backwards compatibility.
+Readable.prototype.setEncoding = function(enc) {
+  if (!StringDecoder)
+    StringDecoder = require('string_decoder/').StringDecoder;
+  this._readableState.decoder = new StringDecoder(enc);
+  this._readableState.encoding = enc;
+};
+
+// Don't raise the hwm > 128MB
+var MAX_HWM = 0x800000;
+function roundUpToNextPowerOf2(n) {
+  if (n >= MAX_HWM) {
+    n = MAX_HWM;
+  } else {
+    // Get the next highest power of 2
+    n--;
+    for (var p = 1; p < 32; p <<= 1) n |= n >> p;
+    n++;
+  }
+  return n;
+}
+
+function howMuchToRead(n, state) {
+  if (state.length === 0 && state.ended)
+    return 0;
+
+  if (state.objectMode)
+    return n === 0 ? 0 : 1;
+
+  if (n === null || isNaN(n)) {
+    // only flow one buffer at a time
+    if (state.flowing && state.buffer.length)
+      return state.buffer[0].length;
+    else
+      return state.length;
+  }
+
+  if (n <= 0)
+    return 0;
+
+  // If we're asking for more than the target buffer level,
+  // then raise the water mark.  Bump up to the next highest
+  // power of 2, to prevent increasing it excessively in tiny
+  // amounts.
+  if (n > state.highWaterMark)
+    state.highWaterMark = roundUpToNextPowerOf2(n);
+
+  // don't have that much.  return null, unless we've ended.
+  if (n > state.length) {
+    if (!state.ended) {
+      state.needReadable = true;
+      return 0;
+    } else
+      return state.length;
+  }
+
+  return n;
+}
+
+// you can override either this method, or the async _read(n) below.
+Readable.prototype.read = function(n) {
+  var state = this._readableState;
+  state.calledRead = true;
+  var nOrig = n;
+  var ret;
+
+  if (typeof n !== 'number' || n > 0)
+    state.emittedReadable = false;
+
+  // if we're doing read(0) to trigger a readable event, but we
+  // already have a bunch of data in the buffer, then just trigger
+  // the 'readable' event and move on.
+  if (n === 0 &&
+      state.needReadable &&
+      (state.length >= state.highWaterMark || state.ended)) {
+    emitReadable(this);
+    return null;
+  }
+
+  n = howMuchToRead(n, state);
+
+  // if we've ended, and we're now clear, then finish it up.
+  if (n === 0 && state.ended) {
+    ret = null;
+
+    // In cases where the decoder did not receive enough data
+    // to produce a full chunk, then immediately received an
+    // EOF, state.buffer will contain [<Buffer >, <Buffer 00 ...>].
+    // howMuchToRead will see this and coerce the amount to
+    // read to zero (because it's looking at the length of the
+    // first <Buffer > in state.buffer), and we'll end up here.
+    //
+    // This can only happen via state.decoder -- no other venue
+    // exists for pushing a zero-length chunk into state.buffer
+    // and triggering this behavior. In this case, we return our
+    // remaining data and end the stream, if appropriate.
+    if (state.length > 0 && state.decoder) {
+      ret = fromList(n, state);
+      state.length -= ret.length;
+    }
+
+    if (state.length === 0)
+      endReadable(this);
+
+    return ret;
+  }
+
+  // All the actual chunk generation logic needs to be
+  // *below* the call to _read.  The reason is that in certain
+  // synthetic stream cases, such as passthrough streams, _read
+  // may be a completely synchronous operation which may change
+  // the state of the read buffer, providing enough data when
+  // before there was *not* enough.
+  //
+  // So, the steps are:
+  // 1. Figure out what the state of things will be after we do
+  // a read from the buffer.
+  //
+  // 2. If that resulting state will trigger a _read, then call _read.
+  // Note that this may be asynchronous, or synchronous.  Yes, it is
+  // deeply ugly to write APIs this way, but that still doesn't mean
+  // that the Readable class should behave improperly, as streams are
+  // designed to be sync/async agnostic.
+  // Take note if the _read call is sync or async (ie, if the read call
+  // has returned yet), so that we know whether or not it's safe to emit
+  // 'readable' etc.
+  //
+  // 3. Actually pull the requested chunks out of the buffer and return.
+
+  // if we need a readable event, then we need to do some reading.
+  var doRead = state.needReadable;
+
+  // if we currently have less than the highWaterMark, then also read some
+  if (state.length - n <= state.highWaterMark)
+    doRead = true;
+
+  // however, if we've ended, then there's no point, and if we're already
+  // reading, then it's unnecessary.
+  if (state.ended || state.reading)
+    doRead = false;
+
+  if (doRead) {
+    state.reading = true;
+    state.sync = true;
+    // if the length is currently zero, then we *need* a readable event.
+    if (state.length === 0)
+      state.needReadable = true;
+    // call internal read method
+    this._read(state.highWaterMark);
+    state.sync = false;
+  }
+
+  // If _read called its callback synchronously, then `reading`
+  // will be false, and we need to re-evaluate how much data we
+  // can return to the user.
+  if (doRead && !state.reading)
+    n = howMuchToRead(nOrig, state);
+
+  if (n > 0)
+    ret = fromList(n, state);
+  else
+    ret = null;
+
+  if (ret === null) {
+    state.needReadable = true;
+    n = 0;
+  }
+
+  state.length -= n;
+
+  // If we have nothing in the buffer, then we want to know
+  // as soon as we *do* get something into the buffer.
+  if (state.length === 0 && !state.ended)
+    state.needReadable = true;
+
+  // If we happened to read() exactly the remaining amount in the
+  // buffer, and the EOF has been seen at this point, then make sure
+  // that we emit 'end' on the very next tick.
+  if (state.ended && !state.endEmitted && state.length === 0)
+    endReadable(this);
+
+  return ret;
+};
+
+function chunkInvalid(state, chunk) {
+  var er = null;
+  if (!Buffer.isBuffer(chunk) &&
+      'string' !== typeof chunk &&
+      chunk !== null &&
+      chunk !== undefined &&
+      !state.objectMode) {
+    er = new TypeError('Invalid non-string/buffer chunk');
+  }
+  return er;
+}
+
+
+function onEofChunk(stream, state) {
+  if (state.decoder && !state.ended) {
+    var chunk = state.decoder.end();
+    if (chunk && chunk.length) {
+      state.buffer.push(chunk);
+      state.length += state.objectMode ? 1 : chunk.length;
+    }
+  }
+  state.ended = true;
+
+  // if we've ended and we have some data left, then emit
+  // 'readable' now to make sure it gets picked up.
+  if (state.length > 0)
+    emitReadable(stream);
+  else
+    endReadable(stream);
+}
+
+// Don't emit readable right away in sync mode, because this can trigger
+// another read() call => stack overflow.  This way, it might trigger
+// a nextTick recursion warning, but that's not so bad.
+function emitReadable(stream) {
+  var state = stream._readableState;
+  state.needReadable = false;
+  if (state.emittedReadable)
+    return;
+
+  state.emittedReadable = true;
+  if (state.sync)
+    process.nextTick(function() {
+      emitReadable_(stream);
+    });
+  else
+    emitReadable_(stream);
+}
+
+function emitReadable_(stream) {
+  stream.emit('readable');
+}
+
+
+// at this point, the user has presumably seen the 'readable' event,
+// and called read() to consume some data.  that may have triggered
+// in turn another _read(n) call, in which case reading = true if
+// it's in progress.
+// However, if we're not ended, or reading, and the length < hwm,
+// then go ahead and try to read some more preemptively.
+function maybeReadMore(stream, state) {
+  if (!state.readingMore) {
+    state.readingMore = true;
+    process.nextTick(function() {
+      maybeReadMore_(stream, state);
+    });
+  }
+}
+
+function maybeReadMore_(stream, state) {
+  var len = state.length;
+  while (!state.reading && !state.flowing && !state.ended &&
+         state.length < state.highWaterMark) {
+    stream.read(0);
+    if (len === state.length)
+      // didn't get any data, stop spinning.
+      break;
+    else
+      len = state.length;
+  }
+  state.readingMore = false;
+}
+
+// abstract method.  to be overridden in specific implementation classes.
+// call cb(er, data) where data is <= n in length.
+// for virtual (non-string, non-buffer) streams, "length" is somewhat
+// arbitrary, and perhaps not very meaningful.
+Readable.prototype._read = function(n) {
+  this.emit('error', new Error('not implemented'));
+};
+
+Readable.prototype.pipe = function(dest, pipeOpts) {
+  var src = this;
+  var state = this._readableState;
+
+  switch (state.pipesCount) {
+    case 0:
+      state.pipes = dest;
+      break;
+    case 1:
+      state.pipes = [state.pipes, dest];
+      break;
+    default:
+      state.pipes.push(dest);
+      break;
+  }
+  state.pipesCount += 1;
+
+  var doEnd = (!pipeOpts || pipeOpts.end !== false) &&
+              dest !== process.stdout &&
+              dest !== process.stderr;
+
+  var endFn = doEnd ? onend : cleanup;
+  if (state.endEmitted)
+    process.nextTick(endFn);
+  else
+    src.once('end', endFn);
+
+  dest.on('unpipe', onunpipe);
+  function onunpipe(readable) {
+    if (readable !== src) return;
+    cleanup();
+  }
+
+  function onend() {
+    dest.end();
+  }
+
+  // when the dest drains, it reduces the awaitDrain counter
+  // on the source.  This would be more elegant with a .once()
+  // handler in flow(), but adding and removing repeatedly is
+  // too slow.
+  var ondrain = pipeOnDrain(src);
+  dest.on('drain', ondrain);
+
+  function cleanup() {
+    // cleanup event handlers once the pipe is broken
+    dest.removeListener('close', onclose);
+    dest.removeListener('finish', onfinish);
+    dest.removeListener('drain', ondrain);
+    dest.removeListener('error', onerror);
+    dest.removeListener('unpipe', onunpipe);
+    src.removeListener('end', onend);
+    src.removeListener('end', cleanup);
+
+    // if the reader is waiting for a drain event from this
+    // specific writer, then it would cause it to never start
+    // flowing again.
+    // So, if this is awaiting a drain, then we just call it now.
+    // If we don't know, then assume that we are waiting for one.
+    if (!dest._writableState || dest._writableState.needDrain)
+      ondrain();
+  }
+
+  // if the dest has an error, then stop piping into it.
+  // however, don't suppress the throwing behavior for this.
+  function onerror(er) {
+    unpipe();
+    dest.removeListener('error', onerror);
+    if (EE.listenerCount(dest, 'error') === 0)
+      dest.emit('error', er);
+  }
+  // This is a brutally ugly hack to make sure that our error handler
+  // is attached before any userland ones.  NEVER DO THIS.
+  if (!dest._events || !dest._events.error)
+    dest.on('error', onerror);
+  else if (isArray(dest._events.error))
+    dest._events.error.unshift(onerror);
+  else
+    dest._events.error = [onerror, dest._events.error];
+
+
+
+  // Both close and finish should trigger unpipe, but only once.
+  function onclose() {
+    dest.removeListener('finish', onfinish);
+    unpipe();
+  }
+  dest.once('close', onclose);
+  function onfinish() {
+    dest.removeListener('close', onclose);
+    unpipe();
+  }
+  dest.once('finish', onfinish);
+
+  function unpipe() {
+    src.unpipe(dest);
+  }
+
+  // tell the dest that it's being piped to
+  dest.emit('pipe', src);
+
+  // start the flow if it hasn't been started already.
+  if (!state.flowing) {
+    // the handler that waits for readable events after all
+    // the data gets sucked out in flow.
+    // This would be easier to follow with a .once() handler
+    // in flow(), but that is too slow.
+    this.on('readable', pipeOnReadable);
+
+    state.flowing = true;
+    process.nextTick(function() {
+      flow(src);
+    });
+  }
+
+  return dest;
+};
+
+function pipeOnDrain(src) {
+  return function() {
+    var dest = this;
+    var state = src._readableState;
+    state.awaitDrain--;
+    if (state.awaitDrain === 0)
+      flow(src);
+  };
+}
+
+function flow(src) {
+  var state = src._readableState;
+  var chunk;
+  state.awaitDrain = 0;
+
+  function write(dest, i, list) {
+    var written = dest.write(chunk);
+    if (false === written) {
+      state.awaitDrain++;
+    }
+  }
+
+  while (state.pipesCount && null !== (chunk = src.read())) {
+
+    if (state.pipesCount === 1)
+      write(state.pipes, 0, null);
+    else
+      forEach(state.pipes, write);
+
+    src.emit('data', chunk);
+
+    // if anyone needs a drain, then we have to wait for that.
+    if (state.awaitDrain > 0)
+      return;
+  }
+
+  // if every destination was unpiped, either before entering this
+  // function, or in the while loop, then stop flowing.
+  //
+  // NB: This is a pretty rare edge case.
+  if (state.pipesCount === 0) {
+    state.flowing = false;
+
+    // if there were data event listeners added, then switch to old mode.
+    if (EE.listenerCount(src, 'data') > 0)
+      emitDataEvents(src);
+    return;
+  }
+
+  // at this point, no one needed a drain, so we just ran out of data
+  // on the next readable event, start it over again.
+  state.ranOut = true;
+}
+
+function pipeOnReadable() {
+  if (this._readableState.ranOut) {
+    this._readableState.ranOut = false;
+    flow(this);
+  }
+}
+
+
+Readable.prototype.unpipe = function(dest) {
+  var state = this._readableState;
+
+  // if we're not piping anywhere, then do nothing.
+  if (state.pipesCount === 0)
+    return this;
+
+  // just one destination.  most common case.
+  if (state.pipesCount === 1) {
+    // passed in one, but it's not the right one.
+    if (dest && dest !== state.pipes)
+      return this;
+
+    if (!dest)
+      dest = state.pipes;
+
+    // got a match.
+    state.pipes = null;
+    state.pipesCount = 0;
+    this.removeListener('readable', pipeOnReadable);
+    state.flowing = false;
+    if (dest)
+      dest.emit('unpipe', this);
+    return this;
+  }
+
+  // slow case. multiple pipe destinations.
+
+  if (!dest) {
+    // remove all.
+    var dests = state.pipes;
+    var len = state.pipesCount;
+    state.pipes = null;
+    state.pipesCount = 0;
+    this.removeListener('readable', pipeOnReadable);
+    state.flowing = false;
+
+    for (var i = 0; i < len; i++)
+      dests[i].emit('unpipe', this);
+    return this;
+  }
+
+  // try to find the right one.
+  var i = indexOf(state.pipes, dest);
+  if (i === -1)
+    return this;
+
+  state.pipes.splice(i, 1);
+  state.pipesCount -= 1;
+  if (state.pipesCount === 1)
+    state.pipes = state.pipes[0];
+
+  dest.emit('unpipe', this);
+
+  return this;
+};
+
+// set up data events if they are asked for
+// Ensure readable listeners eventually get something
+Readable.prototype.on = function(ev, fn) {
+  var res = Stream.prototype.on.call(this, ev, fn);
+
+  if (ev === 'data' && !this._readableState.flowing)
+    emitDataEvents(this);
+
+  if (ev === 'readable' && this.readable) {
+    var state = this._readableState;
+    if (!state.readableListening) {
+      state.readableListening = true;
+      state.emittedReadable = false;
+      state.needReadable = true;
+      if (!state.reading) {
+        this.read(0);
+      } else if (state.length) {
+        emitReadable(this, state);
+      }
+    }
+  }
+
+  return res;
+};
+Readable.prototype.addListener = Readable.prototype.on;
+
+// pause() and resume() are remnants of the legacy readable stream API
+// If the user uses them, then switch into old mode.
+Readable.prototype.resume = function() {
+  emitDataEvents(this);
+  this.read(0);
+  this.emit('resume');
+};
+
+Readable.prototype.pause = function() {
+  emitDataEvents(this, true);
+  this.emit('pause');
+};
+
+function emitDataEvents(stream, startPaused) {
+  var state = stream._readableState;
+
+  if (state.flowing) {
+    // https://github.com/isaacs/readable-stream/issues/16
+    throw new Error('Cannot switch to old mode now.');
+  }
+
+  var paused = startPaused || false;
+  var readable = false;
+
+  // convert to an old-style stream.
+  stream.readable = true;
+  stream.pipe = Stream.prototype.pipe;
+  stream.on = stream.addListener = Stream.prototype.on;
+
+  stream.on('readable', function() {
+    readable = true;
+
+    var c;
+    while (!paused && (null !== (c = stream.read())))
+      stream.emit('data', c);
+
+    if (c === null) {
+      readable = false;
+      stream._readableState.needReadable = true;
+    }
+  });
+
+  stream.pause = function() {
+    paused = true;
+    this.emit('pause');
+  };
+
+  stream.resume = function() {
+    paused = false;
+    if (readable)
+      process.nextTick(function() {
+        stream.emit('readable');
+      });
+    else
+      this.read(0);
+    this.emit('resume');
+  };
+
+  // now make it start, just in case it hadn't already.
+  stream.emit('readable');
+}
+
+// wrap an old-style stream as the async data source.
+// This is *not* part of the readable stream interface.
+// It is an ugly unfortunate mess of history.
+Readable.prototype.wrap = function(stream) {
+  var state = this._readableState;
+  var paused = false;
+
+  var self = this;
+  stream.on('end', function() {
+    if (state.decoder && !state.ended) {
+      var chunk = state.decoder.end();
+      if (chunk && chunk.length)
+        self.push(chunk);
+    }
+
+    self.push(null);
+  });
+
+  stream.on('data', function(chunk) {
+    if (state.decoder)
+      chunk = state.decoder.write(chunk);
+
+    // don't skip over falsy values in objectMode
+    //if (state.objectMode && util.isNullOrUndefined(chunk))
+    if (state.objectMode && (chunk === null || chunk === undefined))
+      return;
+    else if (!state.objectMode && (!chunk || !chunk.length))
+      return;
+
+    var ret = self.push(chunk);
+    if (!ret) {
+      paused = true;
+      stream.pause();
+    }
+  });
+
+  // proxy all the other methods.
+  // important when wrapping filters and duplexes.
+  for (var i in stream) {
+    if (typeof stream[i] === 'function' &&
+        typeof this[i] === 'undefined') {
+      this[i] = function(method) { return function() {
+        return stream[method].apply(stream, arguments);
+      }}(i);
+    }
+  }
+
+  // proxy certain important events.
+  var events = ['error', 'close', 'destroy', 'pause', 'resume'];
+  forEach(events, function(ev) {
+    stream.on(ev, self.emit.bind(self, ev));
+  });
+
+  // when we try to consume some more bytes, simply unpause the
+  // underlying stream.
+  self._read = function(n) {
+    if (paused) {
+      paused = false;
+      stream.resume();
+    }
+  };
+
+  return self;
+};
+
+
+
+// exposed for testing purposes only.
+Readable._fromList = fromList;
+
+// Pluck off n bytes from an array of buffers.
+// Length is the combined lengths of all the buffers in the list.
+function fromList(n, state) {
+  var list = state.buffer;
+  var length = state.length;
+  var stringMode = !!state.decoder;
+  var objectMode = !!state.objectMode;
+  var ret;
+
+  // nothing in the list, definitely empty.
+  if (list.length === 0)
+    return null;
+
+  if (length === 0)
+    ret = null;
+  else if (objectMode)
+    ret = list.shift();
+  else if (!n || n >= length) {
+    // read it all, truncate the array.
+    if (stringMode)
+      ret = list.join('');
+    else
+      ret = Buffer.concat(list, length);
+    list.length = 0;
+  } else {
+    // read just some of it.
+    if (n < list[0].length) {
+      // just take a part of the first list item.
+      // slice is the same for buffers and strings.
+      var buf = list[0];
+      ret = buf.slice(0, n);
+      list[0] = buf.slice(n);
+    } else if (n === list[0].length) {
+      // first list is a perfect match
+      ret = list.shift();
+    } else {
+      // complex case.
+      // we have enough to cover it, but it spans past the first buffer.
+      if (stringMode)
+        ret = '';
+      else
+        ret = new Buffer(n);
+
+      var c = 0;
+      for (var i = 0, l = list.length; i < l && c < n; i++) {
+        var buf = list[0];
+        var cpy = Math.min(n - c, buf.length);
+
+        if (stringMode)
+          ret += buf.slice(0, cpy);
+        else
+          buf.copy(ret, c, 0, cpy);
+
+        if (cpy < buf.length)
+          list[0] = buf.slice(cpy);
+        else
+          list.shift();
+
+        c += cpy;
+      }
+    }
+  }
+
+  return ret;
+}
+
+function endReadable(stream) {
+  var state = stream._readableState;
+
+  // If we get here before consuming all the bytes, then that is a
+  // bug in node.  Should never happen.
+  if (state.length > 0)
+    throw new Error('endReadable called on non-empty stream');
+
+  if (!state.endEmitted && state.calledRead) {
+    state.ended = true;
+    process.nextTick(function() {
+      // Check that we didn't get one last unshift.
+      if (!state.endEmitted && state.length === 0) {
+        state.endEmitted = true;
+        stream.readable = false;
+        stream.emit('end');
+      }
+    });
+  }
+}
+
+function forEach (xs, f) {
+  for (var i = 0, l = xs.length; i < l; i++) {
+    f(xs[i], i);
+  }
+}
+
+function indexOf (xs, x) {
+  for (var i = 0, l = xs.length; i < l; i++) {
+    if (xs[i] === x) return i;
+  }
+  return -1;
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/lib/_stream_transform.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/lib/_stream_transform.js
new file mode 100644
index 0000000..eb188df
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/lib/_stream_transform.js
@@ -0,0 +1,210 @@
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+// a transform stream is a readable/writable stream where you do
+// something with the data.  Sometimes it's called a "filter",
+// but that's not a great name for it, since that implies a thing where
+// some bits pass through, and others are simply ignored.  (That would
+// be a valid example of a transform, of course.)
+//
+// While the output is causally related to the input, it's not a
+// necessarily symmetric or synchronous transformation.  For example,
+// a zlib stream might take multiple plain-text writes(), and then
+// emit a single compressed chunk some time in the future.
+//
+// Here's how this works:
+//
+// The Transform stream has all the aspects of the readable and writable
+// stream classes.  When you write(chunk), that calls _write(chunk,cb)
+// internally, and returns false if there's a lot of pending writes
+// buffered up.  When you call read(), that calls _read(n) until
+// there's enough pending readable data buffered up.
+//
+// In a transform stream, the written data is placed in a buffer.  When
+// _read(n) is called, it transforms the queued up data, calling the
+// buffered _write cb's as it consumes chunks.  If consuming a single
+// written chunk would result in multiple output chunks, then the first
+// outputted bit calls the readcb, and subsequent chunks just go into
+// the read buffer, and will cause it to emit 'readable' if necessary.
+//
+// This way, back-pressure is actually determined by the reading side,
+// since _read has to be called to start processing a new chunk.  However,
+// a pathological inflate type of transform can cause excessive buffering
+// here.  For example, imagine a stream where every byte of input is
+// interpreted as an integer from 0-255, and then results in that many
+// bytes of output.  Writing the 4 bytes {ff,ff,ff,ff} would result in
+// 1kb of data being output.  In this case, you could write a very small
+// amount of input, and end up with a very large amount of output.  In
+// such a pathological inflating mechanism, there'd be no way to tell
+// the system to stop doing the transform.  A single 4MB write could
+// cause the system to run out of memory.
+//
+// However, even in such a pathological case, only a single written chunk
+// would be consumed, and then the rest would wait (un-transformed) until
+// the results of the previous transformed chunk were consumed.
+
+module.exports = Transform;
+
+var Duplex = require('./_stream_duplex');
+
+/*<replacement>*/
+var util = require('core-util-is');
+util.inherits = require('inherits');
+/*</replacement>*/
+
+util.inherits(Transform, Duplex);
+
+
+function TransformState(options, stream) {
+  this.afterTransform = function(er, data) {
+    return afterTransform(stream, er, data);
+  };
+
+  this.needTransform = false;
+  this.transforming = false;
+  this.writecb = null;
+  this.writechunk = null;
+}
+
+function afterTransform(stream, er, data) {
+  var ts = stream._transformState;
+  ts.transforming = false;
+
+  var cb = ts.writecb;
+
+  if (!cb)
+    return stream.emit('error', new Error('no writecb in Transform class'));
+
+  ts.writechunk = null;
+  ts.writecb = null;
+
+  if (data !== null && data !== undefined)
+    stream.push(data);
+
+  if (cb)
+    cb(er);
+
+  var rs = stream._readableState;
+  rs.reading = false;
+  if (rs.needReadable || rs.length < rs.highWaterMark) {
+    stream._read(rs.highWaterMark);
+  }
+}
+
+
+function Transform(options) {
+  if (!(this instanceof Transform))
+    return new Transform(options);
+
+  Duplex.call(this, options);
+
+  var ts = this._transformState = new TransformState(options, this);
+
+  // when the writable side finishes, then flush out anything remaining.
+  var stream = this;
+
+  // start out asking for a readable event once data is transformed.
+  this._readableState.needReadable = true;
+
+  // we have implemented the _read method, and done the other things
+  // that Readable wants before the first _read call, so unset the
+  // sync guard flag.
+  this._readableState.sync = false;
+
+  this.once('finish', function() {
+    if ('function' === typeof this._flush)
+      this._flush(function(er) {
+        done(stream, er);
+      });
+    else
+      done(stream);
+  });
+}
+
+Transform.prototype.push = function(chunk, encoding) {
+  this._transformState.needTransform = false;
+  return Duplex.prototype.push.call(this, chunk, encoding);
+};
+
+// This is the part where you do stuff!
+// override this function in implementation classes.
+// 'chunk' is an input chunk.
+//
+// Call `push(newChunk)` to pass along transformed output
+// to the readable side.  You may call 'push' zero or more times.
+//
+// Call `cb(err)` when you are done with this chunk.  If you pass
+// an error, then that'll put the hurt on the whole operation.  If you
+// never call cb(), then you'll never get another chunk.
+Transform.prototype._transform = function(chunk, encoding, cb) {
+  throw new Error('not implemented');
+};
+
+Transform.prototype._write = function(chunk, encoding, cb) {
+  var ts = this._transformState;
+  ts.writecb = cb;
+  ts.writechunk = chunk;
+  ts.writeencoding = encoding;
+  if (!ts.transforming) {
+    var rs = this._readableState;
+    if (ts.needTransform ||
+        rs.needReadable ||
+        rs.length < rs.highWaterMark)
+      this._read(rs.highWaterMark);
+  }
+};
+
+// Doesn't matter what the args are here.
+// _transform does all the work.
+// That we got here means that the readable side wants more data.
+Transform.prototype._read = function(n) {
+  var ts = this._transformState;
+
+  if (ts.writechunk !== null && ts.writecb && !ts.transforming) {
+    ts.transforming = true;
+    this._transform(ts.writechunk, ts.writeencoding, ts.afterTransform);
+  } else {
+    // mark that we need a transform, so that any data that comes in
+    // will get processed, now that we've asked for it.
+    ts.needTransform = true;
+  }
+};
+
+
+function done(stream, er) {
+  if (er)
+    return stream.emit('error', er);
+
+  // if there's nothing in the write buffer, then that means
+  // that nothing more will ever be provided
+  var ws = stream._writableState;
+  var rs = stream._readableState;
+  var ts = stream._transformState;
+
+  if (ws.length)
+    throw new Error('calling transform done when ws.length != 0');
+
+  if (ts.transforming)
+    throw new Error('calling transform done when still transforming');
+
+  return stream.push(null);
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/lib/_stream_writable.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/lib/_stream_writable.js
new file mode 100644
index 0000000..4bdaa4f
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/lib/_stream_writable.js
@@ -0,0 +1,386 @@
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// A bit simpler than readable streams.
+// Implement an async ._write(chunk, cb), and it'll handle all
+// the drain event emission and buffering.
+
+module.exports = Writable;
+
+/*<replacement>*/
+var Buffer = require('buffer').Buffer;
+/*</replacement>*/
+
+Writable.WritableState = WritableState;
+
+
+/*<replacement>*/
+var util = require('core-util-is');
+util.inherits = require('inherits');
+/*</replacement>*/
+
+var Stream = require('stream');
+
+util.inherits(Writable, Stream);
+
+function WriteReq(chunk, encoding, cb) {
+  this.chunk = chunk;
+  this.encoding = encoding;
+  this.callback = cb;
+}
+
+function WritableState(options, stream) {
+  options = options || {};
+
+  // the point at which write() starts returning false
+  // Note: 0 is a valid value, means that we always return false if
+  // the entire buffer is not flushed immediately on write()
+  var hwm = options.highWaterMark;
+  this.highWaterMark = (hwm || hwm === 0) ? hwm : 16 * 1024;
+
+  // object stream flag to indicate whether or not this stream
+  // contains buffers or objects.
+  this.objectMode = !!options.objectMode;
+
+  // cast to ints.
+  this.highWaterMark = ~~this.highWaterMark;
+
+  this.needDrain = false;
+  // at the start of calling end()
+  this.ending = false;
+  // when end() has been called, and returned
+  this.ended = false;
+  // when 'finish' is emitted
+  this.finished = false;
+
+  // should we decode strings into buffers before passing to _write?
+  // this is here so that some node-core streams can optimize string
+  // handling at a lower level.
+  var noDecode = options.decodeStrings === false;
+  this.decodeStrings = !noDecode;
+
+  // Crypto is kind of old and crusty.  Historically, its default string
+  // encoding is 'binary' so we have to make this configurable.
+  // Everything else in the universe uses 'utf8', though.
+  this.defaultEncoding = options.defaultEncoding || 'utf8';
+
+  // not an actual buffer we keep track of, but a measurement
+  // of how much we're waiting to get pushed to some underlying
+  // socket or file.
+  this.length = 0;
+
+  // a flag to see when we're in the middle of a write.
+  this.writing = false;
+
+  // a flag to be able to tell if the onwrite cb is called immediately,
+  // or on a later tick.  We set this to true at first, becuase any
+  // actions that shouldn't happen until "later" should generally also
+  // not happen before the first write call.
+  this.sync = true;
+
+  // a flag to know if we're processing previously buffered items, which
+  // may call the _write() callback in the same tick, so that we don't
+  // end up in an overlapped onwrite situation.
+  this.bufferProcessing = false;
+
+  // the callback that's passed to _write(chunk,cb)
+  this.onwrite = function(er) {
+    onwrite(stream, er);
+  };
+
+  // the callback that the user supplies to write(chunk,encoding,cb)
+  this.writecb = null;
+
+  // the amount that is being written when _write is called.
+  this.writelen = 0;
+
+  this.buffer = [];
+
+  // True if the error was already emitted and should not be thrown again
+  this.errorEmitted = false;
+}
+
+function Writable(options) {
+  var Duplex = require('./_stream_duplex');
+
+  // Writable ctor is applied to Duplexes, though they're not
+  // instanceof Writable, they're instanceof Readable.
+  if (!(this instanceof Writable) && !(this instanceof Duplex))
+    return new Writable(options);
+
+  this._writableState = new WritableState(options, this);
+
+  // legacy.
+  this.writable = true;
+
+  Stream.call(this);
+}
+
+// Otherwise people can pipe Writable streams, which is just wrong.
+Writable.prototype.pipe = function() {
+  this.emit('error', new Error('Cannot pipe. Not readable.'));
+};
+
+
+function writeAfterEnd(stream, state, cb) {
+  var er = new Error('write after end');
+  // TODO: defer error events consistently everywhere, not just the cb
+  stream.emit('error', er);
+  process.nextTick(function() {
+    cb(er);
+  });
+}
+
+// If we get something that is not a buffer, string, null, or undefined,
+// and we're not in objectMode, then that's an error.
+// Otherwise stream chunks are all considered to be of length=1, and the
+// watermarks determine how many objects to keep in the buffer, rather than
+// how many bytes or characters.
+function validChunk(stream, state, chunk, cb) {
+  var valid = true;
+  if (!Buffer.isBuffer(chunk) &&
+      'string' !== typeof chunk &&
+      chunk !== null &&
+      chunk !== undefined &&
+      !state.objectMode) {
+    var er = new TypeError('Invalid non-string/buffer chunk');
+    stream.emit('error', er);
+    process.nextTick(function() {
+      cb(er);
+    });
+    valid = false;
+  }
+  return valid;
+}
+
+Writable.prototype.write = function(chunk, encoding, cb) {
+  var state = this._writableState;
+  var ret = false;
+
+  if (typeof encoding === 'function') {
+    cb = encoding;
+    encoding = null;
+  }
+
+  if (Buffer.isBuffer(chunk))
+    encoding = 'buffer';
+  else if (!encoding)
+    encoding = state.defaultEncoding;
+
+  if (typeof cb !== 'function')
+    cb = function() {};
+
+  if (state.ended)
+    writeAfterEnd(this, state, cb);
+  else if (validChunk(this, state, chunk, cb))
+    ret = writeOrBuffer(this, state, chunk, encoding, cb);
+
+  return ret;
+};
+
+function decodeChunk(state, chunk, encoding) {
+  if (!state.objectMode &&
+      state.decodeStrings !== false &&
+      typeof chunk === 'string') {
+    chunk = new Buffer(chunk, encoding);
+  }
+  return chunk;
+}
+
+// if we're already writing something, then just put this
+// in the queue, and wait our turn.  Otherwise, call _write
+// If we return false, then we need a drain event, so set that flag.
+function writeOrBuffer(stream, state, chunk, encoding, cb) {
+  chunk = decodeChunk(state, chunk, encoding);
+  if (Buffer.isBuffer(chunk))
+    encoding = 'buffer';
+  var len = state.objectMode ? 1 : chunk.length;
+
+  state.length += len;
+
+  var ret = state.length < state.highWaterMark;
+  // we must ensure that previous needDrain will not be reset to false.
+  if (!ret)
+    state.needDrain = true;
+
+  if (state.writing)
+    state.buffer.push(new WriteReq(chunk, encoding, cb));
+  else
+    doWrite(stream, state, len, chunk, encoding, cb);
+
+  return ret;
+}
+
+function doWrite(stream, state, len, chunk, encoding, cb) {
+  state.writelen = len;
+  state.writecb = cb;
+  state.writing = true;
+  state.sync = true;
+  stream._write(chunk, encoding, state.onwrite);
+  state.sync = false;
+}
+
+function onwriteError(stream, state, sync, er, cb) {
+  if (sync)
+    process.nextTick(function() {
+      cb(er);
+    });
+  else
+    cb(er);
+
+  stream._writableState.errorEmitted = true;
+  stream.emit('error', er);
+}
+
+function onwriteStateUpdate(state) {
+  state.writing = false;
+  state.writecb = null;
+  state.length -= state.writelen;
+  state.writelen = 0;
+}
+
+function onwrite(stream, er) {
+  var state = stream._writableState;
+  var sync = state.sync;
+  var cb = state.writecb;
+
+  onwriteStateUpdate(state);
+
+  if (er)
+    onwriteError(stream, state, sync, er, cb);
+  else {
+    // Check if we're actually ready to finish, but don't emit yet
+    var finished = needFinish(stream, state);
+
+    if (!finished && !state.bufferProcessing && state.buffer.length)
+      clearBuffer(stream, state);
+
+    if (sync) {
+      process.nextTick(function() {
+        afterWrite(stream, state, finished, cb);
+      });
+    } else {
+      afterWrite(stream, state, finished, cb);
+    }
+  }
+}
+
+function afterWrite(stream, state, finished, cb) {
+  if (!finished)
+    onwriteDrain(stream, state);
+  cb();
+  if (finished)
+    finishMaybe(stream, state);
+}
+
+// Must force callback to be called on nextTick, so that we don't
+// emit 'drain' before the write() consumer gets the 'false' return
+// value, and has a chance to attach a 'drain' listener.
+function onwriteDrain(stream, state) {
+  if (state.length === 0 && state.needDrain) {
+    state.needDrain = false;
+    stream.emit('drain');
+  }
+}
+
+
+// if there's something in the buffer waiting, then process it
+function clearBuffer(stream, state) {
+  state.bufferProcessing = true;
+
+  for (var c = 0; c < state.buffer.length; c++) {
+    var entry = state.buffer[c];
+    var chunk = entry.chunk;
+    var encoding = entry.encoding;
+    var cb = entry.callback;
+    var len = state.objectMode ? 1 : chunk.length;
+
+    doWrite(stream, state, len, chunk, encoding, cb);
+
+    // if we didn't call the onwrite immediately, then
+    // it means that we need to wait until it does.
+    // also, that means that the chunk and cb are currently
+    // being processed, so move the buffer counter past them.
+    if (state.writing) {
+      c++;
+      break;
+    }
+  }
+
+  state.bufferProcessing = false;
+  if (c < state.buffer.length)
+    state.buffer = state.buffer.slice(c);
+  else
+    state.buffer.length = 0;
+}
+
+Writable.prototype._write = function(chunk, encoding, cb) {
+  cb(new Error('not implemented'));
+};
+
+Writable.prototype.end = function(chunk, encoding, cb) {
+  var state = this._writableState;
+
+  if (typeof chunk === 'function') {
+    cb = chunk;
+    chunk = null;
+    encoding = null;
+  } else if (typeof encoding === 'function') {
+    cb = encoding;
+    encoding = null;
+  }
+
+  if (typeof chunk !== 'undefined' && chunk !== null)
+    this.write(chunk, encoding);
+
+  // ignore unnecessary end() calls.
+  if (!state.ending && !state.finished)
+    endWritable(this, state, cb);
+};
+
+
+function needFinish(stream, state) {
+  return (state.ending &&
+          state.length === 0 &&
+          !state.finished &&
+          !state.writing);
+}
+
+function finishMaybe(stream, state) {
+  var need = needFinish(stream, state);
+  if (need) {
+    state.finished = true;
+    stream.emit('finish');
+  }
+  return need;
+}
+
+function endWritable(stream, state, cb) {
+  state.ending = true;
+  finishMaybe(stream, state);
+  if (cb) {
+    if (state.finished)
+      process.nextTick(cb);
+    else
+      stream.once('finish', cb);
+  }
+  state.ended = true;
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/core-util-is/README.md b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/core-util-is/README.md
new file mode 100644
index 0000000..5a76b41
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/core-util-is/README.md
@@ -0,0 +1,3 @@
+# core-util-is
+
+The `util.is*` functions introduced in Node v0.12.
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/core-util-is/float.patch b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/core-util-is/float.patch
new file mode 100644
index 0000000..a06d5c0
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/core-util-is/float.patch
@@ -0,0 +1,604 @@
+diff --git a/lib/util.js b/lib/util.js
+index a03e874..9074e8e 100644
+--- a/lib/util.js
++++ b/lib/util.js
+@@ -19,430 +19,6 @@
+ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+ // USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+-var formatRegExp = /%[sdj%]/g;
+-exports.format = function(f) {
+-  if (!isString(f)) {
+-    var objects = [];
+-    for (var i = 0; i < arguments.length; i++) {
+-      objects.push(inspect(arguments[i]));
+-    }
+-    return objects.join(' ');
+-  }
+-
+-  var i = 1;
+-  var args = arguments;
+-  var len = args.length;
+-  var str = String(f).replace(formatRegExp, function(x) {
+-    if (x === '%%') return '%';
+-    if (i >= len) return x;
+-    switch (x) {
+-      case '%s': return String(args[i++]);
+-      case '%d': return Number(args[i++]);
+-      case '%j':
+-        try {
+-          return JSON.stringify(args[i++]);
+-        } catch (_) {
+-          return '[Circular]';
+-        }
+-      default:
+-        return x;
+-    }
+-  });
+-  for (var x = args[i]; i < len; x = args[++i]) {
+-    if (isNull(x) || !isObject(x)) {
+-      str += ' ' + x;
+-    } else {
+-      str += ' ' + inspect(x);
+-    }
+-  }
+-  return str;
+-};
+-
+-
+-// Mark that a method should not be used.
+-// Returns a modified function which warns once by default.
+-// If --no-deprecation is set, then it is a no-op.
+-exports.deprecate = function(fn, msg) {
+-  // Allow for deprecating things in the process of starting up.
+-  if (isUndefined(global.process)) {
+-    return function() {
+-      return exports.deprecate(fn, msg).apply(this, arguments);
+-    };
+-  }
+-
+-  if (process.noDeprecation === true) {
+-    return fn;
+-  }
+-
+-  var warned = false;
+-  function deprecated() {
+-    if (!warned) {
+-      if (process.throwDeprecation) {
+-        throw new Error(msg);
+-      } else if (process.traceDeprecation) {
+-        console.trace(msg);
+-      } else {
+-        console.error(msg);
+-      }
+-      warned = true;
+-    }
+-    return fn.apply(this, arguments);
+-  }
+-
+-  return deprecated;
+-};
+-
+-
+-var debugs = {};
+-var debugEnviron;
+-exports.debuglog = function(set) {
+-  if (isUndefined(debugEnviron))
+-    debugEnviron = process.env.NODE_DEBUG || '';
+-  set = set.toUpperCase();
+-  if (!debugs[set]) {
+-    if (new RegExp('\\b' + set + '\\b', 'i').test(debugEnviron)) {
+-      var pid = process.pid;
+-      debugs[set] = function() {
+-        var msg = exports.format.apply(exports, arguments);
+-        console.error('%s %d: %s', set, pid, msg);
+-      };
+-    } else {
+-      debugs[set] = function() {};
+-    }
+-  }
+-  return debugs[set];
+-};
+-
+-
+-/**
+- * Echos the value of a value. Trys to print the value out
+- * in the best way possible given the different types.
+- *
+- * @param {Object} obj The object to print out.
+- * @param {Object} opts Optional options object that alters the output.
+- */
+-/* legacy: obj, showHidden, depth, colors*/
+-function inspect(obj, opts) {
+-  // default options
+-  var ctx = {
+-    seen: [],
+-    stylize: stylizeNoColor
+-  };
+-  // legacy...
+-  if (arguments.length >= 3) ctx.depth = arguments[2];
+-  if (arguments.length >= 4) ctx.colors = arguments[3];
+-  if (isBoolean(opts)) {
+-    // legacy...
+-    ctx.showHidden = opts;
+-  } else if (opts) {
+-    // got an "options" object
+-    exports._extend(ctx, opts);
+-  }
+-  // set default options
+-  if (isUndefined(ctx.showHidden)) ctx.showHidden = false;
+-  if (isUndefined(ctx.depth)) ctx.depth = 2;
+-  if (isUndefined(ctx.colors)) ctx.colors = false;
+-  if (isUndefined(ctx.customInspect)) ctx.customInspect = true;
+-  if (ctx.colors) ctx.stylize = stylizeWithColor;
+-  return formatValue(ctx, obj, ctx.depth);
+-}
+-exports.inspect = inspect;
+-
+-
+-// http://en.wikipedia.org/wiki/ANSI_escape_code#graphics
+-inspect.colors = {
+-  'bold' : [1, 22],
+-  'italic' : [3, 23],
+-  'underline' : [4, 24],
+-  'inverse' : [7, 27],
+-  'white' : [37, 39],
+-  'grey' : [90, 39],
+-  'black' : [30, 39],
+-  'blue' : [34, 39],
+-  'cyan' : [36, 39],
+-  'green' : [32, 39],
+-  'magenta' : [35, 39],
+-  'red' : [31, 39],
+-  'yellow' : [33, 39]
+-};
+-
+-// Don't use 'blue' not visible on cmd.exe
+-inspect.styles = {
+-  'special': 'cyan',
+-  'number': 'yellow',
+-  'boolean': 'yellow',
+-  'undefined': 'grey',
+-  'null': 'bold',
+-  'string': 'green',
+-  'date': 'magenta',
+-  // "name": intentionally not styling
+-  'regexp': 'red'
+-};
+-
+-
+-function stylizeWithColor(str, styleType) {
+-  var style = inspect.styles[styleType];
+-
+-  if (style) {
+-    return '\u001b[' + inspect.colors[style][0] + 'm' + str +
+-           '\u001b[' + inspect.colors[style][1] + 'm';
+-  } else {
+-    return str;
+-  }
+-}
+-
+-
+-function stylizeNoColor(str, styleType) {
+-  return str;
+-}
+-
+-
+-function arrayToHash(array) {
+-  var hash = {};
+-
+-  array.forEach(function(val, idx) {
+-    hash[val] = true;
+-  });
+-
+-  return hash;
+-}
+-
+-
+-function formatValue(ctx, value, recurseTimes) {
+-  // Provide a hook for user-specified inspect functions.
+-  // Check that value is an object with an inspect function on it
+-  if (ctx.customInspect &&
+-      value &&
+-      isFunction(value.inspect) &&
+-      // Filter out the util module, it's inspect function is special
+-      value.inspect !== exports.inspect &&
+-      // Also filter out any prototype objects using the circular check.
+-      !(value.constructor && value.constructor.prototype === value)) {
+-    var ret = value.inspect(recurseTimes, ctx);
+-    if (!isString(ret)) {
+-      ret = formatValue(ctx, ret, recurseTimes);
+-    }
+-    return ret;
+-  }
+-
+-  // Primitive types cannot have properties
+-  var primitive = formatPrimitive(ctx, value);
+-  if (primitive) {
+-    return primitive;
+-  }
+-
+-  // Look up the keys of the object.
+-  var keys = Object.keys(value);
+-  var visibleKeys = arrayToHash(keys);
+-
+-  if (ctx.showHidden) {
+-    keys = Object.getOwnPropertyNames(value);
+-  }
+-
+-  // Some type of object without properties can be shortcutted.
+-  if (keys.length === 0) {
+-    if (isFunction(value)) {
+-      var name = value.name ? ': ' + value.name : '';
+-      return ctx.stylize('[Function' + name + ']', 'special');
+-    }
+-    if (isRegExp(value)) {
+-      return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
+-    }
+-    if (isDate(value)) {
+-      return ctx.stylize(Date.prototype.toString.call(value), 'date');
+-    }
+-    if (isError(value)) {
+-      return formatError(value);
+-    }
+-  }
+-
+-  var base = '', array = false, braces = ['{', '}'];
+-
+-  // Make Array say that they are Array
+-  if (isArray(value)) {
+-    array = true;
+-    braces = ['[', ']'];
+-  }
+-
+-  // Make functions say that they are functions
+-  if (isFunction(value)) {
+-    var n = value.name ? ': ' + value.name : '';
+-    base = ' [Function' + n + ']';
+-  }
+-
+-  // Make RegExps say that they are RegExps
+-  if (isRegExp(value)) {
+-    base = ' ' + RegExp.prototype.toString.call(value);
+-  }
+-
+-  // Make dates with properties first say the date
+-  if (isDate(value)) {
+-    base = ' ' + Date.prototype.toUTCString.call(value);
+-  }
+-
+-  // Make error with message first say the error
+-  if (isError(value)) {
+-    base = ' ' + formatError(value);
+-  }
+-
+-  if (keys.length === 0 && (!array || value.length == 0)) {
+-    return braces[0] + base + braces[1];
+-  }
+-
+-  if (recurseTimes < 0) {
+-    if (isRegExp(value)) {
+-      return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
+-    } else {
+-      return ctx.stylize('[Object]', 'special');
+-    }
+-  }
+-
+-  ctx.seen.push(value);
+-
+-  var output;
+-  if (array) {
+-    output = formatArray(ctx, value, recurseTimes, visibleKeys, keys);
+-  } else {
+-    output = keys.map(function(key) {
+-      return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array);
+-    });
+-  }
+-
+-  ctx.seen.pop();
+-
+-  return reduceToSingleString(output, base, braces);
+-}
+-
+-
+-function formatPrimitive(ctx, value) {
+-  if (isUndefined(value))
+-    return ctx.stylize('undefined', 'undefined');
+-  if (isString(value)) {
+-    var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '')
+-                                             .replace(/'/g, "\\'")
+-                                             .replace(/\\"/g, '"') + '\'';
+-    return ctx.stylize(simple, 'string');
+-  }
+-  if (isNumber(value)) {
+-    // Format -0 as '-0'. Strict equality won't distinguish 0 from -0,
+-    // so instead we use the fact that 1 / -0 < 0 whereas 1 / 0 > 0 .
+-    if (value === 0 && 1 / value < 0)
+-      return ctx.stylize('-0', 'number');
+-    return ctx.stylize('' + value, 'number');
+-  }
+-  if (isBoolean(value))
+-    return ctx.stylize('' + value, 'boolean');
+-  // For some reason typeof null is "object", so special case here.
+-  if (isNull(value))
+-    return ctx.stylize('null', 'null');
+-}
+-
+-
+-function formatError(value) {
+-  return '[' + Error.prototype.toString.call(value) + ']';
+-}
+-
+-
+-function formatArray(ctx, value, recurseTimes, visibleKeys, keys) {
+-  var output = [];
+-  for (var i = 0, l = value.length; i < l; ++i) {
+-    if (hasOwnProperty(value, String(i))) {
+-      output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
+-          String(i), true));
+-    } else {
+-      output.push('');
+-    }
+-  }
+-  keys.forEach(function(key) {
+-    if (!key.match(/^\d+$/)) {
+-      output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
+-          key, true));
+-    }
+-  });
+-  return output;
+-}
+-
+-
+-function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) {
+-  var name, str, desc;
+-  desc = Object.getOwnPropertyDescriptor(value, key) || { value: value[key] };
+-  if (desc.get) {
+-    if (desc.set) {
+-      str = ctx.stylize('[Getter/Setter]', 'special');
+-    } else {
+-      str = ctx.stylize('[Getter]', 'special');
+-    }
+-  } else {
+-    if (desc.set) {
+-      str = ctx.stylize('[Setter]', 'special');
+-    }
+-  }
+-  if (!hasOwnProperty(visibleKeys, key)) {
+-    name = '[' + key + ']';
+-  }
+-  if (!str) {
+-    if (ctx.seen.indexOf(desc.value) < 0) {
+-      if (isNull(recurseTimes)) {
+-        str = formatValue(ctx, desc.value, null);
+-      } else {
+-        str = formatValue(ctx, desc.value, recurseTimes - 1);
+-      }
+-      if (str.indexOf('\n') > -1) {
+-        if (array) {
+-          str = str.split('\n').map(function(line) {
+-            return '  ' + line;
+-          }).join('\n').substr(2);
+-        } else {
+-          str = '\n' + str.split('\n').map(function(line) {
+-            return '   ' + line;
+-          }).join('\n');
+-        }
+-      }
+-    } else {
+-      str = ctx.stylize('[Circular]', 'special');
+-    }
+-  }
+-  if (isUndefined(name)) {
+-    if (array && key.match(/^\d+$/)) {
+-      return str;
+-    }
+-    name = JSON.stringify('' + key);
+-    if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) {
+-      name = name.substr(1, name.length - 2);
+-      name = ctx.stylize(name, 'name');
+-    } else {
+-      name = name.replace(/'/g, "\\'")
+-                 .replace(/\\"/g, '"')
+-                 .replace(/(^"|"$)/g, "'");
+-      name = ctx.stylize(name, 'string');
+-    }
+-  }
+-
+-  return name + ': ' + str;
+-}
+-
+-
+-function reduceToSingleString(output, base, braces) {
+-  var numLinesEst = 0;
+-  var length = output.reduce(function(prev, cur) {
+-    numLinesEst++;
+-    if (cur.indexOf('\n') >= 0) numLinesEst++;
+-    return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1;
+-  }, 0);
+-
+-  if (length > 60) {
+-    return braces[0] +
+-           (base === '' ? '' : base + '\n ') +
+-           ' ' +
+-           output.join(',\n  ') +
+-           ' ' +
+-           braces[1];
+-  }
+-
+-  return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1];
+-}
+-
+-
+ // NOTE: These type checking functions intentionally don't use `instanceof`
+ // because it is fragile and can be easily faked with `Object.create()`.
+ function isArray(ar) {
+@@ -522,166 +98,10 @@ function isPrimitive(arg) {
+ exports.isPrimitive = isPrimitive;
+
+ function isBuffer(arg) {
+-  return arg instanceof Buffer;
++  return Buffer.isBuffer(arg);
+ }
+ exports.isBuffer = isBuffer;
+
+ function objectToString(o) {
+   return Object.prototype.toString.call(o);
+-}
+-
+-
+-function pad(n) {
+-  return n < 10 ? '0' + n.toString(10) : n.toString(10);
+-}
+-
+-
+-var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep',
+-              'Oct', 'Nov', 'Dec'];
+-
+-// 26 Feb 16:19:34
+-function timestamp() {
+-  var d = new Date();
+-  var time = [pad(d.getHours()),
+-              pad(d.getMinutes()),
+-              pad(d.getSeconds())].join(':');
+-  return [d.getDate(), months[d.getMonth()], time].join(' ');
+-}
+-
+-
+-// log is just a thin wrapper to console.log that prepends a timestamp
+-exports.log = function() {
+-  console.log('%s - %s', timestamp(), exports.format.apply(exports, arguments));
+-};
+-
+-
+-/**
+- * Inherit the prototype methods from one constructor into another.
+- *
+- * The Function.prototype.inherits from lang.js rewritten as a standalone
+- * function (not on Function.prototype). NOTE: If this file is to be loaded
+- * during bootstrapping this function needs to be rewritten using some native
+- * functions as prototype setup using normal JavaScript does not work as
+- * expected during bootstrapping (see mirror.js in r114903).
+- *
+- * @param {function} ctor Constructor function which needs to inherit the
+- *     prototype.
+- * @param {function} superCtor Constructor function to inherit prototype from.
+- */
+-exports.inherits = function(ctor, superCtor) {
+-  ctor.super_ = superCtor;
+-  ctor.prototype = Object.create(superCtor.prototype, {
+-    constructor: {
+-      value: ctor,
+-      enumerable: false,
+-      writable: true,
+-      configurable: true
+-    }
+-  });
+-};
+-
+-exports._extend = function(origin, add) {
+-  // Don't do anything if add isn't an object
+-  if (!add || !isObject(add)) return origin;
+-
+-  var keys = Object.keys(add);
+-  var i = keys.length;
+-  while (i--) {
+-    origin[keys[i]] = add[keys[i]];
+-  }
+-  return origin;
+-};
+-
+-function hasOwnProperty(obj, prop) {
+-  return Object.prototype.hasOwnProperty.call(obj, prop);
+-}
+-
+-
+-// Deprecated old stuff.
+-
+-exports.p = exports.deprecate(function() {
+-  for (var i = 0, len = arguments.length; i < len; ++i) {
+-    console.error(exports.inspect(arguments[i]));
+-  }
+-}, 'util.p: Use console.error() instead');
+-
+-
+-exports.exec = exports.deprecate(function() {
+-  return require('child_process').exec.apply(this, arguments);
+-}, 'util.exec is now called `child_process.exec`.');
+-
+-
+-exports.print = exports.deprecate(function() {
+-  for (var i = 0, len = arguments.length; i < len; ++i) {
+-    process.stdout.write(String(arguments[i]));
+-  }
+-}, 'util.print: Use console.log instead');
+-
+-
+-exports.puts = exports.deprecate(function() {
+-  for (var i = 0, len = arguments.length; i < len; ++i) {
+-    process.stdout.write(arguments[i] + '\n');
+-  }
+-}, 'util.puts: Use console.log instead');
+-
+-
+-exports.debug = exports.deprecate(function(x) {
+-  process.stderr.write('DEBUG: ' + x + '\n');
+-}, 'util.debug: Use console.error instead');
+-
+-
+-exports.error = exports.deprecate(function(x) {
+-  for (var i = 0, len = arguments.length; i < len; ++i) {
+-    process.stderr.write(arguments[i] + '\n');
+-  }
+-}, 'util.error: Use console.error instead');
+-
+-
+-exports.pump = exports.deprecate(function(readStream, writeStream, callback) {
+-  var callbackCalled = false;
+-
+-  function call(a, b, c) {
+-    if (callback && !callbackCalled) {
+-      callback(a, b, c);
+-      callbackCalled = true;
+-    }
+-  }
+-
+-  readStream.addListener('data', function(chunk) {
+-    if (writeStream.write(chunk) === false) readStream.pause();
+-  });
+-
+-  writeStream.addListener('drain', function() {
+-    readStream.resume();
+-  });
+-
+-  readStream.addListener('end', function() {
+-    writeStream.end();
+-  });
+-
+-  readStream.addListener('close', function() {
+-    call();
+-  });
+-
+-  readStream.addListener('error', function(err) {
+-    writeStream.end();
+-    call(err);
+-  });
+-
+-  writeStream.addListener('error', function(err) {
+-    readStream.destroy();
+-    call(err);
+-  });
+-}, 'util.pump(): Use readableStream.pipe() instead');
+-
+-
+-var uv;
+-exports._errnoException = function(err, syscall) {
+-  if (isUndefined(uv)) uv = process.binding('uv');
+-  var errname = uv.errname(err);
+-  var e = new Error(syscall + ' ' + errname);
+-  e.code = errname;
+-  e.errno = errname;
+-  e.syscall = syscall;
+-  return e;
+-};
++}
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/core-util-is/lib/util.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/core-util-is/lib/util.js
new file mode 100644
index 0000000..9074e8e
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/core-util-is/lib/util.js
@@ -0,0 +1,107 @@
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// NOTE: These type checking functions intentionally don't use `instanceof`
+// because it is fragile and can be easily faked with `Object.create()`.
+function isArray(ar) {
+  return Array.isArray(ar);
+}
+exports.isArray = isArray;
+
+function isBoolean(arg) {
+  return typeof arg === 'boolean';
+}
+exports.isBoolean = isBoolean;
+
+function isNull(arg) {
+  return arg === null;
+}
+exports.isNull = isNull;
+
+function isNullOrUndefined(arg) {
+  return arg == null;
+}
+exports.isNullOrUndefined = isNullOrUndefined;
+
+function isNumber(arg) {
+  return typeof arg === 'number';
+}
+exports.isNumber = isNumber;
+
+function isString(arg) {
+  return typeof arg === 'string';
+}
+exports.isString = isString;
+
+function isSymbol(arg) {
+  return typeof arg === 'symbol';
+}
+exports.isSymbol = isSymbol;
+
+function isUndefined(arg) {
+  return arg === void 0;
+}
+exports.isUndefined = isUndefined;
+
+function isRegExp(re) {
+  return isObject(re) && objectToString(re) === '[object RegExp]';
+}
+exports.isRegExp = isRegExp;
+
+function isObject(arg) {
+  return typeof arg === 'object' && arg !== null;
+}
+exports.isObject = isObject;
+
+function isDate(d) {
+  return isObject(d) && objectToString(d) === '[object Date]';
+}
+exports.isDate = isDate;
+
+function isError(e) {
+  return isObject(e) &&
+      (objectToString(e) === '[object Error]' || e instanceof Error);
+}
+exports.isError = isError;
+
+function isFunction(arg) {
+  return typeof arg === 'function';
+}
+exports.isFunction = isFunction;
+
+function isPrimitive(arg) {
+  return arg === null ||
+         typeof arg === 'boolean' ||
+         typeof arg === 'number' ||
+         typeof arg === 'string' ||
+         typeof arg === 'symbol' ||  // ES6 symbol
+         typeof arg === 'undefined';
+}
+exports.isPrimitive = isPrimitive;
+
+function isBuffer(arg) {
+  return Buffer.isBuffer(arg);
+}
+exports.isBuffer = isBuffer;
+
+function objectToString(o) {
+  return Object.prototype.toString.call(o);
+}
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/core-util-is/package.json b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/core-util-is/package.json
new file mode 100644
index 0000000..d28ffc4
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/core-util-is/package.json
@@ -0,0 +1,36 @@
+{
+  "name": "core-util-is",
+  "version": "1.0.1",
+  "description": "The `util.is*` functions introduced in Node v0.12.",
+  "main": "lib/util.js",
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/isaacs/core-util-is"
+  },
+  "keywords": [
+    "util",
+    "isBuffer",
+    "isArray",
+    "isNumber",
+    "isString",
+    "isRegExp",
+    "isThis",
+    "isThat",
+    "polyfill"
+  ],
+  "author": {
+    "name": "Isaac Z. Schlueter",
+    "email": "i@izs.me",
+    "url": "http://blog.izs.me/"
+  },
+  "license": "MIT",
+  "bugs": {
+    "url": "https://github.com/isaacs/core-util-is/issues"
+  },
+  "readme": "# core-util-is\n\nThe `util.is*` functions introduced in Node v0.12.\n",
+  "readmeFilename": "README.md",
+  "homepage": "https://github.com/isaacs/core-util-is",
+  "_id": "core-util-is@1.0.1",
+  "_from": "core-util-is@~1.0.0",
+  "scripts": {}
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/core-util-is/util.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/core-util-is/util.js
new file mode 100644
index 0000000..007fa10
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/core-util-is/util.js
@@ -0,0 +1,106 @@
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// NOTE: These type checking functions intentionally don't use `instanceof`
+// because it is fragile and can be easily faked with `Object.create()`.
+function isArray(ar) {
+  return Array.isArray(ar);
+}
+exports.isArray = isArray;
+
+function isBoolean(arg) {
+  return typeof arg === 'boolean';
+}
+exports.isBoolean = isBoolean;
+
+function isNull(arg) {
+  return arg === null;
+}
+exports.isNull = isNull;
+
+function isNullOrUndefined(arg) {
+  return arg == null;
+}
+exports.isNullOrUndefined = isNullOrUndefined;
+
+function isNumber(arg) {
+  return typeof arg === 'number';
+}
+exports.isNumber = isNumber;
+
+function isString(arg) {
+  return typeof arg === 'string';
+}
+exports.isString = isString;
+
+function isSymbol(arg) {
+  return typeof arg === 'symbol';
+}
+exports.isSymbol = isSymbol;
+
+function isUndefined(arg) {
+  return arg === void 0;
+}
+exports.isUndefined = isUndefined;
+
+function isRegExp(re) {
+  return isObject(re) && objectToString(re) === '[object RegExp]';
+}
+exports.isRegExp = isRegExp;
+
+function isObject(arg) {
+  return typeof arg === 'object' && arg !== null;
+}
+exports.isObject = isObject;
+
+function isDate(d) {
+  return isObject(d) && objectToString(d) === '[object Date]';
+}
+exports.isDate = isDate;
+
+function isError(e) {
+  return isObject(e) && objectToString(e) === '[object Error]';
+}
+exports.isError = isError;
+
+function isFunction(arg) {
+  return typeof arg === 'function';
+}
+exports.isFunction = isFunction;
+
+function isPrimitive(arg) {
+  return arg === null ||
+         typeof arg === 'boolean' ||
+         typeof arg === 'number' ||
+         typeof arg === 'string' ||
+         typeof arg === 'symbol' ||  // ES6 symbol
+         typeof arg === 'undefined';
+}
+exports.isPrimitive = isPrimitive;
+
+function isBuffer(arg) {
+  return arg instanceof Buffer;
+}
+exports.isBuffer = isBuffer;
+
+function objectToString(o) {
+  return Object.prototype.toString.call(o);
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/inherits/LICENSE b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/inherits/LICENSE
new file mode 100644
index 0000000..dea3013
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/inherits/LICENSE
@@ -0,0 +1,16 @@
+The ISC License
+
+Copyright (c) Isaac Z. Schlueter
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.
+
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/inherits/README.md b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/inherits/README.md
new file mode 100644
index 0000000..b1c5665
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/inherits/README.md
@@ -0,0 +1,42 @@
+Browser-friendly inheritance fully compatible with standard node.js
+[inherits](http://nodejs.org/api/util.html#util_util_inherits_constructor_superconstructor).
+
+This package exports standard `inherits` from node.js `util` module in
+node environment, but also provides alternative browser-friendly
+implementation through [browser
+field](https://gist.github.com/shtylman/4339901). Alternative
+implementation is a literal copy of standard one located in standalone
+module to avoid requiring of `util`. It also has a shim for old
+browsers with no `Object.create` support.
+
+While keeping you sure you are using standard `inherits`
+implementation in node.js environment, it allows bundlers such as
+[browserify](https://github.com/substack/node-browserify) to not
+include full `util` package to your client code if all you need is
+just `inherits` function. It worth, because browser shim for `util`
+package is large and `inherits` is often the single function you need
+from it.
+
+It's recommended to use this package instead of
+`require('util').inherits` for any code that has chances to be used
+not only in node.js but in browser too.
+
+## usage
+
+```js
+var inherits = require('inherits');
+// then use exactly as the standard one
+```
+
+## note on version ~1.0
+
+Version ~1.0 had completely different motivation and is not compatible
+neither with 2.0 nor with standard node.js `inherits`.
+
+If you are using version ~1.0 and planning to switch to ~2.0, be
+careful:
+
+* new version uses `super_` instead of `super` for referencing
+  superclass
+* new version overwrites current prototype while old one preserves any
+  existing fields on it
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/inherits/inherits.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/inherits/inherits.js
new file mode 100644
index 0000000..29f5e24
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/inherits/inherits.js
@@ -0,0 +1 @@
+module.exports = require('util').inherits
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/inherits/inherits_browser.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/inherits/inherits_browser.js
new file mode 100644
index 0000000..c1e78a7
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/inherits/inherits_browser.js
@@ -0,0 +1,23 @@
+if (typeof Object.create === 'function') {
+  // implementation from standard node.js 'util' module
+  module.exports = function inherits(ctor, superCtor) {
+    ctor.super_ = superCtor
+    ctor.prototype = Object.create(superCtor.prototype, {
+      constructor: {
+        value: ctor,
+        enumerable: false,
+        writable: true,
+        configurable: true
+      }
+    });
+  };
+} else {
+  // old school shim for old browsers
+  module.exports = function inherits(ctor, superCtor) {
+    ctor.super_ = superCtor
+    var TempCtor = function () {}
+    TempCtor.prototype = superCtor.prototype
+    ctor.prototype = new TempCtor()
+    ctor.prototype.constructor = ctor
+  }
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/inherits/package.json b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/inherits/package.json
new file mode 100644
index 0000000..c532ca6
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/inherits/package.json
@@ -0,0 +1,33 @@
+{
+  "name": "inherits",
+  "description": "Browser-friendly inheritance fully compatible with standard node.js inherits()",
+  "version": "2.0.1",
+  "keywords": [
+    "inheritance",
+    "class",
+    "klass",
+    "oop",
+    "object-oriented",
+    "inherits",
+    "browser",
+    "browserify"
+  ],
+  "main": "./inherits.js",
+  "browser": "./inherits_browser.js",
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/isaacs/inherits"
+  },
+  "license": "ISC",
+  "scripts": {
+    "test": "node test"
+  },
+  "readme": "Browser-friendly inheritance fully compatible with standard node.js\n[inherits](http://nodejs.org/api/util.html#util_util_inherits_constructor_superconstructor).\n\nThis package exports standard `inherits` from node.js `util` module in\nnode environment, but also provides alternative browser-friendly\nimplementation through [browser\nfield](https://gist.github.com/shtylman/4339901). Alternative\nimplementation is a literal copy of standard one located in standalone\nmodule to avoid requiring of `util`. It also has a shim for old\nbrowsers with no `Object.create` support.\n\nWhile keeping you sure you are using standard `inherits`\nimplementation in node.js environment, it allows bundlers such as\n[browserify](https://github.com/substack/node-browserify) to not\ninclude full `util` package to your client code if all you need is\njust `inherits` function. It worth, because browser shim for `util`\npackage is large and `inherits` is often the single function you need\nfrom it.\n\nIt's recommended to use this package instead of\n`require('util').inherits` for any code that has chances to be used\nnot only in node.js but in browser too.\n\n## usage\n\n```js\nvar inherits = require('inherits');\n// then use exactly as the standard one\n```\n\n## note on version ~1.0\n\nVersion ~1.0 had completely different motivation and is not compatible\nneither with 2.0 nor with standard node.js `inherits`.\n\nIf you are using version ~1.0 and planning to switch to ~2.0, be\ncareful:\n\n* new version uses `super_` instead of `super` for referencing\n  superclass\n* new version overwrites current prototype while old one preserves any\n  existing fields on it\n",
+  "readmeFilename": "README.md",
+  "bugs": {
+    "url": "https://github.com/isaacs/inherits/issues"
+  },
+  "homepage": "https://github.com/isaacs/inherits",
+  "_id": "inherits@2.0.1",
+  "_from": "inherits@~2.0.1"
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/inherits/test.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/inherits/test.js
new file mode 100644
index 0000000..fc53012
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/inherits/test.js
@@ -0,0 +1,25 @@
+var inherits = require('./inherits.js')
+var assert = require('assert')
+
+function test(c) {
+  assert(c.constructor === Child)
+  assert(c.constructor.super_ === Parent)
+  assert(Object.getPrototypeOf(c) === Child.prototype)
+  assert(Object.getPrototypeOf(Object.getPrototypeOf(c)) === Parent.prototype)
+  assert(c instanceof Child)
+  assert(c instanceof Parent)
+}
+
+function Child() {
+  Parent.call(this)
+  test(this)
+}
+
+function Parent() {}
+
+inherits(Child, Parent)
+
+var c = new Child
+test(c)
+
+console.log('ok')
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/isarray/README.md b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/isarray/README.md
new file mode 100644
index 0000000..052a62b
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/isarray/README.md
@@ -0,0 +1,54 @@
+
+# isarray
+
+`Array#isArray` for older browsers.
+
+## Usage
+
+```js
+var isArray = require('isarray');
+
+console.log(isArray([])); // => true
+console.log(isArray({})); // => false
+```
+
+## Installation
+
+With [npm](http://npmjs.org) do
+
+```bash
+$ npm install isarray
+```
+
+Then bundle for the browser with
+[browserify](https://github.com/substack/browserify).
+
+With [component](http://component.io) do
+
+```bash
+$ component install juliangruber/isarray
+```
+
+## License
+
+(MIT)
+
+Copyright (c) 2013 Julian Gruber &lt;julian@juliangruber.com&gt;
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/isarray/build/build.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/isarray/build/build.js
new file mode 100644
index 0000000..ec58596
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/isarray/build/build.js
@@ -0,0 +1,209 @@
+
+/**
+ * Require the given path.
+ *
+ * @param {String} path
+ * @return {Object} exports
+ * @api public
+ */
+
+function require(path, parent, orig) {
+  var resolved = require.resolve(path);
+
+  // lookup failed
+  if (null == resolved) {
+    orig = orig || path;
+    parent = parent || 'root';
+    var err = new Error('Failed to require "' + orig + '" from "' + parent + '"');
+    err.path = orig;
+    err.parent = parent;
+    err.require = true;
+    throw err;
+  }
+
+  var module = require.modules[resolved];
+
+  // perform real require()
+  // by invoking the module's
+  // registered function
+  if (!module.exports) {
+    module.exports = {};
+    module.client = module.component = true;
+    module.call(this, module.exports, require.relative(resolved), module);
+  }
+
+  return module.exports;
+}
+
+/**
+ * Registered modules.
+ */
+
+require.modules = {};
+
+/**
+ * Registered aliases.
+ */
+
+require.aliases = {};
+
+/**
+ * Resolve `path`.
+ *
+ * Lookup:
+ *
+ *   - PATH/index.js
+ *   - PATH.js
+ *   - PATH
+ *
+ * @param {String} path
+ * @return {String} path or null
+ * @api private
+ */
+
+require.resolve = function(path) {
+  if (path.charAt(0) === '/') path = path.slice(1);
+  var index = path + '/index.js';
+
+  var paths = [
+    path,
+    path + '.js',
+    path + '.json',
+    path + '/index.js',
+    path + '/index.json'
+  ];
+
+  for (var i = 0; i < paths.length; i++) {
+    var path = paths[i];
+    if (require.modules.hasOwnProperty(path)) return path;
+  }
+
+  if (require.aliases.hasOwnProperty(index)) {
+    return require.aliases[index];
+  }
+};
+
+/**
+ * Normalize `path` relative to the current path.
+ *
+ * @param {String} curr
+ * @param {String} path
+ * @return {String}
+ * @api private
+ */
+
+require.normalize = function(curr, path) {
+  var segs = [];
+
+  if ('.' != path.charAt(0)) return path;
+
+  curr = curr.split('/');
+  path = path.split('/');
+
+  for (var i = 0; i < path.length; ++i) {
+    if ('..' == path[i]) {
+      curr.pop();
+    } else if ('.' != path[i] && '' != path[i]) {
+      segs.push(path[i]);
+    }
+  }
+
+  return curr.concat(segs).join('/');
+};
+
+/**
+ * Register module at `path` with callback `definition`.
+ *
+ * @param {String} path
+ * @param {Function} definition
+ * @api private
+ */
+
+require.register = function(path, definition) {
+  require.modules[path] = definition;
+};
+
+/**
+ * Alias a module definition.
+ *
+ * @param {String} from
+ * @param {String} to
+ * @api private
+ */
+
+require.alias = function(from, to) {
+  if (!require.modules.hasOwnProperty(from)) {
+    throw new Error('Failed to alias "' + from + '", it does not exist');
+  }
+  require.aliases[to] = from;
+};
+
+/**
+ * Return a require function relative to the `parent` path.
+ *
+ * @param {String} parent
+ * @return {Function}
+ * @api private
+ */
+
+require.relative = function(parent) {
+  var p = require.normalize(parent, '..');
+
+  /**
+   * lastIndexOf helper.
+   */
+
+  function lastIndexOf(arr, obj) {
+    var i = arr.length;
+    while (i--) {
+      if (arr[i] === obj) return i;
+    }
+    return -1;
+  }
+
+  /**
+   * The relative require() itself.
+   */
+
+  function localRequire(path) {
+    var resolved = localRequire.resolve(path);
+    return require(resolved, parent, path);
+  }
+
+  /**
+   * Resolve relative to the parent.
+   */
+
+  localRequire.resolve = function(path) {
+    var c = path.charAt(0);
+    if ('/' == c) return path.slice(1);
+    if ('.' == c) return require.normalize(p, path);
+
+    // resolve deps by returning
+    // the dep in the nearest "deps"
+    // directory
+    var segs = parent.split('/');
+    var i = lastIndexOf(segs, 'deps') + 1;
+    if (!i) i = 0;
+    path = segs.slice(0, i + 1).join('/') + '/deps/' + path;
+    return path;
+  };
+
+  /**
+   * Check if module is defined at `path`.
+   */
+
+  localRequire.exists = function(path) {
+    return require.modules.hasOwnProperty(localRequire.resolve(path));
+  };
+
+  return localRequire;
+};
+require.register("isarray/index.js", function(exports, require, module){
+module.exports = Array.isArray || function (arr) {
+  return Object.prototype.toString.call(arr) == '[object Array]';
+};
+
+});
+require.alias("isarray/index.js", "isarray/index.js");
+
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/isarray/component.json b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/isarray/component.json
new file mode 100644
index 0000000..9e31b68
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/isarray/component.json
@@ -0,0 +1,19 @@
+{
+  "name" : "isarray",
+  "description" : "Array#isArray for older browsers",
+  "version" : "0.0.1",
+  "repository" : "juliangruber/isarray",
+  "homepage": "https://github.com/juliangruber/isarray",
+  "main" : "index.js",
+  "scripts" : [
+    "index.js"
+  ],
+  "dependencies" : {},
+  "keywords": ["browser","isarray","array"],
+  "author": {
+    "name": "Julian Gruber",
+    "email": "mail@juliangruber.com",
+    "url": "http://juliangruber.com"
+  },
+  "license": "MIT"
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/isarray/index.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/isarray/index.js
new file mode 100644
index 0000000..5f5ad45
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/isarray/index.js
@@ -0,0 +1,3 @@
+module.exports = Array.isArray || function (arr) {
+  return Object.prototype.toString.call(arr) == '[object Array]';
+};
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/isarray/package.json b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/isarray/package.json
new file mode 100644
index 0000000..25c8581
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/isarray/package.json
@@ -0,0 +1,54 @@
+{
+  "name": "isarray",
+  "description": "Array#isArray for older browsers",
+  "version": "0.0.1",
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/juliangruber/isarray.git"
+  },
+  "homepage": "https://github.com/juliangruber/isarray",
+  "main": "index.js",
+  "scripts": {
+    "test": "tap test/*.js"
+  },
+  "dependencies": {},
+  "devDependencies": {
+    "tap": "*"
+  },
+  "keywords": [
+    "browser",
+    "isarray",
+    "array"
+  ],
+  "author": {
+    "name": "Julian Gruber",
+    "email": "mail@juliangruber.com",
+    "url": "http://juliangruber.com"
+  },
+  "license": "MIT",
+  "_id": "isarray@0.0.1",
+  "dist": {
+    "shasum": "8a18acfca9a8f4177e09abfc6038939b05d1eedf",
+    "tarball": "http://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
+  },
+  "_from": "isarray@0.0.1",
+  "_npmVersion": "1.2.18",
+  "_npmUser": {
+    "name": "juliangruber",
+    "email": "julian@juliangruber.com"
+  },
+  "maintainers": [
+    {
+      "name": "juliangruber",
+      "email": "julian@juliangruber.com"
+    }
+  ],
+  "directories": {},
+  "_shasum": "8a18acfca9a8f4177e09abfc6038939b05d1eedf",
+  "_resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+  "readme": "\n# isarray\n\n`Array#isArray` for older browsers.\n\n## Usage\n\n```js\nvar isArray = require('isarray');\n\nconsole.log(isArray([])); // => true\nconsole.log(isArray({})); // => false\n```\n\n## Installation\n\nWith [npm](http://npmjs.org) do\n\n```bash\n$ npm install isarray\n```\n\nThen bundle for the browser with\n[browserify](https://github.com/substack/browserify).\n\nWith [component](http://component.io) do\n\n```bash\n$ component install juliangruber/isarray\n```\n\n## License\n\n(MIT)\n\nCopyright (c) 2013 Julian Gruber &lt;julian@juliangruber.com&gt;\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies\nof the Software, and to permit persons to whom the Software is furnished to do\nso, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n",
+  "readmeFilename": "README.md",
+  "bugs": {
+    "url": "https://github.com/juliangruber/isarray/issues"
+  }
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/string_decoder/.npmignore b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/string_decoder/.npmignore
new file mode 100644
index 0000000..206320c
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/string_decoder/.npmignore
@@ -0,0 +1,2 @@
+build
+test
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/string_decoder/LICENSE b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/string_decoder/LICENSE
new file mode 100644
index 0000000..6de584a
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/string_decoder/LICENSE
@@ -0,0 +1,20 @@
+Copyright Joyent, Inc. and other Node contributors.
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to permit
+persons to whom the Software is furnished to do so, subject to the
+following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/string_decoder/README.md b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/string_decoder/README.md
new file mode 100644
index 0000000..4d2aa00
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/string_decoder/README.md
@@ -0,0 +1,7 @@
+**string_decoder.js** (`require('string_decoder')`) from Node.js core
+
+Copyright Joyent, Inc. and other Node contributors. See LICENCE file for details.
+
+Version numbers match the versions found in Node core, e.g. 0.10.24 matches Node 0.10.24, likewise 0.11.10 matches Node 0.11.10. **Prefer the stable version over the unstable.**
+
+The *build/* directory contains a build script that will scrape the source from the [joyent/node](https://github.com/joyent/node) repo given a specific Node version.
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/string_decoder/index.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/string_decoder/index.js
new file mode 100644
index 0000000..b00e54f
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/string_decoder/index.js
@@ -0,0 +1,221 @@
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+var Buffer = require('buffer').Buffer;
+
+var isBufferEncoding = Buffer.isEncoding
+  || function(encoding) {
+       switch (encoding && encoding.toLowerCase()) {
+         case 'hex': case 'utf8': case 'utf-8': case 'ascii': case 'binary': case 'base64': case 'ucs2': case 'ucs-2': case 'utf16le': case 'utf-16le': case 'raw': return true;
+         default: return false;
+       }
+     }
+
+
+function assertEncoding(encoding) {
+  if (encoding && !isBufferEncoding(encoding)) {
+    throw new Error('Unknown encoding: ' + encoding);
+  }
+}
+
+// StringDecoder provides an interface for efficiently splitting a series of
+// buffers into a series of JS strings without breaking apart multi-byte
+// characters. CESU-8 is handled as part of the UTF-8 encoding.
+//
+// @TODO Handling all encodings inside a single object makes it very difficult
+// to reason about this code, so it should be split up in the future.
+// @TODO There should be a utf8-strict encoding that rejects invalid UTF-8 code
+// points as used by CESU-8.
+var StringDecoder = exports.StringDecoder = function(encoding) {
+  this.encoding = (encoding || 'utf8').toLowerCase().replace(/[-_]/, '');
+  assertEncoding(encoding);
+  switch (this.encoding) {
+    case 'utf8':
+      // CESU-8 represents each of Surrogate Pair by 3-bytes
+      this.surrogateSize = 3;
+      break;
+    case 'ucs2':
+    case 'utf16le':
+      // UTF-16 represents each of Surrogate Pair by 2-bytes
+      this.surrogateSize = 2;
+      this.detectIncompleteChar = utf16DetectIncompleteChar;
+      break;
+    case 'base64':
+      // Base-64 stores 3 bytes in 4 chars, and pads the remainder.
+      this.surrogateSize = 3;
+      this.detectIncompleteChar = base64DetectIncompleteChar;
+      break;
+    default:
+      this.write = passThroughWrite;
+      return;
+  }
+
+  // Enough space to store all bytes of a single character. UTF-8 needs 4
+  // bytes, but CESU-8 may require up to 6 (3 bytes per surrogate).
+  this.charBuffer = new Buffer(6);
+  // Number of bytes received for the current incomplete multi-byte character.
+  this.charReceived = 0;
+  // Number of bytes expected for the current incomplete multi-byte character.
+  this.charLength = 0;
+};
+
+
+// write decodes the given buffer and returns it as JS string that is
+// guaranteed to not contain any partial multi-byte characters. Any partial
+// character found at the end of the buffer is buffered up, and will be
+// returned when calling write again with the remaining bytes.
+//
+// Note: Converting a Buffer containing an orphan surrogate to a String
+// currently works, but converting a String to a Buffer (via `new Buffer`, or
+// Buffer#write) will replace incomplete surrogates with the unicode
+// replacement character. See https://codereview.chromium.org/121173009/ .
+StringDecoder.prototype.write = function(buffer) {
+  var charStr = '';
+  // if our last write ended with an incomplete multibyte character
+  while (this.charLength) {
+    // determine how many remaining bytes this buffer has to offer for this char
+    var available = (buffer.length >= this.charLength - this.charReceived) ?
+        this.charLength - this.charReceived :
+        buffer.length;
+
+    // add the new bytes to the char buffer
+    buffer.copy(this.charBuffer, this.charReceived, 0, available);
+    this.charReceived += available;
+
+    if (this.charReceived < this.charLength) {
+      // still not enough chars in this buffer? wait for more ...
+      return '';
+    }
+
+    // remove bytes belonging to the current character from the buffer
+    buffer = buffer.slice(available, buffer.length);
+
+    // get the character that was split
+    charStr = this.charBuffer.slice(0, this.charLength).toString(this.encoding);
+
+    // CESU-8: lead surrogate (D800-DBFF) is also the incomplete character
+    var charCode = charStr.charCodeAt(charStr.length - 1);
+    if (charCode >= 0xD800 && charCode <= 0xDBFF) {
+      this.charLength += this.surrogateSize;
+      charStr = '';
+      continue;
+    }
+    this.charReceived = this.charLength = 0;
+
+    // if there are no more bytes in this buffer, just emit our char
+    if (buffer.length === 0) {
+      return charStr;
+    }
+    break;
+  }
+
+  // determine and set charLength / charReceived
+  this.detectIncompleteChar(buffer);
+
+  var end = buffer.length;
+  if (this.charLength) {
+    // buffer the incomplete character bytes we got
+    buffer.copy(this.charBuffer, 0, buffer.length - this.charReceived, end);
+    end -= this.charReceived;
+  }
+
+  charStr += buffer.toString(this.encoding, 0, end);
+
+  var end = charStr.length - 1;
+  var charCode = charStr.charCodeAt(end);
+  // CESU-8: lead surrogate (D800-DBFF) is also the incomplete character
+  if (charCode >= 0xD800 && charCode <= 0xDBFF) {
+    var size = this.surrogateSize;
+    this.charLength += size;
+    this.charReceived += size;
+    this.charBuffer.copy(this.charBuffer, size, 0, size);
+    buffer.copy(this.charBuffer, 0, 0, size);
+    return charStr.substring(0, end);
+  }
+
+  // or just emit the charStr
+  return charStr;
+};
+
+// detectIncompleteChar determines if there is an incomplete UTF-8 character at
+// the end of the given buffer. If so, it sets this.charLength to the byte
+// length that character, and sets this.charReceived to the number of bytes
+// that are available for this character.
+StringDecoder.prototype.detectIncompleteChar = function(buffer) {
+  // determine how many bytes we have to check at the end of this buffer
+  var i = (buffer.length >= 3) ? 3 : buffer.length;
+
+  // Figure out if one of the last i bytes of our buffer announces an
+  // incomplete char.
+  for (; i > 0; i--) {
+    var c = buffer[buffer.length - i];
+
+    // See http://en.wikipedia.org/wiki/UTF-8#Description
+
+    // 110XXXXX
+    if (i == 1 && c >> 5 == 0x06) {
+      this.charLength = 2;
+      break;
+    }
+
+    // 1110XXXX
+    if (i <= 2 && c >> 4 == 0x0E) {
+      this.charLength = 3;
+      break;
+    }
+
+    // 11110XXX
+    if (i <= 3 && c >> 3 == 0x1E) {
+      this.charLength = 4;
+      break;
+    }
+  }
+  this.charReceived = i;
+};
+
+StringDecoder.prototype.end = function(buffer) {
+  var res = '';
+  if (buffer && buffer.length)
+    res = this.write(buffer);
+
+  if (this.charReceived) {
+    var cr = this.charReceived;
+    var buf = this.charBuffer;
+    var enc = this.encoding;
+    res += buf.slice(0, cr).toString(enc);
+  }
+
+  return res;
+};
+
+function passThroughWrite(buffer) {
+  return buffer.toString(this.encoding);
+}
+
+function utf16DetectIncompleteChar(buffer) {
+  this.charReceived = buffer.length % 2;
+  this.charLength = this.charReceived ? 2 : 0;
+}
+
+function base64DetectIncompleteChar(buffer) {
+  this.charReceived = buffer.length % 3;
+  this.charLength = this.charReceived ? 3 : 0;
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/string_decoder/package.json b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/string_decoder/package.json
new file mode 100644
index 0000000..a8c586b
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/node_modules/string_decoder/package.json
@@ -0,0 +1,54 @@
+{
+  "name": "string_decoder",
+  "version": "0.10.31",
+  "description": "The string_decoder module from Node core",
+  "main": "index.js",
+  "dependencies": {},
+  "devDependencies": {
+    "tap": "~0.4.8"
+  },
+  "scripts": {
+    "test": "tap test/simple/*.js"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/rvagg/string_decoder.git"
+  },
+  "homepage": "https://github.com/rvagg/string_decoder",
+  "keywords": [
+    "string",
+    "decoder",
+    "browser",
+    "browserify"
+  ],
+  "license": "MIT",
+  "gitHead": "d46d4fd87cf1d06e031c23f1ba170ca7d4ade9a0",
+  "bugs": {
+    "url": "https://github.com/rvagg/string_decoder/issues"
+  },
+  "_id": "string_decoder@0.10.31",
+  "_shasum": "62e203bc41766c6c28c9fc84301dab1c5310fa94",
+  "_from": "string_decoder@~0.10.x",
+  "_npmVersion": "1.4.23",
+  "_npmUser": {
+    "name": "rvagg",
+    "email": "rod@vagg.org"
+  },
+  "maintainers": [
+    {
+      "name": "substack",
+      "email": "mail@substack.net"
+    },
+    {
+      "name": "rvagg",
+      "email": "rod@vagg.org"
+    }
+  ],
+  "dist": {
+    "shasum": "62e203bc41766c6c28c9fc84301dab1c5310fa94",
+    "tarball": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
+  },
+  "directories": {},
+  "_resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/package.json b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/package.json
new file mode 100644
index 0000000..d9251ca
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/package.json
@@ -0,0 +1,70 @@
+{
+  "name": "readable-stream",
+  "version": "1.0.33",
+  "description": "Streams2, a user-land copy of the stream library from Node.js v0.10.x",
+  "main": "readable.js",
+  "dependencies": {
+    "core-util-is": "~1.0.0",
+    "isarray": "0.0.1",
+    "string_decoder": "~0.10.x",
+    "inherits": "~2.0.1"
+  },
+  "devDependencies": {
+    "tap": "~0.2.6"
+  },
+  "scripts": {
+    "test": "tap test/simple/*.js"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/isaacs/readable-stream"
+  },
+  "keywords": [
+    "readable",
+    "stream",
+    "pipe"
+  ],
+  "browser": {
+    "util": false
+  },
+  "author": {
+    "name": "Isaac Z. Schlueter",
+    "email": "i@izs.me",
+    "url": "http://blog.izs.me/"
+  },
+  "license": "MIT",
+  "gitHead": "0bf97a117c5646556548966409ebc57a6dda2638",
+  "bugs": {
+    "url": "https://github.com/isaacs/readable-stream/issues"
+  },
+  "homepage": "https://github.com/isaacs/readable-stream",
+  "_id": "readable-stream@1.0.33",
+  "_shasum": "3a360dd66c1b1d7fd4705389860eda1d0f61126c",
+  "_from": "readable-stream@~1.0.26",
+  "_npmVersion": "1.4.28",
+  "_npmUser": {
+    "name": "rvagg",
+    "email": "rod@vagg.org"
+  },
+  "maintainers": [
+    {
+      "name": "isaacs",
+      "email": "i@izs.me"
+    },
+    {
+      "name": "tootallnate",
+      "email": "nathan@tootallnate.net"
+    },
+    {
+      "name": "rvagg",
+      "email": "rod@vagg.org"
+    }
+  ],
+  "dist": {
+    "shasum": "3a360dd66c1b1d7fd4705389860eda1d0f61126c",
+    "tarball": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.33.tgz"
+  },
+  "directories": {},
+  "_resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.33.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/passthrough.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/passthrough.js
new file mode 100644
index 0000000..27e8d8a
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/passthrough.js
@@ -0,0 +1 @@
+module.exports = require("./lib/_stream_passthrough.js")
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/readable.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/readable.js
new file mode 100644
index 0000000..8b5337b
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/readable.js
@@ -0,0 +1,8 @@
+var Stream = require('stream'); // hack to fix a circular dependency issue when used with browserify
+exports = module.exports = require('./lib/_stream_readable.js');
+exports.Stream = Stream;
+exports.Readable = exports;
+exports.Writable = require('./lib/_stream_writable.js');
+exports.Duplex = require('./lib/_stream_duplex.js');
+exports.Transform = require('./lib/_stream_transform.js');
+exports.PassThrough = require('./lib/_stream_passthrough.js');
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/transform.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/transform.js
new file mode 100644
index 0000000..5d482f0
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/transform.js
@@ -0,0 +1 @@
+module.exports = require("./lib/_stream_transform.js")
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/writable.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/writable.js
new file mode 100644
index 0000000..e1e9efd
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/readable-stream/writable.js
@@ -0,0 +1 @@
+module.exports = require("./lib/_stream_writable.js")
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/.npmignore b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/.npmignore
new file mode 100644
index 0000000..7938f33
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/.npmignore
@@ -0,0 +1,2 @@
+node_modules
+sandbox.js
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/.travis.yml b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/.travis.yml
new file mode 100644
index 0000000..9672e12
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/.travis.yml
@@ -0,0 +1,6 @@
+language: node_js
+node_js:
+  - "0.8"
+  - "0.10"
+before_install:
+  - npm install -g npm@~1.4.6
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/LICENSE b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/LICENSE
new file mode 100644
index 0000000..757562e
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Mathias Buus
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/README.md b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/README.md
new file mode 100644
index 0000000..39594a3
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/README.md
@@ -0,0 +1,128 @@
+# tar-stream
+
+tar-stream is a streaming tar parser and generator and nothing else. It is streams2 and operates purely using streams which means you can easily extract/parse tarballs without ever hitting the file system.
+
+```
+npm install tar-stream
+```
+
+[![build status](https://secure.travis-ci.org/mafintosh/tar-stream.png)](http://travis-ci.org/mafintosh/tar-stream)
+
+## Usage
+
+tar-stream exposes two streams, [pack](https://github.com/mafintosh/tar-stream#packing) which creates tarballs and [extract](https://github.com/mafintosh/tar-stream#extracting) which extracts tarballs. To [modify an existing tarball](https://github.com/mafintosh/tar-stream#modifying-existing-tarballs) use both.
+
+
+It implementes USTAR with additional support for pax extended headers. It should be compatible with all popular tar distributions out there (gnutar, bsdtar etc)
+
+## Related
+
+If you want to pack/unpack directories on the file system check out [tar-fs](https://github.com/mafintosh/tar-fs) which provides file system bindings to this module.
+
+## Packing
+
+To create a pack stream use `tar.pack()` and call `pack.entry(header, [callback])` to add tar entries.
+
+``` js
+var tar = require('tar-stream')
+var pack = tar.pack() // p is a streams2 stream
+
+// add a file called my-test.txt with the content "Hello World!"
+pack.entry({ name: 'my-test.txt' }, 'Hello World!')
+
+// add a file called my-stream-test.txt from a stream
+var entry = pack.entry({ name: 'my-stream-test.txt' }, function(err) {
+  // the stream was added
+  // no more entries
+  pack.finalize()
+})
+myStream.pipe(entry)
+
+// pipe the pack stream somewhere
+pack.pipe(process.stdout)
+```
+
+## Extracting
+
+To extract a stream use `tar.extract()` and listen for `extract.on('entry', header, stream, callback)`
+
+``` js
+var extract = tar.extract()
+
+extract.on('entry', function(header, stream, callback) {
+  // header is the tar header
+  // stream is the content body (might be an empty stream)
+  // call next when you are done with this entry
+
+  stream.resume() // just auto drain the stream
+  stream.on('end', function() {
+    callback() // ready for next entry
+  })
+})
+
+extract.on('finish', function() {
+  // all entries read
+})
+
+pack.pipe(extract)
+```
+
+## Headers
+
+The header object using in `entry` should contain the following properties.
+Most of these values can be found by stat'ing a file.
+
+``` js
+{
+  name: 'path/to/this/entry.txt',
+  size: 1314,        // entry size. defaults to 0
+  mode: 0644,        // entry mode. defaults to to 0755 for dirs and 0644 otherwise
+  mtime: new Date(), // last modified date for entry. defaults to now.
+  type: 'file',      // type of entry. defaults to file. can be:
+                     // file | link | symlink | directory | block-device
+                     // character-device | fifo | contigious-file
+  linkname: 'path',  // linked file name
+  uid: 0,            // uid of entry owner. defaults to 0
+  gid: 0,            // gid of entry owner. defaults to 0
+  uname: 'maf',      // uname of entry owner. defaults to null
+  gname: 'staff',    // gname of entry owner. defaults to null
+  devmajor: 0,       // device major version. defaults to 0
+  devminor: 0        // device minor version. defaults to 0
+}
+```
+
+## Modifying existing tarballs
+
+Using tar-stream it is easy to rewrite paths / change modes etc in an existing tarball.
+
+``` js
+var extract = tar.extract()
+var pack = tar.pack()
+var path = require('path')
+
+extract.on('entry', function(header, stream, callback) {
+  // let's prefix all names with 'tmp'
+  header.name = path.join('tmp', header.name)
+  // write the new entry to the pack stream
+  stream.pipe(pack.entry(header, callback))
+})
+
+extract.on('finish', function() {
+  // all entries done - lets finalize it
+  pack.finalize()
+})
+
+// pipe the old tarball to the extractor
+oldTarball.pipe(extract)
+
+// pipe the new tarball the another stream
+pack.pipe(newTarball)
+```
+
+## Performance
+
+[See tar-fs for a performance comparison with node-tar](https://github.com/mafintosh/tar-fs/blob/master/README.md#performance)
+
+# License
+
+MIT
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/extract.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/extract.js
new file mode 100644
index 0000000..994f1f8
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/extract.js
@@ -0,0 +1,194 @@
+var util = require('util')
+var bl = require('bl')
+var xtend = require('xtend')
+var headers = require('./headers')
+
+var Writable = require('readable-stream').Writable
+var PassThrough = require('readable-stream').PassThrough
+
+var noop = function() {}
+
+var overflow = function(size) {
+  size &= 511
+  return size && 512 - size
+}
+
+var emptyStream = function() {
+  var s = new PassThrough()
+  s.end()
+  return s
+}
+
+var mixinPax = function(header, pax) {
+  if (pax.path) header.name = pax.path
+  if (pax.linkpath) header.linkname = pax.linkpath
+  return header
+}
+
+var Extract = function(opts) {
+  if (!(this instanceof Extract)) return new Extract(opts)
+  Writable.call(this, opts)
+
+  this._buffer = bl()
+  this._missing = 0
+  this._onparse = noop
+  this._header = null
+  this._stream = null
+  this._overflow = null
+  this._cb = null
+  this._locked = false
+  this._destroyed = false
+  this._pax = null
+  this._paxGlobal = null
+
+  var self = this
+  var b = self._buffer
+
+  var oncontinue = function() {
+    self._continue()
+  }
+
+  var onunlock = function(err) {
+    self._locked = false
+    if (err) return self.destroy(err)
+    if (!self._stream) oncontinue()
+  }
+
+  var onstreamend = function() {
+    self._stream = null
+    var drain = overflow(self._header.size)
+    if (drain) self._parse(drain, ondrain)
+    else self._parse(512, onheader)
+    if (!self._locked) oncontinue()
+  }
+
+  var ondrain = function() {
+    self._buffer.consume(overflow(self._header.size))
+    self._parse(512, onheader)
+    oncontinue()
+  }
+
+  var onpaxglobalheader = function() {
+    var size = self._header.size
+    self._paxGlobal = headers.decodePax(b.slice(0, size))
+    b.consume(size)
+    onstreamend()
+  }
+
+  var onpaxheader = function() {
+    var size = self._header.size
+    self._pax = headers.decodePax(b.slice(0, size))
+    if (self._paxGlobal) self._pax = xtend(self._paxGlobal, self._pax)
+    b.consume(size)
+    onstreamend()
+  }
+
+  var onheader = function() {
+    var header
+    try {
+      header = self._header = headers.decode(b.slice(0, 512))
+    } catch (err) {
+      self.emit('error', err)
+    }
+    b.consume(512)
+
+    if (!header) {
+      self._parse(512, onheader)
+      oncontinue()
+      return
+    }
+    if (header.type === 'pax-global-header') {
+      self._parse(header.size, onpaxglobalheader)
+      oncontinue()
+      return
+    }
+    if (header.type === 'pax-header') {
+      self._parse(header.size, onpaxheader)
+      oncontinue()
+      return
+    }
+
+    if (self._pax) {
+      self._header = header = mixinPax(header, self._pax)
+      self._pax = null
+    }
+
+    self._locked = true
+
+    if (!header.size) {
+      self._parse(512, onheader)
+      self.emit('entry', header, emptyStream(), onunlock)
+      return
+    }
+
+    self._stream = new PassThrough()
+
+    self.emit('entry', header, self._stream, onunlock)
+    self._parse(header.size, onstreamend)
+    oncontinue()
+  }
+
+  this._parse(512, onheader)
+}
+
+util.inherits(Extract, Writable)
+
+Extract.prototype.destroy = function(err) {
+  if (this._destroyed) return
+  this._destroyed = true
+
+  if (err) this.emit('error', err)
+  this.emit('close')
+  if (this._stream) this._stream.emit('close')
+}
+
+Extract.prototype._parse = function(size, onparse) {
+  if (this._destroyed) return
+  this._missing = size
+  this._onparse = onparse
+}
+
+Extract.prototype._continue = function(err) {
+  if (this._destroyed) return
+  var cb = this._cb
+  this._cb = noop
+  if (this._overflow) this._write(this._overflow, undefined, cb)
+  else cb()
+}
+
+Extract.prototype._write = function(data, enc, cb) {
+  if (this._destroyed) return
+
+  var s = this._stream
+  var b = this._buffer
+  var missing = this._missing
+
+  // we do not reach end-of-chunk now. just forward it
+
+  if (data.length < missing) {
+    this._missing -= data.length
+    this._overflow = null
+    if (s) return s.write(data, cb)
+    b.append(data)
+    return cb()
+  }
+
+  // end-of-chunk. the parser should call cb.
+
+  this._cb = cb
+  this._missing = 0
+
+  var overflow = null
+  if (data.length > missing) {
+    overflow = data.slice(missing)
+    data = data.slice(0, missing)
+  }
+
+  if (s) s.end(data)
+  else b.append(data)
+
+  this._overflow = overflow
+  this._onparse()
+}
+
+module.exports = Extract
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/headers.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/headers.js
new file mode 100644
index 0000000..87f4628
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/headers.js
@@ -0,0 +1,221 @@
+var ZEROS = '0000000000000000000'
+var ZERO_OFFSET = '0'.charCodeAt(0)
+var USTAR = 'ustar\x0000'
+
+var clamp = function(index, len, defaultValue) {
+  if (typeof index !== 'number') return defaultValue
+  index = ~~index  // Coerce to integer.
+  if (index >= len) return len
+  if (index >= 0) return index
+  index += len
+  if (index >= 0) return index
+  return 0
+}
+
+var toType = function(flag) {
+  switch (flag) {
+    case 0:
+    return 'file'
+    case 1:
+    return 'link'
+    case 2:
+    return 'symlink'
+    case 3:
+    return 'character-device'
+    case 4:
+    return 'block-device'
+    case 5:
+    return 'directory'
+    case 6:
+    return 'fifo'
+    case 7:
+    return 'contiguous-file'
+    case 72:
+    return 'pax-header'
+    case 55:
+    return 'pax-global-header'
+  }
+
+  return null
+}
+
+var toTypeflag = function(flag) {
+  switch (flag) {
+    case 'file':
+    return 0
+    case 'link':
+    return 1
+    case 'symlink':
+    return 2
+    case 'character-device':
+    return 3
+    case 'block-device':
+    return 4
+    case 'directory':
+    return 5
+    case 'fifo':
+    return 6
+    case 'contiguous-file':
+    return 7
+    case 'pax-header':
+    return 72
+  }
+
+  return 0
+}
+
+var alloc = function(size) {
+  var buf = new Buffer(size)
+  buf.fill(0)
+  return buf
+}
+
+var indexOf = function(block, num, offset, end) {
+  for (; offset < end; offset++) {
+    if (block[offset] === num) return offset
+  }
+  return end
+}
+
+var cksum = function(block) {
+  var sum = 8 * 32
+  for (var i = 0; i < 148; i++)   sum += block[i]
+  for (var i = 156; i < 512; i++) sum += block[i]
+  return sum
+}
+
+var encodeOct = function(val, n) {
+  val = val.toString(8)
+  return ZEROS.slice(0, n-val.length)+val+' '
+}
+
+var decodeOct = function(val, offset) {
+  // Older versions of tar can prefix with spaces
+  while (offset < val.length && val[offset] === 32) offset++
+
+  return parseInt(val.slice(offset, clamp(indexOf(val, 32, offset, val.length), val.length, val.length)).toString(), 8)
+}
+
+var decodeStr = function(val, offset, length) {
+  return val.slice(offset, indexOf(val, 0, offset, offset+length)).toString();
+}
+
+var addLength = function(str) {
+  var len = Buffer.byteLength(str)
+  var digits = Math.floor(Math.log(len) / Math.log(10)) + 1
+  if (len + digits > Math.pow(10, digits)) digits++
+
+  return (len+digits)+str
+}
+
+exports.encodePax = function(opts) { // TODO: encode more stuff in pax
+  var result = ''
+  if (opts.name) result += addLength(' path='+opts.name+'\n')
+  if (opts.linkname) result += addLength(' linkpath='+opts.linkname+'\n')
+  return new Buffer(result)
+}
+
+exports.decodePax = function(buf) {
+  var result = {}
+
+  while (buf.length) {
+    var i = 0
+    while (i < buf.length && buf[i] !== 32) i++
+
+    var len = parseInt(buf.slice(0, i).toString())
+    if (!len) return result
+
+    var b = buf.slice(i+1, len-1).toString()
+    var keyIndex = b.indexOf('=')
+    if (keyIndex === -1) return result
+    result[b.slice(0, keyIndex)] = b.slice(keyIndex+1)
+
+    buf = buf.slice(len)
+  }
+
+  return result
+}
+
+exports.encode = function(opts) {
+  var buf = alloc(512)
+  var name = opts.name
+  var prefix = ''
+
+  if (opts.typeflag === 5 && name[name.length-1] !== '/') name += '/'
+  if (Buffer.byteLength(name) !== name.length) return null // utf-8
+
+  while (Buffer.byteLength(name) > 100) {
+    var i = name.indexOf('/')
+    if (i === -1) return null
+    prefix += prefix ? '/' + name.slice(0, i) : name.slice(0, i)
+    name = name.slice(i+1)
+  }
+
+  if (Buffer.byteLength(name) > 100 || Buffer.byteLength(prefix) > 155) return null
+  if (opts.linkname && Buffer.byteLength(opts.linkname) > 100) return null
+
+  buf.write(name)
+  buf.write(encodeOct(opts.mode & 07777, 6), 100)
+  buf.write(encodeOct(opts.uid, 6), 108)
+  buf.write(encodeOct(opts.gid, 6), 116)
+  buf.write(encodeOct(opts.size, 11), 124)
+  buf.write(encodeOct((opts.mtime.getTime() / 1000) | 0, 11), 136)
+
+  buf[156] = ZERO_OFFSET + toTypeflag(opts.type)
+
+  if (opts.linkname) buf.write(opts.linkname, 157)
+
+  buf.write(USTAR, 257)
+  if (opts.uname) buf.write(opts.uname, 265)
+  if (opts.gname) buf.write(opts.gname, 297)
+  buf.write(encodeOct(opts.devmajor || 0, 6), 329)
+  buf.write(encodeOct(opts.devminor || 0, 6), 337)
+
+  if (prefix) buf.write(prefix, 345)
+
+  buf.write(encodeOct(cksum(buf), 6), 148)
+
+  return buf
+}
+
+exports.decode = function(buf) {
+  var typeflag = buf[156] === 0 ? 0 : buf[156] - ZERO_OFFSET
+  var type = toType(typeflag)
+
+  var name = decodeStr(buf, 0, 100)
+  var mode = decodeOct(buf, 100)
+  var uid = decodeOct(buf, 108)
+  var gid = decodeOct(buf, 116)
+  var size = decodeOct(buf, 124)
+  var mtime = decodeOct(buf, 136)
+  var linkname = buf[157] === 0 ? null : decodeStr(buf, 157, 100)
+  var uname = decodeStr(buf, 265, 32)
+  var gname = decodeStr(buf, 297, 32)
+  var devmajor = decodeOct(buf, 329)
+  var devminor = decodeOct(buf, 337)
+
+  if (buf[345]) name = decodeStr(buf, 345, 155)+'/'+name
+
+  var c = cksum(buf)
+
+  //checksum is still initial value if header was null.
+  if (c === 8*32) return null
+
+  //valid checksum
+  if (c !== decodeOct(buf, 148)) throw new Error('invalid header')
+
+  return {
+    name: name,
+    mode: mode,
+    uid: uid,
+    gid: gid,
+    size: size,
+    mtime: new Date(1000 * mtime),
+    type: toType(typeflag),
+    linkname: linkname,
+    uname: uname,
+    gname: gname,
+    devmajor: devmajor,
+    devminor: devminor
+  }
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/index.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/index.js
new file mode 100644
index 0000000..cf54304
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/index.js
@@ -0,0 +1,2 @@
+exports.extract = require('./extract')
+exports.pack = require('./pack')
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/bl/.jshintrc b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/bl/.jshintrc
new file mode 100644
index 0000000..c8ef3ca
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/bl/.jshintrc
@@ -0,0 +1,59 @@
+{
+    "predef": [ ]
+  , "bitwise": false
+  , "camelcase": false
+  , "curly": false
+  , "eqeqeq": false
+  , "forin": false
+  , "immed": false
+  , "latedef": false
+  , "noarg": true
+  , "noempty": true
+  , "nonew": true
+  , "plusplus": false
+  , "quotmark": true
+  , "regexp": false
+  , "undef": true
+  , "unused": true
+  , "strict": false
+  , "trailing": true
+  , "maxlen": 120
+  , "asi": true
+  , "boss": true
+  , "debug": true
+  , "eqnull": true
+  , "esnext": true
+  , "evil": true
+  , "expr": true
+  , "funcscope": false
+  , "globalstrict": false
+  , "iterator": false
+  , "lastsemic": true
+  , "laxbreak": true
+  , "laxcomma": true
+  , "loopfunc": true
+  , "multistr": false
+  , "onecase": false
+  , "proto": false
+  , "regexdash": false
+  , "scripturl": true
+  , "smarttabs": false
+  , "shadow": false
+  , "sub": true
+  , "supernew": false
+  , "validthis": true
+  , "browser": true
+  , "couch": false
+  , "devel": false
+  , "dojo": false
+  , "mootools": false
+  , "node": true
+  , "nonstandard": true
+  , "prototypejs": false
+  , "rhino": false
+  , "worker": true
+  , "wsh": false
+  , "nomen": false
+  , "onevar": false
+  , "passfail": false
+}
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/bl/.npmignore b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/bl/.npmignore
new file mode 100644
index 0000000..40b878d
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/bl/.npmignore
@@ -0,0 +1 @@
+node_modules/
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/bl/.travis.yml b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/bl/.travis.yml
new file mode 100644
index 0000000..7ddb9c9
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/bl/.travis.yml
@@ -0,0 +1,11 @@
+language: node_js
+node_js:
+  - 0.8
+  - "0.10"
+branches:
+  only:
+    - master
+notifications:
+  email:
+    - rod@vagg.org
+script: npm test
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/bl/LICENSE.md b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/bl/LICENSE.md
new file mode 100644
index 0000000..ccb2479
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/bl/LICENSE.md
@@ -0,0 +1,13 @@
+The MIT License (MIT)
+=====================
+
+Copyright (c) 2014 bl contributors
+----------------------------------
+
+*bl contributors listed at <https://github.com/rvagg/bl#contributors>*
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/bl/README.md b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/bl/README.md
new file mode 100644
index 0000000..6b7fb6d
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/bl/README.md
@@ -0,0 +1,198 @@
+# bl *(BufferList)*
+
+**A Node.js Buffer list collector, reader and streamer thingy.**
+
+[![NPM](https://nodei.co/npm/bl.png?downloads=true&downloadRank=true)](https://nodei.co/npm/bl/)
+[![NPM](https://nodei.co/npm-dl/bl.png?months=6&height=3)](https://nodei.co/npm/bl/)
+
+**bl** is a storage object for collections of Node Buffers, exposing them with the main Buffer readable API. Also works as a duplex stream so you can collect buffers from a stream that emits them and emit buffers to a stream that consumes them!
+
+The original buffers are kept intact and copies are only done as necessary. Any reads that require the use of a single original buffer will return a slice of that buffer only (which references the same memory as the original buffer). Reads that span buffers perform concatenation as required and return the results transparently.
+
+```js
+const BufferList = require('bl')
+
+var bl = new BufferList()
+bl.append(new Buffer('abcd'))
+bl.append(new Buffer('efg'))
+bl.append('hi')                     // bl will also accept & convert Strings
+bl.append(new Buffer('j'))
+bl.append(new Buffer([ 0x3, 0x4 ]))
+
+console.log(bl.length) // 12
+
+console.log(bl.slice(0, 10).toString('ascii')) // 'abcdefghij'
+console.log(bl.slice(3, 10).toString('ascii')) // 'defghij'
+console.log(bl.slice(3, 6).toString('ascii'))  // 'def'
+console.log(bl.slice(3, 8).toString('ascii'))  // 'defgh'
+console.log(bl.slice(5, 10).toString('ascii')) // 'fghij'
+
+// or just use toString!
+console.log(bl.toString())               // 'abcdefghij\u0003\u0004'
+console.log(bl.toString('ascii', 3, 8))  // 'defgh'
+console.log(bl.toString('ascii', 5, 10)) // 'fghij'
+
+// other standard Buffer readables
+console.log(bl.readUInt16BE(10)) // 0x0304
+console.log(bl.readUInt16LE(10)) // 0x0403
+```
+
+Give it a callback in the constructor and use it just like **[concat-stream](https://github.com/maxogden/node-concat-stream)**:
+
+```js
+const bl = require('bl')
+    , fs = require('fs')
+
+fs.createReadStream('README.md')
+  .pipe(bl(function (err, data) { // note 'new' isn't strictly required
+    // `data` is a complete Buffer object containing the full data
+    console.log(data.toString())
+  }))
+```
+
+Note that when you use the *callback* method like this, the resulting `data` parameter is a concatenation of all `Buffer` objects in the list. If you want to avoid the overhead of this concatenation (in cases of extreme performance consciousness), then avoid the *callback* method and just listen to `'end'` instead, like a standard Stream.
+
+Or to fetch a URL using [hyperquest](https://github.com/substack/hyperquest) (should work with [request](http://github.com/mikeal/request) and even plain Node http too!):
+```js
+const hyperquest = require('hyperquest')
+    , bl         = require('bl')
+    , url        = 'https://raw.github.com/rvagg/bl/master/README.md'
+
+hyperquest(url).pipe(bl(function (err, data) {
+  console.log(data.toString())
+}))
+```
+
+Or, use it as a readable stream to recompose a list of Buffers to an output source:
+
+```js
+const BufferList = require('bl')
+    , fs         = require('fs')
+
+var bl = new BufferList()
+bl.append(new Buffer('abcd'))
+bl.append(new Buffer('efg'))
+bl.append(new Buffer('hi'))
+bl.append(new Buffer('j'))
+
+bl.pipe(fs.createWriteStream('gibberish.txt'))
+```
+
+## API
+
+  * <a href="#ctor"><code><b>new BufferList([ callback ])</b></code></a>
+  * <a href="#length"><code>bl.<b>length</b></code></a>
+  * <a href="#append"><code>bl.<b>append(buffer)</b></code></a>
+  * <a href="#get"><code>bl.<b>get(index)</b></code></a>
+  * <a href="#slice"><code>bl.<b>slice([ start[, end ] ])</b></code></a>
+  * <a href="#copy"><code>bl.<b>copy(dest, [ destStart, [ srcStart [, srcEnd ] ] ])</b></code></a>
+  * <a href="#duplicate"><code>bl.<b>duplicate()</b></code></a>
+  * <a href="#consume"><code>bl.<b>consume(bytes)</b></code></a>
+  * <a href="#toString"><code>bl.<b>toString([encoding, [ start, [ end ]]])</b></code></a>
+  * <a href="#readXX"><code>bl.<b>readDoubleBE()</b></code>, <code>bl.<b>readDoubleLE()</b></code>, <code>bl.<b>readFloatBE()</b></code>, <code>bl.<b>readFloatLE()</b></code>, <code>bl.<b>readInt32BE()</b></code>, <code>bl.<b>readInt32LE()</b></code>, <code>bl.<b>readUInt32BE()</b></code>, <code>bl.<b>readUInt32LE()</b></code>, <code>bl.<b>readInt16BE()</b></code>, <code>bl.<b>readInt16LE()</b></code>, <code>bl.<b>readUInt16BE()</b></code>, <code>bl.<b>readUInt16LE()</b></code>, <code>bl.<b>readInt8()</b></code>, <code>bl.<b>readUInt8()</b></code></a>
+  * <a href="#streams">Streams</a>
+
+--------------------------------------------------------
+<a name="ctor"></a>
+### new BufferList([ callback | buffer | buffer array ])
+The constructor takes an optional callback, if supplied, the callback will be called with an error argument followed by a reference to the **bl** instance, when `bl.end()` is called (i.e. from a piped stream). This is a convenient method of collecting the entire contents of a stream, particularly when the stream is *chunky*, such as a network stream.
+
+Normally, no arguments are required for the constructor, but you can initialise the list by passing in a single `Buffer` object or an array of `Buffer` object.
+
+`new` is not strictly required, if you don't instantiate a new object, it will be done automatically for you so you can create a new instance simply with:
+
+```js
+var bl = require('bl')
+var myinstance = bl()
+
+// equivilant to:
+
+var BufferList = require('bl')
+var myinstance = new BufferList()
+```
+
+--------------------------------------------------------
+<a name="length"></a>
+### bl.length
+Get the length of the list in bytes. This is the sum of the lengths of all of the buffers contained in the list, minus any initial offset for a semi-consumed buffer at the beginning. Should accurately represent the total number of bytes that can be read from the list.
+
+--------------------------------------------------------
+<a name="append"></a>
+### bl.append(buffer)
+`append(buffer)` adds an additional buffer or BufferList to the internal list.
+
+--------------------------------------------------------
+<a name="get"></a>
+### bl.get(index)
+`get()` will return the byte at the specified index.
+
+--------------------------------------------------------
+<a name="slice"></a>
+### bl.slice([ start, [ end ] ])
+`slice()` returns a new `Buffer` object containing the bytes within the range specified. Both `start` and `end` are optional and will default to the beginning and end of the list respectively.
+
+If the requested range spans a single internal buffer then a slice of that buffer will be returned which shares the original memory range of that Buffer. If the range spans multiple buffers then copy operations will likely occur to give you a uniform Buffer.
+
+--------------------------------------------------------
+<a name="copy"></a>
+### bl.copy(dest, [ destStart, [ srcStart [, srcEnd ] ] ])
+`copy()` copies the content of the list in the `dest` buffer, starting from `destStart` and containing the bytes within the range specified with `srcStart` to `srcEnd`. `destStart`, `start` and `end` are optional and will default to the beginning of the `dest` buffer, and the beginning and end of the list respectively.
+
+--------------------------------------------------------
+<a name="duplicate"></a>
+### bl.duplicate()
+`duplicate()` performs a **shallow-copy** of the list. The internal Buffers remains the same, so if you change the underlying Buffers, the change will be reflected in both the original and the duplicate. This method is needed if you want to call `consume()` or `pipe()` and still keep the original list.Example:
+
+```js
+var bl = new BufferList()
+
+bl.append('hello')
+bl.append(' world')
+bl.append('\n')
+
+bl.duplicate().pipe(process.stdout, { end: false })
+
+console.log(bl.toString())
+```
+
+--------------------------------------------------------
+<a name="consume"></a>
+### bl.consume(bytes)
+`consume()` will shift bytes *off the start of the list*. The number of bytes consumed don't need to line up with the sizes of the internal Buffers&mdash;initial offsets will be calculated accordingly in order to give you a consistent view of the data.
+
+--------------------------------------------------------
+<a name="toString"></a>
+### bl.toString([encoding, [ start, [ end ]]])
+`toString()` will return a string representation of the buffer. The optional `start` and `end` arguments are passed on to `slice()`, while the `encoding` is passed on to `toString()` of the resulting Buffer. See the [Buffer#toString()](http://nodejs.org/docs/latest/api/buffer.html#buffer_buf_tostring_encoding_start_end) documentation for more information.
+
+--------------------------------------------------------
+<a name="readXX"></a>
+### bl.readDoubleBE(), bl.readDoubleLE(), bl.readFloatBE(), bl.readFloatLE(), bl.readInt32BE(), bl.readInt32LE(), bl.readUInt32BE(), bl.readUInt32LE(), bl.readInt16BE(), bl.readInt16LE(), bl.readUInt16BE(), bl.readUInt16LE(), bl.readInt8(), bl.readUInt8()
+
+All of the standard byte-reading methods of the `Buffer` interface are implemented and will operate across internal Buffer boundaries transparently.
+
+See the <b><code>[Buffer](http://nodejs.org/docs/latest/api/buffer.html)</code></b> documentation for how these work.
+
+--------------------------------------------------------
+<a name="streams"></a>
+### Streams
+**bl** is a Node **[Duplex Stream](http://nodejs.org/docs/latest/api/stream.html#stream_class_stream_duplex)**, so it can be read from and written to like a standard Node stream. You can also `pipe()` to and from a **bl** instance.
+
+--------------------------------------------------------
+
+## Contributors
+
+**bl** is brought to you by the following hackers:
+
+ * [Rod Vagg](https://github.com/rvagg)
+ * [Matteo Collina](https://github.com/mcollina)
+ * [Jarett Cruger](https://github.com/jcrugzz)
+
+=======
+
+<a name="license"></a>
+## License &amp; copyright
+
+Copyright (c) 2013-2014 bl contributors (listed above).
+
+bl is licensed under the MIT license. All rights not explicitly granted in the MIT license are reserved. See the included LICENSE.md file for more details.
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/bl/bl.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/bl/bl.js
new file mode 100644
index 0000000..7a2f997
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/bl/bl.js
@@ -0,0 +1,216 @@
+var DuplexStream = require('readable-stream/duplex')
+  , util         = require('util')
+
+function BufferList (callback) {
+  if (!(this instanceof BufferList))
+    return new BufferList(callback)
+
+  this._bufs  = []
+  this.length = 0
+
+  if (typeof callback == 'function') {
+    this._callback = callback
+
+    var piper = function (err) {
+      if (this._callback) {
+        this._callback(err)
+        this._callback = null
+      }
+    }.bind(this)
+
+    this.on('pipe', function (src) {
+      src.on('error', piper)
+    })
+    this.on('unpipe', function (src) {
+      src.removeListener('error', piper)
+    })
+  }
+  else if (Buffer.isBuffer(callback))
+    this.append(callback)
+  else if (Array.isArray(callback)) {
+    callback.forEach(function (b) {
+      Buffer.isBuffer(b) && this.append(b)
+    }.bind(this))
+  }
+
+  DuplexStream.call(this)
+}
+
+util.inherits(BufferList, DuplexStream)
+
+BufferList.prototype._offset = function (offset) {
+  var tot = 0, i = 0, _t
+  for (; i < this._bufs.length; i++) {
+    _t = tot + this._bufs[i].length
+    if (offset < _t)
+      return [ i, offset - tot ]
+    tot = _t
+  }
+}
+
+BufferList.prototype.append = function (buf) {
+  var isBuffer = Buffer.isBuffer(buf) ||
+                 buf instanceof BufferList
+
+  this._bufs.push(isBuffer ? buf : new Buffer(buf))
+  this.length += buf.length
+  return this
+}
+
+BufferList.prototype._write = function (buf, encoding, callback) {
+  this.append(buf)
+  if (callback)
+    callback()
+}
+
+BufferList.prototype._read = function (size) {
+  if (!this.length)
+    return this.push(null)
+  size = Math.min(size, this.length)
+  this.push(this.slice(0, size))
+  this.consume(size)
+}
+
+BufferList.prototype.end = function (chunk) {
+  DuplexStream.prototype.end.call(this, chunk)
+
+  if (this._callback) {
+    this._callback(null, this.slice())
+    this._callback = null
+  }
+}
+
+BufferList.prototype.get = function (index) {
+  return this.slice(index, index + 1)[0]
+}
+
+BufferList.prototype.slice = function (start, end) {
+  return this.copy(null, 0, start, end)
+}
+
+BufferList.prototype.copy = function (dst, dstStart, srcStart, srcEnd) {
+  if (typeof srcStart != 'number' || srcStart < 0)
+    srcStart = 0
+  if (typeof srcEnd != 'number' || srcEnd > this.length)
+    srcEnd = this.length
+  if (srcStart >= this.length)
+    return dst || new Buffer(0)
+  if (srcEnd <= 0)
+    return dst || new Buffer(0)
+
+  var copy   = !!dst
+    , off    = this._offset(srcStart)
+    , len    = srcEnd - srcStart
+    , bytes  = len
+    , bufoff = (copy && dstStart) || 0
+    , start  = off[1]
+    , l
+    , i
+
+  // copy/slice everything
+  if (srcStart === 0 && srcEnd == this.length) {
+    if (!copy) // slice, just return a full concat
+      return Buffer.concat(this._bufs)
+
+    // copy, need to copy individual buffers
+    for (i = 0; i < this._bufs.length; i++) {
+      this._bufs[i].copy(dst, bufoff)
+      bufoff += this._bufs[i].length
+    }
+
+    return dst
+  }
+
+  // easy, cheap case where it's a subset of one of the buffers
+  if (bytes <= this._bufs[off[0]].length - start) {
+    return copy
+      ? this._bufs[off[0]].copy(dst, dstStart, start, start + bytes)
+      : this._bufs[off[0]].slice(start, start + bytes)
+  }
+
+  if (!copy) // a slice, we need something to copy in to
+    dst = new Buffer(len)
+
+  for (i = off[0]; i < this._bufs.length; i++) {
+    l = this._bufs[i].length - start
+
+    if (bytes > l) {
+      this._bufs[i].copy(dst, bufoff, start)
+    } else {
+      this._bufs[i].copy(dst, bufoff, start, start + bytes)
+      break
+    }
+
+    bufoff += l
+    bytes -= l
+
+    if (start)
+      start = 0
+  }
+
+  return dst
+}
+
+BufferList.prototype.toString = function (encoding, start, end) {
+  return this.slice(start, end).toString(encoding)
+}
+
+BufferList.prototype.consume = function (bytes) {
+  while (this._bufs.length) {
+    if (bytes > this._bufs[0].length) {
+      bytes -= this._bufs[0].length
+      this.length -= this._bufs[0].length
+      this._bufs.shift()
+    } else {
+      this._bufs[0] = this._bufs[0].slice(bytes)
+      this.length -= bytes
+      break
+    }
+  }
+  return this
+}
+
+BufferList.prototype.duplicate = function () {
+  var i = 0
+    , copy = new BufferList()
+
+  for (; i < this._bufs.length; i++)
+    copy.append(this._bufs[i])
+
+  return copy
+}
+
+BufferList.prototype.destroy = function () {
+  this._bufs.length = 0;
+  this.length = 0;
+  this.push(null);
+}
+
+;(function () {
+  var methods = {
+      'readDoubleBE' : 8
+    , 'readDoubleLE' : 8
+    , 'readFloatBE'  : 4
+    , 'readFloatLE'  : 4
+    , 'readInt32BE'  : 4
+    , 'readInt32LE'  : 4
+    , 'readUInt32BE' : 4
+    , 'readUInt32LE' : 4
+    , 'readInt16BE'  : 2
+    , 'readInt16LE'  : 2
+    , 'readUInt16BE' : 2
+    , 'readUInt16LE' : 2
+    , 'readInt8'     : 1
+    , 'readUInt8'    : 1
+  }
+
+  for (var m in methods) {
+    (function (m) {
+      BufferList.prototype[m] = function (offset) {
+        return this.slice(offset, offset + methods[m])[m](0)
+      }
+    }(m))
+  }
+}())
+
+module.exports = BufferList
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/bl/package.json b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/bl/package.json
new file mode 100644
index 0000000..c32bb5a
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/bl/package.json
@@ -0,0 +1,62 @@
+{
+  "name": "bl",
+  "version": "0.9.4",
+  "description": "Buffer List: collect buffers and access with a standard readable Buffer interface, streamable too!",
+  "main": "bl.js",
+  "scripts": {
+    "test": "node test/test.js | faucet",
+    "test-local": "brtapsauce-local test/basic-test.js"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/rvagg/bl.git"
+  },
+  "homepage": "https://github.com/rvagg/bl",
+  "authors": [
+    "Rod Vagg <rod@vagg.org> (https://github.com/rvagg)",
+    "Matteo Collina <matteo.collina@gmail.com> (https://github.com/mcollina)",
+    "Jarett Cruger <jcrugzz@gmail.com> (https://github.com/jcrugzz)"
+  ],
+  "keywords": [
+    "buffer",
+    "buffers",
+    "stream",
+    "awesomesauce"
+  ],
+  "license": "MIT",
+  "dependencies": {
+    "readable-stream": "~1.0.26"
+  },
+  "devDependencies": {
+    "tape": "~2.12.3",
+    "hash_file": "~0.1.1",
+    "faucet": "~0.0.1",
+    "brtapsauce": "~0.3.0"
+  },
+  "gitHead": "e7f90703c5f90ca26f60455ea6ad0b6be4a9feee",
+  "bugs": {
+    "url": "https://github.com/rvagg/bl/issues"
+  },
+  "_id": "bl@0.9.4",
+  "_shasum": "4702ddf72fbe0ecd82787c00c113aea1935ad0e7",
+  "_from": "bl@^0.9.0",
+  "_npmVersion": "2.1.18",
+  "_nodeVersion": "1.0.3",
+  "_npmUser": {
+    "name": "rvagg",
+    "email": "rod@vagg.org"
+  },
+  "maintainers": [
+    {
+      "name": "rvagg",
+      "email": "rod@vagg.org"
+    }
+  ],
+  "dist": {
+    "shasum": "4702ddf72fbe0ecd82787c00c113aea1935ad0e7",
+    "tarball": "http://registry.npmjs.org/bl/-/bl-0.9.4.tgz"
+  },
+  "directories": {},
+  "_resolved": "https://registry.npmjs.org/bl/-/bl-0.9.4.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/bl/test/basic-test.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/bl/test/basic-test.js
new file mode 100644
index 0000000..75116a3
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/bl/test/basic-test.js
@@ -0,0 +1,541 @@
+var tape       = require('tape')
+  , crypto     = require('crypto')
+  , fs         = require('fs')
+  , hash       = require('hash_file')
+  , BufferList = require('../')
+
+  , encodings  =
+      ('hex utf8 utf-8 ascii binary base64'
+          + (process.browser ? '' : ' ucs2 ucs-2 utf16le utf-16le')).split(' ')
+
+tape('single bytes from single buffer', function (t) {
+  var bl = new BufferList()
+  bl.append(new Buffer('abcd'))
+
+  t.equal(bl.length, 4)
+
+  t.equal(bl.get(0), 97)
+  t.equal(bl.get(1), 98)
+  t.equal(bl.get(2), 99)
+  t.equal(bl.get(3), 100)
+
+  t.end()
+})
+
+tape('single bytes from multiple buffers', function (t) {
+  var bl = new BufferList()
+  bl.append(new Buffer('abcd'))
+  bl.append(new Buffer('efg'))
+  bl.append(new Buffer('hi'))
+  bl.append(new Buffer('j'))
+
+  t.equal(bl.length, 10)
+
+  t.equal(bl.get(0), 97)
+  t.equal(bl.get(1), 98)
+  t.equal(bl.get(2), 99)
+  t.equal(bl.get(3), 100)
+  t.equal(bl.get(4), 101)
+  t.equal(bl.get(5), 102)
+  t.equal(bl.get(6), 103)
+  t.equal(bl.get(7), 104)
+  t.equal(bl.get(8), 105)
+  t.equal(bl.get(9), 106)
+  t.end()
+})
+
+tape('multi bytes from single buffer', function (t) {
+  var bl = new BufferList()
+  bl.append(new Buffer('abcd'))
+
+  t.equal(bl.length, 4)
+
+  t.equal(bl.slice(0, 4).toString('ascii'), 'abcd')
+  t.equal(bl.slice(0, 3).toString('ascii'), 'abc')
+  t.equal(bl.slice(1, 4).toString('ascii'), 'bcd')
+
+  t.end()
+})
+
+tape('multiple bytes from multiple buffers', function (t) {
+  var bl = new BufferList()
+
+  bl.append(new Buffer('abcd'))
+  bl.append(new Buffer('efg'))
+  bl.append(new Buffer('hi'))
+  bl.append(new Buffer('j'))
+
+  t.equal(bl.length, 10)
+
+  t.equal(bl.slice(0, 10).toString('ascii'), 'abcdefghij')
+  t.equal(bl.slice(3, 10).toString('ascii'), 'defghij')
+  t.equal(bl.slice(3, 6).toString('ascii'), 'def')
+  t.equal(bl.slice(3, 8).toString('ascii'), 'defgh')
+  t.equal(bl.slice(5, 10).toString('ascii'), 'fghij')
+
+  t.end()
+})
+
+tape('multiple bytes from multiple buffer lists', function (t) {
+  var bl = new BufferList()
+
+  bl.append(new BufferList([new Buffer('abcd'), new Buffer('efg')]))
+  bl.append(new BufferList([new Buffer('hi'), new Buffer('j')]))
+
+  t.equal(bl.length, 10)
+
+  t.equal(bl.slice(0, 10).toString('ascii'), 'abcdefghij')
+  t.equal(bl.slice(3, 10).toString('ascii'), 'defghij')
+  t.equal(bl.slice(3, 6).toString('ascii'), 'def')
+  t.equal(bl.slice(3, 8).toString('ascii'), 'defgh')
+  t.equal(bl.slice(5, 10).toString('ascii'), 'fghij')
+
+  t.end()
+})
+
+tape('consuming from multiple buffers', function (t) {
+  var bl = new BufferList()
+
+  bl.append(new Buffer('abcd'))
+  bl.append(new Buffer('efg'))
+  bl.append(new Buffer('hi'))
+  bl.append(new Buffer('j'))
+
+  t.equal(bl.length, 10)
+
+  t.equal(bl.slice(0, 10).toString('ascii'), 'abcdefghij')
+
+  bl.consume(3)
+  t.equal(bl.length, 7)
+  t.equal(bl.slice(0, 7).toString('ascii'), 'defghij')
+
+  bl.consume(2)
+  t.equal(bl.length, 5)
+  t.equal(bl.slice(0, 5).toString('ascii'), 'fghij')
+
+  bl.consume(1)
+  t.equal(bl.length, 4)
+  t.equal(bl.slice(0, 4).toString('ascii'), 'ghij')
+
+  bl.consume(1)
+  t.equal(bl.length, 3)
+  t.equal(bl.slice(0, 3).toString('ascii'), 'hij')
+
+  bl.consume(2)
+  t.equal(bl.length, 1)
+  t.equal(bl.slice(0, 1).toString('ascii'), 'j')
+
+  t.end()
+})
+
+tape('test readUInt8 / readInt8', function (t) {
+  var buf1 = new Buffer(1)
+    , buf2 = new Buffer(3)
+    , buf3 = new Buffer(3)
+    , bl  = new BufferList()
+
+  buf2[1] = 0x3
+  buf2[2] = 0x4
+  buf3[0] = 0x23
+  buf3[1] = 0x42
+
+  bl.append(buf1)
+  bl.append(buf2)
+  bl.append(buf3)
+
+  t.equal(bl.readUInt8(2), 0x3)
+  t.equal(bl.readInt8(2), 0x3)
+  t.equal(bl.readUInt8(3), 0x4)
+  t.equal(bl.readInt8(3), 0x4)
+  t.equal(bl.readUInt8(4), 0x23)
+  t.equal(bl.readInt8(4), 0x23)
+  t.equal(bl.readUInt8(5), 0x42)
+  t.equal(bl.readInt8(5), 0x42)
+  t.end()
+})
+
+tape('test readUInt16LE / readUInt16BE / readInt16LE / readInt16BE', function (t) {
+  var buf1 = new Buffer(1)
+    , buf2 = new Buffer(3)
+    , buf3 = new Buffer(3)
+    , bl   = new BufferList()
+
+  buf2[1] = 0x3
+  buf2[2] = 0x4
+  buf3[0] = 0x23
+  buf3[1] = 0x42
+
+  bl.append(buf1)
+  bl.append(buf2)
+  bl.append(buf3)
+
+  t.equal(bl.readUInt16BE(2), 0x0304)
+  t.equal(bl.readUInt16LE(2), 0x0403)
+  t.equal(bl.readInt16BE(2), 0x0304)
+  t.equal(bl.readInt16LE(2), 0x0403)
+  t.equal(bl.readUInt16BE(3), 0x0423)
+  t.equal(bl.readUInt16LE(3), 0x2304)
+  t.equal(bl.readInt16BE(3), 0x0423)
+  t.equal(bl.readInt16LE(3), 0x2304)
+  t.equal(bl.readUInt16BE(4), 0x2342)
+  t.equal(bl.readUInt16LE(4), 0x4223)
+  t.equal(bl.readInt16BE(4), 0x2342)
+  t.equal(bl.readInt16LE(4), 0x4223)
+  t.end()
+})
+
+tape('test readUInt32LE / readUInt32BE / readInt32LE / readInt32BE', function (t) {
+  var buf1 = new Buffer(1)
+    , buf2 = new Buffer(3)
+    , buf3 = new Buffer(3)
+    , bl   = new BufferList()
+
+  buf2[1] = 0x3
+  buf2[2] = 0x4
+  buf3[0] = 0x23
+  buf3[1] = 0x42
+
+  bl.append(buf1)
+  bl.append(buf2)
+  bl.append(buf3)
+
+  t.equal(bl.readUInt32BE(2), 0x03042342)
+  t.equal(bl.readUInt32LE(2), 0x42230403)
+  t.equal(bl.readInt32BE(2), 0x03042342)
+  t.equal(bl.readInt32LE(2), 0x42230403)
+  t.end()
+})
+
+tape('test readFloatLE / readFloatBE', function (t) {
+  var buf1 = new Buffer(1)
+    , buf2 = new Buffer(3)
+    , buf3 = new Buffer(3)
+    , bl   = new BufferList()
+
+  buf2[1] = 0x00
+  buf2[2] = 0x00
+  buf3[0] = 0x80
+  buf3[1] = 0x3f
+
+  bl.append(buf1)
+  bl.append(buf2)
+  bl.append(buf3)
+
+  t.equal(bl.readFloatLE(2), 0x01)
+  t.end()
+})
+
+tape('test readDoubleLE / readDoubleBE', function (t) {
+  var buf1 = new Buffer(1)
+    , buf2 = new Buffer(3)
+    , buf3 = new Buffer(10)
+    , bl   = new BufferList()
+
+  buf2[1] = 0x55
+  buf2[2] = 0x55
+  buf3[0] = 0x55
+  buf3[1] = 0x55
+  buf3[2] = 0x55
+  buf3[3] = 0x55
+  buf3[4] = 0xd5
+  buf3[5] = 0x3f
+
+  bl.append(buf1)
+  bl.append(buf2)
+  bl.append(buf3)
+
+  t.equal(bl.readDoubleLE(2), 0.3333333333333333)
+  t.end()
+})
+
+tape('test toString', function (t) {
+  var bl = new BufferList()
+
+  bl.append(new Buffer('abcd'))
+  bl.append(new Buffer('efg'))
+  bl.append(new Buffer('hi'))
+  bl.append(new Buffer('j'))
+
+  t.equal(bl.toString('ascii', 0, 10), 'abcdefghij')
+  t.equal(bl.toString('ascii', 3, 10), 'defghij')
+  t.equal(bl.toString('ascii', 3, 6), 'def')
+  t.equal(bl.toString('ascii', 3, 8), 'defgh')
+  t.equal(bl.toString('ascii', 5, 10), 'fghij')
+
+  t.end()
+})
+
+tape('test toString encoding', function (t) {
+  var bl = new BufferList()
+    , b  = new Buffer('abcdefghij\xff\x00')
+
+  bl.append(new Buffer('abcd'))
+  bl.append(new Buffer('efg'))
+  bl.append(new Buffer('hi'))
+  bl.append(new Buffer('j'))
+  bl.append(new Buffer('\xff\x00'))
+
+  encodings.forEach(function (enc) {
+      t.equal(bl.toString(enc), b.toString(enc), enc)
+    })
+
+  t.end()
+})
+
+!process.browser && tape('test stream', function (t) {
+  var random = crypto.randomBytes(65534)
+    , rndhash = hash(random, 'md5')
+    , md5sum = crypto.createHash('md5')
+    , bl     = new BufferList(function (err, buf) {
+        t.ok(Buffer.isBuffer(buf))
+        t.ok(err === null)
+        t.equal(rndhash, hash(bl.slice(), 'md5'))
+        t.equal(rndhash, hash(buf, 'md5'))
+
+        bl.pipe(fs.createWriteStream('/tmp/bl_test_rnd_out.dat'))
+          .on('close', function () {
+            var s = fs.createReadStream('/tmp/bl_test_rnd_out.dat')
+            s.on('data', md5sum.update.bind(md5sum))
+            s.on('end', function() {
+              t.equal(rndhash, md5sum.digest('hex'), 'woohoo! correct hash!')
+              t.end()
+            })
+          })
+
+      })
+
+  fs.writeFileSync('/tmp/bl_test_rnd.dat', random)
+  fs.createReadStream('/tmp/bl_test_rnd.dat').pipe(bl)
+})
+
+tape('instantiation with Buffer', function (t) {
+  var buf  = crypto.randomBytes(1024)
+    , buf2 = crypto.randomBytes(1024)
+    , b    = BufferList(buf)
+
+  t.equal(buf.toString('hex'), b.slice().toString('hex'), 'same buffer')
+  b = BufferList([ buf, buf2 ])
+  t.equal(b.slice().toString('hex'), Buffer.concat([ buf, buf2 ]).toString('hex'), 'same buffer')
+  t.end()
+})
+
+tape('test String appendage', function (t) {
+  var bl = new BufferList()
+    , b  = new Buffer('abcdefghij\xff\x00')
+
+  bl.append('abcd')
+  bl.append('efg')
+  bl.append('hi')
+  bl.append('j')
+  bl.append('\xff\x00')
+
+  encodings.forEach(function (enc) {
+      t.equal(bl.toString(enc), b.toString(enc))
+    })
+
+  t.end()
+})
+
+tape('write nothing, should get empty buffer', function (t) {
+  t.plan(3)
+  BufferList(function (err, data) {
+    t.notOk(err, 'no error')
+    t.ok(Buffer.isBuffer(data), 'got a buffer')
+    t.equal(0, data.length, 'got a zero-length buffer')
+    t.end()
+  }).end()
+})
+
+tape('unicode string', function (t) {
+  t.plan(2)
+  var inp1 = '\u2600'
+    , inp2 = '\u2603'
+    , exp = inp1 + ' and ' + inp2
+    , bl = BufferList()
+  bl.write(inp1)
+  bl.write(' and ')
+  bl.write(inp2)
+  t.equal(exp, bl.toString())
+  t.equal(new Buffer(exp).toString('hex'), bl.toString('hex'))
+})
+
+tape('should emit finish', function (t) {
+  var source = BufferList()
+    , dest = BufferList()
+
+  source.write('hello')
+  source.pipe(dest)
+
+  dest.on('finish', function () {
+    t.equal(dest.toString('utf8'), 'hello')
+    t.end()
+  })
+})
+
+tape('basic copy', function (t) {
+  var buf  = crypto.randomBytes(1024)
+    , buf2 = new Buffer(1024)
+    , b    = BufferList(buf)
+
+  b.copy(buf2)
+  t.equal(b.slice().toString('hex'), buf2.toString('hex'), 'same buffer')
+  t.end()
+})
+
+tape('copy after many appends', function (t) {
+  var buf  = crypto.randomBytes(512)
+    , buf2 = new Buffer(1024)
+    , b    = BufferList(buf)
+
+  b.append(buf)
+  b.copy(buf2)
+  t.equal(b.slice().toString('hex'), buf2.toString('hex'), 'same buffer')
+  t.end()
+})
+
+tape('copy at a precise position', function (t) {
+  var buf  = crypto.randomBytes(1004)
+    , buf2 = new Buffer(1024)
+    , b    = BufferList(buf)
+
+  b.copy(buf2, 20)
+  t.equal(b.slice().toString('hex'), buf2.slice(20).toString('hex'), 'same buffer')
+  t.end()
+})
+
+tape('copy starting from a precise location', function (t) {
+  var buf  = crypto.randomBytes(10)
+    , buf2 = new Buffer(5)
+    , b    = BufferList(buf)
+
+  b.copy(buf2, 0, 5)
+  t.equal(b.slice(5).toString('hex'), buf2.toString('hex'), 'same buffer')
+  t.end()
+})
+
+tape('copy in an interval', function (t) {
+  var rnd      = crypto.randomBytes(10)
+    , b        = BufferList(rnd) // put the random bytes there
+    , actual   = new Buffer(3)
+    , expected = new Buffer(3)
+
+  rnd.copy(expected, 0, 5, 8)
+  b.copy(actual, 0, 5, 8)
+
+  t.equal(actual.toString('hex'), expected.toString('hex'), 'same buffer')
+  t.end()
+})
+
+tape('copy an interval between two buffers', function (t) {
+  var buf      = crypto.randomBytes(10)
+    , buf2     = new Buffer(10)
+    , b        = BufferList(buf)
+
+  b.append(buf)
+  b.copy(buf2, 0, 5, 15)
+
+  t.equal(b.slice(5, 15).toString('hex'), buf2.toString('hex'), 'same buffer')
+  t.end()
+})
+
+tape('duplicate', function (t) {
+  t.plan(2)
+
+  var bl = new BufferList('abcdefghij\xff\x00')
+    , dup = bl.duplicate()
+
+  t.equal(bl.prototype, dup.prototype)
+  t.equal(bl.toString('hex'), dup.toString('hex'))
+})
+
+tape('destroy no pipe', function (t) {
+  t.plan(2)
+
+  var bl = new BufferList('alsdkfja;lsdkfja;lsdk')
+  bl.destroy()
+
+  t.equal(bl._bufs.length, 0)
+  t.equal(bl.length, 0)
+})
+
+!process.browser && tape('destroy with pipe before read end', function (t) {
+  t.plan(2)
+
+  var bl = new BufferList()
+  fs.createReadStream(__dirname + '/sauce.js')
+    .pipe(bl)
+
+  bl.destroy()
+
+  t.equal(bl._bufs.length, 0)
+  t.equal(bl.length, 0)
+
+})
+
+!process.browser && tape('destroy with pipe before read end with race', function (t) {
+  t.plan(2)
+
+  var bl = new BufferList()
+  fs.createReadStream(__dirname + '/sauce.js')
+    .pipe(bl)
+
+  setTimeout(function () {
+    bl.destroy()
+    setTimeout(function () {
+      t.equal(bl._bufs.length, 0)
+      t.equal(bl.length, 0)
+    }, 500)
+  }, 500)
+})
+
+!process.browser && tape('destroy with pipe after read end', function (t) {
+  t.plan(2)
+
+  var bl = new BufferList()
+  fs.createReadStream(__dirname + '/sauce.js')
+    .on('end', onEnd)
+    .pipe(bl)
+
+  function onEnd () {
+    bl.destroy()
+
+    t.equal(bl._bufs.length, 0)
+    t.equal(bl.length, 0)
+  }
+})
+
+!process.browser && tape('destroy with pipe while writing to a destination', function (t) {
+  t.plan(4)
+
+  var bl = new BufferList()
+    , ds = new BufferList()
+
+  fs.createReadStream(__dirname + '/sauce.js')
+    .on('end', onEnd)
+    .pipe(bl)
+
+  function onEnd () {
+    bl.pipe(ds)
+
+    setTimeout(function () {
+      bl.destroy()
+
+      t.equals(bl._bufs.length, 0)
+      t.equals(bl.length, 0)
+
+      ds.destroy()
+
+      t.equals(bl._bufs.length, 0)
+      t.equals(bl.length, 0)
+
+    }, 100)
+  }
+})
+
+!process.browser && tape('handle error', function (t) {
+  t.plan(2)
+  fs.createReadStream('/does/not/exist').pipe(BufferList(function (err, data) {
+    t.ok(err instanceof Error, 'has error')
+    t.notOk(data, 'no data')
+  }))
+})
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/bl/test/sauce.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/bl/test/sauce.js
new file mode 100644
index 0000000..a6d2862
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/bl/test/sauce.js
@@ -0,0 +1,38 @@
+#!/usr/bin/env node
+
+const user       = process.env.SAUCE_USER
+    , key        = process.env.SAUCE_KEY
+    , path       = require('path')
+    , brtapsauce = require('brtapsauce')
+    , testFile   = path.join(__dirname, 'basic-test.js')
+
+    , capabilities = [
+          { browserName: 'chrome'            , platform: 'Windows XP', version: ''   }
+        , { browserName: 'firefox'           , platform: 'Windows 8' , version: ''   }
+        , { browserName: 'firefox'           , platform: 'Windows XP', version: '4'  }
+        , { browserName: 'internet explorer' , platform: 'Windows 8' , version: '10' }
+        , { browserName: 'internet explorer' , platform: 'Windows 7' , version: '9'  }
+        , { browserName: 'internet explorer' , platform: 'Windows 7' , version: '8'  }
+        , { browserName: 'internet explorer' , platform: 'Windows XP', version: '7'  }
+        , { browserName: 'internet explorer' , platform: 'Windows XP', version: '6'  }
+        , { browserName: 'safari'            , platform: 'Windows 7' , version: '5'  }
+        , { browserName: 'safari'            , platform: 'OS X 10.8' , version: '6'  }
+        , { browserName: 'opera'             , platform: 'Windows 7' , version: ''   }
+        , { browserName: 'opera'             , platform: 'Windows 7' , version: '11' }
+        , { browserName: 'ipad'              , platform: 'OS X 10.8' , version: '6'  }
+        , { browserName: 'android'           , platform: 'Linux'     , version: '4.0', 'device-type': 'tablet' }
+      ]
+
+if (!user)
+  throw new Error('Must set a SAUCE_USER env var')
+if (!key)
+  throw new Error('Must set a SAUCE_KEY env var')
+
+brtapsauce({
+    name         : 'Traversty'
+  , user         : user
+  , key          : key
+  , brsrc        : testFile
+  , capabilities : capabilities
+  , options      : { timeout: 60 * 6 }
+})
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/bl/test/test.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/bl/test/test.js
new file mode 100644
index 0000000..aa9b487
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/bl/test/test.js
@@ -0,0 +1,9 @@
+require('./basic-test')
+
+if (!process.env.SAUCE_KEY || !process.env.SAUCE_USER)
+  return console.log('SAUCE_KEY and/or SAUCE_USER not set, not running sauce tests')
+
+if (!/v0\.10/.test(process.version))
+  return console.log('Not Node v0.10.x, not running sauce tests')
+
+require('./sauce.js')
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/.npmignore b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/.npmignore
new file mode 100644
index 0000000..3c3629e
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/.npmignore
@@ -0,0 +1 @@
+node_modules
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/LICENSE b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/LICENSE
new file mode 100644
index 0000000..757562e
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Mathias Buus
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/README.md b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/README.md
new file mode 100644
index 0000000..df800c1
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/README.md
@@ -0,0 +1,47 @@
+# end-of-stream
+
+A node module that calls a callback when a readable/writable/duplex stream has completed or failed.
+
+	npm install end-of-stream
+
+## Usage
+
+Simply pass a stream and a callback to the `eos`.
+Both legacy streams and streams2 are supported.
+
+``` js
+var eos = require('end-of-stream');
+
+eos(readableStream, function(err) {
+	if (err) return console.log('stream had an error or closed early');
+	console.log('stream has ended');
+});
+
+eos(writableStream, function(err) {
+	if (err) return console.log('stream had an error or closed early');
+	console.log('stream has finished');
+});
+
+eos(duplexStream, function(err) {
+	if (err) return console.log('stream had an error or closed early');
+	console.log('stream has ended and finished');
+});
+
+eos(duplexStream, {readable:false}, function(err) {
+	if (err) return console.log('stream had an error or closed early');
+	console.log('stream has ended but might still be writable');
+});
+
+eos(duplexStream, {writable:false}, function(err) {
+	if (err) return console.log('stream had an error or closed early');
+	console.log('stream has ended but might still be readable');
+});
+
+eos(readableStream, {error:false}, function(err) {
+	// do not treat emit('error', err) as a end-of-stream
+});
+```
+
+## License
+
+MIT
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/index.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/index.js
new file mode 100644
index 0000000..f92fc19
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/index.js
@@ -0,0 +1,83 @@
+var once = require('once');
+
+var noop = function() {};
+
+var isRequest = function(stream) {
+	return stream.setHeader && typeof stream.abort === 'function';
+};
+
+var isChildProcess = function(stream) {
+	return stream.stdio && Array.isArray(stream.stdio) && stream.stdio.length === 3
+};
+
+var eos = function(stream, opts, callback) {
+	if (typeof opts === 'function') return eos(stream, null, opts);
+	if (!opts) opts = {};
+
+	callback = once(callback || noop);
+
+	var ws = stream._writableState;
+	var rs = stream._readableState;
+	var readable = opts.readable || (opts.readable !== false && stream.readable);
+	var writable = opts.writable || (opts.writable !== false && stream.writable);
+
+	var onlegacyfinish = function() {
+		if (!stream.writable) onfinish();
+	};
+
+	var onfinish = function() {
+		writable = false;
+		if (!readable) callback();
+	};
+
+	var onend = function() {
+		readable = false;
+		if (!writable) callback();
+	};
+
+	var onexit = function(exitCode) {
+		callback(exitCode ? new Error('exited with error code: ' + exitCode) : null);
+	};
+
+	var onclose = function() {
+		if (readable && !(rs && rs.ended)) return callback(new Error('premature close'));
+		if (writable && !(ws && ws.ended)) return callback(new Error('premature close'));
+	};
+
+	var onrequest = function() {
+		stream.req.on('finish', onfinish);
+	};
+
+	if (isRequest(stream)) {
+		stream.on('complete', onfinish);
+		stream.on('abort', onclose);
+		if (stream.req) onrequest();
+		else stream.on('request', onrequest);
+	} else if (writable && !ws) { // legacy streams
+		stream.on('end', onlegacyfinish);
+		stream.on('close', onlegacyfinish);
+	}
+
+	if (isChildProcess(stream)) stream.on('exit', onexit);
+
+	stream.on('end', onend);
+	stream.on('finish', onfinish);
+	if (opts.error !== false) stream.on('error', callback);
+	stream.on('close', onclose);
+
+	return function() {
+		stream.removeListener('complete', onfinish);
+		stream.removeListener('abort', onclose);
+		stream.removeListener('request', onrequest);
+		if (stream.req) stream.req.removeListener('finish', onfinish);
+		stream.removeListener('end', onlegacyfinish);
+		stream.removeListener('close', onlegacyfinish);
+		stream.removeListener('finish', onfinish);
+		stream.removeListener('exit', onexit);
+		stream.removeListener('end', onend);
+		stream.removeListener('error', callback);
+		stream.removeListener('close', onclose);
+	};
+};
+
+module.exports = eos;
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/node_modules/once/LICENSE b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/node_modules/once/LICENSE
new file mode 100644
index 0000000..0c44ae7
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/node_modules/once/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) Isaac Z. Schlueter ("Author")
+All rights reserved.
+
+The BSD License
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/node_modules/once/README.md b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/node_modules/once/README.md
new file mode 100644
index 0000000..a2981ea
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/node_modules/once/README.md
@@ -0,0 +1,51 @@
+# once
+
+Only call a function once.
+
+## usage
+
+```javascript
+var once = require('once')
+
+function load (file, cb) {
+  cb = once(cb)
+  loader.load('file')
+  loader.once('load', cb)
+  loader.once('error', cb)
+}
+```
+
+Or add to the Function.prototype in a responsible way:
+
+```javascript
+// only has to be done once
+require('once').proto()
+
+function load (file, cb) {
+  cb = cb.once()
+  loader.load('file')
+  loader.once('load', cb)
+  loader.once('error', cb)
+}
+```
+
+Ironically, the prototype feature makes this module twice as
+complicated as necessary.
+
+To check whether you function has been called, use `fn.called`. Once the
+function is called for the first time the return value of the original
+function is saved in `fn.value` and subsequent calls will continue to
+return this value.
+
+```javascript
+var once = require('once')
+
+function load (cb) {
+  cb = once(cb)
+  var stream = createStream()
+  stream.once('data', cb)
+  stream.once('end', function () {
+    if (!cb.called) cb(new Error('not found'))
+  })
+}
+```
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/node_modules/once/node_modules/wrappy/LICENSE b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/node_modules/once/node_modules/wrappy/LICENSE
new file mode 100644
index 0000000..19129e3
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/node_modules/once/node_modules/wrappy/LICENSE
@@ -0,0 +1,15 @@
+The ISC License
+
+Copyright (c) Isaac Z. Schlueter and Contributors
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/node_modules/once/node_modules/wrappy/README.md b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/node_modules/once/node_modules/wrappy/README.md
new file mode 100644
index 0000000..98eab25
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/node_modules/once/node_modules/wrappy/README.md
@@ -0,0 +1,36 @@
+# wrappy
+
+Callback wrapping utility
+
+## USAGE
+
+```javascript
+var wrappy = require("wrappy")
+
+// var wrapper = wrappy(wrapperFunction)
+
+// make sure a cb is called only once
+// See also: http://npm.im/once for this specific use case
+var once = wrappy(function (cb) {
+  var called = false
+  return function () {
+    if (called) return
+    called = true
+    return cb.apply(this, arguments)
+  }
+})
+
+function printBoo () {
+  console.log('boo')
+}
+// has some rando property
+printBoo.iAmBooPrinter = true
+
+var onlyPrintOnce = once(printBoo)
+
+onlyPrintOnce() // prints 'boo'
+onlyPrintOnce() // does nothing
+
+// random property is retained!
+assert.equal(onlyPrintOnce.iAmBooPrinter, true)
+```
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/node_modules/once/node_modules/wrappy/package.json b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/node_modules/once/node_modules/wrappy/package.json
new file mode 100644
index 0000000..9165c6e
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/node_modules/once/node_modules/wrappy/package.json
@@ -0,0 +1,52 @@
+{
+  "name": "wrappy",
+  "version": "1.0.1",
+  "description": "Callback wrapping utility",
+  "main": "wrappy.js",
+  "directories": {
+    "test": "test"
+  },
+  "dependencies": {},
+  "devDependencies": {
+    "tap": "^0.4.12"
+  },
+  "scripts": {
+    "test": "tap test/*.js"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/npm/wrappy"
+  },
+  "author": {
+    "name": "Isaac Z. Schlueter",
+    "email": "i@izs.me",
+    "url": "http://blog.izs.me/"
+  },
+  "license": "ISC",
+  "bugs": {
+    "url": "https://github.com/npm/wrappy/issues"
+  },
+  "homepage": "https://github.com/npm/wrappy",
+  "gitHead": "006a8cbac6b99988315834c207896eed71fd069a",
+  "_id": "wrappy@1.0.1",
+  "_shasum": "1e65969965ccbc2db4548c6b84a6f2c5aedd4739",
+  "_from": "wrappy@1",
+  "_npmVersion": "2.0.0",
+  "_nodeVersion": "0.10.31",
+  "_npmUser": {
+    "name": "isaacs",
+    "email": "i@izs.me"
+  },
+  "maintainers": [
+    {
+      "name": "isaacs",
+      "email": "i@izs.me"
+    }
+  ],
+  "dist": {
+    "shasum": "1e65969965ccbc2db4548c6b84a6f2c5aedd4739",
+    "tarball": "http://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz"
+  },
+  "_resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/node_modules/once/node_modules/wrappy/test/basic.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/node_modules/once/node_modules/wrappy/test/basic.js
new file mode 100644
index 0000000..5ed0fcd
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/node_modules/once/node_modules/wrappy/test/basic.js
@@ -0,0 +1,51 @@
+var test = require('tap').test
+var wrappy = require('../wrappy.js')
+
+test('basic', function (t) {
+  function onceifier (cb) {
+    var called = false
+    return function () {
+      if (called) return
+      called = true
+      return cb.apply(this, arguments)
+    }
+  }
+  onceifier.iAmOnce = {}
+  var once = wrappy(onceifier)
+  t.equal(once.iAmOnce, onceifier.iAmOnce)
+
+  var called = 0
+  function boo () {
+    t.equal(called, 0)
+    called++
+  }
+  // has some rando property
+  boo.iAmBoo = true
+
+  var onlyPrintOnce = once(boo)
+
+  onlyPrintOnce() // prints 'boo'
+  onlyPrintOnce() // does nothing
+  t.equal(called, 1)
+
+  // random property is retained!
+  t.equal(onlyPrintOnce.iAmBoo, true)
+
+  var logs = []
+  var logwrap = wrappy(function (msg, cb) {
+    logs.push(msg + ' wrapping cb')
+    return function () {
+      logs.push(msg + ' before cb')
+      var ret = cb.apply(this, arguments)
+      logs.push(msg + ' after cb')
+    }
+  })
+
+  var c = logwrap('foo', function () {
+    t.same(logs, [ 'foo wrapping cb', 'foo before cb' ])
+  })
+  c()
+  t.same(logs, [ 'foo wrapping cb', 'foo before cb', 'foo after cb' ])
+
+  t.end()
+})
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/node_modules/once/node_modules/wrappy/wrappy.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/node_modules/once/node_modules/wrappy/wrappy.js
new file mode 100644
index 0000000..bb7e7d6
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/node_modules/once/node_modules/wrappy/wrappy.js
@@ -0,0 +1,33 @@
+// Returns a wrapper function that returns a wrapped callback
+// The wrapper function should do some stuff, and return a
+// presumably different callback function.
+// This makes sure that own properties are retained, so that
+// decorations and such are not lost along the way.
+module.exports = wrappy
+function wrappy (fn, cb) {
+  if (fn && cb) return wrappy(fn)(cb)
+
+  if (typeof fn !== 'function')
+    throw new TypeError('need wrapper function')
+
+  Object.keys(fn).forEach(function (k) {
+    wrapper[k] = fn[k]
+  })
+
+  return wrapper
+
+  function wrapper() {
+    var args = new Array(arguments.length)
+    for (var i = 0; i < args.length; i++) {
+      args[i] = arguments[i]
+    }
+    var ret = fn.apply(this, args)
+    var cb = args[args.length-1]
+    if (typeof ret === 'function' && ret !== cb) {
+      Object.keys(cb).forEach(function (k) {
+        ret[k] = cb[k]
+      })
+    }
+    return ret
+  }
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/node_modules/once/once.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/node_modules/once/once.js
new file mode 100644
index 0000000..2e1e721
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/node_modules/once/once.js
@@ -0,0 +1,21 @@
+var wrappy = require('wrappy')
+module.exports = wrappy(once)
+
+once.proto = once(function () {
+  Object.defineProperty(Function.prototype, 'once', {
+    value: function () {
+      return once(this)
+    },
+    configurable: true
+  })
+})
+
+function once (fn) {
+  var f = function () {
+    if (f.called) return f.value
+    f.called = true
+    return f.value = fn.apply(this, arguments)
+  }
+  f.called = false
+  return f
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/node_modules/once/package.json b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/node_modules/once/package.json
new file mode 100644
index 0000000..154e541
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/node_modules/once/package.json
@@ -0,0 +1,60 @@
+{
+  "name": "once",
+  "version": "1.3.1",
+  "description": "Run a function exactly one time",
+  "main": "once.js",
+  "directories": {
+    "test": "test"
+  },
+  "dependencies": {
+    "wrappy": "1"
+  },
+  "devDependencies": {
+    "tap": "~0.3.0"
+  },
+  "scripts": {
+    "test": "tap test/*.js"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/isaacs/once"
+  },
+  "keywords": [
+    "once",
+    "function",
+    "one",
+    "single"
+  ],
+  "author": {
+    "name": "Isaac Z. Schlueter",
+    "email": "i@izs.me",
+    "url": "http://blog.izs.me/"
+  },
+  "license": "BSD",
+  "gitHead": "c90ac02a74f433ce47f6938869e68dd6196ffc2c",
+  "bugs": {
+    "url": "https://github.com/isaacs/once/issues"
+  },
+  "homepage": "https://github.com/isaacs/once",
+  "_id": "once@1.3.1",
+  "_shasum": "f3f3e4da5b7d27b5c732969ee3e67e729457b31f",
+  "_from": "once@~1.3.0",
+  "_npmVersion": "2.0.0",
+  "_nodeVersion": "0.10.31",
+  "_npmUser": {
+    "name": "isaacs",
+    "email": "i@izs.me"
+  },
+  "maintainers": [
+    {
+      "name": "isaacs",
+      "email": "i@izs.me"
+    }
+  ],
+  "dist": {
+    "shasum": "f3f3e4da5b7d27b5c732969ee3e67e729457b31f",
+    "tarball": "http://registry.npmjs.org/once/-/once-1.3.1.tgz"
+  },
+  "_resolved": "https://registry.npmjs.org/once/-/once-1.3.1.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/node_modules/once/test/once.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/node_modules/once/test/once.js
new file mode 100644
index 0000000..c618360
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/node_modules/once/test/once.js
@@ -0,0 +1,23 @@
+var test = require('tap').test
+var once = require('../once.js')
+
+test('once', function (t) {
+  var f = 0
+  function fn (g) {
+    t.equal(f, 0)
+    f ++
+    return f + g + this
+  }
+  fn.ownProperty = {}
+  var foo = once(fn)
+  t.equal(fn.ownProperty, foo.ownProperty)
+  t.notOk(foo.called)
+  for (var i = 0; i < 1E3; i++) {
+    t.same(f, i === 0 ? 0 : 1)
+    var g = foo.call(1, 1)
+    t.ok(foo.called)
+    t.same(g, 3)
+    t.same(f, 1)
+  }
+  t.end()
+})
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/package.json b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/package.json
new file mode 100644
index 0000000..77aa744
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/package.json
@@ -0,0 +1,56 @@
+{
+  "name": "end-of-stream",
+  "version": "1.1.0",
+  "description": "Call a callback when a readable/writable/duplex stream has completed or failed.",
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/mafintosh/end-of-stream.git"
+  },
+  "dependencies": {
+    "once": "~1.3.0"
+  },
+  "scripts": {
+    "test": "node test.js"
+  },
+  "keywords": [
+    "stream",
+    "streams",
+    "callback",
+    "finish",
+    "close",
+    "end",
+    "wait"
+  ],
+  "bugs": {
+    "url": "https://github.com/mafintosh/end-of-stream/issues"
+  },
+  "homepage": "https://github.com/mafintosh/end-of-stream",
+  "main": "index.js",
+  "author": {
+    "name": "Mathias Buus",
+    "email": "mathiasbuus@gmail.com"
+  },
+  "license": "MIT",
+  "gitHead": "16120f1529961ffd6e48118d8d978c97444633d4",
+  "_id": "end-of-stream@1.1.0",
+  "_shasum": "e9353258baa9108965efc41cb0ef8ade2f3cfb07",
+  "_from": "end-of-stream@^1.0.0",
+  "_npmVersion": "1.4.23",
+  "_npmUser": {
+    "name": "mafintosh",
+    "email": "mathiasbuus@gmail.com"
+  },
+  "maintainers": [
+    {
+      "name": "mafintosh",
+      "email": "mathiasbuus@gmail.com"
+    }
+  ],
+  "dist": {
+    "shasum": "e9353258baa9108965efc41cb0ef8ade2f3cfb07",
+    "tarball": "http://registry.npmjs.org/end-of-stream/-/end-of-stream-1.1.0.tgz"
+  },
+  "directories": {},
+  "_resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.1.0.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/test.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/test.js
new file mode 100644
index 0000000..03cb93e
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/end-of-stream/test.js
@@ -0,0 +1,77 @@
+var assert = require('assert');
+var eos = require('./index');
+
+var expected = 8;
+var fs = require('fs');
+var cp = require('child_process');
+var net = require('net');
+
+var ws = fs.createWriteStream('/dev/null');
+eos(ws, function(err) {
+	expected--;
+	assert(!!err);
+	if (!expected) process.exit(0);
+});
+ws.close();
+
+var rs = fs.createReadStream('/dev/random');
+eos(rs, function(err) {
+	expected--;
+	assert(!!err);
+	if (!expected) process.exit(0);
+});
+rs.close();
+
+var rs = fs.createReadStream(__filename);
+eos(rs, function(err) {
+	expected--;
+	assert(!err);
+	if (!expected) process.exit(0);
+});
+rs.pipe(fs.createWriteStream('/dev/null'));
+
+var rs = fs.createReadStream(__filename);
+eos(rs, function(err) {
+	throw new Error('no go')
+})();
+rs.pipe(fs.createWriteStream('/dev/null'));
+
+var exec = cp.exec('echo hello world');
+eos(exec, function(err) {
+	expected--;
+	assert(!err);
+	if (!expected) process.exit(0);
+});
+
+var spawn = cp.spawn('echo', ['hello world']);
+eos(spawn, function(err) {
+	expected--;
+	assert(!err);
+	if (!expected) process.exit(0);
+});
+
+var socket = net.connect(50000);
+eos(socket, function(err) {
+	expected--;
+	assert(!!err);
+	if (!expected) process.exit(0);
+});
+
+var server = net.createServer(function(socket) {
+	eos(socket, function() {
+		expected--;
+		if (!expected) process.exit(0);
+	});
+	socket.destroy();
+}).listen(30000, function() {
+	var socket = net.connect(30000);
+	eos(socket, function() {
+		expected--;
+		if (!expected) process.exit(0);
+	});
+});
+
+setTimeout(function() {
+	assert(expected === 0);
+	process.exit(0);
+}, 1000);
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/xtend/.jshintrc b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/xtend/.jshintrc
new file mode 100644
index 0000000..77887b5
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/xtend/.jshintrc
@@ -0,0 +1,30 @@
+{
+    "maxdepth": 4,
+    "maxstatements": 200,
+    "maxcomplexity": 12,
+    "maxlen": 80,
+    "maxparams": 5,
+
+    "curly": true,
+    "eqeqeq": true,
+    "immed": true,
+    "latedef": false,
+    "noarg": true,
+    "noempty": true,
+    "nonew": true,
+    "undef": true,
+    "unused": "vars",
+    "trailing": true,
+
+    "quotmark": true,
+    "expr": true,
+    "asi": true,
+
+    "browser": false,
+    "esnext": true,
+    "devel": false,
+    "node": false,
+    "nonstandard": false,
+
+    "predef": ["require", "module", "__dirname", "__filename"]
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/xtend/.npmignore b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/xtend/.npmignore
new file mode 100644
index 0000000..3c3629e
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/xtend/.npmignore
@@ -0,0 +1 @@
+node_modules
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/xtend/LICENCE b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/xtend/LICENCE
new file mode 100644
index 0000000..1a14b43
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/xtend/LICENCE
@@ -0,0 +1,19 @@
+Copyright (c) 2012-2014 Raynos.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/xtend/Makefile b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/xtend/Makefile
new file mode 100644
index 0000000..d583fcf
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/xtend/Makefile
@@ -0,0 +1,4 @@
+browser:
+	node ./support/compile
+
+.PHONY: browser
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/xtend/README.md b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/xtend/README.md
new file mode 100644
index 0000000..093cb29
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/xtend/README.md
@@ -0,0 +1,32 @@
+# xtend
+
+[![browser support][3]][4]
+
+[![locked](http://badges.github.io/stability-badges/dist/locked.svg)](http://github.com/badges/stability-badges)
+
+Extend like a boss
+
+xtend is a basic utility library which allows you to extend an object by appending all of the properties from each object in a list. When there are identical properties, the right-most property takes precedence.
+
+## Examples
+
+```js
+var extend = require("xtend")
+
+// extend returns a new object. Does not mutate arguments
+var combination = extend({
+    a: "a",
+    b: 'c'
+}, {
+    b: "b"
+})
+// { a: "a", b: "b" }
+```
+
+## Stability status: Locked
+
+## MIT Licenced
+
+
+  [3]: http://ci.testling.com/Raynos/xtend.png
+  [4]: http://ci.testling.com/Raynos/xtend
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/xtend/immutable.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/xtend/immutable.js
new file mode 100644
index 0000000..5b76015
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/xtend/immutable.js
@@ -0,0 +1,17 @@
+module.exports = extend
+
+function extend() {
+    var target = {}
+
+    for (var i = 0; i < arguments.length; i++) {
+        var source = arguments[i]
+
+        for (var key in source) {
+            if (source.hasOwnProperty(key)) {
+                target[key] = source[key]
+            }
+        }
+    }
+
+    return target
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/xtend/mutable.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/xtend/mutable.js
new file mode 100644
index 0000000..a34475e
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/xtend/mutable.js
@@ -0,0 +1,15 @@
+module.exports = extend
+
+function extend(target) {
+    for (var i = 1; i < arguments.length; i++) {
+        var source = arguments[i]
+
+        for (var key in source) {
+            if (source.hasOwnProperty(key)) {
+                target[key] = source[key]
+            }
+        }
+    }
+
+    return target
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/xtend/package.json b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/xtend/package.json
new file mode 100644
index 0000000..3bf0363
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/xtend/package.json
@@ -0,0 +1,88 @@
+{
+  "name": "xtend",
+  "version": "4.0.0",
+  "description": "extend like a boss",
+  "keywords": [
+    "extend",
+    "merge",
+    "options",
+    "opts",
+    "object",
+    "array"
+  ],
+  "author": {
+    "name": "Raynos",
+    "email": "raynos2@gmail.com"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/Raynos/xtend.git"
+  },
+  "main": "immutable",
+  "scripts": {
+    "test": "node test"
+  },
+  "dependencies": {},
+  "devDependencies": {
+    "tape": "~1.1.0"
+  },
+  "homepage": "https://github.com/Raynos/xtend",
+  "contributors": [
+    {
+      "name": "Jake Verbaten"
+    },
+    {
+      "name": "Matt Esch"
+    }
+  ],
+  "bugs": {
+    "url": "https://github.com/Raynos/xtend/issues",
+    "email": "raynos2@gmail.com"
+  },
+  "licenses": [
+    {
+      "type": "MIT",
+      "url": "http://github.com/raynos/xtend/raw/master/LICENSE"
+    }
+  ],
+  "testling": {
+    "files": "test.js",
+    "browsers": [
+      "ie/7..latest",
+      "firefox/16..latest",
+      "firefox/nightly",
+      "chrome/22..latest",
+      "chrome/canary",
+      "opera/12..latest",
+      "opera/next",
+      "safari/5.1..latest",
+      "ipad/6.0..latest",
+      "iphone/6.0..latest"
+    ]
+  },
+  "engines": {
+    "node": ">=0.4"
+  },
+  "gitHead": "94a95d76154103290533b2c55ffa0fe4be16bfef",
+  "_id": "xtend@4.0.0",
+  "_shasum": "8bc36ff87aedbe7ce9eaf0bca36b2354a743840f",
+  "_from": "xtend@^4.0.0",
+  "_npmVersion": "1.4.15",
+  "_npmUser": {
+    "name": "raynos",
+    "email": "raynos2@gmail.com"
+  },
+  "maintainers": [
+    {
+      "name": "raynos",
+      "email": "raynos2@gmail.com"
+    }
+  ],
+  "dist": {
+    "shasum": "8bc36ff87aedbe7ce9eaf0bca36b2354a743840f",
+    "tarball": "http://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz"
+  },
+  "directories": {},
+  "_resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/xtend/test.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/xtend/test.js
new file mode 100644
index 0000000..3369d79
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/node_modules/xtend/test.js
@@ -0,0 +1,63 @@
+var test = require("tape")
+var extend = require("./")
+var mutableExtend = require("./mutable")
+
+test("merge", function(assert) {
+    var a = { a: "foo" }
+    var b = { b: "bar" }
+
+    assert.deepEqual(extend(a, b), { a: "foo", b: "bar" })
+    assert.end()
+})
+
+test("replace", function(assert) {
+    var a = { a: "foo" }
+    var b = { a: "bar" }
+
+    assert.deepEqual(extend(a, b), { a: "bar" })
+    assert.end()
+})
+
+test("undefined", function(assert) {
+    var a = { a: undefined }
+    var b = { b: "foo" }
+
+    assert.deepEqual(extend(a, b), { a: undefined, b: "foo" })
+    assert.deepEqual(extend(b, a), { a: undefined, b: "foo" })
+    assert.end()
+})
+
+test("handle 0", function(assert) {
+    var a = { a: "default" }
+    var b = { a: 0 }
+
+    assert.deepEqual(extend(a, b), { a: 0 })
+    assert.deepEqual(extend(b, a), { a: "default" })
+    assert.end()
+})
+
+test("is immutable", function (assert) {
+    var record = {}
+
+    extend(record, { foo: "bar" })
+    assert.equal(record.foo, undefined)
+    assert.end()
+})
+
+test("null as argument", function (assert) {
+    var a = { foo: "bar" }
+    var b = null
+    var c = void 0
+
+    assert.deepEqual(extend(b, a, c), { foo: "bar" })
+    assert.end()
+})
+
+test("mutable", function (assert) {
+    var a = { foo: "bar" }
+
+    mutableExtend(a, { bar: "baz" })
+
+    assert.equal(a.bar, "baz")
+    assert.end()
+})
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/pack.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/pack.js
new file mode 100644
index 0000000..eec3496
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/pack.js
@@ -0,0 +1,194 @@
+var util = require('util')
+var eos = require('end-of-stream')
+var headers = require('./headers')
+
+var Readable = require('readable-stream').Readable
+var Writable = require('readable-stream').Writable
+var PassThrough = require('readable-stream').PassThrough
+
+var END_OF_TAR = new Buffer(1024)
+END_OF_TAR.fill(0)
+
+var noop = function() {}
+
+var overflow = function(self, size) {
+  size &= 511
+  if (size) self.push(END_OF_TAR.slice(0, 512 - size))
+}
+
+var Sink = function(to) {
+  Writable.call(this)
+  this.written = 0
+  this._to = to
+  this._destroyed = false
+}
+
+util.inherits(Sink, Writable)
+
+Sink.prototype._write = function(data, enc, cb) {
+  this.written += data.length
+  if (this._to.push(data)) return cb()
+  this._to._drain = cb
+}
+
+Sink.prototype.destroy = function() {
+  if (this._destroyed) return
+  this._destroyed = true
+  this.emit('close')
+}
+
+var Void = function() {
+  Writable.call(this)
+  this._destroyed = false
+}
+
+util.inherits(Void, Writable)
+
+Void.prototype._write = function(data, enc, cb) {
+  cb(new Error('No body allowed for this entry'))
+}
+
+Void.prototype.destroy = function() {
+  if (this._destroyed) return
+  this._destroyed = true
+  this.emit('close')
+}
+
+var Pack = function(opts) {
+  if (!(this instanceof Pack)) return new Pack(opts)
+  Readable.call(this, opts)
+
+  this._drain = noop
+  this._finalized = false
+  this._finalizing = false
+  this._destroyed = false
+  this._stream = null
+}
+
+util.inherits(Pack, Readable)
+
+Pack.prototype.entry = function(header, buffer, callback) {
+  if (this._stream) throw new Error('already piping an entry')
+  if (this._finalized || this._destroyed) return
+
+  if (typeof buffer === 'function') {
+    callback = buffer
+    buffer = null
+  }
+
+  if (!callback) callback = noop
+
+  var self = this
+
+  if (!header.size)  header.size = 0
+  if (!header.type)  header.type = 'file'
+  if (!header.mode)  header.mode = header.type === 'directory' ? 0755 : 0644
+  if (!header.uid)   header.uid = 0
+  if (!header.gid)   header.gid = 0
+  if (!header.mtime) header.mtime = new Date()
+
+  if (typeof buffer === 'string') buffer = new Buffer(buffer)
+  if (Buffer.isBuffer(buffer)) {
+    header.size = buffer.length
+    this._encode(header)
+    this.push(buffer)
+    overflow(self, header.size)
+    process.nextTick(callback)
+    return new Void()
+  }
+  if (header.type !== 'file' && header.type !== 'contigious-file') {
+    this._encode(header)
+    process.nextTick(callback)
+    return new Void()
+  }
+
+  var sink = new Sink(this)
+
+  this._encode(header)
+  this._stream = sink
+
+  eos(sink, function(err) {
+    self._stream = null
+
+    if (err) { // stream was closed
+      self.destroy()
+      return callback(err)
+    }
+
+    if (sink.written !== header.size) { // corrupting tar
+      self.destroy()
+      return callback(new Error('size mismatch'))
+    }
+
+    overflow(self, header.size)
+    if (self._finalizing) self.finalize()
+    callback()
+  })
+
+  return sink
+}
+
+Pack.prototype.finalize = function() {
+  if (this._stream) {
+    this._finalizing = true
+    return
+  }
+
+  if (this._finalized) return
+  this._finalized = true
+  this.push(END_OF_TAR)
+  this.push(null)
+}
+
+Pack.prototype.destroy = function(err) {
+  if (this._destroyed) return
+  this._destroyed = true
+
+  if (err) this.emit('error', err)
+  this.emit('close')
+  if (this._stream && this._stream.destroy) this._stream.destroy()
+}
+
+Pack.prototype._encode = function(header) {
+  var buf = headers.encode(header)
+  if (buf) this.push(buf)
+  else this._encodePax(header)
+}
+
+Pack.prototype._encodePax = function(header) {
+  var paxHeader = headers.encodePax({
+    name: header.name,
+    linkname: header.linkname
+  })
+
+  var newHeader = {
+    name: 'PaxHeader',
+    mode: header.mode,
+    uid: header.uid,
+    gid: header.gid,
+    size: paxHeader.length,
+    mtime: header.mtime,
+    type: 'pax-header',
+    linkname: header.linkname && 'PaxHeader',
+    uname: header.uname,
+    gname: header.gname,
+    devmajor: header.devmajor,
+    devminor: header.devminor
+  }
+
+  this.push(headers.encode(newHeader))
+  this.push(paxHeader)
+  overflow(this, paxHeader.length)
+
+  newHeader.size = header.size
+  newHeader.type = header.type
+  this.push(headers.encode(newHeader))
+}
+
+Pack.prototype._read = function(n) {
+  var drain = this._drain
+  this._drain = noop
+  drain()
+}
+
+module.exports = Pack
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/package.json b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/package.json
new file mode 100644
index 0000000..f08ee6c
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/package.json
@@ -0,0 +1,75 @@
+{
+  "name": "tar-stream",
+  "version": "0.4.7",
+  "description": "tar-stream is a streaming tar parser and generator and nothing else. It is streams2 and operates purely using streams which means you can easily extract/parse tarballs without ever hitting the file system.",
+  "repository": {
+    "type": "git",
+    "url": "git://github.com:mafintosh/tar-stream.git"
+  },
+  "author": {
+    "name": "Mathias Buus",
+    "email": "mathiasbuus@gmail.com"
+  },
+  "engines": {
+    "node": ">= 0.8.0"
+  },
+  "dependencies": {
+    "bl": "^0.9.0",
+    "end-of-stream": "^1.0.0",
+    "readable-stream": "^1.0.27-1",
+    "xtend": "^4.0.0"
+  },
+  "devDependencies": {
+    "concat-stream": "^1.4.6",
+    "tape": "^2.14.0"
+  },
+  "scripts": {
+    "test": "tape test/*.js"
+  },
+  "keywords": [
+    "tar",
+    "tarball",
+    "parse",
+    "parser",
+    "generate",
+    "generator",
+    "stream",
+    "stream2",
+    "streams",
+    "streams2",
+    "streaming",
+    "pack",
+    "extract",
+    "modify"
+  ],
+  "bugs": {
+    "url": "https://github.com/mafintosh/tar-stream/issues"
+  },
+  "homepage": "https://github.com/mafintosh/tar-stream",
+  "main": "index.js",
+  "directories": {
+    "test": "test"
+  },
+  "license": "MIT",
+  "gitHead": "3bd4c7a0c34e4357105940baf1ff18b1f05a041b",
+  "_id": "tar-stream@0.4.7",
+  "_shasum": "1f1d2ce9ebc7b42765243ca0e8f1b7bfda0aadcd",
+  "_from": "tar-stream@~0.4.0",
+  "_npmVersion": "1.4.23",
+  "_npmUser": {
+    "name": "mafintosh",
+    "email": "mathiasbuus@gmail.com"
+  },
+  "maintainers": [
+    {
+      "name": "mafintosh",
+      "email": "mathiasbuus@gmail.com"
+    }
+  ],
+  "dist": {
+    "shasum": "1f1d2ce9ebc7b42765243ca0e8f1b7bfda0aadcd",
+    "tarball": "http://registry.npmjs.org/tar-stream/-/tar-stream-0.4.7.tgz"
+  },
+  "_resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-0.4.7.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/test/extract.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/test/extract.js
new file mode 100644
index 0000000..e758b27
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/test/extract.js
@@ -0,0 +1,437 @@
+var test = require('tape');
+var tar = require('../index');
+var fixtures = require('./fixtures');
+var concat = require('concat-stream');
+var fs = require('fs');
+
+var clamp = function(index, len, defaultValue) {
+	if (typeof index !== 'number') return defaultValue;
+	index = ~~index;  // Coerce to integer.
+	if (index >= len) return len;
+	if (index >= 0) return index;
+	index += len;
+	if (index >= 0) return index;
+	return 0;
+};
+
+test('one-file', function(t) {
+	t.plan(3);
+
+	var extract = tar.extract();
+	var noEntries = false;
+
+	extract.on('entry', function(header, stream, callback) {
+		t.deepEqual(header, {
+			name: 'test.txt',
+			mode: 0644,
+			uid: 501,
+			gid: 20,
+			size: 12,
+			mtime: new Date(1387580181000),
+			type: 'file',
+			linkname: null,
+			uname: 'maf',
+			gname: 'staff',
+			devmajor: 0,
+			devminor: 0
+		});
+
+		stream.pipe(concat(function(data) {
+			noEntries = true;
+			t.same(data.toString(), 'hello world\n');
+			callback();
+		}));
+	});
+
+	extract.on('finish', function() {
+		t.ok(noEntries);
+	});
+
+	extract.end(fs.readFileSync(fixtures.ONE_FILE_TAR));
+});
+
+test('chunked-one-file', function(t) {
+	t.plan(3);
+
+	var extract = tar.extract();
+	var noEntries = false;
+
+	extract.on('entry', function(header, stream, callback) {
+		t.deepEqual(header, {
+			name: 'test.txt',
+			mode: 0644,
+			uid: 501,
+			gid: 20,
+			size: 12,
+			mtime: new Date(1387580181000),
+			type: 'file',
+			linkname: null,
+			uname: 'maf',
+			gname: 'staff',
+			devmajor: 0,
+			devminor: 0
+		});
+
+		stream.pipe(concat(function(data) {
+			noEntries = true;
+			t.same(data.toString(), 'hello world\n');
+			callback();
+		}));
+	});
+
+	extract.on('finish', function() {
+		t.ok(noEntries);
+	});
+
+	var b = fs.readFileSync(fixtures.ONE_FILE_TAR);
+
+	for (var i = 0; i < b.length; i += 321) {
+		extract.write(b.slice(i, clamp(i+321, b.length, b.length)));
+	}
+	extract.end();
+});
+
+
+test('multi-file', function(t) {
+	t.plan(5);
+
+	var extract = tar.extract();
+	var noEntries = false;
+
+	var onfile1 = function(header, stream, callback) {
+		t.deepEqual(header, {
+			name: 'file-1.txt',
+			mode: 0644,
+			uid: 501,
+			gid: 20,
+			size: 12,
+			mtime: new Date(1387580181000),
+			type: 'file',
+			linkname: null,
+			uname: 'maf',
+			gname: 'staff',
+			devmajor: 0,
+			devminor: 0
+		});
+
+		extract.on('entry', onfile2);
+		stream.pipe(concat(function(data) {
+			t.same(data.toString(), 'i am file-1\n');
+			callback();
+		}));
+	};
+
+	var onfile2 = function(header, stream, callback) {
+		t.deepEqual(header, {
+			name: 'file-2.txt',
+			mode: 0644,
+			uid: 501,
+			gid: 20,
+			size: 12,
+			mtime: new Date(1387580181000),
+			type: 'file',
+			linkname: null,
+			uname: 'maf',
+			gname: 'staff',
+			devmajor: 0,
+			devminor: 0
+		});
+
+		stream.pipe(concat(function(data) {
+			noEntries = true;
+			t.same(data.toString(), 'i am file-2\n');
+			callback();
+		}));
+	};
+
+	extract.once('entry', onfile1);
+
+	extract.on('finish', function() {
+		t.ok(noEntries);
+	});
+
+	extract.end(fs.readFileSync(fixtures.MULTI_FILE_TAR));
+});
+
+test('chunked-multi-file', function(t) {
+	t.plan(5);
+
+	var extract = tar.extract();
+	var noEntries = false;
+
+	var onfile1 = function(header, stream, callback) {
+		t.deepEqual(header, {
+			name: 'file-1.txt',
+			mode: 0644,
+			uid: 501,
+			gid: 20,
+			size: 12,
+			mtime: new Date(1387580181000),
+			type: 'file',
+			linkname: null,
+			uname: 'maf',
+			gname: 'staff',
+			devmajor: 0,
+			devminor: 0
+		});
+
+		extract.on('entry', onfile2);
+		stream.pipe(concat(function(data) {
+			t.same(data.toString(), 'i am file-1\n');
+			callback();
+		}));
+	};
+
+	var onfile2 = function(header, stream, callback) {
+		t.deepEqual(header, {
+			name: 'file-2.txt',
+			mode: 0644,
+			uid: 501,
+			gid: 20,
+			size: 12,
+			mtime: new Date(1387580181000),
+			type: 'file',
+			linkname: null,
+			uname: 'maf',
+			gname: 'staff',
+			devmajor: 0,
+			devminor: 0
+		});
+
+		stream.pipe(concat(function(data) {
+			noEntries = true;
+			t.same(data.toString(), 'i am file-2\n');
+			callback();
+		}));
+	};
+
+	extract.once('entry', onfile1);
+
+	extract.on('finish', function() {
+		t.ok(noEntries);
+	});
+
+	var b = fs.readFileSync(fixtures.MULTI_FILE_TAR);
+	for (var i = 0; i < b.length; i += 321) {
+		extract.write(b.slice(i, clamp(i+321, b.length, b.length)));
+	}
+	extract.end();
+});
+
+test('types', function(t) {
+	t.plan(3);
+
+	var extract = tar.extract();
+	var noEntries = false;
+
+	var ondir = function(header, stream, callback) {
+		t.deepEqual(header, {
+			name: 'directory',
+			mode: 0755,
+			uid: 501,
+			gid: 20,
+			size: 0,
+			mtime: new Date(1387580181000),
+			type: 'directory',
+			linkname: null,
+			uname: 'maf',
+			gname: 'staff',
+			devmajor: 0,
+			devminor: 0
+		});
+		stream.on('data', function() {
+			t.ok(false);
+		});
+		extract.once('entry', onlink);
+		callback();
+	};
+
+	var onlink = function(header, stream, callback) {
+		t.deepEqual(header, {
+			name: 'directory-link',
+			mode: 0755,
+			uid: 501,
+			gid: 20,
+			size: 0,
+			mtime: new Date(1387580181000),
+			type: 'symlink',
+			linkname: 'directory',
+			uname: 'maf',
+			gname: 'staff',
+			devmajor: 0,
+			devminor: 0
+		});
+		stream.on('data', function() {
+			t.ok(false);
+		});
+		noEntries = true;
+		callback();
+	};
+
+	extract.once('entry', ondir);
+
+	extract.on('finish', function() {
+		t.ok(noEntries);
+	});
+
+	extract.end(fs.readFileSync(fixtures.TYPES_TAR));
+});
+
+test('long-name', function(t) {
+	t.plan(3);
+
+	var extract = tar.extract();
+	var noEntries = false;
+
+	extract.on('entry', function(header, stream, callback) {
+		t.deepEqual(header, {
+			name: 'my/file/is/longer/than/100/characters/and/should/use/the/prefix/header/foobarbaz/foobarbaz/foobarbaz/foobarbaz/foobarbaz/foobarbaz/filename.txt',
+			mode: 0644,
+			uid: 501,
+			gid: 20,
+			size: 16,
+			mtime: new Date(1387580181000),
+			type: 'file',
+			linkname: null,
+			uname: 'maf',
+			gname: 'staff',
+			devmajor: 0,
+			devminor: 0
+		});
+
+		stream.pipe(concat(function(data) {
+			noEntries = true;
+			t.same(data.toString(), 'hello long name\n');
+			callback();
+		}));
+	});
+
+	extract.on('finish', function() {
+		t.ok(noEntries);
+	});
+
+	extract.end(fs.readFileSync(fixtures.LONG_NAME_TAR));
+});
+
+test('unicode-bsd', function(t) { // can unpack a bsdtar unicoded tarball
+	t.plan(3);
+
+	var extract = tar.extract();
+	var noEntries = false;
+
+	extract.on('entry', function(header, stream, callback) {
+		t.deepEqual(header, {
+			name: 'høllø.txt',
+			mode: 0644,
+			uid: 501,
+			gid: 20,
+			size: 4,
+			mtime: new Date(1387588646000),
+			type: 'file',
+			linkname: null,
+			uname: 'maf',
+			gname: 'staff',
+			devmajor: 0,
+			devminor: 0
+		});
+
+		stream.pipe(concat(function(data) {
+			noEntries = true;
+			t.same(data.toString(), 'hej\n');
+			callback();
+		}));
+	});
+
+	extract.on('finish', function() {
+		t.ok(noEntries);
+	});
+
+	extract.end(fs.readFileSync(fixtures.UNICODE_BSD_TAR));
+});
+
+test('unicode', function(t) { // can unpack a bsdtar unicoded tarball
+	t.plan(3);
+
+	var extract = tar.extract();
+	var noEntries = false;
+
+	extract.on('entry', function(header, stream, callback) {
+		t.deepEqual(header, {
+			name: 'høstål.txt',
+			mode: 0644,
+			uid: 501,
+			gid: 20,
+			size: 8,
+			mtime: new Date(1387580181000),
+			type: 'file',
+			linkname: null,
+			uname: 'maf',
+			gname: 'staff',
+			devmajor: 0,
+			devminor: 0
+		});
+
+		stream.pipe(concat(function(data) {
+			noEntries = true;
+			t.same(data.toString(), 'høllø\n');
+			callback();
+		}));
+	});
+
+	extract.on('finish', function() {
+		t.ok(noEntries);
+	});
+
+	extract.end(fs.readFileSync(fixtures.UNICODE_TAR));
+});
+
+test('name-is-100', function(t) {
+	t.plan(3);
+
+	var extract = tar.extract();
+
+	extract.on('entry', function(header, stream, callback) {
+		t.same(header.name.length, 100);
+
+		stream.pipe(concat(function(data) {
+			t.same(data.toString(), 'hello\n');
+			callback();
+		}));
+	});
+
+	extract.on('finish', function() {
+		t.ok(true);
+	});
+
+	extract.end(fs.readFileSync(fixtures.NAME_IS_100_TAR));
+});
+
+test('invalid-file', function(t) {
+	t.plan(1);
+
+	var extract = tar.extract();
+
+	extract.on('error', function(err) {
+		t.ok(!!err);
+		extract.destroy();
+	});
+
+	extract.end(fs.readFileSync(fixtures.INVALID_TGZ));
+});
+
+test('space prefixed', function(t) {
+	t.plan(5);
+
+	var extract = tar.extract();
+
+	extract.on('entry', function(header, stream, callback) {
+		t.ok(true)
+		callback();
+	});
+
+	extract.on('finish', function() {
+		t.ok(true);
+	});
+
+	extract.end(fs.readFileSync(fixtures.SPACE_TAR_GZ));
+});
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/test/fixtures/index.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/test/fixtures/index.js
new file mode 100644
index 0000000..0c4b283
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/test/fixtures/index.js
@@ -0,0 +1,11 @@
+var path = require('path');
+
+exports.ONE_FILE_TAR = path.join(__dirname, 'one-file.tar');
+exports.MULTI_FILE_TAR = path.join(__dirname, 'multi-file.tar');
+exports.TYPES_TAR = path.join(__dirname, 'types.tar');
+exports.LONG_NAME_TAR = path.join(__dirname, 'long-name.tar');
+exports.UNICODE_BSD_TAR = path.join(__dirname, 'unicode-bsd.tar');
+exports.UNICODE_TAR = path.join(__dirname, 'unicode.tar');
+exports.NAME_IS_100_TAR = path.join(__dirname, 'name-is-100.tar');
+exports.INVALID_TGZ = path.join(__dirname, 'invalid.tgz');
+exports.SPACE_TAR_GZ = path.join(__dirname, 'space.tar');
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/test/fixtures/invalid.tgz b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/test/fixtures/invalid.tgz
new file mode 100644
index 0000000..ea35ec4
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/test/fixtures/invalid.tgz
Binary files differ
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/test/fixtures/long-name.tar b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/test/fixtures/long-name.tar
new file mode 100644
index 0000000..cf93981
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/test/fixtures/long-name.tar
Binary files differ
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/test/fixtures/multi-file.tar b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/test/fixtures/multi-file.tar
new file mode 100644
index 0000000..6dabdf6
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/test/fixtures/multi-file.tar
Binary files differ
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/test/fixtures/name-is-100.tar b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/test/fixtures/name-is-100.tar
new file mode 100644
index 0000000..299b2e8
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/test/fixtures/name-is-100.tar
Binary files differ
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/test/fixtures/one-file.tar b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/test/fixtures/one-file.tar
new file mode 100644
index 0000000..8d4ac28
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/test/fixtures/one-file.tar
Binary files differ
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/test/fixtures/space.tar b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/test/fixtures/space.tar
new file mode 100644
index 0000000..0bd7cea
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/test/fixtures/space.tar
Binary files differ
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/test/fixtures/types.tar b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/test/fixtures/types.tar
new file mode 100644
index 0000000..197af7b
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/test/fixtures/types.tar
Binary files differ
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/test/fixtures/unicode-bsd.tar b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/test/fixtures/unicode-bsd.tar
new file mode 100644
index 0000000..2f74b5f
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/test/fixtures/unicode-bsd.tar
Binary files differ
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/test/fixtures/unicode.tar b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/test/fixtures/unicode.tar
new file mode 100644
index 0000000..ab7dbad
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/test/fixtures/unicode.tar
Binary files differ
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/test/pack.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/test/pack.js
new file mode 100644
index 0000000..415a9bd
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/tar-stream/test/pack.js
@@ -0,0 +1,144 @@
+var test = require('tape');
+var tar = require('../index');
+var fixtures = require('./fixtures');
+var concat = require('concat-stream');
+var fs = require('fs');
+
+test('one-file', function(t) {
+	t.plan(2);
+
+	var pack = tar.pack();
+
+	pack.entry({
+		name:'test.txt',
+		mtime:new Date(1387580181000),
+		mode:0644,
+		uname:'maf',
+		gname:'staff',
+		uid:501,
+		gid:20
+	}, 'hello world\n');
+
+	pack.finalize();
+
+	pack.pipe(concat(function(data) {
+		t.same(data.length & 511, 0);
+		t.deepEqual(data, fs.readFileSync(fixtures.ONE_FILE_TAR));
+	}));
+});
+
+test('multi-file', function(t) {
+	t.plan(2);
+
+	var pack = tar.pack();
+
+	pack.entry({
+		name:'file-1.txt',
+		mtime:new Date(1387580181000),
+		mode:0644,
+		uname:'maf',
+		gname:'staff',
+		uid:501,
+		gid:20
+	}, 'i am file-1\n');
+
+	pack.entry({
+		name:'file-2.txt',
+		mtime:new Date(1387580181000),
+		mode:0644,
+		size:12,
+		uname:'maf',
+		gname:'staff',
+		uid:501,
+		gid:20
+	}).end('i am file-2\n');
+
+	pack.finalize();
+
+	pack.pipe(concat(function(data) {
+		t.same(data.length & 511, 0);
+		t.deepEqual(data, fs.readFileSync(fixtures.MULTI_FILE_TAR));
+	}));
+});
+
+test('types', function(t) {
+	t.plan(2);
+	var pack = tar.pack();
+
+	pack.entry({
+		name:'directory',
+		mtime:new Date(1387580181000),
+		type:'directory',
+		mode:0755,
+		uname:'maf',
+		gname:'staff',
+		uid:501,
+		gid:20
+	});
+
+	pack.entry({
+		name:'directory-link',
+		mtime:new Date(1387580181000),
+		type:'symlink',
+		linkname: 'directory',
+		mode:0755,
+		uname:'maf',
+		gname:'staff',
+		uid:501,
+		gid:20
+	});
+
+	pack.finalize();
+
+	pack.pipe(concat(function(data) {
+		t.equal(data.length & 511, 0);
+		t.deepEqual(data, fs.readFileSync(fixtures.TYPES_TAR));
+	}));
+
+});
+
+test('long-name', function(t) {
+	t.plan(2);
+	var pack = tar.pack();
+
+	pack.entry({
+		name:'my/file/is/longer/than/100/characters/and/should/use/the/prefix/header/foobarbaz/foobarbaz/foobarbaz/foobarbaz/foobarbaz/foobarbaz/filename.txt',
+		mtime:new Date(1387580181000),
+		type:'file',
+		mode:0644,
+		uname:'maf',
+		gname:'staff',
+		uid:501,
+		gid:20
+	}, 'hello long name\n');
+
+	pack.finalize();
+
+	pack.pipe(concat(function(data) {
+		t.equal(data.length & 511, 0);
+		t.deepEqual(data, fs.readFileSync(fixtures.LONG_NAME_TAR));
+	}));
+});
+
+test('unicode', function(t) {
+	t.plan(2);
+	var pack = tar.pack();
+
+	pack.entry({
+		name:'høstål.txt',
+		mtime:new Date(1387580181000),
+		type:'file',
+		mode:0644,
+		uname:'maf',
+		gname:'staff',
+		uid:501,
+		gid:20
+	}, 'høllø\n');
+
+	pack.finalize();
+
+	pack.pipe(concat(function(data) {
+		t.equal(data.length & 511, 0);
+		t.deepEqual(data, fs.readFileSync(fixtures.UNICODE_TAR));
+	}));
+});
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/LICENSE-MIT b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/LICENSE-MIT
new file mode 100644
index 0000000..819b403
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/LICENSE-MIT
@@ -0,0 +1,22 @@
+Copyright (c) 2014 Chris Talkington, contributors.

+

+Permission is hereby granted, free of charge, to any person

+obtaining a copy of this software and associated documentation

+files (the "Software"), to deal in the Software without

+restriction, including without limitation the rights to use,

+copy, modify, merge, publish, distribute, sublicense, and/or sell

+copies of the Software, and to permit persons to whom the

+Software is furnished to do so, subject to the following

+conditions:

+

+The above copyright notice and this permission notice shall be

+included in all copies or substantial portions of the Software.

+

+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,

+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES

+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND

+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT

+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,

+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING

+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR

+OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/README.md b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/README.md
new file mode 100644
index 0000000..e4ec56c
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/README.md
@@ -0,0 +1,105 @@
+# zip-stream v0.4.1 [![Build Status](https://travis-ci.org/ctalkington/node-zip-stream.svg?branch=master)](https://travis-ci.org/ctalkington/node-zip-stream)

+

+zip-stream is a streaming zip archive generator based on the `ZipArchiveOutputStream` prototype found in the [compress-commons](https://www.npmjs.org/package/compress-commons) project.

+

+It was originally created to be a successor to [zipstream](https://npmjs.org/package/zipstream).

+

+[![NPM](https://nodei.co/npm/zip-stream.png)](https://nodei.co/npm/zip-stream/)

+

+### Install

+

+```bash

+npm install zip-stream --save

+```

+

+You can also use `npm install https://github.com/ctalkington/node-zip-stream/archive/master.tar.gz` to test upcoming versions.

+

+### Usage

+

+This module is meant to be wrapped internally by other modules and therefore lacks any queue management. This means you have to wait until the previous entry has been fully consumed to add another. Nested callbacks should be used to add multiple entries. There are modules like [async](https://npmjs.org/package/async) that ease the so called "callback hell".

+

+If you want a module that handles entry queueing and much more, you should check out [archiver](https://npmjs.org/package/archiver) which uses this module internally.

+

+```js

+var packer = require('zip-stream');

+var archive = new packer(); // OR new packer(options)

+

+archive.on('error', function(err) {

+  throw err;

+});

+

+// pipe archive where you want it (ie fs, http, etc)

+// listen to the destination's end, close, or finish event

+

+archive.entry('string contents', { name: 'string.txt' }, function(err, entry) {

+  if (err) throw err;

+  archive.entry(null, { name: 'directory/' }, function(err, entry) {

+    if (err) throw err;

+    archive.finish();

+  });

+});

+```

+

+### Instance API

+

+#### getBytesWritten()

+

+Returns the current number of bytes written to this stream.

+

+#### entry(input, data, callback(err, data))

+

+Appends an input source (text string, buffer, or stream) to the instance. When the instance has received, processed, and emitted the input, the callback is fired.

+

+#### finish()

+

+Finalizes the instance. You should listen to the destination stream's `end`/`close`/`finish` event to know when all output has been safely consumed. (`finalize` is aliased for back-compat)

+

+### Instance Options

+

+#### comment `string`

+

+Sets the zip comment.

+

+#### store `boolean`

+

+If true, all entry contents will be archived without compression by default.

+

+#### zlib `object`

+

+Passed to node's [zlib](http://nodejs.org/api/zlib.html#zlib_options) module to control compression. Options may vary by node version.

+

+### Entry Data

+

+#### name `string` `required`

+

+Sets the entry name including internal path.

+

+#### type `string`

+

+Sets the entry type. Defaults to `file` or `directory` if name ends with trailing slash.

+

+#### date `string|Date`

+

+Sets the entry date. This can be any valid date string or instance. Defaults to current time in locale.

+

+#### store `boolean`

+

+If true, entry contents will be archived without compression.

+

+#### comment `string`

+

+Sets the entry comment.

+

+#### mode `number`

+

+Sets the entry permissions.

+

+## Things of Interest

+

+- [Releases](https://github.com/ctalkington/node-zip-stream/releases)

+- [Contributing](https://github.com/ctalkington/node-zip-stream/blob/master/CONTRIBUTING.md)

+- [MIT License](https://github.com/ctalkington/node-zip-stream/blob/master/LICENSE-MIT)

+

+## Credits

+

+Concept inspired by Antoine van Wel's [zipstream](https://npmjs.org/package/zipstream) module, which is no longer being updated.
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/lib/util/index.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/lib/util/index.js
new file mode 100644
index 0000000..0b5a468
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/lib/util/index.js
@@ -0,0 +1,103 @@
+/**

+ * node-zip-stream

+ *

+ * Copyright (c) 2014 Chris Talkington, contributors.

+ * Licensed under the MIT license.

+ * https://github.com/ctalkington/node-zip-stream/blob/master/LICENSE-MIT

+ */

+var fs = require('fs');

+var path = require('path');

+

+var Stream = require('stream').Stream;

+var PassThrough = require('readable-stream').PassThrough;

+

+var _ = require('lodash');

+

+var util = module.exports = {};

+

+util.convertDateTimeDos = function(input) {

+  return new Date(

+    ((input >> 25) & 0x7f) + 1980,

+    ((input >> 21) & 0x0f) - 1,

+    (input >> 16) & 0x1f,

+    (input >> 11) & 0x1f,

+    (input >> 5) & 0x3f,

+    (input & 0x1f) << 1

+  );

+};

+

+util.dateify = function(dateish) {

+  dateish = dateish || new Date();

+

+  if (dateish instanceof Date) {

+    dateish = dateish;

+  } else if (typeof dateish === 'string') {

+    dateish = new Date(dateish);

+  } else {

+    dateish = new Date();

+  }

+

+  return dateish;

+};

+

+// this is slightly different from lodash version

+util.defaults = function(object, source, guard) {

+  var args = arguments;

+  args[0] = args[0] || {};

+

+  return _.defaults.apply(_, args);

+};

+

+util.dosDateTime = function(d, utc) {

+  d = (d instanceof Date) ? d : util.dateify(d);

+  utc = utc || false;

+

+  var year = utc ? d.getUTCFullYear() : d.getFullYear();

+

+  if (year < 1980) {

+    return 2162688; // 1980-1-1 00:00:00

+  } else if (year >= 2044) {

+    return 2141175677; // 2043-12-31 23:59:58

+  }

+

+  var val = {

+    year: year,

+    month: utc ? d.getUTCMonth() : d.getMonth(),

+    date: utc ? d.getUTCDate() : d.getDate(),

+    hours: utc ? d.getUTCHours() : d.getHours(),

+    minutes: utc ? d.getUTCMinutes() : d.getMinutes(),

+    seconds: utc ? d.getUTCSeconds() : d.getSeconds()

+  };

+

+  return ((val.year-1980) << 25) | ((val.month+1) << 21) | (val.date << 16) |

+    (val.hours << 11) | (val.minutes << 5) | (val.seconds / 2);

+};

+

+util.isStream = function(source) {

+  return source instanceof Stream;

+};

+

+util.normalizeInputSource = function(source) {

+  if (source === null) {

+    return new Buffer(0);

+  } else if (typeof source === 'string') {

+    return new Buffer(source);

+  } else if (util.isStream(source) && !source._readableState) {

+    var normalized = new PassThrough();

+    source.pipe(normalized);

+

+    return normalized;

+  }

+

+  return source;

+};

+

+util.sanitizePath = function() {

+  var filepath = path.join.apply(path, arguments);

+  return filepath.replace(/\\/g, '/').replace(/:/g, '').replace(/^\/+/, '');

+};

+

+util.unixifyPath = function() {

+  var filepath = path.join.apply(path, arguments);

+  return filepath.replace(/\\/g, '/');

+};
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/lib/zip-stream.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/lib/zip-stream.js
new file mode 100644
index 0000000..d13960b
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/lib/zip-stream.js
@@ -0,0 +1,110 @@
+/**

+ * node-zip-stream

+ *

+ * Copyright (c) 2014 Chris Talkington, contributors.

+ * Licensed under the MIT license.

+ * https://github.com/ctalkington/node-zip-stream/blob/master/LICENSE-MIT

+ */

+var inherits = require('util').inherits;

+

+var ZipArchiveOutputStream = require('compress-commons').ZipArchiveOutputStream;

+var ZipArchiveEntry = require('compress-commons').ZipArchiveEntry;

+

+var util = require('./util');

+

+var ZipStream = module.exports = function(options) {

+  if (!(this instanceof ZipStream)) {

+    return new ZipStream(options);

+  }

+

+  options = this.options = options || {};

+  options.zlib = options.zlib || {};

+

+  ZipArchiveOutputStream.call(this, options);

+

+  if (typeof options.level === 'number' && options.level >= 0) {

+    options.zlib.level = options.level;

+    delete options.level;

+  }

+

+  if (options.zlib.level && options.zlib.level === 0) {

+    options.store = true;

+  }

+

+  if (options.comment && options.comment.length > 0) {

+    this.setComment(options.comment);

+  }

+};

+

+inherits(ZipStream, ZipArchiveOutputStream);

+

+ZipStream.prototype._normalizeFileData = function(data) {

+  data = util.defaults(data, {

+    type: 'file',

+    name: null,

+    date: null,

+    mode: null,

+    store: this.options.store,

+    comment: ''

+  });

+

+  var isDir = data.type === 'directory';

+

+  if (data.name) {

+    data.name = util.sanitizePath(data.name);

+

+    if (data.name.slice(-1) === '/') {

+      isDir = true;

+      data.type = 'directory';

+    } else if (isDir) {

+      data.name += '/';

+    }

+  }

+

+  if (isDir) {

+    data.store = true;

+  }

+

+  data.date = util.dateify(data.date);

+

+  return data;

+};

+

+ZipStream.prototype.entry = function(source, data, callback) {

+  if (typeof callback !== 'function') {

+    callback = this._emitErrorCallback.bind(this);

+  }

+

+  data = this._normalizeFileData(data);

+

+  if (data.type !== 'file' && data.type !== 'directory') {

+    callback(new Error(data.type + ' entries not currently supported'));

+    return;

+  }

+

+  if (typeof data.name !== 'string' || data.name.length === 0) {

+    callback(new Error('entry name must be a non-empty string value'));

+    return;

+  }

+

+  var entry = new ZipArchiveEntry(data.name);

+  entry.setTime(data.date);

+

+  if (data.store) {

+    entry.setMethod(0);

+  }

+

+  if (data.comment.length > 0) {

+    entry.setComment(data.comment);

+  }

+

+  if (typeof data.mode === 'number') {

+    entry.setUnixMode(data.mode);

+  }

+

+  return ZipArchiveOutputStream.prototype.entry.call(this, entry, source, callback);

+};

+

+ZipStream.prototype.finalize = function() {

+  this.finish();

+};
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/LICENSE-MIT b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/LICENSE-MIT
new file mode 100644
index 0000000..819b403
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/LICENSE-MIT
@@ -0,0 +1,22 @@
+Copyright (c) 2014 Chris Talkington, contributors.

+

+Permission is hereby granted, free of charge, to any person

+obtaining a copy of this software and associated documentation

+files (the "Software"), to deal in the Software without

+restriction, including without limitation the rights to use,

+copy, modify, merge, publish, distribute, sublicense, and/or sell

+copies of the Software, and to permit persons to whom the

+Software is furnished to do so, subject to the following

+conditions:

+

+The above copyright notice and this permission notice shall be

+included in all copies or substantial portions of the Software.

+

+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,

+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES

+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND

+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT

+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,

+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING

+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR

+OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/README.md b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/README.md
new file mode 100644
index 0000000..c891a0d
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/README.md
@@ -0,0 +1,26 @@
+# Compress Commons v0.1.6 [![Build Status](https://travis-ci.org/ctalkington/node-compress-commons.svg?branch=master)](https://travis-ci.org/ctalkington/node-compress-commons)

+

+Compress Commons is a library that defines a common interface for working with archives within node.

+

+[![NPM](https://nodei.co/npm/compress-commons.png)](https://nodei.co/npm/compress-commons/)

+

+## Install

+

+```bash

+npm install compress-commons --save

+```

+

+You can also use `npm install https://github.com/ctalkington/node-compress-commons/archive/master.tar.gz` to test upcoming versions.

+

+## Things of Interest

+

+- [Source Docs (coming soon)](https://docsrc.com/compress-commons/)

+- [Changelog](https://github.com/ctalkington/node-compress-commons/releases)

+- [Contributing](https://github.com/ctalkington/node-compress-commons/blob/master/CONTRIBUTING.md)

+- [MIT License](https://github.com/ctalkington/node-compress-commons/blob/master/LICENSE-MIT)

+

+## Credits

+

+Concept inspired by [Apache Commons Compress](http://commons.apache.org/proper/commons-compress/)&trade;.

+

+Some logic derived from [Apache Commons Compress](http://commons.apache.org/proper/commons-compress/)&trade; and [OpenJDK 7](http://openjdk.java.net/).
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/lib/archivers/archive-entry.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/lib/archivers/archive-entry.js
new file mode 100644
index 0000000..3c6f8b8
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/lib/archivers/archive-entry.js
@@ -0,0 +1,16 @@
+/**

+ * node-compress-commons

+ *

+ * Copyright (c) 2014 Chris Talkington, contributors.

+ * Licensed under the MIT license.

+ * https://github.com/ctalkington/node-compress-commons/blob/master/LICENSE-MIT

+ */

+var ArchiveEntry = module.exports = function() {};

+

+ArchiveEntry.prototype.getName = function() {};

+

+ArchiveEntry.prototype.getSize = function() {};

+

+ArchiveEntry.prototype.getLastModifiedDate = function() {};

+

+ArchiveEntry.prototype.isDirectory = function() {};
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/lib/archivers/archive-output-stream.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/lib/archivers/archive-output-stream.js
new file mode 100644
index 0000000..939629d
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/lib/archivers/archive-output-stream.js
@@ -0,0 +1,113 @@
+/**

+ * node-compress-commons

+ *

+ * Copyright (c) 2014 Chris Talkington, contributors.

+ * Licensed under the MIT license.

+ * https://github.com/ctalkington/node-compress-commons/blob/master/LICENSE-MIT

+ */

+var inherits = require('util').inherits;

+var Transform = require('readable-stream').Transform;

+

+var ArchiveEntry = require('./archive-entry');

+var util = require('../util');

+

+var ArchiveOutputStream = module.exports = function(options) {

+  if (!(this instanceof ArchiveOutputStream)) {

+    return new ArchiveOutputStream(options);

+  }

+

+  Transform.call(this, options);

+

+  this.offset = 0;

+};

+

+inherits(ArchiveOutputStream, Transform);

+

+ArchiveOutputStream.prototype._appendBuffer = function(zae, source, callback) {

+  // scaffold only

+};

+

+ArchiveOutputStream.prototype._appendStream = function(zae, source, callback) {

+  // scaffold only

+};

+

+ArchiveOutputStream.prototype._emitErrorCallback = function(err) {

+  if (err) {

+    this.emit('error', err);

+  }

+};

+

+ArchiveOutputStream.prototype._finish = function(ae) {

+  // scaffold only

+};

+

+ArchiveOutputStream.prototype._setDefaults = function(ae) {

+  // scaffold only

+};

+

+ArchiveOutputStream.prototype._transform = function(chunk, encoding, callback) {

+  callback(null, chunk);

+};

+

+ArchiveOutputStream.prototype.entry = function(ae, source, callback) {

+  source = source || null;

+

+  if (typeof callback !== 'function') {

+    callback = this._emitErrorCallback.bind(this);

+  }

+

+  if (!(ae instanceof ArchiveEntry)) {

+    callback(new Error('not a valid instance of ArchiveEntry'));

+    return;

+  }

+

+  if (this._archive.finish || this._archive.finished) {

+    callback(new Error('unacceptable entry after finish'));

+    return;

+  }

+

+  if (this._archive.processing) {

+    callback(new Error('already processing an entry'));

+    return;

+  }

+

+  this._archive.processing = true;

+  this._setDefaults(ae);

+  this._entry = ae;

+

+  source = util.normalizeInputSource(source);

+

+  if (Buffer.isBuffer(source)) {

+    this._appendBuffer(ae, source, callback);

+  } else if (util.isStream(source)) {

+    source.on('error', callback);

+    this._appendStream(ae, source, callback);

+  } else {

+    this._archive.processing = false;

+    callback(new Error('input source must be valid Stream or Buffer instance'));

+    return;

+  }

+

+  return this;

+};

+

+ArchiveOutputStream.prototype.finish = function() {

+  if (this._archive.processing) {

+    this._archive.finish = true;

+    return;

+  }

+

+  this._finish();

+};

+

+ArchiveOutputStream.prototype.getBytesWritten = function() {

+  return this.offset;

+};

+

+ArchiveOutputStream.prototype.write = function(chunk, cb) {

+  if (chunk) {

+    this.offset += chunk.length;

+  }

+

+  return Transform.prototype.write.call(this, chunk, cb);

+};
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/lib/archivers/zip/constants.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/lib/archivers/zip/constants.js
new file mode 100644
index 0000000..47d837e
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/lib/archivers/zip/constants.js
@@ -0,0 +1,67 @@
+/**

+ * node-compress-commons

+ *

+ * Copyright (c) 2014 Chris Talkington, contributors.

+ * Licensed under the MIT license.

+ * https://github.com/ctalkington/node-compress-commons/blob/master/LICENSE-MIT

+ */

+module.exports = {

+  WORD: 4,

+  DWORD: 8,

+  EMPTY: new Buffer(0),

+

+  SHORT: 2,

+  SHORT_MASK: 0xffff,

+  SHORT_SHIFT: 16,

+  SHORT_ZERO: new Buffer(Array(2)),

+  LONG_ZERO: new Buffer(Array(4)),

+

+  DATA_DESCRIPTOR_MIN_VERSION: 20,

+  INITIAL_VERSION: 10,

+

+  METHOD_STORED: 0,

+  METHOD_DEFLATED: 8,

+

+  PLATFORM_UNIX: 3,

+  PLATFORM_FAT: 0,

+

+  SIG_LFH: 0x04034b50,

+  SIG_DD: 0x08074b50,

+  SIG_CFH: 0x02014b50,

+  SIG_EOCD: 0x06054b50,

+  SIG_ZIP64_EOCD: 0x06064B50,

+  SIG_ZIP64_EOCD_LOC: 0x07064B50,

+

+  ZIP64_MIN_VERSION: 45,

+  ZIP64_MAGIC_SHORT: 0xffff,

+  ZIP64_MAGIC: 0xffffffff,

+

+  ZLIB_NO_COMPRESSION: 0,

+  ZLIB_BEST_SPEED: 1,

+  ZLIB_BEST_COMPRESSION: 9,

+  ZLIB_DEFAULT_COMPRESSION: -1,

+

+  MODE_MASK: 0xFFF,

+  DEFAULT_FILE_MODE: 0100644, // 644 -rw-r--r-- = S_IFREG | S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH

+  DEFAULT_DIR_MODE: 040755, // 755 drwxr-xr-x = S_IFDIR | S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH

+

+  EXT_FILE_ATTR_DIR: 010173200020, // 755 drwxr-xr-x = (((S_IFDIR | 0755) << 16) | S_DOS_D)

+  EXT_FILE_ATTR_FILE: 020151000040, // 644 -rw-r--r-- = (((S_IFREG | 0644) << 16) | S_DOS_A) >>> 0

+

+  // Unix file types

+  S_IFIFO: 010000, // named pipe (fifo)

+  S_IFCHR: 020000, // character special

+  S_IFDIR: 040000, // directory

+  S_IFBLK: 060000, // block special

+  S_IFREG: 0100000, // regular

+  S_IFLNK: 0120000, // symbolic link

+  S_IFSOCK: 0140000, // socket

+

+  // DOS file type flags

+  S_DOS_A: 040, // Archive

+  S_DOS_D: 020, // Directory

+  S_DOS_V: 010, // Volume

+  S_DOS_S: 04, // System

+  S_DOS_H: 02, // Hidden

+  S_DOS_R: 01 // Read Only

+};
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/lib/archivers/zip/general-purpose-bit.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/lib/archivers/zip/general-purpose-bit.js
new file mode 100644
index 0000000..dc21073
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/lib/archivers/zip/general-purpose-bit.js
@@ -0,0 +1,101 @@
+/**

+ * node-compress-commons

+ *

+ * Copyright (c) 2014 Chris Talkington, contributors.

+ * Licensed under the MIT license.

+ * https://github.com/ctalkington/node-compress-commons/blob/master/LICENSE-MIT

+ */

+var zipUtil = require('./util');

+

+var DATA_DESCRIPTOR_FLAG = 1 << 3;

+var ENCRYPTION_FLAG = 1 << 0;

+var NUMBER_OF_SHANNON_FANO_TREES_FLAG = 1 << 2;

+var SLIDING_DICTIONARY_SIZE_FLAG = 1 << 1;

+var STRONG_ENCRYPTION_FLAG = 1 << 6;

+var UFT8_NAMES_FLAG = 1 << 11;

+

+var GeneralPurposeBit = module.exports = function() {

+  if (!(this instanceof GeneralPurposeBit)) {

+    return new GeneralPurposeBit();

+  }

+

+  this.descriptor = false;

+  this.encryption = false;

+  this.utf8 = false;

+  this.numberOfShannonFanoTrees = 0;

+  this.strongEncryption = false;

+  this.slidingDictionarySize = 0;

+

+  return this;

+};

+

+GeneralPurposeBit.prototype.encode = function() {

+  return zipUtil.getShortBytes(

+    (this.descriptor ? DATA_DESCRIPTOR_FLAG : 0) |

+    (this.utf8 ? UFT8_NAMES_FLAG : 0) |

+    (this.encryption ? ENCRYPTION_FLAG : 0) |

+    (this.strongEncryption ? STRONG_ENCRYPTION_FLAG : 0)

+  );

+};

+

+GeneralPurposeBit.prototype.parse = function(buf, offset) {

+  var flag = zipUtil.getShortBytesValue(buf, offset);

+  var gbp = new GeneralPurposeBit();

+

+  gbp.useDataDescriptor((flag & DATA_DESCRIPTOR_FLAG) !== 0);

+  gbp.useUTF8ForNames((flag & UFT8_NAMES_FLAG) !== 0);

+  gbp.useStrongEncryption((flag & STRONG_ENCRYPTION_FLAG) !== 0);

+  gbp.useEncryption((flag & ENCRYPTION_FLAG) !== 0);

+  gbp.setSlidingDictionarySize((flag & SLIDING_DICTIONARY_SIZE_FLAG) !== 0 ? 8192 : 4096);

+  gbp.setNumberOfShannonFanoTrees((flag & NUMBER_OF_SHANNON_FANO_TREES_FLAG) !== 0 ? 3 : 2);

+

+  return gbp;

+};

+

+GeneralPurposeBit.prototype.setNumberOfShannonFanoTrees = function(n) {

+  this.numberOfShannonFanoTrees = n;

+};

+

+GeneralPurposeBit.prototype.getNumberOfShannonFanoTrees = function() {

+  return this.numberOfShannonFanoTrees;

+};

+

+GeneralPurposeBit.prototype.setSlidingDictionarySize = function(n) {

+  this.slidingDictionarySize = n;

+};

+

+GeneralPurposeBit.prototype.getSlidingDictionarySize = function() {

+  return this.slidingDictionarySize;

+};

+

+GeneralPurposeBit.prototype.useDataDescriptor = function(b) {

+  this.descriptor = b;

+};

+

+GeneralPurposeBit.prototype.usesDataDescriptor = function() {

+  return this.descriptor;

+};

+

+GeneralPurposeBit.prototype.useEncryption = function(b) {

+  this.encryption = b;

+};

+

+GeneralPurposeBit.prototype.usesEncryption = function() {

+  return this.encryption;

+};

+

+GeneralPurposeBit.prototype.useStrongEncryption = function(b) {

+  this.strongEncryption = b;

+};

+

+GeneralPurposeBit.prototype.usesStrongEncryption = function() {

+  return this.strongEncryption;

+};

+

+GeneralPurposeBit.prototype.useUTF8ForNames = function(b) {

+  this.utf8 = b;

+};

+

+GeneralPurposeBit.prototype.usesUTF8ForNames = function() {

+  return this.utf8;

+};
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/lib/archivers/zip/util.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/lib/archivers/zip/util.js
new file mode 100644
index 0000000..14a6107
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/lib/archivers/zip/util.js
@@ -0,0 +1,71 @@
+/**

+ * node-compress-commons

+ *

+ * Copyright (c) 2014 Chris Talkington, contributors.

+ * Licensed under the MIT license.

+ * https://github.com/ctalkington/node-compress-commons/blob/master/LICENSE-MIT

+ */

+var util = module.exports = {};

+

+util.dateToDos = function(d) {

+  var year = d.getFullYear();

+

+  if (year < 1980) {

+    return 2162688; // 1980-1-1 00:00:00

+  } else if (year >= 2044) {

+    return 2141175677; // 2043-12-31 23:59:58

+  }

+

+  var val = {

+    year: year,

+    month: d.getMonth(),

+    date: d.getDate(),

+    hours: d.getHours(),

+    minutes: d.getMinutes(),

+    seconds: d.getSeconds()

+  };

+

+  return ((val.year - 1980) << 25) | ((val.month + 1) << 21) | (val.date << 16) |

+    (val.hours << 11) | (val.minutes << 5) | (val.seconds / 2);

+};

+

+util.dosToDate = function(dos) {

+  return new Date(

+    ((dos >> 25) & 0x7f) + 1980,

+    ((dos >> 21) & 0x0f) - 1,

+    (dos >> 16) & 0x1f,

+    (dos >> 11) & 0x1f,

+    (dos >> 5) & 0x3f,

+    (dos & 0x1f) << 1

+  );

+};

+

+util.fromDosTime = function(buf) {

+  return util.dosToDate(buf.readUInt32LE());

+};

+

+util.getShortBytes = function(v) {

+  var buf = new Buffer(2);

+  buf.writeUInt16LE(v, 0);

+

+  return buf;

+};

+

+util.getShortBytesValue = function(buf, offset) {

+  return buf.readUInt16LE(offset);

+};

+

+util.getLongBytes = function(v) {

+  var buf = new Buffer(4);

+  buf.writeUInt32LE(v, 0);

+

+  return buf;

+};

+

+util.getLongBytesValue = function(buf, offset) {

+  return buf.readUInt32LE(offset);

+};

+

+util.toDosTime = function(d) {

+  return util.getLongBytes(util.dateToDos(d));

+};
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/lib/archivers/zip/zip-archive-entry.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/lib/archivers/zip/zip-archive-entry.js
new file mode 100644
index 0000000..1883eb5
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/lib/archivers/zip/zip-archive-entry.js
@@ -0,0 +1,223 @@
+/**

+ * node-compress-commons

+ *

+ * Copyright (c) 2014 Chris Talkington, contributors.

+ * Licensed under the MIT license.

+ * https://github.com/ctalkington/node-compress-commons/blob/master/LICENSE-MIT

+ */

+var inherits = require('util').inherits;

+

+var ArchiveEntry = require('../archive-entry');

+var GeneralPurposeBit = require('./general-purpose-bit');

+

+var constants = require('./constants');

+var zipUtil = require('./util');

+

+var ZipArchiveEntry = module.exports = function(name) {

+  if (!(this instanceof ZipArchiveEntry)) {

+    return new ZipArchiveEntry(name);

+  }

+

+  ArchiveEntry.call(this);

+

+  this.platform = constants.PLATFORM_FAT;

+  this.method = -1;

+

+  this.name = null;

+  this.size = -1;

+  this.csize = -1;

+  this.gpb = new GeneralPurposeBit();

+  this.crc = 0;

+  this.time = -1;

+

+  this.minver = constants.INITIAL_VERSION;

+  this.mode = -1;

+  this.extra = null;

+  this.exattr = 0;

+  this.inattr = 0;

+  this.comment = null;

+

+  if (name) {

+    this.setName(name);

+  }

+};

+

+inherits(ZipArchiveEntry, ArchiveEntry);

+

+ZipArchiveEntry.prototype.getCentralDirectoryExtra = function() {

+  return this.getExtra();

+};

+

+ZipArchiveEntry.prototype.getComment = function() {

+  return this.comment !== null ? this.comment : '';

+};

+

+ZipArchiveEntry.prototype.getCompressedSize = function() {

+  return this.csize;

+};

+

+ZipArchiveEntry.prototype.getCrc = function() {

+  return this.crc;

+};

+

+ZipArchiveEntry.prototype.getExternalAttributes = function() {

+  return this.exattr;

+};

+

+ZipArchiveEntry.prototype.getExtra = function() {

+  return this.extra !== null ? this.extra : constants.EMPTY;

+};

+

+ZipArchiveEntry.prototype.getGeneralPurposeBit = function() {

+  return this.gpb;

+};

+

+ZipArchiveEntry.prototype.getInternalAttributes = function() {

+  return this.inattr;

+};

+

+ZipArchiveEntry.prototype.getLastModifiedDate = function() {

+  return this.getTime();

+};

+

+ZipArchiveEntry.prototype.getLocalFileDataExtra = function() {

+  return this.getExtra();

+};

+

+ZipArchiveEntry.prototype.getMethod = function() {

+  return this.method;

+};

+

+ZipArchiveEntry.prototype.getName = function() {

+  return this.name;

+};

+

+ZipArchiveEntry.prototype.getPlatform = function() {

+  return this.platform;

+};

+

+ZipArchiveEntry.prototype.getSize = function() {

+  return this.size;

+};

+

+ZipArchiveEntry.prototype.getTime = function() {

+  return this.time !== -1 ? zipUtil.dosToDate(this.time) : -1;

+};

+

+ZipArchiveEntry.prototype.getTimeDos = function() {

+  return this.time !== -1 ? this.time : 0;

+};

+

+ZipArchiveEntry.prototype.getUnixMode = function() {

+  return this.platform !== constants.PLATFORM_UNIX ? 0 : ((this.getExternalAttributes() >> constants.SHORT_SHIFT) & constants.SHORT_MASK) & constants.MODE_MASK;

+};

+

+ZipArchiveEntry.prototype.getVersionNeededToExtract = function() {

+  return this.minver;

+};

+

+ZipArchiveEntry.prototype.setComment = function(comment) {

+  if (Buffer.byteLength(comment) !== comment.length) {

+    this.getGeneralPurposeBit().useUTF8ForNames(true);

+  }

+

+  this.comment = comment;

+};

+

+ZipArchiveEntry.prototype.setCompressedSize = function(size) {

+  if (size < 0) {

+    throw new Error('invalid entry compressed size');

+  }

+

+  this.csize = size;

+};

+

+ZipArchiveEntry.prototype.setCrc = function(crc) {

+  if (crc < 0) {

+    throw new Error('invalid entry crc32');

+  }

+

+  this.crc = crc;

+};

+

+ZipArchiveEntry.prototype.setExternalAttributes = function(attr) {

+  this.exattr = attr >>> 0;

+};

+

+ZipArchiveEntry.prototype.setExtra = function(extra) {

+  this.extra = extra;

+};

+

+ZipArchiveEntry.prototype.setGeneralPurposeBit = function(gpb) {

+  if (!(gpb instanceof GeneralPurposeBit)) {

+    throw new Error('invalid entry GeneralPurposeBit');

+  }

+

+  this.gpb = gpb;

+};

+

+ZipArchiveEntry.prototype.setInternalAttributes = function(attr) {

+  this.inattr = attr;

+};

+

+ZipArchiveEntry.prototype.setMethod = function(method) {

+  if (method < 0) {

+    throw new Error('invalid entry compression method');

+  }

+

+  this.method = method;

+};

+

+ZipArchiveEntry.prototype.setName = function(name) {

+  name = name.replace(/\\/g, '/').replace(/:/g, '').replace(/^\/+/, '');

+

+  if (Buffer.byteLength(name) !== name.length) {

+    this.getGeneralPurposeBit().useUTF8ForNames(true);

+  }

+

+  this.name = name;

+};

+

+ZipArchiveEntry.prototype.setPlatform = function(platform) {

+  this.platform = platform;

+};

+

+ZipArchiveEntry.prototype.setSize = function(size) {

+  if (size < 0) {

+    throw new Error('invalid entry size');

+  }

+

+  this.size = size;

+};

+

+ZipArchiveEntry.prototype.setTime = function(time) {

+  if (!(time instanceof Date)) {

+    throw new Error('invalid entry time');

+  }

+

+  this.time = zipUtil.dateToDos(time);

+};

+

+ZipArchiveEntry.prototype.setUnixMode = function(mode) {

+  mode &= ~constants.S_IFDIR;

+  var extattr = 0;

+

+  if (!this.isDirectory()) {

+    mode |= constants.S_IFREG;

+  }

+

+  extattr &= ~this.getExternalAttributes();

+  extattr |= (mode << constants.SHORT_SHIFT) | (this.isDirectory() ? constants.S_DOS_D : constants.S_DOS_A);

+

+  this.setExternalAttributes(extattr);

+  this.mode = mode & constants.MODE_MASK;

+  this.platform = constants.PLATFORM_UNIX;

+};

+

+ZipArchiveEntry.prototype.setVersionNeededToExtract = function(minver) {

+  this.minver = minver;

+};

+

+ZipArchiveEntry.prototype.isDirectory = function() {

+  return this.getName().slice(-1) === '/';

+};
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/lib/archivers/zip/zip-archive-output-stream.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/lib/archivers/zip/zip-archive-output-stream.js
new file mode 100644
index 0000000..a1c5e03
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/lib/archivers/zip/zip-archive-output-stream.js
@@ -0,0 +1,337 @@
+/**

+ * node-compress-commons

+ *

+ * Copyright (c) 2014 Chris Talkington, contributors.

+ * Licensed under the MIT license.

+ * https://github.com/ctalkington/node-compress-commons/blob/master/LICENSE-MIT

+ */

+var inherits = require('util').inherits;

+var crc32 = require('buffer-crc32');

+var CRC32Stream = require('crc32-stream');

+var DeflateCRC32Stream = CRC32Stream.DeflateCRC32Stream;

+

+var ArchiveOutputStream = require('../archive-output-stream');

+var ZipArchiveEntry = require('./zip-archive-entry');

+var GeneralPurposeBit = require('./general-purpose-bit');

+

+var constants = require('./constants');

+var util = require('../../util');

+var zipUtil = require('./util');

+

+var ZipArchiveOutputStream = module.exports = function(options) {

+  if (!(this instanceof ZipArchiveOutputStream)) {

+    return new ZipArchiveOutputStream(options);

+  }

+

+  options = this.options = options || {};

+  options.zlib = this._zlibDefaults(options.zlib);

+

+  ArchiveOutputStream.call(this, options);

+

+  this._entry = null;

+  this._entries = [];

+  this._archive = {

+    centralLength: 0,

+    centralOffset: 0,

+    comment: '',

+    finish: false,

+    finished: false,

+    processing: false

+  };

+};

+

+inherits(ZipArchiveOutputStream, ArchiveOutputStream);

+

+ZipArchiveOutputStream.prototype._afterAppend = function(zae) {

+  this._entries.push(zae);

+  this._writeDataDescriptor(zae);

+

+  this._archive.processing = false;

+  this._entry = null;

+

+  if (this._archive.finish && !this._archive.finished) {

+    this._finish();

+  }

+};

+

+ZipArchiveOutputStream.prototype._appendBuffer = function(ae, source, callback) {

+  if (source.length === 0) {

+    ae.setMethod(constants.METHOD_STORED);

+  }

+

+  var method = ae.getMethod();

+

+  if (method === constants.METHOD_STORED) {

+    ae.setSize(source.length);

+    ae.setCompressedSize(source.length);

+    ae.setCrc(crc32.unsigned(source));

+  }

+

+  this._writeLocalFileHeader(ae);

+

+  if (method === constants.METHOD_STORED) {

+    this.write(source);

+    this._afterAppend(ae);

+    callback(null, ae);

+    return;

+  } else if (method === constants.METHOD_DEFLATED) {

+    this._smartStream(ae, callback).end(source);

+    return;

+  } else {

+    callback(new Error('compression method ' + method + ' not implemented'));

+    return;

+  }

+};

+

+ZipArchiveOutputStream.prototype._appendStream = function(ae, source, callback) {

+  ae.getGeneralPurposeBit().useDataDescriptor(true);

+

+  this._writeLocalFileHeader(ae);

+

+  var smart = this._smartStream(ae, callback);

+  source.pipe(smart);

+};

+

+ZipArchiveOutputStream.prototype._finish = function() {

+  this._archive.centralOffset = this.offset;

+

+  this._entries.forEach(function(ae) {

+    this._writeCentralFileHeader(ae);

+  }.bind(this));

+

+  this._archive.centralLength = this.offset - this._archive.centralOffset;

+

+  this._writeCentralDirectoryEnd();

+

+  this._archive.processing = false;

+  this._archive.finish = true;

+  this._archive.finished = true;

+  this.end();

+};

+

+ZipArchiveOutputStream.prototype._setDefaults = function(ae) {

+  if (ae.getMethod() === -1) {

+    ae.setMethod(constants.METHOD_DEFLATED);

+  }

+

+  if (ae.getMethod() === constants.METHOD_DEFLATED) {

+    ae.getGeneralPurposeBit().useDataDescriptor(true);

+    ae.setVersionNeededToExtract(constants.DATA_DESCRIPTOR_MIN_VERSION);

+  }

+

+  if (ae.getTime() === -1) {

+    ae.setTime(new Date());

+  }

+

+  ae._offsets = {

+    file: 0,

+    data: 0,

+    contents: 0,

+  };

+};

+

+ZipArchiveOutputStream.prototype._smartStream = function(ae, callback) {

+  var deflate = ae.getMethod() === constants.METHOD_DEFLATED;

+  var process = deflate ? new DeflateCRC32Stream(this.options.zlib) : new CRC32Stream();

+

+  function handleStuff(err) {

+    ae.setCrc(process.digest());

+    ae.setSize(process.size());

+    ae.setCompressedSize(process.size(true));

+    this._afterAppend(ae);

+    callback(null, ae);

+  }

+

+  process.once('error', callback);

+  process.once('end', handleStuff.bind(this));

+

+  process.pipe(this, { end: false });

+

+  return process;

+};

+

+ZipArchiveOutputStream.prototype._writeCentralDirectoryEnd = function() {

+  // signature

+  this.write(zipUtil.getLongBytes(constants.SIG_EOCD));

+

+  // disk numbers

+  this.write(constants.SHORT_ZERO);

+  this.write(constants.SHORT_ZERO);

+

+  // number of entries

+  this.write(zipUtil.getShortBytes(this._entries.length));

+  this.write(zipUtil.getShortBytes(this._entries.length));

+

+  // length and location of CD

+  this.write(zipUtil.getLongBytes(this._archive.centralLength));

+  this.write(zipUtil.getLongBytes(this._archive.centralOffset));

+

+  // archive comment

+  var comment = this.getComment();

+  var commentLength = Buffer.byteLength(comment);

+  this.write(zipUtil.getShortBytes(commentLength));

+  this.write(comment);

+};

+

+ZipArchiveOutputStream.prototype._writeCentralFileHeader = function(ae) {

+  var gpb = ae.getGeneralPurposeBit();

+  var method = ae.getMethod();

+  var offsets = ae._offsets;

+

+  // signature

+  this.write(zipUtil.getLongBytes(constants.SIG_CFH));

+

+  // version made by

+  this.write(zipUtil.getShortBytes(

+    (ae.getPlatform() << 8) | constants.DATA_DESCRIPTOR_MIN_VERSION

+  ));

+

+  // version to extract and general bit flag

+  this._writeVersionGeneral(ae);

+

+  // compression method

+  this.write(zipUtil.getShortBytes(method));

+

+  // datetime

+  this.write(zipUtil.getLongBytes(ae.getTimeDos()));

+

+  // crc32 checksum

+  this.write(zipUtil.getLongBytes(ae.getCrc()));

+

+  // sizes

+  this.write(zipUtil.getLongBytes(ae.getCompressedSize()));

+  this.write(zipUtil.getLongBytes(ae.getSize()));

+

+  var name = ae.getName();

+  var comment = ae.getComment();

+  var extra = ae.getCentralDirectoryExtra();

+

+  if (gpb.usesUTF8ForNames()) {

+    name = new Buffer(name);

+    comment = new Buffer(comment);

+  }

+

+  // name length

+  this.write(zipUtil.getShortBytes(name.length));

+

+  // extra length

+  this.write(zipUtil.getShortBytes(extra.length));

+

+  // comments length

+  this.write(zipUtil.getShortBytes(comment.length));

+

+  // disk number start

+  this.write(constants.SHORT_ZERO);

+

+  // internal attributes

+  this.write(zipUtil.getShortBytes(ae.getInternalAttributes()));

+

+  // external attributes

+  this.write(zipUtil.getLongBytes(ae.getExternalAttributes()));

+

+  // relative offset of LFH

+  this.write(zipUtil.getLongBytes(offsets.file));

+

+  // name

+  this.write(name);

+

+  // extra

+  this.write(extra);

+

+  // comment

+  this.write(comment);

+};

+

+ZipArchiveOutputStream.prototype._writeDataDescriptor = function(ae) {

+  if (!ae.getGeneralPurposeBit().usesDataDescriptor()) {

+    return;

+  }

+

+  // signature

+  this.write(zipUtil.getLongBytes(constants.SIG_DD));

+

+  // crc32 checksum

+  this.write(zipUtil.getLongBytes(ae.getCrc()));

+

+  // sizes

+  this.write(zipUtil.getLongBytes(ae.getCompressedSize()));

+  this.write(zipUtil.getLongBytes(ae.getSize()));

+};

+

+ZipArchiveOutputStream.prototype._writeLocalFileHeader = function(ae) {

+  var gpb = ae.getGeneralPurposeBit();

+  var method = ae.getMethod();

+  var name = ae.getName();

+  var extra = ae.getLocalFileDataExtra();

+

+  if (gpb.usesUTF8ForNames()) {

+    name = new Buffer(name);

+  }

+

+  ae._offsets.file = this.offset;

+

+  // signature

+  this.write(zipUtil.getLongBytes(constants.SIG_LFH));

+

+  // version to extract and general bit flag

+  this._writeVersionGeneral(ae);

+

+  // compression method

+  this.write(zipUtil.getShortBytes(method));

+

+  // datetime

+  this.write(zipUtil.getLongBytes(ae.getTimeDos()));

+

+  ae._offsets.data = this.offset;

+

+  if (ae.getGeneralPurposeBit().usesDataDescriptor()) {

+    // zero fill and set later

+    this.write(constants.LONG_ZERO);

+    this.write(constants.LONG_ZERO);

+    this.write(constants.LONG_ZERO);

+  } else {

+    // crc32 checksum and sizes

+    this.write(zipUtil.getLongBytes(ae.getCrc()));

+    this.write(zipUtil.getLongBytes(ae.getCompressedSize()));

+    this.write(zipUtil.getLongBytes(ae.getSize()));

+  }

+

+  // name length

+  this.write(zipUtil.getShortBytes(name.length));

+

+  // extra length

+  this.write(zipUtil.getShortBytes(extra.length));

+

+  // name

+  this.write(name);

+

+  // extra

+  this.write(extra);

+

+  ae._offsets.contents = this.offset;

+};

+

+ZipArchiveOutputStream.prototype._writeVersionGeneral = function(ae) {

+  this.write(zipUtil.getShortBytes(ae.getVersionNeededToExtract()));

+  this.write(ae.getGeneralPurposeBit().encode());

+};

+

+ZipArchiveOutputStream.prototype._zlibDefaults = function(o) {

+  if (typeof o !== 'object') {

+    o = {};

+  }

+

+  if (typeof o.level !== 'number') {

+    o.level = constants.ZLIB_BEST_SPEED;

+  }

+

+  return o;

+};

+

+ZipArchiveOutputStream.prototype.getComment = function(comment) {

+  return this._archive.comment !== null ? this._archive.comment : '';

+};

+

+ZipArchiveOutputStream.prototype.setComment = function(comment) {

+  this._archive.comment = comment;

+};
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/lib/compress-commons.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/lib/compress-commons.js
new file mode 100644
index 0000000..fe3a581
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/lib/compress-commons.js
@@ -0,0 +1,13 @@
+/**

+ * node-compress-commons

+ *

+ * Copyright (c) 2014 Chris Talkington, contributors.

+ * Licensed under the MIT license.

+ * https://github.com/ctalkington/node-compress-commons/blob/master/LICENSE-MIT

+ */

+module.exports = {

+  ArchiveEntry: require('./archivers/archive-entry'),

+  ZipArchiveEntry: require('./archivers/zip/zip-archive-entry'),

+  ArchiveOutputStream: require('./archivers/archive-output-stream'),

+  ZipArchiveOutputStream: require('./archivers/zip/zip-archive-output-stream')

+};
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/lib/util/index.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/lib/util/index.js
new file mode 100644
index 0000000..6e89f0e
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/lib/util/index.js
@@ -0,0 +1,30 @@
+/**

+ * node-compress-commons

+ *

+ * Copyright (c) 2014 Chris Talkington, contributors.

+ * Licensed under the MIT license.

+ * https://github.com/ctalkington/node-compress-commons/blob/master/LICENSE-MIT

+ */

+var Stream = require('stream').Stream;

+var PassThrough = require('readable-stream').PassThrough;

+

+var util = module.exports = {};

+

+util.isStream = function(source) {

+  return source instanceof Stream;

+};

+

+util.normalizeInputSource = function(source) {

+  if (source === null) {

+    return new Buffer(0);

+  } else if (typeof source === 'string') {

+    return new Buffer(source);

+  } else if (util.isStream(source) && !source._readableState) {

+    var normalized = new PassThrough();

+    source.pipe(normalized);

+

+    return normalized;

+  }

+

+  return source;

+};
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/node_modules/crc32-stream/CHANGELOG b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/node_modules/crc32-stream/CHANGELOG
new file mode 100644
index 0000000..983c20b
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/node_modules/crc32-stream/CHANGELOG
@@ -0,0 +1,18 @@
+v0.2.0:

+  date: 2014-05-03

+  changes:

+    - add size method to return raw size of data passed-through.

+v0.1.2:

+  date: 2014-04-18

+  changes:

+    - always use readable-stream for consistency.

+    - use crc32.unsigned to get digest.

+v0.1.1:

+  date: 2014-03-30

+  changes:

+    - gracefully handle "no data" scenario.

+    - trim down deps.

+v0.1.0:

+  date: 2014-03-30

+  changes:

+    - initial release.
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/node_modules/crc32-stream/LICENSE-MIT b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/node_modules/crc32-stream/LICENSE-MIT
new file mode 100644
index 0000000..819b403
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/node_modules/crc32-stream/LICENSE-MIT
@@ -0,0 +1,22 @@
+Copyright (c) 2014 Chris Talkington, contributors.

+

+Permission is hereby granted, free of charge, to any person

+obtaining a copy of this software and associated documentation

+files (the "Software"), to deal in the Software without

+restriction, including without limitation the rights to use,

+copy, modify, merge, publish, distribute, sublicense, and/or sell

+copies of the Software, and to permit persons to whom the

+Software is furnished to do so, subject to the following

+conditions:

+

+The above copyright notice and this permission notice shall be

+included in all copies or substantial portions of the Software.

+

+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,

+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES

+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND

+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT

+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,

+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING

+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR

+OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/node_modules/crc32-stream/README.md b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/node_modules/crc32-stream/README.md
new file mode 100644
index 0000000..040224f
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/node_modules/crc32-stream/README.md
@@ -0,0 +1,81 @@
+# crc32-stream v0.3.2 [![Build Status](https://travis-ci.org/archiverjs/node-crc32-stream.svg?branch=master)](https://travis-ci.org/archiverjs/node-crc32-stream)

+

+crc32-stream is a streaming CRC32 checksumer. It uses [buffer-crc32](https://www.npmjs.org/package/buffer-crc32) behind the scenes to reliably handle binary data and fancy character sets. Data is passed through untouched.

+

+[![NPM](https://nodei.co/npm/crc32-stream.png)](https://nodei.co/npm/crc32-stream/)

+

+### Install

+

+```bash

+npm install crc32-stream --save

+```

+

+You can also use `npm install https://github.com/archiverjs/node-crc32-stream/archive/master.tar.gz` to test upcoming versions.

+

+### Usage

+

+#### CRC32Stream

+

+Inherits [Transform Stream](http://nodejs.org/api/stream.html#stream_class_stream_transform) options and methods.

+

+```js

+var CRC32Stream = require('crc32-stream');

+

+var source = fs.createReadStream('file.txt');

+var checksum = new CRC32Stream();

+

+checksum.on('end', function(err) {

+  // do something with checksum.digest() here

+});

+

+// either pipe it

+source.pipe(checksum);

+

+// or write it

+checksum.write('string');

+checksum.end();

+```

+

+#### DeflateCRC32Stream

+

+Inherits [zlib.DeflateRaw](http://nodejs.org/api/zlib.html#zlib_class_zlib_deflateraw) options and methods.

+

+```js

+var DeflateCRC32Stream = require('crc32-stream').DeflateCRC32Stream;

+

+var source = fs.createReadStream('file.txt');

+var checksum = new DeflateCRC32Stream();

+

+checksum.on('end', function(err) {

+  // do something with checksum.digest() here

+});

+

+// either pipe it

+source.pipe(checksum);

+

+// or write it

+checksum.write('string');

+checksum.end();

+```

+

+### Instance API

+

+#### digest()

+

+Returns the checksum digest in unsigned form.

+

+#### hex()

+

+Returns the hexadecimal representation of the checksum digest. (ie E81722F0)

+

+#### size(compressed)

+

+Returns the raw size/length of passed-through data.

+

+If `compressed` is `true`, it returns compressed length instead. (DeflateCRC32Stream)

+

+## Things of Interest

+

+- [Changelog](https://github.com/archiverjs/node-crc32-stream/releases)

+- [Contributing](https://github.com/archiverjs/node-crc32-stream/blob/master/CONTRIBUTING.md)

+- [MIT License](https://github.com/archiverjs/node-crc32-stream/blob/master/LICENSE-MIT)
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/node_modules/crc32-stream/lib/crc32-stream.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/node_modules/crc32-stream/lib/crc32-stream.js
new file mode 100644
index 0000000..42cb454
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/node_modules/crc32-stream/lib/crc32-stream.js
@@ -0,0 +1,42 @@
+/**

+ * node-crc32-stream

+ *

+ * Copyright (c) 2014 Chris Talkington, contributors.

+ * Licensed under the MIT license.

+ * https://github.com/archiverjs/node-crc32-stream/blob/master/LICENSE-MIT

+ */

+var inherits = require('util').inherits;

+var Transform = require('readable-stream').Transform;

+

+var crc32 = require('buffer-crc32');

+

+var CRC32Stream = module.exports = function CRC32Stream(options) {

+  Transform.call(this, options);

+  this.checksum = new Buffer(4);

+  this.checksum.writeInt32BE(0, 0);

+

+  this.rawSize = 0;

+};

+

+inherits(CRC32Stream, Transform);

+

+CRC32Stream.prototype._transform = function(chunk, encoding, callback) {

+  if (chunk) {

+    this.checksum = crc32(chunk, this.checksum);

+    this.rawSize += chunk.length;

+  }

+

+  callback(null, chunk);

+};

+

+CRC32Stream.prototype.digest = function() {

+  return crc32.unsigned(0, this.checksum);

+};

+

+CRC32Stream.prototype.hex = function() {

+  return this.digest().toString(16).toUpperCase();

+};

+

+CRC32Stream.prototype.size = function() {

+  return this.rawSize;

+};
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/node_modules/crc32-stream/lib/deflate-crc32-stream.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/node_modules/crc32-stream/lib/deflate-crc32-stream.js
new file mode 100644
index 0000000..ba31dd1
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/node_modules/crc32-stream/lib/deflate-crc32-stream.js
@@ -0,0 +1,67 @@
+/**

+ * node-crc32-stream

+ *

+ * Copyright (c) 2014 Chris Talkington, contributors.

+ * Licensed under the MIT license.

+ * https://github.com/archiverjs/node-crc32-stream/blob/master/LICENSE-MIT

+ */

+var zlib = require('zlib');

+var inherits = require('util').inherits;

+

+var crc32 = require('buffer-crc32');

+

+var DeflateCRC32Stream = module.exports = function (options) {

+  zlib.DeflateRaw.call(this, options);

+

+  this.checksum = new Buffer(4);

+  this.checksum.writeInt32BE(0, 0);

+

+  this.rawSize = 0;

+  this.compressedSize = 0;

+

+  // BC v0.8

+  if (typeof zlib.DeflateRaw.prototype.push !== 'function') {

+    this.on('data', function(chunk) {

+      if (chunk) {

+        this.compressedSize += chunk.length;

+      }

+    });

+  }

+};

+

+inherits(DeflateCRC32Stream, zlib.DeflateRaw);

+

+DeflateCRC32Stream.prototype.push = function(chunk, encoding) {

+  if (chunk) {

+    this.compressedSize += chunk.length;

+  }

+

+  return zlib.DeflateRaw.prototype.push.call(this, chunk, encoding);

+};

+

+DeflateCRC32Stream.prototype.write = function(chunk, cb) {

+  if (chunk) {

+    this.checksum = crc32(chunk, this.checksum);

+    this.rawSize += chunk.length;

+  }

+

+  return zlib.DeflateRaw.prototype.write.call(this, chunk, cb);

+};

+

+DeflateCRC32Stream.prototype.digest = function() {

+  return crc32.unsigned(0, this.checksum);

+};

+

+DeflateCRC32Stream.prototype.hex = function() {

+  return this.digest().toString(16).toUpperCase();

+};

+

+DeflateCRC32Stream.prototype.size = function(compressed) {

+  compressed = compressed || false;

+

+  if (compressed) {

+    return this.compressedSize;

+  } else {

+    return this.rawSize;

+  }

+};
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/node_modules/crc32-stream/lib/index.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/node_modules/crc32-stream/lib/index.js
new file mode 100644
index 0000000..31187e3
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/node_modules/crc32-stream/lib/index.js
@@ -0,0 +1,10 @@
+/**

+ * node-crc32-stream

+ *

+ * Copyright (c) 2014 Chris Talkington, contributors.

+ * Licensed under the MIT license.

+ * https://github.com/archiverjs/node-crc32-stream/blob/master/LICENSE-MIT

+ */

+exports = module.exports = require('./crc32-stream');

+exports.CRC32Stream = exports;

+exports.DeflateCRC32Stream = require('./deflate-crc32-stream');
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/node_modules/crc32-stream/package.json b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/node_modules/crc32-stream/package.json
new file mode 100644
index 0000000..6dd7829
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/node_modules/crc32-stream/package.json
@@ -0,0 +1,71 @@
+{
+  "name": "crc32-stream",
+  "version": "0.3.2",
+  "description": "a streaming CRC32 checksumer",
+  "homepage": "https://github.com/archiverjs/node-crc32-stream",
+  "author": {
+    "name": "Chris Talkington",
+    "url": "http://christalkington.com/"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/archiverjs/node-crc32-stream.git"
+  },
+  "bugs": {
+    "url": "https://github.com/archiverjs/node-crc32-stream/issues"
+  },
+  "licenses": [
+    {
+      "type": "MIT",
+      "url": "https://github.com/archiverjs/node-crc32-stream/blob/master/LICENSE-MIT"
+    }
+  ],
+  "main": "lib/index.js",
+  "files": [
+    "lib",
+    "LICENSE-MIT"
+  ],
+  "engines": {
+    "node": ">= 0.8.0"
+  },
+  "scripts": {
+    "test": "mocha --reporter dot"
+  },
+  "dependencies": {
+    "readable-stream": "~1.0.24",
+    "buffer-crc32": "~0.2.1"
+  },
+  "devDependencies": {
+    "chai": "~2.0.0",
+    "mocha": "~2.1.0"
+  },
+  "keywords": [
+    "crc32-stream",
+    "crc32",
+    "stream",
+    "checksum"
+  ],
+  "gitHead": "5fff7a70787e28b634c166ecb4e6184ad0efca66",
+  "_id": "crc32-stream@0.3.2",
+  "_shasum": "8c86a5c4ed38c53e36750d662784ad8ec642e38e",
+  "_from": "crc32-stream@~0.3.1",
+  "_npmVersion": "2.5.1",
+  "_nodeVersion": "0.12.0",
+  "_npmUser": {
+    "name": "ctalkington",
+    "email": "chris@christalkington.com"
+  },
+  "maintainers": [
+    {
+      "name": "ctalkington",
+      "email": "chris@christalkington.com"
+    }
+  ],
+  "dist": {
+    "shasum": "8c86a5c4ed38c53e36750d662784ad8ec642e38e",
+    "tarball": "http://registry.npmjs.org/crc32-stream/-/crc32-stream-0.3.2.tgz"
+  },
+  "directories": {},
+  "_resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-0.3.2.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/package.json b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/package.json
new file mode 100644
index 0000000..f916f4e
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/node_modules/compress-commons/package.json
@@ -0,0 +1,72 @@
+{
+  "name": "compress-commons",
+  "version": "0.1.6",
+  "description": "a library that defines a common interface for working with archives within node",
+  "homepage": "https://github.com/ctalkington/node-compress-commons",
+  "author": {
+    "name": "Chris Talkington",
+    "url": "http://christalkington.com/"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/ctalkington/node-compress-commons.git"
+  },
+  "bugs": {
+    "url": "https://github.com/ctalkington/node-compress-commons/issues"
+  },
+  "licenses": [
+    {
+      "type": "MIT",
+      "url": "https://github.com/ctalkington/node-compress-commons/blob/master/LICENSE-MIT"
+    }
+  ],
+  "main": "lib/compress-commons.js",
+  "files": [
+    "lib",
+    "LICENSE-MIT"
+  ],
+  "engines": {
+    "node": ">= 0.8.0"
+  },
+  "scripts": {
+    "test": "mocha --reporter dot"
+  },
+  "dependencies": {
+    "buffer-crc32": "~0.2.1",
+    "crc32-stream": "~0.3.1",
+    "readable-stream": "~1.0.26"
+  },
+  "devDependencies": {
+    "chai": "~1.9.1",
+    "mocha": "~1.21.0",
+    "rimraf": "~2.2.8",
+    "mkdirp": "~0.5.0"
+  },
+  "keywords": [
+    "compress",
+    "commons",
+    "archive"
+  ],
+  "gitHead": "3575877461837f7ca9181e2d0737b001f42d880d",
+  "_id": "compress-commons@0.1.6",
+  "_shasum": "0c740870fde58cba516f0ac0c822e33a0b85dfa3",
+  "_from": "compress-commons@~0.1.0",
+  "_npmVersion": "1.4.23",
+  "_npmUser": {
+    "name": "ctalkington",
+    "email": "chris@christalkington.com"
+  },
+  "maintainers": [
+    {
+      "name": "ctalkington",
+      "email": "chris@christalkington.com"
+    }
+  ],
+  "dist": {
+    "shasum": "0c740870fde58cba516f0ac0c822e33a0b85dfa3",
+    "tarball": "http://registry.npmjs.org/compress-commons/-/compress-commons-0.1.6.tgz"
+  },
+  "directories": {},
+  "_resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-0.1.6.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/package.json b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/package.json
new file mode 100644
index 0000000..938a754
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/node_modules/zip-stream/package.json
@@ -0,0 +1,73 @@
+{
+  "name": "zip-stream",
+  "version": "0.4.1",
+  "description": "a streaming zip archive generator.",
+  "homepage": "https://github.com/ctalkington/node-zip-stream",
+  "author": {
+    "name": "Chris Talkington",
+    "url": "http://christalkington.com/"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/ctalkington/node-zip-stream.git"
+  },
+  "bugs": {
+    "url": "https://github.com/ctalkington/node-zip-stream/issues"
+  },
+  "licenses": [
+    {
+      "type": "MIT",
+      "url": "https://github.com/ctalkington/node-zip-stream/blob/master/LICENSE-MIT"
+    }
+  ],
+  "main": "lib/zip-stream.js",
+  "files": [
+    "lib",
+    "LICENSE-MIT"
+  ],
+  "engines": {
+    "node": ">= 0.8.0"
+  },
+  "scripts": {
+    "test": "mocha --reporter dot"
+  },
+  "dependencies": {
+    "compress-commons": "~0.1.0",
+    "lodash": "~2.4.1",
+    "readable-stream": "~1.0.26"
+  },
+  "devDependencies": {
+    "chai": "~1.9.1",
+    "mocha": "~1.18.2",
+    "rimraf": "~2.2.8",
+    "mkdirp": "~0.5.0"
+  },
+  "keywords": [
+    "archive",
+    "stream",
+    "zip-stream",
+    "zip"
+  ],
+  "gitHead": "e877312d0d92ec607482e4851058a50e67c6f597",
+  "_id": "zip-stream@0.4.1",
+  "_shasum": "4ea795a8ce19e9fab49a31d1d0877214159f03a3",
+  "_from": "zip-stream@~0.4.0",
+  "_npmVersion": "1.4.14",
+  "_npmUser": {
+    "name": "ctalkington",
+    "email": "chris@christalkington.com"
+  },
+  "maintainers": [
+    {
+      "name": "ctalkington",
+      "email": "chris@christalkington.com"
+    }
+  ],
+  "dist": {
+    "shasum": "4ea795a8ce19e9fab49a31d1d0877214159f03a3",
+    "tarball": "http://registry.npmjs.org/zip-stream/-/zip-stream-0.4.1.tgz"
+  },
+  "directories": {},
+  "_resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-0.4.1.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/package.json b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/package.json
new file mode 100644
index 0000000..cc66f6d
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/node_modules/archiver/package.json
@@ -0,0 +1,81 @@
+{
+  "name": "archiver",
+  "version": "0.11.0",
+  "description": "a streaming interface for archive generation",
+  "homepage": "https://github.com/ctalkington/node-archiver",
+  "author": {
+    "name": "Chris Talkington",
+    "url": "http://christalkington.com/"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/ctalkington/node-archiver.git"
+  },
+  "bugs": {
+    "url": "https://github.com/ctalkington/node-archiver/issues"
+  },
+  "licenses": [
+    {
+      "type": "MIT",
+      "url": "https://github.com/ctalkington/node-archiver/blob/master/LICENSE-MIT"
+    }
+  ],
+  "main": "lib/archiver.js",
+  "files": [
+    "lib",
+    "LICENSE-MIT"
+  ],
+  "engines": {
+    "node": ">= 0.10.0"
+  },
+  "scripts": {
+    "test": "mocha --reporter dot",
+    "bench": "node benchmark/simple/pack-zip.js"
+  },
+  "dependencies": {
+    "async": "~0.9.0",
+    "buffer-crc32": "~0.2.1",
+    "glob": "~3.2.6",
+    "lazystream": "~0.1.0",
+    "lodash": "~2.4.1",
+    "readable-stream": "~1.0.26",
+    "tar-stream": "~0.4.0",
+    "zip-stream": "~0.4.0"
+  },
+  "devDependencies": {
+    "chai": "~1.9.1",
+    "mocha": "~1.20.1",
+    "rimraf": "~2.2.8",
+    "mkdirp": "~0.5.0",
+    "stream-bench": "~0.1.2"
+  },
+  "keywords": [
+    "archive",
+    "archiver",
+    "stream",
+    "zip",
+    "tar"
+  ],
+  "gitHead": "9f0a2ed21fedca8aec48605d2b8d31cf841cbbc6",
+  "_id": "archiver@0.11.0",
+  "_shasum": "98177da7a6c0192b7f2798f30cd6eab8abd76690",
+  "_from": "archiver@^0.11.0",
+  "_npmVersion": "1.4.14",
+  "_npmUser": {
+    "name": "ctalkington",
+    "email": "chris@christalkington.com"
+  },
+  "maintainers": [
+    {
+      "name": "ctalkington",
+      "email": "chris@christalkington.com"
+    }
+  ],
+  "dist": {
+    "shasum": "98177da7a6c0192b7f2798f30cd6eab8abd76690",
+    "tarball": "http://registry.npmjs.org/archiver/-/archiver-0.11.0.tgz"
+  },
+  "directories": {},
+  "_resolved": "https://registry.npmjs.org/archiver/-/archiver-0.11.0.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/package.json b/node_modules/node-firefox-install-app/node_modules/zip-folder/package.json
new file mode 100644
index 0000000..3ba1bb3
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/package.json
@@ -0,0 +1,56 @@
+{
+  "name": "zip-folder",
+  "version": "1.0.0",
+  "description": "zips a folder and calls your callback when it's done",
+  "main": "index.js",
+  "scripts": {
+    "test": "nodeunit tests.js"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/sole/node-zip-folder"
+  },
+  "keywords": [
+    "zip",
+    "folder"
+  ],
+  "author": {
+    "name": "sole"
+  },
+  "license": "Apache 2",
+  "bugs": {
+    "url": "https://github.com/sole/node-zip-folder/issues"
+  },
+  "homepage": "https://github.com/sole/node-zip-folder",
+  "dependencies": {
+    "archiver": "^0.11.0"
+  },
+  "devDependencies": {
+    "nodeunit": "^0.9.0",
+    "temporary": "0.0.8",
+    "unzip": "^0.1.11"
+  },
+  "gitHead": "0bf8c16b08a1a6efcf5ed48fb4568109dafcd6b8",
+  "_id": "zip-folder@1.0.0",
+  "_shasum": "70a7744fd1789a2feb41ad3419b32e9fd87957b2",
+  "_from": "zip-folder@^1.0.0",
+  "_npmVersion": "2.1.2",
+  "_nodeVersion": "0.10.32",
+  "_npmUser": {
+    "name": "sole",
+    "email": "listas@soledadpenades.com"
+  },
+  "maintainers": [
+    {
+      "name": "sole",
+      "email": "listas@soledadpenades.com"
+    }
+  ],
+  "dist": {
+    "shasum": "70a7744fd1789a2feb41ad3419b32e9fd87957b2",
+    "tarball": "http://registry.npmjs.org/zip-folder/-/zip-folder-1.0.0.tgz"
+  },
+  "directories": {},
+  "_resolved": "https://registry.npmjs.org/zip-folder/-/zip-folder-1.0.0.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-install-app/node_modules/zip-folder/tests.js b/node_modules/node-firefox-install-app/node_modules/zip-folder/tests.js
new file mode 100644
index 0000000..441173e
--- /dev/null
+++ b/node_modules/node-firefox-install-app/node_modules/zip-folder/tests.js
@@ -0,0 +1,99 @@
+var tmp = require('temporary');
+var fs = require('fs');
+var path = require('path');
+var unzip = require('unzip');
+var zipFolder = require('./index');
+
+var txtFileName = 'file.txt';
+var txtFileContents = 'this is a text file';
+var zipFileName = 'archive.zip';
+
+function emptyDirectory(dirName) {
+	var dirFiles = fs.readdirSync(dirName);
+	dirFiles.forEach(function(f) {
+		var entryPath = path.join(dirName, f);
+		var fileStats = fs.statSync(entryPath);
+		if(fileStats.isFile()) {
+			fs.unlink(entryPath);
+		} else {
+			emptyDirectory(entryPath);
+		}
+	});
+}
+
+module.exports = {
+	setUp: function(callback) {
+		this.tmpSrcDir = new tmp.Dir();
+
+		var writePath = this.tmpSrcDir.path;
+
+		fs.writeFileSync(path.join(writePath, txtFileName), txtFileContents);
+		this.txtFileName = txtFileName;
+		this.txtFileContents = txtFileContents;
+
+		this.tmpZipFile = new tmp.File();
+		this.tmpZipExtractionDir = new tmp.Dir();
+
+		zipFolder(writePath, this.tmpZipFile.path, function() {
+
+			callback();
+
+		});
+		
+	},
+
+	tearDown: function(callback) {
+		
+		emptyDirectory(this.tmpSrcDir.path);
+		this.tmpSrcDir.rmdir();
+
+		emptyDirectory(this.tmpZipExtractionDir.path);
+		this.tmpZipExtractionDir.rmdir();
+		
+		this.tmpZipFile.unlink();
+		
+		callback();
+	},
+
+	// Ensure the zip has been created
+	itCreatesTheZipFile: function(test) {
+		test.ok(fs.existsSync(this.tmpZipFile.path), 'zip exists');
+		test.done();
+	},
+
+	// Assume the zip is valid if it can be unzipped
+	// and the unzipped contents are not empty
+	theZipFileIsValid: function(test) {
+
+		test.expect(1);
+
+		var dstPath = this.tmpZipExtractionDir.path;
+
+		fs.createReadStream(this.tmpZipFile.path)
+			.pipe(unzip.Extract({ path: dstPath }))
+			.on('close', function() {
+				var dirList = fs.readdirSync(dstPath);
+				test.ok(dirList.length > 0, 'the zip contains files');
+				test.done();
+			});
+
+	},
+	
+	theZipFileContainsTheRightFiles: function(test) {
+
+		var dstPath = this.tmpZipExtractionDir.path;
+		var txtFileName = this.txtFileName;
+		var txtFilePath = path.join(dstPath, txtFileName);
+		var txtFileContents = this.txtFileContents;
+
+		fs.createReadStream(this.tmpZipFile.path)
+			.pipe(unzip.Extract({ path: dstPath }))
+			.on('close', function() {
+				
+				test.ok(fs.existsSync(txtFilePath), 'txt file exists');
+				test.equals(fs.readFileSync(txtFilePath), txtFileContents, 'contents are the same we put in');
+				test.done();
+			});
+
+	}
+};
diff --git a/node_modules/node-firefox-install-app/package.json b/node_modules/node-firefox-install-app/package.json
new file mode 100644
index 0000000..63cf044
--- /dev/null
+++ b/node_modules/node-firefox-install-app/package.json
@@ -0,0 +1,68 @@
+{
+  "name": "node-firefox-install-app",
+  "version": "1.0.0",
+  "description": "Install an app to a Firefox runtime",
+  "main": "index.js",
+  "scripts": {
+    "gulp": "gulp",
+    "test": "gulp test"
+  },
+  "keywords": [
+    "firefox",
+    "developer tools",
+    "b2g",
+    "firefox os",
+    "fxos",
+    "webapps"
+  ],
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/mozilla/node-firefox-install-app.git"
+  },
+  "author": {
+    "name": "Mozilla",
+    "url": "https://mozilla.org/"
+  },
+  "license": "Apache 2",
+  "bugs": {
+    "url": "https://github.com/mozilla/node-firefox-install-app/issues"
+  },
+  "homepage": "https://github.com/mozilla/node-firefox-install-app/",
+  "dependencies": {
+    "es6-promise": "^2.0.1",
+    "node-uuid": "^1.4.2",
+    "temporary": "0.0.8",
+    "zip-folder": "^1.0.0"
+  },
+  "devDependencies": {
+    "gulp": "^3.8.10",
+    "mocha": "^1.21.4",
+    "node-firefox-build-tools": "^0.2.0",
+    "node-firefox-connect": "^1.0.0",
+    "node-firefox-start-simulator": "^1.1.0",
+    "should": "^4.0.4"
+  },
+  "gitHead": "afa009f560dcec88d790eba679127d8a0d8549d0",
+  "_id": "node-firefox-install-app@1.0.0",
+  "_shasum": "a27203383d139086eeace6f9e4156229118c99dd",
+  "_from": "node-firefox-install-app@",
+  "_npmVersion": "2.1.6",
+  "_nodeVersion": "0.10.33",
+  "_npmUser": {
+    "name": "sole",
+    "email": "listas@soledadpenades.com"
+  },
+  "maintainers": [
+    {
+      "name": "sole",
+      "email": "listas@soledadpenades.com"
+    }
+  ],
+  "dist": {
+    "shasum": "a27203383d139086eeace6f9e4156229118c99dd",
+    "tarball": "http://registry.npmjs.org/node-firefox-install-app/-/node-firefox-install-app-1.0.0.tgz"
+  },
+  "directories": {},
+  "_resolved": "https://registry.npmjs.org/node-firefox-install-app/-/node-firefox-install-app-1.0.0.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-install-app/test/sampleapp/build/app.zip b/node_modules/node-firefox-install-app/test/sampleapp/build/app.zip
new file mode 100644
index 0000000..8609b30
--- /dev/null
+++ b/node_modules/node-firefox-install-app/test/sampleapp/build/app.zip
Binary files differ
diff --git a/node_modules/node-firefox-install-app/test/sampleapp/icons/icon.svg b/node_modules/node-firefox-install-app/test/sampleapp/icons/icon.svg
new file mode 100644
index 0000000..834968c
--- /dev/null
+++ b/node_modules/node-firefox-install-app/test/sampleapp/icons/icon.svg
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:sketch="http://www.bohemiancoding.com/sketch/ns"
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="128px"
+   height="128px"
+   viewBox="0 0 128 128"
+   version="1.1"
+   id="svg2"
+   inkscape:version="0.48.2 r9819"
+   sodipodi:docname="icon.svg">
+  <metadata
+     id="metadata14">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1142"
+     inkscape:window-height="849"
+     id="namedview12"
+     showgrid="false"
+     inkscape:zoom="1.84375"
+     inkscape:cx="-32.542373"
+     inkscape:cy="64"
+     inkscape:window-x="672"
+     inkscape:window-y="146"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="Page-1" />
+  <!-- Generator: Sketch 3.0.2 (7799) - http://www.bohemiancoding.com/sketch -->
+  <title
+     id="title4">empty</title>
+  <description
+     id="description6">Created with Sketch.</description>
+  <defs
+     id="defs8">
+    <linearGradient
+       id="linearGradient3761">
+      <stop
+         style="stop-color:#4e748b;stop-opacity:1;"
+         offset="0"
+         id="stop3763" />
+      <stop
+         style="stop-color:#393e3f;stop-opacity:1;"
+         offset="1"
+         id="stop3765" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3761"
+       id="linearGradient3767"
+       x1="129.66949"
+       y1="65.8983"
+       x2="129.66949"
+       y2="188.59828"
+       gradientUnits="userSpaceOnUse" />
+  </defs>
+  <path
+     sodipodi:type="arc"
+     style="fill:url(#linearGradient3767);fill-opacity:1;fill-rule:evenodd;stroke:none"
+     id="path2991"
+     sodipodi:cx="129.35593"
+     sodipodi:cy="128.27118"
+     sodipodi:rx="63.18644"
+     sodipodi:ry="63.18644"
+     d="m 192.54237,128.27118 a 63.18644,63.18644 0 1 1 -126.372883,0 63.18644,63.18644 0 1 1 126.372883,0 z"
+     transform="translate(-65.355927,-64.271179)" />
+  <g
+     id="Page-1"
+     sketch:type="MSPage"
+     transform="matrix(0.9,0,0,0.9,6.4,6.4)"
+     style="fill:none;stroke:none">
+    <circle
+       id="empty"
+       sketch:type="MSShapeGroup"
+       cx="64"
+       cy="64"
+       r="58"
+       sodipodi:cx="64"
+       sodipodi:cy="64"
+       sodipodi:rx="58"
+       sodipodi:ry="58"
+       style="stroke:#bcc6c5;stroke-width:11;stroke-linecap:round;stroke-dasharray:20, 20;fill:none;fill-opacity:1"
+       d="M 122,64 C 122,96.032515 96.032515,122 64,122 31.967485,122 6,96.032515 6,64 6,31.967485 31.967485,6 64,6 c 32.032515,0 58,25.967485 58,58 z" />
+  </g>
+</svg>
diff --git a/node_modules/node-firefox-install-app/test/sampleapp/icons/icon128x128.png b/node_modules/node-firefox-install-app/test/sampleapp/icons/icon128x128.png
new file mode 100644
index 0000000..7c672c5
--- /dev/null
+++ b/node_modules/node-firefox-install-app/test/sampleapp/icons/icon128x128.png
Binary files differ
diff --git a/node_modules/node-firefox-install-app/test/sampleapp/icons/icon16x16.png b/node_modules/node-firefox-install-app/test/sampleapp/icons/icon16x16.png
new file mode 100644
index 0000000..abf254e
--- /dev/null
+++ b/node_modules/node-firefox-install-app/test/sampleapp/icons/icon16x16.png
Binary files differ
diff --git a/node_modules/node-firefox-install-app/test/sampleapp/icons/icon48x48.png b/node_modules/node-firefox-install-app/test/sampleapp/icons/icon48x48.png
new file mode 100644
index 0000000..451655b
--- /dev/null
+++ b/node_modules/node-firefox-install-app/test/sampleapp/icons/icon48x48.png
Binary files differ
diff --git a/node_modules/node-firefox-install-app/test/sampleapp/icons/icon60x60.png b/node_modules/node-firefox-install-app/test/sampleapp/icons/icon60x60.png
new file mode 100644
index 0000000..c7d4115
--- /dev/null
+++ b/node_modules/node-firefox-install-app/test/sampleapp/icons/icon60x60.png
Binary files differ
diff --git a/node_modules/node-firefox-install-app/test/sampleapp/index.html b/node_modules/node-firefox-install-app/test/sampleapp/index.html
new file mode 100644
index 0000000..2c9d1a3
--- /dev/null
+++ b/node_modules/node-firefox-install-app/test/sampleapp/index.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta charset="utf-8">
+        <title>Sample app</title>
+        <meta name="description" content="A privileged app stub">
+        <meta name="viewport" content="width=device-width">
+    </head>
+    <body>
+        Sample app
+    </body>
+</html>
diff --git a/node_modules/node-firefox-install-app/test/sampleapp/manifest.webapp b/node_modules/node-firefox-install-app/test/sampleapp/manifest.webapp
new file mode 100644
index 0000000..b2e6bc3
--- /dev/null
+++ b/node_modules/node-firefox-install-app/test/sampleapp/manifest.webapp
@@ -0,0 +1,22 @@
+{
+  "version": "0.1.0",
+  "name": "fxos-deploy-sample",
+  "description": "Sample app",
+  "launch_path": "/index.html",
+  "icons": {
+    "16": "/icons/icon16x16.png",
+    "48": "/icons/icon48x48.png",
+    "60": "/icons/icon60x60.png",
+    "128": "/icons/icon128x128.png"
+  },
+  "developer": {
+    "name": "Your name",
+    "url": "http://example.com"
+  },
+  "type": "privileged",
+  "permissions": {},
+  "installs_allowed_from": [
+    "*"
+  ],
+  "default_locale": "en"
+}
\ No newline at end of file
diff --git a/node_modules/node-firefox-install-app/test/test.js b/node_modules/node-firefox-install-app/test/test.js
new file mode 100644
index 0000000..d1e5463
--- /dev/null
+++ b/node_modules/node-firefox-install-app/test/test.js
@@ -0,0 +1,70 @@
+var assert = require("assert");
+var should = require("should");
+var Deploy = require("../");
+var DeployCmd = require("../command");
+var Ports = require("fx-ports");
+var Connect = require("fxos-connect");
+var Q = require('q');
+
+describe('fxos-deploy', function(){
+  this.timeout(10000);
+  afterEach(function() {
+    Ports({b2g:true}, function(err, instances) {
+      instances.forEach(function(i) {
+        process.kill(i.pid);
+      });
+    });
+  });
+
+  describe('when no open simulator', function(){
+
+    it('should return app id', function(done) {
+
+      Connect(function(err, sim) {
+        return Deploy({
+          manifestURL: './test/sampleapp/manifest.webapp',
+          zip: './test/sampleapp/build/app.zip',
+          client: sim.client
+        })
+        .then(function(sim) {
+          sim.should.be.type('string');
+        })
+        .then(done)
+        .fail(done);
+      });
+
+    });
+
+  });
+
+
+  describe('fxos-deploy/command', function(done) {
+
+    it('should deploy app and disconnect', function(done) {
+      DeployCmd({
+        manifestURL: './test/sampleapp/manifest.webapp',
+        zip: './test/sampleapp/build/app.zip'
+      }, function(err, result, next) {
+        
+        result.value.should.be.type('string');
+        result.client.should.be.ok;
+        next();
+      }, done);
+    });
+
+    it('should connect to a specific port', function(done) {
+      DeployCmd({
+        manifestURL: './test/sampleapp/manifest.webapp',
+        zip: './test/sampleapp/build/app.zip',
+        port: 8181
+      }, function(err, result, next) {
+        Ports({b2g:true}, function(err, instances) {
+          instances[0].port.should.equal(8181);
+          next();
+        })
+      }, done);
+    });
+
+  });
+
+});
\ No newline at end of file
diff --git a/node_modules/node-firefox-launch-app/.editorconfig b/node_modules/node-firefox-launch-app/.editorconfig
new file mode 100644
index 0000000..e717f5e
--- /dev/null
+++ b/node_modules/node-firefox-launch-app/.editorconfig
@@ -0,0 +1,13 @@
+# http://editorconfig.org
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.md]
+trim_trailing_whitespace = false
diff --git a/node_modules/node-firefox-launch-app/.npmignore b/node_modules/node-firefox-launch-app/.npmignore
new file mode 100644
index 0000000..23eeb90
--- /dev/null
+++ b/node_modules/node-firefox-launch-app/.npmignore
@@ -0,0 +1,4 @@
+node_modules
+npm-debug.log
+.DS_Store
+*.sw[po]
diff --git a/node_modules/node-firefox-launch-app/.travis.yml b/node_modules/node-firefox-launch-app/.travis.yml
new file mode 100644
index 0000000..0394ba7
--- /dev/null
+++ b/node_modules/node-firefox-launch-app/.travis.yml
@@ -0,0 +1,3 @@
+language: node_js
+node_js:
+- 0.10
diff --git a/node_modules/node-firefox-launch-app/LICENSE b/node_modules/node-firefox-launch-app/LICENSE
new file mode 100644
index 0000000..a7c6b5e
--- /dev/null
+++ b/node_modules/node-firefox-launch-app/LICENSE
@@ -0,0 +1,13 @@
+Copyright 2015 Mozilla
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/node_modules/node-firefox-launch-app/README.md b/node_modules/node-firefox-launch-app/README.md
new file mode 100644
index 0000000..0c9fc91
--- /dev/null
+++ b/node_modules/node-firefox-launch-app/README.md
@@ -0,0 +1,108 @@
+# node-firefox-launch-app [![Build Status](https://secure.travis-ci.org/mozilla/node-firefox-launch-app.png?branch=master)](http://travis-ci.org/mozilla/node-firefox-launch-app)
+
+> Launch an installed app on a runtime.
+
+This is part of the [node-firefox](https://github.com/mozilla/node-firefox) project.
+
+*NOTE: This module is super experimental and the API is not totally stable yet. Use under your own responsibility.*
+
+## Installation
+
+### From git
+
+```bash
+git clone https://github.com/mozilla/node-firefox-launch-app.git
+cd node-firefox-launch-app
+npm install
+```
+
+If you want to update later on:
+
+```bash
+cd node-firefox-launch-app
+git pull origin master
+npm install
+```
+
+### npm
+
+```bash
+npm install node-firefox-launch-app
+```
+
+## Usage
+
+```javascript
+launchApp(options) // returns a Promise
+```
+
+where `options` is a plain `Object` which must contain the following:
+
+* `manifestURL`: the manifest URL *in the client* (you must have obtained this after a call to <a href="https://github.com/mozilla/node-firefox-find-app"><tt>node-firefox-find-app</tt></a>. It's something that looks like: `manifestURL: 'app://13ab1444-736d-8c4b-83a6-b83afb5f1ea4/manifest.webapp'` in the result from `findApp`.
+* `client`: the remote client where we want to launch this app
+
+If no `options` are provided, or if `options` is an empty `Object` (`{}`), then `launchApp` will fail (how can you launch *you don't know what app exactly* in *you don't know where*?)
+
+
+### Installing and launching a packaged app on a simulator
+
+```javascript
+var startSimulator = require('node-firefox-start-simulator');
+var connect = require('node-firefox-connect');
+var installApp = require('node-firefox-install-app');
+var findApp = require('node-firefox-find-app');
+var launchApp = require('node-firefox-launch-app');
+
+var manifestJSON = loadJSON(path.join(appPath, 'manifest.webapp'));
+
+startSimulator().then(function(simulator) {
+  connect(simulator.port).then(function(client) {
+    installApp({
+      appPath: appPath,
+      client: client
+    }).then(function() {
+      findApp({
+        client: client,
+        manifest: manifestJSON
+      }).then(function(apps) {
+        if(apps.length > 0) {
+          var firstApp = apps[0];
+          launchApp({ manifestURL: firstApp.manifestURL });
+        }
+      });
+    });
+  });
+});
+
+```
+
+You can have a look at the `examples` folder for a complete example.
+
+## Running the tests
+
+After installing, you can simply run the following from the module folder:
+
+```bash
+npm test
+```
+
+To add a new unit test file, create a new file in the `tests/unit` folder. Any file that matches `test.*.js` will be run as a test by the appropriate test runner, based on the folder location.
+
+We use `gulp` behind the scenes to run the test; if you don't have it installed globally you can use `npm gulp` from inside the project's root folder to run `gulp`.
+
+### Code quality and style
+
+Because we have multiple contributors working on our projects, we value consistent code styles. It makes it easier to read code written by many people! :-)
+
+Our tests include unit tests as well as code quality ("linting") tests that make sure our test pass a style guide and [JSHint](http://jshint.com/). Instead of submitting code with the wrong indentation or a different style, run the tests and you will be told where your code quality/style differs from ours and instructions on how to fix it.
+
+## License
+
+This program is free software; it is distributed under an
+[Apache License](https://github.com/mozilla/node-firefox-launch-app/blob/master/LICENSE).
+
+## Copyright
+
+Copyright (c) 2015 [Mozilla](https://mozilla.org)
+([Contributors](https://github.com/mozilla/node-firefox-launch-app/graphs/contributors)).
+
diff --git a/node_modules/node-firefox-launch-app/examples/sampleApp/app.js b/node_modules/node-firefox-launch-app/examples/sampleApp/app.js
new file mode 100644
index 0000000..dc7e45e
--- /dev/null
+++ b/node_modules/node-firefox-launch-app/examples/sampleApp/app.js
@@ -0,0 +1,3 @@
+window.addEventListener("load", function() {
+  console.log("Hello World!");
+});
diff --git a/node_modules/node-firefox-launch-app/examples/sampleApp/icons/icon128x128.png b/node_modules/node-firefox-launch-app/examples/sampleApp/icons/icon128x128.png
new file mode 100644
index 0000000..7db00d7
--- /dev/null
+++ b/node_modules/node-firefox-launch-app/examples/sampleApp/icons/icon128x128.png
Binary files differ
diff --git a/node_modules/node-firefox-launch-app/examples/sampleApp/icons/icon16x16.png b/node_modules/node-firefox-launch-app/examples/sampleApp/icons/icon16x16.png
new file mode 100644
index 0000000..1e751ce
--- /dev/null
+++ b/node_modules/node-firefox-launch-app/examples/sampleApp/icons/icon16x16.png
Binary files differ
diff --git a/node_modules/node-firefox-launch-app/examples/sampleApp/icons/icon48x48.png b/node_modules/node-firefox-launch-app/examples/sampleApp/icons/icon48x48.png
new file mode 100644
index 0000000..acc33ff
--- /dev/null
+++ b/node_modules/node-firefox-launch-app/examples/sampleApp/icons/icon48x48.png
Binary files differ
diff --git a/node_modules/node-firefox-launch-app/examples/sampleApp/icons/icon60x60.png b/node_modules/node-firefox-launch-app/examples/sampleApp/icons/icon60x60.png
new file mode 100644
index 0000000..bc14314
--- /dev/null
+++ b/node_modules/node-firefox-launch-app/examples/sampleApp/icons/icon60x60.png
Binary files differ
diff --git a/node_modules/node-firefox-launch-app/examples/sampleApp/index.html b/node_modules/node-firefox-launch-app/examples/sampleApp/index.html
new file mode 100644
index 0000000..d6bed3f
--- /dev/null
+++ b/node_modules/node-firefox-launch-app/examples/sampleApp/index.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1">
+
+    <title>node-firefox-install-app</title>
+
+    <style>
+      body {
+        border: 1px solid black;
+      }
+    </style>
+
+    <!-- Inline scripts are forbidden in Firefox OS apps (CSP restrictions),
+         so we use a script file. -->
+    <script src="app.js" defer></script>
+
+  </head>
+
+  <body>
+    <!-- This code is in the public domain. Enjoy! -->
+    <h1><tt>node-firefox-install-app</tt></h1>
+    <p>This is a simple test app for demonstrating how to use this module.</p>
+  </body>
+
+</html>
diff --git a/node_modules/node-firefox-launch-app/examples/sampleApp/manifest.webapp b/node_modules/node-firefox-launch-app/examples/sampleApp/manifest.webapp
new file mode 100644
index 0000000..5c2af15
--- /dev/null
+++ b/node_modules/node-firefox-launch-app/examples/sampleApp/manifest.webapp
@@ -0,0 +1,11 @@
+{
+  "name": "node-firefox-launch-app",
+  "description": "A test app for this module",
+  "launch_path": "/index.html",
+  "icons": {
+    "16": "/icons/icon16x16.png",
+    "48": "/icons/icon48x48.png",
+    "60": "/icons/icon60x60.png",
+    "128": "/icons/icon128x128.png"
+  }
+}
diff --git a/node_modules/node-firefox-launch-app/examples/usage.js b/node_modules/node-firefox-launch-app/examples/usage.js
new file mode 100644
index 0000000..bdb9a5b
--- /dev/null
+++ b/node_modules/node-firefox-launch-app/examples/usage.js
@@ -0,0 +1,55 @@
+'use strict';
+
+var path = require('path');
+var fs = require('fs');
+var startSimulator = require('node-firefox-start-simulator');
+var connect = require('node-firefox-connect');
+var installApp = require('node-firefox-install-app');
+var findApp = require('node-firefox-find-app');
+var launchApp = require('..');
+
+var appPath = path.join(__dirname, 'sampleApp');
+var manifest = loadJSON(path.join(appPath, 'manifest.webapp'));
+
+// This example will start a Firefox OS simulator,
+// install the sample app on it,
+// then find and launch the installed app.
+// You will need to have at least a simulator
+// already installed!
+
+startSimulator().then(function(simulator) {
+  connect(simulator.port).then(function(client) {
+    installApp({
+      appPath: appPath,
+      client: client
+    }).then(function(appId) {
+      console.log('App was installed with appId = ', appId);
+      findApp({
+        manifest: manifest,
+        client: client
+      }).then(function(apps) {
+        if (apps.length > 0) {
+          var firstApp = apps[0];
+          launchApp({
+            client: client,
+            manifestURL: firstApp.manifestURL
+          }).then(function(result) {
+            console.log('Launched app', result);
+          }, function(err) {
+            console.error('Could not launch app', err);
+          });
+        }
+      }, function(e) {
+        console.error('Could not find app', e);
+      });
+    }, function(error) {
+      console.error('App could not be installed: ', error);
+    });
+  });
+});
+
+
+function loadJSON(path) {
+  var data = fs.readFileSync(path, 'utf8');
+  return JSON.parse(data);
+}
diff --git a/node_modules/node-firefox-launch-app/gulpfile.js b/node_modules/node-firefox-launch-app/gulpfile.js
new file mode 100644
index 0000000..75941db
--- /dev/null
+++ b/node_modules/node-firefox-launch-app/gulpfile.js
@@ -0,0 +1,4 @@
+var gulp = require('gulp');
+var buildTools = require('node-firefox-build-tools');
+
+buildTools.loadGulpTasks(gulp);
diff --git a/node_modules/node-firefox-launch-app/index.js b/node_modules/node-firefox-launch-app/index.js
new file mode 100644
index 0000000..074a422
--- /dev/null
+++ b/node_modules/node-firefox-launch-app/index.js
@@ -0,0 +1,59 @@
+'use strict';
+
+// See https://github.com/jshint/jshint/issues/1747 for context
+/* global -Promise */
+var Promise = require('es6-promise').Promise;
+
+module.exports = function(options) {
+
+  options = options || {};
+
+  var manifestURL = options.manifestURL;
+  var client = options.client;
+
+  return new Promise(function(resolve, reject) {
+
+    if (manifestURL === undefined || client === undefined) {
+      return reject(new Error('App manifestURL and client are required to launch an app'));
+    }
+
+    getWebAppsActor(client).then(function(webAppsActor) {
+      launchApp(webAppsActor, manifestURL)
+        .then(function(result) {
+          resolve(result);
+        }, function(err) {
+          reject(err);
+        });
+    });
+
+  });
+
+};
+
+
+function getWebAppsActor(client) {
+  return new Promise(function(resolve, reject) {
+
+    client.getWebapps(function(err, webAppsActor) {
+      if (err) {
+        return reject(err);
+      }
+      resolve(webAppsActor);
+    });
+
+  });
+}
+
+
+function launchApp(webAppsActor, appManifestURL) {
+  return new Promise(function(resolve, reject) {
+    webAppsActor.launch(appManifestURL, function(err, response) {
+      if (err) {
+        return reject(err);
+      }
+      resolve(response);
+    });
+  });
+}
+
+
diff --git a/node_modules/node-firefox-launch-app/package.json b/node_modules/node-firefox-launch-app/package.json
new file mode 100644
index 0000000..9e824ca
--- /dev/null
+++ b/node_modules/node-firefox-launch-app/package.json
@@ -0,0 +1,77 @@
+{
+  "name": "node-firefox-launch-app",
+  "version": "1.1.0",
+  "description": "Launch an app on a Firefox runtime",
+  "main": "index.js",
+  "scripts": {
+    "gulp": "gulp",
+    "test": "gulp test"
+  },
+  "keywords": [
+    "firefox",
+    "developer tools",
+    "b2g",
+    "firefox os",
+    "fxos",
+    "webapps"
+  ],
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/mozilla/node-firefox-launch-app.git"
+  },
+  "author": {
+    "name": "Mozilla",
+    "url": "https://mozilla.org/"
+  },
+  "license": "Apache 2",
+  "bugs": {
+    "url": "https://github.com/mozilla/node-firefox-launch-app/issues"
+  },
+  "homepage": "https://github.com/mozilla/node-firefox-launch-app/",
+  "dependencies": {
+    "es6-promise": "^2.0.1"
+  },
+  "devDependencies": {
+    "gulp": "^3.8.10",
+    "node-firefox-build-tools": "^0.2.0",
+    "node-firefox-connect": "^1.0.0",
+    "node-firefox-find-app": "^1.0.0",
+    "node-firefox-install-app": "^1.0.0",
+    "node-firefox-start-simulator": "^1.1.0",
+    "nodemock": "^0.3.4"
+  },
+  "gitHead": "353d4f9b00e9f827dfb789c2ae9fdc04a33d3222",
+  "_id": "node-firefox-launch-app@1.1.0",
+  "_shasum": "90a0f1541dc2b565cb46170bed4b626dc77f1ef5",
+  "_from": "node-firefox-launch-app@^1.1.0",
+  "_npmVersion": "2.1.9",
+  "_nodeVersion": "0.10.33",
+  "_npmUser": {
+    "name": "tofumatt",
+    "email": "matt@lonelyvegan.com"
+  },
+  "maintainers": [
+    {
+      "name": "sole",
+      "email": "listas@soledadpenades.com"
+    },
+    {
+      "name": "brittanystoroz",
+      "email": "brittanystoroz@gmail.com"
+    },
+    {
+      "name": "tofumatt",
+      "email": "matt@lonelyvegan.com"
+    },
+    {
+      "name": "lmorchard",
+      "email": "me@lmorchard.com"
+    }
+  ],
+  "dist": {
+    "shasum": "90a0f1541dc2b565cb46170bed4b626dc77f1ef5",
+    "tarball": "http://registry.npmjs.org/node-firefox-launch-app/-/node-firefox-launch-app-1.1.0.tgz"
+  },
+  "directories": {},
+  "_resolved": "https://registry.npmjs.org/node-firefox-launch-app/-/node-firefox-launch-app-1.1.0.tgz"
+}
diff --git a/node_modules/node-firefox-launch-app/tests/unit/test.index.js b/node_modules/node-firefox-launch-app/tests/unit/test.index.js
new file mode 100644
index 0000000..922077c
--- /dev/null
+++ b/node_modules/node-firefox-launch-app/tests/unit/test.index.js
@@ -0,0 +1,61 @@
+'use strict';
+
+var nodemock = require('nodemock');
+
+var launchApp = require('../../index');
+
+module.exports = {
+
+  'launchApp() should fail when missing client option': function(test) {
+    launchApp({
+      manifestURL: '...'
+    }).then(function(results) {
+      test.ok(false);
+      test.done();
+    }).catch(function(err) {
+      test.done();
+    });
+  },
+
+  'launchApp() should fail when missing manifestURL option': function(test) {
+    launchApp({
+      client: {}
+    }).then(function(results) {
+      test.ok(false);
+      test.done();
+    }).catch(function(err) {
+      test.done();
+    });
+  },
+
+  'launchApp() should launch a given app': function(test) {
+
+    var MANIFEST_URL = 'app://8675309/manifest.webapp';
+    var LAUNCH_RESPONSE = 'expected result';
+
+    var mocked = nodemock
+      .mock('launch')
+        .takes(MANIFEST_URL, function() {})
+        .calls(1, [null, LAUNCH_RESPONSE]);
+
+    var mockClient = {
+      getWebapps: function(webappsCallback) {
+        webappsCallback(null, { launch: mocked.launch });
+      }
+    };
+
+    launchApp({
+      client: mockClient,
+      manifestURL: MANIFEST_URL
+    }).then(function(result) {
+      test.ok(mocked.assert());
+      test.equal(result, LAUNCH_RESPONSE);
+      test.done();
+    }).catch(function(err) {
+      test.ifError(err);
+      test.done();
+    });
+
+  }
+
+};
diff --git a/node_modules/node-firefox-start-simulator/.npmignore b/node_modules/node-firefox-start-simulator/.npmignore
new file mode 100644
index 0000000..b0e3907
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/.npmignore
@@ -0,0 +1,3 @@
+node_modules
+npm-debug.log
+.DS_Store
diff --git a/node_modules/node-firefox-start-simulator/.travis.yml b/node_modules/node-firefox-start-simulator/.travis.yml
new file mode 100644
index 0000000..0394ba7
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/.travis.yml
@@ -0,0 +1,3 @@
+language: node_js
+node_js:
+- 0.10
diff --git a/node_modules/node-firefox-start-simulator/LICENSE b/node_modules/node-firefox-start-simulator/LICENSE
new file mode 100644
index 0000000..f25d17f
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/LICENSE
@@ -0,0 +1,10 @@
+Copyright 2014 Mozilla
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/node_modules/node-firefox-start-simulator/README.md b/node_modules/node-firefox-start-simulator/README.md
new file mode 100644
index 0000000..e3ef68d
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/README.md
@@ -0,0 +1,137 @@
+# node-firefox-start-simulator [![Build Status](https://secure.travis-ci.org/mozilla/node-firefox-start-simulator.png?branch=master)](http://travis-ci.org/mozilla/node-firefox-start-simulator)
+
+> Start a Firefox OS simulator.
+
+[![Install with NPM](https://nodei.co/npm/node-firefox-start-simulator.png?downloads=true&stars=true)](https://nodei.co/npm/node-firefox-start-simulator/)
+
+This is part of the [node-firefox](https://github.com/mozilla/node-firefox) project.
+
+## Installation
+
+### From git
+
+```sh
+git clone https://github.com/mozilla/node-firefox-start-simulator.git
+cd node-firefox-start-simulator
+npm install
+```
+
+If you want to update later on:
+
+```sh
+cd node-firefox-start-simulator
+git pull origin master
+npm install
+```
+
+### npm
+
+```sh
+npm install node-firefox-start-simulator
+```
+
+## Usage
+
+```javascript
+var startSimulator = require('node-firefox-start-simulator');
+
+// `startSimulator` returns a Promise
+startSimulator(options).then(function(simulator) {
+
+});
+```
+
+where `options` is a plain `Object` with any of the following:
+
+* `detached`: start the simulator as a detached process. If our script is killed, the simulator will still be running.
+* `port`: make the simulator listen to this port for debugging. If not specified, we'll find an available port.
+* `version`: start a simulator in this version. If not specified, we'll start the first simulator that we can find.
+* `verbose`: pipe the output from the simulator to standard I/O. For example, you'll get JavaScript `console.log` messages executed in the simulator.
+
+and `simulator` is an object containing:
+
+* `binary`: path to the simulator binary
+* `profile`: path to the simulator profile
+* `pid`: process id
+* `process`: the actual process object
+* `port`: the port where the simulator is listening for debugging connections
+
+## Examples
+
+### Start any simulator on the first available port
+
+```javascript
+var startSimulator = require('node-firefox-start-simulator');
+
+startSimulator().then(function(simulator) {
+  console.log('Started simulator at port', simulator.port);
+}, function(err) {
+  console.log('Error starting a simulator', err);
+});
+
+```
+
+Have a look at the `examples` folder for more!
+
+<!-- These examples need updating to the Promise style and what we're actually returning
+#### Start a simulator on a given port, connect and return client
+
+Start a FirefoxOS simulator and connect to it through [firefox-client](https://github.com/harthur/firefox-client) by returning `client`.
+```javascript
+var start = require('./node-firefox-start-simulator');
+start({ port: 1234, connect: true }, function(err, sim) {
+  // Let's show for example all the running apps
+  sim.client.getWebapps(function(err, webapps) {
+    webapps.listRunningApps(function(err, apps) {
+      console.log("Running apps:", apps);
+    });
+  });
+})
+```
+
+#### Start a simulator on known port without connecting
+Just start a FirefoxOS simulator without opening a connection:
+
+```javascript
+var start = require('./node-firefox-start-simulator');
+start({ port: 1234, connect: false }, function(err, sim) {
+  // Let's show for example all the running apps
+  sim.client.connect(1234, function() {
+    client.getWebapps(function(err, webapps) {
+      webapps.listRunningApps(function(err, apps) {
+        console.log("Running apps:", apps);
+      });
+    });
+  });
+})
+```
+
+#### Start and kill simulator
+
+```javascript
+var start = require('./node-firefox-start-simulator');
+start({ connect: true }, function(err, sim) {
+  sim.client.disconnect();
+  process.kill(sim.pid);
+})
+```
+
+#### Force start a simulator
+
+```javascript
+var start = require('./node-firefox-start-simulator');
+start({ connect: true, force: true }, function(err, sim) {
+  sim.client.disconnect();
+  process.kill(sim.pid);
+})
+```
+-->
+
+## Documentation
+
+If you want to contribute to this module, it might be interesting to have a look at the way WebIDE launches the simulator. The code for this is in [simulator-process.js](https://dxr.mozilla.org/mozilla-central/source/b2g/simulator/lib/simulator-process.js). Whenever possible, we want to mimic the WebIDE experience as closely as possible.
+
+## History
+
+This is based on initial work on fxos-start by Nicola Greco.
+
diff --git a/node_modules/node-firefox-start-simulator/examples/startAndListApps.js b/node_modules/node-firefox-start-simulator/examples/startAndListApps.js
new file mode 100644
index 0000000..c913df7
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/examples/startAndListApps.js
@@ -0,0 +1,29 @@
+'use strict';
+
+var startSimulator = require('../index');
+var FirefoxClient = require('firefox-client');
+
+startSimulator({ port: 8002 }).then(function(simulator) {
+
+  var client = new FirefoxClient();
+  client.connect(simulator.port, function(err, result) {
+
+    // Wait one second for the simulator to finish launching its apps
+    setTimeout(function() {
+
+      // And then list them!
+      client.getWebapps(function(err, webapps) {
+        webapps.listRunningApps(function(err, apps) {
+          console.log('Running apps:', apps);
+          client.disconnect();
+          process.kill(simulator.pid);
+        });
+      });
+      
+    }, 1000);
+ 
+  });
+
+}, function(err) {
+  console.error('Error starting simulator', err);
+});
diff --git a/node_modules/node-firefox-start-simulator/examples/startDetached.js b/node_modules/node-firefox-start-simulator/examples/startDetached.js
new file mode 100644
index 0000000..2492946
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/examples/startDetached.js
@@ -0,0 +1,15 @@
+'use strict';
+
+var startSimulator = require('../');
+
+// Start any simulator, in detached and verbose mode
+startSimulator({
+  detached: true,
+  verbose: true
+}).then(function(results) {
+  console.log('Started simulator in detached mode, port = ' + results.port);
+  console.log(results);
+}, function(err) {
+  console.error('Error starting simulator', err);
+});
+
diff --git a/node_modules/node-firefox-start-simulator/examples/startSpecificSimulatorVersion.js b/node_modules/node-firefox-start-simulator/examples/startSpecificSimulatorVersion.js
new file mode 100644
index 0000000..1414fac
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/examples/startSpecificSimulatorVersion.js
@@ -0,0 +1,17 @@
+'use strict';
+
+var startSimulator = require('../');
+
+// Start a simulator in version 2.2, in verbose mode
+// NOTE: if you don't have a simulator in this version installed,
+// this example won't work and will print an error instead.
+startSimulator({
+  verbose: true,
+  version: '2.2'
+}).then(function(simulator) {
+  console.log('simulator started', simulator);
+}, function(err) {
+  console.error('Error starting simulator', err);
+});
+
+
diff --git a/node_modules/node-firefox-start-simulator/gulpfile.js b/node_modules/node-firefox-start-simulator/gulpfile.js
new file mode 100644
index 0000000..75941db
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/gulpfile.js
@@ -0,0 +1,4 @@
+var gulp = require('gulp');
+var buildTools = require('node-firefox-build-tools');
+
+buildTools.loadGulpTasks(gulp);
diff --git a/node_modules/node-firefox-start-simulator/index.js b/node_modules/node-firefox-start-simulator/index.js
new file mode 100644
index 0000000..7b081ea
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/index.js
@@ -0,0 +1,263 @@
+'use strict';
+
+// See https://github.com/jshint/jshint/issues/1747 for context
+/* global -Promise */
+var Promise = require('es6-promise').Promise;
+var net = require('net');
+var spawn = require('child_process').spawn;
+var fs = require('fs');
+var portFinder = require('portfinder');
+var findSimulators = require('node-firefox-find-simulators');
+
+module.exports = startSimulator;
+
+function startSimulator(options) {
+
+  options = options || {};
+  var detached = options.detached ? true : false;
+  var verbose = options.verbose ? true : false;
+  var port = options.port;
+  var timeout = options.timeout || 25000;
+
+  var simulatorOptions = {};
+  if (options.version) {
+    simulatorOptions.version = options.version;
+  }
+
+  return new Promise(function(resolve, reject) {
+
+    Promise.all([findSimulator(simulatorOptions), findAvailablePort(port)]).then(function(results) {
+
+      var simulator = results[0];
+      port = results[1];
+
+      launchSimulatorAndWaitUntilReady({
+        binary: simulator.bin,
+        profile: simulator.profile,
+        port: port,
+        detached: detached,
+        verbose: verbose,
+        timeout: timeout,
+        version: simulator.version
+      }).then(function(simulatorDetails) {
+        resolve(simulatorDetails);
+      }, function(simulatorLaunchError) {
+        reject(simulatorLaunchError);
+      });
+
+    }, function(error) {
+      reject(error);
+    });
+
+  });
+}
+
+// Find a simulator that matches the options
+function findSimulator(options) {
+
+  return new Promise(function(resolve, reject) {
+
+    findSimulators(options).then(function(results) {
+
+      if (!results || results.length === 0) {
+        reject(new Error('No simulators installed, or cannot find them'));
+      }
+      
+      // just returning the first result for now
+      resolve(results[0]);
+
+    }, function(error) {
+      reject(error);
+    });
+
+  });
+
+}
+
+
+function findAvailablePort(preferredPort) {
+
+  return new Promise(function(resolve, reject) {
+
+    // Start searching with the preferred port, if specified
+    if (preferredPort !== undefined) {
+      portFinder.basePort = preferredPort;
+    }
+    
+    portFinder.getPort({
+      host: '127.0.0.1'
+    }, function(err, port) {
+      if (err) {
+        reject(err);
+      } else {
+        resolve(port);
+      }
+    });
+  });
+
+}
+
+
+// Launches the simulator and wait until it's ready to be used
+function launchSimulatorAndWaitUntilReady(options) {
+
+  var port = options.port;
+  var binary = options.binary;
+  var profile = options.profile;
+  var timeout = options.timeout;
+
+  return new Promise(function(resolve, reject) {
+
+    launchSimulator(options).then(function(simulatorProcess) {
+      waitUntilSimulatorIsReady({ port: port, timeout: timeout }).then(function() {
+        resolve({
+          process: simulatorProcess,
+          pid: simulatorProcess.pid,
+          port: port,
+          binary: binary,
+          profile: profile
+        });
+      }, function(timedOutError) {
+          reject(timedOutError);
+      });
+    }, function(simulatorLaunchError) {
+      reject(simulatorLaunchError);
+    });
+
+  });
+}
+
+// Launches the simulator in the specified port
+function launchSimulator(options) {
+
+  var detached = options.detached;
+
+  return new Promise(function(resolve, reject) {
+
+    startSimulatorProcess(options).then(function(simulatorProcess) {
+
+      // If the simulator is not detached, we need to kill its process
+      // once our own process exits
+      if (!detached) {
+        process.once('exit', function() {
+          simulatorProcess.kill('SIGTERM');
+        });
+
+        process.once('uncaughtException', function(error) {
+          if (process.listeners('uncaughtException').length === 0) {
+            simulatorProcess.kill('SIGTERM');
+            throw error;
+          }
+        });
+      } else {
+        // Totally make sure we don't keep references to this new child--
+        // this removes the child from the parent's event loop
+        // See http://nodejs.org/api/child_process.html#child_process_options_detached
+        simulatorProcess.unref();
+      }
+
+      resolve(simulatorProcess);
+
+    }, function(error) {
+      reject(error);
+    });
+
+  });
+  
+}
+
+
+function startSimulatorProcess(options) {
+
+  return new Promise(function(resolve, reject) {
+
+    var simulatorBinary = options.binary;
+    var childOptions = { stdio: ['ignore', 'ignore', 'ignore'] };
+    var startDebuggerServer = '-start-debugger-server';
+    
+    // Simple sanity check: make sure the simulator binary exists
+    if (!fs.existsSync(simulatorBinary)) {
+      return reject(new Error(simulatorBinary + ' does not exist'));
+    }
+
+    if (options.detached) {
+      childOptions.detached = true;
+    }
+
+    if (options.verbose) {
+      childOptions.stdio = [ process.stdin, process.stdout, process.stderr ];
+    }
+
+    if (options.version === '1.3' || options.version === '1.2') {
+      startDebuggerServer = '-dbgport';
+    }
+
+    // TODO do we want to pipe stdin/stdout/stderr as in commandB2G?
+    // https://github.com/nicola/fxos-start/blob/6b4794814e3a5c97d60abf2ab8619c635d6c3c94/index.js#L55-L57
+
+    var simulatorProcess = spawn(
+      simulatorBinary,
+      [
+        '-profile', options.profile,
+        startDebuggerServer, options.port,
+        '-no-remote',
+		'-foreground'
+      ],
+      childOptions
+    );
+
+    resolve(simulatorProcess);
+
+  });
+
+}
+
+
+function waitUntilSimulatorIsReady(options) {
+  var attemptInterval = 1000;
+  var elapsedTime = 0;
+  var port = options.port;
+  var timeout = options.timeout;
+
+  return new Promise(function(resolve, reject) {
+
+    function ping() {
+      var socket = new net.Socket();
+      socket.on('connect', function() {
+        resolve();
+        socket.destroy();
+      }).on('error', function(error) {
+        if (error && error.code !== 'ECONNREFUSED') {
+          throw error;
+        }
+        socket.destroy();
+        maybeTryAgain();
+      }).connect(port, 'localhost');
+    }
+
+    function maybeTryAgain() {
+      elapsedTime += attemptInterval;
+
+      if (elapsedTime < timeout) {
+        setTimeout(ping, attemptInterval);
+      } else {
+        reject(new Error('Timed out trying to connect to the simulator in ' + port));
+      }
+
+    }
+
+    ping();
+
+  });
+
+}
+
+// These actually make it so that child process get killed when this process
+// gets killed (except when the child process is detached, obviously)
+process.once('SIGTERM', function() {
+  process.exit(0);
+});
+process.once('SIGINT', function() {
+  process.exit(0);
+});
+
diff --git a/node_modules/node-firefox-start-simulator/node_modules/es6-promise/.release.json b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/.release.json
new file mode 100644
index 0000000..dee8cbc
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/.release.json
@@ -0,0 +1,17 @@
+{
+  "non-interactive": true,
+  "dry-run": false,
+  "verbose": false,
+  "force": false,
+  "pkgFiles": ["package.json", "bower.json"],
+  "increment": "patch",
+  "commitMessage": "Release %s",
+  "tagName": "%s",
+  "tagAnnotation": "Release %s",
+  "buildCommand": "npm run-script build-all",
+  "distRepo": "git@github.com:components/rsvp.js.git",
+  "distStageDir": "tmp/stage",
+  "distBase": "dist",
+  "distFiles": ["**/*", "../package.json", "../bower.json"],
+  "publish": false
+}
diff --git a/node_modules/node-firefox-start-simulator/node_modules/es6-promise/Brocfile.js b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/Brocfile.js
new file mode 100644
index 0000000..d34f458
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/Brocfile.js
@@ -0,0 +1,75 @@
+/* jshint node:true, undef:true, unused:true */
+var AMDFormatter     = require('es6-module-transpiler-amd-formatter');
+var closureCompiler  = require('broccoli-closure-compiler');
+var compileModules   = require('broccoli-compile-modules');
+var mergeTrees       = require('broccoli-merge-trees');
+var moveFile         = require('broccoli-file-mover');
+var es3Recast        = require('broccoli-es3-safe-recast');
+var concat           = require('broccoli-concat');
+var replace          = require('broccoli-string-replace');
+var calculateVersion = require('./lib/calculateVersion');
+var path             = require('path');
+var trees            = [];
+var env              = process.env.EMBER_ENV || 'development';
+
+var bundle = compileModules('lib', {
+  inputFiles: ['es6-promise.umd.js'],
+  output: '/es6-promise.js',
+  formatter: 'bundle',
+});
+
+trees.push(bundle);
+trees.push(compileModules('lib', {
+  inputFiles: ['**/*.js'],
+  output: '/amd/',
+  formatter: new AMDFormatter()
+}));
+
+if (env === 'production') {
+  trees.push(closureCompiler(moveFile(bundle, {
+    srcFile: 'es6-promise.js',
+    destFile: 'es6-promise.min.js'
+  }), {
+    compilation_level: 'ADVANCED_OPTIMIZATIONS',
+    externs: ['node'],
+  }));
+}
+
+var distTree = mergeTrees(trees.concat('config'));
+var distTrees = [];
+
+distTrees.push(concat(distTree, {
+  inputFiles: [
+    'versionTemplate.txt',
+    'es6-promise.js'
+  ],
+  outputFile: '/es6-promise.js'
+}));
+
+if (env === 'production') {
+  distTrees.push(concat(distTree, {
+    inputFiles: [
+      'versionTemplate.txt',
+      'es6-promise.min.js'
+    ],
+    outputFile: '/es6-promise.min.js'
+  }));
+}
+
+if (env !== 'development') {
+  distTrees = distTrees.map(es3Recast);
+}
+
+distTree = mergeTrees(distTrees);
+var distTree = replace(distTree, {
+  files: [
+    'es6-promise.js',
+    'es6-promise.min.js'
+  ],
+  pattern: {
+    match: /VERSION_PLACEHOLDER_STRING/g,
+    replacement: calculateVersion()
+  }
+});
+
+module.exports = distTree;
diff --git a/node_modules/node-firefox-start-simulator/node_modules/es6-promise/CHANGELOG.md b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/CHANGELOG.md
new file mode 100644
index 0000000..e06b496
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/CHANGELOG.md
@@ -0,0 +1,9 @@
+# Master
+
+# 2.0.0
+
+* re-sync with RSVP. Many large performance improvements and bugfixes.
+
+# 1.0.0
+
+* first subset of RSVP
diff --git a/node_modules/node-firefox-start-simulator/node_modules/es6-promise/LICENSE b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/LICENSE
new file mode 100644
index 0000000..954ec59
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/node_modules/node-firefox-start-simulator/node_modules/es6-promise/README.md b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/README.md
new file mode 100644
index 0000000..b974ce4
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/README.md
@@ -0,0 +1,58 @@
+# ES6-Promise (subset of [rsvp.js](https://github.com/tildeio/rsvp.js))
+
+This is a polyfill of the [ES6 Promise](http://people.mozilla.org/~jorendorff/es6-draft.html#sec-promise-constructor). The implementation is a subset of [rsvp.js](https://github.com/tildeio/rsvp.js), if you're wanting extra features and more debugging options, check out the [full library](https://github.com/tildeio/rsvp.js).
+
+For API details and how to use promises, see the <a href="http://www.html5rocks.com/en/tutorials/es6/promises/">JavaScript Promises HTML5Rocks article</a>.
+
+## Downloads
+
+* [es6-promise](https://es6-promises.s3.amazonaws.com/es6-promise-2.0.1.js)
+* [es6-promise-min](https://es6-promises.s3.amazonaws.com/es6-promise-2.0.1.min.js) (~2.2k gzipped)
+
+## Node.js
+
+To install:
+
+```sh
+npm install es6-promise
+```
+
+To use:
+
+```js
+var Promise = require('es6-promise').Promise;
+```
+
+## Usage in IE<9
+
+`catch` is a reserved word in IE<9, meaning `promise.catch(func)` throws a syntax error. To work around this, you can use a string to access the property as shown in the following example.
+
+However, please remember that such technique is already provided by most common minifiers, making the resulting code safe for old browsers and production:
+
+```js
+promise['catch'](function(err) {
+  // ...
+});
+```
+
+Or use `.then` instead:
+
+```js
+promise.then(undefined, function(err) {
+  // ...
+});
+```
+
+## Auto-polyfill
+
+To polyfill the global environment (either in Node or in the browser via CommonJS) use the following code snippet:
+
+```js
+require('es6-promise').polyfill();
+```
+
+Notice that we don't assign the result of `polyfill()` to any variable. The `polyfill()` method will patch the global environment (in this case to the `Promise` name) when called.
+
+## Building & Testing
+
+* `npm run build-all && npm test` - Run Mocha tests through Node and PhantomJS.
diff --git a/node_modules/node-firefox-start-simulator/node_modules/es6-promise/bin/publish_to_s3.js b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/bin/publish_to_s3.js
new file mode 100755
index 0000000..7daf49a
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/bin/publish_to_s3.js
@@ -0,0 +1,28 @@
+#!/usr/bin/env node
+
+// To invoke this from the commandline you need the following to env vars to exist:
+//
+// S3_BUCKET_NAME
+// TRAVIS_BRANCH
+// TRAVIS_TAG
+// TRAVIS_COMMIT
+// S3_SECRET_ACCESS_KEY
+// S3_ACCESS_KEY_ID
+//
+// Once you have those you execute with the following:
+//
+// ```sh
+// ./bin/publish_to_s3.js
+// ```
+var S3Publisher = require('ember-publisher');
+var configPath = require('path').join(__dirname, '../config/s3ProjectConfig.js');
+publisher = new S3Publisher({ projectConfigPath: configPath });
+
+// Always use wildcard section of project config.
+// This is useful when the including library does not
+// require channels (like in ember.js / ember-data).
+publisher.currentBranch = function() {
+  return (process.env.TRAVIS_BRANCH === 'master') ? 'wildcard' : 'no-op';
+};
+publisher.publish();
+
diff --git a/node_modules/node-firefox-start-simulator/node_modules/es6-promise/bower.json b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/bower.json
new file mode 100644
index 0000000..84ec5ad
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/bower.json
@@ -0,0 +1,29 @@
+{
+  "name": "es6-promise",
+  "namespace": "Promise",
+  "version": "2.0.1",
+  "description": "A polyfill for ES6-style Promises, tracking rsvp",
+  "authors": [
+    "Stefan Penner <stefan.penner@gmail.com>"
+  ],
+  "main": "dist/es6-promise.js",
+  "keywords": [
+    "promise"
+  ],
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/jakearchibald/ES6-Promises.git"
+  },
+  "bugs": {
+    "url": "https://github.com/jakearchibald/ES6-Promises/issues"
+  },
+  "license": "MIT",
+  "ignore": [
+    "node_modules",
+    "bower_components",
+    "test",
+    "tests",
+    "vendor",
+    "tasks"
+  ]
+}
diff --git a/node_modules/node-firefox-start-simulator/node_modules/es6-promise/config/s3ProjectConfig.js b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/config/s3ProjectConfig.js
new file mode 100644
index 0000000..5f3349a
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/config/s3ProjectConfig.js
@@ -0,0 +1,26 @@
+/*
+ * Using wildcard because es6-promise does not currently have a
+ * channel system in place.
+ */
+module.exports = function(revision,tag,date){
+  return {
+    'es6-promise.js':
+      { contentType: 'text/javascript',
+        destinations: {
+          wildcard: [
+            'es6-promise-latest.js',
+            'es6-promise-' + revision + '.js'
+          ]
+        }
+      },
+    'es6-promise.min.js':
+      { contentType: 'text/javascript',
+        destinations: {
+          wildcard: [
+            'es6-promise-latest.min.js',
+            'es6-promise-' + revision + '.min.js'
+          ]
+        }
+      }
+  }
+}
diff --git a/node_modules/node-firefox-start-simulator/node_modules/es6-promise/config/versionTemplate.txt b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/config/versionTemplate.txt
new file mode 100644
index 0000000..999a5fc
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/config/versionTemplate.txt
@@ -0,0 +1,7 @@
+/*!
+ * @overview es6-promise - a tiny implementation of Promises/A+.
+ * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald)
+ * @license   Licensed under MIT license
+ *            See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE
+ * @version   VERSION_PLACEHOLDER_STRING
+ */
diff --git a/node_modules/node-firefox-start-simulator/node_modules/es6-promise/dist/es6-promise.js b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/dist/es6-promise.js
new file mode 100644
index 0000000..30a6d81
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/dist/es6-promise.js
@@ -0,0 +1,960 @@
+/*!
+ * @overview es6-promise - a tiny implementation of Promises/A+.
+ * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald)
+ * @license   Licensed under MIT license
+ *            See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE
+ * @version   2.0.1
+ */
+
+(function() {
+    "use strict";
+
+    function $$utils$$objectOrFunction(x) {
+      return typeof x === 'function' || (typeof x === 'object' && x !== null);
+    }
+
+    function $$utils$$isFunction(x) {
+      return typeof x === 'function';
+    }
+
+    function $$utils$$isMaybeThenable(x) {
+      return typeof x === 'object' && x !== null;
+    }
+
+    var $$utils$$_isArray;
+
+    if (!Array.isArray) {
+      $$utils$$_isArray = function (x) {
+        return Object.prototype.toString.call(x) === '[object Array]';
+      };
+    } else {
+      $$utils$$_isArray = Array.isArray;
+    }
+
+    var $$utils$$isArray = $$utils$$_isArray;
+    var $$utils$$now = Date.now || function() { return new Date().getTime(); };
+    function $$utils$$F() { }
+
+    var $$utils$$o_create = (Object.create || function (o) {
+      if (arguments.length > 1) {
+        throw new Error('Second argument not supported');
+      }
+      if (typeof o !== 'object') {
+        throw new TypeError('Argument must be an object');
+      }
+      $$utils$$F.prototype = o;
+      return new $$utils$$F();
+    });
+
+    var $$asap$$len = 0;
+
+    var $$asap$$default = function asap(callback, arg) {
+      $$asap$$queue[$$asap$$len] = callback;
+      $$asap$$queue[$$asap$$len + 1] = arg;
+      $$asap$$len += 2;
+      if ($$asap$$len === 2) {
+        // If len is 1, that means that we need to schedule an async flush.
+        // If additional callbacks are queued before the queue is flushed, they
+        // will be processed by this flush that we are scheduling.
+        $$asap$$scheduleFlush();
+      }
+    };
+
+    var $$asap$$browserGlobal = (typeof window !== 'undefined') ? window : {};
+    var $$asap$$BrowserMutationObserver = $$asap$$browserGlobal.MutationObserver || $$asap$$browserGlobal.WebKitMutationObserver;
+
+    // test for web worker but not in IE10
+    var $$asap$$isWorker = typeof Uint8ClampedArray !== 'undefined' &&
+      typeof importScripts !== 'undefined' &&
+      typeof MessageChannel !== 'undefined';
+
+    // node
+    function $$asap$$useNextTick() {
+      return function() {
+        process.nextTick($$asap$$flush);
+      };
+    }
+
+    function $$asap$$useMutationObserver() {
+      var iterations = 0;
+      var observer = new $$asap$$BrowserMutationObserver($$asap$$flush);
+      var node = document.createTextNode('');
+      observer.observe(node, { characterData: true });
+
+      return function() {
+        node.data = (iterations = ++iterations % 2);
+      };
+    }
+
+    // web worker
+    function $$asap$$useMessageChannel() {
+      var channel = new MessageChannel();
+      channel.port1.onmessage = $$asap$$flush;
+      return function () {
+        channel.port2.postMessage(0);
+      };
+    }
+
+    function $$asap$$useSetTimeout() {
+      return function() {
+        setTimeout($$asap$$flush, 1);
+      };
+    }
+
+    var $$asap$$queue = new Array(1000);
+
+    function $$asap$$flush() {
+      for (var i = 0; i < $$asap$$len; i+=2) {
+        var callback = $$asap$$queue[i];
+        var arg = $$asap$$queue[i+1];
+
+        callback(arg);
+
+        $$asap$$queue[i] = undefined;
+        $$asap$$queue[i+1] = undefined;
+      }
+
+      $$asap$$len = 0;
+    }
+
+    var $$asap$$scheduleFlush;
+
+    // Decide what async method to use to triggering processing of queued callbacks:
+    if (typeof process !== 'undefined' && {}.toString.call(process) === '[object process]') {
+      $$asap$$scheduleFlush = $$asap$$useNextTick();
+    } else if ($$asap$$BrowserMutationObserver) {
+      $$asap$$scheduleFlush = $$asap$$useMutationObserver();
+    } else if ($$asap$$isWorker) {
+      $$asap$$scheduleFlush = $$asap$$useMessageChannel();
+    } else {
+      $$asap$$scheduleFlush = $$asap$$useSetTimeout();
+    }
+
+    function $$$internal$$noop() {}
+    var $$$internal$$PENDING   = void 0;
+    var $$$internal$$FULFILLED = 1;
+    var $$$internal$$REJECTED  = 2;
+    var $$$internal$$GET_THEN_ERROR = new $$$internal$$ErrorObject();
+
+    function $$$internal$$selfFullfillment() {
+      return new TypeError("You cannot resolve a promise with itself");
+    }
+
+    function $$$internal$$cannotReturnOwn() {
+      return new TypeError('A promises callback cannot return that same promise.')
+    }
+
+    function $$$internal$$getThen(promise) {
+      try {
+        return promise.then;
+      } catch(error) {
+        $$$internal$$GET_THEN_ERROR.error = error;
+        return $$$internal$$GET_THEN_ERROR;
+      }
+    }
+
+    function $$$internal$$tryThen(then, value, fulfillmentHandler, rejectionHandler) {
+      try {
+        then.call(value, fulfillmentHandler, rejectionHandler);
+      } catch(e) {
+        return e;
+      }
+    }
+
+    function $$$internal$$handleForeignThenable(promise, thenable, then) {
+       $$asap$$default(function(promise) {
+        var sealed = false;
+        var error = $$$internal$$tryThen(then, thenable, function(value) {
+          if (sealed) { return; }
+          sealed = true;
+          if (thenable !== value) {
+            $$$internal$$resolve(promise, value);
+          } else {
+            $$$internal$$fulfill(promise, value);
+          }
+        }, function(reason) {
+          if (sealed) { return; }
+          sealed = true;
+
+          $$$internal$$reject(promise, reason);
+        }, 'Settle: ' + (promise._label || ' unknown promise'));
+
+        if (!sealed && error) {
+          sealed = true;
+          $$$internal$$reject(promise, error);
+        }
+      }, promise);
+    }
+
+    function $$$internal$$handleOwnThenable(promise, thenable) {
+      if (thenable._state === $$$internal$$FULFILLED) {
+        $$$internal$$fulfill(promise, thenable._result);
+      } else if (promise._state === $$$internal$$REJECTED) {
+        $$$internal$$reject(promise, thenable._result);
+      } else {
+        $$$internal$$subscribe(thenable, undefined, function(value) {
+          $$$internal$$resolve(promise, value);
+        }, function(reason) {
+          $$$internal$$reject(promise, reason);
+        });
+      }
+    }
+
+    function $$$internal$$handleMaybeThenable(promise, maybeThenable) {
+      if (maybeThenable.constructor === promise.constructor) {
+        $$$internal$$handleOwnThenable(promise, maybeThenable);
+      } else {
+        var then = $$$internal$$getThen(maybeThenable);
+
+        if (then === $$$internal$$GET_THEN_ERROR) {
+          $$$internal$$reject(promise, $$$internal$$GET_THEN_ERROR.error);
+        } else if (then === undefined) {
+          $$$internal$$fulfill(promise, maybeThenable);
+        } else if ($$utils$$isFunction(then)) {
+          $$$internal$$handleForeignThenable(promise, maybeThenable, then);
+        } else {
+          $$$internal$$fulfill(promise, maybeThenable);
+        }
+      }
+    }
+
+    function $$$internal$$resolve(promise, value) {
+      if (promise === value) {
+        $$$internal$$reject(promise, $$$internal$$selfFullfillment());
+      } else if ($$utils$$objectOrFunction(value)) {
+        $$$internal$$handleMaybeThenable(promise, value);
+      } else {
+        $$$internal$$fulfill(promise, value);
+      }
+    }
+
+    function $$$internal$$publishRejection(promise) {
+      if (promise._onerror) {
+        promise._onerror(promise._result);
+      }
+
+      $$$internal$$publish(promise);
+    }
+
+    function $$$internal$$fulfill(promise, value) {
+      if (promise._state !== $$$internal$$PENDING) { return; }
+
+      promise._result = value;
+      promise._state = $$$internal$$FULFILLED;
+
+      if (promise._subscribers.length === 0) {
+      } else {
+        $$asap$$default($$$internal$$publish, promise);
+      }
+    }
+
+    function $$$internal$$reject(promise, reason) {
+      if (promise._state !== $$$internal$$PENDING) { return; }
+      promise._state = $$$internal$$REJECTED;
+      promise._result = reason;
+
+      $$asap$$default($$$internal$$publishRejection, promise);
+    }
+
+    function $$$internal$$subscribe(parent, child, onFulfillment, onRejection) {
+      var subscribers = parent._subscribers;
+      var length = subscribers.length;
+
+      parent._onerror = null;
+
+      subscribers[length] = child;
+      subscribers[length + $$$internal$$FULFILLED] = onFulfillment;
+      subscribers[length + $$$internal$$REJECTED]  = onRejection;
+
+      if (length === 0 && parent._state) {
+        $$asap$$default($$$internal$$publish, parent);
+      }
+    }
+
+    function $$$internal$$publish(promise) {
+      var subscribers = promise._subscribers;
+      var settled = promise._state;
+
+      if (subscribers.length === 0) { return; }
+
+      var child, callback, detail = promise._result;
+
+      for (var i = 0; i < subscribers.length; i += 3) {
+        child = subscribers[i];
+        callback = subscribers[i + settled];
+
+        if (child) {
+          $$$internal$$invokeCallback(settled, child, callback, detail);
+        } else {
+          callback(detail);
+        }
+      }
+
+      promise._subscribers.length = 0;
+    }
+
+    function $$$internal$$ErrorObject() {
+      this.error = null;
+    }
+
+    var $$$internal$$TRY_CATCH_ERROR = new $$$internal$$ErrorObject();
+
+    function $$$internal$$tryCatch(callback, detail) {
+      try {
+        return callback(detail);
+      } catch(e) {
+        $$$internal$$TRY_CATCH_ERROR.error = e;
+        return $$$internal$$TRY_CATCH_ERROR;
+      }
+    }
+
+    function $$$internal$$invokeCallback(settled, promise, callback, detail) {
+      var hasCallback = $$utils$$isFunction(callback),
+          value, error, succeeded, failed;
+
+      if (hasCallback) {
+        value = $$$internal$$tryCatch(callback, detail);
+
+        if (value === $$$internal$$TRY_CATCH_ERROR) {
+          failed = true;
+          error = value.error;
+          value = null;
+        } else {
+          succeeded = true;
+        }
+
+        if (promise === value) {
+          $$$internal$$reject(promise, $$$internal$$cannotReturnOwn());
+          return;
+        }
+
+      } else {
+        value = detail;
+        succeeded = true;
+      }
+
+      if (promise._state !== $$$internal$$PENDING) {
+        // noop
+      } else if (hasCallback && succeeded) {
+        $$$internal$$resolve(promise, value);
+      } else if (failed) {
+        $$$internal$$reject(promise, error);
+      } else if (settled === $$$internal$$FULFILLED) {
+        $$$internal$$fulfill(promise, value);
+      } else if (settled === $$$internal$$REJECTED) {
+        $$$internal$$reject(promise, value);
+      }
+    }
+
+    function $$$internal$$initializePromise(promise, resolver) {
+      try {
+        resolver(function resolvePromise(value){
+          $$$internal$$resolve(promise, value);
+        }, function rejectPromise(reason) {
+          $$$internal$$reject(promise, reason);
+        });
+      } catch(e) {
+        $$$internal$$reject(promise, e);
+      }
+    }
+
+    function $$$enumerator$$makeSettledResult(state, position, value) {
+      if (state === $$$internal$$FULFILLED) {
+        return {
+          state: 'fulfilled',
+          value: value
+        };
+      } else {
+        return {
+          state: 'rejected',
+          reason: value
+        };
+      }
+    }
+
+    function $$$enumerator$$Enumerator(Constructor, input, abortOnReject, label) {
+      this._instanceConstructor = Constructor;
+      this.promise = new Constructor($$$internal$$noop, label);
+      this._abortOnReject = abortOnReject;
+
+      if (this._validateInput(input)) {
+        this._input     = input;
+        this.length     = input.length;
+        this._remaining = input.length;
+
+        this._init();
+
+        if (this.length === 0) {
+          $$$internal$$fulfill(this.promise, this._result);
+        } else {
+          this.length = this.length || 0;
+          this._enumerate();
+          if (this._remaining === 0) {
+            $$$internal$$fulfill(this.promise, this._result);
+          }
+        }
+      } else {
+        $$$internal$$reject(this.promise, this._validationError());
+      }
+    }
+
+    $$$enumerator$$Enumerator.prototype._validateInput = function(input) {
+      return $$utils$$isArray(input);
+    };
+
+    $$$enumerator$$Enumerator.prototype._validationError = function() {
+      return new Error('Array Methods must be provided an Array');
+    };
+
+    $$$enumerator$$Enumerator.prototype._init = function() {
+      this._result = new Array(this.length);
+    };
+
+    var $$$enumerator$$default = $$$enumerator$$Enumerator;
+
+    $$$enumerator$$Enumerator.prototype._enumerate = function() {
+      var length  = this.length;
+      var promise = this.promise;
+      var input   = this._input;
+
+      for (var i = 0; promise._state === $$$internal$$PENDING && i < length; i++) {
+        this._eachEntry(input[i], i);
+      }
+    };
+
+    $$$enumerator$$Enumerator.prototype._eachEntry = function(entry, i) {
+      var c = this._instanceConstructor;
+      if ($$utils$$isMaybeThenable(entry)) {
+        if (entry.constructor === c && entry._state !== $$$internal$$PENDING) {
+          entry._onerror = null;
+          this._settledAt(entry._state, i, entry._result);
+        } else {
+          this._willSettleAt(c.resolve(entry), i);
+        }
+      } else {
+        this._remaining--;
+        this._result[i] = this._makeResult($$$internal$$FULFILLED, i, entry);
+      }
+    };
+
+    $$$enumerator$$Enumerator.prototype._settledAt = function(state, i, value) {
+      var promise = this.promise;
+
+      if (promise._state === $$$internal$$PENDING) {
+        this._remaining--;
+
+        if (this._abortOnReject && state === $$$internal$$REJECTED) {
+          $$$internal$$reject(promise, value);
+        } else {
+          this._result[i] = this._makeResult(state, i, value);
+        }
+      }
+
+      if (this._remaining === 0) {
+        $$$internal$$fulfill(promise, this._result);
+      }
+    };
+
+    $$$enumerator$$Enumerator.prototype._makeResult = function(state, i, value) {
+      return value;
+    };
+
+    $$$enumerator$$Enumerator.prototype._willSettleAt = function(promise, i) {
+      var enumerator = this;
+
+      $$$internal$$subscribe(promise, undefined, function(value) {
+        enumerator._settledAt($$$internal$$FULFILLED, i, value);
+      }, function(reason) {
+        enumerator._settledAt($$$internal$$REJECTED, i, reason);
+      });
+    };
+
+    var $$promise$all$$default = function all(entries, label) {
+      return new $$$enumerator$$default(this, entries, true /* abort on reject */, label).promise;
+    };
+
+    var $$promise$race$$default = function race(entries, label) {
+      /*jshint validthis:true */
+      var Constructor = this;
+
+      var promise = new Constructor($$$internal$$noop, label);
+
+      if (!$$utils$$isArray(entries)) {
+        $$$internal$$reject(promise, new TypeError('You must pass an array to race.'));
+        return promise;
+      }
+
+      var length = entries.length;
+
+      function onFulfillment(value) {
+        $$$internal$$resolve(promise, value);
+      }
+
+      function onRejection(reason) {
+        $$$internal$$reject(promise, reason);
+      }
+
+      for (var i = 0; promise._state === $$$internal$$PENDING && i < length; i++) {
+        $$$internal$$subscribe(Constructor.resolve(entries[i]), undefined, onFulfillment, onRejection);
+      }
+
+      return promise;
+    };
+
+    var $$promise$resolve$$default = function resolve(object, label) {
+      /*jshint validthis:true */
+      var Constructor = this;
+
+      if (object && typeof object === 'object' && object.constructor === Constructor) {
+        return object;
+      }
+
+      var promise = new Constructor($$$internal$$noop, label);
+      $$$internal$$resolve(promise, object);
+      return promise;
+    };
+
+    var $$promise$reject$$default = function reject(reason, label) {
+      /*jshint validthis:true */
+      var Constructor = this;
+      var promise = new Constructor($$$internal$$noop, label);
+      $$$internal$$reject(promise, reason);
+      return promise;
+    };
+
+    var $$es6$promise$promise$$counter = 0;
+
+    function $$es6$promise$promise$$needsResolver() {
+      throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
+    }
+
+    function $$es6$promise$promise$$needsNew() {
+      throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");
+    }
+
+    var $$es6$promise$promise$$default = $$es6$promise$promise$$Promise;
+
+    /**
+      Promise objects represent the eventual result of an asynchronous operation. The
+      primary way of interacting with a promise is through its `then` method, which
+      registers callbacks to receive either a promise’s eventual value or the reason
+      why the promise cannot be fulfilled.
+
+      Terminology
+      -----------
+
+      - `promise` is an object or function with a `then` method whose behavior conforms to this specification.
+      - `thenable` is an object or function that defines a `then` method.
+      - `value` is any legal JavaScript value (including undefined, a thenable, or a promise).
+      - `exception` is a value that is thrown using the throw statement.
+      - `reason` is a value that indicates why a promise was rejected.
+      - `settled` the final resting state of a promise, fulfilled or rejected.
+
+      A promise can be in one of three states: pending, fulfilled, or rejected.
+
+      Promises that are fulfilled have a fulfillment value and are in the fulfilled
+      state.  Promises that are rejected have a rejection reason and are in the
+      rejected state.  A fulfillment value is never a thenable.
+
+      Promises can also be said to *resolve* a value.  If this value is also a
+      promise, then the original promise's settled state will match the value's
+      settled state.  So a promise that *resolves* a promise that rejects will
+      itself reject, and a promise that *resolves* a promise that fulfills will
+      itself fulfill.
+
+
+      Basic Usage:
+      ------------
+
+      ```js
+      var promise = new Promise(function(resolve, reject) {
+        // on success
+        resolve(value);
+
+        // on failure
+        reject(reason);
+      });
+
+      promise.then(function(value) {
+        // on fulfillment
+      }, function(reason) {
+        // on rejection
+      });
+      ```
+
+      Advanced Usage:
+      ---------------
+
+      Promises shine when abstracting away asynchronous interactions such as
+      `XMLHttpRequest`s.
+
+      ```js
+      function getJSON(url) {
+        return new Promise(function(resolve, reject){
+          var xhr = new XMLHttpRequest();
+
+          xhr.open('GET', url);
+          xhr.onreadystatechange = handler;
+          xhr.responseType = 'json';
+          xhr.setRequestHeader('Accept', 'application/json');
+          xhr.send();
+
+          function handler() {
+            if (this.readyState === this.DONE) {
+              if (this.status === 200) {
+                resolve(this.response);
+              } else {
+                reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']'));
+              }
+            }
+          };
+        });
+      }
+
+      getJSON('/posts.json').then(function(json) {
+        // on fulfillment
+      }, function(reason) {
+        // on rejection
+      });
+      ```
+
+      Unlike callbacks, promises are great composable primitives.
+
+      ```js
+      Promise.all([
+        getJSON('/posts'),
+        getJSON('/comments')
+      ]).then(function(values){
+        values[0] // => postsJSON
+        values[1] // => commentsJSON
+
+        return values;
+      });
+      ```
+
+      @class Promise
+      @param {function} resolver
+      Useful for tooling.
+      @constructor
+    */
+    function $$es6$promise$promise$$Promise(resolver) {
+      this._id = $$es6$promise$promise$$counter++;
+      this._state = undefined;
+      this._result = undefined;
+      this._subscribers = [];
+
+      if ($$$internal$$noop !== resolver) {
+        if (!$$utils$$isFunction(resolver)) {
+          $$es6$promise$promise$$needsResolver();
+        }
+
+        if (!(this instanceof $$es6$promise$promise$$Promise)) {
+          $$es6$promise$promise$$needsNew();
+        }
+
+        $$$internal$$initializePromise(this, resolver);
+      }
+    }
+
+    $$es6$promise$promise$$Promise.all = $$promise$all$$default;
+    $$es6$promise$promise$$Promise.race = $$promise$race$$default;
+    $$es6$promise$promise$$Promise.resolve = $$promise$resolve$$default;
+    $$es6$promise$promise$$Promise.reject = $$promise$reject$$default;
+
+    $$es6$promise$promise$$Promise.prototype = {
+      constructor: $$es6$promise$promise$$Promise,
+
+    /**
+      The primary way of interacting with a promise is through its `then` method,
+      which registers callbacks to receive either a promise's eventual value or the
+      reason why the promise cannot be fulfilled.
+
+      ```js
+      findUser().then(function(user){
+        // user is available
+      }, function(reason){
+        // user is unavailable, and you are given the reason why
+      });
+      ```
+
+      Chaining
+      --------
+
+      The return value of `then` is itself a promise.  This second, 'downstream'
+      promise is resolved with the return value of the first promise's fulfillment
+      or rejection handler, or rejected if the handler throws an exception.
+
+      ```js
+      findUser().then(function (user) {
+        return user.name;
+      }, function (reason) {
+        return 'default name';
+      }).then(function (userName) {
+        // If `findUser` fulfilled, `userName` will be the user's name, otherwise it
+        // will be `'default name'`
+      });
+
+      findUser().then(function (user) {
+        throw new Error('Found user, but still unhappy');
+      }, function (reason) {
+        throw new Error('`findUser` rejected and we're unhappy');
+      }).then(function (value) {
+        // never reached
+      }, function (reason) {
+        // if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'.
+        // If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'.
+      });
+      ```
+      If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream.
+
+      ```js
+      findUser().then(function (user) {
+        throw new PedagogicalException('Upstream error');
+      }).then(function (value) {
+        // never reached
+      }).then(function (value) {
+        // never reached
+      }, function (reason) {
+        // The `PedgagocialException` is propagated all the way down to here
+      });
+      ```
+
+      Assimilation
+      ------------
+
+      Sometimes the value you want to propagate to a downstream promise can only be
+      retrieved asynchronously. This can be achieved by returning a promise in the
+      fulfillment or rejection handler. The downstream promise will then be pending
+      until the returned promise is settled. This is called *assimilation*.
+
+      ```js
+      findUser().then(function (user) {
+        return findCommentsByAuthor(user);
+      }).then(function (comments) {
+        // The user's comments are now available
+      });
+      ```
+
+      If the assimliated promise rejects, then the downstream promise will also reject.
+
+      ```js
+      findUser().then(function (user) {
+        return findCommentsByAuthor(user);
+      }).then(function (comments) {
+        // If `findCommentsByAuthor` fulfills, we'll have the value here
+      }, function (reason) {
+        // If `findCommentsByAuthor` rejects, we'll have the reason here
+      });
+      ```
+
+      Simple Example
+      --------------
+
+      Synchronous Example
+
+      ```javascript
+      var result;
+
+      try {
+        result = findResult();
+        // success
+      } catch(reason) {
+        // failure
+      }
+      ```
+
+      Errback Example
+
+      ```js
+      findResult(function(result, err){
+        if (err) {
+          // failure
+        } else {
+          // success
+        }
+      });
+      ```
+
+      Promise Example;
+
+      ```javascript
+      findResult().then(function(result){
+        // success
+      }, function(reason){
+        // failure
+      });
+      ```
+
+      Advanced Example
+      --------------
+
+      Synchronous Example
+
+      ```javascript
+      var author, books;
+
+      try {
+        author = findAuthor();
+        books  = findBooksByAuthor(author);
+        // success
+      } catch(reason) {
+        // failure
+      }
+      ```
+
+      Errback Example
+
+      ```js
+
+      function foundBooks(books) {
+
+      }
+
+      function failure(reason) {
+
+      }
+
+      findAuthor(function(author, err){
+        if (err) {
+          failure(err);
+          // failure
+        } else {
+          try {
+            findBoooksByAuthor(author, function(books, err) {
+              if (err) {
+                failure(err);
+              } else {
+                try {
+                  foundBooks(books);
+                } catch(reason) {
+                  failure(reason);
+                }
+              }
+            });
+          } catch(error) {
+            failure(err);
+          }
+          // success
+        }
+      });
+      ```
+
+      Promise Example;
+
+      ```javascript
+      findAuthor().
+        then(findBooksByAuthor).
+        then(function(books){
+          // found books
+      }).catch(function(reason){
+        // something went wrong
+      });
+      ```
+
+      @method then
+      @param {Function} onFulfilled
+      @param {Function} onRejected
+      Useful for tooling.
+      @return {Promise}
+    */
+      then: function(onFulfillment, onRejection) {
+        var parent = this;
+        var state = parent._state;
+
+        if (state === $$$internal$$FULFILLED && !onFulfillment || state === $$$internal$$REJECTED && !onRejection) {
+          return this;
+        }
+
+        var child = new this.constructor($$$internal$$noop);
+        var result = parent._result;
+
+        if (state) {
+          var callback = arguments[state - 1];
+          $$asap$$default(function(){
+            $$$internal$$invokeCallback(state, child, callback, result);
+          });
+        } else {
+          $$$internal$$subscribe(parent, child, onFulfillment, onRejection);
+        }
+
+        return child;
+      },
+
+    /**
+      `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same
+      as the catch block of a try/catch statement.
+
+      ```js
+      function findAuthor(){
+        throw new Error('couldn't find that author');
+      }
+
+      // synchronous
+      try {
+        findAuthor();
+      } catch(reason) {
+        // something went wrong
+      }
+
+      // async with promises
+      findAuthor().catch(function(reason){
+        // something went wrong
+      });
+      ```
+
+      @method catch
+      @param {Function} onRejection
+      Useful for tooling.
+      @return {Promise}
+    */
+      'catch': function(onRejection) {
+        return this.then(null, onRejection);
+      }
+    };
+
+    var $$es6$promise$polyfill$$default = function polyfill() {
+      var local;
+
+      if (typeof global !== 'undefined') {
+        local = global;
+      } else if (typeof window !== 'undefined' && window.document) {
+        local = window;
+      } else {
+        local = self;
+      }
+
+      var es6PromiseSupport =
+        "Promise" in local &&
+        // Some of these methods are missing from
+        // Firefox/Chrome experimental implementations
+        "resolve" in local.Promise &&
+        "reject" in local.Promise &&
+        "all" in local.Promise &&
+        "race" in local.Promise &&
+        // Older version of the spec had a resolver object
+        // as the arg rather than a function
+        (function() {
+          var resolve;
+          new local.Promise(function(r) { resolve = r; });
+          return $$utils$$isFunction(resolve);
+        }());
+
+      if (!es6PromiseSupport) {
+        local.Promise = $$es6$promise$promise$$default;
+      }
+    };
+
+    var es6$promise$umd$$ES6Promise = {
+      'Promise': $$es6$promise$promise$$default,
+      'polyfill': $$es6$promise$polyfill$$default
+    };
+
+    /* global define:true module:true window: true */
+    if (typeof define === 'function' && define['amd']) {
+      define(function() { return es6$promise$umd$$ES6Promise; });
+    } else if (typeof module !== 'undefined' && module['exports']) {
+      module['exports'] = es6$promise$umd$$ES6Promise;
+    } else if (typeof this !== 'undefined') {
+      this['ES6Promise'] = es6$promise$umd$$ES6Promise;
+    }
+}).call(this);
\ No newline at end of file
diff --git a/node_modules/node-firefox-start-simulator/node_modules/es6-promise/dist/es6-promise.min.js b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/dist/es6-promise.min.js
new file mode 100644
index 0000000..6e0c99f
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/dist/es6-promise.min.js
@@ -0,0 +1,18 @@
+/*!
+ * @overview es6-promise - a tiny implementation of Promises/A+.
+ * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald)
+ * @license   Licensed under MIT license
+ *            See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE
+ * @version   2.0.1
+ */
+
+(function(){function r(a,b){n[l]=a;n[l+1]=b;l+=2;2===l&&A()}function s(a){return"function"===typeof a}function F(){return function(){process.nextTick(t)}}function G(){var a=0,b=new B(t),c=document.createTextNode("");b.observe(c,{characterData:!0});return function(){c.data=a=++a%2}}function H(){var a=new MessageChannel;a.port1.onmessage=t;return function(){a.port2.postMessage(0)}}function I(){return function(){setTimeout(t,1)}}function t(){for(var a=0;a<l;a+=2)(0,n[a])(n[a+1]),n[a]=void 0,n[a+1]=void 0;
+l=0}function p(){}function J(a,b,c,d){try{a.call(b,c,d)}catch(e){return e}}function K(a,b,c){r(function(a){var e=!1,f=J(c,b,function(c){e||(e=!0,b!==c?q(a,c):m(a,c))},function(b){e||(e=!0,g(a,b))});!e&&f&&(e=!0,g(a,f))},a)}function L(a,b){1===b.a?m(a,b.b):2===a.a?g(a,b.b):u(b,void 0,function(b){q(a,b)},function(b){g(a,b)})}function q(a,b){if(a===b)g(a,new TypeError("You cannot resolve a promise with itself"));else if("function"===typeof b||"object"===typeof b&&null!==b)if(b.constructor===a.constructor)L(a,
+b);else{var c;try{c=b.then}catch(d){v.error=d,c=v}c===v?g(a,v.error):void 0===c?m(a,b):s(c)?K(a,b,c):m(a,b)}else m(a,b)}function M(a){a.f&&a.f(a.b);x(a)}function m(a,b){void 0===a.a&&(a.b=b,a.a=1,0!==a.e.length&&r(x,a))}function g(a,b){void 0===a.a&&(a.a=2,a.b=b,r(M,a))}function u(a,b,c,d){var e=a.e,f=e.length;a.f=null;e[f]=b;e[f+1]=c;e[f+2]=d;0===f&&a.a&&r(x,a)}function x(a){var b=a.e,c=a.a;if(0!==b.length){for(var d,e,f=a.b,g=0;g<b.length;g+=3)d=b[g],e=b[g+c],d?C(c,d,e,f):e(f);a.e.length=0}}function D(){this.error=
+null}function C(a,b,c,d){var e=s(c),f,k,h,l;if(e){try{f=c(d)}catch(n){y.error=n,f=y}f===y?(l=!0,k=f.error,f=null):h=!0;if(b===f){g(b,new TypeError("A promises callback cannot return that same promise."));return}}else f=d,h=!0;void 0===b.a&&(e&&h?q(b,f):l?g(b,k):1===a?m(b,f):2===a&&g(b,f))}function N(a,b){try{b(function(b){q(a,b)},function(b){g(a,b)})}catch(c){g(a,c)}}function k(a,b,c,d){this.n=a;this.c=new a(p,d);this.i=c;this.o(b)?(this.m=b,this.d=this.length=b.length,this.l(),0===this.length?m(this.c,
+this.b):(this.length=this.length||0,this.k(),0===this.d&&m(this.c,this.b))):g(this.c,this.p())}function h(a){O++;this.b=this.a=void 0;this.e=[];if(p!==a){if(!s(a))throw new TypeError("You must pass a resolver function as the first argument to the promise constructor");if(!(this instanceof h))throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");N(this,a)}}var E=Array.isArray?Array.isArray:function(a){return"[object Array]"===
+Object.prototype.toString.call(a)},l=0,w="undefined"!==typeof window?window:{},B=w.MutationObserver||w.WebKitMutationObserver,w="undefined"!==typeof Uint8ClampedArray&&"undefined"!==typeof importScripts&&"undefined"!==typeof MessageChannel,n=Array(1E3),A;A="undefined"!==typeof process&&"[object process]"==={}.toString.call(process)?F():B?G():w?H():I();var v=new D,y=new D;k.prototype.o=function(a){return E(a)};k.prototype.p=function(){return Error("Array Methods must be provided an Array")};k.prototype.l=
+function(){this.b=Array(this.length)};k.prototype.k=function(){for(var a=this.length,b=this.c,c=this.m,d=0;void 0===b.a&&d<a;d++)this.j(c[d],d)};k.prototype.j=function(a,b){var c=this.n;"object"===typeof a&&null!==a?a.constructor===c&&void 0!==a.a?(a.f=null,this.g(a.a,b,a.b)):this.q(c.resolve(a),b):(this.d--,this.b[b]=this.h(a))};k.prototype.g=function(a,b,c){var d=this.c;void 0===d.a&&(this.d--,this.i&&2===a?g(d,c):this.b[b]=this.h(c));0===this.d&&m(d,this.b)};k.prototype.h=function(a){return a};
+k.prototype.q=function(a,b){var c=this;u(a,void 0,function(a){c.g(1,b,a)},function(a){c.g(2,b,a)})};var O=0;h.all=function(a,b){return(new k(this,a,!0,b)).c};h.race=function(a,b){function c(a){q(e,a)}function d(a){g(e,a)}var e=new this(p,b);if(!E(a))return (g(e,new TypeError("You must pass an array to race.")), e);for(var f=a.length,h=0;void 0===e.a&&h<f;h++)u(this.resolve(a[h]),void 0,c,d);return e};h.resolve=function(a,b){if(a&&"object"===typeof a&&a.constructor===this)return a;var c=new this(p,b);
+q(c,a);return c};h.reject=function(a,b){var c=new this(p,b);g(c,a);return c};h.prototype={constructor:h,then:function(a,b){var c=this.a;if(1===c&&!a||2===c&&!b)return this;var d=new this.constructor(p),e=this.b;if(c){var f=arguments[c-1];r(function(){C(c,d,f,e)})}else u(this,d,a,b);return d},"catch":function(a){return this.then(null,a)}};var z={Promise:h,polyfill:function(){var a;a="undefined"!==typeof global?global:"undefined"!==typeof window&&window.document?window:self;"Promise"in a&&"resolve"in
+a.Promise&&"reject"in a.Promise&&"all"in a.Promise&&"race"in a.Promise&&function(){var b;new a.Promise(function(a){b=a});return s(b)}()||(a.Promise=h)}};"function"===typeof define&&define.amd?define(function(){return z}):"undefined"!==typeof module&&module.exports?module.exports=z:"undefined"!==typeof this&&(this.ES6Promise=z)}).call(this);
diff --git a/node_modules/node-firefox-start-simulator/node_modules/es6-promise/lib/calculateVersion.js b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/lib/calculateVersion.js
new file mode 100644
index 0000000..018e364
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/lib/calculateVersion.js
@@ -0,0 +1,36 @@
+'use strict';
+
+var fs   = require('fs');
+var path = require('path');
+
+module.exports = function () {
+  var packageVersion = require('../package.json').version;
+  var output         = [packageVersion];
+  var gitPath        = path.join(__dirname,'..','.git');
+  var headFilePath   = path.join(gitPath, 'HEAD');
+
+  if (packageVersion.indexOf('+') > -1) {
+    try {
+      if (fs.existsSync(headFilePath)) {
+        var headFile = fs.readFileSync(headFilePath, {encoding: 'utf8'});
+        var branchName = headFile.split('/').slice(-1)[0].trim();
+        var refPath = headFile.split(' ')[1];
+        var branchSHA;
+
+        if (refPath) {
+          var branchPath = path.join(gitPath, refPath.trim());
+          branchSHA  = fs.readFileSync(branchPath);
+        } else {
+          branchSHA = branchName;
+        }
+
+        output.push(branchSHA.slice(0,10));
+      }
+    } catch (err) {
+      console.error(err.stack);
+    }
+    return output.join('.');
+  } else {
+    return packageVersion;
+  }
+};
diff --git a/node_modules/node-firefox-start-simulator/node_modules/es6-promise/lib/es6-promise.umd.js b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/lib/es6-promise.umd.js
new file mode 100644
index 0000000..cd01553
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/lib/es6-promise.umd.js
@@ -0,0 +1,16 @@
+import Promise from './es6-promise/promise';
+import polyfill from './es6-promise/polyfill';
+
+var ES6Promise = {
+  'Promise': Promise,
+  'polyfill': polyfill
+};
+
+/* global define:true module:true window: true */
+if (typeof define === 'function' && define['amd']) {
+  define(function() { return ES6Promise; });
+} else if (typeof module !== 'undefined' && module['exports']) {
+  module['exports'] = ES6Promise;
+} else if (typeof this !== 'undefined') {
+  this['ES6Promise'] = ES6Promise;
+}
diff --git a/node_modules/node-firefox-start-simulator/node_modules/es6-promise/lib/es6-promise/-internal.js b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/lib/es6-promise/-internal.js
new file mode 100644
index 0000000..afef22e
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/lib/es6-promise/-internal.js
@@ -0,0 +1,251 @@
+import {
+  objectOrFunction,
+  isFunction
+} from './utils';
+
+import asap from './asap';
+
+function noop() {}
+
+var PENDING   = void 0;
+var FULFILLED = 1;
+var REJECTED  = 2;
+
+var GET_THEN_ERROR = new ErrorObject();
+
+function selfFullfillment() {
+  return new TypeError("You cannot resolve a promise with itself");
+}
+
+function cannotReturnOwn() {
+  return new TypeError('A promises callback cannot return that same promise.')
+}
+
+function getThen(promise) {
+  try {
+    return promise.then;
+  } catch(error) {
+    GET_THEN_ERROR.error = error;
+    return GET_THEN_ERROR;
+  }
+}
+
+function tryThen(then, value, fulfillmentHandler, rejectionHandler) {
+  try {
+    then.call(value, fulfillmentHandler, rejectionHandler);
+  } catch(e) {
+    return e;
+  }
+}
+
+function handleForeignThenable(promise, thenable, then) {
+   asap(function(promise) {
+    var sealed = false;
+    var error = tryThen(then, thenable, function(value) {
+      if (sealed) { return; }
+      sealed = true;
+      if (thenable !== value) {
+        resolve(promise, value);
+      } else {
+        fulfill(promise, value);
+      }
+    }, function(reason) {
+      if (sealed) { return; }
+      sealed = true;
+
+      reject(promise, reason);
+    }, 'Settle: ' + (promise._label || ' unknown promise'));
+
+    if (!sealed && error) {
+      sealed = true;
+      reject(promise, error);
+    }
+  }, promise);
+}
+
+function handleOwnThenable(promise, thenable) {
+  if (thenable._state === FULFILLED) {
+    fulfill(promise, thenable._result);
+  } else if (promise._state === REJECTED) {
+    reject(promise, thenable._result);
+  } else {
+    subscribe(thenable, undefined, function(value) {
+      resolve(promise, value);
+    }, function(reason) {
+      reject(promise, reason);
+    });
+  }
+}
+
+function handleMaybeThenable(promise, maybeThenable) {
+  if (maybeThenable.constructor === promise.constructor) {
+    handleOwnThenable(promise, maybeThenable);
+  } else {
+    var then = getThen(maybeThenable);
+
+    if (then === GET_THEN_ERROR) {
+      reject(promise, GET_THEN_ERROR.error);
+    } else if (then === undefined) {
+      fulfill(promise, maybeThenable);
+    } else if (isFunction(then)) {
+      handleForeignThenable(promise, maybeThenable, then);
+    } else {
+      fulfill(promise, maybeThenable);
+    }
+  }
+}
+
+function resolve(promise, value) {
+  if (promise === value) {
+    reject(promise, selfFullfillment());
+  } else if (objectOrFunction(value)) {
+    handleMaybeThenable(promise, value);
+  } else {
+    fulfill(promise, value);
+  }
+}
+
+function publishRejection(promise) {
+  if (promise._onerror) {
+    promise._onerror(promise._result);
+  }
+
+  publish(promise);
+}
+
+function fulfill(promise, value) {
+  if (promise._state !== PENDING) { return; }
+
+  promise._result = value;
+  promise._state = FULFILLED;
+
+  if (promise._subscribers.length === 0) {
+  } else {
+    asap(publish, promise);
+  }
+}
+
+function reject(promise, reason) {
+  if (promise._state !== PENDING) { return; }
+  promise._state = REJECTED;
+  promise._result = reason;
+
+  asap(publishRejection, promise);
+}
+
+function subscribe(parent, child, onFulfillment, onRejection) {
+  var subscribers = parent._subscribers;
+  var length = subscribers.length;
+
+  parent._onerror = null;
+
+  subscribers[length] = child;
+  subscribers[length + FULFILLED] = onFulfillment;
+  subscribers[length + REJECTED]  = onRejection;
+
+  if (length === 0 && parent._state) {
+    asap(publish, parent);
+  }
+}
+
+function publish(promise) {
+  var subscribers = promise._subscribers;
+  var settled = promise._state;
+
+  if (subscribers.length === 0) { return; }
+
+  var child, callback, detail = promise._result;
+
+  for (var i = 0; i < subscribers.length; i += 3) {
+    child = subscribers[i];
+    callback = subscribers[i + settled];
+
+    if (child) {
+      invokeCallback(settled, child, callback, detail);
+    } else {
+      callback(detail);
+    }
+  }
+
+  promise._subscribers.length = 0;
+}
+
+function ErrorObject() {
+  this.error = null;
+}
+
+var TRY_CATCH_ERROR = new ErrorObject();
+
+function tryCatch(callback, detail) {
+  try {
+    return callback(detail);
+  } catch(e) {
+    TRY_CATCH_ERROR.error = e;
+    return TRY_CATCH_ERROR;
+  }
+}
+
+function invokeCallback(settled, promise, callback, detail) {
+  var hasCallback = isFunction(callback),
+      value, error, succeeded, failed;
+
+  if (hasCallback) {
+    value = tryCatch(callback, detail);
+
+    if (value === TRY_CATCH_ERROR) {
+      failed = true;
+      error = value.error;
+      value = null;
+    } else {
+      succeeded = true;
+    }
+
+    if (promise === value) {
+      reject(promise, cannotReturnOwn());
+      return;
+    }
+
+  } else {
+    value = detail;
+    succeeded = true;
+  }
+
+  if (promise._state !== PENDING) {
+    // noop
+  } else if (hasCallback && succeeded) {
+    resolve(promise, value);
+  } else if (failed) {
+    reject(promise, error);
+  } else if (settled === FULFILLED) {
+    fulfill(promise, value);
+  } else if (settled === REJECTED) {
+    reject(promise, value);
+  }
+}
+
+function initializePromise(promise, resolver) {
+  try {
+    resolver(function resolvePromise(value){
+      resolve(promise, value);
+    }, function rejectPromise(reason) {
+      reject(promise, reason);
+    });
+  } catch(e) {
+    reject(promise, e);
+  }
+}
+
+export {
+  noop,
+  resolve,
+  reject,
+  fulfill,
+  subscribe,
+  publish,
+  publishRejection,
+  initializePromise,
+  invokeCallback,
+  FULFILLED,
+  REJECTED,
+  PENDING
+};
diff --git a/node_modules/node-firefox-start-simulator/node_modules/es6-promise/lib/es6-promise/asap.js b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/lib/es6-promise/asap.js
new file mode 100644
index 0000000..4872589
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/lib/es6-promise/asap.js
@@ -0,0 +1,82 @@
+var len = 0;
+
+export default function asap(callback, arg) {
+  queue[len] = callback;
+  queue[len + 1] = arg;
+  len += 2;
+  if (len === 2) {
+    // If len is 1, that means that we need to schedule an async flush.
+    // If additional callbacks are queued before the queue is flushed, they
+    // will be processed by this flush that we are scheduling.
+    scheduleFlush();
+  }
+}
+
+var browserGlobal = (typeof window !== 'undefined') ? window : {};
+var BrowserMutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver;
+
+// test for web worker but not in IE10
+var isWorker = typeof Uint8ClampedArray !== 'undefined' &&
+  typeof importScripts !== 'undefined' &&
+  typeof MessageChannel !== 'undefined';
+
+// node
+function useNextTick() {
+  return function() {
+    process.nextTick(flush);
+  };
+}
+
+function useMutationObserver() {
+  var iterations = 0;
+  var observer = new BrowserMutationObserver(flush);
+  var node = document.createTextNode('');
+  observer.observe(node, { characterData: true });
+
+  return function() {
+    node.data = (iterations = ++iterations % 2);
+  };
+}
+
+// web worker
+function useMessageChannel() {
+  var channel = new MessageChannel();
+  channel.port1.onmessage = flush;
+  return function () {
+    channel.port2.postMessage(0);
+  };
+}
+
+function useSetTimeout() {
+  return function() {
+    setTimeout(flush, 1);
+  };
+}
+
+var queue = new Array(1000);
+function flush() {
+  for (var i = 0; i < len; i+=2) {
+    var callback = queue[i];
+    var arg = queue[i+1];
+
+    callback(arg);
+
+    queue[i] = undefined;
+    queue[i+1] = undefined;
+  }
+
+  len = 0;
+}
+
+var scheduleFlush;
+
+// Decide what async method to use to triggering processing of queued callbacks:
+if (typeof process !== 'undefined' && {}.toString.call(process) === '[object process]') {
+  scheduleFlush = useNextTick();
+} else if (BrowserMutationObserver) {
+  scheduleFlush = useMutationObserver();
+} else if (isWorker) {
+  scheduleFlush = useMessageChannel();
+} else {
+  scheduleFlush = useSetTimeout();
+}
diff --git a/node_modules/node-firefox-start-simulator/node_modules/es6-promise/lib/es6-promise/enumerator.js b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/lib/es6-promise/enumerator.js
new file mode 100644
index 0000000..7d3f803
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/lib/es6-promise/enumerator.js
@@ -0,0 +1,125 @@
+import {
+  isArray,
+  isMaybeThenable
+} from './utils';
+
+import {
+  noop,
+  reject,
+  fulfill,
+  subscribe,
+  FULFILLED,
+  REJECTED,
+  PENDING
+} from './-internal';
+
+export function makeSettledResult(state, position, value) {
+  if (state === FULFILLED) {
+    return {
+      state: 'fulfilled',
+      value: value
+    };
+  } else {
+    return {
+      state: 'rejected',
+      reason: value
+    };
+  }
+}
+
+function Enumerator(Constructor, input, abortOnReject, label) {
+  this._instanceConstructor = Constructor;
+  this.promise = new Constructor(noop, label);
+  this._abortOnReject = abortOnReject;
+
+  if (this._validateInput(input)) {
+    this._input     = input;
+    this.length     = input.length;
+    this._remaining = input.length;
+
+    this._init();
+
+    if (this.length === 0) {
+      fulfill(this.promise, this._result);
+    } else {
+      this.length = this.length || 0;
+      this._enumerate();
+      if (this._remaining === 0) {
+        fulfill(this.promise, this._result);
+      }
+    }
+  } else {
+    reject(this.promise, this._validationError());
+  }
+}
+
+Enumerator.prototype._validateInput = function(input) {
+  return isArray(input);
+};
+
+Enumerator.prototype._validationError = function() {
+  return new Error('Array Methods must be provided an Array');
+};
+
+Enumerator.prototype._init = function() {
+  this._result = new Array(this.length);
+};
+
+export default Enumerator;
+
+Enumerator.prototype._enumerate = function() {
+  var length  = this.length;
+  var promise = this.promise;
+  var input   = this._input;
+
+  for (var i = 0; promise._state === PENDING && i < length; i++) {
+    this._eachEntry(input[i], i);
+  }
+};
+
+Enumerator.prototype._eachEntry = function(entry, i) {
+  var c = this._instanceConstructor;
+  if (isMaybeThenable(entry)) {
+    if (entry.constructor === c && entry._state !== PENDING) {
+      entry._onerror = null;
+      this._settledAt(entry._state, i, entry._result);
+    } else {
+      this._willSettleAt(c.resolve(entry), i);
+    }
+  } else {
+    this._remaining--;
+    this._result[i] = this._makeResult(FULFILLED, i, entry);
+  }
+};
+
+Enumerator.prototype._settledAt = function(state, i, value) {
+  var promise = this.promise;
+
+  if (promise._state === PENDING) {
+    this._remaining--;
+
+    if (this._abortOnReject && state === REJECTED) {
+      reject(promise, value);
+    } else {
+      this._result[i] = this._makeResult(state, i, value);
+    }
+  }
+
+  if (this._remaining === 0) {
+    fulfill(promise, this._result);
+  }
+};
+
+Enumerator.prototype._makeResult = function(state, i, value) {
+  return value;
+};
+
+Enumerator.prototype._willSettleAt = function(promise, i) {
+  var enumerator = this;
+
+  subscribe(promise, undefined, function(value) {
+    enumerator._settledAt(FULFILLED, i, value);
+  }, function(reason) {
+    enumerator._settledAt(REJECTED, i, reason);
+  });
+};
diff --git a/node_modules/node-firefox-start-simulator/node_modules/es6-promise/lib/es6-promise/polyfill.js b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/lib/es6-promise/polyfill.js
new file mode 100644
index 0000000..e5b0165
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/lib/es6-promise/polyfill.js
@@ -0,0 +1,35 @@
+/*global self*/
+import { default as RSVPPromise } from "./promise";
+import { isFunction } from "./utils";
+
+export default function polyfill() {
+  var local;
+
+  if (typeof global !== 'undefined') {
+    local = global;
+  } else if (typeof window !== 'undefined' && window.document) {
+    local = window;
+  } else {
+    local = self;
+  }
+
+  var es6PromiseSupport =
+    "Promise" in local &&
+    // Some of these methods are missing from
+    // Firefox/Chrome experimental implementations
+    "resolve" in local.Promise &&
+    "reject" in local.Promise &&
+    "all" in local.Promise &&
+    "race" in local.Promise &&
+    // Older version of the spec had a resolver object
+    // as the arg rather than a function
+    (function() {
+      var resolve;
+      new local.Promise(function(r) { resolve = r; });
+      return isFunction(resolve);
+    }());
+
+  if (!es6PromiseSupport) {
+    local.Promise = RSVPPromise;
+  }
+}
diff --git a/node_modules/node-firefox-start-simulator/node_modules/es6-promise/lib/es6-promise/promise.js b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/lib/es6-promise/promise.js
new file mode 100644
index 0000000..5e865a7
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/lib/es6-promise/promise.js
@@ -0,0 +1,409 @@
+import {
+  isFunction,
+  now
+} from './utils';
+
+import {
+  noop,
+  subscribe,
+  initializePromise,
+  invokeCallback,
+  FULFILLED,
+  REJECTED
+} from './-internal';
+
+import asap from './asap';
+
+import all from './promise/all';
+import race from './promise/race';
+import Resolve from './promise/resolve';
+import Reject from './promise/reject';
+
+var counter = 0;
+
+function needsResolver() {
+  throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
+}
+
+function needsNew() {
+  throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");
+}
+
+export default Promise;
+/**
+  Promise objects represent the eventual result of an asynchronous operation. The
+  primary way of interacting with a promise is through its `then` method, which
+  registers callbacks to receive either a promise’s eventual value or the reason
+  why the promise cannot be fulfilled.
+
+  Terminology
+  -----------
+
+  - `promise` is an object or function with a `then` method whose behavior conforms to this specification.
+  - `thenable` is an object or function that defines a `then` method.
+  - `value` is any legal JavaScript value (including undefined, a thenable, or a promise).
+  - `exception` is a value that is thrown using the throw statement.
+  - `reason` is a value that indicates why a promise was rejected.
+  - `settled` the final resting state of a promise, fulfilled or rejected.
+
+  A promise can be in one of three states: pending, fulfilled, or rejected.
+
+  Promises that are fulfilled have a fulfillment value and are in the fulfilled
+  state.  Promises that are rejected have a rejection reason and are in the
+  rejected state.  A fulfillment value is never a thenable.
+
+  Promises can also be said to *resolve* a value.  If this value is also a
+  promise, then the original promise's settled state will match the value's
+  settled state.  So a promise that *resolves* a promise that rejects will
+  itself reject, and a promise that *resolves* a promise that fulfills will
+  itself fulfill.
+
+
+  Basic Usage:
+  ------------
+
+  ```js
+  var promise = new Promise(function(resolve, reject) {
+    // on success
+    resolve(value);
+
+    // on failure
+    reject(reason);
+  });
+
+  promise.then(function(value) {
+    // on fulfillment
+  }, function(reason) {
+    // on rejection
+  });
+  ```
+
+  Advanced Usage:
+  ---------------
+
+  Promises shine when abstracting away asynchronous interactions such as
+  `XMLHttpRequest`s.
+
+  ```js
+  function getJSON(url) {
+    return new Promise(function(resolve, reject){
+      var xhr = new XMLHttpRequest();
+
+      xhr.open('GET', url);
+      xhr.onreadystatechange = handler;
+      xhr.responseType = 'json';
+      xhr.setRequestHeader('Accept', 'application/json');
+      xhr.send();
+
+      function handler() {
+        if (this.readyState === this.DONE) {
+          if (this.status === 200) {
+            resolve(this.response);
+          } else {
+            reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']'));
+          }
+        }
+      };
+    });
+  }
+
+  getJSON('/posts.json').then(function(json) {
+    // on fulfillment
+  }, function(reason) {
+    // on rejection
+  });
+  ```
+
+  Unlike callbacks, promises are great composable primitives.
+
+  ```js
+  Promise.all([
+    getJSON('/posts'),
+    getJSON('/comments')
+  ]).then(function(values){
+    values[0] // => postsJSON
+    values[1] // => commentsJSON
+
+    return values;
+  });
+  ```
+
+  @class Promise
+  @param {function} resolver
+  Useful for tooling.
+  @constructor
+*/
+function Promise(resolver) {
+  this._id = counter++;
+  this._state = undefined;
+  this._result = undefined;
+  this._subscribers = [];
+
+  if (noop !== resolver) {
+    if (!isFunction(resolver)) {
+      needsResolver();
+    }
+
+    if (!(this instanceof Promise)) {
+      needsNew();
+    }
+
+    initializePromise(this, resolver);
+  }
+}
+
+Promise.all = all;
+Promise.race = race;
+Promise.resolve = Resolve;
+Promise.reject = Reject;
+
+Promise.prototype = {
+  constructor: Promise,
+
+/**
+  The primary way of interacting with a promise is through its `then` method,
+  which registers callbacks to receive either a promise's eventual value or the
+  reason why the promise cannot be fulfilled.
+
+  ```js
+  findUser().then(function(user){
+    // user is available
+  }, function(reason){
+    // user is unavailable, and you are given the reason why
+  });
+  ```
+
+  Chaining
+  --------
+
+  The return value of `then` is itself a promise.  This second, 'downstream'
+  promise is resolved with the return value of the first promise's fulfillment
+  or rejection handler, or rejected if the handler throws an exception.
+
+  ```js
+  findUser().then(function (user) {
+    return user.name;
+  }, function (reason) {
+    return 'default name';
+  }).then(function (userName) {
+    // If `findUser` fulfilled, `userName` will be the user's name, otherwise it
+    // will be `'default name'`
+  });
+
+  findUser().then(function (user) {
+    throw new Error('Found user, but still unhappy');
+  }, function (reason) {
+    throw new Error('`findUser` rejected and we're unhappy');
+  }).then(function (value) {
+    // never reached
+  }, function (reason) {
+    // if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'.
+    // If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'.
+  });
+  ```
+  If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream.
+
+  ```js
+  findUser().then(function (user) {
+    throw new PedagogicalException('Upstream error');
+  }).then(function (value) {
+    // never reached
+  }).then(function (value) {
+    // never reached
+  }, function (reason) {
+    // The `PedgagocialException` is propagated all the way down to here
+  });
+  ```
+
+  Assimilation
+  ------------
+
+  Sometimes the value you want to propagate to a downstream promise can only be
+  retrieved asynchronously. This can be achieved by returning a promise in the
+  fulfillment or rejection handler. The downstream promise will then be pending
+  until the returned promise is settled. This is called *assimilation*.
+
+  ```js
+  findUser().then(function (user) {
+    return findCommentsByAuthor(user);
+  }).then(function (comments) {
+    // The user's comments are now available
+  });
+  ```
+
+  If the assimliated promise rejects, then the downstream promise will also reject.
+
+  ```js
+  findUser().then(function (user) {
+    return findCommentsByAuthor(user);
+  }).then(function (comments) {
+    // If `findCommentsByAuthor` fulfills, we'll have the value here
+  }, function (reason) {
+    // If `findCommentsByAuthor` rejects, we'll have the reason here
+  });
+  ```
+
+  Simple Example
+  --------------
+
+  Synchronous Example
+
+  ```javascript
+  var result;
+
+  try {
+    result = findResult();
+    // success
+  } catch(reason) {
+    // failure
+  }
+  ```
+
+  Errback Example
+
+  ```js
+  findResult(function(result, err){
+    if (err) {
+      // failure
+    } else {
+      // success
+    }
+  });
+  ```
+
+  Promise Example;
+
+  ```javascript
+  findResult().then(function(result){
+    // success
+  }, function(reason){
+    // failure
+  });
+  ```
+
+  Advanced Example
+  --------------
+
+  Synchronous Example
+
+  ```javascript
+  var author, books;
+
+  try {
+    author = findAuthor();
+    books  = findBooksByAuthor(author);
+    // success
+  } catch(reason) {
+    // failure
+  }
+  ```
+
+  Errback Example
+
+  ```js
+
+  function foundBooks(books) {
+
+  }
+
+  function failure(reason) {
+
+  }
+
+  findAuthor(function(author, err){
+    if (err) {
+      failure(err);
+      // failure
+    } else {
+      try {
+        findBoooksByAuthor(author, function(books, err) {
+          if (err) {
+            failure(err);
+          } else {
+            try {
+              foundBooks(books);
+            } catch(reason) {
+              failure(reason);
+            }
+          }
+        });
+      } catch(error) {
+        failure(err);
+      }
+      // success
+    }
+  });
+  ```
+
+  Promise Example;
+
+  ```javascript
+  findAuthor().
+    then(findBooksByAuthor).
+    then(function(books){
+      // found books
+  }).catch(function(reason){
+    // something went wrong
+  });
+  ```
+
+  @method then
+  @param {Function} onFulfilled
+  @param {Function} onRejected
+  Useful for tooling.
+  @return {Promise}
+*/
+  then: function(onFulfillment, onRejection) {
+    var parent = this;
+    var state = parent._state;
+
+    if (state === FULFILLED && !onFulfillment || state === REJECTED && !onRejection) {
+      return this;
+    }
+
+    var child = new this.constructor(noop);
+    var result = parent._result;
+
+    if (state) {
+      var callback = arguments[state - 1];
+      asap(function(){
+        invokeCallback(state, child, callback, result);
+      });
+    } else {
+      subscribe(parent, child, onFulfillment, onRejection);
+    }
+
+    return child;
+  },
+
+/**
+  `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same
+  as the catch block of a try/catch statement.
+
+  ```js
+  function findAuthor(){
+    throw new Error('couldn't find that author');
+  }
+
+  // synchronous
+  try {
+    findAuthor();
+  } catch(reason) {
+    // something went wrong
+  }
+
+  // async with promises
+  findAuthor().catch(function(reason){
+    // something went wrong
+  });
+  ```
+
+  @method catch
+  @param {Function} onRejection
+  Useful for tooling.
+  @return {Promise}
+*/
+  'catch': function(onRejection) {
+    return this.then(null, onRejection);
+  }
+};
diff --git a/node_modules/node-firefox-start-simulator/node_modules/es6-promise/lib/es6-promise/promise/all.js b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/lib/es6-promise/promise/all.js
new file mode 100644
index 0000000..8db7e71
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/lib/es6-promise/promise/all.js
@@ -0,0 +1,52 @@
+import Enumerator from '../enumerator';
+
+/**
+  `Promise.all` accepts an array of promises, and returns a new promise which
+  is fulfilled with an array of fulfillment values for the passed promises, or
+  rejected with the reason of the first passed promise to be rejected. It casts all
+  elements of the passed iterable to promises as it runs this algorithm.
+
+  Example:
+
+  ```javascript
+  var promise1 = resolve(1);
+  var promise2 = resolve(2);
+  var promise3 = resolve(3);
+  var promises = [ promise1, promise2, promise3 ];
+
+  Promise.all(promises).then(function(array){
+    // The array here would be [ 1, 2, 3 ];
+  });
+  ```
+
+  If any of the `promises` given to `all` are rejected, the first promise
+  that is rejected will be given as an argument to the returned promises's
+  rejection handler. For example:
+
+  Example:
+
+  ```javascript
+  var promise1 = resolve(1);
+  var promise2 = reject(new Error("2"));
+  var promise3 = reject(new Error("3"));
+  var promises = [ promise1, promise2, promise3 ];
+
+  Promise.all(promises).then(function(array){
+    // Code here never runs because there are rejected promises!
+  }, function(error) {
+    // error.message === "2"
+  });
+  ```
+
+  @method all
+  @static
+  @param {Array} entries array of promises
+  @param {String} label optional string for labeling the promise.
+  Useful for tooling.
+  @return {Promise} promise that is fulfilled when all `promises` have been
+  fulfilled, or rejected if any of them become rejected.
+  @static
+*/
+export default function all(entries, label) {
+  return new Enumerator(this, entries, true /* abort on reject */, label).promise;
+}
diff --git a/node_modules/node-firefox-start-simulator/node_modules/es6-promise/lib/es6-promise/promise/race.js b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/lib/es6-promise/promise/race.js
new file mode 100644
index 0000000..7daa28a
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/lib/es6-promise/promise/race.js
@@ -0,0 +1,105 @@
+import {
+  isArray
+} from "../utils";
+
+import {
+  noop,
+  resolve,
+  reject,
+  subscribe,
+  PENDING
+} from '../-internal';
+
+/**
+  `Promise.race` returns a new promise which is settled in the same way as the
+  first passed promise to settle.
+
+  Example:
+
+  ```javascript
+  var promise1 = new Promise(function(resolve, reject){
+    setTimeout(function(){
+      resolve('promise 1');
+    }, 200);
+  });
+
+  var promise2 = new Promise(function(resolve, reject){
+    setTimeout(function(){
+      resolve('promise 2');
+    }, 100);
+  });
+
+  Promise.race([promise1, promise2]).then(function(result){
+    // result === 'promise 2' because it was resolved before promise1
+    // was resolved.
+  });
+  ```
+
+  `Promise.race` is deterministic in that only the state of the first
+  settled promise matters. For example, even if other promises given to the
+  `promises` array argument are resolved, but the first settled promise has
+  become rejected before the other promises became fulfilled, the returned
+  promise will become rejected:
+
+  ```javascript
+  var promise1 = new Promise(function(resolve, reject){
+    setTimeout(function(){
+      resolve('promise 1');
+    }, 200);
+  });
+
+  var promise2 = new Promise(function(resolve, reject){
+    setTimeout(function(){
+      reject(new Error('promise 2'));
+    }, 100);
+  });
+
+  Promise.race([promise1, promise2]).then(function(result){
+    // Code here never runs
+  }, function(reason){
+    // reason.message === 'promise 2' because promise 2 became rejected before
+    // promise 1 became fulfilled
+  });
+  ```
+
+  An example real-world use case is implementing timeouts:
+
+  ```javascript
+  Promise.race([ajax('foo.json'), timeout(5000)])
+  ```
+
+  @method race
+  @static
+  @param {Array} promises array of promises to observe
+  @param {String} label optional string for describing the promise returned.
+  Useful for tooling.
+  @return {Promise} a promise which settles in the same way as the first passed
+  promise to settle.
+*/
+export default function race(entries, label) {
+  /*jshint validthis:true */
+  var Constructor = this;
+
+  var promise = new Constructor(noop, label);
+
+  if (!isArray(entries)) {
+    reject(promise, new TypeError('You must pass an array to race.'));
+    return promise;
+  }
+
+  var length = entries.length;
+
+  function onFulfillment(value) {
+    resolve(promise, value);
+  }
+
+  function onRejection(reason) {
+    reject(promise, reason);
+  }
+
+  for (var i = 0; promise._state === PENDING && i < length; i++) {
+    subscribe(Constructor.resolve(entries[i]), undefined, onFulfillment, onRejection);
+  }
+
+  return promise;
+}
diff --git a/node_modules/node-firefox-start-simulator/node_modules/es6-promise/lib/es6-promise/promise/reject.js b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/lib/es6-promise/promise/reject.js
new file mode 100644
index 0000000..518eff7
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/lib/es6-promise/promise/reject.js
@@ -0,0 +1,47 @@
+import {
+  noop,
+  reject as _reject
+} from '../-internal';
+
+/**
+  `Promise.reject` returns a promise rejected with the passed `reason`.
+  It is shorthand for the following:
+
+  ```javascript
+  var promise = new Promise(function(resolve, reject){
+    reject(new Error('WHOOPS'));
+  });
+
+  promise.then(function(value){
+    // Code here doesn't run because the promise is rejected!
+  }, function(reason){
+    // reason.message === 'WHOOPS'
+  });
+  ```
+
+  Instead of writing the above, your code now simply becomes the following:
+
+  ```javascript
+  var promise = Promise.reject(new Error('WHOOPS'));
+
+  promise.then(function(value){
+    // Code here doesn't run because the promise is rejected!
+  }, function(reason){
+    // reason.message === 'WHOOPS'
+  });
+  ```
+
+  @method reject
+  @static
+  @param {Any} reason value that the returned promise will be rejected with.
+  @param {String} label optional string for identifying the returned promise.
+  Useful for tooling.
+  @return {Promise} a promise rejected with the given `reason`.
+*/
+export default function reject(reason, label) {
+  /*jshint validthis:true */
+  var Constructor = this;
+  var promise = new Constructor(noop, label);
+  _reject(promise, reason);
+  return promise;
+}
diff --git a/node_modules/node-firefox-start-simulator/node_modules/es6-promise/lib/es6-promise/promise/resolve.js b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/lib/es6-promise/promise/resolve.js
new file mode 100644
index 0000000..1b3c533
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/lib/es6-promise/promise/resolve.js
@@ -0,0 +1,49 @@
+import {
+  noop,
+  resolve as _resolve
+} from '../-internal';
+
+/**
+  `Promise.resolve` returns a promise that will become resolved with the
+  passed `value`. It is shorthand for the following:
+
+  ```javascript
+  var promise = new Promise(function(resolve, reject){
+    resolve(1);
+  });
+
+  promise.then(function(value){
+    // value === 1
+  });
+  ```
+
+  Instead of writing the above, your code now simply becomes the following:
+
+  ```javascript
+  var promise = Promise.resolve(1);
+
+  promise.then(function(value){
+    // value === 1
+  });
+  ```
+
+  @method resolve
+  @static
+  @param {Any} value value that the returned promise will be resolved with
+  @param {String} label optional string for identifying the returned promise.
+  Useful for tooling.
+  @return {Promise} a promise that will become fulfilled with the given
+  `value`
+*/
+export default function resolve(object, label) {
+  /*jshint validthis:true */
+  var Constructor = this;
+
+  if (object && typeof object === 'object' && object.constructor === Constructor) {
+    return object;
+  }
+
+  var promise = new Constructor(noop, label);
+  _resolve(promise, object);
+  return promise;
+}
diff --git a/node_modules/node-firefox-start-simulator/node_modules/es6-promise/lib/es6-promise/utils.js b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/lib/es6-promise/utils.js
new file mode 100644
index 0000000..6b49bbf
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/lib/es6-promise/utils.js
@@ -0,0 +1,39 @@
+export function objectOrFunction(x) {
+  return typeof x === 'function' || (typeof x === 'object' && x !== null);
+}
+
+export function isFunction(x) {
+  return typeof x === 'function';
+}
+
+export function isMaybeThenable(x) {
+  return typeof x === 'object' && x !== null;
+}
+
+var _isArray;
+if (!Array.isArray) {
+  _isArray = function (x) {
+    return Object.prototype.toString.call(x) === '[object Array]';
+  };
+} else {
+  _isArray = Array.isArray;
+}
+
+export var isArray = _isArray;
+
+// Date.now is not available in browsers < IE9
+// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now#Compatibility
+export var now = Date.now || function() { return new Date().getTime(); };
+
+function F() { }
+
+export var o_create = (Object.create || function (o) {
+  if (arguments.length > 1) {
+    throw new Error('Second argument not supported');
+  }
+  if (typeof o !== 'object') {
+    throw new TypeError('Argument must be an object');
+  }
+  F.prototype = o;
+  return new F();
+});
diff --git a/node_modules/node-firefox-start-simulator/node_modules/es6-promise/package.json b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/package.json
new file mode 100644
index 0000000..28494ce
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/package.json
@@ -0,0 +1,81 @@
+{
+  "name": "es6-promise",
+  "namespace": "es6-promise",
+  "version": "2.0.1",
+  "description": "A lightweight library that provides tools for organizing asynchronous code",
+  "main": "dist/es6-promise.js",
+  "directories": {
+    "lib": "lib"
+  },
+  "devDependencies": {
+    "bower": "^1.3.9",
+    "brfs": "0.0.8",
+    "broccoli-closure-compiler": "^0.2.0",
+    "broccoli-compile-modules": "git+https://github.com/eventualbuddha/broccoli-compile-modules",
+    "broccoli-concat": "0.0.7",
+    "broccoli-es3-safe-recast": "0.0.8",
+    "broccoli-file-mover": "^0.4.0",
+    "broccoli-jshint": "^0.5.1",
+    "broccoli-merge-trees": "^0.1.4",
+    "broccoli-static-compiler": "^0.1.4",
+    "broccoli-string-replace": "0.0.1",
+    "browserify": "^4.2.0",
+    "ember-cli": "0.0.40",
+    "ember-publisher": "0.0.7",
+    "es6-module-transpiler-amd-formatter": "0.0.1",
+    "express": "^4.5.0",
+    "jshint": "~0.9.1",
+    "mkdirp": "^0.5.0",
+    "mocha": "^1.20.1",
+    "promises-aplus-tests": "git://github.com/stefanpenner/promises-tests.git",
+    "release-it": "0.0.10",
+    "testem": "^0.6.17",
+    "json3": "^3.3.2"
+  },
+  "scripts": {
+    "test": "testem ci -R dot",
+    "test-server": "testem",
+    "lint": "jshint lib",
+    "prepublish": "ember build --environment production",
+    "aplus": "browserify test/main.js",
+    "build-all": "ember build --environment production && browserify ./test/main.js -o tmp/test-bundle.js",
+    "dry-run-release": "ember build --environment production && release-it --dry-run --non-interactive"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/jakearchibald/ES6-Promises.git"
+  },
+  "bugs": {
+    "url": "https://github.com/jakearchibald/ES6-Promises/issues"
+  },
+  "keywords": [
+    "promises",
+    "futures"
+  ],
+  "author": {
+    "name": "Yehuda Katz, Tom Dale, Stefan Penner and contributors",
+    "url": "Conversion to ES6 API by Jake Archibald"
+  },
+  "license": "MIT",
+  "homepage": "https://github.com/jakearchibald/ES6-Promises",
+  "_id": "es6-promise@2.0.1",
+  "dist": {
+    "shasum": "ccc4963e679f0ca9fb187c777b9e583d3c7573c2",
+    "tarball": "http://registry.npmjs.org/es6-promise/-/es6-promise-2.0.1.tgz"
+  },
+  "_from": "es6-promise@^2.0.1",
+  "_npmVersion": "1.3.24",
+  "_npmUser": {
+    "name": "jaffathecake",
+    "email": "jaffathecake@gmail.com"
+  },
+  "maintainers": [
+    {
+      "name": "jaffathecake",
+      "email": "jaffathecake@gmail.com"
+    }
+  ],
+  "_shasum": "ccc4963e679f0ca9fb187c777b9e583d3c7573c2",
+  "_resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-2.0.1.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-start-simulator/node_modules/es6-promise/server/.jshintrc b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/server/.jshintrc
new file mode 100644
index 0000000..c1f2978
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/server/.jshintrc
@@ -0,0 +1,3 @@
+{
+  "node": true
+}
diff --git a/node_modules/node-firefox-start-simulator/node_modules/es6-promise/server/index.js b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/server/index.js
new file mode 100644
index 0000000..8a54d36
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/server/index.js
@@ -0,0 +1,6 @@
+module.exports = function(app) {
+  app.use(require('express').static(__dirname + '/../'));
+  app.get('/', function(req, res) {
+    res.redirect('/test/');
+  })
+};
diff --git a/node_modules/node-firefox-start-simulator/node_modules/es6-promise/testem.json b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/testem.json
new file mode 100644
index 0000000..7494912
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/es6-promise/testem.json
@@ -0,0 +1,11 @@
+{
+  "test_page": "test/index.html",
+  "parallel": 5,
+  "before_tests": "npm run build-all",
+  "launchers": {
+    "Mocha": {
+      "command": "./node_modules/.bin/mocha test/main.js"
+    }
+  },
+  "launch_in_ci":  ["PhantomJS", "Mocha"]
+}
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/README.md b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/README.md
new file mode 100644
index 0000000..c6aefea
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/README.md
@@ -0,0 +1,157 @@
+# firefox-client
+`firefox-client` is a [node](nodejs.org) library for remote debugging Firefox. You can use it to make things like [fxconsole](https://github.com/harthur/fxconsole), a remote JavaScript REPL.
+
+```javascript
+var FirefoxClient = require("firefox-client");
+
+var client = new FirefoxClient();
+
+client.connect(6000, function() {
+  client.listTabs(function(err, tabs) {
+    console.log("first tab:", tabs[0].url);
+  });
+});
+```
+
+## Install
+With [node.js](http://nodejs.org/) npm package manager:
+
+```bash
+npm install firefox-client
+```
+
+## Connecting
+
+### Desktop Firefox
+1. Enable remote debugging (You'll only have to do this once)
+ 1. Open the DevTools. **Web Developer** > **Toggle Tools**
+ 2. Visit the settings panel (gear icon)
+ 3. Check "Enable remote debugging" under Advanced Settings
+
+2. Listen for a connection
+ 1. Open the Firefox command line with **Tools** > **Web Developer** > **Developer Toolbar**.
+ 2. Start a server by entering this command: `listen 6000` (where `6000` is the port number)
+
+### Firefox for Android
+Follow the instructions in [this Hacks video](https://www.youtube.com/watch?v=Znj_8IFeTVs)
+
+### Firefox OS 1.1 Simulator
+A limited set of the API (`Console`, `StyleSheets`) is compatible with the [Simulator 4.0](https://addons.mozilla.org/en-US/firefox/addon/firefox-os-simulator/). See the [wiki instructions](https://github.com/harthur/firefox-client/wiki/Firefox-OS-Simulator-Instructions) for connecting.
+
+`client.listTabs()` will list the currently open apps in the Simulator.
+
+### Firefox OS 1.2+ Simulator and devices
+
+`client.getWebapps()` will expose the webapps in the Simulator, where each app implements the `Tab` API.
+
+```
+client.getWebapps(function(err, webapps) {
+  webapps.getApp("app://homescreen.gaiamobile.org/manifest.webapp", function (err, app) {
+    console.log("homescreen:", actor.url);
+    app.Console.evaluateJS("alert('foo')", function(err, resp) {
+      console.log("alert dismissed");
+    });
+  });
+});
+```
+
+## Compatibility
+
+This latest version of the library will stay compatible with [Firefox Nightly](http://nightly.mozilla.org/). Almost all of it will be compatible with [Firefox Aurora](http://www.mozilla.org/en-US/firefox/aurora/) as well.
+
+## API
+
+A `FirefoxClient` is the entry point to the API. After connecting, get a `Tab` object with `listTabs()` or `selectedTab()`. Once you have a `Tab`, you can call methods and listen to events from the tab's modules, `Console` or `Network`. There are also experimental `DOM` and `StyleSheets` tab modules, and an upcoming `Debugger` module.
+
+#### Methods
+Almost all API calls take a callback that will get called with an error as the first argument (or `null` if there is no error), and a return value as the second:
+
+```javascript
+tab.Console.evaluateJS("6 + 7", function(err, resp) {
+  if (err) throw err;
+
+  console.log(resp.result);
+});
+```
+
+#### Events
+
+The modules are `EventEmitter`s, listen for events with `on` or `once`, and stop listening with `off`:
+
+```javascript
+tab.Console.on("page-error", function(event) {
+  console.log("new error from tab:", event.errorMessage);
+});
+```
+
+Summary of the offerings of the modules and objects:
+
+#### [FirefoxClient](http://github.com/harthur/firefox-client/wiki/FirefoxClient)
+Methods: `connect()`, `disconnect()`, `listTabs()`, `selectedTab()`, `getWebapps()`, `getRoot()`
+
+Events: `"error"`, `"timeout"`, `"end"`
+
+#### [Tab](https://github.com/harthur/firefox-client/wiki/Tab)
+Properties: `url`, `title`
+
+Methods: `reload()`, `navigateTo()`, `attach()`, `detach()`
+
+Events: `"navigate"`, `"before-navigate"`
+
+#### [Tab.Console](https://github.com/harthur/firefox-client/wiki/Console)
+Methods: `evaluateJS()`, `startListening()`, `stopListening()`, `getCachedLogs()`
+
+Events: `"page-error"`, `"console-api-call"`
+
+#### [JSObject](https://github.com/harthur/firefox-client/wiki/JSObject)
+Properties: `class`, `name`, `displayName`
+
+Methods: `ownPropertyNames()`, `ownPropertyDescriptor()`, `ownProperties()`, `prototype()`
+
+#### [Tab.Network](https://github.com/harthur/firefox-client/wiki/Network)
+Methods: `startLogging()`, `stopLogging()`, `sendHTTPRequest()`
+
+Events: `"network-event"`
+
+#### [NetworkEvent](https://github.com/harthur/firefox-client/wiki/NetworkEvent)
+Properties: `url`, `method`, `isXHR`
+
+Methods: `getRequestHeaders()`, `getRequestCookies()`, `getRequestPostData()`, `getResponseHeaders()`, `getResponseCookies()`, `getResponseContent()`, `getEventTimings()`
+
+Events: `"request-headers"`, `"request-cookies"`, `"request-postdata"`, `"response-start"`, `"response-headers"`, `"response-cookies"`, `"event-timings"`
+
+#### [Tab.DOM](https://github.com/harthur/firefox-client/wiki/DOM)
+Methods: `document()`, `documentElement()`, `querySelector()`, `querySelectorAll()`
+
+#### [DOMNode](https://github.com/harthur/firefox-client/wiki/DOMNode)
+Properties: `nodeValue`, `nodeName`, `namespaceURI`
+
+Methods: `parentNode()`, `parents()`, `siblings()`, `nextSibling()`, `previousSibling()`, `querySelector()`, `querySelectorAll()`, `innerHTML()`, `outerHTML()`, `setAttribute()`, `remove()`, `release()`
+
+#### [Tab.StyleSheets](https://github.com/harthur/firefox-client/wiki/StyleSheets)
+Methods: `getStyleSheets()`, `addStyleSheet()`
+
+#### [StyleSheet](https://github.com/harthur/firefox-client/wiki/StyleSheet)
+Properties: `href`, `disabled`, `ruleCount`
+
+Methods: `getText()`, `update()`, `toggleDisabled()`, `getOriginalSources()`
+
+Events: `"disabled-changed"`, `"ruleCount-changed"`
+
+#### Tab.Memory
+Methods: `measure()`
+
+#### Webapps
+Methods: `listRunningApps()`, `getInstalledApps()`, `watchApps()`, `unwatchApps()`, `launch()`, `close()`, `getApp()`, `installHosted()`, `installPackaged()`, `installPackagedWithADB()`, `uninstall()`
+
+Events: `"appOpen"`, `"appClose"`, `"appInstall"`, `"appUninstall"`
+
+## Examples
+
+[fxconsole](https://github.com/harthur/fxconsole) - a remote JavaScript console for Firefox
+
+[webapps test script](https://pastebin.mozilla.org/5094843) - a sample usage of all webapps features
+
+## Feedback
+
+What do you need from the API? [File an issue](https://github.com/harthur/firefox-client/issues/new).
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/index.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/index.js
new file mode 100644
index 0000000..cbf7a39
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/index.js
@@ -0,0 +1 @@
+module.exports = require("./lib/browser");
\ No newline at end of file
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/lib/browser.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/lib/browser.js
new file mode 100644
index 0000000..2e52220
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/lib/browser.js
@@ -0,0 +1,123 @@
+var extend = require("./extend"),
+    ClientMethods = require("./client-methods"),
+    Client = require("./client"),
+    Tab = require("./tab"),
+    Webapps = require("./webapps"),
+    Device = require("./device"),
+    SimulatorApps = require("./simulator");
+
+const DEFAULT_PORT = 6000;
+const DEFAULT_HOST = "localhost";
+
+module.exports = FirefoxClient;
+
+function FirefoxClient(options) {
+  var client = new Client(options);
+  var actor = 'root';
+
+  client.on("error", this.onError.bind(this));
+  client.on("end", this.onEnd.bind(this));
+  client.on("timeout", this.onTimeout.bind(this));
+
+  this.initialize(client, actor);
+}
+
+FirefoxClient.prototype = extend(ClientMethods, {
+  connect: function(port, host, cb) {
+    if (typeof port == "function") {
+      // (cb)
+      cb = port;
+      port = DEFAULT_PORT;
+      host = DEFAULT_HOST;
+
+    }
+    if (typeof host == "function") {
+      // (port, cb)
+      cb = host;
+      host = DEFAULT_HOST;
+    }
+    // (port, host, cb)
+
+    this.client.connect(port, host, cb);
+
+    this.client.expectReply(this.actor, function(packet) {
+      // root message
+    });
+  },
+
+  disconnect: function() {
+    this.client.disconnect();
+  },
+
+  onError: function(error) {
+    this.emit("error", error);
+  },
+
+  onEnd: function() {
+    this.emit("end");
+  },
+
+  onTimeout: function() {
+    this.emit("timeout");
+  },
+
+  selectedTab: function(cb) {
+    this.request("listTabs", function(resp) {
+      var tab = resp.tabs[resp.selected];
+      return new Tab(this.client, tab);
+    }.bind(this), cb);
+  },
+
+  listTabs: function(cb) {
+    this.request("listTabs", function(err, resp) {
+      if (err) {
+        return cb(err);
+      }
+
+      if (resp.simulatorWebappsActor) {
+        // the server is the Firefox OS Simulator, return apps as "tabs"
+        var apps = new SimulatorApps(this.client, resp.simulatorWebappsActor);
+        apps.listApps(cb);
+      }
+      else {
+        var tabs = resp.tabs.map(function(tab) {
+          return new Tab(this.client, tab);
+        }.bind(this));
+        cb(null, tabs);
+      }
+    }.bind(this));
+  },
+
+  getWebapps: function(cb) {
+    this.request("listTabs", (function(err, resp) {
+      if (err) {
+        return cb(err);
+      }
+      var webapps = new Webapps(this.client, resp);
+      cb(null, webapps);
+    }).bind(this));
+  },
+
+  getDevice: function(cb) {
+    this.request("listTabs", (function(err, resp) {
+      if (err) {
+        return cb(err);
+      }
+      var device = new Device(this.client, resp);
+      cb(null, device);
+    }).bind(this));
+  },
+
+  getRoot: function(cb) {
+    this.request("listTabs", (function(err, resp) {
+      if (err) {
+        return cb(err);
+      }
+      if (!resp.consoleActor) {
+        return cb("No root actor being available.");
+      }
+      var root = new Tab(this.client, resp);
+      cb(null, root);
+    }).bind(this));
+  }
+})
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/lib/client-methods.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/lib/client-methods.js
new file mode 100644
index 0000000..c92c44c
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/lib/client-methods.js
@@ -0,0 +1,115 @@
+var events = require("events"),
+    extend = require("./extend");
+
+// to be instantiated later - to avoid circular dep resolution
+var JSObject;
+
+var ClientMethods = extend(events.EventEmitter.prototype, {
+  /**
+   * Intialize this client object.
+   *
+   * @param  {object} client
+   *         Client to send requests on.
+   * @param  {string} actor
+   *         Actor id to set as 'from' field on requests
+   */
+  initialize: function(client, actor) {
+    this.client = client;
+    this.actor = actor;
+
+    this.client.on('message', function(message) {
+      if (message.from == this.actor) {
+        this.emit(message.type, message);
+      }
+    }.bind(this));
+  },
+
+  /**
+   * Make request to our actor on the server.
+   *
+   * @param  {string}   type
+   *         Method name of the request
+   * @param  {object}   message
+   *         Optional extra properties (arguments to method)
+   * @param  {Function}   transform
+   *         Optional tranform for response object. Takes response object
+   *         and returns object to send on.
+   * @param  {Function} callback
+   *         Callback to call with (maybe transformed) response
+   */
+  request: function(type, message, transform, callback) {
+    if (typeof message == "function") {
+      if (typeof transform == "function") {
+        // (type, trans, cb)
+        callback = transform;
+        transform = message;
+      }
+      else {
+        // (type, cb)
+        callback = message;
+      }
+      message = {};
+    }
+    else if (!callback) {
+      if (!message) {
+        // (type)
+        message = {};
+      }
+      // (type, message, cb)
+      callback = transform;
+      transform = null;
+    }
+
+    message.to = this.actor;
+    message.type = type;
+
+    this.client.makeRequest(message, function(resp) {
+      delete resp.from;
+
+      if (resp.error) {
+        var err = new Error(resp.message);
+        err.name = resp.error;
+
+        callback(err);
+        return;
+      }
+
+      if (transform) {
+        resp = transform(resp);
+      }
+
+      if (callback) {
+        callback(null, resp);
+      }
+    });
+  },
+
+  /*
+   * Transform obj response into a JSObject
+   */
+  createJSObject: function(obj) {
+    if (obj == null) {
+      return;
+    }
+    if (!JSObject) {
+      // circular dependencies
+      JSObject = require("./jsobject");
+    }
+    if (obj.type == "object") {
+      return new JSObject(this.client, obj);
+    }
+    return obj;
+  },
+
+  /**
+   * Create function that plucks out only one value from an object.
+   * Used as the transform function for some responses.
+   */
+  pluck: function(prop) {
+    return function(obj) {
+      return obj[prop];
+    }
+  }
+})
+
+module.exports = ClientMethods;
\ No newline at end of file
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/lib/client.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/lib/client.js
new file mode 100644
index 0000000..22b3179
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/lib/client.js
@@ -0,0 +1,246 @@
+var net = require("net"),
+    events = require("events"),
+    extend = require("./extend");
+
+var colors = require("colors");
+
+module.exports = Client;
+
+// this is very unfortunate! and temporary. we can't
+// rely on 'type' property to signify an event, and we
+// need to write clients for each actor to handle differences
+// in actor protocols
+var unsolicitedEvents = {
+  "tabNavigated": "tabNavigated",
+  "styleApplied": "styleApplied",
+  "propertyChange": "propertyChange",
+  "networkEventUpdate": "networkEventUpdate",
+  "networkEvent": "networkEvent",
+  "propertyChange": "propertyChange",
+  "newMutations": "newMutations",
+  "appOpen": "appOpen",
+  "appClose": "appClose",
+  "appInstall": "appInstall",
+  "appUninstall": "appUninstall",
+  "frameUpdate": "frameUpdate"
+};
+
+/**
+ * a Client object handles connecting with a Firefox remote debugging
+ * server instance (e.g. a Firefox instance), plus sending and receiving
+ * packets on that conection using the Firefox remote debugging protocol.
+ *
+ * Important methods:
+ * connect - Create the connection to the server.
+ * makeRequest - Make a request to the server with a JSON message,
+ *   and a callback to call with the response.
+ *
+ * Important events:
+ * 'message' - An unsolicited (e.g. not a response to a prior request)
+ *    packet has been received. These packets usually describe events.
+ */
+function Client(options) {
+  this.options = options || {};
+
+  this.incoming = new Buffer("");
+
+  this._pendingRequests = [];
+  this._activeRequests = {};
+}
+
+Client.prototype = extend(events.EventEmitter.prototype, {
+  connect: function(port, host, cb) {
+    this.client = net.createConnection({
+      port: port,
+      host: host
+    });
+
+    this.client.on("connect", cb);
+    this.client.on("data", this.onData.bind(this));
+    this.client.on("error", this.onError.bind(this));
+    this.client.on("end", this.onEnd.bind(this));
+    this.client.on("timeout", this.onTimeout.bind(this));
+  },
+
+  disconnect: function() {
+    if (this.client) {
+      this.client.end();
+    }
+  },
+
+  /**
+   * Set a request to be sent to an actor on the server. If the actor
+   * is already handling a request, queue this request until the actor
+   * has responded to the previous request.
+   *
+   * @param {object} request
+   *        Message to be JSON-ified and sent to server.
+   * @param {function} callback
+   *        Function that's called with the response from the server.
+   */
+  makeRequest: function(request, callback) {
+    this.log("request: " + JSON.stringify(request).green);
+
+    if (!request.to) {
+      var type = request.type || "";
+      throw new Error(type + " request packet has no destination.");
+    }
+    this._pendingRequests.push({ to: request.to,
+                                 message: request,
+                                 callback: callback });
+    this._flushRequests();
+  },
+
+  /**
+   * Activate (send) any pending requests to actors that don't have an
+   * active request.
+   */
+  _flushRequests: function() {
+    this._pendingRequests = this._pendingRequests.filter(function(request) {
+      // only one active request per actor at a time
+      if (this._activeRequests[request.to]) {
+        return true;
+      }
+
+      // no active requests for this actor, so activate this one
+      this.sendMessage(request.message);
+      this.expectReply(request.to, request.callback);
+
+      // remove from pending requests
+      return false;
+    }.bind(this));
+  },
+
+  /**
+   * Send a JSON message over the connection to the server.
+   */
+  sendMessage: function(message) {
+    if (!message.to) {
+      throw new Error("No actor specified in request");
+    }
+    if (!this.client) {
+      throw new Error("Not connected, connect() before sending requests");
+    }
+    var str = JSON.stringify(message);
+
+    // message is preceded by byteLength(message):
+    str = (new Buffer(str).length) + ":" + str;
+
+    this.client.write(str);
+  },
+
+  /**
+   * Arrange to hand the next reply from |actor| to |handler|.
+   */
+  expectReply: function(actor, handler) {
+    if (this._activeRequests[actor]) {
+      throw Error("clashing handlers for next reply from " + uneval(actor));
+    }
+    this._activeRequests[actor] = handler;
+  },
+
+  /**
+   * Handler for a new message coming in. It's either an unsolicited event
+   * from the server, or a response to a previous request from the client.
+   */
+  handleMessage: function(message) {
+    if (!message.from) {
+      if (message.error) {
+        throw new Error(message.message);
+      }
+      throw new Error("Server didn't specify an actor: " + JSON.stringify(message));
+    }
+
+    if (!(message.type in unsolicitedEvents)
+        && this._activeRequests[message.from]) {
+      this.log("response: " + JSON.stringify(message).yellow);
+
+      var callback = this._activeRequests[message.from];
+      delete this._activeRequests[message.from];
+
+      callback(message);
+
+      this._flushRequests();
+    }
+    else if (message.type) {
+      // this is an unsolicited event from the server
+      this.log("unsolicited event: ".grey + JSON.stringify(message).grey);
+
+      this.emit('message', message);
+      return;
+    }
+    else {
+      throw new Error("Unexpected packet from actor " +  message.from
+      +  JSON.stringify(message));
+    }
+  },
+
+  /**
+   * Called when a new data chunk is received on the connection.
+   * Parse data into message(s) and call message handler for any full
+   * messages that are read in.
+   */
+  onData: function(data) {
+    this.incoming = Buffer.concat([this.incoming, data]);
+
+    while(this.readMessage()) {};
+  },
+
+  /**
+   * Parse out and process the next message from the data read from
+   * the connection. Returns true if a full meassage was parsed, false
+   * otherwise.
+   */
+  readMessage: function() {
+    var sep = this.incoming.toString().indexOf(':');
+    if (sep < 0) {
+      return false;
+    }
+
+    // beginning of a message is preceded by byteLength(message) + ":"
+    var count = parseInt(this.incoming.slice(0, sep));
+
+    if (this.incoming.length - (sep + 1) < count) {
+      this.log("no complete response yet".grey);
+      return false;
+    }
+    this.incoming = this.incoming.slice(sep + 1);
+
+    var packet = this.incoming.slice(0, count);
+
+    this.incoming = this.incoming.slice(count);
+
+    var message;
+    try {
+      message = JSON.parse(packet.toString());
+    } catch(e) {
+      throw new Error("Couldn't parse packet from server as JSON " + e
+        + ", message:\n" + packet);
+    }
+    this.handleMessage(message);
+
+    return true;
+  },
+
+  onError: function(error) {
+    var code = error.code ? error.code : error;
+    this.log("connection error: ".red + code.red);
+    this.emit("error", error);
+  },
+
+  onEnd: function() {
+    this.log("connection closed by server".red);
+    this.emit("end");
+  },
+
+  onTimeout: function() {
+    this.log("connection timeout".red);
+    this.emit("timeout");
+  },
+
+  log: function(str) {
+    if (this.options.log) {
+      console.log(str);
+    }
+  }
+})
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/lib/console.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/lib/console.js
new file mode 100644
index 0000000..3f3dcff
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/lib/console.js
@@ -0,0 +1,107 @@
+var select = require("js-select"),
+    extend = require("./extend"),
+    ClientMethods = require("./client-methods"),
+    JSObject = require("./jsobject");
+
+module.exports = Console;
+
+function Console(client, actor) {
+  this.initialize(client, actor);
+
+  this.on("consoleAPICall", this.onConsoleAPI.bind(this));
+  this.on("pageError", this.onPageError.bind(this));
+}
+
+Console.prototype = extend(ClientMethods, {
+  types: ["PageError", "ConsoleAPI"],
+
+  /**
+   * Response object:
+   *   -empty-
+   */
+  startListening: function(cb) {
+    this.request('startListeners', { listeners: this.types }, cb);
+  },
+
+  /**
+   * Response object:
+   *   -empty-
+   */
+  stopListening: function(cb) {
+    this.request('stopListeners', { listeners: this.types }, cb);
+  },
+
+  /**
+   * Event object:
+   *   level - "log", etc.
+   *   filename - file with call
+   *   lineNumber - line number of call
+   *   functionName - function log called from
+   *   timeStamp - ms timestamp of call
+   *   arguments - array of the arguments to log call
+   *   private -
+   */
+  onConsoleAPI: function(event) {
+    var message = this.transformConsoleCall(event.message);
+
+    this.emit("console-api-call", message);
+  },
+
+  /**
+   * Event object:
+   *   errorMessage - string error message
+   *   sourceName - file error
+   *   lineText
+   *   lineNumber - line number of error
+   *   columnNumber - column number of error
+   *   category - usually "content javascript",
+   *   timeStamp - time in ms of error occurance
+   *   warning - whether it's a warning
+   *   error - whether it's an error
+   *   exception - whether it's an exception
+   *   strict -
+   *   private -
+   */
+  onPageError: function(event) {
+    this.emit("page-error", event.pageError);
+  },
+
+  /**
+   * Response object: array of page error or console call objects.
+   */
+  getCachedLogs: function(cb) {
+    var message = {
+      messageTypes: this.types
+    };
+    this.request('getCachedMessages', message, function(resp) {
+      select(resp, ".messages > *").update(this.transformConsoleCall.bind(this));
+      return resp.messages;
+    }.bind(this), cb);
+  },
+
+  /**
+   * Response object:
+   *   -empty-
+   */
+  clearCachedLogs: function(cb) {
+    this.request('clearMessagesCache', cb);
+  },
+
+  /**
+   * Response object:
+   *   input - original input
+   *   result - result of the evaluation, a value or JSObject
+   *   timestamp - timestamp in ms of the evaluation
+   *   exception - any exception as a result of the evaluation
+   */
+  evaluateJS: function(text, cb) {
+    this.request('evaluateJS', { text: text }, function(resp) {
+      return select(resp, ".result, .exception")
+             .update(this.createJSObject.bind(this));
+    }.bind(this), cb)
+  },
+
+  transformConsoleCall: function(message) {
+    return select(message, ".arguments > *").update(this.createJSObject.bind(this));
+  }
+})
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/lib/device.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/lib/device.js
new file mode 100644
index 0000000..6c5da84
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/lib/device.js
@@ -0,0 +1,29 @@
+var extend = require("./extend"),
+    ClientMethods = require("./client-methods");
+
+module.exports = Device;
+
+function Device(client, tab) {
+  this.initialize(client, tab.deviceActor);
+}
+
+Device.prototype = extend(ClientMethods, {
+  getDescription: function(cb) {
+    this.request("getDescription", function(err, resp) {
+      if (err) {
+        return cb(err);
+      }
+
+      cb(null, resp.value);
+    });
+  },
+  getRawPermissionsTable: function(cb) {
+    this.request("getRawPermissionsTable", function(err, resp) {
+      if (err) {
+        return cb(err);
+      }
+
+      cb(null, resp.value.rawPermissionsTable);
+    });
+  }
+})
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/lib/dom.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/lib/dom.js
new file mode 100644
index 0000000..a00018c
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/lib/dom.js
@@ -0,0 +1,68 @@
+var extend = require("./extend"),
+    ClientMethods = require("./client-methods"),
+    Node = require("./domnode");
+
+module.exports = DOM;
+
+function DOM(client, actor) {
+  this.initialize(client, actor);
+  this.walker = null;
+}
+
+DOM.prototype = extend(ClientMethods, {
+  document: function(cb) {
+    this.walkerRequest("document", function(err, resp) {
+      if (err) return cb(err);
+
+      var node = new Node(this.client, this.walker, resp.node);
+      cb(null, node);
+    }.bind(this))
+  },
+
+  documentElement: function(cb) {
+    this.walkerRequest("documentElement", function(err, resp) {
+      var node = new Node(this.client, this.walker, resp.node);
+      cb(err, node);
+    }.bind(this))
+  },
+
+  querySelector: function(selector, cb) {
+    this.document(function(err, node) {
+      if (err) return cb(err);
+
+      node.querySelector(selector, cb);
+    })
+  },
+
+  querySelectorAll: function(selector, cb) {
+    this.document(function(err, node) {
+      if (err) return cb(err);
+
+      node.querySelectorAll(selector, cb);
+    })
+  },
+
+  walkerRequest: function(type, message, cb) {
+    this.getWalker(function(err, walker) {
+      walker.request(type, message, cb);
+    });
+  },
+
+  getWalker: function(cb) {
+    if (this.walker) {
+      return cb(null, this.walker);
+    }
+    this.request('getWalker', function(err, resp) {
+      this.walker = new Walker(this.client, resp.walker);
+      cb(err, this.walker);
+    }.bind(this))
+  }
+})
+
+function Walker(client, walker) {
+  this.initialize(client, walker.actor);
+
+  this.root = new Node(client, this, walker.root);
+}
+
+Walker.prototype = extend(ClientMethods, {});
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/lib/domnode.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/lib/domnode.js
new file mode 100644
index 0000000..e0a55b8
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/lib/domnode.js
@@ -0,0 +1,169 @@
+var extend = require("./extend"),
+    ClientMethods = require("./client-methods");
+
+module.exports = Node;
+
+function Node(client, walker, node) {
+  this.initialize(client, node.actor);
+  this.walker = walker;
+
+  this.getNode = this.getNode.bind(this);
+  this.getNodeArray = this.getNodeArray.bind(this);
+  this.getNodeList = this.getNodeList.bind(this);
+
+  walker.on('newMutations', function(event) {
+    //console.log("on new mutations! ", JSON.stringify(event));
+  });
+
+  ['nodeType', 'nodeName', 'namespaceURI', 'attrs']
+  .forEach(function(attr) {
+    this[attr] = node[attr];
+  }.bind(this));
+}
+
+Node.prototype = extend(ClientMethods, {
+  getAttribute: function(name) {
+    for (var i in this.attrs) {
+      var attr = this.attrs[i];
+      if (attr.name == name) {
+        return attr.value;
+      }
+    }
+  },
+
+  setAttribute: function(name, value, cb) {
+    var mods = [{
+      attributeName: name,
+      newValue: value
+    }];
+    this.request('modifyAttributes', { modifications: mods }, cb);
+  },
+
+  parentNode: function(cb) {
+    this.parents(function(err, nodes) {
+      if (err) {
+        return cb(err);
+      }
+      var node = null;
+      if (nodes.length) {
+        node = nodes[0];
+      }
+      cb(null, node);
+    })
+  },
+
+  parents: function(cb) {
+    this.nodeRequest('parents', this.getNodeArray, cb);
+  },
+
+  children: function(cb) {
+    this.nodeRequest('children', this.getNodeArray, cb);
+  },
+
+  siblings: function(cb) {
+    this.nodeRequest('siblings', this.getNodeArray, cb);
+  },
+
+  nextSibling: function(cb) {
+    this.nodeRequest('nextSibling', this.getNode, cb);
+  },
+
+  previousSibling: function(cb) {
+    this.nodeRequest('previousSibling', this.getNode, cb);
+  },
+
+  querySelector: function(selector, cb) {
+    this.nodeRequest('querySelector', { selector: selector },
+                     this.getNode, cb);
+  },
+
+  querySelectorAll: function(selector, cb) {
+    this.nodeRequest('querySelectorAll', { selector: selector },
+                     this.getNodeList, cb);
+  },
+
+  innerHTML: function(cb) {
+    this.nodeRequest('innerHTML', function(resp) {
+      return resp.value;
+    }, cb)
+  },
+
+  outerHTML: function(cb) {
+    this.nodeRequest('outerHTML', function(resp) {
+      return resp.value;
+    }, cb)
+  },
+
+  remove: function(cb) {
+    this.nodeRequest('removeNode', function(resp) {
+      return new Node(this.client, this.walker, resp.nextSibling);
+    }.bind(this), cb);
+  },
+
+  highlight: function(cb) {
+    this.nodeRequest('highlight', cb);
+  },
+
+  release: function(cb) {
+    this.nodeRequest('releaseNode', cb);
+  },
+
+  getNode: function(resp) {
+    if (resp.node) {
+      return new Node(this.client, this.walker, resp.node);
+    }
+    return null;
+  },
+
+  getNodeArray: function(resp) {
+    return resp.nodes.map(function(form) {
+      return new Node(this.client, this.walker, form);
+    }.bind(this));
+  },
+
+  getNodeList: function(resp) {
+    return new NodeList(this.client, this.walker, resp.list);
+  },
+
+  nodeRequest: function(type, message, transform, cb) {
+    if (!cb) {
+      cb = transform;
+      transform = message;
+      message = {};
+    }
+    message.node = this.actor;
+
+    this.walker.request(type, message, transform, cb);
+  }
+});
+
+function NodeList(client, walker, list) {
+  this.client = client;
+  this.walker = walker;
+  this.actor = list.actor;
+
+  this.length = list.length;
+}
+
+NodeList.prototype = extend(ClientMethods, {
+  items: function(start, end, cb) {
+    if (typeof start == "function") {
+      cb = start;
+      start = 0;
+      end = this.length;
+    }
+    else if (typeof end == "function") {
+      cb = end;
+      end = this.length;
+    }
+    this.request('items', { start: start, end: end },
+      this.getNodeArray.bind(this), cb);
+  },
+
+  // TODO: add this function to ClientMethods
+  getNodeArray: function(resp) {
+    return resp.nodes.map(function(form) {
+      return new Node(this.client, this.walker, form);
+    }.bind(this));
+  }
+});
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/lib/extend.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/lib/extend.js
new file mode 100644
index 0000000..8624103
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/lib/extend.js
@@ -0,0 +1,12 @@
+module.exports = function extend(prototype, properties) {
+  return Object.create(prototype, getOwnPropertyDescriptors(properties));
+}
+
+function getOwnPropertyDescriptors(object) {
+  var names = Object.getOwnPropertyNames(object);
+
+  return names.reduce(function(descriptor, name) {
+    descriptor[name] = Object.getOwnPropertyDescriptor(object, name);
+    return descriptor;
+  }, {});
+}
\ No newline at end of file
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/lib/jsobject.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/lib/jsobject.js
new file mode 100644
index 0000000..907999f
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/lib/jsobject.js
@@ -0,0 +1,91 @@
+var select = require("js-select"),
+    extend = require("./extend"),
+    ClientMethods = require("./client-methods");
+
+module.exports = JSObject;
+
+function JSObject(client, obj) {
+  this.initialize(client, obj.actor);
+  this.obj = obj;
+}
+
+JSObject.prototype = extend(ClientMethods, {
+  type: "object",
+
+  get class() {
+    return this.obj.class;
+  },
+
+  get name() {
+    return this.obj.name;
+  },
+
+  get displayName() {
+    return this.obj.displayName;
+  },
+
+  ownPropertyNames: function(cb) {
+    this.request('ownPropertyNames', function(resp) {
+      return resp.ownPropertyNames;
+    }, cb);
+  },
+
+  ownPropertyDescriptor: function(name, cb) {
+    this.request('property', { name: name }, function(resp) {
+      return this.transformDescriptor(resp.descriptor);
+    }.bind(this), cb);
+  },
+
+  ownProperties: function(cb) {
+    this.request('prototypeAndProperties', function(resp) {
+      return this.transformProperties(resp.ownProperties);
+    }.bind(this), cb);
+  },
+
+  prototype: function(cb) {
+    this.request('prototype', function(resp) {
+      return this.createJSObject(resp.prototype);
+    }.bind(this), cb);
+  },
+
+  ownPropertiesAndPrototype: function(cb) {
+    this.request('prototypeAndProperties', function(resp) {
+      resp.ownProperties = this.transformProperties(resp.ownProperties);
+      resp.safeGetterValues = this.transformGetters(resp.safeGetterValues);
+      resp.prototype = this.createJSObject(resp.prototype);
+
+      return resp;
+    }.bind(this), cb);
+  },
+
+  /* helpers */
+  transformProperties: function(props) {
+    var transformed = {};
+    for (var prop in props) {
+      transformed[prop] = this.transformDescriptor(props[prop]);
+    }
+    return transformed;
+  },
+
+  transformGetters: function(getters) {
+    var transformed = {};
+    for (var prop in getters) {
+      transformed[prop] = this.transformGetter(getters[prop]);
+    }
+    return transformed;
+  },
+
+  transformDescriptor: function(descriptor) {
+    descriptor.value = this.createJSObject(descriptor.value);
+    return descriptor;
+  },
+
+  transformGetter: function(getter) {
+    return {
+      value: this.createJSObject(getter.getterValue),
+      prototypeLevel: getter.getterPrototypeLevel,
+      enumerable: getter.enumerable,
+      writable: getter.writable
+    }
+  }
+})
\ No newline at end of file
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/lib/memory.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/lib/memory.js
new file mode 100644
index 0000000..8468539
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/lib/memory.js
@@ -0,0 +1,16 @@
+var extend = require("./extend"),
+    ClientMethods = require("./client-methods");
+
+module.exports = Memory;
+
+function Memory(client, actor) {
+  this.initialize(client, actor);
+}
+
+Memory.prototype = extend(ClientMethods, {
+  measure: function(cb) {
+    this.request('measure', function (err, resp) {
+      cb(err, resp);
+    });
+  }
+})
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/lib/network.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/lib/network.js
new file mode 100644
index 0000000..5c1afdb
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/lib/network.js
@@ -0,0 +1,105 @@
+var extend = require("./extend");
+var ClientMethods = require("./client-methods");
+
+module.exports = Network;
+
+function Network(client, actor) {
+  this.initialize(client, actor);
+
+  this.on("networkEvent", this.onNetworkEvent.bind(this));
+}
+
+Network.prototype = extend(ClientMethods, {
+  types: ["NetworkActivity"],
+
+  startLogging: function(cb) {
+    this.request('startListeners', { listeners: this.types }, cb);
+  },
+
+  stopLogging: function(cb) {
+    this.request('stopListeners', { listeners: this.types }, cb);
+  },
+
+  onNetworkEvent: function(event) {
+    var networkEvent = new NetworkEvent(this.client, event.eventActor);
+
+    this.emit("network-event", networkEvent);
+  },
+
+  sendHTTPRequest: function(request, cb) {
+    this.request('sendHTTPRequest', { request: request }, function(resp) {
+      return new NetworkEvent(this.client, resp.eventActor);
+    }.bind(this), cb);
+  }
+})
+
+function NetworkEvent(client, event) {
+  this.initialize(client, event.actor);
+  this.event = event;
+
+  this.on("networkEventUpdate", this.onUpdate.bind(this));
+}
+
+NetworkEvent.prototype = extend(ClientMethods, {
+  get url() {
+   return this.event.url;
+  },
+
+  get method() {
+    return this.event.method;
+  },
+
+  get isXHR() {
+    return this.event.isXHR;
+  },
+
+  getRequestHeaders: function(cb) {
+    this.request('getRequestHeaders', cb);
+  },
+
+  getRequestCookies: function(cb) {
+    this.request('getRequestCookies', this.pluck('cookies'), cb);
+  },
+
+  getRequestPostData: function(cb) {
+    this.request('getRequestPostData', cb);
+  },
+
+  getResponseHeaders: function(cb) {
+    this.request('getResponseHeaders', cb);
+  },
+
+  getResponseCookies: function(cb) {
+    this.request('getResponseCookies', this.pluck('cookies'), cb);
+  },
+
+  getResponseContent: function(cb) {
+    this.request('getResponseContent', cb);
+  },
+
+  getEventTimings: function(cb) {
+    this.request('getEventTimings', cb);
+  },
+
+  onUpdate: function(event) {
+    var types = {
+      "requestHeaders": "request-headers",
+      "requestCookies": "request-cookies",
+      "requestPostData": "request-postdata",
+      "responseStart": "response-start",
+      "responseHeaders": "response-headers",
+      "responseCookies": "response-cookies",
+      "responseContent": "response-content",
+      "eventTimings": "event-timings"
+    }
+
+    var type = types[event.updateType];
+    delete event.updateType;
+
+    this.emit(type, event);
+  }
+})
+
+
+
+
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/lib/simulator.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/lib/simulator.js
new file mode 100644
index 0000000..4df6a2b
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/lib/simulator.js
@@ -0,0 +1,22 @@
+var extend = require("./extend"),
+    ClientMethods = require("./client-methods"),
+    Tab = require("./tab");
+
+module.exports = SimulatorApps;
+
+function SimulatorApps(client, actor) {
+  this.initialize(client, actor);
+}
+
+SimulatorApps.prototype = extend(ClientMethods, {
+  listApps: function(cb) {
+    this.request('listApps', function(resp) {
+      var apps = [];
+      for (var url in resp.apps) {
+        var app = resp.apps[url];
+        apps.push(new Tab(this.client, app));
+      }
+      return apps;
+    }.bind(this), cb);
+  }
+})
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/lib/stylesheets.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/lib/stylesheets.js
new file mode 100644
index 0000000..0677c02
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/lib/stylesheets.js
@@ -0,0 +1,96 @@
+var extend = require("./extend");
+var ClientMethods = require("./client-methods");
+
+module.exports = StyleSheets;
+
+function StyleSheets(client, actor) {
+  this.initialize(client, actor);
+}
+
+StyleSheets.prototype = extend(ClientMethods, {
+  getStyleSheets: function(cb) {
+    this.request('getStyleSheets', function(resp) {
+      return resp.styleSheets.map(function(sheet) {
+        return new StyleSheet(this.client, sheet);
+      }.bind(this));
+    }.bind(this), cb);
+  },
+
+  addStyleSheet: function(text, cb) {
+    this.request('addStyleSheet', { text: text }, function(resp) {
+      return new StyleSheet(this.client, resp.styleSheet);
+    }.bind(this), cb);
+  }
+})
+
+function StyleSheet(client, sheet) {
+  this.initialize(client, sheet.actor);
+  this.sheet = sheet;
+
+  this.on("propertyChange", this.onPropertyChange.bind(this));
+}
+
+StyleSheet.prototype = extend(ClientMethods, {
+  get href() {
+    return this.sheet.href;
+  },
+
+  get disabled() {
+    return this.sheet.disabled;
+  },
+
+  get ruleCount() {
+    return this.sheet.ruleCount;
+  },
+
+  onPropertyChange: function(event) {
+    this.sheet[event.property] = event.value;
+    this.emit(event.property + "-changed", event.value);
+  },
+
+  toggleDisabled: function(cb) {
+    this.request('toggleDisabled', function(err, resp) {
+      if (err) return cb(err);
+
+      this.sheet.disabled = resp.disabled;
+      cb(null, resp.disabled);
+    }.bind(this));
+  },
+
+  getOriginalSources: function(cb) {
+    this.request('getOriginalSources', function(resp) {
+      if (resp.originalSources === null) {
+        return [];
+      }
+      return resp.originalSources.map(function(form) {
+        return new OriginalSource(this.client, form);
+      }.bind(this));
+    }.bind(this), cb);
+  },
+
+  update: function(text, cb) {
+    this.request('update', { text: text, transition: true }, cb);
+  },
+
+  getText: function(cb) {
+    this.request('getText', this.pluck('text'), cb);
+  }
+});
+
+
+function OriginalSource(client, source) {
+  console.log("source", source);
+  this.initialize(client, source.actor);
+
+  this.source = source;
+}
+
+OriginalSource.prototype = extend(ClientMethods, {
+  get url()  {
+    return this.source.url
+  },
+
+  getText: function(cb) {
+    this.request('getText', this.pluck('text'), cb);
+  }
+});
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/lib/tab.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/lib/tab.js
new file mode 100644
index 0000000..5e19e1c
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/lib/tab.js
@@ -0,0 +1,87 @@
+var extend = require("./extend"),
+    ClientMethods = require("./client-methods"),
+    Console = require("./console"),
+    Memory = require("./memory"),
+    DOM = require("./dom"),
+    Network = require("./network"),
+    StyleSheets = require("./stylesheets");
+
+module.exports = Tab;
+
+function Tab(client, tab) {
+  this.initialize(client, tab.actor);
+
+  this.tab = tab;
+  this.updateInfo(tab);
+
+  this.on("tabNavigated", this.onTabNavigated.bind(this));
+}
+
+Tab.prototype = extend(ClientMethods, {
+  updateInfo: function(form) {
+    this.url = form.url;
+    this.title = form.title;
+  },
+
+  get StyleSheets() {
+    if (!this._StyleSheets) {
+      this._StyleSheets = new StyleSheets(this.client, this.tab.styleSheetsActor);
+    }
+    return this._StyleSheets;
+  },
+
+  get DOM() {
+    if (!this._DOM) {
+      this._DOM = new DOM(this.client, this.tab.inspectorActor);
+    }
+    return this._DOM;
+  },
+
+  get Network() {
+    if (!this._Network) {
+      this._Network = new Network(this.client, this.tab.consoleActor);
+    }
+    return this._Network;
+  },
+
+  get Console() {
+    if (!this._Console) {
+      this._Console = new Console(this.client, this.tab.consoleActor);
+    }
+    return this._Console;
+  },
+
+  get Memory() {
+    if (!this._Memory) {
+      this._Memory = new Memory(this.client, this.tab.memoryActor);
+    }
+    return this._Memory;
+  },
+
+  onTabNavigated: function(event) {
+    if (event.state == "start") {
+      this.emit("before-navigate", { url: event.url });
+    }
+    else if (event.state == "stop") {
+      this.updateInfo(event);
+
+      this.emit("navigate", { url: event.url, title: event.title });
+    }
+  },
+
+  attach: function(cb) {
+    this.request("attach", cb);
+  },
+
+  detach: function(cb) {
+    this.request("detach", cb);
+  },
+
+  reload: function(cb) {
+    this.request("reload", cb);
+  },
+
+  navigateTo: function(url, cb) {
+    this.request("navigateTo", { url: url }, cb);
+  }
+})
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/lib/webapps.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/lib/webapps.js
new file mode 100644
index 0000000..5ede808
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/lib/webapps.js
@@ -0,0 +1,170 @@
+var extend = require("./extend"),
+    ClientMethods = require("./client-methods"),
+    Tab = require("./tab"),
+    fs = require("fs"),
+    spawn = require("child_process").spawn;
+
+module.exports = Webapps;
+
+var CHUNK_SIZE = 20480;
+
+// Also dispatch appOpen/appClose, appInstall/appUninstall events
+function Webapps(client, tab) {
+  this.initialize(client, tab.webappsActor);
+}
+
+Webapps.prototype = extend(ClientMethods, {
+  watchApps: function(cb) {
+    this.request("watchApps", cb);
+  },
+  unwatchApps: function(cb) {
+    this.request("unwatchApps", cb);
+  },
+  launch: function(manifestURL, cb) {
+    this.request("launch", {manifestURL: manifestURL}, cb);
+  },
+  close: function(manifestURL, cb) {
+    this.request("close", {manifestURL: manifestURL}, cb);
+  },
+  getInstalledApps: function(cb) {
+    this.request("getAll", function (err, resp) {
+      if (err) {
+        cb(err);
+        return;
+      }
+      cb(null, resp.apps);
+    });
+  },
+  listRunningApps: function(cb) {
+    this.request("listRunningApps", function (err, resp) {
+      if (err) {
+        cb(err);
+        return;
+      }
+      cb(null, resp.apps);
+    });
+  },
+  getApp: function(manifestURL, cb) {
+    this.request("getAppActor", {manifestURL: manifestURL}, (function (err, resp) {
+      if (err) {
+        cb(err);
+        return;
+      }
+      var actor = new Tab(this.client, resp.actor);
+      cb(null, actor);
+    }).bind(this));
+  },
+  installHosted: function(options, cb) {
+    this.request(
+      "install",
+      { appId: options.appId,
+        metadata: options.metadata,
+        manifest: options.manifest },
+      function (err, resp) {
+        if (err || resp.error) {
+          cb(err || resp.error);
+          return;
+        }
+        cb(null, resp.appId);
+      });
+  },
+  _upload: function (path, cb) {
+    // First create an upload actor
+    this.request("uploadPackage", function (err, resp) {
+      var actor = resp.actor;
+      fs.readFile(path, function(err, data) {
+        chunk(actor, data);
+      });
+    });
+    // Send push the file chunk by chunk
+    var self = this;
+    var step = 0;
+    function chunk(actor, data) {
+      var i = step++ * CHUNK_SIZE;
+      var m = Math.min(i + CHUNK_SIZE, data.length);
+      var c = "";
+      for(; i < m; i++)
+        c += String.fromCharCode(data[i]);
+      var message = {
+        to: actor,
+        type: "chunk",
+        chunk: c
+      };
+      self.client.makeRequest(message, function(resp) {
+        if (resp.error) {
+          cb(resp);
+          return;
+        }
+        if (i < data.length) {
+          setTimeout(chunk, 0, actor, data);
+        } else {
+          done(actor);
+        }
+      });
+    }
+    // Finally close the upload
+    function done(actor) {
+      var message = {
+        to: actor,
+        type: "done"
+      };
+      self.client.makeRequest(message, function(resp) {
+        if (resp.error) {
+          cb(resp);
+        } else {
+          cb(null, actor, cleanup.bind(null, actor));
+        }
+      });
+    }
+
+    // Remove the temporary uploaded file from the server:
+    function cleanup(actor) {
+      var message = {
+        to: actor,
+        type: "remove"
+      };
+      self.client.makeRequest(message, function () {});
+    }
+  },
+  installPackaged: function(path, appId, cb) {
+    this._upload(path, (function (err, actor, cleanup) {
+      this.request("install", {appId: appId, upload: actor},
+        function (err, resp) {
+          if (err) {
+            cb(err);
+            return;
+          }
+          cb(null, resp.appId);
+          cleanup();
+        });
+    }).bind(this));
+  },
+  installPackagedWithADB: function(path, appId, cb) {
+    var self = this;
+    // First ensure the temporary folder exists
+    function createTemporaryFolder() {
+      var c = spawn("adb", ["shell", "mkdir -p /data/local/tmp/b2g/" + appId], {stdio:"inherit"});
+      c.on("close", uploadPackage);
+    }
+    // then upload the package to the temporary directory
+    function uploadPackage() {
+      var child = spawn("adb", ["push", path, "/data/local/tmp/b2g/" + appId + "/application.zip"], {stdio:"inherit"});
+      child.on("close", installApp);
+    }
+    // finally order the webapps actor to install the app
+    function installApp() {
+      self.request("install", {appId: appId},
+        function (err, resp) {
+          if (err) {
+            cb(err);
+            return;
+          }
+          cb(null, resp.appId);
+        });
+    }
+    createTemporaryFolder();
+  },
+  uninstall: function(manifestURL, cb) {
+    this.request("uninstall", {manifestURL: manifestURL}, cb);
+  }
+})
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/colors/MIT-LICENSE.txt b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/colors/MIT-LICENSE.txt
new file mode 100644
index 0000000..7df0d5e
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/colors/MIT-LICENSE.txt
@@ -0,0 +1,19 @@
+Copyright (c) 2010 Alexis Sellier (cloudhead) , Marak Squires
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
\ No newline at end of file
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/colors/ReadMe.md b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/colors/ReadMe.md
new file mode 100644
index 0000000..74acead
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/colors/ReadMe.md
@@ -0,0 +1,30 @@
+<h1>colors.js - get color and style in your node.js console like what</h1>
+
+<img src="http://i.imgur.com/goJdO.png" border = "0"/>
+
+       var sys = require('sys');
+       var colors = require('./colors');
+
+       sys.puts('hello'.green); // outputs green text
+       sys.puts('i like cake and pies'.underline.red) // outputs red underlined text
+       sys.puts('inverse the color'.inverse); // inverses the color
+       sys.puts('OMG Rainbows!'.rainbow); // rainbow (ignores spaces)
+       
+<h2>colors and styles!</h2>
+- bold
+- italic
+- underline
+- inverse
+- yellow
+- cyan
+- white
+- magenta
+- green
+- red
+- grey
+- blue
+
+
+### Authors 
+
+#### Alexis Sellier (cloudhead) , Marak Squires , Justin Campbell, Dustin Diaz (@ded)
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/colors/colors.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/colors/colors.js
new file mode 100644
index 0000000..8c1d706
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/colors/colors.js
@@ -0,0 +1,230 @@
+/*
+colors.js
+
+Copyright (c) 2010 Alexis Sellier (cloudhead) , Marak Squires
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+*/
+
+exports.mode = "console";
+
+// prototypes the string object to have additional method calls that add terminal colors
+
+var addProperty = function (color, func) {
+  exports[color] = function(str) {
+    return func.apply(str);
+  };
+  String.prototype.__defineGetter__(color, func);
+}
+
+var isHeadless = (typeof module !== 'undefined');
+['bold', 'underline', 'italic', 'inverse', 'grey', 'black', 'yellow', 'red', 'green', 'blue', 'white', 'cyan', 'magenta'].forEach(function (style) {
+
+  // __defineGetter__ at the least works in more browsers
+  // http://robertnyman.com/javascript/javascript-getters-setters.html
+  // Object.defineProperty only works in Chrome
+  addProperty(style, function () {
+    return isHeadless ?
+             stylize(this, style) : // for those running in node (headless environments)
+             this.replace(/( )/, '$1'); // and for those running in browsers:
+             // re: ^ you'd think 'return this' works (but doesn't) so replace coerces the string to be a real string
+  });
+});
+
+// prototypes string with method "rainbow"
+// rainbow will apply a the color spectrum to a string, changing colors every letter
+addProperty('rainbow', function () {
+  if (!isHeadless) {
+    return this.replace(/( )/, '$1');
+  }
+  var rainbowcolors = ['red','yellow','green','blue','magenta']; //RoY G BiV
+  var exploded = this.split("");
+  var i=0;
+  exploded = exploded.map(function(letter) {
+    if (letter==" ") {
+      return letter;
+    }
+    else {
+      return stylize(letter,rainbowcolors[i++ % rainbowcolors.length]);
+    }
+  });
+  return exploded.join("");
+});
+
+function stylize(str, style) {
+  if (exports.mode == 'console') {
+    var styles = {
+      //styles
+      'bold'      : ['\033[1m',  '\033[22m'],
+      'italic'    : ['\033[3m',  '\033[23m'],
+      'underline' : ['\033[4m',  '\033[24m'],
+      'inverse'   : ['\033[7m',  '\033[27m'],
+      //grayscale
+      'white'     : ['\033[37m', '\033[39m'],
+      'grey'      : ['\033[90m', '\033[39m'],
+      'black'     : ['\033[30m', '\033[39m'],
+      //colors
+      'blue'      : ['\033[34m', '\033[39m'],
+      'cyan'      : ['\033[36m', '\033[39m'],
+      'green'     : ['\033[32m', '\033[39m'],
+      'magenta'   : ['\033[35m', '\033[39m'],
+      'red'       : ['\033[31m', '\033[39m'],
+      'yellow'    : ['\033[33m', '\033[39m']
+    };
+  } else if (exports.mode == 'browser') {
+    var styles = {
+      //styles
+      'bold'      : ['<b>',  '</b>'],
+      'italic'    : ['<i>',  '</i>'],
+      'underline' : ['<u>',  '</u>'],
+      'inverse'   : ['<span style="background-color:black;color:white;">',  '</span>'],
+      //grayscale
+      'white'     : ['<span style="color:white;">',   '</span>'],
+      'grey'      : ['<span style="color:grey;">',    '</span>'],
+      'black'     : ['<span style="color:black;">',   '</span>'],
+      //colors
+      'blue'      : ['<span style="color:blue;">',    '</span>'],
+      'cyan'      : ['<span style="color:cyan;">',    '</span>'],
+      'green'     : ['<span style="color:green;">',   '</span>'],
+      'magenta'   : ['<span style="color:magenta;">', '</span>'],
+      'red'       : ['<span style="color:red;">',     '</span>'],
+      'yellow'    : ['<span style="color:yellow;">',  '</span>']
+    };
+  } else if (exports.mode == 'none') {
+      return str;
+  } else {
+    console.log('unsupported mode, try "browser", "console" or "none"');
+  }
+
+  return styles[style][0] + str + styles[style][1];
+};
+
+// don't summon zalgo
+addProperty('zalgo', function () {
+  return zalgo(this);
+});
+
+// please no
+function zalgo(text, options) {
+  var soul = {
+    "up" : [
+      '̍','̎','̄','̅',
+      '̿','̑','̆','̐',
+      '͒','͗','͑','̇',
+      '̈','̊','͂','̓',
+      '̈','͊','͋','͌',
+      '̃','̂','̌','͐',
+      '̀','́','̋','̏',
+      '̒','̓','̔','̽',
+      '̉','ͣ','ͤ','ͥ',
+      'ͦ','ͧ','ͨ','ͩ',
+      'ͪ','ͫ','ͬ','ͭ',
+      'ͮ','ͯ','̾','͛',
+      '͆','̚'
+      ],
+    "down" : [
+      '̖','̗','̘','̙',
+      '̜','̝','̞','̟',
+      '̠','̤','̥','̦',
+      '̩','̪','̫','̬',
+      '̭','̮','̯','̰',
+      '̱','̲','̳','̹',
+      '̺','̻','̼','ͅ',
+      '͇','͈','͉','͍',
+      '͎','͓','͔','͕',
+      '͖','͙','͚','̣'
+      ],
+    "mid" : [
+      '̕','̛','̀','́',
+      '͘','̡','̢','̧',
+      '̨','̴','̵','̶',
+      '͜','͝','͞',
+      '͟','͠','͢','̸',
+      '̷','͡',' ҉'
+      ]
+  },
+  all = [].concat(soul.up, soul.down, soul.mid),
+  zalgo = {};
+
+  function randomNumber(range) {
+    r = Math.floor(Math.random()*range);
+    return r;
+  };
+
+  function is_char(character) {
+    var bool = false;
+    all.filter(function(i){
+     bool = (i == character);
+    });
+    return bool;
+  }
+
+  function heComes(text, options){
+      result = '';
+      options = options || {};
+      options["up"] = options["up"] || true;
+      options["mid"] = options["mid"] || true;
+      options["down"] = options["down"] || true;
+      options["size"] = options["size"] || "maxi";
+      var counts;
+      text = text.split('');
+       for(var l in text){
+         if(is_char(l)) { continue; }
+         result = result + text[l];
+
+        counts = {"up" : 0, "down" : 0, "mid" : 0};
+
+        switch(options.size) {
+          case 'mini':
+            counts.up = randomNumber(8);
+            counts.min= randomNumber(2);
+            counts.down = randomNumber(8);
+          break;
+          case 'maxi':
+            counts.up = randomNumber(16) + 3;
+            counts.min = randomNumber(4) + 1;
+            counts.down = randomNumber(64) + 3;
+          break;
+          default:
+            counts.up = randomNumber(8) + 1;
+            counts.mid = randomNumber(6) / 2;
+            counts.down= randomNumber(8) + 1;
+          break;
+        }
+
+        var arr = ["up", "mid", "down"];
+        for(var d in arr){
+          var index = arr[d];
+          for (var i = 0 ; i <= counts[index]; i++)
+          {
+            if(options[index]) {
+                result = result + soul[index][randomNumber(soul[index].length)];
+              }
+            }
+          }
+        }
+      return result;
+  };
+  return heComes(text);
+}
+
+addProperty('stripColors', function() {
+  return ("" + this).replace(/\u001b\[\d+m/g,'');
+});
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/colors/example.html b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/colors/example.html
new file mode 100644
index 0000000..c9cd68c
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/colors/example.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html lang="en-us">
+  <head>
+    <meta http-equiv="Content-type" content="text/html; charset=utf-8">
+    <title>Colors Example</title>
+    <script src="colors.js"></script>
+    <script type="text/javascript">
+      console.log('Rainbows are fun!'.rainbow);
+      console.log('So '.italic + 'are'.underline + ' styles! '.bold + 'inverse'.inverse);
+      console.log('Chains are also cool.'.bold.italic.underline.red);
+    </script>
+  </head>
+  <body>
+    <script>
+      document.write('Rainbows are fun!'.rainbow + '<br>');
+      document.write('So '.italic + 'are'.underline + ' styles! '.bold + 'inverse'.inverse + '<br>');
+      document.write('Chains are also cool.'.bold.italic.underline.red);
+    </script>
+  </body>
+</html>
\ No newline at end of file
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/colors/example.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/colors/example.js
new file mode 100644
index 0000000..12d8b1a
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/colors/example.js
@@ -0,0 +1,20 @@
+var util = require('util');
+var colors = require('./colors');
+
+//colors.mode = "browser";
+
+var test = colors.red("hopefully colorless output");
+util.puts('Rainbows are fun!'.rainbow);
+util.puts('So '.italic + 'are'.underline + ' styles! '.bold + 'inverse'.inverse); // styles not widely supported
+util.puts('Chains are also cool.'.bold.italic.underline.red); // styles not widely supported
+//util.puts('zalgo time!'.zalgo);
+util.puts(test.stripColors);
+util.puts("a".grey + " b".black);
+
+util.puts(colors.rainbow('Rainbows are fun!'));
+util.puts(colors.italic('So ') + colors.underline('are') + colors.bold(' styles! ') + colors.inverse('inverse')); // styles not widely supported
+util.puts(colors.bold(colors.italic(colors.underline(colors.red('Chains are also cool.'))))); // styles not widely supported
+//util.puts(colors.zalgo('zalgo time!'));
+util.puts(colors.stripColors(test));
+util.puts(colors.grey("a") + colors.black(" b"));
+
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/colors/package.json b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/colors/package.json
new file mode 100644
index 0000000..b0b4f79
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/colors/package.json
@@ -0,0 +1,49 @@
+{
+  "name": "colors",
+  "description": "get colors in your node.js console like what",
+  "version": "0.5.1",
+  "author": {
+    "name": "Marak Squires"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/Marak/colors.js.git"
+  },
+  "engines": {
+    "node": ">=0.1.90"
+  },
+  "main": "colors",
+  "_npmJsonOpts": {
+    "file": "/Users/maraksquires/.npm/colors/0.5.1/package/package.json",
+    "wscript": false,
+    "contributors": false,
+    "serverjs": false
+  },
+  "_id": "colors@0.5.1",
+  "dependencies": {},
+  "devDependencies": {},
+  "_engineSupported": true,
+  "_npmVersion": "1.0.30",
+  "_nodeVersion": "v0.4.10",
+  "_defaultsLoaded": true,
+  "dist": {
+    "shasum": "7d0023eaeb154e8ee9fce75dcb923d0ed1667774",
+    "tarball": "http://registry.npmjs.org/colors/-/colors-0.5.1.tgz"
+  },
+  "maintainers": [
+    {
+      "name": "marak",
+      "email": "marak.squires@gmail.com"
+    }
+  ],
+  "directories": {},
+  "_shasum": "7d0023eaeb154e8ee9fce75dcb923d0ed1667774",
+  "_from": "colors@0.5.x",
+  "_resolved": "https://registry.npmjs.org/colors/-/colors-0.5.1.tgz",
+  "bugs": {
+    "url": "https://github.com/Marak/colors.js/issues"
+  },
+  "readme": "ERROR: No README data found!",
+  "homepage": "https://github.com/Marak/colors.js",
+  "scripts": {}
+}
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/LICENSE b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/LICENSE
new file mode 100644
index 0000000..2983774
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/LICENSE
@@ -0,0 +1,21 @@
+Copyright (c) 2011 Heather Arthur
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/README.md b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/README.md
new file mode 100644
index 0000000..6c218a4
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/README.md
@@ -0,0 +1,118 @@
+# js-select
+
+js-select uses [js-traverse](https://github.com/substack/js-traverse) to traverse and modify JavaScript object nodes that match [JSONSelect](http://jsonselect.org/) selectors.
+
+```javascript
+var people = {
+   george: {
+      age : 35,
+      movie: "Repo Man"
+   },
+   mary: {
+      age: 15,
+      movie: "Twilight"
+   }
+};
+```
+
+### .forEach(fn)
+
+Iterates over all matching nodes in the object. The callback gets a special `this` context. See [js-traverse](https://github.com/substack/js-traverse) for all the things you can do to modify and inspect the node with this context. In addition, js-select adds a `this.matches()` which will test if the node matches a selector:
+
+```javascript
+select(people).forEach(function(node) {
+   if (this.matches(".mary > .movie")) {
+      this.remove();
+   }
+});
+```
+
+### .nodes()
+
+Returns all matching nodes from the object.
+
+```javascript
+select(people, ".age").nodes(); // [35, 15]
+```
+
+### .remove()
+
+Removes matching elements from the original object.
+
+```javascript
+select(people, ".age").remove();
+```
+
+### .update(fn)
+
+Updates all matching nodes using the given callback.
+
+```javascript
+select(people, ".age").update(function(age) {
+   return age - 5;
+});
+```
+
+### .condense()
+
+Reduces the original object down to only the matching elements (the hierarchy is maintained).
+
+```javascript
+select(people, ".age").condense();
+```
+
+```javascript
+{
+    george: { age: 35 },
+    mary: { age: 15 }
+}
+```
+
+## Selectors
+
+js-select supports the following [JSONSelect](http://jsonselect.org/) selectors:
+
+```
+*
+type
+.key
+ancestor selector
+parent > selector
+sibling ~ selector
+selector1, selector2
+:root
+:nth-child(n)
+:nth-child(even)
+:nth-child(odd)
+:nth-last-child(n)
+:first-child
+:last-child
+:only-child
+:has(selector)
+:val("string")
+:contains("substring")
+```
+
+See [details](http://jsonselect.org/#docs/overview) on each selector, and [try them](http://jsonselect.org/#tryit) out on the JSONSelect website.
+
+## Install
+
+For [node](http://nodejs.org), install with [npm](http://npmjs.org):
+
+```bash
+npm install js-select
+```
+
+For the browser, download the select.js file or fetch the latest version from [npm](http://npmjs.org) and build a browser file using [browserify](https://github.com/substack/node-browserify):
+
+```bash
+npm install browserify -g
+npm install js-select
+
+browserify --require js-select --outfile select.js
+```
+this will build a browser file with `require('js-select')` available.
+
+## Propers
+
+Huge thanks to [@substack](http://github.com/substack) for the ingenious [js-traverse](https://github.com/substack/js-traverse) and [@lloyd](https://github.com/lloyd) for the ingenious [JSONSelect spec](http://http://jsonselect.org/) and [selector parser](http://search.npmjs.org/#/JSONSelect).
\ No newline at end of file
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/index.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/index.js
new file mode 100644
index 0000000..1489b8e
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/index.js
@@ -0,0 +1,197 @@
+var traverse = require("traverse"),
+    JSONSelect = require("JSONSelect");
+
+module.exports = function(obj, string) {
+   var sels = parseSelectors(string);
+
+   return {
+      nodes: function() {
+         var nodes = [];
+         this.forEach(function(node) {
+            nodes.push(node);
+         });
+         return nodes;
+      },
+
+      update: function(cb) {
+         this.forEach(function(node) {
+            this.update(typeof cb == "function" ? cb(node) : cb);
+         });
+         return obj;
+      },
+
+      remove: function() {
+         this.forEach(function(node) {
+            this.remove();
+         })
+         return obj;
+      },
+
+      condense: function() {
+         traverse(obj).forEach(function(node) {
+            if (!this.parent) return;
+
+            if (this.parent.keep) {
+               this.keep = true;
+            } else {
+               var match = matchesAny(sels, this);
+               this.keep = match;
+               if (!match) {
+                  if (this.isLeaf) {
+                     this.remove();
+                  } else {
+                     this.after(function() {
+                        if (this.keep_child) {
+                           this.parent.keep_child = true;
+                        }
+                        if (!this.keep && !this.keep_child) {
+                           this.remove();
+                        }
+                     });
+                  }
+               } else {
+                  this.parent.keep_child = true;
+               }
+            }
+         });
+         return obj;
+      },
+
+      forEach: function(cb) {
+         traverse(obj).forEach(function(node) {
+            if (matchesAny(sels, this)) {
+               this.matches = function(string) {
+                  return matchesAny(parseSelectors(string), this);
+               };
+               // inherit context from js-traverse
+               cb.call(this, node);
+            }
+         });
+         return obj;
+      }
+   };
+}
+
+function parseSelectors(string) {
+   var parsed = JSONSelect._parse(string || "*")[1];
+   return getSelectors(parsed);
+}
+
+function getSelectors(parsed) {
+   if (parsed[0] == ",") {  // "selector1, selector2"
+      return parsed.slice(1);
+   }
+   return [parsed];
+}
+
+function matchesAny(sels, context) {
+   for (var i = 0; i < sels.length; i++) {
+      if (matches(sels[i], context)) {
+         return true;
+      }
+   }
+   return false;
+}
+
+function matches(sel, context) {
+   var path = context.parents.concat([context]),
+       i = path.length - 1,
+       j = sel.length - 1;
+
+   // walk up the ancestors
+   var must = true;
+   while(j >= 0 && i >= 0) {
+      var part = sel[j],
+          context = path[i];
+
+      if (part == ">") {
+         j--;
+         must = true;
+         continue;
+      }
+
+      if (matchesKey(part, context)) {
+         j--;
+      }
+      else if (must) {
+         return false;
+      }
+
+      i--;
+      must = false;
+   }
+   return j == -1;
+}
+
+function matchesKey(part, context) {
+   var key = context.key,
+       node = context.node,
+       parent = context.parent;
+
+   if (part.id && key != part.id) {
+      return false;
+   }
+   if (part.type) {
+      var type = part.type;
+
+      if (type == "null" && node !== null) {
+         return false;
+      }
+      else if (type == "array" && !isArray(node)) {
+         return false;
+      }
+      else if (type == "object" && (typeof node != "object"
+                 || node === null || isArray(node))) {
+         return false;
+      }
+      else if ((type == "boolean" || type == "string" || type == "number")
+               && type != typeof node) {
+         return false;
+      }
+   }
+   if (part.pf == ":nth-child") {
+      var index = parseInt(key) + 1;
+      if ((part.a == 0 && index !== part.b)         // :nth-child(i)
+        || (part.a == 1 && !(index >= -part.b))     // :nth-child(n)
+        || (part.a == -1 && !(index <= part.b))     // :nth-child(-n + 1)
+        || (part.a == 2 && index % 2 != part.b)) {  // :nth-child(even)
+         return false;
+      }
+   }
+   if (part.pf == ":nth-last-child"
+      && (!parent || key != parent.node.length - part.b)) {
+         return false;
+   }
+   if (part.pc == ":only-child"
+      && (!parent || parent.node.length != 1)) {
+         return false;
+   }
+   if (part.pc == ":root" && key !== undefined) {
+      return false;
+   }
+   if (part.has) {
+      var sels = getSelectors(part.has[0]),
+          match = false;
+      traverse(node).forEach(function(child) {
+         if (matchesAny(sels, this)) {
+            match = true;
+         }
+      });
+      if (!match) {
+         return false;
+      }
+   }
+   if (part.expr) {
+      var expr = part.expr, lhs = expr[0], op = expr[1], rhs = expr[2];
+      if (typeof node != "string"
+          || (!lhs && op == "=" && node != rhs)   // :val("str")
+          || (!lhs && op == "*=" && node.indexOf(rhs) == -1)) { // :contains("substr")
+         return false;
+      }
+   }
+   return true;
+}
+
+var isArray = Array.isArray || function(obj) {
+    return toString.call(obj) === '[object Array]';
+}
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/.gitmodules b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/.gitmodules
new file mode 100644
index 0000000..15339d4
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "src/test/tests"]
+	path = src/test/tests
+	url = git://github.com/lloyd/JSONSelectTests
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/.npmignore b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/.npmignore
new file mode 100644
index 0000000..0e2021f
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/.npmignore
@@ -0,0 +1,5 @@
+*~
+\#*\#
+.DS_Store
+/src/dist
+
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/JSONSelect.md b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/JSONSelect.md
new file mode 100644
index 0000000..bdd8580
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/JSONSelect.md
@@ -0,0 +1,214 @@
+**WARNING**: This document is a work in progress, just like JSONSelect itself.
+View or contribute to the latest version [on github](http://github.com/lloyd/JSONSelect/blob/master/JSONSelect.md)
+
+# JSONSelect
+
+  1. [introduction](#introduction)
+  1. [levels](#levels)
+  1. [language overview](#overview)
+  1. [grouping](#grouping)
+  1. [selectors](#selectors)
+  1. [pseudo classes](#pseudo)
+  1. [expressions](#expressions)
+  1. [combinators](#combinators)
+  1. [grammar](#grammar)
+  1. [conformance tests](#tests)
+  1. [references](#references)
+
+## Introduction<a name="introduction"></a>
+
+JSONSelect defines a language very similar in syntax and structure to
+[CSS3 Selectors](http://www.w3.org/TR/css3-selectors/).  JSONSelect
+expressions are patterns which can be matched against JSON documents.
+
+Potential applications of JSONSelect include:
+
+  * Simplified programmatic matching of nodes within JSON documents.
+  * Stream filtering, allowing efficient and incremental matching of documents.
+  * As a query language for a document database.
+
+## Levels<a name="levels"></a>
+
+The specification of JSONSelect is broken into three levels.  Higher
+levels include more powerful constructs, and are likewise more
+complicated to implement and use.
+
+**JSONSelect Level 1** is a small subset of CSS3.  Every feature is
+derived from a CSS construct that directly maps to JSON.  A level 1
+implementation is not particularly complicated while providing basic
+querying features.
+
+**JSONSelect Level 2** builds upon Level 1 adapting more complex CSS
+constructs which allow expressions to include constraints such as
+patterns that match against values, and those which consider a node's
+siblings.  Level 2 is still a direct adaptation of CSS, but includes
+constructs whose semantic meaning is significantly changed.
+
+**JSONSelect Level 3** adds constructs which do not necessarily have a
+direct analog in CSS, and are added to increase the power and convenience
+of the selector language.  These include aliases, wholly new pseudo
+class functions, and more blue sky dreaming.
+
+## Language Overview<a name="overview"></a>
+
+<table>
+<tr><th>pattern</th><th>meaning</th><th>level</th></tr>
+<tr><td>*</td><td>Any node</td><td>1</td></tr>
+<tr><td>T</td><td>A node of type T, where T is one string, number, object, array, boolean, or null</td><td>1</td></tr>
+<tr><td>T.key</td><td>A node of type T which is the child of an object and is the value its parents key property</td><td>1</td></tr>
+<tr><td>T."complex key"</td><td>Same as previous, but with property name specified as a JSON string</td><td>1</td></tr>
+<tr><td>T:root</td><td>A node of type T which is the root of the JSON document</td><td>1</td></tr>
+<tr><td>T:nth-child(n)</td><td>A node of type T which is the nth child of an array parent</td><td>1</td></tr>
+<tr><td>T:nth-last-child(n)</td><td>A node of type T which is the nth child of an array parent counting from the end</td><td>2</td></tr>
+<tr><td>T:first-child</td><td>A node of type T which is the first child of an array parent (equivalent to T:nth-child(1)</td><td>1</td></tr>
+<tr><td>T:last-child</td><td>A node of type T which is the last child of an array parent (equivalent to T:nth-last-child(1)</td><td>2</td></tr>
+<tr><td>T:only-child</td><td>A node of type T which is the only child of an array parent</td><td>2</td></tr>
+<tr><td>T:empty</td><td>A node of type T which is an array or object with no child</td><td>2</td></tr>
+<tr><td>T U</td><td>A node of type U with an ancestor of type T</td><td>1</td></tr>
+<tr><td>T > U</td><td>A node of type U with a parent of type T</td><td>1</td></tr>
+<tr><td>T ~ U</td><td>A node of type U with a sibling of type T</td><td>2</td></tr>
+<tr><td>S1, S2</td><td>Any node which matches either selector S1 or S2</td><td>1</td></tr>
+<tr><td>T:has(S)</td><td>A node of type T which has a child node satisfying the selector S</td><td>3</td></tr>
+<tr><td>T:expr(E)</td><td>A node of type T with a value that satisfies the expression E</td><td>3</td></tr>
+<tr><td>T:val(V)</td><td>A node of type T with a value that is equal to V</td><td>3</td></tr>
+<tr><td>T:contains(S)</td><td>A node of type T with a string value contains the substring S</td><td>3</td></tr>
+</table>
+
+## Grouping<a name="grouping"></a>
+
+## Selectors<a name="selectors"></a>
+
+## Pseudo Classes<a name="pseudo"></a>
+
+## Expressions<a name="expressions"></a>
+
+## Combinators<a name="combinators"></a>
+
+## Grammar<a name="grammar"></a>
+
+(Adapted from [CSS3](http://www.w3.org/TR/css3-selectors/#descendant-combinators) and
+ [json.org](http://json.org/))
+
+    selectors_group
+      : selector [ `,` selector ]*
+      ;
+
+    selector
+      : simple_selector_sequence [ combinator simple_selector_sequence ]*
+      ;
+
+    combinator
+      : `>` | \s+
+      ;
+
+    simple_selector_sequence
+      /* why allow multiple HASH entities in the grammar? */
+      : [ type_selector | universal ]
+        [ class | pseudo ]*
+      | [ class | pseudo ]+
+      ;
+
+    type_selector
+      : `object` | `array` | `number` | `string` | `boolean` | `null`
+      ;
+
+    universal
+      : '*'
+      ;
+
+    class
+      : `.` name
+      | `.` json_string
+      ;
+
+    pseudo
+      /* Note that pseudo-elements are restricted to one per selector and */
+      /* occur only in the last simple_selector_sequence. */
+      : `:` pseudo_class_name
+      | `:` nth_function_name `(` nth_expression `)`
+      | `:has` `(`  selectors_group `)`
+      | `:expr` `(`  expr `)`
+      | `:contains` `(`  json_string `)`
+      | `:val` `(` val `)`
+      ;
+
+    pseudo_class_name
+      : `root` | `first-child` | `last-child` | `only-child`
+
+    nth_function_name
+      : `nth-child` | `nth-last-child`
+
+    nth_expression
+      /* expression is and of the form "an+b" */
+      : TODO
+      ;
+
+    expr
+      : expr binop expr
+      | '(' expr ')'
+      | val
+      ;
+
+    binop
+      : '*' | '/' | '%' | '+' | '-' | '<=' | '>=' | '$='
+      | '^=' | '*=' | '>' | '<' | '=' | '!=' | '&&' | '||'
+      ;
+
+    val
+      : json_number | json_string | 'true' | 'false' | 'null' | 'x'
+      ;
+
+    json_string
+      : `"` json_chars* `"`
+      ;
+
+    json_chars
+      : any-Unicode-character-except-"-or-\-or-control-character
+      |  `\"`
+      |  `\\`
+      |  `\/`
+      |  `\b`
+      |  `\f`
+      |  `\n`
+      |  `\r`
+      |  `\t`
+      |   \u four-hex-digits
+      ;
+
+    name
+      : nmstart nmchar*
+      ;
+
+    nmstart
+      : escape | [_a-zA-Z] | nonascii
+      ;
+
+    nmchar
+      : [_a-zA-Z0-9-]
+      | escape
+      | nonascii
+      ;
+
+    escape
+      : \\[^\r\n\f0-9a-fA-F]
+      ;
+
+    nonascii
+      : [^\0-0177]
+      ;
+
+## Conformance Tests<a name="tests"></a>
+
+See [https://github.com/lloyd/JSONSelectTests](https://github.com/lloyd/JSONSelectTests)
+
+## References<a name="references"></a>
+
+In no particular order.
+
+  * [http://json.org/](http://json.org/)
+  * [http://www.w3.org/TR/css3-selectors/](http://www.w3.org/TR/css3-selectors/)
+  * [http://ejohn.org/blog/selectors-that-people-actually-use/](http://ejohn.org/blog/selectors-that-people-actually-use/)
+  * [http://shauninman.com/archive/2008/05/05/css\_qualified\_selectors](  * http://shauninman.com/archive/2008/05/05/css_qualified_selectors)
+  * [http://snook.ca/archives/html\_and\_css/css-parent-selectors](http://snook.ca/archives/html_and_css/css-parent-selectors)
+  * [http://remysharp.com/2010/10/11/css-parent-selector/](http://remysharp.com/2010/10/11/css-parent-selector/)
+  * [https://github.com/jquery/sizzle/wiki/Sizzle-Home](https://github.com/jquery/sizzle/wiki/Sizzle-Home)
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/README.md b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/README.md
new file mode 100644
index 0000000..00a573e
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/README.md
@@ -0,0 +1,33 @@
+JSONSelect is *EXPERIMENTAL*, *ALPHA*, etc.
+
+JSONSelect defines a selector language similar to CSS intended for
+JSON documents.  For an introduction to the project see
+[jsonselect.org](http://jsonselect.org) or the [documentation](https://github.com/lloyd/JSONSelect/blob/master/JSONSelect.md).
+
+## Project Overview
+
+JSONSelect is an attempt to create a selector language similar to
+CSS for JSON objects.  A couple key goals of the project's include:
+
+  * **intuitive** - JSONSelect is meant to *feel like* CSS, meaning a developers with an understanding of CSS can probably guess most of the syntax.
+  * **expressive** - As JSONSelect evolves, it will include more of the most popular constructs from the CSS spec and popular implementations (like [sizzle](http://sizzlejs.com/)).  A successful result will be a good balance of simplicity and power.
+  * **language independence** - The project will avoid features which are unnecessarily tied to a particular implementation language.
+  * **incremental adoption** - JSONSelect features are broken in to conformance levels, to make it easier to build basic support and to allow incremental stabilization of the language.
+  * **efficient** - As many constructs of the language as possible will be able to be evaluated in a single document traversal.  This allows for efficient stream filtering.
+
+JSONSelect should make common operations easy, complex operations possible,
+but haughtily ignore weird shit.
+
+## What's Here
+
+This repository is the home to many things related to JSONSelect:
+
+  * [Documentation](https://github.com/lloyd/JSONSelect/blob/master/JSONSelect.md) which describes the language
+  * The [jsonselect.org](http://jsonselect.org) [site source](https://github.com/lloyd/JSONSelect/blob/master/site/)
+  * A [reference implementation](https://github.com/lloyd/JSONSelect/blob/master/src/jsonselect.js) in JavaScript
+
+## Related projects
+
+Conformance tests are broken out into a [separate
+repository](https://github.com/lloyd/JSONSelectTests) and may be used
+by other implementations.
\ No newline at end of file
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/package.json b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/package.json
new file mode 100644
index 0000000..fae9d34
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/package.json
@@ -0,0 +1,49 @@
+{
+  "author": {
+    "name": "Lloyd Hilaiel",
+    "email": "lloyd@hilaiel.com",
+    "url": "http://trickyco.de"
+  },
+  "name": "JSONSelect",
+  "description": "CSS-like selectors for JSON",
+  "version": "0.2.1",
+  "homepage": "http://jsonselect.org",
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/lloyd/JSONSelect.git"
+  },
+  "main": "src/jsonselect",
+  "engines": {
+    "node": ">=0.4.7"
+  },
+  "dependencies": {},
+  "devDependencies": {},
+  "files": [
+    "src/jsonselect.js",
+    "src/test/run.js",
+    "src/test/tests",
+    "tests",
+    "README.md",
+    "JSONSelect.md",
+    "package.json",
+    "LICENSE"
+  ],
+  "_id": "JSONSelect@0.2.1",
+  "_engineSupported": true,
+  "_npmVersion": "1.0.6",
+  "_nodeVersion": "v0.4.7",
+  "_defaultsLoaded": true,
+  "dist": {
+    "shasum": "415418a526d33fe31d74b4defa3c836d485ec203",
+    "tarball": "http://registry.npmjs.org/JSONSelect/-/JSONSelect-0.2.1.tgz"
+  },
+  "scripts": {},
+  "directories": {},
+  "_shasum": "415418a526d33fe31d74b4defa3c836d485ec203",
+  "_from": "JSONSelect@0.2.1",
+  "_resolved": "https://registry.npmjs.org/JSONSelect/-/JSONSelect-0.2.1.tgz",
+  "bugs": {
+    "url": "https://github.com/lloyd/JSONSelect/issues"
+  },
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/css/droid_sans.css b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/css/droid_sans.css
new file mode 100644
index 0000000..0826445
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/css/droid_sans.css
@@ -0,0 +1,6 @@
+@font-face {
+  font-family: 'Droid Sans';
+  font-style: normal;
+  font-weight: normal;
+  src: local('Droid Sans'), local('DroidSans'), url('droid_sans.tt') format('truetype');
+}
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/css/droid_sans.tt b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/css/droid_sans.tt
new file mode 100644
index 0000000..efd1f8b
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/css/droid_sans.tt
Binary files differ
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/css/style.css b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/css/style.css
new file mode 100644
index 0000000..b4fbaa6
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/css/style.css
@@ -0,0 +1,209 @@
+body {
+    background-color: #191919;
+    color: #a1a1a1;
+    font-family: 'Droid Sans', arial, serif;
+    font-size: 14px;
+}
+
+#header {
+    position: fixed;
+    background-color: #191919;
+    opacity: .7;
+    color: #ccc;
+    width: 100%;
+    top: 0px;
+    left: 0px;
+    margin: 0px;
+    padding: 4px 10px 4px 10px;
+}
+
+#main {
+    margin-top: 100px;
+}
+
+#header b {
+    font-weight: bold;
+    color: #fff;
+}
+
+a {
+    color: #999;
+    text-decoration: underline;
+}
+
+a:hover {
+    color: #fff;
+}
+
+#header .title {
+    font-size: 400%;
+}
+
+#header .subtitle {
+    font-size: 150%;
+    margin-left: 1em;
+    font-style: italic;
+}
+
+#header .nav {
+    float: right;
+    margin-right: 100px;
+}
+
+div.content {
+    max-width: 850px;
+    min-width: 675px;
+    margin: auto;
+}
+
+div#tryit {
+    min-width: 825px;
+}
+
+#splash {
+    max-width: 700px;
+}
+
+.json {
+    background-color: #333;
+    padding: .5em 1em .5em 1em;
+    border: 3px solid #444;
+    border-radius: 1em;
+    -moz-border-radius: 1em;
+    -webkit-border-radius: 1em;
+}
+
+#splash .sample {
+    width: 250px;
+    float: right;
+    margin-left: 1em;
+    margin-top: 3em;
+    font-size: 90%;
+}
+
+#doc {
+    margin-top: 140px;
+}
+
+#doc table {
+    width: 100%;
+}
+
+#doc table th {
+    background-color: #aaa;
+    color: #333;
+    padding: 10px;
+}
+
+#doc table tr td:first-child {
+    font-family: "andale mono",monospace;
+}
+
+#doc table td {
+    padding-top: 3px;
+}
+
+#splash .sample tt {
+    font-size: 90%;
+    padding: 0 .2em 0 .2em;
+}
+
+#splash .sample pre {
+    border-top: 1px dashed #aaa;
+    padding-top: 1em;
+}
+
+#splash .pitch {
+    padding-top: 2em;
+    font-size: 170%;
+}
+
+.selected {
+    opacity: .7;
+    padding: 8px;
+    margin: -10px;
+    background-color: #fff475;
+    color: #000;
+    border: 2px solid white;
+    border-radius: 4px;
+    -moz-border-radius: 4px;
+    -webkit-border-radius: 4px;
+}
+
+div.current input, div.selector, pre, code, tt {
+    font-family: "andale mono",monospace;
+    font-size: .9em;
+}
+
+div.current {
+    width: 100%;
+    padding: 1em;
+}
+
+div.current input {
+    color: #fff;
+    font-size: 110%;
+    background-color: #333;
+    border: 1px solid #444;
+    padding: 8px;
+    width: 400px;
+    margin-left: 1em;
+    margin-right: 1em;
+    border-radius: 4px;
+    -moz-border-radius: 4px;
+    -webkit-border-radius: 4px;
+}
+
+pre.doc {
+    float: right;
+    padding: 1em 3em 1em 3em;
+    padding-right: 160px;
+    font-size: 90%;
+}
+
+.selectors {
+    margin: 1em;
+    background-color: #444;
+    width: 300px;
+    padding: 8px;
+    border-radius: 4px;
+    -moz-border-radius: 4px;
+    -webkit-border-radius: 4px;
+
+}
+.selectors .selector {
+    background-color: #333;
+    padding: .4em;
+    margin: 1px;
+    cursor: pointer;
+}
+
+.selectors .selector.inuse {
+    border: 1px solid #9f9;
+    margin: -1px;
+    margin-left: 0px;
+}
+
+.results.error {
+    color: #f99;
+}
+
+.results {
+    color: #9f9;
+    font-size: 90%;
+    width: 300px;
+}
+
+#cred, #code {
+    padding-top: 2em;
+}
+
+p.psst {
+    margin-top: 4em;
+    font-size: .9em;
+}
+
+p.item {
+    margin-top: 2em;
+    margin-left: 1em;
+}
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/index.html b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/index.html
new file mode 100644
index 0000000..984e184
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/index.html
@@ -0,0 +1,139 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title> JSONSelect </title>
+    <link href='css/droid_sans.css' rel='stylesheet' type='text/css'>
+    <link href='css/style.css' type='text/css' rel='stylesheet'>
+  </head>
+  <body>
+    <div id="header">
+      <div class="content">
+        <div class="title">json<b>:</b>select<b>()</b></div>
+        <div class="nav"> <a href="#overview">Overview</a> | <a href="#tryit">Try It!</a> | <a href="#docs">Docs</a> | <a href="#code">Code</a> | <a href="mailto:jsonselect@librelist.com">Contact</a> | <a href="#cred">Credit</a> </div>
+        <div class="subtitle">CSS-like selectors for JSON.</div>
+      </div>
+    </div>
+    <div id="main">
+      <div style="display: none" id="splash" class="content">
+        <div class="sample json">
+          <tt>".author .drinkPref&nbsp;:first-child"</tt>
+          <pre>{
+  "author": {
+    "name": {
+      "first": "Lloyd",
+      "last": "Hilaiel"
+    },
+    "drinkPref": [
+      <span class="selected">"whiskey"</span>,
+      "beer",
+      "wine"
+    ],
+  },
+  "thing": "JSONSelect site",
+  "license": "<a href="http://creativecommons.org/licenses/by-sa/3.0/us/">(cc) BY-SA</a>"
+}</pre></div>
+        <div class="pitch">
+        <p>JSONSelect is an <i>experimental</i> selector language for JSON.</p>
+        <p>It makes it easy to access data  in complex JSON documents.</p>
+        <p>It <i>feels like</i> CSS.</p>
+        <p>Why not <a href="#tryit">give it a try</a>?
+        </div>
+      </div>
+      <div id="tryit" style="display: none" class="content">
+        <div class="current"> Current Selector (click to edit): <input type="text"></input><span class="results"></span></div>
+        <pre class="doc json">
+{
+    "name": {
+        "first": "Lloyd",
+        "last": "Hilaiel"
+    },
+    "favoriteColor": "yellow",
+    "languagesSpoken": [
+        {
+            "lang": "Bulgarian",
+            "level": "advanced"
+        },
+        {
+            "lang": "English",
+            "level": "native",
+            "preferred": true
+        },
+        {
+            "lang": "Spanish",
+            "level": "beginner"
+        }
+    ],
+    "seatingPreference": [
+        "window",
+        "aisle"
+    ],
+    "drinkPreference": [
+        "whiskey",
+        "beer",
+        "wine"
+    ],
+    "weight": 172
+}
+        </pre>
+        <div class="selectors">
+          <div class="title"> Choose A Selector... </div>
+          <div class="selector">.languagesSpoken .lang</div>
+          <div class="selector">.drinkPreference :first-child</div>
+          <div class="selector">."weight"</div>
+          <div class="selector">.lang</div>
+          <div class="selector">.favoriteColor</div>
+          <div class="selector">string.favoriteColor</div>
+          <div class="selector">string:last-child</div>
+          <div class="selector">string:nth-child(-n+2)</div>
+          <div class="selector">string:nth-child(odd)</div>
+          <div class="selector">string:nth-last-child(1)</div>
+          <div class="selector">:root</div>
+          <div class="selector">number</div>
+          <div class="selector">:has(:root > .preferred)</div>
+          <div class="selector">.preferred ~ .lang</div>
+          <div class="selector">:has(.lang:val("Spanish")) > .level</div>
+          <div class="selector">.lang:val("Bulgarian") ~ .level</div>
+          <div class="selector">.seatingPreference :nth-child(1)</div>
+          <div class="selector">.weight:expr(x<180) ~ .name .first</div>
+        </div>
+      </div>
+      <div style="display: none" id="cred" class="content">
+        <p>JSONSelect is dedicated to sad code everywhere that looks like this:</p>
+        <pre class="json">if (foo && foo.bar && foo.bar.baz && foo.bar.baz.length > 2)
+    return foo.bar.baz[2];
+return undefined;</pre>
+        <p><a href="http://trickyco.de/">Lloyd Hilaiel</a> started it, and is surrounded by many <a href="https://github.com/lloyd/JSONSelect/contributors">awesome contributors</a>.</p>
+        <p><a href="http://blog.mozilla.com/dherman/">Dave Herman</a> provided the name, and lots of encouragement.</p>
+        <p><a href="http://open-mike.org/">Mike Hanson</a> gave deep feedback and ideas.</p>
+        <p><a href="http://ejohn.org/">John Resig</a> unwittingly contributed his <a href="http://ejohn.org/blog/selectors-that-people-actually-use/">design thoughts</a>.</p>
+        <p>The jsonselect.org site design was inspired by <a href="http://www.stephenwildish.co.uk/">Stephen Wildish</a>.</p>
+        <p><a href="http://json.org/">JSON</a> and <a href="http://www.w3.org/TR/css3-selectors/">CSS3 Selectors</a> are the prerequisites to JSONSelect's existence, so thanks to you guys too.</p>
+        <p></p>
+      </div>
+      <div style="display: none" id="doc" class="content">
+        Loading documentation...
+      </div>
+      <div style="display: none" id="code" class="content">
+        <p>Several different implementations of JSONSelect are available:</p>
+        <p class="item"><b>JavaScript:</b> Get it <a href="js/jsonselect.js">documented</a>, or <a href="js/jsonselect.min.js">minified</a> (<i>2.9k</i> minified and gzipped).
+           The code is <a href="https://github.com/lloyd/JSONSelect">on github</a>.</p>
+        <p class="item"><b>node.js:</b> <tt>npm install JSONSelect</tt></p>
+        <p class="item"><b>ruby:</b> A gem by <a href="https://github.com/fd">Simon Menke</a>: <a href="https://github.com/fd/json_select">https://github.com/fd/json_select</a></p>
+        <p class="psst">
+          Something missing from this list?  Please, <a href="https://github.com/lloyd/JSONSelect/issues">tell me about it</a>.
+        </p>
+      </div>
+
+    </div>
+
+
+<a href="https://github.com/lloyd/JSONSelect"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://d3nwyuy0nl342s.cloudfront.net/img/4c7dc970b89fd04b81c8e221ba88ff99a06c6b61/687474703a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f77686974655f6666666666662e706e67" alt="Fork me on GitHub"></a>
+  </body>
+  <script src="js/json2.js"></script>
+  <script src="js/showdown.js"></script>
+  <script src="js/jquery-1.6.1.min.js"></script>
+  <script src="js/jquery.ba-hashchange.min.js"></script>
+  <script src="js/jsonselect.js"></script>
+  <script src="js/nav.js"></script>
+  <script src="js/demo.js"></script>
+</html>
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/demo.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/demo.js
new file mode 100644
index 0000000..7462cf9
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/demo.js
@@ -0,0 +1,64 @@
+window.jsel = JSONSelect;
+
+$(document).ready(function() {
+    var theDoc = JSON.parse($("pre.doc").text());
+
+    function highlightMatches(ar) {
+        // first calculate match offsets
+        var wrk = [];
+        var html = $.trim(JSON.stringify(theDoc, undefined, 4));
+        var ss = "<span class=\"selected\">";
+        var es = "</span>";
+        for (var i = 0; i < ar.length; i++) {
+            var found = $.trim(JSON.stringify(ar[i], undefined, 4));
+            // turn the string into a regex to handle indentation
+            found = found.replace(/[-[\]{}()*+?.,\\^$|#]/g, "\\$&").replace(/\s+/gm, "\\s*");
+            var re = new RegExp(found, "m");
+            var m = re.exec(html);
+            if (!m) continue;
+            wrk.push({ off: m.index, typ: "s" });
+            wrk.push({ off: m[0].length+m.index, typ: "e" });
+        }
+        // sort by offset
+        wrk = wrk.sort(function(a,b) { return a.off - b.off; });
+
+        // now start injecting spans into the text
+        var cur = 0;
+        var cons = 0;
+        for (var i = 0; i < wrk.length; i++) {
+            var diff = wrk[i].off - cons;
+            cons = wrk[i].off;
+            var tag = (wrk[i].typ == 's' ? ss : es);
+            cur += diff;
+            html = html.substr(0, cur) + tag + html.substr(cur);
+            cur += tag.length;
+        }
+        return html;
+    }
+
+    // when a selector is chosen, update the text box
+    $(".selectors .selector").click(function() {
+        $(".current input").val($(this).text()).keyup();
+    });
+
+    var lastSel;
+    $(".current input").keyup(function () {
+        try {
+            var sel = $(".current input").val()
+            if (lastSel === $.trim(sel)) return;
+            lastSel = $.trim(sel);
+            var ar = jsel.match(sel, theDoc);
+            $(".current .results").text(ar.length + " match" + (ar.length == 1 ? "" : "es"))
+                .removeClass("error");
+            $("pre.doc").html(highlightMatches(ar));
+            $("pre.doc .selected").hide().fadeIn(700);
+        } catch(e) {
+            $(".current .results").text(e.toString()).addClass("error");
+            $("pre.doc").text($.trim(JSON.stringify(theDoc, undefined, 4)));
+        }
+        $(".selectors .selector").removeClass("inuse");
+        $(".selectors div.selector").each(function() {
+            if ($(this).text() === sel) $(this).addClass("inuse");
+        });
+    });
+});
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/jquery-1.6.1.min.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/jquery-1.6.1.min.js
new file mode 100644
index 0000000..b2ac174
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/jquery-1.6.1.min.js
@@ -0,0 +1,18 @@
+/*!
+ * jQuery JavaScript Library v1.6.1
+ * http://jquery.com/
+ *
+ * Copyright 2011, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ * Copyright 2011, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ *
+ * Date: Thu May 12 15:04:36 2011 -0400
+ */
+(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cv(a){if(!cj[a]){var b=f("<"+a+">").appendTo("body"),d=b.css("display");b.remove();if(d==="none"||d===""){ck||(ck=c.createElement("iframe"),ck.frameBorder=ck.width=ck.height=0),c.body.appendChild(ck);if(!cl||!ck.createElement)cl=(ck.contentWindow||ck.contentDocument).document,cl.write("<!doctype><html><body></body></html>");b=cl.createElement(a),cl.body.appendChild(b),d=f.css(b,"display"),c.body.removeChild(ck)}cj[a]=d}return cj[a]}function cu(a,b){var c={};f.each(cp.concat.apply([],cp.slice(0,b)),function(){c[this]=a});return c}function ct(){cq=b}function cs(){setTimeout(ct,0);return cq=f.now()}function ci(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ch(){try{return new a.XMLHttpRequest}catch(b){}}function cb(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g<i;g++){if(g===1)for(h in a.converters)typeof h=="string"&&(e[h.toLowerCase()]=a.converters[h]);l=k,k=d[g];if(k==="*")k=l;else if(l!=="*"&&l!==k){m=l+" "+k,n=e[m]||e["* "+k];if(!n){p=b;for(o in e){j=o.split(" ");if(j[0]===l||j[0]==="*"){p=e[j[1]+" "+k];if(p){o=e[o],o===!0?n=p:p===!0&&(n=o);break}}}}!n&&!p&&f.error("No conversion from "+m.replace(" "," to ")),n!==!0&&(c=n?n(c):p(o(c)))}}return c}function ca(a,c,d){var e=a.contents,f=a.dataTypes,g=a.responseFields,h,i,j,k;for(i in g)i in d&&(c[g[i]]=d[i]);while(f[0]==="*")f.shift(),h===b&&(h=a.mimeType||c.getResponseHeader("content-type"));if(h)for(i in e)if(e[i]&&e[i].test(h)){f.unshift(i);break}if(f[0]in d)j=f[0];else{for(i in d){if(!f[0]||a.converters[i+" "+f[0]]){j=i;break}k||(k=i)}j=j||k}if(j){j!==f[0]&&f.unshift(j);return d[j]}}function b_(a,b,c,d){if(f.isArray(b))f.each(b,function(b,e){c||bF.test(a)?d(a,e):b_(a+"["+(typeof e=="object"||f.isArray(e)?b:"")+"]",e,c,d)});else if(!c&&b!=null&&typeof b=="object")for(var e in b)b_(a+"["+e+"]",b[e],c,d);else d(a,b)}function b$(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h=a[f],i=0,j=h?h.length:0,k=a===bU,l;for(;i<j&&(k||!l);i++)l=h[i](c,d,e),typeof l=="string"&&(!k||g[l]?l=b:(c.dataTypes.unshift(l),l=b$(a,c,d,e,l,g)));(k||!l)&&!g["*"]&&(l=b$(a,c,d,e,"*",g));return l}function bZ(a){return function(b,c){typeof b!="string"&&(c=b,b="*");if(f.isFunction(c)){var d=b.toLowerCase().split(bQ),e=0,g=d.length,h,i,j;for(;e<g;e++)h=d[e],j=/^\+/.test(h),j&&(h=h.substr(1)||"*"),i=a[h]=a[h]||[],i[j?"unshift":"push"](c)}}}function bD(a,b,c){var d=b==="width"?bx:by,e=b==="width"?a.offsetWidth:a.offsetHeight;if(c==="border")return e;f.each(d,function(){c||(e-=parseFloat(f.css(a,"padding"+this))||0),c==="margin"?e+=parseFloat(f.css(a,"margin"+this))||0:e-=parseFloat(f.css(a,"border"+this+"Width"))||0});return e}function bn(a,b){b.src?f.ajax({url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(bf,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)}function bm(a){f.nodeName(a,"input")?bl(a):a.getElementsByTagName&&f.grep(a.getElementsByTagName("input"),bl)}function bl(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bk(a){return"getElementsByTagName"in a?a.getElementsByTagName("*"):"querySelectorAll"in a?a.querySelectorAll("*"):[]}function bj(a,b){var c;if(b.nodeType===1){b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase();if(c==="object")b.outerHTML=a.outerHTML;else if(c!=="input"||a.type!=="checkbox"&&a.type!=="radio"){if(c==="option")b.selected=a.defaultSelected;else if(c==="input"||c==="textarea")b.defaultValue=a.defaultValue}else a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value);b.removeAttribute(f.expando)}}function bi(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c=f.expando,d=f.data(a),e=f.data(b,d);if(d=d[c]){var g=d.events;e=e[c]=f.extend({},d);if(g){delete e.handle,e.events={};for(var h in g)for(var i=0,j=g[h].length;i<j;i++)f.event.add(b,h+(g[h][i].namespace?".":"")+g[h][i].namespace,g[h][i],g[h][i].data)}}}}function bh(a,b){return f.nodeName(a,"table")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function X(a,b,c){b=b||0;if(f.isFunction(b))return f.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return f.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=f.grep(a,function(a){return a.nodeType===1});if(S.test(b))return f.filter(b,d,!c);b=f.filter(b,d)}return f.grep(a,function(a,d){return f.inArray(a,b)>=0===c})}function W(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function O(a,b){return(a&&a!=="*"?a+".":"")+b.replace(A,"`").replace(B,"&")}function N(a){var b,c,d,e,g,h,i,j,k,l,m,n,o,p=[],q=[],r=f._data(this,"events");if(!(a.liveFired===this||!r||!r.live||a.target.disabled||a.button&&a.type==="click")){a.namespace&&(n=new RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)")),a.liveFired=this;var s=r.live.slice(0);for(i=0;i<s.length;i++)g=s[i],g.origType.replace(y,"")===a.type?q.push(g.selector):s.splice(i--,1);e=f(a.target).closest(q,a.currentTarget);for(j=0,k=e.length;j<k;j++){m=e[j];for(i=0;i<s.length;i++){g=s[i];if(m.selector===g.selector&&(!n||n.test(g.namespace))&&!m.elem.disabled){h=m.elem,d=null;if(g.preType==="mouseenter"||g.preType==="mouseleave")a.type=g.preType,d=f(a.relatedTarget).closest(g.selector)[0],d&&f.contains(h,d)&&(d=h);(!d||d!==h)&&p.push({elem:h,handleObj:g,level:m.level})}}}for(j=0,k=p.length;j<k;j++){e=p[j];if(c&&e.level>c)break;a.currentTarget=e.elem,a.data=e.handleObj.data,a.handleObj=e.handleObj,o=e.handleObj.origHandler.apply(e.elem,arguments);if(o===!1||a.isPropagationStopped()){c=e.level,o===!1&&(b=!1);if(a.isImmediatePropagationStopped())break}}return b}}function L(a,c,d){var e=f.extend({},d[0]);e.type=a,e.originalEvent={},e.liveFired=b,f.event.handle.call(c,e),e.isDefaultPrevented()&&d[0].preventDefault()}function F(){return!0}function E(){return!1}function m(a,c,d){var e=c+"defer",g=c+"queue",h=c+"mark",i=f.data(a,e,b,!0);i&&(d==="queue"||!f.data(a,g,b,!0))&&(d==="mark"||!f.data(a,h,b,!0))&&setTimeout(function(){!f.data(a,g,b,!0)&&!f.data(a,h,b,!0)&&(f.removeData(a,e,!0),i.resolve())},0)}function l(a){for(var b in a)if(b!=="toJSON")return!1;return!0}function k(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(j,"$1-$2").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNaN(d)?i.test(d)?f.parseJSON(d):d:parseFloat(d)}catch(g){}f.data(a,c,d)}else d=b}return d}var c=a.document,d=a.navigator,e=a.location,f=function(){function H(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(H,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/\d/,n=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,o=/^[\],:{}\s]*$/,p=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,q=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,r=/(?:^|:|,)(?:\s*\[)+/g,s=/(webkit)[ \/]([\w.]+)/,t=/(opera)(?:.*version)?[ \/]([\w.]+)/,u=/(msie) ([\w.]+)/,v=/(mozilla)(?:.*? rv:([\w.]+))?/,w=d.userAgent,x,y,z,A=Object.prototype.toString,B=Object.prototype.hasOwnProperty,C=Array.prototype.push,D=Array.prototype.slice,E=String.prototype.trim,F=Array.prototype.indexOf,G={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=n.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.6.1",length:0,size:function(){return this.length},toArray:function(){return D.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?C.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),y.done(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(D.apply(this,arguments),"slice",D.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:C,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j<k;j++)if((a=arguments[j])!=null)for(c in a){d=i[c],f=a[c];if(i===f)continue;l&&f&&(e.isPlainObject(f)||(g=e.isArray(f)))?(g?(g=!1,h=d&&e.isArray(d)?d:[]):h=d&&e.isPlainObject(d)?d:{},i[c]=e.extend(l,h,f)):f!==b&&(i[c]=f)}return i},e.extend({noConflict:function(b){a.$===e&&(a.$=g),b&&a.jQuery===e&&(a.jQuery=f);return e},isReady:!1,readyWait:1,holdReady:function(a){a?e.readyWait++:e.ready(!0)},ready:function(a){if(a===!0&&!--e.readyWait||a!==!0&&!e.isReady){if(!c.body)return setTimeout(e.ready,1);e.isReady=!0;if(a!==!0&&--e.readyWait>0)return;y.resolveWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").unbind("ready")}},bindReady:function(){if(!y){y=e._Deferred();if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",z,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",z),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&H()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNaN:function(a){return a==null||!m.test(a)||isNaN(a)},type:function(a){return a==null?String(a):G[A.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;if(a.constructor&&!B.call(a,"constructor")&&!B.call(a.constructor.prototype,"isPrototypeOf"))return!1;var c;for(c in a);return c===b||B.call(a,c)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw a},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(o.test(b.replace(p,"@").replace(q,"]").replace(r,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(b,c,d){a.DOMParser?(d=new DOMParser,c=d.parseFromString(b,"text/xml")):(c=new ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(b)),d=c.documentElement,(!d||!d.nodeName||d.nodeName==="parsererror")&&e.error("Invalid XML: "+b);return c},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g<h;)if(c.apply(a[g++],d)===!1)break}else if(i){for(f in a)if(c.call(a[f],f,a[f])===!1)break}else for(;g<h;)if(c.call(a[g],g,a[g++])===!1)break;return a},trim:E?function(a){return a==null?"":E.call(a)}:function(a){return a==null?"":(a+"").replace(k,"").replace(l,"")},makeArray:function(a,b){var c=b||[];if(a!=null){var d=e.type(a);a.length==null||d==="string"||d==="function"||d==="regexp"||e.isWindow(a)?C.call(c,a):e.merge(c,a)}return c},inArray:function(a,b){if(F)return F.call(b,a);for(var c=0,d=b.length;c<d;c++)if(b[c]===a)return c;return-1},merge:function(a,c){var d=a.length,e=0;if(typeof c.length=="number")for(var f=c.length;e<f;e++)a[d++]=c[e];else while(c[e]!==b)a[d++]=c[e++];a.length=d;return a},grep:function(a,b,c){var d=[],e;c=!!c;for(var f=0,g=a.length;f<g;f++)e=!!b(a[f],f),c!==e&&d.push(a[f]);return d},map:function(a,c,d){var f,g,h=[],i=0,j=a.length,k=a instanceof e||j!==b&&typeof j=="number"&&(j>0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i<j;i++)f=c(a[i],i,d),f!=null&&(h[h.length]=f);else for(g in a)f=c(a[g],g,d),f!=null&&(h[h.length]=f);return h.concat.apply([],h)},guid:1,proxy:function(a,c){if(typeof c=="string"){var d=a[c];c=a,a=d}if(!e.isFunction(a))return b;var f=D.call(arguments,2),g=function(){return a.apply(c,f.concat(D.call(arguments)))};g.guid=a.guid=a.guid||g.guid||e.guid++;return g},access:function(a,c,d,f,g,h){var i=a.length;if(typeof c=="object"){for(var j in c)e.access(a,j,c[j],f,g,d);return a}if(d!==b){f=!h&&f&&e.isFunction(d);for(var k=0;k<i;k++)g(a[k],c,f?d.call(a[k],k,g(a[k],c)):d,h);return a}return i?g(a[0],c):b},now:function(){return(new Date).getTime()},uaMatch:function(a){a=a.toLowerCase();var b=s.exec(a)||t.exec(a)||u.exec(a)||a.indexOf("compatible")<0&&v.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},sub:function(){function a(b,c){return new a.fn.init(b,c)}e.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function(d,f){f&&f instanceof e&&!(f instanceof a)&&(f=a(f));return e.fn.init.call(this,d,f,b)},a.fn.init.prototype=a.fn;var b=a(c);return a},browser:{}}),e.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){G["[object "+b+"]"]=b.toLowerCase()}),x=e.uaMatch(w),x.browser&&(e.browser[x.browser]=!0,e.browser.version=x.version),e.browser.webkit&&(e.browser.safari=!0),j.test(" ")&&(k=/^[\s\xA0]+/,l=/[\s\xA0]+$/),h=e(c),c.addEventListener?z=function(){c.removeEventListener("DOMContentLoaded",z,!1),e.ready()}:c.attachEvent&&(z=function(){c.readyState==="complete"&&(c.detachEvent("onreadystatechange",z),e.ready())});return e}(),g="done fail isResolved isRejected promise then always pipe".split(" "),h=[].slice;f.extend({_Deferred:function(){var a=[],b,c,d,e={done:function(){if(!d){var c=arguments,g,h,i,j,k;b&&(k=b,b=0);for(g=0,h=c.length;g<h;g++)i=c[g],j=f.type(i),j==="array"?e.done.apply(e,i):j==="function"&&a.push(i);k&&e.resolveWith(k[0],k[1])}return this},resolveWith:function(e,f){if(!d&&!b&&!c){f=f||[],c=1;try{while(a[0])a.shift().apply(e,f)}finally{b=[e,f],c=0}}return this},resolve:function(){e.resolveWith(this,arguments);return this},isResolved:function(){return!!c||!!b},cancel:function(){d=1,a=[];return this}};return e},Deferred:function(a){var b=f._Deferred(),c=f._Deferred(),d;f.extend(b,{then:function(a,c){b.done(a).fail(c);return this},always:function(){return b.done.apply(b,arguments).fail.apply(this,arguments)},fail:c.done,rejectWith:c.resolveWith,reject:c.resolve,isRejected:c.isResolved,pipe:function(a,c){return f.Deferred(function(d){f.each({done:[a,"resolve"],fail:[c,"reject"]},function(a,c){var e=c[0],g=c[1],h;f.isFunction(e)?b[a](function(){h=e.apply(this,arguments),h&&f.isFunction(h.promise)?h.promise().then(d.resolve,d.reject):d[g](h)}):b[a](d[g])})}).promise()},promise:function(a){if(a==null){if(d)return d;d=a={}}var c=g.length;while(c--)a[g[c]]=b[g[c]];return a}}),b.done(c.cancel).fail(b.cancel),delete b.cancel,a&&a.call(b,b);return b},when:function(a){function i(a){return function(c){b[a]=arguments.length>1?h.call(arguments,0):c,--e||g.resolveWith(g,h.call(b,0))}}var b=arguments,c=0,d=b.length,e=d,g=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred();if(d>1){for(;c<d;c++)b[c]&&f.isFunction(b[c].promise)?b[c].promise().then(i(c),g.reject):--e;e||g.resolveWith(g,b)}else g!==a&&g.resolveWith(g,d?[a]:[]);return g.promise()}}),f.support=function(){var a=c.createElement("div"),b=c.documentElement,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r;a.setAttribute("className","t"),a.innerHTML="   <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>",d=a.getElementsByTagName("*"),e=a.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};f=c.createElement("select"),g=f.appendChild(c.createElement("option")),h=a.getElementsByTagName("input")[0],j={leadingWhitespace:a.firstChild.nodeType===3,tbody:!a.getElementsByTagName("tbody").length,htmlSerialize:!!a.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55$/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:h.value==="on",optSelected:g.selected,getSetAttribute:a.className!=="t",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},h.checked=!0,j.noCloneChecked=h.cloneNode(!0).checked,f.disabled=!0,j.optDisabled=!g.disabled;try{delete a.test}catch(s){j.deleteExpando=!1}!a.addEventListener&&a.attachEvent&&a.fireEvent&&(a.attachEvent("onclick",function b(){j.noCloneEvent=!1,a.detachEvent("onclick",b)}),a.cloneNode(!0).fireEvent("onclick")),h=c.createElement("input"),h.value="t",h.setAttribute("type","radio"),j.radioValue=h.value==="t",h.setAttribute("checked","checked"),a.appendChild(h),k=c.createDocumentFragment(),k.appendChild(a.firstChild),j.checkClone=k.cloneNode(!0).cloneNode(!0).lastChild.checked,a.innerHTML="",a.style.width=a.style.paddingLeft="1px",l=c.createElement("body"),m={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"};for(q in m)l.style[q]=m[q];l.appendChild(a),b.insertBefore(l,b.firstChild),j.appendChecked=h.checked,j.boxModel=a.offsetWidth===2,"zoom"in a.style&&(a.style.display="inline",a.style.zoom=1,j.inlineBlockNeedsLayout=a.offsetWidth===2,a.style.display="",a.innerHTML="<div style='width:4px;'></div>",j.shrinkWrapBlocks=a.offsetWidth!==2),a.innerHTML="<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>",n=a.getElementsByTagName("td"),r=n[0].offsetHeight===0,n[0].style.display="",n[1].style.display="none",j.reliableHiddenOffsets=r&&n[0].offsetHeight===0,a.innerHTML="",c.defaultView&&c.defaultView.getComputedStyle&&(i=c.createElement("div"),i.style.width="0",i.style.marginRight="0",a.appendChild(i),j.reliableMarginRight=(parseInt((c.defaultView.getComputedStyle(i,null)||{marginRight:0}).marginRight,10)||0)===0),l.innerHTML="",b.removeChild(l);if(a.attachEvent)for(q in{submit:1,change:1,focusin:1})p="on"+q,r=p in a,r||(a.setAttribute(p,"return;"),r=typeof a[p]=="function"),j[q+"Bubbles"]=r;return j}(),f.boxModel=f.support.boxModel;var i=/^(?:\{.*\}|\[.*\])$/,j=/([a-z])([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!l(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g=f.expando,h=typeof c=="string",i,j=a.nodeType,k=j?f.cache:a,l=j?a[f.expando]:a[f.expando]&&f.expando;if((!l||e&&l&&!k[l][g])&&h&&d===b)return;l||(j?a[f.expando]=l=++f.uuid:l=f.expando),k[l]||(k[l]={},j||(k[l].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?k[l][g]=f.extend(k[l][g],c):k[l]=f.extend(k[l],c);i=k[l],e&&(i[g]||(i[g]={}),i=i[g]),d!==b&&(i[f.camelCase(c)]=d);if(c==="events"&&!i[c])return i[g]&&i[g].events;return h?i[f.camelCase(c)]:i}},removeData:function(b,c,d){if(!!f.acceptData(b)){var e=f.expando,g=b.nodeType,h=g?f.cache:b,i=g?b[f.expando]:f.expando;if(!h[i])return;if(c){var j=d?h[i][e]:h[i];if(j){delete j[c];if(!l(j))return}}if(d){delete h[i][e];if(!l(h[i]))return}var k=h[i][e];f.support.deleteExpando||h!=a?delete h[i]:h[i]=null,k?(h[i]={},g||(h[i].toJSON=f.noop),h[i][e]=k):g&&(f.support.deleteExpando?delete b[f.expando]:b.removeAttribute?b.removeAttribute(f.expando):b[f.expando]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d=null;if(typeof a=="undefined"){if(this.length){d=f.data(this[0]);if(this[0].nodeType===1){var e=this[0].attributes,g;for(var h=0,i=e.length;h<i;h++)g=e[h].name,g.indexOf("data-")===0&&(g=f.camelCase(g.substring(5)),k(this[0],g,d[g]))}}return d}if(typeof a=="object")return this.each(function(){f.data(this,a)});var j=a.split(".");j[1]=j[1]?"."+j[1]:"";if(c===b){d=this.triggerHandler("getData"+j[1]+"!",[j[0]]),d===b&&this.length&&(d=f.data(this[0],a),d=k(this[0],a,d));return d===b&&j[1]?this.data(j[0]):d}return this.each(function(){var b=f(this),d=[j[0],c];b.triggerHandler("setData"+j[1]+"!",d),f.data(this,a,c),b.triggerHandler("changeData"+j[1]+"!",d)})},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,c){a&&(c=(c||"fx")+"mark",f.data(a,c,(f.data(a,c,b,!0)||0)+1,!0))},_unmark:function(a,c,d){a!==!0&&(d=c,c=a,a=!1);if(c){d=d||"fx";var e=d+"mark",g=a?0:(f.data(c,e,b,!0)||1)-1;g?f.data(c,e,g,!0):(f.removeData(c,e,!0),m(c,d,"mark"))}},queue:function(a,c,d){if(a){c=(c||"fx")+"queue";var e=f.data(a,c,b,!0);d&&(!e||f.isArray(d)?e=f.data(a,c,f.makeArray(d),!0):e.push(d));return e||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e;d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),d.call(a,function(){f.dequeue(a,b)})),c.length||(f.removeData(a,b+"queue",!0),m(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){typeof a!="string"&&(c=a,a="fx");if(c===b)return f.queue(this[0],a);return this.each(function(){var b=f.queue(this,a,c);a==="fx"&&b[0]!=="inprogress"&&f.dequeue(this,a)})},dequeue:function(a){return this.each(function(){f.dequeue(this,a)})},delay:function(a,b){a=f.fx?f.fx.speeds[a]||a:a,b=b||"fx";return this.queue(b,function(){var c=this;setTimeout(function(){f.dequeue(c,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){function m(){--h||d.resolveWith(e,[e])}typeof a!="string"&&(c=a,a=b),a=a||"fx";var d=f.Deferred(),e=this,g=e.length,h=1,i=a+"defer",j=a+"queue",k=a+"mark",l;while(g--)if(l=f.data(e[g],i,b,!0)||(f.data(e[g],j,b,!0)||f.data(e[g],k,b,!0))&&f.data(e[g],i,f._Deferred(),!0))h++,l.done(m);m();return d.promise()}});var n=/[\n\t\r]/g,o=/\s+/,p=/\r/g,q=/^(?:button|input)$/i,r=/^(?:button|input|object|select|textarea)$/i,s=/^a(?:rea)?$/i,t=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,u=/\:/,v,w;f.fn.extend({attr:function(a,b){return f.access(this,a,b,!0,f.attr)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,a,b,!0,f.prop)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.addClass(a.call(this,b,c.attr("class")||""))});if(a&&typeof a=="string"){var b=(a||"").split(o);for(var c=0,d=this.length;c<d;c++){var e=this[c];if(e.nodeType===1)if(!e.className)e.className=a;else{var g=" "+e.className+" ",h=e.className;for(var i=0,j=b.length;i<j;i++)g.indexOf(" "+b[i]+" ")<0&&(h+=" "+b[i]);e.className=f.trim(h)}}}return this},removeClass:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.removeClass(a.call(this,b,c.attr("class")))});if(a&&typeof a=="string"||a===b){var c=(a||"").split(o);for(var d=0,e=this.length;d<e;d++){var g=this[d];if(g.nodeType===1&&g.className)if(a){var h=(" "+g.className+" ").replace(n," ");for(var i=0,j=c.length;i<j;i++)h=h.replace(" "+c[i]+" "," ");g.className=f.trim(h)}else g.className=""}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";if(f.isFunction(a))return this.each(function(c){var d=f(this);d.toggleClass(a.call(this,c,d.attr("class"),b),b)});return this.each(function(){if(c==="string"){var e,g=0,h=f(this),i=b,j=a.split(o);while(e=j[g++])i=d?i:!h.hasClass(e),h[i?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&f._data(this,"__className__",this.className),this.className=this.className||a===!1?"":f._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ";for(var c=0,d=this.length;c<d;c++)if((" "+this[c].className+" ").replace(n," ").indexOf(b)>-1)return!0;return!1},val:function(a){var c,d,e=this[0];if(!arguments.length){if(e){c=f.valHooks[e.nodeName.toLowerCase()]||f.valHooks[e.type];if(c&&"get"in c&&(d=c.get(e,"value"))!==b)return d;return(e.value||"").replace(p,"")}return b}var g=f.isFunction(a);return this.each(function(d){var e=f(this),h;if(this.nodeType===1){g?h=a.call(this,d,e.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c=a.selectedIndex,d=[],e=a.options,g=a.type==="select-one";if(c<0)return null;for(var h=g?c:0,i=g?c+1:e.length;h<i;h++){var j=e[h];if(j.selected&&(f.support.optDisabled?!j.disabled:j.getAttribute("disabled")===null)&&(!j.parentNode.disabled||!f.nodeName(j.parentNode,"optgroup"))){b=f(j).val();if(g)return b;d.push(b)}}if(g&&!d.length&&e.length)return f(e[c]).val();return d},set:function(a,b){var c=f.makeArray(b);f(a).find("option").each(function(){this.selected=f.inArray(f(this).val(),c)>=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attrFix:{tabindex:"tabIndex"},attr:function(a,c,d,e){var g=a.nodeType;if(!a||g===3||g===8||g===2)return b;if(e&&c in f.attrFn)return f(a)[c](d);if(!("getAttribute"in a))return f.prop(a,c,d);var h,i,j=g!==1||!f.isXMLDoc(a);c=j&&f.attrFix[c]||c,i=f.attrHooks[c],i||(!t.test(c)||typeof d!="boolean"&&d!==b&&d.toLowerCase()!==c.toLowerCase()?v&&(f.nodeName(a,"form")||u.test(c))&&(i=v):i=w);if(d!==b){if(d===null){f.removeAttr(a,c);return b}if(i&&"set"in i&&j&&(h=i.set(a,d,c))!==b)return h;a.setAttribute(c,""+d);return d}if(i&&"get"in i&&j)return i.get(a,c);h=a.getAttribute(c);return h===null?b:h},removeAttr:function(a,b){var c;a.nodeType===1&&(b=f.attrFix[b]||b,f.support.getSetAttribute?a.removeAttribute(b):(f.attr(a,b,""),a.removeAttributeNode(a.getAttributeNode(b))),t.test(b)&&(c=f.propFix[b]||b)in a&&(a[c]=!1))},attrHooks:{type:{set:function(a,b){if(q.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.value;a.setAttribute("type",b),c&&(a.value=c);return b}}},tabIndex:{get:function(a){var c=a.getAttributeNode("tabIndex");return c&&c.specified?parseInt(c.value,10):r.test(a.nodeName)||s.test(a.nodeName)&&a.href?0:b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e=a.nodeType;if(!a||e===3||e===8||e===2)return b;var g,h,i=e!==1||!f.isXMLDoc(a);c=i&&f.propFix[c]||c,h=f.propHooks[c];return d!==b?h&&"set"in h&&(g=h.set(a,d,c))!==b?g:a[c]=d:h&&"get"in h&&(g=h.get(a,c))!==b?g:a[c]},propHooks:{}}),w={get:function(a,c){return a[f.propFix[c]||c]?c.toLowerCase():b},set:function(a,b,c){var d;b===!1?f.removeAttr(a,c):(d=f.propFix[c]||c,d in a&&(a[d]=b),a.setAttribute(c,c.toLowerCase()));return c}},f.attrHooks.value={get:function(a,b){if(v&&f.nodeName(a,"button"))return v.get(a,b);return a.value},set:function(a,b,c){if(v&&f.nodeName(a,"button"))return v.set(a,b,c);a.value=b}},f.support.getSetAttribute||(f.attrFix=f.propFix,v=f.attrHooks.name=f.valHooks.button={get:function(a,c){var d;d=a.getAttributeNode(c);return d&&d.nodeValue!==""?d.nodeValue:b},set:function(a,b,c){var d=a.getAttributeNode(c);if(d){d.nodeValue=b;return b}}},f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})})),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}})),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var x=Object.prototype.hasOwnProperty,y=/\.(.*)$/,z=/^(?:textarea|input|select)$/i,A=/\./g,B=/ /g,C=/[^\w\s.|`]/g,D=function(a){return a.replace(C,"\\$&")};f.event={add:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){if(d===!1)d=E;else if(!d)return;var g,h;d.handler&&(g=d,d=g.handler),d.guid||(d.guid=f.guid++);var i=f._data(a);if(!i)return;var j=i.events,k=i.handle;j||(i.events=j={}),k||(i.handle=k=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.handle.apply(k.elem,arguments):b}),k.elem=a,c=c.split(" ");var l,m=0,n;while(l=c[m++]){h=g?f.extend({},g):{handler:d,data:e},l.indexOf(".")>-1?(n=l.split("."),l=n.shift(),h.namespace=n.slice(0).sort().join(".")):(n=[],h.namespace=""),h.type=l,h.guid||(h.guid=d.guid);var o=j[l],p=f.event.special[l]||{};if(!o){o=j[l]=[];if(!p.setup||p.setup.call(a,e,n,k)===!1)a.addEventListener?a.addEventListener(l,k,!1):a.attachEvent&&a.attachEvent("on"+l,k)}p.add&&(p.add.call(a,h),h.handler.guid||(h.handler.guid=d.guid)),o.push(h),f.event.global[l]=!0}a=null}},global:{},remove:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){d===!1&&(d=E);var g,h,i,j,k=0,l,m,n,o,p,q,r,s=f.hasData(a)&&f._data(a),t=s&&s.events;if(!s||!t)return;c&&c.type&&(d=c.handler,c=c.type);if(!c||typeof c=="string"&&c.charAt(0)==="."){c=c||"";for(h in t)f.event.remove(a,h+c);return}c=c.split(" ");while(h=c[k++]){r=h,q=null,l=h.indexOf(".")<0,m=[],l||(m=h.split("."),h=m.shift(),n=new RegExp("(^|\\.)"+f.map(m.slice(0).sort(),D).join("\\.(?:.*\\.)?")+"(\\.|$)")),p=t[h];if(!p)continue;if(!d){for(j=0;j<p.length;j++){q=p[j];if(l||n.test(q.namespace))f.event.remove(a,r,q.handler,j),p.splice(j--,1)}continue}o=f.event.special[h]||{};for(j=e||0;j<p.length;j++){q=p[j];if(d.guid===q.guid){if(l||n.test(q.namespace))e==null&&p.splice(j--,1),o.remove&&o.remove.call(a,q);if(e!=null)break}}if(p.length===0||e!=null&&p.length===1)(!o.teardown||o.teardown.call(a,m)===!1)&&f.removeEvent(a,h,s.handle),g=null,delete t[h]}if(f.isEmptyObject(t)){var u=s.handle;u&&(u.elem=null),delete s.events,delete s.handle,f.isEmptyObject(s)&&f.removeData(a,b,!0)}}},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,e,g){var h=c.type||c,i=[],j;h.indexOf("!")>=0&&(h=h.slice(0,-1),j=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if(!!e&&!f.event.customEvent[h]||!!f.event.global[h]){c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.exclusive=j,c.namespace=i.join("."),c.namespace_re=new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)");if(g||!e)c.preventDefault(),c.stopPropagation();if(!e){f.each(f.cache,function(){var a=f.expando,b=this[a];b&&b.events&&b.events[h]&&f.event.trigger(c,d,b.handle.elem
+)});return}if(e.nodeType===3||e.nodeType===8)return;c.result=b,c.target=e,d=d?f.makeArray(d):[],d.unshift(c);var k=e,l=h.indexOf(":")<0?"on"+h:"";do{var m=f._data(k,"handle");c.currentTarget=k,m&&m.apply(k,d),l&&f.acceptData(k)&&k[l]&&k[l].apply(k,d)===!1&&(c.result=!1,c.preventDefault()),k=k.parentNode||k.ownerDocument||k===c.target.ownerDocument&&a}while(k&&!c.isPropagationStopped());if(!c.isDefaultPrevented()){var n,o=f.event.special[h]||{};if((!o._default||o._default.call(e.ownerDocument,c)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)){try{l&&e[h]&&(n=e[l],n&&(e[l]=null),f.event.triggered=h,e[h]())}catch(p){}n&&(e[l]=n),f.event.triggered=b}}return c.result}},handle:function(c){c=f.event.fix(c||a.event);var d=((f._data(this,"events")||{})[c.type]||[]).slice(0),e=!c.exclusive&&!c.namespace,g=Array.prototype.slice.call(arguments,0);g[0]=c,c.currentTarget=this;for(var h=0,i=d.length;h<i;h++){var j=d[h];if(e||c.namespace_re.test(j.namespace)){c.handler=j.handler,c.data=j.data,c.handleObj=j;var k=j.handler.apply(this,g);k!==b&&(c.result=k,k===!1&&(c.preventDefault(),c.stopPropagation()));if(c.isImmediatePropagationStopped())break}}return c.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(a){if(a[f.expando])return a;var d=a;a=f.Event(d);for(var e=this.props.length,g;e;)g=this.props[--e],a[g]=d[g];a.target||(a.target=a.srcElement||c),a.target.nodeType===3&&(a.target=a.target.parentNode),!a.relatedTarget&&a.fromElement&&(a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement);if(a.pageX==null&&a.clientX!=null){var h=a.target.ownerDocument||c,i=h.documentElement,j=h.body;a.pageX=a.clientX+(i&&i.scrollLeft||j&&j.scrollLeft||0)-(i&&i.clientLeft||j&&j.clientLeft||0),a.pageY=a.clientY+(i&&i.scrollTop||j&&j.scrollTop||0)-(i&&i.clientTop||j&&j.clientTop||0)}a.which==null&&(a.charCode!=null||a.keyCode!=null)&&(a.which=a.charCode!=null?a.charCode:a.keyCode),!a.metaKey&&a.ctrlKey&&(a.metaKey=a.ctrlKey),!a.which&&a.button!==b&&(a.which=a.button&1?1:a.button&2?3:a.button&4?2:0);return a},guid:1e8,proxy:f.proxy,special:{ready:{setup:f.bindReady,teardown:f.noop},live:{add:function(a){f.event.add(this,O(a.origType,a.selector),f.extend({},a,{handler:N,guid:a.handler.guid}))},remove:function(a){f.event.remove(this,O(a.origType,a.selector),a)}},beforeunload:{setup:function(a,b,c){f.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}}},f.removeEvent=c.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){a.detachEvent&&a.detachEvent("on"+b,c)},f.Event=function(a,b){if(!this.preventDefault)return new f.Event(a,b);a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?F:E):this.type=a,b&&f.extend(this,b),this.timeStamp=f.now(),this[f.expando]=!0},f.Event.prototype={preventDefault:function(){this.isDefaultPrevented=F;var a=this.originalEvent;!a||(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){this.isPropagationStopped=F;var a=this.originalEvent;!a||(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=F,this.stopPropagation()},isDefaultPrevented:E,isPropagationStopped:E,isImmediatePropagationStopped:E};var G=function(a){var b=a.relatedTarget;a.type=a.data;try{if(b&&b!==c&&!b.parentNode)return;while(b&&b!==this)b=b.parentNode;b!==this&&f.event.handle.apply(this,arguments)}catch(d){}},H=function(a){a.type=a.data,f.event.handle.apply(this,arguments)};f.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){f.event.special[a]={setup:function(c){f.event.add(this,b,c&&c.selector?H:G,a)},teardown:function(a){f.event.remove(this,b,a&&a.selector?H:G)}}}),f.support.submitBubbles||(f.event.special.submit={setup:function(a,b){if(!f.nodeName(this,"form"))f.event.add(this,"click.specialSubmit",function(a){var b=a.target,c=b.type;(c==="submit"||c==="image")&&f(b).closest("form").length&&L("submit",this,arguments)}),f.event.add(this,"keypress.specialSubmit",function(a){var b=a.target,c=b.type;(c==="text"||c==="password")&&f(b).closest("form").length&&a.keyCode===13&&L("submit",this,arguments)});else return!1},teardown:function(a){f.event.remove(this,".specialSubmit")}});if(!f.support.changeBubbles){var I,J=function(a){var b=a.type,c=a.value;b==="radio"||b==="checkbox"?c=a.checked:b==="select-multiple"?c=a.selectedIndex>-1?f.map(a.options,function(a){return a.selected}).join("-"):"":f.nodeName(a,"select")&&(c=a.selectedIndex);return c},K=function(c){var d=c.target,e,g;if(!!z.test(d.nodeName)&&!d.readOnly){e=f._data(d,"_change_data"),g=J(d),(c.type!=="focusout"||d.type!=="radio")&&f._data(d,"_change_data",g);if(e===b||g===e)return;if(e!=null||g)c.type="change",c.liveFired=b,f.event.trigger(c,arguments[1],d)}};f.event.special.change={filters:{focusout:K,beforedeactivate:K,click:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(c==="radio"||c==="checkbox"||f.nodeName(b,"select"))&&K.call(this,a)},keydown:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(a.keyCode===13&&!f.nodeName(b,"textarea")||a.keyCode===32&&(c==="checkbox"||c==="radio")||c==="select-multiple")&&K.call(this,a)},beforeactivate:function(a){var b=a.target;f._data(b,"_change_data",J(b))}},setup:function(a,b){if(this.type==="file")return!1;for(var c in I)f.event.add(this,c+".specialChange",I[c]);return z.test(this.nodeName)},teardown:function(a){f.event.remove(this,".specialChange");return z.test(this.nodeName)}},I=f.event.special.change.filters,I.focus=I.beforeactivate}f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){function e(a){var c=f.event.fix(a);c.type=b,c.originalEvent={},f.event.trigger(c,null,c.target),c.isDefaultPrevented()&&a.preventDefault()}var d=0;f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.each(["bind","one"],function(a,c){f.fn[c]=function(a,d,e){var g;if(typeof a=="object"){for(var h in a)this[c](h,d,a[h],e);return this}if(arguments.length===2||d===!1)e=d,d=b;c==="one"?(g=function(a){f(this).unbind(a,g);return e.apply(this,arguments)},g.guid=e.guid||f.guid++):g=e;if(a==="unload"&&c!=="one")this.one(a,d,e);else for(var i=0,j=this.length;i<j;i++)f.event.add(this[i],a,g,d);return this}}),f.fn.extend({unbind:function(a,b){if(typeof a=="object"&&!a.preventDefault)for(var c in a)this.unbind(c,a[c]);else for(var d=0,e=this.length;d<e;d++)f.event.remove(this[d],a,b);return this},delegate:function(a,b,c,d){return this.live(b,c,d,a)},undelegate:function(a,b,c){return arguments.length===0?this.unbind("live"):this.die(b,null,c,a)},trigger:function(a,b){return this.each(function(){f.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return f.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=arguments,c=a.guid||f.guid++,d=0,e=function(c){var e=(f.data(this,"lastToggle"+a.guid)||0)%d;f.data(this,"lastToggle"+a.guid,e+1),c.preventDefault();return b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});var M={focus:"focusin",blur:"focusout",mouseenter:"mouseover",mouseleave:"mouseout"};f.each(["live","die"],function(a,c){f.fn[c]=function(a,d,e,g){var h,i=0,j,k,l,m=g||this.selector,n=g?this:f(this.context);if(typeof a=="object"&&!a.preventDefault){for(var o in a)n[c](o,d,a[o],m);return this}if(c==="die"&&!a&&g&&g.charAt(0)==="."){n.unbind(g);return this}if(d===!1||f.isFunction(d))e=d||E,d=b;a=(a||"").split(" ");while((h=a[i++])!=null){j=y.exec(h),k="",j&&(k=j[0],h=h.replace(y,""));if(h==="hover"){a.push("mouseenter"+k,"mouseleave"+k);continue}l=h,M[h]?(a.push(M[h]+k),h=h+k):h=(M[h]||h)+k;if(c==="live")for(var p=0,q=n.length;p<q;p++)f.event.add(n[p],"live."+O(h,m),{data:d,selector:m,handler:e,origType:h,origHandler:e,preType:l});else n.unbind("live."+O(h,m),e)}return this}}),f.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error".split(" "),function(a,b){f.fn[b]=function(a,c){c==null&&(c=a,a=null);return arguments.length>0?this.bind(b,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0)}),function(){function u(a,b,c,d,e,f){for(var g=0,h=d.length;g<h;g++){var i=d[g];if(i){var j=!1;i=i[a];while(i){if(i.sizcache===c){j=d[i.sizset];break}if(i.nodeType===1){f||(i.sizcache=c,i.sizset=g);if(typeof b!="string"){if(i===b){j=!0;break}}else if(k.filter(b,[i]).length>0){j=i;break}}i=i[a]}d[g]=j}}}function t(a,b,c,d,e,f){for(var g=0,h=d.length;g<h;g++){var i=d[g];if(i){var j=!1;i=i[a];while(i){if(i.sizcache===c){j=d[i.sizset];break}i.nodeType===1&&!f&&(i.sizcache=c,i.sizset=g);if(i.nodeName.toLowerCase()===b){j=i;break}i=i[a]}d[g]=j}}}var a=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d=0,e=Object.prototype.toString,g=!1,h=!0,i=/\\/g,j=/\W/;[0,0].sort(function(){h=!1;return 0});var k=function(b,d,f,g){f=f||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return f;var i,j,n,o,q,r,s,t,u=!0,w=k.isXML(d),x=[],y=b;do{a.exec(""),i=a.exec(y);if(i){y=i[3],x.push(i[1]);if(i[2]){o=i[3];break}}}while(i);if(x.length>1&&m.exec(b))if(x.length===2&&l.relative[x[0]])j=v(x[0]+x[1],d);else{j=l.relative[x[0]]?[d]:k(x.shift(),d);while(x.length)b=x.shift(),l.relative[b]&&(b+=x.shift()),j=v(b,j)}else{!g&&x.length>1&&d.nodeType===9&&!w&&l.match.ID.test(x[0])&&!l.match.ID.test(x[x.length-1])&&(q=k.find(x.shift(),d,w),d=q.expr?k.filter(q.expr,q.set)[0]:q.set[0]);if(d){q=g?{expr:x.pop(),set:p(g)}:k.find(x.pop(),x.length===1&&(x[0]==="~"||x[0]==="+")&&d.parentNode?d.parentNode:d,w),j=q.expr?k.filter(q.expr,q.set):q.set,x.length>0?n=p(j):u=!1;while(x.length)r=x.pop(),s=r,l.relative[r]?s=x.pop():r="",s==null&&(s=d),l.relative[r](n,s,w)}else n=x=[]}n||(n=j),n||k.error(r||b);if(e.call(n)==="[object Array]")if(!u)f.push.apply(f,n);else if(d&&d.nodeType===1)for(t=0;n[t]!=null;t++)n[t]&&(n[t]===!0||n[t].nodeType===1&&k.contains(d,n[t]))&&f.push(j[t]);else for(t=0;n[t]!=null;t++)n[t]&&n[t].nodeType===1&&f.push(j[t]);else p(n,f);o&&(k(o,h,f,g),k.uniqueSort(f));return f};k.uniqueSort=function(a){if(r){g=h,a.sort(r);if(g)for(var b=1;b<a.length;b++)a[b]===a[b-1]&&a.splice(b--,1)}return a},k.matches=function(a,b){return k(a,null,null,b)},k.matchesSelector=function(a,b){return k(b,null,null,[a]).length>0},k.find=function(a,b,c){var d;if(!a)return[];for(var e=0,f=l.order.length;e<f;e++){var g,h=l.order[e];if(g=l.leftMatch[h].exec(a)){var j=g[1];g.splice(1,1);if(j.substr(j.length-1)!=="\\"){g[1]=(g[1]||"").replace(i,""),d=l.find[h](g,b,c);if(d!=null){a=a.replace(l.match[h],"");break}}}}d||(d=typeof b.getElementsByTagName!="undefined"?b.getElementsByTagName("*"):[]);return{set:d,expr:a}},k.filter=function(a,c,d,e){var f,g,h=a,i=[],j=c,m=c&&c[0]&&k.isXML(c[0]);while(a&&c.length){for(var n in l.filter)if((f=l.leftMatch[n].exec(a))!=null&&f[2]){var o,p,q=l.filter[n],r=f[1];g=!1,f.splice(1,1);if(r.substr(r.length-1)==="\\")continue;j===i&&(i=[]);if(l.preFilter[n]){f=l.preFilter[n](f,j,d,i,e,m);if(!f)g=o=!0;else if(f===!0)continue}if(f)for(var s=0;(p=j[s])!=null;s++)if(p){o=q(p,f,s,j);var t=e^!!o;d&&o!=null?t?g=!0:j[s]=!1:t&&(i.push(p),g=!0)}if(o!==b){d||(j=i),a=a.replace(l.match[n],"");if(!g)return[];break}}if(a===h)if(g==null)k.error(a);else break;h=a}return j},k.error=function(a){throw"Syntax error, unrecognized expression: "+a};var l=k.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(a){return a.getAttribute("href")},type:function(a){return a.getAttribute("type")}},relative:{"+":function(a,b){var c=typeof b=="string",d=c&&!j.test(b),e=c&&!d;d&&(b=b.toLowerCase());for(var f=0,g=a.length,h;f<g;f++)if(h=a[f]){while((h=h.previousSibling)&&h.nodeType!==1);a[f]=e||h&&h.nodeName.toLowerCase()===b?h||!1:h===b}e&&k.filter(b,a,!0)},">":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!j.test(b)){b=b.toLowerCase();for(;e<f;e++){c=a[e];if(c){var g=c.parentNode;a[e]=g.nodeName.toLowerCase()===b?g:!1}}}else{for(;e<f;e++)c=a[e],c&&(a[e]=d?c.parentNode:c.parentNode===b);d&&k.filter(b,a,!0)}},"":function(a,b,c){var e,f=d++,g=u;typeof b=="string"&&!j.test(b)&&(b=b.toLowerCase(),e=b,g=t),g("parentNode",b,f,a,e,c)},"~":function(a,b,c){var e,f=d++,g=u;typeof b=="string"&&!j.test(b)&&(b=b.toLowerCase(),e=b,g=t),g("previousSibling",b,f,a,e,c)}},find:{ID:function(a,b,c){if(typeof b.getElementById!="undefined"&&!c){var d=b.getElementById(a[1]);return d&&d.parentNode?[d]:[]}},NAME:function(a,b){if(typeof b.getElementsByName!="undefined"){var c=[],d=b.getElementsByName(a[1]);for(var e=0,f=d.length;e<f;e++)d[e].getAttribute("name")===a[1]&&c.push(d[e]);return c.length===0?null:c}},TAG:function(a,b){if(typeof b.getElementsByTagName!="undefined")return b.getElementsByTagName(a[1])}},preFilter:{CLASS:function(a,b,c,d,e,f){a=" "+a[1].replace(i,"")+" ";if(f)return a;for(var g=0,h;(h=b[g])!=null;g++)h&&(e^(h.className&&(" "+h.className+" ").replace(/[\t\n\r]/g," ").indexOf(a)>=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(i,"")},TAG:function(a,b){return a[1].replace(i,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||k.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&k.error(a[0]);a[0]=d++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(i,"");!f&&l.attrMap[g]&&(a[1]=l.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(i,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=k(b[3],null,null,c);else{var g=k.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(l.match.POS.test(b[0])||l.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!k(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return b<c[3]-0},gt:function(a,b,c){return b>c[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=l.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||k.getText([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h<i;h++)if(g[h]===a)return!1;return!0}k.error(e)},CHILD:function(a,b){var c=b[1],d=a;switch(c){case"only":case"first":while(d=d.previousSibling)if(d.nodeType===1)return!1;if(c==="first")return!0;d=a;case"last":while(d=d.nextSibling)if(d.nodeType===1)return!1;return!0;case"nth":var e=b[2],f=b[3];if(e===1&&f===0)return!0;var g=b[0],h=a.parentNode;if(h&&(h.sizcache!==g||!a.nodeIndex)){var i=0;for(d=h.firstChild;d;d=d.nextSibling)d.nodeType===1&&(d.nodeIndex=++i);h.sizcache=g}var j=a.nodeIndex-f;return e===0?j===0:j%e===0&&j/e>=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=l.attrHandle[c]?l.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=l.setFilters[e];if(f)return f(a,c,b,d)}}},m=l.match.POS,n=function(a,b){return"\\"+(b-0+1)};for(var o in l.match)l.match[o]=new RegExp(l.match[o].source+/(?![^\[]*\])(?![^\(]*\))/.source),l.leftMatch[o]=new RegExp(/(^(?:.|\r|\n)*?)/.source+l.match[o].source.replace(/\\(\d+)/g,n));var p=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(q){p=function(a,b){var c=0,d=b||[];if(e.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var f=a.length;c<f;c++)d.push(a[c]);else for(;a[c];c++)d.push(a[c]);return d}}var r,s;c.documentElement.compareDocumentPosition?r=function(a,b){if(a===b){g=!0;return 0}if(!a.compareDocumentPosition||!b.compareDocumentPosition)return a.compareDocumentPosition?-1:1;return a.compareDocumentPosition(b)&4?-1:1}:(r=function(a,b){if(a===b){g=!0;return 0}if(a.sourceIndex&&b.sourceIndex)return a.sourceIndex-b.sourceIndex;var c,d,e=[],f=[],h=a.parentNode,i=b.parentNode,j=h;if(h===i)return s(a,b);if(!h)return-1;if(!i)return 1;while(j)e.unshift(j),j=j.parentNode;j=i;while(j)f.unshift(j),j=j.parentNode;c=e.length,d=f.length;for(var k=0;k<c&&k<d;k++)if(e[k]!==f[k])return s(e[k],f[k]);return k===c?s(a,f[k],-1):s(e[k],b,1)},s=function(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}),k.getText=function(a){var b="",c;for(var d=0;a[d];d++)c=a[d],c.nodeType===3||c.nodeType===4?b+=c.nodeValue:c.nodeType!==8&&(b+=k.getText(c.childNodes));return b},function(){var a=c.createElement("div"),d="script"+(new Date).getTime(),e=c.documentElement;a.innerHTML="<a name='"+d+"'/>",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(l.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},l.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(l.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(l.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=k,b=c.createElement("div"),d="__sizzle__";b.innerHTML="<p class='TEST'></p>";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){k=function(b,e,f,g){e=e||c;if(!g&&!k.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return p(e.getElementsByTagName(b),f);if(h[2]&&l.find.CLASS&&e.getElementsByClassName)return p(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return p([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return p([],f);if(i.id===h[3])return p([i],f)}try{return p(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var m=e,n=e.getAttribute("id"),o=n||d,q=e.parentNode,r=/^\s*[+~]/.test(b);n?o=o.replace(/'/g,"\\$&"):e.setAttribute("id",o),r&&q&&(e=e.parentNode);try{if(!r||q)return p(e.querySelectorAll("[id='"+o+"'] "+b),f)}catch(s){}finally{n||m.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)k[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}k.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!k.isXML(a))try{if(e||!l.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return k(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="<div class='test e'></div><div class='test'></div>";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;l.order.splice(1,0,"CLASS"),l.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?k.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?k.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:k.contains=function(){return!1},k.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var v=function(a,b){var c,d=[],e="",f=b.nodeType?[b]:b;while(c=l.match.PSEUDO.exec(a))e+=c[0],a=a.replace(l.match.PSEUDO,"");a=l.relative[a]?a+"*":a;for(var g=0,h=f.length;g<h;g++)k(a,f[g],d);return k.filter(e,d)};f.find=k,f.expr=k.selectors,f.expr[":"]=f.expr.filters,f.unique=k.uniqueSort,f.text=k.getText,f.isXMLDoc=k.isXML,f.contains=k.contains}();var P=/Until$/,Q=/^(?:parents|prevUntil|prevAll)/,R=/,/,S=/^.[^:#\[\.,]*$/,T=Array.prototype.slice,U=f.expr.match.POS,V={children:!0,contents:!0,next:!0,prev:!0};f.fn.extend({find:function(a){var b=this,c,d;if(typeof a!="string")return f(a).filter(function(){for(c=0,d=b.length;c<d;c++)if(f.contains(b[c],this))return!0});var e=this.pushStack("","find",a),g,h,i;for(c=0,d=this.length;c<d;c++){g=e.length,f.find(a,this[c],e);if(c>0)for(h=g;h<e.length;h++)for(i=0;i<g;i++)if(e[i]===e[h]){e.splice(h--,1);break}}return e},has:function(a){var b=f(a);return this.filter(function(){for(var a=0,c=b.length;a<c;a++)if(f.contains(this,b[a]))return!0})},not:function(a){return this.pushStack(X(this,a,!1),"not",a)},filter:function(a){return this.pushStack(X(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h,i,j={},k=1;if(g&&a.length){for(d=0,e=a.length;d<e;d++)i=a[d],j[i]||(j[i]=U.test(i)?f(i,b||this.context):i);while(g&&g.ownerDocument&&g!==b){for(i in j)h=j[i],(h.jquery?h.index(g)>-1:f(g).is(h))&&c.push({selector:i,elem:g,level:k});g=g.parentNode,k++}}return c}var l=U.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d<e;d++){g=this[d];while(g){if(l?l.index(g)>-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a||typeof a=="string")return f.inArray(this[0],a?f(a):this.parent().children());return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(W(c[0])||W(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c),g=T.call(arguments);P.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!V[a]?f.unique(e):e,(this.length>1||R.test(d))&&Q.test(a)&&(e=e.reverse());return this.pushStack(e,a,g.join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var Y=/ jQuery\d+="(?:\d+|null)"/g,Z=/^\s+/,$=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,_=/<([\w:]+)/,ba=/<tbody/i,bb=/<|&#?\w+;/,bc=/<(?:script|object|embed|option|style)/i,bd=/checked\s*(?:[^=]|=\s*.checked.)/i,be=/\/(java|ecma)script/i,bf=/^\s*<!(?:\[CDATA\[|\-\-)/,bg={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]};bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div<div>","</div>"]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){f(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f(arguments[0]).toArray());return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Y,""):null;if(typeof a=="string"&&!bc.test(a)&&(f.support.leadingWhitespace||!Z.test(a))&&!bg[(_.exec(a)||["",""])[1].toLowerCase()]){a=a.replace($,"<$1></$2>");try{for(var c=0,d=this.length;c<d;c++)this[c].nodeType===1&&(f.cleanData(this[c].getElementsByTagName("*")),this[c].innerHTML=a)}catch(e){this.empty().append(a)}}else f.isFunction(a)?this.each(function(b){var c=f(this);c.html(a.call(this,b,c.html()))}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&this[0].parentNode){if(f.isFunction(a))return this.each(function(b){var c=f(this),d=c.html();c.replaceWith(a.call(this,b,d))});typeof a!="string"&&(a=f(a).detach());return this.each(function(){var b=this.nextSibling,c=this.parentNode;f(this).remove(),b?f(b).before(a):f(c).append(a)})}return this.length?this.pushStack(f(f.isFunction(a)?a():a),"replaceWith",a):this},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){var e,g,h,i,j=a[0],k=[];if(!f.support.checkClone&&arguments.length===3&&typeof j=="string"&&bd.test(j))return this.each(function(){f(this).domManip(a,c,d,!0)});if(f.isFunction(j))return this.each(function(e){var g=f(this);a[0]=j.call(this,e,c?g.html():b),g.domManip(a,c,d)});if(this[0]){i=j&&j.parentNode,f.support.parentNode&&i&&i.nodeType===11&&i.childNodes.length===this.length?e={fragment:i}:e=f.buildFragment(a,this,k),h=e.fragment,h.childNodes.length===1?g=h=h.firstChild:g=h.firstChild;if(g){c=c&&f.nodeName(g,"tr");for(var l=0,m=this.length,n=m-1;l<m;l++)d.call(c?bh(this[l],g):this[l],e.cacheable||m>1&&l<n?f.clone(h,!0,!0):h)}k.length&&f.each(k,bn)}return this}}),f.buildFragment=function(a,b,d){var e,g,h,i=b&&b[0]?b[0].ownerDocument||b[0]:c;a.length===1&&typeof a[0]=="string"&&a[0].length<512&&i===c&&a[0].charAt(0)==="<"&&!bc.test(a[0])&&(f.support.checkClone||!bd.test(a[0]))&&(g=!0,h=f.fragments[a[0]],h&&h!==1&&(e=h)),e||(e=i.createDocumentFragment(),f.clean(a,i,e,d)),g&&(f.fragments[a[0]]=h?e:1);return{fragment:e,cacheable:g}},f.fragments={},f.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){f.fn[a]=function(c){var d=[],e=f(c),g=this.length===1&&this[0].parentNode;if(g&&g.nodeType===11&&g.childNodes.length===1&&e.length===1){e[b](this[0]);return this}for(var h=0,i=e.length;h<i;h++){var j=(h>0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d=a.cloneNode(!0),e,g,h;if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bj(a,d),e=bk(a),g=bk(d);for(h=0;e[h];++h)bj(e[h],g[h])}if(b){bi(a,d);if(c){e=bk(a),g=bk(d);for(h=0;e[h];++h)bi(e[h],g[h])}}return d},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||
+b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!bb.test(k))k=b.createTextNode(k);else{k=k.replace($,"<$1></$2>");var l=(_.exec(k)||["",""])[1].toLowerCase(),m=bg[l]||bg._default,n=m[0],o=b.createElement("div");o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=ba.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]==="<table>"&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&Z.test(k)&&o.insertBefore(b.createTextNode(Z.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i<r;i++)bm(k[i]);else bm(k);k.nodeType?h.push(k):h=f.merge(h,k)}if(d){g=function(a){return!a.type||be.test(a.type)};for(j=0;h[j];j++)if(e&&f.nodeName(h[j],"script")&&(!h[j].type||h[j].type.toLowerCase()==="text/javascript"))e.push(h[j].parentNode?h[j].parentNode.removeChild(h[j]):h[j]);else{if(h[j].nodeType===1){var s=f.grep(h[j].getElementsByTagName("script"),g);h.splice.apply(h,[j+1,0].concat(s))}d.appendChild(h[j])}}return h},cleanData:function(a){var b,c,d=f.cache,e=f.expando,g=f.event.special,h=f.support.deleteExpando;for(var i=0,j;(j=a[i])!=null;i++){if(j.nodeName&&f.noData[j.nodeName.toLowerCase()])continue;c=j[f.expando];if(c){b=d[c]&&d[c][e];if(b&&b.events){for(var k in b.events)g[k]?f.event.remove(j,k):f.removeEvent(j,k,b.handle);b.handle&&(b.handle.elem=null)}h?delete j[f.expando]:j.removeAttribute&&j.removeAttribute(f.expando),delete d[c]}}}});var bo=/alpha\([^)]*\)/i,bp=/opacity=([^)]*)/,bq=/-([a-z])/ig,br=/([A-Z]|^ms)/g,bs=/^-?\d+(?:px)?$/i,bt=/^-?\d/,bu=/^[+\-]=/,bv=/[^+\-\.\de]+/g,bw={position:"absolute",visibility:"hidden",display:"block"},bx=["Left","Right"],by=["Top","Bottom"],bz,bA,bB,bC=function(a,b){return b.toUpperCase()};f.fn.css=function(a,c){if(arguments.length===2&&c===b)return this;return f.access(this,a,c,!0,function(a,c,d){return d!==b?f.style(a,c,d):f.css(a,c)})},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bz(a,"opacity","opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{zIndex:!0,fontWeight:!0,opacity:!0,zoom:!0,lineHeight:!0,widows:!0,orphans:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d;if(h==="number"&&isNaN(d)||d==null)return;h==="string"&&bu.test(d)&&(d=+d.replace(bv,"")+parseFloat(f.css(a,c))),h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(bz)return bz(a,c)},swap:function(a,b,c){var d={};for(var e in b)d[e]=a.style[e],a.style[e]=b[e];c.call(a);for(e in b)a.style[e]=d[e]},camelCase:function(a){return a.replace(bq,bC)}}),f.curCSS=f.css,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){var e;if(c){a.offsetWidth!==0?e=bD(a,b,d):f.swap(a,bw,function(){e=bD(a,b,d)});if(e<=0){e=bz(a,b,b),e==="0px"&&bB&&(e=bB(a,b,b));if(e!=null)return e===""||e==="auto"?"0px":e}if(e<0||e==null){e=a.style[b];return e===""||e==="auto"?"0px":e}return typeof e=="string"?e:e+"px"}},set:function(a,b){if(!bs.test(b))return b;b=parseFloat(b);if(b>=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bp.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle;c.zoom=1;var e=f.isNaN(b)?"":"alpha(opacity="+b*100+")",g=d&&d.filter||c.filter||"";c.filter=bo.test(g)?g.replace(bo,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bz(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(bA=function(a,c){var d,e,g;c=c.replace(br,"-$1").toLowerCase();if(!(e=a.ownerDocument.defaultView))return b;if(g=e.getComputedStyle(a,null))d=g.getPropertyValue(c),d===""&&!f.contains(a.ownerDocument.documentElement,a)&&(d=f.style(a,c));return d}),c.documentElement.currentStyle&&(bB=function(a,b){var c,d=a.currentStyle&&a.currentStyle[b],e=a.runtimeStyle&&a.runtimeStyle[b],f=a.style;!bs.test(d)&&bt.test(d)&&(c=f.left,e&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":d||0,d=f.pixelLeft+"px",f.left=c,e&&(a.runtimeStyle.left=e));return d===""?"auto":d}),bz=bA||bB,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bE=/%20/g,bF=/\[\]$/,bG=/\r?\n/g,bH=/#.*$/,bI=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bJ=/^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bK=/^(?:about|app|app\-storage|.+\-extension|file|widget):$/,bL=/^(?:GET|HEAD)$/,bM=/^\/\//,bN=/\?/,bO=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,bP=/^(?:select|textarea)/i,bQ=/\s+/,bR=/([?&])_=[^&]*/,bS=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bT=f.fn.load,bU={},bV={},bW,bX;try{bW=e.href}catch(bY){bW=c.createElement("a"),bW.href="",bW=bW.href}bX=bS.exec(bW.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bT)return bT.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("<div>").append(c.replace(bO,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bP.test(this.nodeName)||bJ.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bG,"\r\n")}}):{name:b.name,value:c.replace(bG,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.bind(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?f.extend(!0,a,f.ajaxSettings,b):(b=a,a=f.extend(!0,f.ajaxSettings,b));for(var c in{context:1,url:1})c in b?a[c]=b[c]:c in f.ajaxSettings&&(a[c]=f.ajaxSettings[c]);return a},ajaxSettings:{url:bW,isLocal:bK.test(bX[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":"*/*"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML}},ajaxPrefilter:bZ(bU),ajaxTransport:bZ(bV),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a?4:0;var o,r,u,w=l?ca(d,v,l):b,x,y;if(a>=200&&a<300||a===304){if(d.ifModified){if(x=v.getResponseHeader("Last-Modified"))f.lastModified[k]=x;if(y=v.getResponseHeader("Etag"))f.etag[k]=y}if(a===304)c="notmodified",o=!0;else try{r=cb(d,w),c="success",o=!0}catch(z){c="parsererror",u=z}}else{u=c;if(!c||a)c="error",a<0&&(a=0)}v.status=a,v.statusText=c,o?h.resolveWith(e,[r,c,v]):h.rejectWith(e,[v,c,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.resolveWith(e,[v,c]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f._Deferred(),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bI.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.done,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bH,"").replace(bM,bX[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bQ),d.crossDomain==null&&(r=bS.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bX[1]&&r[2]==bX[2]&&(r[3]||(r[1]==="http:"?80:443))==(bX[3]||(bX[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),b$(bU,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bL.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bN.test(d.url)?"&":"?")+d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bR,"$1_="+x);d.url=y+(y===d.url?(bN.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", */*; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=b$(bV,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){status<2?w(-1,z):f.error(z)}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)b_(g,a[g],c,e);return d.join("&").replace(bE,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cc=f.now(),cd=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cc++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(cd.test(b.url)||e&&cd.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(cd,l),b.url===j&&(e&&(k=k.replace(cd,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var ce=a.ActiveXObject?function(){for(var a in cg)cg[a](0,1)}:!1,cf=0,cg;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ch()||ci()}:ch,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,ce&&delete cg[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cf,ce&&(cg||(cg={},f(a).unload(ce)),cg[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cj={},ck,cl,cm=/^(?:toggle|show|hide)$/,cn=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,co,cp=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cq,cr=a.webkitRequestAnimationFrame||a.mozRequestAnimationFrame||a.oRequestAnimationFrame;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cu("show",3),a,b,c);for(var g=0,h=this.length;g<h;g++)d=this[g],d.style&&(e=d.style.display,!f._data(d,"olddisplay")&&e==="none"&&(e=d.style.display=""),e===""&&f.css(d,"display")==="none"&&f._data(d,"olddisplay",cv(d.nodeName)));for(g=0;g<h;g++){d=this[g];if(d.style){e=d.style.display;if(e===""||e==="none")d.style.display=f._data(d,"olddisplay")||""}}return this},hide:function(a,b,c){if(a||a===0)return this.animate(cu("hide",3),a,b,c);for(var d=0,e=this.length;d<e;d++)if(this[d].style){var g=f.css(this[d],"display");g!=="none"&&!f._data(this[d],"olddisplay")&&f._data(this[d],"olddisplay",g)}for(d=0;d<e;d++)this[d].style&&(this[d].style.display="none");return this},_toggle:f.fn.toggle,toggle:function(a,b,c){var d=typeof a=="boolean";f.isFunction(a)&&f.isFunction(b)?this._toggle.apply(this,arguments):a==null||d?this.each(function(){var b=d?a:f(this).is(":hidden");f(this)[b?"show":"hide"]()}):this.animate(cu("toggle",3),a,b,c);return this},fadeTo:function(a,b,c,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=f.speed(b,c,d);if(f.isEmptyObject(a))return this.each(e.complete,[!1]);a=f.extend({},a);return this[e.queue===!1?"each":"queue"](function(){e.queue===!1&&f._mark(this);var b=f.extend({},e),c=this.nodeType===1,d=c&&f(this).is(":hidden"),g,h,i,j,k,l,m,n,o;b.animatedProperties={};for(i in a){g=f.camelCase(i),i!==g&&(a[g]=a[i],delete a[i]),h=a[g],f.isArray(h)?(b.animatedProperties[g]=h[1],h=a[g]=h[0]):b.animatedProperties[g]=b.specialEasing&&b.specialEasing[g]||b.easing||"swing";if(h==="hide"&&d||h==="show"&&!d)return b.complete.call(this);c&&(g==="height"||g==="width")&&(b.overflow=[this.style.overflow,this.style.overflowX,this.style.overflowY],f.css(this,"display")==="inline"&&f.css(this,"float")==="none"&&(f.support.inlineBlockNeedsLayout?(j=cv(this.nodeName),j==="inline"?this.style.display="inline-block":(this.style.display="inline",this.style.zoom=1)):this.style.display="inline-block"))}b.overflow!=null&&(this.style.overflow="hidden");for(i in a)k=new f.fx(this,b,i),h=a[i],cm.test(h)?k[h==="toggle"?d?"show":"hide":h]():(l=cn.exec(h),m=k.cur(),l?(n=parseFloat(l[2]),o=l[3]||(f.cssNumber[i]?"":"px"),o!=="px"&&(f.style(this,i,(n||1)+o),m=(n||1)/k.cur()*m,f.style(this,i,m+o)),l[1]&&(n=(l[1]==="-="?-1:1)*n+m),k.custom(m,n,o)):k.custom(m,h,""));return!0})},stop:function(a,b){a&&this.queue([]),this.each(function(){var a=f.timers,c=a.length;b||f._unmark(!0,this);while(c--)a[c].elem===this&&(b&&a[c](!0),a.splice(c,1))}),b||this.dequeue();return this}}),f.each({slideDown:cu("show",1),slideUp:cu("hide",1),slideToggle:cu("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){f.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),f.extend({speed:function(a,b,c){var d=a&&typeof a=="object"?f.extend({},a):{complete:c||!c&&b||f.isFunction(a)&&a,duration:a,easing:c&&b||b&&!f.isFunction(b)&&b};d.duration=f.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in f.fx.speeds?f.fx.speeds[d.duration]:f.fx.speeds._default,d.old=d.complete,d.complete=function(a){d.queue!==!1?f.dequeue(this):a!==!1&&f._unmark(this),f.isFunction(d.old)&&d.old.call(this)};return d},easing:{linear:function(a,b,c,d){return c+d*a},swing:function(a,b,c,d){return(-Math.cos(a*Math.PI)/2+.5)*d+c}},timers:[],fx:function(a,b,c){this.options=b,this.elem=a,this.prop=c,b.orig=b.orig||{}}}),f.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this),(f.fx.step[this.prop]||f.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a,b=f.css(this.elem,this.prop);return isNaN(a=parseFloat(b))?!b||b==="auto"?0:b:a},custom:function(a,b,c){function h(a){return d.step(a)}var d=this,e=f.fx,g;this.startTime=cq||cs(),this.start=a,this.end=b,this.unit=c||this.unit||(f.cssNumber[this.prop]?"":"px"),this.now=this.start,this.pos=this.state=0,h.elem=this.elem,h()&&f.timers.push(h)&&!co&&(cr?(co=1,g=function(){co&&(cr(g),e.tick())},cr(g)):co=setInterval(e.tick,e.interval))},show:function(){this.options.orig[this.prop]=f.style(this.elem,this.prop),this.options.show=!0,this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur()),f(this.elem).show()},hide:function(){this.options.orig[this.prop]=f.style(this.elem,this.prop),this.options.hide=!0,this.custom(this.cur(),0)},step:function(a){var b=cq||cs(),c=!0,d=this.elem,e=this.options,g,h;if(a||b>=e.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),e.animatedProperties[this.prop]=!0;for(g in e.animatedProperties)e.animatedProperties[g]!==!0&&(c=!1);if(c){e.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){d.style["overflow"+b]=e.overflow[a]}),e.hide&&f(d).hide();if(e.hide||e.show)for(var i in e.animatedProperties)f.style(d,i,e.orig[i]);e.complete.call(d)}return!1}e.duration==Infinity?this.now=b:(h=b-this.startTime,this.state=h/e.duration,this.pos=f.easing[e.animatedProperties[this.prop]](this.state,h,0,1,e.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){for(var a=f.timers,b=0;b<a.length;++b)a[b]()||a.splice(b--,1);a.length||f.fx.stop()},interval:13,stop:function(){clearInterval(co),co=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){f.style(a.elem,"opacity",a.now)},_default:function(a){a.elem.style&&a.elem.style[a.prop]!=null?a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit:a.elem[a.prop]=a.now}}}),f.expr&&f.expr.filters&&(f.expr.filters.animated=function(a){return f.grep(f.timers,function(b){return a===b.elem}).length});var cw=/^t(?:able|d|h)$/i,cx=/^(?:body|html)$/i;"getBoundingClientRect"in c.documentElement?f.fn.offset=function(a){var b=this[0],c;if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);try{c=b.getBoundingClientRect()}catch(d){}var e=b.ownerDocument,g=e.documentElement;if(!c||!f.contains(g,b))return c?{top:c.top,left:c.left}:{top:0,left:0};var h=e.body,i=cy(e),j=g.clientTop||h.clientTop||0,k=g.clientLeft||h.clientLeft||0,l=i.pageYOffset||f.support.boxModel&&g.scrollTop||h.scrollTop,m=i.pageXOffset||f.support.boxModel&&g.scrollLeft||h.scrollLeft,n=c.top+l-j,o=c.left+m-k;return{top:n,left:o}}:f.fn.offset=function(a){var b=this[0];if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);f.offset.initialize();var c,d=b.offsetParent,e=b,g=b.ownerDocument,h=g.documentElement,i=g.body,j=g.defaultView,k=j?j.getComputedStyle(b,null):b.currentStyle,l=b.offsetTop,m=b.offsetLeft;while((b=b.parentNode)&&b!==i&&b!==h){if(f.offset.supportsFixedPosition&&k.position==="fixed")break;c=j?j.getComputedStyle(b,null):b.currentStyle,l-=b.scrollTop,m-=b.scrollLeft,b===d&&(l+=b.offsetTop,m+=b.offsetLeft,f.offset.doesNotAddBorder&&(!f.offset.doesAddBorderForTableAndCells||!cw.test(b.nodeName))&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),e=d,d=b.offsetParent),f.offset.subtractsBorderForOverflowNotVisible&&c.overflow!=="visible"&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),k=c}if(k.position==="relative"||k.position==="static")l+=i.offsetTop,m+=i.offsetLeft;f.offset.supportsFixedPosition&&k.position==="fixed"&&(l+=Math.max(h.scrollTop,i.scrollTop),m+=Math.max(h.scrollLeft,i.scrollLeft));return{top:l,left:m}},f.offset={initialize:function(){var a=c.body,b=c.createElement("div"),d,e,g,h,i=parseFloat(f.css(a,"marginTop"))||0,j="<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";f.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"}),b.innerHTML=j,a.insertBefore(b,a.firstChild),d=b.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,this.doesNotAddBorder=e.offsetTop!==5,this.doesAddBorderForTableAndCells=h.offsetTop===5,e.style.position="fixed",e.style.top="20px",this.supportsFixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",this.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==i,a.removeChild(b),f.offset.initialize=f.noop},bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.offset.initialize(),f.offset.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cy(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cy(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){return this[0]?parseFloat(f.css(this[0],d,"padding")):null},f.fn["outer"+c]=function(a){return this[0]?parseFloat(f.css(this[0],d,a?"margin":"border")):null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c];return e.document.compatMode==="CSS1Compat"&&g||e.document.body["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var h=f.css(e,d),i=parseFloat(h);return f.isNaN(i)?h:i}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f})(window);
\ No newline at end of file
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/jquery.ba-hashchange.min.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/jquery.ba-hashchange.min.js
new file mode 100644
index 0000000..3c607ba
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/jquery.ba-hashchange.min.js
@@ -0,0 +1,9 @@
+/*
+ * jQuery hashchange event - v1.3 - 7/21/2010
+ * http://benalman.com/projects/jquery-hashchange-plugin/
+ * 
+ * Copyright (c) 2010 "Cowboy" Ben Alman
+ * Dual licensed under the MIT and GPL licenses.
+ * http://benalman.com/about/license/
+ */
+(function($,e,b){var c="hashchange",h=document,f,g=$.event.special,i=h.documentMode,d="on"+c in e&&(i===b||i>7);function a(j){j=j||location.href;return"#"+j.replace(/^[^#]*#?(.*)$/,"$1")}$.fn[c]=function(j){return j?this.bind(c,j):this.trigger(c)};$.fn[c].delay=50;g[c]=$.extend(g[c],{setup:function(){if(d){return false}$(f.start)},teardown:function(){if(d){return false}$(f.stop)}});f=(function(){var j={},p,m=a(),k=function(q){return q},l=k,o=k;j.start=function(){p||n()};j.stop=function(){p&&clearTimeout(p);p=b};function n(){var r=a(),q=o(m);if(r!==m){l(m=r,q);$(e).trigger(c)}else{if(q!==m){location.href=location.href.replace(/#.*/,"")+q}}p=setTimeout(n,$.fn[c].delay)}$.browser.msie&&!d&&(function(){var q,r;j.start=function(){if(!q){r=$.fn[c].src;r=r&&r+a();q=$('<iframe tabindex="-1" title="empty"/>').hide().one("load",function(){r||l(a());n()}).attr("src",r||"javascript:0").insertAfter("body")[0].contentWindow;h.onpropertychange=function(){try{if(event.propertyName==="title"){q.document.title=h.title}}catch(s){}}}};j.stop=k;o=function(){return a(q.location.href)};l=function(v,s){var u=q.document,t=$.fn[c].domain;if(v!==s){u.title=h.title;u.open();t&&u.write('<script>document.domain="'+t+'"<\/script>');u.close();q.location.hash=v}}})();return j})()})(jQuery,this);
\ No newline at end of file
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/json2.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/json2.js
new file mode 100644
index 0000000..11e1f0a
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/json2.js
@@ -0,0 +1,480 @@
+/*

+    http://www.JSON.org/json2.js

+    2011-02-23

+

+    Public Domain.

+

+    NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.

+

+    See http://www.JSON.org/js.html

+

+

+    This code should be minified before deployment.

+    See http://javascript.crockford.com/jsmin.html

+

+    USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO

+    NOT CONTROL.

+

+

+    This file creates a global JSON object containing two methods: stringify

+    and parse.

+

+        JSON.stringify(value, replacer, space)

+            value       any JavaScript value, usually an object or array.

+

+            replacer    an optional parameter that determines how object

+                        values are stringified for objects. It can be a

+                        function or an array of strings.

+

+            space       an optional parameter that specifies the indentation

+                        of nested structures. If it is omitted, the text will

+                        be packed without extra whitespace. If it is a number,

+                        it will specify the number of spaces to indent at each

+                        level. If it is a string (such as '\t' or '&nbsp;'),

+                        it contains the characters used to indent at each level.

+

+            This method produces a JSON text from a JavaScript value.

+

+            When an object value is found, if the object contains a toJSON

+            method, its toJSON method will be called and the result will be

+            stringified. A toJSON method does not serialize: it returns the

+            value represented by the name/value pair that should be serialized,

+            or undefined if nothing should be serialized. The toJSON method

+            will be passed the key associated with the value, and this will be

+            bound to the value

+

+            For example, this would serialize Dates as ISO strings.

+

+                Date.prototype.toJSON = function (key) {

+                    function f(n) {

+                        // Format integers to have at least two digits.

+                        return n < 10 ? '0' + n : n;

+                    }

+

+                    return this.getUTCFullYear()   + '-' +

+                         f(this.getUTCMonth() + 1) + '-' +

+                         f(this.getUTCDate())      + 'T' +

+                         f(this.getUTCHours())     + ':' +

+                         f(this.getUTCMinutes())   + ':' +

+                         f(this.getUTCSeconds())   + 'Z';

+                };

+

+            You can provide an optional replacer method. It will be passed the

+            key and value of each member, with this bound to the containing

+            object. The value that is returned from your method will be

+            serialized. If your method returns undefined, then the member will

+            be excluded from the serialization.

+

+            If the replacer parameter is an array of strings, then it will be

+            used to select the members to be serialized. It filters the results

+            such that only members with keys listed in the replacer array are

+            stringified.

+

+            Values that do not have JSON representations, such as undefined or

+            functions, will not be serialized. Such values in objects will be

+            dropped; in arrays they will be replaced with null. You can use

+            a replacer function to replace those with JSON values.

+            JSON.stringify(undefined) returns undefined.

+

+            The optional space parameter produces a stringification of the

+            value that is filled with line breaks and indentation to make it

+            easier to read.

+

+            If the space parameter is a non-empty string, then that string will

+            be used for indentation. If the space parameter is a number, then

+            the indentation will be that many spaces.

+

+            Example:

+

+            text = JSON.stringify(['e', {pluribus: 'unum'}]);

+            // text is '["e",{"pluribus":"unum"}]'

+

+

+            text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');

+            // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'

+

+            text = JSON.stringify([new Date()], function (key, value) {

+                return this[key] instanceof Date ?

+                    'Date(' + this[key] + ')' : value;

+            });

+            // text is '["Date(---current time---)"]'

+

+

+        JSON.parse(text, reviver)

+            This method parses a JSON text to produce an object or array.

+            It can throw a SyntaxError exception.

+

+            The optional reviver parameter is a function that can filter and

+            transform the results. It receives each of the keys and values,

+            and its return value is used instead of the original value.

+            If it returns what it received, then the structure is not modified.

+            If it returns undefined then the member is deleted.

+

+            Example:

+

+            // Parse the text. Values that look like ISO date strings will

+            // be converted to Date objects.

+

+            myData = JSON.parse(text, function (key, value) {

+                var a;

+                if (typeof value === 'string') {

+                    a =

+/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);

+                    if (a) {

+                        return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],

+                            +a[5], +a[6]));

+                    }

+                }

+                return value;

+            });

+

+            myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {

+                var d;

+                if (typeof value === 'string' &&

+                        value.slice(0, 5) === 'Date(' &&

+                        value.slice(-1) === ')') {

+                    d = new Date(value.slice(5, -1));

+                    if (d) {

+                        return d;

+                    }

+                }

+                return value;

+            });

+

+

+    This is a reference implementation. You are free to copy, modify, or

+    redistribute.

+*/

+

+/*jslint evil: true, strict: false, regexp: false */

+

+/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,

+    call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,

+    getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,

+    lastIndex, length, parse, prototype, push, replace, slice, stringify,

+    test, toJSON, toString, valueOf

+*/

+

+

+// Create a JSON object only if one does not already exist. We create the

+// methods in a closure to avoid creating global variables.

+

+var JSON;

+if (!JSON) {

+    JSON = {};

+}

+

+(function () {

+    "use strict";

+

+    function f(n) {

+        // Format integers to have at least two digits.

+        return n < 10 ? '0' + n : n;

+    }

+

+    if (typeof Date.prototype.toJSON !== 'function') {

+

+        Date.prototype.toJSON = function (key) {

+

+            return isFinite(this.valueOf()) ?

+                this.getUTCFullYear()     + '-' +

+                f(this.getUTCMonth() + 1) + '-' +

+                f(this.getUTCDate())      + 'T' +

+                f(this.getUTCHours())     + ':' +

+                f(this.getUTCMinutes())   + ':' +

+                f(this.getUTCSeconds())   + 'Z' : null;

+        };

+

+        String.prototype.toJSON      =

+            Number.prototype.toJSON  =

+            Boolean.prototype.toJSON = function (key) {

+                return this.valueOf();

+            };

+    }

+

+    var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,

+        escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,

+        gap,

+        indent,

+        meta = {    // table of character substitutions

+            '\b': '\\b',

+            '\t': '\\t',

+            '\n': '\\n',

+            '\f': '\\f',

+            '\r': '\\r',

+            '"' : '\\"',

+            '\\': '\\\\'

+        },

+        rep;

+

+

+    function quote(string) {

+

+// If the string contains no control characters, no quote characters, and no

+// backslash characters, then we can safely slap some quotes around it.

+// Otherwise we must also replace the offending characters with safe escape

+// sequences.

+

+        escapable.lastIndex = 0;

+        return escapable.test(string) ? '"' + string.replace(escapable, function (a) {

+            var c = meta[a];

+            return typeof c === 'string' ? c :

+                '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);

+        }) + '"' : '"' + string + '"';

+    }

+

+

+    function str(key, holder) {

+

+// Produce a string from holder[key].

+

+        var i,          // The loop counter.

+            k,          // The member key.

+            v,          // The member value.

+            length,

+            mind = gap,

+            partial,

+            value = holder[key];

+

+// If the value has a toJSON method, call it to obtain a replacement value.

+

+        if (value && typeof value === 'object' &&

+                typeof value.toJSON === 'function') {

+            value = value.toJSON(key);

+        }

+

+// If we were called with a replacer function, then call the replacer to

+// obtain a replacement value.

+

+        if (typeof rep === 'function') {

+            value = rep.call(holder, key, value);

+        }

+

+// What happens next depends on the value's type.

+

+        switch (typeof value) {

+        case 'string':

+            return quote(value);

+

+        case 'number':

+

+// JSON numbers must be finite. Encode non-finite numbers as null.

+

+            return isFinite(value) ? String(value) : 'null';

+

+        case 'boolean':

+        case 'null':

+

+// If the value is a boolean or null, convert it to a string. Note:

+// typeof null does not produce 'null'. The case is included here in

+// the remote chance that this gets fixed someday.

+

+            return String(value);

+

+// If the type is 'object', we might be dealing with an object or an array or

+// null.

+

+        case 'object':

+

+// Due to a specification blunder in ECMAScript, typeof null is 'object',

+// so watch out for that case.

+

+            if (!value) {

+                return 'null';

+            }

+

+// Make an array to hold the partial results of stringifying this object value.

+

+            gap += indent;

+            partial = [];

+

+// Is the value an array?

+

+            if (Object.prototype.toString.apply(value) === '[object Array]') {

+

+// The value is an array. Stringify every element. Use null as a placeholder

+// for non-JSON values.

+

+                length = value.length;

+                for (i = 0; i < length; i += 1) {

+                    partial[i] = str(i, value) || 'null';

+                }

+

+// Join all of the elements together, separated with commas, and wrap them in

+// brackets.

+

+                v = partial.length === 0 ? '[]' : gap ?

+                    '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' :

+                    '[' + partial.join(',') + ']';

+                gap = mind;

+                return v;

+            }

+

+// If the replacer is an array, use it to select the members to be stringified.

+

+            if (rep && typeof rep === 'object') {

+                length = rep.length;

+                for (i = 0; i < length; i += 1) {

+                    if (typeof rep[i] === 'string') {

+                        k = rep[i];

+                        v = str(k, value);

+                        if (v) {

+                            partial.push(quote(k) + (gap ? ': ' : ':') + v);

+                        }

+                    }

+                }

+            } else {

+

+// Otherwise, iterate through all of the keys in the object.

+

+                for (k in value) {

+                    if (Object.prototype.hasOwnProperty.call(value, k)) {

+                        v = str(k, value);

+                        if (v) {

+                            partial.push(quote(k) + (gap ? ': ' : ':') + v);

+                        }

+                    }

+                }

+            }

+

+// Join all of the member texts together, separated with commas,

+// and wrap them in braces.

+

+            v = partial.length === 0 ? '{}' : gap ?

+                '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' :

+                '{' + partial.join(',') + '}';

+            gap = mind;

+            return v;

+        }

+    }

+

+// If the JSON object does not yet have a stringify method, give it one.

+

+    if (typeof JSON.stringify !== 'function') {

+        JSON.stringify = function (value, replacer, space) {

+

+// The stringify method takes a value and an optional replacer, and an optional

+// space parameter, and returns a JSON text. The replacer can be a function

+// that can replace values, or an array of strings that will select the keys.

+// A default replacer method can be provided. Use of the space parameter can

+// produce text that is more easily readable.

+

+            var i;

+            gap = '';

+            indent = '';

+

+// If the space parameter is a number, make an indent string containing that

+// many spaces.

+

+            if (typeof space === 'number') {

+                for (i = 0; i < space; i += 1) {

+                    indent += ' ';

+                }

+

+// If the space parameter is a string, it will be used as the indent string.

+

+            } else if (typeof space === 'string') {

+                indent = space;

+            }

+

+// If there is a replacer, it must be a function or an array.

+// Otherwise, throw an error.

+

+            rep = replacer;

+            if (replacer && typeof replacer !== 'function' &&

+                    (typeof replacer !== 'object' ||

+                    typeof replacer.length !== 'number')) {

+                throw new Error('JSON.stringify');

+            }

+

+// Make a fake root object containing our value under the key of ''.

+// Return the result of stringifying the value.

+

+            return str('', {'': value});

+        };

+    }

+

+

+// If the JSON object does not yet have a parse method, give it one.

+

+    if (typeof JSON.parse !== 'function') {

+        JSON.parse = function (text, reviver) {

+

+// The parse method takes a text and an optional reviver function, and returns

+// a JavaScript value if the text is a valid JSON text.

+

+            var j;

+

+            function walk(holder, key) {

+

+// The walk method is used to recursively walk the resulting structure so

+// that modifications can be made.

+

+                var k, v, value = holder[key];

+                if (value && typeof value === 'object') {

+                    for (k in value) {

+                        if (Object.prototype.hasOwnProperty.call(value, k)) {

+                            v = walk(value, k);

+                            if (v !== undefined) {

+                                value[k] = v;

+                            } else {

+                                delete value[k];

+                            }

+                        }

+                    }

+                }

+                return reviver.call(holder, key, value);

+            }

+

+

+// Parsing happens in four stages. In the first stage, we replace certain

+// Unicode characters with escape sequences. JavaScript handles many characters

+// incorrectly, either silently deleting them, or treating them as line endings.

+

+            text = String(text);

+            cx.lastIndex = 0;

+            if (cx.test(text)) {

+                text = text.replace(cx, function (a) {

+                    return '\\u' +

+                        ('0000' + a.charCodeAt(0).toString(16)).slice(-4);

+                });

+            }

+

+// In the second stage, we run the text against regular expressions that look

+// for non-JSON patterns. We are especially concerned with '()' and 'new'

+// because they can cause invocation, and '=' because it can cause mutation.

+// But just to be safe, we want to reject all unexpected forms.

+

+// We split the second stage into 4 regexp operations in order to work around

+// crippling inefficiencies in IE's and Safari's regexp engines. First we

+// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we

+// replace all simple value tokens with ']' characters. Third, we delete all

+// open brackets that follow a colon or comma or that begin the text. Finally,

+// we look to see that the remaining characters are only whitespace or ']' or

+// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.

+

+            if (/^[\],:{}\s]*$/

+                    .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')

+                        .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')

+                        .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {

+

+// In the third stage we use the eval function to compile the text into a

+// JavaScript structure. The '{' operator is subject to a syntactic ambiguity

+// in JavaScript: it can begin a block or an object literal. We wrap the text

+// in parens to eliminate the ambiguity.

+

+                j = eval('(' + text + ')');

+

+// In the optional fourth stage, we recursively walk the new structure, passing

+// each name/value pair to a reviver function for possible transformation.

+

+                return typeof reviver === 'function' ?

+                    walk({'': j}, '') : j;

+            }

+

+// If the text is not JSON parseable, then a SyntaxError is thrown.

+

+            throw new SyntaxError('JSON.parse');

+        };

+    }

+}());

diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/main.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/main.js
new file mode 100644
index 0000000..4a9025a
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/main.js
@@ -0,0 +1,3 @@
+$(document).ready(function() {
+
+});
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/nav.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/nav.js
new file mode 100644
index 0000000..7736b4a
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/nav.js
@@ -0,0 +1,64 @@
+$(document).ready(function() {
+    var docsLoaded = false;
+
+    $(window).hashchange(function(e){
+        e.preventDefault();
+        e.stopPropagation();
+
+        if (location.hash === "#tryit") {
+            $("#main > .content").hide();
+            $("#tryit input").val("").keyup();
+            $("#tryit").fadeIn(400, function() {
+                $("#tryit input").val(".languagesSpoken .lang").keyup();
+            });
+        } else if (location.hash === "#cred") {
+            $("#main > .content").hide();
+            $("#cred").fadeIn(400);
+        } else if (location.hash === '#overview' || location.hash === '') {
+            $("#main > .content").hide();
+            $("#splash").fadeIn(400);
+        } else if (location.hash === '#code' || location.hash === '') {
+            $("#main > .content").hide();
+            $("#code").fadeIn(400);
+        } else if (location.hash.substr(0,5) === "#docs") {
+            function showIt() {
+                var where = window.location.hash.substr(6);
+                if (!where) {
+                    $("#doc").fadeIn(400);
+                } else {
+                    $("#doc").show();
+                    var dst = $("a[name='" + where + "']");
+                    if (dst.length) {
+                        $('html, body').animate({scrollTop:dst.offset().top - 100}, 500);
+                    }
+                }
+            }
+            $("#main > .content").hide();
+            if (!docsLoaded) {
+                $.get("JSONSelect.md").success(function(data) {
+                    var converter = new Showdown.converter();
+                    $("#doc").html(converter.makeHtml(data));
+                    $("#doc a").each(function() {
+                        var n = $(this).attr('href');
+                        if (typeof n === 'string' && n.substr(0,1) === '#') {
+                            $(this).attr('href', "#docs/" + n.substr(1));
+                        }
+                    });
+                    docsLoaded = true;
+                    showIt();
+                }).error(function() {
+                    $("#doc").text("Darnit, error fetching docs...").fadeIn(400);
+                });
+            } else {
+                showIt();
+            }
+        } else {
+        }
+        return false;
+    });
+
+    // Trigger the event (useful on page load).
+    if (window.location.hash === "")
+        window.location.hash = "#overview";
+    $(window).hashchange();
+});
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/showdown.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/showdown.js
new file mode 100644
index 0000000..43920d9
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/site/js/showdown.js
@@ -0,0 +1,1302 @@
+//

+// showdown.js -- A javascript port of Markdown.

+//

+// Copyright (c) 2007 John Fraser.

+//

+// Original Markdown Copyright (c) 2004-2005 John Gruber

+//   <http://daringfireball.net/projects/markdown/>

+//

+// Redistributable under a BSD-style open source license.

+// See license.txt for more information.

+//

+// The full source distribution is at:

+//

+//				A A L

+//				T C A

+//				T K B

+//

+//   <http://www.attacklab.net/>

+//

+

+//

+// Wherever possible, Showdown is a straight, line-by-line port

+// of the Perl version of Markdown.

+//

+// This is not a normal parser design; it's basically just a

+// series of string substitutions.  It's hard to read and

+// maintain this way,  but keeping Showdown close to the original

+// design makes it easier to port new features.

+//

+// More importantly, Showdown behaves like markdown.pl in most

+// edge cases.  So web applications can do client-side preview

+// in Javascript, and then build identical HTML on the server.

+//

+// This port needs the new RegExp functionality of ECMA 262,

+// 3rd Edition (i.e. Javascript 1.5).  Most modern web browsers

+// should do fine.  Even with the new regular expression features,

+// We do a lot of work to emulate Perl's regex functionality.

+// The tricky changes in this file mostly have the "attacklab:"

+// label.  Major or self-explanatory changes don't.

+//

+// Smart diff tools like Araxis Merge will be able to match up

+// this file with markdown.pl in a useful way.  A little tweaking

+// helps: in a copy of markdown.pl, replace "#" with "//" and

+// replace "$text" with "text".  Be sure to ignore whitespace

+// and line endings.

+//

+

+

+//

+// Showdown usage:

+//

+//   var text = "Markdown *rocks*.";

+//

+//   var converter = new Showdown.converter();

+//   var html = converter.makeHtml(text);

+//

+//   alert(html);

+//

+// Note: move the sample code to the bottom of this

+// file before uncommenting it.

+//

+

+

+//

+// Showdown namespace

+//

+var Showdown = {};

+

+//

+// converter

+//

+// Wraps all "globals" so that the only thing

+// exposed is makeHtml().

+//

+Showdown.converter = function() {

+

+//

+// Globals:

+//

+

+// Global hashes, used by various utility routines

+var g_urls;

+var g_titles;

+var g_html_blocks;

+

+// Used to track when we're inside an ordered or unordered list

+// (see _ProcessListItems() for details):

+var g_list_level = 0;

+

+

+this.makeHtml = function(text) {

+//

+// Main function. The order in which other subs are called here is

+// essential. Link and image substitutions need to happen before

+// _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the <a>

+// and <img> tags get encoded.

+//

+

+	// Clear the global hashes. If we don't clear these, you get conflicts

+	// from other articles when generating a page which contains more than

+	// one article (e.g. an index page that shows the N most recent

+	// articles):

+	g_urls = new Array();

+	g_titles = new Array();

+	g_html_blocks = new Array();

+

+	// attacklab: Replace ~ with ~T

+	// This lets us use tilde as an escape char to avoid md5 hashes

+	// The choice of character is arbitray; anything that isn't

+    // magic in Markdown will work.

+	text = text.replace(/~/g,"~T");

+

+	// attacklab: Replace $ with ~D

+	// RegExp interprets $ as a special character

+	// when it's in a replacement string

+	text = text.replace(/\$/g,"~D");

+

+	// Standardize line endings

+	text = text.replace(/\r\n/g,"\n"); // DOS to Unix

+	text = text.replace(/\r/g,"\n"); // Mac to Unix

+

+	// Make sure text begins and ends with a couple of newlines:

+	text = "\n\n" + text + "\n\n";

+

+	// Convert all tabs to spaces.

+	text = _Detab(text);

+

+	// Strip any lines consisting only of spaces and tabs.

+	// This makes subsequent regexen easier to write, because we can

+	// match consecutive blank lines with /\n+/ instead of something

+	// contorted like /[ \t]*\n+/ .

+	text = text.replace(/^[ \t]+$/mg,"");

+

+	// Turn block-level HTML blocks into hash entries

+	text = _HashHTMLBlocks(text);

+

+	// Strip link definitions, store in hashes.

+	text = _StripLinkDefinitions(text);

+

+	text = _RunBlockGamut(text);

+

+	text = _UnescapeSpecialChars(text);

+

+	// attacklab: Restore dollar signs

+	text = text.replace(/~D/g,"$$");

+

+	// attacklab: Restore tildes

+	text = text.replace(/~T/g,"~");

+

+	return text;

+}

+

+

+var _StripLinkDefinitions = function(text) {

+//

+// Strips link definitions from text, stores the URLs and titles in

+// hash references.

+//

+

+	// Link defs are in the form: ^[id]: url "optional title"

+

+	/*

+		var text = text.replace(/

+				^[ ]{0,3}\[(.+)\]:  // id = $1  attacklab: g_tab_width - 1

+				  [ \t]*

+				  \n?				// maybe *one* newline

+				  [ \t]*

+				<?(\S+?)>?			// url = $2

+				  [ \t]*

+				  \n?				// maybe one newline

+				  [ \t]*

+				(?:

+				  (\n*)				// any lines skipped = $3 attacklab: lookbehind removed

+				  ["(]

+				  (.+?)				// title = $4

+				  [")]

+				  [ \t]*

+				)?					// title is optional

+				(?:\n+|$)

+			  /gm,

+			  function(){...});

+	*/

+	var text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|\Z)/gm,

+		function (wholeMatch,m1,m2,m3,m4) {

+			m1 = m1.toLowerCase();

+			g_urls[m1] = _EncodeAmpsAndAngles(m2);  // Link IDs are case-insensitive

+			if (m3) {

+				// Oops, found blank lines, so it's not a title.

+				// Put back the parenthetical statement we stole.

+				return m3+m4;

+			} else if (m4) {

+				g_titles[m1] = m4.replace(/"/g,"&quot;");

+			}

+			

+			// Completely remove the definition from the text

+			return "";

+		}

+	);

+

+	return text;

+}

+

+

+var _HashHTMLBlocks = function(text) {

+	// attacklab: Double up blank lines to reduce lookaround

+	text = text.replace(/\n/g,"\n\n");

+

+	// Hashify HTML blocks:

+	// We only want to do this for block-level HTML tags, such as headers,

+	// lists, and tables. That's because we still want to wrap <p>s around

+	// "paragraphs" that are wrapped in non-block-level tags, such as anchors,

+	// phrase emphasis, and spans. The list of tags we're looking for is

+	// hard-coded:

+	var block_tags_a = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del"

+	var block_tags_b = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math"

+

+	// First, look for nested blocks, e.g.:

+	//   <div>

+	//     <div>

+	//     tags for inner block must be indented.

+	//     </div>

+	//   </div>

+	//

+	// The outermost tags must start at the left margin for this to match, and

+	// the inner nested divs must be indented.

+	// We need to do this before the next, more liberal match, because the next

+	// match will start at the first `<div>` and stop at the first `</div>`.

+

+	// attacklab: This regex can be expensive when it fails.

+	/*

+		var text = text.replace(/

+		(						// save in $1

+			^					// start of line  (with /m)

+			<($block_tags_a)	// start tag = $2

+			\b					// word break

+								// attacklab: hack around khtml/pcre bug...

+			[^\r]*?\n			// any number of lines, minimally matching

+			</\2>				// the matching end tag

+			[ \t]*				// trailing spaces/tabs

+			(?=\n+)				// followed by a newline

+		)						// attacklab: there are sentinel newlines at end of document

+		/gm,function(){...}};

+	*/

+	text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)\b[^\r]*?\n<\/\2>[ \t]*(?=\n+))/gm,hashElement);

+

+	//

+	// Now match more liberally, simply from `\n<tag>` to `</tag>\n`

+	//

+

+	/*

+		var text = text.replace(/

+		(						// save in $1

+			^					// start of line  (with /m)

+			<($block_tags_b)	// start tag = $2

+			\b					// word break

+								// attacklab: hack around khtml/pcre bug...

+			[^\r]*?				// any number of lines, minimally matching

+			.*</\2>				// the matching end tag

+			[ \t]*				// trailing spaces/tabs

+			(?=\n+)				// followed by a newline

+		)						// attacklab: there are sentinel newlines at end of document

+		/gm,function(){...}};

+	*/

+	text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math)\b[^\r]*?.*<\/\2>[ \t]*(?=\n+)\n)/gm,hashElement);

+

+	// Special case just for <hr />. It was easier to make a special case than

+	// to make the other regex more complicated.  

+

+	/*

+		text = text.replace(/

+		(						// save in $1

+			\n\n				// Starting after a blank line

+			[ ]{0,3}

+			(<(hr)				// start tag = $2

+			\b					// word break

+			([^<>])*?			// 

+			\/?>)				// the matching end tag

+			[ \t]*

+			(?=\n{2,})			// followed by a blank line

+		)

+		/g,hashElement);

+	*/

+	text = text.replace(/(\n[ ]{0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,hashElement);

+

+	// Special case for standalone HTML comments:

+

+	/*

+		text = text.replace(/

+		(						// save in $1

+			\n\n				// Starting after a blank line

+			[ ]{0,3}			// attacklab: g_tab_width - 1

+			<!

+			(--[^\r]*?--\s*)+

+			>

+			[ \t]*

+			(?=\n{2,})			// followed by a blank line

+		)

+		/g,hashElement);

+	*/

+	text = text.replace(/(\n\n[ ]{0,3}<!(--[^\r]*?--\s*)+>[ \t]*(?=\n{2,}))/g,hashElement);

+

+	// PHP and ASP-style processor instructions (<?...?> and <%...%>)

+

+	/*

+		text = text.replace(/

+		(?:

+			\n\n				// Starting after a blank line

+		)

+		(						// save in $1

+			[ ]{0,3}			// attacklab: g_tab_width - 1

+			(?:

+				<([?%])			// $2

+				[^\r]*?

+				\2>

+			)

+			[ \t]*

+			(?=\n{2,})			// followed by a blank line

+		)

+		/g,hashElement);

+	*/

+	text = text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,hashElement);

+

+	// attacklab: Undo double lines (see comment at top of this function)

+	text = text.replace(/\n\n/g,"\n");

+	return text;

+}

+

+var hashElement = function(wholeMatch,m1) {

+	var blockText = m1;

+

+	// Undo double lines

+	blockText = blockText.replace(/\n\n/g,"\n");

+	blockText = blockText.replace(/^\n/,"");

+	

+	// strip trailing blank lines

+	blockText = blockText.replace(/\n+$/g,"");

+	

+	// Replace the element text with a marker ("~KxK" where x is its key)

+	blockText = "\n\n~K" + (g_html_blocks.push(blockText)-1) + "K\n\n";

+	

+	return blockText;

+};

+

+var _RunBlockGamut = function(text) {

+//

+// These are all the transformations that form block-level

+// tags like paragraphs, headers, and list items.

+//

+	text = _DoHeaders(text);

+

+	// Do Horizontal Rules:

+	var key = hashBlock("<hr />");

+	text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm,key);

+	text = text.replace(/^[ ]{0,2}([ ]?\-[ ]?){3,}[ \t]*$/gm,key);

+	text = text.replace(/^[ ]{0,2}([ ]?\_[ ]?){3,}[ \t]*$/gm,key);

+

+	text = _DoLists(text);

+	text = _DoCodeBlocks(text);

+	text = _DoBlockQuotes(text);

+

+	// We already ran _HashHTMLBlocks() before, in Markdown(), but that

+	// was to escape raw HTML in the original Markdown source. This time,

+	// we're escaping the markup we've just created, so that we don't wrap

+	// <p> tags around block-level tags.

+	text = _HashHTMLBlocks(text);

+	text = _FormParagraphs(text);

+

+	return text;

+}

+

+

+var _RunSpanGamut = function(text) {

+//

+// These are all the transformations that occur *within* block-level

+// tags like paragraphs, headers, and list items.

+//

+

+	text = _DoCodeSpans(text);

+	text = _EscapeSpecialCharsWithinTagAttributes(text);

+	text = _EncodeBackslashEscapes(text);

+

+	// Process anchor and image tags. Images must come first,

+	// because ![foo][f] looks like an anchor.

+	text = _DoImages(text);

+	text = _DoAnchors(text);

+

+	// Make links out of things like `<http://example.com/>`

+	// Must come after _DoAnchors(), because you can use < and >

+	// delimiters in inline links like [this](<url>).

+	text = _DoAutoLinks(text);

+	text = _EncodeAmpsAndAngles(text);

+	text = _DoItalicsAndBold(text);

+

+	// Do hard breaks:

+	text = text.replace(/  +\n/g," <br />\n");

+

+	return text;

+}

+

+var _EscapeSpecialCharsWithinTagAttributes = function(text) {

+//

+// Within tags -- meaning between < and > -- encode [\ ` * _] so they

+// don't conflict with their use in Markdown for code, italics and strong.

+//

+

+	// Build a regex to find HTML tags and comments.  See Friedl's 

+	// "Mastering Regular Expressions", 2nd Ed., pp. 200-201.

+	var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|<!(--.*?--\s*)+>)/gi;

+

+	text = text.replace(regex, function(wholeMatch) {

+		var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g,"$1`");

+		tag = escapeCharacters(tag,"\\`*_");

+		return tag;

+	});

+

+	return text;

+}

+

+var _DoAnchors = function(text) {

+//

+// Turn Markdown link shortcuts into XHTML <a> tags.

+//

+	//

+	// First, handle reference-style links: [link text] [id]

+	//

+

+	/*

+		text = text.replace(/

+		(							// wrap whole match in $1

+			\[

+			(

+				(?:

+					\[[^\]]*\]		// allow brackets nested one level

+					|

+					[^\[]			// or anything else

+				)*

+			)

+			\]

+

+			[ ]?					// one optional space

+			(?:\n[ ]*)?				// one optional newline followed by spaces

+

+			\[

+			(.*?)					// id = $3

+			\]

+		)()()()()					// pad remaining backreferences

+		/g,_DoAnchors_callback);

+	*/

+	text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeAnchorTag);

+

+	//

+	// Next, inline-style links: [link text](url "optional title")

+	//

+

+	/*

+		text = text.replace(/

+			(						// wrap whole match in $1

+				\[

+				(

+					(?:

+						\[[^\]]*\]	// allow brackets nested one level

+					|

+					[^\[\]]			// or anything else

+				)

+			)

+			\]

+			\(						// literal paren

+			[ \t]*

+			()						// no id, so leave $3 empty

+			<?(.*?)>?				// href = $4

+			[ \t]*

+			(						// $5

+				(['"])				// quote char = $6

+				(.*?)				// Title = $7

+				\6					// matching quote

+				[ \t]*				// ignore any spaces/tabs between closing quote and )

+			)?						// title is optional

+			\)

+		)

+		/g,writeAnchorTag);

+	*/

+	text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()<?(.*?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeAnchorTag);

+

+	//

+	// Last, handle reference-style shortcuts: [link text]

+	// These must come last in case you've also got [link test][1]

+	// or [link test](/foo)

+	//

+

+	/*

+		text = text.replace(/

+		(		 					// wrap whole match in $1

+			\[

+			([^\[\]]+)				// link text = $2; can't contain '[' or ']'

+			\]

+		)()()()()()					// pad rest of backreferences

+		/g, writeAnchorTag);

+	*/

+	text = text.replace(/(\[([^\[\]]+)\])()()()()()/g, writeAnchorTag);

+

+	return text;

+}

+

+var writeAnchorTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) {

+	if (m7 == undefined) m7 = "";

+	var whole_match = m1;

+	var link_text   = m2;

+	var link_id	 = m3.toLowerCase();

+	var url		= m4;

+	var title	= m7;

+	

+	if (url == "") {

+		if (link_id == "") {

+			// lower-case and turn embedded newlines into spaces

+			link_id = link_text.toLowerCase().replace(/ ?\n/g," ");

+		}

+		url = "#"+link_id;

+		

+		if (g_urls[link_id] != undefined) {

+			url = g_urls[link_id];

+			if (g_titles[link_id] != undefined) {

+				title = g_titles[link_id];

+			}

+		}

+		else {

+			if (whole_match.search(/\(\s*\)$/m)>-1) {

+				// Special case for explicit empty url

+				url = "";

+			} else {

+				return whole_match;

+			}

+		}

+	}	

+	

+	url = escapeCharacters(url,"*_");

+	var result = "<a href=\"" + url + "\"";

+	

+	if (title != "") {

+		title = title.replace(/"/g,"&quot;");

+		title = escapeCharacters(title,"*_");

+		result +=  " title=\"" + title + "\"";

+	}

+	

+	result += ">" + link_text + "</a>";

+	

+	return result;

+}

+

+

+var _DoImages = function(text) {

+//

+// Turn Markdown image shortcuts into <img> tags.

+//

+

+	//

+	// First, handle reference-style labeled images: ![alt text][id]

+	//

+

+	/*

+		text = text.replace(/

+		(						// wrap whole match in $1

+			!\[

+			(.*?)				// alt text = $2

+			\]

+

+			[ ]?				// one optional space

+			(?:\n[ ]*)?			// one optional newline followed by spaces

+

+			\[

+			(.*?)				// id = $3

+			\]

+		)()()()()				// pad rest of backreferences

+		/g,writeImageTag);

+	*/

+	text = text.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeImageTag);

+

+	//

+	// Next, handle inline images:  ![alt text](url "optional title")

+	// Don't forget: encode * and _

+

+	/*

+		text = text.replace(/

+		(						// wrap whole match in $1

+			!\[

+			(.*?)				// alt text = $2

+			\]

+			\s?					// One optional whitespace character

+			\(					// literal paren

+			[ \t]*

+			()					// no id, so leave $3 empty

+			<?(\S+?)>?			// src url = $4

+			[ \t]*

+			(					// $5

+				(['"])			// quote char = $6

+				(.*?)			// title = $7

+				\6				// matching quote

+				[ \t]*

+			)?					// title is optional

+		\)

+		)

+		/g,writeImageTag);

+	*/

+	text = text.replace(/(!\[(.*?)\]\s?\([ \t]*()<?(\S+?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeImageTag);

+

+	return text;

+}

+

+var writeImageTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) {

+	var whole_match = m1;

+	var alt_text   = m2;

+	var link_id	 = m3.toLowerCase();

+	var url		= m4;

+	var title	= m7;

+

+	if (!title) title = "";

+	

+	if (url == "") {

+		if (link_id == "") {

+			// lower-case and turn embedded newlines into spaces

+			link_id = alt_text.toLowerCase().replace(/ ?\n/g," ");

+		}

+		url = "#"+link_id;

+		

+		if (g_urls[link_id] != undefined) {

+			url = g_urls[link_id];

+			if (g_titles[link_id] != undefined) {

+				title = g_titles[link_id];

+			}

+		}

+		else {

+			return whole_match;

+		}

+	}	

+	

+	alt_text = alt_text.replace(/"/g,"&quot;");

+	url = escapeCharacters(url,"*_");

+	var result = "<img src=\"" + url + "\" alt=\"" + alt_text + "\"";

+

+	// attacklab: Markdown.pl adds empty title attributes to images.

+	// Replicate this bug.

+

+	//if (title != "") {

+		title = title.replace(/"/g,"&quot;");

+		title = escapeCharacters(title,"*_");

+		result +=  " title=\"" + title + "\"";

+	//}

+	

+	result += " />";

+	

+	return result;

+}

+

+

+var _DoHeaders = function(text) {

+

+	// Setext-style headers:

+	//	Header 1

+	//	========

+	//  

+	//	Header 2

+	//	--------

+	//

+	text = text.replace(/^(.+)[ \t]*\n=+[ \t]*\n+/gm,

+		function(wholeMatch,m1){return hashBlock('<h1 id="' + headerId(m1) + '">' + _RunSpanGamut(m1) + "</h1>");});

+

+	text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm,

+		function(matchFound,m1){return hashBlock('<h2 id="' + headerId(m1) + '">' + _RunSpanGamut(m1) + "</h2>");});

+

+	// atx-style headers:

+	//  # Header 1

+	//  ## Header 2

+	//  ## Header 2 with closing hashes ##

+	//  ...

+	//  ###### Header 6

+	//

+

+	/*

+		text = text.replace(/

+			^(\#{1,6})				// $1 = string of #'s

+			[ \t]*

+			(.+?)					// $2 = Header text

+			[ \t]*

+			\#*						// optional closing #'s (not counted)

+			\n+

+		/gm, function() {...});

+	*/

+

+	text = text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm,

+		function(wholeMatch,m1,m2) {

+			var h_level = m1.length;

+			return hashBlock("<h" + h_level + ' id="' + headerId(m2) + '">' + _RunSpanGamut(m2) + "</h" + h_level + ">");

+		});

+

+	function headerId(m) {

+		return m.replace(/[^\w]/g, '').toLowerCase();

+	}

+	return text;

+}

+

+// This declaration keeps Dojo compressor from outputting garbage:

+var _ProcessListItems;

+

+var _DoLists = function(text) {

+//

+// Form HTML ordered (numbered) and unordered (bulleted) lists.

+//

+

+	// attacklab: add sentinel to hack around khtml/safari bug:

+	// http://bugs.webkit.org/show_bug.cgi?id=11231

+	text += "~0";

+

+	// Re-usable pattern to match any entirel ul or ol list:

+

+	/*

+		var whole_list = /

+		(									// $1 = whole list

+			(								// $2

+				[ ]{0,3}					// attacklab: g_tab_width - 1

+				([*+-]|\d+[.])				// $3 = first list item marker

+				[ \t]+

+			)

+			[^\r]+?

+			(								// $4

+				~0							// sentinel for workaround; should be $

+			|

+				\n{2,}

+				(?=\S)

+				(?!							// Negative lookahead for another list item marker

+					[ \t]*

+					(?:[*+-]|\d+[.])[ \t]+

+				)

+			)

+		)/g

+	*/

+	var whole_list = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;

+

+	if (g_list_level) {

+		text = text.replace(whole_list,function(wholeMatch,m1,m2) {

+			var list = m1;

+			var list_type = (m2.search(/[*+-]/g)>-1) ? "ul" : "ol";

+

+			// Turn double returns into triple returns, so that we can make a

+			// paragraph for the last item in a list, if necessary:

+			list = list.replace(/\n{2,}/g,"\n\n\n");;

+			var result = _ProcessListItems(list);

+	

+			// Trim any trailing whitespace, to put the closing `</$list_type>`

+			// up on the preceding line, to get it past the current stupid

+			// HTML block parser. This is a hack to work around the terrible

+			// hack that is the HTML block parser.

+			result = result.replace(/\s+$/,"");

+			result = "<"+list_type+">" + result + "</"+list_type+">\n";

+			return result;

+		});

+	} else {

+		whole_list = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g;

+		text = text.replace(whole_list,function(wholeMatch,m1,m2,m3) {

+			var runup = m1;

+			var list = m2;

+

+			var list_type = (m3.search(/[*+-]/g)>-1) ? "ul" : "ol";

+			// Turn double returns into triple returns, so that we can make a

+			// paragraph for the last item in a list, if necessary:

+			var list = list.replace(/\n{2,}/g,"\n\n\n");;

+			var result = _ProcessListItems(list);

+			result = runup + "<"+list_type+">\n" + result + "</"+list_type+">\n";	

+			return result;

+		});

+	}

+

+	// attacklab: strip sentinel

+	text = text.replace(/~0/,"");

+

+	return text;

+}

+

+_ProcessListItems = function(list_str) {

+//

+//  Process the contents of a single ordered or unordered list, splitting it

+//  into individual list items.

+//

+	// The $g_list_level global keeps track of when we're inside a list.

+	// Each time we enter a list, we increment it; when we leave a list,

+	// we decrement. If it's zero, we're not in a list anymore.

+	//

+	// We do this because when we're not inside a list, we want to treat

+	// something like this:

+	//

+	//    I recommend upgrading to version

+	//    8. Oops, now this line is treated

+	//    as a sub-list.

+	//

+	// As a single paragraph, despite the fact that the second line starts

+	// with a digit-period-space sequence.

+	//

+	// Whereas when we're inside a list (or sub-list), that line will be

+	// treated as the start of a sub-list. What a kludge, huh? This is

+	// an aspect of Markdown's syntax that's hard to parse perfectly

+	// without resorting to mind-reading. Perhaps the solution is to

+	// change the syntax rules such that sub-lists must start with a

+	// starting cardinal number; e.g. "1." or "a.".

+

+	g_list_level++;

+

+	// trim trailing blank lines:

+	list_str = list_str.replace(/\n{2,}$/,"\n");

+

+	// attacklab: add sentinel to emulate \z

+	list_str += "~0";

+

+	/*

+		list_str = list_str.replace(/

+			(\n)?							// leading line = $1

+			(^[ \t]*)						// leading whitespace = $2

+			([*+-]|\d+[.]) [ \t]+			// list marker = $3

+			([^\r]+?						// list item text   = $4

+			(\n{1,2}))

+			(?= \n* (~0 | \2 ([*+-]|\d+[.]) [ \t]+))

+		/gm, function(){...});

+	*/

+	list_str = list_str.replace(/(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+([^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm,

+		function(wholeMatch,m1,m2,m3,m4){

+			var item = m4;

+			var leading_line = m1;

+			var leading_space = m2;

+

+			if (leading_line || (item.search(/\n{2,}/)>-1)) {

+				item = _RunBlockGamut(_Outdent(item));

+			}

+			else {

+				// Recursion for sub-lists:

+				item = _DoLists(_Outdent(item));

+				item = item.replace(/\n$/,""); // chomp(item)

+				item = _RunSpanGamut(item);

+			}

+

+			return  "<li>" + item + "</li>\n";

+		}

+	);

+

+	// attacklab: strip sentinel

+	list_str = list_str.replace(/~0/g,"");

+

+	g_list_level--;

+	return list_str;

+}

+

+

+var _DoCodeBlocks = function(text) {

+//

+//  Process Markdown `<pre><code>` blocks.

+//  

+

+	/*

+		text = text.replace(text,

+			/(?:\n\n|^)

+			(								// $1 = the code block -- one or more lines, starting with a space/tab

+				(?:

+					(?:[ ]{4}|\t)			// Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width

+					.*\n+

+				)+

+			)

+			(\n*[ ]{0,3}[^ \t\n]|(?=~0))	// attacklab: g_tab_width

+		/g,function(){...});

+	*/

+

+	// attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug

+	text += "~0";

+	

+	text = text.replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,

+		function(wholeMatch,m1,m2) {

+			var codeblock = m1;

+			var nextChar = m2;

+		

+			codeblock = _EncodeCode( _Outdent(codeblock));

+			codeblock = _Detab(codeblock);

+			codeblock = codeblock.replace(/^\n+/g,""); // trim leading newlines

+			codeblock = codeblock.replace(/\n+$/g,""); // trim trailing whitespace

+

+			codeblock = "<pre><code>" + codeblock + "\n</code></pre>";

+

+			return hashBlock(codeblock) + nextChar;

+		}

+	);

+

+	// attacklab: strip sentinel

+	text = text.replace(/~0/,"");

+

+	return text;

+}

+

+var hashBlock = function(text) {

+	text = text.replace(/(^\n+|\n+$)/g,"");

+	return "\n\n~K" + (g_html_blocks.push(text)-1) + "K\n\n";

+}

+

+

+var _DoCodeSpans = function(text) {

+//

+//   *  Backtick quotes are used for <code></code> spans.

+// 

+//   *  You can use multiple backticks as the delimiters if you want to

+//	 include literal backticks in the code span. So, this input:

+//	 

+//		 Just type ``foo `bar` baz`` at the prompt.

+//	 

+//	   Will translate to:

+//	 

+//		 <p>Just type <code>foo `bar` baz</code> at the prompt.</p>

+//	 

+//	There's no arbitrary limit to the number of backticks you

+//	can use as delimters. If you need three consecutive backticks

+//	in your code, use four for delimiters, etc.

+//

+//  *  You can use spaces to get literal backticks at the edges:

+//	 

+//		 ... type `` `bar` `` ...

+//	 

+//	   Turns to:

+//	 

+//		 ... type <code>`bar`</code> ...

+//

+

+	/*

+		text = text.replace(/

+			(^|[^\\])					// Character before opening ` can't be a backslash

+			(`+)						// $2 = Opening run of `

+			(							// $3 = The code block

+				[^\r]*?

+				[^`]					// attacklab: work around lack of lookbehind

+			)

+			\2							// Matching closer

+			(?!`)

+		/gm, function(){...});

+	*/

+

+	text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,

+		function(wholeMatch,m1,m2,m3,m4) {

+			var c = m3;

+			c = c.replace(/^([ \t]*)/g,"");	// leading whitespace

+			c = c.replace(/[ \t]*$/g,"");	// trailing whitespace

+			c = _EncodeCode(c);

+			return m1+"<code>"+c+"</code>";

+		});

+

+	return text;

+}

+

+

+var _EncodeCode = function(text) {

+//

+// Encode/escape certain characters inside Markdown code runs.

+// The point is that in code, these characters are literals,

+// and lose their special Markdown meanings.

+//

+	// Encode all ampersands; HTML entities are not

+	// entities within a Markdown code span.

+	text = text.replace(/&/g,"&amp;");

+

+	// Do the angle bracket song and dance:

+	text = text.replace(/</g,"&lt;");

+	text = text.replace(/>/g,"&gt;");

+

+	// Now, escape characters that are magic in Markdown:

+	text = escapeCharacters(text,"\*_{}[]\\",false);

+

+// jj the line above breaks this:

+//---

+

+//* Item

+

+//   1. Subitem

+

+//            special char: *

+//---

+

+	return text;

+}

+

+

+var _DoItalicsAndBold = function(text) {

+

+	// <strong> must go first:

+	text = text.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g,

+		"<strong>$2</strong>");

+

+	text = text.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g,

+		"<em>$2</em>");

+

+	return text;

+}

+

+

+var _DoBlockQuotes = function(text) {

+

+	/*

+		text = text.replace(/

+		(								// Wrap whole match in $1

+			(

+				^[ \t]*>[ \t]?			// '>' at the start of a line

+				.+\n					// rest of the first line

+				(.+\n)*					// subsequent consecutive lines

+				\n*						// blanks

+			)+

+		)

+		/gm, function(){...});

+	*/

+

+	text = text.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm,

+		function(wholeMatch,m1) {

+			var bq = m1;

+

+			// attacklab: hack around Konqueror 3.5.4 bug:

+			// "----------bug".replace(/^-/g,"") == "bug"

+

+			bq = bq.replace(/^[ \t]*>[ \t]?/gm,"~0");	// trim one level of quoting

+

+			// attacklab: clean up hack

+			bq = bq.replace(/~0/g,"");

+

+			bq = bq.replace(/^[ \t]+$/gm,"");		// trim whitespace-only lines

+			bq = _RunBlockGamut(bq);				// recurse

+			

+			bq = bq.replace(/(^|\n)/g,"$1  ");

+			// These leading spaces screw with <pre> content, so we need to fix that:

+			bq = bq.replace(

+					/(\s*<pre>[^\r]+?<\/pre>)/gm,

+				function(wholeMatch,m1) {

+					var pre = m1;

+					// attacklab: hack around Konqueror 3.5.4 bug:

+					pre = pre.replace(/^  /mg,"~0");

+					pre = pre.replace(/~0/g,"");

+					return pre;

+				});

+			

+			return hashBlock("<blockquote>\n" + bq + "\n</blockquote>");

+		});

+	return text;

+}

+

+

+var _FormParagraphs = function(text) {

+//

+//  Params:

+//    $text - string to process with html <p> tags

+//

+

+	// Strip leading and trailing lines:

+	text = text.replace(/^\n+/g,"");

+	text = text.replace(/\n+$/g,"");

+

+	var grafs = text.split(/\n{2,}/g);

+	var grafsOut = new Array();

+

+	//

+	// Wrap <p> tags.

+	//

+	var end = grafs.length;

+	for (var i=0; i<end; i++) {

+		var str = grafs[i];

+

+		// if this is an HTML marker, copy it

+		if (str.search(/~K(\d+)K/g) >= 0) {

+			grafsOut.push(str);

+		}

+		else if (str.search(/\S/) >= 0) {

+			str = _RunSpanGamut(str);

+			str = str.replace(/^([ \t]*)/g,"<p>");

+			str += "</p>"

+			grafsOut.push(str);

+		}

+

+	}

+

+	//

+	// Unhashify HTML blocks

+	//

+	end = grafsOut.length;

+	for (var i=0; i<end; i++) {

+		// if this is a marker for an html block...

+		while (grafsOut[i].search(/~K(\d+)K/) >= 0) {

+			var blockText = g_html_blocks[RegExp.$1];

+			blockText = blockText.replace(/\$/g,"$$$$"); // Escape any dollar signs

+			grafsOut[i] = grafsOut[i].replace(/~K\d+K/,blockText);

+		}

+	}

+

+	return grafsOut.join("\n\n");

+}

+

+

+var _EncodeAmpsAndAngles = function(text) {

+// Smart processing for ampersands and angle brackets that need to be encoded.

+	

+	// Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin:

+	//   http://bumppo.net/projects/amputator/

+	text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g,"&amp;");

+	

+	// Encode naked <'s

+	text = text.replace(/<(?![a-z\/?\$!])/gi,"&lt;");

+	

+	return text;

+}

+

+

+var _EncodeBackslashEscapes = function(text) {

+//

+//   Parameter:  String.

+//   Returns:	The string, with after processing the following backslash

+//			   escape sequences.

+//

+

+	// attacklab: The polite way to do this is with the new

+	// escapeCharacters() function:

+	//

+	// 	text = escapeCharacters(text,"\\",true);

+	// 	text = escapeCharacters(text,"`*_{}[]()>#+-.!",true);

+	//

+	// ...but we're sidestepping its use of the (slow) RegExp constructor

+	// as an optimization for Firefox.  This function gets called a LOT.

+

+	text = text.replace(/\\(\\)/g,escapeCharacters_callback);

+	text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g,escapeCharacters_callback);

+	return text;

+}

+

+

+var _DoAutoLinks = function(text) {

+

+	text = text.replace(/<((https?|ftp|dict):[^'">\s]+)>/gi,"<a href=\"$1\">$1</a>");

+

+	// Email addresses: <address@domain.foo>

+

+	/*

+		text = text.replace(/

+			<

+			(?:mailto:)?

+			(

+				[-.\w]+

+				\@

+				[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+

+			)

+			>

+		/gi, _DoAutoLinks_callback());

+	*/

+	text = text.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi,

+		function(wholeMatch,m1) {

+			return _EncodeEmailAddress( _UnescapeSpecialChars(m1) );

+		}

+	);

+

+	return text;

+}

+

+

+var _EncodeEmailAddress = function(addr) {

+//

+//  Input: an email address, e.g. "foo@example.com"

+//

+//  Output: the email address as a mailto link, with each character

+//	of the address encoded as either a decimal or hex entity, in

+//	the hopes of foiling most address harvesting spam bots. E.g.:

+//

+//	<a href="&#x6D;&#97;&#105;&#108;&#x74;&#111;:&#102;&#111;&#111;&#64;&#101;

+//	   x&#x61;&#109;&#x70;&#108;&#x65;&#x2E;&#99;&#111;&#109;">&#102;&#111;&#111;

+//	   &#64;&#101;x&#x61;&#109;&#x70;&#108;&#x65;&#x2E;&#99;&#111;&#109;</a>

+//

+//  Based on a filter by Matthew Wickline, posted to the BBEdit-Talk

+//  mailing list: <http://tinyurl.com/yu7ue>

+//

+

+	// attacklab: why can't javascript speak hex?

+	function char2hex(ch) {

+		var hexDigits = '0123456789ABCDEF';

+		var dec = ch.charCodeAt(0);

+		return(hexDigits.charAt(dec>>4) + hexDigits.charAt(dec&15));

+	}

+

+	var encode = [

+		function(ch){return "&#"+ch.charCodeAt(0)+";";},

+		function(ch){return "&#x"+char2hex(ch)+";";},

+		function(ch){return ch;}

+	];

+

+	addr = "mailto:" + addr;

+

+	addr = addr.replace(/./g, function(ch) {

+		if (ch == "@") {

+		   	// this *must* be encoded. I insist.

+			ch = encode[Math.floor(Math.random()*2)](ch);

+		} else if (ch !=":") {

+			// leave ':' alone (to spot mailto: later)

+			var r = Math.random();

+			// roughly 10% raw, 45% hex, 45% dec

+			ch =  (

+					r > .9  ?	encode[2](ch)   :

+					r > .45 ?	encode[1](ch)   :

+								encode[0](ch)

+				);

+		}

+		return ch;

+	});

+

+	addr = "<a href=\"" + addr + "\">" + addr + "</a>";

+	addr = addr.replace(/">.+:/g,"\">"); // strip the mailto: from the visible part

+

+	return addr;

+}

+

+

+var _UnescapeSpecialChars = function(text) {

+//

+// Swap back in all the special characters we've hidden.

+//

+	text = text.replace(/~E(\d+)E/g,

+		function(wholeMatch,m1) {

+			var charCodeToReplace = parseInt(m1);

+			return String.fromCharCode(charCodeToReplace);

+		}

+	);

+	return text;

+}

+

+

+var _Outdent = function(text) {

+//

+// Remove one level of line-leading tabs or spaces

+//

+

+	// attacklab: hack around Konqueror 3.5.4 bug:

+	// "----------bug".replace(/^-/g,"") == "bug"

+

+	text = text.replace(/^(\t|[ ]{1,4})/gm,"~0"); // attacklab: g_tab_width

+

+	// attacklab: clean up hack

+	text = text.replace(/~0/g,"")

+

+	return text;

+}

+

+var _Detab = function(text) {

+// attacklab: Detab's completely rewritten for speed.

+// In perl we could fix it by anchoring the regexp with \G.

+// In javascript we're less fortunate.

+

+	// expand first n-1 tabs

+	text = text.replace(/\t(?=\t)/g,"    "); // attacklab: g_tab_width

+

+	// replace the nth with two sentinels

+	text = text.replace(/\t/g,"~A~B");

+

+	// use the sentinel to anchor our regex so it doesn't explode

+	text = text.replace(/~B(.+?)~A/g,

+		function(wholeMatch,m1,m2) {

+			var leadingText = m1;

+			var numSpaces = 4 - leadingText.length % 4;  // attacklab: g_tab_width

+

+			// there *must* be a better way to do this:

+			for (var i=0; i<numSpaces; i++) leadingText+=" ";

+

+			return leadingText;

+		}

+	);

+

+	// clean up sentinels

+	text = text.replace(/~A/g,"    ");  // attacklab: g_tab_width

+	text = text.replace(/~B/g,"");

+

+	return text;

+}

+

+

+//

+//  attacklab: Utility functions

+//

+

+

+var escapeCharacters = function(text, charsToEscape, afterBackslash) {

+	// First we have to escape the escape characters so that

+	// we can build a character class out of them

+	var regexString = "([" + charsToEscape.replace(/([\[\]\\])/g,"\\$1") + "])";

+

+	if (afterBackslash) {

+		regexString = "\\\\" + regexString;

+	}

+

+	var regex = new RegExp(regexString,"g");

+	text = text.replace(regex,escapeCharacters_callback);

+

+	return text;

+}

+

+

+var escapeCharacters_callback = function(wholeMatch,m1) {

+	var charCodeToEscape = m1.charCodeAt(0);

+	return "~E"+charCodeToEscape+"E";

+}

+

+} // end of Showdown.converter

+

+// export

+if (typeof exports != 'undefined') exports.Showdown = Showdown;
\ No newline at end of file
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/Makefile b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/Makefile
new file mode 100644
index 0000000..ed07610
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/Makefile
@@ -0,0 +1,73 @@
+BUILD_DIR = build
+
+PREFIX = .
+SRC_DIR = ${PREFIX}
+DIST_DIR = ${PREFIX}/dist
+
+
+JS_ENGINE ?= `which node nodejs 2> /dev/null`
+COMPILER = ${JS_ENGINE} ${BUILD_DIR}/uglify.js --unsafe
+POST_COMPILER = ${JS_ENGINE} ${BUILD_DIR}/post-compile.js
+
+SRC = ${SRC_DIR}/jsonselect.js
+DIST = ${DIST_DIR}/jsonselect.js
+DIST_MIN = ${DIST_DIR}/jsonselect.min.js
+
+all: hint project min tests
+	@@echo "Project build complete."
+
+${DIST_DIR}:
+	@@mkdir -p ${DIST_DIR}
+
+project: ${DIST}
+
+${DIST}: ${SRC} | ${DIST_DIR}
+	@@echo "Building" ${DIST}
+	@@echo ${SRC}
+	@@cat ${SRC} > ${DIST};
+
+
+min: project ${DIST_MIN}
+
+${DIST_MIN}: ${DIST}
+	@@if test ! -z ${JS_ENGINE}; then \
+		echo "Minifying Project" ${DIST_MIN}; \
+		${COMPILER} ${DIST} > ${DIST_MIN}.tmp; \
+		${POST_COMPILER} ${DIST_MIN}.tmp > ${DIST_MIN}; \
+		rm -f ${DIST_MIN}.tmp; \
+	else \
+		echo "You must have NodeJS installed in order to minify Project."; \
+	fi
+
+
+hint:
+	@@if test ! -z ${JS_ENGINE}; then \
+		echo "Hinting Project"; \
+		${JS_ENGINE} build/jshint-check.js; \
+	else \
+		echo "Nodejs is missing"; \
+	fi
+
+
+test/tests/README.md: 
+	@@cd .. && git submodule init 
+	@@cd .. && git submodule update 
+
+
+tests: test/tests/README.md
+	@@if test ! -z ${JS_ENGINE}; then \
+		echo "Testing Project"; \
+		${JS_ENGINE} test/run.js; \
+	else \
+		echo "nodejs is missing"; \
+	fi
+
+
+clean:
+	@@echo "Removing Distribution directory:" ${DIST_DIR}
+	@@rm -rf ${DIST_DIR}
+
+
+
+
+.PHONY: all project hint min tests
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/jshint-check.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/jshint-check.js
new file mode 100644
index 0000000..312c643
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/jshint-check.js
@@ -0,0 +1,41 @@
+var JSHINT = require("./lib/jshint").JSHINT,
+	print = require("sys").print,
+	src = require("fs").readFileSync("jsonselect.js", "utf8");
+
+JSHINT(src, { evil: true, forin: true, maxerr: 100 });
+
+var ok = {
+
+	// w.reason
+	"Expected an identifier and instead saw 'undefined' (a reserved word).": true,
+	"Use '===' to compare with 'null'.": true,
+	"Use '!==' to compare with 'null'.": true,
+	"Expected an assignment or function call and instead saw an expression.": true,
+	"Expected a 'break' statement before 'case'.": true,
+	"'e' is already defined.": true,
+	// w.raw
+	"Expected an identifier and instead saw \'{a}\' (a reserved word).": true
+};
+
+var e = JSHINT.errors, 
+		found = 0, 
+		w;
+
+for ( var i = 0; i < e.length; i++ ) {
+	w = e[i];
+
+	//console.log( w );
+	if ( !ok[ w.reason ] && !ok[ w.raw ] ) {
+		found++;
+
+		print( "\n " + found + ". Problem at line " + w.line + " character " + w.character + ": " + w.reason );
+		print( "\n" + w.evidence );
+	}
+}
+
+if ( found > 0 ) {
+	print( "\n\n" + found + " Error(s) found.\n" );
+
+} else {
+	print( "JSHint check passed.\n" );
+}
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/lib/jshint.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/lib/jshint.js
new file mode 100644
index 0000000..6c34fbe
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/lib/jshint.js
@@ -0,0 +1,3854 @@
+/*

+ * JSHint, by JSHint Community.

+ *

+ * Licensed under the same slightly modified MIT license that JSLint is.

+ * It stops evil-doers everywhere.

+ *

+ * JSHint is a derivative work of JSLint:

+ *

+ *   Copyright (c) 2002 Douglas Crockford  (www.JSLint.com)

+ *

+ *   Permission is hereby granted, free of charge, to any person obtaining

+ *   a copy of this software and associated documentation files (the "Software"),

+ *   to deal in the Software without restriction, including without limitation

+ *   the rights to use, copy, modify, merge, publish, distribute, sublicense,

+ *   and/or sell copies of the Software, and to permit persons to whom

+ *   the Software is furnished to do so, subject to the following conditions:

+ *

+ *   The above copyright notice and this permission notice shall be included

+ *   in all copies or substantial portions of the Software.

+ *

+ *   The Software shall be used for Good, not Evil.

+ *

+ *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR

+ *   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,

+ *   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE

+ *   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER

+ *   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING

+ *   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER

+ *   DEALINGS IN THE SOFTWARE.

+ *

+ * JSHint was forked from 2010-12-16 edition of JSLint.

+ *

+ */

+

+/*

+ JSHINT is a global function. It takes two parameters.

+

+     var myResult = JSHINT(source, option);

+

+ The first parameter is either a string or an array of strings. If it is a

+ string, it will be split on '\n' or '\r'. If it is an array of strings, it

+ is assumed that each string represents one line. The source can be a

+ JavaScript text or a JSON text.

+

+ The second parameter is an optional object of options which control the

+ operation of JSHINT. Most of the options are booleans: They are all

+ optional and have a default value of false. One of the options, predef,

+ can be an array of names, which will be used to declare global variables,

+ or an object whose keys are used as global names, with a boolean value

+ that determines if they are assignable.

+

+ If it checks out, JSHINT returns true. Otherwise, it returns false.

+

+ If false, you can inspect JSHINT.errors to find out the problems.

+ JSHINT.errors is an array of objects containing these members:

+

+ {

+     line      : The line (relative to 0) at which the lint was found

+     character : The character (relative to 0) at which the lint was found

+     reason    : The problem

+     evidence  : The text line in which the problem occurred

+     raw       : The raw message before the details were inserted

+     a         : The first detail

+     b         : The second detail

+     c         : The third detail

+     d         : The fourth detail

+ }

+

+ If a fatal error was found, a null will be the last element of the

+ JSHINT.errors array.

+

+ You can request a Function Report, which shows all of the functions

+ and the parameters and vars that they use. This can be used to find

+ implied global variables and other problems. The report is in HTML and

+ can be inserted in an HTML <body>.

+

+     var myReport = JSHINT.report(limited);

+

+ If limited is true, then the report will be limited to only errors.

+

+ You can request a data structure which contains JSHint's results.

+

+     var myData = JSHINT.data();

+

+ It returns a structure with this form:

+

+ {

+     errors: [

+         {

+             line: NUMBER,

+             character: NUMBER,

+             reason: STRING,

+             evidence: STRING

+         }

+     ],

+     functions: [

+         name: STRING,

+         line: NUMBER,

+         last: NUMBER,

+         param: [

+             STRING

+         ],

+         closure: [

+             STRING

+         ],

+         var: [

+             STRING

+         ],

+         exception: [

+             STRING

+         ],

+         outer: [

+             STRING

+         ],

+         unused: [

+             STRING

+         ],

+         global: [

+             STRING

+         ],

+         label: [

+             STRING

+         ]

+     ],

+     globals: [

+         STRING

+     ],

+     member: {

+         STRING: NUMBER

+     },

+     unuseds: [

+         {

+             name: STRING,

+             line: NUMBER

+         }

+     ],

+     implieds: [

+         {

+             name: STRING,

+             line: NUMBER

+         }

+     ],

+     urls: [

+         STRING

+     ],

+     json: BOOLEAN

+ }

+

+ Empty arrays will not be included.

+

+*/

+

+/*jshint

+ evil: true, nomen: false, onevar: false, regexp: false, strict: true, boss: true

+*/

+

+/*members "\b", "\t", "\n", "\f", "\r", "!=", "!==", "\"", "%",

+ "(begin)", "(breakage)", "(context)", "(error)", "(global)",

+ "(identifier)", "(last)", "(line)", "(loopage)", "(name)", "(onevar)",

+ "(params)", "(scope)", "(statement)", "(verb)", "*", "+", "++", "-",

+ "--", "\/", "<", "<=", "==", "===", ">", ">=", $, $$, $A, $F, $H, $R, $break,

+ $continue, $w, Abstract, Ajax, __filename, __dirname, ActiveXObject, Array,

+ ArrayBuffer, ArrayBufferView, Autocompleter, Assets, Boolean, Builder,

+ Buffer, Browser, COM, CScript, Canvas, CustomAnimation, Class, Control,

+ Chain, Color, Cookie, Core, DataView, Date, Debug, Draggable, Draggables,

+ Droppables, Document, DomReady, DOMReady, Drag, E, Enumerator, Enumerable,

+ Element, Elements, Error, Effect, EvalError, Event, Events, FadeAnimation,

+ Field, Flash, Float32Array, Float64Array, Form, FormField, Frame, Function,

+ Fx, Group, Hash, HotKey, HTMLElement, HtmlTable, Iframe, IframeShim, Image,

+ Int16Array, Int32Array, Int8Array, Insertion, InputValidator, JSON, Keyboard,

+ Locale, LN10, LN2, LOG10E, LOG2E, MAX_VALUE, MIN_VALUE, Mask, Math, MenuItem,

+ MoveAnimation, MooTools, Native, NEGATIVE_INFINITY, Number, Object,

+ ObjectRange, Option, Options, OverText, PI, POSITIVE_INFINITY,

+ PeriodicalExecuter, Point, Position, Prototype, RangeError, Rectangle,

+ ReferenceError, RegExp, ResizeAnimation, Request, RotateAnimation, SQRT1_2,

+ SQRT2, ScrollBar, Scriptaculous, Scroller, Slick, Slider, Selector, String,

+ Style, SyntaxError, Sortable, Sortables, SortableObserver, Sound, Spinner,

+ System, Swiff, Text, TextArea, Template, Timer, Tips, Type, TypeError,

+ Toggle, Try, URI, URIError, URL, VBArray, WScript, Web, Window, XMLDOM,

+ XMLHttpRequest, XPathEvaluator, XPathException, XPathExpression,

+ XPathNamespace, XPathNSResolver, XPathResult, "\\", a, addEventListener,

+ address, alert,  apply, applicationCache, arguments, arity, asi, b, bitwise,

+ block, blur, boolOptions, boss, browser, c, call, callee, caller, cases,

+ charAt, charCodeAt, character, clearInterval, clearTimeout, close, closed,

+ closure, comment, condition, confirm, console, constructor, content, couch,

+ create, css, curly, d, data, datalist, dd, debug, decodeURI,

+ decodeURIComponent, defaultStatus, defineClass, deserialize, devel,

+ document, edition, else, emit, encodeURI, encodeURIComponent, entityify,

+ eqeqeq, eqnull, errors, es5, escape, eval, event, evidence, evil, ex,

+ exception, exec, exps, expr, exports, FileReader, first, floor, focus,

+ forin, fragment, frames, from, fromCharCode, fud, funct, function, functions,

+ g, gc, getComputedStyle, getRow, GLOBAL, global, globals, globalstrict,

+ hasOwnProperty, help, history, i, id, identifier, immed, implieds,

+ include, indent, indexOf, init, ins, instanceOf, isAlpha,

+ isApplicationRunning, isArray, isDigit, isFinite, isNaN, join, jshint,

+ JSHINT, json, jquery, jQuery, keys, label, labelled, last, laxbreak,

+ latedef, lbp, led, left, length, line, load, loadClass, localStorage,

+ location, log, loopfunc, m, match, maxerr, maxlen, member,message, meta,

+ module, moveBy, moveTo, mootools, name, navigator, new, newcap, noarg,

+ node, noempty, nomen, nonew, nud, onbeforeunload, onblur, onerror, onevar,

+ onfocus, onload, onresize, onunload, open, openDatabase, openURL, opener,

+ opera, outer, param, parent, parseFloat, parseInt, passfail, plusplus,

+ predef, print, process, prompt, prototype, prototypejs, push, quit, range,

+ raw, reach, reason, regexp, readFile, readUrl, removeEventListener, replace,

+ report, require, reserved, resizeBy, resizeTo, resolvePath, resumeUpdates,

+ respond, rhino, right, runCommand, scroll, screen, scrollBy, scrollTo,

+ scrollbar, search, seal, send, serialize, setInterval, setTimeout, shift,

+ slice, sort,spawn, split, stack, status, start, strict, sub, substr, supernew,

+ shadow, supplant, sum, sync, test, toLowerCase, toString, toUpperCase, toint32,

+ token, top, type, typeOf, Uint16Array, Uint32Array, Uint8Array, undef,

+ unused, urls, value, valueOf, var, version, WebSocket, white, window, Worker

+*/

+

+/*global exports: false */

+

+// We build the application inside a function so that we produce only a single

+// global variable. That function will be invoked immediately, and its return

+// value is the JSHINT function itself.

+

+var JSHINT = (function () {

+    "use strict";

+

+    var anonname,       // The guessed name for anonymous functions.

+

+// These are operators that should not be used with the ! operator.

+

+        bang = {

+            '<'  : true,

+            '<=' : true,

+            '==' : true,

+            '===': true,

+            '!==': true,

+            '!=' : true,

+            '>'  : true,

+            '>=' : true,

+            '+'  : true,

+            '-'  : true,

+            '*'  : true,

+            '/'  : true,

+            '%'  : true

+        },

+

+// These are the JSHint boolean options.

+

+        boolOptions = {

+            asi         : true, // if automatic semicolon insertion should be tolerated

+            bitwise     : true, // if bitwise operators should not be allowed

+            boss        : true, // if advanced usage of assignments should be allowed

+            browser     : true, // if the standard browser globals should be predefined

+            couch       : true, // if CouchDB globals should be predefined

+            curly       : true, // if curly braces around blocks should be required (even in if/for/while)

+            debug       : true, // if debugger statements should be allowed

+            devel       : true, // if logging globals should be predefined (console, alert, etc.)

+            eqeqeq      : true, // if === should be required

+            eqnull      : true, // if == null comparisons should be tolerated

+            es5         : true, // if ES5 syntax should be allowed

+            evil        : true, // if eval should be allowed

+            expr        : true, // if ExpressionStatement should be allowed as Programs

+            forin       : true, // if for in statements must filter

+            globalstrict: true, // if global "use strict"; should be allowed (also enables 'strict')

+            immed       : true, // if immediate invocations must be wrapped in parens

+            jquery      : true, // if jQuery globals should be predefined

+            latedef     : true, // if the use before definition should not be tolerated

+            laxbreak    : true, // if line breaks should not be checked

+            loopfunc    : true, // if functions should be allowed to be defined within loops

+            mootools    : true, // if MooTools globals should be predefined

+            newcap      : true, // if constructor names must be capitalized

+            noarg       : true, // if arguments.caller and arguments.callee should be disallowed

+            node        : true, // if the Node.js environment globals should be predefined

+            noempty     : true, // if empty blocks should be disallowed

+            nonew       : true, // if using `new` for side-effects should be disallowed

+            nomen       : true, // if names should be checked

+            onevar      : true, // if only one var statement per function should be allowed

+            passfail    : true, // if the scan should stop on first error

+            plusplus    : true, // if increment/decrement should not be allowed

+            prototypejs : true, // if Prototype and Scriptaculous globals shoudl be predefined

+            regexp      : true, // if the . should not be allowed in regexp literals

+            rhino       : true, // if the Rhino environment globals should be predefined

+            undef       : true, // if variables should be declared before used

+            shadow      : true, // if variable shadowing should be tolerated

+            strict      : true, // require the "use strict"; pragma

+            sub         : true, // if all forms of subscript notation are tolerated

+            supernew    : true, // if `new function () { ... };` and `new Object;` should be tolerated

+            white       : true  // if strict whitespace rules apply

+        },

+

+// browser contains a set of global names which are commonly provided by a

+// web browser environment.

+

+        browser = {

+            ArrayBuffer     : false,

+            ArrayBufferView : false,

+            addEventListener: false,

+            applicationCache: false,

+            blur            : false,

+            clearInterval   : false,

+            clearTimeout    : false,

+            close           : false,

+            closed          : false,

+            DataView        : false,

+            defaultStatus   : false,

+            document        : false,

+            event           : false,

+            FileReader      : false,

+            Float32Array    : false,

+            Float64Array    : false,

+            focus           : false,

+            frames          : false,

+            getComputedStyle: false,

+            HTMLElement     : false,

+            history         : false,

+            Int16Array      : false,

+            Int32Array      : false,

+            Int8Array       : false,

+            Image           : false,

+            length          : false,

+            localStorage    : false,

+            location        : false,

+            moveBy          : false,

+            moveTo          : false,

+            name            : false,

+            navigator       : false,

+            onbeforeunload  : true,

+            onblur          : true,

+            onerror         : true,

+            onfocus         : true,

+            onload          : true,

+            onresize        : true,

+            onunload        : true,

+            open            : false,

+            openDatabase    : false,

+            opener          : false,

+            Option          : false,

+            parent          : false,

+            print           : false,

+            removeEventListener: false,

+            resizeBy        : false,

+            resizeTo        : false,

+            screen          : false,

+            scroll          : false,

+            scrollBy        : false,

+            scrollTo        : false,

+            setInterval     : false,

+            setTimeout      : false,

+            status          : false,

+            top             : false,

+            Uint16Array     : false,

+            Uint32Array     : false,

+            Uint8Array      : false,

+            WebSocket       : false,

+            window          : false,

+            Worker          : false,

+            XMLHttpRequest  : false,

+            XPathEvaluator  : false,

+            XPathException  : false,

+            XPathExpression : false,

+            XPathNamespace  : false,

+            XPathNSResolver : false,

+            XPathResult     : false

+        },

+

+        couch = {

+            "require" : false,

+            respond   : false,

+            getRow    : false,

+            emit      : false,

+            send      : false,

+            start     : false,

+            sum       : false,

+            log       : false,

+            exports   : false,

+            module    : false

+        },

+

+        devel = {

+            alert           : false,

+            confirm         : false,

+            console         : false,

+            Debug           : false,

+            opera           : false,

+            prompt          : false

+        },

+

+        escapes = {

+            '\b': '\\b',

+            '\t': '\\t',

+            '\n': '\\n',

+            '\f': '\\f',

+            '\r': '\\r',

+            '"' : '\\"',

+            '/' : '\\/',

+            '\\': '\\\\'

+        },

+

+        funct,          // The current function

+

+        functionicity = [

+            'closure', 'exception', 'global', 'label',

+            'outer', 'unused', 'var'

+        ],

+

+        functions,      // All of the functions

+

+        global,         // The global scope

+        implied,        // Implied globals

+        inblock,

+        indent,

+        jsonmode,

+

+        jquery = {

+            '$'    : false,

+            jQuery : false

+        },

+

+        lines,

+        lookahead,

+        member,

+        membersOnly,

+

+        mootools = {

+            '$'             : false,

+            '$$'            : false,

+            Assets          : false,

+            Browser         : false,

+            Chain           : false,

+            Class           : false,

+            Color           : false,

+            Cookie          : false,

+            Core            : false,

+            Document        : false,

+            DomReady        : false,

+            DOMReady        : false,

+            Drag            : false,

+            Element         : false,

+            Elements        : false,

+            Event           : false,

+            Events          : false,

+            Fx              : false,

+            Group           : false,

+            Hash            : false,

+            HtmlTable       : false,

+            Iframe          : false,

+            IframeShim      : false,

+            InputValidator  : false,

+            instanceOf      : false,

+            Keyboard        : false,

+            Locale          : false,

+            Mask            : false,

+            MooTools        : false,

+            Native          : false,

+            Options         : false,

+            OverText        : false,

+            Request         : false,

+            Scroller        : false,

+            Slick           : false,

+            Slider          : false,

+            Sortables       : false,

+            Spinner         : false,

+            Swiff           : false,

+            Tips            : false,

+            Type            : false,

+            typeOf          : false,

+            URI             : false,

+            Window          : false

+        },

+

+        nexttoken,

+

+        node = {

+            __filename  : false,

+            __dirname   : false,

+            exports     : false,

+            Buffer      : false,

+            GLOBAL      : false,

+            global      : false,

+            module      : false,

+            process     : false,

+            require     : false

+        },

+

+        noreach,

+        option,

+        predefined,     // Global variables defined by option

+        prereg,

+        prevtoken,

+

+        prototypejs = {

+            '$'               : false,

+            '$$'              : false,

+            '$A'              : false,

+            '$F'              : false,

+            '$H'              : false,

+            '$R'              : false,

+            '$break'          : false,

+            '$continue'       : false,

+            '$w'              : false,

+            Abstract          : false,

+            Ajax              : false,

+            Class             : false,

+            Enumerable        : false,

+            Element           : false,

+            Event             : false,

+            Field             : false,

+            Form              : false,

+            Hash              : false,

+            Insertion         : false,

+            ObjectRange       : false,

+            PeriodicalExecuter: false,

+            Position          : false,

+            Prototype         : false,

+            Selector          : false,

+            Template          : false,

+            Toggle            : false,

+            Try               : false,

+            Autocompleter     : false,

+            Builder           : false,

+            Control           : false,

+            Draggable         : false,

+            Draggables        : false,

+            Droppables        : false,

+            Effect            : false,

+            Sortable          : false,

+            SortableObserver  : false,

+            Sound             : false,

+            Scriptaculous     : false

+        },

+

+        rhino = {

+            defineClass : false,

+            deserialize : false,

+            gc          : false,

+            help        : false,

+            load        : false,

+            loadClass   : false,

+            print       : false,

+            quit        : false,

+            readFile    : false,

+            readUrl     : false,

+            runCommand  : false,

+            seal        : false,

+            serialize   : false,

+            spawn       : false,

+            sync        : false,

+            toint32     : false,

+            version     : false

+        },

+

+        scope,      // The current scope

+        src,

+        stack,

+

+// standard contains the global names that are provided by the

+// ECMAScript standard.

+

+        standard = {

+            Array               : false,

+            Boolean             : false,

+            Date                : false,

+            decodeURI           : false,

+            decodeURIComponent  : false,

+            encodeURI           : false,

+            encodeURIComponent  : false,

+            Error               : false,

+            'eval'              : false,

+            EvalError           : false,

+            Function            : false,

+            hasOwnProperty      : false,

+            isFinite            : false,

+            isNaN               : false,

+            JSON                : false,

+            Math                : false,

+            Number              : false,

+            Object              : false,

+            parseInt            : false,

+            parseFloat          : false,

+            RangeError          : false,

+            ReferenceError      : false,

+            RegExp              : false,

+            String              : false,

+            SyntaxError         : false,

+            TypeError           : false,

+            URIError            : false

+        },

+

+        standard_member = {

+            E                   : true,

+            LN2                 : true,

+            LN10                : true,

+            LOG2E               : true,

+            LOG10E              : true,

+            MAX_VALUE           : true,

+            MIN_VALUE           : true,

+            NEGATIVE_INFINITY   : true,

+            PI                  : true,

+            POSITIVE_INFINITY   : true,

+            SQRT1_2             : true,

+            SQRT2               : true

+        },

+

+        strict_mode,

+        syntax = {},

+        tab,

+        token,

+        urls,

+        warnings,

+

+// Regular expressions. Some of these are stupidly long.

+

+// unsafe comment or string

+        ax = /@cc|<\/?|script|\]\s*\]|<\s*!|&lt/i,

+// unsafe characters that are silently deleted by one or more browsers

+        cx = /[\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/,

+// token

+        tx = /^\s*([(){}\[.,:;'"~\?\]#@]|==?=?|\/(\*(jshint|jslint|members?|global)?|=|\/)?|\*[\/=]?|\+(?:=|\++)?|-(?:=|-+)?|%=?|&[&=]?|\|[|=]?|>>?>?=?|<([\/=!]|\!(\[|--)?|<=?)?|\^=?|\!=?=?|[a-zA-Z_$][a-zA-Z0-9_$]*|[0-9]+([xX][0-9a-fA-F]+|\.[0-9]*)?([eE][+\-]?[0-9]+)?)/,

+// characters in strings that need escapement

+        nx = /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/,

+        nxg = /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,

+// star slash

+        lx = /\*\/|\/\*/,

+// identifier

+        ix = /^([a-zA-Z_$][a-zA-Z0-9_$]*)$/,

+// javascript url

+        jx = /^(?:javascript|jscript|ecmascript|vbscript|mocha|livescript)\s*:/i,

+// catches /* falls through */ comments

+        ft = /^\s*\/\*\s*falls\sthrough\s*\*\/\s*$/;

+

+    function F() {}     // Used by Object.create

+

+    function is_own(object, name) {

+

+// The object.hasOwnProperty method fails when the property under consideration

+// is named 'hasOwnProperty'. So we have to use this more convoluted form.

+

+        return Object.prototype.hasOwnProperty.call(object, name);

+    }

+

+// Provide critical ES5 functions to ES3.

+

+    if (typeof Array.isArray !== 'function') {

+        Array.isArray = function (o) {

+            return Object.prototype.toString.apply(o) === '[object Array]';

+        };

+    }

+

+    if (typeof Object.create !== 'function') {

+        Object.create = function (o) {

+            F.prototype = o;

+            return new F();

+        };

+    }

+

+    if (typeof Object.keys !== 'function') {

+        Object.keys = function (o) {

+            var a = [], k;

+            for (k in o) {

+                if (is_own(o, k)) {

+                    a.push(k);

+                }

+            }

+            return a;

+        };

+    }

+

+// Non standard methods

+

+    if (typeof String.prototype.entityify !== 'function') {

+        String.prototype.entityify = function () {

+            return this

+                .replace(/&/g, '&amp;')

+                .replace(/</g, '&lt;')

+                .replace(/>/g, '&gt;');

+        };

+    }

+

+    if (typeof String.prototype.isAlpha !== 'function') {

+        String.prototype.isAlpha = function () {

+            return (this >= 'a' && this <= 'z\uffff') ||

+                (this >= 'A' && this <= 'Z\uffff');

+        };

+    }

+

+    if (typeof String.prototype.isDigit !== 'function') {

+        String.prototype.isDigit = function () {

+            return (this >= '0' && this <= '9');

+        };

+    }

+

+    if (typeof String.prototype.supplant !== 'function') {

+        String.prototype.supplant = function (o) {

+            return this.replace(/\{([^{}]*)\}/g, function (a, b) {

+                var r = o[b];

+                return typeof r === 'string' || typeof r === 'number' ? r : a;

+            });

+        };

+    }

+

+    if (typeof String.prototype.name !== 'function') {

+        String.prototype.name = function () {

+

+// If the string looks like an identifier, then we can return it as is.

+// If the string contains no control characters, no quote characters, and no

+// backslash characters, then we can simply slap some quotes around it.

+// Otherwise we must also replace the offending characters with safe

+// sequences.

+

+            if (ix.test(this)) {

+                return this;

+            }

+            if (nx.test(this)) {

+                return '"' + this.replace(nxg, function (a) {

+                    var c = escapes[a];

+                    if (c) {

+                        return c;

+                    }

+                    return '\\u' + ('0000' + a.charCodeAt().toString(16)).slice(-4);

+                }) + '"';

+            }

+            return '"' + this + '"';

+        };

+    }

+

+

+    function combine(t, o) {

+        var n;

+        for (n in o) {

+            if (is_own(o, n)) {

+                t[n] = o[n];

+            }

+        }

+    }

+

+    function assume() {

+        if (option.couch)

+            combine(predefined, couch);

+

+        if (option.rhino)

+            combine(predefined, rhino);

+

+        if (option.prototypejs)

+            combine(predefined, prototypejs);

+

+        if (option.node)

+            combine(predefined, node);

+

+        if (option.devel)

+            combine(predefined, devel);

+

+        if (option.browser)

+            combine(predefined, browser);

+

+        if (option.jquery)

+            combine(predefined, jquery);

+

+        if (option.mootools)

+            combine(predefined, mootools);

+

+        if (option.globalstrict)

+            option.strict = true;

+    }

+

+

+// Produce an error warning.

+

+    function quit(m, l, ch) {

+        throw {

+            name: 'JSHintError',

+            line: l,

+            character: ch,

+            message: m + " (" + Math.floor((l / lines.length) * 100) +

+                    "% scanned)."

+        };

+    }

+

+    function warning(m, t, a, b, c, d) {

+        var ch, l, w;

+        t = t || nexttoken;

+        if (t.id === '(end)') {  // `~

+            t = token;

+        }

+        l = t.line || 0;

+        ch = t.from || 0;

+        w = {

+            id: '(error)',

+            raw: m,

+            evidence: lines[l - 1] || '',

+            line: l,

+            character: ch,

+            a: a,

+            b: b,

+            c: c,

+            d: d

+        };

+        w.reason = m.supplant(w);

+        JSHINT.errors.push(w);

+        if (option.passfail) {

+            quit('Stopping. ', l, ch);

+        }

+        warnings += 1;

+        if (warnings >= option.maxerr) {

+            quit("Too many errors.", l, ch);

+        }

+        return w;

+    }

+

+    function warningAt(m, l, ch, a, b, c, d) {

+        return warning(m, {

+            line: l,

+            from: ch

+        }, a, b, c, d);

+    }

+

+    function error(m, t, a, b, c, d) {

+        var w = warning(m, t, a, b, c, d);

+        quit("Stopping, unable to continue.", w.line, w.character);

+    }

+

+    function errorAt(m, l, ch, a, b, c, d) {

+        return error(m, {

+            line: l,

+            from: ch

+        }, a, b, c, d);

+    }

+

+

+

+// lexical analysis and token construction

+

+    var lex = (function lex() {

+        var character, from, line, s;

+

+// Private lex methods

+

+        function nextLine() {

+            var at,

+                tw; // trailing whitespace check

+

+            if (line >= lines.length)

+                return false;

+

+            character = 1;

+            s = lines[line];

+            line += 1;

+            at = s.search(/ \t/);

+

+            if (at >= 0)

+                warningAt("Mixed spaces and tabs.", line, at + 1);

+

+            s = s.replace(/\t/g, tab);

+            at = s.search(cx);

+

+            if (at >= 0)

+                warningAt("Unsafe character.", line, at);

+

+            if (option.maxlen && option.maxlen < s.length)

+                warningAt("Line too long.", line, s.length);

+

+            // Check for trailing whitespaces

+            tw = s.search(/\s+$/);

+            if (option.white && ~tw)

+                warningAt("Trailing whitespace.", line, tw);

+

+            return true;

+        }

+

+// Produce a token object.  The token inherits from a syntax symbol.

+

+        function it(type, value) {

+            var i, t;

+            if (type === '(color)' || type === '(range)') {

+                t = {type: type};

+            } else if (type === '(punctuator)' ||

+                    (type === '(identifier)' && is_own(syntax, value))) {

+                t = syntax[value] || syntax['(error)'];

+            } else {

+                t = syntax[type];

+            }

+            t = Object.create(t);

+            if (type === '(string)' || type === '(range)') {

+                if (jx.test(value)) {

+                    warningAt("Script URL.", line, from);

+                }

+            }

+            if (type === '(identifier)') {

+                t.identifier = true;

+                if (value === '__iterator__' || value === '__proto__') {

+                    errorAt("Reserved name '{a}'.",

+                        line, from, value);

+                } else if (option.nomen &&

+                        (value.charAt(0) === '_' ||

+                         value.charAt(value.length - 1) === '_')) {

+                    warningAt("Unexpected {a} in '{b}'.", line, from,

+                        "dangling '_'", value);

+                }

+            }

+            t.value = value;

+            t.line = line;

+            t.character = character;

+            t.from = from;

+            i = t.id;

+            if (i !== '(endline)') {

+                prereg = i &&

+                    (('(,=:[!&|?{};'.indexOf(i.charAt(i.length - 1)) >= 0) ||

+                    i === 'return');

+            }

+            return t;

+        }

+

+// Public lex methods

+

+        return {

+            init: function (source) {

+                if (typeof source === 'string') {

+                    lines = source

+                        .replace(/\r\n/g, '\n')

+                        .replace(/\r/g, '\n')

+                        .split('\n');

+                } else {

+                    lines = source;

+                }

+

+                // If the first line is a shebang (#!), make it a blank and move on.

+                // Shebangs are used by Node scripts.

+                if (lines[0] && lines[0].substr(0, 2) == '#!')

+                    lines[0] = '';

+

+                line = 0;

+                nextLine();

+                from = 1;

+            },

+

+            range: function (begin, end) {

+                var c, value = '';

+                from = character;

+                if (s.charAt(0) !== begin) {

+                    errorAt("Expected '{a}' and instead saw '{b}'.",

+                            line, character, begin, s.charAt(0));

+                }

+                for (;;) {

+                    s = s.slice(1);

+                    character += 1;

+                    c = s.charAt(0);

+                    switch (c) {

+                    case '':

+                        errorAt("Missing '{a}'.", line, character, c);

+                        break;

+                    case end:

+                        s = s.slice(1);

+                        character += 1;

+                        return it('(range)', value);

+                    case '\\':

+                        warningAt("Unexpected '{a}'.", line, character, c);

+                    }

+                    value += c;

+                }

+

+            },

+

+// token -- this is called by advance to get the next token.

+

+            token: function () {

+                var b, c, captures, d, depth, high, i, l, low, q, t;

+

+                function match(x) {

+                    var r = x.exec(s), r1;

+                    if (r) {

+                        l = r[0].length;

+                        r1 = r[1];

+                        c = r1.charAt(0);

+                        s = s.substr(l);

+                        from = character + l - r1.length;

+                        character += l;

+                        return r1;

+                    }

+                }

+

+                function string(x) {

+                    var c, j, r = '';

+

+                    if (jsonmode && x !== '"') {

+                        warningAt("Strings must use doublequote.",

+                                line, character);

+                    }

+

+                    function esc(n) {

+                        var i = parseInt(s.substr(j + 1, n), 16);

+                        j += n;

+                        if (i >= 32 && i <= 126 &&

+                                i !== 34 && i !== 92 && i !== 39) {

+                            warningAt("Unnecessary escapement.", line, character);

+                        }

+                        character += n;

+                        c = String.fromCharCode(i);

+                    }

+                    j = 0;

+                    for (;;) {

+                        while (j >= s.length) {

+                            j = 0;

+                            if (!nextLine()) {

+                                errorAt("Unclosed string.", line, from);

+                            }

+                        }

+                        c = s.charAt(j);

+                        if (c === x) {

+                            character += 1;

+                            s = s.substr(j + 1);

+                            return it('(string)', r, x);

+                        }

+                        if (c < ' ') {

+                            if (c === '\n' || c === '\r') {

+                                break;

+                            }

+                            warningAt("Control character in string: {a}.",

+                                    line, character + j, s.slice(0, j));

+                        } else if (c === '\\') {

+                            j += 1;

+                            character += 1;

+                            c = s.charAt(j);

+                            switch (c) {

+                            case '\\':

+                            case '"':

+                            case '/':

+                                break;

+                            case '\'':

+                                if (jsonmode) {

+                                    warningAt("Avoid \\'.", line, character);

+                                }

+                                break;

+                            case 'b':

+                                c = '\b';

+                                break;

+                            case 'f':

+                                c = '\f';

+                                break;

+                            case 'n':

+                                c = '\n';

+                                break;

+                            case 'r':

+                                c = '\r';

+                                break;

+                            case 't':

+                                c = '\t';

+                                break;

+                            case 'u':

+                                esc(4);

+                                break;

+                            case 'v':

+                                if (jsonmode) {

+                                    warningAt("Avoid \\v.", line, character);

+                                }

+                                c = '\v';

+                                break;

+                            case 'x':

+                                if (jsonmode) {

+                                    warningAt("Avoid \\x-.", line, character);

+                                }

+                                esc(2);

+                                break;

+                            default:

+                                warningAt("Bad escapement.", line, character);

+                            }

+                        }

+                        r += c;

+                        character += 1;

+                        j += 1;

+                    }

+                }

+

+                for (;;) {

+                    if (!s) {

+                        return it(nextLine() ? '(endline)' : '(end)', '');

+                    }

+                    t = match(tx);

+                    if (!t) {

+                        t = '';

+                        c = '';

+                        while (s && s < '!') {

+                            s = s.substr(1);

+                        }

+                        if (s) {

+                            errorAt("Unexpected '{a}'.", line, character, s.substr(0, 1));

+                        }

+                    } else {

+

+    //      identifier

+

+                        if (c.isAlpha() || c === '_' || c === '$') {

+                            return it('(identifier)', t);

+                        }

+

+    //      number

+

+                        if (c.isDigit()) {

+                            if (!isFinite(Number(t))) {

+                                warningAt("Bad number '{a}'.",

+                                    line, character, t);

+                            }

+                            if (s.substr(0, 1).isAlpha()) {

+                                warningAt("Missing space after '{a}'.",

+                                        line, character, t);

+                            }

+                            if (c === '0') {

+                                d = t.substr(1, 1);

+                                if (d.isDigit()) {

+                                    if (token.id !== '.') {

+                                        warningAt("Don't use extra leading zeros '{a}'.",

+                                            line, character, t);

+                                    }

+                                } else if (jsonmode && (d === 'x' || d === 'X')) {

+                                    warningAt("Avoid 0x-. '{a}'.",

+                                            line, character, t);

+                                }

+                            }

+                            if (t.substr(t.length - 1) === '.') {

+                                warningAt(

+"A trailing decimal point can be confused with a dot '{a}'.", line, character, t);

+                            }

+                            return it('(number)', t);

+                        }

+                        switch (t) {

+

+    //      string

+

+                        case '"':

+                        case "'":

+                            return string(t);

+

+    //      // comment

+

+                        case '//':

+                            if (src) {

+                                warningAt("Unexpected comment.", line, character);

+                            }

+                            s = '';

+                            token.comment = true;

+                            break;

+

+    //      /* comment

+

+                        case '/*':

+                            if (src) {

+                                warningAt("Unexpected comment.", line, character);

+                            }

+                            for (;;) {

+                                i = s.search(lx);

+                                if (i >= 0) {

+                                    break;

+                                }

+                                if (!nextLine()) {

+                                    errorAt("Unclosed comment.", line, character);

+                                }

+                            }

+                            character += i + 2;

+                            if (s.substr(i, 1) === '/') {

+                                errorAt("Nested comment.", line, character);

+                            }

+                            s = s.substr(i + 2);

+                            token.comment = true;

+                            break;

+

+    //      /*members /*jshint /*global

+

+                        case '/*members':

+                        case '/*member':

+                        case '/*jshint':

+                        case '/*jslint':

+                        case '/*global':

+                        case '*/':

+                            return {

+                                value: t,

+                                type: 'special',

+                                line: line,

+                                character: character,

+                                from: from

+                            };

+

+                        case '':

+                            break;

+    //      /

+                        case '/':

+                            if (token.id === '/=') {

+                                errorAt(

+"A regular expression literal can be confused with '/='.", line, from);

+                            }

+                            if (prereg) {

+                                depth = 0;

+                                captures = 0;

+                                l = 0;

+                                for (;;) {

+                                    b = true;

+                                    c = s.charAt(l);

+                                    l += 1;

+                                    switch (c) {

+                                    case '':

+                                        errorAt("Unclosed regular expression.",

+                                                line, from);

+                                        return;

+                                    case '/':

+                                        if (depth > 0) {

+                                            warningAt("Unescaped '{a}'.",

+                                                    line, from + l, '/');

+                                        }

+                                        c = s.substr(0, l - 1);

+                                        q = {

+                                            g: true,

+                                            i: true,

+                                            m: true

+                                        };

+                                        while (q[s.charAt(l)] === true) {

+                                            q[s.charAt(l)] = false;

+                                            l += 1;

+                                        }

+                                        character += l;

+                                        s = s.substr(l);

+                                        q = s.charAt(0);

+                                        if (q === '/' || q === '*') {

+                                            errorAt("Confusing regular expression.",

+                                                    line, from);

+                                        }

+                                        return it('(regexp)', c);

+                                    case '\\':

+                                        c = s.charAt(l);

+                                        if (c < ' ') {

+                                            warningAt(

+"Unexpected control character in regular expression.", line, from + l);

+                                        } else if (c === '<') {

+                                            warningAt(

+"Unexpected escaped character '{a}' in regular expression.", line, from + l, c);

+                                        }

+                                        l += 1;

+                                        break;

+                                    case '(':

+                                        depth += 1;

+                                        b = false;

+                                        if (s.charAt(l) === '?') {

+                                            l += 1;

+                                            switch (s.charAt(l)) {

+                                            case ':':

+                                            case '=':

+                                            case '!':

+                                                l += 1;

+                                                break;

+                                            default:

+                                                warningAt(

+"Expected '{a}' and instead saw '{b}'.", line, from + l, ':', s.charAt(l));

+                                            }

+                                        } else {

+                                            captures += 1;

+                                        }

+                                        break;

+                                    case '|':

+                                        b = false;

+                                        break;

+                                    case ')':

+                                        if (depth === 0) {

+                                            warningAt("Unescaped '{a}'.",

+                                                    line, from + l, ')');

+                                        } else {

+                                            depth -= 1;

+                                        }

+                                        break;

+                                    case ' ':

+                                        q = 1;

+                                        while (s.charAt(l) === ' ') {

+                                            l += 1;

+                                            q += 1;

+                                        }

+                                        if (q > 1) {

+                                            warningAt(

+"Spaces are hard to count. Use {{a}}.", line, from + l, q);

+                                        }

+                                        break;

+                                    case '[':

+                                        c = s.charAt(l);

+                                        if (c === '^') {

+                                            l += 1;

+                                            if (option.regexp) {

+                                                warningAt("Insecure '{a}'.",

+                                                        line, from + l, c);

+                                            } else if (s.charAt(l) === ']') {

+                                                errorAt("Unescaped '{a}'.",

+                                                    line, from + l, '^');

+                                            }

+                                        }

+                                        q = false;

+                                        if (c === ']') {

+                                            warningAt("Empty class.", line,

+                                                    from + l - 1);

+                                            q = true;

+                                        }

+klass:                                  do {

+                                            c = s.charAt(l);

+                                            l += 1;

+                                            switch (c) {

+                                            case '[':

+                                            case '^':

+                                                warningAt("Unescaped '{a}'.",

+                                                        line, from + l, c);

+                                                q = true;

+                                                break;

+                                            case '-':

+                                                if (q) {

+                                                    q = false;

+                                                } else {

+                                                    warningAt("Unescaped '{a}'.",

+                                                            line, from + l, '-');

+                                                    q = true;

+                                                }

+                                                break;

+                                            case ']':

+                                                if (!q) {

+                                                    warningAt("Unescaped '{a}'.",

+                                                            line, from + l - 1, '-');

+                                                }

+                                                break klass;

+                                            case '\\':

+                                                c = s.charAt(l);

+                                                if (c < ' ') {

+                                                    warningAt(

+"Unexpected control character in regular expression.", line, from + l);

+                                                } else if (c === '<') {

+                                                    warningAt(

+"Unexpected escaped character '{a}' in regular expression.", line, from + l, c);

+                                                }

+                                                l += 1;

+                                                q = true;

+                                                break;

+                                            case '/':

+                                                warningAt("Unescaped '{a}'.",

+                                                        line, from + l - 1, '/');

+                                                q = true;

+                                                break;

+                                            case '<':

+                                                q = true;

+                                                break;

+                                            default:

+                                                q = true;

+                                            }

+                                        } while (c);

+                                        break;

+                                    case '.':

+                                        if (option.regexp) {

+                                            warningAt("Insecure '{a}'.", line,

+                                                    from + l, c);

+                                        }

+                                        break;

+                                    case ']':

+                                    case '?':

+                                    case '{':

+                                    case '}':

+                                    case '+':

+                                    case '*':

+                                        warningAt("Unescaped '{a}'.", line,

+                                                from + l, c);

+                                    }

+                                    if (b) {

+                                        switch (s.charAt(l)) {

+                                        case '?':

+                                        case '+':

+                                        case '*':

+                                            l += 1;

+                                            if (s.charAt(l) === '?') {

+                                                l += 1;

+                                            }

+                                            break;

+                                        case '{':

+                                            l += 1;

+                                            c = s.charAt(l);

+                                            if (c < '0' || c > '9') {

+                                                warningAt(

+"Expected a number and instead saw '{a}'.", line, from + l, c);

+                                            }

+                                            l += 1;

+                                            low = +c;

+                                            for (;;) {

+                                                c = s.charAt(l);

+                                                if (c < '0' || c > '9') {

+                                                    break;

+                                                }

+                                                l += 1;

+                                                low = +c + (low * 10);

+                                            }

+                                            high = low;

+                                            if (c === ',') {

+                                                l += 1;

+                                                high = Infinity;

+                                                c = s.charAt(l);

+                                                if (c >= '0' && c <= '9') {

+                                                    l += 1;

+                                                    high = +c;

+                                                    for (;;) {

+                                                        c = s.charAt(l);

+                                                        if (c < '0' || c > '9') {

+                                                            break;

+                                                        }

+                                                        l += 1;

+                                                        high = +c + (high * 10);

+                                                    }

+                                                }

+                                            }

+                                            if (s.charAt(l) !== '}') {

+                                                warningAt(

+"Expected '{a}' and instead saw '{b}'.", line, from + l, '}', c);

+                                            } else {

+                                                l += 1;

+                                            }

+                                            if (s.charAt(l) === '?') {

+                                                l += 1;

+                                            }

+                                            if (low > high) {

+                                                warningAt(

+"'{a}' should not be greater than '{b}'.", line, from + l, low, high);

+                                            }

+                                        }

+                                    }

+                                }

+                                c = s.substr(0, l - 1);

+                                character += l;

+                                s = s.substr(l);

+                                return it('(regexp)', c);

+                            }

+                            return it('(punctuator)', t);

+

+    //      punctuator

+

+                        case '#':

+                            return it('(punctuator)', t);

+                        default:

+                            return it('(punctuator)', t);

+                        }

+                    }

+                }

+            }

+        };

+    }());

+

+

+    function addlabel(t, type) {

+

+        if (t === 'hasOwnProperty') {

+            warning("'hasOwnProperty' is a really bad name.");

+        }

+

+// Define t in the current function in the current scope.

+

+        if (is_own(funct, t) && !funct['(global)']) {

+            if (funct[t] === true) {

+                if (option.latedef)

+                    warning("'{a}' was used before it was defined.", nexttoken, t);

+            } else {

+                if (!option.shadow)

+                    warning("'{a}' is already defined.", nexttoken, t);

+            }

+        }

+

+        funct[t] = type;

+        if (funct['(global)']) {

+            global[t] = funct;

+            if (is_own(implied, t)) {

+                if (option.latedef)

+                    warning("'{a}' was used before it was defined.", nexttoken, t);

+                delete implied[t];

+            }

+        } else {

+            scope[t] = funct;

+        }

+    }

+

+

+    function doOption() {

+        var b, obj, filter, o = nexttoken.value, t, v;

+        switch (o) {

+        case '*/':

+            error("Unbegun comment.");

+            break;

+        case '/*members':

+        case '/*member':

+            o = '/*members';

+            if (!membersOnly) {

+                membersOnly = {};

+            }

+            obj = membersOnly;

+            break;

+        case '/*jshint':

+        case '/*jslint':

+            obj = option;

+            filter = boolOptions;

+            break;

+        case '/*global':

+            obj = predefined;

+            break;

+        default:

+            error("What?");

+        }

+        t = lex.token();

+loop:   for (;;) {

+            for (;;) {

+                if (t.type === 'special' && t.value === '*/') {

+                    break loop;

+                }

+                if (t.id !== '(endline)' && t.id !== ',') {

+                    break;

+                }

+                t = lex.token();

+            }

+            if (t.type !== '(string)' && t.type !== '(identifier)' &&

+                    o !== '/*members') {

+                error("Bad option.", t);

+            }

+            v = lex.token();

+            if (v.id === ':') {

+                v = lex.token();

+                if (obj === membersOnly) {

+                    error("Expected '{a}' and instead saw '{b}'.",

+                            t, '*/', ':');

+                }

+                if (t.value === 'indent' && (o === '/*jshint' || o === '/*jslint')) {

+                    b = +v.value;

+                    if (typeof b !== 'number' || !isFinite(b) || b <= 0 ||

+                            Math.floor(b) !== b) {

+                        error("Expected a small integer and instead saw '{a}'.",

+                                v, v.value);

+                    }

+                    obj.white = true;

+                    obj.indent = b;

+                } else if (t.value === 'maxerr' && (o === '/*jshint' || o === '/*jslint')) {

+                    b = +v.value;

+                    if (typeof b !== 'number' || !isFinite(b) || b <= 0 ||

+                            Math.floor(b) !== b) {

+                        error("Expected a small integer and instead saw '{a}'.",

+                                v, v.value);

+                    }

+                    obj.maxerr = b;

+                } else if (t.value === 'maxlen' && (o === '/*jshint' || o === '/*jslint')) {

+                    b = +v.value;

+                    if (typeof b !== 'number' || !isFinite(b) || b <= 0 ||

+                            Math.floor(b) !== b) {

+                        error("Expected a small integer and instead saw '{a}'.",

+                                v, v.value);

+                    }

+                    obj.maxlen = b;

+                } else if (v.value === 'true') {

+                    obj[t.value] = true;

+                } else if (v.value === 'false') {

+                    obj[t.value] = false;

+                } else {

+                    error("Bad option value.", v);

+                }

+                t = lex.token();

+            } else {

+                if (o === '/*jshint' || o === '/*jslint') {

+                    error("Missing option value.", t);

+                }

+                obj[t.value] = false;

+                t = v;

+            }

+        }

+        if (filter) {

+            assume();

+        }

+    }

+

+

+// We need a peek function. If it has an argument, it peeks that much farther

+// ahead. It is used to distinguish

+//     for ( var i in ...

+// from

+//     for ( var i = ...

+

+    function peek(p) {

+        var i = p || 0, j = 0, t;

+

+        while (j <= i) {

+            t = lookahead[j];

+            if (!t) {

+                t = lookahead[j] = lex.token();

+            }

+            j += 1;

+        }

+        return t;

+    }

+

+

+

+// Produce the next token. It looks for programming errors.

+

+    function advance(id, t) {

+        switch (token.id) {

+        case '(number)':

+            if (nexttoken.id === '.') {

+                warning("A dot following a number can be confused with a decimal point.", token);

+            }

+            break;

+        case '-':

+            if (nexttoken.id === '-' || nexttoken.id === '--') {

+                warning("Confusing minusses.");

+            }

+            break;

+        case '+':

+            if (nexttoken.id === '+' || nexttoken.id === '++') {

+                warning("Confusing plusses.");

+            }

+            break;

+        }

+        if (token.type === '(string)' || token.identifier) {

+            anonname = token.value;

+        }

+

+        if (id && nexttoken.id !== id) {

+            if (t) {

+                if (nexttoken.id === '(end)') {

+                    warning("Unmatched '{a}'.", t, t.id);

+                } else {

+                    warning("Expected '{a}' to match '{b}' from line {c} and instead saw '{d}'.",

+                            nexttoken, id, t.id, t.line, nexttoken.value);

+                }

+            } else if (nexttoken.type !== '(identifier)' ||

+                            nexttoken.value !== id) {

+                warning("Expected '{a}' and instead saw '{b}'.",

+                        nexttoken, id, nexttoken.value);

+            }

+        }

+        prevtoken = token;

+        token = nexttoken;

+        for (;;) {

+            nexttoken = lookahead.shift() || lex.token();

+            if (nexttoken.id === '(end)' || nexttoken.id === '(error)') {

+                return;

+            }

+            if (nexttoken.type === 'special') {

+                doOption();

+            } else {

+                if (nexttoken.id !== '(endline)') {

+                    break;

+                }

+            }

+        }

+    }

+

+

+// This is the heart of JSHINT, the Pratt parser. In addition to parsing, it

+// is looking for ad hoc lint patterns. We add .fud to Pratt's model, which is

+// like .nud except that it is only used on the first token of a statement.

+// Having .fud makes it much easier to define statement-oriented languages like

+// JavaScript. I retained Pratt's nomenclature.

+

+// .nud     Null denotation

+// .fud     First null denotation

+// .led     Left denotation

+//  lbp     Left binding power

+//  rbp     Right binding power

+

+// They are elements of the parsing method called Top Down Operator Precedence.

+

+    function expression(rbp, initial) {

+        var left, isArray = false;

+

+        if (nexttoken.id === '(end)')

+            error("Unexpected early end of program.", token);

+

+        advance();

+        if (initial) {

+            anonname = 'anonymous';

+            funct['(verb)'] = token.value;

+        }

+        if (initial === true && token.fud) {

+            left = token.fud();

+        } else {

+            if (token.nud) {

+                left = token.nud();

+            } else {

+                if (nexttoken.type === '(number)' && token.id === '.') {

+                    warning("A leading decimal point can be confused with a dot: '.{a}'.",

+                            token, nexttoken.value);

+                    advance();

+                    return token;

+                } else {

+                    error("Expected an identifier and instead saw '{a}'.",

+                            token, token.id);

+                }

+            }

+            while (rbp < nexttoken.lbp) {

+                isArray = token.value == 'Array';

+                advance();

+                if (isArray && token.id == '(' && nexttoken.id == ')')

+                    warning("Use the array literal notation [].", token);

+                if (token.led) {

+                    left = token.led(left);

+                } else {

+                    error("Expected an operator and instead saw '{a}'.",

+                        token, token.id);

+                }

+            }

+        }

+        return left;

+    }

+

+

+// Functions for conformance of style.

+

+    function adjacent(left, right) {

+        left = left || token;

+        right = right || nexttoken;

+        if (option.white) {

+            if (left.character !== right.from && left.line === right.line) {

+                warning("Unexpected space after '{a}'.", right, left.value);

+            }

+        }

+    }

+

+    function nobreak(left, right) {

+        left = left || token;

+        right = right || nexttoken;

+        if (option.white && (left.character !== right.from || left.line !== right.line)) {

+            warning("Unexpected space before '{a}'.", right, right.value);

+        }

+    }

+

+    function nospace(left, right) {

+        left = left || token;

+        right = right || nexttoken;

+        if (option.white && !left.comment) {

+            if (left.line === right.line) {

+                adjacent(left, right);

+            }

+        }

+    }

+

+    function nonadjacent(left, right) {

+        if (option.white) {

+            left = left || token;

+            right = right || nexttoken;

+            if (left.line === right.line && left.character === right.from) {

+                warning("Missing space after '{a}'.",

+                        nexttoken, left.value);

+            }

+        }

+    }

+

+    function nobreaknonadjacent(left, right) {

+        left = left || token;

+        right = right || nexttoken;

+        if (!option.laxbreak && left.line !== right.line) {

+            warning("Bad line breaking before '{a}'.", right, right.id);

+        } else if (option.white) {

+            left = left || token;

+            right = right || nexttoken;

+            if (left.character === right.from) {

+                warning("Missing space after '{a}'.",

+                        nexttoken, left.value);

+            }

+        }

+    }

+

+    function indentation(bias) {

+        var i;

+        if (option.white && nexttoken.id !== '(end)') {

+            i = indent + (bias || 0);

+            if (nexttoken.from !== i) {

+                warning(

+"Expected '{a}' to have an indentation at {b} instead at {c}.",

+                        nexttoken, nexttoken.value, i, nexttoken.from);

+            }

+        }

+    }

+

+    function nolinebreak(t) {

+        t = t || token;

+        if (t.line !== nexttoken.line) {

+            warning("Line breaking error '{a}'.", t, t.value);

+        }

+    }

+

+

+    function comma() {

+        if (token.line !== nexttoken.line) {

+            if (!option.laxbreak) {

+                warning("Bad line breaking before '{a}'.", token, nexttoken.id);

+            }

+        } else if (token.character !== nexttoken.from && option.white) {

+            warning("Unexpected space after '{a}'.", nexttoken, token.value);

+        }

+        advance(',');

+        nonadjacent(token, nexttoken);

+    }

+

+

+// Functional constructors for making the symbols that will be inherited by

+// tokens.

+

+    function symbol(s, p) {

+        var x = syntax[s];

+        if (!x || typeof x !== 'object') {

+            syntax[s] = x = {

+                id: s,

+                lbp: p,

+                value: s

+            };

+        }

+        return x;

+    }

+

+

+    function delim(s) {

+        return symbol(s, 0);

+    }

+

+

+    function stmt(s, f) {

+        var x = delim(s);

+        x.identifier = x.reserved = true;

+        x.fud = f;

+        return x;

+    }

+

+

+    function blockstmt(s, f) {

+        var x = stmt(s, f);

+        x.block = true;

+        return x;

+    }

+

+

+    function reserveName(x) {

+        var c = x.id.charAt(0);

+        if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {

+            x.identifier = x.reserved = true;

+        }

+        return x;

+    }

+

+

+    function prefix(s, f) {

+        var x = symbol(s, 150);

+        reserveName(x);

+        x.nud = (typeof f === 'function') ? f : function () {

+            this.right = expression(150);

+            this.arity = 'unary';

+            if (this.id === '++' || this.id === '--') {

+                if (option.plusplus) {

+                    warning("Unexpected use of '{a}'.", this, this.id);

+                } else if ((!this.right.identifier || this.right.reserved) &&

+                        this.right.id !== '.' && this.right.id !== '[') {

+                    warning("Bad operand.", this);

+                }

+            }

+            return this;

+        };

+        return x;

+    }

+

+

+    function type(s, f) {

+        var x = delim(s);

+        x.type = s;

+        x.nud = f;

+        return x;

+    }

+

+

+    function reserve(s, f) {

+        var x = type(s, f);

+        x.identifier = x.reserved = true;

+        return x;

+    }

+

+

+    function reservevar(s, v) {

+        return reserve(s, function () {

+            if (typeof v === 'function') {

+                v(this);

+            }

+            return this;

+        });

+    }

+

+

+    function infix(s, f, p, w) {

+        var x = symbol(s, p);

+        reserveName(x);

+        x.led = function (left) {

+            if (!w) {

+                nobreaknonadjacent(prevtoken, token);

+                nonadjacent(token, nexttoken);

+            }

+            if (typeof f === 'function') {

+                return f(left, this);

+            } else {

+                this.left = left;

+                this.right = expression(p);

+                return this;

+            }

+        };

+        return x;

+    }

+

+

+    function relation(s, f) {

+        var x = symbol(s, 100);

+        x.led = function (left) {

+            nobreaknonadjacent(prevtoken, token);

+            nonadjacent(token, nexttoken);

+            var right = expression(100);

+            if ((left && left.id === 'NaN') || (right && right.id === 'NaN')) {

+                warning("Use the isNaN function to compare with NaN.", this);

+            } else if (f) {

+                f.apply(this, [left, right]);

+            }

+            if (left.id === '!') {

+                warning("Confusing use of '{a}'.", left, '!');

+            }

+            if (right.id === '!') {

+                warning("Confusing use of '{a}'.", left, '!');

+            }

+            this.left = left;

+            this.right = right;

+            return this;

+        };

+        return x;

+    }

+

+

+    function isPoorRelation(node) {

+        return node &&

+              ((node.type === '(number)' && +node.value === 0) ||

+               (node.type === '(string)' && node.value === '') ||

+               (node.type === 'null' && !option.eqnull) ||

+                node.type === 'true' ||

+                node.type === 'false' ||

+                node.type === 'undefined');

+    }

+

+

+    function assignop(s, f) {

+        symbol(s, 20).exps = true;

+        return infix(s, function (left, that) {

+            var l;

+            that.left = left;

+            if (predefined[left.value] === false &&

+                    scope[left.value]['(global)'] === true) {

+                warning("Read only.", left);

+            } else if (left['function']) {

+                warning("'{a}' is a function.", left, left.value);

+            }

+            if (left) {

+                if (left.id === '.' || left.id === '[') {

+                    if (!left.left || left.left.value === 'arguments') {

+                        warning('Bad assignment.', that);

+                    }

+                    that.right = expression(19);

+                    return that;

+                } else if (left.identifier && !left.reserved) {

+                    if (funct[left.value] === 'exception') {

+                        warning("Do not assign to the exception parameter.", left);

+                    }

+                    that.right = expression(19);

+                    return that;

+                }

+                if (left === syntax['function']) {

+                    warning(

+"Expected an identifier in an assignment and instead saw a function invocation.",

+                                token);

+                }

+            }

+            error("Bad assignment.", that);

+        }, 20);

+    }

+

+

+    function bitwise(s, f, p) {

+        var x = symbol(s, p);

+        reserveName(x);

+        x.led = (typeof f === 'function') ? f : function (left) {

+            if (option.bitwise) {

+                warning("Unexpected use of '{a}'.", this, this.id);

+            }

+            this.left = left;

+            this.right = expression(p);

+            return this;

+        };

+        return x;

+    }

+

+

+    function bitwiseassignop(s) {

+        symbol(s, 20).exps = true;

+        return infix(s, function (left, that) {

+            if (option.bitwise) {

+                warning("Unexpected use of '{a}'.", that, that.id);

+            }

+            nonadjacent(prevtoken, token);

+            nonadjacent(token, nexttoken);

+            if (left) {

+                if (left.id === '.' || left.id === '[' ||

+                        (left.identifier && !left.reserved)) {

+                    expression(19);

+                    return that;

+                }

+                if (left === syntax['function']) {

+                    warning(

+"Expected an identifier in an assignment, and instead saw a function invocation.",

+                                token);

+                }

+                return that;

+            }

+            error("Bad assignment.", that);

+        }, 20);

+    }

+

+

+    function suffix(s, f) {

+        var x = symbol(s, 150);

+        x.led = function (left) {

+            if (option.plusplus) {

+                warning("Unexpected use of '{a}'.", this, this.id);

+            } else if ((!left.identifier || left.reserved) &&

+                    left.id !== '.' && left.id !== '[') {

+                warning("Bad operand.", this);

+            }

+            this.left = left;

+            return this;

+        };

+        return x;

+    }

+

+

+    // fnparam means that this identifier is being defined as a function

+    // argument (see identifier())

+    function optionalidentifier(fnparam) {

+        if (nexttoken.identifier) {

+            advance();

+            if (token.reserved && !option.es5) {

+                // `undefined` as a function param is a common pattern to protect

+                // against the case when somebody does `undefined = true` and

+                // help with minification. More info: https://gist.github.com/315916

+                if (!fnparam || token.value != 'undefined') {

+                    warning("Expected an identifier and instead saw '{a}' (a reserved word).",

+                            token, token.id);

+                }

+            }

+            return token.value;

+        }

+    }

+

+    // fnparam means that this identifier is being defined as a function

+    // argument

+    function identifier(fnparam) {

+        var i = optionalidentifier(fnparam);

+        if (i) {

+            return i;

+        }

+        if (token.id === 'function' && nexttoken.id === '(') {

+            warning("Missing name in function declaration.");

+        } else {

+            error("Expected an identifier and instead saw '{a}'.",

+                    nexttoken, nexttoken.value);

+        }

+    }

+

+

+    function reachable(s) {

+        var i = 0, t;

+        if (nexttoken.id !== ';' || noreach) {

+            return;

+        }

+        for (;;) {

+            t = peek(i);

+            if (t.reach) {

+                return;

+            }

+            if (t.id !== '(endline)') {

+                if (t.id === 'function') {

+                    warning(

+"Inner functions should be listed at the top of the outer function.", t);

+                    break;

+                }

+                warning("Unreachable '{a}' after '{b}'.", t, t.value, s);

+                break;

+            }

+            i += 1;

+        }

+    }

+

+

+    function statement(noindent) {

+        var i = indent, r, s = scope, t = nexttoken;

+

+// We don't like the empty statement.

+

+        if (t.id === ';') {

+            warning("Unnecessary semicolon.", t);

+            advance(';');

+            return;

+        }

+

+// Is this a labelled statement?

+

+        if (t.identifier && !t.reserved && peek().id === ':') {

+            advance();

+            advance(':');

+            scope = Object.create(s);

+            addlabel(t.value, 'label');

+            if (!nexttoken.labelled) {

+                warning("Label '{a}' on {b} statement.",

+                        nexttoken, t.value, nexttoken.value);

+            }

+            if (jx.test(t.value + ':')) {

+                warning("Label '{a}' looks like a javascript url.",

+                        t, t.value);

+            }

+            nexttoken.label = t.value;

+            t = nexttoken;

+        }

+

+// Parse the statement.

+

+        if (!noindent) {

+            indentation();

+        }

+        r = expression(0, true);

+

+// Look for the final semicolon.

+

+        if (!t.block) {

+            if (!option.expr && (!r || !r.exps)) {

+                warning("Expected an assignment or function call and instead saw an expression.", token);

+            } else if (option.nonew && r.id === '(' && r.left.id === 'new') {

+                warning("Do not use 'new' for side effects.");

+            }

+            if (nexttoken.id !== ';') {

+                if (!option.asi) {

+                    warningAt("Missing semicolon.", token.line, token.from + token.value.length);

+                }

+            } else {

+                adjacent(token, nexttoken);

+                advance(';');

+                nonadjacent(token, nexttoken);

+            }

+        }

+

+// Restore the indentation.

+

+        indent = i;

+        scope = s;

+        return r;

+    }

+

+

+    function use_strict() {

+        if (nexttoken.value === 'use strict') {

+            if (strict_mode) {

+                warning("Unnecessary \"use strict\".");

+            }

+            advance();

+            advance(';');

+            strict_mode = true;

+            option.newcap = true;

+            option.undef = true;

+            return true;

+        } else {

+            return false;

+        }

+    }

+

+

+    function statements(begin) {

+        var a = [], f, p;

+

+        while (!nexttoken.reach && nexttoken.id !== '(end)') {

+            if (nexttoken.id === ';') {

+                warning("Unnecessary semicolon.");

+                advance(';');

+            } else {

+                a.push(statement());

+            }

+        }

+        return a;

+    }

+

+

+    /*

+     * Parses a single block. A block is a sequence of statements wrapped in

+     * braces.

+     *

+     * ordinary - true for everything but function bodies and try blocks.

+     * stmt     - true if block can be a single statement (e.g. in if/for/while).

+     */

+    function block(ordinary, stmt) {

+        var a,

+            b = inblock,

+            old_indent = indent,

+            m = strict_mode,

+            s = scope,

+            t;

+

+        inblock = ordinary;

+        scope = Object.create(scope);

+        nonadjacent(token, nexttoken);

+        t = nexttoken;

+

+        if (nexttoken.id === '{') {

+            advance('{');

+            if (nexttoken.id !== '}' || token.line !== nexttoken.line) {

+                indent += option.indent;

+                while (!ordinary && nexttoken.from > indent) {

+                    indent += option.indent;

+                }

+                if (!ordinary && !use_strict() && !m && option.strict &&

+                        funct['(context)']['(global)']) {

+                    warning("Missing \"use strict\" statement.");

+                }

+                a = statements();

+                strict_mode = m;

+                indent -= option.indent;

+                indentation();

+            }

+            advance('}', t);

+            indent = old_indent;

+        } else if (!ordinary) {

+            error("Expected '{a}' and instead saw '{b}'.",

+                  nexttoken, '{', nexttoken.value);

+        } else {

+            if (!stmt || option.curly)

+                warning("Expected '{a}' and instead saw '{b}'.",

+                        nexttoken, '{', nexttoken.value);

+

+            noreach = true;

+            a = [statement()];

+            noreach = false;

+        }

+        funct['(verb)'] = null;

+        scope = s;

+        inblock = b;

+        if (ordinary && option.noempty && (!a || a.length === 0)) {

+            warning("Empty block.");

+        }

+        return a;

+    }

+

+

+    function countMember(m) {

+        if (membersOnly && typeof membersOnly[m] !== 'boolean') {

+            warning("Unexpected /*member '{a}'.", token, m);

+        }

+        if (typeof member[m] === 'number') {

+            member[m] += 1;

+        } else {

+            member[m] = 1;

+        }

+    }

+

+

+    function note_implied(token) {

+        var name = token.value, line = token.line, a = implied[name];

+        if (typeof a === 'function') {

+            a = false;

+        }

+        if (!a) {

+            a = [line];

+            implied[name] = a;

+        } else if (a[a.length - 1] !== line) {

+            a.push(line);

+        }

+    }

+

+// Build the syntax table by declaring the syntactic elements of the language.

+

+    type('(number)', function () {

+        return this;

+    });

+    type('(string)', function () {

+        return this;

+    });

+

+    syntax['(identifier)'] = {

+        type: '(identifier)',

+        lbp: 0,

+        identifier: true,

+        nud: function () {

+            var v = this.value,

+                s = scope[v],

+                f;

+            if (typeof s === 'function') {

+

+// Protection against accidental inheritance.

+

+                s = undefined;

+            } else if (typeof s === 'boolean') {

+                f = funct;

+                funct = functions[0];

+                addlabel(v, 'var');

+                s = funct;

+                funct = f;

+            }

+

+// The name is in scope and defined in the current function.

+

+            if (funct === s) {

+

+//      Change 'unused' to 'var', and reject labels.

+

+                switch (funct[v]) {

+                case 'unused':

+                    funct[v] = 'var';

+                    break;

+                case 'unction':

+                    funct[v] = 'function';

+                    this['function'] = true;

+                    break;

+                case 'function':

+                    this['function'] = true;

+                    break;

+                case 'label':

+                    warning("'{a}' is a statement label.", token, v);

+                    break;

+                }

+

+// The name is not defined in the function.  If we are in the global scope,

+// then we have an undefined variable.

+//

+// Operators typeof and delete do not raise runtime errors even if the base

+// object of a reference is null so no need to display warning if we're

+// inside of typeof or delete.

+

+            } else if (funct['(global)']) {

+                if (anonname != 'typeof' && anonname != 'delete' &&

+                    option.undef && typeof predefined[v] !== 'boolean') {

+                    warning("'{a}' is not defined.", token, v);

+                }

+                note_implied(token);

+

+// If the name is already defined in the current

+// function, but not as outer, then there is a scope error.

+

+            } else {

+                switch (funct[v]) {

+                case 'closure':

+                case 'function':

+                case 'var':

+                case 'unused':

+                    warning("'{a}' used out of scope.", token, v);

+                    break;

+                case 'label':

+                    warning("'{a}' is a statement label.", token, v);

+                    break;

+                case 'outer':

+                case 'global':

+                    break;

+                default:

+

+// If the name is defined in an outer function, make an outer entry, and if

+// it was unused, make it var.

+

+                    if (s === true) {

+                        funct[v] = true;

+                    } else if (s === null) {

+                        warning("'{a}' is not allowed.", token, v);

+                        note_implied(token);

+                    } else if (typeof s !== 'object') {

+

+// Operators typeof and delete do not raise runtime errors even if the base object of

+// a reference is null so no need to display warning if we're inside of typeof or delete.

+

+                        if (anonname != 'typeof' && anonname != 'delete' && option.undef) {

+                            warning("'{a}' is not defined.", token, v);

+                        } else {

+                            funct[v] = true;

+                        }

+                        note_implied(token);

+                    } else {

+                        switch (s[v]) {

+                        case 'function':

+                        case 'unction':

+                            this['function'] = true;

+                            s[v] = 'closure';

+                            funct[v] = s['(global)'] ? 'global' : 'outer';

+                            break;

+                        case 'var':

+                        case 'unused':

+                            s[v] = 'closure';

+                            funct[v] = s['(global)'] ? 'global' : 'outer';

+                            break;

+                        case 'closure':

+                        case 'parameter':

+                            funct[v] = s['(global)'] ? 'global' : 'outer';

+                            break;

+                        case 'label':

+                            warning("'{a}' is a statement label.", token, v);

+                        }

+                    }

+                }

+            }

+            return this;

+        },

+        led: function () {

+            error("Expected an operator and instead saw '{a}'.",

+                nexttoken, nexttoken.value);

+        }

+    };

+

+    type('(regexp)', function () {

+        return this;

+    });

+

+

+// ECMAScript parser

+

+    delim('(endline)');

+    delim('(begin)');

+    delim('(end)').reach = true;

+    delim('</').reach = true;

+    delim('<!');

+    delim('<!--');

+    delim('-->');

+    delim('(error)').reach = true;

+    delim('}').reach = true;

+    delim(')');

+    delim(']');

+    delim('"').reach = true;

+    delim("'").reach = true;

+    delim(';');

+    delim(':').reach = true;

+    delim(',');

+    delim('#');

+    delim('@');

+    reserve('else');

+    reserve('case').reach = true;

+    reserve('catch');

+    reserve('default').reach = true;

+    reserve('finally');

+    reservevar('arguments', function (x) {

+        if (strict_mode && funct['(global)']) {

+            warning("Strict violation.", x);

+        }

+    });

+    reservevar('eval');

+    reservevar('false');

+    reservevar('Infinity');

+    reservevar('NaN');

+    reservevar('null');

+    reservevar('this', function (x) {

+        if (strict_mode && ((funct['(statement)'] &&

+                funct['(name)'].charAt(0) > 'Z') || funct['(global)'])) {

+            warning("Strict violation.", x);

+        }

+    });

+    reservevar('true');

+    reservevar('undefined');

+    assignop('=', 'assign', 20);

+    assignop('+=', 'assignadd', 20);

+    assignop('-=', 'assignsub', 20);

+    assignop('*=', 'assignmult', 20);

+    assignop('/=', 'assigndiv', 20).nud = function () {

+        error("A regular expression literal can be confused with '/='.");

+    };

+    assignop('%=', 'assignmod', 20);

+    bitwiseassignop('&=', 'assignbitand', 20);

+    bitwiseassignop('|=', 'assignbitor', 20);

+    bitwiseassignop('^=', 'assignbitxor', 20);

+    bitwiseassignop('<<=', 'assignshiftleft', 20);

+    bitwiseassignop('>>=', 'assignshiftright', 20);

+    bitwiseassignop('>>>=', 'assignshiftrightunsigned', 20);

+    infix('?', function (left, that) {

+        that.left = left;

+        that.right = expression(10);

+        advance(':');

+        that['else'] = expression(10);

+        return that;

+    }, 30);

+

+    infix('||', 'or', 40);

+    infix('&&', 'and', 50);

+    bitwise('|', 'bitor', 70);

+    bitwise('^', 'bitxor', 80);

+    bitwise('&', 'bitand', 90);

+    relation('==', function (left, right) {

+        var eqnull = option.eqnull &&

+                (left.value == 'null' || right.value == 'null');

+

+        if (!eqnull && option.eqeqeq) {

+            warning("Expected '{a}' and instead saw '{b}'.",

+                    this, '===', '==');

+        } else if (isPoorRelation(left)) {

+            warning("Use '{a}' to compare with '{b}'.",

+                this, '===', left.value);

+        } else if (isPoorRelation(right)) {

+            warning("Use '{a}' to compare with '{b}'.",

+                this, '===', right.value);

+        }

+        return this;

+    });

+    relation('===');

+    relation('!=', function (left, right) {

+        if (option.eqeqeq) {

+            warning("Expected '{a}' and instead saw '{b}'.",

+                    this, '!==', '!=');

+        } else if (isPoorRelation(left)) {

+            warning("Use '{a}' to compare with '{b}'.",

+                    this, '!==', left.value);

+        } else if (isPoorRelation(right)) {

+            warning("Use '{a}' to compare with '{b}'.",

+                    this, '!==', right.value);

+        }

+        return this;

+    });

+    relation('!==');

+    relation('<');

+    relation('>');

+    relation('<=');

+    relation('>=');

+    bitwise('<<', 'shiftleft', 120);

+    bitwise('>>', 'shiftright', 120);

+    bitwise('>>>', 'shiftrightunsigned', 120);

+    infix('in', 'in', 120);

+    infix('instanceof', 'instanceof', 120);

+    infix('+', function (left, that) {

+        var right = expression(130);

+        if (left && right && left.id === '(string)' && right.id === '(string)') {

+            left.value += right.value;

+            left.character = right.character;

+            if (jx.test(left.value)) {

+                warning("JavaScript URL.", left);

+            }

+            return left;

+        }

+        that.left = left;

+        that.right = right;

+        return that;

+    }, 130);

+    prefix('+', 'num');

+    prefix('+++', function () {

+        warning("Confusing pluses.");

+        this.right = expression(150);

+        this.arity = 'unary';

+        return this;

+    });

+    infix('+++', function (left) {

+        warning("Confusing pluses.");

+        this.left = left;

+        this.right = expression(130);

+        return this;

+    }, 130);

+    infix('-', 'sub', 130);

+    prefix('-', 'neg');

+    prefix('---', function () {

+        warning("Confusing minuses.");

+        this.right = expression(150);

+        this.arity = 'unary';

+        return this;

+    });

+    infix('---', function (left) {

+        warning("Confusing minuses.");

+        this.left = left;

+        this.right = expression(130);

+        return this;

+    }, 130);

+    infix('*', 'mult', 140);

+    infix('/', 'div', 140);

+    infix('%', 'mod', 140);

+

+    suffix('++', 'postinc');

+    prefix('++', 'preinc');

+    syntax['++'].exps = true;

+

+    suffix('--', 'postdec');

+    prefix('--', 'predec');

+    syntax['--'].exps = true;

+    prefix('delete', function () {

+        var p = expression(0);

+        if (!p || (p.id !== '.' && p.id !== '[')) {

+            warning("Variables should not be deleted.");

+        }

+        this.first = p;

+        return this;

+    }).exps = true;

+

+    prefix('~', function () {

+        if (option.bitwise) {

+            warning("Unexpected '{a}'.", this, '~');

+        }

+        expression(150);

+        return this;

+    });

+

+    prefix('!', function () {

+        this.right = expression(150);

+        this.arity = 'unary';

+        if (bang[this.right.id] === true) {

+            warning("Confusing use of '{a}'.", this, '!');

+        }

+        return this;

+    });

+    prefix('typeof', 'typeof');

+    prefix('new', function () {

+        var c = expression(155), i;

+        if (c && c.id !== 'function') {

+            if (c.identifier) {

+                c['new'] = true;

+                switch (c.value) {

+                case 'Object':

+                    warning("Use the object literal notation {}.", token);

+                    break;

+                case 'Number':

+                case 'String':

+                case 'Boolean':

+                case 'Math':

+                case 'JSON':

+                    warning("Do not use {a} as a constructor.", token, c.value);

+                    break;

+                case 'Function':

+                    if (!option.evil) {

+                        warning("The Function constructor is eval.");

+                    }

+                    break;

+                case 'Date':

+                case 'RegExp':

+                    break;

+                default:

+                    if (c.id !== 'function') {

+                        i = c.value.substr(0, 1);

+                        if (option.newcap && (i < 'A' || i > 'Z')) {

+                            warning("A constructor name should start with an uppercase letter.", token);

+                        }

+                    }

+                }

+            } else {

+                if (c.id !== '.' && c.id !== '[' && c.id !== '(') {

+                    warning("Bad constructor.", token);

+                }

+            }

+        } else {

+            if (!option.supernew)

+                warning("Weird construction. Delete 'new'.", this);

+        }

+        adjacent(token, nexttoken);

+        if (nexttoken.id !== '(' && !option.supernew) {

+            warning("Missing '()' invoking a constructor.");

+        }

+        this.first = c;

+        return this;

+    });

+    syntax['new'].exps = true;

+

+    prefix('void').exps = true;

+

+    infix('.', function (left, that) {

+        adjacent(prevtoken, token);

+        nobreak();

+        var m = identifier();

+        if (typeof m === 'string') {

+            countMember(m);

+        }

+        that.left = left;

+        that.right = m;

+        if (option.noarg && left && left.value === 'arguments' &&

+                (m === 'callee' || m === 'caller')) {

+            warning("Avoid arguments.{a}.", left, m);

+        } else if (!option.evil && left && left.value === 'document' &&

+                (m === 'write' || m === 'writeln')) {

+            warning("document.write can be a form of eval.", left);

+        }

+        if (!option.evil && (m === 'eval' || m === 'execScript')) {

+            warning('eval is evil.');

+        }

+        return that;

+    }, 160, true);

+

+    infix('(', function (left, that) {

+        if (prevtoken.id !== '}' && prevtoken.id !== ')') {

+            nobreak(prevtoken, token);

+        }

+        nospace();

+        if (option.immed && !left.immed && left.id === 'function') {

+            warning("Wrap an immediate function invocation in parentheses " +

+                "to assist the reader in understanding that the expression " +

+                "is the result of a function, and not the function itself.");

+        }

+        var n = 0,

+            p = [];

+        if (left) {

+            if (left.type === '(identifier)') {

+                if (left.value.match(/^[A-Z]([A-Z0-9_$]*[a-z][A-Za-z0-9_$]*)?$/)) {

+                    if (left.value !== 'Number' && left.value !== 'String' &&

+                            left.value !== 'Boolean' &&

+                            left.value !== 'Date') {

+                        if (left.value === 'Math') {

+                            warning("Math is not a function.", left);

+                        } else if (option.newcap) {

+                            warning(

+"Missing 'new' prefix when invoking a constructor.", left);

+                        }

+                    }

+                }

+            }

+        }

+        if (nexttoken.id !== ')') {

+            for (;;) {

+                p[p.length] = expression(10);

+                n += 1;

+                if (nexttoken.id !== ',') {

+                    break;

+                }

+                comma();

+            }

+        }

+        advance(')');

+        nospace(prevtoken, token);

+        if (typeof left === 'object') {

+            if (left.value === 'parseInt' && n === 1) {

+                warning("Missing radix parameter.", left);

+            }

+            if (!option.evil) {

+                if (left.value === 'eval' || left.value === 'Function' ||

+                        left.value === 'execScript') {

+                    warning("eval is evil.", left);

+                } else if (p[0] && p[0].id === '(string)' &&

+                       (left.value === 'setTimeout' ||

+                        left.value === 'setInterval')) {

+                    warning(

+    "Implied eval is evil. Pass a function instead of a string.", left);

+                }

+            }

+            if (!left.identifier && left.id !== '.' && left.id !== '[' &&

+                    left.id !== '(' && left.id !== '&&' && left.id !== '||' &&

+                    left.id !== '?') {

+                warning("Bad invocation.", left);

+            }

+        }

+        that.left = left;

+        return that;

+    }, 155, true).exps = true;

+

+    prefix('(', function () {

+        nospace();

+        if (nexttoken.id === 'function') {

+            nexttoken.immed = true;

+        }

+        var v = expression(0);

+        advance(')', this);

+        nospace(prevtoken, token);

+        if (option.immed && v.id === 'function') {

+            if (nexttoken.id === '(') {

+                warning(

+"Move the invocation into the parens that contain the function.", nexttoken);

+            } else {

+                warning(

+"Do not wrap function literals in parens unless they are to be immediately invoked.",

+                        this);

+            }

+        }

+        return v;

+    });

+

+    infix('[', function (left, that) {

+        nobreak(prevtoken, token);

+        nospace();

+        var e = expression(0), s;

+        if (e && e.type === '(string)') {

+            if (!option.evil && (e.value === 'eval' || e.value === 'execScript')) {

+                warning("eval is evil.", that);

+            }

+            countMember(e.value);

+            if (!option.sub && ix.test(e.value)) {

+                s = syntax[e.value];

+                if (!s || !s.reserved) {

+                    warning("['{a}'] is better written in dot notation.",

+                            e, e.value);

+                }

+            }

+        }

+        advance(']', that);

+        nospace(prevtoken, token);

+        that.left = left;

+        that.right = e;

+        return that;

+    }, 160, true);

+

+    prefix('[', function () {

+        var b = token.line !== nexttoken.line;

+        this.first = [];

+        if (b) {

+            indent += option.indent;

+            if (nexttoken.from === indent + option.indent) {

+                indent += option.indent;

+            }

+        }

+        while (nexttoken.id !== '(end)') {

+            while (nexttoken.id === ',') {

+                warning("Extra comma.");

+                advance(',');

+            }

+            if (nexttoken.id === ']') {

+                break;

+            }

+            if (b && token.line !== nexttoken.line) {

+                indentation();

+            }

+            this.first.push(expression(10));

+            if (nexttoken.id === ',') {

+                comma();

+                if (nexttoken.id === ']' && !option.es5) {

+                    warning("Extra comma.", token);

+                    break;

+                }

+            } else {

+                break;

+            }

+        }

+        if (b) {

+            indent -= option.indent;

+            indentation();

+        }

+        advance(']', this);

+        return this;

+    }, 160);

+

+

+    function property_name() {

+        var id = optionalidentifier(true);

+        if (!id) {

+            if (nexttoken.id === '(string)') {

+                id = nexttoken.value;

+                advance();

+            } else if (nexttoken.id === '(number)') {

+                id = nexttoken.value.toString();

+                advance();

+            }

+        }

+        return id;

+    }

+

+

+    function functionparams() {

+        var i, t = nexttoken, p = [];

+        advance('(');

+        nospace();

+        if (nexttoken.id === ')') {

+            advance(')');

+            nospace(prevtoken, token);

+            return;

+        }

+        for (;;) {

+            i = identifier(true);

+            p.push(i);

+            addlabel(i, 'parameter');

+            if (nexttoken.id === ',') {

+                comma();

+            } else {

+                advance(')', t);

+                nospace(prevtoken, token);

+                return p;

+            }

+        }

+    }

+

+

+    function doFunction(i, statement) {

+        var f,

+            oldOption = option,

+            oldScope  = scope;

+

+        option = Object.create(option);

+        scope = Object.create(scope);

+

+        funct = {

+            '(name)'     : i || '"' + anonname + '"',

+            '(line)'     : nexttoken.line,

+            '(context)'  : funct,

+            '(breakage)' : 0,

+            '(loopage)'  : 0,

+            '(scope)'    : scope,

+            '(statement)': statement

+        };

+        f = funct;

+        token.funct = funct;

+        functions.push(funct);

+        if (i) {

+            addlabel(i, 'function');

+        }

+        funct['(params)'] = functionparams();

+

+        block(false);

+        scope = oldScope;

+        option = oldOption;

+        funct['(last)'] = token.line;

+        funct = funct['(context)'];

+        return f;

+    }

+

+

+    (function (x) {

+        x.nud = function () {

+            var b, f, i, j, p, seen = {}, t;

+

+            b = token.line !== nexttoken.line;

+            if (b) {

+                indent += option.indent;

+                if (nexttoken.from === indent + option.indent) {

+                    indent += option.indent;

+                }

+            }

+            for (;;) {

+                if (nexttoken.id === '}') {

+                    break;

+                }

+                if (b) {

+                    indentation();

+                }

+                if (nexttoken.value === 'get' && peek().id !== ':') {

+                    advance('get');

+                    if (!option.es5) {

+                        error("get/set are ES5 features.");

+                    }

+                    i = property_name();

+                    if (!i) {

+                        error("Missing property name.");

+                    }

+                    t = nexttoken;

+                    adjacent(token, nexttoken);

+                    f = doFunction();

+                    if (!option.loopfunc && funct['(loopage)']) {

+                        warning("Don't make functions within a loop.", t);

+                    }

+                    p = f['(params)'];

+                    if (p) {

+                        warning("Unexpected parameter '{a}' in get {b} function.", t, p[0], i);

+                    }

+                    adjacent(token, nexttoken);

+                    advance(',');

+                    indentation();

+                    advance('set');

+                    j = property_name();

+                    if (i !== j) {

+                        error("Expected {a} and instead saw {b}.", token, i, j);

+                    }

+                    t = nexttoken;

+                    adjacent(token, nexttoken);

+                    f = doFunction();

+                    p = f['(params)'];

+                    if (!p || p.length !== 1 || p[0] !== 'value') {

+                        warning("Expected (value) in set {a} function.", t, i);

+                    }

+                } else {

+                    i = property_name();

+                    if (typeof i !== 'string') {

+                        break;

+                    }

+                    advance(':');

+                    nonadjacent(token, nexttoken);

+                    expression(10);

+                }

+                if (seen[i] === true) {

+                    warning("Duplicate member '{a}'.", nexttoken, i);

+                }

+                seen[i] = true;

+                countMember(i);

+                if (nexttoken.id === ',') {

+                    comma();

+                    if (nexttoken.id === ',') {

+                        warning("Extra comma.", token);

+                    } else if (nexttoken.id === '}' && !option.es5) {

+                        warning("Extra comma.", token);

+                    }

+                } else {

+                    break;

+                }

+            }

+            if (b) {

+                indent -= option.indent;

+                indentation();

+            }

+            advance('}', this);

+            return this;

+        };

+        x.fud = function () {

+            error("Expected to see a statement and instead saw a block.", token);

+        };

+    }(delim('{')));

+

+    var varstatement = stmt('var', function (prefix) {

+        // JavaScript does not have block scope. It only has function scope. So,

+        // declaring a variable in a block can have unexpected consequences.

+        var id, name, value;

+

+        if (funct['(onevar)'] && option.onevar) {

+            warning("Too many var statements.");

+        } else if (!funct['(global)']) {

+            funct['(onevar)'] = true;

+        }

+        this.first = [];

+        for (;;) {

+            nonadjacent(token, nexttoken);

+            id = identifier();

+            if (funct['(global)'] && predefined[id] === false) {

+                warning("Redefinition of '{a}'.", token, id);

+            }

+            addlabel(id, 'unused');

+            if (prefix) {

+                break;

+            }

+            name = token;

+            this.first.push(token);

+            if (nexttoken.id === '=') {

+                nonadjacent(token, nexttoken);

+                advance('=');

+                nonadjacent(token, nexttoken);

+                if (nexttoken.id === 'undefined') {

+                    warning("It is not necessary to initialize '{a}' to 'undefined'.", token, id);

+                }

+                if (peek(0).id === '=' && nexttoken.identifier) {

+                    error("Variable {a} was not declared correctly.",

+                            nexttoken, nexttoken.value);

+                }

+                value = expression(0);

+                name.first = value;

+            }

+            if (nexttoken.id !== ',') {

+                break;

+            }

+            comma();

+        }

+        return this;

+    });

+    varstatement.exps = true;

+

+    blockstmt('function', function () {

+        if (inblock) {

+            warning(

+"Function declarations should not be placed in blocks. Use a function expression or move the statement to the top of the outer function.", token);

+

+        }

+        var i = identifier();

+        adjacent(token, nexttoken);

+        addlabel(i, 'unction');

+        doFunction(i, true);

+        if (nexttoken.id === '(' && nexttoken.line === token.line) {

+            error(

+"Function declarations are not invocable. Wrap the whole function invocation in parens.");

+        }

+        return this;

+    });

+

+    prefix('function', function () {

+        var i = optionalidentifier();

+        if (i) {

+            adjacent(token, nexttoken);

+        } else {

+            nonadjacent(token, nexttoken);

+        }

+        doFunction(i);

+        if (!option.loopfunc && funct['(loopage)']) {

+            warning("Don't make functions within a loop.");

+        }

+        return this;

+    });

+

+    blockstmt('if', function () {

+        var t = nexttoken;

+        advance('(');

+        nonadjacent(this, t);

+        nospace();

+        expression(20);

+        if (nexttoken.id === '=') {

+            if (!option.boss)

+                warning("Expected a conditional expression and instead saw an assignment.");

+            advance('=');

+            expression(20);

+        }

+        advance(')', t);

+        nospace(prevtoken, token);

+        block(true, true);

+        if (nexttoken.id === 'else') {

+            nonadjacent(token, nexttoken);

+            advance('else');

+            if (nexttoken.id === 'if' || nexttoken.id === 'switch') {

+                statement(true);

+            } else {

+                block(true, true);

+            }

+        }

+        return this;

+    });

+

+    blockstmt('try', function () {

+        var b, e, s;

+

+        block(false);

+        if (nexttoken.id === 'catch') {

+            advance('catch');

+            nonadjacent(token, nexttoken);

+            advance('(');

+            s = scope;

+            scope = Object.create(s);

+            e = nexttoken.value;

+            if (nexttoken.type !== '(identifier)') {

+                warning("Expected an identifier and instead saw '{a}'.",

+                    nexttoken, e);

+            } else {

+                addlabel(e, 'exception');

+            }

+            advance();

+            advance(')');

+            block(false);

+            b = true;

+            scope = s;

+        }

+        if (nexttoken.id === 'finally') {

+            advance('finally');

+            block(false);

+            return;

+        } else if (!b) {

+            error("Expected '{a}' and instead saw '{b}'.",

+                    nexttoken, 'catch', nexttoken.value);

+        }

+        return this;

+    });

+

+    blockstmt('while', function () {

+        var t = nexttoken;

+        funct['(breakage)'] += 1;

+        funct['(loopage)'] += 1;

+        advance('(');

+        nonadjacent(this, t);

+        nospace();

+        expression(20);

+        if (nexttoken.id === '=') {

+            if (!option.boss)

+                warning("Expected a conditional expression and instead saw an assignment.");

+            advance('=');

+            expression(20);

+        }

+        advance(')', t);

+        nospace(prevtoken, token);

+        block(true, true);

+        funct['(breakage)'] -= 1;

+        funct['(loopage)'] -= 1;

+        return this;

+    }).labelled = true;

+

+    reserve('with');

+

+    blockstmt('switch', function () {

+        var t = nexttoken,

+            g = false;

+        funct['(breakage)'] += 1;

+        advance('(');

+        nonadjacent(this, t);

+        nospace();

+        this.condition = expression(20);

+        advance(')', t);

+        nospace(prevtoken, token);

+        nonadjacent(token, nexttoken);

+        t = nexttoken;

+        advance('{');

+        nonadjacent(token, nexttoken);

+        indent += option.indent;

+        this.cases = [];

+        for (;;) {

+            switch (nexttoken.id) {

+            case 'case':

+                switch (funct['(verb)']) {

+                case 'break':

+                case 'case':

+                case 'continue':

+                case 'return':

+                case 'switch':

+                case 'throw':

+                    break;

+                default:

+                    // You can tell JSHint that you don't use break intentionally by

+                    // adding a comment /* falls through */ on a line just before

+                    // the next `case`.

+                    if (!ft.test(lines[nexttoken.line - 2])) {

+                        warning(

+                            "Expected a 'break' statement before 'case'.",

+                            token);

+                    }

+                }

+                indentation(-option.indent);

+                advance('case');

+                this.cases.push(expression(20));

+                g = true;

+                advance(':');

+                funct['(verb)'] = 'case';

+                break;

+            case 'default':

+                switch (funct['(verb)']) {

+                case 'break':

+                case 'continue':

+                case 'return':

+                case 'throw':

+                    break;

+                default:

+                    if (!ft.test(lines[nexttoken.line - 2])) {

+                        warning(

+                            "Expected a 'break' statement before 'default'.",

+                            token);

+                    }

+                }

+                indentation(-option.indent);

+                advance('default');

+                g = true;

+                advance(':');

+                break;

+            case '}':

+                indent -= option.indent;

+                indentation();

+                advance('}', t);

+                if (this.cases.length === 1 || this.condition.id === 'true' ||

+                        this.condition.id === 'false') {

+                    warning("This 'switch' should be an 'if'.", this);

+                }

+                funct['(breakage)'] -= 1;

+                funct['(verb)'] = undefined;

+                return;

+            case '(end)':

+                error("Missing '{a}'.", nexttoken, '}');

+                return;

+            default:

+                if (g) {

+                    switch (token.id) {

+                    case ',':

+                        error("Each value should have its own case label.");

+                        return;

+                    case ':':

+                        statements();

+                        break;

+                    default:

+                        error("Missing ':' on a case clause.", token);

+                    }

+                } else {

+                    error("Expected '{a}' and instead saw '{b}'.",

+                        nexttoken, 'case', nexttoken.value);

+                }

+            }

+        }

+    }).labelled = true;

+

+    stmt('debugger', function () {

+        if (!option.debug) {

+            warning("All 'debugger' statements should be removed.");

+        }

+        return this;

+    }).exps = true;

+

+    (function () {

+        var x = stmt('do', function () {

+            funct['(breakage)'] += 1;

+            funct['(loopage)'] += 1;

+            this.first = block(true);

+            advance('while');

+            var t = nexttoken;

+            nonadjacent(token, t);

+            advance('(');

+            nospace();

+            expression(20);

+            if (nexttoken.id === '=') {

+                if (!option.boss)

+                    warning("Expected a conditional expression and instead saw an assignment.");

+                advance('=');

+                expression(20);

+            }

+            advance(')', t);

+            nospace(prevtoken, token);

+            funct['(breakage)'] -= 1;

+            funct['(loopage)'] -= 1;

+            return this;

+        });

+        x.labelled = true;

+        x.exps = true;

+    }());

+

+    blockstmt('for', function () {

+        var s, t = nexttoken;

+        funct['(breakage)'] += 1;

+        funct['(loopage)'] += 1;

+        advance('(');

+        nonadjacent(this, t);

+        nospace();

+        if (peek(nexttoken.id === 'var' ? 1 : 0).id === 'in') {

+            if (nexttoken.id === 'var') {

+                advance('var');

+                varstatement.fud.call(varstatement, true);

+            } else {

+                switch (funct[nexttoken.value]) {

+                case 'unused':

+                    funct[nexttoken.value] = 'var';

+                    break;

+                case 'var':

+                    break;

+                default:

+                    warning("Bad for in variable '{a}'.",

+                            nexttoken, nexttoken.value);

+                }

+                advance();

+            }

+            advance('in');

+            expression(20);

+            advance(')', t);

+            s = block(true, true);

+            if (option.forin && (s.length > 1 || typeof s[0] !== 'object' ||

+                    s[0].value !== 'if')) {

+                warning("The body of a for in should be wrapped in an if statement to filter unwanted properties from the prototype.", this);

+            }

+            funct['(breakage)'] -= 1;

+            funct['(loopage)'] -= 1;

+            return this;

+        } else {

+            if (nexttoken.id !== ';') {

+                if (nexttoken.id === 'var') {

+                    advance('var');

+                    varstatement.fud.call(varstatement);

+                } else {

+                    for (;;) {

+                        expression(0, 'for');

+                        if (nexttoken.id !== ',') {

+                            break;

+                        }

+                        comma();

+                    }

+                }

+            }

+            nolinebreak(token);

+            advance(';');

+            if (nexttoken.id !== ';') {

+                expression(20);

+                if (nexttoken.id === '=') {

+                    if (!option.boss)

+                        warning("Expected a conditional expression and instead saw an assignment.");

+                    advance('=');

+                    expression(20);

+                }

+            }

+            nolinebreak(token);

+            advance(';');

+            if (nexttoken.id === ';') {

+                error("Expected '{a}' and instead saw '{b}'.",

+                        nexttoken, ')', ';');

+            }

+            if (nexttoken.id !== ')') {

+                for (;;) {

+                    expression(0, 'for');

+                    if (nexttoken.id !== ',') {

+                        break;

+                    }

+                    comma();

+                }

+            }

+            advance(')', t);

+            nospace(prevtoken, token);

+            block(true, true);

+            funct['(breakage)'] -= 1;

+            funct['(loopage)'] -= 1;

+            return this;

+        }

+    }).labelled = true;

+

+

+    stmt('break', function () {

+        var v = nexttoken.value;

+        if (funct['(breakage)'] === 0) {

+            warning("Unexpected '{a}'.", nexttoken, this.value);

+        }

+        nolinebreak(this);

+        if (nexttoken.id !== ';') {

+            if (token.line === nexttoken.line) {

+                if (funct[v] !== 'label') {

+                    warning("'{a}' is not a statement label.", nexttoken, v);

+                } else if (scope[v] !== funct) {

+                    warning("'{a}' is out of scope.", nexttoken, v);

+                }

+                this.first = nexttoken;

+                advance();

+            }

+        }

+        reachable('break');

+        return this;

+    }).exps = true;

+

+

+    stmt('continue', function () {

+        var v = nexttoken.value;

+        if (funct['(breakage)'] === 0) {

+            warning("Unexpected '{a}'.", nexttoken, this.value);

+        }

+        nolinebreak(this);

+        if (nexttoken.id !== ';') {

+            if (token.line === nexttoken.line) {

+                if (funct[v] !== 'label') {

+                    warning("'{a}' is not a statement label.", nexttoken, v);

+                } else if (scope[v] !== funct) {

+                    warning("'{a}' is out of scope.", nexttoken, v);

+                }

+                this.first = nexttoken;

+                advance();

+            }

+        } else if (!funct['(loopage)']) {

+            warning("Unexpected '{a}'.", nexttoken, this.value);

+        }

+        reachable('continue');

+        return this;

+    }).exps = true;

+

+

+    stmt('return', function () {

+        nolinebreak(this);

+        if (nexttoken.id === '(regexp)') {

+            warning("Wrap the /regexp/ literal in parens to disambiguate the slash operator.");

+        }

+        if (nexttoken.id !== ';' && !nexttoken.reach) {

+            nonadjacent(token, nexttoken);

+            this.first = expression(20);

+        }

+        reachable('return');

+        return this;

+    }).exps = true;

+

+

+    stmt('throw', function () {

+        nolinebreak(this);

+        nonadjacent(token, nexttoken);

+        this.first = expression(20);

+        reachable('throw');

+        return this;

+    }).exps = true;

+

+//  Superfluous reserved words

+

+    reserve('class');

+    reserve('const');

+    reserve('enum');

+    reserve('export');

+    reserve('extends');

+    reserve('import');

+    reserve('super');

+

+    reserve('let');

+    reserve('yield');

+    reserve('implements');

+    reserve('interface');

+    reserve('package');

+    reserve('private');

+    reserve('protected');

+    reserve('public');

+    reserve('static');

+

+

+// Parse JSON

+

+    function jsonValue() {

+

+        function jsonObject() {

+            var o = {}, t = nexttoken;

+            advance('{');

+            if (nexttoken.id !== '}') {

+                for (;;) {

+                    if (nexttoken.id === '(end)') {

+                        error("Missing '}' to match '{' from line {a}.",

+                                nexttoken, t.line);

+                    } else if (nexttoken.id === '}') {

+                        warning("Unexpected comma.", token);

+                        break;

+                    } else if (nexttoken.id === ',') {

+                        error("Unexpected comma.", nexttoken);

+                    } else if (nexttoken.id !== '(string)') {

+                        warning("Expected a string and instead saw {a}.",

+                                nexttoken, nexttoken.value);

+                    }

+                    if (o[nexttoken.value] === true) {

+                        warning("Duplicate key '{a}'.",

+                                nexttoken, nexttoken.value);

+                    } else if (nexttoken.value === '__proto__') {

+                        warning("Stupid key '{a}'.",

+                                nexttoken, nexttoken.value);

+                    } else {

+                        o[nexttoken.value] = true;

+                    }

+                    advance();

+                    advance(':');

+                    jsonValue();

+                    if (nexttoken.id !== ',') {

+                        break;

+                    }

+                    advance(',');

+                }

+            }

+            advance('}');

+        }

+

+        function jsonArray() {

+            var t = nexttoken;

+            advance('[');

+            if (nexttoken.id !== ']') {

+                for (;;) {

+                    if (nexttoken.id === '(end)') {

+                        error("Missing ']' to match '[' from line {a}.",

+                                nexttoken, t.line);

+                    } else if (nexttoken.id === ']') {

+                        warning("Unexpected comma.", token);

+                        break;

+                    } else if (nexttoken.id === ',') {

+                        error("Unexpected comma.", nexttoken);

+                    }

+                    jsonValue();

+                    if (nexttoken.id !== ',') {

+                        break;

+                    }

+                    advance(',');

+                }

+            }

+            advance(']');

+        }

+

+        switch (nexttoken.id) {

+        case '{':

+            jsonObject();

+            break;

+        case '[':

+            jsonArray();

+            break;

+        case 'true':

+        case 'false':

+        case 'null':

+        case '(number)':

+        case '(string)':

+            advance();

+            break;

+        case '-':

+            advance('-');

+            if (token.character !== nexttoken.from) {

+                warning("Unexpected space after '-'.", token);

+            }

+            adjacent(token, nexttoken);

+            advance('(number)');

+            break;

+        default:

+            error("Expected a JSON value.", nexttoken);

+        }

+    }

+

+

+// The actual JSHINT function itself.

+

+    var itself = function (s, o, g) {

+        var a, i, k;

+        JSHINT.errors = [];

+        predefined = Object.create(standard);

+        combine(predefined, g || {});

+        if (o) {

+            a = o.predef;

+            if (a) {

+                if (Array.isArray(a)) {

+                    for (i = 0; i < a.length; i += 1) {

+                        predefined[a[i]] = true;

+                    }

+                } else if (typeof a === 'object') {

+                    k = Object.keys(a);

+                    for (i = 0; i < k.length; i += 1) {

+                        predefined[k[i]] = !!a[k];

+                    }

+                }

+            }

+            option = o;

+        } else {

+            option = {};

+        }

+        option.indent = option.indent || 4;

+        option.maxerr = option.maxerr || 50;

+

+        tab = '';

+        for (i = 0; i < option.indent; i += 1) {

+            tab += ' ';

+        }

+        indent = 1;

+        global = Object.create(predefined);

+        scope = global;

+        funct = {

+            '(global)': true,

+            '(name)': '(global)',

+            '(scope)': scope,

+            '(breakage)': 0,

+            '(loopage)': 0

+        };

+        functions = [funct];

+        urls = [];

+        src = false;

+        stack = null;

+        member = {};

+        membersOnly = null;

+        implied = {};

+        inblock = false;

+        lookahead = [];

+        jsonmode = false;

+        warnings = 0;

+        lex.init(s);

+        prereg = true;

+        strict_mode = false;

+

+        prevtoken = token = nexttoken = syntax['(begin)'];

+        assume();

+

+        try {

+            advance();

+            switch (nexttoken.id) {

+            case '{':

+            case '[':

+                option.laxbreak = true;

+                jsonmode = true;

+                jsonValue();

+                break;

+            default:

+                if (nexttoken.value === 'use strict') {

+                    if (!option.globalstrict)

+                        warning("Use the function form of \"use strict\".");

+                    use_strict();

+                }

+                statements('lib');

+            }

+            advance('(end)');

+        } catch (e) {

+            if (e) {

+                JSHINT.errors.push({

+                    reason    : e.message,

+                    line      : e.line || nexttoken.line,

+                    character : e.character || nexttoken.from

+                }, null);

+            }

+        }

+        return JSHINT.errors.length === 0;

+    };

+

+

+// Data summary.

+

+    itself.data = function () {

+

+        var data = {functions: []}, fu, globals, implieds = [], f, i, j,

+            members = [], n, unused = [], v;

+        if (itself.errors.length) {

+            data.errors = itself.errors;

+        }

+

+        if (jsonmode) {

+            data.json = true;

+        }

+

+        for (n in implied) {

+            if (is_own(implied, n)) {

+                implieds.push({

+                    name: n,

+                    line: implied[n]

+                });

+            }

+        }

+        if (implieds.length > 0) {

+            data.implieds = implieds;

+        }

+

+        if (urls.length > 0) {

+            data.urls = urls;

+        }

+

+        globals = Object.keys(scope);

+        if (globals.length > 0) {

+            data.globals = globals;

+        }

+

+        for (i = 1; i < functions.length; i += 1) {

+            f = functions[i];

+            fu = {};

+            for (j = 0; j < functionicity.length; j += 1) {

+                fu[functionicity[j]] = [];

+            }

+            for (n in f) {

+                if (is_own(f, n) && n.charAt(0) !== '(') {

+                    v = f[n];

+                    if (v === 'unction') {

+                        v = 'unused';

+                    }

+                    if (Array.isArray(fu[v])) {

+                        fu[v].push(n);

+                        if (v === 'unused') {

+                            unused.push({

+                                name: n,

+                                line: f['(line)'],

+                                'function': f['(name)']

+                            });

+                        }

+                    }

+                }

+            }

+            for (j = 0; j < functionicity.length; j += 1) {

+                if (fu[functionicity[j]].length === 0) {

+                    delete fu[functionicity[j]];

+                }

+            }

+            fu.name = f['(name)'];

+            fu.param = f['(params)'];

+            fu.line = f['(line)'];

+            fu.last = f['(last)'];

+            data.functions.push(fu);

+        }

+

+        if (unused.length > 0) {

+            data.unused = unused;

+        }

+

+        members = [];

+        for (n in member) {

+            if (typeof member[n] === 'number') {

+                data.member = member;

+                break;

+            }

+        }

+

+        return data;

+    };

+

+    itself.report = function (option) {

+        var data = itself.data();

+

+        var a = [], c, e, err, f, i, k, l, m = '', n, o = [], s;

+

+        function detail(h, array) {

+            var b, i, singularity;

+            if (array) {

+                o.push('<div><i>' + h + '</i> ');

+                array = array.sort();

+                for (i = 0; i < array.length; i += 1) {

+                    if (array[i] !== singularity) {

+                        singularity = array[i];

+                        o.push((b ? ', ' : '') + singularity);

+                        b = true;

+                    }

+                }

+                o.push('</div>');

+            }

+        }

+

+

+        if (data.errors || data.implieds || data.unused) {

+            err = true;

+            o.push('<div id=errors><i>Error:</i>');

+            if (data.errors) {

+                for (i = 0; i < data.errors.length; i += 1) {

+                    c = data.errors[i];

+                    if (c) {

+                        e = c.evidence || '';

+                        o.push('<p>Problem' + (isFinite(c.line) ? ' at line ' +

+                                c.line + ' character ' + c.character : '') +

+                                ': ' + c.reason.entityify() +

+                                '</p><p class=evidence>' +

+                                (e && (e.length > 80 ? e.slice(0, 77) + '...' :

+                                e).entityify()) + '</p>');

+                    }

+                }

+            }

+

+            if (data.implieds) {

+                s = [];

+                for (i = 0; i < data.implieds.length; i += 1) {

+                    s[i] = '<code>' + data.implieds[i].name + '</code>&nbsp;<i>' +

+                        data.implieds[i].line + '</i>';

+                }

+                o.push('<p><i>Implied global:</i> ' + s.join(', ') + '</p>');

+            }

+

+            if (data.unused) {

+                s = [];

+                for (i = 0; i < data.unused.length; i += 1) {

+                    s[i] = '<code><u>' + data.unused[i].name + '</u></code>&nbsp;<i>' +

+                        data.unused[i].line + '</i> <code>' +

+                        data.unused[i]['function'] + '</code>';

+                }

+                o.push('<p><i>Unused variable:</i> ' + s.join(', ') + '</p>');

+            }

+            if (data.json) {

+                o.push('<p>JSON: bad.</p>');

+            }

+            o.push('</div>');

+        }

+

+        if (!option) {

+

+            o.push('<br><div id=functions>');

+

+            if (data.urls) {

+                detail("URLs<br>", data.urls, '<br>');

+            }

+

+            if (data.json && !err) {

+                o.push('<p>JSON: good.</p>');

+            } else if (data.globals) {

+                o.push('<div><i>Global</i> ' +

+                        data.globals.sort().join(', ') + '</div>');

+            } else {

+                o.push('<div><i>No new global variables introduced.</i></div>');

+            }

+

+            for (i = 0; i < data.functions.length; i += 1) {

+                f = data.functions[i];

+

+                o.push('<br><div class=function><i>' + f.line + '-' +

+                        f.last + '</i> ' + (f.name || '') + '(' +

+                        (f.param ? f.param.join(', ') : '') + ')</div>');

+                detail('<big><b>Unused</b></big>', f.unused);

+                detail('Closure', f.closure);

+                detail('Variable', f['var']);

+                detail('Exception', f.exception);

+                detail('Outer', f.outer);

+                detail('Global', f.global);

+                detail('Label', f.label);

+            }

+

+            if (data.member) {

+                a = Object.keys(data.member);

+                if (a.length) {

+                    a = a.sort();

+                    m = '<br><pre id=members>/*members ';

+                    l = 10;

+                    for (i = 0; i < a.length; i += 1) {

+                        k = a[i];

+                        n = k.name();

+                        if (l + n.length > 72) {

+                            o.push(m + '<br>');

+                            m = '    ';

+                            l = 1;

+                        }

+                        l += n.length + 2;

+                        if (data.member[k] === 1) {

+                            n = '<i>' + n + '</i>';

+                        }

+                        if (i < a.length - 1) {

+                            n += ', ';

+                        }

+                        m += n;

+                    }

+                    o.push(m + '<br>*/</pre>');

+                }

+                o.push('</div>');

+            }

+        }

+        return o.join('');

+    };

+    itself.jshint = itself;

+

+    itself.edition = '2011-04-16';

+

+    return itself;

+

+}());

+

+// Make JSHINT a Node module, if possible.

+if (typeof exports == 'object' && exports)

+    exports.JSHINT = JSHINT;

diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/lib/jslint.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/lib/jslint.js
new file mode 100644
index 0000000..f563292
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/lib/jslint.js
@@ -0,0 +1,5504 @@
+// jslint.js
+// 2010-02-20
+
+/*
+Copyright (c) 2002 Douglas Crockford  (www.JSLint.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+/*
+    JSLINT is a global function. It takes two parameters.
+
+        var myResult = JSLINT(source, option);
+
+    The first parameter is either a string or an array of strings. If it is a
+    string, it will be split on '\n' or '\r'. If it is an array of strings, it
+    is assumed that each string represents one line. The source can be a
+    JavaScript text, or HTML text, or a Konfabulator text.
+
+    The second parameter is an optional object of options which control the
+    operation of JSLINT. Most of the options are booleans: They are all are
+    optional and have a default value of false.
+
+    If it checks out, JSLINT returns true. Otherwise, it returns false.
+
+    If false, you can inspect JSLINT.errors to find out the problems.
+    JSLINT.errors is an array of objects containing these members:
+
+    {
+        line      : The line (relative to 0) at which the lint was found
+        character : The character (relative to 0) at which the lint was found
+        reason    : The problem
+        evidence  : The text line in which the problem occurred
+        raw       : The raw message before the details were inserted
+        a         : The first detail
+        b         : The second detail
+        c         : The third detail
+        d         : The fourth detail
+    }
+
+    If a fatal error was found, a null will be the last element of the
+    JSLINT.errors array.
+
+    You can request a Function Report, which shows all of the functions
+    and the parameters and vars that they use. This can be used to find
+    implied global variables and other problems. The report is in HTML and
+    can be inserted in an HTML <body>.
+
+        var myReport = JSLINT.report(limited);
+
+    If limited is true, then the report will be limited to only errors.
+
+    You can request a data structure which contains JSLint's results.
+
+        var myData = JSLINT.data();
+
+    It returns a structure with this form:
+
+    {
+        errors: [
+            {
+                line: NUMBER,
+                character: NUMBER,
+                reason: STRING,
+                evidence: STRING
+            }
+        ],
+        functions: [
+            name: STRING,
+            line: NUMBER,
+            last: NUMBER,
+            param: [
+                STRING
+            ],
+            closure: [
+                STRING
+            ],
+            var: [
+                STRING
+            ],
+            exception: [
+                STRING
+            ],
+            outer: [
+                STRING
+            ],
+            unused: [
+                STRING
+            ],
+            global: [
+                STRING
+            ],
+            label: [
+                STRING
+            ]
+        ],
+        globals: [
+            STRING
+        ],
+        member: {
+            STRING: NUMBER
+        },
+        unuseds: [
+            {
+                name: STRING,
+                line: NUMBER
+            }
+        ],
+        implieds: [
+            {
+                name: STRING,
+                line: NUMBER
+            }
+        ],
+        urls: [
+            STRING
+        ],
+        json: BOOLEAN
+    }
+
+    Empty arrays will not be included.
+
+*/
+
+/*jslint
+    evil: true, nomen: false, onevar: false, regexp: false, strict: true
+*/
+
+/*members "\b", "\t", "\n", "\f", "\r", "!=", "!==", "\"", "%",
+    "(begin)", "(breakage)", "(context)", "(error)", "(global)",
+    "(identifier)", "(last)", "(line)", "(loopage)", "(name)", "(onevar)",
+    "(params)", "(scope)", "(verb)", "*", "+", "++", "-", "--", "\/",
+    "<", "<=", "==", "===", ">", ">=", ADSAFE, Array, Boolean,
+    COM, Canvas, CustomAnimation, Date, Debug, E, Error, EvalError,
+    FadeAnimation, Flash, FormField, Frame, Function, HotKey, Image, JSON,
+    LN10, LN2, LOG10E, LOG2E, MAX_VALUE, MIN_VALUE, Math, MenuItem,
+    MoveAnimation, NEGATIVE_INFINITY, Number, Object, Option, PI,
+    POSITIVE_INFINITY, Point, RangeError, Rectangle, ReferenceError, RegExp,
+    ResizeAnimation, RotateAnimation, SQRT1_2, SQRT2, ScrollBar, String,
+    Style, SyntaxError, System, Text, TextArea, Timer, TypeError, URIError,
+    URL, Web, Window, XMLDOM, XMLHttpRequest, "\\", a, abbr, acronym,
+    addEventListener, address, adsafe, alert, aliceblue, animator,
+    antiquewhite, appleScript, applet, apply, approved, aqua, aquamarine,
+    area, arguments, arity, autocomplete, azure, b, background,
+    "background-attachment", "background-color", "background-image",
+    "background-position", "background-repeat", base, bdo, beep, beige, big,
+    bisque, bitwise, black, blanchedalmond, block, blockquote, blue,
+    blueviolet, blur, body, border, "border-bottom", "border-bottom-color",
+    "border-bottom-style", "border-bottom-width", "border-collapse",
+    "border-color", "border-left", "border-left-color", "border-left-style",
+    "border-left-width", "border-right", "border-right-color",
+    "border-right-style", "border-right-width", "border-spacing",
+    "border-style", "border-top", "border-top-color", "border-top-style",
+    "border-top-width", "border-width", bottom, br, brown, browser,
+    burlywood, button, bytesToUIString, c, cadetblue, call, callee, caller,
+    canvas, cap, caption, "caption-side", cases, center, charAt, charCodeAt,
+    character, chartreuse, chocolate, chooseColor, chooseFile, chooseFolder,
+    cite, clear, clearInterval, clearTimeout, clip, close, closeWidget,
+    closed, closure, cm, code, col, colgroup, color, comment, condition,
+    confirm, console, constructor, content, convertPathToHFS,
+    convertPathToPlatform, coral, cornflowerblue, cornsilk,
+    "counter-increment", "counter-reset", create, crimson, css, cursor,
+    cyan, d, darkblue, darkcyan, darkgoldenrod, darkgray, darkgreen,
+    darkkhaki, darkmagenta, darkolivegreen, darkorange, darkorchid, darkred,
+    darksalmon, darkseagreen, darkslateblue, darkslategray, darkturquoise,
+    darkviolet, data, dd, debug, decodeURI, decodeURIComponent, deeppink,
+    deepskyblue, defaultStatus, defineClass, del, deserialize, devel, dfn,
+    dimension, dimgray, dir, direction, display, div, dl, document,
+    dodgerblue, dt, edition, else, em, embed, empty, "empty-cells",
+    encodeURI, encodeURIComponent, entityify, eqeqeq, errors, escape, eval,
+    event, evidence, evil, ex, exception, exec, exps, fieldset, filesystem,
+    firebrick, first, float, floor, floralwhite, focus, focusWidget, font,
+    "font-face", "font-family", "font-size", "font-size-adjust",
+    "font-stretch", "font-style", "font-variant", "font-weight",
+    forestgreen, forin, form, fragment, frame, frames, frameset, from,
+    fromCharCode, fuchsia, fud, funct, function, functions, g, gainsboro,
+    gc, getComputedStyle, ghostwhite, global, globals, gold, goldenrod,
+    gray, green, greenyellow, h1, h2, h3, h4, h5, h6, hasOwnProperty, head,
+    height, help, history, honeydew, hotpink, hr, html, i, iTunes, id,
+    identifier, iframe, img, immed, implieds, in, include, indent, indexOf,
+    indianred, indigo, init, input, ins, isAlpha, isApplicationRunning,
+    isDigit, isFinite, isNaN, ivory, join, jslint, json, kbd, khaki,
+    konfabulatorVersion, label, labelled, lang, last, lavender,
+    lavenderblush, lawngreen, laxbreak, lbp, led, left, legend,
+    lemonchiffon, length, "letter-spacing", li, lib, lightblue, lightcoral,
+    lightcyan, lightgoldenrodyellow, lightgreen, lightpink, lightsalmon,
+    lightseagreen, lightskyblue, lightslategray, lightsteelblue,
+    lightyellow, lime, limegreen, line, "line-height", linen, link,
+    "list-style", "list-style-image", "list-style-position",
+    "list-style-type", load, loadClass, location, log, m, magenta, map,
+    margin, "margin-bottom", "margin-left", "margin-right", "margin-top",
+    "marker-offset", maroon, match, "max-height", "max-width", maxerr, maxlen,
+    md5, media, mediumaquamarine, mediumblue, mediumorchid, mediumpurple,
+    mediumseagreen, mediumslateblue, mediumspringgreen, mediumturquoise,
+    mediumvioletred, member, menu, message, meta, midnightblue,
+    "min-height", "min-width", mintcream, mistyrose, mm, moccasin, moveBy,
+    moveTo, name, navajowhite, navigator, navy, new, newcap, noframes,
+    nomen, noscript, nud, object, ol, oldlace, olive, olivedrab, on,
+    onbeforeunload, onblur, onerror, onevar, onfocus, onload, onresize,
+    onunload, opacity, open, openURL, opener, opera, optgroup, option,
+    orange, orangered, orchid, outer, outline, "outline-color",
+    "outline-style", "outline-width", overflow, "overflow-x", "overflow-y",
+    p, padding, "padding-bottom", "padding-left", "padding-right",
+    "padding-top", page, "page-break-after", "page-break-before",
+    palegoldenrod, palegreen, paleturquoise, palevioletred, papayawhip,
+    param, parent, parseFloat, parseInt, passfail, pc, peachpuff, peru,
+    pink, play, plum, plusplus, pop, popupMenu, position, powderblue, pre,
+    predef, preferenceGroups, preferences, print, prompt, prototype, pt,
+    purple, push, px, q, quit, quotes, random, range, raw, reach, readFile,
+    readUrl, reason, red, regexp, reloadWidget, removeEventListener,
+    replace, report, reserved, resizeBy, resizeTo, resolvePath,
+    resumeUpdates, rhino, right, rosybrown, royalblue, runCommand,
+    runCommandInBg, saddlebrown, safe, salmon, samp, sandybrown, saveAs,
+    savePreferences, screen, script, scroll, scrollBy, scrollTo, seagreen,
+    seal, search, seashell, select, serialize, setInterval, setTimeout,
+    shift, showWidgetPreferences, sidebar, sienna, silver, skyblue,
+    slateblue, slategray, sleep, slice, small, snow, sort, span, spawn,
+    speak, split, springgreen, src, status, steelblue, strict, strong,
+    style, styleproperty, sub, substr, sup, supplant, suppressUpdates, sync,
+    system, table, "table-layout", tan, tbody, td, teal, tellWidget, test,
+    "text-align", "text-decoration", "text-indent", "text-shadow",
+    "text-transform", textarea, tfoot, th, thead, thistle, title,
+    toLowerCase, toString, toUpperCase, toint32, token, tomato, top, tr, tt,
+    turquoise, type, u, ul, undef, unescape, "unicode-bidi", unused,
+    unwatch, updateNow, urls, value, valueOf, var, version,
+    "vertical-align", violet, visibility, watch, wheat, white,
+    "white-space", whitesmoke, widget, width, "word-spacing", "word-wrap",
+    yahooCheckLogin, yahooLogin, yahooLogout, yellow, yellowgreen,
+    "z-index"
+*/
+
+
+// We build the application inside a function so that we produce only a single
+// global variable. The function will be invoked, its return value is the JSLINT
+// application itself.
+
+"use strict";
+
+var JSLINT = (function () {
+    var adsafe_id,      // The widget's ADsafe id.
+        adsafe_may,     // The widget may load approved scripts.
+        adsafe_went,    // ADSAFE.go has been called.
+        anonname,       // The guessed name for anonymous functions.
+        approved,       // ADsafe approved urls.
+
+        atrule = {
+            media      : true,
+            'font-face': true,
+            page       : true
+        },
+
+// These are operators that should not be used with the ! operator.
+
+        bang = {
+            '<': true,
+            '<=': true,
+            '==': true,
+            '===': true,
+            '!==': true,
+            '!=': true,
+            '>': true,
+            '>=': true,
+            '+': true,
+            '-': true,
+            '*': true,
+            '/': true,
+            '%': true
+        },
+
+// These are members that should not be permitted in the safe subset.
+
+        banned = {              // the member names that ADsafe prohibits.
+            'arguments'     : true,
+            callee          : true,
+            caller          : true,
+            constructor     : true,
+            'eval'          : true,
+            prototype       : true,
+            unwatch         : true,
+            valueOf         : true,
+            watch           : true
+        },
+
+
+// These are the JSLint boolean options.
+
+        boolOptions = {
+            adsafe     : true, // if ADsafe should be enforced
+            bitwise    : true, // if bitwise operators should not be allowed
+            browser    : true, // if the standard browser globals should be predefined
+            cap        : true, // if upper case HTML should be allowed
+            css        : true, // if CSS workarounds should be tolerated
+            debug      : true, // if debugger statements should be allowed
+            devel      : true, // if logging should be allowed (console, alert, etc.)
+            eqeqeq     : true, // if === should be required
+            evil       : true, // if eval should be allowed
+            forin      : true, // if for in statements must filter
+            fragment   : true, // if HTML fragments should be allowed
+            immed      : true, // if immediate invocations must be wrapped in parens
+            laxbreak   : true, // if line breaks should not be checked
+            newcap     : true, // if constructor names must be capitalized
+            nomen      : true, // if names should be checked
+            on         : true, // if HTML event handlers should be allowed
+            onevar     : true, // if only one var statement per function should be allowed
+            passfail   : true, // if the scan should stop on first error
+            plusplus   : true, // if increment/decrement should not be allowed
+            regexp     : true, // if the . should not be allowed in regexp literals
+            rhino      : true, // if the Rhino environment globals should be predefined
+            undef      : true, // if variables should be declared before used
+            safe       : true, // if use of some browser features should be restricted
+            sidebar    : true, // if the System object should be predefined
+            strict     : true, // require the "use strict"; pragma
+            sub        : true, // if all forms of subscript notation are tolerated
+            white      : true, // if strict whitespace rules apply
+            widget     : true  // if the Yahoo Widgets globals should be predefined
+        },
+
+// browser contains a set of global names which are commonly provided by a
+// web browser environment.
+
+        browser = {
+            addEventListener: false,
+            blur            : false,
+            clearInterval   : false,
+            clearTimeout    : false,
+            close           : false,
+            closed          : false,
+            defaultStatus   : false,
+            document        : false,
+            event           : false,
+            focus           : false,
+            frames          : false,
+            getComputedStyle: false,
+            history         : false,
+            Image           : false,
+            length          : false,
+            location        : false,
+            moveBy          : false,
+            moveTo          : false,
+            name            : false,
+            navigator       : false,
+            onbeforeunload  : true,
+            onblur          : true,
+            onerror         : true,
+            onfocus         : true,
+            onload          : true,
+            onresize        : true,
+            onunload        : true,
+            open            : false,
+            opener          : false,
+            Option          : false,
+            parent          : false,
+            print           : false,
+            removeEventListener: false,
+            resizeBy        : false,
+            resizeTo        : false,
+            screen          : false,
+            scroll          : false,
+            scrollBy        : false,
+            scrollTo        : false,
+            setInterval     : false,
+            setTimeout      : false,
+            status          : false,
+            top             : false,
+            XMLHttpRequest  : false
+        },
+
+        cssAttributeData,
+        cssAny,
+
+        cssColorData = {
+            "aliceblue"             : true,
+            "antiquewhite"          : true,
+            "aqua"                  : true,
+            "aquamarine"            : true,
+            "azure"                 : true,
+            "beige"                 : true,
+            "bisque"                : true,
+            "black"                 : true,
+            "blanchedalmond"        : true,
+            "blue"                  : true,
+            "blueviolet"            : true,
+            "brown"                 : true,
+            "burlywood"             : true,
+            "cadetblue"             : true,
+            "chartreuse"            : true,
+            "chocolate"             : true,
+            "coral"                 : true,
+            "cornflowerblue"        : true,
+            "cornsilk"              : true,
+            "crimson"               : true,
+            "cyan"                  : true,
+            "darkblue"              : true,
+            "darkcyan"              : true,
+            "darkgoldenrod"         : true,
+            "darkgray"              : true,
+            "darkgreen"             : true,
+            "darkkhaki"             : true,
+            "darkmagenta"           : true,
+            "darkolivegreen"        : true,
+            "darkorange"            : true,
+            "darkorchid"            : true,
+            "darkred"               : true,
+            "darksalmon"            : true,
+            "darkseagreen"          : true,
+            "darkslateblue"         : true,
+            "darkslategray"         : true,
+            "darkturquoise"         : true,
+            "darkviolet"            : true,
+            "deeppink"              : true,
+            "deepskyblue"           : true,
+            "dimgray"               : true,
+            "dodgerblue"            : true,
+            "firebrick"             : true,
+            "floralwhite"           : true,
+            "forestgreen"           : true,
+            "fuchsia"               : true,
+            "gainsboro"             : true,
+            "ghostwhite"            : true,
+            "gold"                  : true,
+            "goldenrod"             : true,
+            "gray"                  : true,
+            "green"                 : true,
+            "greenyellow"           : true,
+            "honeydew"              : true,
+            "hotpink"               : true,
+            "indianred"             : true,
+            "indigo"                : true,
+            "ivory"                 : true,
+            "khaki"                 : true,
+            "lavender"              : true,
+            "lavenderblush"         : true,
+            "lawngreen"             : true,
+            "lemonchiffon"          : true,
+            "lightblue"             : true,
+            "lightcoral"            : true,
+            "lightcyan"             : true,
+            "lightgoldenrodyellow"  : true,
+            "lightgreen"            : true,
+            "lightpink"             : true,
+            "lightsalmon"           : true,
+            "lightseagreen"         : true,
+            "lightskyblue"          : true,
+            "lightslategray"        : true,
+            "lightsteelblue"        : true,
+            "lightyellow"           : true,
+            "lime"                  : true,
+            "limegreen"             : true,
+            "linen"                 : true,
+            "magenta"               : true,
+            "maroon"                : true,
+            "mediumaquamarine"      : true,
+            "mediumblue"            : true,
+            "mediumorchid"          : true,
+            "mediumpurple"          : true,
+            "mediumseagreen"        : true,
+            "mediumslateblue"       : true,
+            "mediumspringgreen"     : true,
+            "mediumturquoise"       : true,
+            "mediumvioletred"       : true,
+            "midnightblue"          : true,
+            "mintcream"             : true,
+            "mistyrose"             : true,
+            "moccasin"              : true,
+            "navajowhite"           : true,
+            "navy"                  : true,
+            "oldlace"               : true,
+            "olive"                 : true,
+            "olivedrab"             : true,
+            "orange"                : true,
+            "orangered"             : true,
+            "orchid"                : true,
+            "palegoldenrod"         : true,
+            "palegreen"             : true,
+            "paleturquoise"         : true,
+            "palevioletred"         : true,
+            "papayawhip"            : true,
+            "peachpuff"             : true,
+            "peru"                  : true,
+            "pink"                  : true,
+            "plum"                  : true,
+            "powderblue"            : true,
+            "purple"                : true,
+            "red"                   : true,
+            "rosybrown"             : true,
+            "royalblue"             : true,
+            "saddlebrown"           : true,
+            "salmon"                : true,
+            "sandybrown"            : true,
+            "seagreen"              : true,
+            "seashell"              : true,
+            "sienna"                : true,
+            "silver"                : true,
+            "skyblue"               : true,
+            "slateblue"             : true,
+            "slategray"             : true,
+            "snow"                  : true,
+            "springgreen"           : true,
+            "steelblue"             : true,
+            "tan"                   : true,
+            "teal"                  : true,
+            "thistle"               : true,
+            "tomato"                : true,
+            "turquoise"             : true,
+            "violet"                : true,
+            "wheat"                 : true,
+            "white"                 : true,
+            "whitesmoke"            : true,
+            "yellow"                : true,
+            "yellowgreen"           : true
+        },
+
+        cssBorderStyle,
+        cssBreak,
+
+        cssLengthData = {
+            '%': true,
+            'cm': true,
+            'em': true,
+            'ex': true,
+            'in': true,
+            'mm': true,
+            'pc': true,
+            'pt': true,
+            'px': true
+        },
+
+        cssOverflow,
+
+        devel = {
+            alert           : false,
+            confirm         : false,
+            console         : false,
+            Debug           : false,
+            opera           : false,
+            prompt          : false
+        },
+
+        escapes = {
+            '\b': '\\b',
+            '\t': '\\t',
+            '\n': '\\n',
+            '\f': '\\f',
+            '\r': '\\r',
+            '"' : '\\"',
+            '/' : '\\/',
+            '\\': '\\\\'
+        },
+
+        funct,          // The current function
+
+        functionicity = [
+            'closure', 'exception', 'global', 'label',
+            'outer', 'unused', 'var'
+        ],
+
+        functions,      // All of the functions
+
+        global,         // The global scope
+        htmltag = {
+            a:        {},
+            abbr:     {},
+            acronym:  {},
+            address:  {},
+            applet:   {},
+            area:     {empty: true, parent: ' map '},
+            b:        {},
+            base:     {empty: true, parent: ' head '},
+            bdo:      {},
+            big:      {},
+            blockquote: {},
+            body:     {parent: ' html noframes '},
+            br:       {empty: true},
+            button:   {},
+            canvas:   {parent: ' body p div th td '},
+            caption:  {parent: ' table '},
+            center:   {},
+            cite:     {},
+            code:     {},
+            col:      {empty: true, parent: ' table colgroup '},
+            colgroup: {parent: ' table '},
+            dd:       {parent: ' dl '},
+            del:      {},
+            dfn:      {},
+            dir:      {},
+            div:      {},
+            dl:       {},
+            dt:       {parent: ' dl '},
+            em:       {},
+            embed:    {},
+            fieldset: {},
+            font:     {},
+            form:     {},
+            frame:    {empty: true, parent: ' frameset '},
+            frameset: {parent: ' html frameset '},
+            h1:       {},
+            h2:       {},
+            h3:       {},
+            h4:       {},
+            h5:       {},
+            h6:       {},
+            head:     {parent: ' html '},
+            html:     {parent: '*'},
+            hr:       {empty: true},
+            i:        {},
+            iframe:   {},
+            img:      {empty: true},
+            input:    {empty: true},
+            ins:      {},
+            kbd:      {},
+            label:    {},
+            legend:   {parent: ' fieldset '},
+            li:       {parent: ' dir menu ol ul '},
+            link:     {empty: true, parent: ' head '},
+            map:      {},
+            menu:     {},
+            meta:     {empty: true, parent: ' head noframes noscript '},
+            noframes: {parent: ' html body '},
+            noscript: {parent: ' body head noframes '},
+            object:   {},
+            ol:       {},
+            optgroup: {parent: ' select '},
+            option:   {parent: ' optgroup select '},
+            p:        {},
+            param:    {empty: true, parent: ' applet object '},
+            pre:      {},
+            q:        {},
+            samp:     {},
+            script:   {empty: true, parent: ' body div frame head iframe p pre span '},
+            select:   {},
+            small:    {},
+            span:     {},
+            strong:   {},
+            style:    {parent: ' head ', empty: true},
+            sub:      {},
+            sup:      {},
+            table:    {},
+            tbody:    {parent: ' table '},
+            td:       {parent: ' tr '},
+            textarea: {},
+            tfoot:    {parent: ' table '},
+            th:       {parent: ' tr '},
+            thead:    {parent: ' table '},
+            title:    {parent: ' head '},
+            tr:       {parent: ' table tbody thead tfoot '},
+            tt:       {},
+            u:        {},
+            ul:       {},
+            'var':    {}
+        },
+
+        ids,            // HTML ids
+        implied,        // Implied globals
+        inblock,
+        indent,
+        jsonmode,
+        lines,
+        lookahead,
+        member,
+        membersOnly,
+        nexttoken,
+        noreach,
+        option,
+        predefined,     // Global variables defined by option
+        prereg,
+        prevtoken,
+
+        rhino = {
+            defineClass : false,
+            deserialize : false,
+            gc          : false,
+            help        : false,
+            load        : false,
+            loadClass   : false,
+            print       : false,
+            quit        : false,
+            readFile    : false,
+            readUrl     : false,
+            runCommand  : false,
+            seal        : false,
+            serialize   : false,
+            spawn       : false,
+            sync        : false,
+            toint32     : false,
+            version     : false
+        },
+
+        scope,      // The current scope
+
+        sidebar = {
+            System      : false
+        },
+
+        src,
+        stack,
+
+// standard contains the global names that are provided by the
+// ECMAScript standard.
+
+        standard = {
+            Array               : false,
+            Boolean             : false,
+            Date                : false,
+            decodeURI           : false,
+            decodeURIComponent  : false,
+            encodeURI           : false,
+            encodeURIComponent  : false,
+            Error               : false,
+            'eval'              : false,
+            EvalError           : false,
+            Function            : false,
+            hasOwnProperty      : false,
+            isFinite            : false,
+            isNaN               : false,
+            JSON                : false,
+            Math                : false,
+            Number              : false,
+            Object              : false,
+            parseInt            : false,
+            parseFloat          : false,
+            RangeError          : false,
+            ReferenceError      : false,
+            RegExp              : false,
+            String              : false,
+            SyntaxError         : false,
+            TypeError           : false,
+            URIError            : false
+        },
+
+        standard_member = {
+            E                   : true,
+            LN2                 : true,
+            LN10                : true,
+            LOG2E               : true,
+            LOG10E              : true,
+            PI                  : true,
+            SQRT1_2             : true,
+            SQRT2               : true,
+            MAX_VALUE           : true,
+            MIN_VALUE           : true,
+            NEGATIVE_INFINITY   : true,
+            POSITIVE_INFINITY   : true
+        },
+
+        strict_mode,
+        syntax = {},
+        tab,
+        token,
+        urls,
+        warnings,
+
+// widget contains the global names which are provided to a Yahoo
+// (fna Konfabulator) widget.
+
+        widget = {
+            alert                   : true,
+            animator                : true,
+            appleScript             : true,
+            beep                    : true,
+            bytesToUIString         : true,
+            Canvas                  : true,
+            chooseColor             : true,
+            chooseFile              : true,
+            chooseFolder            : true,
+            closeWidget             : true,
+            COM                     : true,
+            convertPathToHFS        : true,
+            convertPathToPlatform   : true,
+            CustomAnimation         : true,
+            escape                  : true,
+            FadeAnimation           : true,
+            filesystem              : true,
+            Flash                   : true,
+            focusWidget             : true,
+            form                    : true,
+            FormField               : true,
+            Frame                   : true,
+            HotKey                  : true,
+            Image                   : true,
+            include                 : true,
+            isApplicationRunning    : true,
+            iTunes                  : true,
+            konfabulatorVersion     : true,
+            log                     : true,
+            md5                     : true,
+            MenuItem                : true,
+            MoveAnimation           : true,
+            openURL                 : true,
+            play                    : true,
+            Point                   : true,
+            popupMenu               : true,
+            preferenceGroups        : true,
+            preferences             : true,
+            print                   : true,
+            prompt                  : true,
+            random                  : true,
+            Rectangle               : true,
+            reloadWidget            : true,
+            ResizeAnimation         : true,
+            resolvePath             : true,
+            resumeUpdates           : true,
+            RotateAnimation         : true,
+            runCommand              : true,
+            runCommandInBg          : true,
+            saveAs                  : true,
+            savePreferences         : true,
+            screen                  : true,
+            ScrollBar               : true,
+            showWidgetPreferences   : true,
+            sleep                   : true,
+            speak                   : true,
+            Style                   : true,
+            suppressUpdates         : true,
+            system                  : true,
+            tellWidget              : true,
+            Text                    : true,
+            TextArea                : true,
+            Timer                   : true,
+            unescape                : true,
+            updateNow               : true,
+            URL                     : true,
+            Web                     : true,
+            widget                  : true,
+            Window                  : true,
+            XMLDOM                  : true,
+            XMLHttpRequest          : true,
+            yahooCheckLogin         : true,
+            yahooLogin              : true,
+            yahooLogout             : true
+        },
+
+//  xmode is used to adapt to the exceptions in html parsing.
+//  It can have these states:
+//      false   .js script file
+//      html
+//      outer
+//      script
+//      style
+//      scriptstring
+//      styleproperty
+
+        xmode,
+        xquote,
+
+// unsafe comment or string
+        ax = /@cc|<\/?|script|\]*s\]|<\s*!|&lt/i,
+// unsafe characters that are silently deleted by one or more browsers
+        cx = /[\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/,
+// token
+        tx = /^\s*([(){}\[.,:;'"~\?\]#@]|==?=?|\/(\*(jslint|members?|global)?|=|\/)?|\*[\/=]?|\+[+=]?|-[\-=]?|%=?|&[&=]?|\|[|=]?|>>?>?=?|<([\/=!]|\!(\[|--)?|<=?)?|\^=?|\!=?=?|[a-zA-Z_$][a-zA-Z0-9_$]*|[0-9]+([xX][0-9a-fA-F]+|\.[0-9]*)?([eE][+\-]?[0-9]+)?)/,
+// html token
+////////        hx = /^\s*(['"=>\/&#]|<(?:\/|\!(?:--)?)?|[a-zA-Z][a-zA-Z0-9_\-]*|[0-9]+|--|.)/,
+        hx = /^\s*(['"=>\/&#]|<(?:\/|\!(?:--)?)?|[a-zA-Z][a-zA-Z0-9_\-]*|[0-9]+|--)/,
+// characters in strings that need escapement
+        nx = /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/,
+        nxg = /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+// outer html token
+        ox = /[>&]|<[\/!]?|--/,
+// star slash
+        lx = /\*\/|\/\*/,
+// identifier
+        ix = /^([a-zA-Z_$][a-zA-Z0-9_$]*)$/,
+// javascript url
+        jx = /^(?:javascript|jscript|ecmascript|vbscript|mocha|livescript)\s*:/i,
+// url badness
+        ux = /&|\+|\u00AD|\.\.|\/\*|%[^;]|base64|url|expression|data|mailto/i,
+// style
+        sx = /^\s*([{:#%.=,>+\[\]@()"';]|\*=?|\$=|\|=|\^=|~=|[a-zA-Z_][a-zA-Z0-9_\-]*|[0-9]+|<\/|\/\*)/,
+        ssx = /^\s*([@#!"'};:\-%.=,+\[\]()*_]|[a-zA-Z][a-zA-Z0-9._\-]*|\/\*?|\d+(?:\.\d+)?|<\/)/,
+// attributes characters
+        qx = /[^a-zA-Z0-9-_\/ ]/,
+// query characters for ids
+        dx = /[\[\]\/\\"'*<>.&:(){}+=#]/,
+
+        rx = {
+            outer: hx,
+            html: hx,
+            style: sx,
+            styleproperty: ssx
+        };
+
+    function F() {}
+
+    if (typeof Object.create !== 'function') {
+        Object.create = function (o) {
+            F.prototype = o;
+            return new F();
+        };
+    }
+
+
+    function is_own(object, name) {
+        return Object.prototype.hasOwnProperty.call(object, name);
+    }
+
+
+    function combine(t, o) {
+        var n;
+        for (n in o) {
+            if (is_own(o, n)) {
+                t[n] = o[n];
+            }
+        }
+    }
+
+    String.prototype.entityify = function () {
+        return this.
+            replace(/&/g, '&amp;').
+            replace(/</g, '&lt;').
+            replace(/>/g, '&gt;');
+    };
+
+    String.prototype.isAlpha = function () {
+        return (this >= 'a' && this <= 'z\uffff') ||
+            (this >= 'A' && this <= 'Z\uffff');
+    };
+
+
+    String.prototype.isDigit = function () {
+        return (this >= '0' && this <= '9');
+    };
+
+
+    String.prototype.supplant = function (o) {
+        return this.replace(/\{([^{}]*)\}/g, function (a, b) {
+            var r = o[b];
+            return typeof r === 'string' || typeof r === 'number' ? r : a;
+        });
+    };
+
+    String.prototype.name = function () {
+
+// If the string looks like an identifier, then we can return it as is.
+// If the string contains no control characters, no quote characters, and no
+// backslash characters, then we can simply slap some quotes around it.
+// Otherwise we must also replace the offending characters with safe
+// sequences.
+
+        if (ix.test(this)) {
+            return this;
+        }
+        if (nx.test(this)) {
+            return '"' + this.replace(nxg, function (a) {
+                var c = escapes[a];
+                if (c) {
+                    return c;
+                }
+                return '\\u' + ('0000' + a.charCodeAt().toString(16)).slice(-4);
+            }) + '"';
+        }
+        return '"' + this + '"';
+    };
+
+
+    function assume() {
+        if (!option.safe) {
+            if (option.rhino) {
+                combine(predefined, rhino);
+            }
+            if (option.devel) {
+                combine(predefined, devel);
+            }
+            if (option.browser || option.sidebar) {
+                combine(predefined, browser);
+            }
+            if (option.sidebar) {
+                combine(predefined, sidebar);
+            }
+            if (option.widget) {
+                combine(predefined, widget);
+            }
+        }
+    }
+
+
+// Produce an error warning.
+
+    function quit(m, l, ch) {
+        throw {
+            name: 'JSLintError',
+            line: l,
+            character: ch,
+            message: m + " (" + Math.floor((l / lines.length) * 100) +
+                    "% scanned)."
+        };
+    }
+
+    function warning(m, t, a, b, c, d) {
+        var ch, l, w;
+        t = t || nexttoken;
+        if (t.id === '(end)') {  // `~
+            t = token;
+        }
+        l = t.line || 0;
+        ch = t.from || 0;
+        w = {
+            id: '(error)',
+            raw: m,
+            evidence: lines[l - 1] || '',
+            line: l,
+            character: ch,
+            a: a,
+            b: b,
+            c: c,
+            d: d
+        };
+        w.reason = m.supplant(w);
+        JSLINT.errors.push(w);
+        if (option.passfail) {
+            quit('Stopping. ', l, ch);
+        }
+        warnings += 1;
+        if (warnings >= option.maxerr) {
+            quit("Too many errors.", l, ch);
+        }
+        return w;
+    }
+
+    function warningAt(m, l, ch, a, b, c, d) {
+        return warning(m, {
+            line: l,
+            from: ch
+        }, a, b, c, d);
+    }
+
+    function error(m, t, a, b, c, d) {
+        var w = warning(m, t, a, b, c, d);
+        quit("Stopping, unable to continue.", w.line, w.character);
+    }
+
+    function errorAt(m, l, ch, a, b, c, d) {
+        return error(m, {
+            line: l,
+            from: ch
+        }, a, b, c, d);
+    }
+
+
+
+// lexical analysis
+
+    var lex = (function lex() {
+        var character, from, line, s;
+
+// Private lex methods
+
+        function nextLine() {
+            var at;
+            if (line >= lines.length) {
+                return false;
+            }
+            character = 1;
+            s = lines[line];
+            line += 1;
+            at = s.search(/ \t/);
+            if (at >= 0) {
+                warningAt("Mixed spaces and tabs.", line, at + 1);
+            }
+            s = s.replace(/\t/g, tab);
+            at = s.search(cx);
+            if (at >= 0) {
+                warningAt("Unsafe character.", line, at);
+            }
+            if (option.maxlen && option.maxlen < s.length) {
+                warningAt("Line too long.", line, s.length);
+            }
+            return true;
+        }
+
+// Produce a token object.  The token inherits from a syntax symbol.
+
+        function it(type, value) {
+            var i, t;
+            if (type === '(color)') {
+                t = {type: type};
+            } else if (type === '(punctuator)' ||
+                    (type === '(identifier)' && is_own(syntax, value))) {
+                t = syntax[value] || syntax['(error)'];
+            } else {
+                t = syntax[type];
+            }
+            t = Object.create(t);
+            if (type === '(string)' || type === '(range)') {
+                if (jx.test(value)) {
+                    warningAt("Script URL.", line, from);
+                }
+            }
+            if (type === '(identifier)') {
+                t.identifier = true;
+                if (value === '__iterator__' || value === '__proto__') {
+                    errorAt("Reserved name '{a}'.",
+                        line, from, value);
+                } else if (option.nomen &&
+                        (value.charAt(0) === '_' ||
+                         value.charAt(value.length - 1) === '_')) {
+                    warningAt("Unexpected {a} in '{b}'.", line, from,
+                        "dangling '_'", value);
+                }
+            }
+            t.value = value;
+            t.line = line;
+            t.character = character;
+            t.from = from;
+            i = t.id;
+            if (i !== '(endline)') {
+                prereg = i &&
+                    (('(,=:[!&|?{};'.indexOf(i.charAt(i.length - 1)) >= 0) ||
+                    i === 'return');
+            }
+            return t;
+        }
+
+// Public lex methods
+
+        return {
+            init: function (source) {
+                if (typeof source === 'string') {
+                    lines = source.
+                        replace(/\r\n/g, '\n').
+                        replace(/\r/g, '\n').
+                        split('\n');
+                } else {
+                    lines = source;
+                }
+                line = 0;
+                nextLine();
+                from = 1;
+            },
+
+            range: function (begin, end) {
+                var c, value = '';
+                from = character;
+                if (s.charAt(0) !== begin) {
+                    errorAt("Expected '{a}' and instead saw '{b}'.",
+                            line, character, begin, s.charAt(0));
+                }
+                for (;;) {
+                    s = s.slice(1);
+                    character += 1;
+                    c = s.charAt(0);
+                    switch (c) {
+                    case '':
+                        errorAt("Missing '{a}'.", line, character, c);
+                        break;
+                    case end:
+                        s = s.slice(1);
+                        character += 1;
+                        return it('(range)', value);
+                    case xquote:
+                    case '\\':
+                        warningAt("Unexpected '{a}'.", line, character, c);
+                    }
+                    value += c;
+                }
+
+            },
+
+// token -- this is called by advance to get the next token.
+
+            token: function () {
+                var b, c, captures, d, depth, high, i, l, low, q, t;
+
+                function match(x) {
+                    var r = x.exec(s), r1;
+                    if (r) {
+                        l = r[0].length;
+                        r1 = r[1];
+                        c = r1.charAt(0);
+                        s = s.substr(l);
+                        from = character + l - r1.length;
+                        character += l;
+                        return r1;
+                    }
+                }
+
+                function string(x) {
+                    var c, j, r = '';
+
+                    if (jsonmode && x !== '"') {
+                        warningAt("Strings must use doublequote.",
+                                line, character);
+                    }
+
+                    if (xquote === x || (xmode === 'scriptstring' && !xquote)) {
+                        return it('(punctuator)', x);
+                    }
+
+                    function esc(n) {
+                        var i = parseInt(s.substr(j + 1, n), 16);
+                        j += n;
+                        if (i >= 32 && i <= 126 &&
+                                i !== 34 && i !== 92 && i !== 39) {
+                            warningAt("Unnecessary escapement.", line, character);
+                        }
+                        character += n;
+                        c = String.fromCharCode(i);
+                    }
+                    j = 0;
+                    for (;;) {
+                        while (j >= s.length) {
+                            j = 0;
+                            if (xmode !== 'html' || !nextLine()) {
+                                errorAt("Unclosed string.", line, from);
+                            }
+                        }
+                        c = s.charAt(j);
+                        if (c === x) {
+                            character += 1;
+                            s = s.substr(j + 1);
+                            return it('(string)', r, x);
+                        }
+                        if (c < ' ') {
+                            if (c === '\n' || c === '\r') {
+                                break;
+                            }
+                            warningAt("Control character in string: {a}.",
+                                    line, character + j, s.slice(0, j));
+                        } else if (c === xquote) {
+                            warningAt("Bad HTML string", line, character + j);
+                        } else if (c === '<') {
+                            if (option.safe && xmode === 'html') {
+                                warningAt("ADsafe string violation.",
+                                        line, character + j);
+                            } else if (s.charAt(j + 1) === '/' && (xmode || option.safe)) {
+                                warningAt("Expected '<\\/' and instead saw '</'.", line, character);
+                            } else if (s.charAt(j + 1) === '!' && (xmode || option.safe)) {
+                                warningAt("Unexpected '<!' in a string.", line, character);
+                            }
+                        } else if (c === '\\') {
+                            if (xmode === 'html') {
+                                if (option.safe) {
+                                    warningAt("ADsafe string violation.",
+                                            line, character + j);
+                                }
+                            } else if (xmode === 'styleproperty') {
+                                j += 1;
+                                character += 1;
+                                c = s.charAt(j);
+                                if (c !== x) {
+                                    warningAt("Escapement in style string.",
+                                            line, character + j);
+                                }
+                            } else {
+                                j += 1;
+                                character += 1;
+                                c = s.charAt(j);
+                                switch (c) {
+                                case xquote:
+                                    warningAt("Bad HTML string", line,
+                                        character + j);
+                                    break;
+                                case '\\':
+                                case '\'':
+                                case '"':
+                                case '/':
+                                    break;
+                                case 'b':
+                                    c = '\b';
+                                    break;
+                                case 'f':
+                                    c = '\f';
+                                    break;
+                                case 'n':
+                                    c = '\n';
+                                    break;
+                                case 'r':
+                                    c = '\r';
+                                    break;
+                                case 't':
+                                    c = '\t';
+                                    break;
+                                case 'u':
+                                    esc(4);
+                                    break;
+                                case 'v':
+                                    c = '\v';
+                                    break;
+                                case 'x':
+                                    if (jsonmode) {
+                                        warningAt("Avoid \\x-.", line, character);
+                                    }
+                                    esc(2);
+                                    break;
+                                default:
+                                    warningAt("Bad escapement.", line, character);
+                                }
+                            }
+                        }
+                        r += c;
+                        character += 1;
+                        j += 1;
+                    }
+                }
+
+                for (;;) {
+                    if (!s) {
+                        return it(nextLine() ? '(endline)' : '(end)', '');
+                    }
+                    while (xmode === 'outer') {
+                        i = s.search(ox);
+                        if (i === 0) {
+                            break;
+                        } else if (i > 0) {
+                            character += 1;
+                            s = s.slice(i);
+                            break;
+                        } else {
+                            if (!nextLine()) {
+                                return it('(end)', '');
+                            }
+                        }
+                    }
+//                     t = match(rx[xmode] || tx);
+//                     if (!t) {
+//                         if (xmode === 'html') {
+//                             return it('(error)', s.charAt(0));
+//                         } else {
+//                             t = '';
+//                             c = '';
+//                             while (s && s < '!') {
+//                                 s = s.substr(1);
+//                             }
+//                             if (s) {
+//                                 errorAt("Unexpected '{a}'.",
+//                                         line, character, s.substr(0, 1));
+//                             }
+//                         }
+                    t = match(rx[xmode] || tx);
+                    if (!t) {
+                        t = '';
+                        c = '';
+                        while (s && s < '!') {
+                            s = s.substr(1);
+                        }
+                        if (s) {
+                            if (xmode === 'html') {
+                                return it('(error)', s.charAt(0));
+                            } else {
+                                errorAt("Unexpected '{a}'.",
+                                        line, character, s.substr(0, 1));
+                            }
+                        }
+                    } else {
+
+    //      identifier
+
+                        if (c.isAlpha() || c === '_' || c === '$') {
+                            return it('(identifier)', t);
+                        }
+
+    //      number
+
+                        if (c.isDigit()) {
+                            if (xmode !== 'style' && !isFinite(Number(t))) {
+                                warningAt("Bad number '{a}'.",
+                                    line, character, t);
+                            }
+                            if (xmode !== 'style' &&
+                                     xmode !== 'styleproperty' &&
+                                     s.substr(0, 1).isAlpha()) {
+                                warningAt("Missing space after '{a}'.",
+                                        line, character, t);
+                            }
+                            if (c === '0') {
+                                d = t.substr(1, 1);
+                                if (d.isDigit()) {
+                                    if (token.id !== '.' && xmode !== 'styleproperty') {
+                                        warningAt("Don't use extra leading zeros '{a}'.",
+                                            line, character, t);
+                                    }
+                                } else if (jsonmode && (d === 'x' || d === 'X')) {
+                                    warningAt("Avoid 0x-. '{a}'.",
+                                            line, character, t);
+                                }
+                            }
+                            if (t.substr(t.length - 1) === '.') {
+                                warningAt(
+        "A trailing decimal point can be confused with a dot '{a}'.",
+                                        line, character, t);
+                            }
+                            return it('(number)', t);
+                        }
+                        switch (t) {
+
+    //      string
+
+                        case '"':
+                        case "'":
+                            return string(t);
+
+    //      // comment
+
+                        case '//':
+                            if (src || (xmode && xmode !== 'script')) {
+                                warningAt("Unexpected comment.", line, character);
+                            } else if (xmode === 'script' && /<\s*\//i.test(s)) {
+                                warningAt("Unexpected <\/ in comment.", line, character);
+                            } else if ((option.safe || xmode === 'script') && ax.test(s)) {
+                                warningAt("Dangerous comment.", line, character);
+                            }
+                            s = '';
+                            token.comment = true;
+                            break;
+
+    //      /* comment
+
+                        case '/*':
+                            if (src || (xmode && xmode !== 'script' && xmode !== 'style' && xmode !== 'styleproperty')) {
+                                warningAt("Unexpected comment.", line, character);
+                            }
+                            if (option.safe && ax.test(s)) {
+                                warningAt("ADsafe comment violation.", line, character);
+                            }
+                            for (;;) {
+                                i = s.search(lx);
+                                if (i >= 0) {
+                                    break;
+                                }
+                                if (!nextLine()) {
+                                    errorAt("Unclosed comment.", line, character);
+                                } else {
+                                    if (option.safe && ax.test(s)) {
+                                        warningAt("ADsafe comment violation.", line, character);
+                                    }
+                                }
+                            }
+                            character += i + 2;
+                            if (s.substr(i, 1) === '/') {
+                                errorAt("Nested comment.", line, character);
+                            }
+                            s = s.substr(i + 2);
+                            token.comment = true;
+                            break;
+
+    //      /*members /*jslint /*global
+
+                        case '/*members':
+                        case '/*member':
+                        case '/*jslint':
+                        case '/*global':
+                        case '*/':
+                            return {
+                                value: t,
+                                type: 'special',
+                                line: line,
+                                character: character,
+                                from: from
+                            };
+
+                        case '':
+                            break;
+    //      /
+                        case '/':
+                            if (token.id === '/=') {
+                                errorAt(
+"A regular expression literal can be confused with '/='.", line, from);
+                            }
+                            if (prereg) {
+                                depth = 0;
+                                captures = 0;
+                                l = 0;
+                                for (;;) {
+                                    b = true;
+                                    c = s.charAt(l);
+                                    l += 1;
+                                    switch (c) {
+                                    case '':
+                                        errorAt("Unclosed regular expression.", line, from);
+                                        return;
+                                    case '/':
+                                        if (depth > 0) {
+                                            warningAt("Unescaped '{a}'.", line, from + l, '/');
+                                        }
+                                        c = s.substr(0, l - 1);
+                                        q = {
+                                            g: true,
+                                            i: true,
+                                            m: true
+                                        };
+                                        while (q[s.charAt(l)] === true) {
+                                            q[s.charAt(l)] = false;
+                                            l += 1;
+                                        }
+                                        character += l;
+                                        s = s.substr(l);
+                                        q = s.charAt(0);
+                                        if (q === '/' || q === '*') {
+                                            errorAt("Confusing regular expression.", line, from);
+                                        }
+                                        return it('(regexp)', c);
+                                    case '\\':
+                                        c = s.charAt(l);
+                                        if (c < ' ') {
+                                            warningAt("Unexpected control character in regular expression.", line, from + l);
+                                        } else if (c === '<') {
+                                            warningAt("Unexpected escaped character '{a}' in regular expression.", line, from + l, c);
+                                        }
+                                        l += 1;
+                                        break;
+                                    case '(':
+                                        depth += 1;
+                                        b = false;
+                                        if (s.charAt(l) === '?') {
+                                            l += 1;
+                                            switch (s.charAt(l)) {
+                                            case ':':
+                                            case '=':
+                                            case '!':
+                                                l += 1;
+                                                break;
+                                            default:
+                                                warningAt("Expected '{a}' and instead saw '{b}'.", line, from + l, ':', s.charAt(l));
+                                            }
+                                        } else {
+                                            captures += 1;
+                                        }
+                                        break;
+                                    case '|':
+                                        b = false;
+                                        break;
+                                    case ')':
+                                        if (depth === 0) {
+                                            warningAt("Unescaped '{a}'.", line, from + l, ')');
+                                        } else {
+                                            depth -= 1;
+                                        }
+                                        break;
+                                    case ' ':
+                                        q = 1;
+                                        while (s.charAt(l) === ' ') {
+                                            l += 1;
+                                            q += 1;
+                                        }
+                                        if (q > 1) {
+                                            warningAt("Spaces are hard to count. Use {{a}}.", line, from + l, q);
+                                        }
+                                        break;
+                                    case '[':
+                                        c = s.charAt(l);
+                                        if (c === '^') {
+                                            l += 1;
+                                            if (option.regexp) {
+                                                warningAt("Insecure '{a}'.", line, from + l, c);
+                                            }
+                                        }
+                                        q = false;
+                                        if (c === ']') {
+                                            warningAt("Empty class.", line, from + l - 1);
+                                            q = true;
+                                        }
+    klass:                              do {
+                                            c = s.charAt(l);
+                                            l += 1;
+                                            switch (c) {
+                                            case '[':
+                                            case '^':
+                                                warningAt("Unescaped '{a}'.", line, from + l, c);
+                                                q = true;
+                                                break;
+                                            case '-':
+                                                if (q) {
+                                                    q = false;
+                                                } else {
+                                                    warningAt("Unescaped '{a}'.", line, from + l, '-');
+                                                    q = true;
+                                                }
+                                                break;
+                                            case ']':
+                                                if (!q) {
+                                                    warningAt("Unescaped '{a}'.", line, from + l - 1, '-');
+                                                }
+                                                break klass;
+                                            case '\\':
+                                                c = s.charAt(l);
+                                                if (c < ' ') {
+                                                    warningAt("Unexpected control character in regular expression.", line, from + l);
+                                                } else if (c === '<') {
+                                                    warningAt("Unexpected escaped character '{a}' in regular expression.", line, from + l, c);
+                                                }
+                                                l += 1;
+                                                q = true;
+                                                break;
+                                            case '/':
+                                                warningAt("Unescaped '{a}'.", line, from + l - 1, '/');
+                                                q = true;
+                                                break;
+                                            case '<':
+                                                if (xmode === 'script') {
+                                                    c = s.charAt(l);
+                                                    if (c === '!' || c === '/') {
+                                                        warningAt("HTML confusion in regular expression '<{a}'.", line, from + l, c);
+                                                    }
+                                                }
+                                                q = true;
+                                                break;
+                                            default:
+                                                q = true;
+                                            }
+                                        } while (c);
+                                        break;
+                                    case '.':
+                                        if (option.regexp) {
+                                            warningAt("Insecure '{a}'.", line, from + l, c);
+                                        }
+                                        break;
+                                    case ']':
+                                    case '?':
+                                    case '{':
+                                    case '}':
+                                    case '+':
+                                    case '*':
+                                        warningAt("Unescaped '{a}'.", line, from + l, c);
+                                        break;
+                                    case '<':
+                                        if (xmode === 'script') {
+                                            c = s.charAt(l);
+                                            if (c === '!' || c === '/') {
+                                                warningAt("HTML confusion in regular expression '<{a}'.", line, from + l, c);
+                                            }
+                                        }
+                                    }
+                                    if (b) {
+                                        switch (s.charAt(l)) {
+                                        case '?':
+                                        case '+':
+                                        case '*':
+                                            l += 1;
+                                            if (s.charAt(l) === '?') {
+                                                l += 1;
+                                            }
+                                            break;
+                                        case '{':
+                                            l += 1;
+                                            c = s.charAt(l);
+                                            if (c < '0' || c > '9') {
+                                                warningAt("Expected a number and instead saw '{a}'.", line, from + l, c);
+                                            }
+                                            l += 1;
+                                            low = +c;
+                                            for (;;) {
+                                                c = s.charAt(l);
+                                                if (c < '0' || c > '9') {
+                                                    break;
+                                                }
+                                                l += 1;
+                                                low = +c + (low * 10);
+                                            }
+                                            high = low;
+                                            if (c === ',') {
+                                                l += 1;
+                                                high = Infinity;
+                                                c = s.charAt(l);
+                                                if (c >= '0' && c <= '9') {
+                                                    l += 1;
+                                                    high = +c;
+                                                    for (;;) {
+                                                        c = s.charAt(l);
+                                                        if (c < '0' || c > '9') {
+                                                            break;
+                                                        }
+                                                        l += 1;
+                                                        high = +c + (high * 10);
+                                                    }
+                                                }
+                                            }
+                                            if (s.charAt(l) !== '}') {
+                                                warningAt("Expected '{a}' and instead saw '{b}'.", line, from + l, '}', c);
+                                            } else {
+                                                l += 1;
+                                            }
+                                            if (s.charAt(l) === '?') {
+                                                l += 1;
+                                            }
+                                            if (low > high) {
+                                                warningAt("'{a}' should not be greater than '{b}'.", line, from + l, low, high);
+                                            }
+                                        }
+                                    }
+                                }
+                                c = s.substr(0, l - 1);
+                                character += l;
+                                s = s.substr(l);
+                                return it('(regexp)', c);
+                            }
+                            return it('(punctuator)', t);
+
+    //      punctuator
+
+                        case '<!--':
+                            l = line;
+                            c = character;
+                            for (;;) {
+                                i = s.indexOf('--');
+                                if (i >= 0) {
+                                    break;
+                                }
+                                i = s.indexOf('<!');
+                                if (i >= 0) {
+                                    errorAt("Nested HTML comment.",
+                                        line, character + i);
+                                }
+                                if (!nextLine()) {
+                                    errorAt("Unclosed HTML comment.", l, c);
+                                }
+                            }
+                            l = s.indexOf('<!');
+                            if (l >= 0 && l < i) {
+                                errorAt("Nested HTML comment.",
+                                    line, character + l);
+                            }
+                            character += i;
+                            if (s[i + 2] !== '>') {
+                                errorAt("Expected -->.", line, character);
+                            }
+                            character += 3;
+                            s = s.slice(i + 3);
+                            break;
+                        case '#':
+                            if (xmode === 'html' || xmode === 'styleproperty') {
+                                for (;;) {
+                                    c = s.charAt(0);
+                                    if ((c < '0' || c > '9') &&
+                                            (c < 'a' || c > 'f') &&
+                                            (c < 'A' || c > 'F')) {
+                                        break;
+                                    }
+                                    character += 1;
+                                    s = s.substr(1);
+                                    t += c;
+                                }
+                                if (t.length !== 4 && t.length !== 7) {
+                                    warningAt("Bad hex color '{a}'.", line,
+                                        from + l, t);
+                                }
+                                return it('(color)', t);
+                            }
+                            return it('(punctuator)', t);
+                        default:
+                            if (xmode === 'outer' && c === '&') {
+                                character += 1;
+                                s = s.substr(1);
+                                for (;;) {
+                                    c = s.charAt(0);
+                                    character += 1;
+                                    s = s.substr(1);
+                                    if (c === ';') {
+                                        break;
+                                    }
+                                    if (!((c >= '0' && c <= '9') ||
+                                            (c >= 'a' && c <= 'z') ||
+                                            c === '#')) {
+                                        errorAt("Bad entity", line, from + l,
+                                        character);
+                                    }
+                                }
+                                break;
+                            }
+                            return it('(punctuator)', t);
+                        }
+                    }
+                }
+            }
+        };
+    }());
+
+
+    function addlabel(t, type) {
+
+        if (option.safe && funct['(global)'] && typeof predefined[t] !== 'boolean') {
+            warning('ADsafe global: ' + t + '.', token);
+        } else if (t === 'hasOwnProperty') {
+            warning("'hasOwnProperty' is a really bad name.");
+        }
+
+// Define t in the current function in the current scope.
+
+        if (is_own(funct, t) && !funct['(global)']) {
+            warning(funct[t] === true ?
+                "'{a}' was used before it was defined." :
+                "'{a}' is already defined.",
+                nexttoken, t);
+        }
+        funct[t] = type;
+        if (funct['(global)']) {
+            global[t] = funct;
+            if (is_own(implied, t)) {
+                warning("'{a}' was used before it was defined.", nexttoken, t);
+                delete implied[t];
+            }
+        } else {
+            scope[t] = funct;
+        }
+    }
+
+
+    function doOption() {
+        var b, obj, filter, o = nexttoken.value, t, v;
+        switch (o) {
+        case '*/':
+            error("Unbegun comment.");
+            break;
+        case '/*members':
+        case '/*member':
+            o = '/*members';
+            if (!membersOnly) {
+                membersOnly = {};
+            }
+            obj = membersOnly;
+            break;
+        case '/*jslint':
+            if (option.safe) {
+                warning("ADsafe restriction.");
+            }
+            obj = option;
+            filter = boolOptions;
+            break;
+        case '/*global':
+            if (option.safe) {
+                warning("ADsafe restriction.");
+            }
+            obj = predefined;
+            break;
+        default:
+        }
+        t = lex.token();
+loop:   for (;;) {
+            for (;;) {
+                if (t.type === 'special' && t.value === '*/') {
+                    break loop;
+                }
+                if (t.id !== '(endline)' && t.id !== ',') {
+                    break;
+                }
+                t = lex.token();
+            }
+            if (t.type !== '(string)' && t.type !== '(identifier)' &&
+                    o !== '/*members') {
+                error("Bad option.", t);
+            }
+            v = lex.token();
+            if (v.id === ':') {
+                v = lex.token();
+                if (obj === membersOnly) {
+                    error("Expected '{a}' and instead saw '{b}'.",
+                            t, '*/', ':');
+                }
+                if (t.value === 'indent' && o === '/*jslint') {
+                    b = +v.value;
+                    if (typeof b !== 'number' || !isFinite(b) || b <= 0 ||
+                            Math.floor(b) !== b) {
+                        error("Expected a small integer and instead saw '{a}'.",
+                                v, v.value);
+                    }
+                    obj.white = true;
+                    obj.indent = b;
+                } else if (t.value === 'maxerr' && o === '/*jslint') {
+                    b = +v.value;
+                    if (typeof b !== 'number' || !isFinite(b) || b <= 0 ||
+                            Math.floor(b) !== b) {
+                        error("Expected a small integer and instead saw '{a}'.",
+                                v, v.value);
+                    }
+                    obj.maxerr = b;
+                } else if (t.value === 'maxlen' && o === '/*jslint') {
+                    b = +v.value;
+                    if (typeof b !== 'number' || !isFinite(b) || b <= 0 ||
+                            Math.floor(b) !== b) {
+                        error("Expected a small integer and instead saw '{a}'.",
+                                v, v.value);
+                    }
+                    obj.maxlen = b;
+                } else if (v.value === 'true') {
+                    obj[t.value] = true;
+                } else if (v.value === 'false') {
+                    obj[t.value] = false;
+                } else {
+                    error("Bad option value.", v);
+                }
+                t = lex.token();
+            } else {
+                if (o === '/*jslint') {
+                    error("Missing option value.", t);
+                }
+                obj[t.value] = false;
+                t = v;
+            }
+        }
+        if (filter) {
+            assume();
+        }
+    }
+
+
+// We need a peek function. If it has an argument, it peeks that much farther
+// ahead. It is used to distinguish
+//     for ( var i in ...
+// from
+//     for ( var i = ...
+
+    function peek(p) {
+        var i = p || 0, j = 0, t;
+
+        while (j <= i) {
+            t = lookahead[j];
+            if (!t) {
+                t = lookahead[j] = lex.token();
+            }
+            j += 1;
+        }
+        return t;
+    }
+
+
+
+// Produce the next token. It looks for programming errors.
+
+    function advance(id, t) {
+        switch (token.id) {
+        case '(number)':
+            if (nexttoken.id === '.') {
+                warning(
+"A dot following a number can be confused with a decimal point.", token);
+            }
+            break;
+        case '-':
+            if (nexttoken.id === '-' || nexttoken.id === '--') {
+                warning("Confusing minusses.");
+            }
+            break;
+        case '+':
+            if (nexttoken.id === '+' || nexttoken.id === '++') {
+                warning("Confusing plusses.");
+            }
+            break;
+        }
+        if (token.type === '(string)' || token.identifier) {
+            anonname = token.value;
+        }
+
+        if (id && nexttoken.id !== id) {
+            if (t) {
+                if (nexttoken.id === '(end)') {
+                    warning("Unmatched '{a}'.", t, t.id);
+                } else {
+                    warning("Expected '{a}' to match '{b}' from line {c} and instead saw '{d}'.",
+                            nexttoken, id, t.id, t.line, nexttoken.value);
+                }
+            } else if (nexttoken.type !== '(identifier)' ||
+                            nexttoken.value !== id) {
+                warning("Expected '{a}' and instead saw '{b}'.",
+                        nexttoken, id, nexttoken.value);
+            }
+        }
+        prevtoken = token;
+        token = nexttoken;
+        for (;;) {
+            nexttoken = lookahead.shift() || lex.token();
+            if (nexttoken.id === '(end)' || nexttoken.id === '(error)') {
+                return;
+            }
+            if (nexttoken.type === 'special') {
+                doOption();
+            } else {
+                if (nexttoken.id !== '(endline)') {
+                    break;
+                }
+            }
+        }
+    }
+
+
+// This is the heart of JSLINT, the Pratt parser. In addition to parsing, it
+// is looking for ad hoc lint patterns. We add to Pratt's model .fud, which is
+// like nud except that it is only used on the first token of a statement.
+// Having .fud makes it much easier to define JavaScript. I retained Pratt's
+// nomenclature.
+
+// .nud     Null denotation
+// .fud     First null denotation
+// .led     Left denotation
+//  lbp     Left binding power
+//  rbp     Right binding power
+
+// They are key to the parsing method called Top Down Operator Precedence.
+
+    function parse(rbp, initial) {
+        var left;
+        if (nexttoken.id === '(end)') {
+            error("Unexpected early end of program.", token);
+        }
+        advance();
+        if (option.safe && typeof predefined[token.value] === 'boolean' &&
+                (nexttoken.id !== '(' && nexttoken.id !== '.')) {
+            warning('ADsafe violation.', token);
+        }
+        if (initial) {
+            anonname = 'anonymous';
+            funct['(verb)'] = token.value;
+        }
+        if (initial === true && token.fud) {
+            left = token.fud();
+        } else {
+            if (token.nud) {
+                left = token.nud();
+            } else {
+                if (nexttoken.type === '(number)' && token.id === '.') {
+                    warning(
+"A leading decimal point can be confused with a dot: '.{a}'.",
+                            token, nexttoken.value);
+                    advance();
+                    return token;
+                } else {
+                    error("Expected an identifier and instead saw '{a}'.",
+                            token, token.id);
+                }
+            }
+            while (rbp < nexttoken.lbp) {
+                advance();
+                if (token.led) {
+                    left = token.led(left);
+                } else {
+                    error("Expected an operator and instead saw '{a}'.",
+                        token, token.id);
+                }
+            }
+        }
+        return left;
+    }
+
+
+// Functions for conformance of style.
+
+    function adjacent(left, right) {
+        left = left || token;
+        right = right || nexttoken;
+        if (option.white || xmode === 'styleproperty' || xmode === 'style') {
+            if (left.character !== right.from && left.line === right.line) {
+                warning("Unexpected space after '{a}'.", right, left.value);
+            }
+        }
+    }
+
+    function nospace(left, right) {
+        left = left || token;
+        right = right || nexttoken;
+        if (option.white && !left.comment) {
+            if (left.line === right.line) {
+                adjacent(left, right);
+            }
+        }
+    }
+
+
+    function nonadjacent(left, right) {
+        if (option.white) {
+            left = left || token;
+            right = right || nexttoken;
+            if (left.line === right.line && left.character === right.from) {
+                warning("Missing space after '{a}'.",
+                        nexttoken, left.value);
+            }
+        }
+    }
+
+    function nobreaknonadjacent(left, right) {
+        left = left || token;
+        right = right || nexttoken;
+        if (!option.laxbreak && left.line !== right.line) {
+            warning("Bad line breaking before '{a}'.", right, right.id);
+        } else if (option.white) {
+            left = left || token;
+            right = right || nexttoken;
+            if (left.character === right.from) {
+                warning("Missing space after '{a}'.",
+                        nexttoken, left.value);
+            }
+        }
+    }
+
+    function indentation(bias) {
+        var i;
+        if (option.white && nexttoken.id !== '(end)') {
+            i = indent + (bias || 0);
+            if (nexttoken.from !== i) {
+                warning("Expected '{a}' to have an indentation at {b} instead at {c}.",
+                        nexttoken, nexttoken.value, i, nexttoken.from);
+            }
+        }
+    }
+
+    function nolinebreak(t) {
+        t = t || token;
+        if (t.line !== nexttoken.line) {
+            warning("Line breaking error '{a}'.", t, t.value);
+        }
+    }
+
+
+    function comma() {
+        if (token.line !== nexttoken.line) {
+            if (!option.laxbreak) {
+                warning("Bad line breaking before '{a}'.", token, nexttoken.id);
+            }
+        } else if (token.character !== nexttoken.from && option.white) {
+            warning("Unexpected space after '{a}'.", nexttoken, token.value);
+        }
+        advance(',');
+        nonadjacent(token, nexttoken);
+    }
+
+
+// Functional constructors for making the symbols that will be inherited by
+// tokens.
+
+    function symbol(s, p) {
+        var x = syntax[s];
+        if (!x || typeof x !== 'object') {
+            syntax[s] = x = {
+                id: s,
+                lbp: p,
+                value: s
+            };
+        }
+        return x;
+    }
+
+
+    function delim(s) {
+        return symbol(s, 0);
+    }
+
+
+    function stmt(s, f) {
+        var x = delim(s);
+        x.identifier = x.reserved = true;
+        x.fud = f;
+        return x;
+    }
+
+
+    function blockstmt(s, f) {
+        var x = stmt(s, f);
+        x.block = true;
+        return x;
+    }
+
+
+    function reserveName(x) {
+        var c = x.id.charAt(0);
+        if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
+            x.identifier = x.reserved = true;
+        }
+        return x;
+    }
+
+
+    function prefix(s, f) {
+        var x = symbol(s, 150);
+        reserveName(x);
+        x.nud = (typeof f === 'function') ? f : function () {
+            this.right = parse(150);
+            this.arity = 'unary';
+            if (this.id === '++' || this.id === '--') {
+                if (option.plusplus) {
+                    warning("Unexpected use of '{a}'.", this, this.id);
+                } else if ((!this.right.identifier || this.right.reserved) &&
+                        this.right.id !== '.' && this.right.id !== '[') {
+                    warning("Bad operand.", this);
+                }
+            }
+            return this;
+        };
+        return x;
+    }
+
+
+    function type(s, f) {
+        var x = delim(s);
+        x.type = s;
+        x.nud = f;
+        return x;
+    }
+
+
+    function reserve(s, f) {
+        var x = type(s, f);
+        x.identifier = x.reserved = true;
+        return x;
+    }
+
+
+    function reservevar(s, v) {
+        return reserve(s, function () {
+            if (this.id === 'this' || this.id === 'arguments') {
+                if (strict_mode && funct['(global)']) {
+                    warning("Strict violation.", this);
+                } else if (option.safe) {
+                    warning("ADsafe violation.", this);
+                }
+            }
+            return this;
+        });
+    }
+
+
+    function infix(s, f, p, w) {
+        var x = symbol(s, p);
+        reserveName(x);
+        x.led = function (left) {
+            if (!w) {
+                nobreaknonadjacent(prevtoken, token);
+                nonadjacent(token, nexttoken);
+            }
+            if (typeof f === 'function') {
+                return f(left, this);
+            } else {
+                this.left = left;
+                this.right = parse(p);
+                return this;
+            }
+        };
+        return x;
+    }
+
+
+    function relation(s, f) {
+        var x = symbol(s, 100);
+        x.led = function (left) {
+            nobreaknonadjacent(prevtoken, token);
+            nonadjacent(token, nexttoken);
+            var right = parse(100);
+            if ((left && left.id === 'NaN') || (right && right.id === 'NaN')) {
+                warning("Use the isNaN function to compare with NaN.", this);
+            } else if (f) {
+                f.apply(this, [left, right]);
+            }
+            if (left.id === '!') {
+                warning("Confusing use of '{a}'.", left, '!');
+            }
+            if (right.id === '!') {
+                warning("Confusing use of '{a}'.", left, '!');
+            }
+            this.left = left;
+            this.right = right;
+            return this;
+        };
+        return x;
+    }
+
+
+    function isPoorRelation(node) {
+        return node &&
+              ((node.type === '(number)' && +node.value === 0) ||
+               (node.type === '(string)' && node.value === ' ') ||
+                node.type === 'true' ||
+                node.type === 'false' ||
+                node.type === 'undefined' ||
+                node.type === 'null');
+    }
+
+
+    function assignop(s, f) {
+        symbol(s, 20).exps = true;
+        return infix(s, function (left, that) {
+            var l;
+            that.left = left;
+            if (predefined[left.value] === false &&
+                    scope[left.value]['(global)'] === true) {
+                warning('Read only.', left);
+            }
+            if (option.safe) {
+                l = left;
+                do {
+                    if (typeof predefined[l.value] === 'boolean') {
+                        warning('ADsafe violation.', l);
+                    }
+                    l = l.left;
+                } while (l);
+            }
+            if (left) {
+                if (left.id === '.' || left.id === '[') {
+                    if (!left.left || left.left.value === 'arguments') {
+                        warning('Bad assignment.', that);
+                    }
+                    that.right = parse(19);
+                    return that;
+                } else if (left.identifier && !left.reserved) {
+                    if (funct[left.value] === 'exception') {
+                        warning("Do not assign to the exception parameter.", left);
+                    }
+                    that.right = parse(19);
+                    return that;
+                }
+                if (left === syntax['function']) {
+                    warning(
+"Expected an identifier in an assignment and instead saw a function invocation.",
+                                token);
+                }
+            }
+            error("Bad assignment.", that);
+        }, 20);
+    }
+
+    function bitwise(s, f, p) {
+        var x = symbol(s, p);
+        reserveName(x);
+        x.led = (typeof f === 'function') ? f : function (left) {
+            if (option.bitwise) {
+                warning("Unexpected use of '{a}'.", this, this.id);
+            }
+            this.left = left;
+            this.right = parse(p);
+            return this;
+        };
+        return x;
+    }
+
+    function bitwiseassignop(s) {
+        symbol(s, 20).exps = true;
+        return infix(s, function (left, that) {
+            if (option.bitwise) {
+                warning("Unexpected use of '{a}'.", that, that.id);
+            }
+            nonadjacent(prevtoken, token);
+            nonadjacent(token, nexttoken);
+            if (left) {
+                if (left.id === '.' || left.id === '[' ||
+                        (left.identifier && !left.reserved)) {
+                    parse(19);
+                    return that;
+                }
+                if (left === syntax['function']) {
+                    warning(
+"Expected an identifier in an assignment, and instead saw a function invocation.",
+                                token);
+                }
+                return that;
+            }
+            error("Bad assignment.", that);
+        }, 20);
+    }
+
+
+    function suffix(s, f) {
+        var x = symbol(s, 150);
+        x.led = function (left) {
+            if (option.plusplus) {
+                warning("Unexpected use of '{a}'.", this, this.id);
+            } else if ((!left.identifier || left.reserved) && left.id !== '.' && left.id !== '[') {
+                warning("Bad operand.", this);
+            }
+            this.left = left;
+            return this;
+        };
+        return x;
+    }
+
+
+    function optionalidentifier() {
+        if (nexttoken.reserved) {
+            warning("Expected an identifier and instead saw '{a}' (a reserved word).",
+                    nexttoken, nexttoken.id);
+        }
+        if (nexttoken.identifier) {
+            advance();
+            return token.value;
+        }
+    }
+
+
+    function identifier() {
+        var i = optionalidentifier();
+        if (i) {
+            return i;
+        }
+        if (token.id === 'function' && nexttoken.id === '(') {
+            warning("Missing name in function statement.");
+        } else {
+            error("Expected an identifier and instead saw '{a}'.",
+                    nexttoken, nexttoken.value);
+        }
+    }
+
+    function reachable(s) {
+        var i = 0, t;
+        if (nexttoken.id !== ';' || noreach) {
+            return;
+        }
+        for (;;) {
+            t = peek(i);
+            if (t.reach) {
+                return;
+            }
+            if (t.id !== '(endline)') {
+                if (t.id === 'function') {
+                    warning(
+"Inner functions should be listed at the top of the outer function.", t);
+                    break;
+                }
+                warning("Unreachable '{a}' after '{b}'.", t, t.value, s);
+                break;
+            }
+            i += 1;
+        }
+    }
+
+
+    function statement(noindent) {
+        var i = indent, r, s = scope, t = nexttoken;
+
+// We don't like the empty statement.
+
+        if (t.id === ';') {
+            warning("Unnecessary semicolon.", t);
+            advance(';');
+            return;
+        }
+
+// Is this a labelled statement?
+
+        if (t.identifier && !t.reserved && peek().id === ':') {
+            advance();
+            advance(':');
+            scope = Object.create(s);
+            addlabel(t.value, 'label');
+            if (!nexttoken.labelled) {
+                warning("Label '{a}' on {b} statement.",
+                        nexttoken, t.value, nexttoken.value);
+            }
+            if (jx.test(t.value + ':')) {
+                warning("Label '{a}' looks like a javascript url.",
+                        t, t.value);
+            }
+            nexttoken.label = t.value;
+            t = nexttoken;
+        }
+
+// Parse the statement.
+
+        if (!noindent) {
+            indentation();
+        }
+        r = parse(0, true);
+
+// Look for the final semicolon.
+
+        if (!t.block) {
+            if (!r || !r.exps) {
+                warning(
+"Expected an assignment or function call and instead saw an expression.",
+                        token);
+            } else if (r.id === '(' && r.left.id === 'new') {
+                warning("Do not use 'new' for side effects.");
+            }
+            if (nexttoken.id !== ';') {
+                warningAt("Missing semicolon.", token.line,
+                        token.from + token.value.length);
+            } else {
+                adjacent(token, nexttoken);
+                advance(';');
+                nonadjacent(token, nexttoken);
+            }
+        }
+
+// Restore the indentation.
+
+        indent = i;
+        scope = s;
+        return r;
+    }
+
+
+    function use_strict() {
+        if (nexttoken.value === 'use strict') {
+            advance();
+            advance(';');
+            strict_mode = true;
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+
+    function statements(begin) {
+        var a = [], f, p;
+        if (begin && !use_strict() && option.strict) {
+            warning('Missing "use strict" statement.', nexttoken);
+        }
+        if (option.adsafe) {
+            switch (begin) {
+            case 'script':
+                if (!adsafe_may) {
+                    if (nexttoken.value !== 'ADSAFE' ||
+                            peek(0).id !== '.' ||
+                            (peek(1).value !== 'id' &&
+                            peek(1).value !== 'go')) {
+                        error('ADsafe violation: Missing ADSAFE.id or ADSAFE.go.',
+                            nexttoken);
+                    }
+                }
+                if (nexttoken.value === 'ADSAFE' &&
+                        peek(0).id === '.' &&
+                        peek(1).value === 'id') {
+                    if (adsafe_may) {
+                        error('ADsafe violation.', nexttoken);
+                    }
+                    advance('ADSAFE');
+                    advance('.');
+                    advance('id');
+                    advance('(');
+                    if (nexttoken.value !== adsafe_id) {
+                        error('ADsafe violation: id does not match.', nexttoken);
+                    }
+                    advance('(string)');
+                    advance(')');
+                    advance(';');
+                    adsafe_may = true;
+                }
+                break;
+            case 'lib':
+                if (nexttoken.value === 'ADSAFE') {
+                    advance('ADSAFE');
+                    advance('.');
+                    advance('lib');
+                    advance('(');
+                    advance('(string)');
+                    comma();
+                    f = parse(0);
+                    if (f.id !== 'function') {
+                        error('The second argument to lib must be a function.', f);
+                    }
+                    p = f.funct['(params)'];
+                    p = p && p.join(', ');
+                    if (p && p !== 'lib') {
+                        error("Expected '{a}' and instead saw '{b}'.",
+                            f, '(lib)', '(' + p + ')');
+                    }
+                    advance(')');
+                    advance(';');
+                    return a;
+                } else {
+                    error("ADsafe lib violation.");
+                }
+            }
+        }
+        while (!nexttoken.reach && nexttoken.id !== '(end)') {
+            if (nexttoken.id === ';') {
+                warning("Unnecessary semicolon.");
+                advance(';');
+            } else {
+                a.push(statement());
+            }
+        }
+        return a;
+    }
+
+
+    function block(f) {
+        var a, b = inblock, old_indent = indent, s = scope, t;
+        inblock = f;
+        scope = Object.create(scope);
+        nonadjacent(token, nexttoken);
+        t = nexttoken;
+        if (nexttoken.id === '{') {
+            advance('{');
+            if (nexttoken.id !== '}' || token.line !== nexttoken.line) {
+                indent += option.indent;
+                while (!f && nexttoken.from > indent) {
+                    indent += option.indent;
+                }
+                if (!f) {
+                    use_strict();
+                }
+                a = statements();
+                indent -= option.indent;
+                indentation();
+            }
+            advance('}', t);
+            indent = old_indent;
+        } else {
+            warning("Expected '{a}' and instead saw '{b}'.",
+                    nexttoken, '{', nexttoken.value);
+            noreach = true;
+            a = [statement()];
+            noreach = false;
+        }
+        funct['(verb)'] = null;
+        scope = s;
+        inblock = b;
+        return a;
+    }
+
+
+// An identity function, used by string and number tokens.
+
+    function idValue() {
+        return this;
+    }
+
+
+    function countMember(m) {
+        if (membersOnly && typeof membersOnly[m] !== 'boolean') {
+            warning("Unexpected /*member '{a}'.", token, m);
+        }
+        if (typeof member[m] === 'number') {
+            member[m] += 1;
+        } else {
+            member[m] = 1;
+        }
+    }
+
+
+    function note_implied(token) {
+        var name = token.value, line = token.line, a = implied[name];
+        if (typeof a === 'function') {
+            a = false;
+        }
+        if (!a) {
+            a = [line];
+            implied[name] = a;
+        } else if (a[a.length - 1] !== line) {
+            a.push(line);
+        }
+    }
+
+// CSS parsing.
+
+
+    function cssName() {
+        if (nexttoken.identifier) {
+            advance();
+            return true;
+        }
+    }
+
+    function cssNumber() {
+        if (nexttoken.id === '-') {
+            advance('-');
+            adjacent();
+            nolinebreak();
+        }
+        if (nexttoken.type === '(number)') {
+            advance('(number)');
+            return true;
+        }
+    }
+
+    function cssString() {
+        if (nexttoken.type === '(string)') {
+            advance();
+            return true;
+        }
+    }
+
+    function cssColor() {
+        var i, number, value;
+        if (nexttoken.identifier) {
+            value = nexttoken.value;
+            if (value === 'rgb' || value === 'rgba') {
+                advance();
+                advance('(');
+                for (i = 0; i < 3; i += 1) {
+                    if (i) {
+                        advance(',');
+                    }
+                    number = nexttoken.value;
+                    if (nexttoken.type !== '(number)' || number < 0) {
+                        warning("Expected a positive number and instead saw '{a}'",
+                            nexttoken, number);
+                        advance();
+                    } else {
+                        advance();
+                        if (nexttoken.id === '%') {
+                            advance('%');
+                            if (number > 100) {
+                                warning("Expected a percentage and instead saw '{a}'",
+                                    token, number);
+                            }
+                        } else {
+                            if (number > 255) {
+                                warning("Expected a small number and instead saw '{a}'",
+                                    token, number);
+                            }
+                        }
+                    }
+                }
+                if (value === 'rgba') {
+                    advance(',');
+                    number = +nexttoken.value;
+                    if (nexttoken.type !== '(number)' || number < 0 || number > 1) {
+                        warning("Expected a number between 0 and 1 and instead saw '{a}'",
+                            nexttoken, number);
+                    }
+                    advance();
+                    if (nexttoken.id === '%') {
+                        warning("Unexpected '%'.");
+                        advance('%');
+                    }
+                }
+                advance(')');
+                return true;
+            } else if (cssColorData[nexttoken.value] === true) {
+                advance();
+                return true;
+            }
+        } else if (nexttoken.type === '(color)') {
+            advance();
+            return true;
+        }
+        return false;
+    }
+
+    function cssLength() {
+        if (nexttoken.id === '-') {
+            advance('-');
+            adjacent();
+            nolinebreak();
+        }
+        if (nexttoken.type === '(number)') {
+            advance();
+            if (nexttoken.type !== '(string)' &&
+                    cssLengthData[nexttoken.value] === true) {
+                adjacent();
+                advance();
+            } else if (+token.value !== 0) {
+                warning("Expected a linear unit and instead saw '{a}'.",
+                    nexttoken, nexttoken.value);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    function cssLineHeight() {
+        if (nexttoken.id === '-') {
+            advance('-');
+            adjacent();
+        }
+        if (nexttoken.type === '(number)') {
+            advance();
+            if (nexttoken.type !== '(string)' &&
+                    cssLengthData[nexttoken.value] === true) {
+                adjacent();
+                advance();
+            }
+            return true;
+        }
+        return false;
+    }
+
+    function cssWidth() {
+        if (nexttoken.identifier) {
+            switch (nexttoken.value) {
+            case 'thin':
+            case 'medium':
+            case 'thick':
+                advance();
+                return true;
+            }
+        } else {
+            return cssLength();
+        }
+    }
+
+    function cssMargin() {
+        if (nexttoken.identifier) {
+            if (nexttoken.value === 'auto') {
+                advance();
+                return true;
+            }
+        } else {
+            return cssLength();
+        }
+    }
+
+    function cssAttr() {
+        if (nexttoken.identifier && nexttoken.value === 'attr') {
+            advance();
+            advance('(');
+            if (!nexttoken.identifier) {
+                warning("Expected a name and instead saw '{a}'.",
+                        nexttoken, nexttoken.value);
+            }
+            advance();
+            advance(')');
+            return true;
+        }
+        return false;
+    }
+
+    function cssCommaList() {
+        while (nexttoken.id !== ';') {
+            if (!cssName() && !cssString()) {
+                warning("Expected a name and instead saw '{a}'.",
+                        nexttoken, nexttoken.value);
+            }
+            if (nexttoken.id !== ',') {
+                return true;
+            }
+            comma();
+        }
+    }
+
+    function cssCounter() {
+        if (nexttoken.identifier && nexttoken.value === 'counter') {
+            advance();
+            advance('(');
+            if (!nexttoken.identifier) {
+            }
+            advance();
+            if (nexttoken.id === ',') {
+                comma();
+                if (nexttoken.type !== '(string)') {
+                    warning("Expected a string and instead saw '{a}'.",
+                        nexttoken, nexttoken.value);
+                }
+                advance();
+            }
+            advance(')');
+            return true;
+        }
+        if (nexttoken.identifier && nexttoken.value === 'counters') {
+            advance();
+            advance('(');
+            if (!nexttoken.identifier) {
+                warning("Expected a name and instead saw '{a}'.",
+                        nexttoken, nexttoken.value);
+            }
+            advance();
+            if (nexttoken.id === ',') {
+                comma();
+                if (nexttoken.type !== '(string)') {
+                    warning("Expected a string and instead saw '{a}'.",
+                        nexttoken, nexttoken.value);
+                }
+                advance();
+            }
+            if (nexttoken.id === ',') {
+                comma();
+                if (nexttoken.type !== '(string)') {
+                    warning("Expected a string and instead saw '{a}'.",
+                        nexttoken, nexttoken.value);
+                }
+                advance();
+            }
+            advance(')');
+            return true;
+        }
+        return false;
+    }
+
+
+    function cssShape() {
+        var i;
+        if (nexttoken.identifier && nexttoken.value === 'rect') {
+            advance();
+            advance('(');
+            for (i = 0; i < 4; i += 1) {
+                if (!cssLength()) {
+                    warning("Expected a number and instead saw '{a}'.",
+                        nexttoken, nexttoken.value);
+                    break;
+                }
+            }
+            advance(')');
+            return true;
+        }
+        return false;
+    }
+
+    function cssUrl() {
+        var c, url;
+        if (nexttoken.identifier && nexttoken.value === 'url') {
+            nexttoken = lex.range('(', ')');
+            url = nexttoken.value;
+            c = url.charAt(0);
+            if (c === '"' || c === '\'') {
+                if (url.slice(-1) !== c) {
+                    warning("Bad url string.");
+                } else {
+                    url = url.slice(1, -1);
+                    if (url.indexOf(c) >= 0) {
+                        warning("Bad url string.");
+                    }
+                }
+            }
+            if (!url) {
+                warning("Missing url.");
+            }
+            advance();
+            if (option.safe && ux.test(url)) {
+                error("ADsafe URL violation.");
+            }
+            urls.push(url);
+            return true;
+        }
+        return false;
+    }
+
+    cssAny = [cssUrl, function () {
+        for (;;) {
+            if (nexttoken.identifier) {
+                switch (nexttoken.value.toLowerCase()) {
+                case 'url':
+                    cssUrl();
+                    break;
+                case 'expression':
+                    warning("Unexpected expression '{a}'.",
+                        nexttoken, nexttoken.value);
+                    advance();
+                    break;
+                default:
+                    advance();
+                }
+            } else {
+                if (nexttoken.id === ';' || nexttoken.id === '!'  ||
+                        nexttoken.id === '(end)' || nexttoken.id === '}') {
+                    return true;
+                }
+                advance();
+            }
+        }
+    }];
+
+    cssBorderStyle = [
+        'none', 'hidden', 'dotted', 'dashed', 'solid', 'double', 'ridge',
+        'inset', 'outset'
+    ];
+
+    cssBreak = [
+        'auto', 'always', 'avoid', 'left', 'right'
+    ];
+
+    cssOverflow = [
+        'auto', 'hidden', 'scroll', 'visible'
+    ];
+
+    cssAttributeData = {
+        background: [
+            true, 'background-attachment', 'background-color',
+            'background-image', 'background-position', 'background-repeat'
+        ],
+        'background-attachment': ['scroll', 'fixed'],
+        'background-color': ['transparent', cssColor],
+        'background-image': ['none', cssUrl],
+        'background-position': [
+            2, [cssLength, 'top', 'bottom', 'left', 'right', 'center']
+        ],
+        'background-repeat': [
+            'repeat', 'repeat-x', 'repeat-y', 'no-repeat'
+        ],
+        'border': [true, 'border-color', 'border-style', 'border-width'],
+        'border-bottom': [
+            true, 'border-bottom-color', 'border-bottom-style',
+            'border-bottom-width'
+        ],
+        'border-bottom-color': cssColor,
+        'border-bottom-style': cssBorderStyle,
+        'border-bottom-width': cssWidth,
+        'border-collapse': ['collapse', 'separate'],
+        'border-color': ['transparent', 4, cssColor],
+        'border-left': [
+            true, 'border-left-color', 'border-left-style', 'border-left-width'
+        ],
+        'border-left-color': cssColor,
+        'border-left-style': cssBorderStyle,
+        'border-left-width': cssWidth,
+        'border-right': [
+            true, 'border-right-color', 'border-right-style',
+            'border-right-width'
+        ],
+        'border-right-color': cssColor,
+        'border-right-style': cssBorderStyle,
+        'border-right-width': cssWidth,
+        'border-spacing': [2, cssLength],
+        'border-style': [4, cssBorderStyle],
+        'border-top': [
+            true, 'border-top-color', 'border-top-style', 'border-top-width'
+        ],
+        'border-top-color': cssColor,
+        'border-top-style': cssBorderStyle,
+        'border-top-width': cssWidth,
+        'border-width': [4, cssWidth],
+        bottom: [cssLength, 'auto'],
+        'caption-side' : ['bottom', 'left', 'right', 'top'],
+        clear: ['both', 'left', 'none', 'right'],
+        clip: [cssShape, 'auto'],
+        color: cssColor,
+        content: [
+            'open-quote', 'close-quote', 'no-open-quote', 'no-close-quote',
+            cssString, cssUrl, cssCounter, cssAttr
+        ],
+        'counter-increment': [
+            cssName, 'none'
+        ],
+        'counter-reset': [
+            cssName, 'none'
+        ],
+        cursor: [
+            cssUrl, 'auto', 'crosshair', 'default', 'e-resize', 'help', 'move',
+            'n-resize', 'ne-resize', 'nw-resize', 'pointer', 's-resize',
+            'se-resize', 'sw-resize', 'w-resize', 'text', 'wait'
+        ],
+        direction: ['ltr', 'rtl'],
+        display: [
+            'block', 'compact', 'inline', 'inline-block', 'inline-table',
+            'list-item', 'marker', 'none', 'run-in', 'table', 'table-caption',
+            'table-cell', 'table-column', 'table-column-group',
+            'table-footer-group', 'table-header-group', 'table-row',
+            'table-row-group'
+        ],
+        'empty-cells': ['show', 'hide'],
+        'float': ['left', 'none', 'right'],
+        font: [
+            'caption', 'icon', 'menu', 'message-box', 'small-caption',
+            'status-bar', true, 'font-size', 'font-style', 'font-weight',
+            'font-family'
+        ],
+        'font-family': cssCommaList,
+        'font-size': [
+            'xx-small', 'x-small', 'small', 'medium', 'large', 'x-large',
+            'xx-large', 'larger', 'smaller', cssLength
+        ],
+        'font-size-adjust': ['none', cssNumber],
+        'font-stretch': [
+            'normal', 'wider', 'narrower', 'ultra-condensed',
+            'extra-condensed', 'condensed', 'semi-condensed',
+            'semi-expanded', 'expanded', 'extra-expanded'
+        ],
+        'font-style': [
+            'normal', 'italic', 'oblique'
+        ],
+        'font-variant': [
+            'normal', 'small-caps'
+        ],
+        'font-weight': [
+            'normal', 'bold', 'bolder', 'lighter', cssNumber
+        ],
+        height: [cssLength, 'auto'],
+        left: [cssLength, 'auto'],
+        'letter-spacing': ['normal', cssLength],
+        'line-height': ['normal', cssLineHeight],
+        'list-style': [
+            true, 'list-style-image', 'list-style-position', 'list-style-type'
+        ],
+        'list-style-image': ['none', cssUrl],
+        'list-style-position': ['inside', 'outside'],
+        'list-style-type': [
+            'circle', 'disc', 'square', 'decimal', 'decimal-leading-zero',
+            'lower-roman', 'upper-roman', 'lower-greek', 'lower-alpha',
+            'lower-latin', 'upper-alpha', 'upper-latin', 'hebrew', 'katakana',
+            'hiragana-iroha', 'katakana-oroha', 'none'
+        ],
+        margin: [4, cssMargin],
+        'margin-bottom': cssMargin,
+        'margin-left': cssMargin,
+        'margin-right': cssMargin,
+        'margin-top': cssMargin,
+        'marker-offset': [cssLength, 'auto'],
+        'max-height': [cssLength, 'none'],
+        'max-width': [cssLength, 'none'],
+        'min-height': cssLength,
+        'min-width': cssLength,
+        opacity: cssNumber,
+        outline: [true, 'outline-color', 'outline-style', 'outline-width'],
+        'outline-color': ['invert', cssColor],
+        'outline-style': [
+            'dashed', 'dotted', 'double', 'groove', 'inset', 'none',
+            'outset', 'ridge', 'solid'
+        ],
+        'outline-width': cssWidth,
+        overflow: cssOverflow,
+        'overflow-x': cssOverflow,
+        'overflow-y': cssOverflow,
+        padding: [4, cssLength],
+        'padding-bottom': cssLength,
+        'padding-left': cssLength,
+        'padding-right': cssLength,
+        'padding-top': cssLength,
+        'page-break-after': cssBreak,
+        'page-break-before': cssBreak,
+        position: ['absolute', 'fixed', 'relative', 'static'],
+        quotes: [8, cssString],
+        right: [cssLength, 'auto'],
+        'table-layout': ['auto', 'fixed'],
+        'text-align': ['center', 'justify', 'left', 'right'],
+        'text-decoration': [
+            'none', 'underline', 'overline', 'line-through', 'blink'
+        ],
+        'text-indent': cssLength,
+        'text-shadow': ['none', 4, [cssColor, cssLength]],
+        'text-transform': ['capitalize', 'uppercase', 'lowercase', 'none'],
+        top: [cssLength, 'auto'],
+        'unicode-bidi': ['normal', 'embed', 'bidi-override'],
+        'vertical-align': [
+            'baseline', 'bottom', 'sub', 'super', 'top', 'text-top', 'middle',
+            'text-bottom', cssLength
+        ],
+        visibility: ['visible', 'hidden', 'collapse'],
+        'white-space': [
+            'normal', 'nowrap', 'pre', 'pre-line', 'pre-wrap', 'inherit'
+        ],
+        width: [cssLength, 'auto'],
+        'word-spacing': ['normal', cssLength],
+        'word-wrap': ['break-word', 'normal'],
+        'z-index': ['auto', cssNumber]
+    };
+
+    function styleAttribute() {
+        var v;
+        while (nexttoken.id === '*' || nexttoken.id === '#' ||
+                nexttoken.value === '_') {
+            if (!option.css) {
+                warning("Unexpected '{a}'.", nexttoken, nexttoken.value);
+            }
+            advance();
+        }
+        if (nexttoken.id === '-') {
+            if (!option.css) {
+                warning("Unexpected '{a}'.", nexttoken, nexttoken.value);
+            }
+            advance('-');
+            if (!nexttoken.identifier) {
+                warning(
+"Expected a non-standard style attribute and instead saw '{a}'.",
+                    nexttoken, nexttoken.value);
+            }
+            advance();
+            return cssAny;
+        } else {
+            if (!nexttoken.identifier) {
+                warning("Excepted a style attribute, and instead saw '{a}'.",
+                    nexttoken, nexttoken.value);
+            } else {
+                if (is_own(cssAttributeData, nexttoken.value)) {
+                    v = cssAttributeData[nexttoken.value];
+                } else {
+                    v = cssAny;
+                    if (!option.css) {
+                        warning("Unrecognized style attribute '{a}'.",
+                                nexttoken, nexttoken.value);
+                    }
+                }
+            }
+            advance();
+            return v;
+        }
+    }
+
+    function styleValue(v) {
+        var i = 0,
+            n,
+            once,
+            match,
+            round,
+            start = 0,
+            vi;
+        switch (typeof v) {
+        case 'function':
+            return v();
+        case 'string':
+            if (nexttoken.identifier && nexttoken.value === v) {
+                advance();
+                return true;
+            }
+            return false;
+        }
+        for (;;) {
+            if (i >= v.length) {
+                return false;
+            }
+            vi = v[i];
+            i += 1;
+            if (vi === true) {
+                break;
+            } else if (typeof vi === 'number') {
+                n = vi;
+                vi = v[i];
+                i += 1;
+            } else {
+                n = 1;
+            }
+            match = false;
+            while (n > 0) {
+                if (styleValue(vi)) {
+                    match = true;
+                    n -= 1;
+                } else {
+                    break;
+                }
+            }
+            if (match) {
+                return true;
+            }
+        }
+        start = i;
+        once = [];
+        for (;;) {
+            round = false;
+            for (i = start; i < v.length; i += 1) {
+                if (!once[i]) {
+                    if (styleValue(cssAttributeData[v[i]])) {
+                        match = true;
+                        round = true;
+                        once[i] = true;
+                        break;
+                    }
+                }
+            }
+            if (!round) {
+                return match;
+            }
+        }
+    }
+
+    function styleChild() {
+        if (nexttoken.id === '(number)') {
+            advance();
+            if (nexttoken.value === 'n' && nexttoken.identifier) {
+                adjacent();
+                advance();
+                if (nexttoken.id === '+') {
+                    adjacent();
+                    advance('+');
+                    adjacent();
+                    advance('(number)');
+                }
+            }
+            return;
+        } else {
+            switch (nexttoken.value) {
+            case 'odd':
+            case 'even':
+                if (nexttoken.identifier) {
+                    advance();
+                    return;
+                }
+            }
+        }
+        warning("Unexpected token '{a}'.", nexttoken, nexttoken.value);
+    }
+
+    function substyle() {
+        var v;
+        for (;;) {
+            if (nexttoken.id === '}' || nexttoken.id === '(end)' ||
+                    xquote && nexttoken.id === xquote) {
+                return;
+            }
+            while (nexttoken.id === ';') {
+                warning("Misplaced ';'.");
+                advance(';');
+            }
+            v = styleAttribute();
+            advance(':');
+            if (nexttoken.identifier && nexttoken.value === 'inherit') {
+                advance();
+            } else {
+                if (!styleValue(v)) {
+                    warning("Unexpected token '{a}'.", nexttoken,
+                        nexttoken.value);
+                    advance();
+                }
+            }
+            if (nexttoken.id === '!') {
+                advance('!');
+                adjacent();
+                if (nexttoken.identifier && nexttoken.value === 'important') {
+                    advance();
+                } else {
+                    warning("Expected '{a}' and instead saw '{b}'.",
+                        nexttoken, 'important', nexttoken.value);
+                }
+            }
+            if (nexttoken.id === '}' || nexttoken.id === xquote) {
+                warning("Missing '{a}'.", nexttoken, ';');
+            } else {
+                advance(';');
+            }
+        }
+    }
+
+    function styleSelector() {
+        if (nexttoken.identifier) {
+            if (!is_own(htmltag, nexttoken.value)) {
+                warning("Expected a tagName, and instead saw {a}.",
+                    nexttoken, nexttoken.value);
+            }
+            advance();
+        } else {
+            switch (nexttoken.id) {
+            case '>':
+            case '+':
+                advance();
+                styleSelector();
+                break;
+            case ':':
+                advance(':');
+                switch (nexttoken.value) {
+                case 'active':
+                case 'after':
+                case 'before':
+                case 'checked':
+                case 'disabled':
+                case 'empty':
+                case 'enabled':
+                case 'first-child':
+                case 'first-letter':
+                case 'first-line':
+                case 'first-of-type':
+                case 'focus':
+                case 'hover':
+                case 'last-of-type':
+                case 'link':
+                case 'only-of-type':
+                case 'root':
+                case 'target':
+                case 'visited':
+                    advance();
+                    break;
+                case 'lang':
+                    advance();
+                    advance('(');
+                    if (!nexttoken.identifier) {
+                        warning("Expected a lang code, and instead saw :{a}.",
+                            nexttoken, nexttoken.value);
+                    }
+                    advance(')');
+                    break;
+                case 'nth-child':
+                case 'nth-last-child':
+                case 'nth-last-of-type':
+                case 'nth-of-type':
+                    advance();
+                    advance('(');
+                    styleChild();
+                    advance(')');
+                    break;
+                case 'not':
+                    advance();
+                    advance('(');
+                    if (nexttoken.id === ':' && peek(0).value === 'not') {
+                        warning("Nested not.");
+                    }
+                    styleSelector();
+                    advance(')');
+                    break;
+                default:
+                    warning("Expected a pseudo, and instead saw :{a}.",
+                        nexttoken, nexttoken.value);
+                }
+                break;
+            case '#':
+                advance('#');
+                if (!nexttoken.identifier) {
+                    warning("Expected an id, and instead saw #{a}.",
+                        nexttoken, nexttoken.value);
+                }
+                advance();
+                break;
+            case '*':
+                advance('*');
+                break;
+            case '.':
+                advance('.');
+                if (!nexttoken.identifier) {
+                    warning("Expected a class, and instead saw #.{a}.",
+                        nexttoken, nexttoken.value);
+                }
+                advance();
+                break;
+            case '[':
+                advance('[');
+                if (!nexttoken.identifier) {
+                    warning("Expected an attribute, and instead saw [{a}].",
+                        nexttoken, nexttoken.value);
+                }
+                advance();
+                if (nexttoken.id === '=' || nexttoken.value === '~=' ||
+                        nexttoken.value === '$=' ||
+                        nexttoken.value === '|=' ||
+                        nexttoken.id === '*=' ||
+                        nexttoken.id === '^=') {
+                    advance();
+                    if (nexttoken.type !== '(string)') {
+                        warning("Expected a string, and instead saw {a}.",
+                            nexttoken, nexttoken.value);
+                    }
+                    advance();
+                }
+                advance(']');
+                break;
+            default:
+                error("Expected a CSS selector, and instead saw {a}.",
+                    nexttoken, nexttoken.value);
+            }
+        }
+    }
+
+    function stylePattern() {
+        var name;
+        if (nexttoken.id === '{') {
+            warning("Expected a style pattern, and instead saw '{a}'.", nexttoken,
+                nexttoken.id);
+        } else if (nexttoken.id === '@') {
+            advance('@');
+            name = nexttoken.value;
+            if (nexttoken.identifier && atrule[name] === true) {
+                advance();
+                return name;
+            }
+            warning("Expected an at-rule, and instead saw @{a}.", nexttoken, name);
+        }
+        for (;;) {
+            styleSelector();
+            if (nexttoken.id === '</' || nexttoken.id === '{' ||
+                    nexttoken.id === '(end)') {
+                return '';
+            }
+            if (nexttoken.id === ',') {
+                comma();
+            }
+        }
+    }
+
+    function styles() {
+        var i;
+        while (nexttoken.id === '@') {
+            i = peek();
+            if (i.identifier && i.value === 'import') {
+                advance('@');
+                advance();
+                if (!cssUrl()) {
+                    warning("Expected '{a}' and instead saw '{b}'.", nexttoken,
+                        'url', nexttoken.value);
+                    advance();
+                }
+                advance(';');
+            } else {
+                break;
+            }
+        }
+        while (nexttoken.id !== '</' && nexttoken.id !== '(end)') {
+            stylePattern();
+            xmode = 'styleproperty';
+            if (nexttoken.id === ';') {
+                advance(';');
+            } else {
+                advance('{');
+                substyle();
+                xmode = 'style';
+                advance('}');
+            }
+        }
+    }
+
+
+// HTML parsing.
+
+    function doBegin(n) {
+        if (n !== 'html' && !option.fragment) {
+            if (n === 'div' && option.adsafe) {
+                error("ADSAFE: Use the fragment option.");
+            } else {
+                error("Expected '{a}' and instead saw '{b}'.",
+                    token, 'html', n);
+            }
+        }
+        if (option.adsafe) {
+            if (n === 'html') {
+                error(
+"Currently, ADsafe does not operate on whole HTML documents. It operates on <div> fragments and .js files.", token);
+            }
+            if (option.fragment) {
+                if (n !== 'div') {
+                    error("ADsafe violation: Wrap the widget in a div.", token);
+                }
+            } else {
+                error("Use the fragment option.", token);
+            }
+        }
+        option.browser = true;
+        assume();
+    }
+
+    function doAttribute(n, a, v) {
+        var u, x;
+        if (a === 'id') {
+            u = typeof v === 'string' ? v.toUpperCase() : '';
+            if (ids[u] === true) {
+                warning("Duplicate id='{a}'.", nexttoken, v);
+            }
+            if (!/^[A-Za-z][A-Za-z0-9._:\-]*$/.test(v)) {
+                warning("Bad id: '{a}'.", nexttoken, v);
+            } else if (option.adsafe) {
+                if (adsafe_id) {
+                    if (v.slice(0, adsafe_id.length) !== adsafe_id) {
+                        warning("ADsafe violation: An id must have a '{a}' prefix",
+                                nexttoken, adsafe_id);
+                    } else if (!/^[A-Z]+_[A-Z]+$/.test(v)) {
+                        warning("ADSAFE violation: bad id.");
+                    }
+                } else {
+                    adsafe_id = v;
+                    if (!/^[A-Z]+_$/.test(v)) {
+                        warning("ADSAFE violation: bad id.");
+                    }
+                }
+            }  
+            x = v.search(dx);
+            if (x >= 0) {
+                warning("Unexpected character '{a}' in {b}.", token, v.charAt(x), a);
+            }
+            ids[u] = true;
+        } else if (a === 'class' || a === 'type' || a === 'name') {
+            x = v.search(qx);
+            if (x >= 0) {
+                warning("Unexpected character '{a}' in {b}.", token, v.charAt(x), a);
+            }
+            ids[u] = true;
+        } else if (a === 'href' || a === 'background' ||
+                a === 'content' || a === 'data' ||
+                a.indexOf('src') >= 0 || a.indexOf('url') >= 0) {
+            if (option.safe && ux.test(v)) {
+                error("ADsafe URL violation.");
+            }
+            urls.push(v);
+        } else if (a === 'for') {
+            if (option.adsafe) {
+                if (adsafe_id) {
+                    if (v.slice(0, adsafe_id.length) !== adsafe_id) {
+                        warning("ADsafe violation: An id must have a '{a}' prefix",
+                                nexttoken, adsafe_id);
+                    } else if (!/^[A-Z]+_[A-Z]+$/.test(v)) {
+                        warning("ADSAFE violation: bad id.");
+                    }
+                } else {
+                    warning("ADSAFE violation: bad id.");
+                }
+            }
+        } else if (a === 'name') {
+            if (option.adsafe && v.indexOf('_') >= 0) {
+                warning("ADsafe name violation.");
+            }
+        }
+    }
+
+    function doTag(n, a) {
+        var i, t = htmltag[n], x;
+        src = false;
+        if (!t) {
+            error("Unrecognized tag '<{a}>'.",
+                    nexttoken,
+                    n === n.toLowerCase() ? n :
+                        n + ' (capitalization error)');
+        }
+        if (stack.length > 0) {
+            if (n === 'html') {
+                error("Too many <html> tags.", token);
+            }
+            x = t.parent;
+            if (x) {
+                if (x.indexOf(' ' + stack[stack.length - 1].name + ' ') < 0) {
+                    error("A '<{a}>' must be within '<{b}>'.",
+                            token, n, x);
+                }
+            } else if (!option.adsafe && !option.fragment) {
+                i = stack.length;
+                do {
+                    if (i <= 0) {
+                        error("A '<{a}>' must be within '<{b}>'.",
+                                token, n, 'body');
+                    }
+                    i -= 1;
+                } while (stack[i].name !== 'body');
+            }
+        }
+        switch (n) {
+        case 'div':
+            if (option.adsafe && stack.length === 1 && !adsafe_id) {
+                warning("ADSAFE violation: missing ID_.");
+            }
+            break;
+        case 'script':
+            xmode = 'script';
+            advance('>');
+            indent = nexttoken.from;
+            if (a.lang) {
+                warning("lang is deprecated.", token);
+            }
+            if (option.adsafe && stack.length !== 1) {
+                warning("ADsafe script placement violation.", token);
+            }
+            if (a.src) {
+                if (option.adsafe && (!adsafe_may || !approved[a.src])) {
+                    warning("ADsafe unapproved script source.", token);
+                }
+                if (a.type) {
+                    warning("type is unnecessary.", token);
+                }
+            } else {
+                if (adsafe_went) {
+                    error("ADsafe script violation.", token);
+                }
+                statements('script');
+            }
+            xmode = 'html';
+            advance('</');
+            if (!nexttoken.identifier && nexttoken.value !== 'script') {
+                warning("Expected '{a}' and instead saw '{b}'.",
+                        nexttoken, 'script', nexttoken.value);
+            }
+            advance();
+            xmode = 'outer';
+            break;
+        case 'style':
+            xmode = 'style';
+            advance('>');
+            styles();
+            xmode = 'html';
+            advance('</');
+            if (!nexttoken.identifier && nexttoken.value !== 'style') {
+                warning("Expected '{a}' and instead saw '{b}'.",
+                        nexttoken, 'style', nexttoken.value);
+            }
+            advance();
+            xmode = 'outer';
+            break;
+        case 'input':
+            switch (a.type) {
+            case 'radio':
+            case 'checkbox':
+            case 'button':
+            case 'reset':
+            case 'submit':
+                break;
+            case 'text':
+            case 'file':
+            case 'password':
+            case 'file':
+            case 'hidden':
+            case 'image':
+                if (option.adsafe && a.autocomplete !== 'off') {
+                    warning("ADsafe autocomplete violation.");
+                }
+                break;
+            default:
+                warning("Bad input type.");
+            }
+            break;
+        case 'applet':
+        case 'body':
+        case 'embed':
+        case 'frame':
+        case 'frameset':
+        case 'head':
+        case 'iframe':
+        case 'noembed':
+        case 'noframes':
+        case 'object':
+        case 'param':
+            if (option.adsafe) {
+                warning("ADsafe violation: Disallowed tag: " + n);
+            }
+            break;
+        }
+    }
+
+
+    function closetag(n) {
+        return '</' + n + '>';
+    }
+
+    function html() {
+        var a, attributes, e, n, q, t, v, w = option.white, wmode;
+        xmode = 'html';
+        xquote = '';
+        stack = null;
+        for (;;) {
+            switch (nexttoken.value) {
+            case '<':
+                xmode = 'html';
+                advance('<');
+                attributes = {};
+                t = nexttoken;
+                if (!t.identifier) {
+                    warning("Bad identifier {a}.", t, t.value);
+                }
+                n = t.value;
+                if (option.cap) {
+                    n = n.toLowerCase();
+                }
+                t.name = n;
+                advance();
+                if (!stack) {
+                    stack = [];
+                    doBegin(n);
+                }
+                v = htmltag[n];
+                if (typeof v !== 'object') {
+                    error("Unrecognized tag '<{a}>'.", t, n);
+                }
+                e = v.empty;
+                t.type = n;
+                for (;;) {
+                    if (nexttoken.id === '/') {
+                        advance('/');
+                        if (nexttoken.id !== '>') {
+                            warning("Expected '{a}' and instead saw '{b}'.",
+                                    nexttoken, '>', nexttoken.value);
+                        }
+                        break;
+                    }
+                    if (nexttoken.id && nexttoken.id.substr(0, 1) === '>') {
+                        break;
+                    }
+                    if (!nexttoken.identifier) {
+                        if (nexttoken.id === '(end)' || nexttoken.id === '(error)') {
+                            error("Missing '>'.", nexttoken);
+                        }
+                        warning("Bad identifier.");
+                    }
+                    option.white = true;
+                    nonadjacent(token, nexttoken);
+                    a = nexttoken.value;
+                    option.white = w;
+                    advance();
+                    if (!option.cap && a !== a.toLowerCase()) {
+                        warning("Attribute '{a}' not all lower case.", nexttoken, a);
+                    }
+                    a = a.toLowerCase();
+                    xquote = '';
+                    if (is_own(attributes, a)) {
+                        warning("Attribute '{a}' repeated.", nexttoken, a);
+                    }
+                    if (a.slice(0, 2) === 'on') {
+                        if (!option.on) {
+                            warning("Avoid HTML event handlers.");
+                        }
+                        xmode = 'scriptstring';
+                        advance('=');
+                        q = nexttoken.id;
+                        if (q !== '"' && q !== "'") {
+                            error("Missing quote.");
+                        }
+                        xquote = q;
+                        wmode = option.white;
+                        option.white = false;
+                        advance(q);
+                        statements('on');
+                        option.white = wmode;
+                        if (nexttoken.id !== q) {
+                            error("Missing close quote on script attribute.");
+                        }
+                        xmode = 'html';
+                        xquote = '';
+                        advance(q);
+                        v = false;
+                    } else if (a === 'style') {
+                        xmode = 'scriptstring';
+                        advance('=');
+                        q = nexttoken.id;
+                        if (q !== '"' && q !== "'") {
+                            error("Missing quote.");
+                        }
+                        xmode = 'styleproperty';
+                        xquote = q;
+                        advance(q);
+                        substyle();
+                        xmode = 'html';
+                        xquote = '';
+                        advance(q);
+                        v = false;
+                    } else {
+                        if (nexttoken.id === '=') {
+                            advance('=');
+                            v = nexttoken.value;
+                            if (!nexttoken.identifier &&
+                                    nexttoken.id !== '"' &&
+                                    nexttoken.id !== '\'' &&
+                                    nexttoken.type !== '(string)' &&
+                                    nexttoken.type !== '(number)' &&
+                                    nexttoken.type !== '(color)') {
+                                warning("Expected an attribute value and instead saw '{a}'.", token, a);
+                            }
+                            advance();
+                        } else {
+                            v = true;
+                        }
+                    }
+                    attributes[a] = v;
+                    doAttribute(n, a, v);
+                }
+                doTag(n, attributes);
+                if (!e) {
+                    stack.push(t);
+                }
+                xmode = 'outer';
+                advance('>');
+                break;
+            case '</':
+                xmode = 'html';
+                advance('</');
+                if (!nexttoken.identifier) {
+                    warning("Bad identifier.");
+                }
+                n = nexttoken.value;
+                if (option.cap) {
+                    n = n.toLowerCase();
+                }
+                advance();
+                if (!stack) {
+                    error("Unexpected '{a}'.", nexttoken, closetag(n));
+                }
+                t = stack.pop();
+                if (!t) {
+                    error("Unexpected '{a}'.", nexttoken, closetag(n));
+                }
+                if (t.name !== n) {
+                    error("Expected '{a}' and instead saw '{b}'.",
+                            nexttoken, closetag(t.name), closetag(n));
+                }
+                if (nexttoken.id !== '>') {
+                    error("Missing '{a}'.", nexttoken, '>');
+                }
+                xmode = 'outer';
+                advance('>');
+                break;
+            case '<!':
+                if (option.safe) {
+                    warning("ADsafe HTML violation.");
+                }
+                xmode = 'html';
+                for (;;) {
+                    advance();
+                    if (nexttoken.id === '>' || nexttoken.id === '(end)') {
+                        break;
+                    }
+                    if (nexttoken.value.indexOf('--') >= 0) {
+                        warning("Unexpected --.");
+                    }
+                    if (nexttoken.value.indexOf('<') >= 0) {
+                        warning("Unexpected <.");
+                    }
+                    if (nexttoken.value.indexOf('>') >= 0) {
+                        warning("Unexpected >.");
+                    }
+                }
+                xmode = 'outer';
+                advance('>');
+                break;
+            case '(end)':
+                return;
+            default:
+                if (nexttoken.id === '(end)') {
+                    error("Missing '{a}'.", nexttoken,
+                            '</' + stack[stack.length - 1].value + '>');
+                } else {
+                    advance();
+                }
+            }
+            if (stack && stack.length === 0 && (option.adsafe ||
+                    !option.fragment || nexttoken.id === '(end)')) {
+                break;
+            }
+        }
+        if (nexttoken.id !== '(end)') {
+            error("Unexpected material after the end.");
+        }
+    }
+
+
+// Build the syntax table by declaring the syntactic elements of the language.
+
+    type('(number)', idValue);
+    type('(string)', idValue);
+
+    syntax['(identifier)'] = {
+        type: '(identifier)',
+        lbp: 0,
+        identifier: true,
+        nud: function () {
+            var v = this.value,
+                s = scope[v],
+                f;
+            if (typeof s === 'function') {
+                s = undefined;
+            } else if (typeof s === 'boolean') {
+                f = funct;
+                funct = functions[0];
+                addlabel(v, 'var');
+                s = funct;
+                funct = f;
+            }
+
+// The name is in scope and defined in the current function.
+
+            if (funct === s) {
+
+//      Change 'unused' to 'var', and reject labels.
+
+                switch (funct[v]) {
+                case 'unused':
+                    funct[v] = 'var';
+                    break;
+                case 'label':
+                    warning("'{a}' is a statement label.", token, v);
+                    break;
+                }
+
+// The name is not defined in the function.  If we are in the global scope,
+// then we have an undefined variable.
+
+            } else if (funct['(global)']) {
+                if (option.undef && predefined[v] !== 'boolean') {
+                    warning("'{a}' is not defined.", token, v);
+                }
+                note_implied(token);
+
+// If the name is already defined in the current
+// function, but not as outer, then there is a scope error.
+
+            } else {
+                switch (funct[v]) {
+                case 'closure':
+                case 'function':
+                case 'var':
+                case 'unused':
+                    warning("'{a}' used out of scope.", token, v);
+                    break;
+                case 'label':
+                    warning("'{a}' is a statement label.", token, v);
+                    break;
+                case 'outer':
+                case 'global':
+                    break;
+                default:
+
+// If the name is defined in an outer function, make an outer entry, and if
+// it was unused, make it var.
+
+                    if (s === true) {
+                        funct[v] = true;
+                    } else if (s === null) {
+                        warning("'{a}' is not allowed.", token, v);
+                        note_implied(token);
+                    } else if (typeof s !== 'object') {
+                        if (option.undef) {
+                            warning("'{a}' is not defined.", token, v);
+                        } else {
+                            funct[v] = true;
+                        }
+                        note_implied(token);
+                    } else {
+                        switch (s[v]) {
+                        case 'function':
+                        case 'var':
+                        case 'unused':
+                            s[v] = 'closure';
+                            funct[v] = s['(global)'] ? 'global' : 'outer';
+                            break;
+                        case 'closure':
+                        case 'parameter':
+                            funct[v] = s['(global)'] ? 'global' : 'outer';
+                            break;
+                        case 'label':
+                            warning("'{a}' is a statement label.", token, v);
+                        }
+                    }
+                }
+            }
+            return this;
+        },
+        led: function () {
+            error("Expected an operator and instead saw '{a}'.",
+                    nexttoken, nexttoken.value);
+        }
+    };
+
+    type('(regexp)', function () {
+        return this;
+    });
+
+    delim('(endline)');
+    delim('(begin)');
+    delim('(end)').reach = true;
+    delim('</').reach = true;
+    delim('<!');
+    delim('<!--');
+    delim('-->');
+    delim('(error)').reach = true;
+    delim('}').reach = true;
+    delim(')');
+    delim(']');
+    delim('"').reach = true;
+    delim("'").reach = true;
+    delim(';');
+    delim(':').reach = true;
+    delim(',');
+    delim('#');
+    delim('@');
+    reserve('else');
+    reserve('case').reach = true;
+    reserve('catch');
+    reserve('default').reach = true;
+    reserve('finally');
+    reservevar('arguments');
+    reservevar('eval');
+    reservevar('false');
+    reservevar('Infinity');
+    reservevar('NaN');
+    reservevar('null');
+    reservevar('this');
+    reservevar('true');
+    reservevar('undefined');
+    assignop('=', 'assign', 20);
+    assignop('+=', 'assignadd', 20);
+    assignop('-=', 'assignsub', 20);
+    assignop('*=', 'assignmult', 20);
+    assignop('/=', 'assigndiv', 20).nud = function () {
+        error("A regular expression literal can be confused with '/='.");
+    };
+    assignop('%=', 'assignmod', 20);
+    bitwiseassignop('&=', 'assignbitand', 20);
+    bitwiseassignop('|=', 'assignbitor', 20);
+    bitwiseassignop('^=', 'assignbitxor', 20);
+    bitwiseassignop('<<=', 'assignshiftleft', 20);
+    bitwiseassignop('>>=', 'assignshiftright', 20);
+    bitwiseassignop('>>>=', 'assignshiftrightunsigned', 20);
+    infix('?', function (left, that) {
+        that.left = left;
+        that.right = parse(10);
+        advance(':');
+        that['else'] = parse(10);
+        return that;
+    }, 30);
+
+    infix('||', 'or', 40);
+    infix('&&', 'and', 50);
+    bitwise('|', 'bitor', 70);
+    bitwise('^', 'bitxor', 80);
+    bitwise('&', 'bitand', 90);
+    relation('==', function (left, right) {
+        if (option.eqeqeq) {
+            warning("Expected '{a}' and instead saw '{b}'.",
+                    this, '===', '==');
+        } else if (isPoorRelation(left)) {
+            warning("Use '{a}' to compare with '{b}'.",
+                this, '===', left.value);
+        } else if (isPoorRelation(right)) {
+            warning("Use '{a}' to compare with '{b}'.",
+                this, '===', right.value);
+        }
+        return this;
+    });
+    relation('===');
+    relation('!=', function (left, right) {
+        if (option.eqeqeq) {
+            warning("Expected '{a}' and instead saw '{b}'.",
+                    this, '!==', '!=');
+        } else if (isPoorRelation(left)) {
+            warning("Use '{a}' to compare with '{b}'.",
+                    this, '!==', left.value);
+        } else if (isPoorRelation(right)) {
+            warning("Use '{a}' to compare with '{b}'.",
+                    this, '!==', right.value);
+        }
+        return this;
+    });
+    relation('!==');
+    relation('<');
+    relation('>');
+    relation('<=');
+    relation('>=');
+    bitwise('<<', 'shiftleft', 120);
+    bitwise('>>', 'shiftright', 120);
+    bitwise('>>>', 'shiftrightunsigned', 120);
+    infix('in', 'in', 120);
+    infix('instanceof', 'instanceof', 120);
+    infix('+', function (left, that) {
+        var right = parse(130);
+        if (left && right && left.id === '(string)' && right.id === '(string)') {
+            left.value += right.value;
+            left.character = right.character;
+            if (jx.test(left.value)) {
+                warning("JavaScript URL.", left);
+            }
+            return left;
+        }
+        that.left = left;
+        that.right = right;
+        return that;
+    }, 130);
+    prefix('+', 'num');
+    infix('-', 'sub', 130);
+    prefix('-', 'neg');
+    infix('*', 'mult', 140);
+    infix('/', 'div', 140);
+    infix('%', 'mod', 140);
+
+    suffix('++', 'postinc');
+    prefix('++', 'preinc');
+    syntax['++'].exps = true;
+
+    suffix('--', 'postdec');
+    prefix('--', 'predec');
+    syntax['--'].exps = true;
+    prefix('delete', function () {
+        var p = parse(0);
+        if (!p || (p.id !== '.' && p.id !== '[')) {
+            warning("Expected '{a}' and instead saw '{b}'.",
+                    nexttoken, '.', nexttoken.value);
+        }
+        this.first = p;
+        return this;
+    }).exps = true;
+
+
+    prefix('~', function () {
+        if (option.bitwise) {
+            warning("Unexpected '{a}'.", this, '~');
+        }
+        parse(150);
+        return this;
+    });
+    prefix('!', function () {
+        this.right = parse(150);
+        this.arity = 'unary';
+        if (bang[this.right.id] === true) {
+            warning("Confusing use of '{a}'.", this, '!');
+        }
+        return this;
+    });
+    prefix('typeof', 'typeof');
+    prefix('new', function () {
+        var c = parse(155), i;
+        if (c && c.id !== 'function') {
+            if (c.identifier) {
+                c['new'] = true;
+                switch (c.value) {
+                case 'Object':
+                    warning("Use the object literal notation {}.", token);
+                    break;
+                case 'Array':
+                    if (nexttoken.id !== '(') {
+                        warning("Use the array literal notation [].", token);
+                    } else {
+                        advance('(');
+                        if (nexttoken.id === ')') {
+                            warning("Use the array literal notation [].", token);
+                        } else {
+                            i = parse(0);
+                            c.dimension = i;
+                            if ((i.id === '(number)' && /[.+\-Ee]/.test(i.value)) ||
+                                    (i.id === '-' && !i.right) ||
+                                    i.id === '(string)' || i.id === '[' ||
+                                    i.id === '{' || i.id === 'true' ||
+                                    i.id === 'false' ||
+                                    i.id === 'null' || i.id === 'undefined' ||
+                                    i.id === 'Infinity') {
+                                warning("Use the array literal notation [].", token);
+                            }
+                            if (nexttoken.id !== ')') {
+                                error("Use the array literal notation [].", token);
+                            }
+                        }
+                        advance(')');
+                    }
+                    this.first = c;
+                    return this;
+                case 'Number':
+                case 'String':
+                case 'Boolean':
+                case 'Math':
+                case 'JSON':
+                    warning("Do not use {a} as a constructor.", token, c.value);
+                    break;
+                case 'Function':
+                    if (!option.evil) {
+                        warning("The Function constructor is eval.");
+                    }
+                    break;
+                case 'Date':
+                case 'RegExp':
+                    break;
+                default:
+                    if (c.id !== 'function') {
+                        i = c.value.substr(0, 1);
+                        if (option.newcap && (i < 'A' || i > 'Z')) {
+                            warning(
+                    "A constructor name should start with an uppercase letter.",
+                                token);
+                        }
+                    }
+                }
+            } else {
+                if (c.id !== '.' && c.id !== '[' && c.id !== '(') {
+                    warning("Bad constructor.", token);
+                }
+            }
+        } else {
+            warning("Weird construction. Delete 'new'.", this);
+        }
+        adjacent(token, nexttoken);
+        if (nexttoken.id !== '(') {
+            warning("Missing '()' invoking a constructor.");
+        }
+        this.first = c;
+        return this;
+    });
+    syntax['new'].exps = true;
+
+    infix('.', function (left, that) {
+        adjacent(prevtoken, token);
+        var m = identifier();
+        if (typeof m === 'string') {
+            countMember(m);
+        }
+        that.left = left;
+        that.right = m;
+        if (!option.evil && left && left.value === 'document' &&
+                (m === 'write' || m === 'writeln')) {
+            warning("document.write can be a form of eval.", left);
+        } else if (option.adsafe) {
+            if (left && left.value === 'ADSAFE') {
+                if (m === 'id' || m === 'lib') {
+                    warning("ADsafe violation.", that);
+                } else if (m === 'go') {
+                    if (xmode !== 'script') {
+                        warning("ADsafe violation.", that);
+                    } else if (adsafe_went || nexttoken.id !== '(' ||
+                            peek(0).id !== '(string)' ||
+                            peek(0).value !== adsafe_id ||
+                            peek(1).id !== ',') {
+                        error("ADsafe violation: go.", that);
+                    }
+                    adsafe_went = true;
+                    adsafe_may = false;
+                }
+            }
+        }
+        if (!option.evil && (m === 'eval' || m === 'execScript')) {
+            warning('eval is evil.');
+        } else if (option.safe) {
+            for (;;) {
+                if (banned[m] === true) {
+                    warning("ADsafe restricted word '{a}'.", token, m);
+                }
+                if (typeof predefined[left.value] !== 'boolean' ||
+                        nexttoken.id === '(') {
+                    break;
+                }
+                if (standard_member[m] === true) {
+                    if (nexttoken.id === '.') {
+                        warning("ADsafe violation.", that);
+                    }
+                    break;
+                }
+                if (nexttoken.id !== '.') {
+                    warning("ADsafe violation.", that);
+                    break;
+                }
+                advance('.');
+                token.left = that;
+                token.right = m;
+                that = token;
+                m = identifier();
+                if (typeof m === 'string') {
+                    countMember(m);
+                }
+            }
+        }
+        return that;
+    }, 160, true);
+
+    infix('(', function (left, that) {
+        adjacent(prevtoken, token);
+        nospace();
+        var n = 0,
+            p = [];
+        if (left) {
+            if (left.type === '(identifier)') {
+                if (left.value.match(/^[A-Z]([A-Z0-9_$]*[a-z][A-Za-z0-9_$]*)?$/)) {
+                    if (left.value !== 'Number' && left.value !== 'String' &&
+                            left.value !== 'Boolean' &&
+                            left.value !== 'Date') {
+                        if (left.value === 'Math') {
+                            warning("Math is not a function.", left);
+                        } else if (option.newcap) {
+                            warning(
+"Missing 'new' prefix when invoking a constructor.", left);
+                        }
+                    }
+                }
+            } else if (left.id === '.') {
+                if (option.safe && left.left.value === 'Math' &&
+                        left.right === 'random') {
+                    warning("ADsafe violation.", left);
+                }
+            }
+        }
+        if (nexttoken.id !== ')') {
+            for (;;) {
+                p[p.length] = parse(10);
+                n += 1;
+                if (nexttoken.id !== ',') {
+                    break;
+                }
+                comma();
+            }
+        }
+        advance(')');
+        if (option.immed && left.id === 'function' && nexttoken.id !== ')') {
+            warning("Wrap the entire immediate function invocation in parens.",
+                that);
+        }
+        nospace(prevtoken, token);
+        if (typeof left === 'object') {
+            if (left.value === 'parseInt' && n === 1) {
+                warning("Missing radix parameter.", left);
+            }
+            if (!option.evil) {
+                if (left.value === 'eval' || left.value === 'Function' ||
+                        left.value === 'execScript') {
+                    warning("eval is evil.", left);
+                } else if (p[0] && p[0].id === '(string)' &&
+                       (left.value === 'setTimeout' ||
+                        left.value === 'setInterval')) {
+                    warning(
+    "Implied eval is evil. Pass a function instead of a string.", left);
+                }
+            }
+            if (!left.identifier && left.id !== '.' && left.id !== '[' &&
+                    left.id !== '(' && left.id !== '&&' && left.id !== '||' &&
+                    left.id !== '?') {
+                warning("Bad invocation.", left);
+            }
+        }
+        that.left = left;
+        return that;
+    }, 155, true).exps = true;
+
+    prefix('(', function () {
+        nospace();
+        var v = parse(0);
+        advance(')', this);
+        nospace(prevtoken, token);
+        if (option.immed && v.id === 'function') {
+            if (nexttoken.id === '(') {
+                warning(
+"Move the invocation into the parens that contain the function.", nexttoken);
+            } else {
+                warning(
+"Do not wrap function literals in parens unless they are to be immediately invoked.",
+                        this);
+            }
+        }
+        return v;
+    });
+
+    infix('[', function (left, that) {
+        nospace();
+        var e = parse(0), s;
+        if (e && e.type === '(string)') {
+            if (option.safe && banned[e.value] === true) {
+                warning("ADsafe restricted word '{a}'.", that, e.value);
+            } else if (!option.evil &&
+                    (e.value === 'eval' || e.value === 'execScript')) {
+                warning("eval is evil.", that);
+            } else if (option.safe &&
+                    (e.value.charAt(0) === '_' || e.value.charAt(0) === '-')) {
+                warning("ADsafe restricted subscript '{a}'.", that, e.value);
+            }
+            countMember(e.value);
+            if (!option.sub && ix.test(e.value)) {
+                s = syntax[e.value];
+                if (!s || !s.reserved) {
+                    warning("['{a}'] is better written in dot notation.",
+                            e, e.value);
+                }
+            }
+        } else if (!e || e.type !== '(number)' || e.value < 0) {
+            if (option.safe) {
+                warning('ADsafe subscripting.');
+            }
+        }
+        advance(']', that);
+        nospace(prevtoken, token);
+        that.left = left;
+        that.right = e;
+        return that;
+    }, 160, true);
+
+    prefix('[', function () {
+        var b = token.line !== nexttoken.line;
+        this.first = [];
+        if (b) {
+            indent += option.indent;
+            if (nexttoken.from === indent + option.indent) {
+                indent += option.indent;
+            }
+        }
+        while (nexttoken.id !== '(end)') {
+            while (nexttoken.id === ',') {
+                warning("Extra comma.");
+                advance(',');
+            }
+            if (nexttoken.id === ']') {
+                break;
+            }
+            if (b && token.line !== nexttoken.line) {
+                indentation();
+            }
+            this.first.push(parse(10));
+            if (nexttoken.id === ',') {
+                comma();
+                if (nexttoken.id === ']') {
+                    warning("Extra comma.", token);
+                    break;
+                }
+            } else {
+                break;
+            }
+        }
+        if (b) {
+            indent -= option.indent;
+            indentation();
+        }
+        advance(']', this);
+        return this;
+    }, 160);
+
+    (function (x) {
+        x.nud = function () {
+            var b, i, s, seen = {};
+            b = token.line !== nexttoken.line;
+            if (b) {
+                indent += option.indent;
+                if (nexttoken.from === indent + option.indent) {
+                    indent += option.indent;
+                }
+            }
+            for (;;) {
+                if (nexttoken.id === '}') {
+                    break;
+                }
+                if (b) {
+                    indentation();
+                }
+                i = optionalidentifier(true);
+                if (!i) {
+                    if (nexttoken.id === '(string)') {
+                        i = nexttoken.value;
+                        if (ix.test(i)) {
+                            s = syntax[i];
+                        }
+                        advance();
+                    } else if (nexttoken.id === '(number)') {
+                        i = nexttoken.value.toString();
+                        advance();
+                    } else {
+                        error("Expected '{a}' and instead saw '{b}'.",
+                                nexttoken, '}', nexttoken.value);
+                    }
+                }
+                if (seen[i] === true) {
+                    warning("Duplicate member '{a}'.", nexttoken, i);
+                }
+                seen[i] = true;
+                countMember(i);
+                advance(':');
+                nonadjacent(token, nexttoken);
+                parse(10);
+                if (nexttoken.id === ',') {
+                    comma();
+                    if (nexttoken.id === ',' || nexttoken.id === '}') {
+                        warning("Extra comma.", token);
+                    }
+                } else {
+                    break;
+                }
+            }
+            if (b) {
+                indent -= option.indent;
+                indentation();
+            }
+            advance('}', this);
+            return this;
+        };
+        x.fud = function () {
+            error("Expected to see a statement and instead saw a block.", token);
+        };
+    }(delim('{')));
+
+
+    function varstatement(prefix) {
+
+// JavaScript does not have block scope. It only has function scope. So,
+// declaring a variable in a block can have unexpected consequences.
+
+        var id, name, value;
+
+        if (funct['(onevar)'] && option.onevar) {
+            warning("Too many var statements.");
+        } else if (!funct['(global)']) {
+            funct['(onevar)'] = true;
+        }
+        this.first = [];
+        for (;;) {
+            nonadjacent(token, nexttoken);
+            id = identifier();
+            if (funct['(global)'] && predefined[id] === false) {
+                warning("Redefinition of '{a}'.", token, id);
+            }
+            addlabel(id, 'unused');
+            if (prefix) {
+                break;
+            }
+            name = token;
+            this.first.push(token);
+            if (nexttoken.id === '=') {
+                nonadjacent(token, nexttoken);
+                advance('=');
+                nonadjacent(token, nexttoken);
+                if (nexttoken.id === 'undefined') {
+                    warning("It is not necessary to initialize '{a}' to 'undefined'.", token, id);
+                }
+                if (peek(0).id === '=' && nexttoken.identifier) {
+                    error("Variable {a} was not declared correctly.",
+                            nexttoken, nexttoken.value);
+                }
+                value = parse(0);
+                name.first = value;
+            }
+            if (nexttoken.id !== ',') {
+                break;
+            }
+            comma();
+        }
+        return this;
+    }
+
+
+    stmt('var', varstatement).exps = true;
+
+
+    function functionparams() {
+        var i, t = nexttoken, p = [];
+        advance('(');
+        nospace();
+        if (nexttoken.id === ')') {
+            advance(')');
+            nospace(prevtoken, token);
+            return;
+        }
+        for (;;) {
+            i = identifier();
+            p.push(i);
+            addlabel(i, 'parameter');
+            if (nexttoken.id === ',') {
+                comma();
+            } else {
+                advance(')', t);
+                nospace(prevtoken, token);
+                return p;
+            }
+        }
+    }
+
+    function doFunction(i) {
+        var s = scope;
+        scope = Object.create(s);
+        funct = {
+            '(name)'    : i || '"' + anonname + '"',
+            '(line)'    : nexttoken.line,
+            '(context)' : funct,
+            '(breakage)': 0,
+            '(loopage)' : 0,
+            '(scope)'   : scope
+        };
+        token.funct = funct;
+        functions.push(funct);
+        if (i) {
+            addlabel(i, 'function');
+        }
+        funct['(params)'] = functionparams();
+
+        block(false);
+        scope = s;
+        funct['(last)'] = token.line;
+        funct = funct['(context)'];
+    }
+
+
+    blockstmt('function', function () {
+        if (inblock) {
+            warning(
+"Function statements cannot be placed in blocks. Use a function expression or move the statement to the top of the outer function.", token);
+
+        }
+        var i = identifier();
+        adjacent(token, nexttoken);
+        addlabel(i, 'unused');
+        doFunction(i);
+        if (nexttoken.id === '(' && nexttoken.line === token.line) {
+            error(
+"Function statements are not invocable. Wrap the whole function invocation in parens.");
+        }
+        return this;
+    });
+
+    prefix('function', function () {
+        var i = optionalidentifier();
+        if (i) {
+            adjacent(token, nexttoken);
+        } else {
+            nonadjacent(token, nexttoken);
+        }
+        doFunction(i);
+        if (funct['(loopage)']) {
+            warning("Don't make functions within a loop.");
+        }
+        return this;
+    });
+
+    blockstmt('if', function () {
+        var t = nexttoken;
+        advance('(');
+        nonadjacent(this, t);
+        nospace();
+        parse(20);
+        if (nexttoken.id === '=') {
+            warning("Expected a conditional expression and instead saw an assignment.");
+            advance('=');
+            parse(20);
+        }
+        advance(')', t);
+        nospace(prevtoken, token);
+        block(true);
+        if (nexttoken.id === 'else') {
+            nonadjacent(token, nexttoken);
+            advance('else');
+            if (nexttoken.id === 'if' || nexttoken.id === 'switch') {
+                statement(true);
+            } else {
+                block(true);
+            }
+        }
+        return this;
+    });
+
+    blockstmt('try', function () {
+        var b, e, s;
+        if (option.adsafe) {
+            warning("ADsafe try violation.", this);
+        }
+        block(false);
+        if (nexttoken.id === 'catch') {
+            advance('catch');
+            nonadjacent(token, nexttoken);
+            advance('(');
+            s = scope;
+            scope = Object.create(s);
+            e = nexttoken.value;
+            if (nexttoken.type !== '(identifier)') {
+                warning("Expected an identifier and instead saw '{a}'.",
+                    nexttoken, e);
+            } else {
+                addlabel(e, 'exception');
+            }
+            advance();
+            advance(')');
+            block(false);
+            b = true;
+            scope = s;
+        }
+        if (nexttoken.id === 'finally') {
+            advance('finally');
+            block(false);
+            return;
+        } else if (!b) {
+            error("Expected '{a}' and instead saw '{b}'.",
+                    nexttoken, 'catch', nexttoken.value);
+        }
+        return this;
+    });
+
+    blockstmt('while', function () {
+        var t = nexttoken;
+        funct['(breakage)'] += 1;
+        funct['(loopage)'] += 1;
+        advance('(');
+        nonadjacent(this, t);
+        nospace();
+        parse(20);
+        if (nexttoken.id === '=') {
+            warning("Expected a conditional expression and instead saw an assignment.");
+            advance('=');
+            parse(20);
+        }
+        advance(')', t);
+        nospace(prevtoken, token);
+        block(true);
+        funct['(breakage)'] -= 1;
+        funct['(loopage)'] -= 1;
+        return this;
+    }).labelled = true;
+
+    reserve('with');
+
+    blockstmt('switch', function () {
+        var t = nexttoken,
+            g = false;
+        funct['(breakage)'] += 1;
+        advance('(');
+        nonadjacent(this, t);
+        nospace();
+        this.condition = parse(20);
+        advance(')', t);
+        nospace(prevtoken, token);
+        nonadjacent(token, nexttoken);
+        t = nexttoken;
+        advance('{');
+        nonadjacent(token, nexttoken);
+        indent += option.indent;
+        this.cases = [];
+        for (;;) {
+            switch (nexttoken.id) {
+            case 'case':
+                switch (funct['(verb)']) {
+                case 'break':
+                case 'case':
+                case 'continue':
+                case 'return':
+                case 'switch':
+                case 'throw':
+                    break;
+                default:
+                    warning(
+                        "Expected a 'break' statement before 'case'.",
+                        token);
+                }
+                indentation(-option.indent);
+                advance('case');
+                this.cases.push(parse(20));
+                g = true;
+                advance(':');
+                funct['(verb)'] = 'case';
+                break;
+            case 'default':
+                switch (funct['(verb)']) {
+                case 'break':
+                case 'continue':
+                case 'return':
+                case 'throw':
+                    break;
+                default:
+                    warning(
+                        "Expected a 'break' statement before 'default'.",
+                        token);
+                }
+                indentation(-option.indent);
+                advance('default');
+                g = true;
+                advance(':');
+                break;
+            case '}':
+                indent -= option.indent;
+                indentation();
+                advance('}', t);
+                if (this.cases.length === 1 || this.condition.id === 'true' ||
+                        this.condition.id === 'false') {
+                    warning("This 'switch' should be an 'if'.", this);
+                }
+                funct['(breakage)'] -= 1;
+                funct['(verb)'] = undefined;
+                return;
+            case '(end)':
+                error("Missing '{a}'.", nexttoken, '}');
+                return;
+            default:
+                if (g) {
+                    switch (token.id) {
+                    case ',':
+                        error("Each value should have its own case label.");
+                        return;
+                    case ':':
+                        statements();
+                        break;
+                    default:
+                        error("Missing ':' on a case clause.", token);
+                    }
+                } else {
+                    error("Expected '{a}' and instead saw '{b}'.",
+                        nexttoken, 'case', nexttoken.value);
+                }
+            }
+        }
+    }).labelled = true;
+
+    stmt('debugger', function () {
+        if (!option.debug) {
+            warning("All 'debugger' statements should be removed.");
+        }
+        return this;
+    }).exps = true;
+
+    (function () {
+        var x = stmt('do', function () {
+            funct['(breakage)'] += 1;
+            funct['(loopage)'] += 1;
+            this.first = block(true);
+            advance('while');
+            var t = nexttoken;
+            nonadjacent(token, t);
+            advance('(');
+            nospace();
+            parse(20);
+            if (nexttoken.id === '=') {
+                warning("Expected a conditional expression and instead saw an assignment.");
+                advance('=');
+                parse(20);
+            }
+            advance(')', t);
+            nospace(prevtoken, token);
+            funct['(breakage)'] -= 1;
+            funct['(loopage)'] -= 1;
+            return this;
+        });
+        x.labelled = true;
+        x.exps = true;
+    }());
+
+    blockstmt('for', function () {
+        var f = option.forin, s, t = nexttoken;
+        funct['(breakage)'] += 1;
+        funct['(loopage)'] += 1;
+        advance('(');
+        nonadjacent(this, t);
+        nospace();
+        if (peek(nexttoken.id === 'var' ? 1 : 0).id === 'in') {
+            if (nexttoken.id === 'var') {
+                advance('var');
+                varstatement(true);
+            } else {
+                switch (funct[nexttoken.value]) {
+                case 'unused':
+                    funct[nexttoken.value] = 'var';
+                    break;
+                case 'var':
+                    break;
+                default:
+                    warning("Bad for in variable '{a}'.",
+                            nexttoken, nexttoken.value);
+                }
+                advance();
+            }
+            advance('in');
+            parse(20);
+            advance(')', t);
+            s = block(true);
+            if (!f && (s.length > 1 || typeof s[0] !== 'object' ||
+                    s[0].value !== 'if')) {
+                warning("The body of a for in should be wrapped in an if statement to filter unwanted properties from the prototype.", this);
+            }
+            funct['(breakage)'] -= 1;
+            funct['(loopage)'] -= 1;
+            return this;
+        } else {
+            if (nexttoken.id !== ';') {
+                if (nexttoken.id === 'var') {
+                    advance('var');
+                    varstatement();
+                } else {
+                    for (;;) {
+                        parse(0, 'for');
+                        if (nexttoken.id !== ',') {
+                            break;
+                        }
+                        comma();
+                    }
+                }
+            }
+            nolinebreak(token);
+            advance(';');
+            if (nexttoken.id !== ';') {
+                parse(20);
+                if (nexttoken.id === '=') {
+                    warning("Expected a conditional expression and instead saw an assignment.");
+                    advance('=');
+                    parse(20);
+                }
+            }
+            nolinebreak(token);
+            advance(';');
+            if (nexttoken.id === ';') {
+                error("Expected '{a}' and instead saw '{b}'.",
+                        nexttoken, ')', ';');
+            }
+            if (nexttoken.id !== ')') {
+                for (;;) {
+                    parse(0, 'for');
+                    if (nexttoken.id !== ',') {
+                        break;
+                    }
+                    comma();
+                }
+            }
+            advance(')', t);
+            nospace(prevtoken, token);
+            block(true);
+            funct['(breakage)'] -= 1;
+            funct['(loopage)'] -= 1;
+            return this;
+        }
+    }).labelled = true;
+
+
+    stmt('break', function () {
+        var v = nexttoken.value;
+        if (funct['(breakage)'] === 0) {
+            warning("Unexpected '{a}'.", nexttoken, this.value);
+        }
+        nolinebreak(this);
+        if (nexttoken.id !== ';') {
+            if (token.line === nexttoken.line) {
+                if (funct[v] !== 'label') {
+                    warning("'{a}' is not a statement label.", nexttoken, v);
+                } else if (scope[v] !== funct) {
+                    warning("'{a}' is out of scope.", nexttoken, v);
+                }
+                this.first = nexttoken;
+                advance();
+            }
+        }
+        reachable('break');
+        return this;
+    }).exps = true;
+
+
+    stmt('continue', function () {
+        var v = nexttoken.value;
+        if (funct['(breakage)'] === 0) {
+            warning("Unexpected '{a}'.", nexttoken, this.value);
+        }
+        nolinebreak(this);
+        if (nexttoken.id !== ';') {
+            if (token.line === nexttoken.line) {
+                if (funct[v] !== 'label') {
+                    warning("'{a}' is not a statement label.", nexttoken, v);
+                } else if (scope[v] !== funct) {
+                    warning("'{a}' is out of scope.", nexttoken, v);
+                }
+                this.first = nexttoken;
+                advance();
+            }
+        } else if (!funct['(loopage)']) {
+            warning("Unexpected '{a}'.", nexttoken, this.value);
+        }
+        reachable('continue');
+        return this;
+    }).exps = true;
+
+
+    stmt('return', function () {
+        nolinebreak(this);
+        if (nexttoken.id === '(regexp)') {
+            warning("Wrap the /regexp/ literal in parens to disambiguate the slash operator.");
+        }
+        if (nexttoken.id !== ';' && !nexttoken.reach) {
+            nonadjacent(token, nexttoken);
+            this.first = parse(20);
+        }
+        reachable('return');
+        return this;
+    }).exps = true;
+
+
+    stmt('throw', function () {
+        nolinebreak(this);
+        nonadjacent(token, nexttoken);
+        this.first = parse(20);
+        reachable('throw');
+        return this;
+    }).exps = true;
+
+    reserve('void');
+
+//  Superfluous reserved words
+
+    reserve('class');
+    reserve('const');
+    reserve('enum');
+    reserve('export');
+    reserve('extends');
+    reserve('import');
+    reserve('super');
+
+    reserve('let');
+    reserve('yield');
+    reserve('implements');
+    reserve('interface');
+    reserve('package');
+    reserve('private');
+    reserve('protected');
+    reserve('public');
+    reserve('static');
+
+    function jsonValue() {
+
+        function jsonObject() {
+            var o = {}, t = nexttoken;
+            advance('{');
+            if (nexttoken.id !== '}') {
+                for (;;) {
+                    if (nexttoken.id === '(end)') {
+                        error("Missing '}' to match '{' from line {a}.",
+                                nexttoken, t.line);
+                    } else if (nexttoken.id === '}') {
+                        warning("Unexpected comma.", token);
+                        break;
+                    } else if (nexttoken.id === ',') {
+                        error("Unexpected comma.", nexttoken);
+                    } else if (nexttoken.id !== '(string)') {
+                        warning("Expected a string and instead saw {a}.",
+                                nexttoken, nexttoken.value);
+                    }
+                    if (o[nexttoken.value] === true) {
+                        warning("Duplicate key '{a}'.",
+                                nexttoken, nexttoken.value);
+                    } else if (nexttoken.value === '__proto__') {
+                        warning("Stupid key '{a}'.",
+                                nexttoken, nexttoken.value);
+                    } else {
+                        o[nexttoken.value] = true;
+                    }
+                    advance();
+                    advance(':');
+                    jsonValue();
+                    if (nexttoken.id !== ',') {
+                        break;
+                    }
+                    advance(',');
+                }
+            }
+            advance('}');
+        }
+
+        function jsonArray() {
+            var t = nexttoken;
+            advance('[');
+            if (nexttoken.id !== ']') {
+                for (;;) {
+                    if (nexttoken.id === '(end)') {
+                        error("Missing ']' to match '[' from line {a}.",
+                                nexttoken, t.line);
+                    } else if (nexttoken.id === ']') {
+                        warning("Unexpected comma.", token);
+                        break;
+                    } else if (nexttoken.id === ',') {
+                        error("Unexpected comma.", nexttoken);
+                    }
+                    jsonValue();
+                    if (nexttoken.id !== ',') {
+                        break;
+                    }
+                    advance(',');
+                }
+            }
+            advance(']');
+        }
+
+        switch (nexttoken.id) {
+        case '{':
+            jsonObject();
+            break;
+        case '[':
+            jsonArray();
+            break;
+        case 'true':
+        case 'false':
+        case 'null':
+        case '(number)':
+        case '(string)':
+            advance();
+            break;
+        case '-':
+            advance('-');
+            if (token.character !== nexttoken.from) {
+                warning("Unexpected space after '-'.", token);
+            }
+            adjacent(token, nexttoken);
+            advance('(number)');
+            break;
+        default:
+            error("Expected a JSON value.", nexttoken);
+        }
+    }
+
+
+// The actual JSLINT function itself.
+
+    var itself = function (s, o) {
+        var a, i;
+        JSLINT.errors = [];
+        predefined = Object.create(standard);
+        if (o) {
+            a = o.predef;
+            if (a instanceof Array) {
+                for (i = 0; i < a.length; i += 1) {
+                    predefined[a[i]] = true;
+                }
+            }
+            if (o.adsafe) {
+                o.safe = true;
+            }
+            if (o.safe) {
+                o.browser = false;
+                o.css     = false;
+                o.debug   = false;
+                o.devel   = false;
+                o.eqeqeq  = true;
+                o.evil    = false;
+                o.forin   = false;
+                o.nomen   = true;
+                o.on      = false;
+                o.rhino   = false;
+                o.safe    = true;
+                o.sidebar = false;
+                o.strict  = true;
+                o.sub     = false;
+                o.undef   = true;
+                o.widget  = false;
+                predefined.Date = null;
+                predefined['eval'] = null;
+                predefined.Function = null;
+                predefined.Object = null;
+                predefined.ADSAFE = false;
+                predefined.lib = false;
+            }
+            option = o;
+        } else {
+            option = {};
+        }
+        option.indent = option.indent || 4;
+        option.maxerr = option.maxerr || 50;
+        adsafe_id = '';
+        adsafe_may = false;
+        adsafe_went = false;
+        approved = {};
+        if (option.approved) {
+            for (i = 0; i < option.approved.length; i += 1) {
+                approved[option.approved[i]] = option.approved[i];
+            }
+        } else {
+            approved.test = 'test';
+        }
+        tab = '';
+        for (i = 0; i < option.indent; i += 1) {
+            tab += ' ';
+        }
+        indent = 1;
+        global = Object.create(predefined);
+        scope = global;
+        funct = {
+            '(global)': true,
+            '(name)': '(global)',
+            '(scope)': scope,
+            '(breakage)': 0,
+            '(loopage)': 0
+        };
+        functions = [funct];
+        ids = {};
+        urls = [];
+        src = false;
+        xmode = false;
+        stack = null;
+        member = {};
+        membersOnly = null;
+        implied = {};
+        inblock = false;
+        lookahead = [];
+        jsonmode = false;
+        warnings = 0;
+        lex.init(s);
+        prereg = true;
+        strict_mode = false;
+
+        prevtoken = token = nexttoken = syntax['(begin)'];
+        assume();
+
+        try {
+            advance();
+            if (nexttoken.value.charAt(0) === '<') {
+                html();
+                if (option.adsafe && !adsafe_went) {
+                    warning("ADsafe violation: Missing ADSAFE.go.", this);
+                }
+            } else {
+                switch (nexttoken.id) {
+                case '{':
+                case '[':
+                    option.laxbreak = true;
+                    jsonmode = true;
+                    jsonValue();
+                    break;
+                case '@':
+                case '*':
+                case '#':
+                case '.':
+                case ':':
+                    xmode = 'style';
+                    advance();
+                    if (token.id !== '@' || !nexttoken.identifier ||
+                            nexttoken.value !== 'charset' || token.line !== 1 ||
+                            token.from !== 1) {
+                        error('A css file should begin with @charset "UTF-8";');
+                    }
+                    advance();
+                    if (nexttoken.type !== '(string)' &&
+                            nexttoken.value !== 'UTF-8') {
+                        error('A css file should begin with @charset "UTF-8";');
+                    }
+                    advance();
+                    advance(';');
+                    styles();
+                    break;
+
+                default:
+                    if (option.adsafe && option.fragment) {
+                        error("Expected '{a}' and instead saw '{b}'.",
+                            nexttoken, '<div>', nexttoken.value);
+                    }
+                    statements('lib');
+                }
+            }
+            advance('(end)');
+        } catch (e) {
+            if (e) {
+                JSLINT.errors.push({
+                    reason    : e.message,
+                    line      : e.line || nexttoken.line,
+                    character : e.character || nexttoken.from
+                }, null);
+            }
+        }
+        return JSLINT.errors.length === 0;
+    };
+
+    function is_array(o) {
+        return Object.prototype.toString.apply(o) === '[object Array]';
+    }
+
+    function to_array(o) {
+        var a = [], k;
+        for (k in o) {
+            if (is_own(o, k)) {
+                a.push(k);
+            }
+        }
+        return a;
+    }
+
+// Data summary.
+
+    itself.data = function () {
+
+        var data = {functions: []}, fu, globals, implieds = [], f, i, j,
+            members = [], n, unused = [], v;
+        if (itself.errors.length) {
+            data.errors = itself.errors;
+        }
+
+        if (jsonmode) {
+            data.json = true;
+        }
+
+        for (n in implied) {
+            if (is_own(implied, n)) {
+                implieds.push({
+                    name: n,
+                    line: implied[n]
+                });
+            }
+        }
+        if (implieds.length > 0) {
+            data.implieds = implieds;
+        }
+
+        if (urls.length > 0) {
+            data.urls = urls;
+        }
+
+        globals = to_array(scope);
+        if (globals.length > 0) {
+            data.globals = globals;
+        }
+
+        for (i = 1; i < functions.length; i += 1) {
+            f = functions[i];
+            fu = {};
+            for (j = 0; j < functionicity.length; j += 1) {
+                fu[functionicity[j]] = [];
+            }
+            for (n in f) {
+                if (is_own(f, n) && n.charAt(0) !== '(') {
+                    v = f[n];
+                    if (is_array(fu[v])) {
+                        fu[v].push(n);
+                        if (v === 'unused') {
+                            unused.push({
+                                name: n,
+                                line: f['(line)'],
+                                'function': f['(name)']
+                            });
+                        }
+                    }
+                }
+            }
+            for (j = 0; j < functionicity.length; j += 1) {
+                if (fu[functionicity[j]].length === 0) {
+                    delete fu[functionicity[j]];
+                }
+            }
+            fu.name = f['(name)'];
+            fu.param = f['(params)'];
+            fu.line = f['(line)'];
+            fu.last = f['(last)'];
+            data.functions.push(fu);
+        }
+
+        if (unused.length > 0) {
+            data.unused = unused;
+        }
+
+        members = [];
+        for (n in member) {
+            if (typeof member[n] === 'number') {
+                data.member = member;
+                break;
+            }
+        }
+
+        return data;
+    };
+
+    itself.report = function (option) {
+        var data = itself.data();
+
+        var a = [], c, e, err, f, i, k, l, m = '', n, o = [], s;
+
+        function detail(h, array) {
+            var b, i, singularity;
+            if (array) {
+                o.push('<div><i>' + h + '</i> ');
+                array = array.sort();
+                for (i = 0; i < array.length; i += 1) {
+                    if (array[i] !== singularity) {
+                        singularity = array[i];
+                        o.push((b ? ', ' : '') + singularity);
+                        b = true;
+                    }
+                }
+                o.push('</div>');
+            }
+        }
+
+
+        if (data.errors || data.implieds || data.unused) {
+            err = true;
+            o.push('<div id=errors><i>Error:</i>');
+            if (data.errors) {
+                for (i = 0; i < data.errors.length; i += 1) {
+                    c = data.errors[i];
+                    if (c) {
+                        e = c.evidence || '';
+                        o.push('<p>Problem' + (isFinite(c.line) ? ' at line ' +
+                                c.line + ' character ' + c.character : '') +
+                                ': ' + c.reason.entityify() +
+                                '</p><p class=evidence>' +
+                                (e && (e.length > 80 ? e.slice(0, 77) + '...' :
+                                e).entityify()) + '</p>');
+                    }
+                }
+            }
+
+            if (data.implieds) {
+                s = [];
+                for (i = 0; i < data.implieds.length; i += 1) {
+                    s[i] = '<code>' + data.implieds[i].name + '</code>&nbsp;<i>' +
+                        data.implieds[i].line + '</i>';
+                }
+                o.push('<p><i>Implied global:</i> ' + s.join(', ') + '</p>');
+            }
+
+            if (data.unused) {
+                s = [];
+                for (i = 0; i < data.unused.length; i += 1) {
+                    s[i] = '<code><u>' + data.unused[i].name + '</u></code>&nbsp;<i>' +
+                        data.unused[i].line + '</i> <code>' +
+                        data.unused[i]['function'] + '</code>';
+                }
+                o.push('<p><i>Unused variable:</i> ' + s.join(', ') + '</p>');
+            }
+            if (data.json) {
+                o.push('<p>JSON: bad.</p>');
+            }
+            o.push('</div>');
+        }
+
+        if (!option) {
+
+            o.push('<br><div id=functions>');
+
+            if (data.urls) {
+                detail("URLs<br>", data.urls, '<br>');
+            }
+
+            if (xmode === 'style') {
+                o.push('<p>CSS.</p>');
+            } else if (data.json && !err) {
+                o.push('<p>JSON: good.</p>');
+            } else if (data.globals) {
+                o.push('<div><i>Global</i> ' +
+                        data.globals.sort().join(', ') + '</div>');
+            } else {
+                o.push('<div><i>No new global variables introduced.</i></div>');
+            }
+
+            for (i = 0; i < data.functions.length; i += 1) {
+                f = data.functions[i];
+
+                o.push('<br><div class=function><i>' + f.line + '-' +
+                        f.last + '</i> ' + (f.name || '') + '(' +
+                        (f.param ? f.param.join(', ') : '') + ')</div>');
+                detail('<big><b>Unused</b></big>', f.unused);
+                detail('Closure', f.closure);
+                detail('Variable', f['var']);
+                detail('Exception', f.exception);
+                detail('Outer', f.outer);
+                detail('Global', f.global);
+                detail('Label', f.label);
+            }
+
+            if (data.member) {
+                a = to_array(data.member);
+                if (a.length) {
+                    a = a.sort();
+                    m = '<br><pre id=members>/*members ';
+                    l = 10;
+                    for (i = 0; i < a.length; i += 1) {
+                        k = a[i];
+                        n = k.name();
+                        if (l + n.length > 72) {
+                            o.push(m + '<br>');
+                            m = '    ';
+                            l = 1;
+                        }
+                        l += n.length + 2;
+                        if (data.member[k] === 1) {
+                            n = '<i>' + n + '</i>';
+                        }
+                        if (i < a.length - 1) {
+                            n += ', ';
+                        }
+                        m += n;
+                    }
+                    o.push(m + '<br>*/</pre>');
+                }
+                o.push('</div>');
+            }
+        }
+        return o.join('');
+    };
+    itself.jslint = itself;
+
+    itself.edition = '2010-02-20';
+
+    if (typeof exports !== "undefined") {
+        exports.JSLINT = itself;
+    }
+
+    return itself;
+
+}());
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/lib/parse-js.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/lib/parse-js.js
new file mode 100644
index 0000000..9f90dfe
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/lib/parse-js.js
@@ -0,0 +1,1319 @@
+/***********************************************************************
+
+  A JavaScript tokenizer / parser / beautifier / compressor.
+
+  This version is suitable for Node.js.  With minimal changes (the
+  exports stuff) it should work on any JS platform.
+
+  This file contains the tokenizer/parser.  It is a port to JavaScript
+  of parse-js [1], a JavaScript parser library written in Common Lisp
+  by Marijn Haverbeke.  Thank you Marijn!
+
+  [1] http://marijn.haverbeke.nl/parse-js/
+
+  Exported functions:
+
+    - tokenizer(code) -- returns a function.  Call the returned
+      function to fetch the next token.
+
+    - parse(code) -- returns an AST of the given JavaScript code.
+
+  -------------------------------- (C) ---------------------------------
+
+                           Author: Mihai Bazon
+                         <mihai.bazon@gmail.com>
+                       http://mihai.bazon.net/blog
+
+  Distributed under the BSD license:
+
+    Copyright 2010 (c) Mihai Bazon <mihai.bazon@gmail.com>
+    Based on parse-js (http://marijn.haverbeke.nl/parse-js/).
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions
+    are met:
+
+        * Redistributions of source code must retain the above
+          copyright notice, this list of conditions and the following
+          disclaimer.
+
+        * Redistributions in binary form must reproduce the above
+          copyright notice, this list of conditions and the following
+          disclaimer in the documentation and/or other materials
+          provided with the distribution.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
+    EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+    PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
+    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+    OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+    TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+    THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+    SUCH DAMAGE.
+
+ ***********************************************************************/
+
+/* -----[ Tokenizer (constants) ]----- */
+
+var KEYWORDS = array_to_hash([
+        "break",
+        "case",
+        "catch",
+        "const",
+        "continue",
+        "default",
+        "delete",
+        "do",
+        "else",
+        "finally",
+        "for",
+        "function",
+        "if",
+        "in",
+        "instanceof",
+        "new",
+        "return",
+        "switch",
+        "throw",
+        "try",
+        "typeof",
+        "var",
+        "void",
+        "while",
+        "with"
+]);
+
+var RESERVED_WORDS = array_to_hash([
+        "abstract",
+        "boolean",
+        "byte",
+        "char",
+        "class",
+        "debugger",
+        "double",
+        "enum",
+        "export",
+        "extends",
+        "final",
+        "float",
+        "goto",
+        "implements",
+        "import",
+        "int",
+        "interface",
+        "long",
+        "native",
+        "package",
+        "private",
+        "protected",
+        "public",
+        "short",
+        "static",
+        "super",
+        "synchronized",
+        "throws",
+        "transient",
+        "volatile"
+]);
+
+var KEYWORDS_BEFORE_EXPRESSION = array_to_hash([
+        "return",
+        "new",
+        "delete",
+        "throw",
+        "else",
+        "case"
+]);
+
+var KEYWORDS_ATOM = array_to_hash([
+        "false",
+        "null",
+        "true",
+        "undefined"
+]);
+
+var OPERATOR_CHARS = array_to_hash(characters("+-*&%=<>!?|~^"));
+
+var RE_HEX_NUMBER = /^0x[0-9a-f]+$/i;
+var RE_OCT_NUMBER = /^0[0-7]+$/;
+var RE_DEC_NUMBER = /^\d*\.?\d*(?:e[+-]?\d*(?:\d\.?|\.?\d)\d*)?$/i;
+
+var OPERATORS = array_to_hash([
+        "in",
+        "instanceof",
+        "typeof",
+        "new",
+        "void",
+        "delete",
+        "++",
+        "--",
+        "+",
+        "-",
+        "!",
+        "~",
+        "&",
+        "|",
+        "^",
+        "*",
+        "/",
+        "%",
+        ">>",
+        "<<",
+        ">>>",
+        "<",
+        ">",
+        "<=",
+        ">=",
+        "==",
+        "===",
+        "!=",
+        "!==",
+        "?",
+        "=",
+        "+=",
+        "-=",
+        "/=",
+        "*=",
+        "%=",
+        ">>=",
+        "<<=",
+        ">>>=",
+        "|=",
+        "^=",
+        "&=",
+        "&&",
+        "||"
+]);
+
+var WHITESPACE_CHARS = array_to_hash(characters(" \n\r\t\u200b"));
+
+var PUNC_BEFORE_EXPRESSION = array_to_hash(characters("[{}(,.;:"));
+
+var PUNC_CHARS = array_to_hash(characters("[]{}(),;:"));
+
+var REGEXP_MODIFIERS = array_to_hash(characters("gmsiy"));
+
+/* -----[ Tokenizer ]----- */
+
+// regexps adapted from http://xregexp.com/plugins/#unicode
+var UNICODE = {
+        letter: new RegExp("[\\u0041-\\u005A\\u0061-\\u007A\\u00AA\\u00B5\\u00BA\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02C1\\u02C6-\\u02D1\\u02E0-\\u02E4\\u02EC\\u02EE\\u0370-\\u0374\\u0376\\u0377\\u037A-\\u037D\\u0386\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03F5\\u03F7-\\u0481\\u048A-\\u0523\\u0531-\\u0556\\u0559\\u0561-\\u0587\\u05D0-\\u05EA\\u05F0-\\u05F2\\u0621-\\u064A\\u066E\\u066F\\u0671-\\u06D3\\u06D5\\u06E5\\u06E6\\u06EE\\u06EF\\u06FA-\\u06FC\\u06FF\\u0710\\u0712-\\u072F\\u074D-\\u07A5\\u07B1\\u07CA-\\u07EA\\u07F4\\u07F5\\u07FA\\u0904-\\u0939\\u093D\\u0950\\u0958-\\u0961\\u0971\\u0972\\u097B-\\u097F\\u0985-\\u098C\\u098F\\u0990\\u0993-\\u09A8\\u09AA-\\u09B0\\u09B2\\u09B6-\\u09B9\\u09BD\\u09CE\\u09DC\\u09DD\\u09DF-\\u09E1\\u09F0\\u09F1\\u0A05-\\u0A0A\\u0A0F\\u0A10\\u0A13-\\u0A28\\u0A2A-\\u0A30\\u0A32\\u0A33\\u0A35\\u0A36\\u0A38\\u0A39\\u0A59-\\u0A5C\\u0A5E\\u0A72-\\u0A74\\u0A85-\\u0A8D\\u0A8F-\\u0A91\\u0A93-\\u0AA8\\u0AAA-\\u0AB0\\u0AB2\\u0AB3\\u0AB5-\\u0AB9\\u0ABD\\u0AD0\\u0AE0\\u0AE1\\u0B05-\\u0B0C\\u0B0F\\u0B10\\u0B13-\\u0B28\\u0B2A-\\u0B30\\u0B32\\u0B33\\u0B35-\\u0B39\\u0B3D\\u0B5C\\u0B5D\\u0B5F-\\u0B61\\u0B71\\u0B83\\u0B85-\\u0B8A\\u0B8E-\\u0B90\\u0B92-\\u0B95\\u0B99\\u0B9A\\u0B9C\\u0B9E\\u0B9F\\u0BA3\\u0BA4\\u0BA8-\\u0BAA\\u0BAE-\\u0BB9\\u0BD0\\u0C05-\\u0C0C\\u0C0E-\\u0C10\\u0C12-\\u0C28\\u0C2A-\\u0C33\\u0C35-\\u0C39\\u0C3D\\u0C58\\u0C59\\u0C60\\u0C61\\u0C85-\\u0C8C\\u0C8E-\\u0C90\\u0C92-\\u0CA8\\u0CAA-\\u0CB3\\u0CB5-\\u0CB9\\u0CBD\\u0CDE\\u0CE0\\u0CE1\\u0D05-\\u0D0C\\u0D0E-\\u0D10\\u0D12-\\u0D28\\u0D2A-\\u0D39\\u0D3D\\u0D60\\u0D61\\u0D7A-\\u0D7F\\u0D85-\\u0D96\\u0D9A-\\u0DB1\\u0DB3-\\u0DBB\\u0DBD\\u0DC0-\\u0DC6\\u0E01-\\u0E30\\u0E32\\u0E33\\u0E40-\\u0E46\\u0E81\\u0E82\\u0E84\\u0E87\\u0E88\\u0E8A\\u0E8D\\u0E94-\\u0E97\\u0E99-\\u0E9F\\u0EA1-\\u0EA3\\u0EA5\\u0EA7\\u0EAA\\u0EAB\\u0EAD-\\u0EB0\\u0EB2\\u0EB3\\u0EBD\\u0EC0-\\u0EC4\\u0EC6\\u0EDC\\u0EDD\\u0F00\\u0F40-\\u0F47\\u0F49-\\u0F6C\\u0F88-\\u0F8B\\u1000-\\u102A\\u103F\\u1050-\\u1055\\u105A-\\u105D\\u1061\\u1065\\u1066\\u106E-\\u1070\\u1075-\\u1081\\u108E\\u10A0-\\u10C5\\u10D0-\\u10FA\\u10FC\\u1100-\\u1159\\u115F-\\u11A2\\u11A8-\\u11F9\\u1200-\\u1248\\u124A-\\u124D\\u1250-\\u1256\\u1258\\u125A-\\u125D\\u1260-\\u1288\\u128A-\\u128D\\u1290-\\u12B0\\u12B2-\\u12B5\\u12B8-\\u12BE\\u12C0\\u12C2-\\u12C5\\u12C8-\\u12D6\\u12D8-\\u1310\\u1312-\\u1315\\u1318-\\u135A\\u1380-\\u138F\\u13A0-\\u13F4\\u1401-\\u166C\\u166F-\\u1676\\u1681-\\u169A\\u16A0-\\u16EA\\u1700-\\u170C\\u170E-\\u1711\\u1720-\\u1731\\u1740-\\u1751\\u1760-\\u176C\\u176E-\\u1770\\u1780-\\u17B3\\u17D7\\u17DC\\u1820-\\u1877\\u1880-\\u18A8\\u18AA\\u1900-\\u191C\\u1950-\\u196D\\u1970-\\u1974\\u1980-\\u19A9\\u19C1-\\u19C7\\u1A00-\\u1A16\\u1B05-\\u1B33\\u1B45-\\u1B4B\\u1B83-\\u1BA0\\u1BAE\\u1BAF\\u1C00-\\u1C23\\u1C4D-\\u1C4F\\u1C5A-\\u1C7D\\u1D00-\\u1DBF\\u1E00-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45\\u1F48-\\u1F4D\\u1F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F7D\\u1F80-\\u1FB4\\u1FB6-\\u1FBC\\u1FBE\\u1FC2-\\u1FC4\\u1FC6-\\u1FCC\\u1FD0-\\u1FD3\\u1FD6-\\u1FDB\\u1FE0-\\u1FEC\\u1FF2-\\u1FF4\\u1FF6-\\u1FFC\\u2071\\u207F\\u2090-\\u2094\\u2102\\u2107\\u210A-\\u2113\\u2115\\u2119-\\u211D\\u2124\\u2126\\u2128\\u212A-\\u212D\\u212F-\\u2139\\u213C-\\u213F\\u2145-\\u2149\\u214E\\u2183\\u2184\\u2C00-\\u2C2E\\u2C30-\\u2C5E\\u2C60-\\u2C6F\\u2C71-\\u2C7D\\u2C80-\\u2CE4\\u2D00-\\u2D25\\u2D30-\\u2D65\\u2D6F\\u2D80-\\u2D96\\u2DA0-\\u2DA6\\u2DA8-\\u2DAE\\u2DB0-\\u2DB6\\u2DB8-\\u2DBE\\u2DC0-\\u2DC6\\u2DC8-\\u2DCE\\u2DD0-\\u2DD6\\u2DD8-\\u2DDE\\u2E2F\\u3005\\u3006\\u3031-\\u3035\\u303B\\u303C\\u3041-\\u3096\\u309D-\\u309F\\u30A1-\\u30FA\\u30FC-\\u30FF\\u3105-\\u312D\\u3131-\\u318E\\u31A0-\\u31B7\\u31F0-\\u31FF\\u3400\\u4DB5\\u4E00\\u9FC3\\uA000-\\uA48C\\uA500-\\uA60C\\uA610-\\uA61F\\uA62A\\uA62B\\uA640-\\uA65F\\uA662-\\uA66E\\uA67F-\\uA697\\uA717-\\uA71F\\uA722-\\uA788\\uA78B\\uA78C\\uA7FB-\\uA801\\uA803-\\uA805\\uA807-\\uA80A\\uA80C-\\uA822\\uA840-\\uA873\\uA882-\\uA8B3\\uA90A-\\uA925\\uA930-\\uA946\\uAA00-\\uAA28\\uAA40-\\uAA42\\uAA44-\\uAA4B\\uAC00\\uD7A3\\uF900-\\uFA2D\\uFA30-\\uFA6A\\uFA70-\\uFAD9\\uFB00-\\uFB06\\uFB13-\\uFB17\\uFB1D\\uFB1F-\\uFB28\\uFB2A-\\uFB36\\uFB38-\\uFB3C\\uFB3E\\uFB40\\uFB41\\uFB43\\uFB44\\uFB46-\\uFBB1\\uFBD3-\\uFD3D\\uFD50-\\uFD8F\\uFD92-\\uFDC7\\uFDF0-\\uFDFB\\uFE70-\\uFE74\\uFE76-\\uFEFC\\uFF21-\\uFF3A\\uFF41-\\uFF5A\\uFF66-\\uFFBE\\uFFC2-\\uFFC7\\uFFCA-\\uFFCF\\uFFD2-\\uFFD7\\uFFDA-\\uFFDC]"),
+        non_spacing_mark: new RegExp("[\\u0300-\\u036F\\u0483-\\u0487\\u0591-\\u05BD\\u05BF\\u05C1\\u05C2\\u05C4\\u05C5\\u05C7\\u0610-\\u061A\\u064B-\\u065E\\u0670\\u06D6-\\u06DC\\u06DF-\\u06E4\\u06E7\\u06E8\\u06EA-\\u06ED\\u0711\\u0730-\\u074A\\u07A6-\\u07B0\\u07EB-\\u07F3\\u0816-\\u0819\\u081B-\\u0823\\u0825-\\u0827\\u0829-\\u082D\\u0900-\\u0902\\u093C\\u0941-\\u0948\\u094D\\u0951-\\u0955\\u0962\\u0963\\u0981\\u09BC\\u09C1-\\u09C4\\u09CD\\u09E2\\u09E3\\u0A01\\u0A02\\u0A3C\\u0A41\\u0A42\\u0A47\\u0A48\\u0A4B-\\u0A4D\\u0A51\\u0A70\\u0A71\\u0A75\\u0A81\\u0A82\\u0ABC\\u0AC1-\\u0AC5\\u0AC7\\u0AC8\\u0ACD\\u0AE2\\u0AE3\\u0B01\\u0B3C\\u0B3F\\u0B41-\\u0B44\\u0B4D\\u0B56\\u0B62\\u0B63\\u0B82\\u0BC0\\u0BCD\\u0C3E-\\u0C40\\u0C46-\\u0C48\\u0C4A-\\u0C4D\\u0C55\\u0C56\\u0C62\\u0C63\\u0CBC\\u0CBF\\u0CC6\\u0CCC\\u0CCD\\u0CE2\\u0CE3\\u0D41-\\u0D44\\u0D4D\\u0D62\\u0D63\\u0DCA\\u0DD2-\\u0DD4\\u0DD6\\u0E31\\u0E34-\\u0E3A\\u0E47-\\u0E4E\\u0EB1\\u0EB4-\\u0EB9\\u0EBB\\u0EBC\\u0EC8-\\u0ECD\\u0F18\\u0F19\\u0F35\\u0F37\\u0F39\\u0F71-\\u0F7E\\u0F80-\\u0F84\\u0F86\\u0F87\\u0F90-\\u0F97\\u0F99-\\u0FBC\\u0FC6\\u102D-\\u1030\\u1032-\\u1037\\u1039\\u103A\\u103D\\u103E\\u1058\\u1059\\u105E-\\u1060\\u1071-\\u1074\\u1082\\u1085\\u1086\\u108D\\u109D\\u135F\\u1712-\\u1714\\u1732-\\u1734\\u1752\\u1753\\u1772\\u1773\\u17B7-\\u17BD\\u17C6\\u17C9-\\u17D3\\u17DD\\u180B-\\u180D\\u18A9\\u1920-\\u1922\\u1927\\u1928\\u1932\\u1939-\\u193B\\u1A17\\u1A18\\u1A56\\u1A58-\\u1A5E\\u1A60\\u1A62\\u1A65-\\u1A6C\\u1A73-\\u1A7C\\u1A7F\\u1B00-\\u1B03\\u1B34\\u1B36-\\u1B3A\\u1B3C\\u1B42\\u1B6B-\\u1B73\\u1B80\\u1B81\\u1BA2-\\u1BA5\\u1BA8\\u1BA9\\u1C2C-\\u1C33\\u1C36\\u1C37\\u1CD0-\\u1CD2\\u1CD4-\\u1CE0\\u1CE2-\\u1CE8\\u1CED\\u1DC0-\\u1DE6\\u1DFD-\\u1DFF\\u20D0-\\u20DC\\u20E1\\u20E5-\\u20F0\\u2CEF-\\u2CF1\\u2DE0-\\u2DFF\\u302A-\\u302F\\u3099\\u309A\\uA66F\\uA67C\\uA67D\\uA6F0\\uA6F1\\uA802\\uA806\\uA80B\\uA825\\uA826\\uA8C4\\uA8E0-\\uA8F1\\uA926-\\uA92D\\uA947-\\uA951\\uA980-\\uA982\\uA9B3\\uA9B6-\\uA9B9\\uA9BC\\uAA29-\\uAA2E\\uAA31\\uAA32\\uAA35\\uAA36\\uAA43\\uAA4C\\uAAB0\\uAAB2-\\uAAB4\\uAAB7\\uAAB8\\uAABE\\uAABF\\uAAC1\\uABE5\\uABE8\\uABED\\uFB1E\\uFE00-\\uFE0F\\uFE20-\\uFE26]"),
+        space_combining_mark: new RegExp("[\\u0903\\u093E-\\u0940\\u0949-\\u094C\\u094E\\u0982\\u0983\\u09BE-\\u09C0\\u09C7\\u09C8\\u09CB\\u09CC\\u09D7\\u0A03\\u0A3E-\\u0A40\\u0A83\\u0ABE-\\u0AC0\\u0AC9\\u0ACB\\u0ACC\\u0B02\\u0B03\\u0B3E\\u0B40\\u0B47\\u0B48\\u0B4B\\u0B4C\\u0B57\\u0BBE\\u0BBF\\u0BC1\\u0BC2\\u0BC6-\\u0BC8\\u0BCA-\\u0BCC\\u0BD7\\u0C01-\\u0C03\\u0C41-\\u0C44\\u0C82\\u0C83\\u0CBE\\u0CC0-\\u0CC4\\u0CC7\\u0CC8\\u0CCA\\u0CCB\\u0CD5\\u0CD6\\u0D02\\u0D03\\u0D3E-\\u0D40\\u0D46-\\u0D48\\u0D4A-\\u0D4C\\u0D57\\u0D82\\u0D83\\u0DCF-\\u0DD1\\u0DD8-\\u0DDF\\u0DF2\\u0DF3\\u0F3E\\u0F3F\\u0F7F\\u102B\\u102C\\u1031\\u1038\\u103B\\u103C\\u1056\\u1057\\u1062-\\u1064\\u1067-\\u106D\\u1083\\u1084\\u1087-\\u108C\\u108F\\u109A-\\u109C\\u17B6\\u17BE-\\u17C5\\u17C7\\u17C8\\u1923-\\u1926\\u1929-\\u192B\\u1930\\u1931\\u1933-\\u1938\\u19B0-\\u19C0\\u19C8\\u19C9\\u1A19-\\u1A1B\\u1A55\\u1A57\\u1A61\\u1A63\\u1A64\\u1A6D-\\u1A72\\u1B04\\u1B35\\u1B3B\\u1B3D-\\u1B41\\u1B43\\u1B44\\u1B82\\u1BA1\\u1BA6\\u1BA7\\u1BAA\\u1C24-\\u1C2B\\u1C34\\u1C35\\u1CE1\\u1CF2\\uA823\\uA824\\uA827\\uA880\\uA881\\uA8B4-\\uA8C3\\uA952\\uA953\\uA983\\uA9B4\\uA9B5\\uA9BA\\uA9BB\\uA9BD-\\uA9C0\\uAA2F\\uAA30\\uAA33\\uAA34\\uAA4D\\uAA7B\\uABE3\\uABE4\\uABE6\\uABE7\\uABE9\\uABEA\\uABEC]"),
+        connector_punctuation: new RegExp("[\\u005F\\u203F\\u2040\\u2054\\uFE33\\uFE34\\uFE4D-\\uFE4F\\uFF3F]")
+};
+
+function is_letter(ch) {
+        return UNICODE.letter.test(ch);
+};
+
+function is_digit(ch) {
+        ch = ch.charCodeAt(0);
+        return ch >= 48 && ch <= 57; //XXX: find out if "UnicodeDigit" means something else than 0..9
+};
+
+function is_alphanumeric_char(ch) {
+        return is_digit(ch) || is_letter(ch);
+};
+
+function is_unicode_combining_mark(ch) {
+        return UNICODE.non_spacing_mark.test(ch) || UNICODE.space_combining_mark.test(ch);
+};
+
+function is_unicode_connector_punctuation(ch) {
+        return UNICODE.connector_punctuation.test(ch);
+};
+
+function is_identifier_start(ch) {
+        return ch == "$" || ch == "_" || is_letter(ch);
+};
+
+function is_identifier_char(ch) {
+        return is_identifier_start(ch)
+                || is_unicode_combining_mark(ch)
+                || is_digit(ch)
+                || is_unicode_connector_punctuation(ch)
+                || ch == "\u200c" // zero-width non-joiner <ZWNJ>
+                || ch == "\u200d" // zero-width joiner <ZWJ> (in my ECMA-262 PDF, this is also 200c)
+        ;
+};
+
+function parse_js_number(num) {
+        if (RE_HEX_NUMBER.test(num)) {
+                return parseInt(num.substr(2), 16);
+        } else if (RE_OCT_NUMBER.test(num)) {
+                return parseInt(num.substr(1), 8);
+        } else if (RE_DEC_NUMBER.test(num)) {
+                return parseFloat(num);
+        }
+};
+
+function JS_Parse_Error(message, line, col, pos) {
+        this.message = message;
+        this.line = line;
+        this.col = col;
+        this.pos = pos;
+        try {
+                ({})();
+        } catch(ex) {
+                this.stack = ex.stack;
+        };
+};
+
+JS_Parse_Error.prototype.toString = function() {
+        return this.message + " (line: " + this.line + ", col: " + this.col + ", pos: " + this.pos + ")" + "\n\n" + this.stack;
+};
+
+function js_error(message, line, col, pos) {
+        throw new JS_Parse_Error(message, line, col, pos);
+};
+
+function is_token(token, type, val) {
+        return token.type == type && (val == null || token.value == val);
+};
+
+var EX_EOF = {};
+
+function tokenizer($TEXT) {
+
+        var S = {
+                text            : $TEXT.replace(/\r\n?|[\n\u2028\u2029]/g, "\n").replace(/^\uFEFF/, ''),
+                pos             : 0,
+                tokpos          : 0,
+                line            : 0,
+                tokline         : 0,
+                col             : 0,
+                tokcol          : 0,
+                newline_before  : false,
+                regex_allowed   : false,
+                comments_before : []
+        };
+
+        function peek() { return S.text.charAt(S.pos); };
+
+        function next(signal_eof) {
+                var ch = S.text.charAt(S.pos++);
+                if (signal_eof && !ch)
+                        throw EX_EOF;
+                if (ch == "\n") {
+                        S.newline_before = true;
+                        ++S.line;
+                        S.col = 0;
+                } else {
+                        ++S.col;
+                }
+                return ch;
+        };
+
+        function eof() {
+                return !S.peek();
+        };
+
+        function find(what, signal_eof) {
+                var pos = S.text.indexOf(what, S.pos);
+                if (signal_eof && pos == -1) throw EX_EOF;
+                return pos;
+        };
+
+        function start_token() {
+                S.tokline = S.line;
+                S.tokcol = S.col;
+                S.tokpos = S.pos;
+        };
+
+        function token(type, value, is_comment) {
+                S.regex_allowed = ((type == "operator" && !HOP(UNARY_POSTFIX, value)) ||
+                                   (type == "keyword" && HOP(KEYWORDS_BEFORE_EXPRESSION, value)) ||
+                                   (type == "punc" && HOP(PUNC_BEFORE_EXPRESSION, value)));
+                var ret = {
+                        type  : type,
+                        value : value,
+                        line  : S.tokline,
+                        col   : S.tokcol,
+                        pos   : S.tokpos,
+                        nlb   : S.newline_before
+                };
+                if (!is_comment) {
+                        ret.comments_before = S.comments_before;
+                        S.comments_before = [];
+                }
+                S.newline_before = false;
+                return ret;
+        };
+
+        function skip_whitespace() {
+                while (HOP(WHITESPACE_CHARS, peek()))
+                        next();
+        };
+
+        function read_while(pred) {
+                var ret = "", ch = peek(), i = 0;
+                while (ch && pred(ch, i++)) {
+                        ret += next();
+                        ch = peek();
+                }
+                return ret;
+        };
+
+        function parse_error(err) {
+                js_error(err, S.tokline, S.tokcol, S.tokpos);
+        };
+
+        function read_num(prefix) {
+                var has_e = false, after_e = false, has_x = false, has_dot = prefix == ".";
+                var num = read_while(function(ch, i){
+                        if (ch == "x" || ch == "X") {
+                                if (has_x) return false;
+                                return has_x = true;
+                        }
+                        if (!has_x && (ch == "E" || ch == "e")) {
+                                if (has_e) return false;
+                                return has_e = after_e = true;
+                        }
+                        if (ch == "-") {
+                                if (after_e || (i == 0 && !prefix)) return true;
+                                return false;
+                        }
+                        if (ch == "+") return after_e;
+                        after_e = false;
+                        if (ch == ".") {
+                                if (!has_dot && !has_x)
+                                        return has_dot = true;
+                                return false;
+                        }
+                        return is_alphanumeric_char(ch);
+                });
+                if (prefix)
+                        num = prefix + num;
+                var valid = parse_js_number(num);
+                if (!isNaN(valid)) {
+                        return token("num", valid);
+                } else {
+                        parse_error("Invalid syntax: " + num);
+                }
+        };
+
+        function read_escaped_char() {
+                var ch = next(true);
+                switch (ch) {
+                    case "n" : return "\n";
+                    case "r" : return "\r";
+                    case "t" : return "\t";
+                    case "b" : return "\b";
+                    case "v" : return "\v";
+                    case "f" : return "\f";
+                    case "0" : return "\0";
+                    case "x" : return String.fromCharCode(hex_bytes(2));
+                    case "u" : return String.fromCharCode(hex_bytes(4));
+                    default  : return ch;
+                }
+        };
+
+        function hex_bytes(n) {
+                var num = 0;
+                for (; n > 0; --n) {
+                        var digit = parseInt(next(true), 16);
+                        if (isNaN(digit))
+                                parse_error("Invalid hex-character pattern in string");
+                        num = (num << 4) | digit;
+                }
+                return num;
+        };
+
+        function read_string() {
+                return with_eof_error("Unterminated string constant", function(){
+                        var quote = next(), ret = "";
+                        for (;;) {
+                                var ch = next(true);
+                                if (ch == "\\") ch = read_escaped_char();
+                                else if (ch == quote) break;
+                                ret += ch;
+                        }
+                        return token("string", ret);
+                });
+        };
+
+        function read_line_comment() {
+                next();
+                var i = find("\n"), ret;
+                if (i == -1) {
+                        ret = S.text.substr(S.pos);
+                        S.pos = S.text.length;
+                } else {
+                        ret = S.text.substring(S.pos, i);
+                        S.pos = i;
+                }
+                return token("comment1", ret, true);
+        };
+
+        function read_multiline_comment() {
+                next();
+                return with_eof_error("Unterminated multiline comment", function(){
+                        var i = find("*/", true),
+                            text = S.text.substring(S.pos, i),
+                            tok = token("comment2", text, true);
+                        S.pos = i + 2;
+                        S.line += text.split("\n").length - 1;
+                        S.newline_before = text.indexOf("\n") >= 0;
+
+                        // https://github.com/mishoo/UglifyJS/issues/#issue/100
+                        if (/^@cc_on/i.test(text)) {
+                                warn("WARNING: at line " + S.line);
+                                warn("*** Found \"conditional comment\": " + text);
+                                warn("*** UglifyJS DISCARDS ALL COMMENTS.  This means your code might no longer work properly in Internet Explorer.");
+                        }
+
+                        return tok;
+                });
+        };
+
+        function read_name() {
+                var backslash = false, name = "", ch;
+                while ((ch = peek()) != null) {
+                        if (!backslash) {
+                                if (ch == "\\") backslash = true, next();
+                                else if (is_identifier_char(ch)) name += next();
+                                else break;
+                        }
+                        else {
+                                if (ch != "u") parse_error("Expecting UnicodeEscapeSequence -- uXXXX");
+                                ch = read_escaped_char();
+                                if (!is_identifier_char(ch)) parse_error("Unicode char: " + ch.charCodeAt(0) + " is not valid in identifier");
+                                name += ch;
+                                backslash = false;
+                        }
+                }
+                return name;
+        };
+
+        function read_regexp() {
+                return with_eof_error("Unterminated regular expression", function(){
+                        var prev_backslash = false, regexp = "", ch, in_class = false;
+                        while ((ch = next(true))) if (prev_backslash) {
+                                regexp += "\\" + ch;
+                                prev_backslash = false;
+                        } else if (ch == "[") {
+                                in_class = true;
+                                regexp += ch;
+                        } else if (ch == "]" && in_class) {
+                                in_class = false;
+                                regexp += ch;
+                        } else if (ch == "/" && !in_class) {
+                                break;
+                        } else if (ch == "\\") {
+                                prev_backslash = true;
+                        } else {
+                                regexp += ch;
+                        }
+                        var mods = read_name();
+                        return token("regexp", [ regexp, mods ]);
+                });
+        };
+
+        function read_operator(prefix) {
+                function grow(op) {
+                        if (!peek()) return op;
+                        var bigger = op + peek();
+                        if (HOP(OPERATORS, bigger)) {
+                                next();
+                                return grow(bigger);
+                        } else {
+                                return op;
+                        }
+                };
+                return token("operator", grow(prefix || next()));
+        };
+
+        function handle_slash() {
+                next();
+                var regex_allowed = S.regex_allowed;
+                switch (peek()) {
+                    case "/":
+                        S.comments_before.push(read_line_comment());
+                        S.regex_allowed = regex_allowed;
+                        return next_token();
+                    case "*":
+                        S.comments_before.push(read_multiline_comment());
+                        S.regex_allowed = regex_allowed;
+                        return next_token();
+                }
+                return S.regex_allowed ? read_regexp() : read_operator("/");
+        };
+
+        function handle_dot() {
+                next();
+                return is_digit(peek())
+                        ? read_num(".")
+                        : token("punc", ".");
+        };
+
+        function read_word() {
+                var word = read_name();
+                return !HOP(KEYWORDS, word)
+                        ? token("name", word)
+                        : HOP(OPERATORS, word)
+                        ? token("operator", word)
+                        : HOP(KEYWORDS_ATOM, word)
+                        ? token("atom", word)
+                        : token("keyword", word);
+        };
+
+        function with_eof_error(eof_error, cont) {
+                try {
+                        return cont();
+                } catch(ex) {
+                        if (ex === EX_EOF) parse_error(eof_error);
+                        else throw ex;
+                }
+        };
+
+        function next_token(force_regexp) {
+                if (force_regexp)
+                        return read_regexp();
+                skip_whitespace();
+                start_token();
+                var ch = peek();
+                if (!ch) return token("eof");
+                if (is_digit(ch)) return read_num();
+                if (ch == '"' || ch == "'") return read_string();
+                if (HOP(PUNC_CHARS, ch)) return token("punc", next());
+                if (ch == ".") return handle_dot();
+                if (ch == "/") return handle_slash();
+                if (HOP(OPERATOR_CHARS, ch)) return read_operator();
+                if (ch == "\\" || is_identifier_start(ch)) return read_word();
+                parse_error("Unexpected character '" + ch + "'");
+        };
+
+        next_token.context = function(nc) {
+                if (nc) S = nc;
+                return S;
+        };
+
+        return next_token;
+
+};
+
+/* -----[ Parser (constants) ]----- */
+
+var UNARY_PREFIX = array_to_hash([
+        "typeof",
+        "void",
+        "delete",
+        "--",
+        "++",
+        "!",
+        "~",
+        "-",
+        "+"
+]);
+
+var UNARY_POSTFIX = array_to_hash([ "--", "++" ]);
+
+var ASSIGNMENT = (function(a, ret, i){
+        while (i < a.length) {
+                ret[a[i]] = a[i].substr(0, a[i].length - 1);
+                i++;
+        }
+        return ret;
+})(
+        ["+=", "-=", "/=", "*=", "%=", ">>=", "<<=", ">>>=", "|=", "^=", "&="],
+        { "=": true },
+        0
+);
+
+var PRECEDENCE = (function(a, ret){
+        for (var i = 0, n = 1; i < a.length; ++i, ++n) {
+                var b = a[i];
+                for (var j = 0; j < b.length; ++j) {
+                        ret[b[j]] = n;
+                }
+        }
+        return ret;
+})(
+        [
+                ["||"],
+                ["&&"],
+                ["|"],
+                ["^"],
+                ["&"],
+                ["==", "===", "!=", "!=="],
+                ["<", ">", "<=", ">=", "in", "instanceof"],
+                [">>", "<<", ">>>"],
+                ["+", "-"],
+                ["*", "/", "%"]
+        ],
+        {}
+);
+
+var STATEMENTS_WITH_LABELS = array_to_hash([ "for", "do", "while", "switch" ]);
+
+var ATOMIC_START_TOKEN = array_to_hash([ "atom", "num", "string", "regexp", "name" ]);
+
+/* -----[ Parser ]----- */
+
+function NodeWithToken(str, start, end) {
+        this.name = str;
+        this.start = start;
+        this.end = end;
+};
+
+NodeWithToken.prototype.toString = function() { return this.name; };
+
+function parse($TEXT, exigent_mode, embed_tokens) {
+
+        var S = {
+                input       : typeof $TEXT == "string" ? tokenizer($TEXT, true) : $TEXT,
+                token       : null,
+                prev        : null,
+                peeked      : null,
+                in_function : 0,
+                in_loop     : 0,
+                labels      : []
+        };
+
+        S.token = next();
+
+        function is(type, value) {
+                return is_token(S.token, type, value);
+        };
+
+        function peek() { return S.peeked || (S.peeked = S.input()); };
+
+        function next() {
+                S.prev = S.token;
+                if (S.peeked) {
+                        S.token = S.peeked;
+                        S.peeked = null;
+                } else {
+                        S.token = S.input();
+                }
+                return S.token;
+        };
+
+        function prev() {
+                return S.prev;
+        };
+
+        function croak(msg, line, col, pos) {
+                var ctx = S.input.context();
+                js_error(msg,
+                         line != null ? line : ctx.tokline,
+                         col != null ? col : ctx.tokcol,
+                         pos != null ? pos : ctx.tokpos);
+        };
+
+        function token_error(token, msg) {
+                croak(msg, token.line, token.col);
+        };
+
+        function unexpected(token) {
+                if (token == null)
+                        token = S.token;
+                token_error(token, "Unexpected token: " + token.type + " (" + token.value + ")");
+        };
+
+        function expect_token(type, val) {
+                if (is(type, val)) {
+                        return next();
+                }
+                token_error(S.token, "Unexpected token " + S.token.type + ", expected " + type);
+        };
+
+        function expect(punc) { return expect_token("punc", punc); };
+
+        function can_insert_semicolon() {
+                return !exigent_mode && (
+                        S.token.nlb || is("eof") || is("punc", "}")
+                );
+        };
+
+        function semicolon() {
+                if (is("punc", ";")) next();
+                else if (!can_insert_semicolon()) unexpected();
+        };
+
+        function as() {
+                return slice(arguments);
+        };
+
+        function parenthesised() {
+                expect("(");
+                var ex = expression();
+                expect(")");
+                return ex;
+        };
+
+        function add_tokens(str, start, end) {
+                return str instanceof NodeWithToken ? str : new NodeWithToken(str, start, end);
+        };
+
+        var statement = embed_tokens ? function() {
+                var start = S.token;
+                var ast = $statement.apply(this, arguments);
+                ast[0] = add_tokens(ast[0], start, prev());
+                return ast;
+        } : $statement;
+
+        function $statement() {
+                if (is("operator", "/")) {
+                        S.peeked = null;
+                        S.token = S.input(true); // force regexp
+                }
+                switch (S.token.type) {
+                    case "num":
+                    case "string":
+                    case "regexp":
+                    case "operator":
+                    case "atom":
+                        return simple_statement();
+
+                    case "name":
+                        return is_token(peek(), "punc", ":")
+                                ? labeled_statement(prog1(S.token.value, next, next))
+                                : simple_statement();
+
+                    case "punc":
+                        switch (S.token.value) {
+                            case "{":
+                                return as("block", block_());
+                            case "[":
+                            case "(":
+                                return simple_statement();
+                            case ";":
+                                next();
+                                return as("block");
+                            default:
+                                unexpected();
+                        }
+
+                    case "keyword":
+                        switch (prog1(S.token.value, next)) {
+                            case "break":
+                                return break_cont("break");
+
+                            case "continue":
+                                return break_cont("continue");
+
+                            case "debugger":
+                                semicolon();
+                                return as("debugger");
+
+                            case "do":
+                                return (function(body){
+                                        expect_token("keyword", "while");
+                                        return as("do", prog1(parenthesised, semicolon), body);
+                                })(in_loop(statement));
+
+                            case "for":
+                                return for_();
+
+                            case "function":
+                                return function_(true);
+
+                            case "if":
+                                return if_();
+
+                            case "return":
+                                if (S.in_function == 0)
+                                        croak("'return' outside of function");
+                                return as("return",
+                                          is("punc", ";")
+                                          ? (next(), null)
+                                          : can_insert_semicolon()
+                                          ? null
+                                          : prog1(expression, semicolon));
+
+                            case "switch":
+                                return as("switch", parenthesised(), switch_block_());
+
+                            case "throw":
+                                return as("throw", prog1(expression, semicolon));
+
+                            case "try":
+                                return try_();
+
+                            case "var":
+                                return prog1(var_, semicolon);
+
+                            case "const":
+                                return prog1(const_, semicolon);
+
+                            case "while":
+                                return as("while", parenthesised(), in_loop(statement));
+
+                            case "with":
+                                return as("with", parenthesised(), statement());
+
+                            default:
+                                unexpected();
+                        }
+                }
+        };
+
+        function labeled_statement(label) {
+                S.labels.push(label);
+                var start = S.token, stat = statement();
+                if (exigent_mode && !HOP(STATEMENTS_WITH_LABELS, stat[0]))
+                        unexpected(start);
+                S.labels.pop();
+                return as("label", label, stat);
+        };
+
+        function simple_statement() {
+                return as("stat", prog1(expression, semicolon));
+        };
+
+        function break_cont(type) {
+                var name = is("name") ? S.token.value : null;
+                if (name != null) {
+                        next();
+                        if (!member(name, S.labels))
+                                croak("Label " + name + " without matching loop or statement");
+                }
+                else if (S.in_loop == 0)
+                        croak(type + " not inside a loop or switch");
+                semicolon();
+                return as(type, name);
+        };
+
+        function for_() {
+                expect("(");
+                var init = null;
+                if (!is("punc", ";")) {
+                        init = is("keyword", "var")
+                                ? (next(), var_(true))
+                                : expression(true, true);
+                        if (is("operator", "in"))
+                                return for_in(init);
+                }
+                return regular_for(init);
+        };
+
+        function regular_for(init) {
+                expect(";");
+                var test = is("punc", ";") ? null : expression();
+                expect(";");
+                var step = is("punc", ")") ? null : expression();
+                expect(")");
+                return as("for", init, test, step, in_loop(statement));
+        };
+
+        function for_in(init) {
+                var lhs = init[0] == "var" ? as("name", init[1][0]) : init;
+                next();
+                var obj = expression();
+                expect(")");
+                return as("for-in", init, lhs, obj, in_loop(statement));
+        };
+
+        var function_ = embed_tokens ? function() {
+                var start = prev();
+                var ast = $function_.apply(this, arguments);
+                ast[0] = add_tokens(ast[0], start, prev());
+                return ast;
+        } : $function_;
+
+        function $function_(in_statement) {
+                var name = is("name") ? prog1(S.token.value, next) : null;
+                if (in_statement && !name)
+                        unexpected();
+                expect("(");
+                return as(in_statement ? "defun" : "function",
+                          name,
+                          // arguments
+                          (function(first, a){
+                                  while (!is("punc", ")")) {
+                                          if (first) first = false; else expect(",");
+                                          if (!is("name")) unexpected();
+                                          a.push(S.token.value);
+                                          next();
+                                  }
+                                  next();
+                                  return a;
+                          })(true, []),
+                          // body
+                          (function(){
+                                  ++S.in_function;
+                                  var loop = S.in_loop;
+                                  S.in_loop = 0;
+                                  var a = block_();
+                                  --S.in_function;
+                                  S.in_loop = loop;
+                                  return a;
+                          })());
+        };
+
+        function if_() {
+                var cond = parenthesised(), body = statement(), belse;
+                if (is("keyword", "else")) {
+                        next();
+                        belse = statement();
+                }
+                return as("if", cond, body, belse);
+        };
+
+        function block_() {
+                expect("{");
+                var a = [];
+                while (!is("punc", "}")) {
+                        if (is("eof")) unexpected();
+                        a.push(statement());
+                }
+                next();
+                return a;
+        };
+
+        var switch_block_ = curry(in_loop, function(){
+                expect("{");
+                var a = [], cur = null;
+                while (!is("punc", "}")) {
+                        if (is("eof")) unexpected();
+                        if (is("keyword", "case")) {
+                                next();
+                                cur = [];
+                                a.push([ expression(), cur ]);
+                                expect(":");
+                        }
+                        else if (is("keyword", "default")) {
+                                next();
+                                expect(":");
+                                cur = [];
+                                a.push([ null, cur ]);
+                        }
+                        else {
+                                if (!cur) unexpected();
+                                cur.push(statement());
+                        }
+                }
+                next();
+                return a;
+        });
+
+        function try_() {
+                var body = block_(), bcatch, bfinally;
+                if (is("keyword", "catch")) {
+                        next();
+                        expect("(");
+                        if (!is("name"))
+                                croak("Name expected");
+                        var name = S.token.value;
+                        next();
+                        expect(")");
+                        bcatch = [ name, block_() ];
+                }
+                if (is("keyword", "finally")) {
+                        next();
+                        bfinally = block_();
+                }
+                if (!bcatch && !bfinally)
+                        croak("Missing catch/finally blocks");
+                return as("try", body, bcatch, bfinally);
+        };
+
+        function vardefs(no_in) {
+                var a = [];
+                for (;;) {
+                        if (!is("name"))
+                                unexpected();
+                        var name = S.token.value;
+                        next();
+                        if (is("operator", "=")) {
+                                next();
+                                a.push([ name, expression(false, no_in) ]);
+                        } else {
+                                a.push([ name ]);
+                        }
+                        if (!is("punc", ","))
+                                break;
+                        next();
+                }
+                return a;
+        };
+
+        function var_(no_in) {
+                return as("var", vardefs(no_in));
+        };
+
+        function const_() {
+                return as("const", vardefs());
+        };
+
+        function new_() {
+                var newexp = expr_atom(false), args;
+                if (is("punc", "(")) {
+                        next();
+                        args = expr_list(")");
+                } else {
+                        args = [];
+                }
+                return subscripts(as("new", newexp, args), true);
+        };
+
+        function expr_atom(allow_calls) {
+                if (is("operator", "new")) {
+                        next();
+                        return new_();
+                }
+                if (is("operator") && HOP(UNARY_PREFIX, S.token.value)) {
+                        return make_unary("unary-prefix",
+                                          prog1(S.token.value, next),
+                                          expr_atom(allow_calls));
+                }
+                if (is("punc")) {
+                        switch (S.token.value) {
+                            case "(":
+                                next();
+                                return subscripts(prog1(expression, curry(expect, ")")), allow_calls);
+                            case "[":
+                                next();
+                                return subscripts(array_(), allow_calls);
+                            case "{":
+                                next();
+                                return subscripts(object_(), allow_calls);
+                        }
+                        unexpected();
+                }
+                if (is("keyword", "function")) {
+                        next();
+                        return subscripts(function_(false), allow_calls);
+                }
+                if (HOP(ATOMIC_START_TOKEN, S.token.type)) {
+                        var atom = S.token.type == "regexp"
+                                ? as("regexp", S.token.value[0], S.token.value[1])
+                                : as(S.token.type, S.token.value);
+                        return subscripts(prog1(atom, next), allow_calls);
+                }
+                unexpected();
+        };
+
+        function expr_list(closing, allow_trailing_comma, allow_empty) {
+                var first = true, a = [];
+                while (!is("punc", closing)) {
+                        if (first) first = false; else expect(",");
+                        if (allow_trailing_comma && is("punc", closing)) break;
+                        if (is("punc", ",") && allow_empty) {
+                                a.push([ "atom", "undefined" ]);
+                        } else {
+                                a.push(expression(false));
+                        }
+                }
+                next();
+                return a;
+        };
+
+        function array_() {
+                return as("array", expr_list("]", !exigent_mode, true));
+        };
+
+        function object_() {
+                var first = true, a = [];
+                while (!is("punc", "}")) {
+                        if (first) first = false; else expect(",");
+                        if (!exigent_mode && is("punc", "}"))
+                                // allow trailing comma
+                                break;
+                        var type = S.token.type;
+                        var name = as_property_name();
+                        if (type == "name" && (name == "get" || name == "set") && !is("punc", ":")) {
+                                a.push([ as_name(), function_(false), name ]);
+                        } else {
+                                expect(":");
+                                a.push([ name, expression(false) ]);
+                        }
+                }
+                next();
+                return as("object", a);
+        };
+
+        function as_property_name() {
+                switch (S.token.type) {
+                    case "num":
+                    case "string":
+                        return prog1(S.token.value, next);
+                }
+                return as_name();
+        };
+
+        function as_name() {
+                switch (S.token.type) {
+                    case "name":
+                    case "operator":
+                    case "keyword":
+                    case "atom":
+                        return prog1(S.token.value, next);
+                    default:
+                        unexpected();
+                }
+        };
+
+        function subscripts(expr, allow_calls) {
+                if (is("punc", ".")) {
+                        next();
+                        return subscripts(as("dot", expr, as_name()), allow_calls);
+                }
+                if (is("punc", "[")) {
+                        next();
+                        return subscripts(as("sub", expr, prog1(expression, curry(expect, "]"))), allow_calls);
+                }
+                if (allow_calls && is("punc", "(")) {
+                        next();
+                        return subscripts(as("call", expr, expr_list(")")), true);
+                }
+                if (allow_calls && is("operator") && HOP(UNARY_POSTFIX, S.token.value)) {
+                        return prog1(curry(make_unary, "unary-postfix", S.token.value, expr),
+                                     next);
+                }
+                return expr;
+        };
+
+        function make_unary(tag, op, expr) {
+                if ((op == "++" || op == "--") && !is_assignable(expr))
+                        croak("Invalid use of " + op + " operator");
+                return as(tag, op, expr);
+        };
+
+        function expr_op(left, min_prec, no_in) {
+                var op = is("operator") ? S.token.value : null;
+                if (op && op == "in" && no_in) op = null;
+                var prec = op != null ? PRECEDENCE[op] : null;
+                if (prec != null && prec > min_prec) {
+                        next();
+                        var right = expr_op(expr_atom(true), prec, no_in);
+                        return expr_op(as("binary", op, left, right), min_prec, no_in);
+                }
+                return left;
+        };
+
+        function expr_ops(no_in) {
+                return expr_op(expr_atom(true), 0, no_in);
+        };
+
+        function maybe_conditional(no_in) {
+                var expr = expr_ops(no_in);
+                if (is("operator", "?")) {
+                        next();
+                        var yes = expression(false);
+                        expect(":");
+                        return as("conditional", expr, yes, expression(false, no_in));
+                }
+                return expr;
+        };
+
+        function is_assignable(expr) {
+                if (!exigent_mode) return true;
+                switch (expr[0]) {
+                    case "dot":
+                    case "sub":
+                    case "new":
+                    case "call":
+                        return true;
+                    case "name":
+                        return expr[1] != "this";
+                }
+        };
+
+        function maybe_assign(no_in) {
+                var left = maybe_conditional(no_in), val = S.token.value;
+                if (is("operator") && HOP(ASSIGNMENT, val)) {
+                        if (is_assignable(left)) {
+                                next();
+                                return as("assign", ASSIGNMENT[val], left, maybe_assign(no_in));
+                        }
+                        croak("Invalid assignment");
+                }
+                return left;
+        };
+
+        function expression(commas, no_in) {
+                if (arguments.length == 0)
+                        commas = true;
+                var expr = maybe_assign(no_in);
+                if (commas && is("punc", ",")) {
+                        next();
+                        return as("seq", expr, expression(true, no_in));
+                }
+                return expr;
+        };
+
+        function in_loop(cont) {
+                try {
+                        ++S.in_loop;
+                        return cont();
+                } finally {
+                        --S.in_loop;
+                }
+        };
+
+        return as("toplevel", (function(a){
+                while (!is("eof"))
+                        a.push(statement());
+                return a;
+        })([]));
+
+};
+
+/* -----[ Utilities ]----- */
+
+function curry(f) {
+        var args = slice(arguments, 1);
+        return function() { return f.apply(this, args.concat(slice(arguments))); };
+};
+
+function prog1(ret) {
+        if (ret instanceof Function)
+                ret = ret();
+        for (var i = 1, n = arguments.length; --n > 0; ++i)
+                arguments[i]();
+        return ret;
+};
+
+function array_to_hash(a) {
+        var ret = {};
+        for (var i = 0; i < a.length; ++i)
+                ret[a[i]] = true;
+        return ret;
+};
+
+function slice(a, start) {
+        return Array.prototype.slice.call(a, start == null ? 0 : start);
+};
+
+function characters(str) {
+        return str.split("");
+};
+
+function member(name, array) {
+        for (var i = array.length; --i >= 0;)
+                if (array[i] === name)
+                        return true;
+        return false;
+};
+
+function HOP(obj, prop) {
+        return Object.prototype.hasOwnProperty.call(obj, prop);
+};
+
+var warn = function() {};
+
+/* -----[ Exports ]----- */
+
+exports.tokenizer = tokenizer;
+exports.parse = parse;
+exports.slice = slice;
+exports.curry = curry;
+exports.member = member;
+exports.array_to_hash = array_to_hash;
+exports.PRECEDENCE = PRECEDENCE;
+exports.KEYWORDS_ATOM = KEYWORDS_ATOM;
+exports.RESERVED_WORDS = RESERVED_WORDS;
+exports.KEYWORDS = KEYWORDS;
+exports.ATOMIC_START_TOKEN = ATOMIC_START_TOKEN;
+exports.OPERATORS = OPERATORS;
+exports.is_alphanumeric_char = is_alphanumeric_char;
+exports.set_logger = function(logger) {
+        warn = logger;
+};
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/lib/process.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/lib/process.js
new file mode 100644
index 0000000..09cbc2a
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/lib/process.js
@@ -0,0 +1,1616 @@
+/***********************************************************************
+
+  A JavaScript tokenizer / parser / beautifier / compressor.
+
+  This version is suitable for Node.js.  With minimal changes (the
+  exports stuff) it should work on any JS platform.
+
+  This file implements some AST processors.  They work on data built
+  by parse-js.
+
+  Exported functions:
+
+    - ast_mangle(ast, options) -- mangles the variable/function names
+      in the AST.  Returns an AST.
+
+    - ast_squeeze(ast) -- employs various optimizations to make the
+      final generated code even smaller.  Returns an AST.
+
+    - gen_code(ast, options) -- generates JS code from the AST.  Pass
+      true (or an object, see the code for some options) as second
+      argument to get "pretty" (indented) code.
+
+  -------------------------------- (C) ---------------------------------
+
+                           Author: Mihai Bazon
+                         <mihai.bazon@gmail.com>
+                       http://mihai.bazon.net/blog
+
+  Distributed under the BSD license:
+
+    Copyright 2010 (c) Mihai Bazon <mihai.bazon@gmail.com>
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions
+    are met:
+
+        * Redistributions of source code must retain the above
+          copyright notice, this list of conditions and the following
+          disclaimer.
+
+        * Redistributions in binary form must reproduce the above
+          copyright notice, this list of conditions and the following
+          disclaimer in the documentation and/or other materials
+          provided with the distribution.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
+    EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+    PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
+    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+    OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+    TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+    THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+    SUCH DAMAGE.
+
+ ***********************************************************************/
+
+var jsp = require("./parse-js"),
+    slice = jsp.slice,
+    member = jsp.member,
+    PRECEDENCE = jsp.PRECEDENCE,
+    OPERATORS = jsp.OPERATORS;
+
+/* -----[ helper for AST traversal ]----- */
+
+function ast_walker(ast) {
+        function _vardefs(defs) {
+                return [ this[0], MAP(defs, function(def){
+                        var a = [ def[0] ];
+                        if (def.length > 1)
+                                a[1] = walk(def[1]);
+                        return a;
+                }) ];
+        };
+        var walkers = {
+                "string": function(str) {
+                        return [ this[0], str ];
+                },
+                "num": function(num) {
+                        return [ this[0], num ];
+                },
+                "name": function(name) {
+                        return [ this[0], name ];
+                },
+                "toplevel": function(statements) {
+                        return [ this[0], MAP(statements, walk) ];
+                },
+                "block": function(statements) {
+                        var out = [ this[0] ];
+                        if (statements != null)
+                                out.push(MAP(statements, walk));
+                        return out;
+                },
+                "var": _vardefs,
+                "const": _vardefs,
+                "try": function(t, c, f) {
+                        return [
+                                this[0],
+                                MAP(t, walk),
+                                c != null ? [ c[0], MAP(c[1], walk) ] : null,
+                                f != null ? MAP(f, walk) : null
+                        ];
+                },
+                "throw": function(expr) {
+                        return [ this[0], walk(expr) ];
+                },
+                "new": function(ctor, args) {
+                        return [ this[0], walk(ctor), MAP(args, walk) ];
+                },
+                "switch": function(expr, body) {
+                        return [ this[0], walk(expr), MAP(body, function(branch){
+                                return [ branch[0] ? walk(branch[0]) : null,
+                                         MAP(branch[1], walk) ];
+                        }) ];
+                },
+                "break": function(label) {
+                        return [ this[0], label ];
+                },
+                "continue": function(label) {
+                        return [ this[0], label ];
+                },
+                "conditional": function(cond, t, e) {
+                        return [ this[0], walk(cond), walk(t), walk(e) ];
+                },
+                "assign": function(op, lvalue, rvalue) {
+                        return [ this[0], op, walk(lvalue), walk(rvalue) ];
+                },
+                "dot": function(expr) {
+                        return [ this[0], walk(expr) ].concat(slice(arguments, 1));
+                },
+                "call": function(expr, args) {
+                        return [ this[0], walk(expr), MAP(args, walk) ];
+                },
+                "function": function(name, args, body) {
+                        return [ this[0], name, args.slice(), MAP(body, walk) ];
+                },
+                "defun": function(name, args, body) {
+                        return [ this[0], name, args.slice(), MAP(body, walk) ];
+                },
+                "if": function(conditional, t, e) {
+                        return [ this[0], walk(conditional), walk(t), walk(e) ];
+                },
+                "for": function(init, cond, step, block) {
+                        return [ this[0], walk(init), walk(cond), walk(step), walk(block) ];
+                },
+                "for-in": function(vvar, key, hash, block) {
+                        return [ this[0], walk(vvar), walk(key), walk(hash), walk(block) ];
+                },
+                "while": function(cond, block) {
+                        return [ this[0], walk(cond), walk(block) ];
+                },
+                "do": function(cond, block) {
+                        return [ this[0], walk(cond), walk(block) ];
+                },
+                "return": function(expr) {
+                        return [ this[0], walk(expr) ];
+                },
+                "binary": function(op, left, right) {
+                        return [ this[0], op, walk(left), walk(right) ];
+                },
+                "unary-prefix": function(op, expr) {
+                        return [ this[0], op, walk(expr) ];
+                },
+                "unary-postfix": function(op, expr) {
+                        return [ this[0], op, walk(expr) ];
+                },
+                "sub": function(expr, subscript) {
+                        return [ this[0], walk(expr), walk(subscript) ];
+                },
+                "object": function(props) {
+                        return [ this[0], MAP(props, function(p){
+                                return p.length == 2
+                                        ? [ p[0], walk(p[1]) ]
+                                        : [ p[0], walk(p[1]), p[2] ]; // get/set-ter
+                        }) ];
+                },
+                "regexp": function(rx, mods) {
+                        return [ this[0], rx, mods ];
+                },
+                "array": function(elements) {
+                        return [ this[0], MAP(elements, walk) ];
+                },
+                "stat": function(stat) {
+                        return [ this[0], walk(stat) ];
+                },
+                "seq": function() {
+                        return [ this[0] ].concat(MAP(slice(arguments), walk));
+                },
+                "label": function(name, block) {
+                        return [ this[0], name, walk(block) ];
+                },
+                "with": function(expr, block) {
+                        return [ this[0], walk(expr), walk(block) ];
+                },
+                "atom": function(name) {
+                        return [ this[0], name ];
+                }
+        };
+
+        var user = {};
+        var stack = [];
+        function walk(ast) {
+                if (ast == null)
+                        return null;
+                try {
+                        stack.push(ast);
+                        var type = ast[0];
+                        var gen = user[type];
+                        if (gen) {
+                                var ret = gen.apply(ast, ast.slice(1));
+                                if (ret != null)
+                                        return ret;
+                        }
+                        gen = walkers[type];
+                        return gen.apply(ast, ast.slice(1));
+                } finally {
+                        stack.pop();
+                }
+        };
+
+        function with_walkers(walkers, cont){
+                var save = {}, i;
+                for (i in walkers) if (HOP(walkers, i)) {
+                        save[i] = user[i];
+                        user[i] = walkers[i];
+                }
+                var ret = cont();
+                for (i in save) if (HOP(save, i)) {
+                        if (!save[i]) delete user[i];
+                        else user[i] = save[i];
+                }
+                return ret;
+        };
+
+        return {
+                walk: walk,
+                with_walkers: with_walkers,
+                parent: function() {
+                        return stack[stack.length - 2]; // last one is current node
+                },
+                stack: function() {
+                        return stack;
+                }
+        };
+};
+
+/* -----[ Scope and mangling ]----- */
+
+function Scope(parent) {
+        this.names = {};        // names defined in this scope
+        this.mangled = {};      // mangled names (orig.name => mangled)
+        this.rev_mangled = {};  // reverse lookup (mangled => orig.name)
+        this.cname = -1;        // current mangled name
+        this.refs = {};         // names referenced from this scope
+        this.uses_with = false; // will become TRUE if with() is detected in this or any subscopes
+        this.uses_eval = false; // will become TRUE if eval() is detected in this or any subscopes
+        this.parent = parent;   // parent scope
+        this.children = [];     // sub-scopes
+        if (parent) {
+                this.level = parent.level + 1;
+                parent.children.push(this);
+        } else {
+                this.level = 0;
+        }
+};
+
+var base54 = (function(){
+        var DIGITS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_";
+        return function(num) {
+                var ret = "";
+                do {
+                        ret = DIGITS.charAt(num % 54) + ret;
+                        num = Math.floor(num / 54);
+                } while (num > 0);
+                return ret;
+        };
+})();
+
+Scope.prototype = {
+        has: function(name) {
+                for (var s = this; s; s = s.parent)
+                        if (HOP(s.names, name))
+                                return s;
+        },
+        has_mangled: function(mname) {
+                for (var s = this; s; s = s.parent)
+                        if (HOP(s.rev_mangled, mname))
+                                return s;
+        },
+        toJSON: function() {
+                return {
+                        names: this.names,
+                        uses_eval: this.uses_eval,
+                        uses_with: this.uses_with
+                };
+        },
+
+        next_mangled: function() {
+                // we must be careful that the new mangled name:
+                //
+                // 1. doesn't shadow a mangled name from a parent
+                //    scope, unless we don't reference the original
+                //    name from this scope OR from any sub-scopes!
+                //    This will get slow.
+                //
+                // 2. doesn't shadow an original name from a parent
+                //    scope, in the event that the name is not mangled
+                //    in the parent scope and we reference that name
+                //    here OR IN ANY SUBSCOPES!
+                //
+                // 3. doesn't shadow a name that is referenced but not
+                //    defined (possibly global defined elsewhere).
+                for (;;) {
+                        var m = base54(++this.cname), prior;
+
+                        // case 1.
+                        prior = this.has_mangled(m);
+                        if (prior && this.refs[prior.rev_mangled[m]] === prior)
+                                continue;
+
+                        // case 2.
+                        prior = this.has(m);
+                        if (prior && prior !== this && this.refs[m] === prior && !prior.has_mangled(m))
+                                continue;
+
+                        // case 3.
+                        if (HOP(this.refs, m) && this.refs[m] == null)
+                                continue;
+
+                        // I got "do" once. :-/
+                        if (!is_identifier(m))
+                                continue;
+
+                        return m;
+                }
+        },
+        get_mangled: function(name, newMangle) {
+                if (this.uses_eval || this.uses_with) return name; // no mangle if eval or with is in use
+                var s = this.has(name);
+                if (!s) return name; // not in visible scope, no mangle
+                if (HOP(s.mangled, name)) return s.mangled[name]; // already mangled in this scope
+                if (!newMangle) return name;                      // not found and no mangling requested
+
+                var m = s.next_mangled();
+                s.rev_mangled[m] = name;
+                return s.mangled[name] = m;
+        },
+        define: function(name) {
+                if (name != null)
+                        return this.names[name] = name;
+        }
+};
+
+function ast_add_scope(ast) {
+
+        var current_scope = null;
+        var w = ast_walker(), walk = w.walk;
+        var having_eval = [];
+
+        function with_new_scope(cont) {
+                current_scope = new Scope(current_scope);
+                var ret = current_scope.body = cont();
+                ret.scope = current_scope;
+                current_scope = current_scope.parent;
+                return ret;
+        };
+
+        function define(name) {
+                return current_scope.define(name);
+        };
+
+        function reference(name) {
+                current_scope.refs[name] = true;
+        };
+
+        function _lambda(name, args, body) {
+                return [ this[0], this[0] == "defun" ? define(name) : name, args, with_new_scope(function(){
+                        MAP(args, define);
+                        return MAP(body, walk);
+                })];
+        };
+
+        return with_new_scope(function(){
+                // process AST
+                var ret = w.with_walkers({
+                        "function": _lambda,
+                        "defun": _lambda,
+                        "with": function(expr, block) {
+                                for (var s = current_scope; s; s = s.parent)
+                                        s.uses_with = true;
+                        },
+                        "var": function(defs) {
+                                MAP(defs, function(d){ define(d[0]) });
+                        },
+                        "const": function(defs) {
+                                MAP(defs, function(d){ define(d[0]) });
+                        },
+                        "try": function(t, c, f) {
+                                if (c != null) return [
+                                        this[0],
+                                        MAP(t, walk),
+                                        [ define(c[0]), MAP(c[1], walk) ],
+                                        f != null ? MAP(f, walk) : null
+                                ];
+                        },
+                        "name": function(name) {
+                                if (name == "eval")
+                                        having_eval.push(current_scope);
+                                reference(name);
+                        }
+                }, function(){
+                        return walk(ast);
+                });
+
+                // the reason why we need an additional pass here is
+                // that names can be used prior to their definition.
+
+                // scopes where eval was detected and their parents
+                // are marked with uses_eval, unless they define the
+                // "eval" name.
+                MAP(having_eval, function(scope){
+                        if (!scope.has("eval")) while (scope) {
+                                scope.uses_eval = true;
+                                scope = scope.parent;
+                        }
+                });
+
+                // for referenced names it might be useful to know
+                // their origin scope.  current_scope here is the
+                // toplevel one.
+                function fixrefs(scope, i) {
+                        // do children first; order shouldn't matter
+                        for (i = scope.children.length; --i >= 0;)
+                                fixrefs(scope.children[i]);
+                        for (i in scope.refs) if (HOP(scope.refs, i)) {
+                                // find origin scope and propagate the reference to origin
+                                for (var origin = scope.has(i), s = scope; s; s = s.parent) {
+                                        s.refs[i] = origin;
+                                        if (s === origin) break;
+                                }
+                        }
+                };
+                fixrefs(current_scope);
+
+                return ret;
+        });
+
+};
+
+/* -----[ mangle names ]----- */
+
+function ast_mangle(ast, options) {
+        var w = ast_walker(), walk = w.walk, scope;
+        options = options || {};
+
+        function get_mangled(name, newMangle) {
+                if (!options.toplevel && !scope.parent) return name; // don't mangle toplevel
+                if (options.except && member(name, options.except))
+                        return name;
+                return scope.get_mangled(name, newMangle);
+        };
+
+        function _lambda(name, args, body) {
+                if (name) name = get_mangled(name);
+                body = with_scope(body.scope, function(){
+                        args = MAP(args, function(name){ return get_mangled(name) });
+                        return MAP(body, walk);
+                });
+                return [ this[0], name, args, body ];
+        };
+
+        function with_scope(s, cont) {
+                var _scope = scope;
+                scope = s;
+                for (var i in s.names) if (HOP(s.names, i)) {
+                        get_mangled(i, true);
+                }
+                var ret = cont();
+                ret.scope = s;
+                scope = _scope;
+                return ret;
+        };
+
+        function _vardefs(defs) {
+                return [ this[0], MAP(defs, function(d){
+                        return [ get_mangled(d[0]), walk(d[1]) ];
+                }) ];
+        };
+
+        return w.with_walkers({
+                "function": _lambda,
+                "defun": function() {
+                        // move function declarations to the top when
+                        // they are not in some block.
+                        var ast = _lambda.apply(this, arguments);
+                        switch (w.parent()[0]) {
+                            case "toplevel":
+                            case "function":
+                            case "defun":
+                                return MAP.at_top(ast);
+                        }
+                        return ast;
+                },
+                "var": _vardefs,
+                "const": _vardefs,
+                "name": function(name) {
+                        return [ this[0], get_mangled(name) ];
+                },
+                "try": function(t, c, f) {
+                        return [ this[0],
+                                 MAP(t, walk),
+                                 c != null ? [ get_mangled(c[0]), MAP(c[1], walk) ] : null,
+                                 f != null ? MAP(f, walk) : null ];
+                },
+                "toplevel": function(body) {
+                        var self = this;
+                        return with_scope(self.scope, function(){
+                                return [ self[0], MAP(body, walk) ];
+                        });
+                }
+        }, function() {
+                return walk(ast_add_scope(ast));
+        });
+};
+
+/* -----[
+   - compress foo["bar"] into foo.bar,
+   - remove block brackets {} where possible
+   - join consecutive var declarations
+   - various optimizations for IFs:
+     - if (cond) foo(); else bar();  ==>  cond?foo():bar();
+     - if (cond) foo();  ==>  cond&&foo();
+     - if (foo) return bar(); else return baz();  ==> return foo?bar():baz(); // also for throw
+     - if (foo) return bar(); else something();  ==> {if(foo)return bar();something()}
+   ]----- */
+
+var warn = function(){};
+
+function best_of(ast1, ast2) {
+        return gen_code(ast1).length > gen_code(ast2[0] == "stat" ? ast2[1] : ast2).length ? ast2 : ast1;
+};
+
+function last_stat(b) {
+        if (b[0] == "block" && b[1] && b[1].length > 0)
+                return b[1][b[1].length - 1];
+        return b;
+}
+
+function aborts(t) {
+        if (t) {
+                t = last_stat(t);
+                if (t[0] == "return" || t[0] == "break" || t[0] == "continue" || t[0] == "throw")
+                        return true;
+        }
+};
+
+function boolean_expr(expr) {
+        return ( (expr[0] == "unary-prefix"
+                  && member(expr[1], [ "!", "delete" ])) ||
+
+                 (expr[0] == "binary"
+                  && member(expr[1], [ "in", "instanceof", "==", "!=", "===", "!==", "<", "<=", ">=", ">" ])) ||
+
+                 (expr[0] == "binary"
+                  && member(expr[1], [ "&&", "||" ])
+                  && boolean_expr(expr[2])
+                  && boolean_expr(expr[3])) ||
+
+                 (expr[0] == "conditional"
+                  && boolean_expr(expr[2])
+                  && boolean_expr(expr[3])) ||
+
+                 (expr[0] == "assign"
+                  && expr[1] === true
+                  && boolean_expr(expr[3])) ||
+
+                 (expr[0] == "seq"
+                  && boolean_expr(expr[expr.length - 1]))
+               );
+};
+
+function make_conditional(c, t, e) {
+        if (c[0] == "unary-prefix" && c[1] == "!") {
+                return e ? [ "conditional", c[2], e, t ] : [ "binary", "||", c[2], t ];
+        } else {
+                return e ? [ "conditional", c, t, e ] : [ "binary", "&&", c, t ];
+        }
+};
+
+function empty(b) {
+        return !b || (b[0] == "block" && (!b[1] || b[1].length == 0));
+};
+
+function is_string(node) {
+        return (node[0] == "string" ||
+                node[0] == "unary-prefix" && node[1] == "typeof" ||
+                node[0] == "binary" && node[1] == "+" &&
+                (is_string(node[2]) || is_string(node[3])));
+};
+
+var when_constant = (function(){
+
+        var $NOT_CONSTANT = {};
+
+        // this can only evaluate constant expressions.  If it finds anything
+        // not constant, it throws $NOT_CONSTANT.
+        function evaluate(expr) {
+                switch (expr[0]) {
+                    case "string":
+                    case "num":
+                        return expr[1];
+                    case "name":
+                    case "atom":
+                        switch (expr[1]) {
+                            case "true": return true;
+                            case "false": return false;
+                        }
+                        break;
+                    case "unary-prefix":
+                        switch (expr[1]) {
+                            case "!": return !evaluate(expr[2]);
+                            case "typeof": return typeof evaluate(expr[2]);
+                            case "~": return ~evaluate(expr[2]);
+                            case "-": return -evaluate(expr[2]);
+                            case "+": return +evaluate(expr[2]);
+                        }
+                        break;
+                    case "binary":
+                        var left = expr[2], right = expr[3];
+                        switch (expr[1]) {
+                            case "&&"         : return evaluate(left) &&         evaluate(right);
+                            case "||"         : return evaluate(left) ||         evaluate(right);
+                            case "|"          : return evaluate(left) |          evaluate(right);
+                            case "&"          : return evaluate(left) &          evaluate(right);
+                            case "^"          : return evaluate(left) ^          evaluate(right);
+                            case "+"          : return evaluate(left) +          evaluate(right);
+                            case "*"          : return evaluate(left) *          evaluate(right);
+                            case "/"          : return evaluate(left) /          evaluate(right);
+                            case "-"          : return evaluate(left) -          evaluate(right);
+                            case "<<"         : return evaluate(left) <<         evaluate(right);
+                            case ">>"         : return evaluate(left) >>         evaluate(right);
+                            case ">>>"        : return evaluate(left) >>>        evaluate(right);
+                            case "=="         : return evaluate(left) ==         evaluate(right);
+                            case "==="        : return evaluate(left) ===        evaluate(right);
+                            case "!="         : return evaluate(left) !=         evaluate(right);
+                            case "!=="        : return evaluate(left) !==        evaluate(right);
+                            case "<"          : return evaluate(left) <          evaluate(right);
+                            case "<="         : return evaluate(left) <=         evaluate(right);
+                            case ">"          : return evaluate(left) >          evaluate(right);
+                            case ">="         : return evaluate(left) >=         evaluate(right);
+                            case "in"         : return evaluate(left) in         evaluate(right);
+                            case "instanceof" : return evaluate(left) instanceof evaluate(right);
+                        }
+                }
+                throw $NOT_CONSTANT;
+        };
+
+        return function(expr, yes, no) {
+                try {
+                        var val = evaluate(expr), ast;
+                        switch (typeof val) {
+                            case "string": ast =  [ "string", val ]; break;
+                            case "number": ast =  [ "num", val ]; break;
+                            case "boolean": ast =  [ "name", String(val) ]; break;
+                            default: throw new Error("Can't handle constant of type: " + (typeof val));
+                        }
+                        return yes.call(expr, ast, val);
+                } catch(ex) {
+                        if (ex === $NOT_CONSTANT) {
+                                if (expr[0] == "binary"
+                                    && (expr[1] == "===" || expr[1] == "!==")
+                                    && ((is_string(expr[2]) && is_string(expr[3]))
+                                        || (boolean_expr(expr[2]) && boolean_expr(expr[3])))) {
+                                        expr[1] = expr[1].substr(0, 2);
+                                }
+                                return no ? no.call(expr, expr) : null;
+                        }
+                        else throw ex;
+                }
+        };
+
+})();
+
+function warn_unreachable(ast) {
+        if (!empty(ast))
+                warn("Dropping unreachable code: " + gen_code(ast, true));
+};
+
+function ast_squeeze(ast, options) {
+        options = defaults(options, {
+                make_seqs   : true,
+                dead_code   : true,
+                keep_comps  : true,
+                no_warnings : false
+        });
+
+        var w = ast_walker(), walk = w.walk, scope;
+
+        function negate(c) {
+                var not_c = [ "unary-prefix", "!", c ];
+                switch (c[0]) {
+                    case "unary-prefix":
+                        return c[1] == "!" && boolean_expr(c[2]) ? c[2] : not_c;
+                    case "seq":
+                        c = slice(c);
+                        c[c.length - 1] = negate(c[c.length - 1]);
+                        return c;
+                    case "conditional":
+                        return best_of(not_c, [ "conditional", c[1], negate(c[2]), negate(c[3]) ]);
+                    case "binary":
+                        var op = c[1], left = c[2], right = c[3];
+                        if (!options.keep_comps) switch (op) {
+                            case "<="  : return [ "binary", ">", left, right ];
+                            case "<"   : return [ "binary", ">=", left, right ];
+                            case ">="  : return [ "binary", "<", left, right ];
+                            case ">"   : return [ "binary", "<=", left, right ];
+                        }
+                        switch (op) {
+                            case "=="  : return [ "binary", "!=", left, right ];
+                            case "!="  : return [ "binary", "==", left, right ];
+                            case "===" : return [ "binary", "!==", left, right ];
+                            case "!==" : return [ "binary", "===", left, right ];
+                            case "&&"  : return best_of(not_c, [ "binary", "||", negate(left), negate(right) ]);
+                            case "||"  : return best_of(not_c, [ "binary", "&&", negate(left), negate(right) ]);
+                        }
+                        break;
+                }
+                return not_c;
+        };
+
+        function with_scope(s, cont) {
+                var _scope = scope;
+                scope = s;
+                var ret = cont();
+                ret.scope = s;
+                scope = _scope;
+                return ret;
+        };
+
+        function rmblock(block) {
+                if (block != null && block[0] == "block" && block[1]) {
+                        if (block[1].length == 1)
+                                block = block[1][0];
+                        else if (block[1].length == 0)
+                                block = [ "block" ];
+                }
+                return block;
+        };
+
+        function _lambda(name, args, body) {
+                return [ this[0], name, args, with_scope(body.scope, function(){
+                        return tighten(MAP(body, walk), "lambda");
+                }) ];
+        };
+
+        // we get here for blocks that have been already transformed.
+        // this function does a few things:
+        // 1. discard useless blocks
+        // 2. join consecutive var declarations
+        // 3. remove obviously dead code
+        // 4. transform consecutive statements using the comma operator
+        // 5. if block_type == "lambda" and it detects constructs like if(foo) return ... - rewrite like if (!foo) { ... }
+        function tighten(statements, block_type) {
+                statements = statements.reduce(function(a, stat){
+                        if (stat[0] == "block") {
+                                if (stat[1]) {
+                                        a.push.apply(a, stat[1]);
+                                }
+                        } else {
+                                a.push(stat);
+                        }
+                        return a;
+                }, []);
+
+                statements = (function(a, prev){
+                        statements.forEach(function(cur){
+                                if (prev && ((cur[0] == "var" && prev[0] == "var") ||
+                                             (cur[0] == "const" && prev[0] == "const"))) {
+                                        prev[1] = prev[1].concat(cur[1]);
+                                } else {
+                                        a.push(cur);
+                                        prev = cur;
+                                }
+                        });
+                        return a;
+                })([]);
+
+                if (options.dead_code) statements = (function(a, has_quit){
+                        statements.forEach(function(st){
+                                if (has_quit) {
+                                        if (member(st[0], [ "function", "defun" , "var", "const" ])) {
+                                                a.push(st);
+                                        }
+                                        else if (!options.no_warnings)
+                                                warn_unreachable(st);
+                                }
+                                else {
+                                        a.push(st);
+                                        if (member(st[0], [ "return", "throw", "break", "continue" ]))
+                                                has_quit = true;
+                                }
+                        });
+                        return a;
+                })([]);
+
+                if (options.make_seqs) statements = (function(a, prev) {
+                        statements.forEach(function(cur){
+                                if (prev && prev[0] == "stat" && cur[0] == "stat") {
+                                        prev[1] = [ "seq", prev[1], cur[1] ];
+                                } else {
+                                        a.push(cur);
+                                        prev = cur;
+                                }
+                        });
+                        return a;
+                })([]);
+
+                if (block_type == "lambda") statements = (function(i, a, stat){
+                        while (i < statements.length) {
+                                stat = statements[i++];
+                                if (stat[0] == "if" && !stat[3]) {
+                                        if (stat[2][0] == "return" && stat[2][1] == null) {
+                                                a.push(make_if(negate(stat[1]), [ "block", statements.slice(i) ]));
+                                                break;
+                                        }
+                                        var last = last_stat(stat[2]);
+                                        if (last[0] == "return" && last[1] == null) {
+                                                a.push(make_if(stat[1], [ "block", stat[2][1].slice(0, -1) ], [ "block", statements.slice(i) ]));
+                                                break;
+                                        }
+                                }
+                                a.push(stat);
+                        }
+                        return a;
+                })(0, []);
+
+                return statements;
+        };
+
+        function make_if(c, t, e) {
+                return when_constant(c, function(ast, val){
+                        if (val) {
+                                warn_unreachable(e);
+                                return t;
+                        } else {
+                                warn_unreachable(t);
+                                return e;
+                        }
+                }, function() {
+                        return make_real_if(c, t, e);
+                });
+        };
+
+        function make_real_if(c, t, e) {
+                c = walk(c);
+                t = walk(t);
+                e = walk(e);
+
+                if (empty(t)) {
+                        c = negate(c);
+                        t = e;
+                        e = null;
+                } else if (empty(e)) {
+                        e = null;
+                } else {
+                        // if we have both else and then, maybe it makes sense to switch them?
+                        (function(){
+                                var a = gen_code(c);
+                                var n = negate(c);
+                                var b = gen_code(n);
+                                if (b.length < a.length) {
+                                        var tmp = t;
+                                        t = e;
+                                        e = tmp;
+                                        c = n;
+                                }
+                        })();
+                }
+                if (empty(e) && empty(t))
+                        return [ "stat", c ];
+                var ret = [ "if", c, t, e ];
+                if (t[0] == "if" && empty(t[3]) && empty(e)) {
+                        ret = best_of(ret, walk([ "if", [ "binary", "&&", c, t[1] ], t[2] ]));
+                }
+                else if (t[0] == "stat") {
+                        if (e) {
+                                if (e[0] == "stat") {
+                                        ret = best_of(ret, [ "stat", make_conditional(c, t[1], e[1]) ]);
+                                }
+                        }
+                        else {
+                                ret = best_of(ret, [ "stat", make_conditional(c, t[1]) ]);
+                        }
+                }
+                else if (e && t[0] == e[0] && (t[0] == "return" || t[0] == "throw") && t[1] && e[1]) {
+                        ret = best_of(ret, [ t[0], make_conditional(c, t[1], e[1] ) ]);
+                }
+                else if (e && aborts(t)) {
+                        ret = [ [ "if", c, t ] ];
+                        if (e[0] == "block") {
+                                if (e[1]) ret = ret.concat(e[1]);
+                        }
+                        else {
+                                ret.push(e);
+                        }
+                        ret = walk([ "block", ret ]);
+                }
+                else if (t && aborts(e)) {
+                        ret = [ [ "if", negate(c), e ] ];
+                        if (t[0] == "block") {
+                                if (t[1]) ret = ret.concat(t[1]);
+                        } else {
+                                ret.push(t);
+                        }
+                        ret = walk([ "block", ret ]);
+                }
+                return ret;
+        };
+
+        function _do_while(cond, body) {
+                return when_constant(cond, function(cond, val){
+                        if (!val) {
+                                warn_unreachable(body);
+                                return [ "block" ];
+                        } else {
+                                return [ "for", null, null, null, walk(body) ];
+                        }
+                });
+        };
+
+        return w.with_walkers({
+                "sub": function(expr, subscript) {
+                        if (subscript[0] == "string") {
+                                var name = subscript[1];
+                                if (is_identifier(name))
+                                        return [ "dot", walk(expr), name ];
+                                else if (/^[1-9][0-9]*$/.test(name) || name === "0")
+                                        return [ "sub", walk(expr), [ "num", parseInt(name, 10) ] ];
+                        }
+                },
+                "if": make_if,
+                "toplevel": function(body) {
+                        return [ "toplevel", with_scope(this.scope, function(){
+                                return tighten(MAP(body, walk));
+                        }) ];
+                },
+                "switch": function(expr, body) {
+                        var last = body.length - 1;
+                        return [ "switch", walk(expr), MAP(body, function(branch, i){
+                                var block = tighten(MAP(branch[1], walk));
+                                if (i == last && block.length > 0) {
+                                        var node = block[block.length - 1];
+                                        if (node[0] == "break" && !node[1])
+                                                block.pop();
+                                }
+                                return [ branch[0] ? walk(branch[0]) : null, block ];
+                        }) ];
+                },
+                "function": function() {
+                        var ret = _lambda.apply(this, arguments);
+                        if (ret[1] && !HOP(scope.refs, ret[1])) {
+                                ret[1] = null;
+                        }
+                        return ret;
+                },
+                "defun": _lambda,
+                "block": function(body) {
+                        if (body) return rmblock([ "block", tighten(MAP(body, walk)) ]);
+                },
+                "binary": function(op, left, right) {
+                        return when_constant([ "binary", op, walk(left), walk(right) ], function yes(c){
+                                return best_of(walk(c), this);
+                        }, function no() {
+                                return this;
+                        });
+                },
+                "conditional": function(c, t, e) {
+                        return make_conditional(walk(c), walk(t), walk(e));
+                },
+                "try": function(t, c, f) {
+                        return [
+                                "try",
+                                tighten(MAP(t, walk)),
+                                c != null ? [ c[0], tighten(MAP(c[1], walk)) ] : null,
+                                f != null ? tighten(MAP(f, walk)) : null
+                        ];
+                },
+                "unary-prefix": function(op, expr) {
+                        expr = walk(expr);
+                        var ret = [ "unary-prefix", op, expr ];
+                        if (op == "!")
+                                ret = best_of(ret, negate(expr));
+                        return when_constant(ret, function(ast, val){
+                                return walk(ast); // it's either true or false, so minifies to !0 or !1
+                        }, function() { return ret });
+                },
+                "name": function(name) {
+                        switch (name) {
+                            case "true": return [ "unary-prefix", "!", [ "num", 0 ]];
+                            case "false": return [ "unary-prefix", "!", [ "num", 1 ]];
+                        }
+                },
+                "new": function(ctor, args) {
+                        if (ctor[0] == "name" && ctor[1] == "Array" && !scope.has("Array")) {
+                                if (args.length != 1) {
+                                        return [ "array", args ];
+                                } else {
+                                        return [ "call", [ "name", "Array" ], args ];
+                                }
+                        }
+                },
+                "call": function(expr, args) {
+                        if (expr[0] == "name" && expr[1] == "Array" && args.length != 1 && !scope.has("Array")) {
+                                return [ "array", args ];
+                        }
+                },
+                "while": _do_while,
+                "do": _do_while
+        }, function() {
+                return walk(ast_add_scope(ast));
+        });
+};
+
+/* -----[ re-generate code from the AST ]----- */
+
+var DOT_CALL_NO_PARENS = jsp.array_to_hash([
+        "name",
+        "array",
+        "object",
+        "string",
+        "dot",
+        "sub",
+        "call",
+        "regexp"
+]);
+
+function make_string(str, ascii_only) {
+        var dq = 0, sq = 0;
+        str = str.replace(/[\\\b\f\n\r\t\x22\x27\u2028\u2029]/g, function(s){
+                switch (s) {
+                    case "\\": return "\\\\";
+                    case "\b": return "\\b";
+                    case "\f": return "\\f";
+                    case "\n": return "\\n";
+                    case "\r": return "\\r";
+                    case "\t": return "\\t";
+                    case "\u2028": return "\\u2028";
+                    case "\u2029": return "\\u2029";
+                    case '"': ++dq; return '"';
+                    case "'": ++sq; return "'";
+                }
+                return s;
+        });
+        if (ascii_only) str = to_ascii(str);
+        if (dq > sq) return "'" + str.replace(/\x27/g, "\\'") + "'";
+        else return '"' + str.replace(/\x22/g, '\\"') + '"';
+};
+
+function to_ascii(str) {
+        return str.replace(/[\u0080-\uffff]/g, function(ch) {
+                var code = ch.charCodeAt(0).toString(16);
+                while (code.length < 4) code = "0" + code;
+                return "\\u" + code;
+        });
+};
+
+function gen_code(ast, options) {
+        options = defaults(options, {
+                indent_start : 0,
+                indent_level : 4,
+                quote_keys   : false,
+                space_colon  : false,
+                beautify     : false,
+                ascii_only   : false
+        });
+        var beautify = !!options.beautify;
+        var indentation = 0,
+            newline = beautify ? "\n" : "",
+            space = beautify ? " " : "";
+
+        function encode_string(str) {
+                return make_string(str, options.ascii_only);
+        };
+
+        function make_name(name) {
+                name = name.toString();
+                if (options.ascii_only)
+                        name = to_ascii(name);
+                return name;
+        };
+
+        function indent(line) {
+                if (line == null)
+                        line = "";
+                if (beautify)
+                        line = repeat_string(" ", options.indent_start + indentation * options.indent_level) + line;
+                return line;
+        };
+
+        function with_indent(cont, incr) {
+                if (incr == null) incr = 1;
+                indentation += incr;
+                try { return cont.apply(null, slice(arguments, 1)); }
+                finally { indentation -= incr; }
+        };
+
+        function add_spaces(a) {
+                if (beautify)
+                        return a.join(" ");
+                var b = [];
+                for (var i = 0; i < a.length; ++i) {
+                        var next = a[i + 1];
+                        b.push(a[i]);
+                        if (next &&
+                            ((/[a-z0-9_\x24]$/i.test(a[i].toString()) && /^[a-z0-9_\x24]/i.test(next.toString())) ||
+                             (/[\+\-]$/.test(a[i].toString()) && /^[\+\-]/.test(next.toString())))) {
+                                b.push(" ");
+                        }
+                }
+                return b.join("");
+        };
+
+        function add_commas(a) {
+                return a.join("," + space);
+        };
+
+        function parenthesize(expr) {
+                var gen = make(expr);
+                for (var i = 1; i < arguments.length; ++i) {
+                        var el = arguments[i];
+                        if ((el instanceof Function && el(expr)) || expr[0] == el)
+                                return "(" + gen + ")";
+                }
+                return gen;
+        };
+
+        function best_of(a) {
+                if (a.length == 1) {
+                        return a[0];
+                }
+                if (a.length == 2) {
+                        var b = a[1];
+                        a = a[0];
+                        return a.length <= b.length ? a : b;
+                }
+                return best_of([ a[0], best_of(a.slice(1)) ]);
+        };
+
+        function needs_parens(expr) {
+                if (expr[0] == "function" || expr[0] == "object") {
+                        // dot/call on a literal function requires the
+                        // function literal itself to be parenthesized
+                        // only if it's the first "thing" in a
+                        // statement.  This means that the parent is
+                        // "stat", but it could also be a "seq" and
+                        // we're the first in this "seq" and the
+                        // parent is "stat", and so on.  Messy stuff,
+                        // but it worths the trouble.
+                        var a = slice($stack), self = a.pop(), p = a.pop();
+                        while (p) {
+                                if (p[0] == "stat") return true;
+                                if (((p[0] == "seq" || p[0] == "call" || p[0] == "dot" || p[0] == "sub" || p[0] == "conditional") && p[1] === self) ||
+                                    ((p[0] == "binary" || p[0] == "assign" || p[0] == "unary-postfix") && p[2] === self)) {
+                                        self = p;
+                                        p = a.pop();
+                                } else {
+                                        return false;
+                                }
+                        }
+                }
+                return !HOP(DOT_CALL_NO_PARENS, expr[0]);
+        };
+
+        function make_num(num) {
+                var str = num.toString(10), a = [ str.replace(/^0\./, ".") ], m;
+                if (Math.floor(num) === num) {
+                        a.push("0x" + num.toString(16).toLowerCase(), // probably pointless
+                               "0" + num.toString(8)); // same.
+                        if ((m = /^(.*?)(0+)$/.exec(num))) {
+                                a.push(m[1] + "e" + m[2].length);
+                        }
+                } else if ((m = /^0?\.(0+)(.*)$/.exec(num))) {
+                        a.push(m[2] + "e-" + (m[1].length + m[2].length),
+                               str.substr(str.indexOf(".")));
+                }
+                return best_of(a);
+        };
+
+        var generators = {
+                "string": encode_string,
+                "num": make_num,
+                "name": make_name,
+                "toplevel": function(statements) {
+                        return make_block_statements(statements)
+                                .join(newline + newline);
+                },
+                "block": make_block,
+                "var": function(defs) {
+                        return "var " + add_commas(MAP(defs, make_1vardef)) + ";";
+                },
+                "const": function(defs) {
+                        return "const " + add_commas(MAP(defs, make_1vardef)) + ";";
+                },
+                "try": function(tr, ca, fi) {
+                        var out = [ "try", make_block(tr) ];
+                        if (ca) out.push("catch", "(" + ca[0] + ")", make_block(ca[1]));
+                        if (fi) out.push("finally", make_block(fi));
+                        return add_spaces(out);
+                },
+                "throw": function(expr) {
+                        return add_spaces([ "throw", make(expr) ]) + ";";
+                },
+                "new": function(ctor, args) {
+                        args = args.length > 0 ? "(" + add_commas(MAP(args, make)) + ")" : "";
+                        return add_spaces([ "new", parenthesize(ctor, "seq", "binary", "conditional", "assign", function(expr){
+                                var w = ast_walker(), has_call = {};
+                                try {
+                                        w.with_walkers({
+                                                "call": function() { throw has_call },
+                                                "function": function() { return this }
+                                        }, function(){
+                                                w.walk(expr);
+                                        });
+                                } catch(ex) {
+                                        if (ex === has_call)
+                                                return true;
+                                        throw ex;
+                                }
+                        }) + args ]);
+                },
+                "switch": function(expr, body) {
+                        return add_spaces([ "switch", "(" + make(expr) + ")", make_switch_block(body) ]);
+                },
+                "break": function(label) {
+                        var out = "break";
+                        if (label != null)
+                                out += " " + make_name(label);
+                        return out + ";";
+                },
+                "continue": function(label) {
+                        var out = "continue";
+                        if (label != null)
+                                out += " " + make_name(label);
+                        return out + ";";
+                },
+                "conditional": function(co, th, el) {
+                        return add_spaces([ parenthesize(co, "assign", "seq", "conditional"), "?",
+                                            parenthesize(th, "seq"), ":",
+                                            parenthesize(el, "seq") ]);
+                },
+                "assign": function(op, lvalue, rvalue) {
+                        if (op && op !== true) op += "=";
+                        else op = "=";
+                        return add_spaces([ make(lvalue), op, parenthesize(rvalue, "seq") ]);
+                },
+                "dot": function(expr) {
+                        var out = make(expr), i = 1;
+                        if (expr[0] == "num") {
+                                if (!/\./.test(expr[1]))
+                                        out += ".";
+                        } else if (needs_parens(expr))
+                                out = "(" + out + ")";
+                        while (i < arguments.length)
+                                out += "." + make_name(arguments[i++]);
+                        return out;
+                },
+                "call": function(func, args) {
+                        var f = make(func);
+                        if (needs_parens(func))
+                                f = "(" + f + ")";
+                        return f + "(" + add_commas(MAP(args, function(expr){
+                                return parenthesize(expr, "seq");
+                        })) + ")";
+                },
+                "function": make_function,
+                "defun": make_function,
+                "if": function(co, th, el) {
+                        var out = [ "if", "(" + make(co) + ")", el ? make_then(th) : make(th) ];
+                        if (el) {
+                                out.push("else", make(el));
+                        }
+                        return add_spaces(out);
+                },
+                "for": function(init, cond, step, block) {
+                        var out = [ "for" ];
+                        init = (init != null ? make(init) : "").replace(/;*\s*$/, ";" + space);
+                        cond = (cond != null ? make(cond) : "").replace(/;*\s*$/, ";" + space);
+                        step = (step != null ? make(step) : "").replace(/;*\s*$/, "");
+                        var args = init + cond + step;
+                        if (args == "; ; ") args = ";;";
+                        out.push("(" + args + ")", make(block));
+                        return add_spaces(out);
+                },
+                "for-in": function(vvar, key, hash, block) {
+                        return add_spaces([ "for", "(" +
+                                            (vvar ? make(vvar).replace(/;+$/, "") : make(key)),
+                                            "in",
+                                            make(hash) + ")", make(block) ]);
+                },
+                "while": function(condition, block) {
+                        return add_spaces([ "while", "(" + make(condition) + ")", make(block) ]);
+                },
+                "do": function(condition, block) {
+                        return add_spaces([ "do", make(block), "while", "(" + make(condition) + ")" ]) + ";";
+                },
+                "return": function(expr) {
+                        var out = [ "return" ];
+                        if (expr != null) out.push(make(expr));
+                        return add_spaces(out) + ";";
+                },
+                "binary": function(operator, lvalue, rvalue) {
+                        var left = make(lvalue), right = make(rvalue);
+                        // XXX: I'm pretty sure other cases will bite here.
+                        //      we need to be smarter.
+                        //      adding parens all the time is the safest bet.
+                        if (member(lvalue[0], [ "assign", "conditional", "seq" ]) ||
+                            lvalue[0] == "binary" && PRECEDENCE[operator] > PRECEDENCE[lvalue[1]]) {
+                                left = "(" + left + ")";
+                        }
+                        if (member(rvalue[0], [ "assign", "conditional", "seq" ]) ||
+                            rvalue[0] == "binary" && PRECEDENCE[operator] >= PRECEDENCE[rvalue[1]] &&
+                            !(rvalue[1] == operator && member(operator, [ "&&", "||", "*" ]))) {
+                                right = "(" + right + ")";
+                        }
+                        return add_spaces([ left, operator, right ]);
+                },
+                "unary-prefix": function(operator, expr) {
+                        var val = make(expr);
+                        if (!(expr[0] == "num" || (expr[0] == "unary-prefix" && !HOP(OPERATORS, operator + expr[1])) || !needs_parens(expr)))
+                                val = "(" + val + ")";
+                        return operator + (jsp.is_alphanumeric_char(operator.charAt(0)) ? " " : "") + val;
+                },
+                "unary-postfix": function(operator, expr) {
+                        var val = make(expr);
+                        if (!(expr[0] == "num" || (expr[0] == "unary-postfix" && !HOP(OPERATORS, operator + expr[1])) || !needs_parens(expr)))
+                                val = "(" + val + ")";
+                        return val + operator;
+                },
+                "sub": function(expr, subscript) {
+                        var hash = make(expr);
+                        if (needs_parens(expr))
+                                hash = "(" + hash + ")";
+                        return hash + "[" + make(subscript) + "]";
+                },
+                "object": function(props) {
+                        if (props.length == 0)
+                                return "{}";
+                        return "{" + newline + with_indent(function(){
+                                return MAP(props, function(p){
+                                        if (p.length == 3) {
+                                                // getter/setter.  The name is in p[0], the arg.list in p[1][2], the
+                                                // body in p[1][3] and type ("get" / "set") in p[2].
+                                                return indent(make_function(p[0], p[1][2], p[1][3], p[2]));
+                                        }
+                                        var key = p[0], val = make(p[1]);
+                                        if (options.quote_keys) {
+                                                key = encode_string(key);
+                                        } else if ((typeof key == "number" || !beautify && +key + "" == key)
+                                                   && parseFloat(key) >= 0) {
+                                                key = make_num(+key);
+                                        } else if (!is_identifier(key)) {
+                                                key = encode_string(key);
+                                        }
+                                        return indent(add_spaces(beautify && options.space_colon
+                                                                 ? [ key, ":", val ]
+                                                                 : [ key + ":", val ]));
+                                }).join("," + newline);
+                        }) + newline + indent("}");
+                },
+                "regexp": function(rx, mods) {
+                        return "/" + rx + "/" + mods;
+                },
+                "array": function(elements) {
+                        if (elements.length == 0) return "[]";
+                        return add_spaces([ "[", add_commas(MAP(elements, function(el){
+                                if (!beautify && el[0] == "atom" && el[1] == "undefined") return "";
+                                return parenthesize(el, "seq");
+                        })), "]" ]);
+                },
+                "stat": function(stmt) {
+                        return make(stmt).replace(/;*\s*$/, ";");
+                },
+                "seq": function() {
+                        return add_commas(MAP(slice(arguments), make));
+                },
+                "label": function(name, block) {
+                        return add_spaces([ make_name(name), ":", make(block) ]);
+                },
+                "with": function(expr, block) {
+                        return add_spaces([ "with", "(" + make(expr) + ")", make(block) ]);
+                },
+                "atom": function(name) {
+                        return make_name(name);
+                }
+        };
+
+        // The squeezer replaces "block"-s that contain only a single
+        // statement with the statement itself; technically, the AST
+        // is correct, but this can create problems when we output an
+        // IF having an ELSE clause where the THEN clause ends in an
+        // IF *without* an ELSE block (then the outer ELSE would refer
+        // to the inner IF).  This function checks for this case and
+        // adds the block brackets if needed.
+        function make_then(th) {
+                if (th[0] == "do") {
+                        // https://github.com/mishoo/UglifyJS/issues/#issue/57
+                        // IE croaks with "syntax error" on code like this:
+                        //     if (foo) do ... while(cond); else ...
+                        // we need block brackets around do/while
+                        return make([ "block", [ th ]]);
+                }
+                var b = th;
+                while (true) {
+                        var type = b[0];
+                        if (type == "if") {
+                                if (!b[3])
+                                        // no else, we must add the block
+                                        return make([ "block", [ th ]]);
+                                b = b[3];
+                        }
+                        else if (type == "while" || type == "do") b = b[2];
+                        else if (type == "for" || type == "for-in") b = b[4];
+                        else break;
+                }
+                return make(th);
+        };
+
+        function make_function(name, args, body, keyword) {
+                var out = keyword || "function";
+                if (name) {
+                        out += " " + make_name(name);
+                }
+                out += "(" + add_commas(MAP(args, make_name)) + ")";
+                return add_spaces([ out, make_block(body) ]);
+        };
+
+        function make_block_statements(statements) {
+                for (var a = [], last = statements.length - 1, i = 0; i <= last; ++i) {
+                        var stat = statements[i];
+                        var code = make(stat);
+                        if (code != ";") {
+                                if (!beautify && i == last) {
+                                        if ((stat[0] == "while" && empty(stat[2])) ||
+                                            (member(stat[0], [ "for", "for-in"] ) && empty(stat[4])) ||
+                                            (stat[0] == "if" && empty(stat[2]) && !stat[3]) ||
+                                            (stat[0] == "if" && stat[3] && empty(stat[3]))) {
+                                                code = code.replace(/;*\s*$/, ";");
+                                        } else {
+                                                code = code.replace(/;+\s*$/, "");
+                                        }
+                                }
+                                a.push(code);
+                        }
+                }
+                return MAP(a, indent);
+        };
+
+        function make_switch_block(body) {
+                var n = body.length;
+                if (n == 0) return "{}";
+                return "{" + newline + MAP(body, function(branch, i){
+                        var has_body = branch[1].length > 0, code = with_indent(function(){
+                                return indent(branch[0]
+                                              ? add_spaces([ "case", make(branch[0]) + ":" ])
+                                              : "default:");
+                        }, 0.5) + (has_body ? newline + with_indent(function(){
+                                return make_block_statements(branch[1]).join(newline);
+                        }) : "");
+                        if (!beautify && has_body && i < n - 1)
+                                code += ";";
+                        return code;
+                }).join(newline) + newline + indent("}");
+        };
+
+        function make_block(statements) {
+                if (!statements) return ";";
+                if (statements.length == 0) return "{}";
+                return "{" + newline + with_indent(function(){
+                        return make_block_statements(statements).join(newline);
+                }) + newline + indent("}");
+        };
+
+        function make_1vardef(def) {
+                var name = def[0], val = def[1];
+                if (val != null)
+                        name = add_spaces([ make_name(name), "=", parenthesize(val, "seq") ]);
+                return name;
+        };
+
+        var $stack = [];
+
+        function make(node) {
+                var type = node[0];
+                var gen = generators[type];
+                if (!gen)
+                        throw new Error("Can't find generator for \"" + type + "\"");
+                $stack.push(node);
+                var ret = gen.apply(type, node.slice(1));
+                $stack.pop();
+                return ret;
+        };
+
+        return make(ast);
+};
+
+function split_lines(code, max_line_length) {
+        var splits = [ 0 ];
+        jsp.parse(function(){
+                var next_token = jsp.tokenizer(code);
+                var last_split = 0;
+                var prev_token;
+                function current_length(tok) {
+                        return tok.pos - last_split;
+                };
+                function split_here(tok) {
+                        last_split = tok.pos;
+                        splits.push(last_split);
+                };
+                function custom(){
+                        var tok = next_token.apply(this, arguments);
+                        out: {
+                                if (prev_token) {
+                                        if (prev_token.type == "keyword") break out;
+                                }
+                                if (current_length(tok) > max_line_length) {
+                                        switch (tok.type) {
+                                            case "keyword":
+                                            case "atom":
+                                            case "name":
+                                            case "punc":
+                                                split_here(tok);
+                                                break out;
+                                        }
+                                }
+                        }
+                        prev_token = tok;
+                        return tok;
+                };
+                custom.context = function() {
+                        return next_token.context.apply(this, arguments);
+                };
+                return custom;
+        }());
+        return splits.map(function(pos, i){
+                return code.substring(pos, splits[i + 1] || code.length);
+        }).join("\n");
+};
+
+/* -----[ Utilities ]----- */
+
+function repeat_string(str, i) {
+        if (i <= 0) return "";
+        if (i == 1) return str;
+        var d = repeat_string(str, i >> 1);
+        d += d;
+        if (i & 1) d += str;
+        return d;
+};
+
+function defaults(args, defs) {
+        var ret = {};
+        if (args === true)
+                args = {};
+        for (var i in defs) if (HOP(defs, i)) {
+                ret[i] = (args && HOP(args, i)) ? args[i] : defs[i];
+        }
+        return ret;
+};
+
+function is_identifier(name) {
+        return /^[a-z_$][a-z0-9_$]*$/i.test(name)
+                && name != "this"
+                && !HOP(jsp.KEYWORDS_ATOM, name)
+                && !HOP(jsp.RESERVED_WORDS, name)
+                && !HOP(jsp.KEYWORDS, name);
+};
+
+function HOP(obj, prop) {
+        return Object.prototype.hasOwnProperty.call(obj, prop);
+};
+
+// some utilities
+
+var MAP;
+
+(function(){
+        MAP = function(a, f, o) {
+                var ret = [];
+                for (var i = 0; i < a.length; ++i) {
+                        var val = f.call(o, a[i], i);
+                        if (val instanceof AtTop) ret.unshift(val.v);
+                        else ret.push(val);
+                }
+                return ret;
+        };
+        MAP.at_top = function(val) { return new AtTop(val) };
+        function AtTop(val) { this.v = val };
+})();
+
+/* -----[ Exports ]----- */
+
+exports.ast_walker = ast_walker;
+exports.ast_mangle = ast_mangle;
+exports.ast_squeeze = ast_squeeze;
+exports.gen_code = gen_code;
+exports.ast_add_scope = ast_add_scope;
+exports.set_logger = function(logger) { warn = logger };
+exports.make_string = make_string;
+exports.split_lines = split_lines;
+exports.MAP = MAP;
+
+// keep this last!
+exports.ast_squeeze_more = require("./squeeze-more").ast_squeeze_more;
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/lib/squeeze-more.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/lib/squeeze-more.js
new file mode 100644
index 0000000..12380af
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/lib/squeeze-more.js
@@ -0,0 +1,22 @@
+var jsp = require("./parse-js"),
+    pro = require("./process"),
+    slice = jsp.slice,
+    member = jsp.member,
+    PRECEDENCE = jsp.PRECEDENCE,
+    OPERATORS = jsp.OPERATORS;
+
+function ast_squeeze_more(ast) {
+        var w = pro.ast_walker(), walk = w.walk;
+        return w.with_walkers({
+                "call": function(expr, args) {
+                        if (expr[0] == "dot" && expr[2] == "toString" && args.length == 0) {
+                                // foo.toString()  ==>  foo+""
+                                return [ "binary", "+", expr[1], [ "string", "" ]];
+                        }
+                }
+        }, function() {
+                return walk(ast);
+        });
+};
+
+exports.ast_squeeze_more = ast_squeeze_more;
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/post-compile.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/post-compile.js
new file mode 100644
index 0000000..4bcafe8
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/post-compile.js
@@ -0,0 +1,7 @@
+#!/usr/bin/env node
+
+var print = require("sys").print,
+	src = require("fs").readFileSync(process.argv[2], "utf8");
+
+// Previously done in sed but reimplemented here due to portability issues
+print(src.replace(/^(\s*\*\/)(.+)/m, "$1\n$2;"));
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/uglify.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/uglify.js
new file mode 100644
index 0000000..943ddd8
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/build/uglify.js
@@ -0,0 +1,199 @@
+#! /usr/bin/env node
+// -*- js2 -*-
+
+global.sys = require(/^v0\.[012]/.test(process.version) ? "sys" : "util");
+var fs = require("fs"),
+    jsp = require("./lib/parse-js"),
+    pro = require("./lib/process");
+
+pro.set_logger(function(msg){
+        sys.debug(msg);
+});
+
+var options = {
+        ast: false,
+        mangle: true,
+        mangle_toplevel: false,
+        squeeze: true,
+        make_seqs: true,
+        dead_code: true,
+        beautify: false,
+        verbose: false,
+        show_copyright: true,
+        out_same_file: false,
+        extra: false,
+        unsafe: false,            // XXX: extra & unsafe?  but maybe we don't want both, so....
+        beautify_options: {
+                indent_level: 4,
+                indent_start: 0,
+                quote_keys: false,
+                space_colon: false
+        },
+        output: true            // stdout
+};
+
+var args = jsp.slice(process.argv, 2);
+var filename;
+
+out: while (args.length > 0) {
+        var v = args.shift();
+        switch (v) {
+            case "-b":
+            case "--beautify":
+                options.beautify = true;
+                break;
+            case "-i":
+            case "--indent":
+                options.beautify_options.indent_level = args.shift();
+                break;
+            case "-q":
+            case "--quote-keys":
+                options.beautify_options.quote_keys = true;
+                break;
+            case "-mt":
+            case "--mangle-toplevel":
+                options.mangle_toplevel = true;
+                break;
+            case "--no-mangle":
+            case "-nm":
+                options.mangle = false;
+                break;
+            case "--no-squeeze":
+            case "-ns":
+                options.squeeze = false;
+                break;
+            case "--no-seqs":
+                options.make_seqs = false;
+                break;
+            case "--no-dead-code":
+                options.dead_code = false;
+                break;
+            case "--no-copyright":
+            case "-nc":
+                options.show_copyright = false;
+                break;
+            case "-o":
+            case "--output":
+                options.output = args.shift();
+                break;
+            case "--overwrite":
+                options.out_same_file = true;
+                break;
+            case "-v":
+            case "--verbose":
+                options.verbose = true;
+                break;
+            case "--ast":
+                options.ast = true;
+                break;
+            case "--extra":
+                options.extra = true;
+                break;
+            case "--unsafe":
+                options.unsafe = true;
+                break;
+            default:
+                filename = v;
+                break out;
+        }
+}
+
+if (filename) {
+        fs.readFile(filename, "utf8", function(err, text){
+                if (err) {
+                        throw err;
+                }
+                output(squeeze_it(text));
+        });
+} else {
+        var stdin = process.openStdin();
+        stdin.setEncoding("utf8");
+        var text = "";
+        stdin.on("data", function(chunk){
+                text += chunk;
+        });
+        stdin.on("end", function() {
+                output(squeeze_it(text));
+        });
+}
+
+function output(text) {
+        var out;
+        if (options.out_same_file && filename)
+                options.output = filename;
+        if (options.output === true) {
+                out = process.stdout;
+        } else {
+                out = fs.createWriteStream(options.output, {
+                        flags: "w",
+                        encoding: "utf8",
+                        mode: 0644
+                });
+        }
+        out.write(text);
+        out.end();
+};
+
+// --------- main ends here.
+
+function show_copyright(comments) {
+        var ret = "";
+        for (var i = 0; i < comments.length; ++i) {
+                var c = comments[i];
+                if (c.type == "comment1") {
+                        ret += "//" + c.value + "\n";
+                } else {
+                        ret += "/*" + c.value + "*/";
+                }
+        }
+        return ret;
+};
+
+function squeeze_it(code) {
+        var result = "";
+        if (options.show_copyright) {
+                var initial_comments = [];
+                // keep first comment
+                var tok = jsp.tokenizer(code, false), c;
+                c = tok();
+                var prev = null;
+                while (/^comment/.test(c.type) && (!prev || prev == c.type)) {
+                        initial_comments.push(c);
+                        prev = c.type;
+                        c = tok();
+                }
+                result += show_copyright(initial_comments);
+        }
+        try {
+                var ast = time_it("parse", function(){ return jsp.parse(code); });
+                if (options.mangle)
+                        ast = time_it("mangle", function(){ return pro.ast_mangle(ast, options.mangle_toplevel); });
+                if (options.squeeze)
+                        ast = time_it("squeeze", function(){
+                                ast = pro.ast_squeeze(ast, {
+                                        make_seqs : options.make_seqs,
+                                        dead_code : options.dead_code,
+                                        extra     : options.extra
+                                });
+                                if (options.unsafe)
+                                        ast = pro.ast_squeeze_more(ast);
+                                return ast;
+                        });
+                if (options.ast)
+                        return sys.inspect(ast, null, null);
+                result += time_it("generate", function(){ return pro.gen_code(ast, options.beautify && options.beautify_options) });
+                return result;
+        } catch(ex) {
+                sys.debug(ex.stack);
+                sys.debug(sys.inspect(ex));
+                sys.debug(JSON.stringify(ex));
+        }
+};
+
+function time_it(name, cont) {
+        if (!options.verbose)
+                return cont();
+        var t1 = new Date().getTime();
+        try { return cont(); }
+        finally { sys.debug("// " + name + ": " + ((new Date().getTime() - t1) / 1000).toFixed(3) + " sec."); }
+};
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/dist/jsonselect.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/dist/jsonselect.js
new file mode 100644
index 0000000..50059b7
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/dist/jsonselect.js
@@ -0,0 +1,522 @@
+/*! Copyright (c) 2011, Lloyd Hilaiel, ISC License */
+/*
+ * This is the JSONSelect reference implementation, in javascript.
+ */
+(function(exports) {
+
+    var // localize references
+    toString = Object.prototype.toString;
+
+    function jsonParse(str) {
+      try {
+          if(JSON && JSON.parse){
+              return JSON.parse(str);
+          }
+          return (new Function("return " + str))();
+      } catch(e) {
+        te("ijs");
+      }
+    }
+
+    // emitted error codes.
+    var errorCodes = {
+        "bop":  "binary operator expected",
+        "ee":   "expression expected",
+        "epex": "closing paren expected ')'",
+        "ijs":  "invalid json string",
+        "mcp":  "missing closing paren",
+        "mepf": "malformed expression in pseudo-function",
+        "mexp": "multiple expressions not allowed",
+        "mpc":  "multiple pseudo classes (:xxx) not allowed",
+        "nmi":  "multiple ids not allowed",
+        "pex":  "opening paren expected '('",
+        "se":   "selector expected",
+        "sex":  "string expected",
+        "sra":  "string required after '.'",
+        "uc":   "unrecognized char",
+        "ucp":  "unexpected closing paren",
+        "ujs":  "unclosed json string",
+        "upc":  "unrecognized pseudo class"
+    };
+
+    // throw an error message
+    function te(ec) {
+      throw new Error(errorCodes[ec]);
+    }
+
+    // THE LEXER
+    var toks = {
+        psc: 1, // pseudo class
+        psf: 2, // pseudo class function
+        typ: 3, // type
+        str: 4, // string
+        ide: 5  // identifiers (or "classes", stuff after a dot)
+    };
+
+    // The primary lexing regular expression in jsonselect
+    var pat = new RegExp(
+        "^(?:" +
+        // (1) whitespace
+        "([\\r\\n\\t\\ ]+)|" +
+        // (2) one-char ops
+        "([~*,>\\)\\(])|" +
+        // (3) types names
+        "(string|boolean|null|array|object|number)|" +
+        // (4) pseudo classes
+        "(:(?:root|first-child|last-child|only-child))|" +
+        // (5) pseudo functions
+        "(:(?:nth-child|nth-last-child|has|expr|val|contains))|" +
+        // (6) bogusly named pseudo something or others
+        "(:\\w+)|" +
+        // (7 & 8) identifiers and JSON strings
+        "(?:(\\.)?(\\\"(?:[^\\\\]|\\\\[^\\\"])*\\\"))|" +
+        // (8) bogus JSON strings missing a trailing quote
+        "(\\\")|" +
+        // (9) identifiers (unquoted)
+        "\\.((?:[_a-zA-Z]|[^\\0-\\0177]|\\\\[^\\r\\n\\f0-9a-fA-F])(?:[_a-zA-Z0-9\\-]|[^\\u0000-\\u0177]|(?:\\\\[^\\r\\n\\f0-9a-fA-F]))*)" +
+        ")"
+    );
+
+    // A regular expression for matching "nth expressions" (see grammar, what :nth-child() eats)
+    var nthPat = /^\s*\(\s*(?:([+\-]?)([0-9]*)n\s*(?:([+\-])\s*([0-9]))?|(odd|even)|([+\-]?[0-9]+))\s*\)/;
+    function lex(str, off) {
+        if (!off) off = 0;
+        var m = pat.exec(str.substr(off));
+        if (!m) return undefined;
+        off+=m[0].length;
+        var a;
+        if (m[1]) a = [off, " "];
+        else if (m[2]) a = [off, m[0]];
+        else if (m[3]) a = [off, toks.typ, m[0]];
+        else if (m[4]) a = [off, toks.psc, m[0]];
+        else if (m[5]) a = [off, toks.psf, m[0]];
+        else if (m[6]) te("upc");
+        else if (m[8]) a = [off, m[7] ? toks.ide : toks.str, jsonParse(m[8])];
+        else if (m[9]) te("ujs");
+        else if (m[10]) a = [off, toks.ide, m[10].replace(/\\([^\r\n\f0-9a-fA-F])/g,"$1")];
+        return a;
+    }
+
+    // THE EXPRESSION SUBSYSTEM
+
+    var exprPat = new RegExp(
+            // skip and don't capture leading whitespace
+            "^\\s*(?:" +
+            // (1) simple vals
+            "(true|false|null)|" + 
+            // (2) numbers
+            "(-?\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?)|" +
+            // (3) strings
+            "(\"(?:[^\\]|\\[^\"])*\")|" +
+            // (4) the 'x' value placeholder
+            "(x)|" +
+            // (5) binops
+            "(&&|\\|\\||[\\$\\^<>!\\*]=|[=+\\-*/%<>])|" +
+            // (6) parens
+            "([\\(\\)])" +
+            ")"
+    );
+
+    function is(o, t) { return typeof o === t; }
+    var operators = {
+        '*':  [ 9, function(lhs, rhs) { return lhs * rhs; } ],
+        '/':  [ 9, function(lhs, rhs) { return lhs / rhs; } ],
+        '%':  [ 9, function(lhs, rhs) { return lhs % rhs; } ],
+        '+':  [ 7, function(lhs, rhs) { return lhs + rhs; } ],
+        '-':  [ 7, function(lhs, rhs) { return lhs - rhs; } ],
+        '<=': [ 5, function(lhs, rhs) { return is(lhs, 'number') && is(rhs, 'number') && lhs <= rhs; } ],
+        '>=': [ 5, function(lhs, rhs) { return is(lhs, 'number') && is(rhs, 'number') && lhs >= rhs; } ],
+        '$=': [ 5, function(lhs, rhs) { return is(lhs, 'string') && is(rhs, 'string') && lhs.lastIndexOf(rhs) === lhs.length - rhs.length; } ],
+        '^=': [ 5, function(lhs, rhs) { return is(lhs, 'string') && is(rhs, 'string') && lhs.indexOf(rhs) === 0; } ],
+        '*=': [ 5, function(lhs, rhs) { return is(lhs, 'string') && is(rhs, 'string') && lhs.indexOf(rhs) !== -1; } ],
+        '>':  [ 5, function(lhs, rhs) { return is(lhs, 'number') && is(rhs, 'number') && lhs > rhs; } ],
+        '<':  [ 5, function(lhs, rhs) { return is(lhs, 'number') && is(rhs, 'number') && lhs < rhs; } ],
+        '=':  [ 3, function(lhs, rhs) { return lhs === rhs; } ],
+        '!=': [ 3, function(lhs, rhs) { return lhs !== rhs; } ],
+        '&&': [ 2, function(lhs, rhs) { return lhs && rhs; } ],
+        '||': [ 1, function(lhs, rhs) { return lhs || rhs; } ]
+    };
+
+    function exprLex(str, off) {
+        var v, m = exprPat.exec(str.substr(off));
+        if (m) {
+            off += m[0].length;
+            v = m[1] || m[2] || m[3] || m[5] || m[6];
+            if (m[1] || m[2] || m[3]) return [off, 0, jsonParse(v)];
+            else if (m[4]) return [off, 0, undefined];
+            return [off, v];
+        }
+    }
+
+    function exprParse2(str, off) {
+        if (!off) off = 0;
+        // first we expect a value or a '('
+        var l = exprLex(str, off),
+            lhs;
+        if (l && l[1] === '(') {
+            lhs = exprParse2(str, l[0]);
+            var p = exprLex(str, lhs[0]);
+            if (!p || p[1] !== ')') te('epex');
+            off = p[0];
+            lhs = [ '(', lhs[1] ];
+        } else if (!l || (l[1] && l[1] != 'x')) {
+            te("ee");
+        } else {
+            lhs = ((l[1] === 'x') ? undefined : l[2]);
+            off = l[0];
+        }
+
+        // now we expect a binary operator or a ')'
+        var op = exprLex(str, off);
+        if (!op || op[1] == ')') return [off, lhs];
+        else if (op[1] == 'x' || !op[1]) {
+            te('bop');
+        }
+
+        // tail recursion to fetch the rhs expression
+        var rhs = exprParse2(str, op[0]);
+        off = rhs[0];
+        rhs = rhs[1];
+
+        // and now precedence!  how shall we put everything together?
+        var v;
+        if (typeof rhs !== 'object' || rhs[0] === '(' || operators[op[1]][0] < operators[rhs[1]][0] ) {
+            v = [lhs, op[1], rhs];
+        }
+        else {
+            v = rhs;
+            while (typeof rhs[0] === 'object' && rhs[0][0] != '(' && operators[op[1]][0] >= operators[rhs[0][1]][0]) {
+                rhs = rhs[0];
+            }
+            rhs[0] = [lhs, op[1], rhs[0]];
+        }
+        return [off, v];
+    }
+
+    function exprParse(str, off) {
+        function deparen(v) {
+            if (typeof v !== 'object' || v === null) return v;
+            else if (v[0] === '(') return deparen(v[1]);
+            else return [deparen(v[0]), v[1], deparen(v[2])];
+        }
+        var e = exprParse2(str, off ? off : 0);
+        return [e[0], deparen(e[1])];
+    }
+
+    function exprEval(expr, x) {
+        if (expr === undefined) return x;
+        else if (expr === null || typeof expr !== 'object') {
+            return expr;
+        }
+        var lhs = exprEval(expr[0], x),
+            rhs = exprEval(expr[2], x);
+        return operators[expr[1]][1](lhs, rhs);
+    }
+
+    // THE PARSER
+
+    function parse(str, off, nested, hints) {
+        if (!nested) hints = {};
+
+        var a = [], am, readParen;
+        if (!off) off = 0; 
+
+        while (true) {
+            var s = parse_selector(str, off, hints);
+            a.push(s[1]);
+            s = lex(str, off = s[0]);
+            if (s && s[1] === " ") s = lex(str, off = s[0]);
+            if (!s) break;
+            // now we've parsed a selector, and have something else...
+            if (s[1] === ">" || s[1] === "~") {
+                if (s[1] === "~") hints.usesSiblingOp = true;
+                a.push(s[1]);
+                off = s[0];
+            } else if (s[1] === ",") {
+                if (am === undefined) am = [ ",", a ];
+                else am.push(a);
+                a = [];
+                off = s[0];
+            } else if (s[1] === ")") {
+                if (!nested) te("ucp");
+                readParen = 1;
+                off = s[0];
+                break;
+            }
+        }
+        if (nested && !readParen) te("mcp");
+        if (am) am.push(a);
+        var rv;
+        if (!nested && hints.usesSiblingOp) {
+            rv = normalize(am ? am : a);
+        } else {
+            rv = am ? am : a;
+        }
+        return [off, rv];
+    }
+
+    function normalizeOne(sel) {
+        var sels = [], s;
+        for (var i = 0; i < sel.length; i++) {
+            if (sel[i] === '~') {
+                // `A ~ B` maps to `:has(:root > A) > B`
+                // `Z A ~ B` maps to `Z :has(:root > A) > B, Z:has(:root > A) > B`
+                // This first clause, takes care of the first case, and the first half of the latter case.
+                if (i < 2 || sel[i-2] != '>') {
+                    s = sel.slice(0,i-1);
+                    s = s.concat([{has:[[{pc: ":root"}, ">", sel[i-1]]]}, ">"]);
+                    s = s.concat(sel.slice(i+1));
+                    sels.push(s);
+                }
+                // here we take care of the second half of above:
+                // (`Z A ~ B` maps to `Z :has(:root > A) > B, Z :has(:root > A) > B`)
+                // and a new case:
+                // Z > A ~ B maps to Z:has(:root > A) > B
+                if (i > 1) {
+                    var at = sel[i-2] === '>' ? i-3 : i-2;
+                    s = sel.slice(0,at);
+                    var z = {};
+                    for (var k in sel[at]) if (sel[at].hasOwnProperty(k)) z[k] = sel[at][k];
+                    if (!z.has) z.has = [];
+                    z.has.push([{pc: ":root"}, ">", sel[i-1]]);
+                    s = s.concat(z, '>', sel.slice(i+1));
+                    sels.push(s);
+                }
+                break;
+            }
+        }
+        if (i == sel.length) return sel;
+        return sels.length > 1 ? [','].concat(sels) : sels[0];
+    }
+
+    function normalize(sels) {
+        if (sels[0] === ',') {
+            var r = [","];
+            for (var i = i; i < sels.length; i++) {
+                var s = normalizeOne(s[i]);
+                r = r.concat(s[0] === "," ? s.slice(1) : s);
+            }
+            return r;
+        } else {
+            return normalizeOne(sels);
+        }
+    }
+
+    function parse_selector(str, off, hints) {
+        var soff = off;
+        var s = { };
+        var l = lex(str, off);
+        // skip space
+        if (l && l[1] === " ") { soff = off = l[0]; l = lex(str, off); }
+        if (l && l[1] === toks.typ) {
+            s.type = l[2];
+            l = lex(str, (off = l[0]));
+        } else if (l && l[1] === "*") {
+            // don't bother representing the universal sel, '*' in the
+            // parse tree, cause it's the default
+            l = lex(str, (off = l[0]));
+        }
+
+        // now support either an id or a pc
+        while (true) {
+            if (l === undefined) {
+                break;
+            } else if (l[1] === toks.ide) {
+                if (s.id) te("nmi");
+                s.id = l[2];
+            } else if (l[1] === toks.psc) {
+                if (s.pc || s.pf) te("mpc");
+                // collapse first-child and last-child into nth-child expressions
+                if (l[2] === ":first-child") {
+                    s.pf = ":nth-child";
+                    s.a = 0;
+                    s.b = 1;
+                } else if (l[2] === ":last-child") {
+                    s.pf = ":nth-last-child";
+                    s.a = 0;
+                    s.b = 1;
+                } else {
+                    s.pc = l[2];
+                }
+            } else if (l[1] === toks.psf) {
+                if (l[2] === ":val" || l[2] === ":contains") {
+                    s.expr = [ undefined, l[2] === ":val" ? "=" : "*=", undefined];
+                    // any amount of whitespace, followed by paren, string, paren
+                    l = lex(str, (off = l[0]));
+                    if (l && l[1] === " ") l = lex(str, off = l[0]);
+                    if (!l || l[1] !== "(") te("pex");
+                    l = lex(str, (off = l[0]));
+                    if (l && l[1] === " ") l = lex(str, off = l[0]);
+                    if (!l || l[1] !== toks.str) te("sex");
+                    s.expr[2] = l[2];
+                    l = lex(str, (off = l[0]));
+                    if (l && l[1] === " ") l = lex(str, off = l[0]);
+                    if (!l || l[1] !== ")") te("epex");
+                } else if (l[2] === ":has") {
+                    // any amount of whitespace, followed by paren
+                    l = lex(str, (off = l[0]));
+                    if (l && l[1] === " ") l = lex(str, off = l[0]);
+                    if (!l || l[1] !== "(") te("pex");
+                    var h = parse(str, l[0], true);
+                    l[0] = h[0];
+                    if (!s.has) s.has = [];
+                    s.has.push(h[1]);
+                } else if (l[2] === ":expr") {
+                    if (s.expr) te("mexp");
+                    var e = exprParse(str, l[0]);
+                    l[0] = e[0];
+                    s.expr = e[1];
+                } else {
+                    if (s.pc || s.pf ) te("mpc");
+                    s.pf = l[2];
+                    var m = nthPat.exec(str.substr(l[0]));
+                    if (!m) te("mepf");
+                    if (m[5]) {
+                        s.a = 2;
+                        s.b = (m[5] === "odd") ? 1 : 0;
+                    } else if (m[6]) {
+                        s.a = 0;
+                        s.b = parseInt(m[6], 10);
+                    } else {
+                        s.a = parseInt((m[1] ? m[1] : "+") + (m[2] ? m[2] : "1"),10);
+                        s.b = m[3] ? parseInt(m[3] + m[4],10) : 0;
+                    }
+                    l[0] += m[0].length;
+                }
+            } else {
+                break;
+            }
+            l = lex(str, (off = l[0]));
+        }
+
+        // now if we didn't actually parse anything it's an error
+        if (soff === off) te("se");
+
+        return [off, s];
+    }
+
+    // THE EVALUATOR
+
+    function isArray(o) {
+        return Array.isArray ? Array.isArray(o) : 
+          toString.call(o) === "[object Array]";
+    }
+
+    function mytypeof(o) {
+        if (o === null) return "null";
+        var to = typeof o;
+        if (to === "object" && isArray(o)) to = "array";
+        return to;
+    }
+
+    function mn(node, sel, id, num, tot) {
+        var sels = [];
+        var cs = (sel[0] === ">") ? sel[1] : sel[0];
+        var m = true, mod;
+        if (cs.type) m = m && (cs.type === mytypeof(node));
+        if (cs.id)   m = m && (cs.id === id);
+        if (m && cs.pf) {
+            if (cs.pf === ":nth-last-child") num = tot - num;
+            else num++;
+            if (cs.a === 0) {
+                m = cs.b === num;
+            } else {
+                mod = ((num - cs.b) % cs.a);
+
+                m = (!mod && ((num*cs.a + cs.b) >= 0));
+            }
+        }
+        if (m && cs.has) {
+            // perhaps we should augment forEach to handle a return value
+            // that indicates "client cancels traversal"?
+            var bail = function() { throw 42; };
+            for (var i = 0; i < cs.has.length; i++) {
+                try {
+                    forEach(cs.has[i], node, bail);
+                } catch (e) {
+                    if (e === 42) continue;
+                }
+                m = false;
+                break;
+            }
+        }
+        if (m && cs.expr) {
+            m = exprEval(cs.expr, node);
+        }
+        // should we repeat this selector for descendants?
+        if (sel[0] !== ">" && sel[0].pc !== ":root") sels.push(sel);
+
+        if (m) {
+            // is there a fragment that we should pass down?
+            if (sel[0] === ">") { if (sel.length > 2) { m = false; sels.push(sel.slice(2)); } }
+            else if (sel.length > 1) { m = false; sels.push(sel.slice(1)); }
+        }
+
+        return [m, sels];
+    }
+
+    function forEach(sel, obj, fun, id, num, tot) {
+        var a = (sel[0] === ",") ? sel.slice(1) : [sel],
+        a0 = [],
+        call = false,
+        i = 0, j = 0, k, x;
+        for (i = 0; i < a.length; i++) {
+            x = mn(obj, a[i], id, num, tot);
+            if (x[0]) {
+                call = true;
+            }
+            for (j = 0; j < x[1].length; j++) {
+                a0.push(x[1][j]);
+            }
+        }
+        if (a0.length && typeof obj === "object") {
+            if (a0.length >= 1) {
+                a0.unshift(",");
+            }
+            if (isArray(obj)) {
+                for (i = 0; i < obj.length; i++) {
+                    forEach(a0, obj[i], fun, undefined, i, obj.length);
+                }
+            } else {
+                for (k in obj) {
+                    if (obj.hasOwnProperty(k)) {
+                        forEach(a0, obj[k], fun, k);
+                    }
+                }
+            }
+        }
+        if (call && fun) {
+            fun(obj);
+        }
+    }
+
+    function match(sel, obj) {
+        var a = [];
+        forEach(sel, obj, function(x) {
+            a.push(x);
+        });
+        return a;
+    }
+
+    function compile(sel) {
+        return {
+            sel: parse(sel)[1],
+            match: function(obj){
+                return match(this.sel, obj);
+            },
+            forEach: function(obj, fun) {
+                return forEach(this.sel, obj, fun);
+            }
+        };
+    }
+
+    exports._lex = lex;
+    exports._parse = parse;
+    exports.match = function (sel, obj) {
+        return compile(sel).match(obj);
+    };
+    exports.forEach = function(sel, obj, fun) {
+        return compile(sel).forEach(obj, fun);
+    };
+    exports.compile = compile;
+})(typeof exports === "undefined" ? (window.JSONSelect = {}) : exports);
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/dist/jsonselect.min.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/dist/jsonselect.min.js
new file mode 100644
index 0000000..ec5015f
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/dist/jsonselect.min.js
@@ -0,0 +1 @@
+(function(a){function z(a){return{sel:q(a)[1],match:function(a){return y(this.sel,a)},forEach:function(a,b){return x(this.sel,a,b)}}}function y(a,b){var c=[];x(a,b,function(a){c.push(a)});return c}function x(a,b,c,d,e,f){var g=a[0]===","?a.slice(1):[a],h=[],i=!1,j=0,k=0,l,m;for(j=0;j<g.length;j++){m=w(b,g[j],d,e,f),m[0]&&(i=!0);for(k=0;k<m[1].length;k++)h.push(m[1][k])}if(h.length&&typeof b=="object"){h.length>=1&&h.unshift(",");if(u(b))for(j=0;j<b.length;j++)x(h,b[j],c,undefined,j,b.length);else for(l in b)b.hasOwnProperty(l)&&x(h,b[l],c,l)}i&&c&&c(b)}function w(a,b,c,d,e){var f=[],g=b[0]===">"?b[1]:b[0],h=!0,i;g.type&&(h=h&&g.type===v(a)),g.id&&(h=h&&g.id===c),h&&g.pf&&(g.pf===":nth-last-child"?d=e-d:d++,g.a===0?h=g.b===d:(i=(d-g.b)%g.a,h=!i&&d*g.a+g.b>=0));if(h&&g.has){var j=function(){throw 42};for(var k=0;k<g.has.length;k++){try{x(g.has[k],a,j)}catch(l){if(l===42)continue}h=!1;break}}h&&g.expr&&(h=p(g.expr,a)),b[0]!==">"&&b[0].pc!==":root"&&f.push(b),h&&(b[0]===">"?b.length>2&&(h=!1,f.push(b.slice(2))):b.length>1&&(h=!1,f.push(b.slice(1))));return[h,f]}function v(a){if(a===null)return"null";var b=typeof a;b==="object"&&u(a)&&(b="array");return b}function u(a){return Array.isArray?Array.isArray(a):b.call(a)==="[object Array]"}function t(a,b,c){var d=b,g={},j=i(a,b);j&&j[1]===" "&&(d=b=j[0],j=i(a,b)),j&&j[1]===f.typ?(g.type=j[2],j=i(a,b=j[0])):j&&j[1]==="*"&&(j=i(a,b=j[0]));for(;;){if(j===undefined)break;if(j[1]===f.ide)g.id&&e("nmi"),g.id=j[2];else if(j[1]===f.psc)(g.pc||g.pf)&&e("mpc"),j[2]===":first-child"?(g.pf=":nth-child",g.a=0,g.b=1):j[2]===":last-child"?(g.pf=":nth-last-child",g.a=0,g.b=1):g.pc=j[2];else{if(j[1]!==f.psf)break;if(j[2]===":val"||j[2]===":contains")g.expr=[undefined,j[2]===":val"?"=":"*=",undefined],j=i(a,b=j[0]),j&&j[1]===" "&&(j=i(a,b=j[0])),(!j||j[1]!=="(")&&e("pex"),j=i(a,b=j[0]),j&&j[1]===" "&&(j=i(a,b=j[0])),(!j||j[1]!==f.str)&&e("sex"),g.expr[2]=j[2],j=i(a,b=j[0]),j&&j[1]===" "&&(j=i(a,b=j[0])),(!j||j[1]!==")")&&e("epex");else if(j[2]===":has"){j=i(a,b=j[0]),j&&j[1]===" "&&(j=i(a,b=j[0])),(!j||j[1]!=="(")&&e("pex");var k=q(a,j[0],!0);j[0]=k[0],g.has||(g.has=[]),g.has.push(k[1])}else if(j[2]===":expr"){g.expr&&e("mexp");var l=o(a,j[0]);j[0]=l[0],g.expr=l[1]}else{(g.pc||g.pf)&&e("mpc"),g.pf=j[2];var m=h.exec(a.substr(j[0]));m||e("mepf"),m[5]?(g.a=2,g.b=m[5]==="odd"?1:0):m[6]?(g.a=0,g.b=parseInt(m[6],10)):(g.a=parseInt((m[1]?m[1]:"+")+(m[2]?m[2]:"1"),10),g.b=m[3]?parseInt(m[3]+m[4],10):0),j[0]+=m[0].length}}j=i(a,b=j[0])}d===b&&e("se");return[b,g]}function s(a){if(a[0]===","){var b=[","];for(var c=c;c<a.length;c++){var d=r(d[c]);b=b.concat(d[0]===","?d.slice(1):d)}return b}return r(a)}function r(a){var b=[],c;for(var d=0;d<a.length;d++)if(a[d]==="~"){if(d<2||a[d-2]!=">")c=a.slice(0,d-1),c=c.concat([{has:[[{pc:":root"},">",a[d-1]]]},">"]),c=c.concat(a.slice(d+1)),b.push(c);if(d>1){var e=a[d-2]===">"?d-3:d-2;c=a.slice(0,e);var f={};for(var g in a[e])a[e].hasOwnProperty(g)&&(f[g]=a[e][g]);f.has||(f.has=[]),f.has.push([{pc:":root"},">",a[d-1]]),c=c.concat(f,">",a.slice(d+1)),b.push(c)}break}if(d==a.length)return a;return b.length>1?[","].concat(b):b[0]}function q(a,b,c,d){c||(d={});var f=[],g,h;b||(b=0);for(;;){var j=t(a,b,d);f.push(j[1]),j=i(a,b=j[0]),j&&j[1]===" "&&(j=i(a,b=j[0]));if(!j)break;if(j[1]===">"||j[1]==="~")j[1]==="~"&&(d.usesSiblingOp=!0),f.push(j[1]),b=j[0];else if(j[1]===",")g===undefined?g=[",",f]:g.push(f),f=[],b=j[0];else if(j[1]===")"){c||e("ucp"),h=1,b=j[0];break}}c&&!h&&e("mcp"),g&&g.push(f);var k;!c&&d.usesSiblingOp?k=s(g?g:f):k=g?g:f;return[b,k]}function p(a,b){if(a===undefined)return b;if(a===null||typeof a!="object")return a;var c=p(a[0],b),d=p(a[2],b);return l[a[1]][1](c,d)}function o(a,b){function c(a){return typeof a!="object"||a===null?a:a[0]==="("?c(a[1]):[c(a[0]),a[1],c(a[2])]}var d=n(a,b?b:0);return[d[0],c(d[1])]}function n(a,b){b||(b=0);var c=m(a,b),d;if(c&&c[1]==="("){d=n(a,c[0]);var f=m(a,d[0]);(!f||f[1]!==")")&&e("epex"),b=f[0],d=["(",d[1]]}else!c||c[1]&&c[1]!="x"?e("ee"):(d=c[1]==="x"?undefined:c[2],b=c[0]);var g=m(a,b);if(!g||g[1]==")")return[b,d];(g[1]=="x"||!g[1])&&e("bop");var h=n(a,g[0]);b=h[0],h=h[1];var i;if(typeof h!="object"||h[0]==="("||l[g[1]][0]<l[h[1]][0])i=[d,g[1],h];else{i=h;while(typeof h[0]=="object"&&h[0][0]!="("&&l[g[1]][0]>=l[h[0][1]][0])h=h[0];h[0]=[d,g[1],h[0]]}return[b,i]}function m(a,b){var d,e=j.exec(a.substr(b));if(e){b+=e[0].length,d=e[1]||e[2]||e[3]||e[5]||e[6];if(e[1]||e[2]||e[3])return[b,0,c(d)];if(e[4])return[b,0,undefined];return[b,d]}}function k(a,b){return typeof a===b}function i(a,b){b||(b=0);var d=g.exec(a.substr(b));if(!d)return undefined;b+=d[0].length;var h;d[1]?h=[b," "]:d[2]?h=[b,d[0]]:d[3]?h=[b,f.typ,d[0]]:d[4]?h=[b,f.psc,d[0]]:d[5]?h=[b,f.psf,d[0]]:d[6]?e("upc"):d[8]?h=[b,d[7]?f.ide:f.str,c(d[8])]:d[9]?e("ujs"):d[10]&&(h=[b,f.ide,d[10].replace(/\\([^\r\n\f0-9a-fA-F])/g,"$1")]);return h}function e(a){throw new Error(d[a])}function c(a){try{if(JSON&&JSON.parse)return JSON.parse(a);return(new Function("return "+a))()}catch(b){e("ijs")}}var b=Object.prototype.toString,d={bop:"binary operator expected",ee:"expression expected",epex:"closing paren expected ')'",ijs:"invalid json string",mcp:"missing closing paren",mepf:"malformed expression in pseudo-function",mexp:"multiple expressions not allowed",mpc:"multiple pseudo classes (:xxx) not allowed",nmi:"multiple ids not allowed",pex:"opening paren expected '('",se:"selector expected",sex:"string expected",sra:"string required after '.'",uc:"unrecognized char",ucp:"unexpected closing paren",ujs:"unclosed json string",upc:"unrecognized pseudo class"},f={psc:1,psf:2,typ:3,str:4,ide:5},g=new RegExp('^(?:([\\r\\n\\t\\ ]+)|([~*,>\\)\\(])|(string|boolean|null|array|object|number)|(:(?:root|first-child|last-child|only-child))|(:(?:nth-child|nth-last-child|has|expr|val|contains))|(:\\w+)|(?:(\\.)?(\\"(?:[^\\\\]|\\\\[^\\"])*\\"))|(\\")|\\.((?:[_a-zA-Z]|[^\\0-\\0177]|\\\\[^\\r\\n\\f0-9a-fA-F])(?:[_a-zA-Z0-9\\-]|[^\\u0000-\\u0177]|(?:\\\\[^\\r\\n\\f0-9a-fA-F]))*))'),h=/^\s*\(\s*(?:([+\-]?)([0-9]*)n\s*(?:([+\-])\s*([0-9]))?|(odd|even)|([+\-]?[0-9]+))\s*\)/,j=new RegExp('^\\s*(?:(true|false|null)|(-?\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?)|("(?:[^\\]|\\[^"])*")|(x)|(&&|\\|\\||[\\$\\^<>!\\*]=|[=+\\-*/%<>])|([\\(\\)]))'),l={"*":[9,function(a,b){return a*b}],"/":[9,function(a,b){return a/b}],"%":[9,function(a,b){return a%b}],"+":[7,function(a,b){return a+b}],"-":[7,function(a,b){return a-b}],"<=":[5,function(a,b){return k(a,"number")&&k(b,"number")&&a<=b}],">=":[5,function(a,b){return k(a,"number")&&k(b,"number")&&a>=b}],"$=":[5,function(a,b){return k(a,"string")&&k(b,"string")&&a.lastIndexOf(b)===a.length-b.length}],"^=":[5,function(a,b){return k(a,"string")&&k(b,"string")&&a.indexOf(b)===0}],"*=":[5,function(a,b){return k(a,"string")&&k(b,"string")&&a.indexOf(b)!==-1}],">":[5,function(a,b){return k(a,"number")&&k(b,"number")&&a>b}],"<":[5,function(a,b){return k(a,"number")&&k(b,"number")&&a<b}],"=":[3,function(a,b){return a===b}],"!=":[3,function(a,b){return a!==b}],"&&":[2,function(a,b){return a&&b}],"||":[1,function(a,b){return a||b}]};a._lex=i,a._parse=q,a.match=function(a,b){return z(a).match(b)},a.forEach=function(a,b,c){return z(a).forEach(b,c)},a.compile=z})(typeof exports=="undefined"?window.JSONSelect={}:exports)
\ No newline at end of file
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/dist/jsonselect.min.js.gz b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/dist/jsonselect.min.js.gz
new file mode 100644
index 0000000..4eb9b37
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/dist/jsonselect.min.js.gz
Binary files differ
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/jsonselect.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/jsonselect.js
new file mode 100644
index 0000000..f0611ea
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/jsonselect.js
@@ -0,0 +1,522 @@
+/*! Copyright (c) 2011, Lloyd Hilaiel, ISC License */
+/*
+ * This is the JSONSelect reference implementation, in javascript.
+ */
+(function(exports) {
+
+    var // localize references
+    toString = Object.prototype.toString;
+
+    function jsonParse(str) {
+      try {
+          if(JSON && JSON.parse){
+              return JSON.parse(str);
+          }
+          return (new Function("return " + str))();
+      } catch(e) {
+        te("ijs", e.message);
+      }
+    }
+
+    // emitted error codes.
+    var errorCodes = {
+        "bop":  "binary operator expected",
+        "ee":   "expression expected",
+        "epex": "closing paren expected ')'",
+        "ijs":  "invalid json string",
+        "mcp":  "missing closing paren",
+        "mepf": "malformed expression in pseudo-function",
+        "mexp": "multiple expressions not allowed",
+        "mpc":  "multiple pseudo classes (:xxx) not allowed",
+        "nmi":  "multiple ids not allowed",
+        "pex":  "opening paren expected '('",
+        "se":   "selector expected",
+        "sex":  "string expected",
+        "sra":  "string required after '.'",
+        "uc":   "unrecognized char",
+        "ucp":  "unexpected closing paren",
+        "ujs":  "unclosed json string",
+        "upc":  "unrecognized pseudo class"
+    };
+
+    // throw an error message
+    function te(ec, context) {
+      throw new Error(errorCodes[ec] + ( context && " in '" + context + "'"));
+    }
+
+    // THE LEXER
+    var toks = {
+        psc: 1, // pseudo class
+        psf: 2, // pseudo class function
+        typ: 3, // type
+        str: 4, // string
+        ide: 5  // identifiers (or "classes", stuff after a dot)
+    };
+
+    // The primary lexing regular expression in jsonselect
+    var pat = new RegExp(
+        "^(?:" +
+        // (1) whitespace
+        "([\\r\\n\\t\\ ]+)|" +
+        // (2) one-char ops
+        "([~*,>\\)\\(])|" +
+        // (3) types names
+        "(string|boolean|null|array|object|number)|" +
+        // (4) pseudo classes
+        "(:(?:root|first-child|last-child|only-child))|" +
+        // (5) pseudo functions
+        "(:(?:nth-child|nth-last-child|has|expr|val|contains))|" +
+        // (6) bogusly named pseudo something or others
+        "(:\\w+)|" +
+        // (7 & 8) identifiers and JSON strings
+        "(?:(\\.)?(\\\"(?:[^\\\\\\\"]|\\\\[^\\\"])*\\\"))|" +
+        // (8) bogus JSON strings missing a trailing quote
+        "(\\\")|" +
+        // (9) identifiers (unquoted)
+        "\\.((?:[_a-zA-Z]|[^\\0-\\0177]|\\\\[^\\r\\n\\f0-9a-fA-F])(?:[_a-zA-Z0-9\\-]|[^\\u0000-\\u0177]|(?:\\\\[^\\r\\n\\f0-9a-fA-F]))*)" +
+        ")"
+    );
+
+    // A regular expression for matching "nth expressions" (see grammar, what :nth-child() eats)
+    var nthPat = /^\s*\(\s*(?:([+\-]?)([0-9]*)n\s*(?:([+\-])\s*([0-9]))?|(odd|even)|([+\-]?[0-9]+))\s*\)/;
+    function lex(str, off) {
+        if (!off) off = 0;
+        var m = pat.exec(str.substr(off));
+        if (!m) return undefined;
+        off+=m[0].length;
+        var a;
+        if (m[1]) a = [off, " "];
+        else if (m[2]) a = [off, m[0]];
+        else if (m[3]) a = [off, toks.typ, m[0]];
+        else if (m[4]) a = [off, toks.psc, m[0]];
+        else if (m[5]) a = [off, toks.psf, m[0]];
+        else if (m[6]) te("upc", str);
+        else if (m[8]) a = [off, m[7] ? toks.ide : toks.str, jsonParse(m[8])];
+        else if (m[9]) te("ujs", str);
+        else if (m[10]) a = [off, toks.ide, m[10].replace(/\\([^\r\n\f0-9a-fA-F])/g,"$1")];
+        return a;
+    }
+
+    // THE EXPRESSION SUBSYSTEM
+
+    var exprPat = new RegExp(
+            // skip and don't capture leading whitespace
+            "^\\s*(?:" +
+            // (1) simple vals
+            "(true|false|null)|" + 
+            // (2) numbers
+            "(-?\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?)|" +
+            // (3) strings
+            "(\"(?:[^\\]|\\[^\"])*\")|" +
+            // (4) the 'x' value placeholder
+            "(x)|" +
+            // (5) binops
+            "(&&|\\|\\||[\\$\\^<>!\\*]=|[=+\\-*/%<>])|" +
+            // (6) parens
+            "([\\(\\)])" +
+            ")"
+    );
+
+    function is(o, t) { return typeof o === t; }
+    var operators = {
+        '*':  [ 9, function(lhs, rhs) { return lhs * rhs; } ],
+        '/':  [ 9, function(lhs, rhs) { return lhs / rhs; } ],
+        '%':  [ 9, function(lhs, rhs) { return lhs % rhs; } ],
+        '+':  [ 7, function(lhs, rhs) { return lhs + rhs; } ],
+        '-':  [ 7, function(lhs, rhs) { return lhs - rhs; } ],
+        '<=': [ 5, function(lhs, rhs) { return is(lhs, 'number') && is(rhs, 'number') && lhs <= rhs; } ],
+        '>=': [ 5, function(lhs, rhs) { return is(lhs, 'number') && is(rhs, 'number') && lhs >= rhs; } ],
+        '$=': [ 5, function(lhs, rhs) { return is(lhs, 'string') && is(rhs, 'string') && lhs.lastIndexOf(rhs) === lhs.length - rhs.length; } ],
+        '^=': [ 5, function(lhs, rhs) { return is(lhs, 'string') && is(rhs, 'string') && lhs.indexOf(rhs) === 0; } ],
+        '*=': [ 5, function(lhs, rhs) { return is(lhs, 'string') && is(rhs, 'string') && lhs.indexOf(rhs) !== -1; } ],
+        '>':  [ 5, function(lhs, rhs) { return is(lhs, 'number') && is(rhs, 'number') && lhs > rhs; } ],
+        '<':  [ 5, function(lhs, rhs) { return is(lhs, 'number') && is(rhs, 'number') && lhs < rhs; } ],
+        '=':  [ 3, function(lhs, rhs) { return lhs === rhs; } ],
+        '!=': [ 3, function(lhs, rhs) { return lhs !== rhs; } ],
+        '&&': [ 2, function(lhs, rhs) { return lhs && rhs; } ],
+        '||': [ 1, function(lhs, rhs) { return lhs || rhs; } ]
+    };
+
+    function exprLex(str, off) {
+        var v, m = exprPat.exec(str.substr(off));
+        if (m) {
+            off += m[0].length;
+            v = m[1] || m[2] || m[3] || m[5] || m[6];
+            if (m[1] || m[2] || m[3]) return [off, 0, jsonParse(v)];
+            else if (m[4]) return [off, 0, undefined];
+            return [off, v];
+        }
+    }
+
+    function exprParse2(str, off) {
+        if (!off) off = 0;
+        // first we expect a value or a '('
+        var l = exprLex(str, off),
+            lhs;
+        if (l && l[1] === '(') {
+            lhs = exprParse2(str, l[0]);
+            var p = exprLex(str, lhs[0]);
+            if (!p || p[1] !== ')') te('epex', str);
+            off = p[0];
+            lhs = [ '(', lhs[1] ];
+        } else if (!l || (l[1] && l[1] != 'x')) {
+            te("ee", str + " - " + ( l[1] && l[1] ));
+        } else {
+            lhs = ((l[1] === 'x') ? undefined : l[2]);
+            off = l[0];
+        }
+
+        // now we expect a binary operator or a ')'
+        var op = exprLex(str, off);
+        if (!op || op[1] == ')') return [off, lhs];
+        else if (op[1] == 'x' || !op[1]) {
+            te('bop', str + " - " + ( op[1] && op[1] ));
+        }
+
+        // tail recursion to fetch the rhs expression
+        var rhs = exprParse2(str, op[0]);
+        off = rhs[0];
+        rhs = rhs[1];
+
+        // and now precedence!  how shall we put everything together?
+        var v;
+        if (typeof rhs !== 'object' || rhs[0] === '(' || operators[op[1]][0] < operators[rhs[1]][0] ) {
+            v = [lhs, op[1], rhs];
+        }
+        else {
+            v = rhs;
+            while (typeof rhs[0] === 'object' && rhs[0][0] != '(' && operators[op[1]][0] >= operators[rhs[0][1]][0]) {
+                rhs = rhs[0];
+            }
+            rhs[0] = [lhs, op[1], rhs[0]];
+        }
+        return [off, v];
+    }
+
+    function exprParse(str, off) {
+        function deparen(v) {
+            if (typeof v !== 'object' || v === null) return v;
+            else if (v[0] === '(') return deparen(v[1]);
+            else return [deparen(v[0]), v[1], deparen(v[2])];
+        }
+        var e = exprParse2(str, off ? off : 0);
+        return [e[0], deparen(e[1])];
+    }
+
+    function exprEval(expr, x) {
+        if (expr === undefined) return x;
+        else if (expr === null || typeof expr !== 'object') {
+            return expr;
+        }
+        var lhs = exprEval(expr[0], x),
+            rhs = exprEval(expr[2], x);
+        return operators[expr[1]][1](lhs, rhs);
+    }
+
+    // THE PARSER
+
+    function parse(str, off, nested, hints) {
+        if (!nested) hints = {};
+
+        var a = [], am, readParen;
+        if (!off) off = 0; 
+
+        while (true) {
+            var s = parse_selector(str, off, hints);
+            a.push(s[1]);
+            s = lex(str, off = s[0]);
+            if (s && s[1] === " ") s = lex(str, off = s[0]);
+            if (!s) break;
+            // now we've parsed a selector, and have something else...
+            if (s[1] === ">" || s[1] === "~") {
+                if (s[1] === "~") hints.usesSiblingOp = true;
+                a.push(s[1]);
+                off = s[0];
+            } else if (s[1] === ",") {
+                if (am === undefined) am = [ ",", a ];
+                else am.push(a);
+                a = [];
+                off = s[0];
+            } else if (s[1] === ")") {
+                if (!nested) te("ucp", s[1]);
+                readParen = 1;
+                off = s[0];
+                break;
+            }
+        }
+        if (nested && !readParen) te("mcp", str);
+        if (am) am.push(a);
+        var rv;
+        if (!nested && hints.usesSiblingOp) {
+            rv = normalize(am ? am : a);
+        } else {
+            rv = am ? am : a;
+        }
+        return [off, rv];
+    }
+
+    function normalizeOne(sel) {
+        var sels = [], s;
+        for (var i = 0; i < sel.length; i++) {
+            if (sel[i] === '~') {
+                // `A ~ B` maps to `:has(:root > A) > B`
+                // `Z A ~ B` maps to `Z :has(:root > A) > B, Z:has(:root > A) > B`
+                // This first clause, takes care of the first case, and the first half of the latter case.
+                if (i < 2 || sel[i-2] != '>') {
+                    s = sel.slice(0,i-1);
+                    s = s.concat([{has:[[{pc: ":root"}, ">", sel[i-1]]]}, ">"]);
+                    s = s.concat(sel.slice(i+1));
+                    sels.push(s);
+                }
+                // here we take care of the second half of above:
+                // (`Z A ~ B` maps to `Z :has(:root > A) > B, Z :has(:root > A) > B`)
+                // and a new case:
+                // Z > A ~ B maps to Z:has(:root > A) > B
+                if (i > 1) {
+                    var at = sel[i-2] === '>' ? i-3 : i-2;
+                    s = sel.slice(0,at);
+                    var z = {};
+                    for (var k in sel[at]) if (sel[at].hasOwnProperty(k)) z[k] = sel[at][k];
+                    if (!z.has) z.has = [];
+                    z.has.push([{pc: ":root"}, ">", sel[i-1]]);
+                    s = s.concat(z, '>', sel.slice(i+1));
+                    sels.push(s);
+                }
+                break;
+            }
+        }
+        if (i == sel.length) return sel;
+        return sels.length > 1 ? [','].concat(sels) : sels[0];
+    }
+
+    function normalize(sels) {
+        if (sels[0] === ',') {
+            var r = [","];
+            for (var i = i; i < sels.length; i++) {
+                var s = normalizeOne(s[i]);
+                r = r.concat(s[0] === "," ? s.slice(1) : s);
+            }
+            return r;
+        } else {
+            return normalizeOne(sels);
+        }
+    }
+
+    function parse_selector(str, off, hints) {
+        var soff = off;
+        var s = { };
+        var l = lex(str, off);
+        // skip space
+        if (l && l[1] === " ") { soff = off = l[0]; l = lex(str, off); }
+        if (l && l[1] === toks.typ) {
+            s.type = l[2];
+            l = lex(str, (off = l[0]));
+        } else if (l && l[1] === "*") {
+            // don't bother representing the universal sel, '*' in the
+            // parse tree, cause it's the default
+            l = lex(str, (off = l[0]));
+        }
+
+        // now support either an id or a pc
+        while (true) {
+            if (l === undefined) {
+                break;
+            } else if (l[1] === toks.ide) {
+                if (s.id) te("nmi", l[1]);
+                s.id = l[2];
+            } else if (l[1] === toks.psc) {
+                if (s.pc || s.pf) te("mpc", l[1]);
+                // collapse first-child and last-child into nth-child expressions
+                if (l[2] === ":first-child") {
+                    s.pf = ":nth-child";
+                    s.a = 0;
+                    s.b = 1;
+                } else if (l[2] === ":last-child") {
+                    s.pf = ":nth-last-child";
+                    s.a = 0;
+                    s.b = 1;
+                } else {
+                    s.pc = l[2];
+                }
+            } else if (l[1] === toks.psf) {
+                if (l[2] === ":val" || l[2] === ":contains") {
+                    s.expr = [ undefined, l[2] === ":val" ? "=" : "*=", undefined];
+                    // any amount of whitespace, followed by paren, string, paren
+                    l = lex(str, (off = l[0]));
+                    if (l && l[1] === " ") l = lex(str, off = l[0]);
+                    if (!l || l[1] !== "(") te("pex", str);
+                    l = lex(str, (off = l[0]));
+                    if (l && l[1] === " ") l = lex(str, off = l[0]);
+                    if (!l || l[1] !== toks.str) te("sex", str);
+                    s.expr[2] = l[2];
+                    l = lex(str, (off = l[0]));
+                    if (l && l[1] === " ") l = lex(str, off = l[0]);
+                    if (!l || l[1] !== ")") te("epex", str);
+                } else if (l[2] === ":has") {
+                    // any amount of whitespace, followed by paren
+                    l = lex(str, (off = l[0]));
+                    if (l && l[1] === " ") l = lex(str, off = l[0]);
+                    if (!l || l[1] !== "(") te("pex", str);
+                    var h = parse(str, l[0], true);
+                    l[0] = h[0];
+                    if (!s.has) s.has = [];
+                    s.has.push(h[1]);
+                } else if (l[2] === ":expr") {
+                    if (s.expr) te("mexp", str);
+                    var e = exprParse(str, l[0]);
+                    l[0] = e[0];
+                    s.expr = e[1];
+                } else {
+                    if (s.pc || s.pf ) te("mpc", str);
+                    s.pf = l[2];
+                    var m = nthPat.exec(str.substr(l[0]));
+                    if (!m) te("mepf", str);
+                    if (m[5]) {
+                        s.a = 2;
+                        s.b = (m[5] === "odd") ? 1 : 0;
+                    } else if (m[6]) {
+                        s.a = 0;
+                        s.b = parseInt(m[6], 10);
+                    } else {
+                        s.a = parseInt((m[1] ? m[1] : "+") + (m[2] ? m[2] : "1"),10);
+                        s.b = m[3] ? parseInt(m[3] + m[4],10) : 0;
+                    }
+                    l[0] += m[0].length;
+                }
+            } else {
+                break;
+            }
+            l = lex(str, (off = l[0]));
+        }
+
+        // now if we didn't actually parse anything it's an error
+        if (soff === off) te("se", str);
+
+        return [off, s];
+    }
+
+    // THE EVALUATOR
+
+    function isArray(o) {
+        return Array.isArray ? Array.isArray(o) : 
+          toString.call(o) === "[object Array]";
+    }
+
+    function mytypeof(o) {
+        if (o === null) return "null";
+        var to = typeof o;
+        if (to === "object" && isArray(o)) to = "array";
+        return to;
+    }
+
+    function mn(node, sel, id, num, tot) {
+        var sels = [];
+        var cs = (sel[0] === ">") ? sel[1] : sel[0];
+        var m = true, mod;
+        if (cs.type) m = m && (cs.type === mytypeof(node));
+        if (cs.id)   m = m && (cs.id === id);
+        if (m && cs.pf) {
+            if (cs.pf === ":nth-last-child") num = tot - num;
+            else num++;
+            if (cs.a === 0) {
+                m = cs.b === num;
+            } else {
+                mod = ((num - cs.b) % cs.a);
+
+                m = (!mod && ((num*cs.a + cs.b) >= 0));
+            }
+        }
+        if (m && cs.has) {
+            // perhaps we should augment forEach to handle a return value
+            // that indicates "client cancels traversal"?
+            var bail = function() { throw 42; };
+            for (var i = 0; i < cs.has.length; i++) {
+                try {
+                    forEach(cs.has[i], node, bail);
+                } catch (e) {
+                    if (e === 42) continue;
+                }
+                m = false;
+                break;
+            }
+        }
+        if (m && cs.expr) {
+            m = exprEval(cs.expr, node);
+        }
+        // should we repeat this selector for descendants?
+        if (sel[0] !== ">" && sel[0].pc !== ":root") sels.push(sel);
+
+        if (m) {
+            // is there a fragment that we should pass down?
+            if (sel[0] === ">") { if (sel.length > 2) { m = false; sels.push(sel.slice(2)); } }
+            else if (sel.length > 1) { m = false; sels.push(sel.slice(1)); }
+        }
+
+        return [m, sels];
+    }
+
+    function forEach(sel, obj, fun, id, num, tot) {
+        var a = (sel[0] === ",") ? sel.slice(1) : [sel],
+        a0 = [],
+        call = false,
+        i = 0, j = 0, k, x;
+        for (i = 0; i < a.length; i++) {
+            x = mn(obj, a[i], id, num, tot);
+            if (x[0]) {
+                call = true;
+            }
+            for (j = 0; j < x[1].length; j++) {
+                a0.push(x[1][j]);
+            }
+        }
+        if (a0.length && typeof obj === "object") {
+            if (a0.length >= 1) {
+                a0.unshift(",");
+            }
+            if (isArray(obj)) {
+                for (i = 0; i < obj.length; i++) {
+                    forEach(a0, obj[i], fun, undefined, i, obj.length);
+                }
+            } else {
+                for (k in obj) {
+                    if (obj.hasOwnProperty(k)) {
+                        forEach(a0, obj[k], fun, k);
+                    }
+                }
+            }
+        }
+        if (call && fun) {
+            fun(obj);
+        }
+    }
+
+    function match(sel, obj) {
+        var a = [];
+        forEach(sel, obj, function(x) {
+            a.push(x);
+        });
+        return a;
+    }
+
+    function compile(sel) {
+        return {
+            sel: parse(sel)[1],
+            match: function(obj){
+                return match(this.sel, obj);
+            },
+            forEach: function(obj, fun) {
+                return forEach(this.sel, obj, fun);
+            }
+        };
+    }
+
+    exports._lex = lex;
+    exports._parse = parse;
+    exports.match = function (sel, obj) {
+        return compile(sel).match(obj);
+    };
+    exports.forEach = function(sel, obj, fun) {
+        return compile(sel).forEach(obj, fun);
+    };
+    exports.compile = compile;
+})(typeof exports === "undefined" ? (window.JSONSelect = {}) : exports);
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/conformance_tests.html b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/conformance_tests.html
new file mode 100644
index 0000000..a53dfd0
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/conformance_tests.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title> JSONSelect conformance tests </title>
+  <link href='css/style.css' type='text/css' rel='stylesheet'>
+</head>
+<body>
+These are the the official JSONQuery conformance tests.  <button id="runit">run tests</button>
+<div id="tests">
+</div>
+</body>
+<script src="../jsonselect.js"></script>
+<script src="js/jquery-1.6.1.min.js"></script>
+<script src="js/conf_tests.js"></script>
+</html>
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/css/style.css b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/css/style.css
new file mode 100644
index 0000000..f62cc9d
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/css/style.css
@@ -0,0 +1,43 @@
+pre {
+  color: #ddd;
+  background-color: #333;
+  padding: 1em;
+  border-radius: 1em;
+  -moz-border-radius: 1em;
+  -webkit-border-radius: 1em;
+}
+
+pre.selector {
+    text-align: center;
+    padding: .5em;
+    cursor: pointer;
+}
+
+pre.document {
+    max-height: 150px;
+    overflow: auto;
+}
+
+pre.success {
+    background-color: #363;
+}
+
+pre.failure {
+    background-color: #633;
+}
+
+table {
+    width: 100%;
+}
+
+table td {
+    width: 50%;
+}
+
+table pre {
+    height: 100%;
+}
+
+h1,h2 {
+    text-align: center;
+}
\ No newline at end of file
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/js/conf_tests.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/js/conf_tests.js
new file mode 100644
index 0000000..42711ea
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/js/conf_tests.js
@@ -0,0 +1,77 @@
+$(document).ready(function() {
+    var tests = {};
+
+    $("#runit").click(function() {
+        for (var k in tests) {
+            var obj = JSON.parse($("." + k + "_document").text());
+            for (var i = 0; i < tests[k].length; i++) {
+                var n = tests[k][i];
+                var cl = k + "_" + n;
+                var b = $("." + cl + "_output.before");
+                var a = $("." + cl + "_output.after");
+                var s = $("." + cl + "_selector.selector");
+                try {
+                    a.text("");
+                    JSONSelect.forEach(s.text(), obj, function(m) {
+                        a.text($.trim(a.text() + "\n" + JSON.stringify(m, null, "    ")));
+                    });
+                } catch(e) {
+                    a.text("Error: " + e);
+                }
+                if (a.text() === b.text()) s.addClass("success").removeClass("failure");
+                else s.addClass("failure").removeClass("success");
+            }
+        }
+    });
+
+    function fetchFile(p, c) {
+        $.get(p, function (data) {
+            $("." + c).text($.trim(data));
+        });
+    }
+
+    function renderTests() {
+        function setClickToggle(cTarget, node) {
+            cTarget.click(function() { node.toggle("medium"); });
+        }
+
+        var c = $("<div/>");
+        for (var k in tests) {
+            c.append($("<h1/>").text("document: " + k));
+            var cl = k + "_document";
+            c.append($("<pre/>").addClass(cl).addClass("document").text("loading document..."));
+            fetchFile("tests/" + k + ".json", cl);
+            for (var i = 0; i < tests[k].length; i++) {
+                var n = tests[k][i];
+                var cl = k + "_" + n + "_selector";
+                var s = $("<pre/>").addClass(cl).addClass("selector").text("loading selector...");
+                c.append(s);
+                fetchFile("tests/" + k + "_" + n + ".selector", cl);
+                cl = k + "_" + n + "_output";
+                var t = $("<table/>").append($("<tr/>").append(
+                    $("<td/>").append($("<pre/>").addClass(cl).addClass("before").text("loading output..."))).append(
+                    $("<td/>").append($("<pre/>").addClass(cl).addClass("after").text("... test output ..."))));
+
+                c.append(t);
+                t.hide();
+                setClickToggle(s, t);
+                fetchFile("tests/" + k + "_" + n + ".output", cl + ".before");
+            }
+        }
+        c.appendTo($("#tests"));
+    }
+
+    $.get("tests/alltests.txt", function (data) {
+        var lines = data.split("\n");
+        for (var i = 0; i < lines.length; i++) {
+            var f = $.trim(lines[i]);
+            if (f.length == 0) continue;
+            var m = /^([A-Za-z]+)_(.+)\.selector$/.exec(f);
+            if (m) {
+                if (!tests.hasOwnProperty(m[1])) tests[m[1]] = [];
+                tests[m[1]].push(m[2]);
+            }
+        }
+        renderTests();
+    });
+});
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/js/doctest.css b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/js/doctest.css
new file mode 100644
index 0000000..7403feb
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/js/doctest.css
@@ -0,0 +1,69 @@
+pre {
+  overflow: auto;
+}
+
+pre.doctest {
+  border-left: 3px solid #99f;
+  padding-left: 1em;
+}
+
+pre.output {
+  border-left: 3px solid #9f9;
+  padding-left: 1em;
+}
+
+.doctest-example-prompt {
+  color: #333;
+}
+
+.doctest-success {
+  color: #060;
+}
+
+.doctest-failure {
+  color: #600;
+}
+
+.doctest-example-detail {
+  color: #060;
+  font-weight: bold;
+}
+
+a.doctest-failure-link {
+  text-decoration: none;
+}
+
+a.doctest-failure-link:hover {
+  text-decoration: underline;
+}
+
+.doctest-example:target {
+  border-left: 3px solid #f00;
+}
+
+div.test:target {
+  border: 3px solid #ff0;
+}
+
+div.test {
+  border: 1px solid #999;
+  margin-bottom: 1em;
+}
+
+div.test .test-id {
+  position: relative;
+  float: right;
+  background-color: #000;
+  color: #bbb;
+  padding: 3px;
+}
+
+div.test .test-id a:link,
+div.test .test-id a:visited {
+  color: #bbb;
+  text-decoration: none;
+}
+
+div.test .test-id a:hover {
+  text-decoration: underline;
+}
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/js/doctest.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/js/doctest.js
new file mode 100644
index 0000000..0cfb7eb
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/js/doctest.js
@@ -0,0 +1,1397 @@
+/*
+
+Javascript doctest runner
+Copyright 2006-2010 Ian Bicking
+
+This program is free software; you can redistribute it and/or modify it under
+the terms of the MIT License.
+
+*/
+
+
+function doctest(verbosity/*default=0*/, elements/*optional*/,
+                 outputId/*optional*/) {
+  var output = document.getElementById(outputId || 'doctestOutput');
+  var reporter = new doctest.Reporter(output, verbosity || 0);
+  if (elements) {
+      if (typeof elements == 'string') {
+        // Treat it as an id
+        elements = [document.getElementById(elementId)];
+      }
+      if (! elements.length) {
+          throw('No elements');
+      }
+      var suite = new doctest.TestSuite(elements, reporter);
+  } else {
+      var els = doctest.getElementsByTagAndClassName('pre', 'doctest');
+      var suite = new doctest.TestSuite(els, reporter);
+  }
+  suite.run();
+}
+
+doctest.runDoctest = function (el, reporter) {
+  logDebug('Testing element', el);
+  reporter.startElement(el);
+  if (el === null) {
+    throw('runDoctest() with a null element');
+  }
+  var parsed = new doctest.Parser(el);
+  var runner = new doctest.JSRunner(reporter);
+  runner.runParsed(parsed);
+};
+
+doctest.TestSuite = function (els, reporter) {
+  if (this === window) {
+    throw('you forgot new!');
+  }
+  this.els = els;
+  this.parsers = [];
+  for (var i=0; i<els.length; i++) {
+    this.parsers.push(new doctest.Parser(els[i]));
+  }
+  this.reporter = reporter;
+};
+
+doctest.TestSuite.prototype.run = function (ctx) {
+  if (! ctx) {
+    ctx = new doctest.Context(this);
+  }
+  if (! ctx.runner ) {
+    ctx.runner = new doctest.JSRunner(this.reporter);
+  }
+  return ctx.run();
+};
+
+// FIXME: should this just be part of TestSuite?
+doctest.Context = function (testSuite) {
+  if (this === window) {
+    throw('You forgot new!');
+  }
+  this.testSuite = testSuite;
+  this.runner = null;
+};
+
+doctest.Context.prototype.run = function (parserIndex) {
+  var self = this;
+  parserIndex = parserIndex || 0;
+  if (parserIndex >= this.testSuite.parsers.length) {
+    logInfo('All examples from all sections tested');
+    this.runner.reporter.finish();
+    return;
+  }
+  logInfo('Testing example ' + (parserIndex+1) + ' of '
+           + this.testSuite.parsers.length);
+  var runNext = function () {
+    self.run(parserIndex+1);
+  };
+  this.runner.runParsed(this.testSuite.parsers[parserIndex], 0, runNext);
+};
+
+doctest.Parser = function (el) {
+  if (this === window) {
+    throw('you forgot new!');
+  }
+  if (! el) {
+    throw('Bad call to doctest.Parser');
+  }
+  if (el.getAttribute('parsed-id')) {
+    var examplesID = el.getAttribute('parsed-id');
+    if (doctest._allExamples[examplesID]) {
+      this.examples = doctest._allExamples[examplesID];
+      return;
+    }
+  }
+  var newHTML = document.createElement('span');
+  newHTML.className = 'doctest-example-set';
+  var examplesID = doctest.genID('example-set');
+  newHTML.setAttribute('id', examplesID);
+  el.setAttribute('parsed-id', examplesID);
+  var text = doctest.getText(el);
+  var lines = text.split(/(?:\r\n|\r|\n)/);
+  this.examples = [];
+  var example_lines = [];
+  var output_lines = [];
+  for (var i=0; i<lines.length; i++) {
+    var line = lines[i];
+    if (/^[$]/.test(line)) {
+      if (example_lines.length) {
+        var ex = new doctest.Example(example_lines, output_lines);
+        this.examples.push(ex);
+        newHTML.appendChild(ex.createSpan());
+      }
+      example_lines = [];
+      output_lines = [];
+      line = line.substr(1).replace(/ *$/, '').replace(/^ /, '');
+      example_lines.push(line);
+    } else if (/^>/.test(line)) {
+      if (! example_lines.length) {
+        throw('Bad example: '+doctest.repr(line)+'\n'
+              +'> line not preceded by $');
+      }
+      line = line.substr(1).replace(/ *$/, '').replace(/^ /, '');
+      example_lines.push(line);
+    } else {
+      output_lines.push(line);
+    }
+  }
+  if (example_lines.length) {
+    var ex = new doctest.Example(example_lines, output_lines);
+    this.examples.push(ex);
+    newHTML.appendChild(ex.createSpan());
+  }
+  el.innerHTML = '';
+  el.appendChild(newHTML);
+  doctest._allExamples[examplesID] = this.examples;
+};
+
+doctest._allExamples = {};
+
+doctest.Example = function (example, output) {
+  if (this === window) {
+    throw('you forgot new!');
+  }
+  this.example = example.join('\n');
+  this.output = output.join('\n');
+  this.htmlID = null;
+  this.detailID = null;
+};
+
+doctest.Example.prototype.createSpan = function () {
+  var id = doctest.genID('example');
+  var span = document.createElement('span');
+  span.className = 'doctest-example';
+  span.setAttribute('id', id);
+  this.htmlID = id;
+  var exampleSpan = document.createElement('span');
+  exampleSpan.className = 'doctest-example-code';
+  var exampleLines = this.example.split(/\n/);
+  for (var i=0; i<exampleLines.length; i++) {
+    var promptSpan = document.createElement('span');
+    promptSpan.className = 'doctest-example-prompt';
+    promptSpan.innerHTML = i == 0 ? '$ ' : '&gt; ';
+    exampleSpan.appendChild(promptSpan);
+    var lineSpan = document.createElement('span');
+    lineSpan.className = 'doctest-example-code-line';
+    lineSpan.appendChild(document.createTextNode(doctest.rstrip(exampleLines[i])));
+    exampleSpan.appendChild(lineSpan);
+    exampleSpan.appendChild(document.createTextNode('\n'));
+  }
+  span.appendChild(exampleSpan);
+  var outputSpan = document.createElement('span');
+  outputSpan.className = 'doctest-example-output';
+  outputSpan.appendChild(document.createTextNode(this.output));
+  span.appendChild(outputSpan);
+  span.appendChild(document.createTextNode('\n'));
+  return span;
+};
+
+doctest.Example.prototype.markExample = function (name, detail) {
+  if (! this.htmlID) {
+    return;
+  }
+  if (this.detailID) {
+    var el = document.getElementById(this.detailID);
+    el.parentNode.removeChild(el);
+    this.detailID = null;
+  }
+  var span = document.getElementById(this.htmlID);
+  span.className = span.className.replace(/ doctest-failure/, '')
+                   .replace(/ doctest-success/, '')
+                   + ' ' + name;
+  if (detail) {
+    this.detailID = doctest.genID('doctest-example-detail');
+    var detailSpan = document.createElement('span');
+    detailSpan.className = 'doctest-example-detail';
+    detailSpan.setAttribute('id', this.detailID);
+    detailSpan.appendChild(document.createTextNode(detail));
+    span.appendChild(detailSpan);
+  }
+};
+
+doctest.Reporter = function (container, verbosity) {
+  if (this === window) {
+    throw('you forgot new!');
+  }
+  if (! container) {
+    throw('No container passed to doctest.Reporter');
+  }
+  this.container = container;
+  this.verbosity = verbosity;
+  this.success = 0;
+  this.failure = 0;
+  this.elements = 0;
+};
+
+doctest.Reporter.prototype.startElement = function (el) {
+  this.elements += 1;
+  logDebug('Adding element', el);
+};
+
+doctest.Reporter.prototype.reportSuccess = function (example, output) {
+  if (this.verbosity > 0) {
+    if (this.verbosity > 1) {
+      this.write('Trying:\n');
+      this.write(this.formatOutput(example.example));
+      this.write('Expecting:\n');
+      this.write(this.formatOutput(example.output));
+      this.write('ok\n');
+    } else {
+      this.writeln(example.example + ' ... passed!');
+    }
+  }
+  this.success += 1;
+  if ((example.output.indexOf('...') >= 0
+       || example.output.indexOf('?') >= 0)
+      && output) {
+    example.markExample('doctest-success', 'Output:\n' + output);
+  } else {
+    example.markExample('doctest-success');
+  }
+};
+
+doctest.Reporter.prototype.reportFailure = function (example, output) {
+  this.write('Failed example:\n');
+  this.write('<span style="color: #00f"><a href="#'
+             + example.htmlID
+             + '" class="doctest-failure-link" title="Go to example">'
+             + this.formatOutput(example.example)
+             +'</a></span>');
+  this.write('Expected:\n');
+  this.write(this.formatOutput(example.output));
+  this.write('Got:\n');
+  this.write(this.formatOutput(output));
+  this.failure += 1;
+  example.markExample('doctest-failure', 'Actual output:\n' + output);
+};
+
+doctest.Reporter.prototype.finish = function () {
+  this.writeln((this.success+this.failure)
+               + ' tests in ' + this.elements + ' items.');
+  if (this.failure) {
+    var color = '#f00';
+  } else {
+    var color = '#0f0';
+  }
+  this.writeln('<span class="passed">' + this.success + '</span> tests of '
+               + '<span class="total">' + (this.success+this.failure) + '</span> passed, '
+               + '<span class="failed" style="color: '+color+'">'
+               + this.failure + '</span> failed.');
+};
+
+doctest.Reporter.prototype.writeln = function (text) {
+  this.write(text + '\n');
+};
+
+doctest.Reporter.prototype.write = function (text) {
+  var leading = /^[ ]*/.exec(text)[0];
+  text = text.substr(leading.length);
+  for (var i=0; i<leading.length; i++) {
+    text = String.fromCharCode(160)+text;
+  }
+  text = text.replace(/\n/g, '<br>');
+  this.container.innerHTML += text;
+};
+
+doctest.Reporter.prototype.formatOutput = function (text) {
+  if (! text) {
+    return '    <span style="color: #999">(nothing)</span>\n';
+  }
+  var lines = text.split(/\n/);
+  var output = '';
+  for (var i=0; i<lines.length; i++) {
+    output += '    '+doctest.escapeSpaces(doctest.escapeHTML(lines[i]))+'\n';
+  }
+  return output;
+};
+
+doctest.JSRunner = function (reporter) {
+  if (this === window) {
+    throw('you forgot new!');
+  }
+  this.reporter = reporter;
+};
+
+doctest.JSRunner.prototype.runParsed = function (parsed, index, finishedCallback) {
+  var self = this;
+  index = index || 0;
+  if (index >= parsed.examples.length) {
+    if (finishedCallback) {
+      finishedCallback();
+    }
+    return;
+  }
+  var example = parsed.examples[index];
+  if (typeof example == 'undefined') {
+    throw('Undefined example (' + (index+1) + ' of ' + parsed.examples.length + ')');
+  }
+  doctest._waitCond = null;
+  this.run(example);
+  var finishThisRun = function () {
+    self.finishRun(example);
+    if (doctest._AbortCalled) {
+      // FIXME: I need to find a way to make this more visible:
+      logWarn('Abort() called');
+      return;
+    }
+    self.runParsed(parsed, index+1, finishedCallback);
+  };
+  if (doctest._waitCond !== null) {
+    if (typeof doctest._waitCond == 'number') {
+      var condition = null;
+      var time = doctest._waitCond;
+      var maxTime = null;
+    } else {
+      var condition = doctest._waitCond;
+      // FIXME: shouldn't be hard-coded
+      var time = 100;
+      var maxTime = doctest._waitTimeout || doctest.defaultTimeout;
+    }
+    var start = (new Date()).getTime();
+    var timeoutFunc = function () {
+      if (condition === null
+          || condition()) {
+        finishThisRun();
+      } else {
+        // Condition not met, try again soon...
+        if ((new Date()).getTime() - start > maxTime) {
+          // Time has run out
+          var msg = 'Error: wait(' + repr(condition) + ') has timed out';
+          writeln(msg);
+          logDebug(msg);
+          logDebug('Timeout after ' + ((new Date()).getTime() - start)
+                   + ' milliseconds');
+          finishThisRun();
+          return;
+        }
+        setTimeout(timeoutFunc, time);
+      }
+    };
+    setTimeout(timeoutFunc, time);
+  } else {
+    finishThisRun();
+  }
+};
+
+doctest.formatTraceback = function (e, skipFrames) {
+  skipFrames = skipFrames || 0;
+  var lines = [];
+  if (typeof e == 'undefined' || !e) {
+    var caughtErr = null;
+    try {
+      (null).foo;
+    } catch (caughtErr) {
+      e = caughtErr;
+    }
+    skipFrames++;
+  }
+  if (e.stack) {
+    var stack = e.stack.split('\n');
+    for (var i=skipFrames; i<stack.length; i++) {
+      if (stack[i] == '@:0' || ! stack[i]) {
+        continue;
+      }
+      if (stack[i].indexOf('@') == -1) {
+        lines.push(stack[i]);
+        continue;
+      }
+      var parts = stack[i].split('@');
+      var context = parts[0];
+      parts = parts[1].split(':');
+      var filename = parts[parts.length-2].split('/');
+      filename = filename[filename.length-1];
+      var lineno = parts[parts.length-1];
+      context = context.replace('\\n', '\n');
+      if (context != '' && filename != 'doctest.js') {
+        lines.push('  ' + context + ' -> ' + filename + ':' + lineno);
+      }
+    }
+  }
+  if (lines.length) {
+    return lines;
+  } else {
+    return null;
+  }
+};
+
+doctest.logTraceback = function (e, skipFrames) {
+  var tracebackLines = doctest.formatTraceback(e, skipFrames);
+  if (! tracebackLines) {
+    return;
+  }
+  for (var i=0; i<tracebackLines.length; i++) {
+    logDebug(tracebackLines[i]);
+  }
+};
+
+doctest.JSRunner.prototype.run = function (example) {
+  this.capturer = new doctest.OutputCapturer();
+  this.capturer.capture();
+  try {
+    var result = doctest.eval(example.example);
+  } catch (e) {
+    var tracebackLines = doctest.formatTraceback(e);
+    writeln('Error: ' + (e.message || e));
+    var result = null;
+    logWarn('Error in expression: ' + example.example);
+    logDebug('Traceback for error', e);
+    if (tracebackLines) {
+      for (var i=0; i<tracebackLines.length; i++) {
+        logDebug(tracebackLines[i]);
+      }
+    }
+    if (e instanceof Abort) {
+      throw e;
+    }
+  }
+  if (typeof result != 'undefined'
+      && result !== null
+      && example.output) {
+    writeln(doctest.repr(result));
+  }
+};
+
+doctest._AbortCalled = false;
+
+doctest.Abort = function (message) {
+  if (this === window) {
+    return new Abort(message);
+  }
+  this.message = message;
+  // We register this so Abort can be raised in an async call:
+  doctest._AbortCalled = true;
+};
+
+doctest.Abort.prototype.toString = function () {
+  return this.message;
+};
+
+if (typeof Abort == 'undefined') {
+  Abort = doctest.Abort;
+}
+
+doctest.JSRunner.prototype.finishRun = function(example) {
+  this.capturer.stopCapture();
+  var success = this.checkResult(this.capturer.output, example.output);
+  if (success) {
+    this.reporter.reportSuccess(example, this.capturer.output);
+  } else {
+    this.reporter.reportFailure(example, this.capturer.output);
+    logDebug('Failure: '+doctest.repr(example.output)
+             +' != '+doctest.repr(this.capturer.output));
+    if (location.href.search(/abort/) != -1) {
+      doctest.Abort('abort on first failure');
+    }
+  }
+};
+
+doctest.JSRunner.prototype.checkResult = function (got, expected) {
+  // Make sure trailing whitespace doesn't matter:
+  got = got.replace(/ +\n/, '\n');
+  expected = expected.replace(/ +\n/, '\n');
+  got = got.replace(/[ \n\r]*$/, '') + '\n';
+  expected = expected.replace(/[ \n\r]*$/, '') + '\n';
+  if (expected == '...\n') {
+    return true;
+  }
+  expected = RegExp.escape(expected);
+  // Note: .* doesn't match newlines, [^] doesn't work on IE
+  expected = '^' + expected.replace(/\\\.\\\.\\\./g, "[\\S\\s\\r\\n]*") + '$';
+  expected = expected.replace(/\\\?/g, "[a-zA-Z0-9_.]+");
+  expected = expected.replace(/[ \t]+/g, " +");
+  expected = expected.replace(/\n/g, '\\n');
+  var re = new RegExp(expected);
+  var result = got.search(re) != -1;
+  if (! result) {
+    if (doctest.strip(got).split('\n').length > 1) {
+      // If it's only one line it's not worth showing this
+      var check = this.showCheckDifference(got, expected);
+      logWarn('Mismatch of output (line-by-line comparison follows)');
+      for (var i=0; i<check.length; i++) {
+        logDebug(check[i]);
+      }
+    }
+  }
+  return result;
+};
+
+doctest.JSRunner.prototype.showCheckDifference = function (got, expectedRegex) {
+  if (expectedRegex.charAt(0) != '^') {
+    throw 'Unexpected regex, no leading ^';
+  }
+  if (expectedRegex.charAt(expectedRegex.length-1) != '$') {
+    throw 'Unexpected regex, no trailing $';
+  }
+  expectedRegex = expectedRegex.substr(1, expectedRegex.length-2);
+  // Technically this might not be right, but this is all a heuristic:
+  var expectedRegex = expectedRegex.replace(/\(\?:\.\|\[\\r\\n\]\)\*/g, '...');
+  var expectedLines = expectedRegex.split('\\n');
+  for (var i=0; i<expectedLines.length; i++) {
+    expectedLines[i] = expectedLines[i].replace(/\.\.\./g, '(?:.|[\r\n])*');
+  }
+  var gotLines = got.split('\n');
+  var result = [];
+  var totalLines = expectedLines.length > gotLines.length ?
+    expectedLines.length : gotLines.length;
+  function displayExpectedLine(line) {
+    return line;
+    line = line.replace(/\[a-zA-Z0-9_.\]\+/g, '?');
+    line = line.replace(/ \+/g, ' ');
+    line = line.replace(/\(\?:\.\|\[\\r\\n\]\)\*/g, '...');
+    // FIXME: also unescape values? e.g., * became \*
+    return line;
+  }
+  for (var i=0; i<totalLines; i++) {
+    if (i >= expectedLines.length) {
+      result.push('got extra line: ' + repr(gotLines[i]));
+      continue;
+    } else if (i >= gotLines.length) {
+      result.push('expected extra line: ' + displayExpectedLine(expectedLines[i]));
+      continue;
+    }
+    var gotLine = gotLines[i];
+    try {
+      var expectRE = new RegExp('^' + expectedLines[i] + '$');
+    } catch (e) {
+      result.push('regex match failed: ' + repr(gotLine) + ' ('
+            + expectedLines[i] + ')');
+      continue;
+    }
+    if (gotLine.search(expectRE) != -1) {
+      result.push('match: ' + repr(gotLine));
+    } else {
+      result.push('no match: ' + repr(gotLine) + ' ('
+            + displayExpectedLine(expectedLines[i]) + ')');
+    }
+  }
+  return result;
+};
+
+// Should I really be setting this on RegExp?
+RegExp.escape = function (text) {
+  if (!arguments.callee.sRE) {
+    var specials = [
+      '/', '.', '*', '+', '?', '|',
+      '(', ')', '[', ']', '{', '}', '\\'
+    ];
+    arguments.callee.sRE = new RegExp(
+      '(\\' + specials.join('|\\') + ')', 'g'
+    );
+  }
+  return text.replace(arguments.callee.sRE, '\\$1');
+};
+
+doctest.OutputCapturer = function () {
+  if (this === window) {
+    throw('you forgot new!');
+  }
+  this.output = '';
+};
+
+doctest._output = null;
+
+doctest.OutputCapturer.prototype.capture = function () {
+  doctest._output = this;
+};
+
+doctest.OutputCapturer.prototype.stopCapture = function () {
+  doctest._output = null;
+};
+
+doctest.OutputCapturer.prototype.write = function (text) {
+  if (typeof text == 'string') {
+    this.output += text;
+  } else {
+    this.output += repr(text);
+  }
+};
+
+// Used to create unique IDs:
+doctest._idGen = 0;
+
+doctest.genID = function (prefix) {
+  prefix = prefix || 'generic-doctest';
+  var id = doctest._idGen++;
+  return prefix + '-' + doctest._idGen;
+};
+
+doctest.writeln = function () {
+  for (var i=0; i<arguments.length; i++) {
+    write(arguments[i]);
+    if (i) {
+      write(' ');
+    }
+  }
+  write('\n');
+};
+
+if (typeof writeln == 'undefined') {
+  writeln = doctest.writeln;
+}
+
+doctest.write = function (text) {
+  if (doctest._output !== null) {
+    doctest._output.write(text);
+  } else {
+    log(text);
+  }
+};
+
+if (typeof write == 'undefined') {
+  write = doctest.write;
+}
+
+doctest._waitCond = null;
+
+function wait(conditionOrTime, hardTimeout) {
+  // FIXME: should support a timeout even with a condition
+  if (typeof conditionOrTime == 'undefined'
+      || conditionOrTime === null) {
+    // same as wait-some-small-amount-of-time
+    conditionOrTime = 0;
+  }
+  doctest._waitCond = conditionOrTime;
+  doctest._waitTimeout = hardTimeout;
+};
+
+doctest.wait = wait;
+
+doctest.assert = function (expr, statement) {
+  if (typeof expr == 'string') {
+    if (! statement) {
+      statement = expr;
+    }
+    expr = doctest.eval(expr);
+  }
+  if (! expr) {
+    throw('AssertionError: '+statement);
+  }
+};
+
+if (typeof assert == 'undefined') {
+  assert = doctest.assert;
+}
+
+doctest.getText = function (el) {
+  if (! el) {
+    throw('You must pass in an element');
+  }
+  var text = '';
+  for (var i=0; i<el.childNodes.length; i++) {
+    var sub = el.childNodes[i];
+    if (sub.nodeType == 3) {
+      // TEXT_NODE
+      text += sub.nodeValue;
+    } else if (sub.childNodes) {
+      text += doctest.getText(sub);
+    }
+  }
+  return text;
+};
+
+doctest.reload = function (button/*optional*/) {
+  if (button) {
+    button.innerHTML = 'reloading...';
+    button.disabled = true;
+  }
+  location.reload();
+};
+
+/* Taken from MochiKit, with an addition to print objects */
+doctest.repr = function (o, indentString, maxLen) {
+    indentString = indentString || '';
+    if (doctest._reprTracker === null) {
+      var iAmTheTop = true;
+      doctest._reprTracker = [];
+    } else {
+      var iAmTheTop = false;
+    }
+    try {
+      if (doctest._reprTrackObj(o)) {
+        return '..recursive..';
+      }
+      if (maxLen === undefined) {
+        maxLen = 120;
+      }
+      if (typeof o == 'undefined') {
+          return 'undefined';
+      } else if (o === null) {
+          return "null";
+      }
+      try {
+          if (typeof(o.__repr__) == 'function') {
+              return o.__repr__(indentString, maxLen);
+          } else if (typeof(o.repr) == 'function' && o.repr != arguments.callee) {
+              return o.repr(indentString, maxLen);
+          }
+          for (var i=0; i<doctest.repr.registry.length; i++) {
+              var item = doctest.repr.registry[i];
+              if (item[0](o)) {
+                  return item[1](o, indentString, maxLen);
+              }
+          }
+      } catch (e) {
+          if (typeof(o.NAME) == 'string' && (
+                  o.toString == Function.prototype.toString ||
+                      o.toString == Object.prototype.toString)) {
+              return o.NAME;
+          }
+      }
+      try {
+          var ostring = (o + "");
+          if (ostring == '[object Object]' || ostring == '[object]') {
+            ostring = doctest.objRepr(o, indentString, maxLen);
+          }
+      } catch (e) {
+          return "[" + typeof(o) + "]";
+      }
+      if (typeof(o) == "function") {
+          var ostring = ostring.replace(/^\s+/, "").replace(/\s+/g, " ");
+          var idx = ostring.indexOf("{");
+          if (idx != -1) {
+              ostring = ostring.substr(o, idx) + "{...}";
+          }
+      }
+      return ostring;
+    } finally {
+      if (iAmTheTop) {
+        doctest._reprTracker = null;
+      }
+    }
+};
+
+doctest._reprTracker = null;
+
+doctest._reprTrackObj = function (obj) {
+  if (typeof obj != 'object') {
+    return false;
+  }
+  for (var i=0; i<doctest._reprTracker.length; i++) {
+    if (doctest._reprTracker[i] === obj) {
+      return true;
+    }
+  }
+  doctest._reprTracker.push(obj);
+  return false;
+};
+
+doctest._reprTrackSave = function () {
+  return doctest._reprTracker.length-1;
+};
+
+doctest._reprTrackRestore = function (point) {
+  doctest._reprTracker.splice(point, doctest._reprTracker.length - point);
+};
+
+doctest._sortedKeys = function (obj) {
+  var keys = [];
+  for (var i in obj) {
+    // FIXME: should I use hasOwnProperty?
+    if (typeof obj.prototype == 'undefined'
+        || obj[i] !== obj.prototype[i]) {
+      keys.push(i);
+    }
+  }
+  keys.sort();
+  return keys;
+};
+
+doctest.objRepr = function (obj, indentString, maxLen) {
+  var restorer = doctest._reprTrackSave();
+  var ostring = '{';
+  var keys = doctest._sortedKeys(obj);
+  for (var i=0; i<keys.length; i++) {
+    if (ostring != '{') {
+      ostring += ', ';
+    }
+    ostring += keys[i] + ': ' + doctest.repr(obj[keys[i]], indentString, maxLen);
+  }
+  ostring += '}';
+  if (ostring.length > (maxLen - indentString.length)) {
+    doctest._reprTrackRestore(restorer);
+    return doctest.multilineObjRepr(obj, indentString, maxLen);
+  }
+  return ostring;
+};
+
+doctest.multilineObjRepr = function (obj, indentString, maxLen) {
+  var keys = doctest._sortedKeys(obj);
+  var ostring = '{\n';
+  for (var i=0; i<keys.length; i++) {
+    ostring += indentString + '  ' + keys[i] + ': ';
+    ostring += doctest.repr(obj[keys[i]], indentString+'  ', maxLen);
+    if (i != keys.length - 1) {
+      ostring += ',';
+    }
+    ostring += '\n';
+  }
+  ostring += indentString + '}';
+  return ostring;
+};
+
+doctest.arrayRepr = function (obj, indentString, maxLen) {
+  var restorer = doctest._reprTrackSave();
+  var s = "[";
+  for (var i=0; i<obj.length; i++) {
+    s += doctest.repr(obj[i], indentString, maxLen);
+    if (i != obj.length-1) {
+      s += ", ";
+    }
+  }
+  s += "]";
+  if (s.length > (maxLen + indentString.length)) {
+    doctest._reprTrackRestore(restorer);
+    return doctest.multilineArrayRepr(obj, indentString, maxLen);
+  }
+  return s;
+};
+
+doctest.multilineArrayRepr = function (obj, indentString, maxLen) {
+  var s = "[\n";
+  for (var i=0; i<obj.length; i++) {
+    s += indentString + '  ' + doctest.repr(obj[i], indentString+'  ', maxLen);
+    if (i != obj.length - 1) {
+      s += ',';
+    }
+    s += '\n';
+  }
+  s += indentString + ']';
+  return s;
+};
+
+doctest.xmlRepr = function (doc, indentString) {
+  var i;
+  if (doc.nodeType == doc.DOCUMENT_NODE) {
+    return doctest.xmlRepr(doc.childNodes[0], indentString);
+  }
+  indentString = indentString || '';
+  var s = indentString + '<' + doc.tagName;
+  var attrs = [];
+  if (doc.attributes && doc.attributes.length) {
+    for (i=0; i<doc.attributes.length; i++) {
+      attrs.push(doc.attributes[i].nodeName);
+    }
+    attrs.sort();
+    for (i=0; i<attrs.length; i++) {
+      s += ' ' + attrs[i] + '="';
+      var value = doc.getAttribute(attrs[i]);
+      value = value.replace('&', '&amp;');
+      value = value.replace('"', '&quot;');
+      s += value;
+      s += '"';
+    }
+  }
+  if (! doc.childNodes.length) {
+    s += ' />';
+    return s;
+  } else {
+    s += '>';
+  }
+  var hasNewline = false;
+  for (i=0; i<doc.childNodes.length; i++) {
+    var el = doc.childNodes[i];
+    if (el.nodeType == doc.TEXT_NODE) {
+      s += doctest.strip(el.textContent);
+    } else {
+      if (! hasNewline) {
+        s += '\n';
+        hasNewline = true;
+      }
+      s += doctest.xmlRepr(el, indentString + '  ');
+      s += '\n';
+    }
+  }
+  if (hasNewline) {
+    s += indentString;
+  }
+  s += '</' + doc.tagName + '>';
+  return s;
+};
+
+doctest.repr.registry = [
+    [function (o) {
+         return typeof o == 'string';},
+     function (o) {
+         o = '"' + o.replace(/([\"\\])/g, '\\$1') + '"';
+         o = o.replace(/[\f]/g, "\\f")
+         .replace(/[\b]/g, "\\b")
+         .replace(/[\n]/g, "\\n")
+         .replace(/[\t]/g, "\\t")
+         .replace(/[\r]/g, "\\r");
+         return o;
+     }],
+    [function (o) {
+         return typeof o == 'number';},
+     function (o) {
+         return o + "";
+     }],
+    [function (o) {
+          return (typeof o == 'object' && o.xmlVersion);
+     },
+     doctest.xmlRepr],
+    [function (o) {
+         var typ = typeof o;
+         if ((typ != 'object' && ! (type == 'function' && typeof o.item == 'function')) ||
+             o === null ||
+             typeof o.length != 'number' ||
+             o.nodeType === 3) {
+             return false;
+         }
+         return true;
+     },
+     doctest.arrayRepr
+     ]];
+
+doctest.objDiff = function (orig, current) {
+  var result = {
+    added: {},
+    removed: {},
+    changed: {},
+    same: {}
+  };
+  for (var i in orig) {
+    if (! (i in current)) {
+      result.removed[i] = orig[i];
+    } else if (orig[i] !== current[i]) {
+      result.changed[i] = [orig[i], current[i]];
+    } else {
+      result.same[i] = orig[i];
+    }
+  }
+  for (i in current) {
+    if (! (i in orig)) {
+      result.added[i] = current[i];
+    }
+  }
+  return result;
+};
+
+doctest.writeDiff = function (orig, current, indentString) {
+  if (typeof orig != 'object' || typeof current != 'object') {
+    writeln(indentString + repr(orig, indentString) + ' -> ' + repr(current, indentString));
+    return;
+  }
+  indentString = indentString || '';
+  var diff = doctest.objDiff(orig, current);
+  var i, keys;
+  var any = false;
+  keys = doctest._sortedKeys(diff.added);
+  for (i=0; i<keys.length; i++) {
+    any = true;
+    writeln(indentString + '+' + keys[i] + ': '
+            + repr(diff.added[keys[i]], indentString));
+  }
+  keys = doctest._sortedKeys(diff.removed);
+  for (i=0; i<keys.length; i++) {
+    any = true;
+    writeln(indentString + '-' + keys[i] + ': '
+            + repr(diff.removed[keys[i]], indentString));
+  }
+  keys = doctest._sortedKeys(diff.changed);
+  for (i=0; i<keys.length; i++) {
+    any = true;
+    writeln(indentString + keys[i] + ': '
+            + repr(diff.changed[keys[i]][0], indentString)
+            + ' -> '
+            + repr(diff.changed[keys[i]][1], indentString));
+  }
+  if (! any) {
+    writeln(indentString + '(no changes)');
+  }
+};
+
+doctest.objectsEqual = function (ob1, ob2) {
+  var i;
+  if (typeof ob1 != 'object' || typeof ob2 != 'object') {
+    return ob1 === ob2;
+  }
+  for (i in ob1) {
+    if (ob1[i] !== ob2[i]) {
+      return false;
+    }
+  }
+  for (i in ob2) {
+    if (! (i in ob1)) {
+      return false;
+    }
+  }
+  return true;
+};
+
+doctest.getElementsByTagAndClassName = function (tagName, className, parent/*optional*/) {
+    parent = parent || document;
+    var els = parent.getElementsByTagName(tagName);
+    var result = [];
+    var regexes = [];
+    if (typeof className == 'string') {
+      className = [className];
+    }
+    for (var i=0; i<className.length; i++) {
+      regexes.push(new RegExp("\\b" + className[i] + "\\b"));
+    }
+    for (i=0; i<els.length; i++) {
+      var el = els[i];
+      if (el.className) {
+        var passed = true;
+        for (var j=0; j<regexes.length; j++) {
+          if (el.className.search(regexes[j]) == -1) {
+            passed = false;
+            break;
+          }
+        }
+        if (passed) {
+          result.push(el);
+        }
+      }
+    }
+    return result;
+};
+
+doctest.strip = function (str) {
+    str = str + "";
+    return str.replace(/\s+$/, "").replace(/^\s+/, "");
+};
+
+doctest.rstrip = function (str) {
+  str = str + "";
+  return str.replace(/\s+$/, "");
+};
+
+doctest.escapeHTML = function (s) {
+    return s.replace(/&/g, '&amp;')
+    .replace(/\"/g, "&quot;")
+    .replace(/</g, '&lt;')
+    .replace(/>/g, '&gt;');
+};
+
+doctest.escapeSpaces = function (s) {
+  return s.replace(/  /g, '&nbsp; ');
+};
+
+doctest.extend = function (obj, extendWith) {
+    for (i in extendWith) {
+        obj[i] = extendWith[i];
+    }
+    return obj;
+};
+
+doctest.extendDefault = function (obj, extendWith) {
+    for (i in extendWith) {
+        if (typeof obj[i] == 'undefined') {
+            obj[i] = extendWith[i];
+        }
+    }
+    return obj;
+};
+
+if (typeof repr == 'undefined') {
+    repr = doctest.repr;
+}
+
+doctest._consoleFunc = function (attr) {
+  if (typeof window.console != 'undefined'
+      && typeof window.console[attr] != 'undefined') {
+    if (typeof console[attr].apply === 'function') {
+      result = function() {
+        console[attr].apply(console, arguments);
+      };
+    } else {
+      result = console[attr];
+    }
+  } else {
+    result = function () {
+      // FIXME: do something
+    };
+  }
+  return result;
+};
+
+if (typeof log == 'undefined') {
+  log = doctest._consoleFunc('log');
+}
+
+if (typeof logDebug == 'undefined') {
+  logDebug = doctest._consoleFunc('log');
+}
+
+if (typeof logInfo == 'undefined') {
+  logInfo = doctest._consoleFunc('info');
+}
+
+if (typeof logWarn == 'undefined') {
+  logWarn = doctest._consoleFunc('warn');
+}
+
+doctest.eval = function () {
+  return window.eval.apply(window, arguments);
+};
+
+doctest.useCoffeeScript = function (options) {
+  options = options || {};
+  options.bare = true;
+  options.globals = true;
+  if (! options.fileName) {
+    options.fileName = 'repl';
+  }
+  if (typeof CoffeeScript == 'undefined') {
+    doctest.logWarn('coffee-script.js is not included');
+    throw 'coffee-script.js is not included';
+  }
+  doctest.eval = function (code) {
+    var src = CoffeeScript.compile(code, options);
+    logDebug('Compiled code to:', src);
+    return window.eval(src);
+  };
+};
+
+doctest.autoSetup = function (parent) {
+  var tags = doctest.getElementsByTagAndClassName('div', 'test', parent);
+  // First we'll make sure everything has an ID
+  var tagsById = {};
+  for (var i=0; i<tags.length; i++) {
+    var tagId = tags[i].getAttribute('id');
+    if (! tagId) {
+      tagId = 'test-' + (++doctest.autoSetup._idCount);
+      tags[i].setAttribute('id', tagId);
+    }
+    // FIXME: test uniqueness here, warn
+    tagsById[tagId] = tags[i];
+  }
+  // Then fill in the labels
+  for (i=0; i<tags.length; i++) {
+    var el = document.createElement('span');
+    el.className = 'test-id';
+    var anchor = document.createElement('a');
+    anchor.setAttribute('href', '#' + tags[i].getAttribute('id'));
+    anchor.appendChild(document.createTextNode(tags[i].getAttribute('id')));
+    var button = document.createElement('button');
+    button.innerHTML = 'test';
+    button.setAttribute('type', 'button');
+    button.setAttribute('test-id', tags[i].getAttribute('id'));
+    button.onclick = function () {
+      location.hash = '#' + this.getAttribute('test-id');
+      location.reload();
+    };
+    el.appendChild(anchor);
+    el.appendChild(button);
+    tags[i].insertBefore(el, tags[i].childNodes[0]);
+  }
+  // Lastly, create output areas in each section
+  for (i=0; i<tags.length; i++) {
+    var outEl = doctest.getElementsByTagAndClassName('pre', 'output', tags[i]);
+    if (! outEl.length) {
+      outEl = document.createElement('pre');
+      outEl.className = 'output';
+      outEl.setAttribute('id', tags[i].getAttribute('id') + '-output');
+    }
+  }
+  if (location.hash.length > 1) {
+    // This makes the :target CSS work, since if the hash points to an
+    // element whose id has just been added, it won't be noticed
+    location.hash = location.hash;
+  }
+  var output = document.getElementById('doctestOutput');
+  if (! tags.length) {
+    tags = document.getElementsByTagName('body');
+  }
+  if (! output) {
+    output = document.createElement('pre');
+    output.setAttribute('id', 'doctestOutput');
+    output.className = 'output';
+    tags[0].parentNode.insertBefore(output, tags[0]);
+  }
+  var reloader = document.getElementById('doctestReload');
+  if (! reloader) {
+    reloader = document.createElement('button');
+    reloader.setAttribute('type', 'button');
+    reloader.setAttribute('id', 'doctest-testall');
+    reloader.innerHTML = 'test all';
+    reloader.onclick = function () {
+      location.hash = '#doctest-testall';
+      location.reload();
+    };
+    output.parentNode.insertBefore(reloader, output);
+  }
+};
+
+doctest.autoSetup._idCount = 0;
+
+doctest.Spy = function (name, options, extraOptions) {
+  var self;
+  if (doctest.spies[name]) {
+     self = doctest.spies[name];
+     if (! options && ! extraOptions) {
+       return self;
+     }
+  } else {
+    self = function () {
+      return self.func.apply(this, arguments);
+    };
+  }
+  name = name || 'spy';
+  options = options || {};
+  if (typeof options == 'function') {
+    options = {applies: options};
+  }
+  if (extraOptions) {
+    doctest.extendDefault(options, extraOptions);
+  }
+  doctest.extendDefault(options, doctest.defaultSpyOptions);
+  self._name = name;
+  self.options = options;
+  self.called = false;
+  self.calledWait = false;
+  self.args = null;
+  self.self = null;
+  self.argList = [];
+  self.selfList = [];
+  self.writes = options.writes || false;
+  self.returns = options.returns || null;
+  self.applies = options.applies || null;
+  self.binds = options.binds || null;
+  self.throwError = options.throwError || null;
+  self.ignoreThis = options.ignoreThis || false;
+  self.wrapArgs = options.wrapArgs || false;
+  self.func = function () {
+    self.called = true;
+    self.calledWait = true;
+    self.args = doctest._argsToArray(arguments);
+    self.self = this;
+    self.argList.push(self.args);
+    self.selfList.push(this);
+    // It might be possible to get the caller?
+    if (self.writes) {
+      writeln(self.formatCall());
+    }
+    if (self.throwError) {
+      throw self.throwError;
+    }
+    if (self.applies) {
+      return self.applies.apply(this, arguments);
+    }
+    return self.returns;
+  };
+  self.func.toString = function () {
+    return "Spy('" + self._name + "').func";
+  };
+
+  // Method definitions:
+  self.formatCall = function () {
+    var s = '';
+    if ((! self.ignoreThis) && self.self !== window && self.self !== self) {
+      s += doctest.repr(self.self) + '.';
+    }
+    s += self._name;
+    if (self.args === null) {
+      return s + ':never called';
+    }
+    s += '(';
+    for (var i=0; i<self.args.length; i++) {
+      if (i) {
+        s += ', ';
+      }
+      if (self.wrapArgs) {
+        var maxLen = 10;
+      } else {
+        var maxLen = undefined;
+      }
+      s += doctest.repr(self.args[i], '', maxLen);
+    }
+    s += ')';
+    return s;
+  };
+
+  self.method = function (name, options, extraOptions) {
+    var desc = self._name + '.' + name;
+    var newSpy = Spy(desc, options, extraOptions);
+    self[name] = self.func[name] = newSpy.func;
+    return newSpy;
+  };
+
+  self.methods = function (props) {
+    for (var i in props) {
+      if (props[i] === props.prototype[i]) {
+        continue;
+      }
+      self.method(i, props[i]);
+    }
+    return self;
+  };
+
+  self.wait = function (timeout) {
+    var func = function () {
+      var value = self.calledWait;
+      if (value) {
+        self.calledWait = false;
+      }
+      return value;
+    };
+    func.repr = function () {
+      return 'called:'+repr(self);
+    };
+    doctest.wait(func, timeout);
+  };
+
+  self.repr = function () {
+    return "Spy('" + self._name + "')";
+  };
+
+  if (options.methods) {
+    self.methods(options.methods);
+  }
+  doctest.spies[name] = self;
+  if (options.wait) {
+    self.wait();
+  }
+  return self;
+};
+
+doctest._argsToArray = function (args) {
+  var array = [];
+  for (var i=0; i<args.length; i++) {
+    array.push(args[i]);
+  }
+  return array;
+};
+
+Spy = doctest.Spy;
+
+doctest.spies = {};
+
+doctest.defaultTimeout = 2000;
+
+doctest.defaultSpyOptions = {writes: true};
+
+var docTestOnLoad = function () {
+  var auto = false;
+  if (/\bautodoctest\b/.exec(document.body.className)) {
+    doctest.autoSetup();
+    auto = true;
+  } else {
+    logDebug('No autodoctest class on <body>');
+  }
+  var loc = window.location.search.substring(1);
+  if (auto || (/doctestRun/).exec(loc)) {
+    var elements = null;
+    // FIXME: we need to put the output near the specific test being tested:
+    if (location.hash) {
+      var el = document.getElementById(location.hash.substr(1));
+      if (el) {
+        if (/\btest\b/.exec(el.className)) {
+          var testEls = doctest.getElementsByTagAndClassName('pre', 'doctest', el);
+          elements = doctest.getElementsByTagAndClassName('pre', ['doctest', 'setup']);
+          for (var i=0; i<testEls.length; i++) {
+            elements.push(testEls[i]);
+          }
+        }
+      }
+    }
+    doctest(0, elements);
+  }
+};
+
+if (window.addEventListener) {
+    window.addEventListener('load', docTestOnLoad, false);
+} else if(window.attachEvent) {
+    window.attachEvent('onload', docTestOnLoad);
+}
+
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/js/jquery-1.6.1.min.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/js/jquery-1.6.1.min.js
new file mode 100644
index 0000000..b2ac174
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/js/jquery-1.6.1.min.js
@@ -0,0 +1,18 @@
+/*!
+ * jQuery JavaScript Library v1.6.1
+ * http://jquery.com/
+ *
+ * Copyright 2011, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ * Copyright 2011, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ *
+ * Date: Thu May 12 15:04:36 2011 -0400
+ */
+(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cv(a){if(!cj[a]){var b=f("<"+a+">").appendTo("body"),d=b.css("display");b.remove();if(d==="none"||d===""){ck||(ck=c.createElement("iframe"),ck.frameBorder=ck.width=ck.height=0),c.body.appendChild(ck);if(!cl||!ck.createElement)cl=(ck.contentWindow||ck.contentDocument).document,cl.write("<!doctype><html><body></body></html>");b=cl.createElement(a),cl.body.appendChild(b),d=f.css(b,"display"),c.body.removeChild(ck)}cj[a]=d}return cj[a]}function cu(a,b){var c={};f.each(cp.concat.apply([],cp.slice(0,b)),function(){c[this]=a});return c}function ct(){cq=b}function cs(){setTimeout(ct,0);return cq=f.now()}function ci(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ch(){try{return new a.XMLHttpRequest}catch(b){}}function cb(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g<i;g++){if(g===1)for(h in a.converters)typeof h=="string"&&(e[h.toLowerCase()]=a.converters[h]);l=k,k=d[g];if(k==="*")k=l;else if(l!=="*"&&l!==k){m=l+" "+k,n=e[m]||e["* "+k];if(!n){p=b;for(o in e){j=o.split(" ");if(j[0]===l||j[0]==="*"){p=e[j[1]+" "+k];if(p){o=e[o],o===!0?n=p:p===!0&&(n=o);break}}}}!n&&!p&&f.error("No conversion from "+m.replace(" "," to ")),n!==!0&&(c=n?n(c):p(o(c)))}}return c}function ca(a,c,d){var e=a.contents,f=a.dataTypes,g=a.responseFields,h,i,j,k;for(i in g)i in d&&(c[g[i]]=d[i]);while(f[0]==="*")f.shift(),h===b&&(h=a.mimeType||c.getResponseHeader("content-type"));if(h)for(i in e)if(e[i]&&e[i].test(h)){f.unshift(i);break}if(f[0]in d)j=f[0];else{for(i in d){if(!f[0]||a.converters[i+" "+f[0]]){j=i;break}k||(k=i)}j=j||k}if(j){j!==f[0]&&f.unshift(j);return d[j]}}function b_(a,b,c,d){if(f.isArray(b))f.each(b,function(b,e){c||bF.test(a)?d(a,e):b_(a+"["+(typeof e=="object"||f.isArray(e)?b:"")+"]",e,c,d)});else if(!c&&b!=null&&typeof b=="object")for(var e in b)b_(a+"["+e+"]",b[e],c,d);else d(a,b)}function b$(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h=a[f],i=0,j=h?h.length:0,k=a===bU,l;for(;i<j&&(k||!l);i++)l=h[i](c,d,e),typeof l=="string"&&(!k||g[l]?l=b:(c.dataTypes.unshift(l),l=b$(a,c,d,e,l,g)));(k||!l)&&!g["*"]&&(l=b$(a,c,d,e,"*",g));return l}function bZ(a){return function(b,c){typeof b!="string"&&(c=b,b="*");if(f.isFunction(c)){var d=b.toLowerCase().split(bQ),e=0,g=d.length,h,i,j;for(;e<g;e++)h=d[e],j=/^\+/.test(h),j&&(h=h.substr(1)||"*"),i=a[h]=a[h]||[],i[j?"unshift":"push"](c)}}}function bD(a,b,c){var d=b==="width"?bx:by,e=b==="width"?a.offsetWidth:a.offsetHeight;if(c==="border")return e;f.each(d,function(){c||(e-=parseFloat(f.css(a,"padding"+this))||0),c==="margin"?e+=parseFloat(f.css(a,"margin"+this))||0:e-=parseFloat(f.css(a,"border"+this+"Width"))||0});return e}function bn(a,b){b.src?f.ajax({url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(bf,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)}function bm(a){f.nodeName(a,"input")?bl(a):a.getElementsByTagName&&f.grep(a.getElementsByTagName("input"),bl)}function bl(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bk(a){return"getElementsByTagName"in a?a.getElementsByTagName("*"):"querySelectorAll"in a?a.querySelectorAll("*"):[]}function bj(a,b){var c;if(b.nodeType===1){b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase();if(c==="object")b.outerHTML=a.outerHTML;else if(c!=="input"||a.type!=="checkbox"&&a.type!=="radio"){if(c==="option")b.selected=a.defaultSelected;else if(c==="input"||c==="textarea")b.defaultValue=a.defaultValue}else a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value);b.removeAttribute(f.expando)}}function bi(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c=f.expando,d=f.data(a),e=f.data(b,d);if(d=d[c]){var g=d.events;e=e[c]=f.extend({},d);if(g){delete e.handle,e.events={};for(var h in g)for(var i=0,j=g[h].length;i<j;i++)f.event.add(b,h+(g[h][i].namespace?".":"")+g[h][i].namespace,g[h][i],g[h][i].data)}}}}function bh(a,b){return f.nodeName(a,"table")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function X(a,b,c){b=b||0;if(f.isFunction(b))return f.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return f.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=f.grep(a,function(a){return a.nodeType===1});if(S.test(b))return f.filter(b,d,!c);b=f.filter(b,d)}return f.grep(a,function(a,d){return f.inArray(a,b)>=0===c})}function W(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function O(a,b){return(a&&a!=="*"?a+".":"")+b.replace(A,"`").replace(B,"&")}function N(a){var b,c,d,e,g,h,i,j,k,l,m,n,o,p=[],q=[],r=f._data(this,"events");if(!(a.liveFired===this||!r||!r.live||a.target.disabled||a.button&&a.type==="click")){a.namespace&&(n=new RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)")),a.liveFired=this;var s=r.live.slice(0);for(i=0;i<s.length;i++)g=s[i],g.origType.replace(y,"")===a.type?q.push(g.selector):s.splice(i--,1);e=f(a.target).closest(q,a.currentTarget);for(j=0,k=e.length;j<k;j++){m=e[j];for(i=0;i<s.length;i++){g=s[i];if(m.selector===g.selector&&(!n||n.test(g.namespace))&&!m.elem.disabled){h=m.elem,d=null;if(g.preType==="mouseenter"||g.preType==="mouseleave")a.type=g.preType,d=f(a.relatedTarget).closest(g.selector)[0],d&&f.contains(h,d)&&(d=h);(!d||d!==h)&&p.push({elem:h,handleObj:g,level:m.level})}}}for(j=0,k=p.length;j<k;j++){e=p[j];if(c&&e.level>c)break;a.currentTarget=e.elem,a.data=e.handleObj.data,a.handleObj=e.handleObj,o=e.handleObj.origHandler.apply(e.elem,arguments);if(o===!1||a.isPropagationStopped()){c=e.level,o===!1&&(b=!1);if(a.isImmediatePropagationStopped())break}}return b}}function L(a,c,d){var e=f.extend({},d[0]);e.type=a,e.originalEvent={},e.liveFired=b,f.event.handle.call(c,e),e.isDefaultPrevented()&&d[0].preventDefault()}function F(){return!0}function E(){return!1}function m(a,c,d){var e=c+"defer",g=c+"queue",h=c+"mark",i=f.data(a,e,b,!0);i&&(d==="queue"||!f.data(a,g,b,!0))&&(d==="mark"||!f.data(a,h,b,!0))&&setTimeout(function(){!f.data(a,g,b,!0)&&!f.data(a,h,b,!0)&&(f.removeData(a,e,!0),i.resolve())},0)}function l(a){for(var b in a)if(b!=="toJSON")return!1;return!0}function k(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(j,"$1-$2").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNaN(d)?i.test(d)?f.parseJSON(d):d:parseFloat(d)}catch(g){}f.data(a,c,d)}else d=b}return d}var c=a.document,d=a.navigator,e=a.location,f=function(){function H(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(H,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/\d/,n=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,o=/^[\],:{}\s]*$/,p=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,q=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,r=/(?:^|:|,)(?:\s*\[)+/g,s=/(webkit)[ \/]([\w.]+)/,t=/(opera)(?:.*version)?[ \/]([\w.]+)/,u=/(msie) ([\w.]+)/,v=/(mozilla)(?:.*? rv:([\w.]+))?/,w=d.userAgent,x,y,z,A=Object.prototype.toString,B=Object.prototype.hasOwnProperty,C=Array.prototype.push,D=Array.prototype.slice,E=String.prototype.trim,F=Array.prototype.indexOf,G={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=n.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.6.1",length:0,size:function(){return this.length},toArray:function(){return D.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?C.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),y.done(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(D.apply(this,arguments),"slice",D.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:C,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j<k;j++)if((a=arguments[j])!=null)for(c in a){d=i[c],f=a[c];if(i===f)continue;l&&f&&(e.isPlainObject(f)||(g=e.isArray(f)))?(g?(g=!1,h=d&&e.isArray(d)?d:[]):h=d&&e.isPlainObject(d)?d:{},i[c]=e.extend(l,h,f)):f!==b&&(i[c]=f)}return i},e.extend({noConflict:function(b){a.$===e&&(a.$=g),b&&a.jQuery===e&&(a.jQuery=f);return e},isReady:!1,readyWait:1,holdReady:function(a){a?e.readyWait++:e.ready(!0)},ready:function(a){if(a===!0&&!--e.readyWait||a!==!0&&!e.isReady){if(!c.body)return setTimeout(e.ready,1);e.isReady=!0;if(a!==!0&&--e.readyWait>0)return;y.resolveWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").unbind("ready")}},bindReady:function(){if(!y){y=e._Deferred();if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",z,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",z),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&H()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNaN:function(a){return a==null||!m.test(a)||isNaN(a)},type:function(a){return a==null?String(a):G[A.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;if(a.constructor&&!B.call(a,"constructor")&&!B.call(a.constructor.prototype,"isPrototypeOf"))return!1;var c;for(c in a);return c===b||B.call(a,c)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw a},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(o.test(b.replace(p,"@").replace(q,"]").replace(r,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(b,c,d){a.DOMParser?(d=new DOMParser,c=d.parseFromString(b,"text/xml")):(c=new ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(b)),d=c.documentElement,(!d||!d.nodeName||d.nodeName==="parsererror")&&e.error("Invalid XML: "+b);return c},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g<h;)if(c.apply(a[g++],d)===!1)break}else if(i){for(f in a)if(c.call(a[f],f,a[f])===!1)break}else for(;g<h;)if(c.call(a[g],g,a[g++])===!1)break;return a},trim:E?function(a){return a==null?"":E.call(a)}:function(a){return a==null?"":(a+"").replace(k,"").replace(l,"")},makeArray:function(a,b){var c=b||[];if(a!=null){var d=e.type(a);a.length==null||d==="string"||d==="function"||d==="regexp"||e.isWindow(a)?C.call(c,a):e.merge(c,a)}return c},inArray:function(a,b){if(F)return F.call(b,a);for(var c=0,d=b.length;c<d;c++)if(b[c]===a)return c;return-1},merge:function(a,c){var d=a.length,e=0;if(typeof c.length=="number")for(var f=c.length;e<f;e++)a[d++]=c[e];else while(c[e]!==b)a[d++]=c[e++];a.length=d;return a},grep:function(a,b,c){var d=[],e;c=!!c;for(var f=0,g=a.length;f<g;f++)e=!!b(a[f],f),c!==e&&d.push(a[f]);return d},map:function(a,c,d){var f,g,h=[],i=0,j=a.length,k=a instanceof e||j!==b&&typeof j=="number"&&(j>0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i<j;i++)f=c(a[i],i,d),f!=null&&(h[h.length]=f);else for(g in a)f=c(a[g],g,d),f!=null&&(h[h.length]=f);return h.concat.apply([],h)},guid:1,proxy:function(a,c){if(typeof c=="string"){var d=a[c];c=a,a=d}if(!e.isFunction(a))return b;var f=D.call(arguments,2),g=function(){return a.apply(c,f.concat(D.call(arguments)))};g.guid=a.guid=a.guid||g.guid||e.guid++;return g},access:function(a,c,d,f,g,h){var i=a.length;if(typeof c=="object"){for(var j in c)e.access(a,j,c[j],f,g,d);return a}if(d!==b){f=!h&&f&&e.isFunction(d);for(var k=0;k<i;k++)g(a[k],c,f?d.call(a[k],k,g(a[k],c)):d,h);return a}return i?g(a[0],c):b},now:function(){return(new Date).getTime()},uaMatch:function(a){a=a.toLowerCase();var b=s.exec(a)||t.exec(a)||u.exec(a)||a.indexOf("compatible")<0&&v.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},sub:function(){function a(b,c){return new a.fn.init(b,c)}e.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function(d,f){f&&f instanceof e&&!(f instanceof a)&&(f=a(f));return e.fn.init.call(this,d,f,b)},a.fn.init.prototype=a.fn;var b=a(c);return a},browser:{}}),e.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){G["[object "+b+"]"]=b.toLowerCase()}),x=e.uaMatch(w),x.browser&&(e.browser[x.browser]=!0,e.browser.version=x.version),e.browser.webkit&&(e.browser.safari=!0),j.test(" ")&&(k=/^[\s\xA0]+/,l=/[\s\xA0]+$/),h=e(c),c.addEventListener?z=function(){c.removeEventListener("DOMContentLoaded",z,!1),e.ready()}:c.attachEvent&&(z=function(){c.readyState==="complete"&&(c.detachEvent("onreadystatechange",z),e.ready())});return e}(),g="done fail isResolved isRejected promise then always pipe".split(" "),h=[].slice;f.extend({_Deferred:function(){var a=[],b,c,d,e={done:function(){if(!d){var c=arguments,g,h,i,j,k;b&&(k=b,b=0);for(g=0,h=c.length;g<h;g++)i=c[g],j=f.type(i),j==="array"?e.done.apply(e,i):j==="function"&&a.push(i);k&&e.resolveWith(k[0],k[1])}return this},resolveWith:function(e,f){if(!d&&!b&&!c){f=f||[],c=1;try{while(a[0])a.shift().apply(e,f)}finally{b=[e,f],c=0}}return this},resolve:function(){e.resolveWith(this,arguments);return this},isResolved:function(){return!!c||!!b},cancel:function(){d=1,a=[];return this}};return e},Deferred:function(a){var b=f._Deferred(),c=f._Deferred(),d;f.extend(b,{then:function(a,c){b.done(a).fail(c);return this},always:function(){return b.done.apply(b,arguments).fail.apply(this,arguments)},fail:c.done,rejectWith:c.resolveWith,reject:c.resolve,isRejected:c.isResolved,pipe:function(a,c){return f.Deferred(function(d){f.each({done:[a,"resolve"],fail:[c,"reject"]},function(a,c){var e=c[0],g=c[1],h;f.isFunction(e)?b[a](function(){h=e.apply(this,arguments),h&&f.isFunction(h.promise)?h.promise().then(d.resolve,d.reject):d[g](h)}):b[a](d[g])})}).promise()},promise:function(a){if(a==null){if(d)return d;d=a={}}var c=g.length;while(c--)a[g[c]]=b[g[c]];return a}}),b.done(c.cancel).fail(b.cancel),delete b.cancel,a&&a.call(b,b);return b},when:function(a){function i(a){return function(c){b[a]=arguments.length>1?h.call(arguments,0):c,--e||g.resolveWith(g,h.call(b,0))}}var b=arguments,c=0,d=b.length,e=d,g=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred();if(d>1){for(;c<d;c++)b[c]&&f.isFunction(b[c].promise)?b[c].promise().then(i(c),g.reject):--e;e||g.resolveWith(g,b)}else g!==a&&g.resolveWith(g,d?[a]:[]);return g.promise()}}),f.support=function(){var a=c.createElement("div"),b=c.documentElement,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r;a.setAttribute("className","t"),a.innerHTML="   <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>",d=a.getElementsByTagName("*"),e=a.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};f=c.createElement("select"),g=f.appendChild(c.createElement("option")),h=a.getElementsByTagName("input")[0],j={leadingWhitespace:a.firstChild.nodeType===3,tbody:!a.getElementsByTagName("tbody").length,htmlSerialize:!!a.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55$/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:h.value==="on",optSelected:g.selected,getSetAttribute:a.className!=="t",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},h.checked=!0,j.noCloneChecked=h.cloneNode(!0).checked,f.disabled=!0,j.optDisabled=!g.disabled;try{delete a.test}catch(s){j.deleteExpando=!1}!a.addEventListener&&a.attachEvent&&a.fireEvent&&(a.attachEvent("onclick",function b(){j.noCloneEvent=!1,a.detachEvent("onclick",b)}),a.cloneNode(!0).fireEvent("onclick")),h=c.createElement("input"),h.value="t",h.setAttribute("type","radio"),j.radioValue=h.value==="t",h.setAttribute("checked","checked"),a.appendChild(h),k=c.createDocumentFragment(),k.appendChild(a.firstChild),j.checkClone=k.cloneNode(!0).cloneNode(!0).lastChild.checked,a.innerHTML="",a.style.width=a.style.paddingLeft="1px",l=c.createElement("body"),m={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"};for(q in m)l.style[q]=m[q];l.appendChild(a),b.insertBefore(l,b.firstChild),j.appendChecked=h.checked,j.boxModel=a.offsetWidth===2,"zoom"in a.style&&(a.style.display="inline",a.style.zoom=1,j.inlineBlockNeedsLayout=a.offsetWidth===2,a.style.display="",a.innerHTML="<div style='width:4px;'></div>",j.shrinkWrapBlocks=a.offsetWidth!==2),a.innerHTML="<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>",n=a.getElementsByTagName("td"),r=n[0].offsetHeight===0,n[0].style.display="",n[1].style.display="none",j.reliableHiddenOffsets=r&&n[0].offsetHeight===0,a.innerHTML="",c.defaultView&&c.defaultView.getComputedStyle&&(i=c.createElement("div"),i.style.width="0",i.style.marginRight="0",a.appendChild(i),j.reliableMarginRight=(parseInt((c.defaultView.getComputedStyle(i,null)||{marginRight:0}).marginRight,10)||0)===0),l.innerHTML="",b.removeChild(l);if(a.attachEvent)for(q in{submit:1,change:1,focusin:1})p="on"+q,r=p in a,r||(a.setAttribute(p,"return;"),r=typeof a[p]=="function"),j[q+"Bubbles"]=r;return j}(),f.boxModel=f.support.boxModel;var i=/^(?:\{.*\}|\[.*\])$/,j=/([a-z])([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!l(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g=f.expando,h=typeof c=="string",i,j=a.nodeType,k=j?f.cache:a,l=j?a[f.expando]:a[f.expando]&&f.expando;if((!l||e&&l&&!k[l][g])&&h&&d===b)return;l||(j?a[f.expando]=l=++f.uuid:l=f.expando),k[l]||(k[l]={},j||(k[l].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?k[l][g]=f.extend(k[l][g],c):k[l]=f.extend(k[l],c);i=k[l],e&&(i[g]||(i[g]={}),i=i[g]),d!==b&&(i[f.camelCase(c)]=d);if(c==="events"&&!i[c])return i[g]&&i[g].events;return h?i[f.camelCase(c)]:i}},removeData:function(b,c,d){if(!!f.acceptData(b)){var e=f.expando,g=b.nodeType,h=g?f.cache:b,i=g?b[f.expando]:f.expando;if(!h[i])return;if(c){var j=d?h[i][e]:h[i];if(j){delete j[c];if(!l(j))return}}if(d){delete h[i][e];if(!l(h[i]))return}var k=h[i][e];f.support.deleteExpando||h!=a?delete h[i]:h[i]=null,k?(h[i]={},g||(h[i].toJSON=f.noop),h[i][e]=k):g&&(f.support.deleteExpando?delete b[f.expando]:b.removeAttribute?b.removeAttribute(f.expando):b[f.expando]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d=null;if(typeof a=="undefined"){if(this.length){d=f.data(this[0]);if(this[0].nodeType===1){var e=this[0].attributes,g;for(var h=0,i=e.length;h<i;h++)g=e[h].name,g.indexOf("data-")===0&&(g=f.camelCase(g.substring(5)),k(this[0],g,d[g]))}}return d}if(typeof a=="object")return this.each(function(){f.data(this,a)});var j=a.split(".");j[1]=j[1]?"."+j[1]:"";if(c===b){d=this.triggerHandler("getData"+j[1]+"!",[j[0]]),d===b&&this.length&&(d=f.data(this[0],a),d=k(this[0],a,d));return d===b&&j[1]?this.data(j[0]):d}return this.each(function(){var b=f(this),d=[j[0],c];b.triggerHandler("setData"+j[1]+"!",d),f.data(this,a,c),b.triggerHandler("changeData"+j[1]+"!",d)})},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,c){a&&(c=(c||"fx")+"mark",f.data(a,c,(f.data(a,c,b,!0)||0)+1,!0))},_unmark:function(a,c,d){a!==!0&&(d=c,c=a,a=!1);if(c){d=d||"fx";var e=d+"mark",g=a?0:(f.data(c,e,b,!0)||1)-1;g?f.data(c,e,g,!0):(f.removeData(c,e,!0),m(c,d,"mark"))}},queue:function(a,c,d){if(a){c=(c||"fx")+"queue";var e=f.data(a,c,b,!0);d&&(!e||f.isArray(d)?e=f.data(a,c,f.makeArray(d),!0):e.push(d));return e||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e;d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),d.call(a,function(){f.dequeue(a,b)})),c.length||(f.removeData(a,b+"queue",!0),m(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){typeof a!="string"&&(c=a,a="fx");if(c===b)return f.queue(this[0],a);return this.each(function(){var b=f.queue(this,a,c);a==="fx"&&b[0]!=="inprogress"&&f.dequeue(this,a)})},dequeue:function(a){return this.each(function(){f.dequeue(this,a)})},delay:function(a,b){a=f.fx?f.fx.speeds[a]||a:a,b=b||"fx";return this.queue(b,function(){var c=this;setTimeout(function(){f.dequeue(c,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){function m(){--h||d.resolveWith(e,[e])}typeof a!="string"&&(c=a,a=b),a=a||"fx";var d=f.Deferred(),e=this,g=e.length,h=1,i=a+"defer",j=a+"queue",k=a+"mark",l;while(g--)if(l=f.data(e[g],i,b,!0)||(f.data(e[g],j,b,!0)||f.data(e[g],k,b,!0))&&f.data(e[g],i,f._Deferred(),!0))h++,l.done(m);m();return d.promise()}});var n=/[\n\t\r]/g,o=/\s+/,p=/\r/g,q=/^(?:button|input)$/i,r=/^(?:button|input|object|select|textarea)$/i,s=/^a(?:rea)?$/i,t=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,u=/\:/,v,w;f.fn.extend({attr:function(a,b){return f.access(this,a,b,!0,f.attr)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,a,b,!0,f.prop)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.addClass(a.call(this,b,c.attr("class")||""))});if(a&&typeof a=="string"){var b=(a||"").split(o);for(var c=0,d=this.length;c<d;c++){var e=this[c];if(e.nodeType===1)if(!e.className)e.className=a;else{var g=" "+e.className+" ",h=e.className;for(var i=0,j=b.length;i<j;i++)g.indexOf(" "+b[i]+" ")<0&&(h+=" "+b[i]);e.className=f.trim(h)}}}return this},removeClass:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.removeClass(a.call(this,b,c.attr("class")))});if(a&&typeof a=="string"||a===b){var c=(a||"").split(o);for(var d=0,e=this.length;d<e;d++){var g=this[d];if(g.nodeType===1&&g.className)if(a){var h=(" "+g.className+" ").replace(n," ");for(var i=0,j=c.length;i<j;i++)h=h.replace(" "+c[i]+" "," ");g.className=f.trim(h)}else g.className=""}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";if(f.isFunction(a))return this.each(function(c){var d=f(this);d.toggleClass(a.call(this,c,d.attr("class"),b),b)});return this.each(function(){if(c==="string"){var e,g=0,h=f(this),i=b,j=a.split(o);while(e=j[g++])i=d?i:!h.hasClass(e),h[i?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&f._data(this,"__className__",this.className),this.className=this.className||a===!1?"":f._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ";for(var c=0,d=this.length;c<d;c++)if((" "+this[c].className+" ").replace(n," ").indexOf(b)>-1)return!0;return!1},val:function(a){var c,d,e=this[0];if(!arguments.length){if(e){c=f.valHooks[e.nodeName.toLowerCase()]||f.valHooks[e.type];if(c&&"get"in c&&(d=c.get(e,"value"))!==b)return d;return(e.value||"").replace(p,"")}return b}var g=f.isFunction(a);return this.each(function(d){var e=f(this),h;if(this.nodeType===1){g?h=a.call(this,d,e.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c=a.selectedIndex,d=[],e=a.options,g=a.type==="select-one";if(c<0)return null;for(var h=g?c:0,i=g?c+1:e.length;h<i;h++){var j=e[h];if(j.selected&&(f.support.optDisabled?!j.disabled:j.getAttribute("disabled")===null)&&(!j.parentNode.disabled||!f.nodeName(j.parentNode,"optgroup"))){b=f(j).val();if(g)return b;d.push(b)}}if(g&&!d.length&&e.length)return f(e[c]).val();return d},set:function(a,b){var c=f.makeArray(b);f(a).find("option").each(function(){this.selected=f.inArray(f(this).val(),c)>=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attrFix:{tabindex:"tabIndex"},attr:function(a,c,d,e){var g=a.nodeType;if(!a||g===3||g===8||g===2)return b;if(e&&c in f.attrFn)return f(a)[c](d);if(!("getAttribute"in a))return f.prop(a,c,d);var h,i,j=g!==1||!f.isXMLDoc(a);c=j&&f.attrFix[c]||c,i=f.attrHooks[c],i||(!t.test(c)||typeof d!="boolean"&&d!==b&&d.toLowerCase()!==c.toLowerCase()?v&&(f.nodeName(a,"form")||u.test(c))&&(i=v):i=w);if(d!==b){if(d===null){f.removeAttr(a,c);return b}if(i&&"set"in i&&j&&(h=i.set(a,d,c))!==b)return h;a.setAttribute(c,""+d);return d}if(i&&"get"in i&&j)return i.get(a,c);h=a.getAttribute(c);return h===null?b:h},removeAttr:function(a,b){var c;a.nodeType===1&&(b=f.attrFix[b]||b,f.support.getSetAttribute?a.removeAttribute(b):(f.attr(a,b,""),a.removeAttributeNode(a.getAttributeNode(b))),t.test(b)&&(c=f.propFix[b]||b)in a&&(a[c]=!1))},attrHooks:{type:{set:function(a,b){if(q.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.value;a.setAttribute("type",b),c&&(a.value=c);return b}}},tabIndex:{get:function(a){var c=a.getAttributeNode("tabIndex");return c&&c.specified?parseInt(c.value,10):r.test(a.nodeName)||s.test(a.nodeName)&&a.href?0:b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e=a.nodeType;if(!a||e===3||e===8||e===2)return b;var g,h,i=e!==1||!f.isXMLDoc(a);c=i&&f.propFix[c]||c,h=f.propHooks[c];return d!==b?h&&"set"in h&&(g=h.set(a,d,c))!==b?g:a[c]=d:h&&"get"in h&&(g=h.get(a,c))!==b?g:a[c]},propHooks:{}}),w={get:function(a,c){return a[f.propFix[c]||c]?c.toLowerCase():b},set:function(a,b,c){var d;b===!1?f.removeAttr(a,c):(d=f.propFix[c]||c,d in a&&(a[d]=b),a.setAttribute(c,c.toLowerCase()));return c}},f.attrHooks.value={get:function(a,b){if(v&&f.nodeName(a,"button"))return v.get(a,b);return a.value},set:function(a,b,c){if(v&&f.nodeName(a,"button"))return v.set(a,b,c);a.value=b}},f.support.getSetAttribute||(f.attrFix=f.propFix,v=f.attrHooks.name=f.valHooks.button={get:function(a,c){var d;d=a.getAttributeNode(c);return d&&d.nodeValue!==""?d.nodeValue:b},set:function(a,b,c){var d=a.getAttributeNode(c);if(d){d.nodeValue=b;return b}}},f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})})),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}})),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var x=Object.prototype.hasOwnProperty,y=/\.(.*)$/,z=/^(?:textarea|input|select)$/i,A=/\./g,B=/ /g,C=/[^\w\s.|`]/g,D=function(a){return a.replace(C,"\\$&")};f.event={add:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){if(d===!1)d=E;else if(!d)return;var g,h;d.handler&&(g=d,d=g.handler),d.guid||(d.guid=f.guid++);var i=f._data(a);if(!i)return;var j=i.events,k=i.handle;j||(i.events=j={}),k||(i.handle=k=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.handle.apply(k.elem,arguments):b}),k.elem=a,c=c.split(" ");var l,m=0,n;while(l=c[m++]){h=g?f.extend({},g):{handler:d,data:e},l.indexOf(".")>-1?(n=l.split("."),l=n.shift(),h.namespace=n.slice(0).sort().join(".")):(n=[],h.namespace=""),h.type=l,h.guid||(h.guid=d.guid);var o=j[l],p=f.event.special[l]||{};if(!o){o=j[l]=[];if(!p.setup||p.setup.call(a,e,n,k)===!1)a.addEventListener?a.addEventListener(l,k,!1):a.attachEvent&&a.attachEvent("on"+l,k)}p.add&&(p.add.call(a,h),h.handler.guid||(h.handler.guid=d.guid)),o.push(h),f.event.global[l]=!0}a=null}},global:{},remove:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){d===!1&&(d=E);var g,h,i,j,k=0,l,m,n,o,p,q,r,s=f.hasData(a)&&f._data(a),t=s&&s.events;if(!s||!t)return;c&&c.type&&(d=c.handler,c=c.type);if(!c||typeof c=="string"&&c.charAt(0)==="."){c=c||"";for(h in t)f.event.remove(a,h+c);return}c=c.split(" ");while(h=c[k++]){r=h,q=null,l=h.indexOf(".")<0,m=[],l||(m=h.split("."),h=m.shift(),n=new RegExp("(^|\\.)"+f.map(m.slice(0).sort(),D).join("\\.(?:.*\\.)?")+"(\\.|$)")),p=t[h];if(!p)continue;if(!d){for(j=0;j<p.length;j++){q=p[j];if(l||n.test(q.namespace))f.event.remove(a,r,q.handler,j),p.splice(j--,1)}continue}o=f.event.special[h]||{};for(j=e||0;j<p.length;j++){q=p[j];if(d.guid===q.guid){if(l||n.test(q.namespace))e==null&&p.splice(j--,1),o.remove&&o.remove.call(a,q);if(e!=null)break}}if(p.length===0||e!=null&&p.length===1)(!o.teardown||o.teardown.call(a,m)===!1)&&f.removeEvent(a,h,s.handle),g=null,delete t[h]}if(f.isEmptyObject(t)){var u=s.handle;u&&(u.elem=null),delete s.events,delete s.handle,f.isEmptyObject(s)&&f.removeData(a,b,!0)}}},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,e,g){var h=c.type||c,i=[],j;h.indexOf("!")>=0&&(h=h.slice(0,-1),j=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if(!!e&&!f.event.customEvent[h]||!!f.event.global[h]){c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.exclusive=j,c.namespace=i.join("."),c.namespace_re=new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)");if(g||!e)c.preventDefault(),c.stopPropagation();if(!e){f.each(f.cache,function(){var a=f.expando,b=this[a];b&&b.events&&b.events[h]&&f.event.trigger(c,d,b.handle.elem
+)});return}if(e.nodeType===3||e.nodeType===8)return;c.result=b,c.target=e,d=d?f.makeArray(d):[],d.unshift(c);var k=e,l=h.indexOf(":")<0?"on"+h:"";do{var m=f._data(k,"handle");c.currentTarget=k,m&&m.apply(k,d),l&&f.acceptData(k)&&k[l]&&k[l].apply(k,d)===!1&&(c.result=!1,c.preventDefault()),k=k.parentNode||k.ownerDocument||k===c.target.ownerDocument&&a}while(k&&!c.isPropagationStopped());if(!c.isDefaultPrevented()){var n,o=f.event.special[h]||{};if((!o._default||o._default.call(e.ownerDocument,c)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)){try{l&&e[h]&&(n=e[l],n&&(e[l]=null),f.event.triggered=h,e[h]())}catch(p){}n&&(e[l]=n),f.event.triggered=b}}return c.result}},handle:function(c){c=f.event.fix(c||a.event);var d=((f._data(this,"events")||{})[c.type]||[]).slice(0),e=!c.exclusive&&!c.namespace,g=Array.prototype.slice.call(arguments,0);g[0]=c,c.currentTarget=this;for(var h=0,i=d.length;h<i;h++){var j=d[h];if(e||c.namespace_re.test(j.namespace)){c.handler=j.handler,c.data=j.data,c.handleObj=j;var k=j.handler.apply(this,g);k!==b&&(c.result=k,k===!1&&(c.preventDefault(),c.stopPropagation()));if(c.isImmediatePropagationStopped())break}}return c.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(a){if(a[f.expando])return a;var d=a;a=f.Event(d);for(var e=this.props.length,g;e;)g=this.props[--e],a[g]=d[g];a.target||(a.target=a.srcElement||c),a.target.nodeType===3&&(a.target=a.target.parentNode),!a.relatedTarget&&a.fromElement&&(a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement);if(a.pageX==null&&a.clientX!=null){var h=a.target.ownerDocument||c,i=h.documentElement,j=h.body;a.pageX=a.clientX+(i&&i.scrollLeft||j&&j.scrollLeft||0)-(i&&i.clientLeft||j&&j.clientLeft||0),a.pageY=a.clientY+(i&&i.scrollTop||j&&j.scrollTop||0)-(i&&i.clientTop||j&&j.clientTop||0)}a.which==null&&(a.charCode!=null||a.keyCode!=null)&&(a.which=a.charCode!=null?a.charCode:a.keyCode),!a.metaKey&&a.ctrlKey&&(a.metaKey=a.ctrlKey),!a.which&&a.button!==b&&(a.which=a.button&1?1:a.button&2?3:a.button&4?2:0);return a},guid:1e8,proxy:f.proxy,special:{ready:{setup:f.bindReady,teardown:f.noop},live:{add:function(a){f.event.add(this,O(a.origType,a.selector),f.extend({},a,{handler:N,guid:a.handler.guid}))},remove:function(a){f.event.remove(this,O(a.origType,a.selector),a)}},beforeunload:{setup:function(a,b,c){f.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}}},f.removeEvent=c.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){a.detachEvent&&a.detachEvent("on"+b,c)},f.Event=function(a,b){if(!this.preventDefault)return new f.Event(a,b);a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?F:E):this.type=a,b&&f.extend(this,b),this.timeStamp=f.now(),this[f.expando]=!0},f.Event.prototype={preventDefault:function(){this.isDefaultPrevented=F;var a=this.originalEvent;!a||(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){this.isPropagationStopped=F;var a=this.originalEvent;!a||(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=F,this.stopPropagation()},isDefaultPrevented:E,isPropagationStopped:E,isImmediatePropagationStopped:E};var G=function(a){var b=a.relatedTarget;a.type=a.data;try{if(b&&b!==c&&!b.parentNode)return;while(b&&b!==this)b=b.parentNode;b!==this&&f.event.handle.apply(this,arguments)}catch(d){}},H=function(a){a.type=a.data,f.event.handle.apply(this,arguments)};f.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){f.event.special[a]={setup:function(c){f.event.add(this,b,c&&c.selector?H:G,a)},teardown:function(a){f.event.remove(this,b,a&&a.selector?H:G)}}}),f.support.submitBubbles||(f.event.special.submit={setup:function(a,b){if(!f.nodeName(this,"form"))f.event.add(this,"click.specialSubmit",function(a){var b=a.target,c=b.type;(c==="submit"||c==="image")&&f(b).closest("form").length&&L("submit",this,arguments)}),f.event.add(this,"keypress.specialSubmit",function(a){var b=a.target,c=b.type;(c==="text"||c==="password")&&f(b).closest("form").length&&a.keyCode===13&&L("submit",this,arguments)});else return!1},teardown:function(a){f.event.remove(this,".specialSubmit")}});if(!f.support.changeBubbles){var I,J=function(a){var b=a.type,c=a.value;b==="radio"||b==="checkbox"?c=a.checked:b==="select-multiple"?c=a.selectedIndex>-1?f.map(a.options,function(a){return a.selected}).join("-"):"":f.nodeName(a,"select")&&(c=a.selectedIndex);return c},K=function(c){var d=c.target,e,g;if(!!z.test(d.nodeName)&&!d.readOnly){e=f._data(d,"_change_data"),g=J(d),(c.type!=="focusout"||d.type!=="radio")&&f._data(d,"_change_data",g);if(e===b||g===e)return;if(e!=null||g)c.type="change",c.liveFired=b,f.event.trigger(c,arguments[1],d)}};f.event.special.change={filters:{focusout:K,beforedeactivate:K,click:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(c==="radio"||c==="checkbox"||f.nodeName(b,"select"))&&K.call(this,a)},keydown:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(a.keyCode===13&&!f.nodeName(b,"textarea")||a.keyCode===32&&(c==="checkbox"||c==="radio")||c==="select-multiple")&&K.call(this,a)},beforeactivate:function(a){var b=a.target;f._data(b,"_change_data",J(b))}},setup:function(a,b){if(this.type==="file")return!1;for(var c in I)f.event.add(this,c+".specialChange",I[c]);return z.test(this.nodeName)},teardown:function(a){f.event.remove(this,".specialChange");return z.test(this.nodeName)}},I=f.event.special.change.filters,I.focus=I.beforeactivate}f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){function e(a){var c=f.event.fix(a);c.type=b,c.originalEvent={},f.event.trigger(c,null,c.target),c.isDefaultPrevented()&&a.preventDefault()}var d=0;f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.each(["bind","one"],function(a,c){f.fn[c]=function(a,d,e){var g;if(typeof a=="object"){for(var h in a)this[c](h,d,a[h],e);return this}if(arguments.length===2||d===!1)e=d,d=b;c==="one"?(g=function(a){f(this).unbind(a,g);return e.apply(this,arguments)},g.guid=e.guid||f.guid++):g=e;if(a==="unload"&&c!=="one")this.one(a,d,e);else for(var i=0,j=this.length;i<j;i++)f.event.add(this[i],a,g,d);return this}}),f.fn.extend({unbind:function(a,b){if(typeof a=="object"&&!a.preventDefault)for(var c in a)this.unbind(c,a[c]);else for(var d=0,e=this.length;d<e;d++)f.event.remove(this[d],a,b);return this},delegate:function(a,b,c,d){return this.live(b,c,d,a)},undelegate:function(a,b,c){return arguments.length===0?this.unbind("live"):this.die(b,null,c,a)},trigger:function(a,b){return this.each(function(){f.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return f.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=arguments,c=a.guid||f.guid++,d=0,e=function(c){var e=(f.data(this,"lastToggle"+a.guid)||0)%d;f.data(this,"lastToggle"+a.guid,e+1),c.preventDefault();return b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});var M={focus:"focusin",blur:"focusout",mouseenter:"mouseover",mouseleave:"mouseout"};f.each(["live","die"],function(a,c){f.fn[c]=function(a,d,e,g){var h,i=0,j,k,l,m=g||this.selector,n=g?this:f(this.context);if(typeof a=="object"&&!a.preventDefault){for(var o in a)n[c](o,d,a[o],m);return this}if(c==="die"&&!a&&g&&g.charAt(0)==="."){n.unbind(g);return this}if(d===!1||f.isFunction(d))e=d||E,d=b;a=(a||"").split(" ");while((h=a[i++])!=null){j=y.exec(h),k="",j&&(k=j[0],h=h.replace(y,""));if(h==="hover"){a.push("mouseenter"+k,"mouseleave"+k);continue}l=h,M[h]?(a.push(M[h]+k),h=h+k):h=(M[h]||h)+k;if(c==="live")for(var p=0,q=n.length;p<q;p++)f.event.add(n[p],"live."+O(h,m),{data:d,selector:m,handler:e,origType:h,origHandler:e,preType:l});else n.unbind("live."+O(h,m),e)}return this}}),f.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error".split(" "),function(a,b){f.fn[b]=function(a,c){c==null&&(c=a,a=null);return arguments.length>0?this.bind(b,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0)}),function(){function u(a,b,c,d,e,f){for(var g=0,h=d.length;g<h;g++){var i=d[g];if(i){var j=!1;i=i[a];while(i){if(i.sizcache===c){j=d[i.sizset];break}if(i.nodeType===1){f||(i.sizcache=c,i.sizset=g);if(typeof b!="string"){if(i===b){j=!0;break}}else if(k.filter(b,[i]).length>0){j=i;break}}i=i[a]}d[g]=j}}}function t(a,b,c,d,e,f){for(var g=0,h=d.length;g<h;g++){var i=d[g];if(i){var j=!1;i=i[a];while(i){if(i.sizcache===c){j=d[i.sizset];break}i.nodeType===1&&!f&&(i.sizcache=c,i.sizset=g);if(i.nodeName.toLowerCase()===b){j=i;break}i=i[a]}d[g]=j}}}var a=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d=0,e=Object.prototype.toString,g=!1,h=!0,i=/\\/g,j=/\W/;[0,0].sort(function(){h=!1;return 0});var k=function(b,d,f,g){f=f||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return f;var i,j,n,o,q,r,s,t,u=!0,w=k.isXML(d),x=[],y=b;do{a.exec(""),i=a.exec(y);if(i){y=i[3],x.push(i[1]);if(i[2]){o=i[3];break}}}while(i);if(x.length>1&&m.exec(b))if(x.length===2&&l.relative[x[0]])j=v(x[0]+x[1],d);else{j=l.relative[x[0]]?[d]:k(x.shift(),d);while(x.length)b=x.shift(),l.relative[b]&&(b+=x.shift()),j=v(b,j)}else{!g&&x.length>1&&d.nodeType===9&&!w&&l.match.ID.test(x[0])&&!l.match.ID.test(x[x.length-1])&&(q=k.find(x.shift(),d,w),d=q.expr?k.filter(q.expr,q.set)[0]:q.set[0]);if(d){q=g?{expr:x.pop(),set:p(g)}:k.find(x.pop(),x.length===1&&(x[0]==="~"||x[0]==="+")&&d.parentNode?d.parentNode:d,w),j=q.expr?k.filter(q.expr,q.set):q.set,x.length>0?n=p(j):u=!1;while(x.length)r=x.pop(),s=r,l.relative[r]?s=x.pop():r="",s==null&&(s=d),l.relative[r](n,s,w)}else n=x=[]}n||(n=j),n||k.error(r||b);if(e.call(n)==="[object Array]")if(!u)f.push.apply(f,n);else if(d&&d.nodeType===1)for(t=0;n[t]!=null;t++)n[t]&&(n[t]===!0||n[t].nodeType===1&&k.contains(d,n[t]))&&f.push(j[t]);else for(t=0;n[t]!=null;t++)n[t]&&n[t].nodeType===1&&f.push(j[t]);else p(n,f);o&&(k(o,h,f,g),k.uniqueSort(f));return f};k.uniqueSort=function(a){if(r){g=h,a.sort(r);if(g)for(var b=1;b<a.length;b++)a[b]===a[b-1]&&a.splice(b--,1)}return a},k.matches=function(a,b){return k(a,null,null,b)},k.matchesSelector=function(a,b){return k(b,null,null,[a]).length>0},k.find=function(a,b,c){var d;if(!a)return[];for(var e=0,f=l.order.length;e<f;e++){var g,h=l.order[e];if(g=l.leftMatch[h].exec(a)){var j=g[1];g.splice(1,1);if(j.substr(j.length-1)!=="\\"){g[1]=(g[1]||"").replace(i,""),d=l.find[h](g,b,c);if(d!=null){a=a.replace(l.match[h],"");break}}}}d||(d=typeof b.getElementsByTagName!="undefined"?b.getElementsByTagName("*"):[]);return{set:d,expr:a}},k.filter=function(a,c,d,e){var f,g,h=a,i=[],j=c,m=c&&c[0]&&k.isXML(c[0]);while(a&&c.length){for(var n in l.filter)if((f=l.leftMatch[n].exec(a))!=null&&f[2]){var o,p,q=l.filter[n],r=f[1];g=!1,f.splice(1,1);if(r.substr(r.length-1)==="\\")continue;j===i&&(i=[]);if(l.preFilter[n]){f=l.preFilter[n](f,j,d,i,e,m);if(!f)g=o=!0;else if(f===!0)continue}if(f)for(var s=0;(p=j[s])!=null;s++)if(p){o=q(p,f,s,j);var t=e^!!o;d&&o!=null?t?g=!0:j[s]=!1:t&&(i.push(p),g=!0)}if(o!==b){d||(j=i),a=a.replace(l.match[n],"");if(!g)return[];break}}if(a===h)if(g==null)k.error(a);else break;h=a}return j},k.error=function(a){throw"Syntax error, unrecognized expression: "+a};var l=k.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(a){return a.getAttribute("href")},type:function(a){return a.getAttribute("type")}},relative:{"+":function(a,b){var c=typeof b=="string",d=c&&!j.test(b),e=c&&!d;d&&(b=b.toLowerCase());for(var f=0,g=a.length,h;f<g;f++)if(h=a[f]){while((h=h.previousSibling)&&h.nodeType!==1);a[f]=e||h&&h.nodeName.toLowerCase()===b?h||!1:h===b}e&&k.filter(b,a,!0)},">":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!j.test(b)){b=b.toLowerCase();for(;e<f;e++){c=a[e];if(c){var g=c.parentNode;a[e]=g.nodeName.toLowerCase()===b?g:!1}}}else{for(;e<f;e++)c=a[e],c&&(a[e]=d?c.parentNode:c.parentNode===b);d&&k.filter(b,a,!0)}},"":function(a,b,c){var e,f=d++,g=u;typeof b=="string"&&!j.test(b)&&(b=b.toLowerCase(),e=b,g=t),g("parentNode",b,f,a,e,c)},"~":function(a,b,c){var e,f=d++,g=u;typeof b=="string"&&!j.test(b)&&(b=b.toLowerCase(),e=b,g=t),g("previousSibling",b,f,a,e,c)}},find:{ID:function(a,b,c){if(typeof b.getElementById!="undefined"&&!c){var d=b.getElementById(a[1]);return d&&d.parentNode?[d]:[]}},NAME:function(a,b){if(typeof b.getElementsByName!="undefined"){var c=[],d=b.getElementsByName(a[1]);for(var e=0,f=d.length;e<f;e++)d[e].getAttribute("name")===a[1]&&c.push(d[e]);return c.length===0?null:c}},TAG:function(a,b){if(typeof b.getElementsByTagName!="undefined")return b.getElementsByTagName(a[1])}},preFilter:{CLASS:function(a,b,c,d,e,f){a=" "+a[1].replace(i,"")+" ";if(f)return a;for(var g=0,h;(h=b[g])!=null;g++)h&&(e^(h.className&&(" "+h.className+" ").replace(/[\t\n\r]/g," ").indexOf(a)>=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(i,"")},TAG:function(a,b){return a[1].replace(i,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||k.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&k.error(a[0]);a[0]=d++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(i,"");!f&&l.attrMap[g]&&(a[1]=l.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(i,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=k(b[3],null,null,c);else{var g=k.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(l.match.POS.test(b[0])||l.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!k(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return b<c[3]-0},gt:function(a,b,c){return b>c[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=l.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||k.getText([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h<i;h++)if(g[h]===a)return!1;return!0}k.error(e)},CHILD:function(a,b){var c=b[1],d=a;switch(c){case"only":case"first":while(d=d.previousSibling)if(d.nodeType===1)return!1;if(c==="first")return!0;d=a;case"last":while(d=d.nextSibling)if(d.nodeType===1)return!1;return!0;case"nth":var e=b[2],f=b[3];if(e===1&&f===0)return!0;var g=b[0],h=a.parentNode;if(h&&(h.sizcache!==g||!a.nodeIndex)){var i=0;for(d=h.firstChild;d;d=d.nextSibling)d.nodeType===1&&(d.nodeIndex=++i);h.sizcache=g}var j=a.nodeIndex-f;return e===0?j===0:j%e===0&&j/e>=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=l.attrHandle[c]?l.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=l.setFilters[e];if(f)return f(a,c,b,d)}}},m=l.match.POS,n=function(a,b){return"\\"+(b-0+1)};for(var o in l.match)l.match[o]=new RegExp(l.match[o].source+/(?![^\[]*\])(?![^\(]*\))/.source),l.leftMatch[o]=new RegExp(/(^(?:.|\r|\n)*?)/.source+l.match[o].source.replace(/\\(\d+)/g,n));var p=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(q){p=function(a,b){var c=0,d=b||[];if(e.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var f=a.length;c<f;c++)d.push(a[c]);else for(;a[c];c++)d.push(a[c]);return d}}var r,s;c.documentElement.compareDocumentPosition?r=function(a,b){if(a===b){g=!0;return 0}if(!a.compareDocumentPosition||!b.compareDocumentPosition)return a.compareDocumentPosition?-1:1;return a.compareDocumentPosition(b)&4?-1:1}:(r=function(a,b){if(a===b){g=!0;return 0}if(a.sourceIndex&&b.sourceIndex)return a.sourceIndex-b.sourceIndex;var c,d,e=[],f=[],h=a.parentNode,i=b.parentNode,j=h;if(h===i)return s(a,b);if(!h)return-1;if(!i)return 1;while(j)e.unshift(j),j=j.parentNode;j=i;while(j)f.unshift(j),j=j.parentNode;c=e.length,d=f.length;for(var k=0;k<c&&k<d;k++)if(e[k]!==f[k])return s(e[k],f[k]);return k===c?s(a,f[k],-1):s(e[k],b,1)},s=function(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}),k.getText=function(a){var b="",c;for(var d=0;a[d];d++)c=a[d],c.nodeType===3||c.nodeType===4?b+=c.nodeValue:c.nodeType!==8&&(b+=k.getText(c.childNodes));return b},function(){var a=c.createElement("div"),d="script"+(new Date).getTime(),e=c.documentElement;a.innerHTML="<a name='"+d+"'/>",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(l.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},l.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(l.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(l.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=k,b=c.createElement("div"),d="__sizzle__";b.innerHTML="<p class='TEST'></p>";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){k=function(b,e,f,g){e=e||c;if(!g&&!k.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return p(e.getElementsByTagName(b),f);if(h[2]&&l.find.CLASS&&e.getElementsByClassName)return p(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return p([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return p([],f);if(i.id===h[3])return p([i],f)}try{return p(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var m=e,n=e.getAttribute("id"),o=n||d,q=e.parentNode,r=/^\s*[+~]/.test(b);n?o=o.replace(/'/g,"\\$&"):e.setAttribute("id",o),r&&q&&(e=e.parentNode);try{if(!r||q)return p(e.querySelectorAll("[id='"+o+"'] "+b),f)}catch(s){}finally{n||m.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)k[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}k.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!k.isXML(a))try{if(e||!l.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return k(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="<div class='test e'></div><div class='test'></div>";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;l.order.splice(1,0,"CLASS"),l.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?k.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?k.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:k.contains=function(){return!1},k.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var v=function(a,b){var c,d=[],e="",f=b.nodeType?[b]:b;while(c=l.match.PSEUDO.exec(a))e+=c[0],a=a.replace(l.match.PSEUDO,"");a=l.relative[a]?a+"*":a;for(var g=0,h=f.length;g<h;g++)k(a,f[g],d);return k.filter(e,d)};f.find=k,f.expr=k.selectors,f.expr[":"]=f.expr.filters,f.unique=k.uniqueSort,f.text=k.getText,f.isXMLDoc=k.isXML,f.contains=k.contains}();var P=/Until$/,Q=/^(?:parents|prevUntil|prevAll)/,R=/,/,S=/^.[^:#\[\.,]*$/,T=Array.prototype.slice,U=f.expr.match.POS,V={children:!0,contents:!0,next:!0,prev:!0};f.fn.extend({find:function(a){var b=this,c,d;if(typeof a!="string")return f(a).filter(function(){for(c=0,d=b.length;c<d;c++)if(f.contains(b[c],this))return!0});var e=this.pushStack("","find",a),g,h,i;for(c=0,d=this.length;c<d;c++){g=e.length,f.find(a,this[c],e);if(c>0)for(h=g;h<e.length;h++)for(i=0;i<g;i++)if(e[i]===e[h]){e.splice(h--,1);break}}return e},has:function(a){var b=f(a);return this.filter(function(){for(var a=0,c=b.length;a<c;a++)if(f.contains(this,b[a]))return!0})},not:function(a){return this.pushStack(X(this,a,!1),"not",a)},filter:function(a){return this.pushStack(X(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h,i,j={},k=1;if(g&&a.length){for(d=0,e=a.length;d<e;d++)i=a[d],j[i]||(j[i]=U.test(i)?f(i,b||this.context):i);while(g&&g.ownerDocument&&g!==b){for(i in j)h=j[i],(h.jquery?h.index(g)>-1:f(g).is(h))&&c.push({selector:i,elem:g,level:k});g=g.parentNode,k++}}return c}var l=U.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d<e;d++){g=this[d];while(g){if(l?l.index(g)>-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a||typeof a=="string")return f.inArray(this[0],a?f(a):this.parent().children());return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(W(c[0])||W(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c),g=T.call(arguments);P.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!V[a]?f.unique(e):e,(this.length>1||R.test(d))&&Q.test(a)&&(e=e.reverse());return this.pushStack(e,a,g.join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var Y=/ jQuery\d+="(?:\d+|null)"/g,Z=/^\s+/,$=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,_=/<([\w:]+)/,ba=/<tbody/i,bb=/<|&#?\w+;/,bc=/<(?:script|object|embed|option|style)/i,bd=/checked\s*(?:[^=]|=\s*.checked.)/i,be=/\/(java|ecma)script/i,bf=/^\s*<!(?:\[CDATA\[|\-\-)/,bg={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]};bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div<div>","</div>"]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){f(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f(arguments[0]).toArray());return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Y,""):null;if(typeof a=="string"&&!bc.test(a)&&(f.support.leadingWhitespace||!Z.test(a))&&!bg[(_.exec(a)||["",""])[1].toLowerCase()]){a=a.replace($,"<$1></$2>");try{for(var c=0,d=this.length;c<d;c++)this[c].nodeType===1&&(f.cleanData(this[c].getElementsByTagName("*")),this[c].innerHTML=a)}catch(e){this.empty().append(a)}}else f.isFunction(a)?this.each(function(b){var c=f(this);c.html(a.call(this,b,c.html()))}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&this[0].parentNode){if(f.isFunction(a))return this.each(function(b){var c=f(this),d=c.html();c.replaceWith(a.call(this,b,d))});typeof a!="string"&&(a=f(a).detach());return this.each(function(){var b=this.nextSibling,c=this.parentNode;f(this).remove(),b?f(b).before(a):f(c).append(a)})}return this.length?this.pushStack(f(f.isFunction(a)?a():a),"replaceWith",a):this},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){var e,g,h,i,j=a[0],k=[];if(!f.support.checkClone&&arguments.length===3&&typeof j=="string"&&bd.test(j))return this.each(function(){f(this).domManip(a,c,d,!0)});if(f.isFunction(j))return this.each(function(e){var g=f(this);a[0]=j.call(this,e,c?g.html():b),g.domManip(a,c,d)});if(this[0]){i=j&&j.parentNode,f.support.parentNode&&i&&i.nodeType===11&&i.childNodes.length===this.length?e={fragment:i}:e=f.buildFragment(a,this,k),h=e.fragment,h.childNodes.length===1?g=h=h.firstChild:g=h.firstChild;if(g){c=c&&f.nodeName(g,"tr");for(var l=0,m=this.length,n=m-1;l<m;l++)d.call(c?bh(this[l],g):this[l],e.cacheable||m>1&&l<n?f.clone(h,!0,!0):h)}k.length&&f.each(k,bn)}return this}}),f.buildFragment=function(a,b,d){var e,g,h,i=b&&b[0]?b[0].ownerDocument||b[0]:c;a.length===1&&typeof a[0]=="string"&&a[0].length<512&&i===c&&a[0].charAt(0)==="<"&&!bc.test(a[0])&&(f.support.checkClone||!bd.test(a[0]))&&(g=!0,h=f.fragments[a[0]],h&&h!==1&&(e=h)),e||(e=i.createDocumentFragment(),f.clean(a,i,e,d)),g&&(f.fragments[a[0]]=h?e:1);return{fragment:e,cacheable:g}},f.fragments={},f.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){f.fn[a]=function(c){var d=[],e=f(c),g=this.length===1&&this[0].parentNode;if(g&&g.nodeType===11&&g.childNodes.length===1&&e.length===1){e[b](this[0]);return this}for(var h=0,i=e.length;h<i;h++){var j=(h>0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d=a.cloneNode(!0),e,g,h;if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bj(a,d),e=bk(a),g=bk(d);for(h=0;e[h];++h)bj(e[h],g[h])}if(b){bi(a,d);if(c){e=bk(a),g=bk(d);for(h=0;e[h];++h)bi(e[h],g[h])}}return d},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||
+b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!bb.test(k))k=b.createTextNode(k);else{k=k.replace($,"<$1></$2>");var l=(_.exec(k)||["",""])[1].toLowerCase(),m=bg[l]||bg._default,n=m[0],o=b.createElement("div");o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=ba.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]==="<table>"&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&Z.test(k)&&o.insertBefore(b.createTextNode(Z.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i<r;i++)bm(k[i]);else bm(k);k.nodeType?h.push(k):h=f.merge(h,k)}if(d){g=function(a){return!a.type||be.test(a.type)};for(j=0;h[j];j++)if(e&&f.nodeName(h[j],"script")&&(!h[j].type||h[j].type.toLowerCase()==="text/javascript"))e.push(h[j].parentNode?h[j].parentNode.removeChild(h[j]):h[j]);else{if(h[j].nodeType===1){var s=f.grep(h[j].getElementsByTagName("script"),g);h.splice.apply(h,[j+1,0].concat(s))}d.appendChild(h[j])}}return h},cleanData:function(a){var b,c,d=f.cache,e=f.expando,g=f.event.special,h=f.support.deleteExpando;for(var i=0,j;(j=a[i])!=null;i++){if(j.nodeName&&f.noData[j.nodeName.toLowerCase()])continue;c=j[f.expando];if(c){b=d[c]&&d[c][e];if(b&&b.events){for(var k in b.events)g[k]?f.event.remove(j,k):f.removeEvent(j,k,b.handle);b.handle&&(b.handle.elem=null)}h?delete j[f.expando]:j.removeAttribute&&j.removeAttribute(f.expando),delete d[c]}}}});var bo=/alpha\([^)]*\)/i,bp=/opacity=([^)]*)/,bq=/-([a-z])/ig,br=/([A-Z]|^ms)/g,bs=/^-?\d+(?:px)?$/i,bt=/^-?\d/,bu=/^[+\-]=/,bv=/[^+\-\.\de]+/g,bw={position:"absolute",visibility:"hidden",display:"block"},bx=["Left","Right"],by=["Top","Bottom"],bz,bA,bB,bC=function(a,b){return b.toUpperCase()};f.fn.css=function(a,c){if(arguments.length===2&&c===b)return this;return f.access(this,a,c,!0,function(a,c,d){return d!==b?f.style(a,c,d):f.css(a,c)})},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bz(a,"opacity","opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{zIndex:!0,fontWeight:!0,opacity:!0,zoom:!0,lineHeight:!0,widows:!0,orphans:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d;if(h==="number"&&isNaN(d)||d==null)return;h==="string"&&bu.test(d)&&(d=+d.replace(bv,"")+parseFloat(f.css(a,c))),h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(bz)return bz(a,c)},swap:function(a,b,c){var d={};for(var e in b)d[e]=a.style[e],a.style[e]=b[e];c.call(a);for(e in b)a.style[e]=d[e]},camelCase:function(a){return a.replace(bq,bC)}}),f.curCSS=f.css,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){var e;if(c){a.offsetWidth!==0?e=bD(a,b,d):f.swap(a,bw,function(){e=bD(a,b,d)});if(e<=0){e=bz(a,b,b),e==="0px"&&bB&&(e=bB(a,b,b));if(e!=null)return e===""||e==="auto"?"0px":e}if(e<0||e==null){e=a.style[b];return e===""||e==="auto"?"0px":e}return typeof e=="string"?e:e+"px"}},set:function(a,b){if(!bs.test(b))return b;b=parseFloat(b);if(b>=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bp.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle;c.zoom=1;var e=f.isNaN(b)?"":"alpha(opacity="+b*100+")",g=d&&d.filter||c.filter||"";c.filter=bo.test(g)?g.replace(bo,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bz(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(bA=function(a,c){var d,e,g;c=c.replace(br,"-$1").toLowerCase();if(!(e=a.ownerDocument.defaultView))return b;if(g=e.getComputedStyle(a,null))d=g.getPropertyValue(c),d===""&&!f.contains(a.ownerDocument.documentElement,a)&&(d=f.style(a,c));return d}),c.documentElement.currentStyle&&(bB=function(a,b){var c,d=a.currentStyle&&a.currentStyle[b],e=a.runtimeStyle&&a.runtimeStyle[b],f=a.style;!bs.test(d)&&bt.test(d)&&(c=f.left,e&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":d||0,d=f.pixelLeft+"px",f.left=c,e&&(a.runtimeStyle.left=e));return d===""?"auto":d}),bz=bA||bB,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bE=/%20/g,bF=/\[\]$/,bG=/\r?\n/g,bH=/#.*$/,bI=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bJ=/^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bK=/^(?:about|app|app\-storage|.+\-extension|file|widget):$/,bL=/^(?:GET|HEAD)$/,bM=/^\/\//,bN=/\?/,bO=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,bP=/^(?:select|textarea)/i,bQ=/\s+/,bR=/([?&])_=[^&]*/,bS=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bT=f.fn.load,bU={},bV={},bW,bX;try{bW=e.href}catch(bY){bW=c.createElement("a"),bW.href="",bW=bW.href}bX=bS.exec(bW.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bT)return bT.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("<div>").append(c.replace(bO,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bP.test(this.nodeName)||bJ.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bG,"\r\n")}}):{name:b.name,value:c.replace(bG,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.bind(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?f.extend(!0,a,f.ajaxSettings,b):(b=a,a=f.extend(!0,f.ajaxSettings,b));for(var c in{context:1,url:1})c in b?a[c]=b[c]:c in f.ajaxSettings&&(a[c]=f.ajaxSettings[c]);return a},ajaxSettings:{url:bW,isLocal:bK.test(bX[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":"*/*"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML}},ajaxPrefilter:bZ(bU),ajaxTransport:bZ(bV),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a?4:0;var o,r,u,w=l?ca(d,v,l):b,x,y;if(a>=200&&a<300||a===304){if(d.ifModified){if(x=v.getResponseHeader("Last-Modified"))f.lastModified[k]=x;if(y=v.getResponseHeader("Etag"))f.etag[k]=y}if(a===304)c="notmodified",o=!0;else try{r=cb(d,w),c="success",o=!0}catch(z){c="parsererror",u=z}}else{u=c;if(!c||a)c="error",a<0&&(a=0)}v.status=a,v.statusText=c,o?h.resolveWith(e,[r,c,v]):h.rejectWith(e,[v,c,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.resolveWith(e,[v,c]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f._Deferred(),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bI.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.done,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bH,"").replace(bM,bX[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bQ),d.crossDomain==null&&(r=bS.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bX[1]&&r[2]==bX[2]&&(r[3]||(r[1]==="http:"?80:443))==(bX[3]||(bX[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),b$(bU,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bL.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bN.test(d.url)?"&":"?")+d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bR,"$1_="+x);d.url=y+(y===d.url?(bN.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", */*; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=b$(bV,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){status<2?w(-1,z):f.error(z)}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)b_(g,a[g],c,e);return d.join("&").replace(bE,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cc=f.now(),cd=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cc++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(cd.test(b.url)||e&&cd.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(cd,l),b.url===j&&(e&&(k=k.replace(cd,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var ce=a.ActiveXObject?function(){for(var a in cg)cg[a](0,1)}:!1,cf=0,cg;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ch()||ci()}:ch,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,ce&&delete cg[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cf,ce&&(cg||(cg={},f(a).unload(ce)),cg[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cj={},ck,cl,cm=/^(?:toggle|show|hide)$/,cn=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,co,cp=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cq,cr=a.webkitRequestAnimationFrame||a.mozRequestAnimationFrame||a.oRequestAnimationFrame;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cu("show",3),a,b,c);for(var g=0,h=this.length;g<h;g++)d=this[g],d.style&&(e=d.style.display,!f._data(d,"olddisplay")&&e==="none"&&(e=d.style.display=""),e===""&&f.css(d,"display")==="none"&&f._data(d,"olddisplay",cv(d.nodeName)));for(g=0;g<h;g++){d=this[g];if(d.style){e=d.style.display;if(e===""||e==="none")d.style.display=f._data(d,"olddisplay")||""}}return this},hide:function(a,b,c){if(a||a===0)return this.animate(cu("hide",3),a,b,c);for(var d=0,e=this.length;d<e;d++)if(this[d].style){var g=f.css(this[d],"display");g!=="none"&&!f._data(this[d],"olddisplay")&&f._data(this[d],"olddisplay",g)}for(d=0;d<e;d++)this[d].style&&(this[d].style.display="none");return this},_toggle:f.fn.toggle,toggle:function(a,b,c){var d=typeof a=="boolean";f.isFunction(a)&&f.isFunction(b)?this._toggle.apply(this,arguments):a==null||d?this.each(function(){var b=d?a:f(this).is(":hidden");f(this)[b?"show":"hide"]()}):this.animate(cu("toggle",3),a,b,c);return this},fadeTo:function(a,b,c,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=f.speed(b,c,d);if(f.isEmptyObject(a))return this.each(e.complete,[!1]);a=f.extend({},a);return this[e.queue===!1?"each":"queue"](function(){e.queue===!1&&f._mark(this);var b=f.extend({},e),c=this.nodeType===1,d=c&&f(this).is(":hidden"),g,h,i,j,k,l,m,n,o;b.animatedProperties={};for(i in a){g=f.camelCase(i),i!==g&&(a[g]=a[i],delete a[i]),h=a[g],f.isArray(h)?(b.animatedProperties[g]=h[1],h=a[g]=h[0]):b.animatedProperties[g]=b.specialEasing&&b.specialEasing[g]||b.easing||"swing";if(h==="hide"&&d||h==="show"&&!d)return b.complete.call(this);c&&(g==="height"||g==="width")&&(b.overflow=[this.style.overflow,this.style.overflowX,this.style.overflowY],f.css(this,"display")==="inline"&&f.css(this,"float")==="none"&&(f.support.inlineBlockNeedsLayout?(j=cv(this.nodeName),j==="inline"?this.style.display="inline-block":(this.style.display="inline",this.style.zoom=1)):this.style.display="inline-block"))}b.overflow!=null&&(this.style.overflow="hidden");for(i in a)k=new f.fx(this,b,i),h=a[i],cm.test(h)?k[h==="toggle"?d?"show":"hide":h]():(l=cn.exec(h),m=k.cur(),l?(n=parseFloat(l[2]),o=l[3]||(f.cssNumber[i]?"":"px"),o!=="px"&&(f.style(this,i,(n||1)+o),m=(n||1)/k.cur()*m,f.style(this,i,m+o)),l[1]&&(n=(l[1]==="-="?-1:1)*n+m),k.custom(m,n,o)):k.custom(m,h,""));return!0})},stop:function(a,b){a&&this.queue([]),this.each(function(){var a=f.timers,c=a.length;b||f._unmark(!0,this);while(c--)a[c].elem===this&&(b&&a[c](!0),a.splice(c,1))}),b||this.dequeue();return this}}),f.each({slideDown:cu("show",1),slideUp:cu("hide",1),slideToggle:cu("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){f.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),f.extend({speed:function(a,b,c){var d=a&&typeof a=="object"?f.extend({},a):{complete:c||!c&&b||f.isFunction(a)&&a,duration:a,easing:c&&b||b&&!f.isFunction(b)&&b};d.duration=f.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in f.fx.speeds?f.fx.speeds[d.duration]:f.fx.speeds._default,d.old=d.complete,d.complete=function(a){d.queue!==!1?f.dequeue(this):a!==!1&&f._unmark(this),f.isFunction(d.old)&&d.old.call(this)};return d},easing:{linear:function(a,b,c,d){return c+d*a},swing:function(a,b,c,d){return(-Math.cos(a*Math.PI)/2+.5)*d+c}},timers:[],fx:function(a,b,c){this.options=b,this.elem=a,this.prop=c,b.orig=b.orig||{}}}),f.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this),(f.fx.step[this.prop]||f.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a,b=f.css(this.elem,this.prop);return isNaN(a=parseFloat(b))?!b||b==="auto"?0:b:a},custom:function(a,b,c){function h(a){return d.step(a)}var d=this,e=f.fx,g;this.startTime=cq||cs(),this.start=a,this.end=b,this.unit=c||this.unit||(f.cssNumber[this.prop]?"":"px"),this.now=this.start,this.pos=this.state=0,h.elem=this.elem,h()&&f.timers.push(h)&&!co&&(cr?(co=1,g=function(){co&&(cr(g),e.tick())},cr(g)):co=setInterval(e.tick,e.interval))},show:function(){this.options.orig[this.prop]=f.style(this.elem,this.prop),this.options.show=!0,this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur()),f(this.elem).show()},hide:function(){this.options.orig[this.prop]=f.style(this.elem,this.prop),this.options.hide=!0,this.custom(this.cur(),0)},step:function(a){var b=cq||cs(),c=!0,d=this.elem,e=this.options,g,h;if(a||b>=e.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),e.animatedProperties[this.prop]=!0;for(g in e.animatedProperties)e.animatedProperties[g]!==!0&&(c=!1);if(c){e.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){d.style["overflow"+b]=e.overflow[a]}),e.hide&&f(d).hide();if(e.hide||e.show)for(var i in e.animatedProperties)f.style(d,i,e.orig[i]);e.complete.call(d)}return!1}e.duration==Infinity?this.now=b:(h=b-this.startTime,this.state=h/e.duration,this.pos=f.easing[e.animatedProperties[this.prop]](this.state,h,0,1,e.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){for(var a=f.timers,b=0;b<a.length;++b)a[b]()||a.splice(b--,1);a.length||f.fx.stop()},interval:13,stop:function(){clearInterval(co),co=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){f.style(a.elem,"opacity",a.now)},_default:function(a){a.elem.style&&a.elem.style[a.prop]!=null?a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit:a.elem[a.prop]=a.now}}}),f.expr&&f.expr.filters&&(f.expr.filters.animated=function(a){return f.grep(f.timers,function(b){return a===b.elem}).length});var cw=/^t(?:able|d|h)$/i,cx=/^(?:body|html)$/i;"getBoundingClientRect"in c.documentElement?f.fn.offset=function(a){var b=this[0],c;if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);try{c=b.getBoundingClientRect()}catch(d){}var e=b.ownerDocument,g=e.documentElement;if(!c||!f.contains(g,b))return c?{top:c.top,left:c.left}:{top:0,left:0};var h=e.body,i=cy(e),j=g.clientTop||h.clientTop||0,k=g.clientLeft||h.clientLeft||0,l=i.pageYOffset||f.support.boxModel&&g.scrollTop||h.scrollTop,m=i.pageXOffset||f.support.boxModel&&g.scrollLeft||h.scrollLeft,n=c.top+l-j,o=c.left+m-k;return{top:n,left:o}}:f.fn.offset=function(a){var b=this[0];if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);f.offset.initialize();var c,d=b.offsetParent,e=b,g=b.ownerDocument,h=g.documentElement,i=g.body,j=g.defaultView,k=j?j.getComputedStyle(b,null):b.currentStyle,l=b.offsetTop,m=b.offsetLeft;while((b=b.parentNode)&&b!==i&&b!==h){if(f.offset.supportsFixedPosition&&k.position==="fixed")break;c=j?j.getComputedStyle(b,null):b.currentStyle,l-=b.scrollTop,m-=b.scrollLeft,b===d&&(l+=b.offsetTop,m+=b.offsetLeft,f.offset.doesNotAddBorder&&(!f.offset.doesAddBorderForTableAndCells||!cw.test(b.nodeName))&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),e=d,d=b.offsetParent),f.offset.subtractsBorderForOverflowNotVisible&&c.overflow!=="visible"&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),k=c}if(k.position==="relative"||k.position==="static")l+=i.offsetTop,m+=i.offsetLeft;f.offset.supportsFixedPosition&&k.position==="fixed"&&(l+=Math.max(h.scrollTop,i.scrollTop),m+=Math.max(h.scrollLeft,i.scrollLeft));return{top:l,left:m}},f.offset={initialize:function(){var a=c.body,b=c.createElement("div"),d,e,g,h,i=parseFloat(f.css(a,"marginTop"))||0,j="<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";f.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"}),b.innerHTML=j,a.insertBefore(b,a.firstChild),d=b.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,this.doesNotAddBorder=e.offsetTop!==5,this.doesAddBorderForTableAndCells=h.offsetTop===5,e.style.position="fixed",e.style.top="20px",this.supportsFixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",this.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==i,a.removeChild(b),f.offset.initialize=f.noop},bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.offset.initialize(),f.offset.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cy(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cy(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){return this[0]?parseFloat(f.css(this[0],d,"padding")):null},f.fn["outer"+c]=function(a){return this[0]?parseFloat(f.css(this[0],d,a?"margin":"border")):null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c];return e.document.compatMode==="CSS1Compat"&&g||e.document.body["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var h=f.css(e,d),i=parseFloat(h);return f.isNaN(i)?h:i}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f})(window);
\ No newline at end of file
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/lex_test.html b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/lex_test.html
new file mode 100644
index 0000000..788bfcd
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/lex_test.html
@@ -0,0 +1,122 @@
+<html>
+  <head>
+    <title>JSONSelect JS lex tests</title>
+    <link rel="stylesheet" type="text/css" href="js/doctest.css" />
+    <script src="js/doctest.js"></script>
+    <script src="../jsonselect.js"></script>
+    <meta http-equiv="Content-type" content="text/html; charset=utf-8">
+  </head>
+  <body>
+
+<div>
+  <button onclick="doctest()" type="button">run tests</button>
+  <pre id="doctestOutput"></pre>
+</div>
+
+    <h2> Tests of the JSONSelect lexer </h2>
+
+<div class="test">
+Simple tokens
+<pre class="doctest">
+$ JSONSelect._lex(">");
+[1, ">"]
+$ JSONSelect._lex("*");
+[1, "*"]
+$ JSONSelect._lex(",");
+[1, ","]
+$ JSONSelect._lex(".");
+[1, "."]
+</pre>
+</div>
+
+<div class="test">
+Offsets
+<pre class="doctest">
+$ JSONSelect._lex("foobar>",6);
+[7, ">"]
+</pre>
+</div>
+
+<div class="test">
+Types
+<pre class="doctest">
+$ JSONSelect._lex("string");
+[6, 3, "string"]
+$ JSONSelect._lex("boolean");
+[7, 3, "boolean"]
+$ JSONSelect._lex("null");
+[4, 3, "null"]
+$ JSONSelect._lex("array");
+[5, 3, "array"]
+$ JSONSelect._lex("object");
+[6, 3, "object"]
+$ JSONSelect._lex("number");
+[6, 3, "number"]
+</pre>
+</div>
+
+<div class="test">
+Whitespace
+<pre class="doctest">
+$ JSONSelect._lex("\r");
+[1, " "]
+$ JSONSelect._lex("\n");
+[1, " "]
+$ JSONSelect._lex("\t");
+[1, " "]
+$ JSONSelect._lex(" ");
+[1, " "]
+$ JSONSelect._lex("     \t   \r\n  !");
+[13, " "]
+</pre>
+
+<div class="test">
+pseudo classes
+<pre class="doctest">
+$ JSONSelect._lex(":root");
+[5, 1, ":root"]
+$ JSONSelect._lex(":first-child");
+[12, 1, ":first-child"]
+$ JSONSelect._lex(":last-child");
+[11, 1, ":last-child"]
+$ JSONSelect._lex(":only-child");
+[11, 1, ":only-child"]
+</pre>
+</div>
+
+<div class="test">
+json strings
+<pre class="doctest">
+$ JSONSelect._lex('"foo bar baz"');
+[13, 4, "foo bar baz"]
+$ JSONSelect._lex('"\\u0020"');
+[8, 4, " "]
+$ JSONSelect._lex('\"not terminated');
+Error: unclosed json string
+$ JSONSelect._lex('"invalid escape: \\y"');
+Error: invalid json string
+</pre>
+</div>
+
+<div class="test">
+identifiers (like after '.')
+<pre class="doctest">
+$ JSONSelect._lex("foo");
+[3, 4, "foo"]
+$ JSONSelect._lex("foo\\ bar");
+[8, 4, "foo bar"]
+$ JSONSelect._lex("_aB129bcde-\\:foo\\@$");
+[18, 4, "_aB129bcde-:foo@"]
+</pre>
+</div>
+
+<div class="test">
+non-ascii
+<pre class="doctest">
+$ JSONSelect._lex("обичам\\ те\\!");
+[12, 4, "обичам те!"]
+</pre>
+</div>
+
+  </body>
+</html>
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/match_test.html b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/match_test.html
new file mode 100644
index 0000000..0fc784f
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/match_test.html
@@ -0,0 +1,71 @@
+<html>
+  <head>
+    <title>JSONSelect JS matching tests</title>
+    <link rel="stylesheet" type="text/css" href="js/doctest.css" />
+    <script src="js/doctest.js"></script>
+    <script src="../jsonselect.js"></script>
+    <meta http-equiv="Content-type" content="text/html; charset=utf-8">
+  </head>
+  <body>
+
+<div>
+  <button onclick="doctest()" type="button">run tests</button>
+  <pre id="doctestOutput"></pre>
+</div>
+
+    <h2> Tests of the JSONSelect matcher </h2>
+
+<div class="test">
+Types
+<pre class="doctest">
+$ JSONSelect.match("null", null);
+[null]
+$ JSONSelect.match("array", { 1: [], 2: [] });
+[[], []]
+$ JSONSelect.match("object", [ {}, {} ]);
+[{}, {}]
+$ JSONSelect.match("string", [ "a", 1, true, null, false, "b", 3.1415, "c" ] );
+["a", "b", "c"]
+$ JSONSelect.match("boolean", [ "a", 1, true, null, false, "b", 3.1415, "c" ] );
+[true, false]
+$ JSONSelect.match("number", [ "a", 1, true, null, false, "b", 3.1415, "c" ] );
+[1, 3.1415]
+</pre>
+</div>
+
+<div class="test">
+IDs
+<pre class="doctest">
+$ JSONSelect.match(".foo", {foo: "aMatch", bar: [ { foo: "anotherMatch" } ] });
+["aMatch", "anotherMatch"]
+</pre>
+</div>
+
+<div class="test">
+Descendants
+<pre class="doctest">
+$ JSONSelect.match(".foo .bar", {foo: { baz: 1, bar: 2 }, bar: 3});
+[2]
+$ JSONSelect.match(".foo > .bar", {foo: { baz: 1, bar: 2 }, bar: 3});
+[2]
+$ JSONSelect.match(".foo > .bar", {foo: { baz: { bar: 4 }, bar: 2 }, bar: 3});
+[2]
+$ JSONSelect.match(".foo .bar", {foo: { baz: { bar: 4 }, bar: 2 }, bar: 3});
+[4, 2]
+</pre>
+</div>
+
+<div class="test">
+Grouping
+<pre class="doctest">
+$ JSONSelect.match("number,boolean", [ "a", 1, true, null, false, "b", 3.1415, "c" ] );
+[1, true, false, 3.1415]
+$ JSONSelect.match("number,boolean,null", [ "a", 1, true, null, false, "b", 3.1415, "c" ] );
+[1, true, null, false, 3.1415]
+</pre>
+</div>
+
+  </body>
+</html>
+
+
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/parse_test.html b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/parse_test.html
new file mode 100644
index 0000000..9744457
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/parse_test.html
@@ -0,0 +1,98 @@
+<html>
+  <head>
+    <title>JSONSelect JS parser tests</title>
+    <link rel="stylesheet" type="text/css" href="js/doctest.css" />
+    <script src="js/doctest.js"></script>
+    <script src="../jsonselect.js"></script>
+    <meta http-equiv="Content-type" content="text/html; charset=utf-8">
+  </head>
+  <body>
+
+<div>
+  <button onclick="doctest()" type="button">run tests</button>
+  <pre id="doctestOutput"></pre>
+</div>
+
+    <h2> Tests of the JSONSelect parser </h2>
+
+<div class="test">
+Selectors
+<pre class="doctest">
+$ JSONSelect._parse(".foo");
+[{id: "foo"}]
+$ JSONSelect._parse('." foo "');
+[{id: " foo "}]
+$ JSONSelect._parse("string.foo:last-child");
+[{a: 0, b: 1, id: "foo", pf: ":nth-last-child", type: "string"}]
+$ JSONSelect._parse("string.foo.bar");
+Error: multiple ids not allowed
+$ JSONSelect._parse("string.foo:first-child.bar");
+Error: multiple ids not allowed
+$ JSONSelect._parse("string:last-child.foo:first-child.bar");
+Error: multiple pseudo classes (:xxx) not allowed
+$ JSONSelect._parse("string.");
+Error: string required after '.'
+$ JSONSelect._parse("string:bogus");
+Error: unrecognized pseudo class
+$ JSONSelect._parse("string.xxx\\@yyy");
+[{id: "xxx@yyy", type: "string"}]
+$ JSONSelect._parse(" ");
+Error: selector expected
+$ JSONSelect._parse("");
+Error: selector expected
+</pre>
+</div>
+
+<div class="test">
+Combinators
+<pre class="doctest">
+$ JSONSelect._parse(".foo .bar");
+[{id: "foo"}, {id: "bar"}]
+$ JSONSelect._parse("string.foo , number.foo");
+[",", [{id: "foo", type: "string"}], [{id: "foo", type: "number"}]]
+$ JSONSelect._parse("string > .foo number.bar");
+[{type: "string"}, ">", {id: "foo"}, {id: "bar", type: "number"}]
+$ JSONSelect._parse("string > .foo number.bar, object");
+[",", [{type: "string"}, ">", {id: "foo"}, {id: "bar", type: "number"}], [{type: "object"}]]
+$ JSONSelect._parse("string > .foo number.bar, object, string, .\"baz bing\", :root");
+[
+  ",",
+  [{type: "string"}, ">", {id: "foo"}, {id: "bar", type: "number"}],
+  [{type: "object"}],
+  [{type: "string"}],
+  [{id: "baz bing"}],
+  [{pc: ":root"}]
+]
+</pre>
+</div>
+
+<div class="test">
+Expressions
+<pre class="doctest">
+$ JSONSelect._parse(":nth-child(1)");
+[{a: 0, b: 1, pf: ":nth-child"}]
+$ JSONSelect._parse(":nth-child(2n+1)");
+[{a: 2, b: 1, pf: ":nth-child"}]
+$ JSONSelect._parse(":nth-child ( 2n + 1 )");
+[{a: 2, b: 1, pf: ":nth-child"}]
+$ JSONSelect._parse(":nth-child(odd)");
+[{a: 2, b: 1, pf: ":nth-child"}]
+$ JSONSelect._parse(":nth-child(even)");
+[{a: 2, b: 0, pf: ":nth-child"}]
+$ JSONSelect._parse(":nth-child(-n+6)");
+[{a: -1, b: 6, pf: ":nth-child"}]
+$ JSONSelect._parse(":nth-child(2n)");
+[{a: 2, b: 0, pf: ":nth-child"}]
+$ JSONSelect._parse(":nth-last-child(-3n - 3)");
+[{a: -3, b: -3, pf: ":nth-last-child"}]
+$ JSONSelect._parse(":first-child");
+[{a: 0, b: 1, pf: ":nth-child"}]
+$ JSONSelect._parse(":last-child");
+[{a: 0, b: 1, pf: ":nth-last-child"}]
+</pre>
+</div>
+
+  </body>
+</html>
+
+
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/run.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/run.js
new file mode 100644
index 0000000..c873c3a
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/run.js
@@ -0,0 +1,85 @@
+/*
+ * a node.js test runner that executes all conformance
+ * tests and outputs results to console.
+ * Process returns zero on success, non-zero on failure.
+ */
+
+const   fs = require('fs'),
+      path = require('path'),
+jsonselect = require('../jsonselect.js'),
+       sys = require('sys');
+
+var pathToTests = path.join(__dirname, "tests");
+
+// a map: document nametest name -> list of se
+var numTests = 0;
+var numPassed = 0;
+var tests = {};
+
+function runOneSync(name, selname, p) {
+    var testDocPath = path.join(p, name + ".json");
+    var selDocPath = path.join(p, name + '_' +
+                               selname + ".selector");
+    var outputDocPath = selDocPath.replace(/selector$/, "output");
+
+    // take `obj`, apply `sel, get `got`, is it what we `want`? 
+    var obj = JSON.parse(fs.readFileSync(testDocPath));
+    var want = String(fs.readFileSync(outputDocPath)).trim();
+    var got = "";
+    var sel = String(fs.readFileSync(selDocPath)).trim();
+
+    try {
+        jsonselect.forEach(sel, obj, function(m) {
+            got += JSON.stringify(m, undefined, 4) + "\n";
+        });
+    } catch(e) {
+        got = e.toString();
+        if (want.trim() != got.trim()) throw e;
+    }
+    if (want.trim() != got.trim()) throw "mismatch";
+}
+
+
+function runTests() {
+    console.log("Running Tests:"); 
+    for (var l in tests) {
+        for (var d in tests[l]) {
+            console.log("  level " + l + " tests against \"" + d + ".json\":");
+            for (var i = 0; i < tests[l][d].length; i++) {
+                sys.print("    " + tests[l][d][i][0] + ": ");
+                try {
+                    runOneSync(d, tests[l][d][i][0], tests[l][d][i][1]);
+                    numPassed++;
+                    console.log("pass");
+                } catch (e) {
+                    console.log("fail (" + e.toString() + ")");
+                }
+            }
+        }
+    }
+    console.log(numPassed + "/" + numTests + " passed");
+    process.exit(numPassed == numTests ? 0 : 1);
+}
+
+// discover all tests
+var pathToTests = path.join(__dirname, "tests");
+
+fs.readdirSync(pathToTests).forEach(function(subdir) {
+    var p = path.join(pathToTests, subdir);
+    if (!fs.statSync(p).isDirectory()) return;
+    var l = /^level_([\d+])$/.exec(subdir);
+    if (!l) return;
+    l = l[1];
+    var files = fs.readdirSync(p);
+    for (var i = 0; i < files.length; i++) {
+        var f = files[i];
+        var m = /^([A-Za-z]+)_(.+)\.selector$/.exec(f);
+        if (m) {
+            if (!tests.hasOwnProperty(l)) tests[l] = [];
+            if (!tests[l].hasOwnProperty(m[1])) tests[l][m[1]] = [];
+            numTests++;
+            tests[l][m[1]].push([m[2], p]);
+        }
+    }
+});
+runTests();
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/.npmignore b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/.npmignore
new file mode 100644
index 0000000..b25c15b
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/.npmignore
@@ -0,0 +1 @@
+*~
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/README.md b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/README.md
new file mode 100644
index 0000000..a9e59ca
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/README.md
@@ -0,0 +1,22 @@
+## JSONSelect Conformance Tests.
+
+This repository contains conformance tests for the
+[JSONSelect](http://jsonselect.org) selector language.  The tests
+are divided among subdirectories which correspond to "level" supported
+by a particular implementation.  Levels are described more fully in the 
+jsonselect [documentation](http://jsonselect.org/#docs).
+
+## Test organization
+
+Test documents have a suffix of `.json`, like `basic.json`.
+
+Selectors to be applied to test documents have the document name,
+followed by an underbar, followed by a description of the test, with
+a `.selector` file name suffix, like `basic_grouping.selector`.
+
+Expected output files have the same name as the `.selector` file, 
+but have a `.output` suffix, like `basic_grouping.output`.
+
+Expected output files contain a stream of JSON objects that are what
+is expected to be produced when a given selector is applied to a given
+document.
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic.json b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic.json
new file mode 100644
index 0000000..83c3769
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic.json
@@ -0,0 +1,31 @@
+{
+    "name": {
+        "first": "Lloyd",
+        "last": "Hilaiel"
+    },
+    "favoriteColor": "yellow",
+    "languagesSpoken": [
+        {
+            "language": "Bulgarian",
+            "level": "advanced"
+        },
+        {
+            "language": "English",
+            "level": "native"
+        },
+        {
+            "language": "Spanish",
+            "level": "beginner"
+        }
+    ],
+    "seatingPreference": [
+        "window",
+        "aisle"
+    ],
+    "drinkPreference": [
+        "beer",
+        "whiskey",
+        "wine"
+    ],
+    "weight": 172
+}
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_first-child.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_first-child.output
new file mode 100644
index 0000000..ba6c81a
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_first-child.output
@@ -0,0 +1,2 @@
+"window"
+"beer"
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_first-child.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_first-child.selector
new file mode 100644
index 0000000..8288dac
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_first-child.selector
@@ -0,0 +1,2 @@
+string:first-child
+
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_grouping.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_grouping.output
new file mode 100644
index 0000000..ebbc034
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_grouping.output
@@ -0,0 +1,4 @@
+"advanced"
+"native"
+"beginner"
+172
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_grouping.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_grouping.selector
new file mode 100644
index 0000000..b8d0d79
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_grouping.selector
@@ -0,0 +1 @@
+string.level,number
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id.output
new file mode 100644
index 0000000..1511f18
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id.output
@@ -0,0 +1 @@
+"yellow"
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id.selector
new file mode 100644
index 0000000..7a3e32b
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id.selector
@@ -0,0 +1 @@
+.favoriteColor
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_multiple.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_multiple.output
new file mode 100644
index 0000000..9021aaa
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_multiple.output
@@ -0,0 +1,3 @@
+"Bulgarian"
+"English"
+"Spanish"
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_multiple.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_multiple.selector
new file mode 100644
index 0000000..24170a9
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_multiple.selector
@@ -0,0 +1 @@
+.language
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_quotes.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_quotes.output
new file mode 100644
index 0000000..1912273
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_quotes.output
@@ -0,0 +1,2 @@
+172
+
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_quotes.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_quotes.selector
new file mode 100644
index 0000000..1adaed0
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_quotes.selector
@@ -0,0 +1 @@
+."weight"
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_with_type.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_with_type.output
new file mode 100644
index 0000000..1511f18
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_with_type.output
@@ -0,0 +1 @@
+"yellow"
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_with_type.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_with_type.selector
new file mode 100644
index 0000000..48c2619
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_id_with_type.selector
@@ -0,0 +1 @@
+string.favoriteColor
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_last-child.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_last-child.output
new file mode 100644
index 0000000..9a334d0
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_last-child.output
@@ -0,0 +1,2 @@
+"aisle"
+"wine"
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_last-child.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_last-child.selector
new file mode 100644
index 0000000..8591de0
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_last-child.selector
@@ -0,0 +1,2 @@
+string:last-child
+
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-child-2.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-child-2.output
new file mode 100644
index 0000000..b55a882
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-child-2.output
@@ -0,0 +1,4 @@
+"window"
+"aisle"
+"beer"
+"whiskey"
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-child-2.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-child-2.selector
new file mode 100644
index 0000000..6418a97
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-child-2.selector
@@ -0,0 +1 @@
+string:nth-child(-n+2)
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-child.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-child.output
new file mode 100644
index 0000000..196d836
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-child.output
@@ -0,0 +1,3 @@
+"window"
+"beer"
+"wine"
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-child.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-child.selector
new file mode 100644
index 0000000..9bd4c5f
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-child.selector
@@ -0,0 +1 @@
+string:nth-child(odd)
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-last-child.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-last-child.output
new file mode 100644
index 0000000..9a334d0
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-last-child.output
@@ -0,0 +1,2 @@
+"aisle"
+"wine"
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-last-child.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-last-child.selector
new file mode 100644
index 0000000..bc100d5
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_nth-last-child.selector
@@ -0,0 +1 @@
+string:nth-last-child(1)
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_root_pseudo.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_root_pseudo.output
new file mode 100644
index 0000000..83c3769
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_root_pseudo.output
@@ -0,0 +1,31 @@
+{
+    "name": {
+        "first": "Lloyd",
+        "last": "Hilaiel"
+    },
+    "favoriteColor": "yellow",
+    "languagesSpoken": [
+        {
+            "language": "Bulgarian",
+            "level": "advanced"
+        },
+        {
+            "language": "English",
+            "level": "native"
+        },
+        {
+            "language": "Spanish",
+            "level": "beginner"
+        }
+    ],
+    "seatingPreference": [
+        "window",
+        "aisle"
+    ],
+    "drinkPreference": [
+        "beer",
+        "whiskey",
+        "wine"
+    ],
+    "weight": 172
+}
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_root_pseudo.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_root_pseudo.selector
new file mode 100644
index 0000000..4035e80
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_root_pseudo.selector
@@ -0,0 +1 @@
+:root
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type.output
new file mode 100644
index 0000000..332db77
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type.output
@@ -0,0 +1,14 @@
+"Lloyd"
+"Hilaiel"
+"yellow"
+"Bulgarian"
+"advanced"
+"English"
+"native"
+"Spanish"
+"beginner"
+"window"
+"aisle"
+"beer"
+"whiskey"
+"wine"
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type.selector
new file mode 100644
index 0000000..ec186f1
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type.selector
@@ -0,0 +1 @@
+string
\ No newline at end of file
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type2.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type2.output
new file mode 100644
index 0000000..730a054
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type2.output
@@ -0,0 +1 @@
+172
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type2.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type2.selector
new file mode 100644
index 0000000..0dfad6f
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type2.selector
@@ -0,0 +1 @@
+number
\ No newline at end of file
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type3.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type3.output
new file mode 100644
index 0000000..8394ec8
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type3.output
@@ -0,0 +1,47 @@
+{
+    "first": "Lloyd",
+    "last": "Hilaiel"
+}
+{
+    "language": "Bulgarian",
+    "level": "advanced"
+}
+{
+    "language": "English",
+    "level": "native"
+}
+{
+    "language": "Spanish",
+    "level": "beginner"
+}
+{
+    "name": {
+        "first": "Lloyd",
+        "last": "Hilaiel"
+    },
+    "favoriteColor": "yellow",
+    "languagesSpoken": [
+        {
+            "language": "Bulgarian",
+            "level": "advanced"
+        },
+        {
+            "language": "English",
+            "level": "native"
+        },
+        {
+            "language": "Spanish",
+            "level": "beginner"
+        }
+    ],
+    "seatingPreference": [
+        "window",
+        "aisle"
+    ],
+    "drinkPreference": [
+        "beer",
+        "whiskey",
+        "wine"
+    ],
+    "weight": 172
+}
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type3.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type3.selector
new file mode 100644
index 0000000..e2f6b77
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_type3.selector
@@ -0,0 +1 @@
+object
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_universal.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_universal.output
new file mode 100644
index 0000000..cfbf20a
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_universal.output
@@ -0,0 +1,85 @@
+"Lloyd"
+"Hilaiel"
+{
+    "first": "Lloyd",
+    "last": "Hilaiel"
+}
+"yellow"
+"Bulgarian"
+"advanced"
+{
+    "language": "Bulgarian",
+    "level": "advanced"
+}
+"English"
+"native"
+{
+    "language": "English",
+    "level": "native"
+}
+"Spanish"
+"beginner"
+{
+    "language": "Spanish",
+    "level": "beginner"
+}
+[
+    {
+        "language": "Bulgarian",
+        "level": "advanced"
+    },
+    {
+        "language": "English",
+        "level": "native"
+    },
+    {
+        "language": "Spanish",
+        "level": "beginner"
+    }
+]
+"window"
+"aisle"
+[
+    "window",
+    "aisle"
+]
+"beer"
+"whiskey"
+"wine"
+[
+    "beer",
+    "whiskey",
+    "wine"
+]
+172
+{
+    "name": {
+        "first": "Lloyd",
+        "last": "Hilaiel"
+    },
+    "favoriteColor": "yellow",
+    "languagesSpoken": [
+        {
+            "language": "Bulgarian",
+            "level": "advanced"
+        },
+        {
+            "language": "English",
+            "level": "native"
+        },
+        {
+            "language": "Spanish",
+            "level": "beginner"
+        }
+    ],
+    "seatingPreference": [
+        "window",
+        "aisle"
+    ],
+    "drinkPreference": [
+        "beer",
+        "whiskey",
+        "wine"
+    ],
+    "weight": 172
+}
\ No newline at end of file
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_universal.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_universal.selector
new file mode 100644
index 0000000..72e8ffc
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/basic_universal.selector
@@ -0,0 +1 @@
+*
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision.json b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision.json
new file mode 100644
index 0000000..d2bc912
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision.json
@@ -0,0 +1,6 @@
+{
+    "object": {
+      "string": "some string",
+      "stringTwo": "some other string"
+    }
+}
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_nested.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_nested.output
new file mode 100644
index 0000000..e39067c
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_nested.output
@@ -0,0 +1 @@
+"some string"
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_nested.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_nested.selector
new file mode 100644
index 0000000..5eaa374
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_nested.selector
@@ -0,0 +1 @@
+.object .string
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_quoted-string.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_quoted-string.output
new file mode 100644
index 0000000..e39067c
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_quoted-string.output
@@ -0,0 +1 @@
+"some string"
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_quoted-string.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_quoted-string.selector
new file mode 100644
index 0000000..4faa4aa
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_quoted-string.selector
@@ -0,0 +1 @@
+."string"
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_string.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_string.output
new file mode 100644
index 0000000..e39067c
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_string.output
@@ -0,0 +1 @@
+"some string"
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_string.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_string.selector
new file mode 100644
index 0000000..8b2be0c
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_1/collision_string.selector
@@ -0,0 +1 @@
+.string
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling.json b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling.json
new file mode 100644
index 0000000..6e5aecb
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling.json
@@ -0,0 +1,18 @@
+{
+  "a": 1,
+  "b": 2,
+  "c": {
+    "a": 3,
+    "b": 4,
+    "c": {
+      "a": 5,
+      "b": 6
+    }
+  },
+  "d": {
+    "a": 7
+  },
+  "e": {
+    "b": 8
+  }
+}
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_childof.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_childof.output
new file mode 100644
index 0000000..0cfbf08
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_childof.output
@@ -0,0 +1 @@
+2
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_childof.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_childof.selector
new file mode 100644
index 0000000..f13fbdd
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_childof.selector
@@ -0,0 +1,2 @@
+:root > .a ~ .b
+
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_descendantof.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_descendantof.output
new file mode 100644
index 0000000..e2ba1ef
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_descendantof.output
@@ -0,0 +1,3 @@
+2
+4
+6
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_descendantof.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_descendantof.selector
new file mode 100644
index 0000000..7c637b9
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_descendantof.selector
@@ -0,0 +1 @@
+:root .a ~ .b
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_unrooted.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_unrooted.output
new file mode 100644
index 0000000..e2ba1ef
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_unrooted.output
@@ -0,0 +1,3 @@
+2
+4
+6
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_unrooted.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_unrooted.selector
new file mode 100644
index 0000000..9a4ae06
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_2/sibling_unrooted.selector
@@ -0,0 +1 @@
+.a ~ .b
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic.json b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic.json
new file mode 100644
index 0000000..83c3769
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic.json
@@ -0,0 +1,31 @@
+{
+    "name": {
+        "first": "Lloyd",
+        "last": "Hilaiel"
+    },
+    "favoriteColor": "yellow",
+    "languagesSpoken": [
+        {
+            "language": "Bulgarian",
+            "level": "advanced"
+        },
+        {
+            "language": "English",
+            "level": "native"
+        },
+        {
+            "language": "Spanish",
+            "level": "beginner"
+        }
+    ],
+    "seatingPreference": [
+        "window",
+        "aisle"
+    ],
+    "drinkPreference": [
+        "beer",
+        "whiskey",
+        "wine"
+    ],
+    "weight": 172
+}
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-multiple.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-multiple.output
new file mode 100644
index 0000000..1cb38cb
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-multiple.output
@@ -0,0 +1,4 @@
+{
+    "first": "Lloyd",
+    "last": "Hilaiel"
+}
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-multiple.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-multiple.selector
new file mode 100644
index 0000000..956f1bc
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-multiple.selector
@@ -0,0 +1 @@
+:root > object:has(string.first):has(string.last)
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-root-in-expr.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-root-in-expr.output
new file mode 100644
index 0000000..1cb38cb
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-root-in-expr.output
@@ -0,0 +1,4 @@
+{
+    "first": "Lloyd",
+    "last": "Hilaiel"
+}
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-root-in-expr.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-root-in-expr.selector
new file mode 100644
index 0000000..329e224
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-root-in-expr.selector
@@ -0,0 +1 @@
+:has(:root > .first)
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-sans-first-paren.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-sans-first-paren.output
new file mode 100644
index 0000000..32f476b
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-sans-first-paren.output
@@ -0,0 +1 @@
+Error: opening paren expected '(' in 'object:has .language)'
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-sans-first-paren.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-sans-first-paren.selector
new file mode 100644
index 0000000..6165481
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-sans-first-paren.selector
@@ -0,0 +1 @@
+object:has .language)
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-sans-paren.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-sans-paren.output
new file mode 100644
index 0000000..3baa674
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-sans-paren.output
@@ -0,0 +1 @@
+Error: missing closing paren in 'object:has(.language'
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-sans-paren.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-sans-paren.selector
new file mode 100644
index 0000000..8111708
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-sans-paren.selector
@@ -0,0 +1,2 @@
+object:has(.language
+
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-whitespace.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-whitespace.output
new file mode 100644
index 0000000..7a87f46
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-whitespace.output
@@ -0,0 +1,12 @@
+{
+    "language": "Bulgarian",
+    "level": "advanced"
+}
+{
+    "language": "English",
+    "level": "native"
+}
+{
+    "language": "Spanish",
+    "level": "beginner"
+}
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-whitespace.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-whitespace.selector
new file mode 100644
index 0000000..69d2801
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-whitespace.selector
@@ -0,0 +1 @@
+.languagesSpoken object:has       (       .language       ) 
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-with-comma.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-with-comma.output
new file mode 100644
index 0000000..2df8708
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-with-comma.output
@@ -0,0 +1,17 @@
+{
+    "first": "Lloyd",
+    "last": "Hilaiel"
+}
+{
+    "language": "Bulgarian",
+    "level": "advanced"
+}
+{
+    "language": "English",
+    "level": "native"
+}
+{
+    "language": "Spanish",
+    "level": "beginner"
+}
+
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-with-comma.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-with-comma.selector
new file mode 100644
index 0000000..67e78d3
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has-with-comma.selector
@@ -0,0 +1 @@
+:has(:root > .language, :root > .last)
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has.output
new file mode 100644
index 0000000..7a87f46
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has.output
@@ -0,0 +1,12 @@
+{
+    "language": "Bulgarian",
+    "level": "advanced"
+}
+{
+    "language": "English",
+    "level": "native"
+}
+{
+    "language": "Spanish",
+    "level": "beginner"
+}
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has.selector
new file mode 100644
index 0000000..aaa0c19
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_has.selector
@@ -0,0 +1 @@
+.languagesSpoken object:has(.language)
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_multiple-has-with-strings.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_multiple-has-with-strings.output
new file mode 100644
index 0000000..1ce5c78
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_multiple-has-with-strings.output
@@ -0,0 +1,5 @@
+{
+    "first": "Lloyd",
+    "last": "Hilaiel"
+}
+
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_multiple-has-with-strings.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_multiple-has-with-strings.selector
new file mode 100644
index 0000000..7dd0647
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/basic_multiple-has-with-strings.selector
@@ -0,0 +1 @@
+:has(:val("Lloyd")) object:has(:val("Hilaiel"))
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr.json b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr.json
new file mode 100644
index 0000000..e1c9de8
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr.json
@@ -0,0 +1,9 @@
+{
+    "int": 42,
+    "float": 3.1415,
+    "string": "foo.exe",
+    "string2": "bar.dmg",
+    "null": null,
+    "true": true,
+    "false": false
+}
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_div.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_div.output
new file mode 100644
index 0000000..d81cc07
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_div.output
@@ -0,0 +1 @@
+42
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_div.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_div.selector
new file mode 100644
index 0000000..55a288c
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_div.selector
@@ -0,0 +1 @@
+:expr(7 = x / 6)
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_ends-with.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_ends-with.output
new file mode 100644
index 0000000..5517937
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_ends-with.output
@@ -0,0 +1 @@
+"bar.dmg"
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_ends-with.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_ends-with.selector
new file mode 100644
index 0000000..456c961
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_ends-with.selector
@@ -0,0 +1 @@
+:expr(x $= ".dmg")
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_false-eq.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_false-eq.output
new file mode 100644
index 0000000..a232278
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_false-eq.output
@@ -0,0 +1,3 @@
+false
+
+
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_false-eq.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_false-eq.selector
new file mode 100644
index 0000000..b906ff9
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_false-eq.selector
@@ -0,0 +1,4 @@
+:expr(false=x)
+
+
+
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_greater-than.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_greater-than.output
new file mode 100644
index 0000000..4f86ad6
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_greater-than.output
@@ -0,0 +1,2 @@
+42
+3.1415
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_greater-than.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_greater-than.selector
new file mode 100644
index 0000000..878cf08
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_greater-than.selector
@@ -0,0 +1 @@
+:expr(x>3.1)
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_less-than.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_less-than.output
new file mode 100644
index 0000000..4f34d97
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_less-than.output
@@ -0,0 +1,2 @@
+3.1415
+
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_less-than.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_less-than.selector
new file mode 100644
index 0000000..bd4f1bc
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_less-than.selector
@@ -0,0 +1 @@
+:expr(x<4)
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_mod.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_mod.output
new file mode 100644
index 0000000..d81cc07
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_mod.output
@@ -0,0 +1 @@
+42
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_mod.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_mod.selector
new file mode 100644
index 0000000..fec575b
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_mod.selector
@@ -0,0 +1 @@
+:expr((12 % 10) + 40 = x)
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_mult.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_mult.output
new file mode 100644
index 0000000..d81cc07
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_mult.output
@@ -0,0 +1 @@
+42
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_mult.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_mult.selector
new file mode 100644
index 0000000..869c723
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_mult.selector
@@ -0,0 +1 @@
+:expr(7 * 6 = x)
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_null-eq.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_null-eq.output
new file mode 100644
index 0000000..9fba3cf
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_null-eq.output
@@ -0,0 +1,5 @@
+null
+
+
+
+
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_null-eq.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_null-eq.selector
new file mode 100644
index 0000000..d015ac3
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_null-eq.selector
@@ -0,0 +1,4 @@
+:expr(null=x)
+
+
+
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_number-eq.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_number-eq.output
new file mode 100644
index 0000000..d81cc07
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_number-eq.output
@@ -0,0 +1 @@
+42
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_number-eq.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_number-eq.selector
new file mode 100644
index 0000000..4440ecb
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_number-eq.selector
@@ -0,0 +1 @@
+:expr(42 = x)
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_precedence-1.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_precedence-1.output
new file mode 100644
index 0000000..f32a580
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_precedence-1.output
@@ -0,0 +1 @@
+true
\ No newline at end of file
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_precedence-1.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_precedence-1.selector
new file mode 100644
index 0000000..eb214ef
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_precedence-1.selector
@@ -0,0 +1 @@
+.true:expr( 4 + 5 * 6 / 5 + 3 * 2 = 16 )
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_precedence-2.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_precedence-2.output
new file mode 100644
index 0000000..27ba77d
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_precedence-2.output
@@ -0,0 +1 @@
+true
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_precedence-2.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_precedence-2.selector
new file mode 100644
index 0000000..79c7f2c
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_precedence-2.selector
@@ -0,0 +1 @@
+.true:expr( (4 + 5) * 6 / (2 + 1) * 2 = 36 )
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_simple-false.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_simple-false.output
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_simple-false.output
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_simple-false.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_simple-false.selector
new file mode 100644
index 0000000..d39a80a
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_simple-false.selector
@@ -0,0 +1 @@
+.true:expr(1=2)
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_simple.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_simple.output
new file mode 100644
index 0000000..27ba77d
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_simple.output
@@ -0,0 +1 @@
+true
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_simple.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_simple.selector
new file mode 100644
index 0000000..d912927
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_simple.selector
@@ -0,0 +1 @@
+.true:expr(1=1)
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_starts-with.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_starts-with.output
new file mode 100644
index 0000000..f07ea50
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_starts-with.output
@@ -0,0 +1 @@
+"foo.exe"
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_starts-with.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_starts-with.selector
new file mode 100644
index 0000000..f76cd75
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_starts-with.selector
@@ -0,0 +1 @@
+:expr(x ^= "foo")
\ No newline at end of file
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_string-eq.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_string-eq.output
new file mode 100644
index 0000000..f07ea50
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_string-eq.output
@@ -0,0 +1 @@
+"foo.exe"
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_string-eq.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_string-eq.selector
new file mode 100644
index 0000000..0f19d62
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_string-eq.selector
@@ -0,0 +1 @@
+:expr(x = "foo.exe")
\ No newline at end of file
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_true-eq.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_true-eq.output
new file mode 100644
index 0000000..28b0c28
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_true-eq.output
@@ -0,0 +1,3 @@
+true
+
+
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_true-eq.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_true-eq.selector
new file mode 100644
index 0000000..02e1841
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/expr_true-eq.selector
@@ -0,0 +1,2 @@
+:expr(true = x)
+
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/polykids.json b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/polykids.json
new file mode 100644
index 0000000..2429d0f
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/polykids.json
@@ -0,0 +1,15 @@
+[
+    {
+        "language": "Bulgarian",
+        "level": "advanced"
+    },
+    {
+        "language": "English",
+        "level": "native",
+        "preferred": true
+    },
+    {
+        "language": "Spanish",
+        "level": "beginner"
+    }
+]
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/polykids_has-with_descendant.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/polykids_has-with_descendant.output
new file mode 100644
index 0000000..9699f99
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/polykids_has-with_descendant.output
@@ -0,0 +1 @@
+"English"
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/polykids_has-with_descendant.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/polykids_has-with_descendant.selector
new file mode 100644
index 0000000..84d51be
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/polykids_has-with_descendant.selector
@@ -0,0 +1 @@
+:has(.preferred) > .language
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/polykids_val.output b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/polykids_val.output
new file mode 100644
index 0000000..fd1d03f
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/polykids_val.output
@@ -0,0 +1,2 @@
+"advanced"
+
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/polykids_val.selector b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/polykids_val.selector
new file mode 100644
index 0000000..076f443
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/JSONSelect/src/test/tests/level_3/polykids_val.selector
@@ -0,0 +1 @@
+:has(:root > .language:val("Bulgarian")) > .level
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/.npmignore b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/.npmignore
new file mode 100644
index 0000000..3c3629e
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/.npmignore
@@ -0,0 +1 @@
+node_modules
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/LICENSE b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/LICENSE
new file mode 100644
index 0000000..7b75500
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/LICENSE
@@ -0,0 +1,24 @@
+Copyright 2010 James Halliday (mail@substack.net)
+
+This project is free software released under the MIT/X11 license:
+http://www.opensource.org/licenses/mit-license.php 
+
+Copyright 2010 James Halliday (mail@substack.net)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/README.markdown b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/README.markdown
new file mode 100644
index 0000000..0734006
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/README.markdown
@@ -0,0 +1,273 @@
+traverse
+========
+
+Traverse and transform objects by visiting every node on a recursive walk.
+
+examples
+========
+
+transform negative numbers in-place
+-----------------------------------
+
+negative.js
+
+````javascript
+var traverse = require('traverse');
+var obj = [ 5, 6, -3, [ 7, 8, -2, 1 ], { f : 10, g : -13 } ];
+
+traverse(obj).forEach(function (x) {
+    if (x < 0) this.update(x + 128);
+});
+
+console.dir(obj);
+````
+
+Output:
+
+    [ 5, 6, 125, [ 7, 8, 126, 1 ], { f: 10, g: 115 } ]
+
+collect leaf nodes
+------------------
+
+leaves.js
+
+````javascript
+var traverse = require('traverse');
+
+var obj = {
+    a : [1,2,3],
+    b : 4,
+    c : [5,6],
+    d : { e : [7,8], f : 9 },
+};
+
+var leaves = traverse(obj).reduce(function (acc, x) {
+    if (this.isLeaf) acc.push(x);
+    return acc;
+}, []);
+
+console.dir(leaves);
+````
+
+Output:
+
+    [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
+
+scrub circular references
+-------------------------
+
+scrub.js:
+
+````javascript
+var traverse = require('traverse');
+
+var obj = { a : 1, b : 2, c : [ 3, 4 ] };
+obj.c.push(obj);
+
+var scrubbed = traverse(obj).map(function (x) {
+    if (this.circular) this.remove()
+});
+console.dir(scrubbed);
+````
+
+output:
+
+    { a: 1, b: 2, c: [ 3, 4 ] }
+
+context
+=======
+
+Each method that takes a callback has a context (its `this` object) with these
+attributes:
+
+this.node
+---------
+
+The present node on the recursive walk
+
+this.path
+---------
+
+An array of string keys from the root to the present node
+
+this.parent
+-----------
+
+The context of the node's parent.
+This is `undefined` for the root node.
+
+this.key
+--------
+
+The name of the key of the present node in its parent.
+This is `undefined` for the root node.
+
+this.isRoot, this.notRoot
+-------------------------
+
+Whether the present node is the root node
+
+this.isLeaf, this.notLeaf
+-------------------------
+
+Whether or not the present node is a leaf node (has no children)
+
+this.level
+----------
+
+Depth of the node within the traversal
+
+this.circular
+-------------
+
+If the node equals one of its parents, the `circular` attribute is set to the
+context of that parent and the traversal progresses no deeper.
+
+this.update(value, stopHere=false)
+----------------------------------
+
+Set a new value for the present node.
+
+All the elements in `value` will be recursively traversed unless `stopHere` is
+true.
+
+this.remove()
+-------------
+
+Remove the current element from the output. If the node is in an Array it will
+be spliced off. Otherwise it will be deleted from its parent.
+
+this.delete()
+-------------
+
+Delete the current element from its parent in the output. Calls `delete` even on
+Arrays.
+
+this.before(fn)
+---------------
+
+Call this function before any of the children are traversed.
+
+You can assign into `this.keys` here to traverse in a custom order.
+
+this.after(fn)
+--------------
+
+Call this function after any of the children are traversed.
+
+this.pre(fn)
+------------
+
+Call this function before each of the children are traversed.
+
+this.post(fn)
+-------------
+
+Call this function after each of the children are traversed.
+
+methods
+=======
+
+.map(fn)
+--------
+
+Execute `fn` for each node in the object and return a new object with the
+results of the walk. To update nodes in the result use `this.update(value)`.
+
+.forEach(fn)
+------------
+
+Execute `fn` for each node in the object but unlike `.map()`, when
+`this.update()` is called it updates the object in-place.
+
+.reduce(fn, acc)
+----------------
+
+For each node in the object, perform a
+[left-fold](http://en.wikipedia.org/wiki/Fold_(higher-order_function))
+with the return value of `fn(acc, node)`.
+
+If `acc` isn't specified, `acc` is set to the root object for the first step
+and the root element is skipped.
+
+.deepEqual(obj)
+---------------
+
+Returns a boolean, whether the instance value is equal to the supplied object
+along a deep traversal using some opinionated choices.
+
+Some notes:
+
+* RegExps are equal if their .toString()s match, but not functions since
+functions can close over different variables.
+
+* Date instances are compared using `.getTime()` just like `assert.deepEqual()`.
+
+* Circular references must refer to the same paths within the data structure for
+both objects. For instance, in this snippet:
+
+````javascript
+var a = [1];
+a.push(a); // a = [ 1, *a ]
+
+var b = [1];
+b.push(a); // b = [ 1, [ 1, *a ] ]
+````
+
+`a` is not the same as `b` since even though the expansion is the same, the
+circular references in each refer to different paths into the data structure.
+
+However, in:
+
+````javascript
+var c = [1];
+c.push(c); // c = [ 1, *c ];
+````
+
+`c` is equal to `a` in a `deepEqual()` because they have the same terminal node
+structure.
+
+* Arguments objects are not arrays and neither are they the same as regular
+objects.
+
+* Instances created with `new` of String, Boolean, and Number types are never
+equal to the native versions.
+
+.paths()
+--------
+
+Return an `Array` of every possible non-cyclic path in the object.
+Paths are `Array`s of string keys.
+
+.nodes()
+--------
+
+Return an `Array` of every node in the object.
+
+.clone()
+--------
+
+Create a deep clone of the object.
+
+installation
+============
+
+Using npm:
+    npm install traverse
+
+Or check out the repository and link your development copy:
+    git clone http://github.com/substack/js-traverse.git
+    cd js-traverse
+    npm link .
+
+You can test traverse with "expresso":http://github.com/visionmedia/expresso
+(`npm install expresso`):
+    js-traverse $ expresso
+    
+    100% wahoo, your stuff is not broken!
+
+hash transforms
+===============
+
+This library formerly had a hash transformation component. It has been
+[moved to the hashish package](https://github.com/substack/node-hashish).
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/examples/json.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/examples/json.js
new file mode 100755
index 0000000..50d612e
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/examples/json.js
@@ -0,0 +1,16 @@
+var traverse = require('traverse');
+
+var id = 54;
+var callbacks = {};
+var obj = { moo : function () {}, foo : [2,3,4, function () {}] };
+
+var scrubbed = traverse(obj).map(function (x) {
+    if (typeof x === 'function') {
+        callbacks[id] = { id : id, f : x, path : this.path };
+        this.update('[Function]');
+        id++;
+    }
+});
+
+console.dir(scrubbed);
+console.dir(callbacks);
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/examples/leaves.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/examples/leaves.js
new file mode 100755
index 0000000..c1b310b
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/examples/leaves.js
@@ -0,0 +1,15 @@
+var traverse = require('traverse');
+
+var obj = {
+    a : [1,2,3],
+    b : 4,
+    c : [5,6],
+    d : { e : [7,8], f : 9 },
+};
+
+var leaves = traverse(obj).reduce(function (acc, x) {
+    if (this.isLeaf) acc.push(x);
+    return acc;
+}, []);
+
+console.dir(leaves);
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/examples/negative.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/examples/negative.js
new file mode 100755
index 0000000..78608a0
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/examples/negative.js
@@ -0,0 +1,8 @@
+var traverse = require('traverse');
+var obj = [ 5, 6, -3, [ 7, 8, -2, 1 ], { f : 10, g : -13 } ];
+
+traverse(obj).forEach(function (x) {
+    if (x < 0) this.update(x + 128);
+});
+
+console.dir(obj);
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/examples/scrub.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/examples/scrub.js
new file mode 100755
index 0000000..5d15b91
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/examples/scrub.js
@@ -0,0 +1,10 @@
+// scrub out circular references
+var traverse = require('traverse');
+
+var obj = { a : 1, b : 2, c : [ 3, 4 ] };
+obj.c.push(obj);
+
+var scrubbed = traverse(obj).map(function (x) {
+    if (this.circular) this.remove()
+});
+console.dir(scrubbed);
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/examples/stringify.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/examples/stringify.js
new file mode 100755
index 0000000..167b68b
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/examples/stringify.js
@@ -0,0 +1,38 @@
+#!/usr/bin/env node
+var traverse = require('traverse');
+
+var obj = [ 'five', 6, -3, [ 7, 8, -2, 1 ], { f : 10, g : -13 } ];
+
+var s = '';
+traverse(obj).forEach(function to_s (node) {
+    if (Array.isArray(node)) {
+        this.before(function () { s += '[' });
+        this.post(function (child) {
+            if (!child.isLast) s += ',';
+        });
+        this.after(function () { s += ']' });
+    }
+    else if (typeof node == 'object') {
+        this.before(function () { s += '{' });
+        this.pre(function (x, key) {
+            to_s(key);
+            s += ':';
+        });
+        this.post(function (child) {
+            if (!child.isLast) s += ',';
+        });
+        this.after(function () { s += '}' });
+    }
+    else if (typeof node == 'string') {
+        s += '"' + node.toString().replace(/"/g, '\\"') + '"';
+    }
+    else if (typeof node == 'function') {
+        s += 'null';
+    }
+    else {
+        s += node.toString();
+    }
+});
+
+console.log('JSON.stringify: ' + JSON.stringify(obj));
+console.log('this stringify: ' + s);
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/index.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/index.js
new file mode 100644
index 0000000..7161685
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/index.js
@@ -0,0 +1,332 @@
+module.exports = Traverse;
+function Traverse (obj) {
+    if (!(this instanceof Traverse)) return new Traverse(obj);
+    this.value = obj;
+}
+
+Traverse.prototype.get = function (ps) {
+    var node = this.value;
+    for (var i = 0; i < ps.length; i ++) {
+        var key = ps[i];
+        if (!Object.hasOwnProperty.call(node, key)) {
+            node = undefined;
+            break;
+        }
+        node = node[key];
+    }
+    return node;
+};
+
+Traverse.prototype.set = function (ps, value) {
+    var node = this.value;
+    for (var i = 0; i < ps.length - 1; i ++) {
+        var key = ps[i];
+        if (!Object.hasOwnProperty.call(node, key)) node[key] = {};
+        node = node[key];
+    }
+    node[ps[i]] = value;
+    return value;
+};
+
+Traverse.prototype.map = function (cb) {
+    return walk(this.value, cb, true);
+};
+
+Traverse.prototype.forEach = function (cb) {
+    this.value = walk(this.value, cb, false);
+    return this.value;
+};
+
+Traverse.prototype.reduce = function (cb, init) {
+    var skip = arguments.length === 1;
+    var acc = skip ? this.value : init;
+    this.forEach(function (x) {
+        if (!this.isRoot || !skip) {
+            acc = cb.call(this, acc, x);
+        }
+    });
+    return acc;
+};
+
+Traverse.prototype.deepEqual = function (obj) {
+    if (arguments.length !== 1) {
+        throw new Error(
+            'deepEqual requires exactly one object to compare against'
+        );
+    }
+    
+    var equal = true;
+    var node = obj;
+    
+    this.forEach(function (y) {
+        var notEqual = (function () {
+            equal = false;
+            //this.stop();
+            return undefined;
+        }).bind(this);
+        
+        //if (node === undefined || node === null) return notEqual();
+        
+        if (!this.isRoot) {
+        /*
+            if (!Object.hasOwnProperty.call(node, this.key)) {
+                return notEqual();
+            }
+        */
+            if (typeof node !== 'object') return notEqual();
+            node = node[this.key];
+        }
+        
+        var x = node;
+        
+        this.post(function () {
+            node = x;
+        });
+        
+        var toS = function (o) {
+            return Object.prototype.toString.call(o);
+        };
+        
+        if (this.circular) {
+            if (Traverse(obj).get(this.circular.path) !== x) notEqual();
+        }
+        else if (typeof x !== typeof y) {
+            notEqual();
+        }
+        else if (x === null || y === null || x === undefined || y === undefined) {
+            if (x !== y) notEqual();
+        }
+        else if (x.__proto__ !== y.__proto__) {
+            notEqual();
+        }
+        else if (x === y) {
+            // nop
+        }
+        else if (typeof x === 'function') {
+            if (x instanceof RegExp) {
+                // both regexps on account of the __proto__ check
+                if (x.toString() != y.toString()) notEqual();
+            }
+            else if (x !== y) notEqual();
+        }
+        else if (typeof x === 'object') {
+            if (toS(y) === '[object Arguments]'
+            || toS(x) === '[object Arguments]') {
+                if (toS(x) !== toS(y)) {
+                    notEqual();
+                }
+            }
+            else if (x instanceof Date || y instanceof Date) {
+                if (!(x instanceof Date) || !(y instanceof Date)
+                || x.getTime() !== y.getTime()) {
+                    notEqual();
+                }
+            }
+            else {
+                var kx = Object.keys(x);
+                var ky = Object.keys(y);
+                if (kx.length !== ky.length) return notEqual();
+                for (var i = 0; i < kx.length; i++) {
+                    var k = kx[i];
+                    if (!Object.hasOwnProperty.call(y, k)) {
+                        notEqual();
+                    }
+                }
+            }
+        }
+    });
+    
+    return equal;
+};
+
+Traverse.prototype.paths = function () {
+    var acc = [];
+    this.forEach(function (x) {
+        acc.push(this.path); 
+    });
+    return acc;
+};
+
+Traverse.prototype.nodes = function () {
+    var acc = [];
+    this.forEach(function (x) {
+        acc.push(this.node);
+    });
+    return acc;
+};
+
+Traverse.prototype.clone = function () {
+    var parents = [], nodes = [];
+    
+    return (function clone (src) {
+        for (var i = 0; i < parents.length; i++) {
+            if (parents[i] === src) {
+                return nodes[i];
+            }
+        }
+        
+        if (typeof src === 'object' && src !== null) {
+            var dst = copy(src);
+            
+            parents.push(src);
+            nodes.push(dst);
+            
+            Object.keys(src).forEach(function (key) {
+                dst[key] = clone(src[key]);
+            });
+            
+            parents.pop();
+            nodes.pop();
+            return dst;
+        }
+        else {
+            return src;
+        }
+    })(this.value);
+};
+
+function walk (root, cb, immutable) {
+    var path = [];
+    var parents = [];
+    var alive = true;
+    
+    return (function walker (node_) {
+        var node = immutable ? copy(node_) : node_;
+        var modifiers = {};
+        
+        var keepGoing = true;
+        
+        var state = {
+            node : node,
+            node_ : node_,
+            path : [].concat(path),
+            parent : parents[parents.length - 1],
+            parents : parents,
+            key : path.slice(-1)[0],
+            isRoot : path.length === 0,
+            level : path.length,
+            circular : null,
+            update : function (x, stopHere) {
+                if (!state.isRoot) {
+                    state.parent.node[state.key] = x;
+                }
+                state.node = x;
+                if (stopHere) keepGoing = false;
+            },
+            'delete' : function () {
+                delete state.parent.node[state.key];
+            },
+            remove : function () {
+                if (Array.isArray(state.parent.node)) {
+                    state.parent.node.splice(state.key, 1);
+                }
+                else {
+                    delete state.parent.node[state.key];
+                }
+            },
+            keys : null,
+            before : function (f) { modifiers.before = f },
+            after : function (f) { modifiers.after = f },
+            pre : function (f) { modifiers.pre = f },
+            post : function (f) { modifiers.post = f },
+            stop : function () { alive = false },
+            block : function () { keepGoing = false }
+        };
+        
+        if (!alive) return state;
+        
+        if (typeof node === 'object' && node !== null) {
+            state.keys = Object.keys(node);
+            
+            state.isLeaf = state.keys.length == 0;
+            
+            for (var i = 0; i < parents.length; i++) {
+                if (parents[i].node_ === node_) {
+                    state.circular = parents[i];
+                    break;
+                }
+            }
+        }
+        else {
+            state.isLeaf = true;
+        }
+        
+        state.notLeaf = !state.isLeaf;
+        state.notRoot = !state.isRoot;
+        
+        // use return values to update if defined
+        var ret = cb.call(state, state.node);
+        if (ret !== undefined && state.update) state.update(ret);
+        
+        if (modifiers.before) modifiers.before.call(state, state.node);
+        
+        if (!keepGoing) return state;
+        
+        if (typeof state.node == 'object'
+        && state.node !== null && !state.circular) {
+            parents.push(state);
+            
+            state.keys.forEach(function (key, i) {
+                path.push(key);
+                
+                if (modifiers.pre) modifiers.pre.call(state, state.node[key], key);
+                
+                var child = walker(state.node[key]);
+                if (immutable && Object.hasOwnProperty.call(state.node, key)) {
+                    state.node[key] = child.node;
+                }
+                
+                child.isLast = i == state.keys.length - 1;
+                child.isFirst = i == 0;
+                
+                if (modifiers.post) modifiers.post.call(state, child);
+                
+                path.pop();
+            });
+            parents.pop();
+        }
+        
+        if (modifiers.after) modifiers.after.call(state, state.node);
+        
+        return state;
+    })(root).node;
+}
+
+Object.keys(Traverse.prototype).forEach(function (key) {
+    Traverse[key] = function (obj) {
+        var args = [].slice.call(arguments, 1);
+        var t = Traverse(obj);
+        return t[key].apply(t, args);
+    };
+});
+
+function copy (src) {
+    if (typeof src === 'object' && src !== null) {
+        var dst;
+        
+        if (Array.isArray(src)) {
+            dst = [];
+        }
+        else if (src instanceof Date) {
+            dst = new Date(src);
+        }
+        else if (src instanceof Boolean) {
+            dst = new Boolean(src);
+        }
+        else if (src instanceof Number) {
+            dst = new Number(src);
+        }
+        else if (src instanceof String) {
+            dst = new String(src);
+        }
+        else {
+            dst = Object.create(Object.getPrototypeOf(src));
+        }
+        
+        Object.keys(src).forEach(function (key) {
+            dst[key] = src[key];
+        });
+        return dst;
+    }
+    else return src;
+}
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/package.json b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/package.json
new file mode 100644
index 0000000..e337200
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/package.json
@@ -0,0 +1,42 @@
+{
+  "name": "traverse",
+  "version": "0.4.6",
+  "description": "Traverse and transform objects by visiting every node on a recursive walk",
+  "author": {
+    "name": "James Halliday"
+  },
+  "license": "MIT/X11",
+  "main": "./index",
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/substack/js-traverse.git"
+  },
+  "devDependencies": {
+    "expresso": "0.7.x"
+  },
+  "scripts": {
+    "test": "expresso"
+  },
+  "_id": "traverse@0.4.6",
+  "dependencies": {},
+  "engines": {
+    "node": "*"
+  },
+  "_engineSupported": true,
+  "_npmVersion": "1.0.10",
+  "_nodeVersion": "v0.5.0-pre",
+  "_defaultsLoaded": true,
+  "dist": {
+    "shasum": "d04b2280e4c792a5815429ef7b8b60c64c9ccc34",
+    "tarball": "http://registry.npmjs.org/traverse/-/traverse-0.4.6.tgz"
+  },
+  "directories": {},
+  "_shasum": "d04b2280e4c792a5815429ef7b8b60c64c9ccc34",
+  "_from": "traverse@0.4.x",
+  "_resolved": "https://registry.npmjs.org/traverse/-/traverse-0.4.6.tgz",
+  "bugs": {
+    "url": "https://github.com/substack/js-traverse/issues"
+  },
+  "readme": "ERROR: No README data found!",
+  "homepage": "https://github.com/substack/js-traverse"
+}
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/circular.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/circular.js
new file mode 100644
index 0000000..e1eef3f
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/circular.js
@@ -0,0 +1,114 @@
+var assert = require('assert');
+var Traverse = require('traverse');
+var util = require('util');
+
+exports.circular = function () {
+    var obj = { x : 3 };
+    obj.y = obj;
+    var foundY = false;
+    Traverse(obj).forEach(function (x) {
+        if (this.path.join('') == 'y') {
+            assert.equal(
+                util.inspect(this.circular.node),
+                util.inspect(obj)
+            );
+            foundY = true;
+        }
+    });
+    assert.ok(foundY);
+};
+
+exports.deepCirc = function () {
+    var obj = { x : [ 1, 2, 3 ], y : [ 4, 5 ] };
+    obj.y[2] = obj;
+    
+    var times = 0;
+    Traverse(obj).forEach(function (x) {
+        if (this.circular) {
+            assert.deepEqual(this.circular.path, []);
+            assert.deepEqual(this.path, [ 'y', 2 ]);
+            times ++;
+        }
+    });
+    
+    assert.deepEqual(times, 1);
+};
+
+exports.doubleCirc = function () {
+    var obj = { x : [ 1, 2, 3 ], y : [ 4, 5 ] };
+    obj.y[2] = obj;
+    obj.x.push(obj.y);
+    
+    var circs = [];
+    Traverse(obj).forEach(function (x) {
+        if (this.circular) {
+            circs.push({ circ : this.circular, self : this, node : x });
+        }
+    });
+    
+    assert.deepEqual(circs[0].self.path, [ 'x', 3, 2 ]);
+    assert.deepEqual(circs[0].circ.path, []);
+     
+    assert.deepEqual(circs[1].self.path, [ 'y', 2 ]);
+    assert.deepEqual(circs[1].circ.path, []);
+    
+    assert.deepEqual(circs.length, 2);
+};
+
+exports.circDubForEach = function () {
+    var obj = { x : [ 1, 2, 3 ], y : [ 4, 5 ] };
+    obj.y[2] = obj;
+    obj.x.push(obj.y);
+    
+    Traverse(obj).forEach(function (x) {
+        if (this.circular) this.update('...');
+    });
+    
+    assert.deepEqual(obj, { x : [ 1, 2, 3, [ 4, 5, '...' ] ], y : [ 4, 5, '...' ] });
+};
+
+exports.circDubMap = function () {
+    var obj = { x : [ 1, 2, 3 ], y : [ 4, 5 ] };
+    obj.y[2] = obj;
+    obj.x.push(obj.y);
+    
+    var c = Traverse(obj).map(function (x) {
+        if (this.circular) {
+            this.update('...');
+        }
+    });
+    
+    assert.deepEqual(c, { x : [ 1, 2, 3, [ 4, 5, '...' ] ], y : [ 4, 5, '...' ] });
+};
+
+exports.circClone = function () {
+    var obj = { x : [ 1, 2, 3 ], y : [ 4, 5 ] };
+    obj.y[2] = obj;
+    obj.x.push(obj.y);
+    
+    var clone = Traverse.clone(obj);
+    assert.ok(obj !== clone);
+    
+    assert.ok(clone.y[2] === clone);
+    assert.ok(clone.y[2] !== obj);
+    assert.ok(clone.x[3][2] === clone);
+    assert.ok(clone.x[3][2] !== obj);
+    assert.deepEqual(clone.x.slice(0,3), [1,2,3]);
+    assert.deepEqual(clone.y.slice(0,2), [4,5]);
+};
+
+exports.circMapScrub = function () {
+    var obj = { a : 1, b : 2 };
+    obj.c = obj;
+    
+    var scrubbed = Traverse(obj).map(function (node) {
+        if (this.circular) this.remove();
+    });
+    assert.deepEqual(
+        Object.keys(scrubbed).sort(),
+        [ 'a', 'b' ]
+    );
+    assert.ok(Traverse.deepEqual(scrubbed, { a : 1, b : 2 }));
+    
+    assert.equal(obj.c, obj);
+};
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/date.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/date.js
new file mode 100644
index 0000000..2cb8252
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/date.js
@@ -0,0 +1,35 @@
+var assert = require('assert');
+var Traverse = require('traverse');
+
+exports.dateEach = function () {
+    var obj = { x : new Date, y : 10, z : 5 };
+    
+    var counts = {};
+    
+    Traverse(obj).forEach(function (node) {
+        var t = (node instanceof Date && 'Date') || typeof node;
+        counts[t] = (counts[t] || 0) + 1;
+    });
+    
+    assert.deepEqual(counts, {
+        object : 1,
+        Date : 1,
+        number : 2,
+    });
+};
+
+exports.dateMap = function () {
+    var obj = { x : new Date, y : 10, z : 5 };
+    
+    var res = Traverse(obj).map(function (node) {
+        if (typeof node === 'number') this.update(node + 100);
+    });
+    
+    assert.ok(obj.x !== res.x);
+    assert.deepEqual(res, {
+        x : obj.x,
+        y : 110,
+        z : 105,
+    });
+};
+
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/equal.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/equal.js
new file mode 100644
index 0000000..4d732fa
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/equal.js
@@ -0,0 +1,219 @@
+var assert = require('assert');
+var traverse = require('traverse');
+
+exports.deepDates = function () {
+    assert.ok(
+        traverse.deepEqual(
+            { d : new Date, x : [ 1, 2, 3 ] },
+            { d : new Date, x : [ 1, 2, 3 ] }
+        ),
+        'dates should be equal'
+    );
+    
+    var d0 = new Date;
+    setTimeout(function () {
+        assert.ok(
+            !traverse.deepEqual(
+                { d : d0, x : [ 1, 2, 3 ], },
+                { d : new Date, x : [ 1, 2, 3 ] }
+            ),
+            'microseconds should count in date equality'
+        );
+    }, 5);
+};
+
+exports.deepCircular = function () {
+    var a = [1];
+    a.push(a); // a = [ 1, *a ]
+    
+    var b = [1];
+    b.push(a); // b = [ 1, [ 1, *a ] ]
+    
+    assert.ok(
+        !traverse.deepEqual(a, b),
+        'circular ref mount points count towards equality'
+    );
+    
+    var c = [1];
+    c.push(c); // c = [ 1, *c ]
+    assert.ok(
+        traverse.deepEqual(a, c),
+        'circular refs are structurally the same here'
+    );
+    
+    var d = [1];
+    d.push(a); // c = [ 1, [ 1, *d ] ]
+    assert.ok(
+        traverse.deepEqual(b, d),
+        'non-root circular ref structural comparison'
+    );
+};
+
+exports.deepInstances = function () {
+    assert.ok(
+        !traverse.deepEqual([ new Boolean(false) ], [ false ]),
+        'boolean instances are not real booleans'
+    );
+    
+    assert.ok(
+        !traverse.deepEqual([ new String('x') ], [ 'x' ]),
+        'string instances are not real strings'
+    );
+    
+    assert.ok(
+        !traverse.deepEqual([ new Number(4) ], [ 4 ]),
+        'number instances are not real numbers'
+    );
+    
+    assert.ok(
+        traverse.deepEqual([ new RegExp('x') ], [ /x/ ]),
+        'regexp instances are real regexps'
+    );
+    
+    assert.ok(
+        !traverse.deepEqual([ new RegExp(/./) ], [ /../ ]),
+        'these regexps aren\'t the same'
+    );
+    
+    assert.ok(
+        !traverse.deepEqual(
+            [ function (x) { return x * 2 } ],
+            [ function (x) { return x * 2 } ]
+        ),
+        'functions with the same .toString() aren\'t necessarily the same'
+    );
+    
+    var f = function (x) { return x * 2 };
+    assert.ok(
+        traverse.deepEqual([ f ], [ f ]),
+        'these functions are actually equal'
+    );
+};
+
+exports.deepEqual = function () {
+    assert.ok(
+        !traverse.deepEqual([ 1, 2, 3 ], { 0 : 1, 1 : 2, 2 : 3 }),
+        'arrays are not objects'
+    );
+};
+
+exports.falsy = function () {
+    assert.ok(
+        !traverse.deepEqual([ undefined ], [ null ]),
+        'null is not undefined!'
+    );
+    
+    assert.ok(
+        !traverse.deepEqual([ null ], [ undefined ]),
+        'undefined is not null!'
+    );
+    
+    assert.ok(
+        !traverse.deepEqual(
+            { a : 1, b : 2, c : [ 3, undefined, 5 ] },
+            { a : 1, b : 2, c : [ 3, null, 5 ] }
+        ),
+        'undefined is not null, however deeply!'
+    );
+    
+    assert.ok(
+        !traverse.deepEqual(
+            { a : 1, b : 2, c : [ 3, undefined, 5 ] },
+            { a : 1, b : 2, c : [ 3, null, 5 ] }
+        ),
+        'null is not undefined, however deeply!'
+    );
+    
+    assert.ok(
+        !traverse.deepEqual(
+            { a : 1, b : 2, c : [ 3, undefined, 5 ] },
+            { a : 1, b : 2, c : [ 3, null, 5 ] }
+        ),
+        'null is not undefined, however deeply!'
+    );
+};
+
+exports.deletedArrayEqual = function () {
+    var xs = [ 1, 2, 3, 4 ];
+    delete xs[2];
+    
+    var ys = Object.create(Array.prototype);
+    ys[0] = 1;
+    ys[1] = 2;
+    ys[3] = 4;
+    
+    assert.ok(
+        traverse.deepEqual(xs, ys),
+        'arrays with deleted elements are only equal to'
+        + ' arrays with similarly deleted elements'
+    );
+    
+    assert.ok(
+        !traverse.deepEqual(xs, [ 1, 2, undefined, 4 ]),
+        'deleted array elements cannot be undefined'
+    );
+    
+    assert.ok(
+        !traverse.deepEqual(xs, [ 1, 2, null, 4 ]),
+        'deleted array elements cannot be null'
+    );
+};
+
+exports.deletedObjectEqual = function () {
+    var obj = { a : 1, b : 2, c : 3 };
+    delete obj.c;
+    
+    assert.ok(
+        traverse.deepEqual(obj, { a : 1, b : 2 }),
+        'deleted object elements should not show up'
+    );
+    
+    assert.ok(
+        !traverse.deepEqual(obj, { a : 1, b : 2, c : undefined }),
+        'deleted object elements are not undefined'
+    );
+    
+    assert.ok(
+        !traverse.deepEqual(obj, { a : 1, b : 2, c : null }),
+        'deleted object elements are not null'
+    );
+};
+
+exports.emptyKeyEqual = function () {
+    assert.ok(!traverse.deepEqual(
+        { a : 1 }, { a : 1, '' : 55 }
+    ));
+};
+
+exports.deepArguments = function () {
+    assert.ok(
+        !traverse.deepEqual(
+            [ 4, 5, 6 ],
+            (function () { return arguments })(4, 5, 6)
+        ),
+        'arguments are not arrays'
+    );
+    
+    assert.ok(
+        traverse.deepEqual(
+            (function () { return arguments })(4, 5, 6),
+            (function () { return arguments })(4, 5, 6)
+        ),
+        'arguments should equal'
+    );
+};
+
+exports.deepUn = function () {
+    assert.ok(!traverse.deepEqual({ a : 1, b : 2 }, undefined));
+    assert.ok(!traverse.deepEqual({ a : 1, b : 2 }, {}));
+    assert.ok(!traverse.deepEqual(undefined, { a : 1, b : 2 }));
+    assert.ok(!traverse.deepEqual({}, { a : 1, b : 2 }));
+    assert.ok(traverse.deepEqual(undefined, undefined));
+    assert.ok(traverse.deepEqual(null, null));
+    assert.ok(!traverse.deepEqual(undefined, null));
+};
+
+exports.deepLevels = function () {
+    var xs = [ 1, 2, [ 3, 4, [ 5, 6 ] ] ];
+    assert.ok(!traverse.deepEqual(xs, []));
+};
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/instance.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/instance.js
new file mode 100644
index 0000000..501981f
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/instance.js
@@ -0,0 +1,17 @@
+var assert = require('assert');
+var Traverse = require('traverse');
+var EventEmitter = require('events').EventEmitter;
+
+exports['check instanceof on node elems'] = function () {
+    
+    var counts = { emitter : 0 };
+    
+    Traverse([ new EventEmitter, 3, 4, { ev : new EventEmitter }])
+        .forEach(function (node) {
+            if (node instanceof EventEmitter) counts.emitter ++;
+        })
+    ;
+    
+    assert.equal(counts.emitter, 2);
+};
+
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/interface.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/interface.js
new file mode 100644
index 0000000..df5b037
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/interface.js
@@ -0,0 +1,42 @@
+var assert = require('assert');
+var Traverse = require('traverse');
+
+exports['interface map'] = function () {
+    var obj = { a : [ 5,6,7 ], b : { c : [8] } };
+    
+    assert.deepEqual(
+        Traverse.paths(obj)
+            .sort()
+            .map(function (path) { return path.join('/') })
+            .slice(1)
+            .join(' ')
+         ,
+         'a a/0 a/1 a/2 b b/c b/c/0'
+    );
+    
+    assert.deepEqual(
+        Traverse.nodes(obj),
+        [
+            { a: [ 5, 6, 7 ], b: { c: [ 8 ] } },
+            [ 5, 6, 7 ], 5, 6, 7,
+            { c: [ 8 ] }, [ 8 ], 8
+        ]
+    );
+    
+    assert.deepEqual(
+        Traverse.map(obj, function (node) {
+            if (typeof node == 'number') {
+                return node + 1000;
+            }
+            else if (Array.isArray(node)) {
+                return node.join(' ');
+            }
+        }),
+        { a: '5 6 7', b: { c: '8' } }
+    );
+    
+    var nodes = 0;
+    Traverse.forEach(obj, function (node) { nodes ++ });
+    assert.deepEqual(nodes, 8);
+};
+
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/json.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/json.js
new file mode 100644
index 0000000..bf36620
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/json.js
@@ -0,0 +1,47 @@
+var assert = require('assert');
+var Traverse = require('traverse');
+
+exports['json test'] = function () {
+    var id = 54;
+    var callbacks = {};
+    var obj = { moo : function () {}, foo : [2,3,4, function () {}] };
+    
+    var scrubbed = Traverse(obj).map(function (x) {
+        if (typeof x === 'function') {
+            callbacks[id] = { id : id, f : x, path : this.path };
+            this.update('[Function]');
+            id++;
+        }
+    });
+    
+    assert.equal(
+        scrubbed.moo, '[Function]',
+        'obj.moo replaced with "[Function]"'
+    );
+    
+    assert.equal(
+        scrubbed.foo[3], '[Function]',
+        'obj.foo[3] replaced with "[Function]"'
+    );
+    
+    assert.deepEqual(scrubbed, {
+        moo : '[Function]',
+        foo : [ 2, 3, 4, "[Function]" ]
+    }, 'Full JSON string matches');
+    
+    assert.deepEqual(
+        typeof obj.moo, 'function',
+        'Original obj.moo still a function'
+    );
+    
+    assert.deepEqual(
+        typeof obj.foo[3], 'function',
+        'Original obj.foo[3] still a function'
+    );
+    
+    assert.deepEqual(callbacks, {
+        54: { id: 54, f : obj.moo, path: [ 'moo' ] },
+        55: { id: 55, f : obj.foo[3], path: [ 'foo', '3' ] },
+    }, 'Check the generated callbacks list');
+};
+
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/keys.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/keys.js
new file mode 100644
index 0000000..8bf88ed
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/keys.js
@@ -0,0 +1,29 @@
+var assert = require('assert');
+var Traverse = require('traverse');
+
+exports['sort test'] = function () {
+    var acc = [];
+    Traverse({
+        a: 30,
+        b: 22,
+        id: 9
+    }).forEach(function (node) {
+        if ((! Array.isArray(node)) && typeof node === 'object') {
+            this.before(function(node) {
+                this.keys = Object.keys(node);
+                this.keys.sort(function(a, b) {
+                    a = [a === "id" ? 0 : 1, a];
+                    b = [b === "id" ? 0 : 1, b];
+                    return a < b ? -1 : a > b ? 1 : 0;
+                });
+            });
+        }
+        if (this.isLeaf) acc.push(node);
+    });
+    
+    assert.equal(
+        acc.join(' '),
+        '9 30 22',
+        'Traversal in a custom order'
+    );
+};
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/leaves.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/leaves.js
new file mode 100644
index 0000000..4e8d280
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/leaves.js
@@ -0,0 +1,21 @@
+var assert = require('assert');
+var Traverse = require('traverse');
+
+exports['leaves test'] = function () {
+    var acc = [];
+    Traverse({
+        a : [1,2,3],
+        b : 4,
+        c : [5,6],
+        d : { e : [7,8], f : 9 }
+    }).forEach(function (x) {
+        if (this.isLeaf) acc.push(x);
+    });
+    
+    assert.equal(
+        acc.join(' '),
+        '1 2 3 4 5 6 7 8 9',
+        'Traversal in the right(?) order'
+    );
+};
+
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/mutability.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/mutability.js
new file mode 100644
index 0000000..5a4d6dd
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/mutability.js
@@ -0,0 +1,203 @@
+var assert = require('assert');
+var Traverse = require('traverse');
+
+exports.mutate = function () {
+    var obj = { a : 1, b : 2, c : [ 3, 4 ] };
+    var res = Traverse(obj).forEach(function (x) {
+        if (typeof x === 'number' && x % 2 === 0) {
+            this.update(x * 10);
+        }
+    });
+    assert.deepEqual(obj, res);
+    assert.deepEqual(obj, { a : 1, b : 20, c : [ 3, 40 ] });
+};
+
+exports.mutateT = function () {
+    var obj = { a : 1, b : 2, c : [ 3, 4 ] };
+    var res = Traverse.forEach(obj, function (x) {
+        if (typeof x === 'number' && x % 2 === 0) {
+            this.update(x * 10);
+        }
+    });
+    assert.deepEqual(obj, res);
+    assert.deepEqual(obj, { a : 1, b : 20, c : [ 3, 40 ] });
+};
+
+exports.map = function () {
+    var obj = { a : 1, b : 2, c : [ 3, 4 ] };
+    var res = Traverse(obj).map(function (x) {
+        if (typeof x === 'number' && x % 2 === 0) {
+            this.update(x * 10);
+        }
+    });
+    assert.deepEqual(obj, { a : 1, b : 2, c : [ 3, 4 ] });
+    assert.deepEqual(res, { a : 1, b : 20, c : [ 3, 40 ] });
+};
+
+exports.mapT = function () {
+    var obj = { a : 1, b : 2, c : [ 3, 4 ] };
+    var res = Traverse.map(obj, function (x) {
+        if (typeof x === 'number' && x % 2 === 0) {
+            this.update(x * 10);
+        }
+    });
+    assert.deepEqual(obj, { a : 1, b : 2, c : [ 3, 4 ] });
+    assert.deepEqual(res, { a : 1, b : 20, c : [ 3, 40 ] });
+};
+
+exports.clone = function () {
+    var obj = { a : 1, b : 2, c : [ 3, 4 ] };
+    var res = Traverse(obj).clone();
+    assert.deepEqual(obj, res);
+    assert.ok(obj !== res);
+    obj.a ++;
+    assert.deepEqual(res.a, 1);
+    obj.c.push(5);
+    assert.deepEqual(res.c, [ 3, 4 ]);
+};
+
+exports.cloneT = function () {
+    var obj = { a : 1, b : 2, c : [ 3, 4 ] };
+    var res = Traverse.clone(obj);
+    assert.deepEqual(obj, res);
+    assert.ok(obj !== res);
+    obj.a ++;
+    assert.deepEqual(res.a, 1);
+    obj.c.push(5);
+    assert.deepEqual(res.c, [ 3, 4 ]);
+};
+
+exports.reduce = function () {
+    var obj = { a : 1, b : 2, c : [ 3, 4 ] };
+    var res = Traverse(obj).reduce(function (acc, x) {
+        if (this.isLeaf) acc.push(x);
+        return acc;
+    }, []);
+    assert.deepEqual(obj, { a : 1, b : 2, c : [ 3, 4 ] });
+    assert.deepEqual(res, [ 1, 2, 3, 4 ]);
+};
+
+exports.reduceInit = function () {
+    var obj = { a : 1, b : 2, c : [ 3, 4 ] };
+    var res = Traverse(obj).reduce(function (acc, x) {
+        if (this.isRoot) assert.fail('got root');
+        return acc;
+    });
+    assert.deepEqual(obj, { a : 1, b : 2, c : [ 3, 4 ] });
+    assert.deepEqual(res, obj);
+};
+
+exports.remove = function () {
+    var obj = { a : 1, b : 2, c : [ 3, 4 ] };
+    Traverse(obj).forEach(function (x) {
+        if (this.isLeaf && x % 2 == 0) this.remove();
+    });
+    
+    assert.deepEqual(obj, { a : 1, c : [ 3 ] });
+};
+
+exports.removeMap = function () {
+    var obj = { a : 1, b : 2, c : [ 3, 4 ] };
+    var res = Traverse(obj).map(function (x) {
+        if (this.isLeaf && x % 2 == 0) this.remove();
+    });
+    
+    assert.deepEqual(obj, { a : 1, b : 2, c : [ 3, 4 ] });
+    assert.deepEqual(res, { a : 1, c : [ 3 ] });
+};
+
+exports.delete = function () {
+    var obj = { a : 1, b : 2, c : [ 3, 4 ] };
+    Traverse(obj).forEach(function (x) {
+        if (this.isLeaf && x % 2 == 0) this.delete();
+    });
+    
+    assert.ok(!Traverse.deepEqual(
+        obj, { a : 1, c : [ 3, undefined ] }
+    ));
+    
+    assert.ok(Traverse.deepEqual(
+        obj, { a : 1, c : [ 3 ] }
+    ));
+    
+    assert.ok(!Traverse.deepEqual(
+        obj, { a : 1, c : [ 3, null ] }
+    ));
+};
+
+exports.deleteRedux = function () {
+    var obj = { a : 1, b : 2, c : [ 3, 4, 5 ] };
+    Traverse(obj).forEach(function (x) {
+        if (this.isLeaf && x % 2 == 0) this.delete();
+    });
+    
+    assert.ok(!Traverse.deepEqual(
+        obj, { a : 1, c : [ 3, undefined, 5 ] }
+    ));
+    
+    assert.ok(Traverse.deepEqual(
+        obj, { a : 1, c : [ 3 ,, 5 ] }
+    ));
+    
+    assert.ok(!Traverse.deepEqual(
+        obj, { a : 1, c : [ 3, null, 5 ] }
+    ));
+    
+    assert.ok(!Traverse.deepEqual(
+        obj, { a : 1, c : [ 3, 5 ] }
+    ));
+};
+
+exports.deleteMap = function () {
+    var obj = { a : 1, b : 2, c : [ 3, 4 ] };
+    var res = Traverse(obj).map(function (x) {
+        if (this.isLeaf && x % 2 == 0) this.delete();
+    });
+    
+    assert.ok(Traverse.deepEqual(
+        obj,
+        { a : 1, b : 2, c : [ 3, 4 ] }
+    ));
+    
+    var xs = [ 3, 4 ];
+    delete xs[1];
+    
+    assert.ok(Traverse.deepEqual(
+        res, { a : 1, c : xs }
+    ));
+    
+    assert.ok(Traverse.deepEqual(
+        res, { a : 1, c : [ 3, ] }
+    ));
+    
+    assert.ok(Traverse.deepEqual(
+        res, { a : 1, c : [ 3 ] }
+    ));
+};
+
+exports.deleteMapRedux = function () {
+    var obj = { a : 1, b : 2, c : [ 3, 4, 5 ] };
+    var res = Traverse(obj).map(function (x) {
+        if (this.isLeaf && x % 2 == 0) this.delete();
+    });
+    
+    assert.ok(Traverse.deepEqual(
+        obj,
+        { a : 1, b : 2, c : [ 3, 4, 5 ] }
+    ));
+    
+    var xs = [ 3, 4, 5 ];
+    delete xs[1];
+    
+    assert.ok(Traverse.deepEqual(
+        res, { a : 1, c : xs }
+    ));
+    
+    assert.ok(!Traverse.deepEqual(
+        res, { a : 1, c : [ 3, 5 ] }
+    ));
+    
+    assert.ok(Traverse.deepEqual(
+        res, { a : 1, c : [ 3 ,, 5 ] }
+    ));
+};
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/negative.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/negative.js
new file mode 100644
index 0000000..6cf287d
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/negative.js
@@ -0,0 +1,20 @@
+var Traverse = require('traverse');
+var assert = require('assert');
+
+exports['negative update test'] = function () {
+    var obj = [ 5, 6, -3, [ 7, 8, -2, 1 ], { f : 10, g : -13 } ];
+    var fixed = Traverse.map(obj, function (x) {
+        if (x < 0) this.update(x + 128);
+    });
+    
+    assert.deepEqual(fixed,
+        [ 5, 6, 125, [ 7, 8, 126, 1 ], { f: 10, g: 115 } ],
+        'Negative values += 128'
+    );
+    
+    assert.deepEqual(obj,
+        [ 5, 6, -3, [ 7, 8, -2, 1 ], { f: 10, g: -13 } ],
+        'Original references not modified'
+    );
+}
+
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/obj.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/obj.js
new file mode 100644
index 0000000..9c3b0db
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/obj.js
@@ -0,0 +1,15 @@
+var assert = require('assert');
+var Traverse = require('traverse');
+
+exports['traverse an object with nested functions'] = function () {
+    var to = setTimeout(function () {
+        assert.fail('never ran');
+    }, 1000);
+    
+    function Cons (x) {
+        clearTimeout(to);
+        assert.equal(x, 10);
+    };
+    Traverse(new Cons(10));
+};
+
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/siblings.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/siblings.js
new file mode 100644
index 0000000..1236834
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/siblings.js
@@ -0,0 +1,35 @@
+var assert = require('assert');
+var traverse = require('traverse');
+
+exports.siblings = function () {
+    var obj = { a : 1, b : 2, c : [ 4, 5, 6 ] };
+    
+    var res = traverse(obj).reduce(function (acc, x) {
+        var p = '/' + this.path.join('/');
+        if (this.parent) {
+            acc[p] = {
+                siblings : this.parent.keys,
+                key : this.key,
+                index : this.parent.keys.indexOf(this.key)
+            };
+        }
+        else {
+            acc[p] = {
+                siblings : [],
+                key : this.key,
+                index : -1
+            }
+        }
+        return acc;
+    }, {});
+    
+    assert.deepEqual(res, {
+        '/' : { siblings : [], key : undefined, index : -1 },
+        '/a' : { siblings : [ 'a', 'b', 'c' ], key : 'a', index : 0 },
+        '/b' : { siblings : [ 'a', 'b', 'c' ], key : 'b', index : 1 },
+        '/c' : { siblings : [ 'a', 'b', 'c' ], key : 'c', index : 2 },
+        '/c/0' : { siblings : [ '0', '1', '2' ], key : '0', index : 0 },
+        '/c/1' : { siblings : [ '0', '1', '2' ], key : '1', index : 1 },
+        '/c/2' : { siblings : [ '0', '1', '2' ], key : '2', index : 2 }
+    });
+};
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/stop.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/stop.js
new file mode 100644
index 0000000..ef6b36e
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/stop.js
@@ -0,0 +1,41 @@
+var assert = require('assert');
+var traverse = require('traverse');
+
+exports.stop = function () {
+    var visits = 0;
+    traverse('abcdefghij'.split('')).forEach(function (node) {
+        if (typeof node === 'string') {
+            visits ++;
+            if (node === 'e') this.stop()
+        }
+    });
+    
+    assert.equal(visits, 5);
+};
+
+exports.stopMap = function () {
+    var s = traverse('abcdefghij'.split('')).map(function (node) {
+        if (typeof node === 'string') {
+            if (node === 'e') this.stop()
+            return node.toUpperCase();
+        }
+    }).join('');
+    
+    assert.equal(s, 'ABCDEfghij');
+};
+
+exports.stopReduce = function () {
+    var obj = {
+        a : [ 4, 5 ],
+        b : [ 6, [ 7, 8, 9 ] ]
+    };
+    var xs = traverse(obj).reduce(function (acc, node) {
+        if (this.isLeaf) {
+            if (node === 7) this.stop();
+            else acc.push(node)
+        }
+        return acc;
+    }, []);
+    
+    assert.deepEqual(xs, [ 4, 5, 6 ]);
+};
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/stringify.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/stringify.js
new file mode 100644
index 0000000..bf36f63
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/stringify.js
@@ -0,0 +1,36 @@
+var assert = require('assert');
+var Traverse = require('traverse');
+
+exports.stringify = function () {
+    var obj = [ 5, 6, -3, [ 7, 8, -2, 1 ], { f : 10, g : -13 } ];
+    
+    var s = '';
+    Traverse(obj).forEach(function (node) {
+        if (Array.isArray(node)) {
+            this.before(function () { s += '[' });
+            this.post(function (child) {
+                if (!child.isLast) s += ',';
+            });
+            this.after(function () { s += ']' });
+        }
+        else if (typeof node == 'object') {
+            this.before(function () { s += '{' });
+            this.pre(function (x, key) {
+                s += '"' + key + '"' + ':';
+            });
+            this.post(function (child) {
+                if (!child.isLast) s += ',';
+            });
+            this.after(function () { s += '}' });
+        }
+        else if (typeof node == 'function') {
+            s += 'null';
+        }
+        else {
+            s += node.toString();
+        }
+    });
+    
+    assert.equal(s, JSON.stringify(obj));
+}
+
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/subexpr.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/subexpr.js
new file mode 100644
index 0000000..a4960fb
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/subexpr.js
@@ -0,0 +1,34 @@
+var traverse = require('traverse');
+var assert = require('assert');
+
+exports.subexpr = function () {
+    var obj = [ 'a', 4, 'b', 5, 'c', 6 ];
+    var r = traverse(obj).map(function (x) {
+        if (typeof x === 'number') {
+            this.update([ x - 0.1, x, x + 0.1 ], true);
+        }
+    });
+    
+    assert.deepEqual(obj, [ 'a', 4, 'b', 5, 'c', 6 ]);
+    assert.deepEqual(r, [
+        'a', [ 3.9, 4, 4.1 ],
+        'b', [ 4.9, 5, 5.1 ],
+        'c', [ 5.9, 6, 6.1 ],
+    ]);
+};
+
+exports.block = function () {
+    var obj = [ [ 1 ], [ 2 ], [ 3 ] ];
+    var r = traverse(obj).map(function (x) {
+        if (Array.isArray(x) && !this.isRoot) {
+            if (x[0] === 5) this.block()
+            else this.update([ [ x[0] + 1 ] ])
+        }
+    });
+    
+    assert.deepEqual(r, [
+        [ [ [ [ [ 5 ] ] ] ] ],
+        [ [ [ [ 5 ] ] ] ],
+        [ [ [ 5 ] ] ],
+    ]);
+};
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/super_deep.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/super_deep.js
new file mode 100644
index 0000000..974181e
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/node_modules/traverse/test/super_deep.js
@@ -0,0 +1,54 @@
+var assert = require('assert');
+var traverse = require('traverse');
+
+exports.super_deep = function () {
+    var util = require('util');
+    var a0 = make();
+    var a1 = make();
+    assert.ok(traverse.deepEqual(a0, a1));
+    
+    a0.c.d.moo = true;
+    assert.ok(!traverse.deepEqual(a0, a1));
+    
+    a1.c.d.moo = true;
+    assert.ok(traverse.deepEqual(a0, a1));
+    
+    // TODO: this one
+    //a0.c.a = a1;
+    //assert.ok(!traverse.deepEqual(a0, a1));
+};
+
+function make () {
+    var a = { self : 'a' };
+    var b = { self : 'b' };
+    var c = { self : 'c' };
+    var d = { self : 'd' };
+    var e = { self : 'e' };
+    
+    a.a = a;
+    a.b = b;
+    a.c = c;
+    
+    b.a = a;
+    b.b = b;
+    b.c = c;
+    
+    c.a = a;
+    c.b = b;
+    c.c = c;
+    c.d = d;
+    
+    d.a = a;
+    d.b = b;
+    d.c = c;
+    d.d = d;
+    d.e = e;
+    
+    e.a = a;
+    e.b = b;
+    e.c = c;
+    e.d = d;
+    e.e = e;
+    
+    return a;
+}
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/package.json b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/package.json
new file mode 100644
index 0000000..074b9db
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/package.json
@@ -0,0 +1,51 @@
+{
+  "name": "js-select",
+  "description": "Traverse and modify objects with JSONSelect selectors",
+  "version": "0.6.0",
+  "author": {
+    "name": "Heather Arthur",
+    "email": "fayearthur@gmail.com"
+  },
+  "repository": {
+    "type": "git",
+    "url": "http://github.com/harthur/js-select.git"
+  },
+  "main": "./index",
+  "dependencies": {
+    "traverse": "0.4.x",
+    "JSONSelect": "0.2.1"
+  },
+  "devDependencies": {
+    "nomnom": "0.6.x",
+    "color": "0.3.x"
+  },
+  "keywords": [
+    "json"
+  ],
+  "readme": "# js-select\n\njs-select uses [js-traverse](https://github.com/substack/js-traverse) to traverse and modify JavaScript object nodes that match [JSONSelect](http://jsonselect.org/) selectors.\n\n```javascript\nvar people = {\n   george: {\n      age : 35,\n      movie: \"Repo Man\"\n   },\n   mary: {\n      age: 15,\n      movie: \"Twilight\"\n   }\n};\n```\n\n### .forEach(fn)\n\nIterates over all matching nodes in the object. The callback gets a special `this` context. See [js-traverse](https://github.com/substack/js-traverse) for all the things you can do to modify and inspect the node with this context. In addition, js-select adds a `this.matches()` which will test if the node matches a selector:\n\n```javascript\nselect(people).forEach(function(node) {\n   if (this.matches(\".mary > .movie\")) {\n      this.remove();\n   }\n});\n```\n\n### .nodes()\n\nReturns all matching nodes from the object.\n\n```javascript\nselect(people, \".age\").nodes(); // [35, 15]\n```\n\n### .remove()\n\nRemoves matching elements from the original object.\n\n```javascript\nselect(people, \".age\").remove();\n```\n\n### .update(fn)\n\nUpdates all matching nodes using the given callback.\n\n```javascript\nselect(people, \".age\").update(function(age) {\n   return age - 5;\n});\n```\n\n### .condense()\n\nReduces the original object down to only the matching elements (the hierarchy is maintained).\n\n```javascript\nselect(people, \".age\").condense();\n```\n\n```javascript\n{\n    george: { age: 35 },\n    mary: { age: 15 }\n}\n```\n\n## Selectors\n\njs-select supports the following [JSONSelect](http://jsonselect.org/) selectors:\n\n```\n*\ntype\n.key\nancestor selector\nparent > selector\nsibling ~ selector\nselector1, selector2\n:root\n:nth-child(n)\n:nth-child(even)\n:nth-child(odd)\n:nth-last-child(n)\n:first-child\n:last-child\n:only-child\n:has(selector)\n:val(\"string\")\n:contains(\"substring\")\n```\n\nSee [details](http://jsonselect.org/#docs/overview) on each selector, and [try them](http://jsonselect.org/#tryit) out on the JSONSelect website.\n\n## Install\n\nFor [node](http://nodejs.org), install with [npm](http://npmjs.org):\n\n```bash\nnpm install js-select\n```\n\nFor the browser, download the select.js file or fetch the latest version from [npm](http://npmjs.org) and build a browser file using [browserify](https://github.com/substack/node-browserify):\n\n```bash\nnpm install browserify -g\nnpm install js-select\n\nbrowserify --require js-select --outfile select.js\n```\nthis will build a browser file with `require('js-select')` available.\n\n## Propers\n\nHuge thanks to [@substack](http://github.com/substack) for the ingenious [js-traverse](https://github.com/substack/js-traverse) and [@lloyd](https://github.com/lloyd) for the ingenious [JSONSelect spec](http://http://jsonselect.org/) and [selector parser](http://search.npmjs.org/#/JSONSelect).",
+  "readmeFilename": "README.md",
+  "_id": "js-select@0.6.0",
+  "dist": {
+    "shasum": "c284e22824d5927aec962dcdf247174aefb0d190",
+    "tarball": "http://registry.npmjs.org/js-select/-/js-select-0.6.0.tgz"
+  },
+  "_from": "js-select@~0.6.0",
+  "_npmVersion": "1.2.14",
+  "_npmUser": {
+    "name": "harth",
+    "email": "fayearthur@gmail.com"
+  },
+  "maintainers": [
+    {
+      "name": "harth",
+      "email": "fayearthur@gmail.com"
+    }
+  ],
+  "directories": {},
+  "_shasum": "c284e22824d5927aec962dcdf247174aefb0d190",
+  "_resolved": "https://registry.npmjs.org/js-select/-/js-select-0.6.0.tgz",
+  "bugs": {
+    "url": "https://github.com/harthur/js-select/issues"
+  },
+  "homepage": "https://github.com/harthur/js-select"
+}
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/select-min.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/select-min.js
new file mode 100644
index 0000000..466ccf7
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/select-min.js
@@ -0,0 +1,37 @@
+var select=function(){var c=function(j,f){var d=c.resolve(j,f||"/"),l=c.modules[d];if(!l)throw Error("Failed to resolve module "+j+", tried "+d);return(d=c.cache[d])?d.exports:l()};c.paths=[];c.modules={};c.cache={};c.extensions=[".js",".coffee"];c._core={assert:!0,events:!0,fs:!0,path:!0,vm:!0};c.resolve=function(j,f){function d(b){b=h.normalize(b);if(c.modules[b])return b;for(var a=0;a<c.extensions.length;a++){var e=c.extensions[a];if(c.modules[b+e])return b+e}}function l(b){var b=b.replace(/\/+$/,
+""),a=h.normalize(b+"/package.json");if(c.modules[a]){var a=c.modules[a](),e=a.browserify;if("object"===typeof e&&e.main){if(a=d(h.resolve(b,e.main)))return a}else if("string"===typeof e){if(a=d(h.resolve(b,e)))return a}else if(a.main&&(a=d(h.resolve(b,a.main))))return a}return d(b+"/index")}f||(f="/");if(c._core[j])return j;var h=c.modules.path(),a=(f=h.resolve("/",f))||"/";if(j.match(/^(?:\.\.?\/|\/)/)){var e=d(h.resolve(a,j))||l(h.resolve(a,j));if(e)return e}a:{for(var e="/"===a?[""]:h.normalize(a).split("/"),
+a=[],g=e.length-1;0<=g;g--)if("node_modules"!==e[g]){var b=e.slice(0,g+1).join("/")+"/node_modules";a.push(b)}for(e=0;e<a.length;e++){g=a[e];if(b=d(g+"/"+j)){a=b;break a}if(g=l(g+"/"+j)){a=g;break a}}a=(b=d(j))?b:void 0}if(a)return a;throw Error("Cannot find module '"+j+"'");};c.alias=function(j,f){var d=c.modules.path(),l=null;try{l=c.resolve(j+"/package.json","/")}catch(h){l=c.resolve(j,"/")}for(var d=d.dirname(l),l=(Object.keys||function(a){var b=[],e;for(e in a)b.push(e);return b})(c.modules),
+a=0;a<l.length;a++){var e=l[a];e.slice(0,d.length+1)===d+"/"?(e=e.slice(d.length),c.modules[f+e]=c.modules[d+e]):e===d&&(c.modules[f]=c.modules[d])}};var u={};c.define=function(j,f){c.modules.__browserify_process&&(u=c.modules.__browserify_process());var d=c._core[j]?"":c.modules.path().dirname(j),l=function(a){var e=c(a,d);if((a=c.cache[c.resolve(a,d)])&&null===a.parent)a.parent=h;return e};l.resolve=function(a){return c.resolve(a,d)};l.modules=c.modules;l.define=c.define;l.cache=c.cache;var h={id:j,
+filename:j,exports:{},loaded:!1,parent:null};c.modules[j]=function(){c.cache[j]=h;f.call(h.exports,l,h,h.exports,d,j,u);h.loaded=!0;return h.exports}};c.define("path",function(c,f,d,l,h,a){function e(a,b){for(var e=[],g=0;g<a.length;g++)b(a[g],g,a)&&e.push(a[g]);return e}function g(a,b){for(var e=0,g=a.length;0<=g;g--){var d=a[g];"."==d?a.splice(g,1):".."===d?(a.splice(g,1),e++):e&&(a.splice(g,1),e--)}if(b)for(;e--;e)a.unshift("..");return a}var b=/^(.+\/(?!$)|\/)?((?:.+?)?(\.[^.]*)?)$/;d.resolve=
+function(){for(var b="",o=!1,d=arguments.length;-1<=d&&!o;d--){var c=0<=d?arguments[d]:a.cwd();"string"===typeof c&&c&&(b=c+"/"+b,o="/"===c.charAt(0))}b=g(e(b.split("/"),function(a){return!!a}),!o).join("/");return(o?"/":"")+b||"."};d.normalize=function(a){var b="/"===a.charAt(0),d="/"===a.slice(-1),a=g(e(a.split("/"),function(a){return!!a}),!b).join("/");!a&&!b&&(a=".");a&&d&&(a+="/");return(b?"/":"")+a};d.join=function(){var a=Array.prototype.slice.call(arguments,0);return d.normalize(e(a,function(a){return a&&
+"string"===typeof a}).join("/"))};d.dirname=function(a){return(a=b.exec(a)[1]||"")?1===a.length?a:a.substring(0,a.length-1):"."};d.basename=function(a,e){var g=b.exec(a)[2]||"";e&&g.substr(-1*e.length)===e&&(g=g.substr(0,g.length-e.length));return g};d.extname=function(a){return b.exec(a)[3]||""}});c.define("__browserify_process",function(c,f,d,l,h,a){var f=a=f.exports={},e=[],g="undefined"!==typeof window&&window.postMessage&&window.addEventListener;g&&window.addEventListener("message",function(a){a.source===
+window&&"browserify-tick"===a.data&&(a.stopPropagation(),0<e.length&&e.shift()())},!0);f.nextTick=function(a){g?(e.push(a),window.postMessage("browserify-tick","*")):setTimeout(a,0)};a.title="browser";a.browser=!0;a.env={};a.argv=[];a.binding=function(a){if("evals"===a)return c("vm");throw Error("No such module. (Possibly not yet loaded)");};var b="/",t;a.cwd=function(){return b};a.chdir=function(a){t||(t=c("path"));b=t.resolve(a,b)}});c.define("vm",function(c,f){f.exports=c("vm-browserify")});c.define("/node_modules/vm-browserify/package.json",
+function(c,f){f.exports={main:"index.js"}});c.define("/node_modules/vm-browserify/index.js",function(c,f,d){var l=function(a){if(Object.keys)return Object.keys(a);var g=[],b;for(b in a)g.push(b);return g},h=function(a,g){if(a.forEach)return a.forEach(g);for(var b=0;b<a.length;b++)g(a[b],b,a)},a=d.Script=function(e){if(!(this instanceof a))return new a(e);this.code=e};a.prototype.runInNewContext=function(a){a||(a={});var g=document.createElement("iframe");g.style||(g.style={});g.style.display="none";
+document.body.appendChild(g);var b=g.contentWindow;h(l(a),function(g){b[g]=a[g]});!b.eval&&b.execScript&&b.execScript("null");var d=b.eval(this.code);h(l(b),function(g){a[g]=b[g]});document.body.removeChild(g);return d};a.prototype.runInThisContext=function(){return eval(this.code)};a.prototype.runInContext=function(a){return this.runInNewContext(a)};h(l(a.prototype),function(e){d[e]=a[e]=function(g){var b=a(g);return b[e].apply(b,[].slice.call(arguments,1))}});d.createScript=function(a){return d.Script(a)};
+d.createContext=a.createContext=function(a){var g={};"object"===typeof a&&h(l(a),function(b){g[b]=a[b]});return g}});c.define("/package.json",function(c,f){f.exports={main:"./index"}});c.define("js-select",function(c,f){function d(a){a=e._parse(a||"*")[1];return","==a[0]?a.slice(1):[a]}function l(a,e){for(var g=0;g<a.length;g++){var d;a:{d=a[g];for(var c=e,l=c.parents.concat([c]),j=l.length-1,f=d.length-1,r=!0;0<=f&&0<=j;){var q=d[f],c=l[j];if(">"==q)f--,r=!0;else{if(h(q,c))f--;else if(r){d=!1;break a}j--;
+r=!1}}d=-1==f}if(d)return!0}return!1}function h(b,e){var d=e.key,c=e.node,h=e.parent;if(b.id&&d!=b.id)return!1;if(b.type){var f=b.type;if("null"==f&&null!==c||"array"==f&&!g(c)||"object"==f&&("object"!=typeof c||null===c||g(c))||("boolean"==f||"string"==f||"number"==f)&&f!=typeof c)return!1}if(":nth-child"==b.pf&&(f=parseInt(d)+1,0==b.a&&f!==b.b||1==b.a&&!(f>=-b.b)||-1==b.a&&!(f<=b.b)||2==b.a&&f%2!=b.b)||":nth-last-child"==b.pf&&(!h||d!=h.node.length-b.b)||":only-child"==b.pc&&(!h||1!=h.node.length)||
+":root"==b.pc&&void 0!==d)return!1;if(b.has){var j=","==b.has[0][0]?b.has[0].slice(1):[b.has[0]],p=!1;a(c).forEach(function(){l(j,this)&&(p=!0)});if(!p)return!1}return b.expr&&(f=b.expr,d=f[0],h=f[1],f=f[2],"string"!=typeof c||!d&&"="==h&&c!=f||!d&&"*="==h&&-1==c.indexOf(f))?!1:!0}var a=c("traverse"),e=c("JSONSelect");f.exports=function(b,e){var g=d(e);return{nodes:function(){var a=[];this.forEach(function(b){a.push(b)});return a},update:function(a){this.forEach(function(b){this.update("function"==
+typeof a?a(b):a)})},remove:function(){this.forEach(function(){this.remove()})},condense:function(){a(b).forEach(function(){if(this.parent)if(this.parent.keep)this.keep=!0;else{var a=l(g,this);(this.keep=a)?this.parent.keep_child=!0:this.isLeaf?this.remove():this.after(function(){this.keep_child&&(this.parent.keep_child=!0);!this.keep&&!this.keep_child&&this.remove()})}})},forEach:function(e){a(b).forEach(function(a){l(g,this)&&(this.matches=function(a){return l(d(a),this)},e.call(this,a))})}}};var g=
+Array.isArray||function(a){return"[object Array]"===toString.call(a)}});c.define("/node_modules/traverse/package.json",function(c,f){f.exports={main:"./index"}});c.define("/node_modules/traverse/index.js",function(c,f){function d(a){if(!(this instanceof d))return new d(a);this.value=a}function l(a,e,g){var b=[],d=[],c=!0;return function x(a){var f=g?h(a):a,l,j,q,v,n=!0,i={node:f,node_:a,path:[].concat(b),parent:d[d.length-1],parents:d,key:b.slice(-1)[0],isRoot:0===b.length,level:b.length,circular:null,
+update:function(a,b){i.isRoot||(i.parent.node[i.key]=a);i.node=a;b&&(n=!1)},"delete":function(){delete i.parent.node[i.key]},remove:function(){Array.isArray(i.parent.node)?i.parent.node.splice(i.key,1):delete i.parent.node[i.key]},keys:null,before:function(a){l=a},after:function(a){j=a},pre:function(a){q=a},post:function(a){v=a},stop:function(){c=!1},block:function(){n=!1}};if(!c)return i;if("object"===typeof f&&null!==f){i.keys=Object.keys(f);i.isLeaf=0==i.keys.length;for(f=0;f<d.length;f++)if(d[f].node_===
+a){i.circular=d[f];break}}else i.isLeaf=!0;i.notLeaf=!i.isLeaf;i.notRoot=!i.isRoot;a=e.call(i,i.node);void 0!==a&&i.update&&i.update(a);l&&l.call(i,i.node);if(!n)return i;"object"==typeof i.node&&(null!==i.node&&!i.circular)&&(d.push(i),i.keys.forEach(function(a,e){b.push(a);q&&q.call(i,i.node[a],a);var d=x(i.node[a]);g&&Object.hasOwnProperty.call(i.node,a)&&(i.node[a]=d.node);d.isLast=e==i.keys.length-1;d.isFirst=0==e;v&&v.call(i,d);b.pop()}),d.pop());j&&j.call(i,i.node);return i}(a).node}function h(a){if("object"===
+typeof a&&null!==a){var e;e=Array.isArray(a)?[]:a instanceof Date?new Date(a):a instanceof Boolean?new Boolean(a):a instanceof Number?new Number(a):a instanceof String?new String(a):Object.create(Object.getPrototypeOf(a));Object.keys(a).forEach(function(d){e[d]=a[d]});return e}return a}f.exports=d;d.prototype.get=function(a){for(var e=this.value,d=0;d<a.length;d++){var b=a[d];if(!Object.hasOwnProperty.call(e,b)){e=void 0;break}e=e[b]}return e};d.prototype.set=function(a,e){for(var d=this.value,b=
+0;b<a.length-1;b++){var c=a[b];Object.hasOwnProperty.call(d,c)||(d[c]={});d=d[c]}return d[a[b]]=e};d.prototype.map=function(a){return l(this.value,a,!0)};d.prototype.forEach=function(a){return this.value=l(this.value,a,!1)};d.prototype.reduce=function(a,d){var c=1===arguments.length,b=c?this.value:d;this.forEach(function(d){if(!this.isRoot||!c)b=a.call(this,b,d)});return b};d.prototype.deepEqual=function(a){if(1!==arguments.length)throw Error("deepEqual requires exactly one object to compare against");
+var e=!0,c=a;this.forEach(function(b){var f=function(){e=!1}.bind(this);if(!this.isRoot){if("object"!==typeof c)return f();c=c[this.key]}var h=c;this.post(function(){c=h});if(this.circular)d(a).get(this.circular.path)!==h&&f();else if(typeof h!==typeof b)f();else if(null===h||null===b||void 0===h||void 0===b)h!==b&&f();else if(h.__proto__!==b.__proto__)f();else if(h!==b)if("function"===typeof h)h instanceof RegExp?h.toString()!=b.toString()&&f():h!==b&&f();else if("object"===typeof h)if("[object Arguments]"===
+Object.prototype.toString.call(b)||"[object Arguments]"===Object.prototype.toString.call(h))Object.prototype.toString.call(h)!==Object.prototype.toString.call(b)&&f();else if(h instanceof Date||b instanceof Date)(!(h instanceof Date)||!(b instanceof Date)||h.getTime()!==b.getTime())&&f();else{var l=Object.keys(h),j=Object.keys(b);if(l.length!==j.length)return f();for(j=0;j<l.length;j++)Object.hasOwnProperty.call(b,l[j])||f()}});return e};d.prototype.paths=function(){var a=[];this.forEach(function(){a.push(this.path)});
+return a};d.prototype.nodes=function(){var a=[];this.forEach(function(){a.push(this.node)});return a};d.prototype.clone=function(){var a=[],d=[];return function b(c){for(var f=0;f<a.length;f++)if(a[f]===c)return d[f];if("object"===typeof c&&null!==c){var j=h(c);a.push(c);d.push(j);Object.keys(c).forEach(function(a){j[a]=b(c[a])});a.pop();d.pop();return j}return c}(this.value)};Object.keys(d.prototype).forEach(function(a){d[a]=function(c){var f=[].slice.call(arguments,1),b=d(c);return b[a].apply(b,
+f)}})});c.define("/node_modules/JSONSelect/package.json",function(c,f){f.exports={main:"src/jsonselect"}});c.define("/node_modules/JSONSelect/src/jsonselect.js",function(c,f,d){var c="undefined"===typeof d?window.JSONSelect={}:d,l=function(a){try{return JSON&&JSON.parse?JSON.parse(a):(new Function("return "+a))()}catch(b){h("ijs",b.message)}},h=function(a,b){throw Error(v[a]+(b&&" in '"+b+"'"));},a=function(a,b){b||(b=0);var d=i.exec(a.substr(b));if(d){var b=b+d[0].length,c;d[1]?c=[b," "]:d[2]?c=
+[b,d[0]]:d[3]?c=[b,n.typ,d[0]]:d[4]?c=[b,n.psc,d[0]]:d[5]?c=[b,n.psf,d[0]]:d[6]?h("upc",a):d[8]?c=[b,d[7]?n.ide:n.str,l(d[8])]:d[9]?h("ujs",a):d[10]&&(c=[b,n.ide,d[10].replace(/\\([^\r\n\f0-9a-fA-F])/g,"$1")]);return c}},e=function(a,b){return typeof a===b},g=function(a,b){var d,c=B.exec(a.substr(b));if(c)return b+=c[0].length,d=c[1]||c[2]||c[3]||c[5]||c[6],c[1]||c[2]||c[3]?[b,0,l(d)]:c[4]?[b,0,void 0]:[b,d]},b=function(a,d){d||(d=0);var c=g(a,d),e;c&&"("===c[1]?(e=b(a,c[0]),c=g(a,e[0]),(!c||")"!==
+c[1])&&h("epex",a),d=c[0],e=["(",e[1]]):!c||c[1]&&"x"!=c[1]?h("ee",a+" - "+(c[1]&&c[1])):(e="x"===c[1]?void 0:c[2],d=c[0]);c=g(a,d);if(!c||")"==c[1])return[d,e];("x"==c[1]||!c[1])&&h("bop",a+" - "+(c[1]&&c[1]));var f=b(a,c[0]),d=f[0],f=f[1],l;if("object"!==typeof f||"("===f[0]||w[c[1]][0]<w[f[1]][0])l=[e,c[1],f];else{for(l=f;"object"===typeof f[0]&&"("!=f[0][0]&&w[c[1]][0]>=w[f[0][1]][0];)f=f[0];f[0]=[e,c[1],f[0]]}return[d,l]},t=function(a,c){function d(a){return"object"!==typeof a||null===a?a:"("===
+a[0]?d(a[1]):[d(a[0]),a[1],d(a[2])]}var e=b(a,c?c:0);return[e[0],d(e[1])]},o=function(a,c){if(void 0===a)return c;if(null===a||"object"!==typeof a)return a;var b=o(a[0],c),d=o(a[2],c);return w[a[1]][1](b,d)},y=function(c,b,d,e){d||(e={});var f=[],g,l;for(b||(b=0);;){var m;m=c;var i=b,b=i,j={},k=a(m,i);k&&" "===k[1]&&(b=i=k[0],k=a(m,i));k&&k[1]===n.typ?(j.type=k[2],k=a(m,i=k[0])):k&&"*"===k[1]&&(k=a(m,i=k[0]));for(;void 0!==k;){if(k[1]===n.ide)j.id&&h("nmi",k[1]),j.id=k[2];else if(k[1]===n.psc)(j.pc||
+j.pf)&&h("mpc",k[1]),":first-child"===k[2]?(j.pf=":nth-child",j.a=0,j.b=1):":last-child"===k[2]?(j.pf=":nth-last-child",j.a=0,j.b=1):j.pc=k[2];else if(k[1]===n.psf)":val"===k[2]||":contains"===k[2]?(j.expr=[void 0,":val"===k[2]?"=":"*=",void 0],(k=a(m,k[0]))&&" "===k[1]&&(k=a(m,k[0])),(!k||"("!==k[1])&&h("pex",m),(k=a(m,k[0]))&&" "===k[1]&&(k=a(m,k[0])),(!k||k[1]!==n.str)&&h("sex",m),j.expr[2]=k[2],(k=a(m,k[0]))&&" "===k[1]&&(k=a(m,k[0])),(!k||")"!==k[1])&&h("epex",m)):":has"===k[2]?((k=a(m,k[0]))&&
+" "===k[1]&&(k=a(m,k[0])),(!k||"("!==k[1])&&h("pex",m),i=y(m,k[0],!0),k[0]=i[0],j.has||(j.has=[]),j.has.push(i[1])):":expr"===k[2]?(j.expr&&h("mexp",m),i=t(m,k[0]),k[0]=i[0],j.expr=i[1]):((j.pc||j.pf)&&h("mpc",m),j.pf=k[2],(i=A.exec(m.substr(k[0])))||h("mepf",m),i[5]?(j.a=2,j.b="odd"===i[5]?1:0):i[6]?(j.a=0,j.b=parseInt(i[6],10)):(j.a=parseInt((i[1]?i[1]:"+")+(i[2]?i[2]:"1"),10),j.b=i[3]?parseInt(i[3]+i[4],10):0),k[0]+=i[0].length);else break;k=a(m,i=k[0])}b===i&&h("se",m);m=[i,j];f.push(m[1]);(m=
+a(c,b=m[0]))&&" "===m[1]&&(m=a(c,b=m[0]));if(!m)break;if(">"===m[1]||"~"===m[1])"~"===m[1]&&(e.usesSiblingOp=!0),f.push(m[1]),b=m[0];else if(","===m[1])void 0===g?g=[",",f]:g.push(f),f=[],b=m[0];else if(")"===m[1]){d||h("ucp",m[1]);l=1;b=m[0];break}}d&&!l&&h("mcp",c);g&&g.push(f);var s;if(!d&&e.usesSiblingOp)if(c=g?g:f,","===c[0]){for(d=[","];s<c.length;s++)var o=x(o[s]),d=d.concat(","===o[0]?o.slice(1):o);s=d}else s=x(c);else s=g?g:f;return[b,s]},x=function(a){for(var c=[],b,d=0;d<a.length;d++)if("~"===
+a[d]){if(2>d||">"!=a[d-2])b=a.slice(0,d-1),b=b.concat([{has:[[{pc:":root"},">",a[d-1]]]},">"]),b=b.concat(a.slice(d+1)),c.push(b);if(1<d){var e=">"===a[d-2]?d-3:d-2;b=a.slice(0,e);var f={},g;for(g in a[e])a[e].hasOwnProperty(g)&&(f[g]=a[e][g]);f.has||(f.has=[]);f.has.push([{pc:":root"},">",a[d-1]]);b=b.concat(f,">",a.slice(d+1));c.push(b)}break}return d==a.length?a:1<c.length?[","].concat(c):c[0]},u=function(a){return Array.isArray?Array.isArray(a):"[object Array]"===q.call(a)},z=function(a,b,c,d,
+e){var f=[],g=">"===b[0]?b[1]:b[0],h=!0;if(g.type&&h){var h=g.type,i;null===a?i="null":(i=typeof a,"object"===i&&u(a)&&(i="array"));h=h===i}g.id&&(h=h&&g.id===c);h&&g.pf&&(":nth-last-child"===g.pf?d=e-d:d++,0===g.a?h=g.b===d:(c=(d-g.b)%g.a,h=!c&&0<=d*g.a+g.b));if(h&&g.has){d=function(){throw 42;};for(c=0;c<g.has.length;c++){try{p(g.has[c],a,d)}catch(j){if(42===j)continue}h=!1;break}}h&&g.expr&&(h=o(g.expr,a));">"!==b[0]&&":root"!==b[0].pc&&f.push(b);h&&(">"===b[0]?2<b.length&&(h=!1,f.push(b.slice(2))):
+1<b.length&&(h=!1,f.push(b.slice(1))));return[h,f]},p=function(a,b,c,d,e,f){for(var a=","===a[0]?a.slice(1):[a],g=[],h=!1,i=0,j=0,k,l,i=0;i<a.length;i++){l=z(b,a[i],d,e,f);l[0]&&(h=!0);for(j=0;j<l[1].length;j++)g.push(l[1][j])}if(g.length&&"object"===typeof b)if(1<=g.length&&g.unshift(","),u(b))for(i=0;i<b.length;i++)p(g,b[i],c,void 0,i,b.length);else for(k in b)b.hasOwnProperty(k)&&p(g,b[k],c,k);h&&c&&c(b)},r=function(a){return{sel:y(a)[1],match:function(a){var b=[];p(this.sel,a,function(a){b.push(a)});
+return b},forEach:function(a,b){return p(this.sel,a,b)}}},q=Object.prototype.toString,v={bop:"binary operator expected",ee:"expression expected",epex:"closing paren expected ')'",ijs:"invalid json string",mcp:"missing closing paren",mepf:"malformed expression in pseudo-function",mexp:"multiple expressions not allowed",mpc:"multiple pseudo classes (:xxx) not allowed",nmi:"multiple ids not allowed",pex:"opening paren expected '('",se:"selector expected",sex:"string expected",sra:"string required after '.'",
+uc:"unrecognized char",ucp:"unexpected closing paren",ujs:"unclosed json string",upc:"unrecognized pseudo class"},n={psc:1,psf:2,typ:3,str:4,ide:5},i=RegExp('^(?:([\\r\\n\\t\\ ]+)|([~*,>\\)\\(])|(string|boolean|null|array|object|number)|(:(?:root|first-child|last-child|only-child))|(:(?:nth-child|nth-last-child|has|expr|val|contains))|(:\\w+)|(?:(\\.)?(\\"(?:[^\\\\\\"]|\\\\[^\\"])*\\"))|(\\")|\\.((?:[_a-zA-Z]|[^\\0-\\0177]|\\\\[^\\r\\n\\f0-9a-fA-F])(?:[_a-zA-Z0-9\\-]|[^\\u0000-\\u0177]|(?:\\\\[^\\r\\n\\f0-9a-fA-F]))*))'),
+A=/^\s*\(\s*(?:([+\-]?)([0-9]*)n\s*(?:([+\-])\s*([0-9]))?|(odd|even)|([+\-]?[0-9]+))\s*\)/,B=RegExp('^\\s*(?:(true|false|null)|(-?\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?)|("(?:[^\\]|\\[^"])*")|(x)|(&&|\\|\\||[\\$\\^<>!\\*]=|[=+\\-*/%<>])|([\\(\\)]))'),w={"*":[9,function(a,b){return a*b}],"/":[9,function(a,b){return a/b}],"%":[9,function(a,b){return a%b}],"+":[7,function(a,b){return a+b}],"-":[7,function(a,b){return a-b}],"<=":[5,function(a,b){return e(a,"number")&&e(b,"number")&&a<=b}],">=":[5,function(a,
+b){return e(a,"number")&&e(b,"number")&&a>=b}],"$=":[5,function(a,b){return e(a,"string")&&e(b,"string")&&a.lastIndexOf(b)===a.length-b.length}],"^=":[5,function(a,b){return e(a,"string")&&e(b,"string")&&0===a.indexOf(b)}],"*=":[5,function(a,b){return e(a,"string")&&e(b,"string")&&-1!==a.indexOf(b)}],">":[5,function(a,b){return e(a,"number")&&e(b,"number")&&a>b}],"<":[5,function(a,b){return e(a,"number")&&e(b,"number")&&a<b}],"=":[3,function(a,b){return a===b}],"!=":[3,function(a,b){return a!==b}],
+"&&":[2,function(a,b){return a&&b}],"||":[1,function(a,b){return a||b}]};c._lex=a;c._parse=y;c.match=function(a,b){return r(a).match(b)};c.forEach=function(a,b,c){return r(a).forEach(b,c)};c.compile=r});return c("js-select")}();
\ No newline at end of file
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/select.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/select.js
new file mode 100644
index 0000000..2a5cb31
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/select.js
@@ -0,0 +1,1533 @@
+var select = (function() {
+	var require = function (file, cwd) {
+	    var resolved = require.resolve(file, cwd || '/');
+	    var mod = require.modules[resolved];
+	    if (!mod) throw new Error(
+	        'Failed to resolve module ' + file + ', tried ' + resolved
+	    );
+	    var cached = require.cache[resolved];
+	    var res = cached? cached.exports : mod();
+	    return res;
+	};
+
+	require.paths = [];
+	require.modules = {};
+	require.cache = {};
+	require.extensions = [".js",".coffee"];
+
+	require._core = {
+	    'assert': true,
+	    'events': true,
+	    'fs': true,
+	    'path': true,
+	    'vm': true
+	};
+
+	require.resolve = (function () {
+	    return function (x, cwd) {
+	        if (!cwd) cwd = '/';
+        
+	        if (require._core[x]) return x;
+	        var path = require.modules.path();
+	        cwd = path.resolve('/', cwd);
+	        var y = cwd || '/';
+        
+	        if (x.match(/^(?:\.\.?\/|\/)/)) {
+	            var m = loadAsFileSync(path.resolve(y, x))
+	                || loadAsDirectorySync(path.resolve(y, x));
+	            if (m) return m;
+	        }
+        
+	        var n = loadNodeModulesSync(x, y);
+	        if (n) return n;
+        
+	        throw new Error("Cannot find module '" + x + "'");
+        
+	        function loadAsFileSync (x) {
+	            x = path.normalize(x);
+	            if (require.modules[x]) {
+	                return x;
+	            }
+            
+	            for (var i = 0; i < require.extensions.length; i++) {
+	                var ext = require.extensions[i];
+	                if (require.modules[x + ext]) return x + ext;
+	            }
+	        }
+        
+	        function loadAsDirectorySync (x) {
+	            x = x.replace(/\/+$/, '');
+	            var pkgfile = path.normalize(x + '/package.json');
+	            if (require.modules[pkgfile]) {
+	                var pkg = require.modules[pkgfile]();
+	                var b = pkg.browserify;
+	                if (typeof b === 'object' && b.main) {
+	                    var m = loadAsFileSync(path.resolve(x, b.main));
+	                    if (m) return m;
+	                }
+	                else if (typeof b === 'string') {
+	                    var m = loadAsFileSync(path.resolve(x, b));
+	                    if (m) return m;
+	                }
+	                else if (pkg.main) {
+	                    var m = loadAsFileSync(path.resolve(x, pkg.main));
+	                    if (m) return m;
+	                }
+	            }
+            
+	            return loadAsFileSync(x + '/index');
+	        }
+        
+	        function loadNodeModulesSync (x, start) {
+	            var dirs = nodeModulesPathsSync(start);
+	            for (var i = 0; i < dirs.length; i++) {
+	                var dir = dirs[i];
+	                var m = loadAsFileSync(dir + '/' + x);
+	                if (m) return m;
+	                var n = loadAsDirectorySync(dir + '/' + x);
+	                if (n) return n;
+	            }
+            
+	            var m = loadAsFileSync(x);
+	            if (m) return m;
+	        }
+        
+	        function nodeModulesPathsSync (start) {
+	            var parts;
+	            if (start === '/') parts = [ '' ];
+	            else parts = path.normalize(start).split('/');
+            
+	            var dirs = [];
+	            for (var i = parts.length - 1; i >= 0; i--) {
+	                if (parts[i] === 'node_modules') continue;
+	                var dir = parts.slice(0, i + 1).join('/') + '/node_modules';
+	                dirs.push(dir);
+	            }
+            
+	            return dirs;
+	        }
+	    };
+	})();
+
+	require.alias = function (from, to) {
+	    var path = require.modules.path();
+	    var res = null;
+	    try {
+	        res = require.resolve(from + '/package.json', '/');
+	    }
+	    catch (err) {
+	        res = require.resolve(from, '/');
+	    }
+	    var basedir = path.dirname(res);
+    
+	    var keys = (Object.keys || function (obj) {
+	        var res = [];
+	        for (var key in obj) res.push(key);
+	        return res;
+	    })(require.modules);
+    
+	    for (var i = 0; i < keys.length; i++) {
+	        var key = keys[i];
+	        if (key.slice(0, basedir.length + 1) === basedir + '/') {
+	            var f = key.slice(basedir.length);
+	            require.modules[to + f] = require.modules[basedir + f];
+	        }
+	        else if (key === basedir) {
+	            require.modules[to] = require.modules[basedir];
+	        }
+	    }
+	};
+
+	(function () {
+	    var process = {};
+    
+	    require.define = function (filename, fn) {
+	        if (require.modules.__browserify_process) {
+	            process = require.modules.__browserify_process();
+	        }
+        
+	        var dirname = require._core[filename]
+	            ? ''
+	            : require.modules.path().dirname(filename)
+	        ;
+        
+	        var require_ = function (file) {
+	            var requiredModule = require(file, dirname);
+	            var cached = require.cache[require.resolve(file, dirname)];
+
+	            if (cached && cached.parent === null) {
+	                cached.parent = module_;
+	            }
+
+	            return requiredModule;
+	        };
+	        require_.resolve = function (name) {
+	            return require.resolve(name, dirname);
+	        };
+	        require_.modules = require.modules;
+	        require_.define = require.define;
+	        require_.cache = require.cache;
+	        var module_ = {
+	            id : filename,
+	            filename: filename,
+	            exports : {},
+	            loaded : false,
+	            parent: null
+	        };
+        
+	        require.modules[filename] = function () {
+	            require.cache[filename] = module_;
+	            fn.call(
+	                module_.exports,
+	                require_,
+	                module_,
+	                module_.exports,
+	                dirname,
+	                filename,
+	                process
+	            );
+	            module_.loaded = true;
+	            return module_.exports;
+	        };
+	    };
+	})();
+
+
+	require.define("path",function(require,module,exports,__dirname,__filename,process){function filter (xs, fn) {
+	    var res = [];
+	    for (var i = 0; i < xs.length; i++) {
+	        if (fn(xs[i], i, xs)) res.push(xs[i]);
+	    }
+	    return res;
+	}
+
+	// resolves . and .. elements in a path array with directory names there
+	// must be no slashes, empty elements, or device names (c:\) in the array
+	// (so also no leading and trailing slashes - it does not distinguish
+	// relative and absolute paths)
+	function normalizeArray(parts, allowAboveRoot) {
+	  // if the path tries to go above the root, `up` ends up > 0
+	  var up = 0;
+	  for (var i = parts.length; i >= 0; i--) {
+	    var last = parts[i];
+	    if (last == '.') {
+	      parts.splice(i, 1);
+	    } else if (last === '..') {
+	      parts.splice(i, 1);
+	      up++;
+	    } else if (up) {
+	      parts.splice(i, 1);
+	      up--;
+	    }
+	  }
+
+	  // if the path is allowed to go above the root, restore leading ..s
+	  if (allowAboveRoot) {
+	    for (; up--; up) {
+	      parts.unshift('..');
+	    }
+	  }
+
+	  return parts;
+	}
+
+	// Regex to split a filename into [*, dir, basename, ext]
+	// posix version
+	var splitPathRe = /^(.+\/(?!$)|\/)?((?:.+?)?(\.[^.]*)?)$/;
+
+	// path.resolve([from ...], to)
+	// posix version
+	exports.resolve = function() {
+	var resolvedPath = '',
+	    resolvedAbsolute = false;
+
+	for (var i = arguments.length; i >= -1 && !resolvedAbsolute; i--) {
+	  var path = (i >= 0)
+	      ? arguments[i]
+	      : process.cwd();
+
+	  // Skip empty and invalid entries
+	  if (typeof path !== 'string' || !path) {
+	    continue;
+	  }
+
+	  resolvedPath = path + '/' + resolvedPath;
+	  resolvedAbsolute = path.charAt(0) === '/';
+	}
+
+	// At this point the path should be resolved to a full absolute path, but
+	// handle relative paths to be safe (might happen when process.cwd() fails)
+
+	// Normalize the path
+	resolvedPath = normalizeArray(filter(resolvedPath.split('/'), function(p) {
+	    return !!p;
+	  }), !resolvedAbsolute).join('/');
+
+	  return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.';
+	};
+
+	// path.normalize(path)
+	// posix version
+	exports.normalize = function(path) {
+	var isAbsolute = path.charAt(0) === '/',
+	    trailingSlash = path.slice(-1) === '/';
+
+	// Normalize the path
+	path = normalizeArray(filter(path.split('/'), function(p) {
+	    return !!p;
+	  }), !isAbsolute).join('/');
+
+	  if (!path && !isAbsolute) {
+	    path = '.';
+	  }
+	  if (path && trailingSlash) {
+	    path += '/';
+	  }
+  
+	  return (isAbsolute ? '/' : '') + path;
+	};
+
+
+	// posix version
+	exports.join = function() {
+	  var paths = Array.prototype.slice.call(arguments, 0);
+	  return exports.normalize(filter(paths, function(p, index) {
+	    return p && typeof p === 'string';
+	  }).join('/'));
+	};
+
+
+	exports.dirname = function(path) {
+	  var dir = splitPathRe.exec(path)[1] || '';
+	  var isWindows = false;
+	  if (!dir) {
+	    // No dirname
+	    return '.';
+	  } else if (dir.length === 1 ||
+	      (isWindows && dir.length <= 3 && dir.charAt(1) === ':')) {
+	    // It is just a slash or a drive letter with a slash
+	    return dir;
+	  } else {
+	    // It is a full dirname, strip trailing slash
+	    return dir.substring(0, dir.length - 1);
+	  }
+	};
+
+
+	exports.basename = function(path, ext) {
+	  var f = splitPathRe.exec(path)[2] || '';
+	  // TODO: make this comparison case-insensitive on windows?
+	  if (ext && f.substr(-1 * ext.length) === ext) {
+	    f = f.substr(0, f.length - ext.length);
+	  }
+	  return f;
+	};
+
+
+	exports.extname = function(path) {
+	  return splitPathRe.exec(path)[3] || '';
+	};
+	});
+
+	require.define("__browserify_process",function(require,module,exports,__dirname,__filename,process){var process = module.exports = {};
+
+	process.nextTick = (function () {
+	    var queue = [];
+	    var canPost = typeof window !== 'undefined'
+	        && window.postMessage && window.addEventListener
+	    ;
+    
+	    if (canPost) {
+	        window.addEventListener('message', function (ev) {
+	            if (ev.source === window && ev.data === 'browserify-tick') {
+	                ev.stopPropagation();
+	                if (queue.length > 0) {
+	                    var fn = queue.shift();
+	                    fn();
+	                }
+	            }
+	        }, true);
+	    }
+    
+	    return function (fn) {
+	        if (canPost) {
+	            queue.push(fn);
+	            window.postMessage('browserify-tick', '*');
+	        }
+	        else setTimeout(fn, 0);
+	    };
+	})();
+
+	process.title = 'browser';
+	process.browser = true;
+	process.env = {};
+	process.argv = [];
+
+	process.binding = function (name) {
+	    if (name === 'evals') return (require)('vm')
+	    else throw new Error('No such module. (Possibly not yet loaded)')
+	};
+
+	(function () {
+	    var cwd = '/';
+	    var path;
+	    process.cwd = function () { return cwd };
+	    process.chdir = function (dir) {
+	        if (!path) path = require('path');
+	        cwd = path.resolve(dir, cwd);
+	    };
+	})();
+	});
+
+	require.define("vm",function(require,module,exports,__dirname,__filename,process){module.exports = require("vm-browserify")});
+
+	require.define("/node_modules/vm-browserify/package.json",function(require,module,exports,__dirname,__filename,process){module.exports = {"main":"index.js"}});
+
+	require.define("/node_modules/vm-browserify/index.js",function(require,module,exports,__dirname,__filename,process){var Object_keys = function (obj) {
+	    if (Object.keys) return Object.keys(obj)
+	    else {
+	        var res = [];
+	        for (var key in obj) res.push(key)
+	        return res;
+	    }
+	};
+
+	var forEach = function (xs, fn) {
+	    if (xs.forEach) return xs.forEach(fn)
+	    else for (var i = 0; i < xs.length; i++) {
+	        fn(xs[i], i, xs);
+	    }
+	};
+
+	var Script = exports.Script = function NodeScript (code) {
+	    if (!(this instanceof Script)) return new Script(code);
+	    this.code = code;
+	};
+
+	Script.prototype.runInNewContext = function (context) {
+	    if (!context) context = {};
+    
+	    var iframe = document.createElement('iframe');
+	    if (!iframe.style) iframe.style = {};
+	    iframe.style.display = 'none';
+    
+	    document.body.appendChild(iframe);
+    
+	    var win = iframe.contentWindow;
+    
+	    forEach(Object_keys(context), function (key) {
+	        win[key] = context[key];
+	    });
+     
+	    if (!win.eval && win.execScript) {
+	        // win.eval() magically appears when this is called in IE:
+	        win.execScript('null');
+	    }
+    
+	    var res = win.eval(this.code);
+    
+	    forEach(Object_keys(win), function (key) {
+	        context[key] = win[key];
+	    });
+    
+	    document.body.removeChild(iframe);
+    
+	    return res;
+	};
+
+	Script.prototype.runInThisContext = function () {
+	    return eval(this.code); // maybe...
+	};
+
+	Script.prototype.runInContext = function (context) {
+	    // seems to be just runInNewContext on magical context objects which are
+	    // otherwise indistinguishable from objects except plain old objects
+	    // for the parameter segfaults node
+	    return this.runInNewContext(context);
+	};
+
+	forEach(Object_keys(Script.prototype), function (name) {
+	    exports[name] = Script[name] = function (code) {
+	        var s = Script(code);
+	        return s[name].apply(s, [].slice.call(arguments, 1));
+	    };
+	});
+
+	exports.createScript = function (code) {
+	    return exports.Script(code);
+	};
+
+	exports.createContext = Script.createContext = function (context) {
+	    // not really sure what this one does
+	    // seems to just make a shallow copy
+	    var copy = {};
+	    if(typeof context === 'object') {
+	        forEach(Object_keys(context), function (key) {
+	            copy[key] = context[key];
+	        });
+	    }
+	    return copy;
+	};
+	});
+
+	require.define("/package.json",function(require,module,exports,__dirname,__filename,process){module.exports = {"main":"./index"}});
+
+	require.define("js-select",function(require,module,exports,__dirname,__filename,process){var traverse = require("traverse"),
+	    JSONSelect = require("JSONSelect");
+
+	module.exports = function(obj, string) {
+	   var sels = parseSelectors(string);
+
+	   return {
+	      nodes: function() {
+	         var nodes = [];
+	         this.forEach(function(node) {
+	            nodes.push(node);
+	         });
+	         return nodes;
+	      },
+      
+	      update: function(cb) {
+	         this.forEach(function(node) {
+	            this.update(typeof cb == "function" ? cb(node) : cb);
+	         });
+	      },
+      
+	      remove: function() {
+	         this.forEach(function(node) {
+	            this.remove();
+	         })
+	      },
+      
+	      condense: function() {
+	         traverse(obj).forEach(function(node) {
+	            if (!this.parent) return;
+            
+	            if (this.parent.keep) {
+	               this.keep = true;
+	            } else {
+	               var match = matchesAny(sels, this);
+	               this.keep = match;
+	               if (!match) {
+	                  if (this.isLeaf) {
+	                     this.remove();
+	                  } else {
+	                     this.after(function() {
+	                        if (this.keep_child) {
+	                           this.parent.keep_child = true;
+	                        }
+	                        if (!this.keep && !this.keep_child) {
+	                           this.remove();
+	                        }
+	                     });
+	                  }
+	               } else {
+	                  this.parent.keep_child = true;
+	               }
+	            }
+	         });
+	      },
+      
+	      forEach: function(cb) {
+	         traverse(obj).forEach(function(node) {
+	            if (matchesAny(sels, this)) {
+	               this.matches = function(string) {
+	                  return matchesAny(parseSelectors(string), this);
+	               };
+	               // inherit context from js-traverse
+	               cb.call(this, node);            
+	            }
+	         });
+	      }
+	   };
+	}
+
+	function parseSelectors(string) {
+	   var parsed = JSONSelect._parse(string || "*")[1];
+	   return getSelectors(parsed);
+	}
+
+	function getSelectors(parsed) {
+	   if (parsed[0] == ",") {  // "selector1, selector2"
+	      return parsed.slice(1);
+	   }
+	   return [parsed];
+	}
+
+	function matchesAny(sels, context) {
+	   for (var i = 0; i < sels.length; i++) {
+	      if (matches(sels[i], context)) {
+	         return true;
+	      }
+	   }
+	   return false;
+	}
+
+	function matches(sel, context) {
+	   var path = context.parents.concat([context]),
+	       i = path.length - 1,
+	       j = sel.length - 1;
+
+	   // walk up the ancestors
+	   var must = true;
+	   while(j >= 0 && i >= 0) {
+	      var part = sel[j],
+	          context = path[i];
+
+	      if (part == ">") {
+	         j--;
+	         must = true;
+	         continue;
+	      }
+
+	      if (matchesKey(part, context)) {
+	         j--;
+	      }
+	      else if (must) {
+	         return false;
+	      }
+
+	      i--;
+	      must = false;
+	   }
+	   return j == -1;
+	}
+
+	function matchesKey(part, context) {
+	   var key = context.key,
+	       node = context.node,
+	       parent = context.parent;
+
+	   if (part.id && key != part.id) {
+	      return false;
+	   }
+	   if (part.type) {
+	      var type = part.type;
+
+	      if (type == "null" && node !== null) {
+	         return false;
+	      }
+	      else if (type == "array" && !isArray(node)) {
+	         return false;
+	      }
+	      else if (type == "object" && (typeof node != "object"
+	                 || node === null || isArray(node))) {
+	         return false;
+	      }
+	      else if ((type == "boolean" || type == "string" || type == "number")
+	               && type != typeof node) {
+	         return false;
+	      }
+	   }
+	   if (part.pf == ":nth-child") {
+	      var index = parseInt(key) + 1;
+	      if ((part.a == 0 && index !== part.b)         // :nth-child(i)
+	        || (part.a == 1 && !(index >= -part.b))     // :nth-child(n)
+	        || (part.a == -1 && !(index <= part.b))     // :nth-child(-n + 1)
+	        || (part.a == 2 && index % 2 != part.b)) {  // :nth-child(even)
+	         return false;
+	      }
+	   }
+	   if (part.pf == ":nth-last-child"
+	      && (!parent || key != parent.node.length - part.b)) {
+	         return false;
+	   }
+	   if (part.pc == ":only-child"
+	      && (!parent || parent.node.length != 1)) {
+	         return false;
+	   }
+	   if (part.pc == ":root" && key !== undefined) {
+	      return false;
+	   }
+	   if (part.has) {
+	      var sels = getSelectors(part.has[0]),
+	          match = false;
+	      traverse(node).forEach(function(child) {
+	         if (matchesAny(sels, this)) {
+	            match = true;
+	         }
+	      });
+	      if (!match) {
+	         return false;
+	      }
+	   }
+	   if (part.expr) {
+	      var expr = part.expr, lhs = expr[0], op = expr[1], rhs = expr[2];
+	      if (typeof node != "string"
+	          || (!lhs && op == "=" && node != rhs)   // :val("str")
+	          || (!lhs && op == "*=" && node.indexOf(rhs) == -1)) { // :contains("substr")
+	         return false;
+	      }
+	   }
+	   return true;
+	}
+
+	var isArray = Array.isArray || function(obj) {
+	    return toString.call(obj) === '[object Array]';
+	}
+	});
+
+	require.define("/node_modules/traverse/package.json",function(require,module,exports,__dirname,__filename,process){module.exports = {"main":"./index"}});
+
+	require.define("/node_modules/traverse/index.js",function(require,module,exports,__dirname,__filename,process){module.exports = Traverse;
+	function Traverse (obj) {
+	    if (!(this instanceof Traverse)) return new Traverse(obj);
+	    this.value = obj;
+	}
+
+	Traverse.prototype.get = function (ps) {
+	    var node = this.value;
+	    for (var i = 0; i < ps.length; i ++) {
+	        var key = ps[i];
+	        if (!Object.hasOwnProperty.call(node, key)) {
+	            node = undefined;
+	            break;
+	        }
+	        node = node[key];
+	    }
+	    return node;
+	};
+
+	Traverse.prototype.set = function (ps, value) {
+	    var node = this.value;
+	    for (var i = 0; i < ps.length - 1; i ++) {
+	        var key = ps[i];
+	        if (!Object.hasOwnProperty.call(node, key)) node[key] = {};
+	        node = node[key];
+	    }
+	    node[ps[i]] = value;
+	    return value;
+	};
+
+	Traverse.prototype.map = function (cb) {
+	    return walk(this.value, cb, true);
+	};
+
+	Traverse.prototype.forEach = function (cb) {
+	    this.value = walk(this.value, cb, false);
+	    return this.value;
+	};
+
+	Traverse.prototype.reduce = function (cb, init) {
+	    var skip = arguments.length === 1;
+	    var acc = skip ? this.value : init;
+	    this.forEach(function (x) {
+	        if (!this.isRoot || !skip) {
+	            acc = cb.call(this, acc, x);
+	        }
+	    });
+	    return acc;
+	};
+
+	Traverse.prototype.deepEqual = function (obj) {
+	    if (arguments.length !== 1) {
+	        throw new Error(
+	            'deepEqual requires exactly one object to compare against'
+	        );
+	    }
+    
+	    var equal = true;
+	    var node = obj;
+    
+	    this.forEach(function (y) {
+	        var notEqual = (function () {
+	            equal = false;
+	            //this.stop();
+	            return undefined;
+	        }).bind(this);
+        
+	        //if (node === undefined || node === null) return notEqual();
+        
+	        if (!this.isRoot) {
+	        /*
+	            if (!Object.hasOwnProperty.call(node, this.key)) {
+	                return notEqual();
+	            }
+	        */
+	            if (typeof node !== 'object') return notEqual();
+	            node = node[this.key];
+	        }
+        
+	        var x = node;
+        
+	        this.post(function () {
+	            node = x;
+	        });
+        
+	        var toS = function (o) {
+	            return Object.prototype.toString.call(o);
+	        };
+        
+	        if (this.circular) {
+	            if (Traverse(obj).get(this.circular.path) !== x) notEqual();
+	        }
+	        else if (typeof x !== typeof y) {
+	            notEqual();
+	        }
+	        else if (x === null || y === null || x === undefined || y === undefined) {
+	            if (x !== y) notEqual();
+	        }
+	        else if (x.__proto__ !== y.__proto__) {
+	            notEqual();
+	        }
+	        else if (x === y) {
+	            // nop
+	        }
+	        else if (typeof x === 'function') {
+	            if (x instanceof RegExp) {
+	                // both regexps on account of the __proto__ check
+	                if (x.toString() != y.toString()) notEqual();
+	            }
+	            else if (x !== y) notEqual();
+	        }
+	        else if (typeof x === 'object') {
+	            if (toS(y) === '[object Arguments]'
+	            || toS(x) === '[object Arguments]') {
+	                if (toS(x) !== toS(y)) {
+	                    notEqual();
+	                }
+	            }
+	            else if (x instanceof Date || y instanceof Date) {
+	                if (!(x instanceof Date) || !(y instanceof Date)
+	                || x.getTime() !== y.getTime()) {
+	                    notEqual();
+	                }
+	            }
+	            else {
+	                var kx = Object.keys(x);
+	                var ky = Object.keys(y);
+	                if (kx.length !== ky.length) return notEqual();
+	                for (var i = 0; i < kx.length; i++) {
+	                    var k = kx[i];
+	                    if (!Object.hasOwnProperty.call(y, k)) {
+	                        notEqual();
+	                    }
+	                }
+	            }
+	        }
+	    });
+    
+	    return equal;
+	};
+
+	Traverse.prototype.paths = function () {
+	    var acc = [];
+	    this.forEach(function (x) {
+	        acc.push(this.path); 
+	    });
+	    return acc;
+	};
+
+	Traverse.prototype.nodes = function () {
+	    var acc = [];
+	    this.forEach(function (x) {
+	        acc.push(this.node);
+	    });
+	    return acc;
+	};
+
+	Traverse.prototype.clone = function () {
+	    var parents = [], nodes = [];
+    
+	    return (function clone (src) {
+	        for (var i = 0; i < parents.length; i++) {
+	            if (parents[i] === src) {
+	                return nodes[i];
+	            }
+	        }
+        
+	        if (typeof src === 'object' && src !== null) {
+	            var dst = copy(src);
+            
+	            parents.push(src);
+	            nodes.push(dst);
+            
+	            Object.keys(src).forEach(function (key) {
+	                dst[key] = clone(src[key]);
+	            });
+            
+	            parents.pop();
+	            nodes.pop();
+	            return dst;
+	        }
+	        else {
+	            return src;
+	        }
+	    })(this.value);
+	};
+
+	function walk (root, cb, immutable) {
+	    var path = [];
+	    var parents = [];
+	    var alive = true;
+    
+	    return (function walker (node_) {
+	        var node = immutable ? copy(node_) : node_;
+	        var modifiers = {};
+        
+	        var keepGoing = true;
+        
+	        var state = {
+	            node : node,
+	            node_ : node_,
+	            path : [].concat(path),
+	            parent : parents[parents.length - 1],
+	            parents : parents,
+	            key : path.slice(-1)[0],
+	            isRoot : path.length === 0,
+	            level : path.length,
+	            circular : null,
+	            update : function (x, stopHere) {
+	                if (!state.isRoot) {
+	                    state.parent.node[state.key] = x;
+	                }
+	                state.node = x;
+	                if (stopHere) keepGoing = false;
+	            },
+	            'delete' : function () {
+	                delete state.parent.node[state.key];
+	            },
+	            remove : function () {
+	                if (Array.isArray(state.parent.node)) {
+	                    state.parent.node.splice(state.key, 1);
+	                }
+	                else {
+	                    delete state.parent.node[state.key];
+	                }
+	            },
+	            keys : null,
+	            before : function (f) { modifiers.before = f },
+	            after : function (f) { modifiers.after = f },
+	            pre : function (f) { modifiers.pre = f },
+	            post : function (f) { modifiers.post = f },
+	            stop : function () { alive = false },
+	            block : function () { keepGoing = false }
+	        };
+        
+	        if (!alive) return state;
+        
+	        if (typeof node === 'object' && node !== null) {
+	            state.keys = Object.keys(node);
+            
+	            state.isLeaf = state.keys.length == 0;
+            
+	            for (var i = 0; i < parents.length; i++) {
+	                if (parents[i].node_ === node_) {
+	                    state.circular = parents[i];
+	                    break;
+	                }
+	            }
+	        }
+	        else {
+	            state.isLeaf = true;
+	        }
+        
+	        state.notLeaf = !state.isLeaf;
+	        state.notRoot = !state.isRoot;
+        
+	        // use return values to update if defined
+	        var ret = cb.call(state, state.node);
+	        if (ret !== undefined && state.update) state.update(ret);
+        
+	        if (modifiers.before) modifiers.before.call(state, state.node);
+        
+	        if (!keepGoing) return state;
+        
+	        if (typeof state.node == 'object'
+	        && state.node !== null && !state.circular) {
+	            parents.push(state);
+            
+	            state.keys.forEach(function (key, i) {
+	                path.push(key);
+                
+	                if (modifiers.pre) modifiers.pre.call(state, state.node[key], key);
+                
+	                var child = walker(state.node[key]);
+	                if (immutable && Object.hasOwnProperty.call(state.node, key)) {
+	                    state.node[key] = child.node;
+	                }
+                
+	                child.isLast = i == state.keys.length - 1;
+	                child.isFirst = i == 0;
+                
+	                if (modifiers.post) modifiers.post.call(state, child);
+                
+	                path.pop();
+	            });
+	            parents.pop();
+	        }
+        
+	        if (modifiers.after) modifiers.after.call(state, state.node);
+        
+	        return state;
+	    })(root).node;
+	}
+
+	Object.keys(Traverse.prototype).forEach(function (key) {
+	    Traverse[key] = function (obj) {
+	        var args = [].slice.call(arguments, 1);
+	        var t = Traverse(obj);
+	        return t[key].apply(t, args);
+	    };
+	});
+
+	function copy (src) {
+	    if (typeof src === 'object' && src !== null) {
+	        var dst;
+        
+	        if (Array.isArray(src)) {
+	            dst = [];
+	        }
+	        else if (src instanceof Date) {
+	            dst = new Date(src);
+	        }
+	        else if (src instanceof Boolean) {
+	            dst = new Boolean(src);
+	        }
+	        else if (src instanceof Number) {
+	            dst = new Number(src);
+	        }
+	        else if (src instanceof String) {
+	            dst = new String(src);
+	        }
+	        else {
+	            dst = Object.create(Object.getPrototypeOf(src));
+	        }
+        
+	        Object.keys(src).forEach(function (key) {
+	            dst[key] = src[key];
+	        });
+	        return dst;
+	    }
+	    else return src;
+	}
+	});
+
+	require.define("/node_modules/JSONSelect/package.json",function(require,module,exports,__dirname,__filename,process){module.exports = {"main":"src/jsonselect"}});
+
+	require.define("/node_modules/JSONSelect/src/jsonselect.js",function(require,module,exports,__dirname,__filename,process){/*! Copyright (c) 2011, Lloyd Hilaiel, ISC License */
+	/*
+	 * This is the JSONSelect reference implementation, in javascript.
+	 */
+	(function(exports) {
+
+	    var // localize references
+	    toString = Object.prototype.toString;
+
+	    function jsonParse(str) {
+	      try {
+	          if(JSON && JSON.parse){
+	              return JSON.parse(str);
+	          }
+	          return (new Function("return " + str))();
+	      } catch(e) {
+	        te("ijs", e.message);
+	      }
+	    }
+
+	    // emitted error codes.
+	    var errorCodes = {
+	        "bop":  "binary operator expected",
+	        "ee":   "expression expected",
+	        "epex": "closing paren expected ')'",
+	        "ijs":  "invalid json string",
+	        "mcp":  "missing closing paren",
+	        "mepf": "malformed expression in pseudo-function",
+	        "mexp": "multiple expressions not allowed",
+	        "mpc":  "multiple pseudo classes (:xxx) not allowed",
+	        "nmi":  "multiple ids not allowed",
+	        "pex":  "opening paren expected '('",
+	        "se":   "selector expected",
+	        "sex":  "string expected",
+	        "sra":  "string required after '.'",
+	        "uc":   "unrecognized char",
+	        "ucp":  "unexpected closing paren",
+	        "ujs":  "unclosed json string",
+	        "upc":  "unrecognized pseudo class"
+	    };
+
+	    // throw an error message
+	    function te(ec, context) {
+	      throw new Error(errorCodes[ec] + ( context && " in '" + context + "'"));
+	    }
+
+	    // THE LEXER
+	    var toks = {
+	        psc: 1, // pseudo class
+	        psf: 2, // pseudo class function
+	        typ: 3, // type
+	        str: 4, // string
+	        ide: 5  // identifiers (or "classes", stuff after a dot)
+	    };
+
+	    // The primary lexing regular expression in jsonselect
+	    var pat = new RegExp(
+	        "^(?:" +
+	        // (1) whitespace
+	        "([\\r\\n\\t\\ ]+)|" +
+	        // (2) one-char ops
+	        "([~*,>\\)\\(])|" +
+	        // (3) types names
+	        "(string|boolean|null|array|object|number)|" +
+	        // (4) pseudo classes
+	        "(:(?:root|first-child|last-child|only-child))|" +
+	        // (5) pseudo functions
+	        "(:(?:nth-child|nth-last-child|has|expr|val|contains))|" +
+	        // (6) bogusly named pseudo something or others
+	        "(:\\w+)|" +
+	        // (7 & 8) identifiers and JSON strings
+	        "(?:(\\.)?(\\\"(?:[^\\\\\\\"]|\\\\[^\\\"])*\\\"))|" +
+	        // (8) bogus JSON strings missing a trailing quote
+	        "(\\\")|" +
+	        // (9) identifiers (unquoted)
+	        "\\.((?:[_a-zA-Z]|[^\\0-\\0177]|\\\\[^\\r\\n\\f0-9a-fA-F])(?:[_a-zA-Z0-9\\-]|[^\\u0000-\\u0177]|(?:\\\\[^\\r\\n\\f0-9a-fA-F]))*)" +
+	        ")"
+	    );
+
+	    // A regular expression for matching "nth expressions" (see grammar, what :nth-child() eats)
+	    var nthPat = /^\s*\(\s*(?:([+\-]?)([0-9]*)n\s*(?:([+\-])\s*([0-9]))?|(odd|even)|([+\-]?[0-9]+))\s*\)/;
+	    function lex(str, off) {
+	        if (!off) off = 0;
+	        var m = pat.exec(str.substr(off));
+	        if (!m) return undefined;
+	        off+=m[0].length;
+	        var a;
+	        if (m[1]) a = [off, " "];
+	        else if (m[2]) a = [off, m[0]];
+	        else if (m[3]) a = [off, toks.typ, m[0]];
+	        else if (m[4]) a = [off, toks.psc, m[0]];
+	        else if (m[5]) a = [off, toks.psf, m[0]];
+	        else if (m[6]) te("upc", str);
+	        else if (m[8]) a = [off, m[7] ? toks.ide : toks.str, jsonParse(m[8])];
+	        else if (m[9]) te("ujs", str);
+	        else if (m[10]) a = [off, toks.ide, m[10].replace(/\\([^\r\n\f0-9a-fA-F])/g,"$1")];
+	        return a;
+	    }
+
+	    // THE EXPRESSION SUBSYSTEM
+
+	    var exprPat = new RegExp(
+	            // skip and don't capture leading whitespace
+	            "^\\s*(?:" +
+	            // (1) simple vals
+	            "(true|false|null)|" + 
+	            // (2) numbers
+	            "(-?\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?)|" +
+	            // (3) strings
+	            "(\"(?:[^\\]|\\[^\"])*\")|" +
+	            // (4) the 'x' value placeholder
+	            "(x)|" +
+	            // (5) binops
+	            "(&&|\\|\\||[\\$\\^<>!\\*]=|[=+\\-*/%<>])|" +
+	            // (6) parens
+	            "([\\(\\)])" +
+	            ")"
+	    );
+
+	    function is(o, t) { return typeof o === t; }
+	    var operators = {
+	        '*':  [ 9, function(lhs, rhs) { return lhs * rhs; } ],
+	        '/':  [ 9, function(lhs, rhs) { return lhs / rhs; } ],
+	        '%':  [ 9, function(lhs, rhs) { return lhs % rhs; } ],
+	        '+':  [ 7, function(lhs, rhs) { return lhs + rhs; } ],
+	        '-':  [ 7, function(lhs, rhs) { return lhs - rhs; } ],
+	        '<=': [ 5, function(lhs, rhs) { return is(lhs, 'number') && is(rhs, 'number') && lhs <= rhs; } ],
+	        '>=': [ 5, function(lhs, rhs) { return is(lhs, 'number') && is(rhs, 'number') && lhs >= rhs; } ],
+	        '$=': [ 5, function(lhs, rhs) { return is(lhs, 'string') && is(rhs, 'string') && lhs.lastIndexOf(rhs) === lhs.length - rhs.length; } ],
+	        '^=': [ 5, function(lhs, rhs) { return is(lhs, 'string') && is(rhs, 'string') && lhs.indexOf(rhs) === 0; } ],
+	        '*=': [ 5, function(lhs, rhs) { return is(lhs, 'string') && is(rhs, 'string') && lhs.indexOf(rhs) !== -1; } ],
+	        '>':  [ 5, function(lhs, rhs) { return is(lhs, 'number') && is(rhs, 'number') && lhs > rhs; } ],
+	        '<':  [ 5, function(lhs, rhs) { return is(lhs, 'number') && is(rhs, 'number') && lhs < rhs; } ],
+	        '=':  [ 3, function(lhs, rhs) { return lhs === rhs; } ],
+	        '!=': [ 3, function(lhs, rhs) { return lhs !== rhs; } ],
+	        '&&': [ 2, function(lhs, rhs) { return lhs && rhs; } ],
+	        '||': [ 1, function(lhs, rhs) { return lhs || rhs; } ]
+	    };
+
+	    function exprLex(str, off) {
+	        var v, m = exprPat.exec(str.substr(off));
+	        if (m) {
+	            off += m[0].length;
+	            v = m[1] || m[2] || m[3] || m[5] || m[6];
+	            if (m[1] || m[2] || m[3]) return [off, 0, jsonParse(v)];
+	            else if (m[4]) return [off, 0, undefined];
+	            return [off, v];
+	        }
+	    }
+
+	    function exprParse2(str, off) {
+	        if (!off) off = 0;
+	        // first we expect a value or a '('
+	        var l = exprLex(str, off),
+	            lhs;
+	        if (l && l[1] === '(') {
+	            lhs = exprParse2(str, l[0]);
+	            var p = exprLex(str, lhs[0]);
+	            if (!p || p[1] !== ')') te('epex', str);
+	            off = p[0];
+	            lhs = [ '(', lhs[1] ];
+	        } else if (!l || (l[1] && l[1] != 'x')) {
+	            te("ee", str + " - " + ( l[1] && l[1] ));
+	        } else {
+	            lhs = ((l[1] === 'x') ? undefined : l[2]);
+	            off = l[0];
+	        }
+
+	        // now we expect a binary operator or a ')'
+	        var op = exprLex(str, off);
+	        if (!op || op[1] == ')') return [off, lhs];
+	        else if (op[1] == 'x' || !op[1]) {
+	            te('bop', str + " - " + ( op[1] && op[1] ));
+	        }
+
+	        // tail recursion to fetch the rhs expression
+	        var rhs = exprParse2(str, op[0]);
+	        off = rhs[0];
+	        rhs = rhs[1];
+
+	        // and now precedence!  how shall we put everything together?
+	        var v;
+	        if (typeof rhs !== 'object' || rhs[0] === '(' || operators[op[1]][0] < operators[rhs[1]][0] ) {
+	            v = [lhs, op[1], rhs];
+	        }
+	        else {
+	            v = rhs;
+	            while (typeof rhs[0] === 'object' && rhs[0][0] != '(' && operators[op[1]][0] >= operators[rhs[0][1]][0]) {
+	                rhs = rhs[0];
+	            }
+	            rhs[0] = [lhs, op[1], rhs[0]];
+	        }
+	        return [off, v];
+	    }
+
+	    function exprParse(str, off) {
+	        function deparen(v) {
+	            if (typeof v !== 'object' || v === null) return v;
+	            else if (v[0] === '(') return deparen(v[1]);
+	            else return [deparen(v[0]), v[1], deparen(v[2])];
+	        }
+	        var e = exprParse2(str, off ? off : 0);
+	        return [e[0], deparen(e[1])];
+	    }
+
+	    function exprEval(expr, x) {
+	        if (expr === undefined) return x;
+	        else if (expr === null || typeof expr !== 'object') {
+	            return expr;
+	        }
+	        var lhs = exprEval(expr[0], x),
+	            rhs = exprEval(expr[2], x);
+	        return operators[expr[1]][1](lhs, rhs);
+	    }
+
+	    // THE PARSER
+
+	    function parse(str, off, nested, hints) {
+	        if (!nested) hints = {};
+
+	        var a = [], am, readParen;
+	        if (!off) off = 0; 
+
+	        while (true) {
+	            var s = parse_selector(str, off, hints);
+	            a.push(s[1]);
+	            s = lex(str, off = s[0]);
+	            if (s && s[1] === " ") s = lex(str, off = s[0]);
+	            if (!s) break;
+	            // now we've parsed a selector, and have something else...
+	            if (s[1] === ">" || s[1] === "~") {
+	                if (s[1] === "~") hints.usesSiblingOp = true;
+	                a.push(s[1]);
+	                off = s[0];
+	            } else if (s[1] === ",") {
+	                if (am === undefined) am = [ ",", a ];
+	                else am.push(a);
+	                a = [];
+	                off = s[0];
+	            } else if (s[1] === ")") {
+	                if (!nested) te("ucp", s[1]);
+	                readParen = 1;
+	                off = s[0];
+	                break;
+	            }
+	        }
+	        if (nested && !readParen) te("mcp", str);
+	        if (am) am.push(a);
+	        var rv;
+	        if (!nested && hints.usesSiblingOp) {
+	            rv = normalize(am ? am : a);
+	        } else {
+	            rv = am ? am : a;
+	        }
+	        return [off, rv];
+	    }
+
+	    function normalizeOne(sel) {
+	        var sels = [], s;
+	        for (var i = 0; i < sel.length; i++) {
+	            if (sel[i] === '~') {
+	                // `A ~ B` maps to `:has(:root > A) > B`
+	                // `Z A ~ B` maps to `Z :has(:root > A) > B, Z:has(:root > A) > B`
+	                // This first clause, takes care of the first case, and the first half of the latter case.
+	                if (i < 2 || sel[i-2] != '>') {
+	                    s = sel.slice(0,i-1);
+	                    s = s.concat([{has:[[{pc: ":root"}, ">", sel[i-1]]]}, ">"]);
+	                    s = s.concat(sel.slice(i+1));
+	                    sels.push(s);
+	                }
+	                // here we take care of the second half of above:
+	                // (`Z A ~ B` maps to `Z :has(:root > A) > B, Z :has(:root > A) > B`)
+	                // and a new case:
+	                // Z > A ~ B maps to Z:has(:root > A) > B
+	                if (i > 1) {
+	                    var at = sel[i-2] === '>' ? i-3 : i-2;
+	                    s = sel.slice(0,at);
+	                    var z = {};
+	                    for (var k in sel[at]) if (sel[at].hasOwnProperty(k)) z[k] = sel[at][k];
+	                    if (!z.has) z.has = [];
+	                    z.has.push([{pc: ":root"}, ">", sel[i-1]]);
+	                    s = s.concat(z, '>', sel.slice(i+1));
+	                    sels.push(s);
+	                }
+	                break;
+	            }
+	        }
+	        if (i == sel.length) return sel;
+	        return sels.length > 1 ? [','].concat(sels) : sels[0];
+	    }
+
+	    function normalize(sels) {
+	        if (sels[0] === ',') {
+	            var r = [","];
+	            for (var i = i; i < sels.length; i++) {
+	                var s = normalizeOne(s[i]);
+	                r = r.concat(s[0] === "," ? s.slice(1) : s);
+	            }
+	            return r;
+	        } else {
+	            return normalizeOne(sels);
+	        }
+	    }
+
+	    function parse_selector(str, off, hints) {
+	        var soff = off;
+	        var s = { };
+	        var l = lex(str, off);
+	        // skip space
+	        if (l && l[1] === " ") { soff = off = l[0]; l = lex(str, off); }
+	        if (l && l[1] === toks.typ) {
+	            s.type = l[2];
+	            l = lex(str, (off = l[0]));
+	        } else if (l && l[1] === "*") {
+	            // don't bother representing the universal sel, '*' in the
+	            // parse tree, cause it's the default
+	            l = lex(str, (off = l[0]));
+	        }
+
+	        // now support either an id or a pc
+	        while (true) {
+	            if (l === undefined) {
+	                break;
+	            } else if (l[1] === toks.ide) {
+	                if (s.id) te("nmi", l[1]);
+	                s.id = l[2];
+	            } else if (l[1] === toks.psc) {
+	                if (s.pc || s.pf) te("mpc", l[1]);
+	                // collapse first-child and last-child into nth-child expressions
+	                if (l[2] === ":first-child") {
+	                    s.pf = ":nth-child";
+	                    s.a = 0;
+	                    s.b = 1;
+	                } else if (l[2] === ":last-child") {
+	                    s.pf = ":nth-last-child";
+	                    s.a = 0;
+	                    s.b = 1;
+	                } else {
+	                    s.pc = l[2];
+	                }
+	            } else if (l[1] === toks.psf) {
+	                if (l[2] === ":val" || l[2] === ":contains") {
+	                    s.expr = [ undefined, l[2] === ":val" ? "=" : "*=", undefined];
+	                    // any amount of whitespace, followed by paren, string, paren
+	                    l = lex(str, (off = l[0]));
+	                    if (l && l[1] === " ") l = lex(str, off = l[0]);
+	                    if (!l || l[1] !== "(") te("pex", str);
+	                    l = lex(str, (off = l[0]));
+	                    if (l && l[1] === " ") l = lex(str, off = l[0]);
+	                    if (!l || l[1] !== toks.str) te("sex", str);
+	                    s.expr[2] = l[2];
+	                    l = lex(str, (off = l[0]));
+	                    if (l && l[1] === " ") l = lex(str, off = l[0]);
+	                    if (!l || l[1] !== ")") te("epex", str);
+	                } else if (l[2] === ":has") {
+	                    // any amount of whitespace, followed by paren
+	                    l = lex(str, (off = l[0]));
+	                    if (l && l[1] === " ") l = lex(str, off = l[0]);
+	                    if (!l || l[1] !== "(") te("pex", str);
+	                    var h = parse(str, l[0], true);
+	                    l[0] = h[0];
+	                    if (!s.has) s.has = [];
+	                    s.has.push(h[1]);
+	                } else if (l[2] === ":expr") {
+	                    if (s.expr) te("mexp", str);
+	                    var e = exprParse(str, l[0]);
+	                    l[0] = e[0];
+	                    s.expr = e[1];
+	                } else {
+	                    if (s.pc || s.pf ) te("mpc", str);
+	                    s.pf = l[2];
+	                    var m = nthPat.exec(str.substr(l[0]));
+	                    if (!m) te("mepf", str);
+	                    if (m[5]) {
+	                        s.a = 2;
+	                        s.b = (m[5] === "odd") ? 1 : 0;
+	                    } else if (m[6]) {
+	                        s.a = 0;
+	                        s.b = parseInt(m[6], 10);
+	                    } else {
+	                        s.a = parseInt((m[1] ? m[1] : "+") + (m[2] ? m[2] : "1"),10);
+	                        s.b = m[3] ? parseInt(m[3] + m[4],10) : 0;
+	                    }
+	                    l[0] += m[0].length;
+	                }
+	            } else {
+	                break;
+	            }
+	            l = lex(str, (off = l[0]));
+	        }
+
+	        // now if we didn't actually parse anything it's an error
+	        if (soff === off) te("se", str);
+
+	        return [off, s];
+	    }
+
+	    // THE EVALUATOR
+
+	    function isArray(o) {
+	        return Array.isArray ? Array.isArray(o) : 
+	          toString.call(o) === "[object Array]";
+	    }
+
+	    function mytypeof(o) {
+	        if (o === null) return "null";
+	        var to = typeof o;
+	        if (to === "object" && isArray(o)) to = "array";
+	        return to;
+	    }
+
+	    function mn(node, sel, id, num, tot) {
+	        var sels = [];
+	        var cs = (sel[0] === ">") ? sel[1] : sel[0];
+	        var m = true, mod;
+	        if (cs.type) m = m && (cs.type === mytypeof(node));
+	        if (cs.id)   m = m && (cs.id === id);
+	        if (m && cs.pf) {
+	            if (cs.pf === ":nth-last-child") num = tot - num;
+	            else num++;
+	            if (cs.a === 0) {
+	                m = cs.b === num;
+	            } else {
+	                mod = ((num - cs.b) % cs.a);
+
+	                m = (!mod && ((num*cs.a + cs.b) >= 0));
+	            }
+	        }
+	        if (m && cs.has) {
+	            // perhaps we should augment forEach to handle a return value
+	            // that indicates "client cancels traversal"?
+	            var bail = function() { throw 42; };
+	            for (var i = 0; i < cs.has.length; i++) {
+	                try {
+	                    forEach(cs.has[i], node, bail);
+	                } catch (e) {
+	                    if (e === 42) continue;
+	                }
+	                m = false;
+	                break;
+	            }
+	        }
+	        if (m && cs.expr) {
+	            m = exprEval(cs.expr, node);
+	        }
+	        // should we repeat this selector for descendants?
+	        if (sel[0] !== ">" && sel[0].pc !== ":root") sels.push(sel);
+
+	        if (m) {
+	            // is there a fragment that we should pass down?
+	            if (sel[0] === ">") { if (sel.length > 2) { m = false; sels.push(sel.slice(2)); } }
+	            else if (sel.length > 1) { m = false; sels.push(sel.slice(1)); }
+	        }
+
+	        return [m, sels];
+	    }
+
+	    function forEach(sel, obj, fun, id, num, tot) {
+	        var a = (sel[0] === ",") ? sel.slice(1) : [sel],
+	        a0 = [],
+	        call = false,
+	        i = 0, j = 0, k, x;
+	        for (i = 0; i < a.length; i++) {
+	            x = mn(obj, a[i], id, num, tot);
+	            if (x[0]) {
+	                call = true;
+	            }
+	            for (j = 0; j < x[1].length; j++) {
+	                a0.push(x[1][j]);
+	            }
+	        }
+	        if (a0.length && typeof obj === "object") {
+	            if (a0.length >= 1) {
+	                a0.unshift(",");
+	            }
+	            if (isArray(obj)) {
+	                for (i = 0; i < obj.length; i++) {
+	                    forEach(a0, obj[i], fun, undefined, i, obj.length);
+	                }
+	            } else {
+	                for (k in obj) {
+	                    if (obj.hasOwnProperty(k)) {
+	                        forEach(a0, obj[k], fun, k);
+	                    }
+	                }
+	            }
+	        }
+	        if (call && fun) {
+	            fun(obj);
+	        }
+	    }
+
+	    function match(sel, obj) {
+	        var a = [];
+	        forEach(sel, obj, function(x) {
+	            a.push(x);
+	        });
+	        return a;
+	    }
+
+	    function compile(sel) {
+	        return {
+	            sel: parse(sel)[1],
+	            match: function(obj){
+	                return match(this.sel, obj);
+	            },
+	            forEach: function(obj, fun) {
+	                return forEach(this.sel, obj, fun);
+	            }
+	        };
+	    }
+
+	    exports._lex = lex;
+	    exports._parse = parse;
+	    exports.match = function (sel, obj) {
+	        return compile(sel).match(obj);
+	    };
+	    exports.forEach = function(sel, obj, fun) {
+	        return compile(sel).forEach(obj, fun);
+	    };
+	    exports.compile = compile;
+	})(typeof exports === "undefined" ? (window.JSONSelect = {}) : exports);
+	});
+	
+	return require('js-select');
+})();
\ No newline at end of file
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/test/runner.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/test/runner.js
new file mode 100644
index 0000000..9d90ac0
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/test/runner.js
@@ -0,0 +1,58 @@
+/* node runner for the JSONSelect conformance tests
+   https://github.com/lloyd/JSONSelectTests */
+
+var assert = require("assert"),
+    fs = require("fs"),
+    path = require("path"),
+    colors = require("colors"),
+    traverse = require("traverse")
+    select = require("../index");
+
+var options = require("nomnom").opts({
+   directory: {
+      abbr: 'd',
+      help: 'directory of tests to run',
+      metavar: 'PATH',
+      default: __dirname + "/level_1"
+   }
+}).parseArgs();
+
+var directory = options.directory,
+    files = fs.readdirSync(directory);
+
+var jsonFiles = files.filter(function(file) {
+   return path.extname(file) == ".json";
+});
+
+jsonFiles.forEach(function(file) {
+   var jsonName = file.replace(/\..*$/, ""),
+       json = JSON.parse(fs.readFileSync(path.join(directory, file), "utf-8"));
+   
+   var selFiles = files.filter(function(file) {
+      return file.indexOf(jsonName) == 0 && path.extname(file) == ".selector"
+   })
+   
+   selFiles.forEach(function(file) {
+      var test = file.replace(/\..*$/, "");
+      var selector = fs.readFileSync(path.join(directory, file), "utf-8");
+      var output = fs.readFileSync(path.join(directory, test + ".output"), "utf-8");
+      
+      var expected = JSON.parse(output).sort(sort);
+      var got = select(json, selector).nodes().sort(sort);
+      try {
+          assert.deepEqual(got, expected);
+      }
+      catch(AssertionError) {
+         console.log("\nfail".red + " " + test + "\ngot: ".blue + JSON.stringify(got)
+            + "\n\expected: ".blue + JSON.stringify(expected) + "\n")
+      };
+      console.log("pass".green + " " + test);
+   });
+});
+
+function sort(a, b) {
+   if (JSON.stringify(a) < JSON.stringify(b)) {
+      return -1;      
+   }
+   return 1;
+}
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/test/test.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/test/test.js
new file mode 100644
index 0000000..2a215da
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/test/test.js
@@ -0,0 +1,161 @@
+var assert = require("assert"),
+    fs = require("fs"),
+    traverse = require("traverse")
+    select = require("../index");
+
+var people = {
+   "george": {
+       age : 35,
+       movies: [{
+          name: "Repo Man",
+          stars: 5
+      }]
+   },
+   "mary": {
+       age: 15,
+       movies: [{
+           name: "Twilight",
+           stars: 3
+       },
+       {
+          name: "Trudy",
+          stars: 2
+       },
+       {
+          name: "The Fighter",
+          stars: 4
+       }]
+   },
+   "chris" : {
+      car: null,
+      male: true
+   }
+};
+
+var people2, obj;
+
+assert.deepEqual(select(people, "*").nodes(), [{"george":{"age":35,"movies":[{"name":"Repo Man","stars":5}]},"mary":{"age":15,"movies":[{"name":"Twilight","stars":3},{"name":"Trudy","stars":2},{"name":"The Fighter","stars":4}]},"chris":{"car":null,"male":true}},{"age":35,"movies":[{"name":"Repo Man","stars":5}]},35,[{"name":"Repo Man","stars":5}],{"name":"Repo Man","stars":5},"Repo Man",5,{"age":15,"movies":[{"name":"Twilight","stars":3},{"name":"Trudy","stars":2},{"name":"The Fighter","stars":4}]},15,[{"name":"Twilight","stars":3},{"name":"Trudy","stars":2},{"name":"The Fighter","stars":4}],{"name":"Twilight","stars":3},"Twilight",3,{"name":"Trudy","stars":2},"Trudy",2,{"name":"The Fighter","stars":4},"The Fighter",4,{"car":null,"male":true},null,true]);
+assert.deepEqual(select(people, ".george").nodes(), [{"age":35,"movies":[{"name":"Repo Man","stars":5}]}]);
+assert.deepEqual(select(people, ".george .age").nodes(), [35]);
+assert.deepEqual(select(people, ".george .name").nodes(), ["Repo Man"]);
+assert.deepEqual(select(people, ".george *").nodes(), [35,[{"name":"Repo Man","stars":5}],{"name":"Repo Man","stars":5},"Repo Man",5])
+
+assert.deepEqual(select(people, ".george > *").nodes(), [35,[{"name":"Repo Man","stars":5}]]);
+assert.deepEqual(select(people, ".george > .name").nodes(), []);
+
+assert.deepEqual(select(people, ":first-child").nodes(), [{"name":"Repo Man","stars":5},{"name":"Twilight","stars":3}]);
+assert.deepEqual(select(people, ":nth-child(1)").nodes(), select(people, ":first-child").nodes());
+assert.deepEqual(select(people, ":nth-child(2)").nodes(), [{"name":"Trudy","stars":2}]);
+assert.deepEqual(select(people, ":nth-child(odd)").nodes(), [{"name":"Repo Man","stars":5},{"name":"Twilight","stars":3},{"name":"The Fighter","stars":4}]);
+assert.deepEqual(select(people, ":nth-child(even)").nodes(), [{"name":"Trudy","stars":2}]);
+
+assert.deepEqual(select(people, ":nth-child(-n+1)").nodes(), select(people, ":first-child").nodes());
+assert.deepEqual(select(people, ":nth-child(-n+2)").nodes(), [{"name":"Repo Man","stars":5},{"name":"Twilight","stars":3},{"name":"Trudy","stars":2}]);
+assert.deepEqual(select(people, ":nth-child(n)").nodes(), [{"name":"Repo Man","stars":5},{"name":"Twilight","stars":3},{"name":"Trudy","stars":2},{"name":"The Fighter","stars":4}]);
+assert.deepEqual(select(people, ":nth-child(n-1)").nodes(), select(people, ":nth-child(n)").nodes());
+assert.deepEqual(select(people, ":nth-child(n-2)").nodes(), [{"name":"Trudy","stars":2},{"name":"The Fighter","stars":4}]);
+
+assert.deepEqual(select(people, ":last-child").nodes(), [{"name":"Repo Man","stars":5},{"name":"The Fighter","stars":4}]);
+assert.deepEqual(select(people, ":nth-last-child(1)").nodes(), select(people, ":last-child").nodes());
+assert.deepEqual(select(people, ":nth-last-child(2)").nodes(), [{"name":"Trudy","stars":2}]);
+assert.deepEqual(select(people, ":only-child").nodes(), [{"name":"Repo Man","stars":5}]);
+assert.deepEqual(select(people, ":root").nodes(),[{"george":{"age":35,"movies":[{"name":"Repo Man","stars":5}]},"mary":{"age":15,"movies":[{"name":"Twilight","stars":3},{"name":"Trudy","stars":2},{"name":"The Fighter","stars":4}]},"chris":{"car":null,"male":true}}])
+
+assert.deepEqual(select(people, "string").nodes(),["Repo Man","Twilight","Trudy","The Fighter"]);
+assert.deepEqual(select(people, "number").nodes(),[35,5,15,3,2,4]);
+assert.deepEqual(select(people, "boolean").nodes(),[true]);
+assert.deepEqual(select(people, "object").nodes(),[{"george":{"age":35,"movies":[{"name":"Repo Man","stars":5}]},"mary":{"age":15,"movies":[{"name":"Twilight","stars":3},{"name":"Trudy","stars":2},{"name":"The Fighter","stars":4}]},"chris":{"car":null,"male":true}},{"age":35,"movies":[{"name":"Repo Man","stars":5}]},{"name":"Repo Man","stars":5},{"age":15,"movies":[{"name":"Twilight","stars":3},{"name":"Trudy","stars":2},{"name":"The Fighter","stars":4}]},{"name":"Twilight","stars":3},{"name":"Trudy","stars":2},{"name":"The Fighter","stars":4},{"car":null,"male":true}]);
+assert.deepEqual(select(people, "array").nodes(),[[{"name":"Repo Man","stars":5}],[{"name":"Twilight","stars":3},{"name":"Trudy","stars":2},{"name":"The Fighter","stars":4}]]);
+assert.deepEqual(select(people, "null").nodes(),[null]);
+
+assert.deepEqual(select(people, "number, string, boolean").nodes(), [35,"Repo Man",5,15,"Twilight",3,"Trudy",2,"The Fighter",4,true])
+
+assert.deepEqual(select(people, ":has(.car) > .male").nodes(), [true]);
+assert.deepEqual(select(people, ".male ~ .car").nodes(), [null])
+
+assert.deepEqual(select(people, ':val("Twilight")').nodes(), ["Twilight"])
+assert.deepEqual(select(people, ':val("Twi")').nodes(), [])
+assert.deepEqual(select(people, ':contains("Twi")').nodes(), ["Twilight"])
+assert.deepEqual(select(people, ':contains("weif")').nodes(), [])
+
+// invalid
+assert.deepEqual(select(people, ".hmmm").nodes(), []);
+assert.throws(function() {
+   select(people, "afcjwiojwe9q28*C@!(# (!#R($R)))").nodes();
+});
+
+// update()
+people2 = traverse.clone(people);
+
+select(people2, ".age").update(function(age) {
+    return age - 5;
+})
+assert.deepEqual(select(people2, ".age").nodes(), [30, 10]);
+
+obj = select(people2, ".age").update(3)
+assert.deepEqual(select(people2, ".age").nodes(), [3, 3]);
+assert.deepEqual(obj, people2);
+
+// remove()
+people2 = traverse.clone(people);
+
+obj = select(people2, ".age").remove();
+assert.deepEqual(select(people2, ".age").nodes(), []);
+assert.deepEqual(obj, people2);
+
+// condense()
+people2 = traverse.clone(people);
+select(people2, ".george").condense();
+assert.deepEqual(people2, {"george": {age: 35, movies: [{name: "Repo Man", stars: 5}]}});
+
+people2 = traverse.clone(people);
+select(people2, ".hmmm").condense();
+assert.deepEqual(people2, {});
+
+people2 = traverse.clone(people);
+obj = select(people2, ".stars").condense();
+assert.deepEqual(people2, {"george": {movies: [{stars: 5}]}, "mary": {movies: [{stars: 3},{stars: 2},{stars: 4}]}});
+assert.deepEqual(obj, people2);
+
+// forEach()
+people2 = traverse.clone(people);
+
+obj = select(people2, ".age").forEach(function(age) {
+    this.update(age - 5);
+})
+assert.deepEqual(select(people2, ".age").nodes(), [30, 10]);
+assert.deepEqual(obj, people2);
+
+
+// this.matches()
+people2 = traverse.clone(people);
+select(people2).forEach(function(node) {
+   if (this.matches(".age")) {
+      this.update(node + 10);
+   }
+});
+assert.deepEqual(select(people2, ".age").nodes(), [45, 25])
+
+
+// bigger stuff
+var timeline = require("./timeline.js");
+
+console.time("select time");
+assert.equal(select(timeline, ".bug .id").nodes().length, 126);
+assert.equal(select(timeline, ".id").nodes().length, 141);
+assert.equal(select(timeline, ".comments .id").nodes().length, 115);
+assert.equal(select(timeline, ":nth-child(n-2)").nodes().length, 335);
+assert.equal(select(timeline, "object").nodes().length, 927);
+assert.equal(select(timeline, "*").nodes().length, 3281);
+console.timeEnd("select time")
+
+var sel = require("JSONSelect");
+
+console.time("JSONSelect time")
+assert.equal(sel.match(".bug .id", timeline).length, 126);
+assert.equal(sel.match(".id", timeline).length, 141);
+assert.equal(sel.match(".comments .id", timeline).length, 115);
+assert.equal(sel.match(":nth-child(n-2)", timeline).length, 335);
+assert.equal(sel.match("object", timeline).length, 927);
+assert.equal(sel.match("*", timeline).length, 3281);
+console.timeEnd("JSONSelect time")
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/test/timeline.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/test/timeline.js
new file mode 100644
index 0000000..59799a0
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/node_modules/js-select/test/timeline.js
@@ -0,0 +1 @@
+module.exports = [{"bug":{"history":[{"changes":[{"removed":"","added":"gmealer@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/gmealer@mozilla.com","name":"gmealer@mozilla.com"},"change_time":"2011-07-19T22:35:56Z"},{"changes":[{"removed":"NEW","added":"RESOLVED","field_name":"status"},{"removed":"","added":"WONTFIX","field_name":"resolution"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/fayearthur+bugs@gmail.com","name":"fayearthur+bugs@gmail.com"},"change_time":"2011-07-19T22:24:59Z"},{"changes":[{"removed":"","added":"fayearthur+bugs@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/fayearthur+bugs@gmail.com","name":"fayearthur+bugs@gmail.com"},"change_time":"2011-07-13T19:49:06Z"},{"changes":[{"removed":"","added":"hskupin@gmail.com, stomlinson@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-07-13T19:11:32Z"},{"changes":[{"removed":"","added":"ctalbert@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-07-13T18:43:17Z"}],"summary":"Adding isNumber, isFunction, isString, isArray and isObject to assert.js","last_change_time":"2011-07-19T22:35:56Z","comments":[{"is_private":false,"creator":{"real_name":"Geo Mealer [:geo]","name":"gmealer"},"text":"","id":5600392,"creation_time":"2011-07-19T22:35:56Z"},{"is_private":false,"creator":{"real_name":"Heather Arthur [:harth]","name":"fayearthur+bugs"},"text":"","id":5600359,"creation_time":"2011-07-19T22:24:59Z"},{"is_private":false,"creator":{"real_name":"Heather Arthur [:harth]","name":"fayearthur+bugs"},"text":"","id":5589849,"creation_time":"2011-07-13T20:24:38Z"},{"is_private":false,"creator":{"real_name":"Shane Tomlinson","name":"stomlinson"},"text":"","id":5589791,"creation_time":"2011-07-13T19:59:43Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5589782,"creation_time":"2011-07-13T19:56:44Z"},{"is_private":false,"creator":{"real_name":"Heather Arthur [:harth]","name":"fayearthur+bugs"},"text":"","id":5589758,"creation_time":"2011-07-13T19:49:06Z"},{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":5589591,"creation_time":"2011-07-13T18:42:07Z"}],"id":671367},"events":[{"time":"2011-07-19T22:35:56Z","changeset":{"changes":[{"removed":"","added":"gmealer@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/gmealer@mozilla.com","name":"gmealer@mozilla.com"},"change_time":"2011-07-19T22:35:56Z"}},{"time":"2011-07-19T22:35:56Z","comment":{"is_private":false,"creator":{"real_name":"Geo Mealer [:geo]","name":"gmealer"},"text":"","id":5600392,"creation_time":"2011-07-19T22:35:56Z"}},{"time":"2011-07-19T22:24:59Z","changeset":{"changes":[{"removed":"NEW","added":"RESOLVED","field_name":"status"},{"removed":"","added":"WONTFIX","field_name":"resolution"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/fayearthur+bugs@gmail.com","name":"fayearthur+bugs@gmail.com"},"change_time":"2011-07-19T22:24:59Z"}},{"time":"2011-07-19T22:24:59Z","comment":{"is_private":false,"creator":{"real_name":"Heather Arthur [:harth]","name":"fayearthur+bugs"},"text":"","id":5600359,"creation_time":"2011-07-19T22:24:59Z"}}]},{"bug":{"history":[{"changes":[{"removed":"","added":"ctalbert@mozilla.com, fayearthur+bugs@gmail.com, jhammel@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-07-19T20:22:11Z"}],"summary":"mozmill \"--profile\" option not working with relative path","last_change_time":"2011-07-19T20:46:19Z","comments":[{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":5600020,"creation_time":"2011-07-19T20:46:19Z"},{"is_private":false,"creator":{"real_name":"Marc-Aurèle DARCHE","name":"mozdev"},"text":"","id":5599999,"creation_time":"2011-07-19T20:39:07Z"},{"is_private":false,"creator":{"real_name":"Marc-Aurèle DARCHE","name":"mozdev"},"text":"","id":5599848,"creation_time":"2011-07-19T19:29:35Z"}],"id":672605},"events":[{"time":"2011-07-19T20:46:19Z","comment":{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":5600020,"creation_time":"2011-07-19T20:46:19Z"}},{"time":"2011-07-19T20:39:07Z","comment":{"is_private":false,"creator":{"real_name":"Marc-Aurèle DARCHE","name":"mozdev"},"text":"","id":5599999,"creation_time":"2011-07-19T20:39:07Z"}},{"time":"2011-07-19T20:22:11Z","changeset":{"changes":[{"removed":"","added":"ctalbert@mozilla.com, fayearthur+bugs@gmail.com, jhammel@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-07-19T20:22:11Z"}},{"time":"2011-07-19T19:29:35Z","comment":{"is_private":false,"creator":{"real_name":"Marc-Aurèle DARCHE","name":"mozdev"},"text":"","id":5599848,"creation_time":"2011-07-19T19:29:35Z"}}]},{"bug":{"history":[{"changes":[{"removed":"","added":"ehsan@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ehsan@mozilla.com","name":"ehsan@mozilla.com"},"change_time":"2011-07-19T20:37:46Z"},{"changes":[{"removed":"","added":"fayearthur+bugs@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2011-07-19T17:37:11Z"},{"changes":[{"removed":"","added":"ctalbert@mozilla.com, jhammel@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2011-07-19T17:35:58Z"},{"changes":[{"removed":"","added":"ted.mielczarek@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ted.mielczarek@gmail.com","name":"ted.mielczarek@gmail.com"},"change_time":"2011-07-18T12:00:25Z"},{"changes":[{"removed":"","added":"bmo@edmorley.co.uk","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/bmo@edmorley.co.uk","name":"bmo@edmorley.co.uk"},"change_time":"2011-07-17T11:20:51Z"},{"changes":[{"removed":"","added":"bzbarsky@mit.edu","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/bzbarsky@mit.edu","name":"bzbarsky@mit.edu"},"change_time":"2011-07-14T03:29:52Z"},{"changes":[{"removed":"","added":"dmandelin@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/dmandelin@mozilla.com","name":"dmandelin@mozilla.com"},"change_time":"2011-07-14T00:27:21Z"},{"changes":[{"removed":"","added":"kairo@kairo.at","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/kairo@kairo.at","name":"kairo@kairo.at"},"change_time":"2011-07-14T00:04:16Z"},{"changes":[{"removed":"","added":"gavin.sharp@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/gavin.sharp@gmail.com","name":"gavin.sharp@gmail.com"},"change_time":"2011-07-13T23:17:24Z"},{"changes":[{"removed":"","added":"anygregor@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/anygregor@gmail.com","name":"anygregor@gmail.com"},"change_time":"2011-07-13T19:57:34Z"},{"changes":[{"removed":"","added":"luke@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/luke@mozilla.com","name":"luke@mozilla.com"},"change_time":"2011-07-13T18:53:42Z"},{"changes":[{"removed":"","added":"sphink@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/sphink@gmail.com","name":"sphink@gmail.com"},"change_time":"2011-07-13T18:23:30Z"},{"changes":[{"removed":"","added":"davemgarrett@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/davemgarrett@gmail.com","name":"davemgarrett@gmail.com"},"change_time":"2011-07-13T18:21:27Z"},{"changes":[{"removed":"","added":"khuey@kylehuey.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/khuey@kylehuey.com","name":"khuey@kylehuey.com"},"change_time":"2011-07-13T18:21:08Z"}],"summary":"Split chrome into multiple compartments for better accounting of JS memory used by chrome code (and add-ons)","last_change_time":"2011-07-19T20:37:46Z","comments":[{"is_private":false,"creator":{"real_name":"Ehsan Akhgari [:ehsan]","name":"ehsan"},"text":"","id":5599990,"creation_time":"2011-07-19T20:37:46Z"},{"is_private":false,"creator":{"real_name":"Gregor Wagner","name":"anygregor"},"text":"","id":5589947,"creation_time":"2011-07-13T21:04:28Z"},{"is_private":false,"creator":{"real_name":"Andreas Gal :gal","name":"gal"},"text":"","id":5589879,"creation_time":"2011-07-13T20:39:32Z"},{"is_private":false,"creator":{"real_name":"Gregor Wagner","name":"anygregor"},"text":"","id":5589812,"creation_time":"2011-07-13T20:07:18Z"},{"is_private":false,"creator":{"real_name":"Luke Wagner [:luke]","name":"luke"},"text":"","id":5589801,"creation_time":"2011-07-13T20:03:36Z"},{"is_private":false,"creator":{"real_name":"Gregor Wagner","name":"anygregor"},"text":"","id":5589787,"creation_time":"2011-07-13T19:57:34Z"},{"is_private":false,"creator":{"real_name":"Ehsan Akhgari [:ehsan]","name":"ehsan"},"text":"","id":5589485,"creation_time":"2011-07-13T18:15:17Z"}],"id":671352},"events":[{"time":"2011-07-19T20:37:46Z","changeset":{"changes":[{"removed":"","added":"ehsan@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ehsan@mozilla.com","name":"ehsan@mozilla.com"},"change_time":"2011-07-19T20:37:46Z"}},{"time":"2011-07-19T20:37:46Z","comment":{"is_private":false,"creator":{"real_name":"Ehsan Akhgari [:ehsan]","name":"ehsan"},"text":"","id":5599990,"creation_time":"2011-07-19T20:37:46Z"}},{"time":"2011-07-19T17:37:11Z","changeset":{"changes":[{"removed":"","added":"fayearthur+bugs@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2011-07-19T17:37:11Z"}},{"time":"2011-07-19T17:35:58Z","changeset":{"changes":[{"removed":"","added":"ctalbert@mozilla.com, jhammel@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2011-07-19T17:35:58Z"}},{"time":"2011-07-18T12:00:25Z","changeset":{"changes":[{"removed":"","added":"ted.mielczarek@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ted.mielczarek@gmail.com","name":"ted.mielczarek@gmail.com"},"change_time":"2011-07-18T12:00:25Z"}}]},{"bug":{"history":[{"changes":[{"attachment_id":"546836","removed":"text/x-python","added":"text/plain","field_name":"attachment.content_type"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-07-19T18:28:25Z"},{"changes":[{"removed":"","added":"ctalbert@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2011-07-19T17:58:20Z"}],"summary":"mozprofile should set permissions for profiles","last_change_time":"2011-07-19T18:28:25Z","comments":[{"is_private":false,"creator":{"real_name":"Joel Maher (:jmaher)","name":"jmaher"},"text":"","id":5599602,"creation_time":"2011-07-19T18:15:26Z"},{"is_private":false,"creator":{"real_name":"Joel Maher (:jmaher)","name":"jmaher"},"text":"","id":5599285,"creation_time":"2011-07-19T16:21:34Z"},{"is_private":false,"creator":{"real_name":"Joel Maher (:jmaher)","name":"jmaher"},"text":"","id":5554367,"creation_time":"2011-06-24T17:41:38Z"},{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":5552758,"creation_time":"2011-06-23T23:26:21Z"}],"id":666791},"events":[{"time":"2011-07-19T18:28:25Z","changeset":{"changes":[{"attachment_id":"546836","removed":"text/x-python","added":"text/plain","field_name":"attachment.content_type"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-07-19T18:28:25Z"}},{"time":"2011-07-19T18:15:26Z","comment":{"is_private":false,"creator":{"real_name":"Joel Maher (:jmaher)","name":"jmaher"},"text":"","id":5599602,"creation_time":"2011-07-19T18:15:26Z"}},{"time":"2011-07-19T17:58:20Z","changeset":{"changes":[{"removed":"","added":"ctalbert@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2011-07-19T17:58:20Z"}},{"time":"2011-07-19T16:21:34Z","comment":{"is_private":false,"creator":{"real_name":"Joel Maher (:jmaher)","name":"jmaher"},"text":"","id":5599285,"creation_time":"2011-07-19T16:21:34Z"}}]},{"bug":{"history":[{"changes":[{"removed":"NEW","added":"RESOLVED","field_name":"status"},{"removed":"","added":"FIXED","field_name":"resolution"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-07-19T17:57:44Z"},{"changes":[{"removed":"nobody@mozilla.org","added":"jhammel@mozilla.com","field_name":"assigned_to"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-07-19T15:39:01Z"},{"changes":[{"removed":"[mozmill-2.0?]","added":"[mozmill-2.0+]","field_name":"whiteboard"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-07-19T04:24:09Z"},{"changes":[{"attachment_id":"545960","removed":"review?(ctalbert@mozilla.com)","added":"review+","field_name":"flag"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2011-07-18T22:04:12Z"},{"changes":[{"attachment_id":"545960","removed":"","added":"review?(ctalbert@mozilla.com)","field_name":"flag"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-07-14T18:16:53Z"},{"changes":[{"removed":"","added":"ctalbert@mozilla.com, fayearthur+bugs@gmail.com","field_name":"cc"},{"removed":"","added":"[mozmill-2.0?]","field_name":"whiteboard"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-07-13T21:37:01Z"}],"summary":"mutt processhandler and mozprocess processhandler: to merge?","last_change_time":"2011-07-19T17:57:44Z","comments":[{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":5599538,"creation_time":"2011-07-19T17:57:44Z"},{"is_private":false,"creator":{"real_name":"Clint Talbert ( :ctalbert )","name":"ctalbert"},"text":"","id":5597782,"creation_time":"2011-07-18T22:04:12Z"},{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":5591772,"creation_time":"2011-07-14T18:16:53Z"},{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":5590023,"creation_time":"2011-07-13T21:35:42Z"}],"id":671420},"events":[{"time":"2011-07-19T17:57:44Z","changeset":{"changes":[{"removed":"NEW","added":"RESOLVED","field_name":"status"},{"removed":"","added":"FIXED","field_name":"resolution"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-07-19T17:57:44Z"}},{"time":"2011-07-19T17:57:44Z","comment":{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":5599538,"creation_time":"2011-07-19T17:57:44Z"}},{"time":"2011-07-19T15:39:01Z","changeset":{"changes":[{"removed":"nobody@mozilla.org","added":"jhammel@mozilla.com","field_name":"assigned_to"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-07-19T15:39:01Z"}},{"time":"2011-07-19T04:24:09Z","changeset":{"changes":[{"removed":"[mozmill-2.0?]","added":"[mozmill-2.0+]","field_name":"whiteboard"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-07-19T04:24:09Z"}},{"time":"2011-07-18T22:04:12Z","changeset":{"changes":[{"attachment_id":"545960","removed":"review?(ctalbert@mozilla.com)","added":"review+","field_name":"flag"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2011-07-18T22:04:12Z"}},{"time":"2011-07-18T22:04:12Z","comment":{"is_private":false,"creator":{"real_name":"Clint Talbert ( :ctalbert )","name":"ctalbert"},"text":"","id":5597782,"creation_time":"2011-07-18T22:04:12Z"}}]},{"bug":{"history":[{"changes":[{"attachment_id":"546171","removed":"review?(fayearthur+bugs@gmail.com)","added":"review+","field_name":"flag"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/fayearthur+bugs@gmail.com","name":"fayearthur+bugs@gmail.com"},"change_time":"2011-07-19T17:42:04Z"},{"changes":[{"attachment_id":"546171","removed":"","added":"review?(fayearthur+bugs@gmail.com), feedback?(halbersa@gmail.com)","field_name":"flag"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-07-15T16:21:21Z"},{"changes":[{"attachment_id":"545775","removed":"review?(fayearthur+bugs@gmail.com)","added":"review+","field_name":"flag"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/fayearthur+bugs@gmail.com","name":"fayearthur+bugs@gmail.com"},"change_time":"2011-07-14T19:40:03Z"},{"changes":[{"attachment_id":"545771","removed":"0","added":"1","field_name":"attachment.is_obsolete"},{"attachment_id":"545771","removed":"review?(fayearthur+bugs@gmail.com)","added":"","field_name":"flag"},{"attachment_id":"545775","removed":"","added":"review?(fayearthur+bugs@gmail.com)","field_name":"flag"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-07-13T23:19:12Z"},{"changes":[{"attachment_id":"545771","removed":"","added":"review?(fayearthur+bugs@gmail.com)","field_name":"flag"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-07-13T23:14:09Z"},{"changes":[{"attachment_id":"535258","removed":"0","added":"1","field_name":"attachment.is_obsolete"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-07-13T23:12:37Z"},{"changes":[{"removed":"","added":"661408","field_name":"blocks"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-06-28T23:56:05Z"},{"changes":[{"attachment_id":"535258","removed":"feedback?(hskupin@gmail.com)","added":"feedback-","field_name":"flag"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-05-26T07:48:02Z"},{"changes":[{"attachment_id":"535258","removed":"","added":"feedback?(hskupin@gmail.com)","field_name":"flag"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2011-05-26T02:57:15Z"},{"changes":[{"removed":"","added":"halbersa@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2011-05-26T02:48:33Z"},{"changes":[{"removed":"waitForPageLoad() fails if the document owner is not a tab but an HTMLDocument","added":"waitForPageLoad() fails if the document owner is not a tab but an HTMLDocument (add support for iframes)","field_name":"summary"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-05-25T23:56:38Z"},{"changes":[{"attachment_id":"534720","removed":"review?(ctalbert@mozilla.com)","added":"review+","field_name":"flag"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2011-05-25T23:50:33Z"},{"changes":[{"removed":"","added":"fayearthur+bugs@gmail.com","field_name":"cc"},{"removed":"[mozmill-1.5.4+]","added":"[mozmill-1.5.4+][mozmill-2.0+]","field_name":"whiteboard"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-05-25T12:08:34Z"},{"changes":[{"removed":"","added":"ctalbert@mozilla.com","field_name":"cc"},{"removed":"[mozmill-1.5.4?]","added":"[mozmill-1.5.4+]","field_name":"whiteboard"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2011-05-24T21:25:21Z"},{"changes":[{"attachment_id":"534714","removed":"0","added":"1","field_name":"attachment.is_obsolete"},{"attachment_id":"534714","removed":"review?(ctalbert@mozilla.com)","added":"","field_name":"flag"},{"attachment_id":"534720","removed":"","added":"review?(ctalbert@mozilla.com)","field_name":"flag"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-05-24T11:15:13Z"},{"changes":[{"attachment_id":"534445","removed":"0","added":"1","field_name":"attachment.is_obsolete"},{"attachment_id":"534714","removed":"","added":"review?(ctalbert@mozilla.com)","field_name":"flag"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-05-24T10:59:06Z"},{"changes":[{"removed":"waitForPageLoad() fails for Discovery Pane with 'Specified tab hasn't been found.'","added":"waitForPageLoad() fails if the document owner is not a tab but an HTMLDocument","field_name":"summary"},{"removed":"","added":"in-testsuite?","field_name":"flag"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-05-24T10:53:16Z"},{"changes":[{"removed":"waitForPageLoad() fails for iFrames with 'Specified tab hasn't been found.'","added":"waitForPageLoad() fails for Discovery Pane with 'Specified tab hasn't been found.'","field_name":"summary"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-05-24T00:22:33Z"},{"changes":[{"removed":"","added":"[mozmill-1.5.4?]","field_name":"whiteboard"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-05-23T21:33:23Z"},{"changes":[{"removed":"","added":"regression","field_name":"keywords"},{"removed":"NEW","added":"ASSIGNED","field_name":"status"},{"removed":"x86_64","added":"All","field_name":"platform"},{"removed":"","added":"604878","field_name":"blocks"},{"removed":"nobody@mozilla.org","added":"hskupin@gmail.com","field_name":"assigned_to"},{"removed":"waitForPageLoad() does not handle iframes","added":"waitForPageLoad() fails for iFrames with 'Specified tab hasn't been found.'","field_name":"summary"},{"removed":"Linux","added":"All","field_name":"op_sys"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-05-23T15:59:53Z"},{"changes":[{"removed":"","added":"alex.lakatos@softvision.ro, hskupin@gmail.com, vlad.maniac@softvision.ro","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/vlad.maniac@softvision.ro","name":"vlad.maniac@softvision.ro"},"change_time":"2011-05-23T15:24:49Z"}],"summary":"waitForPageLoad() fails if the document owner is not a tab but an HTMLDocument (add support for iframes)","last_change_time":"2011-07-19T17:42:04Z","comments":[{"is_private":false,"creator":{"real_name":"Heather Arthur [:harth]","name":"fayearthur+bugs"},"text":"","id":5599498,"creation_time":"2011-07-19T17:42:04Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5593624,"creation_time":"2011-07-15T16:21:21Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5592472,"creation_time":"2011-07-14T22:38:58Z"},{"is_private":false,"creator":{"real_name":"Heather Arthur [:harth]","name":"fayearthur+bugs"},"text":"","id":5591961,"creation_time":"2011-07-14T19:40:03Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5590242,"creation_time":"2011-07-13T23:19:12Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5590234,"creation_time":"2011-07-13T23:14:09Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5590231,"creation_time":"2011-07-13T23:12:37Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5494401,"creation_time":"2011-05-26T07:48:02Z"},{"is_private":false,"creator":{"real_name":"Clint Talbert ( :ctalbert )","name":"ctalbert"},"text":"","id":5494130,"creation_time":"2011-05-26T02:58:56Z"},{"is_private":false,"creator":{"real_name":"Clint Talbert ( :ctalbert )","name":"ctalbert"},"text":"","id":5494127,"creation_time":"2011-05-26T02:57:15Z"},{"is_private":false,"creator":{"real_name":"Clint Talbert ( :ctalbert )","name":"ctalbert"},"text":"","id":5493838,"creation_time":"2011-05-25T23:50:33Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5491980,"creation_time":"2011-05-25T12:08:34Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5488829,"creation_time":"2011-05-24T11:18:12Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5488828,"creation_time":"2011-05-24T11:15:13Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5488811,"creation_time":"2011-05-24T10:59:06Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5488807,"creation_time":"2011-05-24T10:53:16Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5488698,"creation_time":"2011-05-24T09:37:36Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5488122,"creation_time":"2011-05-24T00:22:33Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5487989,"creation_time":"2011-05-23T23:17:41Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5487883,"creation_time":"2011-05-23T22:39:31Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5487655,"creation_time":"2011-05-23T21:33:23Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5486617,"creation_time":"2011-05-23T15:59:53Z"},{"is_private":false,"creator":{"real_name":"Maniac Vlad Florin (:vladmaniac)","name":"vlad.maniac"},"text":"","id":5486535,"creation_time":"2011-05-23T15:23:48Z"}],"id":659000},"events":[{"time":"2011-07-19T17:42:04Z","changeset":{"changes":[{"attachment_id":"546171","removed":"review?(fayearthur+bugs@gmail.com)","added":"review+","field_name":"flag"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/fayearthur+bugs@gmail.com","name":"fayearthur+bugs@gmail.com"},"change_time":"2011-07-19T17:42:04Z"}},{"time":"2011-07-19T17:42:04Z","comment":{"is_private":false,"creator":{"real_name":"Heather Arthur [:harth]","name":"fayearthur+bugs"},"text":"","id":5599498,"creation_time":"2011-07-19T17:42:04Z"}}]},{"bug":{"history":[],"summary":"QA Companion marked incompatible for Firefox versions higher than 6.*.","last_change_time":"2011-07-18T23:00:06Z","comments":[{"is_private":false,"creator":{"real_name":"Al Billings [:abillings]","name":"abillings"},"text":"","id":5597976,"creation_time":"2011-07-18T23:00:06Z"}],"id":672393},"events":[{"time":"2011-07-18T23:00:06Z","comment":{"is_private":false,"creator":{"real_name":"Al Billings [:abillings]","name":"abillings"},"text":"","id":5597976,"creation_time":"2011-07-18T23:00:06Z"}}]},{"bug":{"history":[{"changes":[{"removed":"","added":"khuey@kylehuey.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/khuey@kylehuey.com","name":"khuey@kylehuey.com"},"change_time":"2011-07-18T20:35:37Z"},{"changes":[{"removed":"","added":"kairo@kairo.at","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/kairo@kairo.at","name":"kairo@kairo.at"},"change_time":"2010-04-05T16:18:55Z"},{"changes":[{"removed":"","added":"mook.moz+mozbz@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/mook@songbirdnest.com","name":"mook@songbirdnest.com"},"change_time":"2010-03-16T21:34:23Z"},{"changes":[{"removed":"","added":"harthur@cmu.edu","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/fayearthur+bugs@gmail.com","name":"fayearthur+bugs@gmail.com"},"change_time":"2010-03-16T04:31:00Z"},{"changes":[{"removed":"","added":"ted.mielczarek@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ted.mielczarek@gmail.com","name":"ted.mielczarek@gmail.com"},"change_time":"2010-03-15T23:52:39Z"},{"changes":[{"removed":"","added":"552533","field_name":"depends_on"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/dwitte@gmail.com","name":"dwitte@gmail.com"},"change_time":"2010-03-15T22:02:20Z"},{"changes":[{"removed":"","added":"447581","field_name":"blocks"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/dietrich@mozilla.com","name":"dietrich@mozilla.com"},"change_time":"2010-03-02T23:57:25Z"},{"changes":[{"removed":"412531","added":"","field_name":"blocks"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/dietrich@mozilla.com","name":"dietrich@mozilla.com"},"change_time":"2009-12-11T02:49:08Z"},{"changes":[{"removed":"","added":"Pidgeot18@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/Pidgeot18@gmail.com","name":"Pidgeot18@gmail.com"},"change_time":"2009-10-26T14:47:22Z"},{"changes":[{"removed":"js-ctypes","added":"js-ctypes","field_name":"component"},{"removed":"Trunk","added":"unspecified","field_name":"version"},{"removed":"Other Applications","added":"Core","field_name":"product"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/mozillamarcia.knous@gmail.com","name":"mozillamarcia.knous@gmail.com"},"change_time":"2009-10-02T10:17:18Z"},{"changes":[{"removed":"","added":"jwalden+bmo@mit.edu","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jwalden+bmo@mit.edu","name":"jwalden+bmo@mit.edu"},"change_time":"2009-08-17T19:11:28Z"},{"changes":[{"removed":"","added":"ajvincent@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ajvincent@gmail.com","name":"ajvincent@gmail.com"},"change_time":"2009-08-13T00:28:01Z"},{"changes":[{"removed":"","added":"benjamin@smedbergs.us","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/benjamin@smedbergs.us","name":"benjamin@smedbergs.us"},"change_time":"2009-07-23T03:14:04Z"},{"changes":[{"removed":"","added":"highmind63@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/highmind63@gmail.com","name":"highmind63@gmail.com"},"change_time":"2009-07-23T01:02:51Z"},{"changes":[{"removed":"","added":"shaver@mozilla.org","field_name":"cc"},{"removed":"JavaScript Engine","added":"js-ctypes","field_name":"component"},{"removed":"general@js.bugs","added":"nobody@mozilla.org","field_name":"assigned_to"},{"removed":"Core","added":"Other Applications","field_name":"product"},{"removed":"add a pinvoke-like method to js","added":"Support C++ calling from JSctypes","field_name":"summary"},{"removed":"general@spidermonkey.bugs","added":"js-ctypes@otherapps.bugs","field_name":"qa_contact"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/shaver@mozilla.org","name":"shaver@mozilla.org"},"change_time":"2009-07-23T00:55:48Z"},{"changes":[{"removed":"","added":"ryanvm@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ryanvm@gmail.com","name":"ryanvm@gmail.com"},"change_time":"2009-07-23T00:34:18Z"},{"changes":[{"removed":"","added":"412531","field_name":"blocks"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/dietrich@mozilla.com","name":"dietrich@mozilla.com"},"change_time":"2009-07-23T00:06:41Z"},{"changes":[{"removed":"","added":"dvander@alliedmods.net","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/dvander@alliedmods.net","name":"dvander@alliedmods.net"},"change_time":"2009-07-23T00:03:46Z"},{"changes":[{"removed":"","added":"dietrich@mozilla.com, mark.finkle@gmail.com","field_name":"cc"},{"removed":"","added":"[ts][tsnap]","field_name":"whiteboard"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/dietrich@mozilla.com","name":"dietrich@mozilla.com"},"change_time":"2009-07-22T23:58:23Z"}],"summary":"Support C++ calling from JSctypes","last_change_time":"2011-07-18T20:35:37Z","comments":[{"is_private":false,"creator":{"real_name":"Ted Mielczarek [:ted, :luser]","name":"ted.mielczarek"},"text":"","id":4586274,"creation_time":"2010-03-15T23:52:39Z"},{"is_private":false,"creator":{"real_name":"Dan Witte (:dwitte)","name":"dwitte"},"text":"","id":4586028,"creation_time":"2010-03-15T22:21:21Z"},{"is_private":false,"creator":{"real_name":"Dan Witte (:dwitte)","name":"dwitte"},"text":"","id":4586006,"creation_time":"2010-03-15T22:11:11Z"},{"is_private":false,"creator":{"real_name":"Joshua Cranmer [:jcranmer]","name":"Pidgeot18"},"text":"","id":4364260,"creation_time":"2009-10-26T18:33:21Z"},{"is_private":false,"creator":{"real_name":"Dan Witte (:dwitte)","name":"dwitte"},"text":"","id":4364119,"creation_time":"2009-10-26T17:27:05Z"},{"is_private":false,"creator":{"real_name":"Joshua Cranmer [:jcranmer]","name":"Pidgeot18"},"text":"","id":4363794,"creation_time":"2009-10-26T14:47:22Z"},{"is_private":false,"creator":{"real_name":"Benjamin Smedberg [:bsmedberg]","name":"benjamin"},"text":"","id":4211892,"creation_time":"2009-07-23T03:14:04Z"},{"is_private":false,"creator":{"real_name":"Mike Shaver (:shaver)","name":"shaver"},"text":"","id":4211710,"creation_time":"2009-07-23T00:56:10Z"},{"is_private":false,"creator":{"real_name":"Taras Glek (:taras)","name":"tglek"},"text":"","id":4211707,"creation_time":"2009-07-23T00:54:06Z"},{"is_private":false,"creator":{"real_name":"Mike Shaver (:shaver)","name":"shaver"},"text":"","id":4211677,"creation_time":"2009-07-23T00:42:01Z"},{"is_private":false,"creator":{"real_name":"Taras Glek (:taras)","name":"tglek"},"text":"","id":4211672,"creation_time":"2009-07-23T00:40:10Z"},{"is_private":false,"creator":{"real_name":"Mike Shaver (:shaver)","name":"shaver"},"text":"","id":4211668,"creation_time":"2009-07-23T00:38:34Z"},{"is_private":false,"creator":{"real_name":"Taras Glek (:taras)","name":"tglek"},"text":"","id":4211665,"creation_time":"2009-07-23T00:36:58Z"},{"is_private":false,"creator":{"real_name":"Mike Shaver (:shaver)","name":"shaver"},"text":"","id":4211660,"creation_time":"2009-07-23T00:34:27Z"},{"is_private":false,"creator":{"real_name":"Taras Glek (:taras)","name":"tglek"},"text":"","id":4211624,"creation_time":"2009-07-23T00:15:38Z"},{"is_private":false,"creator":{"real_name":"Mike Shaver (:shaver)","name":"shaver"},"text":"","id":4211608,"creation_time":"2009-07-23T00:08:16Z"},{"is_private":false,"creator":{"real_name":"Taras Glek (:taras)","name":"tglek"},"text":"","id":4211586,"creation_time":"2009-07-22T23:57:14Z"}],"id":505907},"events":[{"time":"2011-07-18T20:35:37Z","changeset":{"changes":[{"removed":"","added":"khuey@kylehuey.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/khuey@kylehuey.com","name":"khuey@kylehuey.com"},"change_time":"2011-07-18T20:35:37Z"}}]},{"bug":{"history":[{"changes":[{"removed":"","added":"bmo@edmorley.co.uk","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/bmo@edmorley.co.uk","name":"bmo@edmorley.co.uk"},"change_time":"2011-07-10T14:30:39Z"},{"changes":[{"removed":"","added":"bear@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/bear@mozilla.com","name":"bear@mozilla.com"},"change_time":"2011-07-07T21:21:56Z"},{"changes":[{"removed":"","added":"ctalbert@mozilla.com, fayearthur+bugs@gmail.com, sliu@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2011-07-07T21:19:46Z"},{"changes":[{"removed":"","added":"armenzg@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/armenzg@mozilla.com","name":"armenzg@mozilla.com"},"change_time":"2011-07-07T17:04:41Z"}],"summary":"Publish Build Faster metrics","last_change_time":"2011-07-18T20:25:40Z","comments":[{"is_private":false,"creator":{"real_name":"Chris AtLee [:catlee]","name":"catlee"},"text":"","id":5597430,"creation_time":"2011-07-18T20:25:40Z"},{"is_private":false,"creator":{"real_name":"Sam Liu","name":"sliu"},"text":"","id":5597428,"creation_time":"2011-07-18T20:24:35Z"},{"is_private":false,"creator":{"real_name":"Chris AtLee [:catlee]","name":"catlee"},"text":"","id":5597393,"creation_time":"2011-07-18T20:10:45Z"},{"is_private":false,"creator":{"real_name":"Sam Liu","name":"sliu"},"text":"","id":5589839,"creation_time":"2011-07-13T20:20:49Z"},{"is_private":false,"creator":{"real_name":"Chris AtLee [:catlee]","name":"catlee"},"text":"","id":5582189,"creation_time":"2011-07-09T01:25:21Z"},{"is_private":false,"creator":{"real_name":"Sam Liu","name":"sliu"},"text":"","id":5581446,"creation_time":"2011-07-08T18:25:33Z"},{"is_private":false,"creator":{"real_name":"Armen Zambrano G. [:armenzg] - Release Engineer","name":"armenzg"},"text":"","id":5580728,"creation_time":"2011-07-08T12:36:01Z"},{"is_private":false,"creator":{"real_name":"Chris AtLee [:catlee]","name":"catlee"},"text":"","id":5580206,"creation_time":"2011-07-08T03:21:41Z"},{"is_private":false,"creator":{"real_name":"Sam Liu","name":"sliu"},"text":"","id":5579985,"creation_time":"2011-07-08T00:01:37Z"},{"is_private":false,"creator":{"real_name":"Chris AtLee [:catlee]","name":"catlee"},"text":"","id":5579980,"creation_time":"2011-07-07T23:57:48Z"},{"is_private":false,"creator":{"real_name":"Sam Liu","name":"sliu"},"text":"","id":5579965,"creation_time":"2011-07-07T23:51:02Z"},{"is_private":false,"creator":{"real_name":"Chris AtLee [:catlee]","name":"catlee"},"text":"","id":5579631,"creation_time":"2011-07-07T21:32:17Z"},{"is_private":false,"creator":{"real_name":"Mike Taylor [:bear]","name":"bear"},"text":"","id":5579612,"creation_time":"2011-07-07T21:21:56Z"},{"is_private":false,"creator":{"real_name":"Clint Talbert ( :ctalbert )","name":"ctalbert"},"text":"","id":5579605,"creation_time":"2011-07-07T21:19:46Z"},{"is_private":false,"creator":{"real_name":"Chris AtLee [:catlee]","name":"catlee"},"text":"","id":5578932,"creation_time":"2011-07-07T17:02:45Z"}],"id":669930},"events":[{"time":"2011-07-18T20:25:40Z","comment":{"is_private":false,"creator":{"real_name":"Chris AtLee [:catlee]","name":"catlee"},"text":"","id":5597430,"creation_time":"2011-07-18T20:25:40Z"}},{"time":"2011-07-18T20:24:35Z","comment":{"is_private":false,"creator":{"real_name":"Sam Liu","name":"sliu"},"text":"","id":5597428,"creation_time":"2011-07-18T20:24:35Z"}},{"time":"2011-07-18T20:10:45Z","comment":{"is_private":false,"creator":{"real_name":"Chris AtLee [:catlee]","name":"catlee"},"text":"","id":5597393,"creation_time":"2011-07-18T20:10:45Z"}}]},{"bug":{"history":[{"changes":[{"removed":"","added":"jwatt@jwatt.org","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jwatt@jwatt.org","name":"jwatt@jwatt.org"},"change_time":"2011-07-18T12:06:33Z"},{"changes":[{"removed":"","added":"taku.eof@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/taku.eof@gmail.com","name":"taku.eof@gmail.com"},"change_time":"2011-06-27T04:50:23Z"},{"changes":[{"removed":"","added":"jeff@stikman.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jeff@stikman.com","name":"jeff@stikman.com"},"change_time":"2011-05-30T15:15:06Z"},{"changes":[{"removed":"","added":"spencerselander@yahoo.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/spencerselander@yahoo.com","name":"spencerselander@yahoo.com"},"change_time":"2011-05-18T07:58:37Z"},{"changes":[{"removed":"VanillaMozilla@hotmail.com","added":"","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/VanillaMozilla@hotmail.com","name":"VanillaMozilla@hotmail.com"},"change_time":"2011-05-17T02:57:17Z"},{"changes":[{"removed":"","added":"mcdavis941.bugs@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/mcdavis941.bugs@gmail.com","name":"mcdavis941.bugs@gmail.com"},"change_time":"2011-05-16T21:08:18Z"},{"changes":[{"removed":"beltzner@mozilla.com","added":"","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/mbeltzner@gmail.com","name":"mbeltzner@gmail.com"},"change_time":"2011-04-02T00:17:26Z"},{"changes":[{"removed":"","added":"cbaker@customsoftwareconsult.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/cbaker@customsoftwareconsult.com","name":"cbaker@customsoftwareconsult.com"},"change_time":"2011-03-28T14:08:35Z"},{"changes":[{"removed":"","added":"SGrage@gmx.net","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/SGrage@gmx.net","name":"SGrage@gmx.net"},"change_time":"2011-03-28T10:13:51Z"},{"changes":[{"removed":"","added":"dewmigg@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/dewmigg@gmail.com","name":"dewmigg@gmail.com"},"change_time":"2011-03-13T21:34:21Z"},{"changes":[{"removed":"","added":"ulysse@email.it","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/euryalus.0@gmail.com","name":"euryalus.0@gmail.com"},"change_time":"2011-03-13T12:14:04Z"},{"changes":[{"removed":"","added":"heraldo99@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/heraldo99@gmail.com","name":"heraldo99@gmail.com"},"change_time":"2011-03-13T11:36:08Z"},{"changes":[{"removed":"","added":"pardal@gmx.de","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/pardal@gmx.de","name":"pardal@gmx.de"},"change_time":"2011-03-13T02:48:03Z"},{"changes":[{"removed":"","added":"terrell.kelley@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/terrell.kelley@gmail.com","name":"terrell.kelley@gmail.com"},"change_time":"2011-01-31T11:46:28Z"},{"changes":[{"removed":"","added":"kiuzeppe@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/kiuzeppe@gmail.com","name":"kiuzeppe@gmail.com"},"change_time":"2011-01-20T22:13:01Z"},{"changes":[{"removed":"","added":"saneyuki.snyk@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/saneyuki.s.snyk@gmail.com","name":"saneyuki.s.snyk@gmail.com"},"change_time":"2011-01-11T09:25:01Z"},{"changes":[{"removed":"","added":"stephen.donner@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/stephen.donner@gmail.com","name":"stephen.donner@gmail.com"},"change_time":"2011-01-04T21:07:17Z"},{"changes":[{"removed":"","added":"prog_when_resolved_notification@bluebottle.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/prog_when_resolved_notification@bluebottle.com","name":"prog_when_resolved_notification@bluebottle.com"},"change_time":"2011-01-02T18:11:00Z"},{"changes":[{"removed":"jmjeffery@embarqmail.com","added":"","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jmjeffery@embarqmail.com","name":"jmjeffery@embarqmail.com"},"change_time":"2010-12-14T18:17:14Z"},{"changes":[{"removed":"","added":"colmeiro@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/colmeiro@gmail.com","name":"colmeiro@gmail.com"},"change_time":"2010-12-10T23:46:15Z"},{"changes":[{"removed":"","added":"willyaranda@mozilla-hispano.org","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/willyaranda@mozilla-hispano.org","name":"willyaranda@mozilla-hispano.org"},"change_time":"2010-12-10T22:34:30Z"},{"changes":[{"removed":"","added":"dale.bugzilla@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/dale.bugzilla@gmail.com","name":"dale.bugzilla@gmail.com"},"change_time":"2010-12-10T00:50:10Z"},{"changes":[{"removed":"","added":"sabret00the@yahoo.co.uk","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/sabret00the@yahoo.co.uk","name":"sabret00the@yahoo.co.uk"},"change_time":"2010-12-09T17:01:15Z"},{"changes":[{"removed":"","added":"daveryeo@telus.net","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/daveryeo@telus.net","name":"daveryeo@telus.net"},"change_time":"2010-12-09T04:46:59Z"},{"changes":[{"removed":"","added":"bugspam.Callek@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/bugspam.Callek@gmail.com","name":"bugspam.Callek@gmail.com"},"change_time":"2010-12-09T01:05:37Z"},{"changes":[{"removed":"","added":"mcepl@redhat.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/mcepl@redhat.com","name":"mcepl@redhat.com"},"change_time":"2010-12-08T23:55:49Z"},{"changes":[{"removed":"","added":"tymerkaev@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/tymerkaev@gmail.com","name":"tymerkaev@gmail.com"},"change_time":"2010-11-29T10:19:49Z"},{"changes":[{"removed":"","added":"kwierso@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/kwierso@gmail.com","name":"kwierso@gmail.com"},"change_time":"2010-11-29T05:42:05Z"},{"changes":[{"removed":"","added":"mtanalin@yandex.ru","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/mtanalin@yandex.ru","name":"mtanalin@yandex.ru"},"change_time":"2010-11-15T22:33:18Z"},{"changes":[{"removed":"","added":"briks.si@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/briks.si@gmail.com","name":"briks.si@gmail.com"},"change_time":"2010-11-15T20:45:37Z"},{"changes":[{"removed":"","added":"mozdiav@aeons.lv","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/mozdiav@aeons.lv","name":"mozdiav@aeons.lv"},"change_time":"2010-11-12T20:25:54Z"},{"changes":[{"removed":"","added":"soufian.j@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/soufian.j@gmail.com","name":"soufian.j@gmail.com"},"change_time":"2010-11-12T16:43:16Z"},{"changes":[{"removed":"","added":"jmjeffery@embarqmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jmjeffery@embarqmail.com","name":"jmjeffery@embarqmail.com"},"change_time":"2010-11-12T12:59:58Z"},{"changes":[{"removed":"","added":"markc@qsiuk.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/markc@qsiuk.com","name":"markc@qsiuk.com"},"change_time":"2010-11-12T12:14:42Z"},{"changes":[{"removed":"","added":"thibaut.bethune@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/thibaut.bethune@gmail.com","name":"thibaut.bethune@gmail.com"},"change_time":"2010-11-11T22:54:55Z"},{"changes":[{"removed":"","added":"gmontagu@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/gmontagu@gmail.com","name":"gmontagu@gmail.com"},"change_time":"2010-11-11T22:45:52Z"},{"changes":[{"removed":"","added":"KenSaunders@AccessFirefox.org","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/KenSaunders@AccessFirefox.org","name":"KenSaunders@AccessFirefox.org"},"change_time":"2010-11-11T19:26:57Z"},{"changes":[{"removed":"","added":"lists@eitanadler.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/lists@eitanadler.com","name":"lists@eitanadler.com"},"change_time":"2010-11-11T18:07:05Z"},{"changes":[{"removed":"","added":"geekshadow@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/geekshadow@gmail.com","name":"geekshadow@gmail.com"},"change_time":"2010-10-20T09:22:55Z"},{"changes":[{"removed":"","added":"webmaster@keryx.se","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/webmaster@keryx.se","name":"webmaster@keryx.se"},"change_time":"2010-10-20T09:06:57Z"},{"changes":[{"removed":"","added":"bugzilla@zirro.se","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/bugzilla@zirro.se","name":"bugzilla@zirro.se"},"change_time":"2010-10-20T09:03:47Z"},{"changes":[{"removed":"","added":"supernova00@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/supernova00@gmail.com","name":"supernova00@gmail.com"},"change_time":"2010-10-19T21:35:02Z"},{"changes":[{"removed":"","added":"beltzner@mozilla.com, dtownsend@mozilla.com, jgriffin@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jgriffin@mozilla.com","name":"jgriffin@mozilla.com"},"change_time":"2010-10-08T22:07:52Z"},{"changes":[{"removed":"","added":"gavin.sharp@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/gavin.sharp@gmail.com","name":"gavin.sharp@gmail.com"},"change_time":"2010-09-24T18:44:50Z"},{"changes":[{"removed":"","added":"omarb.public@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/omarb.public@gmail.com","name":"omarb.public@gmail.com"},"change_time":"2010-09-06T18:12:39Z"},{"changes":[{"removed":"","added":"David.Vo2+bmo@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/auscompgeek@sumovolunteers.org","name":"auscompgeek@sumovolunteers.org"},"change_time":"2010-09-03T02:57:43Z"},{"changes":[{"removed":"","added":"mcs@pearlcrescent.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/mcs@pearlcrescent.com","name":"mcs@pearlcrescent.com"},"change_time":"2010-09-02T02:33:22Z"},{"changes":[{"removed":"","added":"archaeopteryx@coole-files.de","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/archaeopteryx@coole-files.de","name":"archaeopteryx@coole-files.de"},"change_time":"2010-09-01T09:17:01Z"},{"changes":[{"removed":"","added":"antoine.mechelynck@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/antoine.mechelynck@gmail.com","name":"antoine.mechelynck@gmail.com"},"change_time":"2010-09-01T03:05:29Z"},{"changes":[{"removed":"","added":"m-wada@japan.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/m-wada@japan.com","name":"m-wada@japan.com"},"change_time":"2010-08-31T09:53:39Z"},{"changes":[{"removed":"","added":"bugzilla@standard8.plus.com, ludovic@mozillamessaging.com, vseerror@lehigh.edu","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ludovic@mozilla.com","name":"ludovic@mozilla.com"},"change_time":"2010-08-31T06:10:50Z"},{"changes":[{"removed":"","added":"grenavitar@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/grenavitar@gmail.com","name":"grenavitar@gmail.com"},"change_time":"2010-08-31T03:59:31Z"},{"changes":[{"removed":"","added":"jorge@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jorge@mozilla.com","name":"jorge@mozilla.com"},"change_time":"2010-08-30T18:18:56Z"},{"changes":[{"removed":"","added":"ehsan@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ehsan@mozilla.com","name":"ehsan@mozilla.com"},"change_time":"2010-08-30T18:09:28Z"},{"changes":[{"removed":"","added":"tyler.downer@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/tyler.downer@gmail.com","name":"tyler.downer@gmail.com"},"change_time":"2010-08-30T17:40:02Z"},{"changes":[{"removed":"","added":"asqueella@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/asqueella@gmail.com","name":"asqueella@gmail.com"},"change_time":"2010-08-26T20:47:46Z"},{"changes":[{"removed":"","added":"590788","field_name":"depends_on"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2010-08-26T15:49:52Z"},{"changes":[{"removed":"","added":"benjamin@smedbergs.us","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2010-08-26T08:24:38Z"},{"changes":[{"removed":"","added":"bugs-bmo@unknownbrackets.org","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/bugs-bmo@unknownbrackets.org","name":"bugs-bmo@unknownbrackets.org"},"change_time":"2010-08-26T07:24:55Z"},{"changes":[{"removed":"","added":"highmind63@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/highmind63@gmail.com","name":"highmind63@gmail.com"},"change_time":"2010-08-26T03:45:34Z"},{"changes":[{"removed":"stream@abv.bg","added":"","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/stream@abv.bg","name":"stream@abv.bg"},"change_time":"2010-08-24T16:50:19Z"},{"changes":[{"removed":"Infrastructure","added":"ProfileManager","field_name":"component"},{"removed":"jhammel@mozilla.com","added":"nobody@mozilla.org","field_name":"assigned_to"},{"removed":"infra@testing.bugs","added":"profilemanager@testing.bugs","field_name":"qa_contact"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2010-08-24T15:27:00Z"},{"changes":[{"removed":"","added":"justin.lebar+bug@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/justin.lebar+bug@gmail.com","name":"justin.lebar+bug@gmail.com"},"change_time":"2010-07-28T21:26:38Z"},{"changes":[{"removed":"mozilla.bugs@alyoung.com","added":"","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/mozilla.bugs@alyoung.com","name":"mozilla.bugs@alyoung.com"},"change_time":"2010-07-22T23:59:31Z"},{"changes":[{"removed":"","added":"deletesoftware@yandex.ru","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/deletesoftware@yandex.ru","name":"deletesoftware@yandex.ru"},"change_time":"2010-06-19T14:19:55Z"},{"changes":[{"removed":"NEW","added":"ASSIGNED","field_name":"status"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2010-06-14T17:26:17Z"},{"changes":[{"removed":"nobody@mozilla.org","added":"jhammel@mozilla.com","field_name":"assigned_to"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2010-05-27T15:42:33Z"},{"changes":[{"removed":"","added":"harthur@cmu.edu","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2010-03-18T20:49:42Z"},{"changes":[{"removed":"","added":"k0scist@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2010-03-09T17:10:48Z"},{"changes":[{"removed":"","added":"stream@abv.bg","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/stream@abv.bg","name":"stream@abv.bg"},"change_time":"2010-02-07T01:32:42Z"},{"changes":[{"removed":"","added":"VanillaMozilla@hotmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/VanillaMozilla@hotmail.com","name":"VanillaMozilla@hotmail.com"},"change_time":"2010-01-22T17:28:59Z"},{"changes":[{"removed":"","added":"reed@reedloden.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/reed@reedloden.com","name":"reed@reedloden.com"},"change_time":"2010-01-17T19:22:31Z"},{"changes":[{"removed":"","added":"blizzard@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/blizzard@mozilla.com","name":"blizzard@mozilla.com"},"change_time":"2010-01-17T01:12:28Z"},{"changes":[{"removed":"","added":"540194","field_name":"blocks"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/david@rossde.com","name":"david@rossde.com"},"change_time":"2010-01-16T19:18:12Z"},{"changes":[{"removed":"","added":"fullmetaljacket.xp+bugmail@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/fullmetaljacket.xp+bugmail@gmail.com","name":"fullmetaljacket.xp+bugmail@gmail.com"},"change_time":"2010-01-15T09:22:52Z"},{"changes":[{"removed":"","added":"wladow@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/wladow@gmail.com","name":"wladow@gmail.com"},"change_time":"2010-01-14T22:31:39Z"},{"changes":[{"removed":"278860","added":"","field_name":"depends_on"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2010-01-14T17:46:11Z"},{"changes":[{"removed":"","added":"chado_moz@yahoo.co.jp","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/chado_moz@yahoo.co.jp","name":"chado_moz@yahoo.co.jp"},"change_time":"2010-01-14T15:30:57Z"},{"changes":[{"removed":"","added":"mnyromyr@tprac.de","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/mnyromyr@tprac.de","name":"mnyromyr@tprac.de"},"change_time":"2010-01-14T10:56:22Z"},{"changes":[{"removed":"","added":"spitfire.kuden@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/spitfire.kuden@gmail.com","name":"spitfire.kuden@gmail.com"},"change_time":"2010-01-14T10:15:48Z"},{"changes":[{"removed":"","added":"mozilla.bugs@alyoung.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/mozilla.bugs@alyoung.com","name":"mozilla.bugs@alyoung.com"},"change_time":"2010-01-14T05:57:27Z"},{"changes":[{"removed":"","added":"nori@noasobi.net","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/nori@noasobi.net","name":"nori@noasobi.net"},"change_time":"2010-01-14T05:41:47Z"},{"changes":[{"removed":"","added":"michaelkohler@live.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/michaelkohler@linux.com","name":"michaelkohler@linux.com"},"change_time":"2010-01-14T01:39:36Z"},{"changes":[{"removed":"","added":"kairo@kairo.at","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/kairo@kairo.at","name":"kairo@kairo.at"},"change_time":"2010-01-14T00:16:58Z"},{"changes":[{"removed":"normal","added":"enhancement","field_name":"severity"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/david@rossde.com","name":"david@rossde.com"},"change_time":"2010-01-14T00:08:41Z"},{"changes":[{"removed":"","added":"ryanvm@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ryanvm@gmail.com","name":"ryanvm@gmail.com"},"change_time":"2010-01-14T00:02:33Z"},{"changes":[{"removed":"","added":"phiw@l-c-n.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/phiw@l-c-n.com","name":"phiw@l-c-n.com"},"change_time":"2010-01-13T23:56:59Z"},{"changes":[{"removed":"","added":"pcvrcek@mozilla.cz","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/pcvrcek@mozilla.cz","name":"pcvrcek@mozilla.cz"},"change_time":"2010-01-13T23:45:00Z"},{"changes":[{"removed":"","added":"axel@pike.org","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/l10n@mozilla.com","name":"l10n@mozilla.com"},"change_time":"2010-01-13T23:17:08Z"},{"changes":[{"removed":"","added":"stefanh@inbox.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/stefanh@inbox.com","name":"stefanh@inbox.com"},"change_time":"2010-01-13T23:12:13Z"},{"changes":[{"removed":"x86","added":"All","field_name":"platform"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2010-01-13T23:06:25Z"},{"changes":[{"removed":"","added":"johnath@mozilla.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/johnath@mozilla.com","name":"johnath@mozilla.com"},"change_time":"2010-01-13T23:04:10Z"},{"changes":[{"removed":"","added":"mrmazda@earthlink.net","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/mrmazda@earthlink.net","name":"mrmazda@earthlink.net"},"change_time":"2010-01-13T22:46:40Z"},{"changes":[{"removed":"","added":"iann_bugzilla@blueyonder.co.uk","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/iann_bugzilla@blueyonder.co.uk","name":"iann_bugzilla@blueyonder.co.uk"},"change_time":"2010-01-13T21:41:32Z"},{"changes":[{"removed":"","added":"xtc4uall@gmail.com","field_name":"cc"},{"removed":"","added":"214675","field_name":"blocks"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/xtc4uall@gmail.com","name":"xtc4uall@gmail.com"},"change_time":"2010-01-13T21:34:20Z"},{"changes":[{"removed":"","added":"hskupin@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2010-01-13T21:31:14Z"},{"changes":[{"removed":"","added":"hickendorffbas@gmail.com","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hickendorffbas@gmail.com","name":"hickendorffbas@gmail.com"},"change_time":"2010-01-13T20:58:31Z"}],"summary":"New Test/Triage Profile Manager Application","last_change_time":"2011-07-18T12:06:33Z","comments":[{"is_private":false,"creator":{"real_name":"Jonathan Griffin (:jgriffin)","name":"jgriffin"},"text":"","id":5480531,"creation_time":"2011-05-19T17:30:57Z"},{"is_private":false,"creator":{"real_name":"Spencer Selander [greenknight]","name":"spencerselander"},"text":"","id":5476603,"creation_time":"2011-05-18T07:58:37Z"},{"is_private":false,"creator":{"real_name":"Paul [sabret00the]","name":"sabret00the"},"text":"","id":5373656,"creation_time":"2011-03-28T06:59:22Z"},{"is_private":false,"creator":{"real_name":"Jonathan Griffin (:jgriffin)","name":"jgriffin"},"text":"","id":5240204,"creation_time":"2011-01-31T17:58:24Z"},{"is_private":false,"creator":{"real_name":"Terrell Kelley","name":"terrell.kelley"},"text":"","id":5239501,"creation_time":"2011-01-31T11:46:28Z"},{"is_private":false,"creator":{"real_name":"Benjamin Smedberg [:bsmedberg]","name":"benjamin"},"text":"","id":5002085,"creation_time":"2010-10-11T18:04:48Z"},{"is_private":false,"creator":{"real_name":"Clint Talbert ( :ctalbert )","name":"ctalbert"},"text":"","id":5002054,"creation_time":"2010-10-11T17:52:13Z"},{"is_private":false,"creator":{"real_name":"Dave Townsend (:Mossop)","name":"dtownsend"},"text":"","id":4999117,"creation_time":"2010-10-08T22:12:23Z"},{"is_private":false,"creator":{"real_name":"Jonathan Griffin (:jgriffin)","name":"jgriffin"},"text":"","id":4999107,"creation_time":"2010-10-08T22:07:52Z"},{"is_private":false,"creator":{"real_name":"Benjamin Smedberg [:bsmedberg]","name":"benjamin"},"text":"","id":4991281,"creation_time":"2010-10-06T18:42:20Z"},{"is_private":false,"creator":{"real_name":"David E. Ross","name":"david"},"text":"","id":4990011,"creation_time":"2010-10-06T17:50:51Z"},{"is_private":false,"creator":{"real_name":"Benjamin Smedberg [:bsmedberg]","name":"benjamin"},"text":"","id":4986111,"creation_time":"2010-10-05T02:54:24Z"},{"is_private":false,"creator":{"real_name":"Clint Talbert ( :ctalbert )","name":"ctalbert"},"text":"","id":4985884,"creation_time":"2010-10-05T00:30:08Z"},{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":4921920,"creation_time":"2010-09-08T15:20:06Z"},{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":4892626,"creation_time":"2010-08-26T16:05:50Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":4891863,"creation_time":"2010-08-26T08:24:38Z"},{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":4886114,"creation_time":"2010-08-24T15:31:05Z"},{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":4743315,"creation_time":"2010-06-14T17:26:17Z"},{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":4716160,"creation_time":"2010-05-27T16:53:53Z"},{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":4716148,"creation_time":"2010-05-27T16:44:32Z"},{"is_private":false,"creator":{"real_name":"David E. Ross","name":"david"},"text":"","id":4492322,"creation_time":"2010-01-18T15:12:52Z"},{"is_private":false,"creator":{"real_name":"Christopher Blizzard (:blizzard)","name":"blizzard"},"text":"","id":4490922,"creation_time":"2010-01-17T01:13:33Z"},{"is_private":false,"creator":{"real_name":"Clint Talbert ( :ctalbert )","name":"ctalbert"},"text":"","id":4487552,"creation_time":"2010-01-14T17:46:11Z"},{"is_private":false,"creator":{"real_name":"David E. Ross","name":"david"},"text":"","id":4486520,"creation_time":"2010-01-14T00:08:41Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":4486434,"creation_time":"2010-01-13T23:06:25Z"},{"is_private":false,"creator":{"real_name":"XtC4UaLL [:xtc4uall]","name":"xtc4uall"},"text":"","id":4486282,"creation_time":"2010-01-13T21:34:20Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":4486275,"creation_time":"2010-01-13T21:31:14Z"},{"is_private":false,"creator":{"real_name":"Clint Talbert ( :ctalbert )","name":"ctalbert"},"text":"","id":4486171,"creation_time":"2010-01-13T20:37:30Z"}],"id":539524},"events":[{"time":"2011-07-18T12:06:33Z","changeset":{"changes":[{"removed":"","added":"jwatt@jwatt.org","field_name":"cc"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jwatt@jwatt.org","name":"jwatt@jwatt.org"},"change_time":"2011-07-18T12:06:33Z"}}]},{"bug":{"history":[{"changes":[{"removed":"","added":"568943","field_name":"blocks"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-07-18T09:21:42Z"},{"changes":[{"removed":"[mozmill-next?]","added":"[mozmill-2.0-][mozmill-next?]","field_name":"whiteboard"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-05-25T12:01:37Z"},{"changes":[{"removed":"[mozmill-2.0+]","added":"[mozmill-next?]","field_name":"whiteboard"},{"removed":"critical","added":"normal","field_name":"severity"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2011-05-24T22:41:14Z"},{"changes":[{"removed":"[mozmill-2.0?]","added":"[mozmill-2.0+]","field_name":"whiteboard"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/ctalbert@mozilla.com","name":"ctalbert@mozilla.com"},"change_time":"2011-03-29T21:43:13Z"},{"changes":[{"removed":"","added":"dataloss","field_name":"keywords"},{"removed":"enhancement","added":"critical","field_name":"severity"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-03-21T00:24:58Z"},{"changes":[{"removed":"nobody@mozilla.org","added":"jhammel@mozilla.com","field_name":"assigned_to"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-03-18T21:05:30Z"},{"changes":[{"removed":"","added":"ahalberstadt@mozilla.com, ctalbert@mozilla.com, fayearthur+bugs@gmail.com, hskupin@gmail.com","field_name":"cc"},{"removed":"","added":"[mozmill-2.0?]","field_name":"whiteboard"},{"removed":"normal","added":"enhancement","field_name":"severity"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/jhammel@mozilla.com","name":"jhammel@mozilla.com"},"change_time":"2011-03-18T16:29:22Z"}],"summary":"should be able to clone from a profile as a basis","last_change_time":"2011-07-18T09:21:42Z","comments":[{"is_private":false,"creator":{"real_name":"Clint Talbert ( :ctalbert )","name":"ctalbert"},"text":"","id":5490872,"creation_time":"2011-05-24T22:41:14Z"},{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":5357384,"creation_time":"2011-03-21T16:41:15Z"},{"is_private":false,"creator":{"real_name":"Henrik Skupin (:whimboo)","name":"hskupin"},"text":"","id":5356453,"creation_time":"2011-03-21T00:24:58Z"},{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":5353489,"creation_time":"2011-03-18T16:32:57Z"},{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":5353480,"creation_time":"2011-03-18T16:29:22Z"},{"is_private":false,"creator":{"real_name":"Jeff Hammel [:jhammel]","name":"jhammel"},"text":"","id":5353472,"creation_time":"2011-03-18T16:27:14Z"}],"id":642843},"events":[{"time":"2011-07-18T09:21:42Z","changeset":{"changes":[{"removed":"","added":"568943","field_name":"blocks"}],"changer":{"ref":"https://api-dev.bugzilla.mozilla.org/0.9/user/hskupin@gmail.com","name":"hskupin@gmail.com"},"change_time":"2011-07-18T09:21:42Z"}}]}]
\ No newline at end of file
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/package.json b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/package.json
new file mode 100644
index 0000000..8985573
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/package.json
@@ -0,0 +1,52 @@
+{
+  "name": "firefox-client",
+  "description": "Firefox remote debugging client",
+  "version": "0.3.0",
+  "author": {
+    "name": "Heather Arthur",
+    "email": "fayearthur@gmail.com"
+  },
+  "main": "index.js",
+  "repository": {
+    "type": "git",
+    "url": "http://github.com/harthur/firefox-client.git"
+  },
+  "dependencies": {
+    "colors": "0.5.x",
+    "js-select": "~0.6.0"
+  },
+  "devDependencies": {
+    "connect": "~2.8.2",
+    "mocha": "~1.12.0"
+  },
+  "keywords": [
+    "firefox",
+    "debugger",
+    "remote debugging"
+  ],
+  "bugs": {
+    "url": "https://github.com/harthur/firefox-client/issues"
+  },
+  "homepage": "https://github.com/harthur/firefox-client",
+  "_id": "firefox-client@0.3.0",
+  "dist": {
+    "shasum": "3794460f6eb6afcf41376addcbc7462e24a4cd8b",
+    "tarball": "http://registry.npmjs.org/firefox-client/-/firefox-client-0.3.0.tgz"
+  },
+  "_from": "firefox-client@^0.3.0",
+  "_npmVersion": "1.4.3",
+  "_npmUser": {
+    "name": "harth",
+    "email": "fayearthur@gmail.com"
+  },
+  "maintainers": [
+    {
+      "name": "harth",
+      "email": "fayearthur@gmail.com"
+    }
+  ],
+  "directories": {},
+  "_shasum": "3794460f6eb6afcf41376addcbc7462e24a4cd8b",
+  "_resolved": "https://registry.npmjs.org/firefox-client/-/firefox-client-0.3.0.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test.js
new file mode 100644
index 0000000..1f2d334
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test.js
@@ -0,0 +1,54 @@
+var assert = require('assert'),
+    FirefoxClient = require("./index");
+
+
+var url = "file:///Users/harth/repos/sass-wwcode/index.html";
+
+loadUrl(url, function(tab) {
+  tab.StyleSheets.getStyleSheets(function(err, sheets) {
+    var sheet = sheets[1];
+    sheet.getOriginalSources(function(err, sources) {
+      console.log(err);
+      console.log(sources[0].url);
+      sources[0].getText(function(err, resp) {
+        console.log(err);
+        console.log(resp);
+      })
+    });
+    console.log(sheet.href);
+  });
+})
+
+
+/**
+ * Helper functions
+ */
+function loadUrl(url, callback) {
+  getFirstTab(function(tab) {
+    console.log("GOT TAB");
+    tab.navigateTo(url);
+
+    tab.once("navigate", function() {
+      console.log("NAVIGATED");
+      callback(tab);
+    });
+  });
+}
+
+function getFirstTab(callback) {
+  var client = new FirefoxClient({log: true});
+
+  client.connect(function() {
+    client.listTabs(function(err, tabs) {
+      if (err) throw err;
+
+      var tab = tabs[0];
+
+      // attach so we can receive load events
+      tab.attach(function(err) {
+        if (err) throw err;
+        callback(tab);
+      })
+    });
+  });
+}
\ No newline at end of file
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/README.md b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/README.md
new file mode 100644
index 0000000..75bd86f
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/README.md
@@ -0,0 +1,29 @@
+# testing
+
+### dependencies
+To run the tests in this directory, first install the dev dependencies with this command from the top-level directory:
+
+```
+npm install --dev
+```
+
+You'll also have to globally install [mocha](http://visionmedia.github.io/mocha). `npm install mocha -g`.
+
+### running
+First open up a [Firefox Nightly build](http://nightly.mozilla.org/) and serve the test files up:
+
+```
+node server.js &
+```
+
+visit the url the server tells you to visit.
+
+Finally, run the tests with:
+
+```
+mocha test-dom.js --timeout 10000
+````
+
+The increased timeout is to give you enough time to manually verify the incoming connection in Firefox.
+
+Right now you have to run each test individually, until Firefox [bug 891003](https://bugzilla.mozilla.org/show_bug.cgi?id=891003) is fixed.
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/pages/dom.html b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/pages/dom.html
new file mode 100644
index 0000000..c662637
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/pages/dom.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>DOM tests</title>
+  <meta charset="utf-8">
+</head>
+<body>
+  <main>
+    <section id="test-section">
+      <div id="test1" class="item"></div>
+      <div id="test2" class="item">
+          <div id="child1"></div>
+          <div id="child2"></div>
+      </div>
+      <div id="test3" class="item"></div>
+    </section>
+  </main>
+</body>
+</html>
\ No newline at end of file
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/pages/index.html b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/pages/index.html
new file mode 100644
index 0000000..4a67c5a
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/pages/index.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Firefox remote debugging client tests</title>
+  <meta charset="utf-8">
+  <style>
+    header {
+      font-family: Georgia, sans-serif;
+      font-size: 3em;
+      margin: 3em;
+      color: hsl(30, 90%, 50%);
+    }
+  </style>
+</head>
+<body>
+  <header>
+    Firefox Client Tests
+  </header>
+  <main>
+  </main>
+</body>
+</html>
\ No newline at end of file
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/pages/logs.html b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/pages/logs.html
new file mode 100644
index 0000000..56b7c1d
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/pages/logs.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Logs tests</title>
+  <meta charset="utf-8">
+
+  <script>
+    console.log("hi");
+    console.dir({a: 3});
+    foo;
+  </script>
+</head>
+<body>
+  <main>
+  </main>
+</body>
+</html>
\ No newline at end of file
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/pages/network.html b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/pages/network.html
new file mode 100644
index 0000000..922e27c
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/pages/network.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Logs tests</title>
+  <meta charset="utf-8">
+
+  <script>
+    function sendRequest() {
+      var req = new XMLHttpRequest();
+
+      req.open("GET", "test-network.json", true);
+      req.responseType = "json";
+      req.setRequestHeader("test-header", "test-value");
+      req.send();
+    }
+  </script>
+</head>
+<body>
+  <main>
+  </main>
+</body>
+</html>
\ No newline at end of file
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/pages/stylesheet1.css b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/pages/stylesheet1.css
new file mode 100644
index 0000000..ff5907f
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/pages/stylesheet1.css
@@ -0,0 +1,9 @@
+main {
+  font-family: Georgia, sans-serif;
+  color: black;
+}
+
+* {
+  padding: 0;
+  margin: 0;
+}
\ No newline at end of file
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/pages/stylesheets.html b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/pages/stylesheets.html
new file mode 100644
index 0000000..8cab1e8
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/pages/stylesheets.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Stylesheets tests</title>
+  <meta charset="utf-8">
+  <style>
+    main {
+      margin: 20px;
+    }
+  </style>
+  <link rel="stylesheet" href="stylesheet1.css"/>
+</head>
+<body>
+  <main>
+    <main>
+      <div>Stylesheet Tests</div>
+    </main>
+  </main>
+</body>
+</html>
\ No newline at end of file
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/pages/test-network.json b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/pages/test-network.json
new file mode 100644
index 0000000..5b89410
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/pages/test-network.json
@@ -0,0 +1,4 @@
+{
+  "a": 2,
+  "b": "hello"
+}
\ No newline at end of file
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/server.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/server.js
new file mode 100644
index 0000000..aca94f7
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/server.js
@@ -0,0 +1,8 @@
+var path = require("path"),
+    connect = require('connect');
+
+var port = 3000;
+
+connect.createServer(connect.static(path.join(__dirname, "pages"))).listen(port);
+
+console.log("visit:\nhttp://127.0.0.1:" + port + "/index.html");
\ No newline at end of file
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/test-console.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/test-console.js
new file mode 100644
index 0000000..9198ad0
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/test-console.js
@@ -0,0 +1,64 @@
+var assert = require("assert"),
+    utils = require("./utils");
+
+var Console;
+
+before(function(done) {
+  utils.loadTab('dom.html', function(aTab) {
+    Console = aTab.Console;
+    done();
+  });
+});
+
+// Console - evaluateJS()
+
+describe('evaluateJS()', function() {
+  it('should evaluate expr to number', function(done) {
+    Console.evaluateJS('6 + 7', function(err, resp) {
+      assert.strictEqual(err, null);
+      assert.equal(resp.result, 13);
+      done();
+    })
+  })
+
+  it('should evaluate expr to boolean', function(done) {
+    Console.evaluateJS('!!window', function(err, resp) {
+      assert.strictEqual(err, null);
+      assert.strictEqual(resp.result, true);
+      done();
+    })
+  })
+
+  it('should evaluate expr to string', function(done) {
+    Console.evaluateJS('"hello"', function(err, resp) {
+      assert.strictEqual(err, null);
+      assert.equal(resp.result, "hello");
+      done();
+    })
+  })
+
+  it('should evaluate expr to JSObject', function(done) {
+    Console.evaluateJS('x = {a: 2, b: "hello"}', function(err, resp) {
+      assert.strictEqual(err, null);
+      assert.ok(resp.result.ownPropertyNames, "result has JSObject methods");
+      done();
+    })
+  })
+
+  it('should evaluate to undefined', function(done) {
+    Console.evaluateJS('undefined', function(err, resp) {
+      assert.strictEqual(err, null);
+      assert.ok(resp.result.type, "undefined");
+      done();
+    })
+  })
+
+  it('should have exception in response', function(done) {
+    Console.evaluateJS('blargh', function(err, resp) {
+      assert.strictEqual(err, null);
+      assert.equal(resp.exception.class, "Error"); // TODO: error should be JSObject
+      assert.equal(resp.exceptionMessage, "ReferenceError: blargh is not defined");
+      done();
+    })
+  })
+})
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/test-dom.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/test-dom.js
new file mode 100644
index 0000000..9206561
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/test-dom.js
@@ -0,0 +1,329 @@
+var assert = require("assert"),
+    utils = require("./utils");
+
+var doc;
+var DOM;
+var node;
+var firstNode;
+var lastNode;
+
+before(function(done) {
+  utils.loadTab('dom.html', function(aTab) {
+    DOM = aTab.DOM;
+    DOM.document(function(err, aDoc) {
+      doc = aDoc;
+      DOM.querySelectorAll(".item", function(err, list) {
+        list.items(function(err, items) {
+          firstNode = items[0];
+          node = items[1];
+          lastNode = items[2];
+          done();
+        })
+      })
+    })
+  });
+});
+
+// DOM - document(), documentElement()
+
+describe('document()', function() {
+  it('should get document node', function(done) {
+    DOM.document(function(err, doc) {
+      assert.strictEqual(err, null);
+      assert.equal(doc.nodeName, "#document");
+      assert.equal(doc.nodeType, 9);
+      done();
+    })
+  })
+})
+
+
+describe('documentElement()', function() {
+  it('should get documentElement node', function(done) {
+    DOM.documentElement(function(err, elem) {
+      assert.strictEqual(err, null);
+      assert.equal(elem.nodeName, "HTML");
+      assert.equal(elem.nodeType, 1);
+      done();
+    })
+  })
+})
+
+describe('querySelector()', function() {
+  it('should get first item node', function(done) {
+    DOM.querySelector(".item", function(err, child) {
+      assert.strictEqual(err, null);
+      assert.equal(child.getAttribute("id"), "test1");
+      assert.ok(child.querySelector, "node has node methods");
+      done();
+    })
+  })
+})
+
+describe('querySelector()', function() {
+  it('should get all item nodes', function(done) {
+    DOM.querySelectorAll(".item", function(err, list) {
+      assert.strictEqual(err, null);
+      assert.equal(list.length, 3);
+
+      list.items(function(err, children) {
+        assert.strictEqual(err, null);
+        var ids = children.map(function(child) {
+          assert.ok(child.querySelector, "list item has node methods");
+          return child.getAttribute("id");
+        })
+        assert.deepEqual(ids, ["test1","test2","test3"]);
+        done();
+      })
+    })
+  })
+})
+
+// Node - parentNode(), parent(), siblings(), nextSibling(), previousSibling(),
+// querySelector(), querySelectorAll(), innerHTML(), outerHTML(), getAttribute(),
+// setAttribute()
+
+describe('parentNode()', function() {
+  it('should get parent node', function(done) {
+    node.parentNode(function(err, parent) {
+      assert.strictEqual(err, null);
+      assert.equal(parent.nodeName, "SECTION");
+      assert.ok(parent.querySelector, "parent has node methods");
+      done();
+    })
+  })
+
+  it('should be null for document parentNode', function(done) {
+    doc.parentNode(function(err, parent) {
+      assert.strictEqual(err, null);
+      assert.strictEqual(parent, null);
+      done();
+    })
+  })
+})
+
+describe('parents()', function() {
+  it('should get ancestor nodes', function(done) {
+    node.parents(function(err, ancestors) {
+      assert.strictEqual(err, null);
+      var names = ancestors.map(function(ancestor) {
+        assert.ok(ancestor.querySelector, "ancestor has node methods");
+        return ancestor.nodeName;
+      })
+      assert.deepEqual(names, ["SECTION","MAIN","BODY","HTML","#document"]);
+      done();
+    })
+  })
+})
+
+describe('children()', function() {
+  it('should get child nodes', function(done) {
+    node.children(function(err, children) {
+      assert.strictEqual(err, null);
+      var ids = children.map(function(child) {
+        assert.ok(child.querySelector, "child has node methods");
+        return child.getAttribute("id");
+      })
+      assert.deepEqual(ids, ["child1","child2"]);
+      done();
+    })
+  })
+})
+
+describe('siblings()', function() {
+  it('should get sibling nodes', function(done) {
+    node.siblings(function(err, siblings) {
+      assert.strictEqual(err, null);
+      var ids = siblings.map(function(sibling) {
+        assert.ok(sibling.querySelector, "sibling has node methods");
+        return sibling.getAttribute("id");
+      })
+      assert.deepEqual(ids, ["test1","test2","test3"]);
+      done();
+    })
+  })
+})
+
+describe('nextSibling()', function() {
+  it('should get next sibling node', function(done) {
+    node.nextSibling(function(err, sibling) {
+      assert.strictEqual(err, null);
+      assert.equal(sibling.getAttribute("id"), "test3");
+      assert.ok(sibling.querySelector, "next sibling has node methods");
+      done();
+    })
+  })
+
+  it('should be null if no next sibling', function(done) {
+    lastNode.nextSibling(function(err, sibling) {
+      assert.strictEqual(err, null);
+      assert.strictEqual(sibling, null);
+      done();
+    })
+  })
+})
+
+describe('previousSibling()', function() {
+  it('should get next sibling node', function(done) {
+    node.previousSibling(function(err, sibling) {
+      assert.strictEqual(err, null);
+      assert.equal(sibling.getAttribute("id"), "test1");
+      assert.ok(sibling.querySelector, "next sibling has node methods");
+      done();
+    })
+  })
+
+  it('should be null if no prev sibling', function(done) {
+    firstNode.previousSibling(function(err, sibling) {
+      assert.strictEqual(err, null);
+      assert.strictEqual(sibling, null);
+      done();
+    })
+  })
+})
+
+describe('querySelector()', function() {
+  it('should get first child node', function(done) {
+    node.querySelector("*", function(err, child) {
+      assert.strictEqual(err, null);
+      assert.equal(child.getAttribute("id"), "child1");
+      assert.ok(child.querySelector, "node has node methods");
+      done();
+    })
+  })
+
+  it('should be null if no nodes with selector', function(done) {
+    node.querySelector("blarg", function(err, resp) {
+      assert.strictEqual(err, null);
+      assert.strictEqual(resp, null);
+      done();
+    })
+  })
+})
+
+describe('querySelectorAll()', function() {
+  it('should get all child nodes', function(done) {
+    node.querySelectorAll("*", function(err, list) {
+      assert.strictEqual(err, null);
+      assert.equal(list.length, 2);
+
+      list.items(function(err, children) {
+        assert.strictEqual(err, null);
+        var ids = children.map(function(child) {
+          assert.ok(child.querySelector, "list item has node methods");
+          return child.getAttribute("id");
+        })
+        assert.deepEqual(ids, ["child1", "child2"]);
+        done();
+      })
+    })
+  })
+
+  it('should get nodes from "start" to "end"', function(done) {
+    doc.querySelectorAll(".item", function(err, list) {
+      assert.strictEqual(err, null);
+      assert.equal(list.length, 3);
+
+      list.items(1, 2, function(err, items) {
+        assert.strictEqual(err, null);
+        assert.equal(items.length, 1);
+        assert.deepEqual(items[0].getAttribute("id"), "test2")
+        done();
+      })
+    })
+  })
+
+  it('should get nodes from "start"', function(done) {
+    doc.querySelectorAll(".item", function(err, list) {
+      assert.strictEqual(err, null);
+      assert.equal(list.length, 3);
+
+      list.items(1, function(err, items) {
+        assert.strictEqual(err, null);
+        assert.equal(items.length, 2);
+        var ids = items.map(function(item) {
+          assert.ok(item.querySelector, "list item has node methods");
+          return item.getAttribute("id");
+        })
+        assert.deepEqual(ids, ["test2","test3"]);
+        done();
+      })
+    })
+  })
+
+  it('should be empty list if no nodes with selector', function(done) {
+    node.querySelectorAll("blarg", function(err, list) {
+      assert.strictEqual(err, null);
+      assert.equal(list.length, 0);
+
+      list.items(function(err, items) {
+        assert.strictEqual(err, null);
+        assert.deepEqual(items, []);
+        done();
+      })
+    })
+  })
+})
+
+describe('innerHTML()', function() {
+  it('should get innerHTML of node', function(done) {
+    node.innerHTML(function(err, text) {
+      assert.strictEqual(err, null);
+      assert.equal(text, '\n          <div id="child1"></div>\n'
+                   + '          <div id="child2"></div>\n      ');
+      done();
+    })
+  })
+})
+
+describe('outerHTML()', function() {
+  it('should get outerHTML of node', function(done) {
+    node.outerHTML(function(err, text) {
+      assert.strictEqual(err, null);
+      assert.equal(text, '<div id="test2" class="item">\n'
+                   + '          <div id="child1"></div>\n'
+                   + '          <div id="child2"></div>\n      '
+                   + '</div>');
+      done();
+    })
+  })
+})
+
+describe('highlight()', function() {
+  it('should highlight node', function(done) {
+    node.highlight(function(err, resp) {
+      assert.strictEqual(err, null);
+      done();
+    })
+  })
+})
+
+/* MUST BE LAST */
+describe('remove()', function() {
+  it('should remove node', function(done) {
+    node.remove(function(err, nextSibling) {
+      assert.strictEqual(err, null);
+      assert.equal(nextSibling.getAttribute("id"), "test3");
+
+      doc.querySelectorAll(".item", function(err, list) {
+        assert.strictEqual(err, null);
+        assert.equal(list.length, 2);
+        done();
+      })
+    })
+  })
+
+  it("should err if performing further operations after release()", function(done) {
+    node.release(function(err) {
+      assert.strictEqual(err, null);
+
+      node.innerHTML(function(err, text) {
+        assert.equal(err.message, "TypeError: node is null")
+        assert.equal(err.toString(), "unknownError: TypeError: node is null");
+        done();
+      })
+    })
+  })
+})
+
+
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/test-jsobject.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/test-jsobject.js
new file mode 100644
index 0000000..f416674
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/test-jsobject.js
@@ -0,0 +1,115 @@
+var assert = require("assert"),
+    utils = require("./utils");
+
+var Console;
+var obj;
+var func;
+
+before(function(done) {
+  utils.loadTab('dom.html', function(aTab) {
+    Console = aTab.Console;
+    Console.evaluateJS('x = {a: 2, b: {c: 3}, get d() {return 4;}}', function(err, resp) {
+      obj = resp.result;
+
+      var input = 'y = function testfunc(a, b) { return a + b; }';
+      Console.evaluateJS(input, function(err, resp) {
+        func = resp.result;
+        done();
+      })
+    });
+  });
+});
+
+// JSObject - ownPropertyNames(), ownPropertyDescriptor(), prototype(), properties()
+
+describe('ownPropertyNames()', function() {
+  it('should fetch property names', function(done) {
+    obj.ownPropertyNames(function(err, names) {
+      assert.strictEqual(err, null);
+      assert.deepEqual(names, ['a', 'b', 'd']);
+      done();
+    })
+  })
+});
+
+describe('ownPropertyDescriptor()', function() {
+  it('should fetch descriptor for property', function(done) {
+    obj.ownPropertyDescriptor('a', function(err, desc) {
+      assert.strictEqual(err, null);
+      testDescriptor(desc);
+      assert.equal(desc.value, 2);
+      done();
+    })
+  })
+
+  /* TODO: doesn't call callback if not defined property - Server side problem
+  it('should be undefined for nonexistent property', function(done) {
+    obj.ownPropertyDescriptor('g', function(desc) {
+      console.log("desc", desc);
+      done();
+    })
+  }) */
+})
+
+describe('ownProperties()', function() {
+  it('should fetch all own properties and descriptors', function(done) {
+    obj.ownProperties(function(err, props) {
+      assert.strictEqual(err, null);
+      testDescriptor(props.a);
+      assert.equal(props.a.value, 2);
+
+      testDescriptor(props.b);
+      assert.ok(props.b.value.ownProperties, "prop value has JSObject methods");
+      done();
+    })
+  })
+})
+
+describe('prototype()', function() {
+  it('should fetch prototype as an object', function(done) {
+    obj.prototype(function(err, proto) {
+      assert.strictEqual(err, null);
+      assert.ok(proto.ownProperties, "prototype has JSObject methods");
+      done();
+    })
+  })
+})
+
+describe('ownPropertiesAndPrototype()', function() {
+  it('should fetch properties, prototype, and getters', function(done) {
+    obj.ownPropertiesAndPrototype(function(err, resp) {
+      assert.strictEqual(err, null);
+
+      // own properties
+      var props = resp.ownProperties;
+      assert.equal(Object.keys(props).length, 3);
+
+      testDescriptor(props.a);
+      assert.equal(props.a.value, 2);
+
+      // prototype
+      assert.ok(resp.prototype.ownProperties,
+                "prototype has JSObject methods");
+
+      // getters
+      var getters = resp.safeGetterValues;
+      assert.equal(Object.keys(getters).length, 0);
+
+      done();
+    })
+  })
+})
+
+describe('Function objects', function() {
+  it('sould have correct properties', function() {
+    assert.equal(func.class, "Function");
+    assert.equal(func.name, "testfunc");
+    assert.ok(func.ownProperties, "function has JSObject methods")
+  })
+})
+
+function testDescriptor(desc) {
+  assert.strictEqual(desc.configurable, true);
+  assert.strictEqual(desc.enumerable, true);
+  assert.strictEqual(desc.writable, true);
+}
\ No newline at end of file
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/test-logs.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/test-logs.js
new file mode 100644
index 0000000..2c076a6
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/test-logs.js
@@ -0,0 +1,106 @@
+var assert = require("assert"),
+    utils = require("./utils");
+
+var tab;
+var Console;
+
+before(function(done) {
+  utils.loadTab('logs.html', function(aTab) {
+    tab = aTab;
+    Console = aTab.Console;
+
+    Console.startListening(function() {
+      done();
+    })
+  });
+});
+
+// Console - startLogging(), stopLogging(), getCachedMessages(),
+// clearCachedMessages(), event:page-error, event:console-api-call
+
+describe('getCachedMessages()', function() {
+  it('should get messages from before listening', function(done) {
+    Console.getCachedLogs(function(err, messages) {
+      assert.strictEqual(err, null);
+
+      var hasLog = messages.some(function(message) {
+        return message.level == "log";
+      })
+      assert.ok(hasLog);
+
+      var hasDir = messages.some(function(message) {
+        return message.level == "dir";
+      })
+      assert.ok(hasDir);
+
+      var hasError = messages.some(function(message) {
+        return message.errorMessage == "ReferenceError: foo is not defined";
+      })
+      assert.ok(hasError);
+      done();
+    });
+  })
+})
+
+describe('clearCachedMessages()', function() {
+  it('should clear cached messages', function(done) {
+    Console.clearCachedLogs(function() {
+      Console.getCachedLogs(function(err, messages) {
+        assert.strictEqual(err, null);
+        // The error message should be left
+        assert.equal(messages.length, 1);
+        assert.equal(messages[0].errorMessage, "ReferenceError: foo is not defined")
+        done();
+      })
+    });
+  })
+})
+
+describe('"page-error" event', function() {
+  it('should receive "page-error" event with message', function(done) {
+    Console.once('page-error', function(event) {
+      assert.equal(event.errorMessage, "ReferenceError: foo is not defined");
+      assert.ok(event.sourceName.indexOf("logs.html") > 0);
+      assert.equal(event.lineNumber, 10);
+      assert.equal(event.columnNumber, 0);
+      assert.ok(event.exception);
+
+      done();
+    });
+
+    tab.reload();
+  })
+})
+
+describe('"console-api-call" event', function() {
+  it('should receive "console-api-call" for console.log', function(done) {
+    Console.on('console-api-call', function(event) {
+      if (event.level == "log") {
+        assert.deepEqual(event.arguments, ["hi"]);
+
+        Console.removeAllListeners('console-api-call');
+        done();
+      }
+    });
+
+    tab.reload();
+  })
+
+  it('should receive "console-api-call" for console.dir', function(done) {
+    Console.on('console-api-call', function(event) {
+      if (event.level == "dir") {
+        var obj = event.arguments[0];
+        assert.ok(obj.ownPropertyNames, "dir argument has JSObject methods");
+
+        Console.removeAllListeners('console-api-call');
+        done();
+      }
+    });
+
+    tab.reload();
+  })
+})
+
+after(function() {
+  Console.stopListening();
+})
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/test-network.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/test-network.js
new file mode 100644
index 0000000..969ec63
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/test-network.js
@@ -0,0 +1,88 @@
+var assert = require("assert"),
+    path = require("path"),
+    utils = require("./utils");
+
+var tab;
+var Network;
+var Console;
+
+before(function(done) {
+  utils.loadTab('network.html', function(aTab) {
+    tab = aTab;
+    Network = aTab.Network;
+    Console = aTab.Console;
+
+    Network.startLogging(function(err) {
+      assert.strictEqual(err, null);
+      done();
+    })
+  });
+});
+
+// Network - startLogging(), stopLogging(), sendHTTPRequest(), event:network-event
+
+describe('"network-event" event', function() {
+  it('should receive "network-event" event with message', function(done) {
+    Network.once('network-event', function(event) {
+      assert.equal(event.method, "GET");
+      assert.equal(path.basename(event.url), "test-network.json");
+      assert.ok(event.isXHR);
+      assert.ok(event.getResponseHeaders, "event has NetworkEvent methods")
+      done();
+    });
+
+    Console.evaluateJS("sendRequest()")
+  })
+})
+
+describe('sendHTTPRequest()', function() {
+  it('should send a new XHR request from page', function(done) {
+    var request = {
+      url: "test-network.json",
+      method: "GET",
+      headers: [{name: "test-header", value: "test-value"}]
+    };
+
+    Network.sendHTTPRequest(request, function(err, netEvent) {
+      assert.strictEqual(err, null);
+      assert.ok(netEvent.getResponseHeaders, "event has NetworkEvent methods");
+      done();
+    });
+  })
+})
+
+// NetworkEvent - getRequestHeaders(), getRequestCookies(), getRequestPostData(),
+// getResponseHeaders(), getResponseCookies(), getResponseContent(), getEventTimings()
+// event:update
+
+
+describe('getRequestHeaders(', function() {
+  it('should get request headers', function(done) {
+    Network.on('network-event', function(netEvent) {
+      netEvent.on("request-headers", function(event) {
+        assert.ok(event.headers);
+        assert.ok(event.headersSize);
+
+        netEvent.getRequestHeaders(function(err, resp) {
+          assert.strictEqual(err, null);
+
+          var found = resp.headers.some(function(header) {
+            return header.name == "test-header" &&
+                   header.value == "test-value";
+          });
+          assert.ok(found, "contains that header we sent");
+          done();
+        })
+      })
+    });
+    Console.evaluateJS("sendRequest()");
+  })
+})
+
+// TODO: NetworkEvent tests
+
+after(function() {
+  Network.stopLogging(function(err) {
+    assert.strictEqual(err, null);
+  });
+})
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/test-raw.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/test-raw.js
new file mode 100644
index 0000000..433eede
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/test-raw.js
@@ -0,0 +1,26 @@
+var assert = require("assert"),
+    FirefoxClient = require("../index");
+
+var client = new FirefoxClient();
+
+before(function(done) {
+  client.connect(function() {
+    done();
+  })
+});
+
+describe('makeRequest()', function() {
+  it('should do listTabs request', function(done) {
+    var message = {
+      to: 'root',
+      type: 'listTabs'
+    };
+
+    client.client.makeRequest(message, function(resp) {
+      assert.equal(resp.from, "root");
+      assert.ok(resp.tabs);
+      assert.ok(resp.profilerActor)
+      done();
+    })
+  })
+})
\ No newline at end of file
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/test-stylesheets.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/test-stylesheets.js
new file mode 100644
index 0000000..86af8d0
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/test-stylesheets.js
@@ -0,0 +1,124 @@
+var assert = require("assert"),
+    path = require("path"),
+    utils = require("./utils");
+
+var StyleSheets;
+var styleSheet;
+
+var SS_TEXT = [
+"main {",
+"  font-family: Georgia, sans-serif;",
+"  color: black;",
+"}",
+"",
+"* {",
+"  padding: 0;",
+"  margin: 0;",
+"}"
+].join("\n");
+
+before(function(done) {
+  utils.loadTab('stylesheets.html', function(aTab) {
+    StyleSheets = aTab.StyleSheets;
+    StyleSheets.getStyleSheets(function(err, sheets) {
+      assert.strictEqual(err, null);
+      styleSheet = sheets[1];
+      done();
+    })
+  });
+});
+
+// Stylesheets - getStyleSheets(), addStyleSheet()
+
+describe('getStyleSheets()', function() {
+  it('should list all the stylesheets', function(done) {
+    StyleSheets.getStyleSheets(function(err, sheets) {
+      assert.strictEqual(err, null);
+
+      var hrefs = sheets.map(function(sheet) {
+        assert.ok(sheet.update, "sheet has Stylesheet methods");
+        return path.basename(sheet.href);
+      });
+      assert.deepEqual(hrefs, ["null", "stylesheet1.css"]);
+      done();
+    })
+  })
+})
+
+describe('addStyleSheet()', function() {
+  it('should add a new stylesheet', function(done) {
+    var text = "div { font-weight: bold; }";
+
+    StyleSheets.addStyleSheet(text, function(err, sheet) {
+      assert.strictEqual(err, null);
+      assert.ok(sheet.update, "sheet has Stylesheet methods");
+      assert.equal(sheet.ruleCount, 1);
+      done();
+    })
+  })
+})
+
+// StyleSheet - update(), toggleDisabled()
+
+describe('StyleSheet', function() {
+  it('should have the correct properties', function() {
+    assert.equal(path.basename(styleSheet.href), "stylesheet1.css");
+    assert.strictEqual(styleSheet.disabled, false);
+    assert.equal(styleSheet.ruleCount, 2);
+  })
+})
+
+describe('StyleSheet.getText()', function() {
+  it('should get the text of the style sheet', function(done) {
+    styleSheet.getText(function(err, resp) {
+      assert.strictEqual(err, null);
+      assert.equal(resp, SS_TEXT);
+      done();
+    })
+  })
+});
+
+describe('StyleSheet.update()', function() {
+  it('should update stylesheet', function(done) {
+    var text = "main { color: red; }";
+
+    styleSheet.update(text, function(err, resp) {
+      assert.strictEqual(err, null);
+      // TODO: assert.equal(styleSheet.ruleCount, 1);
+      done();
+    })
+  })
+})
+
+describe('StyleSheet.toggleDisabled()', function() {
+  it('should toggle disabled attribute', function(done) {
+    assert.deepEqual(styleSheet.disabled, false);
+
+    styleSheet.toggleDisabled(function(err, resp) {
+      assert.strictEqual(err, null);
+      assert.deepEqual(styleSheet.disabled, true);
+      done();
+    })
+  })
+
+  it('should fire disabled-changed event', function(done) {
+    styleSheet.toggleDisabled(function(err, resp) {
+      assert.strictEqual(err, null);
+      assert.deepEqual(styleSheet.disabled, false);
+    })
+    styleSheet.on("disabled-changed", function(disabled) {
+      assert.strictEqual(disabled, false);
+      done();
+    })
+  })
+})
+
+describe('StyleSheet.getOriginalSources()', function() {
+  it('should get no original sources', function(done) {
+    styleSheet.getOriginalSources(function(err, resp) {
+      assert.strictEqual(err, null);
+      assert.deepEqual(resp, []);
+      done();
+    })
+  })
+})
diff --git a/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/utils.js b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/utils.js
new file mode 100644
index 0000000..45dc191
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/firefox-client/test/utils.js
@@ -0,0 +1,35 @@
+var assert = require('assert'),
+    FirefoxClient = require("../index");
+
+var tab;
+
+exports.loadTab = function(url, callback) {
+  getFirstTab(function(tab) {
+    tab.navigateTo(url);
+
+    tab.once("navigate", function() {
+      callback(tab);
+    });
+  })
+};
+
+
+function getFirstTab(callback) {
+  if (tab) {
+    return callback(tab);
+  }
+  var client = new FirefoxClient({log: true});
+
+  client.connect(function() {
+    client.listTabs(function(err, tabs) {
+      if (err) throw err;
+
+      tab = tabs[0];
+
+      tab.attach(function(err) {
+        if (err) throw err;
+        callback(tab);
+      })
+    });
+  });
+}
\ No newline at end of file
diff --git a/node_modules/node-firefox-start-simulator/node_modules/portfinder/.npmignore b/node_modules/node-firefox-start-simulator/node_modules/portfinder/.npmignore
new file mode 100644
index 0000000..5171c54
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/portfinder/.npmignore
@@ -0,0 +1,2 @@
+node_modules
+npm-debug.log
\ No newline at end of file
diff --git a/node_modules/node-firefox-start-simulator/node_modules/portfinder/README.md b/node_modules/node-firefox-start-simulator/node_modules/portfinder/README.md
new file mode 100644
index 0000000..60d4a08
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/portfinder/README.md
@@ -0,0 +1,37 @@
+# node-portfinder
+
+## Installation
+
+### Installing npm (node package manager)
+``` bash
+  curl http://npmjs.org/install.sh | sh
+```
+
+### Installing node-portfinder
+``` bash
+  $ [sudo] npm install portfinder
+```
+
+## Usage
+The `portfinder` module has a simple interface:
+
+``` js
+  var portfinder = require('portfinder');
+  
+  portfinder.getPort(function (err, port) {
+    //
+    // `port` is guarenteed to be a free port 
+    // in this scope.
+    //
+  });
+```
+
+By default `portfinder` will start searching from `8000`. To change this simply set `portfinder.basePort`.
+
+## Run Tests
+``` bash
+  $ npm test
+```
+
+#### Author: [Charlie Robbins][0]
+[0]: http://nodejitsu.com
\ No newline at end of file
diff --git a/node_modules/node-firefox-start-simulator/node_modules/portfinder/lib/portfinder.js b/node_modules/node-firefox-start-simulator/node_modules/portfinder/lib/portfinder.js
new file mode 100644
index 0000000..2700782
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/portfinder/lib/portfinder.js
@@ -0,0 +1,187 @@
+/*
+ * portfinder.js: A simple tool to find an open port on the current machine.
+ *
+ * (C) 2011, Charlie Robbins
+ *
+ */
+ 
+var fs = require('fs'),
+    net = require('net'),
+    path = require('path'),
+    mkdirp = require('mkdirp').mkdirp;
+
+//
+// ### @basePort {Number}
+// The lowest port to begin any port search from
+//
+exports.basePort = 8000;
+
+//
+// ### @basePath {string}
+// Default path to begin any socket search from
+//
+exports.basePath = '/tmp/portfinder'
+
+//
+// ### function getPort (options, callback)
+// #### @options {Object} Settings to use when finding the necessary port
+// #### @callback {function} Continuation to respond to when complete.
+// Responds with a unbound port on the current machine.
+//
+exports.getPort = function (options, callback) {
+  if (!callback) {
+    callback = options;
+    options = {}; 
+  }
+  
+  options.port   = options.port   || exports.basePort;
+  options.host   = options.host   || null;
+  options.server = options.server || net.createServer(function () {
+    //
+    // Create an empty listener for the port testing server.
+    //
+  });
+  
+  function onListen () {
+    options.server.removeListener('error', onError);
+    options.server.close();
+    callback(null, options.port)
+  }
+  
+  function onError (err) {
+    options.server.removeListener('listening', onListen);
+
+    if (err.code !== 'EADDRINUSE') {
+      return callback(err);
+    }
+
+    exports.getPort({
+      port: exports.nextPort(options.port),
+      host: options.host,
+      server: options.server
+    }, callback);
+  }
+
+  options.server.once('error', onError);
+  options.server.once('listening', onListen);
+  options.server.listen(options.port, options.host);
+};
+
+//
+// ### function getSocket (options, callback)
+// #### @options {Object} Settings to use when finding the necessary port
+// #### @callback {function} Continuation to respond to when complete.
+// Responds with a unbound socket using the specified directory and base
+// name on the current machine.
+//
+exports.getSocket = function (options, callback) {
+  if (!callback) {
+    callback = options;
+    options = {};
+  }
+
+  options.mod  = options.mod    || 0755;
+  options.path = options.path   || exports.basePath + '.sock';
+  
+  //
+  // Tests the specified socket
+  //
+  function testSocket () {
+    fs.stat(options.path, function (err) {
+      //
+      // If file we're checking doesn't exist (thus, stating it emits ENOENT),
+      // we should be OK with listening on this socket.
+      //
+      if (err) {
+        if (err.code == 'ENOENT') {
+          callback(null, options.path);
+        }
+        else {
+          callback(err);
+        }
+      }
+      else {
+        //
+        // This file exists, so it isn't possible to listen on it. Lets try
+        // next socket.
+        //
+        options.path = exports.nextSocket(options.path);
+        exports.getSocket(options, callback);
+      }
+    });
+  }
+  
+  //
+  // Create the target `dir` then test connection
+  // against the socket.
+  //
+  function createAndTestSocket (dir) {
+    mkdirp(dir, options.mod, function (err) {
+      if (err) {
+        return callback(err);
+      }
+      
+      options.exists = true;
+      testSocket();
+    });
+  }
+  
+  //
+  // Check if the parent directory of the target
+  // socket path exists. If it does, test connection
+  // against the socket. Otherwise, create the directory
+  // then test connection. 
+  //
+  function checkAndTestSocket () {
+    var dir = path.dirname(options.path);
+    
+    fs.stat(dir, function (err, stats) {
+      if (err || !stats.isDirectory()) {
+        return createAndTestSocket(dir);
+      }
+
+      options.exists = true;
+      testSocket();
+    });
+  }
+  
+  //
+  // If it has been explicitly stated that the 
+  // target `options.path` already exists, then 
+  // simply test the socket.
+  //
+  return options.exists 
+    ? testSocket()
+    : checkAndTestSocket();
+};
+
+//
+// ### function nextPort (port)
+// #### @port {Number} Port to increment from.
+// Gets the next port in sequence from the 
+// specified `port`.
+//
+exports.nextPort = function (port) {
+  return port + 1;
+};
+
+//
+// ### function nextSocket (socketPath)
+// #### @socketPath {string} Path to increment from
+// Gets the next socket path in sequence from the 
+// specified `socketPath`.
+//
+exports.nextSocket = function (socketPath) {
+  var dir = path.dirname(socketPath),
+      name = path.basename(socketPath, '.sock'),
+      match = name.match(/^([a-zA-z]+)(\d*)$/i),
+      index = parseInt(match[2]),
+      base = match[1];
+  
+  if (isNaN(index)) {
+    index = 0;
+  }
+  
+  index += 1;
+  return path.join(dir, base + index + '.sock');
+};
diff --git a/node_modules/node-firefox-start-simulator/node_modules/portfinder/node_modules/mkdirp/.gitignore.orig b/node_modules/node-firefox-start-simulator/node_modules/portfinder/node_modules/mkdirp/.gitignore.orig
new file mode 100644
index 0000000..9303c34
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/portfinder/node_modules/mkdirp/.gitignore.orig
@@ -0,0 +1,2 @@
+node_modules/
+npm-debug.log
\ No newline at end of file
diff --git a/node_modules/node-firefox-start-simulator/node_modules/portfinder/node_modules/mkdirp/.gitignore.rej b/node_modules/node-firefox-start-simulator/node_modules/portfinder/node_modules/mkdirp/.gitignore.rej
new file mode 100644
index 0000000..69244ff
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/portfinder/node_modules/mkdirp/.gitignore.rej
@@ -0,0 +1,5 @@
+--- /dev/null
++++ .gitignore
+@@ -0,0 +1,2 @@
++node_modules/
++npm-debug.log
\ No newline at end of file
diff --git a/node_modules/node-firefox-start-simulator/node_modules/portfinder/node_modules/mkdirp/.npmignore b/node_modules/node-firefox-start-simulator/node_modules/portfinder/node_modules/mkdirp/.npmignore
new file mode 100644
index 0000000..9303c34
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/portfinder/node_modules/mkdirp/.npmignore
@@ -0,0 +1,2 @@
+node_modules/
+npm-debug.log
\ No newline at end of file
diff --git a/node_modules/node-firefox-start-simulator/node_modules/portfinder/node_modules/mkdirp/LICENSE b/node_modules/node-firefox-start-simulator/node_modules/portfinder/node_modules/mkdirp/LICENSE
new file mode 100644
index 0000000..432d1ae
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/portfinder/node_modules/mkdirp/LICENSE
@@ -0,0 +1,21 @@
+Copyright 2010 James Halliday (mail@substack.net)
+
+This project is free software released under the MIT/X11 license:
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/node_modules/node-firefox-start-simulator/node_modules/portfinder/node_modules/mkdirp/README.markdown b/node_modules/node-firefox-start-simulator/node_modules/portfinder/node_modules/mkdirp/README.markdown
new file mode 100644
index 0000000..0393c4e
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/portfinder/node_modules/mkdirp/README.markdown
@@ -0,0 +1,21 @@
+mkdirp
+======
+
+Like `mkdir -p`, but in node.js!
+
+Example
+=======
+
+pow.js
+------
+    var mkdirp = require('mkdirp');
+    
+    mkdirp('/tmp/foo/bar/baz', 0755, function (err) {
+        if (err) console.error(err)
+        else console.log('pow!')
+    });
+
+Output
+    pow!
+
+And now /tmp/foo/bar/baz exists, huzzah!
diff --git a/node_modules/node-firefox-start-simulator/node_modules/portfinder/node_modules/mkdirp/examples/pow.js b/node_modules/node-firefox-start-simulator/node_modules/portfinder/node_modules/mkdirp/examples/pow.js
new file mode 100644
index 0000000..7741462
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/portfinder/node_modules/mkdirp/examples/pow.js
@@ -0,0 +1,6 @@
+var mkdirp = require('mkdirp');
+
+mkdirp('/tmp/foo/bar/baz', 0755, function (err) {
+    if (err) console.error(err)
+    else console.log('pow!')
+});
diff --git a/node_modules/node-firefox-start-simulator/node_modules/portfinder/node_modules/mkdirp/examples/pow.js.orig b/node_modules/node-firefox-start-simulator/node_modules/portfinder/node_modules/mkdirp/examples/pow.js.orig
new file mode 100644
index 0000000..7741462
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/portfinder/node_modules/mkdirp/examples/pow.js.orig
@@ -0,0 +1,6 @@
+var mkdirp = require('mkdirp');
+
+mkdirp('/tmp/foo/bar/baz', 0755, function (err) {
+    if (err) console.error(err)
+    else console.log('pow!')
+});
diff --git a/node_modules/node-firefox-start-simulator/node_modules/portfinder/node_modules/mkdirp/examples/pow.js.rej b/node_modules/node-firefox-start-simulator/node_modules/portfinder/node_modules/mkdirp/examples/pow.js.rej
new file mode 100644
index 0000000..81e7f43
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/portfinder/node_modules/mkdirp/examples/pow.js.rej
@@ -0,0 +1,19 @@
+--- examples/pow.js
++++ examples/pow.js
+@@ -1,6 +1,15 @@
+-var mkdirp = require('mkdirp').mkdirp;
++var mkdirp = require('../').mkdirp,
++    mkdirpSync = require('../').mkdirpSync;
+ 
+ mkdirp('/tmp/foo/bar/baz', 0755, function (err) {
+     if (err) console.error(err)
+     else console.log('pow!')
+ });
++
++try {
++  mkdirpSync('/tmp/bar/foo/baz', 0755);
++  console.log('double pow!');
++}
++catch (ex) {
++  console.log(ex);
++}
\ No newline at end of file
diff --git a/node_modules/node-firefox-start-simulator/node_modules/portfinder/node_modules/mkdirp/index.js b/node_modules/node-firefox-start-simulator/node_modules/portfinder/node_modules/mkdirp/index.js
new file mode 100644
index 0000000..30e9600
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/portfinder/node_modules/mkdirp/index.js
@@ -0,0 +1,20 @@
+var path = require('path');
+var fs = require('fs');
+
+var exports = module.exports = function mkdirP (p, mode, f) {
+    var cb = f || function () {};
+    p = path.resolve(p);
+    
+    var ps = path.normalize(p).split('/');
+    path.exists(p, function (exists) {
+        if (exists) cb(null);
+        else mkdirP(ps.slice(0,-1).join('/'), mode, function (err) {
+            if (err && err.code !== 'EEXIST') cb(err)
+            else fs.mkdir(p, mode, function (err) {
+                if (err && err.code !== 'EEXIST') cb(err)
+                else cb()
+            });
+        });
+    });
+};
+exports.mkdirp = exports.mkdirP = module.exports;
diff --git a/node_modules/node-firefox-start-simulator/node_modules/portfinder/node_modules/mkdirp/package.json b/node_modules/node-firefox-start-simulator/node_modules/portfinder/node_modules/mkdirp/package.json
new file mode 100644
index 0000000..dad4772
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/portfinder/node_modules/mkdirp/package.json
@@ -0,0 +1,37 @@
+{
+  "name": "mkdirp",
+  "description": "Recursively mkdir, like `mkdir -p`",
+  "version": "0.0.7",
+  "author": {
+    "name": "James Halliday",
+    "email": "mail@substack.net",
+    "url": "http://substack.net"
+  },
+  "main": "./index",
+  "keywords": [
+    "mkdir",
+    "directory"
+  ],
+  "repository": {
+    "type": "git",
+    "url": "http://github.com/substack/node-mkdirp.git"
+  },
+  "scripts": {
+    "test": "node node_modules/tap/bin/tap.js test/*.js"
+  },
+  "devDependencies": {
+    "tap": "0.0.x"
+  },
+  "license": "MIT/X11",
+  "engines": {
+    "node": "*"
+  },
+  "readme": "mkdirp\n======\n\nLike `mkdir -p`, but in node.js!\n\nExample\n=======\n\npow.js\n------\n    var mkdirp = require('mkdirp');\n    \n    mkdirp('/tmp/foo/bar/baz', 0755, function (err) {\n        if (err) console.error(err)\n        else console.log('pow!')\n    });\n\nOutput\n    pow!\n\nAnd now /tmp/foo/bar/baz exists, huzzah!\n",
+  "readmeFilename": "README.markdown",
+  "bugs": {
+    "url": "https://github.com/substack/node-mkdirp/issues"
+  },
+  "homepage": "https://github.com/substack/node-mkdirp",
+  "_id": "mkdirp@0.0.7",
+  "_from": "mkdirp@0.0.x"
+}
diff --git a/node_modules/node-firefox-start-simulator/node_modules/portfinder/node_modules/mkdirp/test/mkdirp.js b/node_modules/node-firefox-start-simulator/node_modules/portfinder/node_modules/mkdirp/test/mkdirp.js
new file mode 100644
index 0000000..b07cd70
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/portfinder/node_modules/mkdirp/test/mkdirp.js
@@ -0,0 +1,28 @@
+var mkdirp = require('../');
+var path = require('path');
+var fs = require('fs');
+var test = require('tap').test;
+
+test('woo', function (t) {
+    t.plan(2);
+    var x = Math.floor(Math.random() * Math.pow(16,4)).toString(16);
+    var y = Math.floor(Math.random() * Math.pow(16,4)).toString(16);
+    var z = Math.floor(Math.random() * Math.pow(16,4)).toString(16);
+    
+    var file = '/tmp/' + [x,y,z].join('/');
+    
+    mkdirp(file, 0755, function (err) {
+        if (err) t.fail(err);
+        else path.exists(file, function (ex) {
+            if (!ex) t.fail('file not created')
+            else fs.stat(file, function (err, stat) {
+                if (err) t.fail(err)
+                else {
+                    t.equal(stat.mode & 0777, 0755);
+                    t.ok(stat.isDirectory(), 'target not a directory');
+                    t.end();
+                }
+            })
+        })
+    });
+});
diff --git a/node_modules/node-firefox-start-simulator/node_modules/portfinder/node_modules/mkdirp/test/race.js b/node_modules/node-firefox-start-simulator/node_modules/portfinder/node_modules/mkdirp/test/race.js
new file mode 100644
index 0000000..96a0447
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/portfinder/node_modules/mkdirp/test/race.js
@@ -0,0 +1,41 @@
+var mkdirp = require('../').mkdirp;
+var path = require('path');
+var fs = require('fs');
+var test = require('tap').test;
+
+test('race', function (t) {
+    t.plan(4);
+    var ps = [ '', 'tmp' ];
+    
+    for (var i = 0; i < 25; i++) {
+        var dir = Math.floor(Math.random() * Math.pow(16,4)).toString(16);
+        ps.push(dir);
+    }
+    var file = ps.join('/');
+    
+    var res = 2;
+    mk(file, function () {
+        if (--res === 0) t.end();
+    });
+    
+    mk(file, function () {
+        if (--res === 0) t.end();
+    });
+    
+    function mk (file, cb) {
+        mkdirp(file, 0755, function (err) {
+            if (err) t.fail(err);
+            else path.exists(file, function (ex) {
+                if (!ex) t.fail('file not created')
+                else fs.stat(file, function (err, stat) {
+                    if (err) t.fail(err)
+                    else {
+                        t.equal(stat.mode & 0777, 0755);
+                        t.ok(stat.isDirectory(), 'target not a directory');
+                        if (cb) cb();
+                    }
+                })
+            })
+        });
+    }
+});
diff --git a/node_modules/node-firefox-start-simulator/node_modules/portfinder/node_modules/mkdirp/test/rel.js b/node_modules/node-firefox-start-simulator/node_modules/portfinder/node_modules/mkdirp/test/rel.js
new file mode 100644
index 0000000..7985824
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/portfinder/node_modules/mkdirp/test/rel.js
@@ -0,0 +1,32 @@
+var mkdirp = require('../');
+var path = require('path');
+var fs = require('fs');
+var test = require('tap').test;
+
+test('rel', function (t) {
+    t.plan(2);
+    var x = Math.floor(Math.random() * Math.pow(16,4)).toString(16);
+    var y = Math.floor(Math.random() * Math.pow(16,4)).toString(16);
+    var z = Math.floor(Math.random() * Math.pow(16,4)).toString(16);
+    
+    var cwd = process.cwd();
+    process.chdir('/tmp');
+    
+    var file = [x,y,z].join('/');
+    
+    mkdirp(file, 0755, function (err) {
+        if (err) t.fail(err);
+        else path.exists(file, function (ex) {
+            if (!ex) t.fail('file not created')
+            else fs.stat(file, function (err, stat) {
+                if (err) t.fail(err)
+                else {
+                    process.chdir(cwd);
+                    t.equal(stat.mode & 0777, 0755);
+                    t.ok(stat.isDirectory(), 'target not a directory');
+                    t.end();
+                }
+            })
+        })
+    });
+});
diff --git a/node_modules/node-firefox-start-simulator/node_modules/portfinder/package.json b/node_modules/node-firefox-start-simulator/node_modules/portfinder/package.json
new file mode 100644
index 0000000..ccceb2a
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/portfinder/package.json
@@ -0,0 +1,60 @@
+{
+  "name": "portfinder",
+  "version": "0.2.1",
+  "description": "A simple tool to find an open port on the current machine",
+  "author": {
+    "name": "Charlie Robbins",
+    "email": "charlie.robbins@gmail.com"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git@github.com:indexzero/node-portfinder.git"
+  },
+  "keywords": [
+    "http",
+    "ports",
+    "utilities"
+  ],
+  "dependencies": {
+    "mkdirp": "0.0.x"
+  },
+  "devDependencies": {
+    "async": "0.1.x",
+    "vows": "0.5.x"
+  },
+  "main": "./lib/portfinder",
+  "scripts": {
+    "test": "vows test/*-test.js --spec"
+  },
+  "engines": {
+    "node": ">= 0.4.0"
+  },
+  "_npmUser": {
+    "name": "indexzero",
+    "email": "charlie.robbins@gmail.com"
+  },
+  "_id": "portfinder@0.2.1",
+  "_engineSupported": true,
+  "_npmVersion": "1.0.103",
+  "_nodeVersion": "v0.4.12",
+  "_defaultsLoaded": true,
+  "dist": {
+    "shasum": "b2b9b0164f9e17fa3a9c7db2304d0a75140c71ad",
+    "tarball": "http://registry.npmjs.org/portfinder/-/portfinder-0.2.1.tgz"
+  },
+  "maintainers": [
+    {
+      "name": "indexzero",
+      "email": "charlie.robbins@gmail.com"
+    }
+  ],
+  "directories": {},
+  "_shasum": "b2b9b0164f9e17fa3a9c7db2304d0a75140c71ad",
+  "_from": "portfinder@^0.2.1",
+  "_resolved": "https://registry.npmjs.org/portfinder/-/portfinder-0.2.1.tgz",
+  "bugs": {
+    "url": "https://github.com/indexzero/node-portfinder/issues"
+  },
+  "readme": "ERROR: No README data found!",
+  "homepage": "https://github.com/indexzero/node-portfinder"
+}
diff --git a/node_modules/node-firefox-start-simulator/node_modules/portfinder/test/fixtures/.gitkeep b/node_modules/node-firefox-start-simulator/node_modules/portfinder/test/fixtures/.gitkeep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/portfinder/test/fixtures/.gitkeep
diff --git a/node_modules/node-firefox-start-simulator/node_modules/portfinder/test/port-finder-socket-test.js b/node_modules/node-firefox-start-simulator/node_modules/portfinder/test/port-finder-socket-test.js
new file mode 100644
index 0000000..91c840c
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/portfinder/test/port-finder-socket-test.js
@@ -0,0 +1,92 @@
+/*
+ * portfinder-test.js: Tests for the `portfinder` module.
+ *
+ * (C) 2011, Charlie Robbins
+ *
+ */
+
+var assert = require('assert'),
+    exec = require('child_process').exec,
+    net = require('net'),
+    path = require('path'),
+    async = require('async'),
+    vows = require('vows'),
+    portfinder = require('../lib/portfinder');
+
+var servers = [],
+    socketDir = path.join(__dirname, 'fixtures'),
+    badDir = path.join(__dirname, 'bad-dir');
+
+function createServers (callback) {
+  var base = 0;
+  
+  async.whilst(
+    function () { return base < 5 },
+    function (next) {
+      var server = net.createServer(function () { }),
+          name = base === 0 ? 'test.sock' : 'test' + base + '.sock';
+      
+      server.listen(path.join(socketDir, name), next);
+      base++;
+      servers.push(server);
+    }, callback);
+}
+
+vows.describe('portfinder').addBatch({
+  "When using portfinder module": {
+    "with 5 existing servers": {
+      topic: function () {
+        createServers(this.callback);
+      },
+      "the getPort() method": {
+        topic: function () {
+          portfinder.getSocket({
+            path: path.join(socketDir, 'test.sock')
+          }, this.callback);
+        },
+        "should respond with the first free socket (test5.sock)": function (err, socket) {
+          assert.isTrue(!err);
+          assert.equal(socket, path.join(socketDir, 'test5.sock'));
+        }
+      }
+    }
+  }
+}).addBatch({
+  "When using portfinder module": {
+    "with no existing servers": {
+      "the getSocket() method": {
+        "with a directory that doesnt exist": {
+          topic: function () {
+            var that = this;
+            exec('rm -rf ' + badDir, function () {
+              portfinder.getSocket({
+                path: path.join(badDir, 'test.sock')
+              }, that.callback);
+            });
+          },
+          "should respond with the first free socket (test.sock)": function (err, socket) {
+            assert.isTrue(!err);
+            assert.equal(socket, path.join(badDir, 'test.sock'));
+          }
+        },
+        "with a directory that exists": {
+          topic: function () {
+            portfinder.getSocket({
+              path: path.join(socketDir, 'exists.sock')
+            }, this.callback);
+          },
+          "should respond with the first free socket (exists.sock)": function (err, socket) {
+            assert.isTrue(!err);
+            assert.equal(socket, path.join(socketDir, 'exists.sock'));
+          }
+        }
+      }
+    }
+  }
+}).addBatch({
+  "When the tests are over": {
+    "necessary cleanup should take place": function () {
+      exec('rm -rf ' + badDir + ' ' + path.join(socketDir, '*'), function () { });
+    }
+  }
+}).export(module);
\ No newline at end of file
diff --git a/node_modules/node-firefox-start-simulator/node_modules/portfinder/test/port-finder-test.js b/node_modules/node-firefox-start-simulator/node_modules/portfinder/test/port-finder-test.js
new file mode 100644
index 0000000..c10ded9
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/node_modules/portfinder/test/port-finder-test.js
@@ -0,0 +1,67 @@
+/*
+ * portfinder-test.js: Tests for the `portfinder` module.
+ *
+ * (C) 2011, Charlie Robbins
+ *
+ */
+
+var vows = require('vows'),
+    assert = require('assert'),
+    async = require('async'),
+    http = require('http'),
+    portfinder = require('../lib/portfinder');
+
+var servers = [];
+
+function createServers (callback) {
+  var base = 8000;
+  
+  async.whilst(
+    function () { return base < 8005 },
+    function (next) {
+      var server = http.createServer(function () { });
+      server.listen(base, next);
+      base++;
+      servers.push(server);
+    }, callback);
+}
+
+vows.describe('portfinder').addBatch({
+  "When using portfinder module": {
+    "with 5 existing servers": {
+      topic: function () {
+        createServers(this.callback);
+      },
+      "the getPort() method": {
+        topic: function () {
+          portfinder.getPort(this.callback);
+        },
+        "should respond with the first free port (8005)": function (err, port) {
+          assert.isTrue(!err);
+          assert.equal(port, 8005);
+        }
+      }
+    }
+  }
+}).addBatch({
+  "When using portfinder module": {
+    "with no existing servers": {
+      topic: function () {
+        servers.forEach(function (server) {
+          server.close();
+        });
+        
+        return null;
+      },
+      "the getPort() method": {
+        topic: function () {
+          portfinder.getPort(this.callback);
+        },
+        "should respond with the first free port (8000)": function (err, port) {
+          assert.isTrue(!err);
+          assert.equal(port, 8000);
+        }
+      }
+    }
+  }
+}).export(module);
\ No newline at end of file
diff --git a/node_modules/node-firefox-start-simulator/package.json b/node_modules/node-firefox-start-simulator/package.json
new file mode 100644
index 0000000..3974ff6
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/package.json
@@ -0,0 +1,95 @@
+{
+  "name": "node-firefox-start-simulator",
+  "version": "1.2.0",
+  "description": "Start a Firefox OS simulator",
+  "main": "index.js",
+  "dependencies": {
+    "es6-promise": "^2.0.1",
+    "firefox-client": "^0.3.0",
+    "node-firefox-find-simulators": "^1.0.0",
+    "portfinder": "^0.2.1"
+  },
+  "devDependencies": {
+    "gulp": "^3.8.10",
+    "mocha": "^1.21.4",
+    "should": "^4.0.4",
+    "node-firefox-build-tools": "^0.1.0"
+  },
+  "scripts": {
+    "gulp": "gulp",
+    "test": "gulp test"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/mozilla/node-firefox-start-simulator.git"
+  },
+  "keywords": [
+    "firefox",
+    "developer tools",
+    "b2g",
+    "firefox os",
+    "firefoxos",
+    "fxos",
+    "start"
+  ],
+  "author": {
+    "name": "Mozilla",
+    "url": "https://mozilla.org/"
+  },
+  "contributors": [
+    {
+      "name": "Nicola Greco",
+      "email": "me@nicola.io",
+      "url": "http://nicolagreco.com/"
+    },
+    {
+      "name": "Rodrigo Silveira",
+      "url": "http://blog.rodms.com/"
+    },
+    {
+      "name": "Brittany Storoz",
+      "email": "bstoroz@mozilla.com"
+    },
+    {
+      "name": "Soledad Penadés",
+      "email": "listas@soledadpenades.com",
+      "url": "http://soledadpenades.com/"
+    }
+  ],
+  "license": "Apache 2.0",
+  "bugs": {
+    "url": "https://github.com/mozilla/node-firefox-start-simulator/issues"
+  },
+  "homepage": "https://github.com/mozilla/node-firefox-start-simulator",
+  "gitHead": "2e744c717ef8f99cd5b85c51453a2724b960773e",
+  "_id": "node-firefox-start-simulator@1.2.0",
+  "_shasum": "cb4da6e938a3cc2c2b5c880e0f0c5c27818c8fe0",
+  "_from": "node-firefox-start-simulator@",
+  "_npmVersion": "2.1.4",
+  "_nodeVersion": "0.10.31",
+  "_npmUser": {
+    "name": "brittanystoroz",
+    "email": "brittanystoroz@gmail.com"
+  },
+  "maintainers": [
+    {
+      "name": "brittanystoroz",
+      "email": "brittanystoroz@gmail.com"
+    },
+    {
+      "name": "sole",
+      "email": "listas@soledadpenades.com"
+    },
+    {
+      "name": "tofumatt",
+      "email": "matt@lonelyvegan.com"
+    }
+  ],
+  "dist": {
+    "shasum": "cb4da6e938a3cc2c2b5c880e0f0c5c27818c8fe0",
+    "tarball": "http://registry.npmjs.org/node-firefox-start-simulator/-/node-firefox-start-simulator-1.2.0.tgz"
+  },
+  "directories": {},
+  "_resolved": "https://registry.npmjs.org/node-firefox-start-simulator/-/node-firefox-start-simulator-1.2.0.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-start-simulator/test/test.js b/node_modules/node-firefox-start-simulator/test/test.js
new file mode 100644
index 0000000..b5503ac
--- /dev/null
+++ b/node_modules/node-firefox-start-simulator/test/test.js
@@ -0,0 +1,70 @@
+var assert = require("assert");
+var should = require("should");
+var Start = require("../");
+var Q = require('q');
+
+
+describe('fxos-start', function(){
+  this.timeout(10000)
+
+  describe('when no simulator is open', function(){
+
+    it('should start a simulator', function(done) {
+      Start()
+        .then(function(sim) {
+          process.kill(sim.pid);
+          sim.pid.should.be.type('number');
+          sim.port.should.be.type('number');
+          sim.release.should.be.type('string');
+        })
+        .then(done)
+        .fail(done);
+    });
+
+    it('should match given release', function(done) {
+      Start({release:['2.1']})
+        .then(function(sim) {
+          process.kill(sim.pid);
+          sim.release.should.equal('2.1');
+        })
+        .then(done)
+        .fail(done);
+    });
+
+    it('should match given port', function(done) {
+      Start({port:8081})
+        .then(function(sim) {
+          process.kill(sim.pid);
+          sim.port.should.equal(8081);
+        })
+        .then(done)
+        .fail(done);
+    });
+  });
+
+  describe('when a simulator is open', function(){
+
+    it('opts.force should force close the ones opened', function(done) {
+      var first = Start({
+        force: true,
+        port: 8081
+      }).fail(done);
+
+      var second = first.then(function(sim) {
+        return Start({force:true, port:8082});
+      }).fail(done);
+
+      Q.all([first, second])
+        .spread(function(sim1, sim2) {
+          sim2.pid.should.not.equal(sim1.pid);
+          sim2.port.should.equal(8082);
+          process.kill(sim2.pid);
+          
+        })
+        .then(done)
+        .fail(done);
+    });
+
+
+  });
+});
diff --git a/node_modules/node-firefox-uninstall-app/.npmignore b/node_modules/node-firefox-uninstall-app/.npmignore
new file mode 100644
index 0000000..9daa824
--- /dev/null
+++ b/node_modules/node-firefox-uninstall-app/.npmignore
@@ -0,0 +1,2 @@
+.DS_Store
+node_modules
diff --git a/node_modules/node-firefox-uninstall-app/LICENSE b/node_modules/node-firefox-uninstall-app/LICENSE
new file mode 100644
index 0000000..a7c6b5e
--- /dev/null
+++ b/node_modules/node-firefox-uninstall-app/LICENSE
@@ -0,0 +1,13 @@
+Copyright 2015 Mozilla
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/node_modules/node-firefox-uninstall-app/README.md b/node_modules/node-firefox-uninstall-app/README.md
new file mode 100644
index 0000000..80201e6
--- /dev/null
+++ b/node_modules/node-firefox-uninstall-app/README.md
@@ -0,0 +1,106 @@
+# node-firefox-uninstall-app [![Build Status](https://secure.travis-ci.org/mozilla/node-firefox-uninstall-app.png?branch=master)](http://travis-ci.org/mozilla/node-firefox-uninstall-app)
+
+> Uninstall an installed app on a runtime.
+
+This is part of the [node-firefox](https://github.com/mozilla/node-firefox) project.
+
+*NOTE: This module is super experimental and the API is not totally stable yet. Use under your own responsibility.*
+
+## Installation
+
+### From git
+
+```bash
+git clone https://github.com/mozilla/node-firefox-uninstall-app.git
+cd node-firefox-uninstall-app
+npm install
+```
+
+If you want to update later on:
+
+```bash
+cd node-firefox-uninstall-app
+git pull origin master
+npm install
+```
+
+### npm
+
+```bash
+npm install node-firefox-uninstall-app
+```
+
+## Usage
+
+```javascript
+uninstallApp(options) // returns a Promise
+```
+
+where `options` is a plain `Object` which must contain the following:
+
+* `manifestURL`: the manifest URL *in the client* (you must have obtained this after a call to <a href="https://github.com/mozilla/node-firefox-find-app"><tt>node-firefox-find-app</tt></a>. It's something that looks like: `manifestURL: 'app://13ab1444-736d-8c4b-83a6-b83afb5f1ea4/manifest.webapp'` in the result from `findApp`.
+* `client`: the remote client from where we want to uninstall this app
+
+If no `options` are provided, or if `options` is an empty `Object` (`{}`), then `uninstallApp` will fail (how can you uninstall *you don't know what app exactly* from *you don't know where*?)
+
+
+### Installing and uninstalling a packaged app on a simulator
+
+```javascript
+startSimulator().then(function(simulator) {
+  connect(simulator.port).then(function(client) {
+    findApp({ client: client, manifest: manifest }).then(function(apps) {
+      console.log('Found', apps.length, 'apps');
+      Promise.all(apps.map(function(app) {
+        console.log('Uninstalling', app.manifestURL);
+        return uninstallApp({ manifestURL: app.manifestURL, client: client });
+      })).then(function(results) {
+        console.log('Installing', appPath);
+        installApp({ appPath: appPath, client: client }).then(function() {
+          findApp({ client: client, manifest: manifest }).then(function(app) {
+            var firstApp = app[0];
+            launchApp({ client: client, manifestURL: firstApp.manifestURL }).then(function(done) {
+              console.log('Launched app');
+            }, function(err) {
+              console.error('App could not be launched', err);
+            });
+          });
+        });
+      });
+    });
+  });
+});
+
+
+```
+
+You can have a look at the `examples` folder for a complete example.
+
+## Running the tests
+
+After installing, you can simply run the following from the module folder:
+
+```bash
+npm test
+```
+
+To add a new unit test file, create a new file in the `tests/unit` folder. Any file that matches `test.*.js` will be run as a test by the appropriate test runner, based on the folder location.
+
+We use `gulp` behind the scenes to run the test; if you don't have it installed globally you can use `npm gulp` from inside the project's root folder to run `gulp`.
+
+### Code quality and style
+
+Because we have multiple contributors working on our projects, we value consistent code styles. It makes it easier to read code written by many people! :-)
+
+Our tests include unit tests as well as code quality ("linting") tests that make sure our test pass a style guide and [JSHint](http://jshint.com/). Instead of submitting code with the wrong indentation or a different style, run the tests and you will be told where your code quality/style differs from ours and instructions on how to fix it.
+
+## License
+
+This program is free software; it is distributed under an
+[Apache License](https://github.com/mozilla/node-firefox-uninstall-app/blob/master/LICENSE).
+
+## Copyright
+
+Copyright (c) 2015 [Mozilla](https://mozilla.org)
+([Contributors](https://github.com/mozilla/node-firefox-uninstall-app/graphs/contributors)).
+
diff --git a/node_modules/node-firefox-uninstall-app/examples/sampleApp/app.js b/node_modules/node-firefox-uninstall-app/examples/sampleApp/app.js
new file mode 100644
index 0000000..dc7e45e
--- /dev/null
+++ b/node_modules/node-firefox-uninstall-app/examples/sampleApp/app.js
@@ -0,0 +1,3 @@
+window.addEventListener("load", function() {
+  console.log("Hello World!");
+});
diff --git a/node_modules/node-firefox-uninstall-app/examples/sampleApp/icons/icon128x128.png b/node_modules/node-firefox-uninstall-app/examples/sampleApp/icons/icon128x128.png
new file mode 100644
index 0000000..7db00d7
--- /dev/null
+++ b/node_modules/node-firefox-uninstall-app/examples/sampleApp/icons/icon128x128.png
Binary files differ
diff --git a/node_modules/node-firefox-uninstall-app/examples/sampleApp/icons/icon16x16.png b/node_modules/node-firefox-uninstall-app/examples/sampleApp/icons/icon16x16.png
new file mode 100644
index 0000000..1e751ce
--- /dev/null
+++ b/node_modules/node-firefox-uninstall-app/examples/sampleApp/icons/icon16x16.png
Binary files differ
diff --git a/node_modules/node-firefox-uninstall-app/examples/sampleApp/icons/icon48x48.png b/node_modules/node-firefox-uninstall-app/examples/sampleApp/icons/icon48x48.png
new file mode 100644
index 0000000..acc33ff
--- /dev/null
+++ b/node_modules/node-firefox-uninstall-app/examples/sampleApp/icons/icon48x48.png
Binary files differ
diff --git a/node_modules/node-firefox-uninstall-app/examples/sampleApp/icons/icon60x60.png b/node_modules/node-firefox-uninstall-app/examples/sampleApp/icons/icon60x60.png
new file mode 100644
index 0000000..bc14314
--- /dev/null
+++ b/node_modules/node-firefox-uninstall-app/examples/sampleApp/icons/icon60x60.png
Binary files differ
diff --git a/node_modules/node-firefox-uninstall-app/examples/sampleApp/index.html b/node_modules/node-firefox-uninstall-app/examples/sampleApp/index.html
new file mode 100644
index 0000000..736382c
--- /dev/null
+++ b/node_modules/node-firefox-uninstall-app/examples/sampleApp/index.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1">
+
+    <title>node-firefox-uninstall-app</title>
+
+    <style>
+      body {
+        border: 1px solid black;
+      }
+    </style>
+
+    <!-- Inline scripts are forbidden in Firefox OS apps (CSP restrictions),
+         so we use a script file. -->
+    <script src="app.js" defer></script>
+
+  </head>
+
+  <body>
+    <!-- This code is in the public domain. Enjoy! -->
+    <h1><tt>node-firefox-uninstall-app</tt></h1>
+    <p>This is a simple test app for demonstrating how to use this module.</p>
+  </body>
+
+</html>
diff --git a/node_modules/node-firefox-uninstall-app/examples/sampleApp/manifest.webapp b/node_modules/node-firefox-uninstall-app/examples/sampleApp/manifest.webapp
new file mode 100644
index 0000000..cf24baa
--- /dev/null
+++ b/node_modules/node-firefox-uninstall-app/examples/sampleApp/manifest.webapp
@@ -0,0 +1,11 @@
+{
+  "name": "node-firefox-uninstall-app",
+  "description": "A test app for this module",
+  "launch_path": "/index.html",
+  "icons": {
+    "16": "/icons/icon16x16.png",
+    "48": "/icons/icon48x48.png",
+    "60": "/icons/icon60x60.png",
+    "128": "/icons/icon128x128.png"
+  }
+}
diff --git a/node_modules/node-firefox-uninstall-app/examples/usage.js b/node_modules/node-firefox-uninstall-app/examples/usage.js
new file mode 100644
index 0000000..2f803ec
--- /dev/null
+++ b/node_modules/node-firefox-uninstall-app/examples/usage.js
@@ -0,0 +1,55 @@
+'use strict';
+
+var path = require('path');
+var fs = require('fs');
+// See https://github.com/jshint/jshint/issues/1747 for context
+/* global -Promise */
+var Promise = require('es6-promise').Promise;
+var startSimulator = require('node-firefox-start-simulator');
+var connect = require('node-firefox-connect');
+var installApp = require('node-firefox-install-app');
+var findApp = require('node-firefox-find-app');
+var launchApp = require('node-firefox-launch-app');
+var uninstallApp = require('..');
+
+var appPath = path.join(__dirname, 'sampleApp');
+var manifest = loadJSON(path.join(appPath, 'manifest.webapp'));
+
+// This example will start a Firefox OS simulator,
+// find if the sample app is already installed,
+// then uninstall it,
+// install it again,
+// then find and launch the installed app.
+// You will need to have at least a simulator
+// already installed!
+
+
+startSimulator().then(function(simulator) {
+  connect(simulator.port).then(function(client) {
+    findApp({ client: client, manifest: manifest }).then(function(apps) {
+      console.log('Found', apps.length, 'apps');
+      Promise.all(apps.map(function(app) {
+        console.log('Uninstalling', app.manifestURL);
+        return uninstallApp({ manifestURL: app.manifestURL, client: client });
+      })).then(function(results) {
+        console.log('Installing', appPath);
+        installApp({ appPath: appPath, client: client }).then(function() {
+          findApp({ client: client, manifest: manifest }).then(function(app) {
+            var firstApp = app[0];
+            launchApp({ client: client, manifestURL: firstApp.manifestURL }).then(function(done) {
+              console.log('Launched app');
+            }, function(err) {
+              console.error('App could not be launched', err);
+            });
+          });
+        });
+      });
+    });
+  });
+});
+
+
+function loadJSON(path) {
+  var data = fs.readFileSync(path, 'utf8');
+  return JSON.parse(data);
+}
diff --git a/node_modules/node-firefox-uninstall-app/gulpfile.js b/node_modules/node-firefox-uninstall-app/gulpfile.js
new file mode 100644
index 0000000..75941db
--- /dev/null
+++ b/node_modules/node-firefox-uninstall-app/gulpfile.js
@@ -0,0 +1,4 @@
+var gulp = require('gulp');
+var buildTools = require('node-firefox-build-tools');
+
+buildTools.loadGulpTasks(gulp);
diff --git a/node_modules/node-firefox-uninstall-app/index.js b/node_modules/node-firefox-uninstall-app/index.js
new file mode 100644
index 0000000..f430c9c
--- /dev/null
+++ b/node_modules/node-firefox-uninstall-app/index.js
@@ -0,0 +1,61 @@
+'use strict';
+
+// See https://github.com/jshint/jshint/issues/1747 for context
+/* global -Promise */
+var Promise = require('es6-Promise').Promise;
+
+module.exports = function(options) {
+
+  options = options || {};
+
+  var manifestURL = options.manifestURL;
+  var client = options.client;
+
+  return new Promise(function(resolve, reject) {
+
+    if (manifestURL === undefined || client === undefined) {
+      return reject(new Error('App manifestURL and client are required to uninstall an app'));
+    }
+
+    getWebAppsActor(client).then(function(webAppsActor) {
+      uninstallApp(webAppsActor, manifestURL)
+        .then(function(result) {
+          resolve(result);
+        }, function(err) {
+          reject(err);
+        });
+    });
+
+  });
+
+};
+
+
+function getWebAppsActor(client) {
+  return new Promise(function(resolve, reject) {
+
+    client.getWebapps(function(err, webAppsActor) {
+      if (err) {
+        return reject(err);
+      }
+      resolve(webAppsActor);
+    });
+
+  });
+}
+
+
+function uninstallApp(webAppsActor, appManifestURL) {
+
+  return new Promise(function(resolve, reject) {
+    webAppsActor.uninstall(appManifestURL, function(err, response) {
+      if (err) {
+        return reject(err);
+      }
+      resolve(response);
+    });
+
+  });
+}
+
+
diff --git a/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/.release.json b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/.release.json
new file mode 100644
index 0000000..dee8cbc
--- /dev/null
+++ b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/.release.json
@@ -0,0 +1,17 @@
+{
+  "non-interactive": true,
+  "dry-run": false,
+  "verbose": false,
+  "force": false,
+  "pkgFiles": ["package.json", "bower.json"],
+  "increment": "patch",
+  "commitMessage": "Release %s",
+  "tagName": "%s",
+  "tagAnnotation": "Release %s",
+  "buildCommand": "npm run-script build-all",
+  "distRepo": "git@github.com:components/rsvp.js.git",
+  "distStageDir": "tmp/stage",
+  "distBase": "dist",
+  "distFiles": ["**/*", "../package.json", "../bower.json"],
+  "publish": false
+}
diff --git a/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/Brocfile.js b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/Brocfile.js
new file mode 100644
index 0000000..d34f458
--- /dev/null
+++ b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/Brocfile.js
@@ -0,0 +1,75 @@
+/* jshint node:true, undef:true, unused:true */
+var AMDFormatter     = require('es6-module-transpiler-amd-formatter');
+var closureCompiler  = require('broccoli-closure-compiler');
+var compileModules   = require('broccoli-compile-modules');
+var mergeTrees       = require('broccoli-merge-trees');
+var moveFile         = require('broccoli-file-mover');
+var es3Recast        = require('broccoli-es3-safe-recast');
+var concat           = require('broccoli-concat');
+var replace          = require('broccoli-string-replace');
+var calculateVersion = require('./lib/calculateVersion');
+var path             = require('path');
+var trees            = [];
+var env              = process.env.EMBER_ENV || 'development';
+
+var bundle = compileModules('lib', {
+  inputFiles: ['es6-promise.umd.js'],
+  output: '/es6-promise.js',
+  formatter: 'bundle',
+});
+
+trees.push(bundle);
+trees.push(compileModules('lib', {
+  inputFiles: ['**/*.js'],
+  output: '/amd/',
+  formatter: new AMDFormatter()
+}));
+
+if (env === 'production') {
+  trees.push(closureCompiler(moveFile(bundle, {
+    srcFile: 'es6-promise.js',
+    destFile: 'es6-promise.min.js'
+  }), {
+    compilation_level: 'ADVANCED_OPTIMIZATIONS',
+    externs: ['node'],
+  }));
+}
+
+var distTree = mergeTrees(trees.concat('config'));
+var distTrees = [];
+
+distTrees.push(concat(distTree, {
+  inputFiles: [
+    'versionTemplate.txt',
+    'es6-promise.js'
+  ],
+  outputFile: '/es6-promise.js'
+}));
+
+if (env === 'production') {
+  distTrees.push(concat(distTree, {
+    inputFiles: [
+      'versionTemplate.txt',
+      'es6-promise.min.js'
+    ],
+    outputFile: '/es6-promise.min.js'
+  }));
+}
+
+if (env !== 'development') {
+  distTrees = distTrees.map(es3Recast);
+}
+
+distTree = mergeTrees(distTrees);
+var distTree = replace(distTree, {
+  files: [
+    'es6-promise.js',
+    'es6-promise.min.js'
+  ],
+  pattern: {
+    match: /VERSION_PLACEHOLDER_STRING/g,
+    replacement: calculateVersion()
+  }
+});
+
+module.exports = distTree;
diff --git a/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/CHANGELOG.md b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/CHANGELOG.md
new file mode 100644
index 0000000..e06b496
--- /dev/null
+++ b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/CHANGELOG.md
@@ -0,0 +1,9 @@
+# Master
+
+# 2.0.0
+
+* re-sync with RSVP. Many large performance improvements and bugfixes.
+
+# 1.0.0
+
+* first subset of RSVP
diff --git a/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/LICENSE b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/LICENSE
new file mode 100644
index 0000000..954ec59
--- /dev/null
+++ b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/README.md b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/README.md
new file mode 100644
index 0000000..b974ce4
--- /dev/null
+++ b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/README.md
@@ -0,0 +1,58 @@
+# ES6-Promise (subset of [rsvp.js](https://github.com/tildeio/rsvp.js))
+
+This is a polyfill of the [ES6 Promise](http://people.mozilla.org/~jorendorff/es6-draft.html#sec-promise-constructor). The implementation is a subset of [rsvp.js](https://github.com/tildeio/rsvp.js), if you're wanting extra features and more debugging options, check out the [full library](https://github.com/tildeio/rsvp.js).
+
+For API details and how to use promises, see the <a href="http://www.html5rocks.com/en/tutorials/es6/promises/">JavaScript Promises HTML5Rocks article</a>.
+
+## Downloads
+
+* [es6-promise](https://es6-promises.s3.amazonaws.com/es6-promise-2.0.1.js)
+* [es6-promise-min](https://es6-promises.s3.amazonaws.com/es6-promise-2.0.1.min.js) (~2.2k gzipped)
+
+## Node.js
+
+To install:
+
+```sh
+npm install es6-promise
+```
+
+To use:
+
+```js
+var Promise = require('es6-promise').Promise;
+```
+
+## Usage in IE<9
+
+`catch` is a reserved word in IE<9, meaning `promise.catch(func)` throws a syntax error. To work around this, you can use a string to access the property as shown in the following example.
+
+However, please remember that such technique is already provided by most common minifiers, making the resulting code safe for old browsers and production:
+
+```js
+promise['catch'](function(err) {
+  // ...
+});
+```
+
+Or use `.then` instead:
+
+```js
+promise.then(undefined, function(err) {
+  // ...
+});
+```
+
+## Auto-polyfill
+
+To polyfill the global environment (either in Node or in the browser via CommonJS) use the following code snippet:
+
+```js
+require('es6-promise').polyfill();
+```
+
+Notice that we don't assign the result of `polyfill()` to any variable. The `polyfill()` method will patch the global environment (in this case to the `Promise` name) when called.
+
+## Building & Testing
+
+* `npm run build-all && npm test` - Run Mocha tests through Node and PhantomJS.
diff --git a/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/bin/publish_to_s3.js b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/bin/publish_to_s3.js
new file mode 100755
index 0000000..7daf49a
--- /dev/null
+++ b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/bin/publish_to_s3.js
@@ -0,0 +1,28 @@
+#!/usr/bin/env node
+
+// To invoke this from the commandline you need the following to env vars to exist:
+//
+// S3_BUCKET_NAME
+// TRAVIS_BRANCH
+// TRAVIS_TAG
+// TRAVIS_COMMIT
+// S3_SECRET_ACCESS_KEY
+// S3_ACCESS_KEY_ID
+//
+// Once you have those you execute with the following:
+//
+// ```sh
+// ./bin/publish_to_s3.js
+// ```
+var S3Publisher = require('ember-publisher');
+var configPath = require('path').join(__dirname, '../config/s3ProjectConfig.js');
+publisher = new S3Publisher({ projectConfigPath: configPath });
+
+// Always use wildcard section of project config.
+// This is useful when the including library does not
+// require channels (like in ember.js / ember-data).
+publisher.currentBranch = function() {
+  return (process.env.TRAVIS_BRANCH === 'master') ? 'wildcard' : 'no-op';
+};
+publisher.publish();
+
diff --git a/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/bower.json b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/bower.json
new file mode 100644
index 0000000..84ec5ad
--- /dev/null
+++ b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/bower.json
@@ -0,0 +1,29 @@
+{
+  "name": "es6-promise",
+  "namespace": "Promise",
+  "version": "2.0.1",
+  "description": "A polyfill for ES6-style Promises, tracking rsvp",
+  "authors": [
+    "Stefan Penner <stefan.penner@gmail.com>"
+  ],
+  "main": "dist/es6-promise.js",
+  "keywords": [
+    "promise"
+  ],
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/jakearchibald/ES6-Promises.git"
+  },
+  "bugs": {
+    "url": "https://github.com/jakearchibald/ES6-Promises/issues"
+  },
+  "license": "MIT",
+  "ignore": [
+    "node_modules",
+    "bower_components",
+    "test",
+    "tests",
+    "vendor",
+    "tasks"
+  ]
+}
diff --git a/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/config/s3ProjectConfig.js b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/config/s3ProjectConfig.js
new file mode 100644
index 0000000..5f3349a
--- /dev/null
+++ b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/config/s3ProjectConfig.js
@@ -0,0 +1,26 @@
+/*
+ * Using wildcard because es6-promise does not currently have a
+ * channel system in place.
+ */
+module.exports = function(revision,tag,date){
+  return {
+    'es6-promise.js':
+      { contentType: 'text/javascript',
+        destinations: {
+          wildcard: [
+            'es6-promise-latest.js',
+            'es6-promise-' + revision + '.js'
+          ]
+        }
+      },
+    'es6-promise.min.js':
+      { contentType: 'text/javascript',
+        destinations: {
+          wildcard: [
+            'es6-promise-latest.min.js',
+            'es6-promise-' + revision + '.min.js'
+          ]
+        }
+      }
+  }
+}
diff --git a/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/config/versionTemplate.txt b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/config/versionTemplate.txt
new file mode 100644
index 0000000..999a5fc
--- /dev/null
+++ b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/config/versionTemplate.txt
@@ -0,0 +1,7 @@
+/*!
+ * @overview es6-promise - a tiny implementation of Promises/A+.
+ * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald)
+ * @license   Licensed under MIT license
+ *            See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE
+ * @version   VERSION_PLACEHOLDER_STRING
+ */
diff --git a/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/dist/es6-promise.js b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/dist/es6-promise.js
new file mode 100644
index 0000000..30a6d81
--- /dev/null
+++ b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/dist/es6-promise.js
@@ -0,0 +1,960 @@
+/*!
+ * @overview es6-promise - a tiny implementation of Promises/A+.
+ * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald)
+ * @license   Licensed under MIT license
+ *            See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE
+ * @version   2.0.1
+ */
+
+(function() {
+    "use strict";
+
+    function $$utils$$objectOrFunction(x) {
+      return typeof x === 'function' || (typeof x === 'object' && x !== null);
+    }
+
+    function $$utils$$isFunction(x) {
+      return typeof x === 'function';
+    }
+
+    function $$utils$$isMaybeThenable(x) {
+      return typeof x === 'object' && x !== null;
+    }
+
+    var $$utils$$_isArray;
+
+    if (!Array.isArray) {
+      $$utils$$_isArray = function (x) {
+        return Object.prototype.toString.call(x) === '[object Array]';
+      };
+    } else {
+      $$utils$$_isArray = Array.isArray;
+    }
+
+    var $$utils$$isArray = $$utils$$_isArray;
+    var $$utils$$now = Date.now || function() { return new Date().getTime(); };
+    function $$utils$$F() { }
+
+    var $$utils$$o_create = (Object.create || function (o) {
+      if (arguments.length > 1) {
+        throw new Error('Second argument not supported');
+      }
+      if (typeof o !== 'object') {
+        throw new TypeError('Argument must be an object');
+      }
+      $$utils$$F.prototype = o;
+      return new $$utils$$F();
+    });
+
+    var $$asap$$len = 0;
+
+    var $$asap$$default = function asap(callback, arg) {
+      $$asap$$queue[$$asap$$len] = callback;
+      $$asap$$queue[$$asap$$len + 1] = arg;
+      $$asap$$len += 2;
+      if ($$asap$$len === 2) {
+        // If len is 1, that means that we need to schedule an async flush.
+        // If additional callbacks are queued before the queue is flushed, they
+        // will be processed by this flush that we are scheduling.
+        $$asap$$scheduleFlush();
+      }
+    };
+
+    var $$asap$$browserGlobal = (typeof window !== 'undefined') ? window : {};
+    var $$asap$$BrowserMutationObserver = $$asap$$browserGlobal.MutationObserver || $$asap$$browserGlobal.WebKitMutationObserver;
+
+    // test for web worker but not in IE10
+    var $$asap$$isWorker = typeof Uint8ClampedArray !== 'undefined' &&
+      typeof importScripts !== 'undefined' &&
+      typeof MessageChannel !== 'undefined';
+
+    // node
+    function $$asap$$useNextTick() {
+      return function() {
+        process.nextTick($$asap$$flush);
+      };
+    }
+
+    function $$asap$$useMutationObserver() {
+      var iterations = 0;
+      var observer = new $$asap$$BrowserMutationObserver($$asap$$flush);
+      var node = document.createTextNode('');
+      observer.observe(node, { characterData: true });
+
+      return function() {
+        node.data = (iterations = ++iterations % 2);
+      };
+    }
+
+    // web worker
+    function $$asap$$useMessageChannel() {
+      var channel = new MessageChannel();
+      channel.port1.onmessage = $$asap$$flush;
+      return function () {
+        channel.port2.postMessage(0);
+      };
+    }
+
+    function $$asap$$useSetTimeout() {
+      return function() {
+        setTimeout($$asap$$flush, 1);
+      };
+    }
+
+    var $$asap$$queue = new Array(1000);
+
+    function $$asap$$flush() {
+      for (var i = 0; i < $$asap$$len; i+=2) {
+        var callback = $$asap$$queue[i];
+        var arg = $$asap$$queue[i+1];
+
+        callback(arg);
+
+        $$asap$$queue[i] = undefined;
+        $$asap$$queue[i+1] = undefined;
+      }
+
+      $$asap$$len = 0;
+    }
+
+    var $$asap$$scheduleFlush;
+
+    // Decide what async method to use to triggering processing of queued callbacks:
+    if (typeof process !== 'undefined' && {}.toString.call(process) === '[object process]') {
+      $$asap$$scheduleFlush = $$asap$$useNextTick();
+    } else if ($$asap$$BrowserMutationObserver) {
+      $$asap$$scheduleFlush = $$asap$$useMutationObserver();
+    } else if ($$asap$$isWorker) {
+      $$asap$$scheduleFlush = $$asap$$useMessageChannel();
+    } else {
+      $$asap$$scheduleFlush = $$asap$$useSetTimeout();
+    }
+
+    function $$$internal$$noop() {}
+    var $$$internal$$PENDING   = void 0;
+    var $$$internal$$FULFILLED = 1;
+    var $$$internal$$REJECTED  = 2;
+    var $$$internal$$GET_THEN_ERROR = new $$$internal$$ErrorObject();
+
+    function $$$internal$$selfFullfillment() {
+      return new TypeError("You cannot resolve a promise with itself");
+    }
+
+    function $$$internal$$cannotReturnOwn() {
+      return new TypeError('A promises callback cannot return that same promise.')
+    }
+
+    function $$$internal$$getThen(promise) {
+      try {
+        return promise.then;
+      } catch(error) {
+        $$$internal$$GET_THEN_ERROR.error = error;
+        return $$$internal$$GET_THEN_ERROR;
+      }
+    }
+
+    function $$$internal$$tryThen(then, value, fulfillmentHandler, rejectionHandler) {
+      try {
+        then.call(value, fulfillmentHandler, rejectionHandler);
+      } catch(e) {
+        return e;
+      }
+    }
+
+    function $$$internal$$handleForeignThenable(promise, thenable, then) {
+       $$asap$$default(function(promise) {
+        var sealed = false;
+        var error = $$$internal$$tryThen(then, thenable, function(value) {
+          if (sealed) { return; }
+          sealed = true;
+          if (thenable !== value) {
+            $$$internal$$resolve(promise, value);
+          } else {
+            $$$internal$$fulfill(promise, value);
+          }
+        }, function(reason) {
+          if (sealed) { return; }
+          sealed = true;
+
+          $$$internal$$reject(promise, reason);
+        }, 'Settle: ' + (promise._label || ' unknown promise'));
+
+        if (!sealed && error) {
+          sealed = true;
+          $$$internal$$reject(promise, error);
+        }
+      }, promise);
+    }
+
+    function $$$internal$$handleOwnThenable(promise, thenable) {
+      if (thenable._state === $$$internal$$FULFILLED) {
+        $$$internal$$fulfill(promise, thenable._result);
+      } else if (promise._state === $$$internal$$REJECTED) {
+        $$$internal$$reject(promise, thenable._result);
+      } else {
+        $$$internal$$subscribe(thenable, undefined, function(value) {
+          $$$internal$$resolve(promise, value);
+        }, function(reason) {
+          $$$internal$$reject(promise, reason);
+        });
+      }
+    }
+
+    function $$$internal$$handleMaybeThenable(promise, maybeThenable) {
+      if (maybeThenable.constructor === promise.constructor) {
+        $$$internal$$handleOwnThenable(promise, maybeThenable);
+      } else {
+        var then = $$$internal$$getThen(maybeThenable);
+
+        if (then === $$$internal$$GET_THEN_ERROR) {
+          $$$internal$$reject(promise, $$$internal$$GET_THEN_ERROR.error);
+        } else if (then === undefined) {
+          $$$internal$$fulfill(promise, maybeThenable);
+        } else if ($$utils$$isFunction(then)) {
+          $$$internal$$handleForeignThenable(promise, maybeThenable, then);
+        } else {
+          $$$internal$$fulfill(promise, maybeThenable);
+        }
+      }
+    }
+
+    function $$$internal$$resolve(promise, value) {
+      if (promise === value) {
+        $$$internal$$reject(promise, $$$internal$$selfFullfillment());
+      } else if ($$utils$$objectOrFunction(value)) {
+        $$$internal$$handleMaybeThenable(promise, value);
+      } else {
+        $$$internal$$fulfill(promise, value);
+      }
+    }
+
+    function $$$internal$$publishRejection(promise) {
+      if (promise._onerror) {
+        promise._onerror(promise._result);
+      }
+
+      $$$internal$$publish(promise);
+    }
+
+    function $$$internal$$fulfill(promise, value) {
+      if (promise._state !== $$$internal$$PENDING) { return; }
+
+      promise._result = value;
+      promise._state = $$$internal$$FULFILLED;
+
+      if (promise._subscribers.length === 0) {
+      } else {
+        $$asap$$default($$$internal$$publish, promise);
+      }
+    }
+
+    function $$$internal$$reject(promise, reason) {
+      if (promise._state !== $$$internal$$PENDING) { return; }
+      promise._state = $$$internal$$REJECTED;
+      promise._result = reason;
+
+      $$asap$$default($$$internal$$publishRejection, promise);
+    }
+
+    function $$$internal$$subscribe(parent, child, onFulfillment, onRejection) {
+      var subscribers = parent._subscribers;
+      var length = subscribers.length;
+
+      parent._onerror = null;
+
+      subscribers[length] = child;
+      subscribers[length + $$$internal$$FULFILLED] = onFulfillment;
+      subscribers[length + $$$internal$$REJECTED]  = onRejection;
+
+      if (length === 0 && parent._state) {
+        $$asap$$default($$$internal$$publish, parent);
+      }
+    }
+
+    function $$$internal$$publish(promise) {
+      var subscribers = promise._subscribers;
+      var settled = promise._state;
+
+      if (subscribers.length === 0) { return; }
+
+      var child, callback, detail = promise._result;
+
+      for (var i = 0; i < subscribers.length; i += 3) {
+        child = subscribers[i];
+        callback = subscribers[i + settled];
+
+        if (child) {
+          $$$internal$$invokeCallback(settled, child, callback, detail);
+        } else {
+          callback(detail);
+        }
+      }
+
+      promise._subscribers.length = 0;
+    }
+
+    function $$$internal$$ErrorObject() {
+      this.error = null;
+    }
+
+    var $$$internal$$TRY_CATCH_ERROR = new $$$internal$$ErrorObject();
+
+    function $$$internal$$tryCatch(callback, detail) {
+      try {
+        return callback(detail);
+      } catch(e) {
+        $$$internal$$TRY_CATCH_ERROR.error = e;
+        return $$$internal$$TRY_CATCH_ERROR;
+      }
+    }
+
+    function $$$internal$$invokeCallback(settled, promise, callback, detail) {
+      var hasCallback = $$utils$$isFunction(callback),
+          value, error, succeeded, failed;
+
+      if (hasCallback) {
+        value = $$$internal$$tryCatch(callback, detail);
+
+        if (value === $$$internal$$TRY_CATCH_ERROR) {
+          failed = true;
+          error = value.error;
+          value = null;
+        } else {
+          succeeded = true;
+        }
+
+        if (promise === value) {
+          $$$internal$$reject(promise, $$$internal$$cannotReturnOwn());
+          return;
+        }
+
+      } else {
+        value = detail;
+        succeeded = true;
+      }
+
+      if (promise._state !== $$$internal$$PENDING) {
+        // noop
+      } else if (hasCallback && succeeded) {
+        $$$internal$$resolve(promise, value);
+      } else if (failed) {
+        $$$internal$$reject(promise, error);
+      } else if (settled === $$$internal$$FULFILLED) {
+        $$$internal$$fulfill(promise, value);
+      } else if (settled === $$$internal$$REJECTED) {
+        $$$internal$$reject(promise, value);
+      }
+    }
+
+    function $$$internal$$initializePromise(promise, resolver) {
+      try {
+        resolver(function resolvePromise(value){
+          $$$internal$$resolve(promise, value);
+        }, function rejectPromise(reason) {
+          $$$internal$$reject(promise, reason);
+        });
+      } catch(e) {
+        $$$internal$$reject(promise, e);
+      }
+    }
+
+    function $$$enumerator$$makeSettledResult(state, position, value) {
+      if (state === $$$internal$$FULFILLED) {
+        return {
+          state: 'fulfilled',
+          value: value
+        };
+      } else {
+        return {
+          state: 'rejected',
+          reason: value
+        };
+      }
+    }
+
+    function $$$enumerator$$Enumerator(Constructor, input, abortOnReject, label) {
+      this._instanceConstructor = Constructor;
+      this.promise = new Constructor($$$internal$$noop, label);
+      this._abortOnReject = abortOnReject;
+
+      if (this._validateInput(input)) {
+        this._input     = input;
+        this.length     = input.length;
+        this._remaining = input.length;
+
+        this._init();
+
+        if (this.length === 0) {
+          $$$internal$$fulfill(this.promise, this._result);
+        } else {
+          this.length = this.length || 0;
+          this._enumerate();
+          if (this._remaining === 0) {
+            $$$internal$$fulfill(this.promise, this._result);
+          }
+        }
+      } else {
+        $$$internal$$reject(this.promise, this._validationError());
+      }
+    }
+
+    $$$enumerator$$Enumerator.prototype._validateInput = function(input) {
+      return $$utils$$isArray(input);
+    };
+
+    $$$enumerator$$Enumerator.prototype._validationError = function() {
+      return new Error('Array Methods must be provided an Array');
+    };
+
+    $$$enumerator$$Enumerator.prototype._init = function() {
+      this._result = new Array(this.length);
+    };
+
+    var $$$enumerator$$default = $$$enumerator$$Enumerator;
+
+    $$$enumerator$$Enumerator.prototype._enumerate = function() {
+      var length  = this.length;
+      var promise = this.promise;
+      var input   = this._input;
+
+      for (var i = 0; promise._state === $$$internal$$PENDING && i < length; i++) {
+        this._eachEntry(input[i], i);
+      }
+    };
+
+    $$$enumerator$$Enumerator.prototype._eachEntry = function(entry, i) {
+      var c = this._instanceConstructor;
+      if ($$utils$$isMaybeThenable(entry)) {
+        if (entry.constructor === c && entry._state !== $$$internal$$PENDING) {
+          entry._onerror = null;
+          this._settledAt(entry._state, i, entry._result);
+        } else {
+          this._willSettleAt(c.resolve(entry), i);
+        }
+      } else {
+        this._remaining--;
+        this._result[i] = this._makeResult($$$internal$$FULFILLED, i, entry);
+      }
+    };
+
+    $$$enumerator$$Enumerator.prototype._settledAt = function(state, i, value) {
+      var promise = this.promise;
+
+      if (promise._state === $$$internal$$PENDING) {
+        this._remaining--;
+
+        if (this._abortOnReject && state === $$$internal$$REJECTED) {
+          $$$internal$$reject(promise, value);
+        } else {
+          this._result[i] = this._makeResult(state, i, value);
+        }
+      }
+
+      if (this._remaining === 0) {
+        $$$internal$$fulfill(promise, this._result);
+      }
+    };
+
+    $$$enumerator$$Enumerator.prototype._makeResult = function(state, i, value) {
+      return value;
+    };
+
+    $$$enumerator$$Enumerator.prototype._willSettleAt = function(promise, i) {
+      var enumerator = this;
+
+      $$$internal$$subscribe(promise, undefined, function(value) {
+        enumerator._settledAt($$$internal$$FULFILLED, i, value);
+      }, function(reason) {
+        enumerator._settledAt($$$internal$$REJECTED, i, reason);
+      });
+    };
+
+    var $$promise$all$$default = function all(entries, label) {
+      return new $$$enumerator$$default(this, entries, true /* abort on reject */, label).promise;
+    };
+
+    var $$promise$race$$default = function race(entries, label) {
+      /*jshint validthis:true */
+      var Constructor = this;
+
+      var promise = new Constructor($$$internal$$noop, label);
+
+      if (!$$utils$$isArray(entries)) {
+        $$$internal$$reject(promise, new TypeError('You must pass an array to race.'));
+        return promise;
+      }
+
+      var length = entries.length;
+
+      function onFulfillment(value) {
+        $$$internal$$resolve(promise, value);
+      }
+
+      function onRejection(reason) {
+        $$$internal$$reject(promise, reason);
+      }
+
+      for (var i = 0; promise._state === $$$internal$$PENDING && i < length; i++) {
+        $$$internal$$subscribe(Constructor.resolve(entries[i]), undefined, onFulfillment, onRejection);
+      }
+
+      return promise;
+    };
+
+    var $$promise$resolve$$default = function resolve(object, label) {
+      /*jshint validthis:true */
+      var Constructor = this;
+
+      if (object && typeof object === 'object' && object.constructor === Constructor) {
+        return object;
+      }
+
+      var promise = new Constructor($$$internal$$noop, label);
+      $$$internal$$resolve(promise, object);
+      return promise;
+    };
+
+    var $$promise$reject$$default = function reject(reason, label) {
+      /*jshint validthis:true */
+      var Constructor = this;
+      var promise = new Constructor($$$internal$$noop, label);
+      $$$internal$$reject(promise, reason);
+      return promise;
+    };
+
+    var $$es6$promise$promise$$counter = 0;
+
+    function $$es6$promise$promise$$needsResolver() {
+      throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
+    }
+
+    function $$es6$promise$promise$$needsNew() {
+      throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");
+    }
+
+    var $$es6$promise$promise$$default = $$es6$promise$promise$$Promise;
+
+    /**
+      Promise objects represent the eventual result of an asynchronous operation. The
+      primary way of interacting with a promise is through its `then` method, which
+      registers callbacks to receive either a promise’s eventual value or the reason
+      why the promise cannot be fulfilled.
+
+      Terminology
+      -----------
+
+      - `promise` is an object or function with a `then` method whose behavior conforms to this specification.
+      - `thenable` is an object or function that defines a `then` method.
+      - `value` is any legal JavaScript value (including undefined, a thenable, or a promise).
+      - `exception` is a value that is thrown using the throw statement.
+      - `reason` is a value that indicates why a promise was rejected.
+      - `settled` the final resting state of a promise, fulfilled or rejected.
+
+      A promise can be in one of three states: pending, fulfilled, or rejected.
+
+      Promises that are fulfilled have a fulfillment value and are in the fulfilled
+      state.  Promises that are rejected have a rejection reason and are in the
+      rejected state.  A fulfillment value is never a thenable.
+
+      Promises can also be said to *resolve* a value.  If this value is also a
+      promise, then the original promise's settled state will match the value's
+      settled state.  So a promise that *resolves* a promise that rejects will
+      itself reject, and a promise that *resolves* a promise that fulfills will
+      itself fulfill.
+
+
+      Basic Usage:
+      ------------
+
+      ```js
+      var promise = new Promise(function(resolve, reject) {
+        // on success
+        resolve(value);
+
+        // on failure
+        reject(reason);
+      });
+
+      promise.then(function(value) {
+        // on fulfillment
+      }, function(reason) {
+        // on rejection
+      });
+      ```
+
+      Advanced Usage:
+      ---------------
+
+      Promises shine when abstracting away asynchronous interactions such as
+      `XMLHttpRequest`s.
+
+      ```js
+      function getJSON(url) {
+        return new Promise(function(resolve, reject){
+          var xhr = new XMLHttpRequest();
+
+          xhr.open('GET', url);
+          xhr.onreadystatechange = handler;
+          xhr.responseType = 'json';
+          xhr.setRequestHeader('Accept', 'application/json');
+          xhr.send();
+
+          function handler() {
+            if (this.readyState === this.DONE) {
+              if (this.status === 200) {
+                resolve(this.response);
+              } else {
+                reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']'));
+              }
+            }
+          };
+        });
+      }
+
+      getJSON('/posts.json').then(function(json) {
+        // on fulfillment
+      }, function(reason) {
+        // on rejection
+      });
+      ```
+
+      Unlike callbacks, promises are great composable primitives.
+
+      ```js
+      Promise.all([
+        getJSON('/posts'),
+        getJSON('/comments')
+      ]).then(function(values){
+        values[0] // => postsJSON
+        values[1] // => commentsJSON
+
+        return values;
+      });
+      ```
+
+      @class Promise
+      @param {function} resolver
+      Useful for tooling.
+      @constructor
+    */
+    function $$es6$promise$promise$$Promise(resolver) {
+      this._id = $$es6$promise$promise$$counter++;
+      this._state = undefined;
+      this._result = undefined;
+      this._subscribers = [];
+
+      if ($$$internal$$noop !== resolver) {
+        if (!$$utils$$isFunction(resolver)) {
+          $$es6$promise$promise$$needsResolver();
+        }
+
+        if (!(this instanceof $$es6$promise$promise$$Promise)) {
+          $$es6$promise$promise$$needsNew();
+        }
+
+        $$$internal$$initializePromise(this, resolver);
+      }
+    }
+
+    $$es6$promise$promise$$Promise.all = $$promise$all$$default;
+    $$es6$promise$promise$$Promise.race = $$promise$race$$default;
+    $$es6$promise$promise$$Promise.resolve = $$promise$resolve$$default;
+    $$es6$promise$promise$$Promise.reject = $$promise$reject$$default;
+
+    $$es6$promise$promise$$Promise.prototype = {
+      constructor: $$es6$promise$promise$$Promise,
+
+    /**
+      The primary way of interacting with a promise is through its `then` method,
+      which registers callbacks to receive either a promise's eventual value or the
+      reason why the promise cannot be fulfilled.
+
+      ```js
+      findUser().then(function(user){
+        // user is available
+      }, function(reason){
+        // user is unavailable, and you are given the reason why
+      });
+      ```
+
+      Chaining
+      --------
+
+      The return value of `then` is itself a promise.  This second, 'downstream'
+      promise is resolved with the return value of the first promise's fulfillment
+      or rejection handler, or rejected if the handler throws an exception.
+
+      ```js
+      findUser().then(function (user) {
+        return user.name;
+      }, function (reason) {
+        return 'default name';
+      }).then(function (userName) {
+        // If `findUser` fulfilled, `userName` will be the user's name, otherwise it
+        // will be `'default name'`
+      });
+
+      findUser().then(function (user) {
+        throw new Error('Found user, but still unhappy');
+      }, function (reason) {
+        throw new Error('`findUser` rejected and we're unhappy');
+      }).then(function (value) {
+        // never reached
+      }, function (reason) {
+        // if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'.
+        // If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'.
+      });
+      ```
+      If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream.
+
+      ```js
+      findUser().then(function (user) {
+        throw new PedagogicalException('Upstream error');
+      }).then(function (value) {
+        // never reached
+      }).then(function (value) {
+        // never reached
+      }, function (reason) {
+        // The `PedgagocialException` is propagated all the way down to here
+      });
+      ```
+
+      Assimilation
+      ------------
+
+      Sometimes the value you want to propagate to a downstream promise can only be
+      retrieved asynchronously. This can be achieved by returning a promise in the
+      fulfillment or rejection handler. The downstream promise will then be pending
+      until the returned promise is settled. This is called *assimilation*.
+
+      ```js
+      findUser().then(function (user) {
+        return findCommentsByAuthor(user);
+      }).then(function (comments) {
+        // The user's comments are now available
+      });
+      ```
+
+      If the assimliated promise rejects, then the downstream promise will also reject.
+
+      ```js
+      findUser().then(function (user) {
+        return findCommentsByAuthor(user);
+      }).then(function (comments) {
+        // If `findCommentsByAuthor` fulfills, we'll have the value here
+      }, function (reason) {
+        // If `findCommentsByAuthor` rejects, we'll have the reason here
+      });
+      ```
+
+      Simple Example
+      --------------
+
+      Synchronous Example
+
+      ```javascript
+      var result;
+
+      try {
+        result = findResult();
+        // success
+      } catch(reason) {
+        // failure
+      }
+      ```
+
+      Errback Example
+
+      ```js
+      findResult(function(result, err){
+        if (err) {
+          // failure
+        } else {
+          // success
+        }
+      });
+      ```
+
+      Promise Example;
+
+      ```javascript
+      findResult().then(function(result){
+        // success
+      }, function(reason){
+        // failure
+      });
+      ```
+
+      Advanced Example
+      --------------
+
+      Synchronous Example
+
+      ```javascript
+      var author, books;
+
+      try {
+        author = findAuthor();
+        books  = findBooksByAuthor(author);
+        // success
+      } catch(reason) {
+        // failure
+      }
+      ```
+
+      Errback Example
+
+      ```js
+
+      function foundBooks(books) {
+
+      }
+
+      function failure(reason) {
+
+      }
+
+      findAuthor(function(author, err){
+        if (err) {
+          failure(err);
+          // failure
+        } else {
+          try {
+            findBoooksByAuthor(author, function(books, err) {
+              if (err) {
+                failure(err);
+              } else {
+                try {
+                  foundBooks(books);
+                } catch(reason) {
+                  failure(reason);
+                }
+              }
+            });
+          } catch(error) {
+            failure(err);
+          }
+          // success
+        }
+      });
+      ```
+
+      Promise Example;
+
+      ```javascript
+      findAuthor().
+        then(findBooksByAuthor).
+        then(function(books){
+          // found books
+      }).catch(function(reason){
+        // something went wrong
+      });
+      ```
+
+      @method then
+      @param {Function} onFulfilled
+      @param {Function} onRejected
+      Useful for tooling.
+      @return {Promise}
+    */
+      then: function(onFulfillment, onRejection) {
+        var parent = this;
+        var state = parent._state;
+
+        if (state === $$$internal$$FULFILLED && !onFulfillment || state === $$$internal$$REJECTED && !onRejection) {
+          return this;
+        }
+
+        var child = new this.constructor($$$internal$$noop);
+        var result = parent._result;
+
+        if (state) {
+          var callback = arguments[state - 1];
+          $$asap$$default(function(){
+            $$$internal$$invokeCallback(state, child, callback, result);
+          });
+        } else {
+          $$$internal$$subscribe(parent, child, onFulfillment, onRejection);
+        }
+
+        return child;
+      },
+
+    /**
+      `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same
+      as the catch block of a try/catch statement.
+
+      ```js
+      function findAuthor(){
+        throw new Error('couldn't find that author');
+      }
+
+      // synchronous
+      try {
+        findAuthor();
+      } catch(reason) {
+        // something went wrong
+      }
+
+      // async with promises
+      findAuthor().catch(function(reason){
+        // something went wrong
+      });
+      ```
+
+      @method catch
+      @param {Function} onRejection
+      Useful for tooling.
+      @return {Promise}
+    */
+      'catch': function(onRejection) {
+        return this.then(null, onRejection);
+      }
+    };
+
+    var $$es6$promise$polyfill$$default = function polyfill() {
+      var local;
+
+      if (typeof global !== 'undefined') {
+        local = global;
+      } else if (typeof window !== 'undefined' && window.document) {
+        local = window;
+      } else {
+        local = self;
+      }
+
+      var es6PromiseSupport =
+        "Promise" in local &&
+        // Some of these methods are missing from
+        // Firefox/Chrome experimental implementations
+        "resolve" in local.Promise &&
+        "reject" in local.Promise &&
+        "all" in local.Promise &&
+        "race" in local.Promise &&
+        // Older version of the spec had a resolver object
+        // as the arg rather than a function
+        (function() {
+          var resolve;
+          new local.Promise(function(r) { resolve = r; });
+          return $$utils$$isFunction(resolve);
+        }());
+
+      if (!es6PromiseSupport) {
+        local.Promise = $$es6$promise$promise$$default;
+      }
+    };
+
+    var es6$promise$umd$$ES6Promise = {
+      'Promise': $$es6$promise$promise$$default,
+      'polyfill': $$es6$promise$polyfill$$default
+    };
+
+    /* global define:true module:true window: true */
+    if (typeof define === 'function' && define['amd']) {
+      define(function() { return es6$promise$umd$$ES6Promise; });
+    } else if (typeof module !== 'undefined' && module['exports']) {
+      module['exports'] = es6$promise$umd$$ES6Promise;
+    } else if (typeof this !== 'undefined') {
+      this['ES6Promise'] = es6$promise$umd$$ES6Promise;
+    }
+}).call(this);
\ No newline at end of file
diff --git a/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/dist/es6-promise.min.js b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/dist/es6-promise.min.js
new file mode 100644
index 0000000..6e0c99f
--- /dev/null
+++ b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/dist/es6-promise.min.js
@@ -0,0 +1,18 @@
+/*!
+ * @overview es6-promise - a tiny implementation of Promises/A+.
+ * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald)
+ * @license   Licensed under MIT license
+ *            See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE
+ * @version   2.0.1
+ */
+
+(function(){function r(a,b){n[l]=a;n[l+1]=b;l+=2;2===l&&A()}function s(a){return"function"===typeof a}function F(){return function(){process.nextTick(t)}}function G(){var a=0,b=new B(t),c=document.createTextNode("");b.observe(c,{characterData:!0});return function(){c.data=a=++a%2}}function H(){var a=new MessageChannel;a.port1.onmessage=t;return function(){a.port2.postMessage(0)}}function I(){return function(){setTimeout(t,1)}}function t(){for(var a=0;a<l;a+=2)(0,n[a])(n[a+1]),n[a]=void 0,n[a+1]=void 0;
+l=0}function p(){}function J(a,b,c,d){try{a.call(b,c,d)}catch(e){return e}}function K(a,b,c){r(function(a){var e=!1,f=J(c,b,function(c){e||(e=!0,b!==c?q(a,c):m(a,c))},function(b){e||(e=!0,g(a,b))});!e&&f&&(e=!0,g(a,f))},a)}function L(a,b){1===b.a?m(a,b.b):2===a.a?g(a,b.b):u(b,void 0,function(b){q(a,b)},function(b){g(a,b)})}function q(a,b){if(a===b)g(a,new TypeError("You cannot resolve a promise with itself"));else if("function"===typeof b||"object"===typeof b&&null!==b)if(b.constructor===a.constructor)L(a,
+b);else{var c;try{c=b.then}catch(d){v.error=d,c=v}c===v?g(a,v.error):void 0===c?m(a,b):s(c)?K(a,b,c):m(a,b)}else m(a,b)}function M(a){a.f&&a.f(a.b);x(a)}function m(a,b){void 0===a.a&&(a.b=b,a.a=1,0!==a.e.length&&r(x,a))}function g(a,b){void 0===a.a&&(a.a=2,a.b=b,r(M,a))}function u(a,b,c,d){var e=a.e,f=e.length;a.f=null;e[f]=b;e[f+1]=c;e[f+2]=d;0===f&&a.a&&r(x,a)}function x(a){var b=a.e,c=a.a;if(0!==b.length){for(var d,e,f=a.b,g=0;g<b.length;g+=3)d=b[g],e=b[g+c],d?C(c,d,e,f):e(f);a.e.length=0}}function D(){this.error=
+null}function C(a,b,c,d){var e=s(c),f,k,h,l;if(e){try{f=c(d)}catch(n){y.error=n,f=y}f===y?(l=!0,k=f.error,f=null):h=!0;if(b===f){g(b,new TypeError("A promises callback cannot return that same promise."));return}}else f=d,h=!0;void 0===b.a&&(e&&h?q(b,f):l?g(b,k):1===a?m(b,f):2===a&&g(b,f))}function N(a,b){try{b(function(b){q(a,b)},function(b){g(a,b)})}catch(c){g(a,c)}}function k(a,b,c,d){this.n=a;this.c=new a(p,d);this.i=c;this.o(b)?(this.m=b,this.d=this.length=b.length,this.l(),0===this.length?m(this.c,
+this.b):(this.length=this.length||0,this.k(),0===this.d&&m(this.c,this.b))):g(this.c,this.p())}function h(a){O++;this.b=this.a=void 0;this.e=[];if(p!==a){if(!s(a))throw new TypeError("You must pass a resolver function as the first argument to the promise constructor");if(!(this instanceof h))throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");N(this,a)}}var E=Array.isArray?Array.isArray:function(a){return"[object Array]"===
+Object.prototype.toString.call(a)},l=0,w="undefined"!==typeof window?window:{},B=w.MutationObserver||w.WebKitMutationObserver,w="undefined"!==typeof Uint8ClampedArray&&"undefined"!==typeof importScripts&&"undefined"!==typeof MessageChannel,n=Array(1E3),A;A="undefined"!==typeof process&&"[object process]"==={}.toString.call(process)?F():B?G():w?H():I();var v=new D,y=new D;k.prototype.o=function(a){return E(a)};k.prototype.p=function(){return Error("Array Methods must be provided an Array")};k.prototype.l=
+function(){this.b=Array(this.length)};k.prototype.k=function(){for(var a=this.length,b=this.c,c=this.m,d=0;void 0===b.a&&d<a;d++)this.j(c[d],d)};k.prototype.j=function(a,b){var c=this.n;"object"===typeof a&&null!==a?a.constructor===c&&void 0!==a.a?(a.f=null,this.g(a.a,b,a.b)):this.q(c.resolve(a),b):(this.d--,this.b[b]=this.h(a))};k.prototype.g=function(a,b,c){var d=this.c;void 0===d.a&&(this.d--,this.i&&2===a?g(d,c):this.b[b]=this.h(c));0===this.d&&m(d,this.b)};k.prototype.h=function(a){return a};
+k.prototype.q=function(a,b){var c=this;u(a,void 0,function(a){c.g(1,b,a)},function(a){c.g(2,b,a)})};var O=0;h.all=function(a,b){return(new k(this,a,!0,b)).c};h.race=function(a,b){function c(a){q(e,a)}function d(a){g(e,a)}var e=new this(p,b);if(!E(a))return (g(e,new TypeError("You must pass an array to race.")), e);for(var f=a.length,h=0;void 0===e.a&&h<f;h++)u(this.resolve(a[h]),void 0,c,d);return e};h.resolve=function(a,b){if(a&&"object"===typeof a&&a.constructor===this)return a;var c=new this(p,b);
+q(c,a);return c};h.reject=function(a,b){var c=new this(p,b);g(c,a);return c};h.prototype={constructor:h,then:function(a,b){var c=this.a;if(1===c&&!a||2===c&&!b)return this;var d=new this.constructor(p),e=this.b;if(c){var f=arguments[c-1];r(function(){C(c,d,f,e)})}else u(this,d,a,b);return d},"catch":function(a){return this.then(null,a)}};var z={Promise:h,polyfill:function(){var a;a="undefined"!==typeof global?global:"undefined"!==typeof window&&window.document?window:self;"Promise"in a&&"resolve"in
+a.Promise&&"reject"in a.Promise&&"all"in a.Promise&&"race"in a.Promise&&function(){var b;new a.Promise(function(a){b=a});return s(b)}()||(a.Promise=h)}};"function"===typeof define&&define.amd?define(function(){return z}):"undefined"!==typeof module&&module.exports?module.exports=z:"undefined"!==typeof this&&(this.ES6Promise=z)}).call(this);
diff --git a/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/lib/calculateVersion.js b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/lib/calculateVersion.js
new file mode 100644
index 0000000..018e364
--- /dev/null
+++ b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/lib/calculateVersion.js
@@ -0,0 +1,36 @@
+'use strict';
+
+var fs   = require('fs');
+var path = require('path');
+
+module.exports = function () {
+  var packageVersion = require('../package.json').version;
+  var output         = [packageVersion];
+  var gitPath        = path.join(__dirname,'..','.git');
+  var headFilePath   = path.join(gitPath, 'HEAD');
+
+  if (packageVersion.indexOf('+') > -1) {
+    try {
+      if (fs.existsSync(headFilePath)) {
+        var headFile = fs.readFileSync(headFilePath, {encoding: 'utf8'});
+        var branchName = headFile.split('/').slice(-1)[0].trim();
+        var refPath = headFile.split(' ')[1];
+        var branchSHA;
+
+        if (refPath) {
+          var branchPath = path.join(gitPath, refPath.trim());
+          branchSHA  = fs.readFileSync(branchPath);
+        } else {
+          branchSHA = branchName;
+        }
+
+        output.push(branchSHA.slice(0,10));
+      }
+    } catch (err) {
+      console.error(err.stack);
+    }
+    return output.join('.');
+  } else {
+    return packageVersion;
+  }
+};
diff --git a/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/lib/es6-promise.umd.js b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/lib/es6-promise.umd.js
new file mode 100644
index 0000000..cd01553
--- /dev/null
+++ b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/lib/es6-promise.umd.js
@@ -0,0 +1,16 @@
+import Promise from './es6-promise/promise';
+import polyfill from './es6-promise/polyfill';
+
+var ES6Promise = {
+  'Promise': Promise,
+  'polyfill': polyfill
+};
+
+/* global define:true module:true window: true */
+if (typeof define === 'function' && define['amd']) {
+  define(function() { return ES6Promise; });
+} else if (typeof module !== 'undefined' && module['exports']) {
+  module['exports'] = ES6Promise;
+} else if (typeof this !== 'undefined') {
+  this['ES6Promise'] = ES6Promise;
+}
diff --git a/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/lib/es6-promise/-internal.js b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/lib/es6-promise/-internal.js
new file mode 100644
index 0000000..afef22e
--- /dev/null
+++ b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/lib/es6-promise/-internal.js
@@ -0,0 +1,251 @@
+import {
+  objectOrFunction,
+  isFunction
+} from './utils';
+
+import asap from './asap';
+
+function noop() {}
+
+var PENDING   = void 0;
+var FULFILLED = 1;
+var REJECTED  = 2;
+
+var GET_THEN_ERROR = new ErrorObject();
+
+function selfFullfillment() {
+  return new TypeError("You cannot resolve a promise with itself");
+}
+
+function cannotReturnOwn() {
+  return new TypeError('A promises callback cannot return that same promise.')
+}
+
+function getThen(promise) {
+  try {
+    return promise.then;
+  } catch(error) {
+    GET_THEN_ERROR.error = error;
+    return GET_THEN_ERROR;
+  }
+}
+
+function tryThen(then, value, fulfillmentHandler, rejectionHandler) {
+  try {
+    then.call(value, fulfillmentHandler, rejectionHandler);
+  } catch(e) {
+    return e;
+  }
+}
+
+function handleForeignThenable(promise, thenable, then) {
+   asap(function(promise) {
+    var sealed = false;
+    var error = tryThen(then, thenable, function(value) {
+      if (sealed) { return; }
+      sealed = true;
+      if (thenable !== value) {
+        resolve(promise, value);
+      } else {
+        fulfill(promise, value);
+      }
+    }, function(reason) {
+      if (sealed) { return; }
+      sealed = true;
+
+      reject(promise, reason);
+    }, 'Settle: ' + (promise._label || ' unknown promise'));
+
+    if (!sealed && error) {
+      sealed = true;
+      reject(promise, error);
+    }
+  }, promise);
+}
+
+function handleOwnThenable(promise, thenable) {
+  if (thenable._state === FULFILLED) {
+    fulfill(promise, thenable._result);
+  } else if (promise._state === REJECTED) {
+    reject(promise, thenable._result);
+  } else {
+    subscribe(thenable, undefined, function(value) {
+      resolve(promise, value);
+    }, function(reason) {
+      reject(promise, reason);
+    });
+  }
+}
+
+function handleMaybeThenable(promise, maybeThenable) {
+  if (maybeThenable.constructor === promise.constructor) {
+    handleOwnThenable(promise, maybeThenable);
+  } else {
+    var then = getThen(maybeThenable);
+
+    if (then === GET_THEN_ERROR) {
+      reject(promise, GET_THEN_ERROR.error);
+    } else if (then === undefined) {
+      fulfill(promise, maybeThenable);
+    } else if (isFunction(then)) {
+      handleForeignThenable(promise, maybeThenable, then);
+    } else {
+      fulfill(promise, maybeThenable);
+    }
+  }
+}
+
+function resolve(promise, value) {
+  if (promise === value) {
+    reject(promise, selfFullfillment());
+  } else if (objectOrFunction(value)) {
+    handleMaybeThenable(promise, value);
+  } else {
+    fulfill(promise, value);
+  }
+}
+
+function publishRejection(promise) {
+  if (promise._onerror) {
+    promise._onerror(promise._result);
+  }
+
+  publish(promise);
+}
+
+function fulfill(promise, value) {
+  if (promise._state !== PENDING) { return; }
+
+  promise._result = value;
+  promise._state = FULFILLED;
+
+  if (promise._subscribers.length === 0) {
+  } else {
+    asap(publish, promise);
+  }
+}
+
+function reject(promise, reason) {
+  if (promise._state !== PENDING) { return; }
+  promise._state = REJECTED;
+  promise._result = reason;
+
+  asap(publishRejection, promise);
+}
+
+function subscribe(parent, child, onFulfillment, onRejection) {
+  var subscribers = parent._subscribers;
+  var length = subscribers.length;
+
+  parent._onerror = null;
+
+  subscribers[length] = child;
+  subscribers[length + FULFILLED] = onFulfillment;
+  subscribers[length + REJECTED]  = onRejection;
+
+  if (length === 0 && parent._state) {
+    asap(publish, parent);
+  }
+}
+
+function publish(promise) {
+  var subscribers = promise._subscribers;
+  var settled = promise._state;
+
+  if (subscribers.length === 0) { return; }
+
+  var child, callback, detail = promise._result;
+
+  for (var i = 0; i < subscribers.length; i += 3) {
+    child = subscribers[i];
+    callback = subscribers[i + settled];
+
+    if (child) {
+      invokeCallback(settled, child, callback, detail);
+    } else {
+      callback(detail);
+    }
+  }
+
+  promise._subscribers.length = 0;
+}
+
+function ErrorObject() {
+  this.error = null;
+}
+
+var TRY_CATCH_ERROR = new ErrorObject();
+
+function tryCatch(callback, detail) {
+  try {
+    return callback(detail);
+  } catch(e) {
+    TRY_CATCH_ERROR.error = e;
+    return TRY_CATCH_ERROR;
+  }
+}
+
+function invokeCallback(settled, promise, callback, detail) {
+  var hasCallback = isFunction(callback),
+      value, error, succeeded, failed;
+
+  if (hasCallback) {
+    value = tryCatch(callback, detail);
+
+    if (value === TRY_CATCH_ERROR) {
+      failed = true;
+      error = value.error;
+      value = null;
+    } else {
+      succeeded = true;
+    }
+
+    if (promise === value) {
+      reject(promise, cannotReturnOwn());
+      return;
+    }
+
+  } else {
+    value = detail;
+    succeeded = true;
+  }
+
+  if (promise._state !== PENDING) {
+    // noop
+  } else if (hasCallback && succeeded) {
+    resolve(promise, value);
+  } else if (failed) {
+    reject(promise, error);
+  } else if (settled === FULFILLED) {
+    fulfill(promise, value);
+  } else if (settled === REJECTED) {
+    reject(promise, value);
+  }
+}
+
+function initializePromise(promise, resolver) {
+  try {
+    resolver(function resolvePromise(value){
+      resolve(promise, value);
+    }, function rejectPromise(reason) {
+      reject(promise, reason);
+    });
+  } catch(e) {
+    reject(promise, e);
+  }
+}
+
+export {
+  noop,
+  resolve,
+  reject,
+  fulfill,
+  subscribe,
+  publish,
+  publishRejection,
+  initializePromise,
+  invokeCallback,
+  FULFILLED,
+  REJECTED,
+  PENDING
+};
diff --git a/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/lib/es6-promise/asap.js b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/lib/es6-promise/asap.js
new file mode 100644
index 0000000..4872589
--- /dev/null
+++ b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/lib/es6-promise/asap.js
@@ -0,0 +1,82 @@
+var len = 0;
+
+export default function asap(callback, arg) {
+  queue[len] = callback;
+  queue[len + 1] = arg;
+  len += 2;
+  if (len === 2) {
+    // If len is 1, that means that we need to schedule an async flush.
+    // If additional callbacks are queued before the queue is flushed, they
+    // will be processed by this flush that we are scheduling.
+    scheduleFlush();
+  }
+}
+
+var browserGlobal = (typeof window !== 'undefined') ? window : {};
+var BrowserMutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver;
+
+// test for web worker but not in IE10
+var isWorker = typeof Uint8ClampedArray !== 'undefined' &&
+  typeof importScripts !== 'undefined' &&
+  typeof MessageChannel !== 'undefined';
+
+// node
+function useNextTick() {
+  return function() {
+    process.nextTick(flush);
+  };
+}
+
+function useMutationObserver() {
+  var iterations = 0;
+  var observer = new BrowserMutationObserver(flush);
+  var node = document.createTextNode('');
+  observer.observe(node, { characterData: true });
+
+  return function() {
+    node.data = (iterations = ++iterations % 2);
+  };
+}
+
+// web worker
+function useMessageChannel() {
+  var channel = new MessageChannel();
+  channel.port1.onmessage = flush;
+  return function () {
+    channel.port2.postMessage(0);
+  };
+}
+
+function useSetTimeout() {
+  return function() {
+    setTimeout(flush, 1);
+  };
+}
+
+var queue = new Array(1000);
+function flush() {
+  for (var i = 0; i < len; i+=2) {
+    var callback = queue[i];
+    var arg = queue[i+1];
+
+    callback(arg);
+
+    queue[i] = undefined;
+    queue[i+1] = undefined;
+  }
+
+  len = 0;
+}
+
+var scheduleFlush;
+
+// Decide what async method to use to triggering processing of queued callbacks:
+if (typeof process !== 'undefined' && {}.toString.call(process) === '[object process]') {
+  scheduleFlush = useNextTick();
+} else if (BrowserMutationObserver) {
+  scheduleFlush = useMutationObserver();
+} else if (isWorker) {
+  scheduleFlush = useMessageChannel();
+} else {
+  scheduleFlush = useSetTimeout();
+}
diff --git a/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/lib/es6-promise/enumerator.js b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/lib/es6-promise/enumerator.js
new file mode 100644
index 0000000..7d3f803
--- /dev/null
+++ b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/lib/es6-promise/enumerator.js
@@ -0,0 +1,125 @@
+import {
+  isArray,
+  isMaybeThenable
+} from './utils';
+
+import {
+  noop,
+  reject,
+  fulfill,
+  subscribe,
+  FULFILLED,
+  REJECTED,
+  PENDING
+} from './-internal';
+
+export function makeSettledResult(state, position, value) {
+  if (state === FULFILLED) {
+    return {
+      state: 'fulfilled',
+      value: value
+    };
+  } else {
+    return {
+      state: 'rejected',
+      reason: value
+    };
+  }
+}
+
+function Enumerator(Constructor, input, abortOnReject, label) {
+  this._instanceConstructor = Constructor;
+  this.promise = new Constructor(noop, label);
+  this._abortOnReject = abortOnReject;
+
+  if (this._validateInput(input)) {
+    this._input     = input;
+    this.length     = input.length;
+    this._remaining = input.length;
+
+    this._init();
+
+    if (this.length === 0) {
+      fulfill(this.promise, this._result);
+    } else {
+      this.length = this.length || 0;
+      this._enumerate();
+      if (this._remaining === 0) {
+        fulfill(this.promise, this._result);
+      }
+    }
+  } else {
+    reject(this.promise, this._validationError());
+  }
+}
+
+Enumerator.prototype._validateInput = function(input) {
+  return isArray(input);
+};
+
+Enumerator.prototype._validationError = function() {
+  return new Error('Array Methods must be provided an Array');
+};
+
+Enumerator.prototype._init = function() {
+  this._result = new Array(this.length);
+};
+
+export default Enumerator;
+
+Enumerator.prototype._enumerate = function() {
+  var length  = this.length;
+  var promise = this.promise;
+  var input   = this._input;
+
+  for (var i = 0; promise._state === PENDING && i < length; i++) {
+    this._eachEntry(input[i], i);
+  }
+};
+
+Enumerator.prototype._eachEntry = function(entry, i) {
+  var c = this._instanceConstructor;
+  if (isMaybeThenable(entry)) {
+    if (entry.constructor === c && entry._state !== PENDING) {
+      entry._onerror = null;
+      this._settledAt(entry._state, i, entry._result);
+    } else {
+      this._willSettleAt(c.resolve(entry), i);
+    }
+  } else {
+    this._remaining--;
+    this._result[i] = this._makeResult(FULFILLED, i, entry);
+  }
+};
+
+Enumerator.prototype._settledAt = function(state, i, value) {
+  var promise = this.promise;
+
+  if (promise._state === PENDING) {
+    this._remaining--;
+
+    if (this._abortOnReject && state === REJECTED) {
+      reject(promise, value);
+    } else {
+      this._result[i] = this._makeResult(state, i, value);
+    }
+  }
+
+  if (this._remaining === 0) {
+    fulfill(promise, this._result);
+  }
+};
+
+Enumerator.prototype._makeResult = function(state, i, value) {
+  return value;
+};
+
+Enumerator.prototype._willSettleAt = function(promise, i) {
+  var enumerator = this;
+
+  subscribe(promise, undefined, function(value) {
+    enumerator._settledAt(FULFILLED, i, value);
+  }, function(reason) {
+    enumerator._settledAt(REJECTED, i, reason);
+  });
+};
diff --git a/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/lib/es6-promise/polyfill.js b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/lib/es6-promise/polyfill.js
new file mode 100644
index 0000000..e5b0165
--- /dev/null
+++ b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/lib/es6-promise/polyfill.js
@@ -0,0 +1,35 @@
+/*global self*/
+import { default as RSVPPromise } from "./promise";
+import { isFunction } from "./utils";
+
+export default function polyfill() {
+  var local;
+
+  if (typeof global !== 'undefined') {
+    local = global;
+  } else if (typeof window !== 'undefined' && window.document) {
+    local = window;
+  } else {
+    local = self;
+  }
+
+  var es6PromiseSupport =
+    "Promise" in local &&
+    // Some of these methods are missing from
+    // Firefox/Chrome experimental implementations
+    "resolve" in local.Promise &&
+    "reject" in local.Promise &&
+    "all" in local.Promise &&
+    "race" in local.Promise &&
+    // Older version of the spec had a resolver object
+    // as the arg rather than a function
+    (function() {
+      var resolve;
+      new local.Promise(function(r) { resolve = r; });
+      return isFunction(resolve);
+    }());
+
+  if (!es6PromiseSupport) {
+    local.Promise = RSVPPromise;
+  }
+}
diff --git a/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/lib/es6-promise/promise.js b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/lib/es6-promise/promise.js
new file mode 100644
index 0000000..5e865a7
--- /dev/null
+++ b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/lib/es6-promise/promise.js
@@ -0,0 +1,409 @@
+import {
+  isFunction,
+  now
+} from './utils';
+
+import {
+  noop,
+  subscribe,
+  initializePromise,
+  invokeCallback,
+  FULFILLED,
+  REJECTED
+} from './-internal';
+
+import asap from './asap';
+
+import all from './promise/all';
+import race from './promise/race';
+import Resolve from './promise/resolve';
+import Reject from './promise/reject';
+
+var counter = 0;
+
+function needsResolver() {
+  throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
+}
+
+function needsNew() {
+  throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");
+}
+
+export default Promise;
+/**
+  Promise objects represent the eventual result of an asynchronous operation. The
+  primary way of interacting with a promise is through its `then` method, which
+  registers callbacks to receive either a promise’s eventual value or the reason
+  why the promise cannot be fulfilled.
+
+  Terminology
+  -----------
+
+  - `promise` is an object or function with a `then` method whose behavior conforms to this specification.
+  - `thenable` is an object or function that defines a `then` method.
+  - `value` is any legal JavaScript value (including undefined, a thenable, or a promise).
+  - `exception` is a value that is thrown using the throw statement.
+  - `reason` is a value that indicates why a promise was rejected.
+  - `settled` the final resting state of a promise, fulfilled or rejected.
+
+  A promise can be in one of three states: pending, fulfilled, or rejected.
+
+  Promises that are fulfilled have a fulfillment value and are in the fulfilled
+  state.  Promises that are rejected have a rejection reason and are in the
+  rejected state.  A fulfillment value is never a thenable.
+
+  Promises can also be said to *resolve* a value.  If this value is also a
+  promise, then the original promise's settled state will match the value's
+  settled state.  So a promise that *resolves* a promise that rejects will
+  itself reject, and a promise that *resolves* a promise that fulfills will
+  itself fulfill.
+
+
+  Basic Usage:
+  ------------
+
+  ```js
+  var promise = new Promise(function(resolve, reject) {
+    // on success
+    resolve(value);
+
+    // on failure
+    reject(reason);
+  });
+
+  promise.then(function(value) {
+    // on fulfillment
+  }, function(reason) {
+    // on rejection
+  });
+  ```
+
+  Advanced Usage:
+  ---------------
+
+  Promises shine when abstracting away asynchronous interactions such as
+  `XMLHttpRequest`s.
+
+  ```js
+  function getJSON(url) {
+    return new Promise(function(resolve, reject){
+      var xhr = new XMLHttpRequest();
+
+      xhr.open('GET', url);
+      xhr.onreadystatechange = handler;
+      xhr.responseType = 'json';
+      xhr.setRequestHeader('Accept', 'application/json');
+      xhr.send();
+
+      function handler() {
+        if (this.readyState === this.DONE) {
+          if (this.status === 200) {
+            resolve(this.response);
+          } else {
+            reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']'));
+          }
+        }
+      };
+    });
+  }
+
+  getJSON('/posts.json').then(function(json) {
+    // on fulfillment
+  }, function(reason) {
+    // on rejection
+  });
+  ```
+
+  Unlike callbacks, promises are great composable primitives.
+
+  ```js
+  Promise.all([
+    getJSON('/posts'),
+    getJSON('/comments')
+  ]).then(function(values){
+    values[0] // => postsJSON
+    values[1] // => commentsJSON
+
+    return values;
+  });
+  ```
+
+  @class Promise
+  @param {function} resolver
+  Useful for tooling.
+  @constructor
+*/
+function Promise(resolver) {
+  this._id = counter++;
+  this._state = undefined;
+  this._result = undefined;
+  this._subscribers = [];
+
+  if (noop !== resolver) {
+    if (!isFunction(resolver)) {
+      needsResolver();
+    }
+
+    if (!(this instanceof Promise)) {
+      needsNew();
+    }
+
+    initializePromise(this, resolver);
+  }
+}
+
+Promise.all = all;
+Promise.race = race;
+Promise.resolve = Resolve;
+Promise.reject = Reject;
+
+Promise.prototype = {
+  constructor: Promise,
+
+/**
+  The primary way of interacting with a promise is through its `then` method,
+  which registers callbacks to receive either a promise's eventual value or the
+  reason why the promise cannot be fulfilled.
+
+  ```js
+  findUser().then(function(user){
+    // user is available
+  }, function(reason){
+    // user is unavailable, and you are given the reason why
+  });
+  ```
+
+  Chaining
+  --------
+
+  The return value of `then` is itself a promise.  This second, 'downstream'
+  promise is resolved with the return value of the first promise's fulfillment
+  or rejection handler, or rejected if the handler throws an exception.
+
+  ```js
+  findUser().then(function (user) {
+    return user.name;
+  }, function (reason) {
+    return 'default name';
+  }).then(function (userName) {
+    // If `findUser` fulfilled, `userName` will be the user's name, otherwise it
+    // will be `'default name'`
+  });
+
+  findUser().then(function (user) {
+    throw new Error('Found user, but still unhappy');
+  }, function (reason) {
+    throw new Error('`findUser` rejected and we're unhappy');
+  }).then(function (value) {
+    // never reached
+  }, function (reason) {
+    // if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'.
+    // If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'.
+  });
+  ```
+  If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream.
+
+  ```js
+  findUser().then(function (user) {
+    throw new PedagogicalException('Upstream error');
+  }).then(function (value) {
+    // never reached
+  }).then(function (value) {
+    // never reached
+  }, function (reason) {
+    // The `PedgagocialException` is propagated all the way down to here
+  });
+  ```
+
+  Assimilation
+  ------------
+
+  Sometimes the value you want to propagate to a downstream promise can only be
+  retrieved asynchronously. This can be achieved by returning a promise in the
+  fulfillment or rejection handler. The downstream promise will then be pending
+  until the returned promise is settled. This is called *assimilation*.
+
+  ```js
+  findUser().then(function (user) {
+    return findCommentsByAuthor(user);
+  }).then(function (comments) {
+    // The user's comments are now available
+  });
+  ```
+
+  If the assimliated promise rejects, then the downstream promise will also reject.
+
+  ```js
+  findUser().then(function (user) {
+    return findCommentsByAuthor(user);
+  }).then(function (comments) {
+    // If `findCommentsByAuthor` fulfills, we'll have the value here
+  }, function (reason) {
+    // If `findCommentsByAuthor` rejects, we'll have the reason here
+  });
+  ```
+
+  Simple Example
+  --------------
+
+  Synchronous Example
+
+  ```javascript
+  var result;
+
+  try {
+    result = findResult();
+    // success
+  } catch(reason) {
+    // failure
+  }
+  ```
+
+  Errback Example
+
+  ```js
+  findResult(function(result, err){
+    if (err) {
+      // failure
+    } else {
+      // success
+    }
+  });
+  ```
+
+  Promise Example;
+
+  ```javascript
+  findResult().then(function(result){
+    // success
+  }, function(reason){
+    // failure
+  });
+  ```
+
+  Advanced Example
+  --------------
+
+  Synchronous Example
+
+  ```javascript
+  var author, books;
+
+  try {
+    author = findAuthor();
+    books  = findBooksByAuthor(author);
+    // success
+  } catch(reason) {
+    // failure
+  }
+  ```
+
+  Errback Example
+
+  ```js
+
+  function foundBooks(books) {
+
+  }
+
+  function failure(reason) {
+
+  }
+
+  findAuthor(function(author, err){
+    if (err) {
+      failure(err);
+      // failure
+    } else {
+      try {
+        findBoooksByAuthor(author, function(books, err) {
+          if (err) {
+            failure(err);
+          } else {
+            try {
+              foundBooks(books);
+            } catch(reason) {
+              failure(reason);
+            }
+          }
+        });
+      } catch(error) {
+        failure(err);
+      }
+      // success
+    }
+  });
+  ```
+
+  Promise Example;
+
+  ```javascript
+  findAuthor().
+    then(findBooksByAuthor).
+    then(function(books){
+      // found books
+  }).catch(function(reason){
+    // something went wrong
+  });
+  ```
+
+  @method then
+  @param {Function} onFulfilled
+  @param {Function} onRejected
+  Useful for tooling.
+  @return {Promise}
+*/
+  then: function(onFulfillment, onRejection) {
+    var parent = this;
+    var state = parent._state;
+
+    if (state === FULFILLED && !onFulfillment || state === REJECTED && !onRejection) {
+      return this;
+    }
+
+    var child = new this.constructor(noop);
+    var result = parent._result;
+
+    if (state) {
+      var callback = arguments[state - 1];
+      asap(function(){
+        invokeCallback(state, child, callback, result);
+      });
+    } else {
+      subscribe(parent, child, onFulfillment, onRejection);
+    }
+
+    return child;
+  },
+
+/**
+  `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same
+  as the catch block of a try/catch statement.
+
+  ```js
+  function findAuthor(){
+    throw new Error('couldn't find that author');
+  }
+
+  // synchronous
+  try {
+    findAuthor();
+  } catch(reason) {
+    // something went wrong
+  }
+
+  // async with promises
+  findAuthor().catch(function(reason){
+    // something went wrong
+  });
+  ```
+
+  @method catch
+  @param {Function} onRejection
+  Useful for tooling.
+  @return {Promise}
+*/
+  'catch': function(onRejection) {
+    return this.then(null, onRejection);
+  }
+};
diff --git a/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/lib/es6-promise/promise/all.js b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/lib/es6-promise/promise/all.js
new file mode 100644
index 0000000..8db7e71
--- /dev/null
+++ b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/lib/es6-promise/promise/all.js
@@ -0,0 +1,52 @@
+import Enumerator from '../enumerator';
+
+/**
+  `Promise.all` accepts an array of promises, and returns a new promise which
+  is fulfilled with an array of fulfillment values for the passed promises, or
+  rejected with the reason of the first passed promise to be rejected. It casts all
+  elements of the passed iterable to promises as it runs this algorithm.
+
+  Example:
+
+  ```javascript
+  var promise1 = resolve(1);
+  var promise2 = resolve(2);
+  var promise3 = resolve(3);
+  var promises = [ promise1, promise2, promise3 ];
+
+  Promise.all(promises).then(function(array){
+    // The array here would be [ 1, 2, 3 ];
+  });
+  ```
+
+  If any of the `promises` given to `all` are rejected, the first promise
+  that is rejected will be given as an argument to the returned promises's
+  rejection handler. For example:
+
+  Example:
+
+  ```javascript
+  var promise1 = resolve(1);
+  var promise2 = reject(new Error("2"));
+  var promise3 = reject(new Error("3"));
+  var promises = [ promise1, promise2, promise3 ];
+
+  Promise.all(promises).then(function(array){
+    // Code here never runs because there are rejected promises!
+  }, function(error) {
+    // error.message === "2"
+  });
+  ```
+
+  @method all
+  @static
+  @param {Array} entries array of promises
+  @param {String} label optional string for labeling the promise.
+  Useful for tooling.
+  @return {Promise} promise that is fulfilled when all `promises` have been
+  fulfilled, or rejected if any of them become rejected.
+  @static
+*/
+export default function all(entries, label) {
+  return new Enumerator(this, entries, true /* abort on reject */, label).promise;
+}
diff --git a/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/lib/es6-promise/promise/race.js b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/lib/es6-promise/promise/race.js
new file mode 100644
index 0000000..7daa28a
--- /dev/null
+++ b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/lib/es6-promise/promise/race.js
@@ -0,0 +1,105 @@
+import {
+  isArray
+} from "../utils";
+
+import {
+  noop,
+  resolve,
+  reject,
+  subscribe,
+  PENDING
+} from '../-internal';
+
+/**
+  `Promise.race` returns a new promise which is settled in the same way as the
+  first passed promise to settle.
+
+  Example:
+
+  ```javascript
+  var promise1 = new Promise(function(resolve, reject){
+    setTimeout(function(){
+      resolve('promise 1');
+    }, 200);
+  });
+
+  var promise2 = new Promise(function(resolve, reject){
+    setTimeout(function(){
+      resolve('promise 2');
+    }, 100);
+  });
+
+  Promise.race([promise1, promise2]).then(function(result){
+    // result === 'promise 2' because it was resolved before promise1
+    // was resolved.
+  });
+  ```
+
+  `Promise.race` is deterministic in that only the state of the first
+  settled promise matters. For example, even if other promises given to the
+  `promises` array argument are resolved, but the first settled promise has
+  become rejected before the other promises became fulfilled, the returned
+  promise will become rejected:
+
+  ```javascript
+  var promise1 = new Promise(function(resolve, reject){
+    setTimeout(function(){
+      resolve('promise 1');
+    }, 200);
+  });
+
+  var promise2 = new Promise(function(resolve, reject){
+    setTimeout(function(){
+      reject(new Error('promise 2'));
+    }, 100);
+  });
+
+  Promise.race([promise1, promise2]).then(function(result){
+    // Code here never runs
+  }, function(reason){
+    // reason.message === 'promise 2' because promise 2 became rejected before
+    // promise 1 became fulfilled
+  });
+  ```
+
+  An example real-world use case is implementing timeouts:
+
+  ```javascript
+  Promise.race([ajax('foo.json'), timeout(5000)])
+  ```
+
+  @method race
+  @static
+  @param {Array} promises array of promises to observe
+  @param {String} label optional string for describing the promise returned.
+  Useful for tooling.
+  @return {Promise} a promise which settles in the same way as the first passed
+  promise to settle.
+*/
+export default function race(entries, label) {
+  /*jshint validthis:true */
+  var Constructor = this;
+
+  var promise = new Constructor(noop, label);
+
+  if (!isArray(entries)) {
+    reject(promise, new TypeError('You must pass an array to race.'));
+    return promise;
+  }
+
+  var length = entries.length;
+
+  function onFulfillment(value) {
+    resolve(promise, value);
+  }
+
+  function onRejection(reason) {
+    reject(promise, reason);
+  }
+
+  for (var i = 0; promise._state === PENDING && i < length; i++) {
+    subscribe(Constructor.resolve(entries[i]), undefined, onFulfillment, onRejection);
+  }
+
+  return promise;
+}
diff --git a/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/lib/es6-promise/promise/reject.js b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/lib/es6-promise/promise/reject.js
new file mode 100644
index 0000000..518eff7
--- /dev/null
+++ b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/lib/es6-promise/promise/reject.js
@@ -0,0 +1,47 @@
+import {
+  noop,
+  reject as _reject
+} from '../-internal';
+
+/**
+  `Promise.reject` returns a promise rejected with the passed `reason`.
+  It is shorthand for the following:
+
+  ```javascript
+  var promise = new Promise(function(resolve, reject){
+    reject(new Error('WHOOPS'));
+  });
+
+  promise.then(function(value){
+    // Code here doesn't run because the promise is rejected!
+  }, function(reason){
+    // reason.message === 'WHOOPS'
+  });
+  ```
+
+  Instead of writing the above, your code now simply becomes the following:
+
+  ```javascript
+  var promise = Promise.reject(new Error('WHOOPS'));
+
+  promise.then(function(value){
+    // Code here doesn't run because the promise is rejected!
+  }, function(reason){
+    // reason.message === 'WHOOPS'
+  });
+  ```
+
+  @method reject
+  @static
+  @param {Any} reason value that the returned promise will be rejected with.
+  @param {String} label optional string for identifying the returned promise.
+  Useful for tooling.
+  @return {Promise} a promise rejected with the given `reason`.
+*/
+export default function reject(reason, label) {
+  /*jshint validthis:true */
+  var Constructor = this;
+  var promise = new Constructor(noop, label);
+  _reject(promise, reason);
+  return promise;
+}
diff --git a/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/lib/es6-promise/promise/resolve.js b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/lib/es6-promise/promise/resolve.js
new file mode 100644
index 0000000..1b3c533
--- /dev/null
+++ b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/lib/es6-promise/promise/resolve.js
@@ -0,0 +1,49 @@
+import {
+  noop,
+  resolve as _resolve
+} from '../-internal';
+
+/**
+  `Promise.resolve` returns a promise that will become resolved with the
+  passed `value`. It is shorthand for the following:
+
+  ```javascript
+  var promise = new Promise(function(resolve, reject){
+    resolve(1);
+  });
+
+  promise.then(function(value){
+    // value === 1
+  });
+  ```
+
+  Instead of writing the above, your code now simply becomes the following:
+
+  ```javascript
+  var promise = Promise.resolve(1);
+
+  promise.then(function(value){
+    // value === 1
+  });
+  ```
+
+  @method resolve
+  @static
+  @param {Any} value value that the returned promise will be resolved with
+  @param {String} label optional string for identifying the returned promise.
+  Useful for tooling.
+  @return {Promise} a promise that will become fulfilled with the given
+  `value`
+*/
+export default function resolve(object, label) {
+  /*jshint validthis:true */
+  var Constructor = this;
+
+  if (object && typeof object === 'object' && object.constructor === Constructor) {
+    return object;
+  }
+
+  var promise = new Constructor(noop, label);
+  _resolve(promise, object);
+  return promise;
+}
diff --git a/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/lib/es6-promise/utils.js b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/lib/es6-promise/utils.js
new file mode 100644
index 0000000..6b49bbf
--- /dev/null
+++ b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/lib/es6-promise/utils.js
@@ -0,0 +1,39 @@
+export function objectOrFunction(x) {
+  return typeof x === 'function' || (typeof x === 'object' && x !== null);
+}
+
+export function isFunction(x) {
+  return typeof x === 'function';
+}
+
+export function isMaybeThenable(x) {
+  return typeof x === 'object' && x !== null;
+}
+
+var _isArray;
+if (!Array.isArray) {
+  _isArray = function (x) {
+    return Object.prototype.toString.call(x) === '[object Array]';
+  };
+} else {
+  _isArray = Array.isArray;
+}
+
+export var isArray = _isArray;
+
+// Date.now is not available in browsers < IE9
+// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now#Compatibility
+export var now = Date.now || function() { return new Date().getTime(); };
+
+function F() { }
+
+export var o_create = (Object.create || function (o) {
+  if (arguments.length > 1) {
+    throw new Error('Second argument not supported');
+  }
+  if (typeof o !== 'object') {
+    throw new TypeError('Argument must be an object');
+  }
+  F.prototype = o;
+  return new F();
+});
diff --git a/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/package.json b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/package.json
new file mode 100644
index 0000000..28494ce
--- /dev/null
+++ b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/package.json
@@ -0,0 +1,81 @@
+{
+  "name": "es6-promise",
+  "namespace": "es6-promise",
+  "version": "2.0.1",
+  "description": "A lightweight library that provides tools for organizing asynchronous code",
+  "main": "dist/es6-promise.js",
+  "directories": {
+    "lib": "lib"
+  },
+  "devDependencies": {
+    "bower": "^1.3.9",
+    "brfs": "0.0.8",
+    "broccoli-closure-compiler": "^0.2.0",
+    "broccoli-compile-modules": "git+https://github.com/eventualbuddha/broccoli-compile-modules",
+    "broccoli-concat": "0.0.7",
+    "broccoli-es3-safe-recast": "0.0.8",
+    "broccoli-file-mover": "^0.4.0",
+    "broccoli-jshint": "^0.5.1",
+    "broccoli-merge-trees": "^0.1.4",
+    "broccoli-static-compiler": "^0.1.4",
+    "broccoli-string-replace": "0.0.1",
+    "browserify": "^4.2.0",
+    "ember-cli": "0.0.40",
+    "ember-publisher": "0.0.7",
+    "es6-module-transpiler-amd-formatter": "0.0.1",
+    "express": "^4.5.0",
+    "jshint": "~0.9.1",
+    "mkdirp": "^0.5.0",
+    "mocha": "^1.20.1",
+    "promises-aplus-tests": "git://github.com/stefanpenner/promises-tests.git",
+    "release-it": "0.0.10",
+    "testem": "^0.6.17",
+    "json3": "^3.3.2"
+  },
+  "scripts": {
+    "test": "testem ci -R dot",
+    "test-server": "testem",
+    "lint": "jshint lib",
+    "prepublish": "ember build --environment production",
+    "aplus": "browserify test/main.js",
+    "build-all": "ember build --environment production && browserify ./test/main.js -o tmp/test-bundle.js",
+    "dry-run-release": "ember build --environment production && release-it --dry-run --non-interactive"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/jakearchibald/ES6-Promises.git"
+  },
+  "bugs": {
+    "url": "https://github.com/jakearchibald/ES6-Promises/issues"
+  },
+  "keywords": [
+    "promises",
+    "futures"
+  ],
+  "author": {
+    "name": "Yehuda Katz, Tom Dale, Stefan Penner and contributors",
+    "url": "Conversion to ES6 API by Jake Archibald"
+  },
+  "license": "MIT",
+  "homepage": "https://github.com/jakearchibald/ES6-Promises",
+  "_id": "es6-promise@2.0.1",
+  "dist": {
+    "shasum": "ccc4963e679f0ca9fb187c777b9e583d3c7573c2",
+    "tarball": "http://registry.npmjs.org/es6-promise/-/es6-promise-2.0.1.tgz"
+  },
+  "_from": "es6-promise@^2.0.1",
+  "_npmVersion": "1.3.24",
+  "_npmUser": {
+    "name": "jaffathecake",
+    "email": "jaffathecake@gmail.com"
+  },
+  "maintainers": [
+    {
+      "name": "jaffathecake",
+      "email": "jaffathecake@gmail.com"
+    }
+  ],
+  "_shasum": "ccc4963e679f0ca9fb187c777b9e583d3c7573c2",
+  "_resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-2.0.1.tgz",
+  "readme": "ERROR: No README data found!"
+}
diff --git a/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/server/.jshintrc b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/server/.jshintrc
new file mode 100644
index 0000000..c1f2978
--- /dev/null
+++ b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/server/.jshintrc
@@ -0,0 +1,3 @@
+{
+  "node": true
+}
diff --git a/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/server/index.js b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/server/index.js
new file mode 100644
index 0000000..8a54d36
--- /dev/null
+++ b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/server/index.js
@@ -0,0 +1,6 @@
+module.exports = function(app) {
+  app.use(require('express').static(__dirname + '/../'));
+  app.get('/', function(req, res) {
+    res.redirect('/test/');
+  })
+};
diff --git a/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/testem.json b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/testem.json
new file mode 100644
index 0000000..7494912
--- /dev/null
+++ b/node_modules/node-firefox-uninstall-app/node_modules/es6-promise/testem.json
@@ -0,0 +1,11 @@
+{
+  "test_page": "test/index.html",
+  "parallel": 5,
+  "before_tests": "npm run build-all",
+  "launchers": {
+    "Mocha": {
+      "command": "./node_modules/.bin/mocha test/main.js"
+    }
+  },
+  "launch_in_ci":  ["PhantomJS", "Mocha"]
+}
diff --git a/node_modules/node-firefox-uninstall-app/package.json b/node_modules/node-firefox-uninstall-app/package.json
new file mode 100644
index 0000000..fe7da7e
--- /dev/null
+++ b/node_modules/node-firefox-uninstall-app/package.json
@@ -0,0 +1,65 @@
+{
+  "name": "node-firefox-uninstall-app",
+  "version": "1.0.0",
+  "description": "Uninstall an app from a Firefox runtime",
+  "main": "index.js",
+  "scripts": {
+    "gulp": "gulp",
+    "test": "gulp test"
+  },
+  "keywords": [
+    "firefox",
+    "developer tools",
+    "b2g",
+    "firefox os",
+    "fxos",
+    "webapps"
+  ],
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/mozilla/node-firefox-uninstall-app.git"
+  },
+  "author": {
+    "name": "Mozilla",
+    "url": "https://mozilla.org/"
+  },
+  "license": "Apache 2",
+  "bugs": {
+    "url": "https://github.com/mozilla/node-firefox-uninstall-app/issues"
+  },
+  "homepage": "https://github.com/mozilla/node-firefox-uninstall-app/",
+  "dependencies": {
+    "es6-promise": "^2.0.1"
+  },
+  "devDependencies": {
+    "gulp": "^3.8.10",
+    "node-firefox-build-tools": "^0.2.0",
+    "node-firefox-connect": "^1.0.0",
+    "node-firefox-find-app": "^1.0.0",
+    "node-firefox-install-app": "^1.0.0",
+    "node-firefox-launch-app": "^1.0.1",
+    "node-firefox-start-simulator": "^1.1.0"
+  },
+  "gitHead": "ba82ea4aabf0020dddfde639d75f49c1e112e3aa",
+  "_id": "node-firefox-uninstall-app@1.0.0",
+  "_shasum": "557345a0b7f60b6509bda4176d008324e4ddfa55",
+  "_from": "node-firefox-uninstall-app@",
+  "_npmVersion": "2.1.6",
+  "_nodeVersion": "0.10.33",
+  "_npmUser": {
+    "name": "sole",
+    "email": "listas@soledadpenades.com"
+  },
+  "maintainers": [
+    {
+      "name": "sole",
+      "email": "listas@soledadpenades.com"
+    }
+  ],
+  "dist": {
+    "shasum": "557345a0b7f60b6509bda4176d008324e4ddfa55",
+    "tarball": "http://registry.npmjs.org/node-firefox-uninstall-app/-/node-firefox-uninstall-app-1.0.0.tgz"
+  },
+  "directories": {},
+  "_resolved": "https://registry.npmjs.org/node-firefox-uninstall-app/-/node-firefox-uninstall-app-1.0.0.tgz"
+}